On Sun, Dec 06, 2009 at 04:45:43PM -0500, Chaogui Zhang wrote: > Hi, > > This is a new driver for supporting the TiVo USB IR Dongle that is included in > the NERO Liquid TV package. The USB IR receiver behaves very much like the MCE > USB receiver, with some minor differences. > > I am aware of the current discussion regarding IR integration into the kernel > and I have been following that debate with great interest myself. However, this > is a simple and self-contained driver. It is based, in part, on the keyspan DMR > driver currently in the input system. I hope this would be useful for others while > the debate is ongoing. Once the final decision is made regarding IR integration > into the kernel, then it can be modified and/or integrated into whatever model is > agreed upon. > > Although the hardware is capable of decoding almost any ir signal from any remote, > the current implementation will only accept the bundled TiVo remote (all other ir > signals that might be passing by would be ignored by the driver). Once the IR debate > settles, this device can potentially be configured to accept any ir remote signal > in the new kernel IR model. > Hi, Dmitry, This is a resubmit of the new TiVo IR dongle driver I sent in a few days ago. Sorry that I forgot to cc my last message to you. I revised the code and this patch should safely ignore any non-TiVo remote. As I mentioned, I hope this would be useful for anyone who might have this receiver. Given the current debate on the kernel IR integration, it will be fine if you would like to wait until the dust settles. Please let me know what you think. Thank you very much! Signed-off-by: Chaogui Zhang <czhang@xxxxxxxxxxxx> --- diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index a9bb254..57ae574 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -148,6 +148,19 @@ config INPUT_KEYSPAN_REMOTE To compile this driver as a module, choose M here: the module will be called keyspan_remote. +config INPUT_TIVOIR + tristate "TiVo USB IR Dongle (EXPERIMENTAL)" + depends on EXPERIMENTAL + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use the TiVo USB IR Dongle. It works with + the bundled TiVo remote and this driver maps all buttons to keypress + events. + + To compile this driver as a module, choose M here: the module will + be called tivoir. + config INPUT_POWERMATE tristate "Griffin PowerMate and Contour Jog support" depends on USB_ARCH_HAS_HCD diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index a8b8485..b449048 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER) += rotary_encoder.o obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o +obj-$(CONFIG_INPUT_TIVOIR) += tivoir.o obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o obj-$(CONFIG_INPUT_UINPUT) += uinput.o obj-$(CONFIG_INPUT_WINBOND_CIR) += winbond-cir.o diff --git a/drivers/input/misc/tivoir.c b/drivers/input/misc/tivoir.c new file mode 100644 index 0000000..bc946c5 --- /dev/null +++ b/drivers/input/misc/tivoir.c @@ -0,0 +1,580 @@ +/* + * tivoir.c: Input driver for the USB TiVo PC IR Dongle + * + * Copyright (C) 2009 Chaogui Zhang (czhang@xxxxxxxxxxxx) + * + * Based in part on the Keyspan DMR driver (keyspan_remote.c) by + * Michael Downey (downey@xxxxxxxxxx) + * + * 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/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/usb/input.h> + +#define DRIVER_VERSION "v0.1" +#define DRIVER_AUTHOR "Chaogui Zhang <czhang@xxxxxxxxxxxx>" +#define DRIVER_DESC "Driver for the TiVo PC IR Dongle." +#define DRIVER_LICENSE "GPL" + +/* Parameters that can be passed to the driver. */ +static int debug; +module_param(debug, int, 0444); +MODULE_PARM_DESC(debug, "Enable extra debug messages and information"); + +/* Vendor and product ids */ +#define USB_TIVOIR_VENDOR_ID 0x105A +#define USB_TIVOIR_PRODUCT_ID 0x2000 +#define TIVO_REMOTE_ADDR 0x3085 + +#define PULSE_BIT 0x80 /* Pulse is indicated by a 1 in the highest bit */ +#define PULSE_MASK 0x7f /* Lower 7 bits is the length of the pulse transmitted */ +#define RECV_SIZE 32 /* TiVo IR Dongle has a transfer limit of 32 bytes. */ + +/* + * Table that maps the remote keycodes to input keys. + * The comments are the labels on the TiVo remote that came with the dongle. + */ + +static const struct { + u8 code; + u16 key; +} tivoir_key_table[] = { + { 0x09, KEY_MENU }, /* TiVo Logo */ + { 0x10, KEY_POWER2 }, /* TV Power */ + { 0x11, KEY_TV }, /* Live TV/Swap */ + { 0x13, KEY_INFO }, + { 0x14, KEY_UP }, + { 0x15, KEY_RIGHT }, + { 0x16, KEY_DOWN }, + { 0x17, KEY_LEFT }, + { 0x18, KEY_RED }, /* Thumb down */ + { 0x19, KEY_SELECT }, + { 0x1a, KEY_GREEN }, /* Thumb up */ + { 0x1b, KEY_MUTE }, + { 0x1c, KEY_VOLUMEUP }, + { 0x1d, KEY_VOLUMEDOWN }, + { 0x1e, KEY_CHANNELUP }, + { 0x1f, KEY_CHANNELDOWN }, + { 0x20, KEY_RECORD }, + { 0x21, KEY_PLAY }, + { 0x22, KEY_REWIND }, + { 0x23, KEY_PAUSE }, + { 0x24, KEY_FASTFORWARD }, + { 0x25, KEY_SLOW }, + { 0x26, KEY_FRAMEBACK }, /* TiVo quick replay */ + { 0x27, KEY_FRAMEFORWARD }, /* Skip */ + { 0x28, KEY_1 }, + { 0x29, KEY_2 }, + { 0x2a, KEY_3 }, + { 0x2b, KEY_4 }, + { 0x2c, KEY_5 }, + { 0x2d, KEY_6 }, + { 0x2e, KEY_7 }, + { 0x2f, KEY_8 }, + { 0x30, KEY_9 }, + { 0x31, KEY_0 }, + { 0x32, KEY_CLEAR }, + { 0x33, KEY_ENTER }, + { 0x34, KEY_VIDEO }, /* TV Input */ + { 0x36, KEY_EPG }, /* Guide */ + { 0x44, KEY_ZOOM }, /* Aspect */ + { 0x48, KEY_STOP }, + { 0x4a, KEY_DVD }, /* DVD Menu */ + { 0x5f, KEY_CYCLEWINDOWS } /* Window */ +}; + +/* table of devices that work with this driver */ +static struct usb_device_id tivoir_table[] = { + {USB_DEVICE(USB_TIVOIR_VENDOR_ID, USB_TIVOIR_PRODUCT_ID)}, + {} /* Terminating entry */ +}; + +/* Structure to hold all of our driver specific stuff */ +struct usb_tivoir { + char name[128]; + char phys[64]; + unsigned short keymap[ARRAY_SIZE(tivoir_key_table)]; + struct usb_device *udev; + struct input_dev *input; + struct usb_interface *interface; + struct usb_endpoint_descriptor *in_endpoint; + struct urb *irq_urb; + int open; + dma_addr_t in_dma; + unsigned char *in_buffer; + + /* variables used to parse messages from remote. */ + int stage; + int pulse; + int space; + u32 code; /* 32 bit raw code from the remote */ + int repeat; + int bitcount; +}; + +static struct usb_driver tivoir_driver; + +/* + * Debug routine that prints out what we've received from the remote. + */ +static void tivoir_print_packet(struct usb_tivoir *remote) +{ + u8 codes[4 * RECV_SIZE]; + int i, length; + + /* The lower 5 bits of the first byte of each packet indicates the size + * of the transferred buffer, not including the first byte itself. + */ + + length = (remote->in_buffer[0]) & 0x1f; + for (i = 0; i <= length; i++) + snprintf(codes + i * 3, 4, "%02x ", remote->in_buffer[i]); + + /* 0x80 at the end of a regular packet or in a separate packet + indicates key release */ + + if (i < RECV_SIZE && remote->in_buffer[i] == 0x80) + snprintf(codes + i * 3, 4, "%02x ", remote->in_buffer[i]); + + dev_info(&remote->udev->dev, "%s: %s\n", __func__, codes); +} + +static inline u16 code_address(u32 code) +{ + return code >> 16; /* Higher 16 bits of the code is the remote address */ +} + +static inline u8 code_command(u32 code) +{ + return code & 0xff; /* Lower 8 bits of the code is the command */ +} + +static int tivoir_lookup(u8 code) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(tivoir_key_table); i++) { + if (tivoir_key_table[i].code == code) + return tivoir_key_table[i].key; + if (tivoir_key_table[i].code > code) + return -1; + } + + return -1; +} + +static void tivoir_report_key(struct usb_tivoir *remote) +{ + struct input_dev *input = remote->input; + u16 key; + + if (debug) + dev_info(&remote->udev->dev, "%s: Remote address = 0x%04x, command = 0x%02x\n", + __func__, remote->code >> 16, remote->code & 0xff); + if (code_address(remote->code) == TIVO_REMOTE_ADDR) { + key = tivoir_lookup(code_command(remote->code)); + if (key < 0) { /* invalid code, do nothing */ + remote->code = 0; + return; + } + input_report_key(input, key, remote->repeat); + input_sync(input); + } else { + if (debug) + dev_info(&remote->udev->dev, "%s: Mismatch of remote address.\n", __func__); + remote->code = 0; + } +} + +static inline int is_pulse(u8 code) +{ + return code & PULSE_BIT; +} + +/* Check the inital AGC burst and space value to match the NEC protocol */ +static inline int is_nec(int leadpulse, int leadspace) +{ + /* leadpulse should be 9 ms = 9000 us and leadspace should be + * 4.5 ms = 4500 us. We allow +/- 200 microseconds for both. + * Time is measured in units of 50 microseconds. + * 170 == 8800/50, 184 == 9200/50, + * 86 == 4300/50, 94 == 4700/50. + */ + return (leadpulse >= 170 && leadpulse <= 184) + && (leadspace >= 86 && leadspace <= 94); +} + +/* Routine that resets the remote data to clean state */ +static inline void reset_remote(struct usb_tivoir *remote) { + remote->stage = 0; + remote->pulse = 0; + remote->space = 0; + remote->bitcount = 0; + remote->code = 0; + remote->repeat = 0; +} + +/* Routine that decode pulse/space value into one NEC logic bit */ +static int nec_bit(int pulse, int space) { + /* Check that pulse is between 0.450ms and 0.650ms (NEC protocol says 0.560ms) */ + if (pulse < 9 || pulse > 14) + return -1; + + /* Space value about 1.690ms (about 33 * 50 micro seconds) indicates a 1 bit. + * Space value about 0.560ms (about 11 * 50 micro seconds) indicates a 0 bit. + */ + if (space >= 30 && space <= 35) { + return 1; /* logic one */ + } + if (space >= 9 && space <= 14) { + return 0; /* logic zero */ + } + + return -1; /* Inappropriate space value for NEC */ +} + +/* + * Routine that processes each data packet coming in from the remote. + */ +static void tivoir_process_packet(struct usb_tivoir *remote) +{ + int i, length, bit; + u8 code; + + /* Lower 5 bits of the first byte is the length of the packet */ + length = (remote->in_buffer[0]) & 0x1f; + + if (length == 0) { + remote->repeat = 0; + if(remote->code != 0) tivoir_report_key(remote); + reset_remote(remote); + return; + } + + for (i = 1; i <= length; i++) { + code = remote->in_buffer[i]; + if (remote->stage == 0) { + if (is_pulse(code)) { + remote->pulse += code & PULSE_MASK; + } else { + remote->space += code; + if (is_nec(remote->pulse, remote->space)) { + /* Get ready to receive the code */ + remote->stage = 1; + remote->pulse = 0; + remote->space = 0; + } else { /* Non NEC remote, ignore the rest + * wait for stop signal + */ + if(debug) dev_info(&remote->udev->dev, "%s: Non NEC remote.\n", + __func__); + remote->stage = 2; + } + } + continue; + } + if (remote->stage == 1) { + if (is_pulse(code)) + remote->pulse = code & PULSE_MASK; + else + remote->space = code; + + if(remote->pulse == 0 || remote->space == 0) /* pulse/space not filled in yet */ + continue; + + bit = nec_bit(remote->pulse, remote->space); + + /* reset pulse/space value after decoding */ + remote->pulse = 0; + remote->space = 0; + + if(bit < 0) { /* Non NEC remote, ignore the rest and wait for stop signal */ + remote->stage = 2; + continue; + } + + /* A logic 1 or 0 bit detected, store it in remote->code. + * First 16 bits are the remote address, LSB first. + * Last 16 bits are the remote command, LSB first. + * We save the address in the higher 16 bits in remote->code + * and the command in the lower 16 bits in remote->code. + */ + if (remote->bitcount < 16) + bit = bit << (remote->bitcount + 16); + else + bit = bit << (remote->bitcount - 16); + remote->code |= bit; + remote->bitcount++; + + if (remote->bitcount == 32) { + /* Received all 32 bits from the remote, report the key pressed */ + remote->repeat = 1; + tivoir_report_key(remote); + remote->stage = 2; + } + continue; + } + if (remote->stage == 2) { /* waiting for stop signal */ + if (code == 0x5f) { /* beginning of stop signal, followed by 0x80 */ + if(i+1 < RECV_SIZE && remote->in_buffer[i+1] == 0x80) + remote->repeat = 0; + if(remote->code != 0) tivoir_report_key(remote); + reset_remote(remote); + } + } + + } +} + +/* + * Routine used to handle a new packet that has come in. + */ +static void tivoir_irq_recv(struct urb *urb) +{ + struct usb_tivoir *dev = urb->context; + int i, retval; + + /* Check our status in case we need to bail out early. */ + switch (urb->status) { + case 0: + break; + + /* Device went away so don't keep trying to read from it. */ + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + return; + + default: + goto resubmit; + break; + } + + if (debug) + tivoir_print_packet(dev); + tivoir_process_packet(dev); + + for (i = 0; i < RECV_SIZE; i++) + dev->in_buffer[i] = 0; + +resubmit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + err("%s - usb_submit_urb failed with result: %d", __func__, + retval); +} + +static int tivoir_open(struct input_dev *dev) +{ + struct usb_tivoir *remote = input_get_drvdata(dev); + + remote->irq_urb->dev = remote->udev; + if (usb_submit_urb(remote->irq_urb, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static void tivoir_close(struct input_dev *dev) +{ + struct usb_tivoir *remote = input_get_drvdata(dev); + + usb_kill_urb(remote->irq_urb); +} + +static struct usb_endpoint_descriptor *tivoir_get_in_endpoint(struct usb_host_interface *iface) +{ + struct usb_endpoint_descriptor *endpoint; + int i; + + for (i = 0; i < iface->desc.bNumEndpoints; ++i) { + endpoint = &iface->endpoint[i].desc; + + if (usb_endpoint_is_int_in(endpoint)) { + /* we found our interrupt in endpoint */ + return endpoint; + } + } + + return NULL; +} + +/* + * Routine that sets up the driver to handle a specific USB device detected on the bus. + */ +static int tivoir_probe(struct usb_interface *interface, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_endpoint_descriptor *endpoint; + struct usb_tivoir *remote; + struct input_dev *input_dev; + int i, error; + + endpoint = tivoir_get_in_endpoint(interface->cur_altsetting); + if (!endpoint) + return -ENODEV; + + /* The interface descriptor has invalid bInterval setting 0x00 and the usb core + * driver sets it to the default of 32ms, which is too big and causes data loss. + * Set it to 16ms here. + */ + endpoint->bInterval = 16; + + remote = kzalloc(sizeof(*remote), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!remote || !input_dev) { + error = -ENOMEM; + goto fail1; + } + + remote->udev = udev; + remote->input = input_dev; + remote->interface = interface; + remote->in_endpoint = endpoint; + + remote->in_buffer = + usb_buffer_alloc(udev, RECV_SIZE, GFP_ATOMIC, &remote->in_dma); + if (!remote->in_buffer) { + error = -ENOMEM; + goto fail1; + } + + remote->irq_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!remote->irq_urb) { + error = -ENOMEM; + goto fail2; + } + + if (udev->manufacturer) + strlcpy(remote->name, udev->manufacturer, sizeof(remote->name)); + + if (udev->product) { + if (udev->manufacturer) + strlcat(remote->name, " ", sizeof(remote->name)); + strlcat(remote->name, udev->product, sizeof(remote->name)); + } + + if (!strlen(remote->name)) + snprintf(remote->name, sizeof(remote->name), + "USB TiVo PC IR Dongle %04x:%04x", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + + usb_make_path(udev, remote->phys, sizeof(remote->phys)); + strlcat(remote->phys, "/input0", sizeof(remote->phys)); + memcpy(remote->keymap, tivoir_key_table, sizeof(remote->keymap)); + + input_dev->name = remote->name; + input_dev->phys = remote->phys; + usb_to_input_id(udev, &input_dev->id); + input_dev->dev.parent = &interface->dev; + input_dev->keycode = remote->keymap; + input_dev->keycodesize = sizeof(unsigned short); + input_dev->keycodemax = ARRAY_SIZE(remote->keymap); + + set_bit(EV_KEY, input_dev->evbit); + for (i = 0; i < ARRAY_SIZE(tivoir_key_table); i++) + set_bit(tivoir_key_table[i].key, input_dev->keybit); + clear_bit(KEY_RESERVED, input_dev->keybit); + + input_set_drvdata(input_dev, remote); + + input_dev->open = tivoir_open; + input_dev->close = tivoir_close; + + /* + * Initialize the URB to access the device. + * The urb gets sent to the device in tivoir_open() + */ + usb_fill_int_urb(remote->irq_urb, + remote->udev, + usb_rcvintpipe(remote->udev, + endpoint->bEndpointAddress), + remote->in_buffer, RECV_SIZE, tivoir_irq_recv, remote, + endpoint->bInterval); + remote->irq_urb->transfer_dma = remote->in_dma; + remote->irq_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* we can register the device now, as it is ready */ + error = input_register_device(remote->input); + if (error) + goto fail3; + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, remote); + + return 0; + +fail3: usb_free_urb(remote->irq_urb); +fail2: usb_buffer_free(udev, RECV_SIZE, remote->in_buffer, + remote->in_dma); +fail1: kfree(remote); + input_free_device(input_dev); + + return error; +} + +/* + * Routine called when a device is disconnected from the USB. + */ +static void tivoir_disconnect(struct usb_interface *interface) +{ + struct usb_tivoir *remote; + + remote = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + + if (remote) { + /* We have a valid driver structure so clean up everything we allocated. */ + input_unregister_device(remote->input); + usb_kill_urb(remote->irq_urb); + usb_free_urb(remote->irq_urb); + usb_buffer_free(remote->udev, RECV_SIZE, remote->in_buffer, + remote->in_dma); + kfree(remote); + } +} + +/* + * Standard driver set up sections + */ +static struct usb_driver tivoir_driver = { + .name = "tivoir", + .probe = tivoir_probe, + .disconnect = tivoir_disconnect, + .id_table = tivoir_table +}; + +static int __init usb_tivoir_init(void) +{ + int result; + + /* register this driver with the USB subsystem */ + result = usb_register(&tivoir_driver); + if (result) + err("usb_register failed. Error number %d\n", result); + + return result; +} + +static void __exit usb_tivoir_exit(void) +{ + /* deregister this driver with the USB subsystem */ + usb_deregister(&tivoir_driver); +} + +module_init(usb_tivoir_init); +module_exit(usb_tivoir_exit); + +MODULE_DEVICE_TABLE(usb, tivoir_table); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENSE); -- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html