All of lore.kernel.org
 help / color / mirror / Atom feed
From: <Yasunari.Takiguchi@sony.com>
To: <linux-kernel@vger.kernel.org>, <devicetree@vger.kernel.org>,
	<linux-media@vger.kernel.org>
Cc: <tbird20d@gmail.com>, <frowand.list@gmail.com>,
	<Yasunari.Takiguchi@sony.com>, <Masayuki.Yamamoto@sony.com>,
	<Hideki.Nozawa@sony.com>, <Kota.Yonezawa@sony.com>,
	<Toshihiko.Matsumoto@sony.com>, <Satoshi.C.Watanabe@sony.com>
Subject: [PATCH v5 07/12] [media] cxd2880: Add top level of the driver
Date: Thu, 18 Jan 2018 17:52:17 +0900	[thread overview]
Message-ID: <20180118085217.21465-1-Yasunari.Takiguchi@sony.com> (raw)
In-Reply-To: <20180118084016.20689-1-Yasunari.Takiguchi@sony.com>

From: Yasunari Takiguchi <Yasunari.Takiguchi@sony.com>

This provides the main dvb frontend operation functions
for the Sony CXD2880 DVB-T2/T tuner + demodulator driver.

Signed-off-by: Yasunari Takiguchi <Yasunari.Takiguchi@sony.com>
Signed-off-by: Masayuki Yamamoto <Masayuki.Yamamoto@sony.com>
Signed-off-by: Hideki Nozawa <Hideki.Nozawa@sony.com>
Signed-off-by: Kota Yonezawa <Kota.Yonezawa@sony.com>
Signed-off-by: Toshihiko Matsumoto <Toshihiko.Matsumoto@sony.com>
Signed-off-by: Satoshi Watanabe <Satoshi.C.Watanabe@sony.com>
---

[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 <linux/spi/spi.h>
+
+#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

WARNING: multiple messages have this Message-ID (diff)
From: <Yasunari.Takiguchi-7U/KSKJipcs@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
Subject: [PATCH v5 07/12] [media] cxd2880: Add top level of the driver
Date: Thu, 18 Jan 2018 17:52:17 +0900	[thread overview]
Message-ID: <20180118085217.21465-1-Yasunari.Takiguchi@sony.com> (raw)
In-Reply-To: <20180118084016.20689-1-Yasunari.Takiguchi-7U/KSKJipcs@public.gmane.org>

From: Yasunari Takiguchi <Yasunari.Takiguchi-7U/KSKJipcs@public.gmane.org>

This provides the main dvb frontend operation functions
for the Sony CXD2880 DVB-T2/T tuner + demodulator driver.

Signed-off-by: Yasunari Takiguchi <Yasunari.Takiguchi-7U/KSKJipcs@public.gmane.org>
Signed-off-by: Masayuki Yamamoto <Masayuki.Yamamoto-7U/KSKJipcs@public.gmane.org>
Signed-off-by: Hideki Nozawa <Hideki.Nozawa-7U/KSKJipcs@public.gmane.org>
Signed-off-by: Kota Yonezawa <Kota.Yonezawa-7U/KSKJipcs@public.gmane.org>
Signed-off-by: Toshihiko Matsumoto <Toshihiko.Matsumoto-7U/KSKJipcs@public.gmane.org>
Signed-off-by: Satoshi Watanabe <Satoshi.C.Watanabe-7U/KSKJipcs@public.gmane.org>
---

[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 <linux/spi/spi.h>
+
+#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

  parent reply	other threads:[~2018-01-18  8:48 UTC|newest]

Thread overview: 34+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-01-18  8:40 [PATCH v5 00/12] [dt-bindings] [media] Add document file and driver for Sony CXD2880 DVB-T2/T tuner + demodulator Yasunari.Takiguchi
2018-01-18  8:40 ` Yasunari.Takiguchi
2018-01-18  8:43 ` [PATCH v5 01/12] [dt-bindings] [media] Add document file for CXD2880 SPI I/F Yasunari.Takiguchi
2018-01-18  8:43   ` Yasunari.Takiguchi-7U/KSKJipcs
2018-01-18  8:46 ` [PATCH v5 02/12] [media] cxd2880-spi: Add support for CXD2880 SPI interface Yasunari.Takiguchi
2018-01-18  8:46   ` Yasunari.Takiguchi-7U/KSKJipcs
2018-03-07 10:15   ` Mauro Carvalho Chehab
2018-03-07 10:15     ` Mauro Carvalho Chehab
2018-03-08  0:51     ` Yasunari.Takiguchi
2018-03-08  0:51       ` Yasunari.Takiguchi
2018-04-06  0:28     ` Yasunari.Takiguchi
2018-04-06  0:28       ` Yasunari.Takiguchi
2018-01-18  8:47 ` [PATCH v5 03/12] [media] cxd2880: Add common files for the driver Yasunari.Takiguchi
2018-01-18  8:47   ` Yasunari.Takiguchi-7U/KSKJipcs
2018-01-18  8:48 ` [PATCH v5 04/12] [media] cxd2880: Add spi device IO routines Yasunari.Takiguchi
2018-01-18  8:48   ` Yasunari.Takiguchi
2018-01-18  8:50 ` [PATCH v5 05/12] [media] cxd2880: Add tuner part of the driver Yasunari.Takiguchi
2018-01-18  8:50   ` Yasunari.Takiguchi
2018-01-18  8:51 ` [PATCH v5 06/12] [media] cxd2880: Add integration layer for " Yasunari.Takiguchi
2018-01-18  8:51   ` Yasunari.Takiguchi
2018-01-18  8:52 ` Yasunari.Takiguchi [this message]
2018-01-18  8:52   ` [PATCH v5 07/12] [media] cxd2880: Add top level of " Yasunari.Takiguchi-7U/KSKJipcs
2018-01-18  8:53 ` [PATCH v5 08/12] [media] cxd2880: Add DVB-T control functions " Yasunari.Takiguchi
2018-01-18  8:53   ` Yasunari.Takiguchi-7U/KSKJipcs
2018-01-18  8:54 ` [PATCH v5 09/12] [media] cxd2880: Add DVB-T monitor functions Yasunari.Takiguchi
2018-01-18  8:54   ` Yasunari.Takiguchi
2018-01-18  8:55 ` [PATCH v5 10/12] [media] cxd2880: Add DVB-T2 control functions for the driver Yasunari.Takiguchi
2018-01-18  8:55   ` Yasunari.Takiguchi-7U/KSKJipcs
2018-01-18  8:57 ` [PATCH v5 11/12] [media] cxd2880: Add DVB-T2 monitor functions Yasunari.Takiguchi
2018-01-18  8:57   ` Yasunari.Takiguchi-7U/KSKJipcs
2018-01-18  8:59 ` [PATCH v5 12/12] [media] cxd2880: Add all Makefile, Kconfig files and Update MAINTAINERS file for the driver Yasunari.Takiguchi
2018-01-18  8:59   ` Yasunari.Takiguchi
2018-02-26  0:49 ` [PATCH v5 00/12] [dt-bindings] [media] Add document file and driver for Sony CXD2880 DVB-T2/T tuner + demodulator Yasunari.Takiguchi
2018-02-26  0:49   ` Yasunari.Takiguchi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20180118085217.21465-1-Yasunari.Takiguchi@sony.com \
    --to=yasunari.takiguchi@sony.com \
    --cc=Hideki.Nozawa@sony.com \
    --cc=Kota.Yonezawa@sony.com \
    --cc=Masayuki.Yamamoto@sony.com \
    --cc=Satoshi.C.Watanabe@sony.com \
    --cc=Toshihiko.Matsumoto@sony.com \
    --cc=devicetree@vger.kernel.org \
    --cc=frowand.list@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-media@vger.kernel.org \
    --cc=tbird20d@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.