/* * 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 #include #include #include #include #include #include #include "dmxdev.h" #include "dvb_demux.h" #include "dvb_filter.h" #include "dvb_frontend.h" #include "dvb_net.h" static int debug; module_param(debug, int, 0x644); MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); #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; /* i2c */ struct i2c_adapter *i2c; }; 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_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 int vp7041_frontend_init(struct usb_vp7041 *vp) { int ret; ret = dvb_register_frontend(vp7041_ioctl, vp->adapter, vp, &vp7041_frontend_info, THIS_MODULE); if (ret) return ret; return 0; } static void vp7041_frontend_exit(struct usb_vp7041 *vp) { dvb_unregister_frontend(vp7041_ioctl, vp->adapter); } 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))) { pci_free_consistent(NULL,8192,vp->buffer,vp->dma_handle); 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); ret = vp7041_device_init(vp); if (ret) { vp7041_exit(vp); return ret; } ret = vp7041_dvb_init(vp); if (ret) { vp7041_exit(vp); return ret; } ret = vp7041_frontend_init(vp); if (ret) { vp7041_dvb_exit(vp); vp7041_exit(vp); return ret; } 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 ((ret = request_firmware(&fw, firmware_filenames[i], &udev->dev)) == 0) { info("found firmware file (%s).",firmware_filenames[i]); break; } dprintk("Firmware '%s' not found. (%d)\n",firmware_filenames[i],ret); } 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_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_LICENSE("GPL");