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: Mauro Carvalho Chehab <mchehab@redhat.com>
This commit is contained in:
Mauro Carvalho Chehab 2009-09-14 09:42:41 -03:00
parent e40152ee1e
commit 9701dc94a1
10 changed files with 3431 additions and 0 deletions

View File

@ -0,0 +1,14 @@
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.

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,409 @@
/*
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");

View File

@ -0,0 +1,633 @@
/*
tm6000-core.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 <linux/video_decoder.h>
#include "tm6000.h"
#include "tm6000-regs.h"
#include <media/v4l2-common.h>
#include <media/tuner.h>
#ifdef HACK /* HACK */
#include "tm6000-hack.c"
#endif
#define USB_TIMEOUT 5*HZ /* ms */
int tm6000_read_write_usb (struct tm6000_core *dev, u8 req_type, u8 req,
u16 value, u16 index, u8 *buf, u16 len)
{
int ret, i;
unsigned int pipe;
static int ini=0, last=0, n=0;
u8 *data=NULL;
if (len)
data = kzalloc(len, GFP_KERNEL);
if (req_type & USB_DIR_IN)
pipe=usb_rcvctrlpipe(dev->udev, 0);
else {
pipe=usb_sndctrlpipe(dev->udev, 0);
memcpy(data, buf, len);
}
if (tm6000_debug & V4L2_DEBUG_I2C) {
if (!ini)
last=ini=jiffies;
printk("%06i (dev %p, pipe %08x): ", n, dev->udev, pipe);
printk( "%s: %06u ms %06u ms %02x %02x %02x %02x %02x %02x %02x %02x ",
(req_type & USB_DIR_IN)?" IN":"OUT",
jiffies_to_msecs(jiffies-last),
jiffies_to_msecs(jiffies-ini),
req_type, req,value&0xff,value>>8, index&0xff, index>>8,
len&0xff, len>>8);
last=jiffies;
n++;
if ( !(req_type & USB_DIR_IN) ) {
printk(">>> ");
for (i=0;i<len;i++) {
printk(" %02x",buf[i]);
}
printk("\n");
}
}
ret = usb_control_msg(dev->udev, pipe, req, req_type, value, index, data,
len, USB_TIMEOUT);
if (req_type & USB_DIR_IN)
memcpy(buf, data, len);
if (tm6000_debug & V4L2_DEBUG_I2C) {
if (ret<0) {
if (req_type & USB_DIR_IN)
printk("<<< (len=%d)\n",len);
printk("%s: Error #%d\n", __FUNCTION__, ret);
} else if (req_type & USB_DIR_IN) {
printk("<<< ");
for (i=0;i<len;i++) {
printk(" %02x",buf[i]);
}
printk("\n");
}
}
kfree(data);
return ret;
}
int tm6000_set_reg (struct tm6000_core *dev, u8 req, u16 value, u16 index)
{
return
tm6000_read_write_usb (dev, USB_DIR_OUT | USB_TYPE_VENDOR,
req, value, index, NULL, 0);
}
int tm6000_get_reg (struct tm6000_core *dev, u8 req, u16 value, u16 index)
{
int rc;
u8 buf[1];
rc=tm6000_read_write_usb (dev, USB_DIR_IN | USB_TYPE_VENDOR, req,
value, index, buf, 1);
if (rc<0)
return rc;
return *buf;
}
int tm6000_get_reg16 (struct tm6000_core *dev, u8 req, u16 value, u16 index)
{
int rc;
u8 buf[2];
rc=tm6000_read_write_usb (dev, USB_DIR_IN | USB_TYPE_VENDOR, req,
value, index, buf, 2);
if (rc<0)
return rc;
return buf[1]|buf[0]<<8;
}
void tm6000_set_fourcc_format(struct tm6000_core *dev)
{
if (dev->fourcc==V4L2_PIX_FMT_UYVY) {
/* Sets driver to UYUV */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xc1, 0xd0);
} else {
/* Sets driver to YUV2 */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xc1, 0x90);
}
}
int tm6000_init_analog_mode (struct tm6000_core *dev)
{
/* Enables soft reset */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x3f, 0x01);
if (dev->scaler) {
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xc0, 0x20);
} else {
/* Enable Hfilter and disable TS Drop err */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xc0, 0x80);
}
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xc3, 0x88);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xda, 0x23);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xd1, 0xc0);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xd2, 0xd8);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xd6, 0x06);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xdf, 0x1f);
/* AP Software reset */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xff, 0x08);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xff, 0x00);
tm6000_set_fourcc_format(dev);
/* Disables soft reset */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x3f, 0x00);
/* E3: Select input 0 - TV tuner */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xe3, 0x00);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xeb, 0x60);
/* Tuner firmware can now be loaded */
tm6000_set_reg (dev, REQ_03_SET_GET_MCU_PIN, TM6000_GPIO_1, 0x00);
msleep(11);
/* This controls input */
tm6000_set_reg (dev, REQ_03_SET_GET_MCU_PIN, TM6000_GPIO_2, 0x0);
tm6000_set_reg (dev, REQ_03_SET_GET_MCU_PIN, TM6000_GPIO_3, 0x01);
msleep(20);
/*FIXME: Hack!!! */
struct v4l2_frequency f;
mutex_lock(&dev->lock);
f.frequency=dev->freq;
tm6000_i2c_call_clients(dev,VIDIOC_S_FREQUENCY,&f);
mutex_unlock(&dev->lock);
msleep(100);
tm6000_set_standard (dev, &dev->norm);
tm6000_set_audio_bitrate (dev,48000);
return 0;
}
/* The meaning of those initializations are unknown */
u8 init_tab[][2] = {
/* REG VALUE */
{ 0xdf, 0x1f },
{ 0xff, 0x08 },
{ 0xff, 0x00 },
{ 0xd5, 0x4f },
{ 0xda, 0x23 },
{ 0xdb, 0x08 },
{ 0xe2, 0x00 },
{ 0xe3, 0x10 },
{ 0xe5, 0x00 },
{ 0xe8, 0x00 },
{ 0xeb, 0x64 }, /* 48000 bits/sample, external input */
{ 0xee, 0xc2 },
{ 0x3f, 0x01 }, /* Start of soft reset */
{ 0x00, 0x00 },
{ 0x01, 0x07 },
{ 0x02, 0x5f },
{ 0x03, 0x00 },
{ 0x05, 0x64 },
{ 0x07, 0x01 },
{ 0x08, 0x82 },
{ 0x09, 0x36 },
{ 0x0a, 0x50 },
{ 0x0c, 0x6a },
{ 0x11, 0xc9 },
{ 0x12, 0x07 },
{ 0x13, 0x3b },
{ 0x14, 0x47 },
{ 0x15, 0x6f },
{ 0x17, 0xcd },
{ 0x18, 0x1e },
{ 0x19, 0x8b },
{ 0x1a, 0xa2 },
{ 0x1b, 0xe9 },
{ 0x1c, 0x1c },
{ 0x1d, 0xcc },
{ 0x1e, 0xcc },
{ 0x1f, 0xcd },
{ 0x20, 0x3c },
{ 0x21, 0x3c },
{ 0x2d, 0x48 },
{ 0x2e, 0x88 },
{ 0x30, 0x22 },
{ 0x31, 0x61 },
{ 0x32, 0x74 },
{ 0x33, 0x1c },
{ 0x34, 0x74 },
{ 0x35, 0x1c },
{ 0x36, 0x7a },
{ 0x37, 0x26 },
{ 0x38, 0x40 },
{ 0x39, 0x0a },
{ 0x42, 0x55 },
{ 0x51, 0x11 },
{ 0x55, 0x01 },
{ 0x57, 0x02 },
{ 0x58, 0x35 },
{ 0x59, 0xa0 },
{ 0x80, 0x15 },
{ 0x82, 0x42 },
{ 0xc1, 0xd0 },
{ 0xc3, 0x88 },
{ 0x3f, 0x00 }, /* End of the soft reset */
};
int tm6000_init (struct tm6000_core *dev)
{
int board, rc=0, i;
#ifdef HACK /* HACK */
init_tm6000(dev);
return 0;
#else
/* Load board's initialization table */
for (i=0; i< ARRAY_SIZE(init_tab); i++) {
rc= tm6000_set_reg (dev, REQ_07_SET_GET_AVREG,
init_tab[i][0],init_tab[i][1]);
if (rc<0) {
printk (KERN_ERR "Error %i while setting reg %d to value %d\n",
rc, init_tab[i][0],init_tab[i][1]);
return rc;
}
}
/* Check board version - maybe 10Moons specific */
board=tm6000_get_reg16 (dev, 0x40, 0, 0);
if (board >=0) {
printk (KERN_INFO "Board version = 0x%04x\n",board);
} else {
printk (KERN_ERR "Error %i while retrieving board version\n",board);
}
tm6000_set_reg (dev, REQ_05_SET_GET_USBREG, 0x18, 0x00);
msleep(5); /* Just to be conservative */
/* Reset GPIO1. Maybe, this is 10 Moons specific */
for (i=0; i< 3; i++) {
rc=tm6000_set_reg (dev, REQ_03_SET_GET_MCU_PIN, TM6000_GPIO_1, 0);
if (rc<0) {
printk (KERN_ERR "Error %i doing GPIO1 reset\n",rc);
return rc;
}
msleep(10); /* Just to be conservative */
rc=tm6000_set_reg (dev, REQ_03_SET_GET_MCU_PIN, TM6000_GPIO_1, 1);
if (rc<0) {
printk (KERN_ERR "Error %i doing GPIO1 reset\n",rc);
return rc;
}
if (!i)
rc=tm6000_get_reg16(dev, 0x40,0,0);
}
return 0;
#endif /* HACK */
}
#define tm6000_wrt(dev,req,reg,val, data...) \
{ const static u8 _val[] = data; \
tm6000_read_write_usb(dev,USB_DIR_OUT | USB_TYPE_VENDOR, \
req,reg, val, (u8 *) _val, ARRAY_SIZE(_val)); \
}
/*
TM5600/6000 register values to set video standards.
There's an adjust, common to all, for composite video
Additional adjustments are required for S-Video, based on std.
Standards values for TV S-Video Changes
REG PAL PAL_M PAL_N SECAM NTSC Comp. PAL PAL_M PAL_N SECAM NTSC
0xdf 0x1f 0x1f 0x1f 0x1f 0x1f
0xe2 0x00 0x00 0x00 0x00 0x00
0xe8 0x0f 0x0f 0x0f 0x0f 0x0f 0x00 0x00 0x00 0x00 0x00
0xeb 0x60 0x60 0x60 0x60 0x60 0x64 0x64 0x64 0x64 0x64 0x64
0xd5 0x5f 0x5f 0x5f 0x4f 0x4f 0x4f 0x4f 0x4f 0x4f 0x4f
0xe3 0x00 0x00 0x00 0x00 0x00 0x10 0x10 0x10 0x10 0x10 0x10
0xe5 0x00 0x00 0x00 0x00 0x00 0x10 0x10 0x10 0x10 0x10
0x3f 0x01 0x01 0x01 0x01 0x01
0x00 0x32 0x04 0x36 0x38 0x00 0x33 0x05 0x37 0x39 0x01
0x01 0x0e 0x0e 0x0e 0x0e 0x0f
0x02 0x5f 0x5f 0x5f 0x5f 0x5f
0x03 0x02 0x00 0x02 0x02 0x00 0x04 0x04 0x04 0x03 0x03
0x07 0x01 0x01 0x01 0x01 0x01 0x00 0x00
0x17 0xcd 0xcd 0xcd 0xcd 0xcd 0x8b
0x18 0x25 0x1e 0x1e 0x24 0x1e
0x19 0xd5 0x83 0x91 0x92 0x8b
0x1a 0x63 0x0a 0x1f 0xe8 0xa2
0x1b 0x50 0xe0 0x0c 0xed 0xe9
0x1c 0x1c 0x1c 0x1c 0x1c 0x1c
0x1d 0xcc 0xcc 0xcc 0xcc 0xcc
0x1e 0xcc 0xcc 0xcc 0xcc 0xcc
0x1f 0xcd 0xcd 0xcd 0xcd 0xcd
0x2e 0x8c 0x88 0x8c 0x8c 0x88 0x88
0x30 0x2c 0x20 0x2c 0x2c 0x22 0x2a 0x22 0x22 0x2a
0x31 0xc1 0x61 0xc1 0xc1 0x61
0x33 0x0c 0x0c 0x0c 0x2c 0x1c
0x35 0x1c 0x1c 0x1c 0x18 0x1c
0x82 0x52 0x52 0x52 0x42 0x42
0x04 0xdc 0xdc 0xdc 0xdd
0x0d 0x07 0x07 0x07 0x87 0x07
0x3f 0x00 0x00 0x00 0x00 0x00
*/
int tm6000_set_standard (struct tm6000_core *dev, v4l2_std_id *norm)
{
dev->norm=*norm;
/* HACK: Should use, instead, the common code!!! */
if (*norm & V4L2_STD_PAL_M) {
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xdf, 0x1f);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xe2, 0x00);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xe8, 0x0f);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xeb, 0x60);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xd5, 0x5f);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xe3, 0x00);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xe5, 0x00);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x3f, 0x01);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00, 0x04);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x01, 0x0e);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x02, 0x5f);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x03, 0x00);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x07, 0x01);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x18, 0x1e);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x19, 0x83);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1a, 0x0a);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1b, 0xe0);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1c, 0x1c);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1d, 0xcc);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1e, 0xcc);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1f, 0xcd);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x2e, 0x88);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x30, 0x20);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x31, 0x61);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x33, 0x0c);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x35, 0x1c);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x82, 0x52);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x04, 0xdc);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x0d, 0x07);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x3f, 0x00);
return 0;
}
/* */
// tm6000_set_reg (dev, REQ_04_EN_DISABLE_MCU_INT, 0x02, 0x01);
// tm6000_set_reg (dev, REQ_04_EN_DISABLE_MCU_INT, 0x02, 0x00);
/* Set registers common to all standards */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xdf, 0x1f);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xe2, 0x00);
switch (dev->input) {
case TM6000_INPUT_TV:
/* Seems to disable ADC2 - needed for TV and RCA */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xe8, 0x0f);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xeb, 0x60);
if (*norm & V4L2_STD_PAL) {
/* Enable UV_FLT_EN */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xd5, 0x5f);
} else {
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xd5, 0x4f);
}
/* E3: Select input 0 */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xe3, 0x00);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xe5, 0x10);
break;
case TM6000_INPUT_COMPOSITE:
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xeb, 0x64);
/* E3: Select input 1 */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xe3, 0x10);
break;
case TM6000_INPUT_SVIDEO:
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xe8, 0x00);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xeb, 0x64);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xd5, 0x4f);
/* E3: Select input 1 */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xe3, 0x10);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xe5, 0x10);
break;
}
/* Software reset */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x3f, 0x01);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x02, 0x5f);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x07, 0x01);
// tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x17, 0xcd);
/* Horizontal Sync DTO = 0x1ccccccd */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1c, 0x1c);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1d, 0xcc);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1e, 0xcc);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1f, 0xcd);
/* Vertical Height */
if (*norm & V4L2_STD_525_60) {
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x31, 0x61);
} else {
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x31, 0xc1);
}
/* Horizontal Length */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x2f, 640/8);
if (*norm & V4L2_STD_PAL) {
/* Common to All PAL Standards */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x01, 0x0e);
/* Vsync Hsinc Lockout End */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x33, 0x0c);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x35, 0x1c);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x82, 0x52);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x04, 0xdc);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x0d, 0x07);
if (*norm & V4L2_STD_PAL_M) {
/* Chroma DTO */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x18, 0x1e);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x19, 0x83);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1a, 0x0a);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1b, 0xe0);
/* Active Video Horiz Start Time */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x2e, 0x88);
if (dev->input==TM6000_INPUT_SVIDEO) {
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00, 0x05);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x03, 0x04);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x30, 0x22);
} else {
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00, 0x04);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x03, 0x00);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x30, 0x20);
}
} else if (*norm & V4L2_STD_PAL_N) {
/* Chroma DTO */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x18, 0x1e);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x19, 0x91);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1a, 0x1f);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1b, 0x0c);
if (dev->input==TM6000_INPUT_SVIDEO) {
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00, 0x37);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x03, 0x04);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x2e, 0x88);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x30, 0x22);
} else {
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00, 0x36);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x03, 0x02);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x2e, 0x8c);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x30, 0x2c);
}
} else { // Other PAL standards
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x18, 0x25);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x19, 0xd5);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1a, 0x63);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1b, 0x50);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x2e, 0x8c);
if (dev->input==TM6000_INPUT_SVIDEO) {
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00, 0x33);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x03, 0x04);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x30, 0x2a);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x30, 0x2c);
} else {
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00, 0x32);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x03, 0x02);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x30, 0x2c);
}
}
} if (*norm & V4L2_STD_SECAM) {
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x01, 0x0e);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x18, 0x24);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x19, 0x92);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1a, 0xe8);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1b, 0xed);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x2e, 0x8c);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x33, 0x2c);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x35, 0x18);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x82, 0x42);
// Register 0x04 is not initialized on SECAM
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x0d, 0x87);
if (dev->input==TM6000_INPUT_SVIDEO) {
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00, 0x39);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x03, 0x03);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x30, 0x2a);
} else {
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00, 0x38);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x03, 0x02);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x30, 0x2c);
}
} else { /* NTSC */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x01, 0x0f);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x18, 0x1e);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x19, 0x8b);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1a, 0xa2);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x1b, 0xe9);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x2e, 0x88);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x30, 0x22);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x33, 0x1c);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x35, 0x1c);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x82, 0x42);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x0d, 0x07);
if (dev->input==TM6000_INPUT_SVIDEO) {
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00, 0x01);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x03, 0x03);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x07, 0x00);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x17, 0x8b);
} else {
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00, 0x00);
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x03, 0x00);
}
}
/* End of software reset */
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x3f, 0x00);
msleep(40);
return 0;
}
int tm6000_set_audio_bitrate (struct tm6000_core *dev, int bitrate)
{
int val;
val=tm6000_get_reg (dev, REQ_07_SET_GET_AVREG, 0xeb, 0x0);
printk("Original value=%d\n",val);
if (val<0)
return val;
val &= 0x0f; /* Preserve the audio input control bits */
switch (bitrate) {
case 44100:
val|=0xd0;
break;
case 48000:
val|=0x60;
break;
}
val=tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xeb, val);
return val;
}

View File

@ -0,0 +1,460 @@
/*
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;
}

View File

@ -0,0 +1,76 @@
/*
tm6000-regs.h - 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.
*/
/*
* Define TV Master TM5600/TM6000 Request codes
*/
#define REQ_00_SET_IR_VALUE 0
#define REQ_01_SET_WAKEUP_IRCODE 1
#define REQ_02_GET_IR_CODE 2
#define REQ_03_SET_GET_MCU_PIN 3
#define REQ_04_EN_DISABLE_MCU_INT 4
#define REQ_05_SET_GET_USBREG 5
/* Write: RegNum, Value, 0 */
/* Read : RegNum, Value, 1, RegStatus */
#define REQ_06_SET_GET_USBREG_BIT 6
#define REQ_07_SET_GET_AVREG 7
/* Write: RegNum, Value, 0 */
/* Read : RegNum, Value, 1, RegStatus */
#define REQ_08_SET_GET_AVREG_BIT 8
#define REQ_09_SET_GET_TUNER_FQ 9
#define REQ_10_SET_TUNER_SYSTEM 10
#define REQ_11_SET_EEPROM_ADDR 11
#define REQ_12_SET_GET_EEPROMBYTE 12
#define REQ_13_GET_EEPROM_SEQREAD 13
#define REQ_14_SET_GET_EEPROM_PAGE 14
#define REQ_15_SET_GET_I2CBYTE 15
/* Write: Subaddr, Slave Addr, value, 0 */
/* Read : Subaddr, Slave Addr, value, 1 */
#define REQ_16_SET_GET_I2CSEQ 16
/* Subaddr, Slave Addr, 0, length */
#define REQ_17_SET_GET_I2CFP 17
/* Write: Slave Addr, register, value */
/* Read : Slave Addr, register, 2, data */
/*
* Define TV Master TM5600/TM6000 GPIO lines
*/
#define TM6000_GPIO_CLK 0x101
#define TM6000_GPIO_DATA 0x100
#define TM6000_GPIO_1 0x102
#define TM6000_GPIO_2 0x103
#define TM6000_GPIO_3 0x104
#define TM6000_GPIO_4 0x300
#define TM6000_GPIO_5 0x301
#define TM6000_GPIO_6 0x304
#define TM6000_GPIO_7 0x305
/*
* Define TV Master TM5600/TM6000 URB message codes and length
*/
#define TM6000_URB_MSG_LEN 180
enum {
TM6000_URB_MSG_VIDEO=1,
TM6000_URB_MSG_AUDIO,
TM6000_URB_MSG_VBI,
TM6000_URB_MSG_PTS,
TM6000_URB_MSG_ERR,
};

View File

@ -0,0 +1,41 @@
/*
tm6000-buf.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/videodev2.h>
struct usb_isoc_ctl {
/* max packet size of isoc transaction */
int max_pkt_size;
/* number of allocated urbs */
int num_bufs;
/* urb for isoc transfers */
struct urb **urb;
/* transfer buffers for isoc transfer */
char **transfer_buffer;
/* Last buffer command and region */
u8 cmd;
int pos, size, pktsize;
/* Last field: ODD or EVEN? */
int field;
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,230 @@
/*
tm6000.h - 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.
*/
// Use the tm6000-hack, instead of the proper initialization code
//#define HACK 1
#include <linux/videodev2.h>
#include <media/v4l2-common.h>
#include <media/videobuf-vmalloc.h>
#include "tm6000-usb-isoc.h"
#include <linux/i2c.h>
#include <linux/mutex.h>
#define TM6000_VERSION KERNEL_VERSION(0, 0, 1)
/* Inputs */
#define TM6000_INPUT_TV 0
#define TM6000_INPUT_COMPOSITE 1
#define TM6000_INPUT_SVIDEO 2
/* ------------------------------------------------------------------
Basic structures
------------------------------------------------------------------*/
struct tm6000_fmt {
char *name;
u32 fourcc; /* v4l2 format id */
int depth;
};
/* buffer for one video frame */
struct tm6000_buffer {
/* common v4l buffer stuff -- must be first */
struct videobuf_buffer vb;
struct tm6000_fmt *fmt;
};
struct tm6000_dmaqueue {
struct list_head active;
struct list_head queued;
struct timer_list timeout;
/* thread for generating video stream*/
struct task_struct *kthread;
wait_queue_head_t wq;
/* Counters to control fps rate */
int frame;
int ini_jiffies;
};
/* device states */
enum tm6000_core_state {
DEV_INITIALIZED = 0x01,
DEV_DISCONNECTED = 0x02,
DEV_MISCONFIGURED = 0x04,
};
/* io methods */
enum tm6000_io_method {
IO_NONE,
IO_READ,
IO_MMAP,
};
enum tm6000_mode {
TM6000_MODE_UNKNOWN=0,
TM6000_MODE_ANALOG,
TM6000_MODE_DIGITAL,
};
struct tm6000_capabilities {
unsigned int has_tuner:1;
unsigned int has_tda9874:1;
unsigned int has_dvb:1;
unsigned int has_zl10353:1;
unsigned int has_eeprom:1;
};
struct tm6000_core {
/* generic device properties */
char name[30]; /* name (including minor) of the device */
int model; /* index in the device_data struct */
int devno; /* marks the number of this device */
v4l2_std_id norm; /* Current norm */
enum tm6000_core_state state;
/* Device Capabilities*/
struct tm6000_capabilities caps;
/* Tuner configuration */
int tuner_type; /* type of the tuner */
int tuner_addr; /* tuner address */
/* i2c i/o */
struct i2c_adapter i2c_adap;
struct i2c_client i2c_client;
/* video for linux */
struct list_head tm6000_corelist;
int users;
/* various device info */
unsigned int resources;
struct video_device vfd;
struct tm6000_dmaqueue vidq;
int input;
int freq;
unsigned int fourcc;
enum tm6000_mode mode;
/* locks */
struct mutex lock;
/* usb transfer */
struct usb_device *udev; /* the usb device */
struct usb_host_endpoint *bulk_in, *bulk_out, *isoc_in, *isoc_out;
unsigned int max_bulk_in, max_bulk_out;
unsigned int max_isoc_in, max_isoc_out;
/* scaler!=0 if scaler is active*/
int scaler;
/* Isoc control struct */
struct usb_isoc_ctl isoc_ctl;
spinlock_t slock;
};
struct tm6000_fh {
struct tm6000_core *dev;
/* video capture */
struct tm6000_fmt *fmt;
unsigned int width,height;
struct videobuf_queue vb_vidq;
enum v4l2_buf_type type;
};
#define TM6000_STD V4L2_STD_PAL|V4L2_STD_PAL_N|V4L2_STD_PAL_Nc| \
V4L2_STD_PAL_M|V4L2_STD_PAL_60|V4L2_STD_NTSC_M| \
V4L2_STD_NTSC_M_JP|V4L2_STD_SECAM
/* In tm6000-core.c */
extern unsigned long tm6000_devused;
int tm6000_read_write_usb (struct tm6000_core *dev, u8 reqtype, u8 req,
u16 value, u16 index, u8 *buf, u16 len);
int tm6000_get_reg (struct tm6000_core *dev, u8 req, u16 value, u16 index);
int tm6000_set_reg (struct tm6000_core *dev, u8 req, u16 value, u16 index);
int tm6000_init (struct tm6000_core *dev);
int tm6000_init_after_firmware (struct tm6000_core *dev);
int tm6000_init_analog_mode (struct tm6000_core *dev);
int tm6000_set_standard (struct tm6000_core *dev, v4l2_std_id *norm);
int tm6000_set_audio_bitrate (struct tm6000_core *dev, int bitrate);
int tm6000_v4l2_register(struct tm6000_core *dev);
int tm6000_v4l2_unregister(struct tm6000_core *dev);
int tm6000_v4l2_exit(void);
void tm6000_set_fourcc_format(struct tm6000_core *dev);
/* In tm6000-i2c.c */
int tm6000_i2c_register(struct tm6000_core *dev);
int tm6000_i2c_unregister(struct tm6000_core *dev);
void tm6000_i2c_call_clients(struct tm6000_core *dev, unsigned int cmd,
void *arg);
/* In tm6000-queue.c */
int tm6000_v4l2_mmap(struct file *filp, struct vm_area_struct *vma);
int tm6000_vidioc_streamon(struct file *file, void *priv,
enum v4l2_buf_type i);
int tm6000_vidioc_streamoff(struct file *file, void *priv,
enum v4l2_buf_type i);
int tm6000_vidioc_reqbufs (struct file *file, void *priv,
struct v4l2_requestbuffers *rb);
int tm6000_vidioc_querybuf (struct file *file, void *priv,
struct v4l2_buffer *b);
int tm6000_vidioc_qbuf (struct file *file, void *priv, struct v4l2_buffer *b);
int tm6000_vidioc_dqbuf (struct file *file, void *priv, struct v4l2_buffer *b);
ssize_t tm6000_v4l2_read(struct file *filp, char __user * buf, size_t count,
loff_t * f_pos);
unsigned int tm6000_v4l2_poll(struct file *file,
struct poll_table_struct *wait);
int tm6000_queue_init(struct tm6000_core *dev);
/* Debug stuff */
extern int tm6000_debug;
#define dprintk(dev, level, fmt, arg...) do {\
if (tm6000_debug & level) \
printk(KERN_INFO "(%lu) %s %s :"fmt, jiffies, \
dev->name, __FUNCTION__ , ##arg); } while (0)
#define V4L2_DEBUG_REG 0x0004
#define V4L2_DEBUG_I2C 0x0008
#define V4L2_DEBUG_QUEUE 0x0010
#define V4L2_DEBUG_ISOC 0x0020
#define V4L2_DEBUG_RES_LOCK 0x0040 /* Resource locking */
#define V4L2_DEBUG_OPEN 0x0080 /* video open/close debug */
#define tm6000_err(fmt, arg...) do {\
printk(KERN_ERR "tm6000 %s :"fmt, \
__FUNCTION__ , ##arg); } while (0)

View File

@ -369,6 +369,7 @@ struct v4l2_pix_format {
#define V4L2_PIX_FMT_OV511 v4l2_fourcc('O', '5', '1', '1') /* ov511 JPEG */
#define V4L2_PIX_FMT_OV518 v4l2_fourcc('O', '5', '1', '8') /* ov518 JPEG */
#define V4L2_PIX_FMT_STV0680 v4l2_fourcc('S', '6', '8', '0') /* stv0680 bayer */
#define V4L2_PIX_FMT_TM6000 v4l2_fourcc('T', 'M', '6', '0') /* tm5600/tm60x0 */
/*
* F O R M A T E N U M E R A T I O N