[RFC PATCH 4/4] Microsoft mceusb2 driver for in-kernel IR subsystem
From: Jon Smirl
Date: Mon Sep 29 2008 - 12:18:41 EST
USB device commonly found on Microsoft Media Center boxes.
Hardware can send and recieve at all common IR frequencies - 36K, 38K, 40K, 56K
---
drivers/input/ir/Kconfig | 6
drivers/input/ir/Makefile | 1
drivers/input/ir/ir-mceusb2.c | 729 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 736 insertions(+), 0 deletions(-)
create mode 100644 drivers/input/ir/ir-mceusb2.c
diff --git a/drivers/input/ir/Kconfig b/drivers/input/ir/Kconfig
index b80ab31..29657d0 100644
--- a/drivers/input/ir/Kconfig
+++ b/drivers/input/ir/Kconfig
@@ -16,5 +16,11 @@ config IR_GPT
default m
help
Driver for GPT-based IR receiver found on Digispeaker
+
+config IR_MCEUSB2
+ tristate "Microsoft Media Center Ed. Receiver, v2"
+ default m
+ help
+ Driver for the Microsoft Media Center Ed. Receiver, v2
endif
diff --git a/drivers/input/ir/Makefile b/drivers/input/ir/Makefile
index 7082f1d..780de6c 100644
--- a/drivers/input/ir/Makefile
+++ b/drivers/input/ir/Makefile
@@ -4,3 +4,4 @@
# Each configuration option enables a list of files.
obj-$(CONFIG_IR_GPT) += ir-gpt.o
+obj-$(CONFIG_IR_MCEUSB2) += ir-mceusb2.o
diff --git a/drivers/input/ir/ir-mceusb2.c b/drivers/input/ir/ir-mceusb2.c
new file mode 100644
index 0000000..14f5a53
--- /dev/null
+++ b/drivers/input/ir/ir-mceusb2.c
@@ -0,0 +1,729 @@
+/*
+ * LIRC driver for Philips eHome USB Infrared Transceiver
+ * and the Microsoft MCE 2005 Remote Control
+ *
+ * (C) by Martin A. Blatter <martin_a_blatter@xxxxxxxxx>
+ *
+ * Transmitter support and reception code cleanup.
+ * (C) by Daniel Melander <lirc@xxxxxxxxxx>
+ *
+ * Derived from ATI USB driver by Paul Miller and the original
+ * MCE USB driver by Dan Corti
+ *
+ * This driver will only work reliably with kernel version 2.6.10
+ * or higher, probably because of differences in USB device enumeration
+ * in the kernel code. Device initialization fails most of the time
+ * with earlier kernel versions.
+ *
+ **********************************************************************
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#define DEBUG
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/input.h>
+
+#define DRIVER_AUTHOR "Daniel Melander <lirc@xxxxxxxxxx>, " \
+ "Martin Blatter <martin_a_blatter@xxxxxxxxx>"
+#define DRIVER_DESC "Philips eHome USB IR Transceiver and Microsoft " \
+ "MCE 2005 Remote Control driver"
+#define DRIVER_NAME "ir_mceusb2"
+
+#define USB_BUFLEN 16 /* USB reception buffer length */
+#define LIRCBUF_SIZE 256 /* LIRC work buffer length */
+
+/* MCE constants */
+#define MCE_CMDBUF_SIZE 384 /* MCE Command buffer length */
+#define MCE_TIME_UNIT 50 /* Approx 50us resolution */
+#define MCE_CODE_LENGTH 5 /* Normal length of packet (with header) */
+#define MCE_PACKET_SIZE 4 /* Normal length of packet (without header) */
+#define MCE_PACKET_HEADER 0x84 /* Actual header format is 0x80 + num_bytes */
+#define MCE_CONTROL_HEADER 0x9F /* MCE status header */
+#define MCE_TX_HEADER_LENGTH 3 /* # of bytes in the initializing tx header */
+#define MCE_MAX_CHANNELS 2 /* Two transmitters, hardware dependent? */
+#define MCE_DEFAULT_TX_MASK 0x03 /* Val opts: TX1=0x01, TX2=0x02, ALL=0x03 */
+#define MCE_PULSE_BIT 0x80 /* Pulse bit, MSB set == PULSE else SPACE */
+#define MCE_PULSE_MASK 0x7F /* Pulse mask */
+#define MCE_MAX_PULSE_LENGTH 0x7F /* Longest transmittable pulse symbol */
+#define MCE_PACKET_LENGTH_MASK 0x7 /* Packet length */
+
+
+/* general constants */
+#define SEND_FLAG_IN_PROGRESS 1
+#define SEND_FLAG_COMPLETE 2
+#define RECV_FLAG_IN_PROGRESS 3
+#define RECV_FLAG_COMPLETE 4
+
+#define PHILUSB_RECEIVE 1
+#define PHILUSB_SEND 2
+
+#define VENDOR_PHILIPS 0x0471
+#define VENDOR_SMK 0x0609
+#define VENDOR_TATUNG 0x1460
+#define VENDOR_GATEWAY 0x107b
+#define VENDOR_SHUTTLE 0x1308
+#define VENDOR_SHUTTLE2 0x051c
+#define VENDOR_MITSUMI 0x03ee
+#define VENDOR_TOPSEED 0x1784
+#define VENDOR_RICAVISION 0x179d
+#define VENDOR_ITRON 0x195d
+#define VENDOR_FIC 0x1509
+#define VENDOR_LG 0x043e
+#define VENDOR_MICROSOFT 0x045e
+#define VENDOR_FORMOSA 0x147a
+#define VENDOR_FINTEK 0x1934
+#define VENDOR_PINNACLE 0x2304
+
+static struct usb_device_id usb_remote_table[] = {
+ /* Philips eHome Infrared Transceiver */
+ { USB_DEVICE(VENDOR_PHILIPS, 0x0815) },
+ /* Philips Infrared Transceiver - HP branded */
+ { USB_DEVICE(VENDOR_PHILIPS, 0x060c) },
+ /* Philips SRM5100 */
+ { USB_DEVICE(VENDOR_PHILIPS, 0x060d) },
+ /* Philips Infrared Transceiver - Omaura */
+ { USB_DEVICE(VENDOR_PHILIPS, 0x060f) },
+ /* SMK/Toshiba G83C0004D410 */
+ { USB_DEVICE(VENDOR_SMK, 0x031d) },
+ /* SMK eHome Infrared Transceiver (Sony VAIO) */
+ { USB_DEVICE(VENDOR_SMK, 0x0322) },
+ /* bundled with Hauppauge PVR-150 */
+ { USB_DEVICE(VENDOR_SMK, 0x0334) },
+ /* Tatung eHome Infrared Transceiver */
+ { USB_DEVICE(VENDOR_TATUNG, 0x9150) },
+ /* Shuttle eHome Infrared Transceiver */
+ { USB_DEVICE(VENDOR_SHUTTLE, 0xc001) },
+ /* Shuttle eHome Infrared Transceiver */
+ { USB_DEVICE(VENDOR_SHUTTLE2, 0xc001) },
+ /* Gateway eHome Infrared Transceiver */
+ { USB_DEVICE(VENDOR_GATEWAY, 0x3009) },
+ /* Mitsumi */
+ { USB_DEVICE(VENDOR_MITSUMI, 0x2501) },
+ /* Topseed eHome Infrared Transceiver */
+ { USB_DEVICE(VENDOR_TOPSEED, 0x0001) },
+ /* Topseed HP eHome Infrared Transceiver */
+ { USB_DEVICE(VENDOR_TOPSEED, 0x0006) },
+ /* Topseed eHome Infrared Transceiver */
+ { USB_DEVICE(VENDOR_TOPSEED, 0x0007) },
+ /* Topseed eHome Infrared Transceiver */
+ { USB_DEVICE(VENDOR_TOPSEED, 0x0008) },
+ /* Ricavision internal Infrared Transceiver */
+ { USB_DEVICE(VENDOR_RICAVISION, 0x0010) },
+ /* Itron ione Libra Q-11 */
+ { USB_DEVICE(VENDOR_ITRON, 0x7002) },
+ /* FIC eHome Infrared Transceiver */
+ { USB_DEVICE(VENDOR_FIC, 0x9242) },
+ /* LG eHome Infrared Transceiver */
+ { USB_DEVICE(VENDOR_LG, 0x9803) },
+ /* Microsoft MCE Infrared Transceiver */
+ { USB_DEVICE(VENDOR_MICROSOFT, 0x00a0) },
+ /* Formosa eHome Infrared Transceiver */
+ { USB_DEVICE(VENDOR_FORMOSA, 0xe015) },
+ /* Formosa21 / eHome Infrared Receiver */
+ { USB_DEVICE(VENDOR_FORMOSA, 0xe016) },
+ /* Formosa aim / Trust MCE Infrared Receiver */
+ { USB_DEVICE(VENDOR_FORMOSA, 0xe017) },
+ /* Formosa Industrial Computing / Beanbag Emulation Device */
+ { USB_DEVICE(VENDOR_FORMOSA, 0xe018) },
+ /* Fintek eHome Infrared Transceiver */
+ { USB_DEVICE(VENDOR_FINTEK, 0x0602) },
+ /* Pinnacle Remote Kit */
+ { USB_DEVICE(VENDOR_PINNACLE, 0x0225) },
+ /* Terminating entry */
+ { }
+};
+
+static struct usb_device_id pinnacle_list[] = {
+ { USB_DEVICE(VENDOR_PINNACLE, 0x0225) },
+ {}
+};
+
+static struct usb_device_id xmit_inverted[] = {
+ { USB_DEVICE(VENDOR_SMK, 0x031d) },
+ { USB_DEVICE(VENDOR_SMK, 0x0322) },
+ { USB_DEVICE(VENDOR_SMK, 0x0334) },
+ { USB_DEVICE(VENDOR_TOPSEED, 0x0001) },
+ { USB_DEVICE(VENDOR_TOPSEED, 0x0007) },
+ { USB_DEVICE(VENDOR_TOPSEED, 0x0008) },
+ { USB_DEVICE(VENDOR_PINNACLE, 0x0225) },
+ {}
+};
+
+/* data structure for each usb remote */
+struct irctl {
+
+ /* usb */
+ struct usb_device *usbdev;
+ struct urb *urb_in;
+ struct usb_endpoint_descriptor *usb_ep_in;
+ struct usb_endpoint_descriptor *usb_ep_out;
+
+ /* buffers and dma */
+ unsigned char *buf_in;
+ unsigned int len_in;
+ dma_addr_t dma_in;
+ dma_addr_t dma_out;
+
+ struct {
+ u32 connected:1;
+ u32 pinnacle:1;
+ u32 transmitter_mask_inverted:1;
+ u32 reserved:29;
+ } flags;
+
+ /* handle sending (init strings) */
+ int send_flags;
+
+ int carrier;
+
+ struct {
+ unsigned int command, partial, delta, bit;
+ } last;
+ struct input_dev *input;
+};
+
+/* init strings */
+static char init1[] = {0x00, 0xff, 0xaa, 0xff, 0x0b};
+static char init2[] = {0xff, 0x18};
+
+static char pin_init1[] = { 0x9f, 0x07};
+static char pin_init2[] = { 0x9f, 0x13};
+static char pin_init3[] = { 0x9f, 0x0d};
+
+static void usb_remote_printdata(struct irctl *ir, char *buf, int len)
+{
+#ifdef DEBUG
+ int i;
+
+ if (len <= 0)
+ return;
+
+ dev_dbg(&ir->usbdev->dev, "data received ");
+ for (i = 0; i < len && i < USB_BUFLEN; i++)
+ printk("%02x ", buf[i] & 0xFF);
+ printk("\n");
+#endif
+}
+
+static void usb_send_callback(struct urb *urb, struct pt_regs *regs)
+{
+ struct irctl *ir = urb->context;
+ int len= urb->actual_length;
+
+ dev_dbg(&ir->usbdev->dev, "usb_send_callback (status=%d len=%d)\n", urb->status, len);
+ usb_remote_printdata(ir, urb->transfer_buffer, len);
+}
+
+
+static void usb_receive_callback(struct urb *urb, struct pt_regs *regs)
+{
+ struct irctl *ir = urb->context;
+ int len= urb->actual_length;
+
+ dev_dbg(&ir->usbdev->dev, "usb_receive_callback (status=%d len=%d)\n", urb->status, len);
+ usb_remote_printdata(ir, urb->transfer_buffer, len);
+}
+
+
+/* request incoming or send outgoing usb packet - used to initialize remote */
+static int request_packet_async(struct irctl *ir, struct usb_endpoint_descriptor *ep,
+ unsigned char *data, int size, int urb_type)
+{
+ int res;
+ struct urb *async_urb;
+ unsigned char *async_buf;
+
+ switch (urb_type) {
+ case PHILUSB_SEND:
+ case PHILUSB_RECEIVE:
+ async_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!async_urb) {
+ dev_dbg(&ir->usbdev->dev, "Filed to allocate URB\n");
+ return -ENOMEM;
+ }
+ /* alloc buffer */
+ async_buf = kmalloc(size, GFP_KERNEL);
+ if (!async_buf) {
+ usb_free_urb(async_urb);
+ dev_dbg(&ir->usbdev->dev, "Filed to allocate async_buf\n");
+ return -ENOMEM;
+ }
+
+ if (urb_type == PHILUSB_SEND) {
+ /* outbound data */
+ usb_fill_int_urb(async_urb, ir->usbdev,
+ usb_sndintpipe(ir->usbdev, ep->bEndpointAddress),
+ async_buf, size, (usb_complete_t) usb_send_callback,
+ ir, ep->bInterval);
+
+ memcpy(async_buf, data, size);
+ dev_dbg(&ir->usbdev->dev, "request_packet_async called (size=%#x, send)\n", size);
+ } else {
+ /* inbound data */
+ usb_fill_int_urb(async_urb, ir->usbdev,
+ usb_rcvintpipe(ir->usbdev, ep->bEndpointAddress),
+ async_buf, size, (usb_complete_t) usb_receive_callback,
+ ir, ep->bInterval);
+ dev_dbg(&ir->usbdev->dev, "request_packet_async called (size=%#x, receive)\n", size);
+ }
+ break;
+ default:
+ case 0:
+ /* standard request */
+ async_urb = ir->urb_in;
+ ir->send_flags = RECV_FLAG_IN_PROGRESS;
+ dev_dbg(&ir->usbdev->dev, "request_packet_async called (size=%#x, standard)\n", size);
+ break;
+ }
+
+ async_urb->transfer_buffer_length = size;
+ async_urb->dev = ir->usbdev;
+
+ res = usb_submit_urb(async_urb, GFP_ATOMIC);
+ if (res)
+ dev_dbg(&ir->usbdev->dev, "request_packet_async (res=%d)\n", res);
+ return res;
+}
+
+static void usb_remote_recv(struct urb *urb, struct pt_regs *regs)
+{
+ struct irctl *ir;
+ int buf_len;
+ int i, delta, bit;
+
+ if (!urb)
+ return;
+
+ ir = urb->context;
+ if (!ir) {
+ usb_unlink_urb(urb);
+ return;
+ }
+ buf_len = urb->actual_length;
+
+ usb_remote_printdata(ir, urb->transfer_buffer, buf_len);
+
+ if (ir->send_flags == RECV_FLAG_IN_PROGRESS) {
+ ir->send_flags = SEND_FLAG_COMPLETE;
+ dev_dbg(&ir->usbdev->dev, "setup answer received %d bytes\n", buf_len);
+ }
+ switch (urb->status) {
+ /* success */
+ case 0:
+ for (i = 0; i < buf_len;) {
+ if (ir->last.partial == 0) {
+ /* decode mce packets of the form (84),AA,BB,CC,DD */
+ /* IR data packets can span USB messages - partial */
+ ir->last.partial = (ir->buf_in[i] & MCE_PACKET_LENGTH_MASK);
+ ir->last.command = ir->buf_in[i] & ~MCE_PACKET_LENGTH_MASK;
+ i++;
+ }
+ for (; (ir->last.partial > 0) && (i < buf_len); i++) {
+ ir->last.partial--;
+
+ if (ir->last.command == 0x80) {
+ bit = ((ir->buf_in[i] & MCE_PULSE_BIT) != 0);
+ delta = (ir->buf_in[i] & MCE_PULSE_MASK) * MCE_TIME_UNIT;
+
+ if ((ir->buf_in[i] & MCE_PULSE_MASK) == 0x7f) {
+ if (ir->last.bit == bit)
+ ir->last.delta += delta;
+ else {
+ ir->last.delta = delta;
+ ir->last.bit = bit;
+ }
+ continue;
+ }
+ delta += ir->last.delta;
+ ir->last.delta = 0;
+ ir->last.bit = bit;
+
+ dev_dbg(&ir->usbdev->dev, "bit %d delta %d\n", bit, delta);
+ input_ir_decode(ir->input, delta, bit);
+ }
+ }
+ }
+ break;
+
+ /* unlink */
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ usb_unlink_urb(urb);
+ return;
+
+ case -EPIPE:
+ default:
+ break;
+ }
+
+ /* resubmit urb */
+ usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+
+/* Sets the send carrier frequency */
+static int set_carrier(struct irctl *ir, int carrier)
+{
+ int clk = 10000000;
+ int prescaler = 0, divisor = 0;
+ unsigned char cmdbuf[] = { 0x9F, 0x06, 0x01, 0x80 };
+
+ /* Carrier is changed */
+ if (ir->carrier != carrier) {
+
+ if (carrier <= 0) {
+ ir->carrier = carrier;
+ dev_dbg(&ir->usbdev->dev, "SET_CARRIER disabling carrier modulation\n");
+ request_packet_async(ir, ir->usb_ep_out,
+ cmdbuf, sizeof(cmdbuf), PHILUSB_SEND);
+ return carrier;
+ }
+
+ for (prescaler = 0; prescaler < 4; ++prescaler) {
+ divisor = (clk >> (2 * prescaler)) / carrier;
+ if (divisor <= 0xFF) {
+ ir->carrier = carrier;
+ cmdbuf[2] = prescaler;
+ cmdbuf[3] = divisor;
+ dev_dbg(&ir->usbdev->dev, "SET_CARRIER requesting %d Hz\n", carrier);
+
+ /* Transmit the new carrier to the mce
+ device */
+ request_packet_async(ir, ir->usb_ep_out,
+ cmdbuf, sizeof(cmdbuf), PHILUSB_SEND);
+ return carrier;
+ }
+ }
+ return -EINVAL;
+ }
+ return carrier;
+}
+
+static int send(void *private, unsigned int *buffer, unsigned int count,
+ unsigned int frequency, unsigned int xmitters)
+{
+ struct irctl *ir = (struct irctl *)private;
+
+ int i, cmdcount = 0;
+ unsigned char cmdbuf[MCE_CMDBUF_SIZE]; /* MCE command buffer */
+ unsigned long signal_duration = 0; /* Singnal length in us */
+
+ if (!ir && !ir->usb_ep_out)
+ return -EFAULT;
+
+ set_carrier(ir, frequency);
+
+ /* MCE tx init header */
+ cmdbuf[cmdcount++] = MCE_CONTROL_HEADER;
+ cmdbuf[cmdcount++] = 0x08;
+ cmdbuf[cmdcount++] = (ir->flags.transmitter_mask_inverted ? ~xmitters : xmitters) << 1;
+
+ /* Generate mce packet data */
+ for (i = 0; (i < count) && (cmdcount < MCE_CMDBUF_SIZE); i++) {
+ signal_duration += buffer[i];
+ buffer[i] = buffer[i] / MCE_TIME_UNIT;
+
+ do { /* loop to support long pulses/spaces > 127*50us=6.35ms */
+
+ /* Insert mce packet header every 4th entry */
+ if ((cmdcount < MCE_CMDBUF_SIZE) && (cmdcount - MCE_TX_HEADER_LENGTH) % MCE_CODE_LENGTH == 0)
+ cmdbuf[cmdcount++] = MCE_PACKET_HEADER;
+
+ /* Insert mce packet data */
+ if (cmdcount < MCE_CMDBUF_SIZE)
+ cmdbuf[cmdcount++] = (buffer[i] < MCE_PULSE_BIT ? buffer[i] :
+ MCE_MAX_PULSE_LENGTH) | (i & 1 ? 0x00 : MCE_PULSE_BIT);
+ else
+ return -EINVAL;
+ } while ((buffer[i] > MCE_MAX_PULSE_LENGTH) && (buffer[i] -= MCE_MAX_PULSE_LENGTH));
+ }
+
+ /* Fix packet length in last header */
+ cmdbuf[cmdcount - (cmdcount - MCE_TX_HEADER_LENGTH) % MCE_CODE_LENGTH] =
+ 0x80 + (cmdcount - MCE_TX_HEADER_LENGTH) % MCE_CODE_LENGTH - 1;
+
+ /* Check if we have room for the empty packet at the end */
+ if (cmdcount >= MCE_CMDBUF_SIZE)
+ return -EINVAL;
+
+ /* All mce commands end with an empty packet (0x80) */
+ cmdbuf[cmdcount++] = 0x80;
+
+ /* Transmit the command to the mce device */
+ request_packet_async(ir, ir->usb_ep_out, cmdbuf,
+ cmdcount, PHILUSB_SEND);
+
+ return 0;
+}
+
+static int usb_remote_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *dev = interface_to_usbdev(intf);
+ struct usb_host_interface *idesc;
+ struct usb_endpoint_descriptor *ep = NULL;
+ struct usb_endpoint_descriptor *ep_in = NULL;
+ struct usb_endpoint_descriptor *ep_out = NULL;
+ struct usb_host_config *config;
+ struct irctl *ir = NULL;
+ int i, devnum, pipe, maxp, ret, is_pinnacle;
+ char name[128];
+
+ dev_dbg(&dev->dev, "usb probe called\n");
+
+ usb_reset_device(dev);
+
+ config = dev->actconfig;
+
+ idesc = intf->cur_altsetting;
+
+ is_pinnacle = usb_match_id(intf, pinnacle_list) ? 1 : 0;
+
+ /* step through the endpoints to find first bulk in and out endpoint */
+ for (i = 0; i < idesc->desc.bNumEndpoints; ++i) {
+ ep = &idesc->endpoint[i].desc;
+
+ if ((ep_in == NULL)
+ && ((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN)
+ && (((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK)
+ || ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT))) {
+
+ dev_dbg(&dev->dev, "acceptable inbound endpoint found\n");
+ ep_in = ep;
+ ep_in->bmAttributes = USB_ENDPOINT_XFER_INT;
+
+ /*
+ * setting seems to 1 seem to cause issues with
+ * Pinnacle timing out on transfer.
+ */
+ if (is_pinnacle)
+ ep_in->bInterval = ep->bInterval;
+ else
+ ep_in->bInterval = 1;
+ }
+ if ((ep_out == NULL)
+ && ((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT)
+ && (((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK)
+ || ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT))) {
+
+ dev_dbg(&dev->dev, "acceptable outbound endpoint found\n");
+ ep_out = ep;
+ ep_out->bmAttributes = USB_ENDPOINT_XFER_INT;
+
+ /*
+ * setting seems to 1 seem to cause issues with
+ * Pinnacle timing out on transfer.
+ */
+ if (is_pinnacle)
+ ep_out->bInterval = ep->bInterval;
+ else
+ ep_out->bInterval = 1;
+ }
+ }
+ if (ep_in == NULL) {
+ dev_dbg(&dev->dev, "inbound and/or endpoint not found\n");
+ return -ENODEV;
+ }
+ devnum = dev->devnum;
+ pipe = usb_rcvintpipe(dev, ep_in->bEndpointAddress);
+ maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
+
+ /* allocate kernel memory */
+ ir = kzalloc(sizeof(struct irctl), GFP_KERNEL);
+ if (!ir)
+ return -ENOMEM;
+
+ ir->buf_in = usb_buffer_alloc(dev, maxp, GFP_ATOMIC, &ir->dma_in);
+ if (!ir->buf_in) {
+ ret = -ENOMEM;
+ goto free_mem;
+ }
+ ir->urb_in = usb_alloc_urb(0, GFP_KERNEL);
+ if (!ir->urb_in) {
+ ret = -ENOMEM;
+ goto free_buffer;
+ }
+
+ ir->usbdev = dev;
+ ir->len_in = maxp;
+ ir->flags.connected = 0;
+ ir->flags.pinnacle = is_pinnacle;
+ ir->flags.transmitter_mask_inverted =
+ usb_match_id(intf, xmit_inverted) ? 0 : 1;
+
+ /* Saving usb interface data for use by the transmitter routine */
+ ir->usb_ep_in = ep_in;
+ ir->usb_ep_out = ep_out;
+
+ name[0] = '\0';
+ if (dev->descriptor.iManufacturer)
+ usb_string(dev, dev->descriptor.iManufacturer, name, sizeof(name));
+ i = strlen(name);
+ name[i++] = ' ';
+ if (dev->descriptor.iProduct)
+ usb_string(dev, dev->descriptor.iProduct, &name[i], sizeof(name) - i);
+ dev_info(&dev->dev, "%s\n", name);
+
+ /* inbound data */
+ usb_fill_int_urb(ir->urb_in, dev, pipe, ir->buf_in,
+ maxp, (usb_complete_t) usb_remote_recv, ir, ep_in->bInterval);
+
+ /* initialize device */
+ if (ir->flags.pinnacle) {
+ int usbret;
+
+ /*
+ * I have no idea why but this reset seems to be crucial to
+ * getting the device to do outbound IO correctly - without
+ * this the device seems to hang, ignoring all input - although
+ * IR signals are correctly sent from the device, no input is
+ * interpreted by the device and the host never does the
+ * completion routine
+ */
+
+ usbret = usb_reset_configuration(dev);
+ dev_info(&dev->dev, "usb reset config ret %x\n", usbret);
+
+ /*
+ * its possible we really should wait for a return
+ * for each of these...
+ */
+ request_packet_async(ir, ep_in, NULL, maxp, PHILUSB_RECEIVE);
+ request_packet_async(ir, ep_out, pin_init1, sizeof(pin_init1), PHILUSB_SEND);
+ request_packet_async(ir, ep_in, NULL, maxp, PHILUSB_RECEIVE);
+ request_packet_async(ir, ep_out, pin_init2, sizeof(pin_init2), PHILUSB_SEND);
+ request_packet_async(ir, ep_in, NULL, maxp, PHILUSB_RECEIVE);
+ request_packet_async(ir, ep_out, pin_init3, sizeof(pin_init3), PHILUSB_SEND);
+ /* if we dont issue the correct number of receives
+ * (PHILUSB_RECEIVE) for each outbound, then the first few ir
+ * pulses will be interpreted by the usb_async_callback routine
+ * - we should ensure we have the right amount OR less - as the
+ * usb_remote_recv routine will handle the control packets OK -
+ * they start with 0x9f - but the async callback doesnt handle
+ * ir pulse packets
+ */
+ request_packet_async(ir, ep_in, NULL, maxp, 0);
+ } else {
+ request_packet_async(ir, ep_in, NULL, maxp, PHILUSB_RECEIVE);
+ request_packet_async(ir, ep_out, init1, sizeof(init1), PHILUSB_SEND);
+ request_packet_async(ir, ep_in, NULL, maxp, PHILUSB_RECEIVE);
+ request_packet_async(ir, ep_out, init2, sizeof(init2), PHILUSB_SEND);
+ request_packet_async(ir, ep_in, NULL, maxp, 0);
+ }
+ usb_set_intfdata(intf, ir);
+
+ ir->input = input_allocate_device();
+ if (!ir->input) {
+ ret = -ENOMEM;
+ goto free_urb;
+ }
+ ret = input_ir_create(ir->input, ir, send);
+ if (ret)
+ goto free_input;
+
+ ir->input->name = " Microsoft MCE2 IR Transceiver";
+ ret = input_register_device(ir->input);
+ if (ret)
+ goto free_input;
+
+ return 0;
+
+free_input:
+ input_ir_destroy(ir->input);
+ input_free_device(ir->input);
+free_urb:
+ usb_free_urb(ir->urb_in);
+free_buffer:
+ usb_buffer_free(dev, maxp, ir->buf_in, ir->dma_in);
+free_mem:
+ kfree(ir);
+ dev_err(&dev->dev, "failed to load (code=%d)\n", ret);
+ return ret;
+}
+
+
+static void usb_remote_disconnect(struct usb_interface *intf)
+{
+ struct usb_device *dev = interface_to_usbdev(intf);
+ struct irctl *ir = usb_get_intfdata(intf);
+
+ usb_set_intfdata(intf, NULL);
+
+ if (!ir)
+ return;
+
+ ir->usbdev = NULL;
+
+ usb_kill_urb(ir->urb_in);
+ usb_free_urb(ir->urb_in);
+ usb_buffer_free(dev, ir->len_in, ir->buf_in, ir->dma_in);
+}
+
+static int usb_remote_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct irctl *ir = usb_get_intfdata(intf);
+ dev_dbg(&ir->usbdev->dev, "suspend\n");
+ usb_kill_urb(ir->urb_in);
+ return 0;
+}
+
+static int usb_remote_resume(struct usb_interface *intf)
+{
+ struct irctl *ir = usb_get_intfdata(intf);
+ dev_dbg(&ir->usbdev->dev, "resume\n");
+ if (usb_submit_urb(ir->urb_in, GFP_ATOMIC))
+ return -EIO;
+ return 0;
+}
+
+static struct usb_driver usb_remote_driver = {
+ .name = DRIVER_NAME,
+ .probe = usb_remote_probe,
+ .disconnect = usb_remote_disconnect,
+ .suspend = usb_remote_suspend,
+ .resume = usb_remote_resume,
+ .id_table = usb_remote_table
+};
+
+static int __init usb_remote_init(void)
+{
+ int ret;
+
+ ret = usb_register(&usb_remote_driver);
+ if (ret < 0) {
+ printk(DRIVER_NAME ": usb register failed, result = %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static void __exit usb_remote_exit(void)
+{
+ usb_deregister(&usb_remote_driver);
+}
+
+module_init(usb_remote_init);
+module_exit(usb_remote_exit);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(usb, usb_remote_table);
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/