mirror of
https://github.com/torvalds/linux.git
synced 2026-04-22 08:44:02 -04:00
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>
301 lines
6.7 KiB
C
301 lines
6.7 KiB
C
// 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;
|
|
}
|