diff options
Diffstat (limited to 'target/linux/olpc/files/drivers/input/mouse')
-rw-r--r-- | target/linux/olpc/files/drivers/input/mouse/olpc.c | 554 | ||||
-rw-r--r-- | target/linux/olpc/files/drivers/input/mouse/olpc.h | 49 |
2 files changed, 603 insertions, 0 deletions
diff --git a/target/linux/olpc/files/drivers/input/mouse/olpc.c b/target/linux/olpc/files/drivers/input/mouse/olpc.c new file mode 100644 index 0000000000..dc575ac062 --- /dev/null +++ b/target/linux/olpc/files/drivers/input/mouse/olpc.c @@ -0,0 +1,554 @@ +/* + * OLPC touchpad PS/2 mouse driver + * + * Copyright (c) 2006 One Laptop Per Child, inc. + * Authors Zephaniah E. Hull and Andres Salomon <dilinger@laptop.org> + * + * This driver is partly based on the ALPS driver, which is: + * + * Copyright (c) 2003 Neil Brown <neilb@cse.unsw.edu.au> + * Copyright (c) 2003-2005 Peter Osterlund <petero2@telia.com> + * Copyright (c) 2004 Dmitry Torokhov <dtor@mail.ru> + * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz> + * + * 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. + */ + +/* + * The touchpad on the OLPC is fairly wide, with the entire area usable + * as a tablet ("PT mode"), and the center 1/3rd also usable as a touchpad + * ("GS mode"). + * + * Earlier version of the device had simultaneous reporting; however, that + * was removed. Instead, the device now reports packets in one mode, and + * tells the driver when a mode switch needs to happen. + */ + +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/libps2.h> +#include <linux/delay.h> +#include <asm/olpc.h> + +#include "psmouse.h" +#include "olpc.h" + +static int tpdebug; +module_param(tpdebug, int, 0644); + +#define OLPC_GS 1 /* The GS sensor. */ +#define OLPC_PT 2 /* The PT sensor. */ + +static struct olpc_model_info olpc_model_data[] = { + { { 0x67, 0x00, 0x00 }, OLPC_GS | OLPC_PT }, /* unknown ID */ + { { 0x67, 0x00, 0x0a }, OLPC_GS | OLPC_PT }, /* pre-B1 */ + { { 0x67, 0x00, 0x14 }, OLPC_GS }, /* B1.*/ + { { 0x67, 0x00, 0x28 }, OLPC_GS | OLPC_PT }, /* B2 */ + { { 0x67, 0x00, 0x3c }, OLPC_GS | OLPC_PT }, /* B2-2 */ + { { 0x67, 0x00, 0x50 }, OLPC_GS | OLPC_PT }, /* C1 */ +}; + +#define OLPC_PKT_PT 0xcf +#define OLPC_PKT_GS 0xff + +static int olpc_absolute_mode(struct psmouse *psmouse, int mode); + +/* + * OLPC absolute Mode - single-mode format + * + * byte 0: 1 1 0 0 1 1 1 1 + * byte 1: 0 x6 x5 x4 x3 x2 x1 x0 + * byte 2(PT): 0 0 x9 x8 x7 ? pt-dsw gs-dsw + * byte 2(GS): 0 x10 x9 x8 x7 ? gs-dsw pt-dsw + * byte 3: 0 y9 y8 y7 1 0 swr swl + * byte 4: 0 y6 y5 y4 y3 y2 y1 y0 + * byte 5: 0 z6 z5 z4 z3 z2 z1 z0 + * + * ?'s are not defined in the protocol spec, may vary between models. + * + * swr/swl are the left/right buttons. + * + * pt-dsw/gs-dsw indicate that the pt/gs sensor is detecting a + * pen/finger + */ + +static void olpc_process_packet_gspt(struct psmouse *psmouse) +{ + struct olpc_data *priv = psmouse->private; + unsigned char *packet = psmouse->packet; + struct input_dev *dev = psmouse->dev; + struct input_dev *dev2 = priv->dev2; + int x, y, z, gs_down = 0, pt_down = 0, left, right; + struct timeval now_tv; + s64 now_ns; + + left = packet[3] & 1; + right = packet[3] & 2; + x = packet[1] | ((packet[2] & 0x78) << 4); + y = packet[4] | ((packet[3] & 0x70) << 3); + z = packet[5]; + + if (psmouse->packet[0] == OLPC_PKT_GS) { + pt_down = !!(packet[2] & 1); + gs_down = !!(packet[2] & 2); + } else if (psmouse->packet[0] == OLPC_PKT_PT) { + gs_down = !!(packet[2] & 1); + pt_down = !!(packet[2] & 2); + } + + /* + * XXX: Kludge. + * If it's been more than 30ms since the last packet, + * assume that there was a lift we were never told about. + */ + do_gettimeofday(&now_tv); + now_ns = timeval_to_ns (&now_tv); + if (now_ns >= priv->late) { + input_report_key(dev, BTN_TOUCH, 0); + input_report_key(dev, BTN_TOOL_PEN, 0); + input_report_key(dev2, BTN_TOUCH, 0); + input_report_key(dev2, BTN_TOOL_FINGER, 0); + + input_sync(dev); + input_sync(dev2); + } + + priv->late = now_ns + (30 * NSEC_PER_MSEC); + + + if (tpdebug) { + printk(KERN_DEBUG "%s %02x %02x %02x %02x %02x %02x\n", + __FUNCTION__, psmouse->packet[0], psmouse->packet[1], + psmouse->packet[2], psmouse->packet[3], psmouse->packet[4], + psmouse->packet[5]); + printk(KERN_DEBUG "l=%d r=%d p=%d g=%d x=%d y=%d z=%d\n", + left, right, pt_down, gs_down, x, y, z); + } + + if (psmouse->packet[0] == OLPC_PKT_PT) { + input_report_key(dev, BTN_LEFT, left); + input_report_key(dev, BTN_RIGHT, right); + } else if (psmouse->packet[0] == OLPC_PKT_GS) { + input_report_key(dev, BTN_LEFT, left); + input_report_key(dev, BTN_RIGHT, right); + input_report_key(dev2, BTN_LEFT, left); + input_report_key(dev2, BTN_RIGHT, right); + } + + input_report_key(dev, BTN_TOUCH, pt_down); + input_report_key(dev, BTN_TOOL_PEN, pt_down); + input_report_key(dev2, BTN_TOUCH, gs_down); + input_report_key(dev2, BTN_TOOL_FINGER, gs_down); + + input_report_abs(dev2, ABS_PRESSURE, z); + + if (psmouse->packet[0] == OLPC_PKT_PT && pt_down) { + input_report_abs(dev, ABS_X, x); + input_report_abs(dev, ABS_Y, y); + } else if (psmouse->packet[0] == OLPC_PKT_GS && gs_down) { + input_report_abs(dev2, ABS_X, x); + input_report_abs(dev2, ABS_Y, y); + } + + input_sync(dev); + input_sync(dev2); + + if (priv->pending_mode == OLPC_GS && + psmouse->packet[0] == OLPC_PKT_PT && pt_down) { + priv->pending_mode = 0; + cancel_delayed_work(&priv->mode_switch); + } + + if (priv->i->flags & (OLPC_PT|OLPC_GS)) { + int pending = 0; + if (psmouse->packet[0] == OLPC_PKT_PT && !pt_down) + pending = OLPC_GS; + else if (psmouse->packet[0] == OLPC_PKT_GS && pt_down) + pending = OLPC_PT; + + if (priv->current_mode == pending) { + priv->pending_mode = 0; + pending = priv->current_mode; + } + else if (priv->pending_mode != pending) { + priv->pending_mode = pending; + if (tpdebug) + printk(KERN_WARNING "Scheduling mode switch to %s.\n", + pending == OLPC_GS ? "GS" : "PT"); + + /* + * Apply a de-bounce when switching from PT to GS, to allow for + * spurious PT-up packets. + */ + if (priv->pending_mode == OLPC_GS) + queue_delayed_work(kpsmoused_wq, &priv->mode_switch, msecs_to_jiffies(50)); + else + queue_delayed_work(kpsmoused_wq, &priv->mode_switch, 0); + } + } +} + +static psmouse_ret_t olpc_process_byte(struct psmouse *psmouse) +{ + psmouse_ret_t ret = PSMOUSE_BAD_DATA; + + if (psmouse->packet[0] != OLPC_PKT_PT && + psmouse->packet[0] != OLPC_PKT_GS) + goto out; + + /* Bytes 2 - 6 should have 0 in the highest bit */ + if (psmouse->pktcnt >= 2 && psmouse->pktcnt <= 6 && + (psmouse->packet[psmouse->pktcnt - 1] & 0x80)) + goto out; + + if (psmouse->pktcnt == 6) { + olpc_process_packet_gspt(psmouse); + ret = PSMOUSE_FULL_PACKET; + goto out; + } + + ret = PSMOUSE_GOOD_DATA; +out: + if (ret != PSMOUSE_GOOD_DATA && ret != PSMOUSE_FULL_PACKET) + printk(KERN_DEBUG "%s: (%d) %02x %02x %02x %02x %02x %02x\n", + __FUNCTION__, psmouse->pktcnt, psmouse->packet[0], + psmouse->packet[1], psmouse->packet[2], + psmouse->packet[3], psmouse->packet[4], + psmouse->packet[5]); + return ret; +} + +static struct olpc_model_info *olpc_get_model(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[4]; + int i; + + /* + * Now try "E7 report". Allowed responses are in + * olpc_model_data[].signature + */ + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21)) + return NULL; + + param[0] = param[1] = param[2] = 0xff; + if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) + return NULL; + + pr_debug("olpc.c(%d): E7 report: %2.2x %2.2x %2.2x", + __LINE__, param[0], param[1], param[2]); + + for (i = 0; i < ARRAY_SIZE(olpc_model_data); i++) { + if (!memcmp(param, olpc_model_data[i].signature, + sizeof(olpc_model_data[i].signature))) { + printk(KERN_INFO __FILE__ ": OLPC touchpad revision 0x%x.\n", param[2]); + return olpc_model_data + i; + } + } + + /* + * ALPS creates new IDs pretty frequently; rather than listing them + * all, just assume they support the defaults. We've set aside the + * first entry of olpc_model_data as the catch-all. + */ + if (!memcmp(param, olpc_model_data[0].signature, 2)) { + printk(KERN_INFO __FILE__ ": unknown ALPS revision %x, assuming default flags.\n", param[2]); + return &olpc_model_data[0]; + } + + return NULL; +} + +static int olpc_find_mode(struct psmouse *psmouse) +{ + struct olpc_data *priv = psmouse->private; + int mode = priv->i->flags; + + if (mode & OLPC_GS) + mode = OLPC_GS; + else if (mode & OLPC_PT) + mode = OLPC_PT; + else + mode = -1; + + return mode; +} + +/* + * Touchpad should be disabled before calling this! + */ +static int olpc_new_mode(struct psmouse *psmouse, int mode) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + struct olpc_data *priv = psmouse->private; + unsigned char param; + int ret; + + if (tpdebug) + printk(KERN_WARNING __FILE__ ": Switching to %d. [%lu]\n", mode, jiffies); + + if ((ret = ps2_command(ps2dev, ¶m, 0x01F2))) + goto failed; + if ((ret = ps2_command(ps2dev, ¶m, 0x01F2))) + goto failed; + if ((ret = ps2_command(ps2dev, ¶m, 0x01F2))) + goto failed; + + switch (mode) { + default: + printk(KERN_WARNING __FILE__ ": Invalid mode %d. Defaulting to OLPC_GS.\n", mode); + case OLPC_GS: + ret = ps2_command(ps2dev, NULL, 0xE6); + break; + case OLPC_PT: + ret = ps2_command(ps2dev, NULL, 0xE7); + break; + } + if (ret) + goto failed; + + /* XXX: This is a bit hacky, make sure this isn't screwing stuff up. */ + psmouse->pktcnt = psmouse->out_of_sync = 0; + psmouse->last = jiffies; + psmouse->state = PSMOUSE_ACTIVATED; + + if ((ret = ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE))) + goto failed; + + priv->current_mode = mode; + priv->pending_mode = 0; + if (tpdebug) + printk(KERN_WARNING __FILE__ ": Switched to mode %d successful.\n", mode); + +failed: + if (ret) + printk(KERN_WARNING __FILE__ ": Mode switch to %d failed! (%d) [%lu]\n", mode, ret, jiffies); + return ret; +} + +static int olpc_absolute_mode(struct psmouse *psmouse, int mode) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + + /* Switch to 'Advanced mode.', four disables in a row. */ + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE)) + return -1; + + return olpc_new_mode(psmouse, mode); +} + +/* + * olpc_poll() - poll the touchpad for current motion packet. + * Used in resync. + * Note: We can't poll, so always return failure. + */ +static int olpc_poll(struct psmouse *psmouse) +{ + return -1; +} + +static int olpc_reconnect(struct psmouse *psmouse) +{ + struct olpc_data *priv = psmouse->private; + int mode; + + if (olpc_rev_after(OLPC_REV_B2)) + if (psmouse->ps2dev.serio->dev.power.power_state.event != PM_EVENT_ON) + return 0; + + psmouse_reset(psmouse); + + if (!(priv->i = olpc_get_model(psmouse))) + return -1; + + mode = olpc_find_mode(psmouse); + if (mode < 0) + return -1; + + if (olpc_absolute_mode(psmouse, mode)) { + printk(KERN_ERR __FILE__ ": Failed to reenable absolute mode.\n"); + return -1; + } + + return 0; +} + +static void olpc_disconnect(struct psmouse *psmouse) +{ + struct olpc_data *priv = psmouse->private; + + psmouse_reset(psmouse); + input_unregister_device(priv->dev2); + kfree(priv); +} + +static void olpc_mode_switch(struct work_struct *w) +{ + struct delayed_work *work = container_of(w, struct delayed_work, work); + struct olpc_data *priv = container_of(work, struct olpc_data, mode_switch); + struct psmouse *psmouse = priv->psmouse; + struct ps2dev *ps2dev = &psmouse->ps2dev; + int pending_mode, ret; + + if (priv->pending_mode == priv->current_mode) { + priv->pending_mode = 0; + printk (KERN_DEBUG __FILE__ ": In switch_mode, no target mode.\n"); + return; + } + + if (tpdebug) + printk(KERN_WARNING __FILE__ ": Disable for switch to %d. [%lu]\n", priv->pending_mode, jiffies); + + /* XXX: This is a bit hacky, make sure this isn't screwing stuff up. */ + psmouse->state = PSMOUSE_INITIALIZING; + + ret = ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE); + if (ret) { + /* XXX: if this ever fails, we need to do a full reset! */ + printk(KERN_WARNING __FILE__ ": Disable failed for switch to %d. (%d) [%lu]\n", priv->pending_mode, ret, jiffies); + return; + } + + /* + * ALPS tells us that it may take up to 20msec for the disable to + * take effect; however, ps2_command() will wait up to 200msec for + * the ACK to come back (and I'm assuming that by the time the + * hardware sends back its ACK, it has stopped sending bytes). + */ + pending_mode = priv->pending_mode; + + if (olpc_new_mode(psmouse, priv->pending_mode)) + goto bad; + + /* + * Deal with a potential race condition. + * + * If there is a brief tap of a stylus or a fingernail that + * triggers a mode switch to PT mode, and the stylus/fingernail is + * lifted after the DISABLE above, but before we reenable in the new mode, + * then we can get stuck in PT mode. + */ + if (pending_mode == OLPC_PT) { + priv->pending_mode = OLPC_GS; + queue_delayed_work(kpsmoused_wq, &priv->mode_switch, msecs_to_jiffies(50)); + } + + return; + +bad: + printk(KERN_WARNING __FILE__ ": Failure to switch modes, resetting device...\n"); + olpc_reconnect(psmouse); +} + +int olpc_init(struct psmouse *psmouse) +{ + struct olpc_data *priv; + struct input_dev *dev = psmouse->dev; + struct input_dev *dev2; + int mode; + + priv = kzalloc(sizeof(struct olpc_data), GFP_KERNEL); + dev2 = input_allocate_device(); + if (!priv || !dev2) + goto init_fail; + + psmouse->private = priv; + priv->dev2 = dev2; + priv->psmouse = psmouse; + + psmouse_reset(psmouse); + if (!(priv->i = olpc_get_model(psmouse))) + goto init_fail; + + mode = olpc_find_mode(psmouse); + if (mode < 0) { + printk(KERN_ERR __FILE__ ": Failed to identify proper mode\n"); + goto init_fail; + } + + if (olpc_absolute_mode(psmouse, mode)) { + printk(KERN_ERR __FILE__ ": Failed to enable absolute mode\n"); + goto init_fail; + } + + /* + * Unset some of the default bits for things we don't have. + */ + dev->evbit[LONG(EV_REL)] &= ~BIT(EV_REL); + dev->relbit[LONG(REL_X)] &= ~(BIT(REL_X) | BIT(REL_Y)); + dev->keybit[LONG(BTN_MIDDLE)] &= ~BIT(BTN_MIDDLE); + + dev->evbit[LONG(EV_KEY)] |= BIT(EV_KEY); + dev->keybit[LONG(BTN_TOUCH)] |= BIT(BTN_TOUCH); + dev->keybit[LONG(BTN_TOOL_PEN)] |= BIT(BTN_TOOL_PEN); + dev->keybit[LONG(BTN_LEFT)] |= BIT(BTN_LEFT) | BIT(BTN_RIGHT); + + dev->evbit[LONG(EV_ABS)] |= BIT(EV_ABS); + input_set_abs_params(dev, ABS_X, 2, 1000, 0, 0); + input_set_abs_params(dev, ABS_Y, 0, 717, 0, 0); + + snprintf(priv->phys, sizeof(priv->phys), + "%s/input1", psmouse->ps2dev.serio->phys); + dev2->phys = priv->phys; + dev2->name = "OLPC ALPS GlideSensor"; + dev2->id.bustype = BUS_I8042; + dev2->id.vendor = 0x0002; + dev2->id.product = PSMOUSE_OLPC; + dev2->id.version = 0x0000; + + dev2->evbit[LONG(EV_KEY)] |= BIT(EV_KEY); + dev2->keybit[LONG(BTN_TOUCH)] |= BIT(BTN_TOUCH); + dev2->keybit[LONG(BTN_TOOL_FINGER)] |= BIT(BTN_TOOL_FINGER); + dev2->keybit[LONG(BTN_LEFT)] |= BIT(BTN_LEFT) | BIT(BTN_RIGHT); + + dev2->evbit[LONG(EV_ABS)] |= BIT(EV_ABS); + input_set_abs_params(dev2, ABS_X, 350, 512, 0, 0); + input_set_abs_params(dev2, ABS_Y, 70, 325, 0, 0); + input_set_abs_params(dev2, ABS_PRESSURE, 0, 63, 0, 0); + + if (input_register_device(dev2)) { + printk(KERN_ERR __FILE__ ": Failed to register GlideSensor\n"); + goto init_fail; + } + + psmouse->protocol_handler = olpc_process_byte; + psmouse->poll = olpc_poll; + psmouse->disconnect = olpc_disconnect; + psmouse->reconnect = olpc_reconnect; + psmouse->pktsize = 6; + + /* Disable the idle resync. */ + psmouse->resync_time = 0; + /* Reset after a lot of bad bytes. */ + psmouse->resetafter = 1024; + + INIT_DELAYED_WORK(&priv->mode_switch, olpc_mode_switch); + + return 0; + +init_fail: + input_free_device(dev2); + kfree(priv); + return -1; +} + +int olpc_detect(struct psmouse *psmouse, int set_properties) +{ + if (!olpc_get_model(psmouse)) + return -1; + + if (set_properties) { + psmouse->vendor = "ALPS"; + psmouse->name = "PenTablet"; + psmouse->model = 0; + } + return 0; +} + diff --git a/target/linux/olpc/files/drivers/input/mouse/olpc.h b/target/linux/olpc/files/drivers/input/mouse/olpc.h new file mode 100644 index 0000000000..426f8b4ea9 --- /dev/null +++ b/target/linux/olpc/files/drivers/input/mouse/olpc.h @@ -0,0 +1,49 @@ +/* + * OLPC touchpad PS/2 mouse driver + * + * Copyright (c) 2006 One Laptop Per Child, inc. + * + * This driver is partly based on the ALPS driver. + * Copyright (c) 2003 Peter Osterlund <petero2@telia.com> + * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz> + * + * 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. + */ + +#ifndef _OLPC_H +#define _OLPC_H + +struct olpc_model_info { + unsigned char signature[3]; + unsigned char flags; +}; + +struct olpc_data { + struct input_dev *dev2; /* Relative device */ + struct psmouse *psmouse; + char name[32]; /* Name */ + char phys[32]; /* Phys */ + struct olpc_model_info *i; /* Info */ + int pending_mode; + int current_mode; + s64 late; + struct delayed_work mode_switch; +}; + +#ifdef CONFIG_MOUSE_PS2_OLPC +int olpc_detect(struct psmouse *psmouse, int set_properties); +int olpc_init(struct psmouse *psmouse); +#else +inline int olpc_detect(struct psmouse *psmouse, int set_properties) +{ + return -ENOSYS; +} +inline int olpc_init(struct psmouse *psmouse) +{ + return -ENOSYS; +} +#endif + +#endif |