diff options
author | Patrick Boettcher <devnull@localhost> | 2004-07-20 20:52:19 +0000 |
---|---|---|
committer | Patrick Boettcher <devnull@localhost> | 2004-07-20 20:52:19 +0000 |
commit | 579832ccff77ff3322bd06fd73256614896e429b (patch) | |
tree | cf40f64c2219719378efadcb7ea98266b5c77c64 | |
parent | c103d051629c32e975aad5fca2d5e07ab44236cb (diff) | |
download | mediapointer-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.txt | 1 | ||||
-rw-r--r-- | linux/Documentation/dvb/readme.txt | 4 | ||||
-rw-r--r-- | linux/Documentation/dvb/vp7041.txt | 148 | ||||
-rw-r--r-- | linux/drivers/media/dvb/Kconfig | 1 | ||||
-rw-r--r-- | linux/drivers/media/dvb/Makefile | 2 | ||||
-rw-r--r-- | linux/drivers/media/dvb/vp7041/Kconfig | 19 | ||||
-rw-r--r-- | linux/drivers/media/dvb/vp7041/Makefile | 3 | ||||
-rw-r--r-- | linux/drivers/media/dvb/vp7041/vp7041.c | 1022 |
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"); |