Input: ALPS - add support for protocol versions 3 and 4
authorSeth Forshee <seth.forshee@canonical.com>
Tue, 8 Nov 2011 03:53:36 +0000 (19:53 -0800)
committerDmitry Torokhov <dmitry.torokhov@gmail.com>
Tue, 8 Nov 2011 06:22:11 +0000 (22:22 -0800)
This patch adds support for two ALPS touchpad protocols not
supported currently by the driver, which I am arbitrarily naming
version 3 and version 4. Support is single-touch only at this time,
although both protocols are capable of limited multitouch support.

Thanks to Andrew Skalski, who did the initial reverse-engineering
of the v3 protocol.

Signed-off-by: Seth Forshee <seth.forshee@canonical.com>
Acked-by: Chase Douglas <chase.douglas@canonical.com>
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
drivers/input/mouse/alps.c
drivers/input/mouse/alps.h
drivers/input/mouse/psmouse.h

index 44a0a71..a0248fd 100644 (file)
 #include "psmouse.h"
 #include "alps.h"
 
+/*
+ * Definitions for ALPS version 3 and 4 command mode protocol
+ */
+#define ALPS_CMD_NIBBLE_10     0x01f2
+
+static const struct alps_nibble_commands alps_v3_nibble_commands[] = {
+       { PSMOUSE_CMD_SETPOLL,          0x00 }, /* 0 */
+       { PSMOUSE_CMD_RESET_DIS,        0x00 }, /* 1 */
+       { PSMOUSE_CMD_SETSCALE21,       0x00 }, /* 2 */
+       { PSMOUSE_CMD_SETRATE,          0x0a }, /* 3 */
+       { PSMOUSE_CMD_SETRATE,          0x14 }, /* 4 */
+       { PSMOUSE_CMD_SETRATE,          0x28 }, /* 5 */
+       { PSMOUSE_CMD_SETRATE,          0x3c }, /* 6 */
+       { PSMOUSE_CMD_SETRATE,          0x50 }, /* 7 */
+       { PSMOUSE_CMD_SETRATE,          0x64 }, /* 8 */
+       { PSMOUSE_CMD_SETRATE,          0xc8 }, /* 9 */
+       { ALPS_CMD_NIBBLE_10,           0x00 }, /* a */
+       { PSMOUSE_CMD_SETRES,           0x00 }, /* b */
+       { PSMOUSE_CMD_SETRES,           0x01 }, /* c */
+       { PSMOUSE_CMD_SETRES,           0x02 }, /* d */
+       { PSMOUSE_CMD_SETRES,           0x03 }, /* e */
+       { PSMOUSE_CMD_SETSCALE11,       0x00 }, /* f */
+};
+
+static const struct alps_nibble_commands alps_v4_nibble_commands[] = {
+       { PSMOUSE_CMD_ENABLE,           0x00 }, /* 0 */
+       { PSMOUSE_CMD_RESET_DIS,        0x00 }, /* 1 */
+       { PSMOUSE_CMD_SETSCALE21,       0x00 }, /* 2 */
+       { PSMOUSE_CMD_SETRATE,          0x0a }, /* 3 */
+       { PSMOUSE_CMD_SETRATE,          0x14 }, /* 4 */
+       { PSMOUSE_CMD_SETRATE,          0x28 }, /* 5 */
+       { PSMOUSE_CMD_SETRATE,          0x3c }, /* 6 */
+       { PSMOUSE_CMD_SETRATE,          0x50 }, /* 7 */
+       { PSMOUSE_CMD_SETRATE,          0x64 }, /* 8 */
+       { PSMOUSE_CMD_SETRATE,          0xc8 }, /* 9 */
+       { ALPS_CMD_NIBBLE_10,           0x00 }, /* a */
+       { PSMOUSE_CMD_SETRES,           0x00 }, /* b */
+       { PSMOUSE_CMD_SETRES,           0x01 }, /* c */
+       { PSMOUSE_CMD_SETRES,           0x02 }, /* d */
+       { PSMOUSE_CMD_SETRES,           0x03 }, /* e */
+       { PSMOUSE_CMD_SETSCALE11,       0x00 }, /* f */
+};
+
+
 #define ALPS_DUALPOINT         0x02    /* touchpad has trackstick */
 #define ALPS_PASS              0x04    /* device has a pass-through port */
 
                                           6-byte ALPS packet */
 
 static const struct alps_model_info alps_model_data[] = {
-       { { 0x32, 0x02, 0x14 }, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* Toshiba Salellite Pro M10 */
-       { { 0x33, 0x02, 0x0a }, ALPS_PROTO_V1, 0x88, 0xf8, 0 },                          /* UMAX-530T */
-       { { 0x53, 0x02, 0x0a }, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
-       { { 0x53, 0x02, 0x14 }, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
-       { { 0x60, 0x03, 0xc8 }, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },                          /* HP ze1115 */
-       { { 0x63, 0x02, 0x0a }, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
-       { { 0x63, 0x02, 0x14 }, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
-       { { 0x63, 0x02, 0x28 }, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_FW_BK_2 },               /* Fujitsu Siemens S6010 */
-       { { 0x63, 0x02, 0x3c }, ALPS_PROTO_V2, 0x8f, 0x8f, ALPS_WHEEL },                 /* Toshiba Satellite S2400-103 */
-       { { 0x63, 0x02, 0x50 }, ALPS_PROTO_V2, 0xef, 0xef, ALPS_FW_BK_1 },               /* NEC Versa L320 */
-       { { 0x63, 0x02, 0x64 }, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
-       { { 0x63, 0x03, 0xc8 }, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* Dell Latitude D800 */
-       { { 0x73, 0x00, 0x0a }, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_DUALPOINT },             /* ThinkPad R61 8918-5QG */
-       { { 0x73, 0x02, 0x0a }, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
-       { { 0x73, 0x02, 0x14 }, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_FW_BK_2 },               /* Ahtec Laptop */
-       { { 0x20, 0x02, 0x0e }, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* XXX */
-       { { 0x22, 0x02, 0x0a }, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT },
-       { { 0x22, 0x02, 0x14 }, ALPS_PROTO_V2, 0xff, 0xff, ALPS_PASS | ALPS_DUALPOINT }, /* Dell Latitude D600 */
+       { { 0x32, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT },  /* Toshiba Salellite Pro M10 */
+       { { 0x33, 0x02, 0x0a }, 0x00, ALPS_PROTO_V1, 0x88, 0xf8, 0 },                           /* UMAX-530T */
+       { { 0x53, 0x02, 0x0a }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
+       { { 0x53, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
+       { { 0x60, 0x03, 0xc8 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },                           /* HP ze1115 */
+       { { 0x63, 0x02, 0x0a }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
+       { { 0x63, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
+       { { 0x63, 0x02, 0x28 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_FW_BK_2 },                /* Fujitsu Siemens S6010 */
+       { { 0x63, 0x02, 0x3c }, 0x00, ALPS_PROTO_V2, 0x8f, 0x8f, ALPS_WHEEL },                  /* Toshiba Satellite S2400-103 */
+       { { 0x63, 0x02, 0x50 }, 0x00, ALPS_PROTO_V2, 0xef, 0xef, ALPS_FW_BK_1 },                /* NEC Versa L320 */
+       { { 0x63, 0x02, 0x64 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
+       { { 0x63, 0x03, 0xc8 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT },  /* Dell Latitude D800 */
+       { { 0x73, 0x00, 0x0a }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_DUALPOINT },              /* ThinkPad R61 8918-5QG */
+       { { 0x73, 0x02, 0x0a }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
+       { { 0x73, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_FW_BK_2 },                /* Ahtec Laptop */
+       { { 0x20, 0x02, 0x0e }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT },  /* XXX */
+       { { 0x22, 0x02, 0x0a }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT },
+       { { 0x22, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xff, 0xff, ALPS_PASS | ALPS_DUALPOINT },  /* Dell Latitude D600 */
        /* Dell Latitude E5500, E6400, E6500, Precision M4400 */
-       { { 0x62, 0x02, 0x14 }, ALPS_PROTO_V2, 0xcf, 0xcf,
+       { { 0x62, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xcf, 0xcf,
                ALPS_PASS | ALPS_DUALPOINT | ALPS_PS2_INTERLEAVED },
-       { { 0x73, 0x02, 0x50 }, ALPS_PROTO_V2, 0xcf, 0xcf, ALPS_FOUR_BUTTONS },          /* Dell Vostro 1400 */
-       { { 0x52, 0x01, 0x14 }, ALPS_PROTO_V2, 0xff, 0xff,
-               ALPS_PASS | ALPS_DUALPOINT | ALPS_PS2_INTERLEAVED },                     /* Toshiba Tecra A11-11L */
+       { { 0x73, 0x02, 0x50 }, 0x00, ALPS_PROTO_V2, 0xcf, 0xcf, ALPS_FOUR_BUTTONS },           /* Dell Vostro 1400 */
+       { { 0x52, 0x01, 0x14 }, 0x00, ALPS_PROTO_V2, 0xff, 0xff,
+               ALPS_PASS | ALPS_DUALPOINT | ALPS_PS2_INTERLEAVED },                            /* Toshiba Tecra A11-11L */
+       { { 0x73, 0x02, 0x64 }, 0x9b, ALPS_PROTO_V3, 0x8f, 0x8f, ALPS_DUALPOINT },
+       { { 0x73, 0x02, 0x64 }, 0x9d, ALPS_PROTO_V3, 0x8f, 0x8f, ALPS_DUALPOINT },
+       { { 0x73, 0x02, 0x64 }, 0x8a, ALPS_PROTO_V4, 0x8f, 0x8f, 0 },
 };
 
 /*
@@ -101,7 +148,7 @@ static void alps_report_buttons(struct psmouse *psmouse,
        input_sync(dev2);
 }
 
-static void alps_process_packet(struct psmouse *psmouse)
+static void alps_process_packet_v1_v2(struct psmouse *psmouse)
 {
        struct alps_data *priv = psmouse->private;
        const struct alps_model_info *model = priv->i;
@@ -203,6 +250,224 @@ static void alps_process_packet(struct psmouse *psmouse)
        input_sync(dev);
 }
 
+static void alps_process_trackstick_packet_v3(struct psmouse *psmouse)
+{
+       struct alps_data *priv = psmouse->private;
+       unsigned char *packet = psmouse->packet;
+       struct input_dev *dev = priv->dev2;
+       int x, y, z, left, right, middle;
+
+       /* Sanity check packet */
+       if (!(packet[0] & 0x40)) {
+               psmouse_dbg(psmouse, "Bad trackstick packet, discarding\n");
+               return;
+       }
+
+       /*
+        * There's a special packet that seems to indicate the end
+        * of a stream of trackstick data. Filter these out.
+        */
+       if (packet[1] == 0x7f && packet[2] == 0x7f && packet[4] == 0x7f)
+               return;
+
+       x = (s8)(((packet[0] & 0x20) << 2) | (packet[1] & 0x7f));
+       y = (s8)(((packet[0] & 0x10) << 3) | (packet[2] & 0x7f));
+       z = (packet[4] & 0x7c) >> 2;
+
+       /*
+        * The x and y values tend to be quite large, and when used
+        * alone the trackstick is difficult to use. Scale them down
+        * to compensate.
+        */
+       x /= 8;
+       y /= 8;
+
+       input_report_rel(dev, REL_X, x);
+       input_report_rel(dev, REL_Y, -y);
+
+       /*
+        * Most ALPS models report the trackstick buttons in the touchpad
+        * packets, but a few report them here. No reliable way has been
+        * found to differentiate between the models upfront, so we enable
+        * the quirk in response to seeing a button press in the trackstick
+        * packet.
+        */
+       left = packet[3] & 0x01;
+       right = packet[3] & 0x02;
+       middle = packet[3] & 0x04;
+
+       if (!(priv->quirks & ALPS_QUIRK_TRACKSTICK_BUTTONS) &&
+           (left || right || middle))
+               priv->quirks |= ALPS_QUIRK_TRACKSTICK_BUTTONS;
+
+       if (priv->quirks & ALPS_QUIRK_TRACKSTICK_BUTTONS) {
+               input_report_key(dev, BTN_LEFT, left);
+               input_report_key(dev, BTN_RIGHT, right);
+               input_report_key(dev, BTN_MIDDLE, middle);
+       }
+
+       input_sync(dev);
+       return;
+}
+
+static void alps_process_touchpad_packet_v3(struct psmouse *psmouse)
+{
+       struct alps_data *priv = psmouse->private;
+       unsigned char *packet = psmouse->packet;
+       struct input_dev *dev = psmouse->dev;
+       struct input_dev *dev2 = priv->dev2;
+       int x, y, z;
+       int left, right, middle;
+
+       /*
+        * There's no single feature of touchpad position and bitmap
+        * packets that can be used to distinguish between them. We
+        * rely on the fact that a bitmap packet should always follow
+        * a position packet with bit 6 of packet[4] set.
+        */
+       if (priv->multi_packet) {
+               priv->multi_packet = 0;
+
+               /*
+                * Sometimes a position packet will indicate a multi-packet
+                * sequence, but then what follows is another position
+                * packet. Check for this, and when it happens process the
+                * position packet as usual.
+                */
+               if (packet[0] & 0x40) {
+                       /*
+                        * Bitmap packets are not yet supported, so for now
+                        * just ignore them.
+                        */
+                       return;
+               }
+       }
+
+       if (!priv->multi_packet && (packet[4] & 0x40))
+               priv->multi_packet = 1;
+       else
+               priv->multi_packet = 0;
+
+       left = packet[3] & 0x01;
+       right = packet[3] & 0x02;
+       middle = packet[3] & 0x04;
+
+       x = ((packet[1] & 0x7f) << 4) | ((packet[4] & 0x30) >> 2) |
+           ((packet[0] & 0x30) >> 4);
+       y = ((packet[2] & 0x7f) << 4) | (packet[4] & 0x0f);
+       z = packet[5] & 0x7f;
+
+       /*
+        * Sometimes the hardware sends a single packet with z = 0
+        * in the middle of a stream. Real releases generate packets
+        * with x, y, and z all zero, so these seem to be flukes.
+        * Ignore them.
+        */
+       if (x && y && !z)
+               return;
+
+       if (z >= 64)
+               input_report_key(dev, BTN_TOUCH, 1);
+       else
+               input_report_key(dev, BTN_TOUCH, 0);
+
+       if (z > 0) {
+               input_report_abs(dev, ABS_X, x);
+               input_report_abs(dev, ABS_Y, y);
+       }
+       input_report_abs(dev, ABS_PRESSURE, z);
+
+       input_report_key(dev, BTN_TOOL_FINGER, z > 0);
+       input_report_key(dev, BTN_LEFT, left);
+       input_report_key(dev, BTN_RIGHT, right);
+       input_report_key(dev, BTN_MIDDLE, middle);
+
+       input_sync(dev);
+
+       if (!(priv->quirks & ALPS_QUIRK_TRACKSTICK_BUTTONS)) {
+               left = packet[3] & 0x10;
+               right = packet[3] & 0x20;
+               middle = packet[3] & 0x40;
+
+               input_report_key(dev2, BTN_LEFT, left);
+               input_report_key(dev2, BTN_RIGHT, right);
+               input_report_key(dev2, BTN_MIDDLE, middle);
+               input_sync(dev2);
+       }
+}
+
+static void alps_process_packet_v3(struct psmouse *psmouse)
+{
+       unsigned char *packet = psmouse->packet;
+
+       /*
+        * v3 protocol packets come in three types, two representing
+        * touchpad data and one representing trackstick data.
+        * Trackstick packets seem to be distinguished by always
+        * having 0x3f in the last byte. This value has never been
+        * observed in the last byte of either of the other types
+        * of packets.
+        */
+       if (packet[5] == 0x3f) {
+               alps_process_trackstick_packet_v3(psmouse);
+               return;
+       }
+
+       alps_process_touchpad_packet_v3(psmouse);
+}
+
+static void alps_process_packet_v4(struct psmouse *psmouse)
+{
+       unsigned char *packet = psmouse->packet;
+       struct input_dev *dev = psmouse->dev;
+       int x, y, z;
+       int left, right;
+
+       left = packet[4] & 0x01;
+       right = packet[4] & 0x02;
+
+       x = ((packet[1] & 0x7f) << 4) | ((packet[3] & 0x30) >> 2) |
+           ((packet[0] & 0x30) >> 4);
+       y = ((packet[2] & 0x7f) << 4) | (packet[3] & 0x0f);
+       z = packet[5] & 0x7f;
+
+       if (z >= 64)
+               input_report_key(dev, BTN_TOUCH, 1);
+       else
+               input_report_key(dev, BTN_TOUCH, 0);
+
+       if (z > 0) {
+               input_report_abs(dev, ABS_X, x);
+               input_report_abs(dev, ABS_Y, y);
+       }
+       input_report_abs(dev, ABS_PRESSURE, z);
+
+       input_report_key(dev, BTN_TOOL_FINGER, z > 0);
+       input_report_key(dev, BTN_LEFT, left);
+       input_report_key(dev, BTN_RIGHT, right);
+
+       input_sync(dev);
+}
+
+static void alps_process_packet(struct psmouse *psmouse)
+{
+       struct alps_data *priv = psmouse->private;
+       const struct alps_model_info *model = priv->i;
+
+       switch (model->proto_version) {
+       case ALPS_PROTO_V1:
+       case ALPS_PROTO_V2:
+               alps_process_packet_v1_v2(psmouse);
+               break;
+       case ALPS_PROTO_V3:
+               alps_process_packet_v3(psmouse);
+               break;
+       case ALPS_PROTO_V4:
+               alps_process_packet_v4(psmouse);
+               break;
+       }
+}
+
 static void alps_report_bare_ps2_packet(struct psmouse *psmouse,
                                        unsigned char packet[],
                                        bool report_buttons)
@@ -376,11 +641,127 @@ static psmouse_ret_t alps_process_byte(struct psmouse *psmouse)
        return PSMOUSE_GOOD_DATA;
 }
 
+static int alps_command_mode_send_nibble(struct psmouse *psmouse, int nibble)
+{
+       struct ps2dev *ps2dev = &psmouse->ps2dev;
+       struct alps_data *priv = psmouse->private;
+       int command;
+       unsigned char *param;
+       unsigned char dummy[4];
+
+       BUG_ON(nibble > 0xf);
+
+       command = priv->nibble_commands[nibble].command;
+       param = (command & 0x0f00) ?
+               dummy : (unsigned char *)&priv->nibble_commands[nibble].data;
+
+       if (ps2_command(ps2dev, param, command))
+               return -1;
+
+       return 0;
+}
+
+static int alps_command_mode_set_addr(struct psmouse *psmouse, int addr)
+{
+       struct ps2dev *ps2dev = &psmouse->ps2dev;
+       struct alps_data *priv = psmouse->private;
+       int i, nibble;
+
+       if (ps2_command(ps2dev, NULL, priv->addr_command))
+               return -1;
+
+       for (i = 12; i >= 0; i -= 4) {
+               nibble = (addr >> i) & 0xf;
+               if (alps_command_mode_send_nibble(psmouse, nibble))
+                       return -1;
+       }
+
+       return 0;
+}
+
+static int __alps_command_mode_read_reg(struct psmouse *psmouse, int addr)
+{
+       struct ps2dev *ps2dev = &psmouse->ps2dev;
+       unsigned char param[4];
+
+       if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO))
+               return -1;
+
+       /*
+        * The address being read is returned in the first two bytes
+        * of the result. Check that this address matches the expected
+        * address.
+        */
+       if (addr != ((param[0] << 8) | param[1]))
+               return -1;
+
+       return param[2];
+}
+
+static int alps_command_mode_read_reg(struct psmouse *psmouse, int addr)
+{
+       if (alps_command_mode_set_addr(psmouse, addr))
+               return -1;
+       return __alps_command_mode_read_reg(psmouse, addr);
+}
+
+static int __alps_command_mode_write_reg(struct psmouse *psmouse, u8 value)
+{
+       if (alps_command_mode_send_nibble(psmouse, (value >> 4) & 0xf))
+               return -1;
+       if (alps_command_mode_send_nibble(psmouse, value & 0xf))
+               return -1;
+       return 0;
+}
+
+static int alps_command_mode_write_reg(struct psmouse *psmouse, int addr,
+                                      u8 value)
+{
+       if (alps_command_mode_set_addr(psmouse, addr))
+               return -1;
+       return __alps_command_mode_write_reg(psmouse, value);
+}
+
+static int alps_enter_command_mode(struct psmouse *psmouse,
+                                  unsigned char *resp)
+{
+       unsigned char param[4];
+       struct ps2dev *ps2dev = &psmouse->ps2dev;
+
+       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) ||
+           ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) {
+               psmouse_err(psmouse, "failed to enter command mode\n");
+               return -1;
+       }
+
+       if (param[0] != 0x88 && param[1] != 0x07) {
+               psmouse_dbg(psmouse,
+                           "unknown response while entering command mode: %2.2x %2.2x %2.2x\n",
+                           param[0], param[1], param[2]);
+               return -1;
+       }
+
+       if (resp)
+               *resp = param[2];
+       return 0;
+}
+
+static inline int alps_exit_command_mode(struct psmouse *psmouse)
+{
+       struct ps2dev *ps2dev = &psmouse->ps2dev;
+       if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSTREAM))
+               return -1;
+       return 0;
+}
+
 static const struct alps_model_info *alps_get_model(struct psmouse *psmouse, int *version)
 {
        struct ps2dev *ps2dev = &psmouse->ps2dev;
        static const unsigned char rates[] = { 0, 10, 20, 40, 60, 80, 100, 200 };
        unsigned char param[4];
+       const struct alps_model_info *model = NULL;
        int i;
 
        /*
@@ -428,12 +809,41 @@ static const struct alps_model_info *alps_get_model(struct psmouse *psmouse, int
                *version = (param[0] << 8) | (param[1] << 4) | i;
        }
 
-       for (i = 0; i < ARRAY_SIZE(alps_model_data); i++)
+       for (i = 0; i < ARRAY_SIZE(alps_model_data); i++) {
                if (!memcmp(param, alps_model_data[i].signature,
-                           sizeof(alps_model_data[i].signature)))
-                       return alps_model_data + i;
+                           sizeof(alps_model_data[i].signature))) {
+                       model = alps_model_data + i;
+                       break;
+               }
+       }
 
-       return NULL;
+       if (model && model->proto_version > ALPS_PROTO_V2) {
+               /*
+                * Need to check command mode response to identify
+                * model
+                */
+               model = NULL;
+               if (alps_enter_command_mode(psmouse, param)) {
+                       psmouse_warn(psmouse,
+                                    "touchpad failed to enter command mode\n");
+               } else {
+                       for (i = 0; i < ARRAY_SIZE(alps_model_data); i++) {
+                               if (alps_model_data[i].proto_version > ALPS_PROTO_V2 &&
+                                   alps_model_data[i].command_mode_resp == param[0]) {
+                                       model = alps_model_data + i;
+                                       break;
+                               }
+                       }
+                       alps_exit_command_mode(psmouse);
+
+                       if (!model)
+                               psmouse_dbg(psmouse,
+                                           "Unknown command mode response %2.2x\n",
+                                           param[0]);
+               }
+       }
+
+       return model;
 }
 
 /*
@@ -441,7 +851,7 @@ static const struct alps_model_info *alps_get_model(struct psmouse *psmouse, int
  * subsequent commands. It looks like glidepad is behind stickpointer,
  * I'd thought it would be other way around...
  */
-static int alps_passthrough_mode(struct psmouse *psmouse, bool enable)
+static int alps_passthrough_mode_v2(struct psmouse *psmouse, bool enable)
 {
        struct ps2dev *ps2dev = &psmouse->ps2dev;
        int cmd = enable ? PSMOUSE_CMD_SETSCALE21 : PSMOUSE_CMD_SETSCALE11;
@@ -458,7 +868,7 @@ static int alps_passthrough_mode(struct psmouse *psmouse, bool enable)
        return 0;
 }
 
-static int alps_absolute_mode(struct psmouse *psmouse)
+static int alps_absolute_mode_v1_v2(struct psmouse *psmouse)
 {
        struct ps2dev *ps2dev = &psmouse->ps2dev;
 
@@ -533,13 +943,13 @@ static int alps_poll(struct psmouse *psmouse)
        bool poll_failed;
 
        if (priv->i->flags & ALPS_PASS)
-               alps_passthrough_mode(psmouse, true);
+               alps_passthrough_mode_v2(psmouse, true);
 
        poll_failed = ps2_command(&psmouse->ps2dev, buf,
                                  PSMOUSE_CMD_POLL | (psmouse->pktsize << 8)) < 0;
 
        if (priv->i->flags & ALPS_PASS)
-               alps_passthrough_mode(psmouse, false);
+               alps_passthrough_mode_v2(psmouse, false);
 
        if (poll_failed || (buf[0] & priv->i->mask0) != priv->i->byte0)
                return -1;
@@ -556,13 +966,13 @@ static int alps_poll(struct psmouse *psmouse)
        return 0;
 }
 
-static int alps_hw_init(struct psmouse *psmouse)
+static int alps_hw_init_v1_v2(struct psmouse *psmouse)
 {
        struct alps_data *priv = psmouse->private;
        const struct alps_model_info *model = priv->i;
 
        if ((model->flags & ALPS_PASS) &&
-           alps_passthrough_mode(psmouse, true)) {
+           alps_passthrough_mode_v2(psmouse, true)) {
                return -1;
        }
 
@@ -571,13 +981,13 @@ static int alps_hw_init(struct psmouse *psmouse)
                return -1;
        }
 
-       if (alps_absolute_mode(psmouse)) {
+       if (alps_absolute_mode_v1_v2(psmouse)) {
                psmouse_err(psmouse, "Failed to enable absolute mode\n");
                return -1;
        }
 
        if ((model->flags & ALPS_PASS) &&
-           alps_passthrough_mode(psmouse, false)) {
+           alps_passthrough_mode_v2(psmouse, false)) {
                return -1;
        }
 
@@ -590,6 +1000,297 @@ static int alps_hw_init(struct psmouse *psmouse)
        return 0;
 }
 
+/*
+ * Enable or disable passthrough mode to the trackstick. Must be in
+ * command mode when calling this function.
+ */
+static int alps_passthrough_mode_v3(struct psmouse *psmouse, bool enable)
+{
+       int reg_val;
+
+       reg_val = alps_command_mode_read_reg(psmouse, 0x0008);
+       if (reg_val == -1)
+               return -1;
+
+       if (enable)
+               reg_val |= 0x01;
+       else
+               reg_val &= ~0x01;
+
+       if (__alps_command_mode_write_reg(psmouse, reg_val))
+               return -1;
+
+       return 0;
+}
+
+/* Must be in command mode when calling this function */
+static int alps_absolute_mode_v3(struct psmouse *psmouse)
+{
+       int reg_val;
+
+       reg_val = alps_command_mode_read_reg(psmouse, 0x0004);
+       if (reg_val == -1)
+               return -1;
+
+       reg_val |= 0x06;
+       if (__alps_command_mode_write_reg(psmouse, reg_val))
+               return -1;
+
+       return 0;
+}
+
+static int alps_hw_init_v3(struct psmouse *psmouse)
+{
+       struct alps_data *priv = psmouse->private;
+       struct ps2dev *ps2dev = &psmouse->ps2dev;
+       int reg_val;
+       unsigned char param[4];
+
+       priv->nibble_commands = alps_v3_nibble_commands;
+       priv->addr_command = PSMOUSE_CMD_RESET_WRAP;
+
+       if (alps_enter_command_mode(psmouse, NULL))
+               goto error;
+
+       /* Check for trackstick */
+       reg_val = alps_command_mode_read_reg(psmouse, 0x0008);
+       if (reg_val == -1)
+               goto error;
+       if (reg_val & 0x80) {
+               if (alps_passthrough_mode_v3(psmouse, true))
+                       goto error;
+               if (alps_exit_command_mode(psmouse))
+                       goto error;
+
+               /*
+                * E7 report for the trackstick
+                *
+                * There have been reports of failures to seem to trace back
+                * to the above trackstick check failing. When these occur
+                * this E7 report fails, so when that happens we continue
+                * with the assumption that there isn't a trackstick after
+                * all.
+                */
+               param[0] = 0x64;
+               if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) ||
+                   ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) ||
+                   ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) ||
+                   ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) {
+                       psmouse_warn(psmouse, "trackstick E7 report failed\n");
+               } else {
+                       psmouse_dbg(psmouse,
+                                   "trackstick E7 report: %2.2x %2.2x %2.2x\n",
+                                   param[0], param[1], param[2]);
+
+                       /*
+                        * Not sure what this does, but it is absolutely
+                        * essential. Without it, the touchpad does not
+                        * work at all and the trackstick just emits normal
+                        * PS/2 packets.
+                        */
+                       if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) ||
+                           ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) ||
+                           ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) ||
+                           alps_command_mode_send_nibble(psmouse, 0x9) ||
+                           alps_command_mode_send_nibble(psmouse, 0x4)) {
+                               psmouse_err(psmouse,
+                                           "Error sending magic E6 sequence\n");
+                               goto error_passthrough;
+                       }
+               }
+
+               if (alps_enter_command_mode(psmouse, NULL))
+                       goto error_passthrough;
+               if (alps_passthrough_mode_v3(psmouse, false))
+                       goto error;
+       }
+
+       if (alps_absolute_mode_v3(psmouse)) {
+               psmouse_err(psmouse, "Failed to enter absolute mode\n");
+               goto error;
+       }
+
+       reg_val = alps_command_mode_read_reg(psmouse, 0x0006);
+       if (reg_val == -1)
+               goto error;
+       if (__alps_command_mode_write_reg(psmouse, reg_val | 0x01))
+               goto error;
+
+       reg_val = alps_command_mode_read_reg(psmouse, 0x0007);
+       if (reg_val == -1)
+               goto error;
+       if (__alps_command_mode_write_reg(psmouse, reg_val | 0x01))
+               goto error;
+
+       if (alps_command_mode_read_reg(psmouse, 0x0144) == -1)
+               goto error;
+       if (__alps_command_mode_write_reg(psmouse, 0x04))
+               goto error;
+
+       if (alps_command_mode_read_reg(psmouse, 0x0159) == -1)
+               goto error;
+       if (__alps_command_mode_write_reg(psmouse, 0x03))
+               goto error;
+
+       if (alps_command_mode_read_reg(psmouse, 0x0163) == -1)
+               goto error;
+       if (alps_command_mode_write_reg(psmouse, 0x0163, 0x03))
+               goto error;
+
+       if (alps_command_mode_read_reg(psmouse, 0x0162) == -1)
+               goto error;
+       if (alps_command_mode_write_reg(psmouse, 0x0162, 0x04))
+               goto error;
+
+       /*
+        * This ensures the trackstick packets are in the format
+        * supported by this driver. If bit 1 isn't set the packet
+        * format is different.
+        */
+       if (alps_command_mode_write_reg(psmouse, 0x0008, 0x82))
+               goto error;
+
+       alps_exit_command_mode(psmouse);
+
+       /* Set rate and enable data reporting */
+       param[0] = 0x64;
+       if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE) ||
+           ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE)) {
+               psmouse_err(psmouse, "Failed to enable data reporting\n");
+               return -1;
+       }
+
+       return 0;
+
+error_passthrough:
+       /* Something failed while in passthrough mode, so try to get out */
+       if (!alps_enter_command_mode(psmouse, NULL))
+               alps_passthrough_mode_v3(psmouse, false);
+error:
+       /*
+        * Leaving the touchpad in command mode will essentially render
+        * it unusable until the machine reboots, so exit it here just
+        * to be safe
+        */
+       alps_exit_command_mode(psmouse);
+       return -1;
+}
+
+/* Must be in command mode when calling this function */
+static int alps_absolute_mode_v4(struct psmouse *psmouse)
+{
+       int reg_val;
+
+       reg_val = alps_command_mode_read_reg(psmouse, 0x0004);
+       if (reg_val == -1)
+               return -1;
+
+       reg_val |= 0x02;
+       if (__alps_command_mode_write_reg(psmouse, reg_val))
+               return -1;
+
+       return 0;
+}
+
+static int alps_hw_init_v4(struct psmouse *psmouse)
+{
+       struct alps_data *priv = psmouse->private;
+       struct ps2dev *ps2dev = &psmouse->ps2dev;
+       unsigned char param[4];
+
+       priv->nibble_commands = alps_v4_nibble_commands;
+       priv->addr_command = PSMOUSE_CMD_DISABLE;
+
+       if (alps_enter_command_mode(psmouse, NULL))
+               goto error;
+
+       if (alps_absolute_mode_v4(psmouse)) {
+               psmouse_err(psmouse, "Failed to enter absolute mode\n");
+               goto error;
+       }
+
+       if (alps_command_mode_write_reg(psmouse, 0x0007, 0x8c))
+               goto error;
+
+       if (alps_command_mode_write_reg(psmouse, 0x0149, 0x03))
+               goto error;
+
+       if (alps_command_mode_write_reg(psmouse, 0x0160, 0x03))
+               goto error;
+
+       if (alps_command_mode_write_reg(psmouse, 0x017f, 0x15))
+               goto error;
+
+       if (alps_command_mode_write_reg(psmouse, 0x0151, 0x01))
+               goto error;
+
+       if (alps_command_mode_write_reg(psmouse, 0x0168, 0x03))
+               goto error;
+
+       if (alps_command_mode_write_reg(psmouse, 0x014a, 0x03))
+               goto error;
+
+       if (alps_command_mode_write_reg(psmouse, 0x0161, 0x03))
+               goto error;
+
+       alps_exit_command_mode(psmouse);
+
+       /*
+        * This sequence changes the output from a 9-byte to an
+        * 8-byte format. All the same data seems to be present,
+        * just in a more compact format.
+        */
+       param[0] = 0xc8;
+       param[1] = 0x64;
+       param[2] = 0x50;
+       if (ps2_command(ps2dev, &param[0], PSMOUSE_CMD_SETRATE) ||
+           ps2_command(ps2dev, &param[1], PSMOUSE_CMD_SETRATE) ||
+           ps2_command(ps2dev, &param[2], PSMOUSE_CMD_SETRATE) ||
+           ps2_command(ps2dev, param, PSMOUSE_CMD_GETID))
+               return -1;
+
+       /* Set rate and enable data reporting */
+       param[0] = 0x64;
+       if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE) ||
+           ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE)) {
+               psmouse_err(psmouse, "Failed to enable data reporting\n");
+               return -1;
+       }
+
+       return 0;
+
+error:
+       /*
+        * Leaving the touchpad in command mode will essentially render
+        * it unusable until the machine reboots, so exit it here just
+        * to be safe
+        */
+       alps_exit_command_mode(psmouse);
+       return -1;
+}
+
+static int alps_hw_init(struct psmouse *psmouse)
+{
+       struct alps_data *priv = psmouse->private;
+       const struct alps_model_info *model = priv->i;
+       int ret = -1;
+
+       switch (model->proto_version) {
+       case ALPS_PROTO_V1:
+       case ALPS_PROTO_V2:
+               ret = alps_hw_init_v1_v2(psmouse);
+               break;
+       case ALPS_PROTO_V3:
+               ret = alps_hw_init_v3(psmouse);
+               break;
+       case ALPS_PROTO_V4:
+               ret = alps_hw_init_v4(psmouse);
+               break;
+       }
+
+       return ret;
+}
+
 static int alps_reconnect(struct psmouse *psmouse)
 {
        const struct alps_model_info *model;
@@ -630,6 +1331,8 @@ int alps_init(struct psmouse *psmouse)
 
        psmouse->private = priv;
 
+       psmouse_reset(psmouse);
+
        model = alps_get_model(psmouse, &version);
        if (!model)
                goto init_fail;
@@ -657,8 +1360,20 @@ int alps_init(struct psmouse *psmouse)
                BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_RIGHT);
 
        dev1->evbit[BIT_WORD(EV_ABS)] |= BIT_MASK(EV_ABS);
-       input_set_abs_params(dev1, ABS_X, 0, 1023, 0, 0);
-       input_set_abs_params(dev1, ABS_Y, 0, 767, 0, 0);
+
+       switch (model->proto_version) {
+       case ALPS_PROTO_V1:
+       case ALPS_PROTO_V2:
+               input_set_abs_params(dev1, ABS_X, 0, 1023, 0, 0);
+               input_set_abs_params(dev1, ABS_Y, 0, 767, 0, 0);
+               break;
+       case ALPS_PROTO_V3:
+       case ALPS_PROTO_V4:
+               input_set_abs_params(dev1, ABS_X, 0, 2000, 0, 0);
+               input_set_abs_params(dev1, ABS_Y, 0, 1400, 0, 0);
+               break;
+       }
+
        input_set_abs_params(dev1, ABS_PRESSURE, 0, 127, 0, 0);
 
        if (model->flags & ALPS_WHEEL) {
@@ -701,7 +1416,7 @@ int alps_init(struct psmouse *psmouse)
        psmouse->poll = alps_poll;
        psmouse->disconnect = alps_disconnect;
        psmouse->reconnect = alps_reconnect;
-       psmouse->pktsize = 6;
+       psmouse->pktsize = model->proto_version == ALPS_PROTO_V4 ? 8 : 6;
 
        /* We are having trouble resyncing ALPS touchpads so disable it for now */
        psmouse->resync_time = 0;
index 4ce9bba..62db7f4 100644 (file)
 
 #define ALPS_PROTO_V1  0
 #define ALPS_PROTO_V2  1
+#define ALPS_PROTO_V3  2
+#define ALPS_PROTO_V4  3
 
 struct alps_model_info {
         unsigned char signature[3];
+       unsigned char command_mode_resp; /* v3/v4 only */
        unsigned char proto_version;
         unsigned char byte0, mask0;
         unsigned char flags;
 };
 
+struct alps_nibble_commands {
+       int command;
+       unsigned char data;
+};
+
 struct alps_data {
        struct input_dev *dev2;         /* Relative device */
        char phys[32];                  /* Phys */
        const struct alps_model_info *i;/* Info */
+       const struct alps_nibble_commands *nibble_commands;
+       int addr_command;               /* Command to set register address */
        int prev_fin;                   /* Finger bit from previous packet */
+       int multi_packet;               /* Multi-packet data in progress */
+       u8 quirks;
        struct timer_list timer;
 };
 
+#define ALPS_QUIRK_TRACKSTICK_BUTTONS  1 /* trakcstick buttons in trackstick packet */
+
 #ifdef CONFIG_MOUSE_PS2_ALPS
 int alps_detect(struct psmouse *psmouse, bool set_properties);
 int alps_init(struct psmouse *psmouse);
index 9b84b0c..11a9c6c 100644 (file)
@@ -8,6 +8,7 @@
 #define PSMOUSE_CMD_SETSTREAM  0x00ea
 #define PSMOUSE_CMD_SETPOLL    0x00f0
 #define PSMOUSE_CMD_POLL       0x00eb  /* caller sets number of bytes to receive */
+#define PSMOUSE_CMD_RESET_WRAP 0x00ec
 #define PSMOUSE_CMD_GETID      0x02f2
 #define PSMOUSE_CMD_SETRATE    0x10f3
 #define PSMOUSE_CMD_ENABLE     0x00f4