I had the same symptoms with an ALPS touchpad in a Dell Latitude E6510.
I found a patch in the following list:
https://bugzilla.redhat.com/show_bug.cgi?id=590880,
which I attach for your convenience. The patch applies cleanly to the
current stable
kernel. It enabled vertical scrolling for me. Also, by a trivial tweak
of the patch
(look at the code, it really is trivial) I was able to disable
click-on-tap, which was
way to sensitive on my machine.
I'm not quite sure how Dell-specific the patch is, but it may be worth a
try.
diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c
index a9f461e..27ec20e 100644
--- a/drivers/input/mouse/alps.c
+++ b/drivers/input/mouse/alps.c
@@ -29,15 +29,17 @@
#define dbg(format, arg...) do {} while (0)
#endif
-#define ALPS_DUALPOINT 0x01
-#define ALPS_WHEEL 0x02
-#define ALPS_FW_BK_1 0x04
-#define ALPS_4BTN 0x08
-#define ALPS_OLDPROTO 0x10
-#define ALPS_PASS 0x20
-#define ALPS_FW_BK_2 0x40
-#define ALPS_PS2_INTERLEAVED 0x80 /* 3-byte PS/2 packet interleaved with
+#define ALPS_DUALPOINT 0x001
+#define ALPS_WHEEL 0x002
+#define ALPS_FW_BK_1 0x004
+#define ALPS_4BTN 0x008
+#define ALPS_OLDPROTO 0x010
+#define ALPS_PASS 0x020
+#define ALPS_FW_BK_2 0x040
+#define ALPS_PS2_INTERLEAVED 0x080 /* 3-byte PS/2 packet interleaved with
6-byte ALPS packet */
+#define ALPS_EC_PROTO 0x100 /* EC memory access protocol */
+#define ALPS_IMPS 0x200 /* IMPS emulation */
static const struct alps_model_info alps_model_data[] = {
{ { 0x32, 0x02, 0x14 }, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* Toshiba Salellite Pro M10 */
@@ -64,6 +66,7 @@ static const struct alps_model_info alps_model_data[] = {
{ { 0x73, 0x02, 0x50 }, 0xcf, 0xcf, ALPS_FW_BK_1 }, /* Dell Vostro 1400 */
{ { 0x52, 0x01, 0x14 }, 0xff, 0xff,
ALPS_PASS | ALPS_DUALPOINT | ALPS_PS2_INTERLEAVED }, /* Toshiba Tecra A11-11L */
+ { { 0x73, 0x02, 0x64 }, 0x00, 0x00, ALPS_EC_PROTO | ALPS_IMPS }, /* Dell E2 series */
};
/*
@@ -516,6 +519,89 @@ static int alps_absolute_mode(struct psmouse *psmouse)
return ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETPOLL);
}
+static int alps_ec_mode(struct psmouse *psmouse, bool enable)
+{
+ struct ps2dev *ps2dev = &psmouse->ps2dev;
+ unsigned char param[4];
+
+ if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSTREAM))
+ return -1;
+ if (enable) {
+ /* EC EC EC E9 */
+ if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_RESET_WRAP) ||
+ ps2_command(ps2dev, NULL, PSMOUSE_CMD_RESET_WRAP) ||
+ ps2_command(ps2dev, NULL, PSMOUSE_CMD_RESET_WRAP))
+ return -1;
+ param[0] = param[1] = param[2] = 0xff;
+ if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO))
+ return -1;
+
+ dbg("EC report: %2.2x %2.2x %2.2x", param[0], param[1], param[2]);
+
+ if (param[0] != 0x88 || param[1] != 0x07 || (param[2] != 0x9b && param[2] != 0x9d))
+ return -1;
+ }
+
+ return 0;
+}
+
+static int alps_ec_nibble(struct psmouse *psmouse, uint8_t nibble)
+{
+ struct ps2dev *ps2dev = &psmouse->ps2dev;
+ unsigned char param;
+ static const int cmds[] = {
+ PSMOUSE_CMD_SETPOLL,
+ PSMOUSE_CMD_RESET_DIS,
+ PSMOUSE_CMD_SETSCALE21,
+ PSMOUSE_CMD_SETRATE,
+ PSMOUSE_CMD_SETRATE,
+ PSMOUSE_CMD_SETRATE,
+ PSMOUSE_CMD_SETRATE,
+ PSMOUSE_CMD_SETRATE,
+ PSMOUSE_CMD_SETRATE,
+ PSMOUSE_CMD_SETRATE,
+ PSMOUSE_CMD_GETINFO,
+ PSMOUSE_CMD_SETRES,
+ PSMOUSE_CMD_SETRES,
+ PSMOUSE_CMD_SETRES,
+ PSMOUSE_CMD_SETRES,
+ PSMOUSE_CMD_SETSCALE11 };
+ static const unsigned char params[] = {
+ 0xff, 0xff, 0xff, 10, 20, 40, 60, 80, 100, 200, 0xff, 0, 1, 2, 3, 0xff };
+
+ nibble &= 0xf;
+ param = params[nibble];
+ if (ps2_command(ps2dev, ¶m, cmds[nibble]))
+ return -1;
+
+ return 0;
+}
+
+static int alps_ec_write(struct psmouse *psmouse, uint16_t addr, uint8_t value)
+{
+ struct ps2dev *ps2dev = &psmouse->ps2dev;
+
+ /* Select new address: EC addr3 addr2 addr1 addr0 */
+ if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_RESET_WRAP) ||
+ alps_ec_nibble(psmouse, addr >> 12) ||
+ alps_ec_nibble(psmouse, addr >> 8) ||
+ alps_ec_nibble(psmouse, addr >> 4) ||
+ alps_ec_nibble(psmouse, addr))
+ return -1;
+
+ /*
+ * PSMOUSE_CMD_GETINFO can be used to read from the current address,
+ * returning { addr_high, addr_low, value } Useful when working with bit fields.
+ */
+
+ /* Write byte: value1 value0 */
+ if (alps_ec_nibble(psmouse, value >> 4) ||
+ alps_ec_nibble(psmouse, value))
+ return -1;
+
+ return 0;
+}
+
static int alps_get_status(struct psmouse *psmouse, char *param)
{
struct ps2dev *ps2dev = &psmouse->ps2dev;
@@ -602,30 +688,65 @@ static int alps_hw_init(struct psmouse *psmouse, int *version)
if (!priv->i)
return -1;
- if ((priv->i->flags & ALPS_PASS) &&
- alps_passthrough_mode(psmouse, true)) {
+ if (!priv->dev2 && !(priv->i->flags & ALPS_IMPS)) {
+ /* Because we never initialized dev2 */
+ printk(KERN_ERR "alps.c: Failed to reconnect to IMPS emulation mode\n");
return -1;
}
- if (alps_tap_mode(psmouse, true)) {
- printk(KERN_WARNING "alps.c: Failed to enable hardware tapping\n");
- return -1;
- }
+ if (priv->i->flags & ALPS_EC_PROTO) {
+ if (alps_ec_mode(psmouse, true)) {
+ printk(KERN_ERR "alps.c: Failed to enable EC memory access mode\n");
+ return -1;
+ }
+ /* Reset touchpad memory (disabling EC mode in the process) */
+ if (alps_ec_write(psmouse, 0x0003, 0x01))
+ return -1;
+ if (alps_ec_mode(psmouse, true)) {
+ printk(KERN_ERR "alps.c: Failed to re-enable EC memory access mode\n");
+ return -1;
+ }
+ if (priv->i->flags & ALPS_IMPS) {
+ /*
+ * Enable IntelliMouse protocol.
+ *
+ * Other known bits at address 0005 are:
+ * 01 - Enable IntelliMouse protocol
+ * 02 - Disable hardware tapping entirely
+ * 04 - Disable corner tap for right-click
+ * 80 - Upper-left corner tap (default upper-right)
+ */
+ if (alps_ec_write(psmouse, 0x0005, 0x01))
+ return -1;
+ }
+ if (alps_ec_mode(psmouse, false))
+ return -1;
+ } else {
+ if ((priv->i->flags & ALPS_PASS) &&
+ alps_passthrough_mode(psmouse, true)) {
+ return -1;
+ }
- if (alps_absolute_mode(psmouse)) {
- printk(KERN_ERR "alps.c: Failed to enable absolute mode\n");
- return -1;
- }
+ if (alps_tap_mode(psmouse, true)) {
+ printk(KERN_WARNING "alps.c: Failed to enable hardware tapping\n");
+ return -1;
+ }
- if ((priv->i->flags & ALPS_PASS) &&
- alps_passthrough_mode(psmouse, false)) {
- return -1;
- }
+ if (alps_absolute_mode(psmouse)) {
+ printk(KERN_ERR "alps.c: Failed to enable absolute mode\n");
+ return -1;
+ }
- /* ALPS needs stream mode, otherwise it won't report any data */
- if (ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSTREAM)) {
- printk(KERN_ERR "alps.c: Failed to enable stream mode\n");
- return -1;
+ if ((priv->i->flags & ALPS_PASS) &&
+ alps_passthrough_mode(psmouse, false)) {
+ return -1;
+ }
+
+ /* ALPS needs stream mode, otherwise it won't report any data */
+ if (ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSTREAM)) {
+ printk(KERN_ERR "alps.c: Failed to enable stream mode\n");
+ return -1;
+ }
}
return 0;
@@ -648,7 +769,8 @@ static void alps_disconnect(struct psmouse *psmouse)
psmouse_reset(psmouse);
del_timer_sync(&priv->timer);
- input_unregister_device(priv->dev2);
+ if (priv->dev2)
+ input_unregister_device(priv->dev2);
kfree(priv);
}
@@ -668,8 +790,23 @@ int alps_init(struct psmouse *psmouse)
psmouse->private = priv;
+ priv->i = NULL;
if (alps_hw_init(psmouse, &version))
goto init_fail;
+ if (priv->i->flags & ALPS_IMPS) {
+ input_free_device(priv->dev2);
+ priv->dev2 = NULL;
+
+ __set_bit(BTN_MIDDLE, dev1->keybit);
+ __set_bit(REL_WHEEL, dev1->relbit);
+
+ psmouse->disconnect = alps_disconnect;
+ psmouse->reconnect = alps_reconnect;
+ psmouse->type = PSMOUSE_IMPS;
+ psmouse->pktsize = 4;
+
+ return 0;
+ }
dev1->evbit[BIT_WORD(EV_KEY)] |= BIT_MASK(EV_KEY);
dev1->keybit[BIT_WORD(BTN_TOUCH)] |= BIT_MASK(BTN_TOUCH);
diff --git a/drivers/input/mouse/alps.h b/drivers/input/mouse/alps.h
index 904ed8b..ba4a7f5 100644
--- a/drivers/input/mouse/alps.h
+++ b/drivers/input/mouse/alps.h
@@ -15,7 +15,7 @@
struct alps_model_info {
unsigned char signature[3];
unsigned char byte0, mask0;
- unsigned char flags;
+ unsigned int flags;
};
struct alps_data {
diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c
index 9451e28..1fb42d4 100644
--- a/drivers/input/mouse/psmouse-base.c
+++ b/drivers/input/mouse/psmouse-base.c
@@ -632,8 +632,9 @@ static int psmouse_extensions(struct psmouse *psmouse,
if (max_proto > PSMOUSE_IMEX) {
ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS);
if (alps_detect(psmouse, set_properties) == 0) {
+ psmouse->type = PSMOUSE_NONE;
if (!set_properties || alps_init(psmouse) == 0)
- return PSMOUSE_ALPS;
+ return (psmouse->type != PSMOUSE_NONE) ? psmouse->type : PSMOUSE_ALPS;
/*
* Init failed, try basic relative protocols
*/
diff --git a/drivers/input/mouse/psmouse.h b/drivers/input/mouse/psmouse.h
index e053bdd..207ca61 100644
--- a/drivers/input/mouse/psmouse.h
+++ b/drivers/input/mouse/psmouse.h
@@ -6,6 +6,8 @@
#define PSMOUSE_CMD_SETRES 0x10e8
#define PSMOUSE_CMD_GETINFO 0x03e9
#define PSMOUSE_CMD_SETSTREAM 0x00ea
+#define PSMOUSE_CMD_RESET_WRAP 0x00ec
+#define PSMOUSE_CMD_SETWRAP 0x00ee
#define PSMOUSE_CMD_SETPOLL 0x00f0
#define PSMOUSE_CMD_POLL 0x00eb /* caller sets number of bytes to receive */
#define PSMOUSE_CMD_GETID 0x02f2