diff options
Diffstat (limited to 'drivers/media/dvb/b2c2/b2c2-usb-core.c')
-rw-r--r-- | drivers/media/dvb/b2c2/b2c2-usb-core.c | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/drivers/media/dvb/b2c2/b2c2-usb-core.c b/drivers/media/dvb/b2c2/b2c2-usb-core.c new file mode 100644 index 000000000000..9306da046c91 --- /dev/null +++ b/drivers/media/dvb/b2c2/b2c2-usb-core.c @@ -0,0 +1,549 @@ +/* + * Copyright (C) 2004 Patrick Boettcher <patrick.boettcher@desy.de>, + * Luca Bertagnolio <>, + * + * based on information provided by John Jurrius from BBTI, Inc. + * + * 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. + * + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/usb.h> +#include <linux/moduleparam.h> +#include <linux/pci.h> +#include <linux/version.h> + +#include "dmxdev.h" +#include "dvb_demux.h" +#include "dvb_filter.h" +#include "dvb_net.h" +#include "dvb_frontend.h" + +/* debug */ +#define dprintk(level,args...) \ + do { if ((debug & level)) { printk(args); } } while (0) +#define debug_dump(b,l) if (debug) {\ + int i; deb_xfer("%s: %d > ",__FUNCTION__,l); \ + for (i = 0; i < l; i++) deb_xfer("%02x ", b[i]); \ + deb_xfer("\n");\ +} + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "set debugging level (1=info,ts=2,ctrl=4 (or-able))."); + +#define deb_info(args...) dprintk(0x01,args) +#define deb_ts(args...) dprintk(0x02,args) +#define deb_ctrl(args...) dprintk(0x04,args) + +/* Version information */ +#define DRIVER_VERSION "0.0" +#define DRIVER_DESC "Driver for B2C2/Technisat Air/Cable/Sky-2-PC USB devices" +#define DRIVER_AUTHOR "Patrick Boettcher, patrick.boettcher@desy.de" + +/* transfer parameters */ +#define B2C2_USB_FRAMES_PER_ISO 4 +#define B2C2_USB_NUM_ISO_URB 4 /* TODO check out a good value */ + +#define B2C2_USB_CTRL_PIPE_IN usb_rcvctrlpipe(b2c2->udev,0) +#define B2C2_USB_CTRL_PIPE_OUT usb_sndctrlpipe(b2c2->udev,0) +#define B2C2_USB_DATA_PIPE usb_rcvisocpipe(b2c2->udev,0x81) + +struct usb_b2c2_usb { + struct usb_device *udev; + struct usb_interface *uintf; + + u8 *iso_buffer; + int buffer_size; + dma_addr_t iso_dma_handle; + struct urb *iso_urb[B2C2_USB_NUM_ISO_URB]; +}; + + +/* + * USB + * 10 90 34 12 78 56 04 00 + * usb_control_msg(udev, usb_sndctrlpipe(udev,0), + * 0x90, + * 0x10, + * 0x1234, + * 0x5678, + * buf, + * 4, + * 5*HZ); + * + * extern int usb_control_msg(struct usb_device *dev, unsigned int pipe, + * __u8 request, + * __u8 requesttype, + * __u16 value, + * __u16 index, + * void *data, + * __u16 size, + * int timeout); + * + */ + +/* request types */ +typedef enum { + +/* something is wrong with this part + RTYPE_READ_DW = (1 << 6), + RTYPE_WRITE_DW_1 = (3 << 6), + RTYPE_READ_V8_MEMORY = (6 << 6), + RTYPE_WRITE_V8_MEMORY = (7 << 6), + RTYPE_WRITE_V8_FLASH = (8 << 6), + RTYPE_GENERIC = (9 << 6), +*/ + RTYPE_READ_DW = (3 << 6), + RTYPE_WRITE_DW_1 = (1 << 6), + + RTYPE_READ_V8_MEMORY = (6 << 6), + RTYPE_WRITE_V8_MEMORY = (7 << 6), + RTYPE_WRITE_V8_FLASH = (8 << 6), + RTYPE_GENERIC = (9 << 6), +} b2c2_usb_request_type_t; + +/* request */ +typedef enum { + B2C2_USB_WRITE_V8_MEM = 0x04, + B2C2_USB_READ_V8_MEM = 0x05, + B2C2_USB_READ_REG = 0x08, + B2C2_USB_WRITE_REG = 0x0A, +/* B2C2_USB_WRITEREGLO = 0x0A, */ + B2C2_USB_WRITEREGHI = 0x0B, + B2C2_USB_FLASH_BLOCK = 0x10, + B2C2_USB_I2C_REQUEST = 0x11, + B2C2_USB_UTILITY = 0x12, +} b2c2_usb_request_t; + +/* function definition for I2C_REQUEST */ +typedef enum { + USB_FUNC_I2C_WRITE = 0x01, + USB_FUNC_I2C_MULTIWRITE = 0x02, + USB_FUNC_I2C_READ = 0x03, + USB_FUNC_I2C_REPEATWRITE = 0x04, + USB_FUNC_GET_DESCRIPTOR = 0x05, + USB_FUNC_I2C_REPEATREAD = 0x06, +/* DKT 020208 - add this to support special case of DiSEqC */ + USB_FUNC_I2C_CHECKWRITE = 0x07, + USB_FUNC_I2C_CHECKRESULT = 0x08, +} b2c2_usb_i2c_function_t; + +/* + * function definition for UTILITY request 0x12 + * DKT 020304 - new utility function + */ +typedef enum { + UTILITY_SET_FILTER = 0x01, + UTILITY_DATA_ENABLE = 0x02, + UTILITY_FLEX_MULTIWRITE = 0x03, + UTILITY_SET_BUFFER_SIZE = 0x04, + UTILITY_FLEX_OPERATOR = 0x05, + UTILITY_FLEX_RESET300_START = 0x06, + UTILITY_FLEX_RESET300_STOP = 0x07, + UTILITY_FLEX_RESET300 = 0x08, + UTILITY_SET_ISO_SIZE = 0x09, + UTILITY_DATA_RESET = 0x0A, + UTILITY_GET_DATA_STATUS = 0x10, + UTILITY_GET_V8_REG = 0x11, +/* DKT 020326 - add function for v1.14 */ + UTILITY_SRAM_WRITE = 0x12, + UTILITY_SRAM_READ = 0x13, + UTILITY_SRAM_TESTFILL = 0x14, + UTILITY_SRAM_TESTSET = 0x15, + UTILITY_SRAM_TESTVERIFY = 0x16, +} b2c2_usb_utility_function_t; + +#define B2C2_WAIT_FOR_OPERATION_RW 1 // 1 s +#define B2C2_WAIT_FOR_OPERATION_RDW 3 // 3 s +#define B2C2_WAIT_FOR_OPERATION_WDW 1 // 1 s + +#define B2C2_WAIT_FOR_OPERATION_V8READ 3 // 3 s +#define B2C2_WAIT_FOR_OPERATION_V8WRITE 3 // 3 s +#define B2C2_WAIT_FOR_OPERATION_V8FLASH 3 // 3 s + +/* JLP 111700: we will include the 1 bit gap between the upper and lower 3 bits + * in the IBI address, to make the V8 code simpler. + * PCI ADDRESS FORMAT: 0x71C -> 0000 0111 0001 1100 (these are the six bits used) + * in general: 0000 0HHH 000L LL00 + * IBI ADDRESS FORMAT: RHHH BLLL + * + * where R is the read(1)/write(0) bit, B is the busy bit + * and HHH and LLL are the two sets of three bits from the PCI address. + */ +#define B2C2_FLEX_PCIOFFSET_TO_INTERNALADDR(usPCI) (u8) (((usPCI >> 2) & 0x07) + ((usPCI >> 4) & 0x70)) +#define B2C2_FLEX_INTERNALADDR_TO_PCIOFFSET(ucAddr) (u16) (((ucAddr & 0x07) << 2) + ((ucAddr & 0x70) << 4)) + +/* + * DKT 020228 - forget about this VENDOR_BUFFER_SIZE, read and write register + * deal with DWORD or 4 bytes, that should be should from now on + */ +static u32 b2c2_usb_read_dw(struct usb_b2c2_usb *b2c2, u16 wRegOffsPCI) +{ + u32 val; + u16 wAddress = B2C2_FLEX_PCIOFFSET_TO_INTERNALADDR(wRegOffsPCI) | 0x0080; + int len = usb_control_msg(b2c2->udev, + B2C2_USB_CTRL_PIPE_IN, + B2C2_USB_READ_REG, + RTYPE_READ_DW, + wAddress, + 0, + &val, + sizeof(u32), + B2C2_WAIT_FOR_OPERATION_RDW * 1000); + + if (len != sizeof(u32)) { + err("error while reading dword from %d (%d).",wAddress,wRegOffsPCI); + return -EIO; + } else + return val; +} + +/* + * DKT 020228 - from now on, we don't support anything older than firm 1.00 + * I eliminated the write register as a 2 trip of writing hi word and lo word + * and force this to write only 4 bytes at a time. + * NOTE: this should work with all the firmware from 1.00 and newer + */ +static int b2c2_usb_write_dw(struct usb_b2c2_usb *b2c2, u16 wRegOffsPCI, u32 val) +{ + u16 wAddress = B2C2_FLEX_PCIOFFSET_TO_INTERNALADDR(wRegOffsPCI); + int len = usb_control_msg(b2c2->udev, + B2C2_USB_CTRL_PIPE_OUT, + B2C2_USB_WRITE_REG, + RTYPE_WRITE_DW_1, + wAddress, + 0, + &val, + sizeof(u32), + B2C2_WAIT_FOR_OPERATION_RDW * 1000); + + if (len != sizeof(u32)) { + err("error while reading dword from %d (%d).",wAddress,wRegOffsPCI); + return -EIO; + } else + return 0; +} + +/* + * DKT 010817 - add support for V8 memory read/write and flash update + */ +static int b2c2_usb_v8_memory_req(struct usb_b2c2_usb *b2c2, + b2c2_usb_request_t req, u8 page, u16 wAddress, + u16 buflen, u8 *pbBuffer) +{ + u8 dwRequestType; + u16 wIndex; + int nWaitTime,pipe,len; + + wIndex = page << 8; + + switch (req) { + case B2C2_USB_READ_V8_MEM: + nWaitTime = B2C2_WAIT_FOR_OPERATION_V8READ; + dwRequestType = (u8) RTYPE_READ_V8_MEMORY; + pipe = B2C2_USB_CTRL_PIPE_IN; + break; + case B2C2_USB_WRITE_V8_MEM: + wIndex |= pbBuffer[0]; + nWaitTime = B2C2_WAIT_FOR_OPERATION_V8WRITE; + dwRequestType = (u8) RTYPE_WRITE_V8_MEMORY; + pipe = B2C2_USB_CTRL_PIPE_OUT; + break; + case B2C2_USB_FLASH_BLOCK: + nWaitTime = B2C2_WAIT_FOR_OPERATION_V8FLASH; + dwRequestType = (u8) RTYPE_WRITE_V8_FLASH; + pipe = B2C2_USB_CTRL_PIPE_OUT; + break; + default: + deb_info("unsupported request for v8_mem_req %x.\n",req); + return -EINVAL; + } + len = usb_control_msg(b2c2->udev,pipe, + req, + dwRequestType, + wAddress, + wIndex, + pbBuffer, + buflen, + nWaitTime * 1000); + return len == buflen ? 0 : -EIO; +} + +static int b2c2_usb_i2c_req(struct usb_b2c2_usb *b2c2, + b2c2_usb_request_t req, b2c2_usb_i2c_function_t func, + u8 port, u8 chipaddr, u8 addr, u8 buflen, u8 *buf) +{ + u16 wValue, wIndex; + int nWaitTime,pipe,len; + u8 dwRequestType; + + switch (func) { + case USB_FUNC_I2C_WRITE: + case USB_FUNC_I2C_MULTIWRITE: + case USB_FUNC_I2C_REPEATWRITE: + /* DKT 020208 - add this to support special case of DiSEqC */ + case USB_FUNC_I2C_CHECKWRITE: + pipe = B2C2_USB_CTRL_PIPE_OUT; + nWaitTime = 2; + dwRequestType = (u8) RTYPE_GENERIC; + break; + case USB_FUNC_I2C_READ: + case USB_FUNC_I2C_REPEATREAD: + pipe = B2C2_USB_CTRL_PIPE_IN; + nWaitTime = 2; + dwRequestType = (u8) RTYPE_GENERIC; + break; + default: + deb_info("unsupported function for i2c_req %x\n",func); + return -EINVAL; + } + wValue = (func << 8 ) | port; + wIndex = (chipaddr << 8 ) | addr; + + len = usb_control_msg(b2c2->udev,pipe, + req, + dwRequestType, + addr, + wIndex, + buf, + buflen, + nWaitTime * 1000); + return len == buflen ? 0 : -EIO; +} + +int static b2c2_usb_utility_req(struct usb_b2c2_usb *b2c2, int set, + b2c2_usb_utility_function_t func, u8 extra, u16 wIndex, + u16 buflen, u8 *pvBuffer) +{ + u16 wValue; + int nWaitTime = 2, + pipe = set ? B2C2_USB_CTRL_PIPE_OUT : B2C2_USB_CTRL_PIPE_IN, + len; + + wValue = (func << 8) | extra; + + len = usb_control_msg(b2c2->udev,pipe, + B2C2_USB_UTILITY, + (u8) RTYPE_GENERIC, + wValue, + wIndex, + pvBuffer, + buflen, + nWaitTime * 1000); + return len == buflen ? 0 : -EIO; +} + + + +static void b2c2_dumpfourreg(struct usb_b2c2_usb *b2c2, u16 offs) +{ + u32 r0,r1,r2,r3; + r0 = r1 = r2 = r3 = 0; + r0 = b2c2_usb_read_dw(b2c2,offs); + r1 = b2c2_usb_read_dw(b2c2,offs + 0x04); + r2 = b2c2_usb_read_dw(b2c2,offs + 0x08); + r3 = b2c2_usb_read_dw(b2c2,offs + 0x0c); + deb_ctrl("dump: offset: %03x, %08x, %08x, %08x, %08x\n",offs,r0,r1,r2,r3); +} + +static void b2c2_urb_complete(struct urb *urb, struct pt_regs *ptregs) +{ + struct usb_b2c2_usb *b2c2 = urb->context; + deb_ts("urb completed, bufsize: %d\n",urb->transfer_buffer_length); + +// urb_submit_urb(urb,GFP_ATOMIC); enable for real action +} + +static void b2c2_exit_usb(struct usb_b2c2_usb *b2c2) +{ + int i; + for (i = 0; i < B2C2_USB_NUM_ISO_URB; i++) + if (b2c2->iso_urb[i] != NULL) { /* not sure about unlink_urb and iso-urbs TODO */ + deb_info("unlinking/killing urb no. %d\n",i); +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,7) + usb_unlink_urb(b2c2->iso_urb[i]); +#else + usb_kill_urb(b2c2->iso_urb[i]); +#endif + usb_free_urb(b2c2->iso_urb[i]); + } + + if (b2c2->iso_buffer != NULL) + pci_free_consistent(NULL,b2c2->buffer_size, b2c2->iso_buffer, b2c2->iso_dma_handle); + +} + +static int b2c2_init_usb(struct usb_b2c2_usb *b2c2) +{ + u16 frame_size = le16_to_cpu(b2c2->uintf->cur_altsetting->endpoint[0].desc.wMaxPacketSize); + int bufsize = B2C2_USB_NUM_ISO_URB * B2C2_USB_FRAMES_PER_ISO * frame_size,i,j,ret; + int buffer_offset = 0; + + deb_info("creating %d iso-urbs with %d frames each of %d bytes size = %d.\n", + B2C2_USB_NUM_ISO_URB, B2C2_USB_FRAMES_PER_ISO, frame_size,bufsize); + + b2c2->iso_buffer = pci_alloc_consistent(NULL,bufsize,&b2c2->iso_dma_handle); + if (b2c2->iso_buffer == NULL) + return -ENOMEM; + memset(b2c2->iso_buffer, 0, bufsize); + b2c2->buffer_size = bufsize; + + /* creating iso urbs */ + for (i = 0; i < B2C2_USB_NUM_ISO_URB; i++) + if (!(b2c2->iso_urb[i] = usb_alloc_urb(B2C2_USB_FRAMES_PER_ISO,GFP_ATOMIC))) { + ret = -ENOMEM; + goto urb_error; + } + /* initialising and submitting iso urbs */ + for (i = 0; i < B2C2_USB_NUM_ISO_URB; i++) { + int frame_offset = 0; + struct urb *urb = b2c2->iso_urb[i]; + deb_info("initializing and submitting urb no. %d (buf_offset: %d).\n",i,buffer_offset); + + urb->dev = b2c2->udev; + urb->context = b2c2; + urb->complete = b2c2_urb_complete; + urb->pipe = B2C2_USB_DATA_PIPE; + urb->transfer_flags = URB_ISO_ASAP; + urb->interval = 1; + urb->number_of_packets = B2C2_USB_FRAMES_PER_ISO; + urb->transfer_buffer_length = frame_size * B2C2_USB_FRAMES_PER_ISO; + urb->transfer_buffer = b2c2->iso_buffer + buffer_offset; + + buffer_offset += frame_size * B2C2_USB_FRAMES_PER_ISO; + for (j = 0; j < B2C2_USB_FRAMES_PER_ISO; j++) { + deb_info("urb no: %d, frame: %d, frame_offset: %d\n",i,j,frame_offset); + urb->iso_frame_desc[j].offset = frame_offset; + urb->iso_frame_desc[j].length = frame_size; + frame_offset += frame_size; + } + + if ((ret = usb_submit_urb(b2c2->iso_urb[i],GFP_ATOMIC))) { + err("submitting urb %d failed with %d.",i,ret); + goto urb_error; + } + deb_info("submitted urb no. %d.\n",i); + } + + ret = 0; + goto success; +urb_error: + b2c2_exit_usb(b2c2); +success: + return ret; +} + +static int b2c2_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_b2c2_usb *b2c2 = NULL; + int ret; + + b2c2 = kmalloc(sizeof(struct usb_b2c2_usb),GFP_KERNEL); + if (b2c2 == NULL) { + err("no memory"); + return -ENOMEM; + } + b2c2->udev = udev; + b2c2->uintf = intf; + + /* use the alternate setting with the larges buffer */ + usb_set_interface(udev,0,1); + + if ((ret = b2c2_init_usb(b2c2))) + goto usb_init_error; + + usb_set_intfdata(intf,b2c2); + + switch (udev->speed) { + case USB_SPEED_LOW: + err("cannot handle USB speed because it is to sLOW."); + break; + case USB_SPEED_FULL: + info("running at FULL speed."); + break; + case USB_SPEED_HIGH: + info("running at HIGH speed."); + break; + case USB_SPEED_UNKNOWN: /* fall through */ + default: + err("cannot handle USB speed because it is unkown."); + break; + } + + b2c2_dumpfourreg(b2c2,0x200); + b2c2_dumpfourreg(b2c2,0x300); + b2c2_dumpfourreg(b2c2,0x400); + b2c2_dumpfourreg(b2c2,0x700); + + + if (ret == 0) + info("%s successfully initialized and connected.",DRIVER_DESC); + else + info("%s error while loading driver (%d)",DRIVER_DESC,ret); + + ret = 0; + goto success; + +usb_init_error: + kfree(b2c2); +success: + return ret; +} + +static void b2c2_usb_disconnect(struct usb_interface *intf) +{ + struct usb_b2c2_usb *b2c2 = usb_get_intfdata(intf); + usb_set_intfdata(intf,NULL); + if (b2c2 != NULL) { + b2c2_exit_usb(b2c2); + kfree(b2c2); + } + info("%s successfully deinitialized and disconnected.",DRIVER_DESC); + +} + +static struct usb_device_id b2c2_usb_table [] = { + { USB_DEVICE(0x0af7, 0x0101) } +}; + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver b2c2_usb_driver = { + .owner = THIS_MODULE, + .name = "dvb_b2c2_usb", + .probe = b2c2_usb_probe, + .disconnect = b2c2_usb_disconnect, + .id_table = b2c2_usb_table, +}; + +/* module stuff */ +static int __init b2c2_usb_init(void) +{ + int result; + if ((result = usb_register(&b2c2_usb_driver))) { + err("usb_register failed. Error number %d",result); + return result; + } + + return 0; +} + +static void __exit b2c2_usb_exit(void) +{ + /* deregister this driver from the USB subsystem */ + usb_deregister(&b2c2_usb_driver); +} + +module_init (b2c2_usb_init); +module_exit (b2c2_usb_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(usb, b2c2_usb_table); |