summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Boettcher <devnull@localhost>2004-07-20 20:52:19 +0000
committerPatrick Boettcher <devnull@localhost>2004-07-20 20:52:19 +0000
commit579832ccff77ff3322bd06fd73256614896e429b (patch)
treecf40f64c2219719378efadcb7ea98266b5c77c64
parentc103d051629c32e975aad5fca2d5e07ab44236cb (diff)
downloadmediapointer-dvb-s2-579832ccff77ff3322bd06fd73256614896e429b.tar.gz
mediapointer-dvb-s2-579832ccff77ff3322bd06fd73256614896e429b.tar.bz2
added new driver for the USB budget devices:
- Twinhan VisionPlus VisionDTV USB-Ter DVB-T Device - CTS Portable documentation, helper scripts and modifications in the necessary files have been done, hopefully.
-rw-r--r--linux/Documentation/dvb/firmware.txt1
-rw-r--r--linux/Documentation/dvb/readme.txt4
-rw-r--r--linux/Documentation/dvb/vp7041.txt148
-rw-r--r--linux/drivers/media/dvb/Kconfig1
-rw-r--r--linux/drivers/media/dvb/Makefile2
-rw-r--r--linux/drivers/media/dvb/vp7041/Kconfig19
-rw-r--r--linux/drivers/media/dvb/vp7041/Makefile3
-rw-r--r--linux/drivers/media/dvb/vp7041/vp7041.c1022
8 files changed, 1199 insertions, 1 deletions
diff --git a/linux/Documentation/dvb/firmware.txt b/linux/Documentation/dvb/firmware.txt
index 2ea97087a..d97852d32 100644
--- a/linux/Documentation/dvb/firmware.txt
+++ b/linux/Documentation/dvb/firmware.txt
@@ -23,6 +23,7 @@ Proprietary solutions which need to be converted:
"mcfile" module parameter; the binary must be
extracted from the Windows driver (Sc_main.mc).
- ttusb-dec: see "ttusb-dec.txt" for details
+- vp7041: see vp7041.txt for more information
0) Getting a usable firmware file
diff --git a/linux/Documentation/dvb/readme.txt b/linux/Documentation/dvb/readme.txt
index 720aa8ce0..e38016cf8 100644
--- a/linux/Documentation/dvb/readme.txt
+++ b/linux/Documentation/dvb/readme.txt
@@ -41,4 +41,8 @@ contains detailed installation instructions for the
various bt8xx based "budget" DVB cards
(Nebula, Pinnacle PCTV, Twinhan DST)
+"vp7041.txt"
+contains detailed informations about the
+Visionplus VisionDTV USB-Ter DVB-T adapter.
+
Good luck and have fun!
diff --git a/linux/Documentation/dvb/vp7041.txt b/linux/Documentation/dvb/vp7041.txt
new file mode 100644
index 000000000..fe8ddae14
--- /dev/null
+++ b/linux/Documentation/dvb/vp7041.txt
@@ -0,0 +1,148 @@
+Linux Driver for
+ VisionPlus VisionDTV USB-Ter DVB-T Device (VP7041)
+ (http://www.twinhan.com/visiontv-2_4.htm)
+and
+ CTS Portable (Chinese Television System)
+ (http://www.2cts.tv/ctsportable/)
+
+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.
+
+Koenigs Wusterhausen, Germany
+
+NEWS:
+ 2004-07-15 - found out, by accident, that the device has a TUA6010XS for
+ frequency generator
+ 2004-07-12 - figured out, that the driver should also work with the
+ CTS Portable (Chinese Television System)
+ 2004-07-08 - firmware-extraction-2.422-problem solved, driver is now working
+ properly with firmware extracted from 2.422
+ - #if for 2.6.4 (dvb), compile issue
+ - changed firmware handling, see vp7041.txt sec 1.1
+ 2004-07-02 - some tuner modifications, v0.1, cleanups, first public
+ 2004-06-28 - now using the dvb_dmx_swfilter_packets, everything
+ runs fine now
+ 2004-06-27 - able to watch and switching channels (pre-alpha)
+ - no section filtering yet
+ 2004-06-06 - first TS received, but kernel oops :/
+ 2004-05-14 - firmware loader is working
+ 2004-05-11 - start writing the driver
+
+0. About the driver.
+This driver is completly reverse-engineered by using the device on a guest PC
+with Windows (where a working driver exists).
+If you have a look into the source code, you will find a lot of buffers with
+a lot of hex-numbers in it. By all means, I will not assert, that I understand
+each of them. But this is like the windows driver controls the device, and
+obviously it is working even in Linux.
+
+If someone of you recognizes something in those buffers (maybe there is
+a frontend or any other device, which are fed like this) please let me know.
+If I could assign a function name to any of those anonymous numbers, I will
+be very happy.
+
+By all means, I will highly appreciate any comment, critic and patch to improve
+the quality of this piece of software.
+
+When using this driver your system can crash or hang. I'm not responsibly for
+any data you may lose. So be warned. (Enabling sysrq saves a lot of data).
+
+Addition: This driver should also work with the CTS Portable since the
+windriver seems to be identical to the Twinhan one. Comment on this badly
+needed.
+
+1. How to use?
+NOTE: This driver was developed on Linux 2.6.6., it is working with 2.6.7.
+Linux 2.4.x support is not planned, but patches are very welcome.
+
+NOTE: I'm using Debian testing, so the following explaination (especially
+the hotplug-path) needn't match your system, but probably it will :).
+
+1.1. Firmware extraction
+
+First of all you have to extract the firmware from the windows driver:
+
+Therefore "Peter Schildmann" originally wrote a shell script, I adopted
+it to a Perl script for easier supporting different firmware version.
+The firmware extract script can be found inside the dvb-kernel-cvs
+(script/vp7041_extract_firmware.pl).
+
+If you want to extract the firmware, you have to run the script like this:
+
+perl vp7041_extract_firmware.pl <driverfile> <fw-destination-path>
+
+The driverfile can be found in the windows driver package. As of version
+2.42 the windows driver the file is called "UDTTload.sys", older (but
+working) versions have "twinload.sys" respectively . (On a
+windows system, where the driver is in use, it can be found in
+\PathtoWindows\System32\drivers)
+The name of the firmware-file for linux has to be "dvb-vp7041-<ver>.fw"
+(the script cares about).
+The firmware has to be put into /usr/lib/hotplug/firmware (As I said
+Debian).
+
+If you figured out the driver file extract the firmware:
+
+perl vp7041_extract_firmware.pl /path/to/UDTTload.sys \
+ /usr/lib/hotplug/firmware
+
+1.2. Compiling
+
+Since the driver is in the linux kernel, activating the driver in
+your favorite config-environment should sufficient. I recommend
+to compile the driver as module. Hotplug does the rest.
+
+1.3. Loading the driver
+
+Hotplug is able to load the driver, when it is needed (because you plugged
+in the device).
+
+If you want to enable debug output, you have to load the driver manually.
+
+modprobe vp7041 debug=1
+
+should do the trick.
+
+When the driver is loaded successfully, the firmware file was in
+the right place and the device is connected, the "Power"-LED should be
+turned on.
+
+If this is done, even in the non-debug mode, dmesg should show
+something like the following:
+
+DVB: registering new adapter (VisionPlus VisionDTV USB-Ter DVB-T Device).
+DVB: registering frontend 0:0 (VisionPlus VisionDTV USB-Ter (VP7041) Frontend)...
+VisionPlus VisionDTV USB-Ter DVB-T Device successfully initialized and connected.
+
+At this point you are able to start a dvb-capable application. For myself
+I used mplayer, dvbscan, tzap and kaxtv, they are working. Using the device
+as a slave device in vdr, was not working for me. Some work has to be done
+(patches and comments are very welcome).
+
+2. Known problems and bugs
+
+see vp7041.c
+
+2.1 TODO
+see vp7041.c
+
+again, patches and comments are very very welcome
+
+3. Acknowledgements
+ Alex Woods for frequently answering question about usb and dvb
+ stuff, a big thank you
+
+ Bernd Wagner for helping with huge bug reports and discussions.
+
+ Some guys on the linux-dvb mailing list for encouraging me
+
+ Peter Schildmann >peter.schildmann-nospam-at-web.de< for his
+ user-level firmware loader, which saves a lot of time
+
+ Ulf Hermenau for helping me out with traditional chinese.
+
+ André Smoktun and Christian Frömmel for supporting me with
+ hardware and listening to my problems very patient
diff --git a/linux/drivers/media/dvb/Kconfig b/linux/drivers/media/dvb/Kconfig
index d9b9f2de1..9d9ea6b60 100644
--- a/linux/drivers/media/dvb/Kconfig
+++ b/linux/drivers/media/dvb/Kconfig
@@ -31,6 +31,7 @@ comment "Supported USB Adapters"
depends on DVB_CORE && USB
source "drivers/media/dvb/ttusb-budget/Kconfig"
source "drivers/media/dvb/ttusb-dec/Kconfig"
+source "drivers/media/dvb/vp7041/Kconfig"
comment "Supported FlexCopII (B2C2) Adapters"
depends on DVB_CORE && PCI
diff --git a/linux/drivers/media/dvb/Makefile b/linux/drivers/media/dvb/Makefile
index 1c53bc0a1..ec580fb15 100644
--- a/linux/drivers/media/dvb/Makefile
+++ b/linux/drivers/media/dvb/Makefile
@@ -2,4 +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/ vp7041/
diff --git a/linux/drivers/media/dvb/vp7041/Kconfig b/linux/drivers/media/dvb/vp7041/Kconfig
new file mode 100644
index 000000000..c8a78976d
--- /dev/null
+++ b/linux/drivers/media/dvb/vp7041/Kconfig
@@ -0,0 +1,19 @@
+config DVB_VP7041
+ tristate "Twinhan Visionplus USB-Ter DVB-T/CTS Portable"
+ depends on DVB_CORE && USB
+ select FW_LOADER
+ help
+ Support for the external USB adapter made by Twinhan (and maybe others)
+ which is called "Twinhan Visionplus VisionDTV USB-Ter" and seems to
+ be identical to "CTS Portable" (Chinese Television System).
+
+ These devices can be understood as budget ones, they only deliver
+ the MPEG data.
+
+ A firmware is needed to use the device. This has to be
+ extracted from the windows driver file and moved into the correct
+ hotplug-firmware path. More information on how to extract the firmware
+ can be found in Documentation/dvb/vp7041.txt .
+
+ Say Y if you own such a device and want to use it. You should build it as
+ a module.
diff --git a/linux/drivers/media/dvb/vp7041/Makefile b/linux/drivers/media/dvb/vp7041/Makefile
new file mode 100644
index 000000000..39c0dd9a4
--- /dev/null
+++ b/linux/drivers/media/dvb/vp7041/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_DVB_VP7041) += vp7041.o
+
+EXTRA_CFLAGS = -Idrivers/media/dvb/dvb-core/
diff --git a/linux/drivers/media/dvb/vp7041/vp7041.c b/linux/drivers/media/dvb/vp7041/vp7041.c
new file mode 100644
index 000000000..e3d530faa
--- /dev/null
+++ b/linux/drivers/media/dvb/vp7041/vp7041.c
@@ -0,0 +1,1022 @@
+/*
+ * Driver for
+ *
+ * Twinhan VisionPlus VisionDTV USB-Ter DVB-T Device (VP7041)
+ * CTS Portable (Chinese Television System)
+ *
+ * http://www.twinhan.com/visiontv-2_4.htm
+ * http://www.2cts.tv/ctsportable/
+ *
+ * This driver should also work with the CTS Portable since the
+ * windriver seems to be identical to the Twinhan one.
+ *
+ * vp7041.c
+ *
+ * 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.
+ *
+ * Acknowledgements
+ * Alex Woods for frequently answering question about usb and dvb
+ * stuff
+ *
+ * Some guys on the linux-dvb mailing list for encouraging me
+ *
+ * Peter Schildmann >peter.schildmann-nospam-at-web.de< for his
+ * user-level firmware loader, which saves a lot of time
+ *
+ * Ulf Hermenau for helping me out with traditional chinese.
+ *
+ * André Smoktun and Christian Frömmel
+ *
+ * Problem:
+ * - when tzap is running and you replug the device, a deadlock occures
+ * - disconnecting the device during mplayer is running brings a big oops
+ * somehow a clean usb exit has to be done
+ *
+ * TODO:
+ * - handling of USB disconnects (Oops when unplugged)
+ * - check init end deinit methods
+ * - different processor types (delays?)
+ * - big/litte endian ?
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/usb.h>
+#include <linux/firmware.h>
+#include <linux/pci.h>
+#include <linux/version.h>
+#include <linux/moduleparam.h>
+
+#include "dmxdev.h"
+#include "dvb_demux.h"
+#include "dvb_i2c.h"
+#include "dvb_filter.h"
+#include "dvb_frontend.h"
+#include "dvb_net.h"
+
+// module parameters
+static int debug = 0;
+
+// debug macro
+#define dprintk if (debug) printk
+
+/* Version information */
+#define DRIVER_VERSION "0.2"
+#define DRIVER_DESC "Twinhan VisionPlus VisionDTV USB-Ter DVB-T / CTS Portable"
+#define DRIVER_AUTHOR "Patrick Boettcher, patrick.boettcher@desy.de"
+
+#define USB_TWINHAN_VENDOR_ID 0x1822
+// product ID befode loading the firmware
+#define USB_VP7041_PRODUCT_PREFW_ID 0x3201
+// product ID afterwards
+#define USB_VP7041_PRODUCT_ID 0x3202
+
+/* USB Driver stuff */
+
+/* table of devices that work with this driver */
+static struct usb_device_id vp7041_table [] = {
+ { USB_DEVICE(USB_TWINHAN_VENDOR_ID, USB_VP7041_PRODUCT_PREFW_ID) },
+ { USB_DEVICE(USB_TWINHAN_VENDOR_ID, USB_VP7041_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, vp7041_table);
+
+static const char *firmware_filenames[] = {
+ "dvb-vp7041-2.42.fw",
+ "dvb-vp7041-2.422.fw"
+};
+
+#define COMMAND_PIPE 0x01
+#define RESULT_PIPE 0x81
+#define DATA_PIPE 0x82
+
+#define VP7041_MAX_PIDS 16
+
+struct vp7041_channel {
+ struct usb_vp7041 *vp;
+
+ u8 id;
+ u16 pid;
+ u8 active;
+};
+
+/* data struct */
+struct usb_vp7041 {
+/* usb */
+ struct usb_device * udev;
+
+ unsigned int command_pipe;
+ unsigned int result_pipe;
+ unsigned int data_pipe;
+
+ struct urb *buf_urb;
+ u8 *buffer;
+ dma_addr_t dma_handle;
+
+ struct vp7041_channel channel[VP7041_MAX_PIDS];
+
+ fe_status_t fe_status;
+ struct dvb_frontend_parameters fe_params;
+ int feed_count;
+ int streaming;
+ int disconnecting;
+
+ struct semaphore usb_sem;
+ spinlock_t channel_lock;
+
+/* dvb */
+ struct dvb_adapter *adapter;
+ struct dmxdev dmxdev;
+ struct dvb_demux demux;
+ struct dvb_net dvb_net;
+ struct dmx_frontend frontend;
+ struct dvb_i2c_bus i2c_bus;
+};
+
+
+static struct dvb_frontend_info vp7041_frontend_info = {
+ .name = "VisionPlus VisionDTV USB-Ter (VP7041) Frontend",
+ .type = FE_OFDM,
+ .frequency_min = 40000000,
+ .frequency_max = 858000000,
+ .frequency_stepsize = 62500,
+ .caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
+ FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |
+ FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO |
+ FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO |
+ FE_CAN_HIERARCHY_AUTO,
+};
+
+static u8 vp7041_init_buf[] = {
+ 0x07, 0x00, 0x01, 0x33, 0x00, 0xc0, 0x80, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1c, 0x00, 0x00, 0x00, 0x86, 0xa0, 0xc2, 0x37, 0xf3, 0x24, 0xd2, 0x4e, 0x85,
+ 0x01, 0xa1, 0xcd, 0x7e, 0x50, 0xbb, 0x08, 0x01
+};
+/* 2004-07-12
+ 0x07, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+*/
+
+
+static int vp7041_cmdmsg(struct usb_vp7041 *vp,u8 *buf, unsigned int buflen,
+ u8 *retbuf, unsigned int retbuflen, unsigned int *actbuflen)
+{
+ u8 *b;
+ int actual_size,ret = -ENOMEM;
+
+ if (buf == NULL || buflen == 0)
+ return -EINVAL;
+
+ if (vp->disconnecting)
+ return -EINVAL;
+
+ if ((ret = down_interruptible(&vp->usb_sem))) {
+ err("Failed to down usb semaphore.\n");
+ return ret;
+ }
+
+ b = kmalloc(buflen,GFP_KERNEL);
+ if (b) {
+ memcpy(b,buf,buflen);
+
+ if (debug) {
+ int i;
+ printk("%s: %d > ", __FUNCTION__,buflen);
+ for (i = 0; i < buflen; i++)
+ printk("%02x ", b[i]);
+ printk("\n");
+ }
+
+ ret = usb_bulk_msg(vp->udev,vp->command_pipe,
+ b,buflen,&actual_size,HZ);
+
+ if (ret)
+ err("bulk message failed: %d (%d/%d)",ret,buflen,actual_size);
+ else
+ ret = actual_size != buflen ? -1 : 0;
+
+ kfree(b);
+ }
+
+ if (!ret && retbuf && retbuflen && actbuflen != NULL) {
+ ret = usb_bulk_msg(vp->udev,vp->result_pipe,retbuf,
+ retbuflen,actbuflen,HZ);
+ if (ret)
+ err("recv bulk message failed: %d",ret);
+ else if (debug) {
+ int i;
+ printk("%s: %d/%d > ", __FUNCTION__,*actbuflen,retbuflen);
+ for (i = 0; i < *actbuflen; i++)
+ printk("%02x ", retbuf[i]);
+ printk("\n");
+ }
+ }
+
+ up(&vp->usb_sem);
+
+ return ret;
+}
+
+static int vp7041_sndmsg(struct usb_vp7041 *vp,u8 *buf, unsigned int buflen)
+{
+ return vp7041_cmdmsg(vp,buf,buflen,NULL,0,NULL);
+}
+
+static int vp7041_02_cmd(struct usb_vp7041 *vp, u8 a, u8 b, u8 c, u8 d, u8 e, u16 *val)
+{
+ u8 buf[64],bin[6] = { 0x02, a, b, c, d, e };
+ int ret,len;
+
+ *val = 0;
+ if ((ret = vp7041_cmdmsg(vp,bin,6,buf,64,&len)))
+ return ret;
+
+ if (len != 2) {
+ err("unexpected return length: %d",len);
+ return -ENOSYS;
+ }
+
+ *val = buf[0] << 8 | buf[1];
+ dprintk("return stat: %d 0x%4x",*val,*val);
+ return 0;
+}
+
+static int vp7041_03_cmd(struct usb_vp7041 *vp, u8 a, u8 b, u8 c, u8 d, u8 e)
+{
+ u8 buf[6] = { 0x03, a, b, c, d, e };
+ return vp7041_sndmsg(vp,buf,6);
+}
+
+static int vp7041_chk_tune (struct usb_vp7041 *vp,u8 stat, u16 *val)
+{
+ return vp7041_02_cmd(vp,0x11,0x81,stat,0x00,0x02,val);
+}
+
+static int vp7041_tune(struct usb_vp7041 *vp, unsigned int freq, unsigned int bw)
+{
+ u8 bwbuf[] = {
+ 0x08,0x00, 0, 0x09, 0, 0,
+ 0x37,0x00,0x00, 0x38,0x00, 0, 0x39, 0, 0, 0x3a,0x00, 0,
+ 0x3b, 0, 0, 0x3c, 0, 0, 0x3d, 0, 0, 0x3e,0x00,0x00,
+ 0x3f,0x03,0xe8, 0x40,0x00,0x00, 0x41,0x03,0xf2, 0x42,0x00,0x01,
+ 0x43,0xb0,0xd0,
+
+ 0x34,0x00,0x04, 0x01,0x00,0x01, 0x02,0x00,0x00,
+ 0x05,0x00,0x01, 0x36,0x00,0x0b,
+ 0x4f,0x00,0x01,
+
+ 0x54,0x00,0x00, 0x79,0x00,0x05,
+
+ 0xc3,0x00,0x01, 0x7e,0x00,0x00, 0x65,0x00,0x00,
+
+ 0x2b,0x09,0x2d, 0x2c,0x00,0x05, 0x2d,0x09,0x2d, 0x2e,0x00,0x05,
+ 0x2f,0x0a,0x1a, 0x30,0x00,0x02, 0x31,0x0a,0x1a, 0x32,0x00,0x02,
+ 0x4f,0x00,0x00,
+ 0x00,0x00,0x0c, 0x00,0x00,0x00,
+
+ 0x2b,0x08,0x28, 0x2c,0x00,0x0a, 0x2d,0x08,0x28, 0x2e,0x00,0x0a,
+ 0x2f,0x0d,0x78, 0x30,0x00,0x05, 0x31,0x0d,0x78, 0x32,0x00,0x05,
+ 0x4f,0x00,0x01,
+
+ 0x00,0x00,0x02, 0x00,0x00,0x00
+ };
+ u8 prefeedbuf[] = {
+ 0x81,0x53, 0x81,0x54, 0x80,0x06, 0x80,0x07, 0x81,0x8e, 0x81,0x8f,
+ 0x81,0x90, 0x81,0x91, 0x81,0x92, 0x81,0x93, 0x81,0x94
+ };
+ u8 prefeedbuf2[] = {
+ 0x34,0x00,0x04, 0x01,0x00,0x01, 0x02,0x00,0x02,
+ 0x05,0x00,0x01, 0x36,0x00,0x00, 0x4f,0x00,0x00,
+ 0x54,0x00,0x00, 0x79,0x00,0x05,
+
+ 0x03,0x00,0x01,
+ 0x04,0x00,0x01, 0x82,0x00,0x01, 0x81,0x00,0x02,
+
+ 0xc3,0x00,0x01, 0x7e,0x00,0x00, 0x65,0x00,0x00,
+ 0x00,0x00,0x04, 0x00,0x00,0x00
+ };
+ u16 vpfreq;
+ u8 vu, p2, p1, p0;
+ int i,ret;
+ u16 tunestat;
+
+ vp->fe_status = 0;
+
+// from at76c651.c it is a TUA6010XS
+ vpfreq = (freq + 36125000) / 62500;
+
+ dprintk("tuning to freq: %u (%2x,%2x), bw: %u, bufsize: %d",
+ freq,(vpfreq >> 8) & 0xff, vpfreq & 0xff,bw,sizeof(bwbuf));
+
+ if (freq > 400000000)
+ vu = 1, p2 = 1, p1 = 0, p0 = 1;
+ else if (freq > 140000000)
+ vu = 0, p2 = 1, p1 = 1, p0 = 0;
+ else
+ vu = 0, p2 = 0, p1 = 1, p0 = 1;
+
+ if ((ret = vp7041_03_cmd(vp,0x10,0x04,0x41,0x61,0x00)) ||
+ (ret = vp7041_03_cmd(vp,0xc2,
+ (vpfreq >> 8) & 0x7f,
+ vpfreq & 0xff,
+ 0x8e,
+ (vu << 7) | (p2 << 2) | (p1 << 1) | p0 )) ||
+ (ret = vp7041_03_cmd(vp,0x10,0x04,0x41,0x61,0x80)) )
+ return ret;
+
+ switch (bw) {
+ case BANDWIDTH_6_MHZ:
+ bwbuf[2] = 0x7e; bwbuf[4] = 0xbe; bwbuf[5] = 0xe9;
+ bwbuf[11] = 0x21; bwbuf[13] = 0xd0; bwbuf[14] = 0x40;
+ bwbuf[17] = 0x70; bwbuf[19] = 0xb6; bwbuf[20] = 0x2b;
+ bwbuf[22] = 0x02; bwbuf[23] = 0x33; bwbuf[25] = 0x8e;
+ bwbuf[26] = 0xd5;
+ break;
+ case BANDWIDTH_7_MHZ:
+ bwbuf[2] = 0x93; bwbuf[4] = 0xde; bwbuf[5] = 0xbb;
+ bwbuf[11] = 0x1c; bwbuf[13] = 0xfb; bwbuf[14] = 0xa5;
+ bwbuf[17] = 0x60; bwbuf[19] = 0x9c; bwbuf[20] = 0x25;
+ bwbuf[22] = 0x01; bwbuf[23] = 0xe3; bwbuf[25] = 0x0c;
+ bwbuf[26] = 0xb7;
+ break;
+ case BANDWIDTH_8_MHZ:
+ bwbuf[2] = 0xa8; bwbuf[4] = 0xfe; bwbuf[5] = 0x8c;
+ bwbuf[11] = 0x19; bwbuf[13] = 0x5c; bwbuf[14] = 0x30;
+ bwbuf[17] = 0x54; bwbuf[19] = 0x88; bwbuf[20] = 0xa0;
+ bwbuf[22] = 0x01; bwbuf[23] = 0xa6; bwbuf[25] = 0xab;
+ bwbuf[26] = 0x20;
+ break;
+ default:
+ err("bandwidth: %d not supported",bw);
+ return -ENOSYS;
+ }
+
+
+ for (i=0; i < sizeof(bwbuf); i+=3)
+ if ((ret = vp7041_03_cmd(vp,0x10,0x00,bwbuf[i],bwbuf[i+1],bwbuf[i+2])))
+ return ret;
+
+ do {
+ ret = vp7041_chk_tune (vp, 0xb2, &tunestat);
+ } while (ret == 0 && !(tunestat & 0x01) && !(tunestat & 0x02));
+
+ if ((ret = vp7041_chk_tune (vp, 0xab, &tunestat)))
+ return ret;
+
+ switch (tunestat) {
+ case 0x01:
+ for (i=0; i < sizeof(prefeedbuf); i += 2) {
+ if ((ret = vp7041_02_cmd(vp,0x11,prefeedbuf[i],prefeedbuf[i+1],
+ 0x00,0x02,&tunestat)))
+ return ret;
+ }
+ vp->fe_status = FE_HAS_SIGNAL | FE_HAS_VITERBI | FE_HAS_SYNC |
+ FE_HAS_CARRIER;
+ break;
+ case 0x00: // Tuning failed
+ return 0;
+ break;
+ default:
+ dprintk("Unknown tunestat (0x%x).\n",tunestat);
+ break;
+ }
+
+ for (i=0; i < sizeof(prefeedbuf2); i+=3)
+ if ((ret = vp7041_03_cmd(vp,0x10,0x00,prefeedbuf2[i],prefeedbuf2[i+1],
+ prefeedbuf2[i+2])))
+ return ret;
+ vp->fe_status |= FE_HAS_LOCK;
+ return ret;
+}
+
+#if 0
+/*
+ * check the signal strength and quality
+ * (TODO reverse engineer the result)
+ */
+static int vp7041_signal_strength(struct usb_vp7041 *vp)
+{
+ u8 b_out[] = {
+ 0x48, 0xa7, 0xa8, 0x9e, 0x9f, 0xa4, 0x7c, 0x74, 0x75, 0x45
+ };
+ u16 vals[sizeof(b_out)];
+ int i, ret;
+
+ for (i=0; i < sizeof(b_out); i++)
+ if ((ret = vp7041_chk_tune(vp,b_out[i],&vals[i])))
+ return ret;
+
+ return 0;
+}
+
+/* remote control (0x04) */
+/*
+ * TODO: a tasklet should run with a delay of (1/10 second)
+ * fill an appropriate event device ?
+ */
+static int vp7041_rc_status(struct usb_vp7041 *vp)
+{
+ u8 b_out[1] = { 0x04 },b_in[5];
+ unsigned int actlen;
+ int ret = 0;
+
+ ret = vp7041_cmdmsg(vp,b_out,1,b_in,5,&actlen);
+ if (!ret) {
+ dprintk("remote control result: %x %x %x %x %x",
+ b_in[0],b_in[1],b_in[2],b_in[3],b_in[4]);
+ } else
+ err("remote control cmd failed: %d",ret);
+ return ret;
+}
+#endif
+static struct vp7041_channel *vp7041_channel_allocate(struct usb_vp7041 *vp)
+{
+ int i;
+ unsigned long flags;
+ struct vp7041_channel *ch = NULL;
+
+ spin_lock_irqsave(&vp->channel_lock,flags);
+ for (i = 0; i < VP7041_MAX_PIDS; i++)
+ if (!vp->channel[i].active) {
+ ch = vp->channel + i;
+ ch->active = 1;
+ break;
+ }
+ spin_unlock_irqrestore(&vp->channel_lock,flags);
+
+ return ch;
+}
+
+static int vp7041_set_channel(struct vp7041_channel *channel)
+{
+ dprintk("set channel: feed_count: %d\n",channel->vp->feed_count);
+ return vp7041_03_cmd(channel->vp,0x10, 0x00, channel->id,
+ 0x20 + (channel->pid >> 8), channel->pid & 0xff);
+}
+
+static int vp7041_del_channel(struct vp7041_channel *channel)
+{
+ // I think, spinlock at this point is not necessary, but maybe I'm wrong
+ int ret;
+ dprintk("del_channel: vp = %p, id = %x, active=%d, streamcount=%d pid=%x\n",
+ channel->vp,channel->id,channel->active,channel->vp->feed_count,channel->pid);
+ ret = vp7041_03_cmd(channel->vp,0x10, 0x00, channel->id, 0, 0);
+ channel->active = 0;
+ return ret;
+}
+
+static void vp7041_urb_complete(struct urb *urb, struct pt_regs *ptregs)
+{
+ struct usb_vp7041 *vp = urb->context;
+
+ if (!vp->streaming)
+ return;
+
+ if (urb->status == 0) {
+ if (urb->actual_length % 188)
+ dprintk("TS Packets: %d, %d\n", urb->actual_length/188,
+ urb->actual_length % 188);
+ dvb_dmx_swfilter_packets(&vp->demux, (u8*) urb->transfer_buffer,
+ urb->actual_length/188);
+ }
+
+ if (vp->streaming)
+ usb_submit_urb(urb,GFP_KERNEL);
+}
+
+static void vp7041_stop_xfer(struct usb_vp7041 *vp)
+{
+ vp->streaming = 0;
+ usb_unlink_urb(vp->buf_urb);
+ if(!vp->disconnecting) {
+ int i;
+ for (i = 0; i < VP7041_MAX_PIDS; i++)
+ vp7041_del_channel(&vp->channel[i]);
+ vp->feed_count = 0;
+
+ vp7041_03_cmd(vp,0x10, 0x00, 0x91, 0x00, 0x01);
+ }
+}
+
+
+static int vp7041_start_xfer(struct usb_vp7041 *vp)
+{
+ int ret;
+
+ if (vp->streaming || vp->disconnecting)
+ return 0;
+
+ usb_fill_bulk_urb( vp->buf_urb, vp->udev, vp->data_pipe,
+ vp->buffer, 8192, vp7041_urb_complete, vp);
+ vp->buf_urb->transfer_flags = 0;
+ vp->buf_urb->timeout = 0;
+
+ // starting transfer in device
+ if ((ret = vp7041_03_cmd(vp,0x10, 0x00, 0x91, 0x00, 0x00)))
+ return ret;
+
+ if ((ret = usb_submit_urb(vp->buf_urb,GFP_KERNEL))) {
+ vp7041_stop_xfer(vp);
+ err("could not submit buffer urb.");
+ return ret;
+ }
+
+ vp->streaming = 1;
+
+ return 0;
+}
+
+static int vp7041_start_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+ struct dvb_demux *dvbdmx = dvbdmxfeed->demux;
+ struct usb_vp7041 *vp = dvbdmxfeed->demux->priv;
+ struct vp7041_channel *channel;
+
+ dprintk("pid: 0x%04x, feedtype: %d\n", dvbdmxfeed->pid,
+ dvbdmxfeed->type);
+
+ if (!dvbdmx->dmx.frontend)
+ return -EINVAL;
+
+ if ((channel = vp7041_channel_allocate(vp)) == NULL)
+ return -EBUSY;
+
+ dvbdmxfeed->priv = channel;
+ channel->pid = dvbdmxfeed->pid;
+
+ vp7041_set_channel(channel);
+
+ if (0 == vp->feed_count++)
+ return vp7041_start_xfer(vp);
+
+ return 0;
+}
+
+static int vp7041_stop_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+ struct usb_vp7041 *vp = dvbdmxfeed->demux->priv;
+ struct vp7041_channel *channel = (struct vp7041_channel *)
+ dvbdmxfeed->priv;
+
+ dprintk("stopfeed pid: 0x%04x, feedtype: %d",dvbdmxfeed->pid,
+ dvbdmxfeed->type);
+
+ if (channel == NULL)
+ err("channel in dmxfeed->priv was NULL");
+ else
+ vp7041_del_channel(channel);
+
+ if (--vp->feed_count == 0)
+ vp7041_stop_xfer(vp);
+
+ return 0;
+}
+
+static int vp7041_device_init (struct usb_vp7041 *vp)
+{
+ int i,ret;
+ u16 stat;
+ u8 initbuf[] = {
+ 0x03,0x00,0x03, 0x04,0x00,0x07,
+
+ 0x06,0x00,0xb2, 0x07,0x23,0x1e, 0x08,0x00,0xa8, 0x09,0xfe,0x8c,
+ 0x0a,0x00,0x00, 0x0b,0x00,0x02, 0x0c,0x00,0x0a, 0x0f,0x01,0xff,
+
+ 0x24,0x03,0x99,
+
+ 0x13,0x00,0x01, 0x14,0xcc,0xcd, 0x15,0x02,0x6f, 0x16,0x00,0x80,
+ 0x17,0x00,0xa6, 0x18,0x00,0xc3, 0x19,0x00,0x3d, 0x1a,0x00,0x01,
+ 0x1b,0xd2,0x06, 0x1c,0x94,0x7b, 0x1d,0x00,0x00, 0x1e,0x00,0x5a,
+ 0x1f,0x00,0x21, 0x20,0x00,0x17,
+
+ 0x57,0x00,0x00,
+
+ 0x21,0x00,0x02, 0x22,0x02,0x20, 0x23,0x00,0x00, 0x25,0x00,0x05,
+ 0x26,0x00,0x04, 0x27,0x00,0x87, 0x28,0x00,0x87,
+
+ 0x2b,0x08,0x28, 0x2c,0x00,0x0a, 0x2d,0x08,0x28, 0x2e,0x00,0x0a,
+ 0x2f,0x0d,0x78, 0x30,0x00,0x05, 0x31,0x0d,0x78, 0x32,0x00,0x05,
+ 0x33,0x00,0x04, 0x34,0x00,0x04, 0x35,0x00,0x80, 0x36,0x00,0x0b,
+ 0x37,0x00,0x00, 0x38,0x00,0x19, 0x39,0x5c,0x30, 0x3a,0x00,0x54,
+ 0x3b,0x88,0xa0, 0x3c,0x01,0xa6, 0x3d,0xab,0x20, 0x3e,0x00,0x00,
+ 0x3f,0x03,0xe8, 0x40,0x00,0x00, 0x41,0x03,0xf2, 0x42,0x00,0x01,
+ 0x43,0xb0,0xd0, 0x44,0x00,0x00, 0x45,0x00,0x00, 0x47,0x00,0x00,
+ 0x4d,0x00,0x06, 0x4e,0x00,0x80, 0x4f,0x00,0x01,
+
+ 0x5c,0x00,0x80,
+
+ 0x60,0x00,0x10, 0x61,0x00,0x09, 0x6a,0x00,0x80, 0x6b,0x00,0x80,
+ 0x6c,0x00,0x80,
+
+ 0x7a,0x0b,0x33, 0x7e,0x00,0x00,
+
+ 0x81,0x00,0x00, 0x82,0x00,0x01,
+
+ 0x87,0x00,0x01, 0x8f,0x00,0x00,
+
+ 0xab,0x00,0xe2, 0xac,0x00,0xa0, 0xad,0x00,0x1d, 0xae,0x03,0xd3,
+ 0xaf,0x03,0xe6, 0xb0,0x00,0x13, 0xb1,0x00,0x16, 0xb2,0x03,0xfb,
+ 0xb3,0x03,0xee, 0xb4,0x03,0xfe, 0xb5,0x00,0x0c, 0xb6,0x00,0x06,
+ 0xb7,0x03,0xf9, 0xb8,0x03,0xf9, 0xb9,0x00,0x03, 0xba,0x00,0x06,
+ 0xbc,0x03,0xfb, 0xbd,0x03,0xfd, 0xbe,0x00,0x02, 0xbf,0x00,0x03,
+ 0xc0,0x00,0x01,
+
+ 0xc2,0x00,0x00, 0xc3,0x00,0x01,
+
+ 0xce,0x7f,0xff, 0xcf,0x0f,0xff,
+
+ 0xa9,0x00,0x06,
+
+ 0x8e,0x00,0x00, 0x8f,0x00,0x01, 0x90,0x00,0x01, 0x91,0x00,0x01,
+ 0x92,0x00,0x03, 0x93,0x01,0x00,
+
+ 0x99,0x00,0x00, 0x99,0x00,0x00, 0x99,0x00,0x00, 0x99,0x00,0x00,
+ 0x99,0x00,0x00, 0x99,0x00,0x00, 0x99,0x00,0x00, 0x99,0x00,0x00,
+ 0x99,0x00,0x00, 0x99,0x00,0x00, 0x99,0x00,0x00, 0x99,0x00,0x00,
+ 0x99,0x00,0x00, 0x99,0x00,0x00, 0x99,0x00,0x00, 0x99,0x00,0x00,
+
+ 0x7f,0x00,0x00
+ };
+ if ((ret = vp7041_sndmsg(vp,vp7041_init_buf,sizeof(vp7041_init_buf))))
+ return ret;
+
+ if ((ret = vp7041_03_cmd(vp,0x10,0x04,0x04,0x00,0x00))) return ret;
+ if ((ret = vp7041_02_cmd(vp,0x11,0x84,0x01,0x00,0x02,&stat))) return ret;
+ if (stat != 0x01b3) {
+ dprintk("unexpected known value returned during initialization (%.4x)",stat);
+ return -ENOSYS;
+ }
+ if ((ret = vp7041_03_cmd(vp,0x10,0x00,0x00,0x00,0x04))) return ret;
+ if ((ret = vp7041_03_cmd(vp,0x10,0x04,0x00,0x81,0x2c))) return ret;
+ if ((ret = vp7041_03_cmd(vp,0x10,0x04,0x00,0x00,0x04))) return ret;
+ if ((ret = vp7041_03_cmd(vp,0x10,0x04,0x03,0x90,0x00))) return ret;
+ if ((ret = vp7041_03_cmd(vp,0x10,0x04,0x05,0x00,0x01))) return ret;
+
+ for (i = 0; i < sizeof(initbuf); i+=3)
+ if ((ret = vp7041_03_cmd(vp,0x10,0x00,initbuf[i],
+ initbuf[i+1],initbuf[i+2])))
+ return ret;
+
+ return 0;
+}
+
+static int vp7041_dvb_init(struct usb_vp7041 *vp)
+{
+ int ret;
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,4)
+ if ((ret = dvb_register_adapter(&vp->adapter, DRIVER_DESC)) < 0) {
+#else
+ if ((ret = dvb_register_adapter(&vp->adapter, DRIVER_DESC ,
+ THIS_MODULE)) < 0) {
+#endif
+ dprintk("dvb_register_adapter failed: error %d", ret);
+ goto err;
+ }
+ vp->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
+
+ vp->demux.priv = (void *)vp;
+ vp->demux.filternum = 15;
+ vp->demux.feednum = 15;
+ vp->demux.start_feed = vp7041_start_feed;
+ vp->demux.stop_feed = vp7041_stop_feed;
+ vp->demux.write_to_decoder = NULL;
+ if ((ret = dvb_dmx_init(&vp->demux)) < 0) {
+ err("dvb_dmx_init failed: error %d",ret);
+ goto err_dmx;
+ }
+
+ vp->dmxdev.filternum = vp->demux.filternum;
+ vp->dmxdev.demux = &vp->demux.dmx;
+ vp->dmxdev.capabilities = 0;
+ if ((ret = dvb_dmxdev_init(&vp->dmxdev, vp->adapter)) < 0) {
+ err("dvb_dmxdev_init failed: error %d",ret);
+ goto err_dmx_dev;
+ }
+
+ vp->frontend.source = DMX_FRONTEND_0;
+ if ((ret = vp->demux.dmx.add_frontend(&vp->demux.dmx,
+ &vp->frontend)) < 0) {
+ err("dmx_add_frontend failed: error %d", ret);
+ goto err_add_fe;
+ }
+
+ if ((ret = vp->demux.dmx.connect_frontend(&vp->demux.dmx,
+ &vp->frontend)) < 0) {
+ err("dmx_connect_frontend failed: error %d\n",ret);
+ goto err_conn_fe;
+ }
+
+ vp->fe_status = 0;
+
+ dvb_net_init(vp->adapter, &vp->dvb_net, &vp->demux.dmx);
+
+ goto success;
+err_conn_fe:
+ vp->demux.dmx.remove_frontend(&vp->demux.dmx, &vp->frontend);
+err_add_fe:
+ dvb_dmxdev_release(&vp->dmxdev);
+err_dmx_dev:
+ dvb_dmx_release(&vp->demux);
+err_dmx:
+ dvb_unregister_adapter(vp->adapter);
+err:
+ return ret;
+success:
+ return 0;
+}
+
+static int vp7041_dvb_exit(struct usb_vp7041 *vp)
+{
+ info("unregistering DVB part");
+ dvb_net_release(&vp->dvb_net);
+ vp->demux.dmx.close(&vp->demux.dmx);
+ vp->demux.dmx.remove_frontend(&vp->demux.dmx, &vp->frontend);
+ dvb_dmxdev_release(&vp->dmxdev);
+ dvb_dmx_release(&vp->demux);
+ dvb_unregister_adapter(vp->adapter);
+
+ return 0;
+}
+
+static int vp7041_frontend_ioctl(struct dvb_frontend *fe, unsigned int cmd,
+ void *arg)
+{
+ struct usb_vp7041 *vp = fe->data;
+ switch (cmd) {
+ case FE_GET_INFO:
+ dprintk("FE_GET_INFO\n");
+ memcpy(arg, &vp7041_frontend_info,
+ sizeof (struct dvb_frontend_info));
+ break;
+ case FE_READ_STATUS: {
+ fe_status_t *status = (fe_status_t *)arg;
+ dprintk("FE_READ_STATUS\n");
+ *status = vp->fe_status;
+ }
+ break;
+ case FE_READ_BER:
+ dprintk("FE_READ_BER\n");
+ return -EINVAL;
+ break;
+ case FE_READ_SIGNAL_STRENGTH: {
+// TODO vp7041_signal_strength(vp);
+ u16 *val = (u16*) arg;
+ *val = 0xFFFF;
+ break;
+ }
+ case FE_READ_SNR: {
+ u16 *val = (u16*) arg;
+ *val = 0xFFFF;
+// TODO vp7041_signal_strength
+ dprintk("FE_READ_SNR\n");
+ break;
+ }
+ case FE_READ_UNCORRECTED_BLOCKS:
+ dprintk("FE_READ_UNCORRECTED_BLOCKS\n");
+ return -EINVAL;
+ break;
+ case FE_SET_FRONTEND: {
+ struct dvb_frontend_parameters *p =
+ (struct dvb_frontend_parameters *) arg;
+ vp->fe_params = *p;
+ dprintk("FE_SET_FRONTEND, freq: %d rate: %d bw:%d inv: %d\n",
+ p->frequency,p->u.qam.symbol_rate,p->u.ofdm.bandwidth,p->inversion);
+ return vp7041_tune(vp,p->frequency,p->u.ofdm.bandwidth);
+ break;
+ }
+ case FE_GET_FRONTEND: {
+ struct dvb_frontend_parameters *p =
+ (struct dvb_frontend_parameters *) arg;
+ dprintk("FE_GET_FRONTEND\n");
+ *p = vp->fe_params;
+ }
+ break;
+ case FE_SLEEP:
+ dprintk("FE_SLEEP\n");
+ return -ENOSYS;
+ break;
+ case FE_INIT:
+ dprintk("FE_INIT\n");
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void vp7041_frontend_init(struct usb_vp7041 *vp)
+{
+ vp->i2c_bus.adapter = vp->adapter;
+ dvb_register_frontend(vp7041_frontend_ioctl, &vp->i2c_bus, (void *)vp,
+ &vp7041_frontend_info);
+}
+
+static void vp7041_frontend_exit(struct usb_vp7041 *vp)
+{
+ dvb_unregister_frontend(vp7041_frontend_ioctl, &vp->i2c_bus);
+}
+
+static int vp7041_exit (struct usb_vp7041 *vp)
+{
+ usb_free_urb(vp->buf_urb);
+ pci_free_consistent(NULL,8192,vp->buffer,vp->dma_handle);
+ return 0;
+}
+
+static int vp7041_init(struct usb_vp7041 *vp)
+{
+ int ret,i;
+ unsigned long flags;
+
+ sema_init(&vp->usb_sem, 1);
+ spin_lock_init(&vp->channel_lock);
+
+ vp->command_pipe = usb_sndbulkpipe(vp->udev, COMMAND_PIPE);
+ vp->result_pipe = usb_rcvbulkpipe(vp->udev, RESULT_PIPE);
+ vp->data_pipe = usb_rcvbulkpipe(vp->udev, DATA_PIPE);
+
+ // when reloading the driver w/o replugging the device
+ // a timeout occures, this should help
+ usb_clear_halt(vp->udev,vp->command_pipe);
+ usb_clear_halt(vp->udev,vp->result_pipe);
+ usb_clear_halt(vp->udev,vp->data_pipe);
+
+ vp->buffer = pci_alloc_consistent(NULL,8192, &vp->dma_handle);
+ memset(vp->buffer,0,8192);
+ if (!(vp->buf_urb = usb_alloc_urb(0,GFP_KERNEL))) {
+ vp7041_exit(vp);
+ return -ENOMEM;
+ }
+ spin_lock_irqsave(&vp->channel_lock,flags);
+ for (i=0; i < VP7041_MAX_PIDS; i++) {
+ vp->channel[i].vp = vp;
+ vp->channel[i].id = 0x99+i;
+ vp->channel[i].active = 0;
+ }
+ spin_unlock_irqrestore(&vp->channel_lock,flags);
+
+ if ((ret = vp7041_device_init(vp)))
+ return ret;
+
+ if ((ret = vp7041_dvb_init(vp)))
+ return ret;
+ vp7041_frontend_init(vp);
+ return 0;
+}
+
+static int vp7041_loadfirmware(struct usb_device *udev)
+{
+ const struct firmware *fw = NULL;
+ int ret = 0, i;
+ for (i = 0; i < sizeof(firmware_filenames)/sizeof(char*); i++)
+ if (request_firmware(&fw, firmware_filenames[i], &udev->dev) == 0) {
+ info("found firmware file (%s).",firmware_filenames[i]);
+ break;
+ }
+
+ if (fw == NULL) {
+ err("Did not find a valid firmware.");
+ return -EINVAL;
+ }
+
+ if (!(fw->size % 22)) {
+ int i;
+ u16 addr;
+ u8 *b,*p = kmalloc(fw->size,GFP_KERNEL);
+ if (p != NULL) {
+ /*
+ * 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);
+ for(i = 0; i < fw->size; i += 22) {
+ b = (u8 *) &p[i];
+ addr = *((u16 *) &b[3]);
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev,0),
+ 0xa0, USB_TYPE_VENDOR, addr, 0x00, &b[6],
+ (u16) b[1], 5*HZ);
+
+ if (ret != b[1]) {
+ err("error while transferring firmware "
+ "(transferred size: %d, block size: %d)",
+ ret,b[1]);
+ ret = -EINVAL;
+ break;
+ }
+ }
+ kfree(p);
+ ret = 0;
+ } else
+ ret = -ENOMEM;
+ } else {
+ err("invalid firmware filesize.");
+ ret = -ENODEV;
+ }
+ release_firmware(fw);
+
+ return ret;
+}
+
+
+static int vp7041_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct usb_vp7041 *vp = NULL;
+
+ int retval = -ENOMEM;
+
+ switch (udev->descriptor.idProduct) {
+ case USB_VP7041_PRODUCT_PREFW_ID:
+ retval = vp7041_loadfirmware(udev);
+ break;
+ case USB_VP7041_PRODUCT_ID:
+ vp = kmalloc(sizeof(struct usb_vp7041),GFP_KERNEL);
+ if (vp == NULL) {
+ err("no memory");
+ return retval;
+ }
+ memset(vp,0,sizeof(struct usb_vp7041));
+ vp->udev = udev;
+ usb_set_intfdata(intf, vp);
+
+ retval = vp7041_init(vp);
+
+ break;
+ default:
+ err("something went very wrong, "
+ "unknown product ID: %.4x",udev->descriptor.idProduct);
+ retval = -ENODEV;
+ break;
+ }
+ if (retval == 0)
+ info( DRIVER_DESC " successfully initialized and connected.");
+ else
+ info( DRIVER_DESC " error while loading driver (%d)",retval);
+ return retval;
+}
+
+static void vp7041_disconnect(struct usb_interface *intf)
+{
+ struct usb_vp7041 *vp = usb_get_intfdata(intf);
+ usb_set_intfdata(intf,NULL);
+
+ if (vp != NULL) {
+ vp->disconnecting = 1;
+ vp7041_stop_xfer(vp);
+ vp7041_frontend_exit(vp);
+ vp7041_dvb_exit(vp);
+ vp7041_exit(vp);
+ kfree(vp);
+ }
+
+ info( DRIVER_DESC " successfully deinitialized and disconnected.");
+}
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver vp7041_driver = {
+ .owner = THIS_MODULE,
+ .name = "vp7041",
+ .probe = vp7041_probe,
+ .disconnect = vp7041_disconnect,
+ .id_table = vp7041_table,
+};
+
+/* module stuff */
+static int __init usb_vp7041_init(void)
+{
+ int result;
+
+ if ((result = usb_register(&vp7041_driver))) {
+ err("usb_register failed. Error number %d",result);
+ return result;
+ }
+
+ return 0;
+}
+
+static void __exit usb_vp7041_exit(void)
+{
+ /* deregister this driver from the USB subsystem */
+ usb_deregister(&vp7041_driver);
+}
+
+module_init (usb_vp7041_init);
+module_exit (usb_vp7041_exit);
+
+/* module parameters */
+module_param(debug, int,0x644);
+MODULE_PARM_DESC(debug, "enable debugging (or not)");
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");