![Tejun Heo](/assets/img/avatar_default.png)
percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
1609 lines
41 KiB
C
1609 lines
41 KiB
C
/*
|
|
* winbond-cir.c - Driver for the Consumer IR functionality of Winbond
|
|
* SuperI/O chips.
|
|
*
|
|
* Currently supports the Winbond WPCD376i chip (PNP id WEC1022), but
|
|
* could probably support others (Winbond WEC102X, NatSemi, etc)
|
|
* with minor modifications.
|
|
*
|
|
* Original Author: David Härdeman <david@hardeman.nu>
|
|
* Copyright (C) 2009 David Härdeman <david@hardeman.nu>
|
|
*
|
|
* Dedicated to Matilda, my newborn daughter, without whose loving attention
|
|
* this driver would have been finished in half the time and with a fraction
|
|
* of the bugs.
|
|
*
|
|
* Written using:
|
|
* o Winbond WPCD376I datasheet helpfully provided by Jesse Barnes at Intel
|
|
* o NatSemi PC87338/PC97338 datasheet (for the serial port stuff)
|
|
* o DSDT dumps
|
|
*
|
|
* Supported features:
|
|
* o RC6
|
|
* o Wake-On-CIR functionality
|
|
*
|
|
* To do:
|
|
* o Test NEC and RC5
|
|
*
|
|
* Left as an exercise for the reader:
|
|
* o Learning (I have neither the hardware, nor the need)
|
|
* o IR Transmit (ibid)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/pnp.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/input.h>
|
|
#include <linux/leds.h>
|
|
#include <linux/list.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/pci_ids.h>
|
|
#include <linux/io.h>
|
|
#include <linux/bitrev.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/slab.h>
|
|
|
|
#define DRVNAME "winbond-cir"
|
|
|
|
/* CEIR Wake-Up Registers, relative to data->wbase */
|
|
#define WBCIR_REG_WCEIR_CTL 0x03 /* CEIR Receiver Control */
|
|
#define WBCIR_REG_WCEIR_STS 0x04 /* CEIR Receiver Status */
|
|
#define WBCIR_REG_WCEIR_EV_EN 0x05 /* CEIR Receiver Event Enable */
|
|
#define WBCIR_REG_WCEIR_CNTL 0x06 /* CEIR Receiver Counter Low */
|
|
#define WBCIR_REG_WCEIR_CNTH 0x07 /* CEIR Receiver Counter High */
|
|
#define WBCIR_REG_WCEIR_INDEX 0x08 /* CEIR Receiver Index */
|
|
#define WBCIR_REG_WCEIR_DATA 0x09 /* CEIR Receiver Data */
|
|
#define WBCIR_REG_WCEIR_CSL 0x0A /* CEIR Re. Compare Strlen */
|
|
#define WBCIR_REG_WCEIR_CFG1 0x0B /* CEIR Re. Configuration 1 */
|
|
#define WBCIR_REG_WCEIR_CFG2 0x0C /* CEIR Re. Configuration 2 */
|
|
|
|
/* CEIR Enhanced Functionality Registers, relative to data->ebase */
|
|
#define WBCIR_REG_ECEIR_CTS 0x00 /* Enhanced IR Control Status */
|
|
#define WBCIR_REG_ECEIR_CCTL 0x01 /* Infrared Counter Control */
|
|
#define WBCIR_REG_ECEIR_CNT_LO 0x02 /* Infrared Counter LSB */
|
|
#define WBCIR_REG_ECEIR_CNT_HI 0x03 /* Infrared Counter MSB */
|
|
#define WBCIR_REG_ECEIR_IREM 0x04 /* Infrared Emitter Status */
|
|
|
|
/* SP3 Banked Registers, relative to data->sbase */
|
|
#define WBCIR_REG_SP3_BSR 0x03 /* Bank Select, all banks */
|
|
/* Bank 0 */
|
|
#define WBCIR_REG_SP3_RXDATA 0x00 /* FIFO RX data (r) */
|
|
#define WBCIR_REG_SP3_TXDATA 0x00 /* FIFO TX data (w) */
|
|
#define WBCIR_REG_SP3_IER 0x01 /* Interrupt Enable */
|
|
#define WBCIR_REG_SP3_EIR 0x02 /* Event Identification (r) */
|
|
#define WBCIR_REG_SP3_FCR 0x02 /* FIFO Control (w) */
|
|
#define WBCIR_REG_SP3_MCR 0x04 /* Mode Control */
|
|
#define WBCIR_REG_SP3_LSR 0x05 /* Link Status */
|
|
#define WBCIR_REG_SP3_MSR 0x06 /* Modem Status */
|
|
#define WBCIR_REG_SP3_ASCR 0x07 /* Aux Status and Control */
|
|
/* Bank 2 */
|
|
#define WBCIR_REG_SP3_BGDL 0x00 /* Baud Divisor LSB */
|
|
#define WBCIR_REG_SP3_BGDH 0x01 /* Baud Divisor MSB */
|
|
#define WBCIR_REG_SP3_EXCR1 0x02 /* Extended Control 1 */
|
|
#define WBCIR_REG_SP3_EXCR2 0x04 /* Extended Control 2 */
|
|
#define WBCIR_REG_SP3_TXFLV 0x06 /* TX FIFO Level */
|
|
#define WBCIR_REG_SP3_RXFLV 0x07 /* RX FIFO Level */
|
|
/* Bank 3 */
|
|
#define WBCIR_REG_SP3_MRID 0x00 /* Module Identification */
|
|
#define WBCIR_REG_SP3_SH_LCR 0x01 /* LCR Shadow */
|
|
#define WBCIR_REG_SP3_SH_FCR 0x02 /* FCR Shadow */
|
|
/* Bank 4 */
|
|
#define WBCIR_REG_SP3_IRCR1 0x02 /* Infrared Control 1 */
|
|
/* Bank 5 */
|
|
#define WBCIR_REG_SP3_IRCR2 0x04 /* Infrared Control 2 */
|
|
/* Bank 6 */
|
|
#define WBCIR_REG_SP3_IRCR3 0x00 /* Infrared Control 3 */
|
|
#define WBCIR_REG_SP3_SIR_PW 0x02 /* SIR Pulse Width */
|
|
/* Bank 7 */
|
|
#define WBCIR_REG_SP3_IRRXDC 0x00 /* IR RX Demod Control */
|
|
#define WBCIR_REG_SP3_IRTXMC 0x01 /* IR TX Mod Control */
|
|
#define WBCIR_REG_SP3_RCCFG 0x02 /* CEIR Config */
|
|
#define WBCIR_REG_SP3_IRCFG1 0x04 /* Infrared Config 1 */
|
|
#define WBCIR_REG_SP3_IRCFG4 0x07 /* Infrared Config 4 */
|
|
|
|
/*
|
|
* Magic values follow
|
|
*/
|
|
|
|
/* No interrupts for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
|
|
#define WBCIR_IRQ_NONE 0x00
|
|
/* RX data bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
|
|
#define WBCIR_IRQ_RX 0x01
|
|
/* Over/Under-flow bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */
|
|
#define WBCIR_IRQ_ERR 0x04
|
|
/* Led enable/disable bit for WBCIR_REG_ECEIR_CTS */
|
|
#define WBCIR_LED_ENABLE 0x80
|
|
/* RX data available bit for WBCIR_REG_SP3_LSR */
|
|
#define WBCIR_RX_AVAIL 0x01
|
|
/* RX disable bit for WBCIR_REG_SP3_ASCR */
|
|
#define WBCIR_RX_DISABLE 0x20
|
|
/* Extended mode enable bit for WBCIR_REG_SP3_EXCR1 */
|
|
#define WBCIR_EXT_ENABLE 0x01
|
|
/* Select compare register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */
|
|
#define WBCIR_REGSEL_COMPARE 0x10
|
|
/* Select mask register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */
|
|
#define WBCIR_REGSEL_MASK 0x20
|
|
/* Starting address of selected register in WBCIR_REG_WCEIR_INDEX */
|
|
#define WBCIR_REG_ADDR0 0x00
|
|
|
|
/* Valid banks for the SP3 UART */
|
|
enum wbcir_bank {
|
|
WBCIR_BANK_0 = 0x00,
|
|
WBCIR_BANK_1 = 0x80,
|
|
WBCIR_BANK_2 = 0xE0,
|
|
WBCIR_BANK_3 = 0xE4,
|
|
WBCIR_BANK_4 = 0xE8,
|
|
WBCIR_BANK_5 = 0xEC,
|
|
WBCIR_BANK_6 = 0xF0,
|
|
WBCIR_BANK_7 = 0xF4,
|
|
};
|
|
|
|
/* Supported IR Protocols */
|
|
enum wbcir_protocol {
|
|
IR_PROTOCOL_RC5 = 0x0,
|
|
IR_PROTOCOL_NEC = 0x1,
|
|
IR_PROTOCOL_RC6 = 0x2,
|
|
};
|
|
|
|
/* Misc */
|
|
#define WBCIR_NAME "Winbond CIR"
|
|
#define WBCIR_ID_FAMILY 0xF1 /* Family ID for the WPCD376I */
|
|
#define WBCIR_ID_CHIP 0x04 /* Chip ID for the WPCD376I */
|
|
#define IR_KEYPRESS_TIMEOUT 250 /* FIXME: should be per-protocol? */
|
|
#define INVALID_SCANCODE 0x7FFFFFFF /* Invalid with all protos */
|
|
#define WAKEUP_IOMEM_LEN 0x10 /* Wake-Up I/O Reg Len */
|
|
#define EHFUNC_IOMEM_LEN 0x10 /* Enhanced Func I/O Reg Len */
|
|
#define SP_IOMEM_LEN 0x08 /* Serial Port 3 (IR) Reg Len */
|
|
#define WBCIR_MAX_IDLE_BYTES 10
|
|
|
|
static DEFINE_SPINLOCK(wbcir_lock);
|
|
static DEFINE_RWLOCK(keytable_lock);
|
|
|
|
struct wbcir_key {
|
|
u32 scancode;
|
|
unsigned int keycode;
|
|
};
|
|
|
|
struct wbcir_keyentry {
|
|
struct wbcir_key key;
|
|
struct list_head list;
|
|
};
|
|
|
|
static struct wbcir_key rc6_def_keymap[] = {
|
|
{ 0x800F0400, KEY_NUMERIC_0 },
|
|
{ 0x800F0401, KEY_NUMERIC_1 },
|
|
{ 0x800F0402, KEY_NUMERIC_2 },
|
|
{ 0x800F0403, KEY_NUMERIC_3 },
|
|
{ 0x800F0404, KEY_NUMERIC_4 },
|
|
{ 0x800F0405, KEY_NUMERIC_5 },
|
|
{ 0x800F0406, KEY_NUMERIC_6 },
|
|
{ 0x800F0407, KEY_NUMERIC_7 },
|
|
{ 0x800F0408, KEY_NUMERIC_8 },
|
|
{ 0x800F0409, KEY_NUMERIC_9 },
|
|
{ 0x800F041D, KEY_NUMERIC_STAR },
|
|
{ 0x800F041C, KEY_NUMERIC_POUND },
|
|
{ 0x800F0410, KEY_VOLUMEUP },
|
|
{ 0x800F0411, KEY_VOLUMEDOWN },
|
|
{ 0x800F0412, KEY_CHANNELUP },
|
|
{ 0x800F0413, KEY_CHANNELDOWN },
|
|
{ 0x800F040E, KEY_MUTE },
|
|
{ 0x800F040D, KEY_VENDOR }, /* Vista Logo Key */
|
|
{ 0x800F041E, KEY_UP },
|
|
{ 0x800F041F, KEY_DOWN },
|
|
{ 0x800F0420, KEY_LEFT },
|
|
{ 0x800F0421, KEY_RIGHT },
|
|
{ 0x800F0422, KEY_OK },
|
|
{ 0x800F0423, KEY_ESC },
|
|
{ 0x800F040F, KEY_INFO },
|
|
{ 0x800F040A, KEY_CLEAR },
|
|
{ 0x800F040B, KEY_ENTER },
|
|
{ 0x800F045B, KEY_RED },
|
|
{ 0x800F045C, KEY_GREEN },
|
|
{ 0x800F045D, KEY_YELLOW },
|
|
{ 0x800F045E, KEY_BLUE },
|
|
{ 0x800F045A, KEY_TEXT },
|
|
{ 0x800F0427, KEY_SWITCHVIDEOMODE },
|
|
{ 0x800F040C, KEY_POWER },
|
|
{ 0x800F0450, KEY_RADIO },
|
|
{ 0x800F0448, KEY_PVR },
|
|
{ 0x800F0447, KEY_AUDIO },
|
|
{ 0x800F0426, KEY_EPG },
|
|
{ 0x800F0449, KEY_CAMERA },
|
|
{ 0x800F0425, KEY_TV },
|
|
{ 0x800F044A, KEY_VIDEO },
|
|
{ 0x800F0424, KEY_DVD },
|
|
{ 0x800F0416, KEY_PLAY },
|
|
{ 0x800F0418, KEY_PAUSE },
|
|
{ 0x800F0419, KEY_STOP },
|
|
{ 0x800F0414, KEY_FASTFORWARD },
|
|
{ 0x800F041A, KEY_NEXT },
|
|
{ 0x800F041B, KEY_PREVIOUS },
|
|
{ 0x800F0415, KEY_REWIND },
|
|
{ 0x800F0417, KEY_RECORD },
|
|
};
|
|
|
|
/* Registers and other state is protected by wbcir_lock */
|
|
struct wbcir_data {
|
|
unsigned long wbase; /* Wake-Up Baseaddr */
|
|
unsigned long ebase; /* Enhanced Func. Baseaddr */
|
|
unsigned long sbase; /* Serial Port Baseaddr */
|
|
unsigned int irq; /* Serial Port IRQ */
|
|
|
|
struct input_dev *input_dev;
|
|
struct timer_list timer_keyup;
|
|
struct led_trigger *rxtrigger;
|
|
struct led_trigger *txtrigger;
|
|
struct led_classdev led;
|
|
|
|
u32 last_scancode;
|
|
unsigned int last_keycode;
|
|
u8 last_toggle;
|
|
u8 keypressed;
|
|
unsigned long keyup_jiffies;
|
|
unsigned int idle_count;
|
|
|
|
/* RX irdata and parsing state */
|
|
unsigned long irdata[30];
|
|
unsigned int irdata_count;
|
|
unsigned int irdata_idle;
|
|
unsigned int irdata_off;
|
|
unsigned int irdata_error;
|
|
|
|
/* Protected by keytable_lock */
|
|
struct list_head keytable;
|
|
};
|
|
|
|
static enum wbcir_protocol protocol = IR_PROTOCOL_RC6;
|
|
module_param(protocol, uint, 0444);
|
|
MODULE_PARM_DESC(protocol, "IR protocol to use "
|
|
"(0 = RC5, 1 = NEC, 2 = RC6A, default)");
|
|
|
|
static int invert; /* default = 0 */
|
|
module_param(invert, bool, 0444);
|
|
MODULE_PARM_DESC(invert, "Invert the signal from the IR receiver");
|
|
|
|
static unsigned int wake_sc = 0x800F040C;
|
|
module_param(wake_sc, uint, 0644);
|
|
MODULE_PARM_DESC(wake_sc, "Scancode of the power-on IR command");
|
|
|
|
static unsigned int wake_rc6mode = 6;
|
|
module_param(wake_rc6mode, uint, 0644);
|
|
MODULE_PARM_DESC(wake_rc6mode, "RC6 mode for the power-on command "
|
|
"(0 = 0, 6 = 6A, default)");
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* UTILITY FUNCTIONS
|
|
*
|
|
*****************************************************************************/
|
|
|
|
/* Caller needs to hold wbcir_lock */
|
|
static void
|
|
wbcir_set_bits(unsigned long addr, u8 bits, u8 mask)
|
|
{
|
|
u8 val;
|
|
|
|
val = inb(addr);
|
|
val = ((val & ~mask) | (bits & mask));
|
|
outb(val, addr);
|
|
}
|
|
|
|
/* Selects the register bank for the serial port */
|
|
static inline void
|
|
wbcir_select_bank(struct wbcir_data *data, enum wbcir_bank bank)
|
|
{
|
|
outb(bank, data->sbase + WBCIR_REG_SP3_BSR);
|
|
}
|
|
|
|
static enum led_brightness
|
|
wbcir_led_brightness_get(struct led_classdev *led_cdev)
|
|
{
|
|
struct wbcir_data *data = container_of(led_cdev,
|
|
struct wbcir_data,
|
|
led);
|
|
|
|
if (inb(data->ebase + WBCIR_REG_ECEIR_CTS) & WBCIR_LED_ENABLE)
|
|
return LED_FULL;
|
|
else
|
|
return LED_OFF;
|
|
}
|
|
|
|
static void
|
|
wbcir_led_brightness_set(struct led_classdev *led_cdev,
|
|
enum led_brightness brightness)
|
|
{
|
|
struct wbcir_data *data = container_of(led_cdev,
|
|
struct wbcir_data,
|
|
led);
|
|
|
|
wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CTS,
|
|
brightness == LED_OFF ? 0x00 : WBCIR_LED_ENABLE,
|
|
WBCIR_LED_ENABLE);
|
|
}
|
|
|
|
/* Manchester encodes bits to RC6 message cells (see wbcir_parse_rc6) */
|
|
static u8
|
|
wbcir_to_rc6cells(u8 val)
|
|
{
|
|
u8 coded = 0x00;
|
|
int i;
|
|
|
|
val &= 0x0F;
|
|
for (i = 0; i < 4; i++) {
|
|
if (val & 0x01)
|
|
coded |= 0x02 << (i * 2);
|
|
else
|
|
coded |= 0x01 << (i * 2);
|
|
val >>= 1;
|
|
}
|
|
|
|
return coded;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* INPUT FUNCTIONS
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static unsigned int
|
|
wbcir_do_getkeycode(struct wbcir_data *data, u32 scancode)
|
|
{
|
|
struct wbcir_keyentry *keyentry;
|
|
unsigned int keycode = KEY_RESERVED;
|
|
unsigned long flags;
|
|
|
|
read_lock_irqsave(&keytable_lock, flags);
|
|
|
|
list_for_each_entry(keyentry, &data->keytable, list) {
|
|
if (keyentry->key.scancode == scancode) {
|
|
keycode = keyentry->key.keycode;
|
|
break;
|
|
}
|
|
}
|
|
|
|
read_unlock_irqrestore(&keytable_lock, flags);
|
|
return keycode;
|
|
}
|
|
|
|
static int
|
|
wbcir_getkeycode(struct input_dev *dev,
|
|
unsigned int scancode, unsigned int *keycode)
|
|
{
|
|
struct wbcir_data *data = input_get_drvdata(dev);
|
|
|
|
*keycode = wbcir_do_getkeycode(data, scancode);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
wbcir_setkeycode(struct input_dev *dev,
|
|
unsigned int scancode, unsigned 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;
|
|
|
|
new_keyentry = kmalloc(sizeof(*new_keyentry), GFP_KERNEL);
|
|
if (!new_keyentry)
|
|
return -ENOMEM;
|
|
|
|
write_lock_irqsave(&keytable_lock, flags);
|
|
|
|
list_for_each_entry(keyentry, &data->keytable, list) {
|
|
if (keyentry->key.scancode != scancode)
|
|
continue;
|
|
|
|
old_keycode = keyentry->key.keycode;
|
|
keyentry->key.keycode = keycode;
|
|
|
|
if (keyentry->key.keycode == KEY_RESERVED) {
|
|
list_del(&keyentry->list);
|
|
kfree(keyentry);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
set_bit(keycode, dev->keybit);
|
|
|
|
if (old_keycode == KEY_RESERVED) {
|
|
new_keyentry->key.scancode = scancode;
|
|
new_keyentry->key.keycode = keycode;
|
|
list_add(&new_keyentry->list, &data->keytable);
|
|
} else {
|
|
kfree(new_keyentry);
|
|
clear_bit(old_keycode, dev->keybit);
|
|
list_for_each_entry(keyentry, &data->keytable, list) {
|
|
if (keyentry->key.keycode == old_keycode) {
|
|
set_bit(old_keycode, dev->keybit);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
write_unlock_irqrestore(&keytable_lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Timer function to report keyup event some time after keydown is
|
|
* reported by the ISR.
|
|
*/
|
|
static void
|
|
wbcir_keyup(unsigned long cookie)
|
|
{
|
|
struct wbcir_data *data = (struct wbcir_data *)cookie;
|
|
unsigned long flags;
|
|
|
|
/*
|
|
* data->keyup_jiffies is used to prevent a race condition if a
|
|
* hardware interrupt occurs at this point and the keyup timer
|
|
* event is moved further into the future as a result.
|
|
*
|
|
* The timer will then be reactivated and this function called
|
|
* again in the future. We need to exit gracefully in that case
|
|
* to allow the input subsystem to do its auto-repeat magic or
|
|
* a keyup event might follow immediately after the keydown.
|
|
*/
|
|
|
|
spin_lock_irqsave(&wbcir_lock, flags);
|
|
|
|
if (time_is_after_eq_jiffies(data->keyup_jiffies) && data->keypressed) {
|
|
data->keypressed = 0;
|
|
led_trigger_event(data->rxtrigger, LED_OFF);
|
|
input_report_key(data->input_dev, data->last_keycode, 0);
|
|
input_sync(data->input_dev);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&wbcir_lock, flags);
|
|
}
|
|
|
|
static void
|
|
wbcir_keydown(struct wbcir_data *data, u32 scancode, u8 toggle)
|
|
{
|
|
unsigned int keycode;
|
|
|
|
/* Repeat? */
|
|
if (data->last_scancode == scancode &&
|
|
data->last_toggle == toggle &&
|
|
data->keypressed)
|
|
goto set_timer;
|
|
data->last_scancode = scancode;
|
|
|
|
/* Do we need to release an old keypress? */
|
|
if (data->keypressed) {
|
|
input_report_key(data->input_dev, data->last_keycode, 0);
|
|
input_sync(data->input_dev);
|
|
data->keypressed = 0;
|
|
}
|
|
|
|
/* Report scancode */
|
|
input_event(data->input_dev, EV_MSC, MSC_SCAN, (int)scancode);
|
|
|
|
/* 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);
|
|
data->keypressed = 1;
|
|
data->last_keycode = keycode;
|
|
data->last_toggle = toggle;
|
|
|
|
set_timer:
|
|
input_sync(data->input_dev);
|
|
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;
|
|
data->idle_count = 0;
|
|
}
|
|
|
|
/* Adds one bit of irdata */
|
|
static void
|
|
add_irdata_bit(struct wbcir_data *data, int set)
|
|
{
|
|
if (data->irdata_count >= sizeof(data->irdata) * 8) {
|
|
data->irdata_error = 1;
|
|
return;
|
|
}
|
|
|
|
if (set)
|
|
__set_bit(data->irdata_count, data->irdata);
|
|
data->irdata_count++;
|
|
}
|
|
|
|
/* Gets count bits of irdata */
|
|
static u16
|
|
get_bits(struct wbcir_data *data, int count)
|
|
{
|
|
u16 val = 0x0;
|
|
|
|
if (data->irdata_count - data->irdata_off < count) {
|
|
data->irdata_error = 1;
|
|
return 0x0;
|
|
}
|
|
|
|
while (count > 0) {
|
|
val <<= 1;
|
|
if (test_bit(data->irdata_off, data->irdata))
|
|
val |= 0x1;
|
|
count--;
|
|
data->irdata_off++;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
/* Reads 16 cells and converts them to a byte */
|
|
static u8
|
|
wbcir_rc6cells_to_byte(struct wbcir_data *data)
|
|
{
|
|
u16 raw = get_bits(data, 16);
|
|
u8 val = 0x00;
|
|
int bit;
|
|
|
|
for (bit = 0; bit < 8; bit++) {
|
|
switch (raw & 0x03) {
|
|
case 0x01:
|
|
break;
|
|
case 0x02:
|
|
val |= (0x01 << bit);
|
|
break;
|
|
default:
|
|
data->irdata_error = 1;
|
|
break;
|
|
}
|
|
raw >>= 2;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
/* Decodes a number of bits from raw RC5 data */
|
|
static u8
|
|
wbcir_get_rc5bits(struct wbcir_data *data, unsigned int count)
|
|
{
|
|
u16 raw = get_bits(data, count * 2);
|
|
u8 val = 0x00;
|
|
int bit;
|
|
|
|
for (bit = 0; bit < count; bit++) {
|
|
switch (raw & 0x03) {
|
|
case 0x01:
|
|
val |= (0x01 << bit);
|
|
break;
|
|
case 0x02:
|
|
break;
|
|
default:
|
|
data->irdata_error = 1;
|
|
break;
|
|
}
|
|
raw >>= 2;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static void
|
|
wbcir_parse_rc6(struct device *dev, struct wbcir_data *data)
|
|
{
|
|
/*
|
|
* Normal bits are manchester coded as follows:
|
|
* cell0 + cell1 = logic "0"
|
|
* cell1 + cell0 = logic "1"
|
|
*
|
|
* The IR pulse has the following components:
|
|
*
|
|
* Leader - 6 * cell1 - discarded
|
|
* Gap - 2 * cell0 - discarded
|
|
* Start bit - Normal Coding - always "1"
|
|
* Mode Bit 2 - 0 - Normal Coding
|
|
* Toggle bit - Normal Coding with double bit time,
|
|
* e.g. cell0 + cell0 + cell1 + cell1
|
|
* means logic "0".
|
|
*
|
|
* The rest depends on the mode, the following modes are known:
|
|
*
|
|
* MODE 0:
|
|
* Address Bit 7 - 0 - Normal Coding
|
|
* Command Bit 7 - 0 - Normal Coding
|
|
*
|
|
* MODE 6:
|
|
* The above Toggle Bit is used as a submode bit, 0 = A, 1 = B.
|
|
* Submode B is for pointing devices, only remotes using submode A
|
|
* are supported.
|
|
*
|
|
* Customer range bit - 0 => Customer = 7 bits, 0...127
|
|
* 1 => Customer = 15 bits, 32768...65535
|
|
* Customer Bits - Normal Coding
|
|
*
|
|
* Customer codes are allocated by Philips. The rest of the bits
|
|
* are customer dependent. The following is commonly used (and the
|
|
* only supported config):
|
|
*
|
|
* Toggle Bit - Normal Coding
|
|
* Address Bit 6 - 0 - Normal Coding
|
|
* Command Bit 7 - 0 - Normal Coding
|
|
*
|
|
* All modes are followed by at least 6 * cell0.
|
|
*
|
|
* MODE 0 msglen:
|
|
* 1 * 2 (start bit) + 3 * 2 (mode) + 2 * 2 (toggle) +
|
|
* 8 * 2 (address) + 8 * 2 (command) =
|
|
* 44 cells
|
|
*
|
|
* MODE 6A msglen:
|
|
* 1 * 2 (start bit) + 3 * 2 (mode) + 2 * 2 (submode) +
|
|
* 1 * 2 (customer range bit) + 7/15 * 2 (customer bits) +
|
|
* 1 * 2 (toggle bit) + 7 * 2 (address) + 8 * 2 (command) =
|
|
* 60 - 76 cells
|
|
*/
|
|
u8 mode;
|
|
u8 toggle;
|
|
u16 customer = 0x0;
|
|
u8 address;
|
|
u8 command;
|
|
u32 scancode;
|
|
|
|
/* Leader mark */
|
|
while (get_bits(data, 1) && !data->irdata_error)
|
|
/* Do nothing */;
|
|
|
|
/* Leader space */
|
|
if (get_bits(data, 1)) {
|
|
dev_dbg(dev, "RC6 - Invalid leader space\n");
|
|
return;
|
|
}
|
|
|
|
/* Start bit */
|
|
if (get_bits(data, 2) != 0x02) {
|
|
dev_dbg(dev, "RC6 - Invalid start bit\n");
|
|
return;
|
|
}
|
|
|
|
/* Mode */
|
|
mode = get_bits(data, 6);
|
|
switch (mode) {
|
|
case 0x15: /* 010101 = b000 */
|
|
mode = 0;
|
|
break;
|
|
case 0x29: /* 101001 = b110 */
|
|
mode = 6;
|
|
break;
|
|
default:
|
|
dev_dbg(dev, "RC6 - Invalid mode\n");
|
|
return;
|
|
}
|
|
|
|
/* Toggle bit / Submode bit */
|
|
toggle = get_bits(data, 4);
|
|
switch (toggle) {
|
|
case 0x03:
|
|
toggle = 0;
|
|
break;
|
|
case 0x0C:
|
|
toggle = 1;
|
|
break;
|
|
default:
|
|
dev_dbg(dev, "RC6 - Toggle bit error\n");
|
|
break;
|
|
}
|
|
|
|
/* Customer */
|
|
if (mode == 6) {
|
|
if (toggle != 0) {
|
|
dev_dbg(dev, "RC6B - Not Supported\n");
|
|
return;
|
|
}
|
|
|
|
customer = wbcir_rc6cells_to_byte(data);
|
|
|
|
if (customer & 0x80) {
|
|
/* 15 bit customer value */
|
|
customer <<= 8;
|
|
customer |= wbcir_rc6cells_to_byte(data);
|
|
}
|
|
}
|
|
|
|
/* Address */
|
|
address = wbcir_rc6cells_to_byte(data);
|
|
if (mode == 6) {
|
|
toggle = address >> 7;
|
|
address &= 0x7F;
|
|
}
|
|
|
|
/* Command */
|
|
command = wbcir_rc6cells_to_byte(data);
|
|
|
|
/* Create scancode */
|
|
scancode = command;
|
|
scancode |= address << 8;
|
|
scancode |= customer << 16;
|
|
|
|
/* Last sanity check */
|
|
if (data->irdata_error) {
|
|
dev_dbg(dev, "RC6 - Cell error(s)\n");
|
|
return;
|
|
}
|
|
|
|
dev_dbg(dev, "IR-RC6 ad 0x%02X cm 0x%02X cu 0x%04X "
|
|
"toggle %u mode %u scan 0x%08X\n",
|
|
address,
|
|
command,
|
|
customer,
|
|
(unsigned int)toggle,
|
|
(unsigned int)mode,
|
|
scancode);
|
|
|
|
wbcir_keydown(data, scancode, toggle);
|
|
}
|
|
|
|
static void
|
|
wbcir_parse_rc5(struct device *dev, struct wbcir_data *data)
|
|
{
|
|
/*
|
|
* Bits are manchester coded as follows:
|
|
* cell1 + cell0 = logic "0"
|
|
* cell0 + cell1 = logic "1"
|
|
* (i.e. the reverse of RC6)
|
|
*
|
|
* Start bit 1 - "1" - discarded
|
|
* Start bit 2 - Must be inverted to get command bit 6
|
|
* Toggle bit
|
|
* Address Bit 4 - 0
|
|
* Command Bit 5 - 0
|
|
*/
|
|
u8 toggle;
|
|
u8 address;
|
|
u8 command;
|
|
u32 scancode;
|
|
|
|
/* Start bit 1 */
|
|
if (!get_bits(data, 1)) {
|
|
dev_dbg(dev, "RC5 - Invalid start bit\n");
|
|
return;
|
|
}
|
|
|
|
/* Start bit 2 */
|
|
if (!wbcir_get_rc5bits(data, 1))
|
|
command = 0x40;
|
|
else
|
|
command = 0x00;
|
|
|
|
toggle = wbcir_get_rc5bits(data, 1);
|
|
address = wbcir_get_rc5bits(data, 5);
|
|
command |= wbcir_get_rc5bits(data, 6);
|
|
scancode = address << 7 | command;
|
|
|
|
/* Last sanity check */
|
|
if (data->irdata_error) {
|
|
dev_dbg(dev, "RC5 - Invalid message\n");
|
|
return;
|
|
}
|
|
|
|
dev_dbg(dev, "IR-RC5 ad %u cm %u t %u s %u\n",
|
|
(unsigned int)address,
|
|
(unsigned int)command,
|
|
(unsigned int)toggle,
|
|
(unsigned int)scancode);
|
|
|
|
wbcir_keydown(data, scancode, toggle);
|
|
}
|
|
|
|
static void
|
|
wbcir_parse_nec(struct device *dev, struct wbcir_data *data)
|
|
{
|
|
/*
|
|
* Each bit represents 560 us.
|
|
*
|
|
* Leader - 9 ms burst
|
|
* Gap - 4.5 ms silence
|
|
* Address1 bit 0 - 7 - Address 1
|
|
* Address2 bit 0 - 7 - Address 2
|
|
* Command1 bit 0 - 7 - Command 1
|
|
* Command2 bit 0 - 7 - Command 2
|
|
*
|
|
* Note the bit order!
|
|
*
|
|
* With the old NEC protocol, Address2 was the inverse of Address1
|
|
* and Command2 was the inverse of Command1 and were used as
|
|
* an error check.
|
|
*
|
|
* With NEC extended, Address1 is the LSB of the Address and
|
|
* Address2 is the MSB, Command parsing remains unchanged.
|
|
*
|
|
* A repeat message is coded as:
|
|
* Leader - 9 ms burst
|
|
* Gap - 2.25 ms silence
|
|
* Repeat - 560 us active
|
|
*/
|
|
u8 address1;
|
|
u8 address2;
|
|
u8 command1;
|
|
u8 command2;
|
|
u16 address;
|
|
u32 scancode;
|
|
|
|
/* Leader mark */
|
|
while (get_bits(data, 1) && !data->irdata_error)
|
|
/* Do nothing */;
|
|
|
|
/* Leader space */
|
|
if (get_bits(data, 4)) {
|
|
dev_dbg(dev, "NEC - Invalid leader space\n");
|
|
return;
|
|
}
|
|
|
|
/* Repeat? */
|
|
if (get_bits(data, 1)) {
|
|
if (!data->keypressed) {
|
|
dev_dbg(dev, "NEC - Stray repeat message\n");
|
|
return;
|
|
}
|
|
|
|
dev_dbg(dev, "IR-NEC repeat s %u\n",
|
|
(unsigned int)data->last_scancode);
|
|
|
|
wbcir_keydown(data, data->last_scancode, data->last_toggle);
|
|
return;
|
|
}
|
|
|
|
/* Remaining leader space */
|
|
if (get_bits(data, 3)) {
|
|
dev_dbg(dev, "NEC - Invalid leader space\n");
|
|
return;
|
|
}
|
|
|
|
address1 = bitrev8(get_bits(data, 8));
|
|
address2 = bitrev8(get_bits(data, 8));
|
|
command1 = bitrev8(get_bits(data, 8));
|
|
command2 = bitrev8(get_bits(data, 8));
|
|
|
|
/* Sanity check */
|
|
if (data->irdata_error) {
|
|
dev_dbg(dev, "NEC - Invalid message\n");
|
|
return;
|
|
}
|
|
|
|
/* Check command validity */
|
|
if (command1 != ~command2) {
|
|
dev_dbg(dev, "NEC - Command bytes mismatch\n");
|
|
return;
|
|
}
|
|
|
|
/* Check for extended NEC protocol */
|
|
address = address1;
|
|
if (address1 != ~address2)
|
|
address |= address2 << 8;
|
|
|
|
scancode = address << 8 | command1;
|
|
|
|
dev_dbg(dev, "IR-NEC ad %u cm %u s %u\n",
|
|
(unsigned int)address,
|
|
(unsigned int)command1,
|
|
(unsigned int)scancode);
|
|
|
|
wbcir_keydown(data, scancode, !data->last_toggle);
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* INTERRUPT FUNCTIONS
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static irqreturn_t
|
|
wbcir_irq_handler(int irqno, void *cookie)
|
|
{
|
|
struct pnp_dev *device = cookie;
|
|
struct wbcir_data *data = pnp_get_drvdata(device);
|
|
struct device *dev = &device->dev;
|
|
u8 status;
|
|
unsigned long flags;
|
|
u8 irdata[8];
|
|
int i;
|
|
unsigned int hw;
|
|
|
|
spin_lock_irqsave(&wbcir_lock, flags);
|
|
|
|
wbcir_select_bank(data, WBCIR_BANK_0);
|
|
|
|
status = inb(data->sbase + WBCIR_REG_SP3_EIR);
|
|
|
|
if (!(status & (WBCIR_IRQ_RX | WBCIR_IRQ_ERR))) {
|
|
spin_unlock_irqrestore(&wbcir_lock, flags);
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
if (status & WBCIR_IRQ_ERR)
|
|
data->irdata_error = 1;
|
|
|
|
if (!(status & WBCIR_IRQ_RX))
|
|
goto out;
|
|
|
|
/* Since RXHDLEV is set, at least 8 bytes are in the FIFO */
|
|
insb(data->sbase + WBCIR_REG_SP3_RXDATA, &irdata[0], 8);
|
|
|
|
for (i = 0; i < sizeof(irdata); 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) {
|
|
/* Set RXINACTIVE... */
|
|
outb(WBCIR_RX_DISABLE, data->sbase + WBCIR_REG_SP3_ASCR);
|
|
|
|
/* ...and drain the FIFO */
|
|
while (inb(data->sbase + WBCIR_REG_SP3_LSR) & WBCIR_RX_AVAIL)
|
|
inb(data->sbase + WBCIR_REG_SP3_RXDATA);
|
|
|
|
dev_dbg(dev, "IRDATA:\n");
|
|
for (i = 0; i < data->irdata_count; i += BITS_PER_LONG)
|
|
dev_dbg(dev, "0x%08lX\n", data->irdata[i/BITS_PER_LONG]);
|
|
|
|
switch (protocol) {
|
|
case IR_PROTOCOL_RC5:
|
|
wbcir_parse_rc5(dev, data);
|
|
break;
|
|
case IR_PROTOCOL_RC6:
|
|
wbcir_parse_rc6(dev, data);
|
|
break;
|
|
case IR_PROTOCOL_NEC:
|
|
wbcir_parse_nec(dev, data);
|
|
break;
|
|
}
|
|
|
|
wbcir_reset_irdata(data);
|
|
}
|
|
|
|
out:
|
|
spin_unlock_irqrestore(&wbcir_lock, flags);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* SETUP/INIT/SUSPEND/RESUME FUNCTIONS
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static void
|
|
wbcir_shutdown(struct pnp_dev *device)
|
|
{
|
|
struct device *dev = &device->dev;
|
|
struct wbcir_data *data = pnp_get_drvdata(device);
|
|
int do_wake = 1;
|
|
u8 match[11];
|
|
u8 mask[11];
|
|
u8 rc6_csl = 0;
|
|
int i;
|
|
|
|
memset(match, 0, sizeof(match));
|
|
memset(mask, 0, sizeof(mask));
|
|
|
|
if (wake_sc == INVALID_SCANCODE || !device_may_wakeup(dev)) {
|
|
do_wake = 0;
|
|
goto finish;
|
|
}
|
|
|
|
switch (protocol) {
|
|
case IR_PROTOCOL_RC5:
|
|
if (wake_sc > 0xFFF) {
|
|
do_wake = 0;
|
|
dev_err(dev, "RC5 - Invalid wake scancode\n");
|
|
break;
|
|
}
|
|
|
|
/* Mask = 13 bits, ex toggle */
|
|
mask[0] = 0xFF;
|
|
mask[1] = 0x17;
|
|
|
|
match[0] = (wake_sc & 0x003F); /* 6 command bits */
|
|
match[0] |= (wake_sc & 0x0180) >> 1; /* 2 address bits */
|
|
match[1] = (wake_sc & 0x0E00) >> 9; /* 3 address bits */
|
|
if (!(wake_sc & 0x0040)) /* 2nd start bit */
|
|
match[1] |= 0x10;
|
|
|
|
break;
|
|
|
|
case IR_PROTOCOL_NEC:
|
|
if (wake_sc > 0xFFFFFF) {
|
|
do_wake = 0;
|
|
dev_err(dev, "NEC - Invalid wake scancode\n");
|
|
break;
|
|
}
|
|
|
|
mask[0] = mask[1] = mask[2] = mask[3] = 0xFF;
|
|
|
|
match[1] = bitrev8((wake_sc & 0xFF));
|
|
match[0] = ~match[1];
|
|
|
|
match[3] = bitrev8((wake_sc & 0xFF00) >> 8);
|
|
if (wake_sc > 0xFFFF)
|
|
match[2] = bitrev8((wake_sc & 0xFF0000) >> 16);
|
|
else
|
|
match[2] = ~match[3];
|
|
|
|
break;
|
|
|
|
case IR_PROTOCOL_RC6:
|
|
|
|
if (wake_rc6mode == 0) {
|
|
if (wake_sc > 0xFFFF) {
|
|
do_wake = 0;
|
|
dev_err(dev, "RC6 - Invalid wake scancode\n");
|
|
break;
|
|
}
|
|
|
|
/* Command */
|
|
match[0] = wbcir_to_rc6cells(wake_sc >> 0);
|
|
mask[0] = 0xFF;
|
|
match[1] = wbcir_to_rc6cells(wake_sc >> 4);
|
|
mask[1] = 0xFF;
|
|
|
|
/* Address */
|
|
match[2] = wbcir_to_rc6cells(wake_sc >> 8);
|
|
mask[2] = 0xFF;
|
|
match[3] = wbcir_to_rc6cells(wake_sc >> 12);
|
|
mask[3] = 0xFF;
|
|
|
|
/* Header */
|
|
match[4] = 0x50; /* mode1 = mode0 = 0, ignore toggle */
|
|
mask[4] = 0xF0;
|
|
match[5] = 0x09; /* start bit = 1, mode2 = 0 */
|
|
mask[5] = 0x0F;
|
|
|
|
rc6_csl = 44;
|
|
|
|
} else if (wake_rc6mode == 6) {
|
|
i = 0;
|
|
|
|
/* Command */
|
|
match[i] = wbcir_to_rc6cells(wake_sc >> 0);
|
|
mask[i++] = 0xFF;
|
|
match[i] = wbcir_to_rc6cells(wake_sc >> 4);
|
|
mask[i++] = 0xFF;
|
|
|
|
/* Address + Toggle */
|
|
match[i] = wbcir_to_rc6cells(wake_sc >> 8);
|
|
mask[i++] = 0xFF;
|
|
match[i] = wbcir_to_rc6cells(wake_sc >> 12);
|
|
mask[i++] = 0x3F;
|
|
|
|
/* Customer bits 7 - 0 */
|
|
match[i] = wbcir_to_rc6cells(wake_sc >> 16);
|
|
mask[i++] = 0xFF;
|
|
match[i] = wbcir_to_rc6cells(wake_sc >> 20);
|
|
mask[i++] = 0xFF;
|
|
|
|
if (wake_sc & 0x80000000) {
|
|
/* Customer range bit and bits 15 - 8 */
|
|
match[i] = wbcir_to_rc6cells(wake_sc >> 24);
|
|
mask[i++] = 0xFF;
|
|
match[i] = wbcir_to_rc6cells(wake_sc >> 28);
|
|
mask[i++] = 0xFF;
|
|
rc6_csl = 76;
|
|
} else if (wake_sc <= 0x007FFFFF) {
|
|
rc6_csl = 60;
|
|
} else {
|
|
do_wake = 0;
|
|
dev_err(dev, "RC6 - Invalid wake scancode\n");
|
|
break;
|
|
}
|
|
|
|
/* Header */
|
|
match[i] = 0x93; /* mode1 = mode0 = 1, submode = 0 */
|
|
mask[i++] = 0xFF;
|
|
match[i] = 0x0A; /* start bit = 1, mode2 = 1 */
|
|
mask[i++] = 0x0F;
|
|
|
|
} else {
|
|
do_wake = 0;
|
|
dev_err(dev, "RC6 - Invalid wake mode\n");
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
do_wake = 0;
|
|
break;
|
|
}
|
|
|
|
finish:
|
|
if (do_wake) {
|
|
/* Set compare and compare mask */
|
|
wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX,
|
|
WBCIR_REGSEL_COMPARE | WBCIR_REG_ADDR0,
|
|
0x3F);
|
|
outsb(data->wbase + WBCIR_REG_WCEIR_DATA, match, 11);
|
|
wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX,
|
|
WBCIR_REGSEL_MASK | WBCIR_REG_ADDR0,
|
|
0x3F);
|
|
outsb(data->wbase + WBCIR_REG_WCEIR_DATA, mask, 11);
|
|
|
|
/* RC6 Compare String Len */
|
|
outb(rc6_csl, data->wbase + WBCIR_REG_WCEIR_CSL);
|
|
|
|
/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
|
|
wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
|
|
|
|
/* Clear BUFF_EN, Clear END_EN, Set MATCH_EN */
|
|
wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x01, 0x07);
|
|
|
|
/* Set CEIR_EN */
|
|
wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x01, 0x01);
|
|
|
|
} else {
|
|
/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
|
|
wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
|
|
|
|
/* Clear CEIR_EN */
|
|
wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
|
|
}
|
|
|
|
/* Disable interrupts */
|
|
wbcir_select_bank(data, WBCIR_BANK_0);
|
|
outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
|
|
|
|
/*
|
|
* ACPI will set the HW disable bit for SP3 which means that the
|
|
* output signals are left in an undefined state which may cause
|
|
* spurious interrupts which we need to ignore until the hardware
|
|
* is reinitialized.
|
|
*/
|
|
disable_irq(data->irq);
|
|
}
|
|
|
|
static int
|
|
wbcir_suspend(struct pnp_dev *device, pm_message_t state)
|
|
{
|
|
wbcir_shutdown(device);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
wbcir_init_hw(struct wbcir_data *data)
|
|
{
|
|
u8 tmp;
|
|
|
|
/* Disable interrupts */
|
|
wbcir_select_bank(data, WBCIR_BANK_0);
|
|
outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
|
|
|
|
/* Set PROT_SEL, RX_INV, Clear CEIR_EN (needed for the led) */
|
|
tmp = protocol << 4;
|
|
if (invert)
|
|
tmp |= 0x08;
|
|
outb(tmp, data->wbase + WBCIR_REG_WCEIR_CTL);
|
|
|
|
/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
|
|
wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
|
|
|
|
/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */
|
|
wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
|
|
|
|
/* Set RC5 cell time to correspond to 36 kHz */
|
|
wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CFG1, 0x4A, 0x7F);
|
|
|
|
/* Set IRTX_INV */
|
|
if (invert)
|
|
outb(0x04, data->ebase + WBCIR_REG_ECEIR_CCTL);
|
|
else
|
|
outb(0x00, data->ebase + WBCIR_REG_ECEIR_CCTL);
|
|
|
|
/*
|
|
* Clear IR LED, set SP3 clock to 24Mhz
|
|
* set SP3_IRRX_SW to binary 01, helpfully not documented
|
|
*/
|
|
outb(0x10, data->ebase + WBCIR_REG_ECEIR_CTS);
|
|
|
|
/* Enable extended mode */
|
|
wbcir_select_bank(data, WBCIR_BANK_2);
|
|
outb(WBCIR_EXT_ENABLE, data->sbase + WBCIR_REG_SP3_EXCR1);
|
|
|
|
/*
|
|
* Configure baud generator, IR data will be sampled at
|
|
* a bitrate of: (24Mhz * prescaler) / (divisor * 16).
|
|
*
|
|
* The ECIR registers include a flag to change the
|
|
* 24Mhz clock freq to 48Mhz.
|
|
*
|
|
* It's not documented in the specs, but fifo levels
|
|
* other than 16 seems to be unsupported.
|
|
*/
|
|
|
|
/* prescaler 1.0, tx/rx fifo lvl 16 */
|
|
outb(0x30, 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 = 8, TX = 3), 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_reset_irdata(data);
|
|
outb(WBCIR_IRQ_RX | WBCIR_IRQ_ERR, data->sbase + WBCIR_REG_SP3_IER);
|
|
}
|
|
|
|
static int
|
|
wbcir_resume(struct pnp_dev *device)
|
|
{
|
|
struct wbcir_data *data = pnp_get_drvdata(device);
|
|
|
|
wbcir_init_hw(data);
|
|
enable_irq(data->irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __devinit
|
|
wbcir_probe(struct pnp_dev *device, const struct pnp_device_id *dev_id)
|
|
{
|
|
struct device *dev = &device->dev;
|
|
struct wbcir_data *data;
|
|
int err;
|
|
|
|
if (!(pnp_port_len(device, 0) == EHFUNC_IOMEM_LEN &&
|
|
pnp_port_len(device, 1) == WAKEUP_IOMEM_LEN &&
|
|
pnp_port_len(device, 2) == SP_IOMEM_LEN)) {
|
|
dev_err(dev, "Invalid resources\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
|
if (!data) {
|
|
err = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
|
|
pnp_set_drvdata(device, data);
|
|
|
|
data->ebase = pnp_port_start(device, 0);
|
|
data->wbase = pnp_port_start(device, 1);
|
|
data->sbase = pnp_port_start(device, 2);
|
|
data->irq = pnp_irq(device, 0);
|
|
|
|
if (data->wbase == 0 || data->ebase == 0 ||
|
|
data->sbase == 0 || data->irq == 0) {
|
|
err = -ENODEV;
|
|
dev_err(dev, "Invalid resources\n");
|
|
goto exit_free_data;
|
|
}
|
|
|
|
dev_dbg(&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_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_capability(data->input_dev, EV_MSC, MSC_SCAN);
|
|
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;
|
|
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_init_hw(data);
|
|
|
|
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);
|
|
}
|
|
}
|
|
input_unregister_device(data->input_dev);
|
|
/* Can't call input_free_device on an unregistered device */
|
|
data->input_dev = NULL;
|
|
exit_free_input:
|
|
input_free_device(data->input_dev);
|
|
exit_unregister_led:
|
|
led_classdev_unregister(&data->led);
|
|
exit_unregister_rxtrigger:
|
|
led_trigger_unregister_simple(data->rxtrigger);
|
|
exit_unregister_txtrigger:
|
|
led_trigger_unregister_simple(data->txtrigger);
|
|
exit_free_irq:
|
|
free_irq(data->irq, device);
|
|
exit_release_sbase:
|
|
release_region(data->sbase, SP_IOMEM_LEN);
|
|
exit_release_ebase:
|
|
release_region(data->ebase, EHFUNC_IOMEM_LEN);
|
|
exit_release_wbase:
|
|
release_region(data->wbase, WAKEUP_IOMEM_LEN);
|
|
exit_free_data:
|
|
kfree(data);
|
|
pnp_set_drvdata(device, NULL);
|
|
exit:
|
|
return err;
|
|
}
|
|
|
|
static void __devexit
|
|
wbcir_remove(struct pnp_dev *device)
|
|
{
|
|
struct wbcir_data *data = pnp_get_drvdata(device);
|
|
struct wbcir_keyentry *key;
|
|
struct wbcir_keyentry *keytmp;
|
|
|
|
/* Disable interrupts */
|
|
wbcir_select_bank(data, WBCIR_BANK_0);
|
|
outb(WBCIR_IRQ_NONE, data->sbase + WBCIR_REG_SP3_IER);
|
|
|
|
del_timer_sync(&data->timer_keyup);
|
|
|
|
free_irq(data->irq, device);
|
|
|
|
/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */
|
|
wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17);
|
|
|
|
/* Clear CEIR_EN */
|
|
wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01);
|
|
|
|
/* Clear BUFF_EN, END_EN, MATCH_EN */
|
|
wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07);
|
|
|
|
/* This will generate a keyup event if necessary */
|
|
input_unregister_device(data->input_dev);
|
|
|
|
led_trigger_unregister_simple(data->rxtrigger);
|
|
led_trigger_unregister_simple(data->txtrigger);
|
|
led_classdev_unregister(&data->led);
|
|
|
|
/* This is ok since &data->led isn't actually used */
|
|
wbcir_led_brightness_set(&data->led, LED_OFF);
|
|
|
|
release_region(data->wbase, WAKEUP_IOMEM_LEN);
|
|
release_region(data->ebase, EHFUNC_IOMEM_LEN);
|
|
release_region(data->sbase, SP_IOMEM_LEN);
|
|
|
|
list_for_each_entry_safe(key, keytmp, &data->keytable, list) {
|
|
list_del(&key->list);
|
|
kfree(key);
|
|
}
|
|
|
|
kfree(data);
|
|
|
|
pnp_set_drvdata(device, NULL);
|
|
}
|
|
|
|
static const struct pnp_device_id wbcir_ids[] = {
|
|
{ "WEC1022", 0 },
|
|
{ "", 0 }
|
|
};
|
|
MODULE_DEVICE_TABLE(pnp, wbcir_ids);
|
|
|
|
static struct pnp_driver wbcir_driver = {
|
|
.name = WBCIR_NAME,
|
|
.id_table = wbcir_ids,
|
|
.probe = wbcir_probe,
|
|
.remove = __devexit_p(wbcir_remove),
|
|
.suspend = wbcir_suspend,
|
|
.resume = wbcir_resume,
|
|
.shutdown = wbcir_shutdown
|
|
};
|
|
|
|
static int __init
|
|
wbcir_init(void)
|
|
{
|
|
int ret;
|
|
|
|
switch (protocol) {
|
|
case IR_PROTOCOL_RC5:
|
|
case IR_PROTOCOL_NEC:
|
|
case IR_PROTOCOL_RC6:
|
|
break;
|
|
default:
|
|
printk(KERN_ERR DRVNAME ": Invalid protocol argument\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = pnp_register_driver(&wbcir_driver);
|
|
if (ret)
|
|
printk(KERN_ERR DRVNAME ": Unable to register driver\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __exit
|
|
wbcir_exit(void)
|
|
{
|
|
pnp_unregister_driver(&wbcir_driver);
|
|
}
|
|
|
|
MODULE_AUTHOR("David Härdeman <david@hardeman.nu>");
|
|
MODULE_DESCRIPTION("Winbond SuperI/O Consumer IR Driver");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
module_init(wbcir_init);
|
|
module_exit(wbcir_exit);
|
|
|
|
|