aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/input/keyboard/mp900_kbd.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/input/keyboard/mp900_kbd.c')
-rw-r--r--drivers/input/keyboard/mp900_kbd.c480
1 files changed, 480 insertions, 0 deletions
diff --git a/drivers/input/keyboard/mp900_kbd.c b/drivers/input/keyboard/mp900_kbd.c
new file mode 100644
index 00000000000000..337ec3bbf810de
--- /dev/null
+++ b/drivers/input/keyboard/mp900_kbd.c
@@ -0,0 +1,480 @@
+/* linux/drivers/input/keyboard/mp900_kbd.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 more 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 revisit the way you do all those readl() and writel()'s */
+/* TODO trim comments ruthlessly ;) info on protocol
+ * could go in Documentation/
+ */
+
+/* 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 delayed_work mp900_kb_work;
+/* TODO LCD closure button generates a keycode,
+ * determine where it goes in here
+ */
+
+/* TODO : allocate memory for keymap so we can change it at runtime */
+static unsigned int mp900_keymap[128] = {
+ 0, 0, KEY_F1, KEY_F5, KEY_F9, KEY_FN, KEY_ESC,
+ KEY_1, KEY_9, KEY_Q, KEY_A, KEY_Z, KEY_O, KEY_L, 0, 0, 0, 0,
+ KEY_F2, KEY_F6, KEY_F10, KEY_TAB, KEY_DELETE, KEY_2, KEY_0,
+ KEY_W, KEY_S, KEY_X, KEY_DOT, KEY_ENTER, 0, 0, 0, 0, KEY_F3,
+ KEY_F7, KEY_BRIGHTNESSUP, KEY_P, KEY_CAPSLOCK, KEY_3, 0, KEY_E,
+ KEY_D, KEY_C, KEY_DOWN, KEY_RIGHT, 0, 0, 0, 0, KEY_F4, KEY_F8,
+ 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
+};
+/* trialing a different keymapping where special function keys
+ * are read as keys F1-F10 above
+ *
+ * below is the array where special function keys are 'special functions'
+ *
+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 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
+ *
+ * TODO nesting gets too deep in these functions
+ */
+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) {
+ 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) {
+ x = cur_buffer[2] + 256 * cur_buffer[1];
+ y = cur_buffer[4] + 256 * cur_buffer[3];
+
+ 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(struct work_struct *work)
+{
+ 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 garbage and drop it
+ */
+static irqreturn_t mp900_kb_interrupt(int irq,void *dev_id)
+{
+ int i;
+ int count;
+ 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) {
+ packet_buffer[i] = readl((void *)&BTRBR);
+ if (++i > 14)
+ break;
+ }
+
+ /* Switch between buffer size */
+ switch (i) {
+ case 1:
+ if (packet_buffer[0] == 0x05) { /* End touchscreen event */
+ input_report_key(dev_ts, BTN_TOUCH, 0);
+ input_report_abs(dev_ts, ABS_PRESSURE, 0);
+ input_sync(dev_ts);
+
+ writel(0x01, (void *)&BTTHR);
+ writel(0x0a, (void *)&BTMCR);
+ return IRQ_HANDLED;
+ }
+ if (packet_buffer[0] == 0x12) { /* Keypress */
+ 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 X/Y event */
+ mp900_ts_report(packet_buffer, i);
+ }
+ break;
+
+// case 10:
+// printk(KERN_ERR "btuart : Package size 10\n");
+// printk(KERN_ERR "btuart_log : %s\n", packet_buffer);
+// break;
+
+ case 14:
+ if (packet_buffer[0] == 0x13) { /* Keyboard poll packet */
+ mp900_kb_decode(packet_buffer, i);
+
+ if (keydown) {
+ if (exiting == 0) {
+ schedule_delayed_work(
+ &mp900_kb_work,
+ 2);
+ }
+ }
+ }
+ break;
+// case 15:
+// printk(KERN_ERR "btuart : Package size 15\n");
+// printk(KERN_ERR "btuart_log : %s\n", packet_buffer);
+// break;
+
+ default:
+ /* make sure ts is clear to transmit */
+ writel(0x01, (void *)&BTTHR);
+
+ if (keydown) {
+ if (exiting == 0) {
+ schedule_delayed_work(&mp900_kb_work,
+ 2);
+ }
+ } else {
+ printk(KERN_ERR "unhandled size of package (size=%d) with [0]=%x\n", i, packet_buffer[0]);
+ }
+ break;
+ }
+
+ writel(0x0a, (void *)&BTMCR); // MCR RTS
+ return IRQ_HANDLED;
+}
+
+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 */
+ /* TODO see arch/arm/mach-pxa/generic.c for pxa_set_cken()
+ * a much nicer 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_DELAYED_WORK(&mp900_kb_work, mp900_kb_poll);
+
+ printk(KERN_INFO PFX "initializing keyboard and touchscreen\n");
+
+ /* enable BTUART clock */
+ /* TODO see arch/arm/mach-pxa/generic.c for pxa_set_cken()
+ * a much nicer way to do this...
+ */
+ i = readl((void *)&CKEN) & 0xFFFF;
+ i |= 0x80;
+ writel(i, (void *)&CKEN);
+
+ /* Keyboard */
+ dev_kb = input_allocate_device();
+ dev_kb->evbit[0] = BIT(EV_KEY) | BIT(EV_REP);
+ set_bit(KEY_SUSPEND, dev_kb->keybit);
+ dev_kb->name = mp900_kb_name;
+ for (i=0; i < 128; i++)
+ if (mp900_keymap[i])
+ set_bit(mp900_keymap[i], dev_kb->keybit);
+
+ /* Touchscreen */
+ 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);
+ set_bit(BTN_TOUCH, dev_ts->keybit);
+
+ /* Not generic values, will need adjusting */
+ dev_ts->absmin[ABS_X] = 32;
+ dev_ts->absmin[ABS_Y] = 96;
+ dev_ts->absmax[ABS_X] = 1004;
+ dev_ts->absmax[ABS_Y] = 792;
+ 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...
+ *
+ * TODO oh boy this looks messy...
+ */
+ 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);
+
+ /* make sure ts is clear to transmit */
+ writel(0x01, (void *)&BTTHR);
+
+ printk(KERN_INFO PFX "registers, and irq set up\n");
+
+ return 0;
+}
+
+module_init(mp900_kb_init);
+module_exit(mp900_kb_exit);
+