[PATCH 04/18] lirc driver for 2nd-gen and later Media Center Ed. USB IR transceivers

From: Jarod Wilson
Date: Tue Sep 09 2008 - 00:09:11 EST


Successfully tested with the mce v2 receiver and remote that shipped with my
Hauppauge HVR-1500 expresscard tuner.

Signed-off-by: Jarod Wilson <jarod@xxxxxxxxxx>
Signed-off-by: Janne Grunau <j@xxxxxxxxxx>
CC: Christoph Bartelmus <lirc@xxxxxxxxxxxx>
Tested-by: Jarod Wilson <jarod@xxxxxxxxxx>
---
drivers/input/lirc/Kconfig | 7 +
drivers/input/lirc/Makefile | 1 +
drivers/input/lirc/lirc_mceusb2.c | 1119 +++++++++++++++++++++++++++++++++++++
3 files changed, 1127 insertions(+), 0 deletions(-)
create mode 100644 drivers/input/lirc/lirc_mceusb2.c

diff --git a/drivers/input/lirc/Kconfig b/drivers/input/lirc/Kconfig
index 7dbf1a5..0a0b059 100644
--- a/drivers/input/lirc/Kconfig
+++ b/drivers/input/lirc/Kconfig
@@ -25,6 +25,13 @@ config LIRC_MCEUSB
help
Driver for the Microsoft Media Center Ed. Receiver, v1

+config LIRC_MCEUSB2
+ tristate "Microsoft Media Center Ed. Receiver, v2"
+ default n
+ depends on LIRC_DEV
+ help
+ Driver for the Microsoft Media Center Ed. Receiver, v2
+
config LIRC_SERIAL
tristate "Homebrew Serial Port Receiver"
default n
diff --git a/drivers/input/lirc/Makefile b/drivers/input/lirc/Makefile
index fafdd25..f6accb9 100644
--- a/drivers/input/lirc/Makefile
+++ b/drivers/input/lirc/Makefile
@@ -7,4 +7,5 @@ EXTRA_CFLAGS =-DIRCTL_DEV_MAJOR=61 -DLIRC_SERIAL_TRANSMITTER -I$(src)

obj-$(CONFIG_LIRC_DEV) += lirc_dev.o
obj-$(CONFIG_LIRC_MCEUSB) += lirc_mceusb.o
+obj-$(CONFIG_LIRC_MCEUSB2) += lirc_mceusb2.o
obj-$(CONFIG_LIRC_SERIAL) += lirc_serial.o
diff --git a/drivers/input/lirc/lirc_mceusb2.c b/drivers/input/lirc/lirc_mceusb2.c
new file mode 100644
index 0000000..1e683d3
--- /dev/null
+++ b/drivers/input/lirc/lirc_mceusb2.c
@@ -0,0 +1,1119 @@
+/*
+ * 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
+ *
+ */
+
+#include <linux/version.h>
+
+#include <linux/autoconf.h>
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kmod.h>
+#include <linux/smp_lock.h>
+#include <linux/completion.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/poll.h>
+#include <linux/wait.h>
+#include <linux/time.h>
+
+#include "lirc.h"
+#include "lirc_dev.h"
+
+#define DRIVER_VERSION "1.48"
+#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 for LIRC"
+#define DRIVER_NAME "lirc_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 0x7F /* Pulse mask */
+
+
+/* module parameters */
+#ifdef CONFIG_USB_DEBUG
+static int debug = 1;
+#else
+static int debug;
+#endif
+#define dprintk(fmt, args...) \
+ do { \
+ if (debug) \
+ printk(KERN_DEBUG fmt, ## args); \
+ } while (0)
+
+/* lock irctl structure */
+#define IRLOCK mutex_lock(&ir->lock)
+#define IRUNLOCK mutex_unlock(&ir->lock)
+
+/* general constants */
+#define SUCCESS 0
+#define SEND_FLAG_IN_PROGRESS 1
+#define SEND_FLAG_COMPLETE 2
+#define RECV_FLAG_IN_PROGRESS 3
+#define RECV_FLAG_COMPLETE 4
+
+#define PHILUSB_INBOUND 1
+#define PHILUSB_OUTBOUND 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 transmitter_mask_list[] = {
+ { 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;
+ int devnum;
+ 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;
+
+ /* lirc */
+ struct lirc_plugin *p;
+ int lircdata;
+ unsigned char is_pulse;
+ struct {
+ u32 connected:1;
+ u32 pinnacle:1;
+ u32 transmitter_mask_inverted:1;
+ u32 reserved:29;
+ } flags;
+
+ unsigned char transmitter_mask;
+ unsigned int carrier_freq;
+
+ /* handle sending (init strings) */
+ int send_flags;
+ wait_queue_head_t wait_out;
+
+ struct mutex lock;
+};
+
+/* 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)
+{
+ char codes[USB_BUFLEN*3 + 1];
+ int i;
+
+ if (len <= 0)
+ return;
+
+ for (i = 0; i < len && i < USB_BUFLEN; i++)
+ snprintf(codes+i*3, 4, "%02x ", buf[i] & 0xFF);
+
+ printk(KERN_INFO "" DRIVER_NAME "[%d]: data received %s (length=%d)\n",
+ ir->devnum, codes, len);
+}
+
+static void usb_async_callback(struct urb *urb, struct pt_regs *regs)
+{
+ struct irctl *ir;
+ int len;
+
+ if (!urb)
+ return;
+
+ ir = urb->context;
+ if (ir) {
+ len = urb->actual_length;
+
+ dprintk(DRIVER_NAME
+ "[%d]: callback called (status=%d len=%d)\n",
+ ir->devnum, urb->status, len);
+
+ if (debug)
+ usb_remote_printdata(ir, urb->transfer_buffer, len);
+ }
+
+}
+
+
+/* request incoming or send outgoing usb packet - used to initialize remote */
+static void 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;
+
+ if (urb_type) {
+ async_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (async_urb) {
+ /* alloc buffer */
+ async_buf = kmalloc(size, GFP_KERNEL);
+ if (async_buf) {
+ if (urb_type == PHILUSB_OUTBOUND) {
+ /* outbound data */
+ usb_fill_int_urb(async_urb, ir->usbdev,
+ usb_sndintpipe(ir->usbdev,
+ ep->bEndpointAddress),
+ async_buf,
+ size,
+ (usb_complete_t) usb_async_callback,
+ ir, ep->bInterval);
+
+ memcpy(async_buf, data, 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_async_callback,
+ ir, ep->bInterval);
+ }
+ } else {
+ usb_free_urb(async_urb);
+ return;
+ }
+ }
+ } else {
+ /* standard request */
+ async_urb = ir->urb_in;
+ ir->send_flags = RECV_FLAG_IN_PROGRESS;
+ }
+ dprintk(DRIVER_NAME "[%d]: receive request called (size=%#x)\n",
+ ir->devnum, size);
+
+ async_urb->transfer_buffer_length = size;
+ async_urb->dev = ir->usbdev;
+
+ res = usb_submit_urb(async_urb, GFP_ATOMIC);
+ if (res) {
+ dprintk(DRIVER_NAME "[%d]: receive request FAILED! (res=%d)\n",
+ ir->devnum, res);
+ return;
+ }
+ dprintk(DRIVER_NAME "[%d]: receive request complete (res=%d)\n",
+ ir->devnum, res);
+}
+
+static int unregister_from_lirc(struct irctl *ir)
+{
+ struct lirc_plugin *p = ir->p;
+ int devnum;
+ int rtn;
+
+ devnum = ir->devnum;
+ dprintk(DRIVER_NAME "[%d]: unregister from lirc called\n", devnum);
+
+ rtn = lirc_unregister_plugin(p->minor);
+ if (rtn > 0) {
+ printk(DRIVER_NAME "[%d]: error in lirc_unregister minor: %d\n"
+ "Trying again...\n", devnum, p->minor);
+ if (rtn == -EBUSY) {
+ printk(DRIVER_NAME
+ "[%d]: device is opened, will unregister"
+ " on close\n", devnum);
+ return -EAGAIN;
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(HZ);
+
+ rtn = lirc_unregister_plugin(p->minor);
+ if (rtn > 0)
+ printk(DRIVER_NAME "[%d]: lirc_unregister failed\n",
+ devnum);
+ }
+
+ if (rtn != SUCCESS) {
+ printk(DRIVER_NAME "[%d]: didn't free resources\n", devnum);
+ return -EAGAIN;
+ }
+
+ printk(DRIVER_NAME "[%d]: usb remote disconnected\n", devnum);
+
+ lirc_buffer_free(p->rbuf);
+ kfree(p->rbuf);
+ kfree(p);
+ kfree(ir);
+ return SUCCESS;
+}
+
+static int set_use_inc(void *data)
+{
+ struct irctl *ir = data;
+
+ if (!ir) {
+ printk(DRIVER_NAME "[?]: set_use_inc called with no context\n");
+ return -EIO;
+ }
+ dprintk(DRIVER_NAME "[%d]: set use inc\n", ir->devnum);
+
+ if (!ir->flags.connected) {
+ if (!ir->usbdev)
+ return -ENOENT;
+ ir->flags.connected = 1;
+ }
+
+ return SUCCESS;
+}
+
+static void set_use_dec(void *data)
+{
+ struct irctl *ir = data;
+
+ if (!ir) {
+ printk(DRIVER_NAME "[?]: set_use_dec called with no context\n");
+ return;
+ }
+ dprintk(DRIVER_NAME "[%d]: set use dec\n", ir->devnum);
+
+ if (ir->flags.connected) {
+ IRLOCK;
+ ir->flags.connected = 0;
+ IRUNLOCK;
+ }
+}
+
+static void send_packet_to_lirc(struct irctl *ir)
+{
+ if (ir->lircdata != 0) {
+ lirc_buffer_write_1(ir->p->rbuf,
+ (unsigned char *) &ir->lircdata);
+ wake_up(&ir->p->rbuf->wait_poll);
+ ir->lircdata = 0;
+ }
+}
+
+static void usb_remote_recv(struct urb *urb, struct pt_regs *regs)
+{
+ struct irctl *ir;
+ int buf_len, packet_len;
+ int i, j;
+
+ if (!urb)
+ return;
+
+ ir = urb->context;
+ if (!ir) {
+ usb_unlink_urb(urb);
+ return;
+ }
+
+ buf_len = urb->actual_length;
+ packet_len = 0;
+
+ if (debug)
+ usb_remote_printdata(ir, urb->transfer_buffer, buf_len);
+
+ if (ir->send_flags == RECV_FLAG_IN_PROGRESS) {
+ ir->send_flags = SEND_FLAG_COMPLETE;
+ dprintk(DRIVER_NAME "[%d]: setup answer received %d bytes\n",
+ ir->devnum, buf_len);
+ }
+
+ switch (urb->status) {
+ /* success */
+ case SUCCESS:
+ for (i = 0; i < buf_len; i++) {
+ /* decode mce packets of the form (84),AA,BB,CC,DD */
+ switch (ir->buf_in[i]) {
+
+ /* data headers */
+ case 0x90: /* used Pinnacle Remote Kit */
+ case 0x8F:
+ case 0x8E:
+ case 0x8D:
+ case 0x8C:
+ case 0x8B:
+ case 0x8A:
+ case 0x89:
+ case 0x88:
+ case 0x87:
+ case 0x86:
+ case 0x85:
+ case 0x84:
+ case 0x83:
+ case 0x82:
+ case 0x81:
+ case 0x80:
+ /* decode packet data */
+ packet_len = ir->buf_in[i] &
+ MCE_PACKET_LENGTH_MASK;
+ for (j = 1;
+ j <= packet_len && (i+j < buf_len);
+ j++) {
+ /* rising/falling flank */
+ if (ir->is_pulse !=
+ (ir->buf_in[i + j] &
+ MCE_PULSE_BIT)) {
+ send_packet_to_lirc(ir);
+ ir->is_pulse =
+ ir->buf_in[i + j] &
+ MCE_PULSE_BIT;
+ }
+
+ /* accumulate mce pulse/space values */
+ ir->lircdata +=
+ (ir->buf_in[i + j] &
+ MCE_PULSE_MASK)*MCE_TIME_UNIT;
+ ir->lircdata |=
+ (ir->is_pulse ? PULSE_BIT : 0);
+ }
+
+ i += packet_len;
+ break;
+
+ /* status header (0x9F) */
+ case MCE_CONTROL_HEADER:
+ /* A transmission containing one or
+ more consecutive ir commands always
+ ends with a GAP of 100ms followed by the
+ sequence 0x9F 0x01 0x01 0x9F 0x15
+ 0x00 0x00 0x80 */
+
+ /*
+ Uncomment this if the last 100ms
+ "infinity"-space should be transmitted
+ to lirc directly instead of at the beginning
+ of the next transmission. Changes pulse/space order.
+
+ if (++i < buf_len && ir->buf_in[i]==0x01)
+ send_packet_to_lirc(ir);
+
+ */
+
+ /* end decode loop */
+ i = buf_len;
+ break;
+ default:
+ break;
+ }
+ }
+
+ 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);
+}
+
+
+static ssize_t lirc_write(struct file *file, const char *buf,
+ size_t n, loff_t *ppos)
+{
+ int i, count = 0, cmdcount = 0;
+ struct irctl *ir = NULL;
+ int wbuf[LIRCBUF_SIZE]; /* Workbuffer with values from lirc */
+ unsigned char cmdbuf[MCE_CMDBUF_SIZE]; /* MCE command buffer */
+ unsigned long signal_duration = 0; /* Singnal length in us */
+ struct timeval start_time, end_time;
+
+ do_gettimeofday(&start_time);
+
+ /* Retrieve lirc_plugin data for the device */
+ ir = lirc_get_pdata(file);
+ if (!ir && !ir->usb_ep_out)
+ return -EFAULT;
+
+ if (n % sizeof(int))
+ return -EINVAL;
+ count = n / sizeof(int);
+
+ /* Check if command is within limits */
+ if (count > LIRCBUF_SIZE || count%2 == 0)
+ return -EINVAL;
+ if (copy_from_user(wbuf, buf, n))
+ return -EFAULT;
+
+ /* MCE tx init header */
+ cmdbuf[cmdcount++] = MCE_CONTROL_HEADER;
+ cmdbuf[cmdcount++] = 0x08;
+ cmdbuf[cmdcount++] = ir->transmitter_mask;
+
+ /* Generate mce packet data */
+ for (i = 0; (i < count) && (cmdcount < MCE_CMDBUF_SIZE); i++) {
+ signal_duration += wbuf[i];
+ wbuf[i] = wbuf[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++] =
+ (wbuf[i] < MCE_PULSE_BIT ?
+ wbuf[i] : MCE_MAX_PULSE_LENGTH) |
+ (i & 1 ? 0x00 : MCE_PULSE_BIT);
+ else
+ return -EINVAL;
+ } while ((wbuf[i] > MCE_MAX_PULSE_LENGTH) &&
+ (wbuf[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_OUTBOUND);
+
+ /* The lircd gap calculation expects the write function to
+ wait the time it takes for the ircommand to be sent before
+ it returns. */
+ do_gettimeofday(&end_time);
+ signal_duration -= (end_time.tv_usec - start_time.tv_usec) +
+ (end_time.tv_sec - start_time.tv_sec) * 1000000;
+
+ /* delay with the closest number of ticks */
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(usecs_to_jiffies(signal_duration));
+
+ return n;
+}
+
+static void set_transmitter_mask(struct irctl *ir, unsigned int mask)
+{
+ if (ir->flags.transmitter_mask_inverted)
+ ir->transmitter_mask = (mask != 0x03 ? mask ^ 0x03 : mask) << 1;
+ else
+ ir->transmitter_mask = mask;
+}
+
+
+/* Sets the send carrier frequency */
+static int set_send_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_freq != carrier) {
+
+ if (carrier <= 0) {
+ ir->carrier_freq = carrier;
+ dprintk(DRIVER_NAME "[%d]: SET_CARRIER disabling "
+ "carrier modulation\n", ir->devnum);
+ request_packet_async(ir, ir->usb_ep_out,
+ cmdbuf, sizeof(cmdbuf),
+ PHILUSB_OUTBOUND);
+ return carrier;
+ }
+
+ for (prescaler = 0; prescaler < 4; ++prescaler) {
+ divisor = (clk >> (2 * prescaler)) / carrier;
+ if (divisor <= 0xFF) {
+ ir->carrier_freq = carrier;
+ cmdbuf[2] = prescaler;
+ cmdbuf[3] = divisor;
+ dprintk(DRIVER_NAME "[%d]: SET_CARRIER "
+ "requesting %d Hz\n",
+ ir->devnum, carrier);
+
+ /* Transmit the new carrier to the mce
+ device */
+ request_packet_async(ir, ir->usb_ep_out,
+ cmdbuf, sizeof(cmdbuf),
+ PHILUSB_OUTBOUND);
+ return carrier;
+ }
+ }
+
+ return -EINVAL;
+
+ }
+
+ return carrier;
+}
+
+
+static int lirc_ioctl(struct inode *node, struct file *filep,
+ unsigned int cmd, unsigned long arg)
+{
+ int result;
+ unsigned int ivalue;
+ unsigned long lvalue;
+ struct irctl *ir = NULL;
+
+ /* Retrieve lirc_plugin data for the device */
+ ir = lirc_get_pdata(filep);
+ if (!ir && !ir->usb_ep_out)
+ return -EFAULT;
+
+
+ switch (cmd) {
+ case LIRC_SET_TRANSMITTER_MASK:
+
+ result = get_user(ivalue, (unsigned int *) arg);
+ if (result)
+ return result;
+ switch (ivalue) {
+ case 0x01: /* Transmitter 1 => 0x04 */
+ case 0x02: /* Transmitter 2 => 0x02 */
+ case 0x03: /* Transmitter 1 & 2 => 0x06 */
+ set_transmitter_mask(ir, ivalue);
+ break;
+
+ default: /* Unsupported transmitter mask */
+ return MCE_MAX_CHANNELS;
+ }
+
+ dprintk(DRIVER_NAME ": SET_TRANSMITTERS mask=%d\n", ivalue);
+ break;
+
+ case LIRC_GET_SEND_MODE:
+
+ result = put_user(LIRC_SEND2MODE(LIRC_CAN_SEND_PULSE &
+ LIRC_CAN_SEND_MASK),
+ (unsigned long *) arg);
+
+ if (result)
+ return result;
+ break;
+
+ case LIRC_SET_SEND_MODE:
+
+ result = get_user(lvalue, (unsigned long *) arg);
+
+ if (result)
+ return result;
+ if (lvalue != (LIRC_MODE_PULSE&LIRC_CAN_SEND_MASK))
+ return -EINVAL;
+ break;
+
+ case LIRC_SET_SEND_CARRIER:
+
+ result = get_user(ivalue, (unsigned int *) arg);
+ if (result)
+ return result;
+
+ set_send_carrier(ir, ivalue);
+ break;
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ return 0;
+}
+
+static struct file_operations lirc_fops = {
+ .write = lirc_write,
+};
+
+
+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;
+ struct lirc_plugin *plugin = NULL;
+ struct lirc_buffer *rbuf = NULL;
+ int devnum, pipe, maxp;
+ int minor = 0;
+ int i;
+ char buf[63], name[128] = "";
+ int mem_failure = 0;
+ int is_pinnacle;
+
+ dprintk(DRIVER_NAME ": 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))) {
+
+ dprintk(DRIVER_NAME ": acceptable inbound endpoint "
+ "found\n");
+ ep_in = ep;
+ ep_in->bmAttributes = USB_ENDPOINT_XFER_INT;
+ if (is_pinnacle)
+ /*
+ * setting seems to 1 seem to cause issues with
+ * Pinnacle timing out on transfer.
+ */
+ 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))) {
+
+ dprintk(DRIVER_NAME ": acceptable outbound endpoint "
+ "found\n");
+ ep_out = ep;
+ ep_out->bmAttributes = USB_ENDPOINT_XFER_INT;
+ if (is_pinnacle)
+ /*
+ * setting seems to 1 seem to cause issues with
+ * Pinnacle timing out on transfer.
+ */
+ ep_out->bInterval = ep->bInterval;
+ else
+ ep_out->bInterval = 1;
+ }
+ }
+ if (ep_in == NULL) {
+ dprintk(DRIVER_NAME ": 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 */
+ mem_failure = 0;
+ ir = kmalloc(sizeof(struct irctl), GFP_KERNEL);
+ if (!ir) {
+ mem_failure = 1;
+ goto mem_failure_switch;
+ }
+
+ memset(ir, 0, sizeof(struct irctl));
+
+ plugin = kmalloc(sizeof(struct lirc_plugin), GFP_KERNEL);
+ if (!plugin) {
+ mem_failure = 2;
+ goto mem_failure_switch;
+ }
+
+ rbuf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL);
+ if (!rbuf) {
+ mem_failure = 3;
+ goto mem_failure_switch;
+ }
+
+ if (lirc_buffer_init(rbuf, sizeof(int), LIRCBUF_SIZE)) {
+ mem_failure = 4;
+ goto mem_failure_switch;
+ }
+
+ ir->buf_in = usb_buffer_alloc(dev, maxp, GFP_ATOMIC, &ir->dma_in);
+ if (!ir->buf_in) {
+ mem_failure = 5;
+ goto mem_failure_switch;
+ }
+
+ ir->urb_in = usb_alloc_urb(0, GFP_KERNEL);
+ if (!ir->urb_in) {
+ mem_failure = 7;
+ goto mem_failure_switch;
+ }
+
+ memset(plugin, 0, sizeof(struct lirc_plugin));
+
+ strcpy(plugin->name, DRIVER_NAME " ");
+ plugin->minor = -1;
+ plugin->features = LIRC_CAN_SEND_PULSE |
+ LIRC_CAN_SET_TRANSMITTER_MASK |
+ LIRC_CAN_REC_MODE2 |
+ LIRC_CAN_SET_SEND_CARRIER;
+ plugin->data = ir;
+ plugin->rbuf = rbuf;
+ plugin->set_use_inc = &set_use_inc;
+ plugin->set_use_dec = &set_use_dec;
+ plugin->code_length = sizeof(int) * 8;
+ plugin->ioctl = lirc_ioctl;
+ plugin->fops = &lirc_fops;
+ plugin->dev = &dev->dev;
+ plugin->owner = THIS_MODULE;
+
+ mutex_init(&ir->lock);
+ init_waitqueue_head(&ir->wait_out);
+
+ minor = lirc_register_plugin(plugin);
+ if (minor < 0)
+ mem_failure = 9;
+
+mem_failure_switch:
+
+ /* free allocated memory incase of failure */
+ switch (mem_failure) {
+ case 9:
+ usb_free_urb(ir->urb_in);
+ case 7:
+ usb_buffer_free(dev, maxp, ir->buf_in, ir->dma_in);
+ case 5:
+ lirc_buffer_free(rbuf);
+ case 4:
+ kfree(rbuf);
+ case 3:
+ kfree(plugin);
+ case 2:
+ kfree(ir);
+ case 1:
+ printk(DRIVER_NAME "[%d]: out of memory (code=%d)\n",
+ devnum, mem_failure);
+ return -ENOMEM;
+ }
+
+ plugin->minor = minor;
+ ir->p = plugin;
+ ir->devnum = devnum;
+ 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, transmitter_mask_list) ? 0 : 1;
+
+ ir->lircdata = PULSE_MASK;
+ ir->is_pulse = 0;
+
+ /* ir->flags.transmitter_mask_inverted must be set */
+ set_transmitter_mask(ir, MCE_DEFAULT_TX_MASK);
+ /* Saving usb interface data for use by the transmitter routine */
+ ir->usb_ep_in = ep_in;
+ ir->usb_ep_out = ep_out;
+
+ if (dev->descriptor.iManufacturer
+ && usb_string(dev, dev->descriptor.iManufacturer, buf, 63) > 0)
+ strncpy(name, buf, 128);
+ if (dev->descriptor.iProduct
+ && usb_string(dev, dev->descriptor.iProduct, buf, 63) > 0)
+ snprintf(name, 128, "%s %s", name, buf);
+ printk(DRIVER_NAME "[%d]: %s on usb%d:%d\n", devnum, name,
+ dev->bus->busnum, devnum);
+
+ /* 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);
+ printk(DRIVER_NAME "[%d]: usb reset config ret %x\n",
+ devnum, usbret);
+
+ /*
+ * its possible we really should wait for a return
+ * for each of these...
+ */
+ request_packet_async(ir, ep_in, NULL, maxp, PHILUSB_INBOUND);
+ request_packet_async(ir, ep_out, pin_init1, sizeof(pin_init1),
+ PHILUSB_OUTBOUND);
+ request_packet_async(ir, ep_in, NULL, maxp, PHILUSB_INBOUND);
+ request_packet_async(ir, ep_out, pin_init2, sizeof(pin_init2),
+ PHILUSB_OUTBOUND);
+ request_packet_async(ir, ep_in, NULL, maxp, PHILUSB_INBOUND);
+ request_packet_async(ir, ep_out, pin_init3, sizeof(pin_init3),
+ PHILUSB_OUTBOUND);
+ /* if we dont issue the correct number of receives
+ * (PHILUSB_INBOUND) 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_INBOUND);
+ request_packet_async(ir, ep_in, NULL, maxp, PHILUSB_INBOUND);
+ request_packet_async(ir, ep_out, init1,
+ sizeof(init1), PHILUSB_OUTBOUND);
+ request_packet_async(ir, ep_in, NULL, maxp, PHILUSB_INBOUND);
+ request_packet_async(ir, ep_out, init2,
+ sizeof(init2), PHILUSB_OUTBOUND);
+ request_packet_async(ir, ep_in, NULL, maxp, 0);
+ }
+
+ usb_set_intfdata(intf, ir);
+
+ return SUCCESS;
+}
+
+
+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 || !ir->p)
+ return;
+
+ ir->usbdev = NULL;
+ wake_up_all(&ir->wait_out);
+
+ IRLOCK;
+ 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);
+ IRUNLOCK;
+
+ unregister_from_lirc(ir);
+}
+
+static int usb_remote_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct irctl *ir = usb_get_intfdata(intf);
+ printk(DRIVER_NAME "[%d]: suspend\n", ir->devnum);
+ usb_kill_urb(ir->urb_in);
+ return 0;
+}
+
+static int usb_remote_resume(struct usb_interface *intf)
+{
+ struct irctl *ir = usb_get_intfdata(intf);
+ printk(DRIVER_NAME "[%d]: resume\n", ir->devnum);
+ 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
+};
+
+#ifdef MODULE
+static int __init usb_remote_init(void)
+{
+ int i;
+
+ printk(KERN_INFO "\n");
+ printk(KERN_INFO DRIVER_NAME ": " DRIVER_DESC " " DRIVER_VERSION "\n");
+ printk(KERN_INFO DRIVER_NAME ": " DRIVER_AUTHOR "\n");
+ dprintk(DRIVER_NAME ": debug mode enabled\n");
+
+ request_module("lirc_dev");
+
+ i = usb_register(&usb_remote_driver);
+ if (i < 0) {
+ printk(DRIVER_NAME ": usb register failed, result = %d\n", i);
+ return -ENODEV;
+ }
+
+ return SUCCESS;
+}
+
+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);
+
+module_param(debug, bool, 0644);
+MODULE_PARM_DESC(debug, "Debug enabled or not");
+#endif /* MODULE */
--
1.6.0.1

--
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/