18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * OLPC HGPK (XO-1) touchpad PS/2 mouse driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 2006-2008 One Laptop Per Child
68c2ecf20Sopenharmony_ci * Authors:
78c2ecf20Sopenharmony_ci *   Zephaniah E. Hull
88c2ecf20Sopenharmony_ci *   Andres Salomon <dilinger@debian.org>
98c2ecf20Sopenharmony_ci *
108c2ecf20Sopenharmony_ci * This driver is partly based on the ALPS driver, which is:
118c2ecf20Sopenharmony_ci *
128c2ecf20Sopenharmony_ci * Copyright (c) 2003 Neil Brown <neilb@cse.unsw.edu.au>
138c2ecf20Sopenharmony_ci * Copyright (c) 2003-2005 Peter Osterlund <petero2@telia.com>
148c2ecf20Sopenharmony_ci * Copyright (c) 2004 Dmitry Torokhov <dtor@mail.ru>
158c2ecf20Sopenharmony_ci * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz>
168c2ecf20Sopenharmony_ci */
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci/*
198c2ecf20Sopenharmony_ci * The spec from ALPS is available from
208c2ecf20Sopenharmony_ci * <http://wiki.laptop.org/go/Touch_Pad/Tablet>.  It refers to this
218c2ecf20Sopenharmony_ci * device as HGPK (Hybrid GS, PT, and Keymatrix).
228c2ecf20Sopenharmony_ci *
238c2ecf20Sopenharmony_ci * The earliest versions of the device had simultaneous reporting; that
248c2ecf20Sopenharmony_ci * was removed.  After that, the device used the Advanced Mode GS/PT streaming
258c2ecf20Sopenharmony_ci * stuff.  That turned out to be too buggy to support, so we've finally
268c2ecf20Sopenharmony_ci * switched to Mouse Mode (which utilizes only the center 1/3 of the touchpad).
278c2ecf20Sopenharmony_ci */
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define DEBUG
308c2ecf20Sopenharmony_ci#include <linux/slab.h>
318c2ecf20Sopenharmony_ci#include <linux/input.h>
328c2ecf20Sopenharmony_ci#include <linux/module.h>
338c2ecf20Sopenharmony_ci#include <linux/serio.h>
348c2ecf20Sopenharmony_ci#include <linux/libps2.h>
358c2ecf20Sopenharmony_ci#include <linux/delay.h>
368c2ecf20Sopenharmony_ci#include <asm/olpc.h>
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci#include "psmouse.h"
398c2ecf20Sopenharmony_ci#include "hgpk.h"
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci#define ILLEGAL_XY 999999
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistatic bool tpdebug;
448c2ecf20Sopenharmony_cimodule_param(tpdebug, bool, 0644);
458c2ecf20Sopenharmony_ciMODULE_PARM_DESC(tpdebug, "enable debugging, dumping packets to KERN_DEBUG.");
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_cistatic int recalib_delta = 100;
488c2ecf20Sopenharmony_cimodule_param(recalib_delta, int, 0644);
498c2ecf20Sopenharmony_ciMODULE_PARM_DESC(recalib_delta,
508c2ecf20Sopenharmony_ci	"packets containing a delta this large will be discarded, and a "
518c2ecf20Sopenharmony_ci	"recalibration may be scheduled.");
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_cistatic int jumpy_delay = 20;
548c2ecf20Sopenharmony_cimodule_param(jumpy_delay, int, 0644);
558c2ecf20Sopenharmony_ciMODULE_PARM_DESC(jumpy_delay,
568c2ecf20Sopenharmony_ci	"delay (ms) before recal after jumpiness detected");
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_cistatic int spew_delay = 1;
598c2ecf20Sopenharmony_cimodule_param(spew_delay, int, 0644);
608c2ecf20Sopenharmony_ciMODULE_PARM_DESC(spew_delay,
618c2ecf20Sopenharmony_ci	"delay (ms) before recal after packet spew detected");
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_cistatic int recal_guard_time;
648c2ecf20Sopenharmony_cimodule_param(recal_guard_time, int, 0644);
658c2ecf20Sopenharmony_ciMODULE_PARM_DESC(recal_guard_time,
668c2ecf20Sopenharmony_ci	"interval (ms) during which recal will be restarted if packet received");
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_cistatic int post_interrupt_delay = 40;
698c2ecf20Sopenharmony_cimodule_param(post_interrupt_delay, int, 0644);
708c2ecf20Sopenharmony_ciMODULE_PARM_DESC(post_interrupt_delay,
718c2ecf20Sopenharmony_ci	"delay (ms) before recal after recal interrupt detected");
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_cistatic bool autorecal = true;
748c2ecf20Sopenharmony_cimodule_param(autorecal, bool, 0644);
758c2ecf20Sopenharmony_ciMODULE_PARM_DESC(autorecal, "enable recalibration in the driver");
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_cistatic char hgpk_mode_name[16];
788c2ecf20Sopenharmony_cimodule_param_string(hgpk_mode, hgpk_mode_name, sizeof(hgpk_mode_name), 0644);
798c2ecf20Sopenharmony_ciMODULE_PARM_DESC(hgpk_mode,
808c2ecf20Sopenharmony_ci	"default hgpk mode: mouse, glidesensor or pentablet");
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_cistatic int hgpk_default_mode = HGPK_MODE_MOUSE;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_cistatic const char * const hgpk_mode_names[] = {
858c2ecf20Sopenharmony_ci	[HGPK_MODE_MOUSE] = "Mouse",
868c2ecf20Sopenharmony_ci	[HGPK_MODE_GLIDESENSOR] = "GlideSensor",
878c2ecf20Sopenharmony_ci	[HGPK_MODE_PENTABLET] = "PenTablet",
888c2ecf20Sopenharmony_ci};
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_cistatic int hgpk_mode_from_name(const char *buf, int len)
918c2ecf20Sopenharmony_ci{
928c2ecf20Sopenharmony_ci	int i;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(hgpk_mode_names); i++) {
958c2ecf20Sopenharmony_ci		const char *name = hgpk_mode_names[i];
968c2ecf20Sopenharmony_ci		if (strlen(name) == len && !strncasecmp(name, buf, len))
978c2ecf20Sopenharmony_ci			return i;
988c2ecf20Sopenharmony_ci	}
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	return HGPK_MODE_INVALID;
1018c2ecf20Sopenharmony_ci}
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci/*
1048c2ecf20Sopenharmony_ci * see if new value is within 20% of half of old value
1058c2ecf20Sopenharmony_ci */
1068c2ecf20Sopenharmony_cistatic int approx_half(int curr, int prev)
1078c2ecf20Sopenharmony_ci{
1088c2ecf20Sopenharmony_ci	int belowhalf, abovehalf;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	if (curr < 5 || prev < 5)
1118c2ecf20Sopenharmony_ci		return 0;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	belowhalf = (prev * 8) / 20;
1148c2ecf20Sopenharmony_ci	abovehalf = (prev * 12) / 20;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	return belowhalf < curr && curr <= abovehalf;
1178c2ecf20Sopenharmony_ci}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci/*
1208c2ecf20Sopenharmony_ci * Throw out oddly large delta packets, and any that immediately follow whose
1218c2ecf20Sopenharmony_ci * values are each approximately half of the previous.  It seems that the ALPS
1228c2ecf20Sopenharmony_ci * firmware emits errant packets, and they get averaged out slowly.
1238c2ecf20Sopenharmony_ci */
1248c2ecf20Sopenharmony_cistatic int hgpk_discard_decay_hack(struct psmouse *psmouse, int x, int y)
1258c2ecf20Sopenharmony_ci{
1268c2ecf20Sopenharmony_ci	struct hgpk_data *priv = psmouse->private;
1278c2ecf20Sopenharmony_ci	int avx, avy;
1288c2ecf20Sopenharmony_ci	bool do_recal = false;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	avx = abs(x);
1318c2ecf20Sopenharmony_ci	avy = abs(y);
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	/* discard if too big, or half that but > 4 times the prev delta */
1348c2ecf20Sopenharmony_ci	if (avx > recalib_delta ||
1358c2ecf20Sopenharmony_ci		(avx > recalib_delta / 2 && ((avx / 4) > priv->xlast))) {
1368c2ecf20Sopenharmony_ci		psmouse_warn(psmouse, "detected %dpx jump in x\n", x);
1378c2ecf20Sopenharmony_ci		priv->xbigj = avx;
1388c2ecf20Sopenharmony_ci	} else if (approx_half(avx, priv->xbigj)) {
1398c2ecf20Sopenharmony_ci		psmouse_warn(psmouse, "detected secondary %dpx jump in x\n", x);
1408c2ecf20Sopenharmony_ci		priv->xbigj = avx;
1418c2ecf20Sopenharmony_ci		priv->xsaw_secondary++;
1428c2ecf20Sopenharmony_ci	} else {
1438c2ecf20Sopenharmony_ci		if (priv->xbigj && priv->xsaw_secondary > 1)
1448c2ecf20Sopenharmony_ci			do_recal = true;
1458c2ecf20Sopenharmony_ci		priv->xbigj = 0;
1468c2ecf20Sopenharmony_ci		priv->xsaw_secondary = 0;
1478c2ecf20Sopenharmony_ci	}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	if (avy > recalib_delta ||
1508c2ecf20Sopenharmony_ci		(avy > recalib_delta / 2 && ((avy / 4) > priv->ylast))) {
1518c2ecf20Sopenharmony_ci		psmouse_warn(psmouse, "detected %dpx jump in y\n", y);
1528c2ecf20Sopenharmony_ci		priv->ybigj = avy;
1538c2ecf20Sopenharmony_ci	} else if (approx_half(avy, priv->ybigj)) {
1548c2ecf20Sopenharmony_ci		psmouse_warn(psmouse, "detected secondary %dpx jump in y\n", y);
1558c2ecf20Sopenharmony_ci		priv->ybigj = avy;
1568c2ecf20Sopenharmony_ci		priv->ysaw_secondary++;
1578c2ecf20Sopenharmony_ci	} else {
1588c2ecf20Sopenharmony_ci		if (priv->ybigj && priv->ysaw_secondary > 1)
1598c2ecf20Sopenharmony_ci			do_recal = true;
1608c2ecf20Sopenharmony_ci		priv->ybigj = 0;
1618c2ecf20Sopenharmony_ci		priv->ysaw_secondary = 0;
1628c2ecf20Sopenharmony_ci	}
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	priv->xlast = avx;
1658c2ecf20Sopenharmony_ci	priv->ylast = avy;
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	if (do_recal && jumpy_delay) {
1688c2ecf20Sopenharmony_ci		psmouse_warn(psmouse, "scheduling recalibration\n");
1698c2ecf20Sopenharmony_ci		psmouse_queue_work(psmouse, &priv->recalib_wq,
1708c2ecf20Sopenharmony_ci				msecs_to_jiffies(jumpy_delay));
1718c2ecf20Sopenharmony_ci	}
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	return priv->xbigj || priv->ybigj;
1748c2ecf20Sopenharmony_ci}
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_cistatic void hgpk_reset_spew_detection(struct hgpk_data *priv)
1778c2ecf20Sopenharmony_ci{
1788c2ecf20Sopenharmony_ci	priv->spew_count = 0;
1798c2ecf20Sopenharmony_ci	priv->dupe_count = 0;
1808c2ecf20Sopenharmony_ci	priv->x_tally = 0;
1818c2ecf20Sopenharmony_ci	priv->y_tally = 0;
1828c2ecf20Sopenharmony_ci	priv->spew_flag = NO_SPEW;
1838c2ecf20Sopenharmony_ci}
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_cistatic void hgpk_reset_hack_state(struct psmouse *psmouse)
1868c2ecf20Sopenharmony_ci{
1878c2ecf20Sopenharmony_ci	struct hgpk_data *priv = psmouse->private;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	priv->abs_x = priv->abs_y = -1;
1908c2ecf20Sopenharmony_ci	priv->xlast = priv->ylast = ILLEGAL_XY;
1918c2ecf20Sopenharmony_ci	priv->xbigj = priv->ybigj = 0;
1928c2ecf20Sopenharmony_ci	priv->xsaw_secondary = priv->ysaw_secondary = 0;
1938c2ecf20Sopenharmony_ci	hgpk_reset_spew_detection(priv);
1948c2ecf20Sopenharmony_ci}
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci/*
1978c2ecf20Sopenharmony_ci * We have no idea why this particular hardware bug occurs.  The touchpad
1988c2ecf20Sopenharmony_ci * will randomly start spewing packets without anything touching the
1998c2ecf20Sopenharmony_ci * pad.  This wouldn't necessarily be bad, but it's indicative of a
2008c2ecf20Sopenharmony_ci * severely miscalibrated pad; attempting to use the touchpad while it's
2018c2ecf20Sopenharmony_ci * spewing means the cursor will jump all over the place, and act "drunk".
2028c2ecf20Sopenharmony_ci *
2038c2ecf20Sopenharmony_ci * The packets that are spewed tend to all have deltas between -2 and 2, and
2048c2ecf20Sopenharmony_ci * the cursor will move around without really going very far.  It will
2058c2ecf20Sopenharmony_ci * tend to end up in the same location; if we tally up the changes over
2068c2ecf20Sopenharmony_ci * 100 packets, we end up w/ a final delta of close to 0.  This happens
2078c2ecf20Sopenharmony_ci * pretty regularly when the touchpad is spewing, and is pretty hard to
2088c2ecf20Sopenharmony_ci * manually trigger (at least for *my* fingers).  So, it makes a perfect
2098c2ecf20Sopenharmony_ci * scheme for detecting spews.
2108c2ecf20Sopenharmony_ci */
2118c2ecf20Sopenharmony_cistatic void hgpk_spewing_hack(struct psmouse *psmouse,
2128c2ecf20Sopenharmony_ci			      int l, int r, int x, int y)
2138c2ecf20Sopenharmony_ci{
2148c2ecf20Sopenharmony_ci	struct hgpk_data *priv = psmouse->private;
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	/* ignore button press packets; many in a row could trigger
2178c2ecf20Sopenharmony_ci	 * a false-positive! */
2188c2ecf20Sopenharmony_ci	if (l || r)
2198c2ecf20Sopenharmony_ci		return;
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	/* don't track spew if the workaround feature has been turned off */
2228c2ecf20Sopenharmony_ci	if (!spew_delay)
2238c2ecf20Sopenharmony_ci		return;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	if (abs(x) > 3 || abs(y) > 3) {
2268c2ecf20Sopenharmony_ci		/* no spew, or spew ended */
2278c2ecf20Sopenharmony_ci		hgpk_reset_spew_detection(priv);
2288c2ecf20Sopenharmony_ci		return;
2298c2ecf20Sopenharmony_ci	}
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	/* Keep a tally of the overall delta to the cursor position caused by
2328c2ecf20Sopenharmony_ci	 * the spew */
2338c2ecf20Sopenharmony_ci	priv->x_tally += x;
2348c2ecf20Sopenharmony_ci	priv->y_tally += y;
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	switch (priv->spew_flag) {
2378c2ecf20Sopenharmony_ci	case NO_SPEW:
2388c2ecf20Sopenharmony_ci		/* we're not spewing, but this packet might be the start */
2398c2ecf20Sopenharmony_ci		priv->spew_flag = MAYBE_SPEWING;
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci		fallthrough;
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	case MAYBE_SPEWING:
2448c2ecf20Sopenharmony_ci		priv->spew_count++;
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci		if (priv->spew_count < SPEW_WATCH_COUNT)
2478c2ecf20Sopenharmony_ci			break;
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci		/* excessive spew detected, request recalibration */
2508c2ecf20Sopenharmony_ci		priv->spew_flag = SPEW_DETECTED;
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci		fallthrough;
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ci	case SPEW_DETECTED:
2558c2ecf20Sopenharmony_ci		/* only recalibrate when the overall delta to the cursor
2568c2ecf20Sopenharmony_ci		 * is really small. if the spew is causing significant cursor
2578c2ecf20Sopenharmony_ci		 * movement, it is probably a case of the user moving the
2588c2ecf20Sopenharmony_ci		 * cursor very slowly across the screen. */
2598c2ecf20Sopenharmony_ci		if (abs(priv->x_tally) < 3 && abs(priv->y_tally) < 3) {
2608c2ecf20Sopenharmony_ci			psmouse_warn(psmouse, "packet spew detected (%d,%d)\n",
2618c2ecf20Sopenharmony_ci				     priv->x_tally, priv->y_tally);
2628c2ecf20Sopenharmony_ci			priv->spew_flag = RECALIBRATING;
2638c2ecf20Sopenharmony_ci			psmouse_queue_work(psmouse, &priv->recalib_wq,
2648c2ecf20Sopenharmony_ci					   msecs_to_jiffies(spew_delay));
2658c2ecf20Sopenharmony_ci		}
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci		break;
2688c2ecf20Sopenharmony_ci	case RECALIBRATING:
2698c2ecf20Sopenharmony_ci		/* we already detected a spew and requested a recalibration,
2708c2ecf20Sopenharmony_ci		 * just wait for the queue to kick into action. */
2718c2ecf20Sopenharmony_ci		break;
2728c2ecf20Sopenharmony_ci	}
2738c2ecf20Sopenharmony_ci}
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci/*
2768c2ecf20Sopenharmony_ci * HGPK Mouse Mode format (standard mouse format, sans middle button)
2778c2ecf20Sopenharmony_ci *
2788c2ecf20Sopenharmony_ci * byte 0:	y-over	x-over	y-neg	x-neg	1	0	swr	swl
2798c2ecf20Sopenharmony_ci * byte 1:	x7	x6	x5	x4	x3	x2	x1	x0
2808c2ecf20Sopenharmony_ci * byte 2:	y7	y6	y5	y4	y3	y2	y1	y0
2818c2ecf20Sopenharmony_ci *
2828c2ecf20Sopenharmony_ci * swr/swl are the left/right buttons.
2838c2ecf20Sopenharmony_ci * x-neg/y-neg are the x and y delta negative bits
2848c2ecf20Sopenharmony_ci * x-over/y-over are the x and y overflow bits
2858c2ecf20Sopenharmony_ci *
2868c2ecf20Sopenharmony_ci * ---
2878c2ecf20Sopenharmony_ci *
2888c2ecf20Sopenharmony_ci * HGPK Advanced Mode - single-mode format
2898c2ecf20Sopenharmony_ci *
2908c2ecf20Sopenharmony_ci * byte 0(PT):  1    1    0    0    1    1     1     1
2918c2ecf20Sopenharmony_ci * byte 0(GS):  1    1    1    1    1    1     1     1
2928c2ecf20Sopenharmony_ci * byte 1:      0   x6   x5   x4   x3   x2    x1    x0
2938c2ecf20Sopenharmony_ci * byte 2(PT):  0    0   x9   x8   x7    ? pt-dsw    0
2948c2ecf20Sopenharmony_ci * byte 2(GS):  0  x10   x9   x8   x7    ? gs-dsw pt-dsw
2958c2ecf20Sopenharmony_ci * byte 3:      0   y9   y8   y7    1    0   swr   swl
2968c2ecf20Sopenharmony_ci * byte 4:      0   y6   y5   y4   y3   y2    y1    y0
2978c2ecf20Sopenharmony_ci * byte 5:      0   z6   z5   z4   z3   z2    z1    z0
2988c2ecf20Sopenharmony_ci *
2998c2ecf20Sopenharmony_ci * ?'s are not defined in the protocol spec, may vary between models.
3008c2ecf20Sopenharmony_ci *
3018c2ecf20Sopenharmony_ci * swr/swl are the left/right buttons.
3028c2ecf20Sopenharmony_ci *
3038c2ecf20Sopenharmony_ci * pt-dsw/gs-dsw indicate that the pt/gs sensor is detecting a
3048c2ecf20Sopenharmony_ci * pen/finger
3058c2ecf20Sopenharmony_ci */
3068c2ecf20Sopenharmony_cistatic bool hgpk_is_byte_valid(struct psmouse *psmouse, unsigned char *packet)
3078c2ecf20Sopenharmony_ci{
3088c2ecf20Sopenharmony_ci	struct hgpk_data *priv = psmouse->private;
3098c2ecf20Sopenharmony_ci	int pktcnt = psmouse->pktcnt;
3108c2ecf20Sopenharmony_ci	bool valid;
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci	switch (priv->mode) {
3138c2ecf20Sopenharmony_ci	case HGPK_MODE_MOUSE:
3148c2ecf20Sopenharmony_ci		valid = (packet[0] & 0x0C) == 0x08;
3158c2ecf20Sopenharmony_ci		break;
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci	case HGPK_MODE_GLIDESENSOR:
3188c2ecf20Sopenharmony_ci		valid = pktcnt == 1 ?
3198c2ecf20Sopenharmony_ci			packet[0] == HGPK_GS : !(packet[pktcnt - 1] & 0x80);
3208c2ecf20Sopenharmony_ci		break;
3218c2ecf20Sopenharmony_ci
3228c2ecf20Sopenharmony_ci	case HGPK_MODE_PENTABLET:
3238c2ecf20Sopenharmony_ci		valid = pktcnt == 1 ?
3248c2ecf20Sopenharmony_ci			packet[0] == HGPK_PT : !(packet[pktcnt - 1] & 0x80);
3258c2ecf20Sopenharmony_ci		break;
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_ci	default:
3288c2ecf20Sopenharmony_ci		valid = false;
3298c2ecf20Sopenharmony_ci		break;
3308c2ecf20Sopenharmony_ci	}
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci	if (!valid)
3338c2ecf20Sopenharmony_ci		psmouse_dbg(psmouse,
3348c2ecf20Sopenharmony_ci			    "bad data, mode %d (%d) %*ph\n",
3358c2ecf20Sopenharmony_ci			    priv->mode, pktcnt, 6, psmouse->packet);
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_ci	return valid;
3388c2ecf20Sopenharmony_ci}
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_cistatic void hgpk_process_advanced_packet(struct psmouse *psmouse)
3418c2ecf20Sopenharmony_ci{
3428c2ecf20Sopenharmony_ci	struct hgpk_data *priv = psmouse->private;
3438c2ecf20Sopenharmony_ci	struct input_dev *idev = psmouse->dev;
3448c2ecf20Sopenharmony_ci	unsigned char *packet = psmouse->packet;
3458c2ecf20Sopenharmony_ci	int down = !!(packet[2] & 2);
3468c2ecf20Sopenharmony_ci	int left = !!(packet[3] & 1);
3478c2ecf20Sopenharmony_ci	int right = !!(packet[3] & 2);
3488c2ecf20Sopenharmony_ci	int x = packet[1] | ((packet[2] & 0x78) << 4);
3498c2ecf20Sopenharmony_ci	int y = packet[4] | ((packet[3] & 0x70) << 3);
3508c2ecf20Sopenharmony_ci
3518c2ecf20Sopenharmony_ci	if (priv->mode == HGPK_MODE_GLIDESENSOR) {
3528c2ecf20Sopenharmony_ci		int pt_down = !!(packet[2] & 1);
3538c2ecf20Sopenharmony_ci		int finger_down = !!(packet[2] & 2);
3548c2ecf20Sopenharmony_ci		int z = packet[5];
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_ci		input_report_abs(idev, ABS_PRESSURE, z);
3578c2ecf20Sopenharmony_ci		if (tpdebug)
3588c2ecf20Sopenharmony_ci			psmouse_dbg(psmouse, "pd=%d fd=%d z=%d",
3598c2ecf20Sopenharmony_ci				    pt_down, finger_down, z);
3608c2ecf20Sopenharmony_ci	} else {
3618c2ecf20Sopenharmony_ci		/*
3628c2ecf20Sopenharmony_ci		 * PenTablet mode does not report pressure, so we don't
3638c2ecf20Sopenharmony_ci		 * report it here
3648c2ecf20Sopenharmony_ci		 */
3658c2ecf20Sopenharmony_ci		if (tpdebug)
3668c2ecf20Sopenharmony_ci			psmouse_dbg(psmouse, "pd=%d ", down);
3678c2ecf20Sopenharmony_ci	}
3688c2ecf20Sopenharmony_ci
3698c2ecf20Sopenharmony_ci	if (tpdebug)
3708c2ecf20Sopenharmony_ci		psmouse_dbg(psmouse, "l=%d r=%d x=%d y=%d\n",
3718c2ecf20Sopenharmony_ci			    left, right, x, y);
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_ci	input_report_key(idev, BTN_TOUCH, down);
3748c2ecf20Sopenharmony_ci	input_report_key(idev, BTN_LEFT, left);
3758c2ecf20Sopenharmony_ci	input_report_key(idev, BTN_RIGHT, right);
3768c2ecf20Sopenharmony_ci
3778c2ecf20Sopenharmony_ci	/*
3788c2ecf20Sopenharmony_ci	 * If this packet says that the finger was removed, reset our position
3798c2ecf20Sopenharmony_ci	 * tracking so that we don't erroneously detect a jump on next press.
3808c2ecf20Sopenharmony_ci	 */
3818c2ecf20Sopenharmony_ci	if (!down) {
3828c2ecf20Sopenharmony_ci		hgpk_reset_hack_state(psmouse);
3838c2ecf20Sopenharmony_ci		goto done;
3848c2ecf20Sopenharmony_ci	}
3858c2ecf20Sopenharmony_ci
3868c2ecf20Sopenharmony_ci	/*
3878c2ecf20Sopenharmony_ci	 * Weed out duplicate packets (we get quite a few, and they mess up
3888c2ecf20Sopenharmony_ci	 * our jump detection)
3898c2ecf20Sopenharmony_ci	 */
3908c2ecf20Sopenharmony_ci	if (x == priv->abs_x && y == priv->abs_y) {
3918c2ecf20Sopenharmony_ci		if (++priv->dupe_count > SPEW_WATCH_COUNT) {
3928c2ecf20Sopenharmony_ci			if (tpdebug)
3938c2ecf20Sopenharmony_ci				psmouse_dbg(psmouse, "hard spew detected\n");
3948c2ecf20Sopenharmony_ci			priv->spew_flag = RECALIBRATING;
3958c2ecf20Sopenharmony_ci			psmouse_queue_work(psmouse, &priv->recalib_wq,
3968c2ecf20Sopenharmony_ci					   msecs_to_jiffies(spew_delay));
3978c2ecf20Sopenharmony_ci		}
3988c2ecf20Sopenharmony_ci		goto done;
3998c2ecf20Sopenharmony_ci	}
4008c2ecf20Sopenharmony_ci
4018c2ecf20Sopenharmony_ci	/* not a duplicate, continue with position reporting */
4028c2ecf20Sopenharmony_ci	priv->dupe_count = 0;
4038c2ecf20Sopenharmony_ci
4048c2ecf20Sopenharmony_ci	/* Don't apply hacks in PT mode, it seems reliable */
4058c2ecf20Sopenharmony_ci	if (priv->mode != HGPK_MODE_PENTABLET && priv->abs_x != -1) {
4068c2ecf20Sopenharmony_ci		int x_diff = priv->abs_x - x;
4078c2ecf20Sopenharmony_ci		int y_diff = priv->abs_y - y;
4088c2ecf20Sopenharmony_ci		if (hgpk_discard_decay_hack(psmouse, x_diff, y_diff)) {
4098c2ecf20Sopenharmony_ci			if (tpdebug)
4108c2ecf20Sopenharmony_ci				psmouse_dbg(psmouse, "discarding\n");
4118c2ecf20Sopenharmony_ci			goto done;
4128c2ecf20Sopenharmony_ci		}
4138c2ecf20Sopenharmony_ci		hgpk_spewing_hack(psmouse, left, right, x_diff, y_diff);
4148c2ecf20Sopenharmony_ci	}
4158c2ecf20Sopenharmony_ci
4168c2ecf20Sopenharmony_ci	input_report_abs(idev, ABS_X, x);
4178c2ecf20Sopenharmony_ci	input_report_abs(idev, ABS_Y, y);
4188c2ecf20Sopenharmony_ci	priv->abs_x = x;
4198c2ecf20Sopenharmony_ci	priv->abs_y = y;
4208c2ecf20Sopenharmony_ci
4218c2ecf20Sopenharmony_cidone:
4228c2ecf20Sopenharmony_ci	input_sync(idev);
4238c2ecf20Sopenharmony_ci}
4248c2ecf20Sopenharmony_ci
4258c2ecf20Sopenharmony_cistatic void hgpk_process_simple_packet(struct psmouse *psmouse)
4268c2ecf20Sopenharmony_ci{
4278c2ecf20Sopenharmony_ci	struct input_dev *dev = psmouse->dev;
4288c2ecf20Sopenharmony_ci	unsigned char *packet = psmouse->packet;
4298c2ecf20Sopenharmony_ci	int left = packet[0] & 1;
4308c2ecf20Sopenharmony_ci	int right = (packet[0] >> 1) & 1;
4318c2ecf20Sopenharmony_ci	int x = packet[1] - ((packet[0] << 4) & 0x100);
4328c2ecf20Sopenharmony_ci	int y = ((packet[0] << 3) & 0x100) - packet[2];
4338c2ecf20Sopenharmony_ci
4348c2ecf20Sopenharmony_ci	if (packet[0] & 0xc0)
4358c2ecf20Sopenharmony_ci		psmouse_dbg(psmouse,
4368c2ecf20Sopenharmony_ci			    "overflow -- 0x%02x 0x%02x 0x%02x\n",
4378c2ecf20Sopenharmony_ci			    packet[0], packet[1], packet[2]);
4388c2ecf20Sopenharmony_ci
4398c2ecf20Sopenharmony_ci	if (hgpk_discard_decay_hack(psmouse, x, y)) {
4408c2ecf20Sopenharmony_ci		if (tpdebug)
4418c2ecf20Sopenharmony_ci			psmouse_dbg(psmouse, "discarding\n");
4428c2ecf20Sopenharmony_ci		return;
4438c2ecf20Sopenharmony_ci	}
4448c2ecf20Sopenharmony_ci
4458c2ecf20Sopenharmony_ci	hgpk_spewing_hack(psmouse, left, right, x, y);
4468c2ecf20Sopenharmony_ci
4478c2ecf20Sopenharmony_ci	if (tpdebug)
4488c2ecf20Sopenharmony_ci		psmouse_dbg(psmouse, "l=%d r=%d x=%d y=%d\n",
4498c2ecf20Sopenharmony_ci			    left, right, x, y);
4508c2ecf20Sopenharmony_ci
4518c2ecf20Sopenharmony_ci	input_report_key(dev, BTN_LEFT, left);
4528c2ecf20Sopenharmony_ci	input_report_key(dev, BTN_RIGHT, right);
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_ci	input_report_rel(dev, REL_X, x);
4558c2ecf20Sopenharmony_ci	input_report_rel(dev, REL_Y, y);
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_ci	input_sync(dev);
4588c2ecf20Sopenharmony_ci}
4598c2ecf20Sopenharmony_ci
4608c2ecf20Sopenharmony_cistatic psmouse_ret_t hgpk_process_byte(struct psmouse *psmouse)
4618c2ecf20Sopenharmony_ci{
4628c2ecf20Sopenharmony_ci	struct hgpk_data *priv = psmouse->private;
4638c2ecf20Sopenharmony_ci
4648c2ecf20Sopenharmony_ci	if (!hgpk_is_byte_valid(psmouse, psmouse->packet))
4658c2ecf20Sopenharmony_ci		return PSMOUSE_BAD_DATA;
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ci	if (psmouse->pktcnt >= psmouse->pktsize) {
4688c2ecf20Sopenharmony_ci		if (priv->mode == HGPK_MODE_MOUSE)
4698c2ecf20Sopenharmony_ci			hgpk_process_simple_packet(psmouse);
4708c2ecf20Sopenharmony_ci		else
4718c2ecf20Sopenharmony_ci			hgpk_process_advanced_packet(psmouse);
4728c2ecf20Sopenharmony_ci		return PSMOUSE_FULL_PACKET;
4738c2ecf20Sopenharmony_ci	}
4748c2ecf20Sopenharmony_ci
4758c2ecf20Sopenharmony_ci	if (priv->recalib_window) {
4768c2ecf20Sopenharmony_ci		if (time_before(jiffies, priv->recalib_window)) {
4778c2ecf20Sopenharmony_ci			/*
4788c2ecf20Sopenharmony_ci			 * ugh, got a packet inside our recalibration
4798c2ecf20Sopenharmony_ci			 * window, schedule another recalibration.
4808c2ecf20Sopenharmony_ci			 */
4818c2ecf20Sopenharmony_ci			psmouse_dbg(psmouse,
4828c2ecf20Sopenharmony_ci				    "packet inside calibration window, queueing another recalibration\n");
4838c2ecf20Sopenharmony_ci			psmouse_queue_work(psmouse, &priv->recalib_wq,
4848c2ecf20Sopenharmony_ci					msecs_to_jiffies(post_interrupt_delay));
4858c2ecf20Sopenharmony_ci		}
4868c2ecf20Sopenharmony_ci		priv->recalib_window = 0;
4878c2ecf20Sopenharmony_ci	}
4888c2ecf20Sopenharmony_ci
4898c2ecf20Sopenharmony_ci	return PSMOUSE_GOOD_DATA;
4908c2ecf20Sopenharmony_ci}
4918c2ecf20Sopenharmony_ci
4928c2ecf20Sopenharmony_cistatic int hgpk_select_mode(struct psmouse *psmouse)
4938c2ecf20Sopenharmony_ci{
4948c2ecf20Sopenharmony_ci	struct ps2dev *ps2dev = &psmouse->ps2dev;
4958c2ecf20Sopenharmony_ci	struct hgpk_data *priv = psmouse->private;
4968c2ecf20Sopenharmony_ci	int i;
4978c2ecf20Sopenharmony_ci	int cmd;
4988c2ecf20Sopenharmony_ci
4998c2ecf20Sopenharmony_ci	/*
5008c2ecf20Sopenharmony_ci	 * 4 disables to enable advanced mode
5018c2ecf20Sopenharmony_ci	 * then 3 0xf2 bytes as the preamble for GS/PT selection
5028c2ecf20Sopenharmony_ci	 */
5038c2ecf20Sopenharmony_ci	const int advanced_init[] = {
5048c2ecf20Sopenharmony_ci		PSMOUSE_CMD_DISABLE, PSMOUSE_CMD_DISABLE,
5058c2ecf20Sopenharmony_ci		PSMOUSE_CMD_DISABLE, PSMOUSE_CMD_DISABLE,
5068c2ecf20Sopenharmony_ci		0xf2, 0xf2, 0xf2,
5078c2ecf20Sopenharmony_ci	};
5088c2ecf20Sopenharmony_ci
5098c2ecf20Sopenharmony_ci	switch (priv->mode) {
5108c2ecf20Sopenharmony_ci	case HGPK_MODE_MOUSE:
5118c2ecf20Sopenharmony_ci		psmouse->pktsize = 3;
5128c2ecf20Sopenharmony_ci		break;
5138c2ecf20Sopenharmony_ci
5148c2ecf20Sopenharmony_ci	case HGPK_MODE_GLIDESENSOR:
5158c2ecf20Sopenharmony_ci	case HGPK_MODE_PENTABLET:
5168c2ecf20Sopenharmony_ci		psmouse->pktsize = 6;
5178c2ecf20Sopenharmony_ci
5188c2ecf20Sopenharmony_ci		/* Switch to 'Advanced mode.', four disables in a row. */
5198c2ecf20Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(advanced_init); i++)
5208c2ecf20Sopenharmony_ci			if (ps2_command(ps2dev, NULL, advanced_init[i]))
5218c2ecf20Sopenharmony_ci				return -EIO;
5228c2ecf20Sopenharmony_ci
5238c2ecf20Sopenharmony_ci		/* select between GlideSensor (mouse) or PenTablet */
5248c2ecf20Sopenharmony_ci		cmd = priv->mode == HGPK_MODE_GLIDESENSOR ?
5258c2ecf20Sopenharmony_ci			PSMOUSE_CMD_SETSCALE11 : PSMOUSE_CMD_SETSCALE21;
5268c2ecf20Sopenharmony_ci
5278c2ecf20Sopenharmony_ci		if (ps2_command(ps2dev, NULL, cmd))
5288c2ecf20Sopenharmony_ci			return -EIO;
5298c2ecf20Sopenharmony_ci		break;
5308c2ecf20Sopenharmony_ci
5318c2ecf20Sopenharmony_ci	default:
5328c2ecf20Sopenharmony_ci		return -EINVAL;
5338c2ecf20Sopenharmony_ci	}
5348c2ecf20Sopenharmony_ci
5358c2ecf20Sopenharmony_ci	return 0;
5368c2ecf20Sopenharmony_ci}
5378c2ecf20Sopenharmony_ci
5388c2ecf20Sopenharmony_cistatic void hgpk_setup_input_device(struct input_dev *input,
5398c2ecf20Sopenharmony_ci				    struct input_dev *old_input,
5408c2ecf20Sopenharmony_ci				    enum hgpk_mode mode)
5418c2ecf20Sopenharmony_ci{
5428c2ecf20Sopenharmony_ci	if (old_input) {
5438c2ecf20Sopenharmony_ci		input->name = old_input->name;
5448c2ecf20Sopenharmony_ci		input->phys = old_input->phys;
5458c2ecf20Sopenharmony_ci		input->id = old_input->id;
5468c2ecf20Sopenharmony_ci		input->dev.parent = old_input->dev.parent;
5478c2ecf20Sopenharmony_ci	}
5488c2ecf20Sopenharmony_ci
5498c2ecf20Sopenharmony_ci	memset(input->evbit, 0, sizeof(input->evbit));
5508c2ecf20Sopenharmony_ci	memset(input->relbit, 0, sizeof(input->relbit));
5518c2ecf20Sopenharmony_ci	memset(input->keybit, 0, sizeof(input->keybit));
5528c2ecf20Sopenharmony_ci
5538c2ecf20Sopenharmony_ci	/* All modes report left and right buttons */
5548c2ecf20Sopenharmony_ci	__set_bit(EV_KEY, input->evbit);
5558c2ecf20Sopenharmony_ci	__set_bit(BTN_LEFT, input->keybit);
5568c2ecf20Sopenharmony_ci	__set_bit(BTN_RIGHT, input->keybit);
5578c2ecf20Sopenharmony_ci
5588c2ecf20Sopenharmony_ci	switch (mode) {
5598c2ecf20Sopenharmony_ci	case HGPK_MODE_MOUSE:
5608c2ecf20Sopenharmony_ci		__set_bit(EV_REL, input->evbit);
5618c2ecf20Sopenharmony_ci		__set_bit(REL_X, input->relbit);
5628c2ecf20Sopenharmony_ci		__set_bit(REL_Y, input->relbit);
5638c2ecf20Sopenharmony_ci		break;
5648c2ecf20Sopenharmony_ci
5658c2ecf20Sopenharmony_ci	case HGPK_MODE_GLIDESENSOR:
5668c2ecf20Sopenharmony_ci		__set_bit(BTN_TOUCH, input->keybit);
5678c2ecf20Sopenharmony_ci		__set_bit(BTN_TOOL_FINGER, input->keybit);
5688c2ecf20Sopenharmony_ci
5698c2ecf20Sopenharmony_ci		__set_bit(EV_ABS, input->evbit);
5708c2ecf20Sopenharmony_ci
5718c2ecf20Sopenharmony_ci		/* GlideSensor has pressure sensor, PenTablet does not */
5728c2ecf20Sopenharmony_ci		input_set_abs_params(input, ABS_PRESSURE, 0, 15, 0, 0);
5738c2ecf20Sopenharmony_ci
5748c2ecf20Sopenharmony_ci		/* From device specs */
5758c2ecf20Sopenharmony_ci		input_set_abs_params(input, ABS_X, 0, 399, 0, 0);
5768c2ecf20Sopenharmony_ci		input_set_abs_params(input, ABS_Y, 0, 290, 0, 0);
5778c2ecf20Sopenharmony_ci
5788c2ecf20Sopenharmony_ci		/* Calculated by hand based on usable size (52mm x 38mm) */
5798c2ecf20Sopenharmony_ci		input_abs_set_res(input, ABS_X, 8);
5808c2ecf20Sopenharmony_ci		input_abs_set_res(input, ABS_Y, 8);
5818c2ecf20Sopenharmony_ci		break;
5828c2ecf20Sopenharmony_ci
5838c2ecf20Sopenharmony_ci	case HGPK_MODE_PENTABLET:
5848c2ecf20Sopenharmony_ci		__set_bit(BTN_TOUCH, input->keybit);
5858c2ecf20Sopenharmony_ci		__set_bit(BTN_TOOL_FINGER, input->keybit);
5868c2ecf20Sopenharmony_ci
5878c2ecf20Sopenharmony_ci		__set_bit(EV_ABS, input->evbit);
5888c2ecf20Sopenharmony_ci
5898c2ecf20Sopenharmony_ci		/* From device specs */
5908c2ecf20Sopenharmony_ci		input_set_abs_params(input, ABS_X, 0, 999, 0, 0);
5918c2ecf20Sopenharmony_ci		input_set_abs_params(input, ABS_Y, 5, 239, 0, 0);
5928c2ecf20Sopenharmony_ci
5938c2ecf20Sopenharmony_ci		/* Calculated by hand based on usable size (156mm x 38mm) */
5948c2ecf20Sopenharmony_ci		input_abs_set_res(input, ABS_X, 6);
5958c2ecf20Sopenharmony_ci		input_abs_set_res(input, ABS_Y, 8);
5968c2ecf20Sopenharmony_ci		break;
5978c2ecf20Sopenharmony_ci
5988c2ecf20Sopenharmony_ci	default:
5998c2ecf20Sopenharmony_ci		BUG();
6008c2ecf20Sopenharmony_ci	}
6018c2ecf20Sopenharmony_ci}
6028c2ecf20Sopenharmony_ci
6038c2ecf20Sopenharmony_cistatic int hgpk_reset_device(struct psmouse *psmouse, bool recalibrate)
6048c2ecf20Sopenharmony_ci{
6058c2ecf20Sopenharmony_ci	int err;
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_ci	psmouse_reset(psmouse);
6088c2ecf20Sopenharmony_ci
6098c2ecf20Sopenharmony_ci	if (recalibrate) {
6108c2ecf20Sopenharmony_ci		struct ps2dev *ps2dev = &psmouse->ps2dev;
6118c2ecf20Sopenharmony_ci
6128c2ecf20Sopenharmony_ci		/* send the recalibrate request */
6138c2ecf20Sopenharmony_ci		if (ps2_command(ps2dev, NULL, 0xf5) ||
6148c2ecf20Sopenharmony_ci		    ps2_command(ps2dev, NULL, 0xf5) ||
6158c2ecf20Sopenharmony_ci		    ps2_command(ps2dev, NULL, 0xe6) ||
6168c2ecf20Sopenharmony_ci		    ps2_command(ps2dev, NULL, 0xf5)) {
6178c2ecf20Sopenharmony_ci			return -1;
6188c2ecf20Sopenharmony_ci		}
6198c2ecf20Sopenharmony_ci
6208c2ecf20Sopenharmony_ci		/* according to ALPS, 150mS is required for recalibration */
6218c2ecf20Sopenharmony_ci		msleep(150);
6228c2ecf20Sopenharmony_ci	}
6238c2ecf20Sopenharmony_ci
6248c2ecf20Sopenharmony_ci	err = hgpk_select_mode(psmouse);
6258c2ecf20Sopenharmony_ci	if (err) {
6268c2ecf20Sopenharmony_ci		psmouse_err(psmouse, "failed to select mode\n");
6278c2ecf20Sopenharmony_ci		return err;
6288c2ecf20Sopenharmony_ci	}
6298c2ecf20Sopenharmony_ci
6308c2ecf20Sopenharmony_ci	hgpk_reset_hack_state(psmouse);
6318c2ecf20Sopenharmony_ci
6328c2ecf20Sopenharmony_ci	return 0;
6338c2ecf20Sopenharmony_ci}
6348c2ecf20Sopenharmony_ci
6358c2ecf20Sopenharmony_cistatic int hgpk_force_recalibrate(struct psmouse *psmouse)
6368c2ecf20Sopenharmony_ci{
6378c2ecf20Sopenharmony_ci	struct hgpk_data *priv = psmouse->private;
6388c2ecf20Sopenharmony_ci	int err;
6398c2ecf20Sopenharmony_ci
6408c2ecf20Sopenharmony_ci	/* C-series touchpads added the recalibrate command */
6418c2ecf20Sopenharmony_ci	if (psmouse->model < HGPK_MODEL_C)
6428c2ecf20Sopenharmony_ci		return 0;
6438c2ecf20Sopenharmony_ci
6448c2ecf20Sopenharmony_ci	if (!autorecal) {
6458c2ecf20Sopenharmony_ci		psmouse_dbg(psmouse, "recalibration disabled, ignoring\n");
6468c2ecf20Sopenharmony_ci		return 0;
6478c2ecf20Sopenharmony_ci	}
6488c2ecf20Sopenharmony_ci
6498c2ecf20Sopenharmony_ci	psmouse_dbg(psmouse, "recalibrating touchpad..\n");
6508c2ecf20Sopenharmony_ci
6518c2ecf20Sopenharmony_ci	/* we don't want to race with the irq handler, nor with resyncs */
6528c2ecf20Sopenharmony_ci	psmouse_set_state(psmouse, PSMOUSE_INITIALIZING);
6538c2ecf20Sopenharmony_ci
6548c2ecf20Sopenharmony_ci	/* start by resetting the device */
6558c2ecf20Sopenharmony_ci	err = hgpk_reset_device(psmouse, true);
6568c2ecf20Sopenharmony_ci	if (err)
6578c2ecf20Sopenharmony_ci		return err;
6588c2ecf20Sopenharmony_ci
6598c2ecf20Sopenharmony_ci	/*
6608c2ecf20Sopenharmony_ci	 * XXX: If a finger is down during this delay, recalibration will
6618c2ecf20Sopenharmony_ci	 * detect capacitance incorrectly.  This is a hardware bug, and
6628c2ecf20Sopenharmony_ci	 * we don't have a good way to deal with it.  The 2s window stuff
6638c2ecf20Sopenharmony_ci	 * (below) is our best option for now.
6648c2ecf20Sopenharmony_ci	 */
6658c2ecf20Sopenharmony_ci	if (psmouse_activate(psmouse))
6668c2ecf20Sopenharmony_ci		return -1;
6678c2ecf20Sopenharmony_ci
6688c2ecf20Sopenharmony_ci	if (tpdebug)
6698c2ecf20Sopenharmony_ci		psmouse_dbg(psmouse, "touchpad reactivated\n");
6708c2ecf20Sopenharmony_ci
6718c2ecf20Sopenharmony_ci	/*
6728c2ecf20Sopenharmony_ci	 * If we get packets right away after recalibrating, it's likely
6738c2ecf20Sopenharmony_ci	 * that a finger was on the touchpad.  If so, it's probably
6748c2ecf20Sopenharmony_ci	 * miscalibrated, so we optionally schedule another.
6758c2ecf20Sopenharmony_ci	 */
6768c2ecf20Sopenharmony_ci	if (recal_guard_time)
6778c2ecf20Sopenharmony_ci		priv->recalib_window = jiffies +
6788c2ecf20Sopenharmony_ci			msecs_to_jiffies(recal_guard_time);
6798c2ecf20Sopenharmony_ci
6808c2ecf20Sopenharmony_ci	return 0;
6818c2ecf20Sopenharmony_ci}
6828c2ecf20Sopenharmony_ci
6838c2ecf20Sopenharmony_ci/*
6848c2ecf20Sopenharmony_ci * This puts the touchpad in a power saving mode; according to ALPS, current
6858c2ecf20Sopenharmony_ci * consumption goes down to 50uA after running this.  To turn power back on,
6868c2ecf20Sopenharmony_ci * we drive MS-DAT low.  Measuring with a 1mA resolution ammeter says that
6878c2ecf20Sopenharmony_ci * the current on the SUS_3.3V rail drops from 3mA or 4mA to 0 when we do this.
6888c2ecf20Sopenharmony_ci *
6898c2ecf20Sopenharmony_ci * We have no formal spec that details this operation -- the low-power
6908c2ecf20Sopenharmony_ci * sequence came from a long-lost email trail.
6918c2ecf20Sopenharmony_ci */
6928c2ecf20Sopenharmony_cistatic int hgpk_toggle_powersave(struct psmouse *psmouse, int enable)
6938c2ecf20Sopenharmony_ci{
6948c2ecf20Sopenharmony_ci	struct ps2dev *ps2dev = &psmouse->ps2dev;
6958c2ecf20Sopenharmony_ci	int timeo;
6968c2ecf20Sopenharmony_ci	int err;
6978c2ecf20Sopenharmony_ci
6988c2ecf20Sopenharmony_ci	/* Added on D-series touchpads */
6998c2ecf20Sopenharmony_ci	if (psmouse->model < HGPK_MODEL_D)
7008c2ecf20Sopenharmony_ci		return 0;
7018c2ecf20Sopenharmony_ci
7028c2ecf20Sopenharmony_ci	if (enable) {
7038c2ecf20Sopenharmony_ci		psmouse_set_state(psmouse, PSMOUSE_INITIALIZING);
7048c2ecf20Sopenharmony_ci
7058c2ecf20Sopenharmony_ci		/*
7068c2ecf20Sopenharmony_ci		 * Sending a byte will drive MS-DAT low; this will wake up
7078c2ecf20Sopenharmony_ci		 * the controller.  Once we get an ACK back from it, it
7088c2ecf20Sopenharmony_ci		 * means we can continue with the touchpad re-init.  ALPS
7098c2ecf20Sopenharmony_ci		 * tells us that 1s should be long enough, so set that as
7108c2ecf20Sopenharmony_ci		 * the upper bound. (in practice, it takes about 3 loops.)
7118c2ecf20Sopenharmony_ci		 */
7128c2ecf20Sopenharmony_ci		for (timeo = 20; timeo > 0; timeo--) {
7138c2ecf20Sopenharmony_ci			if (!ps2_sendbyte(ps2dev, PSMOUSE_CMD_DISABLE, 20))
7148c2ecf20Sopenharmony_ci				break;
7158c2ecf20Sopenharmony_ci			msleep(25);
7168c2ecf20Sopenharmony_ci		}
7178c2ecf20Sopenharmony_ci
7188c2ecf20Sopenharmony_ci		err = hgpk_reset_device(psmouse, false);
7198c2ecf20Sopenharmony_ci		if (err) {
7208c2ecf20Sopenharmony_ci			psmouse_err(psmouse, "Failed to reset device!\n");
7218c2ecf20Sopenharmony_ci			return err;
7228c2ecf20Sopenharmony_ci		}
7238c2ecf20Sopenharmony_ci
7248c2ecf20Sopenharmony_ci		/* should be all set, enable the touchpad */
7258c2ecf20Sopenharmony_ci		psmouse_activate(psmouse);
7268c2ecf20Sopenharmony_ci		psmouse_dbg(psmouse, "Touchpad powered up.\n");
7278c2ecf20Sopenharmony_ci	} else {
7288c2ecf20Sopenharmony_ci		psmouse_dbg(psmouse, "Powering off touchpad.\n");
7298c2ecf20Sopenharmony_ci
7308c2ecf20Sopenharmony_ci		if (ps2_command(ps2dev, NULL, 0xec) ||
7318c2ecf20Sopenharmony_ci		    ps2_command(ps2dev, NULL, 0xec) ||
7328c2ecf20Sopenharmony_ci		    ps2_command(ps2dev, NULL, 0xea)) {
7338c2ecf20Sopenharmony_ci			return -1;
7348c2ecf20Sopenharmony_ci		}
7358c2ecf20Sopenharmony_ci
7368c2ecf20Sopenharmony_ci		psmouse_set_state(psmouse, PSMOUSE_IGNORE);
7378c2ecf20Sopenharmony_ci
7388c2ecf20Sopenharmony_ci		/* probably won't see an ACK, the touchpad will be off */
7398c2ecf20Sopenharmony_ci		ps2_sendbyte(ps2dev, 0xec, 20);
7408c2ecf20Sopenharmony_ci	}
7418c2ecf20Sopenharmony_ci
7428c2ecf20Sopenharmony_ci	return 0;
7438c2ecf20Sopenharmony_ci}
7448c2ecf20Sopenharmony_ci
7458c2ecf20Sopenharmony_cistatic int hgpk_poll(struct psmouse *psmouse)
7468c2ecf20Sopenharmony_ci{
7478c2ecf20Sopenharmony_ci	/* We can't poll, so always return failure. */
7488c2ecf20Sopenharmony_ci	return -1;
7498c2ecf20Sopenharmony_ci}
7508c2ecf20Sopenharmony_ci
7518c2ecf20Sopenharmony_cistatic int hgpk_reconnect(struct psmouse *psmouse)
7528c2ecf20Sopenharmony_ci{
7538c2ecf20Sopenharmony_ci	struct hgpk_data *priv = psmouse->private;
7548c2ecf20Sopenharmony_ci
7558c2ecf20Sopenharmony_ci	/*
7568c2ecf20Sopenharmony_ci	 * During suspend/resume the ps2 rails remain powered.  We don't want
7578c2ecf20Sopenharmony_ci	 * to do a reset because it's flush data out of buffers; however,
7588c2ecf20Sopenharmony_ci	 * earlier prototypes (B1) had some brokenness that required a reset.
7598c2ecf20Sopenharmony_ci	 */
7608c2ecf20Sopenharmony_ci	if (olpc_board_at_least(olpc_board(0xb2)))
7618c2ecf20Sopenharmony_ci		if (psmouse->ps2dev.serio->dev.power.power_state.event !=
7628c2ecf20Sopenharmony_ci				PM_EVENT_ON)
7638c2ecf20Sopenharmony_ci			return 0;
7648c2ecf20Sopenharmony_ci
7658c2ecf20Sopenharmony_ci	priv->powered = 1;
7668c2ecf20Sopenharmony_ci	return hgpk_reset_device(psmouse, false);
7678c2ecf20Sopenharmony_ci}
7688c2ecf20Sopenharmony_ci
7698c2ecf20Sopenharmony_cistatic ssize_t hgpk_show_powered(struct psmouse *psmouse, void *data, char *buf)
7708c2ecf20Sopenharmony_ci{
7718c2ecf20Sopenharmony_ci	struct hgpk_data *priv = psmouse->private;
7728c2ecf20Sopenharmony_ci
7738c2ecf20Sopenharmony_ci	return sprintf(buf, "%d\n", priv->powered);
7748c2ecf20Sopenharmony_ci}
7758c2ecf20Sopenharmony_ci
7768c2ecf20Sopenharmony_cistatic ssize_t hgpk_set_powered(struct psmouse *psmouse, void *data,
7778c2ecf20Sopenharmony_ci				const char *buf, size_t count)
7788c2ecf20Sopenharmony_ci{
7798c2ecf20Sopenharmony_ci	struct hgpk_data *priv = psmouse->private;
7808c2ecf20Sopenharmony_ci	unsigned int value;
7818c2ecf20Sopenharmony_ci	int err;
7828c2ecf20Sopenharmony_ci
7838c2ecf20Sopenharmony_ci	err = kstrtouint(buf, 10, &value);
7848c2ecf20Sopenharmony_ci	if (err)
7858c2ecf20Sopenharmony_ci		return err;
7868c2ecf20Sopenharmony_ci
7878c2ecf20Sopenharmony_ci	if (value > 1)
7888c2ecf20Sopenharmony_ci		return -EINVAL;
7898c2ecf20Sopenharmony_ci
7908c2ecf20Sopenharmony_ci	if (value != priv->powered) {
7918c2ecf20Sopenharmony_ci		/*
7928c2ecf20Sopenharmony_ci		 * hgpk_toggle_power will deal w/ state so
7938c2ecf20Sopenharmony_ci		 * we're not racing w/ irq
7948c2ecf20Sopenharmony_ci		 */
7958c2ecf20Sopenharmony_ci		err = hgpk_toggle_powersave(psmouse, value);
7968c2ecf20Sopenharmony_ci		if (!err)
7978c2ecf20Sopenharmony_ci			priv->powered = value;
7988c2ecf20Sopenharmony_ci	}
7998c2ecf20Sopenharmony_ci
8008c2ecf20Sopenharmony_ci	return err ? err : count;
8018c2ecf20Sopenharmony_ci}
8028c2ecf20Sopenharmony_ci
8038c2ecf20Sopenharmony_ci__PSMOUSE_DEFINE_ATTR(powered, S_IWUSR | S_IRUGO, NULL,
8048c2ecf20Sopenharmony_ci		      hgpk_show_powered, hgpk_set_powered, false);
8058c2ecf20Sopenharmony_ci
8068c2ecf20Sopenharmony_cistatic ssize_t attr_show_mode(struct psmouse *psmouse, void *data, char *buf)
8078c2ecf20Sopenharmony_ci{
8088c2ecf20Sopenharmony_ci	struct hgpk_data *priv = psmouse->private;
8098c2ecf20Sopenharmony_ci
8108c2ecf20Sopenharmony_ci	return sprintf(buf, "%s\n", hgpk_mode_names[priv->mode]);
8118c2ecf20Sopenharmony_ci}
8128c2ecf20Sopenharmony_ci
8138c2ecf20Sopenharmony_cistatic ssize_t attr_set_mode(struct psmouse *psmouse, void *data,
8148c2ecf20Sopenharmony_ci			     const char *buf, size_t len)
8158c2ecf20Sopenharmony_ci{
8168c2ecf20Sopenharmony_ci	struct hgpk_data *priv = psmouse->private;
8178c2ecf20Sopenharmony_ci	enum hgpk_mode old_mode = priv->mode;
8188c2ecf20Sopenharmony_ci	enum hgpk_mode new_mode = hgpk_mode_from_name(buf, len);
8198c2ecf20Sopenharmony_ci	struct input_dev *old_dev = psmouse->dev;
8208c2ecf20Sopenharmony_ci	struct input_dev *new_dev;
8218c2ecf20Sopenharmony_ci	int err;
8228c2ecf20Sopenharmony_ci
8238c2ecf20Sopenharmony_ci	if (new_mode == HGPK_MODE_INVALID)
8248c2ecf20Sopenharmony_ci		return -EINVAL;
8258c2ecf20Sopenharmony_ci
8268c2ecf20Sopenharmony_ci	if (old_mode == new_mode)
8278c2ecf20Sopenharmony_ci		return len;
8288c2ecf20Sopenharmony_ci
8298c2ecf20Sopenharmony_ci	new_dev = input_allocate_device();
8308c2ecf20Sopenharmony_ci	if (!new_dev)
8318c2ecf20Sopenharmony_ci		return -ENOMEM;
8328c2ecf20Sopenharmony_ci
8338c2ecf20Sopenharmony_ci	psmouse_set_state(psmouse, PSMOUSE_INITIALIZING);
8348c2ecf20Sopenharmony_ci
8358c2ecf20Sopenharmony_ci	/* Switch device into the new mode */
8368c2ecf20Sopenharmony_ci	priv->mode = new_mode;
8378c2ecf20Sopenharmony_ci	err = hgpk_reset_device(psmouse, false);
8388c2ecf20Sopenharmony_ci	if (err)
8398c2ecf20Sopenharmony_ci		goto err_try_restore;
8408c2ecf20Sopenharmony_ci
8418c2ecf20Sopenharmony_ci	hgpk_setup_input_device(new_dev, old_dev, new_mode);
8428c2ecf20Sopenharmony_ci
8438c2ecf20Sopenharmony_ci	psmouse_set_state(psmouse, PSMOUSE_CMD_MODE);
8448c2ecf20Sopenharmony_ci
8458c2ecf20Sopenharmony_ci	err = input_register_device(new_dev);
8468c2ecf20Sopenharmony_ci	if (err)
8478c2ecf20Sopenharmony_ci		goto err_try_restore;
8488c2ecf20Sopenharmony_ci
8498c2ecf20Sopenharmony_ci	psmouse->dev = new_dev;
8508c2ecf20Sopenharmony_ci	input_unregister_device(old_dev);
8518c2ecf20Sopenharmony_ci
8528c2ecf20Sopenharmony_ci	return len;
8538c2ecf20Sopenharmony_ci
8548c2ecf20Sopenharmony_cierr_try_restore:
8558c2ecf20Sopenharmony_ci	input_free_device(new_dev);
8568c2ecf20Sopenharmony_ci	priv->mode = old_mode;
8578c2ecf20Sopenharmony_ci	hgpk_reset_device(psmouse, false);
8588c2ecf20Sopenharmony_ci
8598c2ecf20Sopenharmony_ci	return err;
8608c2ecf20Sopenharmony_ci}
8618c2ecf20Sopenharmony_ci
8628c2ecf20Sopenharmony_ciPSMOUSE_DEFINE_ATTR(hgpk_mode, S_IWUSR | S_IRUGO, NULL,
8638c2ecf20Sopenharmony_ci		    attr_show_mode, attr_set_mode);
8648c2ecf20Sopenharmony_ci
8658c2ecf20Sopenharmony_cistatic ssize_t hgpk_trigger_recal_show(struct psmouse *psmouse,
8668c2ecf20Sopenharmony_ci		void *data, char *buf)
8678c2ecf20Sopenharmony_ci{
8688c2ecf20Sopenharmony_ci	return -EINVAL;
8698c2ecf20Sopenharmony_ci}
8708c2ecf20Sopenharmony_ci
8718c2ecf20Sopenharmony_cistatic ssize_t hgpk_trigger_recal(struct psmouse *psmouse, void *data,
8728c2ecf20Sopenharmony_ci				const char *buf, size_t count)
8738c2ecf20Sopenharmony_ci{
8748c2ecf20Sopenharmony_ci	struct hgpk_data *priv = psmouse->private;
8758c2ecf20Sopenharmony_ci	unsigned int value;
8768c2ecf20Sopenharmony_ci	int err;
8778c2ecf20Sopenharmony_ci
8788c2ecf20Sopenharmony_ci	err = kstrtouint(buf, 10, &value);
8798c2ecf20Sopenharmony_ci	if (err)
8808c2ecf20Sopenharmony_ci		return err;
8818c2ecf20Sopenharmony_ci
8828c2ecf20Sopenharmony_ci	if (value != 1)
8838c2ecf20Sopenharmony_ci		return -EINVAL;
8848c2ecf20Sopenharmony_ci
8858c2ecf20Sopenharmony_ci	/*
8868c2ecf20Sopenharmony_ci	 * We queue work instead of doing recalibration right here
8878c2ecf20Sopenharmony_ci	 * to avoid adding locking to to hgpk_force_recalibrate()
8888c2ecf20Sopenharmony_ci	 * since workqueue provides serialization.
8898c2ecf20Sopenharmony_ci	 */
8908c2ecf20Sopenharmony_ci	psmouse_queue_work(psmouse, &priv->recalib_wq, 0);
8918c2ecf20Sopenharmony_ci	return count;
8928c2ecf20Sopenharmony_ci}
8938c2ecf20Sopenharmony_ci
8948c2ecf20Sopenharmony_ci__PSMOUSE_DEFINE_ATTR(recalibrate, S_IWUSR | S_IRUGO, NULL,
8958c2ecf20Sopenharmony_ci		      hgpk_trigger_recal_show, hgpk_trigger_recal, false);
8968c2ecf20Sopenharmony_ci
8978c2ecf20Sopenharmony_cistatic void hgpk_disconnect(struct psmouse *psmouse)
8988c2ecf20Sopenharmony_ci{
8998c2ecf20Sopenharmony_ci	struct hgpk_data *priv = psmouse->private;
9008c2ecf20Sopenharmony_ci
9018c2ecf20Sopenharmony_ci	device_remove_file(&psmouse->ps2dev.serio->dev,
9028c2ecf20Sopenharmony_ci			   &psmouse_attr_powered.dattr);
9038c2ecf20Sopenharmony_ci	device_remove_file(&psmouse->ps2dev.serio->dev,
9048c2ecf20Sopenharmony_ci			   &psmouse_attr_hgpk_mode.dattr);
9058c2ecf20Sopenharmony_ci
9068c2ecf20Sopenharmony_ci	if (psmouse->model >= HGPK_MODEL_C)
9078c2ecf20Sopenharmony_ci		device_remove_file(&psmouse->ps2dev.serio->dev,
9088c2ecf20Sopenharmony_ci				   &psmouse_attr_recalibrate.dattr);
9098c2ecf20Sopenharmony_ci
9108c2ecf20Sopenharmony_ci	psmouse_reset(psmouse);
9118c2ecf20Sopenharmony_ci	kfree(priv);
9128c2ecf20Sopenharmony_ci}
9138c2ecf20Sopenharmony_ci
9148c2ecf20Sopenharmony_cistatic void hgpk_recalib_work(struct work_struct *work)
9158c2ecf20Sopenharmony_ci{
9168c2ecf20Sopenharmony_ci	struct delayed_work *w = to_delayed_work(work);
9178c2ecf20Sopenharmony_ci	struct hgpk_data *priv = container_of(w, struct hgpk_data, recalib_wq);
9188c2ecf20Sopenharmony_ci	struct psmouse *psmouse = priv->psmouse;
9198c2ecf20Sopenharmony_ci
9208c2ecf20Sopenharmony_ci	if (hgpk_force_recalibrate(psmouse))
9218c2ecf20Sopenharmony_ci		psmouse_err(psmouse, "recalibration failed!\n");
9228c2ecf20Sopenharmony_ci}
9238c2ecf20Sopenharmony_ci
9248c2ecf20Sopenharmony_cistatic int hgpk_register(struct psmouse *psmouse)
9258c2ecf20Sopenharmony_ci{
9268c2ecf20Sopenharmony_ci	struct hgpk_data *priv = psmouse->private;
9278c2ecf20Sopenharmony_ci	int err;
9288c2ecf20Sopenharmony_ci
9298c2ecf20Sopenharmony_ci	/* register handlers */
9308c2ecf20Sopenharmony_ci	psmouse->protocol_handler = hgpk_process_byte;
9318c2ecf20Sopenharmony_ci	psmouse->poll = hgpk_poll;
9328c2ecf20Sopenharmony_ci	psmouse->disconnect = hgpk_disconnect;
9338c2ecf20Sopenharmony_ci	psmouse->reconnect = hgpk_reconnect;
9348c2ecf20Sopenharmony_ci
9358c2ecf20Sopenharmony_ci	/* Disable the idle resync. */
9368c2ecf20Sopenharmony_ci	psmouse->resync_time = 0;
9378c2ecf20Sopenharmony_ci	/* Reset after a lot of bad bytes. */
9388c2ecf20Sopenharmony_ci	psmouse->resetafter = 1024;
9398c2ecf20Sopenharmony_ci
9408c2ecf20Sopenharmony_ci	hgpk_setup_input_device(psmouse->dev, NULL, priv->mode);
9418c2ecf20Sopenharmony_ci
9428c2ecf20Sopenharmony_ci	err = device_create_file(&psmouse->ps2dev.serio->dev,
9438c2ecf20Sopenharmony_ci				 &psmouse_attr_powered.dattr);
9448c2ecf20Sopenharmony_ci	if (err) {
9458c2ecf20Sopenharmony_ci		psmouse_err(psmouse, "Failed creating 'powered' sysfs node\n");
9468c2ecf20Sopenharmony_ci		return err;
9478c2ecf20Sopenharmony_ci	}
9488c2ecf20Sopenharmony_ci
9498c2ecf20Sopenharmony_ci	err = device_create_file(&psmouse->ps2dev.serio->dev,
9508c2ecf20Sopenharmony_ci				 &psmouse_attr_hgpk_mode.dattr);
9518c2ecf20Sopenharmony_ci	if (err) {
9528c2ecf20Sopenharmony_ci		psmouse_err(psmouse,
9538c2ecf20Sopenharmony_ci			    "Failed creating 'hgpk_mode' sysfs node\n");
9548c2ecf20Sopenharmony_ci		goto err_remove_powered;
9558c2ecf20Sopenharmony_ci	}
9568c2ecf20Sopenharmony_ci
9578c2ecf20Sopenharmony_ci	/* C-series touchpads added the recalibrate command */
9588c2ecf20Sopenharmony_ci	if (psmouse->model >= HGPK_MODEL_C) {
9598c2ecf20Sopenharmony_ci		err = device_create_file(&psmouse->ps2dev.serio->dev,
9608c2ecf20Sopenharmony_ci					 &psmouse_attr_recalibrate.dattr);
9618c2ecf20Sopenharmony_ci		if (err) {
9628c2ecf20Sopenharmony_ci			psmouse_err(psmouse,
9638c2ecf20Sopenharmony_ci				    "Failed creating 'recalibrate' sysfs node\n");
9648c2ecf20Sopenharmony_ci			goto err_remove_mode;
9658c2ecf20Sopenharmony_ci		}
9668c2ecf20Sopenharmony_ci	}
9678c2ecf20Sopenharmony_ci
9688c2ecf20Sopenharmony_ci	return 0;
9698c2ecf20Sopenharmony_ci
9708c2ecf20Sopenharmony_cierr_remove_mode:
9718c2ecf20Sopenharmony_ci	device_remove_file(&psmouse->ps2dev.serio->dev,
9728c2ecf20Sopenharmony_ci			   &psmouse_attr_hgpk_mode.dattr);
9738c2ecf20Sopenharmony_cierr_remove_powered:
9748c2ecf20Sopenharmony_ci	device_remove_file(&psmouse->ps2dev.serio->dev,
9758c2ecf20Sopenharmony_ci			   &psmouse_attr_powered.dattr);
9768c2ecf20Sopenharmony_ci	return err;
9778c2ecf20Sopenharmony_ci}
9788c2ecf20Sopenharmony_ci
9798c2ecf20Sopenharmony_ciint hgpk_init(struct psmouse *psmouse)
9808c2ecf20Sopenharmony_ci{
9818c2ecf20Sopenharmony_ci	struct hgpk_data *priv;
9828c2ecf20Sopenharmony_ci	int err;
9838c2ecf20Sopenharmony_ci
9848c2ecf20Sopenharmony_ci	priv = kzalloc(sizeof(struct hgpk_data), GFP_KERNEL);
9858c2ecf20Sopenharmony_ci	if (!priv) {
9868c2ecf20Sopenharmony_ci		err = -ENOMEM;
9878c2ecf20Sopenharmony_ci		goto alloc_fail;
9888c2ecf20Sopenharmony_ci	}
9898c2ecf20Sopenharmony_ci
9908c2ecf20Sopenharmony_ci	psmouse->private = priv;
9918c2ecf20Sopenharmony_ci
9928c2ecf20Sopenharmony_ci	priv->psmouse = psmouse;
9938c2ecf20Sopenharmony_ci	priv->powered = true;
9948c2ecf20Sopenharmony_ci	priv->mode = hgpk_default_mode;
9958c2ecf20Sopenharmony_ci	INIT_DELAYED_WORK(&priv->recalib_wq, hgpk_recalib_work);
9968c2ecf20Sopenharmony_ci
9978c2ecf20Sopenharmony_ci	err = hgpk_reset_device(psmouse, false);
9988c2ecf20Sopenharmony_ci	if (err)
9998c2ecf20Sopenharmony_ci		goto init_fail;
10008c2ecf20Sopenharmony_ci
10018c2ecf20Sopenharmony_ci	err = hgpk_register(psmouse);
10028c2ecf20Sopenharmony_ci	if (err)
10038c2ecf20Sopenharmony_ci		goto init_fail;
10048c2ecf20Sopenharmony_ci
10058c2ecf20Sopenharmony_ci	return 0;
10068c2ecf20Sopenharmony_ci
10078c2ecf20Sopenharmony_ciinit_fail:
10088c2ecf20Sopenharmony_ci	kfree(priv);
10098c2ecf20Sopenharmony_cialloc_fail:
10108c2ecf20Sopenharmony_ci	return err;
10118c2ecf20Sopenharmony_ci}
10128c2ecf20Sopenharmony_ci
10138c2ecf20Sopenharmony_cistatic enum hgpk_model_t hgpk_get_model(struct psmouse *psmouse)
10148c2ecf20Sopenharmony_ci{
10158c2ecf20Sopenharmony_ci	struct ps2dev *ps2dev = &psmouse->ps2dev;
10168c2ecf20Sopenharmony_ci	unsigned char param[3];
10178c2ecf20Sopenharmony_ci
10188c2ecf20Sopenharmony_ci	/* E7, E7, E7, E9 gets us a 3 byte identifier */
10198c2ecf20Sopenharmony_ci	if (ps2_command(ps2dev,  NULL, PSMOUSE_CMD_SETSCALE21) ||
10208c2ecf20Sopenharmony_ci	    ps2_command(ps2dev,  NULL, PSMOUSE_CMD_SETSCALE21) ||
10218c2ecf20Sopenharmony_ci	    ps2_command(ps2dev,  NULL, PSMOUSE_CMD_SETSCALE21) ||
10228c2ecf20Sopenharmony_ci	    ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) {
10238c2ecf20Sopenharmony_ci		return -EIO;
10248c2ecf20Sopenharmony_ci	}
10258c2ecf20Sopenharmony_ci
10268c2ecf20Sopenharmony_ci	psmouse_dbg(psmouse, "ID: %*ph\n", 3, param);
10278c2ecf20Sopenharmony_ci
10288c2ecf20Sopenharmony_ci	/* HGPK signature: 0x67, 0x00, 0x<model> */
10298c2ecf20Sopenharmony_ci	if (param[0] != 0x67 || param[1] != 0x00)
10308c2ecf20Sopenharmony_ci		return -ENODEV;
10318c2ecf20Sopenharmony_ci
10328c2ecf20Sopenharmony_ci	psmouse_info(psmouse, "OLPC touchpad revision 0x%x\n", param[2]);
10338c2ecf20Sopenharmony_ci
10348c2ecf20Sopenharmony_ci	return param[2];
10358c2ecf20Sopenharmony_ci}
10368c2ecf20Sopenharmony_ci
10378c2ecf20Sopenharmony_ciint hgpk_detect(struct psmouse *psmouse, bool set_properties)
10388c2ecf20Sopenharmony_ci{
10398c2ecf20Sopenharmony_ci	int version;
10408c2ecf20Sopenharmony_ci
10418c2ecf20Sopenharmony_ci	version = hgpk_get_model(psmouse);
10428c2ecf20Sopenharmony_ci	if (version < 0)
10438c2ecf20Sopenharmony_ci		return version;
10448c2ecf20Sopenharmony_ci
10458c2ecf20Sopenharmony_ci	if (set_properties) {
10468c2ecf20Sopenharmony_ci		psmouse->vendor = "ALPS";
10478c2ecf20Sopenharmony_ci		psmouse->name = "HGPK";
10488c2ecf20Sopenharmony_ci		psmouse->model = version;
10498c2ecf20Sopenharmony_ci	}
10508c2ecf20Sopenharmony_ci
10518c2ecf20Sopenharmony_ci	return 0;
10528c2ecf20Sopenharmony_ci}
10538c2ecf20Sopenharmony_ci
10548c2ecf20Sopenharmony_civoid hgpk_module_init(void)
10558c2ecf20Sopenharmony_ci{
10568c2ecf20Sopenharmony_ci	hgpk_default_mode = hgpk_mode_from_name(hgpk_mode_name,
10578c2ecf20Sopenharmony_ci						strlen(hgpk_mode_name));
10588c2ecf20Sopenharmony_ci	if (hgpk_default_mode == HGPK_MODE_INVALID) {
10598c2ecf20Sopenharmony_ci		hgpk_default_mode = HGPK_MODE_MOUSE;
10608c2ecf20Sopenharmony_ci		strlcpy(hgpk_mode_name, hgpk_mode_names[HGPK_MODE_MOUSE],
10618c2ecf20Sopenharmony_ci			sizeof(hgpk_mode_name));
10628c2ecf20Sopenharmony_ci	}
10638c2ecf20Sopenharmony_ci}
1064