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@hardeman.nu>
* Copyright (C) 2009 David Härdeman <david@hardeman.nu>
*
* Dedicated to Matilda, my newborn daughter, without whose loving attention
* this driver would have been finished in half the time and with a fraction
* of the bugs.
*
* Written using:
* o Winbond WPCD376I datasheet helpfully provided by Jesse Barnes at Intel
* o NatSemi PC87338/PC97338 datasheet (for the serial port stuff)
* o DSDT dumps
*
* Supported features:
* o RC6
* o Wake-On-CIR functionality
*
* To do:
* o Test NEC and RC5
*
* Left as an exercise for the reader:
* o Learning (I have neither the hardware, nor the need)
* o IR Transmit (ibid)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/module.h>
#include <linux/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/* 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 spacen");
return;
}
/* Start bit */
if (get-bits(data, 2) != 0x02) {
dprintk("RC6 - Invalid start bitn");
return;
}
/* Mode */
mode = get-bits(data, 6);
switch (mode) {
case 0x15:
mode = 0;
break;
case 0x29:
mode = 6;
break;
default:
dprintk("RC6 - Invalid moden");
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 errorn");
break;
}
/* Customer */
if (mode == 6) {
if (toggle != 0) {
dprintk("RC6B - Not Supportedn");
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%08Xn",
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 bitn");
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 messagen");
return;
}
dprintk("IR-RC5 ad %u cm %u t %u s %un",
(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 spacen");
return;
}
/* Repeat? */
if (get-bits(data, 1)) {
if (!data->keypressed) {
dprintk("NEC - Stray repeat messagen");
return;
}
dprintk("IR-NEC repeat s %un",
(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 spacen");
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 messagen");
return;
}
/* Check command validity */
if (command1 != ~command2) {
dprintk("NEC - Command bytes mismatchn");
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 %un",
(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 scancoden");
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 scancoden");
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 scancoden");
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 scancoden");
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 moden");
}
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%08Xn", 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 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 %un", 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 case IR-PROTOCOL-NEC:
case IR-PROTOCOL-RC6:
break;
default:
printk(KERN-ERR DRVNAME ": Invalid protocol argumentn");
return -EINVAL;
}
ret = acpi-bus-register-driver(&wbcir-driver);
if (ret)
printk(KERN-ERR DRVNAME ": Unable to register drivern");
return ret;
}
static void MODULE-DESCRIPTION("Winbond SuperI/O Consumer IR Driver");
MODULE-LICENSE("GPL");
module-init(wbcir-init);
module-exit(wbcir-exit);
Re: RFC/PATCH - Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022) by Jesse Barnes on
2009-06-24T22:13:57+00:00
On Wed, 24 Jun 2009 14:36:45 -0700
David Härdeman <david@hardeman.nu> 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
", 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...
Re: RFC/PATCH - Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022) by Alan Cox on
2009-06-24T22:44:32+00:00
> 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.
Re: RFC/PATCH - Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022) by David Härdeman on
2009-06-25T11:46:25+00:00
On Thu, June 25, 2009 00:13, Jesse Barnes wrote:
> On Wed, 24 Jun 2009 14:36:45 -0700
> David Härdeman <david@hardeman.nu> 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
", 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)
Re: RFC/PATCH - Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022) by David Härdeman on
2009-06-25T12:28:35+00:00
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.
Re: RFC/PATCH - Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022) by Alan Cox on
2009-06-25T12:48:44+00:00
> 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
Re: RFC/PATCH - Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022) by David Härdeman on
2009-06-25T13:14:57+00:00
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?
Re: RFC/PATCH - Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022) by Alan Cox on
2009-06-25T13:16:42+00:00
> 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.
Re: RFC/PATCH - Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022) by David Härdeman on
2009-06-25T13:29:30+00:00
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?
Re: RFC/PATCH - Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022) by Alan Cox on
2009-06-25T13:34:30+00:00
On Thu, 25 Jun 2009 15:28:31 +0200 (CEST)
David Härdeman <david@hardeman.nu> 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
Re: RFC/PATCH - Winbond CIR driver for the WPCD376I chip (ACPI/PNP id WEC1022) by Jesse Barnes on
2009-06-25T16:21:03+00:00
On Thu, 25 Jun 2009 04:46:01 -0700
David Härdeman <david@hardeman.nu> wrote:
> On Thu, June 25, 2009 00:13, Jesse Barnes wrote:
> > On Wed, 24 Jun 2009 14:36:45 -0700
> > David Härdeman <david@hardeman.nu> 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.