Skip to content
Permalink
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
chilledHamza authored and intel-lab-lkp committed Feb 10, 2021
1 parent 7a6a53b commit 958fb71223bb82ea01edbcbf4970af1d888b1050
Show file tree
Hide file tree
Showing 6 changed files with 365 additions and 1 deletion.
@@ -184,6 +184,16 @@ config MOUSE_PS2_FOCALTECH

If unsure, say Y.

config MOUSE_PS2_FOCALTECH_V2
bool "FocalTech-v2 PS/2 mouse protocol extension" if EXPERT
default y
depends on MOUSE_PS2
help
Say Y here if you have a FocalTech-V2 PS/2 TouchPad connected to
your system.

If unsure, say Y.

config MOUSE_PS2_VMMOUSE
bool "Virtual mouse (vmmouse)"
depends on MOUSE_PS2 && X86 && HYPERVISOR_GUEST
@@ -26,7 +26,7 @@ obj-$(CONFIG_MOUSE_SYNAPTICS_USB) += synaptics_usb.o
obj-$(CONFIG_MOUSE_VSXXXAA) += vsxxxaa.o

cyapatp-objs := cyapa.o cyapa_gen3.o cyapa_gen5.o cyapa_gen6.o
psmouse-objs := psmouse-base.o synaptics.o focaltech.o
psmouse-objs := psmouse-base.o synaptics.o focaltech.o focaltech_v2.o

psmouse-$(CONFIG_MOUSE_PS2_ALPS) += alps.o
psmouse-$(CONFIG_MOUSE_PS2_BYD) += byd.o
@@ -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 */
@@ -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

0 comments on commit 958fb71

Please sign in to comment.