diff options
Diffstat (limited to 'drivers/input/keyboard/mp900_kb.c')
-rw-r--r-- | drivers/input/keyboard/mp900_kb.c | 482 |
1 files changed, 482 insertions, 0 deletions
diff --git a/drivers/input/keyboard/mp900_kb.c b/drivers/input/keyboard/mp900_kb.c new file mode 100644 index 00000000000000..87ba016decdbd0 --- /dev/null +++ b/drivers/input/keyboard/mp900_kb.c @@ -0,0 +1,482 @@ +/* + * linux/drivers/input/keyboard/mp900_kb.c + * + * input driver for the NEC MobilePro900/c keyboard and touchscreen + * + * keyboard and touchscreen data comes into the processor over + * pxa's BTUART + * + * on keypress 0x12 is received, use this for interrupt + * then poll by sending 0x13 over BTUART waiting for key up + * and to pick up modifier key combos + * + * on touchscreen event (stylus touched to screen) a stream of + * reporting comes through, first byte 0x04 then two bytes X position + * two bytes Y posn - continually until stylus is lifted, then single + * byte 0x05 indicates event end + * + * we get interrupt on character timeout, ie end of received string + * and we're looking for complete, discrete packets (BTUART FIFO holds + * up to 64 bytes) + * + * ignore and discard anything that doesn't read as a clean packet + * + * a little info on the protocol as it's undocumented AFAIK + * -it's a 2 way protocol, send and receive over BTUART + * -when you send 0x13 you receive back a string like + * -0x13 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff + * -single zero bits in the 0xff's indicate a key pressed or held + * -this way presumably any combination of held keys can be reported + * -all bits return to 1 when all keys are released + * -no doubt more 'commands' are possible than just 0x13 !! + * + * thanks to cmonex and friends for recognising that the keyboard is + * on BTUART and providing the initial 'reverse-engineering' ;) + * to decode the protocol + * + * and to TyrianDreams for recognising that the PIC chip on the motherboard + * was an ideal candidate to be doing serial comms ... all the pieces fall + * in to place :) + * + * with reference to jlime's Jornada720 keyboard code + * drivers/input/keyboard/jornada720_kbd.c + * and pxa-serial driver + * drivers/serial/pxa.c + * + * Michael Petchkovsky mkpetch@internode.on.net May 2007 + */ + +/* TODO implement a watchdog while touchscreen event on in case we miss end */ +/* TODO add workqueue event to write to ts maybe combined with watchdog */ + +/* TODO NB I've had some confusion about mapping physical to virtual addresses + * and reading/writing from those locations... code here is working but + * could probably be re-implemented much better. + */ + +/* TODO probably some unnescessary includes here */ +#include <linux/input.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/arch-pxa/pxa-regs.h> +#include <asm/arch-pxa/irqs.h> +#include <asm/arch-pxa/hardware.h> + +MODULE_AUTHOR("Michael Petchkovsky"); +MODULE_DESCRIPTION("MobilePro900/c keyboard driver"); +MODULE_LICENSE("GPL"); + +#define PFX "mp900_keyboard: " + +static char mp900_kb_name[] = "MobilePro900/c keyboard"; +static char mp900_ts_name[] = "MobilePro900/c touchscreen"; + +static struct input_dev *dev_kb, *dev_ts; +static struct workqueue_struct *mp900_kb_workqueue; +static struct work_struct mp900_kb_work; +static unsigned int mp900_keymap[128] = { + 0, 0, KEY_MAIL, KEY_CYCLEWINDOWS, KEY_RECORD, KEY_FN, KEY_ESC, + KEY_1, KEY_9, KEY_Q, KEY_A, KEY_Z, KEY_O, KEY_L, 0, 0, 0, 0, + KEY_WWW, KEY_PROG1, KEY_CALC, KEY_TAB, KEY_DELETE, KEY_2, KEY_0, + KEY_W, KEY_S, KEY_X, KEY_DOT, KEY_ENTER, 0, 0, 0, 0, KEY_CALENDAR, + KEY_PROG2, KEY_BRIGHTNESSUP, KEY_P, KEY_CAPSLOCK, KEY_3, 0, KEY_E, + KEY_D, KEY_C, KEY_DOWN, KEY_RIGHT, 0, 0, 0, 0, KEY_EMAIL, KEY_PROG3, + KEY_BRIGHTNESSDOWN, KEY_BACKSPACE, 0, KEY_4, 0, KEY_R, KEY_F, KEY_V, + KEY_UP, KEY_LEFT, 0, 0, 0, 0, KEY_LEFTSHIFT, 0, 0, 0, KEY_T, KEY_G, + KEY_B, KEY_5, KEY_GRAVE, KEY_MSDOS, KEY_SEMICOLON, KEY_SLASH, 0, 0, + 0, 0, 0, KEY_LEFTCTRL, 0, 0, KEY_Y, KEY_H, KEY_N, KEY_6, KEY_MINUS, + 0, KEY_APOSTROPHE, KEY_BACKSLASH, 0, 0, 0, 0, 0, 0, KEY_LEFTALT, + 0, KEY_U, KEY_J, KEY_M, KEY_7, KEY_EQUAL, 0, KEY_LEFTBRACE, + KEY_RIGHTBRACE, 0, 0, 0, 0, 0, 0, 0, KEY_RIGHTALT, KEY_I, KEY_K, + KEY_COMMA, KEY_8, 0, 0, 0, KEY_SPACE, KEY_POWER, 0 +}; +static unsigned char work_id; +static int exiting=0; +static int keydown=0; + +/* key decoding function + * + * takes a packet of the format + * 0x13XXXXXXXXXXXXXXXXXXXXXXXXXX + * and determines keyboard state, reports that to input layer + * check if all keys are released + */ +static void mp900_kb_decode(char cur_buffer[], int packet_length) +{ + int i, j, ff, keycount=0; + unsigned char keys_buffer[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + static unsigned char last_buffer[32] = {0x13, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0}; + + if (packet_length == 14) // keyboard packet + { + ff = 0; + for (i=1; i < 14; i++) + { + keys_buffer[keycount] = cur_buffer[i] ^ last_buffer[i]; + if (cur_buffer[i] == 0xff) + ff++; + j = 0; + while (keys_buffer[keycount]) + { + /* keyboard state has changed */ + if (keys_buffer[keycount] & 1) + { + keys_buffer[keycount+1] = + keys_buffer[keycount]; + /* TODO adjust mp900_keymap by 1 */ + keys_buffer[keycount] = j * 16 + i + 1; + if ((1<<j) & cur_buffer[i]) + keys_buffer[keycount]+=128; + keycount++; + } + + keys_buffer[keycount]>>=1; + j++; + } + + if (keycount > 5) + { + /* assume we have garbage packet */ + keycount = 0; + ff = 0; + break; + } + } + + if (keycount) + { + for (i=1;i<14;i++) + { + last_buffer[i] = cur_buffer[i]; + } + } + + while (keycount) + { + keycount--; + if (keys_buffer[keycount] < 128) + { + input_report_key(dev_kb, + mp900_keymap[keys_buffer[keycount]], + 1); + input_sync(dev_kb); + } + else + { + input_report_key(dev_kb, + mp900_keymap[keys_buffer[keycount]-128], + 0); + input_sync(dev_kb); + } + keys_buffer[keycount] = 0; + } + + /* will we poll for more? */ + keydown = (ff != 13); + } +} + +/* ts_report + * + * receives a touchscreen packet + * 0x04XXYY + * and reports position to input layer + */ +static void mp900_ts_report(char cur_buffer[], int packet_length) +{ + int x,y; + + if (packet_length == 5) // touchscreen packet + { + x = cur_buffer[2] + 256 * cur_buffer[1]; +// x = (x * 32 / 45) - 30; +// x = (x * 16 / 45) - 30; + y = cur_buffer[4] + 256 * cur_buffer[3]; +// y = 275 - (y * 24 / 69); +// y = 275 - (y * 12 / 69); + + input_report_key(dev_ts, BTN_TOUCH, 1); + input_report_abs(dev_ts, ABS_X, x); + input_report_abs(dev_ts, ABS_Y, y); + input_report_abs(dev_ts, ABS_PRESSURE, 1); + input_sync(dev_ts); + } +} + +/* + * workqueue function polls for keyboard state, + * queue it with delay + */ +static void mp900_kb_poll(void *mp_work_id) +{ + writel(0x13, (void *)&BTTHR); +} + +/* Interrupt handler + * + * character timeout interrupt should occur on receipt of a string from BTUART + * -string could be single 0x12 ie new keypress + * -0x13xxxxxxxxxxxxxxxxxxxxxxxxxx result of keypoll + * -single 0x05 ie end of touchscreen event + * -0x04XXYY touchscreen absolute position + * + * regard any other string as error and drop it + */ +static irqreturn_t mp900_kb_interrupt(int irq, + void *dev_id, + struct pt_regs *regs) +{ + int i, j; + char packet_buffer[32] = {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}; + + /* TODO pause serial traffic if you can */ + writel(0x08, (void *)&BTMCR); // MCR say not RTS + + /* first check for BTUART FIFO overflow */ + if (readl((void *)&BTLSR) & 0x02) + { + writel(0xC7, (void *)&BTFCR); // flush FIFO + printk(KERN_ERR PFX "btuart FIFO overflow\n"); + if (exiting == 0) + { + schedule_delayed_work(&mp900_kb_work, 4); + } + writel(0x0a, (void *)&BTMCR); // MCR RTS + return IRQ_HANDLED; + } + + /* check for FIFO trigger level reached ?? */ + /* check for parity/framing/break errors */ + + /* next read out FIFO contents, count chars */ + i=0; + + while (readl((void *)&BTLSR) & 0x01) // FIFO is not empty + { + packet_buffer[i] = readl((void *)&BTRBR); + if (++i > 14) + break; + } + + switch (i) + { + case 1: + if (packet_buffer[0] == 0x05) + { + /* end ts event */ + input_report_key(dev_ts, BTN_TOUCH, 0); + input_report_abs(dev_ts, ABS_PRESSURE, 0); + input_sync(dev_ts); + /* TODO delay this on a workqueue */ + writel(0x01, (void *)&BTTHR); + writel(0x0a, (void *)&BTMCR); // MCR RTS + return IRQ_HANDLED; + } + else if (packet_buffer[0] == 0x12) + { + if (keydown) + break; + else + { + keydown = 1; + if (exiting == 0) + { + schedule_delayed_work(&mp900_kb_work, 2); + } + break; + } + } + + break; + + case 5: + if (packet_buffer[0] == 0x04) + { + /* touchscreen event */ + /* TODO watchdog */ + /* call ts-report */ + mp900_ts_report(packet_buffer, i); + } + break; + + case 14: + if (packet_buffer[0] == 0x13) + { + /* keyboard packet */ + /* call key decode function */ + mp900_kb_decode(packet_buffer, i); + + if (keydown) + { + if (exiting == 0) + { + schedule_delayed_work(&mp900_kb_work, 2); + } + } + } + break; + + default: +// printk(KERN_ERR PFX "Unexpected packet length %d\n", i); +// for (j=0; j < i; j++) +// { +// printk("0x%02x ", packet_buffer[j]); +// } +// printk("\n"); + + if (keydown) + { + if (exiting == 0) + { + schedule_delayed_work(&mp900_kb_work, 2); + } + } + + break; + } + + writel(0x0a, (void *)&BTMCR); // MCR RTS + return IRQ_HANDLED; +} + +/* + * need to unregister device and free irq on exit + */ +static void __exit mp900_kb_exit(void) +{ + int i; + + exiting = 1; + cancel_delayed_work(&mp900_kb_work); + flush_workqueue(mp900_kb_workqueue); + destroy_workqueue(mp900_kb_workqueue); + + /* stop the BTUART clock */ + /* NB see arch/arm/mach-pxa/generic.c for pxa_set_cken() + * a much better way to do this... + */ + i = readl((void *)&CKEN) & 0xFFFF; + i &= 0xFFEF; + writel(i, (void *)&CKEN); + + free_irq(IRQ_BTUART, NULL); + input_unregister_device(dev_ts); + input_unregister_device(dev_kb); + printk(KERN_INFO PFX "devices removed\n"); +} + +/* + * initialize + * ought to test kb presence + * + * need to disallow userland access to BTUART while keyboard driver + * is operational ... it should be enough to set up a udev rule to + * keep /dev/ttyS1 out of /dev - because pxa-serial driver leaves + * interrupts alone except while someone reads or writes to /dev/ttySx ??? + * + * will need to set baud rate, FIFO, LCR, MCR, interrupt type in + * BTUART registers + * + */ +static int __init mp900_kb_init(void) +{ + int i; + + mp900_kb_workqueue = create_workqueue("poll4key"); + INIT_WORK(&mp900_kb_work, mp900_kb_poll, &work_id); + + printk(KERN_INFO PFX "initializing keyboard and touchscreen\n"); + + /* enable BTUART clock */ + /* NB see arch/arm/mach-pxa/generic.c for pxa_set_cken() + * a much better way to do this... + */ + i = readl((void *)&CKEN) & 0xFFFF; + i |= 0x80; + writel(i, (void *)&CKEN); + + /* set up two devices for keyboard and touchscreen */ + dev_kb = input_allocate_device(); + dev_kb->evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + dev_kb->keybit[LONG(KEY_SUSPEND)] |= BIT(KEY_SUSPEND); + dev_kb->name = mp900_kb_name; + for (i=0; i < 128; i++) + if (mp900_keymap[i]) + set_bit(mp900_keymap[i], dev_kb->keybit); + + dev_ts = input_allocate_device(); + dev_ts->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + dev_ts->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE); + dev_ts->keybit[LONG(BTN_TOUCH)] = BIT(BTN_TOUCH); + dev_ts->absmin[ABS_X] = 32; + dev_ts->absmin[ABS_Y] = 96; + dev_ts->absmax[ABS_X] = 944; + dev_ts->absmax[ABS_Y] = 792; +// dev_ts->absmin[ABS_X] = 0; +// dev_ts->absmin[ABS_Y] = 0; +// dev_ts->absmax[ABS_X] = 640; +// dev_ts->absmax[ABS_Y] = 240; + dev_ts->name = mp900_ts_name; + + input_register_device(dev_kb); + input_register_device(dev_ts); + + if (request_irq(IRQ_BTUART, mp900_kb_interrupt, 0, + "MobilePro900/c keyboard", NULL)) + printk(KERN_ERR PFX "request irq failed!\n"); + + /* set up BTUART regs */ + + /* clear FIFO,clear interrupt regs (by reading) + * set LCR, set MCR, enable interrupts (IER) + * clear interrupt regs again + * that's roughly how pxa-serial.c does it... + */ + writel(0x03, (void *)&BTLCR); // LCR DLAB bit=0 + writel(0xC1, (void *)&BTFCR); // FCR enable, trigger level 32 bytes + writel(0xC7, (void *)&BTFCR); // FCR clear + readl((void *)&BTLSR); // read/clear LSR + readl((void *)&BTRBR); // read/clear RX + readl((void *)&BTIIR); // read/clear IIR + readl((void *)&BTMSR); // read/clear MSR + writel(0x03, (void *)&BTLCR); // LCR again + + /* do we need to spinlock for this ?? + * once OUT2 bit is set interrupt is LIVE + */ + writel(0x0a, (void *)&BTMCR); // MCR OUT2, RTS + + /* what type of interrupts do we catch ? */ + writel(0x51, (void *)&BTIER); // IER UUE, RAVIE, RTOIE (char timeout) + /* TODO - what causes an RLSE interrupt? */ +// writel(0x41, (void *)&BTIER); // IER UUE, RAVIE +// writel(0x55, (void *)&BTIER); // IER UUE, RAVIE, RLSE, RTOIE + + /* once again for good luck ?? */ + readl((void *)&BTLSR); + readl((void *)&BTRBR); + readl((void *)&BTIIR); + readl((void *)&BTMSR); + + printk(KERN_INFO PFX "registers, and irq set up\n"); + + return 0; +} + +module_init(mp900_kb_init); +module_exit(mp900_kb_exit); + |