1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
298ee3771SChris Diamand /*
398ee3771SChris Diamand * BYD TouchPad PS/2 mouse driver
498ee3771SChris Diamand *
598ee3771SChris Diamand * Copyright (C) 2015 Chris Diamand <chris@diamand.org>
682aaa086SChris Diamand * Copyright (C) 2015 Richard Pospesel
782aaa086SChris Diamand * Copyright (C) 2015 Tai Chi Minh Ralph Eastwood
882aaa086SChris Diamand * Copyright (C) 2015 Martin Wimpress
982aaa086SChris Diamand * Copyright (C) 2015 Jay Kuri
1098ee3771SChris Diamand */
1198ee3771SChris Diamand
1298ee3771SChris Diamand #include <linux/delay.h>
1398ee3771SChris Diamand #include <linux/input.h>
1498ee3771SChris Diamand #include <linux/libps2.h>
1598ee3771SChris Diamand #include <linux/serio.h>
162d5f5611SRichard Pospesel #include <linux/slab.h>
1798ee3771SChris Diamand
1898ee3771SChris Diamand #include "psmouse.h"
1998ee3771SChris Diamand #include "byd.h"
2098ee3771SChris Diamand
212d5f5611SRichard Pospesel /* PS2 Bits */
2298ee3771SChris Diamand #define PS2_Y_OVERFLOW BIT_MASK(7)
2398ee3771SChris Diamand #define PS2_X_OVERFLOW BIT_MASK(6)
2498ee3771SChris Diamand #define PS2_Y_SIGN BIT_MASK(5)
2598ee3771SChris Diamand #define PS2_X_SIGN BIT_MASK(4)
2698ee3771SChris Diamand #define PS2_ALWAYS_1 BIT_MASK(3)
2798ee3771SChris Diamand #define PS2_MIDDLE BIT_MASK(2)
2898ee3771SChris Diamand #define PS2_RIGHT BIT_MASK(1)
2998ee3771SChris Diamand #define PS2_LEFT BIT_MASK(0)
3098ee3771SChris Diamand
3198ee3771SChris Diamand /*
322d5f5611SRichard Pospesel * BYD pad constants
3398ee3771SChris Diamand */
3498ee3771SChris Diamand
352d5f5611SRichard Pospesel /*
362d5f5611SRichard Pospesel * True device resolution is unknown, however experiments show the
372d5f5611SRichard Pospesel * resolution is about 111 units/mm.
382d5f5611SRichard Pospesel * Absolute coordinate packets are in the range 0-255 for both X and Y
392d5f5611SRichard Pospesel * we pick ABS_X/ABS_Y dimensions which are multiples of 256 and in
402d5f5611SRichard Pospesel * the right ballpark given the touchpad's physical dimensions and estimate
412d5f5611SRichard Pospesel * resolution per spec sheet, device active area dimensions are
422d5f5611SRichard Pospesel * 101.6 x 60.1 mm.
432d5f5611SRichard Pospesel */
442d5f5611SRichard Pospesel #define BYD_PAD_WIDTH 11264
452d5f5611SRichard Pospesel #define BYD_PAD_HEIGHT 6656
462d5f5611SRichard Pospesel #define BYD_PAD_RESOLUTION 111
4798ee3771SChris Diamand
482d5f5611SRichard Pospesel /*
492d5f5611SRichard Pospesel * Given the above dimensions, relative packets velocity is in multiples of
502d5f5611SRichard Pospesel * 1 unit / 11 milliseconds. We use this dt to estimate distance traveled
512d5f5611SRichard Pospesel */
522d5f5611SRichard Pospesel #define BYD_DT 11
532d5f5611SRichard Pospesel /* Time in jiffies used to timeout various touch events (64 ms) */
542d5f5611SRichard Pospesel #define BYD_TOUCH_TIMEOUT msecs_to_jiffies(64)
552d5f5611SRichard Pospesel
562d5f5611SRichard Pospesel /* BYD commands reverse engineered from windows driver */
572d5f5611SRichard Pospesel
582d5f5611SRichard Pospesel /*
592d5f5611SRichard Pospesel * Swipe gesture from off-pad to on-pad
602d5f5611SRichard Pospesel * 0 : disable
612d5f5611SRichard Pospesel * 1 : enable
622d5f5611SRichard Pospesel */
632d5f5611SRichard Pospesel #define BYD_CMD_SET_OFFSCREEN_SWIPE 0x10cc
642d5f5611SRichard Pospesel /*
652d5f5611SRichard Pospesel * Tap and drag delay time
662d5f5611SRichard Pospesel * 0 : disable
672d5f5611SRichard Pospesel * 1 - 8 : least to most delay
682d5f5611SRichard Pospesel */
692d5f5611SRichard Pospesel #define BYD_CMD_SET_TAP_DRAG_DELAY_TIME 0x10cf
702d5f5611SRichard Pospesel /*
712d5f5611SRichard Pospesel * Physical buttons function mapping
722d5f5611SRichard Pospesel * 0 : enable
732d5f5611SRichard Pospesel * 4 : normal
742d5f5611SRichard Pospesel * 5 : left button custom command
752d5f5611SRichard Pospesel * 6 : right button custom command
762d5f5611SRichard Pospesel * 8 : disable
772d5f5611SRichard Pospesel */
782d5f5611SRichard Pospesel #define BYD_CMD_SET_PHYSICAL_BUTTONS 0x10d0
792d5f5611SRichard Pospesel /*
802d5f5611SRichard Pospesel * Absolute mode (1 byte X/Y resolution)
812d5f5611SRichard Pospesel * 0 : disable
822d5f5611SRichard Pospesel * 2 : enable
832d5f5611SRichard Pospesel */
842d5f5611SRichard Pospesel #define BYD_CMD_SET_ABSOLUTE_MODE 0x10d1
852d5f5611SRichard Pospesel /*
862d5f5611SRichard Pospesel * Two finger scrolling
872d5f5611SRichard Pospesel * 1 : vertical
882d5f5611SRichard Pospesel * 2 : horizontal
892d5f5611SRichard Pospesel * 3 : vertical + horizontal
902d5f5611SRichard Pospesel * 4 : disable
912d5f5611SRichard Pospesel */
922d5f5611SRichard Pospesel #define BYD_CMD_SET_TWO_FINGER_SCROLL 0x10d2
932d5f5611SRichard Pospesel /*
942d5f5611SRichard Pospesel * Handedness
952d5f5611SRichard Pospesel * 1 : right handed
962d5f5611SRichard Pospesel * 2 : left handed
972d5f5611SRichard Pospesel */
982d5f5611SRichard Pospesel #define BYD_CMD_SET_HANDEDNESS 0x10d3
992d5f5611SRichard Pospesel /*
1002d5f5611SRichard Pospesel * Tap to click
1012d5f5611SRichard Pospesel * 1 : enable
1022d5f5611SRichard Pospesel * 2 : disable
1032d5f5611SRichard Pospesel */
1042d5f5611SRichard Pospesel #define BYD_CMD_SET_TAP 0x10d4
1052d5f5611SRichard Pospesel /*
1062d5f5611SRichard Pospesel * Tap and drag
1072d5f5611SRichard Pospesel * 1 : tap and hold to drag
1082d5f5611SRichard Pospesel * 2 : tap and hold to drag + lock
1092d5f5611SRichard Pospesel * 3 : disable
1102d5f5611SRichard Pospesel */
1112d5f5611SRichard Pospesel #define BYD_CMD_SET_TAP_DRAG 0x10d5
1122d5f5611SRichard Pospesel /*
1132d5f5611SRichard Pospesel * Touch sensitivity
1142d5f5611SRichard Pospesel * 1 - 7 : least to most sensitive
1152d5f5611SRichard Pospesel */
1162d5f5611SRichard Pospesel #define BYD_CMD_SET_TOUCH_SENSITIVITY 0x10d6
1172d5f5611SRichard Pospesel /*
1182d5f5611SRichard Pospesel * One finger scrolling
1192d5f5611SRichard Pospesel * 1 : vertical
1202d5f5611SRichard Pospesel * 2 : horizontal
1212d5f5611SRichard Pospesel * 3 : vertical + horizontal
1222d5f5611SRichard Pospesel * 4 : disable
1232d5f5611SRichard Pospesel */
1242d5f5611SRichard Pospesel #define BYD_CMD_SET_ONE_FINGER_SCROLL 0x10d7
1252d5f5611SRichard Pospesel /*
1262d5f5611SRichard Pospesel * One finger scrolling function
1272d5f5611SRichard Pospesel * 1 : free scrolling
1282d5f5611SRichard Pospesel * 2 : edge motion
1292d5f5611SRichard Pospesel * 3 : free scrolling + edge motion
1302d5f5611SRichard Pospesel * 4 : disable
1312d5f5611SRichard Pospesel */
1322d5f5611SRichard Pospesel #define BYD_CMD_SET_ONE_FINGER_SCROLL_FUNC 0x10d8
1332d5f5611SRichard Pospesel /*
1342d5f5611SRichard Pospesel * Sliding speed
1352d5f5611SRichard Pospesel * 1 - 5 : slowest to fastest
1362d5f5611SRichard Pospesel */
1372d5f5611SRichard Pospesel #define BYD_CMD_SET_SLIDING_SPEED 0x10da
1382d5f5611SRichard Pospesel /*
1392d5f5611SRichard Pospesel * Edge motion
1402d5f5611SRichard Pospesel * 1 : disable
1412d5f5611SRichard Pospesel * 2 : enable when dragging
1422d5f5611SRichard Pospesel * 3 : enable when dragging and pointing
1432d5f5611SRichard Pospesel */
1442d5f5611SRichard Pospesel #define BYD_CMD_SET_EDGE_MOTION 0x10db
1452d5f5611SRichard Pospesel /*
1462d5f5611SRichard Pospesel * Left edge region size
1472d5f5611SRichard Pospesel * 0 - 7 : smallest to largest width
1482d5f5611SRichard Pospesel */
1492d5f5611SRichard Pospesel #define BYD_CMD_SET_LEFT_EDGE_REGION 0x10dc
1502d5f5611SRichard Pospesel /*
1512d5f5611SRichard Pospesel * Top edge region size
1522d5f5611SRichard Pospesel * 0 - 9 : smallest to largest height
1532d5f5611SRichard Pospesel */
1542d5f5611SRichard Pospesel #define BYD_CMD_SET_TOP_EDGE_REGION 0x10dd
1552d5f5611SRichard Pospesel /*
1562d5f5611SRichard Pospesel * Disregard palm press as clicks
1572d5f5611SRichard Pospesel * 1 - 6 : smallest to largest
1582d5f5611SRichard Pospesel */
1592d5f5611SRichard Pospesel #define BYD_CMD_SET_PALM_CHECK 0x10de
1602d5f5611SRichard Pospesel /*
1612d5f5611SRichard Pospesel * Right edge region size
1622d5f5611SRichard Pospesel * 0 - 7 : smallest to largest width
1632d5f5611SRichard Pospesel */
1642d5f5611SRichard Pospesel #define BYD_CMD_SET_RIGHT_EDGE_REGION 0x10df
1652d5f5611SRichard Pospesel /*
1662d5f5611SRichard Pospesel * Bottom edge region size
1672d5f5611SRichard Pospesel * 0 - 9 : smallest to largest height
1682d5f5611SRichard Pospesel */
1692d5f5611SRichard Pospesel #define BYD_CMD_SET_BOTTOM_EDGE_REGION 0x10e1
1702d5f5611SRichard Pospesel /*
1712d5f5611SRichard Pospesel * Multitouch gestures
1722d5f5611SRichard Pospesel * 1 : enable
1732d5f5611SRichard Pospesel * 2 : disable
1742d5f5611SRichard Pospesel */
1752d5f5611SRichard Pospesel #define BYD_CMD_SET_MULTITOUCH 0x10e3
1762d5f5611SRichard Pospesel /*
1772d5f5611SRichard Pospesel * Edge motion speed
1782d5f5611SRichard Pospesel * 0 : control with finger pressure
1792d5f5611SRichard Pospesel * 1 - 9 : slowest to fastest
1802d5f5611SRichard Pospesel */
1812d5f5611SRichard Pospesel #define BYD_CMD_SET_EDGE_MOTION_SPEED 0x10e4
1822d5f5611SRichard Pospesel /*
1832d5f5611SRichard Pospesel * Two finger scolling function
1842d5f5611SRichard Pospesel * 0 : free scrolling
1852d5f5611SRichard Pospesel * 1 : free scrolling (with momentum)
1862d5f5611SRichard Pospesel * 2 : edge motion
1872d5f5611SRichard Pospesel * 3 : free scrolling (with momentum) + edge motion
1882d5f5611SRichard Pospesel * 4 : disable
1892d5f5611SRichard Pospesel */
1902d5f5611SRichard Pospesel #define BYD_CMD_SET_TWO_FINGER_SCROLL_FUNC 0x10e5
1912d5f5611SRichard Pospesel
1922d5f5611SRichard Pospesel /*
1932d5f5611SRichard Pospesel * The touchpad generates a mixture of absolute and relative packets, indicated
194*652c0441SXiang wangx * by the last byte of each packet being set to one of the following:
1952d5f5611SRichard Pospesel */
1962d5f5611SRichard Pospesel #define BYD_PACKET_ABSOLUTE 0xf8
1972d5f5611SRichard Pospesel #define BYD_PACKET_RELATIVE 0x00
1982d5f5611SRichard Pospesel /* Multitouch gesture packets */
1992d5f5611SRichard Pospesel #define BYD_PACKET_PINCH_IN 0xd8
2002d5f5611SRichard Pospesel #define BYD_PACKET_PINCH_OUT 0x28
2012d5f5611SRichard Pospesel #define BYD_PACKET_ROTATE_CLOCKWISE 0x29
2022d5f5611SRichard Pospesel #define BYD_PACKET_ROTATE_ANTICLOCKWISE 0xd7
2032d5f5611SRichard Pospesel #define BYD_PACKET_TWO_FINGER_SCROLL_RIGHT 0x2a
2042d5f5611SRichard Pospesel #define BYD_PACKET_TWO_FINGER_SCROLL_DOWN 0x2b
2052d5f5611SRichard Pospesel #define BYD_PACKET_TWO_FINGER_SCROLL_UP 0xd5
2062d5f5611SRichard Pospesel #define BYD_PACKET_TWO_FINGER_SCROLL_LEFT 0xd6
2072d5f5611SRichard Pospesel #define BYD_PACKET_THREE_FINGER_SWIPE_RIGHT 0x2c
2082d5f5611SRichard Pospesel #define BYD_PACKET_THREE_FINGER_SWIPE_DOWN 0x2d
2092d5f5611SRichard Pospesel #define BYD_PACKET_THREE_FINGER_SWIPE_UP 0xd3
2102d5f5611SRichard Pospesel #define BYD_PACKET_THREE_FINGER_SWIPE_LEFT 0xd4
2112d5f5611SRichard Pospesel #define BYD_PACKET_FOUR_FINGER_DOWN 0x33
2122d5f5611SRichard Pospesel #define BYD_PACKET_FOUR_FINGER_UP 0xcd
2132d5f5611SRichard Pospesel #define BYD_PACKET_REGION_SCROLL_RIGHT 0x35
2142d5f5611SRichard Pospesel #define BYD_PACKET_REGION_SCROLL_DOWN 0x36
2152d5f5611SRichard Pospesel #define BYD_PACKET_REGION_SCROLL_UP 0xca
2162d5f5611SRichard Pospesel #define BYD_PACKET_REGION_SCROLL_LEFT 0xcb
2172d5f5611SRichard Pospesel #define BYD_PACKET_RIGHT_CORNER_CLICK 0xd2
2182d5f5611SRichard Pospesel #define BYD_PACKET_LEFT_CORNER_CLICK 0x2e
2192d5f5611SRichard Pospesel #define BYD_PACKET_LEFT_AND_RIGHT_CORNER_CLICK 0x2f
2202d5f5611SRichard Pospesel #define BYD_PACKET_ONTO_PAD_SWIPE_RIGHT 0x37
2212d5f5611SRichard Pospesel #define BYD_PACKET_ONTO_PAD_SWIPE_DOWN 0x30
2222d5f5611SRichard Pospesel #define BYD_PACKET_ONTO_PAD_SWIPE_UP 0xd0
2232d5f5611SRichard Pospesel #define BYD_PACKET_ONTO_PAD_SWIPE_LEFT 0xc9
2242d5f5611SRichard Pospesel
2252d5f5611SRichard Pospesel struct byd_data {
2262d5f5611SRichard Pospesel struct timer_list timer;
227ce23cbc8SKees Cook struct psmouse *psmouse;
2282d5f5611SRichard Pospesel s32 abs_x;
2292d5f5611SRichard Pospesel s32 abs_y;
2302d5f5611SRichard Pospesel typeof(jiffies) last_touch_time;
2312d5f5611SRichard Pospesel bool btn_left;
2322d5f5611SRichard Pospesel bool btn_right;
2332d5f5611SRichard Pospesel bool touch;
2342d5f5611SRichard Pospesel };
2352d5f5611SRichard Pospesel
byd_report_input(struct psmouse * psmouse)2362d5f5611SRichard Pospesel static void byd_report_input(struct psmouse *psmouse)
23798ee3771SChris Diamand {
2382d5f5611SRichard Pospesel struct byd_data *priv = psmouse->private;
2392d5f5611SRichard Pospesel struct input_dev *dev = psmouse->dev;
24098ee3771SChris Diamand
2412d5f5611SRichard Pospesel input_report_key(dev, BTN_TOUCH, priv->touch);
2422d5f5611SRichard Pospesel input_report_key(dev, BTN_TOOL_FINGER, priv->touch);
24398ee3771SChris Diamand
2442d5f5611SRichard Pospesel input_report_abs(dev, ABS_X, priv->abs_x);
2452d5f5611SRichard Pospesel input_report_abs(dev, ABS_Y, priv->abs_y);
2462d5f5611SRichard Pospesel input_report_key(dev, BTN_LEFT, priv->btn_left);
2472d5f5611SRichard Pospesel input_report_key(dev, BTN_RIGHT, priv->btn_right);
24898ee3771SChris Diamand
2492d5f5611SRichard Pospesel input_sync(dev);
25098ee3771SChris Diamand }
25198ee3771SChris Diamand
byd_clear_touch(struct timer_list * t)252ce23cbc8SKees Cook static void byd_clear_touch(struct timer_list *t)
2532d5f5611SRichard Pospesel {
254ce23cbc8SKees Cook struct byd_data *priv = from_timer(priv, t, timer);
255ce23cbc8SKees Cook struct psmouse *psmouse = priv->psmouse;
2562d5f5611SRichard Pospesel
2572d5f5611SRichard Pospesel serio_pause_rx(psmouse->ps2dev.serio);
2582d5f5611SRichard Pospesel priv->touch = false;
2592d5f5611SRichard Pospesel
2602d5f5611SRichard Pospesel byd_report_input(psmouse);
2612d5f5611SRichard Pospesel
2622d5f5611SRichard Pospesel serio_continue_rx(psmouse->ps2dev.serio);
2632d5f5611SRichard Pospesel
2642d5f5611SRichard Pospesel /*
2652d5f5611SRichard Pospesel * Move cursor back to center of pad when we lose touch - this
2662d5f5611SRichard Pospesel * specifically improves user experience when moving cursor with one
2672d5f5611SRichard Pospesel * finger, and pressing a button with another.
2682d5f5611SRichard Pospesel */
2692d5f5611SRichard Pospesel priv->abs_x = BYD_PAD_WIDTH / 2;
2702d5f5611SRichard Pospesel priv->abs_y = BYD_PAD_HEIGHT / 2;
27198ee3771SChris Diamand }
27298ee3771SChris Diamand
byd_process_byte(struct psmouse * psmouse)27398ee3771SChris Diamand static psmouse_ret_t byd_process_byte(struct psmouse *psmouse)
27498ee3771SChris Diamand {
2752d5f5611SRichard Pospesel struct byd_data *priv = psmouse->private;
27698ee3771SChris Diamand u8 *pkt = psmouse->packet;
27798ee3771SChris Diamand
27898ee3771SChris Diamand if (psmouse->pktcnt > 0 && !(pkt[0] & PS2_ALWAYS_1)) {
27998ee3771SChris Diamand psmouse_warn(psmouse, "Always_1 bit not 1. pkt[0] = %02x\n",
28098ee3771SChris Diamand pkt[0]);
28198ee3771SChris Diamand return PSMOUSE_BAD_DATA;
28298ee3771SChris Diamand }
28398ee3771SChris Diamand
28498ee3771SChris Diamand if (psmouse->pktcnt < psmouse->pktsize)
28598ee3771SChris Diamand return PSMOUSE_GOOD_DATA;
28698ee3771SChris Diamand
28798ee3771SChris Diamand /* Otherwise, a full packet has been received */
28898ee3771SChris Diamand switch (pkt[3]) {
2892d5f5611SRichard Pospesel case BYD_PACKET_ABSOLUTE:
2902d5f5611SRichard Pospesel /* Only use absolute packets for the start of movement. */
2912d5f5611SRichard Pospesel if (!priv->touch) {
2922d5f5611SRichard Pospesel /* needed to detect tap */
2932d5f5611SRichard Pospesel typeof(jiffies) tap_time =
2942d5f5611SRichard Pospesel priv->last_touch_time + BYD_TOUCH_TIMEOUT;
2952d5f5611SRichard Pospesel priv->touch = time_after(jiffies, tap_time);
2962d5f5611SRichard Pospesel
2972d5f5611SRichard Pospesel /* init abs position */
2982d5f5611SRichard Pospesel priv->abs_x = pkt[1] * (BYD_PAD_WIDTH / 256);
2992d5f5611SRichard Pospesel priv->abs_y = (255 - pkt[2]) * (BYD_PAD_HEIGHT / 256);
3002d5f5611SRichard Pospesel }
3012d5f5611SRichard Pospesel break;
3022d5f5611SRichard Pospesel case BYD_PACKET_RELATIVE: {
30398ee3771SChris Diamand /* Standard packet */
30498ee3771SChris Diamand /* Sign-extend if a sign bit is set. */
3052d5f5611SRichard Pospesel u32 signx = pkt[0] & PS2_X_SIGN ? ~0xFF : 0;
3062d5f5611SRichard Pospesel u32 signy = pkt[0] & PS2_Y_SIGN ? ~0xFF : 0;
3072d5f5611SRichard Pospesel s32 dx = signx | (int) pkt[1];
3082d5f5611SRichard Pospesel s32 dy = signy | (int) pkt[2];
30998ee3771SChris Diamand
3102d5f5611SRichard Pospesel /* Update position based on velocity */
3112d5f5611SRichard Pospesel priv->abs_x += dx * BYD_DT;
3122d5f5611SRichard Pospesel priv->abs_y -= dy * BYD_DT;
31398ee3771SChris Diamand
3142d5f5611SRichard Pospesel priv->touch = true;
31598ee3771SChris Diamand break;
31698ee3771SChris Diamand }
31798ee3771SChris Diamand default:
31898ee3771SChris Diamand psmouse_warn(psmouse,
31998ee3771SChris Diamand "Unrecognized Z: pkt = %02x %02x %02x %02x\n",
32098ee3771SChris Diamand psmouse->packet[0], psmouse->packet[1],
32198ee3771SChris Diamand psmouse->packet[2], psmouse->packet[3]);
32298ee3771SChris Diamand return PSMOUSE_BAD_DATA;
32398ee3771SChris Diamand }
32498ee3771SChris Diamand
3252d5f5611SRichard Pospesel priv->btn_left = pkt[0] & PS2_LEFT;
3262d5f5611SRichard Pospesel priv->btn_right = pkt[0] & PS2_RIGHT;
3272d5f5611SRichard Pospesel
3282d5f5611SRichard Pospesel byd_report_input(psmouse);
3292d5f5611SRichard Pospesel
3302d5f5611SRichard Pospesel /* Reset time since last touch. */
3312d5f5611SRichard Pospesel if (priv->touch) {
3322d5f5611SRichard Pospesel priv->last_touch_time = jiffies;
3332d5f5611SRichard Pospesel mod_timer(&priv->timer, jiffies + BYD_TOUCH_TIMEOUT);
3342d5f5611SRichard Pospesel }
33598ee3771SChris Diamand
33698ee3771SChris Diamand return PSMOUSE_FULL_PACKET;
33798ee3771SChris Diamand }
33898ee3771SChris Diamand
byd_reset_touchpad(struct psmouse * psmouse)3392d5f5611SRichard Pospesel static int byd_reset_touchpad(struct psmouse *psmouse)
34098ee3771SChris Diamand {
34198ee3771SChris Diamand struct ps2dev *ps2dev = &psmouse->ps2dev;
34298ee3771SChris Diamand u8 param[4];
3432d5f5611SRichard Pospesel size_t i;
3442d5f5611SRichard Pospesel
34508d6ac9eSColin Ian King static const struct {
34698ee3771SChris Diamand u16 command;
34798ee3771SChris Diamand u8 arg;
34898ee3771SChris Diamand } seq[] = {
3492d5f5611SRichard Pospesel /*
3502d5f5611SRichard Pospesel * Intellimouse initialization sequence, to get 4-byte instead
3512d5f5611SRichard Pospesel * of 3-byte packets.
3522d5f5611SRichard Pospesel */
35398ee3771SChris Diamand { PSMOUSE_CMD_SETRATE, 0xC8 },
35498ee3771SChris Diamand { PSMOUSE_CMD_SETRATE, 0x64 },
35598ee3771SChris Diamand { PSMOUSE_CMD_SETRATE, 0x50 },
35698ee3771SChris Diamand { PSMOUSE_CMD_GETID, 0 },
3572d5f5611SRichard Pospesel { PSMOUSE_CMD_ENABLE, 0 },
3582d5f5611SRichard Pospesel /*
3592d5f5611SRichard Pospesel * BYD-specific initialization, which enables absolute mode and
3602d5f5611SRichard Pospesel * (if desired), the touchpad's built-in gesture detection.
3612d5f5611SRichard Pospesel */
3622d5f5611SRichard Pospesel { 0x10E2, 0x00 },
3632d5f5611SRichard Pospesel { 0x10E0, 0x02 },
3642d5f5611SRichard Pospesel /* The touchpad should reply with 4 seemingly-random bytes */
3652d5f5611SRichard Pospesel { 0x14E0, 0x01 },
3662d5f5611SRichard Pospesel /* Pairs of parameters and values. */
3672d5f5611SRichard Pospesel { BYD_CMD_SET_HANDEDNESS, 0x01 },
3682d5f5611SRichard Pospesel { BYD_CMD_SET_PHYSICAL_BUTTONS, 0x04 },
3692d5f5611SRichard Pospesel { BYD_CMD_SET_TAP, 0x02 },
3702d5f5611SRichard Pospesel { BYD_CMD_SET_ONE_FINGER_SCROLL, 0x04 },
3712d5f5611SRichard Pospesel { BYD_CMD_SET_ONE_FINGER_SCROLL_FUNC, 0x04 },
3722d5f5611SRichard Pospesel { BYD_CMD_SET_EDGE_MOTION, 0x01 },
3732d5f5611SRichard Pospesel { BYD_CMD_SET_PALM_CHECK, 0x00 },
3742d5f5611SRichard Pospesel { BYD_CMD_SET_MULTITOUCH, 0x02 },
3752d5f5611SRichard Pospesel { BYD_CMD_SET_TWO_FINGER_SCROLL, 0x04 },
3762d5f5611SRichard Pospesel { BYD_CMD_SET_TWO_FINGER_SCROLL_FUNC, 0x04 },
3772d5f5611SRichard Pospesel { BYD_CMD_SET_LEFT_EDGE_REGION, 0x00 },
3782d5f5611SRichard Pospesel { BYD_CMD_SET_TOP_EDGE_REGION, 0x00 },
3792d5f5611SRichard Pospesel { BYD_CMD_SET_RIGHT_EDGE_REGION, 0x00 },
3802d5f5611SRichard Pospesel { BYD_CMD_SET_BOTTOM_EDGE_REGION, 0x00 },
3812d5f5611SRichard Pospesel { BYD_CMD_SET_ABSOLUTE_MODE, 0x02 },
3822d5f5611SRichard Pospesel /* Finalize initialization. */
3832d5f5611SRichard Pospesel { 0x10E0, 0x00 },
3842d5f5611SRichard Pospesel { 0x10E2, 0x01 },
38598ee3771SChris Diamand };
38698ee3771SChris Diamand
38798ee3771SChris Diamand for (i = 0; i < ARRAY_SIZE(seq); ++i) {
3882d5f5611SRichard Pospesel memset(param, 0, sizeof(param));
38998ee3771SChris Diamand param[0] = seq[i].arg;
39098ee3771SChris Diamand if (ps2_command(ps2dev, param, seq[i].command))
3912d5f5611SRichard Pospesel return -EIO;
39298ee3771SChris Diamand }
39398ee3771SChris Diamand
3942d5f5611SRichard Pospesel psmouse_set_state(psmouse, PSMOUSE_ACTIVATED);
39598ee3771SChris Diamand return 0;
39698ee3771SChris Diamand }
39798ee3771SChris Diamand
byd_reconnect(struct psmouse * psmouse)39898ee3771SChris Diamand static int byd_reconnect(struct psmouse *psmouse)
39998ee3771SChris Diamand {
40098ee3771SChris Diamand int retry = 0, error = 0;
40198ee3771SChris Diamand
40298ee3771SChris Diamand psmouse_dbg(psmouse, "Reconnect\n");
40398ee3771SChris Diamand do {
40498ee3771SChris Diamand psmouse_reset(psmouse);
40598ee3771SChris Diamand if (retry)
40698ee3771SChris Diamand ssleep(1);
40798ee3771SChris Diamand error = byd_detect(psmouse, 0);
40898ee3771SChris Diamand } while (error && ++retry < 3);
40998ee3771SChris Diamand
41098ee3771SChris Diamand if (error)
41198ee3771SChris Diamand return error;
41298ee3771SChris Diamand
41398ee3771SChris Diamand psmouse_dbg(psmouse, "Reconnected after %d attempts\n", retry);
41498ee3771SChris Diamand
41598ee3771SChris Diamand error = byd_reset_touchpad(psmouse);
41698ee3771SChris Diamand if (error) {
41798ee3771SChris Diamand psmouse_err(psmouse, "Unable to initialize device\n");
41898ee3771SChris Diamand return error;
41998ee3771SChris Diamand }
42098ee3771SChris Diamand
42198ee3771SChris Diamand return 0;
42298ee3771SChris Diamand }
42398ee3771SChris Diamand
byd_disconnect(struct psmouse * psmouse)4242d5f5611SRichard Pospesel static void byd_disconnect(struct psmouse *psmouse)
4252d5f5611SRichard Pospesel {
4262d5f5611SRichard Pospesel struct byd_data *priv = psmouse->private;
4272d5f5611SRichard Pospesel
4282d5f5611SRichard Pospesel if (priv) {
4292d5f5611SRichard Pospesel del_timer(&priv->timer);
4302d5f5611SRichard Pospesel kfree(psmouse->private);
4312d5f5611SRichard Pospesel psmouse->private = NULL;
4322d5f5611SRichard Pospesel }
4332d5f5611SRichard Pospesel }
4342d5f5611SRichard Pospesel
byd_detect(struct psmouse * psmouse,bool set_properties)4352d5f5611SRichard Pospesel int byd_detect(struct psmouse *psmouse, bool set_properties)
4362d5f5611SRichard Pospesel {
4372d5f5611SRichard Pospesel struct ps2dev *ps2dev = &psmouse->ps2dev;
4382d5f5611SRichard Pospesel u8 param[4] = {0x03, 0x00, 0x00, 0x00};
4392d5f5611SRichard Pospesel
4402d5f5611SRichard Pospesel if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES))
4412d5f5611SRichard Pospesel return -1;
4422d5f5611SRichard Pospesel if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES))
4432d5f5611SRichard Pospesel return -1;
4442d5f5611SRichard Pospesel if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES))
4452d5f5611SRichard Pospesel return -1;
4462d5f5611SRichard Pospesel if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES))
4472d5f5611SRichard Pospesel return -1;
4482d5f5611SRichard Pospesel if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO))
4492d5f5611SRichard Pospesel return -1;
4502d5f5611SRichard Pospesel
4512d5f5611SRichard Pospesel if (param[1] != 0x03 || param[2] != 0x64)
4522d5f5611SRichard Pospesel return -ENODEV;
4532d5f5611SRichard Pospesel
4542d5f5611SRichard Pospesel psmouse_dbg(psmouse, "BYD touchpad detected\n");
4552d5f5611SRichard Pospesel
4562d5f5611SRichard Pospesel if (set_properties) {
4572d5f5611SRichard Pospesel psmouse->vendor = "BYD";
4582d5f5611SRichard Pospesel psmouse->name = "TouchPad";
4592d5f5611SRichard Pospesel }
4602d5f5611SRichard Pospesel
4612d5f5611SRichard Pospesel return 0;
4622d5f5611SRichard Pospesel }
4632d5f5611SRichard Pospesel
byd_init(struct psmouse * psmouse)46498ee3771SChris Diamand int byd_init(struct psmouse *psmouse)
46598ee3771SChris Diamand {
46698ee3771SChris Diamand struct input_dev *dev = psmouse->dev;
4672d5f5611SRichard Pospesel struct byd_data *priv;
46898ee3771SChris Diamand
46998ee3771SChris Diamand if (psmouse_reset(psmouse))
47098ee3771SChris Diamand return -EIO;
47198ee3771SChris Diamand
47298ee3771SChris Diamand if (byd_reset_touchpad(psmouse))
47398ee3771SChris Diamand return -EIO;
47498ee3771SChris Diamand
4752d5f5611SRichard Pospesel priv = kzalloc(sizeof(*priv), GFP_KERNEL);
4762d5f5611SRichard Pospesel if (!priv)
4772d5f5611SRichard Pospesel return -ENOMEM;
4782d5f5611SRichard Pospesel
479ce23cbc8SKees Cook priv->psmouse = psmouse;
480ce23cbc8SKees Cook timer_setup(&priv->timer, byd_clear_touch, 0);
4812d5f5611SRichard Pospesel
4822d5f5611SRichard Pospesel psmouse->private = priv;
4832d5f5611SRichard Pospesel psmouse->disconnect = byd_disconnect;
48498ee3771SChris Diamand psmouse->reconnect = byd_reconnect;
48598ee3771SChris Diamand psmouse->protocol_handler = byd_process_byte;
48698ee3771SChris Diamand psmouse->pktsize = 4;
48798ee3771SChris Diamand psmouse->resync_time = 0;
48898ee3771SChris Diamand
4892d5f5611SRichard Pospesel __set_bit(INPUT_PROP_POINTER, dev->propbit);
4902d5f5611SRichard Pospesel /* Touchpad */
4912d5f5611SRichard Pospesel __set_bit(BTN_TOUCH, dev->keybit);
4922d5f5611SRichard Pospesel __set_bit(BTN_TOOL_FINGER, dev->keybit);
4932d5f5611SRichard Pospesel /* Buttons */
4942d5f5611SRichard Pospesel __set_bit(BTN_LEFT, dev->keybit);
4952d5f5611SRichard Pospesel __set_bit(BTN_RIGHT, dev->keybit);
4962d5f5611SRichard Pospesel __clear_bit(BTN_MIDDLE, dev->keybit);
4972d5f5611SRichard Pospesel
4982d5f5611SRichard Pospesel /* Absolute position */
4992d5f5611SRichard Pospesel __set_bit(EV_ABS, dev->evbit);
5002d5f5611SRichard Pospesel input_set_abs_params(dev, ABS_X, 0, BYD_PAD_WIDTH, 0, 0);
5012d5f5611SRichard Pospesel input_set_abs_params(dev, ABS_Y, 0, BYD_PAD_HEIGHT, 0, 0);
5022d5f5611SRichard Pospesel input_abs_set_res(dev, ABS_X, BYD_PAD_RESOLUTION);
5032d5f5611SRichard Pospesel input_abs_set_res(dev, ABS_Y, BYD_PAD_RESOLUTION);
5042d5f5611SRichard Pospesel /* No relative support */
5052d5f5611SRichard Pospesel __clear_bit(EV_REL, dev->evbit);
5062d5f5611SRichard Pospesel __clear_bit(REL_X, dev->relbit);
5072d5f5611SRichard Pospesel __clear_bit(REL_Y, dev->relbit);
50898ee3771SChris Diamand
50998ee3771SChris Diamand return 0;
51098ee3771SChris Diamand }
511