1df08ef27SAndres Salomon /* 2df08ef27SAndres Salomon * OLPC HGPK (XO-1) touchpad PS/2 mouse driver 3df08ef27SAndres Salomon * 4df08ef27SAndres Salomon * Copyright (c) 2006-2008 One Laptop Per Child 5df08ef27SAndres Salomon * Authors: 6df08ef27SAndres Salomon * Zephaniah E. Hull 7df08ef27SAndres Salomon * Andres Salomon <dilinger@debian.org> 8df08ef27SAndres Salomon * 9df08ef27SAndres Salomon * This driver is partly based on the ALPS driver, which is: 10df08ef27SAndres Salomon * 11df08ef27SAndres Salomon * Copyright (c) 2003 Neil Brown <neilb@cse.unsw.edu.au> 12df08ef27SAndres Salomon * Copyright (c) 2003-2005 Peter Osterlund <petero2@telia.com> 13df08ef27SAndres Salomon * Copyright (c) 2004 Dmitry Torokhov <dtor@mail.ru> 14df08ef27SAndres Salomon * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz> 15df08ef27SAndres Salomon * 16df08ef27SAndres Salomon * This program is free software; you can redistribute it and/or modify 17df08ef27SAndres Salomon * it under the terms of the GNU General Public License version 2 as 18df08ef27SAndres Salomon * published by the Free Software Foundation. 19df08ef27SAndres Salomon */ 20df08ef27SAndres Salomon 21df08ef27SAndres Salomon /* 22df08ef27SAndres Salomon * The spec from ALPS is available from 23df08ef27SAndres Salomon * <http://wiki.laptop.org/go/Touch_Pad/Tablet>. It refers to this 24df08ef27SAndres Salomon * device as HGPK (Hybrid GS, PT, and Keymatrix). 25df08ef27SAndres Salomon * 26df08ef27SAndres Salomon * The earliest versions of the device had simultaneous reporting; that 27df08ef27SAndres Salomon * was removed. After that, the device used the Advanced Mode GS/PT streaming 28df08ef27SAndres Salomon * stuff. That turned out to be too buggy to support, so we've finally 29df08ef27SAndres Salomon * switched to Mouse Mode (which utilizes only the center 1/3 of the touchpad). 30df08ef27SAndres Salomon */ 31df08ef27SAndres Salomon 32df08ef27SAndres Salomon #define DEBUG 335a0e3ad6STejun Heo #include <linux/slab.h> 34df08ef27SAndres Salomon #include <linux/input.h> 35ab3d0abeSRandy Dunlap #include <linux/module.h> 36df08ef27SAndres Salomon #include <linux/serio.h> 37df08ef27SAndres Salomon #include <linux/libps2.h> 38df08ef27SAndres Salomon #include <linux/delay.h> 39df08ef27SAndres Salomon #include <asm/olpc.h> 40df08ef27SAndres Salomon 41df08ef27SAndres Salomon #include "psmouse.h" 42df08ef27SAndres Salomon #include "hgpk.h" 43df08ef27SAndres Salomon 44a309cdc7SDaniel Drake #define ILLEGAL_XY 999999 45a309cdc7SDaniel Drake 46a62f0d27SDmitry Torokhov static bool tpdebug; 47a62f0d27SDmitry Torokhov module_param(tpdebug, bool, 0644); 48df08ef27SAndres Salomon MODULE_PARM_DESC(tpdebug, "enable debugging, dumping packets to KERN_DEBUG."); 49df08ef27SAndres Salomon 50df08ef27SAndres Salomon static int recalib_delta = 100; 51df08ef27SAndres Salomon module_param(recalib_delta, int, 0644); 52df08ef27SAndres Salomon MODULE_PARM_DESC(recalib_delta, 53a309cdc7SDaniel Drake "packets containing a delta this large will be discarded, and a " 54a309cdc7SDaniel Drake "recalibration may be scheduled."); 55df08ef27SAndres Salomon 56a309cdc7SDaniel Drake static int jumpy_delay = 20; 578bbf2703SPaul Fox module_param(jumpy_delay, int, 0644); 588bbf2703SPaul Fox MODULE_PARM_DESC(jumpy_delay, 598bbf2703SPaul Fox "delay (ms) before recal after jumpiness detected"); 608bbf2703SPaul Fox 61c0dc8342SDaniel Drake static int spew_delay = 1; 628bbf2703SPaul Fox module_param(spew_delay, int, 0644); 638bbf2703SPaul Fox MODULE_PARM_DESC(spew_delay, 648bbf2703SPaul Fox "delay (ms) before recal after packet spew detected"); 658bbf2703SPaul Fox 6634caed20SDaniel Drake static int recal_guard_time; 678bbf2703SPaul Fox module_param(recal_guard_time, int, 0644); 688bbf2703SPaul Fox MODULE_PARM_DESC(recal_guard_time, 698bbf2703SPaul Fox "interval (ms) during which recal will be restarted if packet received"); 708bbf2703SPaul Fox 7134caed20SDaniel Drake static int post_interrupt_delay = 40; 728bbf2703SPaul Fox module_param(post_interrupt_delay, int, 0644); 738bbf2703SPaul Fox MODULE_PARM_DESC(post_interrupt_delay, 748bbf2703SPaul Fox "delay (ms) before recal after recal interrupt detected"); 758bbf2703SPaul Fox 7634caed20SDaniel Drake static bool autorecal = true; 7734caed20SDaniel Drake module_param(autorecal, bool, 0644); 7834caed20SDaniel Drake MODULE_PARM_DESC(autorecal, "enable recalibration in the driver"); 7934caed20SDaniel Drake 80ca94ec43SDaniel Drake static char hgpk_mode_name[16]; 81ca94ec43SDaniel Drake module_param_string(hgpk_mode, hgpk_mode_name, sizeof(hgpk_mode_name), 0644); 82ca94ec43SDaniel Drake MODULE_PARM_DESC(hgpk_mode, 83ca94ec43SDaniel Drake "default hgpk mode: mouse, glidesensor or pentablet"); 84ca94ec43SDaniel Drake 85ca94ec43SDaniel Drake static int hgpk_default_mode = HGPK_MODE_MOUSE; 86ca94ec43SDaniel Drake 87ca94ec43SDaniel Drake static const char * const hgpk_mode_names[] = { 88ca94ec43SDaniel Drake [HGPK_MODE_MOUSE] = "Mouse", 89ca94ec43SDaniel Drake [HGPK_MODE_GLIDESENSOR] = "GlideSensor", 90ca94ec43SDaniel Drake [HGPK_MODE_PENTABLET] = "PenTablet", 91ca94ec43SDaniel Drake }; 92ca94ec43SDaniel Drake 93ca94ec43SDaniel Drake static int hgpk_mode_from_name(const char *buf, int len) 94ca94ec43SDaniel Drake { 95ca94ec43SDaniel Drake int i; 96ca94ec43SDaniel Drake 97ca94ec43SDaniel Drake for (i = 0; i < ARRAY_SIZE(hgpk_mode_names); i++) { 98ca94ec43SDaniel Drake const char *name = hgpk_mode_names[i]; 99ca94ec43SDaniel Drake if (strlen(name) == len && !strncasecmp(name, buf, len)) 100ca94ec43SDaniel Drake return i; 101ca94ec43SDaniel Drake } 102ca94ec43SDaniel Drake 103ca94ec43SDaniel Drake return HGPK_MODE_INVALID; 104ca94ec43SDaniel Drake } 105ca94ec43SDaniel Drake 106df08ef27SAndres Salomon /* 107a309cdc7SDaniel Drake * see if new value is within 20% of half of old value 108df08ef27SAndres Salomon */ 109a309cdc7SDaniel Drake static int approx_half(int curr, int prev) 110a309cdc7SDaniel Drake { 111a309cdc7SDaniel Drake int belowhalf, abovehalf; 112a309cdc7SDaniel Drake 113a309cdc7SDaniel Drake if (curr < 5 || prev < 5) 114a309cdc7SDaniel Drake return 0; 115a309cdc7SDaniel Drake 116a309cdc7SDaniel Drake belowhalf = (prev * 8) / 20; 117a309cdc7SDaniel Drake abovehalf = (prev * 12) / 20; 118a309cdc7SDaniel Drake 119a309cdc7SDaniel Drake return belowhalf < curr && curr <= abovehalf; 120a309cdc7SDaniel Drake } 121a309cdc7SDaniel Drake 122a309cdc7SDaniel Drake /* 123a309cdc7SDaniel Drake * Throw out oddly large delta packets, and any that immediately follow whose 124a309cdc7SDaniel Drake * values are each approximately half of the previous. It seems that the ALPS 125a309cdc7SDaniel Drake * firmware emits errant packets, and they get averaged out slowly. 126a309cdc7SDaniel Drake */ 127a309cdc7SDaniel Drake static int hgpk_discard_decay_hack(struct psmouse *psmouse, int x, int y) 128df08ef27SAndres Salomon { 129df08ef27SAndres Salomon struct hgpk_data *priv = psmouse->private; 130a309cdc7SDaniel Drake int avx, avy; 131a309cdc7SDaniel Drake bool do_recal = false; 132df08ef27SAndres Salomon 133a309cdc7SDaniel Drake avx = abs(x); 134a309cdc7SDaniel Drake avy = abs(y); 135a309cdc7SDaniel Drake 136a309cdc7SDaniel Drake /* discard if too big, or half that but > 4 times the prev delta */ 137a309cdc7SDaniel Drake if (avx > recalib_delta || 138a309cdc7SDaniel Drake (avx > recalib_delta / 2 && ((avx / 4) > priv->xlast))) { 139*b5d21704SDmitry Torokhov psmouse_warn(psmouse, "detected %dpx jump in x\n", x); 140a309cdc7SDaniel Drake priv->xbigj = avx; 141a309cdc7SDaniel Drake } else if (approx_half(avx, priv->xbigj)) { 142*b5d21704SDmitry Torokhov psmouse_warn(psmouse, "detected secondary %dpx jump in x\n", x); 143a309cdc7SDaniel Drake priv->xbigj = avx; 144a309cdc7SDaniel Drake priv->xsaw_secondary++; 145a309cdc7SDaniel Drake } else { 146a309cdc7SDaniel Drake if (priv->xbigj && priv->xsaw_secondary > 1) 147a309cdc7SDaniel Drake do_recal = true; 148a309cdc7SDaniel Drake priv->xbigj = 0; 149a309cdc7SDaniel Drake priv->xsaw_secondary = 0; 150a309cdc7SDaniel Drake } 151a309cdc7SDaniel Drake 152a309cdc7SDaniel Drake if (avy > recalib_delta || 153a309cdc7SDaniel Drake (avy > recalib_delta / 2 && ((avy / 4) > priv->ylast))) { 154*b5d21704SDmitry Torokhov psmouse_warn(psmouse, "detected %dpx jump in y\n", y); 155a309cdc7SDaniel Drake priv->ybigj = avy; 156a309cdc7SDaniel Drake } else if (approx_half(avy, priv->ybigj)) { 157*b5d21704SDmitry Torokhov psmouse_warn(psmouse, "detected secondary %dpx jump in y\n", y); 158a309cdc7SDaniel Drake priv->ybigj = avy; 159a309cdc7SDaniel Drake priv->ysaw_secondary++; 160a309cdc7SDaniel Drake } else { 161a309cdc7SDaniel Drake if (priv->ybigj && priv->ysaw_secondary > 1) 162a309cdc7SDaniel Drake do_recal = true; 163a309cdc7SDaniel Drake priv->ybigj = 0; 164a309cdc7SDaniel Drake priv->ysaw_secondary = 0; 165a309cdc7SDaniel Drake } 166a309cdc7SDaniel Drake 167a309cdc7SDaniel Drake priv->xlast = avx; 168a309cdc7SDaniel Drake priv->ylast = avy; 169a309cdc7SDaniel Drake 170a309cdc7SDaniel Drake if (do_recal && jumpy_delay) { 171*b5d21704SDmitry Torokhov psmouse_warn(psmouse, "scheduling recalibration\n"); 172df08ef27SAndres Salomon psmouse_queue_work(psmouse, &priv->recalib_wq, 1738bbf2703SPaul Fox msecs_to_jiffies(jumpy_delay)); 174df08ef27SAndres Salomon } 175a309cdc7SDaniel Drake 176a309cdc7SDaniel Drake return priv->xbigj || priv->ybigj; 177df08ef27SAndres Salomon } 178df08ef27SAndres Salomon 179c0dc8342SDaniel Drake static void hgpk_reset_spew_detection(struct hgpk_data *priv) 180c0dc8342SDaniel Drake { 181c0dc8342SDaniel Drake priv->spew_count = 0; 182c0dc8342SDaniel Drake priv->dupe_count = 0; 183c0dc8342SDaniel Drake priv->x_tally = 0; 184c0dc8342SDaniel Drake priv->y_tally = 0; 185c0dc8342SDaniel Drake priv->spew_flag = NO_SPEW; 186c0dc8342SDaniel Drake } 187c0dc8342SDaniel Drake 188c0dc8342SDaniel Drake static void hgpk_reset_hack_state(struct psmouse *psmouse) 189c0dc8342SDaniel Drake { 190c0dc8342SDaniel Drake struct hgpk_data *priv = psmouse->private; 191c0dc8342SDaniel Drake 192c0dc8342SDaniel Drake priv->abs_x = priv->abs_y = -1; 193a309cdc7SDaniel Drake priv->xlast = priv->ylast = ILLEGAL_XY; 194a309cdc7SDaniel Drake priv->xbigj = priv->ybigj = 0; 195a309cdc7SDaniel Drake priv->xsaw_secondary = priv->ysaw_secondary = 0; 196c0dc8342SDaniel Drake hgpk_reset_spew_detection(priv); 197c0dc8342SDaniel Drake } 198c0dc8342SDaniel Drake 199df08ef27SAndres Salomon /* 200df08ef27SAndres Salomon * We have no idea why this particular hardware bug occurs. The touchpad 201df08ef27SAndres Salomon * will randomly start spewing packets without anything touching the 202df08ef27SAndres Salomon * pad. This wouldn't necessarily be bad, but it's indicative of a 203df08ef27SAndres Salomon * severely miscalibrated pad; attempting to use the touchpad while it's 204df08ef27SAndres Salomon * spewing means the cursor will jump all over the place, and act "drunk". 205df08ef27SAndres Salomon * 206df08ef27SAndres Salomon * The packets that are spewed tend to all have deltas between -2 and 2, and 207df08ef27SAndres Salomon * the cursor will move around without really going very far. It will 208df08ef27SAndres Salomon * tend to end up in the same location; if we tally up the changes over 209df08ef27SAndres Salomon * 100 packets, we end up w/ a final delta of close to 0. This happens 210df08ef27SAndres Salomon * pretty regularly when the touchpad is spewing, and is pretty hard to 211df08ef27SAndres Salomon * manually trigger (at least for *my* fingers). So, it makes a perfect 212df08ef27SAndres Salomon * scheme for detecting spews. 213df08ef27SAndres Salomon */ 214df08ef27SAndres Salomon static void hgpk_spewing_hack(struct psmouse *psmouse, 215df08ef27SAndres Salomon int l, int r, int x, int y) 216df08ef27SAndres Salomon { 217df08ef27SAndres Salomon struct hgpk_data *priv = psmouse->private; 218df08ef27SAndres Salomon 219df08ef27SAndres Salomon /* ignore button press packets; many in a row could trigger 220df08ef27SAndres Salomon * a false-positive! */ 221df08ef27SAndres Salomon if (l || r) 222df08ef27SAndres Salomon return; 223df08ef27SAndres Salomon 224c0dc8342SDaniel Drake /* don't track spew if the workaround feature has been turned off */ 225c0dc8342SDaniel Drake if (!spew_delay) 226c0dc8342SDaniel Drake return; 227c0dc8342SDaniel Drake 228c0dc8342SDaniel Drake if (abs(x) > 3 || abs(y) > 3) { 229c0dc8342SDaniel Drake /* no spew, or spew ended */ 230c0dc8342SDaniel Drake hgpk_reset_spew_detection(priv); 231c0dc8342SDaniel Drake return; 232c0dc8342SDaniel Drake } 233c0dc8342SDaniel Drake 234c0dc8342SDaniel Drake /* Keep a tally of the overall delta to the cursor position caused by 235c0dc8342SDaniel Drake * the spew */ 236df08ef27SAndres Salomon priv->x_tally += x; 237df08ef27SAndres Salomon priv->y_tally += y; 238df08ef27SAndres Salomon 239c0dc8342SDaniel Drake switch (priv->spew_flag) { 240c0dc8342SDaniel Drake case NO_SPEW: 241c0dc8342SDaniel Drake /* we're not spewing, but this packet might be the start */ 242c0dc8342SDaniel Drake priv->spew_flag = MAYBE_SPEWING; 243c0dc8342SDaniel Drake 244c0dc8342SDaniel Drake /* fall-through */ 245c0dc8342SDaniel Drake 246c0dc8342SDaniel Drake case MAYBE_SPEWING: 247c0dc8342SDaniel Drake priv->spew_count++; 248c0dc8342SDaniel Drake 249c0dc8342SDaniel Drake if (priv->spew_count < SPEW_WATCH_COUNT) 250c0dc8342SDaniel Drake break; 251c0dc8342SDaniel Drake 252c0dc8342SDaniel Drake /* excessive spew detected, request recalibration */ 253c0dc8342SDaniel Drake priv->spew_flag = SPEW_DETECTED; 254c0dc8342SDaniel Drake 255c0dc8342SDaniel Drake /* fall-through */ 256c0dc8342SDaniel Drake 257c0dc8342SDaniel Drake case SPEW_DETECTED: 258c0dc8342SDaniel Drake /* only recalibrate when the overall delta to the cursor 259c0dc8342SDaniel Drake * is really small. if the spew is causing significant cursor 260c0dc8342SDaniel Drake * movement, it is probably a case of the user moving the 261c0dc8342SDaniel Drake * cursor very slowly across the screen. */ 262df08ef27SAndres Salomon if (abs(priv->x_tally) < 3 && abs(priv->y_tally) < 3) { 263*b5d21704SDmitry Torokhov psmouse_warn(psmouse, "packet spew detected (%d,%d)\n", 264df08ef27SAndres Salomon priv->x_tally, priv->y_tally); 265c0dc8342SDaniel Drake priv->spew_flag = RECALIBRATING; 266df08ef27SAndres Salomon psmouse_queue_work(psmouse, &priv->recalib_wq, 2678bbf2703SPaul Fox msecs_to_jiffies(spew_delay)); 268df08ef27SAndres Salomon } 269c0dc8342SDaniel Drake 270c0dc8342SDaniel Drake break; 271c0dc8342SDaniel Drake case RECALIBRATING: 272c0dc8342SDaniel Drake /* we already detected a spew and requested a recalibration, 273c0dc8342SDaniel Drake * just wait for the queue to kick into action. */ 274c0dc8342SDaniel Drake break; 275df08ef27SAndres Salomon } 276df08ef27SAndres Salomon } 277df08ef27SAndres Salomon 278df08ef27SAndres Salomon /* 279df08ef27SAndres Salomon * HGPK Mouse Mode format (standard mouse format, sans middle button) 280df08ef27SAndres Salomon * 281df08ef27SAndres Salomon * byte 0: y-over x-over y-neg x-neg 1 0 swr swl 282df08ef27SAndres Salomon * byte 1: x7 x6 x5 x4 x3 x2 x1 x0 283df08ef27SAndres Salomon * byte 2: y7 y6 y5 y4 y3 y2 y1 y0 284df08ef27SAndres Salomon * 285df08ef27SAndres Salomon * swr/swl are the left/right buttons. 286df08ef27SAndres Salomon * x-neg/y-neg are the x and y delta negative bits 287df08ef27SAndres Salomon * x-over/y-over are the x and y overflow bits 288ca94ec43SDaniel Drake * 289ca94ec43SDaniel Drake * --- 290ca94ec43SDaniel Drake * 291ca94ec43SDaniel Drake * HGPK Advanced Mode - single-mode format 292ca94ec43SDaniel Drake * 293ca94ec43SDaniel Drake * byte 0(PT): 1 1 0 0 1 1 1 1 294ca94ec43SDaniel Drake * byte 0(GS): 1 1 1 1 1 1 1 1 295ca94ec43SDaniel Drake * byte 1: 0 x6 x5 x4 x3 x2 x1 x0 296ca94ec43SDaniel Drake * byte 2(PT): 0 0 x9 x8 x7 ? pt-dsw 0 297ca94ec43SDaniel Drake * byte 2(GS): 0 x10 x9 x8 x7 ? gs-dsw pt-dsw 298ca94ec43SDaniel Drake * byte 3: 0 y9 y8 y7 1 0 swr swl 299ca94ec43SDaniel Drake * byte 4: 0 y6 y5 y4 y3 y2 y1 y0 300ca94ec43SDaniel Drake * byte 5: 0 z6 z5 z4 z3 z2 z1 z0 301ca94ec43SDaniel Drake * 302ca94ec43SDaniel Drake * ?'s are not defined in the protocol spec, may vary between models. 303ca94ec43SDaniel Drake * 304ca94ec43SDaniel Drake * swr/swl are the left/right buttons. 305ca94ec43SDaniel Drake * 306ca94ec43SDaniel Drake * pt-dsw/gs-dsw indicate that the pt/gs sensor is detecting a 307ca94ec43SDaniel Drake * pen/finger 308df08ef27SAndres Salomon */ 309ca94ec43SDaniel Drake static bool hgpk_is_byte_valid(struct psmouse *psmouse, unsigned char *packet) 310df08ef27SAndres Salomon { 311ca94ec43SDaniel Drake struct hgpk_data *priv = psmouse->private; 312ca94ec43SDaniel Drake int pktcnt = psmouse->pktcnt; 313ca94ec43SDaniel Drake bool valid; 314ca94ec43SDaniel Drake 315ca94ec43SDaniel Drake switch (priv->mode) { 316ca94ec43SDaniel Drake case HGPK_MODE_MOUSE: 317ca94ec43SDaniel Drake valid = (packet[0] & 0x0C) == 0x08; 318ca94ec43SDaniel Drake break; 319ca94ec43SDaniel Drake 320ca94ec43SDaniel Drake case HGPK_MODE_GLIDESENSOR: 321ca94ec43SDaniel Drake valid = pktcnt == 1 ? 322ca94ec43SDaniel Drake packet[0] == HGPK_GS : !(packet[pktcnt - 1] & 0x80); 323ca94ec43SDaniel Drake break; 324ca94ec43SDaniel Drake 325ca94ec43SDaniel Drake case HGPK_MODE_PENTABLET: 326ca94ec43SDaniel Drake valid = pktcnt == 1 ? 327ca94ec43SDaniel Drake packet[0] == HGPK_PT : !(packet[pktcnt - 1] & 0x80); 328ca94ec43SDaniel Drake break; 329ca94ec43SDaniel Drake 330ca94ec43SDaniel Drake default: 331ca94ec43SDaniel Drake valid = false; 332ca94ec43SDaniel Drake break; 333df08ef27SAndres Salomon } 334df08ef27SAndres Salomon 335ca94ec43SDaniel Drake if (!valid) 336*b5d21704SDmitry Torokhov psmouse_dbg(psmouse, 337ca94ec43SDaniel Drake "bad data, mode %d (%d) %02x %02x %02x %02x %02x %02x\n", 338ca94ec43SDaniel Drake priv->mode, pktcnt, 339ca94ec43SDaniel Drake psmouse->packet[0], psmouse->packet[1], 340ca94ec43SDaniel Drake psmouse->packet[2], psmouse->packet[3], 341ca94ec43SDaniel Drake psmouse->packet[4], psmouse->packet[5]); 342ca94ec43SDaniel Drake 343ca94ec43SDaniel Drake return valid; 344ca94ec43SDaniel Drake } 345ca94ec43SDaniel Drake 346ca94ec43SDaniel Drake static void hgpk_process_advanced_packet(struct psmouse *psmouse) 347ca94ec43SDaniel Drake { 348ca94ec43SDaniel Drake struct hgpk_data *priv = psmouse->private; 349ca94ec43SDaniel Drake struct input_dev *idev = psmouse->dev; 350ca94ec43SDaniel Drake unsigned char *packet = psmouse->packet; 351ca94ec43SDaniel Drake int down = !!(packet[2] & 2); 352ca94ec43SDaniel Drake int left = !!(packet[3] & 1); 353ca94ec43SDaniel Drake int right = !!(packet[3] & 2); 354ca94ec43SDaniel Drake int x = packet[1] | ((packet[2] & 0x78) << 4); 355ca94ec43SDaniel Drake int y = packet[4] | ((packet[3] & 0x70) << 3); 356ca94ec43SDaniel Drake 357ca94ec43SDaniel Drake if (priv->mode == HGPK_MODE_GLIDESENSOR) { 358ca94ec43SDaniel Drake int pt_down = !!(packet[2] & 1); 359ca94ec43SDaniel Drake int finger_down = !!(packet[2] & 2); 360ca94ec43SDaniel Drake int z = packet[5]; 361ca94ec43SDaniel Drake 362ca94ec43SDaniel Drake input_report_abs(idev, ABS_PRESSURE, z); 363ca94ec43SDaniel Drake if (tpdebug) 364*b5d21704SDmitry Torokhov psmouse_dbg(psmouse, "pd=%d fd=%d z=%d", 365ca94ec43SDaniel Drake pt_down, finger_down, z); 366ca94ec43SDaniel Drake } else { 367ca94ec43SDaniel Drake /* 368ca94ec43SDaniel Drake * PenTablet mode does not report pressure, so we don't 369ca94ec43SDaniel Drake * report it here 370ca94ec43SDaniel Drake */ 371ca94ec43SDaniel Drake if (tpdebug) 372*b5d21704SDmitry Torokhov psmouse_dbg(psmouse, "pd=%d ", down); 373ca94ec43SDaniel Drake } 374ca94ec43SDaniel Drake 375ca94ec43SDaniel Drake if (tpdebug) 376*b5d21704SDmitry Torokhov psmouse_dbg(psmouse, "l=%d r=%d x=%d y=%d\n", 377*b5d21704SDmitry Torokhov left, right, x, y); 378ca94ec43SDaniel Drake 379ca94ec43SDaniel Drake input_report_key(idev, BTN_TOUCH, down); 380ca94ec43SDaniel Drake input_report_key(idev, BTN_LEFT, left); 381ca94ec43SDaniel Drake input_report_key(idev, BTN_RIGHT, right); 382ca94ec43SDaniel Drake 383ca94ec43SDaniel Drake /* 384ca94ec43SDaniel Drake * If this packet says that the finger was removed, reset our position 385ca94ec43SDaniel Drake * tracking so that we don't erroneously detect a jump on next press. 386ca94ec43SDaniel Drake */ 387c0dc8342SDaniel Drake if (!down) { 388a309cdc7SDaniel Drake hgpk_reset_hack_state(psmouse); 389c0dc8342SDaniel Drake goto done; 390c0dc8342SDaniel Drake } 391ca94ec43SDaniel Drake 392ca94ec43SDaniel Drake /* 393c0dc8342SDaniel Drake * Weed out duplicate packets (we get quite a few, and they mess up 394c0dc8342SDaniel Drake * our jump detection) 395ca94ec43SDaniel Drake */ 396c0dc8342SDaniel Drake if (x == priv->abs_x && y == priv->abs_y) { 397c0dc8342SDaniel Drake if (++priv->dupe_count > SPEW_WATCH_COUNT) { 398c0dc8342SDaniel Drake if (tpdebug) 399*b5d21704SDmitry Torokhov psmouse_dbg(psmouse, "hard spew detected\n"); 400c0dc8342SDaniel Drake priv->spew_flag = RECALIBRATING; 401c0dc8342SDaniel Drake psmouse_queue_work(psmouse, &priv->recalib_wq, 402c0dc8342SDaniel Drake msecs_to_jiffies(spew_delay)); 403c0dc8342SDaniel Drake } 404c0dc8342SDaniel Drake goto done; 405c0dc8342SDaniel Drake } 406c0dc8342SDaniel Drake 407c0dc8342SDaniel Drake /* not a duplicate, continue with position reporting */ 408c0dc8342SDaniel Drake priv->dupe_count = 0; 409ca94ec43SDaniel Drake 410ca94ec43SDaniel Drake /* Don't apply hacks in PT mode, it seems reliable */ 411ca94ec43SDaniel Drake if (priv->mode != HGPK_MODE_PENTABLET && priv->abs_x != -1) { 412a309cdc7SDaniel Drake int x_diff = priv->abs_x - x; 413a309cdc7SDaniel Drake int y_diff = priv->abs_y - y; 414a309cdc7SDaniel Drake if (hgpk_discard_decay_hack(psmouse, x_diff, y_diff)) { 415a309cdc7SDaniel Drake if (tpdebug) 416*b5d21704SDmitry Torokhov psmouse_dbg(psmouse, "discarding\n"); 417a309cdc7SDaniel Drake goto done; 418a309cdc7SDaniel Drake } 419a309cdc7SDaniel Drake hgpk_spewing_hack(psmouse, left, right, x_diff, y_diff); 420ca94ec43SDaniel Drake } 421ca94ec43SDaniel Drake 422ca94ec43SDaniel Drake input_report_abs(idev, ABS_X, x); 423ca94ec43SDaniel Drake input_report_abs(idev, ABS_Y, y); 424ca94ec43SDaniel Drake priv->abs_x = x; 425ca94ec43SDaniel Drake priv->abs_y = y; 426ca94ec43SDaniel Drake 427c0dc8342SDaniel Drake done: 428ca94ec43SDaniel Drake input_sync(idev); 429ca94ec43SDaniel Drake } 430ca94ec43SDaniel Drake 431ca94ec43SDaniel Drake static void hgpk_process_simple_packet(struct psmouse *psmouse) 432df08ef27SAndres Salomon { 433df08ef27SAndres Salomon struct input_dev *dev = psmouse->dev; 434df08ef27SAndres Salomon unsigned char *packet = psmouse->packet; 435ca94ec43SDaniel Drake int left = packet[0] & 1; 436ca94ec43SDaniel Drake int right = (packet[0] >> 1) & 1; 437ca94ec43SDaniel Drake int x = packet[1] - ((packet[0] << 4) & 0x100); 438ca94ec43SDaniel Drake int y = ((packet[0] << 3) & 0x100) - packet[2]; 439df08ef27SAndres Salomon 44067f56bb0SDaniel Drake if (packet[0] & 0xc0) 441*b5d21704SDmitry Torokhov psmouse_dbg(psmouse, 44267f56bb0SDaniel Drake "overflow -- 0x%02x 0x%02x 0x%02x\n", 44367f56bb0SDaniel Drake packet[0], packet[1], packet[2]); 44467f56bb0SDaniel Drake 445a309cdc7SDaniel Drake if (hgpk_discard_decay_hack(psmouse, x, y)) { 446a309cdc7SDaniel Drake if (tpdebug) 447*b5d21704SDmitry Torokhov psmouse_dbg(psmouse, "discarding\n"); 448a309cdc7SDaniel Drake return; 449a309cdc7SDaniel Drake } 450a309cdc7SDaniel Drake 451df08ef27SAndres Salomon hgpk_spewing_hack(psmouse, left, right, x, y); 452df08ef27SAndres Salomon 453df08ef27SAndres Salomon if (tpdebug) 454*b5d21704SDmitry Torokhov psmouse_dbg(psmouse, "l=%d r=%d x=%d y=%d\n", 455*b5d21704SDmitry Torokhov left, right, x, y); 456df08ef27SAndres Salomon 457df08ef27SAndres Salomon input_report_key(dev, BTN_LEFT, left); 458df08ef27SAndres Salomon input_report_key(dev, BTN_RIGHT, right); 459df08ef27SAndres Salomon 460df08ef27SAndres Salomon input_report_rel(dev, REL_X, x); 461df08ef27SAndres Salomon input_report_rel(dev, REL_Y, y); 462df08ef27SAndres Salomon 463df08ef27SAndres Salomon input_sync(dev); 464df08ef27SAndres Salomon } 465df08ef27SAndres Salomon 466df08ef27SAndres Salomon static psmouse_ret_t hgpk_process_byte(struct psmouse *psmouse) 467df08ef27SAndres Salomon { 468df08ef27SAndres Salomon struct hgpk_data *priv = psmouse->private; 469df08ef27SAndres Salomon 470ca94ec43SDaniel Drake if (!hgpk_is_byte_valid(psmouse, psmouse->packet)) 471df08ef27SAndres Salomon return PSMOUSE_BAD_DATA; 472df08ef27SAndres Salomon 473df08ef27SAndres Salomon if (psmouse->pktcnt >= psmouse->pktsize) { 474ca94ec43SDaniel Drake if (priv->mode == HGPK_MODE_MOUSE) 475ca94ec43SDaniel Drake hgpk_process_simple_packet(psmouse); 476ca94ec43SDaniel Drake else 477ca94ec43SDaniel Drake hgpk_process_advanced_packet(psmouse); 478df08ef27SAndres Salomon return PSMOUSE_FULL_PACKET; 479df08ef27SAndres Salomon } 480df08ef27SAndres Salomon 481df08ef27SAndres Salomon if (priv->recalib_window) { 482df08ef27SAndres Salomon if (time_before(jiffies, priv->recalib_window)) { 483df08ef27SAndres Salomon /* 484df08ef27SAndres Salomon * ugh, got a packet inside our recalibration 485df08ef27SAndres Salomon * window, schedule another recalibration. 486df08ef27SAndres Salomon */ 487*b5d21704SDmitry Torokhov psmouse_dbg(psmouse, 488*b5d21704SDmitry Torokhov "packet inside calibration window, queueing another recalibration\n"); 489df08ef27SAndres Salomon psmouse_queue_work(psmouse, &priv->recalib_wq, 4908bbf2703SPaul Fox msecs_to_jiffies(post_interrupt_delay)); 491df08ef27SAndres Salomon } 492df08ef27SAndres Salomon priv->recalib_window = 0; 493df08ef27SAndres Salomon } 494df08ef27SAndres Salomon 495df08ef27SAndres Salomon return PSMOUSE_GOOD_DATA; 496df08ef27SAndres Salomon } 497df08ef27SAndres Salomon 498ca94ec43SDaniel Drake static int hgpk_select_mode(struct psmouse *psmouse) 499df08ef27SAndres Salomon { 500df08ef27SAndres Salomon struct ps2dev *ps2dev = &psmouse->ps2dev; 501df08ef27SAndres Salomon struct hgpk_data *priv = psmouse->private; 502ca94ec43SDaniel Drake int i; 503ca94ec43SDaniel Drake int cmd; 504df08ef27SAndres Salomon 505ca94ec43SDaniel Drake /* 506ca94ec43SDaniel Drake * 4 disables to enable advanced mode 507ca94ec43SDaniel Drake * then 3 0xf2 bytes as the preamble for GS/PT selection 508ca94ec43SDaniel Drake */ 509ca94ec43SDaniel Drake const int advanced_init[] = { 510ca94ec43SDaniel Drake PSMOUSE_CMD_DISABLE, PSMOUSE_CMD_DISABLE, 511ca94ec43SDaniel Drake PSMOUSE_CMD_DISABLE, PSMOUSE_CMD_DISABLE, 512ca94ec43SDaniel Drake 0xf2, 0xf2, 0xf2, 513ca94ec43SDaniel Drake }; 514ca94ec43SDaniel Drake 515ca94ec43SDaniel Drake switch (priv->mode) { 516ca94ec43SDaniel Drake case HGPK_MODE_MOUSE: 517ca94ec43SDaniel Drake psmouse->pktsize = 3; 518ca94ec43SDaniel Drake break; 519ca94ec43SDaniel Drake 520ca94ec43SDaniel Drake case HGPK_MODE_GLIDESENSOR: 521ca94ec43SDaniel Drake case HGPK_MODE_PENTABLET: 522ca94ec43SDaniel Drake psmouse->pktsize = 6; 523ca94ec43SDaniel Drake 524ca94ec43SDaniel Drake /* Switch to 'Advanced mode.', four disables in a row. */ 525ca94ec43SDaniel Drake for (i = 0; i < ARRAY_SIZE(advanced_init); i++) 526ca94ec43SDaniel Drake if (ps2_command(ps2dev, NULL, advanced_init[i])) 527ca94ec43SDaniel Drake return -EIO; 528ca94ec43SDaniel Drake 529ca94ec43SDaniel Drake /* select between GlideSensor (mouse) or PenTablet */ 530ca94ec43SDaniel Drake cmd = priv->mode == HGPK_MODE_GLIDESENSOR ? 531ca94ec43SDaniel Drake PSMOUSE_CMD_SETSCALE11 : PSMOUSE_CMD_SETSCALE21; 532ca94ec43SDaniel Drake 533ca94ec43SDaniel Drake if (ps2_command(ps2dev, NULL, cmd)) 534ca94ec43SDaniel Drake return -EIO; 535ca94ec43SDaniel Drake break; 536ca94ec43SDaniel Drake 537ca94ec43SDaniel Drake default: 538ca94ec43SDaniel Drake return -EINVAL; 539ca94ec43SDaniel Drake } 540ca94ec43SDaniel Drake 541df08ef27SAndres Salomon return 0; 542ca94ec43SDaniel Drake } 543df08ef27SAndres Salomon 544ca94ec43SDaniel Drake static void hgpk_setup_input_device(struct input_dev *input, 545ca94ec43SDaniel Drake struct input_dev *old_input, 546ca94ec43SDaniel Drake enum hgpk_mode mode) 547ca94ec43SDaniel Drake { 548ca94ec43SDaniel Drake if (old_input) { 549ca94ec43SDaniel Drake input->name = old_input->name; 550ca94ec43SDaniel Drake input->phys = old_input->phys; 551ca94ec43SDaniel Drake input->id = old_input->id; 552ca94ec43SDaniel Drake input->dev.parent = old_input->dev.parent; 553ca94ec43SDaniel Drake } 554df08ef27SAndres Salomon 555ca94ec43SDaniel Drake memset(input->evbit, 0, sizeof(input->evbit)); 556ca94ec43SDaniel Drake memset(input->relbit, 0, sizeof(input->relbit)); 557ca94ec43SDaniel Drake memset(input->keybit, 0, sizeof(input->keybit)); 558ca94ec43SDaniel Drake 559ca94ec43SDaniel Drake /* All modes report left and right buttons */ 560ca94ec43SDaniel Drake __set_bit(EV_KEY, input->evbit); 561ca94ec43SDaniel Drake __set_bit(BTN_LEFT, input->keybit); 562ca94ec43SDaniel Drake __set_bit(BTN_RIGHT, input->keybit); 563ca94ec43SDaniel Drake 564ca94ec43SDaniel Drake switch (mode) { 565ca94ec43SDaniel Drake case HGPK_MODE_MOUSE: 566ca94ec43SDaniel Drake __set_bit(EV_REL, input->evbit); 567ca94ec43SDaniel Drake __set_bit(REL_X, input->relbit); 568ca94ec43SDaniel Drake __set_bit(REL_Y, input->relbit); 569ca94ec43SDaniel Drake break; 570ca94ec43SDaniel Drake 571ca94ec43SDaniel Drake case HGPK_MODE_GLIDESENSOR: 572ca94ec43SDaniel Drake __set_bit(BTN_TOUCH, input->keybit); 573ca94ec43SDaniel Drake __set_bit(BTN_TOOL_FINGER, input->keybit); 574ca94ec43SDaniel Drake 575ca94ec43SDaniel Drake __set_bit(EV_ABS, input->evbit); 576ca94ec43SDaniel Drake 577ca94ec43SDaniel Drake /* GlideSensor has pressure sensor, PenTablet does not */ 578ca94ec43SDaniel Drake input_set_abs_params(input, ABS_PRESSURE, 0, 15, 0, 0); 579ca94ec43SDaniel Drake 580ca94ec43SDaniel Drake /* From device specs */ 581ca94ec43SDaniel Drake input_set_abs_params(input, ABS_X, 0, 399, 0, 0); 582ca94ec43SDaniel Drake input_set_abs_params(input, ABS_Y, 0, 290, 0, 0); 583ca94ec43SDaniel Drake 584ca94ec43SDaniel Drake /* Calculated by hand based on usable size (52mm x 38mm) */ 585ca94ec43SDaniel Drake input_abs_set_res(input, ABS_X, 8); 586ca94ec43SDaniel Drake input_abs_set_res(input, ABS_Y, 8); 587ca94ec43SDaniel Drake break; 588ca94ec43SDaniel Drake 589ca94ec43SDaniel Drake case HGPK_MODE_PENTABLET: 590ca94ec43SDaniel Drake __set_bit(BTN_TOUCH, input->keybit); 591ca94ec43SDaniel Drake __set_bit(BTN_TOOL_FINGER, input->keybit); 592ca94ec43SDaniel Drake 593ca94ec43SDaniel Drake __set_bit(EV_ABS, input->evbit); 594ca94ec43SDaniel Drake 595ca94ec43SDaniel Drake /* From device specs */ 596ca94ec43SDaniel Drake input_set_abs_params(input, ABS_X, 0, 999, 0, 0); 597ca94ec43SDaniel Drake input_set_abs_params(input, ABS_Y, 5, 239, 0, 0); 598ca94ec43SDaniel Drake 599ca94ec43SDaniel Drake /* Calculated by hand based on usable size (156mm x 38mm) */ 600ca94ec43SDaniel Drake input_abs_set_res(input, ABS_X, 6); 601ca94ec43SDaniel Drake input_abs_set_res(input, ABS_Y, 8); 602ca94ec43SDaniel Drake break; 603ca94ec43SDaniel Drake 604ca94ec43SDaniel Drake default: 605ca94ec43SDaniel Drake BUG(); 606ca94ec43SDaniel Drake } 607ca94ec43SDaniel Drake } 608ca94ec43SDaniel Drake 609ca94ec43SDaniel Drake static int hgpk_reset_device(struct psmouse *psmouse, bool recalibrate) 610ca94ec43SDaniel Drake { 611ca94ec43SDaniel Drake int err; 612ca94ec43SDaniel Drake 613df08ef27SAndres Salomon psmouse_reset(psmouse); 614df08ef27SAndres Salomon 615ca94ec43SDaniel Drake if (recalibrate) { 616ca94ec43SDaniel Drake struct ps2dev *ps2dev = &psmouse->ps2dev; 617ca94ec43SDaniel Drake 618df08ef27SAndres Salomon /* send the recalibrate request */ 619df08ef27SAndres Salomon if (ps2_command(ps2dev, NULL, 0xf5) || 620df08ef27SAndres Salomon ps2_command(ps2dev, NULL, 0xf5) || 621df08ef27SAndres Salomon ps2_command(ps2dev, NULL, 0xe6) || 622df08ef27SAndres Salomon ps2_command(ps2dev, NULL, 0xf5)) { 623df08ef27SAndres Salomon return -1; 624df08ef27SAndres Salomon } 625df08ef27SAndres Salomon 626df08ef27SAndres Salomon /* according to ALPS, 150mS is required for recalibration */ 627df08ef27SAndres Salomon msleep(150); 628ca94ec43SDaniel Drake } 629df08ef27SAndres Salomon 630ca94ec43SDaniel Drake err = hgpk_select_mode(psmouse); 631ca94ec43SDaniel Drake if (err) { 632*b5d21704SDmitry Torokhov psmouse_err(psmouse, "failed to select mode\n"); 633ca94ec43SDaniel Drake return err; 634ca94ec43SDaniel Drake } 635ca94ec43SDaniel Drake 636ca94ec43SDaniel Drake hgpk_reset_hack_state(psmouse); 637ca94ec43SDaniel Drake 638ca94ec43SDaniel Drake return 0; 639ca94ec43SDaniel Drake } 640ca94ec43SDaniel Drake 641ca94ec43SDaniel Drake static int hgpk_force_recalibrate(struct psmouse *psmouse) 642ca94ec43SDaniel Drake { 643ca94ec43SDaniel Drake struct ps2dev *ps2dev = &psmouse->ps2dev; 644ca94ec43SDaniel Drake struct hgpk_data *priv = psmouse->private; 645ca94ec43SDaniel Drake int err; 646ca94ec43SDaniel Drake 647ca94ec43SDaniel Drake /* C-series touchpads added the recalibrate command */ 648ca94ec43SDaniel Drake if (psmouse->model < HGPK_MODEL_C) 649ca94ec43SDaniel Drake return 0; 650ca94ec43SDaniel Drake 65134caed20SDaniel Drake if (!autorecal) { 652*b5d21704SDmitry Torokhov psmouse_dbg(psmouse, "recalibration disabled, ignoring\n"); 65334caed20SDaniel Drake return 0; 65434caed20SDaniel Drake } 65534caed20SDaniel Drake 656*b5d21704SDmitry Torokhov psmouse_dbg(psmouse, "recalibrating touchpad..\n"); 65734caed20SDaniel Drake 658ca94ec43SDaniel Drake /* we don't want to race with the irq handler, nor with resyncs */ 659ca94ec43SDaniel Drake psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); 660ca94ec43SDaniel Drake 661ca94ec43SDaniel Drake /* start by resetting the device */ 662ca94ec43SDaniel Drake err = hgpk_reset_device(psmouse, true); 663ca94ec43SDaniel Drake if (err) 664ca94ec43SDaniel Drake return err; 665ca94ec43SDaniel Drake 666ca94ec43SDaniel Drake /* 667ca94ec43SDaniel Drake * XXX: If a finger is down during this delay, recalibration will 668df08ef27SAndres Salomon * detect capacitance incorrectly. This is a hardware bug, and 669df08ef27SAndres Salomon * we don't have a good way to deal with it. The 2s window stuff 670df08ef27SAndres Salomon * (below) is our best option for now. 671df08ef27SAndres Salomon */ 672df08ef27SAndres Salomon 673df08ef27SAndres Salomon if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE)) 674df08ef27SAndres Salomon return -1; 675df08ef27SAndres Salomon 676df08ef27SAndres Salomon psmouse_set_state(psmouse, PSMOUSE_ACTIVATED); 677df08ef27SAndres Salomon 67834caed20SDaniel Drake if (tpdebug) 679*b5d21704SDmitry Torokhov psmouse_dbg(psmouse, "touchpad reactivated\n"); 68034caed20SDaniel Drake 681ca94ec43SDaniel Drake /* 68234caed20SDaniel Drake * If we get packets right away after recalibrating, it's likely 68334caed20SDaniel Drake * that a finger was on the touchpad. If so, it's probably 68434caed20SDaniel Drake * miscalibrated, so we optionally schedule another. 685df08ef27SAndres Salomon */ 68634caed20SDaniel Drake if (recal_guard_time) 68734caed20SDaniel Drake priv->recalib_window = jiffies + 68834caed20SDaniel Drake msecs_to_jiffies(recal_guard_time); 689df08ef27SAndres Salomon 690df08ef27SAndres Salomon return 0; 691df08ef27SAndres Salomon } 692df08ef27SAndres Salomon 693df08ef27SAndres Salomon /* 69420a4c261SPaul Fox * This puts the touchpad in a power saving mode; according to ALPS, current 69520a4c261SPaul Fox * consumption goes down to 50uA after running this. To turn power back on, 69620a4c261SPaul Fox * we drive MS-DAT low. Measuring with a 1mA resolution ammeter says that 69720a4c261SPaul Fox * the current on the SUS_3.3V rail drops from 3mA or 4mA to 0 when we do this. 69820a4c261SPaul Fox * 69920a4c261SPaul Fox * We have no formal spec that details this operation -- the low-power 70020a4c261SPaul Fox * sequence came from a long-lost email trail. 701df08ef27SAndres Salomon */ 70220a4c261SPaul Fox static int hgpk_toggle_powersave(struct psmouse *psmouse, int enable) 703df08ef27SAndres Salomon { 704df08ef27SAndres Salomon struct ps2dev *ps2dev = &psmouse->ps2dev; 705df08ef27SAndres Salomon int timeo; 706ca94ec43SDaniel Drake int err; 707df08ef27SAndres Salomon 708df08ef27SAndres Salomon /* Added on D-series touchpads */ 709df08ef27SAndres Salomon if (psmouse->model < HGPK_MODEL_D) 710df08ef27SAndres Salomon return 0; 711df08ef27SAndres Salomon 712df08ef27SAndres Salomon if (enable) { 713df08ef27SAndres Salomon psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); 714df08ef27SAndres Salomon 715df08ef27SAndres Salomon /* 716df08ef27SAndres Salomon * Sending a byte will drive MS-DAT low; this will wake up 717df08ef27SAndres Salomon * the controller. Once we get an ACK back from it, it 718df08ef27SAndres Salomon * means we can continue with the touchpad re-init. ALPS 719df08ef27SAndres Salomon * tells us that 1s should be long enough, so set that as 72020a4c261SPaul Fox * the upper bound. (in practice, it takes about 3 loops.) 721df08ef27SAndres Salomon */ 722df08ef27SAndres Salomon for (timeo = 20; timeo > 0; timeo--) { 723df08ef27SAndres Salomon if (!ps2_sendbyte(&psmouse->ps2dev, 724df08ef27SAndres Salomon PSMOUSE_CMD_DISABLE, 20)) 725df08ef27SAndres Salomon break; 72620a4c261SPaul Fox msleep(25); 727df08ef27SAndres Salomon } 728df08ef27SAndres Salomon 729ca94ec43SDaniel Drake err = hgpk_reset_device(psmouse, false); 730ca94ec43SDaniel Drake if (err) { 731*b5d21704SDmitry Torokhov psmouse_err(psmouse, "Failed to reset device!\n"); 732ca94ec43SDaniel Drake return err; 733ca94ec43SDaniel Drake } 734df08ef27SAndres Salomon 735df08ef27SAndres Salomon /* should be all set, enable the touchpad */ 736df08ef27SAndres Salomon ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_ENABLE); 737df08ef27SAndres Salomon psmouse_set_state(psmouse, PSMOUSE_ACTIVATED); 738*b5d21704SDmitry Torokhov psmouse_dbg(psmouse, "Touchpad powered up.\n"); 739df08ef27SAndres Salomon } else { 740*b5d21704SDmitry Torokhov psmouse_dbg(psmouse, "Powering off touchpad.\n"); 741df08ef27SAndres Salomon 742df08ef27SAndres Salomon if (ps2_command(ps2dev, NULL, 0xec) || 743df08ef27SAndres Salomon ps2_command(ps2dev, NULL, 0xec) || 744df08ef27SAndres Salomon ps2_command(ps2dev, NULL, 0xea)) { 745df08ef27SAndres Salomon return -1; 746df08ef27SAndres Salomon } 747df08ef27SAndres Salomon 74820a4c261SPaul Fox psmouse_set_state(psmouse, PSMOUSE_IGNORE); 74920a4c261SPaul Fox 750df08ef27SAndres Salomon /* probably won't see an ACK, the touchpad will be off */ 751df08ef27SAndres Salomon ps2_sendbyte(&psmouse->ps2dev, 0xec, 20); 752df08ef27SAndres Salomon } 753df08ef27SAndres Salomon 754df08ef27SAndres Salomon return 0; 755df08ef27SAndres Salomon } 756df08ef27SAndres Salomon 757df08ef27SAndres Salomon static int hgpk_poll(struct psmouse *psmouse) 758df08ef27SAndres Salomon { 759df08ef27SAndres Salomon /* We can't poll, so always return failure. */ 760df08ef27SAndres Salomon return -1; 761df08ef27SAndres Salomon } 762df08ef27SAndres Salomon 763df08ef27SAndres Salomon static int hgpk_reconnect(struct psmouse *psmouse) 764df08ef27SAndres Salomon { 76520a4c261SPaul Fox struct hgpk_data *priv = psmouse->private; 76620a4c261SPaul Fox 767ca94ec43SDaniel Drake /* 768ca94ec43SDaniel Drake * During suspend/resume the ps2 rails remain powered. We don't want 769df08ef27SAndres Salomon * to do a reset because it's flush data out of buffers; however, 770ca94ec43SDaniel Drake * earlier prototypes (B1) had some brokenness that required a reset. 771ca94ec43SDaniel Drake */ 772df08ef27SAndres Salomon if (olpc_board_at_least(olpc_board(0xb2))) 773df08ef27SAndres Salomon if (psmouse->ps2dev.serio->dev.power.power_state.event != 774df08ef27SAndres Salomon PM_EVENT_ON) 775df08ef27SAndres Salomon return 0; 776df08ef27SAndres Salomon 77720a4c261SPaul Fox priv->powered = 1; 778ca94ec43SDaniel Drake return hgpk_reset_device(psmouse, false); 779df08ef27SAndres Salomon } 780df08ef27SAndres Salomon 781df08ef27SAndres Salomon static ssize_t hgpk_show_powered(struct psmouse *psmouse, void *data, char *buf) 782df08ef27SAndres Salomon { 783df08ef27SAndres Salomon struct hgpk_data *priv = psmouse->private; 784df08ef27SAndres Salomon 785df08ef27SAndres Salomon return sprintf(buf, "%d\n", priv->powered); 786df08ef27SAndres Salomon } 787df08ef27SAndres Salomon 788df08ef27SAndres Salomon static ssize_t hgpk_set_powered(struct psmouse *psmouse, void *data, 789df08ef27SAndres Salomon const char *buf, size_t count) 790df08ef27SAndres Salomon { 791df08ef27SAndres Salomon struct hgpk_data *priv = psmouse->private; 792df08ef27SAndres Salomon unsigned long value; 793df08ef27SAndres Salomon int err; 794df08ef27SAndres Salomon 795df08ef27SAndres Salomon err = strict_strtoul(buf, 10, &value); 796df08ef27SAndres Salomon if (err || value > 1) 797df08ef27SAndres Salomon return -EINVAL; 798df08ef27SAndres Salomon 799df08ef27SAndres Salomon if (value != priv->powered) { 800df08ef27SAndres Salomon /* 801df08ef27SAndres Salomon * hgpk_toggle_power will deal w/ state so 802df08ef27SAndres Salomon * we're not racing w/ irq 803df08ef27SAndres Salomon */ 80420a4c261SPaul Fox err = hgpk_toggle_powersave(psmouse, value); 805df08ef27SAndres Salomon if (!err) 806df08ef27SAndres Salomon priv->powered = value; 807df08ef27SAndres Salomon } 808df08ef27SAndres Salomon 809df08ef27SAndres Salomon return err ? err : count; 810df08ef27SAndres Salomon } 811df08ef27SAndres Salomon 812df08ef27SAndres Salomon __PSMOUSE_DEFINE_ATTR(powered, S_IWUSR | S_IRUGO, NULL, 813b7802c5cSDmitry Torokhov hgpk_show_powered, hgpk_set_powered, false); 814df08ef27SAndres Salomon 815ca94ec43SDaniel Drake static ssize_t attr_show_mode(struct psmouse *psmouse, void *data, char *buf) 816ca94ec43SDaniel Drake { 817ca94ec43SDaniel Drake struct hgpk_data *priv = psmouse->private; 818ca94ec43SDaniel Drake 819ca94ec43SDaniel Drake return sprintf(buf, "%s\n", hgpk_mode_names[priv->mode]); 820ca94ec43SDaniel Drake } 821ca94ec43SDaniel Drake 822ca94ec43SDaniel Drake static ssize_t attr_set_mode(struct psmouse *psmouse, void *data, 823ca94ec43SDaniel Drake const char *buf, size_t len) 824ca94ec43SDaniel Drake { 825ca94ec43SDaniel Drake struct hgpk_data *priv = psmouse->private; 826ca94ec43SDaniel Drake enum hgpk_mode old_mode = priv->mode; 827ca94ec43SDaniel Drake enum hgpk_mode new_mode = hgpk_mode_from_name(buf, len); 828ca94ec43SDaniel Drake struct input_dev *old_dev = psmouse->dev; 829ca94ec43SDaniel Drake struct input_dev *new_dev; 830ca94ec43SDaniel Drake int err; 831ca94ec43SDaniel Drake 832ca94ec43SDaniel Drake if (new_mode == HGPK_MODE_INVALID) 833ca94ec43SDaniel Drake return -EINVAL; 834ca94ec43SDaniel Drake 835ca94ec43SDaniel Drake if (old_mode == new_mode) 836ca94ec43SDaniel Drake return len; 837ca94ec43SDaniel Drake 838ca94ec43SDaniel Drake new_dev = input_allocate_device(); 839ca94ec43SDaniel Drake if (!new_dev) 840ca94ec43SDaniel Drake return -ENOMEM; 841ca94ec43SDaniel Drake 842ca94ec43SDaniel Drake psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); 843ca94ec43SDaniel Drake 844ca94ec43SDaniel Drake /* Switch device into the new mode */ 845ca94ec43SDaniel Drake priv->mode = new_mode; 846ca94ec43SDaniel Drake err = hgpk_reset_device(psmouse, false); 847ca94ec43SDaniel Drake if (err) 848ca94ec43SDaniel Drake goto err_try_restore; 849ca94ec43SDaniel Drake 850ca94ec43SDaniel Drake hgpk_setup_input_device(new_dev, old_dev, new_mode); 851ca94ec43SDaniel Drake 852ca94ec43SDaniel Drake psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); 853ca94ec43SDaniel Drake 854ca94ec43SDaniel Drake err = input_register_device(new_dev); 855ca94ec43SDaniel Drake if (err) 856ca94ec43SDaniel Drake goto err_try_restore; 857ca94ec43SDaniel Drake 858ca94ec43SDaniel Drake psmouse->dev = new_dev; 859ca94ec43SDaniel Drake input_unregister_device(old_dev); 860ca94ec43SDaniel Drake 861ca94ec43SDaniel Drake return len; 862ca94ec43SDaniel Drake 863ca94ec43SDaniel Drake err_try_restore: 864ca94ec43SDaniel Drake input_free_device(new_dev); 865ca94ec43SDaniel Drake priv->mode = old_mode; 866ca94ec43SDaniel Drake hgpk_reset_device(psmouse, false); 867ca94ec43SDaniel Drake 868ca94ec43SDaniel Drake return err; 869ca94ec43SDaniel Drake } 870ca94ec43SDaniel Drake 871ca94ec43SDaniel Drake PSMOUSE_DEFINE_ATTR(hgpk_mode, S_IWUSR | S_IRUGO, NULL, 872ca94ec43SDaniel Drake attr_show_mode, attr_set_mode); 873ca94ec43SDaniel Drake 874c46dd1ebSPaul Fox static ssize_t hgpk_trigger_recal_show(struct psmouse *psmouse, 875c46dd1ebSPaul Fox void *data, char *buf) 876c46dd1ebSPaul Fox { 877c46dd1ebSPaul Fox return -EINVAL; 878c46dd1ebSPaul Fox } 879c46dd1ebSPaul Fox 880c46dd1ebSPaul Fox static ssize_t hgpk_trigger_recal(struct psmouse *psmouse, void *data, 881c46dd1ebSPaul Fox const char *buf, size_t count) 882c46dd1ebSPaul Fox { 883c46dd1ebSPaul Fox struct hgpk_data *priv = psmouse->private; 884c46dd1ebSPaul Fox unsigned long value; 885c46dd1ebSPaul Fox int err; 886c46dd1ebSPaul Fox 887c46dd1ebSPaul Fox err = strict_strtoul(buf, 10, &value); 888c46dd1ebSPaul Fox if (err || value != 1) 889c46dd1ebSPaul Fox return -EINVAL; 890c46dd1ebSPaul Fox 891c46dd1ebSPaul Fox /* 892c46dd1ebSPaul Fox * We queue work instead of doing recalibration right here 893c46dd1ebSPaul Fox * to avoid adding locking to to hgpk_force_recalibrate() 894c46dd1ebSPaul Fox * since workqueue provides serialization. 895c46dd1ebSPaul Fox */ 896c46dd1ebSPaul Fox psmouse_queue_work(psmouse, &priv->recalib_wq, 0); 897c46dd1ebSPaul Fox return count; 898c46dd1ebSPaul Fox } 899c46dd1ebSPaul Fox 900c46dd1ebSPaul Fox __PSMOUSE_DEFINE_ATTR(recalibrate, S_IWUSR | S_IRUGO, NULL, 901b7802c5cSDmitry Torokhov hgpk_trigger_recal_show, hgpk_trigger_recal, false); 902c46dd1ebSPaul Fox 903df08ef27SAndres Salomon static void hgpk_disconnect(struct psmouse *psmouse) 904df08ef27SAndres Salomon { 905df08ef27SAndres Salomon struct hgpk_data *priv = psmouse->private; 906df08ef27SAndres Salomon 907df08ef27SAndres Salomon device_remove_file(&psmouse->ps2dev.serio->dev, 908df08ef27SAndres Salomon &psmouse_attr_powered.dattr); 909ca94ec43SDaniel Drake device_remove_file(&psmouse->ps2dev.serio->dev, 910ca94ec43SDaniel Drake &psmouse_attr_hgpk_mode.dattr); 911c46dd1ebSPaul Fox 912c46dd1ebSPaul Fox if (psmouse->model >= HGPK_MODEL_C) 913c46dd1ebSPaul Fox device_remove_file(&psmouse->ps2dev.serio->dev, 914c46dd1ebSPaul Fox &psmouse_attr_recalibrate.dattr); 915c46dd1ebSPaul Fox 916df08ef27SAndres Salomon psmouse_reset(psmouse); 917df08ef27SAndres Salomon kfree(priv); 918df08ef27SAndres Salomon } 919df08ef27SAndres Salomon 920df08ef27SAndres Salomon static void hgpk_recalib_work(struct work_struct *work) 921df08ef27SAndres Salomon { 922bf6aede7SJean Delvare struct delayed_work *w = to_delayed_work(work); 923df08ef27SAndres Salomon struct hgpk_data *priv = container_of(w, struct hgpk_data, recalib_wq); 924df08ef27SAndres Salomon struct psmouse *psmouse = priv->psmouse; 925df08ef27SAndres Salomon 926df08ef27SAndres Salomon if (hgpk_force_recalibrate(psmouse)) 927*b5d21704SDmitry Torokhov psmouse_err(psmouse, "recalibration failed!\n"); 928df08ef27SAndres Salomon } 929df08ef27SAndres Salomon 930df08ef27SAndres Salomon static int hgpk_register(struct psmouse *psmouse) 931df08ef27SAndres Salomon { 932ca94ec43SDaniel Drake struct hgpk_data *priv = psmouse->private; 933df08ef27SAndres Salomon int err; 934df08ef27SAndres Salomon 935df08ef27SAndres Salomon /* register handlers */ 936df08ef27SAndres Salomon psmouse->protocol_handler = hgpk_process_byte; 937df08ef27SAndres Salomon psmouse->poll = hgpk_poll; 938df08ef27SAndres Salomon psmouse->disconnect = hgpk_disconnect; 939df08ef27SAndres Salomon psmouse->reconnect = hgpk_reconnect; 940df08ef27SAndres Salomon 941df08ef27SAndres Salomon /* Disable the idle resync. */ 942df08ef27SAndres Salomon psmouse->resync_time = 0; 943df08ef27SAndres Salomon /* Reset after a lot of bad bytes. */ 944df08ef27SAndres Salomon psmouse->resetafter = 1024; 945df08ef27SAndres Salomon 946ca94ec43SDaniel Drake hgpk_setup_input_device(psmouse->dev, NULL, priv->mode); 947ca94ec43SDaniel Drake 948df08ef27SAndres Salomon err = device_create_file(&psmouse->ps2dev.serio->dev, 949df08ef27SAndres Salomon &psmouse_attr_powered.dattr); 950c46dd1ebSPaul Fox if (err) { 951*b5d21704SDmitry Torokhov psmouse_err(psmouse, "Failed creating 'powered' sysfs node\n"); 952df08ef27SAndres Salomon return err; 953df08ef27SAndres Salomon } 954df08ef27SAndres Salomon 955ca94ec43SDaniel Drake err = device_create_file(&psmouse->ps2dev.serio->dev, 956ca94ec43SDaniel Drake &psmouse_attr_hgpk_mode.dattr); 957ca94ec43SDaniel Drake if (err) { 958*b5d21704SDmitry Torokhov psmouse_err(psmouse, 959*b5d21704SDmitry Torokhov "Failed creating 'hgpk_mode' sysfs node\n"); 960ca94ec43SDaniel Drake goto err_remove_powered; 961ca94ec43SDaniel Drake } 962ca94ec43SDaniel Drake 963c46dd1ebSPaul Fox /* C-series touchpads added the recalibrate command */ 964c46dd1ebSPaul Fox if (psmouse->model >= HGPK_MODEL_C) { 965c46dd1ebSPaul Fox err = device_create_file(&psmouse->ps2dev.serio->dev, 966c46dd1ebSPaul Fox &psmouse_attr_recalibrate.dattr); 967c46dd1ebSPaul Fox if (err) { 968*b5d21704SDmitry Torokhov psmouse_err(psmouse, 969c46dd1ebSPaul Fox "Failed creating 'recalibrate' sysfs node\n"); 970ca94ec43SDaniel Drake goto err_remove_mode; 971c46dd1ebSPaul Fox } 972c46dd1ebSPaul Fox } 973c46dd1ebSPaul Fox 974c46dd1ebSPaul Fox return 0; 975ca94ec43SDaniel Drake 976ca94ec43SDaniel Drake err_remove_mode: 977ca94ec43SDaniel Drake device_remove_file(&psmouse->ps2dev.serio->dev, 978ca94ec43SDaniel Drake &psmouse_attr_hgpk_mode.dattr); 979ca94ec43SDaniel Drake err_remove_powered: 980ca94ec43SDaniel Drake device_remove_file(&psmouse->ps2dev.serio->dev, 981ca94ec43SDaniel Drake &psmouse_attr_powered.dattr); 982ca94ec43SDaniel Drake return err; 983c46dd1ebSPaul Fox } 984c46dd1ebSPaul Fox 985df08ef27SAndres Salomon int hgpk_init(struct psmouse *psmouse) 986df08ef27SAndres Salomon { 987df08ef27SAndres Salomon struct hgpk_data *priv; 988ca94ec43SDaniel Drake int err; 989df08ef27SAndres Salomon 990df08ef27SAndres Salomon priv = kzalloc(sizeof(struct hgpk_data), GFP_KERNEL); 991ca94ec43SDaniel Drake if (!priv) { 992ca94ec43SDaniel Drake err = -ENOMEM; 993df08ef27SAndres Salomon goto alloc_fail; 994ca94ec43SDaniel Drake } 995df08ef27SAndres Salomon 996df08ef27SAndres Salomon psmouse->private = priv; 997ca94ec43SDaniel Drake 998df08ef27SAndres Salomon priv->psmouse = psmouse; 999b7802c5cSDmitry Torokhov priv->powered = true; 1000ca94ec43SDaniel Drake priv->mode = hgpk_default_mode; 1001df08ef27SAndres Salomon INIT_DELAYED_WORK(&priv->recalib_wq, hgpk_recalib_work); 1002df08ef27SAndres Salomon 1003ca94ec43SDaniel Drake err = hgpk_reset_device(psmouse, false); 1004df08ef27SAndres Salomon if (err) 1005df08ef27SAndres Salomon goto init_fail; 1006df08ef27SAndres Salomon 1007df08ef27SAndres Salomon err = hgpk_register(psmouse); 1008df08ef27SAndres Salomon if (err) 1009df08ef27SAndres Salomon goto init_fail; 1010df08ef27SAndres Salomon 1011df08ef27SAndres Salomon return 0; 1012df08ef27SAndres Salomon 1013df08ef27SAndres Salomon init_fail: 1014df08ef27SAndres Salomon kfree(priv); 1015df08ef27SAndres Salomon alloc_fail: 1016df08ef27SAndres Salomon return err; 1017df08ef27SAndres Salomon } 1018df08ef27SAndres Salomon 1019df08ef27SAndres Salomon static enum hgpk_model_t hgpk_get_model(struct psmouse *psmouse) 1020df08ef27SAndres Salomon { 1021df08ef27SAndres Salomon struct ps2dev *ps2dev = &psmouse->ps2dev; 1022df08ef27SAndres Salomon unsigned char param[3]; 1023df08ef27SAndres Salomon 1024df08ef27SAndres Salomon /* E7, E7, E7, E9 gets us a 3 byte identifier */ 1025df08ef27SAndres Salomon if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) || 1026df08ef27SAndres Salomon ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) || 1027df08ef27SAndres Salomon ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) || 1028df08ef27SAndres Salomon ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) { 1029df08ef27SAndres Salomon return -EIO; 1030df08ef27SAndres Salomon } 1031df08ef27SAndres Salomon 1032*b5d21704SDmitry Torokhov psmouse_dbg(psmouse, "ID: %02x %02x %02x\n", param[0], param[1], param[2]); 1033df08ef27SAndres Salomon 1034df08ef27SAndres Salomon /* HGPK signature: 0x67, 0x00, 0x<model> */ 1035df08ef27SAndres Salomon if (param[0] != 0x67 || param[1] != 0x00) 1036df08ef27SAndres Salomon return -ENODEV; 1037df08ef27SAndres Salomon 1038*b5d21704SDmitry Torokhov psmouse_info(psmouse, "OLPC touchpad revision 0x%x\n", param[2]); 1039df08ef27SAndres Salomon 1040df08ef27SAndres Salomon return param[2]; 1041df08ef27SAndres Salomon } 1042df08ef27SAndres Salomon 1043b7802c5cSDmitry Torokhov int hgpk_detect(struct psmouse *psmouse, bool set_properties) 1044df08ef27SAndres Salomon { 1045df08ef27SAndres Salomon int version; 1046df08ef27SAndres Salomon 1047df08ef27SAndres Salomon version = hgpk_get_model(psmouse); 1048df08ef27SAndres Salomon if (version < 0) 1049df08ef27SAndres Salomon return version; 1050df08ef27SAndres Salomon 1051df08ef27SAndres Salomon if (set_properties) { 1052df08ef27SAndres Salomon psmouse->vendor = "ALPS"; 1053df08ef27SAndres Salomon psmouse->name = "HGPK"; 1054df08ef27SAndres Salomon psmouse->model = version; 1055df08ef27SAndres Salomon } 1056df08ef27SAndres Salomon 1057df08ef27SAndres Salomon return 0; 1058df08ef27SAndres Salomon } 1059ca94ec43SDaniel Drake 1060ca94ec43SDaniel Drake void hgpk_module_init(void) 1061ca94ec43SDaniel Drake { 1062ca94ec43SDaniel Drake hgpk_default_mode = hgpk_mode_from_name(hgpk_mode_name, 1063ca94ec43SDaniel Drake strlen(hgpk_mode_name)); 1064ca94ec43SDaniel Drake if (hgpk_default_mode == HGPK_MODE_INVALID) { 1065ca94ec43SDaniel Drake hgpk_default_mode = HGPK_MODE_MOUSE; 1066ca94ec43SDaniel Drake strlcpy(hgpk_mode_name, hgpk_mode_names[HGPK_MODE_MOUSE], 1067ca94ec43SDaniel Drake sizeof(hgpk_mode_name)); 1068ca94ec43SDaniel Drake } 1069ca94ec43SDaniel Drake } 1070