diff options
Diffstat (limited to 'drivers/input/keyboard')
26 files changed, 9620 insertions, 0 deletions
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig new file mode 100644 index 0000000..efd70a9 --- /dev/null +++ b/drivers/input/keyboard/Kconfig @@ -0,0 +1,326 @@ +# +# Input core configuration +# +menuconfig INPUT_KEYBOARD + bool "Keyboards" if EMBEDDED || !X86 + default y + help + Say Y here, and a list of supported keyboards will be displayed. + This option doesn't affect the kernel. + + If unsure, say Y. + +if INPUT_KEYBOARD + +config KEYBOARD_ATKBD + tristate "AT keyboard" if EMBEDDED || !X86_PC + default y + select SERIO + select SERIO_LIBPS2 + select SERIO_I8042 if X86_PC + select SERIO_GSCPS2 if GSC + help + Say Y here if you want to use a standard AT or PS/2 keyboard. Usually + you'll need this, unless you have a different type keyboard (USB, ADB + or other). This also works for AT and PS/2 keyboards connected over a + PS/2 to serial converter. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called atkbd. + +config KEYBOARD_ATKBD_HP_KEYCODES + bool "Use HP keyboard scancodes" + depends on PARISC && KEYBOARD_ATKBD + default y + help + Say Y here if you have a PA-RISC machine and want to use an AT or + PS/2 keyboard, and your keyboard uses keycodes that are specific to + PA-RISC keyboards. + + Say N if you use a standard keyboard. + +config KEYBOARD_ATKBD_RDI_KEYCODES + bool "Use PrecisionBook keyboard scancodes" + depends on KEYBOARD_ATKBD_HP_KEYCODES + default n + help + If you have an RDI PrecisionBook, say Y here if you want to use its + built-in keyboard (as opposed to an external keyboard). + + The PrecisionBook has five keys that conflict with those used by most + AT and PS/2 keyboards. These are as follows: + + PrecisionBook Standard AT or PS/2 + + F1 F12 + Left Ctrl Left Alt + Caps Lock Left Ctrl + Right Ctrl Caps Lock + Left 102nd key (the key to the right of Left Shift) + + If you say N here, and use the PrecisionBook keyboard, then each key + in the left-hand column will be interpreted as the corresponding key + in the right-hand column. + + If you say Y here, and use an external keyboard, then each key in the + right-hand column will be interpreted as the key shown in the + left-hand column. + +config KEYBOARD_SUNKBD + tristate "Sun Type 4 and Type 5 keyboard" + select SERIO + help + Say Y here if you want to use a Sun Type 4 or Type 5 keyboard, + connected either to the Sun keyboard connector or to an serial + (RS-232) port via a simple adapter. + + To compile this driver as a module, choose M here: the + module will be called sunkbd. + +config KEYBOARD_LKKBD + tristate "DECstation/VAXstation LK201/LK401 keyboard" + select SERIO + help + Say Y here if you want to use a LK201 or LK401 style serial + keyboard. This keyboard is also useable on PCs if you attach + it with the inputattach program. The connector pinout is + described within lkkbd.c. + + To compile this driver as a module, choose M here: the + module will be called lkkbd. + +config KEYBOARD_LOCOMO + tristate "LoCoMo Keyboard Support" + depends on SHARP_LOCOMO && INPUT_KEYBOARD + help + Say Y here if you are running Linux on a Sharp Zaurus Collie or Poodle based PDA + + To compile this driver as a module, choose M here: the + module will be called locomokbd. + +config KEYBOARD_XTKBD + tristate "XT keyboard" + select SERIO + help + Say Y here if you want to use the old IBM PC/XT keyboard (or + compatible) on your system. This is only possible with a + parallel port keyboard adapter, you cannot connect it to the + keyboard port on a PC that runs Linux. + + To compile this driver as a module, choose M here: the + module will be called xtkbd. + +config KEYBOARD_NEWTON + tristate "Newton keyboard" + select SERIO + help + Say Y here if you have a Newton keyboard on a serial port. + + To compile this driver as a module, choose M here: the + module will be called newtonkbd. + +config KEYBOARD_STOWAWAY + tristate "Stowaway keyboard" + select SERIO + help + Say Y here if you have a Stowaway keyboard on a serial port. + Stowaway compatible keyboards like Dicota Input-PDA keyboard + are also supported by this driver. + + To compile this driver as a module, choose M here: the + module will be called stowaway. + +config KEYBOARD_CORGI + tristate "Corgi keyboard" + depends on PXA_SHARPSL + default y + help + Say Y here to enable the keyboard on the Sharp Zaurus SL-C7xx + series of PDAs. + + To compile this driver as a module, choose M here: the + module will be called corgikbd. + +config KEYBOARD_SPITZ + tristate "Spitz keyboard" + depends on PXA_SHARPSL + default y + help + Say Y here to enable the keyboard on the Sharp Zaurus SL-C1000, + SL-C3000 and Sl-C3100 series of PDAs. + + To compile this driver as a module, choose M here: the + module will be called spitzkbd. + +config KEYBOARD_TOSA + tristate "Tosa keyboard" + depends on MACH_TOSA + default y + help + Say Y here to enable the keyboard on the Sharp Zaurus SL-6000x (Tosa) + + To compile this driver as a module, choose M here: the + module will be called tosakbd. + +config KEYBOARD_TOSA_USE_EXT_KEYCODES + bool "Tosa keyboard: use extended keycodes" + depends on KEYBOARD_TOSA + default n + help + Say Y here to enable the tosa keyboard driver to generate extended + (>= 127) keycodes. Be aware, that they can't be correctly interpreted + by either console keyboard driver or by Kdrive keybd driver. + + Say Y only if you know, what you are doing! + +config KEYBOARD_AMIGA + tristate "Amiga keyboard" + depends on AMIGA + help + Say Y here if you are running Linux on any AMIGA and have a keyboard + attached. + + To compile this driver as a module, choose M here: the + module will be called amikbd. + +config ATARI_KBD_CORE + bool + +config KEYBOARD_ATARI + tristate "Atari keyboard" + depends on ATARI + select ATARI_KBD_CORE + help + Say Y here if you are running Linux on any Atari and have a keyboard + attached. + + To compile this driver as a module, choose M here: the + module will be called atakbd. + +config KEYBOARD_HIL_OLD + tristate "HP HIL keyboard support (simple driver)" + depends on GSC || HP300 + default y + help + The "Human Interface Loop" is a older, 8-channel USB-like + controller used in several Hewlett Packard models. This driver + was adapted from the one written for m68k/hp300, and implements + support for a keyboard attached to the HIL port, but not for + any other types of HIL input devices like mice or tablets. + However, it has been thoroughly tested and is stable. + + If you want full HIL support including support for multiple + keyboards, mice, and tablets, you have to enable the + "HP System Device Controller i8042 Support" in the input/serio + submenu. + +config KEYBOARD_HIL + tristate "HP HIL keyboard support" + depends on GSC || HP300 + default y + select HP_SDC + select HIL_MLC + select SERIO + help + The "Human Interface Loop" is a older, 8-channel USB-like + controller used in several Hewlett Packard models. + This driver implements support for HIL-keyboards attached + to your machine, so normally you should say Y here. + +config KEYBOARD_HP6XX + tristate "HP Jornada 6xx keyboard" + depends on SH_HP6XX + select INPUT_POLLDEV + help + Say Y here if you have a HP Jornada 620/660/680/690 and want to + support the built-in keyboard. + + To compile this driver as a module, choose M here: the + module will be called jornada680_kbd. + +config KEYBOARD_HP7XX + tristate "HP Jornada 7xx keyboard" + depends on SA1100_JORNADA720_SSP && SA1100_SSP + help + Say Y here if you have a HP Jornada 710/720/728 and want to + support the built-in keyboard. + + To compile this driver as a module, choose M here: the + module will be called jornada720_kbd. + +config KEYBOARD_OMAP + tristate "TI OMAP keypad support" + depends on (ARCH_OMAP1 || ARCH_OMAP2) + help + Say Y here if you want to use the OMAP keypad. + + To compile this driver as a module, choose M here: the + module will be called omap-keypad. + +config KEYBOARD_PXA27x + tristate "PXA27x/PXA3xx keypad support" + depends on PXA27x || PXA3xx + help + Enable support for PXA27x/PXA3xx keypad controller + + To compile this driver as a module, choose M here: the + module will be called pxa27x_keypad. + +config KEYBOARD_AAED2000 + tristate "AAED-2000 keyboard" + depends on MACH_AAED2000 + select INPUT_POLLDEV + default y + help + Say Y here to enable the keyboard on the Agilent AAED-2000 + development board. + + To compile this driver as a module, choose M here: the + module will be called aaed2000_kbd. + +config KEYBOARD_GPIO + tristate "GPIO Buttons" + depends on GENERIC_GPIO + help + This driver implements support for buttons connected + to GPIO pins of various CPUs (and some other chips). + + Say Y here if your device has buttons connected + directly to such GPIO pins. Your board-specific + setup logic must also provide a platform device, + with configuration data saying which GPIOs are used. + + To compile this driver as a module, choose M here: the + module will be called gpio-keys. + +config KEYBOARD_MAPLE + tristate "Maple bus keyboard" + depends on SH_DREAMCAST && MAPLE + help + Say Y here if you have a Dreamcast console running Linux and have + a keyboard attached to its Maple bus. + + To compile this driver as a module, choose M here: the + module will be called maple_keyb. + +config KEYBOARD_BFIN + tristate "Blackfin BF54x keypad support" + depends on (BF54x && !BF544) + help + Say Y here if you want to use the BF54x keypad. + + To compile this driver as a module, choose M here: the + module will be called bf54x-keys. + +config KEYBOARD_SH_KEYSC + tristate "SuperH KEYSC keypad support" + depends on SUPERH + help + Say Y here if you want to use a keypad attached to the KEYSC block + on SuperH processors such as sh7722 and sh7343. + + To compile this driver as a module, choose M here: the + module will be called sh_keysc. +endif diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile new file mode 100644 index 0000000..0edc8f2 --- /dev/null +++ b/drivers/input/keyboard/Makefile @@ -0,0 +1,29 @@ +# +# Makefile for the input core drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_KEYBOARD_ATKBD) += atkbd.o +obj-$(CONFIG_KEYBOARD_SUNKBD) += sunkbd.o +obj-$(CONFIG_KEYBOARD_LKKBD) += lkkbd.o +obj-$(CONFIG_KEYBOARD_XTKBD) += xtkbd.o +obj-$(CONFIG_KEYBOARD_AMIGA) += amikbd.o +obj-$(CONFIG_KEYBOARD_ATARI) += atakbd.o +obj-$(CONFIG_KEYBOARD_LOCOMO) += locomokbd.o +obj-$(CONFIG_KEYBOARD_NEWTON) += newtonkbd.o +obj-$(CONFIG_KEYBOARD_STOWAWAY) += stowaway.o +obj-$(CONFIG_KEYBOARD_CORGI) += corgikbd.o +obj-$(CONFIG_KEYBOARD_SPITZ) += spitzkbd.o +obj-$(CONFIG_KEYBOARD_TOSA) += tosakbd.o +obj-$(CONFIG_KEYBOARD_HIL) += hil_kbd.o +obj-$(CONFIG_KEYBOARD_HIL_OLD) += hilkbd.o +obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o +obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keypad.o +obj-$(CONFIG_KEYBOARD_AAED2000) += aaed2000_kbd.o +obj-$(CONFIG_KEYBOARD_GPIO) += gpio_keys.o +obj-$(CONFIG_KEYBOARD_HP6XX) += jornada680_kbd.o +obj-$(CONFIG_KEYBOARD_HP7XX) += jornada720_kbd.o +obj-$(CONFIG_KEYBOARD_MAPLE) += maple_keyb.o +obj-$(CONFIG_KEYBOARD_BFIN) += bf54x-keys.o +obj-$(CONFIG_KEYBOARD_SH_KEYSC) += sh_keysc.o diff --git a/drivers/input/keyboard/aaed2000_kbd.c b/drivers/input/keyboard/aaed2000_kbd.c new file mode 100644 index 0000000..18222a6 --- /dev/null +++ b/drivers/input/keyboard/aaed2000_kbd.c @@ -0,0 +1,186 @@ +/* + * Keyboard driver for the AAED-2000 dev board + * + * Copyright (c) 2006 Nicolas Bellido Y Ortega + * + * Based on corgikbd.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/input-polldev.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <mach/hardware.h> +#include <mach/aaed2000.h> + +#define KB_ROWS 12 +#define KB_COLS 8 +#define KB_ROWMASK(r) (1 << (r)) +#define SCANCODE(r,c) (((c) * KB_ROWS) + (r)) +#define NR_SCANCODES (KB_COLS * KB_ROWS) + +#define SCAN_INTERVAL (50) /* ms */ +#define KB_ACTIVATE_DELAY (20) /* us */ + +static unsigned char aaedkbd_keycode[NR_SCANCODES] = { + KEY_9, KEY_0, KEY_MINUS, KEY_EQUAL, KEY_BACKSPACE, 0, KEY_SPACE, KEY_KP6, 0, KEY_KPDOT, 0, 0, + KEY_K, KEY_M, KEY_O, KEY_DOT, KEY_SLASH, 0, KEY_F, 0, 0, 0, KEY_LEFTSHIFT, 0, + KEY_I, KEY_P, KEY_LEFTBRACE, KEY_RIGHTBRACE, KEY_BACKSLASH, 0, 0, 0, 0, 0, KEY_RIGHTSHIFT, 0, + KEY_8, KEY_L, KEY_SEMICOLON, KEY_APOSTROPHE, KEY_ENTER, 0, 0, 0, 0, 0, 0, 0, + KEY_J, KEY_H, KEY_B, KEY_KP8, KEY_KP4, 0, KEY_C, KEY_D, KEY_S, KEY_A, 0, KEY_CAPSLOCK, + KEY_Y, KEY_U, KEY_N, KEY_T, 0, 0, KEY_R, KEY_E, KEY_W, KEY_Q, 0, KEY_TAB, + KEY_7, KEY_6, KEY_G, 0, KEY_5, 0, KEY_4, KEY_3, KEY_2, KEY_1, 0, KEY_GRAVE, + 0, 0, KEY_COMMA, 0, KEY_KP2, 0, KEY_V, KEY_LEFTALT, KEY_X, KEY_Z, 0, KEY_LEFTCTRL +}; + +struct aaedkbd { + unsigned char keycode[ARRAY_SIZE(aaedkbd_keycode)]; + struct input_polled_dev *poll_dev; + int kbdscan_state[KB_COLS]; + int kbdscan_count[KB_COLS]; +}; + +#define KBDSCAN_STABLE_COUNT 2 + +static void aaedkbd_report_col(struct aaedkbd *aaedkbd, + unsigned int col, unsigned int rowd) +{ + unsigned int scancode, pressed; + unsigned int row; + + for (row = 0; row < KB_ROWS; row++) { + scancode = SCANCODE(row, col); + pressed = rowd & KB_ROWMASK(row); + + input_report_key(aaedkbd->poll_dev->input, + aaedkbd->keycode[scancode], pressed); + } +} + +/* Scan the hardware keyboard and push any changes up through the input layer */ +static void aaedkbd_poll(struct input_polled_dev *dev) +{ + struct aaedkbd *aaedkbd = dev->private; + unsigned int col, rowd; + + col = 0; + do { + AAEC_GPIO_KSCAN = col + 8; + udelay(KB_ACTIVATE_DELAY); + rowd = AAED_EXT_GPIO & AAED_EGPIO_KBD_SCAN; + + if (rowd != aaedkbd->kbdscan_state[col]) { + aaedkbd->kbdscan_count[col] = 0; + aaedkbd->kbdscan_state[col] = rowd; + } else if (++aaedkbd->kbdscan_count[col] >= KBDSCAN_STABLE_COUNT) { + aaedkbd_report_col(aaedkbd, col, rowd); + col++; + } + } while (col < KB_COLS); + + AAEC_GPIO_KSCAN = 0x07; + input_sync(dev->input); +} + +static int __devinit aaedkbd_probe(struct platform_device *pdev) +{ + struct aaedkbd *aaedkbd; + struct input_polled_dev *poll_dev; + struct input_dev *input_dev; + int i; + int error; + + aaedkbd = kzalloc(sizeof(struct aaedkbd), GFP_KERNEL); + poll_dev = input_allocate_polled_device(); + if (!aaedkbd || !poll_dev) { + error = -ENOMEM; + goto fail; + } + + platform_set_drvdata(pdev, aaedkbd); + + aaedkbd->poll_dev = poll_dev; + memcpy(aaedkbd->keycode, aaedkbd_keycode, sizeof(aaedkbd->keycode)); + + poll_dev->private = aaedkbd; + poll_dev->poll = aaedkbd_poll; + poll_dev->poll_interval = SCAN_INTERVAL; + + input_dev = poll_dev->input; + input_dev->name = "AAED-2000 Keyboard"; + input_dev->phys = "aaedkbd/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &pdev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + input_dev->keycode = aaedkbd->keycode; + input_dev->keycodesize = sizeof(unsigned char); + input_dev->keycodemax = ARRAY_SIZE(aaedkbd_keycode); + + for (i = 0; i < ARRAY_SIZE(aaedkbd_keycode); i++) + set_bit(aaedkbd->keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + + error = input_register_polled_device(aaedkbd->poll_dev); + if (error) + goto fail; + + return 0; + + fail: kfree(aaedkbd); + input_free_polled_device(poll_dev); + return error; +} + +static int __devexit aaedkbd_remove(struct platform_device *pdev) +{ + struct aaedkbd *aaedkbd = platform_get_drvdata(pdev); + + input_unregister_polled_device(aaedkbd->poll_dev); + input_free_polled_device(aaedkbd->poll_dev); + kfree(aaedkbd); + + return 0; +} + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:aaed2000-keyboard"); + +static struct platform_driver aaedkbd_driver = { + .probe = aaedkbd_probe, + .remove = __devexit_p(aaedkbd_remove), + .driver = { + .name = "aaed2000-keyboard", + .owner = THIS_MODULE, + }, +}; + +static int __init aaedkbd_init(void) +{ + return platform_driver_register(&aaedkbd_driver); +} + +static void __exit aaedkbd_exit(void) +{ + platform_driver_unregister(&aaedkbd_driver); +} + +module_init(aaedkbd_init); +module_exit(aaedkbd_exit); + +MODULE_AUTHOR("Nicolas Bellido Y Ortega"); +MODULE_DESCRIPTION("AAED-2000 Keyboard Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/keyboard/amikbd.c b/drivers/input/keyboard/amikbd.c new file mode 100644 index 0000000..35149ec --- /dev/null +++ b/drivers/input/keyboard/amikbd.c @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2000-2001 Vojtech Pavlik + * + * Based on the work of: + * Hamish Macdonald + */ + +/* + * Amiga keyboard driver for Linux/m68k + */ + +/* + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/keyboard.h> + +#include <asm/amigaints.h> +#include <asm/amigahw.h> +#include <asm/irq.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Amiga keyboard driver"); +MODULE_LICENSE("GPL"); + +static unsigned char amikbd_keycode[0x78] __initdata = { + [0] = KEY_GRAVE, + [1] = KEY_1, + [2] = KEY_2, + [3] = KEY_3, + [4] = KEY_4, + [5] = KEY_5, + [6] = KEY_6, + [7] = KEY_7, + [8] = KEY_8, + [9] = KEY_9, + [10] = KEY_0, + [11] = KEY_MINUS, + [12] = KEY_EQUAL, + [13] = KEY_BACKSLASH, + [15] = KEY_KP0, + [16] = KEY_Q, + [17] = KEY_W, + [18] = KEY_E, + [19] = KEY_R, + [20] = KEY_T, + [21] = KEY_Y, + [22] = KEY_U, + [23] = KEY_I, + [24] = KEY_O, + [25] = KEY_P, + [26] = KEY_LEFTBRACE, + [27] = KEY_RIGHTBRACE, + [29] = KEY_KP1, + [30] = KEY_KP2, + [31] = KEY_KP3, + [32] = KEY_A, + [33] = KEY_S, + [34] = KEY_D, + [35] = KEY_F, + [36] = KEY_G, + [37] = KEY_H, + [38] = KEY_J, + [39] = KEY_K, + [40] = KEY_L, + [41] = KEY_SEMICOLON, + [42] = KEY_APOSTROPHE, + [43] = KEY_BACKSLASH, + [45] = KEY_KP4, + [46] = KEY_KP5, + [47] = KEY_KP6, + [48] = KEY_102ND, + [49] = KEY_Z, + [50] = KEY_X, + [51] = KEY_C, + [52] = KEY_V, + [53] = KEY_B, + [54] = KEY_N, + [55] = KEY_M, + [56] = KEY_COMMA, + [57] = KEY_DOT, + [58] = KEY_SLASH, + [60] = KEY_KPDOT, + [61] = KEY_KP7, + [62] = KEY_KP8, + [63] = KEY_KP9, + [64] = KEY_SPACE, + [65] = KEY_BACKSPACE, + [66] = KEY_TAB, + [67] = KEY_KPENTER, + [68] = KEY_ENTER, + [69] = KEY_ESC, + [70] = KEY_DELETE, + [74] = KEY_KPMINUS, + [76] = KEY_UP, + [77] = KEY_DOWN, + [78] = KEY_RIGHT, + [79] = KEY_LEFT, + [80] = KEY_F1, + [81] = KEY_F2, + [82] = KEY_F3, + [83] = KEY_F4, + [84] = KEY_F5, + [85] = KEY_F6, + [86] = KEY_F7, + [87] = KEY_F8, + [88] = KEY_F9, + [89] = KEY_F10, + [90] = KEY_KPLEFTPAREN, + [91] = KEY_KPRIGHTPAREN, + [92] = KEY_KPSLASH, + [93] = KEY_KPASTERISK, + [94] = KEY_KPPLUS, + [95] = KEY_HELP, + [96] = KEY_LEFTSHIFT, + [97] = KEY_RIGHTSHIFT, + [98] = KEY_CAPSLOCK, + [99] = KEY_LEFTCTRL, + [100] = KEY_LEFTALT, + [101] = KEY_RIGHTALT, + [102] = KEY_LEFTMETA, + [103] = KEY_RIGHTMETA +}; + +static const char *amikbd_messages[8] = { + [0] = KERN_ALERT "amikbd: Ctrl-Amiga-Amiga reset warning!!\n", + [1] = KERN_WARNING "amikbd: keyboard lost sync\n", + [2] = KERN_WARNING "amikbd: keyboard buffer overflow\n", + [3] = KERN_WARNING "amikbd: keyboard controller failure\n", + [4] = KERN_ERR "amikbd: keyboard selftest failure\n", + [5] = KERN_INFO "amikbd: initiate power-up key stream\n", + [6] = KERN_INFO "amikbd: terminate power-up key stream\n", + [7] = KERN_WARNING "amikbd: keyboard interrupt\n" +}; + +static struct input_dev *amikbd_dev; + +static irqreturn_t amikbd_interrupt(int irq, void *dummy) +{ + unsigned char scancode, down; + + scancode = ~ciaa.sdr; /* get and invert scancode (keyboard is active low) */ + ciaa.cra |= 0x40; /* switch SP pin to output for handshake */ + udelay(85); /* wait until 85 us have expired */ + ciaa.cra &= ~0x40; /* switch CIA serial port to input mode */ + + down = !(scancode & 1); /* lowest bit is release bit */ + scancode >>= 1; + + if (scancode < 0x78) { /* scancodes < 0x78 are keys */ + if (scancode == 98) { /* CapsLock is a toggle switch key on Amiga */ + input_report_key(amikbd_dev, scancode, 1); + input_report_key(amikbd_dev, scancode, 0); + } else { + input_report_key(amikbd_dev, scancode, down); + } + + input_sync(amikbd_dev); + } else /* scancodes >= 0x78 are error codes */ + printk(amikbd_messages[scancode - 0x78]); + + return IRQ_HANDLED; +} + +static int __init amikbd_init(void) +{ + int i, j, err; + + if (!AMIGAHW_PRESENT(AMI_KEYBOARD)) + return -ENODEV; + + if (!request_mem_region(CIAA_PHYSADDR-1+0xb00, 0x100, "amikeyb")) + return -EBUSY; + + amikbd_dev = input_allocate_device(); + if (!amikbd_dev) { + printk(KERN_ERR "amikbd: not enough memory for input device\n"); + err = -ENOMEM; + goto fail1; + } + + amikbd_dev->name = "Amiga Keyboard"; + amikbd_dev->phys = "amikbd/input0"; + amikbd_dev->id.bustype = BUS_AMIGA; + amikbd_dev->id.vendor = 0x0001; + amikbd_dev->id.product = 0x0001; + amikbd_dev->id.version = 0x0100; + + amikbd_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + + for (i = 0; i < 0x78; i++) + set_bit(i, amikbd_dev->keybit); + + for (i = 0; i < MAX_NR_KEYMAPS; i++) { + static u_short temp_map[NR_KEYS] __initdata; + if (!key_maps[i]) + continue; + memset(temp_map, 0, sizeof(temp_map)); + for (j = 0; j < 0x78; j++) { + if (!amikbd_keycode[j]) + continue; + temp_map[j] = key_maps[i][amikbd_keycode[j]]; + } + for (j = 0; j < NR_KEYS; j++) { + if (!temp_map[j]) + temp_map[j] = 0xf200; + } + memcpy(key_maps[i], temp_map, sizeof(temp_map)); + } + ciaa.cra &= ~0x41; /* serial data in, turn off TA */ + if (request_irq(IRQ_AMIGA_CIAA_SP, amikbd_interrupt, 0, "amikbd", + amikbd_interrupt)) { + err = -EBUSY; + goto fail2; + } + + err = input_register_device(amikbd_dev); + if (err) + goto fail3; + + return 0; + + fail3: free_irq(IRQ_AMIGA_CIAA_SP, amikbd_interrupt); + fail2: input_free_device(amikbd_dev); + fail1: release_mem_region(CIAA_PHYSADDR - 1 + 0xb00, 0x100); + return err; +} + +static void __exit amikbd_exit(void) +{ + free_irq(IRQ_AMIGA_CIAA_SP, amikbd_interrupt); + input_unregister_device(amikbd_dev); + release_mem_region(CIAA_PHYSADDR - 1 + 0xb00, 0x100); +} + +module_init(amikbd_init); +module_exit(amikbd_exit); diff --git a/drivers/input/keyboard/atakbd.c b/drivers/input/keyboard/atakbd.c new file mode 100644 index 0000000..1839194 --- /dev/null +++ b/drivers/input/keyboard/atakbd.c @@ -0,0 +1,268 @@ +/* + * atakbd.c + * + * Copyright (c) 2005 Michael Schmitz + * + * Based on amikbd.c, which is + * + * Copyright (c) 2000-2001 Vojtech Pavlik + * + * Based on the work of: + * Hamish Macdonald + */ + +/* + * Atari keyboard driver for Linux/m68k + * + * The low level init and interrupt stuff is handled in arch/mm68k/atari/atakeyb.c + * (the keyboard ACIA also handles the mouse and joystick data, and the keyboard + * interrupt is shared with the MIDI ACIA so MIDI data also get handled there). + * This driver only deals with handing key events off to the input layer. + */ + +/* + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/interrupt.h> + +#include <asm/atariints.h> +#include <asm/atarihw.h> +#include <asm/atarikb.h> +#include <asm/irq.h> + +MODULE_AUTHOR("Michael Schmitz <schmitz@biophys.uni-duesseldorf.de>"); +MODULE_DESCRIPTION("Atari keyboard driver"); +MODULE_LICENSE("GPL"); + +/* + 0x47: KP_7 71 + 0x48: KP_8 72 + 0x49: KP_9 73 + 0x62: KP_/ 98 + 0x4b: KP_4 75 + 0x4c: KP_5 76 + 0x4d: KP_6 77 + 0x37: KP_* 55 + 0x4f: KP_1 79 + 0x50: KP_2 80 + 0x51: KP_3 81 + 0x4a: KP_- 74 + 0x52: KP_0 82 + 0x53: KP_. 83 + 0x4e: KP_+ 78 + + 0x67: Up 103 + 0x6c: Down 108 + 0x69: Left 105 + 0x6a: Right 106 + */ + + +static unsigned char atakbd_keycode[0x72] = { /* American layout */ + [0] = KEY_GRAVE, + [1] = KEY_ESC, + [2] = KEY_1, + [3] = KEY_2, + [4] = KEY_3, + [5] = KEY_4, + [6] = KEY_5, + [7] = KEY_6, + [8] = KEY_7, + [9] = KEY_8, + [10] = KEY_9, + [11] = KEY_0, + [12] = KEY_MINUS, + [13] = KEY_EQUAL, + [14] = KEY_BACKSPACE, + [15] = KEY_TAB, + [16] = KEY_Q, + [17] = KEY_W, + [18] = KEY_E, + [19] = KEY_R, + [20] = KEY_T, + [21] = KEY_Y, + [22] = KEY_U, + [23] = KEY_I, + [24] = KEY_O, + [25] = KEY_P, + [26] = KEY_LEFTBRACE, + [27] = KEY_RIGHTBRACE, + [28] = KEY_ENTER, + [29] = KEY_LEFTCTRL, + [30] = KEY_A, + [31] = KEY_S, + [32] = KEY_D, + [33] = KEY_F, + [34] = KEY_G, + [35] = KEY_H, + [36] = KEY_J, + [37] = KEY_K, + [38] = KEY_L, + [39] = KEY_SEMICOLON, + [40] = KEY_APOSTROPHE, + [41] = KEY_BACKSLASH, /* FIXME, '#' */ + [42] = KEY_LEFTSHIFT, + [43] = KEY_GRAVE, /* FIXME: '~' */ + [44] = KEY_Z, + [45] = KEY_X, + [46] = KEY_C, + [47] = KEY_V, + [48] = KEY_B, + [49] = KEY_N, + [50] = KEY_M, + [51] = KEY_COMMA, + [52] = KEY_DOT, + [53] = KEY_SLASH, + [54] = KEY_RIGHTSHIFT, + [55] = KEY_KPASTERISK, + [56] = KEY_LEFTALT, + [57] = KEY_SPACE, + [58] = KEY_CAPSLOCK, + [59] = KEY_F1, + [60] = KEY_F2, + [61] = KEY_F3, + [62] = KEY_F4, + [63] = KEY_F5, + [64] = KEY_F6, + [65] = KEY_F7, + [66] = KEY_F8, + [67] = KEY_F9, + [68] = KEY_F10, + [69] = KEY_ESC, + [70] = KEY_DELETE, + [71] = KEY_KP7, + [72] = KEY_KP8, + [73] = KEY_KP9, + [74] = KEY_KPMINUS, + [75] = KEY_KP4, + [76] = KEY_KP5, + [77] = KEY_KP6, + [78] = KEY_KPPLUS, + [79] = KEY_KP1, + [80] = KEY_KP2, + [81] = KEY_KP3, + [82] = KEY_KP0, + [83] = KEY_KPDOT, + [90] = KEY_KPLEFTPAREN, + [91] = KEY_KPRIGHTPAREN, + [92] = KEY_KPASTERISK, /* FIXME */ + [93] = KEY_KPASTERISK, + [94] = KEY_KPPLUS, + [95] = KEY_HELP, + [96] = KEY_BACKSLASH, /* FIXME: '<' */ + [97] = KEY_KPASTERISK, /* FIXME */ + [98] = KEY_KPSLASH, + [99] = KEY_KPLEFTPAREN, + [100] = KEY_KPRIGHTPAREN, + [101] = KEY_KPSLASH, + [102] = KEY_KPASTERISK, + [103] = KEY_UP, + [104] = KEY_KPASTERISK, /* FIXME */ + [105] = KEY_LEFT, + [106] = KEY_RIGHT, + [107] = KEY_KPASTERISK, /* FIXME */ + [108] = KEY_DOWN, + [109] = KEY_KPASTERISK, /* FIXME */ + [110] = KEY_KPASTERISK, /* FIXME */ + [111] = KEY_KPASTERISK, /* FIXME */ + [112] = KEY_KPASTERISK, /* FIXME */ + [113] = KEY_KPASTERISK /* FIXME */ +}; + +static struct input_dev *atakbd_dev; + +static void atakbd_interrupt(unsigned char scancode, char down) +{ + + if (scancode < 0x72) { /* scancodes < 0xf2 are keys */ + + // report raw events here? + + scancode = atakbd_keycode[scancode]; + + if (scancode == KEY_CAPSLOCK) { /* CapsLock is a toggle switch key on Amiga */ + input_report_key(atakbd_dev, scancode, 1); + input_report_key(atakbd_dev, scancode, 0); + input_sync(atakbd_dev); + } else { + input_report_key(atakbd_dev, scancode, down); + input_sync(atakbd_dev); + } + } else /* scancodes >= 0xf2 are mouse data, most likely */ + printk(KERN_INFO "atakbd: unhandled scancode %x\n", scancode); + + return; +} + +static int __init atakbd_init(void) +{ + int i, error; + + if (!MACH_IS_ATARI || !ATARIHW_PRESENT(ST_MFP)) + return -ENODEV; + + // need to init core driver if not already done so + if (atari_keyb_init()) + return -ENODEV; + + atakbd_dev = input_allocate_device(); + if (!atakbd_dev) + return -ENOMEM; + + atakbd_dev->name = "Atari Keyboard"; + atakbd_dev->phys = "atakbd/input0"; + atakbd_dev->id.bustype = BUS_HOST; + atakbd_dev->id.vendor = 0x0001; + atakbd_dev->id.product = 0x0001; + atakbd_dev->id.version = 0x0100; + + atakbd_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + atakbd_dev->keycode = atakbd_keycode; + atakbd_dev->keycodesize = sizeof(unsigned char); + atakbd_dev->keycodemax = ARRAY_SIZE(atakbd_keycode); + + for (i = 1; i < 0x72; i++) { + set_bit(atakbd_keycode[i], atakbd_dev->keybit); + } + + /* error check */ + error = input_register_device(atakbd_dev); + if (error) { + input_free_device(atakbd_dev); + return error; + } + + atari_input_keyboard_interrupt_hook = atakbd_interrupt; + + return 0; +} + +static void __exit atakbd_exit(void) +{ + atari_input_keyboard_interrupt_hook = NULL; + input_unregister_device(atakbd_dev); +} + +module_init(atakbd_init); +module_exit(atakbd_exit); diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c new file mode 100644 index 0000000..159dbb6 --- /dev/null +++ b/drivers/input/keyboard/atkbd.c @@ -0,0 +1,1537 @@ +/* + * AT and PS/2 keyboard driver + * + * Copyright (c) 1999-2002 Vojtech Pavlik + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * This driver can handle standard AT keyboards and PS/2 keyboards in + * Translated and Raw Set 2 and Set 3, as well as AT keyboards on dumb + * input-only controllers and AT keyboards connected over a one way RS232 + * converter. + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/workqueue.h> +#include <linux/libps2.h> +#include <linux/mutex.h> +#include <linux/dmi.h> + +#define DRIVER_DESC "AT and PS/2 keyboard driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static int atkbd_set = 2; +module_param_named(set, atkbd_set, int, 0); +MODULE_PARM_DESC(set, "Select keyboard code set (2 = default, 3 = PS/2 native)"); + +#if defined(__i386__) || defined(__x86_64__) || defined(__hppa__) +static int atkbd_reset; +#else +static int atkbd_reset = 1; +#endif +module_param_named(reset, atkbd_reset, bool, 0); +MODULE_PARM_DESC(reset, "Reset keyboard during initialization"); + +static int atkbd_softrepeat; +module_param_named(softrepeat, atkbd_softrepeat, bool, 0); +MODULE_PARM_DESC(softrepeat, "Use software keyboard repeat"); + +static int atkbd_softraw = 1; +module_param_named(softraw, atkbd_softraw, bool, 0); +MODULE_PARM_DESC(softraw, "Use software generated rawmode"); + +static int atkbd_scroll; +module_param_named(scroll, atkbd_scroll, bool, 0); +MODULE_PARM_DESC(scroll, "Enable scroll-wheel on MS Office and similar keyboards"); + +static int atkbd_extra; +module_param_named(extra, atkbd_extra, bool, 0); +MODULE_PARM_DESC(extra, "Enable extra LEDs and keys on IBM RapidAcces, EzKey and similar keyboards"); + +/* + * Scancode to keycode tables. These are just the default setting, and + * are loadable via an userland utility. + */ + +static const unsigned short atkbd_set2_keycode[512] = { + +#ifdef CONFIG_KEYBOARD_ATKBD_HP_KEYCODES + +/* XXX: need a more general approach */ + +#include "hpps2atkbd.h" /* include the keyboard scancodes */ + +#else + 0, 67, 65, 63, 61, 59, 60, 88, 0, 68, 66, 64, 62, 15, 41,117, + 0, 56, 42, 93, 29, 16, 2, 0, 0, 0, 44, 31, 30, 17, 3, 0, + 0, 46, 45, 32, 18, 5, 4, 95, 0, 57, 47, 33, 20, 19, 6,183, + 0, 49, 48, 35, 34, 21, 7,184, 0, 0, 50, 36, 22, 8, 9,185, + 0, 51, 37, 23, 24, 11, 10, 0, 0, 52, 53, 38, 39, 25, 12, 0, + 0, 89, 40, 0, 26, 13, 0, 0, 58, 54, 28, 27, 0, 43, 0, 85, + 0, 86, 91, 90, 92, 0, 14, 94, 0, 79,124, 75, 71,121, 0, 0, + 82, 83, 80, 76, 77, 72, 1, 69, 87, 78, 81, 74, 55, 73, 70, 99, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 217,100,255, 0, 97,165, 0, 0,156, 0, 0, 0, 0, 0, 0,125, + 173,114, 0,113, 0, 0, 0,126,128, 0, 0,140, 0, 0, 0,127, + 159, 0,115, 0,164, 0, 0,116,158, 0,172,166, 0, 0, 0,142, + 157, 0, 0, 0, 0, 0, 0, 0,155, 0, 98, 0, 0,163, 0, 0, + 226, 0, 0, 0, 0, 0, 0, 0, 0,255, 96, 0, 0, 0,143, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0,107, 0,105,102, 0, 0,112, + 110,111,108,112,106,103, 0,119, 0,118,109, 0, 99,104,119, 0, + + 0, 0, 0, 65, 99, +#endif +}; + +static const unsigned short atkbd_set3_keycode[512] = { + + 0, 0, 0, 0, 0, 0, 0, 59, 1,138,128,129,130, 15, 41, 60, + 131, 29, 42, 86, 58, 16, 2, 61,133, 56, 44, 31, 30, 17, 3, 62, + 134, 46, 45, 32, 18, 5, 4, 63,135, 57, 47, 33, 20, 19, 6, 64, + 136, 49, 48, 35, 34, 21, 7, 65,137,100, 50, 36, 22, 8, 9, 66, + 125, 51, 37, 23, 24, 11, 10, 67,126, 52, 53, 38, 39, 25, 12, 68, + 113,114, 40, 43, 26, 13, 87, 99, 97, 54, 28, 27, 43, 43, 88, 70, + 108,105,119,103,111,107, 14,110, 0, 79,106, 75, 71,109,102,104, + 82, 83, 80, 76, 77, 72, 69, 98, 0, 96, 81, 0, 78, 73, 55,183, + + 184,185,186,187, 74, 94, 92, 93, 0, 0, 0,125,126,127,112, 0, + 0,139,172,163,165,115,152,172,166,140,160,154,113,114,167,168, + 148,149,147,140 +}; + +static const unsigned short atkbd_unxlate_table[128] = { + 0,118, 22, 30, 38, 37, 46, 54, 61, 62, 70, 69, 78, 85,102, 13, + 21, 29, 36, 45, 44, 53, 60, 67, 68, 77, 84, 91, 90, 20, 28, 27, + 35, 43, 52, 51, 59, 66, 75, 76, 82, 14, 18, 93, 26, 34, 33, 42, + 50, 49, 58, 65, 73, 74, 89,124, 17, 41, 88, 5, 6, 4, 12, 3, + 11, 2, 10, 1, 9,119,126,108,117,125,123,107,115,116,121,105, + 114,122,112,113,127, 96, 97,120, 7, 15, 23, 31, 39, 47, 55, 63, + 71, 79, 86, 94, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 87,111, + 19, 25, 57, 81, 83, 92, 95, 98, 99,100,101,103,104,106,109,110 +}; + +#define ATKBD_CMD_SETLEDS 0x10ed +#define ATKBD_CMD_GSCANSET 0x11f0 +#define ATKBD_CMD_SSCANSET 0x10f0 +#define ATKBD_CMD_GETID 0x02f2 +#define ATKBD_CMD_SETREP 0x10f3 +#define ATKBD_CMD_ENABLE 0x00f4 +#define ATKBD_CMD_RESET_DIS 0x00f5 +#define ATKBD_CMD_SETALL_MBR 0x00fa +#define ATKBD_CMD_RESET_BAT 0x02ff +#define ATKBD_CMD_RESEND 0x00fe +#define ATKBD_CMD_EX_ENABLE 0x10ea +#define ATKBD_CMD_EX_SETLEDS 0x20eb +#define ATKBD_CMD_OK_GETID 0x02e8 + +#define ATKBD_RET_ACK 0xfa +#define ATKBD_RET_NAK 0xfe +#define ATKBD_RET_BAT 0xaa +#define ATKBD_RET_EMUL0 0xe0 +#define ATKBD_RET_EMUL1 0xe1 +#define ATKBD_RET_RELEASE 0xf0 +#define ATKBD_RET_HANJA 0xf1 +#define ATKBD_RET_HANGEUL 0xf2 +#define ATKBD_RET_ERR 0xff + +#define ATKBD_KEY_UNKNOWN 0 +#define ATKBD_KEY_NULL 255 + +#define ATKBD_SCR_1 254 +#define ATKBD_SCR_2 253 +#define ATKBD_SCR_4 252 +#define ATKBD_SCR_8 251 +#define ATKBD_SCR_CLICK 250 +#define ATKBD_SCR_LEFT 249 +#define ATKBD_SCR_RIGHT 248 + +#define ATKBD_SPECIAL ATKBD_SCR_RIGHT + +#define ATKBD_LED_EVENT_BIT 0 +#define ATKBD_REP_EVENT_BIT 1 + +#define ATKBD_XL_ERR 0x01 +#define ATKBD_XL_BAT 0x02 +#define ATKBD_XL_ACK 0x04 +#define ATKBD_XL_NAK 0x08 +#define ATKBD_XL_HANGEUL 0x10 +#define ATKBD_XL_HANJA 0x20 + +static const struct { + unsigned char keycode; + unsigned char set2; +} atkbd_scroll_keys[] = { + { ATKBD_SCR_1, 0xc5 }, + { ATKBD_SCR_2, 0x9d }, + { ATKBD_SCR_4, 0xa4 }, + { ATKBD_SCR_8, 0x9b }, + { ATKBD_SCR_CLICK, 0xe0 }, + { ATKBD_SCR_LEFT, 0xcb }, + { ATKBD_SCR_RIGHT, 0xd2 }, +}; + +/* + * The atkbd control structure + */ + +struct atkbd { + + struct ps2dev ps2dev; + struct input_dev *dev; + + /* Written only during init */ + char name[64]; + char phys[32]; + + unsigned short id; + unsigned short keycode[512]; + DECLARE_BITMAP(force_release_mask, 512); + unsigned char set; + unsigned char translated; + unsigned char extra; + unsigned char write; + unsigned char softrepeat; + unsigned char softraw; + unsigned char scroll; + unsigned char enabled; + + /* Accessed only from interrupt */ + unsigned char emul; + unsigned char resend; + unsigned char release; + unsigned long xl_bit; + unsigned int last; + unsigned long time; + unsigned long err_count; + + struct delayed_work event_work; + unsigned long event_jiffies; + struct mutex event_mutex; + unsigned long event_mask; +}; + +/* + * System-specific ketymap fixup routine + */ +static void (*atkbd_platform_fixup)(struct atkbd *); + +static ssize_t atkbd_attr_show_helper(struct device *dev, char *buf, + ssize_t (*handler)(struct atkbd *, char *)); +static ssize_t atkbd_attr_set_helper(struct device *dev, const char *buf, size_t count, + ssize_t (*handler)(struct atkbd *, const char *, size_t)); +#define ATKBD_DEFINE_ATTR(_name) \ +static ssize_t atkbd_show_##_name(struct atkbd *, char *); \ +static ssize_t atkbd_set_##_name(struct atkbd *, const char *, size_t); \ +static ssize_t atkbd_do_show_##_name(struct device *d, \ + struct device_attribute *attr, char *b) \ +{ \ + return atkbd_attr_show_helper(d, b, atkbd_show_##_name); \ +} \ +static ssize_t atkbd_do_set_##_name(struct device *d, \ + struct device_attribute *attr, const char *b, size_t s) \ +{ \ + return atkbd_attr_set_helper(d, b, s, atkbd_set_##_name); \ +} \ +static struct device_attribute atkbd_attr_##_name = \ + __ATTR(_name, S_IWUSR | S_IRUGO, atkbd_do_show_##_name, atkbd_do_set_##_name); + +ATKBD_DEFINE_ATTR(extra); +ATKBD_DEFINE_ATTR(scroll); +ATKBD_DEFINE_ATTR(set); +ATKBD_DEFINE_ATTR(softrepeat); +ATKBD_DEFINE_ATTR(softraw); + +#define ATKBD_DEFINE_RO_ATTR(_name) \ +static ssize_t atkbd_show_##_name(struct atkbd *, char *); \ +static ssize_t atkbd_do_show_##_name(struct device *d, \ + struct device_attribute *attr, char *b) \ +{ \ + return atkbd_attr_show_helper(d, b, atkbd_show_##_name); \ +} \ +static struct device_attribute atkbd_attr_##_name = \ + __ATTR(_name, S_IRUGO, atkbd_do_show_##_name, NULL); + +ATKBD_DEFINE_RO_ATTR(err_count); + +static struct attribute *atkbd_attributes[] = { + &atkbd_attr_extra.attr, + &atkbd_attr_scroll.attr, + &atkbd_attr_set.attr, + &atkbd_attr_softrepeat.attr, + &atkbd_attr_softraw.attr, + &atkbd_attr_err_count.attr, + NULL +}; + +static struct attribute_group atkbd_attribute_group = { + .attrs = atkbd_attributes, +}; + +static const unsigned int xl_table[] = { + ATKBD_RET_BAT, ATKBD_RET_ERR, ATKBD_RET_ACK, + ATKBD_RET_NAK, ATKBD_RET_HANJA, ATKBD_RET_HANGEUL, +}; + +/* + * Checks if we should mangle the scancode to extract 'release' bit + * in translated mode. + */ +static int atkbd_need_xlate(unsigned long xl_bit, unsigned char code) +{ + int i; + + if (code == ATKBD_RET_EMUL0 || code == ATKBD_RET_EMUL1) + return 0; + + for (i = 0; i < ARRAY_SIZE(xl_table); i++) + if (code == xl_table[i]) + return test_bit(i, &xl_bit); + + return 1; +} + +/* + * Calculates new value of xl_bit so the driver can distinguish + * between make/break pair of scancodes for select keys and PS/2 + * protocol responses. + */ +static void atkbd_calculate_xl_bit(struct atkbd *atkbd, unsigned char code) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(xl_table); i++) { + if (!((code ^ xl_table[i]) & 0x7f)) { + if (code & 0x80) + __clear_bit(i, &atkbd->xl_bit); + else + __set_bit(i, &atkbd->xl_bit); + break; + } + } +} + +/* + * Encode the scancode, 0xe0 prefix, and high bit into a single integer, + * keeping kernel 2.4 compatibility for set 2 + */ +static unsigned int atkbd_compat_scancode(struct atkbd *atkbd, unsigned int code) +{ + if (atkbd->set == 3) { + if (atkbd->emul == 1) + code |= 0x100; + } else { + code = (code & 0x7f) | ((code & 0x80) << 1); + if (atkbd->emul == 1) + code |= 0x80; + } + + return code; +} + +/* + * atkbd_interrupt(). Here takes place processing of data received from + * the keyboard into events. + */ + +static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data, + unsigned int flags) +{ + struct atkbd *atkbd = serio_get_drvdata(serio); + struct input_dev *dev = atkbd->dev; + unsigned int code = data; + int scroll = 0, hscroll = 0, click = -1; + int value; + unsigned short keycode; + +#ifdef ATKBD_DEBUG + printk(KERN_DEBUG "atkbd.c: Received %02x flags %02x\n", data, flags); +#endif + +#if !defined(__i386__) && !defined (__x86_64__) + if ((flags & (SERIO_FRAME | SERIO_PARITY)) && (~flags & SERIO_TIMEOUT) && !atkbd->resend && atkbd->write) { + printk(KERN_WARNING "atkbd.c: frame/parity error: %02x\n", flags); + serio_write(serio, ATKBD_CMD_RESEND); + atkbd->resend = 1; + goto out; + } + + if (!flags && data == ATKBD_RET_ACK) + atkbd->resend = 0; +#endif + + if (unlikely(atkbd->ps2dev.flags & PS2_FLAG_ACK)) + if (ps2_handle_ack(&atkbd->ps2dev, data)) + goto out; + + if (unlikely(atkbd->ps2dev.flags & PS2_FLAG_CMD)) + if (ps2_handle_response(&atkbd->ps2dev, data)) + goto out; + + if (!atkbd->enabled) + goto out; + + input_event(dev, EV_MSC, MSC_RAW, code); + + if (atkbd->translated) { + + if (atkbd->emul || atkbd_need_xlate(atkbd->xl_bit, code)) { + atkbd->release = code >> 7; + code &= 0x7f; + } + + if (!atkbd->emul) + atkbd_calculate_xl_bit(atkbd, data); + } + + switch (code) { + case ATKBD_RET_BAT: + atkbd->enabled = 0; + serio_reconnect(atkbd->ps2dev.serio); + goto out; + case ATKBD_RET_EMUL0: + atkbd->emul = 1; + goto out; + case ATKBD_RET_EMUL1: + atkbd->emul = 2; + goto out; + case ATKBD_RET_RELEASE: + atkbd->release = 1; + goto out; + case ATKBD_RET_ACK: + case ATKBD_RET_NAK: + if (printk_ratelimit()) + printk(KERN_WARNING "atkbd.c: Spurious %s on %s. " + "Some program might be trying access hardware directly.\n", + data == ATKBD_RET_ACK ? "ACK" : "NAK", serio->phys); + goto out; + case ATKBD_RET_ERR: + atkbd->err_count++; +#ifdef ATKBD_DEBUG + printk(KERN_DEBUG "atkbd.c: Keyboard on %s reports too many keys pressed.\n", serio->phys); +#endif + goto out; + } + + code = atkbd_compat_scancode(atkbd, code); + + if (atkbd->emul && --atkbd->emul) + goto out; + + keycode = atkbd->keycode[code]; + + if (keycode != ATKBD_KEY_NULL) + input_event(dev, EV_MSC, MSC_SCAN, code); + + switch (keycode) { + case ATKBD_KEY_NULL: + break; + case ATKBD_KEY_UNKNOWN: + printk(KERN_WARNING + "atkbd.c: Unknown key %s (%s set %d, code %#x on %s).\n", + atkbd->release ? "released" : "pressed", + atkbd->translated ? "translated" : "raw", + atkbd->set, code, serio->phys); + printk(KERN_WARNING + "atkbd.c: Use 'setkeycodes %s%02x <keycode>' to make it known.\n", + code & 0x80 ? "e0" : "", code & 0x7f); + input_sync(dev); + break; + case ATKBD_SCR_1: + scroll = 1 - atkbd->release * 2; + break; + case ATKBD_SCR_2: + scroll = 2 - atkbd->release * 4; + break; + case ATKBD_SCR_4: + scroll = 4 - atkbd->release * 8; + break; + case ATKBD_SCR_8: + scroll = 8 - atkbd->release * 16; + break; + case ATKBD_SCR_CLICK: + click = !atkbd->release; + break; + case ATKBD_SCR_LEFT: + hscroll = -1; + break; + case ATKBD_SCR_RIGHT: + hscroll = 1; + break; + default: + if (atkbd->release) { + value = 0; + atkbd->last = 0; + } else if (!atkbd->softrepeat && test_bit(keycode, dev->key)) { + /* Workaround Toshiba laptop multiple keypress */ + value = time_before(jiffies, atkbd->time) && atkbd->last == code ? 1 : 2; + } else { + value = 1; + atkbd->last = code; + atkbd->time = jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]) / 2; + } + + input_event(dev, EV_KEY, keycode, value); + input_sync(dev); + + if (value && test_bit(code, atkbd->force_release_mask)) { + input_report_key(dev, keycode, 0); + input_sync(dev); + } + } + + if (atkbd->scroll) { + if (click != -1) + input_report_key(dev, BTN_MIDDLE, click); + input_report_rel(dev, REL_WHEEL, scroll); + input_report_rel(dev, REL_HWHEEL, hscroll); + input_sync(dev); + } + + atkbd->release = 0; +out: + return IRQ_HANDLED; +} + +static int atkbd_set_repeat_rate(struct atkbd *atkbd) +{ + const short period[32] = + { 33, 37, 42, 46, 50, 54, 58, 63, 67, 75, 83, 92, 100, 109, 116, 125, + 133, 149, 167, 182, 200, 217, 232, 250, 270, 303, 333, 370, 400, 435, 470, 500 }; + const short delay[4] = + { 250, 500, 750, 1000 }; + + struct input_dev *dev = atkbd->dev; + unsigned char param; + int i = 0, j = 0; + + while (i < ARRAY_SIZE(period) - 1 && period[i] < dev->rep[REP_PERIOD]) + i++; + dev->rep[REP_PERIOD] = period[i]; + + while (j < ARRAY_SIZE(delay) - 1 && delay[j] < dev->rep[REP_DELAY]) + j++; + dev->rep[REP_DELAY] = delay[j]; + + param = i | (j << 5); + return ps2_command(&atkbd->ps2dev, ¶m, ATKBD_CMD_SETREP); +} + +static int atkbd_set_leds(struct atkbd *atkbd) +{ + struct input_dev *dev = atkbd->dev; + unsigned char param[2]; + + param[0] = (test_bit(LED_SCROLLL, dev->led) ? 1 : 0) + | (test_bit(LED_NUML, dev->led) ? 2 : 0) + | (test_bit(LED_CAPSL, dev->led) ? 4 : 0); + if (ps2_command(&atkbd->ps2dev, param, ATKBD_CMD_SETLEDS)) + return -1; + + if (atkbd->extra) { + param[0] = 0; + param[1] = (test_bit(LED_COMPOSE, dev->led) ? 0x01 : 0) + | (test_bit(LED_SLEEP, dev->led) ? 0x02 : 0) + | (test_bit(LED_SUSPEND, dev->led) ? 0x04 : 0) + | (test_bit(LED_MISC, dev->led) ? 0x10 : 0) + | (test_bit(LED_MUTE, dev->led) ? 0x20 : 0); + if (ps2_command(&atkbd->ps2dev, param, ATKBD_CMD_EX_SETLEDS)) + return -1; + } + + return 0; +} + +/* + * atkbd_event_work() is used to complete processing of events that + * can not be processed by input_event() which is often called from + * interrupt context. + */ + +static void atkbd_event_work(struct work_struct *work) +{ + struct atkbd *atkbd = container_of(work, struct atkbd, event_work.work); + + mutex_lock(&atkbd->event_mutex); + + if (test_and_clear_bit(ATKBD_LED_EVENT_BIT, &atkbd->event_mask)) + atkbd_set_leds(atkbd); + + if (test_and_clear_bit(ATKBD_REP_EVENT_BIT, &atkbd->event_mask)) + atkbd_set_repeat_rate(atkbd); + + mutex_unlock(&atkbd->event_mutex); +} + +/* + * Schedule switch for execution. We need to throttle requests, + * otherwise keyboard may become unresponsive. + */ +static void atkbd_schedule_event_work(struct atkbd *atkbd, int event_bit) +{ + unsigned long delay = msecs_to_jiffies(50); + + if (time_after(jiffies, atkbd->event_jiffies + delay)) + delay = 0; + + atkbd->event_jiffies = jiffies; + set_bit(event_bit, &atkbd->event_mask); + wmb(); + schedule_delayed_work(&atkbd->event_work, delay); +} + +/* + * Event callback from the input module. Events that change the state of + * the hardware are processed here. If action can not be performed in + * interrupt context it is offloaded to atkbd_event_work. + */ + +static int atkbd_event(struct input_dev *dev, + unsigned int type, unsigned int code, int value) +{ + struct atkbd *atkbd = input_get_drvdata(dev); + + if (!atkbd->write) + return -1; + + switch (type) { + + case EV_LED: + atkbd_schedule_event_work(atkbd, ATKBD_LED_EVENT_BIT); + return 0; + + case EV_REP: + if (!atkbd->softrepeat) + atkbd_schedule_event_work(atkbd, ATKBD_REP_EVENT_BIT); + return 0; + } + + return -1; +} + +/* + * atkbd_enable() signals that interrupt handler is allowed to + * generate input events. + */ + +static inline void atkbd_enable(struct atkbd *atkbd) +{ + serio_pause_rx(atkbd->ps2dev.serio); + atkbd->enabled = 1; + serio_continue_rx(atkbd->ps2dev.serio); +} + +/* + * atkbd_disable() tells input handler that all incoming data except + * for ACKs and command response should be dropped. + */ + +static inline void atkbd_disable(struct atkbd *atkbd) +{ + serio_pause_rx(atkbd->ps2dev.serio); + atkbd->enabled = 0; + serio_continue_rx(atkbd->ps2dev.serio); +} + +/* + * atkbd_probe() probes for an AT keyboard on a serio port. + */ + +static int atkbd_probe(struct atkbd *atkbd) +{ + struct ps2dev *ps2dev = &atkbd->ps2dev; + unsigned char param[2]; + +/* + * Some systems, where the bit-twiddling when testing the io-lines of the + * controller may confuse the keyboard need a full reset of the keyboard. On + * these systems the BIOS also usually doesn't do it for us. + */ + + if (atkbd_reset) + if (ps2_command(ps2dev, NULL, ATKBD_CMD_RESET_BAT)) + printk(KERN_WARNING "atkbd.c: keyboard reset failed on %s\n", ps2dev->serio->phys); + +/* + * Then we check the keyboard ID. We should get 0xab83 under normal conditions. + * Some keyboards report different values, but the first byte is always 0xab or + * 0xac. Some old AT keyboards don't report anything. If a mouse is connected, this + * should make sure we don't try to set the LEDs on it. + */ + + param[0] = param[1] = 0xa5; /* initialize with invalid values */ + if (ps2_command(ps2dev, param, ATKBD_CMD_GETID)) { + +/* + * If the get ID command failed, we check if we can at least set the LEDs on + * the keyboard. This should work on every keyboard out there. It also turns + * the LEDs off, which we want anyway. + */ + param[0] = 0; + if (ps2_command(ps2dev, param, ATKBD_CMD_SETLEDS)) + return -1; + atkbd->id = 0xabba; + return 0; + } + + if (!ps2_is_keyboard_id(param[0])) + return -1; + + atkbd->id = (param[0] << 8) | param[1]; + + if (atkbd->id == 0xaca1 && atkbd->translated) { + printk(KERN_ERR "atkbd.c: NCD terminal keyboards are only supported on non-translating\n"); + printk(KERN_ERR "atkbd.c: controllers. Use i8042.direct=1 to disable translation.\n"); + return -1; + } + + return 0; +} + +/* + * atkbd_select_set checks if a keyboard has a working Set 3 support, and + * sets it into that. Unfortunately there are keyboards that can be switched + * to Set 3, but don't work well in that (BTC Multimedia ...) + */ + +static int atkbd_select_set(struct atkbd *atkbd, int target_set, int allow_extra) +{ + struct ps2dev *ps2dev = &atkbd->ps2dev; + unsigned char param[2]; + + atkbd->extra = 0; +/* + * For known special keyboards we can go ahead and set the correct set. + * We check for NCD PS/2 Sun, NorthGate OmniKey 101 and + * IBM RapidAccess / IBM EzButton / Chicony KBP-8993 keyboards. + */ + + if (atkbd->translated) + return 2; + + if (atkbd->id == 0xaca1) { + param[0] = 3; + ps2_command(ps2dev, param, ATKBD_CMD_SSCANSET); + return 3; + } + + if (allow_extra) { + param[0] = 0x71; + if (!ps2_command(ps2dev, param, ATKBD_CMD_EX_ENABLE)) { + atkbd->extra = 1; + return 2; + } + } + + if (target_set != 3) + return 2; + + if (!ps2_command(ps2dev, param, ATKBD_CMD_OK_GETID)) { + atkbd->id = param[0] << 8 | param[1]; + return 2; + } + + param[0] = 3; + if (ps2_command(ps2dev, param, ATKBD_CMD_SSCANSET)) + return 2; + + param[0] = 0; + if (ps2_command(ps2dev, param, ATKBD_CMD_GSCANSET)) + return 2; + + if (param[0] != 3) { + param[0] = 2; + if (ps2_command(ps2dev, param, ATKBD_CMD_SSCANSET)) + return 2; + } + + ps2_command(ps2dev, param, ATKBD_CMD_SETALL_MBR); + + return 3; +} + +static int atkbd_activate(struct atkbd *atkbd) +{ + struct ps2dev *ps2dev = &atkbd->ps2dev; + unsigned char param[1]; + +/* + * Set the LEDs to a defined state. + */ + + param[0] = 0; + if (ps2_command(ps2dev, param, ATKBD_CMD_SETLEDS)) + return -1; + +/* + * Set autorepeat to fastest possible. + */ + + param[0] = 0; + if (ps2_command(ps2dev, param, ATKBD_CMD_SETREP)) + return -1; + +/* + * Enable the keyboard to receive keystrokes. + */ + + if (ps2_command(ps2dev, NULL, ATKBD_CMD_ENABLE)) { + printk(KERN_ERR "atkbd.c: Failed to enable keyboard on %s\n", + ps2dev->serio->phys); + return -1; + } + + return 0; +} + +/* + * atkbd_cleanup() restores the keyboard state so that BIOS is happy after a + * reboot. + */ + +static void atkbd_cleanup(struct serio *serio) +{ + struct atkbd *atkbd = serio_get_drvdata(serio); + + atkbd_disable(atkbd); + ps2_command(&atkbd->ps2dev, NULL, ATKBD_CMD_RESET_BAT); +} + + +/* + * atkbd_disconnect() closes and frees. + */ + +static void atkbd_disconnect(struct serio *serio) +{ + struct atkbd *atkbd = serio_get_drvdata(serio); + + atkbd_disable(atkbd); + + /* make sure we don't have a command in flight */ + cancel_delayed_work_sync(&atkbd->event_work); + + sysfs_remove_group(&serio->dev.kobj, &atkbd_attribute_group); + input_unregister_device(atkbd->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(atkbd); +} + +/* + * Most special keys (Fn+F?) on Dell laptops do not generate release + * events so we have to do it ourselves. + */ +static void atkbd_dell_laptop_keymap_fixup(struct atkbd *atkbd) +{ + const unsigned int forced_release_keys[] = { + 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8f, 0x93, + }; + int i; + + if (atkbd->set == 2) + for (i = 0; i < ARRAY_SIZE(forced_release_keys); i++) + __set_bit(forced_release_keys[i], + atkbd->force_release_mask); +} + +/* + * Perform fixup for HP system that doesn't generate release + * for its video switch + */ +static void atkbd_hp_keymap_fixup(struct atkbd *atkbd) +{ + const unsigned int forced_release_keys[] = { + 0x94, + }; + int i; + + if (atkbd->set == 2) + for (i = 0; i < ARRAY_SIZE(forced_release_keys); i++) + __set_bit(forced_release_keys[i], + atkbd->force_release_mask); +} + +/* + * Inventec system with broken key release on volume keys + */ +static void atkbd_inventec_keymap_fixup(struct atkbd *atkbd) +{ + const unsigned int forced_release_keys[] = { + 0xae, 0xb0, + }; + int i; + + if (atkbd->set == 2) + for (i = 0; i < ARRAY_SIZE(forced_release_keys); i++) + __set_bit(forced_release_keys[i], + atkbd->force_release_mask); +} + +/* + * Samsung NC10 with Fn+F? key release not working + */ +static void atkbd_samsung_keymap_fixup(struct atkbd *atkbd) +{ + const unsigned int forced_release_keys[] = { + 0x82, 0x83, 0x84, 0x86, 0x88, 0x89, 0xb3, 0xf7, 0xf9, + }; + int i; + + if (atkbd->set == 2) + for (i = 0; i < ARRAY_SIZE(forced_release_keys); i++) + __set_bit(forced_release_keys[i], + atkbd->force_release_mask); +} + +/* + * atkbd_set_keycode_table() initializes keyboard's keycode table + * according to the selected scancode set + */ + +static void atkbd_set_keycode_table(struct atkbd *atkbd) +{ + unsigned int scancode; + int i, j; + + memset(atkbd->keycode, 0, sizeof(atkbd->keycode)); + bitmap_zero(atkbd->force_release_mask, 512); + + if (atkbd->translated) { + for (i = 0; i < 128; i++) { + scancode = atkbd_unxlate_table[i]; + atkbd->keycode[i] = atkbd_set2_keycode[scancode]; + atkbd->keycode[i | 0x80] = atkbd_set2_keycode[scancode | 0x80]; + if (atkbd->scroll) + for (j = 0; j < ARRAY_SIZE(atkbd_scroll_keys); j++) + if ((scancode | 0x80) == atkbd_scroll_keys[j].set2) + atkbd->keycode[i | 0x80] = atkbd_scroll_keys[j].keycode; + } + } else if (atkbd->set == 3) { + memcpy(atkbd->keycode, atkbd_set3_keycode, sizeof(atkbd->keycode)); + } else { + memcpy(atkbd->keycode, atkbd_set2_keycode, sizeof(atkbd->keycode)); + + if (atkbd->scroll) + for (i = 0; i < ARRAY_SIZE(atkbd_scroll_keys); i++) { + scancode = atkbd_scroll_keys[i].set2; + atkbd->keycode[scancode] = atkbd_scroll_keys[i].keycode; + } + } + +/* + * HANGEUL and HANJA keys do not send release events so we need to + * generate such events ourselves + */ + scancode = atkbd_compat_scancode(atkbd, ATKBD_RET_HANGEUL); + atkbd->keycode[scancode] = KEY_HANGEUL; + __set_bit(scancode, atkbd->force_release_mask); + + scancode = atkbd_compat_scancode(atkbd, ATKBD_RET_HANJA); + atkbd->keycode[scancode] = KEY_HANJA; + __set_bit(scancode, atkbd->force_release_mask); + +/* + * Perform additional fixups + */ + if (atkbd_platform_fixup) + atkbd_platform_fixup(atkbd); +} + +/* + * atkbd_set_device_attrs() sets up keyboard's input device structure + */ + +static void atkbd_set_device_attrs(struct atkbd *atkbd) +{ + struct input_dev *input_dev = atkbd->dev; + int i; + + if (atkbd->extra) + snprintf(atkbd->name, sizeof(atkbd->name), + "AT Set 2 Extra keyboard"); + else + snprintf(atkbd->name, sizeof(atkbd->name), + "AT %s Set %d keyboard", + atkbd->translated ? "Translated" : "Raw", atkbd->set); + + snprintf(atkbd->phys, sizeof(atkbd->phys), + "%s/input0", atkbd->ps2dev.serio->phys); + + input_dev->name = atkbd->name; + input_dev->phys = atkbd->phys; + input_dev->id.bustype = BUS_I8042; + input_dev->id.vendor = 0x0001; + input_dev->id.product = atkbd->translated ? 1 : atkbd->set; + input_dev->id.version = atkbd->id; + input_dev->event = atkbd_event; + input_dev->dev.parent = &atkbd->ps2dev.serio->dev; + + input_set_drvdata(input_dev, atkbd); + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | + BIT_MASK(EV_MSC); + + if (atkbd->write) { + input_dev->evbit[0] |= BIT_MASK(EV_LED); + input_dev->ledbit[0] = BIT_MASK(LED_NUML) | + BIT_MASK(LED_CAPSL) | BIT_MASK(LED_SCROLLL); + } + + if (atkbd->extra) + input_dev->ledbit[0] |= BIT_MASK(LED_COMPOSE) | + BIT_MASK(LED_SUSPEND) | BIT_MASK(LED_SLEEP) | + BIT_MASK(LED_MUTE) | BIT_MASK(LED_MISC); + + if (!atkbd->softrepeat) { + input_dev->rep[REP_DELAY] = 250; + input_dev->rep[REP_PERIOD] = 33; + } + + input_dev->mscbit[0] = atkbd->softraw ? BIT_MASK(MSC_SCAN) : + BIT_MASK(MSC_RAW) | BIT_MASK(MSC_SCAN); + + if (atkbd->scroll) { + input_dev->evbit[0] |= BIT_MASK(EV_REL); + input_dev->relbit[0] = BIT_MASK(REL_WHEEL) | + BIT_MASK(REL_HWHEEL); + __set_bit(BTN_MIDDLE, input_dev->keybit); + } + + input_dev->keycode = atkbd->keycode; + input_dev->keycodesize = sizeof(unsigned short); + input_dev->keycodemax = ARRAY_SIZE(atkbd_set2_keycode); + + for (i = 0; i < 512; i++) + if (atkbd->keycode[i] && atkbd->keycode[i] < ATKBD_SPECIAL) + __set_bit(atkbd->keycode[i], input_dev->keybit); +} + +/* + * atkbd_connect() is called when the serio module finds an interface + * that isn't handled yet by an appropriate device driver. We check if + * there is an AT keyboard out there and if yes, we register ourselves + * to the input module. + */ + +static int atkbd_connect(struct serio *serio, struct serio_driver *drv) +{ + struct atkbd *atkbd; + struct input_dev *dev; + int err = -ENOMEM; + + atkbd = kzalloc(sizeof(struct atkbd), GFP_KERNEL); + dev = input_allocate_device(); + if (!atkbd || !dev) + goto fail1; + + atkbd->dev = dev; + ps2_init(&atkbd->ps2dev, serio); + INIT_DELAYED_WORK(&atkbd->event_work, atkbd_event_work); + mutex_init(&atkbd->event_mutex); + + switch (serio->id.type) { + + case SERIO_8042_XL: + atkbd->translated = 1; + case SERIO_8042: + if (serio->write) + atkbd->write = 1; + break; + } + + atkbd->softraw = atkbd_softraw; + atkbd->softrepeat = atkbd_softrepeat; + atkbd->scroll = atkbd_scroll; + + if (atkbd->softrepeat) + atkbd->softraw = 1; + + serio_set_drvdata(serio, atkbd); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + if (atkbd->write) { + + if (atkbd_probe(atkbd)) { + err = -ENODEV; + goto fail3; + } + + atkbd->set = atkbd_select_set(atkbd, atkbd_set, atkbd_extra); + atkbd_activate(atkbd); + + } else { + atkbd->set = 2; + atkbd->id = 0xab00; + } + + atkbd_set_keycode_table(atkbd); + atkbd_set_device_attrs(atkbd); + + err = sysfs_create_group(&serio->dev.kobj, &atkbd_attribute_group); + if (err) + goto fail3; + + atkbd_enable(atkbd); + + err = input_register_device(atkbd->dev); + if (err) + goto fail4; + + return 0; + + fail4: sysfs_remove_group(&serio->dev.kobj, &atkbd_attribute_group); + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(dev); + kfree(atkbd); + return err; +} + +/* + * atkbd_reconnect() tries to restore keyboard into a sane state and is + * most likely called on resume. + */ + +static int atkbd_reconnect(struct serio *serio) +{ + struct atkbd *atkbd = serio_get_drvdata(serio); + struct serio_driver *drv = serio->drv; + + if (!atkbd || !drv) { + printk(KERN_DEBUG "atkbd: reconnect request, but serio is disconnected, ignoring...\n"); + return -1; + } + + atkbd_disable(atkbd); + + if (atkbd->write) { + if (atkbd_probe(atkbd)) + return -1; + if (atkbd->set != atkbd_select_set(atkbd, atkbd->set, atkbd->extra)) + return -1; + + atkbd_activate(atkbd); + +/* + * Restore repeat rate and LEDs (that were reset by atkbd_activate) + * to pre-resume state + */ + if (!atkbd->softrepeat) + atkbd_set_repeat_rate(atkbd); + atkbd_set_leds(atkbd); + } + + atkbd_enable(atkbd); + + return 0; +} + +static struct serio_device_id atkbd_serio_ids[] = { + { + .type = SERIO_8042, + .proto = SERIO_ANY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_8042_XL, + .proto = SERIO_ANY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_RS232, + .proto = SERIO_PS2SER, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, atkbd_serio_ids); + +static struct serio_driver atkbd_drv = { + .driver = { + .name = "atkbd", + }, + .description = DRIVER_DESC, + .id_table = atkbd_serio_ids, + .interrupt = atkbd_interrupt, + .connect = atkbd_connect, + .reconnect = atkbd_reconnect, + .disconnect = atkbd_disconnect, + .cleanup = atkbd_cleanup, +}; + +static ssize_t atkbd_attr_show_helper(struct device *dev, char *buf, + ssize_t (*handler)(struct atkbd *, char *)) +{ + struct serio *serio = to_serio_port(dev); + int retval; + + retval = serio_pin_driver(serio); + if (retval) + return retval; + + if (serio->drv != &atkbd_drv) { + retval = -ENODEV; + goto out; + } + + retval = handler((struct atkbd *)serio_get_drvdata(serio), buf); + +out: + serio_unpin_driver(serio); + return retval; +} + +static ssize_t atkbd_attr_set_helper(struct device *dev, const char *buf, size_t count, + ssize_t (*handler)(struct atkbd *, const char *, size_t)) +{ + struct serio *serio = to_serio_port(dev); + struct atkbd *atkbd; + int retval; + + retval = serio_pin_driver(serio); + if (retval) + return retval; + + if (serio->drv != &atkbd_drv) { + retval = -ENODEV; + goto out; + } + + atkbd = serio_get_drvdata(serio); + atkbd_disable(atkbd); + retval = handler(atkbd, buf, count); + atkbd_enable(atkbd); + +out: + serio_unpin_driver(serio); + return retval; +} + +static ssize_t atkbd_show_extra(struct atkbd *atkbd, char *buf) +{ + return sprintf(buf, "%d\n", atkbd->extra ? 1 : 0); +} + +static ssize_t atkbd_set_extra(struct atkbd *atkbd, const char *buf, size_t count) +{ + struct input_dev *old_dev, *new_dev; + unsigned long value; + int err; + unsigned char old_extra, old_set; + + if (!atkbd->write) + return -EIO; + + if (strict_strtoul(buf, 10, &value) || value > 1) + return -EINVAL; + + if (atkbd->extra != value) { + /* + * Since device's properties will change we need to + * unregister old device. But allocate and register + * new one first to make sure we have it. + */ + old_dev = atkbd->dev; + old_extra = atkbd->extra; + old_set = atkbd->set; + + new_dev = input_allocate_device(); + if (!new_dev) + return -ENOMEM; + + atkbd->dev = new_dev; + atkbd->set = atkbd_select_set(atkbd, atkbd->set, value); + atkbd_activate(atkbd); + atkbd_set_keycode_table(atkbd); + atkbd_set_device_attrs(atkbd); + + err = input_register_device(atkbd->dev); + if (err) { + input_free_device(new_dev); + + atkbd->dev = old_dev; + atkbd->set = atkbd_select_set(atkbd, old_set, old_extra); + atkbd_set_keycode_table(atkbd); + atkbd_set_device_attrs(atkbd); + + return err; + } + input_unregister_device(old_dev); + + } + return count; +} + +static ssize_t atkbd_show_scroll(struct atkbd *atkbd, char *buf) +{ + return sprintf(buf, "%d\n", atkbd->scroll ? 1 : 0); +} + +static ssize_t atkbd_set_scroll(struct atkbd *atkbd, const char *buf, size_t count) +{ + struct input_dev *old_dev, *new_dev; + unsigned long value; + int err; + unsigned char old_scroll; + + if (strict_strtoul(buf, 10, &value) || value > 1) + return -EINVAL; + + if (atkbd->scroll != value) { + old_dev = atkbd->dev; + old_scroll = atkbd->scroll; + + new_dev = input_allocate_device(); + if (!new_dev) + return -ENOMEM; + + atkbd->dev = new_dev; + atkbd->scroll = value; + atkbd_set_keycode_table(atkbd); + atkbd_set_device_attrs(atkbd); + + err = input_register_device(atkbd->dev); + if (err) { + input_free_device(new_dev); + + atkbd->scroll = old_scroll; + atkbd->dev = old_dev; + atkbd_set_keycode_table(atkbd); + atkbd_set_device_attrs(atkbd); + + return err; + } + input_unregister_device(old_dev); + } + return count; +} + +static ssize_t atkbd_show_set(struct atkbd *atkbd, char *buf) +{ + return sprintf(buf, "%d\n", atkbd->set); +} + +static ssize_t atkbd_set_set(struct atkbd *atkbd, const char *buf, size_t count) +{ + struct input_dev *old_dev, *new_dev; + unsigned long value; + int err; + unsigned char old_set, old_extra; + + if (!atkbd->write) + return -EIO; + + if (strict_strtoul(buf, 10, &value) || (value != 2 && value != 3)) + return -EINVAL; + + if (atkbd->set != value) { + old_dev = atkbd->dev; + old_extra = atkbd->extra; + old_set = atkbd->set; + + new_dev = input_allocate_device(); + if (!new_dev) + return -ENOMEM; + + atkbd->dev = new_dev; + atkbd->set = atkbd_select_set(atkbd, value, atkbd->extra); + atkbd_activate(atkbd); + atkbd_set_keycode_table(atkbd); + atkbd_set_device_attrs(atkbd); + + err = input_register_device(atkbd->dev); + if (err) { + input_free_device(new_dev); + + atkbd->dev = old_dev; + atkbd->set = atkbd_select_set(atkbd, old_set, old_extra); + atkbd_set_keycode_table(atkbd); + atkbd_set_device_attrs(atkbd); + + return err; + } + input_unregister_device(old_dev); + } + return count; +} + +static ssize_t atkbd_show_softrepeat(struct atkbd *atkbd, char *buf) +{ + return sprintf(buf, "%d\n", atkbd->softrepeat ? 1 : 0); +} + +static ssize_t atkbd_set_softrepeat(struct atkbd *atkbd, const char *buf, size_t count) +{ + struct input_dev *old_dev, *new_dev; + unsigned long value; + int err; + unsigned char old_softrepeat, old_softraw; + + if (!atkbd->write) + return -EIO; + + if (strict_strtoul(buf, 10, &value) || value > 1) + return -EINVAL; + + if (atkbd->softrepeat != value) { + old_dev = atkbd->dev; + old_softrepeat = atkbd->softrepeat; + old_softraw = atkbd->softraw; + + new_dev = input_allocate_device(); + if (!new_dev) + return -ENOMEM; + + atkbd->dev = new_dev; + atkbd->softrepeat = value; + if (atkbd->softrepeat) + atkbd->softraw = 1; + atkbd_set_device_attrs(atkbd); + + err = input_register_device(atkbd->dev); + if (err) { + input_free_device(new_dev); + + atkbd->dev = old_dev; + atkbd->softrepeat = old_softrepeat; + atkbd->softraw = old_softraw; + atkbd_set_device_attrs(atkbd); + + return err; + } + input_unregister_device(old_dev); + } + return count; +} + + +static ssize_t atkbd_show_softraw(struct atkbd *atkbd, char *buf) +{ + return sprintf(buf, "%d\n", atkbd->softraw ? 1 : 0); +} + +static ssize_t atkbd_set_softraw(struct atkbd *atkbd, const char *buf, size_t count) +{ + struct input_dev *old_dev, *new_dev; + unsigned long value; + int err; + unsigned char old_softraw; + + if (strict_strtoul(buf, 10, &value) || value > 1) + return -EINVAL; + + if (atkbd->softraw != value) { + old_dev = atkbd->dev; + old_softraw = atkbd->softraw; + + new_dev = input_allocate_device(); + if (!new_dev) + return -ENOMEM; + + atkbd->dev = new_dev; + atkbd->softraw = value; + atkbd_set_device_attrs(atkbd); + + err = input_register_device(atkbd->dev); + if (err) { + input_free_device(new_dev); + + atkbd->dev = old_dev; + atkbd->softraw = old_softraw; + atkbd_set_device_attrs(atkbd); + + return err; + } + input_unregister_device(old_dev); + } + return count; +} + +static ssize_t atkbd_show_err_count(struct atkbd *atkbd, char *buf) +{ + return sprintf(buf, "%lu\n", atkbd->err_count); +} + +static int __init atkbd_setup_fixup(const struct dmi_system_id *id) +{ + atkbd_platform_fixup = id->driver_data; + return 0; +} + +static struct dmi_system_id atkbd_dmi_quirk_table[] __initdata = { + { + .ident = "Dell Laptop", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */ + }, + .callback = atkbd_setup_fixup, + .driver_data = atkbd_dell_laptop_keymap_fixup, + }, + { + .ident = "HP 2133", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP 2133"), + }, + .callback = atkbd_setup_fixup, + .driver_data = atkbd_hp_keymap_fixup, + }, + { + .ident = "Inventec Symphony", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "INVENTEC"), + DMI_MATCH(DMI_PRODUCT_NAME, "SYMPHONY 6.0/7.0"), + }, + .callback = atkbd_setup_fixup, + .driver_data = atkbd_inventec_keymap_fixup, + }, + { + .ident = "Samsung NC10", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "NC10"), + }, + .callback = atkbd_setup_fixup, + .driver_data = atkbd_samsung_keymap_fixup, + }, + { } +}; + +static int __init atkbd_init(void) +{ + dmi_check_system(atkbd_dmi_quirk_table); + + return serio_register_driver(&atkbd_drv); +} + +static void __exit atkbd_exit(void) +{ + serio_unregister_driver(&atkbd_drv); +} + +module_init(atkbd_init); +module_exit(atkbd_exit); diff --git a/drivers/input/keyboard/bf54x-keys.c b/drivers/input/keyboard/bf54x-keys.c new file mode 100644 index 0000000..1928401 --- /dev/null +++ b/drivers/input/keyboard/bf54x-keys.c @@ -0,0 +1,421 @@ +/* + * File: drivers/input/keyboard/bf54x-keys.c + * Based on: + * Author: Michael Hennerich <hennerich@blackfin.uclinux.org> + * + * Created: + * Description: keypad driver for Analog Devices Blackfin BF54x Processors + * + * + * Modified: + * Copyright 2007-2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * 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, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> + +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/sched.h> +#include <linux/pm.h> +#include <linux/sysctl.h> +#include <linux/proc_fs.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/input.h> + +#include <asm/portmux.h> +#include <mach/bf54x_keys.h> + +#define DRV_NAME "bf54x-keys" +#define TIME_SCALE 100 /* 100 ns */ +#define MAX_MULT (0xFF * TIME_SCALE) +#define MAX_RC 8 /* Max Row/Col */ + +static const u16 per_rows[] = { + P_KEY_ROW7, + P_KEY_ROW6, + P_KEY_ROW5, + P_KEY_ROW4, + P_KEY_ROW3, + P_KEY_ROW2, + P_KEY_ROW1, + P_KEY_ROW0, + 0 +}; + +static const u16 per_cols[] = { + P_KEY_COL7, + P_KEY_COL6, + P_KEY_COL5, + P_KEY_COL4, + P_KEY_COL3, + P_KEY_COL2, + P_KEY_COL1, + P_KEY_COL0, + 0 +}; + +struct bf54x_kpad { + struct input_dev *input; + int irq; + unsigned short lastkey; + unsigned short *keycode; + struct timer_list timer; + unsigned int keyup_test_jiffies; + unsigned short kpad_msel; + unsigned short kpad_prescale; + unsigned short kpad_ctl; +}; + +static inline int bfin_kpad_find_key(struct bf54x_kpad *bf54x_kpad, + struct input_dev *input, u16 keyident) +{ + u16 i; + + for (i = 0; i < input->keycodemax; i++) + if (bf54x_kpad->keycode[i + input->keycodemax] == keyident) + return bf54x_kpad->keycode[i]; + return -1; +} + +static inline void bfin_keycodecpy(unsigned short *keycode, + const unsigned int *pdata_kc, + unsigned short keymapsize) +{ + unsigned int i; + + for (i = 0; i < keymapsize; i++) { + keycode[i] = pdata_kc[i] & 0xffff; + keycode[i + keymapsize] = pdata_kc[i] >> 16; + } +} + +static inline u16 bfin_kpad_get_prescale(u32 timescale) +{ + u32 sclk = get_sclk(); + + return ((((sclk / 1000) * timescale) / 1024) - 1); +} + +static inline u16 bfin_kpad_get_keypressed(struct bf54x_kpad *bf54x_kpad) +{ + return (bfin_read_KPAD_STAT() & KPAD_PRESSED); +} + +static inline void bfin_kpad_clear_irq(void) +{ + bfin_write_KPAD_STAT(0xFFFF); + bfin_write_KPAD_ROWCOL(0xFFFF); +} + +static void bfin_kpad_timer(unsigned long data) +{ + struct platform_device *pdev = (struct platform_device *) data; + struct bf54x_kpad *bf54x_kpad = platform_get_drvdata(pdev); + + if (bfin_kpad_get_keypressed(bf54x_kpad)) { + /* Try again later */ + mod_timer(&bf54x_kpad->timer, + jiffies + bf54x_kpad->keyup_test_jiffies); + return; + } + + input_report_key(bf54x_kpad->input, bf54x_kpad->lastkey, 0); + input_sync(bf54x_kpad->input); + + /* Clear IRQ Status */ + + bfin_kpad_clear_irq(); + enable_irq(bf54x_kpad->irq); +} + +static irqreturn_t bfin_kpad_isr(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct bf54x_kpad *bf54x_kpad = platform_get_drvdata(pdev); + struct input_dev *input = bf54x_kpad->input; + int key; + u16 rowcol = bfin_read_KPAD_ROWCOL(); + + key = bfin_kpad_find_key(bf54x_kpad, input, rowcol); + + input_report_key(input, key, 1); + input_sync(input); + + if (bfin_kpad_get_keypressed(bf54x_kpad)) { + disable_irq(bf54x_kpad->irq); + bf54x_kpad->lastkey = key; + mod_timer(&bf54x_kpad->timer, + jiffies + bf54x_kpad->keyup_test_jiffies); + } else { + input_report_key(input, key, 0); + input_sync(input); + + bfin_kpad_clear_irq(); + } + + return IRQ_HANDLED; +} + +static int __devinit bfin_kpad_probe(struct platform_device *pdev) +{ + struct bf54x_kpad *bf54x_kpad; + struct bfin_kpad_platform_data *pdata = pdev->dev.platform_data; + struct input_dev *input; + int i, error; + + if (!pdata->rows || !pdata->cols || !pdata->keymap) { + printk(KERN_ERR DRV_NAME + ": No rows, cols or keymap from pdata\n"); + return -EINVAL; + } + + if (!pdata->keymapsize || + pdata->keymapsize > (pdata->rows * pdata->cols)) { + printk(KERN_ERR DRV_NAME ": Invalid keymapsize\n"); + return -EINVAL; + } + + bf54x_kpad = kzalloc(sizeof(struct bf54x_kpad), GFP_KERNEL); + if (!bf54x_kpad) + return -ENOMEM; + + platform_set_drvdata(pdev, bf54x_kpad); + + /* Allocate memory for keymap followed by private LUT */ + bf54x_kpad->keycode = kmalloc(pdata->keymapsize * + sizeof(unsigned short) * 2, GFP_KERNEL); + if (!bf54x_kpad->keycode) { + error = -ENOMEM; + goto out; + } + + if (!pdata->debounce_time || !pdata->debounce_time > MAX_MULT || + !pdata->coldrive_time || !pdata->coldrive_time > MAX_MULT) { + printk(KERN_ERR DRV_NAME + ": Invalid Debounce/Columdrive Time from pdata\n"); + bfin_write_KPAD_MSEL(0xFF0); /* Default MSEL */ + } else { + bfin_write_KPAD_MSEL( + ((pdata->debounce_time / TIME_SCALE) + & DBON_SCALE) | + (((pdata->coldrive_time / TIME_SCALE) << 8) + & COLDRV_SCALE)); + + } + + if (!pdata->keyup_test_interval) + bf54x_kpad->keyup_test_jiffies = msecs_to_jiffies(50); + else + bf54x_kpad->keyup_test_jiffies = + msecs_to_jiffies(pdata->keyup_test_interval); + + if (peripheral_request_list((u16 *)&per_rows[MAX_RC - pdata->rows], + DRV_NAME)) { + printk(KERN_ERR DRV_NAME + ": Requesting Peripherals failed\n"); + error = -EFAULT; + goto out0; + } + + if (peripheral_request_list((u16 *)&per_cols[MAX_RC - pdata->cols], + DRV_NAME)) { + printk(KERN_ERR DRV_NAME + ": Requesting Peripherals failed\n"); + error = -EFAULT; + goto out1; + } + + bf54x_kpad->irq = platform_get_irq(pdev, 0); + if (bf54x_kpad->irq < 0) { + error = -ENODEV; + goto out2; + } + + error = request_irq(bf54x_kpad->irq, bfin_kpad_isr, + IRQF_SAMPLE_RANDOM, DRV_NAME, pdev); + if (error) { + printk(KERN_ERR DRV_NAME + ": unable to claim irq %d; error %d\n", + bf54x_kpad->irq, error); + goto out2; + } + + input = input_allocate_device(); + if (!input) { + error = -ENOMEM; + goto out3; + } + + bf54x_kpad->input = input; + + input->name = pdev->name; + input->phys = "bf54x-keys/input0"; + input->dev.parent = &pdev->dev; + + input_set_drvdata(input, bf54x_kpad); + + input->id.bustype = BUS_HOST; + input->id.vendor = 0x0001; + input->id.product = 0x0001; + input->id.version = 0x0100; + + input->keycodesize = sizeof(unsigned short); + input->keycodemax = pdata->keymapsize; + input->keycode = bf54x_kpad->keycode; + + bfin_keycodecpy(bf54x_kpad->keycode, pdata->keymap, pdata->keymapsize); + + /* setup input device */ + __set_bit(EV_KEY, input->evbit); + + if (pdata->repeat) + __set_bit(EV_REP, input->evbit); + + for (i = 0; i < input->keycodemax; i++) + __set_bit(bf54x_kpad->keycode[i] & KEY_MAX, input->keybit); + __clear_bit(KEY_RESERVED, input->keybit); + + error = input_register_device(input); + if (error) { + printk(KERN_ERR DRV_NAME + ": Unable to register input device (%d)\n", error); + goto out4; + } + + /* Init Keypad Key Up/Release test timer */ + + setup_timer(&bf54x_kpad->timer, bfin_kpad_timer, (unsigned long) pdev); + + bfin_write_KPAD_PRESCALE(bfin_kpad_get_prescale(TIME_SCALE)); + + bfin_write_KPAD_CTL((((pdata->cols - 1) << 13) & KPAD_COLEN) | + (((pdata->rows - 1) << 10) & KPAD_ROWEN) | + (2 & KPAD_IRQMODE)); + + bfin_write_KPAD_CTL(bfin_read_KPAD_CTL() | KPAD_EN); + + device_init_wakeup(&pdev->dev, 1); + + printk(KERN_ERR DRV_NAME + ": Blackfin BF54x Keypad registered IRQ %d\n", bf54x_kpad->irq); + + return 0; + +out4: + input_free_device(input); +out3: + free_irq(bf54x_kpad->irq, pdev); +out2: + peripheral_free_list((u16 *)&per_cols[MAX_RC - pdata->cols]); +out1: + peripheral_free_list((u16 *)&per_rows[MAX_RC - pdata->rows]); +out0: + kfree(bf54x_kpad->keycode); +out: + kfree(bf54x_kpad); + platform_set_drvdata(pdev, NULL); + + return error; +} + +static int __devexit bfin_kpad_remove(struct platform_device *pdev) +{ + struct bfin_kpad_platform_data *pdata = pdev->dev.platform_data; + struct bf54x_kpad *bf54x_kpad = platform_get_drvdata(pdev); + + del_timer_sync(&bf54x_kpad->timer); + free_irq(bf54x_kpad->irq, pdev); + + input_unregister_device(bf54x_kpad->input); + + peripheral_free_list((u16 *)&per_rows[MAX_RC - pdata->rows]); + peripheral_free_list((u16 *)&per_cols[MAX_RC - pdata->cols]); + + kfree(bf54x_kpad->keycode); + kfree(bf54x_kpad); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int bfin_kpad_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct bf54x_kpad *bf54x_kpad = platform_get_drvdata(pdev); + + bf54x_kpad->kpad_msel = bfin_read_KPAD_MSEL(); + bf54x_kpad->kpad_prescale = bfin_read_KPAD_PRESCALE(); + bf54x_kpad->kpad_ctl = bfin_read_KPAD_CTL(); + + if (device_may_wakeup(&pdev->dev)) + enable_irq_wake(bf54x_kpad->irq); + + return 0; +} + +static int bfin_kpad_resume(struct platform_device *pdev) +{ + struct bf54x_kpad *bf54x_kpad = platform_get_drvdata(pdev); + + bfin_write_KPAD_MSEL(bf54x_kpad->kpad_msel); + bfin_write_KPAD_PRESCALE(bf54x_kpad->kpad_prescale); + bfin_write_KPAD_CTL(bf54x_kpad->kpad_ctl); + + if (device_may_wakeup(&pdev->dev)) + disable_irq_wake(bf54x_kpad->irq); + + return 0; +} +#else +# define bfin_kpad_suspend NULL +# define bfin_kpad_resume NULL +#endif + +struct platform_driver bfin_kpad_device_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = bfin_kpad_probe, + .remove = __devexit_p(bfin_kpad_remove), + .suspend = bfin_kpad_suspend, + .resume = bfin_kpad_resume, +}; + +static int __init bfin_kpad_init(void) +{ + return platform_driver_register(&bfin_kpad_device_driver); +} + +static void __exit bfin_kpad_exit(void) +{ + platform_driver_unregister(&bfin_kpad_device_driver); +} + +module_init(bfin_kpad_init); +module_exit(bfin_kpad_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Keypad driver for BF54x Processors"); +MODULE_ALIAS("platform:bf54x-keys"); diff --git a/drivers/input/keyboard/corgikbd.c b/drivers/input/keyboard/corgikbd.c new file mode 100644 index 0000000..c8ed065 --- /dev/null +++ b/drivers/input/keyboard/corgikbd.c @@ -0,0 +1,416 @@ +/* + * Keyboard driver for Sharp Corgi models (SL-C7xx) + * + * Copyright (c) 2004-2005 Richard Purdie + * + * Based on xtkbd.c/locomkbd.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <mach/corgi.h> +#include <mach/hardware.h> +#include <mach/pxa-regs.h> +#include <mach/pxa2xx-gpio.h> +#include <asm/hardware/scoop.h> + +#define KB_ROWS 8 +#define KB_COLS 12 +#define KB_ROWMASK(r) (1 << (r)) +#define SCANCODE(r,c) ( ((r)<<4) + (c) + 1 ) +/* zero code, 124 scancodes */ +#define NR_SCANCODES ( SCANCODE(KB_ROWS-1,KB_COLS-1) +1 +1 ) + +#define SCAN_INTERVAL (50) /* ms */ +#define HINGE_SCAN_INTERVAL (250) /* ms */ + +#define CORGI_KEY_CALENDER KEY_F1 +#define CORGI_KEY_ADDRESS KEY_F2 +#define CORGI_KEY_FN KEY_F3 +#define CORGI_KEY_CANCEL KEY_F4 +#define CORGI_KEY_OFF KEY_SUSPEND +#define CORGI_KEY_EXOK KEY_F5 +#define CORGI_KEY_EXCANCEL KEY_F6 +#define CORGI_KEY_EXJOGDOWN KEY_F7 +#define CORGI_KEY_EXJOGUP KEY_F8 +#define CORGI_KEY_JAP1 KEY_LEFTCTRL +#define CORGI_KEY_JAP2 KEY_LEFTALT +#define CORGI_KEY_MAIL KEY_F10 +#define CORGI_KEY_OK KEY_F11 +#define CORGI_KEY_MENU KEY_F12 + +static unsigned char corgikbd_keycode[NR_SCANCODES] = { + 0, /* 0 */ + 0, KEY_1, KEY_3, KEY_5, KEY_6, KEY_7, KEY_9, KEY_0, KEY_BACKSPACE, 0, 0, 0, 0, 0, 0, 0, /* 1-16 */ + 0, KEY_2, KEY_4, KEY_R, KEY_Y, KEY_8, KEY_I, KEY_O, KEY_P, 0, 0, 0, 0, 0, 0, 0, /* 17-32 */ + KEY_TAB, KEY_Q, KEY_E, KEY_T, KEY_G, KEY_U, KEY_J, KEY_K, 0, 0, 0, 0, 0, 0, 0, 0, /* 33-48 */ + CORGI_KEY_CALENDER, KEY_W, KEY_S, KEY_F, KEY_V, KEY_H, KEY_M, KEY_L, 0, KEY_RIGHTSHIFT, 0, 0, 0, 0, 0, 0, /* 49-64 */ + CORGI_KEY_ADDRESS, KEY_A, KEY_D, KEY_C, KEY_B, KEY_N, KEY_DOT, 0, KEY_ENTER, 0, KEY_LEFTSHIFT, 0, 0, 0, 0, 0, /* 65-80 */ + CORGI_KEY_MAIL, KEY_Z, KEY_X, KEY_MINUS, KEY_SPACE, KEY_COMMA, 0, KEY_UP, 0, 0, 0, CORGI_KEY_FN, 0, 0, 0, 0, /* 81-96 */ + KEY_SYSRQ, CORGI_KEY_JAP1, CORGI_KEY_JAP2, CORGI_KEY_CANCEL, CORGI_KEY_OK, CORGI_KEY_MENU, KEY_LEFT, KEY_DOWN, KEY_RIGHT, 0, 0, 0, 0, 0, 0, 0, /* 97-112 */ + CORGI_KEY_OFF, CORGI_KEY_EXOK, CORGI_KEY_EXCANCEL, CORGI_KEY_EXJOGDOWN, CORGI_KEY_EXJOGUP, 0, 0, 0, 0, 0, 0, 0, /* 113-124 */ +}; + + +struct corgikbd { + unsigned char keycode[ARRAY_SIZE(corgikbd_keycode)]; + struct input_dev *input; + + spinlock_t lock; + struct timer_list timer; + struct timer_list htimer; + + unsigned int suspended; + unsigned long suspend_jiffies; +}; + +#define KB_DISCHARGE_DELAY 10 +#define KB_ACTIVATE_DELAY 10 + +/* Helper functions for reading the keyboard matrix + * Note: We should really be using the generic gpio functions to alter + * GPDR but it requires a function call per GPIO bit which is + * excessive when we need to access 12 bits at once, multiple times. + * These functions must be called within local_irq_save()/local_irq_restore() + * or similar. + */ +static inline void corgikbd_discharge_all(void) +{ + /* STROBE All HiZ */ + GPCR2 = CORGI_GPIO_ALL_STROBE_BIT; + GPDR2 &= ~CORGI_GPIO_ALL_STROBE_BIT; +} + +static inline void corgikbd_activate_all(void) +{ + /* STROBE ALL -> High */ + GPSR2 = CORGI_GPIO_ALL_STROBE_BIT; + GPDR2 |= CORGI_GPIO_ALL_STROBE_BIT; + + udelay(KB_DISCHARGE_DELAY); + + /* Clear any interrupts we may have triggered when altering the GPIO lines */ + GEDR1 = CORGI_GPIO_HIGH_SENSE_BIT; + GEDR2 = CORGI_GPIO_LOW_SENSE_BIT; +} + +static inline void corgikbd_activate_col(int col) +{ + /* STROBE col -> High, not col -> HiZ */ + GPSR2 = CORGI_GPIO_STROBE_BIT(col); + GPDR2 = (GPDR2 & ~CORGI_GPIO_ALL_STROBE_BIT) | CORGI_GPIO_STROBE_BIT(col); +} + +static inline void corgikbd_reset_col(int col) +{ + /* STROBE col -> Low */ + GPCR2 = CORGI_GPIO_STROBE_BIT(col); + /* STROBE col -> out, not col -> HiZ */ + GPDR2 = (GPDR2 & ~CORGI_GPIO_ALL_STROBE_BIT) | CORGI_GPIO_STROBE_BIT(col); +} + +#define GET_ROWS_STATUS(c) (((GPLR1 & CORGI_GPIO_HIGH_SENSE_BIT) >> CORGI_GPIO_HIGH_SENSE_RSHIFT) | ((GPLR2 & CORGI_GPIO_LOW_SENSE_BIT) << CORGI_GPIO_LOW_SENSE_LSHIFT)) + +/* + * The corgi keyboard only generates interrupts when a key is pressed. + * When a key is pressed, we enable a timer which then scans the + * keyboard to detect when the key is released. + */ + +/* Scan the hardware keyboard and push any changes up through the input layer */ +static void corgikbd_scankeyboard(struct corgikbd *corgikbd_data) +{ + unsigned int row, col, rowd; + unsigned long flags; + unsigned int num_pressed; + + if (corgikbd_data->suspended) + return; + + spin_lock_irqsave(&corgikbd_data->lock, flags); + + num_pressed = 0; + for (col = 0; col < KB_COLS; col++) { + /* + * Discharge the output driver capacitatance + * in the keyboard matrix. (Yes it is significant..) + */ + + corgikbd_discharge_all(); + udelay(KB_DISCHARGE_DELAY); + + corgikbd_activate_col(col); + udelay(KB_ACTIVATE_DELAY); + + rowd = GET_ROWS_STATUS(col); + for (row = 0; row < KB_ROWS; row++) { + unsigned int scancode, pressed; + + scancode = SCANCODE(row, col); + pressed = rowd & KB_ROWMASK(row); + + input_report_key(corgikbd_data->input, corgikbd_data->keycode[scancode], pressed); + + if (pressed) + num_pressed++; + + if (pressed && (corgikbd_data->keycode[scancode] == CORGI_KEY_OFF) + && time_after(jiffies, corgikbd_data->suspend_jiffies + HZ)) { + input_event(corgikbd_data->input, EV_PWR, CORGI_KEY_OFF, 1); + corgikbd_data->suspend_jiffies=jiffies; + } + } + corgikbd_reset_col(col); + } + + corgikbd_activate_all(); + + input_sync(corgikbd_data->input); + + /* if any keys are pressed, enable the timer */ + if (num_pressed) + mod_timer(&corgikbd_data->timer, jiffies + msecs_to_jiffies(SCAN_INTERVAL)); + + spin_unlock_irqrestore(&corgikbd_data->lock, flags); +} + +/* + * corgi keyboard interrupt handler. + */ +static irqreturn_t corgikbd_interrupt(int irq, void *dev_id) +{ + struct corgikbd *corgikbd_data = dev_id; + + if (!timer_pending(&corgikbd_data->timer)) { + /** wait chattering delay **/ + udelay(20); + corgikbd_scankeyboard(corgikbd_data); + } + + return IRQ_HANDLED; +} + +/* + * corgi timer checking for released keys + */ +static void corgikbd_timer_callback(unsigned long data) +{ + struct corgikbd *corgikbd_data = (struct corgikbd *) data; + corgikbd_scankeyboard(corgikbd_data); +} + +/* + * The hinge switches generate no interrupt so they need to be + * monitored by a timer. + * + * We debounce the switches and pass them to the input system. + * + * gprr == 0x00 - Keyboard with Landscape Screen + * 0x08 - No Keyboard with Portrait Screen + * 0x0c - Keyboard and Screen Closed + */ + +#define READ_GPIO_BIT(x) (GPLR(x) & GPIO_bit(x)) +#define HINGE_STABLE_COUNT 2 +static int sharpsl_hinge_state; +static int hinge_count; + +static void corgikbd_hinge_timer(unsigned long data) +{ + struct corgikbd *corgikbd_data = (struct corgikbd *) data; + unsigned long gprr; + unsigned long flags; + + gprr = read_scoop_reg(&corgiscoop_device.dev, SCOOP_GPRR) & (CORGI_SCP_SWA | CORGI_SCP_SWB); + gprr |= (READ_GPIO_BIT(CORGI_GPIO_AK_INT) != 0); + if (gprr != sharpsl_hinge_state) { + hinge_count = 0; + sharpsl_hinge_state = gprr; + } else if (hinge_count < HINGE_STABLE_COUNT) { + hinge_count++; + if (hinge_count >= HINGE_STABLE_COUNT) { + spin_lock_irqsave(&corgikbd_data->lock, flags); + + input_report_switch(corgikbd_data->input, SW_LID, ((sharpsl_hinge_state & CORGI_SCP_SWA) != 0)); + input_report_switch(corgikbd_data->input, SW_TABLET_MODE, ((sharpsl_hinge_state & CORGI_SCP_SWB) != 0)); + input_report_switch(corgikbd_data->input, SW_HEADPHONE_INSERT, (READ_GPIO_BIT(CORGI_GPIO_AK_INT) != 0)); + input_sync(corgikbd_data->input); + + spin_unlock_irqrestore(&corgikbd_data->lock, flags); + } + } + mod_timer(&corgikbd_data->htimer, jiffies + msecs_to_jiffies(HINGE_SCAN_INTERVAL)); +} + +#ifdef CONFIG_PM +static int corgikbd_suspend(struct platform_device *dev, pm_message_t state) +{ + int i; + struct corgikbd *corgikbd = platform_get_drvdata(dev); + + corgikbd->suspended = 1; + /* strobe 0 is the power key so this can't be made an input for + powersaving therefore i = 1 */ + for (i = 1; i < CORGI_KEY_STROBE_NUM; i++) + pxa_gpio_mode(CORGI_GPIO_KEY_STROBE(i) | GPIO_IN); + + return 0; +} + +static int corgikbd_resume(struct platform_device *dev) +{ + int i; + struct corgikbd *corgikbd = platform_get_drvdata(dev); + + for (i = 1; i < CORGI_KEY_STROBE_NUM; i++) + pxa_gpio_mode(CORGI_GPIO_KEY_STROBE(i) | GPIO_OUT | GPIO_DFLT_HIGH); + + /* Upon resume, ignore the suspend key for a short while */ + corgikbd->suspend_jiffies=jiffies; + corgikbd->suspended = 0; + + return 0; +} +#else +#define corgikbd_suspend NULL +#define corgikbd_resume NULL +#endif + +static int __init corgikbd_probe(struct platform_device *pdev) +{ + struct corgikbd *corgikbd; + struct input_dev *input_dev; + int i, err = -ENOMEM; + + corgikbd = kzalloc(sizeof(struct corgikbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!corgikbd || !input_dev) + goto fail; + + platform_set_drvdata(pdev, corgikbd); + + corgikbd->input = input_dev; + spin_lock_init(&corgikbd->lock); + + /* Init Keyboard rescan timer */ + init_timer(&corgikbd->timer); + corgikbd->timer.function = corgikbd_timer_callback; + corgikbd->timer.data = (unsigned long) corgikbd; + + /* Init Hinge Timer */ + init_timer(&corgikbd->htimer); + corgikbd->htimer.function = corgikbd_hinge_timer; + corgikbd->htimer.data = (unsigned long) corgikbd; + + corgikbd->suspend_jiffies=jiffies; + + memcpy(corgikbd->keycode, corgikbd_keycode, sizeof(corgikbd->keycode)); + + input_dev->name = "Corgi Keyboard"; + input_dev->phys = "corgikbd/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &pdev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | + BIT_MASK(EV_PWR) | BIT_MASK(EV_SW); + input_dev->keycode = corgikbd->keycode; + input_dev->keycodesize = sizeof(unsigned char); + input_dev->keycodemax = ARRAY_SIZE(corgikbd_keycode); + + for (i = 0; i < ARRAY_SIZE(corgikbd_keycode); i++) + set_bit(corgikbd->keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + set_bit(SW_LID, input_dev->swbit); + set_bit(SW_TABLET_MODE, input_dev->swbit); + set_bit(SW_HEADPHONE_INSERT, input_dev->swbit); + + err = input_register_device(corgikbd->input); + if (err) + goto fail; + + mod_timer(&corgikbd->htimer, jiffies + msecs_to_jiffies(HINGE_SCAN_INTERVAL)); + + /* Setup sense interrupts - RisingEdge Detect, sense lines as inputs */ + for (i = 0; i < CORGI_KEY_SENSE_NUM; i++) { + pxa_gpio_mode(CORGI_GPIO_KEY_SENSE(i) | GPIO_IN); + if (request_irq(CORGI_IRQ_GPIO_KEY_SENSE(i), corgikbd_interrupt, + IRQF_DISABLED | IRQF_TRIGGER_RISING, + "corgikbd", corgikbd)) + printk(KERN_WARNING "corgikbd: Can't get IRQ: %d!\n", i); + } + + /* Set Strobe lines as outputs - set high */ + for (i = 0; i < CORGI_KEY_STROBE_NUM; i++) + pxa_gpio_mode(CORGI_GPIO_KEY_STROBE(i) | GPIO_OUT | GPIO_DFLT_HIGH); + + /* Setup the headphone jack as an input */ + pxa_gpio_mode(CORGI_GPIO_AK_INT | GPIO_IN); + + return 0; + + fail: input_free_device(input_dev); + kfree(corgikbd); + return err; +} + +static int corgikbd_remove(struct platform_device *pdev) +{ + int i; + struct corgikbd *corgikbd = platform_get_drvdata(pdev); + + for (i = 0; i < CORGI_KEY_SENSE_NUM; i++) + free_irq(CORGI_IRQ_GPIO_KEY_SENSE(i), corgikbd); + + del_timer_sync(&corgikbd->htimer); + del_timer_sync(&corgikbd->timer); + + input_unregister_device(corgikbd->input); + + kfree(corgikbd); + + return 0; +} + +static struct platform_driver corgikbd_driver = { + .probe = corgikbd_probe, + .remove = corgikbd_remove, + .suspend = corgikbd_suspend, + .resume = corgikbd_resume, + .driver = { + .name = "corgi-keyboard", + .owner = THIS_MODULE, + }, +}; + +static int __devinit corgikbd_init(void) +{ + return platform_driver_register(&corgikbd_driver); +} + +static void __exit corgikbd_exit(void) +{ + platform_driver_unregister(&corgikbd_driver); +} + +module_init(corgikbd_init); +module_exit(corgikbd_exit); + +MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>"); +MODULE_DESCRIPTION("Corgi Keyboard Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:corgi-keyboard"); diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c new file mode 100644 index 0000000..05f3f43 --- /dev/null +++ b/drivers/input/keyboard/gpio_keys.c @@ -0,0 +1,276 @@ +/* + * Driver for keys on GPIO lines capable of generating interrupts. + * + * Copyright 2005 Phil Blundell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> + +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/sched.h> +#include <linux/pm.h> +#include <linux/sysctl.h> +#include <linux/proc_fs.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/gpio_keys.h> + +#include <asm/gpio.h> + +struct gpio_button_data { + struct gpio_keys_button *button; + struct input_dev *input; + struct timer_list timer; +}; + +struct gpio_keys_drvdata { + struct input_dev *input; + struct gpio_button_data data[0]; +}; + +static void gpio_keys_report_event(struct gpio_button_data *bdata) +{ + struct gpio_keys_button *button = bdata->button; + struct input_dev *input = bdata->input; + unsigned int type = button->type ?: EV_KEY; + int state = (gpio_get_value(button->gpio) ? 1 : 0) ^ button->active_low; + + input_event(input, type, button->code, !!state); + input_sync(input); +} + +static void gpio_check_button(unsigned long _data) +{ + struct gpio_button_data *data = (struct gpio_button_data *)_data; + + gpio_keys_report_event(data); +} + +static irqreturn_t gpio_keys_isr(int irq, void *dev_id) +{ + struct gpio_button_data *bdata = dev_id; + struct gpio_keys_button *button = bdata->button; + + BUG_ON(irq != gpio_to_irq(button->gpio)); + + if (button->debounce_interval) + mod_timer(&bdata->timer, + jiffies + msecs_to_jiffies(button->debounce_interval)); + else + gpio_keys_report_event(bdata); + + return IRQ_HANDLED; +} + +static int __devinit gpio_keys_probe(struct platform_device *pdev) +{ + struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; + struct gpio_keys_drvdata *ddata; + struct input_dev *input; + int i, error; + int wakeup = 0; + + ddata = kzalloc(sizeof(struct gpio_keys_drvdata) + + pdata->nbuttons * sizeof(struct gpio_button_data), + GFP_KERNEL); + input = input_allocate_device(); + if (!ddata || !input) { + error = -ENOMEM; + goto fail1; + } + + platform_set_drvdata(pdev, ddata); + + input->name = pdev->name; + input->phys = "gpio-keys/input0"; + input->dev.parent = &pdev->dev; + + input->id.bustype = BUS_HOST; + input->id.vendor = 0x0001; + input->id.product = 0x0001; + input->id.version = 0x0100; + + ddata->input = input; + + for (i = 0; i < pdata->nbuttons; i++) { + struct gpio_keys_button *button = &pdata->buttons[i]; + struct gpio_button_data *bdata = &ddata->data[i]; + int irq; + unsigned int type = button->type ?: EV_KEY; + + bdata->input = input; + bdata->button = button; + setup_timer(&bdata->timer, + gpio_check_button, (unsigned long)bdata); + + error = gpio_request(button->gpio, button->desc ?: "gpio_keys"); + if (error < 0) { + pr_err("gpio-keys: failed to request GPIO %d," + " error %d\n", button->gpio, error); + goto fail2; + } + + error = gpio_direction_input(button->gpio); + if (error < 0) { + pr_err("gpio-keys: failed to configure input" + " direction for GPIO %d, error %d\n", + button->gpio, error); + gpio_free(button->gpio); + goto fail2; + } + + irq = gpio_to_irq(button->gpio); + if (irq < 0) { + error = irq; + pr_err("gpio-keys: Unable to get irq number" + " for GPIO %d, error %d\n", + button->gpio, error); + gpio_free(button->gpio); + goto fail2; + } + + error = request_irq(irq, gpio_keys_isr, + IRQF_SAMPLE_RANDOM | IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + button->desc ? button->desc : "gpio_keys", + bdata); + if (error) { + pr_err("gpio-keys: Unable to claim irq %d; error %d\n", + irq, error); + gpio_free(button->gpio); + goto fail2; + } + + if (button->wakeup) + wakeup = 1; + + input_set_capability(input, type, button->code); + } + + error = input_register_device(input); + if (error) { + pr_err("gpio-keys: Unable to register input device, " + "error: %d\n", error); + goto fail2; + } + + device_init_wakeup(&pdev->dev, wakeup); + + return 0; + + fail2: + while (--i >= 0) { + free_irq(gpio_to_irq(pdata->buttons[i].gpio), &ddata->data[i]); + if (pdata->buttons[i].debounce_interval) + del_timer_sync(&ddata->data[i].timer); + gpio_free(pdata->buttons[i].gpio); + } + + platform_set_drvdata(pdev, NULL); + fail1: + input_free_device(input); + kfree(ddata); + + return error; +} + +static int __devexit gpio_keys_remove(struct platform_device *pdev) +{ + struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; + struct gpio_keys_drvdata *ddata = platform_get_drvdata(pdev); + struct input_dev *input = ddata->input; + int i; + + device_init_wakeup(&pdev->dev, 0); + + for (i = 0; i < pdata->nbuttons; i++) { + int irq = gpio_to_irq(pdata->buttons[i].gpio); + free_irq(irq, &ddata->data[i]); + if (pdata->buttons[i].debounce_interval) + del_timer_sync(&ddata->data[i].timer); + gpio_free(pdata->buttons[i].gpio); + } + + input_unregister_device(input); + + return 0; +} + + +#ifdef CONFIG_PM +static int gpio_keys_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; + int i; + + if (device_may_wakeup(&pdev->dev)) { + for (i = 0; i < pdata->nbuttons; i++) { + struct gpio_keys_button *button = &pdata->buttons[i]; + if (button->wakeup) { + int irq = gpio_to_irq(button->gpio); + enable_irq_wake(irq); + } + } + } + + return 0; +} + +static int gpio_keys_resume(struct platform_device *pdev) +{ + struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; + int i; + + if (device_may_wakeup(&pdev->dev)) { + for (i = 0; i < pdata->nbuttons; i++) { + struct gpio_keys_button *button = &pdata->buttons[i]; + if (button->wakeup) { + int irq = gpio_to_irq(button->gpio); + disable_irq_wake(irq); + } + } + } + + return 0; +} +#else +#define gpio_keys_suspend NULL +#define gpio_keys_resume NULL +#endif + +static struct platform_driver gpio_keys_device_driver = { + .probe = gpio_keys_probe, + .remove = __devexit_p(gpio_keys_remove), + .suspend = gpio_keys_suspend, + .resume = gpio_keys_resume, + .driver = { + .name = "gpio-keys", + .owner = THIS_MODULE, + } +}; + +static int __init gpio_keys_init(void) +{ + return platform_driver_register(&gpio_keys_device_driver); +} + +static void __exit gpio_keys_exit(void) +{ + platform_driver_unregister(&gpio_keys_device_driver); +} + +module_init(gpio_keys_init); +module_exit(gpio_keys_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Phil Blundell <pb@handhelds.org>"); +MODULE_DESCRIPTION("Keyboard driver for CPU GPIOs"); +MODULE_ALIAS("platform:gpio-keys"); diff --git a/drivers/input/keyboard/hil_kbd.c b/drivers/input/keyboard/hil_kbd.c new file mode 100644 index 0000000..71c1971 --- /dev/null +++ b/drivers/input/keyboard/hil_kbd.c @@ -0,0 +1,402 @@ +/* + * Generic linux-input device driver for keyboard devices + * + * Copyright (c) 2001 Brian S. Julin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL"). + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * + * References: + * HP-HIL Technical Reference Manual. Hewlett Packard Product No. 45918A + * + */ + +#include <linux/hil.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/semaphore.h> +#include <linux/slab.h> +#include <linux/pci_ids.h> + +#define PREFIX "HIL KEYB: " +#define HIL_GENERIC_NAME "HIL keyboard" + +MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>"); +MODULE_DESCRIPTION(HIL_GENERIC_NAME " driver"); +MODULE_LICENSE("Dual BSD/GPL"); + +#define HIL_KBD_MAX_LENGTH 16 + +#define HIL_KBD_SET1_UPBIT 0x01 +#define HIL_KBD_SET1_SHIFT 1 +static unsigned int hil_kbd_set1[HIL_KEYCODES_SET1_TBLSIZE] __read_mostly = + { HIL_KEYCODES_SET1 }; + +#define HIL_KBD_SET2_UPBIT 0x01 +#define HIL_KBD_SET2_SHIFT 1 +/* Set2 is user defined */ + +#define HIL_KBD_SET3_UPBIT 0x80 +#define HIL_KBD_SET3_SHIFT 0 +static unsigned int hil_kbd_set3[HIL_KEYCODES_SET3_TBLSIZE] __read_mostly = + { HIL_KEYCODES_SET3 }; + +static const char hil_language[][16] = { HIL_LOCALE_MAP }; + +struct hil_kbd { + struct input_dev *dev; + struct serio *serio; + + /* Input buffer and index for packets from HIL bus. */ + hil_packet data[HIL_KBD_MAX_LENGTH]; + int idx4; /* four counts per packet */ + + /* Raw device info records from HIL bus, see hil.h for fields. */ + char idd[HIL_KBD_MAX_LENGTH]; /* DID byte and IDD record */ + char rsc[HIL_KBD_MAX_LENGTH]; /* RSC record */ + char exd[HIL_KBD_MAX_LENGTH]; /* EXD record */ + char rnm[HIL_KBD_MAX_LENGTH + 1]; /* RNM record + NULL term. */ + + /* Something to sleep around with. */ + struct semaphore sem; +}; + +/* Process a complete packet after transfer from the HIL */ +static void hil_kbd_process_record(struct hil_kbd *kbd) +{ + struct input_dev *dev = kbd->dev; + hil_packet *data = kbd->data; + hil_packet p; + int idx, i, cnt; + + idx = kbd->idx4/4; + p = data[idx - 1]; + + if ((p & ~HIL_CMDCT_POL) == + (HIL_ERR_INT | HIL_PKT_CMD | HIL_CMD_POL)) + goto report; + if ((p & ~HIL_CMDCT_RPL) == + (HIL_ERR_INT | HIL_PKT_CMD | HIL_CMD_RPL)) + goto report; + + /* Not a poll response. See if we are loading config records. */ + switch (p & HIL_PKT_DATA_MASK) { + case HIL_CMD_IDD: + for (i = 0; i < idx; i++) + kbd->idd[i] = kbd->data[i] & HIL_PKT_DATA_MASK; + for (; i < HIL_KBD_MAX_LENGTH; i++) + kbd->idd[i] = 0; + break; + + case HIL_CMD_RSC: + for (i = 0; i < idx; i++) + kbd->rsc[i] = kbd->data[i] & HIL_PKT_DATA_MASK; + for (; i < HIL_KBD_MAX_LENGTH; i++) + kbd->rsc[i] = 0; + break; + + case HIL_CMD_EXD: + for (i = 0; i < idx; i++) + kbd->exd[i] = kbd->data[i] & HIL_PKT_DATA_MASK; + for (; i < HIL_KBD_MAX_LENGTH; i++) + kbd->exd[i] = 0; + break; + + case HIL_CMD_RNM: + for (i = 0; i < idx; i++) + kbd->rnm[i] = kbd->data[i] & HIL_PKT_DATA_MASK; + for (; i < HIL_KBD_MAX_LENGTH + 1; i++) + kbd->rnm[i] = '\0'; + break; + + default: + /* These occur when device isn't present */ + if (p == (HIL_ERR_INT | HIL_PKT_CMD)) + break; + /* Anything else we'd like to know about. */ + printk(KERN_WARNING PREFIX "Device sent unknown record %x\n", p); + break; + } + goto out; + + report: + cnt = 1; + switch (kbd->data[0] & HIL_POL_CHARTYPE_MASK) { + case HIL_POL_CHARTYPE_NONE: + break; + + case HIL_POL_CHARTYPE_ASCII: + while (cnt < idx - 1) + input_report_key(dev, kbd->data[cnt++] & 0x7f, 1); + break; + + case HIL_POL_CHARTYPE_RSVD1: + case HIL_POL_CHARTYPE_RSVD2: + case HIL_POL_CHARTYPE_BINARY: + while (cnt < idx - 1) + input_report_key(dev, kbd->data[cnt++], 1); + break; + + case HIL_POL_CHARTYPE_SET1: + while (cnt < idx - 1) { + unsigned int key; + int up; + key = kbd->data[cnt++]; + up = key & HIL_KBD_SET1_UPBIT; + key &= (~HIL_KBD_SET1_UPBIT & 0xff); + key = hil_kbd_set1[key >> HIL_KBD_SET1_SHIFT]; + if (key != KEY_RESERVED) + input_report_key(dev, key, !up); + } + break; + + case HIL_POL_CHARTYPE_SET2: + while (cnt < idx - 1) { + unsigned int key; + int up; + key = kbd->data[cnt++]; + up = key & HIL_KBD_SET2_UPBIT; + key &= (~HIL_KBD_SET1_UPBIT & 0xff); + key = key >> HIL_KBD_SET2_SHIFT; + if (key != KEY_RESERVED) + input_report_key(dev, key, !up); + } + break; + + case HIL_POL_CHARTYPE_SET3: + while (cnt < idx - 1) { + unsigned int key; + int up; + key = kbd->data[cnt++]; + up = key & HIL_KBD_SET3_UPBIT; + key &= (~HIL_KBD_SET1_UPBIT & 0xff); + key = hil_kbd_set3[key >> HIL_KBD_SET3_SHIFT]; + if (key != KEY_RESERVED) + input_report_key(dev, key, !up); + } + break; + } + out: + kbd->idx4 = 0; + up(&kbd->sem); +} + +static void hil_kbd_process_err(struct hil_kbd *kbd) +{ + printk(KERN_WARNING PREFIX "errored HIL packet\n"); + kbd->idx4 = 0; + up(&kbd->sem); +} + +static irqreturn_t hil_kbd_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct hil_kbd *kbd; + hil_packet packet; + int idx; + + kbd = serio_get_drvdata(serio); + BUG_ON(kbd == NULL); + + if (kbd->idx4 >= (HIL_KBD_MAX_LENGTH * sizeof(hil_packet))) { + hil_kbd_process_err(kbd); + return IRQ_HANDLED; + } + idx = kbd->idx4/4; + if (!(kbd->idx4 % 4)) + kbd->data[idx] = 0; + packet = kbd->data[idx]; + packet |= ((hil_packet)data) << ((3 - (kbd->idx4 % 4)) * 8); + kbd->data[idx] = packet; + + /* Records of N 4-byte hil_packets must terminate with a command. */ + if ((++(kbd->idx4)) % 4) + return IRQ_HANDLED; + if ((packet & 0xffff0000) != HIL_ERR_INT) { + hil_kbd_process_err(kbd); + return IRQ_HANDLED; + } + if (packet & HIL_PKT_CMD) + hil_kbd_process_record(kbd); + return IRQ_HANDLED; +} + +static void hil_kbd_disconnect(struct serio *serio) +{ + struct hil_kbd *kbd; + + kbd = serio_get_drvdata(serio); + BUG_ON(kbd == NULL); + + serio_close(serio); + input_unregister_device(kbd->dev); + kfree(kbd); +} + +static int hil_kbd_connect(struct serio *serio, struct serio_driver *drv) +{ + struct hil_kbd *kbd; + uint8_t did, *idd; + int i; + + kbd = kzalloc(sizeof(*kbd), GFP_KERNEL); + if (!kbd) + return -ENOMEM; + + kbd->dev = input_allocate_device(); + if (!kbd->dev) + goto bail0; + + if (serio_open(serio, drv)) + goto bail1; + + serio_set_drvdata(serio, kbd); + kbd->serio = serio; + + init_MUTEX_LOCKED(&kbd->sem); + + /* Get device info. MLC driver supplies devid/status/etc. */ + serio->write(serio, 0); + serio->write(serio, 0); + serio->write(serio, HIL_PKT_CMD >> 8); + serio->write(serio, HIL_CMD_IDD); + down(&kbd->sem); + + serio->write(serio, 0); + serio->write(serio, 0); + serio->write(serio, HIL_PKT_CMD >> 8); + serio->write(serio, HIL_CMD_RSC); + down(&kbd->sem); + + serio->write(serio, 0); + serio->write(serio, 0); + serio->write(serio, HIL_PKT_CMD >> 8); + serio->write(serio, HIL_CMD_RNM); + down(&kbd->sem); + + serio->write(serio, 0); + serio->write(serio, 0); + serio->write(serio, HIL_PKT_CMD >> 8); + serio->write(serio, HIL_CMD_EXD); + down(&kbd->sem); + + up(&kbd->sem); + + did = kbd->idd[0]; + idd = kbd->idd + 1; + switch (did & HIL_IDD_DID_TYPE_MASK) { + case HIL_IDD_DID_TYPE_KB_INTEGRAL: + case HIL_IDD_DID_TYPE_KB_ITF: + case HIL_IDD_DID_TYPE_KB_RSVD: + case HIL_IDD_DID_TYPE_CHAR: + printk(KERN_INFO PREFIX "HIL keyboard found (did = 0x%02x, lang = %s)\n", + did, hil_language[did & HIL_IDD_DID_TYPE_KB_LANG_MASK]); + break; + default: + goto bail2; + } + + if (HIL_IDD_NUM_BUTTONS(idd) || HIL_IDD_NUM_AXES_PER_SET(*idd)) { + printk(KERN_INFO PREFIX "keyboards only, no combo devices supported.\n"); + goto bail2; + } + + kbd->dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + kbd->dev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) | + BIT_MASK(LED_SCROLLL); + kbd->dev->keycodemax = HIL_KEYCODES_SET1_TBLSIZE; + kbd->dev->keycodesize = sizeof(hil_kbd_set1[0]); + kbd->dev->keycode = hil_kbd_set1; + kbd->dev->name = strlen(kbd->rnm) ? kbd->rnm : HIL_GENERIC_NAME; + kbd->dev->phys = "hpkbd/input0"; /* XXX */ + + kbd->dev->id.bustype = BUS_HIL; + kbd->dev->id.vendor = PCI_VENDOR_ID_HP; + kbd->dev->id.product = 0x0001; /* TODO: get from kbd->rsc */ + kbd->dev->id.version = 0x0100; /* TODO: get from kbd->rsc */ + kbd->dev->dev.parent = &serio->dev; + + for (i = 0; i < 128; i++) { + set_bit(hil_kbd_set1[i], kbd->dev->keybit); + set_bit(hil_kbd_set3[i], kbd->dev->keybit); + } + clear_bit(0, kbd->dev->keybit); + + input_register_device(kbd->dev); + printk(KERN_INFO "input: %s, ID: %d\n", + kbd->dev->name, did); + + serio->write(serio, 0); + serio->write(serio, 0); + serio->write(serio, HIL_PKT_CMD >> 8); + serio->write(serio, HIL_CMD_EK1); /* Enable Keyswitch Autorepeat 1 */ + down(&kbd->sem); + up(&kbd->sem); + + return 0; + bail2: + serio_close(serio); + serio_set_drvdata(serio, NULL); + bail1: + input_free_device(kbd->dev); + bail0: + kfree(kbd); + return -EIO; +} + +static struct serio_device_id hil_kbd_ids[] = { + { + .type = SERIO_HIL_MLC, + .proto = SERIO_HIL, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +static struct serio_driver hil_kbd_serio_drv = { + .driver = { + .name = "hil_kbd", + }, + .description = "HP HIL keyboard driver", + .id_table = hil_kbd_ids, + .connect = hil_kbd_connect, + .disconnect = hil_kbd_disconnect, + .interrupt = hil_kbd_interrupt +}; + +static int __init hil_kbd_init(void) +{ + return serio_register_driver(&hil_kbd_serio_drv); +} + +static void __exit hil_kbd_exit(void) +{ + serio_unregister_driver(&hil_kbd_serio_drv); +} + +module_init(hil_kbd_init); +module_exit(hil_kbd_exit); diff --git a/drivers/input/keyboard/hilkbd.c b/drivers/input/keyboard/hilkbd.c new file mode 100644 index 0000000..aacf71f --- /dev/null +++ b/drivers/input/keyboard/hilkbd.c @@ -0,0 +1,377 @@ +/* + * linux/drivers/hil/hilkbd.c + * + * Copyright (C) 1998 Philip Blundell <philb@gnu.org> + * Copyright (C) 1999 Matthew Wilcox <willy@bofh.ai> + * Copyright (C) 1999-2007 Helge Deller <deller@gmx.de> + * + * Very basic HP Human Interface Loop (HIL) driver. + * This driver handles the keyboard on HP300 (m68k) and on some + * HP700 (parisc) series machines. + * + * + * This file is subject to the terms and conditions of the GNU General Public + * License version 2. See the file COPYING in the main directory of this + * archive for more details. + */ + +#include <linux/pci_ids.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/hil.h> +#include <linux/io.h> +#include <linux/spinlock.h> +#include <asm/irq.h> +#ifdef CONFIG_HP300 +#include <asm/hwtest.h> +#endif + + +MODULE_AUTHOR("Philip Blundell, Matthew Wilcox, Helge Deller"); +MODULE_DESCRIPTION("HIL keyboard driver (basic functionality)"); +MODULE_LICENSE("GPL v2"); + + +#if defined(CONFIG_PARISC) + + #include <asm/io.h> + #include <asm/hardware.h> + #include <asm/parisc-device.h> + static unsigned long hil_base; /* HPA for the HIL device */ + static unsigned int hil_irq; + #define HILBASE hil_base /* HPPA (parisc) port address */ + #define HIL_DATA 0x800 + #define HIL_CMD 0x801 + #define HIL_IRQ hil_irq + #define hil_readb(p) gsc_readb(p) + #define hil_writeb(v,p) gsc_writeb((v),(p)) + +#elif defined(CONFIG_HP300) + + #define HILBASE 0xf0428000UL /* HP300 (m68k) port address */ + #define HIL_DATA 0x1 + #define HIL_CMD 0x3 + #define HIL_IRQ 2 + #define hil_readb(p) readb(p) + #define hil_writeb(v,p) writeb((v),(p)) + +#else +#error "HIL is not supported on this platform" +#endif + + + +/* HIL helper functions */ + +#define hil_busy() (hil_readb(HILBASE + HIL_CMD) & HIL_BUSY) +#define hil_data_available() (hil_readb(HILBASE + HIL_CMD) & HIL_DATA_RDY) +#define hil_status() (hil_readb(HILBASE + HIL_CMD)) +#define hil_command(x) do { hil_writeb((x), HILBASE + HIL_CMD); } while (0) +#define hil_read_data() (hil_readb(HILBASE + HIL_DATA)) +#define hil_write_data(x) do { hil_writeb((x), HILBASE + HIL_DATA); } while (0) + +/* HIL constants */ + +#define HIL_BUSY 0x02 +#define HIL_DATA_RDY 0x01 + +#define HIL_SETARD 0xA0 /* set auto-repeat delay */ +#define HIL_SETARR 0xA2 /* set auto-repeat rate */ +#define HIL_SETTONE 0xA3 /* set tone generator */ +#define HIL_CNMT 0xB2 /* clear nmi */ +#define HIL_INTON 0x5C /* Turn on interrupts. */ +#define HIL_INTOFF 0x5D /* Turn off interrupts. */ + +#define HIL_READKBDSADR 0xF9 +#define HIL_WRITEKBDSADR 0xE9 + +static unsigned int hphilkeyb_keycode[HIL_KEYCODES_SET1_TBLSIZE] __read_mostly = + { HIL_KEYCODES_SET1 }; + +/* HIL structure */ +static struct { + struct input_dev *dev; + + unsigned int curdev; + + unsigned char s; + unsigned char c; + int valid; + + unsigned char data[16]; + unsigned int ptr; + spinlock_t lock; + + void *dev_id; /* native bus device */ +} hil_dev; + + +static void poll_finished(void) +{ + int down; + int key; + unsigned char scode; + + switch (hil_dev.data[0]) { + case 0x40: + down = (hil_dev.data[1] & 1) == 0; + scode = hil_dev.data[1] >> 1; + key = hphilkeyb_keycode[scode]; + input_report_key(hil_dev.dev, key, down); + break; + } + hil_dev.curdev = 0; +} + + +static inline void handle_status(unsigned char s, unsigned char c) +{ + if (c & 0x8) { + /* End of block */ + if (c & 0x10) + poll_finished(); + } else { + if (c & 0x10) { + if (hil_dev.curdev) + poll_finished(); /* just in case */ + hil_dev.curdev = c & 7; + hil_dev.ptr = 0; + } + } +} + + +static inline void handle_data(unsigned char s, unsigned char c) +{ + if (hil_dev.curdev) { + hil_dev.data[hil_dev.ptr++] = c; + hil_dev.ptr &= 15; + } +} + + +/* handle HIL interrupts */ +static irqreturn_t hil_interrupt(int irq, void *handle) +{ + unsigned char s, c; + + s = hil_status(); + c = hil_read_data(); + + switch (s >> 4) { + case 0x5: + handle_status(s, c); + break; + case 0x6: + handle_data(s, c); + break; + case 0x4: + hil_dev.s = s; + hil_dev.c = c; + mb(); + hil_dev.valid = 1; + break; + } + return IRQ_HANDLED; +} + + +/* send a command to the HIL */ +static void hil_do(unsigned char cmd, unsigned char *data, unsigned int len) +{ + unsigned long flags; + + spin_lock_irqsave(&hil_dev.lock, flags); + while (hil_busy()) + /* wait */; + hil_command(cmd); + while (len--) { + while (hil_busy()) + /* wait */; + hil_write_data(*(data++)); + } + spin_unlock_irqrestore(&hil_dev.lock, flags); +} + + +/* initialise HIL */ +static int __init +hil_keyb_init(void) +{ + unsigned char c; + unsigned int i, kbid; + wait_queue_head_t hil_wait; + int err; + + if (hil_dev.dev) { + return -ENODEV; /* already initialized */ + } + + spin_lock_init(&hil_dev.lock); + hil_dev.dev = input_allocate_device(); + if (!hil_dev.dev) + return -ENOMEM; + +#if defined(CONFIG_HP300) + if (!MACH_IS_HP300) { + err = -ENODEV; + goto err1; + } + if (!hwreg_present((void *)(HILBASE + HIL_DATA))) { + printk(KERN_ERR "HIL: hardware register was not found\n"); + err = -ENODEV; + goto err1; + } + if (!request_region(HILBASE + HIL_DATA, 2, "hil")) { + printk(KERN_ERR "HIL: IOPORT region already used\n"); + err = -EIO; + goto err1; + } +#endif + + err = request_irq(HIL_IRQ, hil_interrupt, 0, "hil", hil_dev.dev_id); + if (err) { + printk(KERN_ERR "HIL: Can't get IRQ\n"); + goto err2; + } + + /* Turn on interrupts */ + hil_do(HIL_INTON, NULL, 0); + + /* Look for keyboards */ + hil_dev.valid = 0; /* clear any pending data */ + hil_do(HIL_READKBDSADR, NULL, 0); + + init_waitqueue_head(&hil_wait); + wait_event_interruptible_timeout(hil_wait, hil_dev.valid, 3*HZ); + if (!hil_dev.valid) { + printk(KERN_WARNING "HIL: timed out, assuming no keyboard present\n"); + } + + c = hil_dev.c; + hil_dev.valid = 0; + if (c == 0) { + kbid = -1; + printk(KERN_WARNING "HIL: no keyboard present\n"); + } else { + kbid = ffz(~c); + printk(KERN_INFO "HIL: keyboard found at id %d\n", kbid); + } + + /* set it to raw mode */ + c = 0; + hil_do(HIL_WRITEKBDSADR, &c, 1); + + for (i = 0; i < HIL_KEYCODES_SET1_TBLSIZE; i++) + if (hphilkeyb_keycode[i] != KEY_RESERVED) + set_bit(hphilkeyb_keycode[i], hil_dev.dev->keybit); + + hil_dev.dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + hil_dev.dev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) | + BIT_MASK(LED_SCROLLL); + hil_dev.dev->keycodemax = HIL_KEYCODES_SET1_TBLSIZE; + hil_dev.dev->keycodesize= sizeof(hphilkeyb_keycode[0]); + hil_dev.dev->keycode = hphilkeyb_keycode; + hil_dev.dev->name = "HIL keyboard"; + hil_dev.dev->phys = "hpkbd/input0"; + + hil_dev.dev->id.bustype = BUS_HIL; + hil_dev.dev->id.vendor = PCI_VENDOR_ID_HP; + hil_dev.dev->id.product = 0x0001; + hil_dev.dev->id.version = 0x0010; + + err = input_register_device(hil_dev.dev); + if (err) { + printk(KERN_ERR "HIL: Can't register device\n"); + goto err3; + } + printk(KERN_INFO "input: %s, ID %d at 0x%08lx (irq %d) found and attached\n", + hil_dev.dev->name, kbid, HILBASE, HIL_IRQ); + + return 0; + +err3: + hil_do(HIL_INTOFF, NULL, 0); + disable_irq(HIL_IRQ); + free_irq(HIL_IRQ, hil_dev.dev_id); +err2: +#if defined(CONFIG_HP300) + release_region(HILBASE + HIL_DATA, 2); +err1: +#endif + input_free_device(hil_dev.dev); + hil_dev.dev = NULL; + return err; +} + + +#if defined(CONFIG_PARISC) +static int __init +hil_init_chip(struct parisc_device *dev) +{ + if (!dev->irq) { + printk(KERN_WARNING "HIL: IRQ not found for HIL bus at 0x%08lx\n", dev->hpa.start); + return -ENODEV; + } + + hil_base = dev->hpa.start; + hil_irq = dev->irq; + hil_dev.dev_id = dev; + + printk(KERN_INFO "Found HIL bus at 0x%08lx, IRQ %d\n", hil_base, hil_irq); + + return hil_keyb_init(); +} + +static struct parisc_device_id hil_tbl[] = { + { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x00073 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(parisc, hil_tbl); + +static struct parisc_driver hil_driver = { + .name = "hil", + .id_table = hil_tbl, + .probe = hil_init_chip, +}; +#endif /* CONFIG_PARISC */ + + +static int __init hil_init(void) +{ +#if defined(CONFIG_PARISC) + return register_parisc_driver(&hil_driver); +#else + return hil_keyb_init(); +#endif +} + + +static void __exit hil_exit(void) +{ + if (HIL_IRQ) { + disable_irq(HIL_IRQ); + free_irq(HIL_IRQ, hil_dev.dev_id); + } + + /* Turn off interrupts */ + hil_do(HIL_INTOFF, NULL, 0); + + input_unregister_device(hil_dev.dev); + + hil_dev.dev = NULL; + +#if defined(CONFIG_PARISC) + unregister_parisc_driver(&hil_driver); +#else + release_region(HILBASE+HIL_DATA, 2); +#endif +} + +module_init(hil_init); +module_exit(hil_exit); diff --git a/drivers/input/keyboard/hpps2atkbd.h b/drivers/input/keyboard/hpps2atkbd.h new file mode 100644 index 0000000..dc33f69 --- /dev/null +++ b/drivers/input/keyboard/hpps2atkbd.h @@ -0,0 +1,110 @@ +/* + * drivers/input/keyboard/hpps2atkbd.h + * + * Copyright (c) 2004 Helge Deller <deller@gmx.de> + * Copyright (c) 2002 Laurent Canet <canetl@esiee.fr> + * Copyright (c) 2002 Thibaut Varene <varenet@parisc-linux.org> + * Copyright (c) 2000 Xavier Debacker <debackex@esiee.fr> + * + * HP PS/2 AT-compatible Keyboard, found in PA/RISC Workstations & Laptops + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + + +/* Is the keyboard an RDI PrecisionBook? */ +#ifndef CONFIG_KEYBOARD_ATKBD_RDI_KEYCODES +# define CONFLICT(x,y) x +#else +# define CONFLICT(x,y) y +#endif + +/* sadly RDI (Tadpole) decided to ship a different keyboard layout + than HP for their PS/2 laptop keyboard which leads to conflicting + keycodes between a normal HP PS/2 keyboard and a RDI Precisionbook. + HP: RDI: */ +#define C_07 CONFLICT( KEY_F12, KEY_F1 ) +#define C_11 CONFLICT( KEY_LEFTALT, KEY_LEFTCTRL ) +#define C_14 CONFLICT( KEY_LEFTCTRL, KEY_CAPSLOCK ) +#define C_58 CONFLICT( KEY_CAPSLOCK, KEY_RIGHTCTRL ) +#define C_61 CONFLICT( KEY_102ND, KEY_LEFT ) + +/* Raw SET 2 scancode table */ + +/* 00 */ KEY_RESERVED, KEY_F9, KEY_RESERVED, KEY_F5, KEY_F3, KEY_F1, KEY_F2, C_07, +/* 08 */ KEY_ESC, KEY_F10, KEY_F8, KEY_F6, KEY_F4, KEY_TAB, KEY_GRAVE, KEY_F2, +/* 10 */ KEY_RESERVED, C_11, KEY_LEFTSHIFT, KEY_RESERVED, C_14, KEY_Q, KEY_1, KEY_F3, +/* 18 */ KEY_RESERVED, KEY_LEFTALT, KEY_Z, KEY_S, KEY_A, KEY_W, KEY_2, KEY_F4, +/* 20 */ KEY_RESERVED, KEY_C, KEY_X, KEY_D, KEY_E, KEY_4, KEY_3, KEY_F5, +/* 28 */ KEY_RESERVED, KEY_SPACE, KEY_V, KEY_F, KEY_T, KEY_R, KEY_5, KEY_F6, +/* 30 */ KEY_RESERVED, KEY_N, KEY_B, KEY_H, KEY_G, KEY_Y, KEY_6, KEY_F7, +/* 38 */ KEY_RESERVED, KEY_RIGHTALT, KEY_M, KEY_J, KEY_U, KEY_7, KEY_8, KEY_F8, +/* 40 */ KEY_RESERVED, KEY_COMMA, KEY_K, KEY_I, KEY_O, KEY_0, KEY_9, KEY_F9, +/* 48 */ KEY_RESERVED, KEY_DOT, KEY_SLASH, KEY_L, KEY_SEMICOLON, KEY_P, KEY_MINUS, KEY_F10, +/* 50 */ KEY_RESERVED, KEY_RESERVED, KEY_APOSTROPHE,KEY_RESERVED, KEY_LEFTBRACE, KEY_EQUAL, KEY_F11, KEY_SYSRQ, +/* 58 */ C_58, KEY_RIGHTSHIFT,KEY_ENTER, KEY_RIGHTBRACE,KEY_BACKSLASH, KEY_BACKSLASH,KEY_F12, KEY_SCROLLLOCK, +/* 60 */ KEY_DOWN, C_61, KEY_PAUSE, KEY_UP, KEY_DELETE, KEY_END, KEY_BACKSPACE, KEY_INSERT, +/* 68 */ KEY_RESERVED, KEY_KP1, KEY_RIGHT, KEY_KP4, KEY_KP7, KEY_PAGEDOWN, KEY_HOME, KEY_PAGEUP, +/* 70 */ KEY_KP0, KEY_KPDOT, KEY_KP2, KEY_KP5, KEY_KP6, KEY_KP8, KEY_ESC, KEY_NUMLOCK, +/* 78 */ KEY_F11, KEY_KPPLUS, KEY_KP3, KEY_KPMINUS, KEY_KPASTERISK,KEY_KP9, KEY_SCROLLLOCK,KEY_102ND, +/* 80 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 88 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 90 */ KEY_RESERVED, KEY_RIGHTALT, 255, KEY_RESERVED, KEY_RIGHTCTRL, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 98 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_CAPSLOCK, KEY_RESERVED, KEY_LEFTMETA, +/* a0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RIGHTMETA, +/* a8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_COMPOSE, +/* b0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* b8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* c0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* c8 */ KEY_RESERVED, KEY_RESERVED, KEY_KPSLASH, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* d0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* d8 */ KEY_RESERVED, KEY_RESERVED, KEY_KPENTER, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* e0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* e8 */ KEY_RESERVED, KEY_END, KEY_RESERVED, KEY_LEFT, KEY_HOME, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* f0 */ KEY_INSERT, KEY_DELETE, KEY_DOWN, KEY_RESERVED, KEY_RIGHT, KEY_UP, KEY_RESERVED, KEY_PAUSE, +/* f8 */ KEY_RESERVED, KEY_RESERVED, KEY_PAGEDOWN, KEY_RESERVED, KEY_SYSRQ, KEY_PAGEUP, KEY_RESERVED, KEY_RESERVED, + +/* These are offset for escaped keycodes: */ + +/* 00 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_F7, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 08 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 10 */ KEY_RESERVED, KEY_RIGHTALT, KEY_RESERVED, KEY_RESERVED, KEY_RIGHTCTRL, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 18 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 20 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 28 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 30 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 38 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 40 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 48 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 50 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 58 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 60 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 68 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 70 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 78 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 80 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 88 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 90 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* 98 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* a0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* a8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* b0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* b8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* c0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* c8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* d0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* d8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* e0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* e8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* f0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/* f8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED + +#undef CONFLICT +#undef C_07 +#undef C_11 +#undef C_14 +#undef C_58 +#undef C_61 + diff --git a/drivers/input/keyboard/jornada680_kbd.c b/drivers/input/keyboard/jornada680_kbd.c new file mode 100644 index 0000000..781fc61 --- /dev/null +++ b/drivers/input/keyboard/jornada680_kbd.c @@ -0,0 +1,279 @@ +/* + * drivers/input/keyboard/jornada680_kbd.c + * + * HP Jornada 620/660/680/690 scan keyboard platform driver + * Copyright (C) 2007 Kristoffer Ericson <Kristoffer.Ericson@gmail.com> + * + * Based on hp680_keyb.c + * Copyright (C) 2006 Paul Mundt + * Copyright (C) 2005 Andriy Skulysh + * Split from drivers/input/keyboard/hp600_keyb.c + * Copyright (C) 2000 Yaegashi Takeshi (hp6xx kbd scan routine and translation table) + * Copyright (C) 2000 Niibe Yutaka (HP620 Keyb translation table) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/init.h> +#include <linux/input.h> +#include <linux/input-polldev.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <asm/delay.h> +#include <asm/io.h> + +#define PCCR 0xa4000104 +#define PDCR 0xa4000106 +#define PECR 0xa4000108 +#define PFCR 0xa400010a +#define PCDR 0xa4000124 +#define PDDR 0xa4000126 +#define PEDR 0xa4000128 +#define PFDR 0xa400012a +#define PGDR 0xa400012c +#define PHDR 0xa400012e +#define PJDR 0xa4000130 +#define PKDR 0xa4000132 +#define PLDR 0xa4000134 + +static const unsigned short jornada_scancodes[] = { +/* PTD1 */ KEY_CAPSLOCK, KEY_MACRO, KEY_LEFTCTRL, 0, KEY_ESC, KEY_KP5, 0, 0, /* 1 -> 8 */ + KEY_F1, KEY_F2, KEY_F3, KEY_F8, KEY_F7, KEY_F6, KEY_F4, KEY_F5, /* 9 -> 16 */ +/* PTD5 */ KEY_SLASH, KEY_APOSTROPHE, KEY_ENTER, 0, KEY_Z, 0, 0, 0, /* 17 -> 24 */ + KEY_X, KEY_C, KEY_V, KEY_DOT, KEY_COMMA, KEY_M, KEY_B, KEY_N, /* 25 -> 32 */ +/* PTD7 */ KEY_KP2, KEY_KP6, KEY_KP3, 0, 0, 0, 0, 0, /* 33 -> 40 */ + KEY_F10, KEY_RO, KEY_F9, KEY_KP4, KEY_NUMLOCK, KEY_SCROLLLOCK, KEY_LEFTALT, KEY_HANJA, /* 41 -> 48 */ +/* PTE0 */ KEY_KATAKANA, KEY_KP0, KEY_GRAVE, 0, KEY_FINANCE, 0, 0, 0, /* 49 -> 56 */ + KEY_KPMINUS, KEY_HIRAGANA, KEY_SPACE, KEY_KPDOT, KEY_VOLUMEUP, 249, 0, 0, /* 57 -> 64 */ +/* PTE1 */ KEY_SEMICOLON, KEY_RIGHTBRACE, KEY_BACKSLASH, 0, KEY_A, 0, 0, 0, /* 65 -> 72 */ + KEY_S, KEY_D, KEY_F, KEY_L, KEY_K, KEY_J, KEY_G, KEY_H, /* 73 -> 80 */ +/* PTE3 */ KEY_KP8, KEY_LEFTMETA, KEY_RIGHTSHIFT, 0, KEY_TAB, 0, 0, 0, /* 81 -> 88 */ + 0, KEY_LEFTSHIFT, KEY_KP7, KEY_KP9, KEY_KP1, KEY_F11, KEY_KPPLUS, KEY_KPASTERISK, /* 89 -> 96 */ +/* PTE6 */ KEY_P, KEY_LEFTBRACE, KEY_BACKSPACE, 0, KEY_Q, 0, 0, 0, /* 97 -> 104 */ + KEY_W, KEY_E, KEY_R, KEY_O, KEY_I, KEY_U, KEY_T, KEY_Y, /* 105 -> 112 */ +/* PTE7 */ KEY_0, KEY_MINUS, KEY_EQUAL, 0, KEY_1, 0, 0, 0, /* 113 -> 120 */ + KEY_2, KEY_3, KEY_4, KEY_9, KEY_8, KEY_7, KEY_5, KEY_6, /* 121 -> 128 */ +/* **** */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 +}; + +#define JORNADA_SCAN_SIZE 18 + +struct jornadakbd { + struct input_polled_dev *poll_dev; + unsigned short keymap[ARRAY_SIZE(jornada_scancodes)]; + unsigned char length; + unsigned char old_scan[JORNADA_SCAN_SIZE]; + unsigned char new_scan[JORNADA_SCAN_SIZE]; +}; + +static void jornada_parse_kbd(struct jornadakbd *jornadakbd) +{ + struct input_dev *input_dev = jornadakbd->poll_dev->input; + unsigned short *keymap = jornadakbd->keymap; + unsigned int sync_me = 0; + unsigned int i, j; + + for (i = 0; i < JORNADA_SCAN_SIZE; i++) { + unsigned char new = jornadakbd->new_scan[i]; + unsigned char old = jornadakbd->old_scan[i]; + unsigned int xor = new ^ old; + + if (xor == 0) + continue; + + for (j = 0; j < 8; j++) { + unsigned int bit = 1 << j; + if (xor & bit) { + unsigned int scancode = (i << 3) + j; + input_event(input_dev, + EV_MSC, MSC_SCAN, scancode); + input_report_key(input_dev, + keymap[scancode], + !(new & bit)); + sync_me = 1; + } + } + } + + if (sync_me) + input_sync(input_dev); +} + +static void jornada_scan_keyb(unsigned char *s) +{ + int i; + unsigned short ec_static, dc_static; /* = UINT16_t */ + unsigned char matrix_switch[] = { + 0xfd, 0xff, /* PTD1 PD(1) */ + 0xdf, 0xff, /* PTD5 PD(5) */ + 0x7f, 0xff, /* PTD7 PD(7) */ + 0xff, 0xfe, /* PTE0 PE(0) */ + 0xff, 0xfd, /* PTE1 PE(1) */ + 0xff, 0xf7, /* PTE3 PE(3) */ + 0xff, 0xbf, /* PTE6 PE(6) */ + 0xff, 0x7f, /* PTE7 PE(7) */ + }, *t = matrix_switch; + /* PD(x) : + 1. 0xcc0c & (1~(1 << (2*(x)+1))))) + 2. (0xf0cf & 0xfffff) */ + /* PE(x) : + 1. 0xcc0c & 0xffff + 2. 0xf0cf & (1~(1 << (2*(x)+1))))) */ + unsigned short matrix_PDE[] = { + 0xcc04, 0xf0cf, /* PD(1) */ + 0xc40c, 0xf0cf, /* PD(5) */ + 0x4c0c, 0xf0cf, /* PD(7) */ + 0xcc0c, 0xf0cd, /* PE(0) */ + 0xcc0c, 0xf0c7, /* PE(1) */ + 0xcc0c, 0xf04f, /* PE(3) */ + 0xcc0c, 0xd0cf, /* PE(6) */ + 0xcc0c, 0x70cf, /* PE(7) */ + }, *y = matrix_PDE; + + /* Save these control reg bits */ + dc_static = (ctrl_inw(PDCR) & (~0xcc0c)); + ec_static = (ctrl_inw(PECR) & (~0xf0cf)); + + for (i = 0; i < 8; i++) { + /* disable output for all but the one we want to scan */ + ctrl_outw((dc_static | *y++), PDCR); + ctrl_outw((ec_static | *y++), PECR); + udelay(5); + + /* Get scanline row */ + ctrl_outb(*t++, PDDR); + ctrl_outb(*t++, PEDR); + udelay(50); + + /* Read data */ + *s++ = ctrl_inb(PCDR); + *s++ = ctrl_inb(PFDR); + } + /* Scan no lines */ + ctrl_outb(0xff, PDDR); + ctrl_outb(0xff, PEDR); + + /* Enable all scanlines */ + ctrl_outw((dc_static | (0x5555 & 0xcc0c)),PDCR); + ctrl_outw((ec_static | (0x5555 & 0xf0cf)),PECR); + + /* Ignore extra keys and events */ + *s++ = ctrl_inb(PGDR); + *s++ = ctrl_inb(PHDR); +} + +static void jornadakbd680_poll(struct input_polled_dev *dev) +{ + struct jornadakbd *jornadakbd = dev->private; + + jornada_scan_keyb(jornadakbd->new_scan); + jornada_parse_kbd(jornadakbd); + memcpy(jornadakbd->old_scan, jornadakbd->new_scan, JORNADA_SCAN_SIZE); +} + +static int __devinit jornada680kbd_probe(struct platform_device *pdev) +{ + struct jornadakbd *jornadakbd; + struct input_polled_dev *poll_dev; + struct input_dev *input_dev; + int i, error; + + jornadakbd = kzalloc(sizeof(struct jornadakbd), GFP_KERNEL); + if (!jornadakbd) + return -ENOMEM; + + poll_dev = input_allocate_polled_device(); + if (!poll_dev) { + error = -ENOMEM; + goto failed; + } + + platform_set_drvdata(pdev, jornadakbd); + + jornadakbd->poll_dev = poll_dev; + + memcpy(jornadakbd->keymap, jornada_scancodes, + sizeof(jornadakbd->keymap)); + + poll_dev->private = jornadakbd; + poll_dev->poll = jornadakbd680_poll; + poll_dev->poll_interval = 50; /* msec */ + + input_dev = poll_dev->input; + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + input_dev->name = "HP Jornada 680 keyboard"; + input_dev->phys = "jornadakbd/input0"; + input_dev->keycode = jornadakbd->keymap; + input_dev->keycodesize = sizeof(unsigned short); + input_dev->keycodemax = ARRAY_SIZE(jornada_scancodes); + input_dev->dev.parent = &pdev->dev; + input_dev->id.bustype = BUS_HOST; + + for (i = 0; i < 128; i++) + if (jornadakbd->keymap[i]) + __set_bit(jornadakbd->keymap[i], input_dev->keybit); + __clear_bit(KEY_RESERVED, input_dev->keybit); + + input_set_capability(input_dev, EV_MSC, MSC_SCAN); + + error = input_register_polled_device(jornadakbd->poll_dev); + if (error) + goto failed; + + return 0; + + failed: + printk(KERN_ERR "Jornadakbd: failed to register driver, error: %d\n", + error); + platform_set_drvdata(pdev, NULL); + input_free_polled_device(poll_dev); + kfree(jornadakbd); + return error; + +} + +static int __devexit jornada680kbd_remove(struct platform_device *pdev) +{ + struct jornadakbd *jornadakbd = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + input_unregister_polled_device(jornadakbd->poll_dev); + input_free_polled_device(jornadakbd->poll_dev); + kfree(jornadakbd); + + return 0; +} + +static struct platform_driver jornada680kbd_driver = { + .driver = { + .name = "jornada680_kbd", + .owner = THIS_MODULE, + }, + .probe = jornada680kbd_probe, + .remove = __devexit_p(jornada680kbd_remove), +}; + +static int __init jornada680kbd_init(void) +{ + return platform_driver_register(&jornada680kbd_driver); +} + +static void __exit jornada680kbd_exit(void) +{ + platform_driver_unregister(&jornada680kbd_driver); +} + +module_init(jornada680kbd_init); +module_exit(jornada680kbd_exit); + +MODULE_AUTHOR("Kristoffer Ericson <kristoffer.ericson@gmail.com>"); +MODULE_DESCRIPTION("HP Jornada 620/660/680/690 Keyboard Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:jornada680_kbd"); diff --git a/drivers/input/keyboard/jornada720_kbd.c b/drivers/input/keyboard/jornada720_kbd.c new file mode 100644 index 0000000..4e016d8 --- /dev/null +++ b/drivers/input/keyboard/jornada720_kbd.c @@ -0,0 +1,188 @@ +/* + * drivers/input/keyboard/jornada720_kbd.c + * + * HP Jornada 720 keyboard platform driver + * + * Copyright (C) 2006/2007 Kristoffer Ericson <Kristoffer.Ericson@Gmail.com> + * + * Copyright (C) 2006 jornada 720 kbd driver by + Filip Zyzniewsk <Filip.Zyzniewski@tefnet.plX + * based on (C) 2004 jornada 720 kbd driver by + Alex Lange <chicken@handhelds.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <mach/jornada720.h> +#include <mach/hardware.h> + +MODULE_AUTHOR("Kristoffer Ericson <Kristoffer.Ericson@gmail.com>"); +MODULE_DESCRIPTION("HP Jornada 710/720/728 keyboard driver"); +MODULE_LICENSE("GPL v2"); + +static unsigned short jornada_std_keymap[128] = { /* ROW */ + 0, KEY_ESC, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, /* #1 */ + KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_VOLUMEUP, KEY_VOLUMEDOWN, KEY_MUTE, /* -> */ + 0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, /* #2 */ + KEY_0, KEY_MINUS, KEY_EQUAL,0, 0, 0, /* -> */ + 0, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, /* #3 */ + KEY_P, KEY_BACKSLASH, KEY_BACKSPACE, 0, 0, 0, /* -> */ + 0, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, /* #4 */ + KEY_SEMICOLON, KEY_LEFTBRACE, KEY_RIGHTBRACE, 0, 0, 0, /* -> */ + 0, KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, KEY_COMMA, /* #5 */ + KEY_DOT, KEY_KPMINUS, KEY_APOSTROPHE, KEY_ENTER, 0, 0,0, /* -> */ + 0, KEY_TAB, 0, KEY_LEFTSHIFT, 0, KEY_APOSTROPHE, 0, 0, 0, 0, /* #6 */ + KEY_UP, 0, KEY_RIGHTSHIFT, 0, 0, 0,0, 0, 0, 0, 0, KEY_LEFTALT, KEY_GRAVE, /* -> */ + 0, 0, KEY_LEFT, KEY_DOWN, KEY_RIGHT, 0, 0, 0, 0,0, KEY_KPASTERISK, /* -> */ + KEY_LEFTCTRL, 0, KEY_SPACE, 0, 0, 0, KEY_SLASH, KEY_DELETE, 0, 0, /* -> */ + 0, 0, 0, KEY_POWER, /* -> */ +}; + +struct jornadakbd { + unsigned short keymap[ARRAY_SIZE(jornada_std_keymap)]; + struct input_dev *input; +}; + +static irqreturn_t jornada720_kbd_interrupt(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct jornadakbd *jornadakbd = platform_get_drvdata(pdev); + struct input_dev *input = jornadakbd->input; + u8 count, kbd_data, scan_code; + + /* startup ssp with spinlock */ + jornada_ssp_start(); + + if (jornada_ssp_inout(GETSCANKEYCODE) != TXDUMMY) { + printk(KERN_DEBUG + "jornada720_kbd: " + "GetKeycode command failed with ETIMEDOUT, " + "flushed bus\n"); + } else { + /* How many keycodes are waiting for us? */ + count = jornada_ssp_byte(TXDUMMY); + + /* Lets drag them out one at a time */ + while (count--) { + /* Exchange TxDummy for location (keymap[kbddata]) */ + kbd_data = jornada_ssp_byte(TXDUMMY); + scan_code = kbd_data & 0x7f; + + input_event(input, EV_MSC, MSC_SCAN, scan_code); + input_report_key(input, jornadakbd->keymap[scan_code], + !(kbd_data & 0x80)); + input_sync(input); + } + } + + /* release spinlock and turn off ssp */ + jornada_ssp_end(); + + return IRQ_HANDLED; +}; + +static int __devinit jornada720_kbd_probe(struct platform_device *pdev) +{ + struct jornadakbd *jornadakbd; + struct input_dev *input_dev; + int i, err; + + jornadakbd = kzalloc(sizeof(struct jornadakbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!jornadakbd || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + platform_set_drvdata(pdev, jornadakbd); + + memcpy(jornadakbd->keymap, jornada_std_keymap, + sizeof(jornada_std_keymap)); + jornadakbd->input = input_dev; + + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + input_dev->name = "HP Jornada 720 keyboard"; + input_dev->phys = "jornadakbd/input0"; + input_dev->keycode = jornadakbd->keymap; + input_dev->keycodesize = sizeof(unsigned short); + input_dev->keycodemax = ARRAY_SIZE(jornada_std_keymap); + input_dev->id.bustype = BUS_HOST; + input_dev->dev.parent = &pdev->dev; + + for (i = 0; i < ARRAY_SIZE(jornadakbd->keymap); i++) + __set_bit(jornadakbd->keymap[i], input_dev->keybit); + __clear_bit(KEY_RESERVED, input_dev->keybit); + + input_set_capability(input_dev, EV_MSC, MSC_SCAN); + + err = request_irq(IRQ_GPIO0, + jornada720_kbd_interrupt, + IRQF_DISABLED | IRQF_TRIGGER_FALLING, + "jornadakbd", pdev); + if (err) { + printk(KERN_INFO "jornadakbd720_kbd: Unable to grab IRQ\n"); + goto fail1; + } + + err = input_register_device(jornadakbd->input); + if (err) + goto fail2; + + return 0; + + fail2: /* IRQ, DEVICE, MEMORY */ + free_irq(IRQ_GPIO0, pdev); + fail1: /* DEVICE, MEMORY */ + platform_set_drvdata(pdev, NULL); + input_free_device(input_dev); + kfree(jornadakbd); + return err; +}; + +static int __devexit jornada720_kbd_remove(struct platform_device *pdev) +{ + struct jornadakbd *jornadakbd = platform_get_drvdata(pdev); + + free_irq(IRQ_GPIO0, pdev); + platform_set_drvdata(pdev, NULL); + input_unregister_device(jornadakbd->input); + kfree(jornadakbd); + + return 0; +} + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:jornada720_kbd"); + +static struct platform_driver jornada720_kbd_driver = { + .driver = { + .name = "jornada720_kbd", + .owner = THIS_MODULE, + }, + .probe = jornada720_kbd_probe, + .remove = __devexit_p(jornada720_kbd_remove), +}; + +static int __init jornada720_kbd_init(void) +{ + return platform_driver_register(&jornada720_kbd_driver); +} + +static void __exit jornada720_kbd_exit(void) +{ + platform_driver_unregister(&jornada720_kbd_driver); +} + +module_init(jornada720_kbd_init); +module_exit(jornada720_kbd_exit); diff --git a/drivers/input/keyboard/lkkbd.c b/drivers/input/keyboard/lkkbd.c new file mode 100644 index 0000000..4730ef3 --- /dev/null +++ b/drivers/input/keyboard/lkkbd.c @@ -0,0 +1,767 @@ +/* + * Copyright (C) 2004 by Jan-Benedict Glaw <jbglaw@lug-owl.de> + */ + +/* + * LK keyboard driver for Linux, based on sunkbd.c (C) by Vojtech Pavlik + */ + +/* + * DEC LK201 and LK401 keyboard driver for Linux (primary for DECstations + * and VAXstations, but can also be used on any standard RS232 with an + * adaptor). + * + * DISCLAIMER: This works for _me_. If you break anything by using the + * information given below, I will _not_ be liable! + * + * RJ10 pinout: To DE9: Or DB25: + * 1 - RxD <----> Pin 3 (TxD) <-> Pin 2 (TxD) + * 2 - GND <----> Pin 5 (GND) <-> Pin 7 (GND) + * 4 - TxD <----> Pin 2 (RxD) <-> Pin 3 (RxD) + * 3 - +12V (from HDD drive connector), DON'T connect to DE9 or DB25!!! + * + * Pin numbers for DE9 and DB25 are noted on the plug (quite small:). For + * RJ10, it's like this: + * + * __=__ Hold the plug in front of you, cable downwards, + * /___/| nose is hidden behind the plug. Now, pin 1 is at + * |1234|| the left side, pin 4 at the right and 2 and 3 are + * |IIII|| in between, of course:) + * | || + * |____|/ + * || So the adaptor consists of three connected cables + * || for data transmission (RxD and TxD) and signal ground. + * Additionally, you have to get +12V from somewhere. + * Most easily, you'll get that from a floppy or HDD power connector. + * It's the yellow cable there (black is ground and red is +5V). + * + * The keyboard and all the commands it understands are documented in + * "VCB02 Video Subsystem - Technical Manual", EK-104AA-TM-001. This + * document is LK201 specific, but LK401 is mostly compatible. It comes + * up in LK201 mode and doesn't report any of the additional keys it + * has. These need to be switched on with the LK_CMD_ENABLE_LK401 + * command. You'll find this document (scanned .pdf file) on MANX, + * a search engine specific to DEC documentation. Try + * http://www.vt100.net/manx/details?pn=EK-104AA-TM-001;id=21;cp=1 + */ + +/* + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/workqueue.h> + +#define DRIVER_DESC "LK keyboard driver" + +MODULE_AUTHOR ("Jan-Benedict Glaw <jbglaw@lug-owl.de>"); +MODULE_DESCRIPTION (DRIVER_DESC); +MODULE_LICENSE ("GPL"); + +/* + * Known parameters: + * bell_volume + * keyclick_volume + * ctrlclick_volume + * + * Please notice that there's not yet an API to set these at runtime. + */ +static int bell_volume = 100; /* % */ +module_param (bell_volume, int, 0); +MODULE_PARM_DESC (bell_volume, "Bell volume (in %). default is 100%"); + +static int keyclick_volume = 100; /* % */ +module_param (keyclick_volume, int, 0); +MODULE_PARM_DESC (keyclick_volume, "Keyclick volume (in %), default is 100%"); + +static int ctrlclick_volume = 100; /* % */ +module_param (ctrlclick_volume, int, 0); +MODULE_PARM_DESC (ctrlclick_volume, "Ctrlclick volume (in %), default is 100%"); + +static int lk201_compose_is_alt; +module_param (lk201_compose_is_alt, int, 0); +MODULE_PARM_DESC (lk201_compose_is_alt, "If set non-zero, LK201' Compose key " + "will act as an Alt key"); + + + +#undef LKKBD_DEBUG +#ifdef LKKBD_DEBUG +#define DBG(x...) printk (x) +#else +#define DBG(x...) do {} while (0) +#endif + +/* LED control */ +#define LK_LED_WAIT 0x81 +#define LK_LED_COMPOSE 0x82 +#define LK_LED_SHIFTLOCK 0x84 +#define LK_LED_SCROLLLOCK 0x88 +#define LK_CMD_LED_ON 0x13 +#define LK_CMD_LED_OFF 0x11 + +/* Mode control */ +#define LK_MODE_DOWN 0x80 +#define LK_MODE_AUTODOWN 0x82 +#define LK_MODE_UPDOWN 0x86 +#define LK_CMD_SET_MODE(mode,div) ((mode) | ((div) << 3)) + +/* Misc commands */ +#define LK_CMD_ENABLE_KEYCLICK 0x1b +#define LK_CMD_DISABLE_KEYCLICK 0x99 +#define LK_CMD_DISABLE_BELL 0xa1 +#define LK_CMD_SOUND_BELL 0xa7 +#define LK_CMD_ENABLE_BELL 0x23 +#define LK_CMD_DISABLE_CTRCLICK 0xb9 +#define LK_CMD_ENABLE_CTRCLICK 0xbb +#define LK_CMD_SET_DEFAULTS 0xd3 +#define LK_CMD_POWERCYCLE_RESET 0xfd +#define LK_CMD_ENABLE_LK401 0xe9 +#define LK_CMD_REQUEST_ID 0xab + +/* Misc responses from keyboard */ +#define LK_STUCK_KEY 0x3d +#define LK_SELFTEST_FAILED 0x3e +#define LK_ALL_KEYS_UP 0xb3 +#define LK_METRONOME 0xb4 +#define LK_OUTPUT_ERROR 0xb5 +#define LK_INPUT_ERROR 0xb6 +#define LK_KBD_LOCKED 0xb7 +#define LK_KBD_TEST_MODE_ACK 0xb8 +#define LK_PREFIX_KEY_DOWN 0xb9 +#define LK_MODE_CHANGE_ACK 0xba +#define LK_RESPONSE_RESERVED 0xbb + +#define LK_NUM_KEYCODES 256 +#define LK_NUM_IGNORE_BYTES 6 +typedef u_int16_t lk_keycode_t; + + + +static lk_keycode_t lkkbd_keycode[LK_NUM_KEYCODES] = { + [0x56] = KEY_F1, + [0x57] = KEY_F2, + [0x58] = KEY_F3, + [0x59] = KEY_F4, + [0x5a] = KEY_F5, + [0x64] = KEY_F6, + [0x65] = KEY_F7, + [0x66] = KEY_F8, + [0x67] = KEY_F9, + [0x68] = KEY_F10, + [0x71] = KEY_F11, + [0x72] = KEY_F12, + [0x73] = KEY_F13, + [0x74] = KEY_F14, + [0x7c] = KEY_F15, + [0x7d] = KEY_F16, + [0x80] = KEY_F17, + [0x81] = KEY_F18, + [0x82] = KEY_F19, + [0x83] = KEY_F20, + [0x8a] = KEY_FIND, + [0x8b] = KEY_INSERT, + [0x8c] = KEY_DELETE, + [0x8d] = KEY_SELECT, + [0x8e] = KEY_PAGEUP, + [0x8f] = KEY_PAGEDOWN, + [0x92] = KEY_KP0, + [0x94] = KEY_KPDOT, + [0x95] = KEY_KPENTER, + [0x96] = KEY_KP1, + [0x97] = KEY_KP2, + [0x98] = KEY_KP3, + [0x99] = KEY_KP4, + [0x9a] = KEY_KP5, + [0x9b] = KEY_KP6, + [0x9c] = KEY_KPCOMMA, + [0x9d] = KEY_KP7, + [0x9e] = KEY_KP8, + [0x9f] = KEY_KP9, + [0xa0] = KEY_KPMINUS, + [0xa1] = KEY_PROG1, + [0xa2] = KEY_PROG2, + [0xa3] = KEY_PROG3, + [0xa4] = KEY_PROG4, + [0xa7] = KEY_LEFT, + [0xa8] = KEY_RIGHT, + [0xa9] = KEY_DOWN, + [0xaa] = KEY_UP, + [0xab] = KEY_RIGHTSHIFT, + [0xac] = KEY_LEFTALT, + [0xad] = KEY_COMPOSE, /* Right Compose, that is. */ + [0xae] = KEY_LEFTSHIFT, /* Same as KEY_RIGHTSHIFT on LK201 */ + [0xaf] = KEY_LEFTCTRL, + [0xb0] = KEY_CAPSLOCK, + [0xb1] = KEY_COMPOSE, /* Left Compose, that is. */ + [0xb2] = KEY_RIGHTALT, + [0xbc] = KEY_BACKSPACE, + [0xbd] = KEY_ENTER, + [0xbe] = KEY_TAB, + [0xbf] = KEY_ESC, + [0xc0] = KEY_1, + [0xc1] = KEY_Q, + [0xc2] = KEY_A, + [0xc3] = KEY_Z, + [0xc5] = KEY_2, + [0xc6] = KEY_W, + [0xc7] = KEY_S, + [0xc8] = KEY_X, + [0xc9] = KEY_102ND, + [0xcb] = KEY_3, + [0xcc] = KEY_E, + [0xcd] = KEY_D, + [0xce] = KEY_C, + [0xd0] = KEY_4, + [0xd1] = KEY_R, + [0xd2] = KEY_F, + [0xd3] = KEY_V, + [0xd4] = KEY_SPACE, + [0xd6] = KEY_5, + [0xd7] = KEY_T, + [0xd8] = KEY_G, + [0xd9] = KEY_B, + [0xdb] = KEY_6, + [0xdc] = KEY_Y, + [0xdd] = KEY_H, + [0xde] = KEY_N, + [0xe0] = KEY_7, + [0xe1] = KEY_U, + [0xe2] = KEY_J, + [0xe3] = KEY_M, + [0xe5] = KEY_8, + [0xe6] = KEY_I, + [0xe7] = KEY_K, + [0xe8] = KEY_COMMA, + [0xea] = KEY_9, + [0xeb] = KEY_O, + [0xec] = KEY_L, + [0xed] = KEY_DOT, + [0xef] = KEY_0, + [0xf0] = KEY_P, + [0xf2] = KEY_SEMICOLON, + [0xf3] = KEY_SLASH, + [0xf5] = KEY_EQUAL, + [0xf6] = KEY_RIGHTBRACE, + [0xf7] = KEY_BACKSLASH, + [0xf9] = KEY_MINUS, + [0xfa] = KEY_LEFTBRACE, + [0xfb] = KEY_APOSTROPHE, +}; + +#define CHECK_LED(LK, VAR_ON, VAR_OFF, LED, BITS) do { \ + if (test_bit (LED, (LK)->dev->led)) \ + VAR_ON |= BITS; \ + else \ + VAR_OFF |= BITS; \ + } while (0) + +/* + * Per-keyboard data + */ +struct lkkbd { + lk_keycode_t keycode[LK_NUM_KEYCODES]; + int ignore_bytes; + unsigned char id[LK_NUM_IGNORE_BYTES]; + struct input_dev *dev; + struct serio *serio; + struct work_struct tq; + char name[64]; + char phys[32]; + char type; + int bell_volume; + int keyclick_volume; + int ctrlclick_volume; +}; + +#ifdef LKKBD_DEBUG +/* + * Responses from the keyboard and mapping back to their names. + */ +static struct { + unsigned char value; + unsigned char *name; +} lk_response[] = { +#define RESPONSE(x) { .value = (x), .name = #x, } + RESPONSE (LK_STUCK_KEY), + RESPONSE (LK_SELFTEST_FAILED), + RESPONSE (LK_ALL_KEYS_UP), + RESPONSE (LK_METRONOME), + RESPONSE (LK_OUTPUT_ERROR), + RESPONSE (LK_INPUT_ERROR), + RESPONSE (LK_KBD_LOCKED), + RESPONSE (LK_KBD_TEST_MODE_ACK), + RESPONSE (LK_PREFIX_KEY_DOWN), + RESPONSE (LK_MODE_CHANGE_ACK), + RESPONSE (LK_RESPONSE_RESERVED), +#undef RESPONSE +}; + +static unsigned char * +response_name (unsigned char value) +{ + int i; + + for (i = 0; i < ARRAY_SIZE (lk_response); i++) + if (lk_response[i].value == value) + return lk_response[i].name; + + return "<unknown>"; +} +#endif /* LKKBD_DEBUG */ + +/* + * Calculate volume parameter byte for a given volume. + */ +static unsigned char +volume_to_hw (int volume_percent) +{ + unsigned char ret = 0; + + if (volume_percent < 0) + volume_percent = 0; + if (volume_percent > 100) + volume_percent = 100; + + if (volume_percent >= 0) + ret = 7; + if (volume_percent >= 13) /* 12.5 */ + ret = 6; + if (volume_percent >= 25) + ret = 5; + if (volume_percent >= 38) /* 37.5 */ + ret = 4; + if (volume_percent >= 50) + ret = 3; + if (volume_percent >= 63) /* 62.5 */ + ret = 2; /* This is the default volume */ + if (volume_percent >= 75) + ret = 1; + if (volume_percent >= 88) /* 87.5 */ + ret = 0; + + ret |= 0x80; + + return ret; +} + +static void +lkkbd_detection_done (struct lkkbd *lk) +{ + int i; + + /* + * Reset setting for Compose key. Let Compose be KEY_COMPOSE. + */ + lk->keycode[0xb1] = KEY_COMPOSE; + + /* + * Print keyboard name and modify Compose=Alt on user's request. + */ + switch (lk->id[4]) { + case 1: + strlcpy (lk->name, "DEC LK201 keyboard", + sizeof (lk->name)); + + if (lk201_compose_is_alt) + lk->keycode[0xb1] = KEY_LEFTALT; + break; + + case 2: + strlcpy (lk->name, "DEC LK401 keyboard", + sizeof (lk->name)); + break; + + default: + strlcpy (lk->name, "Unknown DEC keyboard", + sizeof (lk->name)); + printk (KERN_ERR "lkkbd: keyboard on %s is unknown, " + "please report to Jan-Benedict Glaw " + "<jbglaw@lug-owl.de>\n", lk->phys); + printk (KERN_ERR "lkkbd: keyboard ID'ed as:"); + for (i = 0; i < LK_NUM_IGNORE_BYTES; i++) + printk (" 0x%02x", lk->id[i]); + printk ("\n"); + break; + } + printk (KERN_INFO "lkkbd: keyboard on %s identified as: %s\n", + lk->phys, lk->name); + + /* + * Report errors during keyboard boot-up. + */ + switch (lk->id[2]) { + case 0x00: + /* All okay */ + break; + + case LK_STUCK_KEY: + printk (KERN_ERR "lkkbd: Stuck key on keyboard at " + "%s\n", lk->phys); + break; + + case LK_SELFTEST_FAILED: + printk (KERN_ERR "lkkbd: Selftest failed on keyboard " + "at %s, keyboard may not work " + "properly\n", lk->phys); + break; + + default: + printk (KERN_ERR "lkkbd: Unknown error %02x on " + "keyboard at %s\n", lk->id[2], + lk->phys); + break; + } + + /* + * Try to hint user if there's a stuck key. + */ + if (lk->id[2] == LK_STUCK_KEY && lk->id[3] != 0) + printk (KERN_ERR "Scancode of stuck key is 0x%02x, keycode " + "is 0x%04x\n", lk->id[3], + lk->keycode[lk->id[3]]); + + return; +} + +/* + * lkkbd_interrupt() is called by the low level driver when a character + * is received. + */ +static irqreturn_t +lkkbd_interrupt (struct serio *serio, unsigned char data, unsigned int flags) +{ + struct lkkbd *lk = serio_get_drvdata (serio); + int i; + + DBG (KERN_INFO "Got byte 0x%02x\n", data); + + if (lk->ignore_bytes > 0) { + DBG (KERN_INFO "Ignoring a byte on %s\n", lk->name); + lk->id[LK_NUM_IGNORE_BYTES - lk->ignore_bytes--] = data; + + if (lk->ignore_bytes == 0) + lkkbd_detection_done (lk); + + return IRQ_HANDLED; + } + + switch (data) { + case LK_ALL_KEYS_UP: + for (i = 0; i < ARRAY_SIZE (lkkbd_keycode); i++) + if (lk->keycode[i] != KEY_RESERVED) + input_report_key (lk->dev, lk->keycode[i], 0); + input_sync (lk->dev); + break; + + case 0x01: + DBG (KERN_INFO "Got 0x01, scheduling re-initialization\n"); + lk->ignore_bytes = LK_NUM_IGNORE_BYTES; + lk->id[LK_NUM_IGNORE_BYTES - lk->ignore_bytes--] = data; + schedule_work (&lk->tq); + break; + + case LK_METRONOME: + case LK_OUTPUT_ERROR: + case LK_INPUT_ERROR: + case LK_KBD_LOCKED: + case LK_KBD_TEST_MODE_ACK: + case LK_PREFIX_KEY_DOWN: + case LK_MODE_CHANGE_ACK: + case LK_RESPONSE_RESERVED: + DBG (KERN_INFO "Got %s and don't know how to handle...\n", + response_name (data)); + break; + + default: + if (lk->keycode[data] != KEY_RESERVED) { + if (!test_bit (lk->keycode[data], lk->dev->key)) + input_report_key (lk->dev, lk->keycode[data], 1); + else + input_report_key (lk->dev, lk->keycode[data], 0); + input_sync (lk->dev); + } else + printk (KERN_WARNING "%s: Unknown key with " + "scancode 0x%02x on %s.\n", + __FILE__, data, lk->name); + } + + return IRQ_HANDLED; +} + +/* + * lkkbd_event() handles events from the input module. + */ +static int +lkkbd_event (struct input_dev *dev, unsigned int type, unsigned int code, + int value) +{ + struct lkkbd *lk = input_get_drvdata (dev); + unsigned char leds_on = 0; + unsigned char leds_off = 0; + + switch (type) { + case EV_LED: + CHECK_LED (lk, leds_on, leds_off, LED_CAPSL, LK_LED_SHIFTLOCK); + CHECK_LED (lk, leds_on, leds_off, LED_COMPOSE, LK_LED_COMPOSE); + CHECK_LED (lk, leds_on, leds_off, LED_SCROLLL, LK_LED_SCROLLLOCK); + CHECK_LED (lk, leds_on, leds_off, LED_SLEEP, LK_LED_WAIT); + if (leds_on != 0) { + lk->serio->write (lk->serio, LK_CMD_LED_ON); + lk->serio->write (lk->serio, leds_on); + } + if (leds_off != 0) { + lk->serio->write (lk->serio, LK_CMD_LED_OFF); + lk->serio->write (lk->serio, leds_off); + } + return 0; + + case EV_SND: + switch (code) { + case SND_CLICK: + if (value == 0) { + DBG ("%s: Deactivating key clicks\n", __func__); + lk->serio->write (lk->serio, LK_CMD_DISABLE_KEYCLICK); + lk->serio->write (lk->serio, LK_CMD_DISABLE_CTRCLICK); + } else { + DBG ("%s: Activating key clicks\n", __func__); + lk->serio->write (lk->serio, LK_CMD_ENABLE_KEYCLICK); + lk->serio->write (lk->serio, volume_to_hw (lk->keyclick_volume)); + lk->serio->write (lk->serio, LK_CMD_ENABLE_CTRCLICK); + lk->serio->write (lk->serio, volume_to_hw (lk->ctrlclick_volume)); + } + return 0; + + case SND_BELL: + if (value != 0) + lk->serio->write (lk->serio, LK_CMD_SOUND_BELL); + + return 0; + } + break; + + default: + printk (KERN_ERR "%s (): Got unknown type %d, code %d, value %d\n", + __func__, type, code, value); + } + + return -1; +} + +/* + * lkkbd_reinit() sets leds and beeps to a state the computer remembers they + * were in. + */ +static void +lkkbd_reinit (struct work_struct *work) +{ + struct lkkbd *lk = container_of(work, struct lkkbd, tq); + int division; + unsigned char leds_on = 0; + unsigned char leds_off = 0; + + /* Ask for ID */ + lk->serio->write (lk->serio, LK_CMD_REQUEST_ID); + + /* Reset parameters */ + lk->serio->write (lk->serio, LK_CMD_SET_DEFAULTS); + + /* Set LEDs */ + CHECK_LED (lk, leds_on, leds_off, LED_CAPSL, LK_LED_SHIFTLOCK); + CHECK_LED (lk, leds_on, leds_off, LED_COMPOSE, LK_LED_COMPOSE); + CHECK_LED (lk, leds_on, leds_off, LED_SCROLLL, LK_LED_SCROLLLOCK); + CHECK_LED (lk, leds_on, leds_off, LED_SLEEP, LK_LED_WAIT); + if (leds_on != 0) { + lk->serio->write (lk->serio, LK_CMD_LED_ON); + lk->serio->write (lk->serio, leds_on); + } + if (leds_off != 0) { + lk->serio->write (lk->serio, LK_CMD_LED_OFF); + lk->serio->write (lk->serio, leds_off); + } + + /* + * Try to activate extended LK401 mode. This command will + * only work with a LK401 keyboard and grants access to + * LAlt, RAlt, RCompose and RShift. + */ + lk->serio->write (lk->serio, LK_CMD_ENABLE_LK401); + + /* Set all keys to UPDOWN mode */ + for (division = 1; division <= 14; division++) + lk->serio->write (lk->serio, LK_CMD_SET_MODE (LK_MODE_UPDOWN, + division)); + + /* Enable bell and set volume */ + lk->serio->write (lk->serio, LK_CMD_ENABLE_BELL); + lk->serio->write (lk->serio, volume_to_hw (lk->bell_volume)); + + /* Enable/disable keyclick (and possibly set volume) */ + if (test_bit (SND_CLICK, lk->dev->snd)) { + lk->serio->write (lk->serio, LK_CMD_ENABLE_KEYCLICK); + lk->serio->write (lk->serio, volume_to_hw (lk->keyclick_volume)); + lk->serio->write (lk->serio, LK_CMD_ENABLE_CTRCLICK); + lk->serio->write (lk->serio, volume_to_hw (lk->ctrlclick_volume)); + } else { + lk->serio->write (lk->serio, LK_CMD_DISABLE_KEYCLICK); + lk->serio->write (lk->serio, LK_CMD_DISABLE_CTRCLICK); + } + + /* Sound the bell if needed */ + if (test_bit (SND_BELL, lk->dev->snd)) + lk->serio->write (lk->serio, LK_CMD_SOUND_BELL); +} + +/* + * lkkbd_connect() probes for a LK keyboard and fills the necessary structures. + */ +static int +lkkbd_connect (struct serio *serio, struct serio_driver *drv) +{ + struct lkkbd *lk; + struct input_dev *input_dev; + int i; + int err; + + lk = kzalloc (sizeof (struct lkkbd), GFP_KERNEL); + input_dev = input_allocate_device (); + if (!lk || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + lk->serio = serio; + lk->dev = input_dev; + INIT_WORK (&lk->tq, lkkbd_reinit); + lk->bell_volume = bell_volume; + lk->keyclick_volume = keyclick_volume; + lk->ctrlclick_volume = ctrlclick_volume; + memcpy (lk->keycode, lkkbd_keycode, sizeof (lk_keycode_t) * LK_NUM_KEYCODES); + + strlcpy (lk->name, "DEC LK keyboard", sizeof(lk->name)); + snprintf (lk->phys, sizeof(lk->phys), "%s/input0", serio->phys); + + input_dev->name = lk->name; + input_dev->phys = lk->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_LKKBD; + input_dev->id.product = 0; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + input_dev->event = lkkbd_event; + + input_set_drvdata (input_dev, lk); + + set_bit (EV_KEY, input_dev->evbit); + set_bit (EV_LED, input_dev->evbit); + set_bit (EV_SND, input_dev->evbit); + set_bit (EV_REP, input_dev->evbit); + set_bit (LED_CAPSL, input_dev->ledbit); + set_bit (LED_SLEEP, input_dev->ledbit); + set_bit (LED_COMPOSE, input_dev->ledbit); + set_bit (LED_SCROLLL, input_dev->ledbit); + set_bit (SND_BELL, input_dev->sndbit); + set_bit (SND_CLICK, input_dev->sndbit); + + input_dev->keycode = lk->keycode; + input_dev->keycodesize = sizeof (lk_keycode_t); + input_dev->keycodemax = LK_NUM_KEYCODES; + for (i = 0; i < LK_NUM_KEYCODES; i++) + set_bit (lk->keycode[i], input_dev->keybit); + + serio_set_drvdata (serio, lk); + + err = serio_open (serio, drv); + if (err) + goto fail2; + + err = input_register_device (lk->dev); + if (err) + goto fail3; + + lk->serio->write (lk->serio, LK_CMD_POWERCYCLE_RESET); + + return 0; + + fail3: serio_close (serio); + fail2: serio_set_drvdata (serio, NULL); + fail1: input_free_device (input_dev); + kfree (lk); + return err; +} + +/* + * lkkbd_disconnect() unregisters and closes behind us. + */ +static void +lkkbd_disconnect (struct serio *serio) +{ + struct lkkbd *lk = serio_get_drvdata (serio); + + input_get_device (lk->dev); + input_unregister_device (lk->dev); + serio_close (serio); + serio_set_drvdata (serio, NULL); + input_put_device (lk->dev); + kfree (lk); +} + +static struct serio_device_id lkkbd_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_LKKBD, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, lkkbd_serio_ids); + +static struct serio_driver lkkbd_drv = { + .driver = { + .name = "lkkbd", + }, + .description = DRIVER_DESC, + .id_table = lkkbd_serio_ids, + .connect = lkkbd_connect, + .disconnect = lkkbd_disconnect, + .interrupt = lkkbd_interrupt, +}; + +/* + * The functions for insering/removing us as a module. + */ +static int __init +lkkbd_init (void) +{ + return serio_register_driver(&lkkbd_drv); +} + +static void __exit +lkkbd_exit (void) +{ + serio_unregister_driver(&lkkbd_drv); +} + +module_init (lkkbd_init); +module_exit (lkkbd_exit); + diff --git a/drivers/input/keyboard/locomokbd.c b/drivers/input/keyboard/locomokbd.c new file mode 100644 index 0000000..9caed30 --- /dev/null +++ b/drivers/input/keyboard/locomokbd.c @@ -0,0 +1,332 @@ +/* + * LoCoMo keyboard driver for Linux-based ARM PDAs: + * - SHARP Zaurus Collie (SL-5500) + * - SHARP Zaurus Poodle (SL-5600) + * + * Copyright (c) 2005 John Lenz + * Based on from xtkbd.c + * + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> + +#include <asm/hardware/locomo.h> +#include <asm/irq.h> + +MODULE_AUTHOR("John Lenz <lenz@cs.wisc.edu>"); +MODULE_DESCRIPTION("LoCoMo keyboard driver"); +MODULE_LICENSE("GPL"); + +#define LOCOMOKBD_NUMKEYS 128 + +#define KEY_ACTIVITY KEY_F16 +#define KEY_CONTACT KEY_F18 +#define KEY_CENTER KEY_F15 + +static const unsigned char +locomokbd_keycode[LOCOMOKBD_NUMKEYS] __devinitconst = { + 0, KEY_ESC, KEY_ACTIVITY, 0, 0, 0, 0, 0, 0, 0, /* 0 - 9 */ + 0, 0, 0, 0, 0, 0, 0, KEY_MENU, KEY_HOME, KEY_CONTACT, /* 10 - 19 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20 - 29 */ + 0, 0, 0, KEY_CENTER, 0, KEY_MAIL, 0, 0, 0, 0, /* 30 - 39 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_RIGHT, /* 40 - 49 */ + KEY_UP, KEY_LEFT, 0, 0, KEY_P, 0, KEY_O, KEY_I, KEY_Y, KEY_T, /* 50 - 59 */ + KEY_E, KEY_W, 0, 0, 0, 0, KEY_DOWN, KEY_ENTER, 0, 0, /* 60 - 69 */ + KEY_BACKSPACE, 0, KEY_L, KEY_U, KEY_H, KEY_R, KEY_D, KEY_Q, 0, 0, /* 70 - 79 */ + 0, 0, 0, 0, 0, 0, KEY_ENTER, KEY_RIGHTSHIFT, KEY_K, KEY_J, /* 80 - 89 */ + KEY_G, KEY_F, KEY_X, KEY_S, 0, 0, 0, 0, 0, 0, /* 90 - 99 */ + 0, 0, KEY_DOT, 0, KEY_COMMA, KEY_N, KEY_B, KEY_C, KEY_Z, KEY_A, /* 100 - 109 */ + KEY_LEFTSHIFT, KEY_TAB, KEY_LEFTCTRL, 0, 0, 0, 0, 0, 0, 0, /* 110 - 119 */ + KEY_M, KEY_SPACE, KEY_V, KEY_APOSTROPHE, KEY_SLASH, 0, 0, 0 /* 120 - 128 */ +}; + +#define KB_ROWS 16 +#define KB_COLS 8 +#define KB_ROWMASK(r) (1 << (r)) +#define SCANCODE(c,r) ( ((c)<<4) + (r) + 1 ) + +#define KB_DELAY 8 +#define SCAN_INTERVAL (HZ/10) + +struct locomokbd { + unsigned char keycode[LOCOMOKBD_NUMKEYS]; + struct input_dev *input; + char phys[32]; + + unsigned long base; + spinlock_t lock; + + struct timer_list timer; + unsigned long suspend_jiffies; + unsigned int count_cancel; +}; + +/* helper functions for reading the keyboard matrix */ +static inline void locomokbd_charge_all(unsigned long membase) +{ + locomo_writel(0x00FF, membase + LOCOMO_KSC); +} + +static inline void locomokbd_activate_all(unsigned long membase) +{ + unsigned long r; + + locomo_writel(0, membase + LOCOMO_KSC); + r = locomo_readl(membase + LOCOMO_KIC); + r &= 0xFEFF; + locomo_writel(r, membase + LOCOMO_KIC); +} + +static inline void locomokbd_activate_col(unsigned long membase, int col) +{ + unsigned short nset; + unsigned short nbset; + + nset = 0xFF & ~(1 << col); + nbset = (nset << 8) + nset; + locomo_writel(nbset, membase + LOCOMO_KSC); +} + +static inline void locomokbd_reset_col(unsigned long membase, int col) +{ + unsigned short nbset; + + nbset = ((0xFF & ~(1 << col)) << 8) + 0xFF; + locomo_writel(nbset, membase + LOCOMO_KSC); +} + +/* + * The LoCoMo keyboard only generates interrupts when a key is pressed. + * So when a key is pressed, we enable a timer. This timer scans the + * keyboard, and this is how we detect when the key is released. + */ + +/* Scan the hardware keyboard and push any changes up through the input layer */ +static void locomokbd_scankeyboard(struct locomokbd *locomokbd) +{ + unsigned int row, col, rowd; + unsigned long flags; + unsigned int num_pressed; + unsigned long membase = locomokbd->base; + + spin_lock_irqsave(&locomokbd->lock, flags); + + locomokbd_charge_all(membase); + + num_pressed = 0; + for (col = 0; col < KB_COLS; col++) { + + locomokbd_activate_col(membase, col); + udelay(KB_DELAY); + + rowd = ~locomo_readl(membase + LOCOMO_KIB); + for (row = 0; row < KB_ROWS; row++) { + unsigned int scancode, pressed, key; + + scancode = SCANCODE(col, row); + pressed = rowd & KB_ROWMASK(row); + key = locomokbd->keycode[scancode]; + + input_report_key(locomokbd->input, key, pressed); + if (likely(!pressed)) + continue; + + num_pressed++; + + /* The "Cancel/ESC" key is labeled "On/Off" on + * Collie and Poodle and should suspend the device + * if it was pressed for more than a second. */ + if (unlikely(key == KEY_ESC)) { + if (!time_after(jiffies, + locomokbd->suspend_jiffies + HZ)) + continue; + if (locomokbd->count_cancel++ + != (HZ/SCAN_INTERVAL + 1)) + continue; + input_event(locomokbd->input, EV_PWR, + KEY_SUSPEND, 1); + locomokbd->suspend_jiffies = jiffies; + } else + locomokbd->count_cancel = 0; + } + locomokbd_reset_col(membase, col); + } + locomokbd_activate_all(membase); + + input_sync(locomokbd->input); + + /* if any keys are pressed, enable the timer */ + if (num_pressed) + mod_timer(&locomokbd->timer, jiffies + SCAN_INTERVAL); + else + locomokbd->count_cancel = 0; + + spin_unlock_irqrestore(&locomokbd->lock, flags); +} + +/* + * LoCoMo keyboard interrupt handler. + */ +static irqreturn_t locomokbd_interrupt(int irq, void *dev_id) +{ + struct locomokbd *locomokbd = dev_id; + /** wait chattering delay **/ + udelay(100); + + locomokbd_scankeyboard(locomokbd); + + return IRQ_HANDLED; +} + +/* + * LoCoMo timer checking for released keys + */ +static void locomokbd_timer_callback(unsigned long data) +{ + struct locomokbd *locomokbd = (struct locomokbd *) data; + + locomokbd_scankeyboard(locomokbd); +} + +static int __devinit locomokbd_probe(struct locomo_dev *dev) +{ + struct locomokbd *locomokbd; + struct input_dev *input_dev; + int i, err; + + locomokbd = kzalloc(sizeof(struct locomokbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!locomokbd || !input_dev) { + err = -ENOMEM; + goto err_free_mem; + } + + /* try and claim memory region */ + if (!request_mem_region((unsigned long) dev->mapbase, + dev->length, + LOCOMO_DRIVER_NAME(dev))) { + err = -EBUSY; + printk(KERN_ERR "locomokbd: Can't acquire access to io memory for keyboard\n"); + goto err_free_mem; + } + + locomo_set_drvdata(dev, locomokbd); + + locomokbd->base = (unsigned long) dev->mapbase; + + spin_lock_init(&locomokbd->lock); + + init_timer(&locomokbd->timer); + locomokbd->timer.function = locomokbd_timer_callback; + locomokbd->timer.data = (unsigned long) locomokbd; + + locomokbd->suspend_jiffies = jiffies; + + locomokbd->input = input_dev; + strcpy(locomokbd->phys, "locomokbd/input0"); + + input_dev->name = "LoCoMo keyboard"; + input_dev->phys = locomokbd->phys; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &dev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | + BIT_MASK(EV_PWR); + input_dev->keycode = locomokbd->keycode; + input_dev->keycodesize = sizeof(locomokbd_keycode[0]); + input_dev->keycodemax = ARRAY_SIZE(locomokbd_keycode); + + memcpy(locomokbd->keycode, locomokbd_keycode, sizeof(locomokbd->keycode)); + for (i = 0; i < LOCOMOKBD_NUMKEYS; i++) + set_bit(locomokbd->keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + + /* attempt to get the interrupt */ + err = request_irq(dev->irq[0], locomokbd_interrupt, 0, "locomokbd", locomokbd); + if (err) { + printk(KERN_ERR "locomokbd: Can't get irq for keyboard\n"); + goto err_release_region; + } + + err = input_register_device(locomokbd->input); + if (err) + goto err_free_irq; + + return 0; + + err_free_irq: + free_irq(dev->irq[0], locomokbd); + err_release_region: + release_mem_region((unsigned long) dev->mapbase, dev->length); + locomo_set_drvdata(dev, NULL); + err_free_mem: + input_free_device(input_dev); + kfree(locomokbd); + + return err; +} + +static int __devexit locomokbd_remove(struct locomo_dev *dev) +{ + struct locomokbd *locomokbd = locomo_get_drvdata(dev); + + free_irq(dev->irq[0], locomokbd); + + del_timer_sync(&locomokbd->timer); + + input_unregister_device(locomokbd->input); + locomo_set_drvdata(dev, NULL); + + release_mem_region((unsigned long) dev->mapbase, dev->length); + + kfree(locomokbd); + + return 0; +} + +static struct locomo_driver keyboard_driver = { + .drv = { + .name = "locomokbd" + }, + .devid = LOCOMO_DEVID_KEYBOARD, + .probe = locomokbd_probe, + .remove = __devexit_p(locomokbd_remove), +}; + +static int __init locomokbd_init(void) +{ + return locomo_driver_register(&keyboard_driver); +} + +static void __exit locomokbd_exit(void) +{ + locomo_driver_unregister(&keyboard_driver); +} + +module_init(locomokbd_init); +module_exit(locomokbd_exit); diff --git a/drivers/input/keyboard/maple_keyb.c b/drivers/input/keyboard/maple_keyb.c new file mode 100644 index 0000000..22f17a5 --- /dev/null +++ b/drivers/input/keyboard/maple_keyb.c @@ -0,0 +1,253 @@ +/* + * SEGA Dreamcast keyboard driver + * Based on drivers/usb/usbkbd.c + * Copyright YAEGASHI Takeshi, 2001 + * Porting to 2.6 Copyright Adrian McMenamin, 2007, 2008 + * + * 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, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/maple.h> + +/* Very simple mutex to ensure proper cleanup */ +static DEFINE_MUTEX(maple_keyb_mutex); + +#define NR_SCANCODES 256 + +MODULE_AUTHOR("YAEGASHI Takeshi, Adrian McMenamin"); +MODULE_DESCRIPTION("SEGA Dreamcast keyboard driver"); +MODULE_LICENSE("GPL"); + +struct dc_kbd { + struct input_dev *dev; + unsigned short keycode[NR_SCANCODES]; + unsigned char new[8]; + unsigned char old[8]; +}; + +static const unsigned short dc_kbd_keycode[NR_SCANCODES] = { + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_A, KEY_B, + KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, + KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, + KEY_W, KEY_X, KEY_Y, KEY_Z, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, + KEY_7, KEY_8, KEY_9, KEY_0, KEY_ENTER, KEY_ESC, KEY_BACKSPACE, + KEY_TAB, KEY_SPACE, KEY_MINUS, KEY_EQUAL, KEY_LEFTBRACE, + KEY_RIGHTBRACE, KEY_BACKSLASH, KEY_BACKSLASH, KEY_SEMICOLON, + KEY_APOSTROPHE, KEY_GRAVE, KEY_COMMA, KEY_DOT, KEY_SLASH, + KEY_CAPSLOCK, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, + KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_SYSRQ, + KEY_SCROLLLOCK, KEY_PAUSE, KEY_INSERT, KEY_HOME, KEY_PAGEUP, + KEY_DELETE, KEY_END, KEY_PAGEDOWN, KEY_RIGHT, KEY_LEFT, KEY_DOWN, + KEY_UP, KEY_NUMLOCK, KEY_KPSLASH, KEY_KPASTERISK, KEY_KPMINUS, + KEY_KPPLUS, KEY_KPENTER, KEY_KP1, KEY_KP2, KEY_KP3, KEY_KP4, KEY_KP5, + KEY_KP6, KEY_KP7, KEY_KP8, KEY_KP9, KEY_KP0, KEY_KPDOT, KEY_102ND, + KEY_COMPOSE, KEY_POWER, KEY_KPEQUAL, KEY_F13, KEY_F14, KEY_F15, + KEY_F16, KEY_F17, KEY_F18, KEY_F19, KEY_F20, KEY_F21, KEY_F22, + KEY_F23, KEY_F24, KEY_OPEN, KEY_HELP, KEY_PROPS, KEY_FRONT, KEY_STOP, + KEY_AGAIN, KEY_UNDO, KEY_CUT, KEY_COPY, KEY_PASTE, KEY_FIND, KEY_MUTE, + KEY_VOLUMEUP, KEY_VOLUMEDOWN, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_KPCOMMA, KEY_RESERVED, KEY_RO, KEY_KATAKANAHIRAGANA , KEY_YEN, + KEY_HENKAN, KEY_MUHENKAN, KEY_KPJPCOMMA, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_HANGEUL, KEY_HANJA, KEY_KATAKANA, KEY_HIRAGANA, + KEY_ZENKAKUHANKAKU, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_LEFTCTRL, KEY_LEFTSHIFT, KEY_LEFTALT, + KEY_LEFTMETA, KEY_RIGHTCTRL, KEY_RIGHTSHIFT, KEY_RIGHTALT, + KEY_RIGHTMETA, KEY_PLAYPAUSE, KEY_STOPCD, KEY_PREVIOUSSONG, + KEY_NEXTSONG, KEY_EJECTCD, KEY_VOLUMEUP, KEY_VOLUMEDOWN, KEY_MUTE, + KEY_WWW, KEY_BACK, KEY_FORWARD, KEY_STOP, KEY_FIND, KEY_SCROLLUP, + KEY_SCROLLDOWN, KEY_EDIT, KEY_SLEEP, KEY_SCREENLOCK, KEY_REFRESH, + KEY_CALC, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED +}; + +static void dc_scan_kbd(struct dc_kbd *kbd) +{ + struct input_dev *dev = kbd->dev; + void *ptr; + int code, keycode; + int i; + + for (i = 0; i < 8; i++) { + code = i + 224; + keycode = kbd->keycode[code]; + input_event(dev, EV_MSC, MSC_SCAN, code); + input_report_key(dev, keycode, (kbd->new[0] >> i) & 1); + } + + for (i = 2; i < 8; i++) { + ptr = memchr(kbd->new + 2, kbd->old[i], 6); + code = kbd->old[i]; + if (code > 3 && ptr == NULL) { + keycode = kbd->keycode[code]; + if (keycode) { + input_event(dev, EV_MSC, MSC_SCAN, code); + input_report_key(dev, keycode, 0); + } else + printk(KERN_DEBUG "maple_keyb: " + "Unknown key (scancode %#x) released.", + code); + } + ptr = memchr(kbd->old + 2, kbd->new[i], 6); + code = kbd->new[i]; + if (code > 3 && ptr) { + keycode = kbd->keycode[code]; + if (keycode) { + input_event(dev, EV_MSC, MSC_SCAN, code); + input_report_key(dev, keycode, 1); + } else + printk(KERN_DEBUG "maple_keyb: " + "Unknown key (scancode %#x) pressed.", + code); + } + } + input_sync(dev); + memcpy(kbd->old, kbd->new, 8); +} + +static void dc_kbd_callback(struct mapleq *mq) +{ + struct maple_device *mapledev = mq->dev; + struct dc_kbd *kbd = maple_get_drvdata(mapledev); + unsigned long *buf = mq->recvbuf; + + /* + * We should always get the lock because the only + * time it may be locked is if the driver is in the cleanup phase. + */ + if (likely(mutex_trylock(&maple_keyb_mutex))) { + + if (buf[1] == mapledev->function) { + memcpy(kbd->new, buf + 2, 8); + dc_scan_kbd(kbd); + } + + mutex_unlock(&maple_keyb_mutex); + } +} + +static int probe_maple_kbd(struct device *dev) +{ + struct maple_device *mdev = to_maple_dev(dev); + struct maple_driver *mdrv = to_maple_driver(dev->driver); + int i, error; + struct dc_kbd *kbd; + struct input_dev *idev; + + if (!(mdev->function & MAPLE_FUNC_KEYBOARD)) + return -EINVAL; + + kbd = kzalloc(sizeof(struct dc_kbd), GFP_KERNEL); + idev = input_allocate_device(); + if (!kbd || !idev) { + error = -ENOMEM; + goto fail; + } + + kbd->dev = idev; + memcpy(kbd->keycode, dc_kbd_keycode, sizeof(kbd->keycode)); + + idev->name = mdev->product_name; + idev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + idev->keycode = kbd->keycode; + idev->keycodesize = sizeof(unsigned short); + idev->keycodemax = ARRAY_SIZE(kbd->keycode); + idev->id.bustype = BUS_HOST; + idev->dev.parent = &mdev->dev; + + for (i = 0; i < NR_SCANCODES; i++) + __set_bit(dc_kbd_keycode[i], idev->keybit); + __clear_bit(KEY_RESERVED, idev->keybit); + + input_set_capability(idev, EV_MSC, MSC_SCAN); + input_set_drvdata(idev, kbd); + + error = input_register_device(idev); + if (error) + goto fail; + + /* Maple polling is locked to VBLANK - which may be just 50/s */ + maple_getcond_callback(mdev, dc_kbd_callback, HZ/50, + MAPLE_FUNC_KEYBOARD); + + mdev->driver = mdrv; + + maple_set_drvdata(mdev, kbd); + + return error; + +fail: + input_free_device(idev); + kfree(kbd); + maple_set_drvdata(mdev, NULL); + return error; +} + +static int remove_maple_kbd(struct device *dev) +{ + struct maple_device *mdev = to_maple_dev(dev); + struct dc_kbd *kbd = maple_get_drvdata(mdev); + + mutex_lock(&maple_keyb_mutex); + + input_unregister_device(kbd->dev); + kfree(kbd); + + maple_set_drvdata(mdev, NULL); + + mutex_unlock(&maple_keyb_mutex); + return 0; +} + +static struct maple_driver dc_kbd_driver = { + .function = MAPLE_FUNC_KEYBOARD, + .drv = { + .name = "Dreamcast_keyboard", + .probe = probe_maple_kbd, + .remove = remove_maple_kbd, + }, +}; + +static int __init dc_kbd_init(void) +{ + return maple_driver_register(&dc_kbd_driver); +} + +static void __exit dc_kbd_exit(void) +{ + maple_driver_unregister(&dc_kbd_driver); +} + +module_init(dc_kbd_init); +module_exit(dc_kbd_exit); diff --git a/drivers/input/keyboard/newtonkbd.c b/drivers/input/keyboard/newtonkbd.c new file mode 100644 index 0000000..48d1cab --- /dev/null +++ b/drivers/input/keyboard/newtonkbd.c @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2000 Justin Cormack + */ + +/* + * Newton keyboard driver for Linux + */ + +/* + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to <j.cormack@doc.ic.ac.uk>, or by paper mail: + * Justin Cormack, 68 Dartmouth Park Road, London NW5 1SN, UK. + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/serio.h> + +#define DRIVER_DESC "Newton keyboard driver" + +MODULE_AUTHOR("Justin Cormack <j.cormack@doc.ic.ac.uk>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define NKBD_KEY 0x7f +#define NKBD_PRESS 0x80 + +static unsigned char nkbd_keycode[128] = { + KEY_A, KEY_S, KEY_D, KEY_F, KEY_H, KEY_G, KEY_Z, KEY_X, + KEY_C, KEY_V, 0, KEY_B, KEY_Q, KEY_W, KEY_E, KEY_R, + KEY_Y, KEY_T, KEY_1, KEY_2, KEY_3, KEY_4, KEY_6, KEY_5, + KEY_EQUAL, KEY_9, KEY_7, KEY_MINUS, KEY_8, KEY_0, KEY_RIGHTBRACE, KEY_O, + KEY_U, KEY_LEFTBRACE, KEY_I, KEY_P, KEY_ENTER, KEY_L, KEY_J, KEY_APOSTROPHE, + KEY_K, KEY_SEMICOLON, KEY_BACKSLASH, KEY_COMMA, KEY_SLASH, KEY_N, KEY_M, KEY_DOT, + KEY_TAB, KEY_SPACE, KEY_GRAVE, KEY_DELETE, 0, 0, 0, KEY_LEFTMETA, + KEY_LEFTSHIFT, KEY_CAPSLOCK, KEY_LEFTALT, KEY_LEFTCTRL, KEY_RIGHTSHIFT, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + KEY_LEFT, KEY_RIGHT, KEY_DOWN, KEY_UP, 0 +}; + +struct nkbd { + unsigned char keycode[128]; + struct input_dev *dev; + struct serio *serio; + char phys[32]; +}; + +static irqreturn_t nkbd_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct nkbd *nkbd = serio_get_drvdata(serio); + + /* invalid scan codes are probably the init sequence, so we ignore them */ + if (nkbd->keycode[data & NKBD_KEY]) { + input_report_key(nkbd->dev, nkbd->keycode[data & NKBD_KEY], data & NKBD_PRESS); + input_sync(nkbd->dev); + } + + else if (data == 0xe7) /* end of init sequence */ + printk(KERN_INFO "input: %s on %s\n", nkbd->dev->name, serio->phys); + return IRQ_HANDLED; + +} + +static int nkbd_connect(struct serio *serio, struct serio_driver *drv) +{ + struct nkbd *nkbd; + struct input_dev *input_dev; + int err = -ENOMEM; + int i; + + nkbd = kzalloc(sizeof(struct nkbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!nkbd || !input_dev) + goto fail1; + + nkbd->serio = serio; + nkbd->dev = input_dev; + snprintf(nkbd->phys, sizeof(nkbd->phys), "%s/input0", serio->phys); + memcpy(nkbd->keycode, nkbd_keycode, sizeof(nkbd->keycode)); + + input_dev->name = "Newton Keyboard"; + input_dev->phys = nkbd->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_NEWTON; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + input_dev->keycode = nkbd->keycode; + input_dev->keycodesize = sizeof(unsigned char); + input_dev->keycodemax = ARRAY_SIZE(nkbd_keycode); + for (i = 0; i < 128; i++) + set_bit(nkbd->keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + + serio_set_drvdata(serio, nkbd); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(nkbd->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(nkbd); + return err; +} + +static void nkbd_disconnect(struct serio *serio) +{ + struct nkbd *nkbd = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(nkbd->dev); + kfree(nkbd); +} + +static struct serio_device_id nkbd_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_NEWTON, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, nkbd_serio_ids); + +static struct serio_driver nkbd_drv = { + .driver = { + .name = "newtonkbd", + }, + .description = DRIVER_DESC, + .id_table = nkbd_serio_ids, + .interrupt = nkbd_interrupt, + .connect = nkbd_connect, + .disconnect = nkbd_disconnect, +}; + +static int __init nkbd_init(void) +{ + return serio_register_driver(&nkbd_drv); +} + +static void __exit nkbd_exit(void) +{ + serio_unregister_driver(&nkbd_drv); +} + +module_init(nkbd_init); +module_exit(nkbd_exit); diff --git a/drivers/input/keyboard/omap-keypad.c b/drivers/input/keyboard/omap-keypad.c new file mode 100644 index 0000000..69e674e --- /dev/null +++ b/drivers/input/keyboard/omap-keypad.c @@ -0,0 +1,487 @@ +/* + * linux/drivers/input/keyboard/omap-keypad.c + * + * OMAP Keypad Driver + * + * Copyright (C) 2003 Nokia Corporation + * Written by Timo Teräs <ext-timo.teras@nokia.com> + * + * Added support for H2 & H3 Keypad + * Copyright (C) 2004 Texas Instruments + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/types.h> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/errno.h> +#include <mach/gpio.h> +#include <mach/keypad.h> +#include <mach/menelaus.h> +#include <asm/irq.h> +#include <mach/hardware.h> +#include <asm/io.h> +#include <mach/mux.h> + +#undef NEW_BOARD_LEARNING_MODE + +static void omap_kp_tasklet(unsigned long); +static void omap_kp_timer(unsigned long); + +static unsigned char keypad_state[8]; +static DEFINE_MUTEX(kp_enable_mutex); +static int kp_enable = 1; +static int kp_cur_group = -1; + +struct omap_kp { + struct input_dev *input; + struct timer_list timer; + int irq; + unsigned int rows; + unsigned int cols; + unsigned long delay; + unsigned int debounce; +}; + +static DECLARE_TASKLET_DISABLED(kp_tasklet, omap_kp_tasklet, 0); + +static int *keymap; +static unsigned int *row_gpios; +static unsigned int *col_gpios; + +#ifdef CONFIG_ARCH_OMAP2 +static void set_col_gpio_val(struct omap_kp *omap_kp, u8 value) +{ + int col; + + for (col = 0; col < omap_kp->cols; col++) + gpio_set_value(col_gpios[col], value & (1 << col)); +} + +static u8 get_row_gpio_val(struct omap_kp *omap_kp) +{ + int row; + u8 value = 0; + + for (row = 0; row < omap_kp->rows; row++) { + if (gpio_get_value(row_gpios[row])) + value |= (1 << row); + } + return value; +} +#else +#define set_col_gpio_val(x, y) do {} while (0) +#define get_row_gpio_val(x) 0 +#endif + +static irqreturn_t omap_kp_interrupt(int irq, void *dev_id) +{ + struct omap_kp *omap_kp = dev_id; + + /* disable keyboard interrupt and schedule for handling */ + if (cpu_is_omap24xx()) { + int i; + for (i = 0; i < omap_kp->rows; i++) + disable_irq(OMAP_GPIO_IRQ(row_gpios[i])); + } else + /* disable keyboard interrupt and schedule for handling */ + omap_writew(1, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT); + + tasklet_schedule(&kp_tasklet); + + return IRQ_HANDLED; +} + +static void omap_kp_timer(unsigned long data) +{ + tasklet_schedule(&kp_tasklet); +} + +static void omap_kp_scan_keypad(struct omap_kp *omap_kp, unsigned char *state) +{ + int col = 0; + + /* read the keypad status */ + if (cpu_is_omap24xx()) { + int i; + for (i = 0; i < omap_kp->rows; i++) + disable_irq(OMAP_GPIO_IRQ(row_gpios[i])); + + /* read the keypad status */ + for (col = 0; col < omap_kp->cols; col++) { + set_col_gpio_val(omap_kp, ~(1 << col)); + state[col] = ~(get_row_gpio_val(omap_kp)) & 0x3f; + } + set_col_gpio_val(omap_kp, 0); + + } else { + /* disable keyboard interrupt and schedule for handling */ + omap_writew(1, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT); + + /* read the keypad status */ + omap_writew(0xff, OMAP_MPUIO_BASE + OMAP_MPUIO_KBC); + for (col = 0; col < omap_kp->cols; col++) { + omap_writew(~(1 << col) & 0xff, + OMAP_MPUIO_BASE + OMAP_MPUIO_KBC); + + udelay(omap_kp->delay); + + state[col] = ~omap_readw(OMAP_MPUIO_BASE + + OMAP_MPUIO_KBR_LATCH) & 0xff; + } + omap_writew(0x00, OMAP_MPUIO_BASE + OMAP_MPUIO_KBC); + udelay(2); + } +} + +static inline int omap_kp_find_key(int col, int row) +{ + int i, key; + + key = KEY(col, row, 0); + for (i = 0; keymap[i] != 0; i++) + if ((keymap[i] & 0xff000000) == key) + return keymap[i] & 0x00ffffff; + return -1; +} + +static void omap_kp_tasklet(unsigned long data) +{ + struct omap_kp *omap_kp_data = (struct omap_kp *) data; + unsigned char new_state[8], changed, key_down = 0; + int col, row; + int spurious = 0; + + /* check for any changes */ + omap_kp_scan_keypad(omap_kp_data, new_state); + + /* check for changes and print those */ + for (col = 0; col < omap_kp_data->cols; col++) { + changed = new_state[col] ^ keypad_state[col]; + key_down |= new_state[col]; + if (changed == 0) + continue; + + for (row = 0; row < omap_kp_data->rows; row++) { + int key; + if (!(changed & (1 << row))) + continue; +#ifdef NEW_BOARD_LEARNING_MODE + printk(KERN_INFO "omap-keypad: key %d-%d %s\n", col, + row, (new_state[col] & (1 << row)) ? + "pressed" : "released"); +#else + key = omap_kp_find_key(col, row); + if (key < 0) { + printk(KERN_WARNING + "omap-keypad: Spurious key event %d-%d\n", + col, row); + /* We scan again after a couple of seconds */ + spurious = 1; + continue; + } + + if (!(kp_cur_group == (key & GROUP_MASK) || + kp_cur_group == -1)) + continue; + + kp_cur_group = key & GROUP_MASK; + input_report_key(omap_kp_data->input, key & ~GROUP_MASK, + new_state[col] & (1 << row)); +#endif + } + } + memcpy(keypad_state, new_state, sizeof(keypad_state)); + + if (key_down) { + int delay = HZ / 20; + /* some key is pressed - keep irq disabled and use timer + * to poll the keypad */ + if (spurious) + delay = 2 * HZ; + mod_timer(&omap_kp_data->timer, jiffies + delay); + } else { + /* enable interrupts */ + if (cpu_is_omap24xx()) { + int i; + for (i = 0; i < omap_kp_data->rows; i++) + enable_irq(OMAP_GPIO_IRQ(row_gpios[i])); + } else { + omap_writew(0, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT); + kp_cur_group = -1; + } + } +} + +static ssize_t omap_kp_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%u\n", kp_enable); +} + +static ssize_t omap_kp_enable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int state; + + if (sscanf(buf, "%u", &state) != 1) + return -EINVAL; + + if ((state != 1) && (state != 0)) + return -EINVAL; + + mutex_lock(&kp_enable_mutex); + if (state != kp_enable) { + if (state) + enable_irq(INT_KEYBOARD); + else + disable_irq(INT_KEYBOARD); + kp_enable = state; + } + mutex_unlock(&kp_enable_mutex); + + return strnlen(buf, count); +} + +static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, omap_kp_enable_show, omap_kp_enable_store); + +#ifdef CONFIG_PM +static int omap_kp_suspend(struct platform_device *dev, pm_message_t state) +{ + /* Nothing yet */ + + return 0; +} + +static int omap_kp_resume(struct platform_device *dev) +{ + /* Nothing yet */ + + return 0; +} +#else +#define omap_kp_suspend NULL +#define omap_kp_resume NULL +#endif + +static int __init omap_kp_probe(struct platform_device *pdev) +{ + struct omap_kp *omap_kp; + struct input_dev *input_dev; + struct omap_kp_platform_data *pdata = pdev->dev.platform_data; + int i, col_idx, row_idx, irq_idx, ret; + + if (!pdata->rows || !pdata->cols || !pdata->keymap) { + printk(KERN_ERR "No rows, cols or keymap from pdata\n"); + return -EINVAL; + } + + omap_kp = kzalloc(sizeof(struct omap_kp), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!omap_kp || !input_dev) { + kfree(omap_kp); + input_free_device(input_dev); + return -ENOMEM; + } + + platform_set_drvdata(pdev, omap_kp); + + omap_kp->input = input_dev; + + /* Disable the interrupt for the MPUIO keyboard */ + if (!cpu_is_omap24xx()) + omap_writew(1, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT); + + keymap = pdata->keymap; + + if (pdata->rep) + __set_bit(EV_REP, input_dev->evbit); + + if (pdata->delay) + omap_kp->delay = pdata->delay; + + if (pdata->row_gpios && pdata->col_gpios) { + row_gpios = pdata->row_gpios; + col_gpios = pdata->col_gpios; + } + + omap_kp->rows = pdata->rows; + omap_kp->cols = pdata->cols; + + if (cpu_is_omap24xx()) { + /* Cols: outputs */ + for (col_idx = 0; col_idx < omap_kp->cols; col_idx++) { + if (gpio_request(col_gpios[col_idx], "omap_kp_col") < 0) { + printk(KERN_ERR "Failed to request" + "GPIO%d for keypad\n", + col_gpios[col_idx]); + goto err1; + } + gpio_direction_output(col_gpios[col_idx], 0); + } + /* Rows: inputs */ + for (row_idx = 0; row_idx < omap_kp->rows; row_idx++) { + if (gpio_request(row_gpios[row_idx], "omap_kp_row") < 0) { + printk(KERN_ERR "Failed to request" + "GPIO%d for keypad\n", + row_gpios[row_idx]); + goto err2; + } + gpio_direction_input(row_gpios[row_idx]); + } + } else { + col_idx = 0; + row_idx = 0; + } + + setup_timer(&omap_kp->timer, omap_kp_timer, (unsigned long)omap_kp); + + /* get the irq and init timer*/ + tasklet_enable(&kp_tasklet); + kp_tasklet.data = (unsigned long) omap_kp; + + ret = device_create_file(&pdev->dev, &dev_attr_enable); + if (ret < 0) + goto err2; + + /* setup input device */ + __set_bit(EV_KEY, input_dev->evbit); + for (i = 0; keymap[i] != 0; i++) + __set_bit(keymap[i] & KEY_MAX, input_dev->keybit); + input_dev->name = "omap-keypad"; + input_dev->phys = "omap-keypad/input0"; + input_dev->dev.parent = &pdev->dev; + + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + + ret = input_register_device(omap_kp->input); + if (ret < 0) { + printk(KERN_ERR "Unable to register omap-keypad input device\n"); + goto err3; + } + + if (pdata->dbounce) + omap_writew(0xff, OMAP_MPUIO_BASE + OMAP_MPUIO_GPIO_DEBOUNCING); + + /* scan current status and enable interrupt */ + omap_kp_scan_keypad(omap_kp, keypad_state); + if (!cpu_is_omap24xx()) { + omap_kp->irq = platform_get_irq(pdev, 0); + if (omap_kp->irq >= 0) { + if (request_irq(omap_kp->irq, omap_kp_interrupt, 0, + "omap-keypad", omap_kp) < 0) + goto err4; + } + omap_writew(0, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT); + } else { + for (irq_idx = 0; irq_idx < omap_kp->rows; irq_idx++) { + if (request_irq(OMAP_GPIO_IRQ(row_gpios[irq_idx]), + omap_kp_interrupt, + IRQF_TRIGGER_FALLING, + "omap-keypad", omap_kp) < 0) + goto err5; + } + } + return 0; +err5: + for (i = irq_idx - 1; i >=0; i--) + free_irq(row_gpios[i], 0); +err4: + input_unregister_device(omap_kp->input); + input_dev = NULL; +err3: + device_remove_file(&pdev->dev, &dev_attr_enable); +err2: + for (i = row_idx - 1; i >=0; i--) + gpio_free(row_gpios[i]); +err1: + for (i = col_idx - 1; i >=0; i--) + gpio_free(col_gpios[i]); + + kfree(omap_kp); + input_free_device(input_dev); + + return -EINVAL; +} + +static int omap_kp_remove(struct platform_device *pdev) +{ + struct omap_kp *omap_kp = platform_get_drvdata(pdev); + + /* disable keypad interrupt handling */ + tasklet_disable(&kp_tasklet); + if (cpu_is_omap24xx()) { + int i; + for (i = 0; i < omap_kp->cols; i++) + gpio_free(col_gpios[i]); + for (i = 0; i < omap_kp->rows; i++) { + gpio_free(row_gpios[i]); + free_irq(OMAP_GPIO_IRQ(row_gpios[i]), 0); + } + } else { + omap_writew(1, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT); + free_irq(omap_kp->irq, 0); + } + + del_timer_sync(&omap_kp->timer); + tasklet_kill(&kp_tasklet); + + /* unregister everything */ + input_unregister_device(omap_kp->input); + + kfree(omap_kp); + + return 0; +} + +static struct platform_driver omap_kp_driver = { + .probe = omap_kp_probe, + .remove = omap_kp_remove, + .suspend = omap_kp_suspend, + .resume = omap_kp_resume, + .driver = { + .name = "omap-keypad", + .owner = THIS_MODULE, + }, +}; + +static int __devinit omap_kp_init(void) +{ + printk(KERN_INFO "OMAP Keypad Driver\n"); + return platform_driver_register(&omap_kp_driver); +} + +static void __exit omap_kp_exit(void) +{ + platform_driver_unregister(&omap_kp_driver); +} + +module_init(omap_kp_init); +module_exit(omap_kp_exit); + +MODULE_AUTHOR("Timo Teräs"); +MODULE_DESCRIPTION("OMAP Keypad Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:omap-keypad"); diff --git a/drivers/input/keyboard/pxa27x_keypad.c b/drivers/input/keyboard/pxa27x_keypad.c new file mode 100644 index 0000000..6d30c6d --- /dev/null +++ b/drivers/input/keyboard/pxa27x_keypad.c @@ -0,0 +1,600 @@ +/* + * linux/drivers/input/keyboard/pxa27x_keypad.c + * + * Driver for the pxa27x matrix keyboard controller. + * + * Created: Feb 22, 2007 + * Author: Rodolfo Giometti <giometti@linux.it> + * + * Based on a previous implementations by Kevin O'Connor + * <kevin_at_koconnor.net> and Alex Osborne <bobofdoom@gmail.com> and + * on some suggestions by Nicolas Pitre <nico@cam.org>. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> + +#include <asm/mach/arch.h> +#include <asm/mach/map.h> + +#include <mach/hardware.h> +#include <mach/pxa27x_keypad.h> + +/* + * Keypad Controller registers + */ +#define KPC 0x0000 /* Keypad Control register */ +#define KPDK 0x0008 /* Keypad Direct Key register */ +#define KPREC 0x0010 /* Keypad Rotary Encoder register */ +#define KPMK 0x0018 /* Keypad Matrix Key register */ +#define KPAS 0x0020 /* Keypad Automatic Scan register */ + +/* Keypad Automatic Scan Multiple Key Presser register 0-3 */ +#define KPASMKP0 0x0028 +#define KPASMKP1 0x0030 +#define KPASMKP2 0x0038 +#define KPASMKP3 0x0040 +#define KPKDI 0x0048 + +/* bit definitions */ +#define KPC_MKRN(n) ((((n) - 1) & 0x7) << 26) /* matrix key row number */ +#define KPC_MKCN(n) ((((n) - 1) & 0x7) << 23) /* matrix key column number */ +#define KPC_DKN(n) ((((n) - 1) & 0x7) << 6) /* direct key number */ + +#define KPC_AS (0x1 << 30) /* Automatic Scan bit */ +#define KPC_ASACT (0x1 << 29) /* Automatic Scan on Activity */ +#define KPC_MI (0x1 << 22) /* Matrix interrupt bit */ +#define KPC_IMKP (0x1 << 21) /* Ignore Multiple Key Press */ + +#define KPC_MS(n) (0x1 << (13 + (n))) /* Matrix scan line 'n' */ +#define KPC_MS_ALL (0xff << 13) + +#define KPC_ME (0x1 << 12) /* Matrix Keypad Enable */ +#define KPC_MIE (0x1 << 11) /* Matrix Interrupt Enable */ +#define KPC_DK_DEB_SEL (0x1 << 9) /* Direct Keypad Debounce Select */ +#define KPC_DI (0x1 << 5) /* Direct key interrupt bit */ +#define KPC_RE_ZERO_DEB (0x1 << 4) /* Rotary Encoder Zero Debounce */ +#define KPC_REE1 (0x1 << 3) /* Rotary Encoder1 Enable */ +#define KPC_REE0 (0x1 << 2) /* Rotary Encoder0 Enable */ +#define KPC_DE (0x1 << 1) /* Direct Keypad Enable */ +#define KPC_DIE (0x1 << 0) /* Direct Keypad interrupt Enable */ + +#define KPDK_DKP (0x1 << 31) +#define KPDK_DK(n) ((n) & 0xff) + +#define KPREC_OF1 (0x1 << 31) +#define kPREC_UF1 (0x1 << 30) +#define KPREC_OF0 (0x1 << 15) +#define KPREC_UF0 (0x1 << 14) + +#define KPREC_RECOUNT0(n) ((n) & 0xff) +#define KPREC_RECOUNT1(n) (((n) >> 16) & 0xff) + +#define KPMK_MKP (0x1 << 31) +#define KPAS_SO (0x1 << 31) +#define KPASMKPx_SO (0x1 << 31) + +#define KPAS_MUKP(n) (((n) >> 26) & 0x1f) +#define KPAS_RP(n) (((n) >> 4) & 0xf) +#define KPAS_CP(n) ((n) & 0xf) + +#define KPASMKP_MKC_MASK (0xff) + +#define keypad_readl(off) __raw_readl(keypad->mmio_base + (off)) +#define keypad_writel(off, v) __raw_writel((v), keypad->mmio_base + (off)) + +#define MAX_MATRIX_KEY_NUM (8 * 8) + +struct pxa27x_keypad { + struct pxa27x_keypad_platform_data *pdata; + + struct clk *clk; + struct input_dev *input_dev; + void __iomem *mmio_base; + + int irq; + + /* matrix key code map */ + unsigned int matrix_keycodes[MAX_MATRIX_KEY_NUM]; + + /* state row bits of each column scan */ + uint32_t matrix_key_state[MAX_MATRIX_KEY_COLS]; + uint32_t direct_key_state; + + unsigned int direct_key_mask; + + int rotary_rel_code[2]; + int rotary_up_key[2]; + int rotary_down_key[2]; +}; + +static void pxa27x_keypad_build_keycode(struct pxa27x_keypad *keypad) +{ + struct pxa27x_keypad_platform_data *pdata = keypad->pdata; + struct input_dev *input_dev = keypad->input_dev; + unsigned int *key; + int i; + + key = &pdata->matrix_key_map[0]; + for (i = 0; i < pdata->matrix_key_map_size; i++, key++) { + int row = ((*key) >> 28) & 0xf; + int col = ((*key) >> 24) & 0xf; + int code = (*key) & 0xffffff; + + keypad->matrix_keycodes[(row << 3) + col] = code; + set_bit(code, input_dev->keybit); + } + + for (i = 0; i < pdata->direct_key_num; i++) + set_bit(pdata->direct_key_map[i], input_dev->keybit); + + keypad->rotary_up_key[0] = pdata->rotary0_up_key; + keypad->rotary_up_key[1] = pdata->rotary1_up_key; + keypad->rotary_down_key[0] = pdata->rotary0_down_key; + keypad->rotary_down_key[1] = pdata->rotary1_down_key; + keypad->rotary_rel_code[0] = pdata->rotary0_rel_code; + keypad->rotary_rel_code[1] = pdata->rotary1_rel_code; + + if (pdata->enable_rotary0) { + if (pdata->rotary0_up_key && pdata->rotary0_down_key) { + set_bit(pdata->rotary0_up_key, input_dev->keybit); + set_bit(pdata->rotary0_down_key, input_dev->keybit); + } else + set_bit(pdata->rotary0_rel_code, input_dev->relbit); + } + + if (pdata->enable_rotary1) { + if (pdata->rotary1_up_key && pdata->rotary1_down_key) { + set_bit(pdata->rotary1_up_key, input_dev->keybit); + set_bit(pdata->rotary1_down_key, input_dev->keybit); + } else + set_bit(pdata->rotary1_rel_code, input_dev->relbit); + } +} + +static inline unsigned int lookup_matrix_keycode( + struct pxa27x_keypad *keypad, int row, int col) +{ + return keypad->matrix_keycodes[(row << 3) + col]; +} + +static void pxa27x_keypad_scan_matrix(struct pxa27x_keypad *keypad) +{ + struct pxa27x_keypad_platform_data *pdata = keypad->pdata; + int row, col, num_keys_pressed = 0; + uint32_t new_state[MAX_MATRIX_KEY_COLS]; + uint32_t kpas = keypad_readl(KPAS); + + num_keys_pressed = KPAS_MUKP(kpas); + + memset(new_state, 0, sizeof(new_state)); + + if (num_keys_pressed == 0) + goto scan; + + if (num_keys_pressed == 1) { + col = KPAS_CP(kpas); + row = KPAS_RP(kpas); + + /* if invalid row/col, treat as no key pressed */ + if (col >= pdata->matrix_key_cols || + row >= pdata->matrix_key_rows) + goto scan; + + new_state[col] = (1 << row); + goto scan; + } + + if (num_keys_pressed > 1) { + uint32_t kpasmkp0 = keypad_readl(KPASMKP0); + uint32_t kpasmkp1 = keypad_readl(KPASMKP1); + uint32_t kpasmkp2 = keypad_readl(KPASMKP2); + uint32_t kpasmkp3 = keypad_readl(KPASMKP3); + + new_state[0] = kpasmkp0 & KPASMKP_MKC_MASK; + new_state[1] = (kpasmkp0 >> 16) & KPASMKP_MKC_MASK; + new_state[2] = kpasmkp1 & KPASMKP_MKC_MASK; + new_state[3] = (kpasmkp1 >> 16) & KPASMKP_MKC_MASK; + new_state[4] = kpasmkp2 & KPASMKP_MKC_MASK; + new_state[5] = (kpasmkp2 >> 16) & KPASMKP_MKC_MASK; + new_state[6] = kpasmkp3 & KPASMKP_MKC_MASK; + new_state[7] = (kpasmkp3 >> 16) & KPASMKP_MKC_MASK; + } +scan: + for (col = 0; col < pdata->matrix_key_cols; col++) { + uint32_t bits_changed; + + bits_changed = keypad->matrix_key_state[col] ^ new_state[col]; + if (bits_changed == 0) + continue; + + for (row = 0; row < pdata->matrix_key_rows; row++) { + if ((bits_changed & (1 << row)) == 0) + continue; + + input_report_key(keypad->input_dev, + lookup_matrix_keycode(keypad, row, col), + new_state[col] & (1 << row)); + } + } + input_sync(keypad->input_dev); + memcpy(keypad->matrix_key_state, new_state, sizeof(new_state)); +} + +#define DEFAULT_KPREC (0x007f007f) + +static inline int rotary_delta(uint32_t kprec) +{ + if (kprec & KPREC_OF0) + return (kprec & 0xff) + 0x7f; + else if (kprec & KPREC_UF0) + return (kprec & 0xff) - 0x7f - 0xff; + else + return (kprec & 0xff) - 0x7f; +} + +static void report_rotary_event(struct pxa27x_keypad *keypad, int r, int delta) +{ + struct input_dev *dev = keypad->input_dev; + + if (delta == 0) + return; + + if (keypad->rotary_up_key[r] && keypad->rotary_down_key[r]) { + int keycode = (delta > 0) ? keypad->rotary_up_key[r] : + keypad->rotary_down_key[r]; + + /* simulate a press-n-release */ + input_report_key(dev, keycode, 1); + input_sync(dev); + input_report_key(dev, keycode, 0); + input_sync(dev); + } else { + input_report_rel(dev, keypad->rotary_rel_code[r], delta); + input_sync(dev); + } +} + +static void pxa27x_keypad_scan_rotary(struct pxa27x_keypad *keypad) +{ + struct pxa27x_keypad_platform_data *pdata = keypad->pdata; + uint32_t kprec; + + /* read and reset to default count value */ + kprec = keypad_readl(KPREC); + keypad_writel(KPREC, DEFAULT_KPREC); + + if (pdata->enable_rotary0) + report_rotary_event(keypad, 0, rotary_delta(kprec)); + + if (pdata->enable_rotary1) + report_rotary_event(keypad, 1, rotary_delta(kprec >> 16)); +} + +static void pxa27x_keypad_scan_direct(struct pxa27x_keypad *keypad) +{ + struct pxa27x_keypad_platform_data *pdata = keypad->pdata; + unsigned int new_state; + uint32_t kpdk, bits_changed; + int i; + + kpdk = keypad_readl(KPDK); + + if (pdata->enable_rotary0 || pdata->enable_rotary1) + pxa27x_keypad_scan_rotary(keypad); + + if (pdata->direct_key_map == NULL) + return; + + new_state = KPDK_DK(kpdk) & keypad->direct_key_mask; + bits_changed = keypad->direct_key_state ^ new_state; + + if (bits_changed == 0) + return; + + for (i = 0; i < pdata->direct_key_num; i++) { + if (bits_changed & (1 << i)) + input_report_key(keypad->input_dev, + pdata->direct_key_map[i], + (new_state & (1 << i))); + } + input_sync(keypad->input_dev); + keypad->direct_key_state = new_state; +} + +static irqreturn_t pxa27x_keypad_irq_handler(int irq, void *dev_id) +{ + struct pxa27x_keypad *keypad = dev_id; + unsigned long kpc = keypad_readl(KPC); + + if (kpc & KPC_DI) + pxa27x_keypad_scan_direct(keypad); + + if (kpc & KPC_MI) + pxa27x_keypad_scan_matrix(keypad); + + return IRQ_HANDLED; +} + +static void pxa27x_keypad_config(struct pxa27x_keypad *keypad) +{ + struct pxa27x_keypad_platform_data *pdata = keypad->pdata; + unsigned int mask = 0, direct_key_num = 0; + unsigned long kpc = 0; + + /* enable matrix keys with automatic scan */ + if (pdata->matrix_key_rows && pdata->matrix_key_cols) { + kpc |= KPC_ASACT | KPC_MIE | KPC_ME | KPC_MS_ALL; + kpc |= KPC_MKRN(pdata->matrix_key_rows) | + KPC_MKCN(pdata->matrix_key_cols); + } + + /* enable rotary key, debounce interval same as direct keys */ + if (pdata->enable_rotary0) { + mask |= 0x03; + direct_key_num = 2; + kpc |= KPC_REE0; + } + + if (pdata->enable_rotary1) { + mask |= 0x0c; + direct_key_num = 4; + kpc |= KPC_REE1; + } + + if (pdata->direct_key_num > direct_key_num) + direct_key_num = pdata->direct_key_num; + + keypad->direct_key_mask = ((2 << direct_key_num) - 1) & ~mask; + + /* enable direct key */ + if (direct_key_num) + kpc |= KPC_DE | KPC_DIE | KPC_DKN(direct_key_num); + + keypad_writel(KPC, kpc | KPC_RE_ZERO_DEB); + keypad_writel(KPREC, DEFAULT_KPREC); + keypad_writel(KPKDI, pdata->debounce_interval); +} + +static int pxa27x_keypad_open(struct input_dev *dev) +{ + struct pxa27x_keypad *keypad = input_get_drvdata(dev); + + /* Enable unit clock */ + clk_enable(keypad->clk); + pxa27x_keypad_config(keypad); + + return 0; +} + +static void pxa27x_keypad_close(struct input_dev *dev) +{ + struct pxa27x_keypad *keypad = input_get_drvdata(dev); + + /* Disable clock unit */ + clk_disable(keypad->clk); +} + +#ifdef CONFIG_PM +static int pxa27x_keypad_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct pxa27x_keypad *keypad = platform_get_drvdata(pdev); + + clk_disable(keypad->clk); + + if (device_may_wakeup(&pdev->dev)) + enable_irq_wake(keypad->irq); + + return 0; +} + +static int pxa27x_keypad_resume(struct platform_device *pdev) +{ + struct pxa27x_keypad *keypad = platform_get_drvdata(pdev); + struct input_dev *input_dev = keypad->input_dev; + + if (device_may_wakeup(&pdev->dev)) + disable_irq_wake(keypad->irq); + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) { + /* Enable unit clock */ + clk_enable(keypad->clk); + pxa27x_keypad_config(keypad); + } + + mutex_unlock(&input_dev->mutex); + + return 0; +} +#else +#define pxa27x_keypad_suspend NULL +#define pxa27x_keypad_resume NULL +#endif + +#define res_size(res) ((res)->end - (res)->start + 1) + +static int __devinit pxa27x_keypad_probe(struct platform_device *pdev) +{ + struct pxa27x_keypad *keypad; + struct input_dev *input_dev; + struct resource *res; + int irq, error; + + keypad = kzalloc(sizeof(struct pxa27x_keypad), GFP_KERNEL); + if (keypad == NULL) { + dev_err(&pdev->dev, "failed to allocate driver data\n"); + return -ENOMEM; + } + + keypad->pdata = pdev->dev.platform_data; + if (keypad->pdata == NULL) { + dev_err(&pdev->dev, "no platform data defined\n"); + error = -EINVAL; + goto failed_free; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "failed to get keypad irq\n"); + error = -ENXIO; + goto failed_free; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "failed to get I/O memory\n"); + error = -ENXIO; + goto failed_free; + } + + res = request_mem_region(res->start, res_size(res), pdev->name); + if (res == NULL) { + dev_err(&pdev->dev, "failed to request I/O memory\n"); + error = -EBUSY; + goto failed_free; + } + + keypad->mmio_base = ioremap(res->start, res_size(res)); + if (keypad->mmio_base == NULL) { + dev_err(&pdev->dev, "failed to remap I/O memory\n"); + error = -ENXIO; + goto failed_free_mem; + } + + keypad->clk = clk_get(&pdev->dev, "KBDCLK"); + if (IS_ERR(keypad->clk)) { + dev_err(&pdev->dev, "failed to get keypad clock\n"); + error = PTR_ERR(keypad->clk); + goto failed_free_io; + } + + /* Create and register the input driver. */ + input_dev = input_allocate_device(); + if (!input_dev) { + dev_err(&pdev->dev, "failed to allocate input device\n"); + error = -ENOMEM; + goto failed_put_clk; + } + + input_dev->name = pdev->name; + input_dev->id.bustype = BUS_HOST; + input_dev->open = pxa27x_keypad_open; + input_dev->close = pxa27x_keypad_close; + input_dev->dev.parent = &pdev->dev; + + keypad->input_dev = input_dev; + input_set_drvdata(input_dev, keypad); + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + if ((keypad->pdata->enable_rotary0 && + keypad->pdata->rotary0_rel_code) || + (keypad->pdata->enable_rotary1 && + keypad->pdata->rotary1_rel_code)) { + input_dev->evbit[0] |= BIT_MASK(EV_REL); + } + + pxa27x_keypad_build_keycode(keypad); + platform_set_drvdata(pdev, keypad); + + error = request_irq(irq, pxa27x_keypad_irq_handler, IRQF_DISABLED, + pdev->name, keypad); + if (error) { + dev_err(&pdev->dev, "failed to request IRQ\n"); + goto failed_free_dev; + } + + keypad->irq = irq; + + /* Register the input device */ + error = input_register_device(input_dev); + if (error) { + dev_err(&pdev->dev, "failed to register input device\n"); + goto failed_free_irq; + } + + device_init_wakeup(&pdev->dev, 1); + + return 0; + +failed_free_irq: + free_irq(irq, pdev); + platform_set_drvdata(pdev, NULL); +failed_free_dev: + input_free_device(input_dev); +failed_put_clk: + clk_put(keypad->clk); +failed_free_io: + iounmap(keypad->mmio_base); +failed_free_mem: + release_mem_region(res->start, res_size(res)); +failed_free: + kfree(keypad); + return error; +} + +static int __devexit pxa27x_keypad_remove(struct platform_device *pdev) +{ + struct pxa27x_keypad *keypad = platform_get_drvdata(pdev); + struct resource *res; + + free_irq(keypad->irq, pdev); + + clk_disable(keypad->clk); + clk_put(keypad->clk); + + input_unregister_device(keypad->input_dev); + input_free_device(keypad->input_dev); + + iounmap(keypad->mmio_base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, res_size(res)); + + platform_set_drvdata(pdev, NULL); + kfree(keypad); + return 0; +} + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:pxa27x-keypad"); + +static struct platform_driver pxa27x_keypad_driver = { + .probe = pxa27x_keypad_probe, + .remove = __devexit_p(pxa27x_keypad_remove), + .suspend = pxa27x_keypad_suspend, + .resume = pxa27x_keypad_resume, + .driver = { + .name = "pxa27x-keypad", + .owner = THIS_MODULE, + }, +}; + +static int __init pxa27x_keypad_init(void) +{ + return platform_driver_register(&pxa27x_keypad_driver); +} + +static void __exit pxa27x_keypad_exit(void) +{ + platform_driver_unregister(&pxa27x_keypad_driver); +} + +module_init(pxa27x_keypad_init); +module_exit(pxa27x_keypad_exit); + +MODULE_DESCRIPTION("PXA27x Keypad Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/keyboard/sh_keysc.c b/drivers/input/keyboard/sh_keysc.c new file mode 100644 index 0000000..c600ab7 --- /dev/null +++ b/drivers/input/keyboard/sh_keysc.c @@ -0,0 +1,267 @@ +/* + * SuperH KEYSC Keypad Driver + * + * Copyright (C) 2008 Magnus Damm + * + * Based on gpio_keys.c, Copyright 2005 Phil Blundell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/io.h> +#include <asm/sh_keysc.h> + +#define KYCR1_OFFS 0x00 +#define KYCR2_OFFS 0x04 +#define KYINDR_OFFS 0x08 +#define KYOUTDR_OFFS 0x0c + +#define KYCR2_IRQ_LEVEL 0x10 +#define KYCR2_IRQ_DISABLED 0x00 + +static const struct { + unsigned char kymd, keyout, keyin; +} sh_keysc_mode[] = { + [SH_KEYSC_MODE_1] = { 0, 6, 5 }, + [SH_KEYSC_MODE_2] = { 1, 5, 6 }, + [SH_KEYSC_MODE_3] = { 2, 4, 7 }, +}; + +struct sh_keysc_priv { + void __iomem *iomem_base; + unsigned long last_keys; + struct input_dev *input; + struct sh_keysc_info pdata; +}; + +static irqreturn_t sh_keysc_isr(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct sh_keysc_priv *priv = platform_get_drvdata(pdev); + struct sh_keysc_info *pdata = &priv->pdata; + unsigned long keys, keys1, keys0, mask; + unsigned char keyin_set, tmp; + int i, k; + + dev_dbg(&pdev->dev, "isr!\n"); + + keys1 = ~0; + keys0 = 0; + + do { + keys = 0; + keyin_set = 0; + + iowrite16(KYCR2_IRQ_DISABLED, priv->iomem_base + KYCR2_OFFS); + + for (i = 0; i < sh_keysc_mode[pdata->mode].keyout; i++) { + iowrite16(0xfff ^ (3 << (i * 2)), + priv->iomem_base + KYOUTDR_OFFS); + udelay(pdata->delay); + tmp = ioread16(priv->iomem_base + KYINDR_OFFS); + keys |= tmp << (sh_keysc_mode[pdata->mode].keyin * i); + tmp ^= (1 << sh_keysc_mode[pdata->mode].keyin) - 1; + keyin_set |= tmp; + } + + iowrite16(0, priv->iomem_base + KYOUTDR_OFFS); + iowrite16(KYCR2_IRQ_LEVEL | (keyin_set << 8), + priv->iomem_base + KYCR2_OFFS); + + keys ^= ~0; + keys &= (1 << (sh_keysc_mode[pdata->mode].keyin * + sh_keysc_mode[pdata->mode].keyout)) - 1; + keys1 &= keys; + keys0 |= keys; + + dev_dbg(&pdev->dev, "keys 0x%08lx\n", keys); + + } while (ioread16(priv->iomem_base + KYCR2_OFFS) & 0x01); + + dev_dbg(&pdev->dev, "last_keys 0x%08lx keys0 0x%08lx keys1 0x%08lx\n", + priv->last_keys, keys0, keys1); + + for (i = 0; i < SH_KEYSC_MAXKEYS; i++) { + k = pdata->keycodes[i]; + if (!k) + continue; + + mask = 1 << i; + + if (!((priv->last_keys ^ keys0) & mask)) + continue; + + if ((keys1 | keys0) & mask) { + input_event(priv->input, EV_KEY, k, 1); + priv->last_keys |= mask; + } + + if (!(keys1 & mask)) { + input_event(priv->input, EV_KEY, k, 0); + priv->last_keys &= ~mask; + } + + } + input_sync(priv->input); + + return IRQ_HANDLED; +} + +#define res_size(res) ((res)->end - (res)->start + 1) + +static int __devinit sh_keysc_probe(struct platform_device *pdev) +{ + struct sh_keysc_priv *priv; + struct sh_keysc_info *pdata; + struct resource *res; + struct input_dev *input; + int i, k; + int irq, error; + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "no platform data defined\n"); + error = -EINVAL; + goto err0; + } + + error = -ENXIO; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "failed to get I/O memory\n"); + goto err0; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "failed to get irq\n"); + goto err0; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (priv == NULL) { + dev_err(&pdev->dev, "failed to allocate driver data\n"); + error = -ENOMEM; + goto err0; + } + + platform_set_drvdata(pdev, priv); + memcpy(&priv->pdata, pdev->dev.platform_data, sizeof(priv->pdata)); + pdata = &priv->pdata; + + priv->iomem_base = ioremap_nocache(res->start, res_size(res)); + if (priv->iomem_base == NULL) { + dev_err(&pdev->dev, "failed to remap I/O memory\n"); + error = -ENXIO; + goto err1; + } + + priv->input = input_allocate_device(); + if (!priv->input) { + dev_err(&pdev->dev, "failed to allocate input device\n"); + error = -ENOMEM; + goto err2; + } + + input = priv->input; + input->evbit[0] = BIT_MASK(EV_KEY); + + input->name = pdev->name; + input->phys = "sh-keysc-keys/input0"; + input->dev.parent = &pdev->dev; + + input->id.bustype = BUS_HOST; + input->id.vendor = 0x0001; + input->id.product = 0x0001; + input->id.version = 0x0100; + + error = request_irq(irq, sh_keysc_isr, 0, pdev->name, pdev); + if (error) { + dev_err(&pdev->dev, "failed to request IRQ\n"); + goto err3; + } + + for (i = 0; i < SH_KEYSC_MAXKEYS; i++) { + k = pdata->keycodes[i]; + if (k) + input_set_capability(input, EV_KEY, k); + } + + error = input_register_device(input); + if (error) { + dev_err(&pdev->dev, "failed to register input device\n"); + goto err4; + } + + iowrite16((sh_keysc_mode[pdata->mode].kymd << 8) | + pdata->scan_timing, priv->iomem_base + KYCR1_OFFS); + iowrite16(0, priv->iomem_base + KYOUTDR_OFFS); + iowrite16(KYCR2_IRQ_LEVEL, priv->iomem_base + KYCR2_OFFS); + return 0; + err4: + free_irq(irq, pdev); + err3: + input_free_device(input); + err2: + iounmap(priv->iomem_base); + err1: + platform_set_drvdata(pdev, NULL); + kfree(priv); + err0: + return error; +} + +static int __devexit sh_keysc_remove(struct platform_device *pdev) +{ + struct sh_keysc_priv *priv = platform_get_drvdata(pdev); + + iowrite16(KYCR2_IRQ_DISABLED, priv->iomem_base + KYCR2_OFFS); + + input_unregister_device(priv->input); + free_irq(platform_get_irq(pdev, 0), pdev); + iounmap(priv->iomem_base); + + platform_set_drvdata(pdev, NULL); + kfree(priv); + return 0; +} + + +#define sh_keysc_suspend NULL +#define sh_keysc_resume NULL + +struct platform_driver sh_keysc_device_driver = { + .probe = sh_keysc_probe, + .remove = __devexit_p(sh_keysc_remove), + .suspend = sh_keysc_suspend, + .resume = sh_keysc_resume, + .driver = { + .name = "sh_keysc", + } +}; + +static int __init sh_keysc_init(void) +{ + return platform_driver_register(&sh_keysc_device_driver); +} + +static void __exit sh_keysc_exit(void) +{ + platform_driver_unregister(&sh_keysc_device_driver); +} + +module_init(sh_keysc_init); +module_exit(sh_keysc_exit); + +MODULE_AUTHOR("Magnus Damm"); +MODULE_DESCRIPTION("SuperH KEYSC Keypad Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/keyboard/spitzkbd.c b/drivers/input/keyboard/spitzkbd.c new file mode 100644 index 0000000..c48b76a --- /dev/null +++ b/drivers/input/keyboard/spitzkbd.c @@ -0,0 +1,498 @@ +/* + * Keyboard driver for Sharp Spitz, Borzoi and Akita (SL-Cxx00 series) + * + * Copyright (c) 2005 Richard Purdie + * + * Based on corgikbd.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <mach/spitz.h> +#include <mach/hardware.h> +#include <mach/pxa-regs.h> +#include <mach/pxa2xx-gpio.h> + +#define KB_ROWS 7 +#define KB_COLS 11 +#define KB_ROWMASK(r) (1 << (r)) +#define SCANCODE(r,c) (((r)<<4) + (c) + 1) +#define NR_SCANCODES ((KB_ROWS<<4) + 1) + +#define SCAN_INTERVAL (50) /* ms */ +#define HINGE_SCAN_INTERVAL (150) /* ms */ + +#define SPITZ_KEY_CALENDER KEY_F1 +#define SPITZ_KEY_ADDRESS KEY_F2 +#define SPITZ_KEY_FN KEY_F3 +#define SPITZ_KEY_CANCEL KEY_F4 +#define SPITZ_KEY_EXOK KEY_F5 +#define SPITZ_KEY_EXCANCEL KEY_F6 +#define SPITZ_KEY_EXJOGDOWN KEY_F7 +#define SPITZ_KEY_EXJOGUP KEY_F8 +#define SPITZ_KEY_JAP1 KEY_LEFTALT +#define SPITZ_KEY_JAP2 KEY_RIGHTCTRL +#define SPITZ_KEY_SYNC KEY_F9 +#define SPITZ_KEY_MAIL KEY_F10 +#define SPITZ_KEY_OK KEY_F11 +#define SPITZ_KEY_MENU KEY_F12 + +static unsigned char spitzkbd_keycode[NR_SCANCODES] = { + 0, /* 0 */ + KEY_LEFTCTRL, KEY_1, KEY_3, KEY_5, KEY_6, KEY_7, KEY_9, KEY_0, KEY_BACKSPACE, SPITZ_KEY_EXOK, SPITZ_KEY_EXCANCEL, 0, 0, 0, 0, 0, /* 1-16 */ + 0, KEY_2, KEY_4, KEY_R, KEY_Y, KEY_8, KEY_I, KEY_O, KEY_P, SPITZ_KEY_EXJOGDOWN, SPITZ_KEY_EXJOGUP, 0, 0, 0, 0, 0, /* 17-32 */ + KEY_TAB, KEY_Q, KEY_E, KEY_T, KEY_G, KEY_U, KEY_J, KEY_K, 0, 0, 0, 0, 0, 0, 0, 0, /* 33-48 */ + SPITZ_KEY_ADDRESS, KEY_W, KEY_S, KEY_F, KEY_V, KEY_H, KEY_M, KEY_L, 0, KEY_RIGHTSHIFT, 0, 0, 0, 0, 0, 0, /* 49-64 */ + SPITZ_KEY_CALENDER, KEY_A, KEY_D, KEY_C, KEY_B, KEY_N, KEY_DOT, 0, KEY_ENTER, KEY_LEFTSHIFT, 0, 0, 0, 0, 0, 0, /* 65-80 */ + SPITZ_KEY_MAIL, KEY_Z, KEY_X, KEY_MINUS, KEY_SPACE, KEY_COMMA, 0, KEY_UP, 0, 0, SPITZ_KEY_FN, 0, 0, 0, 0, 0, /* 81-96 */ + KEY_SYSRQ, SPITZ_KEY_JAP1, SPITZ_KEY_JAP2, SPITZ_KEY_CANCEL, SPITZ_KEY_OK, SPITZ_KEY_MENU, KEY_LEFT, KEY_DOWN, KEY_RIGHT, 0, 0, 0, 0, 0, 0, 0 /* 97-112 */ +}; + +static int spitz_strobes[] = { + SPITZ_GPIO_KEY_STROBE0, + SPITZ_GPIO_KEY_STROBE1, + SPITZ_GPIO_KEY_STROBE2, + SPITZ_GPIO_KEY_STROBE3, + SPITZ_GPIO_KEY_STROBE4, + SPITZ_GPIO_KEY_STROBE5, + SPITZ_GPIO_KEY_STROBE6, + SPITZ_GPIO_KEY_STROBE7, + SPITZ_GPIO_KEY_STROBE8, + SPITZ_GPIO_KEY_STROBE9, + SPITZ_GPIO_KEY_STROBE10, +}; + +static int spitz_senses[] = { + SPITZ_GPIO_KEY_SENSE0, + SPITZ_GPIO_KEY_SENSE1, + SPITZ_GPIO_KEY_SENSE2, + SPITZ_GPIO_KEY_SENSE3, + SPITZ_GPIO_KEY_SENSE4, + SPITZ_GPIO_KEY_SENSE5, + SPITZ_GPIO_KEY_SENSE6, +}; + +struct spitzkbd { + unsigned char keycode[ARRAY_SIZE(spitzkbd_keycode)]; + struct input_dev *input; + char phys[32]; + + spinlock_t lock; + struct timer_list timer; + struct timer_list htimer; + + unsigned int suspended; + unsigned long suspend_jiffies; +}; + +#define KB_DISCHARGE_DELAY 10 +#define KB_ACTIVATE_DELAY 10 + +/* Helper functions for reading the keyboard matrix + * Note: We should really be using the generic gpio functions to alter + * GPDR but it requires a function call per GPIO bit which is + * excessive when we need to access 11 bits at once, multiple times. + * These functions must be called within local_irq_save()/local_irq_restore() + * or similar. + */ +static inline void spitzkbd_discharge_all(void) +{ + /* STROBE All HiZ */ + GPCR0 = SPITZ_GPIO_G0_STROBE_BIT; + GPDR0 &= ~SPITZ_GPIO_G0_STROBE_BIT; + GPCR1 = SPITZ_GPIO_G1_STROBE_BIT; + GPDR1 &= ~SPITZ_GPIO_G1_STROBE_BIT; + GPCR2 = SPITZ_GPIO_G2_STROBE_BIT; + GPDR2 &= ~SPITZ_GPIO_G2_STROBE_BIT; + GPCR3 = SPITZ_GPIO_G3_STROBE_BIT; + GPDR3 &= ~SPITZ_GPIO_G3_STROBE_BIT; +} + +static inline void spitzkbd_activate_all(void) +{ + /* STROBE ALL -> High */ + GPSR0 = SPITZ_GPIO_G0_STROBE_BIT; + GPDR0 |= SPITZ_GPIO_G0_STROBE_BIT; + GPSR1 = SPITZ_GPIO_G1_STROBE_BIT; + GPDR1 |= SPITZ_GPIO_G1_STROBE_BIT; + GPSR2 = SPITZ_GPIO_G2_STROBE_BIT; + GPDR2 |= SPITZ_GPIO_G2_STROBE_BIT; + GPSR3 = SPITZ_GPIO_G3_STROBE_BIT; + GPDR3 |= SPITZ_GPIO_G3_STROBE_BIT; + + udelay(KB_DISCHARGE_DELAY); + + /* Clear any interrupts we may have triggered when altering the GPIO lines */ + GEDR0 = SPITZ_GPIO_G0_SENSE_BIT; + GEDR1 = SPITZ_GPIO_G1_SENSE_BIT; + GEDR2 = SPITZ_GPIO_G2_SENSE_BIT; + GEDR3 = SPITZ_GPIO_G3_SENSE_BIT; +} + +static inline void spitzkbd_activate_col(int col) +{ + int gpio = spitz_strobes[col]; + GPDR0 &= ~SPITZ_GPIO_G0_STROBE_BIT; + GPDR1 &= ~SPITZ_GPIO_G1_STROBE_BIT; + GPDR2 &= ~SPITZ_GPIO_G2_STROBE_BIT; + GPDR3 &= ~SPITZ_GPIO_G3_STROBE_BIT; + GPSR(gpio) = GPIO_bit(gpio); + GPDR(gpio) |= GPIO_bit(gpio); +} + +static inline void spitzkbd_reset_col(int col) +{ + int gpio = spitz_strobes[col]; + GPDR0 &= ~SPITZ_GPIO_G0_STROBE_BIT; + GPDR1 &= ~SPITZ_GPIO_G1_STROBE_BIT; + GPDR2 &= ~SPITZ_GPIO_G2_STROBE_BIT; + GPDR3 &= ~SPITZ_GPIO_G3_STROBE_BIT; + GPCR(gpio) = GPIO_bit(gpio); + GPDR(gpio) |= GPIO_bit(gpio); +} + +static inline int spitzkbd_get_row_status(int col) +{ + return ((GPLR0 >> 12) & 0x01) | ((GPLR0 >> 16) & 0x02) + | ((GPLR2 >> 25) & 0x04) | ((GPLR1 << 1) & 0x08) + | ((GPLR1 >> 0) & 0x10) | ((GPLR1 >> 1) & 0x60); +} + +/* + * The spitz keyboard only generates interrupts when a key is pressed. + * When a key is pressed, we enable a timer which then scans the + * keyboard to detect when the key is released. + */ + +/* Scan the hardware keyboard and push any changes up through the input layer */ +static void spitzkbd_scankeyboard(struct spitzkbd *spitzkbd_data) +{ + unsigned int row, col, rowd; + unsigned long flags; + unsigned int num_pressed, pwrkey = ((GPLR(SPITZ_GPIO_ON_KEY) & GPIO_bit(SPITZ_GPIO_ON_KEY)) != 0); + + if (spitzkbd_data->suspended) + return; + + spin_lock_irqsave(&spitzkbd_data->lock, flags); + + num_pressed = 0; + for (col = 0; col < KB_COLS; col++) { + /* + * Discharge the output driver capacitatance + * in the keyboard matrix. (Yes it is significant..) + */ + + spitzkbd_discharge_all(); + udelay(KB_DISCHARGE_DELAY); + + spitzkbd_activate_col(col); + udelay(KB_ACTIVATE_DELAY); + + rowd = spitzkbd_get_row_status(col); + for (row = 0; row < KB_ROWS; row++) { + unsigned int scancode, pressed; + + scancode = SCANCODE(row, col); + pressed = rowd & KB_ROWMASK(row); + + input_report_key(spitzkbd_data->input, spitzkbd_data->keycode[scancode], pressed); + + if (pressed) + num_pressed++; + } + spitzkbd_reset_col(col); + } + + spitzkbd_activate_all(); + + input_report_key(spitzkbd_data->input, SPITZ_KEY_SYNC, (GPLR(SPITZ_GPIO_SYNC) & GPIO_bit(SPITZ_GPIO_SYNC)) != 0 ); + input_report_key(spitzkbd_data->input, KEY_SUSPEND, pwrkey); + + if (pwrkey && time_after(jiffies, spitzkbd_data->suspend_jiffies + msecs_to_jiffies(1000))) { + input_event(spitzkbd_data->input, EV_PWR, KEY_SUSPEND, 1); + spitzkbd_data->suspend_jiffies = jiffies; + } + + input_sync(spitzkbd_data->input); + + /* if any keys are pressed, enable the timer */ + if (num_pressed) + mod_timer(&spitzkbd_data->timer, jiffies + msecs_to_jiffies(SCAN_INTERVAL)); + + spin_unlock_irqrestore(&spitzkbd_data->lock, flags); +} + +/* + * spitz keyboard interrupt handler. + */ +static irqreturn_t spitzkbd_interrupt(int irq, void *dev_id) +{ + struct spitzkbd *spitzkbd_data = dev_id; + + if (!timer_pending(&spitzkbd_data->timer)) { + /** wait chattering delay **/ + udelay(20); + spitzkbd_scankeyboard(spitzkbd_data); + } + + return IRQ_HANDLED; +} + +/* + * spitz timer checking for released keys + */ +static void spitzkbd_timer_callback(unsigned long data) +{ + struct spitzkbd *spitzkbd_data = (struct spitzkbd *) data; + + spitzkbd_scankeyboard(spitzkbd_data); +} + +/* + * The hinge switches generate an interrupt. + * We debounce the switches and pass them to the input system. + */ + +static irqreturn_t spitzkbd_hinge_isr(int irq, void *dev_id) +{ + struct spitzkbd *spitzkbd_data = dev_id; + + if (!timer_pending(&spitzkbd_data->htimer)) + mod_timer(&spitzkbd_data->htimer, jiffies + msecs_to_jiffies(HINGE_SCAN_INTERVAL)); + + return IRQ_HANDLED; +} + +#define HINGE_STABLE_COUNT 2 +static int sharpsl_hinge_state; +static int hinge_count; + +static void spitzkbd_hinge_timer(unsigned long data) +{ + struct spitzkbd *spitzkbd_data = (struct spitzkbd *) data; + unsigned long state; + unsigned long flags; + + state = GPLR(SPITZ_GPIO_SWA) & (GPIO_bit(SPITZ_GPIO_SWA)|GPIO_bit(SPITZ_GPIO_SWB)); + state |= (GPLR(SPITZ_GPIO_AK_INT) & GPIO_bit(SPITZ_GPIO_AK_INT)); + if (state != sharpsl_hinge_state) { + hinge_count = 0; + sharpsl_hinge_state = state; + } else if (hinge_count < HINGE_STABLE_COUNT) { + hinge_count++; + } + + if (hinge_count >= HINGE_STABLE_COUNT) { + spin_lock_irqsave(&spitzkbd_data->lock, flags); + + input_report_switch(spitzkbd_data->input, SW_LID, ((GPLR(SPITZ_GPIO_SWA) & GPIO_bit(SPITZ_GPIO_SWA)) != 0)); + input_report_switch(spitzkbd_data->input, SW_TABLET_MODE, ((GPLR(SPITZ_GPIO_SWB) & GPIO_bit(SPITZ_GPIO_SWB)) != 0)); + input_report_switch(spitzkbd_data->input, SW_HEADPHONE_INSERT, ((GPLR(SPITZ_GPIO_AK_INT) & GPIO_bit(SPITZ_GPIO_AK_INT)) != 0)); + input_sync(spitzkbd_data->input); + + spin_unlock_irqrestore(&spitzkbd_data->lock, flags); + } else { + mod_timer(&spitzkbd_data->htimer, jiffies + msecs_to_jiffies(HINGE_SCAN_INTERVAL)); + } +} + +#ifdef CONFIG_PM +static int spitzkbd_suspend(struct platform_device *dev, pm_message_t state) +{ + int i; + struct spitzkbd *spitzkbd = platform_get_drvdata(dev); + spitzkbd->suspended = 1; + + /* Set Strobe lines as inputs - *except* strobe line 0 leave this + enabled so we can detect a power button press for resume */ + for (i = 1; i < SPITZ_KEY_STROBE_NUM; i++) + pxa_gpio_mode(spitz_strobes[i] | GPIO_IN); + + return 0; +} + +static int spitzkbd_resume(struct platform_device *dev) +{ + int i; + struct spitzkbd *spitzkbd = platform_get_drvdata(dev); + + for (i = 0; i < SPITZ_KEY_STROBE_NUM; i++) + pxa_gpio_mode(spitz_strobes[i] | GPIO_OUT | GPIO_DFLT_HIGH); + + /* Upon resume, ignore the suspend key for a short while */ + spitzkbd->suspend_jiffies = jiffies; + spitzkbd->suspended = 0; + + return 0; +} +#else +#define spitzkbd_suspend NULL +#define spitzkbd_resume NULL +#endif + +static int __init spitzkbd_probe(struct platform_device *dev) +{ + struct spitzkbd *spitzkbd; + struct input_dev *input_dev; + int i, err = -ENOMEM; + + spitzkbd = kzalloc(sizeof(struct spitzkbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!spitzkbd || !input_dev) + goto fail; + + platform_set_drvdata(dev, spitzkbd); + strcpy(spitzkbd->phys, "spitzkbd/input0"); + + spin_lock_init(&spitzkbd->lock); + + /* Init Keyboard rescan timer */ + init_timer(&spitzkbd->timer); + spitzkbd->timer.function = spitzkbd_timer_callback; + spitzkbd->timer.data = (unsigned long) spitzkbd; + + /* Init Hinge Timer */ + init_timer(&spitzkbd->htimer); + spitzkbd->htimer.function = spitzkbd_hinge_timer; + spitzkbd->htimer.data = (unsigned long) spitzkbd; + + spitzkbd->suspend_jiffies = jiffies; + + spitzkbd->input = input_dev; + + input_dev->name = "Spitz Keyboard"; + input_dev->phys = spitzkbd->phys; + input_dev->dev.parent = &dev->dev; + + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | + BIT_MASK(EV_PWR) | BIT_MASK(EV_SW); + input_dev->keycode = spitzkbd->keycode; + input_dev->keycodesize = sizeof(unsigned char); + input_dev->keycodemax = ARRAY_SIZE(spitzkbd_keycode); + + memcpy(spitzkbd->keycode, spitzkbd_keycode, sizeof(spitzkbd->keycode)); + for (i = 0; i < ARRAY_SIZE(spitzkbd_keycode); i++) + set_bit(spitzkbd->keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + set_bit(KEY_SUSPEND, input_dev->keybit); + set_bit(SW_LID, input_dev->swbit); + set_bit(SW_TABLET_MODE, input_dev->swbit); + set_bit(SW_HEADPHONE_INSERT, input_dev->swbit); + + err = input_register_device(input_dev); + if (err) + goto fail; + + mod_timer(&spitzkbd->htimer, jiffies + msecs_to_jiffies(HINGE_SCAN_INTERVAL)); + + /* Setup sense interrupts - RisingEdge Detect, sense lines as inputs */ + for (i = 0; i < SPITZ_KEY_SENSE_NUM; i++) { + pxa_gpio_mode(spitz_senses[i] | GPIO_IN); + if (request_irq(IRQ_GPIO(spitz_senses[i]), spitzkbd_interrupt, + IRQF_DISABLED|IRQF_TRIGGER_RISING, + "Spitzkbd Sense", spitzkbd)) + printk(KERN_WARNING "spitzkbd: Can't get Sense IRQ: %d!\n", i); + } + + /* Set Strobe lines as outputs - set high */ + for (i = 0; i < SPITZ_KEY_STROBE_NUM; i++) + pxa_gpio_mode(spitz_strobes[i] | GPIO_OUT | GPIO_DFLT_HIGH); + + pxa_gpio_mode(SPITZ_GPIO_SYNC | GPIO_IN); + pxa_gpio_mode(SPITZ_GPIO_ON_KEY | GPIO_IN); + pxa_gpio_mode(SPITZ_GPIO_SWA | GPIO_IN); + pxa_gpio_mode(SPITZ_GPIO_SWB | GPIO_IN); + + request_irq(SPITZ_IRQ_GPIO_SYNC, spitzkbd_interrupt, + IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "Spitzkbd Sync", spitzkbd); + request_irq(SPITZ_IRQ_GPIO_ON_KEY, spitzkbd_interrupt, + IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "Spitzkbd PwrOn", spitzkbd); + request_irq(SPITZ_IRQ_GPIO_SWA, spitzkbd_hinge_isr, + IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "Spitzkbd SWA", spitzkbd); + request_irq(SPITZ_IRQ_GPIO_SWB, spitzkbd_hinge_isr, + IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "Spitzkbd SWB", spitzkbd); + request_irq(SPITZ_IRQ_GPIO_AK_INT, spitzkbd_hinge_isr, + IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "Spitzkbd HP", spitzkbd); + + return 0; + + fail: input_free_device(input_dev); + kfree(spitzkbd); + return err; +} + +static int spitzkbd_remove(struct platform_device *dev) +{ + int i; + struct spitzkbd *spitzkbd = platform_get_drvdata(dev); + + for (i = 0; i < SPITZ_KEY_SENSE_NUM; i++) + free_irq(IRQ_GPIO(spitz_senses[i]), spitzkbd); + + free_irq(SPITZ_IRQ_GPIO_SYNC, spitzkbd); + free_irq(SPITZ_IRQ_GPIO_ON_KEY, spitzkbd); + free_irq(SPITZ_IRQ_GPIO_SWA, spitzkbd); + free_irq(SPITZ_IRQ_GPIO_SWB, spitzkbd); + free_irq(SPITZ_IRQ_GPIO_AK_INT, spitzkbd); + + del_timer_sync(&spitzkbd->htimer); + del_timer_sync(&spitzkbd->timer); + + input_unregister_device(spitzkbd->input); + + kfree(spitzkbd); + + return 0; +} + +static struct platform_driver spitzkbd_driver = { + .probe = spitzkbd_probe, + .remove = spitzkbd_remove, + .suspend = spitzkbd_suspend, + .resume = spitzkbd_resume, + .driver = { + .name = "spitz-keyboard", + .owner = THIS_MODULE, + }, +}; + +static int __devinit spitzkbd_init(void) +{ + return platform_driver_register(&spitzkbd_driver); +} + +static void __exit spitzkbd_exit(void) +{ + platform_driver_unregister(&spitzkbd_driver); +} + +module_init(spitzkbd_init); +module_exit(spitzkbd_exit); + +MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>"); +MODULE_DESCRIPTION("Spitz Keyboard Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:spitz-keyboard"); diff --git a/drivers/input/keyboard/stowaway.c b/drivers/input/keyboard/stowaway.c new file mode 100644 index 0000000..7437219 --- /dev/null +++ b/drivers/input/keyboard/stowaway.c @@ -0,0 +1,184 @@ +/* + * Stowaway keyboard driver for Linux + */ + +/* + * Copyright (c) 2006 Marek Vasut + * + * Based on Newton keyboard driver for Linux + * by Justin Cormack + */ + +/* + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to <marek.vasut@gmail.com>, or by paper mail: + * Marek Vasut, Liskovecka 559, Frydek-Mistek, 738 01 Czech Republic + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/serio.h> + +#define DRIVER_DESC "Stowaway keyboard driver" + +MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define SKBD_KEY_MASK 0x7f +#define SKBD_RELEASE 0x80 + +static unsigned char skbd_keycode[128] = { + KEY_1, KEY_2, KEY_3, KEY_Z, KEY_4, KEY_5, KEY_6, KEY_7, + 0, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_GRAVE, + KEY_X, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_SPACE, + KEY_CAPSLOCK, KEY_TAB, KEY_LEFTCTRL, 0, 0, 0, 0, 0, + 0, 0, 0, KEY_LEFTALT, 0, 0, 0, 0, + 0, 0, 0, 0, KEY_C, KEY_V, KEY_B, KEY_N, + KEY_MINUS, KEY_EQUAL, KEY_BACKSPACE, KEY_HOME, KEY_8, KEY_9, KEY_0, KEY_ESC, + KEY_LEFTBRACE, KEY_RIGHTBRACE, KEY_BACKSLASH, KEY_END, KEY_U, KEY_I, KEY_O, KEY_P, + KEY_APOSTROPHE, KEY_ENTER, KEY_PAGEUP,0, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, + KEY_SLASH, KEY_UP, KEY_PAGEDOWN, 0,KEY_M, KEY_COMMA, KEY_DOT, KEY_INSERT, + KEY_DELETE, KEY_LEFT, KEY_DOWN, KEY_RIGHT, 0, 0, 0, + KEY_LEFTSHIFT, KEY_RIGHTSHIFT, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, + KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, 0, 0, 0 +}; + +struct skbd { + unsigned char keycode[128]; + struct input_dev *dev; + struct serio *serio; + char phys[32]; +}; + +static irqreturn_t skbd_interrupt(struct serio *serio, unsigned char data, + unsigned int flags) +{ + struct skbd *skbd = serio_get_drvdata(serio); + struct input_dev *dev = skbd->dev; + + if (skbd->keycode[data & SKBD_KEY_MASK]) { + input_report_key(dev, skbd->keycode[data & SKBD_KEY_MASK], + !(data & SKBD_RELEASE)); + input_sync(dev); + } + + return IRQ_HANDLED; +} + +static int skbd_connect(struct serio *serio, struct serio_driver *drv) +{ + struct skbd *skbd; + struct input_dev *input_dev; + int err = -ENOMEM; + int i; + + skbd = kzalloc(sizeof(struct skbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!skbd || !input_dev) + goto fail1; + + skbd->serio = serio; + skbd->dev = input_dev; + snprintf(skbd->phys, sizeof(skbd->phys), "%s/input0", serio->phys); + memcpy(skbd->keycode, skbd_keycode, sizeof(skbd->keycode)); + + input_dev->name = "Stowaway Keyboard"; + input_dev->phys = skbd->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_STOWAWAY; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + input_dev->keycode = skbd->keycode; + input_dev->keycodesize = sizeof(unsigned char); + input_dev->keycodemax = ARRAY_SIZE(skbd_keycode); + for (i = 0; i < ARRAY_SIZE(skbd_keycode); i++) + set_bit(skbd_keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + + serio_set_drvdata(serio, skbd); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(skbd->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(skbd); + return err; +} + +static void skbd_disconnect(struct serio *serio) +{ + struct skbd *skbd = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(skbd->dev); + kfree(skbd); +} + +static struct serio_device_id skbd_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_STOWAWAY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, skbd_serio_ids); + +static struct serio_driver skbd_drv = { + .driver = { + .name = "stowaway", + }, + .description = DRIVER_DESC, + .id_table = skbd_serio_ids, + .interrupt = skbd_interrupt, + .connect = skbd_connect, + .disconnect = skbd_disconnect, +}; + +static int __init skbd_init(void) +{ + return serio_register_driver(&skbd_drv); +} + +static void __exit skbd_exit(void) +{ + serio_unregister_driver(&skbd_drv); +} + +module_init(skbd_init); +module_exit(skbd_exit); diff --git a/drivers/input/keyboard/sunkbd.c b/drivers/input/keyboard/sunkbd.c new file mode 100644 index 0000000..9fce6d1 --- /dev/null +++ b/drivers/input/keyboard/sunkbd.c @@ -0,0 +1,366 @@ +/* + * Copyright (c) 1999-2001 Vojtech Pavlik + */ + +/* + * Sun keyboard driver for Linux + */ + +/* + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/workqueue.h> + +#define DRIVER_DESC "Sun keyboard driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static unsigned char sunkbd_keycode[128] = { + 0,128,114,129,115, 59, 60, 68, 61, 87, 62, 88, 63,100, 64,112, + 65, 66, 67, 56,103,119, 99, 70,105,130,131,108,106, 1, 2, 3, + 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 41, 14,110,113, 98, 55, + 116,132, 83,133,102, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27,111,127, 71, 72, 73, 74,134,135,107, 0, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 43, 28, 96, 75, 76, 77, 82,136, + 104,137, 69, 42, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,101, + 79, 80, 81, 0, 0, 0,138, 58,125, 57,126,109, 86, 78 +}; + +#define SUNKBD_CMD_RESET 0x1 +#define SUNKBD_CMD_BELLON 0x2 +#define SUNKBD_CMD_BELLOFF 0x3 +#define SUNKBD_CMD_CLICK 0xa +#define SUNKBD_CMD_NOCLICK 0xb +#define SUNKBD_CMD_SETLED 0xe +#define SUNKBD_CMD_LAYOUT 0xf + +#define SUNKBD_RET_RESET 0xff +#define SUNKBD_RET_ALLUP 0x7f +#define SUNKBD_RET_LAYOUT 0xfe + +#define SUNKBD_LAYOUT_5_MASK 0x20 +#define SUNKBD_RELEASE 0x80 +#define SUNKBD_KEY 0x7f + +/* + * Per-keyboard data. + */ + +struct sunkbd { + unsigned char keycode[128]; + struct input_dev *dev; + struct serio *serio; + struct work_struct tq; + wait_queue_head_t wait; + char name[64]; + char phys[32]; + char type; + unsigned char enabled; + volatile s8 reset; + volatile s8 layout; +}; + +/* + * sunkbd_interrupt() is called by the low level driver when a character + * is received. + */ + +static irqreturn_t sunkbd_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct sunkbd* sunkbd = serio_get_drvdata(serio); + + if (sunkbd->reset <= -1) { /* If cp[i] is 0xff, sunkbd->reset will stay -1. */ + sunkbd->reset = data; /* The keyboard sends 0xff 0xff 0xID on powerup */ + wake_up_interruptible(&sunkbd->wait); + goto out; + } + + if (sunkbd->layout == -1) { + sunkbd->layout = data; + wake_up_interruptible(&sunkbd->wait); + goto out; + } + + switch (data) { + + case SUNKBD_RET_RESET: + schedule_work(&sunkbd->tq); + sunkbd->reset = -1; + break; + + case SUNKBD_RET_LAYOUT: + sunkbd->layout = -1; + break; + + case SUNKBD_RET_ALLUP: /* All keys released */ + break; + + default: + if (!sunkbd->enabled) + break; + + if (sunkbd->keycode[data & SUNKBD_KEY]) { + input_report_key(sunkbd->dev, sunkbd->keycode[data & SUNKBD_KEY], !(data & SUNKBD_RELEASE)); + input_sync(sunkbd->dev); + } else { + printk(KERN_WARNING "sunkbd.c: Unknown key (scancode %#x) %s.\n", + data & SUNKBD_KEY, data & SUNKBD_RELEASE ? "released" : "pressed"); + } + } +out: + return IRQ_HANDLED; +} + +/* + * sunkbd_event() handles events from the input module. + */ + +static int sunkbd_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + struct sunkbd *sunkbd = input_get_drvdata(dev); + + switch (type) { + + case EV_LED: + + sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_SETLED); + sunkbd->serio->write(sunkbd->serio, + (!!test_bit(LED_CAPSL, dev->led) << 3) | (!!test_bit(LED_SCROLLL, dev->led) << 2) | + (!!test_bit(LED_COMPOSE, dev->led) << 1) | !!test_bit(LED_NUML, dev->led)); + return 0; + + case EV_SND: + + switch (code) { + + case SND_CLICK: + sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_NOCLICK - value); + return 0; + + case SND_BELL: + sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_BELLOFF - value); + return 0; + } + + break; + } + + return -1; +} + +/* + * sunkbd_initialize() checks for a Sun keyboard attached, and determines + * its type. + */ + +static int sunkbd_initialize(struct sunkbd *sunkbd) +{ + sunkbd->reset = -2; + sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_RESET); + wait_event_interruptible_timeout(sunkbd->wait, sunkbd->reset >= 0, HZ); + if (sunkbd->reset < 0) + return -1; + + sunkbd->type = sunkbd->reset; + + if (sunkbd->type == 4) { /* Type 4 keyboard */ + sunkbd->layout = -2; + sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_LAYOUT); + wait_event_interruptible_timeout(sunkbd->wait, sunkbd->layout >= 0, HZ/4); + if (sunkbd->layout < 0) return -1; + if (sunkbd->layout & SUNKBD_LAYOUT_5_MASK) sunkbd->type = 5; + } + + return 0; +} + +/* + * sunkbd_reinit() sets leds and beeps to a state the computer remembers they + * were in. + */ + +static void sunkbd_reinit(struct work_struct *work) +{ + struct sunkbd *sunkbd = container_of(work, struct sunkbd, tq); + + wait_event_interruptible_timeout(sunkbd->wait, sunkbd->reset >= 0, HZ); + + sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_SETLED); + sunkbd->serio->write(sunkbd->serio, + (!!test_bit(LED_CAPSL, sunkbd->dev->led) << 3) | (!!test_bit(LED_SCROLLL, sunkbd->dev->led) << 2) | + (!!test_bit(LED_COMPOSE, sunkbd->dev->led) << 1) | !!test_bit(LED_NUML, sunkbd->dev->led)); + sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_NOCLICK - !!test_bit(SND_CLICK, sunkbd->dev->snd)); + sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_BELLOFF - !!test_bit(SND_BELL, sunkbd->dev->snd)); +} + +static void sunkbd_enable(struct sunkbd *sunkbd, int enable) +{ + serio_pause_rx(sunkbd->serio); + sunkbd->enabled = enable; + serio_continue_rx(sunkbd->serio); +} + +/* + * sunkbd_connect() probes for a Sun keyboard and fills the necessary structures. + */ + +static int sunkbd_connect(struct serio *serio, struct serio_driver *drv) +{ + struct sunkbd *sunkbd; + struct input_dev *input_dev; + int err = -ENOMEM; + int i; + + sunkbd = kzalloc(sizeof(struct sunkbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!sunkbd || !input_dev) + goto fail1; + + sunkbd->serio = serio; + sunkbd->dev = input_dev; + init_waitqueue_head(&sunkbd->wait); + INIT_WORK(&sunkbd->tq, sunkbd_reinit); + snprintf(sunkbd->phys, sizeof(sunkbd->phys), "%s/input0", serio->phys); + + serio_set_drvdata(serio, sunkbd); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + if (sunkbd_initialize(sunkbd) < 0) { + err = -ENODEV; + goto fail3; + } + + snprintf(sunkbd->name, sizeof(sunkbd->name), "Sun Type %d keyboard", sunkbd->type); + memcpy(sunkbd->keycode, sunkbd_keycode, sizeof(sunkbd->keycode)); + + input_dev->name = sunkbd->name; + input_dev->phys = sunkbd->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_SUNKBD; + input_dev->id.product = sunkbd->type; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_set_drvdata(input_dev, sunkbd); + + input_dev->event = sunkbd_event; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) | + BIT_MASK(EV_SND) | BIT_MASK(EV_REP); + input_dev->ledbit[0] = BIT_MASK(LED_CAPSL) | BIT_MASK(LED_COMPOSE) | + BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_NUML); + input_dev->sndbit[0] = BIT_MASK(SND_CLICK) | BIT_MASK(SND_BELL); + + input_dev->keycode = sunkbd->keycode; + input_dev->keycodesize = sizeof(unsigned char); + input_dev->keycodemax = ARRAY_SIZE(sunkbd_keycode); + for (i = 0; i < 128; i++) + set_bit(sunkbd->keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + + sunkbd_enable(sunkbd, 1); + + err = input_register_device(sunkbd->dev); + if (err) + goto fail4; + + return 0; + + fail4: sunkbd_enable(sunkbd, 0); + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(sunkbd); + return err; +} + +/* + * sunkbd_disconnect() unregisters and closes behind us. + */ + +static void sunkbd_disconnect(struct serio *serio) +{ + struct sunkbd *sunkbd = serio_get_drvdata(serio); + + sunkbd_enable(sunkbd, 0); + input_unregister_device(sunkbd->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(sunkbd); +} + +static struct serio_device_id sunkbd_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_SUNKBD, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_RS232, + .proto = SERIO_UNKNOWN, /* sunkbd does probe */ + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, sunkbd_serio_ids); + +static struct serio_driver sunkbd_drv = { + .driver = { + .name = "sunkbd", + }, + .description = DRIVER_DESC, + .id_table = sunkbd_serio_ids, + .interrupt = sunkbd_interrupt, + .connect = sunkbd_connect, + .disconnect = sunkbd_disconnect, +}; + +/* + * The functions for insering/removing us as a module. + */ + +static int __init sunkbd_init(void) +{ + return serio_register_driver(&sunkbd_drv); +} + +static void __exit sunkbd_exit(void) +{ + serio_unregister_driver(&sunkbd_drv); +} + +module_init(sunkbd_init); +module_exit(sunkbd_exit); diff --git a/drivers/input/keyboard/tosakbd.c b/drivers/input/keyboard/tosakbd.c new file mode 100644 index 0000000..677276b --- /dev/null +++ b/drivers/input/keyboard/tosakbd.c @@ -0,0 +1,430 @@ +/* + * Keyboard driver for Sharp Tosa models (SL-6000x) + * + * Copyright (c) 2005 Dirk Opfer + * Copyright (c) 2007 Dmitry Baryshkov + * + * Based on xtkbd.c/locomkbd.c/corgikbd.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/interrupt.h> + +#include <mach/gpio.h> +#include <mach/tosa.h> + +#define KB_ROWMASK(r) (1 << (r)) +#define SCANCODE(r, c) (((r)<<4) + (c) + 1) +#define NR_SCANCODES SCANCODE(TOSA_KEY_SENSE_NUM - 1, TOSA_KEY_STROBE_NUM - 1) + 1 + +#define SCAN_INTERVAL (HZ/10) + +#define KB_DISCHARGE_DELAY 10 +#define KB_ACTIVATE_DELAY 10 + +static unsigned int tosakbd_keycode[NR_SCANCODES] = { +0, +0, KEY_W, 0, 0, 0, KEY_K, KEY_BACKSPACE, KEY_P, +0, 0, 0, 0, 0, 0, 0, 0, +KEY_Q, KEY_E, KEY_T, KEY_Y, 0, KEY_O, KEY_I, KEY_COMMA, +0, 0, 0, 0, 0, 0, 0, 0, +KEY_A, KEY_D, KEY_G, KEY_U, 0, KEY_L, KEY_ENTER, KEY_DOT, +0, 0, 0, 0, 0, 0, 0, 0, +KEY_Z, KEY_C, KEY_V, KEY_J, TOSA_KEY_ADDRESSBOOK, TOSA_KEY_CANCEL, TOSA_KEY_CENTER, TOSA_KEY_OK, +KEY_LEFTSHIFT, 0, 0, 0, 0, 0, 0, 0, +KEY_S, KEY_R, KEY_B, KEY_N, TOSA_KEY_CALENDAR, TOSA_KEY_HOMEPAGE, KEY_LEFTCTRL, TOSA_KEY_LIGHT, +0, KEY_RIGHTSHIFT, 0, 0, 0, 0, 0, 0, +KEY_TAB, KEY_SLASH, KEY_H, KEY_M, TOSA_KEY_MENU, 0, KEY_UP, 0, +0, 0, TOSA_KEY_FN, 0, 0, 0, 0, 0, +KEY_X, KEY_F, KEY_SPACE, KEY_APOSTROPHE, TOSA_KEY_MAIL, KEY_LEFT, KEY_DOWN, KEY_RIGHT, +0, 0, 0, +}; + +struct tosakbd { + unsigned int keycode[ARRAY_SIZE(tosakbd_keycode)]; + struct input_dev *input; + int suspended; + spinlock_t lock; /* protect kbd scanning */ + struct timer_list timer; +}; + + +/* Helper functions for reading the keyboard matrix + * Note: We should really be using the generic gpio functions to alter + * GPDR but it requires a function call per GPIO bit which is + * excessive when we need to access 12 bits at once, multiple times. + * These functions must be called within local_irq_save()/local_irq_restore() + * or similar. + */ +#define GET_ROWS_STATUS(c) ((GPLR2 & TOSA_GPIO_ALL_SENSE_BIT) >> TOSA_GPIO_ALL_SENSE_RSHIFT) + +static inline void tosakbd_discharge_all(void) +{ + /* STROBE All HiZ */ + GPCR1 = TOSA_GPIO_HIGH_STROBE_BIT; + GPDR1 &= ~TOSA_GPIO_HIGH_STROBE_BIT; + GPCR2 = TOSA_GPIO_LOW_STROBE_BIT; + GPDR2 &= ~TOSA_GPIO_LOW_STROBE_BIT; +} + +static inline void tosakbd_activate_all(void) +{ + /* STROBE ALL -> High */ + GPSR1 = TOSA_GPIO_HIGH_STROBE_BIT; + GPDR1 |= TOSA_GPIO_HIGH_STROBE_BIT; + GPSR2 = TOSA_GPIO_LOW_STROBE_BIT; + GPDR2 |= TOSA_GPIO_LOW_STROBE_BIT; + + udelay(KB_DISCHARGE_DELAY); + + /* STATE CLEAR */ + GEDR2 |= TOSA_GPIO_ALL_SENSE_BIT; +} + +static inline void tosakbd_activate_col(int col) +{ + if (col <= 5) { + /* STROBE col -> High, not col -> HiZ */ + GPSR1 = TOSA_GPIO_STROBE_BIT(col); + GPDR1 = (GPDR1 & ~TOSA_GPIO_HIGH_STROBE_BIT) | TOSA_GPIO_STROBE_BIT(col); + } else { + /* STROBE col -> High, not col -> HiZ */ + GPSR2 = TOSA_GPIO_STROBE_BIT(col); + GPDR2 = (GPDR2 & ~TOSA_GPIO_LOW_STROBE_BIT) | TOSA_GPIO_STROBE_BIT(col); + } +} + +static inline void tosakbd_reset_col(int col) +{ + if (col <= 5) { + /* STROBE col -> Low */ + GPCR1 = TOSA_GPIO_STROBE_BIT(col); + /* STROBE col -> out, not col -> HiZ */ + GPDR1 = (GPDR1 & ~TOSA_GPIO_HIGH_STROBE_BIT) | TOSA_GPIO_STROBE_BIT(col); + } else { + /* STROBE col -> Low */ + GPCR2 = TOSA_GPIO_STROBE_BIT(col); + /* STROBE col -> out, not col -> HiZ */ + GPDR2 = (GPDR2 & ~TOSA_GPIO_LOW_STROBE_BIT) | TOSA_GPIO_STROBE_BIT(col); + } +} +/* + * The tosa keyboard only generates interrupts when a key is pressed. + * So when a key is pressed, we enable a timer. This timer scans the + * keyboard, and this is how we detect when the key is released. + */ + +/* Scan the hardware keyboard and push any changes up through the input layer */ +static void tosakbd_scankeyboard(struct platform_device *dev) +{ + struct tosakbd *tosakbd = platform_get_drvdata(dev); + unsigned int row, col, rowd; + unsigned long flags; + unsigned int num_pressed = 0; + + spin_lock_irqsave(&tosakbd->lock, flags); + + if (tosakbd->suspended) + goto out; + + for (col = 0; col < TOSA_KEY_STROBE_NUM; col++) { + /* + * Discharge the output driver capacitatance + * in the keyboard matrix. (Yes it is significant..) + */ + tosakbd_discharge_all(); + udelay(KB_DISCHARGE_DELAY); + + tosakbd_activate_col(col); + udelay(KB_ACTIVATE_DELAY); + + rowd = GET_ROWS_STATUS(col); + + for (row = 0; row < TOSA_KEY_SENSE_NUM; row++) { + unsigned int scancode, pressed; + scancode = SCANCODE(row, col); + pressed = rowd & KB_ROWMASK(row); + + if (pressed && !tosakbd->keycode[scancode]) + dev_warn(&dev->dev, + "unhandled scancode: 0x%02x\n", + scancode); + + input_report_key(tosakbd->input, + tosakbd->keycode[scancode], + pressed); + if (pressed) + num_pressed++; + } + + tosakbd_reset_col(col); + } + + tosakbd_activate_all(); + + input_sync(tosakbd->input); + + /* if any keys are pressed, enable the timer */ + if (num_pressed) + mod_timer(&tosakbd->timer, jiffies + SCAN_INTERVAL); + + out: + spin_unlock_irqrestore(&tosakbd->lock, flags); +} + +/* + * tosa keyboard interrupt handler. + */ +static irqreturn_t tosakbd_interrupt(int irq, void *__dev) +{ + struct platform_device *dev = __dev; + struct tosakbd *tosakbd = platform_get_drvdata(dev); + + if (!timer_pending(&tosakbd->timer)) { + /** wait chattering delay **/ + udelay(20); + tosakbd_scankeyboard(dev); + } + + return IRQ_HANDLED; +} + +/* + * tosa timer checking for released keys + */ +static void tosakbd_timer_callback(unsigned long __dev) +{ + struct platform_device *dev = (struct platform_device *)__dev; + + tosakbd_scankeyboard(dev); +} + +#ifdef CONFIG_PM +static int tosakbd_suspend(struct platform_device *dev, pm_message_t state) +{ + struct tosakbd *tosakbd = platform_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&tosakbd->lock, flags); + tosakbd->suspended = 1; + spin_unlock_irqrestore(&tosakbd->lock, flags); + + del_timer_sync(&tosakbd->timer); + + return 0; +} + +static int tosakbd_resume(struct platform_device *dev) +{ + struct tosakbd *tosakbd = platform_get_drvdata(dev); + + tosakbd->suspended = 0; + tosakbd_scankeyboard(dev); + + return 0; +} +#else +#define tosakbd_suspend NULL +#define tosakbd_resume NULL +#endif + +static int __devinit tosakbd_probe(struct platform_device *pdev) { + + int i; + struct tosakbd *tosakbd; + struct input_dev *input_dev; + int error; + + tosakbd = kzalloc(sizeof(struct tosakbd), GFP_KERNEL); + if (!tosakbd) + return -ENOMEM; + + input_dev = input_allocate_device(); + if (!input_dev) { + kfree(tosakbd); + return -ENOMEM; + } + + platform_set_drvdata(pdev, tosakbd); + + spin_lock_init(&tosakbd->lock); + + /* Init Keyboard rescan timer */ + init_timer(&tosakbd->timer); + tosakbd->timer.function = tosakbd_timer_callback; + tosakbd->timer.data = (unsigned long) pdev; + + tosakbd->input = input_dev; + + input_set_drvdata(input_dev, tosakbd); + input_dev->name = "Tosa Keyboard"; + input_dev->phys = "tosakbd/input0"; + input_dev->dev.parent = &pdev->dev; + + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + input_dev->keycode = tosakbd->keycode; + input_dev->keycodesize = sizeof(unsigned int); + input_dev->keycodemax = ARRAY_SIZE(tosakbd_keycode); + + memcpy(tosakbd->keycode, tosakbd_keycode, sizeof(tosakbd_keycode)); + + for (i = 0; i < ARRAY_SIZE(tosakbd_keycode); i++) + __set_bit(tosakbd->keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + + /* Setup sense interrupts - RisingEdge Detect, sense lines as inputs */ + for (i = 0; i < TOSA_KEY_SENSE_NUM; i++) { + int gpio = TOSA_GPIO_KEY_SENSE(i); + int irq; + error = gpio_request(gpio, "tosakbd"); + if (error < 0) { + printk(KERN_ERR "tosakbd: failed to request GPIO %d, " + " error %d\n", gpio, error); + goto fail; + } + + error = gpio_direction_input(TOSA_GPIO_KEY_SENSE(i)); + if (error < 0) { + printk(KERN_ERR "tosakbd: failed to configure input" + " direction for GPIO %d, error %d\n", + gpio, error); + gpio_free(gpio); + goto fail; + } + + irq = gpio_to_irq(gpio); + if (irq < 0) { + error = irq; + printk(KERN_ERR "gpio-keys: Unable to get irq number" + " for GPIO %d, error %d\n", + gpio, error); + gpio_free(gpio); + goto fail; + } + + error = request_irq(irq, tosakbd_interrupt, + IRQF_DISABLED | IRQF_TRIGGER_RISING, + "tosakbd", pdev); + + if (error) { + printk("tosakbd: Can't get IRQ: %d: error %d!\n", + irq, error); + gpio_free(gpio); + goto fail; + } + } + + /* Set Strobe lines as outputs - set high */ + for (i = 0; i < TOSA_KEY_STROBE_NUM; i++) { + int gpio = TOSA_GPIO_KEY_STROBE(i); + error = gpio_request(gpio, "tosakbd"); + if (error < 0) { + printk(KERN_ERR "tosakbd: failed to request GPIO %d, " + " error %d\n", gpio, error); + goto fail2; + } + + error = gpio_direction_output(gpio, 1); + if (error < 0) { + printk(KERN_ERR "tosakbd: failed to configure input" + " direction for GPIO %d, error %d\n", + gpio, error); + gpio_free(gpio); + goto fail; + } + + } + + error = input_register_device(input_dev); + if (error) { + printk(KERN_ERR "tosakbd: Unable to register input device, " + "error: %d\n", error); + goto fail; + } + + printk(KERN_INFO "input: Tosa Keyboard Registered\n"); + + return 0; + +fail2: + while (--i >= 0) + gpio_free(TOSA_GPIO_KEY_STROBE(i)); + + i = TOSA_KEY_SENSE_NUM; +fail: + while (--i >= 0) { + free_irq(gpio_to_irq(TOSA_GPIO_KEY_SENSE(i)), pdev); + gpio_free(TOSA_GPIO_KEY_SENSE(i)); + } + + platform_set_drvdata(pdev, NULL); + input_free_device(input_dev); + kfree(tosakbd); + + return error; +} + +static int __devexit tosakbd_remove(struct platform_device *dev) +{ + int i; + struct tosakbd *tosakbd = platform_get_drvdata(dev); + + for (i = 0; i < TOSA_KEY_STROBE_NUM; i++) + gpio_free(TOSA_GPIO_KEY_STROBE(i)); + + for (i = 0; i < TOSA_KEY_SENSE_NUM; i++) { + free_irq(gpio_to_irq(TOSA_GPIO_KEY_SENSE(i)), dev); + gpio_free(TOSA_GPIO_KEY_SENSE(i)); + } + + del_timer_sync(&tosakbd->timer); + + input_unregister_device(tosakbd->input); + + kfree(tosakbd); + + return 0; +} + +static struct platform_driver tosakbd_driver = { + .probe = tosakbd_probe, + .remove = __devexit_p(tosakbd_remove), + .suspend = tosakbd_suspend, + .resume = tosakbd_resume, + .driver = { + .name = "tosa-keyboard", + .owner = THIS_MODULE, + }, +}; + +static int __devinit tosakbd_init(void) +{ + return platform_driver_register(&tosakbd_driver); +} + +static void __exit tosakbd_exit(void) +{ + platform_driver_unregister(&tosakbd_driver); +} + +module_init(tosakbd_init); +module_exit(tosakbd_exit); + +MODULE_AUTHOR("Dirk Opfer <Dirk@Opfer-Online.de>"); +MODULE_DESCRIPTION("Tosa Keyboard Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:tosa-keyboard"); diff --git a/drivers/input/keyboard/xtkbd.c b/drivers/input/keyboard/xtkbd.c new file mode 100644 index 0000000..37b01d7 --- /dev/null +++ b/drivers/input/keyboard/xtkbd.c @@ -0,0 +1,183 @@ +/* + * Copyright (c) 1999-2001 Vojtech Pavlik + */ + +/* + * XT keyboard driver for Linux + */ + +/* + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/serio.h> + +#define DRIVER_DESC "XT keyboard driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define XTKBD_EMUL0 0xe0 +#define XTKBD_EMUL1 0xe1 +#define XTKBD_KEY 0x7f +#define XTKBD_RELEASE 0x80 + +static unsigned char xtkbd_keycode[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 0, 0, 0, 87, 88, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 87, 88, 0, 0, 0, 0,110,111,103,108,105, + 106 +}; + +struct xtkbd { + unsigned char keycode[256]; + struct input_dev *dev; + struct serio *serio; + char phys[32]; +}; + +static irqreturn_t xtkbd_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct xtkbd *xtkbd = serio_get_drvdata(serio); + + switch (data) { + case XTKBD_EMUL0: + case XTKBD_EMUL1: + break; + default: + + if (xtkbd->keycode[data & XTKBD_KEY]) { + input_report_key(xtkbd->dev, xtkbd->keycode[data & XTKBD_KEY], !(data & XTKBD_RELEASE)); + input_sync(xtkbd->dev); + } else { + printk(KERN_WARNING "xtkbd.c: Unknown key (scancode %#x) %s.\n", + data & XTKBD_KEY, data & XTKBD_RELEASE ? "released" : "pressed"); + } + } + return IRQ_HANDLED; +} + +static int xtkbd_connect(struct serio *serio, struct serio_driver *drv) +{ + struct xtkbd *xtkbd; + struct input_dev *input_dev; + int err = -ENOMEM; + int i; + + xtkbd = kmalloc(sizeof(struct xtkbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!xtkbd || !input_dev) + goto fail1; + + xtkbd->serio = serio; + xtkbd->dev = input_dev; + snprintf(xtkbd->phys, sizeof(xtkbd->phys), "%s/input0", serio->phys); + memcpy(xtkbd->keycode, xtkbd_keycode, sizeof(xtkbd->keycode)); + + input_dev->name = "XT Keyboard"; + input_dev->phys = xtkbd->phys; + input_dev->id.bustype = BUS_XTKBD; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + input_dev->keycode = xtkbd->keycode; + input_dev->keycodesize = sizeof(unsigned char); + input_dev->keycodemax = ARRAY_SIZE(xtkbd_keycode); + + for (i = 0; i < 255; i++) + set_bit(xtkbd->keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + + serio_set_drvdata(serio, xtkbd); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(xtkbd->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(xtkbd); + return err; +} + +static void xtkbd_disconnect(struct serio *serio) +{ + struct xtkbd *xtkbd = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(xtkbd->dev); + kfree(xtkbd); +} + +static struct serio_device_id xtkbd_serio_ids[] = { + { + .type = SERIO_XT, + .proto = SERIO_ANY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, xtkbd_serio_ids); + +static struct serio_driver xtkbd_drv = { + .driver = { + .name = "xtkbd", + }, + .description = DRIVER_DESC, + .id_table = xtkbd_serio_ids, + .interrupt = xtkbd_interrupt, + .connect = xtkbd_connect, + .disconnect = xtkbd_disconnect, +}; + +static int __init xtkbd_init(void) +{ + return serio_register_driver(&xtkbd_drv); +} + +static void __exit xtkbd_exit(void) +{ + serio_unregister_driver(&xtkbd_drv); +} + +module_init(xtkbd_init); +module_exit(xtkbd_exit); |