You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							1334 lines
						
					
					
						
							35 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							1334 lines
						
					
					
						
							35 KiB
						
					
					
				| From 6f6cb195b8f7dbc2d0b4e31a2da4cd58bd69f6bf Mon Sep 17 00:00:00 2001 | |
| From: Athanasios Oikonomou <athoik@gmail.com> | |
| Date: Sat, 5 Mar 2016 00:29:45 +0200 | |
| Subject: [PATCH] Support TBS USB drivers | |
| 
 | |
| This patch add supports for TBS USB drivers based on the following patches: | |
| 
 | |
| https://patchwork.linuxtv.org/patch/23244/ | |
| https://patchwork.linuxtv.org/patch/23243/ | |
| https://patchwork.linuxtv.org/patch/23242/ | |
| 
 | |
| diff --git a/drivers/media/dvb-core/dvb-usb-ids.h b/drivers/media/dvb-core/dvb-usb-ids.h | |
| index c117fb3..5e0735b 100644 | |
| --- a/drivers/media/dvb-core/dvb-usb-ids.h | |
| +++ b/drivers/media/dvb-core/dvb-usb-ids.h | |
| @@ -78,6 +78,7 @@ | |
|  #define USB_VID_EVOLUTEPC			0x1e59 | |
|  #define USB_VID_AZUREWAVE			0x13d3 | |
|  #define USB_VID_TECHNISAT			0x14f7 | |
| +#define USB_VID_TENOW				0x734c | |
|   | |
|  /* Product IDs */ | |
|  #define USB_PID_ADSTECH_USB2_COLD			0xa333 | |
| @@ -413,4 +414,10 @@ | |
|  #define USB_PID_TURBOX_DTT_2000                         0xd3a4 | |
|  #define USB_PID_EVOLVEO_XTRATV_STICK                   0xa115 | |
|  #define USB_PID_WINTV_SOLOHD                            0x0264 | |
| +#define USB_PID_TENOW_TBS5910				0x5910 | |
| +#define USB_PID_TENOW_TBS5920				0x5920 | |
| +#define USB_PID_TENOW_TBS5921				0x5921 | |
| +#define USB_PID_TENOW_TBS5925				0x5925 | |
| +#define USB_PID_TENOW_TBS5928				0x5928 | |
| +#define USB_PID_TENOW_TBS5980				0x5980 | |
|  #endif | |
| diff --git a/drivers/media/dvb-frontends/cx24116.c b/drivers/media/dvb-frontends/cx24116.c | |
| index 8814f36..5ccfeb3 100644 | |
| --- a/drivers/media/dvb-frontends/cx24116.c | |
| +++ b/drivers/media/dvb-frontends/cx24116.c | |
| @@ -705,6 +705,9 @@ static int cx24116_read_status(struct dvb_frontend *fe, enum fe_status *status) | |
|  	if (lock & CX24116_HAS_SYNCLOCK) | |
|  		*status |= FE_HAS_SYNC | FE_HAS_LOCK; | |
|   | |
| +	if (state->config->set_lock_led) | |
| +		state->config->set_lock_led(fe, *status & FE_HAS_LOCK); | |
| + | |
|  	return 0; | |
|  } | |
|   | |
| @@ -1113,6 +1116,10 @@ static void cx24116_release(struct dvb_frontend *fe) | |
|  { | |
|  	struct cx24116_state *state = fe->demodulator_priv; | |
|  	dprintk("%s\n", __func__); | |
| + | |
| +	if (state->config->set_lock_led) | |
| +		state->config->set_lock_led(fe, 0); | |
| + | |
|  	kfree(state); | |
|  } | |
|   | |
| @@ -1198,6 +1205,9 @@ static int cx24116_sleep(struct dvb_frontend *fe) | |
|   | |
|  	dprintk("%s()\n", __func__); | |
|   | |
| +	if (state->config->set_lock_led) | |
| +		state->config->set_lock_led(fe, 0); | |
| + | |
|  	/* Firmware CMD 36: Power config */ | |
|  	cmd.args[0x00] = CMD_TUNERSLEEP; | |
|  	cmd.args[0x01] = 1; | |
| diff --git a/drivers/media/dvb-frontends/cx24116.h b/drivers/media/dvb-frontends/cx24116.h | |
| index f6dbabc..7cbb907 100644 | |
| --- a/drivers/media/dvb-frontends/cx24116.h | |
| +++ b/drivers/media/dvb-frontends/cx24116.h | |
| @@ -38,6 +38,9 @@ struct cx24116_config { | |
|   | |
|  	/* max bytes I2C provider can write at once */ | |
|  	u16 i2c_wr_max; | |
| + | |
| +	/* Hook for Lock LED */ | |
| +	void (*set_lock_led)(struct dvb_frontend *fe, int offon); | |
|  }; | |
|   | |
|  #if IS_REACHABLE(CONFIG_DVB_CX24116) | |
| diff --git a/drivers/media/dvb-frontends/stv0288.c b/drivers/media/dvb-frontends/stv0288.c | |
| index c93d9a4..9dcd922 100644 | |
| --- a/drivers/media/dvb-frontends/stv0288.c | |
| +++ b/drivers/media/dvb-frontends/stv0288.c | |
| @@ -382,6 +382,9 @@ static int stv0288_read_status(struct dvb_frontend *fe, enum fe_status *status) | |
|  		dprintk("stv0288 has locked\n"); | |
|  	} | |
|   | |
| +	if (state->config->set_lock_led) | |
| +		state->config->set_lock_led(fe, *status & FE_HAS_LOCK); | |
| + | |
|  	return 0; | |
|  } | |
|   | |
| @@ -416,6 +419,9 @@ static int stv0288_sleep(struct dvb_frontend *fe) | |
|  { | |
|  	struct stv0288_state *state = fe->demodulator_priv; | |
|   | |
| +	if (state->config->set_lock_led) | |
| +		state->config->set_lock_led(fe, 0); | |
| + | |
|  	stv0288_writeregI(state, 0x41, 0x84); | |
|  	state->initialised = 0; | |
|   | |
| @@ -532,6 +538,10 @@ static int stv0288_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) | |
|  static void stv0288_release(struct dvb_frontend *fe) | |
|  { | |
|  	struct stv0288_state *state = fe->demodulator_priv; | |
| + | |
| +	if (state->config->set_lock_led) | |
| +		state->config->set_lock_led(fe, 0); | |
| + | |
|  	kfree(state); | |
|  } | |
|   | |
| diff --git a/drivers/media/dvb-frontends/stv0288.h b/drivers/media/dvb-frontends/stv0288.h | |
| index b58603c..01d8481 100644 | |
| --- a/drivers/media/dvb-frontends/stv0288.h | |
| +++ b/drivers/media/dvb-frontends/stv0288.h | |
| @@ -40,6 +40,9 @@ struct stv0288_config { | |
|  	int min_delay_ms; | |
|   | |
|  	int (*set_ts_params)(struct dvb_frontend *fe, int is_punctured); | |
| + | |
| +	/* Hook for Lock LED */ | |
| +	void (*set_lock_led)(struct dvb_frontend *fe, int offon); | |
|  }; | |
|   | |
|  #if IS_REACHABLE(CONFIG_DVB_STV0288) | |
| diff --git a/drivers/media/dvb-frontends/stv090x.c b/drivers/media/dvb-frontends/stv090x.c | |
| index 25bdf6e..ce99b9d 100644 | |
| --- a/drivers/media/dvb-frontends/stv090x.c | |
| +++ b/drivers/media/dvb-frontends/stv090x.c | |
| @@ -3553,6 +3553,9 @@ static int stv090x_read_status(struct dvb_frontend *fe, enum fe_status *status) | |
|  		break; | |
|  	} | |
|   | |
| +	if (state->config->set_lock_led) | |
| +		state->config->set_lock_led(fe, *status & FE_HAS_LOCK); | |
| + | |
|  	return 0; | |
|  } | |
|   | |
| @@ -3901,6 +3904,9 @@ static int stv090x_sleep(struct dvb_frontend *fe) | |
|  	u32 reg; | |
|  	u8 full_standby = 0; | |
|   | |
| +	if (state->config->set_lock_led) | |
| +		state->config->set_lock_led(fe, 0); | |
| + | |
|  	if (stv090x_i2c_gate_ctrl(state, 1) < 0) | |
|  		goto err; | |
|   | |
| @@ -4132,6 +4138,9 @@ static void stv090x_release(struct dvb_frontend *fe) | |
|  { | |
|  	struct stv090x_state *state = fe->demodulator_priv; | |
|   | |
| +	if (state->config->set_lock_led) | |
| +		state->config->set_lock_led(fe, 0); | |
| + | |
|  	state->internal->num_used--; | |
|  	if (state->internal->num_used <= 0) { | |
|   | |
| diff --git a/drivers/media/dvb-frontends/stv090x.h b/drivers/media/dvb-frontends/stv090x.h | |
| index 012e55e..545f43a 100644 | |
| --- a/drivers/media/dvb-frontends/stv090x.h | |
| +++ b/drivers/media/dvb-frontends/stv090x.h | |
| @@ -89,6 +89,8 @@ struct stv090x_config { | |
|   | |
|  	bool diseqc_envelope_mode; | |
|   | |
| +	/* Hook for Lock LED */ | |
| +	void (*set_lock_led) (struct dvb_frontend *fe, int offon); | |
|  	int (*tuner_init)(struct dvb_frontend *fe); | |
|  	int (*tuner_sleep)(struct dvb_frontend *fe); | |
|  	int (*tuner_set_mode)(struct dvb_frontend *fe, enum tuner_mode mode); | |
| diff --git a/drivers/media/dvb-frontends/tda10071.h b/drivers/media/dvb-frontends/tda10071.h | |
| index 8f18402..891e007 100644 | |
| --- a/drivers/media/dvb-frontends/tda10071.h | |
| +++ b/drivers/media/dvb-frontends/tda10071.h | |
| @@ -49,6 +49,9 @@ struct tda10071_platform_data { | |
|  	u8 pll_multiplier; | |
|  	u8 tuner_i2c_addr; | |
|   | |
| +	/* Hook for Lock LED */ | |
| +	void (*set_lock_led)(struct dvb_frontend *fe, int offon); | |
| + | |
|  	struct dvb_frontend* (*get_dvb_frontend)(struct i2c_client *); | |
|  }; | |
|   | |
| diff --git a/drivers/media/usb/dvb-usb/Kconfig b/drivers/media/usb/dvb-usb/Kconfig | |
| index 128eee6..49c34f9 100644 | |
| --- a/drivers/media/usb/dvb-usb/Kconfig | |
| +++ b/drivers/media/usb/dvb-usb/Kconfig | |
| @@ -331,3 +331,23 @@ config DVB_USB_TECHNISAT_USB2 | |
|  	select DVB_STV6110x if MEDIA_SUBDRV_AUTOSELECT | |
|  	help | |
|  	  Say Y here to support the Technisat USB2 DVB-S/S2 device | |
| + | |
| +config DVB_USB_TBS | |
| +	tristate "TurboSight DVB-S/S2 USB2.0 support" | |
| +	depends on DVB_USB | |
| +	select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT | |
| +	select DVB_STV090x if MEDIA_SUBDRV_AUTOSELECT | |
| +	select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT | |
| +	select DVB_CX24116 if MEDIA_SUBDRV_AUTOSELECT | |
| +	select DVB_TDA10071 if MEDIA_SUBDRV_AUTOSELECT | |
| +	select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT | |
| +	select DVB_STV0288 if MEDIA_SUBDRV_AUTOSELECT | |
| +	select DVB_STB6000 if MEDIA_SUBDRV_AUTOSELECT | |
| +	help | |
| +	  Say Y here to support TurboSight (TBS) DVB-S/S2 USB2.0 receivers. | |
| +	  Required firmware can be found at http://www.tbsdtv.com/download/ | |
| +	  The tda10071 (TBS5921) firmware can be downloaded by executing: | |
| +	  Documentation/dvb/get_dvb_firmware tda10071 | |
| + | |
| +	  Supported devices are: | |
| +	  TBS5980 TBS5928 TBS5925 TBS5921 TBS5920 TBS5910 | |
| diff --git a/drivers/media/usb/dvb-usb/Makefile b/drivers/media/usb/dvb-usb/Makefile | |
| index acdd1ef..cb00137 100644 | |
| --- a/drivers/media/usb/dvb-usb/Makefile | |
| +++ b/drivers/media/usb/dvb-usb/Makefile | |
| @@ -79,6 +79,9 @@ obj-$(CONFIG_DVB_USB_AZ6027) += dvb-usb-az6027.o | |
|  dvb-usb-technisat-usb2-objs := technisat-usb2.o | |
|  obj-$(CONFIG_DVB_USB_TECHNISAT_USB2) += dvb-usb-technisat-usb2.o | |
|   | |
| +dvb-usb-tbsusb-objs := tbs-usb.o | |
| +obj-$(CONFIG_DVB_USB_TBS) += dvb-usb-tbsusb.o | |
| + | |
|  ccflags-y += -I$(srctree)/drivers/media/dvb-core | |
|  ccflags-y += -I$(srctree)/drivers/media/dvb-frontends/ | |
|  # due to tuner-xc3028 | |
| diff --git a/drivers/media/usb/dvb-usb/tbs-usb.c b/drivers/media/usb/dvb-usb/tbs-usb.c | |
| new file mode 100644 | |
| index 0000000..f142be3 | |
| --- /dev/null | |
| +++ b/drivers/media/usb/dvb-usb/tbs-usb.c | |
| @@ -0,0 +1,1075 @@ | |
| +/* | |
| + * TBS 5980/5928/5925/5921/5920/5910 DVB-S/S2 driver | |
| + * | |
| + * Copyright (c) 2008 Bob Liu (Bob@Turbosight.com) | |
| + *                    Igor M. Liplianin (liplianin@me.by) | |
| + * Copyright (c) 2009 Konstantin Dimitrov <kosio.dimitrov@gmail.com> | |
| + * Copyright (c) 2014 Andreas Steinmetz <ast@domdv.de> | |
| + *                    Lock LED and TBS5921 stuff shamelessly taken from | |
| + *                    CrazyCat's Bitbucket repository | |
| + *                    TBS5925 Open Source version shamelessly taken from | |
| + *                    UpdateLee's Bitbucket repository | |
| + * | |
| + * 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. | |
| + * | |
| + */ | |
| + | |
| +#include <linux/version.h> | |
| +#include "tbs-usb.h" | |
| + | |
| +#include "stv6110x.h" | |
| +#include "stv090x.h" | |
| +#include "stb6100.h" | |
| +#include "stb6100_cfg.h" | |
| + | |
| +#include "cx24116.h" | |
| + | |
| +#include "stv0299.h" | |
| +#include "stv0288.h" | |
| +#include "stb6000.h" | |
| + | |
| +#include "tda10071.h" | |
| + | |
| +#include "dvb_ca_en50221.h" | |
| + | |
| +struct tbsusbci_state { | |
| +	u8 buf[20]; | |
| +	struct dvb_ca_en50221 ca; | |
| +	struct mutex ca_mutex; | |
| +}; | |
| + | |
| +struct tbsusb_state { | |
| +	u8 buf[20]; | |
| +}; | |
| + | |
| +static int dvb_usb_tbsusb_debug; | |
| +module_param_named(debug, dvb_usb_tbsusb_debug, int, 0644); | |
| +MODULE_PARM_DESC(debug, "set debugging level (1=info 2=xfer (or-able))." | |
| +							DVB_USB_DEBUG_STATUS); | |
| + | |
| +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); | |
| + | |
| +static struct rc_map_table tbsusb_rc_keys[] = { | |
| +	{ 0xff84, KEY_POWER2},		/* power */ | |
| +	{ 0xff94, KEY_MUTE},		/* mute */ | |
| +	{ 0xff87, KEY_1}, | |
| +	{ 0xff86, KEY_2}, | |
| +	{ 0xff85, KEY_3}, | |
| +	{ 0xff8b, KEY_4}, | |
| +	{ 0xff8a, KEY_5}, | |
| +	{ 0xff89, KEY_6}, | |
| +	{ 0xff8f, KEY_7}, | |
| +	{ 0xff8e, KEY_8}, | |
| +	{ 0xff8d, KEY_9}, | |
| +	{ 0xff92, KEY_0}, | |
| +	{ 0xff96, KEY_CHANNELUP},	/* ch+ */ | |
| +	{ 0xff91, KEY_CHANNELDOWN},	/* ch- */ | |
| +	{ 0xff93, KEY_VOLUMEUP},	/* vol+ */ | |
| +	{ 0xff8c, KEY_VOLUMEDOWN},	/* vol- */ | |
| +	{ 0xff83, KEY_RECORD},		/* rec */ | |
| +	{ 0xff98, KEY_PAUSE},		/* pause, yellow */ | |
| +	{ 0xff99, KEY_OK},		/* ok */ | |
| +	{ 0xff9a, KEY_CAMERA},		/* snapshot */ | |
| +	{ 0xff81, KEY_UP}, | |
| +	{ 0xff90, KEY_LEFT}, | |
| +	{ 0xff82, KEY_RIGHT}, | |
| +	{ 0xff88, KEY_DOWN}, | |
| +	{ 0xff95, KEY_FAVORITES},	/* blue */ | |
| +	{ 0xff97, KEY_SUBTITLE},	/* green */ | |
| +	{ 0xff9d, KEY_ZOOM}, | |
| +	{ 0xff9f, KEY_EXIT}, | |
| +	{ 0xff9e, KEY_MENU}, | |
| +	{ 0xff9c, KEY_EPG}, | |
| +	{ 0xff80, KEY_PREVIOUS},	/* red */ | |
| +	{ 0xff9b, KEY_MODE}, | |
| +	{ 0xffdd, KEY_TV }, | |
| +	{ 0xffde, KEY_PLAY }, | |
| +	{ 0xffdc, KEY_STOP }, | |
| +	{ 0xffdb, KEY_REWIND }, | |
| +	{ 0xffda, KEY_FASTFORWARD }, | |
| +	{ 0xffd9, KEY_PREVIOUS },	/* replay */ | |
| +	{ 0xffd8, KEY_NEXT },		/* skip */ | |
| +	{ 0xffd1, KEY_NUMERIC_STAR }, | |
| +	{ 0xffd2, KEY_NUMERIC_POUND }, | |
| +	{ 0xffd4, KEY_DELETE },		/* clear */ | |
| +}; | |
| + | |
| +static int tbsusb_op_r_unlocked(struct usb_device *dev, int num, u8 request, | |
| +					u16 value, u16 index, u8 *data, int len) | |
| +{ | |
| +	int ret; | |
| + | |
| +	ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), request, | |
| +		USB_TYPE_VENDOR | USB_DIR_IN, value, index, data, len, 2000); | |
| + | |
| +	/* oh well, rc poll returns -EOVERFLOW but also the required data */ | |
| +	if (ret == -EOVERFLOW && request == 0xb8) | |
| +		ret = 0; | |
| +	else if (ret < 0) { | |
| +		warn("usb read 0x%02x/%d from adapter %d failed. (%d)", | |
| +							request, len, num, ret); | |
| +		ret = -EIO; | |
| +	} else | |
| +		ret = 0; | |
| + | |
| +	deb_xfer("read: adap.: %d, req. %02x, val: %04x, ind: %04x, buffer: ", | |
| +		num, request, value, index); | |
| +	debug_dump(data, len, deb_xfer); | |
| + | |
| +	return ret; | |
| +} | |
| + | |
| +static int tbsusb_op_w_unlocked(struct usb_device *dev, int num, u8 request, | |
| +					u16 value, u16 index, u8 *data, int len) | |
| +{ | |
| +	int ret; | |
| + | |
| +	deb_xfer("write: adap.: %d, req. %02x, val: %04x, ind: %04x, buffer: ", | |
| +		num, request, value, index); | |
| +	debug_dump(data, len, deb_xfer); | |
| + | |
| +	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), request, | |
| +		USB_TYPE_VENDOR | USB_DIR_OUT, value, index, data, len, 2000); | |
| + | |
| +	if (ret != len) { | |
| +		warn("usb write 0x%02x/%d to adapter %d failed. (%d)", | |
| +							request, len, num, ret); | |
| +		ret = -EIO; | |
| +	} else | |
| +		ret = 0; | |
| + | |
| +	return ret; | |
| +} | |
| + | |
| +static int tbsusb_load_firmware(struct usb_device *dev, | |
| +			const struct firmware *frmwr) | |
| +{ | |
| +	u8 *b; | |
| +	int ret, i, j; | |
| + | |
| +	if (!dev || !frmwr) | |
| +		return -ENODEV; | |
| + | |
| +	b = kmalloc(0x40, GFP_KERNEL); | |
| +	if (!b) | |
| +		return -ENOMEM; | |
| + | |
| +	/*stop the CPU*/ | |
| +	b[0] = 1; | |
| +	ret = tbsusb_op_w_unlocked(dev, -1, 0xa0, 0x7f92, 0, b, 1); | |
| +	if (!ret) | |
| +		ret = tbsusb_op_w_unlocked(dev, -1, 0xa0, 0xe600, 0, b, 1); | |
| +	if (ret) { | |
| +		err("could not stop the USB controller CPU."); | |
| +		goto err; | |
| +	} | |
| + | |
| +	for (i = 0; i < frmwr->size; i += 0x40) { | |
| +		j = i & 0x3f; | |
| +		if (!j) | |
| +			j = 0x40; | |
| +		memcpy(b, (u8 *) frmwr->data + i, j); | |
| +		ret = tbsusb_op_w_unlocked(dev, -1, 0xa0, i, 0, b, j); | |
| +		if (ret) { | |
| +			err("error while transferring firmware."); | |
| +			goto err; | |
| +		} | |
| +	} | |
| + | |
| +	/* restart the CPU */ | |
| +	b[0] = 0; | |
| +	ret = tbsusb_op_w_unlocked(dev, -1, 0xa0, 0x7f92, 0, b, 1); | |
| +	if (!ret) | |
| +		ret = tbsusb_op_w_unlocked(dev, -1, 0xa0, 0xe600, 0, b, 1); | |
| +	if (ret) { | |
| +		err("could not restart the USB controller CPU."); | |
| +		goto err; | |
| +	} | |
| + | |
| +	msleep(100); | |
| + | |
| +err:	kfree(b); | |
| +	return ret; | |
| +} | |
| + | |
| +/* without copying the data back and forth usb transfers randomly fail... */ | |
| + | |
| +static int tbsusb_op_wwr(struct dvb_usb_device *d, u8 wrq, u8 wrrq, int wwait, | |
| +				int wrwait, u8 *wdata, int wlen, u8 *wrdata, | |
| +				int wrlen, int wrop) | |
| +{ | |
| +	struct tbsusb_state *s; | |
| +	u8 *b; | |
| +	int ret, len, num; | |
| + | |
| +	if (d && d->udev && d->priv) | |
| +		s = (struct tbsusb_state *)d->priv; | |
| +	else | |
| +		return -ENODEV; | |
| + | |
| +	if (wrlen > wlen) | |
| +		len = wrlen; | |
| +	else | |
| +		len = wlen; | |
| + | |
| +	if (len > sizeof(s->buf)) { | |
| +		b = kmalloc(len, GFP_KERNEL); | |
| +		if (!b) | |
| +			return -ENOMEM; | |
| +	} else | |
| +		b = s->buf; | |
| + | |
| +	if (d->adapter[0].fe_adap[0].fe && d->adapter[0].fe_adap[0].fe->dvb) | |
| +		num = d->adapter[0].fe_adap[0].fe->dvb->num; | |
| +	else | |
| +		num = -1; | |
| + | |
| +	mutex_lock(&d->usb_mutex); | |
| +	if (wdata) { | |
| +		memcpy(b, wdata, wlen); | |
| +		ret = tbsusb_op_w_unlocked(d->udev, num, wrq, 0, 0, b, wlen); | |
| +		if (ret) | |
| +			goto err; | |
| +		if (wwait) | |
| +			usleep_range(wwait, wwait+1000); | |
| +	} | |
| +	if (wrdata) { | |
| +		if (wrop) { | |
| +			memcpy(b, wrdata, wrlen); | |
| +			ret = tbsusb_op_w_unlocked(d->udev, num, wrrq, 0, 0, b, | |
| +									wrlen); | |
| +		} else | |
| +			ret = tbsusb_op_r_unlocked(d->udev, num, wrrq, 0, 0, b, | |
| +									wrlen); | |
| +		if (ret) | |
| +			goto err; | |
| +		if (!wrop) | |
| +			memcpy(wrdata, b, wrlen); | |
| +		if (wrwait) | |
| +			usleep_range(wrwait, wrwait+1000); | |
| +	} | |
| +err:	mutex_unlock(&d->usb_mutex); | |
| + | |
| +	if (len > sizeof(s->buf)) | |
| +		kfree(b); | |
| + | |
| +	return ret; | |
| +} | |
| + | |
| +static int tbsusb_op_ww(struct dvb_usb_device *d, u8 wrq1, u8 wrq2, int wwait1, | |
| +				int wwait2, u8 *wdata1, int wlen1, u8 *wdata2, | |
| +				int wlen2) | |
| +{ | |
| +	return tbsusb_op_wwr(d, wrq1, wrq2, wwait1, wwait2, wdata1, wlen1, | |
| +							wdata2, wlen2, 1); | |
| +} | |
| + | |
| +static int tbsusb_op_wr(struct dvb_usb_device *d, u8 wrq, u8 rrq, int wwait, | |
| +				int rwait, u8 *wdata, int wlen, u8 *rdata, | |
| +				int rlen) | |
| +{ | |
| +	return tbsusb_op_wwr(d, wrq, rrq, wwait, rwait, wdata, wlen, rdata, | |
| +								rlen, 0); | |
| +} | |
| + | |
| +static int tbsusb_op_w(struct dvb_usb_device *d, u8 req, int uwait, | |
| +							u8 *data, int len) | |
| +{ | |
| +	return tbsusb_op_wwr(d, req, 0x00, uwait, 0, data, len, NULL, 0, 0); | |
| +} | |
| + | |
| +static int tbsusb_op_r(struct dvb_usb_device *d, u8 req, int uwait, | |
| +							u8 *data, int len) | |
| +{ | |
| +	return tbsusb_op_wwr(d, 0x00, req, 0, uwait, NULL, 0, data, len, 0); | |
| +} | |
| + | |
| +static int tbsusb_read_mac_address(struct dvb_usb_device *d, u8 mac[6]) | |
| +{ | |
| +	int i, ret; | |
| +	u8 buf[3]; | |
| + | |
| +	for (i = 0; i < 6; i++) { | |
| +		buf[0] = 1;		/* length */ | |
| +		buf[1] = 0xa0;		/* eeprom addr */ | |
| +		buf[2] = i + 16;	/* register (0-255) */ | |
| +		ret = tbsusb_op_wr(d, 0x90, 0x91, 0, 0, buf, 3, &mac[i], 1); | |
| +		if (ret) { | |
| +			err("eeprom read failed."); | |
| +			return ret; | |
| +		} | |
| +	} | |
| +	return 0; | |
| +}; | |
| + | |
| +static int tbsusb_rc_query(struct dvb_usb_device *d, u32 *event, int *state) | |
| +{ | |
| +	int i; | |
| +	u8 buf[4]; | |
| + | |
| +	*state = REMOTE_NO_KEY_PRESSED; | |
| + | |
| +	i = tbsusb_op_r(d, 0xb8, 3000, buf, 4); | |
| +	if (i) | |
| +		goto out; | |
| + | |
| +	for (i = 0; i < ARRAY_SIZE(tbsusb_rc_keys); i++) { | |
| +		if (rc5_data(&tbsusb_rc_keys[i]) == buf[3]) { | |
| +			*state = REMOTE_KEY_PRESSED; | |
| +			*event = tbsusb_rc_keys[i].keycode; | |
| +			break; | |
| +		} | |
| +	} | |
| + | |
| +out:	return 0; | |
| +} | |
| + | |
| +static int tbsusb_set_pin(struct dvb_frontend *fe, u8 *what) | |
| +{ | |
| +	struct dvb_usb_device *d = NULL; | |
| + | |
| +	if (fe && fe->dvb && fe->dvb->priv) | |
| +		d = ((struct dvb_usb_adapter *)fe->dvb->priv)->dev; | |
| +	if (!d) | |
| +		return -ENODEV; | |
| + | |
| +	return tbsusb_op_w(d, 0x8a, 0, what, 2); | |
| +} | |
| + | |
| +static int tbsusb_set_voltage(struct dvb_frontend *fe, | |
| +						fe_sec_voltage_t voltage) | |
| +{ | |
| +	static u8 command_13v[2] = {0x03, 0x00}; | |
| +	static u8 command_18v[2] = {0x03, 0x01}; | |
| + | |
| +	return tbsusb_set_pin(fe, | |
| +			voltage == SEC_VOLTAGE_18 ? command_18v : command_13v); | |
| +} | |
| + | |
| +static void tbsusb_led_ctrl(struct dvb_frontend *fe, int onoff) | |
| +{ | |
| +	static u8 led_off[2] = {0x05, 0x00}; | |
| +	static u8 led_on[2]  = {0x05, 0x01}; | |
| + | |
| +	tbsusb_set_pin(fe, onoff ? led_on : led_off); | |
| +} | |
| + | |
| +static int tbsusb_i2c_transfer(struct i2c_adapter *adap, | |
| +					struct i2c_msg msg[], int num) | |
| +{ | |
| +	struct dvb_usb_device *d = i2c_get_adapdata(adap); | |
| +	int ret = -EINVAL, j = 3000, i; | |
| +	u8 buf[20]; | |
| + | |
| +	if (!d || !d->desc) | |
| +		return -ENODEV; | |
| + | |
| +	switch (num<<16 | d->desc->cold_ids[0]->idProduct) { | |
| +	case 0x20000|USB_PID_TENOW_TBS5980: | |
| +	case 0x20000|USB_PID_TENOW_TBS5925: | |
| +	case 0x20000|USB_PID_TENOW_TBS5920: | |
| +		buf[0] = msg[1].len;		/* length */ | |
| +		buf[1] = msg[0].addr<<1;	/* demod addr */ | |
| +		/* register */ | |
| +		buf[2] = msg[0].buf[0]; | |
| +		buf[3] = msg[0].buf[1]; | |
| + | |
| +		ret = tbsusb_op_wr(d, 0x92, 0x91, 0, 0, buf, 4, msg[1].buf, | |
| +								msg[1].len); | |
| +		break; | |
| +	case 0x20000|USB_PID_TENOW_TBS5928: | |
| +	case 0x20000|USB_PID_TENOW_TBS5921: | |
| +		/* read */ | |
| +		buf[0] = msg[0].len;		/* length */ | |
| +		goto wrop; | |
| +	case 0x20000|USB_PID_TENOW_TBS5910: | |
| +		/* read */ | |
| +		buf[0] = msg[1].len;		/* length */ | |
| +wrop:		buf[1] = msg[0].addr<<1;	/* demod addr */ | |
| +		/* register */ | |
| +		buf[2] = msg[0].buf[0]; | |
| + | |
| +		ret = tbsusb_op_wr(d, 0x90, 0x91, 5000, 0, buf, 3, msg[1].buf, | |
| +								msg[1].len); | |
| +		break; | |
| +	case 0x10000|USB_PID_TENOW_TBS5980: | |
| +	case 0x10000|USB_PID_TENOW_TBS5925: | |
| +	case 0x10000|USB_PID_TENOW_TBS5920: | |
| +		switch (msg[0].addr) { | |
| +		case 0x6a: | |
| +		case 0x68: | |
| +		case 0x61: | |
| +		case 0x60: | |
| +			if (!msg[0].flags) { | |
| +				j = 0; | |
| +				goto wraddr; | |
| +			} else { | |
| +				buf[0] = msg[0].len;		/* length */ | |
| +				buf[1] = msg[0].addr<<1;	/* addr */ | |
| +				buf[2] = 0x00; | |
| +				ret = tbsusb_op_wr(d, 0x90, 0x91, 0, 0, buf, 3, | |
| +							msg[0].buf, msg[0].len); | |
| +			} | |
| +			break; | |
| +		} | |
| +		break; | |
| +	case 0x10000|USB_PID_TENOW_TBS5928: | |
| +		switch (msg[0].addr) { | |
| +		case 0x55: | |
| +			if (msg[0].buf[0] == 0xf7) { | |
| +				/* firmware */ | |
| +				/* Write in small blocks */ | |
| +				buf[2] = 0xf7; | |
| +				goto wrfw; | |
| +			} else { | |
| +				/* write to register */ | |
| +				j = 0; | |
| +				goto wraddr; | |
| +			} | |
| +			break; | |
| +		case 0x60: | |
| +			/* write to register */ | |
| +			goto wraddr; | |
| +		} | |
| +		break; | |
| +	case 0x10000|USB_PID_TENOW_TBS5921: | |
| +		switch (msg[0].addr) { | |
| +		case 0x55: | |
| +			if (msg[0].buf[0] == 0xfa) { | |
| +				/* firmware */ | |
| +				/* Write in small blocks */ | |
| +				buf[2] = 0xfa; | |
| +wrfw:				buf[0] = 0x12; | |
| +				buf[1] = 0xaa; | |
| +				j = msg[0].len - 1; | |
| +				i = 1; | |
| +				do { | |
| +					memcpy(buf + 3, msg[0].buf + i, | |
| +							j > 16 ? 16 : j); | |
| +					ret = tbsusb_op_w(d, 0x80, 0, buf, | |
| +							j > 16 ? 19 : j+3); | |
| +					i += 16; | |
| +					j -= 16; | |
| +				} while (!ret && j > 0); | |
| +			} else { | |
| +				/* write to register */ | |
| +				j = 0; | |
| +				goto wraddr; | |
| +			} | |
| +			break; | |
| +		case 0x60: | |
| +			/* write to register */ | |
| +			goto wraddr; | |
| +			break; | |
| +		} | |
| +		break; | |
| +	case 0x10000|USB_PID_TENOW_TBS5910: | |
| +		switch (msg[0].addr) { | |
| +		case 0x61: | |
| +		case 0x60: | |
| +			if (!msg[0].flags) { | |
| +				/* write to tuner pll */ | |
| +				goto wraddr; | |
| +			} | |
| +			break; | |
| +		case 0x68: | |
| +			/* write to stv0299 register */ | |
| +wraddr:			if (msg[0].len+2 > sizeof(buf)) | |
| +				break; | |
| +			buf[0] = msg[0].len+1;		/* length */ | |
| +			buf[1] = msg[0].addr<<1;	/* addr */ | |
| +			/* register */ | |
| +			memcpy(buf+2, msg[0].buf, msg[0].len); | |
| +			ret = tbsusb_op_w(d, 0x80, j, buf, msg[0].len+2); | |
| +			break; | |
| +		} | |
| +		break; | |
| +	} | |
| + | |
| +	if (ret) | |
| +		return ret; | |
| +	return num; | |
| +} | |
| + | |
| +static u32 tbsusb_i2c_func(struct i2c_adapter *adapter) | |
| +{ | |
| +	return I2C_FUNC_I2C; | |
| +} | |
| + | |
| +static struct i2c_algorithm tbsusb_i2c_algo = { | |
| +	.master_xfer   = tbsusb_i2c_transfer, | |
| +	.functionality = tbsusb_i2c_func, | |
| +}; | |
| + | |
| +static int tbsusb_ci_setup(struct dvb_ca_en50221 *ca, struct dvb_usb_device **d, | |
| +					struct tbsusbci_state **state, int slot) | |
| +{ | |
| +	if (slot) | |
| +		return -EINVAL; | |
| +	if (ca && ca->data) { | |
| +		*d = (struct dvb_usb_device *)ca->data; | |
| +		if (*d && (*d)->udev && (*d)->priv) { | |
| +			*state = (struct tbsusbci_state *)(*d)->priv; | |
| +			mutex_lock(&(*state)->ca_mutex); | |
| +			return 0; | |
| +		} | |
| +	} | |
| +	return -ENODEV; | |
| +} | |
| + | |
| +static void tbsusb_ci_finish(struct tbsusbci_state *state) | |
| +{ | |
| +	mutex_unlock(&state->ca_mutex); | |
| +} | |
| + | |
| +static int tbsusb_slot_reset(struct dvb_ca_en50221 *ca, int slot) | |
| +{ | |
| +	static u8 msg1[2] = {0x01, 0x00}; | |
| +	static u8 msg2[2] = {0x01, 0x01}; | |
| +	struct dvb_usb_device *d; | |
| +	struct tbsusbci_state *state; | |
| +	int ret; | |
| + | |
| +	ret = tbsusb_ci_setup(ca, &d, &state, slot); | |
| +	if (ret) | |
| +		return ret; | |
| + | |
| +	ret = tbsusb_op_ww(d, 0xa6, 0xa6, 5000, 0, msg1, 2, msg2, 2); | |
| +	if (!ret) | |
| +		msleep(1400); | |
| + | |
| +	tbsusb_ci_finish(state); | |
| + | |
| +	return ret; | |
| +} | |
| + | |
| +static int tbsusb_cam_read(struct dvb_ca_en50221 *ca, int slot, int address, | |
| +								u8 where) | |
| +{ | |
| +	struct dvb_usb_device *d; | |
| +	struct tbsusbci_state *state; | |
| +	int ret; | |
| +	u8 buf[4], rbuf[1]; | |
| + | |
| +	buf[0] = 1; | |
| +	buf[1] = where; | |
| +	buf[2] = (address >> 8) & 0x0f; | |
| +	buf[3] = address; | |
| + | |
| +	ret = tbsusb_ci_setup(ca, &d, &state, slot); | |
| +	if (ret) | |
| +		return ret; | |
| + | |
| +	ret = tbsusb_op_wr(d, 0xa4, 0xa5, 0, 0, buf, 4, rbuf, 1); | |
| + | |
| +	tbsusb_ci_finish(state); | |
| + | |
| +	if (ret) | |
| +		return ret; | |
| + | |
| +	return rbuf[0]; | |
| +} | |
| + | |
| +static int tbsusb_cam_write(struct dvb_ca_en50221 *ca, int slot, int address, | |
| +							u8 value, u8 where) | |
| +{ | |
| +	struct dvb_usb_device *d; | |
| +	struct tbsusbci_state *state; | |
| +	int ret; | |
| +	u8 buf[5]; | |
| + | |
| +	buf[0] = 1; | |
| +	buf[1] = where; | |
| +	buf[2] = (address >> 8) & 0x0f; | |
| +	buf[3] = address; | |
| +	buf[4] = value; | |
| + | |
| +	ret = tbsusb_ci_setup(ca, &d, &state, slot); | |
| +	if (ret) | |
| +		return ret; | |
| + | |
| +	ret = tbsusb_op_w(d, 0xa2, 0, buf, 5); | |
| + | |
| +	tbsusb_ci_finish(state); | |
| + | |
| +	if (ret) | |
| +		return ret; | |
| + | |
| +	return 0; | |
| +} | |
| + | |
| +static int tbsusb_read_attribute_mem(struct dvb_ca_en50221 *ca, | |
| +							int slot, int address) | |
| +{ | |
| +	return tbsusb_cam_read(ca, slot, address, 0); | |
| +} | |
| + | |
| +static int tbsusb_write_attribute_mem(struct dvb_ca_en50221 *ca, | |
| +						int slot, int address, u8 value) | |
| +{ | |
| +	return tbsusb_cam_write(ca, slot, address, value, 0); | |
| +} | |
| + | |
| +static int tbsusb_read_cam_control(struct dvb_ca_en50221 *ca, int slot, | |
| +								u8 address) | |
| +{ | |
| +	return tbsusb_cam_read(ca, slot, address, 1); | |
| +} | |
| + | |
| +static int tbsusb_write_cam_control(struct dvb_ca_en50221 *ca, int slot, | |
| +							u8 address, u8 value) | |
| +{ | |
| +	return tbsusb_cam_write(ca, slot, address, value, 1); | |
| +} | |
| + | |
| +static int tbsusb_set_video_port(struct dvb_ca_en50221 *ca, | |
| +							int slot, int enable) | |
| +{ | |
| +	struct dvb_usb_device *d; | |
| +	struct tbsusbci_state *state; | |
| +	int ret; | |
| +	u8 buf[2]; | |
| + | |
| +	buf[0] = 2; | |
| +	buf[1] = enable; | |
| + | |
| +	ret = tbsusb_ci_setup(ca, &d, &state, slot); | |
| +	if (ret) | |
| +		return ret; | |
| + | |
| +	ret = tbsusb_op_w(d, 0xa6, 0, buf, 2); | |
| + | |
| +	tbsusb_ci_finish(state); | |
| + | |
| +	if (ret) { | |
| +		err("CI not %sabled.", enable ? "en" : "dis"); | |
| +		return ret; | |
| +	} | |
| + | |
| +	deb_info("CI %sabled.", enable ? "en" : "dis"); | |
| +	return 0; | |
| +} | |
| + | |
| +static int tbsusb_slot_shutdown(struct dvb_ca_en50221 *ca, int slot) | |
| +{ | |
| +	return tbsusb_set_video_port(ca, slot, 0); | |
| +} | |
| + | |
| +static int tbsusb_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot) | |
| +{ | |
| +	return tbsusb_set_video_port(ca, slot, 1); | |
| +} | |
| + | |
| +static int tbsusb_poll_slot_status(struct dvb_ca_en50221 *ca, | |
| +							int slot, int open) | |
| +{ | |
| +	struct dvb_usb_device *d; | |
| +	struct tbsusbci_state *state; | |
| +	int ret; | |
| +	u8 buf[3]; | |
| + | |
| +	ret = tbsusb_ci_setup(ca, &d, &state, slot); | |
| +	if (ret) | |
| +		return 0; | |
| + | |
| +	ret = tbsusb_op_r(d, 0xa8, 0, buf, 3); | |
| + | |
| +	tbsusb_ci_finish(state); | |
| + | |
| +	if (ret || buf[0] != 0xa9 || buf[1] != 1 || buf[2] != 1) | |
| +		return 0; | |
| + | |
| +	return DVB_CA_EN50221_POLL_CAM_PRESENT | DVB_CA_EN50221_POLL_CAM_READY; | |
| +} | |
| + | |
| +static void tbsusbci_uninit(struct dvb_usb_device *d) | |
| +{ | |
| +	struct tbsusbci_state *state; | |
| + | |
| +	if (d && d->priv) | |
| +		state = (struct tbsusbci_state *)d->priv; | |
| +	else | |
| +		return; | |
| + | |
| +	if (!state || !state->ca.data) | |
| +		return; | |
| + | |
| +	dvb_ca_en50221_release(&state->ca); | |
| +	memset(&state->ca, 0, sizeof(state->ca)); | |
| +} | |
| + | |
| +static int tbsusbci_init(struct dvb_usb_adapter *a) | |
| +{ | |
| +	struct tbsusbci_state *state; | |
| +	int ret = -ENODEV; | |
| + | |
| +	if (a && a->dev && a->dev->priv) | |
| +		state = (struct tbsusbci_state *)a->dev->priv; | |
| +	else | |
| +		goto err; | |
| + | |
| +	mutex_init(&state->ca_mutex); | |
| + | |
| +	state->ca.owner = THIS_MODULE; | |
| +	state->ca.read_attribute_mem = tbsusb_read_attribute_mem; | |
| +	state->ca.write_attribute_mem = tbsusb_write_attribute_mem; | |
| +	state->ca.read_cam_control = tbsusb_read_cam_control; | |
| +	state->ca.write_cam_control = tbsusb_write_cam_control; | |
| +	state->ca.slot_reset = tbsusb_slot_reset; | |
| +	state->ca.slot_shutdown = tbsusb_slot_shutdown; | |
| +	state->ca.slot_ts_enable = tbsusb_slot_ts_enable; | |
| +	state->ca.poll_slot_status = tbsusb_poll_slot_status; | |
| +	state->ca.data = a->dev; | |
| + | |
| +	ret = dvb_ca_en50221_init(&a->dvb_adap, &state->ca, 0, 1); | |
| +	if (ret) | |
| +		goto err; | |
| + | |
| +	ret = tbsusb_poll_slot_status(&state->ca, 0, 0); | |
| +	if (!ret) | |
| +		ret = tbsusb_set_video_port(&state->ca, 0, 0); | |
| +	if (ret < 0) { | |
| +		dvb_ca_en50221_release(&state->ca); | |
| +		goto err; | |
| +	} | |
| +	if (ret) | |
| +		deb_info("CI initialized."); | |
| +	return 0; | |
| + | |
| +err:	err("Cannot initialize CI: Error %d.", ret); | |
| +	memset(&state->ca, 0, sizeof(state->ca)); | |
| +	return ret; | |
| +} | |
| + | |
| +static const struct stv090x_config stv0903_config = { | |
| +	.device			= STV0903, | |
| +	.demod_mode		= STV090x_SINGLE, | |
| +	.clk_mode		= STV090x_CLK_EXT, | |
| + | |
| +	.xtal			= 27000000, | |
| +	.address		= 0x6a, | |
| + | |
| +	.ts1_mode		= STV090x_TSMODE_DVBCI, | |
| +	.ts2_mode		= STV090x_TSMODE_SERIAL_CONTINUOUS, | |
| + | |
| +	.repeater_level         = STV090x_RPTLEVEL_16, | |
| +	.adc1_range		= STV090x_ADC_2Vpp, | |
| + | |
| +	.tuner_get_frequency    = stb6100_get_frequency, | |
| +	.tuner_set_frequency    = stb6100_set_frequency, | |
| +	.tuner_set_bandwidth    = stb6100_set_bandwidth, | |
| +	.tuner_get_bandwidth    = stb6100_get_bandwidth, | |
| + | |
| +	.set_lock_led		= tbsusb_led_ctrl, | |
| +}; | |
| + | |
| +static const struct stv090x_config stv0900_config = { | |
| +	.device			= STV0900, | |
| +	.demod_mode		= STV090x_SINGLE, | |
| +	.clk_mode		= STV090x_CLK_EXT, | |
| + | |
| +	.xtal			= 27000000, | |
| +	.address		= 0x68, | |
| + | |
| +	.ts1_mode		= STV090x_TSMODE_DVBCI, | |
| +	.ts2_mode		= STV090x_TSMODE_SERIAL_CONTINUOUS, | |
| + | |
| +	.repeater_level         = STV090x_RPTLEVEL_16, | |
| +	.adc1_range		= STV090x_ADC_2Vpp, | |
| + | |
| +	.tuner_get_frequency    = stb6100_get_frequency, | |
| +	.tuner_set_frequency    = stb6100_set_frequency, | |
| +	.tuner_set_bandwidth    = stb6100_set_bandwidth, | |
| +	.tuner_get_bandwidth    = stb6100_get_bandwidth, | |
| + | |
| +	.set_lock_led		= tbsusb_led_ctrl, | |
| +}; | |
| + | |
| +static const struct tda10071_config tda10071_config = { | |
| +	.demod_i2c_addr = 0x55, /* (0xaa >> 1) */ | |
| +	.tuner_i2c_addr = 0x14, | |
| +	.i2c_wr_max     = 64, | |
| +	.ts_mode        = TDA10071_TS_PARALLEL, | |
| +	.spec_inv       = 0, | |
| +	.xtal           = 40444000, /* 40.444 MHz */ | |
| +	.pll_multiplier = 20, | |
| +	.set_lock_led   = tbsusb_led_ctrl, | |
| +}; | |
| + | |
| +static const struct cx24116_config cx24116_config = { | |
| +	.demod_address   = 0x55, | |
| +	.mpg_clk_pos_pol = 0x01, | |
| +	.set_lock_led    = tbsusb_led_ctrl, | |
| +}; | |
| + | |
| +static const struct stv0288_config stv0288_config = { | |
| +	.demod_address = 0x68, | |
| +	.set_lock_led  = tbsusb_led_ctrl, | |
| +}; | |
| + | |
| +static int tbsusb_frontend_attach(struct dvb_usb_adapter *d) | |
| +{ | |
| +	static u8 msg1[2] = {0x07, 0x01}; | |
| +	static u8 msg2[2] = {0x01, 0x01}; | |
| +	static u8 msg3[2] = {0x06, 0x01}; | |
| +	u8 *txt; | |
| +	int ret = -ENODEV; | |
| + | |
| +	if (!d || !d->dev || !d->dev->desc) { | |
| +		d->fe_adap[0].fe = NULL; | |
| +		goto err; | |
| +	} | |
| + | |
| +	switch (d->dev->desc->cold_ids[0]->idProduct) { | |
| +	case USB_PID_TENOW_TBS5980: | |
| +	case USB_PID_TENOW_TBS5920: | |
| +		d->fe_adap[0].fe = dvb_attach(stv090x_attach, &stv0903_config, | |
| +				&d->dev->i2c_adap, STV090x_DEMODULATOR_0); | |
| +		break; | |
| +	case USB_PID_TENOW_TBS5925: | |
| +		d->fe_adap[0].fe = dvb_attach(stv090x_attach, &stv0900_config, | |
| +				&d->dev->i2c_adap, STV090x_DEMODULATOR_0); | |
| +		break; | |
| +	case USB_PID_TENOW_TBS5928: | |
| +		d->fe_adap[0].fe = dvb_attach(cx24116_attach, &cx24116_config, | |
| +				&d->dev->i2c_adap); | |
| +		break; | |
| +	case USB_PID_TENOW_TBS5910: | |
| +		d->fe_adap[0].fe = dvb_attach(stv0288_attach, &stv0288_config, | |
| +				&d->dev->i2c_adap); | |
| +		break; | |
| +	case USB_PID_TENOW_TBS5921: | |
| +		d->fe_adap[0].fe = dvb_attach(tda10071_attach, &tda10071_config, | |
| +				&d->dev->i2c_adap); | |
| +	} | |
| + | |
| +	if (!d->fe_adap[0].fe) | |
| +		goto err; | |
| + | |
| +	d->fe_adap[0].fe->ops.set_voltage = tbsusb_set_voltage; | |
| + | |
| +	switch (d->dev->desc->cold_ids[0]->idProduct) { | |
| +	case USB_PID_TENOW_TBS5980: | |
| +		txt = "Attached stv0903!"; | |
| +		ret = tbsusb_op_w(d->dev, 0x8a, 0, msg1, 2); | |
| +		if (ret) | |
| +			goto err; | |
| +		ret = tbsusb_op_w(d->dev, 0x8a, 0, msg2, 2); | |
| +		if (ret) | |
| +			goto err; | |
| +		ret = tbsusb_op_w(d->dev, 0x8a, 0, msg3, 2); | |
| +		if (ret) | |
| +			goto err; | |
| +		ret = tbsusbci_init(d); | |
| +		if (ret) | |
| +			goto err; | |
| +		break; | |
| +	case USB_PID_TENOW_TBS5921: | |
| +		txt = "Attached tda10071!"; | |
| +		ret = tbsusb_op_w(d->dev, 0x8a, 0, msg1, 2); | |
| +		if (ret) | |
| +			goto err; | |
| +		ret = tbsusb_op_w(d->dev, 0x8a, 0, msg2, 2); | |
| +		if (ret) | |
| +			goto err; | |
| +		break; | |
| +	case USB_PID_TENOW_TBS5925: | |
| +		txt = "Attached stv0900!"; | |
| +		ret = tbsusb_op_w(d->dev, 0x8a, 0, msg3, 2); | |
| +		if (ret) | |
| +			goto err; | |
| +		ret = tbsusb_op_w(d->dev, 0x8a, 0, msg2, 2); | |
| +		if (ret) | |
| +			goto err; | |
| +		goto wr0701; | |
| +	case USB_PID_TENOW_TBS5920: | |
| +		txt = "Attached stv0903!"; | |
| +		goto wr0701; | |
| +	case USB_PID_TENOW_TBS5928: | |
| +		txt = "Attached cx24116!"; | |
| +		goto wr0701; | |
| +	case USB_PID_TENOW_TBS5910: | |
| +		txt = "Attached stv0288!"; | |
| +wr0701:		ret = tbsusb_op_w(d->dev, 0x8a, 0, msg1, 2); | |
| +		if (ret) | |
| +			goto err; | |
| +		break; | |
| +	} | |
| + | |
| +	deb_info(txt); | |
| + | |
| +err:	if (ret && d->fe_adap[0].fe) { | |
| +		dvb_frontend_detach(d->fe_adap[0].fe); | |
| +		d->fe_adap[0].fe = NULL; | |
| +	} | |
| + | |
| +	return ret; | |
| +} | |
| + | |
| +static struct stb6100_config stb6100_config = { | |
| +	.tuner_address  = 0x60, | |
| +	.refclock       = 27000000, | |
| +}; | |
| + | |
| +static int tbsusb_tuner_attach(struct dvb_usb_adapter *adap) | |
| +{ | |
| +	int ret = -EIO; | |
| + | |
| +	if (!adap || !adap->dev || !adap->dev->desc || !adap->fe_adap[0].fe) | |
| +		return -ENODEV; | |
| + | |
| +	switch (adap->dev->desc->cold_ids[0]->idProduct) { | |
| +	case USB_PID_TENOW_TBS5980: | |
| +	case USB_PID_TENOW_TBS5925: | |
| +	case USB_PID_TENOW_TBS5920: | |
| +		if (!dvb_attach(stb6100_attach, adap->fe_adap[0].fe, | |
| +				&stb6100_config, &adap->dev->i2c_adap)) | |
| +			goto err; | |
| +		else | |
| +			ret = 0; | |
| +		deb_info("Attached stb6100!"); | |
| +		if (adap->dev->desc->cold_ids[0]->idProduct != | |
| +							USB_PID_TENOW_TBS5925) | |
| +			break; | |
| +		/* call the init function once to initialize | |
| +		   tuner's clock output divider and demod's | |
| +		   master clock */ | |
| +		if (adap->fe_adap[0].fe->ops.init) | |
| +			adap->fe_adap[0].fe->ops.init(adap->fe_adap[0].fe); | |
| +		break; | |
| +	case USB_PID_TENOW_TBS5910: | |
| +		if (!dvb_attach(stb6000_attach, adap->fe_adap[0].fe, 0x61, | |
| +			&adap->dev->i2c_adap)) | |
| +			goto err; | |
| +		else | |
| +			ret = 0; | |
| +		deb_info("Attached stb6000!"); | |
| +		break; | |
| +	} | |
| + | |
| +err:	return ret; | |
| +} | |
| + | |
| +enum tbsusb_index { | |
| +	TBS5980_INDEX = 0, | |
| +	TBS5925_INDEX, | |
| +	TBS5920_INDEX, | |
| +	TBS5928_INDEX, | |
| +	TBS5910_INDEX, | |
| +	TBS5921_INDEX, | |
| +	TBSMAX_INDEX | |
| +}; | |
| + | |
| +static struct usb_device_id tbsusb_table[] = { | |
| +	{USB_DEVICE(USB_VID_TENOW, USB_PID_TENOW_TBS5980)}, | |
| +	{USB_DEVICE(USB_VID_TENOW, USB_PID_TENOW_TBS5925)}, | |
| +	{USB_DEVICE(USB_VID_TENOW, USB_PID_TENOW_TBS5920)}, | |
| +	{USB_DEVICE(USB_VID_TENOW, USB_PID_TENOW_TBS5928)}, | |
| +	{USB_DEVICE(USB_VID_TENOW, USB_PID_TENOW_TBS5910)}, | |
| +	{USB_DEVICE(USB_VID_TENOW, USB_PID_TENOW_TBS5921)}, | |
| +	{ } | |
| +}; | |
| + | |
| +MODULE_DEVICE_TABLE(usb, tbsusb_table); | |
| + | |
| +#define TBSUSB_DEVICE_PROPERTIES(priv, fw, intvl, tunerproc, devname, index) { \ | |
| +	.caps         = DVB_USB_IS_AN_I2C_ADAPTER, \ | |
| +	.usb_ctrl     = DEVICE_SPECIFIC, \ | |
| +	.size_of_priv = sizeof(struct priv), \ | |
| +	.firmware     = fw, \ | |
| +	.no_reconnect = 1, \ | |
| +	.i2c_algo     = &tbsusb_i2c_algo, \ | |
| +	.rc.legacy    = { \ | |
| +		.rc_map_table = tbsusb_rc_keys, \ | |
| +		.rc_map_size  = ARRAY_SIZE(tbsusb_rc_keys), \ | |
| +		.rc_interval  = intvl, \ | |
| +		.rc_query     = tbsusb_rc_query, \ | |
| +	}, \ | |
| +	.generic_bulk_ctrl_endpoint = 0x81, \ | |
| +	.num_adapters               = 1, \ | |
| +	.download_firmware          = tbsusb_load_firmware, \ | |
| +	.read_mac_address           = tbsusb_read_mac_address, \ | |
| +	.adapter                    = { { \ | |
| +		.num_frontends = 1, \ | |
| +		.fe            = { { \ | |
| +			.frontend_attach = tbsusb_frontend_attach, \ | |
| +			.streaming_ctrl  = NULL, \ | |
| +			.tuner_attach    = tunerproc, \ | |
| +			.stream          = { \ | |
| +				.type     = USB_BULK, \ | |
| +				.count    = 8, \ | |
| +				.endpoint = 0x82, \ | |
| +				.u        = { \ | |
| +					.bulk = { \ | |
| +						.buffersize = 4096, \ | |
| +					} \ | |
| +				} \ | |
| +			}, \ | |
| +		} }, \ | |
| +	} }, \ | |
| +	.num_device_descs = 1, \ | |
| +	.devices          = { { \ | |
| +		.name     = devname, \ | |
| +		.cold_ids = {&tbsusb_table[index], NULL}, \ | |
| +		.warm_ids = {NULL}, \ | |
| +	} } \ | |
| +} | |
| + | |
| +static struct dvb_usb_device_properties tbsusb_properties[] = { | |
| +	TBSUSB_DEVICE_PROPERTIES(tbsusbci_state, "dvb-usb-tbsqbox-id5980.fw", | |
| +		450, tbsusb_tuner_attach, "TBS Qbox DVB-S2 CI USB2.0 (TBS5980)", | |
| +		TBS5980_INDEX), | |
| +	TBSUSB_DEVICE_PROPERTIES(tbsusb_state, "dvb-usb-tbsqbox-id5925.fw", | |
| +		250, tbsusb_tuner_attach, "TBS 5925 DVB-S2 USB2.0", | |
| +		TBS5925_INDEX), | |
| +	TBSUSB_DEVICE_PROPERTIES(tbsusb_state, "dvb-usb-tbsqbox-id5920.fw", | |
| +		150, tbsusb_tuner_attach, "TBS QBOX2 DVBS USB2.0 (TBS5920)", | |
| +		TBS5920_INDEX), | |
| +	TBSUSB_DEVICE_PROPERTIES(tbsusb_state, "dvb-usb-tbsqbox-id5928.fw", | |
| +		150, NULL, "TBS QBOXS2 DVBS2 USB2.0 (TBS5928)", | |
| +		TBS5928_INDEX), | |
| +	TBSUSB_DEVICE_PROPERTIES(tbsusb_state, "dvb-usb-tbsqbox-id5910.fw", | |
| +		150, tbsusb_tuner_attach, "TBS QBOX DVBS USB2.0 (TBS5910)", | |
| +		TBS5910_INDEX), | |
| +	TBSUSB_DEVICE_PROPERTIES(tbsusb_state, "dvb-usb-tbsqbox-id5921.fw", | |
| +		150, NULL, "TBS QBOXS3 DVBS2 USB2.0 (TBS5921)", | |
| +		TBS5921_INDEX) | |
| +}; | |
| + | |
| +static int tbsusb_probe(struct usb_interface *intf, | |
| +		const struct usb_device_id *id) | |
| +{ | |
| +	int i; | |
| + | |
| +	for (i = 0; i < TBSMAX_INDEX; i++) | |
| +		if (!dvb_usb_device_init(intf, &tbsusb_properties[i], | |
| +						THIS_MODULE, NULL, adapter_nr)) | |
| +			return 0; | |
| +	return -ENODEV; | |
| +} | |
| + | |
| +static void tbsusb_usb_disconnect(struct usb_interface *dev) | |
| +{ | |
| +	struct dvb_usb_device *d = usb_get_intfdata(dev); | |
| + | |
| +	if (d && d->desc && d->desc->cold_ids[0]->idProduct == | |
| +							USB_PID_TENOW_TBS5980) | |
| +		tbsusbci_uninit(d); | |
| +	dvb_usb_device_exit(dev); | |
| +} | |
| + | |
| +static struct usb_driver tbsusb_driver = { | |
| +	.name       = "tbsusb", | |
| +	.probe      = tbsusb_probe, | |
| +	.disconnect = tbsusb_usb_disconnect, | |
| +	.id_table   = tbsusb_table, | |
| +}; | |
| + | |
| +module_usb_driver(tbsusb_driver); | |
| + | |
| +MODULE_AUTHOR("Various Authors"); | |
| +MODULE_DESCRIPTION("TBS 5980/5928/5925/5921/5920/5910 USB2.0 DVB-S/S2 driver"); | |
| +MODULE_VERSION("1.0"); | |
| +MODULE_LICENSE("GPL"); | |
| diff --git a/drivers/media/usb/dvb-usb/tbs-usb.h b/drivers/media/usb/dvb-usb/tbs-usb.h | |
| new file mode 100644 | |
| index 0000000..2a0a112 | |
| --- /dev/null | |
| +++ b/drivers/media/usb/dvb-usb/tbs-usb.h | |
| @@ -0,0 +1,9 @@ | |
| +#ifndef _DVB_USB_TBSUSB_H_ | |
| +#define _DVB_USB_TBSUSB_H_ | |
| + | |
| +#define DVB_USB_LOG_PREFIX "tbsusb" | |
| +#include "dvb-usb.h" | |
| + | |
| +#define deb_xfer(args...) dprintk(dvb_usb_tbsusb_debug, 0x02, args) | |
| +#define deb_info(args...) dprintk(dvb_usb_tbsusb_debug, 0x01, args) | |
| +#endif | |
| --  | |
| 2.1.4 | |
| 
 | |
| 
 |