aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/input/keyboard/mp900_kb.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/input/keyboard/mp900_kb.c')
-rw-r--r--drivers/input/keyboard/mp900_kb.c482
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);
+