/* * ALPS touchpad PS/2 mouse driver * * Copyright (c) 2003 Neil Brown * Copyright (c) 2003 Peter Osterlund * Copyright (c) 2004 Dmitry Torokhov * * ALPS detection, tap switching and status querying info is taken from * tpconfig utility (by C. Scott Ananian and Bruce Kall). * * 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 #include #include #include "psmouse.h" #include "alps.h" #undef DEBUG #ifdef DEBUG #define dbg(format, arg...) printk(KERN_INFO "alps.c: " format "\n", ## arg) #else #define dbg(format, arg...) do {} while (0) #endif #define ALPS_MODEL_GLIDEPOINT 1 #define ALPS_MODEL_DUALPOINT 2 struct alps_model_info { unsigned char signature[3]; unsigned char model; } alps_model_data[] = { /* { { 0x33, 0x02, 0x0a }, ALPS_MODEL_GLIDEPOINT }, */ { { 0x53, 0x02, 0x0a }, ALPS_MODEL_GLIDEPOINT }, { { 0x53, 0x02, 0x14 }, ALPS_MODEL_GLIDEPOINT }, { { 0x63, 0x02, 0x0a }, ALPS_MODEL_GLIDEPOINT }, { { 0x63, 0x02, 0x14 }, ALPS_MODEL_GLIDEPOINT }, { { 0x73, 0x02, 0x0a }, ALPS_MODEL_GLIDEPOINT }, { { 0x73, 0x02, 0x14 }, ALPS_MODEL_GLIDEPOINT }, { { 0x63, 0x02, 0x28 }, ALPS_MODEL_GLIDEPOINT }, /* { { 0x63, 0x02, 0x3c }, ALPS_MODEL_GLIDEPOINT }, */ /* { { 0x63, 0x02, 0x50 }, ALPS_MODEL_GLIDEPOINT }, */ { { 0x63, 0x02, 0x64 }, ALPS_MODEL_GLIDEPOINT }, { { 0x20, 0x02, 0x0e }, ALPS_MODEL_DUALPOINT }, { { 0x22, 0x02, 0x0a }, ALPS_MODEL_DUALPOINT }, { { 0x22, 0x02, 0x14 }, ALPS_MODEL_DUALPOINT }, { { 0x63, 0x03, 0xc8 }, ALPS_MODEL_DUALPOINT }, }; /* * ALPS abolute Mode * byte 0: 1 1 1 1 1 mid0 rig0 lef0 * byte 1: 0 x6 x5 x4 x3 x2 x1 x0 * byte 2: 0 x10 x9 x8 x7 up1 fin ges * byte 3: 0 y9 y8 y7 1 mid1 rig1 lef1 * byte 4: 0 y6 y5 y4 y3 y2 y1 y0 * byte 5: 0 z6 z5 z4 z3 z2 z1 z0 * * On a dualpoint, {mid,rig,lef}0 are the stick, 1 are the pad. * We just 'or' them together for now. * * We used to send 'ges'tures as BTN_TOUCH but this made it impossible * to disable tap events in the synaptics driver since the driver * was unable to distinguish a gesture tap from an actual button click. * A tap gesture now creates an emulated touch that the synaptics * driver can interpret as a tap event, if MaxTapTime=0 and * MaxTapMove=0 then the driver will ignore taps. * * The touchpad on an 'Acer Aspire' has 4 buttons: * left,right,up,down. * This device always sets {mid,rig,lef}0 to 1 and * reflects left,right,down,up in lef1,rig1,mid1,up1. */ static void alps_process_packet(struct psmouse *psmouse, struct pt_regs *regs) { unsigned char *packet = psmouse->packet; struct input_dev *dev = &psmouse->dev; int x, y, z; int left = 0, right = 0, middle = 0; input_regs(dev, regs); if ((packet[0] & 0xc8) == 0x08) { /* 3-byte PS/2 packet */ x = packet[1]; if (packet[0] & 0x10) x = x - 256; y = packet[2]; if (packet[0] & 0x20) y = y - 256; left = (packet[0] ) & 1; right = (packet[0] >> 1) & 1; input_report_rel(dev, REL_X, x); input_report_rel(dev, REL_Y, -y); input_report_key(dev, BTN_A, left); input_report_key(dev, BTN_B, right); input_sync(dev); return; } x = (packet[1] & 0x7f) | ((packet[2] & 0x78)<<(7-3)); y = (packet[4] & 0x7f) | ((packet[3] & 0x70)<<(7-4)); z = packet[5]; if (z == 127) { /* DualPoint stick is relative, not absolute */ if (x > 383) x = x - 768; if (y > 255) y = y - 512; left = packet[3] & 1; right = (packet[3] >> 1) & 1; input_report_rel(dev, REL_X, x); input_report_rel(dev, REL_Y, -y); input_report_key(dev, BTN_LEFT, left); input_report_key(dev, BTN_RIGHT, right); input_sync(dev); return; } if (z > 30) input_report_key(dev, BTN_TOUCH, 1); if (z < 25) input_report_key(dev, BTN_TOUCH, 0); if (z > 0) { input_report_abs(dev, ABS_X, x); input_report_abs(dev, ABS_Y, y); } input_report_abs(dev, ABS_PRESSURE, z); input_report_key(dev, BTN_TOOL_FINGER, z > 0); left |= (packet[2] ) & 1; left |= (packet[3] ) & 1; right |= (packet[3] >> 1) & 1; if (packet[0] == 0xff) { int back = (packet[3] >> 2) & 1; int forward = (packet[2] >> 2) & 1; if (back && forward) { middle = 1; back = 0; forward = 0; } input_report_key(dev, BTN_BACK, back); input_report_key(dev, BTN_FORWARD, forward); } else { left |= (packet[0] ) & 1; right |= (packet[0] >> 1) & 1; middle |= (packet[0] >> 2) & 1; middle |= (packet[3] >> 2) & 1; } input_report_key(dev, BTN_LEFT, left); input_report_key(dev, BTN_RIGHT, right); input_report_key(dev, BTN_MIDDLE, middle); input_sync(dev); } static psmouse_ret_t alps_process_byte(struct psmouse *psmouse, struct pt_regs *regs) { if ((psmouse->packet[0] & 0xc8) == 0x08) { /* PS/2 packet */ if (psmouse->pktcnt == 3) { alps_process_packet(psmouse, regs); return PSMOUSE_FULL_PACKET; } return PSMOUSE_GOOD_DATA; } /* ALPS absolute mode packets start with 0b11111mrl */ if ((psmouse->packet[0] & 0xf8) != 0xf8) return PSMOUSE_BAD_DATA; /* Bytes 2 - 6 should have 0 in the highest bit */ if (psmouse->pktcnt >= 2 && psmouse->pktcnt <= 6 && (psmouse->packet[psmouse->pktcnt-1] & 0x80)) return PSMOUSE_BAD_DATA; if (psmouse->pktcnt == 6) { alps_process_packet(psmouse, regs); return PSMOUSE_FULL_PACKET; } return PSMOUSE_GOOD_DATA; } int alps_get_model(struct psmouse *psmouse) { struct ps2dev *ps2dev = &psmouse->ps2dev; unsigned char param[4]; int i; /* * First try "E6 report". * ALPS should return 0x00,0x00,0x0a or 0x00,0x00,0x64 */ param[0] = 0; if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES) || ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) || ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) || ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11)) return -1; param[0] = param[1] = param[2] = 0xff; if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) return -1; dbg("E6 report: %2.2x %2.2x %2.2x", param[0], param[1], param[2]); if (param[0] != 0x00 || param[1] != 0x00 || (param[2] != 0x0a && param[2] != 0x64)) return -1; /* Now try "E7 report". ALPS should return 0x33 in byte 1 */ param[0] = 0; if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES) || ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) || ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) || ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21)) return -1; param[0] = param[1] = param[2] = 0xff; if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) return -1; dbg("E7 report: %2.2x %2.2x %2.2x", param[0], param[1], param[2]); for (i = 0; i < ARRAY_SIZE(alps_model_data); i++) if (!memcmp(param, alps_model_data[i].signature, sizeof(alps_model_data[i].signature))) return alps_model_data[i].model; return -1; } /* * For DualPoint devices select the device that should respond to * subsequent commands. It looks like glidepad is behind stickpointer, * I'd thought it would be other way around... */ static int alps_passthrough_mode(struct psmouse *psmouse, int enable) { struct ps2dev *ps2dev = &psmouse->ps2dev; unsigned char param[3]; int cmd = enable ? PSMOUSE_CMD_SETSCALE21 : PSMOUSE_CMD_SETSCALE11; if (ps2_command(ps2dev, NULL, cmd) || ps2_command(ps2dev, NULL, cmd) || ps2_command(ps2dev, NULL, cmd) || ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE)) return -1; /* we may get 3 more bytes, just ignore them */ ps2_command(ps2dev, param, 0x0300); return 0; } static int alps_absolute_mode(struct psmouse *psmouse) { struct ps2dev *ps2dev = &psmouse->ps2dev; /* Try ALPS magic knock - 4 disable before enable */ 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) || ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE)) return -1; /* * Switch mouse to poll (remote) mode so motion data will not * get in our way */ return ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETPOLL); } static int alps_get_status(struct psmouse *psmouse, char *param) { struct ps2dev *ps2dev = &psmouse->ps2dev; /* Get status: 0xF5 0xF5 0xF5 0xE9 */ 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, param, PSMOUSE_CMD_GETINFO)) return -1; dbg("Status: %2.2x %2.2x %2.2x", param[0], param[1], param[2]); return 0; } /* * Turn touchpad tapping on or off. The sequences are: * 0xE9 0xF5 0xF5 0xF3 0x0A to enable, * 0xE9 0xF5 0xF5 0xE8 0x00 to disable. * My guess that 0xE9 (GetInfo) is here as a sync point. * For models that also have stickpointer (DualPoints) its tapping * is controlled separately (0xE6 0xE6 0xE6 0xF3 0x14|0x0A) but * we don't fiddle with it. */ static int alps_tap_mode(struct psmouse *psmouse, int enable) { struct ps2dev *ps2dev = &psmouse->ps2dev; int cmd = enable ? PSMOUSE_CMD_SETRATE : PSMOUSE_CMD_SETRES; unsigned char tap_arg = enable ? 0x0A : 0x00; unsigned char param[4]; if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO) || ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || ps2_command(ps2dev, &tap_arg, cmd)) return -1; if (alps_get_status(psmouse, param)) return -1; return 0; } static int alps_reconnect(struct psmouse *psmouse) { int model; unsigned char param[4]; if ((model = alps_get_model(psmouse)) < 0) return -1; if (model == ALPS_MODEL_DUALPOINT && alps_passthrough_mode(psmouse, 1)) return -1; if (alps_get_status(psmouse, param)) return -1; if (param[0] & 0x04) alps_tap_mode(psmouse, 0); if (alps_absolute_mode(psmouse)) { printk(KERN_ERR "alps.c: Failed to enable absolute mode\n"); return -1; } if (model == ALPS_MODEL_DUALPOINT && alps_passthrough_mode(psmouse, 0)) return -1; return 0; } static void alps_disconnect(struct psmouse *psmouse) { psmouse_reset(psmouse); } int alps_init(struct psmouse *psmouse) { unsigned char param[4]; int model; if ((model = alps_get_model(psmouse)) < 0) return -1; printk(KERN_INFO "ALPS Touchpad (%s) detected\n", model == ALPS_MODEL_GLIDEPOINT ? "Glidepoint" : "Dualpoint"); if (model == ALPS_MODEL_DUALPOINT && alps_passthrough_mode(psmouse, 1)) return -1; if (alps_get_status(psmouse, param)) { printk(KERN_ERR "alps.c: touchpad status report request failed\n"); return -1; } if (param[0] & 0x04) { printk(KERN_INFO " Disabling hardware tapping\n"); if (alps_tap_mode(psmouse, 0)) printk(KERN_WARNING "alps.c: Failed to disable hardware tapping\n"); } if (alps_absolute_mode(psmouse)) { printk(KERN_ERR "alps.c: Failed to enable absolute mode\n"); return -1; } if (model == ALPS_MODEL_DUALPOINT && alps_passthrough_mode(psmouse, 0)) return -1; psmouse->dev.evbit[LONG(EV_REL)] |= BIT(EV_REL); psmouse->dev.relbit[LONG(REL_X)] |= BIT(REL_X); psmouse->dev.relbit[LONG(REL_Y)] |= BIT(REL_Y); psmouse->dev.keybit[LONG(BTN_A)] |= BIT(BTN_A); psmouse->dev.keybit[LONG(BTN_B)] |= BIT(BTN_B); psmouse->dev.evbit[LONG(EV_ABS)] |= BIT(EV_ABS); input_set_abs_params(&psmouse->dev, ABS_X, 0, 1023, 0, 0); input_set_abs_params(&psmouse->dev, ABS_Y, 0, 1023, 0, 0); input_set_abs_params(&psmouse->dev, ABS_PRESSURE, 0, 127, 0, 0); psmouse->dev.keybit[LONG(BTN_TOUCH)] |= BIT(BTN_TOUCH); psmouse->dev.keybit[LONG(BTN_TOOL_FINGER)] |= BIT(BTN_TOOL_FINGER); psmouse->dev.keybit[LONG(BTN_FORWARD)] |= BIT(BTN_FORWARD); psmouse->dev.keybit[LONG(BTN_BACK)] |= BIT(BTN_BACK); psmouse->protocol_handler = alps_process_byte; psmouse->disconnect = alps_disconnect; psmouse->reconnect = alps_reconnect; psmouse->pktsize = 6; return 0; } int alps_detect(struct psmouse *psmouse, int set_properties) { if (alps_get_model(psmouse) < 0) return -1; if (set_properties) { psmouse->vendor = "ALPS"; psmouse->name = "TouchPad"; } return 0; }