[RFC/PATCH] Winbond CIR driver for the WPCD376I chip (ACPI/PNP idWEC1022)
From: David Härdeman
Date: Wed Jun 24 2009 - 17:46:23 EST
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@xxxxxxxxxxx>
* Copyright (C) 2009 David Härdeman <david@xxxxxxxxxxx>
*
* 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@xxxxxxxxxxx>");
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@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/