Input: ALPS - add support for "Dolphin" devices
authorYunkang Tang <tommywill2011@gmail.com>
Thu, 26 Dec 2013 22:54:19 +0000 (14:54 -0800)
committerDmitry Torokhov <dmitry.torokhov@gmail.com>
Thu, 26 Dec 2013 23:44:39 +0000 (15:44 -0800)
This adds support for another flavor of ALPS protocol used in newer
"Dolphin" devices.

Signed-off-by: Yunkang Tang <yunkang.tang@cn.alps.com>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
drivers/input/mouse/alps.c
drivers/input/mouse/alps.h

index 5cf62e3..fb15c64 100644 (file)
@@ -277,6 +277,57 @@ static void alps_process_packet_v1_v2(struct psmouse *psmouse)
 }
 
 /*
+ * Process bitmap data for V5 protocols. Return value is null.
+ *
+ * The bitmaps don't have enough data to track fingers, so this function
+ * only generates points representing a bounding box of at most two contacts.
+ * These two points are returned in x1, y1, x2, and y2.
+ */
+static void alps_process_bitmap_dolphin(struct alps_data *priv,
+                                       struct alps_fields *fields,
+                                       int *x1, int *y1, int *x2, int *y2)
+{
+       int box_middle_x, box_middle_y;
+       unsigned int x_map, y_map;
+       unsigned char start_bit, end_bit;
+       unsigned char x_msb, x_lsb, y_msb, y_lsb;
+
+       x_map = fields->x_map;
+       y_map = fields->y_map;
+
+       if (!x_map || !y_map)
+               return;
+
+       /* Get Most-significant and Least-significant bit */
+       x_msb = fls(x_map);
+       x_lsb = ffs(x_map);
+       y_msb = fls(y_map);
+       y_lsb = ffs(y_map);
+
+       /* Most-significant bit should never exceed max sensor line number */
+       if (x_msb > priv->x_bits || y_msb > priv->y_bits)
+               return;
+
+       *x1 = *y1 = *x2 = *y2 = 0;
+
+       if (fields->fingers > 1) {
+               start_bit = priv->x_bits - x_msb;
+               end_bit = priv->x_bits - x_lsb;
+               box_middle_x = (priv->x_max * (start_bit + end_bit)) /
+                               (2 * (priv->x_bits - 1));
+
+               start_bit = y_lsb - 1;
+               end_bit = y_msb - 1;
+               box_middle_y = (priv->y_max * (start_bit + end_bit)) /
+                               (2 * (priv->y_bits - 1));
+               *x1 = fields->x;
+               *y1 = fields->y;
+               *x2 = 2 * box_middle_x - *x1;
+               *y2 = 2 * box_middle_y - *y1;
+       }
+}
+
+/*
  * Process bitmap data from v3 and v4 protocols. Returns the number of
  * fingers detected. A return value of 0 means at least one of the
  * bitmaps was empty.
@@ -481,7 +532,8 @@ static void alps_decode_buttons_v3(struct alps_fields *f, unsigned char *p)
        f->ts_middle = !!(p[3] & 0x40);
 }
 
-static void alps_decode_pinnacle(struct alps_fields *f, unsigned char *p)
+static void alps_decode_pinnacle(struct alps_fields *f, unsigned char *p,
+                                struct psmouse *psmouse)
 {
        f->first_mp = !!(p[4] & 0x40);
        f->is_mp = !!(p[0] & 0x40);
@@ -502,48 +554,61 @@ static void alps_decode_pinnacle(struct alps_fields *f, unsigned char *p)
        alps_decode_buttons_v3(f, p);
 }
 
-static void alps_decode_rushmore(struct alps_fields *f, unsigned char *p)
+static void alps_decode_rushmore(struct alps_fields *f, unsigned char *p,
+                                struct psmouse *psmouse)
 {
-       alps_decode_pinnacle(f, p);
+       alps_decode_pinnacle(f, p, psmouse);
 
        f->x_map |= (p[5] & 0x10) << 11;
        f->y_map |= (p[5] & 0x20) << 6;
 }
 
-static void alps_decode_dolphin(struct alps_fields *f, unsigned char *p)
+static void alps_decode_dolphin(struct alps_fields *f, unsigned char *p,
+                               struct psmouse *psmouse)
 {
+       u64 palm_data = 0;
+       struct alps_data *priv = psmouse->private;
+
        f->first_mp = !!(p[0] & 0x02);
        f->is_mp = !!(p[0] & 0x20);
 
-       f->fingers = ((p[0] & 0x6) >> 1 |
+       if (!f->is_mp) {
+               f->x = ((p[1] & 0x7f) | ((p[4] & 0x0f) << 7));
+               f->y = ((p[2] & 0x7f) | ((p[4] & 0xf0) << 3));
+               f->z = (p[0] & 4) ? 0 : p[5] & 0x7f;
+               alps_decode_buttons_v3(f, p);
+       } else {
+               f->fingers = ((p[0] & 0x6) >> 1 |
                     (p[0] & 0x10) >> 2);
-       f->x_map = ((p[2] & 0x60) >> 5) |
-                  ((p[4] & 0x7f) << 2) |
-                  ((p[5] & 0x7f) << 9) |
-                  ((p[3] & 0x07) << 16) |
-                  ((p[3] & 0x70) << 15) |
-                  ((p[0] & 0x01) << 22);
-       f->y_map = (p[1] & 0x7f) |
-                  ((p[2] & 0x1f) << 7);
-
-       f->x = ((p[1] & 0x7f) | ((p[4] & 0x0f) << 7));
-       f->y = ((p[2] & 0x7f) | ((p[4] & 0xf0) << 3));
-       f->z = (p[0] & 4) ? 0 : p[5] & 0x7f;
 
-       alps_decode_buttons_v3(f, p);
+               palm_data = (p[1] & 0x7f) |
+                           ((p[2] & 0x7f) << 7) |
+                           ((p[4] & 0x7f) << 14) |
+                           ((p[5] & 0x7f) << 21) |
+                           ((p[3] & 0x07) << 28) |
+                           (((u64)p[3] & 0x70) << 27) |
+                           (((u64)p[0] & 0x01) << 34);
+
+               /* Y-profile is stored in P(0) to p(n-1), n = y_bits; */
+               f->y_map = palm_data & (BIT(priv->y_bits) - 1);
+
+               /* X-profile is stored in p(n) to p(n+m-1), m = x_bits; */
+               f->x_map = (palm_data >> priv->y_bits) &
+                          (BIT(priv->x_bits) - 1);
+       }
 }
 
-static void alps_process_touchpad_packet_v3(struct psmouse *psmouse)
+static void alps_process_touchpad_packet_v3_v5(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 x1 = 0, y1 = 0, x2 = 0, y2 = 0;
-       int fingers = 0, bmap_fingers;
-       struct alps_fields f;
+       int fingers = 0, bmap_fn;
+       struct alps_fields f = {0};
 
-       priv->decode_fields(&f, packet);
+       priv->decode_fields(&f, packet, psmouse);
 
        /*
         * There's no single feature of touchpad position and bitmap packets
@@ -560,19 +625,38 @@ static void alps_process_touchpad_packet_v3(struct psmouse *psmouse)
                 */
                if (f.is_mp) {
                        fingers = f.fingers;
-                       bmap_fingers = alps_process_bitmap(priv,
-                                                          f.x_map, f.y_map,
-                                                          &x1, &y1, &x2, &y2);
-
-                       /*
-                        * We shouldn't report more than one finger if
-                        * we don't have two coordinates.
-                        */
-                       if (fingers > 1 && bmap_fingers < 2)
-                               fingers = bmap_fingers;
-
-                       /* Now process position packet */
-                       priv->decode_fields(&f, priv->multi_data);
+                       if (priv->proto_version == ALPS_PROTO_V3) {
+                               bmap_fn = alps_process_bitmap(priv, f.x_map,
+                                                             f.y_map, &x1, &y1,
+                                                             &x2, &y2);
+
+                               /*
+                                * We shouldn't report more than one finger if
+                                * we don't have two coordinates.
+                                */
+                               if (fingers > 1 && bmap_fn < 2)
+                                       fingers = bmap_fn;
+
+                               /* Now process position packet */
+                               priv->decode_fields(&f, priv->multi_data,
+                                                   psmouse);
+                       } else {
+                               /*
+                                * Because Dolphin uses position packet's
+                                * coordinate data as Pt1 and uses it to
+                                * calculate Pt2, so we need to do position
+                                * packet decode first.
+                                */
+                               priv->decode_fields(&f, priv->multi_data,
+                                                   psmouse);
+
+                               /*
+                                * Since Dolphin's finger number is reliable,
+                                * there is no need to compare with bmap_fn.
+                                */
+                               alps_process_bitmap_dolphin(priv, &f, &x1, &y1,
+                                                           &x2, &y2);
+                       }
                } else {
                        priv->multi_packet = 0;
                }
@@ -662,7 +746,7 @@ static void alps_process_packet_v3(struct psmouse *psmouse)
                return;
        }
 
-       alps_process_touchpad_packet_v3(psmouse);
+       alps_process_touchpad_packet_v3_v5(psmouse);
 }
 
 static void alps_process_packet_v6(struct psmouse *psmouse)
@@ -1709,6 +1793,52 @@ error:
        return -1;
 }
 
+static int alps_dolphin_get_device_area(struct psmouse *psmouse,
+                                       struct alps_data *priv)
+{
+       struct ps2dev *ps2dev = &psmouse->ps2dev;
+       unsigned char param[4] = {0};
+       int num_x_electrode, num_y_electrode;
+
+       if (alps_enter_command_mode(psmouse))
+               return -1;
+
+       param[0] = 0x0a;
+       if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_RESET_WRAP) ||
+           ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETPOLL) ||
+           ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETPOLL) ||
+           ps2_command(ps2dev, &param[0], PSMOUSE_CMD_SETRATE) ||
+           ps2_command(ps2dev, &param[0], PSMOUSE_CMD_SETRATE))
+               return -1;
+
+       if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO))
+               return -1;
+
+       /*
+        * Dolphin's sensor line number is not fixed. It can be calculated
+        * by adding the device's register value with DOLPHIN_PROFILE_X/YOFFSET.
+        * Further more, we can get device's x_max and y_max by multiplying
+        * sensor line number with DOLPHIN_COUNT_PER_ELECTRODE.
+        *
+        * e.g. When we get register's sensor_x = 11 & sensor_y = 8,
+        *      real sensor line number X = 11 + 8 = 19, and
+        *      real sensor line number Y = 8 + 1 = 9.
+        *      So, x_max = (19 - 1) * 64 = 1152, and
+        *          y_max = (9 - 1) * 64 = 512.
+        */
+       num_x_electrode = DOLPHIN_PROFILE_XOFFSET + (param[2] & 0x0F);
+       num_y_electrode = DOLPHIN_PROFILE_YOFFSET + ((param[2] >> 4) & 0x0F);
+       priv->x_bits = num_x_electrode;
+       priv->y_bits = num_y_electrode;
+       priv->x_max = (num_x_electrode - 1) * DOLPHIN_COUNT_PER_ELECTRODE;
+       priv->y_max = (num_y_electrode - 1) * DOLPHIN_COUNT_PER_ELECTRODE;
+
+       if (alps_exit_command_mode(psmouse))
+               return -1;
+
+       return 0;
+}
+
 static int alps_hw_init_dolphin_v1(struct psmouse *psmouse)
 {
        struct ps2dev *ps2dev = &psmouse->ps2dev;
@@ -1763,13 +1893,13 @@ static void alps_set_defaults(struct alps_data *priv)
                break;
        case ALPS_PROTO_V5:
                priv->hw_init = alps_hw_init_dolphin_v1;
-               priv->process_packet = alps_process_packet_v3;
+               priv->process_packet = alps_process_touchpad_packet_v3_v5;
                priv->decode_fields = alps_decode_dolphin;
                priv->set_abs_params = alps_set_abs_params_mt;
                priv->nibble_commands = alps_v3_nibble_commands;
                priv->addr_command = PSMOUSE_CMD_RESET_WRAP;
                priv->byte0 = 0xc8;
-               priv->mask0 = 0xc8;
+               priv->mask0 = 0xd8;
                priv->flags = 0;
                priv->x_max = 1360;
                priv->y_max = 660;
@@ -1845,11 +1975,13 @@ static int alps_identify(struct psmouse *psmouse, struct alps_data *priv)
        if (alps_match_table(psmouse, priv, e7, ec) == 0) {
                return 0;
        } else if (e7[0] == 0x73 && e7[1] == 0x03 && e7[2] == 0x50 &&
-                  ec[0] == 0x73 && ec[1] == 0x01) {
+                  ec[0] == 0x73 && (ec[1] == 0x01 || ec[1] == 0x02)) {
                priv->proto_version = ALPS_PROTO_V5;
                alps_set_defaults(priv);
-
-               return 0;
+               if (alps_dolphin_get_device_area(psmouse, priv))
+                       return -EIO;
+               else
+                       return 0;
        } else if (ec[0] == 0x88 && ec[1] == 0x08) {
                priv->proto_version = ALPS_PROTO_V3;
                alps_set_defaults(priv);
index 704f0f9..03f88b6 100644 (file)
 #define ALPS_PROTO_V5  5
 #define ALPS_PROTO_V6  6
 
+#define DOLPHIN_COUNT_PER_ELECTRODE    64
+#define DOLPHIN_PROFILE_XOFFSET                8       /* x-electrode offset */
+#define DOLPHIN_PROFILE_YOFFSET                1       /* y-electrode offset */
+
 /**
  * struct alps_model_info - touchpad ID table
  * @signature: E7 response string to match.
@@ -146,7 +150,8 @@ struct alps_data {
 
        int (*hw_init)(struct psmouse *psmouse);
        void (*process_packet)(struct psmouse *psmouse);
-       void (*decode_fields)(struct alps_fields *f, unsigned char *p);
+       void (*decode_fields)(struct alps_fields *f, unsigned char *p,
+                             struct psmouse *psmouse);
        void (*set_abs_params)(struct alps_data *priv, struct input_dev *dev1);
 
        int prev_fin;