Input: ALPS - add interleaved protocol support (Dell E6x00 series)
[pandora-kernel.git] / drivers / input / mouse / alps.c
index b03e7e0..f93c2c0 100644 (file)
@@ -5,6 +5,7 @@
  * 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>
+ * Copyright (c) 2009 Sebastian Kapfer <sebastian_kapfer@gmx.net>
  *
  * ALPS detection, tap switching and status querying info is taken from
  * tpconfig utility (by C. Scott Ananian and Bruce Kall).
@@ -28,7 +29,6 @@
 #define dbg(format, arg...) do {} while (0)
 #endif
 
-
 #define ALPS_OLDPROTO          0x01    /* old style input */
 #define ALPS_DUALPOINT         0x02    /* touchpad has trackstick */
 #define ALPS_PASS              0x04    /* device has a pass-through port */
@@ -37,7 +37,8 @@
 #define ALPS_FW_BK_1           0x10    /* front & back buttons present */
 #define ALPS_FW_BK_2           0x20    /* front & back buttons present */
 #define ALPS_FOUR_BUTTONS      0x40    /* 4 direction button present */
-
+#define ALPS_PS2_INTERLEAVED   0x80    /* 3-byte PS/2 packet interleaved with
+                                          6-byte ALPS packet */
 
 static const struct alps_model_info alps_model_data[] = {
        { { 0x32, 0x02, 0x14 }, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* Toshiba Salellite Pro M10 */
@@ -58,7 +59,9 @@ static const struct alps_model_info alps_model_data[] = {
        { { 0x20, 0x02, 0x0e }, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* XXX */
        { { 0x22, 0x02, 0x0a }, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT },
        { { 0x22, 0x02, 0x14 }, 0xff, 0xff, ALPS_PASS | ALPS_DUALPOINT }, /* Dell Latitude D600 */
-       { { 0x62, 0x02, 0x14 }, 0xcf, 0xcf, ALPS_PASS | ALPS_DUALPOINT }, /* Dell Latitude E6500 */
+       /* Dell Latitude E5500, E6400, E6500, Precision M4400 */
+       { { 0x62, 0x02, 0x14 }, 0xcf, 0xcf,
+               ALPS_PASS | ALPS_DUALPOINT | ALPS_PS2_INTERLEAVED },
        { { 0x73, 0x02, 0x50 }, 0xcf, 0xcf, ALPS_FOUR_BUTTONS },          /* Dell Vostro 1400 */
 };
 
@@ -69,20 +72,88 @@ static const struct alps_model_info alps_model_data[] = {
  */
 
 /*
- * ALPS abolute Mode - new format
+ * PS/2 packet format
+ *
+ * byte 0:  0    0 YSGN XSGN    1    M    R    L
+ * byte 1: X7   X6   X5   X4   X3   X2   X1   X0
+ * byte 2: Y7   Y6   Y5   Y4   Y3   Y2   Y1   Y0
+ *
+ * Note that the device never signals overflow condition.
+ *
+ * ALPS absolute Mode - new format
  *
  * byte 0:  1    ?    ?    ?    1    ?    ?    ?
  * byte 1:  0   x6   x5   x4   x3   x2   x1   x0
- * byte 2:  0   x10  x9   x8   x7    ?  fin  ges
+ * byte 2:  0  x10   x9   x8   x7    ?  fin  ges
  * byte 3:  0   y9   y8   y7    1    M    R    L
  * byte 4:  0   y6   y5   y4   y3   y2   y1   y0
  * byte 5:  0   z6   z5   z4   z3   z2   z1   z0
  *
+ * Dualpoint device -- interleaved packet format
+ *
+ * byte 0:    1    1    0    0    1    1    1    1
+ * byte 1:    0   x6   x5   x4   x3   x2   x1   x0
+ * byte 2:    0  x10   x9   x8   x7    0  fin  ges
+ * byte 3:    0    0 YSGN XSGN    1    1    1    1
+ * byte 4:   X7   X6   X5   X4   X3   X2   X1   X0
+ * byte 5:   Y7   Y6   Y5   Y4   Y3   Y2   Y1   Y0
+ * byte 6:    0   y9   y8   y7    1    m    r    l
+ * byte 7:    0   y6   y5   y4   y3   y2   y1   y0
+ * byte 8:    0   z6   z5   z4   z3   z2   z1   z0
+ *
+ * CAPITALS = stick, miniscules = touchpad
+ *
  * ?'s can have different meanings on different models,
  * such as wheel rotation, extra buttons, stick buttons
  * on a dualpoint, etc.
  */
 
+static bool alps_is_valid_first_byte(const struct alps_model_info *model,
+                                    unsigned char data)
+{
+       return (data & model->mask0) == model->byte0;
+}
+
+static void alps_report_buttons(struct psmouse *psmouse,
+                               struct input_dev *dev1, struct input_dev *dev2,
+                               int left, int right, int middle)
+{
+       struct alps_data *priv = psmouse->private;
+       const struct alps_model_info *model = priv->i;
+
+       if (model->flags & ALPS_PS2_INTERLEAVED) {
+               struct input_dev *dev;
+
+               /*
+                * If shared button has already been reported on the
+                * other device (dev2) then this event should be also
+                * sent through that device.
+                */
+               dev = test_bit(BTN_LEFT, dev2->key) ? dev2 : dev1;
+               input_report_key(dev, BTN_LEFT, left);
+
+               dev = test_bit(BTN_RIGHT, dev2->key) ? dev2 : dev1;
+               input_report_key(dev, BTN_RIGHT, right);
+
+               dev = test_bit(BTN_MIDDLE, dev2->key) ? dev2 : dev1;
+               input_report_key(dev, BTN_MIDDLE, middle);
+
+               /*
+                * Sync the _other_ device now, we'll do the first
+                * device later once we report the rest of the events.
+                */
+               input_sync(dev2);
+       } else {
+               /*
+                * For devices with non-interleaved packets we know what
+                * device buttons belong to so we can simply report them.
+                */
+               input_report_key(dev1, BTN_LEFT, left);
+               input_report_key(dev1, BTN_RIGHT, right);
+               input_report_key(dev1, BTN_MIDDLE, middle);
+       }
+}
+
 static void alps_process_packet(struct psmouse *psmouse)
 {
        struct alps_data *priv = psmouse->private;
@@ -93,18 +164,6 @@ static void alps_process_packet(struct psmouse *psmouse)
        int x, y, z, ges, fin, left, right, middle;
        int back = 0, forward = 0;
 
-       if ((packet[0] & 0xc8) == 0x08) {   /* 3-byte PS/2 packet */
-               input_report_key(dev2, BTN_LEFT,   packet[0] & 1);
-               input_report_key(dev2, BTN_RIGHT,  packet[0] & 2);
-               input_report_key(dev2, BTN_MIDDLE, packet[0] & 4);
-               input_report_rel(dev2, REL_X,
-                       packet[1] ? packet[1] - ((packet[0] << 4) & 0x100) : 0);
-               input_report_rel(dev2, REL_Y,
-                       packet[2] ? ((packet[0] << 3) & 0x100) - packet[2] : 0);
-               input_sync(dev2);
-               return;
-       }
-
        if (model->flags & ALPS_OLDPROTO) {
                left = packet[2] & 0x10;
                right = packet[2] & 0x08;
@@ -140,18 +199,13 @@ static void alps_process_packet(struct psmouse *psmouse)
                input_report_rel(dev2, REL_X,  (x > 383 ? (x - 768) : x));
                input_report_rel(dev2, REL_Y, -(y > 255 ? (y - 512) : y));
 
-               input_report_key(dev2, BTN_LEFT, left);
-               input_report_key(dev2, BTN_RIGHT, right);
-               input_report_key(dev2, BTN_MIDDLE, middle);
+               alps_report_buttons(psmouse, dev2, dev, left, right, middle);
 
-               input_sync(dev);
                input_sync(dev2);
                return;
        }
 
-       input_report_key(dev, BTN_LEFT, left);
-       input_report_key(dev, BTN_RIGHT, right);
-       input_report_key(dev, BTN_MIDDLE, middle);
+       alps_report_buttons(psmouse, dev, dev2, left, right, middle);
 
        /* Convert hardware tap to a reasonable Z value */
        if (ges && !fin)
@@ -202,25 +256,168 @@ static void alps_process_packet(struct psmouse *psmouse)
        input_sync(dev);
 }
 
+static void alps_report_bare_ps2_packet(struct psmouse *psmouse,
+                                       unsigned char packet[],
+                                       bool report_buttons)
+{
+       struct alps_data *priv = psmouse->private;
+       struct input_dev *dev2 = priv->dev2;
+
+       if (report_buttons)
+               alps_report_buttons(psmouse, dev2, psmouse->dev,
+                               packet[0] & 1, packet[0] & 2, packet[0] & 4);
+
+       input_report_rel(dev2, REL_X,
+               packet[1] ? packet[1] - ((packet[0] << 4) & 0x100) : 0);
+       input_report_rel(dev2, REL_Y,
+               packet[2] ? ((packet[0] << 3) & 0x100) - packet[2] : 0);
+
+       input_sync(dev2);
+}
+
+static psmouse_ret_t alps_handle_interleaved_ps2(struct psmouse *psmouse)
+{
+       struct alps_data *priv = psmouse->private;
+
+       if (psmouse->pktcnt < 6)
+               return PSMOUSE_GOOD_DATA;
+
+       if (psmouse->pktcnt == 6) {
+               /*
+                * Start a timer to flush the packet if it ends up last
+                * 6-byte packet in the stream. Timer needs to fire
+                * psmouse core times out itself. 20 ms should be enough
+                * to decide if we are getting more data or not.
+                */
+               mod_timer(&priv->timer, jiffies + msecs_to_jiffies(20));
+               return PSMOUSE_GOOD_DATA;
+       }
+
+       del_timer(&priv->timer);
+
+       if (psmouse->packet[6] & 0x80) {
+
+               /*
+                * Highest bit is set - that means we either had
+                * complete ALPS packet and this is start of the
+                * next packet or we got garbage.
+                */
+
+               if (((psmouse->packet[3] |
+                     psmouse->packet[4] |
+                     psmouse->packet[5]) & 0x80) ||
+                   (!alps_is_valid_first_byte(priv->i, psmouse->packet[6]))) {
+                       dbg("refusing packet %x %x %x %x "
+                           "(suspected interleaved ps/2)\n",
+                           psmouse->packet[3], psmouse->packet[4],
+                           psmouse->packet[5], psmouse->packet[6]);
+                       return PSMOUSE_BAD_DATA;
+               }
+
+               alps_process_packet(psmouse);
+
+               /* Continue with the next packet */
+               psmouse->packet[0] = psmouse->packet[6];
+               psmouse->pktcnt = 1;
+
+       } else {
+
+               /*
+                * High bit is 0 - that means that we indeed got a PS/2
+                * packet in the middle of ALPS packet.
+                *
+                * There is also possibility that we got 6-byte ALPS
+                * packet followed  by 3-byte packet from trackpoint. We
+                * can not distinguish between these 2 scenarios but
+                * becase the latter is unlikely to happen in course of
+                * normal operation (user would need to press all
+                * buttons on the pad and start moving trackpoint
+                * without touching the pad surface) we assume former.
+                * Even if we are wrong the wost thing that would happen
+                * the cursor would jump but we should not get protocol
+                * desynchronization.
+                */
+
+               alps_report_bare_ps2_packet(psmouse, &psmouse->packet[3],
+                                           false);
+
+               /*
+                * Continue with the standard ALPS protocol handling,
+                * but make sure we won't process it as an interleaved
+                * packet again, which may happen if all buttons are
+                * pressed. To avoid this let's reset the 4th bit which
+                * is normally 1.
+                */
+               psmouse->packet[3] = psmouse->packet[6] & 0xf7;
+               psmouse->pktcnt = 4;
+       }
+
+       return PSMOUSE_GOOD_DATA;
+}
+
+static void alps_flush_packet(unsigned long data)
+{
+       struct psmouse *psmouse = (struct psmouse *)data;
+
+       serio_pause_rx(psmouse->ps2dev.serio);
+
+       if (psmouse->pktcnt == 6) {
+
+               /*
+                * We did not any more data in reasonable amount of time.
+                * Validate the last 3 bytes and process as a standard
+                * ALPS packet.
+                */
+               if ((psmouse->packet[3] |
+                    psmouse->packet[4] |
+                    psmouse->packet[5]) & 0x80) {
+                       dbg("refusing packet %x %x %x "
+                           "(suspected interleaved ps/2)\n",
+                           psmouse->packet[3], psmouse->packet[4],
+                           psmouse->packet[5]);
+               } else {
+                       alps_process_packet(psmouse);
+               }
+               psmouse->pktcnt = 0;
+       }
+
+       serio_continue_rx(psmouse->ps2dev.serio);
+}
+
 static psmouse_ret_t alps_process_byte(struct psmouse *psmouse)
 {
        struct alps_data *priv = psmouse->private;
+       const struct alps_model_info *model = priv->i;
 
        if ((psmouse->packet[0] & 0xc8) == 0x08) { /* PS/2 packet */
                if (psmouse->pktcnt == 3) {
-                       alps_process_packet(psmouse);
+                       alps_report_bare_ps2_packet(psmouse, psmouse->packet,
+                                                   true);
                        return PSMOUSE_FULL_PACKET;
                }
                return PSMOUSE_GOOD_DATA;
        }
 
-       if ((psmouse->packet[0] & priv->i->mask0) != priv->i->byte0)
+       /* Check for PS/2 packet stuffed in the middle of ALPS packet. */
+
+       if ((model->flags & ALPS_PS2_INTERLEAVED) &&
+           psmouse->pktcnt >= 4 && (psmouse->packet[3] & 0x0f) == 0x0f) {
+               return alps_handle_interleaved_ps2(psmouse);
+       }
+
+       if (!alps_is_valid_first_byte(model, psmouse->packet[0])) {
+               dbg("refusing packet[0] = %x (mask0 = %x, byte0 = %x)\n",
+                   psmouse->packet[0], model->mask0, model->byte0);
                return PSMOUSE_BAD_DATA;
+       }
 
        /* Bytes 2 - 6 should have 0 in the highest bit */
        if (psmouse->pktcnt >= 2 && psmouse->pktcnt <= 6 &&
-           (psmouse->packet[psmouse->pktcnt - 1] & 0x80))
+           (psmouse->packet[psmouse->pktcnt - 1] & 0x80)) {
+               dbg("refusing packet[%i] = %x\n",
+                   psmouse->pktcnt - 1, psmouse->packet[psmouse->pktcnt - 1]);
                return PSMOUSE_BAD_DATA;
+       }
 
        if (psmouse->pktcnt == 6) {
                alps_process_packet(psmouse);
@@ -459,6 +656,7 @@ static void alps_disconnect(struct psmouse *psmouse)
        struct alps_data *priv = psmouse->private;
 
        psmouse_reset(psmouse);
+       del_timer_sync(&priv->timer);
        input_unregister_device(priv->dev2);
        kfree(priv);
 }
@@ -476,6 +674,8 @@ int alps_init(struct psmouse *psmouse)
                goto init_fail;
 
        priv->dev2 = dev2;
+       setup_timer(&priv->timer, alps_flush_packet, (unsigned long)psmouse);
+
        psmouse->private = priv;
 
        model = alps_get_model(psmouse, &version);