]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
Input: Add driver for PixArt PS/2 touchpad
authorBinbin Zhou <zhoubinbin@loongson.cn>
Tue, 23 Jul 2024 18:28:31 +0000 (11:28 -0700)
committerDmitry Torokhov <dmitry.torokhov@gmail.com>
Wed, 24 Jul 2024 04:27:18 +0000 (21:27 -0700)
This patch introduces a driver for the PixArt PS/2 touchpad, which
supports both clickpad and touchpad types.

At the same time, we extended the single data packet length to 16,
because according to the current PixArt hardware and FW design, we need
11 bytes/15 bytes to represent the complete three-finger/four-finger data.

Co-developed-by: Jon Xie <jon_xie@pixart.com>
Signed-off-by: Jon Xie <jon_xie@pixart.com>
Co-developed-by: Jay Lee <jay_lee@pixart.com>
Signed-off-by: Jay Lee <jay_lee@pixart.com>
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
Link: https://lore.kernel.org/r/20240704125243.3633569-1-zhoubinbin@loongson.cn
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
drivers/input/mouse/Kconfig
drivers/input/mouse/Makefile
drivers/input/mouse/pixart_ps2.c [new file with mode: 0644]
drivers/input/mouse/pixart_ps2.h [new file with mode: 0644]
drivers/input/mouse/psmouse-base.c
drivers/input/mouse/psmouse.h

index 833b643f06164920eda67705356e7c9321ac512b..8a27a20d04b01a9599cc14098b2f3084af22e0d9 100644 (file)
@@ -69,6 +69,18 @@ config MOUSE_PS2_LOGIPS2PP
 
          If unsure, say Y.
 
+config MOUSE_PS2_PIXART
+       bool "PixArt PS/2 touchpad protocol extension" if EXPERT
+       default y
+       depends on MOUSE_PS2
+       help
+         This driver supports the PixArt PS/2 touchpad found in some
+         laptops.
+         Say Y here if you have a PixArt PS/2 TouchPad connected to
+         your system.
+
+         If unsure, say Y.
+
 config MOUSE_PS2_SYNAPTICS
        bool "Synaptics PS/2 mouse protocol extension" if EXPERT
        default y
index a1336d5bee6f33dd1733b66e77d6229134f14fdb..56302955152915e4cf9edfe96236fd811b3e2269 100644 (file)
@@ -32,6 +32,7 @@ psmouse-$(CONFIG_MOUSE_PS2_ELANTECH)  += elantech.o
 psmouse-$(CONFIG_MOUSE_PS2_OLPC)       += hgpk.o
 psmouse-$(CONFIG_MOUSE_PS2_LOGIPS2PP)  += logips2pp.o
 psmouse-$(CONFIG_MOUSE_PS2_LIFEBOOK)   += lifebook.o
+psmouse-$(CONFIG_MOUSE_PS2_PIXART)     += pixart_ps2.o
 psmouse-$(CONFIG_MOUSE_PS2_SENTELIC)   += sentelic.o
 psmouse-$(CONFIG_MOUSE_PS2_TRACKPOINT) += trackpoint.o
 psmouse-$(CONFIG_MOUSE_PS2_TOUCHKIT)   += touchkit_ps2.o
diff --git a/drivers/input/mouse/pixart_ps2.c b/drivers/input/mouse/pixart_ps2.c
new file mode 100644 (file)
index 0000000..1993fc7
--- /dev/null
@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Pixart Touchpad Controller 1336U PS2 driver
+ *
+ * Author: Jon Xie <jon_xie@pixart.com>
+ *         Jay Lee <jay_lee@pixart.com>
+ * Further cleanup and restructuring by:
+ *         Binbin Zhou <zhoubinbin@loongson.cn>
+ *
+ * Copyright (C) 2021-2024 Pixart Imaging.
+ * Copyright (C) 2024 Loongson Technology Corporation Limited.
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/libps2.h>
+#include <linux/serio.h>
+#include <linux/slab.h>
+
+#include "pixart_ps2.h"
+
+static int pixart_read_tp_mode(struct ps2dev *ps2dev, u8 *mode)
+{
+       int error;
+       u8 param[1] = { 0 };
+
+       error = ps2_command(ps2dev, param, PIXART_CMD_REPORT_FORMAT);
+       if (error)
+               return error;
+
+       *mode = param[0] == 1 ? PIXART_MODE_ABS : PIXART_MODE_REL;
+
+       return 0;
+}
+
+static int pixart_read_tp_type(struct ps2dev *ps2dev, u8 *type)
+{
+       int error;
+       u8 param[3] = { 0 };
+
+       param[0] = 0x0a;
+       error = ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE);
+       if (error)
+               return error;
+
+       param[0] = 0x0;
+       error = ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES);
+       if (error)
+               return error;
+
+       error = ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES);
+       if (error)
+               return error;
+
+       error = ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES);
+       if (error)
+               return error;
+
+       param[0] = 0x03;
+       error = ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES);
+       if (error)
+               return error;
+
+       error = ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO);
+       if (error)
+               return error;
+
+       *type = param[0] == 0x0e ? PIXART_TYPE_TOUCHPAD : PIXART_TYPE_CLICKPAD;
+
+       return 0;
+}
+
+static void pixart_reset(struct psmouse *psmouse)
+{
+       ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS);
+
+       /* according to PixArt, 100ms is required for the upcoming reset */
+       msleep(100);
+       psmouse_reset(psmouse);
+}
+
+static void pixart_process_packet(struct psmouse *psmouse)
+{
+       struct pixart_data *priv = psmouse->private;
+       struct input_dev *dev = psmouse->dev;
+       const u8 *pkt = psmouse->packet;
+       unsigned int contact_cnt = FIELD_GET(CONTACT_CNT_MASK, pkt[0]);
+       unsigned int i, id, abs_x, abs_y;
+       bool tip;
+
+       for (i = 0; i < contact_cnt; i++) {
+               const u8 *p = &pkt[i * 3];
+
+               id = FIELD_GET(SLOT_ID_MASK, p[3]);
+               abs_y = FIELD_GET(ABS_Y_MASK, p[3]) << 8 | p[1];
+               abs_x = FIELD_GET(ABS_X_MASK, p[3]) << 8 | p[2];
+
+               if (i == PIXART_MAX_FINGERS - 1)
+                       tip = pkt[14] & BIT(1);
+               else
+                       tip = pkt[3 * contact_cnt + 1] & BIT(2 * i + 1);
+
+               input_mt_slot(dev, id);
+               if (input_mt_report_slot_state(dev, MT_TOOL_FINGER, tip)) {
+                       input_report_abs(dev, ABS_MT_POSITION_Y, abs_y);
+                       input_report_abs(dev, ABS_MT_POSITION_X, abs_x);
+               }
+       }
+
+       input_mt_sync_frame(dev);
+
+       if (priv->type == PIXART_TYPE_CLICKPAD) {
+               input_report_key(dev, BTN_LEFT, pkt[0] & 0x03);
+       } else {
+               input_report_key(dev, BTN_LEFT, pkt[0] & BIT(0));
+               input_report_key(dev, BTN_RIGHT, pkt[0] & BIT(1));
+       }
+
+       input_sync(dev);
+}
+
+static psmouse_ret_t pixart_protocol_handler(struct psmouse *psmouse)
+{
+       u8 *pkt = psmouse->packet;
+       u8 contact_cnt;
+
+       if ((pkt[0] & 0x8c) != 0x80)
+               return PSMOUSE_BAD_DATA;
+
+       contact_cnt = FIELD_GET(CONTACT_CNT_MASK, pkt[0]);
+       if (contact_cnt > PIXART_MAX_FINGERS)
+               return PSMOUSE_BAD_DATA;
+
+       if (contact_cnt == PIXART_MAX_FINGERS &&
+           psmouse->pktcnt < psmouse->pktsize) {
+               return PSMOUSE_GOOD_DATA;
+       }
+
+       if (contact_cnt == 0 && psmouse->pktcnt < 5)
+               return PSMOUSE_GOOD_DATA;
+
+       if (psmouse->pktcnt < 3 * contact_cnt + 2)
+               return PSMOUSE_GOOD_DATA;
+
+       pixart_process_packet(psmouse);
+
+       return PSMOUSE_FULL_PACKET;
+}
+
+static void pixart_disconnect(struct psmouse *psmouse)
+{
+       pixart_reset(psmouse);
+       kfree(psmouse->private);
+       psmouse->private = NULL;
+}
+
+static int pixart_reconnect(struct psmouse *psmouse)
+{
+       struct ps2dev *ps2dev = &psmouse->ps2dev;
+       u8 mode;
+       int error;
+
+       pixart_reset(psmouse);
+
+       error = pixart_read_tp_mode(ps2dev, &mode);
+       if (error)
+               return error;
+
+       if (mode != PIXART_MODE_ABS)
+               return -EIO;
+
+       error = ps2_command(ps2dev, NULL, PIXART_CMD_SWITCH_PROTO);
+       if (error)
+               return error;
+
+       return 0;
+}
+
+static int pixart_set_input_params(struct input_dev *dev,
+                                  struct pixart_data *priv)
+{
+       /* No relative support */
+       __clear_bit(EV_REL, dev->evbit);
+       __clear_bit(REL_X, dev->relbit);
+       __clear_bit(REL_Y, dev->relbit);
+       __clear_bit(BTN_MIDDLE, dev->keybit);
+
+       /* Buttons */
+       __set_bit(EV_KEY, dev->evbit);
+       __set_bit(BTN_LEFT, dev->keybit);
+       if (priv->type == PIXART_TYPE_CLICKPAD)
+               __set_bit(INPUT_PROP_BUTTONPAD, dev->propbit);
+       else
+               __set_bit(BTN_RIGHT, dev->keybit);
+
+       /* Absolute position */
+       input_set_abs_params(dev, ABS_X, 0, PIXART_PAD_WIDTH, 0, 0);
+       input_set_abs_params(dev, ABS_Y, 0, PIXART_PAD_HEIGHT, 0, 0);
+
+       input_set_abs_params(dev, ABS_MT_POSITION_X,
+                            0, PIXART_PAD_WIDTH, 0, 0);
+       input_set_abs_params(dev, ABS_MT_POSITION_Y,
+                            0, PIXART_PAD_HEIGHT, 0, 0);
+
+       return input_mt_init_slots(dev, PIXART_MAX_FINGERS, INPUT_MT_POINTER);
+}
+
+static int pixart_query_hardware(struct ps2dev *ps2dev, u8 *mode, u8 *type)
+{
+       int error;
+
+       error = pixart_read_tp_type(ps2dev, type);
+       if (error)
+               return error;
+
+       error = pixart_read_tp_mode(ps2dev, mode);
+       if (error)
+               return error;
+
+       return 0;
+}
+
+int pixart_detect(struct psmouse *psmouse, bool set_properties)
+{
+       u8 type;
+       int error;
+
+       pixart_reset(psmouse);
+
+       error = pixart_read_tp_type(&psmouse->ps2dev, &type);
+       if (error)
+               return error;
+
+       if (set_properties) {
+               psmouse->vendor = "PixArt";
+               psmouse->name = (type == PIXART_TYPE_TOUCHPAD) ?
+                               "touchpad" : "clickpad";
+       }
+
+       return 0;
+}
+
+int pixart_init(struct psmouse *psmouse)
+{
+       int error;
+       struct pixart_data *priv;
+
+       priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       psmouse->private = priv;
+       pixart_reset(psmouse);
+
+       error = pixart_query_hardware(&psmouse->ps2dev,
+                                     &priv->mode, &priv->type);
+       if (error) {
+               psmouse_err(psmouse, "init: Unable to query PixArt touchpad hardware.\n");
+               goto err_exit;
+       }
+
+       /* Relative mode follows standard PS/2 mouse protocol */
+       if (priv->mode != PIXART_MODE_ABS) {
+               error = -EIO;
+               goto err_exit;
+       }
+
+       /* Set absolute mode */
+       error = ps2_command(&psmouse->ps2dev, NULL, PIXART_CMD_SWITCH_PROTO);
+       if (error) {
+               psmouse_err(psmouse, "init: Unable to initialize PixArt absolute mode.\n");
+               goto err_exit;
+       }
+
+       error = pixart_set_input_params(psmouse->dev, priv);
+       if (error) {
+               psmouse_err(psmouse, "init: Unable to set input params.\n");
+               goto err_exit;
+       }
+
+       psmouse->pktsize = 15;
+       psmouse->protocol_handler = pixart_protocol_handler;
+       psmouse->disconnect = pixart_disconnect;
+       psmouse->reconnect = pixart_reconnect;
+       psmouse->cleanup = pixart_reset;
+       /* resync is not supported yet */
+       psmouse->resync_time = 0;
+
+       return 0;
+
+err_exit:
+       pixart_reset(psmouse);
+       kfree(priv);
+       psmouse->private = NULL;
+       return error;
+}
diff --git a/drivers/input/mouse/pixart_ps2.h b/drivers/input/mouse/pixart_ps2.h
new file mode 100644 (file)
index 0000000..47a1d04
--- /dev/null
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef _PIXART_PS2_H
+#define _PIXART_PS2_H
+
+#include "psmouse.h"
+
+#define PIXART_PAD_WIDTH               1023
+#define PIXART_PAD_HEIGHT              579
+#define PIXART_MAX_FINGERS             4
+
+#define PIXART_CMD_REPORT_FORMAT       0x01d8
+#define PIXART_CMD_SWITCH_PROTO                0x00de
+
+#define PIXART_MODE_REL                        0
+#define PIXART_MODE_ABS                        1
+
+#define PIXART_TYPE_CLICKPAD           0
+#define PIXART_TYPE_TOUCHPAD           1
+
+#define CONTACT_CNT_MASK               GENMASK(6, 4)
+
+#define SLOT_ID_MASK                   GENMASK(2, 0)
+#define ABS_Y_MASK                     GENMASK(5, 4)
+#define ABS_X_MASK                     GENMASK(7, 6)
+
+struct pixart_data {
+       u8 mode;
+       u8 type;
+       int x_max;
+       int y_max;
+};
+
+int pixart_detect(struct psmouse *psmouse, bool set_properties);
+int pixart_init(struct psmouse *psmouse);
+
+#endif  /* _PIXART_PS2_H */
index a2c9f7144864e4725e377c1ad904e7dc6f873e9b..5a4defe9cf325c0f32ba3adb2129475c373cc518 100644 (file)
@@ -36,6 +36,7 @@
 #include "focaltech.h"
 #include "vmmouse.h"
 #include "byd.h"
+#include "pixart_ps2.h"
 
 #define DRIVER_DESC    "PS/2 mouse driver"
 
@@ -905,6 +906,15 @@ static const struct psmouse_protocol psmouse_protocols[] = {
                .detect         = byd_detect,
                .init           = byd_init,
        },
+#endif
+#ifdef CONFIG_MOUSE_PS2_PIXART
+       {
+               .type           = PSMOUSE_PIXART,
+               .name           = "PixArtPS/2",
+               .alias          = "pixart",
+               .detect         = pixart_detect,
+               .init           = pixart_init,
+       },
 #endif
        {
                .type           = PSMOUSE_AUTO,
@@ -1172,6 +1182,13 @@ static int psmouse_extensions(struct psmouse *psmouse,
                        return ret;
        }
 
+       /* Try PixArt touchpad */
+       if (max_proto > PSMOUSE_IMEX &&
+           psmouse_try_protocol(psmouse, PSMOUSE_PIXART, &max_proto,
+                                set_properties, true)) {
+               return PSMOUSE_PIXART;
+       }
+
        if (max_proto > PSMOUSE_IMEX) {
                if (psmouse_try_protocol(psmouse, PSMOUSE_GENPS,
                                         &max_proto, set_properties, true))
index 4d8acfe0d82aa3017413628d5a8ba8f917ec080f..23f7fa7243cb599b52dd9494769a2b5dedfc81cb 100644 (file)
@@ -69,6 +69,7 @@ enum psmouse_type {
        PSMOUSE_BYD,
        PSMOUSE_SYNAPTICS_SMBUS,
        PSMOUSE_ELANTECH_SMBUS,
+       PSMOUSE_PIXART,
        PSMOUSE_AUTO            /* This one should always be last */
 };
 
@@ -94,7 +95,7 @@ struct psmouse {
        const char *vendor;
        const char *name;
        const struct psmouse_protocol *protocol;
-       unsigned char packet[8];
+       unsigned char packet[16];
        unsigned char badbyte;
        unsigned char pktcnt;
        unsigned char pktsize;