commit a18816f6b91629eb6038aed4815e4fb94ddc0a6b Author: Markus Rechberger Date: Wed Oct 22 21:56:45 2008 +0200 Adding empia base driver em2880-dvb: * supporting the digital part of Empia based devices, which includes ATSC, ISDB-T and DVB-T em28xx-aad.c: * alternative audio driver, can be used instead of em28xx-audio if alsa is not available or not compiled into the kernel, it provides a raw interface to the PCM samples em28xx-audio.c: * em28xx alsa driver and audio driver for FM radio em28xx-audioep.c: * em28xx alsa driver for devices which are set to vendor specific audio on interface 1, in that case snd-usb-audio will not attach to the interface and em28xx-audioep will be needed em28xx-cards.c: * card definition and initial setup of devices. em28xx-core.c: * core videohandling and VBI frame slicing em28xx-i2c.c: * i2c setup and GPIO setup handling of the devices (including em2888 based ones) em28xx-input.c: * currently mostly disabled since the linuxtv input handling is broken by design and racy em28xx-keymaps.c: * keymap references of some remotes (could be merged into ir-common, although as mentioned this should be in userland done by lirc). em28xx-video.c: * inode handling for analog TV, radio and VBI, also some device probing em28xx-webcam.c: * videology webcam specific i2c commands Signed-off-by: Markus Rechberger diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 47102c2..a726c59 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -783,6 +783,8 @@ source "drivers/media/video/pvrusb2/Kconfig" source "drivers/media/video/em28xx/Kconfig" +source "drivers/media/video/empia/Kconfig" + source "drivers/media/video/usbvision/Kconfig" source "drivers/media/video/usbvideo/Kconfig" diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 16962f3..9357368 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_VIDEO_MEYE) += meye.o obj-$(CONFIG_VIDEO_SAA7134) += saa7134/ obj-$(CONFIG_VIDEO_CX88) += cx88/ obj-$(CONFIG_VIDEO_EM28XX) += em28xx/ +obj-$(CONFIG_VIDEO_EMPIA) += empia/ obj-$(CONFIG_VIDEO_USBVISION) += usbvision/ obj-$(CONFIG_VIDEO_TVP5150) += tvp5150.o obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2/ diff --git a/drivers/media/video/empia/Kconfig b/drivers/media/video/empia/Kconfig new file mode 100644 index 0000000..6bd0ac3 --- /dev/null +++ b/drivers/media/video/empia/Kconfig @@ -0,0 +1,61 @@ +config VIDEO_EMPIA + tristate "Empia EM28xx USB video capture support" + depends on VIDEO_DEV && I2C + select VIDEO_BUF + select VIDEO_TUNER + select VIDEO_SAA711X if VIDEO_HELPER_CHIPS_AUTO + select VIDEO_TVP5150 if VIDEO_HELPER_CHIPS_AUTO + ---help--- + This em28xx driver has been worked on since 2006 and constantly + improved to support the latest available devices including + different TV standards as well as VBI. + + the difference between this and the other em28xx driver is that + it's well tested and supports more devices. + +config VIDEO_EMPIA_DTV + tristate "Empia EM288x/EM275x DVB-T support" + depends on VIDEO_EMPIA && DVB_CORE + select FW_LOADER + select DVB_TUNER_MT2060 + select DVB_TUNER_QT1010 + select DVB_LGDT3304 + select DVB_MT352 if !DVB_FE_CUSTOMISE + select DVB_ZL10353 if !DVB_FE_CUSTOMISE + select DVB_LGDT330X if !DVB_FE_CUSTOMISE + select DVB_LGDT3304 if !DVB_FE_CUSTOMISE + ---help--- + Support for Empia em288x/em2870 DVB-T/ATSC and ISDB-T extension + + If unsure say N. + +config VIDEO_EMPIA_AUDIO + tristate "Empia EM28xx USB Audio support" + depends on VIDEO_EMPIA + ---help--- + This is an audio driver for Em28x[1/2] based usb video devices, + this will be used by Hauppauge HVR 900 for analogue TV + instead of snd-usb-audio. + + To compile this driver as a module, choose M here: the + module will be called em28xx + +config VIDEO_EMPIA_AUDIOEP + tristate "Empia EM28xx USB Audio support (second configuration)" + depends on VIDEO_EMPIA + ---help--- + This is another audiodriver for em28xx based devices which have + a vendor specific audio interface on interface 1. + + choose yes if unsure, the driver will only attach if your + Empia device has such a configuration + +config VIDEO_EMPIA_AAD_AUDIO + tristate "Empia EM28xx USB Audio support (not alsa compatible)" + depends on VIDEO_EMPIA + ---help--- + This driver adds vendor specific audio support to em28xx based devices + It can be used as an alternative if alsa is not available or enabled + in the kernel (eg. to support audio with OSS). + + choose no if unsure diff --git a/drivers/media/video/empia/Makefile b/drivers/media/video/empia/Makefile new file mode 100644 index 0000000..9ee2376 --- /dev/null +++ b/drivers/media/video/empia/Makefile @@ -0,0 +1,19 @@ +em28xx-objs := em28xx-video.o em28xx-i2c.o em28xx-cards.o em28xx-core.o \ + em28xx-input.o em28xx-webcam.o em28xx-keymaps.o + +em28xx-dvb-objs := em2880-dvb.o + +obj-$(CONFIG_VIDEO_EMPIA) += em28xx.o +obj-$(CONFIG_VIDEO_EMPIA_AUDIO) += em28xx-audio.o +obj-$(CONFIG_VIDEO_EMPIA_AUDIOEP) += em28xx-audioep.o +obj-$(CONFIG_VIDEO_EMPIA_AAD_AUDIO) += em28xx-aad.o +obj-$(CONFIG_VIDEO_EMPIA_DTV) += em28xx-dvb.o +obj-$(CONFIG_VIDEO_EMPIA) += cx25843/ +obj-$(CONFIG_VIDEO_EMPIA_DTV) += lgdt3304/ +obj-$(CONFIG_VIDEO_EMPIA) += xc3028/ +obj-$(CONFIG_VIDEO_EMPIA) += xc5000/ + +EXTRA_CFLAGS += -Idrivers/media/video +EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core +EXTRA_CFLAGS += -Idrivers/media/dvb/frontends +EXTRA_CFLAGS += -Idrivers/media/common/tuners diff --git a/drivers/media/video/empia/em2880-dvb.c b/drivers/media/video/empia/em2880-dvb.c new file mode 100644 index 0000000..bd38018 --- /dev/null +++ b/drivers/media/video/empia/em2880-dvb.c @@ -0,0 +1,1139 @@ +/* + * Empiatech em2880 DVB-T extension + * + * Copyright (C) 2006/2007/2008 Markus Rechberger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "em28xx.h" + +#include +#include +#include "dvb_frontend.h" + +#include "zl10353.h" +#include "mt352.h" +#include "xc5000/xc5000_control.h" +#ifdef MICRONAS_DRX3975D +#include "drx3973d/drx3973d_demod.h" +#endif +#include "lgdt3304/lgdt3304.h" +#include "qt1010.h" +#include "mt2060.h" +#ifdef ADIMTV102 +#include "adimtv102/adimtv102.h" +#endif + +#include "lgdt330x.h" +#include "sharp/s921_module.h" + +#define EM2880_DVB_NUM_PACKETS 64 +#define EM2880_URB_COUNT 32 + +#if defined DVB_DEFINE_MOD_OPT_ADAPTER_NR +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); +#endif + + +#define xc3028_offset_8mhz 2750000; +#define xc3028_offset_7mhz 2250000; +#define xc3028_offset_6mhz 2750000; +#define xc3028_offset_atsc 1750000; + + +MODULE_DESCRIPTION("Empiatech em2880 DVB-T extension"); +MODULE_AUTHOR("Markus Rechberger "); +MODULE_LICENSE("GPL"); + +#ifdef MICRONAS_DRX3975D +DRX3973DData_t DRX3973DData_g = { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + DRX_UIO_MODE_DISABLE, + DRX_UIO_MODE_DISABLE, + { + DRX3973D_AGC_CTRL_AUTO, + 0, + 0, + 0, + 0, + 0 + }, + { + DRX3973D_AGC_CTRL_AUTO, + 0, + 0, + 0, + 0, + 0 + }, + DRX3973D_IFFILTER_SAW, + FALSE, + DRX_BANDWIDTH_8MHZ, + FALSE, + DRX3973D_APPENV_PORTABLE, + TRUE, + FALSE, +#ifndef _CH_ + { + "01234567890", + "01234567890" + }, + { + { + DRX_MODULE_UNKNOWN, + (char *)(NULL), + 0, + 0, + 0, + (char *)(NULL) + }, + { + DRX_MODULE_UNKNOWN, + (char *)(NULL), + 0, + 0, + 0, + (char *)(NULL) + } + }, + { + { + (pDRXVersion_t)(NULL), + (pDRXVersionList_t)(NULL) + }, + { + (pDRXVersion_t)(NULL), + (pDRXVersionList_t)(NULL) + } + }, + DRX3973D_SPIN_UNKNOWN, + { + }, + FALSE, + +#if (DRXD_TYPE_B) + DRX3973D_I2C_INIT_ASEL +#endif + + + +#endif +}; + +/** + * \var DRX3973DDefaultAddr_g + * \brief Default I2C address and device identifier. + */ +I2CDeviceAddr_t DRX3973DDefaultAddr_g = { +#define DRX3973D_DEF_I2C_ADDR (0xe0)>>1 + DRX3973D_DEF_I2C_ADDR, /* i2c address */ +#define DRX3973D_DEF_DEMOD_DEV_ID (1) + DRX3973D_DEF_DEMOD_DEV_ID, /* device id */ + NULL /* private data structure */ +}; + +/** + * \var DRX3973DDefaultCommAttr_g + * \brief Default common attributes of a drx3973d demodulator instance. + */ +DRXCommonAttr_t DRX3973DDefaultCommAttr_g = { + (pu8_t)NULL, + 0, + TRUE, + 4560, + 48000L, + 12000L, + 0L, + FALSE, + TRUE, + TRUE, + TRUE, + FALSE, + FALSE, + FALSE, + FALSE, + FALSE, + FALSE, + 32000000UL, + FALSE, + (pDRXScanParam_t)(NULL), + 0, + 0, + FALSE, + 0L, + 0L, + 100, + DRX_LOCKED, + FALSE, + DRX_POWER_DOWN, + 1, + 0L, + 0L +}; + +#endif + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "em2880-dvb debug level (default off)"); + +#define dprintk(lvl, fmt, args...) if (debug >= lvl) do {\ + printk(fmt, ##args); } while (0) + + +static int em2880_set_alternate(struct em2880_dvb *dvb_dev); + +/* ------------------------------------------------------------------ */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) +static void em2880_complete_irq(struct urb *urb, struct pt_regs *ptregs) +{ +#else +static void em2880_complete_irq(struct urb *urb) +{ +#endif + struct em2880_dvb *dvb_dev = urb->context; + int i; + int status; + + if (dvb_dev == NULL) { + dprintk(1, "em2880-dev.c: device not valid!\n"); + return; + } + + if (debug && urb->status < 0) { + printk(KERN_ERR"%s:%u:%s(): status = %i\n", __FILE__, + __LINE__, __func__, urb->status); + } + + switch (urb->status) { + case -ESHUTDOWN: + return; + break; + } + + if (urb->status == -ENOENT) + return; + + for (i = 0; i < urb->number_of_packets; i++) { + if (urb->iso_frame_desc[i].status != 0) { + dprintk(1, "em2880-dvb.c: status != 0\n"); + continue; + } else if (urb->iso_frame_desc[i].actual_length > 0) + dvb_dmx_swfilter(&dvb_dev->demux, + urb->transfer_buffer + + urb->iso_frame_desc[i].offset, + urb->iso_frame_desc[i] + .actual_length); + } + + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) + dprintk(1, "resubmitting urb failed!\n"); +} + +static void em2880_stop_stream(struct em2880_dvb *dvb_dev) +{ + int i; + int arg; + for (i = 0; i < EM2880_DVB_NUM_BUFS; i++) { + if (dvb_dev->urb[i]) { + usb_kill_urb(dvb_dev->urb[i]); + if (dvb_dev->transfer_buffer[i]) { + usb_buffer_free(dvb_dev->udev, + (EM2880_DVB_NUM_PACKETS * + dvb_dev->dtv_packetsize), + dvb_dev->transfer_buffer[i], + dvb_dev->urb[i]->transfer_dma); + } + usb_free_urb(dvb_dev->urb[i]); + } + dvb_dev->urb[i] = NULL; + dvb_dev->transfer_buffer[i] = NULL; + } + arg=EM28XX_REG_OFF; + dvb_dev->em28xx_dev->em28xx_gpio_control(dvb_dev->em28xx_dev, EM28XX_LED1_ON, &arg); + +} + +static int em2880_start_stream(struct em2880_dvb *dvb_dev) +{ + int i, errCode; + int arg; + const int sb_size = EM2880_DVB_NUM_PACKETS * dvb_dev->dtv_packetsize; + + dprintk(1, "em2880-dvb.c: got start stream request %s\n", __func__); + arg=EM28XX_REG_ON; + dvb_dev->em28xx_dev->em28xx_gpio_control(dvb_dev->em28xx_dev, EM28XX_LED1_ON, &arg); + + em2880_set_alternate(dvb_dev); + + /* allocate urbs */ + + for (i = 0; i < EM2880_DVB_NUM_BUFS; i++) { + struct urb *urb; + int j, k; + /* allocate transfer buffer */ + urb = usb_alloc_urb(EM2880_DVB_NUM_PACKETS, GFP_KERNEL); + if (!urb) { + dprintk(1, "cannot alloc urb %i\n", i); + return -ENOMEM; + } + dvb_dev->transfer_buffer[i] = + usb_buffer_alloc(dvb_dev->udev, sb_size, + GFP_KERNEL, &urb->transfer_dma); + + if (!dvb_dev->transfer_buffer[i]) { + dprintk(1, "unable to allocate %i bytes for transfer" + "buffer %i\n", sb_size, i); + + return -ENOMEM; + } + memset(dvb_dev->transfer_buffer[i], 0, sb_size); + urb->dev = dvb_dev->udev; + urb->context = dvb_dev; + urb->pipe = usb_rcvisocpipe(dvb_dev->udev, 0x84); + urb->transfer_flags = URB_ISO_ASAP; + urb->interval = 1; + urb->transfer_buffer = dvb_dev->transfer_buffer[i]; + urb->complete = em2880_complete_irq; + urb->number_of_packets = EM2880_DVB_NUM_PACKETS; + urb->transfer_buffer_length = sb_size; + for (j = k = 0; j < EM2880_DVB_NUM_PACKETS; + j++, k += dvb_dev->dtv_packetsize) { + urb->iso_frame_desc[j].offset = k; + urb->iso_frame_desc[j].length = + dvb_dev->dtv_packetsize; + } + dvb_dev->urb[i] = urb; + } + + /* submit urbs */ + for (i = 0; i < EM2880_DVB_NUM_BUFS; i++) { + errCode = usb_submit_urb(dvb_dev->urb[i], GFP_KERNEL); + if (errCode) { + dprintk(1, "submit of urb %i failed (error=%i)\n", + i, + errCode); + return errCode; + } + } + return 0; +} + +static int em2880_set_alternate(struct em2880_dvb *dvb_dev) +{ + int errCode; + errCode = usb_set_interface(dvb_dev->udev, dvb_dev->em28xx_dev->usb_interface, 1); + + return errCode; +} + +static int em2880_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *demux = dvbdmxfeed->demux; + struct em2880_dvb *dvb_dev = demux->priv; + dprintk(1, "em2880-dvb.c: got start feed request %s\n", __func__); + + if (mutex_lock_interruptible(&dvb_dev->sem)) + return -ERESTARTSYS; + + if (dvb_dev->streaming == 0) + em2880_start_stream(dvb_dev); + + dvb_dev->streaming++; + mutex_unlock(&dvb_dev->sem); + return 0; +} + +static int em2880_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *demux = dvbdmxfeed->demux; + struct em2880_dvb *dvb_dev = demux->priv; + + dprintk(1, "em2880-dvb.c: got stop feed request %s\n", __func__); + + if (mutex_lock_interruptible(&dvb_dev->sem)) + return -ERESTARTSYS; + if (0 == --dvb_dev->streaming) + em2880_stop_stream(dvb_dev); + mutex_unlock(&dvb_dev->sem); + return 0; +} + +static int em28xx_ts_bus_ctrl_int(struct em28xx *dev, int acquire) +{ + u8 gpio; + gpio = dev->em28xx_read_reg(dev, 04); + gpio &= ~((u8)0x3); + + if (acquire == 1) + gpio |= 1; + + return dev->em28xx_write_regs(dev, 0x04, &gpio, 1); +} + +static int em28xx_ts_bus_ctrl(struct dvb_frontend *fe, int acquire) +{ + struct em28xx *dev = fe->dvb->priv; + return dev->em28xx_acquire(dev, EM28XX_DVBT, acquire); +} + +static struct zl10353_config em2880_zl10353_dev = { + .demod_address = (0x1e >> 1), + .no_tuner = 1, + .parallel_ts = 1, + .if2 = 45600 +}; + +struct bcode { + int reg; + char *txt; + int len; + int delay; +}; + + +static int mt352_pinnacle_init(struct dvb_frontend *fe) +{ + int i; + struct bcode zlconf[]={ + {0x1e,"\x8a\x2c",2,0}, + {0x1e,"\x89\x38",2,0}, + {0x1e,"\x50\x80",2,0}, + {0x1e,"\x8e\x40",2,0}, + {0x1e,"\x69\x00",2,0}, + {0x1e,"\x6a\xff",2,0}, + {0x1e,"\x6b\xff",2,0}, + {0x1e,"\x6c\x00",2,0}, + {0x1e,"\x6d\xff",2,0}, + {0x1e,"\x6e\x00",2,0}, + {0x1e,"\x6f\x40",2,0}, + {0x1e,"\x70\x40",2,0}, + {0x1e,"\x68\xa0",2,0}, + + {0x1e,"\x56\x31",2,0}, // set input frequency + {0x1e,"\x57\xb8",2,0}, + {0x1e,"\x75\x33",2,0}, + + {0x1e,"\x7c\x00",2,0}, + {0x1e,"\x7d\x4d",2,0}, + {0x1e,"\xb5\x7a",2,0}, + {0x1e,"\x51\x40",2,0}, + {0x1e,"\x52\x80",2,0}, + {0x1e,"\x53\x50",2,0}, + {0x1e,"\x5d\x01",2,0}, + {} + }; + for(i=0;zlconf[i].txt;i++) + fe->ops.write(fe, zlconf[i].txt,zlconf[i].len); + return 0; +} + + +static struct mt352_config em2880_mt352_dev = { + .demod_address = (0x1e >> 1), + .no_tuner = 1, + //.parallel_ts = 1, + .if2 = 45600, + .demod_init = mt352_pinnacle_init, +}; + + +static struct zl10353_config em2880_zl10353_pinnacle = { + .demod_address = (0x1e >> 1), + .no_tuner = 1, + .parallel_ts = 1, +}; + +static struct zl10353_config em2880_kworld_355u_dev = { + .demod_address = (0x1e >> 1), + .no_tuner = 1, + .parallel_ts = 1, +}; + +static struct lgdt330x_config em2880_lgdt3303_dev = { + .demod_address = 0x0e, + .demod_chip = LGDT3303 +}; + +static struct mt2060_config em2870_mt2060_config = { + .i2c_address = 0x60 +}; + +static struct qt1010_config em2870_qt1010_config = { + .i2c_address = 0x62 +}; + +static int kworld355u_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct zl10353_state { + struct i2c_adapter *i2c; + struct dvb_frontend frontend; + + struct zl10353_config config; + + enum fe_bandwidth bandwidth; + }; + struct zl10353_state *state = fe->demodulator_priv; + + struct em28xx *dev = state->i2c->algo_data; + + return em28xx_ts_bus_ctrl_int(dev, enable); +} + +static struct lgdt3304_config lgdt3304_atsc_config = { + .i2c_address = 0x0e +}; + +static int em28xx_set_params(struct dvb_frontend *fe, + struct dvb_frontend_parameters *params) +{ + struct em28xx *dev = fe->dvb->priv; + struct em2880_dvb *dvbdev = dev->dvb_dev; + int i; + unsigned long frequency = params->frequency; + struct dvb_ofdm_parameters *op = ¶ms->u.ofdm; + + if (!dev->tuner) + return -EINVAL; + + switch (fe->ops.info.type) { + case FE_OFDM: + if (dev->dvbnorm->bandwidth != + op->bandwidth) { + for (i = 0; dev->board->dvbnorms[i].tv_mode || dev->board->dvbnorms[i].index; i++) { + if (dev->board->dvbnorms[i].bandwidth == op->bandwidth) { + switch (dev->tuner_type) { + case TUNER_XCEIVE_XC3028: + { + struct xc3028_init_cmd cmd; + dvbdev->bw_index = i; + cmd.new_tv_mode_ptr = dev->board->dvbnorms[i].tv_mode; + cmd.new_channel_map_ptr = dev->board->dvbnorms[i].channelmap; + dev->dvbnorm = &dev->board->dvbnorms[i]; + if (dev->tuner->tuner_cmd) + dev->tuner->tuner_cmd(dev->tuner, XC3028_INIT_TUNER, &cmd); + break; + } + case TUNER_XCEIVE_XC5000: + { + struct xc_std_conf cmd; + dvbdev->bw_index = i; + cmd.index = dev->board->dvbnorms[i].index; + dev->dvbnorm = &dev->board->dvbnorms[i]; + dev->tuner->tuner_cmd(dev->tuner, XC5000_SET_MODE, &cmd); + break; + } + } + break; + } + } + } + + if (dev->tuner->set_frequency) { + switch (params->u.ofdm.bandwidth) { + case BANDWIDTH_AUTO: + case BANDWIDTH_8_MHZ: + frequency -= xc3028_offset_8mhz; + break; + case BANDWIDTH_7_MHZ: + frequency -= xc3028_offset_7mhz; + break; + case BANDWIDTH_6_MHZ: + frequency -= xc3028_offset_6mhz; + break; + default: + return -EINVAL; + } + dev->tuner->set_frequency(dev->tuner, frequency); + dev->dctl_freq = frequency; + } + break; + case FE_QAM: + switch (dev->tuner_type) { + case TUNER_XCEIVE_XC3028: + { + struct xc3028_init_cmd cmd; + dvbdev->bw_index = 0; + cmd.new_tv_mode_ptr = dev->board->qamnorms[0].tv_mode; + cmd.new_channel_map_ptr = dev->board->qamnorms[0].channelmap; + dev->qamnorm = &dev->board->qamnorms[0]; + if (dev->tuner->tuner_cmd) + dev->tuner->tuner_cmd(dev->tuner, XC3028_INIT_TUNER, &cmd); + break; + } + case TUNER_XCEIVE_XC5000: + { + struct xc_std_conf cmd; + dvbdev->bw_index = 0; + cmd.index = dev->board->atscnorms[0].index; + dev->atscnorm = &dev->board->atscnorms[0]; + dev->tuner->tuner_cmd(dev->tuner, XC5000_SET_MODE, &cmd); + break; + } + } + frequency -= xc3028_offset_atsc; + + dev->tuner->set_frequency(dev->tuner, frequency); + dev->dctl_freq = frequency; + break; + case FE_ATSC: + switch (dev->tuner_type) { + case TUNER_XCEIVE_XC3028: + { + struct xc3028_init_cmd cmd; + dvbdev->bw_index = 0; + cmd.new_tv_mode_ptr = dev->board->atscnorms[0].tv_mode; + cmd.new_channel_map_ptr = dev->board->atscnorms[0].channelmap; + dev->atscnorm = &dev->board->atscnorms[0]; + if (dev->tuner->tuner_cmd) + dev->tuner->tuner_cmd(dev->tuner, XC3028_INIT_TUNER, &cmd); + break; + } + case TUNER_XCEIVE_XC5000: + { + struct xc_std_conf cmd; + dvbdev->bw_index = 0; + cmd.index = dev->board->atscnorms[0].index; + dev->atscnorm = &dev->board->atscnorms[0]; + dev->tuner->tuner_cmd(dev->tuner, XC5000_SET_MODE, &cmd); + break; + } + } + + frequency -= xc3028_offset_atsc; + dev->tuner->set_frequency(dev->tuner, frequency); + dev->dctl_freq = frequency; + break; + case FE_QPSK: + printk(KERN_INFO"FE_QPSK currently not supported\n"); + break; + } + + return 0; +} + +static int em28xx_get_frequency(struct dvb_frontend *fe, u32 *frequency) +{ + struct em28xx *dev = fe->dvb->priv; + *frequency = dev->dctl_freq; + return 0; +} + +static int em28xx_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth) +{ + struct em28xx *dev = fe->dvb->priv; + struct em2880_dvb *dvbdev = dev->dvb_dev; + *bandwidth = (u32)dev->board->dvbnorms[dvbdev->bw_index].bandwidth; + return 0; +} + +static int em28xx_dvb_init(struct dvb_frontend *fe) +{ + struct em28xx *dev = fe->dvb->priv; + int gpio_arg; + + printk(KERN_INFO"em28xx_dvb_init\n"); + + if (dev->mode != V4L2_TUNER_DIGITAL_TV) { + printk(KERN_INFO"switching over from %d\n", dev->mode); + gpio_arg = EM28XX_REG_OFF; + dev->em28xx_gpio_control(dev, EM28XX_ANALOG_ON, &gpio_arg); + + gpio_arg = EM28XX_REG_ON; + dev->em28xx_gpio_control(dev, EM28XX_TS1_ON, &gpio_arg); + dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON, &gpio_arg); + dev->em28xx_gpio_control(dev, EM28XX_DEMOD1_RESET, NULL); + mdelay(100); + } + dev->mode = V4L2_TUNER_DIGITAL_TV; + msleep(100); + switch (dev->tuner_type) { + case TUNER_XCEIVE_XC3028: + { + struct xc3028_init_cmd cmd; + if (dev->dev_modes & EM28XX_DVBT) { + cmd.new_tv_mode_ptr = dev->dvbnorm->tv_mode; + cmd.new_channel_map_ptr = dev->dvbnorm->channelmap; + } else if (dev->dev_modes & EM28XX_ATSC) { + cmd.new_tv_mode_ptr = dev->atscnorm->tv_mode; + cmd.new_channel_map_ptr = dev->atscnorm->channelmap; + } + + if (dev->tuner && dev->tuner->tuner_cmd) + dev->tuner->tuner_cmd(dev->tuner, XC3028_INIT_TUNER, &cmd); + break; + } + case TUNER_XCEIVE_XC5000: + { + struct xc_std_conf cmd; + if (dev->dev_modes & EM28XX_DVBT) { + cmd.index = dev->dvbnorm->index; + } else if (dev->dev_modes & EM28XX_ATSC) { + cmd.index = dev->atscnorm->index; + } + + printk(KERN_INFO"initializing: %d\n", dev->dvbnorm->index); + dev->tuner->tuner_cmd(dev->tuner, XC5000_INIT_TUNER, NULL); + + if (dev->tuner && dev->tuner->tuner_cmd) + dev->tuner->tuner_cmd(dev->tuner, XC5000_SET_MODE, &cmd); + } + } + + return 0; +} + +static int em28xx_s921_init(struct dvb_frontend *fe) +{ + struct em28xx *dev = fe->dvb->priv; + struct em2880_dvb *dvbdev = dev->dvb_dev; + switch (dev->em_type) { + case EM2875: + dev->em28xx_write_regs_req(dev, 0x00, 0x5f, "\x01", 1); + break; + default: + break; + } + if (dvbdev->init_override) + dvbdev->init_override(fe); + + return 0; +} + +static int em28xx_zl10353_init(struct dvb_frontend *fe) +{ + struct em28xx *dev = fe->dvb->priv; + struct em2880_dvb *dvbdev = dev->dvb_dev; + + int gpio_arg; + +// if (dev->mode != V4L2_TUNER_DIGITAL_TV) { + dev->em28xx_write_regs_req(dev, 0x00, 0x48, "\x00", 1); + dev->em28xx_write_regs_req(dev, 0x00, 0x12, "\x77", 1); + + gpio_arg = EM28XX_REG_OFF; + dev->em28xx_gpio_control(dev, EM28XX_ANALOG_ON, &gpio_arg); + gpio_arg = EM28XX_REG_ON; + dev->em28xx_gpio_control(dev, EM28XX_TS1_ON, &gpio_arg); + dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON, &gpio_arg); + dev->em28xx_gpio_control(dev, EM28XX_DECODER_SLEEP, &gpio_arg); + dev->em28xx_gpio_control(dev, EM28XX_DEMOD1_RESET, NULL); + mdelay(100); +// } + dev->mode = V4L2_TUNER_DIGITAL_TV; + msleep(100); + + if (dvbdev->init_override) + dvbdev->init_override(fe); + + switch (dev->em_type) { + case EM2888: + dev->em28xx_write_regs_req(dev, 0x00, 0x5f, "\x01", 1); + /* TODO move this to zl10353, though keep backward compatibility */ + dev->em28xx_write_regs_req(dev, 0x02, 0x1e, "\x50\x0b", 2); + dev->em28xx_write_regs_req(dev, 0x02, 0x1e, "\x51\x64", 2); + break; + case EM2875: + dev->em28xx_write_regs_req(dev, 0x00, 0x5f, "\x01", 1); + break; + default: + dev->em28xx_write_regs_req(dev, 0x02, 0x1e, "\x50\x0b", 2); + dev->em28xx_write_regs_req(dev, 0x02, 0x1e, "\x51\x44", 2); + break; + } + return 0; +} + + +static int em28xx_zl10353_sleep(struct dvb_frontend *fe) +{ + struct em28xx *dev = fe->dvb->priv; +// int gpio_arg = EM28XX_REG_OFF; + if (dev->mode == V4L2_TUNER_DIGITAL_TV && dev->powersaving) { +// dev->em28xx_gpio_control(dev, EM28XX_TS1_ON, &gpio_arg); + printk(KERN_INFO"powered down zl10353\n"); + } + return 0; +} + + +static int em28xx_dvb_sleep(struct dvb_frontend *fe) +{ + struct em28xx *dev = fe->dvb->priv; +#if 0 + int gpio_arg; +#endif + if (dev->mode == V4L2_TUNER_DIGITAL_TV && dev->powersaving) { +// dev->tuner->shutdown(dev->tuner); +#if 0 + gpio_arg = EM28XX_REG_OFF; + dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON, &gpio_arg); + printk(KERN_INFO"powered down xc3028\n"); +#endif + } + return 0; +} + +#ifdef ADIMTV102 +struct adimtv102_config dmbt_adim_config = { + .i2c_address = 0xc2>>1 +}; + +#endif + +struct s921_config sharp_isdbt_config = { + .i2c_address = 0x30>>1 +}; + +static int em2880_dvb_init(struct em28xx *dev) +{ + struct em2880_dvb *dvb_dev; + int err; + u16 if1 = 1220; + int gpio_arg; + int i; + struct dvb_demux *dvbdemux; + + printk(KERN_INFO"em2880-dvb.c: DVB Init\n"); + if (NULL == dev) { + dprintk(1, "em2880-dvb.c: no device attached?\n"); + return -ENOMEM; + } + + dvb_dev = kzalloc(sizeof(struct em2880_dvb), GFP_KERNEL); + if (!dvb_dev) { + dprintk(1, "out of memory!\n"); + return -ENOMEM; + } + + mutex_init(&dvb_dev->sem); + + dev->em28xx_write_regs_req(dev, 0x00, 0x48, "\x00", 1); + dev->em28xx_write_regs_req(dev, 0x00, 0x12, "\x77", 1); + + + if (dev->dvbnorm == NULL) { + kfree(dvb_dev); + return -EINVAL; + } + + gpio_arg = EM28XX_REG_OFF; + dev->em28xx_gpio_control(dev, EM28XX_ANALOG_ON, &gpio_arg); + gpio_arg = EM28XX_REG_ON; + dev->em28xx_gpio_control(dev, EM28XX_TS1_ON, &gpio_arg); + dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON, &gpio_arg); + dev->em28xx_gpio_control(dev, EM28XX_DECODER_SLEEP, &gpio_arg); + dev->em28xx_gpio_control(dev, EM28XX_DEMOD1_RESET, NULL); + mdelay(100); + dev->mode = V4L2_TUNER_DIGITAL_TV; + + + switch (dev->model) { + case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2: + case EM2882_BOARD_PINNACLE_HYBRID_PRO: +#ifdef MICRONAS_DRX3975D + dvb_dev->frontend = dvb_attach(drx3973d_attach, + &DRX3973DDefaultCommAttr_g, + &DRX3973DDefaultAddr_g, + &DRX3973DData_g, + &dev->i2c_adap); +#endif + break; + case EM2883_BOARD_TERRATEC_HYBRID_XS_FM: + case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900: + case EM2880_BOARD_TERRATEC_HYBRID_XS: + case EM2880_BOARD_TERRATEC_HYBRID_XS_FR: + case EM2882_BOARD_TERRATEC_HYBRID_XS: + case EM2882_BOARD_LEADTEK_PALMTOP_DTV_200H: + case EM2870_BOARD_TERRATEC_XS: + case EM2881_BOARD_PINNACLE_HYBRID_PRO: + case EM2881_BOARD_DNT_DA2_HYBRID: + case EM2880_BOARD_MSI_DIGIVOX_AD: + case EM2880_BOARD_MSI_DIGIVOX_AD_II: + case EM2870_BOARD_KWORLD_350U: + case EM2883_BOARD_KWORLD_HYBRID_E323: + case EM2888_BOARD_KWORLD_HYBRID_E329: + case EM2888_BOARD_EMPIA_HYBRID: + case EM2880_BOARD_TERRATEC_PRODIGY_XS: + dvb_dev->frontend = dvb_attach(zl10353_attach, + &em2880_zl10353_dev, &dev->i2c_adap); + if (dvb_dev->frontend == NULL) + dvb_dev->frontend = dvb_attach(mt352_attach, + &em2880_mt352_dev, &dev->i2c_adap); + + break; + case EM2870_BOARD_TERRATEC_XS_MT2060: + dvb_dev->frontend = dvb_attach(zl10353_attach, + &em2880_zl10353_pinnacle, &dev->i2c_adap); + break; + case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950: + case EM2883_BOARD_PINNACLE_PCTV_HD_PRO: + case EM2883_BOARD_KWORLD_HYBRID_A316: + case EM2883_BOARD_ATI_TVWONDER600: + dvb_dev->frontend = dvb_attach(lgdt330x_attach, + &em2880_lgdt3303_dev, &dev->i2c_adap); + break; + case EM2870_BOARD_KWORLD_355U: + case EM2870_BOARD_COMPRO_VIDEOMATE: + em28xx_ts_bus_ctrl_int(dev, 0); + /* fall through */ + case EM2870_BOARD_PINNACLE_PCTV_DVB: + dvb_dev->frontend = dvb_attach(zl10353_attach, + &em2880_kworld_355u_dev, + &dev->i2c_adap); + break; + case EM2883_BOARD_EMPIA_HYBRID_ATSC: + case EM2883_BOARD_EQUINUX_TUBESTICK_ATSC: + dvb_dev->frontend = dvb_attach(lgdt3304_attach, + &lgdt3304_atsc_config, &dev->i2c_adap); + break; + case EM2875_BOARD_SAMPLE_ISDBT: + +#if 1 + dvb_dev->frontend = dvb_attach(s921_attach, + &sharp_isdbt_config, &dev->i2c_adap); +#endif + if (dvb_dev->frontend) { + dvb_dev->frontend->tuner_priv = dev; + dvb_dev->init_override = dvb_dev->frontend->ops.init; + dvb_dev->frontend->ops.init = em28xx_s921_init; + } else { + printk("failed to initialize s921\n"); + } + break; + case EM2879_BOARD_SAMPLE_DMB: +#if 0 + dvb_dev->frontend = dvb_attach(dmb_attach, + &sharp_dmb_config, &dev->i2c_adap); +#endif + break; + default: + printk(KERN_INFO"em2880-dvb.c: unsupported device\n"); + kfree(dvb_dev); + return -EINVAL; + } + + if (dvb_dev->frontend && dev->model != EM2875_BOARD_SAMPLE_ISDBT) { + switch (dev->tuner_type) { + case TUNER_XCEIVE_XC3028: + case TUNER_XCEIVE_XC5000: + dvb_dev->frontend->tuner_priv = dev; + dvb_dev->frontend->ops.tuner_ops.set_params = + em28xx_set_params; + dvb_dev->frontend->ops.tuner_ops.get_frequency = + em28xx_get_frequency; + dvb_dev->frontend->ops.tuner_ops.get_bandwidth = + em28xx_get_bandwidth; + + /* xc3028 powersaving mode */ + dvb_dev->frontend->ops.tuner_ops.init = + em28xx_dvb_init; + dvb_dev->frontend->ops.tuner_ops.sleep = + em28xx_dvb_sleep; + break; + case TUNER_MT2060: + dvb_attach(mt2060_attach, dvb_dev->frontend, + &dev->i2c_adap, &em2870_mt2060_config, + if1); + break; + case TUNER_QT1010: + dvb_dev->frontend->ops.i2c_gate_ctrl = + kworld355u_i2c_gate_ctrl; + dvb_attach(qt1010_attach, dvb_dev->frontend, + &dev->i2c_adap, &em2870_qt1010_config); + break; + case TUNER_ADIMTV102: +#ifdef ADIMTV102 + dvb_attach(adimtv102_attach, dvb_dev->frontend, &dev->i2c_adap, &dmbt_adim_config); +#endif + break; + default: + printk(KERN_INFO"unsupported tuner (%d)\n", + dev->tuner_type); + } + + /* zl10353 powersaving */ + dvb_dev->init_override = dvb_dev->frontend->ops.init; + + dvb_dev->frontend->ops.sleep = em28xx_zl10353_sleep; + dvb_dev->frontend->ops.init = em28xx_zl10353_init; + } + + if (NULL == dvb_dev->frontend) { + printk(KERN_INFO"em2880-dvb.c: failed initializing zl10353" + "DVB-T demodulator\n"); + printk(KERN_INFO"em2880-dvb.c: retrying with mt352 DVB-T" + "demodulator\n"); + if (NULL == dvb_dev->frontend) { + printk(KERN_INFO"em2880-dvb.c: no luck with mt352" + "demodulator, not attaching em2880-dvb\n"); + kfree(dvb_dev); + return -ENODEV; + } + } + + /* this is not backward compatible */ +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 20) + dvb_dev->frontend->ops.ts_bus_ctrl = em28xx_ts_bus_ctrl; +#endif + + dvb_register_adapter(&dvb_dev->adapter, "em2880 DVB-T", THIS_MODULE, + &dev->udev->dev +#if defined DVB_DEFINE_MOD_OPT_ADAPTER_NR + ,adapter_nr +#endif + ); + + + for (i=0; iuif->altsetting[1].desc.bNumEndpoints; i++) { + if (dev->uif->altsetting[1].endpoint[i].desc.bEndpointAddress == 0x84) { + dvb_dev->dtv_packetsize = dev->uif->altsetting[1]. + endpoint[i].desc.wMaxPacketSize; + break; + } + } + + dvb_dev->adapter.priv = dev; + + dvb_register_frontend(&dvb_dev->adapter, dvb_dev->frontend); + + dvb_dev->demux.priv = dvb_dev; + dvb_dev->demux.filternum = 256; + dvb_dev->demux.feednum = 256; + dvb_dev->demux.start_feed = em2880_start_feed; + dvb_dev->demux.stop_feed = em2880_stop_feed; + dvb_dev->demux.dmx.capabilities = DMX_TS_FILTERING | + DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING; + + err = dvb_dmx_init(&dvb_dev->demux); + if (err < 0) { + dprintk(1, "dvb_dmx_init failed!\n"); + kfree(dvb_dev); + return -1; + } + + dvb_dev->dmxdev.filternum = dvb_dev->demux.filternum; + dvb_dev->dmxdev.demux = &dvb_dev->demux.dmx; + dvb_dev->dmxdev.capabilities = 0; + + err = dvb_dmxdev_init(&dvb_dev->dmxdev, &dvb_dev->adapter); + + if (err < 0) { + dprintk(1, "dvb_dmxdev failed!\n"); + dvb_dmxdev_release(&dvb_dev->dmxdev); + kfree(dvb_dev); + return -1; + } + + dvb_dev->udev = dev->udev; + dvb_dev->em28xx_dev = dev; /* FIXME get rid of this */ + + dev->dvb_dev = dvb_dev; + + dvbdemux = &dvb_dev->demux; + dvb_net_init(&dvb_dev->adapter, &dvb_dev->dvbnet, &dvbdemux->dmx); + + return 0; +} + +static int em2880_dvb_fini(struct em28xx *dev) +{ + struct em2880_dvb *dvb_dev = dev->dvb_dev; + if (!dev) { + dprintk(1, "fini already called!\n"); + return -1; + } + if (!dev->dvb_dev) { + dprintk(1, "dvb_dev not initialized!\n"); + return -1; + } + dvb_dev = dev->dvb_dev; + + dprintk(1, "releasing dvb device!\n"); + + dvb_net_release(&dvb_dev->dvbnet); + dvb_unregister_frontend(dvb_dev->frontend); + dvb_frontend_detach(dvb_dev->frontend); + + dvb_dev->demux.dmx.close(&dvb_dev->demux.dmx); + dvb_dmxdev_release(&dvb_dev->dmxdev); + dvb_dmx_release(&dvb_dev->demux); + + dvb_unregister_adapter(&dvb_dev->adapter); + + dprintk(1, "dvb_fini\n"); + if (dev->dvb_dev) { + dprintk(1, "freeing dvb_dev\n"); + kfree(dev->dvb_dev); + dev->dvb_dev = NULL; + } + return 0; +} + +static struct em28xx_ops dvb_ops = { + .id = EM28XX_DVBT | EM28XX_ATSC | EM28XX_ISDB | EM28XX_DMB, + .name = "Em2880 DVB Extension", + .init = em2880_dvb_init, + .fini = em2880_dvb_fini, +}; + +static int __init em2880_dvb_register(void) +{ + return em28xx_register_extension(&dvb_ops); +} + +static void __exit em2880_dvb_unregister(void) +{ + em28xx_unregister_extension(&dvb_ops); +} + +module_init(em2880_dvb_register); +module_exit(em2880_dvb_unregister); + +/* ------------------------------------------------------------------ */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/empia/em28xx-aad.c b/drivers/media/video/empia/em28xx-aad.c new file mode 100644 index 0000000..7c6dcb6 --- /dev/null +++ b/drivers/media/video/empia/em28xx-aad.c @@ -0,0 +1,404 @@ +/* + * Em28xx-aad Empia Alternative Audio Driver + * + * This is a vendor specific driver which provides a vendor specific + * audio interface for Empia based devices. + * + * Copyright (C) 2008 Empia Technology Inc. + * Copyright (C) 2008 Sundtek Ltd. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "em28xx-aad.h" +#include "em28xx.h" + +static unsigned long em28xx_aad_devices; /* map */ +static int em28xx_aad_register(struct em28xx *dev); + +void em28xx_aad_unregister(struct em28xx_aad_info **int_aad); + +static int em28xx_maxdevs; + +static LIST_HEAD(em28xx_aad_devlist); + +/* ringbuffer implementation, taken from empia radio application */ + +#define AAD_BUFFER_SIZE 20000 + +static struct aad_rb_info *aad_rb_init(void); +static struct aad_rb_info *aad_rb_init(void) { + struct aad_rb_info *rb = kzalloc(sizeof(struct aad_rb_info), GFP_KERNEL); + rb->left = rb->right = rb->pos = 0; + rb->bufsize = AAD_BUFFER_SIZE; + rb->buffer = kzalloc(AAD_BUFFER_SIZE*sizeof(u8), GFP_KERNEL); + if (rb->buffer == NULL) + return 0; + spin_lock_init(&rb->__aad_lock); + return rb; +}; + +void aad_rb_free(struct aad_rb_info **rb) { + kfree((*rb)->buffer); + kfree((*rb)); +} + +static inline u32 aad_rb_get_buffer_size(struct aad_rb_info *rb) { + if (rb->left > rb->right) + return (rb->bufsize - rb->left + rb->right); + else if(rb->left < rb->right) + return (rb->right - rb->left); + else if(rb->left == rb->right) + return 0; + return 0; +} + +static void aad_rb_reset_buffer(struct aad_rb_info *rb) { + unsigned long flags; + spin_lock_irqsave(&rb->__aad_lock, flags); + rb->left = rb->right = rb->pos = 0; + spin_unlock_irqrestore(&rb->__aad_lock, flags); +} + +static inline u32 aad_rb_free_buffer_size(struct aad_rb_info *rb) { + u32 cur_buffsize = aad_rb_get_buffer_size(rb); + if (rb->bufsize - cur_buffsize > 0) + return rb->bufsize - cur_buffsize - 1; + return rb->bufsize - 1; +} + +static int aad_rb_write_data(struct aad_rb_info *rb, u8 *data, ssize_t size) { + int len = size; + //int len2; + int free; + unsigned long flags; + /* lock */ + spin_lock_irqsave(&rb->__aad_lock, flags); + free = aad_rb_free_buffer_size(rb); + + if (size > free) + size = free - free % sizeof(u32); /* alignment */ + + if (free == 0) { + if(printk_ratelimit()) + printk("dropping data!\n"); + /* unlock */ + spin_unlock_irqrestore(&rb->__aad_lock, flags); + return 0; + } + if (rb->right + size >= rb->bufsize - 1) { + len = rb->bufsize - rb->right; + memcpy(&rb->buffer[rb->right], data, len); + memcpy(rb->buffer, data+len, size - len); + rb->right = size - len; + } else { + memcpy(&rb->buffer[rb->right], data, size); + rb->right += size; + } + /* unlock */ + spin_unlock_irqrestore(&rb->__aad_lock, flags); + return 0; +} + +/* this is a bit complicated since we cannot do memcpy/copy_to_user within the + * spinlocked block, it might work fine with most systems although the target + * systems just blew up */ + +static int aad_rb_read_data(struct em28xx_aad_info *aad, u8 *buffer, ssize_t size) { + int len; + int bufsize; + unsigned int left; + unsigned long flags; + struct aad_rb_info *rb = aad->__aad_rb[aad->__curr_rb]; + /* lock */ + spin_lock_irqsave(&rb->__aad_lock, flags); + + + if (aad_rb_get_buffer_size(rb) == 0) { + /* unlock */ + spin_unlock_irqrestore(&rb->__aad_lock, flags); + return 0; + } + + bufsize = aad_rb_get_buffer_size(rb); + if (size > bufsize) + size = bufsize; + + size -= size % 8; /* alignment */ + + aad->__curr_rb ^= 1; /* switch rb buffer so the irq can write to the other buffer during + the copy routine */ + + aad->__write_rb = aad->__aad_rb[aad->__curr_rb]; + + if (rb->left + size >= rb->bufsize -1) { + len = rb->bufsize - rb->left; + left = rb->left; + rb->left = size - len; + spin_unlock_irqrestore(&rb->__aad_lock, flags); + copy_to_user(buffer, &rb->buffer[left], len); + copy_to_user(&buffer[len], rb->buffer, size - len); + } else { + left = rb->left; + rb->left += size; + spin_unlock_irqrestore(&rb->__aad_lock, flags); + copy_to_user(buffer, &rb->buffer[left], size); + } + + /* reset our backed up ringbuffer */ + + aad_rb_reset_buffer(rb); + /* unlock */ + return size; +} + +/* end ringbuffer implementation */ + +/* usb code */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +static void em28xx_aad_isocirq(struct urb *urb, struct pt_regs *regs) +#else +static void em28xx_aad_isocirq(struct urb *urb) +#endif +{ + struct em28xx_aad_info *aad = urb->context; + u8 *cp; + int i; + for(i=0;inumber_of_packets;i++){ + int length=urb->iso_frame_desc[i].actual_length; + cp=(unsigned char *) urb->transfer_buffer + urb->iso_frame_desc[i].offset; + + if(!length) + continue; + + + if (urb->iso_frame_desc[i].status) + continue; + + aad_rb_write_data(aad->__write_rb, cp, length); + + } + urb->status = 0; + + if(usb_submit_urb(urb, GFP_ATOMIC)) + printk("unable to submit urb\n"); +} + +static int aad_audio_isoc_deinit(struct em28xx_aad_info *aad) +{ + int i; + for(i=0;iurb[i]); + usb_free_urb(aad->urb[i]); + aad->urb[i]=NULL; + } + return 0; +} + +static int aad_audio_isoc_init(struct em28xx_aad_info *aad) +{ + int i; + int errCode; + const int sb_size=AAD_NUM_AUDIO_PACKETS * AAD_AUDIO_MAX_PACKET_SIZE; + + printk("aad_isoc audio init\n"); + + for(i=0;itransfer_buffer[i]=kmalloc(sb_size,GFP_ATOMIC); + + if(!aad->transfer_buffer[i]) + return -ENOMEM; + + memset(aad->transfer_buffer[i],0x80,sb_size); + urb = usb_alloc_urb(AAD_NUM_AUDIO_PACKETS,GFP_ATOMIC); + + if(urb){ + urb->dev=aad->__aad_udev; + urb->context=aad; + urb->pipe=usb_rcvisocpipe(aad->__aad_udev, 0x83); + urb->transfer_flags = URB_ISO_ASAP; + urb->transfer_buffer = aad->transfer_buffer[i]; + urb->interval=1; + urb->complete = em28xx_aad_isocirq; + urb->number_of_packets = AAD_NUM_AUDIO_PACKETS; + urb->transfer_buffer_length = sb_size; + for(j=k=0; jiso_frame_desc[j].offset = k; + urb->iso_frame_desc[j].length=AAD_AUDIO_MAX_PACKET_SIZE; + } + aad->urb[i]=urb; + } else + return -ENOMEM; + + } + for(i=0;iurb[i], GFP_ATOMIC); + if (errCode){ + aad_audio_isoc_deinit(aad); + return errCode; + } + } + return 0; +} + +/* cheap usercheck */ +static int aad_users; + +static int aad_open(struct inode *inode, struct file *file) { + struct em28xx_aad_info *aad = NULL; + struct list_head *list; + + if (aad_users != 0) + return -EBUSY; + aad_users++; + /* TODO: support multiple devices here */ + list_for_each(list, &em28xx_aad_devlist) { + aad = list_entry(list, struct em28xx_aad_info, __aad_devlist); + break; + } + + if (aad == NULL) + return -EINVAL; + + printk("found device entry!\n"); + + file->private_data = aad; + + aad_rb_reset_buffer(aad->__write_rb); + aad->__aad_callback(aad->__cb_priv, EM28XX_ENABLE_AUDIO, NULL); + aad_audio_isoc_init(aad); + return 0; +} + +static ssize_t aad_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { + struct em28xx_aad_info *aad = NULL; + ssize_t retval; + aad = filp->private_data; + retval = aad_rb_read_data(aad, buf, count); + return retval; +} + +static int aad_release(struct inode *inode, struct file *file) { + struct em28xx_aad_info *aad; + aad = file->private_data; + aad_users--; + aad_audio_isoc_deinit(aad); + return 0; +} + +static struct file_operations em28xx_aad_fops = { + .owner = THIS_MODULE, + .open = aad_open, + .read = aad_read, + .release = aad_release, +}; + +static int em28xx_aad_register(struct em28xx *dev) { + struct em28xx_aad_info *aad; + int id; + aad = kzalloc(sizeof(struct em28xx_aad_info), GFP_KERNEL); + id = find_first_zero_bit(&em28xx_aad_devices, sizeof(u32)); + em28xx_aad_devices |= (1<__id = id; + aad->__aad_udev = dev->udev; + aad->__aad_callback = dev->em28xx_aad_control; + aad->__cb_priv = dev; + dev->aad = aad; + snprintf(aad->__aad_devname, 50, "aad%d", id); + printk("em28xx-aad: registered /dev/%s\n",aad->__aad_devname); + + aad->__aad_major = register_chrdev(0, aad->__aad_devname, &em28xx_aad_fops); + aad->__aad_class = class_create(THIS_MODULE, aad->__aad_devname); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26) + aad->__aad_cdev = class_device_create(aad->__aad_class, NULL, MKDEV(aad->__aad_major, 0), NULL, aad->__aad_devname); +#else +/* XXX now in 2.6.27 device_create == device_create_drvdata ... */ +/* dvb-core/dvbdev.c uses d_c_drvdata already, imitate that since I don't + * know any better... XXX */ + aad->__aad_cdev = device_create_drvdata(aad->__aad_class, NULL, + MKDEV(aad->__aad_major, 0), NULL, aad->__aad_devname); +#endif + + INIT_LIST_HEAD(&aad->__aad_devlist); + list_add_tail(&aad->__aad_devlist, &em28xx_aad_devlist); + + /* we cannot copy out from the ringpuffer while it is spinlocked with 2.6.17 */ + aad->__aad_rb[0] = aad_rb_init(); + aad->__aad_rb[1] = aad_rb_init(); + aad->__write_rb = aad->__aad_rb[0]; + aad->__curr_rb = 0; + return 0; +} + +void em28xx_aad_unregister(struct em28xx_aad_info **aad_int) { + struct em28xx_aad_info *aad = (*aad_int); + em28xx_aad_devices &= ~ (1<__id); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26) + class_device_destroy(aad->__aad_class, MKDEV(aad->__aad_major, 0)); +#else + device_destroy(aad->__aad_class, MKDEV(aad->__aad_major, 0)); +#endif + unregister_chrdev(0, aad->__aad_devname); + class_destroy(aad->__aad_class); + aad_rb_free(&aad->__aad_rb[0]); + aad_rb_free(&aad->__aad_rb[1]); + list_del(&aad->__aad_devlist); + + kfree(aad); +} + +static int em28xx_aad_fini(struct em28xx *dev) { + em28xx_aad_unregister(&dev->aad); + dev->aad = NULL; + return 0; +} + +static struct em28xx_ops audio_ops = { + .id = EM28XX_AUDIO, + .name = "Em28xx Alternative Audio Driver", + .init = em28xx_aad_register, + .fini = em28xx_aad_fini, +}; + +static int __init em28xx_aad_init(void) { + printk("initializing Empia Audio Driver\n"); + printk("Copyright (C) 2008 Empia Technology Inc\n"); + printk("Copyright (C) 2008 Sundtek Ltd.\n"); + + em28xx_maxdevs = sizeof(int); + return em28xx_register_extension(&audio_ops); +} + +static void __exit em28xx_aad_exit(void) { + printk("releasing Empia Audio Driver\n"); + em28xx_unregister_extension(&audio_ops); +} + +MODULE_AUTHOR("Empia Technology Inc./Sundtek Ltd."); +MODULE_DESCRIPTION("Alternative Audio Driver for Empia based devices"); +MODULE_LICENSE("GPL"); + +module_init(em28xx_aad_init); +module_exit(em28xx_aad_exit); diff --git a/drivers/media/video/empia/em28xx-aad.h b/drivers/media/video/empia/em28xx-aad.h new file mode 100644 index 0000000..fd39ed1 --- /dev/null +++ b/drivers/media/video/empia/em28xx-aad.h @@ -0,0 +1,46 @@ +#ifndef __EM28XX_AAD_H +#define __EM28XX_AAD_H + +#include +#include +#include +#include +#include /* need __user */ + +#define MAX_AAD_DEVS 10 + +struct aad_rb_info { + spinlock_t __aad_lock; + u32 left; + u32 right; + u32 pos; + u32 bufsize; + u8 *buffer; +}; + +#define AAD_AUDIO_BUFS 3 +#define AAD_NUM_AUDIO_PACKETS 64 +#define AAD_AUDIO_MAX_PACKET_SIZE 196 /* static value */ + +struct em28xx_aad_info { + u32 __id; + u8 __aad_devname[50]; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26) + struct class_device *__aad_cdev; +#else + struct device *__aad_cdev; +#endif + int __aad_major; + struct class *__aad_class; + struct list_head __aad_devlist; + struct aad_rb_info *__aad_rb[2]; + struct aad_rb_info *__write_rb; + u8 __curr_rb; + struct usb_device *__aad_udev; + u8 *transfer_buffer[AAD_AUDIO_BUFS]; + struct urb *urb[AAD_AUDIO_BUFS]; + int (*__aad_callback)(void *, unsigned int cmd, void *arg); + void *__cb_priv; +}; + +#endif diff --git a/drivers/media/video/empia/em28xx-audio.c b/drivers/media/video/empia/em28xx-audio.c new file mode 100644 index 0000000..a1d06a7 --- /dev/null +++ b/drivers/media/video/empia/em28xx-audio.c @@ -0,0 +1,630 @@ +/* + * Empiatech em28x1 audio extension + * + * Copyright (C) 2006 Markus Rechberger + * + * This driver is based on my previous au600 usb pstn audio driver + * and inherits all the copyrights + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "em28xx.h" +#include "xc5000/xc5000_control.h" + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static int em28xx_cmd(struct em28xx *dev, int cmd,int arg); + + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static int snd_pcm_alloc_vmalloc_buffer(snd_pcm_substream_t *subs, size_t size) +#else +static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, size_t size) +#endif +{ +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) + snd_pcm_runtime_t *runtime = subs->runtime; +#else + struct snd_pcm_runtime *runtime = subs->runtime; +#endif + if(runtime->dma_area){ + if(runtime->dma_bytes > size) + return 0; + vfree(runtime->dma_area); + } + runtime->dma_area = vmalloc(size); + if(!runtime ->dma_area) + return -ENOMEM; + runtime->dma_bytes = size; + return 0; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static snd_pcm_hardware_t snd_em28xx_hw_capture = { +#else +static struct snd_pcm_hardware snd_em28xx_hw_capture = { +#endif + .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 1024*1024, + .period_bytes_min = 64, + .period_bytes_max = 512*1024, + .periods_min = 2, + .periods_max = 1024, +}; + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static int snd_em28xx_capture_open(snd_pcm_substream_t *substream) +#else +static int snd_em28xx_capture_open(struct snd_pcm_substream *substream) +#endif +{ + int ret = 0; + int mode; + int arg; + struct em28xx *dev = snd_pcm_substream_chip(substream); +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) + snd_pcm_runtime_t *runtime = substream->runtime; +#else + struct snd_pcm_runtime *runtime = substream->runtime; +#endif + printk("opening radio device and trying to acquire exclusive lock\n"); + switch(dev->mode){ + case V4L2_TUNER_DIGITAL_TV: + /* digital has no support for analog audio */ + ret = dev->em28xx_acquire(dev, EM28XX_RADIO, 1); + if (ret != 0 ) { + printk("device is already in use by DVB-T\n"); + return -EINVAL; + } else { + struct v4l2_tuner tuner; + struct v4l2_routing arouting; + printk("switching device to FM mode\n"); + + mode = V4L2_TUNER_RADIO; + memset(&tuner, 0x0, sizeof(struct v4l2_tuner)); + tuner.type = V4L2_TUNER_RADIO; + + dev->mode = mode; + + arg = EM28XX_REG_ON; + dev->em28xx_gpio_control(dev, EM28XX_ANALOG_ON, &arg); + dev->em28xx_gpio_control(dev, EM28XX_LED1_ON, &arg); + dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON, &arg); + arg = EM28XX_REG_OFF; + dev->em28xx_gpio_control(dev, EM28XX_TS1_ON, &arg); + mdelay(100); + + /* upload firmware */ + switch(dev->tuner_type) { + case TUNER_XCEIVE_XC3028: + { + struct xc3028_init_cmd cmd; + + /* this is moreover to switch the decoder to FM */ + cmd.new_tv_mode_ptr = dev->fmnorm->tv_mode; + cmd.new_channel_map_ptr = dev->fmnorm->channelmap; + if (dev->tuner) + dev->tuner->tuner_cmd(dev->tuner, XC3028_INIT_TUNER, &cmd); + break; + } + case TUNER_XCEIVE_XC5000: + { + struct xc_std_conf cmd; + /* this is moreover to switch the decoder to FM */ + if (dev->tuner) { + dev->tuner->tuner_cmd(dev->tuner, XC5000_INIT_TUNER, NULL); + cmd.index=dev->fmnorm->index; + dev->tuner->tuner_cmd(dev->tuner, XC5000_SET_MODE, &cmd); + } + break; + } + default: + break; + } + + dev->em28xx_write_reg_bits(dev, R0F_XCLK_REG, 0x80, 0x80); + + em28xx_i2c_call_clients(dev, VIDIOC_INT_RESET, 0); + + arouting.input = CX25843_RADIO; + em28xx_i2c_call_clients(dev, VIDIOC_INT_S_AUDIO_ROUTING, &arouting); + + printk("retrieved mode from tuner: %d\n",mode); + } + break; + + case V4L2_TUNER_ANALOG_TV: + printk("em28xx-audio: device is currently in analog TV mode\n"); + /* unmute by default */ + ret = dev->em28xx_acquire(dev, EM28XX_VIDEO, 1); + if (ret!=0) + return -EBUSY; + + dev->em28xx_write_reg_bits(dev, R0F_XCLK_REG, 0x80, 0x80); + + break; + case V4L2_TUNER_RADIO: + { + struct v4l2_routing arouting; + /* check current mode and put a hard lock onto it */ + printk("em28xx-audio: device is currently in analogue FM mode\n"); + /* unmute by default here */ + ret = dev->em28xx_acquire(dev, EM28XX_RADIO, 1); + + dev->mode = V4L2_TUNER_RADIO; + + if ( ret == 0 ) + printk("device is locked in fmradio mode now\n"); + + dev->em28xx_write_reg_bits(dev, R0F_XCLK_REG, 0x80, 0x80); + arouting.input = CX25843_RADIO; + em28xx_i2c_call_clients(dev, VIDIOC_INT_S_AUDIO_ROUTING, &arouting); + + break; + } + default: + printk("em28xx-audio: unhandled mode %d\n", dev->mode); + return -EINVAL; + } + runtime->hw = snd_em28xx_hw_capture; + if(dev->alt == 0 && dev->adev->users == 0 ) { + int errCode; + dev->alt = 7; + errCode = usb_set_interface(dev->udev, dev->usb_interface, 7); + printk("changing alternate number to 7\n"); + } + dev->adev->users++; + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + dev->adev->capture_pcm_substream = substream; + runtime->private_data = dev; + return 0; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static int snd_em28xx_pcm_close(snd_pcm_substream_t *substream) +#else +static int snd_em28xx_pcm_close(struct snd_pcm_substream *substream) +#endif +{ + struct em28xx *dev = snd_pcm_substream_chip(substream); + int amode = 0; + dev->adev->users--; + + /* decrease audio reference */ + switch(dev->mode) { + case V4L2_TUNER_ANALOG_TV: + amode = EM28XX_VIDEO; + break; + case V4L2_TUNER_RADIO: + amode = EM28XX_RADIO; + break; + default: + printk("invalid mode: %d\n",dev->mode); + return -EINVAL; + } + + dev->em28xx_acquire(dev, amode, 0); + + if(dev->adev->users == 0 && dev->adev->shutdown == 1) { + dev->adev->shutdown = 0; + em28xx_cmd(dev,EM28XX_CAPTURE_STREAM_EN,0); + } + wake_up(&dev->adev->open); + return 0; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static int snd_em28xx_hw_capture_params(snd_pcm_substream_t *substream, snd_pcm_hw_params_t *hw_params) +#else +static int snd_em28xx_hw_capture_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) +#endif +{ + unsigned int channels, rate, format; + struct em28xx *dev = snd_pcm_substream_chip(substream); + int ret; + ret = snd_pcm_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params)); + dev->adev->hwptr_done_capture = 0; + + format = params_format(hw_params); + rate = params_rate(hw_params); + channels = params_channels(hw_params); + /* TODO: set up em28xx audio chip to deliver the correct audio format, + xc5000/cx25843 supports stereo + xc3028/em202 supports mono only + */ + return 0; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static int snd_em28xx_hw_capture_free(snd_pcm_substream_t *substream) +#else +static int snd_em28xx_hw_capture_free(struct snd_pcm_substream *substream) +#endif +{ + struct em28xx *dev = snd_pcm_substream_chip(substream); + + if(dev->adev->capture_stream==STREAM_ON) + em28xx_cmd(dev,EM28XX_CAPTURE_STREAM_EN,0); + + return 0; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static int snd_em28xx_prepare(snd_pcm_substream_t *substream) +#else +static int snd_em28xx_prepare(struct snd_pcm_substream *substream) +#endif +{ + return 0; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static int snd_em28xx_capture_trigger(snd_pcm_substream_t *substream, int cmd) +#else +static int snd_em28xx_capture_trigger(struct snd_pcm_substream *substream, int cmd) +#endif +{ + struct em28xx *dev = snd_pcm_substream_chip(substream); + switch(cmd){ + case SNDRV_PCM_TRIGGER_START: + em28xx_cmd(dev,EM28XX_CAPTURE_STREAM_EN,1); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + dev->adev->shutdown=1; + return 0; + default: + return -EINVAL; + } +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +static void em28xx_audio_isocirq(struct urb *urb, struct pt_regs *regs) +#else +static void em28xx_audio_isocirq(struct urb *urb) +#endif +{ + struct em28xx *dev = urb->context; + struct em28xx_audio *adev = dev->adev; + int i; + unsigned int oldptr; + unsigned long flags; + int period_elapsed = 0; + int status; + unsigned char *cp; + unsigned int stride; +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) + snd_pcm_substream_t *substream; + snd_pcm_runtime_t *runtime; +#else + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; +#endif + if (adev->capture_stream == STREAM_INTERRUPT) { + adev->capture_stream = STREAM_OFF; + wake_up_interruptible(&adev->audio_wait_stream); + } + + if ((dev->state & DEV_DISCONNECTED) || + (dev->state & DEV_MISCONFIGURED)) + return; + + + if(dev->adev->capture_pcm_substream){ + substream=dev->adev->capture_pcm_substream; + runtime=substream->runtime; + + stride = runtime->frame_bits >> 3; + for(i=0;inumber_of_packets;i++){ + + int length=urb->iso_frame_desc[i].actual_length/stride; + cp=(unsigned char *) urb->transfer_buffer + urb->iso_frame_desc[i].offset; + + if(!length) + continue; + + + if (urb->iso_frame_desc[i].status) + continue; + + spin_lock_irqsave(&dev->adev->slock, flags); + oldptr = dev->adev->hwptr_done_capture; + dev->adev->hwptr_done_capture +=length; + if(dev->adev->hwptr_done_capture >= runtime->buffer_size) + dev->adev->hwptr_done_capture -= runtime->buffer_size; + + dev->adev->capture_transfer_done += length; + if(dev->adev->capture_transfer_done >= runtime->period_size){ + dev->adev->capture_transfer_done -= runtime->period_size; + period_elapsed=1; + } + spin_unlock_irqrestore(&dev->adev->slock, flags); + + if(oldptr + length > runtime->buffer_size){ + unsigned int cnt = runtime->buffer_size-oldptr; + memcpy(runtime->dma_area+oldptr*stride, cp , cnt*stride); + memcpy(runtime->dma_area, cp + cnt*stride, length*stride - cnt*stride); + } else { + memcpy(runtime->dma_area+oldptr*stride, cp, length*stride); + } + } + if(period_elapsed){ + snd_pcm_period_elapsed(substream); + } + } + urb->status = 0; + + if(dev->adev->shutdown) + return; + + if((status = usb_submit_urb(urb, GFP_ATOMIC))) + em28xx_errdev("resubmit of audio urb failed (error=%i)\n", status); + return; +} + +static int em28xx_isoc_audio_deinit(struct em28xx *dev) +{ + int i; + for(i=0;iadev->urb[i]); + usb_free_urb(dev->adev->urb[i]); + dev->adev->urb[i]=NULL; + } + return 0; +} + +static int em28xx_init_audio_isoc(struct em28xx *dev) +{ + int i; + int errCode; + const int sb_size=EM28XX_NUM_AUDIO_PACKETS * EM28XX_AUDIO_MAX_PACKET_SIZE; + + for(i=0;iadev->transfer_buffer[i]=kmalloc(sb_size,GFP_ATOMIC); + + if(!dev->adev->transfer_buffer[i]) + return -ENOMEM; + + memset(dev->adev->transfer_buffer[i],0x80,sb_size); + urb = usb_alloc_urb(EM28XX_NUM_AUDIO_PACKETS,GFP_ATOMIC); + + if(urb){ + urb->dev=dev->udev; + urb->context=dev; + urb->pipe=usb_rcvisocpipe(dev->udev,0x83); + urb->transfer_flags = URB_ISO_ASAP; + urb->transfer_buffer = dev->adev->transfer_buffer[i]; + urb->interval=1; + urb->complete = em28xx_audio_isocirq; + urb->number_of_packets = EM28XX_NUM_AUDIO_PACKETS; + urb->transfer_buffer_length = sb_size; + for(j=k=0; jiso_frame_desc[j].offset = k; + urb->iso_frame_desc[j].length=EM28XX_AUDIO_MAX_PACKET_SIZE; + } + dev->adev->urb[i]=urb; + } else + return -ENOMEM; + + } + for(i=0;iadev->urb[i], GFP_ATOMIC); + if (errCode){ + em28xx_isoc_audio_deinit(dev); + return errCode; + } + } + return 0; +} + + +static int em28xx_cmd(struct em28xx *dev, int cmd,int arg) +{ + switch(cmd){ + case EM28XX_CAPTURE_STREAM_EN: + if(dev->adev->capture_stream == STREAM_OFF && arg==1){ + dev->adev->capture_stream=STREAM_ON; + em28xx_init_audio_isoc(dev); + } else if (dev->adev->capture_stream==STREAM_ON && arg==0){ + dev->adev->capture_stream=STREAM_OFF; + em28xx_isoc_audio_deinit(dev); + } + return 0; + default: + return -EINVAL; + } +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static snd_pcm_uframes_t snd_em28xx_capture_pointer(snd_pcm_substream_t *substream) +#else +static snd_pcm_uframes_t snd_em28xx_capture_pointer(struct snd_pcm_substream *substream) +#endif +{ + struct em28xx *dev; + snd_pcm_uframes_t hwptr_done; + dev = snd_pcm_substream_chip(substream); + hwptr_done = dev->adev->hwptr_done_capture; + return hwptr_done; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static struct page *snd_pcm_get_vmalloc_page(snd_pcm_substream_t *subs, + unsigned long offset) +#else +static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs, + unsigned long offset) +#endif +{ + void *pageptr = subs->runtime->dma_area + offset; + return vmalloc_to_page(pageptr); +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static snd_pcm_ops_t snd_em28xx_pcm_capture = { +#else +static struct snd_pcm_ops snd_em28xx_pcm_capture = { +#endif + .open = snd_em28xx_capture_open, + .close = snd_em28xx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_em28xx_hw_capture_params, + .hw_free = snd_em28xx_hw_capture_free, + .prepare = snd_em28xx_prepare, + .trigger = snd_em28xx_capture_trigger, + .pointer = snd_em28xx_capture_pointer, + .page = snd_pcm_get_vmalloc_page, +}; + + +static int em28xx_audio_init(struct em28xx *dev) +{ + struct em28xx_audio *adev; +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) + snd_pcm_t *pcm; + snd_card_t *card; +#else + struct snd_pcm *pcm; + struct snd_card *card; +#endif + static int devnr; + int ret; + int err; + printk("em28xx-audio.c: probing for em28x1 non standard usbaudio\n"); + printk("em28xx-audio.c: Copyright (C) 2006 Markus Rechberger\n"); + adev=kzalloc(sizeof(*adev),GFP_KERNEL); + if(!adev){ + printk("em28xx-audio.c: out of memory\n"); + return -1; + } + card = snd_card_new(index[devnr], "Em28xx Audio", THIS_MODULE,0); + if(card==NULL){ + kfree(adev); + return -ENOMEM; + } + + spin_lock_init(&adev->slock); + init_waitqueue_head(&adev->audio_wait_stream); + init_waitqueue_head(&adev->open); + ret=snd_pcm_new(card, "Em28xx Audio", 0, 0, 1, &pcm); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_em28xx_pcm_capture); + pcm->info_flags = 0; + pcm->private_data = dev; + strcpy(pcm->name,"Empia 28xx Capture"); + strcpy(card->driver, "Empia Em28xx Audio"); + strcpy(card->shortname, "Em28xx Audio"); + strcpy(card->longname,"Empia Em28xx Audio"); + + if((err = snd_card_register(card))<0){ + snd_card_free(card); + return -ENOMEM; + } + adev->sndcard=card; + adev->udev=dev->udev; + dev->adev=adev; + return 0; +} + +static int em28xx_audio_fini(struct em28xx *dev) +{ + struct em28xx_audio *adev = dev->adev; + int ret; + printk("audio: deinitializing audio device\n"); + + if(dev==NULL) { + printk("audio: dev is already null\n"); + return 0; + } + + if(adev){ + printk("audio: before waitstream\n"); + if (adev->capture_stream == STREAM_ON) { + adev->capture_stream = STREAM_INTERRUPT; + ret = wait_event_timeout(adev->audio_wait_stream, + (adev->capture_stream == STREAM_OFF) || + (dev->state & DEV_DISCONNECTED), + EM28XX_URB_TIMEOUT); + } + printk("audio: after waitstream\n"); + printk("audio: waiting for audioclients to release audio node\n"); + + if (adev->users>0) + /* we have to strictly wait for this... */ + + wait_event(adev->open, (adev->users == 0)); + + snd_card_free(dev->adev->sndcard); + kfree(dev->adev); + dev->adev=NULL; + } + return 0; +} + +static struct em28xx_ops audio_ops = { + .id = EM28XX_AUDIO, + .name = "Em28xx Audio Extension", + .init = em28xx_audio_init, + .fini = em28xx_audio_fini, +}; + +static int __init em28xx_alsa_register(void) +{ + return em28xx_register_extension(&audio_ops); +} + +static void __exit em28xx_alsa_unregister(void) +{ + em28xx_unregister_extension(&audio_ops); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Markus Rechberger "); +MODULE_DESCRIPTION("Em28xx Audio driver"); + +module_init(em28xx_alsa_register); +module_exit(em28xx_alsa_unregister); diff --git a/drivers/media/video/empia/em28xx-audioep.c b/drivers/media/video/empia/em28xx-audioep.c new file mode 100644 index 0000000..868e488 --- /dev/null +++ b/drivers/media/video/empia/em28xx-audioep.c @@ -0,0 +1,522 @@ +/* Copyright (C) 2008 Empia Technology Inc. + * Copyright (C) 2008 Markus Rechberger + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25) +#include +#endif +#include +#include +#include +#include +#include +#include + + +#include "em28xx.h" + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; + +MODULE_AUTHOR("Markus Rechberger "); +MODULE_LICENSE("GPL"); // my appropriate code is dual licensed also BSD + +static struct usb_driver em28xx_audio_drv; +static int em28xx_cmd(struct em28xx_audio *adev, int cmd,int arg); + +static struct usb_device_id em28xx_audio_id_table[]={ + { USB_DEVICE(0x0ccd, 0x0042) }, /* just for testing it will not attach to the real device */ + { USB_DEVICE(0xeb1a, 0xe300) }, + { USB_DEVICE(0xeb1a, 0xe301) }, + { USB_DEVICE(0xeb1a, 0xe305) }, + { USB_DEVICE(0xeb1a, 0xe310) }, + { USB_DEVICE(0xeb1a, 0xe320) }, + { USB_DEVICE(0xeb1a, 0x2861) }, + { }, +}; + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static snd_pcm_hardware_t snd_em28xx_hw_capture = { +#else +static struct snd_pcm_hardware snd_em28xx_hw_capture = { +#endif + .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 1024*1024, + .period_bytes_min = 64, + .period_bytes_max = 512*1024, + .periods_min = 2, + .periods_max = 1024, +}; + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static int snd_pcm_alloc_vmalloc_buffer(snd_pcm_substream_t *subs, size_t size) +#else +static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, size_t size) +#endif +{ +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) + snd_pcm_runtime_t *runtime = subs->runtime; +#else + struct snd_pcm_runtime *runtime = subs->runtime; +#endif + if(runtime->dma_area){ + if(runtime->dma_bytes > size) + return 0; + vfree(runtime->dma_area); + } + runtime->dma_area = vmalloc(size); + if(!runtime ->dma_area) + return -ENOMEM; + runtime->dma_bytes = size; + return 0; +} + + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static int snd_em28xx_hw_capture_params(snd_pcm_substream_t *substream, snd_pcm_hw_params_t *hw_params) +#else +static int snd_em28xx_hw_capture_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) +#endif +{ + unsigned int channels, rate, format; + struct em28xx_audio *adev = snd_pcm_substream_chip(substream); + int ret; + ret = snd_pcm_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params)); + adev->hwptr_done_capture = 0; + + format = params_format(hw_params); + rate = params_rate(hw_params); + channels = params_channels(hw_params); + /* TODO: set up em28xx audio chip to deliver the correct audio format, + xc5000/cx25843 supports stereo + xc3028/em202 supports mono only + */ + return 0; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static int snd_em28xx_hw_capture_free(snd_pcm_substream_t *substream) +#else +static int snd_em28xx_hw_capture_free(struct snd_pcm_substream *substream) +#endif +{ + struct em28xx_audio *adev = snd_pcm_substream_chip(substream); + + if(adev->capture_stream == STREAM_ON) + em28xx_cmd(adev, EM28XX_CAPTURE_STREAM_EN, 0); + + return 0; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static int snd_em28xx_prepare(snd_pcm_substream_t *substream) +#else +static int snd_em28xx_prepare(struct snd_pcm_substream *substream) +#endif +{ + return 0; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static int snd_em28xx_capture_trigger(snd_pcm_substream_t *substream, int cmd) +#else +static int snd_em28xx_capture_trigger(struct snd_pcm_substream *substream, int cmd) +#endif +{ + struct em28xx_audio *adev = snd_pcm_substream_chip(substream); + switch(cmd){ + case SNDRV_PCM_TRIGGER_START: + em28xx_cmd(adev, EM28XX_CAPTURE_STREAM_EN, 1); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + adev->shutdown = 1; + return 0; + default: + return -EINVAL; + } +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +static void em28xx_audio_isocirq(struct urb *urb, struct pt_regs *regs) +#else +static void em28xx_audio_isocirq(struct urb *urb) +#endif +{ + struct em28xx_audio *adev = urb->context; + int i; + unsigned int oldptr; + unsigned long flags; + int period_elapsed = 0; + unsigned char *cp; + unsigned int stride; + + int status; +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) + snd_pcm_substream_t *substream; + snd_pcm_runtime_t *runtime; +#else + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; +#endif + if (adev->capture_stream == STREAM_INTERRUPT) { + adev->capture_stream = STREAM_OFF; + wake_up_interruptible(&adev->audio_wait_stream); + } + if ((adev->state & DEV_DISCONNECTED) || + (adev->state & DEV_MISCONFIGURED)) + return; + + + if(adev->capture_pcm_substream){ + substream=adev->capture_pcm_substream; + runtime=substream->runtime; + + stride = runtime->frame_bits >> 3; + for(i=0;inumber_of_packets;i++){ + + int length=urb->iso_frame_desc[i].actual_length/stride; + cp=(unsigned char *) urb->transfer_buffer + urb->iso_frame_desc[i].offset; + + + if(!length) + continue; + + + if (urb->iso_frame_desc[i].status) + continue; + + spin_lock_irqsave(&adev->slock, flags); + oldptr = adev->hwptr_done_capture; + adev->hwptr_done_capture +=length; + if(adev->hwptr_done_capture >= runtime->buffer_size) + adev->hwptr_done_capture -= runtime->buffer_size; + + adev->capture_transfer_done += length; + if(adev->capture_transfer_done >= runtime->period_size){ + adev->capture_transfer_done -= runtime->period_size; + period_elapsed=1; + } + spin_unlock_irqrestore(&adev->slock, flags); + + if(oldptr + length > runtime->buffer_size){ + unsigned int cnt = runtime->buffer_size-oldptr; + memcpy(runtime->dma_area+oldptr*stride, cp , cnt*stride); + memcpy(runtime->dma_area, cp + cnt*stride, length*stride - cnt*stride); + } else { + memcpy(runtime->dma_area+oldptr*stride, cp, length*stride); + } + } + if(period_elapsed){ + snd_pcm_period_elapsed(substream); + } + } + urb->status = 0; + + if(adev->shutdown) + return; + + if((status = usb_submit_urb(urb, GFP_ATOMIC))) + printk("resubmit of audio urb failed (error=%i)\n", status); + return; +} + + +static int em28xx_isoc_audio_deinit(struct em28xx_audio *adev) +{ + int i; + for(i=0; iurb[i]); + usb_free_urb(adev->urb[i]); + adev->urb[i] = NULL; + } + return 0; +} + +static int em28xx_init_audio_isoc(struct em28xx_audio *adev) +{ + int i; + int errCode; + const int sb_size=EM28XX_NUM_AUDIO_PACKETS * EM28XX_AUDIO_MAX_PACKET_SIZE; + + for(i=0; itransfer_buffer[i]=kzalloc(sb_size,GFP_ATOMIC); + + if(!adev->transfer_buffer[i]) + return -ENOMEM; + + urb = usb_alloc_urb(EM28XX_NUM_AUDIO_PACKETS,GFP_ATOMIC); + + if(urb){ + urb->dev = adev->udev; + urb->context = adev; + urb->pipe = usb_rcvisocpipe(adev->udev,0x83); + urb->transfer_flags = URB_ISO_ASAP; + urb->transfer_buffer = adev->transfer_buffer[i]; + urb->interval = 1; + urb->complete = em28xx_audio_isocirq; + urb->number_of_packets = EM28XX_NUM_AUDIO_PACKETS; + urb->transfer_buffer_length = sb_size; + for(j=k=0; jiso_frame_desc[j].offset = k; + urb->iso_frame_desc[j].length = EM28XX_AUDIO_MAX_PACKET_SIZE; + } + adev->urb[i] = urb; + } else + return -ENOMEM; + + } + for(i=0; iurb[i], GFP_ATOMIC); + if (errCode){ + em28xx_isoc_audio_deinit(adev); + return errCode; + } + } + return 0; +} + +static int em28xx_cmd(struct em28xx_audio *adev, int cmd,int arg) +{ + switch(cmd){ + case EM28XX_CAPTURE_STREAM_EN: + if(adev->capture_stream == STREAM_OFF && arg==1){ + adev->capture_stream = STREAM_ON; + em28xx_init_audio_isoc(adev); + } else if (adev->capture_stream == STREAM_ON && arg==0){ + adev->capture_stream = STREAM_OFF; + em28xx_isoc_audio_deinit(adev); + } + return 0; + default: + return -EINVAL; + } +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static snd_pcm_uframes_t snd_em28xx_capture_pointer(snd_pcm_substream_t *substream) +#else +static snd_pcm_uframes_t snd_em28xx_capture_pointer(struct snd_pcm_substream *substream) +#endif +{ + struct em28xx_audio *adev; + snd_pcm_uframes_t hwptr_done; + adev = snd_pcm_substream_chip(substream); + hwptr_done = adev->hwptr_done_capture; + return hwptr_done; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static struct page *snd_pcm_get_vmalloc_page(snd_pcm_substream_t *subs, + unsigned long offset) +#else +static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs, + unsigned long offset) +#endif +{ + void *pageptr = subs->runtime->dma_area + offset; + return vmalloc_to_page(pageptr); +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static int snd_em28xx_capture_open(snd_pcm_substream_t *substream) +#else +static int snd_em28xx_capture_open(struct snd_pcm_substream *substream) +#endif +{ + struct em28xx_audio *adev = snd_pcm_substream_chip(substream); + int errCode; +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) + snd_pcm_runtime_t *runtime = substream->runtime; +#else + struct snd_pcm_runtime *runtime = substream->runtime; +#endif + + if(adev->alt == 0 && adev->users == 0 ) + errCode = usb_set_interface(adev->udev, 1, adev->alt_max); /* Todo */ + + runtime->hw = snd_em28xx_hw_capture; + + adev->users++; + runtime->private_data = adev; + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + adev->capture_pcm_substream = substream; + return 0; + +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static int snd_em28xx_pcm_close(snd_pcm_substream_t *substream) +#else +static int snd_em28xx_pcm_close(struct snd_pcm_substream *substream) +#endif +{ + struct em28xx_audio *adev = snd_pcm_substream_chip(substream); + adev->users--; + if(adev->users == 0 && adev->shutdown == 1) { + adev->shutdown = 0; + em28xx_cmd(adev, EM28XX_CAPTURE_STREAM_EN, 0); + } + wake_up(&adev->open); + return 0; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) +static snd_pcm_ops_t snd_em28xx_pcm_capture = { +#else +static struct snd_pcm_ops snd_em28xx_pcm_capture = { +#endif + .open = snd_em28xx_capture_open, + .close = snd_em28xx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_em28xx_hw_capture_params, + .hw_free = snd_em28xx_hw_capture_free, + .prepare = snd_em28xx_prepare, + .trigger = snd_em28xx_capture_trigger, + .pointer = snd_em28xx_capture_pointer, + .page = snd_pcm_get_vmalloc_page, +}; + + +static int em28xx_audio_probe(struct usb_interface *interface, const struct usb_device_id *id) +{ + int err; + int i; + u8 max_alt; + u8 if_num; + u16 max_pck = 0, cur_pck = 0; + struct usb_device *udev; + struct em28xx_audio *adev = NULL; + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16) + snd_pcm_t *pcm; + snd_card_t *card; +#else + struct snd_pcm *pcm; + struct snd_card *card; +#endif + + + int retval = -ENODEV; + u8 i_max = 0; + static int devnr; + int ret; + + + struct usb_endpoint_descriptor *endpoint; + udev = usb_get_dev(interface_to_usbdev(interface)); + endpoint = &interface->cur_altsetting->endpoint[0].desc; + + if_num = interface->altsetting[0].desc.bInterfaceNumber; + max_alt = interface->num_altsetting; + + if (max_alt == 6 && if_num > 0) { + printk("registering em28xx-audioep\n"); + for (i=0; ialtsetting[i].endpoint[0].desc.wMaxPacketSize); + if (cur_pck > max_pck) { + i_max = i; + max_pck = cur_pck; + } + } + adev = kzalloc(sizeof(struct em28xx_audio), GFP_KERNEL); + retval = 0; + adev->max_pck = max_pck; + adev->alt_max = i_max; + adev->udev = udev; + + printk("em28xx-audioep.c: detected nonstandard usbaudio\n"); + printk("em28xx-audioep.c: Copyright (C) 2008 Empia Technology Inc.\n"); + printk("em28xx-audioep.c: Copyright (C) 2008 Markus Rechberger\n"); + + + card = snd_card_new(index[devnr], "Em28xx Audio2", THIS_MODULE, 0); + + // todo increase devnr .. or better make a bitmap out of it + + if(card == NULL) { + kfree(adev); + return -ENOMEM; + } + + spin_lock_init(&adev->slock); + init_waitqueue_head(&adev->audio_wait_stream); + init_waitqueue_head(&adev->open); + ret = snd_pcm_new(card, "Em28xx Audio2", 0, 0, 1, &pcm); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_em28xx_pcm_capture); + pcm->info_flags = 0; + pcm->private_data = adev; + strcpy(pcm->name, "Empia em28xx Capture"); + strcpy(card->driver, "Empia Em28xx Audio 2"); + strcpy(card->shortname, "Em28xx Audio 2"); + strcpy(card->longname, "Empia Em28xx Audio 2"); + + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return -ENOMEM; + } + adev->sndcard = card; + + usb_set_intfdata(interface, adev); + } + return retval; +} + +static void em28xx_audio_disconnect(struct usb_interface *interface) +{ + struct em28xx_audio *adev = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + if (!adev) + return; + + if (adev->users > 0) + wait_event(adev->open, (adev->users == 0)); + + snd_card_free(adev->sndcard); + kfree(adev); +} + +static struct usb_driver em28xx_audio_drv = { +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 15) + .owner = THIS_MODULE, +#endif + .name = "em28xx-audioep", + .probe = em28xx_audio_probe, + .disconnect = em28xx_audio_disconnect, + .id_table = em28xx_audio_id_table, +}; + +static int __init em28xx_audio_init(void) +{ + int result; + result = usb_register(&em28xx_audio_drv); + if (result) + printk("usb_register failed. Error number %d.\n", result); + return result; +} + +static void __exit em28xx_audio_exit(void) +{ + usb_deregister(&em28xx_audio_drv); +} + +module_init(em28xx_audio_init); +module_exit(em28xx_audio_exit); diff --git a/drivers/media/video/empia/em28xx-cards.c b/drivers/media/video/empia/em28xx-cards.c new file mode 100644 index 0000000..00bea04 --- /dev/null +++ b/drivers/media/video/empia/em28xx-cards.c @@ -0,0 +1,3387 @@ +/* + em28xx-cards.c - driver for Empia EM2800/EM2820/2840/2880 USB video capture devices + + Copyright (C) 2005 - 2008 Markus Rechberger + 2005 Mauro Carvalho Chehab + 2005 Sascha Sommer + 2005 Ludovico Cavedon + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef EM28XX_TVEEPROM +#include +#endif +#include +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 26) +#include +#else +#include +#endif +#include "msp3400-driver.h" +#include +#include + +#include "xc3028/xc3028_firmwares.h" +#include "xc3028/xc3028l_firmwares.h" +#include "xc3028/xc3028_channelmaps.h" +#define XCEIVE_EXTERNAL_FIRMWARE +#ifdef XCEIVE_EXTERNAL_FIRMWARE +#include "xc3028/xc3028fw_helper.h" +#endif + +#include "em28xx.h" +#include "em28xx-keymaps.h" + +extern unsigned char *XC5000_firmware_SEQUENCE; + + + +static const u8 em28xx_terratec_cinergy_200_usb_i2c_devs[] = {0x4a, 0x60, 0x62, 0x64, 0x86, 0xc0, 0xc2, 0}; +static const u8 em28xx_vgear_pockettv_i2c_devs[] = {0x4a, 0x60, 0xc6, 0}; + +/* keep this map private */ +#define EETI_XC3028_DEFAULT_ANALOG { \ + { \ + /* analogue TV */ \ + .name = "PAL-BG", /* AV - tested OK */ \ + .id = V4L2_STD_PAL_BG, \ + .tv_mode = &XC3028_tv_mode_b_g_pal_nicam_b_mono, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 5, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 18 \ + }, { \ + .name = "PAL-DK", /* AV - tested OK */ \ + .id = V4L2_STD_PAL_DK, \ + .tv_mode = &XC3028_tv_mode_d_k_pal_nicam_mono, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 5, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x12 \ + }, { \ + .name = "PAL-I", /* AV - tested OK */ \ + .id = V4L2_STD_PAL_I, \ + .tv_mode = &XC3028_tv_mode_i_pal_nicam_mono, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 5, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x12 \ + }, { \ + .name = "NTSC-M", /* AV - tested OK */ \ + .id = V4L2_STD_NTSC_M, \ + .tv_mode = &XC3028_tv_mode_m_n_ntsc_pal_a2_mono, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 8, \ + .vbi_start_1 = 318, \ + .vbi_count_0 = 12, \ + .vbi_count_1 = 12, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x06, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x0c \ + }, { \ + .name = "SECAM L", /* AV tested ok */ \ + .id = V4L2_STD_SECAM_L, \ + .tv_mode = &XC3028_tv_mode_l_secam_nicam_am_only, \ + .channelmap = &XC3028_channel_map_france_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 5, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x12 \ + }, { \ + .name = "SECAM LC", /* AV - not tested */ \ + .id = V4L2_STD_SECAM_LC, \ + .tv_mode = &XC3028_tv_mode_d_k_secam_a2_dk1_mono, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 5, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x12 \ + }, { \ + .name = "SECAM K1", /* AV - not tested */ \ + .id = V4L2_STD_SECAM_K1, \ + .tv_mode = &XC3028_tv_mode_d_k_secam_a2_dk1_mono, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 5, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x12 \ + }, { \ + .name = "PAL-M", /* AV - not tested */ \ + .id = V4L2_STD_PAL_M, \ + .tv_mode = &XC3028_tv_mode_m_n_ntsc_pal_a2_mono, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 8, \ + .vbi_start_1 = 318, \ + .vbi_count_0 = 12, \ + .vbi_count_1 = 12, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x06, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x0c \ + } \ +} + +/* keep this map private */ +#define EETI_XC3028_DEFAULT_ANALOG_STEREO { \ + { \ + /* analogue TV */ \ + .name = "PAL-BG", /* AV - tested OK */ \ + .id = V4L2_STD_PAL_BG, \ + .tv_mode = &XC3028_tv_mode_b_g_pal_nicam_b, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 5, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 18 \ + }, { \ + .name = "PAL-DK", /* AV - tested OK */ \ + .id = V4L2_STD_PAL_DK, \ + .tv_mode = &XC3028_tv_mode_d_k_pal_nicam, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 5, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x12 \ + }, { \ + .name = "PAL-I", /* AV - tested OK */ \ + .id = V4L2_STD_PAL_I, \ + .tv_mode = &XC3028_tv_mode_i_pal_nicam, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 5, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x12 \ + }, { \ + .name = "NTSC-M", /* AV - tested OK */ \ + .id = V4L2_STD_NTSC_M, \ + .tv_mode = &XC3028_tv_mode_m_n_ntsc_pal_a2_if, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 8, \ + .vbi_start_1 = 318, \ + .vbi_count_0 = 12, \ + .vbi_count_1 = 12, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x06, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x0c \ + }, { \ + .name = "SECAM L", /* AV - not tested */ \ + .id = V4L2_STD_SECAM_L, \ + .tv_mode = &XC3028_tv_mode_d_k_secam_a2_dk1, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 5, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x12 \ + }, { \ + .name = "SECAM LC", /* AV - not tested */ \ + .id = V4L2_STD_SECAM_LC, \ + .tv_mode = &XC3028_tv_mode_d_k_secam_a2_dk1, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 5, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x12 \ + }, { \ + .name = "SECAM K1", /* AV - not tested */ \ + .id = V4L2_STD_SECAM_K1, \ + .tv_mode = &XC3028_tv_mode_d_k_secam_a2_dk1, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 5, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x12 \ + }, { \ + .name = "PAL-M", /* AV - not tested */ \ + .id = V4L2_STD_PAL_M, \ + .tv_mode = &XC3028_tv_mode_m_n_ntsc_pal_a2, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 8, \ + .vbi_start_1 = 318, \ + .vbi_count_0 = 12, \ + .vbi_count_1 = 12, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x06, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x0c \ + } \ +} + +#define EETI_XC3028L_DEFAULT_ANALOG { \ + { \ + /* analogue TV */ \ + .name = "PAL-BG", /* AV - tested OK */ \ + .id = V4L2_STD_PAL_BG, \ + .tv_mode = &XC3028L_tv_mode_b_g_pal_nicam_b, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 6, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 18 \ + }, { \ + .name = "PAL-DK", /* AV - tested OK */ \ + .id = V4L2_STD_PAL_DK, \ + .tv_mode = &XC3028L_tv_mode_d_k_pal_nicam, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 6, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x12 \ + }, { \ + .name = "PAL-I", /* AV - tested OK */ \ + .id = V4L2_STD_PAL_I, \ + .tv_mode = &XC3028L_tv_mode_i_pal_nicam, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 6, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x12 \ + }, { \ + .name = "NTSC-M", /* AV - tested OK */ \ + .id = V4L2_STD_NTSC_M, \ + .tv_mode = &XC3028L_tv_mode_m_n_ntsc_pal_a2_if, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 8, \ + .vbi_start_1 = 318, \ + .vbi_count_0 = 12, \ + .vbi_count_1 = 12, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x06, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x0c \ + }, { \ + .name = "SECAM L", /* AV - not tested */ \ + .id = V4L2_STD_SECAM_L, \ + .tv_mode = &XC3028L_tv_mode_d_k_secam_a2_dk1, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 6, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x12 \ + }, { \ + .name = "SECAM LC", /* AV - not tested */ \ + .id = V4L2_STD_SECAM_LC, \ + .tv_mode = &XC3028L_tv_mode_d_k_secam_a2_dk1, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 6, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x12 \ + }, { \ + .name = "SECAM K1", /* AV - not tested */ \ + .id = V4L2_STD_SECAM_K1, \ + .tv_mode = &XC3028L_tv_mode_d_k_secam_a2_dk1, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 6, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x12 \ + }, { \ + .name = "PAL-M", /* AV - not tested */ \ + .id = V4L2_STD_PAL_M, \ + .tv_mode = &XC3028L_tv_mode_d_k_secam_a2_dk1, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 6, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 18, \ + .vbi_count_1 = 18, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x12 \ + } \ +} + +#define EETI_XC3028L_DEFAULT_DVBT { \ + { \ + /* DVB-T */ \ + .bandwidth = EM28XX_BANDWIDTH_8_MHZ, \ + .tv_mode = &XC3028L_tv_mode_dtv78_zarlink_4_56mhz, \ + .channelmap = &XC3028_channel_map_ccir_digital_air, \ + }, { \ + .bandwidth = EM28XX_BANDWIDTH_7_MHZ, \ + .tv_mode = &XC3028L_tv_mode_dtv78_zarlink_4_56mhz, \ + .channelmap = &XC3028_channel_map_ccir_digital_air, \ + }, { \ + .bandwidth = EM28XX_BANDWIDTH_6_MHZ, \ + .tv_mode = &XC3028L_tv_mode_dtv78_zarlink_4_56mhz, \ + .channelmap = &XC3028_channel_map_ccir_digital_air, \ + } \ +} + +#define EETI_XC3028L_DEFAULT_QAM { \ + { \ + .bandwidth = EM28XX_BANDWIDTH_6_MHZ, \ + .tv_mode = &XC3028L_tv_mode_dtv6_zarlink_qam_4_56mhz, \ + .channelmap = &XC3028_channel_map_ccir_digital_air, \ + } \ +} + +#define EETI_XC3028L_DEFAULT_FM { \ + { \ + .tv_mode = &XC3028L_tv_mode_fm_radio_input1, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + } \ +} + +#define EETI_XC3028L_DEFAULT_ATSC { \ + { \ + /* ATSC */ \ + .bandwidth = EM28XX_BANDWIDTH_6_MHZ, \ + .tv_mode = &XC3028L_tv_mode_dtv6_atsc_lg_6_0mhz, \ + .channelmap = &XC3028_channel_map_ccir_digital_air \ + } \ +} + +#define EETI_XC3028_DEFAULT_QAM { \ + { \ + .bandwidth = EM28XX_BANDWIDTH_6_MHZ, \ + .tv_mode = &XC3028_tv_mode_dtv6_zarlink_qam_4_56mhz, \ + .channelmap = &XC3028_channel_map_ccir_digital_air, \ + } \ +} + +#define EETI_XC3028_DEFAULT_DVBT { \ + { \ + /* DVB-T */ \ + .bandwidth = EM28XX_BANDWIDTH_8_MHZ, \ + .tv_mode = &XC3028_tv_mode_dtv78_zarlink_4_56mhz, \ + .channelmap = &XC3028_channel_map_ccir_digital_air, \ + }, { \ + .bandwidth = EM28XX_BANDWIDTH_7_MHZ, \ + .tv_mode = &XC3028_tv_mode_dtv78_zarlink_4_56mhz, \ + .channelmap = &XC3028_channel_map_ccir_digital_air, \ + }, { \ + .bandwidth = EM28XX_BANDWIDTH_6_MHZ, \ + .tv_mode = &XC3028_tv_mode_dtv78_zarlink_4_56mhz, \ + .channelmap = &XC3028_channel_map_ccir_digital_air, \ + } \ +} + + +#define EETI_XC3028_DEFAULT_ATSC { \ + { \ + /* ATSC */ \ + .bandwidth = EM28XX_BANDWIDTH_6_MHZ, \ + .tv_mode = &XC3028_tv_mode_dtv6_atsc_lg_6_0mhz, \ + .channelmap = &XC3028_channel_map_ccir_digital_air \ + } \ +} + +#define EETI_XC3028_DEFAULT_QAM64 { \ + { /* QAM64 */ \ + .bandwidth = EM28XX_BANDWIDTH_6_MHZ, \ + .tv_mode = &XC3028_tv_mode_dtv6_qam_6_0mhz, \ + .channelmap = &XC3028_channel_map_ccir_digital_air \ + } \ +} + +#define EETI_XC3028_DEFAULT_FM { \ + { \ + .tv_mode = &XC3028_tv_mode_fm_radio_input1, \ + .channelmap = &XC3028_channel_map_ccir_analog_air, \ + } \ +} + +#define EETI_XC5000_DEFAULT_ANALOG { \ + { \ + .name = "PAL-BG", \ + .id = V4L2_STD_PAL_BG, \ + .index = 4, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 6, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 17, \ + .vbi_count_1 = 17, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x11 \ + }, { \ + .name = "PAL-DK", \ + .id = V4L2_STD_PAL_DK, \ + .index = 9, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 6, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 17, \ + .vbi_count_1 = 17, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x11 \ + }, { \ + .name = "PAL-I", \ + .id = V4L2_STD_PAL_I, \ + .index = 7, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 6, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 17, \ + .vbi_count_1 = 17, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x11 \ + }, { \ + .name = "NTSC M", \ + .id = V4L2_STD_NTSC_M, \ + .index = 0, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 6, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 12, \ + .vbi_count_1 = 12, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x06, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x0c \ + }, { \ + .name = "SECAM L", \ + .id = V4L2_STD_SECAM_L, \ + .index = 15, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 6, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 17, \ + .vbi_count_1 = 17, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x11 \ + }, { \ + .name = "SECAM LC", \ + .id = V4L2_STD_SECAM_LC, \ + .index = 16, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 6, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 17, \ + .vbi_count_1 = 17, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x11 \ + }, { \ + .name = "SECAM K1", \ + .id = V4L2_STD_SECAM_K1, \ + .index = 15, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 6, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 17, \ + .vbi_count_1 = 17, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x11 \ + }, { \ + .name = "PAL-M", /* todo, this one needs testing */ \ + .id = V4L2_STD_PAL_M, \ + .index = 1, \ + .vbi_sample_rate = 27000000, \ + .vbi_samples_per_line = 1440, \ + .vbi_start_0 = 6, \ + .vbi_start_1 = 317, \ + .vbi_count_0 = 17, \ + .vbi_count_1 = 17, \ + .vbi_h_start = 0x00, \ + .vbi_v_start = 0x46, \ + .vbi_w = 0xb4, \ + .vbi_h = 0x11 \ + } \ +} + +#define EETI_XC5000_DEFAULT_FM { \ + { \ + .name = "FM Radio", \ + .index = 22, \ + } \ +} + +#define EETI_XC5000_DEFAULT_DVBT { \ + { \ + .bandwidth = EM28XX_BANDWIDTH_8_MHZ, \ + .index = 18 \ + }, { \ + .bandwidth = EM28XX_BANDWIDTH_7_MHZ, \ + .index = 20 \ + }, { \ + .bandwidth = EM28XX_BANDWIDTH_6_MHZ, \ + .index = 17 \ + } \ +} + +#define EETI_XC5000_DEFAULT_ATSC { \ + { \ + .bandwidth = EM28XX_BANDWIDTH_8_MHZ, \ + .index = 0 /* TODO */ \ + } \ +} + +#define EETI_XC5000_DEFAULT_QAM { \ + { \ + .bandwidth = EM28XX_BANDWIDTH_8_MHZ, \ + .index = 0 /* TODO */ \ + } \ +} + +#define EETI_DEFAULT_GPIO { \ + .ts1_on = _BIT_VAL(EM28XX_GPIO0, 0, 0), \ + .a_on = _BIT_VAL(EM28XX_GPIO1, 0, 0), \ + .xc3028_sec = _BIT_VAL(EM28XX_GPIO2, 1, 0), \ + /* reserved */ \ + .t1_reset = _BIT_VAL(EM28XX_GPIO4, 0, 1), \ + /* reserved */ \ + .t1_on = _BIT_VAL(EM28XX_GPIO6, 0, 0), \ + .t2_on = _BIT_VAL(EM28XX_GPIO7, 1, 0), \ + \ + .l1_on = _BIT_VAL(EM28XX_GOP2, 1, 0), \ + .d1_reset = _BIT_VAL(EM28XX_GOP3, 0, 1), \ +} + +#define EETI_DRX3975D_GPIO { \ + .ts1_on = _BIT_VAL(EM28XX_GPIO0, 0, 0), \ + .a_on = _BIT_VAL(EM28XX_GPIO1, 0, 0), \ + .xc3028_sec = _BIT_VAL(EM28XX_GPIO2, 1, 0), \ + /* reserved */ \ + .t1_reset = _BIT_VAL(EM28XX_GPIO4, 0, 1), \ + /* reserved */ \ + .t1_on = _BIT_VAL(EM28XX_GPIO6, 0, 0), \ + .t2_on = _BIT_VAL(EM28XX_GPIO7, 1, 0), \ + \ + .l1_on = _BIT_VAL(EM28XX_GOP2, 1, 0), \ +} + +struct em28xx_board em28xx_boards[] = { + /* FIXME: please verify the supported video standards */ + [EM2800_BOARD_GENERIC] = { + .name = "Generic EM2800 video grabber", + }, + [EM2820_BOARD_GENERIC] = { + .name = "Generic EM2820 video grabber", + }, + [EM2821_BOARD_GENERIC] = { + .name = "Generic EM2821 video grabber", + }, + [EM2750_BOARD_GENERIC] = { + .name = "Generic EM2750 video grabber", + }, + [EM2860_BOARD_GENERIC] = { + .name = "Generic EM2860 video grabber", + }, + [EM2861_BOARD_GENERIC] = { + .name = "Generic EM2861 video grabber", + }, + [EM2870_BOARD_GENERIC] = { + .name = "Generic EM2870 video grabber", + }, + [EM2881_BOARD_GENERIC] = { + .name = "Generic EM2881 video grabber", + }, + [EM2883_BOARD_GENERIC] = { + .name = "Generic EM2883 video grabber", + }, + [EM2820_BOARD_KWORLD_PVRTV2800RF] = { + .name = "KWorld PVR TV 2800 RF", + .vchannels = 2, + .norm = V4L2_STD_PAL_BG, + .tda9887_conf = TDA9887_PRESENT, + .has_tuner = 1, + .decoder = EM28XX_SAA7113, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + } }, + .tvnorms = {{ + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + }, { + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + }, { + .name = "PAL-M", + + .id = V4L2_STD_PAL_M, + } }, + }, + [EM2820_BOARD_TERRATEC_CINERGY_250] = { + .name = "Terratec Cinergy 250 USB", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .tda9887_conf = TDA9887_PRESENT, + .has_tuner = 1, + .decoder = EM28XX_SAA7113, + .dev_modes = EM28XX_VIDEO, + .ir_i2c = 1, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = SAA7115_COMPOSITE2, + .amux = 1, + .amix = EM28XX_MIX_VIDEO, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + .amix = EM28XX_MIX_LINE_IN, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + .amix = EM28XX_MIX_LINE_IN, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + }, { + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + }, { + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + } }, + }, + [EM2820_BOARD_DLINK_USB_TV] = { + .name = "D-Link DUB-T210 TV Tuner", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .tda9887_conf = TDA9887_PRESENT, + .has_tuner = 1, + .decoder = EM28XX_SAA7113, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = SAA7115_COMPOSITE2, + .amux = 1, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + }, { + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + }, { + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + } }, + }, + [EM2821_BOARD_PROLINK_PLAYTV_USB2] = { + .name = "SIIG AVTuner-PVR/Prolink PlayTV USB 2.0", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, /* unknown? */ + .tda9887_conf = TDA9887_PRESENT, /* unknown? */ + .has_tuner = 1, + .decoder = EM28XX_SAA7113, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = SAA7115_COMPOSITE2, + .amux = 1, + .amix = EM28XX_MIX_NOTOUCH, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 2, + .amix = EM28XX_MIX_LINE_IN, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + .amix = EM28XX_MIX_LINE_IN, + }}, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + }, { + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + }, { + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + } }, + }, + [EM2820_BOARD_HERCULES_SMART_TV_USB2] = { + .name = "Hercules Smart TV USB 2.0", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .tda9887_conf = TDA9887_PRESENT, + .has_tuner = 1, + .decoder = EM28XX_SAA7113, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = SAA7115_COMPOSITE2, + .amux = 1, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + }, { + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + }, { + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + } }, + }, + [EM2820_BOARD_PINNACLE_USB_2] = { + .name = "Pinnacle PCTV USB 2", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .tda9887_conf = TDA9887_PRESENT, + .has_tuner = 1, + .decoder = EM28XX_SAA7113, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = SAA7115_COMPOSITE2, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + }, { + .name = "PAL-I", + + .id = V4L2_STD_PAL_I, + }, { + .name = "PAL-M", + + .id = V4L2_STD_PAL_M, + }, { + .name = "NTSC", + + .id = V4L2_STD_NTSC, + } }, + }, + [EM2820_BOARD_PINNACLE_USB_2_FM1216ME] = { + .name = "Pinnacle PCTV USB 2 (Philips FM1216ME)", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .tda9887_conf = TDA9887_PRESENT, + .has_tuner = 1, + .decoder = EM28XX_SAA7113, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = SAA7115_COMPOSITE2, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + }, { + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + }, { + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + }, { + .name = "NTSC", + .id = V4L2_STD_NTSC, + }, { + .name = "SECAM-DK", + .id = V4L2_STD_SECAM_DK, + } }, + }, + [EM2820_BOARD_HAUPPAUGE_WINTV_USB_2_R2] = { + .name = "Hauppauge WinTV USB 2 (R2)", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .tuner_type = TUNER_ABSENT, + .tda9887_conf = TDA9887_PRESENT|TDA9887_PORT1_ACTIVE|TDA9887_PORT2_ACTIVE, + .has_tuner = 1, + .decoder = EM28XX_TVP5150, + .has_msp34xx = 1, + .dev_modes = EM28XX_VIDEO, + /*FIXME: S-Video not tested */ + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 6, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .tvnorms = {{ + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "NTSC", + .id = V4L2_STD_NTSC, + } }, + }, + [EM2820_BOARD_HAUPPAUGE_WINTV_USB_2] = { + .name = "Hauppauge WinTV USB 2", + .vchannels = 3, + .norm = V4L2_STD_NTSC, + .tuner_type = TUNER_ABSENT, + .tda9887_conf = TDA9887_PRESENT|TDA9887_PORT1_ACTIVE|TDA9887_PORT2_ACTIVE, + .has_tuner = 1, + .decoder = EM28XX_TVP5150, + .has_msp34xx = 1, + .dev_modes = EM28XX_VIDEO, + /*FIXME: S-Video not tested */ + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 6, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + } }, + .tvnorms = {{ + .name = "NTSC", + + .id = V4L2_STD_NTSC, + } }, + }, + [EM2870_BOARD_HAUPPAUGE_WINTV_USB_2_R2] = { + .name = "Hauppauge WinTV USB 2 R2", + .vchannels = 3, + .norm = V4L2_STD_NTSC_M, + .tuner_type = TUNER_XCEIVE_XC3028, + .tuner_addr = 0xc2, + .has_tuner = 1, + .has_inttuner = 1, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 6, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + }, + [EM2821_BOARD_SUPERCOMP_USB_2] = { + .name = "Supercomp USB 2.0 TV", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .has_tuner = 1, + .tuner_type = TUNER_PHILIPS_FM1236_MK3, + .tda9887_conf = TDA9887_PRESENT|TDA9887_PORT1_ACTIVE|TDA9887_PORT2_ACTIVE, + .decoder = EM28XX_SAA7113, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = SAA7115_COMPOSITE2, + .amux = 1, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + }, { + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + }, { + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + } }, + }, + [EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900] = { + .name = "Hauppauge WinTV HVR 900", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .tuner_type = TUNER_XCEIVE_XC3028, + .tuner_addr = 0xc2, + .has_tuner = 1, + .has_inttuner = 1, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT + }, + [EM2883_BOARD_PINNACLE_PCTV_HD_PRO] = { + .name = "Pinnacle PCTV HD Pro", + .vchannels = 3, + .norm = V4L2_STD_NTSC_M, + .tuner_type = TUNER_XCEIVE_XC3028, + .has_tuner = 1, + .has_inttuner = 1, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_ATSC, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .atscnorms = EETI_XC3028_DEFAULT_ATSC, + .qamnorms = EETI_XC3028_DEFAULT_QAM, + }, + [EM2883_BOARD_KWORLD_HYBRID_F306] = { + .name = "KWorld PlusTV F306", + .em_type = EM2883, + .vchannels = 3, + .norm = V4L2_STD_NTSC_M, + .tuner_type = TUNER_XCEIVE_XC3028, + .has_inttuner = 1, + .has_tuner = 1, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + }, + [EM2888_BOARD_KWORLD_HYBRID_E329] = { + .name = "KWorld 329u", + .em_type = EM2888, + .vchannels = 3, + .norm = V4L2_STD_NTSC_M, + .tuner_type = TUNER_XCEIVE_XC3028, + .has_inttuner = 1, + .has_tuner = 1, + .decoder = EM28XX_CX25843, + .ir_keytab = ir_codes_em_kworld, + .ir_getkey = em2888_get_key_empia, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT | EM28XX_AUDIO | EM28XX_RADIO, + .powersaving = 1, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = CX25843_TELEVISION, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = CX25843_COMPOSITE1, + .amux = 4, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = CX25843_SVIDEO, + .amux = 5, + } }, + .tvnorms = EETI_XC3028L_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028L_DEFAULT_DVBT, + .fmnorms = EETI_XC3028L_DEFAULT_FM, + }, + [EM2888_BOARD_LINCOLN_TV_FM] = { + .name = "Lincoln TV FM", + .em_type = EM2888, + .vchannels = 3, + .norm = V4L2_STD_NTSC_M, + .tuner_type = TUNER_XCEIVE_XC3028, + .has_inttuner = 1, + .has_tuner = 1, + .decoder = EM28XX_CX25843, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_AUDIO | EM28XX_RADIO, + .powersaving = 1, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = CX25843_TELEVISION, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = CX25843_COMPOSITE1, + .amux = 4, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = CX25843_SVIDEO, + .amux = 5, + } }, + .tvnorms = EETI_XC3028L_DEFAULT_ANALOG, + .fmnorms = EETI_XC3028L_DEFAULT_FM, + }, + [EM2883_BOARD_KWORLD_HYBRID_E323] = { + .name = "KWorld E323", + .vchannels = 3, + .norm = V4L2_STD_NTSC_M, + .tuner_type = TUNER_XCEIVE_XC3028, + .has_inttuner = 1, + .has_tuner = 1, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_DVBT, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + .fmnorms = EETI_XC3028_DEFAULT_FM, + }, + [EM2883_BOARD_KWORLD_HYBRID_A316] = { + .name = "KWorld PlusTV HD Hybrid 330", + .vchannels = 3, + .norm = V4L2_STD_NTSC_M, + .tuner_type = TUNER_XCEIVE_XC3028, + .has_inttuner = 1, + .has_tuner = 1, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_DVBT, + + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + }, + [EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950] = { + .name = "Hauppauge WinTV HVR 950", + .vchannels = 3, + .norm = V4L2_STD_NTSC_M, + .tuner_type = TUNER_XCEIVE_XC3028, + .has_tuner = 1, + .has_inttuner = 1, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_ATSC, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .atscnorms = EETI_XC3028_DEFAULT_ATSC, + .qamnorms = EETI_XC3028_DEFAULT_QAM, + }, + [EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2] = { + .name = "Hauppauge WinTV HVR (B2C0)", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .has_inttuner = 1, + .has_tuner = 1, + .tuner_type = TUNER_XCEIVE_XC3028, + .tuner_addr = 0xc2, + .decoder = EM28XX_TVP5150, + .ir_keytab = ir_codes_hauppauge_new_u, + .ir_getkey = em2880_get_key_empia, + .dev_modes = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_DVBT, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DRX3975D_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + }, + [EM2860_BOARD_TERRATEC_HYBRID_XS] = { + .name = "Terratec Cinergy A Hybrid XS", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .has_tuner = 1, + .has_inttuner = 1, + .tuner_type = TUNER_XCEIVE_XC3028, + .decoder = EM28XX_TVP5150, + .ir_keytab = ir_codes_em_terratec2, + .ir_getkey = em2880_get_key_terratec, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + }, + [EM2880_BOARD_TERRATEC_HYBRID_XS] = { + .name = "Terratec Hybrid XS", + .vchannels = 3, + .has_inttuner = 1, + .has_tuner = 1, + .norm = V4L2_STD_PAL_BG, + .tuner_type = TUNER_XCEIVE_XC3028, + .decoder = EM28XX_TVP5150, + .ir_keytab = ir_codes_em_terratec2, + .ir_getkey = em2880_get_key_terratec, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT, + .powersaving = 1, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + }, + [EM2861_BOARD_KWORLD_PVRTV_300U] = { + .name = "KWorld PVRTV 300U", + .vchannels = 3, + .norm = V4L2_STD_NTSC_M, + .has_tuner = 1, + .has_inttuner = 1, + .tuner_type = TUNER_XCEIVE_XC3028, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_AUDIO2, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + }, + [EM2880_BOARD_KWORLD_DVB_310U] = { + .name = "KWorld DVB-T 310U", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .has_tuner = 1, + .has_inttuner = 1, + .tuner_type = TUNER_XCEIVE_XC3028, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + }, + [EM2880_BOARD_KWORLD_DVB_305U] = { + .name = "KWorld DVB-T 305U", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .has_tuner = 1, + .has_inttuner = 1, + .tuner_type = TUNER_XCEIVE_XC3028, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + }, + [EM2880_BOARD_TERRATEC_HYBRID_XS_FR] = { + .name = "Terratec Hybrid XS Secam", + .vchannels = 3, + .norm = V4L2_STD_SECAM_L, + .has_tuner = 1, + .has_inttuner = 1, + .has_msp34xx = 1, + .tuner_type = TUNER_XCEIVE_XC3028, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + }, + [EM2882_BOARD_TERRATEC_HYBRID_XS] = { + .name = "Empia Hybrid ATSC (em2882)", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .has_tuner = 1, + .has_inttuner = 1, + .tuner_type = TUNER_XCEIVE_XC3028, + .decoder = EM28XX_TVP5150, + .ir_keytab = ir_codes_em_terratec2, + .ir_getkey = em2880_get_key_terratec, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT | EM28XX_AUDIO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + }, + [EM2883_BOARD_TERRATEC_HYBRID_XS_FM] = { + .name = "Terratec Hybrid XS FM (em2883)", + .em_type = EM2883, + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .has_radio = 1, + .has_inttuner = 1, +#if 0 + .powersaving = 1, +#endif + .tuner_type = TUNER_XCEIVE_XC5000, + .decoder = EM28XX_CX25843, + .ir_keytab = ir_codes_em_terratec2, + .ir_getkey = em2880_get_key_terratec, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT | EM28XX_AUDIO | EM28XX_RADIO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = CX25843_TELEVISION, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = CX25843_COMPOSITE1, + .amux = 4, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = CX25843_SVIDEO, + .amux = 5, + } }, + .tvnorms = EETI_XC5000_DEFAULT_ANALOG, + .dvbnorms = EETI_XC5000_DEFAULT_DVBT, + .fmnorms = EETI_XC5000_DEFAULT_FM, + }, + [EM2881_BOARD_DNT_DA2_HYBRID] = { + .name = "DNT DA2 Hybrid", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .has_tuner = 1, + .has_inttuner = 1, + .tuner_type = TUNER_XCEIVE_XC3028, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + }, + [EM2870_BOARD_TERRATEC_XS] = { + .name = "Terratec Cinergy T XS", + .has_tuner = 1, + .has_inttuner = 1, + .tuner_type = TUNER_XCEIVE_XC3028, + .dev_modes = EM28XX_DVBT, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + }, + [EM2870_BOARD_TERRATEC_XS_MT2060] = { + .name = "Terratec Cinergy T XS (MT2060)", + .tuner_type = TUNER_MT2060, + .dev_modes = EM28XX_DVBT, +#if 0 + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, +#endif + }, + [EM2870_BOARD_PINNACLE_PCTV_DVB] = { + .name = "Pinnacle PCTV DVB-T", + .tuner_type = TUNER_MT2060, + .dev_modes = EM28XX_DVBT, + .ir_keytab = ir_codes_pinnacle2, + .ir_getkey = em2880_get_key_empia, +#if 0 + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, +#endif + }, + [EM2870_BOARD_KWORLD_350U] = { + .name = "KWorld 350 U DVB-T", + .has_tuner = 1, + .has_inttuner = 1, + .dev_modes = EM28XX_DVBT, + .tuner_type = TUNER_XCEIVE_XC3028, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + }, + [EM2870_BOARD_KWORLD_355U] = { + .name = "KWorld 355 U DVB-T", + .tuner_type = TUNER_QT1010, + .dev_modes = EM28XX_DVBT, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + }, + [EM2870_BOARD_COMPRO_VIDEOMATE] = { + .name = "Compro, VideoMate U3", + .tuner_type = TUNER_MT2060, + .dev_modes = EM28XX_DVBT, + }, + [EM2881_BOARD_PINNACLE_HYBRID_PRO] = { + .name = "Pinnacle Hybrid Pro", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .has_tuner = 1, + .has_inttuner = 1, + .tuner_type = TUNER_XCEIVE_XC3028, + .decoder = EM28XX_TVP5150, + .ir_keytab = ir_codes_pinnacle2, + .ir_getkey = em2880_get_key_empia, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + }, + [EM2882_BOARD_PINNACLE_HYBRID_PRO] = { + .name = "Pinnacle Hybrid Pro (em2882)", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .tuner_type = TUNER_XCEIVE_XC3028, + .tuner_addr = 0xc2, + .has_tuner = 1, + .has_inttuner = 1, + .decoder = EM28XX_TVP5150, + .ir_keytab = ir_codes_em_pinnacle2_usb, + .ir_getkey = em2880_get_key_empia, + .dev_modes = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_DVBT, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DRX3975D_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + }, + /* maybe there's a reason behind it why Terratec sells the Hybrid XS as Prodigy XS with a + * different PID, let's keep it separated for now maybe we'll need it lateron */ + [EM2880_BOARD_TERRATEC_PRODIGY_XS] = { + .name = "Terratec Prodigy XS", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .has_tuner = 1, + .has_inttuner = 1, + .tuner_type = TUNER_XCEIVE_XC3028, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + }, + [EM2880_BOARD_MSI_DIGIVOX_AD] = { + .name = "MSI DigiVox A/D", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .has_tuner = 1, + .has_inttuner = 1, + .tuner_type = TUNER_XCEIVE_XC3028, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + }, + [EM2880_BOARD_MSI_DIGIVOX_AD_II] = { + .name = "MSI DigiVox A/D II", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .has_tuner = 1, + .has_inttuner = 1, + .tuner_type = TUNER_XCEIVE_XC3028, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + }, + [EM2883_BOARD_ATI_TVWONDER600] = { + .name = "ATI TV Wonder HD 600", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .has_tuner = 1, + .has_inttuner = 1, + .tuner_type = TUNER_XCEIVE_XC3028, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_ATSC | EM28XX_AUDIO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028L_DEFAULT_ANALOG, + .atscnorms = EETI_XC3028L_DEFAULT_ATSC, + .qamnorms = EETI_XC3028L_DEFAULT_QAM, + }, + [EM2820_BOARD_MSI_VOX_USB_2] = { + .name = "MSI VOX USB 2.0", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .tda9887_conf = TDA9887_PRESENT|TDA9887_PORT1_ACTIVE|TDA9887_PORT2_ACTIVE, + .has_tuner = 1, + .decoder = EM28XX_SAA7114, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = SAA7115_COMPOSITE4, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + }, { + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + }, { + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + }, { + .name = "NTSC", + .id = V4L2_STD_NTSC_M, + } }, + }, + [EM2800_BOARD_TERRATEC_CINERGY_200] = { + .name = "Terratec Cinergy 200 USB", + .em_type = EM2800, + .i2c_devs = em28xx_terratec_cinergy_200_usb_i2c_devs, + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .tda9887_conf = TDA9887_PRESENT, + .has_tuner = 1, + .decoder = EM28XX_SAA7113, + .dev_modes = EM28XX_VIDEO, + .ir_i2c = 1, + + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = SAA7115_COMPOSITE2, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + } }, + .tvnorms = { + { + .name = "PAL-BG", + + .id = V4L2_STD_PAL_BG, + }, { + .name = "PAL-DK", + + .id = V4L2_STD_PAL_DK, + }, { + .name = "PAL-I", + + .id = V4L2_STD_PAL_I, + }, { + .name = "PAL-M", + + .id = V4L2_STD_PAL_M, + } }, + }, + [EM2820_BOARD_LEADTEK_WINFAST_USBII_DELUXE] = { + .name = "Leadtek Winfast USB II Deluxe", + .em_type = EM2820, + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .tda9887_conf = TDA9887_PRESENT, + .has_tuner = 1, + .decoder = EM28XX_SAA7114, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = 2, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = 0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = 9, + .amux = 1, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + }, { + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + }, { + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + } }, + }, + [EM2800_BOARD_LEADTEK_WINFAST_USBII] = { + .name = "Leadtek Winfast USB II", + .em_type = EM2800, + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .tda9887_conf = TDA9887_PRESENT, + .has_tuner = 1, + .decoder = EM28XX_SAA7113, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = SAA7115_COMPOSITE2, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + }, { + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + }, { + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + } }, + }, + [EM2800_BOARD_KWORLD_USB2800] = { + .name = "KWorld USB2800", + .em_type = EM2800, + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, +#if defined TUNER_PHILIPS_FCV1236D + .tuner_type = TUNER_PHILIPS_FCV1236D, +#else + .tuner_type = TUNER_PHILIPS_ATSC, +#endif + .tda9887_conf = TDA9887_PRESENT, + .has_tuner = 1, + .decoder = EM28XX_SAA7113, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = SAA7115_COMPOSITE2, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + } + }, + }, + [EM2820_BOARD_PINNACLE_DVC_90] = { + .name = "Pinnacle Dazzle DVC 90", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .has_tuner = 0, + .decoder = EM28XX_SAA7113, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + } }, + }, + [EM2820_BOARD_PINNACLE_DVC_100] = { + .name = "Pinnacle Dazzle DVC 100", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .has_tuner = 0, + .decoder = EM28XX_SAA7113, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "SECAM L", + .id = V4L2_STD_SECAM_L, + }, { + .name = "SECAM LC", + .id = V4L2_STD_SECAM_LC, + }, { + .name = "SECAM K1", + .id = V4L2_STD_SECAM_K1, + } }, + }, + [EM2820_BOARD_VIDEOLOGY_20K14XUSB] = { + .name = "Videology 20K14XUSB USB2.0", + .vchannels = 1, + .norm = V4L2_STD_PAL_BG, + .has_tuner = 0, + .ctrl = em28xx_vy_cctrl, + .gctrl = em28xx_vy_gctrl, + .qctrl = em28xx_vy_qctrl, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = 0, + .amux = 0, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + } }, + }, + [EM2821_BOARD_USBGEAR_VD204] = { + .name = "Usbgear VD204v9", + .vchannels = 2, + .norm = V4L2_STD_PAL_BG, + .decoder = EM28XX_SAA7113, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + } }, + .tvnorms = {{ + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + }, { + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + }, { + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + } }, + }, + [EM2860_BOARD_TYPHOON_DVD_MAKER] = { + .name = "Typhoon DVD Maker", + .vchannels = 2, + .norm = V4L2_STD_PAL_BG, + .decoder = EM28XX_SAA7113, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + } }, + .tvnorms = {{ + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + }, { + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + }, { + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + } }, + }, + [EM2860_BOARD_GADMEI_UTV330] = { + .name = "Gadmei UTV330", + .vchannels = 3, + .norm = V4L2_STD_NTSC_M, + .tuner_type = TUNER_TNF_5335MF, + .tda9887_conf = TDA9887_PRESENT, + .has_tuner = 1, + .decoder = EM28XX_SAA7113, + .ir_keytab = ir_codes_em_gadmei_usb, + .ir_getkey = em2880_get_key_empia, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = SAA7115_COMPOSITE2, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + } }, + .tvnorms = {{ + .name = "NTSC", + .id = V4L2_STD_NTSC_M, + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + } }, + }, + [EM2820_BOARD_GADMEI_UTV310] = { + .name = "Gadmei UTV310", + .vchannels = 3, + .norm = V4L2_STD_NTSC_M, + .tuner_type = TUNER_TNF_5335MF, + .tda9887_conf = TDA9887_PRESENT, + .has_tuner = 1, + .decoder = EM28XX_SAA7113, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = SAA7115_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + } }, + .tvnorms = {{ + .name = "NTSC", + .id = V4L2_STD_NTSC_M, + } }, + }, + [EM2800_BOARD_VGEAR_POCKETTV] = { + .name = "V-Gear PocketTV", + .em_type = EM2800, + .i2c_devs = em28xx_vgear_pockettv_i2c_devs, + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .tda9887_conf = TDA9887_PRESENT, + .has_tuner = 1, + .decoder = EM28XX_SAA7113, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = SAA7115_COMPOSITE2, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = SAA7115_SVIDEO3, + .amux = 1, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + }, { + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + }, { + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + } }, + }, + [EM2861_BOARD_YAKUMO_MOVIE_MIXER] = { + .name = "Yakumo MovieMixer", + .vchannels = 1, + .norm = V4L2_STD_PAL_BG, + .decoder = EM28XX_TVP5150, + .has_tuner = 0, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + } }, + }, + [EM2860_BOARD_NETGMBH_CAM] = { + .name = "NetGMBH Cam", /* Beijing Huaqi Information Digital Technology Co., Ltd */ + .vchannels = 1, + .norm = V4L2_STD_PAL_BG, + .has_tuner = 0, + .dev_modes = EM28XX_VIDEO, + + .input = {{ + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = 0, + .amux = 0, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + } }, + }, + [EM2750_BOARD_DLCW_130] = { + .name = "Huaqi DLCW-130", /* Beijing Huaqi Information Digital Technology Co., Ltd */ + .vchannels = 1, + .norm = V4L2_STD_PAL_BG, + .dev_modes = EM28XX_VIDEO, + .em_type = EM2751, + .input = {{ + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = 0, + .amux = 0, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + } }, + }, + [EM2751_BOARD_EMPIA_SAMPLE] = { + .name = "EM2751 Webcam + Audio", + .vchannels = 1, + .norm = V4L2_STD_PAL_BG, + .dev_modes = EM28XX_VIDEO, + .em_type = EM2751, + .input = {{ + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = 0, + .amux = 0, + } }, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + } }, + }, + [EM2861_BOARD_PLEXTOR_PX_TV100U] = { + .name = "Plextor ConvertX PX-TV100U", + .vchannels = 3, + .norm = V4L2_STD_NTSC_M, + .tuner_type = TUNER_TNF_5335MF, + .tda9887_conf = TDA9887_PRESENT, + .has_tuner = 1, + .has_msp34xx = 1, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO, /* EM28XX_AUDIO ? */ + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .tvnorms = {{ + .name = "NTSC", + + .id = V4L2_STD_NTSC_M, + } }, + }, + [EM2863_BOARD_EMPIA_GENERIC] = { + .name = "Empia generic", + .vchannels = 3, + .norm = V4L2_STD_NTSC_M, + .tuner_type = TUNER_XCEIVE_XC3028, + .has_tuner = 1, + .has_inttuner = 1, + .decoder = EM28XX_CX25843, + .dev_modes = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_RADIO | EM28XX_VBI, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = 1, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = 3, + .amux = 4, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = 2, + .amux = 5, + } }, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, +#if 0 + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, +#endif + + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, +#if 0 + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, +#endif + }, + [EM2883_BOARD_KWORLD_A340] = { + .name = "KWorld ATSC 340", + .tuner_type = TUNER_PHILIPS_TDA18271, + .dev_modes = EM28XX_ATSC, + }, + [EM2875_BOARD_SAMPLE_ISDBT] = { + .name = "Empia ISDB", + .em_type = EM2875, + .dev_modes = EM28XX_ISDB, + }, + [EM2879_BOARD_SAMPLE_DMB] = { + .name = "Empia DMB-T", + .tuner_type = TUNER_ADIMTV102, + .dev_modes = EM28XX_DMB, + }, + [EM2883_BOARD_EMPIA_HYBRID_ATSC] = { + .name = "Empia Hybrid XS ATSC", + .vchannels = 3, + .norm = V4L2_STD_NTSC_M, + .tuner_type = TUNER_XCEIVE_XC5000, + .has_tuner = 1, + .has_inttuner = 1, +#if 0 + .powersaving = 1, +#endif + .decoder = EM28XX_CX25843, + .dev_modes = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_RADIO | EM28XX_VBI/* | EM28XX_ATSC needs more work*/, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = CX25843_TELEVISION, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = CX25843_COMPOSITE1, + .amux = 4, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = CX25843_SVIDEO, + .amux = 5, + } }, + .tvnorms = EETI_XC5000_DEFAULT_ANALOG, + .fmnorms = EETI_XC5000_DEFAULT_FM, + .atscnorms = EETI_XC5000_DEFAULT_ATSC, + .qamnorms = EETI_XC5000_DEFAULT_QAM, + }, + [EM2883_BOARD_EQUINUX_TUBESTICK_ATSC] = { + .name = "Equinux TubeStick Hybrid ATSC", + .vchannels = 3, + .norm = V4L2_STD_NTSC_M, + .tuner_type = TUNER_XCEIVE_XC5000, + .has_tuner = 1, + .has_inttuner = 1, +#if 0 + .powersaving = 1, +#endif + .decoder = EM28XX_CX25843, + .dev_modes = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_RADIO | EM28XX_VBI/* | EM28XX_ATSC needs more work*/, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = CX25843_TELEVISION, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = CX25843_COMPOSITE1, + .amux = 4, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = CX25843_SVIDEO, + .amux = 5, + } }, + .tvnorms = EETI_XC5000_DEFAULT_ANALOG, + .fmnorms = EETI_XC5000_DEFAULT_FM, + .atscnorms = EETI_XC5000_DEFAULT_ATSC, + .qamnorms = EETI_XC5000_DEFAULT_QAM, + }, + [EM2888_BOARD_DVB_TC_HYBRID] = { + .name = "Pinnacle 510e", + .em_type = EM2888, + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .tuner_type = TUNER_XCEIVE_XC5000, + .has_inttuner = 1, + .has_tuner = 1, + .decoder = EM28XX_CX25843, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_AUDIO | EM28XX_RADIO, + .powersaving = 1, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = CX25843_TELEVISION, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = CX25843_COMPOSITE1, + .amux = 4, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = CX25843_SVIDEO, + .amux = 5, + } }, + .tvnorms = EETI_XC5000_DEFAULT_ANALOG, + .fmnorms = EETI_XC5000_DEFAULT_FM, + }, + [EM2888_BOARD_EMPIA_HYBRID] = { + .name = "Empia Hybrid PCTV", + .em_type = EM2888, /* TODO read this from the chip */ + .vchannels = 3, + .norm = V4L2_STD_NTSC_M, + .tuner_type = TUNER_XCEIVE_XC3028, + .has_inttuner = 1, + .has_tuner = 1, + .decoder = EM28XX_CX25843, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT | EM28XX_AUDIO | EM28XX_RADIO, + .powersaving = 1, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = CX25843_TELEVISION, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = CX25843_COMPOSITE1, + .amux = 4, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = CX25843_SVIDEO, + .amux = 5, + } }, + .tvnorms = EETI_XC3028L_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028L_DEFAULT_DVBT, + .fmnorms = EETI_XC3028L_DEFAULT_FM, + }, + [EM2860_BOARD_KAIOMY_TVNPC_U2] = { + /* TODO radio support */ + .name = "Kaiomy TVnPC U2", + .vchannels = 3, + .norm = V4L2_STD_NTSC_M, + .has_tuner = 1, + .has_inttuner = 1, + .tuner_type = TUNER_XCEIVE_XC3028, + .decoder = EM28XX_TVP5150, + .ir_keytab = ir_codes_em_kworld, + .ir_getkey = em2860_get_key_kaiomy, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_AUDIO2, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + }, + [EM2861_BOARD_POLLIN_USB_R1] = { + .name = "Pollin USB-R1", + .vchannels = 1, + .norm = V4L2_STD_PAL_BG, + .decoder = EM28XX_SAA7113, + .has_tuner = 0, + .dev_modes = EM28XX_VIDEO, + .input = {{ + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = SAA7115_COMPOSITE0, + .amux = 1, + }}, + .tvnorms = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + },{ + .name = "NTSC", + .id = V4L2_STD_NTSC, + }}, + }, + [EM2882_BOARD_LEADTEK_PALMTOP_DTV_200H] = { + /* TODO: FM radio support for this Leadtek device */ + .name = "Leadtek PalmTop DTV 200H", + .vchannels = 3, + .norm = V4L2_STD_PAL_BG, + .has_tuner = 1, + .has_inttuner = 1, + .tuner_type = TUNER_XCEIVE_XC3028, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT | EM28XX_AUDIO, + .input = {{ + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 0, + }, { + .type = EM28XX_VMUX_COMPOSITE1, + .vmux = TVP5150_COMPOSITE1, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + .manual_gpio = 1, + .gpio_regs = EETI_DEFAULT_GPIO, + .tvnorms = EETI_XC3028_DEFAULT_ANALOG, + .dvbnorms = EETI_XC3028_DEFAULT_DVBT, + }, + [EM2820_BOARD_COMPRO_VIDEO_MATE] = { + .name = "Compro VideoMate ForYou/Stereo", + .vchannels = 2, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .tda9887_conf = TDA9887_PRESENT, + .decoder = EM28XX_TVP5150, + .dev_modes = EM28XX_VIDEO, + .input = { { + .type = EM28XX_VMUX_TELEVISION, + .vmux = TVP5150_COMPOSITE0, + .amux = 1, + }, { + .type = EM28XX_VMUX_SVIDEO, + .vmux = TVP5150_SVIDEO, + .amux = 1, + } }, + }, +}; + +const unsigned int em28xx_bcount = ARRAY_SIZE(em28xx_boards); + +/* + * seems like it's possible to flash the eeprom, somehow one of my HVR 900 devices suddenly + * had the content of a WinTV USB 2 eeprom and thus identified itself as a Wintv USB 2 + * device which of course didn't work... + * + */ + +/* table of devices that work with this driver */ +struct usb_device_id em28xx_id_table [] = { + { USB_DEVICE(0xeb1a, 0x2800), .driver_info = EM2800_BOARD_GENERIC }, + { USB_DEVICE(0xeb1a, 0x2820), .driver_info = EM2820_BOARD_GENERIC }, + { USB_DEVICE(0xeb1a, 0x2821), .driver_info = EM2821_BOARD_GENERIC }, + { USB_DEVICE(0xeb1a, 0x2750), .driver_info = EM2750_BOARD_GENERIC }, + { USB_DEVICE(0xeb1a, 0x2860), .driver_info = EM2860_BOARD_GENERIC }, + { USB_DEVICE(0xeb1a, 0x2861), .driver_info = EM2861_BOARD_GENERIC }, + { USB_DEVICE(0xeb1a, 0x2881), .driver_info = EM2881_BOARD_GENERIC }, + { USB_DEVICE(0xeb1a, 0x2870), .driver_info = EM2870_BOARD_GENERIC }, + { USB_DEVICE(0xeb1a, 0xe310), .driver_info = EM2880_BOARD_MSI_DIGIVOX_AD }, + { USB_DEVICE(0xeb1a, 0xe320), .driver_info = EM2880_BOARD_MSI_DIGIVOX_AD_II }, + { USB_DEVICE(0xeb1a, 0xe300), .driver_info = EM2861_BOARD_KWORLD_PVRTV_300U }, + { USB_DEVICE(0xeb1a, 0xe350), .driver_info = EM2870_BOARD_KWORLD_350U }, + { USB_DEVICE(0xeb1a, 0xe355), .driver_info = EM2870_BOARD_KWORLD_355U }, + { USB_DEVICE(0xeb1a, 0xe357), .driver_info = EM2870_BOARD_KWORLD_355U }, + { USB_DEVICE(0x1ae7, 0x0380), .driver_info = EM2870_BOARD_KWORLD_355U }, + { USB_DEVICE(0x0ccd, 0x0036), .driver_info = EM2820_BOARD_TERRATEC_CINERGY_250 }, + { USB_DEVICE(0x2304, 0x0208), .driver_info = EM2820_BOARD_PINNACLE_USB_2 }, + { USB_DEVICE(0x2040, 0x4200), .driver_info = EM2820_BOARD_HAUPPAUGE_WINTV_USB_2 }, + { USB_DEVICE(0x2040, 0x4201), .driver_info = EM2820_BOARD_HAUPPAUGE_WINTV_USB_2_R2 }, + { USB_DEVICE(0x2040, 0x650a), .driver_info = EM2870_BOARD_HAUPPAUGE_WINTV_USB_2_R2 }, + { USB_DEVICE(0x2304, 0x0207), .driver_info = EM2820_BOARD_PINNACLE_DVC_90 }, + { USB_DEVICE(0x2304, 0x021a), .driver_info = EM2820_BOARD_PINNACLE_DVC_100 }, + { USB_DEVICE(0x2040, 0x6500), .driver_info = EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900 }, + { USB_DEVICE(0x2040, 0x6502), .driver_info = EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2 }, + { USB_DEVICE(0x0ccd, 0x0042), .driver_info = EM2880_BOARD_TERRATEC_HYBRID_XS }, + { USB_DEVICE(0x0ccd, 0x004f), .driver_info = EM2860_BOARD_TERRATEC_HYBRID_XS }, + { USB_DEVICE(0x0ccd, 0x004c), .driver_info = EM2880_BOARD_TERRATEC_HYBRID_XS_FR }, + { USB_DEVICE(0x0ccd, 0x005e), .driver_info = EM2882_BOARD_TERRATEC_HYBRID_XS }, + { USB_DEVICE(0x0ccd, 0x0043), .driver_info = EM2870_BOARD_TERRATEC_XS }, + { USB_DEVICE(0x0ccd, 0x0047), .driver_info = EM2880_BOARD_TERRATEC_PRODIGY_XS }, + { USB_DEVICE(0x185b, 0x2870), .driver_info = EM2870_BOARD_COMPRO_VIDEOMATE }, + { USB_DEVICE(0x0413, 0x6023), .driver_info = EM2800_BOARD_LEADTEK_WINFAST_USBII }, + { USB_DEVICE(0x2001, 0xf112), .driver_info = EM2820_BOARD_DLINK_USB_TV }, + { USB_DEVICE(0xeb1a, 0x2883), .driver_info = EM2883_BOARD_GENERIC }, + { USB_DEVICE(0x2040, 0x6513), .driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 }, + { USB_DEVICE(0x2040, 0x6517), .driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 }, + { USB_DEVICE(0x2040, 0x651b), .driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 }, + { USB_DEVICE(0x2040, 0x651f), .driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 }, + { USB_DEVICE(0x2304, 0x0227), .driver_info = EM2883_BOARD_PINNACLE_PCTV_HD_PRO }, + { USB_DEVICE(0x2304, 0x0226), .driver_info = EM2882_BOARD_PINNACLE_HYBRID_PRO }, + { USB_DEVICE(0xeb1a, 0x2751), .driver_info = EM2751_BOARD_EMPIA_SAMPLE }, + { USB_DEVICE(0xeb1a, 0xe305), .driver_info = EM2880_BOARD_KWORLD_DVB_305U }, + { USB_DEVICE(0xeb1a, 0xa316), .driver_info = EM2883_BOARD_KWORLD_HYBRID_A316 }, + { USB_DEVICE(0x093b, 0xa005), .driver_info = EM2861_BOARD_PLEXTOR_PX_TV100U }, + { USB_DEVICE(0xeb1a, 0x50a6), .driver_info = EM2860_BOARD_GADMEI_UTV330 }, + { USB_DEVICE(0x0ccd, 0x0072), .driver_info = EM2883_BOARD_TERRATEC_HYBRID_XS_FM }, + { USB_DEVICE(0x0ccd, 0x0092), .driver_info = EM2883_BOARD_TERRATEC_HYBRID_XS_FM }, + { USB_DEVICE(0xeb1a, 0x2863), .driver_info = EM2863_BOARD_EMPIA_GENERIC }, + + { USB_DEVICE(0xeb1a, 0xf306), .driver_info = EM2883_BOARD_KWORLD_HYBRID_F306 }, + { USB_DEVICE(0xeb1a, 0xe306), .driver_info = EM2883_BOARD_KWORLD_HYBRID_F306 }, /* development only */ + { USB_DEVICE(0x1b80, 0xe329), .driver_info = EM2888_BOARD_KWORLD_HYBRID_E329 }, + { USB_DEVICE(0xeb1a, 0x2889), .driver_info = EM2888_BOARD_EMPIA_HYBRID }, + { USB_DEVICE(0xeb1a, 0xe323), .driver_info = EM2883_BOARD_KWORLD_HYBRID_E323 }, + { USB_DEVICE(0xeb1a, 0xa139), .driver_info = EM2888_BOARD_LINCOLN_TV_FM }, +#if 0 + { USB_DEVICE(0x2304, 0x0242), .driver_info = EM2888_BOARD_DVB_TC_HYBRID }, +#endif + { USB_DEVICE(0x0438, 0xb002), .driver_info = EM2883_BOARD_ATI_TVWONDER600 }, + { USB_DEVICE(0xeb1a, 0x2875), .driver_info = EM2875_BOARD_SAMPLE_ISDBT }, + { USB_DEVICE(0xeb1a, 0x2879), .driver_info = EM2879_BOARD_SAMPLE_DMB }, + { USB_DEVICE(0xeb1a, 0xe301), .driver_info = EM2860_BOARD_KAIOMY_TVNPC_U2 }, + { USB_DEVICE(0x0413, 0x6f02), .driver_info = EM2882_BOARD_LEADTEK_PALMTOP_DTV_200H }, + { USB_DEVICE(0x185b, 0x2041), .driver_info = EM2820_BOARD_COMPRO_VIDEO_MATE }, + + { }, +}; +EXPORT_SYMBOL(em28xx_id_table); + +/* TODO clean this up */ +int em28xx_card_setup(struct em28xx *dev) +{ + int ret = 0; + +#ifdef XCEIVE_EXTERNAL_FIRMWARE + switch(dev->tuner_type) { + case TUNER_XCEIVE_XC3028: + { + const struct firmware *fw; + static unsigned char *xc3028_firmware; + + if (xc3028_firmware==NULL) { + ret = request_firmware(&fw, "empia_xc3028.fw", &dev->udev->dev); + + if (ret) { + printk(KERN_INFO"Couldn't find empia_xc3028.fw, please run:\n"); + printk(KERN_INFO"cd /lib/firmware\n"); + printk(KERN_INFO"sudo wget mcentral.de/empia/empia_xc3028.fw\n"); + printk(KERN_INFO"and replug your device\n"); + return -EINVAL; + } + + if (fw->size != 120273) { + printk(KERN_INFO"Firmware size doesn't match, please be sure\n"); + printk(KERN_INFO"that you picked the correct firmware\n"); + printk(KERN_INFO"cd /lib/firmware\n"); + printk(KERN_INFO"sudo wget mcentral.de/empia/empia_xc3028.fw\n"); + printk(KERN_INFO"and replug your device\n"); + return -EINVAL; + } else + printk(KERN_INFO"xc3028 firmware successfully loaded to RAM\n"); + + + xc3028_firmware = kzalloc(fw->size, GFP_KERNEL); + memcpy(xc3028_firmware, fw->data, fw->size); + release_firmware(fw); + + xc3028_set_firmware(xc3028_firmware, 120273); + } + break; + } + case TUNER_XCEIVE_XC5000: + { + const struct firmware *fw; + + if (XC5000_firmware_SEQUENCE == NULL) { + ret = request_firmware(&fw, "empia_xc5000.fw", &dev->udev->dev); + + if (ret) { + printk(KERN_INFO"Couldn't find empia_xc5000.fw, please run:\n"); + printk(KERN_INFO"cd /lib/firmware\n"); + printk(KERN_INFO"sudo wget mcentral.de/empia/empia_xc5000.fw\n"); + printk(KERN_INFO"and replug your device\n"); + return -EINVAL; + } + + if (fw->size != 12376) { + printk(KERN_INFO"Firmware size doesn't match, please be sure\n"); + printk(KERN_INFO"that you picked the correct firmware\n"); + printk(KERN_INFO"cd /lib/firmware\n"); + printk(KERN_INFO"sudo wget mcentral.de/empia/empia_xc5000.fw\n"); + printk(KERN_INFO"and replug your device\n"); + return -EINVAL; + } + + + XC5000_firmware_SEQUENCE = kzalloc(fw->size, GFP_KERNEL); + memcpy(XC5000_firmware_SEQUENCE, &fw->data[6], fw->size-6); + release_firmware(fw); + + } + break; + } + default: + break; + } +#endif + + switch (dev->model) { + case EM2750_BOARD_DLCW_130: + case EM2751_BOARD_EMPIA_SAMPLE: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x0a", 1); + if (ret < 0) + return ret; + break; + } + case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2: + case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2_R2: + case EM2870_BOARD_HAUPPAUGE_WINTV_USB_2_R2: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); + if (ret < 0) + return ret; +#ifdef EM28XX_TVEEPROM + struct tveeprom tv; + request_module("tveeprom"); +#endif +#ifdef CONFIG_MODULES + request_module("ir-kbd-i2c"); + request_module("msp3400"); +#endif + /* Call first TVeeprom */ + +#ifdef EM28XX_TVEEPROM + dev->i2c_client.addr = 0xa0 >> 1; + tveeprom_hauppauge_analog(&dev->i2c_client, &tv, dev->eedata); + + dev->tuner_type = tv.tuner_type; +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 26) + if (tv.audio_processor == AUDIO_CHIP_MSP34XX) { +#else + if (tv.audio_processor == V4L2_IDENT_MSPX4XX) { +#endif + dev->i2s_speed = 2048000; + dev->has_msp34xx = 1; + } else + dev->has_msp34xx = 0; + + if (dev->has_msp34xx) { + /* Send a reset to other chips via gpio */ + ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xf7", 1); + if (ret < 0) + return ret; + udelay(2500); + ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xff", 1); + if (ret < 0) + return ret; + udelay(2500); + } +#endif + + break; + } + case EM2820_BOARD_KWORLD_PVRTV2800RF: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); + if (ret < 0) + return ret; + /* GPIO enables sound on KWORLD PVR TV 2800RF */ + ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xf9", 1); + if (ret < 0) + return ret; + break; + } + case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x4c", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x08, "\x6d", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x08, "\x7d", 1); + if (ret < 0) + return ret; + msleep(10); +#ifdef CONFIG_MODULES + request_module("tveeprom"); +#endif + break; + } + case EM2820_BOARD_MSI_VOX_USB_2: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); + if (ret < 0) + return ret; + /* enables audio for that device */ + ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xfd", 1); + if (ret < 0) + return ret; + break; + } + case EM2880_BOARD_TERRATEC_HYBRID_XS_FR: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, 0x08, "\xfd", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x08, "\xff", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x08, "\xfe", 1); + if (ret < 0) + return ret; + msleep(10); + + ret = em28xx_write_regs(dev, 0x04, "\x00", 1); + if (ret < 0) + return ret; + msleep(100); + ret = em28xx_write_regs(dev, 0x04, "\x08", 1); + if (ret < 0) + return ret; + msleep(100); + ret = em28xx_write_regs(dev, 0x08, "\xfe", 1); + if (ret < 0) + return ret; + msleep(100); + break; + } + + case EM2880_BOARD_TERRATEC_HYBRID_XS: + case EM2860_BOARD_TERRATEC_HYBRID_XS: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x97", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); + if (ret < 0) + return ret; + msleep(10); + break; + } + case EM2882_BOARD_TERRATEC_HYBRID_XS: + case EM2882_BOARD_LEADTEK_PALMTOP_DTV_200H: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x07", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x4c", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x08, "\x6d", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x08, "\x7d", 1); + if (ret < 0) + return ret; + msleep(10); + break; + } + case EM2881_BOARD_DNT_DA2_HYBRID: + case EM2861_BOARD_KWORLD_PVRTV_300U: + case EM2880_BOARD_KWORLD_DVB_310U: + case EM2880_BOARD_KWORLD_DVB_305U: + case EM2880_BOARD_MSI_DIGIVOX_AD: + case EM2880_BOARD_MSI_DIGIVOX_AD_II: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x4c", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x08, "\x6d", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x08, "\x7d", 1); + if (ret < 0) + return ret; + msleep(10); + break; + } +/* case EM2870_BOARD_COMPRO_VIDEOMATE: TODO */ + case EM2870_BOARD_PINNACLE_PCTV_DVB: + { + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); + /* this device needs some gpio writes to get the DVB-T demod work */ + ret = em28xx_write_regs(dev, 0x08, "\xfe", 1); + if (ret < 0) + return ret; + mdelay(70); + ret = em28xx_write_regs(dev, 0x08, "\xde", 1); + if (ret < 0) + return ret; + mdelay(70); + ret = em28xx_write_regs(dev, 0x08, "\xfe", 1); + if (ret < 0) + return ret; + mdelay(70); + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x22", 1); /* switch em2880 rc protocol */ + if (ret < 0) + return ret; + break; + } + case EM2870_BOARD_TERRATEC_XS_MT2060: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); + if (ret < 0) + return ret; + /* this device needs some gpio writes to get the DVB-T demod work */ + ret = em28xx_write_regs(dev, 0x08, "\xfe", 1); + if (ret < 0) + return ret; + mdelay(70); + ret = em28xx_write_regs(dev, 0x08, "\xde", 1); + if (ret < 0) + return ret; + mdelay(70); + ret = dev->em28xx_write_regs(dev, 0x08, "\xfe", 1); + if (ret < 0) + return ret; + mdelay(70); + break; + } + case EM2870_BOARD_KWORLD_350U: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, 0x08, "\xdf", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x08, "\xde", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x08, "\xfe", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x08, "\x6e", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x08, "\x7e", 1); + if (ret < 0) + return ret; + msleep(10); + break; + } + case EM2870_BOARD_KWORLD_355U: + case EM2870_BOARD_COMPRO_VIDEOMATE: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); + if (ret < 0) + return ret; + /* TODO: someone can do some cleanup here... not everything's needed */ + ret = em28xx_write_regs(dev, 0x04, "\x00", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x04, "\x01", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x08, "\xfd", 1); + if (ret < 0) + return ret; + mdelay(70); + ret = em28xx_write_regs(dev, 0x08, "\xfc", 1); + if (ret < 0) + return ret; + mdelay(70); + ret = em28xx_write_regs(dev, 0x08, "\xdc", 1); + if (ret < 0) + return ret; + mdelay(70); + ret = em28xx_write_regs(dev, 0x08, "\xfc", 1); + if (ret < 0) + return ret; + mdelay(70); + break; + } + + case EM2883_BOARD_PINNACLE_PCTV_HD_PRO: + case EM2882_BOARD_PINNACLE_HYBRID_PRO: + case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2: + case EM2883_BOARD_KWORLD_HYBRID_E323: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, 0x08, "\xff", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, 0x04, "\x00", 1); + if (ret < 0) + return ret; + msleep(100); + ret = em28xx_write_regs(dev, 0x04, "\x08", 1); + if (ret < 0) + return ret; + msleep(100); + ret = em28xx_write_regs(dev, 0x08, "\xff", 1); + if (ret < 0) + return ret; + msleep(50); + ret = em28xx_write_regs(dev, 0x08, "\x2d", 1); + if (ret < 0) + return ret; + msleep(50); + ret = em28xx_write_regs(dev, 0x08, "\x3d", 1); + if (ret < 0) + return ret; + + /* enable audio 12 mhz i2s*/ + ret = em28xx_write_regs(dev, 0x0f, "\xa7", 1); + if (ret < 0) + return ret; + msleep(10); + break; + } + case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950: + case EM2883_BOARD_ATI_TVWONDER600: + case EM2883_BOARD_KWORLD_HYBRID_A316: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, 0x08, "\xff", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, 0x04, "\x00", 1); + if (ret < 0) + return ret; + msleep(100); + ret = em28xx_write_regs(dev, 0x04, "\x08", 1); + if (ret < 0) + return ret; + msleep(100); + ret = em28xx_write_regs(dev, 0x08, "\xff", 1); + if (ret < 0) + return ret; + msleep(50); + ret = em28xx_write_regs(dev, 0x08, "\x2d", 1); + if (ret < 0) + return ret; + msleep(50); + ret = em28xx_write_regs(dev, 0x08, "\x3d", 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, 0x0f, "\xa7", 1); /* enable audio 12 mhz i2s*/ + if (ret < 0) + return ret; + msleep(10); + break; + } + case EM2881_BOARD_PINNACLE_HYBRID_PRO: + { + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x4c", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x08, "\xfd", 1); + if (ret < 0) + return ret; + msleep(100); + ret = em28xx_write_regs(dev, 0x08, "\xfd", 1); + if (ret < 0) + return ret; + msleep(100); + ret = em28xx_write_regs(dev, 0x08, "\xff", 1); + if (ret < 0) + return ret; + msleep(5); + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); /* switch em2880 rc protocol */ + if (ret < 0) + return ret; +#if 0 + + ret = dev->em28xx_write_regs(dev, 0x08, "\x6d", 1); + if (ret < 0) + return ret; + msleep(10); + ret = dev->em28xx_write_regs(dev, 0x08, "\x7d", 1); + if (ret < 0) + return ret; + msleep(10); +#endif +#if 0 + em2880_ir_attach(dev, ir_codes_pinnacle2, ARRAY_SIZE(ir_codes_pinnacle2), em2880_get_key_empia); +#endif + break; + } + case EM2820_BOARD_GADMEI_UTV310: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); + if (ret < 0) + return ret; + /* Turn on analog audio output */ + ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xfd", 1); + if (ret < 0) + return ret; + break; + } + case EM2860_BOARD_GADMEI_UTV330: + { + /* Turn on IR */ + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x07", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); + if (ret < 0) + return ret; + break; + } + case EM2861_BOARD_PLEXTOR_PX_TV100U: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); + if (ret < 0) + return ret; + /* Turn on analog audio output */ + ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xfd", 1); + if (ret < 0) + return ret; + break; + } + case EM2888_BOARD_KWORLD_HYBRID_E329: + case EM2888_BOARD_EMPIA_HYBRID: + case EM2888_BOARD_LINCOLN_TV_FM: + { + ret = em28xx_write_regs(dev, 0x80, "\xf0", 1); + msleep(100); + ret = em28xx_write_regs(dev, 0x80, "\xf4", 1); + msleep(50); + ret = em28xx_write_regs(dev, 0x80, "\xf6", 1); + ret = em28xx_write_regs(dev, 0x80, "\xb6", 1); + msleep(100); + ret = em28xx_write_regs(dev, 0x80, "\xf6", 1); + msleep(100); + ret = em28xx_write_regs(dev, 0x80, "\xf5", 1); + if (ret < 0) + return ret; + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x45", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x02", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, 0x50, "\x01", 1); + if (ret < 0) + return ret; + break; + } + case EM2883_BOARD_EMPIA_HYBRID_ATSC: + case EM2883_BOARD_EQUINUX_TUBESTICK_ATSC: + case EM2883_BOARD_TERRATEC_HYBRID_XS_FM: + case EM2863_BOARD_EMPIA_GENERIC: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x97", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x08, "\x2d", 1); + if (ret < 0) + return ret; + msleep(10); +#if 0 + ret = em28xx_write_regs(dev, 0x08, "\x7d", 1); + if (ret < 0) + return ret; + msleep(10); +#endif + break; + } + case EM2860_BOARD_KAIOMY_TVNPC_U2: + { + ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x4c", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x08, "\x6d", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x08, "\x7d", 1); + if (ret < 0) + return ret; + msleep(10); + ret = em28xx_write_regs(dev, 0x0f, "\x07", 1); + if (ret < 0) + return ret; + break; + } + case EM2875_BOARD_SAMPLE_ISDBT: + { + // em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); // EEPROM access + em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x44", 1); + em28xx_write_regs(dev, 0x0f, "\x02", 1); + em28xx_write_regs(dev, 0x50, "\x0e", 1); + em28xx_write_regs(dev, 0x80, "\xf0", 1); + break; + } + + default: + ret = dev->em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); + if (ret < 0) + return ret; + if (dev->em_type != EM2800) { + ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); + if (ret < 0) + return ret; + } + } + return 0; +} + +void em28xx_card_disconnect(struct em28xx *dev) +{ + if (dev->ir_em2880) + em2880_ir_detach(dev); +} + +EXPORT_SYMBOL(em28xx_boards); +EXPORT_SYMBOL(em28xx_bcount); + +MODULE_DEVICE_TABLE (usb, em28xx_id_table); diff --git a/drivers/media/video/empia/em28xx-core.c b/drivers/media/video/empia/em28xx-core.c new file mode 100644 index 0000000..48b4b8c --- /dev/null +++ b/drivers/media/video/empia/em28xx-core.c @@ -0,0 +1,1366 @@ +/* + em28xx-core.c - driver for Empia EM2800/EM2820/2840/2880 USB video capture devices + + Copyright (C) 2005 Ludovico Cavedon + Markus Rechberger + Mauro Carvalho Chehab + Sascha Sommer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "em28xx.h" + +/* #define ENABLE_DEBUG_ISOC_FRAMES */ + +static unsigned int core_debug; +module_param(core_debug, int, 0644); +MODULE_PARM_DESC(core_debug, "enable debug messages [core]"); + +#define em28xx_coredbg(fmt, arg...) do {\ + if (core_debug) \ + printk(KERN_INFO "%s %s :"fmt, \ + dev->name, __FUNCTION__ , ##arg); } while (0) + +static unsigned int reg_debug; +module_param(reg_debug, int, 0644); +MODULE_PARM_DESC(reg_debug, "enable debug messages [URB reg]"); + +#define em28xx_regdbg(fmt, arg...) do {\ + if (reg_debug) \ + printk(KERN_INFO "%s: "fmt, \ + dev->name, ##arg); } while (0) + +static unsigned int isoc_debug; +module_param(isoc_debug, int, 0644); +MODULE_PARM_DESC(isoc_debug, "enable debug messages [isoc transfers]"); + +#define em28xx_isocdbg(fmt, arg...) do {\ + if (isoc_debug) \ + printk(KERN_INFO "%s %s :"fmt, \ + dev->name, __FUNCTION__ , ##arg); } while (0) + +static int alt = EM28XX_PINOUT; +module_param(alt, int, 0644); +MODULE_PARM_DESC(alt, "alternate setting to use for video endpoint"); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15) +static void *rvmalloc(size_t size) +{ + void *mem; + unsigned long adr; + + size = PAGE_ALIGN(size); + + mem = vmalloc_32((unsigned long)size); + if (!mem) + return NULL; + + adr = (unsigned long)mem; + while (size > 0) { + SetPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + + return mem; +} + +static void rvfree(void *mem, size_t size) +{ + unsigned long adr; + + if (!mem) + return; + + size = PAGE_ALIGN(size); + + adr = (unsigned long)mem; + while (size > 0) { + ClearPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + + vfree(mem); +} +#endif + +/* + * em28xx_request_buffers() + * allocate a number of buffers + */ +u32 em28xx_request_buffers(struct em28xx *dev, u32 count, int type) +{ + size_t size; + + void *buff = NULL; + u32 i; + + + size = PAGE_ALIGN((type == EM28XX_VBI) ? + dev->vbi_frame_size : dev->frame_size); + + switch (type) { + case EM28XX_VIDEO: + if (count > EM28XX_NUM_FRAMES) + count = EM28XX_NUM_FRAMES; + + if (dev->num_frames > 0) + return -EINVAL; + + dev->num_frames = count; + size = PAGE_ALIGN(dev->frame_size); + break; + case EM28XX_VBI: + if (count > EM28XX_NUM_FRAMES) /* FIXME VBI_NUM_FRAMES */ + count = EM28XX_NUM_FRAMES; + + if (dev->vbi_num_frames > 0) + return -EINVAL; + + dev->vbi_num_frames = count; + size = PAGE_ALIGN(dev->vbi_frame_size); + break; + default: + printk(KERN_INFO"em28xx-core.c: error wrong buffer request!\n"); + return -EINVAL; + } + + em28xx_coredbg("requested %i buffers with size %zi", count, size); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15) + buff = rvmalloc(count * size); +#else + buff = vmalloc_32(count * size); +#endif + if (buff) + memset(buff, 0, count * size); + else + return -ENOMEM; + + for (i = 0; i < count; i++) { + switch (type) { + case EM28XX_VBI: + dev->vbi_frame[i].bufmem = buff + i * size; + dev->vbi_frame[i].buf.index = i; + dev->vbi_frame[i].buf.m.offset = i * size; + dev->vbi_frame[i].buf.length = dev->vbi_frame_size; + dev->vbi_frame[i].buf.type = V4L2_BUF_TYPE_VBI_CAPTURE; + dev->vbi_frame[i].buf.sequence = 0; + +/* Indicates the field order of the image in the buffer, see Table 3-8>. This + field is not used when the buffer contains VBI data. Drivers must set it + when type refers to an input stream, applications when an output stream. + + this probably should be changed since we can have an interlaced field here +*/ + if (dev->vbi_interlaced) + dev->vbi_frame[i].buf.field = V4L2_VBI_INTERLACED; + else + dev->vbi_frame[i].buf.field = V4L2_FIELD_NONE; + dev->vbi_frame[i].buf.memory = V4L2_MEMORY_MMAP; + dev->vbi_frame[i].buf.flags = 0; + break; + case EM28XX_VIDEO: + dev->frame[i].bufmem = buff + i * size; + dev->frame[i].buf.index = i; + dev->frame[i].buf.m.offset = i * size; + dev->frame[i].buf.length = dev->frame_size; + dev->frame[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dev->frame[i].buf.sequence = 0; + dev->frame[i].buf.field = V4L2_FIELD_INTERLACED; /*_TB; V4L2_FIELD_NONE; */ + dev->frame[i].buf.memory = V4L2_MEMORY_MMAP; + dev->frame[i].buf.flags = 0; + break; + default: + printk(KERN_INFO"error wrong buffer request!\n"); + return -EINVAL; + } + + } + return count; +} + +/* + * em28xx_queue_unusedframes() + * add all frames that are not currently in use to the inbuffer queue + */ +void em28xx_queue_unusedframes(struct em28xx *dev, int type) +{ + unsigned long lock_flags; + u32 i; + switch (type) { + case EM28XX_VIDEO: + for (i = 0; i < dev->num_frames; i++) { + if (dev->frame[i].state == F_UNUSED) { + spin_lock_irqsave(&dev->queue_lock, lock_flags); + dev->frame[i].state = F_QUEUED; + list_add_tail(&dev->frame[i].frame, + &dev->inqueue); + spin_unlock_irqrestore(&dev->queue_lock, + lock_flags); + } + } + break; + case EM28XX_VBI: + for (i = 0; i < dev->vbi_num_frames; i++) { + if (dev->vbi_frame[i].state == F_UNUSED) { + spin_lock_irqsave(&dev->vbi_queue_lock, + lock_flags); + dev->vbi_frame[i].state = F_QUEUED; + list_add_tail(&dev->vbi_frame[i].frame, + &dev->vbi_inqueue); + spin_unlock_irqrestore(&dev->vbi_queue_lock, + lock_flags); + } + } + break; + } +} + +/* + * em28xx_release_buffers() + * free frame buffers + */ +void em28xx_release_buffers(struct em28xx *dev, int type) +{ + if (type == EM28XX_VIDEO && dev->num_frames) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15) + rvfree(dev->frame[0].bufmem, + dev->num_frames * + PAGE_ALIGN(dev->frame[0].buf.length)); +#else + vfree(dev->frame[0].bufmem); + dev->num_frames = 0; +#endif + } else if (type == EM28XX_VBI && dev->vbi_num_frames) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15) + rvfree(dev->vbi_frame[0].bufmem, + dev->vbi_num_frames * + PAGE_ALIGN(dev->vbi_frame[0].buf.length)); +#else + vfree(dev->vbi_frame[0].bufmem); + dev->vbi_num_frames = 0; +#endif + } +} + +/* + * em28xx_read_reg_req() + * reads data from the usb device specifying bRequest + */ +int em28xx_read_reg_req_len(struct em28xx *dev, u8 req, u16 reg, + char *buf, int len) +{ + int ret, byte; + + if (dev->state & DEV_DISCONNECTED) + return -ENODEV; + + ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), req, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, reg, buf, len, HZ); + + if (reg_debug) { + em28xx_regdbg("%s c0 %02x 00 00 %02x 00 %02x 00 <<<", + (ret < 0) ? "[NOK]" : "[ OK]", req, reg, len); + + for (byte = 0; byte < len; byte++) + printk(" %02x", (unsigned char)buf[byte]); + printk("\n"); + } + return ret; +} + +/* + * em28xx_read_reg_req() + * reads data from the usb device specifying bRequest + */ +int em28xx_read_reg_req(struct em28xx *dev, u8 req, u16 reg) +{ + u8 val; + int ret; + + if (dev->state & DEV_DISCONNECTED) + return(-ENODEV); + + + ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), req, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, reg, &val, 1, HZ); + + if (reg_debug) { + if (ret < 0) + em28xx_regdbg("%s c0 %02x 00 00 %02x 00 01 00 <<<\n", + (ret < 0) ? "[NOK]" : "[ OK]", req, + reg); + else + em28xx_regdbg("%s c0 %02x 00 00 %02x 00 01 00 <<< " + "%02x\n", (ret < 0) ? "[NOK]" : "[ OK]", + req, reg, (unsigned char)val); + } + + if (ret < 0) + return ret; + + return val; +} + +int em28xx_read_reg(struct em28xx *dev, u16 reg) +{ + return em28xx_read_reg_req(dev, USB_REQ_GET_STATUS, reg); +} + +/* + * em28xx_write_regs_req() + * sends data to the usb device, specifying bRequest + */ +int em28xx_write_regs_req(struct em28xx *dev, u8 req, u16 reg, char *buf, + int len) +{ + int ret; + + /*usb_control_msg seems to expect a kmalloced buffer */ + unsigned char *bufs; + + if (dev->state & DEV_DISCONNECTED) + return(-ENODEV); + + bufs = kmalloc(len, GFP_KERNEL); + + if (!bufs) + return -ENOMEM; + memcpy(bufs, buf, len); + + ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), req, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, reg, bufs, len, HZ); + if (reg_debug) { + int i; + em28xx_regdbg("%s 40 %02x 00 00 %02x 00 %02x 00 >>>", + (ret < 0) ? "[NOK]" : "[ OK]", req, reg, len); + for (i = 0; i < len; ++i) + printk(" %02x", (unsigned char)buf[i]); + printk("\n"); + } + kfree(bufs); + return ret; +} + +int em28xx_write_regs(struct em28xx *dev, u16 reg, char *buf, int len) +{ + return em28xx_write_regs_req(dev, USB_REQ_GET_STATUS, reg, buf, len); +} + +/* + * em28xx_write_reg_bits() + * sets only some bits (specified by bitmask) of a register, by first reading + * the actual value + */ +int em28xx_write_reg_bits(struct em28xx *dev, u16 reg, u8 val, + u8 bitmask) +{ + int oldval; + u8 newval; + oldval = em28xx_read_reg(dev, reg); + if (oldval < 0) + return oldval; + newval = (((u8) oldval) & ~bitmask) | (val & bitmask); + return em28xx_write_regs(dev, reg, &newval, 1); +} + +/* + * em28xx_write_ac97() + * write a 16 bit value to the specified AC97 address (LSB first!) + */ +int em28xx_write_ac97(struct em28xx *dev, u8 reg, u8 *val) +{ + int ret; + u8 addr; + switch (dev->model) { + case EM2881_BOARD_PINNACLE_HYBRID_PRO: + case EM2880_BOARD_TERRATEC_HYBRID_XS_FR: + case EM2881_BOARD_DNT_DA2_HYBRID: + addr = 0x1a; + break; + default: + addr = reg & 0x7f; + } + + ret = em28xx_read_reg(dev, R43_AC97BUSY_REG); + if (ret < 0) + return ret; + else if (((u8) ret) & 0x01) + return 0; + + ret = em28xx_write_regs(dev, R40_AC97LSB_REG, val, 2); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R42_AC97ADDR_REG, &addr, 1); + if (ret < 0) + return ret; + + return 0; +} + +int em28xx_audio_analog_set(struct em28xx *dev) +{ + char s[2] = { 0x00, 0x00 }; + + switch (dev->model) { + case EM2881_BOARD_PINNACLE_HYBRID_PRO: + case EM2880_BOARD_TERRATEC_HYBRID_XS_FR: + case EM2881_BOARD_DNT_DA2_HYBRID: + s[0] = 0x05; + s[1] = 0x05; + break; + case EM2861_BOARD_PLEXTOR_PX_TV100U: + case EM2860_BOARD_GADMEI_UTV330: + switch (dev->ctl_ainput) { + case 0: + s[0] = 0xfd; + break; + case 1: + s[0] = 0xfc; + break; + default : + s[0] = 0xfe; + break; + } + if (dev->mute) + s[0] = 0xfe; + return em28xx_write_regs(dev, 0x08, s, 1); + default: + s[0] |= 0x1f - dev->volume; + s[1] |= 0x1f - dev->volume; + } + + if (dev->mute) + s[1] |= 0x80; + return em28xx_write_ac97(dev, R02_MASTER_AC97, s); +} + +int em28xx_colorlevels_set_default(struct em28xx *dev) +{ + int ret; + + /* contrast */ + ret = em28xx_write_regs(dev, R20_YGAIN_REG, "\x10", 1); + if (ret < 0) + return ret; + + /* brightness */ + ret = em28xx_write_regs(dev, R21_YOFFSET_REG, "\x00", 1); + if (ret < 0) + return ret; + + /* saturation */ + ret = em28xx_write_regs(dev, R22_UVGAIN_REG, "\x10", 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R23_UOFFSET_REG, "\x00", 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R24_VOFFSET_REG, "\x00", 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R25_SHARPNESS_REG, "\x00", 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R14_GAMMA_REG, "\x20", 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R15_RGAIN_REG, "\x20", 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R16_GGAIN_REG, "\x20", 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R17_BGAIN_REG, "\x20", 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R18_ROFFSET_REG, "\x00", 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R19_GOFFSET_REG, "\x00", 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R1A_BOFFSET_REG, "\x00", 1); + if (ret < 0) + return ret; + + return 0; +} + +int em28xx_capture_start(struct em28xx *dev, int start) +{ + int ret; + /* FIXME: which is the best order? */ + /* video registers are sampled by VREF */ + if ((ret = em28xx_write_reg_bits(dev, R0C_USBSUSP_REG, start ? + 0x10 : 0x00, + 0x10)) < 0) + return ret; + /* enable video capture */ + return em28xx_write_regs(dev, R12_VINENABLE_REG, start ? + "\x67" : "\x27", 1); +} + +int em28xx_outfmt_set_yuv422(struct em28xx *dev) +{ + int ret; + + /* maybe put this configuration into the card configstruct, + it's not entirely clear what values are needed for em2750 + boards so this is a hacking place at the moment */ + + if (dev->em_type == EM2750 || dev->em_type == EM2751) { + ret = em28xx_write_regs(dev, R11_VINCTRL_REG, "\x00", 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R10_VINMODE_REG, "\x0a", 1); + if (ret < 0) + return ret; + } else { + u8 fmt_val; + + fmt_val = ((u8)em28xx_read_reg(dev, R27_OUTFMT_REG) & ~0x1f) | dev->outfmt->config; + + ret = em28xx_write_regs(dev, R27_OUTFMT_REG, &fmt_val, 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R10_VINMODE_REG, "\x10", 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R11_VINCTRL_REG, "\x11", 1); + if (ret < 0) + return ret; + } + if (dev->dev_modes & EM28XX_VBI) + em28xx_set_vbi(dev, 1); + return 0; +} + +int em28xx_set_vbi(struct em28xx *dev, int enable) +{ + u8 vic; + int ret; + ret = em28xx_write_regs_req(dev, 0x00, 0x34, + &dev->tvnorm->vbi_h_start, 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs_req(dev, 0x00, 0x35, + &dev->tvnorm->vbi_v_start, 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs_req(dev, 0x00, 0x36, + &dev->tvnorm->vbi_w, 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs_req(dev, 0x00, 0x37, + &dev->tvnorm->vbi_h, 1); + if (ret < 0) + return ret; + + if (enable) + vic = em28xx_read_reg(dev, R11_VINCTRL_REG) | 0x48; + else + vic = em28xx_read_reg(dev, R11_VINCTRL_REG) & ~0x48; + + ret = em28xx_write_regs(dev, R11_VINCTRL_REG, &vic , 1); + + if (ret < 0) + return ret; + + return 0; +} + +int em28xx_accumulator_set(struct em28xx *dev, u8 xmin, u8 xmax, u8 ymin, + u8 ymax) +{ + int ret; + em28xx_coredbg("em28xx Scale: (%d,%d)-(%d,%d)\n", xmin, + ymin, xmax, ymax); + + ret = em28xx_write_regs(dev, R28_XMIN_REG, &xmin, 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R29_XMAX_REG, &xmax, 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R2A_YMIN_REG, &ymin, 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R2B_YMAX_REG, &ymax, 1); + if (ret < 0) + return ret; + + return 0; +} + +int em28xx_capture_area_set(struct em28xx *dev, u8 hstart, u8 vstart, + u16 width, u16 height) +{ + int ret; + u8 cwidth = width; + u8 cheight = height; + u8 overflow = (height >> 7 & 0x02) | (width >> 8 & 0x01); + + em28xx_coredbg("em28xx Area Set: (%d,%d)\n", + (width | (overflow & 2) << 7), + (height | (overflow & 1) << 8)); + + ret = em28xx_write_regs(dev, R1C_HSTART_REG, &hstart, 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R1D_VSTART_REG, &vstart, 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R1E_CWIDTH_REG, &cwidth, 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R1F_CHEIGHT_REG, &cheight, 1); + if (ret < 0) + return ret; + + ret = em28xx_write_regs(dev, R1B_OFLOW_REG, &overflow, 1); + if (ret < 0) + return ret; + return 0; +} + +int em28xx_scaler_set(struct em28xx *dev, u16 h, u16 v) +{ + int ret; + u8 mode; + u8 buf[2]; + /* the em2800 scaler only supports scaling down to 50% */ + + switch (dev->em_type) { + case EM2750: +#if 1 /* TODO: PLEASE VERIFY */ + mode = (h || v)? 0x30: 0x00; + ret = em28xx_write_regs(dev, R30_HSCALELOW_REG, "\x00\x30", 2); + if (ret < 0) + return ret; + ret = em28xx_write_regs(dev, R32_VSCALELOW_REG, "\x00\x30", 2); + if (ret < 0) + return ret; +#endif + break; + case EM2800: + mode = (v ? 0x20 : 0x00) | (h ? 0x10 : 0x00); + break; + default: + buf[0] = h; + buf[1] = h >> 8; + ret = em28xx_write_regs(dev, R30_HSCALELOW_REG, (char *)buf, 2); + if (ret < 0) + return ret; + buf[0] = v; + buf[1] = v >> 8; + ret = em28xx_write_regs(dev, R32_VSCALELOW_REG, (char *)buf, 2); + if (ret < 0) + return ret; + /* it seems that both H and V scalers must be active to work + correctly */ + mode = (h || v)? 0x30: 0x00; + } + ret = em28xx_write_reg_bits(dev, R26_COMPR_REG, mode, 0x30); + if (ret < 0) + return ret; + + return ret; +} + +/* FIXME: this only function read values from dev */ +int em28xx_resolution_set(struct em28xx *dev) +{ + int width, height; + width = norm_maxw(dev); + height = norm_maxh(dev) >> 1; + + em28xx_outfmt_set_yuv422(dev); + em28xx_accumulator_set(dev, 1, (width - 4) >> 2, 1, (height - 4) >> 2); + em28xx_capture_area_set(dev, 0, 0, width >> 2, height >> 2); + return em28xx_scaler_set(dev, dev->hscale, dev->vscale); +} + + +/******************* isoc transfer handling ****************************/ + +#ifdef ENABLE_DEBUG_ISOC_FRAMES +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) +static void em28xx_isoc_dump(struct urb *urb, struct pt_regs *regs) +#else +static void em28xx_isoc_dump(struct urb *urb) +#endif +{ + int len = 0; + int ntrans = 0; + int i; + + printk(KERN_DEBUG "isocIrq: sf=%d np=%d ec=%x\n", + urb->start_frame, urb->number_of_packets, + urb->error_count); + for (i = 0; i < urb->number_of_packets; i++) { + unsigned char *buf = + urb->transfer_buffer + + urb->iso_frame_desc[i].offset; + int alen = urb->iso_frame_desc[i].actual_length; + if (alen > 0) { + if (buf[0] == 0x88) { + ntrans++; + len += alen; + } else if (buf[0] == 0x22) { + printk(KERN_DEBUG + "= l=%d nt=%d bpp=%d\n", + len - 4 * ntrans, ntrans, + ntrans == 0 ? 0 : len / ntrans); + ntrans = 1; + len = alen; + } else + printk(KERN_DEBUG "!\n"); + } + printk(KERN_DEBUG " n=%d s=%d al=%d %x\n", i, + urb->iso_frame_desc[i].status, + urb->iso_frame_desc[i].actual_length, + (unsigned int) + *((unsigned char *)(urb->transfer_buffer + + urb->iso_frame_desc[i]. + offset))); + } +} +#endif + +static int em28xx_isoc_video(struct em28xx *dev, struct em28xx_frame_t **f, + struct em28xx_frame_t **vbif, + unsigned long *lock_flags, unsigned char buf) +{ + + if (!(buf & 0x01)) { + if ((*vbif)) { + if ((*vbif)->state == F_GRABBING) { + /*previous frame is incomplete */ + if ((*vbif)->fieldbytesused < dev->vbi_field_size) { + (*vbif)->state = F_ERROR; + } else { + (*vbif)->state = F_DONE; + (*vbif)->buf.bytesused = dev->vbi_frame_size; + } + } + if ((*vbif)->state == F_DONE || + (*vbif)->state == F_ERROR) { + /* move current frame to outqueue and get next free buffer from inqueue */ + spin_lock_irqsave(&dev->vbi_queue_lock, + *lock_flags); + list_move_tail(&(*vbif)->frame, + &dev->vbi_outqueue); + if (!list_empty(&dev->vbi_inqueue)) + (*vbif) = list_entry( + dev->vbi_inqueue.next, + struct em28xx_frame_t, + frame); + else + (*vbif) = NULL; + spin_unlock_irqrestore(&dev->vbi_queue_lock, + *lock_flags); + } + if ((*vbif)) { + do_gettimeofday(&(*vbif)->buf.timestamp); + (*vbif)->buf.sequence = ++dev->vbi_frame_count; + (*vbif)->buf.field = V4L2_FIELD_INTERLACED; + (*vbif)->state = F_GRABBING; + (*vbif)->buf.bytesused = 0; + (*vbif)->top_field = 1; + (*vbif)->fieldbytesused = 0; + } + } + if ((*f)) { + if ((*f)->state == F_GRABBING) { + /*previous frame is incomplete */ + if ((*f)->fieldbytesused < dev->field_size) { + (*f)->state = F_ERROR; + } else { + (*f)->state = F_DONE; + (*f)->buf.bytesused = dev->frame_size; + } + } + if ((*f)->state == F_DONE || (*f)->state == F_ERROR) { + /* move current frame to outqueue and get next + free buffer from inqueue */ + spin_lock_irqsave(&dev->queue_lock, *lock_flags); + list_move_tail(&(*f)->frame, &dev->outqueue); + if (!list_empty(&dev->inqueue)) { + (*f) = list_entry(dev->inqueue.next, + struct em28xx_frame_t, + frame); + } else { + (*f) = NULL; + } + spin_unlock_irqrestore(&dev->queue_lock, + *lock_flags); + } + if ((*f)) { + do_gettimeofday(&(*f)->buf.timestamp); + (*f)->buf.sequence = ++dev->frame_count; + (*f)->buf.field = V4L2_FIELD_INTERLACED; + (*f)->state = F_GRABBING; + (*f)->buf.bytesused = 0; + (*f)->top_field = 1; + (*f)->fieldbytesused = 0; + } + } + } else { + if ((*vbif)) { + if ((*vbif)->state == F_QUEUED) + (*vbif)->top_field = 0; + else if ((*vbif)->state == F_GRABBING) { + if (!(*vbif)->top_field) + (*vbif)->state = F_ERROR; + else if ((*vbif)->fieldbytesused < + dev->vbi_field_size - 172) + (*vbif)->state = F_ERROR; + else { + (*vbif)->top_field = 0; + (*vbif)->fieldbytesused = 0; + } + } + } + + if ((*f)) { + if ((*f)->state == F_GRABBING) { + if (!(*f)->top_field) + (*f)->state = F_ERROR; + else if ((*f)->fieldbytesused < + dev->field_size - 172) + (*f)->state = F_ERROR; + else { + (*f)->top_field = 0; + (*f)->fieldbytesused = 0; + } + } + } + } + return 0; +} + +static void em28xx_isoc_video_copy(struct em28xx *dev, + struct em28xx_frame_t **f, unsigned char *buf, int len, int vbioffset) +{ + void *fieldstart, *startwrite, *startread; + int linesdone, currlinedone, offset, lencopy, remain; + + if (dev->frame_size != (*f)->buf.length) + return; + + if (vbioffset > 0) { + startread = buf+vbioffset; + len -= vbioffset; + } else { + startread = buf+4; + len -= 4; + } + + if ((*f)->fieldbytesused + len > dev->field_size) + len = dev->field_size - (*f)->fieldbytesused; + + remain = len; + + + if ((*f)->top_field) + fieldstart = (*f)->bufmem; + else + fieldstart = (*f)->bufmem + dev->bytesperline; + + linesdone = (*f)->fieldbytesused / dev->bytesperline; + currlinedone = (*f)->fieldbytesused % dev->bytesperline; + offset = linesdone * dev->bytesperline * 2 + currlinedone; + startwrite = fieldstart + offset; + lencopy = dev->bytesperline - currlinedone; + lencopy = lencopy > remain ? remain : lencopy; + + memcpy(startwrite, startread, lencopy); + remain -= lencopy; + + while (remain > 0) { + startwrite += lencopy + dev->bytesperline; + startread += lencopy; + if (dev->bytesperline > remain) + lencopy = remain; + else + lencopy = dev->bytesperline; + + memcpy(startwrite, startread, lencopy); + remain -= lencopy; + } + + (*f)->fieldbytesused += len; +} + +static int em28xx_isoc_vbi_copy(struct em28xx *dev, + struct em28xx_frame_t **vbif, unsigned char *buf, int len) +{ + void *fieldstart, *startwrite, *startread; + int linesdone, currlinedone, offset, lencopy, remain; + + if (((*vbif) && dev->vbi_dropbytes == 0) && + !((*vbif)->top_field == 0 && (*vbif)->state == F_QUEUED)) { + if ((*vbif)->fieldbytesused < dev->vbi_field_size) { + if ((*vbif)->fieldbytesused + (len-4) > + dev->vbi_field_size) { + len = dev->vbi_field_size - + (*vbif)->fieldbytesused; + + startread = buf+4; + remain = len-4; + + if ((*vbif)->top_field) + fieldstart = (*vbif)->bufmem; + else { + if (dev->vbi_interlaced) + fieldstart = (*vbif)->bufmem + + dev->vbi_bytesperline; + else + fieldstart = (*vbif)->bufmem + + dev->vbi_field_size; + } + + linesdone = (*vbif)->fieldbytesused / + dev->vbi_bytesperline; + currlinedone = (*vbif)->fieldbytesused % + dev->vbi_bytesperline; + + if (dev->vbi_interlaced) + offset = linesdone * dev->vbi_bytesperline * + 2 + currlinedone; + else + offset = linesdone * dev->vbi_bytesperline + + currlinedone; + + startwrite = fieldstart + offset; + lencopy = dev->vbi_bytesperline - currlinedone; + lencopy = lencopy > remain ? remain : lencopy; + + memcpy(startwrite, startread, lencopy); + remain -= lencopy; + + while (remain > 0) { + startwrite += lencopy; + if (dev->vbi_interlaced) + startwrite += dev->vbi_bytesperline; + + startread += lencopy; + if (dev->vbi_bytesperline > remain) + lencopy = remain; + else + lencopy = dev->vbi_bytesperline; + + memcpy(startwrite, startread, lencopy); + remain -= lencopy; + } + (*vbif)->fieldbytesused = dev->vbi_field_size; + } else { + if ((*vbif)->top_field) + fieldstart = (*vbif)->bufmem; + else { + if (dev->vbi_interlaced) + fieldstart = (*vbif)->bufmem + + dev->vbi_bytesperline; + else + fieldstart = (*vbif)->bufmem + + dev->vbi_field_size; + } + + startread = buf+4; + remain = len-4; + + linesdone = (*vbif)->fieldbytesused / + dev->vbi_bytesperline; + currlinedone = (*vbif)->fieldbytesused % + dev->vbi_bytesperline; + if (dev->vbi_interlaced) + offset = linesdone * dev->vbi_bytesperline * + 2 + currlinedone; + else + offset = linesdone * dev->vbi_bytesperline + + currlinedone; + + startwrite = fieldstart + offset; + lencopy = dev->vbi_bytesperline - currlinedone; + lencopy = lencopy > remain ? remain : lencopy; + + memcpy(startwrite, startread, lencopy); + remain -= lencopy; + + while (remain > 0) { + startwrite += lencopy; + + if (dev->vbi_interlaced) + startwrite += dev->vbi_bytesperline; + + startread += lencopy; + if (dev->vbi_bytesperline > remain) + lencopy = remain; + else + lencopy = dev->vbi_bytesperline; + memcpy(startwrite, startread, lencopy); + remain -= lencopy; + } + (*vbif)->fieldbytesused += (len-4); + len -= 4; + } + } else { + len = -4; + } + } else { + if (dev->vbi_dropbytes < dev->vbi_field_size) { + if (dev->vbi_dropbytes + (len-4) > + dev->vbi_field_size) { + len = dev->vbi_field_size - dev->vbi_dropbytes; + dev->vbi_dropbytes = dev->vbi_field_size; + } else { + dev->vbi_dropbytes += (len-4); + len -= 4; + } + } else { + len = -4; + } + } + return len; +} + +/* + * em28xx_isoIrq() + * handles the incoming isoc urbs and fills the frames from our inqueue + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) +void em28xx_isocIrq(struct urb *urb, struct pt_regs *regs) +#else +static void em28xx_isocIrq(struct urb *urb) +#endif +{ + struct em28xx *dev = urb->context; + int i, status; + struct em28xx_frame_t **f, **vbif; + unsigned long lock_flags; + int vbioffset = 0; + + if (!dev) + return; +#ifdef ENABLE_DEBUG_ISOC_FRAMES + if (isoc_debug > 1) +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) + em28xx_isoc_dump(urb, regs); +#else + em28xx_isoc_dump(urb); +#endif +#endif + if (urb->status == -ENOENT) + return; + + f = &dev->frame_current; + vbif = &dev->vbi_frame_current; + + if (dev->video_stream == STREAM_INTERRUPT) { + dev->video_stream = STREAM_OFF; + if ((*f)) + (*f)->state = F_QUEUED; + (*f) = NULL; + + em28xx_isocdbg("stream interrupted"); + wake_up_interruptible(&dev->video_wait_stream); + } + + if (dev->vbi_stream == STREAM_INTERRUPT) { + dev->vbi_stream = STREAM_OFF; + if ((*vbif)) { + if ((*vbif)->fieldbytesused) + dev->vbi_dropbytes = (*vbif)->fieldbytesused; + + (*vbif)->state = F_QUEUED; + } + (*vbif) = NULL; + wake_up_interruptible(&dev->vbi_wait_stream); + } + + if ((dev->state & DEV_DISCONNECTED) || (dev->state & DEV_MISCONFIGURED)) + return; + + if (dev->video_stream == STREAM_ON || dev->vbi_stream == STREAM_ON) { + if (dev->video_stream == STREAM_ON && + !list_empty(&dev->inqueue) && + !(*f)) + + (*f) = list_entry(dev->inqueue.next, + struct em28xx_frame_t, frame); + + if ((dev->dev_modes & EM28XX_VBI) && + dev->vbi_stream == STREAM_ON && + !list_empty(&dev->vbi_inqueue) && + !(*vbif)) + + (*vbif) = list_entry(dev->vbi_inqueue.next, + struct em28xx_frame_t, frame); + + for (i = 0; i < urb->number_of_packets; i++) { + unsigned char *buf = urb->transfer_buffer + + urb->iso_frame_desc[i].offset; + int len = urb->iso_frame_desc[i].actual_length; + + if (urb->iso_frame_desc[i].status) { + em28xx_isocdbg("data error: [%d] len=%d, " + "status=%d", i, + urb->iso_frame_desc[i].actual_length, + urb->iso_frame_desc[i].status); + + if (urb->iso_frame_desc[i].status != -EPROTO) { + em28xx_isocdbg("continue, len=%d\n", + urb->iso_frame_desc[i].actual_length); + continue; + } + } + if (urb->iso_frame_desc[i].actual_length <= 0) { + em28xx_isocdbg("packet %d is empty\n", i); + continue; + } + + if (urb->iso_frame_desc[i].actual_length > + dev->max_pkt_size) { + em28xx_isocdbg("packet bigger than packet " + "size\n"); + continue; + } +#if 0 + if (buf[0] != 0x88 && buf[1] != 0x88) + printk(KERN_DEBUG"headings: %02x %02x\n", + buf[0], buf[1]); +#endif + if (buf[0] == 0x22 && buf[1] == 0x5a) { + em28xx_isocdbg("Video frame, length=%i!\n", + len); + dev->vbi_dropbytes = 0; + em28xx_isoc_video(dev, f, vbif, &lock_flags, + buf[2]); + } else if (buf[0] == 0x33 && buf[1] == 0x95) { + dev->vbi_dropbytes = 0; + em28xx_isoc_video(dev, f, vbif, &lock_flags, + buf[2]); + } + + /* actual copying */ + if (dev->dev_modes & EM28XX_VBI) { + vbioffset = em28xx_isoc_vbi_copy(dev, vbif, + buf, len) + 4; + } else + vbioffset = 0; + + if ((*f) && (len - vbioffset > 0) && + (*f)->state == F_GRABBING) + em28xx_isoc_video_copy(dev, f, buf, len, + vbioffset); + } + } + + 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; + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + em28xx_errdev("resubmit of urb failed (error=%i)\n", status); + dev->state |= DEV_MISCONFIGURED; + } + wake_up_interruptible(&dev->wait_frame); + if (dev->dev_modes & EM28XX_VBI) + wake_up_interruptible(&dev->wait_vbi_frame); +} + +/* + * em28xx_uninit_isoc() + * deallocates the buffers and urbs allocated during em28xx_init_iosc() + */ +void em28xx_uninit_isoc(struct em28xx *dev) +{ + int i; + for (i = 0; i < EM28XX_NUM_BUFS; i++) { + if (dev->urb[i]) { + usb_kill_urb(dev->urb[i]); + if (dev->transfer_buffer[i]) { + usb_buffer_free(dev->udev, + dev->urb[i]->transfer_buffer_length, + dev->transfer_buffer[i], + dev->urb[i]->transfer_dma); + } + usb_free_urb(dev->urb[i]); + } + dev->urb[i] = NULL; + dev->transfer_buffer[i] = NULL; + } + em28xx_capture_start(dev, 0); +} + +/* + * em28xx_init_isoc() + * allocates transfer buffers and submits the urbs for isoc transfer + */ +int em28xx_init_isoc(struct em28xx *dev) +{ + /* change interface to 3 which allowes the biggest packet sizes */ + int i, errCode; + const int sb_size = EM28XX_NUM_PACKETS * dev->max_pkt_size; + + /* reset streaming vars */ + dev->frame_current = NULL; + dev->frame_count = 0; + + dev->vbi_frame_current = NULL; + dev->vbi_frame_count = 0; + + /* allocate urbs */ + for (i = 0; i < EM28XX_NUM_BUFS; i++) { + struct urb *urb; + int j, k; + /* allocate transfer buffer */ + urb = usb_alloc_urb(EM28XX_NUM_PACKETS, GFP_KERNEL); + if (!urb) { + em28xx_errdev("cannot alloc urb %i\n", i); + em28xx_uninit_isoc(dev); + return -ENOMEM; + } + dev->transfer_buffer[i] = usb_buffer_alloc(dev->udev, + sb_size, GFP_KERNEL, &urb->transfer_dma); + if (!dev->transfer_buffer[i]) { + em28xx_errdev("unable to allocate %i bytes for " + "transfer buffer %i\n", sb_size, i); + em28xx_uninit_isoc(dev); + return -ENOMEM; + } + memset(dev->transfer_buffer[i], 0, sb_size); + urb->dev = dev->udev; + urb->context = dev; + urb->pipe = usb_rcvisocpipe(dev->udev, 0x82); + urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + urb->interval = 1; + urb->transfer_buffer = dev->transfer_buffer[i]; + urb->complete = em28xx_isocIrq; + urb->number_of_packets = EM28XX_NUM_PACKETS; + urb->transfer_buffer_length = sb_size; + for (j = k = 0; j < EM28XX_NUM_PACKETS; + j++, k += dev->max_pkt_size) { + urb->iso_frame_desc[j].offset = k; + urb->iso_frame_desc[j].length = + dev->max_pkt_size; + } + dev->urb[i] = urb; + } + + /* submit urbs */ + for (i = 0; i < EM28XX_NUM_BUFS; i++) { + errCode = usb_submit_urb(dev->urb[i], GFP_KERNEL); + if (errCode) { + em28xx_errdev("submit of urb %i failed (error=%i)\n", i, + errCode); + em28xx_uninit_isoc(dev); + return errCode; + } + } + + return 0; +} + +int em28xx_set_alternate(struct em28xx *dev) +{ + int errCode, prev_alt = dev->alt; + dev->alt = alt; + if (dev->alt == 0) { + int i; +#if 1 /* Always try to get the maximum size value */ + for (i = 0; i < dev->num_alt; i++) + if (dev->alt_max_pkt_size[i] > + dev->alt_max_pkt_size[dev->alt]) + dev->alt = i; +#endif +#if 0 /* Should be dependent of horizontal size */ + if (dev->em_type == EM2800) { + /* always use the max packet size for em2800 + based devices */ + for (i = 0; i < dev->num_alt; i++) + if (dev->alt_max_pkt_size[i] > + dev->alt_max_pkt_size[dev->alt]) + dev->alt = i; + } else { + /* FIXME: empiric magic number */ + unsigned int min_pkt_size = dev->field_size / 137; + em28xx_coredbg("minimum isoc packet size: %u", + min_pkt_size); + dev->alt = 7; + for (i = 0; i < dev->num_alt; i ++) + if (dev->alt_max_pkt_size[i] >= min_pkt_size) { + dev->alt = i; + break; + } + } +#endif + } + + if (dev->alt != prev_alt) { + dev->max_pkt_size = dev->alt_max_pkt_size[dev->alt]; + em28xx_coredbg("setting alternate %d with wMaxPacketSize=%u\n", dev->alt, + dev->max_pkt_size); + + errCode = usb_set_interface(dev->udev, dev->usb_interface, dev->alt); + if (errCode < 0) { + em28xx_errdev("cannot change alternate number to %d " + "(error=%i)\n", + dev->alt, errCode); + return errCode; + } + } + return 0; +} diff --git a/drivers/media/video/empia/em28xx-i2c.c b/drivers/media/video/empia/em28xx-i2c.c new file mode 100644 index 0000000..1278e6e --- /dev/null +++ b/drivers/media/video/empia/em28xx-i2c.c @@ -0,0 +1,983 @@ +/* + em28xx-i2c.c - driver for Empia EM2800/EM2820/2840/2880 USB + video capture devices + + Copyright (C) 2005 Ludovico Cavedon + Markus Rechberger + Mauro Carvalho Chehab + Sascha Sommer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27) +#include +#else +#include +#endif +#ifdef EM28XX_TVEEPROM +#include +#endif + +#include "em28xx.h" +#include +#include + +#include "dvb_frontend.h" + +#include "xc5000/xc5000_control.h" + + +/* ----------------------------------------------------------- */ + +static unsigned int i2c_scan; +module_param(i2c_scan, int, 0444); +MODULE_PARM_DESC(i2c_scan, "scan i2c bus at insmod time"); + +static unsigned int i2c_debug = 1; +module_param(i2c_debug, int, 0644); +MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]"); + +#define dprintk1(lvl, fmt, args...) if (i2c_debug >= lvl) do {\ + printk(fmt, ##args); } while (0) +#define dprintk2(lvl, fmt, args...) if (i2c_debug >= lvl) do { \ + printk(KERN_DEBUG "%s at %s: " fmt, \ + dev->name, __func__, ##args); } while (0) + +/* + * em2800_i2c_send_max4() + * send up to 4 bytes to the i2c device + */ +static int em2800_i2c_send_max4(struct em28xx *dev, unsigned char addr, + char *buf, int len) +{ + int ret; + int write_timeout; + unsigned char b2[6]; + BUG_ON(len < 1 || len > 4); + b2[5] = 0x80 + len - 1; + b2[4] = addr; + b2[3] = buf[0]; + if (len > 1) + b2[2] = buf[1]; + if (len > 2) + b2[1] = buf[2]; + if (len > 3) + b2[0] = buf[3]; + + ret = dev->em28xx_write_regs(dev, 4 - len, &b2[4 - len], 2 + len); + if (ret < 0) { + dprintk1(2, "%s:%u:%s(): FIXME: em28xx_write_regs() failed," + "ret = %i\n", __FILE__, __LINE__, __func__, ret); + return ret; + } + if (ret != 2 + len) { + em28xx_warn("writing to i2c device failed (error=%i)\n", ret); + return -EIO; + } + for (write_timeout = EM2800_I2C_WRITE_TIMEOUT; write_timeout > 0; + write_timeout -= 5) { + ret = dev->em28xx_read_reg(dev, 0x05); + if (ret < 0) { + dprintk1(2, "%s:%u:%s(): FIXME: em28xx_read_reg()" + "failed, ret = %i\n", __FILE__, __LINE__, + __func__, ret); + return ret; + } + if (ret == 0x80 + len - 1) + return len; + msleep(5); + } + em28xx_warn("i2c write timed out\n"); + return -EIO; +} + +int em28xx_gpio_cmd(struct em28xx *dev, unsigned int command, u16 *value, + unsigned int *len) +{ + struct em28xx_gpio *gpio_map = &em28xx_boards[dev->model].gpio_regs; + switch (command) { + case EM28XX_ANALOG_ON: + *value = gpio_map->a_on; + break; + case EM28XX_LED1_ON: + *value = gpio_map->l1_on; + break; + case EM28XX_XC3028_SECAM: + *value = gpio_map->xc3028_sec; + break; + case EM28XX_TS1_ON: + *value = gpio_map->ts1_on; + break; + case EM28XX_MODESWITCH: + *value = gpio_map->m_switch; + break; + case EM28XX_DECODER_SLEEP: + *value = gpio_map->d_sleep; + break; + case EM28XX_LED2_ON: + *value = gpio_map->l2_on; + break; + case EM28XX_RF: + *value = gpio_map->rf; + break; + case EM28XX_DVB1_ON: + *value = gpio_map->dvbs_lnb; + break; + case EM28XX_DVB2_ON: + *value = gpio_map->dvbs_v; + break; + case EM28XX_TUNER1_ON: + *value = gpio_map->t1_on; + break; + case EM28XX_DEMOD1_RESET: + *value = gpio_map->d1_reset; + break; + case EM28XX_TUNER1_RESET: + *value = gpio_map->t1_reset; + break; + case EM28XX_DECODER_RESET: + *value = gpio_map->dc_reset; + break; + case EM28XX_DEMOD2_RESET: + *value = gpio_map->d2_reset; + break; + case EM28XX_TUNER2_RESET: + *value = gpio_map->t2_reset; + break; + case EM28XX_TUNER2_ON: + *value = gpio_map->t2_on; + break; + default: + break; + } + return 0; +} + +int em28xx_gpio_control_translate(void *priv, unsigned int command, void *ptr) +{ + int cmd; + switch (command) { + case XC3028_CHIP_RESET: + cmd = EM28XX_TUNER1_RESET; + break; + case XC5000_CHIP_RESET: + cmd = EM28XX_TUNER1_RESET; + break; + default: + printk("unknown gpio translate command\n"); + break; + } + + return em28xx_gpio_control(priv, cmd, ptr); +} + +int em28xx_gpio_control(void *priv, unsigned int command, void *ptr) +{ + struct em28xx *dev = (struct em28xx *)priv; + unsigned int len; + u16 gpio_value; + u8 buf[2]; + u8 *index; + u8 gpio; + int *arg = ptr; + u16 gpio_reg; + u8 eeprom_offset; + + if (em28xx_boards[dev->model].manual_gpio) + em28xx_gpio_cmd(dev, command, &gpio_value, &len); + else { + switch (dev->em_type) { + case EM2888: + case EM2889: + case EM2875: + eeprom_offset = 0xa0; + buf[0] = 0; + index = &buf[1]; + len = 2; + dev->em28xx_write_regs(dev, 0x06, "\x40", 1); + break; + case EM2883: + default: + eeprom_offset = 0x3c; + index = &buf[0]; + len = 1; + break; + } + + switch (command) { + case EM28XX_ANALOG_ON: + *index = eeprom_offset + 24; + em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len); + gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0); + break; + case EM28XX_TS1_ON: + *index = eeprom_offset + 25; + em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len); + gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0); + break; + case EM28XX_DECODER_SLEEP: + *index = eeprom_offset + 20; + em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len); + gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0); + break; + case EM28XX_DEMOD1_RESET: + *index = eeprom_offset + 16; + em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len); + gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0); + break; + case EM28XX_TUNER1_RESET: + *index = eeprom_offset + 17; + em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len); + gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0); + break; + case EM28XX_TUNER1_ON: + *index = eeprom_offset + 23; + em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len); + gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0); + break; + case EM28XX_LED1_ON: + printk("reading led\n"); + *index = eeprom_offset + 22; + em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len); + gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0); + printk("led returned: %02x\n", gpio_value); + break; + default: + switch (dev->em_type) { + case EM2888: + case EM2889: + case EM2875: + dev->em28xx_write_regs(dev, 0x06, "\x45", 1); + break; + default: + break; + } + return -EINVAL; + } + switch (dev->em_type) { + case EM2888: + case EM2875: + case EM2889: + dev->em28xx_write_regs(dev, 0x06, "\x45", 1); + break; + case EM2883: + default: + eeprom_offset = 0x3b; + index = &buf[0]; + len = 1; + break; + } + } + + /* check if gpio register enabled */ + if (gpio_value & 0x80) { + if (((gpio_value >> 6)&1) == 0) { + if (arg == NULL) { + printk(KERN_INFO"no argument given %02x\n", + gpio_value); + return -EINVAL; + } + /* on/off command */ + switch (dev->em_type) { + case EM2888: + case EM2889: + case EM2875: + gpio_reg = 0x80; + break; + case EM2883: + default: + gpio_reg = gpio_value&0x10 ? 0x04 : 0x08; + break; + } + gpio = dev->em28xx_read_reg(dev, gpio_reg); + gpio &= ~((u8)(1 << (gpio_value&0x7))); + + if (*arg == EM28XX_REG_ON) + gpio |= ((gpio_value >> 5)&1) << + (gpio_value&7); + else + gpio |= (((gpio_value >> 5)&1)^1) << + (gpio_value&7); + + dev->em28xx_write_regs(dev, gpio_reg, &gpio, 1); + dprintk1(2, "(1) writing to %02x -> %02x\n", gpio_reg, gpio); + } else { + switch (dev->em_type) { + case EM2889: + case EM2888: + case EM2875: + gpio_reg = 0x80; + break; + case EM2883: + default: + gpio_reg = gpio_value&0x10 ? 0x04 : 0x08; + break; + } + gpio = dev->em28xx_read_reg(dev, gpio_reg); + gpio &= ~((u8)(1 << (gpio_value&0xf))); + + gpio |= ((gpio_value >> 5)&1) << (gpio_value&7); + dev->em28xx_write_regs(dev, gpio_reg, &gpio, 1); + dprintk1(2, "(2) writing to %02x -> %02x\n", gpio_reg, gpio); + gpio &= ~((u8)(1 << (gpio_value&0xf))); + gpio |= (((gpio_value >> 5)&1)^1) << (gpio_value&7); + mdelay(100); + dev->em28xx_write_regs(dev, gpio_reg, &gpio, 1); + mdelay(100); + dprintk1(2, "(3) writing to %02x -> %02x\n", gpio_reg, gpio); + } + } else { + printk("register disabled\n"); + } + + return 0; +} + +static int em2800_i2c_send_bytes(void *data, unsigned char addr, char *buf, + short len) +{ + char *bufPtr = buf; + int ret; + int wrcount = 0; + int count; + int maxLen = 4; + struct em28xx *dev = (struct em28xx *)data; + while (len > 0) { + count = (len > maxLen) ? maxLen : len; + ret = em2800_i2c_send_max4(dev, addr, bufPtr, count); + if (ret > 0) { + len -= count; + bufPtr += count; + wrcount += count; + } else + return (ret < 0) ? ret : -EFAULT; + } + return wrcount; +} + +/* + * em2800_i2c_check_for_device() + * check if there is a i2c_device at the supplied address + */ +int em2800_i2c_check_for_device(struct em28xx *dev, unsigned char addr) +{ + char msg; + int ret; + int write_timeout; + msg = addr; + ret = dev->em28xx_write_regs(dev, 0x04, &msg, 1); + if (ret < 0) { + em28xx_warn("setting i2c device address failed (error=%i)\n", + ret); + return ret; + } + msg = 0x84; + ret = dev->em28xx_write_regs(dev, 0x05, &msg, 1); + if (ret < 0) { + em28xx_warn("preparing i2c read failed (error=%i)\n", ret); + return ret; + } + for (write_timeout = EM2800_I2C_WRITE_TIMEOUT; write_timeout > 0; + write_timeout -= 5) { + unsigned int msg; + ret = dev->em28xx_read_reg(dev, 0x5); + if (ret < 0) { + dprintk1(2, "%s:%u:%s(): FIXME: em28xx_read_reg() " + "failed, gpval = %i\n", __FILE__, __LINE__, + __func__, ret); + return ret; + } + msg = ret; + if (msg == 0x94) + return -ENODEV; + else if (msg == 0x84) + return 0; + msleep(5); + } + return -ENODEV; +} + +/* + * em2800_i2c_recv_bytes() + * read from the i2c device + */ +static int em2800_i2c_recv_bytes(struct em28xx *dev, unsigned char addr, + char *buf, int len) +{ + int ret; + /* check for the device and set i2c read address */ + ret = em2800_i2c_check_for_device(dev, addr); + if (ret) { + em28xx_warn + ("preparing read at i2c address 0x%x failed (error=%i)\n", + addr, ret); + return ret; + } + ret = dev->em28xx_read_reg_req_len(dev, 0x0, 0x3, buf, len); + if (ret < 0) { + em28xx_warn("reading from i2c device at 0x%x failed (error=%i)", + addr, ret); + return ret; + } + return ret; +} + +/* + * em28xx_i2c_send_bytes() + * untested for more than 4 bytes + */ +static int em28xx_i2c_send_bytes(void *data, unsigned char addr, char *buf, + short len, int stop) +{ + int wrcount = 0; + struct em28xx *dev = (struct em28xx *)data; + int i; + + wrcount = dev->em28xx_write_regs_req(dev, stop ? 2 : 3, addr, buf, len); + if (wrcount < 0) { + dprintk1(2, "%s:%u:%s(): FIXME: em28xx_write_regs_req()" + "failed, wrcount = %i\n", __FILE__, __LINE__, + __func__, wrcount); + + return wrcount; + } + + if (dev->em28xx_read_reg(dev, 0x5) != 0) { + printk(KERN_ERR"FIXME:em28xx_i2c_send_bytes(%02x): " + "write failed:\n", addr); + printk(KERN_ERR"===============================\n"); + for (i = 0; i < len; i++) + printk("%02x ", (unsigned char)buf[i]); + + printk("\n"); + printk(KERN_ERR"================================\n"); + } + + return wrcount; +} + +/* + * em28xx_i2c_recv_bytes() + * read a byte from the i2c device + */ +static int em28xx_i2c_recv_bytes(struct em28xx *dev, unsigned char addr, + char *buf, int len) +{ + int ret; + ret = dev->em28xx_read_reg_req_len(dev, 2, addr, buf, len); + if (ret < 0) { + em28xx_warn("reading i2c device failed (error=%i)\n", ret); + return ret; + } + if (dev->em28xx_read_reg(dev, 0x5) != 0) + return -ENODEV; + return ret; +} + +/* + * em28xx_i2c_check_for_device() + * check if there is a i2c_device at the supplied address + */ +static int em28xx_i2c_check_for_device(struct em28xx *dev, unsigned char addr) +{ + char msg; + int ret; + msg = addr; + + ret = dev->em28xx_read_reg_req(dev, 2, addr); + if (ret < 0) { + em28xx_warn("reading from i2c device failed (error=%i)\n", ret); + return ret; + } + if (dev->em28xx_read_reg(dev, 0x5) != 0) + return -ENODEV; + return 0; +} + +/* + * em28xx_i2c_xfer() + * the main i2c transfer function + */ +static int em28xx_i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg msgs[], int num) +{ + struct em28xx *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; + dprintk2(2, "%s %s addr=%x len=%d:", + (msgs[i].flags & I2C_M_RD) ? "read" : "write", + i == num - 1 ? "stop" : "nonstop", addr, msgs[i].len); + /* no len: check only for device presence */ + if (!msgs[i].len) { + if (dev->em_type == EM2800) + rc = em2800_i2c_check_for_device(dev, addr); + else + rc = em28xx_i2c_check_for_device(dev, addr); + if (rc < 0) { + dprintk2(2, " no device\n"); + return rc; + } + + } else if (msgs[i].flags & I2C_M_RD) { + /* read bytes */ + if (dev->em_type == EM2800) + rc = em2800_i2c_recv_bytes(dev, addr, + msgs[i].buf, + msgs[i].len); + else + rc = em28xx_i2c_recv_bytes(dev, addr, + msgs[i].buf, + msgs[i].len); + if (i2c_debug >= 2) { + for (byte = 0; byte < msgs[i].len; byte++) + printk(KERN_INFO" %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]); + } + if (dev->em_type == EM2800) + rc = em2800_i2c_send_bytes(dev, addr, + msgs[i].buf, + msgs[i].len); + else + rc = em28xx_i2c_send_bytes(dev, addr, + msgs[i].buf, + msgs[i].len, + i == num - 1); + } + if (rc < 0) + goto err; + if (i2c_debug >= 2) + printk("\n"); + } + + return num; +err: + dprintk2(2, " ERROR: %i\n", rc); + return rc; +} + +static int em28xx_i2c_eeprom(struct em28xx *dev, struct i2c_client *client, + unsigned char *eedata, int len) +{ + unsigned char buf, *p = eedata; + struct em28xx_eeprom *em_eeprom = (void *)eedata; + int i, err, size = len, block; + + dev->i2c_client.addr = 0xa0 >> 1; + + /* Check if board has eeprom */ + err = i2c_master_recv(client, &buf, 0); + if (err < 0) + return -1; + + buf = 0; + + err = i2c_master_send(client, &buf, 1); + if (1 != err) { + printk(KERN_INFO "%s: Huh, no eeprom present (err=%d)?\n", + dev->name, err); + return -1; + } + + while (size > 0) { + if (size > 16) + block = 16; + else + block = size; + + if (block != + (err = i2c_master_recv(client, p, block))) { + printk(KERN_WARNING + "%s: i2c eeprom read error (err=%d)\n", + dev->name, err); + return -1; + } + size -= block; + p += block; + } + for (i = 0; i < len; i++) { + if (0 == (i % 16)) + printk(KERN_INFO "%s: i2c eeprom %02x:", + dev->name, i); + printk(" %02x", eedata[i]); + + if (15 == (i % 16)) + printk("\n"); + } + + printk(KERN_INFO "EEPROM ID= 0x%08x\n", em_eeprom->id); + printk(KERN_INFO "Vendor/Product ID= %04x:%04x\n", + em_eeprom->vendor_ID, em_eeprom->product_ID); + + switch (em_eeprom->chip_conf >> 4 & 0x3) { + case 0: + printk(KERN_INFO "No audio on board.\n"); + break; + case 1: + printk(KERN_INFO "AC97 audio (5 sample rates)\n"); + break; + case 2: + printk(KERN_INFO "I2S audio, sample rate=32k\n"); + break; + case 3: + printk(KERN_INFO "I2S audio, 3 sample rates\n"); + break; + } + + if (em_eeprom->chip_conf & 1 << 3) + printk(KERN_INFO "USB Remote wakeup capable\n"); + + if (em_eeprom->chip_conf & 1 << 2) + printk(KERN_INFO "USB Self power capable\n"); + + switch (em_eeprom->chip_conf & 0x3) { + case 0: + printk(KERN_INFO "500mA max power\n"); + break; + case 1: + printk(KERN_INFO "400mA max power\n"); + break; + case 2: + printk(KERN_INFO "300mA max power\n"); + break; + case 3: + printk(KERN_INFO "200mA max power\n"); + break; + } + printk(KERN_INFO "Table at 0x%02x, strings = 0x%04x, 0x%04x, 0x%04x\n", + em_eeprom->string_idx_table, + em_eeprom->string1, + em_eeprom->string2, + em_eeprom->string3); + + 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) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) + MOD_INC_USE_COUNT; +#endif +} + +static void dec_use(struct i2c_adapter *adap) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) + MOD_DEC_USE_COUNT; +#endif +} +#endif + +static int em28xx_set_tuner(int check_eeprom, struct i2c_client *client) +{ + struct em28xx *dev = client->adapter->algo_data; + struct tuner_setup tun_setup; + + if (dev->has_inttuner == 0 && dev->dev_modes != EM28XX_DVBT) { + /* do not set up a tuner if it's a dvb only device */ + tun_setup.mode_mask = T_ANALOG_TV | T_RADIO; + tun_setup.type = dev->tuner_type; + tun_setup.addr = dev->tuner_addr; + em28xx_i2c_call_clients(dev, TUNER_SET_TYPE_ADDR, &tun_setup); + } else { + tun_setup.mode_mask = T_UNINITIALIZED; + tun_setup.type = TUNER_ABSENT; + tun_setup.addr = dev->tuner_addr; + em28xx_i2c_call_clients(dev, TUNER_SET_TYPE_ADDR, &tun_setup); + } + + return 0; +} + +/* + * attach_inform() + * gets called when a device attaches to the i2c bus + * does some basic configuration + */ +static int attach_inform(struct i2c_client *client) +{ + struct em28xx *dev = client->adapter->algo_data; + int ret; + + if (client->driver->id == I2C_DRIVERID_TUNER && dev->has_inttuner==1) { + printk(KERN_INFO"em28xx-i2c: using internal tuner, denying " + "request to i2c tuner module.\n"); + em28xx_set_tuner(-1, client); + return 0; + } + + switch (client->addr << 1) { + case 0x86: + case 0x84: + case 0x96: + case 0x94: + { +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17) +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25) + struct v4l2_priv_tun_config tda9887_cfg; +#endif + + struct tuner_setup tun_setup; + + tun_setup.mode_mask = T_ANALOG_TV | T_RADIO; + tun_setup.type = TUNER_TDA9887; + tun_setup.addr = client->addr; + + em28xx_i2c_call_clients(dev, TUNER_SET_TYPE_ADDR, &tun_setup); + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25) + tda9887_cfg.tuner = TUNER_TDA9887; + tda9887_cfg.priv = &dev->tda9887_conf; + em28xx_i2c_call_clients(dev, TUNER_SET_CONFIG, &tda9887_cfg); +#endif +#endif + break; + } + case 0x42: + dprintk1(1, "attach_inform: saa7114 detected.\n"); + break; + case 0x4a: + dprintk1(1, "attach_inform: saa7113 detected.\n"); + break; + case 0xa0: + dprintk1(1, "attach_inform: eeprom detected.\n"); + em28xx_i2c_eeprom(dev, client, dev->eedata, + sizeof(dev->eedata)); + + switch (dev->model) { + case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2: + case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2_R2: + case EM2870_BOARD_HAUPPAUGE_WINTV_USB_2_R2: + case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900: + case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950: + case EM2883_BOARD_KWORLD_HYBRID_A316: + case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2: + { +#ifdef EM28XX_TVEEPROM + struct tveeprom tv; + tveeprom_hauppauge_analog(client, &tv, dev->eedata); + + if (dev->tuner_type == TUNER_ABSENT) { + dev->tuner_type = tv.tuner_type; + printk(KERN_INFO"setting new tuner type now" + "%d!\n", tv.tuner_type); + em28xx_set_tuner(-1, client); + } +#if defined V4L2_IDENT_MSPX4XX /* XXX 2.6.27 */ + if (tv.audio_processor == V4L2_IDENT_MSPX4XX) { +#else + if (tv.audio_processor == AUDIO_CHIP_MSP34XX) { +#endif + dev->i2s_speed = 2048000; + dev->has_msp34xx = 1; + } else + dev->has_msp34xx = 0; +#endif + + if (dev->has_msp34xx) { + /* Send a reset to other chips via gpio */ + ret = em28xx_write_regs_req(dev, 0x00, 0x08, + "\xf7", 1); + if (ret < 0) { + dprintk1(2, "%s:%u:%s(): FIXME: " + "em28xx_write_regs_req() " + "failed, ret = %i\n", __FILE__, + __LINE__, __func__, ret); + return ret; + } + udelay(2500); + ret = em28xx_write_regs_req(dev, 0x00, 0x08, + "\xff", 1); + if (ret < 0) { + dprintk1(2, "%s:%u:%s(): FIXME: " + "em28xx_write_regs_req() " + "failed, ret = %i\n", __FILE__, + __LINE__, __func__, ret); + return ret; + } + udelay(2500); + } + } + break; + } + case 0x60: + case 0x8e: + { + struct IR_i2c *ir = i2c_get_clientdata(client); + em28xx_set_ir(dev, ir); + break; + } + case 0x80: + case 0x88: + dprintk1(1, "attach_inform: msp34xx/cx25843 detected.\n"); + break; + case 0xb8: + case 0xba: + dprintk1(1, "attach_inform: tvp5150 detected.\n"); + break; + case 0x1e: + dprintk1(1, "zl10353 demodulator found!\n"); + break; + default: + dprintk1(1, "attach inform (default): " + "detected I2C address %x\n", client->addr << 1); + dev->tuner_addr = client->addr; + em28xx_set_tuner(-1, client); + } + return 0; +} + +static struct i2c_algorithm em28xx_algo = { + .master_xfer = em28xx_i2c_xfer, + .functionality = functionality, +}; + +static struct i2c_adapter em28xx_adap_template = { +#ifdef I2C_PEC + .owner = THIS_MODULE, +#else +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24) + .inc_use = inc_use, + .dec_use = dec_use, +#endif +#endif +#ifdef I2C_CLASS_TV_ANALOG + .class = I2C_CLASS_TV_ANALOG, +#endif + .name = "em28xx", + .id = I2C_HW_B_EM28XX, + .algo = &em28xx_algo, + .client_register = attach_inform, +}; + +static struct i2c_client em28xx_client_template = { + .name = "em28xx internal", +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 15) + .flags = I2C_CLIENT_ALLOW_USE, +#endif +}; + +/* ----------------------------------------------------------- */ + +/* + * i2c_devs + * incomplete list of known devices + */ +static char *i2c_devs[128] = { + [0x4a >> 1] = "saa7113h", + [0x60 >> 1] = "remote IR sensor", + [0x8e >> 1] = "remote IR sensor", + [0x86 >> 1] = "tda9887", + [0x80 >> 1] = "msp34xx", + [0x88 >> 1] = "msp34xx/cx25843", + [0xa0 >> 1] = "eeprom", + [0xb8 >> 1] = "tvp5150a", + [0xba >> 1] = "tvp5150a", + [0xc0 >> 1] = "tuner (analog)", + [0xc2 >> 1] = "tuner (analog)", + [0xc4 >> 1] = "tuner (analog)", + [0xc6 >> 1] = "tuner (analog)", + [0x1e >> 1] = "zl10353/mt352 dvb-t demodulator", +}; + +/* + * 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 < ARRAY_SIZE(i2c_devs); 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] : "???"); + } +} + +/* + * em28xx_i2c_call_clients() + * send commands to all attached i2c devices + */ +void em28xx_i2c_call_clients(struct em28xx *dev, unsigned int cmd, void *arg) +{ + BUG_ON(NULL == dev->i2c_adap.algo_data); + i2c_clients_command(&dev->i2c_adap, cmd, arg); +} +EXPORT_SYMBOL(em28xx_i2c_call_clients); + +/* + * em28xx_i2c_register() + * register i2c bus + */ +int em28xx_i2c_register(struct em28xx *dev) +{ + BUG_ON(!dev->em28xx_write_regs || !dev->em28xx_read_reg); + BUG_ON(!dev->em28xx_write_regs_req || !dev->em28xx_read_reg_req); + dev->i2c_adap = em28xx_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 = em28xx_client_template; + dev->i2c_client.adapter = &dev->i2c_adap; + + if (i2c_scan || dev->dev_modes == 0) + do_i2c_scan(dev->name, &dev->i2c_client); + return 0; +} + +/* + * em28xx_i2c_unregister() + * unregister i2c_bus + */ +int em28xx_i2c_unregister(struct em28xx *dev) +{ + i2c_del_adapter(&dev->i2c_adap); + return 0; +} + diff --git a/drivers/media/video/empia/em28xx-input.c b/drivers/media/video/empia/em28xx-input.c new file mode 100644 index 0000000..a0fcba4 --- /dev/null +++ b/drivers/media/video/empia/em28xx-input.c @@ -0,0 +1,536 @@ +/* + handle em28xx IR remotes via linux kernel input layer. + + Copyright (C) 2005-2007 Markus Rechberger + 2005 Mauro Carvalho Chehab + 2005 Sascha Sommer + + + There have been many issues with this input handling routines, starting + from crashing the box, printing random characters or blocking the + keyboard when loading it etc. (those issues were caused because it used + the global runqueue with the asynchronous module mechanism). + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "em28xx.h" +#include "em28xx-keymaps.h" + +static unsigned int disable_ir; +module_param(disable_ir, int, 0444); +MODULE_PARM_DESC(disable_ir, "disable infrared remote support"); + +static unsigned int ir_debug; +module_param(ir_debug, int, 0644); +MODULE_PARM_DESC(ir_debug, "enable debug messages [IR]"); + +#define dprintk(fmt, arg...) if (ir_debug) \ + printk(KERN_DEBUG"%s/ir: " fmt, ir->c.name , ## arg) + +/* ----------------------------------------------------------------------- */ + +static unsigned int get_timestamp(void) +{ + struct timeval tm; + do_gettimeofday(&tm); + return (unsigned int)(tm.tv_sec*1000 + (tm.tv_usec /1000)); +} + +int em2888_get_key_empia(struct em28xx *dev, u32 *ir_key, u32 *keystatus) +{ + struct em2880_ir *ir = dev->ir_em2880; + u8 buf[5]; + static u8 dropkey; + static u8 repeatdelay; + int retval = 0; + unsigned int timeoutval; + + /* this algorithm works best with 3-5 ms polling + 10ms in userspace */ + + msleep(3); + + + dev->em28xx_read_reg_req_len(dev, 0, 0x50, buf, 5); + + timeoutval = get_timestamp(); + + if ((buf[0] != 0x11 || ir->btn != buf[1]) && dev->board->ir_keytab[buf[4]] != 0) { + if ((ir->btn != buf[1] && ir->released == 1) || ir->key != buf[4]) { + +#if 0 + if (ir->key != buffer[4] && ir->released == 0) + printk("old key got released\n"); +#endif + + *ir_key = buf[4]; + ir->released = 0; + ir->oldval = timeoutval; + ir->key = buf[4]; + retval = 1; + + /* volume should be repeated faster than other keys */ + + if (ir->keyboard->ir_keytab[ir->key] == KEY_VOLUMEUP || + dev->board->ir_keytab[ir->key] == KEY_VOLUMEDOWN)) { + dropkey = 1; + repeatdelay = 10; + } else { + dropkey = 3; + repeatdelay = 100; + } + } + + if (timeoutval - ir->oldval > repeatdelay && ir->released == 0) { + ir->oldval = timeoutval; + *ir_key = ir->key; + if (dropkey) { + retval = 0; + dropkey--; + } else + retval = 1; + } + ir->oldtimeoutval = 0; + } else { + if (timeoutval - ir->oldtimeoutval > 150 && ir->oldtimeoutval > 0 && ir->released == 0) { + ir->released = 1; + retval = 0; + } + if (ir->oldtimeoutval == 0) + ir->oldtimeoutval = timeoutval; + retval = 0; + } + ir->btn = buf[1]; + return retval; +} + +/* get_key for terratec's devices */ + +int em2880_get_key_terratec(struct em28xx *dev, u32 *ir_key, u32 *keystatus) +{ + int rc; + int irc = 0; + + msleep(50); + + rc = em28xx_read_reg_req(dev, 0x0, 0x45); + if (rc < 0) + return -1; + else + irc = rc; + + rc = em28xx_read_reg_req(dev, 0x0, 0x47); + if (rc < 0) + return -1; + else + *ir_key = rc; + + dev->ir_em2880->sequence[0] = dev->ir_em2880->sequence[1]; + dev->ir_em2880->sequence[1] = dev->ir_em2880->sequence[2]; + dev->ir_em2880->sequence[2] = irc; + if (dev->ir_em2880->sequence[0] != dev->ir_em2880->sequence[1] && + dev->ir_em2880->sequence[1] != dev->ir_em2880->sequence[2]) + return 1; + else + return 0; +} + +/* get_key for pinnacle's devices */ +/* TODO: this is just a fast implementation ... */ + +int em2880_get_key_empia(struct em28xx *dev, u32 *ir_key, u32 *keystatus) +{ + char buf[4]; + int ret; + + msleep(50); + + ret = dev->em28xx_read_reg_req_len(dev, 0x0, 0x45, buf, 4); + + if (ret < 0) { + switch (ret) { + case -ENODEV: + /* Device was disconnected */ + em28xx_warn("reading key failed " + "(error=%i=-ENODEV)\n", ret); + return -1; + default: + em28xx_warn("reading key failed (error=%i)\n", ret); + return -1; + } + } + + dev->ir_em2880->sequence[0] = dev->ir_em2880->sequence[1]; + dev->ir_em2880->sequence[1] = dev->ir_em2880->sequence[2]; + dev->ir_em2880->sequence[2] = buf[0]&1; + *ir_key = buf[2]; + if ((dev->ir_em2880->sequence[0] == 1 && + dev->ir_em2880->sequence[1] == 0 && + dev->ir_em2880->sequence[2] == 1) || + (dev->ir_em2880->sequence[0] == 0 && + dev->ir_em2880->sequence[1] == 1 && + dev->ir_em2880->sequence[2] == 0)) + return 1; + else + return 0; +} + +static int em2880_ir_key_poll(struct em28xx *dev) +{ + struct em2880_ir *ir = dev->ir_em2880; + static u32 ir_key, keystatus; + u32 keycode; + static u32 old_keycode; + int rc; + rc = ir->get_key(dev, &ir_key, &keystatus); + switch (rc) { + case 0: + if (keystatus == 1) { + if (old_keycode == 0) + return -EINVAL; + input_report_key(ir->input, old_keycode, 0); + keystatus = 0; + } + break; + case -1: + keystatus = 0; + return rc; + default: + /* keydown */ + keycode = IR_KEYCODE(ir->keymap, ir_key); + if (keycode == 0) + return -EINVAL; + input_report_key(ir->input, keycode, 1); + old_keycode = keycode; + keystatus = 1; + } + return 0; + +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) +static void em2880_ir_timer(unsigned long data) +{ + struct em2880_ir *ir = ((struct em28xx *)data)->ir_em2880; + schedule_work(&ir->work); +} +#endif + + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) +static void em28xx_ir_work(void *data) +{ + struct em28xx *dev = (struct em28xx *)data; + struct em2880_ir *ir = dev->ir_em2880; + int rc; +#else +static void em28xx_ir_work(struct work_struct *work) +{ + struct em2880_ir *ir = container_of(work, struct em2880_ir, work.work); + struct em28xx *dev = ir->dev; + int rc; +#endif + if (ir->state == EM28XX_REMOTE_POLLING) { + rc = em2880_ir_key_poll(dev); + if (rc != 0) { + ir->state = EM28XX_REMOTE_IDLE; + return; + } + mutex_lock(&ir->state_lock); + if (ir->state == EM28XX_REMOTE_POLLING) +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) + mod_timer(&dev->ir_em2880->timer, jiffies+msecs_to_jiffies(50)); +#else + schedule_delayed_work(&dev->ir_em2880->work, msecs_to_jiffies(50)); +#endif + mutex_unlock(&ir->state_lock); + } +} + +int em2880_ir_detach(struct em28xx *dev) +{ +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17) + struct em2880_ir *ir = dev->ir_em2880; + + return 0; + mutex_lock(&dev->input_lock); + if (ir == NULL) { + printk(KERN_INFO"em28xx-input.c: ir==NULL, skipping" + "em2880_ir_detach()\n"); + mutex_unlock(&dev->input_lock); + return 0; + } + + mutex_lock(&ir->state_lock); + ir->state = EM28XX_REMOTE_INTERRUPT; + mutex_unlock(&ir->state_lock); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) + del_timer_sync(&ir->timer); +#else + if (delayed_work_pending(&ir->work)) + cancel_rearming_delayed_work(&ir->work); +#endif + + input_unregister_device(ir->input); + kfree(dev->ir_em2880); + dev->ir_em2880 = NULL; + printk(KERN_INFO"em28xx-input.c: remote control handler detached\n"); + mutex_unlock(&dev->input_lock); +#endif + return 0; +} + +int em2880_ir_attach(struct em28xx *dev) +{ +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17) + struct em2880_ir *ir; + struct input_dev *input_dev; + char buf[5]; + int i; + + /* DISABLED this remote control support is broken by design + upcoming support will be moved to userspace plus interrupt + triggering support has to be used here, there are alot problems + with polling at a high interval, where I already think the timer + API is still not bugfree */ + return 0; + + mutex_lock(&dev->input_lock); + if (dev->ir_em2880) { + mutex_unlock(&dev->input_lock); + printk(KERN_INFO"RC Handler already registered\n"); + return 0; + } + dev->ir_em2880 = kzalloc(sizeof(struct em2880_ir), GFP_KERNEL); + ir = dev->ir_em2880; + ir->keymap = dev->board->ir_keytab; + ir->get_key = dev->board->ir_getkey; + ir->dev = dev; + mutex_init(&ir->state_lock); + init_waitqueue_head(&ir->remote_loop); + input_dev = input_allocate_device(); + ir->input = input_dev; + input_dev->id.bustype = BUS_USB; + sprintf(ir->name, "em2880/em2870 remote control"); + sprintf(ir->phys, "USB"); + input_dev->name = ir->name; + input_dev->phys = ir->phys; /* FIXME: this is the wrong entry here, + some applications depend on it */ + input_dev->keycode = ir->keymap; + for (i = 0; i < IR_KEYTAB_SIZE; i++) + set_bit(ir->keymap[i], input_dev->keybit); + + input_dev->keycodesize = sizeof(IR_KEYTAB_TYPE); + input_dev->keycodemax = IR_KEYTAB_SIZE; + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + + input_register_device(ir->input); + + ir->state = EM28XX_REMOTE_POLLING; + + if (dev->board->ir_getkey == em2888_get_key_empia) { + dev->em28xx_read_reg_req_len(dev, 0, 0x50, buf, 5); + ir->key = buf[4]; + ir->oldval = get_timestamp(); + ir->btn = buf[1]; + ir->released = 1; + } + + INIT_DELAYED_WORK(&ir->work, em28xx_ir_work); + schedule_delayed_work(&ir->work, msecs_to_jiffies(50)); + printk(KERN_INFO"em28xx-input.c: remote control handler attached\n"); + mutex_unlock(&dev->input_lock); +#endif + return 0; +} + + +static int get_key_terratec(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) +{ + unsigned char b; + + /* poll IR chip */ + if (1 != i2c_master_recv(&ir->c, &b, 1)) { + dprintk("read error\n"); + return -EIO; + } + + /* it seems that 0xFE indicates that a button is still hold + down, while 0xff indicates that no button is hold + down. 0xfe sequences are sometimes interrupted by 0xFF */ + + dprintk("key %02x\n", b); + + if (b == 0xff) + return 0; + + if (b == 0xfe) + /* keep old data */ + return 1; + + *ir_key = b; + *ir_raw = b; + return 1; +} + + +static int get_key_em_haup(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) +{ + unsigned char buf[2]; + unsigned char code; + + /* poll IR chip */ + if (2 != i2c_master_recv(&ir->c, buf, 2)) + return -EIO; + + /* Does eliminate repeated parity code */ + if (buf[1] == 0xff) + return 0; + + ir->old = buf[1]; + + /* Rearranges bits to the right order */ + code = ((buf[0]&0x01)<<5) | /* 0010 0000 */ + ((buf[0]&0x02)<<3) | /* 0001 0000 */ + ((buf[0]&0x04)<<1) | /* 0000 1000 */ + ((buf[0]&0x08)>>1) | /* 0000 0100 */ + ((buf[0]&0x10)>>3) | /* 0000 0010 */ + ((buf[0]&0x20)>>5); /* 0000 0001 */ + + dprintk("ir hauppauge (em2840): code=0x%02x (rcv=0x%02x)\n", + code, buf[0]); + + /* return key */ + *ir_key = code; + *ir_raw = code; + return 1; +} + +static int get_key_pinnacle_usb(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) +{ + unsigned char buf[3]; + + /* poll IR chip */ + + if (3 != i2c_master_recv(&ir->c, buf, 3)) { + dprintk("read error\n"); + return -EIO; + } + + dprintk("key %02x\n", buf[2]&0x3f); + if (buf[0] != 0x00) { + return 0; + } + + *ir_key = buf[2]&0x3f; + *ir_raw = buf[2]&0x3f; + + return 1; +} + +int em2860_get_key_kaiomy(struct em28xx *dev, u32 *ir_key, u32 *keystatus) +{ + char buf[4]; + int ret; + + msleep(50); + + ret = dev->em28xx_read_reg_req_len(dev, 0x0, 0x45, buf, 4); + + if (ret < 0) { + switch (ret) { + case -ENODEV: + /* Device was disconnected */ + em28xx_warn("reading key failed " + "(error=%i=-ENODEV)\n", ret); + return -1; + default: + em28xx_warn("reading key failed (error=%i)\n", ret); + return -1; + } + } + + ret = em28xx_read_reg_req(dev, 0x0, 0x44); + if (ret < 0) + return -1; + + dev->ir_em2880->sequence[0] = dev->ir_em2880->sequence[1]&0x80; + dev->ir_em2880->sequence[1] = buf[0]; + *ir_key = buf[2]; + return (buf[0]&0x7f) || (dev->ir_em2880->sequence[0] != dev->ir_em2880->sequence[1]); +} + + + +/* ----------------------------------------------------------------------- */ +void em28xx_set_ir(struct em28xx *dev, struct IR_i2c *ir) +{ + if (disable_ir) { + ir->get_key = NULL; + return; + } + + /* detect & configure */ + switch (dev->model) { + case EM2800_BOARD_GENERIC: + break; + case EM2820_BOARD_GENERIC: + break; + case EM2800_BOARD_TERRATEC_CINERGY_200: + case EM2820_BOARD_TERRATEC_CINERGY_250: + ir->ir_codes = ir_codes_em_terratec_u; + ir->get_key = get_key_terratec; + snprintf(ir->c.name, sizeof(ir->c.name), + "i2c IR (EM28XX Terratec)"); + break; + case EM2820_BOARD_PINNACLE_USB_2: + ir->ir_codes = ir_codes_em_pinnacle_usb; + ir->get_key = get_key_pinnacle_usb; + snprintf(ir->c.name, sizeof(ir->c.name), + "i2c IR (EM28XX Pinnacle PCTV)"); + break; + case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2: + ir->ir_codes = ir_codes_hauppauge_new_u; + ir->get_key = get_key_em_haup; + snprintf(ir->c.name, sizeof(ir->c.name), + "i2c IR (EM2840 Hauppauge)"); + break; + case EM2820_BOARD_MSI_VOX_USB_2: + break; + case EM2800_BOARD_LEADTEK_WINFAST_USBII: + break; + case EM2800_BOARD_KWORLD_USB2800: + break; + } +} + +/* ---------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/empia/em28xx-keymaps.c b/drivers/media/video/empia/em28xx-keymaps.c new file mode 100644 index 0000000..5f82f91 --- /dev/null +++ b/drivers/media/video/empia/em28xx-keymaps.c @@ -0,0 +1,350 @@ +#include "em28xx-keymaps.h" + +IR_KEYTAB_TYPE ir_codes_em_terratec_u[IR_KEYTAB_SIZE] = { + [ 0x01 ] = KEY_CHANNEL, + [ 0x02 ] = KEY_SELECT, + [ 0x03 ] = KEY_MUTE, + [ 0x04 ] = KEY_POWER, + [ 0x05 ] = KEY_1, + [ 0x06 ] = KEY_2, + [ 0x07 ] = KEY_3, + [ 0x08 ] = KEY_CHANNELUP, + [ 0x09 ] = KEY_4, + [ 0x0a ] = KEY_5, + [ 0x0b ] = KEY_6, + [ 0x0c ] = KEY_CHANNELDOWN, + [ 0x0d ] = KEY_7, + [ 0x0e ] = KEY_8, + [ 0x0f ] = KEY_9, + [ 0x10 ] = KEY_VOLUMEUP, + [ 0x11 ] = KEY_0, + [ 0x12 ] = KEY_MENU, + [ 0x13 ] = KEY_PRINT, + [ 0x14 ] = KEY_VOLUMEDOWN, + [ 0x16 ] = KEY_PAUSE, + [ 0x18 ] = KEY_RECORD, + [ 0x19 ] = KEY_REWIND, + [ 0x1a ] = KEY_PLAY, + [ 0x1b ] = KEY_FORWARD, + [ 0x1c ] = KEY_BACKSPACE, + [ 0x1e ] = KEY_STOP, + [ 0x40 ] = KEY_ZOOM, +}; + +EXPORT_SYMBOL_GPL(ir_codes_em_terratec_u); + +IR_KEYTAB_TYPE ir_codes_hauppauge_new_u[IR_KEYTAB_SIZE] = { + /* Keys 0 to 9 */ + [ 0x00 ] = KEY_0, + [ 0x01 ] = KEY_1, + [ 0x02 ] = KEY_2, + [ 0x03 ] = KEY_3, + [ 0x04 ] = KEY_4, + [ 0x05 ] = KEY_5, + [ 0x06 ] = KEY_6, + [ 0x07 ] = KEY_7, + [ 0x08 ] = KEY_8, + [ 0x09 ] = KEY_9, + + [ 0x0a ] = KEY_TEXT, /* keypad asterisk as well */ + [ 0x0b ] = KEY_RED, /* red button */ + [ 0x0c ] = KEY_RADIO, + [ 0x0d ] = KEY_MENU, + [ 0x0e ] = KEY_SUBTITLE, /* also the # key */ + [ 0x0f ] = KEY_MUTE, + [ 0x10 ] = KEY_VOLUMEUP, + [ 0x11 ] = KEY_VOLUMEDOWN, + [ 0x12 ] = KEY_PREVIOUS, /* previous channel */ + [ 0x14 ] = KEY_UP, + [ 0x15 ] = KEY_DOWN, + [ 0x16 ] = KEY_LEFT, + [ 0x17 ] = KEY_RIGHT, + [ 0x18 ] = KEY_VIDEO, /* Videos */ + [ 0x19 ] = KEY_AUDIO, /* Music */ + /* 0x1a: Pictures - presume this means + "Multimedia Home Platform" - + no "PICTURES" key in input.h + */ + [ 0x1a ] = KEY_MHP, + + [ 0x1b ] = KEY_EPG, /* Guide */ + [ 0x1c ] = KEY_TV, + [ 0x1e ] = KEY_NEXTSONG, /* skip >| */ + [ 0x1f ] = KEY_EXIT, /* back/exit */ + [ 0x20 ] = KEY_CHANNELUP, /* channel / program + */ + [ 0x21 ] = KEY_CHANNELDOWN, /* channel / program - */ + [ 0x22 ] = KEY_CHANNEL, /* source (old black remote) */ + [ 0x24 ] = KEY_PREVIOUSSONG, /* replay |< */ + [ 0x25 ] = KEY_ENTER, /* OK */ + [ 0x26 ] = KEY_SLEEP, /* minimize (old black remote) */ + [ 0x29 ] = KEY_BLUE, /* blue key */ + [ 0x2e ] = KEY_GREEN, /* green button */ + [ 0x30 ] = KEY_PAUSE, /* pause */ + [ 0x32 ] = KEY_REWIND, /* backward << */ + [ 0x34 ] = KEY_FASTFORWARD, /* forward >> */ + [ 0x35 ] = KEY_PLAY, + [ 0x36 ] = KEY_STOP, + [ 0x37 ] = KEY_RECORD, /* recording */ + [ 0x38 ] = KEY_YELLOW, /* yellow key */ + [ 0x3b ] = KEY_SELECT, /* top right button */ + [ 0x3c ] = KEY_ZOOM, /* full */ + [ 0x3d ] = KEY_POWER, /* system power (green button) */ +}; + +EXPORT_SYMBOL_GPL(ir_codes_hauppauge_new_u); + + + +IR_KEYTAB_TYPE ir_codes_pinnacle2[IR_KEYTAB_SIZE] = { + /* Keys 0 to 9 */ + [ 0x15 ] = KEY_0, + [ 0x08 ] = KEY_1, + [ 0x09 ] = KEY_2, + [ 0x0a ] = KEY_3, + [ 0x0c ] = KEY_4, + [ 0x0d ] = KEY_5, + [ 0x0e ] = KEY_6, + [ 0x10 ] = KEY_7, + [ 0x11 ] = KEY_8, + [ 0x12 ] = KEY_9, + + [ 0x03 ] = KEY_POWER, + + [ 0x0b ] = KEY_VOLUMEUP, + [ 0x0f ] = KEY_VOLUMEDOWN, + [ 0x13 ] = KEY_CHANNELUP, + [ 0x17 ] = KEY_CHANNELDOWN, + [ 0x14 ] = KEY_INFO, + + [ 0x00 ] = KEY_MUTE, + + [ 0x06 ] = KEY_PLAY, + [ 0x04 ] = KEY_REWIND, + [ 0x07 ] = KEY_FORWARD, + [ 0x06 ] = KEY_PAUSE, + [ 0x05 ] = KEY_STOP, + [ 0x01 ] = KEY_RECORD, + [ 0x02 ] = KEY_ZOOM, /* fullscreen */ + [ 0x16 ] = KEY_M, + +}; +EXPORT_SYMBOL_GPL(ir_codes_pinnacle2); + +IR_KEYTAB_TYPE ir_codes_em_gadmei_usb[IR_KEYTAB_SIZE] = { + [ 0x00 ] = KEY_1, + [ 0x01 ] = KEY_2, + [ 0x02 ] = KEY_3, + [ 0x03 ] = KEY_4, + [ 0x04 ] = KEY_5, + [ 0x05 ] = KEY_6, + [ 0x06 ] = KEY_7, + [ 0x07 ] = KEY_8, + [ 0x08 ] = KEY_9, + [ 0x09 ] = KEY_0, + [ 0x0a ] = KEY_A, + [ 0x0b ] = KEY_VIDEO, + [ 0x0c ] = KEY_MUTE, + [ 0x0d ] = KEY_PLAYPAUSE, + [ 0x0e ] = KEY_DVD, + [ 0x0f ] = KEY_RADIO, + [ 0x10 ] = KEY_VOLUMEUP, + [ 0x11 ] = KEY_VOLUMEDOWN, + [ 0x12 ] = KEY_CHANNELUP, + [ 0x13 ] = KEY_CHANNELDOWN, + [ 0x14 ] = KEY_POWER, + [ 0x15 ] = KEY_MENU, + [ 0x17 ] = KEY_STOP, + [ 0x18 ] = KEY_TV, + [ 0x1a ] = KEY_RECORD, + [ 0x1c ] = KEY_PREVIOUS, + [ 0x1e ] = KEY_B, + [ 0x1f ] = KEY_C, + [ 0x44 ] = KEY_E, + [ 0x46 ] = KEY_D, + [ 0x4a ] = KEY_ZOOM, +}; +EXPORT_SYMBOL_GPL(ir_codes_em_gadmei_usb); + +IR_KEYTAB_TYPE ir_codes_em_pinnacle2_usb[IR_KEYTAB_SIZE] = { + [ 0x00 ] = KEY_MUTE, + [ 0x01 ] = KEY_A, + [ 0x39 ] = KEY_POWER, + [ 0x03 ] = KEY_VOLUMEUP, + [ 0x06 ] = KEY_CHANNELUP, + [ 0x09 ] = KEY_VOLUMEDOWN, + [ 0x0c ] = KEY_CHANNELDOWN, + [ 0x0f ] = KEY_1, + [ 0x15 ] = KEY_2, + [ 0x10 ] = KEY_3, + [ 0x18 ] = KEY_4, + [ 0x1b ] = KEY_5, + [ 0x1e ] = KEY_6, + [ 0x11 ] = KEY_7, + [ 0x21 ] = KEY_8, + [ 0x12 ] = KEY_9, + [ 0x24 ] = KEY_ZOOM, + [ 0x27 ] = KEY_0, + [ 0x2a ] = KEY_T, + [ 0x2d ] = KEY_REWIND, + [ 0x30 ] = KEY_PLAY, + [ 0x33 ] = KEY_FORWARD, + [ 0x36 ] = KEY_RECORD, + [ 0x3c ] = KEY_STOP, + [ 0x3f ] = KEY_INFO, +}; +EXPORT_SYMBOL_GPL(ir_codes_em_pinnacle2_usb); + +IR_KEYTAB_TYPE ir_codes_em_pinnacle_usb[IR_KEYTAB_SIZE] = { + [ 0x3a ] = KEY_0, + [ 0x31 ] = KEY_1, + [ 0x32 ] = KEY_2, + [ 0x33 ] = KEY_3, + [ 0x34 ] = KEY_4, + [ 0x35 ] = KEY_5, + [ 0x36 ] = KEY_6, + [ 0x37 ] = KEY_7, + [ 0x38 ] = KEY_8, + [ 0x39 ] = KEY_9, + + [ 0x2f ] = KEY_POWER, + + [ 0x2e ] = KEY_P, + [ 0x1f ] = KEY_L, + [ 0x2b ] = KEY_I, + + [ 0x2d ] = KEY_ZOOM, + [ 0x1e ] = KEY_ZOOM, + [ 0x1b ] = KEY_VOLUMEUP, + [ 0x0f ] = KEY_VOLUMEDOWN, + [ 0x17 ] = KEY_CHANNELUP, + [ 0x1c ] = KEY_CHANNELDOWN, + [ 0x25 ] = KEY_INFO, + + [ 0x3c ] = KEY_MUTE, + + [ 0x3d ] = KEY_LEFT, + [ 0x3b ] = KEY_RIGHT, + + [ 0x3f ] = KEY_UP, + [ 0x3e ] = KEY_DOWN, + [ 0x1a ] = KEY_PAUSE, + + [ 0x1d ] = KEY_MENU, + [ 0x19 ] = KEY_PLAY, + [ 0x16 ] = KEY_REWIND, + [ 0x13 ] = KEY_FORWARD, + [ 0x15 ] = KEY_PAUSE, + [ 0x0e ] = KEY_REWIND, + [ 0x0d ] = KEY_PLAY, + [ 0x0b ] = KEY_STOP, + [ 0x07 ] = KEY_FORWARD, + [ 0x27 ] = KEY_RECORD, + [ 0x26 ] = KEY_TUNER, + [ 0x29 ] = KEY_TEXT, + [ 0x2a ] = KEY_MEDIA, + [ 0x18 ] = KEY_EPG, + [ 0x27 ] = KEY_RECORD, +}; +EXPORT_SYMBOL_GPL(ir_codes_em_pinnacle_usb); + +IR_KEYTAB_TYPE ir_codes_em_terratec2[IR_KEYTAB_SIZE] = { + [ 0x01 ] = KEY_POWER, + [ 0x02 ] = KEY_1, + [ 0x03 ] = KEY_2, + [ 0x04 ] = KEY_3, + [ 0x05 ] = KEY_4, + [ 0x06 ] = KEY_5, + [ 0x07 ] = KEY_6, + [ 0x08 ] = KEY_7, + [ 0x09 ] = KEY_8, + [ 0x0a ] = KEY_9, + [ 0x0b ] = KEY_TUNER, + [ 0x0c ] = KEY_0, + [ 0x0d ] = KEY_PREVIOUSSONG, + + [ 0x41 ] = KEY_HOME, + [ 0x42 ] = KEY_MENU, + [ 0x43 ] = KEY_SUBTITLE, + [ 0x44 ] = KEY_TEXT, + [ 0x45 ] = KEY_DELETE, + [ 0x46 ] = KEY_TV, + [ 0x47 ] = KEY_DVD, + [ 0x49 ] = KEY_VIDEO, + [ 0x4a ] = KEY_AUDIO, + [ 0x4b ] = KEY_SHUFFLE, + + [ 0x10 ] = KEY_UP, + [ 0x11 ] = KEY_LEFT, + [ 0x12 ] = KEY_OK, + [ 0x13 ] = KEY_RIGHT, + [ 0x14 ] = KEY_DOWN, + + [ 0x0f ] = KEY_EPG, + [ 0x16 ] = KEY_INFO, + [ 0x4d ] = KEY_BACK, + + [ 0x1c ] = KEY_VOLUMEUP, + [ 0x4c ] = KEY_PLAY, + [ 0x1b ] = KEY_CHANNELUP, + [ 0x1e ] = KEY_VOLUMEDOWN, + [ 0x1d ] = KEY_MUTE, + [ 0x1f ] = KEY_CHANNELDOWN, + + [ 0x17 ] = KEY_RED, + [ 0x18 ] = KEY_GREEN, + [ 0x19 ] = KEY_YELLOW, + [ 0x1a ] = KEY_BLUE, + + [ 0x58 ] = KEY_RECORD, + [ 0x48 ] = KEY_STOP, + [ 0x40 ] = KEY_PAUSE, + [ 0x54 ] = KEY_LAST, + [ 0x4e ] = KEY_REWIND, + [ 0x4f ] = KEY_FORWARD, + [ 0x5c ] = KEY_NEXT, +}; +EXPORT_SYMBOL_GPL(ir_codes_em_terratec2); + + +IR_KEYTAB_TYPE ir_codes_em_kworld[IR_KEYTAB_SIZE] = { + [ 0x43 ] = KEY_POWER, + [ 0x03 ] = KEY_POWER2, + + [ 0x04 ] = KEY_1, + [ 0x08 ] = KEY_2, + [ 0x02 ] = KEY_3, + [ 0x0f ] = KEY_4, + [ 0x05 ] = KEY_5, + [ 0x06 ] = KEY_6, + [ 0x0c ] = KEY_7, + [ 0x0d ] = KEY_8, + [ 0x0a ] = KEY_9, + [ 0x11 ] = KEY_0, + + [ 0x09 ] = KEY_CHANNELUP, + [ 0x07 ] = KEY_CHANNELDOWN, + [ 0x0e ] = KEY_VOLUMEUP, + [ 0x13 ] = KEY_VOLUMEDOWN, + + [ 0x01 ] = KEY_TUNER, + [ 0x0b ] = KEY_ZOOM, + + [ 0x16 ] = KEY_PLAY, + [ 0x17 ] = KEY_MUTE, + [ 0x14 ] = KEY_RECORD, + [ 0x15 ] = KEY_STOP, + + [ 0x18 ] = KEY_UP, + [ 0x19 ] = KEY_DOWN, + [ 0x1a ] = KEY_LEFT, + [ 0x1b ] = KEY_RIGHT, + + [ 0x1c ] = KEY_RED, + [ 0x1d ] = KEY_GREEN, + [ 0x1e ] = KEY_YELLOW, + [ 0x1f ] = KEY_BLUE, + + [ 0x12 ] = KEY_OK, + [ 0x10 ] = KEY_HOME +}; +EXPORT_SYMBOL_GPL(ir_codes_em_kworld); diff --git a/drivers/media/video/empia/em28xx-keymaps.h b/drivers/media/video/empia/em28xx-keymaps.h new file mode 100644 index 0000000..1205419 --- /dev/null +++ b/drivers/media/video/empia/em28xx-keymaps.h @@ -0,0 +1,21 @@ +#ifndef _EM28XX_KEYMAPS +#define _EM28XX_KEYMAPS +#include +#include + +/* TODO move all that stuff to userspace, fixed keytab definitions in the kernel + * are broken by design, there are multiple remotes available with custom return keys + * -> raw lirc module is required here */ + +#define IR_KEYTAB_TYPE u32 +#define IR_KEYTAB_SIZE 128 // enougth for rc5, probably need more some day ... + +extern IR_KEYTAB_TYPE ir_codes_pinnacle2[IR_KEYTAB_SIZE]; +extern IR_KEYTAB_TYPE ir_codes_em_pinnacle_usb[IR_KEYTAB_SIZE]; +extern IR_KEYTAB_TYPE ir_codes_em_terratec2[IR_KEYTAB_SIZE]; +extern IR_KEYTAB_TYPE ir_codes_em_pinnacle2_usb[IR_KEYTAB_SIZE]; +extern IR_KEYTAB_TYPE ir_codes_em_gadmei_usb[IR_KEYTAB_SIZE]; +extern IR_KEYTAB_TYPE ir_codes_em_terratec_u[IR_KEYTAB_SIZE]; +extern IR_KEYTAB_TYPE ir_codes_hauppauge_new_u[IR_KEYTAB_SIZE]; +extern IR_KEYTAB_TYPE ir_codes_em_kworld[IR_KEYTAB_SIZE]; +#endif diff --git a/drivers/media/video/empia/em28xx-video.c b/drivers/media/video/empia/em28xx-video.c new file mode 100644 index 0000000..49db364 --- /dev/null +++ b/drivers/media/video/empia/em28xx-video.c @@ -0,0 +1,4244 @@ +/* + em28xx-video.c - driver for Empia + EM2800/2820/2840/2870/2880 + USB video capture devices + + Copyright (C) 2005 Sascha Sommer + 2005-2007 Markus Rechberger + 2005-2006 Mauro Carvalho Chehab + 2005 Ludovico Cavedon + + Some parts based on SN9C10x PC Camera Controllers GPL driver made + by Luca Risolia + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dvb_frontend.h" +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 15) +#include +#endif + +#include "em28xx.h" +#include +#include +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 26) +#include +#endif +#include "include/tunerchip.h" +#include "xc3028/xc3028_control.h" +#include "xc3028/xc3028_module.h" + +#include "xc5000/xc5000_control.h" +#include "xc5000/xc5000_module.h" +#include "cx25843/em28xx-cx25843.h" + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) +#include "i2c-compat.h" +#include +#endif + +#define DRIVER_AUTHOR "Ludovico Cavedon , " \ + "Markus Rechberger , " \ + "Mauro Carvalho Chehab , " \ + "Sascha Sommer " + +#define DRIVER_NAME "em28xx" +#define DRIVER_DESC "Empia em28xx based USB video device driver" +#define EM28XX_VERSION_CODE KERNEL_VERSION(0, 0, 1) + +#define em28xx_videodbg(fmt, arg...) do {\ + if (video_debug) \ + printk(KERN_INFO "%s %s :"fmt, \ + dev->name, __FUNCTION__ , ##arg); } while (0) + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +unsigned char *XC5000_firmware_SEQUENCE; + +LIST_HEAD(em28xx_devlist); +static LIST_HEAD(em28xx_extension_devlist); +static DEFINE_MUTEX(em28xx_extension_devlist_lock); + +static unsigned int card[] = {[0 ... (EM28XX_MAXBOARDS - 1)] = UNSET }; +static unsigned int radio_nr[] = {[0 ... (EM28XX_MAXBOARDS - 1)] = UNSET }; +static unsigned int video_nr[] = {[0 ... (EM28XX_MAXBOARDS - 1)] = UNSET }; +static unsigned int vbi_nr[] = {[0 ... (EM28XX_MAXBOARDS - 1)] = UNSET }; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) +MODULE_PARM(card, "1-" __stringify(EM28XX_MAXBOARDS) "i"); +MODULE_PARM(video_nr, "1-" __stringify(EM28XX_MAXBOARDS) "i"); +MODULE_PARM(vbi_nr, "1-" __stringify(EM28XX_MAXBOARDS) "i"); +#else +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 10) +static int dummy; +module_param_array(card, int, dummy, 0444); +module_param_array(video_nr, int, dummy, 0444); +module_param_array(vbi_nr, int, dummy, 0444); +#else +module_param_array(card, int, NULL, 0444); +module_param_array(video_nr, int, NULL, 0444); +module_param_array(vbi_nr, int, NULL, 0444); +#endif +#endif +MODULE_PARM_DESC(card, "card type"); +MODULE_PARM_DESC(video_nr, "video device numbers"); +MODULE_PARM_DESC(vbi_nr, "vbi device numbers"); + +static int tuner = -1; +module_param(tuner, int, 0444); +MODULE_PARM_DESC(tuner, "tuner type"); + +static unsigned int video_debug; +module_param(video_debug, int, 0644); +MODULE_PARM_DESC(video_debug, "enable debug messages [video]"); + +static unsigned int device_mode; +module_param(device_mode, int, 0644); +MODULE_PARM_DESC(device_mode, "device mode (DVB-T/Analogue TV)"); + +static unsigned int vbi_mode = 1; +module_param(vbi_mode, int, 0644); +MODULE_PARM_DESC(vbi_mode, "VBI mode (0 disabled/1 enabled(default, " + "if appropriate))"); + +static unsigned int vbi_interlaced; +module_param(vbi_interlaced, int, 0644); +MODULE_PARM_DESC(vbi_interlaced, "VBI Interlaced (default 0 - off)"); + +/* Bitmask marking allocated devices from 0 to EM28XX_MAXBOARDS */ +static unsigned long em28xx_devused; + +static int em28xx_v4l2_vbi_mmap(struct file *filp, struct vm_area_struct *vma); +static int em28xx_stream_interrupt(struct em28xx *dev, int type); + +struct workqueue_struct *em28xx_wq; /* global workqueue for polling the + remote control and requesting + submodules */ +/* supported controls */ +/* Common to all boards */ +static struct v4l2_queryctrl em28xx_qctrl[] = { + { + .id = V4L2_CID_AUDIO_VOLUME, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Volume", + .minimum = 0x0, + .maximum = 0x1f, + .step = 0x1, + .default_value = 0x1f, + .flags = 0, + }, { + .id = V4L2_CID_AUDIO_MUTE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + .flags = 0, + } +}; + +/* FIXME: These are specific to saa711x - should be moved to its code */ +static struct v4l2_queryctrl saa711x_qctrl[] = { + { + .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Brightness1", + .minimum = -128, + .maximum = 127, + .step = 1, + .default_value = 0, + .flags = 0, + }, { + .id = V4L2_CID_CONTRAST, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Contrast", + .minimum = 0x0, + .maximum = 0x1f, + .step = 0x1, + .default_value = 0x10, + .flags = 0, + }, { + .id = V4L2_CID_SATURATION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Saturation", + .minimum = 0x0, + .maximum = 0x1f, + .step = 0x1, + .default_value = 0x10, + .flags = 0, + }, { + .id = V4L2_CID_RED_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Red chroma balance", + .minimum = -128, + .maximum = 127, + .step = 1, + .default_value = 0, + .flags = 0, + }, { + .id = V4L2_CID_BLUE_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Blue chroma balance", + .minimum = -128, + .maximum = 127, + .step = 1, + .default_value = 0, + .flags = 0, + }, { + .id = V4L2_CID_GAMMA, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gamma", + .minimum = 0x0, + .maximum = 0x3f, + .step = 0x1, + .default_value = 0x20, + .flags = 0, + } +#ifdef V4L2_CID_SHARPNESS + , { + .id = V4L2_CID_SHARPNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Sharpness", + .minimum = 0x0, + .maximum = 0xf, + .step = 0x1, + .default_value = 0x8, + .flags = 0, + } +#endif +}; + +struct em28xx_output_fmt em28xx_out_fmt[]={ + { + .fmt = { + .index = 0, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .description = "Y0-U-Y1-V, 16 bpp", + .pixelformat = V4L2_PIX_FMT_YUYV, + }, + .config = 0x14 + }, { + .fmt = { + .index = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .description = "Y1-U-Y0-V, 16 bpp", + .pixelformat = V4L2_PIX_FMT_YUY1, + }, + .config = 0x15 + }, { + .fmt = { + .index = 2, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .description = "YUV411, 12 bpp", + .pixelformat = V4L2_PIX_FMT_Y41P + }, + .config = 0x18 + }, { + .fmt = { + .index = 3, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .description = "YUV211, 8 bpp", + .pixelformat = V4L2_PIX_FMT_YUV211 + }, + .config = 0x10 + }, { + .fmt = { + .index = 4, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .description = "RGB, 16 bpp, 6-5-6", /* 16 != 656->17 */ + .pixelformat = V4L2_PIX_FMT_RGB565 /* 565->16 */ + }, + .config = 0x04, + }, { + .fmt = { + .index = 5, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .description = "RGB, 8bit RGRG", + .pixelformat = 1 /* TODO */ + }, + .config = 0x00 + }, { + .fmt = { + .index = 6, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .description = "RGB, 8bit GRGR", + .pixelformat = 2 /* TODO */ + }, + .config = 0x01 + }, { + .fmt = { + .index = 7, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .description = "RGB, 8bit GBGB", + .pixelformat = 3 /* TODO */ + }, + .config = 0x02 + }, { + .fmt = { + .index = 8, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .description = "RGB, 8bit BGBG", + .pixelformat = 4 /* TODO */ + }, + .config = 0x03 + } +}; + +static struct usb_driver em28xx_usb_driver; + +static DEFINE_MUTEX(em28xx_sysfs_lock); +static DECLARE_RWSEM(em28xx_disconnect); + +/* ----------------------------------------------------------- */ +/* delayed request_module */ +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17) +#if defined(CONFIG_MODULES) && defined(MODULE) +static void request_module_async(struct work_struct *work) +{ + struct em28xx *dev = container_of(work, + struct em28xx, + request_module_wk.work); + + if ((dev->state & DEV_DISCONNECTED) || + (dev->state & DEV_MISCONFIGURED)) + goto out; + + if (dev->dev_modes & EM28XX_AUDIO) + request_module("em28xx-audio"); + + if ((dev->dev_modes & EM28XX_DVBT || dev->dev_modes & EM28XX_ATSC || dev->dev_modes & EM28XX_ISDB)) + request_module("em28xx-dvb"); + + /* unlock all the device nodes here */ +out: + dev->em28xx_acquire(dev, EM28XX_LOCK, 0); +} +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) +static void request_modules(struct em28xx *dev) { + dev->em28xx_acquire(dev, EM28XX_LOCK, 0); +} +#else +/* this is a bit fishy here, if we'd directly try to load the module here + it would end up in a deadlock since modprobe locks the em28xx module. + When loading the em2880-dvb module it also tries to open the em28xx module + and it gets stuck and won't return, so a small workaround is to set up + a workqueue and run modprobe from another process + + the previous approach used the global workqueue, although this could have + locked up the keyboard input system, currently when running + + while :; do modprobe em28xx; rmmod em28xx-dvb; rmmod em28xx-audio; rmmod + em28xx; done + + the unloading process might deadlock with the request thread, the requesting- insmod needs to get killed manually ...(the "bug/feature" is in the + linux module code...) + + This whole issue goes back for a very long time already and quite a few + people went on this before... + */ + +static void request_modules(struct em28xx *dev) +{ + schedule_delayed_work(&dev->request_module_wk, msecs_to_jiffies(50)); +} +#endif +#else +static void request_modules(struct em28xx *dev) { + dev->em28xx_acquire(dev, EM28XX_LOCK, 0); +} +#endif +#endif + +/********************* v4l2 interface ******************************************/ + +/* + * em28xx_config() + * inits registers with sane defaults + */ +int em28xx_config(struct em28xx *dev) +{ + em28xx_audio_usb_mute(dev, EM28XX_MUTED); + dev->mute = 1; /* maybe not the right place... */ + dev->volume = 0x1f; + em28xx_audio_analog_set(dev); + em28xx_audio_analog_setup(dev); + em28xx_outfmt_set_yuv422(dev); + em28xx_colorlevels_set_default(dev); + em28xx_compression_disable(dev); + + return 0; +} + +/* + * em28xx_config_i2c() + * configure i2c attached devices + * TODO: add MSI_VOX_USB_2 support again! + * + */ +void em28xx_config_i2c(struct em28xx *dev) +{ + struct v4l2_routing route; + + route.input = INPUT(dev->ctl_input)->vmux; + route.output = 0; + + /* configure decoder */ + em28xx_i2c_call_clients(dev, VIDIOC_INT_RESET, 0); + em28xx_i2c_call_clients(dev, VIDIOC_INT_S_VIDEO_ROUTING, &route); + em28xx_i2c_call_clients(dev, VIDIOC_STREAMON, NULL); + +} + +/* + * em28xx_empty_framequeues() + * prepare queues for incoming and outgoing frames + */ +static void em28xx_empty_framequeues(struct em28xx *dev, int type) +{ + u32 i; + /* FIXME please verify EM28XX_NUM_FRAMES */ + + if (type == EM28XX_VBI) { + INIT_LIST_HEAD(&dev->vbi_inqueue); + INIT_LIST_HEAD(&dev->vbi_outqueue); + for (i = 0; i < EM28XX_NUM_FRAMES; i++) { + dev->vbi_frame[i].state = F_UNUSED; + dev->vbi_frame[i].buf.bytesused = 0; + } + } else { + INIT_LIST_HEAD(&dev->inqueue); + INIT_LIST_HEAD(&dev->outqueue); + for (i = 0; i < EM28XX_NUM_FRAMES; i++) { + dev->frame[i].state = F_UNUSED; + dev->frame[i].buf.bytesused = 0; + } + } + +} + +static void video_mux(struct em28xx *dev, int index) +{ + int input = INPUT(index)->vmux; + struct v4l2_routing route; + int ainput; + + route.input = INPUT(index)->vmux; + route.output = 0; + + dev->ctl_input = index; + dev->ctl_ainput = INPUT(index)->amux; + dev->ctl_amix = INPUT(index)->amix; + + em28xx_i2c_call_clients(dev, VIDIOC_INT_S_VIDEO_ROUTING, &route); + + em28xx_videodbg("Setting input index = %d, vmux = %d, amux = %d\n", + index, input, dev->ctl_ainput); + + if (dev->has_msp34xx) { + if (dev->i2s_speed) + em28xx_i2c_call_clients(dev, VIDIOC_INT_I2S_CLOCK_FREQ, + &dev->i2s_speed); + em28xx_i2c_call_clients(dev, VIDIOC_S_AUDIO, &dev->ctl_ainput); + ainput = EM28XX_AUDIO_SRC_TUNER; + em28xx_audio_source(dev, ainput); + } else { + switch (dev->model) { + case EM2860_BOARD_GADMEI_UTV330: + { + u8 val; + switch (dev->ctl_ainput) { + case 0: + val = 0xfd; + break; + case 1: + val = 0xfc; + break; + default : + val = 0xfe; + break; + } + if (dev->mute) + val = 0xfe; + + em28xx_write_regs(dev, 0x08, &val, 1); + break; + } + default : + switch (dev->ctl_ainput) { + case 0: + ainput = EM28XX_AUDIO_SRC_TUNER; + break; + default: + ainput = EM28XX_AUDIO_SRC_LINE; + } + em28xx_audio_source(dev, ainput); + break; + } + } + em28xx_audio_set_mixer(dev, dev->ctl_amix); +} + + +static int em28xx_acquire(struct em28xx *dev, int mode, int lock) +{ + int ret = 0; + + if (dev->mode_lock && mode != EM28XX_LOCK) + return -EBUSY; + + switch (mode) { + case EM28XX_LOCK: + if (lock) + dev->mode_lock = 1; + else + dev->mode_lock = 0; + break; + /* radio is allowed if TV and DVB is not in use */ + case EM28XX_RADIO: + if (lock) { + if (dev->fe_user || dev->video_user || dev->vbi_user) + ret = -EBUSY; + else + dev->radio_user++; + } else + dev->radio_user--; + break; + /* video, vbi and analogue audio are allowed at the same time */ + case EM28XX_VIDEO: + if (lock) { + if (dev->fe_user || dev->radio_user) + ret = -EBUSY; + else + dev->video_user++; + } else + dev->video_user--; + break; + case EM28XX_VBI: + if (lock) { + if (dev->fe_user || dev->radio_user) + ret = -EBUSY; + else + dev->vbi_user++; + } else + dev->vbi_user--; + break; + case EM28XX_DVBT: + if (lock) { + if (dev->radio_user || dev->video_user || dev->vbi_user) + ret = -EBUSY; + else + dev->fe_user++; + } else { + dev->fe_user--; + } + break; + case EM28XX_ATSC: + if (lock) { + if (dev->radio_user || dev->video_user || dev->vbi_user) + ret = -EBUSY; + else + dev->fe_user++; + } else + dev->fe_user--; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/* + * em28xx_v4l2_open() + * inits the device and starts isoc transfer + */ + +static int em28xx_v4l2_open(struct inode *inode, struct file *filp) +{ + int minor = iminor(inode); + int errCode = 0; + int mode = V4L2_TUNER_ANALOG_TV; + int type = 0; + int ret; + struct em28xx *h, *dev = NULL; + struct em28xx_fh *fh; + struct list_head *list; + + list_for_each(list, &em28xx_devlist) { + h = list_entry(list, struct em28xx, devlist); + if (h == NULL) { + printk("em28xx-devlist empty\n"); + return -ENODEV; + } + + if (h->vdev && + h->vdev->minor == minor) { + dev = h; + type = EM28XX_VIDEO; + mode = V4L2_TUNER_ANALOG_TV; + break; + } + + if ((h->dev_modes & EM28XX_RADIO) && + h->rdev && + h->rdev->minor == minor) { + dev = h; + type = EM28XX_RADIO; + mode = V4L2_TUNER_RADIO; + break; + } + + if ((h->dev_modes & EM28XX_VBI) && + h->vbi_dev && + h->vbi_dev->minor == minor) { + dev = h; + type = EM28XX_VBI; + mode = V4L2_TUNER_ANALOG_TV; + break; + } + } + + if (NULL == dev) { + printk(KERN_INFO"device struct is not set\n"); + return -ENODEV; + } + + ret = dev->em28xx_acquire(dev, type, 1); + + if (ret != 0) + return ret; + + if (dev->has_inttuner) { + if (dev->mode != mode) { + int arg; + + dev->mode = mode; + arg = EM28XX_REG_ON; + + dev->em28xx_gpio_control(dev, EM28XX_ANALOG_ON, &arg); + dev->em28xx_gpio_control(dev, EM28XX_LED1_ON, &arg); + dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON, &arg); + arg = EM28XX_REG_OFF; + dev->em28xx_gpio_control(dev, EM28XX_TS1_ON, &arg); + dev->em28xx_gpio_control(dev, EM28XX_DECODER_SLEEP, &arg); + mdelay(100); + + em28xx_config(dev); + em28xx_config_i2c(dev); + if (dev->mode == V4L2_TUNER_ANALOG_TV) + em28xx_i2c_call_clients(dev, VIDIOC_S_STD, + &dev->tvnorm->id); + + switch (dev->tuner_type) { + case TUNER_XCEIVE_XC3028: + { + struct xc3028_init_cmd cmd; + + if (dev->tuner == NULL) { + printk(KERN_INFO"tuner is not " + "attached\n"); + dev->em28xx_acquire(dev, type, 0); + return -EINVAL; + } + switch (dev->mode) { + case V4L2_TUNER_RADIO: + /* this is moreover to switch the + decoder to FM */ + + cmd.new_tv_mode_ptr = + dev->fmnorm->tv_mode; + cmd.new_channel_map_ptr = + dev->fmnorm->channelmap; + if (dev->tuner) + dev->tuner->tuner_cmd( + dev->tuner, + XC3028_INIT_TUNER, + &cmd); + break; + case V4L2_TUNER_ANALOG_TV: + cmd.new_tv_mode_ptr = + dev->tvnorm->tv_mode; + cmd.new_channel_map_ptr = + dev->tvnorm->channelmap; + if (dev->tuner && dev->tuner->tuner_cmd) + dev->tuner->tuner_cmd( + dev->tuner, + XC3028_INIT_TUNER, + &cmd); + break; + } + break; + } + case TUNER_XCEIVE_XC5000: + { + if (dev->tuner == NULL) { + printk("tuner is not attached\n"); + dev->em28xx_acquire(dev, type, 0); + return -EINVAL; + } + + if (dev->tuner && dev->tuner->tuner_cmd) + dev->tuner->tuner_cmd(dev->tuner, + XC5000_INIT_TUNER, + NULL); + switch (dev->mode) { + case V4L2_TUNER_RADIO: + { + struct xc_std_conf cmd; + cmd.index = dev->fmnorm->index; + dev->tuner->tuner_cmd(dev->tuner, + XC5000_SET_MODE, &cmd); + break; + } + case V4L2_TUNER_ANALOG_TV: + { + struct xc_std_conf cmd; + cmd.index = dev->tvnorm->index; + dev->tuner->tuner_cmd(dev->tuner, + XC5000_SET_MODE, &cmd); + break; + } + } + break; + } + } + if (dev->mode == V4L2_TUNER_RADIO) { + struct v4l2_routing arouting; + arouting.input = CX25843_RADIO; + em28xx_i2c_call_clients(dev, + VIDIOC_INT_S_AUDIO_ROUTING, + &arouting); + } + + } + } + + fh = kzalloc(sizeof(struct em28xx_fh), GFP_KERNEL); + + if (!fh) { + printk(KERN_INFO"em28xx-video.c: Out of memory\n"); + dev->em28xx_acquire(dev, type, 0); + return -ENOMEM; + } + fh->type = type; + fh->dev = dev; + filp->private_data = fh; + + em28xx_videodbg("open minor = %d type = %s users = %d\n", + minor, v4l2_type_names[dev->type], dev->users); + + if (!down_read_trylock(&em28xx_disconnect)) { + dev->em28xx_acquire(dev, type, 0); + return -ERESTARTSYS; + } + + dev->users++; + if (dev->vbi_user == 1 && fh->type == EM28XX_VBI) + dev->vbi_bytesread = 0; + + if ((fh->type == EM28XX_VIDEO || fh->type == EM28XX_VBI) && + dev->users == 1) { + + em28xx_set_alternate(dev); + + dev->width = norm_maxw(dev); + dev->height = norm_maxh(dev); + dev->frame_size = dev->width * dev->height * 2; + dev->vbi_frame_size = 720 * 2 * + (dev->tvnorm->vbi_count_0 + dev->tvnorm->vbi_count_1); + dev->field_size = dev->frame_size >> 1; + dev->vbi_field_size = dev->vbi_frame_size >> 1; + dev->bytesperline = dev->width * 2; + dev->vbi_bytesperline = dev->width * 2; + dev->hscale = 0; + dev->vscale = 0; + dev->video_bytesread = 0; + dev->vbi_bytesread = 0; + dev->vbi_dropbytes = 0; + + dev->vbi_frame_current = NULL; + dev->frame_current = NULL; + + em28xx_capture_start(dev, 1); + em28xx_resolution_set(dev); + + /* start the transfer */ + errCode = em28xx_init_isoc(dev); + + em28xx_empty_framequeues(dev, EM28XX_VBI); + em28xx_empty_framequeues(dev, EM28XX_VIDEO); + if (errCode) { + dev->users--; + goto err; + } + dev->video_io = IO_NONE; + dev->vbi_io = IO_NONE; + dev->stream = STREAM_OFF; + dev->num_frames = 0; + dev->vbi_num_frames = 0; + dev->state |= DEV_INITIALIZED; + dev->state &= ~DEV_MISCONFIGURED; + } + up_read(&em28xx_disconnect); + return 0; +err: + up_read(&em28xx_disconnect); + dev->em28xx_acquire(dev, type, 0); + return errCode; +} + +/* + * em28xx_realease_resources() + * unregisters the v4l2, i2c and usb devices + * called when the device gets disconected or at module unload +*/ +static void em28xx_release_resources(struct em28xx *dev) +{ + mutex_lock(&em28xx_sysfs_lock); + + list_del(&dev->devlist); + if (dev->dev_modes&EM28XX_VIDEO) { + em28xx_info("disconnecting %s\n", dev->vdev->name); + em28xx_info("V4L2 VIDEO devices /dev/video%d deregistered\n", + dev->vdev->num); + video_unregister_device(dev->vdev); + if (dev->dev_modes & EM28XX_VBI) { + em28xx_info("V4L2 VBI devices /dev/vbi%d " + "deregistered\n", + dev->vbi_dev->num); + video_unregister_device(dev->vbi_dev); + } + } + if (dev->dev_modes & EM28XX_RADIO) + video_unregister_device(dev->rdev); + + em28xx_i2c_unregister(dev); + usb_put_dev(dev->udev); + mutex_unlock(&em28xx_sysfs_lock); + + + /* Mark device as unused */ + em28xx_devused &= ~(1<devno); + +} + +/* + * em28xx_v4l2_close() + * stops streaming and deallocates all resources allocated by the + * v4l2 calls and ioctls + */ +static int em28xx_v4l2_close(struct inode *inode, struct file *filp) +{ + int errCode; + struct em28xx_fh *fh; + struct em28xx *dev; + int ret = 0; + + if (filp->private_data == NULL) + return 0; + + + fh = filp->private_data; + dev = fh->dev; + em28xx_videodbg("users = %d\n", dev->users); + + if ((dev->vbi_user == 1 || fh->reader == 1) && + fh->type == EM28XX_VBI) { + if (dev->vbi_stream == STREAM_ON) { + em28xx_videodbg("VIDIOC_STREAMOFF: interrupting " + "stream\n"); + ret = em28xx_stream_interrupt(dev, EM28XX_VBI); + em28xx_empty_framequeues(dev, EM28XX_VBI); + dev->vbi_stream = STREAM_OFF; + } + dev->vbi_io = IO_NONE; + fh->reader = 0; + dev->vbi_reader = 0; + } + if ((dev->video_user == 1 || fh->reader == 1) && + fh->type == EM28XX_VIDEO) { + + if (dev->video_stream == STREAM_ON) { + em28xx_videodbg("VIDIOC_STREAMOFF: interrupting " + "stream\n"); + ret = em28xx_stream_interrupt(dev, EM28XX_VIDEO); + em28xx_empty_framequeues(dev, EM28XX_VIDEO); + dev->video_stream = STREAM_OFF; + } + fh->reader = 0; + dev->video_reader = 0; + dev->video_io = IO_NONE; + } + + dev->em28xx_acquire(dev, fh->type, 0); + + /* assume that all handles are closed */ + if (dev->users == 1) { + int gpio_arg; + dev->video_reader = 0; + dev->vbi_reader = 0; + + em28xx_uninit_isoc(dev); + em28xx_release_buffers(dev, EM28XX_VIDEO); + em28xx_release_buffers(dev, EM28XX_VBI); + + /* turn off led */ + gpio_arg = EM28XX_REG_OFF; + em28xx_gpio_control(dev, EM28XX_LED1_ON, &gpio_arg); + /* the device is already disconnect, free the remaining + resources */ + if (dev->state & DEV_DISCONNECTED) { + em28xx_release_resources(dev); + kfree(dev->alt_max_pkt_size); + tuner_chip_detach(dev->tuner); + kfree(dev); + kfree(fh); + return 0; + } + + /* set alternate 0 */ + dev->alt = 0; + em28xx_videodbg("setting alternate 0\n"); + if (dev->adev && dev->adev->users == 0) { + dev->alt = 0; + em28xx_videodbg("setting alternate 0\n"); + errCode = usb_set_interface(dev->udev, dev->usb_interface, 0); + if (errCode < 0) { + em28xx_errdev("cannot change alternate " + "number to 0 (error = %i)\n", + errCode); + } + } + } + kfree(fh); + dev->users--; + wake_up_interruptible_nr(&dev->open, 1); + return ret; +} + +/* + * em28xx_v4l2_read() + * will allocate buffers when called for the first time + */ +static ssize_t +em28xx_v4l2_read(struct file *filp, char __user *buf, size_t count, + loff_t *f_pos) +{ + struct em28xx_frame_t *f, *i; + unsigned long lock_flags; + int ret = 0; + struct em28xx_fh *fh = filp->private_data; + struct em28xx *dev = fh->dev; + + /* TODO: either add support for sliced VBI */ + if (fh->type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) { + em28xx_videodbg("V4L2_BUF_TYPE_SLICED_VBI_CAPTURE is set\n"); + if (copy_to_user(buf, "", 1)) + return -EFAULT; + return 1; + } + + if (dev->state & DEV_DISCONNECTED) { + em28xx_videodbg("device not present\n"); + return -ENODEV; + } + + if (dev->state & DEV_MISCONFIGURED) { + em28xx_videodbg("device misconfigured; close and open it " + "again\n"); + return -EIO; + } + + if (fh->type == EM28XX_VIDEO && dev->video_reader > 0 && + fh->reader == 0) + return -EBUSY; + + if (fh->type == EM28XX_VBI && dev->vbi_reader > 0 && fh->reader == 0) + return -EBUSY; + + if (fh->type == EM28XX_VIDEO && dev->video_io == IO_MMAP) { + em28xx_videodbg("Video IO method is set to mmap" + " the device again to choose the" + "read method\n"); + return -EINVAL; + } else { + if (fh->type == EM28XX_VIDEO) { + dev->video_reader = 1; + fh->reader = 1; + dev->video_io = IO_READ; + } + } + + if (fh->type == EM28XX_VBI && dev->vbi_io == IO_MMAP) { + em28xx_videodbg("VBI IO method is set to mmap\n"); + return -EINVAL; + } else { + if (fh->type == EM28XX_VBI) { + dev->vbi_reader = 1; + fh->reader = 1; + dev->vbi_io = IO_READ; + } + } + + if (fh->type == EM28XX_VIDEO && dev->video_stream == STREAM_OFF) { + if (!em28xx_request_buffers(dev, + EM28XX_NUM_READ_FRAMES, + EM28XX_VIDEO)) { + em28xx_errdev("read failed, not enough memory\n"); + return -ENOMEM; + } + + dev->video_stream = STREAM_ON; + em28xx_queue_unusedframes(dev, EM28XX_VIDEO); + } + + if (fh->type == EM28XX_VBI && dev->vbi_stream == STREAM_OFF) { + if (!em28xx_request_buffers(dev, EM28XX_NUM_READ_FRAMES, + EM28XX_VBI)) { + em28xx_errdev("read failed not enough memory\n"); + return -ENOMEM; + } + + dev->vbi_stream = STREAM_ON; + em28xx_queue_unusedframes(dev, EM28XX_VBI); + } + + + + if (!count) + return 0; + + if (fh->type == EM28XX_VIDEO) { + if (list_empty(&dev->outqueue)) { + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible + (dev->wait_frame, + (!list_empty(&dev->outqueue)) || + (dev->state & DEV_DISCONNECTED)); + if (ret) + return ret; + + if (dev->state & DEV_DISCONNECTED) + return -ENODEV; + + dev->video_bytesread = 0; + } + + f = list_entry(dev->outqueue.prev, struct em28xx_frame_t, + frame); + + + em28xx_queue_unusedframes(dev, EM28XX_VIDEO); + + if (count > f->buf.length) + count = f->buf.length; + + if ((dev->video_bytesread+count) > dev->frame_size) + count = dev->frame_size - dev->video_bytesread; + + if (copy_to_user(buf, f->bufmem + dev->video_bytesread, count)) + return -EFAULT; + + dev->video_bytesread += count; + + if (dev->video_bytesread == dev->frame_size) { + spin_lock_irqsave(&dev->queue_lock, lock_flags); + list_for_each_entry(i, &dev->outqueue, frame) + i->state = F_UNUSED; + INIT_LIST_HEAD(&dev->outqueue); + spin_unlock_irqrestore(&dev->queue_lock, lock_flags); + em28xx_queue_unusedframes(dev, EM28XX_VIDEO); + dev->video_bytesread = 0; + } + + *f_pos += count; + } else if (fh->type == EM28XX_VBI) { + + if (list_empty(&dev->vbi_outqueue)) { + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (dev->vbi_num_frames == 0) { + em28xx_request_buffers(dev, + EM28XX_NUM_READ_FRAMES, + EM28XX_VBI); + em28xx_queue_unusedframes(dev, EM28XX_VBI); + } + + ret = wait_event_interruptible + (dev->wait_vbi_frame, + (!list_empty(&dev->vbi_outqueue)) || + (dev->state & DEV_DISCONNECTED)); + + if (ret) + return ret; + + if (dev->state & DEV_DISCONNECTED) + return -ENODEV; + + dev->vbi_bytesread = 0; + } + + f = list_entry(dev->vbi_outqueue.prev, struct em28xx_frame_t, + frame); + em28xx_queue_unusedframes(dev, EM28XX_VBI); + + if (count > f->buf.length) + count = f->buf.length; + + if ((dev->vbi_bytesread + count) > dev->vbi_frame_size) + count = dev->vbi_frame_size-dev->vbi_bytesread; + + if (copy_to_user(buf, f->bufmem+dev->vbi_bytesread, count)) + return -EFAULT; + + dev->vbi_bytesread += count; + if (dev->vbi_bytesread == dev->vbi_frame_size) { + spin_lock_irqsave(&dev->vbi_queue_lock, lock_flags); + list_for_each_entry(i, &dev->vbi_outqueue, frame) + i->state = F_UNUSED; + INIT_LIST_HEAD(&dev->vbi_outqueue); + spin_unlock_irqrestore(&dev->vbi_queue_lock, + lock_flags); + em28xx_queue_unusedframes(dev, EM28XX_VBI); + dev->vbi_bytesread = 0; + } + + *f_pos += count; + } + + return count; +} + +/* + * em28xx_v4l2_poll() + * will allocate buffers when called for the first time + */ +static unsigned int em28xx_v4l2_poll(struct file *filp, poll_table * wait) +{ + unsigned int mask = 0; + struct em28xx_fh *fh = filp->private_data; + struct em28xx *dev = fh->dev; + + if (dev->state & DEV_DISCONNECTED) + em28xx_videodbg("device not present\n"); + else if (dev->state & DEV_MISCONFIGURED) + em28xx_videodbg("device is misconfigured; close and open it " + "again\n"); + else if (fh->type == EM28XX_VBI) { + if (fh->reader == 0 && dev->vbi_io != IO_NONE) + return -EINVAL; + + if (dev->vbi_num_frames == 0 && dev->vbi_reader == 0) { + em28xx_empty_framequeues(dev, EM28XX_VBI); + if (!em28xx_request_buffers(dev, + EM28XX_NUM_READ_FRAMES, + EM28XX_VBI)) + return -ENOMEM; + } + + if (dev->vbi_io == IO_NONE && dev->vbi_reader == 0) { + em28xx_queue_unusedframes(dev, EM28XX_VBI); + dev->vbi_io = IO_READ; + dev->vbi_stream = STREAM_ON; + dev->vbi_reader = 1; + fh->reader = 1; + } + + poll_wait(filp, &dev->wait_vbi_frame, wait); + if (!list_empty(&dev->vbi_outqueue)) + mask |= POLLIN | POLLRDNORM; + return mask; + } else if (fh->type == EM28XX_VIDEO) { + + if (dev->num_frames == 0 && dev->reader == 0) { + em28xx_empty_framequeues(dev, EM28XX_VIDEO); + if (!em28xx_request_buffers(dev, EM28XX_NUM_READ_FRAMES, + EM28XX_VIDEO)) + return -ENOMEM; + } + + if (dev->video_io == IO_NONE && dev->reader == 0) { + em28xx_queue_unusedframes(dev, EM28XX_VIDEO); + dev->video_io = IO_READ; + dev->video_stream = STREAM_ON; + dev->reader = 1; + fh->reader = 1; + } + + poll_wait(filp, &dev->wait_frame, wait); + if (!list_empty(&dev->outqueue)) + mask |= POLLIN | POLLRDNORM; + return mask; + } + + return POLLERR; +} + +/* + * em28xx_vm_open() + */ +static void em28xx_vm_open(struct vm_area_struct *vma) +{ + struct em28xx_frame_t *f = vma->vm_private_data; + f->vma_use_count++; +} + +/* + * em28xx_vm_close() + */ +static void em28xx_vm_close(struct vm_area_struct *vma) +{ + /* NOTE: buffers are not freed here */ + struct em28xx_frame_t *f = vma->vm_private_data; + f->vma_use_count--; +} + +static struct vm_operations_struct em28xx_vm_ops = { + .open = em28xx_vm_open, + .close = em28xx_vm_close, +}; + +/* + * em28xx_v4l2_mmap() + */ +static int em28xx_v4l2_mmap(struct file *filp, struct vm_area_struct *vma) +{ + unsigned long size = vma->vm_end - vma->vm_start, + start = vma->vm_start; + void *pos; + u32 i; + + struct em28xx_fh *fh = filp->private_data; + struct em28xx *dev = fh->dev; + + if (fh->type == EM28XX_VBI) + return em28xx_v4l2_vbi_mmap(filp, vma); + + if (fh->type != EM28XX_VIDEO) + return -EINVAL; + + + if (dev->video_reader > 0 && fh->reader == 0) + return -EBUSY; + else { + dev->video_reader = 1; + fh->reader = 1; + } + + if (dev->state & DEV_DISCONNECTED) { + em28xx_videodbg("mmap: device not present\n"); + return -ENODEV; + } + + if (dev->state & DEV_MISCONFIGURED) { + em28xx_videodbg("mmap: Device is misconfigured; close and " + "open it again\n"); + return -EIO; + } + + if (dev->video_io != IO_MMAP || !(vma->vm_flags & VM_WRITE) /*|| + size != PAGE_ALIGN(dev->frame[0].buf.length) */) { + return -EINVAL; + } + + for (i = 0; i < dev->num_frames; i++) + if ((dev->frame[i].buf.m.offset >> PAGE_SHIFT) == vma->vm_pgoff) + break; + + if (i == dev->num_frames) { + em28xx_videodbg("mmap: user supplied mapping address is out " + "of range\n"); + return -EINVAL; + } + + /* VM_IO is eventually going to replace PageReserved altogether */ + vma->vm_flags |= VM_IO; + vma->vm_flags |= VM_RESERVED; /* avoid to swap out this VMA */ + + pos = dev->frame[i].bufmem; + if (pos == 0) { + em28xx_videodbg("em28xx-video.c: exception pos is 0\n"); + return -EINVAL; + } + if (dev->frame[0].buf.length == 0) + + return -EINVAL; + + if (dev->frame[i].buf.length == 0) + return -EINVAL; + + while (size > 0) { /* size is page-aligned */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15) + unsigned long page = vmalloc_to_pfn(pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, + vma->vm_page_prot)) { + em28xx_videodbg("mmap: rename page map failed\n"); +#else + if (vm_insert_page(vma, start, vmalloc_to_page(pos))) { + em28xx_videodbg("mmap: vm_insert_page failed\n"); +#endif + return -EAGAIN; + } + start += PAGE_SIZE; + pos += PAGE_SIZE; + size -= PAGE_SIZE; + } + + vma->vm_ops = &em28xx_vm_ops; + vma->vm_private_data = &dev->frame[i]; + + em28xx_vm_open(vma); + return 0; +} + +static int em28xx_v4l2_vbi_mmap(struct file *filp, struct vm_area_struct *vma) +{ + unsigned long size = vma->vm_end - vma->vm_start, + start = vma->vm_start; + void *pos; + u32 i; + + struct em28xx_fh *fh = filp->private_data; + struct em28xx *dev = fh->dev; + + if (fh->type != EM28XX_VBI) + return -EINVAL; + + if (dev->vbi_reader > 0 && fh->reader == 0) { + return -EBUSY; + } else { + dev->vbi_reader = 1; + fh->reader = 1; + } + + if (dev->state & DEV_DISCONNECTED) { + em28xx_videodbg("mmap: device not present\n"); + return -ENODEV; + } + + if (dev->state & DEV_MISCONFIGURED) { + em28xx_videodbg("mmap: Device is misconfigured; close and " + "open it again\n"); + return -EIO; + } + + if (dev->vbi_io != IO_MMAP || !(vma->vm_flags & VM_WRITE) /*|| + size != PAGE_ALIGN(dev->frame[0].buf.length) */) { + return -EINVAL; + } + + for (i = 0; i < dev->vbi_num_frames; i++) { + if ((dev->vbi_frame[i].buf.m.offset >> PAGE_SHIFT) == + vma->vm_pgoff) + break; + } + if (i == dev->vbi_num_frames) { + em28xx_videodbg("mmap: user supplied mapping address is out " + "of range\n"); + return -EINVAL; + } + + /* VM_IO is eventually going to replace PageReserved altogether */ + vma->vm_flags |= VM_IO; + vma->vm_flags |= VM_RESERVED; /* avoid to swap out this VMA */ + + pos = dev->vbi_frame[i].bufmem; + if (pos == 0) + return -EINVAL; + + if (dev->vbi_frame[0].buf.length == 0) + return -EINVAL; + + if (dev->vbi_frame[i].buf.length == 0) + return -EINVAL; + + while (size > 0) { /* size is page-aligned */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15) + unsigned long page = vmalloc_to_pfn(pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, + vma->vm_page_prot)) { + em28xx_videodbg("mmap: rename page map failed\n"); +#else + if (vm_insert_page(vma, start, vmalloc_to_page(pos))) { + em28xx_videodbg("mmap: vm_insert_page failed\n"); +#endif + return -EAGAIN; + } + start += PAGE_SIZE; + pos += PAGE_SIZE; + size -= PAGE_SIZE; + } + + vma->vm_ops = &em28xx_vm_ops; + vma->vm_private_data = &dev->vbi_frame[i]; + + em28xx_vm_open(vma); + return 0; +} + +/* + * em28xx_get_ctrl() + * return the current saturation, brightness or contrast, mute state + */ +static int em28xx_get_ctrl(struct em28xx *dev, struct v4l2_control *ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + ctrl->value = dev->mute; + return 0; + case V4L2_CID_AUDIO_VOLUME: + ctrl->value = dev->volume; + return 0; + default: + return -EINVAL; + } +} + +/*FIXME: should be moved to saa711x */ +static int saa711x_get_ctrl(struct em28xx *dev, struct v4l2_control *ctrl) +{ + s32 tmp; + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + tmp = em28xx_brightness_get(dev); + if (tmp < 0) + return -EIO; + /* FIXME: cleaner way to extend sign? */ + ctrl->value = (s32) ((s8) tmp); + return 0; + case V4L2_CID_CONTRAST: + ctrl->value = em28xx_contrast_get(dev); + if (ctrl->value < 0) + return -EIO; + return 0; + case V4L2_CID_SATURATION: + ctrl->value = em28xx_saturation_get(dev); + if (ctrl->value < 0) + return -EIO; + return 0; + case V4L2_CID_RED_BALANCE: + tmp = em28xx_v_balance_get(dev); + if (tmp < 0) + return -EIO; + /* FIXME: clenaer way to extend sign? */ + ctrl->value = (s32) ((s8) tmp); + return 0; + case V4L2_CID_BLUE_BALANCE: + tmp = em28xx_u_balance_get(dev); + if (tmp < 0) + return -EIO; + /* FIXME: clenaer way to extend sign? */ + ctrl->value = (s32) ((s8) tmp); + return 0; + case V4L2_CID_GAMMA: + ctrl->value = em28xx_gamma_get(dev); + if (ctrl->value < 0) + return -EIO; + return 0; + default: + return -EINVAL; + } +} + +/* + * em28xx_set_ctrl() + * mute or set new saturation, brightness or contrast + */ +static int em28xx_set_ctrl(struct em28xx *dev, const struct v4l2_control *ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + if (ctrl->value != dev->mute) { + dev->mute = ctrl->value; + em28xx_audio_usb_mute(dev, ctrl->value); + return em28xx_audio_analog_set(dev); + } + return 0; + case V4L2_CID_AUDIO_VOLUME: + dev->volume = ctrl->value; + return em28xx_audio_analog_set(dev); + default: + return -EINVAL; + } +} + +/*FIXME: should be moved to saa711x */ +static int saa711x_set_ctrl(struct em28xx *dev, const struct v4l2_control *ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + return em28xx_brightness_set(dev, ctrl->value); + case V4L2_CID_CONTRAST: + return em28xx_contrast_set(dev, ctrl->value); + case V4L2_CID_SATURATION: + return em28xx_saturation_set(dev, ctrl->value); + case V4L2_CID_RED_BALANCE: + return em28xx_v_balance_set(dev, ctrl->value); + case V4L2_CID_BLUE_BALANCE: + return em28xx_u_balance_set(dev, ctrl->value); + case V4L2_CID_GAMMA: + return em28xx_gamma_set(dev, ctrl->value); + default: + return -EINVAL; + } +} + +/* + * em28xx_stream_interrupt() + * stops streaming + */ +static int em28xx_stream_interrupt(struct em28xx *dev, int type) +{ + int ret = 0; + + /* stop reading from the device */ + switch (type) { + case EM28XX_VBI: + dev->vbi_stream = STREAM_INTERRUPT; + ret = wait_event_timeout(dev->vbi_wait_stream, + (dev->vbi_stream == STREAM_OFF) || + (dev->state & DEV_DISCONNECTED), + EM28XX_URB_TIMEOUT); + + if (dev->state & DEV_DISCONNECTED) + return -ENODEV; + else if (ret) { + dev->state |= DEV_MISCONFIGURED; + em28xx_videodbg("device is misconfigured; close and " + "open /dev/video%d again\n", + dev->vdev->num); + } + break; + case EM28XX_VIDEO: + dev->video_stream = STREAM_INTERRUPT; + ret = wait_event_timeout(dev->video_wait_stream, + (dev->video_stream == STREAM_OFF) || + (dev->state & DEV_DISCONNECTED), + EM28XX_URB_TIMEOUT); + if (dev->state & DEV_DISCONNECTED) + return -ENODEV; + else if (ret) { + dev->state |= DEV_MISCONFIGURED; + em28xx_videodbg("device is misconfigured; close and " + "open /dev/video%d again\n", + dev->vdev->num); + return ret; + } + break; + } + return 0; +} + +static int em28xx_set_norm(struct em28xx *dev, int width, int height) +{ + unsigned int hscale, vscale; + unsigned int maxh, maxw; + + maxw = norm_maxw(dev); + maxh = norm_maxh(dev); + + /* width must even because of the YUYV format */ + /* height must be even because of interlacing */ + height &= 0xfffe; + width &= 0xfffe; + + if (height < 32) + height = 32; + if (height > maxh) + height = maxh; + if (width < 48) + width = 48; + if (width > maxw) + width = maxw; + + hscale = (((unsigned long)maxw) << 12) / width - 4096L; + + if (hscale >= 0x4000) + hscale = 0x3fff; + width = (((unsigned long)maxw) << 12) / (hscale + 4096L); + + vscale = (((unsigned long)maxh) << 12) / height - 4096L; + + if (vscale >= 0x4000) + vscale = 0x3fff; + + height = (((unsigned long)maxh) << 12) / (vscale + 4096L); + + /* set new image size */ + dev->width = width; + dev->height = height; + dev->frame_size = dev->width * dev->height * 2; + dev->field_size = dev->frame_size >> 1; + dev->bytesperline = dev->width * 2; + dev->hscale = hscale; + dev->vscale = vscale; + + em28xx_resolution_set(dev); + + return 0; +} + +static int em28xx_get_fmt(struct em28xx *dev, struct v4l2_format *format) +{ + em28xx_videodbg("VIDIOC_G_FMT: type = %s\n", + (format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) ? + "V4L2_BUF_TYPE_VIDEO_CAPTURE" : + (format->type == V4L2_BUF_TYPE_VBI_CAPTURE) ? + "V4L2_BUF_TYPE_VBI_CAPTURE" : + (format->type == V4L2_CAP_SLICED_VBI_CAPTURE) ? + "V4L2_BUF_TYPE_SLICED_VBI_CAPTURE " : + "not supported"); + + switch (format->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + { + format->fmt.pix.width = dev->width; + format->fmt.pix.height = dev->height; + format->fmt.pix.pixelformat = dev->outfmt->fmt.pixelformat; + // V4L2_PIX_FMT_YUYV; + format->fmt.pix.bytesperline = dev->bytesperline; + format->fmt.pix.sizeimage = dev->frame_size; + format->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + format->fmt.pix.field = V4L2_FIELD_INTERLACED; +#if 0 + format->fmt.pix.field = V4L2_FIELD_INTERLACED_TB; +#endif + + em28xx_videodbg("VIDIOC_G_FMT: %dx%d\n", dev->width, + dev->height); + break; + } + + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + { + format->fmt.sliced.service_set = 0; + em28xx_i2c_call_clients(dev, VIDIOC_G_FMT, format); + if (format->fmt.sliced.service_set == 0) + return -EINVAL; + break; + } + case V4L2_BUF_TYPE_VBI_CAPTURE: + { + format->type = V4L2_BUF_TYPE_VBI_CAPTURE; + + switch (dev->tvnorm->id) { + case V4L2_STD_NTSC_M: + format->fmt.vbi.sampling_rate = + dev->tvnorm->vbi_sample_rate; + break; + case V4L2_STD_PAL: + default: + format->fmt.vbi.sampling_rate = + dev->tvnorm->vbi_sample_rate; + } + + format->fmt.vbi.samples_per_line = + dev->tvnorm->vbi_samples_per_line; + format->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + format->fmt.vbi.offset = dev->tvnorm->vbi_offset; + format->fmt.vbi.start[0] = dev->tvnorm->vbi_start_0; + format->fmt.vbi.count[0] = dev->tvnorm->vbi_count_0; + format->fmt.vbi.start[1] = dev->tvnorm->vbi_start_1; + format->fmt.vbi.count[1] = dev->tvnorm->vbi_count_1; + if (dev->vbi_interlaced) + format->fmt.vbi.flags = V4L2_VBI_INTERLACED; + else + format->fmt.vbi.flags = 0; + return 0; + } + default: + return -EINVAL; + } + return 0; +} + +static int em28xx_set_fmt(struct em28xx *dev, unsigned int cmd, struct v4l2_format *format) +{ + u32 i; + int ret = 0; + int width = format->fmt.pix.width; + int height = format->fmt.pix.height; + unsigned int hscale, vscale; + unsigned int maxh, maxw; + + maxw = norm_maxw(dev); + maxh = norm_maxh(dev); + + em28xx_videodbg("%s: type = %s\n", + cmd == VIDIOC_TRY_FMT ? + "VIDIOC_TRY_FMT" : "VIDIOC_S_FMT", + format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE ? + "V4L2_BUF_TYPE_VIDEO_CAPTURE" : + format->type == V4L2_BUF_TYPE_VBI_CAPTURE ? + "V4L2_BUF_TYPE_VBI_CAPTURE " : + "not supported"); + + if (format->type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) { + em28xx_i2c_call_clients(dev, VIDIOC_G_FMT, format); + + if (format->fmt.sliced.service_set == 0) + return -EINVAL; + + return 0; + } + if (format->type == V4L2_BUF_TYPE_VBI_CAPTURE && + dev->dev_modes&EM28XX_VBI) { + em28xx_i2c_call_clients(dev, VIDIOC_S_FMT, format); + format->type = V4L2_BUF_TYPE_VBI_CAPTURE; + switch (dev->tvnorm->id) { + case V4L2_STD_NTSC_M: + format->fmt.vbi.sampling_rate = + dev->tvnorm->vbi_sample_rate; + break; + case V4L2_STD_PAL: + default: + format->fmt.vbi.sampling_rate = + dev->tvnorm->vbi_sample_rate; + } + format->fmt.vbi.samples_per_line = + dev->tvnorm->vbi_samples_per_line; + format->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + format->fmt.vbi.offset = dev->tvnorm->vbi_offset; + + if (dev->vbi_interlaced) + format->fmt.vbi.flags = V4L2_VBI_INTERLACED; + else + format->fmt.vbi.flags = 0; + + format->fmt.vbi.start[0] = dev->tvnorm->vbi_start_0; + format->fmt.vbi.start[1] = dev->tvnorm->vbi_start_1; + format->fmt.vbi.count[0] = dev->tvnorm->vbi_count_0; + format->fmt.vbi.count[1] = dev->tvnorm->vbi_count_1; + dev->vbi_frame_size = 720 * 2 * + (dev->tvnorm->vbi_count_0 + dev->tvnorm->vbi_count_1); + dev->vbi_field_size = dev->vbi_frame_size >> 1; + + return 0; + } + + if (format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + em28xx_videodbg("%s: requested %dx%d\n", + cmd == VIDIOC_TRY_FMT ? + "VIDIOC_TRY_FMT" : "VIDIOC_S_FMT", + format->fmt.pix.width, format->fmt.pix.height); + + /* FIXME: Move some code away from here */ + /* width must even because of the YUYV format */ + /* height must be even because of interlacing */ + height &= 0xfffe; + width &= 0xfffe; + + if (height < 32) + height = 32; + if (height > maxh) + height = maxh; + if (width < 48) + width = 48; + if (width > maxw) + width = maxw; + + if (dev->em_type == EM2800) { + /* the em2800 can only scale down to 50% */ + if (height % (maxh / 2)) + height = maxh; + if (width % (maxw / 2)) + width = maxw; + /* according to empiatech support */ + /* the MaxPacketSize is to small to support */ + /* framesizes larger than 640x480 @ 30 fps */ + /* or 640x576 @ 25 fps. As this would cut */ + /* of a part of the image we prefer */ + /* 360x576 or 360x480 for now */ + if (width == maxw && height == maxh) + width /= 2; + } + + hscale = (((unsigned long)maxw) << 12) / width - 4096L; + if (hscale >= 0x4000) + hscale = 0x3fff; + + width = (((unsigned long)maxw) << 12) / (hscale + 4096L); + + vscale = (((unsigned long)maxh) << 12) / height - 4096L; + if (vscale >= 0x4000) + vscale = 0x3fff; + + height = (((unsigned long)maxh) << 12) / (vscale + 4096L); + + format->fmt.pix.width = width; + format->fmt.pix.height = height; + + dev->outfmt = &em28xx_out_fmt[0]; + + for (i = 0; i < ARRAY_SIZE(em28xx_out_fmt); i++) { + if (em28xx_out_fmt[i].fmt.pixelformat == format->fmt.pix.pixelformat) { + dev->outfmt = &em28xx_out_fmt[i]; + break; + } + } + + format->fmt.pix.pixelformat = dev->outfmt->fmt.pixelformat; + format->fmt.pix.bytesperline = width * 2; + format->fmt.pix.sizeimage = width * 2 * height; + format->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + format->fmt.pix.field = V4L2_FIELD_INTERLACED; + +#if 0 + /* available since what kernel version ? */ + format->fmt.pix.field = V4L2_FIELD_INTERLACED_TB; +#endif + + em28xx_videodbg("%s: returned %dx%d (%d, %d)\n", + cmd == VIDIOC_TRY_FMT ? + "VIDIOC_TRY_FMT" :"VIDIOC_S_FMT", + format->fmt.pix.width, format->fmt.pix.height, hscale, vscale); + + if (cmd == VIDIOC_TRY_FMT) { + return 0; + } + + for (i = 0; i < dev->num_frames; i++) + if (dev->frame[i].vma_use_count) { + em28xx_videodbg("VIDIOC_S_FMT failed. " + "Unmap the buffers first.\n"); + return -EINVAL; + } + + /* stop io in case it is already in progress */ + + /* set new image size */ + dev->width = width; + dev->height = height; + dev->frame_size = dev->width * dev->height * 2; + dev->field_size = dev->frame_size >> 1; + dev->bytesperline = dev->width * 2; + dev->hscale = hscale; + dev->vscale = vscale; + dev->vbi_frame_size = 720 * 2 * + (dev->tvnorm->vbi_count_0 + dev->tvnorm->vbi_count_1); + dev->vbi_field_size = dev->vbi_frame_size >> 1; + + if (dev->video_stream == STREAM_ON) { + em28xx_videodbg("VIDIOC_SET_FMT: interupting video stream\n"); + ret = em28xx_stream_interrupt(dev, EM28XX_VIDEO); + if (ret) + return ret; + } + if (dev->vbi_stream == STREAM_ON) { + em28xx_videodbg("VIDIOC_SET_FMT: interupting vbi stream\n"); + ret = em28xx_stream_interrupt(dev, EM28XX_VBI); + if (ret) + return ret; + } + + em28xx_release_buffers(dev, EM28XX_VIDEO); + em28xx_release_buffers(dev, EM28XX_VBI); + dev->video_io = IO_NONE; + dev->vbi_io = IO_NONE; + em28xx_uninit_isoc(dev); + em28xx_set_alternate(dev); + em28xx_capture_start(dev, 1); + em28xx_resolution_set(dev); + em28xx_init_isoc(dev); + return 0; +} + +/* + * em28xx_v4l2_do_ioctl() + * This function is _not_ called directly, but from + * em28xx_v4l2_ioctl. Userspace + * copying is done already, arg is a kernel pointer. + */ +static int em28xx_do_ioctl(struct inode *inode, struct file *filp, + struct em28xx *dev, unsigned int cmd, void *arg, + v4l2_kioctl driver_ioctl) +{ + int ret; + struct em28xx_fh *fh = filp->private_data; + + switch (cmd) { + /* ---------- tv norms ---------- */ + case VIDIOC_ENUMSTD: + { + struct v4l2_standard *e = arg; + unsigned int i; + + i = e->index; + if (i >= MAX_EM28XX_TVNORMS || dev->board->tvnorms[i].id == 0) + return -EINVAL; + + ret = v4l2_video_std_construct(e, + dev->board->tvnorms[e->index].id, + dev->board->tvnorms[e->index].name); + + e->index = i; + if (ret < 0) + return ret; + return 0; + } + case VIDIOC_G_STD: + { + v4l2_std_id *id = arg; + *id = dev->tvnorm->id; + return 0; + } + case VIDIOC_S_STD: + { + v4l2_std_id *id = arg; + unsigned int i; + int switch_std = 0; + + for (i = 0; i < MAX_EM28XX_TVNORMS && + dev->board->tvnorms[i].id; i++) + if (dev->board->tvnorms[i].id == *id) + break; + + if (i == MAX_EM28XX_TVNORMS || dev->board->tvnorms[i].id == 0) + for (i = 0; i < MAX_EM28XX_TVNORMS && + dev->board->tvnorms[i].id; i++) + if ((*id & dev->board->tvnorms[i].id) && + dev->board->tvnorms[i].id != 0) + break; + + if (i == MAX_EM28XX_TVNORMS || dev->board->tvnorms[i].id == 0) + return -EINVAL; + + if (((dev->tvnorm->id & V4L2_STD_SECAM) && + (dev->board->tvnorms[i].id & V4L2_STD_SECAM) == 0) || + (((dev->tvnorm->id & V4L2_STD_SECAM) == 0) && + (dev->board->tvnorms[i].id & V4L2_STD_SECAM))) { + switch_std = 1; + } + + if (((dev->tvnorm->id & V4L2_STD_625_50) && + ((dev->board->tvnorms[i].id & V4L2_STD_625_50) == 0)) || + (((dev->tvnorm->id & V4L2_STD_625_50) == 0) && + (dev->board->tvnorms[i].id & V4L2_STD_625_50))) { + if (dev->vbi_stream == STREAM_ON || + dev->video_stream == STREAM_ON) { + printk(KERN_INFO"em28xx: don't switch the standard " + "while the device is capturing"); + return -EBUSY; + } + dev->vbi_frame_size = 720 * 2 * + (dev->board->tvnorms[i].vbi_count_0 + dev->board->tvnorms[i].vbi_count_1); + dev->vbi_field_size = dev->vbi_frame_size >> 1; + } + + dev->tvnorm = &dev->board->tvnorms[i]; + + if (dev->dev_modes & EM28XX_VBI) + em28xx_set_vbi(dev, 1); + + em28xx_set_norm(dev, dev->width, dev->height); + + if (dev->has_inttuner) { + switch (dev->tuner_type) { + case TUNER_XCEIVE_XC3028: + { + struct xc3028_init_cmd cmd; + int gpio_arg; + cmd.new_tv_mode_ptr = dev->tvnorm->tv_mode; + cmd.new_channel_map_ptr = + dev->tvnorm->channelmap; + + if (switch_std) { + if (*id & V4L2_STD_SECAM) + gpio_arg = EM28XX_REG_ON; + else + gpio_arg = EM28XX_REG_OFF; + + em28xx_gpio_control(dev, + EM28XX_XC3028_SECAM, + &gpio_arg); + } + + if (dev->tuner && dev->tuner->tuner_cmd) + dev->tuner->tuner_cmd(dev->tuner, + XC3028_INIT_TUNER, + &cmd); + break; + } + case TUNER_XCEIVE_XC5000: + { + struct xc_std_conf cmd; + cmd.index = dev->tvnorm->index; + dev->tuner->tuner_cmd(dev->tuner, + XC5000_SET_MODE, + &cmd); + break; + } + default: + break; + } + } + + em28xx_i2c_call_clients(dev, VIDIOC_S_STD, + &dev->tvnorm->id); + + return 0; + } + + /* ------ input switching ---------- */ + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + unsigned int n; + static const char *iname[] = { + [EM28XX_VMUX_COMPOSITE1] = "Composite1", + [EM28XX_VMUX_COMPOSITE2] = "Composite2", + [EM28XX_VMUX_COMPOSITE3] = "Composite3", + [EM28XX_VMUX_COMPOSITE4] = "Composite4", + [EM28XX_VMUX_SVIDEO] = "S-Video", + [EM28XX_VMUX_TELEVISION] = "Television", + [EM28XX_VMUX_CABLE] = "Cable TV", + [EM28XX_VMUX_DVB] = "DVB", + [EM28XX_VMUX_DEBUG] = "for debug only", + }; + + n = i->index; + if (n >= MAX_EM28XX_INPUT) + return -EINVAL; + if (0 == INPUT(n)->type) + return -EINVAL; + memset(i, 0, sizeof(*i)); + i->index = n; + i->type = V4L2_INPUT_TYPE_CAMERA; + strcpy(i->name, iname[INPUT(n)->type]); + if ((EM28XX_VMUX_TELEVISION == INPUT(n)->type) || + (EM28XX_VMUX_CABLE == INPUT(n)->type)) + i->type = V4L2_INPUT_TYPE_TUNER; + for (n = 0; dev->board->tvnorms[n].id; n++) + i->std |= dev->board->tvnorms[n].id; + return 0; + } + case VIDIOC_G_INPUT: + { + int *i = arg; + *i = dev->ctl_input; + + return 0; + } + case VIDIOC_S_INPUT: + { + int *index = arg; + + if (*index >= MAX_EM28XX_INPUT) + return -EINVAL; + + if (0 == INPUT(*index)->type) + return -EINVAL; + + video_mux(dev, *index); + + return 0; + } + case VIDIOC_G_AUDIO: + { + struct v4l2_audio *a = arg; + unsigned int index = a->index; + + if (a->index > 1) + return -EINVAL; + memset(a, 0, sizeof(*a)); + index = dev->ctl_ainput; + + if (index == 0) + strcpy(a->name, "Television"); + else + strcpy(a->name, "Line In"); + + a->capability = V4L2_AUDCAP_STEREO; + a->index = index; + return 0; + } + case VIDIOC_S_AUDIO: + { + struct v4l2_audio *a = arg; + + if (a->index != dev->ctl_ainput) + return -EINVAL; + + return 0; + } + + /* --- controls ---------------------------------------------- */ + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *qc = arg; + int i, id = qc->id; + int retval; + if (fh->type == EM28XX_RADIO) + return -EINVAL; + + memset(qc, 0, sizeof(*qc)); + qc->id = id; + + if (!dev->has_msp34xx) { + for (i = 0; i < ARRAY_SIZE(em28xx_qctrl); i++) { + if (qc->id && qc->id == em28xx_qctrl[i].id) { + memcpy(qc, &(em28xx_qctrl[i]), + sizeof(*qc)); + return 0; + } + } + } + if (dev->em28xx_qctrl) { + retval = dev->em28xx_qctrl(arg); + if (retval == 0) + return 0; + } + + em28xx_i2c_call_clients(dev, cmd, qc); + + if (qc->type) + return 0; + else + return -EINVAL; + break; + } + case VIDIOC_G_CTRL: + { + struct v4l2_control *ctrl = arg; + int retval = -EINVAL; + if (fh->type == EM28XX_RADIO) + return -EINVAL; + + if (!dev->has_msp34xx) + retval = em28xx_get_ctrl(dev, ctrl); + if (retval == -EINVAL) { + if (dev->decoder == EM28XX_TVP5150) { + em28xx_i2c_call_clients(dev, cmd, arg); + return 0; + } + + return saa711x_get_ctrl(dev, ctrl); + } else if (dev->em28xx_gctrl) + return dev->em28xx_ctrl(dev, arg); + else + return retval; + } + case VIDIOC_S_CTRL: + { + struct v4l2_control *ctrl = arg; + u8 i; + if (fh->type == EM28XX_RADIO) + return -EINVAL; + + if (!dev->has_msp34xx) { + for (i = 0; i < ARRAY_SIZE(em28xx_qctrl); i++) { + if (ctrl->id == em28xx_qctrl[i].id) { + if (ctrl->value < + em28xx_qctrl[i].minimum + || ctrl->value > + em28xx_qctrl[i].maximum) + return -ERANGE; + return em28xx_set_ctrl(dev, ctrl); + } + } + } + + if (dev->decoder == EM28XX_TVP5150) { + em28xx_i2c_call_clients(dev, cmd, arg); + return 0; + } else if (!dev->has_msp34xx) { + for (i = 0; i < ARRAY_SIZE(em28xx_qctrl); i++) { + if (ctrl->id == em28xx_qctrl[i].id) { + if (ctrl->value < + em28xx_qctrl[i].minimum + || ctrl->value > + em28xx_qctrl[i].maximum) + return -ERANGE; + return em28xx_set_ctrl(dev, ctrl); + } + } + for (i = 0; i < ARRAY_SIZE(saa711x_qctrl); i++) { + if (ctrl->id == saa711x_qctrl[i].id) { + if (ctrl->value < + saa711x_qctrl[i].minimum + || ctrl->value > + saa711x_qctrl[i].maximum) + return -ERANGE; + return saa711x_set_ctrl(dev, ctrl); + } + } + } + + return -EINVAL; + } + /* --- tuner ioctls ------------------------------------------ */ + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *t = arg; + static unsigned int tv_range[2] = { 44, 958 }; + unsigned short int lock_status = 0; + + if (0 != t->index) + return -EINVAL; + + switch (fh->type) { + case EM28XX_RADIO: + t->type = V4L2_TUNER_RADIO; + /* em2882 + cx2584x + xc5000 uses the videodecoder + for radio detection */ + em28xx_i2c_call_clients(dev, cmd, t); + break; + case EM28XX_VBI: + case EM28XX_VIDEO: + memset(t, 0, sizeof(*t)); + strcpy(t->name, "Tuner"); + t->type = V4L2_TUNER_ANALOG_TV; + t->capability |= V4L2_TUNER_CAP_NORM; + t->rangelow = tv_range[0] * 16; + t->rangehigh = tv_range[1] * 16; + + if (dev->tuner && dev->tuner->get_lock_status) { + dev->tuner->get_lock_status(dev->tuner, &lock_status); + if (lock_status == 1) + t->signal = 0xffff; + } else + em28xx_i2c_call_clients(dev, cmd, t); + + em28xx_videodbg("VIDIO_G_TUNER: signal = %x, afc = %x\n", t->signal, + t->afc); + } + return 0; + } + case VIDIOC_S_TUNER: + { + struct v4l2_tuner *t = arg; + + if (0 != t->index) + return -EINVAL; + /* there are only a very few devices with more than one tuner + available, this can be updated to work correctly later */ + return 0; + } + case VIDIOC_G_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + memset(f, 0, sizeof(*f)); + switch (fh->type) { + case EM28XX_RADIO: + f->type = V4L2_TUNER_RADIO; + f->frequency = dev->rctl_freq; + break; + case EM28XX_VIDEO: + case EM28XX_VBI: + f->type = V4L2_TUNER_ANALOG_TV; + f->frequency = dev->vctl_freq; + break; + } + return 0; + } + case VIDIOC_S_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + if (0 != f->tuner) + return -EINVAL; + + if ((fh->type == EM28XX_RADIO && f->type != V4L2_TUNER_RADIO) || + (fh->type == EM28XX_VIDEO && f->type != V4L2_TUNER_ANALOG_TV)) + return -EINVAL; + + switch (fh->type) { + case EM28XX_RADIO: + dev->rctl_freq = f->frequency; + break; + case EM28XX_VIDEO: + case EM28XX_VBI: + dev->vctl_freq = f->frequency; + break; + } + + if (dev->tuner) { + switch (fh->type) { + case EM28XX_VBI: + case EM28XX_VIDEO: + dev->tuner->set_frequency(dev->tuner, (unsigned long)f->frequency*1000/16*1000); + break; + case EM28XX_RADIO: + dev->tuner->set_frequency(dev->tuner, (unsigned long)f->frequency/16*1000); + break; + } + } else + em28xx_i2c_call_clients(dev, VIDIOC_S_FREQUENCY, f); + + return 0; + } +#if 0 /* ioctl is optional */ + case VIDIOC_G_PARM: + { + struct v4l2_captureparm *parm = arg; + memset(parm, 0, sizeof(*parm)); + return 0; + } +#endif + case VIDIOC_CROPCAP: + { + struct v4l2_cropcap *cc = arg; + + if (cc->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + cc->bounds.left = 0; + cc->bounds.top = 0; + cc->bounds.width = dev->width; + cc->bounds.height = dev->height; + cc->defrect = cc->bounds; + cc->pixelaspect.numerator = 54; /* 4:3 FIXME: remove magic numbers */ + cc->pixelaspect.denominator = 59; + return 0; + } + case VIDIOC_STREAMON: + { + int *type = arg; + if (fh->type == EM28XX_VBI) { + dev->vbi_stream = STREAM_ON; + return 0; + } + + if (*type != V4L2_BUF_TYPE_VIDEO_CAPTURE + || dev->video_io != IO_MMAP) + return -EINVAL; +#if 0 + if (list_empty(&dev->inqueue)) + return -EINVAL; +#endif + + dev->video_stream = STREAM_ON; + + em28xx_videodbg("VIDIOC_STREAMON: starting stream\n"); + + return 0; + + } + case VIDIOC_STREAMOFF: + { + int *type = arg; + int ret; + + if (fh->type == EM28XX_VBI) { + dev->vbi_reader = 0; + fh->reader = 0; + if (dev->vbi_stream == STREAM_ON) { + em28xx_videodbg("VIDIOC_STREAMOFF: interrupting stream\n"); + ret = em28xx_stream_interrupt(dev, EM28XX_VBI); + if (ret) + return ret; + } + em28xx_empty_framequeues(dev, EM28XX_VBI); + dev->vbi_stream = STREAM_OFF; + return 0; + } + + if (*type != V4L2_BUF_TYPE_VIDEO_CAPTURE + || dev->video_io != IO_MMAP) + return -EINVAL; + + if (dev->video_stream == STREAM_ON) { + em28xx_videodbg("VIDIOC_STREAMOFF: interrupting stream\n"); + ret = em28xx_stream_interrupt(dev, EM28XX_VIDEO); + if (ret) + return ret; + } + dev->video_reader = 0; + dev->video_stream = STREAM_OFF; + fh->reader = 0; + em28xx_empty_framequeues(dev, EM28XX_VIDEO); + + return 0; + } + default: + return v4l_compat_translate_ioctl(inode, filp, cmd, arg, + driver_ioctl); + } + return 0; +} + +/* + * em28xx_v4l2_do_ioctl() + * This function is _not_ called directly, but from + * em28xx_v4l2_ioctl. Userspace + * copying is done already, arg is a kernel pointer. + */ +static int em28xx_video_do_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, void *arg) +{ + struct em28xx_fh *fh = filp->private_data; + struct em28xx *dev = fh->dev; + + if (!dev) + return -ENODEV; + + if (video_debug > 1) + v4l_print_ioctl(dev->name, cmd); + + switch (cmd) { + + /* --- capabilities ------------------------------------------ */ + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + + memset(cap, 0, sizeof(*cap)); + strlcpy(cap->driver, "em28xx", sizeof(cap->driver)); + strlcpy(cap->card, em28xx_boards[dev->model].name, + sizeof(cap->card)); + strlcpy(cap->bus_info, dev->udev->dev.bus_id, + sizeof(cap->bus_info)); + cap->version = EM28XX_VERSION_CODE; + cap->capabilities = +#if 0 + V4L2_CAP_SLICED_VBI_CAPTURE | +#endif + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_AUDIO | + V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; + if (dev->has_tuner) + cap->capabilities |= V4L2_CAP_TUNER; + if (dev->dev_modes & EM28XX_RADIO) + cap->capabilities |= V4L2_CAP_TUNER; + if (dev->dev_modes & EM28XX_VBI) + cap->capabilities |= V4L2_CAP_VBI_CAPTURE; + return 0; + } + /* --- capture ioctls ---------------------------------------- */ + case VIDIOC_ENUM_FMT: + { + struct v4l2_fmtdesc *fmtd = arg; + int index = fmtd->index; + + if (fmtd->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (index >= ARRAY_SIZE(em28xx_out_fmt)) + return -EINVAL; + + memset(fmtd, 0, sizeof(*fmtd)); + fmtd->index = index; + fmtd->type = em28xx_out_fmt[index].fmt.type; + strcpy(fmtd->description, em28xx_out_fmt[index].fmt.description); + fmtd->pixelformat = em28xx_out_fmt[index].fmt.pixelformat; + return 0; + } + case VIDIOC_G_FMT: + return em28xx_get_fmt(dev, (struct v4l2_format *) arg); + + case VIDIOC_TRY_FMT: + case VIDIOC_S_FMT: + { + struct v4l2_format *fmt = arg; + if ((dev->video_reader == 1 && fh->reader == 0) || + (dev->vbi_reader == 1 && fh->reader == 0)) { + + /* this is something to struggle around however you need it + mplayer tries to set 640x480 and ongoing formats till + it hits 720x576 if requested, if the first try will fail + the ongoing tries will also fail and mplayer will not + work with the em28xx driver + + the enabled code is mplayer aware, commented out code + would be a small workaround not perfect either but + slightly better. + */ + if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + fmt->fmt.pix.height = dev->height; + fmt->fmt.pix.width = dev->width; + return 0; +#if 0 + if (dev->width == (fmt->fmt.pix.width & 0xfffe) && + dev->height == (fmt->fmt.pix.height & 0xfffe)) { + fmt->fmt.pix.height &= 0xfffe; + fmt->fmt.pix.width &= 0xfffe; + printk(KERN_INFO"everything ok not changing anything and returning 0\n"); + return 0; + } +#endif + } + printk("em28xx-video.c: device is currently busy!\n"); + return -EBUSY; + } + return em28xx_set_fmt(dev, cmd, (struct v4l2_format *)arg); + } + + /* obsolete v4l1 implementation, but it's still used by some applications - + * it should be implemented but we don't support it + */ + + case VIDIOCGMBUF: + { + struct video_mbuf *mbuf = arg; + int i; + + int gbuffers = EM28XX_NUM_READ_FRAMES; + int gbufsize = PAGE_ALIGN(dev->frame_size); /* max resolution of em28xx based devices */ + + if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + return -EINVAL; + } + + if (dev->video_io != IO_NONE) { + em28xx_videodbg("method is set to read;" + " close and open the device again to" + " choose the mmap I/O method\n"); + printk(KERN_INFO"VIDEO_IO is busy\n"); + return -EBUSY; + } + + memset(mbuf, 0, sizeof(*mbuf)); + mbuf->frames = gbuffers; + + mbuf->size = gbufsize * gbuffers; + for (i = 0; i < gbuffers; i++) { + mbuf->offsets[i] = i*gbufsize; + } + + /* FIXME: setting resolution using the v4l1 PICT command */ + + em28xx_empty_framequeues(dev, EM28XX_VIDEO); + em28xx_release_buffers(dev, EM28XX_VIDEO); + + if (!em28xx_request_buffers(dev, EM28XX_NUM_READ_FRAMES, EM28XX_VIDEO)) { + printk(KERN_INFO"em28xx-video.c: unable to map" + " buffers\n"); + return -ENOMEM; + } + + dev->video_stream = STREAM_ON; + em28xx_empty_framequeues(dev, EM28XX_VIDEO); + dev->frame_current = NULL; + dev->video_io = IO_MMAP; + return 0; + } + case VIDIOCMCAPTURE: + { + struct video_mmap *vm = arg; + unsigned long lock_flags; + + if (vm->format != VIDEO_PALETTE_YUV422) + return -EINVAL; + + if (dev->frame[vm->frame].state != F_UNUSED) { + return -EAGAIN; + } + + dev->frame[vm->frame].state = F_QUEUED; + + spin_lock_irqsave(&dev->queue_lock, lock_flags); + list_add_tail(&dev->frame[vm->frame].frame, &dev->inqueue); + spin_unlock_irqrestore(&dev->queue_lock, lock_flags); + return 0; + } + case VIDIOCSYNC: + { + /* int *frame = arg; FIXME - argument not used?*/ + struct em28xx_frame_t *f; + unsigned long lock_flags; + int ret = 0; + + if (list_empty(&dev->outqueue)) { + if (dev->video_stream == STREAM_OFF) { + return -EINVAL; + } + if (filp->f_flags & O_NONBLOCK) { + return -EAGAIN; + } + ret = wait_event_interruptible( + dev->wait_frame, + (!list_empty(&dev->outqueue)) || + (dev->state&DEV_DISCONNECTED)); + if (ret) { + return ret; + } + if (dev->state & DEV_DISCONNECTED) { + return -ENODEV; + } + } + + spin_lock_irqsave(&dev->queue_lock, lock_flags); + f = list_entry(dev->outqueue.next, struct em28xx_frame_t, frame); + list_del(dev->outqueue.next); + spin_unlock_irqrestore(&dev->queue_lock, lock_flags); + f->state = F_UNUSED; + return 0; + } + + /* end v4l1 compat code */ + + case VIDIOC_REQBUFS: + { + struct v4l2_requestbuffers *rb = arg; + u32 i; + int ret; + + if (rb->type == V4L2_BUF_TYPE_VBI_CAPTURE && + rb->memory == V4L2_MEMORY_MMAP) { + if (dev->vbi_reader == 1 && fh->reader == 0) + return -EBUSY; + + if (dev->vbi_io == IO_READ) { + printk("method is set to read;" + " close and open the device again to" + " choose the mmap I/O method\n"); + return -EBUSY; + } + em28xx_empty_framequeues(dev, EM28XX_VBI); + + if (dev->vbi_stream == STREAM_ON) { + em28xx_videodbg("VIDIOC_REQBUFS: interrupting stream\n"); + ret = em28xx_stream_interrupt(dev, EM28XX_VBI); + if (ret) { + printk(KERN_INFO"em28xx-video.c: VIDIOC_REQBUFS 4\n"); + return ret; + } + } + + em28xx_release_buffers(dev, EM28XX_VBI); + + if (rb->count) + rb->count = em28xx_request_buffers(dev, rb->count, EM28XX_VBI); + + dev->vbi_frame_current = NULL; + + dev->vbi_stream = STREAM_ON; + em28xx_videodbg("VIDIOC_REQBUFS: setting io method to mmap: num bufs %i\n", + rb->count); + + dev->vbi_io = rb->count ? IO_MMAP : IO_NONE; + fh->reader = 1; + dev->vbi_reader = 1; + return 0; + } + + if (rb->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + rb->memory != V4L2_MEMORY_MMAP) { + return -EINVAL; + } + + if (dev->video_io == IO_READ) { + em28xx_videodbg("method is set to read;" + " close and open the device again to" + " choose the mmap I/O method\n"); + printk(KERN_INFO"em28xx-video.c: VIDIOC_REQBUFS 2\n"); + return -EBUSY; + } + + for (i = 0; i < dev->num_frames; i++) + if (dev->frame[i].vma_use_count) { + em28xx_videodbg("VIDIOC_REQBUFS failed; previous buffers are still mapped\n"); + printk(KERN_INFO"em28xx-video.c: VIDIOC_REQBUFS 3\n"); + return -EINVAL; + } + + if (dev->video_stream == STREAM_ON) { + em28xx_videodbg("VIDIOC_REQBUFS: interrupting stream\n"); + ret = em28xx_stream_interrupt(dev, EM28XX_VIDEO); + if (ret) { + printk(KERN_INFO"em28xx-video.c: VIDIOC_REQBUFS 4\n"); + return ret; + } + } + + em28xx_empty_framequeues(dev, EM28XX_VIDEO); + + em28xx_release_buffers(dev, EM28XX_VIDEO); + if (rb->count) + rb->count = + em28xx_request_buffers(dev, rb->count, EM28XX_VIDEO); + + dev->frame_current = NULL; + + dev->video_stream = STREAM_ON; + em28xx_videodbg("VIDIOC_REQBUFS: setting io method to mmap: num bufs %i\n", + rb->count); + dev->video_io = rb->count ? IO_MMAP : IO_NONE; + return 0; + } + case VIDIOC_QUERYBUF: + { + struct v4l2_buffer *b = arg; + + if (b->type == V4L2_BUF_TYPE_VBI_CAPTURE && + b->index < dev->vbi_num_frames && + dev->vbi_io == IO_MMAP) { + memcpy(b, &dev->vbi_frame[b->index].buf, sizeof(*b)); + + if (dev->vbi_frame[b->index].vma_use_count) + b->flags |= V4L2_BUF_FLAG_MAPPED; + + if (dev->frame[b->index].state == F_DONE) + b->flags |= V4L2_BUF_FLAG_DONE; + else if (dev->frame[b->index].state != F_UNUSED) + b->flags |= V4L2_BUF_FLAG_QUEUED; + + return 0; + } + + if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + b->index >= dev->num_frames || dev->video_io != IO_MMAP) + return -EINVAL; + + memcpy(b, &dev->frame[b->index].buf, sizeof(*b)); + + if (dev->frame[b->index].vma_use_count) + b->flags |= V4L2_BUF_FLAG_MAPPED; + + if (dev->frame[b->index].state == F_DONE) + b->flags |= V4L2_BUF_FLAG_DONE; + else if (dev->frame[b->index].state != F_UNUSED) + b->flags |= V4L2_BUF_FLAG_QUEUED; + + return 0; + } + case VIDIOC_QBUF: + { + struct v4l2_buffer *b = arg; + unsigned long lock_flags; + + if (b->type == V4L2_BUF_TYPE_VBI_CAPTURE && + b->index < dev->vbi_num_frames && + dev->vbi_io == IO_MMAP) { + if (dev->vbi_frame[b->index].state != F_UNUSED) + return -EAGAIN; + + dev->vbi_frame[b->index].state = F_QUEUED; + + spin_lock_irqsave(&dev->queue_lock, lock_flags); + list_add_tail(&dev->vbi_frame[b->index].frame, + &dev->vbi_inqueue); + spin_unlock_irqrestore(&dev->queue_lock, lock_flags); + return 0; + } + + if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + b->index >= dev->num_frames || dev->video_io != IO_MMAP) { + return -EINVAL; + } + + if (dev->frame[b->index].state != F_UNUSED) + return -EAGAIN; + + dev->frame[b->index].state = F_QUEUED; + + /* add frame to fifo */ + spin_lock_irqsave(&dev->queue_lock, lock_flags); + list_add_tail(&dev->frame[b->index].frame, + &dev->inqueue); + spin_unlock_irqrestore(&dev->queue_lock, lock_flags); + + return 0; + } + case VIDIOC_DQBUF: + { + struct v4l2_buffer *b = arg; + struct em28xx_frame_t *f; + unsigned long lock_flags; + int ret = 0; + + if (b->type == V4L2_BUF_TYPE_VBI_CAPTURE && + b->index < dev->vbi_num_frames && + dev->vbi_io == IO_MMAP) { + if (list_empty(&dev->vbi_outqueue)) { + if (dev->vbi_stream == STREAM_OFF) + return -EINVAL; + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + ret = wait_event_interruptible(dev->wait_vbi_frame, (!list_empty(&dev->vbi_outqueue)) || + (dev->state & DEV_DISCONNECTED)); + + if (ret) + return ret; + + if (dev->state & DEV_DISCONNECTED) + return -ENODEV; + } + + spin_lock_irqsave(&dev->queue_lock, lock_flags); + f = list_entry(dev->vbi_outqueue.next, + struct em28xx_frame_t, frame); + list_del(dev->vbi_outqueue.next); + spin_unlock_irqrestore(&dev->queue_lock, lock_flags); + + f->state = F_UNUSED; + memcpy(b, &f->buf, sizeof(*b)); + + if (f->vma_use_count) + b->flags |= V4L2_BUF_FLAG_MAPPED; + + return 0; + } + + if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE + || dev->video_io != IO_MMAP) + return -EINVAL; + + if (list_empty(&dev->outqueue)) { + if (dev->video_stream == STREAM_OFF) + return -EINVAL; + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + ret = wait_event_interruptible + (dev->wait_frame, + (!list_empty(&dev->outqueue)) || + (dev->state & DEV_DISCONNECTED)); + if (ret) + return ret; + if (dev->state & DEV_DISCONNECTED) + return -ENODEV; + } + + spin_lock_irqsave(&dev->queue_lock, lock_flags); + f = list_entry(dev->outqueue.next, + struct em28xx_frame_t, frame); + list_del(dev->outqueue.next); + spin_unlock_irqrestore(&dev->queue_lock, lock_flags); + + f->state = F_UNUSED; + memcpy(b, &f->buf, sizeof(*b)); + + if (f->vma_use_count) + b->flags |= V4L2_BUF_FLAG_MAPPED; + + return 0; + } + default: + return em28xx_do_ioctl(inode, filp, dev, cmd, arg, + em28xx_video_do_ioctl); + } + return 0; +} + +/* + * em28xx_v4l2_ioctl() + * handle v4l2 ioctl the main action happens in em28xx_v4l2_do_ioctl() + */ +static int em28xx_v4l2_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + struct em28xx_fh *fh = filp->private_data; + struct em28xx *dev = fh->dev; + + + if (dev->state & DEV_DISCONNECTED) { + em28xx_errdev("v4l2 ioctl: device not present\n"); + return -ENODEV; + } + + if (dev->state & DEV_MISCONFIGURED) { + em28xx_errdev + ("v4l2 ioctl: device is misconfigured; close and open it again\n"); + return -EIO; + } + + ret = video_usercopy(inode, filp, cmd, arg, em28xx_video_do_ioctl); + + return ret; +} + +static struct file_operations em28xx_v4l_fops = { + .owner = THIS_MODULE, + .open = em28xx_v4l2_open, + .release = em28xx_v4l2_close, + .ioctl = em28xx_v4l2_ioctl, + .read = em28xx_v4l2_read, + .poll = em28xx_v4l2_poll, + .mmap = em28xx_v4l2_mmap, + .llseek = no_llseek, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11) +#if 0 + .compat_ioctl = v4l_compat_ioctl32, +#endif +#endif + +}; + +int em28xx_aad_control(void *priv, unsigned int command, void *ptr) { + struct em28xx *dev = (struct em28xx*)priv; + int ret = 0; + switch(command) { + case EM28XX_ENABLE_AUDIO: + ret = dev->em28xx_write_reg_bits(dev, R0F_XCLK_REG, 0x80, 0x80); + if(dev->alt == 0 ) { + int errCode; + dev->alt = 7; + errCode = usb_set_interface(dev->udev, dev->usb_interface, 7); + printk("changing alternate number to 7\n"); + } + break; + case EM28XX_DISABLE_AUDIO: + if(dev->alt != 0) { + int errCode; + dev->alt = 0; + errCode = usb_set_interface(dev->udev, dev->usb_interface, 0); + printk("changing alternate number to 0\n"); + } + break; + default: + break; + } + return ret; +} + +/******************************** usb interface *****************************************/ + +/* + * em28xx_init_dev() + * allocates and inits the device structs, registers i2c bus and v4l device + */ +static int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev, + int minor, int model) +{ + struct em28xx *dev = *devhandle; + int retval = -ENOMEM; + int errCode, i; + unsigned int maxh, maxw; + int ret; + int gpio_arg; + struct list_head *pos = NULL; + struct em28xx_ops *ops = NULL; + u8 value; + + dev->model = model; + dev->dvb_dev = NULL; + init_waitqueue_head(&dev->open); + + mutex_init(&dev->input_lock); + + dev->em_type = em28xx_boards[model].em_type; + dev->has_tuner = em28xx_boards[model].has_tuner; + dev->has_inttuner = em28xx_boards[model].has_inttuner; + dev->powersaving = em28xx_boards[model].powersaving; + dev->has_msp34xx = em28xx_boards[model].has_msp34xx; + dev->tda9887_conf = em28xx_boards[model].tda9887_conf; + dev->decoder = em28xx_boards[model].decoder; + dev->em28xx_ctrl = em28xx_boards[model].ctrl; + dev->em28xx_qctrl = em28xx_boards[model].qctrl; + dev->em28xx_gctrl = em28xx_boards[model].gctrl; + dev->dev_modes = em28xx_boards[model].dev_modes; + dev->board = &em28xx_boards[model]; + dev->outfmt = &em28xx_out_fmt[0]; /* default to YUYV */ + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17) + INIT_DELAYED_WORK(&dev->request_module_wk, request_module_async); +#endif + dev->tvnorm = &em28xx_boards[model].tvnorms[0]; + dev->dvbnorm = &em28xx_boards[model].dvbnorms[0]; + dev->fmnorm = &em28xx_boards[model].fmnorms[0]; + dev->qamnorm = &em28xx_boards[model].qamnorms[0]; + dev->atscnorm = &em28xx_boards[model].atscnorms[0]; + + dev->vbi_interlaced = vbi_interlaced; + + dev->em28xx_gpio_control = em28xx_gpio_control; + dev->manual_gpio = em28xx_boards[model].manual_gpio; + dev->em28xx_aad_control = em28xx_aad_control; + + /* check if gpio config is stored onboard */ + dev->qamnorm = &em28xx_boards[model].qamnorms[0]; + dev->atscnorm = &em28xx_boards[model].atscnorms[0]; + + em28xx_write_regs_req(dev, 0x02, 0xa0, "\x23", 1); + ret = dev->em28xx_read_reg(dev, 0x05); + + if (ret == 0) { + value = em28xx_read_reg_req(dev, 0x2, 0xa0); + /* keep it overridable */ + if (dev->manual_gpio == 0) + dev->manual_gpio = (em28xx_read_reg_req(dev, 0x2, 0xa0)>>3)&1; + } + + + if (dev->udev == NULL) + dev->udev = udev; + + if (dev->dev_modes == EM28XX_DVBT || dev->dev_modes == EM28XX_ATSC) + dev->device_mode = 1; + else + dev->device_mode = device_mode; + + if (vbi_mode == 0) + dev->dev_modes &= ~EM28XX_VBI; + + if (dev->dev_modes & EM28XX_VBI) + em28xx_set_vbi(dev, 1); + + if (tuner >= 0) + dev->tuner_type = tuner; + else + dev->tuner_type = em28xx_boards[model].tuner_type; + + /* Do board specific init and eeprom reading */ + ret = em28xx_card_setup(dev); + + if (ret) + return ret; + + gpio_arg = EM28XX_REG_OFF; + em28xx_gpio_control(dev, EM28XX_LED1_ON, &gpio_arg); + em28xx_gpio_control(dev, EM28XX_TS1_ON, &gpio_arg); + em28xx_gpio_control(dev, EM28XX_ANALOG_ON, &gpio_arg); + em28xx_gpio_control(dev, EM28XX_XC3028_SECAM, &gpio_arg); + em28xx_gpio_control(dev, EM28XX_TUNER2_ON, &gpio_arg); + em28xx_gpio_control(dev, EM28XX_DECODER_SLEEP, &gpio_arg); + mdelay(100); + gpio_arg = EM28XX_REG_ON; + em28xx_gpio_control(dev, EM28XX_ANALOG_ON, &gpio_arg); + em28xx_gpio_control(dev, EM28XX_TUNER1_ON, &gpio_arg); + mdelay(100); + + em28xx_gpio_control(dev, EM28XX_DECODER_RESET, NULL); + +#ifdef CONFIG_MODULES + /* request some modules */ + if (dev->has_tuner && dev->has_inttuner == 0) + request_module("tuner"); + if (dev->decoder == EM28XX_SAA7113 || dev->decoder == EM28XX_SAA7114) + request_module("saa7115"); + if (dev->decoder == EM28XX_TVP5150) + request_module("tvp5150"); + if (dev->tda9887_conf) + request_module("tda9887"); + if (dev->decoder == EM28XX_CX25843) + request_module("em28xx-cx25843"); +#endif + spin_lock_init(&dev->queue_lock); + spin_lock_init(&dev->vbi_queue_lock); + init_waitqueue_head(&dev->wait_frame); + init_waitqueue_head(&dev->wait_vbi_frame); + init_waitqueue_head(&dev->video_wait_stream); + init_waitqueue_head(&dev->vbi_wait_stream); + + if (ret < 0) { + printk("%s:%u:%s(): em28xx_card_setup() failed, ret = %i\n", __FILE__, __LINE__, __FUNCTION__, ret); + return ret; + } + + em28xx_i2c_register(dev); + + em28xx_config_i2c(dev); + + if (dev->has_inttuner) { + switch (dev->tuner_type) { + case TUNER_XCEIVE_XC3028: + { + struct xc3028_config config; + memset(&config, 0x0, sizeof(struct xc3028_config)); + config.new_tv_mode_ptr = em28xx_boards[model].tvnorms[0].tv_mode; + config.new_channel_map_ptr = em28xx_boards[model].tvnorms[0].channelmap; + config.callback = em28xx_gpio_control_translate; + config.dev = dev; + config.adap = &dev->i2c_adap; + config.i2c_address = 0xc2>>1; +#if 0 + em28xx_gpio_control(dev, EM28XX_XC3028_SECAM, arg); +#endif + dev->tuner = tuner_chip_attach(xc3028_tuner_attach, &config); + break; + } + case TUNER_XCEIVE_XC5000: + { + struct xc5000_config config; + printk("trying to attach xc5000\n"); + memset(&config, 0x0, sizeof(struct xc5000_config)); + config.callback = em28xx_gpio_control_translate; + config.dev = dev; + config.adap = &dev->i2c_adap; + config.i2c_address = 0xc2>>1; + dev->tuner = tuner_chip_attach(xc5000_tuner_attach, &config, XC5000_firmware_SEQUENCE); + break; + } + } + + /* configure the device */ + + if (dev->tuner == NULL) + printk(KERN_INFO"unable to attach tuner\n"); + else + printk(KERN_INFO"successfully attached tuner\n"); + } + + dev->em28xx_acquire = em28xx_acquire; + + dev->em28xx_acquire(dev, EM28XX_LOCK, 1); + + list_add_tail(&dev->devlist, &em28xx_devlist); + + if (dev->dev_modes&EM28XX_RADIO) { + dev->rdev = video_device_alloc(); + memset(dev->rdev, 0x0, sizeof(struct video_device)); + + if (NULL == dev->rdev) { + list_del(&dev->devlist); + kfree(dev); + return -ENOMEM; + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27) + dev->rdev->type = VID_TYPE_TUNER; + dev->rdev->dev = &dev->udev->dev; +#else + dev->rdev->vfl_type = VID_TYPE_TUNER; + dev->rdev->parent = &dev->udev->dev; +#endif + dev->rdev->fops = &em28xx_v4l_fops; + dev->rdev->minor = -1; + dev->rdev->release = video_device_release; + snprintf(dev->rdev->name, sizeof(dev->rdev->name), "%s#%d %s", + "em28xx", dev->devno, "radio"); + + if (video_register_device(dev->rdev, VFL_TYPE_RADIO, + radio_nr[dev->devno]) < 0) { + printk(KERN_INFO"unable to register radio device\n"); + list_del(&dev->devlist); + kfree(dev); + return -ENODEV; + } + printk(KERN_INFO"radio device registered as /dev/radio%d\n", + dev->rdev->minor & 0x1f); + } + + if (dev->dev_modes&EM28XX_VIDEO) { + + dev->video_inputs = em28xx_boards[model].vchannels; + + dev->tvnorm = &em28xx_boards[model].tvnorms[0]; /* set default norm */ + for (i = 0; em28xx_boards[model].tvnorms[i].id; i++) + if (em28xx_boards[model].norm == em28xx_boards[model].tvnorms[i].id) { + dev->tvnorm = &em28xx_boards[model].tvnorms[i]; /* set default norm */ + break; + } + + em28xx_videodbg("tvnorm = %s\n", dev->tvnorm->name); + + maxw = norm_maxw(dev); + maxh = norm_maxh(dev); + + /* set default image size */ + dev->width = maxw; + dev->height = maxh; + dev->interlaced = EM28XX_INTERLACED_DEFAULT; + dev->field_size = dev->width * dev->height; + dev->frame_size = + dev->interlaced ? dev->field_size << 1 : dev->field_size; + dev->bytesperline = dev->width * 2; + dev->hscale = 0; + dev->vscale = 0; + dev->ctl_input = 2; + + if (dev->dev_modes&EM28XX_VIDEO) { + errCode = em28xx_config(dev); /* TODO: check for errorvalue */ + if (errCode) { + em28xx_errdev("error configuring device\n"); + em28xx_devused &= ~(1<devno); + list_del(&dev->devlist); + kfree(dev); + return -ENOMEM; + } + } + +#ifdef CONFIG_MODULES + /* FIXME: is this module really required? some devices just spit errors + when using that modules */ + if (dev->has_msp34xx) + request_module("msp3400"); +#endif + + /* allocate and fill v4l2 device struct */ + dev->vdev = video_device_alloc(); + if (NULL == dev->vdev) { + em28xx_errdev("cannot allocate video_device.\n"); + em28xx_devused &= ~(1<devno); + list_del(&dev->devlist); + kfree(dev); + return -ENOMEM; + } + memset(dev->vdev, 0x0, sizeof(struct video_device)); + + + /* Fills CAPTURE device info */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27) + dev->vdev->type = VID_TYPE_CAPTURE; + if (dev->has_tuner) + dev->vdev->type |= VID_TYPE_TUNER; + dev->vdev->dev = &dev->udev->dev; +#else + dev->vdev->vfl_type = VID_TYPE_CAPTURE; + if (dev->has_tuner) + dev->vdev->vfl_type |= VID_TYPE_TUNER; + dev->vdev->parent = &dev->udev->dev; +#endif + dev->vdev->fops = &em28xx_v4l_fops; + dev->vdev->minor = -1; + dev->vdev->release = video_device_release; + snprintf(dev->vdev->name, sizeof(dev->vbi_dev->name), "%s#%d %s", + "em28xx", dev->devno, "video"); + + /* register v4l2 device */ + if ((retval = video_register_device(dev->vdev, VFL_TYPE_GRABBER, + video_nr[dev->devno]))) { + em28xx_errdev("unable to register video device (error = %i).\n", + retval); + list_del(&dev->devlist); + video_device_release(dev->vdev); + em28xx_devused &= ~(1<devno); + kfree(dev); + return -ENODEV; + } + if (dev->dev_modes&EM28XX_VBI) { + + dev->vbi_dev = video_device_alloc(); + if (NULL == dev->vbi_dev) { + em28xx_errdev("cannot allocate video_device.\n"); + em28xx_devused &= ~(1<devno); + list_del(&dev->devlist); + video_device_release(dev->vdev); + kfree(dev); + return -ENOMEM; + } + memset(dev->vbi_dev, 0x0, sizeof(struct video_device)); + + /* Fills VBI device info */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27) + dev->vbi_dev->type = VFL_TYPE_VBI; + dev->vbi_dev->dev = &dev->udev->dev; +#else + dev->vbi_dev->vfl_type = VFL_TYPE_VBI; + dev->vbi_dev->parent = &dev->udev->dev; +#endif + dev->vbi_dev->fops = &em28xx_v4l_fops; + dev->vbi_dev->minor = -1; + dev->vbi_dev->release = video_device_release; + snprintf(dev->vbi_dev->name, sizeof(dev->vbi_dev->name), "%s#%d %s", + "em28xx", dev->devno, "vbi"); + + if (video_register_device(dev->vbi_dev, VFL_TYPE_VBI, + vbi_nr[dev->devno]) < 0) { + printk(KERN_INFO"unable to register vbi device\n"); + list_del(&dev->devlist); + video_device_release(dev->vbi_dev); + video_device_release(dev->vdev); + em28xx_devused &= ~(1<devno); + kfree(dev); + return -ENODEV; + } + em28xx_info("V4L2 VBI device registered as /dev/vbi%d\n", + dev->vbi_dev->num); + } + video_mux(dev, 0); + em28xx_info("V4L2 device registered as /dev/video%d\n", + dev->vdev->num); + } + + /* when reattaching the device reinitialize the attached submodules */ + mutex_lock(&em28xx_extension_devlist_lock); + if (!list_empty(&em28xx_extension_devlist)) { + list_for_each(pos, &em28xx_extension_devlist) { + ops = list_entry(pos, struct em28xx_ops, next); + if (ops->id & dev->dev_modes) + ops->init(dev); + } + } + mutex_unlock(&em28xx_extension_devlist_lock); + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17) + request_modules(dev); +#else + dev->em28xx_acquire(dev, EM28XX_LOCK, 0); +#endif + if (dev->board->ir_i2c == 0 && + dev->board->ir_keytab && + dev->board->ir_getkey) + em2880_ir_attach(dev); + + return 0; +} + +/* + * em28xx_generic_probe() + * probes generic em28xx devices + * TODO: add em2800 based card detection (cinergy 200) + * + */ + +struct drequest_t { + int brequest; + int index; + char *buffer; + int retval; + int blen; + int msecs; +}; + +static int em28xx_generic_probe(int *model, struct usb_device *udev, + struct em28xx *dev) +{ + int i; + char buffer = -1; + char *sendbuf; + int ret; + int rv = 0; + switch ((*model)) { + case EM2870_BOARD_TERRATEC_XS: + { + sendbuf = kmalloc(1, GFP_KERNEL); + memcpy(sendbuf, "\x00", 1); + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x02, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x00c0, sendbuf, 1, HZ); + kfree(sendbuf); + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x02, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x00c0, &buffer, 1, HZ); + if (buffer == 0x63) { + (*model) = EM2870_BOARD_TERRATEC_XS_MT2060; + printk(KERN_INFO"em28xx-video.c: New Terratec XS Detected\n"); + } + } + return 0; + case EM2800_BOARD_LEADTEK_WINFAST_USBII: + { + /* seems like leadtek didn't change the product id for em2820/em2800 based + devices */ + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x000a, &buffer, 1, HZ); + switch (buffer) { + case 0x12: + printk("EM2820 based device detected\n"); + (*model) = EM2820_BOARD_LEADTEK_WINFAST_USBII_DELUXE; + return 0; + } + } + case EM2800_BOARD_GENERIC: + { + u8 i2c_devs[128]; + int nr_i2c_devs = 0; + printk(KERN_INFO"em28xx: generic EM2800 board trying to guess card by i2c addresses\n"); + /* do a i2c scan */ + for (i = 0; i < 128; i++) { + if (!em2800_i2c_check_for_device(dev, i<<1)) + i2c_devs[nr_i2c_devs++] = i<<1; + } + /* something went wrong */ + if (!nr_i2c_devs) + break; + /* now loop through all em2800 cards */ + for (i = 0; i < em28xx_bcount; i++) { + /* if the card definition contains a list of i2c devices */ + if (em28xx_boards[i].em_type == EM2800 && + em28xx_boards[i].i2c_devs) { + int x; + /* compare the list to the scan results */ + for (x = 0; x < nr_i2c_devs && + em28xx_boards[i].i2c_devs[x]; + x++) { + if (i2c_devs[x] != + em28xx_boards[i].i2c_devs[x]) + break; + } + if (x == nr_i2c_devs && !em28xx_boards[i].i2c_devs[x]) { + (*model) = i; + return 0; + } + } + } + } + break; + case EM2861_BOARD_GENERIC: + { + struct drequest_t yakumo[] = { + {0x03, 0xb8, "\x00", 0x00, 1, 0} + /* this is the only i2c port which is + available */ + }; + for (i = 0, rv = 0; i < ARRAY_SIZE(yakumo); i++) { + sendbuf = kmalloc(yakumo[i].blen, GFP_KERNEL); + memcpy(sendbuf, yakumo[i].buffer, yakumo[i].blen); + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), yakumo[i].brequest, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, yakumo[i].index, sendbuf, yakumo[i].blen, HZ); + kfree(sendbuf); + if (yakumo[i].msecs) + msleep(yakumo[i].msecs); + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x05, &buffer, 1, HZ); + if (buffer != yakumo[i].retval) { + rv = 1; + break; + } + } + if (rv == 0) { + (*model) = EM2861_BOARD_YAKUMO_MOVIE_MIXER; + return 0; + } + + } + break; + case EM2820_BOARD_GENERIC: + { + struct drequest_t videology[] = { /* todo videology + has a device + identifier + somewhere in the + eeprom */ + {0x03, 0xa2, "\x00", 0x00, 1, 0}, + {0x03, 0xa0, "\x00", 0x00, 1, 0}, + {0x03, 0x68, "\x00", 0x00, 1, 0}, + {0x03, 0xa4, "\x00", 0x00, 1, 0}, + {0x03, 0xa6, "\x00", 0x00, 1, 0}, + {0x03, 0x66, "\x00", 0x10, 1, 0}, + {0x03, 0x86, "\x00", 0x10, 1, 0}, + {0x03, 0xc0, "\x00", 0x10, 1, 0}, + {0x03, 0xc2, "\x00", 0x10, 1, 0}, + }; + + struct drequest_t hercules_stv[] = { + {0x03, 0x42, "\x00", 0x10, 1, 0}, + {0x03, 0x4a, "\x00", 0x00, 1, 0}, + {0x03, 0x60, "\x00", 0x00, 1, 0}, + {0x03, 0x66, "\x00", 0x10, 1, 0}, + {0x03, 0x68, "\x00", 0x10, 1, 0}, + {0x03, 0x86, "\x00", 0x10, 1, 0}, + {0x03, 0xa0, "\x00", 0x00, 1, 0}, + {0x03, 0xa2, "\x00", 0x10, 1, 0}, + {0x03, 0xc0, "\x00", 0x10, 1, 0}, + {0x03, 0xc2, "\x00", 0x10, 1, 0}, + {0x03, 0xc6, "\x00", 0x00, 1, 0}, + }; + + struct drequest_t gadmei_utv_310[] = { + {0x03, 0x42, "\x00", 0x10, 1, 0}, + {0x03, 0x4a, "\x00", 0x00, 1, 0}, + {0x03, 0x60, "\x00", 0x10, 1, 0}, + {0x03, 0x66, "\x00", 0x10, 1, 0}, + {0x03, 0x68, "\x00", 0x10, 1, 0}, + {0x03, 0x86, "\x00", 0x10, 1, 0}, + {0x03, 0xa0, "\x00", 0x10, 1, 0}, + {0x03, 0xa2, "\x00", 0x10, 1, 0}, + {0x03, 0xc0, "\x00", 0x10, 1, 0}, + {0x03, 0xc2, "\x00", 0x10, 1, 0}, + }; + + struct drequest_t msi_vox[] = { + {0x03, 0x42, "\x00", 0x00, 1, 0}, + {0x03, 0x4a, "\x00", 0x10, 1, 0}, + {0x03, 0x86, "\x00", 0x00, 1, 0}, + {0x03, 0xa0, "\x00", 0x10, 1, 0}, + {0x03, 0xa2, "\x00", 0x10, 1, 0}, + {0x03, 0xc0, "\x00", 0x00, 1, 0}, + {0x03, 0xc2, "\x00", 0x00, 1, 0}, + }; + struct drequest_t msi_vox_ntsc[] = { + {0x03, 0x42, "\x00", 0x00, 1, 0}, +/* {0x03, 0x66, "\x00", 0x00, 1, 0}, */ +/* {0x03, 0x68, "\x00", 0x00, 1, 0}, */ + {0x03, 0xa0, "\x00", 0x10, 1, 0}, + {0x03, 0xa2, "\x00", 0x10, 1, 0}, + {0x03, 0xc0, "\x00", 0x00, 1, 0}, + {0x03, 0xc2, "\x00", 0x00, 1, 0}, + }; + + + for (i = 0, rv = 0; i < ARRAY_SIZE(videology); i++) { + /* usb_control_msg() expects kmalloced memory, otherwise the host controller will die */ + sendbuf = kmalloc(videology[i].blen, GFP_KERNEL); + memcpy(sendbuf, videology[i].buffer, videology[i].blen); + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), videology[i].brequest, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, videology[i].index, sendbuf, videology[i].blen, HZ); + kfree(sendbuf); + if (videology[i].msecs) + msleep(videology[i].msecs); + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x05, &buffer, 1, HZ); + if (buffer != videology[i].retval) { + rv = 1; + break; + } + } + if (rv == 0) { + (*model) = EM2820_BOARD_VIDEOLOGY_20K14XUSB; + return 0; + } + + + for (i = 0, rv = 0; i < ARRAY_SIZE(msi_vox); i++) { + sendbuf = kmalloc(msi_vox[i].blen, GFP_KERNEL); + memcpy(sendbuf, msi_vox[i].buffer, msi_vox[i].blen); + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), msi_vox[i].brequest, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, msi_vox[i].index, sendbuf, msi_vox[i].blen, HZ); + kfree(sendbuf); + if (msi_vox[i].msecs) + msleep(msi_vox[i].msecs); + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x05, &buffer, 1, HZ); + if (buffer != msi_vox[i].retval) { + rv = 1; + break; + } + } + if (rv == 0) { + (*model) = EM2820_BOARD_MSI_VOX_USB_2; + return 0; + } + + for (i = 0, rv = 0; i < ARRAY_SIZE(hercules_stv); i++) { + sendbuf = kmalloc(hercules_stv[i].blen, GFP_KERNEL); + memcpy(sendbuf, hercules_stv[i].buffer, hercules_stv[i].blen); + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), hercules_stv[i].brequest, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, hercules_stv[i].index, sendbuf, hercules_stv[i].blen, HZ); + kfree(sendbuf); + if (hercules_stv[i].msecs) + msleep(hercules_stv[i].msecs); + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x05, &buffer, 1, HZ); + if (buffer != hercules_stv[i].retval) { + rv = 1; + break; + } + } + if (rv == 0) { + (*model) = EM2820_BOARD_HERCULES_SMART_TV_USB2; + return 0; + } + + for (i = 0, rv = 0; i < ARRAY_SIZE(gadmei_utv_310); i++) { + sendbuf = kmalloc(gadmei_utv_310[i].blen, GFP_KERNEL); + memcpy(sendbuf, gadmei_utv_310[i].buffer, gadmei_utv_310[i].blen); + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), gadmei_utv_310[i].brequest, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, gadmei_utv_310[i].index, sendbuf, gadmei_utv_310[i].blen, HZ); + kfree(sendbuf); + if (gadmei_utv_310[i].msecs) + msleep(gadmei_utv_310[i].msecs); + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x05, &buffer, 1, HZ); + if (buffer != gadmei_utv_310[i].retval) { + rv = 1; + break; + } + } + if (rv == 0) { + (*model) = EM2820_BOARD_GADMEI_UTV310; + return 0; + } + for (i = 0, rv = 0; i < ARRAY_SIZE(msi_vox_ntsc); i++) { + sendbuf = kmalloc(msi_vox_ntsc[i].blen, GFP_KERNEL); + memcpy(sendbuf, msi_vox_ntsc[i].buffer, msi_vox_ntsc[i].blen); + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), msi_vox_ntsc[i].brequest, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, msi_vox_ntsc[i].index, sendbuf, msi_vox_ntsc[i].blen, HZ); + kfree(sendbuf); + if (msi_vox_ntsc[i].msecs) + msleep(msi_vox_ntsc[i].msecs); + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x05, &buffer, 1, HZ); + if (buffer != msi_vox_ntsc[i].retval) { + rv = 1; + break; + } + } + if (rv == 0) { + (*model) = EM2820_BOARD_MSI_VOX_USB_2; + return 0; + } + } + break; + case EM2750_BOARD_GENERIC: + { + /* TODO */ + (*model) = EM2750_BOARD_DLCW_130; + return 0; + } + break; + case EM2860_BOARD_GENERIC: + { + struct drequest_t gadmei_utv_330[] = { + {0x03, 0x42, "\x00", 0x10, 1, 0}, + {0x03, 0x4a, "\x00", 0x00, 1, 0}, + {0x03, 0x60, "\x00", 0x10, 1, 0}, + {0x03, 0x66, "\x00", 0x10, 1, 0}, + {0x03, 0x68, "\x00", 0x10, 1, 0}, + {0x03, 0x86, "\x00", 0x10, 1, 0}, + {0x03, 0xa0, "\x00", 0x00, 1, 0}, + {0x03, 0xa2, "\x00", 0x10, 1, 0}, + {0x03, 0xc0, "\x00", 0x00, 1, 0}, + {0x03, 0xc2, "\x00", 0x10, 1, 0}, + }; + struct drequest_t netgmbh_cam[] = { +#if 0 + {0x02, 0x48, "\x00", 0x00, 1, 0}, +#endif + {0x03, 0x5a, "\x00", 0x00, 1, 0}, + }; + + struct drequest_t typhoon_dvdmaker[] = { + {0x03, 0x4a, "\x00", 0x00, 1, 0}, + }; + + for (i = 0, rv = 0; i < ARRAY_SIZE(gadmei_utv_330); + i++) { + sendbuf = kmalloc(gadmei_utv_330[i].blen, GFP_KERNEL); + memcpy(sendbuf, gadmei_utv_330[i].buffer, gadmei_utv_330[i].blen); + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), gadmei_utv_330[i].brequest, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, gadmei_utv_330[i].index, sendbuf, gadmei_utv_330[i].blen, HZ); + kfree(sendbuf); + if (gadmei_utv_330[i].msecs) + msleep(gadmei_utv_330[i].msecs); + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x05, &buffer, 1, HZ); + if (buffer != gadmei_utv_330[i].retval) { + rv = 1; + break; + } + } + if (rv == 0) { + (*model) = EM2860_BOARD_GADMEI_UTV330; + return 0; + } + for (i = 0, rv = 0; i < ARRAY_SIZE(netgmbh_cam); i++) { + sendbuf = kmalloc(netgmbh_cam[i].blen, GFP_KERNEL); + memcpy(sendbuf, netgmbh_cam[i].buffer, netgmbh_cam[i].blen); + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), netgmbh_cam[i].brequest, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, netgmbh_cam[i].index, sendbuf, netgmbh_cam[i].blen, HZ); + kfree(sendbuf); + if (netgmbh_cam[i].msecs) + msleep(netgmbh_cam[i].msecs); + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x05, &buffer, 1, HZ); + if (buffer != netgmbh_cam[i].retval) { + rv = 1; + break; + } + } + if (rv == 0) { + (*model) = EM2860_BOARD_NETGMBH_CAM; + return 0; + } + + + for (i = 0, rv = 0; i < ARRAY_SIZE(typhoon_dvdmaker); i++) { + sendbuf = kmalloc(typhoon_dvdmaker[i].blen, GFP_KERNEL); + memcpy(sendbuf, typhoon_dvdmaker[i].buffer, typhoon_dvdmaker[i].blen); + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), typhoon_dvdmaker[i].brequest, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, typhoon_dvdmaker[i].index, sendbuf, typhoon_dvdmaker[i].blen, HZ); + kfree(sendbuf); + if (typhoon_dvdmaker[i].msecs) + msleep(typhoon_dvdmaker[i].msecs); + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x05, &buffer, 1, HZ); + if (buffer != typhoon_dvdmaker[i].retval) { + rv = 1; + break; + } + } + if (rv == 0) { + (*model) = EM2860_BOARD_TYPHOON_DVD_MAKER; + return 0; + } + } + break; + case EM2821_BOARD_GENERIC: + { + struct drequest_t usbgear[] = { + {0x03, 0xa2, "\x00", 0x10, 1, 0}, + {0x03, 0xa0, "\x00", 0x10, 1, 0}, + {0x03, 0x68, "\x00", 0x10, 1, 0}, + {0x03, 0x4a, "\x00", 0x00, 1, 0}, + {0x03, 0x66, "\x00", 0x10, 1, 0}, + {0x03, 0x68, "\x00", 0x10, 1, 0}, + {0x03, 0x86, "\x00", 0x10, 1, 0}, + {0x03, 0xc0, "\x00", 0x10, 1, 0}, + {0x03, 0xc2, "\x00", 0x10, 1, 0}, + }; + struct drequest_t siig_prolink[] = { + {0x03, 0x4a, "\x00", 0x00, 1, 0}, + {0x03, 0x60, "\x00", 0x00, 1, 0}, + {0x03, 0xa0, "\x00", 0x00, 1, 0}, + {0x03, 0xc6, "\x00", 0x00, 1, 0}, + }; + struct drequest_t supercomp[] = { + {0x03, 0x4a, "\x00", 0x00, 1, 0}, +#if 0 + {0x03, 0x60, "\x00", 0x00, 1, 0}, +#endif + {0x03, 0xc2, "\x00", 0x00, 1, 0}, + {0x03, 0xc6, "\x00", 0x00, 1, 0}, + }; + + for (i = 0, rv = 0; i < ARRAY_SIZE(usbgear); i++) { + sendbuf = kmalloc(usbgear[i].blen, GFP_KERNEL); + memcpy(sendbuf, usbgear[i].buffer, usbgear[i].blen); + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), usbgear[i].brequest, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, usbgear[i].index, sendbuf, usbgear[i].blen, HZ); + kfree(sendbuf); + if (usbgear[i].msecs) + msleep(usbgear[i].msecs); + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x05, &buffer, 1, HZ); + if (buffer != usbgear[i].retval) { + rv = 1; + break; + } + } + if (rv == 0) { + (*model) = EM2821_BOARD_USBGEAR_VD204; + return 0; + } + + for (i = 0, rv = 0; i < ARRAY_SIZE(siig_prolink); i++) { + sendbuf = kmalloc(siig_prolink[i].blen, GFP_KERNEL); + memcpy(sendbuf, siig_prolink[i].buffer, siig_prolink[i].blen); + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), siig_prolink[i].brequest, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, siig_prolink[i].index, sendbuf, siig_prolink[i].blen, HZ); + kfree(sendbuf); + if (siig_prolink[i].msecs) + msleep(siig_prolink[i].msecs); + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x05, &buffer, 1, HZ); + if (buffer != siig_prolink[i].retval) { + rv = 1; + break; + } + } + if (rv == 0) { + (*model) = EM2821_BOARD_PROLINK_PLAYTV_USB2; + return 0; + } + for (i = 0, rv = 0; i < ARRAY_SIZE(supercomp); i++) { + sendbuf = kmalloc(supercomp[i].blen, GFP_KERNEL); + memcpy(sendbuf, supercomp[i].buffer, supercomp[i].blen); + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), supercomp[i].brequest, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, supercomp[i].index, sendbuf, supercomp[i].blen, HZ); + kfree(sendbuf); + if (supercomp[i].msecs) + msleep(supercomp[i].msecs); + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x05, &buffer, 1, HZ); + if (buffer != supercomp[i].retval) { + rv = 1; + break; + } + } + if (rv == 0) { + (*model) = EM2821_BOARD_SUPERCOMP_USB_2; + return 0; + } + } + break; + case EM2870_BOARD_GENERIC: + { + struct drequest_t pinnacle_pctv[] = { + /* {0x03, 0xb8, "\x00", 0x00, 1, 0}, tvp5150 -- it's possible that this IC isn't reachable*/ + {0x03, 0xa0, "\x00", 0x00, 1, 0}, /* eeprom */ + {0x03, 0xc0, "\x00", 0x00, 1, 0}, /* mt2060 */ + }; + for (i = 0, rv = 0; i < ARRAY_SIZE(pinnacle_pctv); + i++) { + sendbuf = kmalloc(pinnacle_pctv[i].blen, GFP_KERNEL); + memcpy(sendbuf, pinnacle_pctv[i].buffer, pinnacle_pctv[i].blen); + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), pinnacle_pctv[i].brequest, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, pinnacle_pctv[i].index, sendbuf, pinnacle_pctv[i].blen, HZ); + kfree(sendbuf); + if (pinnacle_pctv[i].msecs) + msleep(pinnacle_pctv[i].msecs); + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x05, &buffer, 1, HZ); + if (buffer != pinnacle_pctv[i].retval) { + rv = 1; + break; + } + } + if (rv == 0) { + (*model) = EM2870_BOARD_PINNACLE_PCTV_DVB; + return 0; + } + } + break; + /* no other card has been available yet with that IC, + so we assume HVR_950 by default */ + case EM2883_BOARD_GENERIC: + { + (*model) = EM2883_BOARD_EMPIA_HYBRID_ATSC; + return 0; + } + break; + case EM2881_BOARD_GENERIC: + { + struct drequest_t dnt_hybrid[] = { + {0x03, 0xa0, "\x15", 0x56, 1, 0}, +#if 0 + {0x03, 0xc2, "\x00", 0x00, 1, 0}, + {0x00, 0x04, "\x08", 0x00, 1, 20}, + {0x00, 0x08, "\x6f", 0x00, 1, 20}, + {0x00, 0x08, "\x7f", 0x00, 1, 20}, + {0x03, 0xb8, "\x00", 0x00, 1, 0}, + {0x00, 0x04, "\x0c", 0x00, 1, 20}, + {0x00, 0x08, "\x6e", 0x00, 1, 20}, + {0x00, 0x08, "\x7e", 0x00, 1, 20}, + {0x03, 0x1e, "\x00", 0x00, 1, 0}, +#endif + }; + struct drequest_t pinnacle_hybrid_pro[] = { + {0x03, 0xa0, "\x15", 0x57, 1, 0}, +#if 0 + {0x03, 0xc2, "\x00", 0x00, 1, 0}, + {0x00, 0x04, "\x08", 0x00, 1, 20}, + {0x00, 0x08, "\x6f", 0x00, 1, 20}, + {0x00, 0x08, "\x7f", 0x00, 1, 20}, + {0x03, 0xb8, "\x00", 0x00, 1, 0}, + {0x00, 0x04, "\x0c", 0x00, 1, 20}, + {0x00, 0x08, "\x6e", 0x00, 1, 20}, + {0x00, 0x08, "\x7e", 0x00, 1, 20}, + {0x03, 0x1e, "\x00", 0x00, 1, 0}, +#endif + }; + for (i = 0, rv = 0; i < ARRAY_SIZE(pinnacle_hybrid_pro); i++) + { + sendbuf = kmalloc(pinnacle_hybrid_pro[i].blen, GFP_KERNEL); + memcpy(sendbuf, pinnacle_hybrid_pro[i].buffer, pinnacle_hybrid_pro[i].blen); + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), pinnacle_hybrid_pro[i].brequest, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, pinnacle_hybrid_pro[i].index, sendbuf, pinnacle_hybrid_pro[i].blen, HZ); + kfree(sendbuf); + if (pinnacle_hybrid_pro[i].msecs) + msleep(pinnacle_hybrid_pro[i].msecs); + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x05, &buffer, 1, HZ); + if (pinnacle_hybrid_pro[i].index == 0xa0) + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x02, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, pinnacle_hybrid_pro[i].index, &buffer, 1, HZ); + + if (buffer != pinnacle_hybrid_pro[i].retval) { + printk("FAILED: 0x%02x 0x%02x\n", pinnacle_hybrid_pro[i].index, buffer); + rv = 1; + break; + } + } + if (rv == 0) { + (*model) = EM2881_BOARD_PINNACLE_HYBRID_PRO; + return 0; + } + for (i = 0, rv = 0; i < ARRAY_SIZE(dnt_hybrid); i++) + { + sendbuf = kmalloc(dnt_hybrid[i].blen, GFP_KERNEL); + memcpy(sendbuf, dnt_hybrid[i].buffer, dnt_hybrid[i].blen); + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), dnt_hybrid[i].brequest, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, dnt_hybrid[i].index, sendbuf, dnt_hybrid[i].blen, HZ); + kfree(sendbuf); + if (dnt_hybrid[i].msecs) + msleep(dnt_hybrid[i].msecs); + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, 0x05, &buffer, 1, HZ); + if (dnt_hybrid[i].index == 0xa0) + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x02, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0000, pinnacle_hybrid_pro[i].index, &buffer, 1, HZ); + if (buffer != dnt_hybrid[i].retval) { + rv = 1; + break; + } + } + if (rv == 0) { + (*model) = EM2881_BOARD_DNT_DA2_HYBRID; + return 0; + } + } + break; + default: + return 0; + } + + em28xx_errdev("Your board has no eeprom inside it and thus can't\n" + "%s: be autodetected. Please pass card = insmod option to\n" + "%s: workaround that. Redirect complaints to the vendor of\n" + "%s: the TV card. Generic type will be used.\n" + "%s: Best regards, \n" + "%s: -- tux\n", + dev->name, dev->name, dev->name, dev->name, dev->name); + em28xx_errdev("%s: Here is a list of valid choices for the card = insmod option:\n", + dev->name); + for (i = 0; i < em28xx_bcount; i++) { + em28xx_errdev(" card = %d -> %s\n", i, + em28xx_boards[i].name); + } + return 0; +} + +/* + * em28xx_usb_probe() + * checks for supported devices + */ + +static int em28xx_usb_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + const struct usb_endpoint_descriptor *endpoint; + struct usb_device *udev; + struct usb_interface *uif; + struct em28xx *dev = NULL; + int retval = -ENODEV; + int model, i, nr, ifnum, is_em2875 = 0; + + udev = usb_get_dev(interface_to_usbdev(interface)); + + if (udev == NULL) { + printk("something's weird here udev returned 0!\n"); + return -EINVAL; + } + + ifnum = interface->altsetting[0].desc.bInterfaceNumber; + + /* Check to see next free device and mark as used */ + nr = find_first_zero_bit(&em28xx_devused, EM28XX_MAXBOARDS); + em28xx_devused |= 1<altsetting[0].desc.bInterfaceClass == USB_CLASS_AUDIO) { + printk(KERN_INFO "audio device (%04x:%04x): interface %i, class %i\n", + udev->descriptor.idVendor, udev->descriptor.idProduct, + ifnum, + interface->altsetting[0].desc.bInterfaceClass); + + em28xx_devused &= ~(1<descriptor.idVendor, udev->descriptor.idProduct, + ifnum, + interface->altsetting[0].desc.bInterfaceClass); + + + /* check if the device has the iso in endpoint at the correct place */ + + endpoint = &interface->cur_altsetting->endpoint[0].desc; + if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_ISOC && + (interface->altsetting[1].endpoint[0].desc.wMaxPacketSize == 940)) { + printk(KERN_INFO "em28xx: DTV Device detected\n"); + is_em2875 = 1; + } else { + endpoint = &interface->cur_altsetting->endpoint[1].desc; + if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != + USB_ENDPOINT_XFER_ISOC) { + em28xx_err("em28xx: probing error: endpoint is non-ISO endpoint!\n"); + em28xx_devused &= ~(1<bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT) { + em28xx_err("em28xx: probing error: endpoint is ISO OUT endpoint!\n"); + em28xx_devused &= ~(1<speed) { + case USB_SPEED_HIGH: + printk(KERN_INFO"em28xx: device is attached to a USB 2.0 bus\n"); + break; + case USB_SPEED_FULL: + printk(KERN_INFO"em28xx: setting up device on a USB 1.1 bus\n"); + /* passthrough */ + default: + printk(KERN_INFO"em28xx: your device won't work properly when\n"); + printk(KERN_INFO"em28xx: not attached to a USB 2.0 highspeed bus\n"); + printk(KERN_INFO"em28xx: more information:\n"); + printk(KERN_INFO"em28xx: http://mcentral.de/wiki/index.php5/Talk:Em2880\n"); + } + + model = id->driver_info; + + if (nr >= EM28XX_MAXBOARDS) { + printk(DRIVER_NAME ": Supports only %i em28xx boards.\n", EM28XX_MAXBOARDS); + em28xx_devused &= ~(1<name, 28, "em28xx #%d", nr); + dev->devno = nr; + + dev->udev = udev; + + dev->em28xx_write_regs = em28xx_write_regs; + dev->em28xx_read_reg = em28xx_read_reg; + dev->em28xx_read_reg_req_len = em28xx_read_reg_req_len; + dev->em28xx_write_regs_req = em28xx_write_regs_req; + dev->em28xx_read_reg_req = em28xx_read_reg_req; + dev->em28xx_write_reg_bits = em28xx_write_reg_bits; + dev->usb_interface = ifnum; + + /* compute alternate max packet sizes */ + uif = udev->actconfig->interface[ifnum]; + + dev->num_alt = uif->num_altsetting; + dev->uif = uif; + + em28xx_info("Alternate settings: %i\n", dev->num_alt); + dev->alt_max_pkt_size = kmalloc((dev->num_alt+1)*sizeof(int), + GFP_KERNEL); + if (dev->alt_max_pkt_size == NULL) { + em28xx_errdev("out of memory!\n"); + em28xx_devused &= ~(1<num_alt ; i++) { + u16 tmp = le16_to_cpu(uif->altsetting[i].endpoint[is_em2875?0:1].desc. + wMaxPacketSize); + + dev->alt_max_pkt_size[i] = + (tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1); + em28xx_info("Alternate setting %i, max size= %i\n", i, + dev->alt_max_pkt_size[i]); + } + + if ((card[nr] >= 0) && (card[nr] < em28xx_bcount)) + model = card[nr]; + + em28xx_generic_probe(&model, udev, dev); + + /* allocate device struct */ + retval = em28xx_init_dev(&dev, udev, nr, model); + if (retval) + return retval; + + em28xx_info("Found %s\n", em28xx_boards[model].name); + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + return 0; +} + +/* + * em28xx_usb_disconnect() + * called when the device gets diconencted + * video device will be unregistered on v4l2_close in case it is still open + */ +static void em28xx_usb_disconnect(struct usb_interface *interface) +{ + struct em28xx *dev = usb_get_intfdata(interface); + struct list_head *pos; + struct em28xx_ops *ops = 0; + + /* might be racy */ + dev->state |= DEV_DISCONNECTED; + + usb_set_intfdata(interface, NULL); + + /* TODO: kernel versions <=2.6.21 */ +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17) + if (delayed_work_pending(&dev->request_module_wk)) + cancel_rearming_delayed_work(&dev->request_module_wk); +#endif + + if (!dev) + return; + + down_write(&em28xx_disconnect); + + em28xx_card_disconnect(dev); + + mutex_lock(&em28xx_extension_devlist_lock); + if (!list_empty(&em28xx_extension_devlist)) { + list_for_each(pos, &em28xx_extension_devlist) { + ops = list_entry(pos, struct em28xx_ops, next); + ops->fini(dev); + } + } + mutex_unlock(&em28xx_extension_devlist_lock); + + wake_up_interruptible_all(&dev->open); + + if (dev->users) { + dev->state |= DEV_MISCONFIGURED; + em28xx_uninit_isoc(dev); + wake_up_interruptible(&dev->wait_frame); + wake_up_interruptible(&dev->video_wait_stream); + if (dev->dev_modes&EM28XX_VBI) { + wake_up_interruptible(&dev->wait_vbi_frame); + wake_up_interruptible(&dev->vbi_wait_stream); + } + } else { + em28xx_release_resources(dev); + kfree(dev->alt_max_pkt_size); + tuner_chip_detach(dev->tuner); + kfree(dev); + } + + up_write(&em28xx_disconnect); +} + +static struct usb_driver em28xx_usb_driver = { +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 15) + .owner = THIS_MODULE, +#endif + .name = "em28xx", + .probe = em28xx_usb_probe, + .disconnect = em28xx_usb_disconnect, + .id_table = em28xx_id_table, +}; + +int em28xx_register_extension(struct em28xx_ops *ops) +{ + struct em28xx *dev = NULL; + struct list_head *list; + + mutex_lock(&em28xx_extension_devlist_lock); + list_add_tail(&ops->next, &em28xx_extension_devlist); + + /* TODO: defer the work put the module request into a linked list + lock the device when the corresponding node got closed and load + the module: + + problem here to consider is if module autoloading is not enabled + to make it more safe it would be good to lock the driver and unlock + it when the corresponding extension got unlocked + + better ideas are welcome of course + + */ + + list_for_each(list, &em28xx_devlist) { + dev = list_entry(list, struct em28xx, devlist); + if (dev && + (dev->state & DEV_DISCONNECTED) == 0 && + (dev->state & DEV_MISCONFIGURED) == 0 && + dev->radio_user == 0 && + dev->video_user == 0 && + dev->vbi_user == 0 && + dev->fe_user == 0 && + dev->dev_modes & ops->id) { + dev->em28xx_acquire(dev, EM28XX_LOCK, 1); + + ops->init(dev); + printk("Em28xx: Initialized (%s) extension\n",ops->name); + + dev->em28xx_acquire(dev, EM28XX_LOCK, 0); + } + } + + mutex_unlock(&em28xx_extension_devlist_lock); + return 0; +} + +void em28xx_unregister_extension(struct em28xx_ops *ops) +{ + struct em28xx *dev = NULL; + struct list_head *list; + + mutex_lock(&em28xx_extension_devlist_lock); + list_for_each(list, &em28xx_devlist) { + dev = list_entry(list, struct em28xx, devlist); + if (dev) + ops->fini(dev); + } + printk(KERN_INFO"Em28xx: Removed (%s) extension\n", ops->name); + list_del(&ops->next); + mutex_unlock(&em28xx_extension_devlist_lock); +} + + +static int __init em28xx_module_init(void) +{ + int result; + + printk(KERN_INFO DRIVER_NAME " v4l2 driver version %d.%d.%d loaded\n", + (EM28XX_VERSION_CODE >> 16) & 0xff, + (EM28XX_VERSION_CODE >> 8) & 0xff, EM28XX_VERSION_CODE & 0xff); +#ifdef SNAPSHOT + printk(KERN_INFO DRIVER_NAME " snapshot date %04d-%02d-%02d\n", + SNAPSHOT / 10000, (SNAPSHOT / 100) % 100, SNAPSHOT % 100); +#endif + + em28xx_wq = create_singlethread_workqueue("em28xx-worker"); + if (em28xx_wq == NULL) + return -ENOMEM; + + /* register this driver with the USB subsystem */ + result = usb_register(&em28xx_usb_driver); + if (result) + em28xx_err(DRIVER_NAME + " usb_register failed. Error number %d.\n", result); + + return result; +} + +static void __exit em28xx_module_exit(void) +{ + /* deregister this driver with the USB subsystem */ + usb_deregister(&em28xx_usb_driver); + destroy_workqueue(em28xx_wq); +} + +module_init(em28xx_module_init); +module_exit(em28xx_module_exit); +EXPORT_SYMBOL(em28xx_register_extension); +EXPORT_SYMBOL(em28xx_unregister_extension); + diff --git a/drivers/media/video/empia/em28xx-webcam.c b/drivers/media/video/empia/em28xx-webcam.c new file mode 100644 index 0000000..228c04e --- /dev/null +++ b/drivers/media/video/empia/em28xx-webcam.c @@ -0,0 +1,375 @@ +/* + em28xx-vy.c - driver for em28xx based webcams + + Copyright (C) 2006 Markus Rechberger + + I got a few specs but none of them were clear enough to get anything + work. Finally I end up with writing usbreplay and stepping through + the sniffed logfiles... + + all this is quite device specific (em2820 specific) so I decided to + not write an I2C client no other device will behave like this one + + Thanks to Walter Grom from MT (www.mt.com) for implementing + the reversed specs + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "em28xx.h" + +#define VY_FEATURE_REGISTER_1 0x02 /* Gain control en, gain control + values */ +#define VY_FEATURE_REGISTER_2 0x04 /* WBM, Shutter, Mirror */ +#define VY_FEATURE_REGISTER_3 0x7c /* edge enhance */ +#define VY_FEATURE_REGISTER_4 0x7b /* edge enhance en */ +#define VY_FEATURE_REGISTER_5 0x7e /* edge enhance again? */ +#define VY_FEATURE_REGISTER_6 0x13 /* BLC ?? */ +#define VY_FEATURE_REGISTER_7 0x1d /* gain control values */ +#define VY_FEATURE_REGISTER_8 0x42 /* RGain */ +#define VY_FEATURE_REGISTER_9 0x43 /* BGain */ + +#define V4L2_VY_WBM_MASK 0x03 +#define V4L2_VY_SHUTTER_MASK 0x78 +#define V4L2_VY_MIRROR_MASK 0x80 +#define V4L2_VY_GAIN_CONTROL_EN_MASK 0x02 +#define V4L2_VY_GAIN_CONTROL_MASK 0xfe +#define V4L2_VY_EDGE_ENHANCE_EN_MASK 0x03 +#define V4L2_VY_EDGE_ENHANCE_MASK 0x1f +#define V4L2_VY_BLC_MASK 0x00 /* FIXME */ +#define V4L2_VY_RGAIN_MASK 0xFF +#define V4L2_VY_BGAIN_MASK 0xFF + +/* videology specific data */ +struct v4l2_queryctrl em28xx_vy_ctrl[] = { + { + .id = V4L2_VY_WBM, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "whiteblc", + .minimum = 0x0, + .maximum = 0x3, + .step = 0x1, + .default_value = 0x0, + .flags = 0, + }, { + .id = V4L2_VY_SHUTTER, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "shuttermode", + .minimum = 0x0, + .maximum = 0xf, + .step = 1, + .default_value = 0xf, + .flags = 0, + }, { + .id = V4L2_VY_MIRROR, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "mirror", + .minimum = 0x0, + .maximum = 0x1, + .step = 1, + .default_value = 0x0, + .flags = 0, + }, { + .id = V4L2_VY_GAIN_CONTROL_EN, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "gain_en", + .minimum = 0x0, + .maximum = 0x1, + .step = 1, + .default_value = 0x0, + .flags = 0, + }, { + .id = V4L2_VY_GAIN_CONTROL, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "gainctrl", + .minimum = 0x0, + .maximum = 0x7f, + .step = 1, + .default_value = 0x0, + .flags = 0, + }, { + .id = V4L2_VY_EDGE_ENHANCE_EN, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "edge_en", + .minimum = 0x0, + .maximum = 0x1, + .step = 1, + .default_value = 0x0, + .flags = 0, + }, { + .id = V4L2_VY_EDGE_ENHANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "edgectrl", + .minimum = 0x0, + .maximum = 0x1f, + .step = 1, + .default_value = 0x0, + .flags = 0, + }, { + .id = V4L2_VY_BLC, /* wonder what this really is? */ + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "blc", + .minimum = 0x0, + .maximum = 0x40, + .step = 1, + .default_value = 0x0, + .flags = 0, + }, { + .id = V4L2_VY_RGAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "rgain", + .minimum = 0x0, + .maximum = 0xff, + .step = 1, + .default_value = 0x3b, + .flags = 0, + }, { + .id = V4L2_VY_BGAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "bgain", + .minimum = 0x0, + .maximum = 0xff, + .step = 1, + .default_value = 0x74, + .flags = 0, + } +}; + + +static int em28xx_vy_readmap(struct em28xx *dev, u8 reg, int mask) +{ + int value; + + em28xx_write_regs_req(dev, 0x02, 0xa2, ®, 1); + value = em28xx_read_reg_req(dev, 0x2, 0xa2); + value &= mask; + return value; +} + + +/* --------------------------------------------- */ +int em28xx_vy_write(struct em28xx *dev, u8 reg_index, u8 value, u8 mask) +{ + u8 msg[2]; + u8 val; + u8 oldval; + u8 state = 1; /* 1 == OK */ + + /* write index of desired register to adress 0xa2 */ + em28xx_write_regs_req(dev, 0x02, 0xa2, ®_index, 1); + /* read old register value from eeprom */ + oldval = em28xx_read_reg_req(dev, 0x2, 0xa2); + /* write again index of desired register to adress 0xa2 */ + em28xx_write_regs_req(dev, 0x02, 0xa2, ®_index, 1); + + /* calculate new register value */ + msg[0] = reg_index; + msg[1] = (oldval & ~mask)|(value&mask); + /* write new register value to eeprom */ + dev->em28xx_write_regs_req(dev, 0x02, 0xa0, msg, 2); + + /* instruct microcontroller to write values into the eeprom */ + msg[0] = 0x11; + msg[1] = reg_index; + dev->em28xx_write_regs_req(dev, 0x02, 0x68, msg, 2); + dev->em28xx_write_regs_req(dev, 0x03, 0x68, "\x00", 1); + val = em28xx_read_reg_req(dev, 0x2, 0x68); + if (val == 0x05) + ; /* everything's fine */ + else + state = 0; + + /* write new register value to eeprom again */ + msg[0] = reg_index; + msg[1] = (oldval & ~mask)|(value&mask); + dev->em28xx_write_regs_req(dev, 0x02, 0xa2, msg, 2); + + /* instruct microcontroller for verification */ + msg[0] = 0x21; + msg[1] = reg_index; + em28xx_write_regs_req(dev, 0x02, 0x68, msg, 2); + em28xx_write_regs_req(dev, 0x03, 0x68, "\x00", 1); + val = em28xx_read_reg_req(dev, 0x2, 0x68); + if (val == 0x05) + ; /* everything's fine */ + else + state = 0; + + if (state == 0) + return oldval; /* error */ + else + return value; +} + +/* embedded QUERYCTRL */ +int em28xx_vy_qctrl(struct v4l2_queryctrl *qctrl) +{ + struct v4l2_queryctrl *qc = qctrl; + int i; + for (i = 0; i < ARRAY_SIZE(em28xx_vy_ctrl); i++) { + if (qc->id && qc->id == em28xx_vy_ctrl[i].id) { + memcpy(qc, &(em28xx_vy_ctrl[i]), + sizeof(*qc)); + return 0; + } + } + return -EINVAL; /* no ctrl found here */ +} + +/* VIDIOC_G_CTRL */ +int em28xx_vy_gctrl(struct em28xx *dev, struct v4l2_control *ctrl) +{ + switch (ctrl->id) { + case V4L2_VY_WBM: + ctrl->value = em28xx_vy_readmap(dev, + VY_FEATURE_REGISTER_2, + V4L2_VY_WBM_MASK); + break; + case V4L2_VY_SHUTTER: + ctrl->value = (em28xx_vy_readmap(dev, + VY_FEATURE_REGISTER_2, + V4L2_VY_SHUTTER_MASK)) >> 3; + break; + case V4L2_VY_MIRROR: + ctrl->value = (em28xx_vy_readmap(dev, + VY_FEATURE_REGISTER_2, + V4L2_VY_MIRROR_MASK)) >> 7; + break; + case V4L2_VY_GAIN_CONTROL_EN: + ctrl->value = (em28xx_vy_readmap(dev, + VY_FEATURE_REGISTER_1, + V4L2_VY_GAIN_CONTROL_EN_MASK)) >> 1; + break; + case V4L2_VY_GAIN_CONTROL: + ctrl->value = (em28xx_vy_readmap(dev, + VY_FEATURE_REGISTER_7, + V4L2_VY_GAIN_CONTROL_MASK)) >> 1; + break; + case V4L2_VY_EDGE_ENHANCE_EN: + ctrl->value = (em28xx_vy_readmap(dev, + VY_FEATURE_REGISTER_4, + V4L2_VY_EDGE_ENHANCE_EN_MASK) & 0x01) ? + 0 : 1; + break; + case V4L2_VY_EDGE_ENHANCE: + ctrl->value = em28xx_vy_readmap(dev, VY_FEATURE_REGISTER_3, + V4L2_VY_EDGE_ENHANCE_MASK); + break; + case V4L2_VY_BLC: +#if 0 + ctrl->value = em28xx_vy_readmap(dev, VY_FEATURE_REGISTER_6, + V4L2_VY_BLC_MASK); +#endif + printk(KERN_INFO"em28xx-vy.c V4L2_VY_BLC: not implemented\n"); + break; + case V4L2_VY_RGAIN: + ctrl->value = em28xx_vy_readmap(dev, VY_FEATURE_REGISTER_8, + V4L2_VY_RGAIN_MASK); + break; + case V4L2_VY_BGAIN: + ctrl->value = em28xx_vy_readmap(dev, VY_FEATURE_REGISTER_9, + V4L2_VY_BGAIN_MASK); + break; + default: + printk(KERN_INFO"em28xx-vy.c: unknown command!\n"); + return -EINVAL; + } + return 0; +} + +/* VIDIOC_S_CTRL */ +int em28xx_vy_cctrl(struct em28xx *dev, struct v4l2_control *ctrl) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(em28xx_vy_ctrl); i++) { + if (ctrl->id == em28xx_vy_ctrl[i].id) { + if (em28xx_vy_ctrl[i].minimum >= ctrl->value && + em28xx_vy_ctrl[i].maximum <= + ctrl->value) { + printk(KERN_INFO"em28xx-vy.c: value out of " + "range\n"); + return -EINVAL; + } + } + } + + switch (ctrl->id) { + case V4L2_VY_WBM: + em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_2, + (u8)ctrl->value, V4L2_VY_WBM_MASK); + break; + case V4L2_VY_SHUTTER: + em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_2, + (u8)(ctrl->value)<<3, + V4L2_VY_SHUTTER_MASK); + break; + case V4L2_VY_MIRROR: + em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_2, + (u8)(ctrl->value)<<7, + V4L2_VY_MIRROR_MASK); + break; + case V4L2_VY_GAIN_CONTROL_EN: + em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_1, + (u8)(ctrl->value)<<1, + V4L2_VY_GAIN_CONTROL_EN_MASK); + break; + case V4L2_VY_GAIN_CONTROL: + em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_7, + (u8)(ctrl->value)<<1, + V4L2_VY_GAIN_CONTROL_MASK); + break; + case V4L2_VY_EDGE_ENHANCE_EN: + em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_4, + (u8)(~(((ctrl->value)<<1) | (ctrl->value))), + V4L2_VY_EDGE_ENHANCE_EN_MASK); + break; + case V4L2_VY_EDGE_ENHANCE: + em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_3, + (u8)(ctrl->value), + V4L2_VY_EDGE_ENHANCE_MASK); + em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_5, + (u8)(ctrl->value), + V4L2_VY_EDGE_ENHANCE_MASK); + break; + case V4L2_VY_RGAIN: + em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_8, + (u8)(ctrl->value), + V4L2_VY_RGAIN_MASK); + break; + case V4L2_VY_BGAIN: + em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_9, + (u8)(ctrl->value), + V4L2_VY_BGAIN_MASK); + break; + case V4L2_VY_BLC: + printk(KERN_INFO"em28xx-vy.c V4l2_VY_BLC not implemented\n"); + break; + default: + printk(KERN_INFO"em28xx-vy.c: unknown command!\n"); + } + return 0; +} + + diff --git a/drivers/media/video/empia/em28xx.h b/drivers/media/video/empia/em28xx.h new file mode 100644 index 0000000..069dd5a --- /dev/null +++ b/drivers/media/video/empia/em28xx.h @@ -0,0 +1,1153 @@ +/* + em28xx.h - driver for Empia EM2800/EM2820/2840/2880 USB video capture devices + + Copyright (C) 2005 Markus Rechberger + Ludovico Cavedon + Mauro Carvalho Chehab + + Based on the em2800 driver from Sascha Sommer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _EM28XX_H +#define _EM28XX_H + +#include +#include +#include +#include +#include "dmxdev.h" +#include "dvb_demux.h" +#include "dvb_net.h" +#include "dvb_frontend.h" +#include + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25) +#include +#endif +#include +#include +#include +#include "xc3028/xc3028_control.h" +#include "cx25843/em28xx-cx25843.h" +#include "media/tuner.h" +#include "em28xx-aad.h" + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 15) +#include +#endif +#include + +/* Boards supported by driver */ + +#define EM2800_BOARD_GENERIC 0 +#define EM2820_BOARD_GENERIC 1 +#define EM2821_BOARD_GENERIC 2 +#define EM2870_BOARD_GENERIC 3 +#define EM2881_BOARD_GENERIC 4 +#define EM2860_BOARD_GENERIC 5 +#define EM2861_BOARD_GENERIC 6 +#define EM2820_BOARD_TERRATEC_CINERGY_250 7 +#define EM2820_BOARD_PINNACLE_USB_2 8 +#define EM2820_BOARD_HAUPPAUGE_WINTV_USB_2 9 +#define EM2820_BOARD_MSI_VOX_USB_2 10 +#define EM2800_BOARD_TERRATEC_CINERGY_200 11 +#define EM2800_BOARD_LEADTEK_WINFAST_USBII 12 +#define EM2800_BOARD_KWORLD_USB2800 13 +#define EM2820_BOARD_PINNACLE_DVC_90 14 +#define EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900 15 +#define EM2880_BOARD_TERRATEC_HYBRID_XS 16 +#define EM2880_BOARD_TERRATEC_HYBRID_XS_FR 17 +#define EM2820_BOARD_KWORLD_PVRTV2800RF 18 +#define EM2880_BOARD_TERRATEC_PRODIGY_XS 19 +#define EM2820_BOARD_VIDEOLOGY_20K14XUSB 20 +#define EM2821_BOARD_USBGEAR_VD204 21 +#define EM2870_BOARD_TERRATEC_XS 22 +#define EM2870_BOARD_PINNACLE_PCTV_DVB 23 +#define EM2881_BOARD_DNT_DA2_HYBRID 24 +#define EM2881_BOARD_PINNACLE_HYBRID_PRO 25 +#define EM2820_BOARD_HERCULES_SMART_TV_USB2 26 +#define EM2870_BOARD_COMPRO_VIDEOMATE 27 +#define EM2880_BOARD_KWORLD_DVB_310U 28 +#define EM2821_BOARD_PROLINK_PLAYTV_USB2 29 +#define EM2870_BOARD_TERRATEC_XS_MT2060 30 +#define EM2880_BOARD_MSI_DIGIVOX_AD 31 +#define EM2820_BOARD_DLINK_USB_TV 32 +#define EM2820_BOARD_GADMEI_UTV310 33 +#define EM2870_BOARD_KWORLD_355U 34 +#define EM2821_BOARD_SUPERCOMP_USB_2 35 +#define EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2 36 +#define EM2860_BOARD_GADMEI_UTV330 37 +#define EM2800_BOARD_VGEAR_POCKETTV 38 +#define EM2870_BOARD_KWORLD_350U 39 +#define EM2882_BOARD_TERRATEC_HYBRID_XS 40 +#define EM2820_BOARD_PINNACLE_DVC_100 41 +#define EM2861_BOARD_YAKUMO_MOVIE_MIXER 43 +#define EM2750_BOARD_DLCW_130 44 +#define EM2750_BOARD_GENERIC 42 +#define EM2883_BOARD_GENERIC 45 +#define EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 46 +#define EM2883_BOARD_PINNACLE_PCTV_HD_PRO 47 +#define EM2882_BOARD_PINNACLE_HYBRID_PRO 48 +#define EM2820_BOARD_HAUPPAUGE_WINTV_USB_2_R2 49 +#define EM2860_BOARD_NETGMBH_CAM 50 +#define EM2820_BOARD_LEADTEK_WINFAST_USBII_DELUXE 51 +#define EM2880_BOARD_MSI_DIGIVOX_AD_II 52 +#define EM2860_BOARD_TYPHOON_DVD_MAKER 53 +#define EM2820_BOARD_PINNACLE_USB_2_FM1216ME 54 +#define EM2751_BOARD_EMPIA_SAMPLE 55 +#define EM2880_BOARD_KWORLD_DVB_305U 56 +#define EM2861_BOARD_KWORLD_PVRTV_300U 57 +#define EM2883_BOARD_KWORLD_HYBRID_A316 58 +#define EM2860_BOARD_TERRATEC_HYBRID_XS 59 +#define EM2861_BOARD_PLEXTOR_PX_TV100U 60 +#define EM2883_BOARD_TERRATEC_HYBRID_XS_FM 61 +#define EM2870_BOARD_HAUPPAUGE_WINTV_USB_2_R2 62 +#define EM2883_BOARD_EMPIA_HYBRID_ATSC 63 +#define EM2863_BOARD_EMPIA_GENERIC 64 +#define EM2883_BOARD_KWORLD_HYBRID_F306 65 +#define EM2888_BOARD_KWORLD_HYBRID_E329 66 +#define EM2883_BOARD_KWORLD_HYBRID_E323 67 +#define EM2883_BOARD_KWORLD_A340 68 +#define EM2888_BOARD_LINCOLN_TV_FM 69 +#define EM2888_BOARD_DVB_TC_HYBRID 70 +#define EM2888_BOARD_EMPIA_HYBRID 71 +#define EM2883_BOARD_ATI_TVWONDER600 72 +#define EM2875_BOARD_SAMPLE_ISDBT 73 +#define EM2879_BOARD_SAMPLE_DMB 74 +#define EM2860_BOARD_KAIOMY_TVNPC_U2 75 +#define EM2861_BOARD_POLLIN_USB_R1 76 +#define EM2883_BOARD_EQUINUX_TUBESTICK_ATSC 77 +#define EM2882_BOARD_LEADTEK_PALMTOP_DTV_200H 78 +#define EM2820_BOARD_COMPRO_VIDEO_MATE 79 + +#ifndef TUNER_XCEIVE_XC5000 +#define TUNER_XCEIVE_XC5000 10000 +#endif + +#ifndef TUNER_XCEIVE_XC3028 +#define TUNER_XCEIVE_XC3028 10001 +#endif + +#ifndef TUNER_MT2060 +#define TUNER_MT2060 10002 +#endif + +#ifndef TUNER_QT1010 +#define TUNER_QT1010 10003 +#endif + +#ifndef TUNER_PHILIPS_TDA18271 +#define TUNER_PHILIPS_TDA18271 10004 +#endif + +#ifndef TUNER_ADIMTV102 +#define TUNER_ADIMTV102 10005 +#endif + + +#define UNSET -1 + +/* maximum number of em28xx boards */ +/* TODO: we should distinct here between + * DVB-T + * analogue only + * hybrid devices + 1 analogue tuner requires up to 170 mbit + 2 analogue tuners will work in alt 3 mode, but the default + is alt 7 at the moment (it's possible to override + with the module parameter alt=3), though it should be + implemented properly. + 2 devices take up to 340 mbit which should work. + (3 tuners would be 510 mbit which is too much for + one usb controller) + + DVB-T tuner only take around 15 mbit, so we should be able + to support more than the limit below. + + I think a controller based bandwidth table would do here + and guarantee that the initialized devices will work +*/ +#define EM28XX_MAXBOARDS 3 /* FIXME: should be bigger */ + +/* maximum number of frames that can be queued */ +#define EM28XX_NUM_FRAMES 5 +/* number of frames that get used for v4l2_read() */ +#define EM28XX_NUM_READ_FRAMES 2 + +/* number of buffers for isoc transfers */ +#define EM28XX_NUM_BUFS 5 +#define EM2880_DVB_NUM_BUFS 5 +#define EM28XX_AUDIO_BUFS 3 +#define EM28XX_NUM_AUDIO_PACKETS 64 +#define EM28XX_AUDIO_MAX_PACKET_SIZE 196 /* static value */ + + +/* number of packets for each buffer + windows requests only 40 packets .. so we better do the same + this is what I found out for all alternate numbers there! + */ +#define EM28XX_NUM_PACKETS 64 + +/* default alternate; 0 means choose the best */ +#define EM28XX_PINOUT 0 + +#define EM28XX_INTERLACED_DEFAULT 1 + +/* +#define (use usbview if you want to get the other alternate number infos) +#define +#define alternate number 2 +#define Endpoint Address: 82 + Direction: in + Attribute: 1 + Type: Isoc + Max Packet Size: 1448 + Interval: 125us + + alternate number 7 + + Endpoint Address: 82 + Direction: in + Attribute: 1 + Type: Isoc + Max Packet Size: 3072 + Interval: 125us +*/ + +/* time to wait when stopping the isoc transfer */ +#define EM28XX_URB_TIMEOUT \ + msecs_to_jiffies(EM28XX_NUM_BUFS * EM28XX_NUM_PACKETS) + +/* time in msecs to wait for i2c writes to finish */ +#define EM2800_I2C_WRITE_TIMEOUT 20 + +enum em28xx_fe_bandwidth { + EM28XX_BANDWIDTH_8_MHZ, + EM28XX_BANDWIDTH_7_MHZ, + EM28XX_BANDWIDTH_6_MHZ, + EM28XX_BANDWIDTH_AUTO +}; + +extern struct list_head em28xx_devlist; + +/* the various frame states */ +enum em28xx_frame_state { + F_UNUSED = 0, + F_QUEUED, + F_GRABBING, + F_DONE, + F_ERROR, +}; + +/* stream states */ +enum em28xx_stream_state { + STREAM_OFF, + STREAM_ON, + STREAM_INTERRUPT, +}; + +/* frames */ +struct em28xx_frame_t { + void *bufmem; + struct v4l2_buffer buf; + enum em28xx_frame_state state; + struct list_head frame; + unsigned long vma_use_count; + int top_field; + int fieldbytesused; +}; + +/* io methods */ +enum em28xx_io_method { + IO_NONE, + IO_READ, + IO_MMAP, +}; + +/* Colorspace Output */ + +/* To be discussed */ + +#ifndef V4L2_PIX_FMT_YUV211 +#define V4L2_PIX_FMT_YUV211 v4l2_fourcc('Y','2','1','1') +#endif + +#ifndef V4L2_PIX_FMT_YUY1 +#define V4L2_PIX_FMT_YUY1 v4l2_fourcc('Y','U','Y','1') +#endif + +#ifndef V4L2_PIX_FMT_Y21P +#define V4L2_PIX_FMT_Y21P v4l2_fourcc('Y','2','1','P') +#endif + +struct em28xx_output_fmt { + struct v4l2_fmtdesc fmt; + u8 config; +}; + +/* inputs */ + +#define MAX_EM28XX_INPUT 4 +#define MAX_EM28XX_TVNORMS 10 +#define MAX_EM28XX_DVBNORMS 5 +#define MAX_EM28XX_FMNORMS 2 +#define MAX_EM28XX_ATSCNORMS 2 +#define MAX_EM28XX_QAMNORMS 2 + +enum enum28xx_itype { + EM28XX_VMUX_COMPOSITE1 = 1, + EM28XX_VMUX_COMPOSITE2, + EM28XX_VMUX_COMPOSITE3, + EM28XX_VMUX_COMPOSITE4, + EM28XX_VMUX_SVIDEO, + EM28XX_VMUX_TELEVISION, + EM28XX_VMUX_CABLE, + EM28XX_VMUX_DVB, + EM28XX_VMUX_DEBUG, + EM28XX_AMUX_RADIO, +}; + +enum enum28xx_mixchannel { + EM28XX_MIX_NOTOUCH = 0, + EM28XX_MIX_LINE_IN = 1, + EM28XX_MIX_VIDEO = 2, +}; + +struct em28xx_input { + enum enum28xx_itype type; + unsigned int vmux; + unsigned int amux; + enum enum28xx_mixchannel amix; +}; + +#define INPUT(nr) (&em28xx_boards[dev->model].input[nr]) + +enum em28xx_decoder { + EM28XX_TVP5150, + EM28XX_SAA7113, + EM28XX_SAA7114, + EM28XX_CX25843, +}; + +struct em28xx; + +/* 0x0 - undef */ +enum empia_type { + EM2800 = 1, + EM2820, + EM2840, + EM2750, + EM2751, + EM2860, + EM2875, + EM2880, + EM2881, + EM2882, + EM2883, + EM2888, + EM2889 +}; + +#define EM28XX_VIDEO 0x01 +#define EM28XX_DVBT 0x02 +#define EM28XX_DVBC 0x04 +#define EM28XX_ATSC 0x08 +#define EM28XX_DMB 0x10 +#define EM28XX_ISDB 0x20 +#define EM28XX_VBI 0x40 +#define EM28XX_REMOTE 0x80 +#define EM28XX_AUDIO 0x100 /* vendor specific audiodriver */ +#define EM28XX_AUDIO2 0x200 /* vendor specific audiodriver, different interface number */ +#define EM28XX_RADIO 0x400 +#define EM28XX_LOCK 0x800 + +#define EM28XX_CAPTURE_STREAM_EN 1 + +/* used by the audio driver */ +#define EM28XX_ENABLE_AUDIO 1 +#define EM28XX_DISABLE_AUDIO 2 + +/* tvnorms */ +struct em28xx_tvnorm { + char *name; + XC3028_TV_MODE *tv_mode; + XC3028_CHANNEL_MAP *channelmap; + int index; /* xc5000 configuration */ + + /* digital tv */ + enum em28xx_fe_bandwidth bandwidth; + /* analog tv */ + v4l2_std_id id; + u32 vbi_sample_rate; + u16 vbi_samples_per_line; + u16 vbi_lines; + u16 vbi_offset; + u16 vbi_start_0; + u16 vbi_start_1; + u8 vbi_count_0; + u8 vbi_count_1; + + u8 vbi_h_start; + u8 vbi_v_start; + u8 vbi_w; + u8 vbi_h; +}; + +/* gpio definitions */ +#define EM28XX_GPIO_EN 0x1 + +#define EM28XX_GOP0 0x10 +#define EM28XX_GOP1 0x11 +#define EM28XX_GOP2 0x12 +#define EM28XX_GOP3 0x13 + +#define EM28XX_GPIO0 0x0 +#define EM28XX_GPIO1 0x1 +#define EM28XX_GPIO2 0x2 +#define EM28XX_GPIO3 0x3 +#define EM28XX_GPIO4 0x4 +#define EM28XX_GPIO5 0x5 +#define EM28XX_GPIO6 0x6 +#define EM28XX_GPIO7 0x7 + +/* helpers */ +#define _BIT_VAL(reg, val, reset) (reg | 0x80 | (reset?1<<6:0) | (val<<5)) + +/* internal gpio controls */ +#define EM28XX_TS1_ON 1 +#define EM28XX_ANALOG_ON 2 +#define EM28XX_LED1_ON 3 +#define EM28XX_XC3028_SECAM 4 +#define EM28XX_MODESWITCH 5 +#define EM28XX_DECODER_SLEEP 6 +#define EM28XX_LED2_ON 7 +#define EM28XX_RF 8 +#define EM28XX_DVB1_ON 9 +#define EM28XX_DVB2_ON 10 +#define EM28XX_TUNER1_ON 11 +#define EM28XX_TUNER2_ON 12 +#define EM28XX_DEMOD1_RESET 13 +#define EM28XX_TUNER1_RESET 14 +#define EM28XX_DECODER_RESET 15 +#define EM28XX_DEMOD2_RESET 16 +#define EM28XX_TUNER2_RESET 17 +#define EM28XX_I2C_BUS 18 + +/* internal gpio control arguments */ +#define EM28XX_REG_ON 1 +#define EM28XX_REG_OFF 2 + +struct em28xx_gpio { + u16 d1_reset; + u16 d2_reset; + u16 t1_reset; + u16 t2_reset; + u16 dc_reset; + u16 a_on; + u16 l1_on; + u16 l2_on; + u16 xc3028_sec; + u16 m_switch; + u16 d_sleep; + u16 ts1_on; + u16 ts2_on; + u16 t1_on; + u16 t2_on; + u16 rf; + u16 dvbs_lnb; + u16 dvbs_v; +}; + +struct em28xx_board { + char *name; + int vchannels; + v4l2_std_id norm; + int tuner_type; + int tuner_addr; + + /* i2c flags */ + unsigned int em_type; + /* null terminated list of used i2c addresses */ + /* used to autodetect some em2800 devices without eeprom */ + const u8 *i2c_devs; + u8 tda9887_conf; + + u8 has_tuner:1; + u8 has_inttuner:1; + u8 has_msp34xx:1; + u8 has_radio:1; + u8 powersaving:1; + + u8 manual_gpio:1; + u8 ir_i2c:1; /* remote is i2c based */ + + struct em28xx_gpio gpio_regs; + + u16 dev_modes; + + enum em28xx_decoder decoder; + + struct em28xx_tvnorm tvnorms[MAX_EM28XX_TVNORMS]; + struct em28xx_tvnorm dvbnorms[MAX_EM28XX_DVBNORMS]; + struct em28xx_tvnorm fmnorms[MAX_EM28XX_FMNORMS]; + struct em28xx_tvnorm atscnorms[MAX_EM28XX_ATSCNORMS]; + struct em28xx_tvnorm qamnorms[MAX_EM28XX_QAMNORMS]; + + int (*ctrl)(struct em28xx *dev, struct v4l2_control *ctrl); + int (*gctrl)(struct em28xx *dev, struct v4l2_control *ctrl); + int (*qctrl)(struct v4l2_queryctrl *qctrl); + + IR_KEYTAB_TYPE *ir_keytab; + int (*ir_getkey)(struct em28xx *dev, u32 *ir_key, u32 *keystatus); + + struct em28xx_input input[MAX_EM28XX_INPUT]; +}; + +struct em28xx_eeprom { + u32 id; /* 0x9567eb1a */ + u16 vendor_ID; + u16 product_ID; + + u16 chip_conf; + + u16 board_conf; + + u16 string1, string2, string3; + + u8 string_idx_table; +}; + +/* device states */ +enum em28xx_dev_state { + DEV_INITIALIZED = 0x01, + DEV_DISCONNECTED = 0x02, + DEV_MISCONFIGURED = 0x04, +}; + + +/* digital main device struct */ + +enum mtype { + EM28XX_ZL10353, + EM28XX_MT352, + EM28XX_DRX3975D, + EM28XX_LGDT330X, +}; + +struct em2880_dvb { + char *name; + struct dvb_demux demux; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 15) + struct mutex sem; +#else + struct semaphore sem; +#endif + int streaming; + enum mtype mod_type; + struct dvb_adapter adapter; + struct dvb_frontend *frontend; + struct dvb_device *fedev; + struct dmxdev dmxdev; + struct em28xx *em28xx_dev; /* please get rid of it lateron */ + struct dvb_net dvbnet; + struct usb_device *udev; /* the usb device */ + char *transfer_buffer[EM2880_DVB_NUM_BUFS]; /* transfer buffers for isoc transfer */ + struct urb *urb[EM2880_DVB_NUM_BUFS]; /* urb for isoc transfers */ + int (*demod_init)(struct dvb_frontend *fe); + int bw_index; /* bandwidth index */ + int (*init_override)(struct dvb_frontend *fe); + u16 dtv_packetsize; +}; + +struct em28xx_audio { + char name[50]; + char *transfer_buffer[EM28XX_AUDIO_BUFS]; + struct urb *urb[EM28XX_AUDIO_BUFS]; + struct usb_device *udev; + unsigned int capture_transfer_done; +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 16) + snd_pcm_substream_t *capture_pcm_substream; +#else + struct snd_pcm_substream *capture_pcm_substream; +#endif + + unsigned int hwptr_done_capture; +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 16) + snd_card_t *sndcard; +#else + struct snd_card *sndcard; +#endif + + int users; + unsigned int shutdown:1; + spinlock_t slock; /* for protecting the alsa buffer */ + enum em28xx_stream_state capture_stream; + wait_queue_head_t audio_wait_stream, open; + u16 max_pck; + u8 alt_max; + /* states */ + enum em28xx_dev_state state; + u8 alt; /* alternate */ + struct usb_interface usb_dev; +}; + + +struct em2880_ir { + u8 old; + u8 sequence[4]; + IR_KEYTAB_TYPE *keymap; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) + struct work_struct work; +#else + struct delayed_work work; +#endif + + struct input_dev *input; + struct em28xx *dev; + int keypressed; + struct timer_list timer; + u32 keycode; + + u32 oldval; + u32 oldtimeoutval; + u32 key; + u8 btn; + + u8 name[50]; + u8 phys[50]; + u8 released:1; + u8 state; + struct mutex state_lock; +#define EM28XX_REMOTE_IDLE 0 +#define EM28XX_REMOTE_POLLING 1 +#define EM28XX_REMOTE_INTERRUPT 2 + int (*get_key)(struct em28xx *dev, u32 *ir_key, u32 *keystatus); + wait_queue_head_t remote_loop; +}; + +struct em28xx_fh { + struct em28xx *dev; + unsigned int reader:1; + int type; +}; + + +/* main device struct */ +struct em28xx { + /* 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 */ + unsigned int em_type; + u8 video_inputs; /* number of video inputs */ + struct list_head devlist; + u8 has_tuner:1; + u8 has_inttuner:1; + u8 has_msp34xx:1; + u8 has_tda9887:1; + u8 has_vbi:1; + u8 device_mode:1; /* EM28XX_VIDEO | EM28XX_DVB */ + u16 dev_modes; + u8 manual_gpio:1; + u8 powersaving:1; + + u8 modules_requested; +#define EM28XX_MODULES_REQUESTED 0 +#define EM28XX_MODULES_PENDING 1 + wait_queue_head_t mod_request; + + void (*request_modules)(struct em28xx *dev); + + u8 vbi_interlaced:1; + + u8 vbi_frame_done:1; + + u8 usb_interface; + + u8 audio_user; + u8 video_user; + u8 radio_user; + u8 fe_user; + u8 vbi_user; + u8 mode_lock; + + + int mode; + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17) + struct delayed_work request_module_wk; +#endif + + struct em28xx_board *board; + struct usb_interface *uif; + struct em28xx_output_fmt *outfmt; + + u32 i2s_speed; /* I2S speed for audio digital stream */ + + enum em28xx_decoder decoder; + + int tuner_type; /* type of the tuner */ + u8 tuner_addr; /* tuner address */ + int tda9887_conf; + + struct tuner_module *tuner; + /* i2c i/o */ + struct i2c_adapter i2c_adap; + struct i2c_client i2c_client; + + /* dvb */ + struct em2880_dvb *dvb_dev; + struct em28xx_audio *adev; + struct em2880_ir *ir_em2880; + + /* video for linux */ + u8 users; /* user count for exclusive use */ + + struct video_device *rdev; /* video for linux device struct */ + struct video_device *vdev; /* video for linux device struct */ + struct video_device *vbi_dev; + + struct em28xx_tvnorm *tvnorm; /* selected tv norm */ + struct em28xx_tvnorm *dvbnorm; + struct em28xx_tvnorm *atscnorm; + struct em28xx_tvnorm *qamnorm; + struct em28xx_tvnorm *fmnorm; + unsigned int tv_std; + + unsigned long rctl_freq; /* selected radio frequency */ + unsigned long vctl_freq; /* selected video frequency */ + unsigned long dctl_freq; /* selected dvb-t/atsc frequency */ + u8 ctl_input; /* selected input */ + u8 ctl_ainput; /* selected audio input */ + enum enum28xx_mixchannel ctl_amix; /* slected audio mixer channel */ + u8 mute; + int volume; + /* frame properties */ + struct em28xx_frame_t frame[EM28XX_NUM_FRAMES]; /* list of frames */ + struct em28xx_frame_t vbi_frame[EM28XX_NUM_FRAMES]; /* list of frames */ + u8 num_frames; /* number of frames currently in use */ + u8 vbi_num_frames; + u32 frame_count; /* total number of transfered frames */ + u32 vbi_frame_count; /* total number of transfered frames */ + struct em28xx_frame_t *frame_current; /* the frame that is being filled */ + struct em28xx_frame_t *vbi_frame_current; /* the frame that is being filled */ + u16 width; /* current frame width */ + u16 height; /* current frame height */ + u32 frame_size; /* current frame size */ + u32 field_size; /* current field size */ + u16 vbi_frame_size; + u16 vbi_field_size; + u16 bytesperline; + u16 vbi_bytesperline; + u16 hscale; /* horizontal scale factor (see datasheet) */ + u16 vscale; /* vertical scale factor (see datasheet) */ + u8 interlaced:1; /* 1=interlace fileds, 0=just top fileds */ + int type; + + u8 reader:1; + u8 vbi_reader:1; + u8 video_reader:1; + + /* states */ + enum em28xx_dev_state state; + + enum em28xx_stream_state stream; + enum em28xx_stream_state video_stream; + enum em28xx_stream_state vbi_stream; + + enum em28xx_io_method io; + + enum em28xx_io_method video_io; + enum em28xx_io_method vbi_io; + + /* locks */ +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 15) + struct mutex lock, fileop_lock, vbi_fileop_lock, input_lock; +#else + struct semaphore lock, fileop_lock, vbi_fileop_lock, input_lock; +#endif + spinlock_t queue_lock; + spinlock_t vbi_queue_lock; + + struct list_head inqueue, outqueue, vbi_inqueue, vbi_outqueue; + wait_queue_head_t open, wait_frame, wait_vbi_frame, vbi_wait_stream; + wait_queue_head_t video_wait_stream; + u32 video_bytesread; + u16 vbi_bytesread; + u16 vbi_dropbytes; + + unsigned char eedata[256]; + + /* usb transfer */ + struct usb_device *udev; /* the usb device */ + u8 alt; /* alternate */ + u16 max_pkt_size; /* max packet size of isoc transaction */ + u8 num_alt; /* Number of alternative settings */ + unsigned int *alt_max_pkt_size; /* array of wMaxPacketSize */ + struct urb *urb[EM28XX_NUM_BUFS]; /* urb for isoc transfers */ + char *transfer_buffer[EM28XX_NUM_BUFS]; /* transfer buffers for isoc transfer */ + + /* helper funcs that call usb_control_msg */ + + int (*em28xx_acquire)(struct em28xx *dev, int mode, int lock); + int (*em28xx_callback)(void *priv, int ptr, int mode); + + int (*em28xx_write_regs) (struct em28xx *dev, u16 reg, char *buf, + int len); + int (*em28xx_read_reg) (struct em28xx *dev, u16 reg); + int (*em28xx_read_reg_req_len) (struct em28xx *dev, u8 req, u16 reg, + char *buf, int len); + int (*em28xx_write_regs_req) (struct em28xx *dev, u8 req, u16 reg, + char *buf, int len); + int (*em28xx_read_reg_req) (struct em28xx *dev, u8 req, u16 reg); + int (*em28xx_write_reg_bits)(struct em28xx *dev, u16 reg, u8 val, + u8 bitmask); + + int (*em28xx_ctrl)(struct em28xx *dev, struct v4l2_control *ctrl); + int (*em28xx_gctrl)(struct em28xx *dev, struct v4l2_control *ctrl); + int (*em28xx_qctrl)(struct v4l2_queryctrl *qctrl); + + int (*em28xx_gpio_control)(void *priv, unsigned int command, void *ptr); + int (*em28xx_aad_control)(void *priv, unsigned int command, void *ptr); + + struct em28xx_aad_info *aad; + +}; + +struct em28xx_ops { + struct list_head next; + char *name; + int id; + int (*init)(struct em28xx *); + int (*fini)(struct em28xx *); +}; + +/* Provided by em28xx-i2c.c */ +int em2800_i2c_check_for_device(struct em28xx *dev, unsigned char addr); +void em28xx_i2c_call_clients(struct em28xx *dev, unsigned int cmd, void *arg); +int em28xx_i2c_register(struct em28xx *dev); +int em28xx_i2c_unregister(struct em28xx *dev); + +/* Provided by em28xx-input.c */ + +void em28xx_set_ir(struct em28xx *dev, struct IR_i2c *ir); + +/* Provided by em28xx-core.c */ + +u32 em28xx_request_buffers(struct em28xx *dev, u32 count, int type); +void em28xx_queue_unusedframes(struct em28xx *dev, int type); +void em28xx_release_buffers(struct em28xx *dev, int type); + +int em28xx_read_reg_req_len(struct em28xx *dev, u8 req, u16 reg, + char *buf, int len); +int em28xx_read_reg_req(struct em28xx *dev, u8 req, u16 reg); +int em28xx_read_reg(struct em28xx *dev, u16 reg); +int em28xx_write_regs_req(struct em28xx *dev, u8 req, u16 reg, char *buf, + int len); +int em28xx_write_regs(struct em28xx *dev, u16 reg, char *buf, int len); +int em28xx_write_reg_bits(struct em28xx *dev, u16 reg, u8 val, + u8 bitmask); +int em28xx_write_ac97(struct em28xx *dev, u8 reg, u8 *val); +int em28xx_audio_analog_set(struct em28xx *dev); +int em28xx_colorlevels_set_default(struct em28xx *dev); +int em28xx_capture_start(struct em28xx *dev, int start); +int em28xx_outfmt_set_yuv422(struct em28xx *dev); +int em28xx_accumulator_set(struct em28xx *dev, u8 xmin, u8 xmax, u8 ymin, + u8 ymax); +int em28xx_capture_area_set(struct em28xx *dev, u8 hstart, u8 vstart, + u16 width, u16 height); +int em28xx_scaler_set(struct em28xx *dev, u16 h, u16 v); +int em28xx_resolution_set(struct em28xx *dev); +int em28xx_init_isoc(struct em28xx *dev); +void em28xx_uninit_isoc(struct em28xx *dev); +int em28xx_set_alternate(struct em28xx *dev); +int em28xx_register_extension(struct em28xx_ops *dev); +void em28xx_unregister_extension(struct em28xx_ops *dev); +int em2880_ir_detach(struct em28xx *dev); +int em2880_ir_attach(struct em28xx *dev); +int em2880_get_key_terratec(struct em28xx *dev, u32 *ir_key, u32 *keystatus); +int em2880_get_key_empia(struct em28xx *dev, u32 *ir_key, u32 *keystatus); +int em2888_get_key_empia(struct em28xx *dev, u32 *ir_key, u32 *keystatus); +int em2860_get_key_kaiomy(struct em28xx *dev, u32 *ir_key, u32 *keystatus); +int em28xx_set_vbi(struct em28xx *dev, int enable); + + +extern struct workqueue_struct *em28xx_wq; + + +/* Provided by em28xx-cards.c */ +extern int em2800_variant_detect(struct usb_device* udev, int model); +extern int em28xx_card_setup(struct em28xx *dev); +extern void em28xx_card_disconnect(struct em28xx *dev); +extern struct em28xx_board em28xx_boards[]; +extern struct usb_device_id em28xx_id_table[]; +extern const unsigned int em28xx_bcount; +extern int em28xx_gpio_control(void *priv, unsigned int command, void *ptr); +extern int em28xx_gpio_control_translate(void *priv, unsigned int command, void *ptr); +extern int em28xx_qt1010_reset_control(void *priv, int command, void *ptr); + +/* Videology specific functions */ + +/* white balance mode */ +#define V4L2_VY_WBM (V4L2_CID_PRIVATE_BASE+0) +/* shutter speed */ +#define V4L2_VY_SHUTTER (V4L2_CID_PRIVATE_BASE+1) +/* mirror mode */ +#define V4L2_VY_MIRROR (V4L2_CID_PRIVATE_BASE+2) +/* gain control on/off */ +#define V4L2_VY_GAIN_CONTROL_EN (V4L2_CID_PRIVATE_BASE+3) +/* gain control values 0x00 - 0x7f */ +#define V4L2_VY_GAIN_CONTROL (V4L2_CID_PRIVATE_BASE+4) +/* enable edge enhance */ +#define V4L2_VY_EDGE_ENHANCE_EN (V4L2_CID_PRIVATE_BASE+5) +/* edge enhance values 0x00 - 0x1f */ +#define V4L2_VY_EDGE_ENHANCE (V4L2_CID_PRIVATE_BASE+6) +/* BLC (???) values 0x00 (off) - 0x40 */ +#define V4L2_VY_BLC (V4L2_CID_PRIVATE_BASE+7) +/* RGain value 0x00-0xff */ +#define V4L2_VY_RGAIN (V4L2_CID_PRIVATE_BASE+8) +/* BGain value 0x00-0xff */ +#define V4L2_VY_BGAIN (V4L2_CID_PRIVATE_BASE+9) + + +int em28xx_vy_cctrl(struct em28xx *dev, struct v4l2_control *ctrl); +int em28xx_vy_gctrl(struct em28xx *dev, struct v4l2_control *ctrl); +int em28xx_vy_qctrl(struct v4l2_queryctrl *qctrl); + +/* em28xx registers */ +#define R06_I2C_CLK_REG 0x06 +#define R0A_CHIPID_REG 0x0a +#define R0C_USBSUSP_REG 0x0c + +#define R0E_AUDIOSRC_REG 0x0e +#define R0F_XCLK_REG 0x0f + +#define R10_VINMODE_REG 0x10 +#define R11_VINCTRL_REG 0x11 +#define R12_VINENABLE_REG 0x12 + +#define R14_GAMMA_REG 0x14 +#define R15_RGAIN_REG 0x15 +#define R16_GGAIN_REG 0x16 +#define R17_BGAIN_REG 0x17 +#define R18_ROFFSET_REG 0x18 +#define R19_GOFFSET_REG 0x19 +#define R1A_BOFFSET_REG 0x1a + +#define R1B_OFLOW_REG 0x1b +#define R1C_HSTART_REG 0x1c +#define R1D_VSTART_REG 0x1d +#define R1E_CWIDTH_REG 0x1e +#define R1F_CHEIGHT_REG 0x1f + +#define R20_YGAIN_REG 0x20 +#define R21_YOFFSET_REG 0x21 +#define R22_UVGAIN_REG 0x22 +#define R23_UOFFSET_REG 0x23 +#define R24_VOFFSET_REG 0x24 +#define R25_SHARPNESS_REG 0x25 + +#define R26_COMPR_REG 0x26 +#define R27_OUTFMT_REG 0x27 + +#define R28_XMIN_REG 0x28 +#define R29_XMAX_REG 0x29 +#define R2A_YMIN_REG 0x2a +#define R2B_YMAX_REG 0x2b + +#define R30_HSCALELOW_REG 0x30 +#define R31_HSCALEHIGH_REG 0x31 +#define R32_VSCALELOW_REG 0x32 +#define R33_VSCALEHIGH_REG 0x33 + +#define R40_AC97LSB_REG 0x40 +#define R41_AC97MSB_REG 0x41 +#define R42_AC97ADDR_REG 0x42 +#define R43_AC97BUSY_REG 0x43 + +/* em202 registers */ +#define R02_MASTER_AC97 0x02 +#define R10_LINE_IN_AC97 0x10 +#define R14_VIDEO_AC97 0x14 + +/* em2800 registers */ +#define EM2800_AUDIOSRC_REG 0x08 + +/* register settings */ +#define EM2800_AUDIO_SRC_TUNER 0x0d +#define EM2800_AUDIO_SRC_LINE 0x0c +#define EM28XX_AUDIO_SRC_TUNER 0xc0 +#define EM28XX_AUDIO_SRC_LINE 0x80 + +/* printk macros */ + +#define em28xx_err(fmt, arg...) do {\ + printk(KERN_ERR fmt , ##arg); } while (0) + +#define em28xx_errdev(fmt, arg...) do {\ + printk(KERN_ERR "%s: "fmt,\ + dev->name , ##arg); } while (0) + +#define em28xx_info(fmt, arg...) do {\ + printk(KERN_INFO "%s: "fmt,\ + dev->name , ##arg); } while (0) +#define em28xx_warn(fmt, arg...) do {\ + printk(KERN_WARNING "%s: "fmt,\ + dev->name , ##arg); } while (0) + +void em28xx_config_i2c(struct em28xx *dev); +int em28xx_config(struct em28xx *dev); + +static inline int em28xx_audio_source(struct em28xx *dev, int input) +{ + if (dev->em_type == EM2800) { + u8 tmp = EM2800_AUDIO_SRC_TUNER; + if (input == EM28XX_AUDIO_SRC_LINE) + tmp = EM2800_AUDIO_SRC_LINE; + em28xx_write_regs(dev, EM2800_AUDIOSRC_REG, &tmp, 1); + } + return em28xx_write_reg_bits(dev, R0E_AUDIOSRC_REG, input, 0xc0); +} + +/* FIXME: return something sane here */ + +#define EM28XX_MUTED 1 +#define EM28XX_UNMUTED 0 + +static inline int em28xx_audio_usb_mute(struct em28xx *dev, int mute) +{ + switch (dev->em_type) { + case EM2750: + em28xx_write_regs(dev, R0F_XCLK_REG, "\x0a", 1); + break; + default: + em28xx_write_reg_bits(dev, R0F_XCLK_REG, mute ? 0x00 : 0x80, 0x80); + } + return 0; +} + +static inline int em28xx_audio_analog_setup(struct em28xx *dev) +{ + /* unmute video mixer with default volume level */ + return em28xx_write_ac97(dev, R14_VIDEO_AC97, "\x08\x08"); +} + +static inline int em28xx_audio_set_mixer(struct em28xx *dev, enum enum28xx_mixchannel chan) +{ + int ret = 0; + + if (chan == EM28XX_MIX_NOTOUCH) + return ret; + if ((ret = em28xx_write_ac97(dev, R10_LINE_IN_AC97, + chan == EM28XX_MIX_LINE_IN ? + "\x08\x08" : "\x08\x88"))) + return ret; + if ((ret = em28xx_write_ac97(dev, R14_VIDEO_AC97, chan == EM28XX_MIX_VIDEO ? + "\x08\x08":"\x08\x88"))) + return ret; + return ret; +} + +static inline int em28xx_compression_disable(struct em28xx *dev) +{ + /* side effect of disabling scaler and mixer */ + return em28xx_write_regs(dev, R26_COMPR_REG, "\x00", 1); +} + +static inline int em28xx_contrast_get(struct em28xx *dev) +{ + return em28xx_read_reg(dev, R20_YGAIN_REG) & 0x1f; +} + +static inline int em28xx_brightness_get(struct em28xx *dev) +{ + return em28xx_read_reg(dev, R21_YOFFSET_REG); +} + +static inline int em28xx_saturation_get(struct em28xx *dev) +{ + return em28xx_read_reg(dev, R22_UVGAIN_REG) & 0x1f; +} + +static inline int em28xx_u_balance_get(struct em28xx *dev) +{ + return em28xx_read_reg(dev, R23_UOFFSET_REG); +} + +static inline int em28xx_v_balance_get(struct em28xx *dev) +{ + return em28xx_read_reg(dev, R24_VOFFSET_REG); +} + +static inline int em28xx_gamma_get(struct em28xx *dev) +{ + return em28xx_read_reg(dev, R14_GAMMA_REG) & 0x3f; +} + +static inline int em28xx_contrast_set(struct em28xx *dev, s32 val) +{ + u8 tmp = (u8) val; + return em28xx_write_regs(dev, R20_YGAIN_REG, &tmp, 1); +} + +static inline int em28xx_brightness_set(struct em28xx *dev, s32 val) +{ + u8 tmp = (u8) val; + return em28xx_write_regs(dev, R21_YOFFSET_REG, &tmp, 1); +} + +static inline int em28xx_saturation_set(struct em28xx *dev, s32 val) +{ + u8 tmp = (u8) val; + return em28xx_write_regs(dev, R22_UVGAIN_REG, &tmp, 1); +} + +static inline int em28xx_u_balance_set(struct em28xx *dev, s32 val) +{ + u8 tmp = (u8) val; + return em28xx_write_regs(dev, R23_UOFFSET_REG, &tmp, 1); +} + +static inline int em28xx_v_balance_set(struct em28xx *dev, s32 val) +{ + u8 tmp = (u8) val; + return em28xx_write_regs(dev, R24_VOFFSET_REG, &tmp, 1); +} + +static inline int em28xx_gamma_set(struct em28xx *dev, s32 val) +{ + u8 tmp = (u8) val; + return em28xx_write_regs(dev, R14_GAMMA_REG, &tmp, 1); +} + +/*FIXME: especially em2800 devices have a too small framebuffer + for 720/YUY2 frames */ +static inline unsigned int norm_maxw(struct em28xx *dev) +{ + switch (dev->model) { + default: return 720; + } +} + +static inline unsigned int norm_maxh(struct em28xx *dev) +{ + switch (dev->model) { + default: return (dev->tvnorm->id & V4L2_STD_625_50) ? 576 : 480; + } +} + +#endif