mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-04 04:06:26 +00:00
c472d33bcb
Commit8bccf667f6
("Input: cypress_ps2 - report timeouts when reading command status") uncovered an existing problem with cypress_ps2 driver: it tries waiting on a PS/2 device waitqueue without using the rest of libps2. Unfortunately without it nobody signals wakeup for the waiting process, and each "extended" command was timing out. But the rest of the code simply did not notice it. Fix this by switching from homegrown way of sending request to get command response and reading it to standard ps2_command() which does the right thing. Reported-by: Woody Suwalski <terraluna977@gmail.com> Tested-by: Woody Suwalski <terraluna977@gmail.com> Fixes:8bccf667f6
("Input: cypress_ps2 - report timeouts when reading command status") Link: https://lore.kernel.org/r/a8252e0f-dab4-ef5e-2aa1-407a6f4c7204@gmail.com Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
676 lines
18 KiB
C
676 lines
18 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Cypress Trackpad PS/2 mouse driver
|
|
*
|
|
* Copyright (c) 2012 Cypress Semiconductor Corporation.
|
|
*
|
|
* Author:
|
|
* Dudley Du <dudl@cypress.com>
|
|
*
|
|
* Additional contributors include:
|
|
* Kamal Mostafa <kamal@canonical.com>
|
|
* Kyle Fazzari <git@status.e4ward.com>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/serio.h>
|
|
#include <linux/libps2.h>
|
|
#include <linux/input.h>
|
|
#include <linux/input/mt.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/wait.h>
|
|
|
|
#include "cypress_ps2.h"
|
|
|
|
#undef CYTP_DEBUG_VERBOSE /* define this and DEBUG for more verbose dump */
|
|
|
|
static void cypress_set_packet_size(struct psmouse *psmouse, unsigned int n)
|
|
{
|
|
struct cytp_data *cytp = psmouse->private;
|
|
cytp->pkt_size = n;
|
|
}
|
|
|
|
static const u8 cytp_rate[] = {10, 20, 40, 60, 100, 200};
|
|
static const u8 cytp_resolution[] = {0x00, 0x01, 0x02, 0x03};
|
|
|
|
static int cypress_ps2_sendbyte(struct psmouse *psmouse, u8 cmd)
|
|
{
|
|
struct ps2dev *ps2dev = &psmouse->ps2dev;
|
|
int error;
|
|
|
|
error = ps2_sendbyte(ps2dev, cmd, CYTP_CMD_TIMEOUT);
|
|
if (error) {
|
|
psmouse_dbg(psmouse,
|
|
"sending command 0x%02x failed, resp 0x%02x, error %d\n",
|
|
cmd, ps2dev->nak, error);
|
|
return error;
|
|
}
|
|
|
|
#ifdef CYTP_DEBUG_VERBOSE
|
|
psmouse_dbg(psmouse, "sending command 0x%02x succeeded\n", cmd);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cypress_ps2_ext_cmd(struct psmouse *psmouse, u8 prefix, u8 nibble)
|
|
{
|
|
struct ps2dev *ps2dev = &psmouse->ps2dev;
|
|
int tries = CYTP_PS2_CMD_TRIES;
|
|
int rc;
|
|
|
|
ps2_begin_command(ps2dev);
|
|
|
|
do {
|
|
/*
|
|
* Send extension command byte (0xE8 or 0xF3).
|
|
* If sending the command fails, send recovery command
|
|
* to make the device return to the ready state.
|
|
*/
|
|
rc = cypress_ps2_sendbyte(psmouse, prefix);
|
|
if (rc == -EAGAIN) {
|
|
rc = cypress_ps2_sendbyte(psmouse, 0x00);
|
|
if (rc == -EAGAIN)
|
|
rc = cypress_ps2_sendbyte(psmouse, 0x0a);
|
|
}
|
|
|
|
if (!rc) {
|
|
rc = cypress_ps2_sendbyte(psmouse, nibble);
|
|
if (rc == -EAGAIN)
|
|
rc = cypress_ps2_sendbyte(psmouse, nibble);
|
|
|
|
if (!rc)
|
|
break;
|
|
}
|
|
} while (--tries > 0);
|
|
|
|
ps2_end_command(ps2dev);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static bool cypress_verify_cmd_state(struct psmouse *psmouse, u8 cmd, u8* param)
|
|
{
|
|
bool rate_match = false;
|
|
bool resolution_match = false;
|
|
int i;
|
|
|
|
/* callers will do further checking. */
|
|
if (cmd == CYTP_CMD_READ_CYPRESS_ID ||
|
|
cmd == CYTP_CMD_STANDARD_MODE ||
|
|
cmd == CYTP_CMD_READ_TP_METRICS)
|
|
return true;
|
|
|
|
if ((~param[0] & DFLT_RESP_BITS_VALID) == DFLT_RESP_BITS_VALID &&
|
|
(param[0] & DFLT_RESP_BIT_MODE) == DFLT_RESP_STREAM_MODE) {
|
|
for (i = 0; i < sizeof(cytp_resolution); i++)
|
|
if (cytp_resolution[i] == param[1])
|
|
resolution_match = true;
|
|
|
|
for (i = 0; i < sizeof(cytp_rate); i++)
|
|
if (cytp_rate[i] == param[2])
|
|
rate_match = true;
|
|
|
|
if (resolution_match && rate_match)
|
|
return true;
|
|
}
|
|
|
|
psmouse_dbg(psmouse, "verify cmd state failed.\n");
|
|
return false;
|
|
}
|
|
|
|
static int cypress_send_ext_cmd(struct psmouse *psmouse, u8 cmd, u8 *param)
|
|
{
|
|
u8 cmd_prefix = PSMOUSE_CMD_SETRES & 0xff;
|
|
unsigned int resp_size = cmd == CYTP_CMD_READ_TP_METRICS ? 8 : 3;
|
|
unsigned int ps2_cmd = (PSMOUSE_CMD_GETINFO & 0xff) | (resp_size << 8);
|
|
int tries = CYTP_PS2_CMD_TRIES;
|
|
int error;
|
|
|
|
psmouse_dbg(psmouse, "send extension cmd 0x%02x, [%d %d %d %d]\n",
|
|
cmd, DECODE_CMD_AA(cmd), DECODE_CMD_BB(cmd),
|
|
DECODE_CMD_CC(cmd), DECODE_CMD_DD(cmd));
|
|
|
|
do {
|
|
cypress_ps2_ext_cmd(psmouse, cmd_prefix, DECODE_CMD_DD(cmd));
|
|
cypress_ps2_ext_cmd(psmouse, cmd_prefix, DECODE_CMD_CC(cmd));
|
|
cypress_ps2_ext_cmd(psmouse, cmd_prefix, DECODE_CMD_BB(cmd));
|
|
cypress_ps2_ext_cmd(psmouse, cmd_prefix, DECODE_CMD_AA(cmd));
|
|
|
|
error = ps2_command(&psmouse->ps2dev, param, ps2_cmd);
|
|
if (error) {
|
|
psmouse_dbg(psmouse, "Command 0x%02x failed: %d\n",
|
|
cmd, error);
|
|
} else {
|
|
psmouse_dbg(psmouse,
|
|
"Command 0x%02x response data (0x): %*ph\n",
|
|
cmd, resp_size, param);
|
|
|
|
if (cypress_verify_cmd_state(psmouse, cmd, param))
|
|
return 0;
|
|
}
|
|
} while (--tries > 0);
|
|
|
|
return -EIO;
|
|
}
|
|
|
|
int cypress_detect(struct psmouse *psmouse, bool set_properties)
|
|
{
|
|
u8 param[3];
|
|
|
|
if (cypress_send_ext_cmd(psmouse, CYTP_CMD_READ_CYPRESS_ID, param))
|
|
return -ENODEV;
|
|
|
|
/* Check for Cypress Trackpad signature bytes: 0x33 0xCC */
|
|
if (param[0] != 0x33 || param[1] != 0xCC)
|
|
return -ENODEV;
|
|
|
|
if (set_properties) {
|
|
psmouse->vendor = "Cypress";
|
|
psmouse->name = "Trackpad";
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cypress_read_fw_version(struct psmouse *psmouse)
|
|
{
|
|
struct cytp_data *cytp = psmouse->private;
|
|
u8 param[3];
|
|
|
|
if (cypress_send_ext_cmd(psmouse, CYTP_CMD_READ_CYPRESS_ID, param))
|
|
return -ENODEV;
|
|
|
|
/* Check for Cypress Trackpad signature bytes: 0x33 0xCC */
|
|
if (param[0] != 0x33 || param[1] != 0xCC)
|
|
return -ENODEV;
|
|
|
|
cytp->fw_version = param[2] & FW_VERSION_MASX;
|
|
cytp->tp_metrics_supported = (param[2] & TP_METRICS_MASK) ? 1 : 0;
|
|
|
|
/*
|
|
* Trackpad fw_version 11 (in Dell XPS12) yields a bogus response to
|
|
* CYTP_CMD_READ_TP_METRICS so do not try to use it. LP: #1103594.
|
|
*/
|
|
if (cytp->fw_version >= 11)
|
|
cytp->tp_metrics_supported = 0;
|
|
|
|
psmouse_dbg(psmouse, "cytp->fw_version = %d\n", cytp->fw_version);
|
|
psmouse_dbg(psmouse, "cytp->tp_metrics_supported = %d\n",
|
|
cytp->tp_metrics_supported);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cypress_read_tp_metrics(struct psmouse *psmouse)
|
|
{
|
|
struct cytp_data *cytp = psmouse->private;
|
|
u8 param[8];
|
|
|
|
/* set default values for tp metrics. */
|
|
cytp->tp_width = CYTP_DEFAULT_WIDTH;
|
|
cytp->tp_high = CYTP_DEFAULT_HIGH;
|
|
cytp->tp_max_abs_x = CYTP_ABS_MAX_X;
|
|
cytp->tp_max_abs_y = CYTP_ABS_MAX_Y;
|
|
cytp->tp_min_pressure = CYTP_MIN_PRESSURE;
|
|
cytp->tp_max_pressure = CYTP_MAX_PRESSURE;
|
|
cytp->tp_res_x = cytp->tp_max_abs_x / cytp->tp_width;
|
|
cytp->tp_res_y = cytp->tp_max_abs_y / cytp->tp_high;
|
|
|
|
if (!cytp->tp_metrics_supported)
|
|
return 0;
|
|
|
|
memset(param, 0, sizeof(param));
|
|
if (cypress_send_ext_cmd(psmouse, CYTP_CMD_READ_TP_METRICS, param) == 0) {
|
|
/* Update trackpad parameters. */
|
|
cytp->tp_max_abs_x = (param[1] << 8) | param[0];
|
|
cytp->tp_max_abs_y = (param[3] << 8) | param[2];
|
|
cytp->tp_min_pressure = param[4];
|
|
cytp->tp_max_pressure = param[5];
|
|
}
|
|
|
|
if (!cytp->tp_max_pressure ||
|
|
cytp->tp_max_pressure < cytp->tp_min_pressure ||
|
|
!cytp->tp_width || !cytp->tp_high ||
|
|
!cytp->tp_max_abs_x ||
|
|
cytp->tp_max_abs_x < cytp->tp_width ||
|
|
!cytp->tp_max_abs_y ||
|
|
cytp->tp_max_abs_y < cytp->tp_high)
|
|
return -EINVAL;
|
|
|
|
cytp->tp_res_x = cytp->tp_max_abs_x / cytp->tp_width;
|
|
cytp->tp_res_y = cytp->tp_max_abs_y / cytp->tp_high;
|
|
|
|
#ifdef CYTP_DEBUG_VERBOSE
|
|
psmouse_dbg(psmouse, "Dump trackpad hardware configuration as below:\n");
|
|
psmouse_dbg(psmouse, "cytp->tp_width = %d\n", cytp->tp_width);
|
|
psmouse_dbg(psmouse, "cytp->tp_high = %d\n", cytp->tp_high);
|
|
psmouse_dbg(psmouse, "cytp->tp_max_abs_x = %d\n", cytp->tp_max_abs_x);
|
|
psmouse_dbg(psmouse, "cytp->tp_max_abs_y = %d\n", cytp->tp_max_abs_y);
|
|
psmouse_dbg(psmouse, "cytp->tp_min_pressure = %d\n", cytp->tp_min_pressure);
|
|
psmouse_dbg(psmouse, "cytp->tp_max_pressure = %d\n", cytp->tp_max_pressure);
|
|
psmouse_dbg(psmouse, "cytp->tp_res_x = %d\n", cytp->tp_res_x);
|
|
psmouse_dbg(psmouse, "cytp->tp_res_y = %d\n", cytp->tp_res_y);
|
|
|
|
psmouse_dbg(psmouse, "tp_type_APA = %d\n",
|
|
(param[6] & TP_METRICS_BIT_APA) ? 1 : 0);
|
|
psmouse_dbg(psmouse, "tp_type_MTG = %d\n",
|
|
(param[6] & TP_METRICS_BIT_MTG) ? 1 : 0);
|
|
psmouse_dbg(psmouse, "tp_palm = %d\n",
|
|
(param[6] & TP_METRICS_BIT_PALM) ? 1 : 0);
|
|
psmouse_dbg(psmouse, "tp_stubborn = %d\n",
|
|
(param[6] & TP_METRICS_BIT_STUBBORN) ? 1 : 0);
|
|
psmouse_dbg(psmouse, "tp_1f_jitter = %d\n",
|
|
(param[6] & TP_METRICS_BIT_1F_JITTER) >> 2);
|
|
psmouse_dbg(psmouse, "tp_2f_jitter = %d\n",
|
|
(param[6] & TP_METRICS_BIT_2F_JITTER) >> 4);
|
|
psmouse_dbg(psmouse, "tp_1f_spike = %d\n",
|
|
param[7] & TP_METRICS_BIT_1F_SPIKE);
|
|
psmouse_dbg(psmouse, "tp_2f_spike = %d\n",
|
|
(param[7] & TP_METRICS_BIT_2F_SPIKE) >> 2);
|
|
psmouse_dbg(psmouse, "tp_abs_packet_format_set = %d\n",
|
|
(param[7] & TP_METRICS_BIT_ABS_PKT_FORMAT_SET) >> 4);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cypress_query_hardware(struct psmouse *psmouse)
|
|
{
|
|
int error;
|
|
|
|
error = cypress_read_fw_version(psmouse);
|
|
if (error)
|
|
return error;
|
|
|
|
error = cypress_read_tp_metrics(psmouse);
|
|
if (error)
|
|
return error;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cypress_set_absolute_mode(struct psmouse *psmouse)
|
|
{
|
|
struct cytp_data *cytp = psmouse->private;
|
|
u8 param[3];
|
|
int error;
|
|
|
|
error = cypress_send_ext_cmd(psmouse, CYTP_CMD_ABS_WITH_PRESSURE_MODE,
|
|
param);
|
|
if (error)
|
|
return error;
|
|
|
|
cytp->mode = (cytp->mode & ~CYTP_BIT_ABS_REL_MASK)
|
|
| CYTP_BIT_ABS_PRESSURE;
|
|
cypress_set_packet_size(psmouse, 5);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Reset trackpad device.
|
|
* This is also the default mode when trackpad powered on.
|
|
*/
|
|
static void cypress_reset(struct psmouse *psmouse)
|
|
{
|
|
struct cytp_data *cytp = psmouse->private;
|
|
|
|
cytp->mode = 0;
|
|
|
|
psmouse_reset(psmouse);
|
|
}
|
|
|
|
static int cypress_set_input_params(struct input_dev *input,
|
|
struct cytp_data *cytp)
|
|
{
|
|
int error;
|
|
|
|
if (!cytp->tp_res_x || !cytp->tp_res_y)
|
|
return -EINVAL;
|
|
|
|
__set_bit(EV_ABS, input->evbit);
|
|
input_set_abs_params(input, ABS_X, 0, cytp->tp_max_abs_x, 0, 0);
|
|
input_set_abs_params(input, ABS_Y, 0, cytp->tp_max_abs_y, 0, 0);
|
|
input_set_abs_params(input, ABS_PRESSURE,
|
|
cytp->tp_min_pressure, cytp->tp_max_pressure, 0, 0);
|
|
input_set_abs_params(input, ABS_TOOL_WIDTH, 0, 255, 0, 0);
|
|
|
|
/* finger position */
|
|
input_set_abs_params(input, ABS_MT_POSITION_X, 0, cytp->tp_max_abs_x, 0, 0);
|
|
input_set_abs_params(input, ABS_MT_POSITION_Y, 0, cytp->tp_max_abs_y, 0, 0);
|
|
input_set_abs_params(input, ABS_MT_PRESSURE, 0, 255, 0, 0);
|
|
|
|
error = input_mt_init_slots(input, CYTP_MAX_MT_SLOTS,
|
|
INPUT_MT_DROP_UNUSED | INPUT_MT_TRACK);
|
|
if (error)
|
|
return error;
|
|
|
|
__set_bit(INPUT_PROP_SEMI_MT, input->propbit);
|
|
|
|
input_abs_set_res(input, ABS_X, cytp->tp_res_x);
|
|
input_abs_set_res(input, ABS_Y, cytp->tp_res_y);
|
|
|
|
input_abs_set_res(input, ABS_MT_POSITION_X, cytp->tp_res_x);
|
|
input_abs_set_res(input, ABS_MT_POSITION_Y, cytp->tp_res_y);
|
|
|
|
__set_bit(BTN_TOUCH, input->keybit);
|
|
__set_bit(BTN_TOOL_FINGER, input->keybit);
|
|
__set_bit(BTN_TOOL_DOUBLETAP, input->keybit);
|
|
__set_bit(BTN_TOOL_TRIPLETAP, input->keybit);
|
|
__set_bit(BTN_TOOL_QUADTAP, input->keybit);
|
|
__set_bit(BTN_TOOL_QUINTTAP, input->keybit);
|
|
|
|
__clear_bit(EV_REL, input->evbit);
|
|
__clear_bit(REL_X, input->relbit);
|
|
__clear_bit(REL_Y, input->relbit);
|
|
|
|
__set_bit(EV_KEY, input->evbit);
|
|
__set_bit(BTN_LEFT, input->keybit);
|
|
__set_bit(BTN_RIGHT, input->keybit);
|
|
__set_bit(BTN_MIDDLE, input->keybit);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cypress_get_finger_count(u8 header_byte)
|
|
{
|
|
u8 bits6_7;
|
|
int finger_count;
|
|
|
|
bits6_7 = header_byte >> 6;
|
|
finger_count = bits6_7 & 0x03;
|
|
|
|
if (finger_count == 1)
|
|
return 1;
|
|
|
|
if (header_byte & ABS_HSCROLL_BIT) {
|
|
/* HSCROLL gets added on to 0 finger count. */
|
|
switch (finger_count) {
|
|
case 0: return 4;
|
|
case 2: return 5;
|
|
default:
|
|
/* Invalid contact (e.g. palm). Ignore it. */
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return finger_count;
|
|
}
|
|
|
|
|
|
static int cypress_parse_packet(struct psmouse *psmouse,
|
|
struct cytp_data *cytp,
|
|
struct cytp_report_data *report_data)
|
|
{
|
|
u8 *packet = psmouse->packet;
|
|
u8 header_byte = packet[0];
|
|
|
|
memset(report_data, 0, sizeof(struct cytp_report_data));
|
|
|
|
report_data->contact_cnt = cypress_get_finger_count(header_byte);
|
|
report_data->tap = (header_byte & ABS_MULTIFINGER_TAP) ? 1 : 0;
|
|
|
|
if (report_data->contact_cnt == 1) {
|
|
report_data->contacts[0].x =
|
|
((packet[1] & 0x70) << 4) | packet[2];
|
|
report_data->contacts[0].y =
|
|
((packet[1] & 0x07) << 8) | packet[3];
|
|
if (cytp->mode & CYTP_BIT_ABS_PRESSURE)
|
|
report_data->contacts[0].z = packet[4];
|
|
|
|
} else if (report_data->contact_cnt >= 2) {
|
|
report_data->contacts[0].x =
|
|
((packet[1] & 0x70) << 4) | packet[2];
|
|
report_data->contacts[0].y =
|
|
((packet[1] & 0x07) << 8) | packet[3];
|
|
if (cytp->mode & CYTP_BIT_ABS_PRESSURE)
|
|
report_data->contacts[0].z = packet[4];
|
|
|
|
report_data->contacts[1].x =
|
|
((packet[5] & 0xf0) << 4) | packet[6];
|
|
report_data->contacts[1].y =
|
|
((packet[5] & 0x0f) << 8) | packet[7];
|
|
if (cytp->mode & CYTP_BIT_ABS_PRESSURE)
|
|
report_data->contacts[1].z = report_data->contacts[0].z;
|
|
}
|
|
|
|
report_data->left = (header_byte & BTN_LEFT_BIT) ? 1 : 0;
|
|
report_data->right = (header_byte & BTN_RIGHT_BIT) ? 1 : 0;
|
|
|
|
/*
|
|
* This is only true if one of the mouse buttons were tapped. Make
|
|
* sure it doesn't turn into a click. The regular tap-to-click
|
|
* functionality will handle that on its own. If we don't do this,
|
|
* disabling tap-to-click won't affect the mouse button zones.
|
|
*/
|
|
if (report_data->tap)
|
|
report_data->left = 0;
|
|
|
|
#ifdef CYTP_DEBUG_VERBOSE
|
|
{
|
|
int i;
|
|
int n = report_data->contact_cnt;
|
|
psmouse_dbg(psmouse, "Dump parsed report data as below:\n");
|
|
psmouse_dbg(psmouse, "contact_cnt = %d\n",
|
|
report_data->contact_cnt);
|
|
if (n > CYTP_MAX_MT_SLOTS)
|
|
n = CYTP_MAX_MT_SLOTS;
|
|
for (i = 0; i < n; i++)
|
|
psmouse_dbg(psmouse, "contacts[%d] = {%d, %d, %d}\n", i,
|
|
report_data->contacts[i].x,
|
|
report_data->contacts[i].y,
|
|
report_data->contacts[i].z);
|
|
psmouse_dbg(psmouse, "left = %d\n", report_data->left);
|
|
psmouse_dbg(psmouse, "right = %d\n", report_data->right);
|
|
psmouse_dbg(psmouse, "middle = %d\n", report_data->middle);
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void cypress_process_packet(struct psmouse *psmouse, bool zero_pkt)
|
|
{
|
|
int i;
|
|
struct input_dev *input = psmouse->dev;
|
|
struct cytp_data *cytp = psmouse->private;
|
|
struct cytp_report_data report_data;
|
|
struct cytp_contact *contact;
|
|
struct input_mt_pos pos[CYTP_MAX_MT_SLOTS];
|
|
int slots[CYTP_MAX_MT_SLOTS];
|
|
int n;
|
|
|
|
cypress_parse_packet(psmouse, cytp, &report_data);
|
|
|
|
n = report_data.contact_cnt;
|
|
if (n > CYTP_MAX_MT_SLOTS)
|
|
n = CYTP_MAX_MT_SLOTS;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
contact = &report_data.contacts[i];
|
|
pos[i].x = contact->x;
|
|
pos[i].y = contact->y;
|
|
}
|
|
|
|
input_mt_assign_slots(input, slots, pos, n, 0);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
contact = &report_data.contacts[i];
|
|
input_mt_slot(input, slots[i]);
|
|
input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
|
|
input_report_abs(input, ABS_MT_POSITION_X, contact->x);
|
|
input_report_abs(input, ABS_MT_POSITION_Y, contact->y);
|
|
input_report_abs(input, ABS_MT_PRESSURE, contact->z);
|
|
}
|
|
|
|
input_mt_sync_frame(input);
|
|
|
|
input_mt_report_finger_count(input, report_data.contact_cnt);
|
|
|
|
input_report_key(input, BTN_LEFT, report_data.left);
|
|
input_report_key(input, BTN_RIGHT, report_data.right);
|
|
input_report_key(input, BTN_MIDDLE, report_data.middle);
|
|
|
|
input_sync(input);
|
|
}
|
|
|
|
static psmouse_ret_t cypress_validate_byte(struct psmouse *psmouse)
|
|
{
|
|
int contact_cnt;
|
|
int index = psmouse->pktcnt - 1;
|
|
u8 *packet = psmouse->packet;
|
|
struct cytp_data *cytp = psmouse->private;
|
|
|
|
if (index < 0 || index > cytp->pkt_size)
|
|
return PSMOUSE_BAD_DATA;
|
|
|
|
if (index == 0 && (packet[0] & 0xfc) == 0) {
|
|
/* call packet process for reporting finger leave. */
|
|
cypress_process_packet(psmouse, 1);
|
|
return PSMOUSE_FULL_PACKET;
|
|
}
|
|
|
|
/*
|
|
* Perform validation (and adjust packet size) based only on the
|
|
* first byte; allow all further bytes through.
|
|
*/
|
|
if (index != 0)
|
|
return PSMOUSE_GOOD_DATA;
|
|
|
|
/*
|
|
* If absolute/relative mode bit has not been set yet, just pass
|
|
* the byte through.
|
|
*/
|
|
if ((cytp->mode & CYTP_BIT_ABS_REL_MASK) == 0)
|
|
return PSMOUSE_GOOD_DATA;
|
|
|
|
if ((packet[0] & 0x08) == 0x08)
|
|
return PSMOUSE_BAD_DATA;
|
|
|
|
contact_cnt = cypress_get_finger_count(packet[0]);
|
|
if (cytp->mode & CYTP_BIT_ABS_NO_PRESSURE)
|
|
cypress_set_packet_size(psmouse, contact_cnt == 2 ? 7 : 4);
|
|
else
|
|
cypress_set_packet_size(psmouse, contact_cnt == 2 ? 8 : 5);
|
|
|
|
return PSMOUSE_GOOD_DATA;
|
|
}
|
|
|
|
static psmouse_ret_t cypress_protocol_handler(struct psmouse *psmouse)
|
|
{
|
|
struct cytp_data *cytp = psmouse->private;
|
|
|
|
if (psmouse->pktcnt >= cytp->pkt_size) {
|
|
cypress_process_packet(psmouse, 0);
|
|
return PSMOUSE_FULL_PACKET;
|
|
}
|
|
|
|
return cypress_validate_byte(psmouse);
|
|
}
|
|
|
|
static void cypress_set_rate(struct psmouse *psmouse, unsigned int rate)
|
|
{
|
|
struct cytp_data *cytp = psmouse->private;
|
|
u8 rate_param;
|
|
|
|
if (rate >= 80) {
|
|
psmouse->rate = 80;
|
|
cytp->mode |= CYTP_BIT_HIGH_RATE;
|
|
} else {
|
|
psmouse->rate = 40;
|
|
cytp->mode &= ~CYTP_BIT_HIGH_RATE;
|
|
}
|
|
|
|
rate_param = (u8)rate;
|
|
ps2_command(&psmouse->ps2dev, &rate_param, PSMOUSE_CMD_SETRATE);
|
|
}
|
|
|
|
static void cypress_disconnect(struct psmouse *psmouse)
|
|
{
|
|
cypress_reset(psmouse);
|
|
kfree(psmouse->private);
|
|
psmouse->private = NULL;
|
|
}
|
|
|
|
static int cypress_reconnect(struct psmouse *psmouse)
|
|
{
|
|
int tries = CYTP_PS2_CMD_TRIES;
|
|
int error;
|
|
|
|
do {
|
|
cypress_reset(psmouse);
|
|
error = cypress_detect(psmouse, false);
|
|
} while (error && (--tries > 0));
|
|
|
|
if (error) {
|
|
psmouse_err(psmouse, "Reconnect: unable to detect trackpad.\n");
|
|
return error;
|
|
}
|
|
|
|
error = cypress_set_absolute_mode(psmouse);
|
|
if (error) {
|
|
psmouse_err(psmouse, "Reconnect: Unable to initialize Cypress absolute mode.\n");
|
|
return error;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cypress_init(struct psmouse *psmouse)
|
|
{
|
|
struct cytp_data *cytp;
|
|
int error;
|
|
|
|
cytp = kzalloc(sizeof(*cytp), GFP_KERNEL);
|
|
if (!cytp)
|
|
return -ENOMEM;
|
|
|
|
psmouse->private = cytp;
|
|
psmouse->pktsize = 8;
|
|
|
|
cypress_reset(psmouse);
|
|
|
|
error = cypress_query_hardware(psmouse);
|
|
if (error) {
|
|
psmouse_err(psmouse, "Unable to query Trackpad hardware.\n");
|
|
goto err_exit;
|
|
}
|
|
|
|
error = cypress_set_absolute_mode(psmouse);
|
|
if (error) {
|
|
psmouse_err(psmouse, "init: Unable to initialize Cypress absolute mode.\n");
|
|
goto err_exit;
|
|
}
|
|
|
|
error = cypress_set_input_params(psmouse->dev, cytp);
|
|
if (error) {
|
|
psmouse_err(psmouse, "init: Unable to set input params.\n");
|
|
goto err_exit;
|
|
}
|
|
|
|
psmouse->model = 1;
|
|
psmouse->protocol_handler = cypress_protocol_handler;
|
|
psmouse->set_rate = cypress_set_rate;
|
|
psmouse->disconnect = cypress_disconnect;
|
|
psmouse->reconnect = cypress_reconnect;
|
|
psmouse->cleanup = cypress_reset;
|
|
psmouse->resync_time = 0;
|
|
|
|
return 0;
|
|
|
|
err_exit:
|
|
/*
|
|
* Reset Cypress Trackpad as a standard mouse. Then
|
|
* let psmouse driver communicating with it as default PS2 mouse.
|
|
*/
|
|
cypress_reset(psmouse);
|
|
|
|
psmouse->private = NULL;
|
|
kfree(cytp);
|
|
|
|
return error;
|
|
}
|