From: Michael Hunold <hunold@linuxtv.org>

- [DVB] new driver for mobile USB Budget DVB-T devices, thanks to Patrick
  Boettcher

Signed-off-by: Michael Hunold <hunold@linuxtv.org>
Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 25-akpm/drivers/media/dvb/Makefile            |    3 
 25-akpm/drivers/media/dvb/dibusb/Kconfig      |   31 +
 25-akpm/drivers/media/dvb/dibusb/Makefile     |    3 
 25-akpm/drivers/media/dvb/dibusb/dvb-dibusb.c |  719 ++++++++++++++++++++++++++
 25-akpm/drivers/media/dvb/dibusb/dvb-dibusb.h |  175 ++++++
 25-akpm/drivers/media/dvb/frontends/Kconfig   |   46 +
 6 files changed, 965 insertions(+), 12 deletions(-)

diff -puN /dev/null drivers/media/dvb/dibusb/dvb-dibusb.c
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ 25-akpm/drivers/media/dvb/dibusb/dvb-dibusb.c	2004-09-20 11:22:37.730914992 -0700
@@ -0,0 +1,719 @@
+/*
+ * Driver for mobile USB Budget DVB-T devices based on reference
+ * design made by DiBcom (http://www.dibcom.fr/)
+ *
+ * dvb-dibusb.c
+ *
+ * Copyright (C) 2004 Patrick Boettcher (patrick.boettcher@desy.de)
+ *
+ * based on GPL code from DiBcom, which has
+ *
+ * Copyright (C) 2004 Amaury Demol for DiBcom (ademol@dibcom.fr)
+ *
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License as
+ *	published by the Free Software Foundation, version 2.
+ *
+ * Acknowledgements
+ *
+ *  Amaury Demol (ademol@dibcom.fr) from DiBcom for providing specs and driver
+ *  sources, on which this driver (and the dib3000mb frontend) are based.
+ *
+ *  TODO
+ *   - probing for i2c addresses, it is possible, that they have been changed
+ *     by the vendor
+ *
+ * see Documentation/dvb/README.dibusb for more information
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/usb.h>
+#include <linux/firmware.h>
+#include <linux/version.h>
+#include <linux/moduleparam.h>
+#include <linux/pci.h>
+
+#include "dmxdev.h"
+#include "dvb_demux.h"
+#include "dvb_filter.h"
+#include "dvb_net.h"
+#include "dvb_frontend.h"
+
+#include "dvb-dibusb.h"
+
+/* debug */
+
+#ifdef CONFIG_DVB_DIBCOM_DEBUG
+#define dprintk_new(level,args...) \
+	    do { if ((debug & level)) { printk(args); } } while (0)
+
+#define debug_dump(b,l) if (debug) {\
+	int i; deb_xfer("%s: %d > ",__FUNCTION__,l); \
+	for (i = 0; i < l; i++) deb_xfer("%02x ", b[i]); \
+	deb_xfer("\n");\
+}
+
+static int debug;
+module_param(debug, int, 0x644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info,2=xfer,4=alotmore (|-able)).");
+#else
+#define dprintk_new(args...)
+#define debug_dump(b,l)
+#endif
+
+#define deb_info(args...) dprintk_new(0x01,args)
+#define deb_xfer(args...) dprintk_new(0x02,args)
+#define deb_alot(args...) dprintk_new(0x04,args)
+
+/* Version information */
+#define DRIVER_VERSION "0.0"
+#define DRIVER_DESC "DiBcom based USB Budget DVB-T device"
+#define DRIVER_AUTHOR "Patrick Boettcher, patrick.boettcher@desy.de"
+
+/* USB Driver stuff */
+
+/* table of devices that work with this driver */
+static struct usb_device_id dibusb_table [] = {
+	{ USB_DEVICE(USB_TWINHAN_VENDOR_ID, USB_VP7041_PRODUCT_PREFW_ID) },
+	{ USB_DEVICE(USB_TWINHAN_VENDOR_ID, USB_VP7041_PRODUCT_ID) },
+	{ USB_DEVICE(USB_IMC_NETWORKS_VENDOR_ID, USB_VP7041_PRODUCT_PREFW_ID) },
+	{ USB_DEVICE(USB_IMC_NETWORKS_VENDOR_ID, USB_VP7041_PRODUCT_ID) },
+	{ USB_DEVICE(USB_KWORLD_VENDOR_ID, USB_VSTREAM_PRODUCT_PREFW_ID) },
+	{ USB_DEVICE(USB_KWORLD_VENDOR_ID, USB_VSTREAM_PRODUCT_ID) },
+	{ USB_DEVICE(USB_DIBCOM_VENDOR_ID, USB_DIBCOM_PRODUCT_PREFW_ID) },
+	{ USB_DEVICE(USB_DIBCOM_VENDOR_ID, USB_DIBCOM_PRODUCT_ID) },
+	{ USB_DEVICE(USB_ULTIMA_ELECTRONIC_ID, USB_ULTIMA_ELEC_PROD_PREFW_ID) },
+	{ USB_DEVICE(USB_ULTIMA_ELECTRONIC_ID, USB_ULTIMA_ELEC_PROD_ID) },
+	{ }					/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, dibusb_table);
+
+static int dibusb_readwrite_usb(struct usb_dibusb *dib,
+		u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen)
+{
+	int actlen,ret = -ENOMEM;
+
+	if (wbuf == NULL || wlen == 0)
+		return -EINVAL;
+
+/*	if (dib->disconnecting)
+		return -EINVAL;*/
+
+	if ((ret = down_interruptible(&dib->usb_sem)))
+		return ret;
+
+	debug_dump(wbuf,wlen);
+
+	ret = usb_bulk_msg(dib->udev,COMMAND_PIPE,
+			wbuf,wlen,&actlen,DIBUSB_I2C_TIMEOUT);
+
+	if (ret)
+		err("bulk message failed: %d (%d/%d)",ret,wlen,actlen);
+	else
+		ret = actlen != wlen ? -1 : 0;
+
+	/* an answer is expected */
+	if (!ret && rbuf && rlen) {
+		ret = usb_bulk_msg(dib->udev,RESULT_PIPE,rbuf,rlen,
+				&actlen,DIBUSB_I2C_TIMEOUT);
+
+		if (ret)
+			err("recv bulk message failed: %d",ret);
+		else {
+			deb_alot("rlen: %d\n",rlen);
+			debug_dump(rbuf,actlen);
+		}
+	}
+
+	up(&dib->usb_sem);
+	return ret;
+}
+
+static int dibusb_write_usb(struct usb_dibusb *dib, u8 *buf, u16 len)
+{
+	return dibusb_readwrite_usb(dib,buf,len,NULL,0);
+}
+
+static int dibusb_i2c_msg(struct usb_dibusb *dib, u8 addr,
+		u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen)
+{
+	u8 sndbuf[wlen+4]; /* lead(1) devaddr,direction(1) addr(2) data(wlen) (len(2) (when reading)) */
+	/* write only ? */
+	int wo = (rbuf == NULL || rlen == 0),
+		len = 2 + wlen + (wo ? 0 : 2);
+
+	deb_alot("wo: %d, wlen: %d, len: %d\n",wo,wlen,len);
+
+	sndbuf[0] = wo ? DIBUSB_REQ_I2C_WRITE : DIBUSB_REQ_I2C_READ;
+	sndbuf[1] = (addr & 0xfe) | (wo ? 0 : 1);
+
+	memcpy(&sndbuf[2],wbuf,wlen);
+
+	if (!wo) {
+		sndbuf[wlen+2] = (rlen >> 8) & 0xff;
+		sndbuf[wlen+3] = rlen & 0xff;
+	}
+
+	return dibusb_readwrite_usb(dib,sndbuf,len,rbuf,rlen);
+}
+
+/*
+ * DVB stuff
+ */
+
+static struct dibusb_pid * dibusb_get_free_pid(struct usb_dibusb *dib)
+{
+	int i;
+	unsigned long flags;
+	struct dibusb_pid *dpid = NULL;
+
+	spin_lock_irqsave(&dib->pid_list_lock,flags);
+	for (i=0; i < DIBUSB_MAX_PIDS; i++)
+		if (!dib->pid_list[i].active) {
+			dpid = dib->pid_list + i;
+			dpid->active = 1;
+			break;
+		}
+	spin_unlock_irqrestore(&dib->pid_list_lock,flags);
+	return dpid;
+}
+
+static int dibusb_start_xfer(struct usb_dibusb *dib)
+{
+	u8 b[4] = {
+		(DIB3000MB_REG_FIFO >> 8) & 0xff,
+		(DIB3000MB_REG_FIFO) & 0xff,
+		(DIB3000MB_FIFO_ACTIVATE >> 8) & 0xff,
+		(DIB3000MB_FIFO_ACTIVATE) & 0xff
+	};
+	return dibusb_i2c_msg(dib,DIBUSB_DEMOD_I2C_ADDR_DEFAULT,b,4,NULL,0);
+}
+
+static int dibusb_stop_xfer(struct usb_dibusb *dib)
+{
+	u8 b[4] = {
+		(DIB3000MB_REG_FIFO >> 8) & 0xff,
+		(DIB3000MB_REG_FIFO) & 0xff,
+		(DIB3000MB_FIFO_INHIBIT >> 8) & 0xff,
+		(DIB3000MB_FIFO_INHIBIT) & 0xff
+	};
+	return dibusb_i2c_msg(dib,DIBUSB_DEMOD_I2C_ADDR_DEFAULT,b,4,NULL,0);
+}
+
+static int dibusb_set_pid(struct dibusb_pid *dpid)
+{
+	u16 pid = dpid->pid | (dpid->active ? DIB3000MB_ACTIVATE_FILTERING : 0);
+	u8 b[4] = {
+		(dpid->reg >> 8) & 0xff,
+		(dpid->reg) & 0xff,
+		(pid >> 8) & 0xff,
+		(pid) & 0xff
+	};
+
+	return dibusb_i2c_msg(dpid->dib,DIBUSB_DEMOD_I2C_ADDR_DEFAULT,b,4,NULL,0);
+}
+
+static void dibusb_urb_complete(struct urb *urb, struct pt_regs *ptregs)
+{
+	struct usb_dibusb *dib = urb->context;
+
+	if (!dib->streaming)
+		return;
+
+	if (urb->status == 0) {
+		deb_info("URB return len: %d\n",urb->actual_length);
+		if (urb->actual_length % 188)
+			deb_info("TS Packets: %d, %d\n", urb->actual_length/188,urb->actual_length % 188);
+		dvb_dmx_swfilter_packets(&dib->demux, (u8*) urb->transfer_buffer,urb->actual_length/188);
+	}
+
+	if (dib->streaming)
+		usb_submit_urb(urb,GFP_KERNEL);
+}
+
+
+static int dibusb_start_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+//	struct dvb_demux *dvbdmx = dvbdmxfeed->demux;
+	struct usb_dibusb *dib = dvbdmxfeed->demux->priv;
+	struct dibusb_pid *dpid;
+	int ret = 0;
+
+	deb_info("pid: 0x%04x, feedtype: %d\n", dvbdmxfeed->pid,dvbdmxfeed->type);
+
+	if ((dpid = dibusb_get_free_pid(dib)) == NULL) {
+		err("no free pid in list.");
+		return -ENODEV;
+	}
+	dvbdmxfeed->priv = dpid;
+	dpid->pid = dvbdmxfeed->pid;
+
+	dibusb_set_pid(dpid);
+
+	if (0 == dib->feed_count++) {
+		usb_fill_bulk_urb( dib->buf_urb, dib->udev, DATA_PIPE,
+			dib->buffer, 8192, dibusb_urb_complete, dib);
+		dib->buf_urb->transfer_flags = 0;
+
+
+		if ((ret = usb_submit_urb(dib->buf_urb,GFP_KERNEL))) {
+			dibusb_stop_xfer(dib);
+			err("could not submit buffer urb.");
+			return ret;
+		}
+
+		if ((ret = dibusb_start_xfer(dib)))
+			return ret;
+
+		dib->streaming = 1;
+	}
+	return 0;
+}
+
+static int dibusb_stop_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	struct usb_dibusb *dib = dvbdmxfeed->demux->priv;
+	struct dibusb_pid *dpid = (struct dibusb_pid *) dvbdmxfeed->priv;
+
+	deb_info("stopfeed pid: 0x%04x, feedtype: %d",dvbdmxfeed->pid, dvbdmxfeed->type);
+
+	if (dpid == NULL)
+		err("channel in dmxfeed->priv was NULL");
+	else {
+		dpid->active = 0;
+		dpid->pid = 0;
+		dibusb_set_pid(dpid);
+	}
+
+	if (--dib->feed_count == 0) {
+		dib->streaming = 0;
+		usb_unlink_urb(dib->buf_urb);
+		dibusb_stop_xfer(dib);
+	}
+	return 0;
+}
+
+/*
+ * firmware transfers
+ */
+
+/*
+ * do not use this, just a workaround for a bug,
+ * which will never occur :).
+ */
+static int dibusb_interrupt_read_loop(struct usb_dibusb *dib)
+{
+	u8 b[1] = { DIBUSB_REQ_INTR_READ };
+	return dibusb_write_usb(dib,b,1);
+}
+
+/*
+ * TODO: a tasklet should run with a delay of 1/10 second
+ * and fill an appropriate event device ?
+ */
+static int dibusb_read_remote_control(struct usb_dibusb *dib)
+{
+	u8 b[1] = { DIBUSB_REQ_POLL_REMOTE }, rb[5];
+	int ret;
+	if ((ret = dibusb_readwrite_usb(dib,b,1,rb,5)))
+		return ret;
+
+	return 0;
+}
+
+/*
+ * ioctl for the firmware
+ */
+static int dibusb_ioctl_cmd(struct usb_dibusb *dib, u8 cmd, u8 *param, int plen)
+{
+	u8 b[34];
+	int size = plen > 32 ? 32 : plen;
+	b[0] = DIBUSB_REQ_SET_IOCTL;
+	b[1] = cmd;
+	memcpy(&b[2],param,size);
+
+	return dibusb_write_usb(dib,b,2+size);
+}
+
+/*
+ * ioctl for power control
+ */
+static int dibusb_hw_sleep(struct usb_dibusb *dib)
+{
+	u8 b[1] = { DIBUSB_IOCTL_POWER_SLEEP };
+	return dibusb_ioctl_cmd(dib,DIBUSB_IOCTL_CMD_POWER_MODE, b,1);
+}
+
+static int dibusb_hw_wakeup(struct usb_dibusb *dib)
+{
+	u8 b[1] = { DIBUSB_IOCTL_POWER_WAKEUP };
+	return dibusb_ioctl_cmd(dib,DIBUSB_IOCTL_CMD_POWER_MODE, b,1);
+}
+
+/*
+ * I2C
+ */
+static int dibusb_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg msg[],int num)
+{
+	struct usb_dibusb *dib = i2c_get_adapdata(adap);
+	int i;
+
+	if (down_interruptible(&dib->i2c_sem) < 0)
+		return -EAGAIN;
+
+	for (i = 0; i < num; i++) {
+		/* write/read request */
+		if (i+1 < num && (msg[i+1].flags & I2C_M_RD)) {
+			if (dibusb_i2c_msg(dib, msg[i].addr, msg[i].buf,msg[i].len,
+						msg[i+1].buf,msg[i+1].len) < 0)
+				break;
+			i++;
+		} else
+			if (dibusb_i2c_msg(dib, msg[i].addr, msg[i].buf,msg[i].len,NULL,0) < 0)
+				break;
+	}
+
+	up(&dib->i2c_sem);
+	return i;
+}
+
+static u32 dibusb_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static int dibusb_i2c_client_register (struct i2c_client *i2c)
+{
+	struct usb_dibusb *dib = i2c_get_adapdata(i2c->adapter);
+	if (i2c->driver->command)
+		return i2c->driver->command(i2c,FE_REGISTER,dib->adapter);
+	return 0;
+}
+
+static int dibusb_i2c_client_unregister (struct i2c_client *i2c)
+{
+	struct usb_dibusb *dib = i2c_get_adapdata(i2c->adapter);
+	if (i2c->driver->command)
+		return i2c->driver->command(i2c,FE_UNREGISTER,dib->adapter);
+	return 0;
+}
+
+static struct i2c_algorithm dibusb_algo = {
+	.name			= "DiBcom USB i2c algorithm",
+	.id				= I2C_ALGO_BIT,
+	.master_xfer	= dibusb_i2c_xfer,
+	.functionality	= dibusb_i2c_func,
+};
+
+static int dibusb_dvb_init(struct usb_dibusb *dib)
+{
+	int ret;
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,4)
+    if ((ret = dvb_register_adapter(&dib->adapter, DRIVER_DESC)) < 0) {
+#else
+    if ((ret = dvb_register_adapter(&dib->adapter, DRIVER_DESC ,
+			THIS_MODULE)) < 0) {
+#endif
+		deb_info("dvb_register_adapter failed: error %d", ret);
+		goto err;
+	}
+
+	strncpy(dib->i2c_adap.name,dib->dibdev->name,I2C_NAME_SIZE);
+#ifdef I2C_ADAP_CLASS_TV_DIGITAL
+	dib->i2c_adap.class = I2C_ADAP_CLASS_TV_DIGITAL,
+#else
+	dib->i2c_adap.class = I2C_CLASS_TV_DIGITAL,
+#endif
+	dib->i2c_adap.algo 		= &dibusb_algo;
+	dib->i2c_adap.algo_data = NULL;
+	dib->i2c_adap.id		= I2C_ALGO_BIT;
+	dib->i2c_adap.client_register   = dibusb_i2c_client_register,
+	dib->i2c_adap.client_unregister = dibusb_i2c_client_unregister,
+
+	i2c_set_adapdata(&dib->i2c_adap, dib);
+
+	if ((i2c_add_adapter(&dib->i2c_adap) < 0)) {
+		err("could not add i2c adapter");
+		goto err_i2c;
+	}
+
+	dib->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
+
+	dib->demux.priv = (void *)dib;
+	dib->demux.filternum = DIBUSB_MAX_PIDS;
+	dib->demux.feednum = DIBUSB_MAX_PIDS;
+	dib->demux.start_feed = dibusb_start_feed;
+	dib->demux.stop_feed = dibusb_stop_feed;
+	dib->demux.write_to_decoder = NULL;
+	if ((ret = dvb_dmx_init(&dib->demux)) < 0) {
+		err("dvb_dmx_init failed: error %d",ret);
+		goto err_dmx;
+	}
+
+	dib->dmxdev.filternum = dib->demux.filternum;
+	dib->dmxdev.demux = &dib->demux.dmx;
+	dib->dmxdev.capabilities = 0;
+	if ((ret = dvb_dmxdev_init(&dib->dmxdev, dib->adapter)) < 0) {
+		err("dvb_dmxdev_init failed: error %d",ret);
+		goto err_dmx_dev;
+	}
+
+	dvb_net_init(dib->adapter, &dib->dvb_net, &dib->demux.dmx);
+
+	goto success;
+err_dmx_dev:
+	dvb_dmx_release(&dib->demux);
+err_dmx:
+	i2c_del_adapter(&dib->i2c_adap);
+err_i2c:
+	dvb_unregister_adapter(dib->adapter);
+err:
+	return ret;
+success:
+	return 0;
+}
+
+static int dibusb_dvb_exit(struct usb_dibusb *dib)
+{
+	deb_info("unregistering DVB part\n");
+	dvb_net_release(&dib->dvb_net);
+	dib->demux.dmx.close(&dib->demux.dmx);
+	dvb_dmxdev_release(&dib->dmxdev);
+	dvb_dmx_release(&dib->demux);
+	i2c_del_adapter(&dib->i2c_adap);
+	dvb_unregister_adapter(dib->adapter);
+
+	return 0;
+}
+
+static int dibusb_exit(struct usb_dibusb *dib)
+{
+	usb_free_urb(dib->buf_urb);
+	pci_free_consistent(NULL,8192,dib->buffer,dib->dma_handle);
+	return 0;
+}
+
+static int dibusb_init(struct usb_dibusb *dib)
+{
+	int ret,i;
+	sema_init(&dib->usb_sem, 1);
+	sema_init(&dib->i2c_sem, 1);
+
+	/*
+	 * when reloading the driver w/o replugging the device
+	 * a timeout occures, this helps
+	 */
+	usb_clear_halt(dib->udev,COMMAND_PIPE);
+	usb_clear_halt(dib->udev,RESULT_PIPE);
+	usb_clear_halt(dib->udev,DATA_PIPE);
+
+	/* dibusb_reset_cpu(dib); */
+
+	dib->buffer = pci_alloc_consistent(NULL,8192, &dib->dma_handle);
+	memset(dib->buffer,0,8192);
+	if (!(dib->buf_urb = usb_alloc_urb(0,GFP_KERNEL))) {
+		dibusb_exit(dib);
+		return -ENOMEM;
+	}
+
+	for (i=0; i < DIBUSB_MAX_PIDS; i++) {
+		dib->pid_list[i].reg = i+DIB3000MB_REG_FIRST_PID;
+		dib->pid_list[i].pid = 0;
+		dib->pid_list[i].active = 0;
+		dib->pid_list[i].dib = dib;
+	}
+
+	dib->streaming = 0;
+	dib->feed_count = 0;
+
+	if ((ret = dibusb_dvb_init(dib))) {
+		dibusb_exit(dib);
+		return ret;
+	}
+	return 0;
+}
+
+/*
+ * load a firmware packet to the device
+ */
+static int dibusb_writemem(struct usb_device *udev,u16 addr,u8 *data, u8 len)
+{
+	return usb_control_msg(udev, usb_sndctrlpipe(udev,0),
+			0xa0, USB_TYPE_VENDOR, addr, 0x00, data, len, 5*HZ);
+}
+
+static int dibusb_loadfirmware(struct usb_device *udev,
+		struct dibusb_device *dibdev)
+{
+	const struct firmware *fw = NULL;
+	u16 addr;
+	u8 *b,*p;
+	int ret = 0,i;
+
+	for (i = 0; i < sizeof(valid_firmware_filenames)/sizeof(const char*); i++) {
+		if ((ret = request_firmware(&fw, valid_firmware_filenames[i], &udev->dev)) == 0) {
+			info("using firmware file (%s).",valid_firmware_filenames[i]);
+			break;
+		}
+		deb_info("tried to find '%s' firmware - unsuccessful. (%d)\n",
+				valid_firmware_filenames[i],ret);
+	}
+
+	if (fw == NULL) {
+		err("did not find a valid firmware file. "
+			"Please see linux/Documentation/dvb/ for more details on firmware-problems.");
+		return -EINVAL;
+	}
+	p = kmalloc(fw->size,GFP_KERNEL);
+	if (p != NULL) {
+		u8 reset;
+		/*
+		 * you cannot use the fw->data as buffer for
+		 * usb_control_msg, a new buffer has to be
+		 * created
+		 */
+		memcpy(p,fw->data,fw->size);
+
+		/* stop the CPU */
+		reset = 1;
+		if ((ret = dibusb_writemem(udev,DIBUSB_CPU_CSREG,&reset,1)) != 1)
+			err("could not stop the USB controller CPU.");
+		for(i = 0; p[i+3] == 0 && i < fw->size; ) {
+			b = (u8 *) &p[i];
+			addr = *((u16 *) &b[1]);
+
+			ret = dibusb_writemem(udev,addr,&b[4],b[0]);
+
+			if (ret != b[0]) {
+				err("error while transferring firmware "
+					"(transferred size: %d, block size: %d)",
+					ret,b[1]);
+				ret = -EINVAL;
+				break;
+			}
+			i += 5 + b[0];
+		}
+		/* restart the CPU */
+		reset = 0;
+		if ((ret = dibusb_writemem(udev,DIBUSB_CPU_CSREG,&reset,1)) != 1)
+			err("could not restart the USB controller CPU.");
+
+		kfree(p);
+		ret = 0;
+	} else {
+		ret = -ENOMEM;
+	}
+	release_firmware(fw);
+
+	return ret;
+}
+
+/*
+ * USB
+ */
+static int dibusb_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	struct usb_device *udev = interface_to_usbdev(intf);
+	struct usb_dibusb *dib = NULL;
+	struct dibusb_device *dibdev = NULL;
+
+	int ret = -ENOMEM,i,cold=0;
+
+	for (i = 0; i < DIBUSB_SUPPORTED_DEVICES; i++)
+		if (dibusb_devices[i].cold_product_id == udev->descriptor.idProduct ||
+			dibusb_devices[i].warm_product_id == udev->descriptor.idProduct) {
+			dibdev = &dibusb_devices[i];
+
+			cold = dibdev->cold_product_id == udev->descriptor.idProduct;
+
+			if (cold)
+				info("found a '%s' in cold state, will try to load a firmware",dibdev->name);
+			else
+				info("found a '%s' in warm state.",dibdev->name);
+		}
+
+	if (dibdev == NULL) {
+		err("something went very wrong, "
+				"unknown product ID: %.4x",udev->descriptor.idProduct);
+		return -ENODEV;
+	}
+
+	if (cold)
+		ret = dibusb_loadfirmware(udev,dibdev);
+	else {
+		dib = kmalloc(sizeof(struct usb_dibusb),GFP_KERNEL);
+		if (dib == NULL) {
+			err("no memory");
+			return ret;
+		}
+		memset(dib,0,sizeof(struct usb_dibusb));
+
+		dib->udev = udev;
+		dib->dibdev = dibdev;
+
+		usb_set_intfdata(intf, dib);
+
+		ret = dibusb_init(dib);
+	}
+
+	if (ret == 0)
+		info("%s successfully initialized and connected.",dibdev->name);
+	else
+		info("%s error while loading driver (%d)",dibdev->name,ret);
+	return ret;
+}
+
+static void dibusb_disconnect(struct usb_interface *intf)
+{
+	struct usb_dibusb *dib = usb_get_intfdata(intf);
+	const char *name = DRIVER_DESC;
+
+	usb_set_intfdata(intf,NULL);
+	if (dib != NULL) {
+		name = dib->dibdev->name;
+		dibusb_dvb_exit(dib);
+		dibusb_exit(dib);
+		kfree(dib);
+	}
+	info("%s successfully deinitialized and disconnected.",name);
+
+}
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver dibusb_driver = {
+	.owner		= THIS_MODULE,
+	.name		= "dvb_dibusb",
+	.probe 		= dibusb_probe,
+	.disconnect = dibusb_disconnect,
+	.id_table 	= dibusb_table,
+};
+
+/* module stuff */
+static int __init usb_dibusb_init(void)
+{
+	int result;
+	if ((result = usb_register(&dibusb_driver))) {
+		err("usb_register failed. Error number %d",result);
+		return result;
+	}
+
+	return 0;
+}
+
+static void __exit usb_dibusb_exit(void)
+{
+	/* deregister this driver from the USB subsystem */
+	usb_deregister(&dibusb_driver);
+}
+
+module_init (usb_dibusb_init);
+module_exit (usb_dibusb_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff -puN /dev/null drivers/media/dvb/dibusb/dvb-dibusb.h
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ 25-akpm/drivers/media/dvb/dibusb/dvb-dibusb.h	2004-09-20 11:22:37.731914840 -0700
@@ -0,0 +1,175 @@
+/*
+ * dvb-dibusb.h
+ *
+ * Copyright (C) 2004 Patrick Boettcher (patrick.boettcher@desy.de)
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License as
+ *	published by the Free Software Foundation, version 2.
+ *
+ *
+ * for more information see dvb-dibusb.c .
+ */
+
+#ifndef __DVB_DIBUSB_H__
+#define __DVB_DIBUSB_H__
+
+/* Vendor IDs */
+#define USB_TWINHAN_VENDOR_ID			0x1822
+#define USB_IMC_NETWORKS_VENDOR_ID		0x13d3
+#define USB_KWORLD_VENDOR_ID			0xeb1a
+#define USB_DIBCOM_VENDOR_ID			0x10b8
+#define USB_ULTIMA_ELECTRONIC_ID		0x05d8
+
+/* Product IDs before loading the firmware */
+#define USB_VP7041_PRODUCT_PREFW_ID		0x3201
+#define USB_VSTREAM_PRODUCT_PREFW_ID	0x17de
+#define USB_DIBCOM_PRODUCT_PREFW_ID		0x0bb8
+#define USB_ULTIMA_ELEC_PROD_PREFW_ID	0x8105
+
+/* product ID afterwards */
+#define USB_VP7041_PRODUCT_ID			0x3202
+#define USB_VSTREAM_PRODUCT_ID			0x17df
+#define USB_DIBCOM_PRODUCT_ID			0x0bb9
+#define USB_ULTIMA_ELEC_PROD_ID			0x8106
+
+/* CS register start/stop the usb controller cpu */
+#define DIBUSB_CPU_CSREG				0x7F92
+
+// 0x10 is the I2C address of the first demodulator on the board
+#define DIBUSB_DEMOD_I2C_ADDR_DEFAULT	0x10
+#define DIBUSB_I2C_TIMEOUT 				HZ*5
+
+#define DIBUSB_MAX_PIDS					16
+
+#define DIB3000MB_REG_FIRST_PID			(   153)
+
+struct usb_dibusb;
+
+struct dibusb_pid {
+	u16 reg;
+	u16 pid;
+	int active;
+	struct usb_dibusb *dib;
+};
+
+struct usb_dibusb {
+	/* usb */
+	struct usb_device * udev;
+
+	struct dibusb_device * dibdev;
+
+	int streaming;
+	int feed_count;
+	struct urb *buf_urb;
+	u8 *buffer;
+	dma_addr_t dma_handle;
+
+	spinlock_t pid_list_lock;
+	struct dibusb_pid pid_list[DIBUSB_MAX_PIDS];
+
+	/* I2C */
+	struct i2c_adapter i2c_adap;
+	struct i2c_client i2c_client;
+
+	/* locking */
+	struct semaphore usb_sem;
+	struct semaphore i2c_sem;
+
+	/* dvb */
+	struct dvb_adapter *adapter;
+	struct dmxdev dmxdev;
+	struct dvb_demux demux;
+	struct dvb_net dvb_net;
+};
+
+
+struct dibusb_device {
+	u16 cold_product_id;
+	u16 warm_product_id;
+	u8 demod_addr;
+	const char *name;
+};
+
+/* static array of valid firmware names, the best one first */
+static const char * valid_firmware_filenames[] = {
+	"dvb-dibusb-5.0.0.11.fw",
+};
+
+#define DIBUSB_SUPPORTED_DEVICES	4
+
+/* USB Driver stuff */
+static struct dibusb_device dibusb_devices[DIBUSB_SUPPORTED_DEVICES] = {
+	{	.cold_product_id = USB_VP7041_PRODUCT_PREFW_ID,
+		.warm_product_id = USB_VP7041_PRODUCT_ID,
+		.name = "Twinhan VisionDTV USB-Ter/HAMA USB DVB-T device",
+		.demod_addr = DIBUSB_DEMOD_I2C_ADDR_DEFAULT,
+	},
+	{	.cold_product_id = USB_VSTREAM_PRODUCT_PREFW_ID,
+		.warm_product_id = USB_VSTREAM_PRODUCT_ID,
+		.name = "KWorld V-Stream XPERT DTV - DVB-T USB",
+		.demod_addr = DIBUSB_DEMOD_I2C_ADDR_DEFAULT,
+	},
+	{	.cold_product_id = USB_DIBCOM_PRODUCT_PREFW_ID,
+		.warm_product_id = USB_DIBCOM_PRODUCT_ID,
+		.name = "DiBcom USB reference design",
+		.demod_addr = DIBUSB_DEMOD_I2C_ADDR_DEFAULT,
+	},
+	{
+ 		.cold_product_id = USB_ULTIMA_ELEC_PROD_PREFW_ID,
+		.warm_product_id = USB_ULTIMA_ELEC_PROD_ID,
+		.name = "Ultima Electronic/Artec T1 USB TVBOX",
+		.demod_addr = DIBUSB_DEMOD_I2C_ADDR_DEFAULT,
+	},
+};
+
+#define COMMAND_PIPE	usb_sndbulkpipe(dib->udev, 0x01)
+#define RESULT_PIPE		usb_rcvbulkpipe(dib->udev, 0x81)
+#define DATA_PIPE		usb_rcvbulkpipe(dib->udev, 0x82)
+/*
+ * last endpoint 0x83 only used for chaining the buffers
+ * of the endpoints in the cypress
+ */
+#define CHAIN_PIPE_DO_NOT_USE	usb_rcvbulkpipe(dib->udev, 0x83)
+
+/* types of first byte of each buffer */
+
+#define DIBUSB_REQ_START_READ			0x00
+#define DIBUSB_REQ_START_DEMOD			0x01
+#define DIBUSB_REQ_I2C_READ  			0x02
+#define DIBUSB_REQ_I2C_WRITE 			0x03
+
+/* prefix for reading the current RC key */
+#define DIBUSB_REQ_POLL_REMOTE			0x04
+
+/* 0x05 0xXX */
+#define DIBUSB_REQ_SET_STREAMING_MODE	0x05
+
+/* interrupt the internal read loop, when blocking */
+#define DIBUSB_REQ_INTR_READ		   	0x06
+
+/* IO control
+ * 0x07 <cmd 1 byte> <param 32 bytes>
+ */
+#define DIBUSB_REQ_SET_IOCTL			0x07
+
+/* IOCTL commands */
+
+/* change the power mode in firmware */
+#define DIBUSB_IOCTL_CMD_POWER_MODE		0x00
+#define DIBUSB_IOCTL_POWER_SLEEP			0x00
+#define DIBUSB_IOCTL_POWER_WAKEUP			0x01
+
+
+/*
+ * values from the demodulator which are needed in
+ * the usb driver as well
+ */
+
+#define DIB3000MB_REG_FIFO              (   145)
+#define DIB3000MB_FIFO_INHIBIT              (     1)
+#define DIB3000MB_FIFO_ACTIVATE             (     0)
+
+#define DIB3000MB_ACTIVATE_FILTERING            (0x2000)
+
+#endif
diff -puN /dev/null drivers/media/dvb/dibusb/Kconfig
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ 25-akpm/drivers/media/dvb/dibusb/Kconfig	2004-09-20 11:22:37.731914840 -0700
@@ -0,0 +1,31 @@
+config DVB_DIBUSB
+	tristate "Twinhan/KWorld/Hama/Artec USB DVB-T devices"
+	depends on DVB_CORE && USB
+	select FW_LOADER
+	help
+	  Support for USB 1.1 DVB-T devices based on a reference design made by
+	  DiBcom (http://www.dibcom.fr).
+
+	  Devices supported by this driver:
+
+	    Twinhan VisionPlus VisionDTV USB-Ter (VP7041)
+	    KWorld V-Stream XPERT DTV - DVB-T USB
+	    Hama DVB-T USB-Box
+	    DiBcom reference device (non-public)
+	    Ultima Electronic/Artec T1 USB TVBOX
+
+	  The VP7041 seems to be identical to "CTS Portable" (Chinese
+	  Television System).
+
+	  These devices can be understood as budget ones, they "only" deliver
+	  the MPEG data.
+
+	  Currently all known copies of the DiBcom reference design have the DiBcom 3000MB
+	  frontend onboard. Please enable and load this one manually in order to use this
+	  device.
+
+	  A firmware is needed to use the device. See Documentation/dvb/README.dibusb
+	  details.
+
+	  Say Y if you own such a device and want to use it. You should build it as
+	  a module.
diff -puN /dev/null drivers/media/dvb/dibusb/Makefile
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ 25-akpm/drivers/media/dvb/dibusb/Makefile	2004-09-20 11:22:37.731914840 -0700
@@ -0,0 +1,3 @@
+obj-$(CONFIG_DVB_DIBUSB) += dvb-dibusb.o
+
+EXTRA_CFLAGS = -Idrivers/media/dvb/dvb-core/
diff -puN drivers/media/dvb/frontends/Kconfig~DVB-new-driver-dibusb drivers/media/dvb/frontends/Kconfig
--- 25/drivers/media/dvb/frontends/Kconfig~DVB-new-driver-dibusb	2004-09-20 11:22:37.712917728 -0700
+++ 25-akpm/drivers/media/dvb/frontends/Kconfig	2004-09-20 11:22:37.733914536 -0700
@@ -63,13 +63,9 @@ config DVB_SP887X
 	help
  	  A DVB-T tuner module. Say Y when you want to support this frontend.
 
-	  This driver needs a copy of the Avermedia firmware. The version tested
-	  is part of the Avermedia DVB-T 1.3.26.3 Application. If the software is
-	  installed in Windoze the file will be in the /Program Files/AVerTV DVB-T/
-	  directory and is called sc_main.mc. Alternatively it can "extracted" from
-	  the install cab files.
-
-   	  Copy this file to '/usr/lib/hotplug/firmware/dvb-fe-sp887x.fw'.
+	  This driver needs external firmware. Please use the command
+	  "<kerneldir>/Documentation/dvb/get_dvb_firmware sp887x" to
+	  download/extract it, and then copy it to /usr/lib/hotplug/firmware.
 
 	  If you don't know what tuner module is soldered on your 
 	  DVB adapter simply enable all supported frontends, the 
@@ -81,9 +77,9 @@ config DVB_ALPS_TDLB7
 	help
 	  A DVB-T tuner module. Say Y when you want to support this frontend.
 
-	  This driver needs a copy of the firmware file from the Haupauge
-	  Windoze driver. Copy 'Sc_main.mc' to
-	  '/usr/lib/hotplug/firmware/dvb-fe-tdlb7.fw'.
+	  This driver needs external firmware. Please use the command
+	  "<kerneldir>/Documentation/dvb/get_dvb_firmware alps_tdlb7" to
+	  download/extract it, and then copy it to /usr/lib/hotplug/firmware.
 
 	  If you don't know what tuner module is soldered on your 
 	  DVB adapter simply enable all supported frontends, the 
@@ -99,6 +95,16 @@ config DVB_ALPS_TDMB7
 	  DVB adapter simply enable all supported frontends, the 
 	  right one will get autodetected.
 
+config DVB_CX22702
+ 	tristate "Conexant cx22702 demodulator (OFDM)"
+ 	depends on DVB_CORE
+ 	help
+ 	  A DVB-T tuner module. Say Y when you want to support this frontend.
+
+ 	  If you don't know what tuner module is soldered on your
+ 	  DVB adapter simply enable all supported frontends, the
+ 	  right one will get autodetected.
+
 config DVB_GRUNDIG_29504_401
 	tristate "Grundig 29504-401 based"
 	depends on DVB_CORE
@@ -115,6 +121,11 @@ config DVB_TDA1004X
 	help
 	  A DVB-T tuner module. Say Y when you want to support this frontend.
 
+	  This driver needs external firmware. Please use the commands
+	  "<kerneldir>/Documentation/dvb/get_dvb_firmware tda10045",
+  	  "<kerneldir>/Documentation/dvb/get_dvb_firmware tda10046" to
+	  download/extract them, and then copy them to /usr/lib/hotplug/firmware.
+
 	  If you don't know what tuner module is soldered on your 
 	  DVB adapter simply enable all supported frontends, the 
 	  right one will get autodetected.
@@ -139,6 +150,21 @@ config DVB_MT352
 	  DVB adapter simply enable all supported frontends, the
 	  right one will get autodetected.
 
+config DVB_DIB3000MB
+	tristate "DiBcom 3000-MB"
+	depends on DVB_CORE
+	help
+	  A DVB-T tuner module. Designed for mobile usage. Say Y when you want
+	  to support this frontend.
+
+	  Used on USB-powered devices. You should also say Y to DVB_DIBUSB
+	  (DiBcom USB DVB-T Adapter) to support the actual device,
+	  this is "only" the frontend/tuner.
+
+	  If you don't know what tuner module is soldered on your
+	  DVB adapter simply enable all supported frontends, the
+	  right one will get autodetected.
+
 comment "DVB-C (cable) frontends"
 	depends on DVB_CORE
 
diff -puN drivers/media/dvb/Makefile~DVB-new-driver-dibusb drivers/media/dvb/Makefile
--- 25/drivers/media/dvb/Makefile~DVB-new-driver-dibusb	2004-09-20 11:22:37.724915904 -0700
+++ 25-akpm/drivers/media/dvb/Makefile	2004-09-20 11:22:37.732914688 -0700
@@ -2,5 +2,4 @@
 # Makefile for the kernel multimedia device drivers.
 #
 
-obj-y        := dvb-core/ frontends/ ttpci/ ttusb-dec/ ttusb-budget/ b2c2/ bt8xx/
-
+obj-y        := dvb-core/ frontends/ ttpci/ ttusb-dec/ ttusb-budget/ b2c2/ bt8xx/ dibusb/
_