forked from torvalds/linux
Permalink
Show file tree
Hide file tree
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Input: psmouse - add support for FocalTech PS/2 Protocol v2
Haier Y11C Laptop have FocalTech PS/2 Touchpad, BIOS Device Name is FTE0001. This device have different protocol than exisiting FocalTech PS/2 Driver. This commit adds a basic multitouch-capable driver. Some of the protcol is still unknown (just like the other FocalTech driver) Device can only be identified with PNP ID. Signed-off-by: Hamza Farooq <0xA6C4@gmail.com>
- Loading branch information
1 parent
7a6a53b
commit 958fb71223bb82ea01edbcbf4970af1d888b1050
Showing
6 changed files
with
365 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,265 @@ | ||
| /* SPDX-License-Identifier: GPL-2.0-or-later */ | ||
| /* | ||
| * Focaltech v2 TouchPad PS/2 mouse driver | ||
| * | ||
| * Copyright (c) 2021 Hamza Farooq <0xA6C4@gmail.com> | ||
| */ | ||
|
|
||
|
|
||
| #include <linux/device.h> | ||
| #include <linux/libps2.h> | ||
| #include <linux/input/mt.h> | ||
| #include <linux/serio.h> | ||
| #include <linux/slab.h> | ||
| #include "psmouse.h" | ||
| #include "focaltech_v2.h" | ||
|
|
||
| static const struct fte_command switch_protocol[] = { | ||
| {PSMOUSE_CMD_SETRATE, 0xea}, | ||
| {PSMOUSE_CMD_SETRATE, 0xed}, | ||
| {PSMOUSE_CMD_ENABLE, 0x00}, | ||
| }; | ||
|
|
||
| static const char *const focaltech_pnp_ids[] = { | ||
| "FTE0001", | ||
| NULL}; | ||
|
|
||
| int focaltech_v2_detect(struct psmouse *psmouse, bool set_properties) | ||
| { | ||
| if (!psmouse_matches_pnp_id(psmouse, focaltech_pnp_ids)) | ||
| return -ENODEV; | ||
|
|
||
| if (set_properties) { | ||
| psmouse->vendor = "FocalTech"; | ||
| psmouse->name = "Touchpad V2"; | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| #ifdef CONFIG_MOUSE_PS2_FOCALTECH_V2 | ||
|
|
||
| static void focaltech_report_state(struct psmouse *psmouse) | ||
| { | ||
| struct focaltech_data *priv = psmouse->private; | ||
| struct focaltech_hw_state *state = &priv->state; | ||
| struct input_dev *dev = psmouse->dev; | ||
| int i; | ||
|
|
||
| for (i = 0; i < FOCALTECH_MAX_FINGERS; i++) { | ||
| struct focaltech_finger_state *finger = &state->fingers[i]; | ||
|
|
||
| input_mt_slot(dev, i); | ||
| input_mt_report_slot_state(dev, MT_TOOL_FINGER, finger->valid); | ||
| if (finger->valid) { | ||
| input_report_abs(dev, ABS_MT_POSITION_X, finger->x); | ||
| input_report_abs(dev, ABS_MT_POSITION_Y, finger->y); | ||
| input_report_abs(dev, ABS_MT_TOUCH_MAJOR, finger->major); | ||
| input_report_abs(dev, ABS_MT_TOUCH_MINOR, finger->minor); | ||
| input_report_abs(dev, ABS_MT_PRESSURE, finger->pressure); | ||
| } | ||
| } | ||
| input_mt_sync_frame(dev); | ||
| input_report_key(dev, BTN_LEFT, state->left); | ||
| input_report_key(dev, BTN_RIGHT, state->right); | ||
| input_mt_report_finger_count(dev, state->fingerCount); | ||
| input_sync(dev); | ||
| } | ||
|
|
||
| static void focaltech_process_packet(struct psmouse *psmouse) | ||
| { | ||
| unsigned char *packet = psmouse->packet; | ||
| struct focaltech_data *priv = psmouse->private; | ||
| struct focaltech_hw_state *state = &priv->state; | ||
| int i, j; | ||
|
|
||
| if (!priv->isReadNext) { | ||
| for (i = 0; i < 8; i++) | ||
| priv->lastDeviceData[i] = packet[i]; | ||
| for (i = 8; i < 16; i++) | ||
| priv->lastDeviceData[i] = 0xff; | ||
| state->fingerCount = (int)(packet[4] & 3) + ((packet[4] & 48) >> 2); | ||
| if ((state->fingerCount > 2) && (packet[0] != 0xff && packet[1] != 0xff && packet[2] != 0xff && packet[3] != 0xff) && (packet[0] & 48) != 32) | ||
| priv->isReadNext = true; | ||
| } else { | ||
| priv->isReadNext = false; | ||
| for (i = 8; i < 16; i++) | ||
| priv->lastDeviceData[i] = packet[i - 8]; | ||
| } | ||
| if (!priv->isReadNext) { | ||
| if (!((priv->lastDeviceData[0] == 0xff) && (priv->lastDeviceData[1] == 0xff) && (priv->lastDeviceData[2] == 0xff) && (priv->lastDeviceData[3] == 0xff))) { | ||
| if ((priv->lastDeviceData[0] & 1) == 1) | ||
| state->left = true; | ||
| else | ||
| state->left = false; | ||
| if ((priv->lastDeviceData[0] & 2) == 2) | ||
| state->right = true; | ||
| else | ||
| state->right = false; | ||
| if ((priv->lastDeviceData[0] & 48) == 16) { | ||
| for (i = 0; i < 4; i++) { | ||
| j = i * 4; | ||
| if (!((priv->lastDeviceData[j + 1] == 0xff) && (priv->lastDeviceData[j + 2] == 0xff) && (priv->lastDeviceData[j + 3] == 0xff))) { | ||
| state->fingers[i].minor = priv->lastDeviceData[j + 1]; | ||
| state->fingers[i].major = priv->lastDeviceData[j + 2]; | ||
| state->fingers[i].pressure = priv->lastDeviceData[j + 3] * 2; | ||
| if (state->fingers[i].pressure > MAX_PRESSURE) | ||
| state->fingers[i].pressure = MAX_PRESSURE; | ||
| } | ||
| } | ||
| } else { | ||
| for (i = 0; i < 4; i++) { | ||
| j = i * 4; | ||
| if (!((priv->lastDeviceData[j + 1] == 0xff) && (priv->lastDeviceData[j + 2] == 0xff) && (priv->lastDeviceData[j + 3] == 0xff))) { | ||
| state->fingers[i].valid = true; | ||
| state->fingers[i].x = (priv->lastDeviceData[j + 1] << 4) + ((priv->lastDeviceData[j + 3] & 240) >> 4); | ||
| state->fingers[i].y = (priv->lastDeviceData[j + 2] << 4) + (priv->lastDeviceData[j + 3] & 15); | ||
| } else | ||
| state->fingers[i].valid = false; | ||
| } | ||
| } | ||
| if (state->fingerCount == 0) | ||
| for (i = 0; i < 4; i++) | ||
| state->fingers[i].valid = false; | ||
| } | ||
| } | ||
| focaltech_report_state(psmouse); | ||
| } | ||
|
|
||
| static psmouse_ret_t focaltech_process_byte(struct psmouse *psmouse) | ||
| { | ||
| if (psmouse->pktcnt >= 8) { /* packet received */ | ||
| focaltech_process_packet(psmouse); | ||
| return PSMOUSE_FULL_PACKET; | ||
| } | ||
| return PSMOUSE_GOOD_DATA; | ||
| } | ||
|
|
||
| static int focaltech_switch_protocol(struct psmouse *psmouse) | ||
| { | ||
| struct ps2dev *ps2dev = &psmouse->ps2dev; | ||
| unsigned char param[4]; | ||
| size_t i; | ||
|
|
||
| for (i = 0; i < ARRAY_SIZE(switch_protocol); ++i) { | ||
| memset(param, 0, sizeof(param)); | ||
| param[0] = switch_protocol[i].data; | ||
| if (ps2_command(ps2dev, param, switch_protocol[i].command)) | ||
| return -EIO; | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| static void focaltech_reset(struct psmouse *psmouse) | ||
| { | ||
| ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS); | ||
| psmouse_reset(psmouse); | ||
| } | ||
|
|
||
| static void focaltech_disconnect(struct psmouse *psmouse) | ||
| { | ||
| focaltech_reset(psmouse); | ||
| kfree(psmouse->private); | ||
| psmouse->private = NULL; | ||
| } | ||
|
|
||
| static int focaltech_reconnect(struct psmouse *psmouse) | ||
| { | ||
| int error; | ||
|
|
||
| focaltech_reset(psmouse); | ||
|
|
||
| error = focaltech_switch_protocol(psmouse); | ||
| if (error) { | ||
| psmouse_err(psmouse, "Unable to initialize the device\n"); | ||
| return error; | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| static void focaltech_set_input_params(struct psmouse *psmouse) | ||
| { | ||
| struct input_dev *dev = psmouse->dev; | ||
|
|
||
| /* | ||
| * Undo part of setup done for us by psmouse core since touchpad | ||
| * is not a relative device. | ||
| */ | ||
| __clear_bit(EV_REL, dev->evbit); | ||
| __clear_bit(REL_X, dev->relbit); | ||
| __clear_bit(REL_Y, dev->relbit); | ||
|
|
||
| /* | ||
| * Now set up our capabilities. | ||
| */ | ||
| __set_bit(EV_ABS, dev->evbit); | ||
| input_set_abs_params(dev, ABS_MT_POSITION_X, 0, MAX_X, 0, 0); | ||
| input_set_abs_params(dev, ABS_MT_POSITION_Y, 0, MAX_Y, 0, 0); | ||
| input_set_abs_params(dev, ABS_MT_PRESSURE, 0, MAX_PRESSURE, 0, 0); | ||
| input_set_abs_params(dev, ABS_MT_TOUCH_MINOR, 0, MAX_MAJOR, 0, 0); | ||
| input_set_abs_params(dev, ABS_MT_TOUCH_MAJOR, 0, MAX_MINOR, 0, 0); | ||
| input_abs_set_res(dev, ABS_MT_POSITION_X, RESOLUTION); | ||
| input_abs_set_res(dev, ABS_MT_POSITION_Y, RESOLUTION); | ||
| input_mt_init_slots(dev, FOCALTECH_MAX_FINGERS, INPUT_MT_POINTER); | ||
| } | ||
|
|
||
| static void focaltech_set_resolution(struct psmouse *psmouse, unsigned int resolution) | ||
| { | ||
| /* not supported yet */ | ||
| } | ||
|
|
||
| static void focaltech_set_rate(struct psmouse *psmouse, unsigned int rate) | ||
| { | ||
| /* not supported yet */ | ||
| } | ||
|
|
||
| static void focaltech_set_scale(struct psmouse *psmouse, enum psmouse_scale scale) | ||
| { | ||
| /* not supported yet */ | ||
| } | ||
|
|
||
| int focaltech_v2_init(struct psmouse *psmouse) | ||
| { | ||
| struct focaltech_data *priv; | ||
| int error; | ||
|
|
||
| psmouse->private = priv = kzalloc(sizeof(struct focaltech_data), GFP_KERNEL); | ||
| if (!priv) | ||
| return -ENOMEM; | ||
|
|
||
| focaltech_reset(psmouse); | ||
|
|
||
| error = focaltech_switch_protocol(psmouse); | ||
| if (error) { | ||
| psmouse_err(psmouse, "Unable to initialize the device\n"); | ||
| goto fail; | ||
| } | ||
|
|
||
| focaltech_set_input_params(psmouse); | ||
|
|
||
| psmouse->protocol_handler = focaltech_process_byte; | ||
| psmouse->pktsize = 8; | ||
| psmouse->disconnect = focaltech_disconnect; | ||
| psmouse->reconnect = focaltech_reconnect; | ||
| psmouse->cleanup = focaltech_reset; | ||
| /* resync is not supported yet */ | ||
| psmouse->resync_time = 0; | ||
| /* | ||
| * rate/resolution/scale changes are not supported yet, and | ||
| * the generic implementations of these functions seem to | ||
| * confuse some touchpads | ||
| */ | ||
| psmouse->set_resolution = focaltech_set_resolution; | ||
| psmouse->set_rate = focaltech_set_rate; | ||
| psmouse->set_scale = focaltech_set_scale; | ||
|
|
||
| return 0; | ||
|
|
||
| fail: | ||
| focaltech_reset(psmouse); | ||
| kfree(priv); | ||
| return error; | ||
| } | ||
| #endif /* CONFIG_MOUSE_PS2_FOCALTECH_V2 */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| /* SPDX-License-Identifier: GPL-2.0-or-later */ | ||
| /* | ||
| * Focaltech v2 TouchPad PS/2 mouse driver | ||
| * | ||
| * Copyright (c) 2021 Hamza Farooq <0xA6C4@gmail.com> | ||
| */ | ||
|
|
||
| #ifndef _FOCALTECH_V2_H | ||
| #define _FOCALTECH_V2_H | ||
|
|
||
| #define FOCALTECH_MAX_FINGERS 4 | ||
| #define MAX_X 0x08E0 /* 2272 */ | ||
| #define MAX_Y 0x03E0 /* 992 */ | ||
| #define RESOLUTION 26 /* 87mm x 38mm */ | ||
| #define MAX_MAJOR 10 | ||
| #define MAX_MINOR 10 | ||
| #define MAX_PRESSURE 127 | ||
|
|
||
| struct fte_command { | ||
| int command; | ||
| unsigned char data; | ||
| }; | ||
|
|
||
| struct focaltech_finger_state { | ||
| int x; | ||
| int y; | ||
| int major; | ||
| int minor; | ||
| int pressure; | ||
| bool valid; | ||
| }; | ||
|
|
||
| struct focaltech_hw_state { | ||
| struct focaltech_finger_state fingers[FOCALTECH_MAX_FINGERS]; | ||
| int fingerCount; | ||
| bool left; | ||
| bool right; | ||
| }; | ||
|
|
||
| struct focaltech_data { | ||
| bool isReadNext; | ||
| int lastDeviceData[16]; | ||
| struct focaltech_hw_state state; | ||
| }; | ||
|
|
||
| #ifdef CONFIG_MOUSE_PS2_FOCALTECH_V2 | ||
| int focaltech_v2_detect(struct psmouse *psmouse, bool set_properties); | ||
| int focaltech_v2_init(struct psmouse *psmouse); | ||
| #else | ||
| static inline int focaltech_v2_init(struct psmouse *psmouse) | ||
| { | ||
| return -ENOSYS; | ||
| } | ||
| #endif | ||
|
|
||
| #endif |
Oops, something went wrong.