From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755199AbeARIsW (ORCPT ); Thu, 18 Jan 2018 03:48:22 -0500 Received: from mail-cys01nam02on0122.outbound.protection.outlook.com ([104.47.37.122]:45078 "EHLO NAM02-CY1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1754920AbeARIsT (ORCPT ); Thu, 18 Jan 2018 03:48:19 -0500 Authentication-Results: spf=pass (sender IP is 117.103.190.44) smtp.mailfrom=sony.com; vger.kernel.org; dkim=none (message not signed) header.d=none;vger.kernel.org; dmarc=bestguesspass action=none header.from=sony.com; From: To: , , CC: , , , , , , , Subject: [PATCH v5 07/12] [media] cxd2880: Add top level of the driver Date: Thu, 18 Jan 2018 17:52:17 +0900 Message-ID: <20180118085217.21465-1-Yasunari.Takiguchi@sony.com> X-Mailer: git-send-email 2.15.1 In-Reply-To: <20180118084016.20689-1-Yasunari.Takiguchi@sony.com> References: <20180118084016.20689-1-Yasunari.Takiguchi@sony.com> MIME-Version: 1.0 Content-Type: text/plain X-Originating-IP: [43.25.41.74] X-EOPAttributedMessage: 0 X-MS-Office365-Filtering-HT: Tenant X-Forefront-Antispam-Report: CIP:117.103.190.44;IPV:NLI;CTRY:JP;EFV:NLI;SFV:NSPM;SFS:(10019020)(346002)(396003)(39860400002)(376002)(39380400002)(2980300002)(438002)(199004)(189003)(478600001)(48376002)(8676002)(2201001)(86362001)(50226002)(8936002)(49486002)(16526018)(956003)(575784001)(106002)(36756003)(51416003)(59450400001)(54906003)(53946003)(316002)(110136005)(86152003)(5660300001)(2950100002)(16586007)(246002)(5890100001)(50466002)(26005)(356003)(7636002)(66066001)(305945005)(47776003)(7736002)(107886003)(39060400002)(4326008)(72206003)(6116002)(2876002)(3846002)(1076002)(106466001)(2906002)(76176011)(559001)(579004)(414714003)(473944003);DIR:OUT;SFP:1102;SCL:1;SRVR:BN6PR1301MB2019;H:jp.sony.com;FPR:;SPF:Pass;PTR:jpyokxeg104.jp.sony.com;A:1;MX:1;LANG:en; X-Microsoft-Exchange-Diagnostics: 1;SN1NAM02FT005;1:Tya8EkJtLSIJaodOis4XidDxcpaPPYQskJb0LXf7T5NWzL0eO+QqXSd0Y0Esge3eFYCrrRDVRMRXVHIC2FYJ9poaOiwiDXJTvAtdSmSOs0z0nJRxpcbQAAqhBbxCXCyS X-MS-PublicTrafficType: Email X-MS-Office365-Filtering-Correlation-Id: a26a3d03-67ec-40f6-a6ee-08d55e5036f3 X-Microsoft-Antispam: UriScan:;BCL:0;PCL:0;RULEID:(7020095)(4652020)(5600026)(4604075)(4608076)(4534125)(4602075)(4627221)(201703031133081)(201702281549075)(2017052603307)(7193020);SRVR:BN6PR1301MB2019; X-Microsoft-Exchange-Diagnostics: 1;BN6PR1301MB2019;3:7yQe5mjGAUWoT6hIaYNfR25RW4s+zRncegyFpW5PiDDnWaOwrXeTQesYdMVwanY1r61v5SwqgDEnwYF7lOG/iNtYyh4xQrZN0mucqamdm/q6yq0SwuRHfPOCvFQghZzFU9RIld/EVzVtFz+IBsdkC/FmrhFlN6XuxL7/VDhWo0iC5eXlOpV1gbfL1RX7ljtYjbjx3uvtRVmEv/657kG6bXSxV8sR8IqtFMe4R8LhAb4SmpUC31xYWlYWtStdmL+V3NZI7XnpWHG2OymRNcyuzrwYvbvVBP7Da0AC62nCsmvCRQpjGMBTGsSFSlPZFqYocr2nq/5L5IYPFxFK6pMOAl+GPMOWYbxVzjJZtV9wvhA=;25:1Mh49fagK+wfYSR0qEUsl1HM3TKNiFxhmFQbH8F0fYyi9P1woh0bi3ersOYWxDuseSouOJRR9TbE+8XMVJQhuCrboBaDTWpsKLQPxbk3SxO+68oJ06jimD6I2PC/xrqfca59vY9Ot0VUiVJp7SvxeVzkN0damEVFBPO9s+GdIO99HjmkDpIJA7Eee8xM9dd5RnONkjYsJe2pRXbiKcXUYVvBxNQk2QeS4TUyWIdnirwZoJe2/BEkKi0N8hWLVx5ahLdh43GBySd0ejFhfQ+V/cibmboALH25i/zjdfEyoVGI6ARyhKD/QUghCe716/UAOT2WHvMMlj7uV1JdtBLjzw== X-MS-TrafficTypeDiagnostic: BN6PR1301MB2019: X-Microsoft-Exchange-Diagnostics: 1;BN6PR1301MB2019;31:zKuBeO0IvN0qYk2VrVDWrbemcAaZE3nhzCw/1lB6nU01JC6FqNIwx4gN4SAbg56RzK89dQHTf5juQZu0Dbx25/YqU/Qeb3eX6+bpgS7dvBy42Etsz3lvDctnmXHjPHLcZ/oLfV9Eq6+cIdkKDAxSpnZ/zk3/XAiBe7f9mubAta3aII4aSxbZ2zxuJsNL9REX4cdx6K3v32f5LPooECsWegBgUNMva16oi60U69g1FME=;20:8twhqZDCRafRNgaZTZBhh6vjQXSyZSrR8o7ruoq0ICkrAvpNfQYLb36VDflk0roEIANuIeQsxRnlnA/MnJZ68dh4v78ehoPVsBGJSXQQXvVoA8xvKGrV/+gZsC+N3zGSSMIdSt8f4OKURoMCW8VGMhwANRvta1AIfJ2XJ3j0oSj4vGevtv/eOaTN8Wx3kyx/P/ARQIA53DfUcn35NrJLFM3qx6WgVPc2oq1Qo1QMU6aorFbwXsB977+UoKAD99h+5371mRZFMbA4Dp2cj5j7kUeKRiNxADgpdzYXwHwmXVBaMrUgHARAsa5vykFo8hDEL5yGcK64CpKYb49ji67bT0sm5dZwIM58/euGaz0qHPRfaCMxC4O223pYH0dHoIobyPDSJqY9StcX8xYPgW8ZcpfDJgFEZ+9KCDszL/d/cfmSjSQOb5tGjzAZZG+v/w7FdLd9Cr5qde0Aa5+G+pRcHwAuQEwsSkQJpBTDj0MvjuBASArucJ+63MUurR/uHYWf X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:(182409339516656); X-Exchange-Antispam-Report-CFA-Test: BCL:0;PCL:0;RULEID:(6040470)(2401047)(8121501046)(5005006)(3002001)(10201501046)(93006095)(93004095)(3231023)(11241501184)(944501161)(6055026)(6041268)(201703131423095)(201702281528075)(20161123555045)(201703061421075)(201703061406153)(20161123558120)(20161123560045)(20161123564045)(20161123562045)(6072148)(201708071742011);SRVR:BN6PR1301MB2019;BCL:0;PCL:0;RULEID:(100000803101)(100110400095);SRVR:BN6PR1301MB2019; X-Microsoft-Exchange-Diagnostics: 1;BN6PR1301MB2019;4:A3rkex7co0nzRfUVPUCeSOwE79Umg7Nu+AHUtMsSFLwE9+YtFu9vzhnfpdk3mTJjRfpbqkoz4yAQ5ftxI78KkZpLqbvKgAOHPBq5fPOpBgX1imGQkWGhpmNJ390bn/URu6kk04fDQ11620uySWWI7kXgn/Nhi7NgLyWcSs0rjjXJstGAiFrj3cYFDMVx9T8GLhTjjDl+r3oCjd6ivCrHzB4/lMbCoS412FUCkOCFC9XRV1XE+O23jcWIgFNwLSbDD22uAUCQ6OISGYTXUsNwbB1HzS7hGNAeslDhoQYGCy85Uxb3nuHA83l5rIvOI0Th X-Forefront-PRVS: 05568D1FF7 X-Microsoft-Exchange-Diagnostics: =?us-ascii?Q?1;BN6PR1301MB2019;23:W9mKe/UIofSzu6Rew8JnjWWgfQTxLJF5xnhCt95?= =?us-ascii?Q?3qtl8njfTjgDgker2eoj3qtgHt2qkQra9BOoiwyGM0jjVakL6jg5NI8a0vnE?= =?us-ascii?Q?v0Zy6zwLbUbKsGklZVcL5G/5yI8u0QtJIT/ZmvD3z1lucjtAowaD2O5MLdNJ?= =?us-ascii?Q?PcHT3N3/MPxyVo00fdpsS733DP1Sk9CzfmRQ2+yVh1cMi1PZv0iq2j9+W45e?= =?us-ascii?Q?GhnT4kElpuf5Hjbm78zT4g+8IJvux7UrCxuC2NSUjqkNrO+wwB/WzRtdSwkT?= =?us-ascii?Q?QXNrUCd5TCcEPMC9/H+P4VTDjAaBmi5i9CmcAlkzcQY+oRu6hs7pa58EhHxu?= =?us-ascii?Q?8OyHRVICAismwz1N+ZNp3VC+oS7jEPCysJEDG3dvaaoYOM7Rh/BG4Fw9+Jhk?= =?us-ascii?Q?NMab3oINZqBFA61O6zi01iTofsrYo9nQ6n0HSlg4j+cdQFLnFacyP9j1XUqO?= =?us-ascii?Q?zG1WxEcGnw7t2mnEM0EHMCD8+mwCvwzkgV/gBm0pwlDe3TnCPIE88vjFst7Z?= =?us-ascii?Q?WJpt6McVn2Yd4QFVGYTRIE2uKgGMJnnDA0N4tEwGn4y3fhmBM3w0lE/AcQDN?= =?us-ascii?Q?URgcZpGEmnqxvjRxsNtQrS5rH1VE5P10SicmghEwFYCtQUmYAuSuAZhMiaXx?= =?us-ascii?Q?hKKUCi9pX1abEkAEPXbVuQavQXuLH9n8O9+MS0C8oS3I+uUz3D3VGMnNCI2p?= =?us-ascii?Q?TLX3kXY5vggqepxhsqnlBsWavpqabygH3qFdlvJUHDshYKRCQd5fVlOZKH/c?= =?us-ascii?Q?vjtvJvrWb7I+cPh7rk7Xi7bW4SvtZ5nwqc3D4pBsAo4USD5VqYqwLigiWZ+q?= =?us-ascii?Q?K0RIFlzkbasHnzv5XggueE937ZrSAl4DQw/qr5i6QMk2l4FKtHVomHtVcR5o?= =?us-ascii?Q?4ddSCq0K0wYXdvLHH9yIMLhIq9Sl8Jti8PJbpaDHq+1sF2u+Saar4R5Jm6qn?= =?us-ascii?Q?NUZ7huol2lAMdC6Upqyydu7m2m+7czYQjPmnzfFak2RIICmG6urMtHNg4n2v?= =?us-ascii?Q?rVQhxJLD55yigNwdH7ishE9ST75Fl9AIVLK7S+9AM0LslxDXOJtpkM4j6s9H?= =?us-ascii?Q?pSPsnrHMu2zP96AjnBgzc2QD5Tx31p76sIsb1aLWhUfk3aoy2W7/aFKpWrlR?= =?us-ascii?Q?IvgEsrlEVdyFUJVkMXYLhClVqHKNg0obbnabGSykcqJg12gWYfJD6z36Sg1r?= =?us-ascii?Q?zqn+aamPB8/gf8x8vUBQioMlb8f1AdfXJzQiUhCxAPsDUdmVDxXsfapzZzvd?= =?us-ascii?Q?dkBIXnBQPzriaZKG2bWFsQTfOn2gYa4ru/qwJV6LkJwl9K8C0a5lWeofs9/9?= =?us-ascii?Q?Vrw=3D=3D?= X-Microsoft-Exchange-Diagnostics: 1;BN6PR1301MB2019;6:ZOs3I3cBfAgnV/uWudOt4ZmSh1+MtoudR/0BEtsAt7AtIt5NLp90r+MS93KBxgy6jbI2HiwRjlR9HLRb6cWqvsUk+OS964MIGXroBHiG6v1ZlL/94ady6OyPgwyacOMRVtFJ2JaR+5qVeFTFCTn+DUgr+ArF9gSABF1U+t9IJjjbjPrJLeDMnLNEoH9PaIb3SPQNqvvbuScLCrkt3UZDqIRtgOi4t6fLM1WXnVtF49T3oBP9NVhINnK4RbNjhj+LT0Igypj3kWU9sPw+3m2iqlRTrLXs+MMpDHaEPkfVQUjB8+g3kdqdSLVoGoGM8nTLpbl4jb/EyZv3sFHmB5XVgomJVkDvRwTlRyBS7nJ00yc=;5:rF8TTrdKpYNdCrEn/OcvSOkYBZ7nHlltwCoXdSc5NiydOUr3sCV+wf0EF7RYWVyBhmE3KB3ceS6PLEI3V5+w5CNJXtie0E0snopXj8cCxPWy20T20giH0g/3LIe9xC6NMd2y870uf85ZF7WI9HcBt4QhTYquX1zThG3h1Hj8fOg=;24:G/QD7EDRdcwhqlQCR398vK5FUFzNX8xtf/iaxzAQqbqkj2tDLN3JVDizb9UIblZN+iVn0oqzolthkzTdedbi13PCJpe+fHpVDfNGcOKtHz8=;7:Qxzq83WFtDJ/LHR5iGKDEH1WTjtq3oZiWXxERSfWr3d8vFxaSAbapp/I2zjwbu5YB0cvM2h26YHhlJRuy88HHu0RscFFgZrleraFdBPbhbd5SJFlWpOwi/L99nKsjjHrof99z8VFPH5EyVoMWBNh/L27dn8f9j/YKRApCeAbIwSNst04NuiFhon+lWAqxTbBn9nCjE1rm2ecfvLVCQMsZBRoowwYkqdNVcgdAktK/IrUN+nNXKdOyPB3FHkUAAdB SpamDiagnosticOutput: 1:99 SpamDiagnosticMetadata: NSPM X-OriginatorOrg: sony.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 18 Jan 2018 08:48:14.9187 (UTC) X-MS-Exchange-CrossTenant-Network-Message-Id: a26a3d03-67ec-40f6-a6ee-08d55e5036f3 X-MS-Exchange-CrossTenant-Id: 66c65d8a-9158-4521-a2d8-664963db48e4 X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp: TenantId=66c65d8a-9158-4521-a2d8-664963db48e4;Ip=[117.103.190.44];Helo=[jp.sony.com] X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem X-MS-Exchange-Transport-CrossTenantHeadersStamped: BN6PR1301MB2019 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Yasunari Takiguchi This provides the main dvb frontend operation functions for the Sony CXD2880 DVB-T2/T tuner + demodulator driver. Signed-off-by: Yasunari Takiguchi Signed-off-by: Masayuki Yamamoto Signed-off-by: Hideki Nozawa Signed-off-by: Kota Yonezawa Signed-off-by: Toshihiko Matsumoto Signed-off-by: Satoshi Watanabe --- [Change list] Changes in V5 Using SPDX-License-Identifier drivers/media/dvb-frontends/cxd2880/cxd2880_top.c -changed define position of cxd2880_dvbt_t2_ops -modified typo about "drvier" -> "driver" -removed unnecessary cast -removed unnecessary if() -modified return error code -removed unnecessary parentheses -modified for "Lines should not end with a '(' " -modified to return constant 0 from read_ber function Changes in V4 drivers/media/dvb-frontends/cxd2880/cxd2880_top.c -modified typo "inavlid" to "invalid" at pr_err -removed unnecessary initialization at variable declaration -removed unnecessary brace {} -changed to use cxd2880_dvbt_tune and cxd2880_dvbt2_tune instead of cxd2880_integ_dvbt_tune and cxd2880_integ_dvbt2_tune (because we changed it so that demodulator does not wait for locking the signal.) Changes in V3 drivers/media/dvb-frontends/cxd2880/cxd2880_top.c -adjusted indent spaces -modified debugging code -removed unnecessary cast -modified return code -modified coding style of if() -modified about measurement period of PER/BER. -changed hexadecimal code to lower case. drivers/media/dvb-frontends/cxd2880/cxd2880_top.c | 1954 +++++++++++++++++++++ 1 file changed, 1954 insertions(+) create mode 100644 drivers/media/dvb-frontends/cxd2880/cxd2880_top.c diff --git a/drivers/media/dvb-frontends/cxd2880/cxd2880_top.c b/drivers/media/dvb-frontends/cxd2880/cxd2880_top.c new file mode 100644 index 000000000000..f109e9d98cc0 --- /dev/null +++ b/drivers/media/dvb-frontends/cxd2880/cxd2880_top.c @@ -0,0 +1,1954 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * cxd2880_top.c + * Sony CXD2880 DVB-T2/T tuner + demodulator driver + * + * Copyright (C) 2016, 2017, 2018 Sony Semiconductor Solutions Corporation + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__ + +#include + +#include "dvb_frontend.h" +#include "dvb_math.h" + +#include "cxd2880.h" +#include "cxd2880_tnrdmd_mon.h" +#include "cxd2880_tnrdmd_dvbt2_mon.h" +#include "cxd2880_tnrdmd_dvbt_mon.h" +#include "cxd2880_integ.h" +#include "cxd2880_tnrdmd_dvbt2.h" +#include "cxd2880_tnrdmd_dvbt.h" +#include "cxd2880_devio_spi.h" +#include "cxd2880_spi_device.h" +#include "cxd2880_tnrdmd_driver_version.h" + +struct cxd2880_priv { + struct cxd2880_tnrdmd tnrdmd; + struct spi_device *spi; + struct cxd2880_io regio; + struct cxd2880_spi_device spi_device; + struct cxd2880_spi cxd2880_spi; + struct cxd2880_dvbt_tune_param dvbt_tune_param; + struct cxd2880_dvbt2_tune_param dvbt2_tune_param; + struct mutex *spi_mutex; /* For SPI access exclusive control */ + unsigned long pre_ber_update; + unsigned long pre_ber_interval; + unsigned long post_ber_update; + unsigned long post_ber_interval; + unsigned long ucblock_update; + unsigned long ucblock_interval; + enum fe_status s; +}; + +static int cxd2880_pre_bit_err_t(struct cxd2880_tnrdmd *tnrdmd, + u32 *pre_bit_err, u32 *pre_bit_count) +{ + u8 rdata[2]; + int ret; + + if (!tnrdmd || !pre_bit_err || !pre_bit_count) + return -EINVAL; + + if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB) + return -EINVAL; + + if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE) + return -EINVAL; + + if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT) + return -EINVAL; + + ret = slvt_freeze_reg(tnrdmd); + if (ret) + return ret; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x10); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x39, rdata, 1); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + if ((rdata[0] & 0x01) == 0) { + slvt_unfreeze_reg(tnrdmd); + return -EAGAIN; + } + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x22, rdata, 2); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + *pre_bit_err = (rdata[0] << 8) | rdata[1]; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x6f, rdata, 1); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + slvt_unfreeze_reg(tnrdmd); + + *pre_bit_count = ((rdata[0] & 0x07) == 0) ? + 256 : (0x1000 << (rdata[0] & 0x07)); + + return 0; +} + +static int cxd2880_pre_bit_err_t2(struct cxd2880_tnrdmd *tnrdmd, + u32 *pre_bit_err, + u32 *pre_bit_count) +{ + u32 period_exp = 0; + u32 n_ldpc = 0; + u8 data[5]; + int ret; + + if (!tnrdmd || !pre_bit_err || !pre_bit_count) + return -EINVAL; + + if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB) + return -EINVAL; + + if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE) + return -EINVAL; + + if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT2) + return -EINVAL; + + ret = slvt_freeze_reg(tnrdmd); + if (ret) + return ret; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x0b); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x3c, data, sizeof(data)); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + if (!(data[0] & 0x01)) { + slvt_unfreeze_reg(tnrdmd); + return -EAGAIN; + } + *pre_bit_err = + ((data[1] & 0x0f) << 24) | (data[2] << 16) | (data[3] << 8) | data[4]; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0xa0, data, 1); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + if (((enum cxd2880_dvbt2_plp_fec)(data[0] & 0x03)) == + CXD2880_DVBT2_FEC_LDPC_16K) + n_ldpc = 16200; + else + n_ldpc = 64800; + slvt_unfreeze_reg(tnrdmd); + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x20); + if (ret) + return ret; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x6f, data, 1); + if (ret) + return ret; + + period_exp = data[0] & 0x0f; + + *pre_bit_count = (1U << period_exp) * n_ldpc; + + return 0; +} + +static int cxd2880_post_bit_err_t(struct cxd2880_tnrdmd *tnrdmd, + u32 *post_bit_err, + u32 *post_bit_count) +{ + u8 rdata[3]; + u32 bit_error = 0; + u32 period_exp = 0; + int ret; + + if (!tnrdmd || !post_bit_err || !post_bit_count) + return -EINVAL; + + if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB) + return -EINVAL; + + if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE) + return -EINVAL; + + if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT) + return -EINVAL; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x0d); + if (ret) + return ret; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x15, rdata, 3); + if (ret) + return ret; + + if ((rdata[0] & 0x40) == 0) + return -EAGAIN; + + *post_bit_err = ((rdata[0] & 0x3f) << 16) | (rdata[1] << 8) | rdata[2]; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x10); + if (ret) + return ret; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x60, rdata, 1); + if (ret) + return ret; + + period_exp = (rdata[0] & 0x1f); + + if (period_exp <= 11 && (bit_error > (1U << period_exp) * 204 * 8)) + return -EAGAIN; + + *post_bit_count = (1U << period_exp) * 204 * 8; + + return 0; +} + +static int cxd2880_post_bit_err_t2(struct cxd2880_tnrdmd *tnrdmd, + u32 *post_bit_err, + u32 *post_bit_count) +{ + u32 period_exp = 0; + u32 n_bch = 0; + u8 data[3]; + enum cxd2880_dvbt2_plp_fec plp_fec_type = + CXD2880_DVBT2_FEC_LDPC_16K; + enum cxd2880_dvbt2_plp_code_rate plp_code_rate = + CXD2880_DVBT2_R1_2; + int ret; + static const u16 n_bch_bits_lookup[2][8] = { + {7200, 9720, 10800, 11880, 12600, 13320, 5400, 6480}, + {32400, 38880, 43200, 48600, 51840, 54000, 21600, 25920} + }; + + if (!tnrdmd || !post_bit_err || !post_bit_count) + return -EINVAL; + + if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB) + return -EINVAL; + + if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE) + return -EINVAL; + + if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT2) + return -EINVAL; + + ret = slvt_freeze_reg(tnrdmd); + if (ret) + return ret; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x0b); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x15, data, 3); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + if (!(data[0] & 0x40)) { + slvt_unfreeze_reg(tnrdmd); + return -EAGAIN; + } + + *post_bit_err = + ((data[0] & 0x3f) << 16) | (data[1] << 8) | data[2]; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x9d, data, 1); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + plp_code_rate = + (enum cxd2880_dvbt2_plp_code_rate)(data[0] & 0x07); + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0xa0, data, 1); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + plp_fec_type = (enum cxd2880_dvbt2_plp_fec)(data[0] & 0x03); + + slvt_unfreeze_reg(tnrdmd); + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x20); + if (ret) + return ret; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x72, data, 1); + if (ret) + return ret; + + period_exp = data[0] & 0x0f; + + if (plp_fec_type > CXD2880_DVBT2_FEC_LDPC_64K || + plp_code_rate > CXD2880_DVBT2_R2_5) + return -EAGAIN; + + n_bch = n_bch_bits_lookup[plp_fec_type][plp_code_rate]; + + if (*post_bit_err > ((1U << period_exp) * n_bch)) + return -EAGAIN; + + *post_bit_count = (1U << period_exp) * n_bch; + + return 0; +} + +static int cxd2880_read_block_err_t(struct cxd2880_tnrdmd *tnrdmd, + u32 *block_err, + u32 *block_count) +{ + u8 rdata[3]; + int ret; + + if (!tnrdmd || !block_err || !block_count) + return -EINVAL; + + if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB) + return -EINVAL; + + if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE) + return -EINVAL; + + if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT) + return -EINVAL; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x0d); + if (ret) + return ret; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x18, rdata, 3); + if (ret) + return ret; + + if ((rdata[0] & 0x01) == 0) + return -EAGAIN; + + *block_err = (rdata[1] << 8) | rdata[2]; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x10); + if (ret) + return ret; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x5c, rdata, 1); + if (ret) + return ret; + + *block_count = 1U << (rdata[0] & 0x0f); + + if ((*block_count == 0) || (*block_err > *block_count)) + return -EAGAIN; + + return 0; +} + +static int cxd2880_read_block_err_t2(struct cxd2880_tnrdmd *tnrdmd, + u32 *block_err, + u32 *block_count) +{ + u8 rdata[3]; + int ret; + + if (!tnrdmd || !block_err || !block_count) + return -EINVAL; + + if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB) + return -EINVAL; + + if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE) + return -EINVAL; + if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT2) + return -EINVAL; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x0b); + if (ret) + return ret; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x18, rdata, 3); + if (ret) + return ret; + + if ((rdata[0] & 0x01) == 0) + return -EAGAIN; + + *block_err = (rdata[1] << 8) | rdata[2]; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x24); + if (ret) + return ret; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0xdc, rdata, 1); + if (ret) + return ret; + + *block_count = 1U << (rdata[0] & 0x0f); + + if ((*block_count == 0) || (*block_err > *block_count)) + return -EAGAIN; + + return 0; +} + +static void cxd2880_release(struct dvb_frontend *fe) +{ + struct cxd2880_priv *priv = NULL; + + if (!fe) { + pr_err("invalid arg.\n"); + return; + } + priv = fe->demodulator_priv; + kfree(priv); +} + +static int cxd2880_init(struct dvb_frontend *fe) +{ + int ret; + struct cxd2880_priv *priv = NULL; + struct cxd2880_tnrdmd_create_param create_param; + + if (!fe) { + pr_err("invalid arg.\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + + create_param.ts_output_if = CXD2880_TNRDMD_TSOUT_IF_SPI; + create_param.xtal_share_type = CXD2880_TNRDMD_XTAL_SHARE_NONE; + create_param.en_internal_ldo = 1; + create_param.xosc_cap = 18; + create_param.xosc_i = 8; + create_param.stationary_use = 1; + + mutex_lock(priv->spi_mutex); + if (priv->tnrdmd.io != &priv->regio) { + ret = cxd2880_tnrdmd_create(&priv->tnrdmd, + &priv->regio, &create_param); + if (ret) { + mutex_unlock(priv->spi_mutex); + pr_info("cxd2880 tnrdmd create failed %d\n", ret); + return ret; + } + } + ret = cxd2880_integ_init(&priv->tnrdmd); + if (ret) { + mutex_unlock(priv->spi_mutex); + pr_err("cxd2880 integ init failed %d\n", ret); + return ret; + } + mutex_unlock(priv->spi_mutex); + + pr_debug("OK.\n"); + + return ret; +} + +static int cxd2880_sleep(struct dvb_frontend *fe) +{ + int ret; + struct cxd2880_priv *priv = NULL; + + if (!fe) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_sleep(&priv->tnrdmd); + mutex_unlock(priv->spi_mutex); + + pr_debug("tnrdmd_sleep ret %d\n", ret); + + return ret; +} + +static int cxd2880_read_signal_strength(struct dvb_frontend *fe, + u16 *strength) +{ + int ret; + struct cxd2880_priv *priv = NULL; + struct dtv_frontend_properties *c = NULL; + int level = 0; + + if (!fe || !strength) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + c = &fe->dtv_property_cache; + + mutex_lock(priv->spi_mutex); + if (c->delivery_system == SYS_DVBT || + c->delivery_system == SYS_DVBT2) { + ret = cxd2880_tnrdmd_mon_rf_lvl(&priv->tnrdmd, &level); + } else { + pr_debug("invalid system\n"); + mutex_unlock(priv->spi_mutex); + return -EINVAL; + } + mutex_unlock(priv->spi_mutex); + + level /= 125; + /* + * level should be between -105dBm and -30dBm. + * E.g. they should be between: + * -105000/125 = -840 and -30000/125 = -240 + */ + level = clamp(level, -840, -240); + /* scale value to 0x0000-0xffff */ + *strength = ((level + 840) * 0xffff) / (-240 + 840); + + if (ret) + pr_debug("ret = %d\n", ret); + + return ret; +} + +static int cxd2880_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + int ret; + int snrvalue = 0; + struct cxd2880_priv *priv = NULL; + struct dtv_frontend_properties *c = NULL; + + if (!fe || !snr) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + c = &fe->dtv_property_cache; + + mutex_lock(priv->spi_mutex); + if (c->delivery_system == SYS_DVBT) { + ret = cxd2880_tnrdmd_dvbt_mon_snr(&priv->tnrdmd, + &snrvalue); + } else if (c->delivery_system == SYS_DVBT2) { + ret = cxd2880_tnrdmd_dvbt2_mon_snr(&priv->tnrdmd, + &snrvalue); + } else { + pr_err("invalid system\n"); + mutex_unlock(priv->spi_mutex); + return -EINVAL; + } + mutex_unlock(priv->spi_mutex); + + if (snrvalue < 0) + snrvalue = 0; + *snr = snrvalue; + + if (ret) + pr_debug("ret = %d\n", ret); + + return ret; +} + +static int cxd2880_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + int ret; + struct cxd2880_priv *priv = NULL; + struct dtv_frontend_properties *c = NULL; + + if (!fe || !ucblocks) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + c = &fe->dtv_property_cache; + + mutex_lock(priv->spi_mutex); + if (c->delivery_system == SYS_DVBT) { + ret = cxd2880_tnrdmd_dvbt_mon_packet_error_number(&priv->tnrdmd, + ucblocks); + } else if (c->delivery_system == SYS_DVBT2) { + ret = cxd2880_tnrdmd_dvbt2_mon_packet_error_number(&priv->tnrdmd, + ucblocks); + } else { + pr_err("invalid system\n"); + mutex_unlock(priv->spi_mutex); + return -EINVAL; + } + mutex_unlock(priv->spi_mutex); + + if (ret) + pr_debug("ret = %d\n", ret); + + return ret; +} + +static int cxd2880_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + *ber = 0; + + return 0; +} + +static int cxd2880_set_ber_per_period_t(struct dvb_frontend *fe) +{ + int ret; + struct dtv_frontend_properties *c; + struct cxd2880_priv *priv; + struct cxd2880_dvbt_tpsinfo info; + enum cxd2880_dtv_bandwidth bw = CXD2880_DTV_BW_1_7_MHZ; + u32 pre_ber_rate = 0; + u32 post_ber_rate = 0; + u32 ucblock_rate = 0; + u32 mes_exp = 0; + static const int cr_table[5] = {31500, 42000, 47250, 52500, 55125}; + static const int denominator_tbl[4] = {125664, 129472, 137088, 152320}; + + if (!fe) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + c = &fe->dtv_property_cache; + bw = priv->dvbt_tune_param.bandwidth; + + ret = cxd2880_tnrdmd_dvbt_mon_tps_info(&priv->tnrdmd, + &info); + if (ret) { + pr_err("tps monitor error ret = %d\n", ret); + info.hierarchy = CXD2880_DVBT_HIERARCHY_NON; + info.constellation = CXD2880_DVBT_CONSTELLATION_QPSK; + info.guard = CXD2880_DVBT_GUARD_1_4; + info.rate_hp = CXD2880_DVBT_CODERATE_1_2; + info.rate_lp = CXD2880_DVBT_CODERATE_1_2; + } + + if (info.hierarchy == CXD2880_DVBT_HIERARCHY_NON) { + pre_ber_rate = 63000000 * bw * (info.constellation * 2 + 2) / + denominator_tbl[info.guard]; + + post_ber_rate = 1000 * cr_table[info.rate_hp] * bw * + (info.constellation * 2 + 2) / + denominator_tbl[info.guard]; + + ucblock_rate = 875 * cr_table[info.rate_hp] * bw * + (info.constellation * 2 + 2) / + denominator_tbl[info.guard]; + } else { + u8 data = 0; + struct cxd2880_tnrdmd *tnrdmd = &priv->tnrdmd; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x10); + if (!ret) { + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x67, &data, 1); + if (ret) + data = 0x00; + } else { + data = 0x00; + } + + if (data & 0x01) { /* Low priority */ + pre_ber_rate = + 63000000 * bw * (info.constellation * 2 + 2) / + denominator_tbl[info.guard]; + + post_ber_rate = 1000 * cr_table[info.rate_lp] * bw * + (info.constellation * 2 + 2) / + denominator_tbl[info.guard]; + + ucblock_rate = (1000 * 7 / 8) * cr_table[info.rate_lp] * + bw * (info.constellation * 2 + 2) / + denominator_tbl[info.guard]; + } else { /* High priority */ + pre_ber_rate = + 63000000 * bw * 2 / denominator_tbl[info.guard]; + + post_ber_rate = 1000 * cr_table[info.rate_hp] * bw * 2 / + denominator_tbl[info.guard]; + + ucblock_rate = (1000 * 7 / 8) * cr_table[info.rate_hp] * + bw * 2 / denominator_tbl[info.guard]; + } + } + + mes_exp = pre_ber_rate < 8192 ? 8 : intlog2(pre_ber_rate) >> 24; + priv->pre_ber_interval = + ((1U << mes_exp) * 1000 + (pre_ber_rate / 2)) / + pre_ber_rate; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT_VBER_PERIOD, + mes_exp == 8 ? 0 : mes_exp - 12); + + mes_exp = intlog2(post_ber_rate) >> 24; + priv->post_ber_interval = + ((1U << mes_exp) * 1000 + (post_ber_rate / 2)) / + post_ber_rate; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT_BERN_PERIOD, + mes_exp); + + mes_exp = intlog2(ucblock_rate) >> 24; + priv->ucblock_interval = + ((1U << mes_exp) * 1000 + (ucblock_rate / 2)) / + ucblock_rate; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT_PER_MES, + mes_exp); + + return 0; +} + +static int cxd2880_set_ber_per_period_t2(struct dvb_frontend *fe) +{ + int ret; + struct dtv_frontend_properties *c; + struct cxd2880_priv *priv; + struct cxd2880_dvbt2_l1pre l1pre; + struct cxd2880_dvbt2_l1post l1post; + struct cxd2880_dvbt2_plp plp; + struct cxd2880_dvbt2_bbheader bbheader; + enum cxd2880_dtv_bandwidth bw = CXD2880_DTV_BW_1_7_MHZ; + u32 pre_ber_rate = 0; + u32 post_ber_rate = 0; + u32 ucblock_rate = 0; + u32 mes_exp = 0; + u32 term_a = 0; + u32 term_b = 0; + u32 denominator = 0; + static const u32 gi_tbl[7] = {32, 64, 128, 256, 8, 152, 76}; + static const u8 n_tbl[6] = {8, 2, 4, 16, 1, 1}; + static const u8 mode_tbl[6] = {2, 8, 4, 1, 16, 32}; + static const u32 kbch_tbl[2][8] = { + {6952, 9472, 10552, 11632, 12352, 13072, 5152, 6232}, + {32128, 38608, 42960, 48328, 51568, 53760, 0, 0} + }; + + if (!fe) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + c = &fe->dtv_property_cache; + bw = priv->dvbt2_tune_param.bandwidth; + + ret = cxd2880_tnrdmd_dvbt2_mon_l1_pre(&priv->tnrdmd, &l1pre); + if (ret) { + pr_info("l1 pre error\n"); + goto error_ber_setting; + } + + ret = cxd2880_tnrdmd_dvbt2_mon_active_plp(&priv->tnrdmd, + CXD2880_DVBT2_PLP_DATA, &plp); + if (ret) { + pr_info("plp info error\n"); + goto error_ber_setting; + } + + ret = cxd2880_tnrdmd_dvbt2_mon_l1_post(&priv->tnrdmd, &l1post); + if (ret) { + pr_info("l1 post error\n"); + goto error_ber_setting; + } + + term_a = + (mode_tbl[l1pre.fft_mode] * (1024 + gi_tbl[l1pre.gi])) * + (l1pre.num_symbols + n_tbl[l1pre.fft_mode]) + 2048; + + if (l1pre.mixed && l1post.fef_intvl) { + term_b = (l1post.fef_length + (l1post.fef_intvl / 2)) / + l1post.fef_intvl; + } else { + term_b = 0; + } + + switch (bw) { + case CXD2880_DTV_BW_1_7_MHZ: + denominator = ((term_a + term_b) * 71 + (131 / 2)) / 131; + break; + case CXD2880_DTV_BW_5_MHZ: + denominator = ((term_a + term_b) * 7 + 20) / 40; + break; + case CXD2880_DTV_BW_6_MHZ: + denominator = ((term_a + term_b) * 7 + 24) / 48; + break; + case CXD2880_DTV_BW_7_MHZ: + denominator = ((term_a + term_b) + 4) / 8; + break; + case CXD2880_DTV_BW_8_MHZ: + default: + denominator = ((term_a + term_b) * 7 + 32) / 64; + break; + } + + if (plp.til_type && plp.til_len) { + pre_ber_rate = + (plp.num_blocks_max * 1000000 + (denominator / 2)) / + denominator; + pre_ber_rate = (pre_ber_rate + (plp.til_len / 2)) / + plp.til_len; + } else { + pre_ber_rate = + (plp.num_blocks_max * 1000000 + (denominator / 2)) / + denominator; + } + + post_ber_rate = pre_ber_rate; + + mes_exp = intlog2(pre_ber_rate) >> 24; + priv->pre_ber_interval = + ((1U << mes_exp) * 1000 + (pre_ber_rate / 2)) / + pre_ber_rate; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT2_LBER_MES, + mes_exp); + + mes_exp = intlog2(post_ber_rate) >> 24; + priv->post_ber_interval = + ((1U << mes_exp) * 1000 + (post_ber_rate / 2)) / + post_ber_rate; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT2_BBER_MES, + mes_exp); + + ret = cxd2880_tnrdmd_dvbt2_mon_bbheader(&priv->tnrdmd, + CXD2880_DVBT2_PLP_DATA, + &bbheader); + if (ret) { + pr_info("bb header error\n"); + goto error_ucblock_setting; + } + + if (bbheader.plp_mode == CXD2880_DVBT2_PLP_MODE_NM) { + if (!bbheader.issy_indicator) { + ucblock_rate = + (pre_ber_rate * kbch_tbl[plp.fec][plp.plp_cr] + + 752) / 1504; + } else { + ucblock_rate = + (pre_ber_rate * kbch_tbl[plp.fec][plp.plp_cr] + + 764) / 1528; + } + } else if (bbheader.plp_mode == CXD2880_DVBT2_PLP_MODE_HEM) { + ucblock_rate = + (pre_ber_rate * kbch_tbl[plp.fec][plp.plp_cr] + 748) / + 1496; + } else { + pr_info("plp mode is not Normal or HEM\n"); + goto error_ucblock_setting; + } + + mes_exp = intlog2(ucblock_rate) >> 24; + priv->ucblock_interval = + ((1U << mes_exp) * 1000 + (ucblock_rate / 2)) / + ucblock_rate; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT2_PER_MES, + mes_exp); + + return 0; + +error_ber_setting: + priv->pre_ber_interval = 1000; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT2_LBER_MES, 0); + + priv->post_ber_interval = 1000; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT2_BBER_MES, 0); + +error_ucblock_setting: + priv->ucblock_interval = 1000; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT2_PER_MES, 8); + + return 0; +} + +static int cxd2880_dvbt_tune(struct cxd2880_tnrdmd *tnr_dmd, + struct cxd2880_dvbt_tune_param + *tune_param) +{ + int ret; + + if (!tnr_dmd || !tune_param) + return -EINVAL; + + if (tnr_dmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB) + return -EINVAL; + + if (tnr_dmd->state != CXD2880_TNRDMD_STATE_SLEEP && + tnr_dmd->state != CXD2880_TNRDMD_STATE_ACTIVE) + return -EINVAL; + + atomic_set(&tnr_dmd->cancel, 0); + + if (tune_param->bandwidth != CXD2880_DTV_BW_5_MHZ && + tune_param->bandwidth != CXD2880_DTV_BW_6_MHZ && + tune_param->bandwidth != CXD2880_DTV_BW_7_MHZ && + tune_param->bandwidth != CXD2880_DTV_BW_8_MHZ) { + return -ENOTTY; + } + + ret = cxd2880_tnrdmd_dvbt_tune1(tnr_dmd, tune_param); + if (ret) + return ret; + + usleep_range(CXD2880_TNRDMD_WAIT_AGC_STABLE * 10000, + CXD2880_TNRDMD_WAIT_AGC_STABLE * 10000 + 1000); + + return cxd2880_tnrdmd_dvbt_tune2(tnr_dmd, tune_param); +} + +static int cxd2880_dvbt2_tune(struct cxd2880_tnrdmd *tnr_dmd, + struct cxd2880_dvbt2_tune_param + *tune_param) +{ + int ret; + + if (!tnr_dmd || !tune_param) + return -EINVAL; + + if (tnr_dmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB) + return -EINVAL; + + if (tnr_dmd->state != CXD2880_TNRDMD_STATE_SLEEP && + tnr_dmd->state != CXD2880_TNRDMD_STATE_ACTIVE) + return -EINVAL; + + atomic_set(&tnr_dmd->cancel, 0); + + if (tune_param->bandwidth != CXD2880_DTV_BW_1_7_MHZ && + tune_param->bandwidth != CXD2880_DTV_BW_5_MHZ && + tune_param->bandwidth != CXD2880_DTV_BW_6_MHZ && + tune_param->bandwidth != CXD2880_DTV_BW_7_MHZ && + tune_param->bandwidth != CXD2880_DTV_BW_8_MHZ) { + return -ENOTTY; + } + + if (tune_param->profile != CXD2880_DVBT2_PROFILE_BASE && + tune_param->profile != CXD2880_DVBT2_PROFILE_LITE) + return -EINVAL; + + ret = cxd2880_tnrdmd_dvbt2_tune1(tnr_dmd, tune_param); + if (ret) + return ret; + + usleep_range(CXD2880_TNRDMD_WAIT_AGC_STABLE * 10000, + CXD2880_TNRDMD_WAIT_AGC_STABLE * 10000 + 1000); + + return cxd2880_tnrdmd_dvbt2_tune2(tnr_dmd, tune_param); +} + +static int cxd2880_set_frontend(struct dvb_frontend *fe) +{ + int ret; + struct dtv_frontend_properties *c; + struct cxd2880_priv *priv; + enum cxd2880_dtv_bandwidth bw = CXD2880_DTV_BW_1_7_MHZ; + + if (!fe) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + c = &fe->dtv_property_cache; + + c->pre_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->pre_bit_error.stat[0].uvalue = 0; + c->pre_bit_error.len = 1; + c->pre_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->pre_bit_count.stat[0].uvalue = 0; + c->pre_bit_count.len = 1; + c->post_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_error.stat[0].uvalue = 0; + c->post_bit_error.len = 1; + c->post_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_count.stat[0].uvalue = 0; + c->post_bit_count.len = 1; + c->block_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_error.stat[0].uvalue = 0; + c->block_error.len = 1; + c->block_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_count.stat[0].uvalue = 0; + c->block_count.len = 1; + + switch (c->bandwidth_hz) { + case 1712000: + bw = CXD2880_DTV_BW_1_7_MHZ; + break; + case 5000000: + bw = CXD2880_DTV_BW_5_MHZ; + break; + case 6000000: + bw = CXD2880_DTV_BW_6_MHZ; + break; + case 7000000: + bw = CXD2880_DTV_BW_7_MHZ; + break; + case 8000000: + bw = CXD2880_DTV_BW_8_MHZ; + break; + default: + return -EINVAL; + } + + priv->s = 0; + + pr_info("sys:%d freq:%d bw:%d\n", + c->delivery_system, c->frequency, bw); + mutex_lock(priv->spi_mutex); + if (c->delivery_system == SYS_DVBT) { + priv->tnrdmd.sys = CXD2880_DTV_SYS_DVBT; + priv->dvbt_tune_param.center_freq_khz = c->frequency / 1000; + priv->dvbt_tune_param.bandwidth = bw; + priv->dvbt_tune_param.profile = CXD2880_DVBT_PROFILE_HP; + ret = cxd2880_dvbt_tune(&priv->tnrdmd, + &priv->dvbt_tune_param); + } else if (c->delivery_system == SYS_DVBT2) { + priv->tnrdmd.sys = CXD2880_DTV_SYS_DVBT2; + priv->dvbt2_tune_param.center_freq_khz = c->frequency / 1000; + priv->dvbt2_tune_param.bandwidth = bw; + priv->dvbt2_tune_param.data_plp_id = (u16)c->stream_id; + priv->dvbt2_tune_param.profile = CXD2880_DVBT2_PROFILE_BASE; + ret = cxd2880_dvbt2_tune(&priv->tnrdmd, + &priv->dvbt2_tune_param); + } else { + pr_err("invalid system\n"); + mutex_unlock(priv->spi_mutex); + return -EINVAL; + } + mutex_unlock(priv->spi_mutex); + + pr_info("tune result %d\n", ret); + + return ret; +} + +static int cxd2880_get_stats(struct dvb_frontend *fe, + enum fe_status status) +{ + struct cxd2880_priv *priv = NULL; + struct dtv_frontend_properties *c = NULL; + u32 pre_bit_err = 0, pre_bit_count = 0; + u32 post_bit_err = 0, post_bit_count = 0; + u32 block_err = 0, block_count = 0; + int ret; + + if (!fe) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + c = &fe->dtv_property_cache; + + if (!(status & FE_HAS_LOCK)) { + c->pre_bit_error.len = 1; + c->pre_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->pre_bit_count.len = 1; + c->pre_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_error.len = 1; + c->post_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_count.len = 1; + c->post_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_error.len = 1; + c->block_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_count.len = 1; + c->block_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + + return 0; + } + + if (time_after(jiffies, priv->pre_ber_update)) { + priv->pre_ber_update = + jiffies + msecs_to_jiffies(priv->pre_ber_interval); + if (c->delivery_system == SYS_DVBT) { + mutex_lock(priv->spi_mutex); + ret = cxd2880_pre_bit_err_t(&priv->tnrdmd, + &pre_bit_err, + &pre_bit_count); + mutex_unlock(priv->spi_mutex); + } else if (c->delivery_system == SYS_DVBT2) { + mutex_lock(priv->spi_mutex); + ret = cxd2880_pre_bit_err_t2(&priv->tnrdmd, + &pre_bit_err, + &pre_bit_count); + mutex_unlock(priv->spi_mutex); + } else { + return -EINVAL; + } + + if (!ret) { + c->pre_bit_error.len = 1; + c->pre_bit_error.stat[0].scale = FE_SCALE_COUNTER; + c->pre_bit_error.stat[0].uvalue += pre_bit_err; + c->pre_bit_count.len = 1; + c->pre_bit_count.stat[0].scale = FE_SCALE_COUNTER; + c->pre_bit_count.stat[0].uvalue += pre_bit_count; + } else { + c->pre_bit_error.len = 1; + c->pre_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->pre_bit_count.len = 1; + c->pre_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + pr_debug("pre_bit_error_t failed %d\n", ret); + } + } + + if (time_after(jiffies, priv->post_ber_update)) { + priv->post_ber_update = + jiffies + msecs_to_jiffies(priv->post_ber_interval); + if (c->delivery_system == SYS_DVBT) { + mutex_lock(priv->spi_mutex); + ret = cxd2880_post_bit_err_t(&priv->tnrdmd, + &post_bit_err, + &post_bit_count); + mutex_unlock(priv->spi_mutex); + } else if (c->delivery_system == SYS_DVBT2) { + mutex_lock(priv->spi_mutex); + ret = cxd2880_post_bit_err_t2(&priv->tnrdmd, + &post_bit_err, + &post_bit_count); + mutex_unlock(priv->spi_mutex); + } else { + return -EINVAL; + } + + if (!ret) { + c->post_bit_error.len = 1; + c->post_bit_error.stat[0].scale = FE_SCALE_COUNTER; + c->post_bit_error.stat[0].uvalue += post_bit_err; + c->post_bit_count.len = 1; + c->post_bit_count.stat[0].scale = FE_SCALE_COUNTER; + c->post_bit_count.stat[0].uvalue += post_bit_count; + } else { + c->post_bit_error.len = 1; + c->post_bit_error.stat[0].scale = + FE_SCALE_NOT_AVAILABLE; + c->post_bit_count.len = 1; + c->post_bit_count.stat[0].scale = + FE_SCALE_NOT_AVAILABLE; + pr_debug("post_bit_err_t %d\n", ret); + } + } + + if (time_after(jiffies, priv->ucblock_update)) { + priv->ucblock_update = + jiffies + msecs_to_jiffies(priv->ucblock_interval); + if (c->delivery_system == SYS_DVBT) { + mutex_lock(priv->spi_mutex); + ret = cxd2880_read_block_err_t(&priv->tnrdmd, + &block_err, + &block_count); + mutex_unlock(priv->spi_mutex); + } else if (c->delivery_system == SYS_DVBT2) { + mutex_lock(priv->spi_mutex); + ret = cxd2880_read_block_err_t2(&priv->tnrdmd, + &block_err, + &block_count); + mutex_unlock(priv->spi_mutex); + } else { + return -EINVAL; + } + if (!ret) { + c->block_error.len = 1; + c->block_error.stat[0].scale = FE_SCALE_COUNTER; + c->block_error.stat[0].uvalue += block_err; + c->block_count.len = 1; + c->block_count.stat[0].scale = FE_SCALE_COUNTER; + c->block_count.stat[0].uvalue += block_count; + } else { + c->block_error.len = 1; + c->block_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_count.len = 1; + c->block_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + pr_debug("read_block_err_t %d\n", ret); + } + } + + return 0; +} + +static int cxd2880_check_l1post_plp(struct dvb_frontend *fe) +{ + u8 valid = 0; + u8 plp_not_found; + int ret; + struct cxd2880_priv *priv = NULL; + + if (!fe) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + + ret = cxd2880_tnrdmd_dvbt2_check_l1post_valid(&priv->tnrdmd, + &valid); + if (ret) + return ret; + + if (!valid) + return -EAGAIN; + + ret = cxd2880_tnrdmd_dvbt2_mon_data_plp_error(&priv->tnrdmd, + &plp_not_found); + if (ret) + return ret; + + if (plp_not_found) { + priv->dvbt2_tune_param.tune_info = + CXD2880_TNRDMD_DVBT2_TUNE_INFO_INVALID_PLP_ID; + } else { + priv->dvbt2_tune_param.tune_info = + CXD2880_TNRDMD_DVBT2_TUNE_INFO_OK; + } + + return 0; +} + +static int cxd2880_read_status(struct dvb_frontend *fe, + enum fe_status *status) +{ + int ret; + u8 sync = 0; + u8 lock = 0; + u8 unlock = 0; + struct cxd2880_priv *priv = NULL; + struct dtv_frontend_properties *c = NULL; + + if (!fe || !status) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + c = &fe->dtv_property_cache; + *status = 0; + + if (priv->tnrdmd.state == CXD2880_TNRDMD_STATE_ACTIVE) { + mutex_lock(priv->spi_mutex); + if (c->delivery_system == SYS_DVBT) { + ret = cxd2880_tnrdmd_dvbt_mon_sync_stat(&priv->tnrdmd, + &sync, + &lock, + &unlock); + } else if (c->delivery_system == SYS_DVBT2) { + ret = cxd2880_tnrdmd_dvbt2_mon_sync_stat(&priv->tnrdmd, + &sync, + &lock, + &unlock); + } else { + pr_err("invalid system"); + mutex_unlock(priv->spi_mutex); + return -EINVAL; + } + + mutex_unlock(priv->spi_mutex); + if (ret) { + pr_err("failed. sys = %d\n", priv->tnrdmd.sys); + return ret; + } + + if (sync == 6) { + *status = FE_HAS_SIGNAL | + FE_HAS_CARRIER; + } + if (lock) + *status |= FE_HAS_VITERBI | + FE_HAS_SYNC | + FE_HAS_LOCK; + } + + pr_debug("status %d\n", *status); + + if (priv->s == 0 && (*status & FE_HAS_LOCK)) { + mutex_lock(priv->spi_mutex); + if (c->delivery_system == SYS_DVBT) { + ret = cxd2880_set_ber_per_period_t(fe); + priv->s = *status; + } else if (c->delivery_system == SYS_DVBT2) { + ret = cxd2880_check_l1post_plp(fe); + if (!ret) { + ret = cxd2880_set_ber_per_period_t2(fe); + priv->s = *status; + } + } else { + pr_err("invalid system\n"); + mutex_unlock(priv->spi_mutex); + return -EINVAL; + } + mutex_unlock(priv->spi_mutex); + } + + cxd2880_get_stats(fe, *status); + return 0; +} + +static int cxd2880_tune(struct dvb_frontend *fe, + bool retune, + unsigned int mode_flags, + unsigned int *delay, + enum fe_status *status) +{ + int ret; + + if (!fe || !delay || !status) { + pr_err("invalid arg."); + return -EINVAL; + } + + if (retune) { + ret = cxd2880_set_frontend(fe); + if (ret) { + pr_err("cxd2880_set_frontend failed %d\n", ret); + return ret; + } + } + + *delay = HZ / 5; + + return cxd2880_read_status(fe, status); +} + +static int cxd2880_get_frontend_t(struct dvb_frontend *fe, + struct dtv_frontend_properties *c) +{ + int ret; + struct cxd2880_priv *priv = NULL; + enum cxd2880_dvbt_mode mode = CXD2880_DVBT_MODE_2K; + enum cxd2880_dvbt_guard guard = CXD2880_DVBT_GUARD_1_32; + struct cxd2880_dvbt_tpsinfo tps; + enum cxd2880_tnrdmd_spectrum_sense sense; + u16 snr = 0; + int strength = 0; + + if (!fe || !c) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_dvbt_mon_mode_guard(&priv->tnrdmd, + &mode, &guard); + mutex_unlock(priv->spi_mutex); + if (!ret) { + switch (mode) { + case CXD2880_DVBT_MODE_2K: + c->transmission_mode = TRANSMISSION_MODE_2K; + break; + case CXD2880_DVBT_MODE_8K: + c->transmission_mode = TRANSMISSION_MODE_8K; + break; + default: + c->transmission_mode = TRANSMISSION_MODE_2K; + pr_debug("transmission mode is invalid %d\n", mode); + break; + } + switch (guard) { + case CXD2880_DVBT_GUARD_1_32: + c->guard_interval = GUARD_INTERVAL_1_32; + break; + case CXD2880_DVBT_GUARD_1_16: + c->guard_interval = GUARD_INTERVAL_1_16; + break; + case CXD2880_DVBT_GUARD_1_8: + c->guard_interval = GUARD_INTERVAL_1_8; + break; + case CXD2880_DVBT_GUARD_1_4: + c->guard_interval = GUARD_INTERVAL_1_4; + break; + default: + c->guard_interval = GUARD_INTERVAL_1_32; + pr_debug("guard interval is invalid %d\n", + guard); + break; + } + } else { + c->transmission_mode = TRANSMISSION_MODE_2K; + c->guard_interval = GUARD_INTERVAL_1_32; + pr_debug("ModeGuard err %d\n", ret); + } + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_dvbt_mon_tps_info(&priv->tnrdmd, &tps); + mutex_unlock(priv->spi_mutex); + if (!ret) { + switch (tps.hierarchy) { + case CXD2880_DVBT_HIERARCHY_NON: + c->hierarchy = HIERARCHY_NONE; + break; + case CXD2880_DVBT_HIERARCHY_1: + c->hierarchy = HIERARCHY_1; + break; + case CXD2880_DVBT_HIERARCHY_2: + c->hierarchy = HIERARCHY_2; + break; + case CXD2880_DVBT_HIERARCHY_4: + c->hierarchy = HIERARCHY_4; + break; + default: + c->hierarchy = HIERARCHY_NONE; + pr_debug("TPSInfo hierarchy is invalid %d\n", + tps.hierarchy); + break; + } + + switch (tps.rate_hp) { + case CXD2880_DVBT_CODERATE_1_2: + c->code_rate_HP = FEC_1_2; + break; + case CXD2880_DVBT_CODERATE_2_3: + c->code_rate_HP = FEC_2_3; + break; + case CXD2880_DVBT_CODERATE_3_4: + c->code_rate_HP = FEC_3_4; + break; + case CXD2880_DVBT_CODERATE_5_6: + c->code_rate_HP = FEC_5_6; + break; + case CXD2880_DVBT_CODERATE_7_8: + c->code_rate_HP = FEC_7_8; + break; + default: + c->code_rate_HP = FEC_NONE; + pr_debug("TPSInfo rateHP is invalid %d\n", + tps.rate_hp); + break; + } + switch (tps.rate_lp) { + case CXD2880_DVBT_CODERATE_1_2: + c->code_rate_LP = FEC_1_2; + break; + case CXD2880_DVBT_CODERATE_2_3: + c->code_rate_LP = FEC_2_3; + break; + case CXD2880_DVBT_CODERATE_3_4: + c->code_rate_LP = FEC_3_4; + break; + case CXD2880_DVBT_CODERATE_5_6: + c->code_rate_LP = FEC_5_6; + break; + case CXD2880_DVBT_CODERATE_7_8: + c->code_rate_LP = FEC_7_8; + break; + default: + c->code_rate_LP = FEC_NONE; + pr_debug("TPSInfo rateLP is invalid %d\n", + tps.rate_lp); + break; + } + switch (tps.constellation) { + case CXD2880_DVBT_CONSTELLATION_QPSK: + c->modulation = QPSK; + break; + case CXD2880_DVBT_CONSTELLATION_16QAM: + c->modulation = QAM_16; + break; + case CXD2880_DVBT_CONSTELLATION_64QAM: + c->modulation = QAM_64; + break; + default: + c->modulation = QPSK; + pr_debug("TPSInfo constellation is invalid %d\n", + tps.constellation); + break; + } + } else { + c->hierarchy = HIERARCHY_NONE; + c->code_rate_HP = FEC_NONE; + c->code_rate_LP = FEC_NONE; + c->modulation = QPSK; + pr_debug("TPS info err %d\n", ret); + } + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_dvbt_mon_spectrum_sense(&priv->tnrdmd, &sense); + mutex_unlock(priv->spi_mutex); + if (!ret) { + switch (sense) { + case CXD2880_TNRDMD_SPECTRUM_NORMAL: + c->inversion = INVERSION_OFF; + break; + case CXD2880_TNRDMD_SPECTRUM_INV: + c->inversion = INVERSION_ON; + break; + default: + c->inversion = INVERSION_OFF; + pr_debug("spectrum sense is invalid %d\n", sense); + break; + } + } else { + c->inversion = INVERSION_OFF; + pr_debug("spectrum_sense %d\n", ret); + } + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_mon_rf_lvl(&priv->tnrdmd, &strength); + mutex_unlock(priv->spi_mutex); + if (!ret) { + c->strength.len = 1; + c->strength.stat[0].scale = FE_SCALE_DECIBEL; + c->strength.stat[0].svalue = strength; + } else { + c->strength.len = 1; + c->strength.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + pr_debug("mon_rf_lvl %d\n", ret); + } + + ret = cxd2880_read_snr(fe, &snr); + if (!ret) { + c->cnr.len = 1; + c->cnr.stat[0].scale = FE_SCALE_DECIBEL; + c->cnr.stat[0].svalue = snr; + } else { + c->cnr.len = 1; + c->cnr.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + pr_debug("read_snr %d\n", ret); + } + + return 0; +} + +static int cxd2880_get_frontend_t2(struct dvb_frontend *fe, + struct dtv_frontend_properties *c) +{ + int ret; + struct cxd2880_priv *priv = NULL; + struct cxd2880_dvbt2_l1pre l1pre; + enum cxd2880_dvbt2_plp_code_rate coderate; + enum cxd2880_dvbt2_plp_constell qam; + enum cxd2880_tnrdmd_spectrum_sense sense; + u16 snr = 0; + int strength = 0; + + if (!fe || !c) { + pr_err("invalid arg.\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_dvbt2_mon_l1_pre(&priv->tnrdmd, &l1pre); + mutex_unlock(priv->spi_mutex); + if (!ret) { + switch (l1pre.fft_mode) { + case CXD2880_DVBT2_M2K: + c->transmission_mode = TRANSMISSION_MODE_2K; + break; + case CXD2880_DVBT2_M8K: + c->transmission_mode = TRANSMISSION_MODE_8K; + break; + case CXD2880_DVBT2_M4K: + c->transmission_mode = TRANSMISSION_MODE_4K; + break; + case CXD2880_DVBT2_M1K: + c->transmission_mode = TRANSMISSION_MODE_1K; + break; + case CXD2880_DVBT2_M16K: + c->transmission_mode = TRANSMISSION_MODE_16K; + break; + case CXD2880_DVBT2_M32K: + c->transmission_mode = TRANSMISSION_MODE_32K; + break; + default: + c->transmission_mode = TRANSMISSION_MODE_2K; + pr_debug("L1Pre fft_mode is invalid %d\n", + l1pre.fft_mode); + break; + } + switch (l1pre.gi) { + case CXD2880_DVBT2_G1_32: + c->guard_interval = GUARD_INTERVAL_1_32; + break; + case CXD2880_DVBT2_G1_16: + c->guard_interval = GUARD_INTERVAL_1_16; + break; + case CXD2880_DVBT2_G1_8: + c->guard_interval = GUARD_INTERVAL_1_8; + break; + case CXD2880_DVBT2_G1_4: + c->guard_interval = GUARD_INTERVAL_1_4; + break; + case CXD2880_DVBT2_G1_128: + c->guard_interval = GUARD_INTERVAL_1_128; + break; + case CXD2880_DVBT2_G19_128: + c->guard_interval = GUARD_INTERVAL_19_128; + break; + case CXD2880_DVBT2_G19_256: + c->guard_interval = GUARD_INTERVAL_19_256; + break; + default: + c->guard_interval = GUARD_INTERVAL_1_32; + pr_debug("L1Pre guard interval is invalid %d\n", + l1pre.gi); + break; + } + } else { + c->transmission_mode = TRANSMISSION_MODE_2K; + c->guard_interval = GUARD_INTERVAL_1_32; + pr_debug("L1Pre err %d\n", ret); + } + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_dvbt2_mon_code_rate(&priv->tnrdmd, + CXD2880_DVBT2_PLP_DATA, + &coderate); + mutex_unlock(priv->spi_mutex); + if (!ret) { + switch (coderate) { + case CXD2880_DVBT2_R1_2: + c->fec_inner = FEC_1_2; + break; + case CXD2880_DVBT2_R3_5: + c->fec_inner = FEC_3_5; + break; + case CXD2880_DVBT2_R2_3: + c->fec_inner = FEC_2_3; + break; + case CXD2880_DVBT2_R3_4: + c->fec_inner = FEC_3_4; + break; + case CXD2880_DVBT2_R4_5: + c->fec_inner = FEC_4_5; + break; + case CXD2880_DVBT2_R5_6: + c->fec_inner = FEC_5_6; + break; + default: + c->fec_inner = FEC_NONE; + pr_debug("CodeRate is invalid %d\n", coderate); + break; + } + } else { + c->fec_inner = FEC_NONE; + pr_debug("CodeRate %d\n", ret); + } + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_dvbt2_mon_qam(&priv->tnrdmd, + CXD2880_DVBT2_PLP_DATA, + &qam); + mutex_unlock(priv->spi_mutex); + if (!ret) { + switch (qam) { + case CXD2880_DVBT2_QPSK: + c->modulation = QPSK; + break; + case CXD2880_DVBT2_QAM16: + c->modulation = QAM_16; + break; + case CXD2880_DVBT2_QAM64: + c->modulation = QAM_64; + break; + case CXD2880_DVBT2_QAM256: + c->modulation = QAM_256; + break; + default: + c->modulation = QPSK; + pr_debug("QAM is invalid %d\n", qam); + break; + } + } else { + c->modulation = QPSK; + pr_debug("QAM %d\n", ret); + } + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_dvbt2_mon_spectrum_sense(&priv->tnrdmd, &sense); + mutex_unlock(priv->spi_mutex); + if (!ret) { + switch (sense) { + case CXD2880_TNRDMD_SPECTRUM_NORMAL: + c->inversion = INVERSION_OFF; + break; + case CXD2880_TNRDMD_SPECTRUM_INV: + c->inversion = INVERSION_ON; + break; + default: + c->inversion = INVERSION_OFF; + pr_debug("spectrum sense is invalid %d\n", sense); + break; + } + } else { + c->inversion = INVERSION_OFF; + pr_debug("SpectrumSense %d\n", ret); + } + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_mon_rf_lvl(&priv->tnrdmd, &strength); + mutex_unlock(priv->spi_mutex); + if (!ret) { + c->strength.len = 1; + c->strength.stat[0].scale = FE_SCALE_DECIBEL; + c->strength.stat[0].svalue = strength; + } else { + c->strength.len = 1; + c->strength.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + pr_debug("mon_rf_lvl %d\n", ret); + } + + ret = cxd2880_read_snr(fe, &snr); + if (!ret) { + c->cnr.len = 1; + c->cnr.stat[0].scale = FE_SCALE_DECIBEL; + c->cnr.stat[0].svalue = snr; + } else { + c->cnr.len = 1; + c->cnr.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + pr_debug("read_snr %d\n", ret); + } + + return 0; +} + +static int cxd2880_get_frontend(struct dvb_frontend *fe, + struct dtv_frontend_properties *props) +{ + struct cxd2880_priv *priv = NULL; + int ret; + + if (!fe || !props) { + pr_err("invalid arg."); + return -EINVAL; + } + + priv = fe->demodulator_priv; + + pr_debug("system=%d\n", fe->dtv_property_cache.delivery_system); + switch (fe->dtv_property_cache.delivery_system) { + case SYS_DVBT: + ret = cxd2880_get_frontend_t(fe, props); + break; + case SYS_DVBT2: + ret = cxd2880_get_frontend_t2(fe, props); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum dvbfe_algo cxd2880_get_frontend_algo(struct dvb_frontend *fe) +{ + return DVBFE_ALGO_HW; +} + +static struct dvb_frontend_ops cxd2880_dvbt_t2_ops = { + .info = { + .name = "Sony CXD2880", + .frequency_min = 174000000, + .frequency_max = 862000000, + .frequency_stepsize = 1000, + .caps = FE_CAN_INVERSION_AUTO | + FE_CAN_FEC_1_2 | + FE_CAN_FEC_2_3 | + FE_CAN_FEC_3_4 | + FE_CAN_FEC_4_5 | + FE_CAN_FEC_5_6 | + FE_CAN_FEC_7_8 | + FE_CAN_FEC_AUTO | + FE_CAN_QPSK | + FE_CAN_QAM_16 | + FE_CAN_QAM_32 | + FE_CAN_QAM_64 | + FE_CAN_QAM_128 | + FE_CAN_QAM_256 | + FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_2G_MODULATION | + FE_CAN_RECOVER | + FE_CAN_MUTE_TS, + }, + .delsys = { SYS_DVBT, SYS_DVBT2 }, + + .release = cxd2880_release, + .init = cxd2880_init, + .sleep = cxd2880_sleep, + .tune = cxd2880_tune, + .set_frontend = cxd2880_set_frontend, + .get_frontend = cxd2880_get_frontend, + .read_status = cxd2880_read_status, + .read_ber = cxd2880_read_ber, + .read_signal_strength = cxd2880_read_signal_strength, + .read_snr = cxd2880_read_snr, + .read_ucblocks = cxd2880_read_ucblocks, + .get_frontend_algo = cxd2880_get_frontend_algo, +}; + +struct dvb_frontend *cxd2880_attach(struct dvb_frontend *fe, + struct cxd2880_config *cfg) +{ + int ret; + enum cxd2880_tnrdmd_chip_id chipid = + CXD2880_TNRDMD_CHIP_ID_UNKNOWN; + static struct cxd2880_priv *priv; + u8 data = 0; + + if (!fe) { + pr_err("invalid arg.\n"); + return NULL; + } + + priv = kzalloc(sizeof(struct cxd2880_priv), GFP_KERNEL); + if (!priv) + return NULL; + + priv->spi = cfg->spi; + priv->spi_mutex = cfg->spi_mutex; + priv->spi_device.spi = cfg->spi; + + memcpy(&fe->ops, &cxd2880_dvbt_t2_ops, + sizeof(struct dvb_frontend_ops)); + + ret = cxd2880_spi_device_initialize(&priv->spi_device, + CXD2880_SPI_MODE_0, + 55000000); + if (ret) { + pr_err("spi_device_initialize failed. %d\n", ret); + kfree(priv); + return NULL; + } + + ret = cxd2880_spi_device_create_spi(&priv->cxd2880_spi, + &priv->spi_device); + if (ret) { + pr_err("spi_device_create_spi failed. %d\n", ret); + kfree(priv); + return NULL; + } + + ret = cxd2880_io_spi_create(&priv->regio, &priv->cxd2880_spi, 0); + if (ret) { + pr_err("io_spi_create failed. %d\n", ret); + kfree(priv); + return NULL; + } + ret = priv->regio.write_reg(&priv->regio, + CXD2880_IO_TGT_SYS, 0x00, 0x00); + if (ret) { + pr_err("set bank to 0x00 failed.\n"); + kfree(priv); + return NULL; + } + ret = priv->regio.read_regs(&priv->regio, + CXD2880_IO_TGT_SYS, 0xfd, &data, 1); + if (ret) { + pr_err("read chip id failed.\n"); + kfree(priv); + return NULL; + } + + chipid = (enum cxd2880_tnrdmd_chip_id)data; + if (chipid != CXD2880_TNRDMD_CHIP_ID_CXD2880_ES1_0X && + chipid != CXD2880_TNRDMD_CHIP_ID_CXD2880_ES1_11) { + pr_err("chip id invalid.\n"); + kfree(priv); + return NULL; + } + + fe->demodulator_priv = priv; + pr_info("CXD2880 driver version: Ver %s\n", + CXD2880_TNRDMD_DRIVER_VERSION); + + return fe; +} +EXPORT_SYMBOL(cxd2880_attach); + +MODULE_DESCRIPTION("Sony CXD2880 DVB-T2/T tuner + demod driver"); +MODULE_AUTHOR("Sony Semiconductor Solutions Corporation"); +MODULE_LICENSE("GPL v2"); -- 2.15.1 From mboxrd@z Thu Jan 1 00:00:00 1970 From: Subject: [PATCH v5 07/12] [media] cxd2880: Add top level of the driver Date: Thu, 18 Jan 2018 17:52:17 +0900 Message-ID: <20180118085217.21465-1-Yasunari.Takiguchi@sony.com> References: <20180118084016.20689-1-Yasunari.Takiguchi@sony.com> Mime-Version: 1.0 Content-Type: text/plain Return-path: In-Reply-To: <20180118084016.20689-1-Yasunari.Takiguchi-7U/KSKJipcs@public.gmane.org> Sender: devicetree-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org To: linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-media-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Cc: tbird20d-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org, frowand.list-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org, Yasunari.Takiguchi-7U/KSKJipcs@public.gmane.org, Masayuki.Yamamoto-7U/KSKJipcs@public.gmane.org, Hideki.Nozawa-7U/KSKJipcs@public.gmane.org, Kota.Yonezawa-7U/KSKJipcs@public.gmane.org, Toshihiko.Matsumoto-7U/KSKJipcs@public.gmane.org, Satoshi.C.Watanabe-7U/KSKJipcs@public.gmane.org List-Id: devicetree@vger.kernel.org From: Yasunari Takiguchi This provides the main dvb frontend operation functions for the Sony CXD2880 DVB-T2/T tuner + demodulator driver. Signed-off-by: Yasunari Takiguchi Signed-off-by: Masayuki Yamamoto Signed-off-by: Hideki Nozawa Signed-off-by: Kota Yonezawa Signed-off-by: Toshihiko Matsumoto Signed-off-by: Satoshi Watanabe --- [Change list] Changes in V5 Using SPDX-License-Identifier drivers/media/dvb-frontends/cxd2880/cxd2880_top.c -changed define position of cxd2880_dvbt_t2_ops -modified typo about "drvier" -> "driver" -removed unnecessary cast -removed unnecessary if() -modified return error code -removed unnecessary parentheses -modified for "Lines should not end with a '(' " -modified to return constant 0 from read_ber function Changes in V4 drivers/media/dvb-frontends/cxd2880/cxd2880_top.c -modified typo "inavlid" to "invalid" at pr_err -removed unnecessary initialization at variable declaration -removed unnecessary brace {} -changed to use cxd2880_dvbt_tune and cxd2880_dvbt2_tune instead of cxd2880_integ_dvbt_tune and cxd2880_integ_dvbt2_tune (because we changed it so that demodulator does not wait for locking the signal.) Changes in V3 drivers/media/dvb-frontends/cxd2880/cxd2880_top.c -adjusted indent spaces -modified debugging code -removed unnecessary cast -modified return code -modified coding style of if() -modified about measurement period of PER/BER. -changed hexadecimal code to lower case. drivers/media/dvb-frontends/cxd2880/cxd2880_top.c | 1954 +++++++++++++++++++++ 1 file changed, 1954 insertions(+) create mode 100644 drivers/media/dvb-frontends/cxd2880/cxd2880_top.c diff --git a/drivers/media/dvb-frontends/cxd2880/cxd2880_top.c b/drivers/media/dvb-frontends/cxd2880/cxd2880_top.c new file mode 100644 index 000000000000..f109e9d98cc0 --- /dev/null +++ b/drivers/media/dvb-frontends/cxd2880/cxd2880_top.c @@ -0,0 +1,1954 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * cxd2880_top.c + * Sony CXD2880 DVB-T2/T tuner + demodulator driver + * + * Copyright (C) 2016, 2017, 2018 Sony Semiconductor Solutions Corporation + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__ + +#include + +#include "dvb_frontend.h" +#include "dvb_math.h" + +#include "cxd2880.h" +#include "cxd2880_tnrdmd_mon.h" +#include "cxd2880_tnrdmd_dvbt2_mon.h" +#include "cxd2880_tnrdmd_dvbt_mon.h" +#include "cxd2880_integ.h" +#include "cxd2880_tnrdmd_dvbt2.h" +#include "cxd2880_tnrdmd_dvbt.h" +#include "cxd2880_devio_spi.h" +#include "cxd2880_spi_device.h" +#include "cxd2880_tnrdmd_driver_version.h" + +struct cxd2880_priv { + struct cxd2880_tnrdmd tnrdmd; + struct spi_device *spi; + struct cxd2880_io regio; + struct cxd2880_spi_device spi_device; + struct cxd2880_spi cxd2880_spi; + struct cxd2880_dvbt_tune_param dvbt_tune_param; + struct cxd2880_dvbt2_tune_param dvbt2_tune_param; + struct mutex *spi_mutex; /* For SPI access exclusive control */ + unsigned long pre_ber_update; + unsigned long pre_ber_interval; + unsigned long post_ber_update; + unsigned long post_ber_interval; + unsigned long ucblock_update; + unsigned long ucblock_interval; + enum fe_status s; +}; + +static int cxd2880_pre_bit_err_t(struct cxd2880_tnrdmd *tnrdmd, + u32 *pre_bit_err, u32 *pre_bit_count) +{ + u8 rdata[2]; + int ret; + + if (!tnrdmd || !pre_bit_err || !pre_bit_count) + return -EINVAL; + + if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB) + return -EINVAL; + + if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE) + return -EINVAL; + + if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT) + return -EINVAL; + + ret = slvt_freeze_reg(tnrdmd); + if (ret) + return ret; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x10); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x39, rdata, 1); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + if ((rdata[0] & 0x01) == 0) { + slvt_unfreeze_reg(tnrdmd); + return -EAGAIN; + } + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x22, rdata, 2); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + *pre_bit_err = (rdata[0] << 8) | rdata[1]; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x6f, rdata, 1); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + slvt_unfreeze_reg(tnrdmd); + + *pre_bit_count = ((rdata[0] & 0x07) == 0) ? + 256 : (0x1000 << (rdata[0] & 0x07)); + + return 0; +} + +static int cxd2880_pre_bit_err_t2(struct cxd2880_tnrdmd *tnrdmd, + u32 *pre_bit_err, + u32 *pre_bit_count) +{ + u32 period_exp = 0; + u32 n_ldpc = 0; + u8 data[5]; + int ret; + + if (!tnrdmd || !pre_bit_err || !pre_bit_count) + return -EINVAL; + + if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB) + return -EINVAL; + + if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE) + return -EINVAL; + + if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT2) + return -EINVAL; + + ret = slvt_freeze_reg(tnrdmd); + if (ret) + return ret; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x0b); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x3c, data, sizeof(data)); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + if (!(data[0] & 0x01)) { + slvt_unfreeze_reg(tnrdmd); + return -EAGAIN; + } + *pre_bit_err = + ((data[1] & 0x0f) << 24) | (data[2] << 16) | (data[3] << 8) | data[4]; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0xa0, data, 1); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + if (((enum cxd2880_dvbt2_plp_fec)(data[0] & 0x03)) == + CXD2880_DVBT2_FEC_LDPC_16K) + n_ldpc = 16200; + else + n_ldpc = 64800; + slvt_unfreeze_reg(tnrdmd); + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x20); + if (ret) + return ret; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x6f, data, 1); + if (ret) + return ret; + + period_exp = data[0] & 0x0f; + + *pre_bit_count = (1U << period_exp) * n_ldpc; + + return 0; +} + +static int cxd2880_post_bit_err_t(struct cxd2880_tnrdmd *tnrdmd, + u32 *post_bit_err, + u32 *post_bit_count) +{ + u8 rdata[3]; + u32 bit_error = 0; + u32 period_exp = 0; + int ret; + + if (!tnrdmd || !post_bit_err || !post_bit_count) + return -EINVAL; + + if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB) + return -EINVAL; + + if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE) + return -EINVAL; + + if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT) + return -EINVAL; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x0d); + if (ret) + return ret; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x15, rdata, 3); + if (ret) + return ret; + + if ((rdata[0] & 0x40) == 0) + return -EAGAIN; + + *post_bit_err = ((rdata[0] & 0x3f) << 16) | (rdata[1] << 8) | rdata[2]; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x10); + if (ret) + return ret; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x60, rdata, 1); + if (ret) + return ret; + + period_exp = (rdata[0] & 0x1f); + + if (period_exp <= 11 && (bit_error > (1U << period_exp) * 204 * 8)) + return -EAGAIN; + + *post_bit_count = (1U << period_exp) * 204 * 8; + + return 0; +} + +static int cxd2880_post_bit_err_t2(struct cxd2880_tnrdmd *tnrdmd, + u32 *post_bit_err, + u32 *post_bit_count) +{ + u32 period_exp = 0; + u32 n_bch = 0; + u8 data[3]; + enum cxd2880_dvbt2_plp_fec plp_fec_type = + CXD2880_DVBT2_FEC_LDPC_16K; + enum cxd2880_dvbt2_plp_code_rate plp_code_rate = + CXD2880_DVBT2_R1_2; + int ret; + static const u16 n_bch_bits_lookup[2][8] = { + {7200, 9720, 10800, 11880, 12600, 13320, 5400, 6480}, + {32400, 38880, 43200, 48600, 51840, 54000, 21600, 25920} + }; + + if (!tnrdmd || !post_bit_err || !post_bit_count) + return -EINVAL; + + if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB) + return -EINVAL; + + if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE) + return -EINVAL; + + if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT2) + return -EINVAL; + + ret = slvt_freeze_reg(tnrdmd); + if (ret) + return ret; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x0b); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x15, data, 3); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + if (!(data[0] & 0x40)) { + slvt_unfreeze_reg(tnrdmd); + return -EAGAIN; + } + + *post_bit_err = + ((data[0] & 0x3f) << 16) | (data[1] << 8) | data[2]; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x9d, data, 1); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + plp_code_rate = + (enum cxd2880_dvbt2_plp_code_rate)(data[0] & 0x07); + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0xa0, data, 1); + if (ret) { + slvt_unfreeze_reg(tnrdmd); + return ret; + } + + plp_fec_type = (enum cxd2880_dvbt2_plp_fec)(data[0] & 0x03); + + slvt_unfreeze_reg(tnrdmd); + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x20); + if (ret) + return ret; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x72, data, 1); + if (ret) + return ret; + + period_exp = data[0] & 0x0f; + + if (plp_fec_type > CXD2880_DVBT2_FEC_LDPC_64K || + plp_code_rate > CXD2880_DVBT2_R2_5) + return -EAGAIN; + + n_bch = n_bch_bits_lookup[plp_fec_type][plp_code_rate]; + + if (*post_bit_err > ((1U << period_exp) * n_bch)) + return -EAGAIN; + + *post_bit_count = (1U << period_exp) * n_bch; + + return 0; +} + +static int cxd2880_read_block_err_t(struct cxd2880_tnrdmd *tnrdmd, + u32 *block_err, + u32 *block_count) +{ + u8 rdata[3]; + int ret; + + if (!tnrdmd || !block_err || !block_count) + return -EINVAL; + + if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB) + return -EINVAL; + + if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE) + return -EINVAL; + + if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT) + return -EINVAL; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x0d); + if (ret) + return ret; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x18, rdata, 3); + if (ret) + return ret; + + if ((rdata[0] & 0x01) == 0) + return -EAGAIN; + + *block_err = (rdata[1] << 8) | rdata[2]; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x10); + if (ret) + return ret; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x5c, rdata, 1); + if (ret) + return ret; + + *block_count = 1U << (rdata[0] & 0x0f); + + if ((*block_count == 0) || (*block_err > *block_count)) + return -EAGAIN; + + return 0; +} + +static int cxd2880_read_block_err_t2(struct cxd2880_tnrdmd *tnrdmd, + u32 *block_err, + u32 *block_count) +{ + u8 rdata[3]; + int ret; + + if (!tnrdmd || !block_err || !block_count) + return -EINVAL; + + if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB) + return -EINVAL; + + if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE) + return -EINVAL; + if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT2) + return -EINVAL; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x0b); + if (ret) + return ret; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x18, rdata, 3); + if (ret) + return ret; + + if ((rdata[0] & 0x01) == 0) + return -EAGAIN; + + *block_err = (rdata[1] << 8) | rdata[2]; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x24); + if (ret) + return ret; + + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0xdc, rdata, 1); + if (ret) + return ret; + + *block_count = 1U << (rdata[0] & 0x0f); + + if ((*block_count == 0) || (*block_err > *block_count)) + return -EAGAIN; + + return 0; +} + +static void cxd2880_release(struct dvb_frontend *fe) +{ + struct cxd2880_priv *priv = NULL; + + if (!fe) { + pr_err("invalid arg.\n"); + return; + } + priv = fe->demodulator_priv; + kfree(priv); +} + +static int cxd2880_init(struct dvb_frontend *fe) +{ + int ret; + struct cxd2880_priv *priv = NULL; + struct cxd2880_tnrdmd_create_param create_param; + + if (!fe) { + pr_err("invalid arg.\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + + create_param.ts_output_if = CXD2880_TNRDMD_TSOUT_IF_SPI; + create_param.xtal_share_type = CXD2880_TNRDMD_XTAL_SHARE_NONE; + create_param.en_internal_ldo = 1; + create_param.xosc_cap = 18; + create_param.xosc_i = 8; + create_param.stationary_use = 1; + + mutex_lock(priv->spi_mutex); + if (priv->tnrdmd.io != &priv->regio) { + ret = cxd2880_tnrdmd_create(&priv->tnrdmd, + &priv->regio, &create_param); + if (ret) { + mutex_unlock(priv->spi_mutex); + pr_info("cxd2880 tnrdmd create failed %d\n", ret); + return ret; + } + } + ret = cxd2880_integ_init(&priv->tnrdmd); + if (ret) { + mutex_unlock(priv->spi_mutex); + pr_err("cxd2880 integ init failed %d\n", ret); + return ret; + } + mutex_unlock(priv->spi_mutex); + + pr_debug("OK.\n"); + + return ret; +} + +static int cxd2880_sleep(struct dvb_frontend *fe) +{ + int ret; + struct cxd2880_priv *priv = NULL; + + if (!fe) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_sleep(&priv->tnrdmd); + mutex_unlock(priv->spi_mutex); + + pr_debug("tnrdmd_sleep ret %d\n", ret); + + return ret; +} + +static int cxd2880_read_signal_strength(struct dvb_frontend *fe, + u16 *strength) +{ + int ret; + struct cxd2880_priv *priv = NULL; + struct dtv_frontend_properties *c = NULL; + int level = 0; + + if (!fe || !strength) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + c = &fe->dtv_property_cache; + + mutex_lock(priv->spi_mutex); + if (c->delivery_system == SYS_DVBT || + c->delivery_system == SYS_DVBT2) { + ret = cxd2880_tnrdmd_mon_rf_lvl(&priv->tnrdmd, &level); + } else { + pr_debug("invalid system\n"); + mutex_unlock(priv->spi_mutex); + return -EINVAL; + } + mutex_unlock(priv->spi_mutex); + + level /= 125; + /* + * level should be between -105dBm and -30dBm. + * E.g. they should be between: + * -105000/125 = -840 and -30000/125 = -240 + */ + level = clamp(level, -840, -240); + /* scale value to 0x0000-0xffff */ + *strength = ((level + 840) * 0xffff) / (-240 + 840); + + if (ret) + pr_debug("ret = %d\n", ret); + + return ret; +} + +static int cxd2880_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + int ret; + int snrvalue = 0; + struct cxd2880_priv *priv = NULL; + struct dtv_frontend_properties *c = NULL; + + if (!fe || !snr) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + c = &fe->dtv_property_cache; + + mutex_lock(priv->spi_mutex); + if (c->delivery_system == SYS_DVBT) { + ret = cxd2880_tnrdmd_dvbt_mon_snr(&priv->tnrdmd, + &snrvalue); + } else if (c->delivery_system == SYS_DVBT2) { + ret = cxd2880_tnrdmd_dvbt2_mon_snr(&priv->tnrdmd, + &snrvalue); + } else { + pr_err("invalid system\n"); + mutex_unlock(priv->spi_mutex); + return -EINVAL; + } + mutex_unlock(priv->spi_mutex); + + if (snrvalue < 0) + snrvalue = 0; + *snr = snrvalue; + + if (ret) + pr_debug("ret = %d\n", ret); + + return ret; +} + +static int cxd2880_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + int ret; + struct cxd2880_priv *priv = NULL; + struct dtv_frontend_properties *c = NULL; + + if (!fe || !ucblocks) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + c = &fe->dtv_property_cache; + + mutex_lock(priv->spi_mutex); + if (c->delivery_system == SYS_DVBT) { + ret = cxd2880_tnrdmd_dvbt_mon_packet_error_number(&priv->tnrdmd, + ucblocks); + } else if (c->delivery_system == SYS_DVBT2) { + ret = cxd2880_tnrdmd_dvbt2_mon_packet_error_number(&priv->tnrdmd, + ucblocks); + } else { + pr_err("invalid system\n"); + mutex_unlock(priv->spi_mutex); + return -EINVAL; + } + mutex_unlock(priv->spi_mutex); + + if (ret) + pr_debug("ret = %d\n", ret); + + return ret; +} + +static int cxd2880_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + *ber = 0; + + return 0; +} + +static int cxd2880_set_ber_per_period_t(struct dvb_frontend *fe) +{ + int ret; + struct dtv_frontend_properties *c; + struct cxd2880_priv *priv; + struct cxd2880_dvbt_tpsinfo info; + enum cxd2880_dtv_bandwidth bw = CXD2880_DTV_BW_1_7_MHZ; + u32 pre_ber_rate = 0; + u32 post_ber_rate = 0; + u32 ucblock_rate = 0; + u32 mes_exp = 0; + static const int cr_table[5] = {31500, 42000, 47250, 52500, 55125}; + static const int denominator_tbl[4] = {125664, 129472, 137088, 152320}; + + if (!fe) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + c = &fe->dtv_property_cache; + bw = priv->dvbt_tune_param.bandwidth; + + ret = cxd2880_tnrdmd_dvbt_mon_tps_info(&priv->tnrdmd, + &info); + if (ret) { + pr_err("tps monitor error ret = %d\n", ret); + info.hierarchy = CXD2880_DVBT_HIERARCHY_NON; + info.constellation = CXD2880_DVBT_CONSTELLATION_QPSK; + info.guard = CXD2880_DVBT_GUARD_1_4; + info.rate_hp = CXD2880_DVBT_CODERATE_1_2; + info.rate_lp = CXD2880_DVBT_CODERATE_1_2; + } + + if (info.hierarchy == CXD2880_DVBT_HIERARCHY_NON) { + pre_ber_rate = 63000000 * bw * (info.constellation * 2 + 2) / + denominator_tbl[info.guard]; + + post_ber_rate = 1000 * cr_table[info.rate_hp] * bw * + (info.constellation * 2 + 2) / + denominator_tbl[info.guard]; + + ucblock_rate = 875 * cr_table[info.rate_hp] * bw * + (info.constellation * 2 + 2) / + denominator_tbl[info.guard]; + } else { + u8 data = 0; + struct cxd2880_tnrdmd *tnrdmd = &priv->tnrdmd; + + ret = tnrdmd->io->write_reg(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x00, 0x10); + if (!ret) { + ret = tnrdmd->io->read_regs(tnrdmd->io, + CXD2880_IO_TGT_DMD, + 0x67, &data, 1); + if (ret) + data = 0x00; + } else { + data = 0x00; + } + + if (data & 0x01) { /* Low priority */ + pre_ber_rate = + 63000000 * bw * (info.constellation * 2 + 2) / + denominator_tbl[info.guard]; + + post_ber_rate = 1000 * cr_table[info.rate_lp] * bw * + (info.constellation * 2 + 2) / + denominator_tbl[info.guard]; + + ucblock_rate = (1000 * 7 / 8) * cr_table[info.rate_lp] * + bw * (info.constellation * 2 + 2) / + denominator_tbl[info.guard]; + } else { /* High priority */ + pre_ber_rate = + 63000000 * bw * 2 / denominator_tbl[info.guard]; + + post_ber_rate = 1000 * cr_table[info.rate_hp] * bw * 2 / + denominator_tbl[info.guard]; + + ucblock_rate = (1000 * 7 / 8) * cr_table[info.rate_hp] * + bw * 2 / denominator_tbl[info.guard]; + } + } + + mes_exp = pre_ber_rate < 8192 ? 8 : intlog2(pre_ber_rate) >> 24; + priv->pre_ber_interval = + ((1U << mes_exp) * 1000 + (pre_ber_rate / 2)) / + pre_ber_rate; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT_VBER_PERIOD, + mes_exp == 8 ? 0 : mes_exp - 12); + + mes_exp = intlog2(post_ber_rate) >> 24; + priv->post_ber_interval = + ((1U << mes_exp) * 1000 + (post_ber_rate / 2)) / + post_ber_rate; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT_BERN_PERIOD, + mes_exp); + + mes_exp = intlog2(ucblock_rate) >> 24; + priv->ucblock_interval = + ((1U << mes_exp) * 1000 + (ucblock_rate / 2)) / + ucblock_rate; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT_PER_MES, + mes_exp); + + return 0; +} + +static int cxd2880_set_ber_per_period_t2(struct dvb_frontend *fe) +{ + int ret; + struct dtv_frontend_properties *c; + struct cxd2880_priv *priv; + struct cxd2880_dvbt2_l1pre l1pre; + struct cxd2880_dvbt2_l1post l1post; + struct cxd2880_dvbt2_plp plp; + struct cxd2880_dvbt2_bbheader bbheader; + enum cxd2880_dtv_bandwidth bw = CXD2880_DTV_BW_1_7_MHZ; + u32 pre_ber_rate = 0; + u32 post_ber_rate = 0; + u32 ucblock_rate = 0; + u32 mes_exp = 0; + u32 term_a = 0; + u32 term_b = 0; + u32 denominator = 0; + static const u32 gi_tbl[7] = {32, 64, 128, 256, 8, 152, 76}; + static const u8 n_tbl[6] = {8, 2, 4, 16, 1, 1}; + static const u8 mode_tbl[6] = {2, 8, 4, 1, 16, 32}; + static const u32 kbch_tbl[2][8] = { + {6952, 9472, 10552, 11632, 12352, 13072, 5152, 6232}, + {32128, 38608, 42960, 48328, 51568, 53760, 0, 0} + }; + + if (!fe) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + c = &fe->dtv_property_cache; + bw = priv->dvbt2_tune_param.bandwidth; + + ret = cxd2880_tnrdmd_dvbt2_mon_l1_pre(&priv->tnrdmd, &l1pre); + if (ret) { + pr_info("l1 pre error\n"); + goto error_ber_setting; + } + + ret = cxd2880_tnrdmd_dvbt2_mon_active_plp(&priv->tnrdmd, + CXD2880_DVBT2_PLP_DATA, &plp); + if (ret) { + pr_info("plp info error\n"); + goto error_ber_setting; + } + + ret = cxd2880_tnrdmd_dvbt2_mon_l1_post(&priv->tnrdmd, &l1post); + if (ret) { + pr_info("l1 post error\n"); + goto error_ber_setting; + } + + term_a = + (mode_tbl[l1pre.fft_mode] * (1024 + gi_tbl[l1pre.gi])) * + (l1pre.num_symbols + n_tbl[l1pre.fft_mode]) + 2048; + + if (l1pre.mixed && l1post.fef_intvl) { + term_b = (l1post.fef_length + (l1post.fef_intvl / 2)) / + l1post.fef_intvl; + } else { + term_b = 0; + } + + switch (bw) { + case CXD2880_DTV_BW_1_7_MHZ: + denominator = ((term_a + term_b) * 71 + (131 / 2)) / 131; + break; + case CXD2880_DTV_BW_5_MHZ: + denominator = ((term_a + term_b) * 7 + 20) / 40; + break; + case CXD2880_DTV_BW_6_MHZ: + denominator = ((term_a + term_b) * 7 + 24) / 48; + break; + case CXD2880_DTV_BW_7_MHZ: + denominator = ((term_a + term_b) + 4) / 8; + break; + case CXD2880_DTV_BW_8_MHZ: + default: + denominator = ((term_a + term_b) * 7 + 32) / 64; + break; + } + + if (plp.til_type && plp.til_len) { + pre_ber_rate = + (plp.num_blocks_max * 1000000 + (denominator / 2)) / + denominator; + pre_ber_rate = (pre_ber_rate + (plp.til_len / 2)) / + plp.til_len; + } else { + pre_ber_rate = + (plp.num_blocks_max * 1000000 + (denominator / 2)) / + denominator; + } + + post_ber_rate = pre_ber_rate; + + mes_exp = intlog2(pre_ber_rate) >> 24; + priv->pre_ber_interval = + ((1U << mes_exp) * 1000 + (pre_ber_rate / 2)) / + pre_ber_rate; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT2_LBER_MES, + mes_exp); + + mes_exp = intlog2(post_ber_rate) >> 24; + priv->post_ber_interval = + ((1U << mes_exp) * 1000 + (post_ber_rate / 2)) / + post_ber_rate; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT2_BBER_MES, + mes_exp); + + ret = cxd2880_tnrdmd_dvbt2_mon_bbheader(&priv->tnrdmd, + CXD2880_DVBT2_PLP_DATA, + &bbheader); + if (ret) { + pr_info("bb header error\n"); + goto error_ucblock_setting; + } + + if (bbheader.plp_mode == CXD2880_DVBT2_PLP_MODE_NM) { + if (!bbheader.issy_indicator) { + ucblock_rate = + (pre_ber_rate * kbch_tbl[plp.fec][plp.plp_cr] + + 752) / 1504; + } else { + ucblock_rate = + (pre_ber_rate * kbch_tbl[plp.fec][plp.plp_cr] + + 764) / 1528; + } + } else if (bbheader.plp_mode == CXD2880_DVBT2_PLP_MODE_HEM) { + ucblock_rate = + (pre_ber_rate * kbch_tbl[plp.fec][plp.plp_cr] + 748) / + 1496; + } else { + pr_info("plp mode is not Normal or HEM\n"); + goto error_ucblock_setting; + } + + mes_exp = intlog2(ucblock_rate) >> 24; + priv->ucblock_interval = + ((1U << mes_exp) * 1000 + (ucblock_rate / 2)) / + ucblock_rate; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT2_PER_MES, + mes_exp); + + return 0; + +error_ber_setting: + priv->pre_ber_interval = 1000; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT2_LBER_MES, 0); + + priv->post_ber_interval = 1000; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT2_BBER_MES, 0); + +error_ucblock_setting: + priv->ucblock_interval = 1000; + cxd2880_tnrdmd_set_cfg(&priv->tnrdmd, + CXD2880_TNRDMD_CFG_DVBT2_PER_MES, 8); + + return 0; +} + +static int cxd2880_dvbt_tune(struct cxd2880_tnrdmd *tnr_dmd, + struct cxd2880_dvbt_tune_param + *tune_param) +{ + int ret; + + if (!tnr_dmd || !tune_param) + return -EINVAL; + + if (tnr_dmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB) + return -EINVAL; + + if (tnr_dmd->state != CXD2880_TNRDMD_STATE_SLEEP && + tnr_dmd->state != CXD2880_TNRDMD_STATE_ACTIVE) + return -EINVAL; + + atomic_set(&tnr_dmd->cancel, 0); + + if (tune_param->bandwidth != CXD2880_DTV_BW_5_MHZ && + tune_param->bandwidth != CXD2880_DTV_BW_6_MHZ && + tune_param->bandwidth != CXD2880_DTV_BW_7_MHZ && + tune_param->bandwidth != CXD2880_DTV_BW_8_MHZ) { + return -ENOTTY; + } + + ret = cxd2880_tnrdmd_dvbt_tune1(tnr_dmd, tune_param); + if (ret) + return ret; + + usleep_range(CXD2880_TNRDMD_WAIT_AGC_STABLE * 10000, + CXD2880_TNRDMD_WAIT_AGC_STABLE * 10000 + 1000); + + return cxd2880_tnrdmd_dvbt_tune2(tnr_dmd, tune_param); +} + +static int cxd2880_dvbt2_tune(struct cxd2880_tnrdmd *tnr_dmd, + struct cxd2880_dvbt2_tune_param + *tune_param) +{ + int ret; + + if (!tnr_dmd || !tune_param) + return -EINVAL; + + if (tnr_dmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB) + return -EINVAL; + + if (tnr_dmd->state != CXD2880_TNRDMD_STATE_SLEEP && + tnr_dmd->state != CXD2880_TNRDMD_STATE_ACTIVE) + return -EINVAL; + + atomic_set(&tnr_dmd->cancel, 0); + + if (tune_param->bandwidth != CXD2880_DTV_BW_1_7_MHZ && + tune_param->bandwidth != CXD2880_DTV_BW_5_MHZ && + tune_param->bandwidth != CXD2880_DTV_BW_6_MHZ && + tune_param->bandwidth != CXD2880_DTV_BW_7_MHZ && + tune_param->bandwidth != CXD2880_DTV_BW_8_MHZ) { + return -ENOTTY; + } + + if (tune_param->profile != CXD2880_DVBT2_PROFILE_BASE && + tune_param->profile != CXD2880_DVBT2_PROFILE_LITE) + return -EINVAL; + + ret = cxd2880_tnrdmd_dvbt2_tune1(tnr_dmd, tune_param); + if (ret) + return ret; + + usleep_range(CXD2880_TNRDMD_WAIT_AGC_STABLE * 10000, + CXD2880_TNRDMD_WAIT_AGC_STABLE * 10000 + 1000); + + return cxd2880_tnrdmd_dvbt2_tune2(tnr_dmd, tune_param); +} + +static int cxd2880_set_frontend(struct dvb_frontend *fe) +{ + int ret; + struct dtv_frontend_properties *c; + struct cxd2880_priv *priv; + enum cxd2880_dtv_bandwidth bw = CXD2880_DTV_BW_1_7_MHZ; + + if (!fe) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + c = &fe->dtv_property_cache; + + c->pre_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->pre_bit_error.stat[0].uvalue = 0; + c->pre_bit_error.len = 1; + c->pre_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->pre_bit_count.stat[0].uvalue = 0; + c->pre_bit_count.len = 1; + c->post_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_error.stat[0].uvalue = 0; + c->post_bit_error.len = 1; + c->post_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_count.stat[0].uvalue = 0; + c->post_bit_count.len = 1; + c->block_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_error.stat[0].uvalue = 0; + c->block_error.len = 1; + c->block_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_count.stat[0].uvalue = 0; + c->block_count.len = 1; + + switch (c->bandwidth_hz) { + case 1712000: + bw = CXD2880_DTV_BW_1_7_MHZ; + break; + case 5000000: + bw = CXD2880_DTV_BW_5_MHZ; + break; + case 6000000: + bw = CXD2880_DTV_BW_6_MHZ; + break; + case 7000000: + bw = CXD2880_DTV_BW_7_MHZ; + break; + case 8000000: + bw = CXD2880_DTV_BW_8_MHZ; + break; + default: + return -EINVAL; + } + + priv->s = 0; + + pr_info("sys:%d freq:%d bw:%d\n", + c->delivery_system, c->frequency, bw); + mutex_lock(priv->spi_mutex); + if (c->delivery_system == SYS_DVBT) { + priv->tnrdmd.sys = CXD2880_DTV_SYS_DVBT; + priv->dvbt_tune_param.center_freq_khz = c->frequency / 1000; + priv->dvbt_tune_param.bandwidth = bw; + priv->dvbt_tune_param.profile = CXD2880_DVBT_PROFILE_HP; + ret = cxd2880_dvbt_tune(&priv->tnrdmd, + &priv->dvbt_tune_param); + } else if (c->delivery_system == SYS_DVBT2) { + priv->tnrdmd.sys = CXD2880_DTV_SYS_DVBT2; + priv->dvbt2_tune_param.center_freq_khz = c->frequency / 1000; + priv->dvbt2_tune_param.bandwidth = bw; + priv->dvbt2_tune_param.data_plp_id = (u16)c->stream_id; + priv->dvbt2_tune_param.profile = CXD2880_DVBT2_PROFILE_BASE; + ret = cxd2880_dvbt2_tune(&priv->tnrdmd, + &priv->dvbt2_tune_param); + } else { + pr_err("invalid system\n"); + mutex_unlock(priv->spi_mutex); + return -EINVAL; + } + mutex_unlock(priv->spi_mutex); + + pr_info("tune result %d\n", ret); + + return ret; +} + +static int cxd2880_get_stats(struct dvb_frontend *fe, + enum fe_status status) +{ + struct cxd2880_priv *priv = NULL; + struct dtv_frontend_properties *c = NULL; + u32 pre_bit_err = 0, pre_bit_count = 0; + u32 post_bit_err = 0, post_bit_count = 0; + u32 block_err = 0, block_count = 0; + int ret; + + if (!fe) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + c = &fe->dtv_property_cache; + + if (!(status & FE_HAS_LOCK)) { + c->pre_bit_error.len = 1; + c->pre_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->pre_bit_count.len = 1; + c->pre_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_error.len = 1; + c->post_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_count.len = 1; + c->post_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_error.len = 1; + c->block_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_count.len = 1; + c->block_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + + return 0; + } + + if (time_after(jiffies, priv->pre_ber_update)) { + priv->pre_ber_update = + jiffies + msecs_to_jiffies(priv->pre_ber_interval); + if (c->delivery_system == SYS_DVBT) { + mutex_lock(priv->spi_mutex); + ret = cxd2880_pre_bit_err_t(&priv->tnrdmd, + &pre_bit_err, + &pre_bit_count); + mutex_unlock(priv->spi_mutex); + } else if (c->delivery_system == SYS_DVBT2) { + mutex_lock(priv->spi_mutex); + ret = cxd2880_pre_bit_err_t2(&priv->tnrdmd, + &pre_bit_err, + &pre_bit_count); + mutex_unlock(priv->spi_mutex); + } else { + return -EINVAL; + } + + if (!ret) { + c->pre_bit_error.len = 1; + c->pre_bit_error.stat[0].scale = FE_SCALE_COUNTER; + c->pre_bit_error.stat[0].uvalue += pre_bit_err; + c->pre_bit_count.len = 1; + c->pre_bit_count.stat[0].scale = FE_SCALE_COUNTER; + c->pre_bit_count.stat[0].uvalue += pre_bit_count; + } else { + c->pre_bit_error.len = 1; + c->pre_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->pre_bit_count.len = 1; + c->pre_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + pr_debug("pre_bit_error_t failed %d\n", ret); + } + } + + if (time_after(jiffies, priv->post_ber_update)) { + priv->post_ber_update = + jiffies + msecs_to_jiffies(priv->post_ber_interval); + if (c->delivery_system == SYS_DVBT) { + mutex_lock(priv->spi_mutex); + ret = cxd2880_post_bit_err_t(&priv->tnrdmd, + &post_bit_err, + &post_bit_count); + mutex_unlock(priv->spi_mutex); + } else if (c->delivery_system == SYS_DVBT2) { + mutex_lock(priv->spi_mutex); + ret = cxd2880_post_bit_err_t2(&priv->tnrdmd, + &post_bit_err, + &post_bit_count); + mutex_unlock(priv->spi_mutex); + } else { + return -EINVAL; + } + + if (!ret) { + c->post_bit_error.len = 1; + c->post_bit_error.stat[0].scale = FE_SCALE_COUNTER; + c->post_bit_error.stat[0].uvalue += post_bit_err; + c->post_bit_count.len = 1; + c->post_bit_count.stat[0].scale = FE_SCALE_COUNTER; + c->post_bit_count.stat[0].uvalue += post_bit_count; + } else { + c->post_bit_error.len = 1; + c->post_bit_error.stat[0].scale = + FE_SCALE_NOT_AVAILABLE; + c->post_bit_count.len = 1; + c->post_bit_count.stat[0].scale = + FE_SCALE_NOT_AVAILABLE; + pr_debug("post_bit_err_t %d\n", ret); + } + } + + if (time_after(jiffies, priv->ucblock_update)) { + priv->ucblock_update = + jiffies + msecs_to_jiffies(priv->ucblock_interval); + if (c->delivery_system == SYS_DVBT) { + mutex_lock(priv->spi_mutex); + ret = cxd2880_read_block_err_t(&priv->tnrdmd, + &block_err, + &block_count); + mutex_unlock(priv->spi_mutex); + } else if (c->delivery_system == SYS_DVBT2) { + mutex_lock(priv->spi_mutex); + ret = cxd2880_read_block_err_t2(&priv->tnrdmd, + &block_err, + &block_count); + mutex_unlock(priv->spi_mutex); + } else { + return -EINVAL; + } + if (!ret) { + c->block_error.len = 1; + c->block_error.stat[0].scale = FE_SCALE_COUNTER; + c->block_error.stat[0].uvalue += block_err; + c->block_count.len = 1; + c->block_count.stat[0].scale = FE_SCALE_COUNTER; + c->block_count.stat[0].uvalue += block_count; + } else { + c->block_error.len = 1; + c->block_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_count.len = 1; + c->block_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + pr_debug("read_block_err_t %d\n", ret); + } + } + + return 0; +} + +static int cxd2880_check_l1post_plp(struct dvb_frontend *fe) +{ + u8 valid = 0; + u8 plp_not_found; + int ret; + struct cxd2880_priv *priv = NULL; + + if (!fe) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + + ret = cxd2880_tnrdmd_dvbt2_check_l1post_valid(&priv->tnrdmd, + &valid); + if (ret) + return ret; + + if (!valid) + return -EAGAIN; + + ret = cxd2880_tnrdmd_dvbt2_mon_data_plp_error(&priv->tnrdmd, + &plp_not_found); + if (ret) + return ret; + + if (plp_not_found) { + priv->dvbt2_tune_param.tune_info = + CXD2880_TNRDMD_DVBT2_TUNE_INFO_INVALID_PLP_ID; + } else { + priv->dvbt2_tune_param.tune_info = + CXD2880_TNRDMD_DVBT2_TUNE_INFO_OK; + } + + return 0; +} + +static int cxd2880_read_status(struct dvb_frontend *fe, + enum fe_status *status) +{ + int ret; + u8 sync = 0; + u8 lock = 0; + u8 unlock = 0; + struct cxd2880_priv *priv = NULL; + struct dtv_frontend_properties *c = NULL; + + if (!fe || !status) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + c = &fe->dtv_property_cache; + *status = 0; + + if (priv->tnrdmd.state == CXD2880_TNRDMD_STATE_ACTIVE) { + mutex_lock(priv->spi_mutex); + if (c->delivery_system == SYS_DVBT) { + ret = cxd2880_tnrdmd_dvbt_mon_sync_stat(&priv->tnrdmd, + &sync, + &lock, + &unlock); + } else if (c->delivery_system == SYS_DVBT2) { + ret = cxd2880_tnrdmd_dvbt2_mon_sync_stat(&priv->tnrdmd, + &sync, + &lock, + &unlock); + } else { + pr_err("invalid system"); + mutex_unlock(priv->spi_mutex); + return -EINVAL; + } + + mutex_unlock(priv->spi_mutex); + if (ret) { + pr_err("failed. sys = %d\n", priv->tnrdmd.sys); + return ret; + } + + if (sync == 6) { + *status = FE_HAS_SIGNAL | + FE_HAS_CARRIER; + } + if (lock) + *status |= FE_HAS_VITERBI | + FE_HAS_SYNC | + FE_HAS_LOCK; + } + + pr_debug("status %d\n", *status); + + if (priv->s == 0 && (*status & FE_HAS_LOCK)) { + mutex_lock(priv->spi_mutex); + if (c->delivery_system == SYS_DVBT) { + ret = cxd2880_set_ber_per_period_t(fe); + priv->s = *status; + } else if (c->delivery_system == SYS_DVBT2) { + ret = cxd2880_check_l1post_plp(fe); + if (!ret) { + ret = cxd2880_set_ber_per_period_t2(fe); + priv->s = *status; + } + } else { + pr_err("invalid system\n"); + mutex_unlock(priv->spi_mutex); + return -EINVAL; + } + mutex_unlock(priv->spi_mutex); + } + + cxd2880_get_stats(fe, *status); + return 0; +} + +static int cxd2880_tune(struct dvb_frontend *fe, + bool retune, + unsigned int mode_flags, + unsigned int *delay, + enum fe_status *status) +{ + int ret; + + if (!fe || !delay || !status) { + pr_err("invalid arg."); + return -EINVAL; + } + + if (retune) { + ret = cxd2880_set_frontend(fe); + if (ret) { + pr_err("cxd2880_set_frontend failed %d\n", ret); + return ret; + } + } + + *delay = HZ / 5; + + return cxd2880_read_status(fe, status); +} + +static int cxd2880_get_frontend_t(struct dvb_frontend *fe, + struct dtv_frontend_properties *c) +{ + int ret; + struct cxd2880_priv *priv = NULL; + enum cxd2880_dvbt_mode mode = CXD2880_DVBT_MODE_2K; + enum cxd2880_dvbt_guard guard = CXD2880_DVBT_GUARD_1_32; + struct cxd2880_dvbt_tpsinfo tps; + enum cxd2880_tnrdmd_spectrum_sense sense; + u16 snr = 0; + int strength = 0; + + if (!fe || !c) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_dvbt_mon_mode_guard(&priv->tnrdmd, + &mode, &guard); + mutex_unlock(priv->spi_mutex); + if (!ret) { + switch (mode) { + case CXD2880_DVBT_MODE_2K: + c->transmission_mode = TRANSMISSION_MODE_2K; + break; + case CXD2880_DVBT_MODE_8K: + c->transmission_mode = TRANSMISSION_MODE_8K; + break; + default: + c->transmission_mode = TRANSMISSION_MODE_2K; + pr_debug("transmission mode is invalid %d\n", mode); + break; + } + switch (guard) { + case CXD2880_DVBT_GUARD_1_32: + c->guard_interval = GUARD_INTERVAL_1_32; + break; + case CXD2880_DVBT_GUARD_1_16: + c->guard_interval = GUARD_INTERVAL_1_16; + break; + case CXD2880_DVBT_GUARD_1_8: + c->guard_interval = GUARD_INTERVAL_1_8; + break; + case CXD2880_DVBT_GUARD_1_4: + c->guard_interval = GUARD_INTERVAL_1_4; + break; + default: + c->guard_interval = GUARD_INTERVAL_1_32; + pr_debug("guard interval is invalid %d\n", + guard); + break; + } + } else { + c->transmission_mode = TRANSMISSION_MODE_2K; + c->guard_interval = GUARD_INTERVAL_1_32; + pr_debug("ModeGuard err %d\n", ret); + } + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_dvbt_mon_tps_info(&priv->tnrdmd, &tps); + mutex_unlock(priv->spi_mutex); + if (!ret) { + switch (tps.hierarchy) { + case CXD2880_DVBT_HIERARCHY_NON: + c->hierarchy = HIERARCHY_NONE; + break; + case CXD2880_DVBT_HIERARCHY_1: + c->hierarchy = HIERARCHY_1; + break; + case CXD2880_DVBT_HIERARCHY_2: + c->hierarchy = HIERARCHY_2; + break; + case CXD2880_DVBT_HIERARCHY_4: + c->hierarchy = HIERARCHY_4; + break; + default: + c->hierarchy = HIERARCHY_NONE; + pr_debug("TPSInfo hierarchy is invalid %d\n", + tps.hierarchy); + break; + } + + switch (tps.rate_hp) { + case CXD2880_DVBT_CODERATE_1_2: + c->code_rate_HP = FEC_1_2; + break; + case CXD2880_DVBT_CODERATE_2_3: + c->code_rate_HP = FEC_2_3; + break; + case CXD2880_DVBT_CODERATE_3_4: + c->code_rate_HP = FEC_3_4; + break; + case CXD2880_DVBT_CODERATE_5_6: + c->code_rate_HP = FEC_5_6; + break; + case CXD2880_DVBT_CODERATE_7_8: + c->code_rate_HP = FEC_7_8; + break; + default: + c->code_rate_HP = FEC_NONE; + pr_debug("TPSInfo rateHP is invalid %d\n", + tps.rate_hp); + break; + } + switch (tps.rate_lp) { + case CXD2880_DVBT_CODERATE_1_2: + c->code_rate_LP = FEC_1_2; + break; + case CXD2880_DVBT_CODERATE_2_3: + c->code_rate_LP = FEC_2_3; + break; + case CXD2880_DVBT_CODERATE_3_4: + c->code_rate_LP = FEC_3_4; + break; + case CXD2880_DVBT_CODERATE_5_6: + c->code_rate_LP = FEC_5_6; + break; + case CXD2880_DVBT_CODERATE_7_8: + c->code_rate_LP = FEC_7_8; + break; + default: + c->code_rate_LP = FEC_NONE; + pr_debug("TPSInfo rateLP is invalid %d\n", + tps.rate_lp); + break; + } + switch (tps.constellation) { + case CXD2880_DVBT_CONSTELLATION_QPSK: + c->modulation = QPSK; + break; + case CXD2880_DVBT_CONSTELLATION_16QAM: + c->modulation = QAM_16; + break; + case CXD2880_DVBT_CONSTELLATION_64QAM: + c->modulation = QAM_64; + break; + default: + c->modulation = QPSK; + pr_debug("TPSInfo constellation is invalid %d\n", + tps.constellation); + break; + } + } else { + c->hierarchy = HIERARCHY_NONE; + c->code_rate_HP = FEC_NONE; + c->code_rate_LP = FEC_NONE; + c->modulation = QPSK; + pr_debug("TPS info err %d\n", ret); + } + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_dvbt_mon_spectrum_sense(&priv->tnrdmd, &sense); + mutex_unlock(priv->spi_mutex); + if (!ret) { + switch (sense) { + case CXD2880_TNRDMD_SPECTRUM_NORMAL: + c->inversion = INVERSION_OFF; + break; + case CXD2880_TNRDMD_SPECTRUM_INV: + c->inversion = INVERSION_ON; + break; + default: + c->inversion = INVERSION_OFF; + pr_debug("spectrum sense is invalid %d\n", sense); + break; + } + } else { + c->inversion = INVERSION_OFF; + pr_debug("spectrum_sense %d\n", ret); + } + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_mon_rf_lvl(&priv->tnrdmd, &strength); + mutex_unlock(priv->spi_mutex); + if (!ret) { + c->strength.len = 1; + c->strength.stat[0].scale = FE_SCALE_DECIBEL; + c->strength.stat[0].svalue = strength; + } else { + c->strength.len = 1; + c->strength.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + pr_debug("mon_rf_lvl %d\n", ret); + } + + ret = cxd2880_read_snr(fe, &snr); + if (!ret) { + c->cnr.len = 1; + c->cnr.stat[0].scale = FE_SCALE_DECIBEL; + c->cnr.stat[0].svalue = snr; + } else { + c->cnr.len = 1; + c->cnr.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + pr_debug("read_snr %d\n", ret); + } + + return 0; +} + +static int cxd2880_get_frontend_t2(struct dvb_frontend *fe, + struct dtv_frontend_properties *c) +{ + int ret; + struct cxd2880_priv *priv = NULL; + struct cxd2880_dvbt2_l1pre l1pre; + enum cxd2880_dvbt2_plp_code_rate coderate; + enum cxd2880_dvbt2_plp_constell qam; + enum cxd2880_tnrdmd_spectrum_sense sense; + u16 snr = 0; + int strength = 0; + + if (!fe || !c) { + pr_err("invalid arg.\n"); + return -EINVAL; + } + + priv = fe->demodulator_priv; + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_dvbt2_mon_l1_pre(&priv->tnrdmd, &l1pre); + mutex_unlock(priv->spi_mutex); + if (!ret) { + switch (l1pre.fft_mode) { + case CXD2880_DVBT2_M2K: + c->transmission_mode = TRANSMISSION_MODE_2K; + break; + case CXD2880_DVBT2_M8K: + c->transmission_mode = TRANSMISSION_MODE_8K; + break; + case CXD2880_DVBT2_M4K: + c->transmission_mode = TRANSMISSION_MODE_4K; + break; + case CXD2880_DVBT2_M1K: + c->transmission_mode = TRANSMISSION_MODE_1K; + break; + case CXD2880_DVBT2_M16K: + c->transmission_mode = TRANSMISSION_MODE_16K; + break; + case CXD2880_DVBT2_M32K: + c->transmission_mode = TRANSMISSION_MODE_32K; + break; + default: + c->transmission_mode = TRANSMISSION_MODE_2K; + pr_debug("L1Pre fft_mode is invalid %d\n", + l1pre.fft_mode); + break; + } + switch (l1pre.gi) { + case CXD2880_DVBT2_G1_32: + c->guard_interval = GUARD_INTERVAL_1_32; + break; + case CXD2880_DVBT2_G1_16: + c->guard_interval = GUARD_INTERVAL_1_16; + break; + case CXD2880_DVBT2_G1_8: + c->guard_interval = GUARD_INTERVAL_1_8; + break; + case CXD2880_DVBT2_G1_4: + c->guard_interval = GUARD_INTERVAL_1_4; + break; + case CXD2880_DVBT2_G1_128: + c->guard_interval = GUARD_INTERVAL_1_128; + break; + case CXD2880_DVBT2_G19_128: + c->guard_interval = GUARD_INTERVAL_19_128; + break; + case CXD2880_DVBT2_G19_256: + c->guard_interval = GUARD_INTERVAL_19_256; + break; + default: + c->guard_interval = GUARD_INTERVAL_1_32; + pr_debug("L1Pre guard interval is invalid %d\n", + l1pre.gi); + break; + } + } else { + c->transmission_mode = TRANSMISSION_MODE_2K; + c->guard_interval = GUARD_INTERVAL_1_32; + pr_debug("L1Pre err %d\n", ret); + } + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_dvbt2_mon_code_rate(&priv->tnrdmd, + CXD2880_DVBT2_PLP_DATA, + &coderate); + mutex_unlock(priv->spi_mutex); + if (!ret) { + switch (coderate) { + case CXD2880_DVBT2_R1_2: + c->fec_inner = FEC_1_2; + break; + case CXD2880_DVBT2_R3_5: + c->fec_inner = FEC_3_5; + break; + case CXD2880_DVBT2_R2_3: + c->fec_inner = FEC_2_3; + break; + case CXD2880_DVBT2_R3_4: + c->fec_inner = FEC_3_4; + break; + case CXD2880_DVBT2_R4_5: + c->fec_inner = FEC_4_5; + break; + case CXD2880_DVBT2_R5_6: + c->fec_inner = FEC_5_6; + break; + default: + c->fec_inner = FEC_NONE; + pr_debug("CodeRate is invalid %d\n", coderate); + break; + } + } else { + c->fec_inner = FEC_NONE; + pr_debug("CodeRate %d\n", ret); + } + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_dvbt2_mon_qam(&priv->tnrdmd, + CXD2880_DVBT2_PLP_DATA, + &qam); + mutex_unlock(priv->spi_mutex); + if (!ret) { + switch (qam) { + case CXD2880_DVBT2_QPSK: + c->modulation = QPSK; + break; + case CXD2880_DVBT2_QAM16: + c->modulation = QAM_16; + break; + case CXD2880_DVBT2_QAM64: + c->modulation = QAM_64; + break; + case CXD2880_DVBT2_QAM256: + c->modulation = QAM_256; + break; + default: + c->modulation = QPSK; + pr_debug("QAM is invalid %d\n", qam); + break; + } + } else { + c->modulation = QPSK; + pr_debug("QAM %d\n", ret); + } + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_dvbt2_mon_spectrum_sense(&priv->tnrdmd, &sense); + mutex_unlock(priv->spi_mutex); + if (!ret) { + switch (sense) { + case CXD2880_TNRDMD_SPECTRUM_NORMAL: + c->inversion = INVERSION_OFF; + break; + case CXD2880_TNRDMD_SPECTRUM_INV: + c->inversion = INVERSION_ON; + break; + default: + c->inversion = INVERSION_OFF; + pr_debug("spectrum sense is invalid %d\n", sense); + break; + } + } else { + c->inversion = INVERSION_OFF; + pr_debug("SpectrumSense %d\n", ret); + } + + mutex_lock(priv->spi_mutex); + ret = cxd2880_tnrdmd_mon_rf_lvl(&priv->tnrdmd, &strength); + mutex_unlock(priv->spi_mutex); + if (!ret) { + c->strength.len = 1; + c->strength.stat[0].scale = FE_SCALE_DECIBEL; + c->strength.stat[0].svalue = strength; + } else { + c->strength.len = 1; + c->strength.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + pr_debug("mon_rf_lvl %d\n", ret); + } + + ret = cxd2880_read_snr(fe, &snr); + if (!ret) { + c->cnr.len = 1; + c->cnr.stat[0].scale = FE_SCALE_DECIBEL; + c->cnr.stat[0].svalue = snr; + } else { + c->cnr.len = 1; + c->cnr.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + pr_debug("read_snr %d\n", ret); + } + + return 0; +} + +static int cxd2880_get_frontend(struct dvb_frontend *fe, + struct dtv_frontend_properties *props) +{ + struct cxd2880_priv *priv = NULL; + int ret; + + if (!fe || !props) { + pr_err("invalid arg."); + return -EINVAL; + } + + priv = fe->demodulator_priv; + + pr_debug("system=%d\n", fe->dtv_property_cache.delivery_system); + switch (fe->dtv_property_cache.delivery_system) { + case SYS_DVBT: + ret = cxd2880_get_frontend_t(fe, props); + break; + case SYS_DVBT2: + ret = cxd2880_get_frontend_t2(fe, props); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum dvbfe_algo cxd2880_get_frontend_algo(struct dvb_frontend *fe) +{ + return DVBFE_ALGO_HW; +} + +static struct dvb_frontend_ops cxd2880_dvbt_t2_ops = { + .info = { + .name = "Sony CXD2880", + .frequency_min = 174000000, + .frequency_max = 862000000, + .frequency_stepsize = 1000, + .caps = FE_CAN_INVERSION_AUTO | + FE_CAN_FEC_1_2 | + FE_CAN_FEC_2_3 | + FE_CAN_FEC_3_4 | + FE_CAN_FEC_4_5 | + FE_CAN_FEC_5_6 | + FE_CAN_FEC_7_8 | + FE_CAN_FEC_AUTO | + FE_CAN_QPSK | + FE_CAN_QAM_16 | + FE_CAN_QAM_32 | + FE_CAN_QAM_64 | + FE_CAN_QAM_128 | + FE_CAN_QAM_256 | + FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_2G_MODULATION | + FE_CAN_RECOVER | + FE_CAN_MUTE_TS, + }, + .delsys = { SYS_DVBT, SYS_DVBT2 }, + + .release = cxd2880_release, + .init = cxd2880_init, + .sleep = cxd2880_sleep, + .tune = cxd2880_tune, + .set_frontend = cxd2880_set_frontend, + .get_frontend = cxd2880_get_frontend, + .read_status = cxd2880_read_status, + .read_ber = cxd2880_read_ber, + .read_signal_strength = cxd2880_read_signal_strength, + .read_snr = cxd2880_read_snr, + .read_ucblocks = cxd2880_read_ucblocks, + .get_frontend_algo = cxd2880_get_frontend_algo, +}; + +struct dvb_frontend *cxd2880_attach(struct dvb_frontend *fe, + struct cxd2880_config *cfg) +{ + int ret; + enum cxd2880_tnrdmd_chip_id chipid = + CXD2880_TNRDMD_CHIP_ID_UNKNOWN; + static struct cxd2880_priv *priv; + u8 data = 0; + + if (!fe) { + pr_err("invalid arg.\n"); + return NULL; + } + + priv = kzalloc(sizeof(struct cxd2880_priv), GFP_KERNEL); + if (!priv) + return NULL; + + priv->spi = cfg->spi; + priv->spi_mutex = cfg->spi_mutex; + priv->spi_device.spi = cfg->spi; + + memcpy(&fe->ops, &cxd2880_dvbt_t2_ops, + sizeof(struct dvb_frontend_ops)); + + ret = cxd2880_spi_device_initialize(&priv->spi_device, + CXD2880_SPI_MODE_0, + 55000000); + if (ret) { + pr_err("spi_device_initialize failed. %d\n", ret); + kfree(priv); + return NULL; + } + + ret = cxd2880_spi_device_create_spi(&priv->cxd2880_spi, + &priv->spi_device); + if (ret) { + pr_err("spi_device_create_spi failed. %d\n", ret); + kfree(priv); + return NULL; + } + + ret = cxd2880_io_spi_create(&priv->regio, &priv->cxd2880_spi, 0); + if (ret) { + pr_err("io_spi_create failed. %d\n", ret); + kfree(priv); + return NULL; + } + ret = priv->regio.write_reg(&priv->regio, + CXD2880_IO_TGT_SYS, 0x00, 0x00); + if (ret) { + pr_err("set bank to 0x00 failed.\n"); + kfree(priv); + return NULL; + } + ret = priv->regio.read_regs(&priv->regio, + CXD2880_IO_TGT_SYS, 0xfd, &data, 1); + if (ret) { + pr_err("read chip id failed.\n"); + kfree(priv); + return NULL; + } + + chipid = (enum cxd2880_tnrdmd_chip_id)data; + if (chipid != CXD2880_TNRDMD_CHIP_ID_CXD2880_ES1_0X && + chipid != CXD2880_TNRDMD_CHIP_ID_CXD2880_ES1_11) { + pr_err("chip id invalid.\n"); + kfree(priv); + return NULL; + } + + fe->demodulator_priv = priv; + pr_info("CXD2880 driver version: Ver %s\n", + CXD2880_TNRDMD_DRIVER_VERSION); + + return fe; +} +EXPORT_SYMBOL(cxd2880_attach); + +MODULE_DESCRIPTION("Sony CXD2880 DVB-T2/T tuner + demod driver"); +MODULE_AUTHOR("Sony Semiconductor Solutions Corporation"); +MODULE_LICENSE("GPL v2"); -- 2.15.1 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html