lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date:	Wed, 24 Jun 2009 23:36:45 +0200
From:	David Härdeman <david@...deman.nu>
To:	linux-kernel@...r.kernel.org
Cc:	jesse.barnes@...el.com, terry@...m.ltd.uk
Subject: [RFC/PATCH] Winbond CIR driver for the WPCD376I chip (ACPI/PNP id
	WEC1022)

I've written a driver for the Consumer IR (CIR) functionality of the 
Winbond WPCD376I chipset (found on e.g. Intel DG45FC motherboards) using 
documentation helpfully provided by Jesse Barnes at Intel.

The driver currently supports receiving IR commands (only tested RC6 
using a "Vista" remote so far) and wake from sleep/power-off (haven't 
tested sleep yet, can't get the DG45FC to suspend/resume properly).

I'd appreciate having the driver reviewed...and in addition I have some 
questions for the list:

   1) SuperI/O concurrency

Lots of drivers support one or more logical devices provided by 
different SuperI/O chips, but there seems to be no synchronisation 
between the different drivers? Since my driver gets all info from ACPI, 
it's no real problem here, but I'm curious...shouldn't there be some 
kind of synchronisation between SuperI/O drivers which might all be 
changing global registers, such as the logical device select register?


   2) Location of driver

Where should this driver go in the tree? drivers/platform/x86/?


   3) ACPI resource order

Using ACPI I can get the three I/O memory ranges and the IRQ used by the 
device, but how do I actually know for sure that the order that my 
board/BIOS returns those resources will be the same as all other 
motherboard/BIOS combinations? It seems kind of weird that ACPI provides 
all this info without any tags to tell the driver which of the resources 
is to be used for what (I'm assuming this is an ACPI limitation?).


   4) Input layer changes, 32 bit scancodes

In order to support RC6 (as well as RC5 and NEC), the driver currently 
relies on 32 bit scancodes using a sparse keymap. I'm not sure if this 
is a good approach or not. The input syscalls all seem to use an int for 
the scancode (which will be at least 32 bits on any platform which has 
the hardware - i.e. x86 and amd64), but I'm worried if this is an "ok" 
use of the input layer?

Might it be a good idea to add IR specific ioctls to the input subsystem 
(similar to the force feedback ones) which allows different IR codes to 
be specified in a clearer manner? (this is also relevant to e.g. 
drivers/media/dvb/ttpci/budget-ci.c where I've meddled in the IR 
functionality, that driver is currently artificially limited to 
supporting one RC5 address only due to input limitations).


   5) Other winbond devices

Not so much a question as a note to others that the driver might also be 
useful for other Winbond chips in the WEC102X PNP range by making some 
trivial changes (i.e. making the wake-on-CIR parts optional for chips 
which lack the functionality)...I've even seen that SuperI/O chips such 
as the National Semiconductor PC87338/PC97338 seem to have uart 
registers similar enough to use the same driver...


   6) Reclaiming the serial port

The serial port which the WPCD376I uses for IR TX/RX is only useful for 
Consumer IR, but it looks enough like a "normal" uart for the serial 
driver to claim the port. I currently have to boot with 
"8250.nr_uarts=1" to stop the serial driver from using the IR uart 
(there is one "real" serial port in the chip). However, that's not a 
very elegant or user-friendly option. Is there a way to blacklist the 
port in the serial driver and/or to reclaim the port from the serial 
driver when the CIR driver is loaded?


   7) kmalloc and spinlocks

In wbcir_setkeycode the driver might need to kmalloc memory for a new 
keytable entry, but kmalloc isn't allowed with rwlocks held so I've 
currently written the driver to do a kmalloc before taking the rwlock 
and then to kfree it later if it wasn't necessary, which feels quite 
inelegant to me. Any suggestions on a better approach?


And with all that said...on to the driver :)

Regards,
David Härdeman

-- 


/*
  *  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@...deman.nu>
  *     Copyright (C) 2009 David Härdeman <david@...deman.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/acpi.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>

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

/* 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_ACPI_CLASS	"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		},
};

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;

	/* The rest is protected by wbcir_lock */
	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 */
	u8 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)");

static uint debug; /* default = 0 */
module_param(debug, bool, 0644);
MODULE_PARM_DESC(debug, "Print debugging information");

#define dprintk(fmt, arg...) 					\
do {								\
	if (debug)						\
		printk(KERN_DEBUG DRVNAME fmt , ## arg);	\
} while (0)



/*****************************************************************************
  *
  * UTILITY FUNCTIONS
  *
  *****************************************************************************/

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);
}

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) & 0x80)
		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 : 0x80, 0x80);
}

static u8
wbcir_revbyte(u8 byte)
{
	byte = ((byte >> 1) & 0x55) | ((byte << 1) & 0xAA);
	byte = ((byte >> 2) & 0x33) | ((byte << 2) & 0xCC);
	return (byte >> 4) | (byte<<4);
}

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 sscancode, int *keycode)
{
	unsigned int scancode = (unsigned int)sscancode;
	struct wbcir_data *data = input_get_drvdata(dev);

	if (scancode < 0 || scancode > 0xFFFFFFFF)
		return -EINVAL;

	*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;
	unsigned int scancode = (unsigned int)sscancode;

	if (scancode < 0 || scancode > 0xFFFFFFFF)
		return -EINVAL;

	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 = (u32)scancode;
		new_keyentry->key.keycode = (unsigned int)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;
}

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

	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 (set)
		data->irdata[data->irdata_count / 8] |=
			0x01 << (data->irdata_count % 8);
	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 (data->irdata[data->irdata_off / 8] &
		    (0x01 << (data->irdata_off % 8)))
			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 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)) {
		dprintk("RC6 - Invalid leader space\n");
		return;
	}

	/* Start bit */
	if (get_bits(data, 2) != 0x02) {
		dprintk("RC6 - Invalid start bit\n");
		return;
	}

	/* Mode */
	mode = get_bits(data, 6);
	switch (mode) {
	case 0x15:
		mode = 0;
		break;
	case 0x29:
		mode = 6;
		break;
	default:
		dprintk("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:
		dprintk("RC6 - Toggle bit error\n");
		break;
	}

	/* Customer */
	if (mode == 6) {
		if (toggle != 0) {
			dprintk("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) {
		dprintk("RC6 - Cell error(s)\n");
		return;
	}

	dprintk("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 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)) {
		dprintk("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) {
		dprintk("RC5 - Invalid message\n");
		return;
	}

	dprintk("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 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)) {
		dprintk("NEC - Invalid leader space\n");
		return;
	}

	/* Repeat? */
	if (get_bits(data, 1)) {
		if (!data->keypressed) {
			dprintk("NEC - Stray repeat message\n");
			return;
		}

		dprintk("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)) {
		dprintk("NEC - Invalid leader space\n");
		return;
	}

	address1  = wbcir_revbyte(get_bits(data, 8));
	address2  = wbcir_revbyte(get_bits(data, 8));
	command1  = wbcir_revbyte(get_bits(data, 8));
	command2  = wbcir_revbyte(get_bits(data, 8));

	/* Sanity check */
	if (data->irdata_error) {
		dprintk("NEC - Invalid message\n");
		return;
	}

	/* Check command validity */
	if (command1 != ~command2) {
		dprintk("NEC - Command bytes mismatch\n");
		return;
	}

	/* Check for extended NEC protocol */
	address = address1;
	if (address1 != ~address2)
		address |= address2 << 8;

	scancode = address << 8 | command1;

	dprintk("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 acpi_device *device = cookie;
	struct wbcir_data *data = acpi_driver_data(device);
	u8 status;
	u8 bdata;
	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 & 0x05)) {
		spin_unlock_irqrestore(&wbcir_lock, flags);
		return IRQ_NONE;
	}

	if (status & 0x04)
		data->irdata_error = 1;

	if (!(status & 0x01))
		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) & 0x01)
			inb(data->sbase + WBCIR_REG_SP3_RXDATA);

		/* And set RXINACTIVE */
		outb(0x20, data->sbase + WBCIR_REG_SP3_ASCR);

		if (debug) {
			printk(KERN_DEBUG DRVNAME ": IR DATA - ");
			for (i = 0; i < data->irdata_count; i++) {
				bdata = data->irdata[i/8] & (1 << (i % 8));
				printk("%i", bdata ? 1 : 0);
			}
			printk("\n");
		}

		switch (protocol) {
		case IR_PROTOCOL_RC5:
			wbcir_parse_rc5(data);
			break;
		case IR_PROTOCOL_RC6:
			wbcir_parse_rc6(data);
			break;
		case IR_PROTOCOL_NEC:
			wbcir_parse_nec(data);
			break;
		}

		wbcir_reset_irdata(data);
		data->idle_count = 0;
	}

out:
	spin_unlock_irqrestore(&wbcir_lock, flags);
	return IRQ_HANDLED;
}



/*****************************************************************************
  *
  * SUSPEND/RESUME FUNCTIONS
  *
  *****************************************************************************/

static int
wbcir_shutdown(struct acpi_device *device)
{
	struct device *dev = &device->dev;
	struct wbcir_data *data = acpi_driver_data(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] = wbcir_revbyte((wake_sc & 0xFF));
		match[0] = ~match[1];

		match[3] = wbcir_revbyte((wake_sc & 0xFF00) >> 8);
		if (wake_sc > 0xFFFF)
			match[2] = wbcir_revbyte((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, 0x10, 0x3F);
		outsb(data->wbase + WBCIR_REG_WCEIR_INDEX, match, 11);
		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX, 0x20, 0x3F);
		outsb(data->wbase + WBCIR_REG_WCEIR_INDEX, 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(0x00, data->sbase + WBCIR_REG_SP3_IER);

	return 0;
}

static int
wbcir_suspend(struct acpi_device *device, pm_message_t state)
{
	return wbcir_shutdown(device);
}

static int
wbcir_resume(struct acpi_device *device)
{
	struct wbcir_data *data = acpi_driver_data(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(0x05, 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 acpi_device *device = container_of(dev, struct acpi_device, dev);
	struct wbcir_data *data = acpi_driver_data(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 acpi_status
wbcir_walk_resources(struct acpi_resource *resource, void *context)
{
	struct wbcir_data *data = context;

	switch (resource->type) {

	case ACPI_RESOURCE_TYPE_IO:
		if (!data->ebase) {
			if (resource->data.io.address_length !=
			    EHFUNC_IOMEM_LEN)
				goto error;
			data->ebase = resource->data.io.minimum;
		} else if (!data->wbase) {
			if (resource->data.io.address_length !=
			    WAKEUP_IOMEM_LEN)
				goto error;
			data->wbase = resource->data.io.minimum;
		} else if (!data->sbase) {
			if (resource->data.io.address_length !=
			    SP_IOMEM_LEN)
				goto error;
			data->sbase = resource->data.io.minimum;
		} else {
			goto error;
		}
		break;

	case ACPI_RESOURCE_TYPE_IRQ:
		if (resource->data.irq.interrupt_count != 1)
			goto error;
		else if (!data->irq)
			data->irq = resource->data.irq.interrupts[0];
		else
			goto error;
		break;

	case ACPI_RESOURCE_TYPE_END_TAG:
		break;

	default:
		goto error;
	}

	return AE_OK;

error:
	return AE_ERROR;
}

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 */
	outb(0x04, 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_add(struct acpi_device *device)
{
	struct device *dev = &device->dev;
	struct wbcir_data *data;
	acpi_status status;
	int err;

	data = kzalloc(sizeof(*data), GFP_KERNEL);
	if (!data) {
		err = -ENOMEM;
		goto exit;
	}

	device->driver_data = data;

	status = acpi_walk_resources(device->handle, METHOD_NAME__CRS,
				     wbcir_walk_resources, data);

	if (ACPI_FAILURE(status) || data->wbase == 0 || data->ebase == 0 ||
	    data->sbase == 0 || data->irq == 0) {
		err = -ENODEV;
		dev_err(dev, "Invalid ACPI 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(0x00, data->sbase + WBCIR_REG_SP3_IER);

	/* Enable extended mode */
	wbcir_select_bank(data, WBCIR_BANK_2);
	outb(0x01, 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(0x05, 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);
	device->driver_data = NULL;
exit:
	return err;
}

static int
wbcir_remove(struct acpi_device *device, int type)
{
	struct wbcir_data *data = acpi_driver_data(device);
	struct wbcir_keyentry *key;
	struct wbcir_keyentry *keytmp;

	/* Disable interrupts */
	wbcir_select_bank(data, WBCIR_BANK_0);
	outb(0x00, 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);

	device->driver_data = NULL;

	return 0;
}

static const struct acpi_device_id wbcir_ids[] = {
	{ "WEC1022", 0 },
	{ "", 0 }
};
MODULE_DEVICE_TABLE(acpi, wbcir_ids);

static struct acpi_driver wbcir_driver = {
	.name  = WBCIR_ACPI_NAME,
	.class = WBCIR_ACPI_CLASS,
	.ids   = wbcir_ids,
	.ops = {
		.add      = wbcir_add,
		.remove   = wbcir_remove,
		.suspend  = wbcir_suspend,
		.resume   = wbcir_resume,
		.shutdown = wbcir_shutdown
	},
	.owner = THIS_MODULE
};

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 = acpi_bus_register_driver(&wbcir_driver);
	if (ret)
		printk(KERN_ERR DRVNAME ": Unable to register driver\n");

	return ret;
}

static void __exit
wbcir_exit(void)
{
	acpi_bus_unregister_driver(&wbcir_driver);
}

MODULE_AUTHOR("David Härdeman <david@...deman.nu>");
MODULE_DESCRIPTION("Winbond SuperI/O Consumer IR Driver");
MODULE_LICENSE("GPL");

module_init(wbcir_init);
module_exit(wbcir_exit);


--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ