All of lore.kernel.org
 help / color / mirror / Atom feed
* Re: [media next v3.4] Add support for TBS-Tech ISDB-T Full Seg DTB08
       [not found] <BLU157-W6519D7CC9237EFB29FB24ED8230@phx.gbl>
@ 2012-04-26 18:35 ` Mauro Carvalho Chehab
  0 siblings, 0 replies; 3+ messages in thread
From: Mauro Carvalho Chehab @ 2012-04-26 18:35 UTC (permalink / raw)
  To: Manoel PN, Linux Media Mailing List

Em 21-04-2012 00:56, Manoel PN escreveu:
> 
>>> +static u8 mb86a20s_soft_reset[] = {
>>> + 0x70, 0xf0, 0x70, 0xff, 0x08, 0x01, 0x08, 0x00
>>> +};
>>
>> Huh? Why do you need to add mb86 stuff here? That sounds wrong.
>>
> 
> Need?  Don't need.
> 
> The device tbs_dtb08 does not work with the configurations that are in the
> mb86a20s module, but various suggestions submitted were rejected.

[resending message, as it were not c/c to the linux-media ML]

Yes, because you didn't just add there what it were needed for your driver.
You did several other changes that weren't needed.

That made very hard to discover what you really changed there.

I had to manually dig into each change you've proposed, at the string
initialization, in order to double check what was there, and manually
apply each change using the current struct. Anyway, I did it back in
January, and tested that the changes there didn't break for the existing
devices using mb86a20s:

commit ebe967492c681da781dbc0f7c0d6a1b5c1977d45
Author: Mauro Carvalho Chehab <mchehab@redhat.com>
Date:   Wed Jan 11 11:00:28 2012 -0200

    mb86a20s: Add a few more register settings at the init seq
    
    Some time ago, Manoel sent us a patch adding more stuff
    to the init sequence. However, his patch were also doing
    non-related stuff, by changing the init logic without
    any good reason. So, it was asked for him to submit a
    patch with just the data that has changed, in order to
    allow us to better analyze it.
    
    As he didn't what it was requested, I finally found some
    time to dig into his init sequence and add it here.
    
    Basically, new stuff is added there. There are a few changes:
    
    1) The removal of the extra (duplicated) logic that puts
       the chip into the serial mode;
    2) Some Viterbi VBER measurement init data was changed from
       0x00 to 0xff for layer A, to match what was done for
       layers B and C.
    
    None of those caused any regressions and both make sense
    on my eyes.
    
    The other parameters additions actually increased the
    tuning quality for some channels. Yet, some channels that
    were previously discovered with scan disappered, while
    others appeared instead. This were tested in Brasilia,
    with an external antena.
    
    At the overall, it is now a little better. So, better to
    add these, and then try to figure out a configuration that
    would get even better scanning results.
    
    Reported-by: Manoel Pinheiro <pinusdtv@hotmail.com>
    Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>

> The idea is to allow individual configuration of the mb86a20s registers.
> And these modifications here make exactly this.
> Besides adding/modifying functions that do not work on this device.
> 
> For example:
> 
> static struct mb86a20s_reg_subreg_val mb86a20s_regs_val[] = {
> ...
>   { 0x28, 0x20, 0x04, 0x33dfa9 },
> ...
> };
> 
> static struct mb86a20s_reg_subreg_config dtb08_a20s_config_regs[] = {
>     { 0x28, 0x20, 0x33dd00 },  /* modif reg 0x28 sub 0x20 to 0x33dfa9 */
>     { 0x3C, 0x00, 0x38 }       /* modif reg 0x3c to 0x38 */
> };
> 
> struct mb86a20s_state *state;
> ...
> state->config_size = ARRAY_SIZE(dtb08_a20s_config_regs);
> state->config_regs = dtb08_a20s_config_regs;
> 
> if (mb86a20s_init_regs(state) != 0)
>     return -ENODEV;
> ...
> 

Individual configuration for the registers is a very bad idea. No driver
does that, as it becomes a maintenance nightmare. 

What drivers do, instead, is to have a config struct with the options that
are different. In the case of mb8a20s, there are currently only two options:

struct mb86a20s_config {
	u8 demod_address;
	bool is_serial;
};

But other drivers, like DRX-K (with supports both DVB-C and DVB-T) have
much more parameters to configure:

struct drxk_config {
	u8	adr;
	bool	single_master;
	bool	no_i2c_bridge;
	bool	parallel_ts;
	bool	dynamic_clk;
	bool	enable_merr_cfg;

	bool	antenna_dvbt;
	u16	antenna_gpio;

	u8	mpeg_out_clk_strength;
	int	chunk_size;

	const char *microcode_name;
};

So, if two devices require to set different configurations, it is clear for
reviewers and other developers that may be working with the same chipset for
what those changes are.

>>> +MODULE_AUTHOR("Manoel Pinheiro <pinusdtv@hotmail.com>");
>>> +MODULE_DESCRIPTION("Driver for TBS-Tech ISDB-T USB2.0 Receiver (DTB08 Full Seg)");
>>> +MODULE_LICENSE("GPL");
>>
> 
> Well I will send the last modification and stop here because no one else besides me have this device.

There aren't many Brazilian people reading this ML. That may explain why there's not much
comments about that. 

Regards,
Mauro

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [media next v3.4] Add support for TBS-Tech ISDB-T Full Seg DTB08
  2012-03-29 23:24 Manoel Pinheiro
@ 2012-04-11  0:28 ` Mauro Carvalho Chehab
  0 siblings, 0 replies; 3+ messages in thread
From: Mauro Carvalho Chehab @ 2012-04-11  0:28 UTC (permalink / raw)
  To: Manoel Pinheiro; +Cc: linux-media, lgspn

Em 29-03-2012 20:24, Manoel Pinheiro escreveu:
> Driver for TBS-Tech ISDB-T USB2.0 Receiver (DTB08 Full Seg).
> 
> The device used as a reference is described in the link
> http://linuxtv.org/wiki/index.php/JH_Full_HD_Digital_TV_Receiver

Please, always check your patches with checkpatch.pl. While it may produce some
false alarms, in general, it shows CodingStile and/or a few trivial errors:


WARNING: please write a paragraph that describes the config symbol fully
#33: FILE: drivers/media/dvb/dvb-usb/Kconfig:426:
+config DVB_USB_TBSDTB08

WARNING: please write a paragraph that describes the config symbol fully
#41: FILE: drivers/media/dvb/dvb-usb/Kconfig:434:
 config DVB_USB_AF9035

ERROR: do not initialise statics to 0 or NULL
#87: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:29:
+static int debug = 0;

ERROR: space required before the open parenthesis '('
#94: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:36:
+} while(0)

ERROR: do not initialise statics to 0 or NULL
#117: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:59:
+static int fw_ok = 0;

WARNING: line over 80 characters
#295: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:237:
+	return tbs_dtb08_generic_read_addr(udev, TBS_DTB08_GET_IR_CODE, 0, data, len);

ERROR: trailing statements should be on next line
#305: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:247:
+		if (ret >= 0 && val == 0) return 0;

WARNING: msleep < 20ms can sleep for up to 20ms; see Documentation/timers/timers-howto.txt
+		msleep(1);
ERROR: that open brace { should be on the previous line
#325: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:267:
+	if (ret == 0)
+	{

WARNING: line over 80 characters
#335: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:277:
+				ret = tbs_dtb08_generic_read(udev, 0x91, data, len);

WARNING: line over 80 characters
#409: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:351:
+static int tbs_dtb08_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[],

WARNING: line over 80 characters
#426: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:368:
+		if ((msg[0].addr == DEMOD_I2C_ADDR || msg[0].addr == TUNER_I2C_ADDR) &&

WARNING: line over 80 characters
#428: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:370:
+			msg[1].flags == I2C_M_RD && msg[0].len == 1 && msg[1].len > 0) {

ERROR: space required after that ',' (ctx:VxV)
#430: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:372:
+					msg[0].buf[0],msg[1].buf, msg[1].len);
 					             ^

ERROR: else should follow close brace '}'
#432: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:374:
+		}
+		else {

WARNING: quoted string split across lines
#434: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:376:
+			err("%s: num==2, msg[0].addr==%02x, msg[0].flags==%d, "
+			    "msg[0].len==%d, msg[1].addr==%02x, msg[1].flags==%d, "

WARNING: line over 80 characters
#435: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:377:
+			    "not suported!", __func__, msg[0].addr, msg[0].flags,

WARNING: line over 80 characters
#562: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:504:
+	return tbs_dtb08_i2c_write(state->udev, state->demod_addr, reg, &val, 1);

WARNING: line over 80 characters
#565: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:507:
+inline int mb86a20s_read_subreg(struct mb86a20s_state *state, u8 reg, u8 subreg, u8 *val)

WARNING: line over 80 characters
#570: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:512:
+	if ((ret = tbs_dtb08_i2c_write(state->udev, state->demod_addr, reg, &subreg, 1)) < 0)

ERROR: do not use assignment in if condition
#570: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:512:
+	if ((ret = tbs_dtb08_i2c_write(state->udev, state->demod_addr, reg, &subreg, 1)) < 0)

WARNING: line over 80 characters
#572: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:514:
+	return tbs_dtb08_i2c_read(state->udev, state->demod_addr, reg+1, val, 1);

ERROR: do not use assignment in if condition
#583: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:525:
+	if (!state || !(config_regs = state->config_regs))

ERROR: else should follow close brace '}'
#639: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:581:
+			}
+			else if (buf[0] == 0x50) {

ERROR: else should follow close brace '}'
#651: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:593:
+			}
+			else {

ERROR: that open brace { should be on the previous line
#759: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:701:
+	for (i = 0; i < sizeof(mb86a20s_soft_reset); i += 2)
+	{

ERROR: trailing statements should be on next line
#769: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:711:
+		if (ret == 0 && val >= 2) break;

ERROR: trailing statements should be on next line
#803: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:745:
+		if (val >= 2) break;

WARNING: msleep < 20ms can sleep for up to 20ms; see Documentation/timers/timers-howto.txt
+		msleep(10);
ERROR: space required before the open parenthesis '('
#829: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:771:
+	switch(tvp->cmd) {

ERROR: that open brace { should be on the previous line
#849: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:791:
+	for (i = 0; i < 10; i++)
+	{

ERROR: trailing statements should be on next line
#853: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:795:
+		if (val < 2) goto next;

WARNING: msleep < 20ms can sleep for up to 20ms; see Documentation/timers/timers-howto.txt
+		msleep(10);
ERROR: code indent should use tabs where possible
#887: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:829:
+ ^I^Ireturn 0;$

WARNING: please, no space before tabs
#887: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:829:
+ ^I^Ireturn 0;$

WARNING: please, no spaces at the start of a line
#887: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:829:
+ ^I^Ireturn 0;$

ERROR: trailing statements should be on next line
#895: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:837:
+		if (n < 0 || val < 2) goto next;

ERROR: trailing statements should be on next line
#896: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:838:
+		if (mb86a20s_read_reg(state, 0x45, &val) < 0) goto next;

ERROR: trailing statements should be on next line
#899: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:841:
+			if (mb86a20s_read_reg(state, 0x46, &val) < 0) goto next;

ERROR: trailing statements should be on next line
#901: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:843:
+			if (mb86a20s_read_reg(state, 0x47, &val) < 0) goto next;

WARNING: msleep < 20ms can sleep for up to 20ms; see Documentation/timers/timers-howto.txt
+			msleep(5);
ERROR: trailing statements should be on next line
#910: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:852:
+			if (cnr > 0x4cc0) cnr = 0x4cc0;

WARNING: msleep < 20ms can sleep for up to 20ms; see Documentation/timers/timers-howto.txt
+		msleep(10);
ERROR: trailing statements should be on next line
#998: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:940:
+	if (!fe) return -ENODEV;

WARNING: line over 80 characters
#1024: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:966:
+static int tbs_dtb08_download_firmware(struct usb_device *dev, const struct firmware *fw)

ERROR: code indent should use tabs where possible
#1030: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:972:
+ ^Iif (dev == NULL || fw == NULL || fw->size <= 0) {$

WARNING: please, no space before tabs
#1030: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:972:
+ ^Iif (dev == NULL || fw == NULL || fw->size <= 0) {$

WARNING: please, no spaces at the start of a line
#1030: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:972:
+ ^Iif (dev == NULL || fw == NULL || fw->size <= 0) {$

ERROR: trailing statements should be on next line
#1058: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:1000:
+			if (fx2_renum) continue;

WARNING: line over 80 characters
#1061: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:1003:
+		if (tbs_dtb08_generic_write_addr(dev, 0xa0, n, buf, count) != count) {

WARNING: quoted string split across lines
#1063: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:1005:
+			err("%s: addr=%04x: error while "
+				"transferring firmware", __func__, n);

ERROR: space required after that close brace '}'
#1150: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:1092:
+		}}}

WARNING: suspect code indent for conditional statements (24, 26)
#1193: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:1135:
+			if (adap && adap->fe_adap[0].fe) {
+			  ret++;

WARNING: braces {} are not necessary for single statement blocks
#1193: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:1135:
+			if (adap && adap->fe_adap[0].fe) {
+			  ret++;
+			}

ERROR: else should follow close brace '}'
#1204: FILE: drivers/media/dvb/dvb-usb/tbs-dtb08.c:1146:
+	}
+	else

total: 28 errors, 27 warnings, 1190 lines checked

NOTE: whitespace errors detected, you may wish to use scripts/cleanpatch or
      scripts/cleanfile


> 
> 
> Signed-off-by: Manoel Pinheiro <pinusdtv@hotmail.com>
> ---
>  drivers/media/dvb/dvb-usb/Kconfig     |    8 +
>  drivers/media/dvb/dvb-usb/Makefile    |    3 +
>  drivers/media/dvb/dvb-usb/tbs-dtb08.c | 1167 +++++++++++++++++++++++++++++++++
>  3 files changed, 1178 insertions(+)
>  create mode 100644 drivers/media/dvb/dvb-usb/tbs-dtb08.c
> 
> diff --git a/drivers/media/dvb/dvb-usb/Kconfig b/drivers/media/dvb/dvb-usb/Kconfig
> index 63bf456..63a794d 100644
> --- a/drivers/media/dvb/dvb-usb/Kconfig
> +++ b/drivers/media/dvb/dvb-usb/Kconfig
> @@ -422,3 +422,11 @@ config DVB_USB_RTL28XXU
>  	select MEDIA_TUNER_MXL5005S if !MEDIA_TUNER_CUSTOMISE
>  	help
>  	  Say Y here to support the Realtek RTL28xxU DVB USB receiver.
> +
> +config DVB_USB_TBSDTB08
> +	tristate "TBS-Tech ISDB-T Full Seg DTB08 USB2.0 support"
> +	depends on DVB_USB
> +	select DVB_MB86A20S
> +	select MEDIA_TUNER_TDA18271 if !MEDIA_TUNER_CUSTOMISE
> +	help
> +	  Say Y here to support the TBS-Tech Full Seg DTB08 ISDB-T USB2.0 receivers
> diff --git a/drivers/media/dvb/dvb-usb/Makefile b/drivers/media/dvb/dvb-usb/Makefile
> index b76acb5..051756c 100644
> --- a/drivers/media/dvb/dvb-usb/Makefile
> +++ b/drivers/media/dvb/dvb-usb/Makefile
> @@ -110,6 +110,9 @@ obj-$(CONFIG_DVB_USB_MXL111SF) += mxl111sf-tuner.o
>  dvb-usb-rtl28xxu-objs = rtl28xxu.o
>  obj-$(CONFIG_DVB_USB_RTL28XXU) += dvb-usb-rtl28xxu.o
>  
> +dvb-usb-tbsdtb08-objs = tbs-dtb08.o
> +obj-$(CONFIG_DVB_USB_TBSDTB08) += dvb-usb-tbsdtb08.o
> +
>  ccflags-y += -I$(srctree)/drivers/media/dvb/dvb-core
>  ccflags-y += -I$(srctree)/drivers/media/dvb/frontends/
>  # due to tuner-xc3028
> diff --git a/drivers/media/dvb/dvb-usb/tbs-dtb08.c b/drivers/media/dvb/dvb-usb/tbs-dtb08.c
> new file mode 100644
> index 0000000..f909312
> --- /dev/null
> +++ b/drivers/media/dvb/dvb-usb/tbs-dtb08.c
> @@ -0,0 +1,1167 @@
> +/*
> + *   TBS-Tech ISDB-T Full Seg DTB08 device driver
> + *
> + *   Copyright (C) 2010-2012 Manoel Pinheiro <pinusdtv@hotmail.com>
> + *
> + *   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; either version 2 of the License, or
> + *   (at your option) any later version.
> + *
> + *   This program is distributed in the hope that it will be useful,
> + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *   GNU General Public License for more details.
> + *
> + *   You should have received a copy of the GNU General Public License
> + *   along with this program; if not, write to the Free Software
> + *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#include <linux/module.h>
> +
> +#define DVB_USB_LOG_PREFIX "tbs_dtb08"
> +
> +#include "dvb-usb.h"
> +#include "tda18271.h"
> +#include "mb86a20s.h"
> +
> +static int debug = 0;
> +module_param(debug, int, 0644);
> +MODULE_PARM_DESC(debug, "Turn on/off module debugging (default:0).");
> +
> +#define dbg_info(format, args...)	do {	\
> +	if (debug)				\
> +		info(format, ## args);		\
> +} while(0)
> +
> +#define	DEMOD_I2C_ADDR	0x20
> +#define	TUNER_I2C_ADDR	0xc0
> +
> +#ifndef USB_PID_TBS_DTB08
> +#define USB_PID_TBS_DTB08	0xdb08
> +#endif
> +
> +#define USB_VID_TBS_734C	0x734c
> +
> +#define TBS_DTB08_GET_IR_CODE	0xb8
> +#define TBS_DTB08_LED_CONTROL	5
> +
> +#define FX2_IE_EX0	7
> +#define FX2_EX0_ENABLE	1
> +#define FX2_EX0_DISABLE	0
> +#define FX2_I2CTL	6
> +#define I2CTL_100Khz	0
> +#define I2CTL_400Khz	1
> +
> +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
> +static DEFINE_MUTEX(tbs_dtb08_usb_mutex);
> +static int fw_ok = 0;
> +
> +struct mb86a20s_reg_subreg_config {
> +	u8 reg;
> +	u8 subreg;
> +	u32 val;
> +};
> +
> +struct mb86a20s_state {
> +	struct usb_device *udev;
> +	int demod_addr;
> +	u32 current_frequency;
> +	fe_status_t status;
> +	u16 snr;
> +	u16 strength;
> +	unsigned long next_snr_check;
> +	unsigned long next_strength_check;
> +	unsigned long next_set_frontend_check;
> +	unsigned long next_status_check;
> +	int config_size;
> +	struct mb86a20s_reg_subreg_config *config_regs;
> +	bool need_init;
> +	bool tuner_ctrl;
> +};
> +
> +struct mb86a20s_reg_subreg_val {
> +	u8 reg;
> +	u8 subreg;
> +	u8 type;	/* 0=8 bits wo/sub, 1=8 bits w/sub
> +			 * 2=16 bits wo/sub, 3=16 bits w/sub, 4=24 bits */
> +	u32 val;
> +};
> +
> +static struct mb86a20s_reg_subreg_val mb86a20s_regs_val[] = {
> +	{ 0x70, 0x00, 0x00, 0x0f },
> +	{ 0x70, 0x00, 0x00, 0xff },
> +	{ 0x08, 0x00, 0x00, 0x01 },
> +	{ 0x09, 0x00, 0x00, 0x3e },
> +	{ 0x50, 0xd1, 0x01, 0x22 },
> +	{ 0x39, 0x00, 0x00, 0x01 },
> +	{ 0x71, 0x00, 0x00, 0x00 },
> +	{ 0x28, 0x2a, 0x04, 0xff80 },
> +	{ 0x28, 0x20, 0x04, 0x33dfa9 },
> +	{ 0x28, 0x22, 0x04, 0x1ff0 },
> +	{ 0x3b, 0x00, 0x00, 0x21 },
> +	{ 0x3c, 0x00, 0x00, 0x3a },
> +	{ 0x01, 0x00, 0x00, 0x0d },
> +	{ 0x04, 0x08, 0x01, 0x05 },
> +	{ 0x04, 0x0e, 0x03, 0x0014 },
> +	{ 0x04, 0x0b, 0x01, 0x8c },
> +	{ 0x04, 0x00, 0x03, 0x0007 },
> +	{ 0x04, 0x02, 0x03, 0x0fa0 },
> +	{ 0x04, 0x09, 0x01, 0x00 },
> +	{ 0x04, 0x0a, 0x01, 0xff },
> +	{ 0x04, 0x27, 0x01, 0x64 },
> +	{ 0x04, 0x28, 0x01, 0x00 },
> +	{ 0x04, 0x1e, 0x01, 0xff },
> +	{ 0x04, 0x29, 0x01, 0x0a },
> +	{ 0x04, 0x32, 0x01, 0x0a },
> +	{ 0x04, 0x14, 0x01, 0x02 },
> +	{ 0x04, 0x04, 0x03, 0x0022 },
> +	{ 0x04, 0x06, 0x03, 0x0ed8 },
> +	{ 0x04, 0x12, 0x01, 0x00 },
> +	{ 0x04, 0x13, 0x01, 0xff },
> +	{ 0x04, 0x15, 0x01, 0x4e },
> +	{ 0x04, 0x16, 0x01, 0x20 },
> +	{ 0x52, 0x00, 0x00, 0x01 },
> +	{ 0x50, 0xa7, 0x04, 0xffff },
> +	{ 0x50, 0xaa, 0x04, 0xffff },
> +	{ 0x50, 0xad, 0x04, 0xffff },
> +	{ 0x5e, 0x00, 0x00, 0x07 },
> +	{ 0x50, 0xdc, 0x03, 0x01f4 },
> +	{ 0x50, 0xde, 0x03, 0x01f4 },
> +	{ 0x50, 0xe0, 0x03, 0x01f4 },
> +	{ 0x50, 0xb0, 0x01, 0x07 },
> +	{ 0x50, 0xb2, 0x03, 0xffff },
> +	{ 0x50, 0xb4, 0x03, 0xffff },
> +	{ 0x50, 0xb6, 0x03, 0xffff },
> +	{ 0x50, 0x50, 0x01, 0x02 },
> +	{ 0x50, 0x51, 0x01, 0x04 },
> +	{ 0x45, 0x00, 0x00, 0x04 },
> +	{ 0x48, 0x00, 0x00, 0x04 },
> +	{ 0x50, 0xd5, 0x01, 0x01 },
> +	{ 0x50, 0xd6, 0x01, 0x1f },
> +	{ 0x50, 0xd2, 0x01, 0x03 },
> +	{ 0x50, 0xd7, 0x01, 0x3f },
> +	{ 0x28, 0x74, 0x04, 0x0040 },
> +	{ 0x28, 0x46, 0x04, 0x2c0c },
> +	{ 0x04, 0x40, 0x01, 0x01 },
> +	{ 0x28, 0x00, 0x01, 0x10 },
> +	{ 0x28, 0x05, 0x01, 0x02 },
> +	{ 0x1c, 0x00, 0x00, 0x01 },
> +	{ 0x28, 0x06, 0x04, 0x0003 },
> +	{ 0x28, 0x07, 0x04, 0x000d },
> +	{ 0x28, 0x08, 0x04, 0x0002 },
> +	{ 0x28, 0x09, 0x04, 0x0001 },
> +	{ 0x28, 0x0a, 0x04, 0x0021 },
> +	{ 0x28, 0x0b, 0x04, 0x0029 },
> +	{ 0x28, 0x0c, 0x04, 0x0016 },
> +	{ 0x28, 0x0d, 0x04, 0x0031 },
> +	{ 0x28, 0x0e, 0x04, 0x000e },
> +	{ 0x28, 0x0f, 0x04, 0x004e },
> +	{ 0x28, 0x10, 0x04, 0x0046 },
> +	{ 0x28, 0x11, 0x04, 0x000f },
> +	{ 0x28, 0x12, 0x04, 0x0056 },
> +	{ 0x28, 0x13, 0x04, 0x0035 },
> +	{ 0x28, 0x14, 0x04, 0x01be },
> +	{ 0x28, 0x15, 0x04, 0x0184 },
> +	{ 0x28, 0x16, 0x04, 0x03ee },
> +	{ 0x28, 0x17, 0x04, 0x0098 },
> +	{ 0x28, 0x18, 0x04, 0x009f },
> +	{ 0x28, 0x19, 0x04, 0x07b2 },
> +	{ 0x28, 0x1a, 0x04, 0x06c2 },
> +	{ 0x28, 0x1b, 0x04, 0x074a },
> +	{ 0x28, 0x1c, 0x04, 0x01bc },
> +	{ 0x28, 0x1d, 0x04, 0x04ba },
> +	{ 0x28, 0x1e, 0x04, 0x0614 },
> +	{ 0x50, 0x1e, 0x01, 0x5d },
> +	{ 0x50, 0x22, 0x01, 0x00 },
> +	{ 0x50, 0x23, 0x01, 0xc8 },
> +	{ 0x50, 0x24, 0x01, 0x00 },
> +	{ 0x50, 0x25, 0x01, 0xf0 },
> +	{ 0x50, 0x26, 0x01, 0x00 },
> +	{ 0x50, 0x27, 0x01, 0xc3 },
> +	{ 0x50, 0x39, 0x01, 0x02 },
> +	{ 0x28, 0x6a, 0x04, 0x0000 }
> +};
> +
> +static u8 mb86a20s_soft_reset[] = {
> +	0x70, 0xf0, 0x70, 0xff, 0x08, 0x01, 0x08, 0x00
> +};

Huh? Why do you need to add mb86 stuff here? That sounds wrong.

> +
> +static int tbs_dtb08_generic_read_addr(struct usb_device *udev, u8 req,
> +				       u16 addr, u8 *data, u16 len)
> +{
> +	int ret;
> +
> +	ret = usb_control_msg(udev,
> +			      usb_rcvctrlpipe(udev, 0),
> +			      req,
> +			      USB_TYPE_VENDOR | USB_DIR_IN,
> +			      addr, 0, data, len, 2000);
> +	if (ret < 0)
> +		err("%s: ret=%d", __func__, ret);
> +
> +	return ret;
> +}
> +
> +inline int tbs_dtb08_generic_read(struct usb_device *udev,
> +				  u8 req, u8 *data, u16 len)
> +{
> +	return tbs_dtb08_generic_read_addr(udev, req, 0, data, len);
> +}
> +
> +static int tbs_dtb08_generic_write_addr(struct usb_device *udev, u8 req,
> +					u16 addr, u8 *data, u16 len)
> +{
> +	int ret;
> +
> +	ret = usb_control_msg(udev,
> +			      usb_sndctrlpipe(udev, 0),
> +			      req,
> +			      USB_TYPE_VENDOR | USB_DIR_OUT,
> +			      addr, 0, data, len, 2000);
> +	if (ret < 0)
> +		err("%s: ret=%d", __func__, ret);
> +
> +	return ret;
> +}
> +
> +inline int tbs_dtb08_generic_write(struct usb_device *udev,
> +				   u8 req, u8 *data, u16 len)
> +{
> +	return tbs_dtb08_generic_write_addr(udev, req, 0, data, len);
> +}
> +
> +inline int tbs_dtb08_ir_code_read(struct usb_device *udev, u8 *data, u16 len)
> +{
> +	return tbs_dtb08_generic_read_addr(udev, TBS_DTB08_GET_IR_CODE, 0, data, len);
> +}
> +
> +static int tbs_dtb08_i2c_busy(struct usb_device *udev)
> +{
> +	u8 val;
> +	int i, ret;
> +
> +	for (i = 0; i < 10; i++) {
> +		ret = tbs_dtb08_generic_read(udev, 0x81, &val, 1);
> +		if (ret >= 0 && val == 0) return 0;
> +		msleep(1);
> +	}
> +
> +	return -1;
> +}
> +
> +static int tbs_dtb08_i2c_read(struct usb_device *udev, u8 addr,
> +			      u8 reg, u8 *data, u8 len)
> +{
> +	int ret;
> +	u8 obuf[3];
> +
> +	if (len < 1) {
> +		err("%s: len less than 1 bytes. Makes no sense.", __func__);
> +		return -EINVAL;
> +	}
> +
> +	mutex_lock(&tbs_dtb08_usb_mutex);
> +	ret = tbs_dtb08_i2c_busy(udev);
> +	if (ret == 0)
> +	{
> +		obuf[0] = len;
> +		obuf[1] = addr;
> +		obuf[2] = reg;
> +
> +		ret = tbs_dtb08_generic_write(udev, 0x90, &obuf[0], 3);
> +		if (ret >= 0) {
> +			ret = tbs_dtb08_i2c_busy(udev);
> +			if (ret == 0)
> +				ret = tbs_dtb08_generic_read(udev, 0x91, data, len);
> +		}
> +	}
> +	mutex_unlock(&tbs_dtb08_usb_mutex);
> +
> +	return ret;
> +}
> +
> +static int tbs_dtb08_i2c_write(struct usb_device *udev, u8 addr,
> +			       u8 reg, u8 *data, u8 len)
> +{
> +	int i, ret;
> +	u8 *buf;
> +
> +	if (len < 1) {
> +		err("%s: len less than 1 bytes. Makes no sense.", __func__);
> +		return -EINVAL;
> +	}
> +	if (len > 61) {
> +		err("%s: len more than 61 bytes. Not supported.", __func__);
> +		return -EINVAL;

Those are not the better return codes for I2C. 
See Documentation/i2c/fault-codes.

> +	}
> +
> +	buf = kmalloc(64 , GFP_KERNEL);

Why are you allocating a size bigger than len + 3?

> +	if (!buf)
> +		return -ENOMEM;
> +
> +	mutex_lock(&tbs_dtb08_usb_mutex);
> +
> +	buf[0] = len + 2;
> +	buf[1] = addr;
> +	buf[2] = reg;
> +
> +	for (i = 0; i < len; i++)
> +		buf[i+3] = data[i];
> +
> +	ret = tbs_dtb08_i2c_busy(udev);
> +	if (ret < 0)
> +		goto ret_err;
> +
> +	ret = tbs_dtb08_generic_write(udev, 0x80, buf, len + 3);
> +
> +ret_err:
> +	kfree(buf);
> +	mutex_unlock(&tbs_dtb08_usb_mutex);
> +	return ret;
> +}
> +
> +static int tbs_dtb08_send_cmd_8a(struct usb_device *udev, u8 val1, u8 val2)
> +{
> +	int ret;
> +	u8 obuf[2] = { val1, val2 };
> +
> +	ret = tbs_dtb08_generic_write(udev, 0x8a, &obuf[0], 2);
> +
> +	return (ret < 0) ? ret : 0;
> +}
> +
> +inline int tbs_dtb08_fx2_ie_ex0(struct usb_device *udev, u8 enable)

You don't need to use inline here. gcc will do it automatically. The same is
valid for the other simila functions.

> +{
> +	return tbs_dtb08_send_cmd_8a(udev, FX2_IE_EX0, enable ? 1 : 0);
> +}
> +
> +inline int tbs_dtb08_led_control(struct usb_device *udev, int onoff)
> +{
> +	return tbs_dtb08_send_cmd_8a(udev, TBS_DTB08_LED_CONTROL,
> +				     onoff ? 1 : 0);
> +}
> +
> +inline int tbs_dtb08_fx2_i2ctl(struct usb_device *udev, u8 i2cfreq)
> +{
> +	return tbs_dtb08_send_cmd_8a(udev, FX2_I2CTL, i2cfreq);
> +}
> +
> +static int tbs_dtb08_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[],
> +		int num)
> +{
> +	struct dvb_usb_device *dev = i2c_get_adapdata(adap);
> +	int ii, ret = 0;
> +
> +	dbg_info("%s: num=%02x, msg[0].addr=%02x, jiffies=%ld",
> +		 __func__, num, msg[0].addr, jiffies);
> +
> +	if (!dev || !dev->udev)
> +		return -ENODEV;
> +
> +	if (mutex_lock_interruptible(&dev->i2c_mutex) < 0)
> +		return -EAGAIN;
> +
> +	switch (num) {
> +	case 2:
> +		if ((msg[0].addr == DEMOD_I2C_ADDR || msg[0].addr == TUNER_I2C_ADDR) &&
> +			msg[0].addr == msg[1].addr &&  msg[0].flags == 0 &&
> +			msg[1].flags == I2C_M_RD && msg[0].len == 1 && msg[1].len > 0) {
> +			ret = tbs_dtb08_i2c_read(dev->udev, msg[0].addr,
> +					msg[0].buf[0],msg[1].buf, msg[1].len);
> +		}
> +		else {
> +			err("%s: num==2, msg[0].addr==%02x, msg[0].flags==%d, "
> +			    "msg[0].len==%d, msg[1].addr==%02x, msg[1].flags==%d, "
> +			    "not suported!", __func__, msg[0].addr, msg[0].flags,
> +				msg[0].len, msg[1].addr, msg[1].flags);
> +			ret = -EINVAL;
> +		}
> +		break;
> +	case 1:
> +		switch (msg[0].addr) {
> +		case DEMOD_I2C_ADDR:
> +		case TUNER_I2C_ADDR:

Why do you need to check it here? Is there are any special I2C messages
for other I2C modules?

> +			ii = msg[0].len - 1;
> +			if (ii < 0 || ii > 63) {
> +				ret = -EINVAL;
> +				break;
> +			}
> +
> +			if (msg[0].flags == 0) {
> +				ret = tbs_dtb08_i2c_write(
> +					dev->udev, msg[0].addr,
> +					msg[0].buf[0],
> +					&msg[0].buf[1], ii);
> +			} else {
> +				ret = tbs_dtb08_i2c_read(
> +					dev->udev, msg[0].addr,
> +					msg[0].buf[0],
> +					msg[0].buf, msg[0].len);
> +			}
> +			break;
> +		default:
> +			err("%s: num==1, addr==%02x, not suported!",
> +			    __func__, msg[0].addr);
> +			ret = -EINVAL;
> +		}
> +		break;
> +	default:
> +		err("%s:num == %d, not suported!", __func__, num);
> +		ret = -EINVAL;
> +		break;
> +	}
> +
> +	mutex_unlock(&dev->i2c_mutex);
> +
> +	return (ret < 0) ? ret : num;
> +}
> +
> +static struct rc_map_table tbs_dtb08_rc_map_table[] = {
> +	{ 0x0010, KEY_POWER },		/* power */
> +	{ 0x0006, KEY_MUTE },		/* mute */
> +	{ 0x004c, KEY_1 },
> +	{ 0x0004, KEY_2 },
> +	{ 0x0000, KEY_3 },
> +	{ 0x001e, KEY_4 },
> +	{ 0x000e, KEY_5 },
> +	{ 0x001a, KEY_6 },
> +	{ 0x0014, KEY_7 },
> +	{ 0x000f, KEY_8 },
> +	{ 0x000c, KEY_9 },
> +	{ 0x001c, KEY_0 },
> +	{ 0x0040, KEY_CHANNELUP },	/* ch+ */
> +	{ 0x000a, KEY_CHANNELDOWN },	/* ch- */
> +	{ 0x0019, KEY_VOLUMEUP },	/* vol+ */
> +	{ 0x0017, KEY_VOLUMEDOWN },	/* vol- */
> +	{ 0x0011, KEY_OK },		/* ok */
> +	{ 0x0009, KEY_SAVE },		/* scrn shot */
> +	{ 0x001f, KEY_UP },
> +	{ 0x001b, KEY_LEFT },
> +	{ 0x0015, KEY_RIGHT },
> +	{ 0x0016, KEY_DOWN },
> +	{ 0x004d, KEY_FAVORITES },	/* fav */
> +	{ 0x0001, KEY_ZOOM },
> +	{ 0x0003, KEY_EPG },
> +	{ 0x001d, KEY_PLAY },
> +	{ 0x000d, KEY_STOP },
> +	{ 0x0012, KEY_LAST  },		/* recall */
> +};

Please put the IR keymap table under drivers/media/rc/keymaps/.

> +
> +static int tbs_dtb08_rc_query(struct dvb_usb_device *d, u32 *event, int *state)
> +{
> +	u8 *buf;
> +	int i;
> +	struct rc_map_table *rc_map = d->props.rc.legacy.rc_map_table;
> +
> +	*state = REMOTE_NO_KEY_PRESSED;
> +	*event = 0;
> +
> +	if (!fw_ok) {
> +		dbg_info("%s: fw_ok == 0", __func__);
> +		return 0;
> +	}
> +
> +	buf = kzalloc(5, GFP_KERNEL);
> +	if (!buf)
> +		return -ENOMEM;
> +
> +	if (mutex_lock_interruptible(&tbs_dtb08_usb_mutex))
> +		goto free_buf_leave;
> +
> +	tbs_dtb08_ir_code_read(d->udev, buf, 5);
> +	mutex_unlock(&tbs_dtb08_usb_mutex);
> +
> +	dbg_info("%s: %02x %02x %02x %02x %02x",
> +		 __func__, buf[0], buf[1], buf[2], buf[3], buf[4]);
> +
> +	if (buf[1] != (u8)~buf[2] || buf[3] != (u8)~buf[4])
> +		goto free_buf_leave;
> +
> +	for (i = 0; i < d->props.rc.legacy.rc_map_size; i++) {
> +		u16 scan = (u16)buf[1] << 8 | buf[3];
> +		if (rc_map[i].scancode == scan) {
> +			*event = rc_map[i].keycode;
> +			*state = REMOTE_KEY_PRESSED;

Please, instead use the RC core. See az6007 for an example on how to implement it
if you have some doubts. It is also based on Cypress FX2.

> +			break;
> +		}
> +	}
> +
> +free_buf_leave:
> +	kfree(buf);
> +	return 0;
> +}
> +
> +inline int mb86a20s_read_reg(struct mb86a20s_state *state, u8 reg, u8 *val)
> +{
> +	*val = 0;
> +	return tbs_dtb08_i2c_read(state->udev, state->demod_addr, reg, val, 1);
> +}
> +
> +inline int mb86a20s_write_reg(struct mb86a20s_state *state, u8 reg, u8 val)
> +{
> +	return tbs_dtb08_i2c_write(state->udev, state->demod_addr, reg, &val, 1);
> +}
> +
> +inline int mb86a20s_read_subreg(struct mb86a20s_state *state, u8 reg, u8 subreg, u8 *val)
> +{
> +	int ret;
> +
> +	*val = 0;
> +	if ((ret = tbs_dtb08_i2c_write(state->udev, state->demod_addr, reg, &subreg, 1)) < 0)
> +		return ret;
> +	return tbs_dtb08_i2c_read(state->udev, state->demod_addr, reg+1, val, 1);
> +}

Again, frontend stuff should be at mb86a20s module, and not here.

> +
> +static u32 get_config_reg_val(struct mb86a20s_state *state,
> +			      struct mb86a20s_reg_subreg_val *reg_val)
> +{
> +	struct mb86a20s_reg_subreg_config *config_regs;
> +	int i;
> +
> +	if (!reg_val)
> +		return 0;
> +	if (!state || !(config_regs = state->config_regs))
> +		return reg_val->val;
> +	for (i = 0; i < state->config_size; i++) {
> +		if (config_regs->reg == reg_val->reg &&
> +		    config_regs->subreg == reg_val->subreg)
> +			return config_regs->val;
> +		config_regs++;
> +	}
> +	return reg_val->val;
> +}
> +
> +static int mb86a20s_init_regs(struct mb86a20s_state *state)
> +{
> +	u8 *buf;
> +	u32 val;
> +	int i, i2, count, ret = 0;
> +
> +	buf = kmalloc(12 , GFP_KERNEL);
> +	if (!buf)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < ARRAY_SIZE(mb86a20s_regs_val); i++) {
> +		struct mb86a20s_reg_subreg_val *reg_val = &mb86a20s_regs_val[i];
> +		val = get_config_reg_val(state, reg_val);
> +		buf[0] = reg_val->reg;
> +		count = 1;
> +		switch (reg_val->type) {
> +		case 1:
> +			buf[count++] = reg_val->subreg;
> +			if (buf[0] == 0x28)
> +				buf[count++] = 0x2b;
> +			else
> +				buf[count++] = buf[0] + 1;
> +			break;
> +		case 2:
> +			buf[count++] = (u8)(val >> 0x08);
> +			buf[count++] = buf[0] + 1;
> +			buf[count++] = (u8)val;
> +			break;
> +		case 3:
> +			buf[count++] = reg_val->subreg;
> +			buf[count++] = buf[0] + 1;
> +			buf[count++] = (u8)(val >> 0x08);
> +			buf[count++] = buf[0];
> +			buf[count++] = reg_val->subreg + 1;
> +			buf[count++] = buf[0] + 1;
> +			break;
> +		case 4:
> +			if (buf[0] == 0x28) {
> +				buf[count++] = reg_val->subreg;
> +				buf[count++] = 0x29;
> +				buf[count++] = (u8)(val >> 0x10);
> +				buf[count++] = 0x2a;
> +				buf[count++] = (u8)(val >> 0x08);
> +				buf[count++] = 0x2b;
> +			}
> +			else if (buf[0] == 0x50) {
> +				buf[count++] = reg_val->subreg;
> +				buf[count++] = 0x51;
> +				buf[count++] = (u8)(val >> 0x10);
> +				buf[count++] = 0x50;
> +				buf[count++] = reg_val->subreg + 1;
> +				buf[count++] = 0x51;
> +				buf[count++] = (u8)(val >> 0x08);
> +				buf[count++] = 0x50;
> +				buf[count++] = reg_val->subreg + 2;
> +				buf[count++] = 0x51;
> +			}
> +			else {
> +				ret = -1;
> +				goto ret_err;
> +			}
> +			break;
> +		}
> +		buf[count++] = (u8)val;
> +		i2 = 0;
> +		while (i2 < count) {
> +			ret = mb86a20s_write_reg(state, buf[i2], buf[i2 + 1]);
> +			if (ret < 0)
> +				goto ret_err;
> +			i2 += 2;
> +		}
> +	}
> +	state->need_init = false;
> +	kfree(buf);
> +	return 0;
> +
> +ret_err:
> +	state->need_init = true;
> +	err("%s: mb86a20s init failed.", __func__);
> +	kfree(buf);
> +	return ret;
> +}
> +
> +static int mb86a20s_init_fe(struct dvb_frontend *fe)
> +{
> +	int n;
> +	struct mb86a20s_state *state = fe->sec_priv;
> +
> +	fe->dtv_property_cache.delivery_system = SYS_ISDBT;
> +
> +	n = mb86a20s_init_regs(state);
> +	if (n < 0)
> +		return n;
> +	else
> +		return 0;
> +}
> +
> +static int mb86a20s_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
> +{
> +	struct mb86a20s_state *state = fe->sec_priv;
> +	char val = 1;
> +
> +	/*
> +	 * In the document "OFDM-LSI for Digital Terrestrial Broadcasting
> +	 * Reception MB86A20S" says:
> +	 * I2C bus and I2C bus for tuner control
> +	 * This product realizes I2C bus for register setup in the main
> +	 * unit and I2C bus for tuner control to shut off the bus noise from
> +	 * the tuner by connecting the tuner to the I2C bus line only when
> +	 * it is controlled.
> +	 */
> +	if (!state->tuner_ctrl)
> +		return 0;
> +
> +	if (enable)
> +		val = 0;
> +
> +	/* Enable/Disable I2C bus for tuner control */
> +	return mb86a20s_write_reg(state, 0xfe, val);
> +}
> +
> +static int mb86a20s_sleep(struct dvb_frontend *fe)
> +{
> +	struct mb86a20s_state *state = fe->sec_priv;
> +
> +	tbs_dtb08_led_control(state->udev, 0);
> +	state->current_frequency = 0;
> +	state->need_init = 1;
> +	return 0;
> +}
> +
> +static int mb86a20s_set_frontend(struct dvb_frontend *fe)
> +{
> +	int i, ret;
> +	u8 val;
> +	struct mb86a20s_state *state = fe->sec_priv;
> +	struct dtv_frontend_properties *dpc = &fe->dtv_property_cache;
> +
> +	if (time_before(jiffies, state->next_set_frontend_check) &&
> +		state->current_frequency == dpc->frequency)
> +			return 0;
> +
> +	state->current_frequency = dpc->frequency;
> +	state->next_set_frontend_check = jiffies + msecs_to_jiffies(200);
> +
> +	/* turn off the led */
> +	tbs_dtb08_led_control(state->udev, 0);
> +
> +	if (state->need_init) {
> +		ret = mb86a20s_init_regs(state);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	/* program tuner */
> +	if (fe->ops.tuner_ops.set_params) {
> +		state->tuner_ctrl = true;
> +		fe->ops.tuner_ops.set_params(fe);
> +		/* disable I2C bus tuner control */
> +		if (fe->ops.i2c_gate_ctrl)
> +			fe->ops.i2c_gate_ctrl(fe, 0);
> +		state->tuner_ctrl = false;
> +		msleep(100);
> +	}
> +
> +	for (i = 0; i < sizeof(mb86a20s_soft_reset); i += 2)
> +	{
> +		ret = mb86a20s_write_reg(state, mb86a20s_soft_reset[i],
> +					mb86a20s_soft_reset[i+1]);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	for (i = 0; i < 10; i++) {
> +		ret = mb86a20s_read_reg(state, 0x0a, &val);
> +		if (ret == 0 && val >= 2) break;
> +		msleep(100);
> +	}
> +
> +	/* turn on the led */
> +	tbs_dtb08_led_control(state->udev, 1);
> +
> +	return 0;
> +}
> +
> +static int mb86a20s_get_tune_settings(struct dvb_frontend *fe,
> +				struct dvb_frontend_tune_settings *feset)
> +{
> +	feset->min_delay_ms = 600;
> +	feset->step_size = 0;
> +	feset->max_drift = 0;
> +
> +	return 0;
> +}
> +
> +static int mb86a20s_read_status(struct dvb_frontend *fe, fe_status_t *status)
> +{
> +	int i;
> +	u8 val = 0;
> +	struct mb86a20s_state *state = fe->sec_priv;
> +
> +	if (time_before(jiffies, state->next_status_check)) {
> +		*status = state->status;
> +		return 0;
> +	}
> +
> +	state->next_status_check = jiffies + msecs_to_jiffies(100);
> +	for (i = 0; i < 10; i++) {
> +		mb86a20s_read_reg(state, 0x0a, &val);
> +		if (val >= 2) break;
> +		msleep(10);
> +	}
> +
> +	*status = 0;
> +
> +	if (val >= 2)
> +		*status |= FE_HAS_SIGNAL;
> +	if (val >= 4)
> +		*status |= FE_HAS_CARRIER;
> +	if (val >= 5)
> +		*status |= FE_HAS_VITERBI;
> +	if (val >= 7)
> +		*status |= FE_HAS_SYNC;
> +	if (val >= 8)
> +		*status |= FE_HAS_LOCK;
> +
> +	state->status = *status;
> +	return 0;
> +}
> +
> +static int mb86a20s_get_property(struct dvb_frontend *fe,
> +				 struct dtv_property *tvp)
> +{
> +	struct dtv_frontend_properties *dpc = &fe->dtv_property_cache;
> +
> +	switch(tvp->cmd) {
> +	case DTV_DELIVERY_SYSTEM:
> +		tvp->u.data = dpc->delivery_system = SYS_ISDBT;
> +		break;
> +	}
> +	return 0;
> +}
> +
> +static int mb86a20s_read_signal_strength(struct dvb_frontend *fe,
> +					   u16 *strength)
> +{
> +	struct mb86a20s_state *state = fe->sec_priv;
> +	int i, n;
> +
> +	if (time_before(jiffies, state->next_strength_check)) {
> +		*strength = state->strength;
> +		return 0;
> +	}
> +	state->next_strength_check = jiffies + msecs_to_jiffies(100);
> +	*strength = state->strength = 0;
> +	for (i = 0; i < 10; i++)
> +	{
> +		u8 val = 0;
> +		mb86a20s_read_reg(state, 0x0a, &val);
> +		if (val < 2) goto next;
> +#if 0
> +		if (mb86a20s_read_subreg(state, 0x04, 0x3a, &val) < 0)
> +			goto next;
> +		n = ((255 - val) * 10000) / 255;
> +		state->strength = *strength = (u16)((65535 * n) / 10000);
> +
> +		dbg_info("%s: val=%d, n=%d, strength=%d %d%%",
> +			__func__, val, n, *strength, (255-val) * 100 / 255);
> +		return 0;
> +#else
> +		if (mb86a20s_read_subreg(state, 0x04, 0x25, &val) < 0)
> +			goto next;
> +		n = val;
> +		if (mb86a20s_read_subreg(state, 0x04, 0x26, &val) < 0)
> +			goto next;
> +		n = (((n << 8) | val) * 0x100100) >> 16;
> +		*strength = state->strength = n ;
> +		return 0;
> +#endif
> +next:
> +		msleep(10);
> +	}
> +
> +	return 0;
> +}
> +
> +static int mb86a20s_read_snr(struct dvb_frontend *fe, u16 *snr)
> +{
> +	struct mb86a20s_state *state = fe->sec_priv;
> +	int i, n, cnr;
> +
> +	if (time_before(jiffies, state->next_snr_check)) {
> +		*snr = state->snr;
> + 		return 0;
> +	}
> +
> +	state->next_snr_check = jiffies + msecs_to_jiffies(100);
> +	*snr = state->snr = 0;
> +	for (i = 0; i < 10; i++) {
> +		u8 val = 0;
> +		n = mb86a20s_read_reg(state, 0x0a, &val);
> +		if (n < 0 || val < 2) goto next;
> +		if (mb86a20s_read_reg(state, 0x45, &val) < 0) goto next;
> +		/* read cnr_flag */
> +		if (((val >> 6) & 1) != 0) {
> +			if (mb86a20s_read_reg(state, 0x46, &val) < 0) goto next;
> +			n = val;
> +			if (mb86a20s_read_reg(state, 0x47, &val) < 0) goto next;
> +			cnr = (n << 0x08) | val;
> +			/* reset cnr_counter */
> +			mb86a20s_read_reg(state, 0x45, &val);
> +			val |= 0x10;
> +			mb86a20s_write_reg(state, 0x45, val);
> +			msleep(5);
> +			val &= 0x6f; /* FIXME: or 0xef ? */
> +			mb86a20s_write_reg(state, 0x45, val);
> +			if (cnr > 0x4cc0) cnr = 0x4cc0;
> +			n = ((0x4cc0 - cnr) * 10000) / 0x4cc0;
> +			n = (65535 * n) / 10000;
> +			*snr = state->snr = n;
> +			return 0;
> +		}
> +next:
> +		msleep(10);
> +	}
> +
> +	return 0;
> +}

Frontend stuff should be at mb86a20s module, and not here.

> +
> +static struct tda18271_std_map mb86a20s_tda18271_config = {
> +	.dvbt_6 = { .if_freq = 3300, .agc_mode = 3, .std = 4,
> +		    .if_lvl = 7, .rfagc_top = 0x37, },
> +};
> +
> +static struct tda18271_config tbs_dtb08_tda18271_config = {
> +	.std_map = &mb86a20s_tda18271_config,
> +	.gate = TDA18271_GATE_DIGITAL,
> +};
> +
> +static int tbs_dtb08_tuner_attach(struct dvb_usb_adapter *adap)
> +{
> +	int ret;
> +	struct mb86a20s_state *state = adap->fe_adap[0].priv;
> +
> +	if (adap == NULL || adap->fe_adap[0].fe == NULL)
> +		return -ENODEV;
> +
> +	if (adap->fe_adap[0].fe->ops.tuner_ops.init != NULL)
> +		return 0;
> +
> +	state->tuner_ctrl = true;
> +	ret = dvb_attach(tda18271_attach, adap->fe_adap[0].fe,
> +			 TUNER_I2C_ADDR, &adap->dev->i2c_adap,
> +			 &tbs_dtb08_tda18271_config) == NULL ? -ENODEV : 0;
> +	state->tuner_ctrl = false;
> +	return ret;
> +}
> +
> +static struct mb86a20s_reg_subreg_config mb86a20s_config_regs[] = {
> +	{ 0x3C, 0x00, 0x38 },
> +	{ 0x04, 0x00, 0x001e },
> +	{ 0x04, 0x0E, 0x0032 },
> +	{ 0x04, 0x15, 0x55 },
> +	{ 0x04, 0x16, 0x00 },
> +	{ 0x28, 0x20, 0x33dd00 },
> +	{ 0x28, 0x6A, 0x0002f0 },
> +	{ 0x28, 0x74, 0x0001f4 },
> +	{ 0x50, 0xD5, 0x00 },
> +	{ 0x50, 0xD6, 0x17 },
> +	{ 0x50, 0xDC, 0x3fff },
> +	{ 0x50, 0xDE, 0x3fff },
> +	{ 0x50, 0xE0, 0x3fff }
> +};
> +
> +static struct mb86a20s_config mb86a20s_cfg = {
> +	.demod_address = DEMOD_I2C_ADDR,
> +};
> +
> +static int tbs_dtb08_frontend_attach(struct dvb_usb_adapter *adap)
> +{
> +	struct dvb_frontend *fe;
> +	struct dvb_usb_device *dev = adap->dev;
> +	struct mb86a20s_state *state = adap->fe_adap[0].priv;
> +
> +	if (state == NULL) {
> +		err("%s: adap->dev->priv == NULL!", __func__);
> +		return -ENOMEM;
> +	}
> +
> +	tbs_dtb08_fx2_ie_ex0(dev->udev, FX2_EX0_DISABLE);
> +	tbs_dtb08_fx2_i2ctl(dev->udev, I2CTL_400Khz);
> +
> +	state->udev = dev->udev;
> +	state->demod_addr = DEMOD_I2C_ADDR;
> +	state->config_size = ARRAY_SIZE(mb86a20s_config_regs);
> +	state->config_regs = mb86a20s_config_regs;
> +
> +	if (mb86a20s_init_regs(state) != 0) {
> +		err("%s: demodulator not found!", __func__);
> +		return -ENODEV;
> +	}
> +
> +	fe = dvb_attach(mb86a20s_attach, &mb86a20s_cfg,
> +			&adap->dev->i2c_adap);
> +	if (!fe) return -ENODEV;
> +
> +	adap->fe_adap[0].fe = fe;
> +
> +	fe->sec_priv = state;
> +	fe->ops.init = mb86a20s_init_fe;
> +	fe->ops.sleep = mb86a20s_sleep;
> +	fe->ops.set_frontend = mb86a20s_set_frontend;
> +	fe->ops.read_status = mb86a20s_read_status;
> +	fe->ops.read_signal_strength = mb86a20s_read_signal_strength;
> +	fe->ops.read_snr = mb86a20s_read_snr;
> +	fe->ops.get_tune_settings = mb86a20s_get_tune_settings;
> +	fe->ops.get_property = mb86a20s_get_property;
> +	fe->ops.i2c_gate_ctrl = mb86a20s_i2c_gate_ctrl;
> +
> +	if (tbs_dtb08_tuner_attach(adap) == 0) {
> +		tbs_dtb08_fx2_ie_ex0(dev->udev, FX2_EX0_ENABLE);
> +		return 0;
> +	}
> +
> +	dvb_frontend_detach(fe);
> +	adap->fe_adap[0].fe = NULL;
> +
> +	return -ENODEV;
> +}
> +
> +static int tbs_dtb08_download_firmware(struct usb_device *dev, const struct firmware *fw)
> +{
> +	int i, n, count, fx2_renum;
> +	u8 val, *buf;
> +
> +	fw_ok = 0;
> + 	if (dev == NULL || fw == NULL || fw->size <= 0) {
> +		err("%s: invalid parameters.", __func__);
> +		return -EINVAL;
> +	}
> +
> +	/* stop the CPU */
> +	val = 1;
> +	if (tbs_dtb08_generic_write_addr(dev, 0xa0, 0xe600, &val, 1) != 1)
> +		return -EINVAL;
> +
> +	buf = kmalloc(64 , GFP_KERNEL);

Please don't use magic numbers. Instead use a define for "64". Btw, the dvb-usb core has already
a logic to load FX2 firmwares. See az6007. Use it, instead of creating your own copy.

> +	if (!buf)
> +		return -ENOMEM;
> +
> +	fx2_renum = 1;
> +	for (n = 0; n < fw->size; n += 64) {
> +		count = fw->size - n;
> +		if (count > 64)
> +			count = 64;
> +
> +		if (fx2_renum) {
> +			tbs_dtb08_generic_read_addr(dev, 0xa0, n, buf, count);
> +			for (i = 0; i < count; i++) {
> +				if (buf[i] != fw->data[n+i]) {
> +					fx2_renum = 0;
> +					break;
> +				}
> +			}
> +			if (fx2_renum) continue;
> +		}
> +		memcpy(buf, fw->data + n, count);
> +		if (tbs_dtb08_generic_write_addr(dev, 0xa0, n, buf, count) != count) {
> +			err("%s: addr=%04x: error while "
> +				"transferring firmware", __func__, n);
> +			goto ret_err2;
> +		}
> +	}
> +
> +	/* restart the CPU */
> +	val = 0;
> +	if (tbs_dtb08_generic_write_addr(dev, 0xa0, 0xe600, &val, 1) != 1) {
> +		err("%s: could not restart the USB controller CPU.", __func__);
> +		goto ret_err2;
> +	}
> +
> +	msleep(200);
> +
> +	if (!fx2_renum) { /* ReEnumeration */
> +		err("%s: ReEnumeration!", __func__);
> +		goto ret_err2;
> +	}
> +
> +	if (tbs_dtb08_i2c_read(dev, DEMOD_I2C_ADDR, 0, &val, 1) < 0)
> +		goto ret_err;
> +
> +	if (tbs_dtb08_fx2_ie_ex0(dev, FX2_EX0_ENABLE) < 0)
> +		goto ret_err;
> +
> +	if (tbs_dtb08_ir_code_read(dev, buf, 5) < 0)
> +		goto ret_err;
> +
> +	kfree(buf);
> +	fw_ok = 1;
> +	return 0;
> +
> +ret_err:
> +	err("%s: failed!", __func__);
> +
> +ret_err2:
> +	kfree(buf);
> +	return -EINVAL;
> +}
> +
> +static int tbs_dtb08_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
> +{
> +	return tbs_dtb08_led_control(adap->dev->udev, onoff);
> +}
> +
> +static u32 tbs_dtb08_i2c_func(struct i2c_adapter *adapter)
> +{
> +	return I2C_FUNC_I2C;
> +}
> +
> +static struct i2c_algorithm tbs_dtb08_i2c_algo = {
> +	.master_xfer = tbs_dtb08_i2c_transfer,
> +	.functionality = tbs_dtb08_i2c_func,
> +};
> +
> +static struct usb_device_id tbs_dtb08_table[] = {
> +	{ USB_DEVICE(USB_VID_TBS_734C, USB_PID_TBS_DTB08) },
> +	{ }	/* Terminating entry */
> +};
> +
> +MODULE_DEVICE_TABLE(usb, tbs_dtb08_table);
> +
> +static struct dvb_usb_device_properties tbs_dtb08_properties = {
> +	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
> +	.usb_ctrl = DEVICE_SPECIFIC,
> +	.download_firmware = tbs_dtb08_download_firmware,
> +	.firmware = "tbs-dtb08-fw.bin",
> +	.no_reconnect = 1,
> +
> +	.num_adapters = 1,
> +	.adapter = {{
> +		.num_frontends = 1,
> +		.fe = {{
> +			.size_of_priv = sizeof(struct mb86a20s_state),
> +			.streaming_ctrl = tbs_dtb08_streaming_ctrl,
> +			.frontend_attach = tbs_dtb08_frontend_attach,
> +			.tuner_attach = tbs_dtb08_tuner_attach,
> +			.stream = {
> +				.type = USB_BULK,
> +				.count = 8,
> +				.endpoint = 0x82,
> +				.u = {
> +					.bulk = {
> +						.buffersize = 4096,
> +					}
> +				}
> +			},
> +		}}}
> +	},
> +
> +	.rc.legacy = {
> +		.rc_interval      = 250,
> +		.rc_map_table     = tbs_dtb08_rc_map_table,
> +		.rc_map_size      = ARRAY_SIZE(tbs_dtb08_rc_map_table),
> +		.rc_query         = tbs_dtb08_rc_query,
> +	},

Don't use rc.legacy. This is only for _legacy_ drivers.

> +
> +	.i2c_algo = &tbs_dtb08_i2c_algo,
> +
> +	.num_device_descs = 1,
> +	.devices = {
> +		{
> +			.name = "TBS-Tech ISDB-T USB 2.0 (DTB08)",
> +			.cold_ids = { &tbs_dtb08_table[0], NULL },
> +			.warm_ids = { NULL },
> +		},
> +	},
> +};
> +
> +static int tbs_dtb08_probe(struct usb_interface *intf,
> +		const struct usb_device_id *id)
> +{
> +	struct dvb_usb_device *d = NULL;
> +	int n, ret;
> +
> +	dbg_info("%s: %d, num_altsetting=%d", __func__,
> +		 intf->cur_altsetting->desc.bInterfaceNumber,
> +		 intf->num_altsetting);
> +
> +	ret = dvb_usb_device_init(intf, &tbs_dtb08_properties,
> +			THIS_MODULE, &d, adapter_nr);
> +
> +	if (ret != 0) {
> +		err("%s: failed err=%d", __func__, ret);
> +		return ret;
> +	}
> +
> +	if (d) {
> +		for (n = 0; n < d->props.num_adapters; n++) {
> +			struct dvb_usb_adapter *adap = &d->adapter[n];
> +			if (adap && adap->fe_adap[0].fe) {
> +			  ret++;
> +			}
> +		}
> +	}
> +
> +	if (ret == 0) {
> +		fw_ok = 0;
> +		dvb_usb_device_exit(intf);
> +		return -1;
> +	}
> +	else
> +		return 0;
> +}
> +
> +static void tbs_dtb08_usb_disconnect(struct usb_interface *intf)
> +{
> +	fw_ok = 0;
> +	dvb_usb_device_exit(intf);
> +}
> +
> +static struct usb_driver tbs_dtb08_driver = {
> +	.name = "dvb-usb-tbsdtb08",
> +	.probe = tbs_dtb08_probe,
> +	.disconnect = tbs_dtb08_usb_disconnect,
> +	.id_table = tbs_dtb08_table,
> +};
> +
> +module_usb_driver(tbs_dtb08_driver);
> +
> +MODULE_AUTHOR("Manoel Pinheiro <pinusdtv@hotmail.com>");
> +MODULE_DESCRIPTION("Driver for TBS-Tech ISDB-T USB2.0 Receiver (DTB08 Full Seg)");
> +MODULE_LICENSE("GPL");


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [media next v3.4] Add support for TBS-Tech ISDB-T Full Seg DTB08
@ 2012-03-29 23:24 Manoel Pinheiro
  2012-04-11  0:28 ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 3+ messages in thread
From: Manoel Pinheiro @ 2012-03-29 23:24 UTC (permalink / raw)
  To: mchehab, linux-media, lgspn

Driver for TBS-Tech ISDB-T USB2.0 Receiver (DTB08 Full Seg).

The device used as a reference is described in the link
http://linuxtv.org/wiki/index.php/JH_Full_HD_Digital_TV_Receiver


Signed-off-by: Manoel Pinheiro <pinusdtv@hotmail.com>
---
 drivers/media/dvb/dvb-usb/Kconfig     |    8 +
 drivers/media/dvb/dvb-usb/Makefile    |    3 +
 drivers/media/dvb/dvb-usb/tbs-dtb08.c | 1167 +++++++++++++++++++++++++++++++++
 3 files changed, 1178 insertions(+)
 create mode 100644 drivers/media/dvb/dvb-usb/tbs-dtb08.c

diff --git a/drivers/media/dvb/dvb-usb/Kconfig b/drivers/media/dvb/dvb-usb/Kconfig
index 63bf456..63a794d 100644
--- a/drivers/media/dvb/dvb-usb/Kconfig
+++ b/drivers/media/dvb/dvb-usb/Kconfig
@@ -422,3 +422,11 @@ config DVB_USB_RTL28XXU
 	select MEDIA_TUNER_MXL5005S if !MEDIA_TUNER_CUSTOMISE
 	help
 	  Say Y here to support the Realtek RTL28xxU DVB USB receiver.
+
+config DVB_USB_TBSDTB08
+	tristate "TBS-Tech ISDB-T Full Seg DTB08 USB2.0 support"
+	depends on DVB_USB
+	select DVB_MB86A20S
+	select MEDIA_TUNER_TDA18271 if !MEDIA_TUNER_CUSTOMISE
+	help
+	  Say Y here to support the TBS-Tech Full Seg DTB08 ISDB-T USB2.0 receivers
diff --git a/drivers/media/dvb/dvb-usb/Makefile b/drivers/media/dvb/dvb-usb/Makefile
index b76acb5..051756c 100644
--- a/drivers/media/dvb/dvb-usb/Makefile
+++ b/drivers/media/dvb/dvb-usb/Makefile
@@ -110,6 +110,9 @@ obj-$(CONFIG_DVB_USB_MXL111SF) += mxl111sf-tuner.o
 dvb-usb-rtl28xxu-objs = rtl28xxu.o
 obj-$(CONFIG_DVB_USB_RTL28XXU) += dvb-usb-rtl28xxu.o
 
+dvb-usb-tbsdtb08-objs = tbs-dtb08.o
+obj-$(CONFIG_DVB_USB_TBSDTB08) += dvb-usb-tbsdtb08.o
+
 ccflags-y += -I$(srctree)/drivers/media/dvb/dvb-core
 ccflags-y += -I$(srctree)/drivers/media/dvb/frontends/
 # due to tuner-xc3028
diff --git a/drivers/media/dvb/dvb-usb/tbs-dtb08.c b/drivers/media/dvb/dvb-usb/tbs-dtb08.c
new file mode 100644
index 0000000..f909312
--- /dev/null
+++ b/drivers/media/dvb/dvb-usb/tbs-dtb08.c
@@ -0,0 +1,1167 @@
+/*
+ *   TBS-Tech ISDB-T Full Seg DTB08 device driver
+ *
+ *   Copyright (C) 2010-2012 Manoel Pinheiro <pinusdtv@hotmail.com>
+ *
+ *   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; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+
+#define DVB_USB_LOG_PREFIX "tbs_dtb08"
+
+#include "dvb-usb.h"
+#include "tda18271.h"
+#include "mb86a20s.h"
+
+static int debug = 0;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Turn on/off module debugging (default:0).");
+
+#define dbg_info(format, args...)	do {	\
+	if (debug)				\
+		info(format, ## args);		\
+} while(0)
+
+#define	DEMOD_I2C_ADDR	0x20
+#define	TUNER_I2C_ADDR	0xc0
+
+#ifndef USB_PID_TBS_DTB08
+#define USB_PID_TBS_DTB08	0xdb08
+#endif
+
+#define USB_VID_TBS_734C	0x734c
+
+#define TBS_DTB08_GET_IR_CODE	0xb8
+#define TBS_DTB08_LED_CONTROL	5
+
+#define FX2_IE_EX0	7
+#define FX2_EX0_ENABLE	1
+#define FX2_EX0_DISABLE	0
+#define FX2_I2CTL	6
+#define I2CTL_100Khz	0
+#define I2CTL_400Khz	1
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+static DEFINE_MUTEX(tbs_dtb08_usb_mutex);
+static int fw_ok = 0;
+
+struct mb86a20s_reg_subreg_config {
+	u8 reg;
+	u8 subreg;
+	u32 val;
+};
+
+struct mb86a20s_state {
+	struct usb_device *udev;
+	int demod_addr;
+	u32 current_frequency;
+	fe_status_t status;
+	u16 snr;
+	u16 strength;
+	unsigned long next_snr_check;
+	unsigned long next_strength_check;
+	unsigned long next_set_frontend_check;
+	unsigned long next_status_check;
+	int config_size;
+	struct mb86a20s_reg_subreg_config *config_regs;
+	bool need_init;
+	bool tuner_ctrl;
+};
+
+struct mb86a20s_reg_subreg_val {
+	u8 reg;
+	u8 subreg;
+	u8 type;	/* 0=8 bits wo/sub, 1=8 bits w/sub
+			 * 2=16 bits wo/sub, 3=16 bits w/sub, 4=24 bits */
+	u32 val;
+};
+
+static struct mb86a20s_reg_subreg_val mb86a20s_regs_val[] = {
+	{ 0x70, 0x00, 0x00, 0x0f },
+	{ 0x70, 0x00, 0x00, 0xff },
+	{ 0x08, 0x00, 0x00, 0x01 },
+	{ 0x09, 0x00, 0x00, 0x3e },
+	{ 0x50, 0xd1, 0x01, 0x22 },
+	{ 0x39, 0x00, 0x00, 0x01 },
+	{ 0x71, 0x00, 0x00, 0x00 },
+	{ 0x28, 0x2a, 0x04, 0xff80 },
+	{ 0x28, 0x20, 0x04, 0x33dfa9 },
+	{ 0x28, 0x22, 0x04, 0x1ff0 },
+	{ 0x3b, 0x00, 0x00, 0x21 },
+	{ 0x3c, 0x00, 0x00, 0x3a },
+	{ 0x01, 0x00, 0x00, 0x0d },
+	{ 0x04, 0x08, 0x01, 0x05 },
+	{ 0x04, 0x0e, 0x03, 0x0014 },
+	{ 0x04, 0x0b, 0x01, 0x8c },
+	{ 0x04, 0x00, 0x03, 0x0007 },
+	{ 0x04, 0x02, 0x03, 0x0fa0 },
+	{ 0x04, 0x09, 0x01, 0x00 },
+	{ 0x04, 0x0a, 0x01, 0xff },
+	{ 0x04, 0x27, 0x01, 0x64 },
+	{ 0x04, 0x28, 0x01, 0x00 },
+	{ 0x04, 0x1e, 0x01, 0xff },
+	{ 0x04, 0x29, 0x01, 0x0a },
+	{ 0x04, 0x32, 0x01, 0x0a },
+	{ 0x04, 0x14, 0x01, 0x02 },
+	{ 0x04, 0x04, 0x03, 0x0022 },
+	{ 0x04, 0x06, 0x03, 0x0ed8 },
+	{ 0x04, 0x12, 0x01, 0x00 },
+	{ 0x04, 0x13, 0x01, 0xff },
+	{ 0x04, 0x15, 0x01, 0x4e },
+	{ 0x04, 0x16, 0x01, 0x20 },
+	{ 0x52, 0x00, 0x00, 0x01 },
+	{ 0x50, 0xa7, 0x04, 0xffff },
+	{ 0x50, 0xaa, 0x04, 0xffff },
+	{ 0x50, 0xad, 0x04, 0xffff },
+	{ 0x5e, 0x00, 0x00, 0x07 },
+	{ 0x50, 0xdc, 0x03, 0x01f4 },
+	{ 0x50, 0xde, 0x03, 0x01f4 },
+	{ 0x50, 0xe0, 0x03, 0x01f4 },
+	{ 0x50, 0xb0, 0x01, 0x07 },
+	{ 0x50, 0xb2, 0x03, 0xffff },
+	{ 0x50, 0xb4, 0x03, 0xffff },
+	{ 0x50, 0xb6, 0x03, 0xffff },
+	{ 0x50, 0x50, 0x01, 0x02 },
+	{ 0x50, 0x51, 0x01, 0x04 },
+	{ 0x45, 0x00, 0x00, 0x04 },
+	{ 0x48, 0x00, 0x00, 0x04 },
+	{ 0x50, 0xd5, 0x01, 0x01 },
+	{ 0x50, 0xd6, 0x01, 0x1f },
+	{ 0x50, 0xd2, 0x01, 0x03 },
+	{ 0x50, 0xd7, 0x01, 0x3f },
+	{ 0x28, 0x74, 0x04, 0x0040 },
+	{ 0x28, 0x46, 0x04, 0x2c0c },
+	{ 0x04, 0x40, 0x01, 0x01 },
+	{ 0x28, 0x00, 0x01, 0x10 },
+	{ 0x28, 0x05, 0x01, 0x02 },
+	{ 0x1c, 0x00, 0x00, 0x01 },
+	{ 0x28, 0x06, 0x04, 0x0003 },
+	{ 0x28, 0x07, 0x04, 0x000d },
+	{ 0x28, 0x08, 0x04, 0x0002 },
+	{ 0x28, 0x09, 0x04, 0x0001 },
+	{ 0x28, 0x0a, 0x04, 0x0021 },
+	{ 0x28, 0x0b, 0x04, 0x0029 },
+	{ 0x28, 0x0c, 0x04, 0x0016 },
+	{ 0x28, 0x0d, 0x04, 0x0031 },
+	{ 0x28, 0x0e, 0x04, 0x000e },
+	{ 0x28, 0x0f, 0x04, 0x004e },
+	{ 0x28, 0x10, 0x04, 0x0046 },
+	{ 0x28, 0x11, 0x04, 0x000f },
+	{ 0x28, 0x12, 0x04, 0x0056 },
+	{ 0x28, 0x13, 0x04, 0x0035 },
+	{ 0x28, 0x14, 0x04, 0x01be },
+	{ 0x28, 0x15, 0x04, 0x0184 },
+	{ 0x28, 0x16, 0x04, 0x03ee },
+	{ 0x28, 0x17, 0x04, 0x0098 },
+	{ 0x28, 0x18, 0x04, 0x009f },
+	{ 0x28, 0x19, 0x04, 0x07b2 },
+	{ 0x28, 0x1a, 0x04, 0x06c2 },
+	{ 0x28, 0x1b, 0x04, 0x074a },
+	{ 0x28, 0x1c, 0x04, 0x01bc },
+	{ 0x28, 0x1d, 0x04, 0x04ba },
+	{ 0x28, 0x1e, 0x04, 0x0614 },
+	{ 0x50, 0x1e, 0x01, 0x5d },
+	{ 0x50, 0x22, 0x01, 0x00 },
+	{ 0x50, 0x23, 0x01, 0xc8 },
+	{ 0x50, 0x24, 0x01, 0x00 },
+	{ 0x50, 0x25, 0x01, 0xf0 },
+	{ 0x50, 0x26, 0x01, 0x00 },
+	{ 0x50, 0x27, 0x01, 0xc3 },
+	{ 0x50, 0x39, 0x01, 0x02 },
+	{ 0x28, 0x6a, 0x04, 0x0000 }
+};
+
+static u8 mb86a20s_soft_reset[] = {
+	0x70, 0xf0, 0x70, 0xff, 0x08, 0x01, 0x08, 0x00
+};
+
+static int tbs_dtb08_generic_read_addr(struct usb_device *udev, u8 req,
+				       u16 addr, u8 *data, u16 len)
+{
+	int ret;
+
+	ret = usb_control_msg(udev,
+			      usb_rcvctrlpipe(udev, 0),
+			      req,
+			      USB_TYPE_VENDOR | USB_DIR_IN,
+			      addr, 0, data, len, 2000);
+	if (ret < 0)
+		err("%s: ret=%d", __func__, ret);
+
+	return ret;
+}
+
+inline int tbs_dtb08_generic_read(struct usb_device *udev,
+				  u8 req, u8 *data, u16 len)
+{
+	return tbs_dtb08_generic_read_addr(udev, req, 0, data, len);
+}
+
+static int tbs_dtb08_generic_write_addr(struct usb_device *udev, u8 req,
+					u16 addr, u8 *data, u16 len)
+{
+	int ret;
+
+	ret = usb_control_msg(udev,
+			      usb_sndctrlpipe(udev, 0),
+			      req,
+			      USB_TYPE_VENDOR | USB_DIR_OUT,
+			      addr, 0, data, len, 2000);
+	if (ret < 0)
+		err("%s: ret=%d", __func__, ret);
+
+	return ret;
+}
+
+inline int tbs_dtb08_generic_write(struct usb_device *udev,
+				   u8 req, u8 *data, u16 len)
+{
+	return tbs_dtb08_generic_write_addr(udev, req, 0, data, len);
+}
+
+inline int tbs_dtb08_ir_code_read(struct usb_device *udev, u8 *data, u16 len)
+{
+	return tbs_dtb08_generic_read_addr(udev, TBS_DTB08_GET_IR_CODE, 0, data, len);
+}
+
+static int tbs_dtb08_i2c_busy(struct usb_device *udev)
+{
+	u8 val;
+	int i, ret;
+
+	for (i = 0; i < 10; i++) {
+		ret = tbs_dtb08_generic_read(udev, 0x81, &val, 1);
+		if (ret >= 0 && val == 0) return 0;
+		msleep(1);
+	}
+
+	return -1;
+}
+
+static int tbs_dtb08_i2c_read(struct usb_device *udev, u8 addr,
+			      u8 reg, u8 *data, u8 len)
+{
+	int ret;
+	u8 obuf[3];
+
+	if (len < 1) {
+		err("%s: len less than 1 bytes. Makes no sense.", __func__);
+		return -EINVAL;
+	}
+
+	mutex_lock(&tbs_dtb08_usb_mutex);
+	ret = tbs_dtb08_i2c_busy(udev);
+	if (ret == 0)
+	{
+		obuf[0] = len;
+		obuf[1] = addr;
+		obuf[2] = reg;
+
+		ret = tbs_dtb08_generic_write(udev, 0x90, &obuf[0], 3);
+		if (ret >= 0) {
+			ret = tbs_dtb08_i2c_busy(udev);
+			if (ret == 0)
+				ret = tbs_dtb08_generic_read(udev, 0x91, data, len);
+		}
+	}
+	mutex_unlock(&tbs_dtb08_usb_mutex);
+
+	return ret;
+}
+
+static int tbs_dtb08_i2c_write(struct usb_device *udev, u8 addr,
+			       u8 reg, u8 *data, u8 len)
+{
+	int i, ret;
+	u8 *buf;
+
+	if (len < 1) {
+		err("%s: len less than 1 bytes. Makes no sense.", __func__);
+		return -EINVAL;
+	}
+	if (len > 61) {
+		err("%s: len more than 61 bytes. Not supported.", __func__);
+		return -EINVAL;
+	}
+
+	buf = kmalloc(64 , GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	mutex_lock(&tbs_dtb08_usb_mutex);
+
+	buf[0] = len + 2;
+	buf[1] = addr;
+	buf[2] = reg;
+
+	for (i = 0; i < len; i++)
+		buf[i+3] = data[i];
+
+	ret = tbs_dtb08_i2c_busy(udev);
+	if (ret < 0)
+		goto ret_err;
+
+	ret = tbs_dtb08_generic_write(udev, 0x80, buf, len + 3);
+
+ret_err:
+	kfree(buf);
+	mutex_unlock(&tbs_dtb08_usb_mutex);
+	return ret;
+}
+
+static int tbs_dtb08_send_cmd_8a(struct usb_device *udev, u8 val1, u8 val2)
+{
+	int ret;
+	u8 obuf[2] = { val1, val2 };
+
+	ret = tbs_dtb08_generic_write(udev, 0x8a, &obuf[0], 2);
+
+	return (ret < 0) ? ret : 0;
+}
+
+inline int tbs_dtb08_fx2_ie_ex0(struct usb_device *udev, u8 enable)
+{
+	return tbs_dtb08_send_cmd_8a(udev, FX2_IE_EX0, enable ? 1 : 0);
+}
+
+inline int tbs_dtb08_led_control(struct usb_device *udev, int onoff)
+{
+	return tbs_dtb08_send_cmd_8a(udev, TBS_DTB08_LED_CONTROL,
+				     onoff ? 1 : 0);
+}
+
+inline int tbs_dtb08_fx2_i2ctl(struct usb_device *udev, u8 i2cfreq)
+{
+	return tbs_dtb08_send_cmd_8a(udev, FX2_I2CTL, i2cfreq);
+}
+
+static int tbs_dtb08_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+		int num)
+{
+	struct dvb_usb_device *dev = i2c_get_adapdata(adap);
+	int ii, ret = 0;
+
+	dbg_info("%s: num=%02x, msg[0].addr=%02x, jiffies=%ld",
+		 __func__, num, msg[0].addr, jiffies);
+
+	if (!dev || !dev->udev)
+		return -ENODEV;
+
+	if (mutex_lock_interruptible(&dev->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	switch (num) {
+	case 2:
+		if ((msg[0].addr == DEMOD_I2C_ADDR || msg[0].addr == TUNER_I2C_ADDR) &&
+			msg[0].addr == msg[1].addr &&  msg[0].flags == 0 &&
+			msg[1].flags == I2C_M_RD && msg[0].len == 1 && msg[1].len > 0) {
+			ret = tbs_dtb08_i2c_read(dev->udev, msg[0].addr,
+					msg[0].buf[0],msg[1].buf, msg[1].len);
+		}
+		else {
+			err("%s: num==2, msg[0].addr==%02x, msg[0].flags==%d, "
+			    "msg[0].len==%d, msg[1].addr==%02x, msg[1].flags==%d, "
+			    "not suported!", __func__, msg[0].addr, msg[0].flags,
+				msg[0].len, msg[1].addr, msg[1].flags);
+			ret = -EINVAL;
+		}
+		break;
+	case 1:
+		switch (msg[0].addr) {
+		case DEMOD_I2C_ADDR:
+		case TUNER_I2C_ADDR:
+			ii = msg[0].len - 1;
+			if (ii < 0 || ii > 63) {
+				ret = -EINVAL;
+				break;
+			}
+
+			if (msg[0].flags == 0) {
+				ret = tbs_dtb08_i2c_write(
+					dev->udev, msg[0].addr,
+					msg[0].buf[0],
+					&msg[0].buf[1], ii);
+			} else {
+				ret = tbs_dtb08_i2c_read(
+					dev->udev, msg[0].addr,
+					msg[0].buf[0],
+					msg[0].buf, msg[0].len);
+			}
+			break;
+		default:
+			err("%s: num==1, addr==%02x, not suported!",
+			    __func__, msg[0].addr);
+			ret = -EINVAL;
+		}
+		break;
+	default:
+		err("%s:num == %d, not suported!", __func__, num);
+		ret = -EINVAL;
+		break;
+	}
+
+	mutex_unlock(&dev->i2c_mutex);
+
+	return (ret < 0) ? ret : num;
+}
+
+static struct rc_map_table tbs_dtb08_rc_map_table[] = {
+	{ 0x0010, KEY_POWER },		/* power */
+	{ 0x0006, KEY_MUTE },		/* mute */
+	{ 0x004c, KEY_1 },
+	{ 0x0004, KEY_2 },
+	{ 0x0000, KEY_3 },
+	{ 0x001e, KEY_4 },
+	{ 0x000e, KEY_5 },
+	{ 0x001a, KEY_6 },
+	{ 0x0014, KEY_7 },
+	{ 0x000f, KEY_8 },
+	{ 0x000c, KEY_9 },
+	{ 0x001c, KEY_0 },
+	{ 0x0040, KEY_CHANNELUP },	/* ch+ */
+	{ 0x000a, KEY_CHANNELDOWN },	/* ch- */
+	{ 0x0019, KEY_VOLUMEUP },	/* vol+ */
+	{ 0x0017, KEY_VOLUMEDOWN },	/* vol- */
+	{ 0x0011, KEY_OK },		/* ok */
+	{ 0x0009, KEY_SAVE },		/* scrn shot */
+	{ 0x001f, KEY_UP },
+	{ 0x001b, KEY_LEFT },
+	{ 0x0015, KEY_RIGHT },
+	{ 0x0016, KEY_DOWN },
+	{ 0x004d, KEY_FAVORITES },	/* fav */
+	{ 0x0001, KEY_ZOOM },
+	{ 0x0003, KEY_EPG },
+	{ 0x001d, KEY_PLAY },
+	{ 0x000d, KEY_STOP },
+	{ 0x0012, KEY_LAST  },		/* recall */
+};
+
+static int tbs_dtb08_rc_query(struct dvb_usb_device *d, u32 *event, int *state)
+{
+	u8 *buf;
+	int i;
+	struct rc_map_table *rc_map = d->props.rc.legacy.rc_map_table;
+
+	*state = REMOTE_NO_KEY_PRESSED;
+	*event = 0;
+
+	if (!fw_ok) {
+		dbg_info("%s: fw_ok == 0", __func__);
+		return 0;
+	}
+
+	buf = kzalloc(5, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	if (mutex_lock_interruptible(&tbs_dtb08_usb_mutex))
+		goto free_buf_leave;
+
+	tbs_dtb08_ir_code_read(d->udev, buf, 5);
+	mutex_unlock(&tbs_dtb08_usb_mutex);
+
+	dbg_info("%s: %02x %02x %02x %02x %02x",
+		 __func__, buf[0], buf[1], buf[2], buf[3], buf[4]);
+
+	if (buf[1] != (u8)~buf[2] || buf[3] != (u8)~buf[4])
+		goto free_buf_leave;
+
+	for (i = 0; i < d->props.rc.legacy.rc_map_size; i++) {
+		u16 scan = (u16)buf[1] << 8 | buf[3];
+		if (rc_map[i].scancode == scan) {
+			*event = rc_map[i].keycode;
+			*state = REMOTE_KEY_PRESSED;
+			break;
+		}
+	}
+
+free_buf_leave:
+	kfree(buf);
+	return 0;
+}
+
+inline int mb86a20s_read_reg(struct mb86a20s_state *state, u8 reg, u8 *val)
+{
+	*val = 0;
+	return tbs_dtb08_i2c_read(state->udev, state->demod_addr, reg, val, 1);
+}
+
+inline int mb86a20s_write_reg(struct mb86a20s_state *state, u8 reg, u8 val)
+{
+	return tbs_dtb08_i2c_write(state->udev, state->demod_addr, reg, &val, 1);
+}
+
+inline int mb86a20s_read_subreg(struct mb86a20s_state *state, u8 reg, u8 subreg, u8 *val)
+{
+	int ret;
+
+	*val = 0;
+	if ((ret = tbs_dtb08_i2c_write(state->udev, state->demod_addr, reg, &subreg, 1)) < 0)
+		return ret;
+	return tbs_dtb08_i2c_read(state->udev, state->demod_addr, reg+1, val, 1);
+}
+
+static u32 get_config_reg_val(struct mb86a20s_state *state,
+			      struct mb86a20s_reg_subreg_val *reg_val)
+{
+	struct mb86a20s_reg_subreg_config *config_regs;
+	int i;
+
+	if (!reg_val)
+		return 0;
+	if (!state || !(config_regs = state->config_regs))
+		return reg_val->val;
+	for (i = 0; i < state->config_size; i++) {
+		if (config_regs->reg == reg_val->reg &&
+		    config_regs->subreg == reg_val->subreg)
+			return config_regs->val;
+		config_regs++;
+	}
+	return reg_val->val;
+}
+
+static int mb86a20s_init_regs(struct mb86a20s_state *state)
+{
+	u8 *buf;
+	u32 val;
+	int i, i2, count, ret = 0;
+
+	buf = kmalloc(12 , GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	for (i = 0; i < ARRAY_SIZE(mb86a20s_regs_val); i++) {
+		struct mb86a20s_reg_subreg_val *reg_val = &mb86a20s_regs_val[i];
+		val = get_config_reg_val(state, reg_val);
+		buf[0] = reg_val->reg;
+		count = 1;
+		switch (reg_val->type) {
+		case 1:
+			buf[count++] = reg_val->subreg;
+			if (buf[0] == 0x28)
+				buf[count++] = 0x2b;
+			else
+				buf[count++] = buf[0] + 1;
+			break;
+		case 2:
+			buf[count++] = (u8)(val >> 0x08);
+			buf[count++] = buf[0] + 1;
+			buf[count++] = (u8)val;
+			break;
+		case 3:
+			buf[count++] = reg_val->subreg;
+			buf[count++] = buf[0] + 1;
+			buf[count++] = (u8)(val >> 0x08);
+			buf[count++] = buf[0];
+			buf[count++] = reg_val->subreg + 1;
+			buf[count++] = buf[0] + 1;
+			break;
+		case 4:
+			if (buf[0] == 0x28) {
+				buf[count++] = reg_val->subreg;
+				buf[count++] = 0x29;
+				buf[count++] = (u8)(val >> 0x10);
+				buf[count++] = 0x2a;
+				buf[count++] = (u8)(val >> 0x08);
+				buf[count++] = 0x2b;
+			}
+			else if (buf[0] == 0x50) {
+				buf[count++] = reg_val->subreg;
+				buf[count++] = 0x51;
+				buf[count++] = (u8)(val >> 0x10);
+				buf[count++] = 0x50;
+				buf[count++] = reg_val->subreg + 1;
+				buf[count++] = 0x51;
+				buf[count++] = (u8)(val >> 0x08);
+				buf[count++] = 0x50;
+				buf[count++] = reg_val->subreg + 2;
+				buf[count++] = 0x51;
+			}
+			else {
+				ret = -1;
+				goto ret_err;
+			}
+			break;
+		}
+		buf[count++] = (u8)val;
+		i2 = 0;
+		while (i2 < count) {
+			ret = mb86a20s_write_reg(state, buf[i2], buf[i2 + 1]);
+			if (ret < 0)
+				goto ret_err;
+			i2 += 2;
+		}
+	}
+	state->need_init = false;
+	kfree(buf);
+	return 0;
+
+ret_err:
+	state->need_init = true;
+	err("%s: mb86a20s init failed.", __func__);
+	kfree(buf);
+	return ret;
+}
+
+static int mb86a20s_init_fe(struct dvb_frontend *fe)
+{
+	int n;
+	struct mb86a20s_state *state = fe->sec_priv;
+
+	fe->dtv_property_cache.delivery_system = SYS_ISDBT;
+
+	n = mb86a20s_init_regs(state);
+	if (n < 0)
+		return n;
+	else
+		return 0;
+}
+
+static int mb86a20s_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
+{
+	struct mb86a20s_state *state = fe->sec_priv;
+	char val = 1;
+
+	/*
+	 * In the document "OFDM-LSI for Digital Terrestrial Broadcasting
+	 * Reception MB86A20S" says:
+	 * I2C bus and I2C bus for tuner control
+	 * This product realizes I2C bus for register setup in the main
+	 * unit and I2C bus for tuner control to shut off the bus noise from
+	 * the tuner by connecting the tuner to the I2C bus line only when
+	 * it is controlled.
+	 */
+	if (!state->tuner_ctrl)
+		return 0;
+
+	if (enable)
+		val = 0;
+
+	/* Enable/Disable I2C bus for tuner control */
+	return mb86a20s_write_reg(state, 0xfe, val);
+}
+
+static int mb86a20s_sleep(struct dvb_frontend *fe)
+{
+	struct mb86a20s_state *state = fe->sec_priv;
+
+	tbs_dtb08_led_control(state->udev, 0);
+	state->current_frequency = 0;
+	state->need_init = 1;
+	return 0;
+}
+
+static int mb86a20s_set_frontend(struct dvb_frontend *fe)
+{
+	int i, ret;
+	u8 val;
+	struct mb86a20s_state *state = fe->sec_priv;
+	struct dtv_frontend_properties *dpc = &fe->dtv_property_cache;
+
+	if (time_before(jiffies, state->next_set_frontend_check) &&
+		state->current_frequency == dpc->frequency)
+			return 0;
+
+	state->current_frequency = dpc->frequency;
+	state->next_set_frontend_check = jiffies + msecs_to_jiffies(200);
+
+	/* turn off the led */
+	tbs_dtb08_led_control(state->udev, 0);
+
+	if (state->need_init) {
+		ret = mb86a20s_init_regs(state);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* program tuner */
+	if (fe->ops.tuner_ops.set_params) {
+		state->tuner_ctrl = true;
+		fe->ops.tuner_ops.set_params(fe);
+		/* disable I2C bus tuner control */
+		if (fe->ops.i2c_gate_ctrl)
+			fe->ops.i2c_gate_ctrl(fe, 0);
+		state->tuner_ctrl = false;
+		msleep(100);
+	}
+
+	for (i = 0; i < sizeof(mb86a20s_soft_reset); i += 2)
+	{
+		ret = mb86a20s_write_reg(state, mb86a20s_soft_reset[i],
+					mb86a20s_soft_reset[i+1]);
+		if (ret < 0)
+			return ret;
+	}
+
+	for (i = 0; i < 10; i++) {
+		ret = mb86a20s_read_reg(state, 0x0a, &val);
+		if (ret == 0 && val >= 2) break;
+		msleep(100);
+	}
+
+	/* turn on the led */
+	tbs_dtb08_led_control(state->udev, 1);
+
+	return 0;
+}
+
+static int mb86a20s_get_tune_settings(struct dvb_frontend *fe,
+				struct dvb_frontend_tune_settings *feset)
+{
+	feset->min_delay_ms = 600;
+	feset->step_size = 0;
+	feset->max_drift = 0;
+
+	return 0;
+}
+
+static int mb86a20s_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+	int i;
+	u8 val = 0;
+	struct mb86a20s_state *state = fe->sec_priv;
+
+	if (time_before(jiffies, state->next_status_check)) {
+		*status = state->status;
+		return 0;
+	}
+
+	state->next_status_check = jiffies + msecs_to_jiffies(100);
+	for (i = 0; i < 10; i++) {
+		mb86a20s_read_reg(state, 0x0a, &val);
+		if (val >= 2) break;
+		msleep(10);
+	}
+
+	*status = 0;
+
+	if (val >= 2)
+		*status |= FE_HAS_SIGNAL;
+	if (val >= 4)
+		*status |= FE_HAS_CARRIER;
+	if (val >= 5)
+		*status |= FE_HAS_VITERBI;
+	if (val >= 7)
+		*status |= FE_HAS_SYNC;
+	if (val >= 8)
+		*status |= FE_HAS_LOCK;
+
+	state->status = *status;
+	return 0;
+}
+
+static int mb86a20s_get_property(struct dvb_frontend *fe,
+				 struct dtv_property *tvp)
+{
+	struct dtv_frontend_properties *dpc = &fe->dtv_property_cache;
+
+	switch(tvp->cmd) {
+	case DTV_DELIVERY_SYSTEM:
+		tvp->u.data = dpc->delivery_system = SYS_ISDBT;
+		break;
+	}
+	return 0;
+}
+
+static int mb86a20s_read_signal_strength(struct dvb_frontend *fe,
+					   u16 *strength)
+{
+	struct mb86a20s_state *state = fe->sec_priv;
+	int i, n;
+
+	if (time_before(jiffies, state->next_strength_check)) {
+		*strength = state->strength;
+		return 0;
+	}
+	state->next_strength_check = jiffies + msecs_to_jiffies(100);
+	*strength = state->strength = 0;
+	for (i = 0; i < 10; i++)
+	{
+		u8 val = 0;
+		mb86a20s_read_reg(state, 0x0a, &val);
+		if (val < 2) goto next;
+#if 0
+		if (mb86a20s_read_subreg(state, 0x04, 0x3a, &val) < 0)
+			goto next;
+		n = ((255 - val) * 10000) / 255;
+		state->strength = *strength = (u16)((65535 * n) / 10000);
+
+		dbg_info("%s: val=%d, n=%d, strength=%d %d%%",
+			__func__, val, n, *strength, (255-val) * 100 / 255);
+		return 0;
+#else
+		if (mb86a20s_read_subreg(state, 0x04, 0x25, &val) < 0)
+			goto next;
+		n = val;
+		if (mb86a20s_read_subreg(state, 0x04, 0x26, &val) < 0)
+			goto next;
+		n = (((n << 8) | val) * 0x100100) >> 16;
+		*strength = state->strength = n ;
+		return 0;
+#endif
+next:
+		msleep(10);
+	}
+
+	return 0;
+}
+
+static int mb86a20s_read_snr(struct dvb_frontend *fe, u16 *snr)
+{
+	struct mb86a20s_state *state = fe->sec_priv;
+	int i, n, cnr;
+
+	if (time_before(jiffies, state->next_snr_check)) {
+		*snr = state->snr;
+ 		return 0;
+	}
+
+	state->next_snr_check = jiffies + msecs_to_jiffies(100);
+	*snr = state->snr = 0;
+	for (i = 0; i < 10; i++) {
+		u8 val = 0;
+		n = mb86a20s_read_reg(state, 0x0a, &val);
+		if (n < 0 || val < 2) goto next;
+		if (mb86a20s_read_reg(state, 0x45, &val) < 0) goto next;
+		/* read cnr_flag */
+		if (((val >> 6) & 1) != 0) {
+			if (mb86a20s_read_reg(state, 0x46, &val) < 0) goto next;
+			n = val;
+			if (mb86a20s_read_reg(state, 0x47, &val) < 0) goto next;
+			cnr = (n << 0x08) | val;
+			/* reset cnr_counter */
+			mb86a20s_read_reg(state, 0x45, &val);
+			val |= 0x10;
+			mb86a20s_write_reg(state, 0x45, val);
+			msleep(5);
+			val &= 0x6f; /* FIXME: or 0xef ? */
+			mb86a20s_write_reg(state, 0x45, val);
+			if (cnr > 0x4cc0) cnr = 0x4cc0;
+			n = ((0x4cc0 - cnr) * 10000) / 0x4cc0;
+			n = (65535 * n) / 10000;
+			*snr = state->snr = n;
+			return 0;
+		}
+next:
+		msleep(10);
+	}
+
+	return 0;
+}
+
+static struct tda18271_std_map mb86a20s_tda18271_config = {
+	.dvbt_6 = { .if_freq = 3300, .agc_mode = 3, .std = 4,
+		    .if_lvl = 7, .rfagc_top = 0x37, },
+};
+
+static struct tda18271_config tbs_dtb08_tda18271_config = {
+	.std_map = &mb86a20s_tda18271_config,
+	.gate = TDA18271_GATE_DIGITAL,
+};
+
+static int tbs_dtb08_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	int ret;
+	struct mb86a20s_state *state = adap->fe_adap[0].priv;
+
+	if (adap == NULL || adap->fe_adap[0].fe == NULL)
+		return -ENODEV;
+
+	if (adap->fe_adap[0].fe->ops.tuner_ops.init != NULL)
+		return 0;
+
+	state->tuner_ctrl = true;
+	ret = dvb_attach(tda18271_attach, adap->fe_adap[0].fe,
+			 TUNER_I2C_ADDR, &adap->dev->i2c_adap,
+			 &tbs_dtb08_tda18271_config) == NULL ? -ENODEV : 0;
+	state->tuner_ctrl = false;
+	return ret;
+}
+
+static struct mb86a20s_reg_subreg_config mb86a20s_config_regs[] = {
+	{ 0x3C, 0x00, 0x38 },
+	{ 0x04, 0x00, 0x001e },
+	{ 0x04, 0x0E, 0x0032 },
+	{ 0x04, 0x15, 0x55 },
+	{ 0x04, 0x16, 0x00 },
+	{ 0x28, 0x20, 0x33dd00 },
+	{ 0x28, 0x6A, 0x0002f0 },
+	{ 0x28, 0x74, 0x0001f4 },
+	{ 0x50, 0xD5, 0x00 },
+	{ 0x50, 0xD6, 0x17 },
+	{ 0x50, 0xDC, 0x3fff },
+	{ 0x50, 0xDE, 0x3fff },
+	{ 0x50, 0xE0, 0x3fff }
+};
+
+static struct mb86a20s_config mb86a20s_cfg = {
+	.demod_address = DEMOD_I2C_ADDR,
+};
+
+static int tbs_dtb08_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_frontend *fe;
+	struct dvb_usb_device *dev = adap->dev;
+	struct mb86a20s_state *state = adap->fe_adap[0].priv;
+
+	if (state == NULL) {
+		err("%s: adap->dev->priv == NULL!", __func__);
+		return -ENOMEM;
+	}
+
+	tbs_dtb08_fx2_ie_ex0(dev->udev, FX2_EX0_DISABLE);
+	tbs_dtb08_fx2_i2ctl(dev->udev, I2CTL_400Khz);
+
+	state->udev = dev->udev;
+	state->demod_addr = DEMOD_I2C_ADDR;
+	state->config_size = ARRAY_SIZE(mb86a20s_config_regs);
+	state->config_regs = mb86a20s_config_regs;
+
+	if (mb86a20s_init_regs(state) != 0) {
+		err("%s: demodulator not found!", __func__);
+		return -ENODEV;
+	}
+
+	fe = dvb_attach(mb86a20s_attach, &mb86a20s_cfg,
+			&adap->dev->i2c_adap);
+	if (!fe) return -ENODEV;
+
+	adap->fe_adap[0].fe = fe;
+
+	fe->sec_priv = state;
+	fe->ops.init = mb86a20s_init_fe;
+	fe->ops.sleep = mb86a20s_sleep;
+	fe->ops.set_frontend = mb86a20s_set_frontend;
+	fe->ops.read_status = mb86a20s_read_status;
+	fe->ops.read_signal_strength = mb86a20s_read_signal_strength;
+	fe->ops.read_snr = mb86a20s_read_snr;
+	fe->ops.get_tune_settings = mb86a20s_get_tune_settings;
+	fe->ops.get_property = mb86a20s_get_property;
+	fe->ops.i2c_gate_ctrl = mb86a20s_i2c_gate_ctrl;
+
+	if (tbs_dtb08_tuner_attach(adap) == 0) {
+		tbs_dtb08_fx2_ie_ex0(dev->udev, FX2_EX0_ENABLE);
+		return 0;
+	}
+
+	dvb_frontend_detach(fe);
+	adap->fe_adap[0].fe = NULL;
+
+	return -ENODEV;
+}
+
+static int tbs_dtb08_download_firmware(struct usb_device *dev, const struct firmware *fw)
+{
+	int i, n, count, fx2_renum;
+	u8 val, *buf;
+
+	fw_ok = 0;
+ 	if (dev == NULL || fw == NULL || fw->size <= 0) {
+		err("%s: invalid parameters.", __func__);
+		return -EINVAL;
+	}
+
+	/* stop the CPU */
+	val = 1;
+	if (tbs_dtb08_generic_write_addr(dev, 0xa0, 0xe600, &val, 1) != 1)
+		return -EINVAL;
+
+	buf = kmalloc(64 , GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	fx2_renum = 1;
+	for (n = 0; n < fw->size; n += 64) {
+		count = fw->size - n;
+		if (count > 64)
+			count = 64;
+
+		if (fx2_renum) {
+			tbs_dtb08_generic_read_addr(dev, 0xa0, n, buf, count);
+			for (i = 0; i < count; i++) {
+				if (buf[i] != fw->data[n+i]) {
+					fx2_renum = 0;
+					break;
+				}
+			}
+			if (fx2_renum) continue;
+		}
+		memcpy(buf, fw->data + n, count);
+		if (tbs_dtb08_generic_write_addr(dev, 0xa0, n, buf, count) != count) {
+			err("%s: addr=%04x: error while "
+				"transferring firmware", __func__, n);
+			goto ret_err2;
+		}
+	}
+
+	/* restart the CPU */
+	val = 0;
+	if (tbs_dtb08_generic_write_addr(dev, 0xa0, 0xe600, &val, 1) != 1) {
+		err("%s: could not restart the USB controller CPU.", __func__);
+		goto ret_err2;
+	}
+
+	msleep(200);
+
+	if (!fx2_renum) { /* ReEnumeration */
+		err("%s: ReEnumeration!", __func__);
+		goto ret_err2;
+	}
+
+	if (tbs_dtb08_i2c_read(dev, DEMOD_I2C_ADDR, 0, &val, 1) < 0)
+		goto ret_err;
+
+	if (tbs_dtb08_fx2_ie_ex0(dev, FX2_EX0_ENABLE) < 0)
+		goto ret_err;
+
+	if (tbs_dtb08_ir_code_read(dev, buf, 5) < 0)
+		goto ret_err;
+
+	kfree(buf);
+	fw_ok = 1;
+	return 0;
+
+ret_err:
+	err("%s: failed!", __func__);
+
+ret_err2:
+	kfree(buf);
+	return -EINVAL;
+}
+
+static int tbs_dtb08_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	return tbs_dtb08_led_control(adap->dev->udev, onoff);
+}
+
+static u32 tbs_dtb08_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm tbs_dtb08_i2c_algo = {
+	.master_xfer = tbs_dtb08_i2c_transfer,
+	.functionality = tbs_dtb08_i2c_func,
+};
+
+static struct usb_device_id tbs_dtb08_table[] = {
+	{ USB_DEVICE(USB_VID_TBS_734C, USB_PID_TBS_DTB08) },
+	{ }	/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, tbs_dtb08_table);
+
+static struct dvb_usb_device_properties tbs_dtb08_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+	.usb_ctrl = DEVICE_SPECIFIC,
+	.download_firmware = tbs_dtb08_download_firmware,
+	.firmware = "tbs-dtb08-fw.bin",
+	.no_reconnect = 1,
+
+	.num_adapters = 1,
+	.adapter = {{
+		.num_frontends = 1,
+		.fe = {{
+			.size_of_priv = sizeof(struct mb86a20s_state),
+			.streaming_ctrl = tbs_dtb08_streaming_ctrl,
+			.frontend_attach = tbs_dtb08_frontend_attach,
+			.tuner_attach = tbs_dtb08_tuner_attach,
+			.stream = {
+				.type = USB_BULK,
+				.count = 8,
+				.endpoint = 0x82,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}}}
+	},
+
+	.rc.legacy = {
+		.rc_interval      = 250,
+		.rc_map_table     = tbs_dtb08_rc_map_table,
+		.rc_map_size      = ARRAY_SIZE(tbs_dtb08_rc_map_table),
+		.rc_query         = tbs_dtb08_rc_query,
+	},
+
+	.i2c_algo = &tbs_dtb08_i2c_algo,
+
+	.num_device_descs = 1,
+	.devices = {
+		{
+			.name = "TBS-Tech ISDB-T USB 2.0 (DTB08)",
+			.cold_ids = { &tbs_dtb08_table[0], NULL },
+			.warm_ids = { NULL },
+		},
+	},
+};
+
+static int tbs_dtb08_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	struct dvb_usb_device *d = NULL;
+	int n, ret;
+
+	dbg_info("%s: %d, num_altsetting=%d", __func__,
+		 intf->cur_altsetting->desc.bInterfaceNumber,
+		 intf->num_altsetting);
+
+	ret = dvb_usb_device_init(intf, &tbs_dtb08_properties,
+			THIS_MODULE, &d, adapter_nr);
+
+	if (ret != 0) {
+		err("%s: failed err=%d", __func__, ret);
+		return ret;
+	}
+
+	if (d) {
+		for (n = 0; n < d->props.num_adapters; n++) {
+			struct dvb_usb_adapter *adap = &d->adapter[n];
+			if (adap && adap->fe_adap[0].fe) {
+			  ret++;
+			}
+		}
+	}
+
+	if (ret == 0) {
+		fw_ok = 0;
+		dvb_usb_device_exit(intf);
+		return -1;
+	}
+	else
+		return 0;
+}
+
+static void tbs_dtb08_usb_disconnect(struct usb_interface *intf)
+{
+	fw_ok = 0;
+	dvb_usb_device_exit(intf);
+}
+
+static struct usb_driver tbs_dtb08_driver = {
+	.name = "dvb-usb-tbsdtb08",
+	.probe = tbs_dtb08_probe,
+	.disconnect = tbs_dtb08_usb_disconnect,
+	.id_table = tbs_dtb08_table,
+};
+
+module_usb_driver(tbs_dtb08_driver);
+
+MODULE_AUTHOR("Manoel Pinheiro <pinusdtv@hotmail.com>");
+MODULE_DESCRIPTION("Driver for TBS-Tech ISDB-T USB2.0 Receiver (DTB08 Full Seg)");
+MODULE_LICENSE("GPL");
-- 
1.7.9.3


^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2012-04-26 18:35 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <BLU157-W6519D7CC9237EFB29FB24ED8230@phx.gbl>
2012-04-26 18:35 ` [media next v3.4] Add support for TBS-Tech ISDB-T Full Seg DTB08 Mauro Carvalho Chehab
2012-03-29 23:24 Manoel Pinheiro
2012-04-11  0:28 ` Mauro Carvalho Chehab

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.