Commit 71f32267 authored by Mauro Carvalho Chehab's avatar Mauro Carvalho Chehab

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 9444a960
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.
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
/*
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");
/*
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;
}
/*
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;
}
/*
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,
};
/*
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;
};
/*
tm6000-video.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/delay.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/random.h>
#include <linux/version.h>
#include <linux/usb.h>
#include <linux/videodev2.h>
#ifdef CONFIG_VIDEO_V4L1_COMPAT
#include <linux/videodev.h>
#endif
#include <linux/interrupt.h>
#include <linux/kthread.h>
#include <linux/highmem.h>
#include <linux/freezer.h>
#include "tm6000-regs.h"
#include "tm6000.h"
#define BUFFER_TIMEOUT msecs_to_jiffies(2000) /* 2 seconds */
/* Declare static vars that will be used as parameters */
static unsigned int vid_limit = 16; /* Video memory limit, in Mb */
static int video_nr = -1; /* /dev/videoN, -1 for autodetect */
unsigned long tm6000_devused;
/* Debug level */
int tm6000_debug;
/* supported controls */
static struct v4l2_queryctrl tm6000_qctrl[] = {
{
.id = V4L2_CID_BRIGHTNESS,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Brightness",
.minimum = 0,
.maximum = 255,
.step = 1,
.default_value = 54,
.flags = 0,
}, {
.id = V4L2_CID_CONTRAST,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Contrast",
.minimum = 0,
.maximum = 255,
.step = 0x1,
.default_value = 119,
.flags = 0,
}, {
.id = V4L2_CID_SATURATION,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Saturation",
.minimum = 0,
.maximum = 255,
.step = 0x1,
.default_value = 112,
.flags = 0,
}, {
.id = V4L2_CID_HUE,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Hue",
.minimum = -128,
.maximum = 127,
.step = 0x1,
.default_value = 0, //4 ?
.flags = 0,
}
};
static int qctl_regs[ARRAY_SIZE(tm6000_qctrl)];
static struct tm6000_fmt format[] = {
{
.name = "4:2:2, packed, YVY2",
.fourcc = V4L2_PIX_FMT_YUYV,
.depth = 16,
},{
.name = "4:2:2, packed, UYVY",
.fourcc = V4L2_PIX_FMT_UYVY,
.depth = 16,
},{
.name = "A/V + VBI mux packet",
.fourcc = V4L2_PIX_FMT_TM6000,
.depth = 16,
}
};
static LIST_HEAD(tm6000_corelist);
/* ------------------------------------------------------------------
DMA and thread functions
------------------------------------------------------------------*/
#define norm_maxw(a) 720
#define norm_maxh(a) 480
//#define norm_minw(a) norm_maxw(a)
#define norm_minw(a) norm_maxw(a)
#define norm_minh(a) norm_maxh(a)
/*
* video-buf generic routine to get the next available buffer
*/
static int inline get_next_buf (struct tm6000_dmaqueue *dma_q,
struct tm6000_buffer **buf)
{
struct tm6000_core *dev= container_of(dma_q,struct tm6000_core,vidq);
if (list_empty(&dma_q->active)) {
dprintk(dev, V4L2_DEBUG_QUEUE,"No active queue to serve\n");
return 0;
}
*buf = list_entry(dma_q->active.next,
struct tm6000_buffer, vb.queue);
/* Nobody is waiting something to be done, just return */
if (!waitqueue_active(&(*buf)->vb.done)) {
mod_timer(&dma_q->timeout, jiffies+BUFFER_TIMEOUT);
return -1;
}
return 1;
}
/*
* Announces that a buffer were filled and request the next
*/
static void inline buffer_filled (struct tm6000_core *dev,
struct tm6000_buffer *buf)
{
/* Advice that buffer was filled */
dprintk(dev, V4L2_DEBUG_QUEUE, "[%p/%d] wakeup\n",buf,buf->vb.i);
buf->vb.state = STATE_DONE;
buf->vb.field_count++;
do_gettimeofday(&buf->vb.ts);
list_del(&buf->vb.queue);
wake_up(&buf->vb.done);
}
/*
* Macro to allow copying data into the proper memory type
*/
#define bufcpy(buf,out_ptr,in_ptr,size) \
{ \
if (__copy_to_user(out_ptr,in_ptr,size)!=0) \
tm6000_err("copy_to_user failed.\n"); \
}
/*
* Identify the tm5600/6000 buffer header type and properly handles
*/
static int copy_streams(u8 *data, u8 *out_p, unsigned long len,
struct urb *urb, struct tm6000_buffer **buf)
{
struct tm6000_dmaqueue *dma_q = urb->context;
struct tm6000_core *dev= container_of(dma_q,struct tm6000_core,vidq);
u8 *ptr=data, *endp=data+len;
u8 c;
unsigned int cmd, cpysize, pktsize, size, field, block, line, pos=0;
unsigned long header;
int rc=0;
/* FIXME: this is the hardcoded window size
*/
unsigned int linesize=720*2;
//static int last_line=-2;
for (ptr=data; ptr<endp;) {
if (!dev->isoc_ctl.cmd) {
/* Seek for sync */
for (ptr+=3;ptr<endp;ptr++) {
if (*ptr==0x47) {
ptr-=3;
break;
}
}
if (ptr>=endp)
return rc;
/* Get message header */
header=*(unsigned long *)ptr;
ptr+=4;
c=(header>>24) & 0xff;
/* split the header fields */
size = (((header & 0x7e)<<1) -1) *4;
block = (header>>7) & 0xf;
field = (header>>11) & 0x1;
line = (header>>12) & 0x1ff;
cmd = (header>>21) & 0x7;
/* FIXME: Maximum possible line is 511.
* This doesn't seem to be enough for PAL standards
*/
/* Validates header fields */
if(size>TM6000_URB_MSG_LEN)
size=TM6000_URB_MSG_LEN;
if(block>=8)
cmd = TM6000_URB_MSG_ERR;
/* FIXME: Mounts the image as field0+field1
* It should, instead, check if the user selected
* entrelaced or non-entrelaced mode
*/
pos=((line<<1)+field)*linesize+
block*TM6000_URB_MSG_LEN;
/* Don't allow to write out of the buffer */
if (pos+TM6000_URB_MSG_LEN > (*buf)->vb.size)
cmd = TM6000_URB_MSG_ERR;
/* Prints debug info */
dprintk(dev, V4L2_DEBUG_ISOC, "size=%d, num=%d, "
" line=%d, field=%d\n",
size, block, line, field);
dev->isoc_ctl.cmd = cmd;
dev->isoc_ctl.size = size;
dev->isoc_ctl.pos = pos;
dev->isoc_ctl.pktsize = pktsize = TM6000_URB_MSG_LEN;
} else {
cmd = dev->isoc_ctl.cmd;
size= dev->isoc_ctl.size;
pos = dev->isoc_ctl.pos;
pktsize = dev->isoc_ctl.pktsize;
}
cpysize=(endp-ptr>size)?size:endp-ptr;
if (cpysize) {
/* handles each different URB message */
switch(cmd) {
case TM6000_URB_MSG_VIDEO:
/* Fills video buffer */
bufcpy(*buf,&out_p[pos],ptr,cpysize);
break;
}
}
if (cpysize<size) {
/* End of URB packet, but cmd processing is not
* complete. Preserve the state for a next packet
*/
dev->isoc_ctl.pos = pos+cpysize;
dev->isoc_ctl.size= size-cpysize;
dev->isoc_ctl.cmd = cmd;
dev->isoc_ctl.pktsize = pktsize-cpysize;
ptr+=cpysize;
} else {
dev->isoc_ctl.cmd = 0;
ptr+=pktsize;
}
}
return rc;
}
/*
* Identify the tm5600/6000 buffer header type and properly handles
*/
static int copy_multiplexed(u8 *ptr, u8 *out_p, unsigned long len,
struct urb *urb, struct tm6000_buffer **buf)
{
struct tm6000_dmaqueue *dma_q = urb->context;
struct tm6000_core *dev= container_of(dma_q,struct tm6000_core,vidq);
unsigned int pos=dev->isoc_ctl.pos,cpysize;
int rc=1;
while (len>0) {
cpysize=min(len,(*buf)->vb.size-pos);
//printk("Copying %d bytes (max=%lu) from %p to %p[%u]\n",cpysize,(*buf)->vb.size,ptr,out_p,pos);
bufcpy(*buf,&out_p[pos],ptr,cpysize);
pos+=cpysize;
ptr+=cpysize;
len-=cpysize;
if (pos >= (*buf)->vb.size) {
pos=0;
/* Announces that a new buffer were filled */
buffer_filled (dev, *buf);
dprintk(dev, V4L2_DEBUG_QUEUE, "new buffer filled\n");
rc=get_next_buf (dma_q, buf);
if (rc<=0) {
*buf=NULL;
printk(KERN_ERR "tm6000: buffer underrun\n");
break;
}
}
}
dev->isoc_ctl.pos=pos;
return rc;
}
/*
* Controls the isoc copy of each urb packet
*/
static inline int tm6000_isoc_copy(struct urb *urb, struct tm6000_buffer **buf)
{
struct tm6000_dmaqueue *dma_q = urb->context;
struct tm6000_core *dev= container_of(dma_q,struct tm6000_core,vidq);
void *outp=videobuf_to_vmalloc (&((*buf)->vb));
int i, len=0, rc=1;
int size=(*buf)->vb.size;
char *p;
unsigned long copied;
copied=0;
for (i = 0; i < urb->number_of_packets; i++) {
int status = urb->iso_frame_desc[i].status;
char *errmsg = "Unknown";
switch(status) {
case -ENOENT:
errmsg = "unlinked synchronuously";
break;
case -ECONNRESET:
errmsg = "unlinked asynchronuously";
break;
case -ENOSR:
errmsg = "Buffer error (overrun)";
break;
case -EPIPE:
errmsg = "Stalled (device not responding)";
break;
case -EOVERFLOW:
errmsg = "Babble (bad cable?)";
break;
case -EPROTO:
errmsg = "Bit-stuff error (bad cable?)";
break;
case -EILSEQ:
errmsg = "CRC/Timeout (could be anything)";
break;
case -ETIME:
errmsg = "Device does not respond";
break;
}
dprintk(dev, V4L2_DEBUG_QUEUE, "URB status %d [%s].\n",
status, errmsg);
if (status<0)
continue;
len=urb->iso_frame_desc[i].actual_length;
if (len>=TM6000_URB_MSG_LEN) {
p=urb->transfer_buffer + urb->iso_frame_desc[i].offset;
if (!urb->iso_frame_desc[i].status) {
if (((*buf)->fmt->fourcc)==V4L2_PIX_FMT_TM6000) {
rc=copy_multiplexed(p,outp,len,urb,buf);
if (rc<=0)
return rc;
} else {
rc=copy_streams(p,outp,len,urb,buf);
}
}
copied += len;
if (copied>=size)
break;
}
}
if (((*buf)->fmt->fourcc)!=V4L2_PIX_FMT_TM6000) {
buffer_filled (dev, *buf);
dprintk(dev, V4L2_DEBUG_QUEUE, "new buffer filled\n");
}
return rc;
}
/* ------------------------------------------------------------------
URB control
------------------------------------------------------------------*/
/*
* IRQ callback, called by URB callback
*/
static void tm6000_irq_callback(struct urb *urb)
{
struct tm6000_buffer *buf;
struct tm6000_dmaqueue *dma_q = urb->context;
struct tm6000_core *dev= container_of(dma_q,struct tm6000_core,vidq);
int rc,i;
unsigned long flags;
spin_lock_irqsave(&dev->slock,flags);
rc=get_next_buf (dma_q, &buf);
if (rc<=0)
goto ret;
/* Copy data from URB */
rc=tm6000_isoc_copy(urb, &buf);
ret:
/* Reset urb buffers */
for (i = 0; i < urb->number_of_packets; i++) {
urb->iso_frame_desc[i].status = 0;
urb->iso_frame_desc[i].actual_length = 0;
}
urb->status = 0;
if ((urb->status = usb_submit_urb(urb, GFP_ATOMIC))) {
tm6000_err("urb resubmit failed (error=%i)\n",
urb->status);
}
if (rc>=0) {
if (!rc) {
dprintk(dev, V4L2_DEBUG_QUEUE, "No active queue to serve\n");
del_timer(&dma_q->timeout);
} else {
/* Data filled, reset watchdog */
mod_timer(&dma_q->timeout, jiffies+BUFFER_TIMEOUT);
}
}
spin_unlock_irqrestore(&dev->slock,flags);
}
/*
* Stop and Deallocate URBs
*/
static void tm6000_uninit_isoc(struct tm6000_core *dev)
{
struct urb *urb;
int i;
for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
urb=dev->isoc_ctl.urb[i];
if (urb) {
usb_kill_urb(urb);
usb_unlink_urb(urb);
if (dev->isoc_ctl.transfer_buffer[i]) {
usb_buffer_free(dev->udev,
urb->transfer_buffer_length,
dev->isoc_ctl.transfer_buffer[i],
urb->transfer_dma);
}
usb_free_urb(urb);
dev->isoc_ctl.urb[i] = NULL;
}
dev->isoc_ctl.transfer_buffer[i] = NULL;
}
kfree (dev->isoc_ctl.urb);
kfree (dev->isoc_ctl.transfer_buffer);
dev->isoc_ctl.urb=NULL;
dev->isoc_ctl.transfer_buffer=NULL;
dev->isoc_ctl.num_bufs=0;
}
/*
* Stop video thread - FIXME: Can be easily removed
*/
static void tm6000_stop_thread(struct tm6000_dmaqueue *dma_q)
{
struct tm6000_core *dev= container_of(dma_q,struct tm6000_core,vidq);
tm6000_uninit_isoc(dev);
}
/*
* Allocate URBs and start IRQ
*/
static int tm6000_prepare_isoc(struct tm6000_core *dev,
int max_packets, int num_bufs)
{
struct tm6000_dmaqueue *dma_q = &dev->vidq;
int i;
int sb_size, pipe;
struct urb *urb;
int j, k;
/* De-allocates all pending stuff */
tm6000_uninit_isoc(dev);
dev->isoc_ctl.num_bufs=num_bufs;
dev->isoc_ctl.urb=kmalloc(sizeof(void *)*num_bufs,
GFP_KERNEL);
if (!dev->isoc_ctl.urb) {
tm6000_err("cannot alloc memory for usb buffers\n");
return -ENOMEM;
}
dev->isoc_ctl.transfer_buffer=kmalloc(sizeof(void *)*num_bufs,
GFP_KERNEL);
if (!dev->isoc_ctl.urb) {
tm6000_err("cannot allocate memory for usbtransfer\n");
kfree(dev->isoc_ctl.urb);
return -ENOMEM;
}
dev->isoc_ctl.max_pkt_size=dev->max_isoc_in;
sb_size = max_packets * dev->isoc_ctl.max_pkt_size;
/* allocate urbs and transfer buffers */
for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
urb = usb_alloc_urb(max_packets, GFP_KERNEL);
if (!urb) {
tm6000_err("cannot alloc isoc_ctl.urb %i\n", i);
tm6000_uninit_isoc(dev);
return -ENOMEM;
}
dev->isoc_ctl.urb[i] = urb;
dev->isoc_ctl.transfer_buffer[i] = usb_buffer_alloc(dev->udev,
sb_size, GFP_KERNEL,
&dev->isoc_ctl.urb[i]->transfer_dma);
if (!dev->isoc_ctl.transfer_buffer[i]) {
tm6000_err ("unable to allocate %i bytes for transfer"
" buffer %i\n", sb_size, i);
tm6000_uninit_isoc(dev);
return -ENOMEM;
}
memset(dev->isoc_ctl.transfer_buffer[i], 0, sb_size);
pipe=usb_rcvisocpipe(dev->udev,
dev->isoc_in->desc.bEndpointAddress &
USB_ENDPOINT_NUMBER_MASK);
usb_fill_int_urb(urb, dev->udev, pipe,
dev->isoc_ctl.transfer_buffer[i],sb_size,
tm6000_irq_callback, dma_q,
dev->isoc_in->desc.bInterval);
urb->number_of_packets = max_packets;
urb->transfer_flags = URB_ISO_ASAP;
k = 0;
for (j = 0; j < max_packets; j++) {
urb->iso_frame_desc[j].offset = k;
urb->iso_frame_desc[j].length =
dev->isoc_ctl.max_pkt_size;
k += dev->isoc_ctl.max_pkt_size;
}
}
return 0;
}
static int tm6000_start_thread( struct tm6000_dmaqueue *dma_q,
struct tm6000_buffer *buf)
{
struct tm6000_core *dev= container_of(dma_q,struct tm6000_core,vidq);
int i,rc;
dma_q->frame=0;
dma_q->ini_jiffies=jiffies;
init_waitqueue_head(&dma_q->wq);
/* submit urbs and enables IRQ */
for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
rc = usb_submit_urb(dev->isoc_ctl.urb[i], GFP_KERNEL);
if (rc) {
tm6000_err("submit of urb %i failed (error=%i)\n", i,
rc);
tm6000_uninit_isoc(dev);
return rc;
}
}
if (rc<0)
return rc;
return 0;
}
static int restart_video_queue(struct tm6000_dmaqueue *dma_q)
{
struct tm6000_core *dev= container_of(dma_q,struct tm6000_core,vidq);
struct tm6000_buffer *buf, *prev;
struct list_head *item;
dprintk(dev, V4L2_DEBUG_QUEUE, "%s dma_q=0x%08lx\n",
__FUNCTION__,(unsigned long)dma_q);
if (!list_empty(&dma_q->active)) {
buf = list_entry(dma_q->active.next, struct tm6000_buffer, vb.queue);
dprintk(dev, V4L2_DEBUG_QUEUE,
"restart_queue [%p/%d]: restart dma\n", buf, buf->vb.i);
dprintk(dev, V4L2_DEBUG_QUEUE, "Restarting video dma\n");
tm6000_stop_thread(dma_q);
tm6000_start_thread(dma_q, buf);
/* cancel all outstanding capture / vbi requests */
list_for_each(item,&dma_q->active) {
buf = list_entry(item, struct tm6000_buffer, vb.queue);
list_del(&buf->vb.queue);
buf->vb.state = STATE_ERROR;
wake_up(&buf->vb.done);
}
mod_timer(&dma_q->timeout, jiffies+BUFFER_TIMEOUT);
return 0;
}
prev = NULL;
for (;;) {
if (list_empty(&dma_q->queued))
return 0;
buf = list_entry(dma_q->queued.next, struct tm6000_buffer, vb.queue);
if (NULL == prev) {
list_del(&buf->vb.queue);
list_add_tail(&buf->vb.queue,&dma_q->active);
dprintk(dev, V4L2_DEBUG_QUEUE, "Restarting video dma\n");
tm6000_stop_thread(dma_q);
tm6000_start_thread(dma_q, buf);
buf->vb.state = STATE_ACTIVE;
mod_timer(&dma_q->timeout, jiffies+BUFFER_TIMEOUT);
dprintk(dev, V4L2_DEBUG_QUEUE, "[%p/%d] restart_queue -"
" first active\n", buf, buf->vb.i);
} else if (prev->vb.width == buf->vb.width &&
prev->vb.height == buf->vb.height &&
prev->fmt == buf->fmt) {
list_del(&buf->vb.queue);
list_add_tail(&buf->vb.queue,&dma_q->active);
buf->vb.state = STATE_ACTIVE;
dprintk(dev, V4L2_DEBUG_QUEUE, "[%p/%d] restart_queue -"
" move to active\n",buf,buf->vb.i);
} else {
return 0;
}
prev = buf;
}
}
static void tm6000_vid_timeout(unsigned long data)
{
struct tm6000_core *dev = (struct tm6000_core*)data;
struct tm6000_dmaqueue *vidq = &dev->vidq;
struct tm6000_buffer *buf;
unsigned long flags;
spin_lock_irqsave(&dev->slock,flags);
while (!list_empty(&vidq->active)) {
buf = list_entry(vidq->active.next, struct tm6000_buffer,
vb.queue);
list_del(&buf->vb.queue);
buf->vb.state = STATE_ERROR;
wake_up(&buf->vb.done);
dprintk(dev, V4L2_DEBUG_QUEUE, "tm6000/0: [%p/%d] timeout\n",
buf, buf->vb.i);
}
restart_video_queue(vidq);
spin_unlock_irqrestore(&dev->slock,flags);
}
/* ------------------------------------------------------------------
Videobuf operations
------------------------------------------------------------------*/
static int
buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size)
{
struct tm6000_fh *fh = vq->priv_data;
*size = fh->fmt->depth * fh->width * fh->height >> 3;
if (0 == *count)
*count = 32;
while (*size * *count > vid_limit * 1024 * 1024)
(*count)--;
return 0;
}
static void free_buffer(struct videobuf_queue *vq, struct tm6000_buffer *buf)
{
if (in_interrupt())
BUG();
videobuf_waiton(&buf->vb,0,0);
videobuf_vmalloc_free(&buf->vb);
buf->vb.state = STATE_NEEDS_INIT;
}
static int
buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
enum v4l2_field field)
{
struct tm6000_fh *fh = vq->priv_data;
struct tm6000_buffer *buf = container_of(vb,struct tm6000_buffer,vb);
struct tm6000_core *dev = fh->dev;
int rc=0, urbsize, urb_init=0;
BUG_ON(NULL == fh->fmt);
if (fh->width < norm_minw(core) || fh->width > norm_maxw(core) ||
fh->height < norm_minh(core) || fh->height > norm_maxh(core)) {
dprintk(dev, V4L2_DEBUG_QUEUE, "Window size (%dx%d) is out of "
"supported range\n", fh->width, fh->height);
dprintk(dev, V4L2_DEBUG_QUEUE, "Valid range is from (%dx%d) to "
"(%dx%d)\n", norm_minw(core), norm_minh(core),
norm_maxw(core),norm_maxh(core));
return -EINVAL;
}
/* FIXME: It assumes depth=2 */
/* The only currently supported format is 16 bits/pixel */
buf->vb.size = fh->fmt->depth*fh->width*fh->height >> 3;
if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size)
return -EINVAL;
if (buf->fmt != fh->fmt ||
buf->vb.width != fh->width ||
buf->vb.height != fh->height ||
buf->vb.field != field) {
buf->fmt = fh->fmt;
buf->vb.width = fh->width;
buf->vb.height = fh->height;
buf->vb.field = field;
buf->vb.state = STATE_NEEDS_INIT;
}
if (STATE_NEEDS_INIT == buf->vb.state) {
if (0 != (rc = videobuf_iolock(vq,&buf->vb,NULL)))
goto fail;
urb_init=1;
}
if (!dev->isoc_ctl.num_bufs)
urb_init=1;
if (urb_init) {
/* Should allocate/request at least h
res x v res x 2 bytes/pixel */
urbsize=(buf->vb.size+dev->max_isoc_in-1)/dev->max_isoc_in;
/* Hack to allocate memory for Video + Audio */
/* FIXME: should also consider header ovehead of
4 bytes/180 bytes */
urbsize+=((48000*4+24)/25+dev->max_isoc_in-1)/dev->max_isoc_in;
dprintk(dev, V4L2_DEBUG_QUEUE, "Allocating %d packets to handle "
"%lu size\n", urbsize,buf->vb.size);
rc = tm6000_prepare_isoc(dev, urbsize, 2);
if (rc<0)
goto fail;
}
buf->vb.state = STATE_PREPARED;
return 0;
fail:
free_buffer(vq,buf);
return rc;
}
static void
buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
{
struct tm6000_buffer *buf = container_of(vb,struct tm6000_buffer,vb);
struct tm6000_fh *fh = vq->priv_data;
struct tm6000_core *dev = fh->dev;
struct tm6000_dmaqueue *vidq = &dev->vidq;
struct tm6000_buffer *prev;
if (!list_empty(&vidq->queued)) {
list_add_tail(&buf->vb.queue,&vidq->queued);
buf->vb.state = STATE_QUEUED;
dprintk(dev, V4L2_DEBUG_QUEUE, "[%p/%d] buffer_queue - "
"append to queued\n", buf, buf->vb.i);
} else if (list_empty(&vidq->active)) {
list_add_tail(&buf->vb.queue,&vidq->active);
buf->vb.state = STATE_ACTIVE;
mod_timer(&vidq->timeout, jiffies+BUFFER_TIMEOUT);
dprintk(dev, V4L2_DEBUG_QUEUE, "[%p/%d] buffer_queue - "
"first active\n", buf, buf->vb.i);
tm6000_start_thread(vidq, buf);
} else {
prev = list_entry(vidq->active.prev, struct tm6000_buffer, vb.queue);
if (prev->vb.width == buf->vb.width &&
prev->vb.height == buf->vb.height &&
prev->fmt == buf->fmt) {
list_add_tail(&buf->vb.queue,&vidq->active);
buf->vb.state = STATE_ACTIVE;
dprintk(dev, V4L2_DEBUG_QUEUE, "[%p/%d] buffer_queue -"
" append to active\n", buf, buf->vb.i);
} else {
list_add_tail(&buf->vb.queue,&vidq->queued);
buf->vb.state = STATE_QUEUED;
dprintk(dev, V4L2_DEBUG_QUEUE, "[%p/%d] buffer_queue -"
" first queued\n", buf, buf->vb.i);
}
}
}
static void buffer_release(struct videobuf_queue *vq, struct videobuf_buffer *vb)
{
struct tm6000_buffer *buf = container_of(vb,struct tm6000_buffer,vb);
struct tm6000_fh *fh = vq->priv_data;
struct tm6000_core *dev = (struct tm6000_core*)fh->dev;
struct tm6000_dmaqueue *vidq = &dev->vidq;
tm6000_stop_thread(vidq);
free_buffer(vq,buf);
}
static struct videobuf_queue_ops tm6000_video_qops = {
.buf_setup = buffer_setup,
.buf_prepare = buffer_prepare,
.buf_queue = buffer_queue,
.buf_release = buffer_release,
};
/* ------------------------------------------------------------------
IOCTL handling
------------------------------------------------------------------*/
static int res_get(struct tm6000_core *dev, struct tm6000_fh *fh)
{
/* is it free? */
mutex_lock(&dev->lock);
if (dev->resources) {
/* no, someone else uses it */
mutex_unlock(&dev->lock);
return 0;
}
/* it's free, grab it */
dev->resources =1;
dprintk(dev, V4L2_DEBUG_RES_LOCK, "res: get\n");
mutex_unlock(&dev->lock);
return 1;
}
static int res_locked(struct tm6000_core *dev)
{
return (dev->resources);
}
static void res_free(struct tm6000_core *dev, struct tm6000_fh *fh)
{
mutex_lock(&dev->lock);
dev->resources = 0;
dprintk(dev, V4L2_DEBUG_RES_LOCK, "res: put\n");
mutex_unlock(&dev->lock);
}
/* ------------------------------------------------------------------
IOCTL vidioc handling
------------------------------------------------------------------*/
static int vidioc_querycap (struct file *file, void *priv,
struct v4l2_capability *cap)
{
// struct tm6000_core *dev = ((struct tm6000_fh *)priv)->dev;
strlcpy(cap->driver, "tm6000", sizeof(cap->driver));
strlcpy(cap->card,"Trident TVMaster TM5600/6000", sizeof(cap->card));
// strlcpy(cap->bus_info, dev->udev->dev.bus_id, sizeof(cap->bus_info));
cap->version = TM6000_VERSION;
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE |
V4L2_CAP_STREAMING |
V4L2_CAP_TUNER |
V4L2_CAP_READWRITE;
return 0;
}
static int vidioc_enum_fmt_cap (struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
if (unlikely(f->index >= ARRAY_SIZE(format)))
return -EINVAL;
strlcpy(f->description,format[f->index].name,sizeof(f->description));
f->pixelformat = format[f->index].fourcc;
return 0;
}
static int vidioc_g_fmt_cap (struct file *file, void *priv,
struct v4l2_format *f)
{
struct tm6000_fh *fh=priv;
f->fmt.pix.width = fh->width;
f->fmt.pix.height = fh->height;
f->fmt.pix.field = fh->vb_vidq.field;
f->fmt.pix.pixelformat = fh->fmt->fourcc;
f->fmt.pix.bytesperline =
(f->fmt.pix.width * fh->fmt->depth) >> 3;
f->fmt.pix.sizeimage =
f->fmt.pix.height * f->fmt.pix.bytesperline;
return (0);
}
static struct tm6000_fmt* format_by_fourcc(unsigned int fourcc)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(format); i++)
if (format[i].fourcc == fourcc)
return format+i;
return NULL;
}
static int vidioc_try_fmt_cap (struct file *file, void *priv,
struct v4l2_format *f)
{
struct tm6000_core *dev = ((struct tm6000_fh *)priv)->dev;
struct tm6000_fmt *fmt;
enum v4l2_field field;
fmt = format_by_fourcc(f->fmt.pix.pixelformat);
if (NULL == fmt) {
dprintk(dev, V4L2_DEBUG_IOCTL_ARG, "Fourcc format (0x%08x)"
" invalid.\n", f->fmt.pix.pixelformat);
return -EINVAL;
}
field = f->fmt.pix.field;
if (field == V4L2_FIELD_ANY) {
// field=V4L2_FIELD_INTERLACED;
field=V4L2_FIELD_SEQ_TB;
} else if (V4L2_FIELD_INTERLACED != field) {
dprintk(dev, V4L2_DEBUG_IOCTL_ARG, "Field type invalid.\n");
return -EINVAL;
}
if (f->fmt.pix.width < norm_minw(core))
f->fmt.pix.width = norm_minw(core);
if (f->fmt.pix.width > norm_maxw(core))
f->fmt.pix.width = norm_maxw(core);
if (f->fmt.pix.height < norm_minh(core))
f->fmt.pix.height = norm_minh(core);
if (f->fmt.pix.height > norm_maxh(core))
f->fmt.pix.height = norm_maxh(core);
f->fmt.pix.width &= ~0x01;
f->fmt.pix.field = field;
f->fmt.pix.bytesperline =
(f->fmt.pix.width * fmt->depth) >> 3;
f->fmt.pix.sizeimage =
f->fmt.pix.height * f->fmt.pix.bytesperline;
return 0;
}
/*FIXME: This seems to be generic enough to be at videodev2 */
static int vidioc_s_fmt_cap (struct file *file, void *priv,
struct v4l2_format *f)
{
struct tm6000_fh *fh=priv;
struct tm6000_core *dev = fh->dev;
int ret = vidioc_try_fmt_cap(file,fh,f);
if (ret < 0)
return (ret);
fh->fmt = format_by_fourcc(f->fmt.pix.pixelformat);
fh->width = f->fmt.pix.width;
fh->height = f->fmt.pix.height;
fh->vb_vidq.field = f->fmt.pix.field;
fh->type = f->type;
dev->fourcc = f->fmt.pix.pixelformat;
tm6000_set_fourcc_format(dev);
return (0);
}
static int vidioc_reqbufs (struct file *file, void *priv,
struct v4l2_requestbuffers *p)
{
struct tm6000_fh *fh=priv;
return (videobuf_reqbufs(&fh->vb_vidq, p));
}
static int vidioc_querybuf (struct file *file, void *priv,
struct v4l2_buffer *p)
{
struct tm6000_fh *fh=priv;
return (videobuf_querybuf(&fh->vb_vidq, p));
}
static int vidioc_qbuf (struct file *file, void *priv, struct v4l2_buffer *p)
{
struct tm6000_fh *fh=priv;
return (videobuf_qbuf(&fh->vb_vidq, p));
}
static int vidioc_dqbuf (struct file *file, void *priv, struct v4l2_buffer *p)
{
struct tm6000_fh *fh=priv;
return (videobuf_dqbuf(&fh->vb_vidq, p,
file->f_flags & O_NONBLOCK));
}
#ifdef CONFIG_VIDEO_V4L1_COMPAT
static int vidiocgmbuf (struct file *file, void *priv, struct video_mbuf *mbuf)
{
struct tm6000_fh *fh=priv;
return videobuf_cgmbuf (&fh->vb_vidq, mbuf, 8);
}
#endif
static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
struct tm6000_fh *fh=priv;
struct tm6000_core *dev = fh->dev;
if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
if (i != fh->type)
return -EINVAL;
if (!res_get(dev,fh))
return -EBUSY;
return (videobuf_streamon(&fh->vb_vidq));
}
static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
{
struct tm6000_fh *fh=priv;
struct tm6000_core *dev = fh->dev;
if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
if (i != fh->type)
return -EINVAL;
videobuf_streamoff(&fh->vb_vidq);
res_free(dev,fh);
return (0);
}
static int vidioc_s_std (struct file *file, void *priv, v4l2_std_id *norm)
{
int rc=0;
struct tm6000_fh *fh=priv;
struct tm6000_core *dev = fh->dev;
rc=tm6000_set_standard (dev, norm);
if (rc<0)
return rc;
tm6000_i2c_call_clients(dev, VIDIOC_S_STD, &dev->norm);
return 0;
}
static int vidioc_enum_input (struct file *file, void *priv,
struct v4l2_input *inp)
{
switch (inp->index) {
case TM6000_INPUT_TV:
inp->type = V4L2_INPUT_TYPE_TUNER;
strcpy(inp->name,"Television");
break;
case TM6000_INPUT_COMPOSITE:
inp->type = V4L2_INPUT_TYPE_CAMERA;
strcpy(inp->name,"Composite");
break;
case TM6000_INPUT_SVIDEO:
inp->type = V4L2_INPUT_TYPE_CAMERA;
strcpy(inp->name,"S-Video");
break;
default:
return -EINVAL;
}
inp->std = TM6000_STD;
return 0;
}
static int vidioc_g_input (struct file *file, void *priv, unsigned int *i)
{
struct tm6000_fh *fh=priv;
struct tm6000_core *dev = fh->dev;
*i=dev->input;
return 0;
}
static int vidioc_s_input (struct file *file, void *priv, unsigned int i)
{
struct tm6000_fh *fh=priv;
struct tm6000_core *dev = fh->dev;
int rc=0;
char buf[1];
switch (i) {
case TM6000_INPUT_TV:
dev->input=i;
*buf=0;
break;
case TM6000_INPUT_COMPOSITE:
case TM6000_INPUT_SVIDEO:
dev->input=i;
*buf=1;
break;
default:
return -EINVAL;
}
rc=tm6000_read_write_usb (dev, USB_DIR_OUT | USB_TYPE_VENDOR,
REQ_03_SET_GET_MCU_PIN, 0x03, 1, buf, 1);
if (!rc) {
dev->input=i;
rc=vidioc_s_std (file, priv, &dev->vfd.current_norm);
}
return (rc);
}
/* --- controls ---------------------------------------------- */
static int vidioc_queryctrl (struct file *file, void *priv,
struct v4l2_queryctrl *qc)
{
int i;
for (i = 0; i < ARRAY_SIZE(tm6000_qctrl); i++)
if (qc->id && qc->id == tm6000_qctrl[i].id) {
memcpy(qc, &(tm6000_qctrl[i]),
sizeof(*qc));
return (0);
}
return -EINVAL;
}
static int vidioc_g_ctrl (struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct tm6000_fh *fh=priv;
struct tm6000_core *dev = fh->dev;
int val;
/* FIXME: Probably, those won't work! Maybe we need shadow regs */
switch (ctrl->id) {
case V4L2_CID_CONTRAST:
val=tm6000_get_reg (dev, REQ_07_SET_GET_AVREG, 0x08, 0);
break;
case V4L2_CID_BRIGHTNESS:
val=tm6000_get_reg (dev, REQ_07_SET_GET_AVREG, 0x09, 0);
return 0;
case V4L2_CID_SATURATION:
val=tm6000_get_reg (dev, REQ_07_SET_GET_AVREG, 0x0a, 0);
return 0;
case V4L2_CID_HUE:
val=tm6000_get_reg (dev, REQ_07_SET_GET_AVREG, 0x0b, 0);
return 0;
default:
return -EINVAL;
}
if (val<0)
return val;
ctrl->value=val;
return 0;
}
static int vidioc_s_ctrl (struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct tm6000_fh *fh =priv;
struct tm6000_core *dev = fh->dev;
u8 val=ctrl->value;
switch (ctrl->id) {
case V4L2_CID_CONTRAST:
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x08, val);
return 0;
case V4L2_CID_BRIGHTNESS:
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x09, val);
return 0;
case V4L2_CID_SATURATION:
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x0a, val);
return 0;
case V4L2_CID_HUE:
tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x0b, val);
return 0;
}
return -EINVAL;
}
static int vidioc_g_tuner (struct file *file, void *priv,
struct v4l2_tuner *t)
{
struct tm6000_fh *fh =priv;
struct tm6000_core *dev = fh->dev;
if (unlikely(UNSET == dev->tuner_type))
return -EINVAL;
if (0 != t->index)
return -EINVAL;
strcpy(t->name, "Television");
t->type = V4L2_TUNER_ANALOG_TV;
t->capability = V4L2_TUNER_CAP_NORM;
t->rangehigh = 0xffffffffUL;
t->rxsubchans = V4L2_TUNER_SUB_MONO;
return 0;
}
static int vidioc_s_tuner (struct file *file, void *priv,
struct v4l2_tuner *t)
{
struct tm6000_fh *fh =priv;
struct tm6000_core *dev = fh->dev;
if (UNSET == dev->tuner_type)
return -EINVAL;
if (0 != t->index)
return -EINVAL;
return 0;
}
static int vidioc_g_frequency (struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct tm6000_fh *fh =priv;
struct tm6000_core *dev = fh->dev;
if (unlikely(UNSET == dev->tuner_type))
return -EINVAL;
f->type = V4L2_TUNER_ANALOG_TV;
f->frequency = dev->freq;
tm6000_i2c_call_clients(dev,VIDIOC_G_FREQUENCY,f);
return 0;
}
static int vidioc_s_frequency (struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct tm6000_fh *fh =priv;
struct tm6000_core *dev = fh->dev;
if (unlikely(f->type != V4L2_TUNER_ANALOG_TV))
return -EINVAL;
if (unlikely(UNSET == dev->tuner_type))
return -EINVAL;
if (unlikely(f->tuner != 0))
return -EINVAL;
// mutex_lock(&dev->lock);
dev->freq = f->frequency;
tm6000_i2c_call_clients(dev,VIDIOC_S_FREQUENCY,f);
// mutex_unlock(&dev->lock);
return 0;
}
/* ------------------------------------------------------------------
File operations for the device
------------------------------------------------------------------*/
static int tm6000_open(struct inode *inode, struct file *file)
{
int minor = iminor(inode);
struct tm6000_core *h,*dev = NULL;
struct tm6000_fh *fh;
struct list_head *list;
enum v4l2_buf_type type = 0;
int i,rc;
dprintk(dev, V4L2_DEBUG_OPEN, "tm6000: open called "
"(minor=%d)\n",minor);
list_for_each(list,&tm6000_corelist) {
h = list_entry(list, struct tm6000_core, tm6000_corelist);
if (h->vfd.minor == minor) {
dev = h;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
}
}
if (NULL == dev)
return -ENODEV;
/* If more than one user, mutex should be added */
dev->users++;
dprintk(dev, V4L2_DEBUG_OPEN, "open minor=%d type=%s users=%d\n",
minor,v4l2_type_names[type],dev->users);
/* allocate + initialize per filehandle data */
fh = kzalloc(sizeof(*fh),GFP_KERNEL);
if (NULL == fh) {
dev->users--;
return -ENOMEM;
}
file->private_data = fh;
fh->dev = dev;
fh->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
dev->fourcc = format[0].fourcc;
fh->fmt = format_by_fourcc(dev->fourcc);
fh->width = norm_maxw();
fh->height = norm_maxh();
dprintk(dev, V4L2_DEBUG_OPEN, "Open: fh=0x%08lx, dev=0x%08lx, "
"dev->vidq=0x%08lx\n",
(unsigned long)fh,(unsigned long)dev,(unsigned long)&dev->vidq);
dprintk(dev, V4L2_DEBUG_OPEN, "Open: list_empty "
"queued=%d\n",list_empty(&dev->vidq.queued));
dprintk(dev, V4L2_DEBUG_OPEN, "Open: list_empty "
"active=%d\n",list_empty(&dev->vidq.active));
/* initialize hardware on analog mode */
if (dev->mode!=TM6000_MODE_ANALOG) {
rc=tm6000_init_analog_mode (dev);
if (rc<0)
return rc;
/* Put all controls at a sane state */
for (i = 0; i < ARRAY_SIZE(tm6000_qctrl); i++)
qctl_regs[i] =tm6000_qctrl[i].default_value;
dev->mode=TM6000_MODE_ANALOG;
}
videobuf_queue_vmalloc_init(&fh->vb_vidq, &tm6000_video_qops,
NULL, &dev->slock,
fh->type,
V4L2_FIELD_INTERLACED,
sizeof(struct tm6000_buffer),fh);
return 0;
}
static ssize_t
tm6000_read(struct file *file, char __user *data, size_t count, loff_t *pos)
{
struct tm6000_fh *fh = file->private_data;
if (fh->type==V4L2_BUF_TYPE_VIDEO_CAPTURE) {
if (res_locked(fh->dev))
return -EBUSY;
return videobuf_read_stream(&fh->vb_vidq, data, count, pos, 0,
file->f_flags & O_NONBLOCK);
}
return 0;
}
static unsigned int
tm6000_poll(struct file *file, struct poll_table_struct *wait)
{
struct tm6000_fh *fh = file->private_data;
struct tm6000_buffer *buf;
if (V4L2_BUF_TYPE_VIDEO_CAPTURE != fh->type)
return POLLERR;
if (res_get(fh->dev,fh)) {
/* streaming capture */
if (list_empty(&fh->vb_vidq.stream))
return POLLERR;
buf = list_entry(fh->vb_vidq.stream.next,struct tm6000_buffer,vb.stream);
} else {
/* read() capture */
buf = (struct tm6000_buffer*)fh->vb_vidq.read_buf;
if (NULL == buf)
return POLLERR;
}
poll_wait(file, &buf->vb.done, wait);
if (buf->vb.state == STATE_DONE ||
buf->vb.state == STATE_ERROR)
return POLLIN|POLLRDNORM;
return 0;
}
static int tm6000_release(struct inode *inode, struct file *file)
{
struct tm6000_fh *fh = file->private_data;
struct tm6000_core *dev = fh->dev;
struct tm6000_dmaqueue *vidq = &dev->vidq;
int minor = iminor(inode);
tm6000_stop_thread(vidq);
videobuf_mmap_free(&fh->vb_vidq);
kfree (fh);
dprintk(dev, V4L2_DEBUG_OPEN, "tm6000: close called (minor=%d, users=%d)\n",minor,dev->users);
return 0;
}
static int tm6000_mmap(struct file *file, struct vm_area_struct * vma)
{
struct tm6000_fh *fh = file->private_data;
int ret;
ret=videobuf_mmap_mapper(&fh->vb_vidq, vma);
return ret;
}
static struct file_operations tm6000_fops = {
.owner = THIS_MODULE,
.open = tm6000_open,
.release = tm6000_release,
.ioctl = video_ioctl2, /* V4L2 ioctl handler */
.read = tm6000_read,
.poll = tm6000_poll,
.mmap = tm6000_mmap,
.llseek = no_llseek,
};
static struct video_device tm6000_template = {
.name = "tm6000",
.type = VID_TYPE_CAPTURE,
.fops = &tm6000_fops,
.minor = -1,
.release = video_device_release,
.vidioc_querycap = vidioc_querycap,
.vidioc_enum_fmt_cap = vidioc_enum_fmt_cap,
.vidioc_g_fmt_cap = vidioc_g_fmt_cap,
.vidioc_try_fmt_cap = vidioc_try_fmt_cap,
.vidioc_s_fmt_cap = vidioc_s_fmt_cap,
.vidioc_s_std = vidioc_s_std,
.vidioc_enum_input = vidioc_enum_input,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
.vidioc_queryctrl = vidioc_queryctrl,
.vidioc_g_ctrl = vidioc_g_ctrl,
.vidioc_s_ctrl = vidioc_s_ctrl,
.vidioc_g_tuner = vidioc_g_tuner,
.vidioc_s_tuner = vidioc_s_tuner,
.vidioc_g_frequency = vidioc_g_frequency,
.vidioc_s_frequency = vidioc_s_frequency,
.vidioc_streamon = vidioc_streamon,
.vidioc_streamoff = vidioc_streamoff,
.vidioc_reqbufs = vidioc_reqbufs,
.vidioc_querybuf = vidioc_querybuf,
.vidioc_qbuf = vidioc_qbuf,
.vidioc_dqbuf = vidioc_dqbuf,
#ifdef CONFIG_VIDEO_V4L1_COMPAT
.vidiocgmbuf = vidiocgmbuf,
#endif
.tvnorms = TM6000_STD,
.current_norm = V4L2_STD_NTSC_M,
};
/* -----------------------------------------------------------------
Initialization and module stuff
------------------------------------------------------------------*/
int tm6000_v4l2_register(struct tm6000_core *dev)
{
int ret;
list_add_tail(&dev->tm6000_corelist,&tm6000_corelist);
/* init video dma queues */
INIT_LIST_HEAD(&dev->vidq.active);
INIT_LIST_HEAD(&dev->vidq.queued);
dev->vidq.timeout.function = tm6000_vid_timeout;
dev->vidq.timeout.data = (unsigned long)dev;
init_timer(&dev->vidq.timeout);
memcpy (&dev->vfd, &tm6000_template, sizeof(dev->vfd));
dev->vfd.debug=tm6000_debug;
ret = video_register_device(&dev->vfd, VFL_TYPE_GRABBER, video_nr);
printk(KERN_INFO "Trident TVMaster TM5600/TM6000 USB2 board (Load status: %d)\n", ret);
return ret;
}
int tm6000_v4l2_unregister(struct tm6000_core *dev)
{
struct tm6000_core *h;
struct list_head *list;
while (!list_empty(&tm6000_corelist)) {
list = tm6000_corelist.next;
h = list_entry(list, struct tm6000_core, tm6000_corelist);
if (h == dev) {
video_unregister_device(&dev->vfd);
list_del(list);
kfree (h);
}
}
return 0;
}
int tm6000_v4l2_exit(void)
{
return 0;
}
module_param(video_nr, int, 0);
MODULE_PARM_DESC(video_nr,"Allow changing video device number");
module_param_named (debug, tm6000_debug, int, 0444);
MODULE_PARM_DESC(debug,"activates debug info");
module_param(vid_limit,int,0644);
MODULE_PARM_DESC(vid_limit,"capture memory limit in megabytes");
/*
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)
...@@ -361,6 +361,7 @@ struct v4l2_pix_format { ...@@ -361,6 +361,7 @@ struct v4l2_pix_format {
#define V4L2_PIX_FMT_PJPG v4l2_fourcc('P', 'J', 'P', 'G') /* Pixart 73xx JPEG */ #define V4L2_PIX_FMT_PJPG v4l2_fourcc('P', 'J', 'P', 'G') /* Pixart 73xx JPEG */
#define V4L2_PIX_FMT_OV511 v4l2_fourcc('O', '5', '1', '1') /* ov511 JPEG */ #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_OV518 v4l2_fourcc('O', '5', '1', '8') /* ov518 JPEG */
#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 * F O R M A T E N U M E R A T I O N
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment