[PATCH 1/7] Adding empia base driver

From: Markus Rechberger
Date: Wed Oct 22 2008 - 17:15:24 EST


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
commit a18816f6b91629eb6038aed4815e4fb94ddc0a6b
Author: Markus Rechberger <mrechberger@xxxxxxxxxx>
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 <mrechberger@xxxxxxxxxx>

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 <mrechberger@xxxxxxxxxx>
+ *
+ * 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 <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/suspend.h>
+#include <linux/dvb/frontend.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+
+#include "em28xx.h"
+
+#include <media/tuner.h>
+#include <media/v4l2-common.h>
+#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 <mrechberger@xxxxxxxxx>");
+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 = &params->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; i<dev->uif->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 <linux/version.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <linux/wait.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/proc_fs.h>
+#include <linux/i2c.h>
+#include <linux/usb.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/fcntl.h>
+#include <asm/uaccess.h>
+
+#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;i<urb->number_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;i<AAD_AUDIO_BUFS;i++){
+ usb_kill_urb(aad->urb[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;i<AAD_AUDIO_BUFS;i++){
+ struct urb *urb;
+ int j,k;
+ aad->transfer_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; j<AAD_NUM_AUDIO_PACKETS;j++,k+=AAD_AUDIO_MAX_PACKET_SIZE){
+ urb->iso_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;i<AAD_AUDIO_BUFS;i++){
+ errCode = usb_submit_urb(aad->urb[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);
+ aad->__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<<aad->__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 <linux/poll.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/compiler.h> /* 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 <mrechberger@xxxxxxxxxx>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/usb.h>
+#include <linux/init.h>
+#include <linux/sound.h>
+#include <linux/spinlock.h>
+#include <linux/soundcard.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25)
+#include <sound/driver.h>
+#endif
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/proc_fs.h>
+#include <linux/moduleparam.h>
+#include <linux/wait.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <media/v4l2-common.h>
+#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;i<urb->number_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;i<EM28XX_AUDIO_BUFS;i++){
+ usb_kill_urb(dev->adev->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;i<EM28XX_AUDIO_BUFS;i++){
+ struct urb *urb;
+ int j,k;
+ dev->adev->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; j<EM28XX_NUM_AUDIO_PACKETS;j++,k+=EM28XX_AUDIO_MAX_PACKET_SIZE){
+ urb->iso_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;i<EM28XX_AUDIO_BUFS;i++){
+ errCode = usb_submit_urb(dev->adev->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 <mrechberger@xxxxxxxxx>");
+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 <mrechberger@xxxxxxxxx>
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/usb.h>
+#include <linux/sound.h>
+#include <linux/soundcard.h>
+#include <linux/vmalloc.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25)
+#include <sound/driver.h>
+#endif
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+
+
+#include "em28xx.h"
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+
+MODULE_AUTHOR("Markus Rechberger <mrechberger@xxxxxxxxx>");
+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;i<urb->number_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; i<EM28XX_AUDIO_BUFS; i++){
+ usb_kill_urb(adev->urb[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; i<EM28XX_AUDIO_BUFS; i++){
+ struct urb *urb;
+ int j,k;
+ adev->transfer_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; j<EM28XX_NUM_AUDIO_PACKETS; j++,k+=EM28XX_AUDIO_MAX_PACKET_SIZE){
+ urb->iso_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; i<EM28XX_AUDIO_BUFS; i++){
+ errCode = usb_submit_urb(adev->urb[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; i<max_alt; i++) {
+ cur_pck=le16_to_cpu(interface->altsetting[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 <mrechberger@xxxxxxxxx>
+ 2005 Mauro Carvalho Chehab <mchehab@xxxxxxxxxxxxx>
+ 2005 Sascha Sommer <saschasommer@xxxxxxxxxx>
+ 2005 Ludovico Cavedon <cavedon@xxxxxxxx>
+
+ 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 <linux/init.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+#include <linux/firmware.h>
+#include <media/tuner.h>
+#ifdef EM28XX_TVEEPROM
+#include <media/tveeprom.h>
+#endif
+#include <media/v4l2-common.h>
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 26)
+#include <media/audiochip.h>
+#else
+#include <media/v4l2-chip-ident.h>
+#endif
+#include "msp3400-driver.h"
+#include <media/saa7115.h>
+#include <media/tvp5150.h>
+
+#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 <cavedon@xxxxxxxx>
+ Markus Rechberger <mrechberger@xxxxxxxxx>
+ Mauro Carvalho Chehab <mchehab@xxxxxxxxxxxxx>
+ Sascha Sommer <saschasommer@xxxxxxxxxx>
+
+ 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 <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/usb.h>
+#include <linux/vmalloc.h>
+
+#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 <cavedon@xxxxxxxx>
+ Markus Rechberger <mrechberger@xxxxxxxxx>
+ Mauro Carvalho Chehab <mchehab@xxxxxxxxxxxxx>
+ Sascha Sommer <saschasommer@xxxxxxxxxx>
+
+ 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <linux/version.h>
+#include <linux/video_decoder.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27)
+#include <media/audiochip.h>
+#else
+#include <media/v4l2-chip-ident.h>
+#endif
+#ifdef EM28XX_TVEEPROM
+#include <media/tveeprom.h>
+#endif
+
+#include "em28xx.h"
+#include <media/v4l2-common.h>
+#include <media/tuner.h>
+
+#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 <mrechberger@xxxxxxxxx>
+ 2005 Mauro Carvalho Chehab <mchehab@xxxxxxxxxxxxx>
+ 2005 Sascha Sommer <saschasommer@xxxxxxxxxx>
+
+
+ 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 <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+
+#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->key<IR_KEYTAB_SIZE && (
+ dev->board->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 <linux/input.h>
+#include <linux/workqueue.h>
+
+/* 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 <saschasommer@xxxxxxxxxx>
+ 2005-2007 Markus Rechberger <mrechberger@xxxxxxxxx>
+ 2005-2006 Mauro Carvalho Chehab <mchehab@xxxxxxxxxxxxx>
+ 2005 Ludovico Cavedon <cavedon@xxxxxxxx>
+
+ Some parts based on SN9C10x PC Camera Controllers GPL driver made
+ by Luca Risolia <luca.risolia@xxxxxxxxxxxxxxx>
+
+ 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 <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/bitmap.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <linux/version.h>
+#include <linux/video_decoder.h>
+#include "dvb_frontend.h"
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 15)
+#include <linux/mutex.h>
+#endif
+
+#include "em28xx.h"
+#include <media/tuner.h>
+#include <media/v4l2-common.h>
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 26)
+#include <media/v4l2-ioctl.h>
+#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 <linux/moduleparam.h>
+#endif
+
+#define DRIVER_AUTHOR "Ludovico Cavedon <cavedon@xxxxxxxx>, " \
+ "Markus Rechberger <mrechberger@xxxxxxxxx>, " \
+ "Mauro Carvalho Chehab <mchehab@xxxxxxxxxxxxx>, " \
+ "Sascha Sommer <saschasommer@xxxxxxxxxx>"
+
+#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<<dev->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<<dev->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<<dev->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<<dev->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<<dev->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<<dev->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 = <n> 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 = <n> 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<<nr;
+
+ /* Don't register audio interfaces */
+ if (interface->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<<nr);
+ return -ENODEV;
+ }
+
+ printk(KERN_INFO "em28xx: new video device (%04x:%04x): interface %i, class %i\n",
+ udev->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<<nr);
+ return -ENODEV;
+ }
+ if ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT) {
+ em28xx_err("em28xx: probing error: endpoint is ISO OUT endpoint!\n");
+ em28xx_devused &= ~(1<<nr);
+ return -ENODEV;
+ }
+ }
+
+ switch (udev->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<<nr);
+ return -ENOMEM;
+ }
+
+ /* allocate memory for our device state and initialize it */
+ dev = kzalloc(sizeof(struct em28xx), GFP_KERNEL);
+ if (dev == NULL) {
+ em28xx_err(DRIVER_NAME ": out of memory!\n");
+ em28xx_devused &= ~(1<<nr);
+ return -ENOMEM;
+ }
+
+ snprintf(dev->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<<nr);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < dev->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 <mrechberger@xxxxxxxxx>
+
+ 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 <linux/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+#include <media/v4l2-common.h>
+
+#include "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, &reg, 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, &reg_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, &reg_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 <mrechberger@xxxxxxxxx>
+ Ludovico Cavedon <cavedon@xxxxxxxx>
+ Mauro Carvalho Chehab <mchehab@xxxxxxxxxxxxx>
+
+ Based on the em2800 driver from Sascha Sommer <saschasommer@xxxxxxxxxx>
+
+ 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 <linux/version.h>
+#include <linux/videodev.h>
+#include <linux/i2c.h>
+#include <linux/dvb/frontend.h>
+#include "dmxdev.h"
+#include "dvb_demux.h"
+#include "dvb_net.h"
+#include "dvb_frontend.h"
+#include <linux/soundcard.h>
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25)
+#include <sound/driver.h>
+#endif
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#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 <linux/mutex.h>
+#endif
+#include <media/ir-kbd-i2c.h>
+
+/* 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