From patchwork Tue Oct 20 14:00:00 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jarod Wilson X-Patchwork-Id: 54958 Received: from vger.kernel.org (vger.kernel.org [209.132.176.167]) by demeter.kernel.org (8.14.2/8.14.2) with ESMTP id n9KE2fjq021361 for ; Tue, 20 Oct 2009 14:02:41 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752397AbZJTOCg (ORCPT ); Tue, 20 Oct 2009 10:02:36 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1751190AbZJTOCf (ORCPT ); Tue, 20 Oct 2009 10:02:35 -0400 Received: from mx1.redhat.com ([209.132.183.28]:33780 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752180AbZJTOCb (ORCPT ); Tue, 20 Oct 2009 10:02:31 -0400 Received: from int-mx01.intmail.prod.int.phx2.redhat.com (int-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.11]) by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id n9KE2V8d016961; Tue, 20 Oct 2009 10:02:31 -0400 Received: from xavier.bos.redhat.com (xavier.bos.redhat.com [10.16.16.50]) by int-mx01.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id n9KE2UMg030492; Tue, 20 Oct 2009 10:02:30 -0400 From: Jarod Wilson Organization: Red Hat, Inc. To: linux-kernel@vger.kernel.org Subject: [PATCH 2/3 v2] lirc driver for Windows MCE IR transceivers Date: Tue, 20 Oct 2009 10:00:00 -0400 User-Agent: KMail/1.12.1 (Linux/2.6.30.8-64.fc11.x86_64; KDE/4.3.1; x86_64; ; ) Cc: linux-input@vger.kernel.org, linux-media@vger.kernel.org, Janne Grunau , Christoph Bartelmus References: <200910200956.33391.jarod@redhat.com> In-Reply-To: <200910200956.33391.jarod@redhat.com> MIME-Version: 1.0 Message-Id: <200910201000.00372.jarod@redhat.com> X-Scanned-By: MIMEDefang 2.67 on 10.5.11.11 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org 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 + * + * Transmitter support and reception code cleanup. + * (C) by Daniel Melander + * + * Original lirc_mceusb driver for 1st-gen device: + * Copyright (c) 2003-2004 Dan Conti + * + * 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 + * + * Initial transmission support for 1st-gen device added August 2009, + * by Patrick Calhoun + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lirc.h" +#include "lirc_dev.h" + +#define DRIVER_VERSION "1.90" +#define DRIVER_AUTHOR "Daniel Melander , " \ + "Martin Blatter , " \ + "Dan Conti " +#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");