lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <200910201000.00372.jarod@redhat.com>
Date:	Tue, 20 Oct 2009 10:00:00 -0400
From:	Jarod Wilson <jarod@...hat.com>
To:	linux-kernel@...r.kernel.org
Cc:	linux-input@...r.kernel.org, linux-media@...r.kernel.org,
	Janne Grunau <j@...nau.net>,
	Christoph Bartelmus <lirc@...telmus.de>
Subject: [PATCH 2/3 v2] lirc driver for Windows MCE IR transceivers

lirc driver for Windows Media Center Ed. IR transceivers

Successfully tested with the mce v2 transceiver and remote that shipped with a
Hauppauge HVR-1500 expresscard tuner and an mce v1 transceiver from an old HP
Media Center system.

Changes from prior submission:
- both v1 and v2 transceivers supported by one driver now
- transmit works on the v1 devices
- support for several new devices
- now uses dev_dbg (and friends) instead of its own dprintk

Signed-off-by: Jarod Wilson <jarod@...hat.com>
Signed-off-by: Janne Grunau <j@...nau.net>
CC: Christoph Bartelmus <lirc@...telmus.de>
Tested-by: Jarod Wilson <jarod@...hat.com>

---
 drivers/input/lirc/Kconfig       |    6 
 drivers/input/lirc/Makefile      |    1 
 drivers/input/lirc/lirc_mceusb.c | 1235 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 1241 insertions(+), 1 deletion(-)

Index: b/drivers/input/lirc/Kconfig
===================================================================
--- a/drivers/input/lirc/Kconfig
+++ b/drivers/input/lirc/Kconfig
@@ -11,6 +11,10 @@ menuconfig INPUT_LIRC
 
 if INPUT_LIRC
 
-# Device-specific drivers go here
+config LIRC_MCEUSB
+	tristate "Windows Media Center Ed. USB IR Transceiver"
+	depends on LIRC_DEV && USB
+	help
+	  Driver for Windows Media Center Ed. USB IR Transceivers
 
 endif
Index: b/drivers/input/lirc/Makefile
===================================================================
--- a/drivers/input/lirc/Makefile
+++ b/drivers/input/lirc/Makefile
@@ -4,3 +4,4 @@
 # Each configuration option enables a list of files.
 
 obj-$(CONFIG_INPUT_LIRC)	+= lirc_dev.o
+obj-$(CONFIG_LIRC_MCEUSB)	+= lirc_mceusb.o
Index: b/drivers/input/lirc/lirc_mceusb.c
===================================================================
--- /dev/null
+++ b/drivers/input/lirc/lirc_mceusb.c
@@ -0,0 +1,1235 @@
+/*
+ * LIRC driver for Windows Media Center Edition USB Infrared Transceivers
+ *
+ * (C) by Martin A. Blatter <martin_a_blatter@...oo.com>
+ *
+ * Transmitter support and reception code cleanup.
+ * (C) by Daniel Melander <lirc@...idae.se>
+ *
+ * Original lirc_mceusb driver for 1st-gen device:
+ * Copyright (c) 2003-2004 Dan Conti <dconti@....wwu.edu>
+ *
+ * Original lirc_mceusb driver deprecated in favor of this driver, which
+ * supports the 1st-gen device now too. Transmitting on the 1st-gen device
+ * only functions on port #2 at the moment.
+ *
+ * Support for 1st-gen device added June 2009,
+ * by Jarod Wilson <jarod@...sonet.com>
+ *
+ * Initial transmission support for 1st-gen device added August 2009,
+ * by Patrick Calhoun <phineas@...edu>
+ *
+ * Derived from ATI USB driver by Paul Miller and the original
+ * MCE USB driver by Dan Conti (and now including chunks of the latter
+ * relevant to the 1st-gen device initialization)
+ *
+ *
+ * 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/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/wait.h>
+#include <linux/time.h>
+
+#include "lirc.h"
+#include "lirc_dev.h"
+
+#define DRIVER_VERSION	"1.90"
+#define DRIVER_AUTHOR	"Daniel Melander <lirc@...idae.se>, " \
+			"Martin Blatter <martin_a_blatter@...oo.com>, " \
+			"Dan Conti <dconti@....wwu.edu>"
+#define DRIVER_DESC	"Windows Media Center Edition USB IR Transceiver " \
+			"driver for LIRC"
+#define DRIVER_NAME	"lirc_mceusb"
+
+#define USB_BUFLEN	32	/* 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
+
+/* 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 MCEUSB_INBOUND		1
+#define MCEUSB_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
+#define VENDOR_ECS		0x1019
+#define VENDOR_WISTRON		0x0fb8
+#define VENDOR_COMPRO		0x185b
+#define VENDOR_NORTHSTAR	0x04eb
+
+static struct usb_device_id mceusb_dev_table[] = {
+	/* Original Microsoft MCE IR Transceiver (often HP-branded) */
+	{ USB_DEVICE(VENDOR_MICROSOFT, 0x006d) },
+	/* Philips Infrared Transceiver - Sahara branded */
+	{ USB_DEVICE(VENDOR_PHILIPS, 0x0608) },
+	/* 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) },
+	/* Philips Infrared Transceiver - Spinel plus */
+	{ USB_DEVICE(VENDOR_PHILIPS, 0x0613) },
+	/* Philips eHome Infrared Transceiver */
+	{ USB_DEVICE(VENDOR_PHILIPS, 0x0815) },
+	/* 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) },
+	/* Topseed eHome Infrared Transceiver */
+	{ USB_DEVICE(VENDOR_TOPSEED, 0x000a) },
+	/* 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) },
+	/* Formosa21 / eHome Infrared Receiver */
+	{ USB_DEVICE(VENDOR_FORMOSA, 0xe03a) },
+	/* Formosa Industrial Computing AIM IR605/A */
+	{ USB_DEVICE(VENDOR_FORMOSA, 0xe03c) },
+	/* Fintek eHome Infrared Transceiver */
+	{ USB_DEVICE(VENDOR_FINTEK, 0x0602) },
+	/* Fintek eHome Infrared Transceiver (in the AOpen MP45) */
+	{ USB_DEVICE(VENDOR_FINTEK, 0x0702) },
+	/* Pinnacle Remote Kit */
+	{ USB_DEVICE(VENDOR_PINNACLE, 0x0225) },
+	/* Elitegroup Computer Systems IR */
+	{ USB_DEVICE(VENDOR_ECS, 0x0f38) },
+	/* Wistron Corp. eHome Infrared Receiver */
+	{ USB_DEVICE(VENDOR_WISTRON, 0x0002) },
+	/* Compro K100 */
+	{ USB_DEVICE(VENDOR_COMPRO, 0x3020) },
+	/* Compro K100 v2 */
+	{ USB_DEVICE(VENDOR_COMPRO, 0x3082) },
+	/* Northstar Systems, Inc. eHome Infrared Transceiver */
+	{ USB_DEVICE(VENDOR_NORTHSTAR, 0xe004) },
+	/* Terminating entry */
+	{ }
+};
+
+static struct usb_device_id pinnacle_list[] = {
+	{ USB_DEVICE(VENDOR_PINNACLE, 0x0225) },
+	{}
+};
+
+static struct usb_device_id microsoft_gen1_list[] = {
+	{ USB_DEVICE(VENDOR_MICROSOFT, 0x006d) },
+	{}
+};
+
+static struct usb_device_id transmitter_mask_list[] = {
+	{ USB_DEVICE(VENDOR_MICROSOFT, 0x006d) },
+	{ USB_DEVICE(VENDOR_SMK, 0x031d) },
+	{ USB_DEVICE(VENDOR_SMK, 0x0322) },
+	{ USB_DEVICE(VENDOR_SMK, 0x0334) },
+	{ USB_DEVICE(VENDOR_TOPSEED, 0x0001) },
+	{ USB_DEVICE(VENDOR_TOPSEED, 0x0006) },
+	{ USB_DEVICE(VENDOR_TOPSEED, 0x0007) },
+	{ USB_DEVICE(VENDOR_TOPSEED, 0x0008) },
+	{ USB_DEVICE(VENDOR_TOPSEED, 0x000a) },
+	{ USB_DEVICE(VENDOR_PINNACLE, 0x0225) },
+	{}
+};
+
+/* data structure for each usb transceiver */
+struct mceusb_dev {
+
+	/* 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;
+	unsigned int overflow_len;
+
+	/* lirc */
+	struct lirc_driver *d;
+	int lircdata;
+	unsigned char is_pulse;
+	struct {
+		u32 connected:1;
+		u32 pinnacle:1;
+		u32 transmitter_mask_inverted:1;
+		u32 microsoft_gen1:1;
+		u32 reserved:28;
+	} 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 mceusb_dev_printdata(struct mceusb_dev *ir, char *buf, int len)
+{
+	char codes[USB_BUFLEN * 3 + 1];
+	int i;
+
+	if (len <= 0)
+		return;
+
+	if (ir->flags.microsoft_gen1 && len <= 2)
+		return;
+
+	for (i = 0; i < len && i < USB_BUFLEN; i++)
+		snprintf(codes + i * 3, 4, "%02x ", buf[i] & 0xFF);
+
+	dev_info(ir->d->dev, "data received %s (length=%d)\n", codes, len);
+}
+
+static void usb_async_callback(struct urb *urb, struct pt_regs *regs)
+{
+	struct mceusb_dev *ir;
+	int len;
+
+	if (!urb)
+		return;
+
+	ir = urb->context;
+	if (ir) {
+		len = urb->actual_length;
+
+		dev_dbg(ir->d->dev, "callback called (status=%d len=%d)\n",
+			urb->status, len);
+
+		if (debug)
+			mceusb_dev_printdata(ir, urb->transfer_buffer, len);
+	}
+
+}
+
+/* request incoming or send outgoing usb packet - used to initialize remote */
+static void request_packet_async(struct mceusb_dev *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 (unlikely(!async_urb))
+			return;
+
+		async_buf = kmalloc(size, GFP_KERNEL);
+		if (!async_buf) {
+			usb_free_urb(async_urb);
+			return;
+		}
+
+		if (urb_type == MCEUSB_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 {
+		/* standard request */
+		async_urb = ir->urb_in;
+		ir->send_flags = RECV_FLAG_IN_PROGRESS;
+	}
+
+	dev_dbg(ir->d->dev, "receive request called (size=%#x)\n", size);
+
+	async_urb->transfer_buffer_length = size;
+	async_urb->dev = ir->usbdev;
+
+	res = usb_submit_urb(async_urb, GFP_ATOMIC);
+	if (res) {
+		dev_dbg(ir->d->dev, "receive request FAILED! (res=%d)\n", res);
+		return;
+	}
+	dev_dbg(ir->d->dev, "receive request complete (res=%d)\n", res);
+}
+
+static int unregister_from_lirc(struct mceusb_dev *ir)
+{
+	struct lirc_driver *d = ir->d;
+	int devnum;
+	int rtn;
+
+	devnum = ir->devnum;
+	dev_dbg(ir->d->dev, "unregister from lirc called\n");
+
+	rtn = lirc_unregister_driver(d->minor);
+	if (rtn > 0) {
+		dev_info(ir->d->dev, "error in lirc_unregister minor: %d\n"
+			 "Trying again...\n", d->minor);
+		if (rtn == -EBUSY) {
+			dev_info(ir->d->dev, "device is opened, will "
+				 "unregister on close\n");
+			return -EAGAIN;
+		}
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(HZ);
+
+		rtn = lirc_unregister_driver(d->minor);
+		if (rtn > 0)
+			dev_info(ir->d->dev, "lirc_unregister failed\n");
+	}
+
+	if (rtn) {
+		dev_info(ir->d->dev, "didn't free resources\n");
+		return -EAGAIN;
+	}
+
+	dev_info(ir->d->dev, "usb remote disconnected\n");
+
+	lirc_buffer_free(d->rbuf);
+	kfree(d->rbuf);
+	kfree(d);
+	kfree(ir);
+	return 0;
+}
+
+static int mceusb_ir_open(void *data)
+{
+	struct mceusb_dev *ir = data;
+
+	if (!ir) {
+		printk(KERN_WARNING DRIVER_NAME
+		       "[?]: %s called with no context\n", __func__);
+		return -EIO;
+	}
+
+	dev_dbg(ir->d->dev, "mceusb IR device opened\n");
+
+	if (!ir->flags.connected) {
+		if (!ir->usbdev)
+			return -ENOENT;
+		ir->flags.connected = 1;
+	}
+
+	return 0;
+}
+
+static void mceusb_ir_close(void *data)
+{
+	struct mceusb_dev *ir = data;
+
+	if (!ir) {
+		printk(KERN_WARNING DRIVER_NAME
+		       "[?]: %s called with no context\n", __func__);
+		return;
+	}
+
+	dev_dbg(ir->d->dev, "mceusb IR device closed\n");
+
+	if (ir->flags.connected) {
+		mutex_lock(&ir->lock);
+		ir->flags.connected = 0;
+		mutex_unlock(&ir->lock);
+	}
+}
+
+static void send_packet_to_lirc(struct mceusb_dev *ir)
+{
+	if (ir->lircdata) {
+		lirc_buffer_write(ir->d->rbuf,
+				    (unsigned char *) &ir->lircdata);
+		wake_up(&ir->d->rbuf->wait_poll);
+		ir->lircdata = 0;
+	}
+}
+
+static void mceusb_process_ir_data(struct mceusb_dev *ir, int buf_len)
+{
+	int i, j;
+	int packet_len = 0;
+	int start_index = 0;
+
+	/* skip meaningless 0xb1 0x60 header bytes on orig receiver */
+	if (ir->flags.microsoft_gen1)
+		start_index = 2;
+
+	/* this should only trigger w/the 1st-gen mce receiver */
+	for (i = start_index; i < (start_index + ir->overflow_len) &&
+	     i < buf_len; i++) {
+		/* rising/falling flank */
+		if (ir->is_pulse != (ir->buf_in[i] & MCE_PULSE_BIT)) {
+			send_packet_to_lirc(ir);
+			ir->is_pulse = ir->buf_in[i] & MCE_PULSE_BIT;
+		}
+
+		/* accumulate mce pulse/space values */
+		ir->lircdata += (ir->buf_in[i] & MCE_PULSE_MASK) *
+				MCE_TIME_UNIT;
+		ir->lircdata |= (ir->is_pulse ? PULSE_BIT : 0);
+	}
+	start_index += ir->overflow_len;
+	ir->overflow_len = 0;
+
+	for (i = start_index; i < buf_len; i++) {
+		/* decode mce packets of the form (84),AA,BB,CC,DD */
+
+		/* data headers */
+		if (ir->buf_in[i] >= 0x80 && ir->buf_in[i] <= 0x9e) {
+			/* decode packet data */
+			packet_len = ir->buf_in[i] & MCE_PACKET_LENGTH_MASK;
+			ir->overflow_len = i + 1 + packet_len - buf_len;
+			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;
+
+		/* status header (0x9F) */
+		} else if (ir->buf_in[i] == 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
+			 */
+
+#if 0
+	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);
+
+#endif
+
+			/* end decode loop */
+			dev_dbg(ir->d->dev, "[%d] %s: found control header\n",
+				ir->devnum, __func__);
+			ir->overflow_len = 0;
+			break;
+		} else {
+			dev_dbg(ir->d->dev, "[%d] %s: stray packet?\n",
+				ir->devnum, __func__);
+			ir->overflow_len = 0;
+		}
+	}
+
+	return;
+}
+
+static void mceusb_dev_recv(struct urb *urb, struct pt_regs *regs)
+{
+	struct mceusb_dev *ir;
+	int buf_len;
+
+	if (!urb)
+		return;
+
+	ir = urb->context;
+	if (!ir) {
+		usb_unlink_urb(urb);
+		return;
+	}
+
+	buf_len = urb->actual_length;
+
+	if (debug)
+		mceusb_dev_printdata(ir, urb->transfer_buffer, buf_len);
+
+	if (ir->send_flags == RECV_FLAG_IN_PROGRESS) {
+		ir->send_flags = SEND_FLAG_COMPLETE;
+		dev_dbg(ir->d->dev, "setup answer received %d bytes\n",
+			buf_len);
+	}
+
+	switch (urb->status) {
+	/* success */
+	case 0:
+		mceusb_process_ir_data(ir, buf_len);
+		break;
+
+	case -ECONNRESET:
+	case -ENOENT:
+	case -ESHUTDOWN:
+		usb_unlink_urb(urb);
+		return;
+
+	case -EPIPE:
+	default:
+		break;
+	}
+
+	usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+
+static ssize_t mceusb_transmit_ir(struct file *file, const char *buf,
+				  size_t n, loff_t *ppos)
+{
+	int i, count = 0, cmdcount = 0;
+	struct mceusb_dev *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_driver 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, MCEUSB_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 mceusb_dev *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 mceusb_dev *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;
+			dev_dbg(ir->d->dev, "SET_CARRIER disabling carrier "
+				"modulation\n");
+			request_packet_async(ir, ir->usb_ep_out,
+					     cmdbuf, sizeof(cmdbuf),
+					     MCEUSB_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;
+				dev_dbg(ir->d->dev, "SET_CARRIER requesting "
+					"%d Hz\n", carrier);
+
+				/* Transmit new carrier to mce device */
+				request_packet_async(ir, ir->usb_ep_out,
+						     cmdbuf, sizeof(cmdbuf),
+						     MCEUSB_OUTBOUND);
+				return carrier;
+			}
+		}
+
+		return -EINVAL;
+
+	}
+
+	return carrier;
+}
+
+
+static int mceusb_lirc_ioctl(struct inode *node, struct file *filep,
+			     unsigned int cmd, unsigned long arg)
+{
+	int result;
+	unsigned int ivalue;
+	unsigned long lvalue;
+	struct mceusb_dev *ir = NULL;
+
+	/* Retrieve lirc_driver 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;
+		}
+
+		dev_dbg(ir->d->dev, ": 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 lirc_dev_fop_ioctl(node, filep, cmd, arg);
+	}
+
+	return 0;
+}
+
+static struct file_operations lirc_fops = {
+	.owner		= THIS_MODULE,
+	.write		= mceusb_transmit_ir,
+	.ioctl		= mceusb_lirc_ioctl,
+	.read		= lirc_dev_fop_read,
+	.poll		= lirc_dev_fop_poll,
+	.open		= lirc_dev_fop_open,
+	.release	= lirc_dev_fop_close,
+};
+
+static int mceusb_gen1_init(struct mceusb_dev *ir)
+{
+	int i, ret;
+	char junk[64], data[8];
+	int partial = 0;
+
+	/*
+	 * Clear off the first few messages. These look like calibration
+	 * or test data, I can't really tell. This also flushes in case
+	 * we have random ir data queued up.
+	 */
+	for (i = 0; i < 40; i++)
+		usb_bulk_msg(ir->usbdev,
+			usb_rcvbulkpipe(ir->usbdev,
+				ir->usb_ep_in->bEndpointAddress),
+			junk, 64, &partial, HZ * 10);
+
+	ir->is_pulse = 1;
+
+	memset(data, 0, 8);
+
+	/* Get Status */
+	ret = usb_control_msg(ir->usbdev, usb_rcvctrlpipe(ir->usbdev, 0),
+			      USB_REQ_GET_STATUS, USB_DIR_IN,
+			      0, 0, data, 2, HZ * 3);
+
+	/*    ret = usb_get_status( ir->usbdev, 0, 0, data ); */
+	dev_dbg(ir->d->dev, "%s - ret = %d status = 0x%x 0x%x\n", __func__,
+		ret, data[0], data[1]);
+
+	/*
+	 * This is a strange one. They issue a set address to the device
+	 * on the receive control pipe and expect a certain value pair back
+	 */
+	memset(data, 0, 8);
+
+	ret = usb_control_msg(ir->usbdev, usb_rcvctrlpipe(ir->usbdev, 0),
+			      USB_REQ_SET_ADDRESS, USB_TYPE_VENDOR, 0, 0,
+			      data, 2, HZ * 3);
+	dev_dbg(ir->d->dev, "%s - ret = %d, devnum = %d\n",
+		__func__, ret, ir->usbdev->devnum);
+	dev_dbg(ir->d->dev, "%s - data[0] = %d, data[1] = %d\n",
+		__func__, data[0], data[1]);
+
+	/* set feature: bit rate 38400 bps */
+	ret = usb_control_msg(ir->usbdev, usb_sndctrlpipe(ir->usbdev, 0),
+			      USB_REQ_SET_FEATURE, USB_TYPE_VENDOR,
+			      0xc04e, 0x0000, NULL, 0, HZ * 3);
+
+	dev_dbg(ir->d->dev, "%s - ret = %d\n", __func__, ret);
+
+	/* bRequest 4: set char length to 8 bits */
+	ret = usb_control_msg(ir->usbdev, usb_sndctrlpipe(ir->usbdev, 0),
+			      4, USB_TYPE_VENDOR,
+			      0x0808, 0x0000, NULL, 0, HZ * 3);
+	dev_dbg(ir->d->dev, "%s - retB = %d\n", __func__, ret);
+
+	/* bRequest 2: set handshaking to use DTR/DSR */
+	ret = usb_control_msg(ir->usbdev, usb_sndctrlpipe(ir->usbdev, 0),
+			      2, USB_TYPE_VENDOR,
+			      0x0000, 0x0100, NULL, 0, HZ * 3);
+	dev_dbg(ir->d->dev, "%s - retC = %d\n", __func__, ret);
+
+	return ret;
+
+};
+
+static int mceusb_dev_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 mceusb_dev *ir = NULL;
+	struct lirc_driver *driver = 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;
+	int is_microsoft_gen1;
+
+	dev_dbg(&intf->dev, ": %s called\n", __func__);
+
+	usb_reset_device(dev);
+
+	config = dev->actconfig;
+
+	idesc = intf->cur_altsetting;
+
+	is_pinnacle = usb_match_id(intf, pinnacle_list) ? 1 : 0;
+
+	is_microsoft_gen1 = usb_match_id(intf, microsoft_gen1_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(&intf->dev, ": 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))) {
+
+			dev_dbg(&intf->dev, ": 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) {
+		dev_dbg(&intf->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));
+
+	mem_failure = 0;
+	ir = kzalloc(sizeof(struct mceusb_dev), GFP_KERNEL);
+	if (!ir) {
+		mem_failure = 1;
+		goto mem_failure_switch;
+	}
+
+	driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL);
+	if (!driver) {
+		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;
+	}
+
+	strcpy(driver->name, DRIVER_NAME);
+	driver->minor = -1;
+	driver->features = LIRC_CAN_SEND_PULSE |
+		LIRC_CAN_SET_TRANSMITTER_MASK |
+		LIRC_CAN_REC_MODE2 |
+		LIRC_CAN_SET_SEND_CARRIER;
+	driver->data = ir;
+	driver->rbuf = rbuf;
+	driver->set_use_inc = &mceusb_ir_open;
+	driver->set_use_dec = &mceusb_ir_close;
+	driver->code_length = sizeof(int) * 8;
+	driver->fops  = &lirc_fops;
+	driver->dev   = &intf->dev;
+	driver->owner = THIS_MODULE;
+
+	mutex_init(&ir->lock);
+	init_waitqueue_head(&ir->wait_out);
+
+	minor = lirc_register_driver(driver);
+	if (minor < 0)
+		mem_failure = 9;
+
+mem_failure_switch:
+
+	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(driver);
+	case 2:
+		kfree(ir);
+	case 1:
+		dev_info(&intf->dev, "out of memory (code=%d)\n", mem_failure);
+		return -ENOMEM;
+	}
+
+	driver->minor = minor;
+	ir->d = driver;
+	ir->devnum = devnum;
+	ir->usbdev = dev;
+	ir->len_in = maxp;
+	ir->overflow_len = 0;
+	ir->flags.connected = 0;
+	ir->flags.pinnacle = is_pinnacle;
+	ir->flags.microsoft_gen1 = is_microsoft_gen1;
+	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, sizeof(buf)) > 0)
+		strlcpy(name, buf, sizeof(name));
+	if (dev->descriptor.iProduct
+	    && usb_string(dev, dev->descriptor.iProduct,
+			  buf, sizeof(buf)) > 0)
+		snprintf(name + strlen(name), sizeof(name) - strlen(name),
+			 " %s", buf);
+
+	/* inbound data */
+	usb_fill_int_urb(ir->urb_in, dev, pipe, ir->buf_in,
+		maxp, (usb_complete_t) mceusb_dev_recv, ir, ep_in->bInterval);
+	ir->urb_in->transfer_dma = ir->dma_in;
+	ir->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+	/* 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(ir->d->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, MCEUSB_INBOUND);
+		request_packet_async(ir, ep_out, pin_init1, sizeof(pin_init1),
+				     MCEUSB_OUTBOUND);
+		request_packet_async(ir, ep_in, NULL, maxp, MCEUSB_INBOUND);
+		request_packet_async(ir, ep_out, pin_init2, sizeof(pin_init2),
+				     MCEUSB_OUTBOUND);
+		request_packet_async(ir, ep_in, NULL, maxp, MCEUSB_INBOUND);
+		request_packet_async(ir, ep_out, pin_init3, sizeof(pin_init3),
+				     MCEUSB_OUTBOUND);
+	} else if (ir->flags.microsoft_gen1) {
+		/* original ms mce device requires some additional setup */
+		mceusb_gen1_init(ir);
+	} else {
+
+		request_packet_async(ir, ep_in, NULL, maxp, MCEUSB_INBOUND);
+		request_packet_async(ir, ep_in, NULL, maxp, MCEUSB_INBOUND);
+		request_packet_async(ir, ep_out, init1,
+				     sizeof(init1), MCEUSB_OUTBOUND);
+		request_packet_async(ir, ep_in, NULL, maxp, MCEUSB_INBOUND);
+		request_packet_async(ir, ep_out, init2,
+				     sizeof(init2), MCEUSB_OUTBOUND);
+	}
+
+	/*
+	 * if we don't issue the correct number of receives (MCEUSB_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 mceusb_dev_recv routine will handle
+	 * the control packets OK - they start with 0x9f - but the async
+	 * callback doesn't handle ir pulse packets
+	 */
+	request_packet_async(ir, ep_in, NULL, maxp, 0);
+
+	usb_set_intfdata(intf, ir);
+
+	dev_info(ir->d->dev, "Registered %s on usb%d:%d\n", name,
+		 dev->bus->busnum, devnum);
+
+	return 0;
+}
+
+
+static void mceusb_dev_disconnect(struct usb_interface *intf)
+{
+	struct usb_device *dev = interface_to_usbdev(intf);
+	struct mceusb_dev *ir = usb_get_intfdata(intf);
+
+	usb_set_intfdata(intf, NULL);
+
+	if (!ir || !ir->d)
+		return;
+
+	ir->usbdev = NULL;
+	wake_up_all(&ir->wait_out);
+
+	mutex_lock(&ir->lock);
+	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);
+	mutex_unlock(&ir->lock);
+
+	unregister_from_lirc(ir);
+}
+
+static int mceusb_dev_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct mceusb_dev *ir = usb_get_intfdata(intf);
+	dev_info(ir->d->dev, "suspend\n");
+	usb_kill_urb(ir->urb_in);
+	return 0;
+}
+
+static int mceusb_dev_resume(struct usb_interface *intf)
+{
+	struct mceusb_dev *ir = usb_get_intfdata(intf);
+	dev_info(ir->d->dev, "resume\n");
+	if (usb_submit_urb(ir->urb_in, GFP_ATOMIC))
+		return -EIO;
+	return 0;
+}
+
+static struct usb_driver mceusb_dev_driver = {
+	.name =		DRIVER_NAME,
+	.probe =	mceusb_dev_probe,
+	.disconnect =	mceusb_dev_disconnect,
+	.suspend =	mceusb_dev_suspend,
+	.resume =	mceusb_dev_resume,
+	.reset_resume =	mceusb_dev_resume,
+	.id_table =	mceusb_dev_table
+};
+
+static int __init mceusb_dev_init(void)
+{
+	int i;
+
+	printk(KERN_INFO DRIVER_NAME ": " DRIVER_DESC " " DRIVER_VERSION "\n");
+	printk(KERN_INFO DRIVER_NAME ": " DRIVER_AUTHOR "\n");
+	if (debug)
+		printk(KERN_DEBUG DRIVER_NAME ": debug mode enabled\n");
+
+	i = usb_register(&mceusb_dev_driver);
+	if (i < 0) {
+		printk(KERN_ERR DRIVER_NAME
+		       ": usb register failed, result = %d\n", i);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void __exit mceusb_dev_exit(void)
+{
+	usb_deregister(&mceusb_dev_driver);
+}
+
+module_init(mceusb_dev_init);
+module_exit(mceusb_dev_exit);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(usb, mceusb_dev_table);
+/* this was originally lirc_mceusb2, lirc_mceusb and lirc_mceusb2 merged now */
+MODULE_ALIAS("lirc_mceusb2");
+
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug enabled or not");
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ