diff mbox

[v2,08/15,media] cxd2880: Add top level of the driver

Message ID 20170414023150.17685-1-Yasunari.Takiguchi@sony.com (mailing list archive)
State New, archived
Headers show

Commit Message

Yasunari.Takiguchi@sony.com April 14, 2017, 2:31 a.m. UTC
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>
---
 drivers/media/dvb-frontends/cxd2880/cxd2880_top.c | 1550 +++++++++++++++++++++
 1 file changed, 1550 insertions(+)
 create mode 100644 drivers/media/dvb-frontends/cxd2880/cxd2880_top.c

Comments

Mauro Carvalho Chehab June 13, 2017, 1:30 p.m. UTC | #1
Em Fri, 14 Apr 2017 11:31:50 +0900
<Yasunari.Takiguchi@sony.com> escreveu:

> 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.

For now, I'll do only a quick review on patches 6-15, as there are several
things to be ajusted on the code, from my past comments.

I'll do a better review on the next version. So, I'll focus only on
a few random issues I see on the code below.

> 
> 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>
> ---
>  drivers/media/dvb-frontends/cxd2880/cxd2880_top.c | 1550 +++++++++++++++++++++
>  1 file changed, 1550 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..66d78fb93a13
> --- /dev/null
> +++ b/drivers/media/dvb-frontends/cxd2880/cxd2880_top.c
> @@ -0,0 +1,1550 @@
> +/*
> + * cxd2880_top.c
> + * Sony CXD2880 DVB-T2/T tuner + demodulator driver
> + *
> + * Copyright (C) 2016, 2017 Sony Semiconductor Solutions Corporation
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the
> + * Free Software Foundation; version 2 of the License.
> + *
> + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
> + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
> + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
> + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
> + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
> + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
> + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
> + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
> + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <linux/spi/spi.h>
> +
> +#include "dvb_frontend.h"
> +
> +#include "cxd2880.h"
> +#include "cxd2880_tnrdmd_mon.h"
> +#include "cxd2880_tnrdmd_dvbt2_mon.h"
> +#include "cxd2880_tnrdmd_dvbt_mon.h"
> +#include "cxd2880_integ_dvbt2.h"
> +#include "cxd2880_integ_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 */
> +};
> +
> +/*
> + * return value conversion table
> + */
> +static int return_tbl[] = {
> +	0,             /* CXD2880_RESULT_OK */
> +	-EINVAL,       /* CXD2880_RESULT_ERROR_ARG*/
> +	-EIO,          /* CXD2880_RESULT_ERROR_IO */
> +	-EPERM,        /* CXD2880_RESULT_ERROR_SW_STATE */
> +	-EBUSY,        /* CXD2880_RESULT_ERROR_HW_STATE */
> +	-ETIME,        /* CXD2880_RESULT_ERROR_TIMEOUT */
> +	-EAGAIN,       /* CXD2880_RESULT_ERROR_UNLOCK */
> +	-ERANGE,       /* CXD2880_RESULT_ERROR_RANGE */
> +	-EOPNOTSUPP,   /* CXD2880_RESULT_ERROR_NOSUPPORT */
> +	-ECANCELED,    /* CXD2880_RESULT_ERROR_CANCEL */
> +	-EPERM,        /* CXD2880_RESULT_ERROR_OTHER */
> +	-EOVERFLOW,    /* CXD2880_RESULT_ERROR_OVERFLOW */
> +	0,             /* CXD2880_RESULT_OK_CONFIRM */
> +};

Please, don't use a conversion table. That makes the driver more
complex to be understood without any good reason. Instead, just 
replace the values at the original enum.

> +
> +static enum cxd2880_ret cxd2880_pre_bit_err_t(
> +		struct cxd2880_tnrdmd *tnrdmd, u32 *pre_bit_err,
> +		u32 *pre_bit_count)
> +{
> +	u8 rdata[2];
> +
> +	if ((!tnrdmd) || (!pre_bit_err) || (!pre_bit_count))
> +		return CXD2880_RESULT_ERROR_ARG;
> +
> +	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
> +		return CXD2880_RESULT_ERROR_ARG;
> +
> +	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
> +		return CXD2880_RESULT_ERROR_SW_STATE;
> +
> +	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT)
> +		return CXD2880_RESULT_ERROR_SW_STATE;
> +
> +	if (slvt_freeze_reg(tnrdmd) != CXD2880_RESULT_OK)
> +		return CXD2880_RESULT_ERROR_IO;
> +
> +	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +				0x00, 0x10) != CXD2880_RESULT_OK) {
> +		slvt_unfreeze_reg(tnrdmd);
> +		return CXD2880_RESULT_ERROR_IO;
> +	}
> +
> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +				0x39, rdata, 1) != CXD2880_RESULT_OK) {
> +		slvt_unfreeze_reg(tnrdmd);
> +		return CXD2880_RESULT_ERROR_IO;
> +	}
> +
> +	if ((rdata[0] & 0x01) == 0) {
> +		slvt_unfreeze_reg(tnrdmd);
> +		return CXD2880_RESULT_ERROR_HW_STATE;
> +	}
> +
> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +				0x22, rdata, 2) != CXD2880_RESULT_OK) {
> +		slvt_unfreeze_reg(tnrdmd);
> +		return CXD2880_RESULT_ERROR_IO;
> +	}
> +
> +	*pre_bit_err = (rdata[0] << 8) | rdata[1];
> +
> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +				0x6F, rdata, 1) != CXD2880_RESULT_OK) {
> +		slvt_unfreeze_reg(tnrdmd);
> +		return CXD2880_RESULT_ERROR_IO;
> +	}
> +
> +	slvt_unfreeze_reg(tnrdmd);
> +
> +	*pre_bit_count = ((rdata[0] & 0x07) == 0) ?
> +			256 : (0x1000 << (rdata[0] & 0x07));
> +
> +	return CXD2880_RESULT_OK;
> +}
> +
> +static enum cxd2880_ret 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];
> +
> +	if ((!tnrdmd) || (!pre_bit_err) || (!pre_bit_count))
> +		return CXD2880_RESULT_ERROR_ARG;
> +
> +	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
> +		return CXD2880_RESULT_ERROR_ARG;
> +
> +	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
> +		return CXD2880_RESULT_ERROR_SW_STATE;
> +
> +	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT2)
> +		return CXD2880_RESULT_ERROR_SW_STATE;
> +
> +	if (slvt_freeze_reg(tnrdmd) != CXD2880_RESULT_OK)
> +		return CXD2880_RESULT_ERROR_IO;
> +
> +	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +				0x00, 0x0B) != CXD2880_RESULT_OK) {
> +		slvt_unfreeze_reg(tnrdmd);
> +		return CXD2880_RESULT_ERROR_IO;
> +	}
> +
> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +				0x3C, data, sizeof(data))
> +				 != CXD2880_RESULT_OK) {
> +		slvt_unfreeze_reg(tnrdmd);
> +		return CXD2880_RESULT_ERROR_IO;
> +	}
> +
> +	if (!(data[0] & 0x01)) {
> +		slvt_unfreeze_reg(tnrdmd);
> +		return CXD2880_RESULT_ERROR_HW_STATE;
> +	}
> +	*pre_bit_err =
> +	((data[1] & 0x0F) << 24) | (data[2] << 16) | (data[3] << 8) | data[4];
> +
> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +				0xA0, data, 1) != CXD2880_RESULT_OK) {
> +		slvt_unfreeze_reg(tnrdmd);
> +		return CXD2880_RESULT_ERROR_IO;
> +	}
> +
> +	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);
> +
> +	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +				0x00, 0x20) != CXD2880_RESULT_OK)
> +		return CXD2880_RESULT_ERROR_IO;
> +
> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +				0x6F, data, 1) != CXD2880_RESULT_OK)
> +		return CXD2880_RESULT_ERROR_IO;
> +
> +	period_exp = data[0] & 0x0F;
> +
> +	*pre_bit_count = (1U << period_exp) * n_ldpc;
> +
> +	return CXD2880_RESULT_OK;
> +}
> +
> +static enum cxd2880_ret 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;
> +
> +	if ((!tnrdmd) || (!post_bit_err) || (!post_bit_count))
> +		return CXD2880_RESULT_ERROR_ARG;
> +
> +	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
> +		return CXD2880_RESULT_ERROR_ARG;
> +
> +	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
> +		return CXD2880_RESULT_ERROR_SW_STATE;
> +
> +	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT)
> +		return CXD2880_RESULT_ERROR_SW_STATE;
> +
> +	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +				0x00, 0x0D) != CXD2880_RESULT_OK)
> +		return CXD2880_RESULT_ERROR_IO;
> +
> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +				0x15, rdata, 3) != CXD2880_RESULT_OK)
> +		return CXD2880_RESULT_ERROR_IO;
> +
> +	if ((rdata[0] & 0x40) == 0)
> +		return CXD2880_RESULT_ERROR_HW_STATE;
> +
> +	*post_bit_err = ((rdata[0] & 0x3F) << 16) | (rdata[1] << 8) | rdata[2];
> +
> +	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +				0x00, 0x10) != CXD2880_RESULT_OK)
> +		return CXD2880_RESULT_ERROR_IO;
> +
> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +				0x60, rdata, 1) != CXD2880_RESULT_OK)
> +		return CXD2880_RESULT_ERROR_IO;
> +
> +	period_exp = (rdata[0] & 0x1F);
> +
> +	if ((period_exp <= 11) && (bit_error > (1U << period_exp) * 204 * 8))
> +		return CXD2880_RESULT_ERROR_HW_STATE;
> +
> +	if (period_exp == 11)
> +		*post_bit_count = 3342336;
> +	else
> +		*post_bit_count = (1U << period_exp) * 204 * 81;
> +
> +	return CXD2880_RESULT_OK;
> +}
> +
> +static enum cxd2880_ret 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;
> +
> +	if ((!tnrdmd) || (!post_bit_err) || (!post_bit_count))
> +		return CXD2880_RESULT_ERROR_ARG;
> +
> +	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
> +		return CXD2880_RESULT_ERROR_ARG;
> +
> +	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
> +		return CXD2880_RESULT_ERROR_SW_STATE;
> +
> +	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT2)
> +		return CXD2880_RESULT_ERROR_SW_STATE;
> +
> +	{
> +		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;
> +
> +		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 (slvt_freeze_reg(tnrdmd) != CXD2880_RESULT_OK)
> +			return CXD2880_RESULT_ERROR_IO;
> +
> +		if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +					0x00, 0x0B) != CXD2880_RESULT_OK) {
> +			slvt_unfreeze_reg(tnrdmd);
> +			return CXD2880_RESULT_ERROR_IO;
> +		}
> +
> +		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +					 0x15, data, 3) != CXD2880_RESULT_OK) {
> +			slvt_unfreeze_reg(tnrdmd);
> +			return CXD2880_RESULT_ERROR_IO;
> +		}
> +
> +		if (!(data[0] & 0x40)) {
> +			slvt_unfreeze_reg(tnrdmd);
> +			return CXD2880_RESULT_ERROR_HW_STATE;
> +		}
> +
> +		*post_bit_err =
> +			((data[0] & 0x3F) << 16) | (data[1] << 8) | data[2];
> +
> +		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +					0x9D, data, 1) != CXD2880_RESULT_OK) {
> +			slvt_unfreeze_reg(tnrdmd);
> +			return CXD2880_RESULT_ERROR_IO;
> +		}
> +
> +		plp_code_rate =
> +		(enum cxd2880_dvbt2_plp_code_rate)(data[0] & 0x07);
> +
> +		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +					0xA0, data, 1) != CXD2880_RESULT_OK) {
> +			slvt_unfreeze_reg(tnrdmd);
> +			return CXD2880_RESULT_ERROR_IO;
> +		}
> +
> +		plp_fec_type = (enum cxd2880_dvbt2_plp_fec)(data[0] & 0x03);
> +
> +		slvt_unfreeze_reg(tnrdmd);
> +
> +		if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +					0x00, 0x20) != CXD2880_RESULT_OK)
> +			return CXD2880_RESULT_ERROR_IO;
> +
> +		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +					0x72, data, 1) != CXD2880_RESULT_OK)
> +			return CXD2880_RESULT_ERROR_IO;
> +
> +		period_exp = data[0] & 0x0F;
> +
> +		if ((plp_fec_type > CXD2880_DVBT2_FEC_LDPC_64K) ||
> +			(plp_code_rate > CXD2880_DVBT2_R2_5))
> +			return CXD2880_RESULT_ERROR_HW_STATE;
> +
> +		n_bch = n_bch_bits_lookup[plp_fec_type][plp_code_rate];
> +	}
> +
> +	if (*post_bit_err > ((1U << period_exp) * n_bch))
> +		return CXD2880_RESULT_ERROR_HW_STATE;
> +
> +	*post_bit_count = (1U << period_exp) * n_bch;
> +
> +	return CXD2880_RESULT_OK;
> +}
> +
> +static enum cxd2880_ret cxd2880_read_block_err_t(
> +					struct cxd2880_tnrdmd *tnrdmd,
> +					u32 *block_err,
> +					u32 *block_count)
> +{
> +	u8 rdata[3];
> +
> +	if ((!tnrdmd) || (!block_err) || (!block_count))
> +		return CXD2880_RESULT_ERROR_ARG;
> +
> +	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
> +		return CXD2880_RESULT_ERROR_ARG;
> +
> +	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
> +		return CXD2880_RESULT_ERROR_SW_STATE;
> +
> +	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT)
> +		return CXD2880_RESULT_ERROR_SW_STATE;
> +
> +	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +				0x00, 0x0D) != CXD2880_RESULT_OK)
> +		return CXD2880_RESULT_ERROR_IO;
> +
> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +				0x18, rdata, 3) != CXD2880_RESULT_OK)
> +		return CXD2880_RESULT_ERROR_IO;
> +
> +	if ((rdata[0] & 0x01) == 0)
> +		return CXD2880_RESULT_ERROR_HW_STATE;
> +
> +	*block_err = (rdata[1] << 8) | rdata[2];
> +
> +	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +				0x00, 0x10) != CXD2880_RESULT_OK)
> +		return CXD2880_RESULT_ERROR_IO;
> +
> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +				0x5C, rdata, 1) != CXD2880_RESULT_OK)
> +		return CXD2880_RESULT_ERROR_IO;
> +
> +	*block_count = 1U << (rdata[0] & 0x0F);
> +
> +	if ((*block_count == 0) || (*block_err > *block_count))
> +		return CXD2880_RESULT_ERROR_HW_STATE;
> +
> +	return CXD2880_RESULT_OK;
> +}
> +
> +static enum cxd2880_ret cxd2880_read_block_err_t2(
> +					struct cxd2880_tnrdmd *tnrdmd,
> +					u32 *block_err,
> +					u32 *block_count)
> +{
> +	if ((!tnrdmd) || (!block_err) || (!block_count))
> +		return CXD2880_RESULT_ERROR_ARG;
> +
> +	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
> +		return CXD2880_RESULT_ERROR_ARG;
> +
> +	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
> +		return CXD2880_RESULT_ERROR_SW_STATE;
> +	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT2)
> +		return CXD2880_RESULT_ERROR_SW_STATE;
> +
> +	{
> +		u8 rdata[3];
> +
> +		if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +					0x00, 0x0B) != CXD2880_RESULT_OK)
> +			return CXD2880_RESULT_ERROR_IO;
> +
> +		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +					0x18, rdata, 3) != CXD2880_RESULT_OK)
> +			return CXD2880_RESULT_ERROR_IO;
> +
> +		if ((rdata[0] & 0x01) == 0)
> +			return CXD2880_RESULT_ERROR_HW_STATE;
> +
> +		*block_err = (rdata[1] << 8) | rdata[2];
> +
> +		if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +					0x00, 0x24) != CXD2880_RESULT_OK)
> +			return CXD2880_RESULT_ERROR_IO;
> +
> +		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
> +					0xDC, rdata, 1) != CXD2880_RESULT_OK)
> +			return CXD2880_RESULT_ERROR_IO;
> +
> +		*block_count = 1U << (rdata[0] & 0x0F);
> +	}
> +
> +	if ((*block_count == 0) || (*block_err > *block_count))
> +		return CXD2880_RESULT_ERROR_HW_STATE;
> +
> +	return CXD2880_RESULT_OK;
> +}
> +
> +static void cxd2880_release(struct dvb_frontend *fe)
> +{
> +	struct cxd2880_priv *priv = NULL;
> +
> +	if (!fe) {
> +		pr_err("%s: invalid arg.\n", __func__);
> +		return;
> +	}
> +	priv = (struct cxd2880_priv *)fe->demodulator_priv;
> +	kfree(priv);
> +}
> +
> +static int cxd2880_init(struct dvb_frontend *fe)
> +{
> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
> +	struct cxd2880_priv *priv = NULL;
> +	struct cxd2880_tnrdmd_create_param create_param;
> +
> +	if (!fe) {
> +		pr_err("%s: invalid arg.\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	priv = (struct cxd2880_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 != CXD2880_RESULT_OK) {
> +			mutex_unlock(priv->spi_mutex);
> +			dev_info(&priv->spi->dev,
> +				"%s: cxd2880 tnrdmd create failed %d\n",
> +				__func__, ret);
> +			return return_tbl[ret];
> +		}
> +	}
> +	ret = cxd2880_integ_init(&priv->tnrdmd);
> +	if (ret != CXD2880_RESULT_OK) {
> +		mutex_unlock(priv->spi_mutex);
> +		dev_err(&priv->spi->dev, "%s: cxd2880 integ init failed %d\n",
> +				__func__, ret);
> +		return return_tbl[ret];
> +	}
> +	mutex_unlock(priv->spi_mutex);
> +
> +	dev_dbg(&priv->spi->dev, "%s: OK.\n", __func__);
> +
> +	return return_tbl[ret];
> +}
> +
> +static int cxd2880_sleep(struct dvb_frontend *fe)
> +{
> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
> +	struct cxd2880_priv *priv = NULL;
> +
> +	if (!fe) {
> +		pr_err("%s: inavlid arg\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	priv = (struct cxd2880_priv *)fe->demodulator_priv;
> +
> +	mutex_lock(priv->spi_mutex);
> +	ret = cxd2880_tnrdmd_sleep(&priv->tnrdmd);
> +	mutex_unlock(priv->spi_mutex);
> +
> +	dev_dbg(&priv->spi->dev, "%s: tnrdmd_sleep ret %d\n",
> +		__func__, ret);
> +
> +	return return_tbl[ret];
> +}
> +
> +static int cxd2880_read_signal_strength(struct dvb_frontend *fe,
> +				u16 *strength)
> +{
> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
> +	struct cxd2880_priv *priv = NULL;
> +	struct dtv_frontend_properties *c = NULL;
> +	int level = 0;
> +
> +	if ((!fe) || (!strength)) {
> +		pr_err("%s: inavlid arg\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	priv = (struct cxd2880_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 {
> +		dev_dbg(&priv->spi->dev, "%s: invalid system\n", __func__);
> +		mutex_unlock(priv->spi_mutex);
> +		return -EINVAL;
> +	}
> +	mutex_unlock(priv->spi_mutex);
> +
> +	level /= 125;
> +	/* -105dBm - -30dBm (-105000/125 = -840, -30000/125 = -240 */
> +	level = clamp(level, -840, -240);
> +	/* scale value to 0x0000-0xFFFF */
> +	*strength = (u16)(((level + 840) * 0xFFFF) / (-240 + 840));
> +
> +	if (ret != CXD2880_RESULT_OK)
> +		dev_dbg(&priv->spi->dev, "%s: ret = %d\n", __func__, ret);
> +
> +	return return_tbl[ret];
> +}
> +
> +static int cxd2880_read_snr(struct dvb_frontend *fe, u16 *snr)
> +{
> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
> +	int snrvalue = 0;
> +	struct cxd2880_priv *priv = NULL;
> +	struct dtv_frontend_properties *c = NULL;
> +
> +	if ((!fe) || (!snr)) {
> +		pr_err("%s: inavlid arg\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	priv = (struct cxd2880_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 {
> +		dev_err(&priv->spi->dev, "%s: invalid system\n", __func__);
> +		mutex_unlock(priv->spi_mutex);
> +		return -EINVAL;
> +	}
> +	mutex_unlock(priv->spi_mutex);
> +
> +	if (snrvalue < 0)
> +		snrvalue = 0;
> +	*snr = (u16)snrvalue;
> +
> +	if (ret != CXD2880_RESULT_OK)
> +		dev_dbg(&priv->spi->dev, "%s: ret = %d\n", __func__, ret);
> +
> +	return return_tbl[ret];
> +}
> +
> +static int cxd2880_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
> +{
> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
> +	struct cxd2880_priv *priv = NULL;
> +	struct dtv_frontend_properties *c = NULL;
> +
> +	if ((!fe) || (!ucblocks)) {
> +		pr_err("%s: inavlid arg\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	priv = (struct cxd2880_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 {
> +		dev_err(&priv->spi->dev, "%s: invlaid system\n", __func__);
> +		mutex_unlock(priv->spi_mutex);
> +		return -EINVAL;
> +	}
> +	mutex_unlock(priv->spi_mutex);
> +
> +	if (ret != CXD2880_RESULT_OK)
> +		dev_dbg(&priv->spi->dev, "%s: ret = %d\n", __func__, ret);
> +
> +	return return_tbl[ret];
> +}
> +
> +static int cxd2880_read_ber(struct dvb_frontend *fe, u32 *ber)
> +{
> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
> +	struct cxd2880_priv *priv = NULL;
> +	struct dtv_frontend_properties *c = NULL;
> +
> +	if ((!fe) || (!ber)) {
> +		pr_err("%s: inavlid arg\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	priv = (struct cxd2880_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_pre_rsber(&priv->tnrdmd,
> +						ber);
> +		/* x100 to change unit.(10^7 -> 10^9 */
> +		*ber *= 100;
> +	} else if (c->delivery_system == SYS_DVBT2) {
> +		ret = cxd2880_tnrdmd_dvbt2_mon_pre_bchber(&priv->tnrdmd,
> +						ber);
> +	} else {
> +		dev_err(&priv->spi->dev, "%s: invlaid system\n", __func__);
> +		mutex_unlock(priv->spi_mutex);
> +		return -EINVAL;
> +	}
> +	mutex_unlock(priv->spi_mutex);
> +
> +	if (ret != CXD2880_RESULT_OK)
> +		dev_dbg(&priv->spi->dev, "%s: ret = %d\n", __func__, ret);
> +
> +	return return_tbl[ret];
> +}
> +
> +static int cxd2880_set_frontend(struct dvb_frontend *fe)
> +{
> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
> +	struct dtv_frontend_properties *c;
> +	struct cxd2880_priv *priv;
> +	enum cxd2880_dtv_bandwidth bw = CXD2880_DTV_BW_1_7_MHZ;
> +
> +	if (!fe) {
> +		pr_err("%s: inavlid arg\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	priv = (struct cxd2880_priv *)fe->demodulator_priv;
> +	c = &fe->dtv_property_cache;
> +
> +	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;
> +	}
> +
> +	dev_info(&priv->spi->dev, "%s: sys:%d freq:%d bw:%d\n", __func__,
> +			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_integ_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;
> +		ret = cxd2880_integ_dvbt2_tune(&priv->tnrdmd,
> +						&priv->dvbt2_tune_param);
> +	} else {
> +		dev_err(&priv->spi->dev, "%s: invalid system\n", __func__);
> +		mutex_unlock(priv->spi_mutex);
> +		return -EINVAL;
> +	}
> +	mutex_unlock(priv->spi_mutex);
> +	dev_info(&priv->spi->dev, "%s: tune result %d\n", __func__, ret);
> +
> +	return return_tbl[ret];
> +}
> +
> +static int cxd2880_read_status(struct dvb_frontend *fe,
> +				enum fe_status *status)
> +{
> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
> +	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("%s: invalid arg\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	priv = (struct cxd2880_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 {
> +			dev_err(&priv->spi->dev,
> +				"%s: invlaid system", __func__);
> +			mutex_unlock(priv->spi_mutex);
> +			return -EINVAL;
> +		}
> +
> +		mutex_unlock(priv->spi_mutex);
> +		if (ret != CXD2880_RESULT_OK) {
> +			dev_err(&priv->spi->dev, "%s: failed. sys = %d\n",
> +				__func__, priv->tnrdmd.sys);
> +			return  return_tbl[ret];
> +		}
> +
> +		if (sync == 6) {
> +			*status = FE_HAS_SIGNAL |
> +					FE_HAS_CARRIER;
> +		}
> +		if (lock)
> +			*status |= FE_HAS_VITERBI |
> +					FE_HAS_SYNC |
> +					FE_HAS_LOCK;
> +	}
> +
> +	dev_dbg(&priv->spi->dev, "%s: status %d result %d\n", __func__,
> +		*status, ret);
> +
> +	return  return_tbl[CXD2880_RESULT_OK];
> +}
> +
> +static int cxd2880_tune(struct dvb_frontend *fe,
> +			bool retune,
> +			unsigned int mode_flags,
> +			unsigned int *delay,
> +			enum fe_status *status)
> +{
> +	int ret = 0;
> +
> +	if ((!fe) || (!delay) || (!status)) {
> +		pr_err("%s: invalid arg.", __func__);
> +		return -EINVAL;
> +	}
> +
> +	if (retune) {
> +		ret = cxd2880_set_frontend(fe);
> +		if (ret) {
> +			pr_err("%s: cxd2880_set_frontend failed %d\n",
> +				__func__, 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)
> +{
> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
> +	int result = 0;
> +	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;
> +	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;
> +
> +	if ((!fe) || (!c)) {
> +		pr_err("%s: invalid arg\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	priv = (struct cxd2880_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 == CXD2880_RESULT_OK) {
> +		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;
> +			dev_err(&priv->spi->dev, "%s: get invalid mode %d\n",
> +					__func__, 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;
> +			dev_err(&priv->spi->dev, "%s: get invalid guard %d\n",
> +					__func__, guard);
> +			break;
> +		}
> +	} else {
> +		c->transmission_mode = TRANSMISSION_MODE_2K;
> +		c->guard_interval = GUARD_INTERVAL_1_32;
> +		dev_dbg(&priv->spi->dev,
> +			"%s: ModeGuard err %d\n", __func__, ret);
> +	}
> +
> +	mutex_lock(priv->spi_mutex);
> +	ret = cxd2880_tnrdmd_dvbt_mon_tps_info(&priv->tnrdmd, &tps);
> +	mutex_unlock(priv->spi_mutex);
> +	if (ret == CXD2880_RESULT_OK) {
> +		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;
> +			dev_err(&priv->spi->dev,
> +				"%s: TPSInfo hierarchy invalid %d\n",
> +				__func__, 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;
> +			dev_err(&priv->spi->dev,
> +				"%s: TPSInfo rateHP invalid %d\n",
> +				__func__, 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;
> +			dev_err(&priv->spi->dev,
> +				"%s: TPSInfo rateLP invalid %d\n",
> +				__func__, 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;
> +			dev_err(&priv->spi->dev,
> +				"%s: TPSInfo constellation invalid %d\n",
> +				__func__, tps.constellation);
> +			break;
> +		}
> +	} else {
> +		c->hierarchy = HIERARCHY_NONE;
> +		c->code_rate_HP = FEC_NONE;
> +		c->code_rate_LP = FEC_NONE;
> +		c->modulation = QPSK;
> +		dev_dbg(&priv->spi->dev,
> +			"%s: TPS info err %d\n", __func__, ret);
> +	}
> +
> +	mutex_lock(priv->spi_mutex);
> +	ret = cxd2880_tnrdmd_dvbt_mon_spectrum_sense(&priv->tnrdmd, &sense);
> +	mutex_unlock(priv->spi_mutex);
> +	if (ret == CXD2880_RESULT_OK) {
> +		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;
> +			dev_err(&priv->spi->dev,
> +				"%s: spectrum sense invalid %d\n",
> +				__func__, sense);
> +			break;
> +		}
> +	} else {
> +		c->inversion = INVERSION_OFF;
> +		dev_dbg(&priv->spi->dev,
> +			"%s: spectrum_sense %d\n", __func__, ret);
> +	}
> +
> +	mutex_lock(priv->spi_mutex);
> +	ret = cxd2880_tnrdmd_mon_rf_lvl(&priv->tnrdmd, &strength);
> +	mutex_unlock(priv->spi_mutex);
> +	if (ret == CXD2880_RESULT_OK) {
> +		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;
> +		dev_dbg(&priv->spi->dev, "%s: mon_rf_lvl %d\n",
> +			__func__, result);
> +	}
> +
> +	result = cxd2880_read_snr(fe, &snr);
> +	if (!result) {
> +		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;
> +		dev_dbg(&priv->spi->dev, "%s: read_snr %d\n", __func__, result);
> +	}
> +
> +	mutex_lock(priv->spi_mutex);
> +	ret = cxd2880_pre_bit_err_t(&priv->tnrdmd, &pre_bit_err,
> +					&pre_bit_count);

Hmm... reading BER-based measures at get_frontend() may not be
the best idea, depending on how the hardware works.

I mean, you need to be sure that, if userspace is calling it too
often, it won't affect the hardware or the measurement.

On other drivers, where each call generates an I2C access,
we do that by moving the code that actually call the hardware
at get status, and we use jiffies to be sure that it won't be
called too often.

I dunno if, on your SPI design, this would be an issue or not.

> +	mutex_unlock(priv->spi_mutex);
> +	if (ret == CXD2880_RESULT_OK) {
> +		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;
> +		dev_dbg(&priv->spi->dev,
> +			"%s: pre_bit_error_t failed %d\n",
> +			__func__, ret);
> +	}
> +
> +	mutex_lock(priv->spi_mutex);
> +	ret = cxd2880_post_bit_err_t(&priv->tnrdmd,
> +				&post_bit_err, &post_bit_count);
> +	mutex_unlock(priv->spi_mutex);
> +	if (ret == CXD2880_RESULT_OK) {
> +		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;
> +		dev_dbg(&priv->spi->dev,
> +			"%s: post_bit_err_t %d\n", __func__, ret);
> +	}
> +
> +	mutex_lock(priv->spi_mutex);
> +	ret = cxd2880_read_block_err_t(&priv->tnrdmd,
> +					&block_err, &block_count);
> +	mutex_unlock(priv->spi_mutex);
> +	if (ret == CXD2880_RESULT_OK) {
> +		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;
> +		dev_dbg(&priv->spi->dev,
> +			"%s: read_block_err_t  %d\n", __func__, ret);
> +	}
> +
> +	return 0;
> +}
> +
> +static int cxd2880_get_frontend_t2(struct dvb_frontend *fe,
> +				struct dtv_frontend_properties *c)
> +{
> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
> +	int result = 0;
> +	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;
> +	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;
> +
> +	if ((!fe) || (!c)) {
> +		pr_err("%s: invalid arg.\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	priv = (struct cxd2880_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 == CXD2880_RESULT_OK) {
> +		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;
> +			dev_err(&priv->spi->dev,
> +				"%s: L1Pre fft_mode invalid %d\n",
> +				__func__, 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;
> +			dev_err(&priv->spi->dev,
> +				"%s: L1Pre gi invalid %d\n",
> +				__func__, l1pre.gi);
> +			break;
> +		}
> +	} else {
> +		c->transmission_mode = TRANSMISSION_MODE_2K;
> +		c->guard_interval = GUARD_INTERVAL_1_32;
> +		dev_dbg(&priv->spi->dev,
> +			"%s: L1Pre err %d\n", __func__, 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 == CXD2880_RESULT_OK) {
> +		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;
> +			dev_err(&priv->spi->dev,
> +				"%s: CodeRate invalid %d\n",
> +				__func__, coderate);
> +			break;
> +		}
> +	} else {
> +		c->fec_inner = FEC_NONE;
> +		dev_dbg(&priv->spi->dev, "%s: CodeRate %d\n", __func__, 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 == CXD2880_RESULT_OK) {
> +		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;
> +			dev_err(&priv->spi->dev,
> +				"%s: QAM invalid %d\n",
> +				__func__, qam);
> +			break;
> +		}
> +	} else {
> +		c->modulation = QPSK;
> +		dev_dbg(&priv->spi->dev, "%s: QAM %d\n", __func__, ret);
> +	}
> +
> +	mutex_lock(priv->spi_mutex);
> +	ret = cxd2880_tnrdmd_dvbt2_mon_spectrum_sense(&priv->tnrdmd, &sense);
> +	mutex_unlock(priv->spi_mutex);
> +	if (ret == CXD2880_RESULT_OK) {
> +		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;
> +			dev_err(&priv->spi->dev,
> +				"%s: spectrum sense invalid %d\n",
> +				__func__, sense);
> +			break;
> +		}
> +	} else {
> +		c->inversion = INVERSION_OFF;
> +		dev_dbg(&priv->spi->dev,
> +			"%s: SpectrumSense %d\n", __func__, ret);
> +	}
> +
> +	mutex_lock(priv->spi_mutex);
> +	ret = cxd2880_tnrdmd_mon_rf_lvl(&priv->tnrdmd, &strength);
> +	mutex_unlock(priv->spi_mutex);
> +	if (ret == CXD2880_RESULT_OK) {
> +		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;
> +		dev_dbg(&priv->spi->dev,
> +			"%s: mon_rf_lvl %d\n", __func__, ret);
> +	}
> +
> +	result = cxd2880_read_snr(fe, &snr);
> +	if (!result) {
> +		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;
> +		dev_dbg(&priv->spi->dev, "%s: read_snr %d\n", __func__, result);
> +	}
> +
> +	mutex_lock(priv->spi_mutex);
> +	ret = cxd2880_pre_bit_err_t2(&priv->tnrdmd,
> +				&pre_bit_err,
> +				&pre_bit_count);
> +	mutex_unlock(priv->spi_mutex);
> +	if (ret == CXD2880_RESULT_OK) {
> +		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;
> +		dev_dbg(&priv->spi->dev,
> +			"%s: read_bit_err_t2 %d\n", __func__, ret);
> +	}
> +
> +	mutex_lock(priv->spi_mutex);
> +	ret = cxd2880_post_bit_err_t2(&priv->tnrdmd,
> +				&post_bit_err, &post_bit_count);
> +	mutex_unlock(priv->spi_mutex);
> +	if (ret == CXD2880_RESULT_OK) {
> +		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;
> +		dev_dbg(&priv->spi->dev,
> +			"%s: post_bit_err_t2 %d\n", __func__, ret);
> +	}
> +
> +	mutex_lock(priv->spi_mutex);
> +	ret = cxd2880_read_block_err_t2(&priv->tnrdmd,
> +					&block_err, &block_count);
> +	mutex_unlock(priv->spi_mutex);
> +	if (ret == CXD2880_RESULT_OK) {
> +		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;
> +		dev_dbg(&priv->spi->dev,
> +			"%s: read_block_err_t2 %d\n", __func__, ret);
> +	}
> +
> +	return 0;
> +}
> +
> +static int cxd2880_get_frontend(struct dvb_frontend *fe,
> +				struct dtv_frontend_properties *props)
> +{
> +	struct cxd2880_priv *priv = NULL;
> +	int result = 0;
> +
> +	if ((!fe) || (!props)) {
> +		pr_err("%s: invalid arg.", __func__);
> +		return -EINVAL;
> +	}
> +
> +	priv = (struct cxd2880_priv *)fe->demodulator_priv;
> +
> +	dev_dbg(&priv->spi->dev, "%s: system=%d\n", __func__,
> +		fe->dtv_property_cache.delivery_system);
> +	switch (fe->dtv_property_cache.delivery_system) {
> +	case SYS_DVBT:
> +		result = cxd2880_get_frontend_t(fe, props);
> +		break;
> +	case SYS_DVBT2:
> +		result = cxd2880_get_frontend_t2(fe, props);
> +		break;

Hmm... I noticed that already when reviewing other parts of the driver...

At patch 6, you defined:


enum cxd2880_dtv_sys {
+	CXD2880_DTV_SYS_UNKNOWN,
+	CXD2880_DTV_SYS_DVBT,
+	CXD2880_DTV_SYS_DVBT2,
+	CXD2880_DTV_SYS_ISDBT,
+	CXD2880_DTV_SYS_ISDBTSB,
+	CXD2880_DTV_SYS_ISDBTMM_A,
+	CXD2880_DTV_SYS_ISDBTMM_B,
+	CXD2880_DTV_SYS_ANY
+};

So, I was expecting to see not only DVB T/T2 here (and at the tuner
driver) but also ISDB, in order to match the defined standards,
but you're setting only DVB T/T2 here, and some parts of the tuner
driver also seem to be focused only on those two standards.

So, the question is: does the hardware support ISDB? If so,
please add a /* FIXME */ note where pertinent, saying that
ISDB support should be added in the future. If, on the other 
hand, the hardware doesn't support it at all, please cleanup the
code by removing references for it.


> +	default:
> +		result = -EINVAL;
> +		break;
> +	}
> +
> +	return result;
> +}
> +
> +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;
> +
> +struct dvb_frontend *cxd2880_attach(struct dvb_frontend *fe,
> +				struct cxd2880_config *cfg)
> +{
> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
> +	enum cxd2880_tnrdmd_chip_id chipid =
> +					CXD2880_TNRDMD_CHIP_ID_UNKNOWN;
> +	static struct cxd2880_priv *priv;
> +	u8 data = 0;
> +
> +	if (!fe) {
> +		pr_err("%s: invalid arg.\n", __func__);
> +		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 != CXD2880_RESULT_OK) {
> +		dev_err(&priv->spi->dev,
> +			"%s: spi_device_initialize failed. %d\n",
> +			__func__, ret);
> +		kfree(priv);
> +		return NULL;
> +	}
> +
> +	ret = cxd2880_spi_device_create_spi(&priv->cxd2880_spi,
> +					&priv->spi_device);
> +	if (ret != CXD2880_RESULT_OK) {
> +		dev_err(&priv->spi->dev,
> +			"%s: spi_device_create_spi failed. %d\n",
> +			__func__, ret);
> +		kfree(priv);
> +		return NULL;
> +	}
> +
> +	ret = cxd2880_io_spi_create(&priv->regio, &priv->cxd2880_spi, 0);
> +	if (ret != CXD2880_RESULT_OK) {
> +		dev_err(&priv->spi->dev,
> +			"%s: io_spi_create failed. %d\n", __func__, ret);
> +		kfree(priv);
> +		return NULL;
> +	}
> +	if (priv->regio.write_reg(&priv->regio, CXD2880_IO_TGT_SYS, 0x00, 0x00)
> +		!= CXD2880_RESULT_OK) {
> +		dev_err(&priv->spi->dev,
> +			"%s: set bank to 0x00 failed.\n", __func__);
> +		kfree(priv);
> +		return NULL;
> +	}
> +	if (priv->regio.read_regs(&priv->regio,
> +					CXD2880_IO_TGT_SYS, 0xFD, &data, 1)
> +					!= CXD2880_RESULT_OK) {
> +		dev_err(&priv->spi->dev,
> +			"%s: read chip id failed.\n", __func__);
> +		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)) {
> +		dev_err(&priv->spi->dev,
> +			"%s: chip id invalid.\n", __func__);
> +		kfree(priv);
> +		return NULL;
> +	}
> +
> +	fe->demodulator_priv = priv;
> +	dev_info(&priv->spi->dev,
> +		"CXD2880 driver version: Ver %s\n",
> +		CXD2880_TNRDMD_DRIVER_VERSION);
> +
> +	return fe;
> +}
> +EXPORT_SYMBOL(cxd2880_attach);
> +
> +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,
> +};
> +
> +MODULE_DESCRIPTION(
> +"Sony CXD2880 DVB-T2/T tuner + demodulator drvier");
> +MODULE_AUTHOR("Sony Semiconductor Solutions Corporation");
> +MODULE_LICENSE("GPL v2");



Thanks,
Mauro
Yasunari.Takiguchi@sony.com June 19, 2017, 7:56 a.m. UTC | #2
Thanks for your kindly reply.

We are think of how to change our driver to follow the comments now.
I reply one thing about reading BER measures at get_frontend() in this mail.

On 2017/06/13 22:30, Mauro Carvalho Chehab wrote:
> Em Fri, 14 Apr 2017 11:31:50 +0900
> <Yasunari.Takiguchi@sony.com> escreveu:
> 
>> 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.
> 
> For now, I'll do only a quick review on patches 6-15, as there are several
> things to be ajusted on the code, from my past comments.
> 
> I'll do a better review on the next version. So, I'll focus only on
> a few random issues I see on the code below.
> 
>>
>> 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>
>> ---
>>  drivers/media/dvb-frontends/cxd2880/cxd2880_top.c | 1550 +++++++++++++++++++++
>>  1 file changed, 1550 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..66d78fb93a13
>> --- /dev/null
>> +++ b/drivers/media/dvb-frontends/cxd2880/cxd2880_top.c
>> @@ -0,0 +1,1550 @@
>> +/*
>> + * cxd2880_top.c
>> + * Sony CXD2880 DVB-T2/T tuner + demodulator driver
>> + *
>> + * Copyright (C) 2016, 2017 Sony Semiconductor Solutions Corporation
>> + *
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms of the GNU General Public License as published by the
>> + * Free Software Foundation; version 2 of the License.
>> + *
>> + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
>> + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
>> + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
>> + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
>> + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
>> + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
>> + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
>> + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
>> + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
>> + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
>> + *
>> + * You should have received a copy of the GNU General Public License along
>> + * with this program; if not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#include <linux/spi/spi.h>
>> +
>> +#include "dvb_frontend.h"
>> +
>> +#include "cxd2880.h"
>> +#include "cxd2880_tnrdmd_mon.h"
>> +#include "cxd2880_tnrdmd_dvbt2_mon.h"
>> +#include "cxd2880_tnrdmd_dvbt_mon.h"
>> +#include "cxd2880_integ_dvbt2.h"
>> +#include "cxd2880_integ_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 */
>> +};
>> +
>> +/*
>> + * return value conversion table
>> + */
>> +static int return_tbl[] = {
>> +	0,             /* CXD2880_RESULT_OK */
>> +	-EINVAL,       /* CXD2880_RESULT_ERROR_ARG*/
>> +	-EIO,          /* CXD2880_RESULT_ERROR_IO */
>> +	-EPERM,        /* CXD2880_RESULT_ERROR_SW_STATE */
>> +	-EBUSY,        /* CXD2880_RESULT_ERROR_HW_STATE */
>> +	-ETIME,        /* CXD2880_RESULT_ERROR_TIMEOUT */
>> +	-EAGAIN,       /* CXD2880_RESULT_ERROR_UNLOCK */
>> +	-ERANGE,       /* CXD2880_RESULT_ERROR_RANGE */
>> +	-EOPNOTSUPP,   /* CXD2880_RESULT_ERROR_NOSUPPORT */
>> +	-ECANCELED,    /* CXD2880_RESULT_ERROR_CANCEL */
>> +	-EPERM,        /* CXD2880_RESULT_ERROR_OTHER */
>> +	-EOVERFLOW,    /* CXD2880_RESULT_ERROR_OVERFLOW */
>> +	0,             /* CXD2880_RESULT_OK_CONFIRM */
>> +};
> 
> Please, don't use a conversion table. That makes the driver more
> complex to be understood without any good reason. Instead, just 
> replace the values at the original enum.
> 
>> +
>> +static enum cxd2880_ret cxd2880_pre_bit_err_t(
>> +		struct cxd2880_tnrdmd *tnrdmd, u32 *pre_bit_err,
>> +		u32 *pre_bit_count)
>> +{
>> +	u8 rdata[2];
>> +
>> +	if ((!tnrdmd) || (!pre_bit_err) || (!pre_bit_count))
>> +		return CXD2880_RESULT_ERROR_ARG;
>> +
>> +	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
>> +		return CXD2880_RESULT_ERROR_ARG;
>> +
>> +	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
>> +		return CXD2880_RESULT_ERROR_SW_STATE;
>> +
>> +	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT)
>> +		return CXD2880_RESULT_ERROR_SW_STATE;
>> +
>> +	if (slvt_freeze_reg(tnrdmd) != CXD2880_RESULT_OK)
>> +		return CXD2880_RESULT_ERROR_IO;
>> +
>> +	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +				0x00, 0x10) != CXD2880_RESULT_OK) {
>> +		slvt_unfreeze_reg(tnrdmd);
>> +		return CXD2880_RESULT_ERROR_IO;
>> +	}
>> +
>> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +				0x39, rdata, 1) != CXD2880_RESULT_OK) {
>> +		slvt_unfreeze_reg(tnrdmd);
>> +		return CXD2880_RESULT_ERROR_IO;
>> +	}
>> +
>> +	if ((rdata[0] & 0x01) == 0) {
>> +		slvt_unfreeze_reg(tnrdmd);
>> +		return CXD2880_RESULT_ERROR_HW_STATE;
>> +	}
>> +
>> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +				0x22, rdata, 2) != CXD2880_RESULT_OK) {
>> +		slvt_unfreeze_reg(tnrdmd);
>> +		return CXD2880_RESULT_ERROR_IO;
>> +	}
>> +
>> +	*pre_bit_err = (rdata[0] << 8) | rdata[1];
>> +
>> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +				0x6F, rdata, 1) != CXD2880_RESULT_OK) {
>> +		slvt_unfreeze_reg(tnrdmd);
>> +		return CXD2880_RESULT_ERROR_IO;
>> +	}
>> +
>> +	slvt_unfreeze_reg(tnrdmd);
>> +
>> +	*pre_bit_count = ((rdata[0] & 0x07) == 0) ?
>> +			256 : (0x1000 << (rdata[0] & 0x07));
>> +
>> +	return CXD2880_RESULT_OK;
>> +}
>> +
>> +static enum cxd2880_ret 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];
>> +
>> +	if ((!tnrdmd) || (!pre_bit_err) || (!pre_bit_count))
>> +		return CXD2880_RESULT_ERROR_ARG;
>> +
>> +	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
>> +		return CXD2880_RESULT_ERROR_ARG;
>> +
>> +	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
>> +		return CXD2880_RESULT_ERROR_SW_STATE;
>> +
>> +	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT2)
>> +		return CXD2880_RESULT_ERROR_SW_STATE;
>> +
>> +	if (slvt_freeze_reg(tnrdmd) != CXD2880_RESULT_OK)
>> +		return CXD2880_RESULT_ERROR_IO;
>> +
>> +	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +				0x00, 0x0B) != CXD2880_RESULT_OK) {
>> +		slvt_unfreeze_reg(tnrdmd);
>> +		return CXD2880_RESULT_ERROR_IO;
>> +	}
>> +
>> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +				0x3C, data, sizeof(data))
>> +				 != CXD2880_RESULT_OK) {
>> +		slvt_unfreeze_reg(tnrdmd);
>> +		return CXD2880_RESULT_ERROR_IO;
>> +	}
>> +
>> +	if (!(data[0] & 0x01)) {
>> +		slvt_unfreeze_reg(tnrdmd);
>> +		return CXD2880_RESULT_ERROR_HW_STATE;
>> +	}
>> +	*pre_bit_err =
>> +	((data[1] & 0x0F) << 24) | (data[2] << 16) | (data[3] << 8) | data[4];
>> +
>> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +				0xA0, data, 1) != CXD2880_RESULT_OK) {
>> +		slvt_unfreeze_reg(tnrdmd);
>> +		return CXD2880_RESULT_ERROR_IO;
>> +	}
>> +
>> +	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);
>> +
>> +	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +				0x00, 0x20) != CXD2880_RESULT_OK)
>> +		return CXD2880_RESULT_ERROR_IO;
>> +
>> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +				0x6F, data, 1) != CXD2880_RESULT_OK)
>> +		return CXD2880_RESULT_ERROR_IO;
>> +
>> +	period_exp = data[0] & 0x0F;
>> +
>> +	*pre_bit_count = (1U << period_exp) * n_ldpc;
>> +
>> +	return CXD2880_RESULT_OK;
>> +}
>> +
>> +static enum cxd2880_ret 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;
>> +
>> +	if ((!tnrdmd) || (!post_bit_err) || (!post_bit_count))
>> +		return CXD2880_RESULT_ERROR_ARG;
>> +
>> +	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
>> +		return CXD2880_RESULT_ERROR_ARG;
>> +
>> +	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
>> +		return CXD2880_RESULT_ERROR_SW_STATE;
>> +
>> +	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT)
>> +		return CXD2880_RESULT_ERROR_SW_STATE;
>> +
>> +	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +				0x00, 0x0D) != CXD2880_RESULT_OK)
>> +		return CXD2880_RESULT_ERROR_IO;
>> +
>> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +				0x15, rdata, 3) != CXD2880_RESULT_OK)
>> +		return CXD2880_RESULT_ERROR_IO;
>> +
>> +	if ((rdata[0] & 0x40) == 0)
>> +		return CXD2880_RESULT_ERROR_HW_STATE;
>> +
>> +	*post_bit_err = ((rdata[0] & 0x3F) << 16) | (rdata[1] << 8) | rdata[2];
>> +
>> +	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +				0x00, 0x10) != CXD2880_RESULT_OK)
>> +		return CXD2880_RESULT_ERROR_IO;
>> +
>> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +				0x60, rdata, 1) != CXD2880_RESULT_OK)
>> +		return CXD2880_RESULT_ERROR_IO;
>> +
>> +	period_exp = (rdata[0] & 0x1F);
>> +
>> +	if ((period_exp <= 11) && (bit_error > (1U << period_exp) * 204 * 8))
>> +		return CXD2880_RESULT_ERROR_HW_STATE;
>> +
>> +	if (period_exp == 11)
>> +		*post_bit_count = 3342336;
>> +	else
>> +		*post_bit_count = (1U << period_exp) * 204 * 81;
>> +
>> +	return CXD2880_RESULT_OK;
>> +}
>> +
>> +static enum cxd2880_ret 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;
>> +
>> +	if ((!tnrdmd) || (!post_bit_err) || (!post_bit_count))
>> +		return CXD2880_RESULT_ERROR_ARG;
>> +
>> +	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
>> +		return CXD2880_RESULT_ERROR_ARG;
>> +
>> +	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
>> +		return CXD2880_RESULT_ERROR_SW_STATE;
>> +
>> +	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT2)
>> +		return CXD2880_RESULT_ERROR_SW_STATE;
>> +
>> +	{
>> +		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;
>> +
>> +		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 (slvt_freeze_reg(tnrdmd) != CXD2880_RESULT_OK)
>> +			return CXD2880_RESULT_ERROR_IO;
>> +
>> +		if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +					0x00, 0x0B) != CXD2880_RESULT_OK) {
>> +			slvt_unfreeze_reg(tnrdmd);
>> +			return CXD2880_RESULT_ERROR_IO;
>> +		}
>> +
>> +		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +					 0x15, data, 3) != CXD2880_RESULT_OK) {
>> +			slvt_unfreeze_reg(tnrdmd);
>> +			return CXD2880_RESULT_ERROR_IO;
>> +		}
>> +
>> +		if (!(data[0] & 0x40)) {
>> +			slvt_unfreeze_reg(tnrdmd);
>> +			return CXD2880_RESULT_ERROR_HW_STATE;
>> +		}
>> +
>> +		*post_bit_err =
>> +			((data[0] & 0x3F) << 16) | (data[1] << 8) | data[2];
>> +
>> +		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +					0x9D, data, 1) != CXD2880_RESULT_OK) {
>> +			slvt_unfreeze_reg(tnrdmd);
>> +			return CXD2880_RESULT_ERROR_IO;
>> +		}
>> +
>> +		plp_code_rate =
>> +		(enum cxd2880_dvbt2_plp_code_rate)(data[0] & 0x07);
>> +
>> +		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +					0xA0, data, 1) != CXD2880_RESULT_OK) {
>> +			slvt_unfreeze_reg(tnrdmd);
>> +			return CXD2880_RESULT_ERROR_IO;
>> +		}
>> +
>> +		plp_fec_type = (enum cxd2880_dvbt2_plp_fec)(data[0] & 0x03);
>> +
>> +		slvt_unfreeze_reg(tnrdmd);
>> +
>> +		if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +					0x00, 0x20) != CXD2880_RESULT_OK)
>> +			return CXD2880_RESULT_ERROR_IO;
>> +
>> +		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +					0x72, data, 1) != CXD2880_RESULT_OK)
>> +			return CXD2880_RESULT_ERROR_IO;
>> +
>> +		period_exp = data[0] & 0x0F;
>> +
>> +		if ((plp_fec_type > CXD2880_DVBT2_FEC_LDPC_64K) ||
>> +			(plp_code_rate > CXD2880_DVBT2_R2_5))
>> +			return CXD2880_RESULT_ERROR_HW_STATE;
>> +
>> +		n_bch = n_bch_bits_lookup[plp_fec_type][plp_code_rate];
>> +	}
>> +
>> +	if (*post_bit_err > ((1U << period_exp) * n_bch))
>> +		return CXD2880_RESULT_ERROR_HW_STATE;
>> +
>> +	*post_bit_count = (1U << period_exp) * n_bch;
>> +
>> +	return CXD2880_RESULT_OK;
>> +}
>> +
>> +static enum cxd2880_ret cxd2880_read_block_err_t(
>> +					struct cxd2880_tnrdmd *tnrdmd,
>> +					u32 *block_err,
>> +					u32 *block_count)
>> +{
>> +	u8 rdata[3];
>> +
>> +	if ((!tnrdmd) || (!block_err) || (!block_count))
>> +		return CXD2880_RESULT_ERROR_ARG;
>> +
>> +	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
>> +		return CXD2880_RESULT_ERROR_ARG;
>> +
>> +	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
>> +		return CXD2880_RESULT_ERROR_SW_STATE;
>> +
>> +	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT)
>> +		return CXD2880_RESULT_ERROR_SW_STATE;
>> +
>> +	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +				0x00, 0x0D) != CXD2880_RESULT_OK)
>> +		return CXD2880_RESULT_ERROR_IO;
>> +
>> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +				0x18, rdata, 3) != CXD2880_RESULT_OK)
>> +		return CXD2880_RESULT_ERROR_IO;
>> +
>> +	if ((rdata[0] & 0x01) == 0)
>> +		return CXD2880_RESULT_ERROR_HW_STATE;
>> +
>> +	*block_err = (rdata[1] << 8) | rdata[2];
>> +
>> +	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +				0x00, 0x10) != CXD2880_RESULT_OK)
>> +		return CXD2880_RESULT_ERROR_IO;
>> +
>> +	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +				0x5C, rdata, 1) != CXD2880_RESULT_OK)
>> +		return CXD2880_RESULT_ERROR_IO;
>> +
>> +	*block_count = 1U << (rdata[0] & 0x0F);
>> +
>> +	if ((*block_count == 0) || (*block_err > *block_count))
>> +		return CXD2880_RESULT_ERROR_HW_STATE;
>> +
>> +	return CXD2880_RESULT_OK;
>> +}
>> +
>> +static enum cxd2880_ret cxd2880_read_block_err_t2(
>> +					struct cxd2880_tnrdmd *tnrdmd,
>> +					u32 *block_err,
>> +					u32 *block_count)
>> +{
>> +	if ((!tnrdmd) || (!block_err) || (!block_count))
>> +		return CXD2880_RESULT_ERROR_ARG;
>> +
>> +	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
>> +		return CXD2880_RESULT_ERROR_ARG;
>> +
>> +	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
>> +		return CXD2880_RESULT_ERROR_SW_STATE;
>> +	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT2)
>> +		return CXD2880_RESULT_ERROR_SW_STATE;
>> +
>> +	{
>> +		u8 rdata[3];
>> +
>> +		if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +					0x00, 0x0B) != CXD2880_RESULT_OK)
>> +			return CXD2880_RESULT_ERROR_IO;
>> +
>> +		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +					0x18, rdata, 3) != CXD2880_RESULT_OK)
>> +			return CXD2880_RESULT_ERROR_IO;
>> +
>> +		if ((rdata[0] & 0x01) == 0)
>> +			return CXD2880_RESULT_ERROR_HW_STATE;
>> +
>> +		*block_err = (rdata[1] << 8) | rdata[2];
>> +
>> +		if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +					0x00, 0x24) != CXD2880_RESULT_OK)
>> +			return CXD2880_RESULT_ERROR_IO;
>> +
>> +		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
>> +					0xDC, rdata, 1) != CXD2880_RESULT_OK)
>> +			return CXD2880_RESULT_ERROR_IO;
>> +
>> +		*block_count = 1U << (rdata[0] & 0x0F);
>> +	}
>> +
>> +	if ((*block_count == 0) || (*block_err > *block_count))
>> +		return CXD2880_RESULT_ERROR_HW_STATE;
>> +
>> +	return CXD2880_RESULT_OK;
>> +}
>> +
>> +static void cxd2880_release(struct dvb_frontend *fe)
>> +{
>> +	struct cxd2880_priv *priv = NULL;
>> +
>> +	if (!fe) {
>> +		pr_err("%s: invalid arg.\n", __func__);
>> +		return;
>> +	}
>> +	priv = (struct cxd2880_priv *)fe->demodulator_priv;
>> +	kfree(priv);
>> +}
>> +
>> +static int cxd2880_init(struct dvb_frontend *fe)
>> +{
>> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
>> +	struct cxd2880_priv *priv = NULL;
>> +	struct cxd2880_tnrdmd_create_param create_param;
>> +
>> +	if (!fe) {
>> +		pr_err("%s: invalid arg.\n", __func__);
>> +		return -EINVAL;
>> +	}
>> +
>> +	priv = (struct cxd2880_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 != CXD2880_RESULT_OK) {
>> +			mutex_unlock(priv->spi_mutex);
>> +			dev_info(&priv->spi->dev,
>> +				"%s: cxd2880 tnrdmd create failed %d\n",
>> +				__func__, ret);
>> +			return return_tbl[ret];
>> +		}
>> +	}
>> +	ret = cxd2880_integ_init(&priv->tnrdmd);
>> +	if (ret != CXD2880_RESULT_OK) {
>> +		mutex_unlock(priv->spi_mutex);
>> +		dev_err(&priv->spi->dev, "%s: cxd2880 integ init failed %d\n",
>> +				__func__, ret);
>> +		return return_tbl[ret];
>> +	}
>> +	mutex_unlock(priv->spi_mutex);
>> +
>> +	dev_dbg(&priv->spi->dev, "%s: OK.\n", __func__);
>> +
>> +	return return_tbl[ret];
>> +}
>> +
>> +static int cxd2880_sleep(struct dvb_frontend *fe)
>> +{
>> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
>> +	struct cxd2880_priv *priv = NULL;
>> +
>> +	if (!fe) {
>> +		pr_err("%s: inavlid arg\n", __func__);
>> +		return -EINVAL;
>> +	}
>> +
>> +	priv = (struct cxd2880_priv *)fe->demodulator_priv;
>> +
>> +	mutex_lock(priv->spi_mutex);
>> +	ret = cxd2880_tnrdmd_sleep(&priv->tnrdmd);
>> +	mutex_unlock(priv->spi_mutex);
>> +
>> +	dev_dbg(&priv->spi->dev, "%s: tnrdmd_sleep ret %d\n",
>> +		__func__, ret);
>> +
>> +	return return_tbl[ret];
>> +}
>> +
>> +static int cxd2880_read_signal_strength(struct dvb_frontend *fe,
>> +				u16 *strength)
>> +{
>> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
>> +	struct cxd2880_priv *priv = NULL;
>> +	struct dtv_frontend_properties *c = NULL;
>> +	int level = 0;
>> +
>> +	if ((!fe) || (!strength)) {
>> +		pr_err("%s: inavlid arg\n", __func__);
>> +		return -EINVAL;
>> +	}
>> +
>> +	priv = (struct cxd2880_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 {
>> +		dev_dbg(&priv->spi->dev, "%s: invalid system\n", __func__);
>> +		mutex_unlock(priv->spi_mutex);
>> +		return -EINVAL;
>> +	}
>> +	mutex_unlock(priv->spi_mutex);
>> +
>> +	level /= 125;
>> +	/* -105dBm - -30dBm (-105000/125 = -840, -30000/125 = -240 */
>> +	level = clamp(level, -840, -240);
>> +	/* scale value to 0x0000-0xFFFF */
>> +	*strength = (u16)(((level + 840) * 0xFFFF) / (-240 + 840));
>> +
>> +	if (ret != CXD2880_RESULT_OK)
>> +		dev_dbg(&priv->spi->dev, "%s: ret = %d\n", __func__, ret);
>> +
>> +	return return_tbl[ret];
>> +}
>> +
>> +static int cxd2880_read_snr(struct dvb_frontend *fe, u16 *snr)
>> +{
>> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
>> +	int snrvalue = 0;
>> +	struct cxd2880_priv *priv = NULL;
>> +	struct dtv_frontend_properties *c = NULL;
>> +
>> +	if ((!fe) || (!snr)) {
>> +		pr_err("%s: inavlid arg\n", __func__);
>> +		return -EINVAL;
>> +	}
>> +
>> +	priv = (struct cxd2880_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 {
>> +		dev_err(&priv->spi->dev, "%s: invalid system\n", __func__);
>> +		mutex_unlock(priv->spi_mutex);
>> +		return -EINVAL;
>> +	}
>> +	mutex_unlock(priv->spi_mutex);
>> +
>> +	if (snrvalue < 0)
>> +		snrvalue = 0;
>> +	*snr = (u16)snrvalue;
>> +
>> +	if (ret != CXD2880_RESULT_OK)
>> +		dev_dbg(&priv->spi->dev, "%s: ret = %d\n", __func__, ret);
>> +
>> +	return return_tbl[ret];
>> +}
>> +
>> +static int cxd2880_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
>> +{
>> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
>> +	struct cxd2880_priv *priv = NULL;
>> +	struct dtv_frontend_properties *c = NULL;
>> +
>> +	if ((!fe) || (!ucblocks)) {
>> +		pr_err("%s: inavlid arg\n", __func__);
>> +		return -EINVAL;
>> +	}
>> +
>> +	priv = (struct cxd2880_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 {
>> +		dev_err(&priv->spi->dev, "%s: invlaid system\n", __func__);
>> +		mutex_unlock(priv->spi_mutex);
>> +		return -EINVAL;
>> +	}
>> +	mutex_unlock(priv->spi_mutex);
>> +
>> +	if (ret != CXD2880_RESULT_OK)
>> +		dev_dbg(&priv->spi->dev, "%s: ret = %d\n", __func__, ret);
>> +
>> +	return return_tbl[ret];
>> +}
>> +
>> +static int cxd2880_read_ber(struct dvb_frontend *fe, u32 *ber)
>> +{
>> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
>> +	struct cxd2880_priv *priv = NULL;
>> +	struct dtv_frontend_properties *c = NULL;
>> +
>> +	if ((!fe) || (!ber)) {
>> +		pr_err("%s: inavlid arg\n", __func__);
>> +		return -EINVAL;
>> +	}
>> +
>> +	priv = (struct cxd2880_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_pre_rsber(&priv->tnrdmd,
>> +						ber);
>> +		/* x100 to change unit.(10^7 -> 10^9 */
>> +		*ber *= 100;
>> +	} else if (c->delivery_system == SYS_DVBT2) {
>> +		ret = cxd2880_tnrdmd_dvbt2_mon_pre_bchber(&priv->tnrdmd,
>> +						ber);
>> +	} else {
>> +		dev_err(&priv->spi->dev, "%s: invlaid system\n", __func__);
>> +		mutex_unlock(priv->spi_mutex);
>> +		return -EINVAL;
>> +	}
>> +	mutex_unlock(priv->spi_mutex);
>> +
>> +	if (ret != CXD2880_RESULT_OK)
>> +		dev_dbg(&priv->spi->dev, "%s: ret = %d\n", __func__, ret);
>> +
>> +	return return_tbl[ret];
>> +}
>> +
>> +static int cxd2880_set_frontend(struct dvb_frontend *fe)
>> +{
>> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
>> +	struct dtv_frontend_properties *c;
>> +	struct cxd2880_priv *priv;
>> +	enum cxd2880_dtv_bandwidth bw = CXD2880_DTV_BW_1_7_MHZ;
>> +
>> +	if (!fe) {
>> +		pr_err("%s: inavlid arg\n", __func__);
>> +		return -EINVAL;
>> +	}
>> +
>> +	priv = (struct cxd2880_priv *)fe->demodulator_priv;
>> +	c = &fe->dtv_property_cache;
>> +
>> +	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;
>> +	}
>> +
>> +	dev_info(&priv->spi->dev, "%s: sys:%d freq:%d bw:%d\n", __func__,
>> +			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_integ_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;
>> +		ret = cxd2880_integ_dvbt2_tune(&priv->tnrdmd,
>> +						&priv->dvbt2_tune_param);
>> +	} else {
>> +		dev_err(&priv->spi->dev, "%s: invalid system\n", __func__);
>> +		mutex_unlock(priv->spi_mutex);
>> +		return -EINVAL;
>> +	}
>> +	mutex_unlock(priv->spi_mutex);
>> +	dev_info(&priv->spi->dev, "%s: tune result %d\n", __func__, ret);
>> +
>> +	return return_tbl[ret];
>> +}
>> +
>> +static int cxd2880_read_status(struct dvb_frontend *fe,
>> +				enum fe_status *status)
>> +{
>> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
>> +	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("%s: invalid arg\n", __func__);
>> +		return -EINVAL;
>> +	}
>> +
>> +	priv = (struct cxd2880_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 {
>> +			dev_err(&priv->spi->dev,
>> +				"%s: invlaid system", __func__);
>> +			mutex_unlock(priv->spi_mutex);
>> +			return -EINVAL;
>> +		}
>> +
>> +		mutex_unlock(priv->spi_mutex);
>> +		if (ret != CXD2880_RESULT_OK) {
>> +			dev_err(&priv->spi->dev, "%s: failed. sys = %d\n",
>> +				__func__, priv->tnrdmd.sys);
>> +			return  return_tbl[ret];
>> +		}
>> +
>> +		if (sync == 6) {
>> +			*status = FE_HAS_SIGNAL |
>> +					FE_HAS_CARRIER;
>> +		}
>> +		if (lock)
>> +			*status |= FE_HAS_VITERBI |
>> +					FE_HAS_SYNC |
>> +					FE_HAS_LOCK;
>> +	}
>> +
>> +	dev_dbg(&priv->spi->dev, "%s: status %d result %d\n", __func__,
>> +		*status, ret);
>> +
>> +	return  return_tbl[CXD2880_RESULT_OK];
>> +}
>> +
>> +static int cxd2880_tune(struct dvb_frontend *fe,
>> +			bool retune,
>> +			unsigned int mode_flags,
>> +			unsigned int *delay,
>> +			enum fe_status *status)
>> +{
>> +	int ret = 0;
>> +
>> +	if ((!fe) || (!delay) || (!status)) {
>> +		pr_err("%s: invalid arg.", __func__);
>> +		return -EINVAL;
>> +	}
>> +
>> +	if (retune) {
>> +		ret = cxd2880_set_frontend(fe);
>> +		if (ret) {
>> +			pr_err("%s: cxd2880_set_frontend failed %d\n",
>> +				__func__, 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)
>> +{
>> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
>> +	int result = 0;
>> +	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;
>> +	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;
>> +
>> +	if ((!fe) || (!c)) {
>> +		pr_err("%s: invalid arg\n", __func__);
>> +		return -EINVAL;
>> +	}
>> +
>> +	priv = (struct cxd2880_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 == CXD2880_RESULT_OK) {
>> +		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;
>> +			dev_err(&priv->spi->dev, "%s: get invalid mode %d\n",
>> +					__func__, 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;
>> +			dev_err(&priv->spi->dev, "%s: get invalid guard %d\n",
>> +					__func__, guard);
>> +			break;
>> +		}
>> +	} else {
>> +		c->transmission_mode = TRANSMISSION_MODE_2K;
>> +		c->guard_interval = GUARD_INTERVAL_1_32;
>> +		dev_dbg(&priv->spi->dev,
>> +			"%s: ModeGuard err %d\n", __func__, ret);
>> +	}
>> +
>> +	mutex_lock(priv->spi_mutex);
>> +	ret = cxd2880_tnrdmd_dvbt_mon_tps_info(&priv->tnrdmd, &tps);
>> +	mutex_unlock(priv->spi_mutex);
>> +	if (ret == CXD2880_RESULT_OK) {
>> +		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;
>> +			dev_err(&priv->spi->dev,
>> +				"%s: TPSInfo hierarchy invalid %d\n",
>> +				__func__, 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;
>> +			dev_err(&priv->spi->dev,
>> +				"%s: TPSInfo rateHP invalid %d\n",
>> +				__func__, 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;
>> +			dev_err(&priv->spi->dev,
>> +				"%s: TPSInfo rateLP invalid %d\n",
>> +				__func__, 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;
>> +			dev_err(&priv->spi->dev,
>> +				"%s: TPSInfo constellation invalid %d\n",
>> +				__func__, tps.constellation);
>> +			break;
>> +		}
>> +	} else {
>> +		c->hierarchy = HIERARCHY_NONE;
>> +		c->code_rate_HP = FEC_NONE;
>> +		c->code_rate_LP = FEC_NONE;
>> +		c->modulation = QPSK;
>> +		dev_dbg(&priv->spi->dev,
>> +			"%s: TPS info err %d\n", __func__, ret);
>> +	}
>> +
>> +	mutex_lock(priv->spi_mutex);
>> +	ret = cxd2880_tnrdmd_dvbt_mon_spectrum_sense(&priv->tnrdmd, &sense);
>> +	mutex_unlock(priv->spi_mutex);
>> +	if (ret == CXD2880_RESULT_OK) {
>> +		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;
>> +			dev_err(&priv->spi->dev,
>> +				"%s: spectrum sense invalid %d\n",
>> +				__func__, sense);
>> +			break;
>> +		}
>> +	} else {
>> +		c->inversion = INVERSION_OFF;
>> +		dev_dbg(&priv->spi->dev,
>> +			"%s: spectrum_sense %d\n", __func__, ret);
>> +	}
>> +
>> +	mutex_lock(priv->spi_mutex);
>> +	ret = cxd2880_tnrdmd_mon_rf_lvl(&priv->tnrdmd, &strength);
>> +	mutex_unlock(priv->spi_mutex);
>> +	if (ret == CXD2880_RESULT_OK) {
>> +		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;
>> +		dev_dbg(&priv->spi->dev, "%s: mon_rf_lvl %d\n",
>> +			__func__, result);
>> +	}
>> +
>> +	result = cxd2880_read_snr(fe, &snr);
>> +	if (!result) {
>> +		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;
>> +		dev_dbg(&priv->spi->dev, "%s: read_snr %d\n", __func__, result);
>> +	}
>> +
>> +	mutex_lock(priv->spi_mutex);
>> +	ret = cxd2880_pre_bit_err_t(&priv->tnrdmd, &pre_bit_err,
>> +					&pre_bit_count);
> 
> Hmm... reading BER-based measures at get_frontend() may not be
> the best idea, depending on how the hardware works.
> 
> I mean, you need to be sure that, if userspace is calling it too
> often, it won't affect the hardware or the measurement.
> 
> On other drivers, where each call generates an I2C access,
> we do that by moving the code that actually call the hardware
> at get status, and we use jiffies to be sure that it won't be
> called too often.
> 
> I dunno if, on your SPI design, this would be an issue or not.

CXD2880 IC can accept frequently counter register access by SPI.
Even if such access occurred, it will not affect to IC behavior.
We checked Sony demodulator IC driver code, dvb_frontends/cxd2841er.c and it reads register information
when get_frontend called.

In fact, CXD2880 IC do NOT have bit error/packet error counter function to achieve DVB framework API completely.
Sorry for long explanation, but I'll write in detail.

DTV_STAT_PRE_ERROR_BIT_COUNT
DTV_STAT_PRE_TOTAL_BIT_COUNT
DTV_STAT_POST_ERROR_BIT_COUNT
DTV_STAT_POST_TOTAL_BIT_COUNT
DTV_STAT_ERROR_BLOCK_COUNT
DTV_STAT_TOTAL_BLOCK_COUNT

From DVB framework documentation, it seems that the demodulator hardware should have counter
to accumulate total bit/packet count to decode, and bit/packet error corrected by FEC.
But unfortunately, CXD2880 demodulator does not have such function.

Instead of it, Sony demodulator has bit/packet error counter for BER, PER calculation.
The user should configure total bit/packet count (denominator) for BER/PER measurement by some register setting.
The IC hardware will update the error counter automatically in period determined by
configured total bit/packet count (denominator).

So, we implemented like as follows.

DTV_STAT_PRE_ERROR_BIT_COUNT
DTV_STAT_POST_ERROR_BIT_COUNT
DTV_STAT_ERROR_BLOCK_COUNT
=> Return bit/packet error counter value in period determined by configured total bit/packet count (numerator).

DTV_STAT_PRE_TOTAL_BIT_COUNT
DTV_STAT_POST_TOTAL_BIT_COUNT
DTV_STAT_TOTAL_BLOCK_COUNT
=> Return currently configured total bit/packet count (denominator).

By this implementation, the user can calculate BER, PER by following calculation.

DTV_STAT_PRE_ERROR_BIT_COUNT / DTV_STAT_PRE_TOTAL_BIT_COUNT
DTV_STAT_POST_ERROR_BIT_COUNT / DTV_STAT_POST_TOTAL_BIT_COUNT
DTV_STAT_ERROR_BLOCK_COUNT / DTV_STAT_TOTAL_BLOCK_COUNT

But instead, DTV_STAT_XXX_ERROR_XXX_COUNT values are not monotonically increased,
and DTV_STAT_XXX_TOTAL_XX_COUNT will return fixed value.




> 
>> +	mutex_unlock(priv->spi_mutex);
>> +	if (ret == CXD2880_RESULT_OK) {
>> +		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;
>> +		dev_dbg(&priv->spi->dev,
>> +			"%s: pre_bit_error_t failed %d\n",
>> +			__func__, ret);
>> +	}
>> +
>> +	mutex_lock(priv->spi_mutex);
>> +	ret = cxd2880_post_bit_err_t(&priv->tnrdmd,
>> +				&post_bit_err, &post_bit_count);
>> +	mutex_unlock(priv->spi_mutex);
>> +	if (ret == CXD2880_RESULT_OK) {
>> +		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;
>> +		dev_dbg(&priv->spi->dev,
>> +			"%s: post_bit_err_t %d\n", __func__, ret);
>> +	}
>> +
>> +	mutex_lock(priv->spi_mutex);
>> +	ret = cxd2880_read_block_err_t(&priv->tnrdmd,
>> +					&block_err, &block_count);
>> +	mutex_unlock(priv->spi_mutex);
>> +	if (ret == CXD2880_RESULT_OK) {
>> +		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;
>> +		dev_dbg(&priv->spi->dev,
>> +			"%s: read_block_err_t  %d\n", __func__, ret);
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int cxd2880_get_frontend_t2(struct dvb_frontend *fe,
>> +				struct dtv_frontend_properties *c)
>> +{
>> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
>> +	int result = 0;
>> +	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;
>> +	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;
>> +
>> +	if ((!fe) || (!c)) {
>> +		pr_err("%s: invalid arg.\n", __func__);
>> +		return -EINVAL;
>> +	}
>> +
>> +	priv = (struct cxd2880_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 == CXD2880_RESULT_OK) {
>> +		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;
>> +			dev_err(&priv->spi->dev,
>> +				"%s: L1Pre fft_mode invalid %d\n",
>> +				__func__, 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;
>> +			dev_err(&priv->spi->dev,
>> +				"%s: L1Pre gi invalid %d\n",
>> +				__func__, l1pre.gi);
>> +			break;
>> +		}
>> +	} else {
>> +		c->transmission_mode = TRANSMISSION_MODE_2K;
>> +		c->guard_interval = GUARD_INTERVAL_1_32;
>> +		dev_dbg(&priv->spi->dev,
>> +			"%s: L1Pre err %d\n", __func__, 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 == CXD2880_RESULT_OK) {
>> +		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;
>> +			dev_err(&priv->spi->dev,
>> +				"%s: CodeRate invalid %d\n",
>> +				__func__, coderate);
>> +			break;
>> +		}
>> +	} else {
>> +		c->fec_inner = FEC_NONE;
>> +		dev_dbg(&priv->spi->dev, "%s: CodeRate %d\n", __func__, 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 == CXD2880_RESULT_OK) {
>> +		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;
>> +			dev_err(&priv->spi->dev,
>> +				"%s: QAM invalid %d\n",
>> +				__func__, qam);
>> +			break;
>> +		}
>> +	} else {
>> +		c->modulation = QPSK;
>> +		dev_dbg(&priv->spi->dev, "%s: QAM %d\n", __func__, ret);
>> +	}
>> +
>> +	mutex_lock(priv->spi_mutex);
>> +	ret = cxd2880_tnrdmd_dvbt2_mon_spectrum_sense(&priv->tnrdmd, &sense);
>> +	mutex_unlock(priv->spi_mutex);
>> +	if (ret == CXD2880_RESULT_OK) {
>> +		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;
>> +			dev_err(&priv->spi->dev,
>> +				"%s: spectrum sense invalid %d\n",
>> +				__func__, sense);
>> +			break;
>> +		}
>> +	} else {
>> +		c->inversion = INVERSION_OFF;
>> +		dev_dbg(&priv->spi->dev,
>> +			"%s: SpectrumSense %d\n", __func__, ret);
>> +	}
>> +
>> +	mutex_lock(priv->spi_mutex);
>> +	ret = cxd2880_tnrdmd_mon_rf_lvl(&priv->tnrdmd, &strength);
>> +	mutex_unlock(priv->spi_mutex);
>> +	if (ret == CXD2880_RESULT_OK) {
>> +		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;
>> +		dev_dbg(&priv->spi->dev,
>> +			"%s: mon_rf_lvl %d\n", __func__, ret);
>> +	}
>> +
>> +	result = cxd2880_read_snr(fe, &snr);
>> +	if (!result) {
>> +		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;
>> +		dev_dbg(&priv->spi->dev, "%s: read_snr %d\n", __func__, result);
>> +	}
>> +
>> +	mutex_lock(priv->spi_mutex);
>> +	ret = cxd2880_pre_bit_err_t2(&priv->tnrdmd,
>> +				&pre_bit_err,
>> +				&pre_bit_count);
>> +	mutex_unlock(priv->spi_mutex);
>> +	if (ret == CXD2880_RESULT_OK) {
>> +		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;
>> +		dev_dbg(&priv->spi->dev,
>> +			"%s: read_bit_err_t2 %d\n", __func__, ret);
>> +	}
>> +
>> +	mutex_lock(priv->spi_mutex);
>> +	ret = cxd2880_post_bit_err_t2(&priv->tnrdmd,
>> +				&post_bit_err, &post_bit_count);
>> +	mutex_unlock(priv->spi_mutex);
>> +	if (ret == CXD2880_RESULT_OK) {
>> +		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;
>> +		dev_dbg(&priv->spi->dev,
>> +			"%s: post_bit_err_t2 %d\n", __func__, ret);
>> +	}
>> +
>> +	mutex_lock(priv->spi_mutex);
>> +	ret = cxd2880_read_block_err_t2(&priv->tnrdmd,
>> +					&block_err, &block_count);
>> +	mutex_unlock(priv->spi_mutex);
>> +	if (ret == CXD2880_RESULT_OK) {
>> +		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;
>> +		dev_dbg(&priv->spi->dev,
>> +			"%s: read_block_err_t2 %d\n", __func__, ret);
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int cxd2880_get_frontend(struct dvb_frontend *fe,
>> +				struct dtv_frontend_properties *props)
>> +{
>> +	struct cxd2880_priv *priv = NULL;
>> +	int result = 0;
>> +
>> +	if ((!fe) || (!props)) {
>> +		pr_err("%s: invalid arg.", __func__);
>> +		return -EINVAL;
>> +	}
>> +
>> +	priv = (struct cxd2880_priv *)fe->demodulator_priv;
>> +
>> +	dev_dbg(&priv->spi->dev, "%s: system=%d\n", __func__,
>> +		fe->dtv_property_cache.delivery_system);
>> +	switch (fe->dtv_property_cache.delivery_system) {
>> +	case SYS_DVBT:
>> +		result = cxd2880_get_frontend_t(fe, props);
>> +		break;
>> +	case SYS_DVBT2:
>> +		result = cxd2880_get_frontend_t2(fe, props);
>> +		break;
> 
> Hmm... I noticed that already when reviewing other parts of the driver...
> 
> At patch 6, you defined:
> 
> 
> enum cxd2880_dtv_sys {
> +	CXD2880_DTV_SYS_UNKNOWN,
> +	CXD2880_DTV_SYS_DVBT,
> +	CXD2880_DTV_SYS_DVBT2,
> +	CXD2880_DTV_SYS_ISDBT,
> +	CXD2880_DTV_SYS_ISDBTSB,
> +	CXD2880_DTV_SYS_ISDBTMM_A,
> +	CXD2880_DTV_SYS_ISDBTMM_B,
> +	CXD2880_DTV_SYS_ANY
> +};
> 
> So, I was expecting to see not only DVB T/T2 here (and at the tuner
> driver) but also ISDB, in order to match the defined standards,
> but you're setting only DVB T/T2 here, and some parts of the tuner
> driver also seem to be focused only on those two standards.
> 
> So, the question is: does the hardware support ISDB? If so,
> please add a /* FIXME */ note where pertinent, saying that
> ISDB support should be added in the future. If, on the other 
> hand, the hardware doesn't support it at all, please cleanup the
> code by removing references for it.
> 
> 
>> +	default:
>> +		result = -EINVAL;
>> +		break;
>> +	}
>> +
>> +	return result;
>> +}
>> +
>> +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;
>> +
>> +struct dvb_frontend *cxd2880_attach(struct dvb_frontend *fe,
>> +				struct cxd2880_config *cfg)
>> +{
>> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
>> +	enum cxd2880_tnrdmd_chip_id chipid =
>> +					CXD2880_TNRDMD_CHIP_ID_UNKNOWN;
>> +	static struct cxd2880_priv *priv;
>> +	u8 data = 0;
>> +
>> +	if (!fe) {
>> +		pr_err("%s: invalid arg.\n", __func__);
>> +		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 != CXD2880_RESULT_OK) {
>> +		dev_err(&priv->spi->dev,
>> +			"%s: spi_device_initialize failed. %d\n",
>> +			__func__, ret);
>> +		kfree(priv);
>> +		return NULL;
>> +	}
>> +
>> +	ret = cxd2880_spi_device_create_spi(&priv->cxd2880_spi,
>> +					&priv->spi_device);
>> +	if (ret != CXD2880_RESULT_OK) {
>> +		dev_err(&priv->spi->dev,
>> +			"%s: spi_device_create_spi failed. %d\n",
>> +			__func__, ret);
>> +		kfree(priv);
>> +		return NULL;
>> +	}
>> +
>> +	ret = cxd2880_io_spi_create(&priv->regio, &priv->cxd2880_spi, 0);
>> +	if (ret != CXD2880_RESULT_OK) {
>> +		dev_err(&priv->spi->dev,
>> +			"%s: io_spi_create failed. %d\n", __func__, ret);
>> +		kfree(priv);
>> +		return NULL;
>> +	}
>> +	if (priv->regio.write_reg(&priv->regio, CXD2880_IO_TGT_SYS, 0x00, 0x00)
>> +		!= CXD2880_RESULT_OK) {
>> +		dev_err(&priv->spi->dev,
>> +			"%s: set bank to 0x00 failed.\n", __func__);
>> +		kfree(priv);
>> +		return NULL;
>> +	}
>> +	if (priv->regio.read_regs(&priv->regio,
>> +					CXD2880_IO_TGT_SYS, 0xFD, &data, 1)
>> +					!= CXD2880_RESULT_OK) {
>> +		dev_err(&priv->spi->dev,
>> +			"%s: read chip id failed.\n", __func__);
>> +		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)) {
>> +		dev_err(&priv->spi->dev,
>> +			"%s: chip id invalid.\n", __func__);
>> +		kfree(priv);
>> +		return NULL;
>> +	}
>> +
>> +	fe->demodulator_priv = priv;
>> +	dev_info(&priv->spi->dev,
>> +		"CXD2880 driver version: Ver %s\n",
>> +		CXD2880_TNRDMD_DRIVER_VERSION);
>> +
>> +	return fe;
>> +}
>> +EXPORT_SYMBOL(cxd2880_attach);
>> +
>> +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,
>> +};
>> +
>> +MODULE_DESCRIPTION(
>> +"Sony CXD2880 DVB-T2/T tuner + demodulator drvier");
>> +MODULE_AUTHOR("Sony Semiconductor Solutions Corporation");
>> +MODULE_LICENSE("GPL v2");
> 
> 
> 
> Thanks,
> Mauro
> .

Regards and Thanks
Takiguchi
Mauro Carvalho Chehab June 23, 2017, 1:02 p.m. UTC | #3
Em Mon, 19 Jun 2017 16:56:13 +0900
"Takiguchi, Yasunari" <Yasunari.Takiguchi@sony.com> escreveu:

> >> +static int cxd2880_get_frontend_t(struct dvb_frontend *fe,
> >> +				struct dtv_frontend_properties *c)
> >> +{
> >> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
> >> +	int result = 0;
> >> +	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;
> >> +	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;
> >> +
> >> +	if ((!fe) || (!c)) {
> >> +		pr_err("%s: invalid arg\n", __func__);
> >> +		return -EINVAL;
> >> +	}
> >> +
> >> +	priv = (struct cxd2880_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 == CXD2880_RESULT_OK) {
> >> +		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;
> >> +			dev_err(&priv->spi->dev, "%s: get invalid mode %d\n",
> >> +					__func__, 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;
> >> +			dev_err(&priv->spi->dev, "%s: get invalid guard %d\n",
> >> +					__func__, guard);
> >> +			break;
> >> +		}
> >> +	} else {
> >> +		c->transmission_mode = TRANSMISSION_MODE_2K;
> >> +		c->guard_interval = GUARD_INTERVAL_1_32;
> >> +		dev_dbg(&priv->spi->dev,
> >> +			"%s: ModeGuard err %d\n", __func__, ret);
> >> +	}
> >> +
> >> +	mutex_lock(priv->spi_mutex);
> >> +	ret = cxd2880_tnrdmd_dvbt_mon_tps_info(&priv->tnrdmd, &tps);
> >> +	mutex_unlock(priv->spi_mutex);
> >> +	if (ret == CXD2880_RESULT_OK) {
> >> +		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;
> >> +			dev_err(&priv->spi->dev,
> >> +				"%s: TPSInfo hierarchy invalid %d\n",
> >> +				__func__, 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;
> >> +			dev_err(&priv->spi->dev,
> >> +				"%s: TPSInfo rateHP invalid %d\n",
> >> +				__func__, 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;
> >> +			dev_err(&priv->spi->dev,
> >> +				"%s: TPSInfo rateLP invalid %d\n",
> >> +				__func__, 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;
> >> +			dev_err(&priv->spi->dev,
> >> +				"%s: TPSInfo constellation invalid %d\n",
> >> +				__func__, tps.constellation);
> >> +			break;
> >> +		}
> >> +	} else {
> >> +		c->hierarchy = HIERARCHY_NONE;
> >> +		c->code_rate_HP = FEC_NONE;
> >> +		c->code_rate_LP = FEC_NONE;
> >> +		c->modulation = QPSK;
> >> +		dev_dbg(&priv->spi->dev,
> >> +			"%s: TPS info err %d\n", __func__, ret);
> >> +	}
> >> +
> >> +	mutex_lock(priv->spi_mutex);
> >> +	ret = cxd2880_tnrdmd_dvbt_mon_spectrum_sense(&priv->tnrdmd, &sense);
> >> +	mutex_unlock(priv->spi_mutex);
> >> +	if (ret == CXD2880_RESULT_OK) {
> >> +		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;
> >> +			dev_err(&priv->spi->dev,
> >> +				"%s: spectrum sense invalid %d\n",
> >> +				__func__, sense);
> >> +			break;
> >> +		}
> >> +	} else {
> >> +		c->inversion = INVERSION_OFF;
> >> +		dev_dbg(&priv->spi->dev,
> >> +			"%s: spectrum_sense %d\n", __func__, ret);
> >> +	}
> >> +
> >> +	mutex_lock(priv->spi_mutex);
> >> +	ret = cxd2880_tnrdmd_mon_rf_lvl(&priv->tnrdmd, &strength);
> >> +	mutex_unlock(priv->spi_mutex);
> >> +	if (ret == CXD2880_RESULT_OK) {
> >> +		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;
> >> +		dev_dbg(&priv->spi->dev, "%s: mon_rf_lvl %d\n",
> >> +			__func__, result);
> >> +	}
> >> +
> >> +	result = cxd2880_read_snr(fe, &snr);
> >> +	if (!result) {
> >> +		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;
> >> +		dev_dbg(&priv->spi->dev, "%s: read_snr %d\n", __func__, result);
> >> +	}
> >> +
> >> +	mutex_lock(priv->spi_mutex);
> >> +	ret = cxd2880_pre_bit_err_t(&priv->tnrdmd, &pre_bit_err,
> >> +					&pre_bit_count);  
> > 
> > Hmm... reading BER-based measures at get_frontend() may not be
> > the best idea, depending on how the hardware works.
> > 
> > I mean, you need to be sure that, if userspace is calling it too
> > often, it won't affect the hardware or the measurement.
> > 
> > On other drivers, where each call generates an I2C access,
> > we do that by moving the code that actually call the hardware
> > at get status, and we use jiffies to be sure that it won't be
> > called too often.
> > 
> > I dunno if, on your SPI design, this would be an issue or not.  
> 
> CXD2880 IC can accept frequently counter register access by SPI.
> Even if such access occurred, it will not affect to IC behavior.
> We checked Sony demodulator IC driver code, dvb_frontends/cxd2841er.c and it reads register information
> when get_frontend called.
> 
> In fact, CXD2880 IC do NOT have bit error/packet error counter function to achieve DVB framework API completely.
> Sorry for long explanation, but I'll write in detail.
> 
> DTV_STAT_PRE_ERROR_BIT_COUNT
> DTV_STAT_PRE_TOTAL_BIT_COUNT
> DTV_STAT_POST_ERROR_BIT_COUNT
> DTV_STAT_POST_TOTAL_BIT_COUNT
> DTV_STAT_ERROR_BLOCK_COUNT
> DTV_STAT_TOTAL_BLOCK_COUNT
> 
> From DVB framework documentation, it seems that the demodulator hardware should have counter
> to accumulate total bit/packet count to decode, and bit/packet error corrected by FEC.
> But unfortunately, CXD2880 demodulator does not have such function.

No, this is not a requirement. What actually happens is that userspace
expect those values to be monotonically incremented, as new data is
made available.

> 
> Instead of it, Sony demodulator has bit/packet error counter for BER, PER calculation.
> The user should configure total bit/packet count (denominator) for BER/PER measurement by some register setting.

Yeah, that's a common practice: usually, the counters are initialized
by some registers (on a few hardware, it is hardcoded).

> The IC hardware will update the error counter automatically in period determined by
> configured total bit/packet count (denominator).

So, the driver need to be sure that it won't read the register too
early, e. g. it should either be reading some register bits to know
when to read the data, or it needs a logic that will estimate when
the data will be ready, not updating the values to early.

An example of the first case is the mb86a20s. There, you can see at:
mb86a20s_get_pre_ber():

	/* Check if the BER measures are already available */
	rc = mb86a20s_readreg(state, 0x54);
	if (rc < 0)
		return rc;

	/* Check if data is available for that layer */
	if (!(rc & (1 << layer))) {
		dev_dbg(&state->i2c->dev,
			"%s: preBER for layer %c is not available yet.\n",
			__func__, 'A' + layer);
		return -EBUSY;
	}

An example of the second case is dib8000. There, you can see at:
dib8000_get_stats():


	/* Check if time for stats was elapsed */
	if (time_after(jiffies, state->per_jiffies_stats)) {
		state->per_jiffies_stats = jiffies + msecs_to_jiffies(1000);

...
		/* Get UCB measures */
		dib8000_read_unc_blocks(fe, &val);
		if (val < state->init_ucb)
			state->init_ucb += 0x100000000LL;

		c->block_error.stat[0].scale = FE_SCALE_COUNTER;
		c->block_error.stat[0].uvalue = val + state->init_ucb;

		/* Estimate the number of packets based on bitrate */
		if (!time_us)
			time_us = dib8000_get_time_us(fe, -1);

		if (time_us) {
			blocks = 1250000ULL * 1000000ULL;
			do_div(blocks, time_us * 8 * 204);
			c->block_count.stat[0].scale = FE_SCALE_COUNTER;
			c->block_count.stat[0].uvalue += blocks;
		}

		show_per_stats = 1;
	}

At the above, the hardware is set to provide the number of uncorrected
blocks on every second, and the logic there calculates the number of
blocks based on the bitrate. It shouldn't be hard to do the opposite
(e. g. set the hardware for a given number of blocks and calculate
the time - I remember I coded it already - just don't remember on
what driver - perhaps on an earlier implementation at mb86a20s).

In any case, as the code will require periodic pulls, in order
to provide monotonic counters, we implement it via get_stats(), as
the DVB core will ensure that it will be called periodically
(typically 3 times per second).

> 
> So, we implemented like as follows.
> 
> DTV_STAT_PRE_ERROR_BIT_COUNT
> DTV_STAT_POST_ERROR_BIT_COUNT
> DTV_STAT_ERROR_BLOCK_COUNT
> => Return bit/packet error counter value in period determined by configured total bit/packet count (numerator).  
> 
> DTV_STAT_PRE_TOTAL_BIT_COUNT
> DTV_STAT_POST_TOTAL_BIT_COUNT
> DTV_STAT_TOTAL_BLOCK_COUNT
> => Return currently configured total bit/packet count (denominator).  
> 
> By this implementation, the user can calculate BER, PER by following calculation.
> 
> DTV_STAT_PRE_ERROR_BIT_COUNT / DTV_STAT_PRE_TOTAL_BIT_COUNT
> DTV_STAT_POST_ERROR_BIT_COUNT / DTV_STAT_POST_TOTAL_BIT_COUNT
> DTV_STAT_ERROR_BLOCK_COUNT / DTV_STAT_TOTAL_BLOCK_COUNT
> 
> But instead, DTV_STAT_XXX_ERROR_XXX_COUNT values are not monotonically increased,
> and DTV_STAT_XXX_TOTAL_XX_COUNT will return fixed value.

That's wrong. you should be doing:

	DTV_STAT_TOTAL_BLOCK_COUNT += number_of_blocks

every time the block counter registers are updated (and the
same for the total bit counter).

Thanks,
Mauro
Mauro Carvalho Chehab June 25, 2017, 12:15 p.m. UTC | #4
Em Fri, 23 Jun 2017 10:02:39 -0300
Mauro Carvalho Chehab <mchehab@s-opensource.com> escreveu:

> Em Mon, 19 Jun 2017 16:56:13 +0900
> "Takiguchi, Yasunari" <Yasunari.Takiguchi@sony.com> escreveu:
> 
> > >> +static int cxd2880_get_frontend_t(struct dvb_frontend *fe,
> > >> +				struct dtv_frontend_properties *c)
> > >> +{
> > >> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
> > >> +	int result = 0;
> > >> +	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;
> > >> +	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;
> > >> +
> > >> +	if ((!fe) || (!c)) {
> > >> +		pr_err("%s: invalid arg\n", __func__);
> > >> +		return -EINVAL;
> > >> +	}
> > >> +
> > >> +	priv = (struct cxd2880_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 == CXD2880_RESULT_OK) {
> > >> +		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;
> > >> +			dev_err(&priv->spi->dev, "%s: get invalid mode %d\n",
> > >> +					__func__, 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;
> > >> +			dev_err(&priv->spi->dev, "%s: get invalid guard %d\n",
> > >> +					__func__, guard);
> > >> +			break;
> > >> +		}
> > >> +	} else {
> > >> +		c->transmission_mode = TRANSMISSION_MODE_2K;
> > >> +		c->guard_interval = GUARD_INTERVAL_1_32;
> > >> +		dev_dbg(&priv->spi->dev,
> > >> +			"%s: ModeGuard err %d\n", __func__, ret);
> > >> +	}
> > >> +
> > >> +	mutex_lock(priv->spi_mutex);
> > >> +	ret = cxd2880_tnrdmd_dvbt_mon_tps_info(&priv->tnrdmd, &tps);
> > >> +	mutex_unlock(priv->spi_mutex);
> > >> +	if (ret == CXD2880_RESULT_OK) {
> > >> +		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;
> > >> +			dev_err(&priv->spi->dev,
> > >> +				"%s: TPSInfo hierarchy invalid %d\n",
> > >> +				__func__, 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;
> > >> +			dev_err(&priv->spi->dev,
> > >> +				"%s: TPSInfo rateHP invalid %d\n",
> > >> +				__func__, 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;
> > >> +			dev_err(&priv->spi->dev,
> > >> +				"%s: TPSInfo rateLP invalid %d\n",
> > >> +				__func__, 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;
> > >> +			dev_err(&priv->spi->dev,
> > >> +				"%s: TPSInfo constellation invalid %d\n",
> > >> +				__func__, tps.constellation);
> > >> +			break;
> > >> +		}
> > >> +	} else {
> > >> +		c->hierarchy = HIERARCHY_NONE;
> > >> +		c->code_rate_HP = FEC_NONE;
> > >> +		c->code_rate_LP = FEC_NONE;
> > >> +		c->modulation = QPSK;
> > >> +		dev_dbg(&priv->spi->dev,
> > >> +			"%s: TPS info err %d\n", __func__, ret);
> > >> +	}
> > >> +
> > >> +	mutex_lock(priv->spi_mutex);
> > >> +	ret = cxd2880_tnrdmd_dvbt_mon_spectrum_sense(&priv->tnrdmd, &sense);
> > >> +	mutex_unlock(priv->spi_mutex);
> > >> +	if (ret == CXD2880_RESULT_OK) {
> > >> +		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;
> > >> +			dev_err(&priv->spi->dev,
> > >> +				"%s: spectrum sense invalid %d\n",
> > >> +				__func__, sense);
> > >> +			break;
> > >> +		}
> > >> +	} else {
> > >> +		c->inversion = INVERSION_OFF;
> > >> +		dev_dbg(&priv->spi->dev,
> > >> +			"%s: spectrum_sense %d\n", __func__, ret);
> > >> +	}
> > >> +
> > >> +	mutex_lock(priv->spi_mutex);
> > >> +	ret = cxd2880_tnrdmd_mon_rf_lvl(&priv->tnrdmd, &strength);
> > >> +	mutex_unlock(priv->spi_mutex);
> > >> +	if (ret == CXD2880_RESULT_OK) {
> > >> +		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;
> > >> +		dev_dbg(&priv->spi->dev, "%s: mon_rf_lvl %d\n",
> > >> +			__func__, result);
> > >> +	}
> > >> +
> > >> +	result = cxd2880_read_snr(fe, &snr);
> > >> +	if (!result) {
> > >> +		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;
> > >> +		dev_dbg(&priv->spi->dev, "%s: read_snr %d\n", __func__, result);
> > >> +	}
> > >> +
> > >> +	mutex_lock(priv->spi_mutex);
> > >> +	ret = cxd2880_pre_bit_err_t(&priv->tnrdmd, &pre_bit_err,
> > >> +					&pre_bit_count);    
> > > 
> > > Hmm... reading BER-based measures at get_frontend() may not be
> > > the best idea, depending on how the hardware works.
> > > 
> > > I mean, you need to be sure that, if userspace is calling it too
> > > often, it won't affect the hardware or the measurement.
> > > 
> > > On other drivers, where each call generates an I2C access,
> > > we do that by moving the code that actually call the hardware
> > > at get status, and we use jiffies to be sure that it won't be
> > > called too often.
> > > 
> > > I dunno if, on your SPI design, this would be an issue or not.    
> > 
> > CXD2880 IC can accept frequently counter register access by SPI.
> > Even if such access occurred, it will not affect to IC behavior.
> > We checked Sony demodulator IC driver code, dvb_frontends/cxd2841er.c and it reads register information
> > when get_frontend called.
> > 
> > In fact, CXD2880 IC do NOT have bit error/packet error counter function to achieve DVB framework API completely.
> > Sorry for long explanation, but I'll write in detail.
> > 
> > DTV_STAT_PRE_ERROR_BIT_COUNT
> > DTV_STAT_PRE_TOTAL_BIT_COUNT
> > DTV_STAT_POST_ERROR_BIT_COUNT
> > DTV_STAT_POST_TOTAL_BIT_COUNT
> > DTV_STAT_ERROR_BLOCK_COUNT
> > DTV_STAT_TOTAL_BLOCK_COUNT
> > 
> > From DVB framework documentation, it seems that the demodulator hardware should have counter
> > to accumulate total bit/packet count to decode, and bit/packet error corrected by FEC.
> > But unfortunately, CXD2880 demodulator does not have such function.  
> 
> No, this is not a requirement. What actually happens is that userspace
> expect those values to be monotonically incremented, as new data is
> made available.
> 
> > 
> > Instead of it, Sony demodulator has bit/packet error counter for BER, PER calculation.
> > The user should configure total bit/packet count (denominator) for BER/PER measurement by some register setting.  
> 
> Yeah, that's a common practice: usually, the counters are initialized
> by some registers (on a few hardware, it is hardcoded).
> 
> > The IC hardware will update the error counter automatically in period determined by
> > configured total bit/packet count (denominator).  
> 
> So, the driver need to be sure that it won't read the register too
> early, e. g. it should either be reading some register bits to know
> when to read the data, or it needs a logic that will estimate when
> the data will be ready, not updating the values to early.
> 
> An example of the first case is the mb86a20s. There, you can see at:
> mb86a20s_get_pre_ber():
> 
> 	/* Check if the BER measures are already available */
> 	rc = mb86a20s_readreg(state, 0x54);
> 	if (rc < 0)
> 		return rc;
> 
> 	/* Check if data is available for that layer */
> 	if (!(rc & (1 << layer))) {
> 		dev_dbg(&state->i2c->dev,
> 			"%s: preBER for layer %c is not available yet.\n",
> 			__func__, 'A' + layer);
> 		return -EBUSY;
> 	}
> 
> An example of the second case is dib8000. There, you can see at:
> dib8000_get_stats():
> 
> 
> 	/* Check if time for stats was elapsed */
> 	if (time_after(jiffies, state->per_jiffies_stats)) {
> 		state->per_jiffies_stats = jiffies + msecs_to_jiffies(1000);
> 
> ...
> 		/* Get UCB measures */
> 		dib8000_read_unc_blocks(fe, &val);
> 		if (val < state->init_ucb)
> 			state->init_ucb += 0x100000000LL;
> 
> 		c->block_error.stat[0].scale = FE_SCALE_COUNTER;
> 		c->block_error.stat[0].uvalue = val + state->init_ucb;
> 
> 		/* Estimate the number of packets based on bitrate */
> 		if (!time_us)
> 			time_us = dib8000_get_time_us(fe, -1);
> 
> 		if (time_us) {
> 			blocks = 1250000ULL * 1000000ULL;
> 			do_div(blocks, time_us * 8 * 204);
> 			c->block_count.stat[0].scale = FE_SCALE_COUNTER;
> 			c->block_count.stat[0].uvalue += blocks;
> 		}
> 
> 		show_per_stats = 1;
> 	}
> 
> At the above, the hardware is set to provide the number of uncorrected
> blocks on every second, and the logic there calculates the number of
> blocks based on the bitrate. It shouldn't be hard to do the opposite
> (e. g. set the hardware for a given number of blocks and calculate
> the time - I remember I coded it already - just don't remember on
> what driver - perhaps on an earlier implementation at mb86a20s).
> 
> In any case, as the code will require periodic pulls, in order
> to provide monotonic counters, we implement it via get_stats(), as
> the DVB core will ensure that it will be called periodically
> (typically 3 times per second).
> 
> > 
> > So, we implemented like as follows.
> > 
> > DTV_STAT_PRE_ERROR_BIT_COUNT
> > DTV_STAT_POST_ERROR_BIT_COUNT
> > DTV_STAT_ERROR_BLOCK_COUNT  
> > => Return bit/packet error counter value in period determined by configured total bit/packet count (numerator).    
> > 
> > DTV_STAT_PRE_TOTAL_BIT_COUNT
> > DTV_STAT_POST_TOTAL_BIT_COUNT
> > DTV_STAT_TOTAL_BLOCK_COUNT  
> > => Return currently configured total bit/packet count (denominator).    
> > 
> > By this implementation, the user can calculate BER, PER by following calculation.
> > 
> > DTV_STAT_PRE_ERROR_BIT_COUNT / DTV_STAT_PRE_TOTAL_BIT_COUNT
> > DTV_STAT_POST_ERROR_BIT_COUNT / DTV_STAT_POST_TOTAL_BIT_COUNT
> > DTV_STAT_ERROR_BLOCK_COUNT / DTV_STAT_TOTAL_BLOCK_COUNT
> > 
> > But instead, DTV_STAT_XXX_ERROR_XXX_COUNT values are not monotonically increased,
> > and DTV_STAT_XXX_TOTAL_XX_COUNT will return fixed value.  
> 
> That's wrong. you should be doing:
> 
> 	DTV_STAT_TOTAL_BLOCK_COUNT += number_of_blocks
> 
> every time the block counter registers are updated (and the
> same for the total bit counter).

Btw, as we had another developer with some doubts about stats
implementation, I wrote a chapter at the documentation in
order to explain how to properly implement it:

	https://linuxtv.org/downloads/v4l-dvb-apis-new/kapi/dtv-core.html#digital-tv-frontend-statistics

> 
> Thanks,
> Mauro



Thanks,
Mauro
Mauro Carvalho Chehab June 26, 2017, 1:24 a.m. UTC | #5
Em Sun, 25 Jun 2017 09:15:06 -0300
Mauro Carvalho Chehab <mchehab@s-opensource.com> escreveu:

> Em Fri, 23 Jun 2017 10:02:39 -0300
> Mauro Carvalho Chehab <mchehab@s-opensource.com> escreveu:
> 
> > Em Mon, 19 Jun 2017 16:56:13 +0900
> > "Takiguchi, Yasunari" <Yasunari.Takiguchi@sony.com> escreveu:
> >   
> > > >> +static int cxd2880_get_frontend_t(struct dvb_frontend *fe,
> > > >> +				struct dtv_frontend_properties *c)
> > > >> +{
> > > >> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
> > > >> +	int result = 0;
> > > >> +	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;
> > > >> +	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;
> > > >> +
> > > >> +	if ((!fe) || (!c)) {
> > > >> +		pr_err("%s: invalid arg\n", __func__);
> > > >> +		return -EINVAL;
> > > >> +	}
> > > >> +
> > > >> +	priv = (struct cxd2880_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 == CXD2880_RESULT_OK) {
> > > >> +		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;
> > > >> +			dev_err(&priv->spi->dev, "%s: get invalid mode %d\n",
> > > >> +					__func__, 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;
> > > >> +			dev_err(&priv->spi->dev, "%s: get invalid guard %d\n",
> > > >> +					__func__, guard);
> > > >> +			break;
> > > >> +		}
> > > >> +	} else {
> > > >> +		c->transmission_mode = TRANSMISSION_MODE_2K;
> > > >> +		c->guard_interval = GUARD_INTERVAL_1_32;
> > > >> +		dev_dbg(&priv->spi->dev,
> > > >> +			"%s: ModeGuard err %d\n", __func__, ret);
> > > >> +	}
> > > >> +
> > > >> +	mutex_lock(priv->spi_mutex);
> > > >> +	ret = cxd2880_tnrdmd_dvbt_mon_tps_info(&priv->tnrdmd, &tps);
> > > >> +	mutex_unlock(priv->spi_mutex);
> > > >> +	if (ret == CXD2880_RESULT_OK) {
> > > >> +		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;
> > > >> +			dev_err(&priv->spi->dev,
> > > >> +				"%s: TPSInfo hierarchy invalid %d\n",
> > > >> +				__func__, 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;
> > > >> +			dev_err(&priv->spi->dev,
> > > >> +				"%s: TPSInfo rateHP invalid %d\n",
> > > >> +				__func__, 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;
> > > >> +			dev_err(&priv->spi->dev,
> > > >> +				"%s: TPSInfo rateLP invalid %d\n",
> > > >> +				__func__, 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;
> > > >> +			dev_err(&priv->spi->dev,
> > > >> +				"%s: TPSInfo constellation invalid %d\n",
> > > >> +				__func__, tps.constellation);
> > > >> +			break;
> > > >> +		}
> > > >> +	} else {
> > > >> +		c->hierarchy = HIERARCHY_NONE;
> > > >> +		c->code_rate_HP = FEC_NONE;
> > > >> +		c->code_rate_LP = FEC_NONE;
> > > >> +		c->modulation = QPSK;
> > > >> +		dev_dbg(&priv->spi->dev,
> > > >> +			"%s: TPS info err %d\n", __func__, ret);
> > > >> +	}
> > > >> +
> > > >> +	mutex_lock(priv->spi_mutex);
> > > >> +	ret = cxd2880_tnrdmd_dvbt_mon_spectrum_sense(&priv->tnrdmd, &sense);
> > > >> +	mutex_unlock(priv->spi_mutex);
> > > >> +	if (ret == CXD2880_RESULT_OK) {
> > > >> +		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;
> > > >> +			dev_err(&priv->spi->dev,
> > > >> +				"%s: spectrum sense invalid %d\n",
> > > >> +				__func__, sense);
> > > >> +			break;
> > > >> +		}
> > > >> +	} else {
> > > >> +		c->inversion = INVERSION_OFF;
> > > >> +		dev_dbg(&priv->spi->dev,
> > > >> +			"%s: spectrum_sense %d\n", __func__, ret);
> > > >> +	}
> > > >> +
> > > >> +	mutex_lock(priv->spi_mutex);
> > > >> +	ret = cxd2880_tnrdmd_mon_rf_lvl(&priv->tnrdmd, &strength);
> > > >> +	mutex_unlock(priv->spi_mutex);
> > > >> +	if (ret == CXD2880_RESULT_OK) {
> > > >> +		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;
> > > >> +		dev_dbg(&priv->spi->dev, "%s: mon_rf_lvl %d\n",
> > > >> +			__func__, result);
> > > >> +	}
> > > >> +
> > > >> +	result = cxd2880_read_snr(fe, &snr);
> > > >> +	if (!result) {
> > > >> +		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;
> > > >> +		dev_dbg(&priv->spi->dev, "%s: read_snr %d\n", __func__, result);
> > > >> +	}
> > > >> +
> > > >> +	mutex_lock(priv->spi_mutex);
> > > >> +	ret = cxd2880_pre_bit_err_t(&priv->tnrdmd, &pre_bit_err,
> > > >> +					&pre_bit_count);      
> > > > 
> > > > Hmm... reading BER-based measures at get_frontend() may not be
> > > > the best idea, depending on how the hardware works.
> > > > 
> > > > I mean, you need to be sure that, if userspace is calling it too
> > > > often, it won't affect the hardware or the measurement.
> > > > 
> > > > On other drivers, where each call generates an I2C access,
> > > > we do that by moving the code that actually call the hardware
> > > > at get status, and we use jiffies to be sure that it won't be
> > > > called too often.
> > > > 
> > > > I dunno if, on your SPI design, this would be an issue or not.      
> > > 
> > > CXD2880 IC can accept frequently counter register access by SPI.
> > > Even if such access occurred, it will not affect to IC behavior.
> > > We checked Sony demodulator IC driver code, dvb_frontends/cxd2841er.c and it reads register information
> > > when get_frontend called.
> > > 
> > > In fact, CXD2880 IC do NOT have bit error/packet error counter function to achieve DVB framework API completely.
> > > Sorry for long explanation, but I'll write in detail.
> > > 
> > > DTV_STAT_PRE_ERROR_BIT_COUNT
> > > DTV_STAT_PRE_TOTAL_BIT_COUNT
> > > DTV_STAT_POST_ERROR_BIT_COUNT
> > > DTV_STAT_POST_TOTAL_BIT_COUNT
> > > DTV_STAT_ERROR_BLOCK_COUNT
> > > DTV_STAT_TOTAL_BLOCK_COUNT
> > > 
> > > From DVB framework documentation, it seems that the demodulator hardware should have counter
> > > to accumulate total bit/packet count to decode, and bit/packet error corrected by FEC.
> > > But unfortunately, CXD2880 demodulator does not have such function.    
> > 
> > No, this is not a requirement. What actually happens is that userspace
> > expect those values to be monotonically incremented, as new data is
> > made available.
> >   
> > > 
> > > Instead of it, Sony demodulator has bit/packet error counter for BER, PER calculation.
> > > The user should configure total bit/packet count (denominator) for BER/PER measurement by some register setting.    
> > 
> > Yeah, that's a common practice: usually, the counters are initialized
> > by some registers (on a few hardware, it is hardcoded).
> >   
> > > The IC hardware will update the error counter automatically in period determined by
> > > configured total bit/packet count (denominator).    
> > 
> > So, the driver need to be sure that it won't read the register too
> > early, e. g. it should either be reading some register bits to know
> > when to read the data, or it needs a logic that will estimate when
> > the data will be ready, not updating the values to early.
> > 
> > An example of the first case is the mb86a20s. There, you can see at:
> > mb86a20s_get_pre_ber():
> > 
> > 	/* Check if the BER measures are already available */
> > 	rc = mb86a20s_readreg(state, 0x54);
> > 	if (rc < 0)
> > 		return rc;
> > 
> > 	/* Check if data is available for that layer */
> > 	if (!(rc & (1 << layer))) {
> > 		dev_dbg(&state->i2c->dev,
> > 			"%s: preBER for layer %c is not available yet.\n",
> > 			__func__, 'A' + layer);
> > 		return -EBUSY;
> > 	}
> > 
> > An example of the second case is dib8000. There, you can see at:
> > dib8000_get_stats():
> > 
> > 
> > 	/* Check if time for stats was elapsed */
> > 	if (time_after(jiffies, state->per_jiffies_stats)) {
> > 		state->per_jiffies_stats = jiffies + msecs_to_jiffies(1000);
> > 
> > ...
> > 		/* Get UCB measures */
> > 		dib8000_read_unc_blocks(fe, &val);
> > 		if (val < state->init_ucb)
> > 			state->init_ucb += 0x100000000LL;
> > 
> > 		c->block_error.stat[0].scale = FE_SCALE_COUNTER;
> > 		c->block_error.stat[0].uvalue = val + state->init_ucb;
> > 
> > 		/* Estimate the number of packets based on bitrate */
> > 		if (!time_us)
> > 			time_us = dib8000_get_time_us(fe, -1);
> > 
> > 		if (time_us) {
> > 			blocks = 1250000ULL * 1000000ULL;
> > 			do_div(blocks, time_us * 8 * 204);
> > 			c->block_count.stat[0].scale = FE_SCALE_COUNTER;
> > 			c->block_count.stat[0].uvalue += blocks;
> > 		}
> > 
> > 		show_per_stats = 1;
> > 	}
> > 
> > At the above, the hardware is set to provide the number of uncorrected
> > blocks on every second, and the logic there calculates the number of
> > blocks based on the bitrate. It shouldn't be hard to do the opposite
> > (e. g. set the hardware for a given number of blocks and calculate
> > the time - I remember I coded it already - just don't remember on
> > what driver - perhaps on an earlier implementation at mb86a20s).
> > 
> > In any case, as the code will require periodic pulls, in order
> > to provide monotonic counters, we implement it via get_stats(), as
> > the DVB core will ensure that it will be called periodically
> > (typically 3 times per second).
> >   
> > > 
> > > So, we implemented like as follows.
> > > 
> > > DTV_STAT_PRE_ERROR_BIT_COUNT
> > > DTV_STAT_POST_ERROR_BIT_COUNT
> > > DTV_STAT_ERROR_BLOCK_COUNT    
> > > => Return bit/packet error counter value in period determined by configured total bit/packet count (numerator).      
> > > 
> > > DTV_STAT_PRE_TOTAL_BIT_COUNT
> > > DTV_STAT_POST_TOTAL_BIT_COUNT
> > > DTV_STAT_TOTAL_BLOCK_COUNT    
> > > => Return currently configured total bit/packet count (denominator).      
> > > 
> > > By this implementation, the user can calculate BER, PER by following calculation.
> > > 
> > > DTV_STAT_PRE_ERROR_BIT_COUNT / DTV_STAT_PRE_TOTAL_BIT_COUNT
> > > DTV_STAT_POST_ERROR_BIT_COUNT / DTV_STAT_POST_TOTAL_BIT_COUNT
> > > DTV_STAT_ERROR_BLOCK_COUNT / DTV_STAT_TOTAL_BLOCK_COUNT
> > > 
> > > But instead, DTV_STAT_XXX_ERROR_XXX_COUNT values are not monotonically increased,
> > > and DTV_STAT_XXX_TOTAL_XX_COUNT will return fixed value.    
> > 
> > That's wrong. you should be doing:
> > 
> > 	DTV_STAT_TOTAL_BLOCK_COUNT += number_of_blocks
> > 
> > every time the block counter registers are updated (and the
> > same for the total bit counter).  
> 
> Btw, as we had another developer with some doubts about stats
> implementation, I wrote a chapter at the documentation in
> order to explain how to properly implement it:
> 
> 	https://linuxtv.org/downloads/v4l-dvb-apis-new/kapi/dtv-core.html#digital-tv-frontend-statistics

I added a few more patches today, improving the docs. They're not
yet upstream, but you can see the documentation at this link:

	https://www.infradead.org/~mchehab/kernel_docs/media/kapi/dtv-core.html#digital-tv-frontend-kabi

> 
> > 
> > Thanks,
> > Mauro  
> 
> 
> 
> Thanks,
> Mauro



Thanks,
Mauro
Yasunari.Takiguchi@sony.com June 27, 2017, 1:51 a.m. UTC | #6
On 2017/06/26 10:24, Mauro Carvalho Chehab wrote:
> Em Sun, 25 Jun 2017 09:15:06 -0300
> Mauro Carvalho Chehab <mchehab@s-opensource.com> escreveu:
> 
>> Em Fri, 23 Jun 2017 10:02:39 -0300
>> Mauro Carvalho Chehab <mchehab@s-opensource.com> escreveu:
>>
>>> Em Mon, 19 Jun 2017 16:56:13 +0900
>>> "Takiguchi, Yasunari" <Yasunari.Takiguchi@sony.com> escreveu:
>>>   
>>>>>> +static int cxd2880_get_frontend_t(struct dvb_frontend *fe,
>>>>>> +				struct dtv_frontend_properties *c)
>>>>>> +{
>>>>>> +	enum cxd2880_ret ret = CXD2880_RESULT_OK;
>>>>>> +	int result = 0;
>>>>>> +	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;
>>>>>> +	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;
>>>>>> +
>>>>>> +	if ((!fe) || (!c)) {
>>>>>> +		pr_err("%s: invalid arg\n", __func__);
>>>>>> +		return -EINVAL;
>>>>>> +	}
>>>>>> +
>>>>>> +	priv = (struct cxd2880_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 == CXD2880_RESULT_OK) {
>>>>>> +		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;
>>>>>> +			dev_err(&priv->spi->dev, "%s: get invalid mode %d\n",
>>>>>> +					__func__, 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;
>>>>>> +			dev_err(&priv->spi->dev, "%s: get invalid guard %d\n",
>>>>>> +					__func__, guard);
>>>>>> +			break;
>>>>>> +		}
>>>>>> +	} else {
>>>>>> +		c->transmission_mode = TRANSMISSION_MODE_2K;
>>>>>> +		c->guard_interval = GUARD_INTERVAL_1_32;
>>>>>> +		dev_dbg(&priv->spi->dev,
>>>>>> +			"%s: ModeGuard err %d\n", __func__, ret);
>>>>>> +	}
>>>>>> +
>>>>>> +	mutex_lock(priv->spi_mutex);
>>>>>> +	ret = cxd2880_tnrdmd_dvbt_mon_tps_info(&priv->tnrdmd, &tps);
>>>>>> +	mutex_unlock(priv->spi_mutex);
>>>>>> +	if (ret == CXD2880_RESULT_OK) {
>>>>>> +		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;
>>>>>> +			dev_err(&priv->spi->dev,
>>>>>> +				"%s: TPSInfo hierarchy invalid %d\n",
>>>>>> +				__func__, 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;
>>>>>> +			dev_err(&priv->spi->dev,
>>>>>> +				"%s: TPSInfo rateHP invalid %d\n",
>>>>>> +				__func__, 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;
>>>>>> +			dev_err(&priv->spi->dev,
>>>>>> +				"%s: TPSInfo rateLP invalid %d\n",
>>>>>> +				__func__, 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;
>>>>>> +			dev_err(&priv->spi->dev,
>>>>>> +				"%s: TPSInfo constellation invalid %d\n",
>>>>>> +				__func__, tps.constellation);
>>>>>> +			break;
>>>>>> +		}
>>>>>> +	} else {
>>>>>> +		c->hierarchy = HIERARCHY_NONE;
>>>>>> +		c->code_rate_HP = FEC_NONE;
>>>>>> +		c->code_rate_LP = FEC_NONE;
>>>>>> +		c->modulation = QPSK;
>>>>>> +		dev_dbg(&priv->spi->dev,
>>>>>> +			"%s: TPS info err %d\n", __func__, ret);
>>>>>> +	}
>>>>>> +
>>>>>> +	mutex_lock(priv->spi_mutex);
>>>>>> +	ret = cxd2880_tnrdmd_dvbt_mon_spectrum_sense(&priv->tnrdmd, &sense);
>>>>>> +	mutex_unlock(priv->spi_mutex);
>>>>>> +	if (ret == CXD2880_RESULT_OK) {
>>>>>> +		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;
>>>>>> +			dev_err(&priv->spi->dev,
>>>>>> +				"%s: spectrum sense invalid %d\n",
>>>>>> +				__func__, sense);
>>>>>> +			break;
>>>>>> +		}
>>>>>> +	} else {
>>>>>> +		c->inversion = INVERSION_OFF;
>>>>>> +		dev_dbg(&priv->spi->dev,
>>>>>> +			"%s: spectrum_sense %d\n", __func__, ret);
>>>>>> +	}
>>>>>> +
>>>>>> +	mutex_lock(priv->spi_mutex);
>>>>>> +	ret = cxd2880_tnrdmd_mon_rf_lvl(&priv->tnrdmd, &strength);
>>>>>> +	mutex_unlock(priv->spi_mutex);
>>>>>> +	if (ret == CXD2880_RESULT_OK) {
>>>>>> +		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;
>>>>>> +		dev_dbg(&priv->spi->dev, "%s: mon_rf_lvl %d\n",
>>>>>> +			__func__, result);
>>>>>> +	}
>>>>>> +
>>>>>> +	result = cxd2880_read_snr(fe, &snr);
>>>>>> +	if (!result) {
>>>>>> +		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;
>>>>>> +		dev_dbg(&priv->spi->dev, "%s: read_snr %d\n", __func__, result);
>>>>>> +	}
>>>>>> +
>>>>>> +	mutex_lock(priv->spi_mutex);
>>>>>> +	ret = cxd2880_pre_bit_err_t(&priv->tnrdmd, &pre_bit_err,
>>>>>> +					&pre_bit_count);      
>>>>>
>>>>> Hmm... reading BER-based measures at get_frontend() may not be
>>>>> the best idea, depending on how the hardware works.
>>>>>
>>>>> I mean, you need to be sure that, if userspace is calling it too
>>>>> often, it won't affect the hardware or the measurement.
>>>>>
>>>>> On other drivers, where each call generates an I2C access,
>>>>> we do that by moving the code that actually call the hardware
>>>>> at get status, and we use jiffies to be sure that it won't be
>>>>> called too often.
>>>>>
>>>>> I dunno if, on your SPI design, this would be an issue or not.      
>>>>
>>>> CXD2880 IC can accept frequently counter register access by SPI.
>>>> Even if such access occurred, it will not affect to IC behavior.
>>>> We checked Sony demodulator IC driver code, dvb_frontends/cxd2841er.c and it reads register information
>>>> when get_frontend called.
>>>>
>>>> In fact, CXD2880 IC do NOT have bit error/packet error counter function to achieve DVB framework API completely.
>>>> Sorry for long explanation, but I'll write in detail.
>>>>
>>>> DTV_STAT_PRE_ERROR_BIT_COUNT
>>>> DTV_STAT_PRE_TOTAL_BIT_COUNT
>>>> DTV_STAT_POST_ERROR_BIT_COUNT
>>>> DTV_STAT_POST_TOTAL_BIT_COUNT
>>>> DTV_STAT_ERROR_BLOCK_COUNT
>>>> DTV_STAT_TOTAL_BLOCK_COUNT
>>>>
>>>> From DVB framework documentation, it seems that the demodulator hardware should have counter
>>>> to accumulate total bit/packet count to decode, and bit/packet error corrected by FEC.
>>>> But unfortunately, CXD2880 demodulator does not have such function.    
>>>
>>> No, this is not a requirement. What actually happens is that userspace
>>> expect those values to be monotonically incremented, as new data is
>>> made available.
>>>   
>>>>
>>>> Instead of it, Sony demodulator has bit/packet error counter for BER, PER calculation.
>>>> The user should configure total bit/packet count (denominator) for BER/PER measurement by some register setting.    
>>>
>>> Yeah, that's a common practice: usually, the counters are initialized
>>> by some registers (on a few hardware, it is hardcoded).
>>>   
>>>> The IC hardware will update the error counter automatically in period determined by
>>>> configured total bit/packet count (denominator).    
>>>
>>> So, the driver need to be sure that it won't read the register too
>>> early, e. g. it should either be reading some register bits to know
>>> when to read the data, or it needs a logic that will estimate when
>>> the data will be ready, not updating the values to early.
>>>
>>> An example of the first case is the mb86a20s. There, you can see at:
>>> mb86a20s_get_pre_ber():
>>>
>>> 	/* Check if the BER measures are already available */
>>> 	rc = mb86a20s_readreg(state, 0x54);
>>> 	if (rc < 0)
>>> 		return rc;
>>>
>>> 	/* Check if data is available for that layer */
>>> 	if (!(rc & (1 << layer))) {
>>> 		dev_dbg(&state->i2c->dev,
>>> 			"%s: preBER for layer %c is not available yet.\n",
>>> 			__func__, 'A' + layer);
>>> 		return -EBUSY;
>>> 	}
>>>
>>> An example of the second case is dib8000. There, you can see at:
>>> dib8000_get_stats():
>>>
>>>
>>> 	/* Check if time for stats was elapsed */
>>> 	if (time_after(jiffies, state->per_jiffies_stats)) {
>>> 		state->per_jiffies_stats = jiffies + msecs_to_jiffies(1000);
>>>
>>> ...
>>> 		/* Get UCB measures */
>>> 		dib8000_read_unc_blocks(fe, &val);
>>> 		if (val < state->init_ucb)
>>> 			state->init_ucb += 0x100000000LL;
>>>
>>> 		c->block_error.stat[0].scale = FE_SCALE_COUNTER;
>>> 		c->block_error.stat[0].uvalue = val + state->init_ucb;
>>>
>>> 		/* Estimate the number of packets based on bitrate */
>>> 		if (!time_us)
>>> 			time_us = dib8000_get_time_us(fe, -1);
>>>
>>> 		if (time_us) {
>>> 			blocks = 1250000ULL * 1000000ULL;
>>> 			do_div(blocks, time_us * 8 * 204);
>>> 			c->block_count.stat[0].scale = FE_SCALE_COUNTER;
>>> 			c->block_count.stat[0].uvalue += blocks;
>>> 		}
>>>
>>> 		show_per_stats = 1;
>>> 	}
>>>
>>> At the above, the hardware is set to provide the number of uncorrected
>>> blocks on every second, and the logic there calculates the number of
>>> blocks based on the bitrate. It shouldn't be hard to do the opposite
>>> (e. g. set the hardware for a given number of blocks and calculate
>>> the time - I remember I coded it already - just don't remember on
>>> what driver - perhaps on an earlier implementation at mb86a20s).
>>>
>>> In any case, as the code will require periodic pulls, in order
>>> to provide monotonic counters, we implement it via get_stats(), as
>>> the DVB core will ensure that it will be called periodically
>>> (typically 3 times per second).
>>>   
>>>>
>>>> So, we implemented like as follows.
>>>>
>>>> DTV_STAT_PRE_ERROR_BIT_COUNT
>>>> DTV_STAT_POST_ERROR_BIT_COUNT
>>>> DTV_STAT_ERROR_BLOCK_COUNT    
>>>> => Return bit/packet error counter value in period determined by configured total bit/packet count (numerator).      
>>>>
>>>> DTV_STAT_PRE_TOTAL_BIT_COUNT
>>>> DTV_STAT_POST_TOTAL_BIT_COUNT
>>>> DTV_STAT_TOTAL_BLOCK_COUNT    
>>>> => Return currently configured total bit/packet count (denominator).      
>>>>
>>>> By this implementation, the user can calculate BER, PER by following calculation.
>>>>
>>>> DTV_STAT_PRE_ERROR_BIT_COUNT / DTV_STAT_PRE_TOTAL_BIT_COUNT
>>>> DTV_STAT_POST_ERROR_BIT_COUNT / DTV_STAT_POST_TOTAL_BIT_COUNT
>>>> DTV_STAT_ERROR_BLOCK_COUNT / DTV_STAT_TOTAL_BLOCK_COUNT
>>>>
>>>> But instead, DTV_STAT_XXX_ERROR_XXX_COUNT values are not monotonically increased,
>>>> and DTV_STAT_XXX_TOTAL_XX_COUNT will return fixed value.    
>>>
>>> That's wrong. you should be doing:
>>>
>>> 	DTV_STAT_TOTAL_BLOCK_COUNT += number_of_blocks
>>>
>>> every time the block counter registers are updated (and the
>>> same for the total bit counter).  
>>
>> Btw, as we had another developer with some doubts about stats
>> implementation, I wrote a chapter at the documentation in
>> order to explain how to properly implement it:
>>
>> 	https://linuxtv.org/downloads/v4l-dvb-apis-new/kapi/dtv-core.html#digital-tv-frontend-statistics
> 
> I added a few more patches today, improving the docs. They're not
> yet upstream, but you can see the documentation at this link:
> 
> 	https://www.infradead.org/~mchehab/kernel_docs/media/kapi/dtv-core.html#digital-tv-frontend-kabi

Thanks a lot for your detail explanation.
We understood your thought.
 
In fact, CXD2880 does NOT have mb86a20s like registers to know that
the error counters are updated from previous read or not.
 
In addition, unfortunately, CXD2880 does NOT have a feature that
the user can configure the error counter update period by time.
 
So, as you wrote in the documentation at above link, we'll need to implement like:
 “the driver need to ensure that it won’t be reading from the register too often
 and/or estimate the total number of bits/blocks.”

We'll discuss how to do it internally.

Thanks
Takiguchi
diff mbox

Patch

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..66d78fb93a13
--- /dev/null
+++ b/drivers/media/dvb-frontends/cxd2880/cxd2880_top.c
@@ -0,0 +1,1550 @@ 
+/*
+ * cxd2880_top.c
+ * Sony CXD2880 DVB-T2/T tuner + demodulator driver
+ *
+ * Copyright (C) 2016, 2017 Sony Semiconductor Solutions Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; version 2 of the License.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/spi/spi.h>
+
+#include "dvb_frontend.h"
+
+#include "cxd2880.h"
+#include "cxd2880_tnrdmd_mon.h"
+#include "cxd2880_tnrdmd_dvbt2_mon.h"
+#include "cxd2880_tnrdmd_dvbt_mon.h"
+#include "cxd2880_integ_dvbt2.h"
+#include "cxd2880_integ_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 */
+};
+
+/*
+ * return value conversion table
+ */
+static int return_tbl[] = {
+	0,             /* CXD2880_RESULT_OK */
+	-EINVAL,       /* CXD2880_RESULT_ERROR_ARG*/
+	-EIO,          /* CXD2880_RESULT_ERROR_IO */
+	-EPERM,        /* CXD2880_RESULT_ERROR_SW_STATE */
+	-EBUSY,        /* CXD2880_RESULT_ERROR_HW_STATE */
+	-ETIME,        /* CXD2880_RESULT_ERROR_TIMEOUT */
+	-EAGAIN,       /* CXD2880_RESULT_ERROR_UNLOCK */
+	-ERANGE,       /* CXD2880_RESULT_ERROR_RANGE */
+	-EOPNOTSUPP,   /* CXD2880_RESULT_ERROR_NOSUPPORT */
+	-ECANCELED,    /* CXD2880_RESULT_ERROR_CANCEL */
+	-EPERM,        /* CXD2880_RESULT_ERROR_OTHER */
+	-EOVERFLOW,    /* CXD2880_RESULT_ERROR_OVERFLOW */
+	0,             /* CXD2880_RESULT_OK_CONFIRM */
+};
+
+static enum cxd2880_ret cxd2880_pre_bit_err_t(
+		struct cxd2880_tnrdmd *tnrdmd, u32 *pre_bit_err,
+		u32 *pre_bit_count)
+{
+	u8 rdata[2];
+
+	if ((!tnrdmd) || (!pre_bit_err) || (!pre_bit_count))
+		return CXD2880_RESULT_ERROR_ARG;
+
+	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
+		return CXD2880_RESULT_ERROR_ARG;
+
+	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
+		return CXD2880_RESULT_ERROR_SW_STATE;
+
+	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT)
+		return CXD2880_RESULT_ERROR_SW_STATE;
+
+	if (slvt_freeze_reg(tnrdmd) != CXD2880_RESULT_OK)
+		return CXD2880_RESULT_ERROR_IO;
+
+	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
+				0x00, 0x10) != CXD2880_RESULT_OK) {
+		slvt_unfreeze_reg(tnrdmd);
+		return CXD2880_RESULT_ERROR_IO;
+	}
+
+	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
+				0x39, rdata, 1) != CXD2880_RESULT_OK) {
+		slvt_unfreeze_reg(tnrdmd);
+		return CXD2880_RESULT_ERROR_IO;
+	}
+
+	if ((rdata[0] & 0x01) == 0) {
+		slvt_unfreeze_reg(tnrdmd);
+		return CXD2880_RESULT_ERROR_HW_STATE;
+	}
+
+	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
+				0x22, rdata, 2) != CXD2880_RESULT_OK) {
+		slvt_unfreeze_reg(tnrdmd);
+		return CXD2880_RESULT_ERROR_IO;
+	}
+
+	*pre_bit_err = (rdata[0] << 8) | rdata[1];
+
+	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
+				0x6F, rdata, 1) != CXD2880_RESULT_OK) {
+		slvt_unfreeze_reg(tnrdmd);
+		return CXD2880_RESULT_ERROR_IO;
+	}
+
+	slvt_unfreeze_reg(tnrdmd);
+
+	*pre_bit_count = ((rdata[0] & 0x07) == 0) ?
+			256 : (0x1000 << (rdata[0] & 0x07));
+
+	return CXD2880_RESULT_OK;
+}
+
+static enum cxd2880_ret 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];
+
+	if ((!tnrdmd) || (!pre_bit_err) || (!pre_bit_count))
+		return CXD2880_RESULT_ERROR_ARG;
+
+	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
+		return CXD2880_RESULT_ERROR_ARG;
+
+	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
+		return CXD2880_RESULT_ERROR_SW_STATE;
+
+	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT2)
+		return CXD2880_RESULT_ERROR_SW_STATE;
+
+	if (slvt_freeze_reg(tnrdmd) != CXD2880_RESULT_OK)
+		return CXD2880_RESULT_ERROR_IO;
+
+	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
+				0x00, 0x0B) != CXD2880_RESULT_OK) {
+		slvt_unfreeze_reg(tnrdmd);
+		return CXD2880_RESULT_ERROR_IO;
+	}
+
+	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
+				0x3C, data, sizeof(data))
+				 != CXD2880_RESULT_OK) {
+		slvt_unfreeze_reg(tnrdmd);
+		return CXD2880_RESULT_ERROR_IO;
+	}
+
+	if (!(data[0] & 0x01)) {
+		slvt_unfreeze_reg(tnrdmd);
+		return CXD2880_RESULT_ERROR_HW_STATE;
+	}
+	*pre_bit_err =
+	((data[1] & 0x0F) << 24) | (data[2] << 16) | (data[3] << 8) | data[4];
+
+	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
+				0xA0, data, 1) != CXD2880_RESULT_OK) {
+		slvt_unfreeze_reg(tnrdmd);
+		return CXD2880_RESULT_ERROR_IO;
+	}
+
+	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);
+
+	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
+				0x00, 0x20) != CXD2880_RESULT_OK)
+		return CXD2880_RESULT_ERROR_IO;
+
+	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
+				0x6F, data, 1) != CXD2880_RESULT_OK)
+		return CXD2880_RESULT_ERROR_IO;
+
+	period_exp = data[0] & 0x0F;
+
+	*pre_bit_count = (1U << period_exp) * n_ldpc;
+
+	return CXD2880_RESULT_OK;
+}
+
+static enum cxd2880_ret 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;
+
+	if ((!tnrdmd) || (!post_bit_err) || (!post_bit_count))
+		return CXD2880_RESULT_ERROR_ARG;
+
+	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
+		return CXD2880_RESULT_ERROR_ARG;
+
+	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
+		return CXD2880_RESULT_ERROR_SW_STATE;
+
+	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT)
+		return CXD2880_RESULT_ERROR_SW_STATE;
+
+	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
+				0x00, 0x0D) != CXD2880_RESULT_OK)
+		return CXD2880_RESULT_ERROR_IO;
+
+	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
+				0x15, rdata, 3) != CXD2880_RESULT_OK)
+		return CXD2880_RESULT_ERROR_IO;
+
+	if ((rdata[0] & 0x40) == 0)
+		return CXD2880_RESULT_ERROR_HW_STATE;
+
+	*post_bit_err = ((rdata[0] & 0x3F) << 16) | (rdata[1] << 8) | rdata[2];
+
+	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
+				0x00, 0x10) != CXD2880_RESULT_OK)
+		return CXD2880_RESULT_ERROR_IO;
+
+	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
+				0x60, rdata, 1) != CXD2880_RESULT_OK)
+		return CXD2880_RESULT_ERROR_IO;
+
+	period_exp = (rdata[0] & 0x1F);
+
+	if ((period_exp <= 11) && (bit_error > (1U << period_exp) * 204 * 8))
+		return CXD2880_RESULT_ERROR_HW_STATE;
+
+	if (period_exp == 11)
+		*post_bit_count = 3342336;
+	else
+		*post_bit_count = (1U << period_exp) * 204 * 81;
+
+	return CXD2880_RESULT_OK;
+}
+
+static enum cxd2880_ret 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;
+
+	if ((!tnrdmd) || (!post_bit_err) || (!post_bit_count))
+		return CXD2880_RESULT_ERROR_ARG;
+
+	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
+		return CXD2880_RESULT_ERROR_ARG;
+
+	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
+		return CXD2880_RESULT_ERROR_SW_STATE;
+
+	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT2)
+		return CXD2880_RESULT_ERROR_SW_STATE;
+
+	{
+		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;
+
+		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 (slvt_freeze_reg(tnrdmd) != CXD2880_RESULT_OK)
+			return CXD2880_RESULT_ERROR_IO;
+
+		if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
+					0x00, 0x0B) != CXD2880_RESULT_OK) {
+			slvt_unfreeze_reg(tnrdmd);
+			return CXD2880_RESULT_ERROR_IO;
+		}
+
+		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
+					 0x15, data, 3) != CXD2880_RESULT_OK) {
+			slvt_unfreeze_reg(tnrdmd);
+			return CXD2880_RESULT_ERROR_IO;
+		}
+
+		if (!(data[0] & 0x40)) {
+			slvt_unfreeze_reg(tnrdmd);
+			return CXD2880_RESULT_ERROR_HW_STATE;
+		}
+
+		*post_bit_err =
+			((data[0] & 0x3F) << 16) | (data[1] << 8) | data[2];
+
+		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
+					0x9D, data, 1) != CXD2880_RESULT_OK) {
+			slvt_unfreeze_reg(tnrdmd);
+			return CXD2880_RESULT_ERROR_IO;
+		}
+
+		plp_code_rate =
+		(enum cxd2880_dvbt2_plp_code_rate)(data[0] & 0x07);
+
+		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
+					0xA0, data, 1) != CXD2880_RESULT_OK) {
+			slvt_unfreeze_reg(tnrdmd);
+			return CXD2880_RESULT_ERROR_IO;
+		}
+
+		plp_fec_type = (enum cxd2880_dvbt2_plp_fec)(data[0] & 0x03);
+
+		slvt_unfreeze_reg(tnrdmd);
+
+		if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
+					0x00, 0x20) != CXD2880_RESULT_OK)
+			return CXD2880_RESULT_ERROR_IO;
+
+		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
+					0x72, data, 1) != CXD2880_RESULT_OK)
+			return CXD2880_RESULT_ERROR_IO;
+
+		period_exp = data[0] & 0x0F;
+
+		if ((plp_fec_type > CXD2880_DVBT2_FEC_LDPC_64K) ||
+			(plp_code_rate > CXD2880_DVBT2_R2_5))
+			return CXD2880_RESULT_ERROR_HW_STATE;
+
+		n_bch = n_bch_bits_lookup[plp_fec_type][plp_code_rate];
+	}
+
+	if (*post_bit_err > ((1U << period_exp) * n_bch))
+		return CXD2880_RESULT_ERROR_HW_STATE;
+
+	*post_bit_count = (1U << period_exp) * n_bch;
+
+	return CXD2880_RESULT_OK;
+}
+
+static enum cxd2880_ret cxd2880_read_block_err_t(
+					struct cxd2880_tnrdmd *tnrdmd,
+					u32 *block_err,
+					u32 *block_count)
+{
+	u8 rdata[3];
+
+	if ((!tnrdmd) || (!block_err) || (!block_count))
+		return CXD2880_RESULT_ERROR_ARG;
+
+	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
+		return CXD2880_RESULT_ERROR_ARG;
+
+	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
+		return CXD2880_RESULT_ERROR_SW_STATE;
+
+	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT)
+		return CXD2880_RESULT_ERROR_SW_STATE;
+
+	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
+				0x00, 0x0D) != CXD2880_RESULT_OK)
+		return CXD2880_RESULT_ERROR_IO;
+
+	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
+				0x18, rdata, 3) != CXD2880_RESULT_OK)
+		return CXD2880_RESULT_ERROR_IO;
+
+	if ((rdata[0] & 0x01) == 0)
+		return CXD2880_RESULT_ERROR_HW_STATE;
+
+	*block_err = (rdata[1] << 8) | rdata[2];
+
+	if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
+				0x00, 0x10) != CXD2880_RESULT_OK)
+		return CXD2880_RESULT_ERROR_IO;
+
+	if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
+				0x5C, rdata, 1) != CXD2880_RESULT_OK)
+		return CXD2880_RESULT_ERROR_IO;
+
+	*block_count = 1U << (rdata[0] & 0x0F);
+
+	if ((*block_count == 0) || (*block_err > *block_count))
+		return CXD2880_RESULT_ERROR_HW_STATE;
+
+	return CXD2880_RESULT_OK;
+}
+
+static enum cxd2880_ret cxd2880_read_block_err_t2(
+					struct cxd2880_tnrdmd *tnrdmd,
+					u32 *block_err,
+					u32 *block_count)
+{
+	if ((!tnrdmd) || (!block_err) || (!block_count))
+		return CXD2880_RESULT_ERROR_ARG;
+
+	if (tnrdmd->diver_mode == CXD2880_TNRDMD_DIVERMODE_SUB)
+		return CXD2880_RESULT_ERROR_ARG;
+
+	if (tnrdmd->state != CXD2880_TNRDMD_STATE_ACTIVE)
+		return CXD2880_RESULT_ERROR_SW_STATE;
+	if (tnrdmd->sys != CXD2880_DTV_SYS_DVBT2)
+		return CXD2880_RESULT_ERROR_SW_STATE;
+
+	{
+		u8 rdata[3];
+
+		if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
+					0x00, 0x0B) != CXD2880_RESULT_OK)
+			return CXD2880_RESULT_ERROR_IO;
+
+		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
+					0x18, rdata, 3) != CXD2880_RESULT_OK)
+			return CXD2880_RESULT_ERROR_IO;
+
+		if ((rdata[0] & 0x01) == 0)
+			return CXD2880_RESULT_ERROR_HW_STATE;
+
+		*block_err = (rdata[1] << 8) | rdata[2];
+
+		if (tnrdmd->io->write_reg(tnrdmd->io, CXD2880_IO_TGT_DMD,
+					0x00, 0x24) != CXD2880_RESULT_OK)
+			return CXD2880_RESULT_ERROR_IO;
+
+		if (tnrdmd->io->read_regs(tnrdmd->io, CXD2880_IO_TGT_DMD,
+					0xDC, rdata, 1) != CXD2880_RESULT_OK)
+			return CXD2880_RESULT_ERROR_IO;
+
+		*block_count = 1U << (rdata[0] & 0x0F);
+	}
+
+	if ((*block_count == 0) || (*block_err > *block_count))
+		return CXD2880_RESULT_ERROR_HW_STATE;
+
+	return CXD2880_RESULT_OK;
+}
+
+static void cxd2880_release(struct dvb_frontend *fe)
+{
+	struct cxd2880_priv *priv = NULL;
+
+	if (!fe) {
+		pr_err("%s: invalid arg.\n", __func__);
+		return;
+	}
+	priv = (struct cxd2880_priv *)fe->demodulator_priv;
+	kfree(priv);
+}
+
+static int cxd2880_init(struct dvb_frontend *fe)
+{
+	enum cxd2880_ret ret = CXD2880_RESULT_OK;
+	struct cxd2880_priv *priv = NULL;
+	struct cxd2880_tnrdmd_create_param create_param;
+
+	if (!fe) {
+		pr_err("%s: invalid arg.\n", __func__);
+		return -EINVAL;
+	}
+
+	priv = (struct cxd2880_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 != CXD2880_RESULT_OK) {
+			mutex_unlock(priv->spi_mutex);
+			dev_info(&priv->spi->dev,
+				"%s: cxd2880 tnrdmd create failed %d\n",
+				__func__, ret);
+			return return_tbl[ret];
+		}
+	}
+	ret = cxd2880_integ_init(&priv->tnrdmd);
+	if (ret != CXD2880_RESULT_OK) {
+		mutex_unlock(priv->spi_mutex);
+		dev_err(&priv->spi->dev, "%s: cxd2880 integ init failed %d\n",
+				__func__, ret);
+		return return_tbl[ret];
+	}
+	mutex_unlock(priv->spi_mutex);
+
+	dev_dbg(&priv->spi->dev, "%s: OK.\n", __func__);
+
+	return return_tbl[ret];
+}
+
+static int cxd2880_sleep(struct dvb_frontend *fe)
+{
+	enum cxd2880_ret ret = CXD2880_RESULT_OK;
+	struct cxd2880_priv *priv = NULL;
+
+	if (!fe) {
+		pr_err("%s: inavlid arg\n", __func__);
+		return -EINVAL;
+	}
+
+	priv = (struct cxd2880_priv *)fe->demodulator_priv;
+
+	mutex_lock(priv->spi_mutex);
+	ret = cxd2880_tnrdmd_sleep(&priv->tnrdmd);
+	mutex_unlock(priv->spi_mutex);
+
+	dev_dbg(&priv->spi->dev, "%s: tnrdmd_sleep ret %d\n",
+		__func__, ret);
+
+	return return_tbl[ret];
+}
+
+static int cxd2880_read_signal_strength(struct dvb_frontend *fe,
+				u16 *strength)
+{
+	enum cxd2880_ret ret = CXD2880_RESULT_OK;
+	struct cxd2880_priv *priv = NULL;
+	struct dtv_frontend_properties *c = NULL;
+	int level = 0;
+
+	if ((!fe) || (!strength)) {
+		pr_err("%s: inavlid arg\n", __func__);
+		return -EINVAL;
+	}
+
+	priv = (struct cxd2880_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 {
+		dev_dbg(&priv->spi->dev, "%s: invalid system\n", __func__);
+		mutex_unlock(priv->spi_mutex);
+		return -EINVAL;
+	}
+	mutex_unlock(priv->spi_mutex);
+
+	level /= 125;
+	/* -105dBm - -30dBm (-105000/125 = -840, -30000/125 = -240 */
+	level = clamp(level, -840, -240);
+	/* scale value to 0x0000-0xFFFF */
+	*strength = (u16)(((level + 840) * 0xFFFF) / (-240 + 840));
+
+	if (ret != CXD2880_RESULT_OK)
+		dev_dbg(&priv->spi->dev, "%s: ret = %d\n", __func__, ret);
+
+	return return_tbl[ret];
+}
+
+static int cxd2880_read_snr(struct dvb_frontend *fe, u16 *snr)
+{
+	enum cxd2880_ret ret = CXD2880_RESULT_OK;
+	int snrvalue = 0;
+	struct cxd2880_priv *priv = NULL;
+	struct dtv_frontend_properties *c = NULL;
+
+	if ((!fe) || (!snr)) {
+		pr_err("%s: inavlid arg\n", __func__);
+		return -EINVAL;
+	}
+
+	priv = (struct cxd2880_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 {
+		dev_err(&priv->spi->dev, "%s: invalid system\n", __func__);
+		mutex_unlock(priv->spi_mutex);
+		return -EINVAL;
+	}
+	mutex_unlock(priv->spi_mutex);
+
+	if (snrvalue < 0)
+		snrvalue = 0;
+	*snr = (u16)snrvalue;
+
+	if (ret != CXD2880_RESULT_OK)
+		dev_dbg(&priv->spi->dev, "%s: ret = %d\n", __func__, ret);
+
+	return return_tbl[ret];
+}
+
+static int cxd2880_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
+{
+	enum cxd2880_ret ret = CXD2880_RESULT_OK;
+	struct cxd2880_priv *priv = NULL;
+	struct dtv_frontend_properties *c = NULL;
+
+	if ((!fe) || (!ucblocks)) {
+		pr_err("%s: inavlid arg\n", __func__);
+		return -EINVAL;
+	}
+
+	priv = (struct cxd2880_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 {
+		dev_err(&priv->spi->dev, "%s: invlaid system\n", __func__);
+		mutex_unlock(priv->spi_mutex);
+		return -EINVAL;
+	}
+	mutex_unlock(priv->spi_mutex);
+
+	if (ret != CXD2880_RESULT_OK)
+		dev_dbg(&priv->spi->dev, "%s: ret = %d\n", __func__, ret);
+
+	return return_tbl[ret];
+}
+
+static int cxd2880_read_ber(struct dvb_frontend *fe, u32 *ber)
+{
+	enum cxd2880_ret ret = CXD2880_RESULT_OK;
+	struct cxd2880_priv *priv = NULL;
+	struct dtv_frontend_properties *c = NULL;
+
+	if ((!fe) || (!ber)) {
+		pr_err("%s: inavlid arg\n", __func__);
+		return -EINVAL;
+	}
+
+	priv = (struct cxd2880_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_pre_rsber(&priv->tnrdmd,
+						ber);
+		/* x100 to change unit.(10^7 -> 10^9 */
+		*ber *= 100;
+	} else if (c->delivery_system == SYS_DVBT2) {
+		ret = cxd2880_tnrdmd_dvbt2_mon_pre_bchber(&priv->tnrdmd,
+						ber);
+	} else {
+		dev_err(&priv->spi->dev, "%s: invlaid system\n", __func__);
+		mutex_unlock(priv->spi_mutex);
+		return -EINVAL;
+	}
+	mutex_unlock(priv->spi_mutex);
+
+	if (ret != CXD2880_RESULT_OK)
+		dev_dbg(&priv->spi->dev, "%s: ret = %d\n", __func__, ret);
+
+	return return_tbl[ret];
+}
+
+static int cxd2880_set_frontend(struct dvb_frontend *fe)
+{
+	enum cxd2880_ret ret = CXD2880_RESULT_OK;
+	struct dtv_frontend_properties *c;
+	struct cxd2880_priv *priv;
+	enum cxd2880_dtv_bandwidth bw = CXD2880_DTV_BW_1_7_MHZ;
+
+	if (!fe) {
+		pr_err("%s: inavlid arg\n", __func__);
+		return -EINVAL;
+	}
+
+	priv = (struct cxd2880_priv *)fe->demodulator_priv;
+	c = &fe->dtv_property_cache;
+
+	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;
+	}
+
+	dev_info(&priv->spi->dev, "%s: sys:%d freq:%d bw:%d\n", __func__,
+			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_integ_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;
+		ret = cxd2880_integ_dvbt2_tune(&priv->tnrdmd,
+						&priv->dvbt2_tune_param);
+	} else {
+		dev_err(&priv->spi->dev, "%s: invalid system\n", __func__);
+		mutex_unlock(priv->spi_mutex);
+		return -EINVAL;
+	}
+	mutex_unlock(priv->spi_mutex);
+	dev_info(&priv->spi->dev, "%s: tune result %d\n", __func__, ret);
+
+	return return_tbl[ret];
+}
+
+static int cxd2880_read_status(struct dvb_frontend *fe,
+				enum fe_status *status)
+{
+	enum cxd2880_ret ret = CXD2880_RESULT_OK;
+	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("%s: invalid arg\n", __func__);
+		return -EINVAL;
+	}
+
+	priv = (struct cxd2880_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 {
+			dev_err(&priv->spi->dev,
+				"%s: invlaid system", __func__);
+			mutex_unlock(priv->spi_mutex);
+			return -EINVAL;
+		}
+
+		mutex_unlock(priv->spi_mutex);
+		if (ret != CXD2880_RESULT_OK) {
+			dev_err(&priv->spi->dev, "%s: failed. sys = %d\n",
+				__func__, priv->tnrdmd.sys);
+			return  return_tbl[ret];
+		}
+
+		if (sync == 6) {
+			*status = FE_HAS_SIGNAL |
+					FE_HAS_CARRIER;
+		}
+		if (lock)
+			*status |= FE_HAS_VITERBI |
+					FE_HAS_SYNC |
+					FE_HAS_LOCK;
+	}
+
+	dev_dbg(&priv->spi->dev, "%s: status %d result %d\n", __func__,
+		*status, ret);
+
+	return  return_tbl[CXD2880_RESULT_OK];
+}
+
+static int cxd2880_tune(struct dvb_frontend *fe,
+			bool retune,
+			unsigned int mode_flags,
+			unsigned int *delay,
+			enum fe_status *status)
+{
+	int ret = 0;
+
+	if ((!fe) || (!delay) || (!status)) {
+		pr_err("%s: invalid arg.", __func__);
+		return -EINVAL;
+	}
+
+	if (retune) {
+		ret = cxd2880_set_frontend(fe);
+		if (ret) {
+			pr_err("%s: cxd2880_set_frontend failed %d\n",
+				__func__, 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)
+{
+	enum cxd2880_ret ret = CXD2880_RESULT_OK;
+	int result = 0;
+	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;
+	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;
+
+	if ((!fe) || (!c)) {
+		pr_err("%s: invalid arg\n", __func__);
+		return -EINVAL;
+	}
+
+	priv = (struct cxd2880_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 == CXD2880_RESULT_OK) {
+		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;
+			dev_err(&priv->spi->dev, "%s: get invalid mode %d\n",
+					__func__, 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;
+			dev_err(&priv->spi->dev, "%s: get invalid guard %d\n",
+					__func__, guard);
+			break;
+		}
+	} else {
+		c->transmission_mode = TRANSMISSION_MODE_2K;
+		c->guard_interval = GUARD_INTERVAL_1_32;
+		dev_dbg(&priv->spi->dev,
+			"%s: ModeGuard err %d\n", __func__, ret);
+	}
+
+	mutex_lock(priv->spi_mutex);
+	ret = cxd2880_tnrdmd_dvbt_mon_tps_info(&priv->tnrdmd, &tps);
+	mutex_unlock(priv->spi_mutex);
+	if (ret == CXD2880_RESULT_OK) {
+		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;
+			dev_err(&priv->spi->dev,
+				"%s: TPSInfo hierarchy invalid %d\n",
+				__func__, 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;
+			dev_err(&priv->spi->dev,
+				"%s: TPSInfo rateHP invalid %d\n",
+				__func__, 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;
+			dev_err(&priv->spi->dev,
+				"%s: TPSInfo rateLP invalid %d\n",
+				__func__, 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;
+			dev_err(&priv->spi->dev,
+				"%s: TPSInfo constellation invalid %d\n",
+				__func__, tps.constellation);
+			break;
+		}
+	} else {
+		c->hierarchy = HIERARCHY_NONE;
+		c->code_rate_HP = FEC_NONE;
+		c->code_rate_LP = FEC_NONE;
+		c->modulation = QPSK;
+		dev_dbg(&priv->spi->dev,
+			"%s: TPS info err %d\n", __func__, ret);
+	}
+
+	mutex_lock(priv->spi_mutex);
+	ret = cxd2880_tnrdmd_dvbt_mon_spectrum_sense(&priv->tnrdmd, &sense);
+	mutex_unlock(priv->spi_mutex);
+	if (ret == CXD2880_RESULT_OK) {
+		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;
+			dev_err(&priv->spi->dev,
+				"%s: spectrum sense invalid %d\n",
+				__func__, sense);
+			break;
+		}
+	} else {
+		c->inversion = INVERSION_OFF;
+		dev_dbg(&priv->spi->dev,
+			"%s: spectrum_sense %d\n", __func__, ret);
+	}
+
+	mutex_lock(priv->spi_mutex);
+	ret = cxd2880_tnrdmd_mon_rf_lvl(&priv->tnrdmd, &strength);
+	mutex_unlock(priv->spi_mutex);
+	if (ret == CXD2880_RESULT_OK) {
+		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;
+		dev_dbg(&priv->spi->dev, "%s: mon_rf_lvl %d\n",
+			__func__, result);
+	}
+
+	result = cxd2880_read_snr(fe, &snr);
+	if (!result) {
+		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;
+		dev_dbg(&priv->spi->dev, "%s: read_snr %d\n", __func__, result);
+	}
+
+	mutex_lock(priv->spi_mutex);
+	ret = cxd2880_pre_bit_err_t(&priv->tnrdmd, &pre_bit_err,
+					&pre_bit_count);
+	mutex_unlock(priv->spi_mutex);
+	if (ret == CXD2880_RESULT_OK) {
+		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;
+		dev_dbg(&priv->spi->dev,
+			"%s: pre_bit_error_t failed %d\n",
+			__func__, ret);
+	}
+
+	mutex_lock(priv->spi_mutex);
+	ret = cxd2880_post_bit_err_t(&priv->tnrdmd,
+				&post_bit_err, &post_bit_count);
+	mutex_unlock(priv->spi_mutex);
+	if (ret == CXD2880_RESULT_OK) {
+		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;
+		dev_dbg(&priv->spi->dev,
+			"%s: post_bit_err_t %d\n", __func__, ret);
+	}
+
+	mutex_lock(priv->spi_mutex);
+	ret = cxd2880_read_block_err_t(&priv->tnrdmd,
+					&block_err, &block_count);
+	mutex_unlock(priv->spi_mutex);
+	if (ret == CXD2880_RESULT_OK) {
+		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;
+		dev_dbg(&priv->spi->dev,
+			"%s: read_block_err_t  %d\n", __func__, ret);
+	}
+
+	return 0;
+}
+
+static int cxd2880_get_frontend_t2(struct dvb_frontend *fe,
+				struct dtv_frontend_properties *c)
+{
+	enum cxd2880_ret ret = CXD2880_RESULT_OK;
+	int result = 0;
+	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;
+	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;
+
+	if ((!fe) || (!c)) {
+		pr_err("%s: invalid arg.\n", __func__);
+		return -EINVAL;
+	}
+
+	priv = (struct cxd2880_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 == CXD2880_RESULT_OK) {
+		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;
+			dev_err(&priv->spi->dev,
+				"%s: L1Pre fft_mode invalid %d\n",
+				__func__, 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;
+			dev_err(&priv->spi->dev,
+				"%s: L1Pre gi invalid %d\n",
+				__func__, l1pre.gi);
+			break;
+		}
+	} else {
+		c->transmission_mode = TRANSMISSION_MODE_2K;
+		c->guard_interval = GUARD_INTERVAL_1_32;
+		dev_dbg(&priv->spi->dev,
+			"%s: L1Pre err %d\n", __func__, 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 == CXD2880_RESULT_OK) {
+		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;
+			dev_err(&priv->spi->dev,
+				"%s: CodeRate invalid %d\n",
+				__func__, coderate);
+			break;
+		}
+	} else {
+		c->fec_inner = FEC_NONE;
+		dev_dbg(&priv->spi->dev, "%s: CodeRate %d\n", __func__, 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 == CXD2880_RESULT_OK) {
+		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;
+			dev_err(&priv->spi->dev,
+				"%s: QAM invalid %d\n",
+				__func__, qam);
+			break;
+		}
+	} else {
+		c->modulation = QPSK;
+		dev_dbg(&priv->spi->dev, "%s: QAM %d\n", __func__, ret);
+	}
+
+	mutex_lock(priv->spi_mutex);
+	ret = cxd2880_tnrdmd_dvbt2_mon_spectrum_sense(&priv->tnrdmd, &sense);
+	mutex_unlock(priv->spi_mutex);
+	if (ret == CXD2880_RESULT_OK) {
+		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;
+			dev_err(&priv->spi->dev,
+				"%s: spectrum sense invalid %d\n",
+				__func__, sense);
+			break;
+		}
+	} else {
+		c->inversion = INVERSION_OFF;
+		dev_dbg(&priv->spi->dev,
+			"%s: SpectrumSense %d\n", __func__, ret);
+	}
+
+	mutex_lock(priv->spi_mutex);
+	ret = cxd2880_tnrdmd_mon_rf_lvl(&priv->tnrdmd, &strength);
+	mutex_unlock(priv->spi_mutex);
+	if (ret == CXD2880_RESULT_OK) {
+		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;
+		dev_dbg(&priv->spi->dev,
+			"%s: mon_rf_lvl %d\n", __func__, ret);
+	}
+
+	result = cxd2880_read_snr(fe, &snr);
+	if (!result) {
+		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;
+		dev_dbg(&priv->spi->dev, "%s: read_snr %d\n", __func__, result);
+	}
+
+	mutex_lock(priv->spi_mutex);
+	ret = cxd2880_pre_bit_err_t2(&priv->tnrdmd,
+				&pre_bit_err,
+				&pre_bit_count);
+	mutex_unlock(priv->spi_mutex);
+	if (ret == CXD2880_RESULT_OK) {
+		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;
+		dev_dbg(&priv->spi->dev,
+			"%s: read_bit_err_t2 %d\n", __func__, ret);
+	}
+
+	mutex_lock(priv->spi_mutex);
+	ret = cxd2880_post_bit_err_t2(&priv->tnrdmd,
+				&post_bit_err, &post_bit_count);
+	mutex_unlock(priv->spi_mutex);
+	if (ret == CXD2880_RESULT_OK) {
+		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;
+		dev_dbg(&priv->spi->dev,
+			"%s: post_bit_err_t2 %d\n", __func__, ret);
+	}
+
+	mutex_lock(priv->spi_mutex);
+	ret = cxd2880_read_block_err_t2(&priv->tnrdmd,
+					&block_err, &block_count);
+	mutex_unlock(priv->spi_mutex);
+	if (ret == CXD2880_RESULT_OK) {
+		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;
+		dev_dbg(&priv->spi->dev,
+			"%s: read_block_err_t2 %d\n", __func__, ret);
+	}
+
+	return 0;
+}
+
+static int cxd2880_get_frontend(struct dvb_frontend *fe,
+				struct dtv_frontend_properties *props)
+{
+	struct cxd2880_priv *priv = NULL;
+	int result = 0;
+
+	if ((!fe) || (!props)) {
+		pr_err("%s: invalid arg.", __func__);
+		return -EINVAL;
+	}
+
+	priv = (struct cxd2880_priv *)fe->demodulator_priv;
+
+	dev_dbg(&priv->spi->dev, "%s: system=%d\n", __func__,
+		fe->dtv_property_cache.delivery_system);
+	switch (fe->dtv_property_cache.delivery_system) {
+	case SYS_DVBT:
+		result = cxd2880_get_frontend_t(fe, props);
+		break;
+	case SYS_DVBT2:
+		result = cxd2880_get_frontend_t2(fe, props);
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+
+	return result;
+}
+
+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;
+
+struct dvb_frontend *cxd2880_attach(struct dvb_frontend *fe,
+				struct cxd2880_config *cfg)
+{
+	enum cxd2880_ret ret = CXD2880_RESULT_OK;
+	enum cxd2880_tnrdmd_chip_id chipid =
+					CXD2880_TNRDMD_CHIP_ID_UNKNOWN;
+	static struct cxd2880_priv *priv;
+	u8 data = 0;
+
+	if (!fe) {
+		pr_err("%s: invalid arg.\n", __func__);
+		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 != CXD2880_RESULT_OK) {
+		dev_err(&priv->spi->dev,
+			"%s: spi_device_initialize failed. %d\n",
+			__func__, ret);
+		kfree(priv);
+		return NULL;
+	}
+
+	ret = cxd2880_spi_device_create_spi(&priv->cxd2880_spi,
+					&priv->spi_device);
+	if (ret != CXD2880_RESULT_OK) {
+		dev_err(&priv->spi->dev,
+			"%s: spi_device_create_spi failed. %d\n",
+			__func__, ret);
+		kfree(priv);
+		return NULL;
+	}
+
+	ret = cxd2880_io_spi_create(&priv->regio, &priv->cxd2880_spi, 0);
+	if (ret != CXD2880_RESULT_OK) {
+		dev_err(&priv->spi->dev,
+			"%s: io_spi_create failed. %d\n", __func__, ret);
+		kfree(priv);
+		return NULL;
+	}
+	if (priv->regio.write_reg(&priv->regio, CXD2880_IO_TGT_SYS, 0x00, 0x00)
+		!= CXD2880_RESULT_OK) {
+		dev_err(&priv->spi->dev,
+			"%s: set bank to 0x00 failed.\n", __func__);
+		kfree(priv);
+		return NULL;
+	}
+	if (priv->regio.read_regs(&priv->regio,
+					CXD2880_IO_TGT_SYS, 0xFD, &data, 1)
+					!= CXD2880_RESULT_OK) {
+		dev_err(&priv->spi->dev,
+			"%s: read chip id failed.\n", __func__);
+		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)) {
+		dev_err(&priv->spi->dev,
+			"%s: chip id invalid.\n", __func__);
+		kfree(priv);
+		return NULL;
+	}
+
+	fe->demodulator_priv = priv;
+	dev_info(&priv->spi->dev,
+		"CXD2880 driver version: Ver %s\n",
+		CXD2880_TNRDMD_DRIVER_VERSION);
+
+	return fe;
+}
+EXPORT_SYMBOL(cxd2880_attach);
+
+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,
+};
+
+MODULE_DESCRIPTION(
+"Sony CXD2880 DVB-T2/T tuner + demodulator drvier");
+MODULE_AUTHOR("Sony Semiconductor Solutions Corporation");
+MODULE_LICENSE("GPL v2");