2009-06-24 21:46:18

by David Härdeman

[permalink] [raw]
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 <[email protected]>
* Copyright (C) 2009 David H?rdeman <[email protected]>
*
* 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 <[email protected]>");
MODULE_DESCRIPTION("Winbond SuperI/O Consumer IR Driver");
MODULE_LICENSE("GPL");

module_init(wbcir_init);
module_exit(wbcir_exit);


2009-06-24 22:13:45

by Jesse Barnes

[permalink] [raw]
Subject: Re: [RFC/PATCH] Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022)

On Wed, 24 Jun 2009 14:36:45 -0700
David Härdeman <[email protected]> wrote:

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

Yay, glad I could get these released for you. I just did a quick scan
of the driver (notes below), I'm sure others will have comments too.
I'd guess Andrew would be the one to pick this up and send it to Linus
(probably sooner rather than later, no reason to block a small and
reasonable looking driver from going upstream quickly).

> 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?

Yeah, often multifunction devices like this have higher level "bus
drivers" that take care of managing the global parts, and drivers that
attach to it to manage individual functions. If you were feeling
really ambitious you could do that for the superio chip and port any
sub-drivers... :)

> 2) Location of driver
>
> Where should this driver go in the tree? drivers/platform/x86/?

drivers/char is probably fine.

> 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?).

Not sure, I'd have to check the ACPI docs about this. Len or someone
on the ACPI mailing list would probably know though.

> 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).

Question for Dmitry and the input guys I guess.

> 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?

Alan should know the answer to this question.

> 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?

You could use a GFP_ATOMIC allocation... but it's best if you can avoid
that.


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

Maybe you could use the generic debug functions instead (pr_debug iirc)?

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

There are a few magic numbers above here you could possibly make into
#defines just to make things more readable.

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

The key up/down timeout handling seems like a pretty general problem,
maybe the input layer has some helpers for it? Dunno.

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

Are these just for debugging? If so, you could put them in debugfs
instead...

--
Jesse Barnes, Intel Open Source Technology Center

2009-06-24 22:44:20

by Alan

[permalink] [raw]
Subject: Re: [RFC/PATCH] Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022)

> 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?

I'm looking at a similar case (clash between super I/O config for serial
and watchdog) at the moment that affects a proposed driver for some
serial port save/restore config stuff. We can request_region to avoid
collisions but there is no wait mechanism for super I/O devices which
probably wants fixing with a simple list of super I/O ports and a helper
lib. I was thinking something like

handle = superio_request(name, dev, start, len);

EBUSY - someone else has the I/O space registered other than
super I/O. The super I/O lib would then request the I/O space and
hog it.

superio_claim(handle, block)

Claim the super I/O providing another person isn't using it,
optionally wait if so

superio_release(handle);

Give back the claim

superio_free(handle)

Free allocation


> 2) Location of driver
>
> Where should this driver go in the tree? drivers/platform/x86/?

Not if the device is not x86 specific - eg a generic super I/O device

> 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?

How similar is it to a normal UART and if it looks like a normal UART why
not drive it as one ?

> 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

Thats actually a common way to do it and usually cleaner than the
alternatives.

2009-06-25 11:46:12

by David Härdeman

[permalink] [raw]
Subject: Re: [RFC/PATCH] Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022)

On Thu, June 25, 2009 00:13, Jesse Barnes wrote:
> On Wed, 24 Jun 2009 14:36:45 -0700
> David H?rdeman <[email protected]> wrote:
>
>> I've written a driver for the
...
>> Winbond WPCD376I chipset
>
> Yay, glad I could get these released for you. I just did a quick scan
> of the driver (notes below)

Two more things that Intel could provide:

a) Publish the datasheet (I know you mentioned doing this but
I can't find it on the Intel website)

b) Make the hardware needed to actually use the CIR functionality
available for purchase. http://www.easy-cir.com seems to be more
or less dead (which is curious since an ad for the website
seems to be included with every CIR-enabled Intel motherboard).
I had to solder my own IR receiver in order to write the driver.

>> I'd appreciate having the driver reviewed...and in addition I have
>> some questions for the list:
>>
>> 1) SuperI/O concurrency
>> ...
>
> Yeah, often multifunction devices like this have higher level "bus
> drivers" that take care of managing the global parts, and drivers that
> attach to it to manage individual functions. If you were feeling
> really ambitious you could do that for the superio chip and port any
> sub-drivers... :)

My ambitions are more directed towards some kind of IR-subsystem into the
kernel at the moment :) Besides, the Intel mainboards doesn't actually
seem to use any of the other logical devices (which are mostly supported
by existing drivers anyway).

>> Where should this driver go in the tree? drivers/platform/x86/?
>
> drivers/char is probably fine.

I'm leaning towards drivers/input/misc now...

>> #define dprintk(fmt, arg...) \
>> do { \
>> if (debug) \
>> printk(KERN_DEBUG DRVNAME fmt , ## arg); \
>> } while (0)
>
> Maybe you could use the generic debug functions instead (pr_debug iirc)?

Yes

> ...
> There are a few magic numbers above here you could possibly make into
> #defines just to make things more readable.

I'll try

> The key up/down timeout handling seems like a pretty general problem,
> maybe the input layer has some helpers for it? Dunno.

drivers/media/common/ir-functions.c is the closest thing I could find
while writing the driver. The functions there aren't usable because they
do not properly implement the toggle/repeat handling and it forces the use
of a small, fixed-size keymap. The same problem existed when I improved
the IR functionality in drivers/media/dvb/ttpci/budget-ci.c by the way, so
a generic version could probably be added to ir-functions in the future.

>> 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,
>> };
>
> Are these just for debugging? If so, you could put them in debugfs
> instead...

No, they are there to help the user when generating a keymap for an
unknown remote. Press key on remote, read value from
/sys/.../last_scancode, add line saying "0x12345678 = KEY_EXPLODE" to
keymap file, repeat...there aren't any user-friendly tools for this yet
though.

(Dropped Terry from the CC, I just saw that he had requested a driver for
this chip earlier but I'm not sure he's that interested in the rest of the
discussion)

--
David H?rdeman

2009-06-25 12:28:21

by David Härdeman

[permalink] [raw]
Subject: Re: [RFC/PATCH] Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022)

On Thu, June 25, 2009 00:45, Alan Cox wrote:
>> 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?
>
> I'm looking at a similar case (clash between super I/O config for serial
> and watchdog) at the moment that affects a proposed driver for some
> serial port save/restore config stuff. We can request_region to avoid
> collisions but there is no wait mechanism for super I/O devices which
> probably wants fixing with a simple list of super I/O ports and a helper
> lib. I was thinking something like
>
> handle = superio_request(name, dev, start, len);
>
> EBUSY - someone else has the I/O space registered other than
> super I/O. The super I/O lib would then request the I/O space and
> hog it.
>
> superio_claim(handle, block)
>
> Claim the super I/O providing another person isn't using it,
> optionally wait if so
>
> superio_release(handle);
>
> Give back the claim
>
> superio_free(handle)
>
> Free allocation

How would it work in conjunction with ACPI? When I looked at the ACPI
dumps for my motherboard, the AML code seemed happy to fiddle with global
SuperI/O registers (protected using an internal mutex).

>> 2) Location of driver
>>
>> Where should this driver go in the tree? drivers/platform/x86/?
>
> Not if the device is not x86 specific - eg a generic super I/O device

I think the SuperI/O device itself can only be found on Intel motherboards
at the moment (Winbond told me that the WPCD376I was an Intel-specific
design), but perhaps drivers/input/misc is a better fit?

>> 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?
>
> How similar is it to a normal UART and if it looks like a normal UART why
> not drive it as one ?

(Sorry this is a brain-dump, not intended to be a rant, hopefully you can
follow my line of reasoning and provide some feedback)

I'm not that familiar with UART's in general, but the fact that I could
write a working driver using a datasheet for a 10 year older SuperI/O chip
from a different manufacturer suggests that the register layout is fairly
standard :)

There are some differences, for example, the Wake-On-IR (a separate
logical device with two sets of registers on its own) registers control
the input (there is one long-range command input and a short-range,
wide-band learning input) and output (there are 4 different IR blaster
pins which output can be directed to) as well as the baud clock generator
(24/48Mhz) for the serial port.

Considering that it's IR-only hardware, some of the uart registers and
operations (such as the modem control register, modem status interrupts,
break signals, termios and timeouts) make no sense. Baud rates can be set
freely but there is little point in doing so.

The UART detects IR activity and then continues sampling data to the RX
fifo until told to stop doing so (that's what the
how-many-0xff-bytes-in-a-row check in the irq handler does).

Also, some special handling (like disabling RX while doing TX) might be
necessary in order not to receive the sent IR commands.

Also, I don't think it's very user-friendly to have a IR device show up as
/dev/tty...how would userspace be able to find out that it's actually an
IR transceiver?

If the driver supported all of the capabilities of the hardware, there
would need to be a way to select the TX output (1-4) and RX input
(learn/command) as well as some other minor things (like getting a report
on which of the 4 TX outputs that are actually connected). These are
functionalities which are supported in Vista by using a number of
CIR-specific ioctls (and by creating a CIR-specific driver class).

The WPCD376I hardware matches the requirements Microsoft has made for
Vista MCE IR receivers (and is therefore identical, feature-wise to other
alternatives such as the SIO1049 chip, as they all want that Vista
MCE-compatible badge), so I imagine that future IR hardware will have the
same capability.

I'm not saying that the Linux kernel should replicate Vista, but some kind
of CIR-specific ioctl's or other solution will have to be defined anyway,
and the same ioctl's (or whatever) will also need to work for e.g. a
USB-based CIR transceiver or other hardware which doesn't happen to
include an UART.

Therefore I still think it's better to model the driver as an input
device, and ultimately to teach the input subsystem some specifics of CIR
devices, similar to how force-feedback devices are handled today.

--
David H?rdeman

2009-06-25 12:48:29

by Alan

[permalink] [raw]
Subject: Re: [RFC/PATCH] Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022)

> How would it work in conjunction with ACPI? When I looked at the ACPI
> dumps for my motherboard, the AML code seemed happy to fiddle with global
> SuperI/O registers (protected using an internal mutex).

If the device is ACPI managed in this way then all accesses to all devices
at that address must go via ACPI as I understand it.

> Also, I don't think it's very user-friendly to have a IR device show up as
> /dev/tty...how would userspace be able to find out that it's actually an
> IR transceiver?

Thats easy to fix - the question is more "is it an 8250 UART with some
quirks" or is it best driven by another device.

You can certainly stop the serial layer grabbing it (or undo that)
providing the port isn't the console (which in this case would make no
sense).

> Therefore I still think it's better to model the driver as an input
> device, and ultimately to teach the input subsystem some specifics of CIR
> devices, similar to how force-feedback devices are handled today.

Seems reasonable for this device.

Alan

2009-06-25 13:14:43

by David Härdeman

[permalink] [raw]
Subject: Re: [RFC/PATCH] Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022)

On Thu, June 25, 2009 14:49, Alan Cox wrote:
> You can certainly stop the serial layer grabbing it (or undo that)
> providing the port isn't the console (which in this case would make no
> sense).

Which way of stopping the serial layer from grabbing the port did you have
in mind?

If I call uart_remove_one_port, I'd need to get struct uart_driver *drv
and struct uart_port *port from somewhere. Also, it'd be quite ugly to
have /dev/ttyX appear and magically disappear during boot (since 8250 is
compiled in with most distro kernels and my driver would be a module which
would be loaded much later).

The only alternative I could think of would be to get the serial core to
check with acpi_check_resource_conflict for ports that have not been
discovered via PNP/ACPI?

--
David H?rdeman

2009-06-25 13:16:30

by Alan

[permalink] [raw]
Subject: Re: [RFC/PATCH] Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022)

> Which way of stopping the serial layer from grabbing the port did you have
> in mind?
>
> If I call uart_remove_one_port, I'd need to get struct uart_driver *drv
> and struct uart_port *port from somewhere. Also, it'd be quite ugly to
> have /dev/ttyX appear and magically disappear during boot (since 8250 is
> compiled in with most distro kernels and my driver would be a module which
> would be loaded much later).
>
> The only alternative I could think of would be to get the serial core to
> check with acpi_check_resource_conflict for ports that have not been
> discovered via PNP/ACPI?

You can vanish it with setserial as stands. There isn't a good
interface for doing that from kernel side but as you can see from
setserial the infrastructure is all there to add it.

2009-06-25 13:28:56

by David Härdeman

[permalink] [raw]
Subject: Re: [RFC/PATCH] Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022)

On Thu, June 25, 2009 15:17, Alan Cox wrote:
>> Which way of stopping the serial layer from grabbing the port did you
>> have in mind?
>
> You can vanish it with setserial as stands. There isn't a good
> interface for doing that from kernel side but as you can see from
> setserial the infrastructure is all there to add it.

Seems user-unfriendly...wouldn't blacklisting that particular port (using
ACPI or PNP id or something) be a better solution?

--
David H?rdeman

2009-06-25 13:34:17

by Alan

[permalink] [raw]
Subject: Re: [RFC/PATCH] Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022)

On Thu, 25 Jun 2009 15:28:31 +0200 (CEST)
David H?rdeman <[email protected]> wrote:

> On Thu, June 25, 2009 15:17, Alan Cox wrote:
> >> Which way of stopping the serial layer from grabbing the port did you
> >> have in mind?
> >
> > You can vanish it with setserial as stands. There isn't a good
> > interface for doing that from kernel side but as you can see from
> > setserial the infrastructure is all there to add it.
>
> Seems user-unfriendly...wouldn't blacklisting that particular port (using
> ACPI or PNP id or something) be a better solution?

Possibly - what I am saying is that the mechanisms exist internally for
this including flipping a port at run time between IR and normal modes
when appropriate

2009-06-25 16:20:43

by Jesse Barnes

[permalink] [raw]
Subject: Re: [RFC/PATCH] Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022)

On Thu, 25 Jun 2009 04:46:01 -0700
David Härdeman <[email protected]> wrote:

> On Thu, June 25, 2009 00:13, Jesse Barnes wrote:
> > On Wed, 24 Jun 2009 14:36:45 -0700
> > David Härdeman <[email protected]> wrote:
> >
> >> I've written a driver for the
> ...
> >> Winbond WPCD376I chipset
> >
> > Yay, glad I could get these released for you. I just did a quick
> > scan of the driver (notes below)
>
> Two more things that Intel could provide:
>
> a) Publish the datasheet (I know you mentioned doing this but
> I can't find it on the Intel website)

Ah I was hoping that had been done already; I'll ping the docs people
about it.

> b) Make the hardware needed to actually use the CIR functionality
> available for purchase. http://www.easy-cir.com seems to be more
> or less dead (which is curious since an ad for the website
> seems to be included with every CIR-enabled Intel motherboard).
> I had to solder my own IR receiver in order to write the driver.

Oh that might be harder. We just provide the boards for OEMs and
resellers; often not made directly for end users...

> >> Where should this driver go in the tree? drivers/platform/x86/?
> >
> > drivers/char is probably fine.
>
> I'm leaning towards drivers/input/misc now...

Seems ok too.

> > The key up/down timeout handling seems like a pretty general
> > problem, maybe the input layer has some helpers for it? Dunno.
>
> drivers/media/common/ir-functions.c is the closest thing I could find
> while writing the driver. The functions there aren't usable because
> they do not properly implement the toggle/repeat handling and it
> forces the use of a small, fixed-size keymap. The same problem
> existed when I improved the IR functionality in
> drivers/media/dvb/ttpci/budget-ci.c by the way, so a generic version
> could probably be added to ir-functions in the future.

Sounds good.

> > Are these just for debugging? If so, you could put them in debugfs
> > instead...
>
> No, they are there to help the user when generating a keymap for an
> unknown remote. Press key on remote, read value from
> /sys/.../last_scancode, add line saying "0x12345678 = KEY_EXPLODE" to
> keymap file, repeat...there aren't any user-friendly tools for this
> yet though.

Ah right, yeah that's a good use for sysfs.

--
Jesse Barnes, Intel Open Source Technology Center

2009-10-06 07:55:38

by Sean Young

[permalink] [raw]
Subject: Re: [RFC/PATCH] Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022)

On Thu, Jun 25, 2009 at 02:35:33PM +0100, Alan Cox wrote:
> On Thu, 25 Jun 2009 15:28:31 +0200 (CEST)
> David H?rdeman <[email protected]> wrote:
> > On Thu, June 25, 2009 15:17, Alan Cox wrote:
> > >> Which way of stopping the serial layer from grabbing the port did you
> > >> have in mind?
> > >
> > > You can vanish it with setserial as stands. There isn't a good
> > > interface for doing that from kernel side but as you can see from
> > > setserial the infrastructure is all there to add it.
> >
> > Seems user-unfriendly...wouldn't blacklisting that particular port (using
> > ACPI or PNP id or something) be a better solution?
>
> Possibly - what I am saying is that the mechanisms exist internally for
> this including flipping a port at run time between IR and normal modes
> when appropriate

Thanks to the documentation Jesse Barnes provided me:

This particular "serial port" is a bastardised serial port. From a
software perspective it looks like a serial port with extensions, but
it can only function as an IR device to the physical world (there are no
uart pins for this port on the superio chip). So there is no reason
to flip to "uart" mode.

Due to the extensions it can't be used with just the serial interface;
some functions won't be available to userspace.

The problem here is that arch/x86/include/asm/serial.h defines
SERIAL_PORT_DFN, which lists the four serial ports which are detected
in serial8250_isa_init_ports(). The bastardised IR serial port is
detected as a serial port and then cannot be claimed by the winbond-cir
driver.

There is a real serial port on this superio chip which is accurately
described by PNP. Should we really be guessing what hardware is present
and then getting a false positive on modern x86 hardware?

Alternatively, we could:

1) Detect the type of port better and discard it as unusable

I'm not sure this can be done. On an earlier version of this Super I/O
chip (the PC8374L), this port can be used as an uart and I can't find no
way of detecting the difference other than the Super I/O chip (see below).

2) Detect the presence of the WEC1022 PNP id, somehow

Not sure how this can be done, depends on initialisation order.

3) Detect the Super I/O chip and ignore this port

This is done in the patch below. Any review comments would be appreciated;
it does work.

Actually, Super I/O detection is done in several places in the tree.
Shouldn't there be a central place which does this detection? This would
also allow switching of port modes on Super I/O chips which do support
that.

4) Make the port vanish.

Having hardware appear and disappear because it doesn't really exist seems
like a horrible kludge to me.

Thanks,
Sean
--
diff --git a/drivers/serial/8250.c b/drivers/serial/8250.c
index b1ae774..0555453 100644
--- a/drivers/serial/8250.c
+++ b/drivers/serial/8250.c
@@ -926,6 +926,36 @@ static int broken_efr(struct uart_8250_port *up)
return 0;
}

+#ifdef CONFIG_X86
+/*
+ * The WPCD376I has one fake NS16550 port (the second serial port) which can
+ * only be used for IR purposes. It cannot be used as an UART.
+ */
+#define CHIP_ID_REG 0x20 /* Super I/O ID (SID) / family */
+#define CHIP_REV_REG 0x27 /* Super I/O revision ID (SRID) */
+
+static int superio_is_WPCD376I()
+{
+ int rc = -ENOENT;
+
+ if (!request_region(0x2e, 2, "wpcd376i"))
+ return -EBUSY;
+
+ outb(CHIP_ID_REG, 0x2e);
+ if (inb(0x2e) != CHIP_ID_REG || inb(0x2f) != 0xf1)
+ goto out;
+
+ outb(CHIP_REV_REG, 0x2e);
+ if (inb(0x2e) == CHIP_REV_REG && (inb(0x2f) & 0xe0) == 0x80)
+ rc = 0;
+
+out:
+ release_region(0x2e, 2);
+
+ return rc;
+}
+#endif
+
/*
* We know that the chip has FIFOs. Does it have an EFR? The
* EFR is located in the same register position as the IIR and
@@ -1006,9 +1036,14 @@ static void autoconfig_16550a(struct uart_8250_port *up)

serial_outp(up, UART_LCR, 0);

- up->port.uartclk = 921600*16;
- up->port.type = PORT_NS16550A;
- up->capabilities |= UART_NATSEMI;
+ if (!superio_is_WPCD376I()) {
+ up->capabilities &= ~UART_CAP_FIFO;
+ up->port.type = PORT_UNKNOWN;
+ } else {
+ up->port.uartclk = 921600*16;
+ up->port.type = PORT_NS16550A;
+ up->capabilities |= UART_NATSEMI;
+ }
return;
}
}