/*
* OLPC HGPK (XO-1) touchpad PS/2 mouse driver
*
* Copyright (c) 2006-2008 One Laptop Per Child
* Authors:
* Zephaniah E. Hull
* Andres Salomon <dilinger@debian.org>
*
* This driver is partly based on the ALPS driver, which is:
*
* Copyright (c) 2003 Neil Brown <neilb@cse.unsw.edu.au>
* Copyright (c) 2003-2005 Peter Osterlund <petero2@telia.com>
* Copyright (c) 2004 Dmitry Torokhov <dtor@mail.ru>
* Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
/*
* The spec from ALPS is available from
* <http://wiki.laptop.org/go/Touch_Pad/Tablet>. It refers to this
* device as HGPK (Hybrid GS, PT, and Keymatrix).
*
* The earliest versions of the device had simultaneous reporting; that
* was removed. After that, the device used the Advanced Mode GS/PT streaming
* stuff. That turned out to be too buggy to support, so we've finally
* switched to Mouse Mode (which utilizes only the center 1/3 of the touchpad).
*/
#define DEBUG
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/serio.h>
#include <linux/libps2.h>
#include <linux/delay.h>
#include <asm/olpc.h>
#include "psmouse.h"
#include "hgpk.h"
#define ILLEGAL_XY 999999
static bool tpdebug;
module_param(tpdebug, bool, 0644);
MODULE_PARM_DESC(tpdebug, "enable debugging, dumping packets to KERN_DEBUG.");
static int recalib_delta = 100;
module_param(recalib_delta, int, 0644);
MODULE_PARM_DESC(recalib_delta,
"packets containing a delta this large will be discarded, and a "
"recalibration may be scheduled.");
static int jumpy_delay = 20;
module_param(jumpy_delay, int, 0644);
MODULE_PARM_DESC(jumpy_delay,
"delay (ms) before recal after jumpiness detected");
static int spew_delay = 1;
module_param(spew_delay, int, 0644);
MODULE_PARM_DESC(spew_delay,
"delay (ms) before recal after packet spew detected");
static int recal_guard_time;
module_param(recal_guard_time, int, 0644);
MODULE_PARM_DESC(recal_guard_time,
"interval (ms) during which recal will be restarted if packet received");
static int post_interrupt_delay = 40;
module_param(post_interrupt_delay, int, 0644);
MODULE_PARM_DESC(post_interrupt_delay,
"delay (ms) before recal after recal interrupt detected");
static bool autorecal = true;
module_param(autorecal, bool, 0644);
MODULE_PARM_DESC(autorecal, "enable recalibration in the driver");
static char hgpk_mode_name[16];
module_param_string(hgpk_mode, hgpk_mode_name, sizeof(hgpk_mode_name), 0644);
MODULE_PARM_DESC(hgpk_mode,
"default hgpk mode: mouse, glidesensor or pentablet");
static int hgpk_default_mode = HGPK_MODE_MOUSE;
static const char * const hgpk_mode_names[] = {
[HGPK_MODE_MOUSE] = "Mouse",
[HGPK_MODE_GLIDESENSOR] = "GlideSensor",
[HGPK_MODE_PENTABLET] = "PenTablet",
};
static int hgpk_mode_from_name(const char *buf, int len)
{
int i;
for (i = 0; i < ARRAY_SIZE(hgpk_mode_names); i++) {
const char *name = hgpk_mode_names[i];
if (strlen(name) == len && !strncasecmp(name, buf, len))
return i;
}
return HGPK_MODE_INVALID;
}
/*
* see if new value is within 20% of half of old value
*/
static int approx_half(int curr, int prev)
{
int belowhalf, abovehalf;
if (curr < 5 || prev < 5)
return 0;
belowhalf = (prev * 8) / 20;
abovehalf = (prev * 12) / 20;
return belowhalf < curr && curr <= abovehalf;
}
/*
* Throw out oddly large delta packets, and any that immediately follow whose
* values are each approximately half of the previous. It seems that the ALPS
* firmware emits errant packets, and they get averaged out slowly.
*/
static int hgpk_discard_decay_hack(struct psmouse *psmouse, int x, int y)
{
struct hgpk_data *priv = psmouse->private;
int avx, avy;
bool do_recal = false;
avx = abs(x);
avy = abs(y);
/* discard if too big, or half that but > 4 times the prev delta */
if (avx > recalib_delta ||
(avx > recalib_delta / 2 && ((avx / 4) > priv->xlast))) {
psmouse_warn(psmouse, "detected %dpx jump in x\n", x);
priv->xbigj = avx;
} else if (approx_half(avx, priv->xbigj)) {
psmouse_warn(