Commit 9701dc94 authored by Mauro Carvalho Chehab's avatar Mauro Carvalho Chehab
Browse files

V4L/DVB (12770): Add tm6000 driver to staging tree



Adds a driver for Trident TV Master tm5600/tm6000 chips.

Those USB devices are usually found with a Xceive xc2028/xc3028
tuner, although the firmware seems to be modified to work with
those chips on some older devices.

Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@redhat.com>
parent e40152ee
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
config VIDEO_TM6000
	tristate "TV Master TM5600/6000 driver"
	select VIDEO_V4L2
	select TUNER_XC2028
	select VIDEO_USB_ISOC
	select VIDEOBUF_VMALLOC
	help
	  Support for TM5600/TM6000 USB Device

	  Since these cards have no MPEG decoder onboard, they transmit
	  only compressed MPEG data over the usb bus, so you need
	  an external software decoder to watch TV on your computer.

	  Say Y if you own such a device and want to use it.
+8 −0
Original line number Diff line number Diff line
tm6000-objs := tm6000-cards.o \
		   tm6000-core.o  \
		   tm6000-i2c.o   \
		   tm6000-video.o

obj-$(CONFIG_VIDEO_TM6000) += tm6000.o

EXTRA_CFLAGS = -Idrivers/media/video
+409 −0
Original line number Diff line number Diff line
/*
   tm6000-cards.c - driver for TM5600/TM6000 USB video capture devices

   Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.org>

   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

   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/init.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/usb.h>
#include <linux/version.h>
#include <media/v4l2-common.h>
#include <media/tuner.h>

#include "tm6000.h"

#define TM6000_BOARD_UNKNOWN		0
#define TM5600_BOARD_GENERIC		1
#define TM6000_BOARD_GENERIC		2
#define TM5600_BOARD_10MOONS_UT821	3
#define TM6000_BOARD_10MOONS_UT330	4
#define TM6000_BOARD_ADSTECH_DUAL_TV	5

#define TM6000_MAXBOARDS        16
static unsigned int card[]     = {[0 ... (TM6000_MAXBOARDS - 1)] = UNSET };

module_param_array(card,  int, NULL, 0444);

struct tm6000_board {
	char            *name;

	struct tm6000_capabilities caps;

	int             tuner_type;     /* type of the tuner */
	int             tuner_addr;     /* tuner address */
};


struct tm6000_board tm6000_boards[] = {
	[TM6000_BOARD_UNKNOWN] = {
		.name         = "Unknown tm6000 video grabber",
		.caps = {
			.has_tuner    = 1,
		},
	},
	[TM5600_BOARD_GENERIC] = {
		.name         = "Generic tm5600 board",
		.tuner_type   = TUNER_XC2028,
		.tuner_addr   = 0xc2,
		.caps = {
			.has_tuner	= 1,
		},
	},
	[TM6000_BOARD_GENERIC] = {
		.name         = "Generic tm6000 board",
		.tuner_type   = TUNER_XC2028,
		.tuner_addr   = 0xc2,
		.caps = {
			.has_tuner	= 1,
			.has_dvb	= 1,
		},
	},
	[TM5600_BOARD_10MOONS_UT821] = {
		.name         = "10Moons UT 821",
		.tuner_type   = TUNER_XC2028,
		.tuner_addr   = 0xc2,
		.caps = {
			.has_tuner    = 1,
			.has_eeprom   = 1,
		},
	},
	[TM6000_BOARD_10MOONS_UT330] = {
		.name         = "10Moons UT 330",
		.tuner_type   = TUNER_XC2028,
		.tuner_addr   = 0xc8,
		.caps = {
			.has_tuner    = 1,
			.has_dvb      = 1,
			.has_zl10353  = 1,
			.has_eeprom   = 1,
		},
	},
	[TM6000_BOARD_ADSTECH_DUAL_TV] = {
		.name         = "ADSTECH Dual TV USB",
		.tuner_type   = TUNER_XC2028,
		.tuner_addr   = 0xc8,
		.caps = {
			.has_tuner    = 1,
			.has_tda9874  = 1,
			.has_dvb      = 1,
			.has_zl10353  = 1,
			.has_eeprom   = 1,
		},
	},
};

/* table of devices that work with this driver */
struct usb_device_id tm6000_id_table [] = {
	{ USB_DEVICE(0x6000, 0x0001), .driver_info = TM5600_BOARD_10MOONS_UT821 },
	{ USB_DEVICE(0x06e1, 0xf332), .driver_info = TM6000_BOARD_ADSTECH_DUAL_TV },
	{ },
};

static int tm6000_init_dev(struct tm6000_core *dev)
{
	struct v4l2_frequency f;
	int rc = 0;

	mutex_init(&dev->lock);

	mutex_lock(&dev->lock);

	/* Initializa board-specific data */
	dev->tuner_type = tm6000_boards[dev->model].tuner_type;
	dev->tuner_addr = tm6000_boards[dev->model].tuner_addr;

	dev->caps = tm6000_boards[dev->model].caps;

	/* initialize hardware */
	rc=tm6000_init (dev);
	if (rc<0)
		goto err;

	/* register i2c bus */
	rc=tm6000_i2c_register(dev);
	if (rc<0)
		goto err;

	/* register and initialize V4L2 */
	rc=tm6000_v4l2_register(dev);
	if (rc<0)
		goto err;

	/* Request tuner */
	request_module ("tuner");
//	norm=V4L2_STD_NTSC_M;
	dev->norm=V4L2_STD_PAL_M;
	tm6000_i2c_call_clients(dev, VIDIOC_S_STD, &dev->norm);

	/* configure tuner */
	f.tuner = 0;
	f.type = V4L2_TUNER_ANALOG_TV;
	f.frequency = 3092;	/* 193.25 MHz */
	dev->freq = f.frequency;

	tm6000_i2c_call_clients(dev, VIDIOC_S_FREQUENCY, &f);

err:
	mutex_unlock(&dev->lock);
	return rc;
}

/* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */
#define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03))

static void get_max_endpoint (  struct usb_device *usbdev,
				char *msgtype,
				struct usb_host_endpoint *curr_e,
				unsigned int *maxsize,
				struct usb_host_endpoint **ep  )
{
	u16 tmp = le16_to_cpu(curr_e->desc.wMaxPacketSize);
	unsigned int size = tmp & 0x7ff;

	if (usbdev->speed == USB_SPEED_HIGH)
		size = size * hb_mult (tmp);

	if (size>*maxsize) {
		*ep = curr_e;
		*maxsize = size;
		printk("tm6000: %s endpoint: 0x%02x (max size=%u bytes)\n",
					msgtype, curr_e->desc.bEndpointAddress,
					size);
	}
}

/*
 * tm6000_usb_probe()
 * checks for supported devices
 */
static int tm6000_usb_probe(struct usb_interface *interface,
			    const struct usb_device_id *id)
{
	struct usb_device *usbdev;
	struct tm6000_core *dev = NULL;
	int i,rc=0;
	int nr=0;
	char *speed;


	usbdev=usb_get_dev(interface_to_usbdev(interface));

	/* Selects the proper interface */
	rc=usb_set_interface(usbdev,0,1);
	if (rc<0)
		goto err;

	/* Check to see next free device and mark as used */
	nr=find_first_zero_bit(&tm6000_devused,TM6000_MAXBOARDS);
	if (nr >= TM6000_MAXBOARDS) {
		printk ("tm6000: Supports only %i em28xx boards.\n",TM6000_MAXBOARDS);
		usb_put_dev(usbdev);
		return -ENOMEM;
	}

	/* Create and initialize dev struct */
	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (dev == NULL) {
		printk ("tm6000" ": out of memory!\n");
		usb_put_dev(usbdev);
		return -ENOMEM;
	}
	spin_lock_init(&dev->slock);

	/* Increment usage count */
	tm6000_devused|=1<<nr;

	dev->udev= usbdev;
	dev->model=id->driver_info;
	snprintf(dev->name, 29, "tm6000 #%d", nr);
	dev->devno=nr;

	switch (usbdev->speed) {
	case USB_SPEED_LOW:
		speed = "1.5";
		break;
	case USB_SPEED_UNKNOWN:
	case USB_SPEED_FULL:
		speed = "12";
		break;
	case USB_SPEED_HIGH:
		speed = "480";
		break;
	default:
		speed = "unknown";
	}



	/* Get endpoints */
	for (i = 0; i < interface->num_altsetting; i++) {
		int ep;

		for (ep = 0; ep < interface->altsetting[i].desc.bNumEndpoints; ep++) {
			struct usb_host_endpoint	*e;
			int dir_out;

			e = &interface->altsetting[i].endpoint[ep];

			dir_out = ((e->desc.bEndpointAddress &
					USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT);

			printk("tm6000: alt %d, interface %i, class %i\n",
			       i,
			       interface->altsetting[i].desc.bInterfaceNumber,
			       interface->altsetting[i].desc.bInterfaceClass);

			switch (e->desc.bmAttributes) {
			case USB_ENDPOINT_XFER_BULK:
				if (!dir_out) {
					get_max_endpoint (usbdev, "Bulk IN", e,
							&dev->max_bulk_in,
							&dev->bulk_in);
				} else {
					get_max_endpoint (usbdev, "Bulk OUT", e,
							&dev->max_bulk_out,
							&dev->bulk_out);
				}
				break;
			case USB_ENDPOINT_XFER_ISOC:
				if (!dir_out) {
					get_max_endpoint (usbdev, "ISOC IN", e,
							&dev->max_isoc_in,
							&dev->isoc_in);
				} else {
					get_max_endpoint (usbdev, "ISOC OUT", e,
							&dev->max_isoc_out,
							&dev->isoc_out);
				}
				break;
			}
		}
	}

	if (interface->altsetting->desc.bAlternateSetting) {
		printk("selecting alt setting %d\n",
		       interface->altsetting->desc.bAlternateSetting);
		rc = usb_set_interface (usbdev,
				interface->altsetting->desc.bInterfaceNumber,
				interface->altsetting->desc.bAlternateSetting);
		if (rc<0)
			goto err;
	}

	printk("tm6000: New video device @ %s Mbps (%04x:%04x, ifnum %d)\n",
		speed,
		le16_to_cpu(dev->udev->descriptor.idVendor),
		le16_to_cpu(dev->udev->descriptor.idProduct),
		interface->altsetting->desc.bInterfaceNumber);

/* check if the the device has the iso in endpoint at the correct place */
	if (!dev->isoc_in) {
		printk("tm6000: probing error: no IN ISOC endpoint!\n");
		rc= -ENODEV;

		goto err;
	}

	/* save our data pointer in this interface device */
	usb_set_intfdata(interface, dev);

	printk("tm6000: Found %s\n", tm6000_boards[dev->model].name);

	rc=tm6000_init_dev(dev);

	if (rc<0)
		goto err;

	return 0;

err:
	tm6000_devused&=~(1<<nr);
	usb_put_dev(usbdev);

	kfree(dev);
	return rc;
}

/*
 * tm6000_usb_disconnect()
 * called when the device gets diconencted
 * video device will be unregistered on v4l2_close in case it is still open
 */
static void tm6000_usb_disconnect(struct usb_interface *interface)
{
	struct tm6000_core *dev = usb_get_intfdata(interface);
	usb_set_intfdata(interface, NULL);

	if (!dev)
		return;

	tm6000_i2c_unregister(dev);

	printk("tm6000: disconnecting %s\n", dev->name);

	mutex_lock(&dev->lock);

	tm6000_i2c_unregister(dev);

	tm6000_v4l2_unregister(dev);

//	wake_up_interruptible_all(&dev->open);

	dev->state |= DEV_DISCONNECTED;

	mutex_unlock(&dev->lock);
}

static struct usb_driver tm6000_usb_driver = {
		.name = "tm6000",
		.probe = tm6000_usb_probe,
		.disconnect = tm6000_usb_disconnect,
		.id_table = tm6000_id_table,
};

static int __init tm6000_module_init(void)
{
	int result;

	printk(KERN_INFO "tm6000" " v4l2 driver version %d.%d.%d loaded\n",
	       (TM6000_VERSION  >> 16) & 0xff,
	       (TM6000_VERSION  >> 8) & 0xff, TM6000_VERSION  & 0xff);

	/* register this driver with the USB subsystem */
	result = usb_register(&tm6000_usb_driver);
	if (result)
		printk("tm6000"
			   " usb_register failed. Error number %d.\n", result);

	return result;
}

static void __exit tm6000_module_exit(void)
{
	/* deregister at USB subsystem */
	usb_deregister(&tm6000_usb_driver);
}

module_init(tm6000_module_init);
module_exit(tm6000_module_exit);

MODULE_DESCRIPTION("Trident TVMaster TM5600/TM6000 USB2 adapter");
MODULE_AUTHOR("Mauro Carvalho Chehab");
MODULE_LICENSE("GPL");
+633 −0

File added.

Preview size limit exceeded, changes collapsed.

+460 −0
Original line number Diff line number Diff line
/*
   tm6000-i2c.c - driver for TM5600/TM6000 USB video capture devices

   Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.org>

   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

   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>
#include <linux/kernel.h>
#include <linux/usb.h>
#include <linux/i2c.h>

#include "tm6000.h"
#include "tm6000-regs.h"
#include <media/v4l2-common.h>
#include <media/tuner.h>
#include "tuner-xc2028.h"


/*FIXME: Hack to avoid needing to patch i2c-id.h */
#define I2C_HW_B_TM6000 I2C_HW_B_EM28XX
/* ----------------------------------------------------------- */

static unsigned int i2c_scan = 0;
module_param(i2c_scan, int, 0444);
MODULE_PARM_DESC(i2c_scan, "scan i2c bus at insmod time");

static unsigned int i2c_debug = 0;
module_param(i2c_debug, int, 0644);
MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]");

#define i2c_dprintk(lvl,fmt, args...) if (i2c_debug>=lvl) do{ \
			printk(KERN_DEBUG "%s at %s: " fmt, \
			dev->name, __FUNCTION__ , ##args); } while (0)


/* Returns 0 if address is found */
static int tm6000_i2c_scan(struct i2c_adapter *i2c_adap, int addr)
{
	struct tm6000_core *dev = i2c_adap->algo_data;

#if 1
	/* HACK: i2c scan is not working yet */
	if (
		(dev->caps.has_tuner   && (addr==dev->tuner_addr)) ||
		(dev->caps.has_tda9874 && (addr==0xb0)) ||
		(dev->caps.has_zl10353 && (addr==0x1e)) ||
		(dev->caps.has_eeprom  && (addr==0xa0))
	   ) {
		printk("Hack: enabling device at addr 0x%02x\n",addr);
		return (1);
	} else {
		return -ENODEV;
	}
#else
	int rc=-ENODEV;
	char buf[1];

	/* This sends addr + 1 byte with 0 */
	rc = tm6000_read_write_usb (dev,
		USB_DIR_IN | USB_TYPE_VENDOR,
		REQ_16_SET_GET_I2CSEQ,
		addr, 0,
		buf, 0);
	msleep(10);

	if (rc<0) {
		if (i2c_debug>=2)
			printk("no device at addr 0x%02x\n",addr);
	}

	printk("Hack: check on addr 0x%02x returned %d\n",addr,rc);

	return rc;
#endif
}

static int tm6000_i2c_xfer(struct i2c_adapter *i2c_adap,
			   struct i2c_msg msgs[], int num)
{
	struct tm6000_core *dev = i2c_adap->algo_data;
	int addr, rc, i, byte;

	if (num <= 0)
		return 0;
	for (i = 0; i < num; i++) {
		addr = (msgs[i].addr << 1) &0xff;
		i2c_dprintk(2,"%s %s addr=0x%x len=%d:",
			 (msgs[i].flags & I2C_M_RD) ? "read" : "write",
			 i == num - 1 ? "stop" : "nonstop", addr, msgs[i].len);

		if (!msgs[i].len) {
			/* Do I2C scan */
			rc=tm6000_i2c_scan(i2c_adap, addr);
		} else if (msgs[i].flags & I2C_M_RD) {
			char buf[msgs[i].len];
			memcpy(buf,msgs[i].buf, msgs[i].len-1);
			buf[msgs[i].len-1]=0;

			/* Read bytes */
	/* I2C is assumed to have always a subaddr at the first byte of the
	   message bus. Also, the first i2c value of the answer is returned
	   out of message data.
	 */
			rc = tm6000_read_write_usb (dev,
				USB_DIR_IN | USB_TYPE_VENDOR,
				REQ_16_SET_GET_I2CSEQ,
				addr|(*msgs[i].buf)<<8, 0,
				msgs[i].buf, msgs[i].len);
			if (i2c_debug>=2) {
				for (byte = 0; byte < msgs[i].len; byte++) {
					printk(" %02x", msgs[i].buf[byte]);
				}
			}
		} else {
			/* write bytes */
			if (i2c_debug>=2) {
				for (byte = 0; byte < msgs[i].len; byte++)
					printk(" %02x", msgs[i].buf[byte]);
			}

			rc = tm6000_read_write_usb (dev,
				USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
				REQ_16_SET_GET_I2CSEQ,
				addr|(*msgs[i].buf)<<8, 0,
				msgs[i].buf+1, msgs[i].len-1);
		}
		if (i2c_debug>=2)
			printk("\n");
		if (rc < 0)
			goto err;
	}

	return num;
err:
	i2c_dprintk(2," ERROR: %i\n", rc);
	return rc;
}


static int tm6000_i2c_eeprom( struct tm6000_core *dev,
			      unsigned char *eedata, int len )
{
	int i, rc;
	unsigned char *p = eedata;
	unsigned char bytes[17];

	dev->i2c_client.addr = 0xa0 >> 1;

//006779:  OUT: 000006 ms 089867 ms c0 0e a0 00 00 00 01 00 <<<  00
//006780:  OUT: 000005 ms 089873 ms c0 10 a0 00 00 00 01 00 <<<  00
//006781:  OUT: 000108 ms 089878 ms 40 0e a0 00 00 00 01 00 >>>  99
//006782:  OUT: 000015 ms 089986 ms c0 0e a0 00 01 00 01 00 <<<  99
//006783:  OUT: 000004 ms 090001 ms c0 0e a0 00 10 00 01 00 <<<  99
//006784:  OUT: 000005 ms 090005 ms 40 10 a0 00 00 00 01 00 >>>  00
//006785:  OUT: 000308 ms 090010 ms 40 0e a0 00 00 00 01 00 >>>  00


	for (i = 0; i < len; i++) {
		bytes[0x14+i] = 0;

		rc = i2c_master_recv(&dev->i2c_client, p, 1);
		if (rc<1) {
			if (p==eedata) {
				printk (KERN_WARNING "%s doesn't have eeprom",
					dev->name);
			} else {
				printk(KERN_WARNING
				"%s: i2c eeprom read error (err=%d)\n",
				dev->name, rc);
			}
			return -1;
		}
		p++;
		if (0 == (i % 16))
			printk(KERN_INFO "%s: i2c eeprom %02x:", dev->name, i);
		printk(" %02x", eedata[i]);
		if ((eedata[i]>=' ')&&(eedata[i]<='z')) {
			bytes[i%16]=eedata[i];
		} else {
			bytes[i%16]='.';
		}
		if (15 == (i % 16)) {
			bytes[i%16]='\0';
			printk("  %s\n", bytes);
		}
	}
	if ((i%16)!=15) {
		bytes[i%16]='\0';
		printk("  %s\n", bytes);
	}
	return 0;
}

/* ----------------------------------------------------------- */

/*
 * algo_control()
 */
static int algo_control(struct i2c_adapter *adapter,
			unsigned int cmd, unsigned long arg)
{
	return 0;
}

/*
 * functionality()
 */
static u32 functionality(struct i2c_adapter *adap)
{
	return I2C_FUNC_SMBUS_EMUL;
}

#ifndef I2C_PEC
static void inc_use(struct i2c_adapter *adap)
{
	MOD_INC_USE_COUNT;
}

static void dec_use(struct i2c_adapter *adap)
{
	MOD_DEC_USE_COUNT;
}
#endif

#define mass_write(addr, reg, data...)					\
	{ const static u8 _val[] = data;				\
	rc=tm6000_read_write_usb(dev,USB_DIR_OUT | USB_TYPE_VENDOR,	\
	REQ_16_SET_GET_I2CSEQ,(reg<<8)+addr, 0x00, (u8 *) _val,		\
	ARRAY_SIZE(_val));						\
	if (rc<0) {							\
		printk(KERN_ERR "Error on line %d: %d\n",__LINE__,rc);	\
		return rc;						\
	}								\
	msleep (10);							\
	}

int static init_zl10353 (struct tm6000_core *dev, u8 addr)
{
	int rc=0;

	mass_write (addr, 0x89, { 0x38 });
	mass_write (addr, 0x8a, { 0x2d });
	mass_write (addr, 0x50, { 0xff });
	mass_write (addr, 0x51, { 0x00 , 0x00 , 0x50 });
	mass_write (addr, 0x54, { 0x72 , 0x49 });
	mass_write (addr, 0x87, { 0x0e , 0x0e });
	mass_write (addr, 0x7b, { 0x04 });
	mass_write (addr, 0x57, { 0xb8 , 0xc2 });
	mass_write (addr, 0x59, { 0x00 , 0x02 , 0x00 , 0x00 , 0x01 });
	mass_write (addr, 0x59, { 0x00 , 0x00 , 0xb3 , 0xd0 , 0x01 });
	mass_write (addr, 0x58, { 0xc0 , 0x11 , 0xc5 , 0xc2 , 0xa4 , 0x01 });
	mass_write (addr, 0x5e, { 0x01 });
	mass_write (addr, 0x67, { 0x1c , 0x20 });
	mass_write (addr, 0x75, { 0x33 });
	mass_write (addr, 0x85, { 0x10 , 0x40 });
	mass_write (addr, 0x8c, { 0x0b , 0x00 , 0x40 , 0x00 });

	return 0;
}

/* Tuner callback to provide the proper gpio changes needed for xc2028 */

static int tm6000_tuner_callback(void *ptr, int command, int arg)
{
	int rc=0;
	struct tm6000_core *dev = ptr;

	if (dev->tuner_type!=TUNER_XC2028)
		return 0;

	switch (command) {
	case XC2028_RESET_CLK:
		tm6000_set_reg (dev, REQ_04_EN_DISABLE_MCU_INT,
					0x02, arg);
		msleep(10);
		rc=tm6000_set_reg (dev, REQ_03_SET_GET_MCU_PIN,
					TM6000_GPIO_CLK, 0);
		if (rc<0)
			return rc;
		msleep(10);
		rc=tm6000_set_reg (dev, REQ_03_SET_GET_MCU_PIN,
					TM6000_GPIO_CLK, 1);
		break;
	case XC2028_TUNER_RESET:
		/* Reset codes during load firmware */
		switch (arg) {
		case 0:
			tm6000_set_reg (dev, REQ_03_SET_GET_MCU_PIN,
						TM6000_GPIO_1, 0x00);
			msleep(10);
			tm6000_set_reg (dev, REQ_03_SET_GET_MCU_PIN,
						TM6000_GPIO_1, 0x01);
			break;
		case 1:
			tm6000_set_reg (dev, REQ_04_EN_DISABLE_MCU_INT,
						0x02, 0x01);
			msleep(10);
			break;

		case 2:
			rc=tm6000_set_reg (dev, REQ_03_SET_GET_MCU_PIN,
						TM6000_GPIO_CLK, 0);
			if (rc<0)
				return rc;
			msleep(10);
			rc=tm6000_set_reg (dev, REQ_03_SET_GET_MCU_PIN,
						TM6000_GPIO_CLK, 1);
			break;
		}
	}
	return (rc);
}

static int attach_inform(struct i2c_client *client)
{
	struct tm6000_core *dev = client->adapter->algo_data;
	struct tuner_setup tun_setup;
	unsigned char eedata[11];

	i2c_dprintk(1, "%s i2c attach [addr=0x%x,client=%s]\n",
		client->driver->driver.name, client->addr, client->name);

	switch (client->addr<<1) {
	case 0x1e:
		init_zl10353 (dev, client->addr);
		return 0;
	case 0xa0:
		tm6000_i2c_eeprom(dev, eedata, sizeof(eedata)-1);
		eedata[sizeof(eedata)]='\0';

		printk("Board string ID = %s\n",eedata);
		return 0;
	case 0xb0:
		request_module("tvaudio");
		return 0;
	}

	/* If tuner, initialize the tuner part */
	if ( dev->tuner_addr != client->addr<<1 ) {
		return 0;
	}

	memset (&tun_setup, 0, sizeof(tun_setup));

	tun_setup.mode_mask = T_ANALOG_TV | T_RADIO;
	tun_setup.type = dev->tuner_type;
	tun_setup.addr = dev->tuner_addr>>1;
	tun_setup.tuner_callback = tm6000_tuner_callback;

	client->driver->command (client,TUNER_SET_TYPE_ADDR, &tun_setup);

	return 0;
}

static struct i2c_algorithm tm6000_algo = {
	.master_xfer   = tm6000_i2c_xfer,
	.algo_control  = algo_control,
	.functionality = functionality,
};

static struct i2c_adapter tm6000_adap_template = {
#ifdef I2C_PEC
	.owner = THIS_MODULE,
#else
	.inc_use = inc_use,
	.dec_use = dec_use,
#endif
	.class = I2C_CLASS_TV_ANALOG,
	.name = "tm6000",
	.id = I2C_HW_B_TM6000,
	.algo = &tm6000_algo,
	.client_register = attach_inform,
};

static struct i2c_client tm6000_client_template = {
	.name = "tm6000 internal",
};

/* ----------------------------------------------------------- */

/*
 * i2c_devs
 * incomplete list of known devices
 */
static char *i2c_devs[128] = {
	[0xc2 >> 1] = "tuner (analog)",
};

/*
 * do_i2c_scan()
 * check i2c address range for devices
 */
static void do_i2c_scan(char *name, struct i2c_client *c)
{
	unsigned char buf;
	int i, rc;

	for (i = 0; i < 128; i++) {
		c->addr = i;
		rc = i2c_master_recv(c, &buf, 0);
		if (rc < 0)
			continue;
		printk(KERN_INFO "%s: found i2c device @ 0x%x [%s]\n", name,
		       i << 1, i2c_devs[i] ? i2c_devs[i] : "???");
	}
}

/*
 * tm6000_i2c_call_clients()
 * send commands to all attached i2c devices
 */
void tm6000_i2c_call_clients(struct tm6000_core *dev, unsigned int cmd, void *arg)
{
	BUG_ON(NULL == dev->i2c_adap.algo_data);
	i2c_clients_command(&dev->i2c_adap, cmd, arg);
}

/*
 * tm6000_i2c_register()
 * register i2c bus
 */
int tm6000_i2c_register(struct tm6000_core *dev)
{
	dev->i2c_adap = tm6000_adap_template;
	dev->i2c_adap.dev.parent = &dev->udev->dev;
	strcpy(dev->i2c_adap.name, dev->name);
	dev->i2c_adap.algo_data = dev;
	i2c_add_adapter(&dev->i2c_adap);

	dev->i2c_client = tm6000_client_template;
	dev->i2c_client.adapter = &dev->i2c_adap;

	if (i2c_scan)
		do_i2c_scan(dev->name, &dev->i2c_client);

	return 0;
}

/*
 * tm6000_i2c_unregister()
 * unregister i2c_bus
 */
int tm6000_i2c_unregister(struct tm6000_core *dev)
{
	i2c_del_adapter(&dev->i2c_adap);
	return 0;
}
Loading