linux-input.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [patch 0/2] Winbond IR Driver - v2
@ 2009-08-09  9:56 david
  2009-08-09  9:56 ` [patch 1/2] Add a shutdown method to pnp drivers david
                   ` (2 more replies)
  0 siblings, 3 replies; 15+ 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

Here's a new patch set which should replace all the patches currently in the
-mm tree for the winbond cir driver. The new series incorporate feedback from
Bjorn Helgaas (convert the driver from ACPI to PNP) and Randy Dunlap (Kconfig
fixes).

The IR decoding can still be improved but the driver works for in daily use
decoding RC6 commands, so I believe it is ready to go upstream.

-- 
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] 15+ messages in thread

* [patch 1/2] Add a shutdown method to pnp drivers
  2009-08-09  9:56 [patch 0/2] Winbond IR Driver - v2 david
@ 2009-08-09  9:56 ` david
  2009-08-09  9:56 ` [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware david
  2009-08-15  3:02 ` [patch 0/2] Winbond IR Driver - v2 Maxim Levitsky
  2 siblings, 0 replies; 15+ 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-pnp-shutdown-method --]
[-- Type: TEXT/PLAIN, Size: 1947 bytes --]

The shutdown method is used by the winbond cir driver to setup the
hardware for wake-from-S5.

Changes since last version: the previous patch added a shutdown method
to acpi drivers, the winbond cir driver is now a pnp driver so the
shutdown method is added to pnp drivers instead. Patch by
Bjorn Helgaas <bjorn.helgaas@hp.com>.

Signed-off-by: Bjorn Helgaas <bjorn.helgaas@hp.com>
Signed-off-by: David Härdeman <david@hardeman.nu>

Index: linux-2.6/drivers/pnp/driver.c
===================================================================
--- linux-2.6.orig/drivers/pnp/driver.c	2009-07-25 19:53:32.000000000 +0200
+++ linux-2.6/drivers/pnp/driver.c	2009-07-25 19:54:21.000000000 +0200
@@ -135,6 +135,15 @@
 	return 0;
 }
 
+static void pnp_device_shutdown(struct device *dev)
+{
+	struct pnp_dev *pnp_dev = to_pnp_dev(dev);
+	struct pnp_driver *drv = pnp_dev->driver;
+
+	if (drv && drv->shutdown)
+		drv->shutdown(pnp_dev);
+}
+
 static int pnp_bus_match(struct device *dev, struct device_driver *drv)
 {
 	struct pnp_dev *pnp_dev = to_pnp_dev(dev);
@@ -203,6 +212,7 @@
 	.match   = pnp_bus_match,
 	.probe   = pnp_device_probe,
 	.remove  = pnp_device_remove,
+	.shutdown = pnp_device_shutdown,
 	.suspend = pnp_bus_suspend,
 	.resume  = pnp_bus_resume,
 	.dev_attrs = pnp_interface_attrs,
Index: linux-2.6/include/linux/pnp.h
===================================================================
--- linux-2.6.orig/include/linux/pnp.h	2009-07-25 19:53:32.000000000 +0200
+++ linux-2.6/include/linux/pnp.h	2009-07-25 19:54:21.000000000 +0200
@@ -360,6 +360,7 @@
 	unsigned int flags;
 	int (*probe) (struct pnp_dev *dev, const struct pnp_device_id *dev_id);
 	void (*remove) (struct pnp_dev *dev);
+	void (*shutdown) (struct pnp_dev *dev);
 	int (*suspend) (struct pnp_dev *dev, pm_message_t state);
 	int (*resume) (struct pnp_dev *dev);
 	struct device_driver driver;

-- 
David Härdeman

^ permalink raw reply	[flat|nested] 15+ 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 ` [patch 1/2] Add a shutdown method to pnp drivers david
@ 2009-08-09  9:56 ` david
  2009-08-10 15:39   ` Bjorn Helgaas
  2009-08-13  6:58   ` Dmitry Torokhov
  2009-08-15  3:02 ` [patch 0/2] Winbond IR Driver - v2 Maxim Levitsky
  2 siblings, 2 replies; 15+ 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] 15+ messages in thread

* Re: [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware
  2009-08-09  9:56 ` [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware david
@ 2009-08-10 15:39   ` Bjorn Helgaas
  2009-08-13  6:58   ` Dmitry Torokhov
  1 sibling, 0 replies; 15+ 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] 15+ messages in thread

* Re: [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware
  2009-08-09  9:56 ` [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware 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 17:14     ` David Härdeman
  1 sibling, 2 replies; 15+ 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] 15+ 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 15:56       ` Dmitry Torokhov
  2009-08-13 17:14     ` David Härdeman
  1 sibling, 1 reply; 15+ 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] 15+ 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
  2009-08-13 17:58         ` David Härdeman
  0 siblings, 1 reply; 15+ 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] 15+ 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; 15+ 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] 15+ 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
  0 siblings, 0 replies; 15+ 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] 15+ messages in thread

* Re: [patch 0/2] Winbond IR Driver - v2
  2009-08-09  9:56 [patch 0/2] Winbond IR Driver - v2 david
  2009-08-09  9:56 ` [patch 1/2] Add a shutdown method to pnp drivers david
  2009-08-09  9:56 ` [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware david
@ 2009-08-15  3:02 ` Maxim Levitsky
  2009-08-15 20:10   ` David Härdeman
  2 siblings, 1 reply; 15+ messages in thread
From: Maxim Levitsky @ 2009-08-15  3:02 UTC (permalink / raw)
  To: david
  Cc: linux-kernel, linux-input, jbarnes, akpm, bjorn.helgaas, randy.dunlap

On Sun, 2009-08-09 at 11:56 +0200, david@hardeman.nu wrote:
> Here's a new patch set which should replace all the patches currently in the
> -mm tree for the winbond cir driver. The new series incorporate feedback from
> Bjorn Helgaas (convert the driver from ACPI to PNP) and Randy Dunlap (Kconfig
> fixes).
> 
> The IR decoding can still be improved but the driver works for in daily use
> decoding RC6 commands, so I believe it is ready to go upstream.
> 

Hi.

As I understand, this hardware returns sampled IR signal, so it can be
used with any remote, right?

Then why not to implement lirc driver?

This driver as I understand is a driver for single remote shipped with
the notebook.

I have recently wrote a lirc driver for  my receiver, a lirc driver. Now
I can use all my remotes.

And no, I don't need any software support for that. LIRC happily
forwards all events via uinput to kernel, so I use it a an ordinary
keyboard.

Best regards,
	Maxim Levitsky


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

* Re: [patch 0/2] Winbond IR Driver - v2
  2009-08-15  3:02 ` [patch 0/2] Winbond IR Driver - v2 Maxim Levitsky
@ 2009-08-15 20:10   ` David Härdeman
  2009-08-15 22:10     ` Maxim Levitsky
  0 siblings, 1 reply; 15+ messages in thread
From: David Härdeman @ 2009-08-15 20:10 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: linux-kernel, linux-input, jbarnes, akpm, bjorn.helgaas,
	randy.dunlap, dmitry.torokhov

On Sat, Aug 15, 2009 at 06:02:27AM +0300, Maxim Levitsky wrote:
>On Sun, 2009-08-09 at 11:56 +0200, david@hardeman.nu wrote:
>> Here's a new patch set which should replace all the patches currently in the
>> -mm tree for the winbond cir driver. The new series incorporate feedback from
>> Bjorn Helgaas (convert the driver from ACPI to PNP) and Randy Dunlap (Kconfig
>> fixes).
>> 
>> The IR decoding can still be improved but the driver works for in daily use
>> decoding RC6 commands, so I believe it is ready to go upstream.
>> 
>
>Hi.

Hi,

>As I understand, this hardware returns sampled IR signal, so it can be
>used with any remote, right?

Yes.

>Then why not to implement lirc driver?

That's a fair question, but I'm afraid you're putting the cart before 
the horse.

The question is not why anyone would want to write an in-kernel driver 
but rather why anyone would want to write an out-of-kernel driver.

There have been repeated attempts to get LIRC merged with the kernel, 
and the feedback has been pretty consistent - make it part of the input 
subsystem.

I have written the driver for the input system and it limits the driver 
somewhat. I am working on extending the input system to accomodate IR 
drivers (see the discussion of EV_IR on the linux-input list).

As an example, the input system already has a quite extensive set of 
additional functions to deal with force-feedback hardware (mostly 
additional ioctl's, see include/linux/input.h). I want the input system 
to grow similar extensions for IR hardware which would then allow IR 
drivers to be written as input drivers with their full functionality 
exposed to user-space.

Feel free to help me out in implementing that API, and porting LIRC 
drivers, and all the benefits of in-kernel drivers will flow from that 
work.

To be more specific, the things that are on my radar right now are:

o IR TX - I believe an IOCTL which will take an array of signed integers 
           symbolizing IR on/off timings together with parameters for IR 
           carrier modulation is the way to go and to let userspace deal 
           with the generation of those integers (for a number of 
           reasons).

o IR RX - We need to expose the full functionality of hardware with 
           multiple receivers to userspace. Microsoft's current CIR specs 
           mandate that IR hardware that includes transmitters must 
           include both a long-distance, narrow-band receiver for common 
           use and a short-distance, wide-band receiver for learning 
           purposes. Expect future hardware to meet those demands...

o Hardware differences -
           The lircd daemon currently has a "-H" parameter to specify the 
           driver to use. To me, that is a dead giveaway for an 
           inconsistent kernel <-> userspace API. We can do better, given 
           the proper HW flags from the input layer, userspace should 
           adapt automatically. Some hardware, say 
           drivers/media/dvb/ttpci/budget-ci.c (where you'll find my name 
           in the git logs) only support passing decoded RC5 events. Some 
           hardware (like the lirc mceusb(2) driver) passes the timing 
           for the IR signals and supports any remotely sane IR protocol. 
           The WPCD376I has even a bit more capabilities like 
           wake-from-poweroff, two receivers and up to four transmitters
           with the capability to detect which transmitters are connected 
           and which ones are not.

           The challenge at this point is not in writing drivers for the 
           hardware, it is designing an extension to the input layer that 
           can sanely deal with the diversity.

Anyhow, do check the linux-input list for my EV_IR patches, they are the 
first step in that direction (and they're based on the API proposal by 
Jon Smirl posted at the end of last year). Raw signalling information 
for wonky remotes is part of that proposal (though the patches are not 
fully fleshed out yet).

>This driver as I understand is a driver for single remote shipped with
>the notebook.

It's a driver for a winbond chipset shipped with many Intel desktop 
"media" motherboards (DP35DP, DG33TL, DX388T, DX488T2, DP455G, DG45ID 
and DG45FC are the ones I'm aware of).

>I have recently wrote a lirc driver for  my receiver, a lirc driver. Now
>I can use all my remotes.

So can I. But for a easy-to-use and consistent user interface, some 
changes are needed to the input layer.

>And no, I don't need any software support for that. LIRC happily
>forwards all events via uinput to kernel, so I use it a an ordinary
>keyboard.

I know LIRC and what it can do, look in the CVS logs and the LIRC 
mailing list archives and I think you'll find my name there.

Regards,
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] 15+ messages in thread

* Re: [patch 0/2] Winbond IR Driver - v2
  2009-08-15 20:10   ` David Härdeman
@ 2009-08-15 22:10     ` Maxim Levitsky
  2009-08-16 18:43       ` David Härdeman
  0 siblings, 1 reply; 15+ messages in thread
From: Maxim Levitsky @ 2009-08-15 22:10 UTC (permalink / raw)
  To: David Härdeman
  Cc: linux-kernel, linux-input, jbarnes, akpm, bjorn.helgaas,
	randy.dunlap, dmitry.torokhov

On Sat, 2009-08-15 at 22:10 +0200, David Härdeman wrote:
> On Sat, Aug 15, 2009 at 06:02:27AM +0300, Maxim Levitsky wrote:
> >On Sun, 2009-08-09 at 11:56 +0200, david@hardeman.nu wrote:
> >> Here's a new patch set which should replace all the patches currently in the
> >> -mm tree for the winbond cir driver. The new series incorporate feedback from
> >> Bjorn Helgaas (convert the driver from ACPI to PNP) and Randy Dunlap (Kconfig
> >> fixes).
> >> 
> >> The IR decoding can still be improved but the driver works for in daily use
> >> decoding RC6 commands, so I believe it is ready to go upstream.
> >> 
> >
> >Hi.
> 
> Hi,
> 
> >As I understand, this hardware returns sampled IR signal, so it can be
> >used with any remote, right?
> 
> Yes.
> 
> >Then why not to implement lirc driver?
> 
> That's a fair question, but I'm afraid you're putting the cart before 
> the horse.
No, I just want to write the driver that fully exposes the hardware. I
don't care if it has to be outside or not.

> 
> The question is not why anyone would want to write an in-kernel driver 
> but rather why anyone would want to write an out-of-kernel driver.
> 
> There have been repeated attempts to get LIRC merged with the kernel, 
> and the feedback has been pretty consistent - make it part of the input 
> subsystem.
> 
> I have written the driver for theo input system and it limits the driver 
> somewhat. I am working on extending the input system to accomodate IR 
> drivers (see the discussion of EV_IR on the linux-input list).
The EV_IR thing is that he attempts to put all IR decoding in kernel,
and on top of that create a configfs config system.
I first thought it would be nice, but then realized that this is really
bad idea.
Currently LIRC has very oiled system for decoding pretty much every
remote that exist. It can cope with all kind of troubles, including not
very accurate receivers.
On top of that there are pure userspace devices, like a IR diode
connected to soundcard. It would be nice to do all the raw signal
decoding in one place. Once signal is decoded, lirc forwards the input
signal to the kernel via uinput, so it is a part of input system.

The way kernel hands in the raw IR data to lirc doesn't matter much. It
is really just a queue of numbers. It can be forced into input system,
but there is really no need for that.





> 
> As an example, the input system already has a quite extensive set of 
> additional functions to deal with force-feedback hardware (mostly 
> additional ioctl's, see include/linux/input.h). I want the input system 
> to grow similar extensions for IR hardware which would then allow IR 
> drivers to be written as input drivers with their full functionality 
> exposed to user-space.
This is straight forward to implement as an ioctl for that lirc device.

> 
> Feel free to help me out in implementing that API, and porting LIRC 
> drivers, and all the benefits of in-kernel drivers will flow from that 
> work.
This isn't a bad idea.


> 
> To be more specific, the things that are on my radar right now are:
> 
> o IR TX - I believe an IOCTL which will take an array of signed integers 
>            symbolizing IR on/off timings together with parameters for IR 
>            carrier modulation is the way to go and to let userspace deal 
>            with the generation of those integers (for a number of 
>            reasons).
This is done in lirc already via ioctls.

> 
> o IR RX - We need to expose the full functionality of hardware with 
>            multiple receivers to userspace. Microsoft's current CIR specs 
>            mandate that IR hardware that includes transmitters must 
>            include both a long-distance, narrow-band receiver for common 
>            use and a short-distance, wide-band receiver for learning 
>            purposes. Expect future hardware to meet those demands...
> 
> o Hardware differences -
>            The lircd daemon currently has a "-H" parameter to specify the 
>            driver to use. To me, that is a dead giveaway for an 
>            inconsistent kernel <-> userspace API. We can do better, given 
>            the proper HW flags from the input layer, userspace should 
>            adapt automatically. Some hardware, say 
>            drivers/media/dvb/ttpci/budget-ci.c (where you'll find my name 
>            in the git logs) only support passing decoded RC5 events. Some 
>            hardware (like the lirc mceusb(2) driver) passes the timing 
>            for the IR signals and supports any remotely sane IR protocol. 
>            The WPCD376I has even a bit more capabilities like 
>            wake-from-poweroff, two receivers and up to four transmitters
>            with the capability to detect which transmitters are connected 
>            and which ones are not.
This isn't a problem. LIRC has a 'generic' driver "default" its name I
think, that deals with kernel drivers. It has the -H for all kinds of
wierd and old devices that work via userspace.

> 
>            The challenge at this point is not in writing drivers for the 
>            hardware, it is designing an extension to the input layer that 
>            can sanely deal with the diversity.
> 
> Anyhow, do check the linux-input list for my EV_IR patches, they are the 
> first step in that direction (and they're based on the API proposal by 
> Jon Smirl posted at the end of last year). Raw signalling information 
> for wonky remotes is part of that proposal (though the patches are not 
> fully fleshed out yet).
> 
> >This driver as I understand is a driver for single remote shipped with
> >the notebook.
> 
> It's a driver for a winbond chipset shipped with many Intel desktop 
> "media" motherboards (DP35DP, DG33TL, DX388T, DX488T2, DP455G, DG45ID 
> and DG45FC are the ones I'm aware of).
But it won't work with my JVC remote?....

> 
> >I have recently wrote a lirc driver for  my receiver, a lirc driver. Now
> >I can use all my remotes.
> 
> So can I. But for a easy-to-use and consistent user interface, some 
> changes are needed to the input layer.


Best regards,
	Maxim Levitsky

> 
> >And no, I don't need any software support for that. LIRC happily
> >forwards all events via uinput to kernel, so I use it a an ordinary
> >keyboard.
> 
> I know LIRC and what it can do, look in the CVS logs and the LIRC 
> mailing list archives and I think you'll find my name there.
> 
> Regards,
> 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] 15+ messages in thread

* Re: [patch 0/2] Winbond IR Driver - v2
  2009-08-15 22:10     ` Maxim Levitsky
@ 2009-08-16 18:43       ` David Härdeman
  2009-08-17  3:56         ` Maxim Levitsky
  0 siblings, 1 reply; 15+ messages in thread
From: David Härdeman @ 2009-08-16 18:43 UTC (permalink / raw)
  To: Maxim Levitsky; +Cc: linux-kernel, linux-input, jbarnes, akpm, dmitry.torokhov

On Sun, Aug 16, 2009 at 01:10:11AM +0300, Maxim Levitsky wrote:
>On Sat, 2009-08-15 at 22:10 +0200, David Härdeman wrote:
>> On Sat, Aug 15, 2009 at 06:02:27AM +0300, Maxim Levitsky wrote:
>>>
>>> Then why not to implement lirc driver?
>>
>> That's a fair question, but I'm afraid you're putting the cart before
>> the horse.
>
>No, I just want to write the driver that fully exposes the hardware. I
>don't care if it has to be outside or not.

That's not a very user-friendly sentiment. The entire idea is to extend 
the input subsystem so that it fully exposes the hardware *and* gives 
users the benefit of in-kernel drivers.

>> The question is not why anyone would want to write an in-kernel driver
>> but rather why anyone would want to write an out-of-kernel driver.
>>
>> There have been repeated attempts to get LIRC merged with the kernel,
>> and the feedback has been pretty consistent - make it part of the input
>> subsystem.
>>
>> I have written the driver for theo input system and it limits the driver
>> somewhat. I am working on extending the input system to accomodate IR
>> drivers (see the discussion of EV_IR on the linux-input list).
>
>The EV_IR thing is that he attempts to put all IR decoding in kernel,
>and on top of that create a configfs config system.

I never proposed a configfs system. I only proposed a part of Jon 
Smirl's EV_IR functionality. I think the configfs system as well as the 
in-kernel protocol _en_coders are overengineering.

>I first thought it would be nice, but then realized that this is really
>bad idea.
>Currently LIRC has very oiled system for decoding pretty much every
>remote that exist. It can cope with all kind of troubles, including not
>very accurate receivers.

I think you've misunderstood my EV_IR suggestion on the linux-input 
list. Part of that proposal is to allow drivers to generate IR_RAW 
timing events (if asked to do so via an ioctl), then you could continue 
to use lirc with some minimal changes to the lirc daemon while still 
getting the benefits of in-kernel drivers. I have a hard time seeing 
what would be wrong with that? Whether the input subsystem *also* 
includes (optional) IR decoding or not should not matter to lirc fans as 
long as it includes some kind of IR_RAW support (which it does both in 
Jon's proposal and in mine).

>On top of that there are pure userspace devices, like a IR diode
>connected to soundcard. It would be nice to do all the raw signal
>decoding in one place. Once signal is decoded, lirc forwards the input
>signal to the kernel via uinput, so it is a part of input system.

That's a red herring, there is always going to be esoteric DIY hardware, 
and no matter which API is used in the kernel, that hardware can always 
use user-space drivers.

>The way kernel hands in the raw IR data to lirc doesn't matter much. It
>is really just a queue of numbers. It can be forced into input system,
>but there is really no need for that.

There really is a need if you want in-kernel drivers.

>> Feel free to help me out in implementing that API, and porting LIRC
>> drivers, and all the benefits of in-kernel drivers will flow from that
>> work.
>
>This isn't a bad idea.

Good, we agree on this point, which is the most important one. IR_RAW 
should be enough for the lirc daemon, right? So let's make sure 
something like that gets added to the input subsystem and we can take it 
from there...

>>>This driver as I understand is a driver for single remote shipped with
>>>the notebook.
>>
>> It's a driver for a winbond chipset shipped with many Intel desktop
>> "media" motherboards (DP35DP, DG33TL, DX388T, DX488T2, DP455G, DG45ID
>> and DG45FC are the ones I'm aware of).
>
>But it won't work with my JVC remote?....

Not sure what you JVC remote has to do with the Intel mainboards that 
include the WPCD376I chipset.

Anyway, based on past experience of JVC remotes I'm guessing that your 
remote uses some version of the NEC protocol, in that case it will be 
supported...and IR_RAW (or something similar) will be supported if it is 
accepted by the input subsystem maintainers so you will additionally be 
able to use that air conditioning remote which implements a completely 
wonky IR protocol together with lirc - if you want to...

Regards,
David Härdeman

(trimmed some people of the CC list which are unlikely to be interested 
in this discussion, I hope Dmitry will speak up soon).

--
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] 15+ messages in thread

* Re: [patch 0/2] Winbond IR Driver - v2
  2009-08-16 18:43       ` David Härdeman
@ 2009-08-17  3:56         ` Maxim Levitsky
  0 siblings, 0 replies; 15+ messages in thread
From: Maxim Levitsky @ 2009-08-17  3:56 UTC (permalink / raw)
  To: David Härdeman
  Cc: linux-kernel, linux-input, jbarnes, akpm, dmitry.torokhov

On Sun, 2009-08-16 at 20:43 +0200, David Härdeman wrote:
> On Sun, Aug 16, 2009 at 01:10:11AM +0300, Maxim Levitsky wrote:
> >On Sat, 2009-08-15 at 22:10 +0200, David Härdeman wrote:
> >> On Sat, Aug 15, 2009 at 06:02:27AM +0300, Maxim Levitsky wrote:
> >>>
> >>> Then why not to implement lirc driver?
> >>
> >> That's a fair question, but I'm afraid you're putting the cart before
> >> the horse.
> >
> >No, I just want to write the driver that fully exposes the hardware. I
> >don't care if it has to be outside or not.
> 
> That's not a very user-friendly sentiment. The entire idea is to extend 
> the input subsystem so that it fully exposes the hardware *and* gives 
> users the benefit of in-kernel drivers.
> 
> >> The question is not why anyone would want to write an in-kernel driver
> >> but rather why anyone would want to write an out-of-kernel driver.
It doesn't matter that much. I have written a driver. It works.
Eventually some IR subsystem will make into kernel, I then port my
driver to that system.

Currently, there is no such system. attempting to write a driver that
decode just one remote, in my opinion aren't that great.
IR_RAW of course, it nice, and will solve all problems, so go ahead with
that.

As long as there is a normal (not debug or so) access to raw data, its
fine with me.

I do think however that in kernel ir decoders are redundant, except
maybe some embedded systems.


> >>
> >> There have been repeated attempts to get LIRC merged with the kernel,
> >> and the feedback has been pretty consistent - make it part of the input
> >> subsystem.
> >>
> >> I have written the driver for theo input system and it limits the driver
> >> somewhat. I am working on extending the input system to accomodate IR
> >> drivers (see the discussion of EV_IR on the linux-input list).
> >
> >The EV_IR thing is that he attempts to put all IR decoding in kernel,
> >and on top of that create a configfs config system.
> 
> I never proposed a configfs system. I only proposed a part of Jon 
> Smirl's EV_IR functionality. I think the configfs system as well as the 
> in-kernel protocol _en_coders are overengineering.
and decoders?


> 
> >I first thought it would be nice, but then realized that this is really
> >bad idea.
> >Currently LIRC has very oiled system for decoding pretty much every
> >remote that exist. It can cope with all kind of troubles, including not
> >very accurate receivers.
> 
> I think you've misunderstood my EV_IR suggestion on the linux-input 
> list. Part of that proposal is to allow drivers to generate IR_RAW 
> timing events (if asked to do so via an ioctl), then you could continue 
> to use lirc with some minimal changes to the lirc daemon while still 
> getting the benefits of in-kernel drivers. I have a hard time seeing 
> what would be wrong with that? Whether the input subsystem *also* 
> includes (optional) IR decoding or not should not matter to lirc fans as 
> long as it includes some kind of IR_RAW support (which it does both in 
> Jon's proposal and in mine).
Thats of course is different.
I am not yet a lirc fan :-), but I can assure you that there are many
protocols. much more that we think.

The way lirc works, ensures that it can adapt to any remote.

> 
> >On top of that there are pure userspace devices, like a IR diode
> >connected to soundcard. It would be nice to do all the raw signal
> >decoding in one place. Once signal is decoded, lirc forwards the input
> >signal to the kernel via uinput, so it is a part of input system.
> 
> That's a red herring, there is always going to be esoteric DIY hardware, 
> and no matter which API is used in the kernel, that hardware can always 
> use user-space drivers.
The point is that doing it all in userspace helps ensure that settings
are kept in one place.
I have a lirc.conf. It will work with any receiver, homebrew or not.

> 
> >The way kernel hands in the raw IR data to lirc doesn't matter much. It
> >is really just a queue of numbers. It can be forced into input system,
> >but there is really no need for that.
I personally don't care. If you want to send IR raw data via input
layer, it is very fine with me. I am sure that lirc devs won't mind that
ether.



> 
> There really is a need if you want in-kernel drivers.
> 
> >> Feel free to help me out in implementing that API, and porting LIRC
> >> drivers, and all the benefits of in-kernel drivers will flow from that
> >> work.
> >
> >This isn't a bad idea.
> 
> Good, we agree on this point, which is the most important one. IR_RAW 
> should be enough for the lirc daemon, right? So let's make sure 
> something like that gets added to the input subsystem and we can take it 
> from there...
This indeed will be great.


> 
> >>>This driver as I understand is a driver for single remote shipped with
> >>>the notebook.
> >>
> >> It's a driver for a winbond chipset shipped with many Intel desktop
> >> "media" motherboards (DP35DP, DG33TL, DX388T, DX488T2, DP455G, DG45ID
> >> and DG45FC are the ones I'm aware of).
> >
> >But it won't work with my JVC remote?....
> 
> Not sure what you JVC remote has to do with the Intel mainboards that 
> include the WPCD376I chipset.
Lets see. I have a remote from a VCR I no longer use, I now use it with
my computer....
It isn't a NEC protocol, it actually has its own protocol (JVC)
But all these protocols are very similar.


> 
> Anyway, based on past experience of JVC remotes I'm guessing that your 
> remote uses some version of the NEC protocol, in that case it will be 
> supported...and IR_RAW (or something similar) will be supported if it is 
> accepted by the input subsystem maintainers so you will additionally be 
> able to use that air conditioning remote which implements a completely 
> wonky IR protocol together with lirc - if you want to...
Indeed. Not with lirc, as it doesn't implement support for decoding such
remotes... but it is trivial to add such support, or write my own app
for that.


So I do agree with you mostly. I don't care how the raw timings will be
given to lirc.


Best regards,
	Maxim Levitsky



> 
> Regards,
> David Härdeman
> 
> (trimmed some people of the CC list which are unlikely to be interested 
> in this discussion, I hope Dmitry will speak up soon).
> 

--
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] 15+ messages in thread

* [patch 0/2] Winbond IR Driver - v2
@ 2009-08-11 16:26 David Härdeman
  0 siblings, 0 replies; 15+ 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

Here's a new patch set which should replace all the patches currently in the
-mm tree for the winbond cir driver. The new series incorporate feedback from
Bjorn Helgaas (convert the driver from ACPI to PNP) and Randy Dunlap (Kconfig
fixes).

This set fixes the comments Bjorn Helgaas had on the previous set (wrong Kconfig
depends).

The IR decoding can still be improved but the driver works for in daily use
decoding RC6 commands, so I believe it is ready to go upstream. 

-- 
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] 15+ messages in thread

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

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-08-09  9:56 [patch 0/2] Winbond IR Driver - v2 david
2009-08-09  9:56 ` [patch 1/2] Add a shutdown method to pnp drivers david
2009-08-09  9:56 ` [patch 2/2] Add a driver for the Winbond WPCD376I Consumer IR hardware 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 15:56       ` Dmitry Torokhov
2009-08-13 17:58         ` David Härdeman
2009-08-13 17:14     ` David Härdeman
2009-08-15  3:02 ` [patch 0/2] Winbond IR Driver - v2 Maxim Levitsky
2009-08-15 20:10   ` David Härdeman
2009-08-15 22:10     ` Maxim Levitsky
2009-08-16 18:43       ` David Härdeman
2009-08-17  3:56         ` Maxim Levitsky
2009-08-11 16:26 David Härdeman

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).