All of lore.kernel.org
 help / color / mirror / Atom feed
* [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware
       [not found] <20090813185050.356225129@hardeman.nu>
@ 2009-08-13 18:57   ` David Härdeman
  0 siblings, 0 replies; 16+ messages in thread
From: David Härdeman @ 2009-08-13 18:57 UTC (permalink / raw)
  To: linux-kernel; +Cc: linux-input, akpm, dmitry.torokhov

[-- Attachment #1: add-a-driver-for-the-winbond-wpcd376i-cir-functionality --]
[-- Type: text/plain, Size: 46648 bytes --]

Add a driver for the the Consumer IR (CIR) functionality of the Winbond
WPCD376I chipset (found on e.g. Intel DG45FC motherboards).

Changes since the first version:

  o converted from an ACPI to a PNP driver using a patch provided by 
    Bjorn Helgaas <bjorn.helgaas@hp.com>

Changes since the second version:

  o misc fixes to the irqhandler after discovering that the FIFO
    enforces 16 levels (was a separate patch in the -mm tree)
  o __devexit annotations
  o KEY_NUMERIC_[0-9] instead of KEY_[0-9]
  o EV_MSC/MSC_SCAN instead of "last_scancode" sysfs attr
  o last three points based on feedback from Dmitry Torokhov
    <dmitry.torokhov@gmail.com>

Signed-off-by: David Härdeman <david@hardeman.nu>
Reviewed-by: Jesse Barnes <jbarnes@virtuousgeek.org>

Index: linux-2.6/MAINTAINERS
===================================================================
--- linux-2.6.orig/MAINTAINERS	2009-08-09 11:32:42.000000000 +0200
+++ linux-2.6/MAINTAINERS	2009-08-09 11:44:35.000000000 +0200
@@ -6448,6 +6448,12 @@
  S:	Maintained
  F:	drivers/scsi/wd7000.c
  
+WINBOND CIR DRIVER
+P:	David Härdeman
+M:	david@hardeman.nu
+S:	Maintained
+F:	drivers/input/misc/winbond-cir.c
+
  WIMAX STACK
  P:	Inaky Perez-Gonzalez
  M:	inaky.perez-gonzalez@intel.com
Index: linux-2.6/drivers/input/misc/Kconfig
===================================================================
--- linux-2.6.orig/drivers/input/misc/Kconfig	2009-08-09 11:32:42.000000000 +0200
+++ linux-2.6/drivers/input/misc/Kconfig	2009-08-11 18:20:41.000000000 +0200
@@ -222,6 +222,22 @@
  	  To compile this driver as a module, choose M here: the
  	  module will be called sgi_btns.
  
+config INPUT_WINBOND_CIR
+	tristate "Winbond IR remote control"
+	depends on X86 && PNP
+	select LEDS_CLASS
+	select BITREVERSE
+	help
+	  Say Y here if you want to use the IR remote functionality found
+	  in some Winbond SuperI/O chips. Currently only the WPCD376I
+	  chip is supported (included in some Intel Media series motherboards).
+
+	  IR Receive and wake-on-IR from suspend and power-off is currently
+	  supported.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called winbond_cir.
+
  config HP_SDC_RTC
  	tristate "HP SDC Real Time Clock"
  	depends on (GSC || HP300) && SERIO
Index: linux-2.6/drivers/input/misc/Makefile
===================================================================
--- linux-2.6.orig/drivers/input/misc/Makefile	2009-08-09 11:32:42.000000000 +0200
+++ linux-2.6/drivers/input/misc/Makefile	2009-08-13 18:25:06.000000000 +0200
@@ -24,5 +24,6 @@
  obj-$(CONFIG_INPUT_SPARCSPKR)		+= sparcspkr.o
  obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON)	+= twl4030-pwrbutton.o
  obj-$(CONFIG_INPUT_UINPUT)		+= uinput.o
+obj-$(CONFIG_INPUT_WINBOND_CIR)		+= winbond-cir.o
  obj-$(CONFIG_INPUT_WISTRON_BTNS)	+= wistron_btns.o
  obj-$(CONFIG_INPUT_YEALINK)		+= yealink.o
Index: linux-2.6/drivers/input/misc/winbond-cir.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/drivers/input/misc/winbond-cir.c	2009-08-13 20:33:06.000000000 +0200
@@ -0,0 +1,1614 @@
+/*
+ *  winbond-cir.c - Driver for the Consumer IR functionality of Winbond
+ *                  SuperI/O chips.
+ *
+ *  Currently supports the Winbond WPCD376i chip (PNP id WEC1022), but
+ *  could probably support others (Winbond WEC102X, NatSemi, etc)
+ *  with minor modifications.
+ *
+ *  Original Author: David Härdeman <david@hardeman.nu>
+ *     Copyright (C) 2009 David Härdeman <david@hardeman.nu>
+ *
+ *  Dedicated to Matilda, my newborn daughter, without whose loving attention
+ *  this driver would have been finished in half the time and with a fraction
+ *  of the bugs.
+ *
+ *  Written using:
+ *    o Winbond WPCD376I datasheet helpfully provided by Jesse Barnes at Intel
+ *    o NatSemi PC87338/PC97338 datasheet (for the serial port stuff)
+ *    o DSDT dumps
+ *
+ *  Supported features:
+ *    o RC6
+ *    o Wake-On-CIR functionality
+ *
+ *  To do:
+ *    o Test NEC and RC5
+ *
+ *  Left as an exercise for the reader:
+ *    o Learning (I have neither the hardware, nor the need)
+ *    o IR Transmit (ibid)
+ *
+ *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/pnp.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/pci_ids.h>
+#include <linux/io.h>
+#include <linux/bitrev.h>
+#include <linux/bitops.h>
+
+#define DRVNAME "winbond-cir"
+
+/* CEIR Wake-Up Registers, relative to data->wbase                      */
+#define WBCIR_REG_WCEIR_CTL	0x03 /* CEIR Receiver Control		*/
+#define WBCIR_REG_WCEIR_STS	0x04 /* CEIR Receiver Status		*/
+#define WBCIR_REG_WCEIR_EV_EN	0x05 /* CEIR Receiver Event Enable	*/
+#define WBCIR_REG_WCEIR_CNTL	0x06 /* CEIR Receiver Counter Low	*/
+#define WBCIR_REG_WCEIR_CNTH	0x07 /* CEIR Receiver Counter High	*/
+#define WBCIR_REG_WCEIR_INDEX	0x08 /* CEIR Receiver Index		*/
+#define WBCIR_REG_WCEIR_DATA	0x09 /* CEIR Receiver Data		*/
+#define WBCIR_REG_WCEIR_CSL	0x0A /* CEIR Re. Compare Strlen		*/
+#define WBCIR_REG_WCEIR_CFG1	0x0B /* CEIR Re. Configuration 1	*/
+#define WBCIR_REG_WCEIR_CFG2	0x0C /* CEIR Re. Configuration 2	*/
+
+/* CEIR Enhanced Functionality Registers, relative to data->ebase       */
+#define WBCIR_REG_ECEIR_CTS	0x00 /* Enhanced IR Control Status	*/
+#define WBCIR_REG_ECEIR_CCTL	0x01 /* Infrared Counter Control	*/
+#define WBCIR_REG_ECEIR_CNT_LO	0x02 /* Infrared Counter LSB		*/
+#define WBCIR_REG_ECEIR_CNT_HI	0x03 /* Infrared Counter MSB		*/
+#define WBCIR_REG_ECEIR_IREM	0x04 /* Infrared Emitter Status		*/
+
+/* SP3 Banked Registers, relative to data->sbase                        */
+#define WBCIR_REG_SP3_BSR	0x03 /* Bank Select, all banks		*/
+				      /* Bank 0				*/
+#define WBCIR_REG_SP3_RXDATA	0x00 /* FIFO RX data (r)		*/
+#define WBCIR_REG_SP3_TXDATA	0x00 /* FIFO TX data (w)		*/
+#define WBCIR_REG_SP3_IER	0x01 /* Interrupt Enable		*/
+#define WBCIR_REG_SP3_EIR	0x02 /* Event Identification (r)	*/
+#define WBCIR_REG_SP3_FCR	0x02 /* FIFO Control (w)		*/
+#define WBCIR_REG_SP3_MCR	0x04 /* Mode Control			*/
+#define WBCIR_REG_SP3_LSR	0x05 /* Link Status			*/
+#define WBCIR_REG_SP3_MSR	0x06 /* Modem Status			*/
+#define WBCIR_REG_SP3_ASCR	0x07 /* Aux Status and Control		*/
+				      /* Bank 2				*/
+#define WBCIR_REG_SP3_BGDL	0x00 /* Baud Divisor LSB		*/
+#define WBCIR_REG_SP3_BGDH	0x01 /* Baud Divisor MSB		*/
+#define WBCIR_REG_SP3_EXCR1	0x02 /* Extended Control 1		*/
+#define WBCIR_REG_SP3_EXCR2	0x04 /* Extended Control 2		*/
+#define WBCIR_REG_SP3_TXFLV	0x06 /* TX FIFO Level			*/
+#define WBCIR_REG_SP3_RXFLV	0x07 /* RX FIFO Level			*/
+				      /* Bank 3				*/
+#define WBCIR_REG_SP3_MRID	0x00 /* Module Identification		*/
+#define WBCIR_REG_SP3_SH_LCR	0x01 /* LCR Shadow			*/
+#define WBCIR_REG_SP3_SH_FCR	0x02 /* FCR Shadow			*/
+				      /* Bank 4				*/
+#define WBCIR_REG_SP3_IRCR1	0x02 /* Infrared Control 1		*/
+				      /* Bank 5				*/
+#define WBCIR_REG_SP3_IRCR2	0x04 /* Infrared Control 2		*/
+				      /* Bank 6				*/
+#define WBCIR_REG_SP3_IRCR3	0x00 /* Infrared Control 3		*/
+#define WBCIR_REG_SP3_SIR_PW	0x02 /* SIR Pulse Width		*/
+				      /* Bank 7				*/
+#define WBCIR_REG_SP3_IRRXDC	0x00 /* IR RX Demod Control		*/
+#define WBCIR_REG_SP3_IRTXMC	0x01 /* IR TX Mod Control		*/
+#define WBCIR_REG_SP3_RCCFG	0x02 /* CEIR Config			*/
+#define WBCIR_REG_SP3_IRCFG1	0x04 /* Infrared Config 1		*/
+#define WBCIR_REG_SP3_IRCFG4	0x07 /* Infrared Config 4		*/
+
+/*
+ * Magic values follow
+ */
+
+/* No interrupts for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_NONE		0x00
+/* RX data bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_RX		0x01
+/* Over/Under-flow bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_ERR		0x04
+/* Led enable/disable bit for WBCIR_REG_ECEIR_CTS */
+#define WBCIR_LED_ENABLE	0x80
+/* RX data available bit for WBCIR_REG_SP3_LSR */
+#define WBCIR_RX_AVAIL		0x01
+/* RX disable bit for WBCIR_REG_SP3_ASCR */
+#define WBCIR_RX_DISABLE	0x20
+/* Extended mode enable bit for WBCIR_REG_SP3_EXCR1 */
+#define WBCIR_EXT_ENABLE	0x01
+/* Select compare register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */
+#define WBCIR_REGSEL_COMPARE	0x10
+/* Select mask register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */
+#define WBCIR_REGSEL_MASK	0x20
+/* Starting address of selected register in WBCIR_REG_WCEIR_INDEX */
+#define WBCIR_REG_ADDR0		0x00
+
+/* Valid banks for the SP3 UART */
+enum wbcir_bank {
+	WBCIR_BANK_0          = 0x00,
+	WBCIR_BANK_1          = 0x80,
+	WBCIR_BANK_2          = 0xE0,
+	WBCIR_BANK_3          = 0xE4,
+	WBCIR_BANK_4          = 0xE8,
+	WBCIR_BANK_5          = 0xEC,
+	WBCIR_BANK_6          = 0xF0,
+	WBCIR_BANK_7          = 0xF4,
+};
+
+/* Supported IR Protocols */
+enum wbcir_protocol {
+	IR_PROTOCOL_RC5          = 0x0,
+	IR_PROTOCOL_NEC          = 0x1,
+	IR_PROTOCOL_RC6          = 0x2,
+};
+
+/* Misc */
+#define WBCIR_NAME	"Winbond CIR"
+#define WBCIR_ID_FAMILY          0xF1 /* Family ID for the WPCD376I	*/
+#define	WBCIR_ID_CHIP            0x04 /* Chip ID for the WPCD376I	*/
+#define IR_KEYPRESS_TIMEOUT       250 /* FIXME: should be per-protocol? */
+#define INVALID_SCANCODE   0x7FFFFFFF /* Invalid with all protos	*/
+#define WAKEUP_IOMEM_LEN         0x10 /* Wake-Up I/O Reg Len		*/
+#define EHFUNC_IOMEM_LEN         0x10 /* Enhanced Func I/O Reg Len	*/
+#define SP_IOMEM_LEN             0x08 /* Serial Port 3 (IR) Reg Len	*/
+#define WBCIR_MAX_IDLE_BYTES       10
+
+static DEFINE_SPINLOCK(wbcir_lock);
+static DEFINE_RWLOCK(keytable_lock);
+
+struct wbcir_key {
+	u32 scancode;
+	unsigned int keycode;
+};
+
+struct wbcir_keyentry {
+	struct wbcir_key key;
+	struct list_head list;
+};
+
+static struct wbcir_key rc6_def_keymap[] = {
+	{ 0x800F0400, KEY_NUMERIC_0		},
+	{ 0x800F0401, KEY_NUMERIC_1		},
+	{ 0x800F0402, KEY_NUMERIC_2		},
+	{ 0x800F0403, KEY_NUMERIC_3		},
+	{ 0x800F0404, KEY_NUMERIC_4		},
+	{ 0x800F0405, KEY_NUMERIC_5		},
+	{ 0x800F0406, KEY_NUMERIC_6		},
+	{ 0x800F0407, KEY_NUMERIC_7		},
+	{ 0x800F0408, KEY_NUMERIC_8		},
+	{ 0x800F0409, KEY_NUMERIC_9		},
+	{ 0x800F041D, KEY_NUMERIC_STAR		},
+	{ 0x800F041C, KEY_NUMERIC_POUND		},
+	{ 0x800F0410, KEY_VOLUMEUP		},
+	{ 0x800F0411, KEY_VOLUMEDOWN		},
+	{ 0x800F0412, KEY_CHANNELUP		},
+	{ 0x800F0413, KEY_CHANNELDOWN		},
+	{ 0x800F040E, KEY_MUTE			},
+	{ 0x800F040D, KEY_VENDOR		}, /* Vista Logo Key */
+	{ 0x800F041E, KEY_UP			},
+	{ 0x800F041F, KEY_DOWN			},
+	{ 0x800F0420, KEY_LEFT			},
+	{ 0x800F0421, KEY_RIGHT			},
+	{ 0x800F0422, KEY_OK			},
+	{ 0x800F0423, KEY_ESC			},
+	{ 0x800F040F, KEY_INFO			},
+	{ 0x800F040A, KEY_CLEAR			},
+	{ 0x800F040B, KEY_ENTER			},
+	{ 0x800F045B, KEY_RED			},
+	{ 0x800F045C, KEY_GREEN			},
+	{ 0x800F045D, KEY_YELLOW		},
+	{ 0x800F045E, KEY_BLUE			},
+	{ 0x800F045A, KEY_TEXT			},
+	{ 0x800F0427, KEY_SWITCHVIDEOMODE	},
+	{ 0x800F040C, KEY_POWER			},
+	{ 0x800F0450, KEY_RADIO			},
+	{ 0x800F0448, KEY_PVR			},
+	{ 0x800F0447, KEY_AUDIO			},
+	{ 0x800F0426, KEY_EPG			},
+	{ 0x800F0449, KEY_CAMERA		},
+	{ 0x800F0425, KEY_TV			},
+	{ 0x800F044A, KEY_VIDEO			},
+	{ 0x800F0424, KEY_DVD			},
+	{ 0x800F0416, KEY_PLAY			},
+	{ 0x800F0418, KEY_PAUSE			},
+	{ 0x800F0419, KEY_STOP			},
+	{ 0x800F0414, KEY_FASTFORWARD		},
+	{ 0x800F041A, KEY_NEXT			},
+	{ 0x800F041B, KEY_PREVIOUS		},
+	{ 0x800F0415, KEY_REWIND		},
+	{ 0x800F0417, KEY_RECORD		},
+};
+
+/* Registers and other state is protected by wbcir_lock */
+struct wbcir_data {
+	unsigned long wbase;        /* Wake-Up Baseaddr		*/
+	unsigned long ebase;        /* Enhanced Func. Baseaddr	*/
+	unsigned long sbase;        /* Serial Port Baseaddr	*/
+	unsigned int  irq;          /* Serial Port IRQ		*/
+
+	struct input_dev *input_dev;
+	struct timer_list timer_keyup;
+	struct led_trigger *rxtrigger;
+	struct led_trigger *txtrigger;
+	struct led_classdev led;
+
+	u32 last_scancode;
+	unsigned int last_keycode;
+	u8 last_toggle;
+	u8 keypressed;
+	unsigned long keyup_jiffies;
+	unsigned int idle_count;
+
+	/* RX irdata and parsing state */
+	unsigned long irdata[30];
+	unsigned int irdata_count;
+	unsigned int irdata_idle;
+	unsigned int irdata_off;
+	unsigned int irdata_error;
+
+	/* Protected by keytable_lock */
+	struct list_head keytable;
+};
+
+static enum wbcir_protocol protocol = IR_PROTOCOL_RC6;
+module_param(protocol, uint, 0444);
+MODULE_PARM_DESC(protocol, "IR protocol to use "
+		 "(0 = RC5, 1 = NEC, 2 = RC6A, default)");
+
+static int invert; /* default = 0 */
+module_param(invert, bool, 0444);
+MODULE_PARM_DESC(invert, "Invert the signal from the IR receiver");
+
+static unsigned int wake_sc = 0x800F040C;
+module_param(wake_sc, uint, 0644);
+MODULE_PARM_DESC(wake_sc, "Scancode of the power-on IR command");
+
+static unsigned int wake_rc6mode = 6;
+module_param(wake_rc6mode, uint, 0644);
+MODULE_PARM_DESC(wake_rc6mode, "RC6 mode for the power-on command "
+		 "(0 = 0, 6 = 6A, default)");
+
+
+
+/*****************************************************************************
+ *
+ * UTILITY FUNCTIONS
+ *
+ *****************************************************************************/
+
+/* Caller needs to hold wbcir_lock */
+static void
+wbcir_set_bits(unsigned long addr, u8 bits, u8 mask)
+{
+	u8 val;
+
+	val = inb(addr);
+	val = ((val & ~mask) | (bits & mask));
+	outb(val, addr);
+}
+
+/* Selects the register bank for the serial port */
+static inline void
+wbcir_select_bank(struct wbcir_data *data, enum wbcir_bank bank)
+{
+	outb(bank, data->sbase + WBCIR_REG_SP3_BSR);
+}
+
+static enum led_brightness
+wbcir_led_brightness_get(struct led_classdev *led_cdev)
+{
+	struct wbcir_data *data = container_of(led_cdev,
+					       struct wbcir_data,
+					       led);
+
+	if (inb(data->ebase + WBCIR_REG_ECEIR_CTS) & WBCIR_LED_ENABLE)
+		return LED_FULL;
+	else
+		return LED_OFF;
+}
+
+static void
+wbcir_led_brightness_set(struct led_classdev *led_cdev,
+			    enum led_brightness brightness)
+{
+	struct wbcir_data *data = container_of(led_cdev,
+					       struct wbcir_data,
+					       led);
+
+	wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CTS,
+		       brightness == LED_OFF ? 0x00 : WBCIR_LED_ENABLE,
+		       WBCIR_LED_ENABLE);
+}
+
+/* Manchester encodes bits to RC6 message cells (see wbcir_parse_rc6) */
+static u8
+wbcir_to_rc6cells(u8 val)
+{
+	u8 coded = 0x00;
+	int i;
+
+	val &= 0x0F;
+	for (i = 0; i < 4; i++) {
+		if (val & 0x01)
+			coded |= 0x02 << (i * 2);
+		else
+			coded |= 0x01 << (i * 2);
+		val >>= 1;
+	}
+
+	return coded;
+}
+
+
+
+/*****************************************************************************
+ *
+ * INPUT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static unsigned int
+wbcir_do_getkeycode(struct wbcir_data *data, u32 scancode)
+{
+	struct wbcir_keyentry *keyentry;
+	unsigned int keycode = KEY_RESERVED;
+	unsigned long flags;
+
+	read_lock_irqsave(&keytable_lock, flags);
+
+	list_for_each_entry(keyentry, &data->keytable, list) {
+		if (keyentry->key.scancode == scancode) {
+			keycode = keyentry->key.keycode;
+			break;
+		}
+	}
+
+	read_unlock_irqrestore(&keytable_lock, flags);
+	return keycode;
+}
+
+static int
+wbcir_getkeycode(struct input_dev *dev, int scancode, int *keycode)
+{
+	struct wbcir_data *data = input_get_drvdata(dev);
+
+	*keycode = (int)wbcir_do_getkeycode(data, (u32)scancode);
+	return 0;
+}
+
+static int
+wbcir_setkeycode(struct input_dev *dev, int sscancode, int keycode)
+{
+	struct wbcir_data *data = input_get_drvdata(dev);
+	struct wbcir_keyentry *keyentry;
+	struct wbcir_keyentry *new_keyentry;
+	unsigned long flags;
+	unsigned int old_keycode = KEY_RESERVED;
+	u32 scancode = (u32)sscancode;
+
+	if (keycode < 0 || keycode > KEY_MAX)
+		return -EINVAL;
+
+	new_keyentry = kmalloc(sizeof(*new_keyentry), GFP_KERNEL);
+	if (!new_keyentry)
+		return -ENOMEM;
+
+	write_lock_irqsave(&keytable_lock, flags);
+
+	list_for_each_entry(keyentry, &data->keytable, list) {
+		if (keyentry->key.scancode != scancode)
+			continue;
+
+		old_keycode = keyentry->key.keycode;
+		keyentry->key.keycode = keycode;
+
+		if (keyentry->key.keycode == KEY_RESERVED) {
+			list_del(&keyentry->list);
+			kfree(keyentry);
+		}
+
+		break;
+	}
+
+	set_bit(keycode, dev->keybit);
+
+	if (old_keycode == KEY_RESERVED) {
+		new_keyentry->key.scancode = scancode;
+		new_keyentry->key.keycode = keycode;
+		list_add(&new_keyentry->list, &data->keytable);
+	} else {
+		kfree(new_keyentry);
+		clear_bit(old_keycode, dev->keybit);
+		list_for_each_entry(keyentry, &data->keytable, list) {
+			if (keyentry->key.keycode == old_keycode) {
+				set_bit(old_keycode, dev->keybit);
+				break;
+			}
+		}
+	}
+
+	write_unlock_irqrestore(&keytable_lock, flags);
+	return 0;
+}
+
+/*
+ * Timer function to report keyup event some time after keydown is
+ * reported by the ISR.
+ */
+static void
+wbcir_keyup(unsigned long cookie)
+{
+	struct wbcir_data *data = (struct wbcir_data *)cookie;
+	unsigned long flags;
+
+	/*
+	 * data->keyup_jiffies is used to prevent a race condition if a
+	 * hardware interrupt occurs at this point and the keyup timer
+	 * event is moved further into the future as a result.
+	 *
+	 * The timer will then be reactivated and this function called
+	 * again in the future. We need to exit gracefully in that case
+	 * to allow the input subsystem to do its auto-repeat magic or
+	 * a keyup event might follow immediately after the keydown.
+	 */
+
+	spin_lock_irqsave(&wbcir_lock, flags);
+
+	if (time_is_after_eq_jiffies(data->keyup_jiffies) && data->keypressed) {
+		data->keypressed = 0;
+		led_trigger_event(data->rxtrigger, LED_OFF);
+		input_report_key(data->input_dev, data->last_keycode, 0);
+		input_sync(data->input_dev);
+	}
+
+	spin_unlock_irqrestore(&wbcir_lock, flags);
+}
+
+static void
+wbcir_keydown(struct wbcir_data *data, u32 scancode, u8 toggle)
+{
+	unsigned int keycode;
+
+	/* Repeat? */
+	if (data->last_scancode == scancode &&
+	    data->last_toggle == toggle &&
+	    data->keypressed)
+		goto set_timer;
+	data->last_scancode = scancode;
+
+	/* Do we need to release an old keypress? */
+	if (data->keypressed) {
+		input_report_key(data->input_dev, data->last_keycode, 0);
+		input_sync(data->input_dev);
+		data->keypressed = 0;
+	}
+
+	/* Report scancode */
+	input_event(data->input_dev, EV_MSC, MSC_SCAN, (int)scancode);
+
+	/* Do we know this scancode? */
+	keycode = wbcir_do_getkeycode(data, scancode);
+	if (keycode == KEY_RESERVED)
+		goto set_timer;
+
+	/* Register a keypress */
+	input_report_key(data->input_dev, keycode, 1);
+	data->keypressed = 1;
+	data->last_keycode = keycode;
+	data->last_toggle = toggle;
+
+set_timer:
+	input_sync(data->input_dev);
+	led_trigger_event(data->rxtrigger,
+			  data->keypressed ? LED_FULL : LED_OFF);
+	data->keyup_jiffies = jiffies + msecs_to_jiffies(IR_KEYPRESS_TIMEOUT);
+	mod_timer(&data->timer_keyup, data->keyup_jiffies);
+}
+
+
+
+/*****************************************************************************
+ *
+ * IR PARSING FUNCTIONS
+ *
+ *****************************************************************************/
+
+/* Resets all irdata */
+static void
+wbcir_reset_irdata(struct wbcir_data *data)
+{
+	memset(data->irdata, 0, sizeof(data->irdata));
+	data->irdata_count = 0;
+	data->irdata_off = 0;
+	data->irdata_error = 0;
+}
+
+/* Adds one bit of irdata */
+static void
+add_irdata_bit(struct wbcir_data *data, int set)
+{
+	if (data->irdata_count >= sizeof(data->irdata) * 8) {
+		data->irdata_error = 1;
+		return;
+	}
+
+	if (set)
+		__set_bit(data->irdata_count, data->irdata);
+	data->irdata_count++;
+}
+
+/* Gets count bits of irdata */
+static u16
+get_bits(struct wbcir_data *data, int count)
+{
+	u16 val = 0x0;
+
+	if (data->irdata_count - data->irdata_off < count) {
+		data->irdata_error = 1;
+		return 0x0;
+	}
+
+	while (count > 0) {
+		val <<= 1;
+		if (test_bit(data->irdata_off, data->irdata))
+			val |= 0x1;
+		count--;
+		data->irdata_off++;
+	}
+
+	return val;
+}
+
+/* Reads 16 cells and converts them to a byte */
+static u8
+wbcir_rc6cells_to_byte(struct wbcir_data *data)
+{
+	u16 raw = get_bits(data, 16);
+	u8 val = 0x00;
+	int bit;
+
+	for (bit = 0; bit < 8; bit++) {
+		switch (raw & 0x03) {
+		case 0x01:
+			break;
+		case 0x02:
+			val |= (0x01 << bit);
+			break;
+		default:
+			data->irdata_error = 1;
+			break;
+		}
+		raw >>= 2;
+	}
+
+	return val;
+}
+
+/* Decodes a number of bits from raw RC5 data */
+static u8
+wbcir_get_rc5bits(struct wbcir_data *data, unsigned int count)
+{
+	u16 raw = get_bits(data, count * 2);
+	u8 val = 0x00;
+	int bit;
+
+	for (bit = 0; bit < count; bit++) {
+		switch (raw & 0x03) {
+		case 0x01:
+			val |= (0x01 << bit);
+			break;
+		case 0x02:
+			break;
+		default:
+			data->irdata_error = 1;
+			break;
+		}
+		raw >>= 2;
+	}
+
+	return val;
+}
+
+static void
+wbcir_parse_rc6(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Normal bits are manchester coded as follows:
+	 * cell0 + cell1 = logic "0"
+	 * cell1 + cell0 = logic "1"
+	 *
+	 * The IR pulse has the following components:
+	 *
+	 * Leader		- 6 * cell1 - discarded
+	 * Gap    		- 2 * cell0 - discarded
+	 * Start bit		- Normal Coding - always "1"
+	 * Mode Bit 2 - 0	- Normal Coding
+	 * Toggle bit		- Normal Coding with double bit time,
+	 *			  e.g. cell0 + cell0 + cell1 + cell1
+	 *			  means logic "0".
+	 *
+	 * The rest depends on the mode, the following modes are known:
+	 *
+	 * MODE 0:
+	 *  Address Bit 7 - 0	- Normal Coding
+	 *  Command Bit 7 - 0	- Normal Coding
+	 *
+	 * MODE 6:
+	 *  The above Toggle Bit is used as a submode bit, 0 = A, 1 = B.
+	 *  Submode B is for pointing devices, only remotes using submode A
+	 *  are supported.
+	 *
+	 *  Customer range bit	- 0 => Customer = 7 bits, 0...127
+	 *                        1 => Customer = 15 bits, 32768...65535
+	 *  Customer Bits	- Normal Coding
+	 *
+	 *  Customer codes are allocated by Philips. The rest of the bits
+	 *  are customer dependent. The following is commonly used (and the
+	 *  only supported config):
+	 *
+	 *  Toggle Bit		- Normal Coding
+	 *  Address Bit 6 - 0	- Normal Coding
+	 *  Command Bit 7 - 0	- Normal Coding
+	 *
+	 * All modes are followed by at least 6 * cell0.
+	 *
+	 * MODE 0 msglen:
+	 *  1 * 2 (start bit) + 3 * 2 (mode) + 2 * 2 (toggle) +
+	 *  8 * 2 (address) + 8 * 2 (command) =
+	 *  44 cells
+	 *
+	 * MODE 6A msglen:
+	 *  1 * 2 (start bit) + 3 * 2 (mode) + 2 * 2 (submode) +
+	 *  1 * 2 (customer range bit) + 7/15 * 2 (customer bits) +
+	 *  1 * 2 (toggle bit) + 7 * 2 (address) + 8 * 2 (command) =
+	 *  60 - 76 cells
+	 */
+	u8 mode;
+	u8 toggle;
+	u16 customer = 0x0;
+	u8 address;
+	u8 command;
+	u32 scancode;
+
+	/* Leader mark */
+	while (get_bits(data, 1) && !data->irdata_error)
+		/* Do nothing */;
+
+	/* Leader space */
+	if (get_bits(data, 1)) {
+		dev_dbg(dev, "RC6 - Invalid leader space\n");
+		return;
+	}
+
+	/* Start bit */
+	if (get_bits(data, 2) != 0x02) {
+		dev_dbg(dev, "RC6 - Invalid start bit\n");
+		return;
+	}
+
+	/* Mode */
+	mode = get_bits(data, 6);
+	switch (mode) {
+	case 0x15: /* 010101 = b000 */
+		mode = 0;
+		break;
+	case 0x29: /* 101001 = b110 */
+		mode = 6;
+		break;
+	default:
+		dev_dbg(dev, "RC6 - Invalid mode\n");
+		return;
+	}
+
+	/* Toggle bit / Submode bit */
+	toggle = get_bits(data, 4);
+	switch (toggle) {
+	case 0x03:
+		toggle = 0;
+		break;
+	case 0x0C:
+		toggle = 1;
+		break;
+	default:
+		dev_dbg(dev, "RC6 - Toggle bit error\n");
+		break;
+	}
+
+	/* Customer */
+	if (mode == 6) {
+		if (toggle != 0) {
+			dev_dbg(dev, "RC6B - Not Supported\n");
+			return;
+		}
+
+		customer = wbcir_rc6cells_to_byte(data);
+
+		if (customer & 0x80) {
+			/* 15 bit customer value */
+			customer <<= 8;
+			customer |= wbcir_rc6cells_to_byte(data);
+		}
+	}
+
+	/* Address */
+	address = wbcir_rc6cells_to_byte(data);
+	if (mode == 6) {
+		toggle = address >> 7;
+		address &= 0x7F;
+	}
+
+	/* Command */
+	command = wbcir_rc6cells_to_byte(data);
+
+	/* Create scancode */
+	scancode =  command;
+	scancode |= address << 8;
+	scancode |= customer << 16;
+
+	/* Last sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "RC6 - Cell error(s)\n");
+		return;
+	}
+
+	dev_info(dev, "IR-RC6 ad 0x%02X cm 0x%02X cu 0x%04X "
+		"toggle %u mode %u scan 0x%08X\n",
+		address,
+		command,
+		customer,
+		(unsigned int)toggle,
+		(unsigned int)mode,
+		scancode);
+
+	wbcir_keydown(data, scancode, toggle);
+}
+
+static void
+wbcir_parse_rc5(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Bits are manchester coded as follows:
+	 * cell1 + cell0 = logic "0"
+	 * cell0 + cell1 = logic "1"
+	 * (i.e. the reverse of RC6)
+	 *
+	 * Start bit 1		- "1" - discarded
+	 * Start bit 2		- Must be inverted to get command bit 6
+	 * Toggle bit
+	 * Address Bit 4 - 0
+	 * Command Bit 5 - 0
+	 */
+	u8 toggle;
+	u8 address;
+	u8 command;
+	u32 scancode;
+
+	/* Start bit 1 */
+	if (!get_bits(data, 1)) {
+		dev_dbg(dev, "RC5 - Invalid start bit\n");
+		return;
+	}
+
+	/* Start bit 2 */
+	if (!wbcir_get_rc5bits(data, 1))
+		command = 0x40;
+	else
+		command = 0x00;
+
+	toggle   = wbcir_get_rc5bits(data, 1);
+	address  = wbcir_get_rc5bits(data, 5);
+	command |= wbcir_get_rc5bits(data, 6);
+	scancode = address << 7 | command;
+
+	/* Last sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "RC5 - Invalid message\n");
+		return;
+	}
+
+	dev_dbg(dev, "IR-RC5 ad %u cm %u t %u s %u\n",
+		(unsigned int)address,
+		(unsigned int)command,
+		(unsigned int)toggle,
+		(unsigned int)scancode);
+
+	wbcir_keydown(data, scancode, toggle);
+}
+
+static void
+wbcir_parse_nec(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Each bit represents 560 us.
+	 *
+	 * Leader		- 9 ms burst
+	 * Gap			- 4.5 ms silence
+	 * Address1 bit 0 - 7	- Address 1
+	 * Address2 bit 0 - 7	- Address 2
+	 * Command1 bit 0 - 7	- Command 1
+	 * Command2 bit 0 - 7	- Command 2
+	 *
+	 * Note the bit order!
+	 *
+	 * With the old NEC protocol, Address2 was the inverse of Address1
+	 * and Command2 was the inverse of Command1 and were used as
+	 * an error check.
+	 *
+	 * With NEC extended, Address1 is the LSB of the Address and
+	 * Address2 is the MSB, Command parsing remains unchanged.
+	 *
+	 * A repeat message is coded as:
+	 * Leader		- 9 ms burst
+	 * Gap			- 2.25 ms silence
+	 * Repeat		- 560 us active
+	 */
+	u8 address1;
+	u8 address2;
+	u8 command1;
+	u8 command2;
+	u16 address;
+	u32 scancode;
+
+	/* Leader mark */
+	while (get_bits(data, 1) && !data->irdata_error)
+		/* Do nothing */;
+
+	/* Leader space */
+	if (get_bits(data, 4)) {
+		dev_dbg(dev, "NEC - Invalid leader space\n");
+		return;
+	}
+
+	/* Repeat? */
+	if (get_bits(data, 1)) {
+		if (!data->keypressed) {
+			dev_dbg(dev, "NEC - Stray repeat message\n");
+			return;
+		}
+
+		dev_dbg(dev, "IR-NEC repeat s %u\n",
+			(unsigned int)data->last_scancode);
+
+		wbcir_keydown(data, data->last_scancode, data->last_toggle);
+		return;
+	}
+
+	/* Remaining leader space */
+	if (get_bits(data, 3)) {
+		dev_dbg(dev, "NEC - Invalid leader space\n");
+		return;
+	}
+
+	address1  = bitrev8(get_bits(data, 8));
+	address2  = bitrev8(get_bits(data, 8));
+	command1  = bitrev8(get_bits(data, 8));
+	command2  = bitrev8(get_bits(data, 8));
+
+	/* Sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "NEC - Invalid message\n");
+		return;
+	}
+
+	/* Check command validity */
+	if (command1 != ~command2) {
+		dev_dbg(dev, "NEC - Command bytes mismatch\n");
+		return;
+	}
+
+	/* Check for extended NEC protocol */
+	address = address1;
+	if (address1 != ~address2)
+		address |= address2 << 8;
+
+	scancode = address << 8 | command1;
+
+	dev_dbg(dev, "IR-NEC ad %u cm %u s %u\n",
+		(unsigned int)address,
+		(unsigned int)command1,
+		(unsigned int)scancode);
+
+	wbcir_keydown(data, scancode, !data->last_toggle);
+}
+
+
+
+/*****************************************************************************
+ *
+ * INTERRUPT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static irqreturn_t
+wbcir_irq_handler(int irqno, void *cookie)
+{
+	struct pnp_dev *device = cookie;
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	struct device *dev = &device->dev;
+	u8 status;
+	unsigned long flags;
+	u8 irdata[8];
+	int i;
+	unsigned int hw;
+
+	spin_lock_irqsave(&wbcir_lock, flags);
+
+	wbcir_select_bank(data, WBCIR_BANK_0);
+
+	status = inb(data->sbase + WBCIR_REG_SP3_EIR);
+
+	if (!(status & (WBCIR_IRQ_RX | WBCIR_IRQ_ERR))) {
+		spin_unlock_irqrestore(&wbcir_lock, flags);
+		return IRQ_NONE;
+	}
+
+	if (status & WBCIR_IRQ_ERR)
+		data->irdata_error = 1;
+
+	if (!(status & WBCIR_IRQ_RX))
+		goto out;
+
+	/* Since RXHDLEV is set, at least 8 bytes are in the FIFO */
+	insb(data->sbase + WBCIR_REG_SP3_RXDATA, &irdata[0], 8);
+
+	for (i = 0; i < sizeof(irdata); i++) {
+		hw = hweight8(irdata[i]);
+		if (hw > 4)
+			add_irdata_bit(data, 0);
+		else
+			add_irdata_bit(data, 1);
+
+		if (hw == 8)
+			data->idle_count++;
+		else
+			data->idle_count = 0;
+	}
+
+	if (data->idle_count > WBCIR_MAX_IDLE_BYTES) {
+		/* Set RXINACTIVE... */
+		outb(WBCIR_RX_DISABLE, data->sbase + WBCIR_REG_SP3_ASCR);
+
+		/* ...and drain the FIFO */
+		while (inb(data->sbase + WBCIR_REG_SP3_LSR) & WBCIR_RX_AVAIL)
+			inb(data->sbase + WBCIR_REG_SP3_RXDATA);
+
+		dev_dbg(dev, "IRDATA:\n");
+		for (i = 0; i < data->irdata_count; i += BITS_PER_LONG)
+			dev_dbg(dev, "0x%08lX\n", data->irdata[i/BITS_PER_LONG]);
+
+		switch (protocol) {
+		case IR_PROTOCOL_RC5:
+			wbcir_parse_rc5(dev, data);
+			break;
+		case IR_PROTOCOL_RC6:
+			wbcir_parse_rc6(dev, data);
+			break;
+		case IR_PROTOCOL_NEC:
+			wbcir_parse_nec(dev, data);
+			break;
+		}
+
+		wbcir_reset_irdata(data);
+		data->idle_count = 0;
+	}
+
+out:
+	spin_unlock_irqrestore(&wbcir_lock, flags);
+	return IRQ_HANDLED;
+}
+
+
+
+/*****************************************************************************
+ *
+ * SUSPEND/RESUME FUNCTIONS
+ *
+ *****************************************************************************/
+
+static void
+wbcir_shutdown(struct pnp_dev *device)
+{
+	struct device *dev = &device->dev;
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	int do_wake = 1;
+	u8 match[11];
+	u8 mask[11];
+	u8 rc6_csl = 0;
+	int i;
+
+	memset(match, 0, sizeof(match));
+	memset(mask, 0, sizeof(mask));
+
+	if (wake_sc == INVALID_SCANCODE || !device_may_wakeup(dev)) {
+		do_wake = 0;
+		goto finish;
+	}
+
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+		if (wake_sc > 0xFFF) {
+			do_wake = 0;
+			dev_err(dev, "RC5 - Invalid wake scancode\n");
+			break;
+		}
+
+		/* Mask = 13 bits, ex toggle */
+		mask[0] = 0xFF;
+		mask[1] = 0x17;
+
+		match[0]  = (wake_sc & 0x003F);      /* 6 command bits */
+		match[0] |= (wake_sc & 0x0180) >> 1; /* 2 address bits */
+		match[1]  = (wake_sc & 0x0E00) >> 9; /* 3 address bits */
+		if (!(wake_sc & 0x0040))             /* 2nd start bit  */
+			match[1] |= 0x10;
+
+		break;
+
+	case IR_PROTOCOL_NEC:
+		if (wake_sc > 0xFFFFFF) {
+			do_wake = 0;
+			dev_err(dev, "NEC - Invalid wake scancode\n");
+			break;
+		}
+
+		mask[0] = mask[1] = mask[2] = mask[3] = 0xFF;
+
+		match[1] = bitrev8((wake_sc & 0xFF));
+		match[0] = ~match[1];
+
+		match[3] = bitrev8((wake_sc & 0xFF00) >> 8);
+		if (wake_sc > 0xFFFF)
+			match[2] = bitrev8((wake_sc & 0xFF0000) >> 16);
+		else
+			match[2] = ~match[3];
+
+		break;
+
+	case IR_PROTOCOL_RC6:
+
+		if (wake_rc6mode == 0) {
+			if (wake_sc > 0xFFFF) {
+				do_wake = 0;
+				dev_err(dev, "RC6 - Invalid wake scancode\n");
+				break;
+			}
+
+			/* Command */
+			match[0] = wbcir_to_rc6cells(wake_sc >>  0);
+			mask[0]  = 0xFF;
+			match[1] = wbcir_to_rc6cells(wake_sc >>  4);
+			mask[1]  = 0xFF;
+
+			/* Address */
+			match[2] = wbcir_to_rc6cells(wake_sc >>  8);
+			mask[2]  = 0xFF;
+			match[3] = wbcir_to_rc6cells(wake_sc >> 12);
+			mask[3]  = 0xFF;
+
+			/* Header */
+			match[4] = 0x50; /* mode1 = mode0 = 0, ignore toggle */
+			mask[4]  = 0xF0;
+			match[5] = 0x09; /* start bit = 1, mode2 = 0 */
+			mask[5]  = 0x0F;
+
+			rc6_csl = 44;
+
+		} else if (wake_rc6mode == 6) {
+			i = 0;
+
+			/* Command */
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  0);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  4);
+			mask[i++] = 0xFF;
+
+			/* Address + Toggle */
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  8);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 12);
+			mask[i++] = 0x3F;
+
+			/* Customer bits 7 - 0 */
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 16);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 20);
+			mask[i++] = 0xFF;
+
+			if (wake_sc & 0x80000000) {
+				/* Customer range bit and bits 15 - 8 */
+				match[i]  = wbcir_to_rc6cells(wake_sc >> 24);
+				mask[i++] = 0xFF;
+				match[i]  = wbcir_to_rc6cells(wake_sc >> 28);
+				mask[i++] = 0xFF;
+				rc6_csl = 76;
+			} else if (wake_sc <= 0x007FFFFF) {
+				rc6_csl = 60;
+			} else {
+				do_wake = 0;
+				dev_err(dev, "RC6 - Invalid wake scancode\n");
+				break;
+			}
+
+			/* Header */
+			match[i]  = 0x93; /* mode1 = mode0 = 1, submode = 0 */
+			mask[i++] = 0xFF;
+			match[i]  = 0x0A; /* start bit = 1, mode2 = 1 */
+			mask[i++] = 0x0F;
+
+		} else {
+			do_wake = 0;
+			dev_err(dev, "RC6 - Invalid wake mode\n");
+		}
+
+		break;
+
+	default:
+		do_wake = 0;
+		break;
+	}
+
+finish:
+	if (do_wake) {
+		/* Set compare and compare mask */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX,
+			       WBCIR_REGSEL_COMPARE | WBCIR_REG_ADDR0,
+			       0x3F);
+		outsb(data->wbase + WBCIR_REG_WCEIR_DATA, match, 11);
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX,
+			       WBCIR_REGSEL_MASK | WBCIR_REG_ADDR0,
+			       0x3F);
+		outsb(data->wbase + WBCIR_REG_WCEIR_DATA, mask, 11);
+
+		/* RC6 Compare String Len */
+		outb(rc6_csl, data->wbase + WBCIR_REG_WCEIR_CSL);
+
+		/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+		/* Clear BUFF_EN, Clear END_EN, Set MATCH_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x01, 0x07);
+
+		/* Set CEIR_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x01, 0x01);
+
+	} else {
+		/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+		/* Clear CEIR_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+	}
+
+	/* Disable interrupts */
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+}
+
+static int
+wbcir_suspend(struct pnp_dev *device, pm_message_t state)
+{
+	wbcir_shutdown(device);
+	return 0;
+}
+
+static int
+wbcir_resume(struct pnp_dev *device)
+{
+	struct wbcir_data *data = pnp_get_drvdata(device);
+
+	/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	/* Clear CEIR_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+
+	/* Enable interrupts */
+	wbcir_reset_irdata(data);
+	outb(WBCIR_IRQ_RX | WBCIR_IRQ_ERR, data->sbase + WBCIR_REG_SP3_IER);
+
+	return 0;
+}
+
+
+
+/*****************************************************************************
+ *
+ * SETUP/INIT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static void
+wbcir_cfg_ceir(struct wbcir_data *data)
+{
+	u8 tmp;
+
+	/* Set PROT_SEL, RX_INV, Clear CEIR_EN (needed for the led) */
+	tmp = protocol << 4;
+	if (invert)
+		tmp |= 0x08;
+	outb(tmp, data->wbase + WBCIR_REG_WCEIR_CTL);
+
+	/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+	/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	/* Set RC5 cell time to correspond to 36 kHz */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CFG1, 0x4A, 0x7F);
+
+	/* Set IRTX_INV */
+	if (invert)
+		outb(0x04, data->ebase + WBCIR_REG_ECEIR_CCTL);
+	else
+		outb(0x00, data->ebase + WBCIR_REG_ECEIR_CCTL);
+
+	/*
+	 * Clear IR LED, set SP3 clock to 24Mhz
+	 * set SP3_IRRX_SW to binary 01, helpfully not documented
+	 */
+	outb(0x10, data->ebase + WBCIR_REG_ECEIR_CTS);
+}
+
+static int __devinit
+wbcir_probe(struct pnp_dev *device, const struct pnp_device_id *dev_id)
+{
+	struct device *dev = &device->dev;
+	struct wbcir_data *data;
+	int err;
+
+	if (!(pnp_port_len(device, 0) == EHFUNC_IOMEM_LEN &&
+	      pnp_port_len(device, 1) == WAKEUP_IOMEM_LEN &&
+	      pnp_port_len(device, 2) == SP_IOMEM_LEN)) {
+		dev_err(dev, "Invalid resources\n");
+		return -ENODEV;
+	}
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data) {
+		err = -ENOMEM;
+		goto exit;
+	}
+
+	pnp_set_drvdata(device, data);
+
+	data->ebase = pnp_port_start(device, 0);
+	data->wbase = pnp_port_start(device, 1);
+	data->sbase = pnp_port_start(device, 2);
+	data->irq = pnp_irq(device, 0);
+
+	if (data->wbase == 0 || data->ebase == 0 ||
+	    data->sbase == 0 || data->irq == 0) {
+		err = -ENODEV;
+		dev_err(dev, "Invalid resources\n");
+		goto exit_free_data;
+	}
+
+	dev_dbg(&device->dev, "Found device "
+		"(w: 0x%lX, e: 0x%lX, s: 0x%lX, i: %u)\n",
+		data->wbase, data->ebase, data->sbase, data->irq);
+
+	if (!request_region(data->wbase, WAKEUP_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->wbase, data->wbase + WAKEUP_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_free_data;
+	}
+
+	if (!request_region(data->ebase, EHFUNC_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->ebase, data->ebase + EHFUNC_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_release_wbase;
+	}
+
+	if (!request_region(data->sbase, SP_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->sbase, data->sbase + SP_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_release_ebase;
+	}
+
+	err = request_irq(data->irq, wbcir_irq_handler,
+			  IRQF_DISABLED, DRVNAME, device);
+	if (err) {
+		dev_err(dev, "Failed to claim IRQ %u\n", data->irq);
+		err = -EBUSY;
+		goto exit_release_sbase;
+	}
+
+	led_trigger_register_simple("cir-tx", &data->txtrigger);
+	if (!data->txtrigger) {
+		err = -ENOMEM;
+		goto exit_free_irq;
+	}
+
+	led_trigger_register_simple("cir-rx", &data->rxtrigger);
+	if (!data->rxtrigger) {
+		err = -ENOMEM;
+		goto exit_unregister_txtrigger;
+	}
+
+	data->led.name = "cir::activity";
+	data->led.default_trigger = "cir-rx";
+	data->led.brightness_set = wbcir_led_brightness_set;
+	data->led.brightness_get = wbcir_led_brightness_get;
+	err = led_classdev_register(&device->dev, &data->led);
+	if (err)
+		goto exit_unregister_rxtrigger;
+
+	data->input_dev = input_allocate_device();
+	if (!data->input_dev) {
+		err = -ENOMEM;
+		goto exit_unregister_led;
+	}
+
+	data->input_dev->evbit[0] = BIT(EV_KEY);
+	data->input_dev->name = WBCIR_NAME;
+	data->input_dev->phys = "wbcir/cir0";
+	data->input_dev->id.bustype = BUS_HOST;
+	data->input_dev->id.vendor  = PCI_VENDOR_ID_WINBOND;
+	data->input_dev->id.product = WBCIR_ID_FAMILY;
+	data->input_dev->id.version = WBCIR_ID_CHIP;
+	data->input_dev->getkeycode = wbcir_getkeycode;
+	data->input_dev->setkeycode = wbcir_setkeycode;
+	input_set_capability(data->input_dev, EV_MSC, MSC_SCAN);
+	input_set_drvdata(data->input_dev, data);
+
+	err = input_register_device(data->input_dev);
+	if (err)
+		goto exit_free_input;
+
+	data->last_scancode = INVALID_SCANCODE;
+	INIT_LIST_HEAD(&data->keytable);
+	setup_timer(&data->timer_keyup, wbcir_keyup, (unsigned long)data);
+
+	/* Load default keymaps */
+	if (protocol == IR_PROTOCOL_RC6) {
+		int i;
+		for (i = 0; i < ARRAY_SIZE(rc6_def_keymap); i++) {
+			err = wbcir_setkeycode(data->input_dev,
+					       (int)rc6_def_keymap[i].scancode,
+					       (int)rc6_def_keymap[i].keycode);
+			if (err)
+				goto exit_unregister_keys;
+		}
+	}
+
+	device_init_wakeup(&device->dev, 1);
+
+	wbcir_cfg_ceir(data);
+
+	/* Disable interrupts */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+
+	/* Enable extended mode */
+	wbcir_select_bank(data, WBCIR_BANK_2);
+	outb(WBCIR_EXT_ENABLE, data->sbase + WBCIR_REG_SP3_EXCR1);
+
+	/*
+	 * Configure baud generator, IR data will be sampled at
+	 * a bitrate of: (24Mhz * prescaler) / (divisor * 16).
+	 *
+	 * The ECIR registers include a flag to change the
+	 * 24Mhz clock freq to 48Mhz.
+	 *
+	 * It's not documented in the specs, but fifo levels
+	 * other than 16 seems to be unsupported.
+	 */
+
+	/* prescaler 1.0, tx/rx fifo lvl 16 */
+	outb(0x30, data->sbase + WBCIR_REG_SP3_EXCR2);
+
+	/* Set baud divisor to generate one byte per bit/cell */
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+		outb(0xA7, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	case IR_PROTOCOL_RC6:
+		outb(0x53, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	case IR_PROTOCOL_NEC:
+		outb(0x69, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	}
+	outb(0x00, data->sbase + WBCIR_REG_SP3_BGDH);
+
+	/* Set CEIR mode */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(0xC0, data->sbase + WBCIR_REG_SP3_MCR);
+	inb(data->sbase + WBCIR_REG_SP3_LSR); /* Clear LSR */
+	inb(data->sbase + WBCIR_REG_SP3_MSR); /* Clear MSR */
+
+	/* Disable RX demod, run-length encoding/decoding, set freq span */
+	wbcir_select_bank(data, WBCIR_BANK_7);
+	outb(0x10, data->sbase + WBCIR_REG_SP3_RCCFG);
+
+	/* Disable timer */
+	wbcir_select_bank(data, WBCIR_BANK_4);
+	outb(0x00, data->sbase + WBCIR_REG_SP3_IRCR1);
+
+	/* Enable MSR interrupt, Clear AUX_IRX */
+	wbcir_select_bank(data, WBCIR_BANK_5);
+	outb(0x00, data->sbase + WBCIR_REG_SP3_IRCR2);
+
+	/* Disable CRC */
+	wbcir_select_bank(data, WBCIR_BANK_6);
+	outb(0x20, data->sbase + WBCIR_REG_SP3_IRCR3);
+
+	/* Set RX/TX (de)modulation freq, not really used */
+	wbcir_select_bank(data, WBCIR_BANK_7);
+	outb(0xF2, data->sbase + WBCIR_REG_SP3_IRRXDC);
+	outb(0x69, data->sbase + WBCIR_REG_SP3_IRTXMC);
+
+	/* Set invert and pin direction */
+	if (invert)
+		outb(0x10, data->sbase + WBCIR_REG_SP3_IRCFG4);
+	else
+		outb(0x00, data->sbase + WBCIR_REG_SP3_IRCFG4);
+
+	/* Set FIFO thresholds (RX = 8, TX = 3), reset RX/TX */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(0x97, data->sbase + WBCIR_REG_SP3_FCR);
+
+	/* Clear AUX status bits */
+	outb(0xE0, data->sbase + WBCIR_REG_SP3_ASCR);
+
+	/* Enable interrupts */
+	outb(WBCIR_IRQ_RX | WBCIR_IRQ_ERR, data->sbase + WBCIR_REG_SP3_IER);
+
+	return 0;
+
+exit_unregister_keys:
+	if (!list_empty(&data->keytable)) {
+		struct wbcir_keyentry *key;
+		struct wbcir_keyentry *keytmp;
+
+		list_for_each_entry_safe(key, keytmp, &data->keytable, list) {
+			list_del(&key->list);
+			kfree(key);
+		}
+	}
+	input_unregister_device(data->input_dev);
+	/* Can't call input_free_device on an unregistered device */
+	data->input_dev = NULL;
+exit_free_input:
+	input_free_device(data->input_dev);
+exit_unregister_led:
+	led_classdev_unregister(&data->led);
+exit_unregister_rxtrigger:
+	led_trigger_unregister_simple(data->rxtrigger);
+exit_unregister_txtrigger:
+	led_trigger_unregister_simple(data->txtrigger);
+exit_free_irq:
+	free_irq(data->irq, device);
+exit_release_sbase:
+	release_region(data->sbase, SP_IOMEM_LEN);
+exit_release_ebase:
+	release_region(data->ebase, EHFUNC_IOMEM_LEN);
+exit_release_wbase:
+	release_region(data->wbase, WAKEUP_IOMEM_LEN);
+exit_free_data:
+	kfree(data);
+	pnp_set_drvdata(device, NULL);
+exit:
+	return err;
+}
+
+static void __devexit
+wbcir_remove(struct pnp_dev *device)
+{
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	struct wbcir_keyentry *key;
+	struct wbcir_keyentry *keytmp;
+
+	/* Disable interrupts */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+
+	del_timer_sync(&data->timer_keyup);
+
+	free_irq(data->irq, device);
+
+	/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+	/* Clear CEIR_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+
+	/* Clear BUFF_EN, END_EN, MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	/* This will generate a keyup event if necessary */
+	input_unregister_device(data->input_dev);
+
+	led_trigger_unregister_simple(data->rxtrigger);
+	led_trigger_unregister_simple(data->txtrigger);
+	led_classdev_unregister(&data->led);
+
+	/* This is ok since &data->led isn't actually used */
+	wbcir_led_brightness_set(&data->led, LED_OFF);
+
+	release_region(data->wbase, WAKEUP_IOMEM_LEN);
+	release_region(data->ebase, EHFUNC_IOMEM_LEN);
+	release_region(data->sbase, SP_IOMEM_LEN);
+
+	list_for_each_entry_safe(key, keytmp, &data->keytable, list) {
+		list_del(&key->list);
+		kfree(key);
+	}
+
+	kfree(data);
+
+	pnp_set_drvdata(device, NULL);
+}
+
+static const struct pnp_device_id wbcir_ids[] = {
+	{ "WEC1022", 0 },
+	{ "", 0 }
+};
+MODULE_DEVICE_TABLE(pnp, wbcir_ids);
+
+static struct pnp_driver wbcir_driver = {
+	.name     = WBCIR_NAME,
+	.id_table = wbcir_ids,
+	.probe    = wbcir_probe,
+	.remove   = __devexit_p(wbcir_remove),
+	.suspend  = wbcir_suspend,
+	.resume   = wbcir_resume,
+	.shutdown = wbcir_shutdown
+};
+
+static int __init
+wbcir_init(void)
+{
+	int ret;
+
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+	case IR_PROTOCOL_NEC:
+	case IR_PROTOCOL_RC6:
+		break;
+	default:
+		printk(KERN_ERR DRVNAME ": Invalid protocol argument\n");
+		return -EINVAL;
+	}
+
+	ret = pnp_register_driver(&wbcir_driver);
+	if (ret)
+		printk(KERN_ERR DRVNAME ": Unable to register driver\n");
+
+	return ret;
+}
+
+static void __exit
+wbcir_exit(void)
+{
+	pnp_unregister_driver(&wbcir_driver);
+}
+
+MODULE_AUTHOR("David Härdeman <david@hardeman.nu>");
+MODULE_DESCRIPTION("Winbond SuperI/O Consumer IR Driver");
+MODULE_LICENSE("GPL");
+
+module_init(wbcir_init);
+module_exit(wbcir_exit);
+
+

-- 
David Härdeman

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware
@ 2009-08-13 18:57   ` David Härdeman
  0 siblings, 0 replies; 16+ messages in thread
From: David Härdeman @ 2009-08-13 18:57 UTC (permalink / raw)
  To: linux-kernel; +Cc: linux-input, akpm, dmitry.torokhov

[-- Attachment #1: add-a-driver-for-the-winbond-wpcd376i-cir-functionality --]
[-- Type: text/plain, Size: 48545 bytes --]

Add a driver for the the Consumer IR (CIR) functionality of the Winbond
WPCD376I chipset (found on e.g. Intel DG45FC motherboards).

Changes since the first version:

  o converted from an ACPI to a PNP driver using a patch provided by 
    Bjorn Helgaas <bjorn.helgaas@hp.com>

Changes since the second version:

  o misc fixes to the irqhandler after discovering that the FIFO
    enforces 16 levels (was a separate patch in the -mm tree)
  o __devexit annotations
  o KEY_NUMERIC_[0-9] instead of KEY_[0-9]
  o EV_MSC/MSC_SCAN instead of "last_scancode" sysfs attr
  o last three points based on feedback from Dmitry Torokhov
    <dmitry.torokhov@gmail.com>

Signed-off-by: David Härdeman <david@hardeman.nu>
Reviewed-by: Jesse Barnes <jbarnes@virtuousgeek.org>

Index: linux-2.6/MAINTAINERS
===================================================================
--- linux-2.6.orig/MAINTAINERS	2009-08-09 11:32:42.000000000 +0200
+++ linux-2.6/MAINTAINERS	2009-08-09 11:44:35.000000000 +0200
@@ -6448,6 +6448,12 @@
  S:	Maintained
  F:	drivers/scsi/wd7000.c
  
+WINBOND CIR DRIVER
+P:	David Härdeman
+M:	david@hardeman.nu
+S:	Maintained
+F:	drivers/input/misc/winbond-cir.c
+
  WIMAX STACK
  P:	Inaky Perez-Gonzalez
  M:	inaky.perez-gonzalez@intel.com
Index: linux-2.6/drivers/input/misc/Kconfig
===================================================================
--- linux-2.6.orig/drivers/input/misc/Kconfig	2009-08-09 11:32:42.000000000 +0200
+++ linux-2.6/drivers/input/misc/Kconfig	2009-08-11 18:20:41.000000000 +0200
@@ -222,6 +222,22 @@
  	  To compile this driver as a module, choose M here: the
  	  module will be called sgi_btns.
  
+config INPUT_WINBOND_CIR
+	tristate "Winbond IR remote control"
+	depends on X86 && PNP
+	select LEDS_CLASS
+	select BITREVERSE
+	help
+	  Say Y here if you want to use the IR remote functionality found
+	  in some Winbond SuperI/O chips. Currently only the WPCD376I
+	  chip is supported (included in some Intel Media series motherboards).
+
+	  IR Receive and wake-on-IR from suspend and power-off is currently
+	  supported.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called winbond_cir.
+
  config HP_SDC_RTC
  	tristate "HP SDC Real Time Clock"
  	depends on (GSC || HP300) && SERIO
Index: linux-2.6/drivers/input/misc/Makefile
===================================================================
--- linux-2.6.orig/drivers/input/misc/Makefile	2009-08-09 11:32:42.000000000 +0200
+++ linux-2.6/drivers/input/misc/Makefile	2009-08-13 18:25:06.000000000 +0200
@@ -24,5 +24,6 @@
  obj-$(CONFIG_INPUT_SPARCSPKR)		+= sparcspkr.o
  obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON)	+= twl4030-pwrbutton.o
  obj-$(CONFIG_INPUT_UINPUT)		+= uinput.o
+obj-$(CONFIG_INPUT_WINBOND_CIR)		+= winbond-cir.o
  obj-$(CONFIG_INPUT_WISTRON_BTNS)	+= wistron_btns.o
  obj-$(CONFIG_INPUT_YEALINK)		+= yealink.o
Index: linux-2.6/drivers/input/misc/winbond-cir.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/drivers/input/misc/winbond-cir.c	2009-08-13 20:33:06.000000000 +0200
@@ -0,0 +1,1614 @@
+/*
+ *  winbond-cir.c - Driver for the Consumer IR functionality of Winbond
+ *                  SuperI/O chips.
+ *
+ *  Currently supports the Winbond WPCD376i chip (PNP id WEC1022), but
+ *  could probably support others (Winbond WEC102X, NatSemi, etc)
+ *  with minor modifications.
+ *
+ *  Original Author: David Härdeman <david@hardeman.nu>
+ *     Copyright (C) 2009 David Härdeman <david@hardeman.nu>
+ *
+ *  Dedicated to Matilda, my newborn daughter, without whose loving attention
+ *  this driver would have been finished in half the time and with a fraction
+ *  of the bugs.
+ *
+ *  Written using:
+ *    o Winbond WPCD376I datasheet helpfully provided by Jesse Barnes at Intel
+ *    o NatSemi PC87338/PC97338 datasheet (for the serial port stuff)
+ *    o DSDT dumps
+ *
+ *  Supported features:
+ *    o RC6
+ *    o Wake-On-CIR functionality
+ *
+ *  To do:
+ *    o Test NEC and RC5
+ *
+ *  Left as an exercise for the reader:
+ *    o Learning (I have neither the hardware, nor the need)
+ *    o IR Transmit (ibid)
+ *
+ *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/pnp.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/pci_ids.h>
+#include <linux/io.h>
+#include <linux/bitrev.h>
+#include <linux/bitops.h>
+
+#define DRVNAME "winbond-cir"
+
+/* CEIR Wake-Up Registers, relative to data->wbase                      */
+#define WBCIR_REG_WCEIR_CTL	0x03 /* CEIR Receiver Control		*/
+#define WBCIR_REG_WCEIR_STS	0x04 /* CEIR Receiver Status		*/
+#define WBCIR_REG_WCEIR_EV_EN	0x05 /* CEIR Receiver Event Enable	*/
+#define WBCIR_REG_WCEIR_CNTL	0x06 /* CEIR Receiver Counter Low	*/
+#define WBCIR_REG_WCEIR_CNTH	0x07 /* CEIR Receiver Counter High	*/
+#define WBCIR_REG_WCEIR_INDEX	0x08 /* CEIR Receiver Index		*/
+#define WBCIR_REG_WCEIR_DATA	0x09 /* CEIR Receiver Data		*/
+#define WBCIR_REG_WCEIR_CSL	0x0A /* CEIR Re. Compare Strlen		*/
+#define WBCIR_REG_WCEIR_CFG1	0x0B /* CEIR Re. Configuration 1	*/
+#define WBCIR_REG_WCEIR_CFG2	0x0C /* CEIR Re. Configuration 2	*/
+
+/* CEIR Enhanced Functionality Registers, relative to data->ebase       */
+#define WBCIR_REG_ECEIR_CTS	0x00 /* Enhanced IR Control Status	*/
+#define WBCIR_REG_ECEIR_CCTL	0x01 /* Infrared Counter Control	*/
+#define WBCIR_REG_ECEIR_CNT_LO	0x02 /* Infrared Counter LSB		*/
+#define WBCIR_REG_ECEIR_CNT_HI	0x03 /* Infrared Counter MSB		*/
+#define WBCIR_REG_ECEIR_IREM	0x04 /* Infrared Emitter Status		*/
+
+/* SP3 Banked Registers, relative to data->sbase                        */
+#define WBCIR_REG_SP3_BSR	0x03 /* Bank Select, all banks		*/
+				      /* Bank 0				*/
+#define WBCIR_REG_SP3_RXDATA	0x00 /* FIFO RX data (r)		*/
+#define WBCIR_REG_SP3_TXDATA	0x00 /* FIFO TX data (w)		*/
+#define WBCIR_REG_SP3_IER	0x01 /* Interrupt Enable		*/
+#define WBCIR_REG_SP3_EIR	0x02 /* Event Identification (r)	*/
+#define WBCIR_REG_SP3_FCR	0x02 /* FIFO Control (w)		*/
+#define WBCIR_REG_SP3_MCR	0x04 /* Mode Control			*/
+#define WBCIR_REG_SP3_LSR	0x05 /* Link Status			*/
+#define WBCIR_REG_SP3_MSR	0x06 /* Modem Status			*/
+#define WBCIR_REG_SP3_ASCR	0x07 /* Aux Status and Control		*/
+				      /* Bank 2				*/
+#define WBCIR_REG_SP3_BGDL	0x00 /* Baud Divisor LSB		*/
+#define WBCIR_REG_SP3_BGDH	0x01 /* Baud Divisor MSB		*/
+#define WBCIR_REG_SP3_EXCR1	0x02 /* Extended Control 1		*/
+#define WBCIR_REG_SP3_EXCR2	0x04 /* Extended Control 2		*/
+#define WBCIR_REG_SP3_TXFLV	0x06 /* TX FIFO Level			*/
+#define WBCIR_REG_SP3_RXFLV	0x07 /* RX FIFO Level			*/
+				      /* Bank 3				*/
+#define WBCIR_REG_SP3_MRID	0x00 /* Module Identification		*/
+#define WBCIR_REG_SP3_SH_LCR	0x01 /* LCR Shadow			*/
+#define WBCIR_REG_SP3_SH_FCR	0x02 /* FCR Shadow			*/
+				      /* Bank 4				*/
+#define WBCIR_REG_SP3_IRCR1	0x02 /* Infrared Control 1		*/
+				      /* Bank 5				*/
+#define WBCIR_REG_SP3_IRCR2	0x04 /* Infrared Control 2		*/
+				      /* Bank 6				*/
+#define WBCIR_REG_SP3_IRCR3	0x00 /* Infrared Control 3		*/
+#define WBCIR_REG_SP3_SIR_PW	0x02 /* SIR Pulse Width		*/
+				      /* Bank 7				*/
+#define WBCIR_REG_SP3_IRRXDC	0x00 /* IR RX Demod Control		*/
+#define WBCIR_REG_SP3_IRTXMC	0x01 /* IR TX Mod Control		*/
+#define WBCIR_REG_SP3_RCCFG	0x02 /* CEIR Config			*/
+#define WBCIR_REG_SP3_IRCFG1	0x04 /* Infrared Config 1		*/
+#define WBCIR_REG_SP3_IRCFG4	0x07 /* Infrared Config 4		*/
+
+/*
+ * Magic values follow
+ */
+
+/* No interrupts for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_NONE		0x00
+/* RX data bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_RX		0x01
+/* Over/Under-flow bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_ERR		0x04
+/* Led enable/disable bit for WBCIR_REG_ECEIR_CTS */
+#define WBCIR_LED_ENABLE	0x80
+/* RX data available bit for WBCIR_REG_SP3_LSR */
+#define WBCIR_RX_AVAIL		0x01
+/* RX disable bit for WBCIR_REG_SP3_ASCR */
+#define WBCIR_RX_DISABLE	0x20
+/* Extended mode enable bit for WBCIR_REG_SP3_EXCR1 */
+#define WBCIR_EXT_ENABLE	0x01
+/* Select compare register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */
+#define WBCIR_REGSEL_COMPARE	0x10
+/* Select mask register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */
+#define WBCIR_REGSEL_MASK	0x20
+/* Starting address of selected register in WBCIR_REG_WCEIR_INDEX */
+#define WBCIR_REG_ADDR0		0x00
+
+/* Valid banks for the SP3 UART */
+enum wbcir_bank {
+	WBCIR_BANK_0          = 0x00,
+	WBCIR_BANK_1          = 0x80,
+	WBCIR_BANK_2          = 0xE0,
+	WBCIR_BANK_3          = 0xE4,
+	WBCIR_BANK_4          = 0xE8,
+	WBCIR_BANK_5          = 0xEC,
+	WBCIR_BANK_6          = 0xF0,
+	WBCIR_BANK_7          = 0xF4,
+};
+
+/* Supported IR Protocols */
+enum wbcir_protocol {
+	IR_PROTOCOL_RC5          = 0x0,
+	IR_PROTOCOL_NEC          = 0x1,
+	IR_PROTOCOL_RC6          = 0x2,
+};
+
+/* Misc */
+#define WBCIR_NAME	"Winbond CIR"
+#define WBCIR_ID_FAMILY          0xF1 /* Family ID for the WPCD376I	*/
+#define	WBCIR_ID_CHIP            0x04 /* Chip ID for the WPCD376I	*/
+#define IR_KEYPRESS_TIMEOUT       250 /* FIXME: should be per-protocol? */
+#define INVALID_SCANCODE   0x7FFFFFFF /* Invalid with all protos	*/
+#define WAKEUP_IOMEM_LEN         0x10 /* Wake-Up I/O Reg Len		*/
+#define EHFUNC_IOMEM_LEN         0x10 /* Enhanced Func I/O Reg Len	*/
+#define SP_IOMEM_LEN             0x08 /* Serial Port 3 (IR) Reg Len	*/
+#define WBCIR_MAX_IDLE_BYTES       10
+
+static DEFINE_SPINLOCK(wbcir_lock);
+static DEFINE_RWLOCK(keytable_lock);
+
+struct wbcir_key {
+	u32 scancode;
+	unsigned int keycode;
+};
+
+struct wbcir_keyentry {
+	struct wbcir_key key;
+	struct list_head list;
+};
+
+static struct wbcir_key rc6_def_keymap[] = {
+	{ 0x800F0400, KEY_NUMERIC_0		},
+	{ 0x800F0401, KEY_NUMERIC_1		},
+	{ 0x800F0402, KEY_NUMERIC_2		},
+	{ 0x800F0403, KEY_NUMERIC_3		},
+	{ 0x800F0404, KEY_NUMERIC_4		},
+	{ 0x800F0405, KEY_NUMERIC_5		},
+	{ 0x800F0406, KEY_NUMERIC_6		},
+	{ 0x800F0407, KEY_NUMERIC_7		},
+	{ 0x800F0408, KEY_NUMERIC_8		},
+	{ 0x800F0409, KEY_NUMERIC_9		},
+	{ 0x800F041D, KEY_NUMERIC_STAR		},
+	{ 0x800F041C, KEY_NUMERIC_POUND		},
+	{ 0x800F0410, KEY_VOLUMEUP		},
+	{ 0x800F0411, KEY_VOLUMEDOWN		},
+	{ 0x800F0412, KEY_CHANNELUP		},
+	{ 0x800F0413, KEY_CHANNELDOWN		},
+	{ 0x800F040E, KEY_MUTE			},
+	{ 0x800F040D, KEY_VENDOR		}, /* Vista Logo Key */
+	{ 0x800F041E, KEY_UP			},
+	{ 0x800F041F, KEY_DOWN			},
+	{ 0x800F0420, KEY_LEFT			},
+	{ 0x800F0421, KEY_RIGHT			},
+	{ 0x800F0422, KEY_OK			},
+	{ 0x800F0423, KEY_ESC			},
+	{ 0x800F040F, KEY_INFO			},
+	{ 0x800F040A, KEY_CLEAR			},
+	{ 0x800F040B, KEY_ENTER			},
+	{ 0x800F045B, KEY_RED			},
+	{ 0x800F045C, KEY_GREEN			},
+	{ 0x800F045D, KEY_YELLOW		},
+	{ 0x800F045E, KEY_BLUE			},
+	{ 0x800F045A, KEY_TEXT			},
+	{ 0x800F0427, KEY_SWITCHVIDEOMODE	},
+	{ 0x800F040C, KEY_POWER			},
+	{ 0x800F0450, KEY_RADIO			},
+	{ 0x800F0448, KEY_PVR			},
+	{ 0x800F0447, KEY_AUDIO			},
+	{ 0x800F0426, KEY_EPG			},
+	{ 0x800F0449, KEY_CAMERA		},
+	{ 0x800F0425, KEY_TV			},
+	{ 0x800F044A, KEY_VIDEO			},
+	{ 0x800F0424, KEY_DVD			},
+	{ 0x800F0416, KEY_PLAY			},
+	{ 0x800F0418, KEY_PAUSE			},
+	{ 0x800F0419, KEY_STOP			},
+	{ 0x800F0414, KEY_FASTFORWARD		},
+	{ 0x800F041A, KEY_NEXT			},
+	{ 0x800F041B, KEY_PREVIOUS		},
+	{ 0x800F0415, KEY_REWIND		},
+	{ 0x800F0417, KEY_RECORD		},
+};
+
+/* Registers and other state is protected by wbcir_lock */
+struct wbcir_data {
+	unsigned long wbase;        /* Wake-Up Baseaddr		*/
+	unsigned long ebase;        /* Enhanced Func. Baseaddr	*/
+	unsigned long sbase;        /* Serial Port Baseaddr	*/
+	unsigned int  irq;          /* Serial Port IRQ		*/
+
+	struct input_dev *input_dev;
+	struct timer_list timer_keyup;
+	struct led_trigger *rxtrigger;
+	struct led_trigger *txtrigger;
+	struct led_classdev led;
+
+	u32 last_scancode;
+	unsigned int last_keycode;
+	u8 last_toggle;
+	u8 keypressed;
+	unsigned long keyup_jiffies;
+	unsigned int idle_count;
+
+	/* RX irdata and parsing state */
+	unsigned long irdata[30];
+	unsigned int irdata_count;
+	unsigned int irdata_idle;
+	unsigned int irdata_off;
+	unsigned int irdata_error;
+
+	/* Protected by keytable_lock */
+	struct list_head keytable;
+};
+
+static enum wbcir_protocol protocol = IR_PROTOCOL_RC6;
+module_param(protocol, uint, 0444);
+MODULE_PARM_DESC(protocol, "IR protocol to use "
+		 "(0 = RC5, 1 = NEC, 2 = RC6A, default)");
+
+static int invert; /* default = 0 */
+module_param(invert, bool, 0444);
+MODULE_PARM_DESC(invert, "Invert the signal from the IR receiver");
+
+static unsigned int wake_sc = 0x800F040C;
+module_param(wake_sc, uint, 0644);
+MODULE_PARM_DESC(wake_sc, "Scancode of the power-on IR command");
+
+static unsigned int wake_rc6mode = 6;
+module_param(wake_rc6mode, uint, 0644);
+MODULE_PARM_DESC(wake_rc6mode, "RC6 mode for the power-on command "
+		 "(0 = 0, 6 = 6A, default)");
+
+
+
+/*****************************************************************************
+ *
+ * UTILITY FUNCTIONS
+ *
+ *****************************************************************************/
+
+/* Caller needs to hold wbcir_lock */
+static void
+wbcir_set_bits(unsigned long addr, u8 bits, u8 mask)
+{
+	u8 val;
+
+	val = inb(addr);
+	val = ((val & ~mask) | (bits & mask));
+	outb(val, addr);
+}
+
+/* Selects the register bank for the serial port */
+static inline void
+wbcir_select_bank(struct wbcir_data *data, enum wbcir_bank bank)
+{
+	outb(bank, data->sbase + WBCIR_REG_SP3_BSR);
+}
+
+static enum led_brightness
+wbcir_led_brightness_get(struct led_classdev *led_cdev)
+{
+	struct wbcir_data *data = container_of(led_cdev,
+					       struct wbcir_data,
+					       led);
+
+	if (inb(data->ebase + WBCIR_REG_ECEIR_CTS) & WBCIR_LED_ENABLE)
+		return LED_FULL;
+	else
+		return LED_OFF;
+}
+
+static void
+wbcir_led_brightness_set(struct led_classdev *led_cdev,
+			    enum led_brightness brightness)
+{
+	struct wbcir_data *data = container_of(led_cdev,
+					       struct wbcir_data,
+					       led);
+
+	wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CTS,
+		       brightness == LED_OFF ? 0x00 : WBCIR_LED_ENABLE,
+		       WBCIR_LED_ENABLE);
+}
+
+/* Manchester encodes bits to RC6 message cells (see wbcir_parse_rc6) */
+static u8
+wbcir_to_rc6cells(u8 val)
+{
+	u8 coded = 0x00;
+	int i;
+
+	val &= 0x0F;
+	for (i = 0; i < 4; i++) {
+		if (val & 0x01)
+			coded |= 0x02 << (i * 2);
+		else
+			coded |= 0x01 << (i * 2);
+		val >>= 1;
+	}
+
+	return coded;
+}
+
+
+
+/*****************************************************************************
+ *
+ * INPUT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static unsigned int
+wbcir_do_getkeycode(struct wbcir_data *data, u32 scancode)
+{
+	struct wbcir_keyentry *keyentry;
+	unsigned int keycode = KEY_RESERVED;
+	unsigned long flags;
+
+	read_lock_irqsave(&keytable_lock, flags);
+
+	list_for_each_entry(keyentry, &data->keytable, list) {
+		if (keyentry->key.scancode == scancode) {
+			keycode = keyentry->key.keycode;
+			break;
+		}
+	}
+
+	read_unlock_irqrestore(&keytable_lock, flags);
+	return keycode;
+}
+
+static int
+wbcir_getkeycode(struct input_dev *dev, int scancode, int *keycode)
+{
+	struct wbcir_data *data = input_get_drvdata(dev);
+
+	*keycode = (int)wbcir_do_getkeycode(data, (u32)scancode);
+	return 0;
+}
+
+static int
+wbcir_setkeycode(struct input_dev *dev, int sscancode, int keycode)
+{
+	struct wbcir_data *data = input_get_drvdata(dev);
+	struct wbcir_keyentry *keyentry;
+	struct wbcir_keyentry *new_keyentry;
+	unsigned long flags;
+	unsigned int old_keycode = KEY_RESERVED;
+	u32 scancode = (u32)sscancode;
+
+	if (keycode < 0 || keycode > KEY_MAX)
+		return -EINVAL;
+
+	new_keyentry = kmalloc(sizeof(*new_keyentry), GFP_KERNEL);
+	if (!new_keyentry)
+		return -ENOMEM;
+
+	write_lock_irqsave(&keytable_lock, flags);
+
+	list_for_each_entry(keyentry, &data->keytable, list) {
+		if (keyentry->key.scancode != scancode)
+			continue;
+
+		old_keycode = keyentry->key.keycode;
+		keyentry->key.keycode = keycode;
+
+		if (keyentry->key.keycode == KEY_RESERVED) {
+			list_del(&keyentry->list);
+			kfree(keyentry);
+		}
+
+		break;
+	}
+
+	set_bit(keycode, dev->keybit);
+
+	if (old_keycode == KEY_RESERVED) {
+		new_keyentry->key.scancode = scancode;
+		new_keyentry->key.keycode = keycode;
+		list_add(&new_keyentry->list, &data->keytable);
+	} else {
+		kfree(new_keyentry);
+		clear_bit(old_keycode, dev->keybit);
+		list_for_each_entry(keyentry, &data->keytable, list) {
+			if (keyentry->key.keycode == old_keycode) {
+				set_bit(old_keycode, dev->keybit);
+				break;
+			}
+		}
+	}
+
+	write_unlock_irqrestore(&keytable_lock, flags);
+	return 0;
+}
+
+/*
+ * Timer function to report keyup event some time after keydown is
+ * reported by the ISR.
+ */
+static void
+wbcir_keyup(unsigned long cookie)
+{
+	struct wbcir_data *data = (struct wbcir_data *)cookie;
+	unsigned long flags;
+
+	/*
+	 * data->keyup_jiffies is used to prevent a race condition if a
+	 * hardware interrupt occurs at this point and the keyup timer
+	 * event is moved further into the future as a result.
+	 *
+	 * The timer will then be reactivated and this function called
+	 * again in the future. We need to exit gracefully in that case
+	 * to allow the input subsystem to do its auto-repeat magic or
+	 * a keyup event might follow immediately after the keydown.
+	 */
+
+	spin_lock_irqsave(&wbcir_lock, flags);
+
+	if (time_is_after_eq_jiffies(data->keyup_jiffies) && data->keypressed) {
+		data->keypressed = 0;
+		led_trigger_event(data->rxtrigger, LED_OFF);
+		input_report_key(data->input_dev, data->last_keycode, 0);
+		input_sync(data->input_dev);
+	}
+
+	spin_unlock_irqrestore(&wbcir_lock, flags);
+}
+
+static void
+wbcir_keydown(struct wbcir_data *data, u32 scancode, u8 toggle)
+{
+	unsigned int keycode;
+
+	/* Repeat? */
+	if (data->last_scancode == scancode &&
+	    data->last_toggle == toggle &&
+	    data->keypressed)
+		goto set_timer;
+	data->last_scancode = scancode;
+
+	/* Do we need to release an old keypress? */
+	if (data->keypressed) {
+		input_report_key(data->input_dev, data->last_keycode, 0);
+		input_sync(data->input_dev);
+		data->keypressed = 0;
+	}
+
+	/* Report scancode */
+	input_event(data->input_dev, EV_MSC, MSC_SCAN, (int)scancode);
+
+	/* Do we know this scancode? */
+	keycode = wbcir_do_getkeycode(data, scancode);
+	if (keycode == KEY_RESERVED)
+		goto set_timer;
+
+	/* Register a keypress */
+	input_report_key(data->input_dev, keycode, 1);
+	data->keypressed = 1;
+	data->last_keycode = keycode;
+	data->last_toggle = toggle;
+
+set_timer:
+	input_sync(data->input_dev);
+	led_trigger_event(data->rxtrigger,
+			  data->keypressed ? LED_FULL : LED_OFF);
+	data->keyup_jiffies = jiffies + msecs_to_jiffies(IR_KEYPRESS_TIMEOUT);
+	mod_timer(&data->timer_keyup, data->keyup_jiffies);
+}
+
+
+
+/*****************************************************************************
+ *
+ * IR PARSING FUNCTIONS
+ *
+ *****************************************************************************/
+
+/* Resets all irdata */
+static void
+wbcir_reset_irdata(struct wbcir_data *data)
+{
+	memset(data->irdata, 0, sizeof(data->irdata));
+	data->irdata_count = 0;
+	data->irdata_off = 0;
+	data->irdata_error = 0;
+}
+
+/* Adds one bit of irdata */
+static void
+add_irdata_bit(struct wbcir_data *data, int set)
+{
+	if (data->irdata_count >= sizeof(data->irdata) * 8) {
+		data->irdata_error = 1;
+		return;
+	}
+
+	if (set)
+		__set_bit(data->irdata_count, data->irdata);
+	data->irdata_count++;
+}
+
+/* Gets count bits of irdata */
+static u16
+get_bits(struct wbcir_data *data, int count)
+{
+	u16 val = 0x0;
+
+	if (data->irdata_count - data->irdata_off < count) {
+		data->irdata_error = 1;
+		return 0x0;
+	}
+
+	while (count > 0) {
+		val <<= 1;
+		if (test_bit(data->irdata_off, data->irdata))
+			val |= 0x1;
+		count--;
+		data->irdata_off++;
+	}
+
+	return val;
+}
+
+/* Reads 16 cells and converts them to a byte */
+static u8
+wbcir_rc6cells_to_byte(struct wbcir_data *data)
+{
+	u16 raw = get_bits(data, 16);
+	u8 val = 0x00;
+	int bit;
+
+	for (bit = 0; bit < 8; bit++) {
+		switch (raw & 0x03) {
+		case 0x01:
+			break;
+		case 0x02:
+			val |= (0x01 << bit);
+			break;
+		default:
+			data->irdata_error = 1;
+			break;
+		}
+		raw >>= 2;
+	}
+
+	return val;
+}
+
+/* Decodes a number of bits from raw RC5 data */
+static u8
+wbcir_get_rc5bits(struct wbcir_data *data, unsigned int count)
+{
+	u16 raw = get_bits(data, count * 2);
+	u8 val = 0x00;
+	int bit;
+
+	for (bit = 0; bit < count; bit++) {
+		switch (raw & 0x03) {
+		case 0x01:
+			val |= (0x01 << bit);
+			break;
+		case 0x02:
+			break;
+		default:
+			data->irdata_error = 1;
+			break;
+		}
+		raw >>= 2;
+	}
+
+	return val;
+}
+
+static void
+wbcir_parse_rc6(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Normal bits are manchester coded as follows:
+	 * cell0 + cell1 = logic "0"
+	 * cell1 + cell0 = logic "1"
+	 *
+	 * The IR pulse has the following components:
+	 *
+	 * Leader		- 6 * cell1 - discarded
+	 * Gap    		- 2 * cell0 - discarded
+	 * Start bit		- Normal Coding - always "1"
+	 * Mode Bit 2 - 0	- Normal Coding
+	 * Toggle bit		- Normal Coding with double bit time,
+	 *			  e.g. cell0 + cell0 + cell1 + cell1
+	 *			  means logic "0".
+	 *
+	 * The rest depends on the mode, the following modes are known:
+	 *
+	 * MODE 0:
+	 *  Address Bit 7 - 0	- Normal Coding
+	 *  Command Bit 7 - 0	- Normal Coding
+	 *
+	 * MODE 6:
+	 *  The above Toggle Bit is used as a submode bit, 0 = A, 1 = B.
+	 *  Submode B is for pointing devices, only remotes using submode A
+	 *  are supported.
+	 *
+	 *  Customer range bit	- 0 => Customer = 7 bits, 0...127
+	 *                        1 => Customer = 15 bits, 32768...65535
+	 *  Customer Bits	- Normal Coding
+	 *
+	 *  Customer codes are allocated by Philips. The rest of the bits
+	 *  are customer dependent. The following is commonly used (and the
+	 *  only supported config):
+	 *
+	 *  Toggle Bit		- Normal Coding
+	 *  Address Bit 6 - 0	- Normal Coding
+	 *  Command Bit 7 - 0	- Normal Coding
+	 *
+	 * All modes are followed by at least 6 * cell0.
+	 *
+	 * MODE 0 msglen:
+	 *  1 * 2 (start bit) + 3 * 2 (mode) + 2 * 2 (toggle) +
+	 *  8 * 2 (address) + 8 * 2 (command) =
+	 *  44 cells
+	 *
+	 * MODE 6A msglen:
+	 *  1 * 2 (start bit) + 3 * 2 (mode) + 2 * 2 (submode) +
+	 *  1 * 2 (customer range bit) + 7/15 * 2 (customer bits) +
+	 *  1 * 2 (toggle bit) + 7 * 2 (address) + 8 * 2 (command) =
+	 *  60 - 76 cells
+	 */
+	u8 mode;
+	u8 toggle;
+	u16 customer = 0x0;
+	u8 address;
+	u8 command;
+	u32 scancode;
+
+	/* Leader mark */
+	while (get_bits(data, 1) && !data->irdata_error)
+		/* Do nothing */;
+
+	/* Leader space */
+	if (get_bits(data, 1)) {
+		dev_dbg(dev, "RC6 - Invalid leader space\n");
+		return;
+	}
+
+	/* Start bit */
+	if (get_bits(data, 2) != 0x02) {
+		dev_dbg(dev, "RC6 - Invalid start bit\n");
+		return;
+	}
+
+	/* Mode */
+	mode = get_bits(data, 6);
+	switch (mode) {
+	case 0x15: /* 010101 = b000 */
+		mode = 0;
+		break;
+	case 0x29: /* 101001 = b110 */
+		mode = 6;
+		break;
+	default:
+		dev_dbg(dev, "RC6 - Invalid mode\n");
+		return;
+	}
+
+	/* Toggle bit / Submode bit */
+	toggle = get_bits(data, 4);
+	switch (toggle) {
+	case 0x03:
+		toggle = 0;
+		break;
+	case 0x0C:
+		toggle = 1;
+		break;
+	default:
+		dev_dbg(dev, "RC6 - Toggle bit error\n");
+		break;
+	}
+
+	/* Customer */
+	if (mode == 6) {
+		if (toggle != 0) {
+			dev_dbg(dev, "RC6B - Not Supported\n");
+			return;
+		}
+
+		customer = wbcir_rc6cells_to_byte(data);
+
+		if (customer & 0x80) {
+			/* 15 bit customer value */
+			customer <<= 8;
+			customer |= wbcir_rc6cells_to_byte(data);
+		}
+	}
+
+	/* Address */
+	address = wbcir_rc6cells_to_byte(data);
+	if (mode == 6) {
+		toggle = address >> 7;
+		address &= 0x7F;
+	}
+
+	/* Command */
+	command = wbcir_rc6cells_to_byte(data);
+
+	/* Create scancode */
+	scancode =  command;
+	scancode |= address << 8;
+	scancode |= customer << 16;
+
+	/* Last sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "RC6 - Cell error(s)\n");
+		return;
+	}
+
+	dev_info(dev, "IR-RC6 ad 0x%02X cm 0x%02X cu 0x%04X "
+		"toggle %u mode %u scan 0x%08X\n",
+		address,
+		command,
+		customer,
+		(unsigned int)toggle,
+		(unsigned int)mode,
+		scancode);
+
+	wbcir_keydown(data, scancode, toggle);
+}
+
+static void
+wbcir_parse_rc5(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Bits are manchester coded as follows:
+	 * cell1 + cell0 = logic "0"
+	 * cell0 + cell1 = logic "1"
+	 * (i.e. the reverse of RC6)
+	 *
+	 * Start bit 1		- "1" - discarded
+	 * Start bit 2		- Must be inverted to get command bit 6
+	 * Toggle bit
+	 * Address Bit 4 - 0
+	 * Command Bit 5 - 0
+	 */
+	u8 toggle;
+	u8 address;
+	u8 command;
+	u32 scancode;
+
+	/* Start bit 1 */
+	if (!get_bits(data, 1)) {
+		dev_dbg(dev, "RC5 - Invalid start bit\n");
+		return;
+	}
+
+	/* Start bit 2 */
+	if (!wbcir_get_rc5bits(data, 1))
+		command = 0x40;
+	else
+		command = 0x00;
+
+	toggle   = wbcir_get_rc5bits(data, 1);
+	address  = wbcir_get_rc5bits(data, 5);
+	command |= wbcir_get_rc5bits(data, 6);
+	scancode = address << 7 | command;
+
+	/* Last sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "RC5 - Invalid message\n");
+		return;
+	}
+
+	dev_dbg(dev, "IR-RC5 ad %u cm %u t %u s %u\n",
+		(unsigned int)address,
+		(unsigned int)command,
+		(unsigned int)toggle,
+		(unsigned int)scancode);
+
+	wbcir_keydown(data, scancode, toggle);
+}
+
+static void
+wbcir_parse_nec(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Each bit represents 560 us.
+	 *
+	 * Leader		- 9 ms burst
+	 * Gap			- 4.5 ms silence
+	 * Address1 bit 0 - 7	- Address 1
+	 * Address2 bit 0 - 7	- Address 2
+	 * Command1 bit 0 - 7	- Command 1
+	 * Command2 bit 0 - 7	- Command 2
+	 *
+	 * Note the bit order!
+	 *
+	 * With the old NEC protocol, Address2 was the inverse of Address1
+	 * and Command2 was the inverse of Command1 and were used as
+	 * an error check.
+	 *
+	 * With NEC extended, Address1 is the LSB of the Address and
+	 * Address2 is the MSB, Command parsing remains unchanged.
+	 *
+	 * A repeat message is coded as:
+	 * Leader		- 9 ms burst
+	 * Gap			- 2.25 ms silence
+	 * Repeat		- 560 us active
+	 */
+	u8 address1;
+	u8 address2;
+	u8 command1;
+	u8 command2;
+	u16 address;
+	u32 scancode;
+
+	/* Leader mark */
+	while (get_bits(data, 1) && !data->irdata_error)
+		/* Do nothing */;
+
+	/* Leader space */
+	if (get_bits(data, 4)) {
+		dev_dbg(dev, "NEC - Invalid leader space\n");
+		return;
+	}
+
+	/* Repeat? */
+	if (get_bits(data, 1)) {
+		if (!data->keypressed) {
+			dev_dbg(dev, "NEC - Stray repeat message\n");
+			return;
+		}
+
+		dev_dbg(dev, "IR-NEC repeat s %u\n",
+			(unsigned int)data->last_scancode);
+
+		wbcir_keydown(data, data->last_scancode, data->last_toggle);
+		return;
+	}
+
+	/* Remaining leader space */
+	if (get_bits(data, 3)) {
+		dev_dbg(dev, "NEC - Invalid leader space\n");
+		return;
+	}
+
+	address1  = bitrev8(get_bits(data, 8));
+	address2  = bitrev8(get_bits(data, 8));
+	command1  = bitrev8(get_bits(data, 8));
+	command2  = bitrev8(get_bits(data, 8));
+
+	/* Sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "NEC - Invalid message\n");
+		return;
+	}
+
+	/* Check command validity */
+	if (command1 != ~command2) {
+		dev_dbg(dev, "NEC - Command bytes mismatch\n");
+		return;
+	}
+
+	/* Check for extended NEC protocol */
+	address = address1;
+	if (address1 != ~address2)
+		address |= address2 << 8;
+
+	scancode = address << 8 | command1;
+
+	dev_dbg(dev, "IR-NEC ad %u cm %u s %u\n",
+		(unsigned int)address,
+		(unsigned int)command1,
+		(unsigned int)scancode);
+
+	wbcir_keydown(data, scancode, !data->last_toggle);
+}
+
+
+
+/*****************************************************************************
+ *
+ * INTERRUPT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static irqreturn_t
+wbcir_irq_handler(int irqno, void *cookie)
+{
+	struct pnp_dev *device = cookie;
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	struct device *dev = &device->dev;
+	u8 status;
+	unsigned long flags;
+	u8 irdata[8];
+	int i;
+	unsigned int hw;
+
+	spin_lock_irqsave(&wbcir_lock, flags);
+
+	wbcir_select_bank(data, WBCIR_BANK_0);
+
+	status = inb(data->sbase + WBCIR_REG_SP3_EIR);
+
+	if (!(status & (WBCIR_IRQ_RX | WBCIR_IRQ_ERR))) {
+		spin_unlock_irqrestore(&wbcir_lock, flags);
+		return IRQ_NONE;
+	}
+
+	if (status & WBCIR_IRQ_ERR)
+		data->irdata_error = 1;
+
+	if (!(status & WBCIR_IRQ_RX))
+		goto out;
+
+	/* Since RXHDLEV is set, at least 8 bytes are in the FIFO */
+	insb(data->sbase + WBCIR_REG_SP3_RXDATA, &irdata[0], 8);
+
+	for (i = 0; i < sizeof(irdata); i++) {
+		hw = hweight8(irdata[i]);
+		if (hw > 4)
+			add_irdata_bit(data, 0);
+		else
+			add_irdata_bit(data, 1);
+
+		if (hw == 8)
+			data->idle_count++;
+		else
+			data->idle_count = 0;
+	}
+
+	if (data->idle_count > WBCIR_MAX_IDLE_BYTES) {
+		/* Set RXINACTIVE... */
+		outb(WBCIR_RX_DISABLE, data->sbase + WBCIR_REG_SP3_ASCR);
+
+		/* ...and drain the FIFO */
+		while (inb(data->sbase + WBCIR_REG_SP3_LSR) & WBCIR_RX_AVAIL)
+			inb(data->sbase + WBCIR_REG_SP3_RXDATA);
+
+		dev_dbg(dev, "IRDATA:\n");
+		for (i = 0; i < data->irdata_count; i += BITS_PER_LONG)
+			dev_dbg(dev, "0x%08lX\n", data->irdata[i/BITS_PER_LONG]);
+
+		switch (protocol) {
+		case IR_PROTOCOL_RC5:
+			wbcir_parse_rc5(dev, data);
+			break;
+		case IR_PROTOCOL_RC6:
+			wbcir_parse_rc6(dev, data);
+			break;
+		case IR_PROTOCOL_NEC:
+			wbcir_parse_nec(dev, data);
+			break;
+		}
+
+		wbcir_reset_irdata(data);
+		data->idle_count = 0;
+	}
+
+out:
+	spin_unlock_irqrestore(&wbcir_lock, flags);
+	return IRQ_HANDLED;
+}
+
+
+
+/*****************************************************************************
+ *
+ * SUSPEND/RESUME FUNCTIONS
+ *
+ *****************************************************************************/
+
+static void
+wbcir_shutdown(struct pnp_dev *device)
+{
+	struct device *dev = &device->dev;
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	int do_wake = 1;
+	u8 match[11];
+	u8 mask[11];
+	u8 rc6_csl = 0;
+	int i;
+
+	memset(match, 0, sizeof(match));
+	memset(mask, 0, sizeof(mask));
+
+	if (wake_sc == INVALID_SCANCODE || !device_may_wakeup(dev)) {
+		do_wake = 0;
+		goto finish;
+	}
+
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+		if (wake_sc > 0xFFF) {
+			do_wake = 0;
+			dev_err(dev, "RC5 - Invalid wake scancode\n");
+			break;
+		}
+
+		/* Mask = 13 bits, ex toggle */
+		mask[0] = 0xFF;
+		mask[1] = 0x17;
+
+		match[0]  = (wake_sc & 0x003F);      /* 6 command bits */
+		match[0] |= (wake_sc & 0x0180) >> 1; /* 2 address bits */
+		match[1]  = (wake_sc & 0x0E00) >> 9; /* 3 address bits */
+		if (!(wake_sc & 0x0040))             /* 2nd start bit  */
+			match[1] |= 0x10;
+
+		break;
+
+	case IR_PROTOCOL_NEC:
+		if (wake_sc > 0xFFFFFF) {
+			do_wake = 0;
+			dev_err(dev, "NEC - Invalid wake scancode\n");
+			break;
+		}
+
+		mask[0] = mask[1] = mask[2] = mask[3] = 0xFF;
+
+		match[1] = bitrev8((wake_sc & 0xFF));
+		match[0] = ~match[1];
+
+		match[3] = bitrev8((wake_sc & 0xFF00) >> 8);
+		if (wake_sc > 0xFFFF)
+			match[2] = bitrev8((wake_sc & 0xFF0000) >> 16);
+		else
+			match[2] = ~match[3];
+
+		break;
+
+	case IR_PROTOCOL_RC6:
+
+		if (wake_rc6mode == 0) {
+			if (wake_sc > 0xFFFF) {
+				do_wake = 0;
+				dev_err(dev, "RC6 - Invalid wake scancode\n");
+				break;
+			}
+
+			/* Command */
+			match[0] = wbcir_to_rc6cells(wake_sc >>  0);
+			mask[0]  = 0xFF;
+			match[1] = wbcir_to_rc6cells(wake_sc >>  4);
+			mask[1]  = 0xFF;
+
+			/* Address */
+			match[2] = wbcir_to_rc6cells(wake_sc >>  8);
+			mask[2]  = 0xFF;
+			match[3] = wbcir_to_rc6cells(wake_sc >> 12);
+			mask[3]  = 0xFF;
+
+			/* Header */
+			match[4] = 0x50; /* mode1 = mode0 = 0, ignore toggle */
+			mask[4]  = 0xF0;
+			match[5] = 0x09; /* start bit = 1, mode2 = 0 */
+			mask[5]  = 0x0F;
+
+			rc6_csl = 44;
+
+		} else if (wake_rc6mode == 6) {
+			i = 0;
+
+			/* Command */
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  0);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  4);
+			mask[i++] = 0xFF;
+
+			/* Address + Toggle */
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  8);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 12);
+			mask[i++] = 0x3F;
+
+			/* Customer bits 7 - 0 */
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 16);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 20);
+			mask[i++] = 0xFF;
+
+			if (wake_sc & 0x80000000) {
+				/* Customer range bit and bits 15 - 8 */
+				match[i]  = wbcir_to_rc6cells(wake_sc >> 24);
+				mask[i++] = 0xFF;
+				match[i]  = wbcir_to_rc6cells(wake_sc >> 28);
+				mask[i++] = 0xFF;
+				rc6_csl = 76;
+			} else if (wake_sc <= 0x007FFFFF) {
+				rc6_csl = 60;
+			} else {
+				do_wake = 0;
+				dev_err(dev, "RC6 - Invalid wake scancode\n");
+				break;
+			}
+
+			/* Header */
+			match[i]  = 0x93; /* mode1 = mode0 = 1, submode = 0 */
+			mask[i++] = 0xFF;
+			match[i]  = 0x0A; /* start bit = 1, mode2 = 1 */
+			mask[i++] = 0x0F;
+
+		} else {
+			do_wake = 0;
+			dev_err(dev, "RC6 - Invalid wake mode\n");
+		}
+
+		break;
+
+	default:
+		do_wake = 0;
+		break;
+	}
+
+finish:
+	if (do_wake) {
+		/* Set compare and compare mask */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX,
+			       WBCIR_REGSEL_COMPARE | WBCIR_REG_ADDR0,
+			       0x3F);
+		outsb(data->wbase + WBCIR_REG_WCEIR_DATA, match, 11);
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX,
+			       WBCIR_REGSEL_MASK | WBCIR_REG_ADDR0,
+			       0x3F);
+		outsb(data->wbase + WBCIR_REG_WCEIR_DATA, mask, 11);
+
+		/* RC6 Compare String Len */
+		outb(rc6_csl, data->wbase + WBCIR_REG_WCEIR_CSL);
+
+		/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+		/* Clear BUFF_EN, Clear END_EN, Set MATCH_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x01, 0x07);
+
+		/* Set CEIR_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x01, 0x01);
+
+	} else {
+		/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+		/* Clear CEIR_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+	}
+
+	/* Disable interrupts */
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+}
+
+static int
+wbcir_suspend(struct pnp_dev *device, pm_message_t state)
+{
+	wbcir_shutdown(device);
+	return 0;
+}
+
+static int
+wbcir_resume(struct pnp_dev *device)
+{
+	struct wbcir_data *data = pnp_get_drvdata(device);
+
+	/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	/* Clear CEIR_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+
+	/* Enable interrupts */
+	wbcir_reset_irdata(data);
+	outb(WBCIR_IRQ_RX | WBCIR_IRQ_ERR, data->sbase + WBCIR_REG_SP3_IER);
+
+	return 0;
+}
+
+
+
+/*****************************************************************************
+ *
+ * SETUP/INIT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static void
+wbcir_cfg_ceir(struct wbcir_data *data)
+{
+	u8 tmp;
+
+	/* Set PROT_SEL, RX_INV, Clear CEIR_EN (needed for the led) */
+	tmp = protocol << 4;
+	if (invert)
+		tmp |= 0x08;
+	outb(tmp, data->wbase + WBCIR_REG_WCEIR_CTL);
+
+	/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+	/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	/* Set RC5 cell time to correspond to 36 kHz */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CFG1, 0x4A, 0x7F);
+
+	/* Set IRTX_INV */
+	if (invert)
+		outb(0x04, data->ebase + WBCIR_REG_ECEIR_CCTL);
+	else
+		outb(0x00, data->ebase + WBCIR_REG_ECEIR_CCTL);
+
+	/*
+	 * Clear IR LED, set SP3 clock to 24Mhz
+	 * set SP3_IRRX_SW to binary 01, helpfully not documented
+	 */
+	outb(0x10, data->ebase + WBCIR_REG_ECEIR_CTS);
+}
+
+static int __devinit
+wbcir_probe(struct pnp_dev *device, const struct pnp_device_id *dev_id)
+{
+	struct device *dev = &device->dev;
+	struct wbcir_data *data;
+	int err;
+
+	if (!(pnp_port_len(device, 0) == EHFUNC_IOMEM_LEN &&
+	      pnp_port_len(device, 1) == WAKEUP_IOMEM_LEN &&
+	      pnp_port_len(device, 2) == SP_IOMEM_LEN)) {
+		dev_err(dev, "Invalid resources\n");
+		return -ENODEV;
+	}
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data) {
+		err = -ENOMEM;
+		goto exit;
+	}
+
+	pnp_set_drvdata(device, data);
+
+	data->ebase = pnp_port_start(device, 0);
+	data->wbase = pnp_port_start(device, 1);
+	data->sbase = pnp_port_start(device, 2);
+	data->irq = pnp_irq(device, 0);
+
+	if (data->wbase == 0 || data->ebase == 0 ||
+	    data->sbase == 0 || data->irq == 0) {
+		err = -ENODEV;
+		dev_err(dev, "Invalid resources\n");
+		goto exit_free_data;
+	}
+
+	dev_dbg(&device->dev, "Found device "
+		"(w: 0x%lX, e: 0x%lX, s: 0x%lX, i: %u)\n",
+		data->wbase, data->ebase, data->sbase, data->irq);
+
+	if (!request_region(data->wbase, WAKEUP_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->wbase, data->wbase + WAKEUP_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_free_data;
+	}
+
+	if (!request_region(data->ebase, EHFUNC_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->ebase, data->ebase + EHFUNC_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_release_wbase;
+	}
+
+	if (!request_region(data->sbase, SP_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->sbase, data->sbase + SP_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_release_ebase;
+	}
+
+	err = request_irq(data->irq, wbcir_irq_handler,
+			  IRQF_DISABLED, DRVNAME, device);
+	if (err) {
+		dev_err(dev, "Failed to claim IRQ %u\n", data->irq);
+		err = -EBUSY;
+		goto exit_release_sbase;
+	}
+
+	led_trigger_register_simple("cir-tx", &data->txtrigger);
+	if (!data->txtrigger) {
+		err = -ENOMEM;
+		goto exit_free_irq;
+	}
+
+	led_trigger_register_simple("cir-rx", &data->rxtrigger);
+	if (!data->rxtrigger) {
+		err = -ENOMEM;
+		goto exit_unregister_txtrigger;
+	}
+
+	data->led.name = "cir::activity";
+	data->led.default_trigger = "cir-rx";
+	data->led.brightness_set = wbcir_led_brightness_set;
+	data->led.brightness_get = wbcir_led_brightness_get;
+	err = led_classdev_register(&device->dev, &data->led);
+	if (err)
+		goto exit_unregister_rxtrigger;
+
+	data->input_dev = input_allocate_device();
+	if (!data->input_dev) {
+		err = -ENOMEM;
+		goto exit_unregister_led;
+	}
+
+	data->input_dev->evbit[0] = BIT(EV_KEY);
+	data->input_dev->name = WBCIR_NAME;
+	data->input_dev->phys = "wbcir/cir0";
+	data->input_dev->id.bustype = BUS_HOST;
+	data->input_dev->id.vendor  = PCI_VENDOR_ID_WINBOND;
+	data->input_dev->id.product = WBCIR_ID_FAMILY;
+	data->input_dev->id.version = WBCIR_ID_CHIP;
+	data->input_dev->getkeycode = wbcir_getkeycode;
+	data->input_dev->setkeycode = wbcir_setkeycode;
+	input_set_capability(data->input_dev, EV_MSC, MSC_SCAN);
+	input_set_drvdata(data->input_dev, data);
+
+	err = input_register_device(data->input_dev);
+	if (err)
+		goto exit_free_input;
+
+	data->last_scancode = INVALID_SCANCODE;
+	INIT_LIST_HEAD(&data->keytable);
+	setup_timer(&data->timer_keyup, wbcir_keyup, (unsigned long)data);
+
+	/* Load default keymaps */
+	if (protocol == IR_PROTOCOL_RC6) {
+		int i;
+		for (i = 0; i < ARRAY_SIZE(rc6_def_keymap); i++) {
+			err = wbcir_setkeycode(data->input_dev,
+					       (int)rc6_def_keymap[i].scancode,
+					       (int)rc6_def_keymap[i].keycode);
+			if (err)
+				goto exit_unregister_keys;
+		}
+	}
+
+	device_init_wakeup(&device->dev, 1);
+
+	wbcir_cfg_ceir(data);
+
+	/* Disable interrupts */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+
+	/* Enable extended mode */
+	wbcir_select_bank(data, WBCIR_BANK_2);
+	outb(WBCIR_EXT_ENABLE, data->sbase + WBCIR_REG_SP3_EXCR1);
+
+	/*
+	 * Configure baud generator, IR data will be sampled at
+	 * a bitrate of: (24Mhz * prescaler) / (divisor * 16).
+	 *
+	 * The ECIR registers include a flag to change the
+	 * 24Mhz clock freq to 48Mhz.
+	 *
+	 * It's not documented in the specs, but fifo levels
+	 * other than 16 seems to be unsupported.
+	 */
+
+	/* prescaler 1.0, tx/rx fifo lvl 16 */
+	outb(0x30, data->sbase + WBCIR_REG_SP3_EXCR2);
+
+	/* Set baud divisor to generate one byte per bit/cell */
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+		outb(0xA7, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	case IR_PROTOCOL_RC6:
+		outb(0x53, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	case IR_PROTOCOL_NEC:
+		outb(0x69, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	}
+	outb(0x00, data->sbase + WBCIR_REG_SP3_BGDH);
+
+	/* Set CEIR mode */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(0xC0, data->sbase + WBCIR_REG_SP3_MCR);
+	inb(data->sbase + WBCIR_REG_SP3_LSR); /* Clear LSR */
+	inb(data->sbase + WBCIR_REG_SP3_MSR); /* Clear MSR */
+
+	/* Disable RX demod, run-length encoding/decoding, set freq span */
+	wbcir_select_bank(data, WBCIR_BANK_7);
+	outb(0x10, data->sbase + WBCIR_REG_SP3_RCCFG);
+
+	/* Disable timer */
+	wbcir_select_bank(data, WBCIR_BANK_4);
+	outb(0x00, data->sbase + WBCIR_REG_SP3_IRCR1);
+
+	/* Enable MSR interrupt, Clear AUX_IRX */
+	wbcir_select_bank(data, WBCIR_BANK_5);
+	outb(0x00, data->sbase + WBCIR_REG_SP3_IRCR2);
+
+	/* Disable CRC */
+	wbcir_select_bank(data, WBCIR_BANK_6);
+	outb(0x20, data->sbase + WBCIR_REG_SP3_IRCR3);
+
+	/* Set RX/TX (de)modulation freq, not really used */
+	wbcir_select_bank(data, WBCIR_BANK_7);
+	outb(0xF2, data->sbase + WBCIR_REG_SP3_IRRXDC);
+	outb(0x69, data->sbase + WBCIR_REG_SP3_IRTXMC);
+
+	/* Set invert and pin direction */
+	if (invert)
+		outb(0x10, data->sbase + WBCIR_REG_SP3_IRCFG4);
+	else
+		outb(0x00, data->sbase + WBCIR_REG_SP3_IRCFG4);
+
+	/* Set FIFO thresholds (RX = 8, TX = 3), reset RX/TX */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(0x97, data->sbase + WBCIR_REG_SP3_FCR);
+
+	/* Clear AUX status bits */
+	outb(0xE0, data->sbase + WBCIR_REG_SP3_ASCR);
+
+	/* Enable interrupts */
+	outb(WBCIR_IRQ_RX | WBCIR_IRQ_ERR, data->sbase + WBCIR_REG_SP3_IER);
+
+	return 0;
+
+exit_unregister_keys:
+	if (!list_empty(&data->keytable)) {
+		struct wbcir_keyentry *key;
+		struct wbcir_keyentry *keytmp;
+
+		list_for_each_entry_safe(key, keytmp, &data->keytable, list) {
+			list_del(&key->list);
+			kfree(key);
+		}
+	}
+	input_unregister_device(data->input_dev);
+	/* Can't call input_free_device on an unregistered device */
+	data->input_dev = NULL;
+exit_free_input:
+	input_free_device(data->input_dev);
+exit_unregister_led:
+	led_classdev_unregister(&data->led);
+exit_unregister_rxtrigger:
+	led_trigger_unregister_simple(data->rxtrigger);
+exit_unregister_txtrigger:
+	led_trigger_unregister_simple(data->txtrigger);
+exit_free_irq:
+	free_irq(data->irq, device);
+exit_release_sbase:
+	release_region(data->sbase, SP_IOMEM_LEN);
+exit_release_ebase:
+	release_region(data->ebase, EHFUNC_IOMEM_LEN);
+exit_release_wbase:
+	release_region(data->wbase, WAKEUP_IOMEM_LEN);
+exit_free_data:
+	kfree(data);
+	pnp_set_drvdata(device, NULL);
+exit:
+	return err;
+}
+
+static void __devexit
+wbcir_remove(struct pnp_dev *device)
+{
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	struct wbcir_keyentry *key;
+	struct wbcir_keyentry *keytmp;
+
+	/* Disable interrupts */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+
+	del_timer_sync(&data->timer_keyup);
+
+	free_irq(data->irq, device);
+
+	/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+	/* Clear CEIR_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+
+	/* Clear BUFF_EN, END_EN, MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	/* This will generate a keyup event if necessary */
+	input_unregister_device(data->input_dev);
+
+	led_trigger_unregister_simple(data->rxtrigger);
+	led_trigger_unregister_simple(data->txtrigger);
+	led_classdev_unregister(&data->led);
+
+	/* This is ok since &data->led isn't actually used */
+	wbcir_led_brightness_set(&data->led, LED_OFF);
+
+	release_region(data->wbase, WAKEUP_IOMEM_LEN);
+	release_region(data->ebase, EHFUNC_IOMEM_LEN);
+	release_region(data->sbase, SP_IOMEM_LEN);
+
+	list_for_each_entry_safe(key, keytmp, &data->keytable, list) {
+		list_del(&key->list);
+		kfree(key);
+	}
+
+	kfree(data);
+
+	pnp_set_drvdata(device, NULL);
+}
+
+static const struct pnp_device_id wbcir_ids[] = {
+	{ "WEC1022", 0 },
+	{ "", 0 }
+};
+MODULE_DEVICE_TABLE(pnp, wbcir_ids);
+
+static struct pnp_driver wbcir_driver = {
+	.name     = WBCIR_NAME,
+	.id_table = wbcir_ids,
+	.probe    = wbcir_probe,
+	.remove   = __devexit_p(wbcir_remove),
+	.suspend  = wbcir_suspend,
+	.resume   = wbcir_resume,
+	.shutdown = wbcir_shutdown
+};
+
+static int __init
+wbcir_init(void)
+{
+	int ret;
+
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+	case IR_PROTOCOL_NEC:
+	case IR_PROTOCOL_RC6:
+		break;
+	default:
+		printk(KERN_ERR DRVNAME ": Invalid protocol argument\n");
+		return -EINVAL;
+	}
+
+	ret = pnp_register_driver(&wbcir_driver);
+	if (ret)
+		printk(KERN_ERR DRVNAME ": Unable to register driver\n");
+
+	return ret;
+}
+
+static void __exit
+wbcir_exit(void)
+{
+	pnp_unregister_driver(&wbcir_driver);
+}
+
+MODULE_AUTHOR("David Härdeman <david@hardeman.nu>");
+MODULE_DESCRIPTION("Winbond SuperI/O Consumer IR Driver");
+MODULE_LICENSE("GPL");
+
+module_init(wbcir_init);
+module_exit(wbcir_exit);
+
+

-- 
David Härdeman
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware
  2009-08-13 15:56         ` Dmitry Torokhov
@ 2009-08-13 17:58           ` David Härdeman
  -1 siblings, 0 replies; 16+ messages in thread
From: David Härdeman @ 2009-08-13 17:58 UTC (permalink / raw)
  To: Dmitry Torokhov; +Cc: linux-kernel, linux-input, jbarnes, akpm

On Thu, Aug 13, 2009 at 08:56:37AM -0700, Dmitry Torokhov wrote:
>On Thu, Aug 13, 2009 at 11:34:44AM +0200, David Härdeman wrote:
>> The main problem right now is that getkeycode is practically useless since
>> you can't blindly guess at a full range of 2^32 different scancodes to get
>> the complete keymap. Perhaps a index-based getkeycode would make sense...
>
>The drivers that have such sparce keymaps are expected to issue
>EV_MSC/MSC_SCAN events to aid userspace in identifying the "scancodes"
>that are emitted by the device.

Ok, I've added EV_MSC/MSC_SCAN support.

>>>> +static struct device_attribute dev_attr_last_scancode = {
>>>> +	.attr = {
>>>> +		.name = "last_scancode",
>>>> +		.mode = 0444,
>>>> +	},
>>>> +	.show = wbcir_show_last_scancode,
>>>> +	.store = NULL,
>>>> +
>>>> +};
>>>
>>> Why is this needed? And if this is needed we have a nice macro
>>> for that.
>>
>> I hope I've explained it wrt. EV_IR in my other mail. It's for building
>> keymaps of unknown remotes. And it'll go away once EV_IR is supported so I
>> don't think there's much point in fiddling with it now?
>>
>
>Because once the driver is in mainline it becomes part of userspace ABI
>and has to stay for a looong time.

I've removed the sysfs attribute as EV_MSC/MSC_SCAN provides the same 
functionality.

>> Thanks for the review. Are you willing to push the driver upstream through
>> the input tree once I've implemented your suggested fixes?
>>
>
>I'd need to take a look at your EV_IR patyches and see how they will
>affect this driver. I do nt want to merge something that will stay one
>way for half a release and then will switch to completely new interface.

The EV_IR functionality is intended to be used in addition to regular 
key up/down/repeat events. Much like EV_MSC/MSC_SCAN but more 
descriptive and specific to IR protocols. Then advanced user-space apps 
can choose whether to use the in-kernel keymap or map remote codes 
directly to suitable events (and it allows remote keymaps to be built 
easily in user-space).

As the future EV_IR functionality is additional to the current 
functionality, I hope the driver still can be merged before I have EV_IR 
patches ready for review. I'll post an updated patch shortly.

-- 
David Härdeman

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware
@ 2009-08-13 17:58           ` David Härdeman
  0 siblings, 0 replies; 16+ messages in thread
From: David Härdeman @ 2009-08-13 17:58 UTC (permalink / raw)
  To: Dmitry Torokhov; +Cc: linux-kernel, linux-input, jbarnes, akpm

On Thu, Aug 13, 2009 at 08:56:37AM -0700, Dmitry Torokhov wrote:
>On Thu, Aug 13, 2009 at 11:34:44AM +0200, David Härdeman wrote:
>> The main problem right now is that getkeycode is practically useless since
>> you can't blindly guess at a full range of 2^32 different scancodes to get
>> the complete keymap. Perhaps a index-based getkeycode would make sense...
>
>The drivers that have such sparce keymaps are expected to issue
>EV_MSC/MSC_SCAN events to aid userspace in identifying the "scancodes"
>that are emitted by the device.

Ok, I've added EV_MSC/MSC_SCAN support.

>>>> +static struct device_attribute dev_attr_last_scancode = {
>>>> +	.attr = {
>>>> +		.name = "last_scancode",
>>>> +		.mode = 0444,
>>>> +	},
>>>> +	.show = wbcir_show_last_scancode,
>>>> +	.store = NULL,
>>>> +
>>>> +};
>>>
>>> Why is this needed? And if this is needed we have a nice macro
>>> for that.
>>
>> I hope I've explained it wrt. EV_IR in my other mail. It's for building
>> keymaps of unknown remotes. And it'll go away once EV_IR is supported so I
>> don't think there's much point in fiddling with it now?
>>
>
>Because once the driver is in mainline it becomes part of userspace ABI
>and has to stay for a looong time.

I've removed the sysfs attribute as EV_MSC/MSC_SCAN provides the same 
functionality.

>> Thanks for the review. Are you willing to push the driver upstream through
>> the input tree once I've implemented your suggested fixes?
>>
>
>I'd need to take a look at your EV_IR patyches and see how they will
>affect this driver. I do nt want to merge something that will stay one
>way for half a release and then will switch to completely new interface.

The EV_IR functionality is intended to be used in addition to regular 
key up/down/repeat events. Much like EV_MSC/MSC_SCAN but more 
descriptive and specific to IR protocols. Then advanced user-space apps 
can choose whether to use the in-kernel keymap or map remote codes 
directly to suitable events (and it allows remote keymaps to be built 
easily in user-space).

As the future EV_IR functionality is additional to the current 
functionality, I hope the driver still can be merged before I have EV_IR 
patches ready for review. I'll post an updated patch shortly.

-- 
David Härdeman
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware
  2009-08-13  6:58   ` Dmitry Torokhov
@ 2009-08-13 17:14       ` David Härdeman
  2009-08-13 17:14       ` David Härdeman
  1 sibling, 0 replies; 16+ messages in thread
From: David Härdeman @ 2009-08-13 17:14 UTC (permalink / raw)
  To: Dmitry Torokhov; +Cc: linux-kernel, linux-input

On Wed, Aug 12, 2009 at 11:58:22PM -0700, Dmitry Torokhov wrote:
>> +/* Registers and other state is protected by wbcir_lock */
>> +struct wbcir_data {
>> +	unsigned long wbase;        /* Wake-Up Baseaddr		*/
>> +	unsigned long ebase;        /* Enhanced Func. Baseaddr	*/
>> +	unsigned long sbase;        /* Serial Port Baseaddr	*/
>> +	unsigned int  irq;          /* Serial Port IRQ		*/
>> +
>> +	struct input_dev *input_dev;
>> +	struct timer_list timer_keyup;
>> +	struct led_trigger *rxtrigger;
>> +	struct led_trigger *txtrigger;
>> +	struct led_classdev led;
>> +
>> +	u32 last_scancode;
>> +	unsigned int last_keycode;
>> +	u8 last_toggle;
>> +	u8 keypressed;
>> +	unsigned long keyup_jiffies;
>> +	unsigned int idle_count;
>> +
>> +	/* RX irdata and parsing state */
>> +	unsigned long irdata[30];
>> +	unsigned int irdata_count;
>> +	unsigned int irdata_idle;
>> +	unsigned int irdata_off;
>> +	unsigned int irdata_error;
>> +
>> +	/* Protected by keytable_lock */
>> +	struct list_head keytable;
>
>I think this has a potential to deadlock... Set and get keycodes are
>called with event lock taken, and then your implementations acquire
>keytable lock. When you emit input events the opposite happens - you
>take the keytable lock and then input core takes event lock.

I've taken a look at it, and I think it's ok. set/getkeycode take the 
event lock and then the keytable lock.

The irqhandler call sequence (which generates the input events) will 
take the keytable lock, copy the keycode, release the lock and report 
the event (which will take the event lock).

-- 
David Härdeman

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware
@ 2009-08-13 17:14       ` David Härdeman
  0 siblings, 0 replies; 16+ messages in thread
From: David Härdeman @ 2009-08-13 17:14 UTC (permalink / raw)
  To: Dmitry Torokhov; +Cc: linux-kernel, linux-input

On Wed, Aug 12, 2009 at 11:58:22PM -0700, Dmitry Torokhov wrote:
>> +/* Registers and other state is protected by wbcir_lock */
>> +struct wbcir_data {
>> +	unsigned long wbase;        /* Wake-Up Baseaddr		*/
>> +	unsigned long ebase;        /* Enhanced Func. Baseaddr	*/
>> +	unsigned long sbase;        /* Serial Port Baseaddr	*/
>> +	unsigned int  irq;          /* Serial Port IRQ		*/
>> +
>> +	struct input_dev *input_dev;
>> +	struct timer_list timer_keyup;
>> +	struct led_trigger *rxtrigger;
>> +	struct led_trigger *txtrigger;
>> +	struct led_classdev led;
>> +
>> +	u32 last_scancode;
>> +	unsigned int last_keycode;
>> +	u8 last_toggle;
>> +	u8 keypressed;
>> +	unsigned long keyup_jiffies;
>> +	unsigned int idle_count;
>> +
>> +	/* RX irdata and parsing state */
>> +	unsigned long irdata[30];
>> +	unsigned int irdata_count;
>> +	unsigned int irdata_idle;
>> +	unsigned int irdata_off;
>> +	unsigned int irdata_error;
>> +
>> +	/* Protected by keytable_lock */
>> +	struct list_head keytable;
>
>I think this has a potential to deadlock... Set and get keycodes are
>called with event lock taken, and then your implementations acquire
>keytable lock. When you emit input events the opposite happens - you
>take the keytable lock and then input core takes event lock.

I've taken a look at it, and I think it's ok. set/getkeycode take the 
event lock and then the keytable lock.

The irqhandler call sequence (which generates the input events) will 
take the keytable lock, copy the keycode, release the lock and report 
the event (which will take the event lock).

-- 
David Härdeman
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware
  2009-08-13  9:34       ` David Härdeman
@ 2009-08-13 15:56         ` Dmitry Torokhov
  -1 siblings, 0 replies; 16+ messages in thread
From: Dmitry Torokhov @ 2009-08-13 15:56 UTC (permalink / raw)
  To: David Härdeman
  Cc: linux-kernel, linux-input, jbarnes, akpm, bjorn.helgaas, randy.dunlap

On Thu, Aug 13, 2009 at 11:34:44AM +0200, David Härdeman wrote:
> On Thu, August 13, 2009 08:58, Dmitry Torokhov wrote:
> > Hi David,
> 
> Hi,
> 
> > Please keep Makefile sorted alphabetically.
> 
> Ok
> 
> >> +static struct wbcir_key rc6_def_keymap[] = {
> >> +	{ 0x800F0400, KEY_0			},
> >> +	{ 0x800F0401, KEY_1			},
> >> +	{ 0x800F0402, KEY_2			},
> >> +	{ 0x800F0403, KEY_3			},
> >> +	{ 0x800F0404, KEY_4			},
> >> +	{ 0x800F0405, KEY_5			},
> >> +	{ 0x800F0406, KEY_6			},
> >> +	{ 0x800F0407, KEY_7			},
> >> +	{ 0x800F0408, KEY_8			},
> >> +	{ 0x800F0409, KEY_9			},
> >
> > Make these ones KEY_NUMERIC_* as well, this should help users whose
> > keymaps have numbers in upper register normally.
> 
> Ok
> 
> >> +	{ 0x800F041D, KEY_NUMERIC_STAR		},
> >> +	{ 0x800F041C, KEY_NUMERIC_POUND		},
> >> +	{ 0x800F0410, KEY_VOLUMEUP		},
> >> +	{ 0x800F0411, KEY_VOLUMEDOWN		},
> >> +	{ 0x800F0412, KEY_CHANNELUP		},
> >> +	{ 0x800F0413, KEY_CHANNELDOWN		},
> >> +	{ 0x800F040E, KEY_MUTE			},
> >> +	{ 0x800F040D, KEY_VENDOR		}, /* Vista Logo Key */
> >> +	{ 0x800F041E, KEY_UP			},
> >> +	{ 0x800F041F, KEY_DOWN			},
> >> +	{ 0x800F0420, KEY_LEFT			},
> >> +	{ 0x800F0421, KEY_RIGHT			},
> >> +	{ 0x800F0422, KEY_OK			},
> >> +	{ 0x800F0423, KEY_ESC			},
> >> +	{ 0x800F040F, KEY_INFO			},
> >> +	{ 0x800F040A, KEY_CLEAR			},
> >> +	{ 0x800F040B, KEY_ENTER			},
> >> +	{ 0x800F045B, KEY_RED			},
> >> +	{ 0x800F045C, KEY_GREEN			},
> >> +	{ 0x800F045D, KEY_YELLOW		},
> >> +	{ 0x800F045E, KEY_BLUE			},
> >> +	{ 0x800F045A, KEY_TEXT			},
> >> +	{ 0x800F0427, KEY_SWITCHVIDEOMODE	},
> >> +	{ 0x800F040C, KEY_POWER			},
> >> +	{ 0x800F0450, KEY_RADIO			},
> >> +	{ 0x800F0448, KEY_PVR			},
> >> +	{ 0x800F0447, KEY_AUDIO			},
> >> +	{ 0x800F0426, KEY_EPG			},
> >> +	{ 0x800F0449, KEY_CAMERA		},
> >> +	{ 0x800F0425, KEY_TV			},
> >> +	{ 0x800F044A, KEY_VIDEO			},
> >> +	{ 0x800F0424, KEY_DVD			},
> >> +	{ 0x800F0416, KEY_PLAY			},
> >> +	{ 0x800F0418, KEY_PAUSE			},
> >> +	{ 0x800F0419, KEY_STOP			},
> >> +	{ 0x800F0414, KEY_FASTFORWARD		},
> >> +	{ 0x800F041A, KEY_NEXT			},
> >> +	{ 0x800F041B, KEY_PREVIOUS		},
> >> +	{ 0x800F0415, KEY_REWIND		},
> >> +	{ 0x800F0417, KEY_RECORD		},
> >
> > Umm, it looks like if you do (code & 0x800F0400) you can switch to
> > standard array-based keymap and won't even need list manipulation.
> 
> I didn't want to do that since it would potentially tie the driver to one
> particular remote (the one I used for testing while writing the driver).
> The hardware isn't shipped with any specific remote so that wouldn't be
> very user friendly.
> 
> This was the best solution I could come up with without adding some real
> limitations to the functionality of the driver.

I see.

> 
> The main problem right now is that getkeycode is practically useless since
> you can't blindly guess at a full range of 2^32 different scancodes to get
> the complete keymap. Perhaps a index-based getkeycode would make sense...

The drivers that have such sparce keymaps are expected to issue
EV_MSC/MSC_SCAN events to aid userspace in identifying the "scancodes"
that are emitted by the device.

> 
> Anyway, I hope that I can make this a moot point in the future by adding
> more IR-specific functionality to the input system. I've been thinking
> along the lines of IR-specific get/set-keycode ioctl's which would take a
> struct which defines the IR command to map to a key.
> 
> >> +};
> >> +
> >> +/* Registers and other state is protected by wbcir_lock */
> >> +struct wbcir_data {
> >> +	unsigned long wbase;        /* Wake-Up Baseaddr		*/
> >> +	unsigned long ebase;        /* Enhanced Func. Baseaddr	*/
> >> +	unsigned long sbase;        /* Serial Port Baseaddr	*/
> >> +	unsigned int  irq;          /* Serial Port IRQ		*/
> >> +
> >> +	struct input_dev *input_dev;
> >> +	struct timer_list timer_keyup;
> >> +	struct led_trigger *rxtrigger;
> >> +	struct led_trigger *txtrigger;
> >> +	struct led_classdev led;
> >> +
> >> +	u32 last_scancode;
> >> +	unsigned int last_keycode;
> >> +	u8 last_toggle;
> >> +	u8 keypressed;
> >> +	unsigned long keyup_jiffies;
> >> +	unsigned int idle_count;
> >> +
> >> +	/* RX irdata and parsing state */
> >> +	unsigned long irdata[30];
> >> +	unsigned int irdata_count;
> >> +	unsigned int irdata_idle;
> >> +	unsigned int irdata_off;
> >> +	unsigned int irdata_error;
> >> +
> >> +	/* Protected by keytable_lock */
> >> +	struct list_head keytable;
> >
> > I think this has a potential to deadlock... Set and get keycodes are
> > called with event lock taken, and then your implementations acquire
> > keytable lock. When you emit input events the opposite happens - you
> > take the keytable lock and then input core takes event lock.
> 
> Good catch, I'll have to look into that...
> 
> >> +static struct device_attribute dev_attr_last_scancode = {
> >> +	.attr = {
> >> +		.name = "last_scancode",
> >> +		.mode = 0444,
> >> +	},
> >> +	.show = wbcir_show_last_scancode,
> >> +	.store = NULL,
> >> +
> >> +};
> >
> > Why is this needed? And if this is needed we have a nice macro
> > for that.
> 
> I hope I've explained it wrt. EV_IR in my other mail. It's for building
> keymaps of unknown remotes. And it'll go away once EV_IR is supported so I
> don't think there's much point in fiddling with it now?
> 

Because once the driver is in mainline it becomes part of userspace ABI
and has to stay for a looong time.

> >> +static int
> >> +wbcir_probe(struct pnp_dev *device, const struct pnp_device_id *dev_id)
> >
> > __devinit
> >
> ...
> >> +	dev_info(&device->dev, "Found device "
> >> +		 "(w: 0x%lX, e: 0x%lX, s: 0x%lX, i: %u)\n",
> >> +		 data->wbase, data->ebase, data->sbase, data->irq);
> >> +
> >
> > dev_dbg() I think.
> 
> Ok
> 
> >> +static void
> >> +wbcir_remove(struct pnp_dev *device)
> >
> > __devexit
> 
> Ok
> 
> >> +static struct pnp_driver wbcir_driver = {
> >> +	.name     = WBCIR_ACPI_NAME,
> >> +	.id_table = wbcir_ids,
> >> +	.probe    = wbcir_probe,
> >> +	.remove   = wbcir_remove,
> >
> > __devexit_p()
> 
> Ok
> 
> >> +	.suspend  = wbcir_suspend,
> >> +	.resume   = wbcir_resume,
> >
> > Switch to dev_pm_ops?
> 
> Don't want to do that just yet. dev_pm_ops wasn't even on my radar until
> yesterday when I stumbled upon the documentation (in a header file rather
> than in Documentation/...eh?). I'll certainly look into it when the
> suspend/resume functionality has seen more testing and bug fixing.
> 
> Thanks for the review. Are you willing to push the driver upstream through
> the input tree once I've implemented your suggested fixes?
>

I'd need to take a look at your EV_IR patyches and see how they will
affect this driver. I do nt want to merge something that will stay one
way for half a release and then will switch to completely new interface.

-- 
Dmitry

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware
@ 2009-08-13 15:56         ` Dmitry Torokhov
  0 siblings, 0 replies; 16+ messages in thread
From: Dmitry Torokhov @ 2009-08-13 15:56 UTC (permalink / raw)
  To: David Härdeman
  Cc: linux-kernel, linux-input, jbarnes, akpm, bjorn.helgaas, randy.dunlap

On Thu, Aug 13, 2009 at 11:34:44AM +0200, David Härdeman wrote:
> On Thu, August 13, 2009 08:58, Dmitry Torokhov wrote:
> > Hi David,
> 
> Hi,
> 
> > Please keep Makefile sorted alphabetically.
> 
> Ok
> 
> >> +static struct wbcir_key rc6_def_keymap[] = {
> >> +	{ 0x800F0400, KEY_0			},
> >> +	{ 0x800F0401, KEY_1			},
> >> +	{ 0x800F0402, KEY_2			},
> >> +	{ 0x800F0403, KEY_3			},
> >> +	{ 0x800F0404, KEY_4			},
> >> +	{ 0x800F0405, KEY_5			},
> >> +	{ 0x800F0406, KEY_6			},
> >> +	{ 0x800F0407, KEY_7			},
> >> +	{ 0x800F0408, KEY_8			},
> >> +	{ 0x800F0409, KEY_9			},
> >
> > Make these ones KEY_NUMERIC_* as well, this should help users whose
> > keymaps have numbers in upper register normally.
> 
> Ok
> 
> >> +	{ 0x800F041D, KEY_NUMERIC_STAR		},
> >> +	{ 0x800F041C, KEY_NUMERIC_POUND		},
> >> +	{ 0x800F0410, KEY_VOLUMEUP		},
> >> +	{ 0x800F0411, KEY_VOLUMEDOWN		},
> >> +	{ 0x800F0412, KEY_CHANNELUP		},
> >> +	{ 0x800F0413, KEY_CHANNELDOWN		},
> >> +	{ 0x800F040E, KEY_MUTE			},
> >> +	{ 0x800F040D, KEY_VENDOR		}, /* Vista Logo Key */
> >> +	{ 0x800F041E, KEY_UP			},
> >> +	{ 0x800F041F, KEY_DOWN			},
> >> +	{ 0x800F0420, KEY_LEFT			},
> >> +	{ 0x800F0421, KEY_RIGHT			},
> >> +	{ 0x800F0422, KEY_OK			},
> >> +	{ 0x800F0423, KEY_ESC			},
> >> +	{ 0x800F040F, KEY_INFO			},
> >> +	{ 0x800F040A, KEY_CLEAR			},
> >> +	{ 0x800F040B, KEY_ENTER			},
> >> +	{ 0x800F045B, KEY_RED			},
> >> +	{ 0x800F045C, KEY_GREEN			},
> >> +	{ 0x800F045D, KEY_YELLOW		},
> >> +	{ 0x800F045E, KEY_BLUE			},
> >> +	{ 0x800F045A, KEY_TEXT			},
> >> +	{ 0x800F0427, KEY_SWITCHVIDEOMODE	},
> >> +	{ 0x800F040C, KEY_POWER			},
> >> +	{ 0x800F0450, KEY_RADIO			},
> >> +	{ 0x800F0448, KEY_PVR			},
> >> +	{ 0x800F0447, KEY_AUDIO			},
> >> +	{ 0x800F0426, KEY_EPG			},
> >> +	{ 0x800F0449, KEY_CAMERA		},
> >> +	{ 0x800F0425, KEY_TV			},
> >> +	{ 0x800F044A, KEY_VIDEO			},
> >> +	{ 0x800F0424, KEY_DVD			},
> >> +	{ 0x800F0416, KEY_PLAY			},
> >> +	{ 0x800F0418, KEY_PAUSE			},
> >> +	{ 0x800F0419, KEY_STOP			},
> >> +	{ 0x800F0414, KEY_FASTFORWARD		},
> >> +	{ 0x800F041A, KEY_NEXT			},
> >> +	{ 0x800F041B, KEY_PREVIOUS		},
> >> +	{ 0x800F0415, KEY_REWIND		},
> >> +	{ 0x800F0417, KEY_RECORD		},
> >
> > Umm, it looks like if you do (code & 0x800F0400) you can switch to
> > standard array-based keymap and won't even need list manipulation.
> 
> I didn't want to do that since it would potentially tie the driver to one
> particular remote (the one I used for testing while writing the driver).
> The hardware isn't shipped with any specific remote so that wouldn't be
> very user friendly.
> 
> This was the best solution I could come up with without adding some real
> limitations to the functionality of the driver.

I see.

> 
> The main problem right now is that getkeycode is practically useless since
> you can't blindly guess at a full range of 2^32 different scancodes to get
> the complete keymap. Perhaps a index-based getkeycode would make sense...

The drivers that have such sparce keymaps are expected to issue
EV_MSC/MSC_SCAN events to aid userspace in identifying the "scancodes"
that are emitted by the device.

> 
> Anyway, I hope that I can make this a moot point in the future by adding
> more IR-specific functionality to the input system. I've been thinking
> along the lines of IR-specific get/set-keycode ioctl's which would take a
> struct which defines the IR command to map to a key.
> 
> >> +};
> >> +
> >> +/* Registers and other state is protected by wbcir_lock */
> >> +struct wbcir_data {
> >> +	unsigned long wbase;        /* Wake-Up Baseaddr		*/
> >> +	unsigned long ebase;        /* Enhanced Func. Baseaddr	*/
> >> +	unsigned long sbase;        /* Serial Port Baseaddr	*/
> >> +	unsigned int  irq;          /* Serial Port IRQ		*/
> >> +
> >> +	struct input_dev *input_dev;
> >> +	struct timer_list timer_keyup;
> >> +	struct led_trigger *rxtrigger;
> >> +	struct led_trigger *txtrigger;
> >> +	struct led_classdev led;
> >> +
> >> +	u32 last_scancode;
> >> +	unsigned int last_keycode;
> >> +	u8 last_toggle;
> >> +	u8 keypressed;
> >> +	unsigned long keyup_jiffies;
> >> +	unsigned int idle_count;
> >> +
> >> +	/* RX irdata and parsing state */
> >> +	unsigned long irdata[30];
> >> +	unsigned int irdata_count;
> >> +	unsigned int irdata_idle;
> >> +	unsigned int irdata_off;
> >> +	unsigned int irdata_error;
> >> +
> >> +	/* Protected by keytable_lock */
> >> +	struct list_head keytable;
> >
> > I think this has a potential to deadlock... Set and get keycodes are
> > called with event lock taken, and then your implementations acquire
> > keytable lock. When you emit input events the opposite happens - you
> > take the keytable lock and then input core takes event lock.
> 
> Good catch, I'll have to look into that...
> 
> >> +static struct device_attribute dev_attr_last_scancode = {
> >> +	.attr = {
> >> +		.name = "last_scancode",
> >> +		.mode = 0444,
> >> +	},
> >> +	.show = wbcir_show_last_scancode,
> >> +	.store = NULL,
> >> +
> >> +};
> >
> > Why is this needed? And if this is needed we have a nice macro
> > for that.
> 
> I hope I've explained it wrt. EV_IR in my other mail. It's for building
> keymaps of unknown remotes. And it'll go away once EV_IR is supported so I
> don't think there's much point in fiddling with it now?
> 

Because once the driver is in mainline it becomes part of userspace ABI
and has to stay for a looong time.

> >> +static int
> >> +wbcir_probe(struct pnp_dev *device, const struct pnp_device_id *dev_id)
> >
> > __devinit
> >
> ...
> >> +	dev_info(&device->dev, "Found device "
> >> +		 "(w: 0x%lX, e: 0x%lX, s: 0x%lX, i: %u)\n",
> >> +		 data->wbase, data->ebase, data->sbase, data->irq);
> >> +
> >
> > dev_dbg() I think.
> 
> Ok
> 
> >> +static void
> >> +wbcir_remove(struct pnp_dev *device)
> >
> > __devexit
> 
> Ok
> 
> >> +static struct pnp_driver wbcir_driver = {
> >> +	.name     = WBCIR_ACPI_NAME,
> >> +	.id_table = wbcir_ids,
> >> +	.probe    = wbcir_probe,
> >> +	.remove   = wbcir_remove,
> >
> > __devexit_p()
> 
> Ok
> 
> >> +	.suspend  = wbcir_suspend,
> >> +	.resume   = wbcir_resume,
> >
> > Switch to dev_pm_ops?
> 
> Don't want to do that just yet. dev_pm_ops wasn't even on my radar until
> yesterday when I stumbled upon the documentation (in a header file rather
> than in Documentation/...eh?). I'll certainly look into it when the
> suspend/resume functionality has seen more testing and bug fixing.
> 
> Thanks for the review. Are you willing to push the driver upstream through
> the input tree once I've implemented your suggested fixes?
>

I'd need to take a look at your EV_IR patyches and see how they will
affect this driver. I do nt want to merge something that will stay one
way for half a release and then will switch to completely new interface.

-- 
Dmitry
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR  hardware
  2009-08-13  6:58   ` Dmitry Torokhov
@ 2009-08-13  9:34       ` David Härdeman
  2009-08-13 17:14       ` David Härdeman
  1 sibling, 0 replies; 16+ messages in thread
From: David Härdeman @ 2009-08-13  9:34 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: linux-kernel, linux-input, jbarnes, akpm, bjorn.helgaas, randy.dunlap

On Thu, August 13, 2009 08:58, Dmitry Torokhov wrote:
> Hi David,

Hi,

> Please keep Makefile sorted alphabetically.

Ok

>> +static struct wbcir_key rc6_def_keymap[] = {
>> +	{ 0x800F0400, KEY_0			},
>> +	{ 0x800F0401, KEY_1			},
>> +	{ 0x800F0402, KEY_2			},
>> +	{ 0x800F0403, KEY_3			},
>> +	{ 0x800F0404, KEY_4			},
>> +	{ 0x800F0405, KEY_5			},
>> +	{ 0x800F0406, KEY_6			},
>> +	{ 0x800F0407, KEY_7			},
>> +	{ 0x800F0408, KEY_8			},
>> +	{ 0x800F0409, KEY_9			},
>
> Make these ones KEY_NUMERIC_* as well, this should help users whose
> keymaps have numbers in upper register normally.

Ok

>> +	{ 0x800F041D, KEY_NUMERIC_STAR		},
>> +	{ 0x800F041C, KEY_NUMERIC_POUND		},
>> +	{ 0x800F0410, KEY_VOLUMEUP		},
>> +	{ 0x800F0411, KEY_VOLUMEDOWN		},
>> +	{ 0x800F0412, KEY_CHANNELUP		},
>> +	{ 0x800F0413, KEY_CHANNELDOWN		},
>> +	{ 0x800F040E, KEY_MUTE			},
>> +	{ 0x800F040D, KEY_VENDOR		}, /* Vista Logo Key */
>> +	{ 0x800F041E, KEY_UP			},
>> +	{ 0x800F041F, KEY_DOWN			},
>> +	{ 0x800F0420, KEY_LEFT			},
>> +	{ 0x800F0421, KEY_RIGHT			},
>> +	{ 0x800F0422, KEY_OK			},
>> +	{ 0x800F0423, KEY_ESC			},
>> +	{ 0x800F040F, KEY_INFO			},
>> +	{ 0x800F040A, KEY_CLEAR			},
>> +	{ 0x800F040B, KEY_ENTER			},
>> +	{ 0x800F045B, KEY_RED			},
>> +	{ 0x800F045C, KEY_GREEN			},
>> +	{ 0x800F045D, KEY_YELLOW		},
>> +	{ 0x800F045E, KEY_BLUE			},
>> +	{ 0x800F045A, KEY_TEXT			},
>> +	{ 0x800F0427, KEY_SWITCHVIDEOMODE	},
>> +	{ 0x800F040C, KEY_POWER			},
>> +	{ 0x800F0450, KEY_RADIO			},
>> +	{ 0x800F0448, KEY_PVR			},
>> +	{ 0x800F0447, KEY_AUDIO			},
>> +	{ 0x800F0426, KEY_EPG			},
>> +	{ 0x800F0449, KEY_CAMERA		},
>> +	{ 0x800F0425, KEY_TV			},
>> +	{ 0x800F044A, KEY_VIDEO			},
>> +	{ 0x800F0424, KEY_DVD			},
>> +	{ 0x800F0416, KEY_PLAY			},
>> +	{ 0x800F0418, KEY_PAUSE			},
>> +	{ 0x800F0419, KEY_STOP			},
>> +	{ 0x800F0414, KEY_FASTFORWARD		},
>> +	{ 0x800F041A, KEY_NEXT			},
>> +	{ 0x800F041B, KEY_PREVIOUS		},
>> +	{ 0x800F0415, KEY_REWIND		},
>> +	{ 0x800F0417, KEY_RECORD		},
>
> Umm, it looks like if you do (code & 0x800F0400) you can switch to
> standard array-based keymap and won't even need list manipulation.

I didn't want to do that since it would potentially tie the driver to one
particular remote (the one I used for testing while writing the driver).
The hardware isn't shipped with any specific remote so that wouldn't be
very user friendly.

This was the best solution I could come up with without adding some real
limitations to the functionality of the driver.

The main problem right now is that getkeycode is practically useless since
you can't blindly guess at a full range of 2^32 different scancodes to get
the complete keymap. Perhaps a index-based getkeycode would make sense...

Anyway, I hope that I can make this a moot point in the future by adding
more IR-specific functionality to the input system. I've been thinking
along the lines of IR-specific get/set-keycode ioctl's which would take a
struct which defines the IR command to map to a key.

>> +};
>> +
>> +/* Registers and other state is protected by wbcir_lock */
>> +struct wbcir_data {
>> +	unsigned long wbase;        /* Wake-Up Baseaddr		*/
>> +	unsigned long ebase;        /* Enhanced Func. Baseaddr	*/
>> +	unsigned long sbase;        /* Serial Port Baseaddr	*/
>> +	unsigned int  irq;          /* Serial Port IRQ		*/
>> +
>> +	struct input_dev *input_dev;
>> +	struct timer_list timer_keyup;
>> +	struct led_trigger *rxtrigger;
>> +	struct led_trigger *txtrigger;
>> +	struct led_classdev led;
>> +
>> +	u32 last_scancode;
>> +	unsigned int last_keycode;
>> +	u8 last_toggle;
>> +	u8 keypressed;
>> +	unsigned long keyup_jiffies;
>> +	unsigned int idle_count;
>> +
>> +	/* RX irdata and parsing state */
>> +	unsigned long irdata[30];
>> +	unsigned int irdata_count;
>> +	unsigned int irdata_idle;
>> +	unsigned int irdata_off;
>> +	unsigned int irdata_error;
>> +
>> +	/* Protected by keytable_lock */
>> +	struct list_head keytable;
>
> I think this has a potential to deadlock... Set and get keycodes are
> called with event lock taken, and then your implementations acquire
> keytable lock. When you emit input events the opposite happens - you
> take the keytable lock and then input core takes event lock.

Good catch, I'll have to look into that...

>> +static struct device_attribute dev_attr_last_scancode = {
>> +	.attr = {
>> +		.name = "last_scancode",
>> +		.mode = 0444,
>> +	},
>> +	.show = wbcir_show_last_scancode,
>> +	.store = NULL,
>> +
>> +};
>
> Why is this needed? And if this is needed we have a nice macro
> for that.

I hope I've explained it wrt. EV_IR in my other mail. It's for building
keymaps of unknown remotes. And it'll go away once EV_IR is supported so I
don't think there's much point in fiddling with it now?

>> +static int
>> +wbcir_probe(struct pnp_dev *device, const struct pnp_device_id *dev_id)
>
> __devinit
>
...
>> +	dev_info(&device->dev, "Found device "
>> +		 "(w: 0x%lX, e: 0x%lX, s: 0x%lX, i: %u)\n",
>> +		 data->wbase, data->ebase, data->sbase, data->irq);
>> +
>
> dev_dbg() I think.

Ok

>> +static void
>> +wbcir_remove(struct pnp_dev *device)
>
> __devexit

Ok

>> +static struct pnp_driver wbcir_driver = {
>> +	.name     = WBCIR_ACPI_NAME,
>> +	.id_table = wbcir_ids,
>> +	.probe    = wbcir_probe,
>> +	.remove   = wbcir_remove,
>
> __devexit_p()

Ok

>> +	.suspend  = wbcir_suspend,
>> +	.resume   = wbcir_resume,
>
> Switch to dev_pm_ops?

Don't want to do that just yet. dev_pm_ops wasn't even on my radar until
yesterday when I stumbled upon the documentation (in a header file rather
than in Documentation/...eh?). I'll certainly look into it when the
suspend/resume functionality has seen more testing and bug fixing.

Thanks for the review. Are you willing to push the driver upstream through
the input tree once I've implemented your suggested fixes?

-- 
David Härdeman


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware
@ 2009-08-13  9:34       ` David Härdeman
  0 siblings, 0 replies; 16+ messages in thread
From: David Härdeman @ 2009-08-13  9:34 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: linux-kernel, linux-input, jbarnes, akpm, bjorn.helgaas, randy.dunlap

On Thu, August 13, 2009 08:58, Dmitry Torokhov wrote:
> Hi David,

Hi,

> Please keep Makefile sorted alphabetically.

Ok

>> +static struct wbcir_key rc6_def_keymap[] = {
>> +	{ 0x800F0400, KEY_0			},
>> +	{ 0x800F0401, KEY_1			},
>> +	{ 0x800F0402, KEY_2			},
>> +	{ 0x800F0403, KEY_3			},
>> +	{ 0x800F0404, KEY_4			},
>> +	{ 0x800F0405, KEY_5			},
>> +	{ 0x800F0406, KEY_6			},
>> +	{ 0x800F0407, KEY_7			},
>> +	{ 0x800F0408, KEY_8			},
>> +	{ 0x800F0409, KEY_9			},
>
> Make these ones KEY_NUMERIC_* as well, this should help users whose
> keymaps have numbers in upper register normally.

Ok

>> +	{ 0x800F041D, KEY_NUMERIC_STAR		},
>> +	{ 0x800F041C, KEY_NUMERIC_POUND		},
>> +	{ 0x800F0410, KEY_VOLUMEUP		},
>> +	{ 0x800F0411, KEY_VOLUMEDOWN		},
>> +	{ 0x800F0412, KEY_CHANNELUP		},
>> +	{ 0x800F0413, KEY_CHANNELDOWN		},
>> +	{ 0x800F040E, KEY_MUTE			},
>> +	{ 0x800F040D, KEY_VENDOR		}, /* Vista Logo Key */
>> +	{ 0x800F041E, KEY_UP			},
>> +	{ 0x800F041F, KEY_DOWN			},
>> +	{ 0x800F0420, KEY_LEFT			},
>> +	{ 0x800F0421, KEY_RIGHT			},
>> +	{ 0x800F0422, KEY_OK			},
>> +	{ 0x800F0423, KEY_ESC			},
>> +	{ 0x800F040F, KEY_INFO			},
>> +	{ 0x800F040A, KEY_CLEAR			},
>> +	{ 0x800F040B, KEY_ENTER			},
>> +	{ 0x800F045B, KEY_RED			},
>> +	{ 0x800F045C, KEY_GREEN			},
>> +	{ 0x800F045D, KEY_YELLOW		},
>> +	{ 0x800F045E, KEY_BLUE			},
>> +	{ 0x800F045A, KEY_TEXT			},
>> +	{ 0x800F0427, KEY_SWITCHVIDEOMODE	},
>> +	{ 0x800F040C, KEY_POWER			},
>> +	{ 0x800F0450, KEY_RADIO			},
>> +	{ 0x800F0448, KEY_PVR			},
>> +	{ 0x800F0447, KEY_AUDIO			},
>> +	{ 0x800F0426, KEY_EPG			},
>> +	{ 0x800F0449, KEY_CAMERA		},
>> +	{ 0x800F0425, KEY_TV			},
>> +	{ 0x800F044A, KEY_VIDEO			},
>> +	{ 0x800F0424, KEY_DVD			},
>> +	{ 0x800F0416, KEY_PLAY			},
>> +	{ 0x800F0418, KEY_PAUSE			},
>> +	{ 0x800F0419, KEY_STOP			},
>> +	{ 0x800F0414, KEY_FASTFORWARD		},
>> +	{ 0x800F041A, KEY_NEXT			},
>> +	{ 0x800F041B, KEY_PREVIOUS		},
>> +	{ 0x800F0415, KEY_REWIND		},
>> +	{ 0x800F0417, KEY_RECORD		},
>
> Umm, it looks like if you do (code & 0x800F0400) you can switch to
> standard array-based keymap and won't even need list manipulation.

I didn't want to do that since it would potentially tie the driver to one
particular remote (the one I used for testing while writing the driver).
The hardware isn't shipped with any specific remote so that wouldn't be
very user friendly.

This was the best solution I could come up with without adding some real
limitations to the functionality of the driver.

The main problem right now is that getkeycode is practically useless since
you can't blindly guess at a full range of 2^32 different scancodes to get
the complete keymap. Perhaps a index-based getkeycode would make sense...

Anyway, I hope that I can make this a moot point in the future by adding
more IR-specific functionality to the input system. I've been thinking
along the lines of IR-specific get/set-keycode ioctl's which would take a
struct which defines the IR command to map to a key.

>> +};
>> +
>> +/* Registers and other state is protected by wbcir_lock */
>> +struct wbcir_data {
>> +	unsigned long wbase;        /* Wake-Up Baseaddr		*/
>> +	unsigned long ebase;        /* Enhanced Func. Baseaddr	*/
>> +	unsigned long sbase;        /* Serial Port Baseaddr	*/
>> +	unsigned int  irq;          /* Serial Port IRQ		*/
>> +
>> +	struct input_dev *input_dev;
>> +	struct timer_list timer_keyup;
>> +	struct led_trigger *rxtrigger;
>> +	struct led_trigger *txtrigger;
>> +	struct led_classdev led;
>> +
>> +	u32 last_scancode;
>> +	unsigned int last_keycode;
>> +	u8 last_toggle;
>> +	u8 keypressed;
>> +	unsigned long keyup_jiffies;
>> +	unsigned int idle_count;
>> +
>> +	/* RX irdata and parsing state */
>> +	unsigned long irdata[30];
>> +	unsigned int irdata_count;
>> +	unsigned int irdata_idle;
>> +	unsigned int irdata_off;
>> +	unsigned int irdata_error;
>> +
>> +	/* Protected by keytable_lock */
>> +	struct list_head keytable;
>
> I think this has a potential to deadlock... Set and get keycodes are
> called with event lock taken, and then your implementations acquire
> keytable lock. When you emit input events the opposite happens - you
> take the keytable lock and then input core takes event lock.

Good catch, I'll have to look into that...

>> +static struct device_attribute dev_attr_last_scancode = {
>> +	.attr = {
>> +		.name = "last_scancode",
>> +		.mode = 0444,
>> +	},
>> +	.show = wbcir_show_last_scancode,
>> +	.store = NULL,
>> +
>> +};
>
> Why is this needed? And if this is needed we have a nice macro
> for that.

I hope I've explained it wrt. EV_IR in my other mail. It's for building
keymaps of unknown remotes. And it'll go away once EV_IR is supported so I
don't think there's much point in fiddling with it now?

>> +static int
>> +wbcir_probe(struct pnp_dev *device, const struct pnp_device_id *dev_id)
>
> __devinit
>
...
>> +	dev_info(&device->dev, "Found device "
>> +		 "(w: 0x%lX, e: 0x%lX, s: 0x%lX, i: %u)\n",
>> +		 data->wbase, data->ebase, data->sbase, data->irq);
>> +
>
> dev_dbg() I think.

Ok

>> +static void
>> +wbcir_remove(struct pnp_dev *device)
>
> __devexit

Ok

>> +static struct pnp_driver wbcir_driver = {
>> +	.name     = WBCIR_ACPI_NAME,
>> +	.id_table = wbcir_ids,
>> +	.probe    = wbcir_probe,
>> +	.remove   = wbcir_remove,
>
> __devexit_p()

Ok

>> +	.suspend  = wbcir_suspend,
>> +	.resume   = wbcir_resume,
>
> Switch to dev_pm_ops?

Don't want to do that just yet. dev_pm_ops wasn't even on my radar until
yesterday when I stumbled upon the documentation (in a header file rather
than in Documentation/...eh?). I'll certainly look into it when the
suspend/resume functionality has seen more testing and bug fixing.

Thanks for the review. Are you willing to push the driver upstream through
the input tree once I've implemented your suggested fixes?

-- 
David Härdeman

--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware
  2009-08-09  9:56   ` david
  (?)
  (?)
@ 2009-08-13  6:58   ` Dmitry Torokhov
  2009-08-13  9:34       ` David Härdeman
  2009-08-13 17:14       ` David Härdeman
  -1 siblings, 2 replies; 16+ messages in thread
From: Dmitry Torokhov @ 2009-08-13  6:58 UTC (permalink / raw)
  To: david
  Cc: linux-kernel, linux-input, jbarnes, akpm, bjorn.helgaas, randy.dunlap

Hi David,

On Sun, Aug 09, 2009 at 11:56:47AM +0200, david@hardeman.nu wrote:
>  
> +config INPUT_WINBOND_CIR
> +	tristate "Winbond IR remote control"
> +	depends on X86 && ACPI
> +	select LEDS_CLASS
> +	select BITREVERSE
> +	help
> +	  Say Y here if you want to use the IR remote functionality found
> +	  in some Winbond SuperI/O chips. Currently only the WPCD376I
> +	  chip is supported (included in some Intel Media series motherboards).
> +
> +	  IR Receive and wake-on-IR from suspend and power-off is currently
> +	  supported.
> +
> +	  To compile this driver as a module, choose M here: the module will be
> +	  called winbond_cir.
> +
>  config HP_SDC_RTC
>  	tristate "HP SDC Real Time Clock"
>  	depends on (GSC || HP300) && SERIO
> Index: linux-2.6/drivers/input/misc/Makefile
> ===================================================================
> --- linux-2.6.orig/drivers/input/misc/Makefile	2009-07-25 21:18:57.000000000 +0200
> +++ linux-2.6/drivers/input/misc/Makefile	2009-07-25 21:19:28.000000000 +0200
> @@ -19,6 +19,7 @@
>  obj-$(CONFIG_INPUT_PCSPKR)		+= pcspkr.o
>  obj-$(CONFIG_INPUT_POWERMATE)		+= powermate.o
>  obj-$(CONFIG_INPUT_RB532_BUTTON)	+= rb532_button.o
> +obj-$(CONFIG_INPUT_WINBOND_CIR)		+= winbond-cir.o
>  obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER)	+= rotary_encoder.o
>  obj-$(CONFIG_INPUT_SGI_BTNS)		+= sgi_btns.o
>  obj-$(CONFIG_INPUT_SPARCSPKR)		+= sparcspkr.o

Please keep Makefile sorted alphabetically.

> Index: linux-2.6/drivers/input/misc/winbond-cir.c
> ===================================================================
> --- /dev/null	1970-01-01 00:00:00.000000000 +0000
> +++ linux-2.6/drivers/input/misc/winbond-cir.c	2009-07-25 21:19:28.000000000 +0200
> @@ -0,0 +1,1644 @@
> +/*
> + *  winbond-cir.c - Driver for the Consumer IR functionality of Winbond
> + *                  SuperI/O chips.
> + *
> + *  Currently supports the Winbond WPCD376i chip (PNP id WEC1022), but
> + *  could probably support others (Winbond WEC102X, NatSemi, etc)
> + *  with minor modifications.
> + *
> + *  Original Author: David Härdeman <david@hardeman.nu>
> + *     Copyright (C) 2009 David Härdeman <david@hardeman.nu>
> + *
> + *  Dedicated to Matilda, my newborn daughter, without whose loving attention
> + *  this driver would have been finished in half the time and with a fraction
> + *  of the bugs.
> + *
> + *  Written using:
> + *    o Winbond WPCD376I datasheet helpfully provided by Jesse Barnes at Intel
> + *    o NatSemi PC87338/PC97338 datasheet (for the serial port stuff)
> + *    o DSDT dumps
> + *
> + *  Supported features:
> + *    o RC6
> + *    o Wake-On-CIR functionality
> + *
> + *  To do:
> + *    o Test NEC and RC5
> + *
> + *  Left as an exercise for the reader:
> + *    o Learning (I have neither the hardware, nor the need)
> + *    o IR Transmit (ibid)
> + *
> + *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/pnp.h>
> +#include <linux/interrupt.h>
> +#include <linux/timer.h>
> +#include <linux/input.h>
> +#include <linux/leds.h>
> +#include <linux/list.h>
> +#include <linux/spinlock.h>
> +#include <linux/pci_ids.h>
> +#include <linux/io.h>
> +#include <linux/bitrev.h>
> +#include <linux/bitops.h>
> +
> +#define DRVNAME "winbond-cir"
> +
> +/* CEIR Wake-Up Registers, relative to data->wbase                      */
> +#define WBCIR_REG_WCEIR_CTL	0x03 /* CEIR Receiver Control		*/
> +#define WBCIR_REG_WCEIR_STS	0x04 /* CEIR Receiver Status		*/
> +#define WBCIR_REG_WCEIR_EV_EN	0x05 /* CEIR Receiver Event Enable	*/
> +#define WBCIR_REG_WCEIR_CNTL	0x06 /* CEIR Receiver Counter Low	*/
> +#define WBCIR_REG_WCEIR_CNTH	0x07 /* CEIR Receiver Counter High	*/
> +#define WBCIR_REG_WCEIR_INDEX	0x08 /* CEIR Receiver Index		*/
> +#define WBCIR_REG_WCEIR_DATA	0x09 /* CEIR Receiver Data		*/
> +#define WBCIR_REG_WCEIR_CSL	0x0A /* CEIR Re. Compare Strlen		*/
> +#define WBCIR_REG_WCEIR_CFG1	0x0B /* CEIR Re. Configuration 1	*/
> +#define WBCIR_REG_WCEIR_CFG2	0x0C /* CEIR Re. Configuration 2	*/
> +
> +/* CEIR Enhanced Functionality Registers, relative to data->ebase       */
> +#define WBCIR_REG_ECEIR_CTS	0x00 /* Enhanced IR Control Status	*/
> +#define WBCIR_REG_ECEIR_CCTL	0x01 /* Infrared Counter Control	*/
> +#define WBCIR_REG_ECEIR_CNT_LO	0x02 /* Infrared Counter LSB		*/
> +#define WBCIR_REG_ECEIR_CNT_HI	0x03 /* Infrared Counter MSB		*/
> +#define WBCIR_REG_ECEIR_IREM	0x04 /* Infrared Emitter Status		*/
> +
> +/* SP3 Banked Registers, relative to data->sbase                        */
> +#define WBCIR_REG_SP3_BSR	0x03 /* Bank Select, all banks		*/
> +				      /* Bank 0				*/
> +#define WBCIR_REG_SP3_RXDATA	0x00 /* FIFO RX data (r)		*/
> +#define WBCIR_REG_SP3_TXDATA	0x00 /* FIFO TX data (w)		*/
> +#define WBCIR_REG_SP3_IER	0x01 /* Interrupt Enable		*/
> +#define WBCIR_REG_SP3_EIR	0x02 /* Event Identification (r)	*/
> +#define WBCIR_REG_SP3_FCR	0x02 /* FIFO Control (w)		*/
> +#define WBCIR_REG_SP3_MCR	0x04 /* Mode Control			*/
> +#define WBCIR_REG_SP3_LSR	0x05 /* Link Status			*/
> +#define WBCIR_REG_SP3_MSR	0x06 /* Modem Status			*/
> +#define WBCIR_REG_SP3_ASCR	0x07 /* Aux Status and Control		*/
> +				      /* Bank 2				*/
> +#define WBCIR_REG_SP3_BGDL	0x00 /* Baud Divisor LSB		*/
> +#define WBCIR_REG_SP3_BGDH	0x01 /* Baud Divisor MSB		*/
> +#define WBCIR_REG_SP3_EXCR1	0x02 /* Extended Control 1		*/
> +#define WBCIR_REG_SP3_EXCR2	0x04 /* Extended Control 2		*/
> +#define WBCIR_REG_SP3_TXFLV	0x06 /* TX FIFO Level			*/
> +#define WBCIR_REG_SP3_RXFLV	0x07 /* RX FIFO Level			*/
> +				      /* Bank 3				*/
> +#define WBCIR_REG_SP3_MRID	0x00 /* Module Identification		*/
> +#define WBCIR_REG_SP3_SH_LCR	0x01 /* LCR Shadow			*/
> +#define WBCIR_REG_SP3_SH_FCR	0x02 /* FCR Shadow			*/
> +				      /* Bank 4				*/
> +#define WBCIR_REG_SP3_IRCR1	0x02 /* Infrared Control 1		*/
> +				      /* Bank 5				*/
> +#define WBCIR_REG_SP3_IRCR2	0x04 /* Infrared Control 2		*/
> +				      /* Bank 6				*/
> +#define WBCIR_REG_SP3_IRCR3	0x00 /* Infrared Control 3		*/
> +#define WBCIR_REG_SP3_SIR_PW	0x02 /* SIR Pulse Width		*/
> +				      /* Bank 7				*/
> +#define WBCIR_REG_SP3_IRRXDC	0x00 /* IR RX Demod Control		*/
> +#define WBCIR_REG_SP3_IRTXMC	0x01 /* IR TX Mod Control		*/
> +#define WBCIR_REG_SP3_RCCFG	0x02 /* CEIR Config			*/
> +#define WBCIR_REG_SP3_IRCFG1	0x04 /* Infrared Config 1		*/
> +#define WBCIR_REG_SP3_IRCFG4	0x07 /* Infrared Config 4		*/
> +
> +/*
> + * Magic values follow
> + */
> +
> +/* No interrupts for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
> +#define WBCIR_IRQ_NONE		0x00
> +/* RX data bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
> +#define WBCIR_IRQ_RX		0x01
> +/* Over/Under-flow bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
> +#define WBCIR_IRQ_ERR		0x04
> +/* Led enable/disable bit for WBCIR_REG_ECEIR_CTS */
> +#define WBCIR_LED_ENABLE	0x80
> +/* RX data available bit for WBCIR_REG_SP3_LSR */
> +#define WBCIR_RX_AVAIL		0x01
> +/* RX disable bit for WBCIR_REG_SP3_ASCR */
> +#define WBCIR_RX_DISABLE	0x20
> +/* Extended mode enable bit for WBCIR_REG_SP3_EXCR1 */
> +#define WBCIR_EXT_ENABLE	0x01
> +/* Select compare register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */
> +#define WBCIR_REGSEL_COMPARE	0x10
> +/* Select mask register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */
> +#define WBCIR_REGSEL_MASK	0x20
> +/* Starting address of selected register in WBCIR_REG_WCEIR_INDEX */
> +#define WBCIR_REG_ADDR0		0x00
> +
> +/* Valid banks for the SP3 UART */
> +enum wbcir_bank {
> +	WBCIR_BANK_0          = 0x00,
> +	WBCIR_BANK_1          = 0x80,
> +	WBCIR_BANK_2          = 0xE0,
> +	WBCIR_BANK_3          = 0xE4,
> +	WBCIR_BANK_4          = 0xE8,
> +	WBCIR_BANK_5          = 0xEC,
> +	WBCIR_BANK_6          = 0xF0,
> +	WBCIR_BANK_7          = 0xF4,
> +};
> +
> +/* Supported IR Protocols */
> +enum wbcir_protocol {
> +	IR_PROTOCOL_RC5          = 0x0,
> +	IR_PROTOCOL_NEC          = 0x1,
> +	IR_PROTOCOL_RC6          = 0x2,
> +};
> +
> +/* Misc */
> +#define WBCIR_ACPI_NAME	"Winbond CIR"
> +#define WBCIR_ID_FAMILY          0xF1 /* Family ID for the WPCD376I	*/
> +#define	WBCIR_ID_CHIP            0x04 /* Chip ID for the WPCD376I	*/
> +#define IR_KEYPRESS_TIMEOUT       250 /* FIXME: should be per-protocol? */
> +#define INVALID_SCANCODE   0x7FFFFFFF /* Invalid with all protos	*/
> +#define WAKEUP_IOMEM_LEN         0x10 /* Wake-Up I/O Reg Len		*/
> +#define EHFUNC_IOMEM_LEN         0x10 /* Enhanced Func I/O Reg Len	*/
> +#define SP_IOMEM_LEN             0x08 /* Serial Port 3 (IR) Reg Len	*/
> +#define WBCIR_MAX_IDLE_BYTES       10
> +
> +static DEFINE_SPINLOCK(wbcir_lock);
> +static DEFINE_RWLOCK(keytable_lock);
> +
> +struct wbcir_key {
> +	u32 scancode;
> +	unsigned int keycode;
> +};
> +
> +struct wbcir_keyentry {
> +	struct wbcir_key key;
> +	struct list_head list;
> +};
> +
> +static struct wbcir_key rc6_def_keymap[] = {
> +	{ 0x800F0400, KEY_0			},
> +	{ 0x800F0401, KEY_1			},
> +	{ 0x800F0402, KEY_2			},
> +	{ 0x800F0403, KEY_3			},
> +	{ 0x800F0404, KEY_4			},
> +	{ 0x800F0405, KEY_5			},
> +	{ 0x800F0406, KEY_6			},
> +	{ 0x800F0407, KEY_7			},
> +	{ 0x800F0408, KEY_8			},
> +	{ 0x800F0409, KEY_9			},

Make these ones KEY_NUMERIC_* as well, this should help users whose
keymaps have numbers in upper register normally.

> +	{ 0x800F041D, KEY_NUMERIC_STAR		},
> +	{ 0x800F041C, KEY_NUMERIC_POUND		},
> +	{ 0x800F0410, KEY_VOLUMEUP		},
> +	{ 0x800F0411, KEY_VOLUMEDOWN		},
> +	{ 0x800F0412, KEY_CHANNELUP		},
> +	{ 0x800F0413, KEY_CHANNELDOWN		},
> +	{ 0x800F040E, KEY_MUTE			},
> +	{ 0x800F040D, KEY_VENDOR		}, /* Vista Logo Key */
> +	{ 0x800F041E, KEY_UP			},
> +	{ 0x800F041F, KEY_DOWN			},
> +	{ 0x800F0420, KEY_LEFT			},
> +	{ 0x800F0421, KEY_RIGHT			},
> +	{ 0x800F0422, KEY_OK			},
> +	{ 0x800F0423, KEY_ESC			},
> +	{ 0x800F040F, KEY_INFO			},
> +	{ 0x800F040A, KEY_CLEAR			},
> +	{ 0x800F040B, KEY_ENTER			},
> +	{ 0x800F045B, KEY_RED			},
> +	{ 0x800F045C, KEY_GREEN			},
> +	{ 0x800F045D, KEY_YELLOW		},
> +	{ 0x800F045E, KEY_BLUE			},
> +	{ 0x800F045A, KEY_TEXT			},
> +	{ 0x800F0427, KEY_SWITCHVIDEOMODE	},
> +	{ 0x800F040C, KEY_POWER			},
> +	{ 0x800F0450, KEY_RADIO			},
> +	{ 0x800F0448, KEY_PVR			},
> +	{ 0x800F0447, KEY_AUDIO			},
> +	{ 0x800F0426, KEY_EPG			},
> +	{ 0x800F0449, KEY_CAMERA		},
> +	{ 0x800F0425, KEY_TV			},
> +	{ 0x800F044A, KEY_VIDEO			},
> +	{ 0x800F0424, KEY_DVD			},
> +	{ 0x800F0416, KEY_PLAY			},
> +	{ 0x800F0418, KEY_PAUSE			},
> +	{ 0x800F0419, KEY_STOP			},
> +	{ 0x800F0414, KEY_FASTFORWARD		},
> +	{ 0x800F041A, KEY_NEXT			},
> +	{ 0x800F041B, KEY_PREVIOUS		},
> +	{ 0x800F0415, KEY_REWIND		},
> +	{ 0x800F0417, KEY_RECORD		},

Umm, it looks like if you do (code & 0x800F0400) you can switch to
standard array-based keymap and won't even need list manipulation.

> +};
> +
> +/* Registers and other state is protected by wbcir_lock */
> +struct wbcir_data {
> +	unsigned long wbase;        /* Wake-Up Baseaddr		*/
> +	unsigned long ebase;        /* Enhanced Func. Baseaddr	*/
> +	unsigned long sbase;        /* Serial Port Baseaddr	*/
> +	unsigned int  irq;          /* Serial Port IRQ		*/
> +
> +	struct input_dev *input_dev;
> +	struct timer_list timer_keyup;
> +	struct led_trigger *rxtrigger;
> +	struct led_trigger *txtrigger;
> +	struct led_classdev led;
> +
> +	u32 last_scancode;
> +	unsigned int last_keycode;
> +	u8 last_toggle;
> +	u8 keypressed;
> +	unsigned long keyup_jiffies;
> +	unsigned int idle_count;
> +
> +	/* RX irdata and parsing state */
> +	unsigned long irdata[30];
> +	unsigned int irdata_count;
> +	unsigned int irdata_idle;
> +	unsigned int irdata_off;
> +	unsigned int irdata_error;
> +
> +	/* Protected by keytable_lock */
> +	struct list_head keytable;

I think this has a potential to deadlock... Set and get keycodes are
called with event lock taken, and then your implementations acquire
keytable lock. When you emit input events the opposite happens - you
take the keytable lock and then input core takes event lock.

> +};
> +
> +static enum wbcir_protocol protocol = IR_PROTOCOL_RC6;
> +module_param(protocol, uint, 0444);
> +MODULE_PARM_DESC(protocol, "IR protocol to use "
> +		 "(0 = RC5, 1 = NEC, 2 = RC6A, default)");
> +
> +static int invert; /* default = 0 */
> +module_param(invert, bool, 0444);
> +MODULE_PARM_DESC(invert, "Invert the signal from the IR receiver");
> +
> +static unsigned int wake_sc = 0x800F040C;
> +module_param(wake_sc, uint, 0644);
> +MODULE_PARM_DESC(wake_sc, "Scancode of the power-on IR command");
> +
> +static unsigned int wake_rc6mode = 6;
> +module_param(wake_rc6mode, uint, 0644);
> +MODULE_PARM_DESC(wake_rc6mode, "RC6 mode for the power-on command "
> +		 "(0 = 0, 6 = 6A, default)");
> +
> +
> +
> +/*****************************************************************************
> + *
> + * UTILITY FUNCTIONS
> + *
> + *****************************************************************************/
> +
> +/* Caller needs to hold wbcir_lock */
> +static void
> +wbcir_set_bits(unsigned long addr, u8 bits, u8 mask)
> +{
> +	u8 val;
> +
> +	val = inb(addr);
> +	val = ((val & ~mask) | (bits & mask));
> +	outb(val, addr);
> +}
> +
> +/* Selects the register bank for the serial port */
> +static inline void
> +wbcir_select_bank(struct wbcir_data *data, enum wbcir_bank bank)
> +{
> +	outb(bank, data->sbase + WBCIR_REG_SP3_BSR);
> +}
> +
> +static enum led_brightness
> +wbcir_led_brightness_get(struct led_classdev *led_cdev)
> +{
> +	struct wbcir_data *data = container_of(led_cdev,
> +					       struct wbcir_data,
> +					       led);
> +
> +	if (inb(data->ebase + WBCIR_REG_ECEIR_CTS) & WBCIR_LED_ENABLE)
> +		return LED_FULL;
> +	else
> +		return LED_OFF;
> +}
> +
> +static void
> +wbcir_led_brightness_set(struct led_classdev *led_cdev,
> +			    enum led_brightness brightness)
> +{
> +	struct wbcir_data *data = container_of(led_cdev,
> +					       struct wbcir_data,
> +					       led);
> +
> +	wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CTS,
> +		       brightness == LED_OFF ? 0x00 : WBCIR_LED_ENABLE,
> +		       WBCIR_LED_ENABLE);
> +}
> +
> +/* Manchester encodes bits to RC6 message cells (see wbcir_parse_rc6) */
> +static u8
> +wbcir_to_rc6cells(u8 val)
> +{
> +	u8 coded = 0x00;
> +	int i;
> +
> +	val &= 0x0F;
> +	for (i = 0; i < 4; i++) {
> +		if (val & 0x01)
> +			coded |= 0x02 << (i * 2);
> +		else
> +			coded |= 0x01 << (i * 2);
> +		val >>= 1;
> +	}
> +
> +	return coded;
> +}
> +
> +
> +
> +/*****************************************************************************
> + *
> + * INPUT FUNCTIONS
> + *
> + *****************************************************************************/
> +
> +static unsigned int
> +wbcir_do_getkeycode(struct wbcir_data *data, u32 scancode)
> +{
> +	struct wbcir_keyentry *keyentry;
> +	unsigned int keycode = KEY_RESERVED;
> +	unsigned long flags;
> +
> +	read_lock_irqsave(&keytable_lock, flags);
> +
> +	list_for_each_entry(keyentry, &data->keytable, list) {
> +		if (keyentry->key.scancode == scancode) {
> +			keycode = keyentry->key.keycode;
> +			break;
> +		}
> +	}
> +
> +	read_unlock_irqrestore(&keytable_lock, flags);
> +	return keycode;
> +}
> +
> +static int
> +wbcir_getkeycode(struct input_dev *dev, int scancode, int *keycode)
> +{
> +	struct wbcir_data *data = input_get_drvdata(dev);
> +
> +	*keycode = (int)wbcir_do_getkeycode(data, (u32)scancode);
> +	return 0;
> +}
> +
> +static int
> +wbcir_setkeycode(struct input_dev *dev, int sscancode, int keycode)
> +{
> +	struct wbcir_data *data = input_get_drvdata(dev);
> +	struct wbcir_keyentry *keyentry;
> +	struct wbcir_keyentry *new_keyentry;
> +	unsigned long flags;
> +	unsigned int old_keycode = KEY_RESERVED;
> +	u32 scancode = (u32)sscancode;
> +
> +	if (keycode < 0 || keycode > KEY_MAX)
> +		return -EINVAL;
> +
> +	new_keyentry = kmalloc(sizeof(*new_keyentry), GFP_KERNEL);
> +	if (!new_keyentry)
> +		return -ENOMEM;
> +
> +	write_lock_irqsave(&keytable_lock, flags);
> +
> +	list_for_each_entry(keyentry, &data->keytable, list) {
> +		if (keyentry->key.scancode != scancode)
> +			continue;
> +
> +		old_keycode = keyentry->key.keycode;
> +		keyentry->key.keycode = keycode;
> +
> +		if (keyentry->key.keycode == KEY_RESERVED) {
> +			list_del(&keyentry->list);
> +			kfree(keyentry);
> +		}
> +
> +		break;
> +	}
> +
> +	set_bit(keycode, dev->keybit);
> +
> +	if (old_keycode == KEY_RESERVED) {
> +		new_keyentry->key.scancode = scancode;
> +		new_keyentry->key.keycode = keycode;
> +		list_add(&new_keyentry->list, &data->keytable);
> +	} else {
> +		kfree(new_keyentry);
> +		clear_bit(old_keycode, dev->keybit);
> +		list_for_each_entry(keyentry, &data->keytable, list) {
> +			if (keyentry->key.keycode == old_keycode) {
> +				set_bit(old_keycode, dev->keybit);
> +				break;
> +			}
> +		}
> +	}
> +
> +	write_unlock_irqrestore(&keytable_lock, flags);
> +	return 0;
> +}
> +
> +/*
> + * Timer function to report keyup event some time after keydown is
> + * reported by the ISR.
> + */
> +static void
> +wbcir_keyup(unsigned long cookie)
> +{
> +	struct wbcir_data *data = (struct wbcir_data *)cookie;
> +	unsigned long flags;
> +
> +	/*
> +	 * data->keyup_jiffies is used to prevent a race condition if a
> +	 * hardware interrupt occurs at this point and the keyup timer
> +	 * event is moved further into the future as a result.
> +	 *
> +	 * The timer will then be reactivated and this function called
> +	 * again in the future. We need to exit gracefully in that case
> +	 * to allow the input subsystem to do its auto-repeat magic or
> +	 * a keyup event might follow immediately after the keydown.
> +	 */
> +
> +	spin_lock_irqsave(&wbcir_lock, flags);
> +
> +	if (time_is_after_eq_jiffies(data->keyup_jiffies) && data->keypressed) {
> +		data->keypressed = 0;
> +		led_trigger_event(data->rxtrigger, LED_OFF);
> +		input_report_key(data->input_dev, data->last_keycode, 0);
> +		input_sync(data->input_dev);
> +	}
> +
> +	spin_unlock_irqrestore(&wbcir_lock, flags);
> +}
> +
> +static void
> +wbcir_keydown(struct wbcir_data *data, u32 scancode, u8 toggle)
> +{
> +	unsigned int keycode;
> +
> +	/* Repeat? */
> +	if (data->last_scancode == scancode &&
> +	    data->last_toggle == toggle &&
> +	    data->keypressed)
> +		goto set_timer;
> +	data->last_scancode = scancode;
> +
> +	/* Do we need to release an old keypress? */
> +	if (data->keypressed) {
> +		input_report_key(data->input_dev, data->last_keycode, 0);
> +		input_sync(data->input_dev);
> +		data->keypressed = 0;
> +	}
> +
> +	/* Do we know this scancode? */
> +	keycode = wbcir_do_getkeycode(data, scancode);
> +	if (keycode == KEY_RESERVED)
> +		goto set_timer;
> +
> +	/* Register a keypress */
> +	input_report_key(data->input_dev, keycode, 1);
> +	input_sync(data->input_dev);
> +	data->keypressed = 1;
> +	data->last_keycode = keycode;
> +	data->last_toggle = toggle;
> +
> +set_timer:
> +	led_trigger_event(data->rxtrigger,
> +			  data->keypressed ? LED_FULL : LED_OFF);
> +	data->keyup_jiffies = jiffies + msecs_to_jiffies(IR_KEYPRESS_TIMEOUT);
> +	mod_timer(&data->timer_keyup, data->keyup_jiffies);
> +}
> +
> +
> +
> +/*****************************************************************************
> + *
> + * IR PARSING FUNCTIONS
> + *
> + *****************************************************************************/
> +
> +/* Resets all irdata */
> +static void
> +wbcir_reset_irdata(struct wbcir_data *data)
> +{
> +	memset(data->irdata, 0, sizeof(data->irdata));
> +	data->irdata_count = 0;
> +	data->irdata_off = 0;
> +	data->irdata_error = 0;
> +}
> +
> +/* Adds one bit of irdata */
> +static void
> +add_irdata_bit(struct wbcir_data *data, int set)
> +{
> +	if (data->irdata_count >= sizeof(data->irdata) * 8) {
> +		data->irdata_error = 1;
> +		return;
> +	}
> +
> +	if (set)
> +		__set_bit(data->irdata_count, data->irdata);
> +	data->irdata_count++;
> +}
> +
> +/* Gets count bits of irdata */
> +static u16
> +get_bits(struct wbcir_data *data, int count)
> +{
> +	u16 val = 0x0;
> +
> +	if (data->irdata_count - data->irdata_off < count) {
> +		data->irdata_error = 1;
> +		return 0x0;
> +	}
> +
> +	while (count > 0) {
> +		val <<= 1;
> +		if (test_bit(data->irdata_off, data->irdata))
> +			val |= 0x1;
> +		count--;
> +		data->irdata_off++;
> +	}
> +
> +	return val;
> +}
> +
> +/* Reads 16 cells and converts them to a byte */
> +static u8
> +wbcir_rc6cells_to_byte(struct wbcir_data *data)
> +{
> +	u16 raw = get_bits(data, 16);
> +	u8 val = 0x00;
> +	int bit;
> +
> +	for (bit = 0; bit < 8; bit++) {
> +		switch (raw & 0x03) {
> +		case 0x01:
> +			break;
> +		case 0x02:
> +			val |= (0x01 << bit);
> +			break;
> +		default:
> +			data->irdata_error = 1;
> +			break;
> +		}
> +		raw >>= 2;
> +	}
> +
> +	return val;
> +}
> +
> +/* Decodes a number of bits from raw RC5 data */
> +static u8
> +wbcir_get_rc5bits(struct wbcir_data *data, unsigned int count)
> +{
> +	u16 raw = get_bits(data, count * 2);
> +	u8 val = 0x00;
> +	int bit;
> +
> +	for (bit = 0; bit < count; bit++) {
> +		switch (raw & 0x03) {
> +		case 0x01:
> +			val |= (0x01 << bit);
> +			break;
> +		case 0x02:
> +			break;
> +		default:
> +			data->irdata_error = 1;
> +			break;
> +		}
> +		raw >>= 2;
> +	}
> +
> +	return val;
> +}
> +
> +static void
> +wbcir_parse_rc6(struct device *dev, struct wbcir_data *data)
> +{
> +	/*
> +	 * Normal bits are manchester coded as follows:
> +	 * cell0 + cell1 = logic "0"
> +	 * cell1 + cell0 = logic "1"
> +	 *
> +	 * The IR pulse has the following components:
> +	 *
> +	 * Leader		- 6 * cell1 - discarded
> +	 * Gap    		- 2 * cell0 - discarded
> +	 * Start bit		- Normal Coding - always "1"
> +	 * Mode Bit 2 - 0	- Normal Coding
> +	 * Toggle bit		- Normal Coding with double bit time,
> +	 *			  e.g. cell0 + cell0 + cell1 + cell1
> +	 *			  means logic "0".
> +	 *
> +	 * The rest depends on the mode, the following modes are known:
> +	 *
> +	 * MODE 0:
> +	 *  Address Bit 7 - 0	- Normal Coding
> +	 *  Command Bit 7 - 0	- Normal Coding
> +	 *
> +	 * MODE 6:
> +	 *  The above Toggle Bit is used as a submode bit, 0 = A, 1 = B.
> +	 *  Submode B is for pointing devices, only remotes using submode A
> +	 *  are supported.
> +	 *
> +	 *  Customer range bit	- 0 => Customer = 7 bits, 0...127
> +	 *                        1 => Customer = 15 bits, 32768...65535
> +	 *  Customer Bits	- Normal Coding
> +	 *
> +	 *  Customer codes are allocated by Philips. The rest of the bits
> +	 *  are customer dependent. The following is commonly used (and the
> +	 *  only supported config):
> +	 *
> +	 *  Toggle Bit		- Normal Coding
> +	 *  Address Bit 6 - 0	- Normal Coding
> +	 *  Command Bit 7 - 0	- Normal Coding
> +	 *
> +	 * All modes are followed by at least 6 * cell0.
> +	 *
> +	 * MODE 0 msglen:
> +	 *  1 * 2 (start bit) + 3 * 2 (mode) + 2 * 2 (toggle) +
> +	 *  8 * 2 (address) + 8 * 2 (command) =
> +	 *  44 cells
> +	 *
> +	 * MODE 6A msglen:
> +	 *  1 * 2 (start bit) + 3 * 2 (mode) + 2 * 2 (submode) +
> +	 *  1 * 2 (customer range bit) + 7/15 * 2 (customer bits) +
> +	 *  1 * 2 (toggle bit) + 7 * 2 (address) + 8 * 2 (command) =
> +	 *  60 - 76 cells
> +	 */
> +	u8 mode;
> +	u8 toggle;
> +	u16 customer = 0x0;
> +	u8 address;
> +	u8 command;
> +	u32 scancode;
> +
> +	/* Leader mark */
> +	while (get_bits(data, 1) && !data->irdata_error)
> +		/* Do nothing */;
> +
> +	/* Leader space */
> +	if (get_bits(data, 1)) {
> +		dev_dbg(dev, "RC6 - Invalid leader space\n");
> +		return;
> +	}
> +
> +	/* Start bit */
> +	if (get_bits(data, 2) != 0x02) {
> +		dev_dbg(dev, "RC6 - Invalid start bit\n");
> +		return;
> +	}
> +
> +	/* Mode */
> +	mode = get_bits(data, 6);
> +	switch (mode) {
> +	case 0x15: /* 010101 = b000 */
> +		mode = 0;
> +		break;
> +	case 0x29: /* 101001 = b110 */
> +		mode = 6;
> +		break;
> +	default:
> +		dev_dbg(dev, "RC6 - Invalid mode\n");
> +		return;
> +	}
> +
> +	/* Toggle bit / Submode bit */
> +	toggle = get_bits(data, 4);
> +	switch (toggle) {
> +	case 0x03:
> +		toggle = 0;
> +		break;
> +	case 0x0C:
> +		toggle = 1;
> +		break;
> +	default:
> +		dev_dbg(dev, "RC6 - Toggle bit error\n");
> +		break;
> +	}
> +
> +	/* Customer */
> +	if (mode == 6) {
> +		if (toggle != 0) {
> +			dev_dbg(dev, "RC6B - Not Supported\n");
> +			return;
> +		}
> +
> +		customer = wbcir_rc6cells_to_byte(data);
> +
> +		if (customer & 0x80) {
> +			/* 15 bit customer value */
> +			customer <<= 8;
> +			customer |= wbcir_rc6cells_to_byte(data);
> +		}
> +	}
> +
> +	/* Address */
> +	address = wbcir_rc6cells_to_byte(data);
> +	if (mode == 6) {
> +		toggle = address >> 7;
> +		address &= 0x7F;
> +	}
> +
> +	/* Command */
> +	command = wbcir_rc6cells_to_byte(data);
> +
> +	/* Create scancode */
> +	scancode =  command;
> +	scancode |= address << 8;
> +	scancode |= customer << 16;
> +
> +	/* Last sanity check */
> +	if (data->irdata_error) {
> +		dev_dbg(dev, "RC6 - Cell error(s)\n");
> +		return;
> +	}
> +
> +	dev_dbg(dev, "IR-RC6 ad 0x%02X cm 0x%02X cu 0x%04X "
> +		"toggle %u mode %u scan 0x%08X\n",
> +		address,
> +		command,
> +		customer,
> +		(unsigned int)toggle,
> +		(unsigned int)mode,
> +		scancode);
> +
> +	wbcir_keydown(data, scancode, toggle);
> +}
> +
> +static void
> +wbcir_parse_rc5(struct device *dev, struct wbcir_data *data)
> +{
> +	/*
> +	 * Bits are manchester coded as follows:
> +	 * cell1 + cell0 = logic "0"
> +	 * cell0 + cell1 = logic "1"
> +	 * (i.e. the reverse of RC6)
> +	 *
> +	 * Start bit 1		- "1" - discarded
> +	 * Start bit 2		- Must be inverted to get command bit 6
> +	 * Toggle bit
> +	 * Address Bit 4 - 0
> +	 * Command Bit 5 - 0
> +	 */
> +	u8 toggle;
> +	u8 address;
> +	u8 command;
> +	u32 scancode;
> +
> +	/* Start bit 1 */
> +	if (!get_bits(data, 1)) {
> +		dev_dbg(dev, "RC5 - Invalid start bit\n");
> +		return;
> +	}
> +
> +	/* Start bit 2 */
> +	if (!wbcir_get_rc5bits(data, 1))
> +		command = 0x40;
> +	else
> +		command = 0x00;
> +
> +	toggle   = wbcir_get_rc5bits(data, 1);
> +	address  = wbcir_get_rc5bits(data, 5);
> +	command |= wbcir_get_rc5bits(data, 6);
> +	scancode = address << 7 | command;
> +
> +	/* Last sanity check */
> +	if (data->irdata_error) {
> +		dev_dbg(dev, "RC5 - Invalid message\n");
> +		return;
> +	}
> +
> +	dev_dbg(dev, "IR-RC5 ad %u cm %u t %u s %u\n",
> +		(unsigned int)address,
> +		(unsigned int)command,
> +		(unsigned int)toggle,
> +		(unsigned int)scancode);
> +
> +	wbcir_keydown(data, scancode, toggle);
> +}
> +
> +static void
> +wbcir_parse_nec(struct device *dev, struct wbcir_data *data)
> +{
> +	/*
> +	 * Each bit represents 560 us.
> +	 *
> +	 * Leader		- 9 ms burst
> +	 * Gap			- 4.5 ms silence
> +	 * Address1 bit 0 - 7	- Address 1
> +	 * Address2 bit 0 - 7	- Address 2
> +	 * Command1 bit 0 - 7	- Command 1
> +	 * Command2 bit 0 - 7	- Command 2
> +	 *
> +	 * Note the bit order!
> +	 *
> +	 * With the old NEC protocol, Address2 was the inverse of Address1
> +	 * and Command2 was the inverse of Command1 and were used as
> +	 * an error check.
> +	 *
> +	 * With NEC extended, Address1 is the LSB of the Address and
> +	 * Address2 is the MSB, Command parsing remains unchanged.
> +	 *
> +	 * A repeat message is coded as:
> +	 * Leader		- 9 ms burst
> +	 * Gap			- 2.25 ms silence
> +	 * Repeat		- 560 us active
> +	 */
> +	u8 address1;
> +	u8 address2;
> +	u8 command1;
> +	u8 command2;
> +	u16 address;
> +	u32 scancode;
> +
> +	/* Leader mark */
> +	while (get_bits(data, 1) && !data->irdata_error)
> +		/* Do nothing */;
> +
> +	/* Leader space */
> +	if (get_bits(data, 4)) {
> +		dev_dbg(dev, "NEC - Invalid leader space\n");
> +		return;
> +	}
> +
> +	/* Repeat? */
> +	if (get_bits(data, 1)) {
> +		if (!data->keypressed) {
> +			dev_dbg(dev, "NEC - Stray repeat message\n");
> +			return;
> +		}
> +
> +		dev_dbg(dev, "IR-NEC repeat s %u\n",
> +			(unsigned int)data->last_scancode);
> +
> +		wbcir_keydown(data, data->last_scancode, data->last_toggle);
> +		return;
> +	}
> +
> +	/* Remaining leader space */
> +	if (get_bits(data, 3)) {
> +		dev_dbg(dev, "NEC - Invalid leader space\n");
> +		return;
> +	}
> +
> +	address1  = bitrev8(get_bits(data, 8));
> +	address2  = bitrev8(get_bits(data, 8));
> +	command1  = bitrev8(get_bits(data, 8));
> +	command2  = bitrev8(get_bits(data, 8));
> +
> +	/* Sanity check */
> +	if (data->irdata_error) {
> +		dev_dbg(dev, "NEC - Invalid message\n");
> +		return;
> +	}
> +
> +	/* Check command validity */
> +	if (command1 != ~command2) {
> +		dev_dbg(dev, "NEC - Command bytes mismatch\n");
> +		return;
> +	}
> +
> +	/* Check for extended NEC protocol */
> +	address = address1;
> +	if (address1 != ~address2)
> +		address |= address2 << 8;
> +
> +	scancode = address << 8 | command1;
> +
> +	dev_dbg(dev, "IR-NEC ad %u cm %u s %u\n",
> +		(unsigned int)address,
> +		(unsigned int)command1,
> +		(unsigned int)scancode);
> +
> +	wbcir_keydown(data, scancode, !data->last_toggle);
> +}
> +
> +
> +
> +/*****************************************************************************
> + *
> + * INTERRUPT FUNCTIONS
> + *
> + *****************************************************************************/
> +
> +static irqreturn_t
> +wbcir_irq_handler(int irqno, void *cookie)
> +{
> +	struct pnp_dev *device = cookie;
> +	struct wbcir_data *data = pnp_get_drvdata(device);
> +	struct device *dev = &device->dev;
> +	u8 status;
> +	unsigned long flags;
> +	u8 irdata[16];
> +	int i;
> +	unsigned int hw;
> +
> +	spin_lock_irqsave(&wbcir_lock, flags);
> +
> +	wbcir_select_bank(data, WBCIR_BANK_0);
> +
> +	status = inb(data->sbase + WBCIR_REG_SP3_EIR);
> +
> +	if (!(status & (WBCIR_IRQ_RX | WBCIR_IRQ_ERR))) {
> +		spin_unlock_irqrestore(&wbcir_lock, flags);
> +		return IRQ_NONE;
> +	}
> +
> +	if (status & WBCIR_IRQ_ERR)
> +		data->irdata_error = 1;
> +
> +	if (!(status & WBCIR_IRQ_RX))
> +		goto out;
> +
> +	/* Since RXHDLEV is set, at least 16 bytes are in the FIFO */
> +	insb(data->sbase + WBCIR_REG_SP3_RXDATA, &irdata[0], 8);
> +	insb(data->sbase + WBCIR_REG_SP3_RXDATA, &irdata[8], 8);
> +
> +	for (i = 0; i < sizeof(data); i++) {
> +		hw = hweight8(irdata[i]);
> +		if (hw > 4)
> +			add_irdata_bit(data, 0);
> +		else
> +			add_irdata_bit(data, 1);
> +
> +		if (hw == 8)
> +			data->idle_count++;
> +		else
> +			data->idle_count = 0;
> +	}
> +
> +	if (data->idle_count > WBCIR_MAX_IDLE_BYTES) {
> +		/* Drain the FIFO */
> +		while (inb(data->sbase + WBCIR_REG_SP3_LSR) & WBCIR_RX_AVAIL)
> +			inb(data->sbase + WBCIR_REG_SP3_RXDATA);
> +
> +		/* And set RXINACTIVE */
> +		outb(WBCIR_RX_DISABLE, data->sbase + WBCIR_REG_SP3_ASCR);
> +
> +		dev_dbg(dev, "IRDATA:\n");
> +		for (i = 0; i < data->irdata_count; i += BITS_PER_LONG)
> +			dev_dbg(dev, "0x%08lX\n", data->irdata[i/BITS_PER_LONG]);
> +
> +		switch (protocol) {
> +		case IR_PROTOCOL_RC5:
> +			wbcir_parse_rc5(dev, data);
> +			break;
> +		case IR_PROTOCOL_RC6:
> +			wbcir_parse_rc6(dev, data);
> +			break;
> +		case IR_PROTOCOL_NEC:
> +			wbcir_parse_nec(dev, data);
> +			break;
> +		}
> +
> +		wbcir_reset_irdata(data);
> +		data->idle_count = 0;
> +	}
> +
> +out:
> +	spin_unlock_irqrestore(&wbcir_lock, flags);
> +	return IRQ_HANDLED;
> +}
> +
> +
> +
> +/*****************************************************************************
> + *
> + * SUSPEND/RESUME FUNCTIONS
> + *
> + *****************************************************************************/
> +
> +static void
> +wbcir_shutdown(struct pnp_dev *device)
> +{
> +	struct device *dev = &device->dev;
> +	struct wbcir_data *data = pnp_get_drvdata(device);
> +	int do_wake = 1;
> +	u8 match[11];
> +	u8 mask[11];
> +	u8 rc6_csl = 0;
> +	int i;
> +
> +	memset(match, 0, sizeof(match));
> +	memset(mask, 0, sizeof(mask));
> +
> +	if (wake_sc == INVALID_SCANCODE || !device_may_wakeup(dev)) {
> +		do_wake = 0;
> +		goto finish;
> +	}
> +
> +	switch (protocol) {
> +	case IR_PROTOCOL_RC5:
> +		if (wake_sc > 0xFFF) {
> +			do_wake = 0;
> +			dev_err(dev, "RC5 - Invalid wake scancode\n");
> +			break;
> +		}
> +
> +		/* Mask = 13 bits, ex toggle */
> +		mask[0] = 0xFF;
> +		mask[1] = 0x17;
> +
> +		match[0]  = (wake_sc & 0x003F);      /* 6 command bits */
> +		match[0] |= (wake_sc & 0x0180) >> 1; /* 2 address bits */
> +		match[1]  = (wake_sc & 0x0E00) >> 9; /* 3 address bits */
> +		if (!(wake_sc & 0x0040))             /* 2nd start bit  */
> +			match[1] |= 0x10;
> +
> +		break;
> +
> +	case IR_PROTOCOL_NEC:
> +		if (wake_sc > 0xFFFFFF) {
> +			do_wake = 0;
> +			dev_err(dev, "NEC - Invalid wake scancode\n");
> +			break;
> +		}
> +
> +		mask[0] = mask[1] = mask[2] = mask[3] = 0xFF;
> +
> +		match[1] = bitrev8((wake_sc & 0xFF));
> +		match[0] = ~match[1];
> +
> +		match[3] = bitrev8((wake_sc & 0xFF00) >> 8);
> +		if (wake_sc > 0xFFFF)
> +			match[2] = bitrev8((wake_sc & 0xFF0000) >> 16);
> +		else
> +			match[2] = ~match[3];
> +
> +		break;
> +
> +	case IR_PROTOCOL_RC6:
> +
> +		if (wake_rc6mode == 0) {
> +			if (wake_sc > 0xFFFF) {
> +				do_wake = 0;
> +				dev_err(dev, "RC6 - Invalid wake scancode\n");
> +				break;
> +			}
> +
> +			/* Command */
> +			match[0] = wbcir_to_rc6cells(wake_sc >>  0);
> +			mask[0]  = 0xFF;
> +			match[1] = wbcir_to_rc6cells(wake_sc >>  4);
> +			mask[1]  = 0xFF;
> +
> +			/* Address */
> +			match[2] = wbcir_to_rc6cells(wake_sc >>  8);
> +			mask[2]  = 0xFF;
> +			match[3] = wbcir_to_rc6cells(wake_sc >> 12);
> +			mask[3]  = 0xFF;
> +
> +			/* Header */
> +			match[4] = 0x50; /* mode1 = mode0 = 0, ignore toggle */
> +			mask[4]  = 0xF0;
> +			match[5] = 0x09; /* start bit = 1, mode2 = 0 */
> +			mask[5]  = 0x0F;
> +
> +			rc6_csl = 44;
> +
> +		} else if (wake_rc6mode == 6) {
> +			i = 0;
> +
> +			/* Command */
> +			match[i]  = wbcir_to_rc6cells(wake_sc >>  0);
> +			mask[i++] = 0xFF;
> +			match[i]  = wbcir_to_rc6cells(wake_sc >>  4);
> +			mask[i++] = 0xFF;
> +
> +			/* Address + Toggle */
> +			match[i]  = wbcir_to_rc6cells(wake_sc >>  8);
> +			mask[i++] = 0xFF;
> +			match[i]  = wbcir_to_rc6cells(wake_sc >> 12);
> +			mask[i++] = 0x3F;
> +
> +			/* Customer bits 7 - 0 */
> +			match[i]  = wbcir_to_rc6cells(wake_sc >> 16);
> +			mask[i++] = 0xFF;
> +			match[i]  = wbcir_to_rc6cells(wake_sc >> 20);
> +			mask[i++] = 0xFF;
> +
> +			if (wake_sc & 0x80000000) {
> +				/* Customer range bit and bits 15 - 8 */
> +				match[i]  = wbcir_to_rc6cells(wake_sc >> 24);
> +				mask[i++] = 0xFF;
> +				match[i]  = wbcir_to_rc6cells(wake_sc >> 28);
> +				mask[i++] = 0xFF;
> +				rc6_csl = 76;
> +			} else if (wake_sc <= 0x007FFFFF) {
> +				rc6_csl = 60;
> +			} else {
> +				do_wake = 0;
> +				dev_err(dev, "RC6 - Invalid wake scancode\n");
> +				break;
> +			}
> +
> +			/* Header */
> +			match[i]  = 0x93; /* mode1 = mode0 = 1, submode = 0 */
> +			mask[i++] = 0xFF;
> +			match[i]  = 0x0A; /* start bit = 1, mode2 = 1 */
> +			mask[i++] = 0x0F;
> +
> +		} else {
> +			do_wake = 0;
> +			dev_err(dev, "RC6 - Invalid wake mode\n");
> +		}
> +
> +		break;
> +
> +	default:
> +		do_wake = 0;
> +		break;
> +	}
> +
> +finish:
> +	if (do_wake) {
> +		/* Set compare and compare mask */
> +		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX,
> +			       WBCIR_REGSEL_COMPARE | WBCIR_REG_ADDR0,
> +			       0x3F);
> +		outsb(data->wbase + WBCIR_REG_WCEIR_DATA, match, 11);
> +		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX,
> +			       WBCIR_REGSEL_MASK | WBCIR_REG_ADDR0,
> +			       0x3F);
> +		outsb(data->wbase + WBCIR_REG_WCEIR_DATA, mask, 11);
> +
> +		/* RC6 Compare String Len */
> +		outb(rc6_csl, data->wbase + WBCIR_REG_WCEIR_CSL);
> +
> +		/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
> +		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
> +
> +		/* Clear BUFF_EN, Clear END_EN, Set MATCH_EN */
> +		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x01, 0x07);
> +
> +		/* Set CEIR_EN */
> +		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x01, 0x01);
> +
> +	} else {
> +		/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
> +		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
> +
> +		/* Clear CEIR_EN */
> +		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
> +	}
> +
> +	/* Disable interrupts */
> +	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
> +}
> +
> +static int
> +wbcir_suspend(struct pnp_dev *device, pm_message_t state)
> +{
> +	wbcir_shutdown(device);
> +	return 0;
> +}
> +
> +static int
> +wbcir_resume(struct pnp_dev *device)
> +{
> +	struct wbcir_data *data = pnp_get_drvdata(device);
> +
> +	/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
> +	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
> +
> +	/* Clear CEIR_EN */
> +	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
> +
> +	/* Enable interrupts */
> +	wbcir_reset_irdata(data);
> +	outb(WBCIR_IRQ_RX | WBCIR_IRQ_ERR, data->sbase + WBCIR_REG_SP3_IER);
> +
> +	return 0;
> +}
> +
> +
> +
> +/*****************************************************************************
> + *
> + * SETUP/INIT FUNCTIONS
> + *
> + *****************************************************************************/
> +
> +static ssize_t
> +wbcir_show_last_scancode(struct device *dev,
> +			    struct device_attribute *attr, char *buf)
> +{
> +	struct pnp_dev *device = container_of(dev, struct pnp_dev, dev);
> +	struct wbcir_data *data = pnp_get_drvdata(device);
> +	return sprintf(buf, "0x%08X\n", data->last_scancode);
> +}
> +
> +static struct device_attribute dev_attr_last_scancode = {
> +	.attr = {
> +		.name = "last_scancode",
> +		.mode = 0444,
> +	},
> +	.show = wbcir_show_last_scancode,
> +	.store = NULL,
> +
> +};

Why is this needed? And if this is needed we have a nice macro
for that.

> +
> +static struct attribute *wbcir_attributes[] = {
> +	&dev_attr_last_scancode.attr,
> +	NULL,
> +};
> +
> +static struct attribute_group wbcir_attribute_group = {
> +	.attrs = wbcir_attributes,
> +};
> +
> +static void
> +wbcir_cfg_ceir(struct wbcir_data *data)
> +{
> +	u8 tmp;
> +
> +	/* Set PROT_SEL, RX_INV, Clear CEIR_EN (needed for the led) */
> +	tmp = protocol << 4;
> +	if (invert)
> +		tmp |= 0x08;
> +	outb(tmp, data->wbase + WBCIR_REG_WCEIR_CTL);
> +
> +	/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
> +	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
> +
> +	/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
> +	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
> +
> +	/* Set RC5 cell time to correspond to 36 kHz */
> +	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CFG1, 0x4A, 0x7F);
> +
> +	/* Set IRTX_INV */
> +	if (invert)
> +		outb(0x04, data->ebase + WBCIR_REG_ECEIR_CCTL);
> +	else
> +		outb(0x00, data->ebase + WBCIR_REG_ECEIR_CCTL);
> +
> +	/*
> +	 * Clear IR LED, set SP3 clock to 24Mhz
> +	 * set SP3_IRRX_SW to binary 01, helpfully not documented
> +	 */
> +	outb(0x10, data->ebase + WBCIR_REG_ECEIR_CTS);
> +}
> +
> +static int
> +wbcir_probe(struct pnp_dev *device, const struct pnp_device_id *dev_id)

__devinit

> +{
> +	struct device *dev = &device->dev;
> +	struct wbcir_data *data;
> +	int err;
> +
> +	if (!(pnp_port_len(device, 0) == EHFUNC_IOMEM_LEN &&
> +	      pnp_port_len(device, 1) == WAKEUP_IOMEM_LEN &&
> +	      pnp_port_len(device, 2) == SP_IOMEM_LEN)) {
> +		dev_err(dev, "Invalid resources\n");
> +		return -ENODEV;
> +	}
> +
> +	data = kzalloc(sizeof(*data), GFP_KERNEL);
> +	if (!data) {
> +		err = -ENOMEM;
> +		goto exit;
> +	}
> +
> +	pnp_set_drvdata(device, data);
> +
> +	data->ebase = pnp_port_start(device, 0);
> +	data->wbase = pnp_port_start(device, 1);
> +	data->sbase = pnp_port_start(device, 2);
> +	data->irq = pnp_irq(device, 0);
> +
> +	if (data->wbase == 0 || data->ebase == 0 ||
> +	    data->sbase == 0 || data->irq == 0) {
> +		err = -ENODEV;
> +		dev_err(dev, "Invalid resources\n");
> +		goto exit_free_data;
> +	}
> +
> +	dev_info(&device->dev, "Found device "
> +		 "(w: 0x%lX, e: 0x%lX, s: 0x%lX, i: %u)\n",
> +		 data->wbase, data->ebase, data->sbase, data->irq);
> +

dev_dbg() I think.

> +	if (!request_region(data->wbase, WAKEUP_IOMEM_LEN, DRVNAME)) {
> +		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
> +			data->wbase, data->wbase + WAKEUP_IOMEM_LEN - 1);
> +		err = -EBUSY;
> +		goto exit_free_data;
> +	}
> +
> +	if (!request_region(data->ebase, EHFUNC_IOMEM_LEN, DRVNAME)) {
> +		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
> +			data->ebase, data->ebase + EHFUNC_IOMEM_LEN - 1);
> +		err = -EBUSY;
> +		goto exit_release_wbase;
> +	}
> +
> +	if (!request_region(data->sbase, SP_IOMEM_LEN, DRVNAME)) {
> +		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
> +			data->sbase, data->sbase + SP_IOMEM_LEN - 1);
> +		err = -EBUSY;
> +		goto exit_release_ebase;
> +	}
> +
> +	err = request_irq(data->irq, wbcir_irq_handler,
> +			  IRQF_DISABLED, DRVNAME, device);
> +	if (err) {
> +		dev_err(dev, "Failed to claim IRQ %u\n", data->irq);
> +		err = -EBUSY;
> +		goto exit_release_sbase;
> +	}
> +
> +	led_trigger_register_simple("cir-tx", &data->txtrigger);
> +	if (!data->txtrigger) {
> +		err = -ENOMEM;
> +		goto exit_free_irq;
> +	}
> +
> +	led_trigger_register_simple("cir-rx", &data->rxtrigger);
> +	if (!data->rxtrigger) {
> +		err = -ENOMEM;
> +		goto exit_unregister_txtrigger;
> +	}
> +
> +	data->led.name = "cir::activity";
> +	data->led.default_trigger = "cir-rx";
> +	data->led.brightness_set = wbcir_led_brightness_set;
> +	data->led.brightness_get = wbcir_led_brightness_get;
> +	err = led_classdev_register(&device->dev, &data->led);
> +	if (err)
> +		goto exit_unregister_rxtrigger;
> +
> +	data->input_dev = input_allocate_device();
> +	if (!data->input_dev) {
> +		err = -ENOMEM;
> +		goto exit_unregister_led;
> +	}
> +
> +	data->input_dev->evbit[0] = BIT(EV_KEY);
> +	data->input_dev->name = WBCIR_ACPI_NAME;
> +	data->input_dev->phys = "wbcir/cir0";
> +	data->input_dev->id.bustype = BUS_HOST;
> +	data->input_dev->id.vendor  = PCI_VENDOR_ID_WINBOND;
> +	data->input_dev->id.product = WBCIR_ID_FAMILY;
> +	data->input_dev->id.version = WBCIR_ID_CHIP;
> +	data->input_dev->getkeycode = wbcir_getkeycode;
> +	data->input_dev->setkeycode = wbcir_setkeycode;
> +	input_set_drvdata(data->input_dev, data);
> +
> +	err = input_register_device(data->input_dev);
> +	if (err)
> +		goto exit_free_input;
> +
> +	data->last_scancode = INVALID_SCANCODE;
> +	err = sysfs_create_group(&device->dev.kobj, &wbcir_attribute_group);
> +	if (err)
> +		goto exit_unregister_input;
> +
> +	INIT_LIST_HEAD(&data->keytable);
> +	setup_timer(&data->timer_keyup, wbcir_keyup, (unsigned long)data);
> +
> +	/* Load default keymaps */
> +	if (protocol == IR_PROTOCOL_RC6) {
> +		int i;
> +		for (i = 0; i < ARRAY_SIZE(rc6_def_keymap); i++) {
> +			err = wbcir_setkeycode(data->input_dev,
> +					       (int)rc6_def_keymap[i].scancode,
> +					       (int)rc6_def_keymap[i].keycode);
> +			if (err)
> +				goto exit_unregister_keys;
> +		}
> +	}
> +
> +	device_init_wakeup(&device->dev, 1);
> +
> +	wbcir_cfg_ceir(data);
> +
> +	/* Disable interrupts */
> +	wbcir_select_bank(data, WBCIR_BANK_0);
> +	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
> +
> +	/* Enable extended mode */
> +	wbcir_select_bank(data, WBCIR_BANK_2);
> +	outb(WBCIR_EXT_ENABLE, data->sbase + WBCIR_REG_SP3_EXCR1);
> +
> +	/*
> +	 * Configure baud generator, IR data will be sampled at
> +	 * a bitrate of: (24Mhz * prescaler) / (divisor * 16).
> +	 *
> +	 * The ECIR registers include a flag to change the
> +	 * 24Mhz clock freq to 48Mhz.
> +	 */
> +
> +	/* prescaler 1.0, tx/rx fifo lvl 32 */
> +	outb(0x35, data->sbase + WBCIR_REG_SP3_EXCR2);
> +
> +	/* Set baud divisor to generate one byte per bit/cell */
> +	switch (protocol) {
> +	case IR_PROTOCOL_RC5:
> +		outb(0xA7, data->sbase + WBCIR_REG_SP3_BGDL);
> +		break;
> +	case IR_PROTOCOL_RC6:
> +		outb(0x53, data->sbase + WBCIR_REG_SP3_BGDL);
> +		break;
> +	case IR_PROTOCOL_NEC:
> +		outb(0x69, data->sbase + WBCIR_REG_SP3_BGDL);
> +		break;
> +	}
> +	outb(0x00, data->sbase + WBCIR_REG_SP3_BGDH);
> +
> +	/* Set CEIR mode */
> +	wbcir_select_bank(data, WBCIR_BANK_0);
> +	outb(0xC0, data->sbase + WBCIR_REG_SP3_MCR);
> +	inb(data->sbase + WBCIR_REG_SP3_LSR); /* Clear LSR */
> +	inb(data->sbase + WBCIR_REG_SP3_MSR); /* Clear MSR */
> +
> +	/* Disable RX demod, run-length encoding/decoding, set freq span */
> +	wbcir_select_bank(data, WBCIR_BANK_7);
> +	outb(0x10, data->sbase + WBCIR_REG_SP3_RCCFG);
> +
> +	/* Disable timer */
> +	wbcir_select_bank(data, WBCIR_BANK_4);
> +	outb(0x00, data->sbase + WBCIR_REG_SP3_IRCR1);
> +
> +	/* Enable MSR interrupt, Clear AUX_IRX */
> +	wbcir_select_bank(data, WBCIR_BANK_5);
> +	outb(0x00, data->sbase + WBCIR_REG_SP3_IRCR2);
> +
> +	/* Disable CRC */
> +	wbcir_select_bank(data, WBCIR_BANK_6);
> +	outb(0x20, data->sbase + WBCIR_REG_SP3_IRCR3);
> +
> +	/* Set RX/TX (de)modulation freq, not really used */
> +	wbcir_select_bank(data, WBCIR_BANK_7);
> +	outb(0xF2, data->sbase + WBCIR_REG_SP3_IRRXDC);
> +	outb(0x69, data->sbase + WBCIR_REG_SP3_IRTXMC);
> +
> +	/* Set invert and pin direction */
> +	if (invert)
> +		outb(0x10, data->sbase + WBCIR_REG_SP3_IRCFG4);
> +	else
> +		outb(0x00, data->sbase + WBCIR_REG_SP3_IRCFG4);
> +
> +	/* Set FIFO thresholds (RX = 16, TX = 7), reset RX/TX */
> +	wbcir_select_bank(data, WBCIR_BANK_0);
> +	outb(0x97, data->sbase + WBCIR_REG_SP3_FCR);
> +
> +	/* Clear AUX status bits */
> +	outb(0xE0, data->sbase + WBCIR_REG_SP3_ASCR);
> +
> +	/* Enable interrupts */
> +	wbcir_select_bank(data, WBCIR_BANK_0);
> +	outb(WBCIR_IRQ_RX | WBCIR_IRQ_ERR, data->sbase + WBCIR_REG_SP3_IER);
> +
> +	return 0;
> +
> +exit_unregister_keys:
> +	if (!list_empty(&data->keytable)) {
> +		struct wbcir_keyentry *key;
> +		struct wbcir_keyentry *keytmp;
> +
> +		list_for_each_entry_safe(key, keytmp, &data->keytable, list) {
> +			list_del(&key->list);
> +			kfree(key);
> +		}
> +	}
> +exit_unregister_input:
> +	input_unregister_device(data->input_dev);
> +	/* Can't call input_free_device on an unregistered device */
> +	data->input_dev = NULL;
> +exit_free_input:
> +	input_free_device(data->input_dev);
> +exit_unregister_led:
> +	led_classdev_unregister(&data->led);
> +exit_unregister_rxtrigger:
> +	led_trigger_unregister_simple(data->rxtrigger);
> +exit_unregister_txtrigger:
> +	led_trigger_unregister_simple(data->txtrigger);
> +exit_free_irq:
> +	free_irq(data->irq, device);
> +exit_release_sbase:
> +	release_region(data->sbase, SP_IOMEM_LEN);
> +exit_release_ebase:
> +	release_region(data->ebase, EHFUNC_IOMEM_LEN);
> +exit_release_wbase:
> +	release_region(data->wbase, WAKEUP_IOMEM_LEN);
> +exit_free_data:
> +	kfree(data);
> +	pnp_set_drvdata(device, NULL);
> +exit:
> +	return err;
> +}
> +
> +static void
> +wbcir_remove(struct pnp_dev *device)

__devexit

> +{
> +	struct wbcir_data *data = pnp_get_drvdata(device);
> +	struct wbcir_keyentry *key;
> +	struct wbcir_keyentry *keytmp;
> +
> +	/* Disable interrupts */
> +	wbcir_select_bank(data, WBCIR_BANK_0);
> +	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
> +
> +	del_timer_sync(&data->timer_keyup);
> +
> +	free_irq(data->irq, device);
> +
> +	/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
> +	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
> +
> +	/* Clear CEIR_EN */
> +	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
> +
> +	/* Clear BUFF_EN, END_EN, MATCH_EN */
> +	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
> +
> +	sysfs_remove_group(&device->dev.kobj, &wbcir_attribute_group);
> +
> +	/* This will generate a keyup event if necessary */
> +	input_unregister_device(data->input_dev);
> +
> +	led_trigger_unregister_simple(data->rxtrigger);
> +	led_trigger_unregister_simple(data->txtrigger);
> +	led_classdev_unregister(&data->led);
> +
> +	/* This is ok since &data->led isn't actually used */
> +	wbcir_led_brightness_set(&data->led, LED_OFF);
> +
> +	release_region(data->wbase, WAKEUP_IOMEM_LEN);
> +	release_region(data->ebase, EHFUNC_IOMEM_LEN);
> +	release_region(data->sbase, SP_IOMEM_LEN);
> +
> +	list_for_each_entry_safe(key, keytmp, &data->keytable, list) {
> +		list_del(&key->list);
> +		kfree(key);
> +	}
> +
> +	kfree(data);
> +
> +	pnp_set_drvdata(device, NULL);
> +}
> +
> +static const struct pnp_device_id wbcir_ids[] = {
> +	{ "WEC1022", 0 },
> +	{ "", 0 }
> +};
> +MODULE_DEVICE_TABLE(pnp, wbcir_ids);
> +
> +static struct pnp_driver wbcir_driver = {
> +	.name     = WBCIR_ACPI_NAME,
> +	.id_table = wbcir_ids,
> +	.probe    = wbcir_probe,
> +	.remove   = wbcir_remove,

__devexit_p()

> +	.suspend  = wbcir_suspend,
> +	.resume   = wbcir_resume,

Switch to dev_pm_ops?

> +	.shutdown = wbcir_shutdown
> +};
> +
> +static int __init
> +wbcir_init(void)
> +{
> +	int ret;
> +
> +	switch (protocol) {
> +	case IR_PROTOCOL_RC5:
> +	case IR_PROTOCOL_NEC:
> +	case IR_PROTOCOL_RC6:
> +		break;
> +	default:
> +		printk(KERN_ERR DRVNAME ": Invalid protocol argument\n");
> +		return -EINVAL;
> +	}
> +
> +	ret = pnp_register_driver(&wbcir_driver);
> +	if (ret)
> +		printk(KERN_ERR DRVNAME ": Unable to register driver\n");
> +
> +	return ret;
> +}
> +
> +static void __exit
> +wbcir_exit(void)
> +{
> +	pnp_unregister_driver(&wbcir_driver);
> +}
> +
> +MODULE_AUTHOR("David Härdeman <david@hardeman.nu>");
> +MODULE_DESCRIPTION("Winbond SuperI/O Consumer IR Driver");
> +MODULE_LICENSE("GPL");
> +
> +module_init(wbcir_init);
> +module_exit(wbcir_exit);
> +
> +
> 
> -- 
> David Härdeman
> --
> To unsubscribe from this list: send the line "unsubscribe linux-input" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

-- 
Dmitry

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware
  2009-08-11 16:26 [patch 0/2] Winbond IR Driver - v2 David Härdeman
@ 2009-08-11 16:26   ` David Härdeman
  0 siblings, 0 replies; 16+ messages in thread
From: David Härdeman @ 2009-08-11 16:26 UTC (permalink / raw)
  To: linux-kernel; +Cc: jbarnes, akpm, bjorn.helgaas, linux-input

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: add-a-driver-for-the-winbond-wpcd376i-cir-functionality --]
[-- Type: text/plain, Size: 46903 bytes --]

Add a driver for the the Consumer IR (CIR) functionality of the Winbond
WPCD376I chipset (found on e.g. Intel DG45FC motherboards).

Changes since the last version: converted from an ACPI to a PNP driver
using a patch provided by Bjorn Helgaas <bjorn.helgaas@hp.com>.

Signed-off-by: David Härdeman <david@hardeman.nu>
Reviewed-by: Jesse Barnes <jbarnes@virtuousgeek.org>

Index: linux-2.6/MAINTAINERS
===================================================================
--- linux-2.6.orig/MAINTAINERS	2009-08-09 11:32:42.000000000 +0200
+++ linux-2.6/MAINTAINERS	2009-08-09 11:44:35.000000000 +0200
@@ -6448,6 +6448,12 @@
 S:	Maintained
 F:	drivers/scsi/wd7000.c
 
+WINBOND CIR DRIVER
+P:	David Härdeman
+M:	david@hardeman.nu
+S:	Maintained
+F:	drivers/input/misc/winbond-cir.c
+
 WIMAX STACK
 P:	Inaky Perez-Gonzalez
 M:	inaky.perez-gonzalez@intel.com
Index: linux-2.6/drivers/input/misc/Kconfig
===================================================================
--- linux-2.6.orig/drivers/input/misc/Kconfig	2009-08-09 11:32:42.000000000 +0200
+++ linux-2.6/drivers/input/misc/Kconfig	2009-08-11 18:20:41.000000000 +0200
@@ -222,6 +222,22 @@
 	  To compile this driver as a module, choose M here: the
 	  module will be called sgi_btns.
 
+config INPUT_WINBOND_CIR
+	tristate "Winbond IR remote control"
+	depends on X86 && PNP
+	select LEDS_CLASS
+	select BITREVERSE
+	help
+	  Say Y here if you want to use the IR remote functionality found
+	  in some Winbond SuperI/O chips. Currently only the WPCD376I
+	  chip is supported (included in some Intel Media series motherboards).
+
+	  IR Receive and wake-on-IR from suspend and power-off is currently
+	  supported.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called winbond_cir.
+
 config HP_SDC_RTC
 	tristate "HP SDC Real Time Clock"
 	depends on (GSC || HP300) && SERIO
Index: linux-2.6/drivers/input/misc/Makefile
===================================================================
--- linux-2.6.orig/drivers/input/misc/Makefile	2009-08-09 11:32:42.000000000 +0200
+++ linux-2.6/drivers/input/misc/Makefile	2009-08-09 11:44:35.000000000 +0200
@@ -19,6 +19,7 @@
 obj-$(CONFIG_INPUT_PCSPKR)		+= pcspkr.o
 obj-$(CONFIG_INPUT_POWERMATE)		+= powermate.o
 obj-$(CONFIG_INPUT_RB532_BUTTON)	+= rb532_button.o
+obj-$(CONFIG_INPUT_WINBOND_CIR)		+= winbond-cir.o
 obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER)	+= rotary_encoder.o
 obj-$(CONFIG_INPUT_SGI_BTNS)		+= sgi_btns.o
 obj-$(CONFIG_INPUT_SPARCSPKR)		+= sparcspkr.o
Index: linux-2.6/drivers/input/misc/winbond-cir.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/drivers/input/misc/winbond-cir.c	2009-08-11 18:19:46.000000000 +0200
@@ -0,0 +1,1644 @@
+/*
+ *  winbond-cir.c - Driver for the Consumer IR functionality of Winbond
+ *                  SuperI/O chips.
+ *
+ *  Currently supports the Winbond WPCD376i chip (PNP id WEC1022), but
+ *  could probably support others (Winbond WEC102X, NatSemi, etc)
+ *  with minor modifications.
+ *
+ *  Original Author: David Härdeman <david@hardeman.nu>
+ *     Copyright (C) 2009 David Härdeman <david@hardeman.nu>
+ *
+ *  Dedicated to Matilda, my newborn daughter, without whose loving attention
+ *  this driver would have been finished in half the time and with a fraction
+ *  of the bugs.
+ *
+ *  Written using:
+ *    o Winbond WPCD376I datasheet helpfully provided by Jesse Barnes at Intel
+ *    o NatSemi PC87338/PC97338 datasheet (for the serial port stuff)
+ *    o DSDT dumps
+ *
+ *  Supported features:
+ *    o RC6
+ *    o Wake-On-CIR functionality
+ *
+ *  To do:
+ *    o Test NEC and RC5
+ *
+ *  Left as an exercise for the reader:
+ *    o Learning (I have neither the hardware, nor the need)
+ *    o IR Transmit (ibid)
+ *
+ *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/pnp.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/pci_ids.h>
+#include <linux/io.h>
+#include <linux/bitrev.h>
+#include <linux/bitops.h>
+
+#define DRVNAME "winbond-cir"
+
+/* CEIR Wake-Up Registers, relative to data->wbase                      */
+#define WBCIR_REG_WCEIR_CTL	0x03 /* CEIR Receiver Control		*/
+#define WBCIR_REG_WCEIR_STS	0x04 /* CEIR Receiver Status		*/
+#define WBCIR_REG_WCEIR_EV_EN	0x05 /* CEIR Receiver Event Enable	*/
+#define WBCIR_REG_WCEIR_CNTL	0x06 /* CEIR Receiver Counter Low	*/
+#define WBCIR_REG_WCEIR_CNTH	0x07 /* CEIR Receiver Counter High	*/
+#define WBCIR_REG_WCEIR_INDEX	0x08 /* CEIR Receiver Index		*/
+#define WBCIR_REG_WCEIR_DATA	0x09 /* CEIR Receiver Data		*/
+#define WBCIR_REG_WCEIR_CSL	0x0A /* CEIR Re. Compare Strlen		*/
+#define WBCIR_REG_WCEIR_CFG1	0x0B /* CEIR Re. Configuration 1	*/
+#define WBCIR_REG_WCEIR_CFG2	0x0C /* CEIR Re. Configuration 2	*/
+
+/* CEIR Enhanced Functionality Registers, relative to data->ebase       */
+#define WBCIR_REG_ECEIR_CTS	0x00 /* Enhanced IR Control Status	*/
+#define WBCIR_REG_ECEIR_CCTL	0x01 /* Infrared Counter Control	*/
+#define WBCIR_REG_ECEIR_CNT_LO	0x02 /* Infrared Counter LSB		*/
+#define WBCIR_REG_ECEIR_CNT_HI	0x03 /* Infrared Counter MSB		*/
+#define WBCIR_REG_ECEIR_IREM	0x04 /* Infrared Emitter Status		*/
+
+/* SP3 Banked Registers, relative to data->sbase                        */
+#define WBCIR_REG_SP3_BSR	0x03 /* Bank Select, all banks		*/
+				      /* Bank 0				*/
+#define WBCIR_REG_SP3_RXDATA	0x00 /* FIFO RX data (r)		*/
+#define WBCIR_REG_SP3_TXDATA	0x00 /* FIFO TX data (w)		*/
+#define WBCIR_REG_SP3_IER	0x01 /* Interrupt Enable		*/
+#define WBCIR_REG_SP3_EIR	0x02 /* Event Identification (r)	*/
+#define WBCIR_REG_SP3_FCR	0x02 /* FIFO Control (w)		*/
+#define WBCIR_REG_SP3_MCR	0x04 /* Mode Control			*/
+#define WBCIR_REG_SP3_LSR	0x05 /* Link Status			*/
+#define WBCIR_REG_SP3_MSR	0x06 /* Modem Status			*/
+#define WBCIR_REG_SP3_ASCR	0x07 /* Aux Status and Control		*/
+				      /* Bank 2				*/
+#define WBCIR_REG_SP3_BGDL	0x00 /* Baud Divisor LSB		*/
+#define WBCIR_REG_SP3_BGDH	0x01 /* Baud Divisor MSB		*/
+#define WBCIR_REG_SP3_EXCR1	0x02 /* Extended Control 1		*/
+#define WBCIR_REG_SP3_EXCR2	0x04 /* Extended Control 2		*/
+#define WBCIR_REG_SP3_TXFLV	0x06 /* TX FIFO Level			*/
+#define WBCIR_REG_SP3_RXFLV	0x07 /* RX FIFO Level			*/
+				      /* Bank 3				*/
+#define WBCIR_REG_SP3_MRID	0x00 /* Module Identification		*/
+#define WBCIR_REG_SP3_SH_LCR	0x01 /* LCR Shadow			*/
+#define WBCIR_REG_SP3_SH_FCR	0x02 /* FCR Shadow			*/
+				      /* Bank 4				*/
+#define WBCIR_REG_SP3_IRCR1	0x02 /* Infrared Control 1		*/
+				      /* Bank 5				*/
+#define WBCIR_REG_SP3_IRCR2	0x04 /* Infrared Control 2		*/
+				      /* Bank 6				*/
+#define WBCIR_REG_SP3_IRCR3	0x00 /* Infrared Control 3		*/
+#define WBCIR_REG_SP3_SIR_PW	0x02 /* SIR Pulse Width		*/
+				      /* Bank 7				*/
+#define WBCIR_REG_SP3_IRRXDC	0x00 /* IR RX Demod Control		*/
+#define WBCIR_REG_SP3_IRTXMC	0x01 /* IR TX Mod Control		*/
+#define WBCIR_REG_SP3_RCCFG	0x02 /* CEIR Config			*/
+#define WBCIR_REG_SP3_IRCFG1	0x04 /* Infrared Config 1		*/
+#define WBCIR_REG_SP3_IRCFG4	0x07 /* Infrared Config 4		*/
+
+/*
+ * Magic values follow
+ */
+
+/* No interrupts for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_NONE		0x00
+/* RX data bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_RX		0x01
+/* Over/Under-flow bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_ERR		0x04
+/* Led enable/disable bit for WBCIR_REG_ECEIR_CTS */
+#define WBCIR_LED_ENABLE	0x80
+/* RX data available bit for WBCIR_REG_SP3_LSR */
+#define WBCIR_RX_AVAIL		0x01
+/* RX disable bit for WBCIR_REG_SP3_ASCR */
+#define WBCIR_RX_DISABLE	0x20
+/* Extended mode enable bit for WBCIR_REG_SP3_EXCR1 */
+#define WBCIR_EXT_ENABLE	0x01
+/* Select compare register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */
+#define WBCIR_REGSEL_COMPARE	0x10
+/* Select mask register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */
+#define WBCIR_REGSEL_MASK	0x20
+/* Starting address of selected register in WBCIR_REG_WCEIR_INDEX */
+#define WBCIR_REG_ADDR0		0x00
+
+/* Valid banks for the SP3 UART */
+enum wbcir_bank {
+	WBCIR_BANK_0          = 0x00,
+	WBCIR_BANK_1          = 0x80,
+	WBCIR_BANK_2          = 0xE0,
+	WBCIR_BANK_3          = 0xE4,
+	WBCIR_BANK_4          = 0xE8,
+	WBCIR_BANK_5          = 0xEC,
+	WBCIR_BANK_6          = 0xF0,
+	WBCIR_BANK_7          = 0xF4,
+};
+
+/* Supported IR Protocols */
+enum wbcir_protocol {
+	IR_PROTOCOL_RC5          = 0x0,
+	IR_PROTOCOL_NEC          = 0x1,
+	IR_PROTOCOL_RC6          = 0x2,
+};
+
+/* Misc */
+#define WBCIR_NAME	"Winbond CIR"
+#define WBCIR_ID_FAMILY          0xF1 /* Family ID for the WPCD376I	*/
+#define	WBCIR_ID_CHIP            0x04 /* Chip ID for the WPCD376I	*/
+#define IR_KEYPRESS_TIMEOUT       250 /* FIXME: should be per-protocol? */
+#define INVALID_SCANCODE   0x7FFFFFFF /* Invalid with all protos	*/
+#define WAKEUP_IOMEM_LEN         0x10 /* Wake-Up I/O Reg Len		*/
+#define EHFUNC_IOMEM_LEN         0x10 /* Enhanced Func I/O Reg Len	*/
+#define SP_IOMEM_LEN             0x08 /* Serial Port 3 (IR) Reg Len	*/
+#define WBCIR_MAX_IDLE_BYTES       10
+
+static DEFINE_SPINLOCK(wbcir_lock);
+static DEFINE_RWLOCK(keytable_lock);
+
+struct wbcir_key {
+	u32 scancode;
+	unsigned int keycode;
+};
+
+struct wbcir_keyentry {
+	struct wbcir_key key;
+	struct list_head list;
+};
+
+static struct wbcir_key rc6_def_keymap[] = {
+	{ 0x800F0400, KEY_0			},
+	{ 0x800F0401, KEY_1			},
+	{ 0x800F0402, KEY_2			},
+	{ 0x800F0403, KEY_3			},
+	{ 0x800F0404, KEY_4			},
+	{ 0x800F0405, KEY_5			},
+	{ 0x800F0406, KEY_6			},
+	{ 0x800F0407, KEY_7			},
+	{ 0x800F0408, KEY_8			},
+	{ 0x800F0409, KEY_9			},
+	{ 0x800F041D, KEY_NUMERIC_STAR		},
+	{ 0x800F041C, KEY_NUMERIC_POUND		},
+	{ 0x800F0410, KEY_VOLUMEUP		},
+	{ 0x800F0411, KEY_VOLUMEDOWN		},
+	{ 0x800F0412, KEY_CHANNELUP		},
+	{ 0x800F0413, KEY_CHANNELDOWN		},
+	{ 0x800F040E, KEY_MUTE			},
+	{ 0x800F040D, KEY_VENDOR		}, /* Vista Logo Key */
+	{ 0x800F041E, KEY_UP			},
+	{ 0x800F041F, KEY_DOWN			},
+	{ 0x800F0420, KEY_LEFT			},
+	{ 0x800F0421, KEY_RIGHT			},
+	{ 0x800F0422, KEY_OK			},
+	{ 0x800F0423, KEY_ESC			},
+	{ 0x800F040F, KEY_INFO			},
+	{ 0x800F040A, KEY_CLEAR			},
+	{ 0x800F040B, KEY_ENTER			},
+	{ 0x800F045B, KEY_RED			},
+	{ 0x800F045C, KEY_GREEN			},
+	{ 0x800F045D, KEY_YELLOW		},
+	{ 0x800F045E, KEY_BLUE			},
+	{ 0x800F045A, KEY_TEXT			},
+	{ 0x800F0427, KEY_SWITCHVIDEOMODE	},
+	{ 0x800F040C, KEY_POWER			},
+	{ 0x800F0450, KEY_RADIO			},
+	{ 0x800F0448, KEY_PVR			},
+	{ 0x800F0447, KEY_AUDIO			},
+	{ 0x800F0426, KEY_EPG			},
+	{ 0x800F0449, KEY_CAMERA		},
+	{ 0x800F0425, KEY_TV			},
+	{ 0x800F044A, KEY_VIDEO			},
+	{ 0x800F0424, KEY_DVD			},
+	{ 0x800F0416, KEY_PLAY			},
+	{ 0x800F0418, KEY_PAUSE			},
+	{ 0x800F0419, KEY_STOP			},
+	{ 0x800F0414, KEY_FASTFORWARD		},
+	{ 0x800F041A, KEY_NEXT			},
+	{ 0x800F041B, KEY_PREVIOUS		},
+	{ 0x800F0415, KEY_REWIND		},
+	{ 0x800F0417, KEY_RECORD		},
+};
+
+/* Registers and other state is protected by wbcir_lock */
+struct wbcir_data {
+	unsigned long wbase;        /* Wake-Up Baseaddr		*/
+	unsigned long ebase;        /* Enhanced Func. Baseaddr	*/
+	unsigned long sbase;        /* Serial Port Baseaddr	*/
+	unsigned int  irq;          /* Serial Port IRQ		*/
+
+	struct input_dev *input_dev;
+	struct timer_list timer_keyup;
+	struct led_trigger *rxtrigger;
+	struct led_trigger *txtrigger;
+	struct led_classdev led;
+
+	u32 last_scancode;
+	unsigned int last_keycode;
+	u8 last_toggle;
+	u8 keypressed;
+	unsigned long keyup_jiffies;
+	unsigned int idle_count;
+
+	/* RX irdata and parsing state */
+	unsigned long irdata[30];
+	unsigned int irdata_count;
+	unsigned int irdata_idle;
+	unsigned int irdata_off;
+	unsigned int irdata_error;
+
+	/* Protected by keytable_lock */
+	struct list_head keytable;
+};
+
+static enum wbcir_protocol protocol = IR_PROTOCOL_RC6;
+module_param(protocol, uint, 0444);
+MODULE_PARM_DESC(protocol, "IR protocol to use "
+		 "(0 = RC5, 1 = NEC, 2 = RC6A, default)");
+
+static int invert; /* default = 0 */
+module_param(invert, bool, 0444);
+MODULE_PARM_DESC(invert, "Invert the signal from the IR receiver");
+
+static unsigned int wake_sc = 0x800F040C;
+module_param(wake_sc, uint, 0644);
+MODULE_PARM_DESC(wake_sc, "Scancode of the power-on IR command");
+
+static unsigned int wake_rc6mode = 6;
+module_param(wake_rc6mode, uint, 0644);
+MODULE_PARM_DESC(wake_rc6mode, "RC6 mode for the power-on command "
+		 "(0 = 0, 6 = 6A, default)");
+
+
+
+/*****************************************************************************
+ *
+ * UTILITY FUNCTIONS
+ *
+ *****************************************************************************/
+
+/* Caller needs to hold wbcir_lock */
+static void
+wbcir_set_bits(unsigned long addr, u8 bits, u8 mask)
+{
+	u8 val;
+
+	val = inb(addr);
+	val = ((val & ~mask) | (bits & mask));
+	outb(val, addr);
+}
+
+/* Selects the register bank for the serial port */
+static inline void
+wbcir_select_bank(struct wbcir_data *data, enum wbcir_bank bank)
+{
+	outb(bank, data->sbase + WBCIR_REG_SP3_BSR);
+}
+
+static enum led_brightness
+wbcir_led_brightness_get(struct led_classdev *led_cdev)
+{
+	struct wbcir_data *data = container_of(led_cdev,
+					       struct wbcir_data,
+					       led);
+
+	if (inb(data->ebase + WBCIR_REG_ECEIR_CTS) & WBCIR_LED_ENABLE)
+		return LED_FULL;
+	else
+		return LED_OFF;
+}
+
+static void
+wbcir_led_brightness_set(struct led_classdev *led_cdev,
+			    enum led_brightness brightness)
+{
+	struct wbcir_data *data = container_of(led_cdev,
+					       struct wbcir_data,
+					       led);
+
+	wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CTS,
+		       brightness == LED_OFF ? 0x00 : WBCIR_LED_ENABLE,
+		       WBCIR_LED_ENABLE);
+}
+
+/* Manchester encodes bits to RC6 message cells (see wbcir_parse_rc6) */
+static u8
+wbcir_to_rc6cells(u8 val)
+{
+	u8 coded = 0x00;
+	int i;
+
+	val &= 0x0F;
+	for (i = 0; i < 4; i++) {
+		if (val & 0x01)
+			coded |= 0x02 << (i * 2);
+		else
+			coded |= 0x01 << (i * 2);
+		val >>= 1;
+	}
+
+	return coded;
+}
+
+
+
+/*****************************************************************************
+ *
+ * INPUT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static unsigned int
+wbcir_do_getkeycode(struct wbcir_data *data, u32 scancode)
+{
+	struct wbcir_keyentry *keyentry;
+	unsigned int keycode = KEY_RESERVED;
+	unsigned long flags;
+
+	read_lock_irqsave(&keytable_lock, flags);
+
+	list_for_each_entry(keyentry, &data->keytable, list) {
+		if (keyentry->key.scancode == scancode) {
+			keycode = keyentry->key.keycode;
+			break;
+		}
+	}
+
+	read_unlock_irqrestore(&keytable_lock, flags);
+	return keycode;
+}
+
+static int
+wbcir_getkeycode(struct input_dev *dev, int scancode, int *keycode)
+{
+	struct wbcir_data *data = input_get_drvdata(dev);
+
+	*keycode = (int)wbcir_do_getkeycode(data, (u32)scancode);
+	return 0;
+}
+
+static int
+wbcir_setkeycode(struct input_dev *dev, int sscancode, int keycode)
+{
+	struct wbcir_data *data = input_get_drvdata(dev);
+	struct wbcir_keyentry *keyentry;
+	struct wbcir_keyentry *new_keyentry;
+	unsigned long flags;
+	unsigned int old_keycode = KEY_RESERVED;
+	u32 scancode = (u32)sscancode;
+
+	if (keycode < 0 || keycode > KEY_MAX)
+		return -EINVAL;
+
+	new_keyentry = kmalloc(sizeof(*new_keyentry), GFP_KERNEL);
+	if (!new_keyentry)
+		return -ENOMEM;
+
+	write_lock_irqsave(&keytable_lock, flags);
+
+	list_for_each_entry(keyentry, &data->keytable, list) {
+		if (keyentry->key.scancode != scancode)
+			continue;
+
+		old_keycode = keyentry->key.keycode;
+		keyentry->key.keycode = keycode;
+
+		if (keyentry->key.keycode == KEY_RESERVED) {
+			list_del(&keyentry->list);
+			kfree(keyentry);
+		}
+
+		break;
+	}
+
+	set_bit(keycode, dev->keybit);
+
+	if (old_keycode == KEY_RESERVED) {
+		new_keyentry->key.scancode = scancode;
+		new_keyentry->key.keycode = keycode;
+		list_add(&new_keyentry->list, &data->keytable);
+	} else {
+		kfree(new_keyentry);
+		clear_bit(old_keycode, dev->keybit);
+		list_for_each_entry(keyentry, &data->keytable, list) {
+			if (keyentry->key.keycode == old_keycode) {
+				set_bit(old_keycode, dev->keybit);
+				break;
+			}
+		}
+	}
+
+	write_unlock_irqrestore(&keytable_lock, flags);
+	return 0;
+}
+
+/*
+ * Timer function to report keyup event some time after keydown is
+ * reported by the ISR.
+ */
+static void
+wbcir_keyup(unsigned long cookie)
+{
+	struct wbcir_data *data = (struct wbcir_data *)cookie;
+	unsigned long flags;
+
+	/*
+	 * data->keyup_jiffies is used to prevent a race condition if a
+	 * hardware interrupt occurs at this point and the keyup timer
+	 * event is moved further into the future as a result.
+	 *
+	 * The timer will then be reactivated and this function called
+	 * again in the future. We need to exit gracefully in that case
+	 * to allow the input subsystem to do its auto-repeat magic or
+	 * a keyup event might follow immediately after the keydown.
+	 */
+
+	spin_lock_irqsave(&wbcir_lock, flags);
+
+	if (time_is_after_eq_jiffies(data->keyup_jiffies) && data->keypressed) {
+		data->keypressed = 0;
+		led_trigger_event(data->rxtrigger, LED_OFF);
+		input_report_key(data->input_dev, data->last_keycode, 0);
+		input_sync(data->input_dev);
+	}
+
+	spin_unlock_irqrestore(&wbcir_lock, flags);
+}
+
+static void
+wbcir_keydown(struct wbcir_data *data, u32 scancode, u8 toggle)
+{
+	unsigned int keycode;
+
+	/* Repeat? */
+	if (data->last_scancode == scancode &&
+	    data->last_toggle == toggle &&
+	    data->keypressed)
+		goto set_timer;
+	data->last_scancode = scancode;
+
+	/* Do we need to release an old keypress? */
+	if (data->keypressed) {
+		input_report_key(data->input_dev, data->last_keycode, 0);
+		input_sync(data->input_dev);
+		data->keypressed = 0;
+	}
+
+	/* Do we know this scancode? */
+	keycode = wbcir_do_getkeycode(data, scancode);
+	if (keycode == KEY_RESERVED)
+		goto set_timer;
+
+	/* Register a keypress */
+	input_report_key(data->input_dev, keycode, 1);
+	input_sync(data->input_dev);
+	data->keypressed = 1;
+	data->last_keycode = keycode;
+	data->last_toggle = toggle;
+
+set_timer:
+	led_trigger_event(data->rxtrigger,
+			  data->keypressed ? LED_FULL : LED_OFF);
+	data->keyup_jiffies = jiffies + msecs_to_jiffies(IR_KEYPRESS_TIMEOUT);
+	mod_timer(&data->timer_keyup, data->keyup_jiffies);
+}
+
+
+
+/*****************************************************************************
+ *
+ * IR PARSING FUNCTIONS
+ *
+ *****************************************************************************/
+
+/* Resets all irdata */
+static void
+wbcir_reset_irdata(struct wbcir_data *data)
+{
+	memset(data->irdata, 0, sizeof(data->irdata));
+	data->irdata_count = 0;
+	data->irdata_off = 0;
+	data->irdata_error = 0;
+}
+
+/* Adds one bit of irdata */
+static void
+add_irdata_bit(struct wbcir_data *data, int set)
+{
+	if (data->irdata_count >= sizeof(data->irdata) * 8) {
+		data->irdata_error = 1;
+		return;
+	}
+
+	if (set)
+		__set_bit(data->irdata_count, data->irdata);
+	data->irdata_count++;
+}
+
+/* Gets count bits of irdata */
+static u16
+get_bits(struct wbcir_data *data, int count)
+{
+	u16 val = 0x0;
+
+	if (data->irdata_count - data->irdata_off < count) {
+		data->irdata_error = 1;
+		return 0x0;
+	}
+
+	while (count > 0) {
+		val <<= 1;
+		if (test_bit(data->irdata_off, data->irdata))
+			val |= 0x1;
+		count--;
+		data->irdata_off++;
+	}
+
+	return val;
+}
+
+/* Reads 16 cells and converts them to a byte */
+static u8
+wbcir_rc6cells_to_byte(struct wbcir_data *data)
+{
+	u16 raw = get_bits(data, 16);
+	u8 val = 0x00;
+	int bit;
+
+	for (bit = 0; bit < 8; bit++) {
+		switch (raw & 0x03) {
+		case 0x01:
+			break;
+		case 0x02:
+			val |= (0x01 << bit);
+			break;
+		default:
+			data->irdata_error = 1;
+			break;
+		}
+		raw >>= 2;
+	}
+
+	return val;
+}
+
+/* Decodes a number of bits from raw RC5 data */
+static u8
+wbcir_get_rc5bits(struct wbcir_data *data, unsigned int count)
+{
+	u16 raw = get_bits(data, count * 2);
+	u8 val = 0x00;
+	int bit;
+
+	for (bit = 0; bit < count; bit++) {
+		switch (raw & 0x03) {
+		case 0x01:
+			val |= (0x01 << bit);
+			break;
+		case 0x02:
+			break;
+		default:
+			data->irdata_error = 1;
+			break;
+		}
+		raw >>= 2;
+	}
+
+	return val;
+}
+
+static void
+wbcir_parse_rc6(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Normal bits are manchester coded as follows:
+	 * cell0 + cell1 = logic "0"
+	 * cell1 + cell0 = logic "1"
+	 *
+	 * The IR pulse has the following components:
+	 *
+	 * Leader		- 6 * cell1 - discarded
+	 * Gap    		- 2 * cell0 - discarded
+	 * Start bit		- Normal Coding - always "1"
+	 * Mode Bit 2 - 0	- Normal Coding
+	 * Toggle bit		- Normal Coding with double bit time,
+	 *			  e.g. cell0 + cell0 + cell1 + cell1
+	 *			  means logic "0".
+	 *
+	 * The rest depends on the mode, the following modes are known:
+	 *
+	 * MODE 0:
+	 *  Address Bit 7 - 0	- Normal Coding
+	 *  Command Bit 7 - 0	- Normal Coding
+	 *
+	 * MODE 6:
+	 *  The above Toggle Bit is used as a submode bit, 0 = A, 1 = B.
+	 *  Submode B is for pointing devices, only remotes using submode A
+	 *  are supported.
+	 *
+	 *  Customer range bit	- 0 => Customer = 7 bits, 0...127
+	 *                        1 => Customer = 15 bits, 32768...65535
+	 *  Customer Bits	- Normal Coding
+	 *
+	 *  Customer codes are allocated by Philips. The rest of the bits
+	 *  are customer dependent. The following is commonly used (and the
+	 *  only supported config):
+	 *
+	 *  Toggle Bit		- Normal Coding
+	 *  Address Bit 6 - 0	- Normal Coding
+	 *  Command Bit 7 - 0	- Normal Coding
+	 *
+	 * All modes are followed by at least 6 * cell0.
+	 *
+	 * MODE 0 msglen:
+	 *  1 * 2 (start bit) + 3 * 2 (mode) + 2 * 2 (toggle) +
+	 *  8 * 2 (address) + 8 * 2 (command) =
+	 *  44 cells
+	 *
+	 * MODE 6A msglen:
+	 *  1 * 2 (start bit) + 3 * 2 (mode) + 2 * 2 (submode) +
+	 *  1 * 2 (customer range bit) + 7/15 * 2 (customer bits) +
+	 *  1 * 2 (toggle bit) + 7 * 2 (address) + 8 * 2 (command) =
+	 *  60 - 76 cells
+	 */
+	u8 mode;
+	u8 toggle;
+	u16 customer = 0x0;
+	u8 address;
+	u8 command;
+	u32 scancode;
+
+	/* Leader mark */
+	while (get_bits(data, 1) && !data->irdata_error)
+		/* Do nothing */;
+
+	/* Leader space */
+	if (get_bits(data, 1)) {
+		dev_dbg(dev, "RC6 - Invalid leader space\n");
+		return;
+	}
+
+	/* Start bit */
+	if (get_bits(data, 2) != 0x02) {
+		dev_dbg(dev, "RC6 - Invalid start bit\n");
+		return;
+	}
+
+	/* Mode */
+	mode = get_bits(data, 6);
+	switch (mode) {
+	case 0x15: /* 010101 = b000 */
+		mode = 0;
+		break;
+	case 0x29: /* 101001 = b110 */
+		mode = 6;
+		break;
+	default:
+		dev_dbg(dev, "RC6 - Invalid mode\n");
+		return;
+	}
+
+	/* Toggle bit / Submode bit */
+	toggle = get_bits(data, 4);
+	switch (toggle) {
+	case 0x03:
+		toggle = 0;
+		break;
+	case 0x0C:
+		toggle = 1;
+		break;
+	default:
+		dev_dbg(dev, "RC6 - Toggle bit error\n");
+		break;
+	}
+
+	/* Customer */
+	if (mode == 6) {
+		if (toggle != 0) {
+			dev_dbg(dev, "RC6B - Not Supported\n");
+			return;
+		}
+
+		customer = wbcir_rc6cells_to_byte(data);
+
+		if (customer & 0x80) {
+			/* 15 bit customer value */
+			customer <<= 8;
+			customer |= wbcir_rc6cells_to_byte(data);
+		}
+	}
+
+	/* Address */
+	address = wbcir_rc6cells_to_byte(data);
+	if (mode == 6) {
+		toggle = address >> 7;
+		address &= 0x7F;
+	}
+
+	/* Command */
+	command = wbcir_rc6cells_to_byte(data);
+
+	/* Create scancode */
+	scancode =  command;
+	scancode |= address << 8;
+	scancode |= customer << 16;
+
+	/* Last sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "RC6 - Cell error(s)\n");
+		return;
+	}
+
+	dev_dbg(dev, "IR-RC6 ad 0x%02X cm 0x%02X cu 0x%04X "
+		"toggle %u mode %u scan 0x%08X\n",
+		address,
+		command,
+		customer,
+		(unsigned int)toggle,
+		(unsigned int)mode,
+		scancode);
+
+	wbcir_keydown(data, scancode, toggle);
+}
+
+static void
+wbcir_parse_rc5(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Bits are manchester coded as follows:
+	 * cell1 + cell0 = logic "0"
+	 * cell0 + cell1 = logic "1"
+	 * (i.e. the reverse of RC6)
+	 *
+	 * Start bit 1		- "1" - discarded
+	 * Start bit 2		- Must be inverted to get command bit 6
+	 * Toggle bit
+	 * Address Bit 4 - 0
+	 * Command Bit 5 - 0
+	 */
+	u8 toggle;
+	u8 address;
+	u8 command;
+	u32 scancode;
+
+	/* Start bit 1 */
+	if (!get_bits(data, 1)) {
+		dev_dbg(dev, "RC5 - Invalid start bit\n");
+		return;
+	}
+
+	/* Start bit 2 */
+	if (!wbcir_get_rc5bits(data, 1))
+		command = 0x40;
+	else
+		command = 0x00;
+
+	toggle   = wbcir_get_rc5bits(data, 1);
+	address  = wbcir_get_rc5bits(data, 5);
+	command |= wbcir_get_rc5bits(data, 6);
+	scancode = address << 7 | command;
+
+	/* Last sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "RC5 - Invalid message\n");
+		return;
+	}
+
+	dev_dbg(dev, "IR-RC5 ad %u cm %u t %u s %u\n",
+		(unsigned int)address,
+		(unsigned int)command,
+		(unsigned int)toggle,
+		(unsigned int)scancode);
+
+	wbcir_keydown(data, scancode, toggle);
+}
+
+static void
+wbcir_parse_nec(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Each bit represents 560 us.
+	 *
+	 * Leader		- 9 ms burst
+	 * Gap			- 4.5 ms silence
+	 * Address1 bit 0 - 7	- Address 1
+	 * Address2 bit 0 - 7	- Address 2
+	 * Command1 bit 0 - 7	- Command 1
+	 * Command2 bit 0 - 7	- Command 2
+	 *
+	 * Note the bit order!
+	 *
+	 * With the old NEC protocol, Address2 was the inverse of Address1
+	 * and Command2 was the inverse of Command1 and were used as
+	 * an error check.
+	 *
+	 * With NEC extended, Address1 is the LSB of the Address and
+	 * Address2 is the MSB, Command parsing remains unchanged.
+	 *
+	 * A repeat message is coded as:
+	 * Leader		- 9 ms burst
+	 * Gap			- 2.25 ms silence
+	 * Repeat		- 560 us active
+	 */
+	u8 address1;
+	u8 address2;
+	u8 command1;
+	u8 command2;
+	u16 address;
+	u32 scancode;
+
+	/* Leader mark */
+	while (get_bits(data, 1) && !data->irdata_error)
+		/* Do nothing */;
+
+	/* Leader space */
+	if (get_bits(data, 4)) {
+		dev_dbg(dev, "NEC - Invalid leader space\n");
+		return;
+	}
+
+	/* Repeat? */
+	if (get_bits(data, 1)) {
+		if (!data->keypressed) {
+			dev_dbg(dev, "NEC - Stray repeat message\n");
+			return;
+		}
+
+		dev_dbg(dev, "IR-NEC repeat s %u\n",
+			(unsigned int)data->last_scancode);
+
+		wbcir_keydown(data, data->last_scancode, data->last_toggle);
+		return;
+	}
+
+	/* Remaining leader space */
+	if (get_bits(data, 3)) {
+		dev_dbg(dev, "NEC - Invalid leader space\n");
+		return;
+	}
+
+	address1  = bitrev8(get_bits(data, 8));
+	address2  = bitrev8(get_bits(data, 8));
+	command1  = bitrev8(get_bits(data, 8));
+	command2  = bitrev8(get_bits(data, 8));
+
+	/* Sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "NEC - Invalid message\n");
+		return;
+	}
+
+	/* Check command validity */
+	if (command1 != ~command2) {
+		dev_dbg(dev, "NEC - Command bytes mismatch\n");
+		return;
+	}
+
+	/* Check for extended NEC protocol */
+	address = address1;
+	if (address1 != ~address2)
+		address |= address2 << 8;
+
+	scancode = address << 8 | command1;
+
+	dev_dbg(dev, "IR-NEC ad %u cm %u s %u\n",
+		(unsigned int)address,
+		(unsigned int)command1,
+		(unsigned int)scancode);
+
+	wbcir_keydown(data, scancode, !data->last_toggle);
+}
+
+
+
+/*****************************************************************************
+ *
+ * INTERRUPT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static irqreturn_t
+wbcir_irq_handler(int irqno, void *cookie)
+{
+	struct pnp_dev *device = cookie;
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	struct device *dev = &device->dev;
+	u8 status;
+	unsigned long flags;
+	u8 irdata[16];
+	int i;
+	unsigned int hw;
+
+	spin_lock_irqsave(&wbcir_lock, flags);
+
+	wbcir_select_bank(data, WBCIR_BANK_0);
+
+	status = inb(data->sbase + WBCIR_REG_SP3_EIR);
+
+	if (!(status & (WBCIR_IRQ_RX | WBCIR_IRQ_ERR))) {
+		spin_unlock_irqrestore(&wbcir_lock, flags);
+		return IRQ_NONE;
+	}
+
+	if (status & WBCIR_IRQ_ERR)
+		data->irdata_error = 1;
+
+	if (!(status & WBCIR_IRQ_RX))
+		goto out;
+
+	/* Since RXHDLEV is set, at least 16 bytes are in the FIFO */
+	insb(data->sbase + WBCIR_REG_SP3_RXDATA, &irdata[0], 8);
+	insb(data->sbase + WBCIR_REG_SP3_RXDATA, &irdata[8], 8);
+
+	for (i = 0; i < sizeof(data); i++) {
+		hw = hweight8(irdata[i]);
+		if (hw > 4)
+			add_irdata_bit(data, 0);
+		else
+			add_irdata_bit(data, 1);
+
+		if (hw == 8)
+			data->idle_count++;
+		else
+			data->idle_count = 0;
+	}
+
+	if (data->idle_count > WBCIR_MAX_IDLE_BYTES) {
+		/* Drain the FIFO */
+		while (inb(data->sbase + WBCIR_REG_SP3_LSR) & WBCIR_RX_AVAIL)
+			inb(data->sbase + WBCIR_REG_SP3_RXDATA);
+
+		/* And set RXINACTIVE */
+		outb(WBCIR_RX_DISABLE, data->sbase + WBCIR_REG_SP3_ASCR);
+
+		dev_dbg(dev, "IRDATA:\n");
+		for (i = 0; i < data->irdata_count; i += BITS_PER_LONG)
+			dev_dbg(dev, "0x%08lX\n", data->irdata[i/BITS_PER_LONG]);
+
+		switch (protocol) {
+		case IR_PROTOCOL_RC5:
+			wbcir_parse_rc5(dev, data);
+			break;
+		case IR_PROTOCOL_RC6:
+			wbcir_parse_rc6(dev, data);
+			break;
+		case IR_PROTOCOL_NEC:
+			wbcir_parse_nec(dev, data);
+			break;
+		}
+
+		wbcir_reset_irdata(data);
+		data->idle_count = 0;
+	}
+
+out:
+	spin_unlock_irqrestore(&wbcir_lock, flags);
+	return IRQ_HANDLED;
+}
+
+
+
+/*****************************************************************************
+ *
+ * SUSPEND/RESUME FUNCTIONS
+ *
+ *****************************************************************************/
+
+static void
+wbcir_shutdown(struct pnp_dev *device)
+{
+	struct device *dev = &device->dev;
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	int do_wake = 1;
+	u8 match[11];
+	u8 mask[11];
+	u8 rc6_csl = 0;
+	int i;
+
+	memset(match, 0, sizeof(match));
+	memset(mask, 0, sizeof(mask));
+
+	if (wake_sc == INVALID_SCANCODE || !device_may_wakeup(dev)) {
+		do_wake = 0;
+		goto finish;
+	}
+
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+		if (wake_sc > 0xFFF) {
+			do_wake = 0;
+			dev_err(dev, "RC5 - Invalid wake scancode\n");
+			break;
+		}
+
+		/* Mask = 13 bits, ex toggle */
+		mask[0] = 0xFF;
+		mask[1] = 0x17;
+
+		match[0]  = (wake_sc & 0x003F);      /* 6 command bits */
+		match[0] |= (wake_sc & 0x0180) >> 1; /* 2 address bits */
+		match[1]  = (wake_sc & 0x0E00) >> 9; /* 3 address bits */
+		if (!(wake_sc & 0x0040))             /* 2nd start bit  */
+			match[1] |= 0x10;
+
+		break;
+
+	case IR_PROTOCOL_NEC:
+		if (wake_sc > 0xFFFFFF) {
+			do_wake = 0;
+			dev_err(dev, "NEC - Invalid wake scancode\n");
+			break;
+		}
+
+		mask[0] = mask[1] = mask[2] = mask[3] = 0xFF;
+
+		match[1] = bitrev8((wake_sc & 0xFF));
+		match[0] = ~match[1];
+
+		match[3] = bitrev8((wake_sc & 0xFF00) >> 8);
+		if (wake_sc > 0xFFFF)
+			match[2] = bitrev8((wake_sc & 0xFF0000) >> 16);
+		else
+			match[2] = ~match[3];
+
+		break;
+
+	case IR_PROTOCOL_RC6:
+
+		if (wake_rc6mode == 0) {
+			if (wake_sc > 0xFFFF) {
+				do_wake = 0;
+				dev_err(dev, "RC6 - Invalid wake scancode\n");
+				break;
+			}
+
+			/* Command */
+			match[0] = wbcir_to_rc6cells(wake_sc >>  0);
+			mask[0]  = 0xFF;
+			match[1] = wbcir_to_rc6cells(wake_sc >>  4);
+			mask[1]  = 0xFF;
+
+			/* Address */
+			match[2] = wbcir_to_rc6cells(wake_sc >>  8);
+			mask[2]  = 0xFF;
+			match[3] = wbcir_to_rc6cells(wake_sc >> 12);
+			mask[3]  = 0xFF;
+
+			/* Header */
+			match[4] = 0x50; /* mode1 = mode0 = 0, ignore toggle */
+			mask[4]  = 0xF0;
+			match[5] = 0x09; /* start bit = 1, mode2 = 0 */
+			mask[5]  = 0x0F;
+
+			rc6_csl = 44;
+
+		} else if (wake_rc6mode == 6) {
+			i = 0;
+
+			/* Command */
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  0);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  4);
+			mask[i++] = 0xFF;
+
+			/* Address + Toggle */
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  8);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 12);
+			mask[i++] = 0x3F;
+
+			/* Customer bits 7 - 0 */
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 16);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 20);
+			mask[i++] = 0xFF;
+
+			if (wake_sc & 0x80000000) {
+				/* Customer range bit and bits 15 - 8 */
+				match[i]  = wbcir_to_rc6cells(wake_sc >> 24);
+				mask[i++] = 0xFF;
+				match[i]  = wbcir_to_rc6cells(wake_sc >> 28);
+				mask[i++] = 0xFF;
+				rc6_csl = 76;
+			} else if (wake_sc <= 0x007FFFFF) {
+				rc6_csl = 60;
+			} else {
+				do_wake = 0;
+				dev_err(dev, "RC6 - Invalid wake scancode\n");
+				break;
+			}
+
+			/* Header */
+			match[i]  = 0x93; /* mode1 = mode0 = 1, submode = 0 */
+			mask[i++] = 0xFF;
+			match[i]  = 0x0A; /* start bit = 1, mode2 = 1 */
+			mask[i++] = 0x0F;
+
+		} else {
+			do_wake = 0;
+			dev_err(dev, "RC6 - Invalid wake mode\n");
+		}
+
+		break;
+
+	default:
+		do_wake = 0;
+		break;
+	}
+
+finish:
+	if (do_wake) {
+		/* Set compare and compare mask */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX,
+			       WBCIR_REGSEL_COMPARE | WBCIR_REG_ADDR0,
+			       0x3F);
+		outsb(data->wbase + WBCIR_REG_WCEIR_DATA, match, 11);
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX,
+			       WBCIR_REGSEL_MASK | WBCIR_REG_ADDR0,
+			       0x3F);
+		outsb(data->wbase + WBCIR_REG_WCEIR_DATA, mask, 11);
+
+		/* RC6 Compare String Len */
+		outb(rc6_csl, data->wbase + WBCIR_REG_WCEIR_CSL);
+
+		/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+		/* Clear BUFF_EN, Clear END_EN, Set MATCH_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x01, 0x07);
+
+		/* Set CEIR_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x01, 0x01);
+
+	} else {
+		/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+		/* Clear CEIR_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+	}
+
+	/* Disable interrupts */
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+}
+
+static int
+wbcir_suspend(struct pnp_dev *device, pm_message_t state)
+{
+	wbcir_shutdown(device);
+	return 0;
+}
+
+static int
+wbcir_resume(struct pnp_dev *device)
+{
+	struct wbcir_data *data = pnp_get_drvdata(device);
+
+	/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	/* Clear CEIR_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+
+	/* Enable interrupts */
+	wbcir_reset_irdata(data);
+	outb(WBCIR_IRQ_RX | WBCIR_IRQ_ERR, data->sbase + WBCIR_REG_SP3_IER);
+
+	return 0;
+}
+
+
+
+/*****************************************************************************
+ *
+ * SETUP/INIT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static ssize_t
+wbcir_show_last_scancode(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	struct pnp_dev *device = container_of(dev, struct pnp_dev, dev);
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	return sprintf(buf, "0x%08X\n", data->last_scancode);
+}
+
+static struct device_attribute dev_attr_last_scancode = {
+	.attr = {
+		.name = "last_scancode",
+		.mode = 0444,
+	},
+	.show = wbcir_show_last_scancode,
+	.store = NULL,
+
+};
+
+static struct attribute *wbcir_attributes[] = {
+	&dev_attr_last_scancode.attr,
+	NULL,
+};
+
+static struct attribute_group wbcir_attribute_group = {
+	.attrs = wbcir_attributes,
+};
+
+static void
+wbcir_cfg_ceir(struct wbcir_data *data)
+{
+	u8 tmp;
+
+	/* Set PROT_SEL, RX_INV, Clear CEIR_EN (needed for the led) */
+	tmp = protocol << 4;
+	if (invert)
+		tmp |= 0x08;
+	outb(tmp, data->wbase + WBCIR_REG_WCEIR_CTL);
+
+	/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+	/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	/* Set RC5 cell time to correspond to 36 kHz */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CFG1, 0x4A, 0x7F);
+
+	/* Set IRTX_INV */
+	if (invert)
+		outb(0x04, data->ebase + WBCIR_REG_ECEIR_CCTL);
+	else
+		outb(0x00, data->ebase + WBCIR_REG_ECEIR_CCTL);
+
+	/*
+	 * Clear IR LED, set SP3 clock to 24Mhz
+	 * set SP3_IRRX_SW to binary 01, helpfully not documented
+	 */
+	outb(0x10, data->ebase + WBCIR_REG_ECEIR_CTS);
+}
+
+static int
+wbcir_probe(struct pnp_dev *device, const struct pnp_device_id *dev_id)
+{
+	struct device *dev = &device->dev;
+	struct wbcir_data *data;
+	int err;
+
+	if (!(pnp_port_len(device, 0) == EHFUNC_IOMEM_LEN &&
+	      pnp_port_len(device, 1) == WAKEUP_IOMEM_LEN &&
+	      pnp_port_len(device, 2) == SP_IOMEM_LEN)) {
+		dev_err(dev, "Invalid resources\n");
+		return -ENODEV;
+	}
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data) {
+		err = -ENOMEM;
+		goto exit;
+	}
+
+	pnp_set_drvdata(device, data);
+
+	data->ebase = pnp_port_start(device, 0);
+	data->wbase = pnp_port_start(device, 1);
+	data->sbase = pnp_port_start(device, 2);
+	data->irq = pnp_irq(device, 0);
+
+	if (data->wbase == 0 || data->ebase == 0 ||
+	    data->sbase == 0 || data->irq == 0) {
+		err = -ENODEV;
+		dev_err(dev, "Invalid resources\n");
+		goto exit_free_data;
+	}
+
+	dev_info(&device->dev, "Found device "
+		 "(w: 0x%lX, e: 0x%lX, s: 0x%lX, i: %u)\n",
+		 data->wbase, data->ebase, data->sbase, data->irq);
+
+	if (!request_region(data->wbase, WAKEUP_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->wbase, data->wbase + WAKEUP_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_free_data;
+	}
+
+	if (!request_region(data->ebase, EHFUNC_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->ebase, data->ebase + EHFUNC_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_release_wbase;
+	}
+
+	if (!request_region(data->sbase, SP_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->sbase, data->sbase + SP_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_release_ebase;
+	}
+
+	err = request_irq(data->irq, wbcir_irq_handler,
+			  IRQF_DISABLED, DRVNAME, device);
+	if (err) {
+		dev_err(dev, "Failed to claim IRQ %u\n", data->irq);
+		err = -EBUSY;
+		goto exit_release_sbase;
+	}
+
+	led_trigger_register_simple("cir-tx", &data->txtrigger);
+	if (!data->txtrigger) {
+		err = -ENOMEM;
+		goto exit_free_irq;
+	}
+
+	led_trigger_register_simple("cir-rx", &data->rxtrigger);
+	if (!data->rxtrigger) {
+		err = -ENOMEM;
+		goto exit_unregister_txtrigger;
+	}
+
+	data->led.name = "cir::activity";
+	data->led.default_trigger = "cir-rx";
+	data->led.brightness_set = wbcir_led_brightness_set;
+	data->led.brightness_get = wbcir_led_brightness_get;
+	err = led_classdev_register(&device->dev, &data->led);
+	if (err)
+		goto exit_unregister_rxtrigger;
+
+	data->input_dev = input_allocate_device();
+	if (!data->input_dev) {
+		err = -ENOMEM;
+		goto exit_unregister_led;
+	}
+
+	data->input_dev->evbit[0] = BIT(EV_KEY);
+	data->input_dev->name = WBCIR_NAME;
+	data->input_dev->phys = "wbcir/cir0";
+	data->input_dev->id.bustype = BUS_HOST;
+	data->input_dev->id.vendor  = PCI_VENDOR_ID_WINBOND;
+	data->input_dev->id.product = WBCIR_ID_FAMILY;
+	data->input_dev->id.version = WBCIR_ID_CHIP;
+	data->input_dev->getkeycode = wbcir_getkeycode;
+	data->input_dev->setkeycode = wbcir_setkeycode;
+	input_set_drvdata(data->input_dev, data);
+
+	err = input_register_device(data->input_dev);
+	if (err)
+		goto exit_free_input;
+
+	data->last_scancode = INVALID_SCANCODE;
+	err = sysfs_create_group(&device->dev.kobj, &wbcir_attribute_group);
+	if (err)
+		goto exit_unregister_input;
+
+	INIT_LIST_HEAD(&data->keytable);
+	setup_timer(&data->timer_keyup, wbcir_keyup, (unsigned long)data);
+
+	/* Load default keymaps */
+	if (protocol == IR_PROTOCOL_RC6) {
+		int i;
+		for (i = 0; i < ARRAY_SIZE(rc6_def_keymap); i++) {
+			err = wbcir_setkeycode(data->input_dev,
+					       (int)rc6_def_keymap[i].scancode,
+					       (int)rc6_def_keymap[i].keycode);
+			if (err)
+				goto exit_unregister_keys;
+		}
+	}
+
+	device_init_wakeup(&device->dev, 1);
+
+	wbcir_cfg_ceir(data);
+
+	/* Disable interrupts */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+
+	/* Enable extended mode */
+	wbcir_select_bank(data, WBCIR_BANK_2);
+	outb(WBCIR_EXT_ENABLE, data->sbase + WBCIR_REG_SP3_EXCR1);
+
+	/*
+	 * Configure baud generator, IR data will be sampled at
+	 * a bitrate of: (24Mhz * prescaler) / (divisor * 16).
+	 *
+	 * The ECIR registers include a flag to change the
+	 * 24Mhz clock freq to 48Mhz.
+	 */
+
+	/* prescaler 1.0, tx/rx fifo lvl 32 */
+	outb(0x35, data->sbase + WBCIR_REG_SP3_EXCR2);
+
+	/* Set baud divisor to generate one byte per bit/cell */
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+		outb(0xA7, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	case IR_PROTOCOL_RC6:
+		outb(0x53, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	case IR_PROTOCOL_NEC:
+		outb(0x69, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	}
+	outb(0x00, data->sbase + WBCIR_REG_SP3_BGDH);
+
+	/* Set CEIR mode */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(0xC0, data->sbase + WBCIR_REG_SP3_MCR);
+	inb(data->sbase + WBCIR_REG_SP3_LSR); /* Clear LSR */
+	inb(data->sbase + WBCIR_REG_SP3_MSR); /* Clear MSR */
+
+	/* Disable RX demod, run-length encoding/decoding, set freq span */
+	wbcir_select_bank(data, WBCIR_BANK_7);
+	outb(0x10, data->sbase + WBCIR_REG_SP3_RCCFG);
+
+	/* Disable timer */
+	wbcir_select_bank(data, WBCIR_BANK_4);
+	outb(0x00, data->sbase + WBCIR_REG_SP3_IRCR1);
+
+	/* Enable MSR interrupt, Clear AUX_IRX */
+	wbcir_select_bank(data, WBCIR_BANK_5);
+	outb(0x00, data->sbase + WBCIR_REG_SP3_IRCR2);
+
+	/* Disable CRC */
+	wbcir_select_bank(data, WBCIR_BANK_6);
+	outb(0x20, data->sbase + WBCIR_REG_SP3_IRCR3);
+
+	/* Set RX/TX (de)modulation freq, not really used */
+	wbcir_select_bank(data, WBCIR_BANK_7);
+	outb(0xF2, data->sbase + WBCIR_REG_SP3_IRRXDC);
+	outb(0x69, data->sbase + WBCIR_REG_SP3_IRTXMC);
+
+	/* Set invert and pin direction */
+	if (invert)
+		outb(0x10, data->sbase + WBCIR_REG_SP3_IRCFG4);
+	else
+		outb(0x00, data->sbase + WBCIR_REG_SP3_IRCFG4);
+
+	/* Set FIFO thresholds (RX = 16, TX = 7), reset RX/TX */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(0x97, data->sbase + WBCIR_REG_SP3_FCR);
+
+	/* Clear AUX status bits */
+	outb(0xE0, data->sbase + WBCIR_REG_SP3_ASCR);
+
+	/* Enable interrupts */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(WBCIR_IRQ_RX | WBCIR_IRQ_ERR, data->sbase + WBCIR_REG_SP3_IER);
+
+	return 0;
+
+exit_unregister_keys:
+	if (!list_empty(&data->keytable)) {
+		struct wbcir_keyentry *key;
+		struct wbcir_keyentry *keytmp;
+
+		list_for_each_entry_safe(key, keytmp, &data->keytable, list) {
+			list_del(&key->list);
+			kfree(key);
+		}
+	}
+exit_unregister_input:
+	input_unregister_device(data->input_dev);
+	/* Can't call input_free_device on an unregistered device */
+	data->input_dev = NULL;
+exit_free_input:
+	input_free_device(data->input_dev);
+exit_unregister_led:
+	led_classdev_unregister(&data->led);
+exit_unregister_rxtrigger:
+	led_trigger_unregister_simple(data->rxtrigger);
+exit_unregister_txtrigger:
+	led_trigger_unregister_simple(data->txtrigger);
+exit_free_irq:
+	free_irq(data->irq, device);
+exit_release_sbase:
+	release_region(data->sbase, SP_IOMEM_LEN);
+exit_release_ebase:
+	release_region(data->ebase, EHFUNC_IOMEM_LEN);
+exit_release_wbase:
+	release_region(data->wbase, WAKEUP_IOMEM_LEN);
+exit_free_data:
+	kfree(data);
+	pnp_set_drvdata(device, NULL);
+exit:
+	return err;
+}
+
+static void
+wbcir_remove(struct pnp_dev *device)
+{
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	struct wbcir_keyentry *key;
+	struct wbcir_keyentry *keytmp;
+
+	/* Disable interrupts */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+
+	del_timer_sync(&data->timer_keyup);
+
+	free_irq(data->irq, device);
+
+	/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+	/* Clear CEIR_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+
+	/* Clear BUFF_EN, END_EN, MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	sysfs_remove_group(&device->dev.kobj, &wbcir_attribute_group);
+
+	/* This will generate a keyup event if necessary */
+	input_unregister_device(data->input_dev);
+
+	led_trigger_unregister_simple(data->rxtrigger);
+	led_trigger_unregister_simple(data->txtrigger);
+	led_classdev_unregister(&data->led);
+
+	/* This is ok since &data->led isn't actually used */
+	wbcir_led_brightness_set(&data->led, LED_OFF);
+
+	release_region(data->wbase, WAKEUP_IOMEM_LEN);
+	release_region(data->ebase, EHFUNC_IOMEM_LEN);
+	release_region(data->sbase, SP_IOMEM_LEN);
+
+	list_for_each_entry_safe(key, keytmp, &data->keytable, list) {
+		list_del(&key->list);
+		kfree(key);
+	}
+
+	kfree(data);
+
+	pnp_set_drvdata(device, NULL);
+}
+
+static const struct pnp_device_id wbcir_ids[] = {
+	{ "WEC1022", 0 },
+	{ "", 0 }
+};
+MODULE_DEVICE_TABLE(pnp, wbcir_ids);
+
+static struct pnp_driver wbcir_driver = {
+	.name     = WBCIR_NAME,
+	.id_table = wbcir_ids,
+	.probe    = wbcir_probe,
+	.remove   = wbcir_remove,
+	.suspend  = wbcir_suspend,
+	.resume   = wbcir_resume,
+	.shutdown = wbcir_shutdown
+};
+
+static int __init
+wbcir_init(void)
+{
+	int ret;
+
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+	case IR_PROTOCOL_NEC:
+	case IR_PROTOCOL_RC6:
+		break;
+	default:
+		printk(KERN_ERR DRVNAME ": Invalid protocol argument\n");
+		return -EINVAL;
+	}
+
+	ret = pnp_register_driver(&wbcir_driver);
+	if (ret)
+		printk(KERN_ERR DRVNAME ": Unable to register driver\n");
+
+	return ret;
+}
+
+static void __exit
+wbcir_exit(void)
+{
+	pnp_unregister_driver(&wbcir_driver);
+}
+
+MODULE_AUTHOR("David Härdeman <david@hardeman.nu>");
+MODULE_DESCRIPTION("Winbond SuperI/O Consumer IR Driver");
+MODULE_LICENSE("GPL");
+
+module_init(wbcir_init);
+module_exit(wbcir_exit);
+
+

-- 
David Härdeman

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware
@ 2009-08-11 16:26   ` David Härdeman
  0 siblings, 0 replies; 16+ messages in thread
From: David Härdeman @ 2009-08-11 16:26 UTC (permalink / raw)
  To: linux-kernel; +Cc: jbarnes, akpm, bjorn.helgaas, linux-input

[-- Attachment #1: add-a-driver-for-the-winbond-wpcd376i-cir-functionality --]
[-- Type: TEXT/PLAIN, Size: 48819 bytes --]

Add a driver for the the Consumer IR (CIR) functionality of the Winbond
WPCD376I chipset (found on e.g. Intel DG45FC motherboards).

Changes since the last version: converted from an ACPI to a PNP driver
using a patch provided by Bjorn Helgaas <bjorn.helgaas@hp.com>.

Signed-off-by: David Härdeman <david@hardeman.nu>
Reviewed-by: Jesse Barnes <jbarnes@virtuousgeek.org>

Index: linux-2.6/MAINTAINERS
===================================================================
--- linux-2.6.orig/MAINTAINERS	2009-08-09 11:32:42.000000000 +0200
+++ linux-2.6/MAINTAINERS	2009-08-09 11:44:35.000000000 +0200
@@ -6448,6 +6448,12 @@
 S:	Maintained
 F:	drivers/scsi/wd7000.c
 
+WINBOND CIR DRIVER
+P:	David Härdeman
+M:	david@hardeman.nu
+S:	Maintained
+F:	drivers/input/misc/winbond-cir.c
+
 WIMAX STACK
 P:	Inaky Perez-Gonzalez
 M:	inaky.perez-gonzalez@intel.com
Index: linux-2.6/drivers/input/misc/Kconfig
===================================================================
--- linux-2.6.orig/drivers/input/misc/Kconfig	2009-08-09 11:32:42.000000000 +0200
+++ linux-2.6/drivers/input/misc/Kconfig	2009-08-11 18:20:41.000000000 +0200
@@ -222,6 +222,22 @@
 	  To compile this driver as a module, choose M here: the
 	  module will be called sgi_btns.
 
+config INPUT_WINBOND_CIR
+	tristate "Winbond IR remote control"
+	depends on X86 && PNP
+	select LEDS_CLASS
+	select BITREVERSE
+	help
+	  Say Y here if you want to use the IR remote functionality found
+	  in some Winbond SuperI/O chips. Currently only the WPCD376I
+	  chip is supported (included in some Intel Media series motherboards).
+
+	  IR Receive and wake-on-IR from suspend and power-off is currently
+	  supported.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called winbond_cir.
+
 config HP_SDC_RTC
 	tristate "HP SDC Real Time Clock"
 	depends on (GSC || HP300) && SERIO
Index: linux-2.6/drivers/input/misc/Makefile
===================================================================
--- linux-2.6.orig/drivers/input/misc/Makefile	2009-08-09 11:32:42.000000000 +0200
+++ linux-2.6/drivers/input/misc/Makefile	2009-08-09 11:44:35.000000000 +0200
@@ -19,6 +19,7 @@
 obj-$(CONFIG_INPUT_PCSPKR)		+= pcspkr.o
 obj-$(CONFIG_INPUT_POWERMATE)		+= powermate.o
 obj-$(CONFIG_INPUT_RB532_BUTTON)	+= rb532_button.o
+obj-$(CONFIG_INPUT_WINBOND_CIR)		+= winbond-cir.o
 obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER)	+= rotary_encoder.o
 obj-$(CONFIG_INPUT_SGI_BTNS)		+= sgi_btns.o
 obj-$(CONFIG_INPUT_SPARCSPKR)		+= sparcspkr.o
Index: linux-2.6/drivers/input/misc/winbond-cir.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/drivers/input/misc/winbond-cir.c	2009-08-11 18:19:46.000000000 +0200
@@ -0,0 +1,1644 @@
+/*
+ *  winbond-cir.c - Driver for the Consumer IR functionality of Winbond
+ *                  SuperI/O chips.
+ *
+ *  Currently supports the Winbond WPCD376i chip (PNP id WEC1022), but
+ *  could probably support others (Winbond WEC102X, NatSemi, etc)
+ *  with minor modifications.
+ *
+ *  Original Author: David Härdeman <david@hardeman.nu>
+ *     Copyright (C) 2009 David Härdeman <david@hardeman.nu>
+ *
+ *  Dedicated to Matilda, my newborn daughter, without whose loving attention
+ *  this driver would have been finished in half the time and with a fraction
+ *  of the bugs.
+ *
+ *  Written using:
+ *    o Winbond WPCD376I datasheet helpfully provided by Jesse Barnes at Intel
+ *    o NatSemi PC87338/PC97338 datasheet (for the serial port stuff)
+ *    o DSDT dumps
+ *
+ *  Supported features:
+ *    o RC6
+ *    o Wake-On-CIR functionality
+ *
+ *  To do:
+ *    o Test NEC and RC5
+ *
+ *  Left as an exercise for the reader:
+ *    o Learning (I have neither the hardware, nor the need)
+ *    o IR Transmit (ibid)
+ *
+ *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/pnp.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/pci_ids.h>
+#include <linux/io.h>
+#include <linux/bitrev.h>
+#include <linux/bitops.h>
+
+#define DRVNAME "winbond-cir"
+
+/* CEIR Wake-Up Registers, relative to data->wbase                      */
+#define WBCIR_REG_WCEIR_CTL	0x03 /* CEIR Receiver Control		*/
+#define WBCIR_REG_WCEIR_STS	0x04 /* CEIR Receiver Status		*/
+#define WBCIR_REG_WCEIR_EV_EN	0x05 /* CEIR Receiver Event Enable	*/
+#define WBCIR_REG_WCEIR_CNTL	0x06 /* CEIR Receiver Counter Low	*/
+#define WBCIR_REG_WCEIR_CNTH	0x07 /* CEIR Receiver Counter High	*/
+#define WBCIR_REG_WCEIR_INDEX	0x08 /* CEIR Receiver Index		*/
+#define WBCIR_REG_WCEIR_DATA	0x09 /* CEIR Receiver Data		*/
+#define WBCIR_REG_WCEIR_CSL	0x0A /* CEIR Re. Compare Strlen		*/
+#define WBCIR_REG_WCEIR_CFG1	0x0B /* CEIR Re. Configuration 1	*/
+#define WBCIR_REG_WCEIR_CFG2	0x0C /* CEIR Re. Configuration 2	*/
+
+/* CEIR Enhanced Functionality Registers, relative to data->ebase       */
+#define WBCIR_REG_ECEIR_CTS	0x00 /* Enhanced IR Control Status	*/
+#define WBCIR_REG_ECEIR_CCTL	0x01 /* Infrared Counter Control	*/
+#define WBCIR_REG_ECEIR_CNT_LO	0x02 /* Infrared Counter LSB		*/
+#define WBCIR_REG_ECEIR_CNT_HI	0x03 /* Infrared Counter MSB		*/
+#define WBCIR_REG_ECEIR_IREM	0x04 /* Infrared Emitter Status		*/
+
+/* SP3 Banked Registers, relative to data->sbase                        */
+#define WBCIR_REG_SP3_BSR	0x03 /* Bank Select, all banks		*/
+				      /* Bank 0				*/
+#define WBCIR_REG_SP3_RXDATA	0x00 /* FIFO RX data (r)		*/
+#define WBCIR_REG_SP3_TXDATA	0x00 /* FIFO TX data (w)		*/
+#define WBCIR_REG_SP3_IER	0x01 /* Interrupt Enable		*/
+#define WBCIR_REG_SP3_EIR	0x02 /* Event Identification (r)	*/
+#define WBCIR_REG_SP3_FCR	0x02 /* FIFO Control (w)		*/
+#define WBCIR_REG_SP3_MCR	0x04 /* Mode Control			*/
+#define WBCIR_REG_SP3_LSR	0x05 /* Link Status			*/
+#define WBCIR_REG_SP3_MSR	0x06 /* Modem Status			*/
+#define WBCIR_REG_SP3_ASCR	0x07 /* Aux Status and Control		*/
+				      /* Bank 2				*/
+#define WBCIR_REG_SP3_BGDL	0x00 /* Baud Divisor LSB		*/
+#define WBCIR_REG_SP3_BGDH	0x01 /* Baud Divisor MSB		*/
+#define WBCIR_REG_SP3_EXCR1	0x02 /* Extended Control 1		*/
+#define WBCIR_REG_SP3_EXCR2	0x04 /* Extended Control 2		*/
+#define WBCIR_REG_SP3_TXFLV	0x06 /* TX FIFO Level			*/
+#define WBCIR_REG_SP3_RXFLV	0x07 /* RX FIFO Level			*/
+				      /* Bank 3				*/
+#define WBCIR_REG_SP3_MRID	0x00 /* Module Identification		*/
+#define WBCIR_REG_SP3_SH_LCR	0x01 /* LCR Shadow			*/
+#define WBCIR_REG_SP3_SH_FCR	0x02 /* FCR Shadow			*/
+				      /* Bank 4				*/
+#define WBCIR_REG_SP3_IRCR1	0x02 /* Infrared Control 1		*/
+				      /* Bank 5				*/
+#define WBCIR_REG_SP3_IRCR2	0x04 /* Infrared Control 2		*/
+				      /* Bank 6				*/
+#define WBCIR_REG_SP3_IRCR3	0x00 /* Infrared Control 3		*/
+#define WBCIR_REG_SP3_SIR_PW	0x02 /* SIR Pulse Width		*/
+				      /* Bank 7				*/
+#define WBCIR_REG_SP3_IRRXDC	0x00 /* IR RX Demod Control		*/
+#define WBCIR_REG_SP3_IRTXMC	0x01 /* IR TX Mod Control		*/
+#define WBCIR_REG_SP3_RCCFG	0x02 /* CEIR Config			*/
+#define WBCIR_REG_SP3_IRCFG1	0x04 /* Infrared Config 1		*/
+#define WBCIR_REG_SP3_IRCFG4	0x07 /* Infrared Config 4		*/
+
+/*
+ * Magic values follow
+ */
+
+/* No interrupts for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_NONE		0x00
+/* RX data bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_RX		0x01
+/* Over/Under-flow bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_ERR		0x04
+/* Led enable/disable bit for WBCIR_REG_ECEIR_CTS */
+#define WBCIR_LED_ENABLE	0x80
+/* RX data available bit for WBCIR_REG_SP3_LSR */
+#define WBCIR_RX_AVAIL		0x01
+/* RX disable bit for WBCIR_REG_SP3_ASCR */
+#define WBCIR_RX_DISABLE	0x20
+/* Extended mode enable bit for WBCIR_REG_SP3_EXCR1 */
+#define WBCIR_EXT_ENABLE	0x01
+/* Select compare register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */
+#define WBCIR_REGSEL_COMPARE	0x10
+/* Select mask register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */
+#define WBCIR_REGSEL_MASK	0x20
+/* Starting address of selected register in WBCIR_REG_WCEIR_INDEX */
+#define WBCIR_REG_ADDR0		0x00
+
+/* Valid banks for the SP3 UART */
+enum wbcir_bank {
+	WBCIR_BANK_0          = 0x00,
+	WBCIR_BANK_1          = 0x80,
+	WBCIR_BANK_2          = 0xE0,
+	WBCIR_BANK_3          = 0xE4,
+	WBCIR_BANK_4          = 0xE8,
+	WBCIR_BANK_5          = 0xEC,
+	WBCIR_BANK_6          = 0xF0,
+	WBCIR_BANK_7          = 0xF4,
+};
+
+/* Supported IR Protocols */
+enum wbcir_protocol {
+	IR_PROTOCOL_RC5          = 0x0,
+	IR_PROTOCOL_NEC          = 0x1,
+	IR_PROTOCOL_RC6          = 0x2,
+};
+
+/* Misc */
+#define WBCIR_NAME	"Winbond CIR"
+#define WBCIR_ID_FAMILY          0xF1 /* Family ID for the WPCD376I	*/
+#define	WBCIR_ID_CHIP            0x04 /* Chip ID for the WPCD376I	*/
+#define IR_KEYPRESS_TIMEOUT       250 /* FIXME: should be per-protocol? */
+#define INVALID_SCANCODE   0x7FFFFFFF /* Invalid with all protos	*/
+#define WAKEUP_IOMEM_LEN         0x10 /* Wake-Up I/O Reg Len		*/
+#define EHFUNC_IOMEM_LEN         0x10 /* Enhanced Func I/O Reg Len	*/
+#define SP_IOMEM_LEN             0x08 /* Serial Port 3 (IR) Reg Len	*/
+#define WBCIR_MAX_IDLE_BYTES       10
+
+static DEFINE_SPINLOCK(wbcir_lock);
+static DEFINE_RWLOCK(keytable_lock);
+
+struct wbcir_key {
+	u32 scancode;
+	unsigned int keycode;
+};
+
+struct wbcir_keyentry {
+	struct wbcir_key key;
+	struct list_head list;
+};
+
+static struct wbcir_key rc6_def_keymap[] = {
+	{ 0x800F0400, KEY_0			},
+	{ 0x800F0401, KEY_1			},
+	{ 0x800F0402, KEY_2			},
+	{ 0x800F0403, KEY_3			},
+	{ 0x800F0404, KEY_4			},
+	{ 0x800F0405, KEY_5			},
+	{ 0x800F0406, KEY_6			},
+	{ 0x800F0407, KEY_7			},
+	{ 0x800F0408, KEY_8			},
+	{ 0x800F0409, KEY_9			},
+	{ 0x800F041D, KEY_NUMERIC_STAR		},
+	{ 0x800F041C, KEY_NUMERIC_POUND		},
+	{ 0x800F0410, KEY_VOLUMEUP		},
+	{ 0x800F0411, KEY_VOLUMEDOWN		},
+	{ 0x800F0412, KEY_CHANNELUP		},
+	{ 0x800F0413, KEY_CHANNELDOWN		},
+	{ 0x800F040E, KEY_MUTE			},
+	{ 0x800F040D, KEY_VENDOR		}, /* Vista Logo Key */
+	{ 0x800F041E, KEY_UP			},
+	{ 0x800F041F, KEY_DOWN			},
+	{ 0x800F0420, KEY_LEFT			},
+	{ 0x800F0421, KEY_RIGHT			},
+	{ 0x800F0422, KEY_OK			},
+	{ 0x800F0423, KEY_ESC			},
+	{ 0x800F040F, KEY_INFO			},
+	{ 0x800F040A, KEY_CLEAR			},
+	{ 0x800F040B, KEY_ENTER			},
+	{ 0x800F045B, KEY_RED			},
+	{ 0x800F045C, KEY_GREEN			},
+	{ 0x800F045D, KEY_YELLOW		},
+	{ 0x800F045E, KEY_BLUE			},
+	{ 0x800F045A, KEY_TEXT			},
+	{ 0x800F0427, KEY_SWITCHVIDEOMODE	},
+	{ 0x800F040C, KEY_POWER			},
+	{ 0x800F0450, KEY_RADIO			},
+	{ 0x800F0448, KEY_PVR			},
+	{ 0x800F0447, KEY_AUDIO			},
+	{ 0x800F0426, KEY_EPG			},
+	{ 0x800F0449, KEY_CAMERA		},
+	{ 0x800F0425, KEY_TV			},
+	{ 0x800F044A, KEY_VIDEO			},
+	{ 0x800F0424, KEY_DVD			},
+	{ 0x800F0416, KEY_PLAY			},
+	{ 0x800F0418, KEY_PAUSE			},
+	{ 0x800F0419, KEY_STOP			},
+	{ 0x800F0414, KEY_FASTFORWARD		},
+	{ 0x800F041A, KEY_NEXT			},
+	{ 0x800F041B, KEY_PREVIOUS		},
+	{ 0x800F0415, KEY_REWIND		},
+	{ 0x800F0417, KEY_RECORD		},
+};
+
+/* Registers and other state is protected by wbcir_lock */
+struct wbcir_data {
+	unsigned long wbase;        /* Wake-Up Baseaddr		*/
+	unsigned long ebase;        /* Enhanced Func. Baseaddr	*/
+	unsigned long sbase;        /* Serial Port Baseaddr	*/
+	unsigned int  irq;          /* Serial Port IRQ		*/
+
+	struct input_dev *input_dev;
+	struct timer_list timer_keyup;
+	struct led_trigger *rxtrigger;
+	struct led_trigger *txtrigger;
+	struct led_classdev led;
+
+	u32 last_scancode;
+	unsigned int last_keycode;
+	u8 last_toggle;
+	u8 keypressed;
+	unsigned long keyup_jiffies;
+	unsigned int idle_count;
+
+	/* RX irdata and parsing state */
+	unsigned long irdata[30];
+	unsigned int irdata_count;
+	unsigned int irdata_idle;
+	unsigned int irdata_off;
+	unsigned int irdata_error;
+
+	/* Protected by keytable_lock */
+	struct list_head keytable;
+};
+
+static enum wbcir_protocol protocol = IR_PROTOCOL_RC6;
+module_param(protocol, uint, 0444);
+MODULE_PARM_DESC(protocol, "IR protocol to use "
+		 "(0 = RC5, 1 = NEC, 2 = RC6A, default)");
+
+static int invert; /* default = 0 */
+module_param(invert, bool, 0444);
+MODULE_PARM_DESC(invert, "Invert the signal from the IR receiver");
+
+static unsigned int wake_sc = 0x800F040C;
+module_param(wake_sc, uint, 0644);
+MODULE_PARM_DESC(wake_sc, "Scancode of the power-on IR command");
+
+static unsigned int wake_rc6mode = 6;
+module_param(wake_rc6mode, uint, 0644);
+MODULE_PARM_DESC(wake_rc6mode, "RC6 mode for the power-on command "
+		 "(0 = 0, 6 = 6A, default)");
+
+
+
+/*****************************************************************************
+ *
+ * UTILITY FUNCTIONS
+ *
+ *****************************************************************************/
+
+/* Caller needs to hold wbcir_lock */
+static void
+wbcir_set_bits(unsigned long addr, u8 bits, u8 mask)
+{
+	u8 val;
+
+	val = inb(addr);
+	val = ((val & ~mask) | (bits & mask));
+	outb(val, addr);
+}
+
+/* Selects the register bank for the serial port */
+static inline void
+wbcir_select_bank(struct wbcir_data *data, enum wbcir_bank bank)
+{
+	outb(bank, data->sbase + WBCIR_REG_SP3_BSR);
+}
+
+static enum led_brightness
+wbcir_led_brightness_get(struct led_classdev *led_cdev)
+{
+	struct wbcir_data *data = container_of(led_cdev,
+					       struct wbcir_data,
+					       led);
+
+	if (inb(data->ebase + WBCIR_REG_ECEIR_CTS) & WBCIR_LED_ENABLE)
+		return LED_FULL;
+	else
+		return LED_OFF;
+}
+
+static void
+wbcir_led_brightness_set(struct led_classdev *led_cdev,
+			    enum led_brightness brightness)
+{
+	struct wbcir_data *data = container_of(led_cdev,
+					       struct wbcir_data,
+					       led);
+
+	wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CTS,
+		       brightness == LED_OFF ? 0x00 : WBCIR_LED_ENABLE,
+		       WBCIR_LED_ENABLE);
+}
+
+/* Manchester encodes bits to RC6 message cells (see wbcir_parse_rc6) */
+static u8
+wbcir_to_rc6cells(u8 val)
+{
+	u8 coded = 0x00;
+	int i;
+
+	val &= 0x0F;
+	for (i = 0; i < 4; i++) {
+		if (val & 0x01)
+			coded |= 0x02 << (i * 2);
+		else
+			coded |= 0x01 << (i * 2);
+		val >>= 1;
+	}
+
+	return coded;
+}
+
+
+
+/*****************************************************************************
+ *
+ * INPUT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static unsigned int
+wbcir_do_getkeycode(struct wbcir_data *data, u32 scancode)
+{
+	struct wbcir_keyentry *keyentry;
+	unsigned int keycode = KEY_RESERVED;
+	unsigned long flags;
+
+	read_lock_irqsave(&keytable_lock, flags);
+
+	list_for_each_entry(keyentry, &data->keytable, list) {
+		if (keyentry->key.scancode == scancode) {
+			keycode = keyentry->key.keycode;
+			break;
+		}
+	}
+
+	read_unlock_irqrestore(&keytable_lock, flags);
+	return keycode;
+}
+
+static int
+wbcir_getkeycode(struct input_dev *dev, int scancode, int *keycode)
+{
+	struct wbcir_data *data = input_get_drvdata(dev);
+
+	*keycode = (int)wbcir_do_getkeycode(data, (u32)scancode);
+	return 0;
+}
+
+static int
+wbcir_setkeycode(struct input_dev *dev, int sscancode, int keycode)
+{
+	struct wbcir_data *data = input_get_drvdata(dev);
+	struct wbcir_keyentry *keyentry;
+	struct wbcir_keyentry *new_keyentry;
+	unsigned long flags;
+	unsigned int old_keycode = KEY_RESERVED;
+	u32 scancode = (u32)sscancode;
+
+	if (keycode < 0 || keycode > KEY_MAX)
+		return -EINVAL;
+
+	new_keyentry = kmalloc(sizeof(*new_keyentry), GFP_KERNEL);
+	if (!new_keyentry)
+		return -ENOMEM;
+
+	write_lock_irqsave(&keytable_lock, flags);
+
+	list_for_each_entry(keyentry, &data->keytable, list) {
+		if (keyentry->key.scancode != scancode)
+			continue;
+
+		old_keycode = keyentry->key.keycode;
+		keyentry->key.keycode = keycode;
+
+		if (keyentry->key.keycode == KEY_RESERVED) {
+			list_del(&keyentry->list);
+			kfree(keyentry);
+		}
+
+		break;
+	}
+
+	set_bit(keycode, dev->keybit);
+
+	if (old_keycode == KEY_RESERVED) {
+		new_keyentry->key.scancode = scancode;
+		new_keyentry->key.keycode = keycode;
+		list_add(&new_keyentry->list, &data->keytable);
+	} else {
+		kfree(new_keyentry);
+		clear_bit(old_keycode, dev->keybit);
+		list_for_each_entry(keyentry, &data->keytable, list) {
+			if (keyentry->key.keycode == old_keycode) {
+				set_bit(old_keycode, dev->keybit);
+				break;
+			}
+		}
+	}
+
+	write_unlock_irqrestore(&keytable_lock, flags);
+	return 0;
+}
+
+/*
+ * Timer function to report keyup event some time after keydown is
+ * reported by the ISR.
+ */
+static void
+wbcir_keyup(unsigned long cookie)
+{
+	struct wbcir_data *data = (struct wbcir_data *)cookie;
+	unsigned long flags;
+
+	/*
+	 * data->keyup_jiffies is used to prevent a race condition if a
+	 * hardware interrupt occurs at this point and the keyup timer
+	 * event is moved further into the future as a result.
+	 *
+	 * The timer will then be reactivated and this function called
+	 * again in the future. We need to exit gracefully in that case
+	 * to allow the input subsystem to do its auto-repeat magic or
+	 * a keyup event might follow immediately after the keydown.
+	 */
+
+	spin_lock_irqsave(&wbcir_lock, flags);
+
+	if (time_is_after_eq_jiffies(data->keyup_jiffies) && data->keypressed) {
+		data->keypressed = 0;
+		led_trigger_event(data->rxtrigger, LED_OFF);
+		input_report_key(data->input_dev, data->last_keycode, 0);
+		input_sync(data->input_dev);
+	}
+
+	spin_unlock_irqrestore(&wbcir_lock, flags);
+}
+
+static void
+wbcir_keydown(struct wbcir_data *data, u32 scancode, u8 toggle)
+{
+	unsigned int keycode;
+
+	/* Repeat? */
+	if (data->last_scancode == scancode &&
+	    data->last_toggle == toggle &&
+	    data->keypressed)
+		goto set_timer;
+	data->last_scancode = scancode;
+
+	/* Do we need to release an old keypress? */
+	if (data->keypressed) {
+		input_report_key(data->input_dev, data->last_keycode, 0);
+		input_sync(data->input_dev);
+		data->keypressed = 0;
+	}
+
+	/* Do we know this scancode? */
+	keycode = wbcir_do_getkeycode(data, scancode);
+	if (keycode == KEY_RESERVED)
+		goto set_timer;
+
+	/* Register a keypress */
+	input_report_key(data->input_dev, keycode, 1);
+	input_sync(data->input_dev);
+	data->keypressed = 1;
+	data->last_keycode = keycode;
+	data->last_toggle = toggle;
+
+set_timer:
+	led_trigger_event(data->rxtrigger,
+			  data->keypressed ? LED_FULL : LED_OFF);
+	data->keyup_jiffies = jiffies + msecs_to_jiffies(IR_KEYPRESS_TIMEOUT);
+	mod_timer(&data->timer_keyup, data->keyup_jiffies);
+}
+
+
+
+/*****************************************************************************
+ *
+ * IR PARSING FUNCTIONS
+ *
+ *****************************************************************************/
+
+/* Resets all irdata */
+static void
+wbcir_reset_irdata(struct wbcir_data *data)
+{
+	memset(data->irdata, 0, sizeof(data->irdata));
+	data->irdata_count = 0;
+	data->irdata_off = 0;
+	data->irdata_error = 0;
+}
+
+/* Adds one bit of irdata */
+static void
+add_irdata_bit(struct wbcir_data *data, int set)
+{
+	if (data->irdata_count >= sizeof(data->irdata) * 8) {
+		data->irdata_error = 1;
+		return;
+	}
+
+	if (set)
+		__set_bit(data->irdata_count, data->irdata);
+	data->irdata_count++;
+}
+
+/* Gets count bits of irdata */
+static u16
+get_bits(struct wbcir_data *data, int count)
+{
+	u16 val = 0x0;
+
+	if (data->irdata_count - data->irdata_off < count) {
+		data->irdata_error = 1;
+		return 0x0;
+	}
+
+	while (count > 0) {
+		val <<= 1;
+		if (test_bit(data->irdata_off, data->irdata))
+			val |= 0x1;
+		count--;
+		data->irdata_off++;
+	}
+
+	return val;
+}
+
+/* Reads 16 cells and converts them to a byte */
+static u8
+wbcir_rc6cells_to_byte(struct wbcir_data *data)
+{
+	u16 raw = get_bits(data, 16);
+	u8 val = 0x00;
+	int bit;
+
+	for (bit = 0; bit < 8; bit++) {
+		switch (raw & 0x03) {
+		case 0x01:
+			break;
+		case 0x02:
+			val |= (0x01 << bit);
+			break;
+		default:
+			data->irdata_error = 1;
+			break;
+		}
+		raw >>= 2;
+	}
+
+	return val;
+}
+
+/* Decodes a number of bits from raw RC5 data */
+static u8
+wbcir_get_rc5bits(struct wbcir_data *data, unsigned int count)
+{
+	u16 raw = get_bits(data, count * 2);
+	u8 val = 0x00;
+	int bit;
+
+	for (bit = 0; bit < count; bit++) {
+		switch (raw & 0x03) {
+		case 0x01:
+			val |= (0x01 << bit);
+			break;
+		case 0x02:
+			break;
+		default:
+			data->irdata_error = 1;
+			break;
+		}
+		raw >>= 2;
+	}
+
+	return val;
+}
+
+static void
+wbcir_parse_rc6(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Normal bits are manchester coded as follows:
+	 * cell0 + cell1 = logic "0"
+	 * cell1 + cell0 = logic "1"
+	 *
+	 * The IR pulse has the following components:
+	 *
+	 * Leader		- 6 * cell1 - discarded
+	 * Gap    		- 2 * cell0 - discarded
+	 * Start bit		- Normal Coding - always "1"
+	 * Mode Bit 2 - 0	- Normal Coding
+	 * Toggle bit		- Normal Coding with double bit time,
+	 *			  e.g. cell0 + cell0 + cell1 + cell1
+	 *			  means logic "0".
+	 *
+	 * The rest depends on the mode, the following modes are known:
+	 *
+	 * MODE 0:
+	 *  Address Bit 7 - 0	- Normal Coding
+	 *  Command Bit 7 - 0	- Normal Coding
+	 *
+	 * MODE 6:
+	 *  The above Toggle Bit is used as a submode bit, 0 = A, 1 = B.
+	 *  Submode B is for pointing devices, only remotes using submode A
+	 *  are supported.
+	 *
+	 *  Customer range bit	- 0 => Customer = 7 bits, 0...127
+	 *                        1 => Customer = 15 bits, 32768...65535
+	 *  Customer Bits	- Normal Coding
+	 *
+	 *  Customer codes are allocated by Philips. The rest of the bits
+	 *  are customer dependent. The following is commonly used (and the
+	 *  only supported config):
+	 *
+	 *  Toggle Bit		- Normal Coding
+	 *  Address Bit 6 - 0	- Normal Coding
+	 *  Command Bit 7 - 0	- Normal Coding
+	 *
+	 * All modes are followed by at least 6 * cell0.
+	 *
+	 * MODE 0 msglen:
+	 *  1 * 2 (start bit) + 3 * 2 (mode) + 2 * 2 (toggle) +
+	 *  8 * 2 (address) + 8 * 2 (command) =
+	 *  44 cells
+	 *
+	 * MODE 6A msglen:
+	 *  1 * 2 (start bit) + 3 * 2 (mode) + 2 * 2 (submode) +
+	 *  1 * 2 (customer range bit) + 7/15 * 2 (customer bits) +
+	 *  1 * 2 (toggle bit) + 7 * 2 (address) + 8 * 2 (command) =
+	 *  60 - 76 cells
+	 */
+	u8 mode;
+	u8 toggle;
+	u16 customer = 0x0;
+	u8 address;
+	u8 command;
+	u32 scancode;
+
+	/* Leader mark */
+	while (get_bits(data, 1) && !data->irdata_error)
+		/* Do nothing */;
+
+	/* Leader space */
+	if (get_bits(data, 1)) {
+		dev_dbg(dev, "RC6 - Invalid leader space\n");
+		return;
+	}
+
+	/* Start bit */
+	if (get_bits(data, 2) != 0x02) {
+		dev_dbg(dev, "RC6 - Invalid start bit\n");
+		return;
+	}
+
+	/* Mode */
+	mode = get_bits(data, 6);
+	switch (mode) {
+	case 0x15: /* 010101 = b000 */
+		mode = 0;
+		break;
+	case 0x29: /* 101001 = b110 */
+		mode = 6;
+		break;
+	default:
+		dev_dbg(dev, "RC6 - Invalid mode\n");
+		return;
+	}
+
+	/* Toggle bit / Submode bit */
+	toggle = get_bits(data, 4);
+	switch (toggle) {
+	case 0x03:
+		toggle = 0;
+		break;
+	case 0x0C:
+		toggle = 1;
+		break;
+	default:
+		dev_dbg(dev, "RC6 - Toggle bit error\n");
+		break;
+	}
+
+	/* Customer */
+	if (mode == 6) {
+		if (toggle != 0) {
+			dev_dbg(dev, "RC6B - Not Supported\n");
+			return;
+		}
+
+		customer = wbcir_rc6cells_to_byte(data);
+
+		if (customer & 0x80) {
+			/* 15 bit customer value */
+			customer <<= 8;
+			customer |= wbcir_rc6cells_to_byte(data);
+		}
+	}
+
+	/* Address */
+	address = wbcir_rc6cells_to_byte(data);
+	if (mode == 6) {
+		toggle = address >> 7;
+		address &= 0x7F;
+	}
+
+	/* Command */
+	command = wbcir_rc6cells_to_byte(data);
+
+	/* Create scancode */
+	scancode =  command;
+	scancode |= address << 8;
+	scancode |= customer << 16;
+
+	/* Last sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "RC6 - Cell error(s)\n");
+		return;
+	}
+
+	dev_dbg(dev, "IR-RC6 ad 0x%02X cm 0x%02X cu 0x%04X "
+		"toggle %u mode %u scan 0x%08X\n",
+		address,
+		command,
+		customer,
+		(unsigned int)toggle,
+		(unsigned int)mode,
+		scancode);
+
+	wbcir_keydown(data, scancode, toggle);
+}
+
+static void
+wbcir_parse_rc5(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Bits are manchester coded as follows:
+	 * cell1 + cell0 = logic "0"
+	 * cell0 + cell1 = logic "1"
+	 * (i.e. the reverse of RC6)
+	 *
+	 * Start bit 1		- "1" - discarded
+	 * Start bit 2		- Must be inverted to get command bit 6
+	 * Toggle bit
+	 * Address Bit 4 - 0
+	 * Command Bit 5 - 0
+	 */
+	u8 toggle;
+	u8 address;
+	u8 command;
+	u32 scancode;
+
+	/* Start bit 1 */
+	if (!get_bits(data, 1)) {
+		dev_dbg(dev, "RC5 - Invalid start bit\n");
+		return;
+	}
+
+	/* Start bit 2 */
+	if (!wbcir_get_rc5bits(data, 1))
+		command = 0x40;
+	else
+		command = 0x00;
+
+	toggle   = wbcir_get_rc5bits(data, 1);
+	address  = wbcir_get_rc5bits(data, 5);
+	command |= wbcir_get_rc5bits(data, 6);
+	scancode = address << 7 | command;
+
+	/* Last sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "RC5 - Invalid message\n");
+		return;
+	}
+
+	dev_dbg(dev, "IR-RC5 ad %u cm %u t %u s %u\n",
+		(unsigned int)address,
+		(unsigned int)command,
+		(unsigned int)toggle,
+		(unsigned int)scancode);
+
+	wbcir_keydown(data, scancode, toggle);
+}
+
+static void
+wbcir_parse_nec(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Each bit represents 560 us.
+	 *
+	 * Leader		- 9 ms burst
+	 * Gap			- 4.5 ms silence
+	 * Address1 bit 0 - 7	- Address 1
+	 * Address2 bit 0 - 7	- Address 2
+	 * Command1 bit 0 - 7	- Command 1
+	 * Command2 bit 0 - 7	- Command 2
+	 *
+	 * Note the bit order!
+	 *
+	 * With the old NEC protocol, Address2 was the inverse of Address1
+	 * and Command2 was the inverse of Command1 and were used as
+	 * an error check.
+	 *
+	 * With NEC extended, Address1 is the LSB of the Address and
+	 * Address2 is the MSB, Command parsing remains unchanged.
+	 *
+	 * A repeat message is coded as:
+	 * Leader		- 9 ms burst
+	 * Gap			- 2.25 ms silence
+	 * Repeat		- 560 us active
+	 */
+	u8 address1;
+	u8 address2;
+	u8 command1;
+	u8 command2;
+	u16 address;
+	u32 scancode;
+
+	/* Leader mark */
+	while (get_bits(data, 1) && !data->irdata_error)
+		/* Do nothing */;
+
+	/* Leader space */
+	if (get_bits(data, 4)) {
+		dev_dbg(dev, "NEC - Invalid leader space\n");
+		return;
+	}
+
+	/* Repeat? */
+	if (get_bits(data, 1)) {
+		if (!data->keypressed) {
+			dev_dbg(dev, "NEC - Stray repeat message\n");
+			return;
+		}
+
+		dev_dbg(dev, "IR-NEC repeat s %u\n",
+			(unsigned int)data->last_scancode);
+
+		wbcir_keydown(data, data->last_scancode, data->last_toggle);
+		return;
+	}
+
+	/* Remaining leader space */
+	if (get_bits(data, 3)) {
+		dev_dbg(dev, "NEC - Invalid leader space\n");
+		return;
+	}
+
+	address1  = bitrev8(get_bits(data, 8));
+	address2  = bitrev8(get_bits(data, 8));
+	command1  = bitrev8(get_bits(data, 8));
+	command2  = bitrev8(get_bits(data, 8));
+
+	/* Sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "NEC - Invalid message\n");
+		return;
+	}
+
+	/* Check command validity */
+	if (command1 != ~command2) {
+		dev_dbg(dev, "NEC - Command bytes mismatch\n");
+		return;
+	}
+
+	/* Check for extended NEC protocol */
+	address = address1;
+	if (address1 != ~address2)
+		address |= address2 << 8;
+
+	scancode = address << 8 | command1;
+
+	dev_dbg(dev, "IR-NEC ad %u cm %u s %u\n",
+		(unsigned int)address,
+		(unsigned int)command1,
+		(unsigned int)scancode);
+
+	wbcir_keydown(data, scancode, !data->last_toggle);
+}
+
+
+
+/*****************************************************************************
+ *
+ * INTERRUPT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static irqreturn_t
+wbcir_irq_handler(int irqno, void *cookie)
+{
+	struct pnp_dev *device = cookie;
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	struct device *dev = &device->dev;
+	u8 status;
+	unsigned long flags;
+	u8 irdata[16];
+	int i;
+	unsigned int hw;
+
+	spin_lock_irqsave(&wbcir_lock, flags);
+
+	wbcir_select_bank(data, WBCIR_BANK_0);
+
+	status = inb(data->sbase + WBCIR_REG_SP3_EIR);
+
+	if (!(status & (WBCIR_IRQ_RX | WBCIR_IRQ_ERR))) {
+		spin_unlock_irqrestore(&wbcir_lock, flags);
+		return IRQ_NONE;
+	}
+
+	if (status & WBCIR_IRQ_ERR)
+		data->irdata_error = 1;
+
+	if (!(status & WBCIR_IRQ_RX))
+		goto out;
+
+	/* Since RXHDLEV is set, at least 16 bytes are in the FIFO */
+	insb(data->sbase + WBCIR_REG_SP3_RXDATA, &irdata[0], 8);
+	insb(data->sbase + WBCIR_REG_SP3_RXDATA, &irdata[8], 8);
+
+	for (i = 0; i < sizeof(data); i++) {
+		hw = hweight8(irdata[i]);
+		if (hw > 4)
+			add_irdata_bit(data, 0);
+		else
+			add_irdata_bit(data, 1);
+
+		if (hw == 8)
+			data->idle_count++;
+		else
+			data->idle_count = 0;
+	}
+
+	if (data->idle_count > WBCIR_MAX_IDLE_BYTES) {
+		/* Drain the FIFO */
+		while (inb(data->sbase + WBCIR_REG_SP3_LSR) & WBCIR_RX_AVAIL)
+			inb(data->sbase + WBCIR_REG_SP3_RXDATA);
+
+		/* And set RXINACTIVE */
+		outb(WBCIR_RX_DISABLE, data->sbase + WBCIR_REG_SP3_ASCR);
+
+		dev_dbg(dev, "IRDATA:\n");
+		for (i = 0; i < data->irdata_count; i += BITS_PER_LONG)
+			dev_dbg(dev, "0x%08lX\n", data->irdata[i/BITS_PER_LONG]);
+
+		switch (protocol) {
+		case IR_PROTOCOL_RC5:
+			wbcir_parse_rc5(dev, data);
+			break;
+		case IR_PROTOCOL_RC6:
+			wbcir_parse_rc6(dev, data);
+			break;
+		case IR_PROTOCOL_NEC:
+			wbcir_parse_nec(dev, data);
+			break;
+		}
+
+		wbcir_reset_irdata(data);
+		data->idle_count = 0;
+	}
+
+out:
+	spin_unlock_irqrestore(&wbcir_lock, flags);
+	return IRQ_HANDLED;
+}
+
+
+
+/*****************************************************************************
+ *
+ * SUSPEND/RESUME FUNCTIONS
+ *
+ *****************************************************************************/
+
+static void
+wbcir_shutdown(struct pnp_dev *device)
+{
+	struct device *dev = &device->dev;
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	int do_wake = 1;
+	u8 match[11];
+	u8 mask[11];
+	u8 rc6_csl = 0;
+	int i;
+
+	memset(match, 0, sizeof(match));
+	memset(mask, 0, sizeof(mask));
+
+	if (wake_sc == INVALID_SCANCODE || !device_may_wakeup(dev)) {
+		do_wake = 0;
+		goto finish;
+	}
+
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+		if (wake_sc > 0xFFF) {
+			do_wake = 0;
+			dev_err(dev, "RC5 - Invalid wake scancode\n");
+			break;
+		}
+
+		/* Mask = 13 bits, ex toggle */
+		mask[0] = 0xFF;
+		mask[1] = 0x17;
+
+		match[0]  = (wake_sc & 0x003F);      /* 6 command bits */
+		match[0] |= (wake_sc & 0x0180) >> 1; /* 2 address bits */
+		match[1]  = (wake_sc & 0x0E00) >> 9; /* 3 address bits */
+		if (!(wake_sc & 0x0040))             /* 2nd start bit  */
+			match[1] |= 0x10;
+
+		break;
+
+	case IR_PROTOCOL_NEC:
+		if (wake_sc > 0xFFFFFF) {
+			do_wake = 0;
+			dev_err(dev, "NEC - Invalid wake scancode\n");
+			break;
+		}
+
+		mask[0] = mask[1] = mask[2] = mask[3] = 0xFF;
+
+		match[1] = bitrev8((wake_sc & 0xFF));
+		match[0] = ~match[1];
+
+		match[3] = bitrev8((wake_sc & 0xFF00) >> 8);
+		if (wake_sc > 0xFFFF)
+			match[2] = bitrev8((wake_sc & 0xFF0000) >> 16);
+		else
+			match[2] = ~match[3];
+
+		break;
+
+	case IR_PROTOCOL_RC6:
+
+		if (wake_rc6mode == 0) {
+			if (wake_sc > 0xFFFF) {
+				do_wake = 0;
+				dev_err(dev, "RC6 - Invalid wake scancode\n");
+				break;
+			}
+
+			/* Command */
+			match[0] = wbcir_to_rc6cells(wake_sc >>  0);
+			mask[0]  = 0xFF;
+			match[1] = wbcir_to_rc6cells(wake_sc >>  4);
+			mask[1]  = 0xFF;
+
+			/* Address */
+			match[2] = wbcir_to_rc6cells(wake_sc >>  8);
+			mask[2]  = 0xFF;
+			match[3] = wbcir_to_rc6cells(wake_sc >> 12);
+			mask[3]  = 0xFF;
+
+			/* Header */
+			match[4] = 0x50; /* mode1 = mode0 = 0, ignore toggle */
+			mask[4]  = 0xF0;
+			match[5] = 0x09; /* start bit = 1, mode2 = 0 */
+			mask[5]  = 0x0F;
+
+			rc6_csl = 44;
+
+		} else if (wake_rc6mode == 6) {
+			i = 0;
+
+			/* Command */
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  0);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  4);
+			mask[i++] = 0xFF;
+
+			/* Address + Toggle */
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  8);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 12);
+			mask[i++] = 0x3F;
+
+			/* Customer bits 7 - 0 */
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 16);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 20);
+			mask[i++] = 0xFF;
+
+			if (wake_sc & 0x80000000) {
+				/* Customer range bit and bits 15 - 8 */
+				match[i]  = wbcir_to_rc6cells(wake_sc >> 24);
+				mask[i++] = 0xFF;
+				match[i]  = wbcir_to_rc6cells(wake_sc >> 28);
+				mask[i++] = 0xFF;
+				rc6_csl = 76;
+			} else if (wake_sc <= 0x007FFFFF) {
+				rc6_csl = 60;
+			} else {
+				do_wake = 0;
+				dev_err(dev, "RC6 - Invalid wake scancode\n");
+				break;
+			}
+
+			/* Header */
+			match[i]  = 0x93; /* mode1 = mode0 = 1, submode = 0 */
+			mask[i++] = 0xFF;
+			match[i]  = 0x0A; /* start bit = 1, mode2 = 1 */
+			mask[i++] = 0x0F;
+
+		} else {
+			do_wake = 0;
+			dev_err(dev, "RC6 - Invalid wake mode\n");
+		}
+
+		break;
+
+	default:
+		do_wake = 0;
+		break;
+	}
+
+finish:
+	if (do_wake) {
+		/* Set compare and compare mask */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX,
+			       WBCIR_REGSEL_COMPARE | WBCIR_REG_ADDR0,
+			       0x3F);
+		outsb(data->wbase + WBCIR_REG_WCEIR_DATA, match, 11);
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX,
+			       WBCIR_REGSEL_MASK | WBCIR_REG_ADDR0,
+			       0x3F);
+		outsb(data->wbase + WBCIR_REG_WCEIR_DATA, mask, 11);
+
+		/* RC6 Compare String Len */
+		outb(rc6_csl, data->wbase + WBCIR_REG_WCEIR_CSL);
+
+		/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+		/* Clear BUFF_EN, Clear END_EN, Set MATCH_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x01, 0x07);
+
+		/* Set CEIR_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x01, 0x01);
+
+	} else {
+		/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+		/* Clear CEIR_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+	}
+
+	/* Disable interrupts */
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+}
+
+static int
+wbcir_suspend(struct pnp_dev *device, pm_message_t state)
+{
+	wbcir_shutdown(device);
+	return 0;
+}
+
+static int
+wbcir_resume(struct pnp_dev *device)
+{
+	struct wbcir_data *data = pnp_get_drvdata(device);
+
+	/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	/* Clear CEIR_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+
+	/* Enable interrupts */
+	wbcir_reset_irdata(data);
+	outb(WBCIR_IRQ_RX | WBCIR_IRQ_ERR, data->sbase + WBCIR_REG_SP3_IER);
+
+	return 0;
+}
+
+
+
+/*****************************************************************************
+ *
+ * SETUP/INIT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static ssize_t
+wbcir_show_last_scancode(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	struct pnp_dev *device = container_of(dev, struct pnp_dev, dev);
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	return sprintf(buf, "0x%08X\n", data->last_scancode);
+}
+
+static struct device_attribute dev_attr_last_scancode = {
+	.attr = {
+		.name = "last_scancode",
+		.mode = 0444,
+	},
+	.show = wbcir_show_last_scancode,
+	.store = NULL,
+
+};
+
+static struct attribute *wbcir_attributes[] = {
+	&dev_attr_last_scancode.attr,
+	NULL,
+};
+
+static struct attribute_group wbcir_attribute_group = {
+	.attrs = wbcir_attributes,
+};
+
+static void
+wbcir_cfg_ceir(struct wbcir_data *data)
+{
+	u8 tmp;
+
+	/* Set PROT_SEL, RX_INV, Clear CEIR_EN (needed for the led) */
+	tmp = protocol << 4;
+	if (invert)
+		tmp |= 0x08;
+	outb(tmp, data->wbase + WBCIR_REG_WCEIR_CTL);
+
+	/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+	/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	/* Set RC5 cell time to correspond to 36 kHz */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CFG1, 0x4A, 0x7F);
+
+	/* Set IRTX_INV */
+	if (invert)
+		outb(0x04, data->ebase + WBCIR_REG_ECEIR_CCTL);
+	else
+		outb(0x00, data->ebase + WBCIR_REG_ECEIR_CCTL);
+
+	/*
+	 * Clear IR LED, set SP3 clock to 24Mhz
+	 * set SP3_IRRX_SW to binary 01, helpfully not documented
+	 */
+	outb(0x10, data->ebase + WBCIR_REG_ECEIR_CTS);
+}
+
+static int
+wbcir_probe(struct pnp_dev *device, const struct pnp_device_id *dev_id)
+{
+	struct device *dev = &device->dev;
+	struct wbcir_data *data;
+	int err;
+
+	if (!(pnp_port_len(device, 0) == EHFUNC_IOMEM_LEN &&
+	      pnp_port_len(device, 1) == WAKEUP_IOMEM_LEN &&
+	      pnp_port_len(device, 2) == SP_IOMEM_LEN)) {
+		dev_err(dev, "Invalid resources\n");
+		return -ENODEV;
+	}
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data) {
+		err = -ENOMEM;
+		goto exit;
+	}
+
+	pnp_set_drvdata(device, data);
+
+	data->ebase = pnp_port_start(device, 0);
+	data->wbase = pnp_port_start(device, 1);
+	data->sbase = pnp_port_start(device, 2);
+	data->irq = pnp_irq(device, 0);
+
+	if (data->wbase == 0 || data->ebase == 0 ||
+	    data->sbase == 0 || data->irq == 0) {
+		err = -ENODEV;
+		dev_err(dev, "Invalid resources\n");
+		goto exit_free_data;
+	}
+
+	dev_info(&device->dev, "Found device "
+		 "(w: 0x%lX, e: 0x%lX, s: 0x%lX, i: %u)\n",
+		 data->wbase, data->ebase, data->sbase, data->irq);
+
+	if (!request_region(data->wbase, WAKEUP_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->wbase, data->wbase + WAKEUP_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_free_data;
+	}
+
+	if (!request_region(data->ebase, EHFUNC_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->ebase, data->ebase + EHFUNC_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_release_wbase;
+	}
+
+	if (!request_region(data->sbase, SP_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->sbase, data->sbase + SP_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_release_ebase;
+	}
+
+	err = request_irq(data->irq, wbcir_irq_handler,
+			  IRQF_DISABLED, DRVNAME, device);
+	if (err) {
+		dev_err(dev, "Failed to claim IRQ %u\n", data->irq);
+		err = -EBUSY;
+		goto exit_release_sbase;
+	}
+
+	led_trigger_register_simple("cir-tx", &data->txtrigger);
+	if (!data->txtrigger) {
+		err = -ENOMEM;
+		goto exit_free_irq;
+	}
+
+	led_trigger_register_simple("cir-rx", &data->rxtrigger);
+	if (!data->rxtrigger) {
+		err = -ENOMEM;
+		goto exit_unregister_txtrigger;
+	}
+
+	data->led.name = "cir::activity";
+	data->led.default_trigger = "cir-rx";
+	data->led.brightness_set = wbcir_led_brightness_set;
+	data->led.brightness_get = wbcir_led_brightness_get;
+	err = led_classdev_register(&device->dev, &data->led);
+	if (err)
+		goto exit_unregister_rxtrigger;
+
+	data->input_dev = input_allocate_device();
+	if (!data->input_dev) {
+		err = -ENOMEM;
+		goto exit_unregister_led;
+	}
+
+	data->input_dev->evbit[0] = BIT(EV_KEY);
+	data->input_dev->name = WBCIR_NAME;
+	data->input_dev->phys = "wbcir/cir0";
+	data->input_dev->id.bustype = BUS_HOST;
+	data->input_dev->id.vendor  = PCI_VENDOR_ID_WINBOND;
+	data->input_dev->id.product = WBCIR_ID_FAMILY;
+	data->input_dev->id.version = WBCIR_ID_CHIP;
+	data->input_dev->getkeycode = wbcir_getkeycode;
+	data->input_dev->setkeycode = wbcir_setkeycode;
+	input_set_drvdata(data->input_dev, data);
+
+	err = input_register_device(data->input_dev);
+	if (err)
+		goto exit_free_input;
+
+	data->last_scancode = INVALID_SCANCODE;
+	err = sysfs_create_group(&device->dev.kobj, &wbcir_attribute_group);
+	if (err)
+		goto exit_unregister_input;
+
+	INIT_LIST_HEAD(&data->keytable);
+	setup_timer(&data->timer_keyup, wbcir_keyup, (unsigned long)data);
+
+	/* Load default keymaps */
+	if (protocol == IR_PROTOCOL_RC6) {
+		int i;
+		for (i = 0; i < ARRAY_SIZE(rc6_def_keymap); i++) {
+			err = wbcir_setkeycode(data->input_dev,
+					       (int)rc6_def_keymap[i].scancode,
+					       (int)rc6_def_keymap[i].keycode);
+			if (err)
+				goto exit_unregister_keys;
+		}
+	}
+
+	device_init_wakeup(&device->dev, 1);
+
+	wbcir_cfg_ceir(data);
+
+	/* Disable interrupts */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+
+	/* Enable extended mode */
+	wbcir_select_bank(data, WBCIR_BANK_2);
+	outb(WBCIR_EXT_ENABLE, data->sbase + WBCIR_REG_SP3_EXCR1);
+
+	/*
+	 * Configure baud generator, IR data will be sampled at
+	 * a bitrate of: (24Mhz * prescaler) / (divisor * 16).
+	 *
+	 * The ECIR registers include a flag to change the
+	 * 24Mhz clock freq to 48Mhz.
+	 */
+
+	/* prescaler 1.0, tx/rx fifo lvl 32 */
+	outb(0x35, data->sbase + WBCIR_REG_SP3_EXCR2);
+
+	/* Set baud divisor to generate one byte per bit/cell */
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+		outb(0xA7, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	case IR_PROTOCOL_RC6:
+		outb(0x53, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	case IR_PROTOCOL_NEC:
+		outb(0x69, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	}
+	outb(0x00, data->sbase + WBCIR_REG_SP3_BGDH);
+
+	/* Set CEIR mode */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(0xC0, data->sbase + WBCIR_REG_SP3_MCR);
+	inb(data->sbase + WBCIR_REG_SP3_LSR); /* Clear LSR */
+	inb(data->sbase + WBCIR_REG_SP3_MSR); /* Clear MSR */
+
+	/* Disable RX demod, run-length encoding/decoding, set freq span */
+	wbcir_select_bank(data, WBCIR_BANK_7);
+	outb(0x10, data->sbase + WBCIR_REG_SP3_RCCFG);
+
+	/* Disable timer */
+	wbcir_select_bank(data, WBCIR_BANK_4);
+	outb(0x00, data->sbase + WBCIR_REG_SP3_IRCR1);
+
+	/* Enable MSR interrupt, Clear AUX_IRX */
+	wbcir_select_bank(data, WBCIR_BANK_5);
+	outb(0x00, data->sbase + WBCIR_REG_SP3_IRCR2);
+
+	/* Disable CRC */
+	wbcir_select_bank(data, WBCIR_BANK_6);
+	outb(0x20, data->sbase + WBCIR_REG_SP3_IRCR3);
+
+	/* Set RX/TX (de)modulation freq, not really used */
+	wbcir_select_bank(data, WBCIR_BANK_7);
+	outb(0xF2, data->sbase + WBCIR_REG_SP3_IRRXDC);
+	outb(0x69, data->sbase + WBCIR_REG_SP3_IRTXMC);
+
+	/* Set invert and pin direction */
+	if (invert)
+		outb(0x10, data->sbase + WBCIR_REG_SP3_IRCFG4);
+	else
+		outb(0x00, data->sbase + WBCIR_REG_SP3_IRCFG4);
+
+	/* Set FIFO thresholds (RX = 16, TX = 7), reset RX/TX */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(0x97, data->sbase + WBCIR_REG_SP3_FCR);
+
+	/* Clear AUX status bits */
+	outb(0xE0, data->sbase + WBCIR_REG_SP3_ASCR);
+
+	/* Enable interrupts */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(WBCIR_IRQ_RX | WBCIR_IRQ_ERR, data->sbase + WBCIR_REG_SP3_IER);
+
+	return 0;
+
+exit_unregister_keys:
+	if (!list_empty(&data->keytable)) {
+		struct wbcir_keyentry *key;
+		struct wbcir_keyentry *keytmp;
+
+		list_for_each_entry_safe(key, keytmp, &data->keytable, list) {
+			list_del(&key->list);
+			kfree(key);
+		}
+	}
+exit_unregister_input:
+	input_unregister_device(data->input_dev);
+	/* Can't call input_free_device on an unregistered device */
+	data->input_dev = NULL;
+exit_free_input:
+	input_free_device(data->input_dev);
+exit_unregister_led:
+	led_classdev_unregister(&data->led);
+exit_unregister_rxtrigger:
+	led_trigger_unregister_simple(data->rxtrigger);
+exit_unregister_txtrigger:
+	led_trigger_unregister_simple(data->txtrigger);
+exit_free_irq:
+	free_irq(data->irq, device);
+exit_release_sbase:
+	release_region(data->sbase, SP_IOMEM_LEN);
+exit_release_ebase:
+	release_region(data->ebase, EHFUNC_IOMEM_LEN);
+exit_release_wbase:
+	release_region(data->wbase, WAKEUP_IOMEM_LEN);
+exit_free_data:
+	kfree(data);
+	pnp_set_drvdata(device, NULL);
+exit:
+	return err;
+}
+
+static void
+wbcir_remove(struct pnp_dev *device)
+{
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	struct wbcir_keyentry *key;
+	struct wbcir_keyentry *keytmp;
+
+	/* Disable interrupts */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+
+	del_timer_sync(&data->timer_keyup);
+
+	free_irq(data->irq, device);
+
+	/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+	/* Clear CEIR_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+
+	/* Clear BUFF_EN, END_EN, MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	sysfs_remove_group(&device->dev.kobj, &wbcir_attribute_group);
+
+	/* This will generate a keyup event if necessary */
+	input_unregister_device(data->input_dev);
+
+	led_trigger_unregister_simple(data->rxtrigger);
+	led_trigger_unregister_simple(data->txtrigger);
+	led_classdev_unregister(&data->led);
+
+	/* This is ok since &data->led isn't actually used */
+	wbcir_led_brightness_set(&data->led, LED_OFF);
+
+	release_region(data->wbase, WAKEUP_IOMEM_LEN);
+	release_region(data->ebase, EHFUNC_IOMEM_LEN);
+	release_region(data->sbase, SP_IOMEM_LEN);
+
+	list_for_each_entry_safe(key, keytmp, &data->keytable, list) {
+		list_del(&key->list);
+		kfree(key);
+	}
+
+	kfree(data);
+
+	pnp_set_drvdata(device, NULL);
+}
+
+static const struct pnp_device_id wbcir_ids[] = {
+	{ "WEC1022", 0 },
+	{ "", 0 }
+};
+MODULE_DEVICE_TABLE(pnp, wbcir_ids);
+
+static struct pnp_driver wbcir_driver = {
+	.name     = WBCIR_NAME,
+	.id_table = wbcir_ids,
+	.probe    = wbcir_probe,
+	.remove   = wbcir_remove,
+	.suspend  = wbcir_suspend,
+	.resume   = wbcir_resume,
+	.shutdown = wbcir_shutdown
+};
+
+static int __init
+wbcir_init(void)
+{
+	int ret;
+
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+	case IR_PROTOCOL_NEC:
+	case IR_PROTOCOL_RC6:
+		break;
+	default:
+		printk(KERN_ERR DRVNAME ": Invalid protocol argument\n");
+		return -EINVAL;
+	}
+
+	ret = pnp_register_driver(&wbcir_driver);
+	if (ret)
+		printk(KERN_ERR DRVNAME ": Unable to register driver\n");
+
+	return ret;
+}
+
+static void __exit
+wbcir_exit(void)
+{
+	pnp_unregister_driver(&wbcir_driver);
+}
+
+MODULE_AUTHOR("David Härdeman <david@hardeman.nu>");
+MODULE_DESCRIPTION("Winbond SuperI/O Consumer IR Driver");
+MODULE_LICENSE("GPL");
+
+module_init(wbcir_init);
+module_exit(wbcir_exit);
+
+

-- 
David Härdeman
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware
  2009-08-09  9:56   ` david
  (?)
@ 2009-08-10 15:39   ` Bjorn Helgaas
  -1 siblings, 0 replies; 16+ messages in thread
From: Bjorn Helgaas @ 2009-08-10 15:39 UTC (permalink / raw)
  To: david; +Cc: linux-kernel, linux-input, jbarnes, akpm, randy.dunlap

On Sunday 09 August 2009 03:56:47 am david@hardeman.nu wrote:
> --- linux-2.6.orig/drivers/input/misc/Kconfig	2009-07-25 21:18:57.000000000 +0200
> +++ linux-2.6/drivers/input/misc/Kconfig	2009-08-07 21:45:41.000000000 +0200
> @@ -222,6 +222,22 @@
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called sgi_btns.
>  
> +config INPUT_WINBOND_CIR
> +	tristate "Winbond IR remote control"
> +	depends on X86 && ACPI

This should depend on PNP, not ACPI.

> +#define WBCIR_ACPI_NAME	"Winbond CIR"

You might remove "ACPI" from "WBCIR_ACPI_NAME," since it has nothing
to do with ACPI.

Otherwise, looks good to me.

Bjorn



^ permalink raw reply	[flat|nested] 16+ messages in thread

* [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware
  2009-08-09  9:56 [patch 0/2] Winbond IR Driver - v2 david
@ 2009-08-09  9:56   ` david
  0 siblings, 0 replies; 16+ messages in thread
From: david @ 2009-08-09  9:56 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-input, jbarnes, akpm, bjorn.helgaas, randy.dunlap, david

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: add-a-driver-for-the-winbond-wpcd376i-cir-functionality --]
[-- Type: text/plain, Size: 46919 bytes --]

Add a driver for the the Consumer IR (CIR) functionality of the Winbond
WPCD376I chipset (found on e.g. Intel DG45FC motherboards).

Changes since the last version: converted from an ACPI to a PNP driver
using a patch provided by Bjorn Helgaas <bjorn.helgaas@hp.com>.

Signed-off-by: David Härdeman <david@hardeman.nu>
Reviewed-by: Jesse Barnes <jbarnes@virtuousgeek.org>

Index: linux-2.6/MAINTAINERS
===================================================================
--- linux-2.6.orig/MAINTAINERS	2009-07-25 21:18:57.000000000 +0200
+++ linux-2.6/MAINTAINERS	2009-07-25 21:19:28.000000000 +0200
@@ -6448,6 +6448,12 @@
 S:	Maintained
 F:	drivers/scsi/wd7000.c
 
+WINBOND CIR DRIVER
+P:	David Härdeman
+M:	david@hardeman.nu
+S:	Maintained
+F:	drivers/input/misc/winbond-cir.c
+
 WIMAX STACK
 P:	Inaky Perez-Gonzalez
 M:	inaky.perez-gonzalez@intel.com
Index: linux-2.6/drivers/input/misc/Kconfig
===================================================================
--- linux-2.6.orig/drivers/input/misc/Kconfig	2009-07-25 21:18:57.000000000 +0200
+++ linux-2.6/drivers/input/misc/Kconfig	2009-08-07 21:45:41.000000000 +0200
@@ -222,6 +222,22 @@
 	  To compile this driver as a module, choose M here: the
 	  module will be called sgi_btns.
 
+config INPUT_WINBOND_CIR
+	tristate "Winbond IR remote control"
+	depends on X86 && ACPI
+	select LEDS_CLASS
+	select BITREVERSE
+	help
+	  Say Y here if you want to use the IR remote functionality found
+	  in some Winbond SuperI/O chips. Currently only the WPCD376I
+	  chip is supported (included in some Intel Media series motherboards).
+
+	  IR Receive and wake-on-IR from suspend and power-off is currently
+	  supported.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called winbond_cir.
+
 config HP_SDC_RTC
 	tristate "HP SDC Real Time Clock"
 	depends on (GSC || HP300) && SERIO
Index: linux-2.6/drivers/input/misc/Makefile
===================================================================
--- linux-2.6.orig/drivers/input/misc/Makefile	2009-07-25 21:18:57.000000000 +0200
+++ linux-2.6/drivers/input/misc/Makefile	2009-07-25 21:19:28.000000000 +0200
@@ -19,6 +19,7 @@
 obj-$(CONFIG_INPUT_PCSPKR)		+= pcspkr.o
 obj-$(CONFIG_INPUT_POWERMATE)		+= powermate.o
 obj-$(CONFIG_INPUT_RB532_BUTTON)	+= rb532_button.o
+obj-$(CONFIG_INPUT_WINBOND_CIR)		+= winbond-cir.o
 obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER)	+= rotary_encoder.o
 obj-$(CONFIG_INPUT_SGI_BTNS)		+= sgi_btns.o
 obj-$(CONFIG_INPUT_SPARCSPKR)		+= sparcspkr.o
Index: linux-2.6/drivers/input/misc/winbond-cir.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/drivers/input/misc/winbond-cir.c	2009-07-25 21:19:28.000000000 +0200
@@ -0,0 +1,1644 @@
+/*
+ *  winbond-cir.c - Driver for the Consumer IR functionality of Winbond
+ *                  SuperI/O chips.
+ *
+ *  Currently supports the Winbond WPCD376i chip (PNP id WEC1022), but
+ *  could probably support others (Winbond WEC102X, NatSemi, etc)
+ *  with minor modifications.
+ *
+ *  Original Author: David Härdeman <david@hardeman.nu>
+ *     Copyright (C) 2009 David Härdeman <david@hardeman.nu>
+ *
+ *  Dedicated to Matilda, my newborn daughter, without whose loving attention
+ *  this driver would have been finished in half the time and with a fraction
+ *  of the bugs.
+ *
+ *  Written using:
+ *    o Winbond WPCD376I datasheet helpfully provided by Jesse Barnes at Intel
+ *    o NatSemi PC87338/PC97338 datasheet (for the serial port stuff)
+ *    o DSDT dumps
+ *
+ *  Supported features:
+ *    o RC6
+ *    o Wake-On-CIR functionality
+ *
+ *  To do:
+ *    o Test NEC and RC5
+ *
+ *  Left as an exercise for the reader:
+ *    o Learning (I have neither the hardware, nor the need)
+ *    o IR Transmit (ibid)
+ *
+ *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/pnp.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/pci_ids.h>
+#include <linux/io.h>
+#include <linux/bitrev.h>
+#include <linux/bitops.h>
+
+#define DRVNAME "winbond-cir"
+
+/* CEIR Wake-Up Registers, relative to data->wbase                      */
+#define WBCIR_REG_WCEIR_CTL	0x03 /* CEIR Receiver Control		*/
+#define WBCIR_REG_WCEIR_STS	0x04 /* CEIR Receiver Status		*/
+#define WBCIR_REG_WCEIR_EV_EN	0x05 /* CEIR Receiver Event Enable	*/
+#define WBCIR_REG_WCEIR_CNTL	0x06 /* CEIR Receiver Counter Low	*/
+#define WBCIR_REG_WCEIR_CNTH	0x07 /* CEIR Receiver Counter High	*/
+#define WBCIR_REG_WCEIR_INDEX	0x08 /* CEIR Receiver Index		*/
+#define WBCIR_REG_WCEIR_DATA	0x09 /* CEIR Receiver Data		*/
+#define WBCIR_REG_WCEIR_CSL	0x0A /* CEIR Re. Compare Strlen		*/
+#define WBCIR_REG_WCEIR_CFG1	0x0B /* CEIR Re. Configuration 1	*/
+#define WBCIR_REG_WCEIR_CFG2	0x0C /* CEIR Re. Configuration 2	*/
+
+/* CEIR Enhanced Functionality Registers, relative to data->ebase       */
+#define WBCIR_REG_ECEIR_CTS	0x00 /* Enhanced IR Control Status	*/
+#define WBCIR_REG_ECEIR_CCTL	0x01 /* Infrared Counter Control	*/
+#define WBCIR_REG_ECEIR_CNT_LO	0x02 /* Infrared Counter LSB		*/
+#define WBCIR_REG_ECEIR_CNT_HI	0x03 /* Infrared Counter MSB		*/
+#define WBCIR_REG_ECEIR_IREM	0x04 /* Infrared Emitter Status		*/
+
+/* SP3 Banked Registers, relative to data->sbase                        */
+#define WBCIR_REG_SP3_BSR	0x03 /* Bank Select, all banks		*/
+				      /* Bank 0				*/
+#define WBCIR_REG_SP3_RXDATA	0x00 /* FIFO RX data (r)		*/
+#define WBCIR_REG_SP3_TXDATA	0x00 /* FIFO TX data (w)		*/
+#define WBCIR_REG_SP3_IER	0x01 /* Interrupt Enable		*/
+#define WBCIR_REG_SP3_EIR	0x02 /* Event Identification (r)	*/
+#define WBCIR_REG_SP3_FCR	0x02 /* FIFO Control (w)		*/
+#define WBCIR_REG_SP3_MCR	0x04 /* Mode Control			*/
+#define WBCIR_REG_SP3_LSR	0x05 /* Link Status			*/
+#define WBCIR_REG_SP3_MSR	0x06 /* Modem Status			*/
+#define WBCIR_REG_SP3_ASCR	0x07 /* Aux Status and Control		*/
+				      /* Bank 2				*/
+#define WBCIR_REG_SP3_BGDL	0x00 /* Baud Divisor LSB		*/
+#define WBCIR_REG_SP3_BGDH	0x01 /* Baud Divisor MSB		*/
+#define WBCIR_REG_SP3_EXCR1	0x02 /* Extended Control 1		*/
+#define WBCIR_REG_SP3_EXCR2	0x04 /* Extended Control 2		*/
+#define WBCIR_REG_SP3_TXFLV	0x06 /* TX FIFO Level			*/
+#define WBCIR_REG_SP3_RXFLV	0x07 /* RX FIFO Level			*/
+				      /* Bank 3				*/
+#define WBCIR_REG_SP3_MRID	0x00 /* Module Identification		*/
+#define WBCIR_REG_SP3_SH_LCR	0x01 /* LCR Shadow			*/
+#define WBCIR_REG_SP3_SH_FCR	0x02 /* FCR Shadow			*/
+				      /* Bank 4				*/
+#define WBCIR_REG_SP3_IRCR1	0x02 /* Infrared Control 1		*/
+				      /* Bank 5				*/
+#define WBCIR_REG_SP3_IRCR2	0x04 /* Infrared Control 2		*/
+				      /* Bank 6				*/
+#define WBCIR_REG_SP3_IRCR3	0x00 /* Infrared Control 3		*/
+#define WBCIR_REG_SP3_SIR_PW	0x02 /* SIR Pulse Width		*/
+				      /* Bank 7				*/
+#define WBCIR_REG_SP3_IRRXDC	0x00 /* IR RX Demod Control		*/
+#define WBCIR_REG_SP3_IRTXMC	0x01 /* IR TX Mod Control		*/
+#define WBCIR_REG_SP3_RCCFG	0x02 /* CEIR Config			*/
+#define WBCIR_REG_SP3_IRCFG1	0x04 /* Infrared Config 1		*/
+#define WBCIR_REG_SP3_IRCFG4	0x07 /* Infrared Config 4		*/
+
+/*
+ * Magic values follow
+ */
+
+/* No interrupts for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_NONE		0x00
+/* RX data bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_RX		0x01
+/* Over/Under-flow bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_ERR		0x04
+/* Led enable/disable bit for WBCIR_REG_ECEIR_CTS */
+#define WBCIR_LED_ENABLE	0x80
+/* RX data available bit for WBCIR_REG_SP3_LSR */
+#define WBCIR_RX_AVAIL		0x01
+/* RX disable bit for WBCIR_REG_SP3_ASCR */
+#define WBCIR_RX_DISABLE	0x20
+/* Extended mode enable bit for WBCIR_REG_SP3_EXCR1 */
+#define WBCIR_EXT_ENABLE	0x01
+/* Select compare register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */
+#define WBCIR_REGSEL_COMPARE	0x10
+/* Select mask register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */
+#define WBCIR_REGSEL_MASK	0x20
+/* Starting address of selected register in WBCIR_REG_WCEIR_INDEX */
+#define WBCIR_REG_ADDR0		0x00
+
+/* Valid banks for the SP3 UART */
+enum wbcir_bank {
+	WBCIR_BANK_0          = 0x00,
+	WBCIR_BANK_1          = 0x80,
+	WBCIR_BANK_2          = 0xE0,
+	WBCIR_BANK_3          = 0xE4,
+	WBCIR_BANK_4          = 0xE8,
+	WBCIR_BANK_5          = 0xEC,
+	WBCIR_BANK_6          = 0xF0,
+	WBCIR_BANK_7          = 0xF4,
+};
+
+/* Supported IR Protocols */
+enum wbcir_protocol {
+	IR_PROTOCOL_RC5          = 0x0,
+	IR_PROTOCOL_NEC          = 0x1,
+	IR_PROTOCOL_RC6          = 0x2,
+};
+
+/* Misc */
+#define WBCIR_ACPI_NAME	"Winbond CIR"
+#define WBCIR_ID_FAMILY          0xF1 /* Family ID for the WPCD376I	*/
+#define	WBCIR_ID_CHIP            0x04 /* Chip ID for the WPCD376I	*/
+#define IR_KEYPRESS_TIMEOUT       250 /* FIXME: should be per-protocol? */
+#define INVALID_SCANCODE   0x7FFFFFFF /* Invalid with all protos	*/
+#define WAKEUP_IOMEM_LEN         0x10 /* Wake-Up I/O Reg Len		*/
+#define EHFUNC_IOMEM_LEN         0x10 /* Enhanced Func I/O Reg Len	*/
+#define SP_IOMEM_LEN             0x08 /* Serial Port 3 (IR) Reg Len	*/
+#define WBCIR_MAX_IDLE_BYTES       10
+
+static DEFINE_SPINLOCK(wbcir_lock);
+static DEFINE_RWLOCK(keytable_lock);
+
+struct wbcir_key {
+	u32 scancode;
+	unsigned int keycode;
+};
+
+struct wbcir_keyentry {
+	struct wbcir_key key;
+	struct list_head list;
+};
+
+static struct wbcir_key rc6_def_keymap[] = {
+	{ 0x800F0400, KEY_0			},
+	{ 0x800F0401, KEY_1			},
+	{ 0x800F0402, KEY_2			},
+	{ 0x800F0403, KEY_3			},
+	{ 0x800F0404, KEY_4			},
+	{ 0x800F0405, KEY_5			},
+	{ 0x800F0406, KEY_6			},
+	{ 0x800F0407, KEY_7			},
+	{ 0x800F0408, KEY_8			},
+	{ 0x800F0409, KEY_9			},
+	{ 0x800F041D, KEY_NUMERIC_STAR		},
+	{ 0x800F041C, KEY_NUMERIC_POUND		},
+	{ 0x800F0410, KEY_VOLUMEUP		},
+	{ 0x800F0411, KEY_VOLUMEDOWN		},
+	{ 0x800F0412, KEY_CHANNELUP		},
+	{ 0x800F0413, KEY_CHANNELDOWN		},
+	{ 0x800F040E, KEY_MUTE			},
+	{ 0x800F040D, KEY_VENDOR		}, /* Vista Logo Key */
+	{ 0x800F041E, KEY_UP			},
+	{ 0x800F041F, KEY_DOWN			},
+	{ 0x800F0420, KEY_LEFT			},
+	{ 0x800F0421, KEY_RIGHT			},
+	{ 0x800F0422, KEY_OK			},
+	{ 0x800F0423, KEY_ESC			},
+	{ 0x800F040F, KEY_INFO			},
+	{ 0x800F040A, KEY_CLEAR			},
+	{ 0x800F040B, KEY_ENTER			},
+	{ 0x800F045B, KEY_RED			},
+	{ 0x800F045C, KEY_GREEN			},
+	{ 0x800F045D, KEY_YELLOW		},
+	{ 0x800F045E, KEY_BLUE			},
+	{ 0x800F045A, KEY_TEXT			},
+	{ 0x800F0427, KEY_SWITCHVIDEOMODE	},
+	{ 0x800F040C, KEY_POWER			},
+	{ 0x800F0450, KEY_RADIO			},
+	{ 0x800F0448, KEY_PVR			},
+	{ 0x800F0447, KEY_AUDIO			},
+	{ 0x800F0426, KEY_EPG			},
+	{ 0x800F0449, KEY_CAMERA		},
+	{ 0x800F0425, KEY_TV			},
+	{ 0x800F044A, KEY_VIDEO			},
+	{ 0x800F0424, KEY_DVD			},
+	{ 0x800F0416, KEY_PLAY			},
+	{ 0x800F0418, KEY_PAUSE			},
+	{ 0x800F0419, KEY_STOP			},
+	{ 0x800F0414, KEY_FASTFORWARD		},
+	{ 0x800F041A, KEY_NEXT			},
+	{ 0x800F041B, KEY_PREVIOUS		},
+	{ 0x800F0415, KEY_REWIND		},
+	{ 0x800F0417, KEY_RECORD		},
+};
+
+/* Registers and other state is protected by wbcir_lock */
+struct wbcir_data {
+	unsigned long wbase;        /* Wake-Up Baseaddr		*/
+	unsigned long ebase;        /* Enhanced Func. Baseaddr	*/
+	unsigned long sbase;        /* Serial Port Baseaddr	*/
+	unsigned int  irq;          /* Serial Port IRQ		*/
+
+	struct input_dev *input_dev;
+	struct timer_list timer_keyup;
+	struct led_trigger *rxtrigger;
+	struct led_trigger *txtrigger;
+	struct led_classdev led;
+
+	u32 last_scancode;
+	unsigned int last_keycode;
+	u8 last_toggle;
+	u8 keypressed;
+	unsigned long keyup_jiffies;
+	unsigned int idle_count;
+
+	/* RX irdata and parsing state */
+	unsigned long irdata[30];
+	unsigned int irdata_count;
+	unsigned int irdata_idle;
+	unsigned int irdata_off;
+	unsigned int irdata_error;
+
+	/* Protected by keytable_lock */
+	struct list_head keytable;
+};
+
+static enum wbcir_protocol protocol = IR_PROTOCOL_RC6;
+module_param(protocol, uint, 0444);
+MODULE_PARM_DESC(protocol, "IR protocol to use "
+		 "(0 = RC5, 1 = NEC, 2 = RC6A, default)");
+
+static int invert; /* default = 0 */
+module_param(invert, bool, 0444);
+MODULE_PARM_DESC(invert, "Invert the signal from the IR receiver");
+
+static unsigned int wake_sc = 0x800F040C;
+module_param(wake_sc, uint, 0644);
+MODULE_PARM_DESC(wake_sc, "Scancode of the power-on IR command");
+
+static unsigned int wake_rc6mode = 6;
+module_param(wake_rc6mode, uint, 0644);
+MODULE_PARM_DESC(wake_rc6mode, "RC6 mode for the power-on command "
+		 "(0 = 0, 6 = 6A, default)");
+
+
+
+/*****************************************************************************
+ *
+ * UTILITY FUNCTIONS
+ *
+ *****************************************************************************/
+
+/* Caller needs to hold wbcir_lock */
+static void
+wbcir_set_bits(unsigned long addr, u8 bits, u8 mask)
+{
+	u8 val;
+
+	val = inb(addr);
+	val = ((val & ~mask) | (bits & mask));
+	outb(val, addr);
+}
+
+/* Selects the register bank for the serial port */
+static inline void
+wbcir_select_bank(struct wbcir_data *data, enum wbcir_bank bank)
+{
+	outb(bank, data->sbase + WBCIR_REG_SP3_BSR);
+}
+
+static enum led_brightness
+wbcir_led_brightness_get(struct led_classdev *led_cdev)
+{
+	struct wbcir_data *data = container_of(led_cdev,
+					       struct wbcir_data,
+					       led);
+
+	if (inb(data->ebase + WBCIR_REG_ECEIR_CTS) & WBCIR_LED_ENABLE)
+		return LED_FULL;
+	else
+		return LED_OFF;
+}
+
+static void
+wbcir_led_brightness_set(struct led_classdev *led_cdev,
+			    enum led_brightness brightness)
+{
+	struct wbcir_data *data = container_of(led_cdev,
+					       struct wbcir_data,
+					       led);
+
+	wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CTS,
+		       brightness == LED_OFF ? 0x00 : WBCIR_LED_ENABLE,
+		       WBCIR_LED_ENABLE);
+}
+
+/* Manchester encodes bits to RC6 message cells (see wbcir_parse_rc6) */
+static u8
+wbcir_to_rc6cells(u8 val)
+{
+	u8 coded = 0x00;
+	int i;
+
+	val &= 0x0F;
+	for (i = 0; i < 4; i++) {
+		if (val & 0x01)
+			coded |= 0x02 << (i * 2);
+		else
+			coded |= 0x01 << (i * 2);
+		val >>= 1;
+	}
+
+	return coded;
+}
+
+
+
+/*****************************************************************************
+ *
+ * INPUT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static unsigned int
+wbcir_do_getkeycode(struct wbcir_data *data, u32 scancode)
+{
+	struct wbcir_keyentry *keyentry;
+	unsigned int keycode = KEY_RESERVED;
+	unsigned long flags;
+
+	read_lock_irqsave(&keytable_lock, flags);
+
+	list_for_each_entry(keyentry, &data->keytable, list) {
+		if (keyentry->key.scancode == scancode) {
+			keycode = keyentry->key.keycode;
+			break;
+		}
+	}
+
+	read_unlock_irqrestore(&keytable_lock, flags);
+	return keycode;
+}
+
+static int
+wbcir_getkeycode(struct input_dev *dev, int scancode, int *keycode)
+{
+	struct wbcir_data *data = input_get_drvdata(dev);
+
+	*keycode = (int)wbcir_do_getkeycode(data, (u32)scancode);
+	return 0;
+}
+
+static int
+wbcir_setkeycode(struct input_dev *dev, int sscancode, int keycode)
+{
+	struct wbcir_data *data = input_get_drvdata(dev);
+	struct wbcir_keyentry *keyentry;
+	struct wbcir_keyentry *new_keyentry;
+	unsigned long flags;
+	unsigned int old_keycode = KEY_RESERVED;
+	u32 scancode = (u32)sscancode;
+
+	if (keycode < 0 || keycode > KEY_MAX)
+		return -EINVAL;
+
+	new_keyentry = kmalloc(sizeof(*new_keyentry), GFP_KERNEL);
+	if (!new_keyentry)
+		return -ENOMEM;
+
+	write_lock_irqsave(&keytable_lock, flags);
+
+	list_for_each_entry(keyentry, &data->keytable, list) {
+		if (keyentry->key.scancode != scancode)
+			continue;
+
+		old_keycode = keyentry->key.keycode;
+		keyentry->key.keycode = keycode;
+
+		if (keyentry->key.keycode == KEY_RESERVED) {
+			list_del(&keyentry->list);
+			kfree(keyentry);
+		}
+
+		break;
+	}
+
+	set_bit(keycode, dev->keybit);
+
+	if (old_keycode == KEY_RESERVED) {
+		new_keyentry->key.scancode = scancode;
+		new_keyentry->key.keycode = keycode;
+		list_add(&new_keyentry->list, &data->keytable);
+	} else {
+		kfree(new_keyentry);
+		clear_bit(old_keycode, dev->keybit);
+		list_for_each_entry(keyentry, &data->keytable, list) {
+			if (keyentry->key.keycode == old_keycode) {
+				set_bit(old_keycode, dev->keybit);
+				break;
+			}
+		}
+	}
+
+	write_unlock_irqrestore(&keytable_lock, flags);
+	return 0;
+}
+
+/*
+ * Timer function to report keyup event some time after keydown is
+ * reported by the ISR.
+ */
+static void
+wbcir_keyup(unsigned long cookie)
+{
+	struct wbcir_data *data = (struct wbcir_data *)cookie;
+	unsigned long flags;
+
+	/*
+	 * data->keyup_jiffies is used to prevent a race condition if a
+	 * hardware interrupt occurs at this point and the keyup timer
+	 * event is moved further into the future as a result.
+	 *
+	 * The timer will then be reactivated and this function called
+	 * again in the future. We need to exit gracefully in that case
+	 * to allow the input subsystem to do its auto-repeat magic or
+	 * a keyup event might follow immediately after the keydown.
+	 */
+
+	spin_lock_irqsave(&wbcir_lock, flags);
+
+	if (time_is_after_eq_jiffies(data->keyup_jiffies) && data->keypressed) {
+		data->keypressed = 0;
+		led_trigger_event(data->rxtrigger, LED_OFF);
+		input_report_key(data->input_dev, data->last_keycode, 0);
+		input_sync(data->input_dev);
+	}
+
+	spin_unlock_irqrestore(&wbcir_lock, flags);
+}
+
+static void
+wbcir_keydown(struct wbcir_data *data, u32 scancode, u8 toggle)
+{
+	unsigned int keycode;
+
+	/* Repeat? */
+	if (data->last_scancode == scancode &&
+	    data->last_toggle == toggle &&
+	    data->keypressed)
+		goto set_timer;
+	data->last_scancode = scancode;
+
+	/* Do we need to release an old keypress? */
+	if (data->keypressed) {
+		input_report_key(data->input_dev, data->last_keycode, 0);
+		input_sync(data->input_dev);
+		data->keypressed = 0;
+	}
+
+	/* Do we know this scancode? */
+	keycode = wbcir_do_getkeycode(data, scancode);
+	if (keycode == KEY_RESERVED)
+		goto set_timer;
+
+	/* Register a keypress */
+	input_report_key(data->input_dev, keycode, 1);
+	input_sync(data->input_dev);
+	data->keypressed = 1;
+	data->last_keycode = keycode;
+	data->last_toggle = toggle;
+
+set_timer:
+	led_trigger_event(data->rxtrigger,
+			  data->keypressed ? LED_FULL : LED_OFF);
+	data->keyup_jiffies = jiffies + msecs_to_jiffies(IR_KEYPRESS_TIMEOUT);
+	mod_timer(&data->timer_keyup, data->keyup_jiffies);
+}
+
+
+
+/*****************************************************************************
+ *
+ * IR PARSING FUNCTIONS
+ *
+ *****************************************************************************/
+
+/* Resets all irdata */
+static void
+wbcir_reset_irdata(struct wbcir_data *data)
+{
+	memset(data->irdata, 0, sizeof(data->irdata));
+	data->irdata_count = 0;
+	data->irdata_off = 0;
+	data->irdata_error = 0;
+}
+
+/* Adds one bit of irdata */
+static void
+add_irdata_bit(struct wbcir_data *data, int set)
+{
+	if (data->irdata_count >= sizeof(data->irdata) * 8) {
+		data->irdata_error = 1;
+		return;
+	}
+
+	if (set)
+		__set_bit(data->irdata_count, data->irdata);
+	data->irdata_count++;
+}
+
+/* Gets count bits of irdata */
+static u16
+get_bits(struct wbcir_data *data, int count)
+{
+	u16 val = 0x0;
+
+	if (data->irdata_count - data->irdata_off < count) {
+		data->irdata_error = 1;
+		return 0x0;
+	}
+
+	while (count > 0) {
+		val <<= 1;
+		if (test_bit(data->irdata_off, data->irdata))
+			val |= 0x1;
+		count--;
+		data->irdata_off++;
+	}
+
+	return val;
+}
+
+/* Reads 16 cells and converts them to a byte */
+static u8
+wbcir_rc6cells_to_byte(struct wbcir_data *data)
+{
+	u16 raw = get_bits(data, 16);
+	u8 val = 0x00;
+	int bit;
+
+	for (bit = 0; bit < 8; bit++) {
+		switch (raw & 0x03) {
+		case 0x01:
+			break;
+		case 0x02:
+			val |= (0x01 << bit);
+			break;
+		default:
+			data->irdata_error = 1;
+			break;
+		}
+		raw >>= 2;
+	}
+
+	return val;
+}
+
+/* Decodes a number of bits from raw RC5 data */
+static u8
+wbcir_get_rc5bits(struct wbcir_data *data, unsigned int count)
+{
+	u16 raw = get_bits(data, count * 2);
+	u8 val = 0x00;
+	int bit;
+
+	for (bit = 0; bit < count; bit++) {
+		switch (raw & 0x03) {
+		case 0x01:
+			val |= (0x01 << bit);
+			break;
+		case 0x02:
+			break;
+		default:
+			data->irdata_error = 1;
+			break;
+		}
+		raw >>= 2;
+	}
+
+	return val;
+}
+
+static void
+wbcir_parse_rc6(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Normal bits are manchester coded as follows:
+	 * cell0 + cell1 = logic "0"
+	 * cell1 + cell0 = logic "1"
+	 *
+	 * The IR pulse has the following components:
+	 *
+	 * Leader		- 6 * cell1 - discarded
+	 * Gap    		- 2 * cell0 - discarded
+	 * Start bit		- Normal Coding - always "1"
+	 * Mode Bit 2 - 0	- Normal Coding
+	 * Toggle bit		- Normal Coding with double bit time,
+	 *			  e.g. cell0 + cell0 + cell1 + cell1
+	 *			  means logic "0".
+	 *
+	 * The rest depends on the mode, the following modes are known:
+	 *
+	 * MODE 0:
+	 *  Address Bit 7 - 0	- Normal Coding
+	 *  Command Bit 7 - 0	- Normal Coding
+	 *
+	 * MODE 6:
+	 *  The above Toggle Bit is used as a submode bit, 0 = A, 1 = B.
+	 *  Submode B is for pointing devices, only remotes using submode A
+	 *  are supported.
+	 *
+	 *  Customer range bit	- 0 => Customer = 7 bits, 0...127
+	 *                        1 => Customer = 15 bits, 32768...65535
+	 *  Customer Bits	- Normal Coding
+	 *
+	 *  Customer codes are allocated by Philips. The rest of the bits
+	 *  are customer dependent. The following is commonly used (and the
+	 *  only supported config):
+	 *
+	 *  Toggle Bit		- Normal Coding
+	 *  Address Bit 6 - 0	- Normal Coding
+	 *  Command Bit 7 - 0	- Normal Coding
+	 *
+	 * All modes are followed by at least 6 * cell0.
+	 *
+	 * MODE 0 msglen:
+	 *  1 * 2 (start bit) + 3 * 2 (mode) + 2 * 2 (toggle) +
+	 *  8 * 2 (address) + 8 * 2 (command) =
+	 *  44 cells
+	 *
+	 * MODE 6A msglen:
+	 *  1 * 2 (start bit) + 3 * 2 (mode) + 2 * 2 (submode) +
+	 *  1 * 2 (customer range bit) + 7/15 * 2 (customer bits) +
+	 *  1 * 2 (toggle bit) + 7 * 2 (address) + 8 * 2 (command) =
+	 *  60 - 76 cells
+	 */
+	u8 mode;
+	u8 toggle;
+	u16 customer = 0x0;
+	u8 address;
+	u8 command;
+	u32 scancode;
+
+	/* Leader mark */
+	while (get_bits(data, 1) && !data->irdata_error)
+		/* Do nothing */;
+
+	/* Leader space */
+	if (get_bits(data, 1)) {
+		dev_dbg(dev, "RC6 - Invalid leader space\n");
+		return;
+	}
+
+	/* Start bit */
+	if (get_bits(data, 2) != 0x02) {
+		dev_dbg(dev, "RC6 - Invalid start bit\n");
+		return;
+	}
+
+	/* Mode */
+	mode = get_bits(data, 6);
+	switch (mode) {
+	case 0x15: /* 010101 = b000 */
+		mode = 0;
+		break;
+	case 0x29: /* 101001 = b110 */
+		mode = 6;
+		break;
+	default:
+		dev_dbg(dev, "RC6 - Invalid mode\n");
+		return;
+	}
+
+	/* Toggle bit / Submode bit */
+	toggle = get_bits(data, 4);
+	switch (toggle) {
+	case 0x03:
+		toggle = 0;
+		break;
+	case 0x0C:
+		toggle = 1;
+		break;
+	default:
+		dev_dbg(dev, "RC6 - Toggle bit error\n");
+		break;
+	}
+
+	/* Customer */
+	if (mode == 6) {
+		if (toggle != 0) {
+			dev_dbg(dev, "RC6B - Not Supported\n");
+			return;
+		}
+
+		customer = wbcir_rc6cells_to_byte(data);
+
+		if (customer & 0x80) {
+			/* 15 bit customer value */
+			customer <<= 8;
+			customer |= wbcir_rc6cells_to_byte(data);
+		}
+	}
+
+	/* Address */
+	address = wbcir_rc6cells_to_byte(data);
+	if (mode == 6) {
+		toggle = address >> 7;
+		address &= 0x7F;
+	}
+
+	/* Command */
+	command = wbcir_rc6cells_to_byte(data);
+
+	/* Create scancode */
+	scancode =  command;
+	scancode |= address << 8;
+	scancode |= customer << 16;
+
+	/* Last sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "RC6 - Cell error(s)\n");
+		return;
+	}
+
+	dev_dbg(dev, "IR-RC6 ad 0x%02X cm 0x%02X cu 0x%04X "
+		"toggle %u mode %u scan 0x%08X\n",
+		address,
+		command,
+		customer,
+		(unsigned int)toggle,
+		(unsigned int)mode,
+		scancode);
+
+	wbcir_keydown(data, scancode, toggle);
+}
+
+static void
+wbcir_parse_rc5(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Bits are manchester coded as follows:
+	 * cell1 + cell0 = logic "0"
+	 * cell0 + cell1 = logic "1"
+	 * (i.e. the reverse of RC6)
+	 *
+	 * Start bit 1		- "1" - discarded
+	 * Start bit 2		- Must be inverted to get command bit 6
+	 * Toggle bit
+	 * Address Bit 4 - 0
+	 * Command Bit 5 - 0
+	 */
+	u8 toggle;
+	u8 address;
+	u8 command;
+	u32 scancode;
+
+	/* Start bit 1 */
+	if (!get_bits(data, 1)) {
+		dev_dbg(dev, "RC5 - Invalid start bit\n");
+		return;
+	}
+
+	/* Start bit 2 */
+	if (!wbcir_get_rc5bits(data, 1))
+		command = 0x40;
+	else
+		command = 0x00;
+
+	toggle   = wbcir_get_rc5bits(data, 1);
+	address  = wbcir_get_rc5bits(data, 5);
+	command |= wbcir_get_rc5bits(data, 6);
+	scancode = address << 7 | command;
+
+	/* Last sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "RC5 - Invalid message\n");
+		return;
+	}
+
+	dev_dbg(dev, "IR-RC5 ad %u cm %u t %u s %u\n",
+		(unsigned int)address,
+		(unsigned int)command,
+		(unsigned int)toggle,
+		(unsigned int)scancode);
+
+	wbcir_keydown(data, scancode, toggle);
+}
+
+static void
+wbcir_parse_nec(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Each bit represents 560 us.
+	 *
+	 * Leader		- 9 ms burst
+	 * Gap			- 4.5 ms silence
+	 * Address1 bit 0 - 7	- Address 1
+	 * Address2 bit 0 - 7	- Address 2
+	 * Command1 bit 0 - 7	- Command 1
+	 * Command2 bit 0 - 7	- Command 2
+	 *
+	 * Note the bit order!
+	 *
+	 * With the old NEC protocol, Address2 was the inverse of Address1
+	 * and Command2 was the inverse of Command1 and were used as
+	 * an error check.
+	 *
+	 * With NEC extended, Address1 is the LSB of the Address and
+	 * Address2 is the MSB, Command parsing remains unchanged.
+	 *
+	 * A repeat message is coded as:
+	 * Leader		- 9 ms burst
+	 * Gap			- 2.25 ms silence
+	 * Repeat		- 560 us active
+	 */
+	u8 address1;
+	u8 address2;
+	u8 command1;
+	u8 command2;
+	u16 address;
+	u32 scancode;
+
+	/* Leader mark */
+	while (get_bits(data, 1) && !data->irdata_error)
+		/* Do nothing */;
+
+	/* Leader space */
+	if (get_bits(data, 4)) {
+		dev_dbg(dev, "NEC - Invalid leader space\n");
+		return;
+	}
+
+	/* Repeat? */
+	if (get_bits(data, 1)) {
+		if (!data->keypressed) {
+			dev_dbg(dev, "NEC - Stray repeat message\n");
+			return;
+		}
+
+		dev_dbg(dev, "IR-NEC repeat s %u\n",
+			(unsigned int)data->last_scancode);
+
+		wbcir_keydown(data, data->last_scancode, data->last_toggle);
+		return;
+	}
+
+	/* Remaining leader space */
+	if (get_bits(data, 3)) {
+		dev_dbg(dev, "NEC - Invalid leader space\n");
+		return;
+	}
+
+	address1  = bitrev8(get_bits(data, 8));
+	address2  = bitrev8(get_bits(data, 8));
+	command1  = bitrev8(get_bits(data, 8));
+	command2  = bitrev8(get_bits(data, 8));
+
+	/* Sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "NEC - Invalid message\n");
+		return;
+	}
+
+	/* Check command validity */
+	if (command1 != ~command2) {
+		dev_dbg(dev, "NEC - Command bytes mismatch\n");
+		return;
+	}
+
+	/* Check for extended NEC protocol */
+	address = address1;
+	if (address1 != ~address2)
+		address |= address2 << 8;
+
+	scancode = address << 8 | command1;
+
+	dev_dbg(dev, "IR-NEC ad %u cm %u s %u\n",
+		(unsigned int)address,
+		(unsigned int)command1,
+		(unsigned int)scancode);
+
+	wbcir_keydown(data, scancode, !data->last_toggle);
+}
+
+
+
+/*****************************************************************************
+ *
+ * INTERRUPT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static irqreturn_t
+wbcir_irq_handler(int irqno, void *cookie)
+{
+	struct pnp_dev *device = cookie;
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	struct device *dev = &device->dev;
+	u8 status;
+	unsigned long flags;
+	u8 irdata[16];
+	int i;
+	unsigned int hw;
+
+	spin_lock_irqsave(&wbcir_lock, flags);
+
+	wbcir_select_bank(data, WBCIR_BANK_0);
+
+	status = inb(data->sbase + WBCIR_REG_SP3_EIR);
+
+	if (!(status & (WBCIR_IRQ_RX | WBCIR_IRQ_ERR))) {
+		spin_unlock_irqrestore(&wbcir_lock, flags);
+		return IRQ_NONE;
+	}
+
+	if (status & WBCIR_IRQ_ERR)
+		data->irdata_error = 1;
+
+	if (!(status & WBCIR_IRQ_RX))
+		goto out;
+
+	/* Since RXHDLEV is set, at least 16 bytes are in the FIFO */
+	insb(data->sbase + WBCIR_REG_SP3_RXDATA, &irdata[0], 8);
+	insb(data->sbase + WBCIR_REG_SP3_RXDATA, &irdata[8], 8);
+
+	for (i = 0; i < sizeof(data); i++) {
+		hw = hweight8(irdata[i]);
+		if (hw > 4)
+			add_irdata_bit(data, 0);
+		else
+			add_irdata_bit(data, 1);
+
+		if (hw == 8)
+			data->idle_count++;
+		else
+			data->idle_count = 0;
+	}
+
+	if (data->idle_count > WBCIR_MAX_IDLE_BYTES) {
+		/* Drain the FIFO */
+		while (inb(data->sbase + WBCIR_REG_SP3_LSR) & WBCIR_RX_AVAIL)
+			inb(data->sbase + WBCIR_REG_SP3_RXDATA);
+
+		/* And set RXINACTIVE */
+		outb(WBCIR_RX_DISABLE, data->sbase + WBCIR_REG_SP3_ASCR);
+
+		dev_dbg(dev, "IRDATA:\n");
+		for (i = 0; i < data->irdata_count; i += BITS_PER_LONG)
+			dev_dbg(dev, "0x%08lX\n", data->irdata[i/BITS_PER_LONG]);
+
+		switch (protocol) {
+		case IR_PROTOCOL_RC5:
+			wbcir_parse_rc5(dev, data);
+			break;
+		case IR_PROTOCOL_RC6:
+			wbcir_parse_rc6(dev, data);
+			break;
+		case IR_PROTOCOL_NEC:
+			wbcir_parse_nec(dev, data);
+			break;
+		}
+
+		wbcir_reset_irdata(data);
+		data->idle_count = 0;
+	}
+
+out:
+	spin_unlock_irqrestore(&wbcir_lock, flags);
+	return IRQ_HANDLED;
+}
+
+
+
+/*****************************************************************************
+ *
+ * SUSPEND/RESUME FUNCTIONS
+ *
+ *****************************************************************************/
+
+static void
+wbcir_shutdown(struct pnp_dev *device)
+{
+	struct device *dev = &device->dev;
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	int do_wake = 1;
+	u8 match[11];
+	u8 mask[11];
+	u8 rc6_csl = 0;
+	int i;
+
+	memset(match, 0, sizeof(match));
+	memset(mask, 0, sizeof(mask));
+
+	if (wake_sc == INVALID_SCANCODE || !device_may_wakeup(dev)) {
+		do_wake = 0;
+		goto finish;
+	}
+
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+		if (wake_sc > 0xFFF) {
+			do_wake = 0;
+			dev_err(dev, "RC5 - Invalid wake scancode\n");
+			break;
+		}
+
+		/* Mask = 13 bits, ex toggle */
+		mask[0] = 0xFF;
+		mask[1] = 0x17;
+
+		match[0]  = (wake_sc & 0x003F);      /* 6 command bits */
+		match[0] |= (wake_sc & 0x0180) >> 1; /* 2 address bits */
+		match[1]  = (wake_sc & 0x0E00) >> 9; /* 3 address bits */
+		if (!(wake_sc & 0x0040))             /* 2nd start bit  */
+			match[1] |= 0x10;
+
+		break;
+
+	case IR_PROTOCOL_NEC:
+		if (wake_sc > 0xFFFFFF) {
+			do_wake = 0;
+			dev_err(dev, "NEC - Invalid wake scancode\n");
+			break;
+		}
+
+		mask[0] = mask[1] = mask[2] = mask[3] = 0xFF;
+
+		match[1] = bitrev8((wake_sc & 0xFF));
+		match[0] = ~match[1];
+
+		match[3] = bitrev8((wake_sc & 0xFF00) >> 8);
+		if (wake_sc > 0xFFFF)
+			match[2] = bitrev8((wake_sc & 0xFF0000) >> 16);
+		else
+			match[2] = ~match[3];
+
+		break;
+
+	case IR_PROTOCOL_RC6:
+
+		if (wake_rc6mode == 0) {
+			if (wake_sc > 0xFFFF) {
+				do_wake = 0;
+				dev_err(dev, "RC6 - Invalid wake scancode\n");
+				break;
+			}
+
+			/* Command */
+			match[0] = wbcir_to_rc6cells(wake_sc >>  0);
+			mask[0]  = 0xFF;
+			match[1] = wbcir_to_rc6cells(wake_sc >>  4);
+			mask[1]  = 0xFF;
+
+			/* Address */
+			match[2] = wbcir_to_rc6cells(wake_sc >>  8);
+			mask[2]  = 0xFF;
+			match[3] = wbcir_to_rc6cells(wake_sc >> 12);
+			mask[3]  = 0xFF;
+
+			/* Header */
+			match[4] = 0x50; /* mode1 = mode0 = 0, ignore toggle */
+			mask[4]  = 0xF0;
+			match[5] = 0x09; /* start bit = 1, mode2 = 0 */
+			mask[5]  = 0x0F;
+
+			rc6_csl = 44;
+
+		} else if (wake_rc6mode == 6) {
+			i = 0;
+
+			/* Command */
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  0);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  4);
+			mask[i++] = 0xFF;
+
+			/* Address + Toggle */
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  8);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 12);
+			mask[i++] = 0x3F;
+
+			/* Customer bits 7 - 0 */
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 16);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 20);
+			mask[i++] = 0xFF;
+
+			if (wake_sc & 0x80000000) {
+				/* Customer range bit and bits 15 - 8 */
+				match[i]  = wbcir_to_rc6cells(wake_sc >> 24);
+				mask[i++] = 0xFF;
+				match[i]  = wbcir_to_rc6cells(wake_sc >> 28);
+				mask[i++] = 0xFF;
+				rc6_csl = 76;
+			} else if (wake_sc <= 0x007FFFFF) {
+				rc6_csl = 60;
+			} else {
+				do_wake = 0;
+				dev_err(dev, "RC6 - Invalid wake scancode\n");
+				break;
+			}
+
+			/* Header */
+			match[i]  = 0x93; /* mode1 = mode0 = 1, submode = 0 */
+			mask[i++] = 0xFF;
+			match[i]  = 0x0A; /* start bit = 1, mode2 = 1 */
+			mask[i++] = 0x0F;
+
+		} else {
+			do_wake = 0;
+			dev_err(dev, "RC6 - Invalid wake mode\n");
+		}
+
+		break;
+
+	default:
+		do_wake = 0;
+		break;
+	}
+
+finish:
+	if (do_wake) {
+		/* Set compare and compare mask */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX,
+			       WBCIR_REGSEL_COMPARE | WBCIR_REG_ADDR0,
+			       0x3F);
+		outsb(data->wbase + WBCIR_REG_WCEIR_DATA, match, 11);
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX,
+			       WBCIR_REGSEL_MASK | WBCIR_REG_ADDR0,
+			       0x3F);
+		outsb(data->wbase + WBCIR_REG_WCEIR_DATA, mask, 11);
+
+		/* RC6 Compare String Len */
+		outb(rc6_csl, data->wbase + WBCIR_REG_WCEIR_CSL);
+
+		/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+		/* Clear BUFF_EN, Clear END_EN, Set MATCH_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x01, 0x07);
+
+		/* Set CEIR_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x01, 0x01);
+
+	} else {
+		/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+		/* Clear CEIR_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+	}
+
+	/* Disable interrupts */
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+}
+
+static int
+wbcir_suspend(struct pnp_dev *device, pm_message_t state)
+{
+	wbcir_shutdown(device);
+	return 0;
+}
+
+static int
+wbcir_resume(struct pnp_dev *device)
+{
+	struct wbcir_data *data = pnp_get_drvdata(device);
+
+	/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	/* Clear CEIR_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+
+	/* Enable interrupts */
+	wbcir_reset_irdata(data);
+	outb(WBCIR_IRQ_RX | WBCIR_IRQ_ERR, data->sbase + WBCIR_REG_SP3_IER);
+
+	return 0;
+}
+
+
+
+/*****************************************************************************
+ *
+ * SETUP/INIT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static ssize_t
+wbcir_show_last_scancode(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	struct pnp_dev *device = container_of(dev, struct pnp_dev, dev);
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	return sprintf(buf, "0x%08X\n", data->last_scancode);
+}
+
+static struct device_attribute dev_attr_last_scancode = {
+	.attr = {
+		.name = "last_scancode",
+		.mode = 0444,
+	},
+	.show = wbcir_show_last_scancode,
+	.store = NULL,
+
+};
+
+static struct attribute *wbcir_attributes[] = {
+	&dev_attr_last_scancode.attr,
+	NULL,
+};
+
+static struct attribute_group wbcir_attribute_group = {
+	.attrs = wbcir_attributes,
+};
+
+static void
+wbcir_cfg_ceir(struct wbcir_data *data)
+{
+	u8 tmp;
+
+	/* Set PROT_SEL, RX_INV, Clear CEIR_EN (needed for the led) */
+	tmp = protocol << 4;
+	if (invert)
+		tmp |= 0x08;
+	outb(tmp, data->wbase + WBCIR_REG_WCEIR_CTL);
+
+	/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+	/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	/* Set RC5 cell time to correspond to 36 kHz */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CFG1, 0x4A, 0x7F);
+
+	/* Set IRTX_INV */
+	if (invert)
+		outb(0x04, data->ebase + WBCIR_REG_ECEIR_CCTL);
+	else
+		outb(0x00, data->ebase + WBCIR_REG_ECEIR_CCTL);
+
+	/*
+	 * Clear IR LED, set SP3 clock to 24Mhz
+	 * set SP3_IRRX_SW to binary 01, helpfully not documented
+	 */
+	outb(0x10, data->ebase + WBCIR_REG_ECEIR_CTS);
+}
+
+static int
+wbcir_probe(struct pnp_dev *device, const struct pnp_device_id *dev_id)
+{
+	struct device *dev = &device->dev;
+	struct wbcir_data *data;
+	int err;
+
+	if (!(pnp_port_len(device, 0) == EHFUNC_IOMEM_LEN &&
+	      pnp_port_len(device, 1) == WAKEUP_IOMEM_LEN &&
+	      pnp_port_len(device, 2) == SP_IOMEM_LEN)) {
+		dev_err(dev, "Invalid resources\n");
+		return -ENODEV;
+	}
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data) {
+		err = -ENOMEM;
+		goto exit;
+	}
+
+	pnp_set_drvdata(device, data);
+
+	data->ebase = pnp_port_start(device, 0);
+	data->wbase = pnp_port_start(device, 1);
+	data->sbase = pnp_port_start(device, 2);
+	data->irq = pnp_irq(device, 0);
+
+	if (data->wbase == 0 || data->ebase == 0 ||
+	    data->sbase == 0 || data->irq == 0) {
+		err = -ENODEV;
+		dev_err(dev, "Invalid resources\n");
+		goto exit_free_data;
+	}
+
+	dev_info(&device->dev, "Found device "
+		 "(w: 0x%lX, e: 0x%lX, s: 0x%lX, i: %u)\n",
+		 data->wbase, data->ebase, data->sbase, data->irq);
+
+	if (!request_region(data->wbase, WAKEUP_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->wbase, data->wbase + WAKEUP_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_free_data;
+	}
+
+	if (!request_region(data->ebase, EHFUNC_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->ebase, data->ebase + EHFUNC_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_release_wbase;
+	}
+
+	if (!request_region(data->sbase, SP_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->sbase, data->sbase + SP_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_release_ebase;
+	}
+
+	err = request_irq(data->irq, wbcir_irq_handler,
+			  IRQF_DISABLED, DRVNAME, device);
+	if (err) {
+		dev_err(dev, "Failed to claim IRQ %u\n", data->irq);
+		err = -EBUSY;
+		goto exit_release_sbase;
+	}
+
+	led_trigger_register_simple("cir-tx", &data->txtrigger);
+	if (!data->txtrigger) {
+		err = -ENOMEM;
+		goto exit_free_irq;
+	}
+
+	led_trigger_register_simple("cir-rx", &data->rxtrigger);
+	if (!data->rxtrigger) {
+		err = -ENOMEM;
+		goto exit_unregister_txtrigger;
+	}
+
+	data->led.name = "cir::activity";
+	data->led.default_trigger = "cir-rx";
+	data->led.brightness_set = wbcir_led_brightness_set;
+	data->led.brightness_get = wbcir_led_brightness_get;
+	err = led_classdev_register(&device->dev, &data->led);
+	if (err)
+		goto exit_unregister_rxtrigger;
+
+	data->input_dev = input_allocate_device();
+	if (!data->input_dev) {
+		err = -ENOMEM;
+		goto exit_unregister_led;
+	}
+
+	data->input_dev->evbit[0] = BIT(EV_KEY);
+	data->input_dev->name = WBCIR_ACPI_NAME;
+	data->input_dev->phys = "wbcir/cir0";
+	data->input_dev->id.bustype = BUS_HOST;
+	data->input_dev->id.vendor  = PCI_VENDOR_ID_WINBOND;
+	data->input_dev->id.product = WBCIR_ID_FAMILY;
+	data->input_dev->id.version = WBCIR_ID_CHIP;
+	data->input_dev->getkeycode = wbcir_getkeycode;
+	data->input_dev->setkeycode = wbcir_setkeycode;
+	input_set_drvdata(data->input_dev, data);
+
+	err = input_register_device(data->input_dev);
+	if (err)
+		goto exit_free_input;
+
+	data->last_scancode = INVALID_SCANCODE;
+	err = sysfs_create_group(&device->dev.kobj, &wbcir_attribute_group);
+	if (err)
+		goto exit_unregister_input;
+
+	INIT_LIST_HEAD(&data->keytable);
+	setup_timer(&data->timer_keyup, wbcir_keyup, (unsigned long)data);
+
+	/* Load default keymaps */
+	if (protocol == IR_PROTOCOL_RC6) {
+		int i;
+		for (i = 0; i < ARRAY_SIZE(rc6_def_keymap); i++) {
+			err = wbcir_setkeycode(data->input_dev,
+					       (int)rc6_def_keymap[i].scancode,
+					       (int)rc6_def_keymap[i].keycode);
+			if (err)
+				goto exit_unregister_keys;
+		}
+	}
+
+	device_init_wakeup(&device->dev, 1);
+
+	wbcir_cfg_ceir(data);
+
+	/* Disable interrupts */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+
+	/* Enable extended mode */
+	wbcir_select_bank(data, WBCIR_BANK_2);
+	outb(WBCIR_EXT_ENABLE, data->sbase + WBCIR_REG_SP3_EXCR1);
+
+	/*
+	 * Configure baud generator, IR data will be sampled at
+	 * a bitrate of: (24Mhz * prescaler) / (divisor * 16).
+	 *
+	 * The ECIR registers include a flag to change the
+	 * 24Mhz clock freq to 48Mhz.
+	 */
+
+	/* prescaler 1.0, tx/rx fifo lvl 32 */
+	outb(0x35, data->sbase + WBCIR_REG_SP3_EXCR2);
+
+	/* Set baud divisor to generate one byte per bit/cell */
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+		outb(0xA7, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	case IR_PROTOCOL_RC6:
+		outb(0x53, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	case IR_PROTOCOL_NEC:
+		outb(0x69, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	}
+	outb(0x00, data->sbase + WBCIR_REG_SP3_BGDH);
+
+	/* Set CEIR mode */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(0xC0, data->sbase + WBCIR_REG_SP3_MCR);
+	inb(data->sbase + WBCIR_REG_SP3_LSR); /* Clear LSR */
+	inb(data->sbase + WBCIR_REG_SP3_MSR); /* Clear MSR */
+
+	/* Disable RX demod, run-length encoding/decoding, set freq span */
+	wbcir_select_bank(data, WBCIR_BANK_7);
+	outb(0x10, data->sbase + WBCIR_REG_SP3_RCCFG);
+
+	/* Disable timer */
+	wbcir_select_bank(data, WBCIR_BANK_4);
+	outb(0x00, data->sbase + WBCIR_REG_SP3_IRCR1);
+
+	/* Enable MSR interrupt, Clear AUX_IRX */
+	wbcir_select_bank(data, WBCIR_BANK_5);
+	outb(0x00, data->sbase + WBCIR_REG_SP3_IRCR2);
+
+	/* Disable CRC */
+	wbcir_select_bank(data, WBCIR_BANK_6);
+	outb(0x20, data->sbase + WBCIR_REG_SP3_IRCR3);
+
+	/* Set RX/TX (de)modulation freq, not really used */
+	wbcir_select_bank(data, WBCIR_BANK_7);
+	outb(0xF2, data->sbase + WBCIR_REG_SP3_IRRXDC);
+	outb(0x69, data->sbase + WBCIR_REG_SP3_IRTXMC);
+
+	/* Set invert and pin direction */
+	if (invert)
+		outb(0x10, data->sbase + WBCIR_REG_SP3_IRCFG4);
+	else
+		outb(0x00, data->sbase + WBCIR_REG_SP3_IRCFG4);
+
+	/* Set FIFO thresholds (RX = 16, TX = 7), reset RX/TX */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(0x97, data->sbase + WBCIR_REG_SP3_FCR);
+
+	/* Clear AUX status bits */
+	outb(0xE0, data->sbase + WBCIR_REG_SP3_ASCR);
+
+	/* Enable interrupts */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(WBCIR_IRQ_RX | WBCIR_IRQ_ERR, data->sbase + WBCIR_REG_SP3_IER);
+
+	return 0;
+
+exit_unregister_keys:
+	if (!list_empty(&data->keytable)) {
+		struct wbcir_keyentry *key;
+		struct wbcir_keyentry *keytmp;
+
+		list_for_each_entry_safe(key, keytmp, &data->keytable, list) {
+			list_del(&key->list);
+			kfree(key);
+		}
+	}
+exit_unregister_input:
+	input_unregister_device(data->input_dev);
+	/* Can't call input_free_device on an unregistered device */
+	data->input_dev = NULL;
+exit_free_input:
+	input_free_device(data->input_dev);
+exit_unregister_led:
+	led_classdev_unregister(&data->led);
+exit_unregister_rxtrigger:
+	led_trigger_unregister_simple(data->rxtrigger);
+exit_unregister_txtrigger:
+	led_trigger_unregister_simple(data->txtrigger);
+exit_free_irq:
+	free_irq(data->irq, device);
+exit_release_sbase:
+	release_region(data->sbase, SP_IOMEM_LEN);
+exit_release_ebase:
+	release_region(data->ebase, EHFUNC_IOMEM_LEN);
+exit_release_wbase:
+	release_region(data->wbase, WAKEUP_IOMEM_LEN);
+exit_free_data:
+	kfree(data);
+	pnp_set_drvdata(device, NULL);
+exit:
+	return err;
+}
+
+static void
+wbcir_remove(struct pnp_dev *device)
+{
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	struct wbcir_keyentry *key;
+	struct wbcir_keyentry *keytmp;
+
+	/* Disable interrupts */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+
+	del_timer_sync(&data->timer_keyup);
+
+	free_irq(data->irq, device);
+
+	/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+	/* Clear CEIR_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+
+	/* Clear BUFF_EN, END_EN, MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	sysfs_remove_group(&device->dev.kobj, &wbcir_attribute_group);
+
+	/* This will generate a keyup event if necessary */
+	input_unregister_device(data->input_dev);
+
+	led_trigger_unregister_simple(data->rxtrigger);
+	led_trigger_unregister_simple(data->txtrigger);
+	led_classdev_unregister(&data->led);
+
+	/* This is ok since &data->led isn't actually used */
+	wbcir_led_brightness_set(&data->led, LED_OFF);
+
+	release_region(data->wbase, WAKEUP_IOMEM_LEN);
+	release_region(data->ebase, EHFUNC_IOMEM_LEN);
+	release_region(data->sbase, SP_IOMEM_LEN);
+
+	list_for_each_entry_safe(key, keytmp, &data->keytable, list) {
+		list_del(&key->list);
+		kfree(key);
+	}
+
+	kfree(data);
+
+	pnp_set_drvdata(device, NULL);
+}
+
+static const struct pnp_device_id wbcir_ids[] = {
+	{ "WEC1022", 0 },
+	{ "", 0 }
+};
+MODULE_DEVICE_TABLE(pnp, wbcir_ids);
+
+static struct pnp_driver wbcir_driver = {
+	.name     = WBCIR_ACPI_NAME,
+	.id_table = wbcir_ids,
+	.probe    = wbcir_probe,
+	.remove   = wbcir_remove,
+	.suspend  = wbcir_suspend,
+	.resume   = wbcir_resume,
+	.shutdown = wbcir_shutdown
+};
+
+static int __init
+wbcir_init(void)
+{
+	int ret;
+
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+	case IR_PROTOCOL_NEC:
+	case IR_PROTOCOL_RC6:
+		break;
+	default:
+		printk(KERN_ERR DRVNAME ": Invalid protocol argument\n");
+		return -EINVAL;
+	}
+
+	ret = pnp_register_driver(&wbcir_driver);
+	if (ret)
+		printk(KERN_ERR DRVNAME ": Unable to register driver\n");
+
+	return ret;
+}
+
+static void __exit
+wbcir_exit(void)
+{
+	pnp_unregister_driver(&wbcir_driver);
+}
+
+MODULE_AUTHOR("David Härdeman <david@hardeman.nu>");
+MODULE_DESCRIPTION("Winbond SuperI/O Consumer IR Driver");
+MODULE_LICENSE("GPL");
+
+module_init(wbcir_init);
+module_exit(wbcir_exit);
+
+

-- 
David Härdeman

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware
@ 2009-08-09  9:56   ` david
  0 siblings, 0 replies; 16+ messages in thread
From: david @ 2009-08-09  9:56 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-input, jbarnes, akpm, bjorn.helgaas, randy.dunlap, david

[-- Attachment #1: add-a-driver-for-the-winbond-wpcd376i-cir-functionality --]
[-- Type: TEXT/PLAIN, Size: 48835 bytes --]

Add a driver for the the Consumer IR (CIR) functionality of the Winbond
WPCD376I chipset (found on e.g. Intel DG45FC motherboards).

Changes since the last version: converted from an ACPI to a PNP driver
using a patch provided by Bjorn Helgaas <bjorn.helgaas@hp.com>.

Signed-off-by: David Härdeman <david@hardeman.nu>
Reviewed-by: Jesse Barnes <jbarnes@virtuousgeek.org>

Index: linux-2.6/MAINTAINERS
===================================================================
--- linux-2.6.orig/MAINTAINERS	2009-07-25 21:18:57.000000000 +0200
+++ linux-2.6/MAINTAINERS	2009-07-25 21:19:28.000000000 +0200
@@ -6448,6 +6448,12 @@
 S:	Maintained
 F:	drivers/scsi/wd7000.c
 
+WINBOND CIR DRIVER
+P:	David Härdeman
+M:	david@hardeman.nu
+S:	Maintained
+F:	drivers/input/misc/winbond-cir.c
+
 WIMAX STACK
 P:	Inaky Perez-Gonzalez
 M:	inaky.perez-gonzalez@intel.com
Index: linux-2.6/drivers/input/misc/Kconfig
===================================================================
--- linux-2.6.orig/drivers/input/misc/Kconfig	2009-07-25 21:18:57.000000000 +0200
+++ linux-2.6/drivers/input/misc/Kconfig	2009-08-07 21:45:41.000000000 +0200
@@ -222,6 +222,22 @@
 	  To compile this driver as a module, choose M here: the
 	  module will be called sgi_btns.
 
+config INPUT_WINBOND_CIR
+	tristate "Winbond IR remote control"
+	depends on X86 && ACPI
+	select LEDS_CLASS
+	select BITREVERSE
+	help
+	  Say Y here if you want to use the IR remote functionality found
+	  in some Winbond SuperI/O chips. Currently only the WPCD376I
+	  chip is supported (included in some Intel Media series motherboards).
+
+	  IR Receive and wake-on-IR from suspend and power-off is currently
+	  supported.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called winbond_cir.
+
 config HP_SDC_RTC
 	tristate "HP SDC Real Time Clock"
 	depends on (GSC || HP300) && SERIO
Index: linux-2.6/drivers/input/misc/Makefile
===================================================================
--- linux-2.6.orig/drivers/input/misc/Makefile	2009-07-25 21:18:57.000000000 +0200
+++ linux-2.6/drivers/input/misc/Makefile	2009-07-25 21:19:28.000000000 +0200
@@ -19,6 +19,7 @@
 obj-$(CONFIG_INPUT_PCSPKR)		+= pcspkr.o
 obj-$(CONFIG_INPUT_POWERMATE)		+= powermate.o
 obj-$(CONFIG_INPUT_RB532_BUTTON)	+= rb532_button.o
+obj-$(CONFIG_INPUT_WINBOND_CIR)		+= winbond-cir.o
 obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER)	+= rotary_encoder.o
 obj-$(CONFIG_INPUT_SGI_BTNS)		+= sgi_btns.o
 obj-$(CONFIG_INPUT_SPARCSPKR)		+= sparcspkr.o
Index: linux-2.6/drivers/input/misc/winbond-cir.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/drivers/input/misc/winbond-cir.c	2009-07-25 21:19:28.000000000 +0200
@@ -0,0 +1,1644 @@
+/*
+ *  winbond-cir.c - Driver for the Consumer IR functionality of Winbond
+ *                  SuperI/O chips.
+ *
+ *  Currently supports the Winbond WPCD376i chip (PNP id WEC1022), but
+ *  could probably support others (Winbond WEC102X, NatSemi, etc)
+ *  with minor modifications.
+ *
+ *  Original Author: David Härdeman <david@hardeman.nu>
+ *     Copyright (C) 2009 David Härdeman <david@hardeman.nu>
+ *
+ *  Dedicated to Matilda, my newborn daughter, without whose loving attention
+ *  this driver would have been finished in half the time and with a fraction
+ *  of the bugs.
+ *
+ *  Written using:
+ *    o Winbond WPCD376I datasheet helpfully provided by Jesse Barnes at Intel
+ *    o NatSemi PC87338/PC97338 datasheet (for the serial port stuff)
+ *    o DSDT dumps
+ *
+ *  Supported features:
+ *    o RC6
+ *    o Wake-On-CIR functionality
+ *
+ *  To do:
+ *    o Test NEC and RC5
+ *
+ *  Left as an exercise for the reader:
+ *    o Learning (I have neither the hardware, nor the need)
+ *    o IR Transmit (ibid)
+ *
+ *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/pnp.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/pci_ids.h>
+#include <linux/io.h>
+#include <linux/bitrev.h>
+#include <linux/bitops.h>
+
+#define DRVNAME "winbond-cir"
+
+/* CEIR Wake-Up Registers, relative to data->wbase                      */
+#define WBCIR_REG_WCEIR_CTL	0x03 /* CEIR Receiver Control		*/
+#define WBCIR_REG_WCEIR_STS	0x04 /* CEIR Receiver Status		*/
+#define WBCIR_REG_WCEIR_EV_EN	0x05 /* CEIR Receiver Event Enable	*/
+#define WBCIR_REG_WCEIR_CNTL	0x06 /* CEIR Receiver Counter Low	*/
+#define WBCIR_REG_WCEIR_CNTH	0x07 /* CEIR Receiver Counter High	*/
+#define WBCIR_REG_WCEIR_INDEX	0x08 /* CEIR Receiver Index		*/
+#define WBCIR_REG_WCEIR_DATA	0x09 /* CEIR Receiver Data		*/
+#define WBCIR_REG_WCEIR_CSL	0x0A /* CEIR Re. Compare Strlen		*/
+#define WBCIR_REG_WCEIR_CFG1	0x0B /* CEIR Re. Configuration 1	*/
+#define WBCIR_REG_WCEIR_CFG2	0x0C /* CEIR Re. Configuration 2	*/
+
+/* CEIR Enhanced Functionality Registers, relative to data->ebase       */
+#define WBCIR_REG_ECEIR_CTS	0x00 /* Enhanced IR Control Status	*/
+#define WBCIR_REG_ECEIR_CCTL	0x01 /* Infrared Counter Control	*/
+#define WBCIR_REG_ECEIR_CNT_LO	0x02 /* Infrared Counter LSB		*/
+#define WBCIR_REG_ECEIR_CNT_HI	0x03 /* Infrared Counter MSB		*/
+#define WBCIR_REG_ECEIR_IREM	0x04 /* Infrared Emitter Status		*/
+
+/* SP3 Banked Registers, relative to data->sbase                        */
+#define WBCIR_REG_SP3_BSR	0x03 /* Bank Select, all banks		*/
+				      /* Bank 0				*/
+#define WBCIR_REG_SP3_RXDATA	0x00 /* FIFO RX data (r)		*/
+#define WBCIR_REG_SP3_TXDATA	0x00 /* FIFO TX data (w)		*/
+#define WBCIR_REG_SP3_IER	0x01 /* Interrupt Enable		*/
+#define WBCIR_REG_SP3_EIR	0x02 /* Event Identification (r)	*/
+#define WBCIR_REG_SP3_FCR	0x02 /* FIFO Control (w)		*/
+#define WBCIR_REG_SP3_MCR	0x04 /* Mode Control			*/
+#define WBCIR_REG_SP3_LSR	0x05 /* Link Status			*/
+#define WBCIR_REG_SP3_MSR	0x06 /* Modem Status			*/
+#define WBCIR_REG_SP3_ASCR	0x07 /* Aux Status and Control		*/
+				      /* Bank 2				*/
+#define WBCIR_REG_SP3_BGDL	0x00 /* Baud Divisor LSB		*/
+#define WBCIR_REG_SP3_BGDH	0x01 /* Baud Divisor MSB		*/
+#define WBCIR_REG_SP3_EXCR1	0x02 /* Extended Control 1		*/
+#define WBCIR_REG_SP3_EXCR2	0x04 /* Extended Control 2		*/
+#define WBCIR_REG_SP3_TXFLV	0x06 /* TX FIFO Level			*/
+#define WBCIR_REG_SP3_RXFLV	0x07 /* RX FIFO Level			*/
+				      /* Bank 3				*/
+#define WBCIR_REG_SP3_MRID	0x00 /* Module Identification		*/
+#define WBCIR_REG_SP3_SH_LCR	0x01 /* LCR Shadow			*/
+#define WBCIR_REG_SP3_SH_FCR	0x02 /* FCR Shadow			*/
+				      /* Bank 4				*/
+#define WBCIR_REG_SP3_IRCR1	0x02 /* Infrared Control 1		*/
+				      /* Bank 5				*/
+#define WBCIR_REG_SP3_IRCR2	0x04 /* Infrared Control 2		*/
+				      /* Bank 6				*/
+#define WBCIR_REG_SP3_IRCR3	0x00 /* Infrared Control 3		*/
+#define WBCIR_REG_SP3_SIR_PW	0x02 /* SIR Pulse Width		*/
+				      /* Bank 7				*/
+#define WBCIR_REG_SP3_IRRXDC	0x00 /* IR RX Demod Control		*/
+#define WBCIR_REG_SP3_IRTXMC	0x01 /* IR TX Mod Control		*/
+#define WBCIR_REG_SP3_RCCFG	0x02 /* CEIR Config			*/
+#define WBCIR_REG_SP3_IRCFG1	0x04 /* Infrared Config 1		*/
+#define WBCIR_REG_SP3_IRCFG4	0x07 /* Infrared Config 4		*/
+
+/*
+ * Magic values follow
+ */
+
+/* No interrupts for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_NONE		0x00
+/* RX data bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_RX		0x01
+/* Over/Under-flow bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
+#define WBCIR_IRQ_ERR		0x04
+/* Led enable/disable bit for WBCIR_REG_ECEIR_CTS */
+#define WBCIR_LED_ENABLE	0x80
+/* RX data available bit for WBCIR_REG_SP3_LSR */
+#define WBCIR_RX_AVAIL		0x01
+/* RX disable bit for WBCIR_REG_SP3_ASCR */
+#define WBCIR_RX_DISABLE	0x20
+/* Extended mode enable bit for WBCIR_REG_SP3_EXCR1 */
+#define WBCIR_EXT_ENABLE	0x01
+/* Select compare register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */
+#define WBCIR_REGSEL_COMPARE	0x10
+/* Select mask register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */
+#define WBCIR_REGSEL_MASK	0x20
+/* Starting address of selected register in WBCIR_REG_WCEIR_INDEX */
+#define WBCIR_REG_ADDR0		0x00
+
+/* Valid banks for the SP3 UART */
+enum wbcir_bank {
+	WBCIR_BANK_0          = 0x00,
+	WBCIR_BANK_1          = 0x80,
+	WBCIR_BANK_2          = 0xE0,
+	WBCIR_BANK_3          = 0xE4,
+	WBCIR_BANK_4          = 0xE8,
+	WBCIR_BANK_5          = 0xEC,
+	WBCIR_BANK_6          = 0xF0,
+	WBCIR_BANK_7          = 0xF4,
+};
+
+/* Supported IR Protocols */
+enum wbcir_protocol {
+	IR_PROTOCOL_RC5          = 0x0,
+	IR_PROTOCOL_NEC          = 0x1,
+	IR_PROTOCOL_RC6          = 0x2,
+};
+
+/* Misc */
+#define WBCIR_ACPI_NAME	"Winbond CIR"
+#define WBCIR_ID_FAMILY          0xF1 /* Family ID for the WPCD376I	*/
+#define	WBCIR_ID_CHIP            0x04 /* Chip ID for the WPCD376I	*/
+#define IR_KEYPRESS_TIMEOUT       250 /* FIXME: should be per-protocol? */
+#define INVALID_SCANCODE   0x7FFFFFFF /* Invalid with all protos	*/
+#define WAKEUP_IOMEM_LEN         0x10 /* Wake-Up I/O Reg Len		*/
+#define EHFUNC_IOMEM_LEN         0x10 /* Enhanced Func I/O Reg Len	*/
+#define SP_IOMEM_LEN             0x08 /* Serial Port 3 (IR) Reg Len	*/
+#define WBCIR_MAX_IDLE_BYTES       10
+
+static DEFINE_SPINLOCK(wbcir_lock);
+static DEFINE_RWLOCK(keytable_lock);
+
+struct wbcir_key {
+	u32 scancode;
+	unsigned int keycode;
+};
+
+struct wbcir_keyentry {
+	struct wbcir_key key;
+	struct list_head list;
+};
+
+static struct wbcir_key rc6_def_keymap[] = {
+	{ 0x800F0400, KEY_0			},
+	{ 0x800F0401, KEY_1			},
+	{ 0x800F0402, KEY_2			},
+	{ 0x800F0403, KEY_3			},
+	{ 0x800F0404, KEY_4			},
+	{ 0x800F0405, KEY_5			},
+	{ 0x800F0406, KEY_6			},
+	{ 0x800F0407, KEY_7			},
+	{ 0x800F0408, KEY_8			},
+	{ 0x800F0409, KEY_9			},
+	{ 0x800F041D, KEY_NUMERIC_STAR		},
+	{ 0x800F041C, KEY_NUMERIC_POUND		},
+	{ 0x800F0410, KEY_VOLUMEUP		},
+	{ 0x800F0411, KEY_VOLUMEDOWN		},
+	{ 0x800F0412, KEY_CHANNELUP		},
+	{ 0x800F0413, KEY_CHANNELDOWN		},
+	{ 0x800F040E, KEY_MUTE			},
+	{ 0x800F040D, KEY_VENDOR		}, /* Vista Logo Key */
+	{ 0x800F041E, KEY_UP			},
+	{ 0x800F041F, KEY_DOWN			},
+	{ 0x800F0420, KEY_LEFT			},
+	{ 0x800F0421, KEY_RIGHT			},
+	{ 0x800F0422, KEY_OK			},
+	{ 0x800F0423, KEY_ESC			},
+	{ 0x800F040F, KEY_INFO			},
+	{ 0x800F040A, KEY_CLEAR			},
+	{ 0x800F040B, KEY_ENTER			},
+	{ 0x800F045B, KEY_RED			},
+	{ 0x800F045C, KEY_GREEN			},
+	{ 0x800F045D, KEY_YELLOW		},
+	{ 0x800F045E, KEY_BLUE			},
+	{ 0x800F045A, KEY_TEXT			},
+	{ 0x800F0427, KEY_SWITCHVIDEOMODE	},
+	{ 0x800F040C, KEY_POWER			},
+	{ 0x800F0450, KEY_RADIO			},
+	{ 0x800F0448, KEY_PVR			},
+	{ 0x800F0447, KEY_AUDIO			},
+	{ 0x800F0426, KEY_EPG			},
+	{ 0x800F0449, KEY_CAMERA		},
+	{ 0x800F0425, KEY_TV			},
+	{ 0x800F044A, KEY_VIDEO			},
+	{ 0x800F0424, KEY_DVD			},
+	{ 0x800F0416, KEY_PLAY			},
+	{ 0x800F0418, KEY_PAUSE			},
+	{ 0x800F0419, KEY_STOP			},
+	{ 0x800F0414, KEY_FASTFORWARD		},
+	{ 0x800F041A, KEY_NEXT			},
+	{ 0x800F041B, KEY_PREVIOUS		},
+	{ 0x800F0415, KEY_REWIND		},
+	{ 0x800F0417, KEY_RECORD		},
+};
+
+/* Registers and other state is protected by wbcir_lock */
+struct wbcir_data {
+	unsigned long wbase;        /* Wake-Up Baseaddr		*/
+	unsigned long ebase;        /* Enhanced Func. Baseaddr	*/
+	unsigned long sbase;        /* Serial Port Baseaddr	*/
+	unsigned int  irq;          /* Serial Port IRQ		*/
+
+	struct input_dev *input_dev;
+	struct timer_list timer_keyup;
+	struct led_trigger *rxtrigger;
+	struct led_trigger *txtrigger;
+	struct led_classdev led;
+
+	u32 last_scancode;
+	unsigned int last_keycode;
+	u8 last_toggle;
+	u8 keypressed;
+	unsigned long keyup_jiffies;
+	unsigned int idle_count;
+
+	/* RX irdata and parsing state */
+	unsigned long irdata[30];
+	unsigned int irdata_count;
+	unsigned int irdata_idle;
+	unsigned int irdata_off;
+	unsigned int irdata_error;
+
+	/* Protected by keytable_lock */
+	struct list_head keytable;
+};
+
+static enum wbcir_protocol protocol = IR_PROTOCOL_RC6;
+module_param(protocol, uint, 0444);
+MODULE_PARM_DESC(protocol, "IR protocol to use "
+		 "(0 = RC5, 1 = NEC, 2 = RC6A, default)");
+
+static int invert; /* default = 0 */
+module_param(invert, bool, 0444);
+MODULE_PARM_DESC(invert, "Invert the signal from the IR receiver");
+
+static unsigned int wake_sc = 0x800F040C;
+module_param(wake_sc, uint, 0644);
+MODULE_PARM_DESC(wake_sc, "Scancode of the power-on IR command");
+
+static unsigned int wake_rc6mode = 6;
+module_param(wake_rc6mode, uint, 0644);
+MODULE_PARM_DESC(wake_rc6mode, "RC6 mode for the power-on command "
+		 "(0 = 0, 6 = 6A, default)");
+
+
+
+/*****************************************************************************
+ *
+ * UTILITY FUNCTIONS
+ *
+ *****************************************************************************/
+
+/* Caller needs to hold wbcir_lock */
+static void
+wbcir_set_bits(unsigned long addr, u8 bits, u8 mask)
+{
+	u8 val;
+
+	val = inb(addr);
+	val = ((val & ~mask) | (bits & mask));
+	outb(val, addr);
+}
+
+/* Selects the register bank for the serial port */
+static inline void
+wbcir_select_bank(struct wbcir_data *data, enum wbcir_bank bank)
+{
+	outb(bank, data->sbase + WBCIR_REG_SP3_BSR);
+}
+
+static enum led_brightness
+wbcir_led_brightness_get(struct led_classdev *led_cdev)
+{
+	struct wbcir_data *data = container_of(led_cdev,
+					       struct wbcir_data,
+					       led);
+
+	if (inb(data->ebase + WBCIR_REG_ECEIR_CTS) & WBCIR_LED_ENABLE)
+		return LED_FULL;
+	else
+		return LED_OFF;
+}
+
+static void
+wbcir_led_brightness_set(struct led_classdev *led_cdev,
+			    enum led_brightness brightness)
+{
+	struct wbcir_data *data = container_of(led_cdev,
+					       struct wbcir_data,
+					       led);
+
+	wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CTS,
+		       brightness == LED_OFF ? 0x00 : WBCIR_LED_ENABLE,
+		       WBCIR_LED_ENABLE);
+}
+
+/* Manchester encodes bits to RC6 message cells (see wbcir_parse_rc6) */
+static u8
+wbcir_to_rc6cells(u8 val)
+{
+	u8 coded = 0x00;
+	int i;
+
+	val &= 0x0F;
+	for (i = 0; i < 4; i++) {
+		if (val & 0x01)
+			coded |= 0x02 << (i * 2);
+		else
+			coded |= 0x01 << (i * 2);
+		val >>= 1;
+	}
+
+	return coded;
+}
+
+
+
+/*****************************************************************************
+ *
+ * INPUT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static unsigned int
+wbcir_do_getkeycode(struct wbcir_data *data, u32 scancode)
+{
+	struct wbcir_keyentry *keyentry;
+	unsigned int keycode = KEY_RESERVED;
+	unsigned long flags;
+
+	read_lock_irqsave(&keytable_lock, flags);
+
+	list_for_each_entry(keyentry, &data->keytable, list) {
+		if (keyentry->key.scancode == scancode) {
+			keycode = keyentry->key.keycode;
+			break;
+		}
+	}
+
+	read_unlock_irqrestore(&keytable_lock, flags);
+	return keycode;
+}
+
+static int
+wbcir_getkeycode(struct input_dev *dev, int scancode, int *keycode)
+{
+	struct wbcir_data *data = input_get_drvdata(dev);
+
+	*keycode = (int)wbcir_do_getkeycode(data, (u32)scancode);
+	return 0;
+}
+
+static int
+wbcir_setkeycode(struct input_dev *dev, int sscancode, int keycode)
+{
+	struct wbcir_data *data = input_get_drvdata(dev);
+	struct wbcir_keyentry *keyentry;
+	struct wbcir_keyentry *new_keyentry;
+	unsigned long flags;
+	unsigned int old_keycode = KEY_RESERVED;
+	u32 scancode = (u32)sscancode;
+
+	if (keycode < 0 || keycode > KEY_MAX)
+		return -EINVAL;
+
+	new_keyentry = kmalloc(sizeof(*new_keyentry), GFP_KERNEL);
+	if (!new_keyentry)
+		return -ENOMEM;
+
+	write_lock_irqsave(&keytable_lock, flags);
+
+	list_for_each_entry(keyentry, &data->keytable, list) {
+		if (keyentry->key.scancode != scancode)
+			continue;
+
+		old_keycode = keyentry->key.keycode;
+		keyentry->key.keycode = keycode;
+
+		if (keyentry->key.keycode == KEY_RESERVED) {
+			list_del(&keyentry->list);
+			kfree(keyentry);
+		}
+
+		break;
+	}
+
+	set_bit(keycode, dev->keybit);
+
+	if (old_keycode == KEY_RESERVED) {
+		new_keyentry->key.scancode = scancode;
+		new_keyentry->key.keycode = keycode;
+		list_add(&new_keyentry->list, &data->keytable);
+	} else {
+		kfree(new_keyentry);
+		clear_bit(old_keycode, dev->keybit);
+		list_for_each_entry(keyentry, &data->keytable, list) {
+			if (keyentry->key.keycode == old_keycode) {
+				set_bit(old_keycode, dev->keybit);
+				break;
+			}
+		}
+	}
+
+	write_unlock_irqrestore(&keytable_lock, flags);
+	return 0;
+}
+
+/*
+ * Timer function to report keyup event some time after keydown is
+ * reported by the ISR.
+ */
+static void
+wbcir_keyup(unsigned long cookie)
+{
+	struct wbcir_data *data = (struct wbcir_data *)cookie;
+	unsigned long flags;
+
+	/*
+	 * data->keyup_jiffies is used to prevent a race condition if a
+	 * hardware interrupt occurs at this point and the keyup timer
+	 * event is moved further into the future as a result.
+	 *
+	 * The timer will then be reactivated and this function called
+	 * again in the future. We need to exit gracefully in that case
+	 * to allow the input subsystem to do its auto-repeat magic or
+	 * a keyup event might follow immediately after the keydown.
+	 */
+
+	spin_lock_irqsave(&wbcir_lock, flags);
+
+	if (time_is_after_eq_jiffies(data->keyup_jiffies) && data->keypressed) {
+		data->keypressed = 0;
+		led_trigger_event(data->rxtrigger, LED_OFF);
+		input_report_key(data->input_dev, data->last_keycode, 0);
+		input_sync(data->input_dev);
+	}
+
+	spin_unlock_irqrestore(&wbcir_lock, flags);
+}
+
+static void
+wbcir_keydown(struct wbcir_data *data, u32 scancode, u8 toggle)
+{
+	unsigned int keycode;
+
+	/* Repeat? */
+	if (data->last_scancode == scancode &&
+	    data->last_toggle == toggle &&
+	    data->keypressed)
+		goto set_timer;
+	data->last_scancode = scancode;
+
+	/* Do we need to release an old keypress? */
+	if (data->keypressed) {
+		input_report_key(data->input_dev, data->last_keycode, 0);
+		input_sync(data->input_dev);
+		data->keypressed = 0;
+	}
+
+	/* Do we know this scancode? */
+	keycode = wbcir_do_getkeycode(data, scancode);
+	if (keycode == KEY_RESERVED)
+		goto set_timer;
+
+	/* Register a keypress */
+	input_report_key(data->input_dev, keycode, 1);
+	input_sync(data->input_dev);
+	data->keypressed = 1;
+	data->last_keycode = keycode;
+	data->last_toggle = toggle;
+
+set_timer:
+	led_trigger_event(data->rxtrigger,
+			  data->keypressed ? LED_FULL : LED_OFF);
+	data->keyup_jiffies = jiffies + msecs_to_jiffies(IR_KEYPRESS_TIMEOUT);
+	mod_timer(&data->timer_keyup, data->keyup_jiffies);
+}
+
+
+
+/*****************************************************************************
+ *
+ * IR PARSING FUNCTIONS
+ *
+ *****************************************************************************/
+
+/* Resets all irdata */
+static void
+wbcir_reset_irdata(struct wbcir_data *data)
+{
+	memset(data->irdata, 0, sizeof(data->irdata));
+	data->irdata_count = 0;
+	data->irdata_off = 0;
+	data->irdata_error = 0;
+}
+
+/* Adds one bit of irdata */
+static void
+add_irdata_bit(struct wbcir_data *data, int set)
+{
+	if (data->irdata_count >= sizeof(data->irdata) * 8) {
+		data->irdata_error = 1;
+		return;
+	}
+
+	if (set)
+		__set_bit(data->irdata_count, data->irdata);
+	data->irdata_count++;
+}
+
+/* Gets count bits of irdata */
+static u16
+get_bits(struct wbcir_data *data, int count)
+{
+	u16 val = 0x0;
+
+	if (data->irdata_count - data->irdata_off < count) {
+		data->irdata_error = 1;
+		return 0x0;
+	}
+
+	while (count > 0) {
+		val <<= 1;
+		if (test_bit(data->irdata_off, data->irdata))
+			val |= 0x1;
+		count--;
+		data->irdata_off++;
+	}
+
+	return val;
+}
+
+/* Reads 16 cells and converts them to a byte */
+static u8
+wbcir_rc6cells_to_byte(struct wbcir_data *data)
+{
+	u16 raw = get_bits(data, 16);
+	u8 val = 0x00;
+	int bit;
+
+	for (bit = 0; bit < 8; bit++) {
+		switch (raw & 0x03) {
+		case 0x01:
+			break;
+		case 0x02:
+			val |= (0x01 << bit);
+			break;
+		default:
+			data->irdata_error = 1;
+			break;
+		}
+		raw >>= 2;
+	}
+
+	return val;
+}
+
+/* Decodes a number of bits from raw RC5 data */
+static u8
+wbcir_get_rc5bits(struct wbcir_data *data, unsigned int count)
+{
+	u16 raw = get_bits(data, count * 2);
+	u8 val = 0x00;
+	int bit;
+
+	for (bit = 0; bit < count; bit++) {
+		switch (raw & 0x03) {
+		case 0x01:
+			val |= (0x01 << bit);
+			break;
+		case 0x02:
+			break;
+		default:
+			data->irdata_error = 1;
+			break;
+		}
+		raw >>= 2;
+	}
+
+	return val;
+}
+
+static void
+wbcir_parse_rc6(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Normal bits are manchester coded as follows:
+	 * cell0 + cell1 = logic "0"
+	 * cell1 + cell0 = logic "1"
+	 *
+	 * The IR pulse has the following components:
+	 *
+	 * Leader		- 6 * cell1 - discarded
+	 * Gap    		- 2 * cell0 - discarded
+	 * Start bit		- Normal Coding - always "1"
+	 * Mode Bit 2 - 0	- Normal Coding
+	 * Toggle bit		- Normal Coding with double bit time,
+	 *			  e.g. cell0 + cell0 + cell1 + cell1
+	 *			  means logic "0".
+	 *
+	 * The rest depends on the mode, the following modes are known:
+	 *
+	 * MODE 0:
+	 *  Address Bit 7 - 0	- Normal Coding
+	 *  Command Bit 7 - 0	- Normal Coding
+	 *
+	 * MODE 6:
+	 *  The above Toggle Bit is used as a submode bit, 0 = A, 1 = B.
+	 *  Submode B is for pointing devices, only remotes using submode A
+	 *  are supported.
+	 *
+	 *  Customer range bit	- 0 => Customer = 7 bits, 0...127
+	 *                        1 => Customer = 15 bits, 32768...65535
+	 *  Customer Bits	- Normal Coding
+	 *
+	 *  Customer codes are allocated by Philips. The rest of the bits
+	 *  are customer dependent. The following is commonly used (and the
+	 *  only supported config):
+	 *
+	 *  Toggle Bit		- Normal Coding
+	 *  Address Bit 6 - 0	- Normal Coding
+	 *  Command Bit 7 - 0	- Normal Coding
+	 *
+	 * All modes are followed by at least 6 * cell0.
+	 *
+	 * MODE 0 msglen:
+	 *  1 * 2 (start bit) + 3 * 2 (mode) + 2 * 2 (toggle) +
+	 *  8 * 2 (address) + 8 * 2 (command) =
+	 *  44 cells
+	 *
+	 * MODE 6A msglen:
+	 *  1 * 2 (start bit) + 3 * 2 (mode) + 2 * 2 (submode) +
+	 *  1 * 2 (customer range bit) + 7/15 * 2 (customer bits) +
+	 *  1 * 2 (toggle bit) + 7 * 2 (address) + 8 * 2 (command) =
+	 *  60 - 76 cells
+	 */
+	u8 mode;
+	u8 toggle;
+	u16 customer = 0x0;
+	u8 address;
+	u8 command;
+	u32 scancode;
+
+	/* Leader mark */
+	while (get_bits(data, 1) && !data->irdata_error)
+		/* Do nothing */;
+
+	/* Leader space */
+	if (get_bits(data, 1)) {
+		dev_dbg(dev, "RC6 - Invalid leader space\n");
+		return;
+	}
+
+	/* Start bit */
+	if (get_bits(data, 2) != 0x02) {
+		dev_dbg(dev, "RC6 - Invalid start bit\n");
+		return;
+	}
+
+	/* Mode */
+	mode = get_bits(data, 6);
+	switch (mode) {
+	case 0x15: /* 010101 = b000 */
+		mode = 0;
+		break;
+	case 0x29: /* 101001 = b110 */
+		mode = 6;
+		break;
+	default:
+		dev_dbg(dev, "RC6 - Invalid mode\n");
+		return;
+	}
+
+	/* Toggle bit / Submode bit */
+	toggle = get_bits(data, 4);
+	switch (toggle) {
+	case 0x03:
+		toggle = 0;
+		break;
+	case 0x0C:
+		toggle = 1;
+		break;
+	default:
+		dev_dbg(dev, "RC6 - Toggle bit error\n");
+		break;
+	}
+
+	/* Customer */
+	if (mode == 6) {
+		if (toggle != 0) {
+			dev_dbg(dev, "RC6B - Not Supported\n");
+			return;
+		}
+
+		customer = wbcir_rc6cells_to_byte(data);
+
+		if (customer & 0x80) {
+			/* 15 bit customer value */
+			customer <<= 8;
+			customer |= wbcir_rc6cells_to_byte(data);
+		}
+	}
+
+	/* Address */
+	address = wbcir_rc6cells_to_byte(data);
+	if (mode == 6) {
+		toggle = address >> 7;
+		address &= 0x7F;
+	}
+
+	/* Command */
+	command = wbcir_rc6cells_to_byte(data);
+
+	/* Create scancode */
+	scancode =  command;
+	scancode |= address << 8;
+	scancode |= customer << 16;
+
+	/* Last sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "RC6 - Cell error(s)\n");
+		return;
+	}
+
+	dev_dbg(dev, "IR-RC6 ad 0x%02X cm 0x%02X cu 0x%04X "
+		"toggle %u mode %u scan 0x%08X\n",
+		address,
+		command,
+		customer,
+		(unsigned int)toggle,
+		(unsigned int)mode,
+		scancode);
+
+	wbcir_keydown(data, scancode, toggle);
+}
+
+static void
+wbcir_parse_rc5(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Bits are manchester coded as follows:
+	 * cell1 + cell0 = logic "0"
+	 * cell0 + cell1 = logic "1"
+	 * (i.e. the reverse of RC6)
+	 *
+	 * Start bit 1		- "1" - discarded
+	 * Start bit 2		- Must be inverted to get command bit 6
+	 * Toggle bit
+	 * Address Bit 4 - 0
+	 * Command Bit 5 - 0
+	 */
+	u8 toggle;
+	u8 address;
+	u8 command;
+	u32 scancode;
+
+	/* Start bit 1 */
+	if (!get_bits(data, 1)) {
+		dev_dbg(dev, "RC5 - Invalid start bit\n");
+		return;
+	}
+
+	/* Start bit 2 */
+	if (!wbcir_get_rc5bits(data, 1))
+		command = 0x40;
+	else
+		command = 0x00;
+
+	toggle   = wbcir_get_rc5bits(data, 1);
+	address  = wbcir_get_rc5bits(data, 5);
+	command |= wbcir_get_rc5bits(data, 6);
+	scancode = address << 7 | command;
+
+	/* Last sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "RC5 - Invalid message\n");
+		return;
+	}
+
+	dev_dbg(dev, "IR-RC5 ad %u cm %u t %u s %u\n",
+		(unsigned int)address,
+		(unsigned int)command,
+		(unsigned int)toggle,
+		(unsigned int)scancode);
+
+	wbcir_keydown(data, scancode, toggle);
+}
+
+static void
+wbcir_parse_nec(struct device *dev, struct wbcir_data *data)
+{
+	/*
+	 * Each bit represents 560 us.
+	 *
+	 * Leader		- 9 ms burst
+	 * Gap			- 4.5 ms silence
+	 * Address1 bit 0 - 7	- Address 1
+	 * Address2 bit 0 - 7	- Address 2
+	 * Command1 bit 0 - 7	- Command 1
+	 * Command2 bit 0 - 7	- Command 2
+	 *
+	 * Note the bit order!
+	 *
+	 * With the old NEC protocol, Address2 was the inverse of Address1
+	 * and Command2 was the inverse of Command1 and were used as
+	 * an error check.
+	 *
+	 * With NEC extended, Address1 is the LSB of the Address and
+	 * Address2 is the MSB, Command parsing remains unchanged.
+	 *
+	 * A repeat message is coded as:
+	 * Leader		- 9 ms burst
+	 * Gap			- 2.25 ms silence
+	 * Repeat		- 560 us active
+	 */
+	u8 address1;
+	u8 address2;
+	u8 command1;
+	u8 command2;
+	u16 address;
+	u32 scancode;
+
+	/* Leader mark */
+	while (get_bits(data, 1) && !data->irdata_error)
+		/* Do nothing */;
+
+	/* Leader space */
+	if (get_bits(data, 4)) {
+		dev_dbg(dev, "NEC - Invalid leader space\n");
+		return;
+	}
+
+	/* Repeat? */
+	if (get_bits(data, 1)) {
+		if (!data->keypressed) {
+			dev_dbg(dev, "NEC - Stray repeat message\n");
+			return;
+		}
+
+		dev_dbg(dev, "IR-NEC repeat s %u\n",
+			(unsigned int)data->last_scancode);
+
+		wbcir_keydown(data, data->last_scancode, data->last_toggle);
+		return;
+	}
+
+	/* Remaining leader space */
+	if (get_bits(data, 3)) {
+		dev_dbg(dev, "NEC - Invalid leader space\n");
+		return;
+	}
+
+	address1  = bitrev8(get_bits(data, 8));
+	address2  = bitrev8(get_bits(data, 8));
+	command1  = bitrev8(get_bits(data, 8));
+	command2  = bitrev8(get_bits(data, 8));
+
+	/* Sanity check */
+	if (data->irdata_error) {
+		dev_dbg(dev, "NEC - Invalid message\n");
+		return;
+	}
+
+	/* Check command validity */
+	if (command1 != ~command2) {
+		dev_dbg(dev, "NEC - Command bytes mismatch\n");
+		return;
+	}
+
+	/* Check for extended NEC protocol */
+	address = address1;
+	if (address1 != ~address2)
+		address |= address2 << 8;
+
+	scancode = address << 8 | command1;
+
+	dev_dbg(dev, "IR-NEC ad %u cm %u s %u\n",
+		(unsigned int)address,
+		(unsigned int)command1,
+		(unsigned int)scancode);
+
+	wbcir_keydown(data, scancode, !data->last_toggle);
+}
+
+
+
+/*****************************************************************************
+ *
+ * INTERRUPT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static irqreturn_t
+wbcir_irq_handler(int irqno, void *cookie)
+{
+	struct pnp_dev *device = cookie;
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	struct device *dev = &device->dev;
+	u8 status;
+	unsigned long flags;
+	u8 irdata[16];
+	int i;
+	unsigned int hw;
+
+	spin_lock_irqsave(&wbcir_lock, flags);
+
+	wbcir_select_bank(data, WBCIR_BANK_0);
+
+	status = inb(data->sbase + WBCIR_REG_SP3_EIR);
+
+	if (!(status & (WBCIR_IRQ_RX | WBCIR_IRQ_ERR))) {
+		spin_unlock_irqrestore(&wbcir_lock, flags);
+		return IRQ_NONE;
+	}
+
+	if (status & WBCIR_IRQ_ERR)
+		data->irdata_error = 1;
+
+	if (!(status & WBCIR_IRQ_RX))
+		goto out;
+
+	/* Since RXHDLEV is set, at least 16 bytes are in the FIFO */
+	insb(data->sbase + WBCIR_REG_SP3_RXDATA, &irdata[0], 8);
+	insb(data->sbase + WBCIR_REG_SP3_RXDATA, &irdata[8], 8);
+
+	for (i = 0; i < sizeof(data); i++) {
+		hw = hweight8(irdata[i]);
+		if (hw > 4)
+			add_irdata_bit(data, 0);
+		else
+			add_irdata_bit(data, 1);
+
+		if (hw == 8)
+			data->idle_count++;
+		else
+			data->idle_count = 0;
+	}
+
+	if (data->idle_count > WBCIR_MAX_IDLE_BYTES) {
+		/* Drain the FIFO */
+		while (inb(data->sbase + WBCIR_REG_SP3_LSR) & WBCIR_RX_AVAIL)
+			inb(data->sbase + WBCIR_REG_SP3_RXDATA);
+
+		/* And set RXINACTIVE */
+		outb(WBCIR_RX_DISABLE, data->sbase + WBCIR_REG_SP3_ASCR);
+
+		dev_dbg(dev, "IRDATA:\n");
+		for (i = 0; i < data->irdata_count; i += BITS_PER_LONG)
+			dev_dbg(dev, "0x%08lX\n", data->irdata[i/BITS_PER_LONG]);
+
+		switch (protocol) {
+		case IR_PROTOCOL_RC5:
+			wbcir_parse_rc5(dev, data);
+			break;
+		case IR_PROTOCOL_RC6:
+			wbcir_parse_rc6(dev, data);
+			break;
+		case IR_PROTOCOL_NEC:
+			wbcir_parse_nec(dev, data);
+			break;
+		}
+
+		wbcir_reset_irdata(data);
+		data->idle_count = 0;
+	}
+
+out:
+	spin_unlock_irqrestore(&wbcir_lock, flags);
+	return IRQ_HANDLED;
+}
+
+
+
+/*****************************************************************************
+ *
+ * SUSPEND/RESUME FUNCTIONS
+ *
+ *****************************************************************************/
+
+static void
+wbcir_shutdown(struct pnp_dev *device)
+{
+	struct device *dev = &device->dev;
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	int do_wake = 1;
+	u8 match[11];
+	u8 mask[11];
+	u8 rc6_csl = 0;
+	int i;
+
+	memset(match, 0, sizeof(match));
+	memset(mask, 0, sizeof(mask));
+
+	if (wake_sc == INVALID_SCANCODE || !device_may_wakeup(dev)) {
+		do_wake = 0;
+		goto finish;
+	}
+
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+		if (wake_sc > 0xFFF) {
+			do_wake = 0;
+			dev_err(dev, "RC5 - Invalid wake scancode\n");
+			break;
+		}
+
+		/* Mask = 13 bits, ex toggle */
+		mask[0] = 0xFF;
+		mask[1] = 0x17;
+
+		match[0]  = (wake_sc & 0x003F);      /* 6 command bits */
+		match[0] |= (wake_sc & 0x0180) >> 1; /* 2 address bits */
+		match[1]  = (wake_sc & 0x0E00) >> 9; /* 3 address bits */
+		if (!(wake_sc & 0x0040))             /* 2nd start bit  */
+			match[1] |= 0x10;
+
+		break;
+
+	case IR_PROTOCOL_NEC:
+		if (wake_sc > 0xFFFFFF) {
+			do_wake = 0;
+			dev_err(dev, "NEC - Invalid wake scancode\n");
+			break;
+		}
+
+		mask[0] = mask[1] = mask[2] = mask[3] = 0xFF;
+
+		match[1] = bitrev8((wake_sc & 0xFF));
+		match[0] = ~match[1];
+
+		match[3] = bitrev8((wake_sc & 0xFF00) >> 8);
+		if (wake_sc > 0xFFFF)
+			match[2] = bitrev8((wake_sc & 0xFF0000) >> 16);
+		else
+			match[2] = ~match[3];
+
+		break;
+
+	case IR_PROTOCOL_RC6:
+
+		if (wake_rc6mode == 0) {
+			if (wake_sc > 0xFFFF) {
+				do_wake = 0;
+				dev_err(dev, "RC6 - Invalid wake scancode\n");
+				break;
+			}
+
+			/* Command */
+			match[0] = wbcir_to_rc6cells(wake_sc >>  0);
+			mask[0]  = 0xFF;
+			match[1] = wbcir_to_rc6cells(wake_sc >>  4);
+			mask[1]  = 0xFF;
+
+			/* Address */
+			match[2] = wbcir_to_rc6cells(wake_sc >>  8);
+			mask[2]  = 0xFF;
+			match[3] = wbcir_to_rc6cells(wake_sc >> 12);
+			mask[3]  = 0xFF;
+
+			/* Header */
+			match[4] = 0x50; /* mode1 = mode0 = 0, ignore toggle */
+			mask[4]  = 0xF0;
+			match[5] = 0x09; /* start bit = 1, mode2 = 0 */
+			mask[5]  = 0x0F;
+
+			rc6_csl = 44;
+
+		} else if (wake_rc6mode == 6) {
+			i = 0;
+
+			/* Command */
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  0);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  4);
+			mask[i++] = 0xFF;
+
+			/* Address + Toggle */
+			match[i]  = wbcir_to_rc6cells(wake_sc >>  8);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 12);
+			mask[i++] = 0x3F;
+
+			/* Customer bits 7 - 0 */
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 16);
+			mask[i++] = 0xFF;
+			match[i]  = wbcir_to_rc6cells(wake_sc >> 20);
+			mask[i++] = 0xFF;
+
+			if (wake_sc & 0x80000000) {
+				/* Customer range bit and bits 15 - 8 */
+				match[i]  = wbcir_to_rc6cells(wake_sc >> 24);
+				mask[i++] = 0xFF;
+				match[i]  = wbcir_to_rc6cells(wake_sc >> 28);
+				mask[i++] = 0xFF;
+				rc6_csl = 76;
+			} else if (wake_sc <= 0x007FFFFF) {
+				rc6_csl = 60;
+			} else {
+				do_wake = 0;
+				dev_err(dev, "RC6 - Invalid wake scancode\n");
+				break;
+			}
+
+			/* Header */
+			match[i]  = 0x93; /* mode1 = mode0 = 1, submode = 0 */
+			mask[i++] = 0xFF;
+			match[i]  = 0x0A; /* start bit = 1, mode2 = 1 */
+			mask[i++] = 0x0F;
+
+		} else {
+			do_wake = 0;
+			dev_err(dev, "RC6 - Invalid wake mode\n");
+		}
+
+		break;
+
+	default:
+		do_wake = 0;
+		break;
+	}
+
+finish:
+	if (do_wake) {
+		/* Set compare and compare mask */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX,
+			       WBCIR_REGSEL_COMPARE | WBCIR_REG_ADDR0,
+			       0x3F);
+		outsb(data->wbase + WBCIR_REG_WCEIR_DATA, match, 11);
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX,
+			       WBCIR_REGSEL_MASK | WBCIR_REG_ADDR0,
+			       0x3F);
+		outsb(data->wbase + WBCIR_REG_WCEIR_DATA, mask, 11);
+
+		/* RC6 Compare String Len */
+		outb(rc6_csl, data->wbase + WBCIR_REG_WCEIR_CSL);
+
+		/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+		/* Clear BUFF_EN, Clear END_EN, Set MATCH_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x01, 0x07);
+
+		/* Set CEIR_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x01, 0x01);
+
+	} else {
+		/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+		/* Clear CEIR_EN */
+		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+	}
+
+	/* Disable interrupts */
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+}
+
+static int
+wbcir_suspend(struct pnp_dev *device, pm_message_t state)
+{
+	wbcir_shutdown(device);
+	return 0;
+}
+
+static int
+wbcir_resume(struct pnp_dev *device)
+{
+	struct wbcir_data *data = pnp_get_drvdata(device);
+
+	/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	/* Clear CEIR_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+
+	/* Enable interrupts */
+	wbcir_reset_irdata(data);
+	outb(WBCIR_IRQ_RX | WBCIR_IRQ_ERR, data->sbase + WBCIR_REG_SP3_IER);
+
+	return 0;
+}
+
+
+
+/*****************************************************************************
+ *
+ * SETUP/INIT FUNCTIONS
+ *
+ *****************************************************************************/
+
+static ssize_t
+wbcir_show_last_scancode(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	struct pnp_dev *device = container_of(dev, struct pnp_dev, dev);
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	return sprintf(buf, "0x%08X\n", data->last_scancode);
+}
+
+static struct device_attribute dev_attr_last_scancode = {
+	.attr = {
+		.name = "last_scancode",
+		.mode = 0444,
+	},
+	.show = wbcir_show_last_scancode,
+	.store = NULL,
+
+};
+
+static struct attribute *wbcir_attributes[] = {
+	&dev_attr_last_scancode.attr,
+	NULL,
+};
+
+static struct attribute_group wbcir_attribute_group = {
+	.attrs = wbcir_attributes,
+};
+
+static void
+wbcir_cfg_ceir(struct wbcir_data *data)
+{
+	u8 tmp;
+
+	/* Set PROT_SEL, RX_INV, Clear CEIR_EN (needed for the led) */
+	tmp = protocol << 4;
+	if (invert)
+		tmp |= 0x08;
+	outb(tmp, data->wbase + WBCIR_REG_WCEIR_CTL);
+
+	/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+	/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	/* Set RC5 cell time to correspond to 36 kHz */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CFG1, 0x4A, 0x7F);
+
+	/* Set IRTX_INV */
+	if (invert)
+		outb(0x04, data->ebase + WBCIR_REG_ECEIR_CCTL);
+	else
+		outb(0x00, data->ebase + WBCIR_REG_ECEIR_CCTL);
+
+	/*
+	 * Clear IR LED, set SP3 clock to 24Mhz
+	 * set SP3_IRRX_SW to binary 01, helpfully not documented
+	 */
+	outb(0x10, data->ebase + WBCIR_REG_ECEIR_CTS);
+}
+
+static int
+wbcir_probe(struct pnp_dev *device, const struct pnp_device_id *dev_id)
+{
+	struct device *dev = &device->dev;
+	struct wbcir_data *data;
+	int err;
+
+	if (!(pnp_port_len(device, 0) == EHFUNC_IOMEM_LEN &&
+	      pnp_port_len(device, 1) == WAKEUP_IOMEM_LEN &&
+	      pnp_port_len(device, 2) == SP_IOMEM_LEN)) {
+		dev_err(dev, "Invalid resources\n");
+		return -ENODEV;
+	}
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data) {
+		err = -ENOMEM;
+		goto exit;
+	}
+
+	pnp_set_drvdata(device, data);
+
+	data->ebase = pnp_port_start(device, 0);
+	data->wbase = pnp_port_start(device, 1);
+	data->sbase = pnp_port_start(device, 2);
+	data->irq = pnp_irq(device, 0);
+
+	if (data->wbase == 0 || data->ebase == 0 ||
+	    data->sbase == 0 || data->irq == 0) {
+		err = -ENODEV;
+		dev_err(dev, "Invalid resources\n");
+		goto exit_free_data;
+	}
+
+	dev_info(&device->dev, "Found device "
+		 "(w: 0x%lX, e: 0x%lX, s: 0x%lX, i: %u)\n",
+		 data->wbase, data->ebase, data->sbase, data->irq);
+
+	if (!request_region(data->wbase, WAKEUP_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->wbase, data->wbase + WAKEUP_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_free_data;
+	}
+
+	if (!request_region(data->ebase, EHFUNC_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->ebase, data->ebase + EHFUNC_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_release_wbase;
+	}
+
+	if (!request_region(data->sbase, SP_IOMEM_LEN, DRVNAME)) {
+		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
+			data->sbase, data->sbase + SP_IOMEM_LEN - 1);
+		err = -EBUSY;
+		goto exit_release_ebase;
+	}
+
+	err = request_irq(data->irq, wbcir_irq_handler,
+			  IRQF_DISABLED, DRVNAME, device);
+	if (err) {
+		dev_err(dev, "Failed to claim IRQ %u\n", data->irq);
+		err = -EBUSY;
+		goto exit_release_sbase;
+	}
+
+	led_trigger_register_simple("cir-tx", &data->txtrigger);
+	if (!data->txtrigger) {
+		err = -ENOMEM;
+		goto exit_free_irq;
+	}
+
+	led_trigger_register_simple("cir-rx", &data->rxtrigger);
+	if (!data->rxtrigger) {
+		err = -ENOMEM;
+		goto exit_unregister_txtrigger;
+	}
+
+	data->led.name = "cir::activity";
+	data->led.default_trigger = "cir-rx";
+	data->led.brightness_set = wbcir_led_brightness_set;
+	data->led.brightness_get = wbcir_led_brightness_get;
+	err = led_classdev_register(&device->dev, &data->led);
+	if (err)
+		goto exit_unregister_rxtrigger;
+
+	data->input_dev = input_allocate_device();
+	if (!data->input_dev) {
+		err = -ENOMEM;
+		goto exit_unregister_led;
+	}
+
+	data->input_dev->evbit[0] = BIT(EV_KEY);
+	data->input_dev->name = WBCIR_ACPI_NAME;
+	data->input_dev->phys = "wbcir/cir0";
+	data->input_dev->id.bustype = BUS_HOST;
+	data->input_dev->id.vendor  = PCI_VENDOR_ID_WINBOND;
+	data->input_dev->id.product = WBCIR_ID_FAMILY;
+	data->input_dev->id.version = WBCIR_ID_CHIP;
+	data->input_dev->getkeycode = wbcir_getkeycode;
+	data->input_dev->setkeycode = wbcir_setkeycode;
+	input_set_drvdata(data->input_dev, data);
+
+	err = input_register_device(data->input_dev);
+	if (err)
+		goto exit_free_input;
+
+	data->last_scancode = INVALID_SCANCODE;
+	err = sysfs_create_group(&device->dev.kobj, &wbcir_attribute_group);
+	if (err)
+		goto exit_unregister_input;
+
+	INIT_LIST_HEAD(&data->keytable);
+	setup_timer(&data->timer_keyup, wbcir_keyup, (unsigned long)data);
+
+	/* Load default keymaps */
+	if (protocol == IR_PROTOCOL_RC6) {
+		int i;
+		for (i = 0; i < ARRAY_SIZE(rc6_def_keymap); i++) {
+			err = wbcir_setkeycode(data->input_dev,
+					       (int)rc6_def_keymap[i].scancode,
+					       (int)rc6_def_keymap[i].keycode);
+			if (err)
+				goto exit_unregister_keys;
+		}
+	}
+
+	device_init_wakeup(&device->dev, 1);
+
+	wbcir_cfg_ceir(data);
+
+	/* Disable interrupts */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+
+	/* Enable extended mode */
+	wbcir_select_bank(data, WBCIR_BANK_2);
+	outb(WBCIR_EXT_ENABLE, data->sbase + WBCIR_REG_SP3_EXCR1);
+
+	/*
+	 * Configure baud generator, IR data will be sampled at
+	 * a bitrate of: (24Mhz * prescaler) / (divisor * 16).
+	 *
+	 * The ECIR registers include a flag to change the
+	 * 24Mhz clock freq to 48Mhz.
+	 */
+
+	/* prescaler 1.0, tx/rx fifo lvl 32 */
+	outb(0x35, data->sbase + WBCIR_REG_SP3_EXCR2);
+
+	/* Set baud divisor to generate one byte per bit/cell */
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+		outb(0xA7, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	case IR_PROTOCOL_RC6:
+		outb(0x53, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	case IR_PROTOCOL_NEC:
+		outb(0x69, data->sbase + WBCIR_REG_SP3_BGDL);
+		break;
+	}
+	outb(0x00, data->sbase + WBCIR_REG_SP3_BGDH);
+
+	/* Set CEIR mode */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(0xC0, data->sbase + WBCIR_REG_SP3_MCR);
+	inb(data->sbase + WBCIR_REG_SP3_LSR); /* Clear LSR */
+	inb(data->sbase + WBCIR_REG_SP3_MSR); /* Clear MSR */
+
+	/* Disable RX demod, run-length encoding/decoding, set freq span */
+	wbcir_select_bank(data, WBCIR_BANK_7);
+	outb(0x10, data->sbase + WBCIR_REG_SP3_RCCFG);
+
+	/* Disable timer */
+	wbcir_select_bank(data, WBCIR_BANK_4);
+	outb(0x00, data->sbase + WBCIR_REG_SP3_IRCR1);
+
+	/* Enable MSR interrupt, Clear AUX_IRX */
+	wbcir_select_bank(data, WBCIR_BANK_5);
+	outb(0x00, data->sbase + WBCIR_REG_SP3_IRCR2);
+
+	/* Disable CRC */
+	wbcir_select_bank(data, WBCIR_BANK_6);
+	outb(0x20, data->sbase + WBCIR_REG_SP3_IRCR3);
+
+	/* Set RX/TX (de)modulation freq, not really used */
+	wbcir_select_bank(data, WBCIR_BANK_7);
+	outb(0xF2, data->sbase + WBCIR_REG_SP3_IRRXDC);
+	outb(0x69, data->sbase + WBCIR_REG_SP3_IRTXMC);
+
+	/* Set invert and pin direction */
+	if (invert)
+		outb(0x10, data->sbase + WBCIR_REG_SP3_IRCFG4);
+	else
+		outb(0x00, data->sbase + WBCIR_REG_SP3_IRCFG4);
+
+	/* Set FIFO thresholds (RX = 16, TX = 7), reset RX/TX */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(0x97, data->sbase + WBCIR_REG_SP3_FCR);
+
+	/* Clear AUX status bits */
+	outb(0xE0, data->sbase + WBCIR_REG_SP3_ASCR);
+
+	/* Enable interrupts */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(WBCIR_IRQ_RX | WBCIR_IRQ_ERR, data->sbase + WBCIR_REG_SP3_IER);
+
+	return 0;
+
+exit_unregister_keys:
+	if (!list_empty(&data->keytable)) {
+		struct wbcir_keyentry *key;
+		struct wbcir_keyentry *keytmp;
+
+		list_for_each_entry_safe(key, keytmp, &data->keytable, list) {
+			list_del(&key->list);
+			kfree(key);
+		}
+	}
+exit_unregister_input:
+	input_unregister_device(data->input_dev);
+	/* Can't call input_free_device on an unregistered device */
+	data->input_dev = NULL;
+exit_free_input:
+	input_free_device(data->input_dev);
+exit_unregister_led:
+	led_classdev_unregister(&data->led);
+exit_unregister_rxtrigger:
+	led_trigger_unregister_simple(data->rxtrigger);
+exit_unregister_txtrigger:
+	led_trigger_unregister_simple(data->txtrigger);
+exit_free_irq:
+	free_irq(data->irq, device);
+exit_release_sbase:
+	release_region(data->sbase, SP_IOMEM_LEN);
+exit_release_ebase:
+	release_region(data->ebase, EHFUNC_IOMEM_LEN);
+exit_release_wbase:
+	release_region(data->wbase, WAKEUP_IOMEM_LEN);
+exit_free_data:
+	kfree(data);
+	pnp_set_drvdata(device, NULL);
+exit:
+	return err;
+}
+
+static void
+wbcir_remove(struct pnp_dev *device)
+{
+	struct wbcir_data *data = pnp_get_drvdata(device);
+	struct wbcir_keyentry *key;
+	struct wbcir_keyentry *keytmp;
+
+	/* Disable interrupts */
+	wbcir_select_bank(data, WBCIR_BANK_0);
+	outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
+
+	del_timer_sync(&data->timer_keyup);
+
+	free_irq(data->irq, device);
+
+	/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
+
+	/* Clear CEIR_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
+
+	/* Clear BUFF_EN, END_EN, MATCH_EN */
+	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
+
+	sysfs_remove_group(&device->dev.kobj, &wbcir_attribute_group);
+
+	/* This will generate a keyup event if necessary */
+	input_unregister_device(data->input_dev);
+
+	led_trigger_unregister_simple(data->rxtrigger);
+	led_trigger_unregister_simple(data->txtrigger);
+	led_classdev_unregister(&data->led);
+
+	/* This is ok since &data->led isn't actually used */
+	wbcir_led_brightness_set(&data->led, LED_OFF);
+
+	release_region(data->wbase, WAKEUP_IOMEM_LEN);
+	release_region(data->ebase, EHFUNC_IOMEM_LEN);
+	release_region(data->sbase, SP_IOMEM_LEN);
+
+	list_for_each_entry_safe(key, keytmp, &data->keytable, list) {
+		list_del(&key->list);
+		kfree(key);
+	}
+
+	kfree(data);
+
+	pnp_set_drvdata(device, NULL);
+}
+
+static const struct pnp_device_id wbcir_ids[] = {
+	{ "WEC1022", 0 },
+	{ "", 0 }
+};
+MODULE_DEVICE_TABLE(pnp, wbcir_ids);
+
+static struct pnp_driver wbcir_driver = {
+	.name     = WBCIR_ACPI_NAME,
+	.id_table = wbcir_ids,
+	.probe    = wbcir_probe,
+	.remove   = wbcir_remove,
+	.suspend  = wbcir_suspend,
+	.resume   = wbcir_resume,
+	.shutdown = wbcir_shutdown
+};
+
+static int __init
+wbcir_init(void)
+{
+	int ret;
+
+	switch (protocol) {
+	case IR_PROTOCOL_RC5:
+	case IR_PROTOCOL_NEC:
+	case IR_PROTOCOL_RC6:
+		break;
+	default:
+		printk(KERN_ERR DRVNAME ": Invalid protocol argument\n");
+		return -EINVAL;
+	}
+
+	ret = pnp_register_driver(&wbcir_driver);
+	if (ret)
+		printk(KERN_ERR DRVNAME ": Unable to register driver\n");
+
+	return ret;
+}
+
+static void __exit
+wbcir_exit(void)
+{
+	pnp_unregister_driver(&wbcir_driver);
+}
+
+MODULE_AUTHOR("David Härdeman <david@hardeman.nu>");
+MODULE_DESCRIPTION("Winbond SuperI/O Consumer IR Driver");
+MODULE_LICENSE("GPL");
+
+module_init(wbcir_init);
+module_exit(wbcir_exit);
+
+

-- 
David Härdeman
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2009-08-13 18:57 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <20090813185050.356225129@hardeman.nu>
2009-08-13 18:57 ` [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware David Härdeman
2009-08-13 18:57   ` David Härdeman
2009-08-11 16:26 [patch 0/2] Winbond IR Driver - v2 David Härdeman
2009-08-11 16:26 ` [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware David Härdeman
2009-08-11 16:26   ` David Härdeman
  -- strict thread matches above, loose matches on Subject: below --
2009-08-09  9:56 [patch 0/2] Winbond IR Driver - v2 david
2009-08-09  9:56 ` [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware david
2009-08-09  9:56   ` david
2009-08-10 15:39   ` Bjorn Helgaas
2009-08-13  6:58   ` Dmitry Torokhov
2009-08-13  9:34     ` David Härdeman
2009-08-13  9:34       ` David Härdeman
2009-08-13 15:56       ` Dmitry Torokhov
2009-08-13 15:56         ` Dmitry Torokhov
2009-08-13 17:58         ` David Härdeman
2009-08-13 17:58           ` David Härdeman
2009-08-13 17:14     ` David Härdeman
2009-08-13 17:14       ` David Härdeman

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.