cregit-Linux how code gets into the kernel

Release 4.11 drivers/input/mouse/elantech.c

/*
 * Elantech Touchpad driver (v6)
 *
 * Copyright (C) 2007-2009 Arjan Opmeer <arjan@opmeer.net>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published
 * by the Free Software Foundation.
 *
 * Trademarks are the property of their respective owners.
 */

#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/serio.h>
#include <linux/libps2.h>
#include <asm/unaligned.h>
#include "psmouse.h"
#include "elantech.h"


#define elantech_debug(fmt, ...)					\
	do {                                                            \
                if (etd->debug)                                         \
                        psmouse_printk(KERN_DEBUG, psmouse,             \
                                        fmt, ##__VA_ARGS__);            \
        } while (0)

/*
 * Send a Synaptics style sliced query command
 */

static int synaptics_send_cmd(struct psmouse *psmouse, unsigned char c, unsigned char *param) { if (psmouse_sliced_command(psmouse, c) || ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETINFO)) { psmouse_err(psmouse, "%s query 0x%02x failed.\n", __func__, c); return -1; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Arjan Opmeer5590.16%150.00%
Dmitry Torokhov69.84%150.00%
Total61100.00%2100.00%

/* * V3 and later support this fast command */
static int elantech_send_cmd(struct psmouse *psmouse, unsigned char c, unsigned char *param) { struct ps2dev *ps2dev = &psmouse->ps2dev; if (ps2_command(ps2dev, NULL, ETP_PS2_CUSTOM_COMMAND) || ps2_command(ps2dev, NULL, c) || ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) { psmouse_err(psmouse, "%s query 0x%02x failed.\n", __func__, c); return -1; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
JJ Ding79100.00%1100.00%
Total79100.00%1100.00%

/* * A retrying version of ps2_command */
static int elantech_ps2_command(struct psmouse *psmouse, unsigned char *param, int command) { struct ps2dev *ps2dev = &psmouse->ps2dev; struct elantech_data *etd = psmouse->private; int rc; int tries = ETP_PS2_COMMAND_TRIES; do { rc = ps2_command(ps2dev, param, command); if (rc == 0) break; tries--; elantech_debug("retrying ps2 command 0x%02x (%d).\n", command, tries); msleep(ETP_PS2_COMMAND_DELAY); } while (tries > 0); if (rc) psmouse_err(psmouse, "ps2 command 0x%02x failed.\n", command); return rc; }

Contributors

PersonTokensPropCommitsCommitProp
Arjan Opmeer10295.33%133.33%
Dmitry Torokhov54.67%266.67%
Total107100.00%3100.00%

/* * Send an Elantech style special command to read a value from a register */
static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg, unsigned char *val) { struct elantech_data *etd = psmouse->private; unsigned char param[3]; int rc = 0; if (reg < 0x07 || reg > 0x26) return -1; if (reg > 0x11 && reg < 0x20) return -1; switch (etd->hw_version) { case 1: if (psmouse_sliced_command(psmouse, ETP_REGISTER_READ) || psmouse_sliced_command(psmouse, reg) || ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETINFO)) { rc = -1; } break; case 2: if (elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || elantech_ps2_command(psmouse, NULL, ETP_REGISTER_READ) || elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || elantech_ps2_command(psmouse, NULL, reg) || elantech_ps2_command(psmouse, param, PSMOUSE_CMD_GETINFO)) { rc = -1; } break; case 3 ... 4: if (elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || elantech_ps2_command(psmouse, NULL, ETP_REGISTER_READWRITE) || elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || elantech_ps2_command(psmouse, NULL, reg) || elantech_ps2_command(psmouse, param, PSMOUSE_CMD_GETINFO)) { rc = -1; } break; } if (rc) psmouse_err(psmouse, "failed to read register 0x%02x.\n", reg); else if (etd->hw_version != 4) *val = param[0]; else *val = param[1]; return rc; }

Contributors

PersonTokensPropCommitsCommitProp
Arjan Opmeer19369.93%120.00%
JJ Ding7928.62%240.00%
Dmitry Torokhov41.45%240.00%
Total276100.00%5100.00%

/* * Send an Elantech style special command to write a register with a value */
static int elantech_write_reg(struct psmouse *psmouse, unsigned char reg, unsigned char val) { struct elantech_data *etd = psmouse->private; int rc = 0; if (reg < 0x07 || reg > 0x26) return -1; if (reg > 0x11 && reg < 0x20) return -1; switch (etd->hw_version) { case 1: if (psmouse_sliced_command(psmouse, ETP_REGISTER_WRITE) || psmouse_sliced_command(psmouse, reg) || psmouse_sliced_command(psmouse, val) || ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE11)) { rc = -1; } break; case 2: if (elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || elantech_ps2_command(psmouse, NULL, ETP_REGISTER_WRITE) || elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || elantech_ps2_command(psmouse, NULL, reg) || elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || elantech_ps2_command(psmouse, NULL, val) || elantech_ps2_command(psmouse, NULL, PSMOUSE_CMD_SETSCALE11)) { rc = -1; } break; case 3: if (elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || elantech_ps2_command(psmouse, NULL, ETP_REGISTER_READWRITE) || elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || elantech_ps2_command(psmouse, NULL, reg) || elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || elantech_ps2_command(psmouse, NULL, val) || elantech_ps2_command(psmouse, NULL, PSMOUSE_CMD_SETSCALE11)) { rc = -1; } break; case 4: if (elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || elantech_ps2_command(psmouse, NULL, ETP_REGISTER_READWRITE) || elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || elantech_ps2_command(psmouse, NULL, reg) || elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || elantech_ps2_command(psmouse, NULL, ETP_REGISTER_READWRITE) || elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || elantech_ps2_command(psmouse, NULL, val) || elantech_ps2_command(psmouse, NULL, PSMOUSE_CMD_SETSCALE11)) { rc = -1; } break; } if (rc) psmouse_err(psmouse, "failed to write register 0x%02x with value 0x%02x.\n", reg, val); return rc; }

Contributors

PersonTokensPropCommitsCommitProp
Arjan Opmeer20453.83%120.00%
JJ Ding17145.12%240.00%
Dmitry Torokhov41.06%240.00%
Total379100.00%5100.00%

/* * Dump a complete mouse movement packet to the syslog */
static void elantech_packet_dump(struct psmouse *psmouse) { psmouse_printk(KERN_DEBUG, psmouse, "PS/2 packet [%*ph]\n", psmouse->pktsize, psmouse->packet); }

Contributors

PersonTokensPropCommitsCommitProp
Arjan Opmeer1346.43%133.33%
Dmitry Torokhov1346.43%133.33%
Benjamin Tissoires27.14%133.33%
Total28100.00%3100.00%

/* * Interpret complete data packets and report absolute mode input events for * hardware version 1. (4 byte packets) */
static void elantech_report_absolute_v1(struct psmouse *psmouse) { struct input_dev *dev = psmouse->dev; struct elantech_data *etd = psmouse->private; unsigned char *packet = psmouse->packet; int fingers; if (etd->fw_version < 0x020000) { /* * byte 0: D U p1 p2 1 p3 R L * byte 1: f 0 th tw x9 x8 y9 y8 */ fingers = ((packet[1] & 0x80) >> 7) + ((packet[1] & 0x30) >> 4); } else { /* * byte 0: n1 n0 p2 p1 1 p3 R L * byte 1: 0 0 0 0 x9 x8 y9 y8 */ fingers = (packet[0] & 0xc0) >> 6; } if (etd->jumpy_cursor) { if (fingers != 1) { etd->single_finger_reports = 0; } else if (etd->single_finger_reports < 2) { /* Discard first 2 reports of one finger, bogus */ etd->single_finger_reports++; elantech_debug("discarding packet\n"); return; } } input_report_key(dev, BTN_TOUCH, fingers != 0); /* * byte 2: x7 x6 x5 x4 x3 x2 x1 x0 * byte 3: y7 y6 y5 y4 y3 y2 y1 y0 */ if (fingers) { input_report_abs(dev, ABS_X, ((packet[1] & 0x0c) << 6) | packet[2]); input_report_abs(dev, ABS_Y, etd->y_max - (((packet[1] & 0x03) << 8) | packet[3])); } input_report_key(dev, BTN_TOOL_FINGER, fingers == 1); input_report_key(dev, BTN_TOOL_DOUBLETAP, fingers == 2); input_report_key(dev, BTN_TOOL_TRIPLETAP, fingers == 3); input_report_key(dev, BTN_LEFT, packet[0] & 0x01); input_report_key(dev, BTN_RIGHT, packet[0] & 0x02); if (etd->fw_version < 0x020000 && (etd->capabilities[0] & ETP_CAP_HAS_ROCKER)) { /* rocker up */ input_report_key(dev, BTN_FORWARD, packet[0] & 0x40); /* rocker down */ input_report_key(dev, BTN_BACK, packet[0] & 0x80); } input_sync(dev); }

Contributors

PersonTokensPropCommitsCommitProp
Arjan Opmeer29187.39%228.57%
Éric Piel267.81%114.29%
Dmitry Torokhov72.10%228.57%
JJ Ding61.80%114.29%
Florian Ragwitz30.90%114.29%
Total333100.00%7100.00%


static void elantech_set_slot(struct input_dev *dev, int slot, bool active, unsigned int x, unsigned int y) { input_mt_slot(dev, slot); input_mt_report_slot_state(dev, MT_TOOL_FINGER, active); if (active) { input_report_abs(dev, ABS_MT_POSITION_X, x); input_report_abs(dev, ABS_MT_POSITION_Y, y); } }

Contributors

PersonTokensPropCommitsCommitProp
Éric Piel65100.00%1100.00%
Total65100.00%1100.00%

/* x1 < x2 and y1 < y2 when two fingers, x = y = 0 when not pressed */
static void elantech_report_semi_mt_data(struct input_dev *dev, unsigned int num_fingers, unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2) { elantech_set_slot(dev, 0, num_fingers != 0, x1, y1); elantech_set_slot(dev, 1, num_fingers >= 2, x2, y2); }

Contributors

PersonTokensPropCommitsCommitProp
Éric Piel6098.36%150.00%
Benjamin Tissoires11.64%150.00%
Total61100.00%2100.00%

/* * Interpret complete data packets and report absolute mode input events for * hardware version 2. (6 byte packets) */
static void elantech_report_absolute_v2(struct psmouse *psmouse) { struct elantech_data *etd = psmouse->private; struct input_dev *dev = psmouse->dev; unsigned char *packet = psmouse->packet; unsigned int fingers, x1 = 0, y1 = 0, x2 = 0, y2 = 0; unsigned int width = 0, pres = 0; /* byte 0: n1 n0 . . . . R L */ fingers = (packet[0] & 0xc0) >> 6; switch (fingers) { case 3: /* * Same as one finger, except report of more than 3 fingers: * byte 3: n4 . w1 w0 . . . . */ if (packet[3] & 0x80) fingers = 4; /* pass through... */ case 1: /* * byte 1: . . . . x11 x10 x9 x8 * byte 2: x7 x6 x5 x4 x4 x2 x1 x0 */ x1 = ((packet[1] & 0x0f) << 8) | packet[2]; /* * byte 4: . . . . y11 y10 y9 y8 * byte 5: y7 y6 y5 y4 y3 y2 y1 y0 */ y1 = etd->y_max - (((packet[4] & 0x0f) << 8) | packet[5]); pres = (packet[1] & 0xf0) | ((packet[4] & 0xf0) >> 4); width = ((packet[0] & 0x30) >> 2) | ((packet[3] & 0x30) >> 4); break; case 2: /* * The coordinate of each finger is reported separately * with a lower resolution for two finger touches: * byte 0: . . ay8 ax8 . . . . * byte 1: ax7 ax6 ax5 ax4 ax3 ax2 ax1 ax0 */ x1 = (((packet[0] & 0x10) << 4) | packet[1]) << 2; /* byte 2: ay7 ay6 ay5 ay4 ay3 ay2 ay1 ay0 */ y1 = etd->y_max - ((((packet[0] & 0x20) << 3) | packet[2]) << 2); /* * byte 3: . . by8 bx8 . . . . * byte 4: bx7 bx6 bx5 bx4 bx3 bx2 bx1 bx0 */ x2 = (((packet[3] & 0x10) << 4) | packet[4]) << 2; /* byte 5: by7 by8 by5 by4 by3 by2 by1 by0 */ y2 = etd->y_max - ((((packet[3] & 0x20) << 3) | packet[5]) << 2); /* Unknown so just report sensible values */ pres = 127; width = 7; break; } input_report_key(dev, BTN_TOUCH, fingers != 0); if (fingers != 0) { input_report_abs(dev, ABS_X, x1); input_report_abs(dev, ABS_Y, y1); } elantech_report_semi_mt_data(dev, fingers, x1, y1, x2, y2); input_report_key(dev, BTN_TOOL_FINGER, fingers == 1); input_report_key(dev, BTN_TOOL_DOUBLETAP, fingers == 2); input_report_key(dev, BTN_TOOL_TRIPLETAP, fingers == 3); input_report_key(dev, BTN_TOOL_QUADTAP, fingers == 4); input_report_key(dev, BTN_LEFT, packet[0] & 0x01); input_report_key(dev, BTN_RIGHT, packet[0] & 0x02); if (etd->reports_pressure) { input_report_abs(dev, ABS_PRESSURE, pres); input_report_abs(dev, ABS_TOOL_WIDTH, width); } input_sync(dev); }

Contributors

PersonTokensPropCommitsCommitProp
Arjan Opmeer26954.90%112.50%
Éric Piel15231.02%337.50%
JJ Ding6112.45%337.50%
Florian Ragwitz81.63%112.50%
Total490100.00%8100.00%


static void elantech_report_trackpoint(struct psmouse *psmouse, int packet_type) { /* * byte 0: 0 0 sx sy 0 M R L * byte 1:~sx 0 0 0 0 0 0 0 * byte 2:~sy 0 0 0 0 0 0 0 * byte 3: 0 0 ~sy ~sx 0 1 1 0 * byte 4: x7 x6 x5 x4 x3 x2 x1 x0 * byte 5: y7 y6 y5 y4 y3 y2 y1 y0 * * x and y are written in two's complement spread * over 9 bits with sx/sy the relative top bit and * x7..x0 and y7..y0 the lower bits. * The sign of y is opposite to what the input driver * expects for a relative movement */ struct elantech_data *etd = psmouse->private; struct input_dev *tp_dev = etd->tp_dev; unsigned char *packet = psmouse->packet; int x, y; u32 t; t = get_unaligned_le32(&packet[0]); switch (t & ~7U) { case 0x06000030U: case 0x16008020U: case 0x26800010U: case 0x36808000U: x = packet[4] - (int)((packet[1]^0x80) << 1); y = (int)((packet[2]^0x80) << 1) - packet[5]; input_report_key(tp_dev, BTN_LEFT, packet[0] & 0x01); input_report_key(tp_dev, BTN_RIGHT, packet[0] & 0x02); input_report_key(tp_dev, BTN_MIDDLE, packet[0] & 0x04); input_report_rel(tp_dev, REL_X, x); input_report_rel(tp_dev, REL_Y, y); input_sync(tp_dev); break; default: /* Dump unexpected packet sequences if debug=1 (default) */ if (etd->debug == 1) elantech_packet_dump(psmouse); break; } }

Contributors

PersonTokensPropCommitsCommitProp
Ulrik De Bie210100.00%1100.00%
Total210100.00%1100.00%

/* * Interpret complete data packets and report absolute mode input events for * hardware version 3. (12 byte packets for two fingers) */
static void elantech_report_absolute_v3(struct psmouse *psmouse, int packet_type) { struct input_dev *dev = psmouse->dev; struct elantech_data *etd = psmouse->private; unsigned char *packet = psmouse->packet; unsigned int fingers = 0, x1 = 0, y1 = 0, x2 = 0, y2 = 0; unsigned int width = 0, pres = 0; /* byte 0: n1 n0 . . . . R L */ fingers = (packet[0] & 0xc0) >> 6; switch (fingers) { case 3: case 1: /* * byte 1: . . . . x11 x10 x9 x8 * byte 2: x7 x6 x5 x4 x4 x2 x1 x0 */ x1 = ((packet[1] & 0x0f) << 8) | packet[2]; /* * byte 4: . . . . y11 y10 y9 y8 * byte 5: y7 y6 y5 y4 y3 y2 y1 y0 */ y1 = etd->y_max - (((packet[4] & 0x0f) << 8) | packet[5]); break; case 2: if (packet_type == PACKET_V3_HEAD) { /* * byte 1: . . . . ax11 ax10 ax9 ax8 * byte 2: ax7 ax6 ax5 ax4 ax3 ax2 ax1 ax0 */ etd->mt[0].x = ((packet[1] & 0x0f) << 8) | packet[2]; /* * byte 4: . . . . ay11 ay10 ay9 ay8 * byte 5: ay7 ay6 ay5 ay4 ay3 ay2 ay1 ay0 */ etd->mt[0].y = etd->y_max - (((packet[4] & 0x0f) << 8) | packet[5]); /* * wait for next packet */ return; } /* packet_type == PACKET_V3_TAIL */ x1 = etd->mt[0].x; y1 = etd->mt[0].y; x2 = ((packet[1] & 0x0f) << 8) | packet[2]; y2 = etd->y_max - (((packet[4] & 0x0f) << 8) | packet[5]); break; } pres = (packet[1] & 0xf0) | ((packet[4] & 0xf0) >> 4); width = ((packet[0] & 0x30) >> 2) | ((packet[3] & 0x30) >> 4); input_report_key(dev, BTN_TOUCH, fingers != 0); if (fingers != 0) { input_report_abs(dev, ABS_X, x1); input_report_abs(dev, ABS_Y, y1); } elantech_report_semi_mt_data(dev, fingers, x1, y1, x2, y2); input_report_key(dev, BTN_TOOL_FINGER, fingers == 1); input_report_key(dev, BTN_TOOL_DOUBLETAP, fingers == 2); input_report_key(dev, BTN_TOOL_TRIPLETAP, fingers == 3); /* For clickpads map both buttons to BTN_LEFT */ if (etd->fw_version & 0x001000) { input_report_key(dev, BTN_LEFT, packet[0] & 0x03); } else { input_report_key(dev, BTN_LEFT, packet[0] & 0x01); input_report_key(dev, BTN_RIGHT, packet[0] & 0x02); } input_report_abs(dev, ABS_PRESSURE, pres); input_report_abs(dev, ABS_TOOL_WIDTH, width); input_sync(dev); }

Contributors

PersonTokensPropCommitsCommitProp
JJ Ding42383.10%250.00%
Arjan Opmeer5811.39%125.00%
Hans de Goede285.50%125.00%
Total509100.00%4100.00%


static void elantech_input_sync_v4(struct psmouse *psmouse) { struct input_dev *dev = psmouse->dev; struct elantech_data *etd = psmouse->private; unsigned char *packet = psmouse->packet; /* For clickpads map both buttons to BTN_LEFT */ if (etd->fw_version & 0x001000) { input_report_key(dev, BTN_LEFT, packet[0] & 0x03); } else { input_report_key(dev, BTN_LEFT, packet[0] & 0x01); input_report_key(dev, BTN_RIGHT, packet[0] & 0x02); input_report_key(dev, BTN_MIDDLE, packet[0] & 0x04); } input_mt_report_pointer_emulation(dev, true); input_sync(dev); }

Contributors

PersonTokensPropCommitsCommitProp
JJ Ding5545.83%240.00%
Hans de Goede5142.50%240.00%
Ulrik De Bie1411.67%120.00%
Total120100.00%5100.00%


static void process_packet_status_v4(struct psmouse *psmouse) { struct input_dev *dev = psmouse->dev; unsigned char *packet = psmouse->packet; unsigned fingers; int i; /* notify finger state change */ fingers = packet[1] & 0x1f; for (i = 0; i < ETP_MAX_FINGERS; i++) { if ((fingers & (1 << i)) == 0) { input_mt_slot(dev, i); input_mt_report_slot_state(dev, MT_TOOL_FINGER, false); } } elantech_input_sync_v4(psmouse); }

Contributors

PersonTokensPropCommitsCommitProp
JJ Ding97100.00%2100.00%
Total97100.00%2100.00%


static void process_packet_head_v4(struct psmouse *psmouse) { struct input_dev *dev = psmouse->dev; struct elantech_data *etd = psmouse->private; unsigned char *packet = psmouse->packet; int id = ((packet[3] & 0xe0) >> 5) - 1; int pres, traces; if (id < 0) return; etd->mt[id].x = ((packet[1] & 0x0f) << 8) | packet[2]; etd->mt[id].y = etd->y_max - (((packet[4] & 0x0f) << 8) | packet[5]); pres = (packet[1] & 0xf0) | ((packet[4] & 0xf0) >> 4); traces = (packet[0] & 0xf0