diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/input/mouse | |
parent | Initial commit. (diff) | |
download | linux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip |
Adding upstream version 4.19.249.upstream/4.19.249
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/input/mouse')
56 files changed, 33322 insertions, 0 deletions
diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig new file mode 100644 index 000000000..566a1e3aa --- /dev/null +++ b/drivers/input/mouse/Kconfig @@ -0,0 +1,460 @@ +# +# Mouse driver configuration +# +menuconfig INPUT_MOUSE + bool "Mice" + default y + help + Say Y here, and a list of supported mice will be displayed. + This option doesn't affect the kernel. + + If unsure, say Y. + +if INPUT_MOUSE + +config MOUSE_PS2 + tristate "PS/2 mouse" + default y + select SERIO + select SERIO_LIBPS2 + select SERIO_I8042 if ARCH_MIGHT_HAVE_PC_SERIO + select SERIO_GSCPS2 if GSC + help + Say Y here if you have a PS/2 mouse connected to your system. This + includes the standard 2 or 3-button PS/2 mouse, as well as PS/2 + mice with wheels and extra buttons, Microsoft, Logitech or Genius + compatible. + + Synaptics, ALPS or Elantech TouchPad users might be interested + in a specialized Xorg/XFree86 driver at: + <http://w1.894.telia.com/~u89404340/touchpad/index.html> + and a new version of GPM at: + <http://www.geocities.com/dt_or/gpm/gpm.html> + <http://xorg.freedesktop.org/archive/individual/driver/> + to take advantage of the advanced features of the touchpad. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called psmouse. + +config MOUSE_PS2_ALPS + bool "ALPS PS/2 mouse protocol extension" if EXPERT + default y + depends on MOUSE_PS2 + help + Say Y here if you have an ALPS PS/2 touchpad connected to + your system. + + If unsure, say Y. + +config MOUSE_PS2_BYD + bool "BYD PS/2 mouse protocol extension" if EXPERT + default y + depends on MOUSE_PS2 + help + Say Y here if you have a BYD PS/2 touchpad connected to + your system. + + If unsure, say Y. + +config MOUSE_PS2_LOGIPS2PP + bool "Logitech PS/2++ mouse protocol extension" if EXPERT + default y + depends on MOUSE_PS2 + help + Say Y here if you have a Logitech PS/2++ mouse connected to + your system. + + If unsure, say Y. + +config MOUSE_PS2_SYNAPTICS + bool "Synaptics PS/2 mouse protocol extension" if EXPERT + default y + depends on MOUSE_PS2 + help + Say Y here if you have a Synaptics PS/2 TouchPad connected to + your system. + + If unsure, say Y. + +config MOUSE_PS2_SYNAPTICS_SMBUS + bool "Synaptics PS/2 SMbus companion" if EXPERT + default y + depends on MOUSE_PS2 + depends on I2C=y || I2C=MOUSE_PS2 + select MOUSE_PS2_SMBUS + help + Say Y here if you have a Synaptics RMI4 touchpad connected to + to an SMBus, but enumerated through PS/2. + + If unsure, say Y. + +config MOUSE_PS2_CYPRESS + bool "Cypress PS/2 mouse protocol extension" if EXPERT + default y + depends on MOUSE_PS2 + help + Say Y here if you have a Cypress PS/2 Trackpad connected to + your system. + + If unsure, say Y. + +config MOUSE_PS2_LIFEBOOK + bool "Fujitsu Lifebook PS/2 mouse protocol extension" if EXPERT + default y + depends on MOUSE_PS2 && X86 && DMI + help + Say Y here if you have a Fujitsu B-series Lifebook PS/2 + TouchScreen connected to your system. + + If unsure, say Y. + +config MOUSE_PS2_TRACKPOINT + bool "IBM Trackpoint PS/2 mouse protocol extension" if EXPERT + default y + depends on MOUSE_PS2 + help + Say Y here if you have an IBM Trackpoint PS/2 mouse connected + to your system. + + If unsure, say Y. + +config MOUSE_PS2_ELANTECH + bool "Elantech PS/2 protocol extension" + depends on MOUSE_PS2 + help + Say Y here if you have an Elantech PS/2 touchpad connected + to your system. + + This driver exposes some configuration registers via sysfs + entries. For further information, + see <file:Documentation/input/devices/elantech.rst>. + + If unsure, say N. + +config MOUSE_PS2_ELANTECH_SMBUS + bool "Elantech PS/2 SMbus companion" if EXPERT + default y + depends on MOUSE_PS2 && MOUSE_PS2_ELANTECH + depends on I2C=y || I2C=MOUSE_PS2 + select MOUSE_PS2_SMBUS + help + Say Y here if you have a Elantech touchpad connected to + to an SMBus, but enumerated through PS/2. + + If unsure, say Y. + +config MOUSE_PS2_SENTELIC + bool "Sentelic Finger Sensing Pad PS/2 protocol extension" + depends on MOUSE_PS2 + help + Say Y here if you have a laptop (such as MSI WIND Netbook) + with Sentelic Finger Sensing Pad touchpad. + + If unsure, say N. + +config MOUSE_PS2_TOUCHKIT + bool "eGalax TouchKit PS/2 protocol extension" + depends on MOUSE_PS2 + help + Say Y here if you have an eGalax TouchKit PS/2 touchscreen + connected to your system. + + If unsure, say N. + +config MOUSE_PS2_OLPC + bool "OLPC PS/2 mouse protocol extension" + depends on MOUSE_PS2 && OLPC + help + Say Y here if you have an OLPC XO-1 laptop (with built-in + PS/2 touchpad/tablet device). The manufacturer calls the + touchpad an HGPK. + + If unsure, say N. + +config MOUSE_PS2_FOCALTECH + bool "FocalTech PS/2 mouse protocol extension" if EXPERT + default y + depends on MOUSE_PS2 + help + Say Y here if you have a FocalTech 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 + help + Say Y here if you are running under control of VMware hypervisor + (ESXi, Workstation or Fusion). Also make sure that when you enable + this option, you remove the xf86-input-vmmouse user-space driver + or upgrade it to at least xf86-input-vmmouse 13.1.0, which doesn't + load in the presence of an in-kernel vmmouse driver. + + If unsure, say N. + +config MOUSE_PS2_SMBUS + bool + depends on MOUSE_PS2 + +config MOUSE_SERIAL + tristate "Serial mouse" + select SERIO + help + Say Y here if you have a serial (RS-232, COM port) mouse connected + to your system. This includes Sun, MouseSystems, Microsoft, + Logitech and all other compatible serial mice. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called sermouse. + +config MOUSE_APPLETOUCH + tristate "Apple USB Touchpad support" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use an Apple USB Touchpad. + + These are the touchpads that can be found on post-February 2005 + Apple Powerbooks (prior models have a Synaptics touchpad connected + to the ADB bus). + + This driver provides a basic mouse driver but can be interfaced + with the synaptics X11 driver to provide acceleration and + scrolling in X11. + + For further information, see + <file:Documentation/input/devices/appletouch.rst>. + + To compile this driver as a module, choose M here: the + module will be called appletouch. + +config MOUSE_BCM5974 + tristate "Apple USB BCM5974 Multitouch trackpad support" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you have an Apple USB BCM5974 Multitouch + trackpad. + + The BCM5974 is the multitouch trackpad found in the Macbook + Air (JAN2008) and Macbook Pro Penryn (FEB2008) laptops. + + It is also found in the IPhone (2007) and Ipod Touch (2008). + + This driver provides multitouch functionality together with + the synaptics X11 driver. + + The interface is currently identical to the appletouch interface, + for further information, see + <file:Documentation/input/devices/appletouch.rst>. + + To compile this driver as a module, choose M here: the + module will be called bcm5974. + +config MOUSE_CYAPA + tristate "Cypress APA I2C Trackpad support" + depends on I2C + select CRC_ITU_T + help + This driver adds support for Cypress All Points Addressable (APA) + I2C Trackpads, including the ones used in 2012 Samsung Chromebooks. + + Say Y here if you have a Cypress APA I2C Trackpad. + + To compile this driver as a module, choose M here: the module will be + called cyapa. + +config MOUSE_ELAN_I2C + tristate "ELAN I2C Touchpad support" + depends on I2C + help + This driver adds support for Elan I2C/SMbus Trackpads. + + Say Y here if you have a ELAN I2C/SMbus Touchpad. + + To compile this driver as a module, choose M here: the module will be + called elan_i2c. + +config MOUSE_ELAN_I2C_I2C + bool "Enable I2C support" + depends on MOUSE_ELAN_I2C + default y + help + Say Y here if Elan Touchpad in your system is connected to + a standard I2C controller. + + If unsure, say Y. + +config MOUSE_ELAN_I2C_SMBUS + bool "Enable SMbus support" + depends on MOUSE_ELAN_I2C + help + Say Y here if Elan Touchpad in your system is connected to + a SMbus adapter. + + If unsure, say Y. + +config MOUSE_INPORT + tristate "InPort/MS/ATIXL busmouse" + depends on ISA + help + Say Y here if you have an InPort, Microsoft or ATI XL busmouse. + They are rather rare these days. + + To compile this driver as a module, choose M here: the + module will be called inport. + +config MOUSE_ATIXL + bool "ATI XL variant" + depends on MOUSE_INPORT + help + Say Y here if your mouse is of the ATI XL variety. + +config MOUSE_LOGIBM + tristate "Logitech busmouse" + depends on ISA + help + Say Y here if you have a Logitech busmouse. + They are rather rare these days. + + To compile this driver as a module, choose M here: the + module will be called logibm. + +config MOUSE_PC110PAD + tristate "IBM PC110 touchpad" + depends on ISA + help + Say Y if you have the IBM PC-110 micro-notebook and want its + touchpad supported. + + To compile this driver as a module, choose M here: the + module will be called pc110pad. + +config MOUSE_AMIGA + tristate "Amiga mouse" + depends on AMIGA + help + Say Y here if you have an Amiga and want its native mouse + supported by the kernel. + + To compile this driver as a module, choose M here: the + module will be called amimouse. + +config MOUSE_ATARI + tristate "Atari mouse" + depends on ATARI + select ATARI_KBD_CORE + help + Say Y here if you have an Atari and want its native mouse + supported by the kernel. + + To compile this driver as a module, choose M here: the + module will be called atarimouse. + +config MOUSE_RISCPC + tristate "Acorn RiscPC mouse" + depends on ARCH_ACORN + help + Say Y here if you have the Acorn RiscPC computer and want its + native mouse supported. + + To compile this driver as a module, choose M here: the + module will be called rpcmouse. + +config MOUSE_VSXXXAA + tristate "DEC VSXXX-AA/GA mouse and VSXXX-AB tablet" + select SERIO + help + Say Y (or M) if you want to use a DEC VSXXX-AA (hockey + puck) or a VSXXX-GA (rectangular) mouse. Theses mice are + typically used on DECstations or VAXstations, but can also + be used on any box capable of RS232 (with some adaptor + described in the source file). This driver also works with the + digitizer (VSXXX-AB) DEC produced. + +config MOUSE_GPIO + tristate "GPIO mouse" + depends on GPIOLIB || COMPILE_TEST + select INPUT_POLLDEV + help + This driver simulates a mouse on GPIO lines of various CPUs (and some + other chips). + + Say Y here if your device has buttons or a simple joystick connected + directly to GPIO lines. Your board-specific setup logic must also + provide a platform device and platform data saying which GPIOs are + used. + + To compile this driver as a module, choose M here: the + module will be called gpio_mouse. + +config MOUSE_PXA930_TRKBALL + tristate "PXA930 Trackball mouse" + depends on CPU_PXA930 || CPU_PXA935 + help + Say Y here to support PXA930 Trackball mouse. + +config MOUSE_MAPLE + tristate "Maple mouse (for the Dreamcast)" + depends on MAPLE + help + This driver supports the Maple mouse on the SEGA Dreamcast. + + Most Dreamcast users, who have a mouse, will say Y here. + + To compile this driver as a module choose M here: the module will be + called maplemouse. + +config MOUSE_SYNAPTICS_I2C + tristate "Synaptics I2C Touchpad support" + depends on I2C + help + This driver supports Synaptics I2C touchpad controller on eXeda + mobile device. + The device will not work the synaptics X11 driver because + (i) it reports only relative coordinates and has no capabilities + to report absolute coordinates + (ii) the eXeda device itself uses Xfbdev as X Server and it does + not allow using xf86-input-* drivers. + + Say y here if you have eXeda device and want to use a Synaptics + I2C Touchpad. + + To compile this driver as a module, choose M here: the + module will be called synaptics_i2c. + +config MOUSE_SYNAPTICS_USB + tristate "Synaptics USB device support" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use a Synaptics USB touchpad or pointing + stick. + + While these devices emulate an USB mouse by default and can be used + with standard usbhid driver, this driver, together with its X.Org + counterpart, allows you to fully utilize capabilities of the device. + More information can be found at: + <http://jan-steinhoff.de/linux/synaptics-usb.html> + + To compile this driver as a module, choose M here: the + module will be called synaptics_usb. + +config MOUSE_NAVPOINT_PXA27x + tristate "Synaptics NavPoint (PXA27x SSP/SPI)" + depends on PXA27x && PXA_SSP + help + This driver adds support for the Synaptics NavPoint touchpad connected + to a PXA27x SSP port in SPI slave mode. The device emulates a mouse; + a tap or tap-and-a-half drag gesture emulates the left mouse button. + For example, use the xf86-input-evdev driver for an X pointing device. + + To compile this driver as a module, choose M here: the + module will be called navpoint. + +endif diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile new file mode 100644 index 000000000..e49f08565 --- /dev/null +++ b/drivers/input/mouse/Makefile @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the mouse drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_MOUSE_AMIGA) += amimouse.o +obj-$(CONFIG_MOUSE_APPLETOUCH) += appletouch.o +obj-$(CONFIG_MOUSE_ATARI) += atarimouse.o +obj-$(CONFIG_MOUSE_BCM5974) += bcm5974.o +obj-$(CONFIG_MOUSE_CYAPA) += cyapatp.o +obj-$(CONFIG_MOUSE_ELAN_I2C) += elan_i2c.o +obj-$(CONFIG_MOUSE_GPIO) += gpio_mouse.o +obj-$(CONFIG_MOUSE_INPORT) += inport.o +obj-$(CONFIG_MOUSE_LOGIBM) += logibm.o +obj-$(CONFIG_MOUSE_MAPLE) += maplemouse.o +obj-$(CONFIG_MOUSE_NAVPOINT_PXA27x) += navpoint.o +obj-$(CONFIG_MOUSE_PC110PAD) += pc110pad.o +obj-$(CONFIG_MOUSE_PS2) += psmouse.o +obj-$(CONFIG_MOUSE_PXA930_TRKBALL) += pxa930_trkball.o +obj-$(CONFIG_MOUSE_RISCPC) += rpcmouse.o +obj-$(CONFIG_MOUSE_SERIAL) += sermouse.o +obj-$(CONFIG_MOUSE_SYNAPTICS_I2C) += synaptics_i2c.o +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-$(CONFIG_MOUSE_PS2_ALPS) += alps.o +psmouse-$(CONFIG_MOUSE_PS2_BYD) += byd.o +psmouse-$(CONFIG_MOUSE_PS2_ELANTECH) += elantech.o +psmouse-$(CONFIG_MOUSE_PS2_OLPC) += hgpk.o +psmouse-$(CONFIG_MOUSE_PS2_LOGIPS2PP) += logips2pp.o +psmouse-$(CONFIG_MOUSE_PS2_LIFEBOOK) += lifebook.o +psmouse-$(CONFIG_MOUSE_PS2_SENTELIC) += sentelic.o +psmouse-$(CONFIG_MOUSE_PS2_TRACKPOINT) += trackpoint.o +psmouse-$(CONFIG_MOUSE_PS2_TOUCHKIT) += touchkit_ps2.o +psmouse-$(CONFIG_MOUSE_PS2_CYPRESS) += cypress_ps2.o +psmouse-$(CONFIG_MOUSE_PS2_VMMOUSE) += vmmouse.o + +psmouse-$(CONFIG_MOUSE_PS2_SMBUS) += psmouse-smbus.o + +elan_i2c-objs := elan_i2c_core.o +elan_i2c-$(CONFIG_MOUSE_ELAN_I2C_I2C) += elan_i2c_i2c.o +elan_i2c-$(CONFIG_MOUSE_ELAN_I2C_SMBUS) += elan_i2c_smbus.o diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c new file mode 100644 index 000000000..dd80ff6cc --- /dev/null +++ b/drivers/input/mouse/alps.c @@ -0,0 +1,3235 @@ +/* + * ALPS touchpad PS/2 mouse driver + * + * Copyright (c) 2003 Neil Brown <neilb@cse.unsw.edu.au> + * Copyright (c) 2003-2005 Peter Osterlund <petero2@telia.com> + * Copyright (c) 2004 Dmitry Torokhov <dtor@mail.ru> + * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz> + * Copyright (c) 2009 Sebastian Kapfer <sebastian_kapfer@gmx.net> + * + * ALPS detection, tap switching and status querying info is taken from + * tpconfig utility (by C. Scott Ananian and Bruce Kall). + * + * 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. + */ + +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/serio.h> +#include <linux/libps2.h> +#include <linux/dmi.h> + +#include "psmouse.h" +#include "alps.h" +#include "trackpoint.h" + +/* + * Definitions for ALPS version 3 and 4 command mode protocol + */ +#define ALPS_CMD_NIBBLE_10 0x01f2 + +#define ALPS_REG_BASE_RUSHMORE 0xc2c0 +#define ALPS_REG_BASE_V7 0xc2c0 +#define ALPS_REG_BASE_PINNACLE 0x0000 + +static const struct alps_nibble_commands alps_v3_nibble_commands[] = { + { PSMOUSE_CMD_SETPOLL, 0x00 }, /* 0 */ + { PSMOUSE_CMD_RESET_DIS, 0x00 }, /* 1 */ + { PSMOUSE_CMD_SETSCALE21, 0x00 }, /* 2 */ + { PSMOUSE_CMD_SETRATE, 0x0a }, /* 3 */ + { PSMOUSE_CMD_SETRATE, 0x14 }, /* 4 */ + { PSMOUSE_CMD_SETRATE, 0x28 }, /* 5 */ + { PSMOUSE_CMD_SETRATE, 0x3c }, /* 6 */ + { PSMOUSE_CMD_SETRATE, 0x50 }, /* 7 */ + { PSMOUSE_CMD_SETRATE, 0x64 }, /* 8 */ + { PSMOUSE_CMD_SETRATE, 0xc8 }, /* 9 */ + { ALPS_CMD_NIBBLE_10, 0x00 }, /* a */ + { PSMOUSE_CMD_SETRES, 0x00 }, /* b */ + { PSMOUSE_CMD_SETRES, 0x01 }, /* c */ + { PSMOUSE_CMD_SETRES, 0x02 }, /* d */ + { PSMOUSE_CMD_SETRES, 0x03 }, /* e */ + { PSMOUSE_CMD_SETSCALE11, 0x00 }, /* f */ +}; + +static const struct alps_nibble_commands alps_v4_nibble_commands[] = { + { PSMOUSE_CMD_ENABLE, 0x00 }, /* 0 */ + { PSMOUSE_CMD_RESET_DIS, 0x00 }, /* 1 */ + { PSMOUSE_CMD_SETSCALE21, 0x00 }, /* 2 */ + { PSMOUSE_CMD_SETRATE, 0x0a }, /* 3 */ + { PSMOUSE_CMD_SETRATE, 0x14 }, /* 4 */ + { PSMOUSE_CMD_SETRATE, 0x28 }, /* 5 */ + { PSMOUSE_CMD_SETRATE, 0x3c }, /* 6 */ + { PSMOUSE_CMD_SETRATE, 0x50 }, /* 7 */ + { PSMOUSE_CMD_SETRATE, 0x64 }, /* 8 */ + { PSMOUSE_CMD_SETRATE, 0xc8 }, /* 9 */ + { ALPS_CMD_NIBBLE_10, 0x00 }, /* a */ + { PSMOUSE_CMD_SETRES, 0x00 }, /* b */ + { PSMOUSE_CMD_SETRES, 0x01 }, /* c */ + { PSMOUSE_CMD_SETRES, 0x02 }, /* d */ + { PSMOUSE_CMD_SETRES, 0x03 }, /* e */ + { PSMOUSE_CMD_SETSCALE11, 0x00 }, /* f */ +}; + +static const struct alps_nibble_commands alps_v6_nibble_commands[] = { + { PSMOUSE_CMD_ENABLE, 0x00 }, /* 0 */ + { PSMOUSE_CMD_SETRATE, 0x0a }, /* 1 */ + { PSMOUSE_CMD_SETRATE, 0x14 }, /* 2 */ + { PSMOUSE_CMD_SETRATE, 0x28 }, /* 3 */ + { PSMOUSE_CMD_SETRATE, 0x3c }, /* 4 */ + { PSMOUSE_CMD_SETRATE, 0x50 }, /* 5 */ + { PSMOUSE_CMD_SETRATE, 0x64 }, /* 6 */ + { PSMOUSE_CMD_SETRATE, 0xc8 }, /* 7 */ + { PSMOUSE_CMD_GETID, 0x00 }, /* 8 */ + { PSMOUSE_CMD_GETINFO, 0x00 }, /* 9 */ + { PSMOUSE_CMD_SETRES, 0x00 }, /* a */ + { PSMOUSE_CMD_SETRES, 0x01 }, /* b */ + { PSMOUSE_CMD_SETRES, 0x02 }, /* c */ + { PSMOUSE_CMD_SETRES, 0x03 }, /* d */ + { PSMOUSE_CMD_SETSCALE21, 0x00 }, /* e */ + { PSMOUSE_CMD_SETSCALE11, 0x00 }, /* f */ +}; + + +#define ALPS_DUALPOINT 0x02 /* touchpad has trackstick */ +#define ALPS_PASS 0x04 /* device has a pass-through port */ + +#define ALPS_WHEEL 0x08 /* hardware wheel present */ +#define ALPS_FW_BK_1 0x10 /* front & back buttons present */ +#define ALPS_FW_BK_2 0x20 /* front & back buttons present */ +#define ALPS_FOUR_BUTTONS 0x40 /* 4 direction button present */ +#define ALPS_PS2_INTERLEAVED 0x80 /* 3-byte PS/2 packet interleaved with + 6-byte ALPS packet */ +#define ALPS_STICK_BITS 0x100 /* separate stick button bits */ +#define ALPS_BUTTONPAD 0x200 /* device is a clickpad */ +#define ALPS_DUALPOINT_WITH_PRESSURE 0x400 /* device can report trackpoint pressure */ + +static const struct alps_model_info alps_model_data[] = { + /* + * XXX This entry is suspicious. First byte has zero lower nibble, + * which is what a normal mouse would report. Also, the value 0x0e + * isn't valid per PS/2 spec. + */ + { { 0x20, 0x02, 0x0e }, { ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT } }, + + { { 0x22, 0x02, 0x0a }, { ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT } }, + { { 0x22, 0x02, 0x14 }, { ALPS_PROTO_V2, 0xff, 0xff, ALPS_PASS | ALPS_DUALPOINT } }, /* Dell Latitude D600 */ + { { 0x32, 0x02, 0x14 }, { ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT } }, /* Toshiba Salellite Pro M10 */ + { { 0x33, 0x02, 0x0a }, { ALPS_PROTO_V1, 0x88, 0xf8, 0 } }, /* UMAX-530T */ + { { 0x52, 0x01, 0x14 }, { ALPS_PROTO_V2, 0xff, 0xff, + ALPS_PASS | ALPS_DUALPOINT | ALPS_PS2_INTERLEAVED } }, /* Toshiba Tecra A11-11L */ + { { 0x53, 0x02, 0x0a }, { ALPS_PROTO_V2, 0xf8, 0xf8, 0 } }, + { { 0x53, 0x02, 0x14 }, { ALPS_PROTO_V2, 0xf8, 0xf8, 0 } }, + { { 0x60, 0x03, 0xc8 }, { ALPS_PROTO_V2, 0xf8, 0xf8, 0 } }, /* HP ze1115 */ + { { 0x62, 0x02, 0x14 }, { ALPS_PROTO_V2, 0xcf, 0xcf, + ALPS_PASS | ALPS_DUALPOINT | ALPS_PS2_INTERLEAVED } }, /* Dell Latitude E5500, E6400, E6500, Precision M4400 */ + { { 0x63, 0x02, 0x0a }, { ALPS_PROTO_V2, 0xf8, 0xf8, 0 } }, + { { 0x63, 0x02, 0x14 }, { ALPS_PROTO_V2, 0xf8, 0xf8, 0 } }, + { { 0x63, 0x02, 0x28 }, { ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_FW_BK_2 } }, /* Fujitsu Siemens S6010 */ + { { 0x63, 0x02, 0x3c }, { ALPS_PROTO_V2, 0x8f, 0x8f, ALPS_WHEEL } }, /* Toshiba Satellite S2400-103 */ + { { 0x63, 0x02, 0x50 }, { ALPS_PROTO_V2, 0xef, 0xef, ALPS_FW_BK_1 } }, /* NEC Versa L320 */ + { { 0x63, 0x02, 0x64 }, { ALPS_PROTO_V2, 0xf8, 0xf8, 0 } }, + { { 0x63, 0x03, 0xc8 }, { ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT } }, /* Dell Latitude D800 */ + { { 0x73, 0x00, 0x0a }, { ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_DUALPOINT } }, /* ThinkPad R61 8918-5QG */ + { { 0x73, 0x00, 0x14 }, { ALPS_PROTO_V6, 0xff, 0xff, ALPS_DUALPOINT } }, /* Dell XT2 */ + { { 0x73, 0x02, 0x0a }, { ALPS_PROTO_V2, 0xf8, 0xf8, 0 } }, + { { 0x73, 0x02, 0x14 }, { ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_FW_BK_2 } }, /* Ahtec Laptop */ + { { 0x73, 0x02, 0x50 }, { ALPS_PROTO_V2, 0xcf, 0xcf, ALPS_FOUR_BUTTONS } }, /* Dell Vostro 1400 */ +}; + +static const struct alps_protocol_info alps_v3_protocol_data = { + ALPS_PROTO_V3, 0x8f, 0x8f, ALPS_DUALPOINT | ALPS_DUALPOINT_WITH_PRESSURE +}; + +static const struct alps_protocol_info alps_v3_rushmore_data = { + ALPS_PROTO_V3_RUSHMORE, 0x8f, 0x8f, ALPS_DUALPOINT | ALPS_DUALPOINT_WITH_PRESSURE +}; + +static const struct alps_protocol_info alps_v4_protocol_data = { + ALPS_PROTO_V4, 0x8f, 0x8f, 0 +}; + +static const struct alps_protocol_info alps_v5_protocol_data = { + ALPS_PROTO_V5, 0xc8, 0xd8, 0 +}; + +static const struct alps_protocol_info alps_v7_protocol_data = { + ALPS_PROTO_V7, 0x48, 0x48, ALPS_DUALPOINT | ALPS_DUALPOINT_WITH_PRESSURE +}; + +static const struct alps_protocol_info alps_v8_protocol_data = { + ALPS_PROTO_V8, 0x18, 0x18, 0 +}; + +static const struct alps_protocol_info alps_v9_protocol_data = { + ALPS_PROTO_V9, 0xc8, 0xc8, 0 +}; + +/* + * Some v2 models report the stick buttons in separate bits + */ +static const struct dmi_system_id alps_dmi_has_separate_stick_buttons[] = { +#if defined(CONFIG_DMI) && defined(CONFIG_X86) + { + /* Extrapolated from other entries */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D420"), + }, + }, + { + /* Reported-by: Hans de Bruin <jmdebruin@xmsnet.nl> */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D430"), + }, + }, + { + /* Reported-by: Hans de Goede <hdegoede@redhat.com> */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D620"), + }, + }, + { + /* Extrapolated from other entries */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D630"), + }, + }, +#endif + { } +}; + +static void alps_set_abs_params_st(struct alps_data *priv, + struct input_dev *dev1); +static void alps_set_abs_params_semi_mt(struct alps_data *priv, + struct input_dev *dev1); +static void alps_set_abs_params_v7(struct alps_data *priv, + struct input_dev *dev1); +static void alps_set_abs_params_ss4_v2(struct alps_data *priv, + struct input_dev *dev1); + +/* Packet formats are described in Documentation/input/devices/alps.rst */ + +static bool alps_is_valid_first_byte(struct alps_data *priv, + unsigned char data) +{ + return (data & priv->mask0) == priv->byte0; +} + +static void alps_report_buttons(struct input_dev *dev1, struct input_dev *dev2, + int left, int right, int middle) +{ + struct input_dev *dev; + + /* + * If shared button has already been reported on the + * other device (dev2) then this event should be also + * sent through that device. + */ + dev = (dev2 && test_bit(BTN_LEFT, dev2->key)) ? dev2 : dev1; + input_report_key(dev, BTN_LEFT, left); + + dev = (dev2 && test_bit(BTN_RIGHT, dev2->key)) ? dev2 : dev1; + input_report_key(dev, BTN_RIGHT, right); + + dev = (dev2 && test_bit(BTN_MIDDLE, dev2->key)) ? dev2 : dev1; + input_report_key(dev, BTN_MIDDLE, middle); + + /* + * Sync the _other_ device now, we'll do the first + * device later once we report the rest of the events. + */ + if (dev2) + input_sync(dev2); +} + +static void alps_process_packet_v1_v2(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + unsigned char *packet = psmouse->packet; + struct input_dev *dev = psmouse->dev; + struct input_dev *dev2 = priv->dev2; + int x, y, z, ges, fin, left, right, middle; + int back = 0, forward = 0; + + if (priv->proto_version == ALPS_PROTO_V1) { + left = packet[2] & 0x10; + right = packet[2] & 0x08; + middle = 0; + x = packet[1] | ((packet[0] & 0x07) << 7); + y = packet[4] | ((packet[3] & 0x07) << 7); + z = packet[5]; + } else { + left = packet[3] & 1; + right = packet[3] & 2; + middle = packet[3] & 4; + x = packet[1] | ((packet[2] & 0x78) << (7 - 3)); + y = packet[4] | ((packet[3] & 0x70) << (7 - 4)); + z = packet[5]; + } + + if (priv->flags & ALPS_FW_BK_1) { + back = packet[0] & 0x10; + forward = packet[2] & 4; + } + + if (priv->flags & ALPS_FW_BK_2) { + back = packet[3] & 4; + forward = packet[2] & 4; + if ((middle = forward && back)) + forward = back = 0; + } + + ges = packet[2] & 1; + fin = packet[2] & 2; + + if ((priv->flags & ALPS_DUALPOINT) && z == 127) { + input_report_rel(dev2, REL_X, (x > 383 ? (x - 768) : x)); + input_report_rel(dev2, REL_Y, -(y > 255 ? (y - 512) : y)); + + alps_report_buttons(dev2, dev, left, right, middle); + + input_sync(dev2); + return; + } + + /* Some models have separate stick button bits */ + if (priv->flags & ALPS_STICK_BITS) { + left |= packet[0] & 1; + right |= packet[0] & 2; + middle |= packet[0] & 4; + } + + alps_report_buttons(dev, dev2, left, right, middle); + + /* Convert hardware tap to a reasonable Z value */ + if (ges && !fin) + z = 40; + + /* + * A "tap and drag" operation is reported by the hardware as a transition + * from (!fin && ges) to (fin && ges). This should be translated to the + * sequence Z>0, Z==0, Z>0, so the Z==0 event has to be generated manually. + */ + if (ges && fin && !priv->prev_fin) { + input_report_abs(dev, ABS_X, x); + input_report_abs(dev, ABS_Y, y); + input_report_abs(dev, ABS_PRESSURE, 0); + input_report_key(dev, BTN_TOOL_FINGER, 0); + input_sync(dev); + } + priv->prev_fin = fin; + + if (z > 30) + input_report_key(dev, BTN_TOUCH, 1); + if (z < 25) + input_report_key(dev, BTN_TOUCH, 0); + + if (z > 0) { + input_report_abs(dev, ABS_X, x); + input_report_abs(dev, ABS_Y, y); + } + + input_report_abs(dev, ABS_PRESSURE, z); + input_report_key(dev, BTN_TOOL_FINGER, z > 0); + + if (priv->flags & ALPS_WHEEL) + input_report_rel(dev, REL_WHEEL, ((packet[2] << 1) & 0x08) - ((packet[0] >> 4) & 0x07)); + + if (priv->flags & (ALPS_FW_BK_1 | ALPS_FW_BK_2)) { + input_report_key(dev, BTN_FORWARD, forward); + input_report_key(dev, BTN_BACK, back); + } + + if (priv->flags & ALPS_FOUR_BUTTONS) { + input_report_key(dev, BTN_0, packet[2] & 4); + input_report_key(dev, BTN_1, packet[0] & 0x10); + input_report_key(dev, BTN_2, packet[3] & 4); + input_report_key(dev, BTN_3, packet[0] & 0x20); + } + + input_sync(dev); +} + +static void alps_get_bitmap_points(unsigned int map, + struct alps_bitmap_point *low, + struct alps_bitmap_point *high, + int *fingers) +{ + struct alps_bitmap_point *point; + int i, bit, prev_bit = 0; + + point = low; + for (i = 0; map != 0; i++, map >>= 1) { + bit = map & 1; + if (bit) { + if (!prev_bit) { + point->start_bit = i; + point->num_bits = 0; + (*fingers)++; + } + point->num_bits++; + } else { + if (prev_bit) + point = high; + } + prev_bit = bit; + } +} + +/* + * Process bitmap data from semi-mt protocols. Returns the number of + * fingers detected. A return value of 0 means at least one of the + * bitmaps was empty. + * + * The bitmaps don't have enough data to track fingers, so this function + * only generates points representing a bounding box of all contacts. + * These points are returned in fields->mt when the return value + * is greater than 0. + */ +static int alps_process_bitmap(struct alps_data *priv, + struct alps_fields *fields) +{ + int i, fingers_x = 0, fingers_y = 0, fingers, closest; + struct alps_bitmap_point x_low = {0,}, x_high = {0,}; + struct alps_bitmap_point y_low = {0,}, y_high = {0,}; + struct input_mt_pos corner[4]; + + if (!fields->x_map || !fields->y_map) + return 0; + + alps_get_bitmap_points(fields->x_map, &x_low, &x_high, &fingers_x); + alps_get_bitmap_points(fields->y_map, &y_low, &y_high, &fingers_y); + + /* + * Fingers can overlap, so we use the maximum count of fingers + * on either axis as the finger count. + */ + fingers = max(fingers_x, fingers_y); + + /* + * If an axis reports only a single contact, we have overlapping or + * adjacent fingers. Divide the single contact between the two points. + */ + if (fingers_x == 1) { + i = (x_low.num_bits - 1) / 2; + x_low.num_bits = x_low.num_bits - i; + x_high.start_bit = x_low.start_bit + i; + x_high.num_bits = max(i, 1); + } + if (fingers_y == 1) { + i = (y_low.num_bits - 1) / 2; + y_low.num_bits = y_low.num_bits - i; + y_high.start_bit = y_low.start_bit + i; + y_high.num_bits = max(i, 1); + } + + /* top-left corner */ + corner[0].x = + (priv->x_max * (2 * x_low.start_bit + x_low.num_bits - 1)) / + (2 * (priv->x_bits - 1)); + corner[0].y = + (priv->y_max * (2 * y_low.start_bit + y_low.num_bits - 1)) / + (2 * (priv->y_bits - 1)); + + /* top-right corner */ + corner[1].x = + (priv->x_max * (2 * x_high.start_bit + x_high.num_bits - 1)) / + (2 * (priv->x_bits - 1)); + corner[1].y = + (priv->y_max * (2 * y_low.start_bit + y_low.num_bits - 1)) / + (2 * (priv->y_bits - 1)); + + /* bottom-right corner */ + corner[2].x = + (priv->x_max * (2 * x_high.start_bit + x_high.num_bits - 1)) / + (2 * (priv->x_bits - 1)); + corner[2].y = + (priv->y_max * (2 * y_high.start_bit + y_high.num_bits - 1)) / + (2 * (priv->y_bits - 1)); + + /* bottom-left corner */ + corner[3].x = + (priv->x_max * (2 * x_low.start_bit + x_low.num_bits - 1)) / + (2 * (priv->x_bits - 1)); + corner[3].y = + (priv->y_max * (2 * y_high.start_bit + y_high.num_bits - 1)) / + (2 * (priv->y_bits - 1)); + + /* x-bitmap order is reversed on v5 touchpads */ + if (priv->proto_version == ALPS_PROTO_V5) { + for (i = 0; i < 4; i++) + corner[i].x = priv->x_max - corner[i].x; + } + + /* y-bitmap order is reversed on v3 and v4 touchpads */ + if (priv->proto_version == ALPS_PROTO_V3 || + priv->proto_version == ALPS_PROTO_V4) { + for (i = 0; i < 4; i++) + corner[i].y = priv->y_max - corner[i].y; + } + + /* + * We only select a corner for the second touch once per 2 finger + * touch sequence to avoid the chosen corner (and thus the coordinates) + * jumping around when the first touch is in the middle. + */ + if (priv->second_touch == -1) { + /* Find corner closest to our st coordinates */ + closest = 0x7fffffff; + for (i = 0; i < 4; i++) { + int dx = fields->st.x - corner[i].x; + int dy = fields->st.y - corner[i].y; + int distance = dx * dx + dy * dy; + + if (distance < closest) { + priv->second_touch = i; + closest = distance; + } + } + /* And select the opposite corner to use for the 2nd touch */ + priv->second_touch = (priv->second_touch + 2) % 4; + } + + fields->mt[0] = fields->st; + fields->mt[1] = corner[priv->second_touch]; + + return fingers; +} + +static void alps_set_slot(struct input_dev *dev, int slot, int x, int y) +{ + input_mt_slot(dev, slot); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, true); + input_report_abs(dev, ABS_MT_POSITION_X, x); + input_report_abs(dev, ABS_MT_POSITION_Y, y); +} + +static void alps_report_mt_data(struct psmouse *psmouse, int n) +{ + struct alps_data *priv = psmouse->private; + struct input_dev *dev = psmouse->dev; + struct alps_fields *f = &priv->f; + int i, slot[MAX_TOUCHES]; + + input_mt_assign_slots(dev, slot, f->mt, n, 0); + for (i = 0; i < n; i++) + alps_set_slot(dev, slot[i], f->mt[i].x, f->mt[i].y); + + input_mt_sync_frame(dev); +} + +static void alps_report_semi_mt_data(struct psmouse *psmouse, int fingers) +{ + struct alps_data *priv = psmouse->private; + struct input_dev *dev = psmouse->dev; + struct alps_fields *f = &priv->f; + + /* Use st data when we don't have mt data */ + if (fingers < 2) { + f->mt[0].x = f->st.x; + f->mt[0].y = f->st.y; + fingers = f->pressure > 0 ? 1 : 0; + priv->second_touch = -1; + } + + if (fingers >= 1) + alps_set_slot(dev, 0, f->mt[0].x, f->mt[0].y); + if (fingers >= 2) + alps_set_slot(dev, 1, f->mt[1].x, f->mt[1].y); + input_mt_sync_frame(dev); + + input_mt_report_finger_count(dev, fingers); + + input_report_key(dev, BTN_LEFT, f->left); + input_report_key(dev, BTN_RIGHT, f->right); + input_report_key(dev, BTN_MIDDLE, f->middle); + + input_report_abs(dev, ABS_PRESSURE, f->pressure); + + input_sync(dev); +} + +static void alps_process_trackstick_packet_v3(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + unsigned char *packet = psmouse->packet; + struct input_dev *dev = priv->dev2; + int x, y, z, left, right, middle; + + /* It should be a DualPoint when received trackstick packet */ + if (!(priv->flags & ALPS_DUALPOINT)) { + psmouse_warn(psmouse, + "Rejected trackstick packet from non DualPoint device"); + return; + } + + /* Sanity check packet */ + if (!(packet[0] & 0x40)) { + psmouse_dbg(psmouse, "Bad trackstick packet, discarding\n"); + return; + } + + /* + * There's a special packet that seems to indicate the end + * of a stream of trackstick data. Filter these out. + */ + if (packet[1] == 0x7f && packet[2] == 0x7f && packet[4] == 0x7f) + return; + + x = (s8)(((packet[0] & 0x20) << 2) | (packet[1] & 0x7f)); + y = (s8)(((packet[0] & 0x10) << 3) | (packet[2] & 0x7f)); + z = packet[4] & 0x7f; + + /* + * The x and y values tend to be quite large, and when used + * alone the trackstick is difficult to use. Scale them down + * to compensate. + */ + x /= 8; + y /= 8; + + input_report_rel(dev, REL_X, x); + input_report_rel(dev, REL_Y, -y); + input_report_abs(dev, ABS_PRESSURE, z); + + /* + * Most ALPS models report the trackstick buttons in the touchpad + * packets, but a few report them here. No reliable way has been + * found to differentiate between the models upfront, so we enable + * the quirk in response to seeing a button press in the trackstick + * packet. + */ + left = packet[3] & 0x01; + right = packet[3] & 0x02; + middle = packet[3] & 0x04; + + if (!(priv->quirks & ALPS_QUIRK_TRACKSTICK_BUTTONS) && + (left || right || middle)) + priv->quirks |= ALPS_QUIRK_TRACKSTICK_BUTTONS; + + if (priv->quirks & ALPS_QUIRK_TRACKSTICK_BUTTONS) { + input_report_key(dev, BTN_LEFT, left); + input_report_key(dev, BTN_RIGHT, right); + input_report_key(dev, BTN_MIDDLE, middle); + } + + input_sync(dev); + return; +} + +static void alps_decode_buttons_v3(struct alps_fields *f, unsigned char *p) +{ + f->left = !!(p[3] & 0x01); + f->right = !!(p[3] & 0x02); + f->middle = !!(p[3] & 0x04); + + f->ts_left = !!(p[3] & 0x10); + f->ts_right = !!(p[3] & 0x20); + f->ts_middle = !!(p[3] & 0x40); +} + +static int alps_decode_pinnacle(struct alps_fields *f, unsigned char *p, + struct psmouse *psmouse) +{ + f->first_mp = !!(p[4] & 0x40); + f->is_mp = !!(p[0] & 0x40); + + if (f->is_mp) { + f->fingers = (p[5] & 0x3) + 1; + f->x_map = ((p[4] & 0x7e) << 8) | + ((p[1] & 0x7f) << 2) | + ((p[0] & 0x30) >> 4); + f->y_map = ((p[3] & 0x70) << 4) | + ((p[2] & 0x7f) << 1) | + (p[4] & 0x01); + } else { + f->st.x = ((p[1] & 0x7f) << 4) | ((p[4] & 0x30) >> 2) | + ((p[0] & 0x30) >> 4); + f->st.y = ((p[2] & 0x7f) << 4) | (p[4] & 0x0f); + f->pressure = p[5] & 0x7f; + + alps_decode_buttons_v3(f, p); + } + + return 0; +} + +static int alps_decode_rushmore(struct alps_fields *f, unsigned char *p, + struct psmouse *psmouse) +{ + f->first_mp = !!(p[4] & 0x40); + f->is_mp = !!(p[5] & 0x40); + + if (f->is_mp) { + f->fingers = max((p[5] & 0x3), ((p[5] >> 2) & 0x3)) + 1; + f->x_map = ((p[5] & 0x10) << 11) | + ((p[4] & 0x7e) << 8) | + ((p[1] & 0x7f) << 2) | + ((p[0] & 0x30) >> 4); + f->y_map = ((p[5] & 0x20) << 6) | + ((p[3] & 0x70) << 4) | + ((p[2] & 0x7f) << 1) | + (p[4] & 0x01); + } else { + f->st.x = ((p[1] & 0x7f) << 4) | ((p[4] & 0x30) >> 2) | + ((p[0] & 0x30) >> 4); + f->st.y = ((p[2] & 0x7f) << 4) | (p[4] & 0x0f); + f->pressure = p[5] & 0x7f; + + alps_decode_buttons_v3(f, p); + } + + return 0; +} + +static int alps_decode_dolphin(struct alps_fields *f, unsigned char *p, + struct psmouse *psmouse) +{ + u64 palm_data = 0; + struct alps_data *priv = psmouse->private; + + f->first_mp = !!(p[0] & 0x02); + f->is_mp = !!(p[0] & 0x20); + + if (!f->is_mp) { + f->st.x = ((p[1] & 0x7f) | ((p[4] & 0x0f) << 7)); + f->st.y = ((p[2] & 0x7f) | ((p[4] & 0xf0) << 3)); + f->pressure = (p[0] & 4) ? 0 : p[5] & 0x7f; + alps_decode_buttons_v3(f, p); + } else { + f->fingers = ((p[0] & 0x6) >> 1 | + (p[0] & 0x10) >> 2); + + palm_data = (p[1] & 0x7f) | + ((p[2] & 0x7f) << 7) | + ((p[4] & 0x7f) << 14) | + ((p[5] & 0x7f) << 21) | + ((p[3] & 0x07) << 28) | + (((u64)p[3] & 0x70) << 27) | + (((u64)p[0] & 0x01) << 34); + + /* Y-profile is stored in P(0) to p(n-1), n = y_bits; */ + f->y_map = palm_data & (BIT(priv->y_bits) - 1); + + /* X-profile is stored in p(n) to p(n+m-1), m = x_bits; */ + f->x_map = (palm_data >> priv->y_bits) & + (BIT(priv->x_bits) - 1); + } + + return 0; +} + +static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + unsigned char *packet = psmouse->packet; + struct input_dev *dev2 = priv->dev2; + struct alps_fields *f = &priv->f; + int fingers = 0; + + memset(f, 0, sizeof(*f)); + + priv->decode_fields(f, packet, psmouse); + + /* + * There's no single feature of touchpad position and bitmap packets + * that can be used to distinguish between them. We rely on the fact + * that a bitmap packet should always follow a position packet with + * bit 6 of packet[4] set. + */ + if (priv->multi_packet) { + /* + * Sometimes a position packet will indicate a multi-packet + * sequence, but then what follows is another position + * packet. Check for this, and when it happens process the + * position packet as usual. + */ + if (f->is_mp) { + fingers = f->fingers; + /* + * Bitmap processing uses position packet's coordinate + * data, so we need to do decode it first. + */ + priv->decode_fields(f, priv->multi_data, psmouse); + if (alps_process_bitmap(priv, f) == 0) + fingers = 0; /* Use st data */ + } else { + priv->multi_packet = 0; + } + } + + /* + * Bit 6 of byte 0 is not usually set in position packets. The only + * times it seems to be set is in situations where the data is + * suspect anyway, e.g. a palm resting flat on the touchpad. Given + * this combined with the fact that this bit is useful for filtering + * out misidentified bitmap packets, we reject anything with this + * bit set. + */ + if (f->is_mp) + return; + + if (!priv->multi_packet && f->first_mp) { + priv->multi_packet = 1; + memcpy(priv->multi_data, packet, sizeof(priv->multi_data)); + return; + } + + priv->multi_packet = 0; + + /* + * Sometimes the hardware sends a single packet with z = 0 + * in the middle of a stream. Real releases generate packets + * with x, y, and z all zero, so these seem to be flukes. + * Ignore them. + */ + if (f->st.x && f->st.y && !f->pressure) + return; + + alps_report_semi_mt_data(psmouse, fingers); + + if ((priv->flags & ALPS_DUALPOINT) && + !(priv->quirks & ALPS_QUIRK_TRACKSTICK_BUTTONS)) { + input_report_key(dev2, BTN_LEFT, f->ts_left); + input_report_key(dev2, BTN_RIGHT, f->ts_right); + input_report_key(dev2, BTN_MIDDLE, f->ts_middle); + input_sync(dev2); + } +} + +static void alps_process_packet_v3(struct psmouse *psmouse) +{ + unsigned char *packet = psmouse->packet; + + /* + * v3 protocol packets come in three types, two representing + * touchpad data and one representing trackstick data. + * Trackstick packets seem to be distinguished by always + * having 0x3f in the last byte. This value has never been + * observed in the last byte of either of the other types + * of packets. + */ + if (packet[5] == 0x3f) { + alps_process_trackstick_packet_v3(psmouse); + return; + } + + alps_process_touchpad_packet_v3_v5(psmouse); +} + +static void alps_process_packet_v6(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + unsigned char *packet = psmouse->packet; + struct input_dev *dev = psmouse->dev; + struct input_dev *dev2 = priv->dev2; + int x, y, z; + + /* + * We can use Byte5 to distinguish if the packet is from Touchpad + * or Trackpoint. + * Touchpad: 0 - 0x7E + * Trackpoint: 0x7F + */ + if (packet[5] == 0x7F) { + /* It should be a DualPoint when received Trackpoint packet */ + if (!(priv->flags & ALPS_DUALPOINT)) { + psmouse_warn(psmouse, + "Rejected trackstick packet from non DualPoint device"); + return; + } + + /* Trackpoint packet */ + x = packet[1] | ((packet[3] & 0x20) << 2); + y = packet[2] | ((packet[3] & 0x40) << 1); + z = packet[4]; + + /* To prevent the cursor jump when finger lifted */ + if (x == 0x7F && y == 0x7F && z == 0x7F) + x = y = z = 0; + + /* Divide 4 since trackpoint's speed is too fast */ + input_report_rel(dev2, REL_X, (char)x / 4); + input_report_rel(dev2, REL_Y, -((char)y / 4)); + + psmouse_report_standard_buttons(dev2, packet[3]); + + input_sync(dev2); + return; + } + + /* Touchpad packet */ + x = packet[1] | ((packet[3] & 0x78) << 4); + y = packet[2] | ((packet[4] & 0x78) << 4); + z = packet[5]; + + if (z > 30) + input_report_key(dev, BTN_TOUCH, 1); + if (z < 25) + input_report_key(dev, BTN_TOUCH, 0); + + if (z > 0) { + input_report_abs(dev, ABS_X, x); + input_report_abs(dev, ABS_Y, y); + } + + input_report_abs(dev, ABS_PRESSURE, z); + input_report_key(dev, BTN_TOOL_FINGER, z > 0); + + /* v6 touchpad does not have middle button */ + packet[3] &= ~BIT(2); + psmouse_report_standard_buttons(dev2, packet[3]); + + input_sync(dev); +} + +static void alps_process_packet_v4(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + unsigned char *packet = psmouse->packet; + struct alps_fields *f = &priv->f; + int offset; + + /* + * v4 has a 6-byte encoding for bitmap data, but this data is + * broken up between 3 normal packets. Use priv->multi_packet to + * track our position in the bitmap packet. + */ + if (packet[6] & 0x40) { + /* sync, reset position */ + priv->multi_packet = 0; + } + + if (WARN_ON_ONCE(priv->multi_packet > 2)) + return; + + offset = 2 * priv->multi_packet; + priv->multi_data[offset] = packet[6]; + priv->multi_data[offset + 1] = packet[7]; + + f->left = !!(packet[4] & 0x01); + f->right = !!(packet[4] & 0x02); + + f->st.x = ((packet[1] & 0x7f) << 4) | ((packet[3] & 0x30) >> 2) | + ((packet[0] & 0x30) >> 4); + f->st.y = ((packet[2] & 0x7f) << 4) | (packet[3] & 0x0f); + f->pressure = packet[5] & 0x7f; + + if (++priv->multi_packet > 2) { + priv->multi_packet = 0; + + f->x_map = ((priv->multi_data[2] & 0x1f) << 10) | + ((priv->multi_data[3] & 0x60) << 3) | + ((priv->multi_data[0] & 0x3f) << 2) | + ((priv->multi_data[1] & 0x60) >> 5); + f->y_map = ((priv->multi_data[5] & 0x01) << 10) | + ((priv->multi_data[3] & 0x1f) << 5) | + (priv->multi_data[1] & 0x1f); + + f->fingers = alps_process_bitmap(priv, f); + } + + alps_report_semi_mt_data(psmouse, f->fingers); +} + +static bool alps_is_valid_package_v7(struct psmouse *psmouse) +{ + switch (psmouse->pktcnt) { + case 3: + return (psmouse->packet[2] & 0x40) == 0x40; + case 4: + return (psmouse->packet[3] & 0x48) == 0x48; + case 6: + return (psmouse->packet[5] & 0x40) == 0x00; + } + return true; +} + +static unsigned char alps_get_packet_id_v7(char *byte) +{ + unsigned char packet_id; + + if (byte[4] & 0x40) + packet_id = V7_PACKET_ID_TWO; + else if (byte[4] & 0x01) + packet_id = V7_PACKET_ID_MULTI; + else if ((byte[0] & 0x10) && !(byte[4] & 0x43)) + packet_id = V7_PACKET_ID_NEW; + else if (byte[1] == 0x00 && byte[4] == 0x00) + packet_id = V7_PACKET_ID_IDLE; + else + packet_id = V7_PACKET_ID_UNKNOWN; + + return packet_id; +} + +static void alps_get_finger_coordinate_v7(struct input_mt_pos *mt, + unsigned char *pkt, + unsigned char pkt_id) +{ + mt[0].x = ((pkt[2] & 0x80) << 4); + mt[0].x |= ((pkt[2] & 0x3F) << 5); + mt[0].x |= ((pkt[3] & 0x30) >> 1); + mt[0].x |= (pkt[3] & 0x07); + mt[0].y = (pkt[1] << 3) | (pkt[0] & 0x07); + + mt[1].x = ((pkt[3] & 0x80) << 4); + mt[1].x |= ((pkt[4] & 0x80) << 3); + mt[1].x |= ((pkt[4] & 0x3F) << 4); + mt[1].y = ((pkt[5] & 0x80) << 3); + mt[1].y |= ((pkt[5] & 0x3F) << 4); + + switch (pkt_id) { + case V7_PACKET_ID_TWO: + mt[1].x &= ~0x000F; + mt[1].y |= 0x000F; + /* Detect false-postive touches where x & y report max value */ + if (mt[1].y == 0x7ff && mt[1].x == 0xff0) { + mt[1].x = 0; + /* y gets set to 0 at the end of this function */ + } + break; + + case V7_PACKET_ID_MULTI: + mt[1].x &= ~0x003F; + mt[1].y &= ~0x0020; + mt[1].y |= ((pkt[4] & 0x02) << 4); + mt[1].y |= 0x001F; + break; + + case V7_PACKET_ID_NEW: + mt[1].x &= ~0x003F; + mt[1].x |= (pkt[0] & 0x20); + mt[1].y |= 0x000F; + break; + } + + mt[0].y = 0x7FF - mt[0].y; + mt[1].y = 0x7FF - mt[1].y; +} + +static int alps_get_mt_count(struct input_mt_pos *mt) +{ + int i, fingers = 0; + + for (i = 0; i < MAX_TOUCHES; i++) { + if (mt[i].x != 0 || mt[i].y != 0) + fingers++; + } + + return fingers; +} + +static int alps_decode_packet_v7(struct alps_fields *f, + unsigned char *p, + struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + unsigned char pkt_id; + + pkt_id = alps_get_packet_id_v7(p); + if (pkt_id == V7_PACKET_ID_IDLE) + return 0; + if (pkt_id == V7_PACKET_ID_UNKNOWN) + return -1; + /* + * NEW packets are send to indicate a discontinuity in the finger + * coordinate reporting. Specifically a finger may have moved from + * slot 0 to 1 or vice versa. INPUT_MT_TRACK takes care of this for + * us. + * + * NEW packets have 3 problems: + * 1) They do not contain middle / right button info (on non clickpads) + * this can be worked around by preserving the old button state + * 2) They do not contain an accurate fingercount, and they are + * typically send when the number of fingers changes. We cannot use + * the old finger count as that may mismatch with the amount of + * touch coordinates we've available in the NEW packet + * 3) Their x data for the second touch is inaccurate leading to + * a possible jump of the x coordinate by 16 units when the first + * non NEW packet comes in + * Since problems 2 & 3 cannot be worked around, just ignore them. + */ + if (pkt_id == V7_PACKET_ID_NEW) + return 1; + + alps_get_finger_coordinate_v7(f->mt, p, pkt_id); + + if (pkt_id == V7_PACKET_ID_TWO) + f->fingers = alps_get_mt_count(f->mt); + else /* pkt_id == V7_PACKET_ID_MULTI */ + f->fingers = 3 + (p[5] & 0x03); + + f->left = (p[0] & 0x80) >> 7; + if (priv->flags & ALPS_BUTTONPAD) { + if (p[0] & 0x20) + f->fingers++; + if (p[0] & 0x10) + f->fingers++; + } else { + f->right = (p[0] & 0x20) >> 5; + f->middle = (p[0] & 0x10) >> 4; + } + + /* Sometimes a single touch is reported in mt[1] rather then mt[0] */ + if (f->fingers == 1 && f->mt[0].x == 0 && f->mt[0].y == 0) { + f->mt[0].x = f->mt[1].x; + f->mt[0].y = f->mt[1].y; + f->mt[1].x = 0; + f->mt[1].y = 0; + } + + return 0; +} + +static void alps_process_trackstick_packet_v7(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + unsigned char *packet = psmouse->packet; + struct input_dev *dev2 = priv->dev2; + int x, y, z; + + /* It should be a DualPoint when received trackstick packet */ + if (!(priv->flags & ALPS_DUALPOINT)) { + psmouse_warn(psmouse, + "Rejected trackstick packet from non DualPoint device"); + return; + } + + x = ((packet[2] & 0xbf)) | ((packet[3] & 0x10) << 2); + y = (packet[3] & 0x07) | (packet[4] & 0xb8) | + ((packet[3] & 0x20) << 1); + z = (packet[5] & 0x3f) | ((packet[3] & 0x80) >> 1); + + input_report_rel(dev2, REL_X, (char)x); + input_report_rel(dev2, REL_Y, -((char)y)); + input_report_abs(dev2, ABS_PRESSURE, z); + + psmouse_report_standard_buttons(dev2, packet[1]); + + input_sync(dev2); +} + +static void alps_process_touchpad_packet_v7(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + struct input_dev *dev = psmouse->dev; + struct alps_fields *f = &priv->f; + + memset(f, 0, sizeof(*f)); + + if (priv->decode_fields(f, psmouse->packet, psmouse)) + return; + + alps_report_mt_data(psmouse, alps_get_mt_count(f->mt)); + + input_mt_report_finger_count(dev, f->fingers); + + input_report_key(dev, BTN_LEFT, f->left); + input_report_key(dev, BTN_RIGHT, f->right); + input_report_key(dev, BTN_MIDDLE, f->middle); + + input_sync(dev); +} + +static void alps_process_packet_v7(struct psmouse *psmouse) +{ + unsigned char *packet = psmouse->packet; + + if (packet[0] == 0x48 && (packet[4] & 0x47) == 0x06) + alps_process_trackstick_packet_v7(psmouse); + else + alps_process_touchpad_packet_v7(psmouse); +} + +static enum SS4_PACKET_ID alps_get_pkt_id_ss4_v2(unsigned char *byte) +{ + enum SS4_PACKET_ID pkt_id = SS4_PACKET_ID_IDLE; + + switch (byte[3] & 0x30) { + case 0x00: + if (SS4_IS_IDLE_V2(byte)) { + pkt_id = SS4_PACKET_ID_IDLE; + } else { + pkt_id = SS4_PACKET_ID_ONE; + } + break; + case 0x10: + /* two-finger finger positions */ + pkt_id = SS4_PACKET_ID_TWO; + break; + case 0x20: + /* stick pointer */ + pkt_id = SS4_PACKET_ID_STICK; + break; + case 0x30: + /* third and fourth finger positions */ + pkt_id = SS4_PACKET_ID_MULTI; + break; + } + + return pkt_id; +} + +static int alps_decode_ss4_v2(struct alps_fields *f, + unsigned char *p, struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + enum SS4_PACKET_ID pkt_id; + unsigned int no_data_x, no_data_y; + + pkt_id = alps_get_pkt_id_ss4_v2(p); + + /* Current packet is 1Finger coordinate packet */ + switch (pkt_id) { + case SS4_PACKET_ID_ONE: + f->mt[0].x = SS4_1F_X_V2(p); + f->mt[0].y = SS4_1F_Y_V2(p); + f->pressure = ((SS4_1F_Z_V2(p)) * 2) & 0x7f; + /* + * When a button is held the device will give us events + * with x, y, and pressure of 0. This causes annoying jumps + * if a touch is released while the button is held. + * Handle this by claiming zero contacts. + */ + f->fingers = f->pressure > 0 ? 1 : 0; + f->first_mp = 0; + f->is_mp = 0; + break; + + case SS4_PACKET_ID_TWO: + if (priv->flags & ALPS_BUTTONPAD) { + if (IS_SS4PLUS_DEV(priv->dev_id)) { + f->mt[0].x = SS4_PLUS_BTL_MF_X_V2(p, 0); + f->mt[1].x = SS4_PLUS_BTL_MF_X_V2(p, 1); + } else { + f->mt[0].x = SS4_BTL_MF_X_V2(p, 0); + f->mt[1].x = SS4_BTL_MF_X_V2(p, 1); + } + f->mt[0].y = SS4_BTL_MF_Y_V2(p, 0); + f->mt[1].y = SS4_BTL_MF_Y_V2(p, 1); + } else { + if (IS_SS4PLUS_DEV(priv->dev_id)) { + f->mt[0].x = SS4_PLUS_STD_MF_X_V2(p, 0); + f->mt[1].x = SS4_PLUS_STD_MF_X_V2(p, 1); + } else { + f->mt[0].x = SS4_STD_MF_X_V2(p, 0); + f->mt[1].x = SS4_STD_MF_X_V2(p, 1); + } + f->mt[0].y = SS4_STD_MF_Y_V2(p, 0); + f->mt[1].y = SS4_STD_MF_Y_V2(p, 1); + } + f->pressure = SS4_MF_Z_V2(p, 0) ? 0x30 : 0; + + if (SS4_IS_MF_CONTINUE(p)) { + f->first_mp = 1; + } else { + f->fingers = 2; + f->first_mp = 0; + } + f->is_mp = 0; + + break; + + case SS4_PACKET_ID_MULTI: + if (priv->flags & ALPS_BUTTONPAD) { + if (IS_SS4PLUS_DEV(priv->dev_id)) { + f->mt[2].x = SS4_PLUS_BTL_MF_X_V2(p, 0); + f->mt[3].x = SS4_PLUS_BTL_MF_X_V2(p, 1); + no_data_x = SS4_PLUS_MFPACKET_NO_AX_BL; + } else { + f->mt[2].x = SS4_BTL_MF_X_V2(p, 0); + f->mt[3].x = SS4_BTL_MF_X_V2(p, 1); + no_data_x = SS4_MFPACKET_NO_AX_BL; + } + no_data_y = SS4_MFPACKET_NO_AY_BL; + + f->mt[2].y = SS4_BTL_MF_Y_V2(p, 0); + f->mt[3].y = SS4_BTL_MF_Y_V2(p, 1); + } else { + if (IS_SS4PLUS_DEV(priv->dev_id)) { + f->mt[2].x = SS4_PLUS_STD_MF_X_V2(p, 0); + f->mt[3].x = SS4_PLUS_STD_MF_X_V2(p, 1); + no_data_x = SS4_PLUS_MFPACKET_NO_AX; + } else { + f->mt[2].x = SS4_STD_MF_X_V2(p, 0); + f->mt[3].x = SS4_STD_MF_X_V2(p, 1); + no_data_x = SS4_MFPACKET_NO_AX; + } + no_data_y = SS4_MFPACKET_NO_AY; + + f->mt[2].y = SS4_STD_MF_Y_V2(p, 0); + f->mt[3].y = SS4_STD_MF_Y_V2(p, 1); + } + + f->first_mp = 0; + f->is_mp = 1; + + if (SS4_IS_5F_DETECTED(p)) { + f->fingers = 5; + } else if (f->mt[3].x == no_data_x && + f->mt[3].y == no_data_y) { + f->mt[3].x = 0; + f->mt[3].y = 0; + f->fingers = 3; + } else { + f->fingers = 4; + } + break; + + case SS4_PACKET_ID_STICK: + /* + * x, y, and pressure are decoded in + * alps_process_packet_ss4_v2() + */ + f->first_mp = 0; + f->is_mp = 0; + break; + + case SS4_PACKET_ID_IDLE: + default: + memset(f, 0, sizeof(struct alps_fields)); + break; + } + + /* handle buttons */ + if (pkt_id == SS4_PACKET_ID_STICK) { + f->ts_left = !!(SS4_BTN_V2(p) & 0x01); + f->ts_right = !!(SS4_BTN_V2(p) & 0x02); + f->ts_middle = !!(SS4_BTN_V2(p) & 0x04); + } else { + f->left = !!(SS4_BTN_V2(p) & 0x01); + if (!(priv->flags & ALPS_BUTTONPAD)) { + f->right = !!(SS4_BTN_V2(p) & 0x02); + f->middle = !!(SS4_BTN_V2(p) & 0x04); + } + } + + return 0; +} + +static void alps_process_packet_ss4_v2(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + unsigned char *packet = psmouse->packet; + struct input_dev *dev = psmouse->dev; + struct input_dev *dev2 = priv->dev2; + struct alps_fields *f = &priv->f; + + memset(f, 0, sizeof(struct alps_fields)); + priv->decode_fields(f, packet, psmouse); + if (priv->multi_packet) { + /* + * Sometimes the first packet will indicate a multi-packet + * sequence, but sometimes the next multi-packet would not + * come. Check for this, and when it happens process the + * position packet as usual. + */ + if (f->is_mp) { + /* Now process the 1st packet */ + priv->decode_fields(f, priv->multi_data, psmouse); + } else { + priv->multi_packet = 0; + } + } + + /* + * "f.is_mp" would always be '0' after merging the 1st and 2nd packet. + * When it is set, it means 2nd packet comes without 1st packet come. + */ + if (f->is_mp) + return; + + /* Save the first packet */ + if (!priv->multi_packet && f->first_mp) { + priv->multi_packet = 1; + memcpy(priv->multi_data, packet, sizeof(priv->multi_data)); + return; + } + + priv->multi_packet = 0; + + /* Report trackstick */ + if (alps_get_pkt_id_ss4_v2(packet) == SS4_PACKET_ID_STICK) { + if (!(priv->flags & ALPS_DUALPOINT)) { + psmouse_warn(psmouse, + "Rejected trackstick packet from non DualPoint device"); + return; + } + + input_report_rel(dev2, REL_X, SS4_TS_X_V2(packet)); + input_report_rel(dev2, REL_Y, SS4_TS_Y_V2(packet)); + input_report_abs(dev2, ABS_PRESSURE, SS4_TS_Z_V2(packet)); + + input_report_key(dev2, BTN_LEFT, f->ts_left); + input_report_key(dev2, BTN_RIGHT, f->ts_right); + input_report_key(dev2, BTN_MIDDLE, f->ts_middle); + + input_sync(dev2); + return; + } + + /* Report touchpad */ + alps_report_mt_data(psmouse, (f->fingers <= 4) ? f->fingers : 4); + + input_mt_report_finger_count(dev, f->fingers); + + input_report_key(dev, BTN_LEFT, f->left); + input_report_key(dev, BTN_RIGHT, f->right); + input_report_key(dev, BTN_MIDDLE, f->middle); + + input_report_abs(dev, ABS_PRESSURE, f->pressure); + input_sync(dev); +} + +static bool alps_is_valid_package_ss4_v2(struct psmouse *psmouse) +{ + if (psmouse->pktcnt == 4 && ((psmouse->packet[3] & 0x08) != 0x08)) + return false; + if (psmouse->pktcnt == 6 && ((psmouse->packet[5] & 0x10) != 0x0)) + return false; + return true; +} + +static DEFINE_MUTEX(alps_mutex); + +static void alps_register_bare_ps2_mouse(struct work_struct *work) +{ + struct alps_data *priv = + container_of(work, struct alps_data, dev3_register_work.work); + struct psmouse *psmouse = priv->psmouse; + struct input_dev *dev3; + int error = 0; + + mutex_lock(&alps_mutex); + + if (priv->dev3) + goto out; + + dev3 = input_allocate_device(); + if (!dev3) { + psmouse_err(psmouse, "failed to allocate secondary device\n"); + error = -ENOMEM; + goto out; + } + + snprintf(priv->phys3, sizeof(priv->phys3), "%s/%s", + psmouse->ps2dev.serio->phys, + (priv->dev2 ? "input2" : "input1")); + dev3->phys = priv->phys3; + + /* + * format of input device name is: "protocol vendor name" + * see function psmouse_switch_protocol() in psmouse-base.c + */ + dev3->name = "PS/2 ALPS Mouse"; + + dev3->id.bustype = BUS_I8042; + dev3->id.vendor = 0x0002; + dev3->id.product = PSMOUSE_PS2; + dev3->id.version = 0x0000; + dev3->dev.parent = &psmouse->ps2dev.serio->dev; + + input_set_capability(dev3, EV_REL, REL_X); + input_set_capability(dev3, EV_REL, REL_Y); + input_set_capability(dev3, EV_KEY, BTN_LEFT); + input_set_capability(dev3, EV_KEY, BTN_RIGHT); + input_set_capability(dev3, EV_KEY, BTN_MIDDLE); + + __set_bit(INPUT_PROP_POINTER, dev3->propbit); + + error = input_register_device(dev3); + if (error) { + psmouse_err(psmouse, + "failed to register secondary device: %d\n", + error); + input_free_device(dev3); + goto out; + } + + priv->dev3 = dev3; + +out: + /* + * Save the error code so that we can detect that we + * already tried to create the device. + */ + if (error) + priv->dev3 = ERR_PTR(error); + + mutex_unlock(&alps_mutex); +} + +static void alps_report_bare_ps2_packet(struct psmouse *psmouse, + unsigned char packet[], + bool report_buttons) +{ + struct alps_data *priv = psmouse->private; + struct input_dev *dev, *dev2 = NULL; + + /* Figure out which device to use to report the bare packet */ + if (priv->proto_version == ALPS_PROTO_V2 && + (priv->flags & ALPS_DUALPOINT)) { + /* On V2 devices the DualPoint Stick reports bare packets */ + dev = priv->dev2; + dev2 = psmouse->dev; + } else if (unlikely(IS_ERR_OR_NULL(priv->dev3))) { + /* Register dev3 mouse if we received PS/2 packet first time */ + if (!IS_ERR(priv->dev3)) + psmouse_queue_work(psmouse, &priv->dev3_register_work, + 0); + return; + } else { + dev = priv->dev3; + } + + if (report_buttons) + alps_report_buttons(dev, dev2, + packet[0] & 1, packet[0] & 2, packet[0] & 4); + + psmouse_report_standard_motion(dev, packet); + + input_sync(dev); +} + +static psmouse_ret_t alps_handle_interleaved_ps2(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + + if (psmouse->pktcnt < 6) + return PSMOUSE_GOOD_DATA; + + if (psmouse->pktcnt == 6) { + /* + * Start a timer to flush the packet if it ends up last + * 6-byte packet in the stream. Timer needs to fire + * psmouse core times out itself. 20 ms should be enough + * to decide if we are getting more data or not. + */ + mod_timer(&priv->timer, jiffies + msecs_to_jiffies(20)); + return PSMOUSE_GOOD_DATA; + } + + del_timer(&priv->timer); + + if (psmouse->packet[6] & 0x80) { + + /* + * Highest bit is set - that means we either had + * complete ALPS packet and this is start of the + * next packet or we got garbage. + */ + + if (((psmouse->packet[3] | + psmouse->packet[4] | + psmouse->packet[5]) & 0x80) || + (!alps_is_valid_first_byte(priv, psmouse->packet[6]))) { + psmouse_dbg(psmouse, + "refusing packet %4ph (suspected interleaved ps/2)\n", + psmouse->packet + 3); + return PSMOUSE_BAD_DATA; + } + + priv->process_packet(psmouse); + + /* Continue with the next packet */ + psmouse->packet[0] = psmouse->packet[6]; + psmouse->pktcnt = 1; + + } else { + + /* + * High bit is 0 - that means that we indeed got a PS/2 + * packet in the middle of ALPS packet. + * + * There is also possibility that we got 6-byte ALPS + * packet followed by 3-byte packet from trackpoint. We + * can not distinguish between these 2 scenarios but + * because the latter is unlikely to happen in course of + * normal operation (user would need to press all + * buttons on the pad and start moving trackpoint + * without touching the pad surface) we assume former. + * Even if we are wrong the wost thing that would happen + * the cursor would jump but we should not get protocol + * de-synchronization. + */ + + alps_report_bare_ps2_packet(psmouse, &psmouse->packet[3], + false); + + /* + * Continue with the standard ALPS protocol handling, + * but make sure we won't process it as an interleaved + * packet again, which may happen if all buttons are + * pressed. To avoid this let's reset the 4th bit which + * is normally 1. + */ + psmouse->packet[3] = psmouse->packet[6] & 0xf7; + psmouse->pktcnt = 4; + } + + return PSMOUSE_GOOD_DATA; +} + +static void alps_flush_packet(struct timer_list *t) +{ + struct alps_data *priv = from_timer(priv, t, timer); + struct psmouse *psmouse = priv->psmouse; + + serio_pause_rx(psmouse->ps2dev.serio); + + if (psmouse->pktcnt == psmouse->pktsize) { + + /* + * We did not any more data in reasonable amount of time. + * Validate the last 3 bytes and process as a standard + * ALPS packet. + */ + if ((psmouse->packet[3] | + psmouse->packet[4] | + psmouse->packet[5]) & 0x80) { + psmouse_dbg(psmouse, + "refusing packet %3ph (suspected interleaved ps/2)\n", + psmouse->packet + 3); + } else { + priv->process_packet(psmouse); + } + psmouse->pktcnt = 0; + } + + serio_continue_rx(psmouse->ps2dev.serio); +} + +static psmouse_ret_t alps_process_byte(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + + /* + * Check if we are dealing with a bare PS/2 packet, presumably from + * a device connected to the external PS/2 port. Because bare PS/2 + * protocol does not have enough constant bits to self-synchronize + * properly we only do this if the device is fully synchronized. + * Can not distinguish V8's first byte from PS/2 packet's + */ + if (priv->proto_version != ALPS_PROTO_V8 && + !psmouse->out_of_sync_cnt && + (psmouse->packet[0] & 0xc8) == 0x08) { + + if (psmouse->pktcnt == 3) { + alps_report_bare_ps2_packet(psmouse, psmouse->packet, + true); + return PSMOUSE_FULL_PACKET; + } + return PSMOUSE_GOOD_DATA; + } + + /* Check for PS/2 packet stuffed in the middle of ALPS packet. */ + + if ((priv->flags & ALPS_PS2_INTERLEAVED) && + psmouse->pktcnt >= 4 && (psmouse->packet[3] & 0x0f) == 0x0f) { + return alps_handle_interleaved_ps2(psmouse); + } + + if (!alps_is_valid_first_byte(priv, psmouse->packet[0])) { + psmouse_dbg(psmouse, + "refusing packet[0] = %x (mask0 = %x, byte0 = %x)\n", + psmouse->packet[0], priv->mask0, priv->byte0); + return PSMOUSE_BAD_DATA; + } + + /* Bytes 2 - pktsize should have 0 in the highest bit */ + if (priv->proto_version < ALPS_PROTO_V5 && + psmouse->pktcnt >= 2 && psmouse->pktcnt <= psmouse->pktsize && + (psmouse->packet[psmouse->pktcnt - 1] & 0x80)) { + psmouse_dbg(psmouse, "refusing packet[%i] = %x\n", + psmouse->pktcnt - 1, + psmouse->packet[psmouse->pktcnt - 1]); + + if (priv->proto_version == ALPS_PROTO_V3_RUSHMORE && + psmouse->pktcnt == psmouse->pktsize) { + /* + * Some Dell boxes, such as Latitude E6440 or E7440 + * with closed lid, quite often smash last byte of + * otherwise valid packet with 0xff. Given that the + * next packet is very likely to be valid let's + * report PSMOUSE_FULL_PACKET but not process data, + * rather than reporting PSMOUSE_BAD_DATA and + * filling the logs. + */ + return PSMOUSE_FULL_PACKET; + } + + return PSMOUSE_BAD_DATA; + } + + if ((priv->proto_version == ALPS_PROTO_V7 && + !alps_is_valid_package_v7(psmouse)) || + (priv->proto_version == ALPS_PROTO_V8 && + !alps_is_valid_package_ss4_v2(psmouse))) { + psmouse_dbg(psmouse, "refusing packet[%i] = %x\n", + psmouse->pktcnt - 1, + psmouse->packet[psmouse->pktcnt - 1]); + return PSMOUSE_BAD_DATA; + } + + if (psmouse->pktcnt == psmouse->pktsize) { + priv->process_packet(psmouse); + return PSMOUSE_FULL_PACKET; + } + + return PSMOUSE_GOOD_DATA; +} + +static int alps_command_mode_send_nibble(struct psmouse *psmouse, int nibble) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + struct alps_data *priv = psmouse->private; + int command; + unsigned char *param; + unsigned char dummy[4]; + + BUG_ON(nibble > 0xf); + + command = priv->nibble_commands[nibble].command; + param = (command & 0x0f00) ? + dummy : (unsigned char *)&priv->nibble_commands[nibble].data; + + if (ps2_command(ps2dev, param, command)) + return -1; + + return 0; +} + +static int alps_command_mode_set_addr(struct psmouse *psmouse, int addr) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + struct alps_data *priv = psmouse->private; + int i, nibble; + + if (ps2_command(ps2dev, NULL, priv->addr_command)) + return -1; + + for (i = 12; i >= 0; i -= 4) { + nibble = (addr >> i) & 0xf; + if (alps_command_mode_send_nibble(psmouse, nibble)) + return -1; + } + + return 0; +} + +static int __alps_command_mode_read_reg(struct psmouse *psmouse, int addr) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[4]; + + if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) + return -1; + + /* + * The address being read is returned in the first two bytes + * of the result. Check that this address matches the expected + * address. + */ + if (addr != ((param[0] << 8) | param[1])) + return -1; + + return param[2]; +} + +static int alps_command_mode_read_reg(struct psmouse *psmouse, int addr) +{ + if (alps_command_mode_set_addr(psmouse, addr)) + return -1; + return __alps_command_mode_read_reg(psmouse, addr); +} + +static int __alps_command_mode_write_reg(struct psmouse *psmouse, u8 value) +{ + if (alps_command_mode_send_nibble(psmouse, (value >> 4) & 0xf)) + return -1; + if (alps_command_mode_send_nibble(psmouse, value & 0xf)) + return -1; + return 0; +} + +static int alps_command_mode_write_reg(struct psmouse *psmouse, int addr, + u8 value) +{ + if (alps_command_mode_set_addr(psmouse, addr)) + return -1; + return __alps_command_mode_write_reg(psmouse, value); +} + +static int alps_rpt_cmd(struct psmouse *psmouse, int init_command, + int repeated_command, unsigned char *param) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + + param[0] = 0; + if (init_command && ps2_command(ps2dev, param, init_command)) + return -EIO; + + if (ps2_command(ps2dev, NULL, repeated_command) || + ps2_command(ps2dev, NULL, repeated_command) || + ps2_command(ps2dev, NULL, repeated_command)) + return -EIO; + + param[0] = param[1] = param[2] = 0xff; + if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) + return -EIO; + + psmouse_dbg(psmouse, "%2.2X report: %3ph\n", + repeated_command, param); + return 0; +} + +static bool alps_check_valid_firmware_id(unsigned char id[]) +{ + if (id[0] == 0x73) + return true; + + if (id[0] == 0x88 && + (id[1] == 0x07 || + id[1] == 0x08 || + (id[1] & 0xf0) == 0xb0 || + (id[1] & 0xf0) == 0xc0)) { + return true; + } + + return false; +} + +static int alps_enter_command_mode(struct psmouse *psmouse) +{ + unsigned char param[4]; + + if (alps_rpt_cmd(psmouse, 0, PSMOUSE_CMD_RESET_WRAP, param)) { + psmouse_err(psmouse, "failed to enter command mode\n"); + return -1; + } + + if (!alps_check_valid_firmware_id(param)) { + psmouse_dbg(psmouse, + "unknown response while entering command mode\n"); + return -1; + } + return 0; +} + +static inline int alps_exit_command_mode(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSTREAM)) + return -1; + return 0; +} + +/* + * For DualPoint devices select the device that should respond to + * subsequent commands. It looks like glidepad is behind stickpointer, + * I'd thought it would be other way around... + */ +static int alps_passthrough_mode_v2(struct psmouse *psmouse, bool enable) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + int cmd = enable ? PSMOUSE_CMD_SETSCALE21 : PSMOUSE_CMD_SETSCALE11; + + if (ps2_command(ps2dev, NULL, cmd) || + ps2_command(ps2dev, NULL, cmd) || + ps2_command(ps2dev, NULL, cmd) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE)) + return -1; + + /* we may get 3 more bytes, just ignore them */ + ps2_drain(ps2dev, 3, 100); + + return 0; +} + +static int alps_absolute_mode_v1_v2(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + + /* Try ALPS magic knock - 4 disable before enable */ + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE)) + return -1; + + /* + * Switch mouse to poll (remote) mode so motion data will not + * get in our way + */ + return ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETPOLL); +} + +static int alps_monitor_mode_send_word(struct psmouse *psmouse, u16 word) +{ + int i, nibble; + + /* + * b0-b11 are valid bits, send sequence is inverse. + * e.g. when word = 0x0123, nibble send sequence is 3, 2, 1 + */ + for (i = 0; i <= 8; i += 4) { + nibble = (word >> i) & 0xf; + if (alps_command_mode_send_nibble(psmouse, nibble)) + return -1; + } + + return 0; +} + +static int alps_monitor_mode_write_reg(struct psmouse *psmouse, + u16 addr, u16 value) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + + /* 0x0A0 is the command to write the word */ + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE) || + alps_monitor_mode_send_word(psmouse, 0x0A0) || + alps_monitor_mode_send_word(psmouse, addr) || + alps_monitor_mode_send_word(psmouse, value) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE)) + return -1; + + return 0; +} + +static int alps_monitor_mode(struct psmouse *psmouse, bool enable) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + + if (enable) { + /* EC E9 F5 F5 E7 E6 E7 E9 to enter monitor mode */ + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_RESET_WRAP) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_GETINFO) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_GETINFO)) + return -1; + } else { + /* EC to exit monitor mode */ + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_RESET_WRAP)) + return -1; + } + + return 0; +} + +static int alps_absolute_mode_v6(struct psmouse *psmouse) +{ + u16 reg_val = 0x181; + int ret = -1; + + /* enter monitor mode, to write the register */ + if (alps_monitor_mode(psmouse, true)) + return -1; + + ret = alps_monitor_mode_write_reg(psmouse, 0x000, reg_val); + + if (alps_monitor_mode(psmouse, false)) + ret = -1; + + return ret; +} + +static int alps_get_status(struct psmouse *psmouse, char *param) +{ + /* Get status: 0xF5 0xF5 0xF5 0xE9 */ + if (alps_rpt_cmd(psmouse, 0, PSMOUSE_CMD_DISABLE, param)) + return -1; + + return 0; +} + +/* + * Turn touchpad tapping on or off. The sequences are: + * 0xE9 0xF5 0xF5 0xF3 0x0A to enable, + * 0xE9 0xF5 0xF5 0xE8 0x00 to disable. + * My guess that 0xE9 (GetInfo) is here as a sync point. + * For models that also have stickpointer (DualPoints) its tapping + * is controlled separately (0xE6 0xE6 0xE6 0xF3 0x14|0x0A) but + * we don't fiddle with it. + */ +static int alps_tap_mode(struct psmouse *psmouse, int enable) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + int cmd = enable ? PSMOUSE_CMD_SETRATE : PSMOUSE_CMD_SETRES; + unsigned char tap_arg = enable ? 0x0A : 0x00; + unsigned char param[4]; + + if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, &tap_arg, cmd)) + return -1; + + if (alps_get_status(psmouse, param)) + return -1; + + return 0; +} + +/* + * alps_poll() - poll the touchpad for current motion packet. + * Used in resync. + */ +static int alps_poll(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + unsigned char buf[sizeof(psmouse->packet)]; + bool poll_failed; + + if (priv->flags & ALPS_PASS) + alps_passthrough_mode_v2(psmouse, true); + + poll_failed = ps2_command(&psmouse->ps2dev, buf, + PSMOUSE_CMD_POLL | (psmouse->pktsize << 8)) < 0; + + if (priv->flags & ALPS_PASS) + alps_passthrough_mode_v2(psmouse, false); + + if (poll_failed || (buf[0] & priv->mask0) != priv->byte0) + return -1; + + if ((psmouse->badbyte & 0xc8) == 0x08) { +/* + * Poll the track stick ... + */ + if (ps2_command(&psmouse->ps2dev, buf, PSMOUSE_CMD_POLL | (3 << 8))) + return -1; + } + + memcpy(psmouse->packet, buf, sizeof(buf)); + return 0; +} + +static int alps_hw_init_v1_v2(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + + if ((priv->flags & ALPS_PASS) && + alps_passthrough_mode_v2(psmouse, true)) { + return -1; + } + + if (alps_tap_mode(psmouse, true)) { + psmouse_warn(psmouse, "Failed to enable hardware tapping\n"); + return -1; + } + + if (alps_absolute_mode_v1_v2(psmouse)) { + psmouse_err(psmouse, "Failed to enable absolute mode\n"); + return -1; + } + + if ((priv->flags & ALPS_PASS) && + alps_passthrough_mode_v2(psmouse, false)) { + return -1; + } + + /* ALPS needs stream mode, otherwise it won't report any data */ + if (ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSTREAM)) { + psmouse_err(psmouse, "Failed to enable stream mode\n"); + return -1; + } + + return 0; +} + +/* Must be in passthrough mode when calling this function */ +static int alps_trackstick_enter_extended_mode_v3_v6(struct psmouse *psmouse) +{ + unsigned char param[2] = {0xC8, 0x14}; + + if (ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) || + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) || + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) || + ps2_command(&psmouse->ps2dev, ¶m[0], PSMOUSE_CMD_SETRATE) || + ps2_command(&psmouse->ps2dev, ¶m[1], PSMOUSE_CMD_SETRATE)) + return -1; + + return 0; +} + +static int alps_hw_init_v6(struct psmouse *psmouse) +{ + int ret; + + /* Enter passthrough mode to let trackpoint enter 6byte raw mode */ + if (alps_passthrough_mode_v2(psmouse, true)) + return -1; + + ret = alps_trackstick_enter_extended_mode_v3_v6(psmouse); + + if (alps_passthrough_mode_v2(psmouse, false)) + return -1; + + if (ret) + return ret; + + if (alps_absolute_mode_v6(psmouse)) { + psmouse_err(psmouse, "Failed to enable absolute mode\n"); + return -1; + } + + return 0; +} + +/* + * Enable or disable passthrough mode to the trackstick. + */ +static int alps_passthrough_mode_v3(struct psmouse *psmouse, + int reg_base, bool enable) +{ + int reg_val, ret = -1; + + if (alps_enter_command_mode(psmouse)) + return -1; + + reg_val = alps_command_mode_read_reg(psmouse, reg_base + 0x0008); + if (reg_val == -1) + goto error; + + if (enable) + reg_val |= 0x01; + else + reg_val &= ~0x01; + + ret = __alps_command_mode_write_reg(psmouse, reg_val); + +error: + if (alps_exit_command_mode(psmouse)) + ret = -1; + return ret; +} + +/* Must be in command mode when calling this function */ +static int alps_absolute_mode_v3(struct psmouse *psmouse) +{ + int reg_val; + + reg_val = alps_command_mode_read_reg(psmouse, 0x0004); + if (reg_val == -1) + return -1; + + reg_val |= 0x06; + if (__alps_command_mode_write_reg(psmouse, reg_val)) + return -1; + + return 0; +} + +static int alps_probe_trackstick_v3_v7(struct psmouse *psmouse, int reg_base) +{ + int ret = -EIO, reg_val; + + if (alps_enter_command_mode(psmouse)) + goto error; + + reg_val = alps_command_mode_read_reg(psmouse, reg_base + 0x08); + if (reg_val == -1) + goto error; + + /* bit 7: trackstick is present */ + ret = reg_val & 0x80 ? 0 : -ENODEV; + +error: + alps_exit_command_mode(psmouse); + return ret; +} + +static int alps_setup_trackstick_v3(struct psmouse *psmouse, int reg_base) +{ + int ret = 0; + int reg_val; + unsigned char param[4]; + + /* + * We need to configure trackstick to report data for touchpad in + * extended format. And also we need to tell touchpad to expect data + * from trackstick in extended format. Without this configuration + * trackstick packets sent from touchpad are in basic format which is + * different from what we expect. + */ + + if (alps_passthrough_mode_v3(psmouse, reg_base, true)) + return -EIO; + + /* + * E7 report for the trackstick + * + * There have been reports of failures to seem to trace back + * to the above trackstick check failing. When these occur + * this E7 report fails, so when that happens we continue + * with the assumption that there isn't a trackstick after + * all. + */ + if (alps_rpt_cmd(psmouse, 0, PSMOUSE_CMD_SETSCALE21, param)) { + psmouse_warn(psmouse, "Failed to initialize trackstick (E7 report failed)\n"); + ret = -ENODEV; + } else { + psmouse_dbg(psmouse, "trackstick E7 report: %3ph\n", param); + if (alps_trackstick_enter_extended_mode_v3_v6(psmouse)) { + psmouse_err(psmouse, "Failed to enter into trackstick extended mode\n"); + ret = -EIO; + } + } + + if (alps_passthrough_mode_v3(psmouse, reg_base, false)) + return -EIO; + + if (ret) + return ret; + + if (alps_enter_command_mode(psmouse)) + return -EIO; + + reg_val = alps_command_mode_read_reg(psmouse, reg_base + 0x08); + if (reg_val == -1) { + ret = -EIO; + } else { + /* + * Tell touchpad that trackstick is now in extended mode. + * If bit 1 isn't set the packet format is different. + */ + reg_val |= BIT(1); + if (__alps_command_mode_write_reg(psmouse, reg_val)) + ret = -EIO; + } + + if (alps_exit_command_mode(psmouse)) + return -EIO; + + return ret; +} + +static int alps_hw_init_v3(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + struct ps2dev *ps2dev = &psmouse->ps2dev; + int reg_val; + unsigned char param[4]; + + if ((priv->flags & ALPS_DUALPOINT) && + alps_setup_trackstick_v3(psmouse, ALPS_REG_BASE_PINNACLE) == -EIO) + goto error; + + if (alps_enter_command_mode(psmouse) || + alps_absolute_mode_v3(psmouse)) { + psmouse_err(psmouse, "Failed to enter absolute mode\n"); + goto error; + } + + reg_val = alps_command_mode_read_reg(psmouse, 0x0006); + if (reg_val == -1) + goto error; + if (__alps_command_mode_write_reg(psmouse, reg_val | 0x01)) + goto error; + + reg_val = alps_command_mode_read_reg(psmouse, 0x0007); + if (reg_val == -1) + goto error; + if (__alps_command_mode_write_reg(psmouse, reg_val | 0x01)) + goto error; + + if (alps_command_mode_read_reg(psmouse, 0x0144) == -1) + goto error; + if (__alps_command_mode_write_reg(psmouse, 0x04)) + goto error; + + if (alps_command_mode_read_reg(psmouse, 0x0159) == -1) + goto error; + if (__alps_command_mode_write_reg(psmouse, 0x03)) + goto error; + + if (alps_command_mode_read_reg(psmouse, 0x0163) == -1) + goto error; + if (alps_command_mode_write_reg(psmouse, 0x0163, 0x03)) + goto error; + + if (alps_command_mode_read_reg(psmouse, 0x0162) == -1) + goto error; + if (alps_command_mode_write_reg(psmouse, 0x0162, 0x04)) + goto error; + + alps_exit_command_mode(psmouse); + + /* Set rate and enable data reporting */ + param[0] = 0x64; + if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE)) { + psmouse_err(psmouse, "Failed to enable data reporting\n"); + return -1; + } + + return 0; + +error: + /* + * Leaving the touchpad in command mode will essentially render + * it unusable until the machine reboots, so exit it here just + * to be safe + */ + alps_exit_command_mode(psmouse); + return -1; +} + +static int alps_get_v3_v7_resolution(struct psmouse *psmouse, int reg_pitch) +{ + int reg, x_pitch, y_pitch, x_electrode, y_electrode, x_phys, y_phys; + struct alps_data *priv = psmouse->private; + + reg = alps_command_mode_read_reg(psmouse, reg_pitch); + if (reg < 0) + return reg; + + x_pitch = (char)(reg << 4) >> 4; /* sign extend lower 4 bits */ + x_pitch = 50 + 2 * x_pitch; /* In 0.1 mm units */ + + y_pitch = (char)reg >> 4; /* sign extend upper 4 bits */ + y_pitch = 36 + 2 * y_pitch; /* In 0.1 mm units */ + + reg = alps_command_mode_read_reg(psmouse, reg_pitch + 1); + if (reg < 0) + return reg; + + x_electrode = (char)(reg << 4) >> 4; /* sign extend lower 4 bits */ + x_electrode = 17 + x_electrode; + + y_electrode = (char)reg >> 4; /* sign extend upper 4 bits */ + y_electrode = 13 + y_electrode; + + x_phys = x_pitch * (x_electrode - 1); /* In 0.1 mm units */ + y_phys = y_pitch * (y_electrode - 1); /* In 0.1 mm units */ + + priv->x_res = priv->x_max * 10 / x_phys; /* units / mm */ + priv->y_res = priv->y_max * 10 / y_phys; /* units / mm */ + + psmouse_dbg(psmouse, + "pitch %dx%d num-electrodes %dx%d physical size %dx%d mm res %dx%d\n", + x_pitch, y_pitch, x_electrode, y_electrode, + x_phys / 10, y_phys / 10, priv->x_res, priv->y_res); + + return 0; +} + +static int alps_hw_init_rushmore_v3(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + struct ps2dev *ps2dev = &psmouse->ps2dev; + int reg_val, ret = -1; + + if (priv->flags & ALPS_DUALPOINT) { + reg_val = alps_setup_trackstick_v3(psmouse, + ALPS_REG_BASE_RUSHMORE); + if (reg_val == -EIO) + goto error; + } + + if (alps_enter_command_mode(psmouse) || + alps_command_mode_read_reg(psmouse, 0xc2d9) == -1 || + alps_command_mode_write_reg(psmouse, 0xc2cb, 0x00)) + goto error; + + if (alps_get_v3_v7_resolution(psmouse, 0xc2da)) + goto error; + + reg_val = alps_command_mode_read_reg(psmouse, 0xc2c6); + if (reg_val == -1) + goto error; + if (__alps_command_mode_write_reg(psmouse, reg_val & 0xfd)) + goto error; + + if (alps_command_mode_write_reg(psmouse, 0xc2c9, 0x64)) + goto error; + + /* enter absolute mode */ + reg_val = alps_command_mode_read_reg(psmouse, 0xc2c4); + if (reg_val == -1) + goto error; + if (__alps_command_mode_write_reg(psmouse, reg_val | 0x02)) + goto error; + + alps_exit_command_mode(psmouse); + return ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE); + +error: + alps_exit_command_mode(psmouse); + return ret; +} + +/* Must be in command mode when calling this function */ +static int alps_absolute_mode_v4(struct psmouse *psmouse) +{ + int reg_val; + + reg_val = alps_command_mode_read_reg(psmouse, 0x0004); + if (reg_val == -1) + return -1; + + reg_val |= 0x02; + if (__alps_command_mode_write_reg(psmouse, reg_val)) + return -1; + + return 0; +} + +static int alps_hw_init_v4(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[4]; + + if (alps_enter_command_mode(psmouse)) + goto error; + + if (alps_absolute_mode_v4(psmouse)) { + psmouse_err(psmouse, "Failed to enter absolute mode\n"); + goto error; + } + + if (alps_command_mode_write_reg(psmouse, 0x0007, 0x8c)) + goto error; + + if (alps_command_mode_write_reg(psmouse, 0x0149, 0x03)) + goto error; + + if (alps_command_mode_write_reg(psmouse, 0x0160, 0x03)) + goto error; + + if (alps_command_mode_write_reg(psmouse, 0x017f, 0x15)) + goto error; + + if (alps_command_mode_write_reg(psmouse, 0x0151, 0x01)) + goto error; + + if (alps_command_mode_write_reg(psmouse, 0x0168, 0x03)) + goto error; + + if (alps_command_mode_write_reg(psmouse, 0x014a, 0x03)) + goto error; + + if (alps_command_mode_write_reg(psmouse, 0x0161, 0x03)) + goto error; + + alps_exit_command_mode(psmouse); + + /* + * This sequence changes the output from a 9-byte to an + * 8-byte format. All the same data seems to be present, + * just in a more compact format. + */ + param[0] = 0xc8; + param[1] = 0x64; + param[2] = 0x50; + if (ps2_command(ps2dev, ¶m[0], PSMOUSE_CMD_SETRATE) || + ps2_command(ps2dev, ¶m[1], PSMOUSE_CMD_SETRATE) || + ps2_command(ps2dev, ¶m[2], PSMOUSE_CMD_SETRATE) || + ps2_command(ps2dev, param, PSMOUSE_CMD_GETID)) + return -1; + + /* Set rate and enable data reporting */ + param[0] = 0x64; + if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE)) { + psmouse_err(psmouse, "Failed to enable data reporting\n"); + return -1; + } + + return 0; + +error: + /* + * Leaving the touchpad in command mode will essentially render + * it unusable until the machine reboots, so exit it here just + * to be safe + */ + alps_exit_command_mode(psmouse); + return -1; +} + +static int alps_get_otp_values_ss4_v2(struct psmouse *psmouse, + unsigned char index, unsigned char otp[]) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + + switch (index) { + case 0: + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSTREAM) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSTREAM) || + ps2_command(ps2dev, otp, PSMOUSE_CMD_GETINFO)) + return -1; + + break; + + case 1: + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETPOLL) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETPOLL) || + ps2_command(ps2dev, otp, PSMOUSE_CMD_GETINFO)) + return -1; + + break; + } + + return 0; +} + +static int alps_update_device_area_ss4_v2(unsigned char otp[][4], + struct alps_data *priv) +{ + int num_x_electrode; + int num_y_electrode; + int x_pitch, y_pitch, x_phys, y_phys; + + if (IS_SS4PLUS_DEV(priv->dev_id)) { + num_x_electrode = + SS4PLUS_NUMSENSOR_XOFFSET + (otp[0][2] & 0x0F); + num_y_electrode = + SS4PLUS_NUMSENSOR_YOFFSET + ((otp[0][2] >> 4) & 0x0F); + + priv->x_max = + (num_x_electrode - 1) * SS4PLUS_COUNT_PER_ELECTRODE; + priv->y_max = + (num_y_electrode - 1) * SS4PLUS_COUNT_PER_ELECTRODE; + + x_pitch = (otp[0][1] & 0x0F) + SS4PLUS_MIN_PITCH_MM; + y_pitch = ((otp[0][1] >> 4) & 0x0F) + SS4PLUS_MIN_PITCH_MM; + + } else { + num_x_electrode = + SS4_NUMSENSOR_XOFFSET + (otp[1][0] & 0x0F); + num_y_electrode = + SS4_NUMSENSOR_YOFFSET + ((otp[1][0] >> 4) & 0x0F); + + priv->x_max = + (num_x_electrode - 1) * SS4_COUNT_PER_ELECTRODE; + priv->y_max = + (num_y_electrode - 1) * SS4_COUNT_PER_ELECTRODE; + + x_pitch = ((otp[1][2] >> 2) & 0x07) + SS4_MIN_PITCH_MM; + y_pitch = ((otp[1][2] >> 5) & 0x07) + SS4_MIN_PITCH_MM; + } + + x_phys = x_pitch * (num_x_electrode - 1); /* In 0.1 mm units */ + y_phys = y_pitch * (num_y_electrode - 1); /* In 0.1 mm units */ + + priv->x_res = priv->x_max * 10 / x_phys; /* units / mm */ + priv->y_res = priv->y_max * 10 / y_phys; /* units / mm */ + + return 0; +} + +static int alps_update_btn_info_ss4_v2(unsigned char otp[][4], + struct alps_data *priv) +{ + unsigned char is_btnless; + + if (IS_SS4PLUS_DEV(priv->dev_id)) + is_btnless = (otp[1][0] >> 1) & 0x01; + else + is_btnless = (otp[1][1] >> 3) & 0x01; + + if (is_btnless) + priv->flags |= ALPS_BUTTONPAD; + + return 0; +} + +static int alps_update_dual_info_ss4_v2(unsigned char otp[][4], + struct alps_data *priv, + struct psmouse *psmouse) +{ + bool is_dual = false; + int reg_val = 0; + struct ps2dev *ps2dev = &psmouse->ps2dev; + + if (IS_SS4PLUS_DEV(priv->dev_id)) { + is_dual = (otp[0][0] >> 4) & 0x01; + + if (!is_dual) { + /* For support TrackStick of Thinkpad L/E series */ + if (alps_exit_command_mode(psmouse) == 0 && + alps_enter_command_mode(psmouse) == 0) { + reg_val = alps_command_mode_read_reg(psmouse, + 0xD7); + } + alps_exit_command_mode(psmouse); + ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE); + + if (reg_val == 0x0C || reg_val == 0x1D) + is_dual = true; + } + } + + if (is_dual) + priv->flags |= ALPS_DUALPOINT | + ALPS_DUALPOINT_WITH_PRESSURE; + + return 0; +} + +static int alps_set_defaults_ss4_v2(struct psmouse *psmouse, + struct alps_data *priv) +{ + unsigned char otp[2][4]; + + memset(otp, 0, sizeof(otp)); + + if (alps_get_otp_values_ss4_v2(psmouse, 1, &otp[1][0]) || + alps_get_otp_values_ss4_v2(psmouse, 0, &otp[0][0])) + return -1; + + alps_update_device_area_ss4_v2(otp, priv); + + alps_update_btn_info_ss4_v2(otp, priv); + + alps_update_dual_info_ss4_v2(otp, priv, psmouse); + + return 0; +} + +static int alps_dolphin_get_device_area(struct psmouse *psmouse, + struct alps_data *priv) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[4] = {0}; + int num_x_electrode, num_y_electrode; + + if (alps_enter_command_mode(psmouse)) + return -1; + + param[0] = 0x0a; + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_RESET_WRAP) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETPOLL) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETPOLL) || + ps2_command(ps2dev, ¶m[0], PSMOUSE_CMD_SETRATE) || + ps2_command(ps2dev, ¶m[0], PSMOUSE_CMD_SETRATE)) + return -1; + + if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) + return -1; + + /* + * Dolphin's sensor line number is not fixed. It can be calculated + * by adding the device's register value with DOLPHIN_PROFILE_X/YOFFSET. + * Further more, we can get device's x_max and y_max by multiplying + * sensor line number with DOLPHIN_COUNT_PER_ELECTRODE. + * + * e.g. When we get register's sensor_x = 11 & sensor_y = 8, + * real sensor line number X = 11 + 8 = 19, and + * real sensor line number Y = 8 + 1 = 9. + * So, x_max = (19 - 1) * 64 = 1152, and + * y_max = (9 - 1) * 64 = 512. + */ + num_x_electrode = DOLPHIN_PROFILE_XOFFSET + (param[2] & 0x0F); + num_y_electrode = DOLPHIN_PROFILE_YOFFSET + ((param[2] >> 4) & 0x0F); + priv->x_bits = num_x_electrode; + priv->y_bits = num_y_electrode; + priv->x_max = (num_x_electrode - 1) * DOLPHIN_COUNT_PER_ELECTRODE; + priv->y_max = (num_y_electrode - 1) * DOLPHIN_COUNT_PER_ELECTRODE; + + if (alps_exit_command_mode(psmouse)) + return -1; + + return 0; +} + +static int alps_hw_init_dolphin_v1(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[2]; + + /* This is dolphin "v1" as empirically defined by florin9doi */ + param[0] = 0x64; + param[1] = 0x28; + + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSTREAM) || + ps2_command(ps2dev, ¶m[0], PSMOUSE_CMD_SETRATE) || + ps2_command(ps2dev, ¶m[1], PSMOUSE_CMD_SETRATE)) + return -1; + + return 0; +} + +static int alps_hw_init_v7(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + int reg_val, ret = -1; + + if (alps_enter_command_mode(psmouse) || + alps_command_mode_read_reg(psmouse, 0xc2d9) == -1) + goto error; + + if (alps_get_v3_v7_resolution(psmouse, 0xc397)) + goto error; + + if (alps_command_mode_write_reg(psmouse, 0xc2c9, 0x64)) + goto error; + + reg_val = alps_command_mode_read_reg(psmouse, 0xc2c4); + if (reg_val == -1) + goto error; + if (__alps_command_mode_write_reg(psmouse, reg_val | 0x02)) + goto error; + + alps_exit_command_mode(psmouse); + return ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE); + +error: + alps_exit_command_mode(psmouse); + return ret; +} + +static int alps_hw_init_ss4_v2(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + char param[2] = {0x64, 0x28}; + int ret = -1; + + /* enter absolute mode */ + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSTREAM) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSTREAM) || + ps2_command(ps2dev, ¶m[0], PSMOUSE_CMD_SETRATE) || + ps2_command(ps2dev, ¶m[1], PSMOUSE_CMD_SETRATE)) { + goto error; + } + + /* T.B.D. Decread noise packet number, delete in the future */ + if (alps_exit_command_mode(psmouse) || + alps_enter_command_mode(psmouse) || + alps_command_mode_write_reg(psmouse, 0x001D, 0x20)) { + goto error; + } + alps_exit_command_mode(psmouse); + + return ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE); + +error: + alps_exit_command_mode(psmouse); + return ret; +} + +static int alps_set_protocol(struct psmouse *psmouse, + struct alps_data *priv, + const struct alps_protocol_info *protocol) +{ + psmouse->private = priv; + + timer_setup(&priv->timer, alps_flush_packet, 0); + + priv->proto_version = protocol->version; + priv->byte0 = protocol->byte0; + priv->mask0 = protocol->mask0; + priv->flags = protocol->flags; + + priv->x_max = 2000; + priv->y_max = 1400; + priv->x_bits = 15; + priv->y_bits = 11; + + switch (priv->proto_version) { + case ALPS_PROTO_V1: + case ALPS_PROTO_V2: + priv->hw_init = alps_hw_init_v1_v2; + priv->process_packet = alps_process_packet_v1_v2; + priv->set_abs_params = alps_set_abs_params_st; + priv->x_max = 1023; + priv->y_max = 767; + if (dmi_check_system(alps_dmi_has_separate_stick_buttons)) + priv->flags |= ALPS_STICK_BITS; + break; + + case ALPS_PROTO_V3: + priv->hw_init = alps_hw_init_v3; + priv->process_packet = alps_process_packet_v3; + priv->set_abs_params = alps_set_abs_params_semi_mt; + priv->decode_fields = alps_decode_pinnacle; + priv->nibble_commands = alps_v3_nibble_commands; + priv->addr_command = PSMOUSE_CMD_RESET_WRAP; + + if (alps_probe_trackstick_v3_v7(psmouse, + ALPS_REG_BASE_PINNACLE) < 0) + priv->flags &= ~ALPS_DUALPOINT; + + break; + + case ALPS_PROTO_V3_RUSHMORE: + priv->hw_init = alps_hw_init_rushmore_v3; + priv->process_packet = alps_process_packet_v3; + priv->set_abs_params = alps_set_abs_params_semi_mt; + priv->decode_fields = alps_decode_rushmore; + priv->nibble_commands = alps_v3_nibble_commands; + priv->addr_command = PSMOUSE_CMD_RESET_WRAP; + priv->x_bits = 16; + priv->y_bits = 12; + + if (alps_probe_trackstick_v3_v7(psmouse, + ALPS_REG_BASE_RUSHMORE) < 0) + priv->flags &= ~ALPS_DUALPOINT; + + break; + + case ALPS_PROTO_V4: + priv->hw_init = alps_hw_init_v4; + priv->process_packet = alps_process_packet_v4; + priv->set_abs_params = alps_set_abs_params_semi_mt; + priv->nibble_commands = alps_v4_nibble_commands; + priv->addr_command = PSMOUSE_CMD_DISABLE; + break; + + case ALPS_PROTO_V5: + priv->hw_init = alps_hw_init_dolphin_v1; + priv->process_packet = alps_process_touchpad_packet_v3_v5; + priv->decode_fields = alps_decode_dolphin; + priv->set_abs_params = alps_set_abs_params_semi_mt; + priv->nibble_commands = alps_v3_nibble_commands; + priv->addr_command = PSMOUSE_CMD_RESET_WRAP; + priv->x_bits = 23; + priv->y_bits = 12; + + if (alps_dolphin_get_device_area(psmouse, priv)) + return -EIO; + + break; + + case ALPS_PROTO_V6: + priv->hw_init = alps_hw_init_v6; + priv->process_packet = alps_process_packet_v6; + priv->set_abs_params = alps_set_abs_params_st; + priv->nibble_commands = alps_v6_nibble_commands; + priv->x_max = 2047; + priv->y_max = 1535; + break; + + case ALPS_PROTO_V7: + priv->hw_init = alps_hw_init_v7; + priv->process_packet = alps_process_packet_v7; + priv->decode_fields = alps_decode_packet_v7; + priv->set_abs_params = alps_set_abs_params_v7; + priv->nibble_commands = alps_v3_nibble_commands; + priv->addr_command = PSMOUSE_CMD_RESET_WRAP; + priv->x_max = 0xfff; + priv->y_max = 0x7ff; + + if (priv->fw_ver[1] != 0xba) + priv->flags |= ALPS_BUTTONPAD; + + if (alps_probe_trackstick_v3_v7(psmouse, ALPS_REG_BASE_V7) < 0) + priv->flags &= ~ALPS_DUALPOINT; + + break; + + case ALPS_PROTO_V8: + priv->hw_init = alps_hw_init_ss4_v2; + priv->process_packet = alps_process_packet_ss4_v2; + priv->decode_fields = alps_decode_ss4_v2; + priv->set_abs_params = alps_set_abs_params_ss4_v2; + priv->nibble_commands = alps_v3_nibble_commands; + priv->addr_command = PSMOUSE_CMD_RESET_WRAP; + + if (alps_set_defaults_ss4_v2(psmouse, priv)) + return -EIO; + + break; + } + + return 0; +} + +static const struct alps_protocol_info *alps_match_table(unsigned char *e7, + unsigned char *ec) +{ + const struct alps_model_info *model; + int i; + + for (i = 0; i < ARRAY_SIZE(alps_model_data); i++) { + model = &alps_model_data[i]; + + if (!memcmp(e7, model->signature, sizeof(model->signature))) + return &model->protocol_info; + } + + return NULL; +} + +static bool alps_is_cs19_trackpoint(struct psmouse *psmouse) +{ + u8 param[2] = { 0 }; + + if (ps2_command(&psmouse->ps2dev, + param, MAKE_PS2_CMD(0, 2, TP_READ_ID))) + return false; + + /* + * param[0] contains the trackpoint device variant_id while + * param[1] contains the firmware_id. So far all alps + * trackpoint-only devices have their variant_ids equal + * TP_VARIANT_ALPS and their firmware_ids are in 0x20~0x2f range. + */ + return param[0] == TP_VARIANT_ALPS && ((param[1] & 0xf0) == 0x20); +} + +static int alps_identify(struct psmouse *psmouse, struct alps_data *priv) +{ + const struct alps_protocol_info *protocol; + unsigned char e6[4], e7[4], ec[4]; + int error; + + /* + * First try "E6 report". + * ALPS should return 0,0,10 or 0,0,100 if no buttons are pressed. + * The bits 0-2 of the first byte will be 1s if some buttons are + * pressed. + */ + if (alps_rpt_cmd(psmouse, PSMOUSE_CMD_SETRES, + PSMOUSE_CMD_SETSCALE11, e6)) + return -EIO; + + if ((e6[0] & 0xf8) != 0 || e6[1] != 0 || (e6[2] != 10 && e6[2] != 100)) + return -EINVAL; + + /* + * Now get the "E7" and "EC" reports. These will uniquely identify + * most ALPS touchpads. + */ + if (alps_rpt_cmd(psmouse, PSMOUSE_CMD_SETRES, + PSMOUSE_CMD_SETSCALE21, e7) || + alps_rpt_cmd(psmouse, PSMOUSE_CMD_SETRES, + PSMOUSE_CMD_RESET_WRAP, ec) || + alps_exit_command_mode(psmouse)) + return -EIO; + + protocol = alps_match_table(e7, ec); + if (!protocol) { + if (e7[0] == 0x73 && e7[1] == 0x02 && e7[2] == 0x64 && + ec[2] == 0x8a) { + protocol = &alps_v4_protocol_data; + } else if (e7[0] == 0x73 && e7[1] == 0x03 && e7[2] == 0x50 && + ec[0] == 0x73 && (ec[1] == 0x01 || ec[1] == 0x02)) { + protocol = &alps_v5_protocol_data; + } else if (ec[0] == 0x88 && + ((ec[1] & 0xf0) == 0xb0 || (ec[1] & 0xf0) == 0xc0)) { + protocol = &alps_v7_protocol_data; + } else if (ec[0] == 0x88 && ec[1] == 0x08) { + protocol = &alps_v3_rushmore_data; + } else if (ec[0] == 0x88 && ec[1] == 0x07 && + ec[2] >= 0x90 && ec[2] <= 0x9d) { + protocol = &alps_v3_protocol_data; + } else if (e7[0] == 0x73 && e7[1] == 0x03 && + (e7[2] == 0x14 || e7[2] == 0x28)) { + protocol = &alps_v8_protocol_data; + } else if (e7[0] == 0x73 && e7[1] == 0x03 && e7[2] == 0xc8) { + protocol = &alps_v9_protocol_data; + psmouse_warn(psmouse, + "Unsupported ALPS V9 touchpad: E7=%3ph, EC=%3ph\n", + e7, ec); + return -EINVAL; + } else { + psmouse_dbg(psmouse, + "Likely not an ALPS touchpad: E7=%3ph, EC=%3ph\n", e7, ec); + return -EINVAL; + } + } + + if (priv) { + /* Save Device ID and Firmware version */ + memcpy(priv->dev_id, e7, 3); + memcpy(priv->fw_ver, ec, 3); + error = alps_set_protocol(psmouse, priv, protocol); + if (error) + return error; + } + + return 0; +} + +static int alps_reconnect(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + + psmouse_reset(psmouse); + + if (alps_identify(psmouse, priv) < 0) + return -1; + + return priv->hw_init(psmouse); +} + +static void alps_disconnect(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + + psmouse_reset(psmouse); + del_timer_sync(&priv->timer); + if (priv->dev2) + input_unregister_device(priv->dev2); + if (!IS_ERR_OR_NULL(priv->dev3)) + input_unregister_device(priv->dev3); + kfree(priv); +} + +static void alps_set_abs_params_st(struct alps_data *priv, + struct input_dev *dev1) +{ + input_set_abs_params(dev1, ABS_X, 0, priv->x_max, 0, 0); + input_set_abs_params(dev1, ABS_Y, 0, priv->y_max, 0, 0); + input_set_abs_params(dev1, ABS_PRESSURE, 0, 127, 0, 0); +} + +static void alps_set_abs_params_mt_common(struct alps_data *priv, + struct input_dev *dev1) +{ + input_set_abs_params(dev1, ABS_MT_POSITION_X, 0, priv->x_max, 0, 0); + input_set_abs_params(dev1, ABS_MT_POSITION_Y, 0, priv->y_max, 0, 0); + + input_abs_set_res(dev1, ABS_MT_POSITION_X, priv->x_res); + input_abs_set_res(dev1, ABS_MT_POSITION_Y, priv->y_res); + + set_bit(BTN_TOOL_TRIPLETAP, dev1->keybit); + set_bit(BTN_TOOL_QUADTAP, dev1->keybit); +} + +static void alps_set_abs_params_semi_mt(struct alps_data *priv, + struct input_dev *dev1) +{ + alps_set_abs_params_mt_common(priv, dev1); + input_set_abs_params(dev1, ABS_PRESSURE, 0, 127, 0, 0); + + input_mt_init_slots(dev1, MAX_TOUCHES, + INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED | + INPUT_MT_SEMI_MT); +} + +static void alps_set_abs_params_v7(struct alps_data *priv, + struct input_dev *dev1) +{ + alps_set_abs_params_mt_common(priv, dev1); + set_bit(BTN_TOOL_QUINTTAP, dev1->keybit); + + input_mt_init_slots(dev1, MAX_TOUCHES, + INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED | + INPUT_MT_TRACK); + + set_bit(BTN_TOOL_QUINTTAP, dev1->keybit); +} + +static void alps_set_abs_params_ss4_v2(struct alps_data *priv, + struct input_dev *dev1) +{ + alps_set_abs_params_mt_common(priv, dev1); + input_set_abs_params(dev1, ABS_PRESSURE, 0, 127, 0, 0); + set_bit(BTN_TOOL_QUINTTAP, dev1->keybit); + + input_mt_init_slots(dev1, MAX_TOUCHES, + INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED | + INPUT_MT_TRACK); +} + +int alps_init(struct psmouse *psmouse) +{ + struct alps_data *priv = psmouse->private; + struct input_dev *dev1 = psmouse->dev; + int error; + + error = priv->hw_init(psmouse); + if (error) + goto init_fail; + + /* + * Undo part of setup done for us by psmouse core since touchpad + * is not a relative device. + */ + __clear_bit(EV_REL, dev1->evbit); + __clear_bit(REL_X, dev1->relbit); + __clear_bit(REL_Y, dev1->relbit); + + /* + * Now set up our capabilities. + */ + dev1->evbit[BIT_WORD(EV_KEY)] |= BIT_MASK(EV_KEY); + dev1->keybit[BIT_WORD(BTN_TOUCH)] |= BIT_MASK(BTN_TOUCH); + dev1->keybit[BIT_WORD(BTN_TOOL_FINGER)] |= BIT_MASK(BTN_TOOL_FINGER); + dev1->keybit[BIT_WORD(BTN_LEFT)] |= + BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_RIGHT); + + dev1->evbit[BIT_WORD(EV_ABS)] |= BIT_MASK(EV_ABS); + + priv->set_abs_params(priv, dev1); + + if (priv->flags & ALPS_WHEEL) { + dev1->evbit[BIT_WORD(EV_REL)] |= BIT_MASK(EV_REL); + dev1->relbit[BIT_WORD(REL_WHEEL)] |= BIT_MASK(REL_WHEEL); + } + + if (priv->flags & (ALPS_FW_BK_1 | ALPS_FW_BK_2)) { + dev1->keybit[BIT_WORD(BTN_FORWARD)] |= BIT_MASK(BTN_FORWARD); + dev1->keybit[BIT_WORD(BTN_BACK)] |= BIT_MASK(BTN_BACK); + } + + if (priv->flags & ALPS_FOUR_BUTTONS) { + dev1->keybit[BIT_WORD(BTN_0)] |= BIT_MASK(BTN_0); + dev1->keybit[BIT_WORD(BTN_1)] |= BIT_MASK(BTN_1); + dev1->keybit[BIT_WORD(BTN_2)] |= BIT_MASK(BTN_2); + dev1->keybit[BIT_WORD(BTN_3)] |= BIT_MASK(BTN_3); + } else if (priv->flags & ALPS_BUTTONPAD) { + set_bit(INPUT_PROP_BUTTONPAD, dev1->propbit); + clear_bit(BTN_RIGHT, dev1->keybit); + } else { + dev1->keybit[BIT_WORD(BTN_MIDDLE)] |= BIT_MASK(BTN_MIDDLE); + } + + if (priv->flags & ALPS_DUALPOINT) { + struct input_dev *dev2; + + dev2 = input_allocate_device(); + if (!dev2) { + psmouse_err(psmouse, + "failed to allocate trackstick device\n"); + error = -ENOMEM; + goto init_fail; + } + + snprintf(priv->phys2, sizeof(priv->phys2), "%s/input1", + psmouse->ps2dev.serio->phys); + dev2->phys = priv->phys2; + + /* + * format of input device name is: "protocol vendor name" + * see function psmouse_switch_protocol() in psmouse-base.c + */ + dev2->name = "AlpsPS/2 ALPS DualPoint Stick"; + + dev2->id.bustype = BUS_I8042; + dev2->id.vendor = 0x0002; + dev2->id.product = PSMOUSE_ALPS; + dev2->id.version = priv->proto_version; + dev2->dev.parent = &psmouse->ps2dev.serio->dev; + + input_set_capability(dev2, EV_REL, REL_X); + input_set_capability(dev2, EV_REL, REL_Y); + if (priv->flags & ALPS_DUALPOINT_WITH_PRESSURE) { + input_set_capability(dev2, EV_ABS, ABS_PRESSURE); + input_set_abs_params(dev2, ABS_PRESSURE, 0, 127, 0, 0); + } + input_set_capability(dev2, EV_KEY, BTN_LEFT); + input_set_capability(dev2, EV_KEY, BTN_RIGHT); + input_set_capability(dev2, EV_KEY, BTN_MIDDLE); + + __set_bit(INPUT_PROP_POINTER, dev2->propbit); + __set_bit(INPUT_PROP_POINTING_STICK, dev2->propbit); + + error = input_register_device(dev2); + if (error) { + psmouse_err(psmouse, + "failed to register trackstick device: %d\n", + error); + input_free_device(dev2); + goto init_fail; + } + + priv->dev2 = dev2; + } + + priv->psmouse = psmouse; + + INIT_DELAYED_WORK(&priv->dev3_register_work, + alps_register_bare_ps2_mouse); + + psmouse->protocol_handler = alps_process_byte; + psmouse->poll = alps_poll; + psmouse->disconnect = alps_disconnect; + psmouse->reconnect = alps_reconnect; + psmouse->pktsize = priv->proto_version == ALPS_PROTO_V4 ? 8 : 6; + + /* We are having trouble resyncing ALPS touchpads so disable it for now */ + psmouse->resync_time = 0; + + /* Allow 2 invalid packets without resetting device */ + psmouse->resetafter = psmouse->pktsize * 2; + + return 0; + +init_fail: + psmouse_reset(psmouse); + /* + * Even though we did not allocate psmouse->private we do free + * it here. + */ + kfree(psmouse->private); + psmouse->private = NULL; + return error; +} + +int alps_detect(struct psmouse *psmouse, bool set_properties) +{ + struct alps_data *priv; + int error; + + error = alps_identify(psmouse, NULL); + if (error) + return error; + + /* + * ALPS cs19 is a trackpoint-only device, and uses different + * protocol than DualPoint ones, so we return -EINVAL here and let + * trackpoint.c drive this device. If the trackpoint driver is not + * enabled, the device will fall back to a bare PS/2 mouse. + * If ps2_command() fails here, we depend on the immediately + * followed psmouse_reset() to reset the device to normal state. + */ + if (alps_is_cs19_trackpoint(psmouse)) { + psmouse_dbg(psmouse, + "ALPS CS19 trackpoint-only device detected, ignoring\n"); + return -EINVAL; + } + + /* + * Reset the device to make sure it is fully operational: + * on some laptops, like certain Dell Latitudes, we may + * fail to properly detect presence of trackstick if device + * has not been reset. + */ + psmouse_reset(psmouse); + + priv = kzalloc(sizeof(struct alps_data), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + error = alps_identify(psmouse, priv); + if (error) { + kfree(priv); + return error; + } + + if (set_properties) { + psmouse->vendor = "ALPS"; + psmouse->name = priv->flags & ALPS_DUALPOINT ? + "DualPoint TouchPad" : "GlidePoint"; + psmouse->model = priv->proto_version; + } else { + /* + * Destroy alps_data structure we allocated earlier since + * this was just a "trial run". Otherwise we'll keep it + * to be used by alps_init() which has to be called if + * we succeed and set_properties is true. + */ + kfree(priv); + psmouse->private = NULL; + } + + return 0; +} + diff --git a/drivers/input/mouse/alps.h b/drivers/input/mouse/alps.h new file mode 100644 index 000000000..79b6d69d1 --- /dev/null +++ b/drivers/input/mouse/alps.h @@ -0,0 +1,343 @@ +/* + * ALPS touchpad PS/2 mouse driver + * + * Copyright (c) 2003 Peter Osterlund <petero2@telia.com> + * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz> + * + * 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. + */ + +#ifndef _ALPS_H +#define _ALPS_H + +#include <linux/input/mt.h> + +#define ALPS_PROTO_V1 0x100 +#define ALPS_PROTO_V2 0x200 +#define ALPS_PROTO_V3 0x300 +#define ALPS_PROTO_V3_RUSHMORE 0x310 +#define ALPS_PROTO_V4 0x400 +#define ALPS_PROTO_V5 0x500 +#define ALPS_PROTO_V6 0x600 +#define ALPS_PROTO_V7 0x700 /* t3btl t4s */ +#define ALPS_PROTO_V8 0x800 /* SS4btl SS4s */ +#define ALPS_PROTO_V9 0x900 /* ss3btl */ + +#define MAX_TOUCHES 4 + +#define DOLPHIN_COUNT_PER_ELECTRODE 64 +#define DOLPHIN_PROFILE_XOFFSET 8 /* x-electrode offset */ +#define DOLPHIN_PROFILE_YOFFSET 1 /* y-electrode offset */ + +/* + * enum SS4_PACKET_ID - defines the packet type for V8 + * SS4_PACKET_ID_IDLE: There's no finger and no button activity. + * SS4_PACKET_ID_ONE: There's one finger on touchpad + * or there's button activities. + * SS4_PACKET_ID_TWO: There's two or more fingers on touchpad + * SS4_PACKET_ID_MULTI: There's three or more fingers on touchpad + * SS4_PACKET_ID_STICK: A stick pointer packet +*/ +enum SS4_PACKET_ID { + SS4_PACKET_ID_IDLE = 0, + SS4_PACKET_ID_ONE, + SS4_PACKET_ID_TWO, + SS4_PACKET_ID_MULTI, + SS4_PACKET_ID_STICK, +}; + +#define SS4_COUNT_PER_ELECTRODE 256 +#define SS4_NUMSENSOR_XOFFSET 7 +#define SS4_NUMSENSOR_YOFFSET 7 +#define SS4_MIN_PITCH_MM 50 + +#define SS4_MASK_NORMAL_BUTTONS 0x07 + +#define SS4PLUS_COUNT_PER_ELECTRODE 128 +#define SS4PLUS_NUMSENSOR_XOFFSET 16 +#define SS4PLUS_NUMSENSOR_YOFFSET 5 +#define SS4PLUS_MIN_PITCH_MM 37 + +#define IS_SS4PLUS_DEV(_b) (((_b[0]) == 0x73) && \ + ((_b[1]) == 0x03) && \ + ((_b[2]) == 0x28) \ + ) + +#define SS4_IS_IDLE_V2(_b) (((_b[0]) == 0x18) && \ + ((_b[1]) == 0x10) && \ + ((_b[2]) == 0x00) && \ + ((_b[3] & 0x88) == 0x08) && \ + ((_b[4]) == 0x10) && \ + ((_b[5]) == 0x00) \ + ) + +#define SS4_1F_X_V2(_b) (((_b[0]) & 0x0007) | \ + ((_b[1] << 3) & 0x0078) | \ + ((_b[1] << 2) & 0x0380) | \ + ((_b[2] << 5) & 0x1C00) \ + ) + +#define SS4_1F_Y_V2(_b) (((_b[2]) & 0x000F) | \ + ((_b[3] >> 2) & 0x0030) | \ + ((_b[4] << 6) & 0x03C0) | \ + ((_b[4] << 5) & 0x0C00) \ + ) + +#define SS4_1F_Z_V2(_b) (((_b[5]) & 0x0F) | \ + ((_b[5] >> 1) & 0x70) | \ + ((_b[4]) & 0x80) \ + ) + +#define SS4_1F_LFB_V2(_b) (((_b[2] >> 4) & 0x01) == 0x01) + +#define SS4_MF_LF_V2(_b, _i) ((_b[1 + (_i) * 3] & 0x0004) == 0x0004) + +#define SS4_BTN_V2(_b) ((_b[0] >> 5) & SS4_MASK_NORMAL_BUTTONS) + +#define SS4_STD_MF_X_V2(_b, _i) (((_b[0 + (_i) * 3] << 5) & 0x00E0) | \ + ((_b[1 + _i * 3] << 5) & 0x1F00) \ + ) + +#define SS4_PLUS_STD_MF_X_V2(_b, _i) (((_b[0 + (_i) * 3] << 4) & 0x0070) | \ + ((_b[1 + (_i) * 3] << 4) & 0x0F80) \ + ) + +#define SS4_STD_MF_Y_V2(_b, _i) (((_b[1 + (_i) * 3] << 3) & 0x0010) | \ + ((_b[2 + (_i) * 3] << 5) & 0x01E0) | \ + ((_b[2 + (_i) * 3] << 4) & 0x0E00) \ + ) + +#define SS4_BTL_MF_X_V2(_b, _i) (SS4_STD_MF_X_V2(_b, _i) | \ + ((_b[0 + (_i) * 3] >> 3) & 0x0010) \ + ) + +#define SS4_PLUS_BTL_MF_X_V2(_b, _i) (SS4_PLUS_STD_MF_X_V2(_b, _i) | \ + ((_b[0 + (_i) * 3] >> 4) & 0x0008) \ + ) + +#define SS4_BTL_MF_Y_V2(_b, _i) (SS4_STD_MF_Y_V2(_b, _i) | \ + ((_b[0 + (_i) * 3] >> 3) & 0x0008) \ + ) + +#define SS4_MF_Z_V2(_b, _i) (((_b[1 + (_i) * 3]) & 0x0001) | \ + ((_b[1 + (_i) * 3] >> 1) & 0x0002) \ + ) + +#define SS4_IS_MF_CONTINUE(_b) ((_b[2] & 0x10) == 0x10) +#define SS4_IS_5F_DETECTED(_b) ((_b[2] & 0x10) == 0x10) + +#define SS4_TS_X_V2(_b) (s8)( \ + ((_b[0] & 0x01) << 7) | \ + (_b[1] & 0x7F) \ + ) + +#define SS4_TS_Y_V2(_b) -(s8)( \ + ((_b[3] & 0x01) << 7) | \ + (_b[2] & 0x7F) \ + ) + +#define SS4_TS_Z_V2(_b) (s8)(_b[4] & 0x7F) + + +#define SS4_MFPACKET_NO_AX 8160 /* X-Coordinate value */ +#define SS4_MFPACKET_NO_AY 4080 /* Y-Coordinate value */ +#define SS4_MFPACKET_NO_AX_BL 8176 /* Buttonless X-Coord value */ +#define SS4_MFPACKET_NO_AY_BL 4088 /* Buttonless Y-Coord value */ +#define SS4_PLUS_MFPACKET_NO_AX 4080 /* SS4 PLUS, X */ +#define SS4_PLUS_MFPACKET_NO_AX_BL 4088 /* Buttonless SS4 PLUS, X */ + +/* + * enum V7_PACKET_ID - defines the packet type for V7 + * V7_PACKET_ID_IDLE: There's no finger and no button activity. + * V7_PACKET_ID_TWO: There's one or two non-resting fingers on touchpad + * or there's button activities. + * V7_PACKET_ID_MULTI: There are at least three non-resting fingers. + * V7_PACKET_ID_NEW: The finger position in slot is not continues from + * previous packet. +*/ +enum V7_PACKET_ID { + V7_PACKET_ID_IDLE, + V7_PACKET_ID_TWO, + V7_PACKET_ID_MULTI, + V7_PACKET_ID_NEW, + V7_PACKET_ID_UNKNOWN, +}; + +/** + * struct alps_protocol_info - information about protocol used by a device + * @version: Indicates V1/V2/V3/... + * @byte0: Helps figure out whether a position report packet matches the + * known format for this model. The first byte of the report, ANDed with + * mask0, should match byte0. + * @mask0: The mask used to check the first byte of the report. + * @flags: Additional device capabilities (passthrough port, trackstick, etc.). + */ +struct alps_protocol_info { + u16 version; + u8 byte0, mask0; + unsigned int flags; +}; + +/** + * struct alps_model_info - touchpad ID table + * @signature: E7 response string to match. + * @protocol_info: information about protocol used by the device. + * + * Many (but not all) ALPS touchpads can be identified by looking at the + * values returned in the "E7 report" and/or the "EC report." This table + * lists a number of such touchpads. + */ +struct alps_model_info { + u8 signature[3]; + struct alps_protocol_info protocol_info; +}; + +/** + * struct alps_nibble_commands - encodings for register accesses + * @command: PS/2 command used for the nibble + * @data: Data supplied as an argument to the PS/2 command, if applicable + * + * The ALPS protocol uses magic sequences to transmit binary data to the + * touchpad, as it is generally not OK to send arbitrary bytes out the + * PS/2 port. Each of the sequences in this table sends one nibble of the + * register address or (write) data. Different versions of the ALPS protocol + * use slightly different encodings. + */ +struct alps_nibble_commands { + int command; + unsigned char data; +}; + +struct alps_bitmap_point { + int start_bit; + int num_bits; +}; + +/** + * struct alps_fields - decoded version of the report packet + * @x_map: Bitmap of active X positions for MT. + * @y_map: Bitmap of active Y positions for MT. + * @fingers: Number of fingers for MT. + * @pressure: Pressure. + * @st: position for ST. + * @mt: position for MT. + * @first_mp: Packet is the first of a multi-packet report. + * @is_mp: Packet is part of a multi-packet report. + * @left: Left touchpad button is active. + * @right: Right touchpad button is active. + * @middle: Middle touchpad button is active. + * @ts_left: Left trackstick button is active. + * @ts_right: Right trackstick button is active. + * @ts_middle: Middle trackstick button is active. + */ +struct alps_fields { + unsigned int x_map; + unsigned int y_map; + unsigned int fingers; + + int pressure; + struct input_mt_pos st; + struct input_mt_pos mt[MAX_TOUCHES]; + + unsigned int first_mp:1; + unsigned int is_mp:1; + + unsigned int left:1; + unsigned int right:1; + unsigned int middle:1; + + unsigned int ts_left:1; + unsigned int ts_right:1; + unsigned int ts_middle:1; +}; + +/** + * struct alps_data - private data structure for the ALPS driver + * @psmouse: Pointer to parent psmouse device + * @dev2: Trackstick device (can be NULL). + * @dev3: Generic PS/2 mouse (can be NULL, delayed registering). + * @phys2: Physical path for the trackstick device. + * @phys3: Physical path for the generic PS/2 mouse. + * @dev3_register_work: Delayed work for registering PS/2 mouse. + * @nibble_commands: Command mapping used for touchpad register accesses. + * @addr_command: Command used to tell the touchpad that a register address + * follows. + * @proto_version: Indicates V1/V2/V3/... + * @byte0: Helps figure out whether a position report packet matches the + * known format for this model. The first byte of the report, ANDed with + * mask0, should match byte0. + * @mask0: The mask used to check the first byte of the report. + * @fw_ver: cached copy of firmware version (EC report) + * @flags: Additional device capabilities (passthrough port, trackstick, etc.). + * @x_max: Largest possible X position value. + * @y_max: Largest possible Y position value. + * @x_bits: Number of X bits in the MT bitmap. + * @y_bits: Number of Y bits in the MT bitmap. + * @hw_init: Protocol-specific hardware init function. + * @process_packet: Protocol-specific function to process a report packet. + * @decode_fields: Protocol-specific function to read packet bitfields. + * @set_abs_params: Protocol-specific function to configure the input_dev. + * @prev_fin: Finger bit from previous packet. + * @multi_packet: Multi-packet data in progress. + * @multi_data: Saved multi-packet data. + * @f: Decoded packet data fields. + * @quirks: Bitmap of ALPS_QUIRK_*. + * @timer: Timer for flushing out the final report packet in the stream. + */ +struct alps_data { + struct psmouse *psmouse; + struct input_dev *dev2; + struct input_dev *dev3; + char phys2[32]; + char phys3[32]; + struct delayed_work dev3_register_work; + + /* these are autodetected when the device is identified */ + const struct alps_nibble_commands *nibble_commands; + int addr_command; + u16 proto_version; + u8 byte0, mask0; + u8 dev_id[3]; + u8 fw_ver[3]; + int flags; + int x_max; + int y_max; + int x_bits; + int y_bits; + unsigned int x_res; + unsigned int y_res; + + int (*hw_init)(struct psmouse *psmouse); + void (*process_packet)(struct psmouse *psmouse); + int (*decode_fields)(struct alps_fields *f, unsigned char *p, + struct psmouse *psmouse); + void (*set_abs_params)(struct alps_data *priv, struct input_dev *dev1); + + int prev_fin; + int multi_packet; + int second_touch; + unsigned char multi_data[6]; + struct alps_fields f; + u8 quirks; + struct timer_list timer; +}; + +#define ALPS_QUIRK_TRACKSTICK_BUTTONS 1 /* trakcstick buttons in trackstick packet */ + +#ifdef CONFIG_MOUSE_PS2_ALPS +int alps_detect(struct psmouse *psmouse, bool set_properties); +int alps_init(struct psmouse *psmouse); +#else +inline int alps_detect(struct psmouse *psmouse, bool set_properties) +{ + return -ENOSYS; +} +inline int alps_init(struct psmouse *psmouse) +{ + return -ENOSYS; +} +#endif /* CONFIG_MOUSE_PS2_ALPS */ + +#endif diff --git a/drivers/input/mouse/amimouse.c b/drivers/input/mouse/amimouse.c new file mode 100644 index 000000000..a33437c48 --- /dev/null +++ b/drivers/input/mouse/amimouse.c @@ -0,0 +1,149 @@ +/* + * Amiga mouse driver for Linux/m68k + * + * Copyright (c) 2000-2002 Vojtech Pavlik + * + * Based on the work of: + * Michael Rausch James Banks + * Matther Dillon David Giller + * Nathan Laredo Linus Torvalds + * Johan Myreen Jes Sorensen + * Russell King + */ + +/* + * 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 + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> + +#include <asm/irq.h> +#include <asm/setup.h> +#include <linux/uaccess.h> +#include <asm/amigahw.h> +#include <asm/amigaints.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Amiga mouse driver"); +MODULE_LICENSE("GPL"); + +static int amimouse_lastx, amimouse_lasty; + +static irqreturn_t amimouse_interrupt(int irq, void *data) +{ + struct input_dev *dev = data; + unsigned short joy0dat, potgor; + int nx, ny, dx, dy; + + joy0dat = amiga_custom.joy0dat; + + nx = joy0dat & 0xff; + ny = joy0dat >> 8; + + dx = nx - amimouse_lastx; + dy = ny - amimouse_lasty; + + if (dx < -127) dx = (256 + nx) - amimouse_lastx; + if (dx > 127) dx = (nx - 256) - amimouse_lastx; + if (dy < -127) dy = (256 + ny) - amimouse_lasty; + if (dy > 127) dy = (ny - 256) - amimouse_lasty; + + amimouse_lastx = nx; + amimouse_lasty = ny; + + potgor = amiga_custom.potgor; + + input_report_rel(dev, REL_X, dx); + input_report_rel(dev, REL_Y, dy); + + input_report_key(dev, BTN_LEFT, ciaa.pra & 0x40); + input_report_key(dev, BTN_MIDDLE, potgor & 0x0100); + input_report_key(dev, BTN_RIGHT, potgor & 0x0400); + + input_sync(dev); + + return IRQ_HANDLED; +} + +static int amimouse_open(struct input_dev *dev) +{ + unsigned short joy0dat; + int error; + + joy0dat = amiga_custom.joy0dat; + + amimouse_lastx = joy0dat & 0xff; + amimouse_lasty = joy0dat >> 8; + + error = request_irq(IRQ_AMIGA_VERTB, amimouse_interrupt, 0, "amimouse", + dev); + if (error) + dev_err(&dev->dev, "Can't allocate irq %d\n", IRQ_AMIGA_VERTB); + + return error; +} + +static void amimouse_close(struct input_dev *dev) +{ + free_irq(IRQ_AMIGA_VERTB, dev); +} + +static int __init amimouse_probe(struct platform_device *pdev) +{ + int err; + struct input_dev *dev; + + dev = input_allocate_device(); + if (!dev) + return -ENOMEM; + + dev->name = pdev->name; + dev->phys = "amimouse/input0"; + dev->id.bustype = BUS_AMIGA; + dev->id.vendor = 0x0001; + dev->id.product = 0x0002; + dev->id.version = 0x0100; + + dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + dev->keybit[BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT); + dev->open = amimouse_open; + dev->close = amimouse_close; + dev->dev.parent = &pdev->dev; + + err = input_register_device(dev); + if (err) { + input_free_device(dev); + return err; + } + + platform_set_drvdata(pdev, dev); + + return 0; +} + +static int __exit amimouse_remove(struct platform_device *pdev) +{ + struct input_dev *dev = platform_get_drvdata(pdev); + + input_unregister_device(dev); + return 0; +} + +static struct platform_driver amimouse_driver = { + .remove = __exit_p(amimouse_remove), + .driver = { + .name = "amiga-mouse", + }, +}; + +module_platform_driver_probe(amimouse_driver, amimouse_probe); + +MODULE_ALIAS("platform:amiga-mouse"); diff --git a/drivers/input/mouse/appletouch.c b/drivers/input/mouse/appletouch.c new file mode 100644 index 000000000..e305a4f2b --- /dev/null +++ b/drivers/input/mouse/appletouch.c @@ -0,0 +1,1021 @@ +/* + * Apple USB Touchpad (for post-February 2005 PowerBooks and MacBooks) driver + * + * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2005-2008 Johannes Berg (johannes@sipsolutions.net) + * Copyright (C) 2005-2008 Stelian Pop (stelian@popies.net) + * Copyright (C) 2005 Frank Arnold (frank@scirocco-5v-turbo.de) + * Copyright (C) 2005 Peter Osterlund (petero2@telia.com) + * Copyright (C) 2005 Michael Hanselmann (linux-kernel@hansmi.ch) + * Copyright (C) 2006 Nicolas Boichat (nicolas@boichat.ch) + * Copyright (C) 2007-2008 Sven Anders (anders@anduras.de) + * + * Thanks to Alex Harper <basilisk@foobox.net> for his inputs. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/usb/input.h> + +/* + * Note: We try to keep the touchpad aspect ratio while still doing only + * simple arithmetics: + * 0 <= x <= (xsensors - 1) * xfact + * 0 <= y <= (ysensors - 1) * yfact + */ +struct atp_info { + int xsensors; /* number of X sensors */ + int xsensors_17; /* 17" models have more sensors */ + int ysensors; /* number of Y sensors */ + int xfact; /* X multiplication factor */ + int yfact; /* Y multiplication factor */ + int datalen; /* size of USB transfers */ + void (*callback)(struct urb *); /* callback function */ + int fuzz; /* fuzz touchpad generates */ +}; + +static void atp_complete_geyser_1_2(struct urb *urb); +static void atp_complete_geyser_3_4(struct urb *urb); + +static const struct atp_info fountain_info = { + .xsensors = 16, + .xsensors_17 = 26, + .ysensors = 16, + .xfact = 64, + .yfact = 43, + .datalen = 81, + .callback = atp_complete_geyser_1_2, + .fuzz = 16, +}; + +static const struct atp_info geyser1_info = { + .xsensors = 16, + .xsensors_17 = 26, + .ysensors = 16, + .xfact = 64, + .yfact = 43, + .datalen = 81, + .callback = atp_complete_geyser_1_2, + .fuzz = 16, +}; + +static const struct atp_info geyser2_info = { + .xsensors = 15, + .xsensors_17 = 20, + .ysensors = 9, + .xfact = 64, + .yfact = 43, + .datalen = 64, + .callback = atp_complete_geyser_1_2, + .fuzz = 0, +}; + +static const struct atp_info geyser3_info = { + .xsensors = 20, + .ysensors = 10, + .xfact = 64, + .yfact = 64, + .datalen = 64, + .callback = atp_complete_geyser_3_4, + .fuzz = 0, +}; + +static const struct atp_info geyser4_info = { + .xsensors = 20, + .ysensors = 10, + .xfact = 64, + .yfact = 64, + .datalen = 64, + .callback = atp_complete_geyser_3_4, + .fuzz = 0, +}; + +#define ATP_DEVICE(prod, info) \ +{ \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \ + USB_DEVICE_ID_MATCH_INT_CLASS | \ + USB_DEVICE_ID_MATCH_INT_PROTOCOL, \ + .idVendor = 0x05ac, /* Apple */ \ + .idProduct = (prod), \ + .bInterfaceClass = 0x03, \ + .bInterfaceProtocol = 0x02, \ + .driver_info = (unsigned long) &info, \ +} + +/* + * Table of devices (Product IDs) that work with this driver. + * (The names come from Info.plist in AppleUSBTrackpad.kext, + * According to Info.plist Geyser IV is the same as Geyser III.) + */ + +static const struct usb_device_id atp_table[] = { + /* PowerBooks Feb 2005, iBooks G4 */ + ATP_DEVICE(0x020e, fountain_info), /* FOUNTAIN ANSI */ + ATP_DEVICE(0x020f, fountain_info), /* FOUNTAIN ISO */ + ATP_DEVICE(0x030a, fountain_info), /* FOUNTAIN TP ONLY */ + ATP_DEVICE(0x030b, geyser1_info), /* GEYSER 1 TP ONLY */ + + /* PowerBooks Oct 2005 */ + ATP_DEVICE(0x0214, geyser2_info), /* GEYSER 2 ANSI */ + ATP_DEVICE(0x0215, geyser2_info), /* GEYSER 2 ISO */ + ATP_DEVICE(0x0216, geyser2_info), /* GEYSER 2 JIS */ + + /* Core Duo MacBook & MacBook Pro */ + ATP_DEVICE(0x0217, geyser3_info), /* GEYSER 3 ANSI */ + ATP_DEVICE(0x0218, geyser3_info), /* GEYSER 3 ISO */ + ATP_DEVICE(0x0219, geyser3_info), /* GEYSER 3 JIS */ + + /* Core2 Duo MacBook & MacBook Pro */ + ATP_DEVICE(0x021a, geyser4_info), /* GEYSER 4 ANSI */ + ATP_DEVICE(0x021b, geyser4_info), /* GEYSER 4 ISO */ + ATP_DEVICE(0x021c, geyser4_info), /* GEYSER 4 JIS */ + + /* Core2 Duo MacBook3,1 */ + ATP_DEVICE(0x0229, geyser4_info), /* GEYSER 4 HF ANSI */ + ATP_DEVICE(0x022a, geyser4_info), /* GEYSER 4 HF ISO */ + ATP_DEVICE(0x022b, geyser4_info), /* GEYSER 4 HF JIS */ + + /* Terminating entry */ + { } +}; +MODULE_DEVICE_TABLE(usb, atp_table); + +/* maximum number of sensors */ +#define ATP_XSENSORS 26 +#define ATP_YSENSORS 16 + +/* + * The largest possible bank of sensors with additional buffer of 4 extra values + * on either side, for an array of smoothed sensor values. + */ +#define ATP_SMOOTHSIZE 34 + +/* maximum pressure this driver will report */ +#define ATP_PRESSURE 300 + +/* + * Threshold for the touchpad sensors. Any change less than ATP_THRESHOLD is + * ignored. + */ +#define ATP_THRESHOLD 5 + +/* + * How far we'll bitshift our sensor values before averaging them. Mitigates + * rounding errors. + */ +#define ATP_SCALE 12 + +/* Geyser initialization constants */ +#define ATP_GEYSER_MODE_READ_REQUEST_ID 1 +#define ATP_GEYSER_MODE_WRITE_REQUEST_ID 9 +#define ATP_GEYSER_MODE_REQUEST_VALUE 0x300 +#define ATP_GEYSER_MODE_REQUEST_INDEX 0 +#define ATP_GEYSER_MODE_VENDOR_VALUE 0x04 + +/** + * enum atp_status_bits - status bit meanings + * + * These constants represent the meaning of the status bits. + * (only Geyser 3/4) + * + * @ATP_STATUS_BUTTON: The button was pressed + * @ATP_STATUS_BASE_UPDATE: Update of the base values (untouched pad) + * @ATP_STATUS_FROM_RESET: Reset previously performed + */ +enum atp_status_bits { + ATP_STATUS_BUTTON = BIT(0), + ATP_STATUS_BASE_UPDATE = BIT(2), + ATP_STATUS_FROM_RESET = BIT(4), +}; + +/* Structure to hold all of our device specific stuff */ +struct atp { + char phys[64]; + struct usb_device *udev; /* usb device */ + struct usb_interface *intf; /* usb interface */ + struct urb *urb; /* usb request block */ + u8 *data; /* transferred data */ + struct input_dev *input; /* input dev */ + const struct atp_info *info; /* touchpad model */ + bool open; + bool valid; /* are the samples valid? */ + bool size_detect_done; + bool overflow_warned; + int fingers_old; /* last reported finger count */ + int x_old; /* last reported x/y, */ + int y_old; /* used for smoothing */ + signed char xy_cur[ATP_XSENSORS + ATP_YSENSORS]; + signed char xy_old[ATP_XSENSORS + ATP_YSENSORS]; + int xy_acc[ATP_XSENSORS + ATP_YSENSORS]; + int smooth[ATP_SMOOTHSIZE]; + int smooth_tmp[ATP_SMOOTHSIZE]; + int idlecount; /* number of empty packets */ + struct work_struct work; +}; + +#define dbg_dump(msg, tab) \ + if (debug > 1) { \ + int __i; \ + printk(KERN_DEBUG "appletouch: %s", msg); \ + for (__i = 0; __i < ATP_XSENSORS + ATP_YSENSORS; __i++) \ + printk(" %02x", tab[__i]); \ + printk("\n"); \ + } + +#define dprintk(format, a...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG format, ##a); \ + } while (0) + +MODULE_AUTHOR("Johannes Berg"); +MODULE_AUTHOR("Stelian Pop"); +MODULE_AUTHOR("Frank Arnold"); +MODULE_AUTHOR("Michael Hanselmann"); +MODULE_AUTHOR("Sven Anders"); +MODULE_DESCRIPTION("Apple PowerBook and MacBook USB touchpad driver"); +MODULE_LICENSE("GPL"); + +/* + * Make the threshold a module parameter + */ +static int threshold = ATP_THRESHOLD; +module_param(threshold, int, 0644); +MODULE_PARM_DESC(threshold, "Discard any change in data from a sensor" + " (the trackpad has many of these sensors)" + " less than this value."); + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Activate debugging output"); + +/* + * By default newer Geyser devices send standard USB HID mouse + * packets (Report ID 2). This code changes device mode, so it + * sends raw sensor reports (Report ID 5). + */ +static int atp_geyser_init(struct atp *dev) +{ + struct usb_device *udev = dev->udev; + char *data; + int size; + int i; + int ret; + + data = kmalloc(8, GFP_KERNEL); + if (!data) { + dev_err(&dev->intf->dev, "Out of memory\n"); + return -ENOMEM; + } + + size = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + ATP_GEYSER_MODE_READ_REQUEST_ID, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + ATP_GEYSER_MODE_REQUEST_VALUE, + ATP_GEYSER_MODE_REQUEST_INDEX, data, 8, 5000); + + if (size != 8) { + dprintk("atp_geyser_init: read error\n"); + for (i = 0; i < 8; i++) + dprintk("appletouch[%d]: %d\n", i, data[i]); + + dev_err(&dev->intf->dev, "Failed to read mode from device.\n"); + ret = -EIO; + goto out_free; + } + + /* Apply the mode switch */ + data[0] = ATP_GEYSER_MODE_VENDOR_VALUE; + + size = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + ATP_GEYSER_MODE_WRITE_REQUEST_ID, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + ATP_GEYSER_MODE_REQUEST_VALUE, + ATP_GEYSER_MODE_REQUEST_INDEX, data, 8, 5000); + + if (size != 8) { + dprintk("atp_geyser_init: write error\n"); + for (i = 0; i < 8; i++) + dprintk("appletouch[%d]: %d\n", i, data[i]); + + dev_err(&dev->intf->dev, "Failed to request geyser raw mode\n"); + ret = -EIO; + goto out_free; + } + ret = 0; +out_free: + kfree(data); + return ret; +} + +/* + * Reinitialise the device. This usually stops stream of empty packets + * coming from it. + */ +static void atp_reinit(struct work_struct *work) +{ + struct atp *dev = container_of(work, struct atp, work); + int retval; + + dprintk("appletouch: putting appletouch to sleep (reinit)\n"); + atp_geyser_init(dev); + + retval = usb_submit_urb(dev->urb, GFP_ATOMIC); + if (retval) + dev_err(&dev->intf->dev, + "atp_reinit: usb_submit_urb failed with error %d\n", + retval); +} + +static int atp_calculate_abs(struct atp *dev, int offset, int nb_sensors, + int fact, int *z, int *fingers) +{ + int i, pass; + + /* + * Use offset to point xy_sensors at the first value in dev->xy_acc + * for whichever dimension we're looking at this particular go-round. + */ + int *xy_sensors = dev->xy_acc + offset; + + /* values to calculate mean */ + int pcum = 0, psum = 0; + int is_increasing = 0; + + *fingers = 0; + + for (i = 0; i < nb_sensors; i++) { + if (xy_sensors[i] < threshold) { + if (is_increasing) + is_increasing = 0; + + /* + * Makes the finger detection more versatile. For example, + * two fingers with no gap will be detected. Also, my + * tests show it less likely to have intermittent loss + * of multiple finger readings while moving around (scrolling). + * + * Changes the multiple finger detection to counting humps on + * sensors (transitions from nonincreasing to increasing) + * instead of counting transitions from low sensors (no + * finger reading) to high sensors (finger above + * sensor) + * + * - Jason Parekh <jasonparekh@gmail.com> + */ + + } else if (i < 1 || + (!is_increasing && xy_sensors[i - 1] < xy_sensors[i])) { + (*fingers)++; + is_increasing = 1; + } else if (i > 0 && (xy_sensors[i - 1] - xy_sensors[i] > threshold)) { + is_increasing = 0; + } + } + + if (*fingers < 1) /* No need to continue if no fingers are found. */ + return 0; + + /* + * Use a smoothed version of sensor data for movement calculations, to + * combat noise without needing to rely so heavily on a threshold. + * This improves tracking. + * + * The smoothed array is bigger than the original so that the smoothing + * doesn't result in edge values being truncated. + */ + + memset(dev->smooth, 0, 4 * sizeof(dev->smooth[0])); + /* Pull base values, scaled up to help avoid truncation errors. */ + for (i = 0; i < nb_sensors; i++) + dev->smooth[i + 4] = xy_sensors[i] << ATP_SCALE; + memset(&dev->smooth[nb_sensors + 4], 0, 4 * sizeof(dev->smooth[0])); + + for (pass = 0; pass < 4; pass++) { + /* Handle edge. */ + dev->smooth_tmp[0] = (dev->smooth[0] + dev->smooth[1]) / 2; + + /* Average values with neighbors. */ + for (i = 1; i < nb_sensors + 7; i++) + dev->smooth_tmp[i] = (dev->smooth[i - 1] + + dev->smooth[i] * 2 + + dev->smooth[i + 1]) / 4; + + /* Handle other edge. */ + dev->smooth_tmp[i] = (dev->smooth[i - 1] + dev->smooth[i]) / 2; + + memcpy(dev->smooth, dev->smooth_tmp, sizeof(dev->smooth)); + } + + for (i = 0; i < nb_sensors + 8; i++) { + /* + * Skip values if they're small enough to be truncated to 0 + * by scale. Mostly noise. + */ + if ((dev->smooth[i] >> ATP_SCALE) > 0) { + pcum += dev->smooth[i] * i; + psum += dev->smooth[i]; + } + } + + if (psum > 0) { + *z = psum >> ATP_SCALE; /* Scale down pressure output. */ + return pcum * fact / psum; + } + + return 0; +} + +static inline void atp_report_fingers(struct input_dev *input, int fingers) +{ + input_report_key(input, BTN_TOOL_FINGER, fingers == 1); + input_report_key(input, BTN_TOOL_DOUBLETAP, fingers == 2); + input_report_key(input, BTN_TOOL_TRIPLETAP, fingers > 2); +} + +/* Check URB status and for correct length of data package */ + +#define ATP_URB_STATUS_SUCCESS 0 +#define ATP_URB_STATUS_ERROR 1 +#define ATP_URB_STATUS_ERROR_FATAL 2 + +static int atp_status_check(struct urb *urb) +{ + struct atp *dev = urb->context; + struct usb_interface *intf = dev->intf; + + switch (urb->status) { + case 0: + /* success */ + break; + case -EOVERFLOW: + if (!dev->overflow_warned) { + dev_warn(&intf->dev, + "appletouch: OVERFLOW with data length %d, actual length is %d\n", + dev->info->datalen, dev->urb->actual_length); + dev->overflow_warned = true; + } + /* fall through */ + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* This urb is terminated, clean up */ + dev_dbg(&intf->dev, + "atp_complete: urb shutting down with status: %d\n", + urb->status); + return ATP_URB_STATUS_ERROR_FATAL; + + default: + dev_dbg(&intf->dev, + "atp_complete: nonzero urb status received: %d\n", + urb->status); + return ATP_URB_STATUS_ERROR; + } + + /* drop incomplete datasets */ + if (dev->urb->actual_length != dev->info->datalen) { + dprintk("appletouch: incomplete data package" + " (first byte: %d, length: %d).\n", + dev->data[0], dev->urb->actual_length); + return ATP_URB_STATUS_ERROR; + } + + return ATP_URB_STATUS_SUCCESS; +} + +static void atp_detect_size(struct atp *dev) +{ + int i; + + /* 17" Powerbooks have extra X sensors */ + for (i = dev->info->xsensors; i < ATP_XSENSORS; i++) { + if (dev->xy_cur[i]) { + + dev_info(&dev->intf->dev, + "appletouch: 17\" model detected.\n"); + + input_set_abs_params(dev->input, ABS_X, 0, + (dev->info->xsensors_17 - 1) * + dev->info->xfact - 1, + dev->info->fuzz, 0); + break; + } + } +} + +/* + * USB interrupt callback functions + */ + +/* Interrupt function for older touchpads: FOUNTAIN/GEYSER1/GEYSER2 */ + +static void atp_complete_geyser_1_2(struct urb *urb) +{ + int x, y, x_z, y_z, x_f, y_f; + int retval, i, j; + int key, fingers; + struct atp *dev = urb->context; + int status = atp_status_check(urb); + + if (status == ATP_URB_STATUS_ERROR_FATAL) + return; + else if (status == ATP_URB_STATUS_ERROR) + goto exit; + + /* reorder the sensors values */ + if (dev->info == &geyser2_info) { + memset(dev->xy_cur, 0, sizeof(dev->xy_cur)); + + /* + * The values are laid out like this: + * Y1, Y2, -, Y3, Y4, -, ..., X1, X2, -, X3, X4, -, ... + * '-' is an unused value. + */ + + /* read X values */ + for (i = 0, j = 19; i < 20; i += 2, j += 3) { + dev->xy_cur[i] = dev->data[j]; + dev->xy_cur[i + 1] = dev->data[j + 1]; + } + + /* read Y values */ + for (i = 0, j = 1; i < 9; i += 2, j += 3) { + dev->xy_cur[ATP_XSENSORS + i] = dev->data[j]; + dev->xy_cur[ATP_XSENSORS + i + 1] = dev->data[j + 1]; + } + } else { + for (i = 0; i < 8; i++) { + /* X values */ + dev->xy_cur[i + 0] = dev->data[5 * i + 2]; + dev->xy_cur[i + 8] = dev->data[5 * i + 4]; + dev->xy_cur[i + 16] = dev->data[5 * i + 42]; + if (i < 2) + dev->xy_cur[i + 24] = dev->data[5 * i + 44]; + + /* Y values */ + dev->xy_cur[ATP_XSENSORS + i] = dev->data[5 * i + 1]; + dev->xy_cur[ATP_XSENSORS + i + 8] = dev->data[5 * i + 3]; + } + } + + dbg_dump("sample", dev->xy_cur); + + if (!dev->valid) { + /* first sample */ + dev->valid = true; + dev->x_old = dev->y_old = -1; + + /* Store first sample */ + memcpy(dev->xy_old, dev->xy_cur, sizeof(dev->xy_old)); + + /* Perform size detection, if not done already */ + if (unlikely(!dev->size_detect_done)) { + atp_detect_size(dev); + dev->size_detect_done = true; + goto exit; + } + } + + for (i = 0; i < ATP_XSENSORS + ATP_YSENSORS; i++) { + /* accumulate the change */ + signed char change = dev->xy_old[i] - dev->xy_cur[i]; + dev->xy_acc[i] -= change; + + /* prevent down drifting */ + if (dev->xy_acc[i] < 0) + dev->xy_acc[i] = 0; + } + + memcpy(dev->xy_old, dev->xy_cur, sizeof(dev->xy_old)); + + dbg_dump("accumulator", dev->xy_acc); + + x = atp_calculate_abs(dev, 0, ATP_XSENSORS, + dev->info->xfact, &x_z, &x_f); + y = atp_calculate_abs(dev, ATP_XSENSORS, ATP_YSENSORS, + dev->info->yfact, &y_z, &y_f); + key = dev->data[dev->info->datalen - 1] & ATP_STATUS_BUTTON; + + fingers = max(x_f, y_f); + + if (x && y && fingers == dev->fingers_old) { + if (dev->x_old != -1) { + x = (dev->x_old * 7 + x) >> 3; + y = (dev->y_old * 7 + y) >> 3; + dev->x_old = x; + dev->y_old = y; + + if (debug > 1) + printk(KERN_DEBUG "appletouch: " + "X: %3d Y: %3d Xz: %3d Yz: %3d\n", + x, y, x_z, y_z); + + input_report_key(dev->input, BTN_TOUCH, 1); + input_report_abs(dev->input, ABS_X, x); + input_report_abs(dev->input, ABS_Y, y); + input_report_abs(dev->input, ABS_PRESSURE, + min(ATP_PRESSURE, x_z + y_z)); + atp_report_fingers(dev->input, fingers); + } + dev->x_old = x; + dev->y_old = y; + + } else if (!x && !y) { + + dev->x_old = dev->y_old = -1; + dev->fingers_old = 0; + input_report_key(dev->input, BTN_TOUCH, 0); + input_report_abs(dev->input, ABS_PRESSURE, 0); + atp_report_fingers(dev->input, 0); + + /* reset the accumulator on release */ + memset(dev->xy_acc, 0, sizeof(dev->xy_acc)); + } + + if (fingers != dev->fingers_old) + dev->x_old = dev->y_old = -1; + dev->fingers_old = fingers; + + input_report_key(dev->input, BTN_LEFT, key); + input_sync(dev->input); + + exit: + retval = usb_submit_urb(dev->urb, GFP_ATOMIC); + if (retval) + dev_err(&dev->intf->dev, + "atp_complete: usb_submit_urb failed with result %d\n", + retval); +} + +/* Interrupt function for older touchpads: GEYSER3/GEYSER4 */ + +static void atp_complete_geyser_3_4(struct urb *urb) +{ + int x, y, x_z, y_z, x_f, y_f; + int retval, i, j; + int key, fingers; + struct atp *dev = urb->context; + int status = atp_status_check(urb); + + if (status == ATP_URB_STATUS_ERROR_FATAL) + return; + else if (status == ATP_URB_STATUS_ERROR) + goto exit; + + /* Reorder the sensors values: + * + * The values are laid out like this: + * -, Y1, Y2, -, Y3, Y4, -, ..., -, X1, X2, -, X3, X4, ... + * '-' is an unused value. + */ + + /* read X values */ + for (i = 0, j = 19; i < 20; i += 2, j += 3) { + dev->xy_cur[i] = dev->data[j + 1]; + dev->xy_cur[i + 1] = dev->data[j + 2]; + } + /* read Y values */ + for (i = 0, j = 1; i < 9; i += 2, j += 3) { + dev->xy_cur[ATP_XSENSORS + i] = dev->data[j + 1]; + dev->xy_cur[ATP_XSENSORS + i + 1] = dev->data[j + 2]; + } + + dbg_dump("sample", dev->xy_cur); + + /* Just update the base values (i.e. touchpad in untouched state) */ + if (dev->data[dev->info->datalen - 1] & ATP_STATUS_BASE_UPDATE) { + + dprintk("appletouch: updated base values\n"); + + memcpy(dev->xy_old, dev->xy_cur, sizeof(dev->xy_old)); + goto exit; + } + + for (i = 0; i < ATP_XSENSORS + ATP_YSENSORS; i++) { + /* calculate the change */ + dev->xy_acc[i] = dev->xy_cur[i] - dev->xy_old[i]; + + /* this is a round-robin value, so couple with that */ + if (dev->xy_acc[i] > 127) + dev->xy_acc[i] -= 256; + + if (dev->xy_acc[i] < -127) + dev->xy_acc[i] += 256; + + /* prevent down drifting */ + if (dev->xy_acc[i] < 0) + dev->xy_acc[i] = 0; + } + + dbg_dump("accumulator", dev->xy_acc); + + x = atp_calculate_abs(dev, 0, ATP_XSENSORS, + dev->info->xfact, &x_z, &x_f); + y = atp_calculate_abs(dev, ATP_XSENSORS, ATP_YSENSORS, + dev->info->yfact, &y_z, &y_f); + + key = dev->data[dev->info->datalen - 1] & ATP_STATUS_BUTTON; + + fingers = max(x_f, y_f); + + if (x && y && fingers == dev->fingers_old) { + if (dev->x_old != -1) { + x = (dev->x_old * 7 + x) >> 3; + y = (dev->y_old * 7 + y) >> 3; + dev->x_old = x; + dev->y_old = y; + + if (debug > 1) + printk(KERN_DEBUG "appletouch: X: %3d Y: %3d " + "Xz: %3d Yz: %3d\n", + x, y, x_z, y_z); + + input_report_key(dev->input, BTN_TOUCH, 1); + input_report_abs(dev->input, ABS_X, x); + input_report_abs(dev->input, ABS_Y, y); + input_report_abs(dev->input, ABS_PRESSURE, + min(ATP_PRESSURE, x_z + y_z)); + atp_report_fingers(dev->input, fingers); + } + dev->x_old = x; + dev->y_old = y; + + } else if (!x && !y) { + + dev->x_old = dev->y_old = -1; + dev->fingers_old = 0; + input_report_key(dev->input, BTN_TOUCH, 0); + input_report_abs(dev->input, ABS_PRESSURE, 0); + atp_report_fingers(dev->input, 0); + + /* reset the accumulator on release */ + memset(dev->xy_acc, 0, sizeof(dev->xy_acc)); + } + + if (fingers != dev->fingers_old) + dev->x_old = dev->y_old = -1; + dev->fingers_old = fingers; + + input_report_key(dev->input, BTN_LEFT, key); + input_sync(dev->input); + + /* + * Geysers 3/4 will continue to send packets continually after + * the first touch unless reinitialised. Do so if it's been + * idle for a while in order to avoid waking the kernel up + * several hundred times a second. + */ + + /* + * Button must not be pressed when entering suspend, + * otherwise we will never release the button. + */ + if (!x && !y && !key) { + dev->idlecount++; + if (dev->idlecount == 10) { + dev->x_old = dev->y_old = -1; + dev->idlecount = 0; + schedule_work(&dev->work); + /* Don't resubmit urb here, wait for reinit */ + return; + } + } else + dev->idlecount = 0; + + exit: + retval = usb_submit_urb(dev->urb, GFP_ATOMIC); + if (retval) + dev_err(&dev->intf->dev, + "atp_complete: usb_submit_urb failed with result %d\n", + retval); +} + +static int atp_open(struct input_dev *input) +{ + struct atp *dev = input_get_drvdata(input); + + if (usb_submit_urb(dev->urb, GFP_KERNEL)) + return -EIO; + + dev->open = true; + return 0; +} + +static void atp_close(struct input_dev *input) +{ + struct atp *dev = input_get_drvdata(input); + + usb_kill_urb(dev->urb); + cancel_work_sync(&dev->work); + dev->open = false; +} + +static int atp_handle_geyser(struct atp *dev) +{ + if (dev->info != &fountain_info) { + /* switch to raw sensor mode */ + if (atp_geyser_init(dev)) + return -EIO; + + dev_info(&dev->intf->dev, "Geyser mode initialized.\n"); + } + + return 0; +} + +static int atp_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + struct atp *dev; + struct input_dev *input_dev; + struct usb_device *udev = interface_to_usbdev(iface); + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int int_in_endpointAddr = 0; + int i, error = -ENOMEM; + const struct atp_info *info = (const struct atp_info *)id->driver_info; + + /* set up the endpoint information */ + /* use only the first interrupt-in endpoint */ + iface_desc = iface->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { + endpoint = &iface_desc->endpoint[i].desc; + if (!int_in_endpointAddr && usb_endpoint_is_int_in(endpoint)) { + /* we found an interrupt in endpoint */ + int_in_endpointAddr = endpoint->bEndpointAddress; + break; + } + } + if (!int_in_endpointAddr) { + dev_err(&iface->dev, "Could not find int-in endpoint\n"); + return -EIO; + } + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(struct atp), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!dev || !input_dev) { + dev_err(&iface->dev, "Out of memory\n"); + goto err_free_devs; + } + + dev->udev = udev; + dev->intf = iface; + dev->input = input_dev; + dev->info = info; + dev->overflow_warned = false; + + dev->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->urb) + goto err_free_devs; + + dev->data = usb_alloc_coherent(dev->udev, dev->info->datalen, GFP_KERNEL, + &dev->urb->transfer_dma); + if (!dev->data) + goto err_free_urb; + + usb_fill_int_urb(dev->urb, udev, + usb_rcvintpipe(udev, int_in_endpointAddr), + dev->data, dev->info->datalen, + dev->info->callback, dev, 1); + + error = atp_handle_geyser(dev); + if (error) + goto err_free_buffer; + + usb_make_path(udev, dev->phys, sizeof(dev->phys)); + strlcat(dev->phys, "/input0", sizeof(dev->phys)); + + input_dev->name = "appletouch"; + input_dev->phys = dev->phys; + usb_to_input_id(dev->udev, &input_dev->id); + input_dev->dev.parent = &iface->dev; + + input_set_drvdata(input_dev, dev); + + input_dev->open = atp_open; + input_dev->close = atp_close; + + set_bit(EV_ABS, input_dev->evbit); + + input_set_abs_params(input_dev, ABS_X, 0, + (dev->info->xsensors - 1) * dev->info->xfact - 1, + dev->info->fuzz, 0); + input_set_abs_params(input_dev, ABS_Y, 0, + (dev->info->ysensors - 1) * dev->info->yfact - 1, + dev->info->fuzz, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, 0, ATP_PRESSURE, 0, 0); + + set_bit(EV_KEY, input_dev->evbit); + set_bit(BTN_TOUCH, input_dev->keybit); + set_bit(BTN_TOOL_FINGER, input_dev->keybit); + set_bit(BTN_TOOL_DOUBLETAP, input_dev->keybit); + set_bit(BTN_TOOL_TRIPLETAP, input_dev->keybit); + set_bit(BTN_LEFT, input_dev->keybit); + + INIT_WORK(&dev->work, atp_reinit); + + error = input_register_device(dev->input); + if (error) + goto err_free_buffer; + + /* save our data pointer in this interface device */ + usb_set_intfdata(iface, dev); + + return 0; + + err_free_buffer: + usb_free_coherent(dev->udev, dev->info->datalen, + dev->data, dev->urb->transfer_dma); + err_free_urb: + usb_free_urb(dev->urb); + err_free_devs: + usb_set_intfdata(iface, NULL); + kfree(dev); + input_free_device(input_dev); + return error; +} + +static void atp_disconnect(struct usb_interface *iface) +{ + struct atp *dev = usb_get_intfdata(iface); + + usb_set_intfdata(iface, NULL); + if (dev) { + usb_kill_urb(dev->urb); + input_unregister_device(dev->input); + usb_free_coherent(dev->udev, dev->info->datalen, + dev->data, dev->urb->transfer_dma); + usb_free_urb(dev->urb); + kfree(dev); + } + dev_info(&iface->dev, "input: appletouch disconnected\n"); +} + +static int atp_recover(struct atp *dev) +{ + int error; + + error = atp_handle_geyser(dev); + if (error) + return error; + + if (dev->open && usb_submit_urb(dev->urb, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static int atp_suspend(struct usb_interface *iface, pm_message_t message) +{ + struct atp *dev = usb_get_intfdata(iface); + + usb_kill_urb(dev->urb); + return 0; +} + +static int atp_resume(struct usb_interface *iface) +{ + struct atp *dev = usb_get_intfdata(iface); + + if (dev->open && usb_submit_urb(dev->urb, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static int atp_reset_resume(struct usb_interface *iface) +{ + struct atp *dev = usb_get_intfdata(iface); + + return atp_recover(dev); +} + +static struct usb_driver atp_driver = { + .name = "appletouch", + .probe = atp_probe, + .disconnect = atp_disconnect, + .suspend = atp_suspend, + .resume = atp_resume, + .reset_resume = atp_reset_resume, + .id_table = atp_table, +}; + +module_usb_driver(atp_driver); diff --git a/drivers/input/mouse/atarimouse.c b/drivers/input/mouse/atarimouse.c new file mode 100644 index 000000000..96f2f5160 --- /dev/null +++ b/drivers/input/mouse/atarimouse.c @@ -0,0 +1,158 @@ +/* + * Atari mouse driver for Linux/m68k + * + * Copyright (c) 2005 Michael Schmitz + * + * Based on: + * Amiga mouse driver for Linux/m68k + * + * Copyright (c) 2000-2002 Vojtech Pavlik + * + */ +/* + * The low level init and interrupt stuff is handled in arch/mm68k/atari/atakeyb.c + * (the keyboard ACIA also handles the mouse and joystick data, and the keyboard + * interrupt is shared with the MIDI ACIA so MIDI data also get handled there). + * This driver only deals with handing key events off to the input layer. + * + * Largely based on the old: + * + * Atari Mouse Driver for Linux + * by Robert de Vries (robert@and.nl) 19Jul93 + * + * 16 Nov 1994 Andreas Schwab + * Compatibility with busmouse + * Support for three button mouse (shamelessly stolen from MiNT) + * third button wired to one of the joystick directions on joystick 1 + * + * 1996/02/11 Andreas Schwab + * Module support + * Allow multiple open's + * + * Converted to use new generic busmouse code. 5 Apr 1998 + * Russell King <rmk@arm.uk.linux.org> + */ + + +/* + * 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 + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> + +#include <asm/irq.h> +#include <asm/setup.h> +#include <linux/uaccess.h> +#include <asm/atarihw.h> +#include <asm/atarikb.h> +#include <asm/atariints.h> + +MODULE_AUTHOR("Michael Schmitz <schmitz@biophys.uni-duesseldorf.de>"); +MODULE_DESCRIPTION("Atari mouse driver"); +MODULE_LICENSE("GPL"); + +static int mouse_threshold[2] = {2, 2}; +module_param_array(mouse_threshold, int, NULL, 0); + +#ifdef FIXED_ATARI_JOYSTICK +extern int atari_mouse_buttons; +#endif + +static struct input_dev *atamouse_dev; + +static void atamouse_interrupt(char *buf) +{ + int buttons, dx, dy; + + buttons = (buf[0] & 1) | ((buf[0] & 2) << 1); +#ifdef FIXED_ATARI_JOYSTICK + buttons |= atari_mouse_buttons & 2; + atari_mouse_buttons = buttons; +#endif + + /* only relative events get here */ + dx = buf[1]; + dy = buf[2]; + + input_report_rel(atamouse_dev, REL_X, dx); + input_report_rel(atamouse_dev, REL_Y, dy); + + input_report_key(atamouse_dev, BTN_LEFT, buttons & 0x4); + input_report_key(atamouse_dev, BTN_MIDDLE, buttons & 0x2); + input_report_key(atamouse_dev, BTN_RIGHT, buttons & 0x1); + + input_sync(atamouse_dev); + + return; +} + +static int atamouse_open(struct input_dev *dev) +{ +#ifdef FIXED_ATARI_JOYSTICK + atari_mouse_buttons = 0; +#endif + ikbd_mouse_y0_top(); + ikbd_mouse_thresh(mouse_threshold[0], mouse_threshold[1]); + ikbd_mouse_rel_pos(); + atari_input_mouse_interrupt_hook = atamouse_interrupt; + + return 0; +} + +static void atamouse_close(struct input_dev *dev) +{ + ikbd_mouse_disable(); + atari_input_mouse_interrupt_hook = NULL; +} + +static int __init atamouse_init(void) +{ + int error; + + if (!MACH_IS_ATARI || !ATARIHW_PRESENT(ST_MFP)) + return -ENODEV; + + error = atari_keyb_init(); + if (error) + return error; + + atamouse_dev = input_allocate_device(); + if (!atamouse_dev) + return -ENOMEM; + + atamouse_dev->name = "Atari mouse"; + atamouse_dev->phys = "atamouse/input0"; + atamouse_dev->id.bustype = BUS_HOST; + atamouse_dev->id.vendor = 0x0001; + atamouse_dev->id.product = 0x0002; + atamouse_dev->id.version = 0x0100; + + atamouse_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + atamouse_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + atamouse_dev->keybit[BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT); + + atamouse_dev->open = atamouse_open; + atamouse_dev->close = atamouse_close; + + error = input_register_device(atamouse_dev); + if (error) { + input_free_device(atamouse_dev); + return error; + } + + return 0; +} + +static void __exit atamouse_exit(void) +{ + input_unregister_device(atamouse_dev); +} + +module_init(atamouse_init); +module_exit(atamouse_exit); diff --git a/drivers/input/mouse/bcm5974.c b/drivers/input/mouse/bcm5974.c new file mode 100644 index 000000000..f68816329 --- /dev/null +++ b/drivers/input/mouse/bcm5974.c @@ -0,0 +1,1047 @@ +/* + * Apple USB BCM5974 (Macbook Air and Penryn Macbook Pro) multitouch driver + * + * Copyright (C) 2008 Henrik Rydberg (rydberg@euromail.se) + * Copyright (C) 2015 John Horan (knasher@gmail.com) + * + * The USB initialization and package decoding was made by + * Scott Shawcroft as part of the touchd user-space driver project: + * Copyright (C) 2008 Scott Shawcroft (scott.shawcroft@gmail.com) + * + * The BCM5974 driver is based on the appletouch driver: + * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2005 Johannes Berg (johannes@sipsolutions.net) + * Copyright (C) 2005 Stelian Pop (stelian@popies.net) + * Copyright (C) 2005 Frank Arnold (frank@scirocco-5v-turbo.de) + * Copyright (C) 2005 Peter Osterlund (petero2@telia.com) + * Copyright (C) 2005 Michael Hanselmann (linux-kernel@hansmi.ch) + * Copyright (C) 2006 Nicolas Boichat (nicolas@boichat.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/usb/input.h> +#include <linux/hid.h> +#include <linux/mutex.h> +#include <linux/input/mt.h> + +#define USB_VENDOR_ID_APPLE 0x05ac + +/* MacbookAir, aka wellspring */ +#define USB_DEVICE_ID_APPLE_WELLSPRING_ANSI 0x0223 +#define USB_DEVICE_ID_APPLE_WELLSPRING_ISO 0x0224 +#define USB_DEVICE_ID_APPLE_WELLSPRING_JIS 0x0225 +/* MacbookProPenryn, aka wellspring2 */ +#define USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI 0x0230 +#define USB_DEVICE_ID_APPLE_WELLSPRING2_ISO 0x0231 +#define USB_DEVICE_ID_APPLE_WELLSPRING2_JIS 0x0232 +/* Macbook5,1 (unibody), aka wellspring3 */ +#define USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI 0x0236 +#define USB_DEVICE_ID_APPLE_WELLSPRING3_ISO 0x0237 +#define USB_DEVICE_ID_APPLE_WELLSPRING3_JIS 0x0238 +/* MacbookAir3,2 (unibody), aka wellspring5 */ +#define USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI 0x023f +#define USB_DEVICE_ID_APPLE_WELLSPRING4_ISO 0x0240 +#define USB_DEVICE_ID_APPLE_WELLSPRING4_JIS 0x0241 +/* MacbookAir3,1 (unibody), aka wellspring4 */ +#define USB_DEVICE_ID_APPLE_WELLSPRING4A_ANSI 0x0242 +#define USB_DEVICE_ID_APPLE_WELLSPRING4A_ISO 0x0243 +#define USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS 0x0244 +/* Macbook8 (unibody, March 2011) */ +#define USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI 0x0245 +#define USB_DEVICE_ID_APPLE_WELLSPRING5_ISO 0x0246 +#define USB_DEVICE_ID_APPLE_WELLSPRING5_JIS 0x0247 +/* MacbookAir4,1 (unibody, July 2011) */ +#define USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI 0x0249 +#define USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO 0x024a +#define USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS 0x024b +/* MacbookAir4,2 (unibody, July 2011) */ +#define USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI 0x024c +#define USB_DEVICE_ID_APPLE_WELLSPRING6_ISO 0x024d +#define USB_DEVICE_ID_APPLE_WELLSPRING6_JIS 0x024e +/* Macbook8,2 (unibody) */ +#define USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI 0x0252 +#define USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO 0x0253 +#define USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS 0x0254 +/* MacbookPro10,1 (unibody, June 2012) */ +#define USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI 0x0262 +#define USB_DEVICE_ID_APPLE_WELLSPRING7_ISO 0x0263 +#define USB_DEVICE_ID_APPLE_WELLSPRING7_JIS 0x0264 +/* MacbookPro10,2 (unibody, October 2012) */ +#define USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI 0x0259 +#define USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO 0x025a +#define USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS 0x025b +/* MacbookAir6,2 (unibody, June 2013) */ +#define USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI 0x0290 +#define USB_DEVICE_ID_APPLE_WELLSPRING8_ISO 0x0291 +#define USB_DEVICE_ID_APPLE_WELLSPRING8_JIS 0x0292 +/* MacbookPro12,1 (2015) */ +#define USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI 0x0272 +#define USB_DEVICE_ID_APPLE_WELLSPRING9_ISO 0x0273 +#define USB_DEVICE_ID_APPLE_WELLSPRING9_JIS 0x0274 + +#define BCM5974_DEVICE(prod) { \ + .match_flags = (USB_DEVICE_ID_MATCH_DEVICE | \ + USB_DEVICE_ID_MATCH_INT_CLASS | \ + USB_DEVICE_ID_MATCH_INT_PROTOCOL), \ + .idVendor = USB_VENDOR_ID_APPLE, \ + .idProduct = (prod), \ + .bInterfaceClass = USB_INTERFACE_CLASS_HID, \ + .bInterfaceProtocol = USB_INTERFACE_PROTOCOL_MOUSE \ +} + +/* table of devices that work with this driver */ +static const struct usb_device_id bcm5974_table[] = { + /* MacbookAir1.1 */ + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING_ANSI), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING_ISO), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING_JIS), + /* MacbookProPenryn */ + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING2_ISO), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING2_JIS), + /* Macbook5,1 */ + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING3_ISO), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING3_JIS), + /* MacbookAir3,2 */ + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING4_ISO), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING4_JIS), + /* MacbookAir3,1 */ + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING4A_ANSI), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING4A_ISO), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS), + /* MacbookPro8 */ + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING5_ISO), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING5_JIS), + /* MacbookAir4,1 */ + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS), + /* MacbookAir4,2 */ + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING6_ISO), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING6_JIS), + /* MacbookPro8,2 */ + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS), + /* MacbookPro10,1 */ + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING7_ISO), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING7_JIS), + /* MacbookPro10,2 */ + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS), + /* MacbookAir6,2 */ + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING8_ISO), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING8_JIS), + /* MacbookPro12,1 */ + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING9_ISO), + BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING9_JIS), + /* Terminating entry */ + {} +}; +MODULE_DEVICE_TABLE(usb, bcm5974_table); + +MODULE_AUTHOR("Henrik Rydberg"); +MODULE_DESCRIPTION("Apple USB BCM5974 multitouch driver"); +MODULE_LICENSE("GPL"); + +#define dprintk(level, format, a...)\ + { if (debug >= level) printk(KERN_DEBUG format, ##a); } + +static int debug = 1; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Activate debugging output"); + +/* button data structure */ +struct bt_data { + u8 unknown1; /* constant */ + u8 button; /* left button */ + u8 rel_x; /* relative x coordinate */ + u8 rel_y; /* relative y coordinate */ +}; + +/* trackpad header types */ +enum tp_type { + TYPE1, /* plain trackpad */ + TYPE2, /* button integrated in trackpad */ + TYPE3, /* additional header fields since June 2013 */ + TYPE4 /* additional header field for pressure data */ +}; + +/* trackpad finger data offsets, le16-aligned */ +#define HEADER_TYPE1 (13 * sizeof(__le16)) +#define HEADER_TYPE2 (15 * sizeof(__le16)) +#define HEADER_TYPE3 (19 * sizeof(__le16)) +#define HEADER_TYPE4 (23 * sizeof(__le16)) + +/* trackpad button data offsets */ +#define BUTTON_TYPE1 0 +#define BUTTON_TYPE2 15 +#define BUTTON_TYPE3 23 +#define BUTTON_TYPE4 31 + +/* list of device capability bits */ +#define HAS_INTEGRATED_BUTTON 1 + +/* trackpad finger data block size */ +#define FSIZE_TYPE1 (14 * sizeof(__le16)) +#define FSIZE_TYPE2 (14 * sizeof(__le16)) +#define FSIZE_TYPE3 (14 * sizeof(__le16)) +#define FSIZE_TYPE4 (15 * sizeof(__le16)) + +/* offset from header to finger struct */ +#define DELTA_TYPE1 (0 * sizeof(__le16)) +#define DELTA_TYPE2 (0 * sizeof(__le16)) +#define DELTA_TYPE3 (0 * sizeof(__le16)) +#define DELTA_TYPE4 (1 * sizeof(__le16)) + +/* usb control message mode switch data */ +#define USBMSG_TYPE1 8, 0x300, 0, 0, 0x1, 0x8 +#define USBMSG_TYPE2 8, 0x300, 0, 0, 0x1, 0x8 +#define USBMSG_TYPE3 8, 0x300, 0, 0, 0x1, 0x8 +#define USBMSG_TYPE4 2, 0x302, 2, 1, 0x1, 0x0 + +/* Wellspring initialization constants */ +#define BCM5974_WELLSPRING_MODE_READ_REQUEST_ID 1 +#define BCM5974_WELLSPRING_MODE_WRITE_REQUEST_ID 9 + +/* trackpad finger structure, le16-aligned */ +struct tp_finger { + __le16 origin; /* zero when switching track finger */ + __le16 abs_x; /* absolute x coodinate */ + __le16 abs_y; /* absolute y coodinate */ + __le16 rel_x; /* relative x coodinate */ + __le16 rel_y; /* relative y coodinate */ + __le16 tool_major; /* tool area, major axis */ + __le16 tool_minor; /* tool area, minor axis */ + __le16 orientation; /* 16384 when point, else 15 bit angle */ + __le16 touch_major; /* touch area, major axis */ + __le16 touch_minor; /* touch area, minor axis */ + __le16 unused[2]; /* zeros */ + __le16 pressure; /* pressure on forcetouch touchpad */ + __le16 multi; /* one finger: varies, more fingers: constant */ +} __attribute__((packed,aligned(2))); + +/* trackpad finger data size, empirically at least ten fingers */ +#define MAX_FINGERS 16 +#define MAX_FINGER_ORIENTATION 16384 + +/* device-specific parameters */ +struct bcm5974_param { + int snratio; /* signal-to-noise ratio */ + int min; /* device minimum reading */ + int max; /* device maximum reading */ +}; + +/* device-specific configuration */ +struct bcm5974_config { + int ansi, iso, jis; /* the product id of this device */ + int caps; /* device capability bitmask */ + int bt_ep; /* the endpoint of the button interface */ + int bt_datalen; /* data length of the button interface */ + int tp_ep; /* the endpoint of the trackpad interface */ + enum tp_type tp_type; /* type of trackpad interface */ + int tp_header; /* bytes in header block */ + int tp_datalen; /* data length of the trackpad interface */ + int tp_button; /* offset to button data */ + int tp_fsize; /* bytes in single finger block */ + int tp_delta; /* offset from header to finger struct */ + int um_size; /* usb control message length */ + int um_req_val; /* usb control message value */ + int um_req_idx; /* usb control message index */ + int um_switch_idx; /* usb control message mode switch index */ + int um_switch_on; /* usb control message mode switch on */ + int um_switch_off; /* usb control message mode switch off */ + struct bcm5974_param p; /* finger pressure limits */ + struct bcm5974_param w; /* finger width limits */ + struct bcm5974_param x; /* horizontal limits */ + struct bcm5974_param y; /* vertical limits */ + struct bcm5974_param o; /* orientation limits */ +}; + +/* logical device structure */ +struct bcm5974 { + char phys[64]; + struct usb_device *udev; /* usb device */ + struct usb_interface *intf; /* our interface */ + struct input_dev *input; /* input dev */ + struct bcm5974_config cfg; /* device configuration */ + struct mutex pm_mutex; /* serialize access to open/suspend */ + int opened; /* 1: opened, 0: closed */ + struct urb *bt_urb; /* button usb request block */ + struct bt_data *bt_data; /* button transferred data */ + struct urb *tp_urb; /* trackpad usb request block */ + u8 *tp_data; /* trackpad transferred data */ + const struct tp_finger *index[MAX_FINGERS]; /* finger index data */ + struct input_mt_pos pos[MAX_FINGERS]; /* position array */ + int slots[MAX_FINGERS]; /* slot assignments */ +}; + +/* trackpad finger block data, le16-aligned */ +static const struct tp_finger *get_tp_finger(const struct bcm5974 *dev, int i) +{ + const struct bcm5974_config *c = &dev->cfg; + u8 *f_base = dev->tp_data + c->tp_header + c->tp_delta; + + return (const struct tp_finger *)(f_base + i * c->tp_fsize); +} + +#define DATAFORMAT(type) \ + type, \ + HEADER_##type, \ + HEADER_##type + (MAX_FINGERS) * (FSIZE_##type), \ + BUTTON_##type, \ + FSIZE_##type, \ + DELTA_##type, \ + USBMSG_##type + +/* logical signal quality */ +#define SN_PRESSURE 45 /* pressure signal-to-noise ratio */ +#define SN_WIDTH 25 /* width signal-to-noise ratio */ +#define SN_COORD 250 /* coordinate signal-to-noise ratio */ +#define SN_ORIENT 10 /* orientation signal-to-noise ratio */ + +/* device constants */ +static const struct bcm5974_config bcm5974_config_table[] = { + { + USB_DEVICE_ID_APPLE_WELLSPRING_ANSI, + USB_DEVICE_ID_APPLE_WELLSPRING_ISO, + USB_DEVICE_ID_APPLE_WELLSPRING_JIS, + 0, + 0x84, sizeof(struct bt_data), + 0x81, DATAFORMAT(TYPE1), + { SN_PRESSURE, 0, 256 }, + { SN_WIDTH, 0, 2048 }, + { SN_COORD, -4824, 5342 }, + { SN_COORD, -172, 5820 }, + { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } + }, + { + USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI, + USB_DEVICE_ID_APPLE_WELLSPRING2_ISO, + USB_DEVICE_ID_APPLE_WELLSPRING2_JIS, + 0, + 0x84, sizeof(struct bt_data), + 0x81, DATAFORMAT(TYPE1), + { SN_PRESSURE, 0, 256 }, + { SN_WIDTH, 0, 2048 }, + { SN_COORD, -4824, 4824 }, + { SN_COORD, -172, 4290 }, + { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } + }, + { + USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI, + USB_DEVICE_ID_APPLE_WELLSPRING3_ISO, + USB_DEVICE_ID_APPLE_WELLSPRING3_JIS, + HAS_INTEGRATED_BUTTON, + 0x84, sizeof(struct bt_data), + 0x81, DATAFORMAT(TYPE2), + { SN_PRESSURE, 0, 300 }, + { SN_WIDTH, 0, 2048 }, + { SN_COORD, -4460, 5166 }, + { SN_COORD, -75, 6700 }, + { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } + }, + { + USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI, + USB_DEVICE_ID_APPLE_WELLSPRING4_ISO, + USB_DEVICE_ID_APPLE_WELLSPRING4_JIS, + HAS_INTEGRATED_BUTTON, + 0x84, sizeof(struct bt_data), + 0x81, DATAFORMAT(TYPE2), + { SN_PRESSURE, 0, 300 }, + { SN_WIDTH, 0, 2048 }, + { SN_COORD, -4620, 5140 }, + { SN_COORD, -150, 6600 }, + { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } + }, + { + USB_DEVICE_ID_APPLE_WELLSPRING4A_ANSI, + USB_DEVICE_ID_APPLE_WELLSPRING4A_ISO, + USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS, + HAS_INTEGRATED_BUTTON, + 0x84, sizeof(struct bt_data), + 0x81, DATAFORMAT(TYPE2), + { SN_PRESSURE, 0, 300 }, + { SN_WIDTH, 0, 2048 }, + { SN_COORD, -4616, 5112 }, + { SN_COORD, -142, 5234 }, + { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } + }, + { + USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI, + USB_DEVICE_ID_APPLE_WELLSPRING5_ISO, + USB_DEVICE_ID_APPLE_WELLSPRING5_JIS, + HAS_INTEGRATED_BUTTON, + 0x84, sizeof(struct bt_data), + 0x81, DATAFORMAT(TYPE2), + { SN_PRESSURE, 0, 300 }, + { SN_WIDTH, 0, 2048 }, + { SN_COORD, -4415, 5050 }, + { SN_COORD, -55, 6680 }, + { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } + }, + { + USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI, + USB_DEVICE_ID_APPLE_WELLSPRING6_ISO, + USB_DEVICE_ID_APPLE_WELLSPRING6_JIS, + HAS_INTEGRATED_BUTTON, + 0x84, sizeof(struct bt_data), + 0x81, DATAFORMAT(TYPE2), + { SN_PRESSURE, 0, 300 }, + { SN_WIDTH, 0, 2048 }, + { SN_COORD, -4620, 5140 }, + { SN_COORD, -150, 6600 }, + { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } + }, + { + USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI, + USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO, + USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS, + HAS_INTEGRATED_BUTTON, + 0x84, sizeof(struct bt_data), + 0x81, DATAFORMAT(TYPE2), + { SN_PRESSURE, 0, 300 }, + { SN_WIDTH, 0, 2048 }, + { SN_COORD, -4750, 5280 }, + { SN_COORD, -150, 6730 }, + { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } + }, + { + USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI, + USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO, + USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS, + HAS_INTEGRATED_BUTTON, + 0x84, sizeof(struct bt_data), + 0x81, DATAFORMAT(TYPE2), + { SN_PRESSURE, 0, 300 }, + { SN_WIDTH, 0, 2048 }, + { SN_COORD, -4620, 5140 }, + { SN_COORD, -150, 6600 }, + { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } + }, + { + USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI, + USB_DEVICE_ID_APPLE_WELLSPRING7_ISO, + USB_DEVICE_ID_APPLE_WELLSPRING7_JIS, + HAS_INTEGRATED_BUTTON, + 0x84, sizeof(struct bt_data), + 0x81, DATAFORMAT(TYPE2), + { SN_PRESSURE, 0, 300 }, + { SN_WIDTH, 0, 2048 }, + { SN_COORD, -4750, 5280 }, + { SN_COORD, -150, 6730 }, + { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } + }, + { + USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI, + USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO, + USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS, + HAS_INTEGRATED_BUTTON, + 0x84, sizeof(struct bt_data), + 0x81, DATAFORMAT(TYPE2), + { SN_PRESSURE, 0, 300 }, + { SN_WIDTH, 0, 2048 }, + { SN_COORD, -4750, 5280 }, + { SN_COORD, -150, 6730 }, + { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } + }, + { + USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI, + USB_DEVICE_ID_APPLE_WELLSPRING8_ISO, + USB_DEVICE_ID_APPLE_WELLSPRING8_JIS, + HAS_INTEGRATED_BUTTON, + 0, sizeof(struct bt_data), + 0x83, DATAFORMAT(TYPE3), + { SN_PRESSURE, 0, 300 }, + { SN_WIDTH, 0, 2048 }, + { SN_COORD, -4620, 5140 }, + { SN_COORD, -150, 6600 }, + { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } + }, + { + USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI, + USB_DEVICE_ID_APPLE_WELLSPRING9_ISO, + USB_DEVICE_ID_APPLE_WELLSPRING9_JIS, + HAS_INTEGRATED_BUTTON, + 0, sizeof(struct bt_data), + 0x83, DATAFORMAT(TYPE4), + { SN_PRESSURE, 0, 300 }, + { SN_WIDTH, 0, 2048 }, + { SN_COORD, -4828, 5345 }, + { SN_COORD, -203, 6803 }, + { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION } + }, + {} +}; + +/* return the device-specific configuration by device */ +static const struct bcm5974_config *bcm5974_get_config(struct usb_device *udev) +{ + u16 id = le16_to_cpu(udev->descriptor.idProduct); + const struct bcm5974_config *cfg; + + for (cfg = bcm5974_config_table; cfg->ansi; ++cfg) + if (cfg->ansi == id || cfg->iso == id || cfg->jis == id) + return cfg; + + return bcm5974_config_table; +} + +/* convert 16-bit little endian to signed integer */ +static inline int raw2int(__le16 x) +{ + return (signed short)le16_to_cpu(x); +} + +static void set_abs(struct input_dev *input, unsigned int code, + const struct bcm5974_param *p) +{ + int fuzz = p->snratio ? (p->max - p->min) / p->snratio : 0; + input_set_abs_params(input, code, p->min, p->max, fuzz, 0); +} + +/* setup which logical events to report */ +static void setup_events_to_report(struct input_dev *input_dev, + const struct bcm5974_config *cfg) +{ + __set_bit(EV_ABS, input_dev->evbit); + + /* for synaptics only */ + input_set_abs_params(input_dev, ABS_PRESSURE, 0, 256, 5, 0); + input_set_abs_params(input_dev, ABS_TOOL_WIDTH, 0, 16, 0, 0); + + /* finger touch area */ + set_abs(input_dev, ABS_MT_TOUCH_MAJOR, &cfg->w); + set_abs(input_dev, ABS_MT_TOUCH_MINOR, &cfg->w); + /* finger approach area */ + set_abs(input_dev, ABS_MT_WIDTH_MAJOR, &cfg->w); + set_abs(input_dev, ABS_MT_WIDTH_MINOR, &cfg->w); + /* finger orientation */ + set_abs(input_dev, ABS_MT_ORIENTATION, &cfg->o); + /* finger position */ + set_abs(input_dev, ABS_MT_POSITION_X, &cfg->x); + set_abs(input_dev, ABS_MT_POSITION_Y, &cfg->y); + + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(BTN_LEFT, input_dev->keybit); + + if (cfg->caps & HAS_INTEGRATED_BUTTON) + __set_bit(INPUT_PROP_BUTTONPAD, input_dev->propbit); + + input_mt_init_slots(input_dev, MAX_FINGERS, + INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED | INPUT_MT_TRACK); +} + +/* report button data as logical button state */ +static int report_bt_state(struct bcm5974 *dev, int size) +{ + if (size != sizeof(struct bt_data)) + return -EIO; + + dprintk(7, + "bcm5974: button data: %x %x %x %x\n", + dev->bt_data->unknown1, dev->bt_data->button, + dev->bt_data->rel_x, dev->bt_data->rel_y); + + input_report_key(dev->input, BTN_LEFT, dev->bt_data->button); + input_sync(dev->input); + + return 0; +} + +static void report_finger_data(struct input_dev *input, int slot, + const struct input_mt_pos *pos, + const struct tp_finger *f) +{ + input_mt_slot(input, slot); + input_mt_report_slot_state(input, MT_TOOL_FINGER, true); + + input_report_abs(input, ABS_MT_TOUCH_MAJOR, + raw2int(f->touch_major) << 1); + input_report_abs(input, ABS_MT_TOUCH_MINOR, + raw2int(f->touch_minor) << 1); + input_report_abs(input, ABS_MT_WIDTH_MAJOR, + raw2int(f->tool_major) << 1); + input_report_abs(input, ABS_MT_WIDTH_MINOR, + raw2int(f->tool_minor) << 1); + input_report_abs(input, ABS_MT_ORIENTATION, + MAX_FINGER_ORIENTATION - raw2int(f->orientation)); + input_report_abs(input, ABS_MT_POSITION_X, pos->x); + input_report_abs(input, ABS_MT_POSITION_Y, pos->y); +} + +static void report_synaptics_data(struct input_dev *input, + const struct bcm5974_config *cfg, + const struct tp_finger *f, int raw_n) +{ + int abs_p = 0, abs_w = 0; + + if (raw_n) { + int p = raw2int(f->touch_major); + int w = raw2int(f->tool_major); + if (p > 0 && raw2int(f->origin)) { + abs_p = clamp_val(256 * p / cfg->p.max, 0, 255); + abs_w = clamp_val(16 * w / cfg->w.max, 0, 15); + } + } + + input_report_abs(input, ABS_PRESSURE, abs_p); + input_report_abs(input, ABS_TOOL_WIDTH, abs_w); +} + +/* report trackpad data as logical trackpad state */ +static int report_tp_state(struct bcm5974 *dev, int size) +{ + const struct bcm5974_config *c = &dev->cfg; + const struct tp_finger *f; + struct input_dev *input = dev->input; + int raw_n, i, n = 0; + + if (size < c->tp_header || (size - c->tp_header) % c->tp_fsize != 0) + return -EIO; + + raw_n = (size - c->tp_header) / c->tp_fsize; + + for (i = 0; i < raw_n; i++) { + f = get_tp_finger(dev, i); + if (raw2int(f->touch_major) == 0) + continue; + dev->pos[n].x = raw2int(f->abs_x); + dev->pos[n].y = c->y.min + c->y.max - raw2int(f->abs_y); + dev->index[n++] = f; + } + + input_mt_assign_slots(input, dev->slots, dev->pos, n, 0); + + for (i = 0; i < n; i++) + report_finger_data(input, dev->slots[i], + &dev->pos[i], dev->index[i]); + + input_mt_sync_frame(input); + + report_synaptics_data(input, c, get_tp_finger(dev, 0), raw_n); + + /* later types report button events via integrated button only */ + if (c->caps & HAS_INTEGRATED_BUTTON) { + int ibt = raw2int(dev->tp_data[c->tp_button]); + input_report_key(input, BTN_LEFT, ibt); + } + + input_sync(input); + + return 0; +} + +static int bcm5974_wellspring_mode(struct bcm5974 *dev, bool on) +{ + const struct bcm5974_config *c = &dev->cfg; + int retval = 0, size; + char *data; + + /* Type 3 does not require a mode switch */ + if (c->tp_type == TYPE3) + return 0; + + data = kmalloc(c->um_size, GFP_KERNEL); + if (!data) { + dev_err(&dev->intf->dev, "out of memory\n"); + retval = -ENOMEM; + goto out; + } + + /* read configuration */ + size = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), + BCM5974_WELLSPRING_MODE_READ_REQUEST_ID, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + c->um_req_val, c->um_req_idx, data, c->um_size, 5000); + + if (size != c->um_size) { + dev_err(&dev->intf->dev, "could not read from device\n"); + retval = -EIO; + goto out; + } + + /* apply the mode switch */ + data[c->um_switch_idx] = on ? c->um_switch_on : c->um_switch_off; + + /* write configuration */ + size = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), + BCM5974_WELLSPRING_MODE_WRITE_REQUEST_ID, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + c->um_req_val, c->um_req_idx, data, c->um_size, 5000); + + if (size != c->um_size) { + dev_err(&dev->intf->dev, "could not write to device\n"); + retval = -EIO; + goto out; + } + + dprintk(2, "bcm5974: switched to %s mode.\n", + on ? "wellspring" : "normal"); + + out: + kfree(data); + return retval; +} + +static void bcm5974_irq_button(struct urb *urb) +{ + struct bcm5974 *dev = urb->context; + struct usb_interface *intf = dev->intf; + int error; + + switch (urb->status) { + case 0: + break; + case -EOVERFLOW: + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + dev_dbg(&intf->dev, "button urb shutting down: %d\n", + urb->status); + return; + default: + dev_dbg(&intf->dev, "button urb status: %d\n", urb->status); + goto exit; + } + + if (report_bt_state(dev, dev->bt_urb->actual_length)) + dprintk(1, "bcm5974: bad button package, length: %d\n", + dev->bt_urb->actual_length); + +exit: + error = usb_submit_urb(dev->bt_urb, GFP_ATOMIC); + if (error) + dev_err(&intf->dev, "button urb failed: %d\n", error); +} + +static void bcm5974_irq_trackpad(struct urb *urb) +{ + struct bcm5974 *dev = urb->context; + struct usb_interface *intf = dev->intf; + int error; + + switch (urb->status) { + case 0: + break; + case -EOVERFLOW: + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + dev_dbg(&intf->dev, "trackpad urb shutting down: %d\n", + urb->status); + return; + default: + dev_dbg(&intf->dev, "trackpad urb status: %d\n", urb->status); + goto exit; + } + + /* control response ignored */ + if (dev->tp_urb->actual_length == 2) + goto exit; + + if (report_tp_state(dev, dev->tp_urb->actual_length)) + dprintk(1, "bcm5974: bad trackpad package, length: %d\n", + dev->tp_urb->actual_length); + +exit: + error = usb_submit_urb(dev->tp_urb, GFP_ATOMIC); + if (error) + dev_err(&intf->dev, "trackpad urb failed: %d\n", error); +} + +/* + * The Wellspring trackpad, like many recent Apple trackpads, share + * the usb device with the keyboard. Since keyboards are usually + * handled by the HID system, the device ends up being handled by two + * modules. Setting up the device therefore becomes slightly + * complicated. To enable multitouch features, a mode switch is + * required, which is usually applied via the control interface of the + * device. It can be argued where this switch should take place. In + * some drivers, like appletouch, the switch is made during + * probe. However, the hid module may also alter the state of the + * device, resulting in trackpad malfunction under certain + * circumstances. To get around this problem, there is at least one + * example that utilizes the USB_QUIRK_RESET_RESUME quirk in order to + * receive a reset_resume request rather than the normal resume. + * Since the implementation of reset_resume is equal to mode switch + * plus start_traffic, it seems easier to always do the switch when + * starting traffic on the device. + */ +static int bcm5974_start_traffic(struct bcm5974 *dev) +{ + int error; + + error = bcm5974_wellspring_mode(dev, true); + if (error) { + dprintk(1, "bcm5974: mode switch failed\n"); + goto err_out; + } + + if (dev->bt_urb) { + error = usb_submit_urb(dev->bt_urb, GFP_KERNEL); + if (error) + goto err_reset_mode; + } + + error = usb_submit_urb(dev->tp_urb, GFP_KERNEL); + if (error) + goto err_kill_bt; + + return 0; + +err_kill_bt: + usb_kill_urb(dev->bt_urb); +err_reset_mode: + bcm5974_wellspring_mode(dev, false); +err_out: + return error; +} + +static void bcm5974_pause_traffic(struct bcm5974 *dev) +{ + usb_kill_urb(dev->tp_urb); + usb_kill_urb(dev->bt_urb); + bcm5974_wellspring_mode(dev, false); +} + +/* + * The code below implements open/close and manual suspend/resume. + * All functions may be called in random order. + * + * Opening a suspended device fails with EACCES - permission denied. + * + * Failing a resume leaves the device resumed but closed. + */ +static int bcm5974_open(struct input_dev *input) +{ + struct bcm5974 *dev = input_get_drvdata(input); + int error; + + error = usb_autopm_get_interface(dev->intf); + if (error) + return error; + + mutex_lock(&dev->pm_mutex); + + error = bcm5974_start_traffic(dev); + if (!error) + dev->opened = 1; + + mutex_unlock(&dev->pm_mutex); + + if (error) + usb_autopm_put_interface(dev->intf); + + return error; +} + +static void bcm5974_close(struct input_dev *input) +{ + struct bcm5974 *dev = input_get_drvdata(input); + + mutex_lock(&dev->pm_mutex); + + bcm5974_pause_traffic(dev); + dev->opened = 0; + + mutex_unlock(&dev->pm_mutex); + + usb_autopm_put_interface(dev->intf); +} + +static int bcm5974_suspend(struct usb_interface *iface, pm_message_t message) +{ + struct bcm5974 *dev = usb_get_intfdata(iface); + + mutex_lock(&dev->pm_mutex); + + if (dev->opened) + bcm5974_pause_traffic(dev); + + mutex_unlock(&dev->pm_mutex); + + return 0; +} + +static int bcm5974_resume(struct usb_interface *iface) +{ + struct bcm5974 *dev = usb_get_intfdata(iface); + int error = 0; + + mutex_lock(&dev->pm_mutex); + + if (dev->opened) + error = bcm5974_start_traffic(dev); + + mutex_unlock(&dev->pm_mutex); + + return error; +} + +static int bcm5974_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(iface); + const struct bcm5974_config *cfg; + struct bcm5974 *dev; + struct input_dev *input_dev; + int error = -ENOMEM; + + /* find the product index */ + cfg = bcm5974_get_config(udev); + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(struct bcm5974), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!dev || !input_dev) { + dev_err(&iface->dev, "out of memory\n"); + goto err_free_devs; + } + + dev->udev = udev; + dev->intf = iface; + dev->input = input_dev; + dev->cfg = *cfg; + mutex_init(&dev->pm_mutex); + + /* setup urbs */ + if (cfg->tp_type == TYPE1) { + dev->bt_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->bt_urb) + goto err_free_devs; + } + + dev->tp_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->tp_urb) + goto err_free_bt_urb; + + if (dev->bt_urb) { + dev->bt_data = usb_alloc_coherent(dev->udev, + dev->cfg.bt_datalen, GFP_KERNEL, + &dev->bt_urb->transfer_dma); + if (!dev->bt_data) + goto err_free_urb; + } + + dev->tp_data = usb_alloc_coherent(dev->udev, + dev->cfg.tp_datalen, GFP_KERNEL, + &dev->tp_urb->transfer_dma); + if (!dev->tp_data) + goto err_free_bt_buffer; + + if (dev->bt_urb) { + usb_fill_int_urb(dev->bt_urb, udev, + usb_rcvintpipe(udev, cfg->bt_ep), + dev->bt_data, dev->cfg.bt_datalen, + bcm5974_irq_button, dev, 1); + + dev->bt_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + } + + usb_fill_int_urb(dev->tp_urb, udev, + usb_rcvintpipe(udev, cfg->tp_ep), + dev->tp_data, dev->cfg.tp_datalen, + bcm5974_irq_trackpad, dev, 1); + + dev->tp_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* create bcm5974 device */ + usb_make_path(udev, dev->phys, sizeof(dev->phys)); + strlcat(dev->phys, "/input0", sizeof(dev->phys)); + + input_dev->name = "bcm5974"; + input_dev->phys = dev->phys; + usb_to_input_id(dev->udev, &input_dev->id); + /* report driver capabilities via the version field */ + input_dev->id.version = cfg->caps; + input_dev->dev.parent = &iface->dev; + + input_set_drvdata(input_dev, dev); + + input_dev->open = bcm5974_open; + input_dev->close = bcm5974_close; + + setup_events_to_report(input_dev, cfg); + + error = input_register_device(dev->input); + if (error) + goto err_free_buffer; + + /* save our data pointer in this interface device */ + usb_set_intfdata(iface, dev); + + return 0; + +err_free_buffer: + usb_free_coherent(dev->udev, dev->cfg.tp_datalen, + dev->tp_data, dev->tp_urb->transfer_dma); +err_free_bt_buffer: + if (dev->bt_urb) + usb_free_coherent(dev->udev, dev->cfg.bt_datalen, + dev->bt_data, dev->bt_urb->transfer_dma); +err_free_urb: + usb_free_urb(dev->tp_urb); +err_free_bt_urb: + usb_free_urb(dev->bt_urb); +err_free_devs: + usb_set_intfdata(iface, NULL); + input_free_device(input_dev); + kfree(dev); + return error; +} + +static void bcm5974_disconnect(struct usb_interface *iface) +{ + struct bcm5974 *dev = usb_get_intfdata(iface); + + usb_set_intfdata(iface, NULL); + + input_unregister_device(dev->input); + usb_free_coherent(dev->udev, dev->cfg.tp_datalen, + dev->tp_data, dev->tp_urb->transfer_dma); + if (dev->bt_urb) + usb_free_coherent(dev->udev, dev->cfg.bt_datalen, + dev->bt_data, dev->bt_urb->transfer_dma); + usb_free_urb(dev->tp_urb); + usb_free_urb(dev->bt_urb); + kfree(dev); +} + +static struct usb_driver bcm5974_driver = { + .name = "bcm5974", + .probe = bcm5974_probe, + .disconnect = bcm5974_disconnect, + .suspend = bcm5974_suspend, + .resume = bcm5974_resume, + .id_table = bcm5974_table, + .supports_autosuspend = 1, +}; + +module_usb_driver(bcm5974_driver); diff --git a/drivers/input/mouse/byd.c b/drivers/input/mouse/byd.c new file mode 100644 index 000000000..f2aabf7f9 --- /dev/null +++ b/drivers/input/mouse/byd.c @@ -0,0 +1,513 @@ +/* + * BYD TouchPad PS/2 mouse driver + * + * Copyright (C) 2015 Chris Diamand <chris@diamand.org> + * Copyright (C) 2015 Richard Pospesel + * Copyright (C) 2015 Tai Chi Minh Ralph Eastwood + * Copyright (C) 2015 Martin Wimpress + * Copyright (C) 2015 Jay Kuri + * + * 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. + */ + +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/libps2.h> +#include <linux/serio.h> +#include <linux/slab.h> + +#include "psmouse.h" +#include "byd.h" + +/* PS2 Bits */ +#define PS2_Y_OVERFLOW BIT_MASK(7) +#define PS2_X_OVERFLOW BIT_MASK(6) +#define PS2_Y_SIGN BIT_MASK(5) +#define PS2_X_SIGN BIT_MASK(4) +#define PS2_ALWAYS_1 BIT_MASK(3) +#define PS2_MIDDLE BIT_MASK(2) +#define PS2_RIGHT BIT_MASK(1) +#define PS2_LEFT BIT_MASK(0) + +/* + * BYD pad constants + */ + +/* + * True device resolution is unknown, however experiments show the + * resolution is about 111 units/mm. + * Absolute coordinate packets are in the range 0-255 for both X and Y + * we pick ABS_X/ABS_Y dimensions which are multiples of 256 and in + * the right ballpark given the touchpad's physical dimensions and estimate + * resolution per spec sheet, device active area dimensions are + * 101.6 x 60.1 mm. + */ +#define BYD_PAD_WIDTH 11264 +#define BYD_PAD_HEIGHT 6656 +#define BYD_PAD_RESOLUTION 111 + +/* + * Given the above dimensions, relative packets velocity is in multiples of + * 1 unit / 11 milliseconds. We use this dt to estimate distance traveled + */ +#define BYD_DT 11 +/* Time in jiffies used to timeout various touch events (64 ms) */ +#define BYD_TOUCH_TIMEOUT msecs_to_jiffies(64) + +/* BYD commands reverse engineered from windows driver */ + +/* + * Swipe gesture from off-pad to on-pad + * 0 : disable + * 1 : enable + */ +#define BYD_CMD_SET_OFFSCREEN_SWIPE 0x10cc +/* + * Tap and drag delay time + * 0 : disable + * 1 - 8 : least to most delay + */ +#define BYD_CMD_SET_TAP_DRAG_DELAY_TIME 0x10cf +/* + * Physical buttons function mapping + * 0 : enable + * 4 : normal + * 5 : left button custom command + * 6 : right button custom command + * 8 : disable + */ +#define BYD_CMD_SET_PHYSICAL_BUTTONS 0x10d0 +/* + * Absolute mode (1 byte X/Y resolution) + * 0 : disable + * 2 : enable + */ +#define BYD_CMD_SET_ABSOLUTE_MODE 0x10d1 +/* + * Two finger scrolling + * 1 : vertical + * 2 : horizontal + * 3 : vertical + horizontal + * 4 : disable + */ +#define BYD_CMD_SET_TWO_FINGER_SCROLL 0x10d2 +/* + * Handedness + * 1 : right handed + * 2 : left handed + */ +#define BYD_CMD_SET_HANDEDNESS 0x10d3 +/* + * Tap to click + * 1 : enable + * 2 : disable + */ +#define BYD_CMD_SET_TAP 0x10d4 +/* + * Tap and drag + * 1 : tap and hold to drag + * 2 : tap and hold to drag + lock + * 3 : disable + */ +#define BYD_CMD_SET_TAP_DRAG 0x10d5 +/* + * Touch sensitivity + * 1 - 7 : least to most sensitive + */ +#define BYD_CMD_SET_TOUCH_SENSITIVITY 0x10d6 +/* + * One finger scrolling + * 1 : vertical + * 2 : horizontal + * 3 : vertical + horizontal + * 4 : disable + */ +#define BYD_CMD_SET_ONE_FINGER_SCROLL 0x10d7 +/* + * One finger scrolling function + * 1 : free scrolling + * 2 : edge motion + * 3 : free scrolling + edge motion + * 4 : disable + */ +#define BYD_CMD_SET_ONE_FINGER_SCROLL_FUNC 0x10d8 +/* + * Sliding speed + * 1 - 5 : slowest to fastest + */ +#define BYD_CMD_SET_SLIDING_SPEED 0x10da +/* + * Edge motion + * 1 : disable + * 2 : enable when dragging + * 3 : enable when dragging and pointing + */ +#define BYD_CMD_SET_EDGE_MOTION 0x10db +/* + * Left edge region size + * 0 - 7 : smallest to largest width + */ +#define BYD_CMD_SET_LEFT_EDGE_REGION 0x10dc +/* + * Top edge region size + * 0 - 9 : smallest to largest height + */ +#define BYD_CMD_SET_TOP_EDGE_REGION 0x10dd +/* + * Disregard palm press as clicks + * 1 - 6 : smallest to largest + */ +#define BYD_CMD_SET_PALM_CHECK 0x10de +/* + * Right edge region size + * 0 - 7 : smallest to largest width + */ +#define BYD_CMD_SET_RIGHT_EDGE_REGION 0x10df +/* + * Bottom edge region size + * 0 - 9 : smallest to largest height + */ +#define BYD_CMD_SET_BOTTOM_EDGE_REGION 0x10e1 +/* + * Multitouch gestures + * 1 : enable + * 2 : disable + */ +#define BYD_CMD_SET_MULTITOUCH 0x10e3 +/* + * Edge motion speed + * 0 : control with finger pressure + * 1 - 9 : slowest to fastest + */ +#define BYD_CMD_SET_EDGE_MOTION_SPEED 0x10e4 +/* + * Two finger scolling function + * 0 : free scrolling + * 1 : free scrolling (with momentum) + * 2 : edge motion + * 3 : free scrolling (with momentum) + edge motion + * 4 : disable + */ +#define BYD_CMD_SET_TWO_FINGER_SCROLL_FUNC 0x10e5 + +/* + * The touchpad generates a mixture of absolute and relative packets, indicated + * by the the last byte of each packet being set to one of the following: + */ +#define BYD_PACKET_ABSOLUTE 0xf8 +#define BYD_PACKET_RELATIVE 0x00 +/* Multitouch gesture packets */ +#define BYD_PACKET_PINCH_IN 0xd8 +#define BYD_PACKET_PINCH_OUT 0x28 +#define BYD_PACKET_ROTATE_CLOCKWISE 0x29 +#define BYD_PACKET_ROTATE_ANTICLOCKWISE 0xd7 +#define BYD_PACKET_TWO_FINGER_SCROLL_RIGHT 0x2a +#define BYD_PACKET_TWO_FINGER_SCROLL_DOWN 0x2b +#define BYD_PACKET_TWO_FINGER_SCROLL_UP 0xd5 +#define BYD_PACKET_TWO_FINGER_SCROLL_LEFT 0xd6 +#define BYD_PACKET_THREE_FINGER_SWIPE_RIGHT 0x2c +#define BYD_PACKET_THREE_FINGER_SWIPE_DOWN 0x2d +#define BYD_PACKET_THREE_FINGER_SWIPE_UP 0xd3 +#define BYD_PACKET_THREE_FINGER_SWIPE_LEFT 0xd4 +#define BYD_PACKET_FOUR_FINGER_DOWN 0x33 +#define BYD_PACKET_FOUR_FINGER_UP 0xcd +#define BYD_PACKET_REGION_SCROLL_RIGHT 0x35 +#define BYD_PACKET_REGION_SCROLL_DOWN 0x36 +#define BYD_PACKET_REGION_SCROLL_UP 0xca +#define BYD_PACKET_REGION_SCROLL_LEFT 0xcb +#define BYD_PACKET_RIGHT_CORNER_CLICK 0xd2 +#define BYD_PACKET_LEFT_CORNER_CLICK 0x2e +#define BYD_PACKET_LEFT_AND_RIGHT_CORNER_CLICK 0x2f +#define BYD_PACKET_ONTO_PAD_SWIPE_RIGHT 0x37 +#define BYD_PACKET_ONTO_PAD_SWIPE_DOWN 0x30 +#define BYD_PACKET_ONTO_PAD_SWIPE_UP 0xd0 +#define BYD_PACKET_ONTO_PAD_SWIPE_LEFT 0xc9 + +struct byd_data { + struct timer_list timer; + struct psmouse *psmouse; + s32 abs_x; + s32 abs_y; + typeof(jiffies) last_touch_time; + bool btn_left; + bool btn_right; + bool touch; +}; + +static void byd_report_input(struct psmouse *psmouse) +{ + struct byd_data *priv = psmouse->private; + struct input_dev *dev = psmouse->dev; + + input_report_key(dev, BTN_TOUCH, priv->touch); + input_report_key(dev, BTN_TOOL_FINGER, priv->touch); + + input_report_abs(dev, ABS_X, priv->abs_x); + input_report_abs(dev, ABS_Y, priv->abs_y); + input_report_key(dev, BTN_LEFT, priv->btn_left); + input_report_key(dev, BTN_RIGHT, priv->btn_right); + + input_sync(dev); +} + +static void byd_clear_touch(struct timer_list *t) +{ + struct byd_data *priv = from_timer(priv, t, timer); + struct psmouse *psmouse = priv->psmouse; + + serio_pause_rx(psmouse->ps2dev.serio); + priv->touch = false; + + byd_report_input(psmouse); + + serio_continue_rx(psmouse->ps2dev.serio); + + /* + * Move cursor back to center of pad when we lose touch - this + * specifically improves user experience when moving cursor with one + * finger, and pressing a button with another. + */ + priv->abs_x = BYD_PAD_WIDTH / 2; + priv->abs_y = BYD_PAD_HEIGHT / 2; +} + +static psmouse_ret_t byd_process_byte(struct psmouse *psmouse) +{ + struct byd_data *priv = psmouse->private; + u8 *pkt = psmouse->packet; + + if (psmouse->pktcnt > 0 && !(pkt[0] & PS2_ALWAYS_1)) { + psmouse_warn(psmouse, "Always_1 bit not 1. pkt[0] = %02x\n", + pkt[0]); + return PSMOUSE_BAD_DATA; + } + + if (psmouse->pktcnt < psmouse->pktsize) + return PSMOUSE_GOOD_DATA; + + /* Otherwise, a full packet has been received */ + switch (pkt[3]) { + case BYD_PACKET_ABSOLUTE: + /* Only use absolute packets for the start of movement. */ + if (!priv->touch) { + /* needed to detect tap */ + typeof(jiffies) tap_time = + priv->last_touch_time + BYD_TOUCH_TIMEOUT; + priv->touch = time_after(jiffies, tap_time); + + /* init abs position */ + priv->abs_x = pkt[1] * (BYD_PAD_WIDTH / 256); + priv->abs_y = (255 - pkt[2]) * (BYD_PAD_HEIGHT / 256); + } + break; + case BYD_PACKET_RELATIVE: { + /* Standard packet */ + /* Sign-extend if a sign bit is set. */ + u32 signx = pkt[0] & PS2_X_SIGN ? ~0xFF : 0; + u32 signy = pkt[0] & PS2_Y_SIGN ? ~0xFF : 0; + s32 dx = signx | (int) pkt[1]; + s32 dy = signy | (int) pkt[2]; + + /* Update position based on velocity */ + priv->abs_x += dx * BYD_DT; + priv->abs_y -= dy * BYD_DT; + + priv->touch = true; + break; + } + default: + psmouse_warn(psmouse, + "Unrecognized Z: pkt = %02x %02x %02x %02x\n", + psmouse->packet[0], psmouse->packet[1], + psmouse->packet[2], psmouse->packet[3]); + return PSMOUSE_BAD_DATA; + } + + priv->btn_left = pkt[0] & PS2_LEFT; + priv->btn_right = pkt[0] & PS2_RIGHT; + + byd_report_input(psmouse); + + /* Reset time since last touch. */ + if (priv->touch) { + priv->last_touch_time = jiffies; + mod_timer(&priv->timer, jiffies + BYD_TOUCH_TIMEOUT); + } + + return PSMOUSE_FULL_PACKET; +} + +static int byd_reset_touchpad(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + u8 param[4]; + size_t i; + + static const struct { + u16 command; + u8 arg; + } seq[] = { + /* + * Intellimouse initialization sequence, to get 4-byte instead + * of 3-byte packets. + */ + { PSMOUSE_CMD_SETRATE, 0xC8 }, + { PSMOUSE_CMD_SETRATE, 0x64 }, + { PSMOUSE_CMD_SETRATE, 0x50 }, + { PSMOUSE_CMD_GETID, 0 }, + { PSMOUSE_CMD_ENABLE, 0 }, + /* + * BYD-specific initialization, which enables absolute mode and + * (if desired), the touchpad's built-in gesture detection. + */ + { 0x10E2, 0x00 }, + { 0x10E0, 0x02 }, + /* The touchpad should reply with 4 seemingly-random bytes */ + { 0x14E0, 0x01 }, + /* Pairs of parameters and values. */ + { BYD_CMD_SET_HANDEDNESS, 0x01 }, + { BYD_CMD_SET_PHYSICAL_BUTTONS, 0x04 }, + { BYD_CMD_SET_TAP, 0x02 }, + { BYD_CMD_SET_ONE_FINGER_SCROLL, 0x04 }, + { BYD_CMD_SET_ONE_FINGER_SCROLL_FUNC, 0x04 }, + { BYD_CMD_SET_EDGE_MOTION, 0x01 }, + { BYD_CMD_SET_PALM_CHECK, 0x00 }, + { BYD_CMD_SET_MULTITOUCH, 0x02 }, + { BYD_CMD_SET_TWO_FINGER_SCROLL, 0x04 }, + { BYD_CMD_SET_TWO_FINGER_SCROLL_FUNC, 0x04 }, + { BYD_CMD_SET_LEFT_EDGE_REGION, 0x00 }, + { BYD_CMD_SET_TOP_EDGE_REGION, 0x00 }, + { BYD_CMD_SET_RIGHT_EDGE_REGION, 0x00 }, + { BYD_CMD_SET_BOTTOM_EDGE_REGION, 0x00 }, + { BYD_CMD_SET_ABSOLUTE_MODE, 0x02 }, + /* Finalize initialization. */ + { 0x10E0, 0x00 }, + { 0x10E2, 0x01 }, + }; + + for (i = 0; i < ARRAY_SIZE(seq); ++i) { + memset(param, 0, sizeof(param)); + param[0] = seq[i].arg; + if (ps2_command(ps2dev, param, seq[i].command)) + return -EIO; + } + + psmouse_set_state(psmouse, PSMOUSE_ACTIVATED); + return 0; +} + +static int byd_reconnect(struct psmouse *psmouse) +{ + int retry = 0, error = 0; + + psmouse_dbg(psmouse, "Reconnect\n"); + do { + psmouse_reset(psmouse); + if (retry) + ssleep(1); + error = byd_detect(psmouse, 0); + } while (error && ++retry < 3); + + if (error) + return error; + + psmouse_dbg(psmouse, "Reconnected after %d attempts\n", retry); + + error = byd_reset_touchpad(psmouse); + if (error) { + psmouse_err(psmouse, "Unable to initialize device\n"); + return error; + } + + return 0; +} + +static void byd_disconnect(struct psmouse *psmouse) +{ + struct byd_data *priv = psmouse->private; + + if (priv) { + del_timer(&priv->timer); + kfree(psmouse->private); + psmouse->private = NULL; + } +} + +int byd_detect(struct psmouse *psmouse, bool set_properties) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + u8 param[4] = {0x03, 0x00, 0x00, 0x00}; + + if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES)) + return -1; + if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES)) + return -1; + if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES)) + return -1; + if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES)) + return -1; + if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) + return -1; + + if (param[1] != 0x03 || param[2] != 0x64) + return -ENODEV; + + psmouse_dbg(psmouse, "BYD touchpad detected\n"); + + if (set_properties) { + psmouse->vendor = "BYD"; + psmouse->name = "TouchPad"; + } + + return 0; +} + +int byd_init(struct psmouse *psmouse) +{ + struct input_dev *dev = psmouse->dev; + struct byd_data *priv; + + if (psmouse_reset(psmouse)) + return -EIO; + + if (byd_reset_touchpad(psmouse)) + return -EIO; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->psmouse = psmouse; + timer_setup(&priv->timer, byd_clear_touch, 0); + + psmouse->private = priv; + psmouse->disconnect = byd_disconnect; + psmouse->reconnect = byd_reconnect; + psmouse->protocol_handler = byd_process_byte; + psmouse->pktsize = 4; + psmouse->resync_time = 0; + + __set_bit(INPUT_PROP_POINTER, dev->propbit); + /* Touchpad */ + __set_bit(BTN_TOUCH, dev->keybit); + __set_bit(BTN_TOOL_FINGER, dev->keybit); + /* Buttons */ + __set_bit(BTN_LEFT, dev->keybit); + __set_bit(BTN_RIGHT, dev->keybit); + __clear_bit(BTN_MIDDLE, dev->keybit); + + /* Absolute position */ + __set_bit(EV_ABS, dev->evbit); + input_set_abs_params(dev, ABS_X, 0, BYD_PAD_WIDTH, 0, 0); + input_set_abs_params(dev, ABS_Y, 0, BYD_PAD_HEIGHT, 0, 0); + input_abs_set_res(dev, ABS_X, BYD_PAD_RESOLUTION); + input_abs_set_res(dev, ABS_Y, BYD_PAD_RESOLUTION); + /* No relative support */ + __clear_bit(EV_REL, dev->evbit); + __clear_bit(REL_X, dev->relbit); + __clear_bit(REL_Y, dev->relbit); + + return 0; +} diff --git a/drivers/input/mouse/byd.h b/drivers/input/mouse/byd.h new file mode 100644 index 000000000..8cb90d904 --- /dev/null +++ b/drivers/input/mouse/byd.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _BYD_H +#define _BYD_H + +#ifdef CONFIG_MOUSE_PS2_BYD +int byd_detect(struct psmouse *psmouse, bool set_properties); +int byd_init(struct psmouse *psmouse); +#else +static inline int byd_detect(struct psmouse *psmouse, bool set_properties) +{ + return -ENOSYS; +} +static inline int byd_init(struct psmouse *psmouse) +{ + return -ENOSYS; +} +#endif /* CONFIG_MOUSE_PS2_BYD */ + +#endif /* _BYD_H */ diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c new file mode 100644 index 000000000..dfd387351 --- /dev/null +++ b/drivers/input/mouse/cyapa.c @@ -0,0 +1,1517 @@ +/* + * Cypress APA trackpad with I2C interface + * + * Author: Dudley Du <dudl@cypress.com> + * Further cleanup and restructuring by: + * Daniel Kurtz <djkurtz@chromium.org> + * Benson Leung <bleung@chromium.org> + * + * Copyright (C) 2011-2015 Cypress Semiconductor, Inc. + * Copyright (C) 2011-2012 Google, Inc. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/pm_runtime.h> +#include <linux/acpi.h> +#include <linux/of.h> +#include "cyapa.h" + + +#define CYAPA_ADAPTER_FUNC_NONE 0 +#define CYAPA_ADAPTER_FUNC_I2C 1 +#define CYAPA_ADAPTER_FUNC_SMBUS 2 +#define CYAPA_ADAPTER_FUNC_BOTH 3 + +#define CYAPA_FW_NAME "cyapa.bin" + +const char product_id[] = "CYTRA"; + +static int cyapa_reinitialize(struct cyapa *cyapa); + +bool cyapa_is_pip_bl_mode(struct cyapa *cyapa) +{ + if (cyapa->gen == CYAPA_GEN6 && cyapa->state == CYAPA_STATE_GEN6_BL) + return true; + + if (cyapa->gen == CYAPA_GEN5 && cyapa->state == CYAPA_STATE_GEN5_BL) + return true; + + return false; +} + +bool cyapa_is_pip_app_mode(struct cyapa *cyapa) +{ + if (cyapa->gen == CYAPA_GEN6 && cyapa->state == CYAPA_STATE_GEN6_APP) + return true; + + if (cyapa->gen == CYAPA_GEN5 && cyapa->state == CYAPA_STATE_GEN5_APP) + return true; + + return false; +} + +static bool cyapa_is_bootloader_mode(struct cyapa *cyapa) +{ + if (cyapa_is_pip_bl_mode(cyapa)) + return true; + + if (cyapa->gen == CYAPA_GEN3 && + cyapa->state >= CYAPA_STATE_BL_BUSY && + cyapa->state <= CYAPA_STATE_BL_ACTIVE) + return true; + + return false; +} + +static inline bool cyapa_is_operational_mode(struct cyapa *cyapa) +{ + if (cyapa_is_pip_app_mode(cyapa)) + return true; + + if (cyapa->gen == CYAPA_GEN3 && cyapa->state == CYAPA_STATE_OP) + return true; + + return false; +} + +/* Returns 0 on success, else negative errno on failure. */ +static ssize_t cyapa_i2c_read(struct cyapa *cyapa, u8 reg, size_t len, + u8 *values) +{ + struct i2c_client *client = cyapa->client; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = ®, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = values, + }, + }; + int ret; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + + if (ret != ARRAY_SIZE(msgs)) + return ret < 0 ? ret : -EIO; + + return 0; +} + +/** + * cyapa_i2c_write - Execute i2c block data write operation + * @cyapa: Handle to this driver + * @ret: Offset of the data to written in the register map + * @len: number of bytes to write + * @values: Data to be written + * + * Return negative errno code on error; return zero when success. + */ +static int cyapa_i2c_write(struct cyapa *cyapa, u8 reg, + size_t len, const void *values) +{ + struct i2c_client *client = cyapa->client; + char buf[32]; + int ret; + + if (len > sizeof(buf) - 1) + return -ENOMEM; + + buf[0] = reg; + memcpy(&buf[1], values, len); + + ret = i2c_master_send(client, buf, len + 1); + if (ret != len + 1) + return ret < 0 ? ret : -EIO; + + return 0; +} + +static u8 cyapa_check_adapter_functionality(struct i2c_client *client) +{ + u8 ret = CYAPA_ADAPTER_FUNC_NONE; + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + ret |= CYAPA_ADAPTER_FUNC_I2C; + if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK)) + ret |= CYAPA_ADAPTER_FUNC_SMBUS; + return ret; +} + +/* + * Query device for its current operating state. + */ +static int cyapa_get_state(struct cyapa *cyapa) +{ + u8 status[BL_STATUS_SIZE]; + u8 cmd[32]; + /* The i2c address of gen4 and gen5 trackpad device must be even. */ + bool even_addr = ((cyapa->client->addr & 0x0001) == 0); + bool smbus = false; + int retries = 2; + int error; + + cyapa->state = CYAPA_STATE_NO_DEVICE; + + /* + * Get trackpad status by reading 3 registers starting from 0. + * If the device is in the bootloader, this will be BL_HEAD. + * If the device is in operation mode, this will be the DATA regs. + * + */ + error = cyapa_i2c_reg_read_block(cyapa, BL_HEAD_OFFSET, BL_STATUS_SIZE, + status); + + /* + * On smbus systems in OP mode, the i2c_reg_read will fail with + * -ETIMEDOUT. In this case, try again using the smbus equivalent + * command. This should return a BL_HEAD indicating CYAPA_STATE_OP. + */ + if (cyapa->smbus && (error == -ETIMEDOUT || error == -ENXIO)) { + if (!even_addr) + error = cyapa_read_block(cyapa, + CYAPA_CMD_BL_STATUS, status); + smbus = true; + } + + if (error != BL_STATUS_SIZE) + goto error; + + /* + * Detect trackpad protocol based on characteristic registers and bits. + */ + do { + cyapa->status[REG_OP_STATUS] = status[REG_OP_STATUS]; + cyapa->status[REG_BL_STATUS] = status[REG_BL_STATUS]; + cyapa->status[REG_BL_ERROR] = status[REG_BL_ERROR]; + + if (cyapa->gen == CYAPA_GEN_UNKNOWN || + cyapa->gen == CYAPA_GEN3) { + error = cyapa_gen3_ops.state_parse(cyapa, + status, BL_STATUS_SIZE); + if (!error) + goto out_detected; + } + if (cyapa->gen == CYAPA_GEN_UNKNOWN || + cyapa->gen == CYAPA_GEN6 || + cyapa->gen == CYAPA_GEN5) { + error = cyapa_pip_state_parse(cyapa, + status, BL_STATUS_SIZE); + if (!error) + goto out_detected; + } + /* For old Gen5 trackpads detecting. */ + if ((cyapa->gen == CYAPA_GEN_UNKNOWN || + cyapa->gen == CYAPA_GEN5) && + !smbus && even_addr) { + error = cyapa_gen5_ops.state_parse(cyapa, + status, BL_STATUS_SIZE); + if (!error) + goto out_detected; + } + + /* + * Write 0x00 0x00 to trackpad device to force update its + * status, then redo the detection again. + */ + if (!smbus) { + cmd[0] = 0x00; + cmd[1] = 0x00; + error = cyapa_i2c_write(cyapa, 0, 2, cmd); + if (error) + goto error; + + msleep(50); + + error = cyapa_i2c_read(cyapa, BL_HEAD_OFFSET, + BL_STATUS_SIZE, status); + if (error) + goto error; + } + } while (--retries > 0 && !smbus); + + goto error; + +out_detected: + if (cyapa->state <= CYAPA_STATE_BL_BUSY) + return -EAGAIN; + return 0; + +error: + return (error < 0) ? error : -EAGAIN; +} + +/* + * Poll device for its status in a loop, waiting up to timeout for a response. + * + * When the device switches state, it usually takes ~300 ms. + * However, when running a new firmware image, the device must calibrate its + * sensors, which can take as long as 2 seconds. + * + * Note: The timeout has granularity of the polling rate, which is 100 ms. + * + * Returns: + * 0 when the device eventually responds with a valid non-busy state. + * -ETIMEDOUT if device never responds (too many -EAGAIN) + * -EAGAIN if bootload is busy, or unknown state. + * < 0 other errors + */ +int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout) +{ + int error; + int tries = timeout / 100; + + do { + error = cyapa_get_state(cyapa); + if (!error && cyapa->state > CYAPA_STATE_BL_BUSY) + return 0; + + msleep(100); + } while (tries--); + + return (error == -EAGAIN || error == -ETIMEDOUT) ? -ETIMEDOUT : error; +} + +/* + * Check if device is operational. + * + * An operational device is responding, has exited bootloader, and has + * firmware supported by this driver. + * + * Returns: + * -ENODEV no device + * -EBUSY no device or in bootloader + * -EIO failure while reading from device + * -ETIMEDOUT timeout failure for bus idle or bus no response + * -EAGAIN device is still in bootloader + * if ->state = CYAPA_STATE_BL_IDLE, device has invalid firmware + * -EINVAL device is in operational mode, but not supported by this driver + * 0 device is supported + */ +static int cyapa_check_is_operational(struct cyapa *cyapa) +{ + int error; + + error = cyapa_poll_state(cyapa, 4000); + if (error) + return error; + + switch (cyapa->gen) { + case CYAPA_GEN6: + cyapa->ops = &cyapa_gen6_ops; + break; + case CYAPA_GEN5: + cyapa->ops = &cyapa_gen5_ops; + break; + case CYAPA_GEN3: + cyapa->ops = &cyapa_gen3_ops; + break; + default: + return -ENODEV; + } + + error = cyapa->ops->operational_check(cyapa); + if (!error && cyapa_is_operational_mode(cyapa)) + cyapa->operational = true; + else + cyapa->operational = false; + + return error; +} + + +/* + * Returns 0 on device detected, negative errno on no device detected. + * And when the device is detected and operational, it will be reset to + * full power active mode automatically. + */ +static int cyapa_detect(struct cyapa *cyapa) +{ + struct device *dev = &cyapa->client->dev; + int error; + + error = cyapa_check_is_operational(cyapa); + if (error) { + if (error != -ETIMEDOUT && error != -ENODEV && + cyapa_is_bootloader_mode(cyapa)) { + dev_warn(dev, "device detected but not operational\n"); + return 0; + } + + dev_err(dev, "no device detected: %d\n", error); + return error; + } + + return 0; +} + +static int cyapa_open(struct input_dev *input) +{ + struct cyapa *cyapa = input_get_drvdata(input); + struct i2c_client *client = cyapa->client; + struct device *dev = &client->dev; + int error; + + error = mutex_lock_interruptible(&cyapa->state_sync_lock); + if (error) + return error; + + if (cyapa->operational) { + /* + * though failed to set active power mode, + * but still may be able to work in lower scan rate + * when in operational mode. + */ + error = cyapa->ops->set_power_mode(cyapa, + PWR_MODE_FULL_ACTIVE, 0, CYAPA_PM_ACTIVE); + if (error) { + dev_warn(dev, "set active power failed: %d\n", error); + goto out; + } + } else { + error = cyapa_reinitialize(cyapa); + if (error || !cyapa->operational) { + error = error ? error : -EAGAIN; + goto out; + } + } + + enable_irq(client->irq); + if (!pm_runtime_enabled(dev)) { + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } + + pm_runtime_get_sync(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_sync_autosuspend(dev); +out: + mutex_unlock(&cyapa->state_sync_lock); + return error; +} + +static void cyapa_close(struct input_dev *input) +{ + struct cyapa *cyapa = input_get_drvdata(input); + struct i2c_client *client = cyapa->client; + struct device *dev = &cyapa->client->dev; + + mutex_lock(&cyapa->state_sync_lock); + + disable_irq(client->irq); + if (pm_runtime_enabled(dev)) + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + + if (cyapa->operational) + cyapa->ops->set_power_mode(cyapa, + PWR_MODE_OFF, 0, CYAPA_PM_DEACTIVE); + + mutex_unlock(&cyapa->state_sync_lock); +} + +static int cyapa_create_input_dev(struct cyapa *cyapa) +{ + struct device *dev = &cyapa->client->dev; + struct input_dev *input; + int error; + + if (!cyapa->physical_size_x || !cyapa->physical_size_y) + return -EINVAL; + + input = devm_input_allocate_device(dev); + if (!input) { + dev_err(dev, "failed to allocate memory for input device.\n"); + return -ENOMEM; + } + + input->name = CYAPA_NAME; + input->phys = cyapa->phys; + input->id.bustype = BUS_I2C; + input->id.version = 1; + input->id.product = 0; /* Means any product in eventcomm. */ + input->dev.parent = &cyapa->client->dev; + + input->open = cyapa_open; + input->close = cyapa_close; + + input_set_drvdata(input, cyapa); + + __set_bit(EV_ABS, input->evbit); + + /* Finger position */ + input_set_abs_params(input, ABS_MT_POSITION_X, 0, cyapa->max_abs_x, 0, + 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, cyapa->max_abs_y, 0, + 0); + input_set_abs_params(input, ABS_MT_PRESSURE, 0, cyapa->max_z, 0, 0); + if (cyapa->gen > CYAPA_GEN3) { + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0); + /* + * Orientation is the angle between the vertical axis and + * the major axis of the contact ellipse. + * The range is -127 to 127. + * the positive direction is clockwise form the vertical axis. + * If the ellipse of contact degenerates into a circle, + * orientation is reported as 0. + * + * Also, for Gen5 trackpad the accurate of this orientation + * value is value + (-30 ~ 30). + */ + input_set_abs_params(input, ABS_MT_ORIENTATION, + -127, 127, 0, 0); + } + if (cyapa->gen >= CYAPA_GEN5) { + input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0); + input_set_abs_params(input, ABS_MT_WIDTH_MINOR, 0, 255, 0, 0); + input_set_abs_params(input, ABS_DISTANCE, 0, 1, 0, 0); + } + + input_abs_set_res(input, ABS_MT_POSITION_X, + cyapa->max_abs_x / cyapa->physical_size_x); + input_abs_set_res(input, ABS_MT_POSITION_Y, + cyapa->max_abs_y / cyapa->physical_size_y); + + if (cyapa->btn_capability & CAPABILITY_LEFT_BTN_MASK) + __set_bit(BTN_LEFT, input->keybit); + if (cyapa->btn_capability & CAPABILITY_MIDDLE_BTN_MASK) + __set_bit(BTN_MIDDLE, input->keybit); + if (cyapa->btn_capability & CAPABILITY_RIGHT_BTN_MASK) + __set_bit(BTN_RIGHT, input->keybit); + + if (cyapa->btn_capability == CAPABILITY_LEFT_BTN_MASK) + __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); + + /* Handle pointer emulation and unused slots in core */ + error = input_mt_init_slots(input, CYAPA_MAX_MT_SLOTS, + INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED); + if (error) { + dev_err(dev, "failed to initialize MT slots: %d\n", error); + return error; + } + + /* Register the device in input subsystem */ + error = input_register_device(input); + if (error) { + dev_err(dev, "failed to register input device: %d\n", error); + return error; + } + + cyapa->input = input; + return 0; +} + +static void cyapa_enable_irq_for_cmd(struct cyapa *cyapa) +{ + struct input_dev *input = cyapa->input; + + if (!input || !input->users) { + /* + * When input is NULL, TP must be in deep sleep mode. + * In this mode, later non-power I2C command will always failed + * if not bring it out of deep sleep mode firstly, + * so must command TP to active mode here. + */ + if (!input || cyapa->operational) + cyapa->ops->set_power_mode(cyapa, + PWR_MODE_FULL_ACTIVE, 0, CYAPA_PM_ACTIVE); + /* Gen3 always using polling mode for command. */ + if (cyapa->gen >= CYAPA_GEN5) + enable_irq(cyapa->client->irq); + } +} + +static void cyapa_disable_irq_for_cmd(struct cyapa *cyapa) +{ + struct input_dev *input = cyapa->input; + + if (!input || !input->users) { + if (cyapa->gen >= CYAPA_GEN5) + disable_irq(cyapa->client->irq); + if (!input || cyapa->operational) + cyapa->ops->set_power_mode(cyapa, + PWR_MODE_OFF, 0, CYAPA_PM_ACTIVE); + } +} + +/* + * cyapa_sleep_time_to_pwr_cmd and cyapa_pwr_cmd_to_sleep_time + * + * These are helper functions that convert to and from integer idle + * times and register settings to write to the PowerMode register. + * The trackpad supports between 20ms to 1000ms scan intervals. + * The time will be increased in increments of 10ms from 20ms to 100ms. + * From 100ms to 1000ms, time will be increased in increments of 20ms. + * + * When Idle_Time < 100, the format to convert Idle_Time to Idle_Command is: + * Idle_Command = Idle Time / 10; + * When Idle_Time >= 100, the format to convert Idle_Time to Idle_Command is: + * Idle_Command = Idle Time / 20 + 5; + */ +u8 cyapa_sleep_time_to_pwr_cmd(u16 sleep_time) +{ + u16 encoded_time; + + sleep_time = clamp_val(sleep_time, 20, 1000); + encoded_time = sleep_time < 100 ? sleep_time / 10 : sleep_time / 20 + 5; + return (encoded_time << 2) & PWR_MODE_MASK; +} + +u16 cyapa_pwr_cmd_to_sleep_time(u8 pwr_mode) +{ + u8 encoded_time = pwr_mode >> 2; + + return (encoded_time < 10) ? encoded_time * 10 + : (encoded_time - 5) * 20; +} + +/* 0 on driver initialize and detected successfully, negative on failure. */ +static int cyapa_initialize(struct cyapa *cyapa) +{ + int error = 0; + + cyapa->state = CYAPA_STATE_NO_DEVICE; + cyapa->gen = CYAPA_GEN_UNKNOWN; + mutex_init(&cyapa->state_sync_lock); + + /* + * Set to hard code default, they will be updated with trackpad set + * default values after probe and initialized. + */ + cyapa->suspend_power_mode = PWR_MODE_SLEEP; + cyapa->suspend_sleep_time = + cyapa_pwr_cmd_to_sleep_time(cyapa->suspend_power_mode); + + /* ops.initialize() is aimed to prepare for module communications. */ + error = cyapa_gen3_ops.initialize(cyapa); + if (!error) + error = cyapa_gen5_ops.initialize(cyapa); + if (!error) + error = cyapa_gen6_ops.initialize(cyapa); + if (error) + return error; + + error = cyapa_detect(cyapa); + if (error) + return error; + + /* Power down the device until we need it. */ + if (cyapa->operational) + cyapa->ops->set_power_mode(cyapa, + PWR_MODE_OFF, 0, CYAPA_PM_ACTIVE); + + return 0; +} + +static int cyapa_reinitialize(struct cyapa *cyapa) +{ + struct device *dev = &cyapa->client->dev; + struct input_dev *input = cyapa->input; + int error; + + if (pm_runtime_enabled(dev)) + pm_runtime_disable(dev); + + /* Avoid command failures when TP was in OFF state. */ + if (cyapa->operational) + cyapa->ops->set_power_mode(cyapa, + PWR_MODE_FULL_ACTIVE, 0, CYAPA_PM_ACTIVE); + + error = cyapa_detect(cyapa); + if (error) + goto out; + + if (!input && cyapa->operational) { + error = cyapa_create_input_dev(cyapa); + if (error) { + dev_err(dev, "create input_dev instance failed: %d\n", + error); + goto out; + } + } + +out: + if (!input || !input->users) { + /* Reset to power OFF state to save power when no user open. */ + if (cyapa->operational) + cyapa->ops->set_power_mode(cyapa, + PWR_MODE_OFF, 0, CYAPA_PM_DEACTIVE); + } else if (!error && cyapa->operational) { + /* + * Make sure only enable runtime PM when device is + * in operational mode and input->users > 0. + */ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + pm_runtime_get_sync(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_sync_autosuspend(dev); + } + + return error; +} + +static irqreturn_t cyapa_irq(int irq, void *dev_id) +{ + struct cyapa *cyapa = dev_id; + struct device *dev = &cyapa->client->dev; + int error; + + if (device_may_wakeup(dev)) + pm_wakeup_event(dev, 0); + + /* Interrupt event can be caused by host command to trackpad device. */ + if (cyapa->ops->irq_cmd_handler(cyapa)) { + /* + * Interrupt event maybe from trackpad device input reporting. + */ + if (!cyapa->input) { + /* + * Still in probing or in firmware image + * updating or reading. + */ + cyapa->ops->sort_empty_output_data(cyapa, + NULL, NULL, NULL); + goto out; + } + + if (cyapa->operational) { + error = cyapa->ops->irq_handler(cyapa); + + /* + * Apply runtime power management to touch report event + * except the events caused by the command responses. + * Note: + * It will introduce about 20~40 ms additional delay + * time in receiving for first valid touch report data. + * The time is used to execute device runtime resume + * process. + */ + pm_runtime_get_sync(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_sync_autosuspend(dev); + } + + if (!cyapa->operational || error) { + if (!mutex_trylock(&cyapa->state_sync_lock)) { + cyapa->ops->sort_empty_output_data(cyapa, + NULL, NULL, NULL); + goto out; + } + cyapa_reinitialize(cyapa); + mutex_unlock(&cyapa->state_sync_lock); + } + } + +out: + return IRQ_HANDLED; +} + +/* + ************************************************************** + * sysfs interface + ************************************************************** +*/ +#ifdef CONFIG_PM_SLEEP +static ssize_t cyapa_show_suspend_scanrate(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct cyapa *cyapa = dev_get_drvdata(dev); + u8 pwr_cmd; + u16 sleep_time; + int len; + int error; + + error = mutex_lock_interruptible(&cyapa->state_sync_lock); + if (error) + return error; + + pwr_cmd = cyapa->suspend_power_mode; + sleep_time = cyapa->suspend_sleep_time; + + mutex_unlock(&cyapa->state_sync_lock); + + switch (pwr_cmd) { + case PWR_MODE_BTN_ONLY: + len = scnprintf(buf, PAGE_SIZE, "%s\n", BTN_ONLY_MODE_NAME); + break; + + case PWR_MODE_OFF: + len = scnprintf(buf, PAGE_SIZE, "%s\n", OFF_MODE_NAME); + break; + + default: + len = scnprintf(buf, PAGE_SIZE, "%u\n", + cyapa->gen == CYAPA_GEN3 ? + cyapa_pwr_cmd_to_sleep_time(pwr_cmd) : + sleep_time); + break; + } + + return len; +} + +static ssize_t cyapa_update_suspend_scanrate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cyapa *cyapa = dev_get_drvdata(dev); + u16 sleep_time; + int error; + + error = mutex_lock_interruptible(&cyapa->state_sync_lock); + if (error) + return error; + + if (sysfs_streq(buf, BTN_ONLY_MODE_NAME)) { + cyapa->suspend_power_mode = PWR_MODE_BTN_ONLY; + } else if (sysfs_streq(buf, OFF_MODE_NAME)) { + cyapa->suspend_power_mode = PWR_MODE_OFF; + } else if (!kstrtou16(buf, 10, &sleep_time)) { + cyapa->suspend_sleep_time = min_t(u16, sleep_time, 1000); + cyapa->suspend_power_mode = + cyapa_sleep_time_to_pwr_cmd(cyapa->suspend_sleep_time); + } else { + count = -EINVAL; + } + + mutex_unlock(&cyapa->state_sync_lock); + + return count; +} + +static DEVICE_ATTR(suspend_scanrate_ms, S_IRUGO|S_IWUSR, + cyapa_show_suspend_scanrate, + cyapa_update_suspend_scanrate); + +static struct attribute *cyapa_power_wakeup_entries[] = { + &dev_attr_suspend_scanrate_ms.attr, + NULL, +}; + +static const struct attribute_group cyapa_power_wakeup_group = { + .name = power_group_name, + .attrs = cyapa_power_wakeup_entries, +}; + +static void cyapa_remove_power_wakeup_group(void *data) +{ + struct cyapa *cyapa = data; + + sysfs_unmerge_group(&cyapa->client->dev.kobj, + &cyapa_power_wakeup_group); +} + +static int cyapa_prepare_wakeup_controls(struct cyapa *cyapa) +{ + struct i2c_client *client = cyapa->client; + struct device *dev = &client->dev; + int error; + + if (device_can_wakeup(dev)) { + error = sysfs_merge_group(&dev->kobj, + &cyapa_power_wakeup_group); + if (error) { + dev_err(dev, "failed to add power wakeup group: %d\n", + error); + return error; + } + + error = devm_add_action(dev, + cyapa_remove_power_wakeup_group, cyapa); + if (error) { + cyapa_remove_power_wakeup_group(cyapa); + dev_err(dev, "failed to add power cleanup action: %d\n", + error); + return error; + } + } + + return 0; +} +#else +static inline int cyapa_prepare_wakeup_controls(struct cyapa *cyapa) +{ + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM +static ssize_t cyapa_show_rt_suspend_scanrate(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct cyapa *cyapa = dev_get_drvdata(dev); + u8 pwr_cmd; + u16 sleep_time; + int error; + + error = mutex_lock_interruptible(&cyapa->state_sync_lock); + if (error) + return error; + + pwr_cmd = cyapa->runtime_suspend_power_mode; + sleep_time = cyapa->runtime_suspend_sleep_time; + + mutex_unlock(&cyapa->state_sync_lock); + + return scnprintf(buf, PAGE_SIZE, "%u\n", + cyapa->gen == CYAPA_GEN3 ? + cyapa_pwr_cmd_to_sleep_time(pwr_cmd) : + sleep_time); +} + +static ssize_t cyapa_update_rt_suspend_scanrate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cyapa *cyapa = dev_get_drvdata(dev); + u16 time; + int error; + + if (buf == NULL || count == 0 || kstrtou16(buf, 10, &time)) { + dev_err(dev, "invalid runtime suspend scanrate ms parameter\n"); + return -EINVAL; + } + + /* + * When the suspend scanrate is changed, pm_runtime_get to resume + * a potentially suspended device, update to the new pwr_cmd + * and then pm_runtime_put to suspend into the new power mode. + */ + pm_runtime_get_sync(dev); + + error = mutex_lock_interruptible(&cyapa->state_sync_lock); + if (error) + return error; + + cyapa->runtime_suspend_sleep_time = min_t(u16, time, 1000); + cyapa->runtime_suspend_power_mode = + cyapa_sleep_time_to_pwr_cmd(cyapa->runtime_suspend_sleep_time); + + mutex_unlock(&cyapa->state_sync_lock); + + pm_runtime_put_sync_autosuspend(dev); + + return count; +} + +static DEVICE_ATTR(runtime_suspend_scanrate_ms, S_IRUGO|S_IWUSR, + cyapa_show_rt_suspend_scanrate, + cyapa_update_rt_suspend_scanrate); + +static struct attribute *cyapa_power_runtime_entries[] = { + &dev_attr_runtime_suspend_scanrate_ms.attr, + NULL, +}; + +static const struct attribute_group cyapa_power_runtime_group = { + .name = power_group_name, + .attrs = cyapa_power_runtime_entries, +}; + +static void cyapa_remove_power_runtime_group(void *data) +{ + struct cyapa *cyapa = data; + + sysfs_unmerge_group(&cyapa->client->dev.kobj, + &cyapa_power_runtime_group); +} + +static int cyapa_start_runtime(struct cyapa *cyapa) +{ + struct device *dev = &cyapa->client->dev; + int error; + + cyapa->runtime_suspend_power_mode = PWR_MODE_IDLE; + cyapa->runtime_suspend_sleep_time = + cyapa_pwr_cmd_to_sleep_time(cyapa->runtime_suspend_power_mode); + + error = sysfs_merge_group(&dev->kobj, &cyapa_power_runtime_group); + if (error) { + dev_err(dev, + "failed to create power runtime group: %d\n", error); + return error; + } + + error = devm_add_action(dev, cyapa_remove_power_runtime_group, cyapa); + if (error) { + cyapa_remove_power_runtime_group(cyapa); + dev_err(dev, + "failed to add power runtime cleanup action: %d\n", + error); + return error; + } + + /* runtime is enabled until device is operational and opened. */ + pm_runtime_set_suspended(dev); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, AUTOSUSPEND_DELAY); + + return 0; +} +#else +static inline int cyapa_start_runtime(struct cyapa *cyapa) +{ + return 0; +} +#endif /* CONFIG_PM */ + +static ssize_t cyapa_show_fm_ver(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int error; + struct cyapa *cyapa = dev_get_drvdata(dev); + + error = mutex_lock_interruptible(&cyapa->state_sync_lock); + if (error) + return error; + error = scnprintf(buf, PAGE_SIZE, "%d.%d\n", cyapa->fw_maj_ver, + cyapa->fw_min_ver); + mutex_unlock(&cyapa->state_sync_lock); + return error; +} + +static ssize_t cyapa_show_product_id(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyapa *cyapa = dev_get_drvdata(dev); + int size; + int error; + + error = mutex_lock_interruptible(&cyapa->state_sync_lock); + if (error) + return error; + size = scnprintf(buf, PAGE_SIZE, "%s\n", cyapa->product_id); + mutex_unlock(&cyapa->state_sync_lock); + return size; +} + +static int cyapa_firmware(struct cyapa *cyapa, const char *fw_name) +{ + struct device *dev = &cyapa->client->dev; + const struct firmware *fw; + int error; + + error = request_firmware(&fw, fw_name, dev); + if (error) { + dev_err(dev, "Could not load firmware from %s: %d\n", + fw_name, error); + return error; + } + + error = cyapa->ops->check_fw(cyapa, fw); + if (error) { + dev_err(dev, "Invalid CYAPA firmware image: %s\n", + fw_name); + goto done; + } + + /* + * Resume the potentially suspended device because doing FW + * update on a device not in the FULL mode has a chance to + * fail. + */ + pm_runtime_get_sync(dev); + + /* Require IRQ support for firmware update commands. */ + cyapa_enable_irq_for_cmd(cyapa); + + error = cyapa->ops->bl_enter(cyapa); + if (error) { + dev_err(dev, "bl_enter failed, %d\n", error); + goto err_detect; + } + + error = cyapa->ops->bl_activate(cyapa); + if (error) { + dev_err(dev, "bl_activate failed, %d\n", error); + goto err_detect; + } + + error = cyapa->ops->bl_initiate(cyapa, fw); + if (error) { + dev_err(dev, "bl_initiate failed, %d\n", error); + goto err_detect; + } + + error = cyapa->ops->update_fw(cyapa, fw); + if (error) { + dev_err(dev, "update_fw failed, %d\n", error); + goto err_detect; + } + +err_detect: + cyapa_disable_irq_for_cmd(cyapa); + pm_runtime_put_noidle(dev); + +done: + release_firmware(fw); + return error; +} + +static ssize_t cyapa_update_fw_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cyapa *cyapa = dev_get_drvdata(dev); + char fw_name[NAME_MAX]; + int ret, error; + + if (count >= NAME_MAX) { + dev_err(dev, "File name too long\n"); + return -EINVAL; + } + + memcpy(fw_name, buf, count); + if (fw_name[count - 1] == '\n') + fw_name[count - 1] = '\0'; + else + fw_name[count] = '\0'; + + if (cyapa->input) { + /* + * Force the input device to be registered after the firmware + * image is updated, so if the corresponding parameters updated + * in the new firmware image can taken effect immediately. + */ + input_unregister_device(cyapa->input); + cyapa->input = NULL; + } + + error = mutex_lock_interruptible(&cyapa->state_sync_lock); + if (error) { + /* + * Whatever, do reinitialize to try to recover TP state to + * previous state just as it entered fw update entrance. + */ + cyapa_reinitialize(cyapa); + return error; + } + + error = cyapa_firmware(cyapa, fw_name); + if (error) + dev_err(dev, "firmware update failed: %d\n", error); + else + dev_dbg(dev, "firmware update successfully done.\n"); + + /* + * Re-detect trackpad device states because firmware update process + * will reset trackpad device into bootloader mode. + */ + ret = cyapa_reinitialize(cyapa); + if (ret) { + dev_err(dev, "failed to re-detect after updated: %d\n", ret); + error = error ? error : ret; + } + + mutex_unlock(&cyapa->state_sync_lock); + + return error ? error : count; +} + +static ssize_t cyapa_calibrate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cyapa *cyapa = dev_get_drvdata(dev); + int error; + + error = mutex_lock_interruptible(&cyapa->state_sync_lock); + if (error) + return error; + + if (cyapa->operational) { + cyapa_enable_irq_for_cmd(cyapa); + error = cyapa->ops->calibrate_store(dev, attr, buf, count); + cyapa_disable_irq_for_cmd(cyapa); + } else { + error = -EBUSY; /* Still running in bootloader mode. */ + } + + mutex_unlock(&cyapa->state_sync_lock); + return error < 0 ? error : count; +} + +static ssize_t cyapa_show_baseline(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyapa *cyapa = dev_get_drvdata(dev); + ssize_t error; + + error = mutex_lock_interruptible(&cyapa->state_sync_lock); + if (error) + return error; + + if (cyapa->operational) { + cyapa_enable_irq_for_cmd(cyapa); + error = cyapa->ops->show_baseline(dev, attr, buf); + cyapa_disable_irq_for_cmd(cyapa); + } else { + error = -EBUSY; /* Still running in bootloader mode. */ + } + + mutex_unlock(&cyapa->state_sync_lock); + return error; +} + +static char *cyapa_state_to_string(struct cyapa *cyapa) +{ + switch (cyapa->state) { + case CYAPA_STATE_BL_BUSY: + return "bootloader busy"; + case CYAPA_STATE_BL_IDLE: + return "bootloader idle"; + case CYAPA_STATE_BL_ACTIVE: + return "bootloader active"; + case CYAPA_STATE_GEN5_BL: + case CYAPA_STATE_GEN6_BL: + return "bootloader"; + case CYAPA_STATE_OP: + case CYAPA_STATE_GEN5_APP: + case CYAPA_STATE_GEN6_APP: + return "operational"; /* Normal valid state. */ + default: + return "invalid mode"; + } +} + +static ssize_t cyapa_show_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyapa *cyapa = dev_get_drvdata(dev); + int size; + int error; + + error = mutex_lock_interruptible(&cyapa->state_sync_lock); + if (error) + return error; + + size = scnprintf(buf, PAGE_SIZE, "gen%d %s\n", + cyapa->gen, cyapa_state_to_string(cyapa)); + + mutex_unlock(&cyapa->state_sync_lock); + return size; +} + +static DEVICE_ATTR(firmware_version, S_IRUGO, cyapa_show_fm_ver, NULL); +static DEVICE_ATTR(product_id, S_IRUGO, cyapa_show_product_id, NULL); +static DEVICE_ATTR(update_fw, S_IWUSR, NULL, cyapa_update_fw_store); +static DEVICE_ATTR(baseline, S_IRUGO, cyapa_show_baseline, NULL); +static DEVICE_ATTR(calibrate, S_IWUSR, NULL, cyapa_calibrate_store); +static DEVICE_ATTR(mode, S_IRUGO, cyapa_show_mode, NULL); + +static struct attribute *cyapa_sysfs_entries[] = { + &dev_attr_firmware_version.attr, + &dev_attr_product_id.attr, + &dev_attr_update_fw.attr, + &dev_attr_baseline.attr, + &dev_attr_calibrate.attr, + &dev_attr_mode.attr, + NULL, +}; + +static const struct attribute_group cyapa_sysfs_group = { + .attrs = cyapa_sysfs_entries, +}; + +static void cyapa_remove_sysfs_group(void *data) +{ + struct cyapa *cyapa = data; + + sysfs_remove_group(&cyapa->client->dev.kobj, &cyapa_sysfs_group); +} + +static void cyapa_disable_regulator(void *data) +{ + struct cyapa *cyapa = data; + + regulator_disable(cyapa->vcc); +} + +static int cyapa_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + struct device *dev = &client->dev; + struct cyapa *cyapa; + u8 adapter_func; + union i2c_smbus_data dummy; + int error; + + adapter_func = cyapa_check_adapter_functionality(client); + if (adapter_func == CYAPA_ADAPTER_FUNC_NONE) { + dev_err(dev, "not a supported I2C/SMBus adapter\n"); + return -EIO; + } + + /* Make sure there is something at this address */ + if (i2c_smbus_xfer(client->adapter, client->addr, 0, + I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0) + return -ENODEV; + + cyapa = devm_kzalloc(dev, sizeof(struct cyapa), GFP_KERNEL); + if (!cyapa) + return -ENOMEM; + + /* i2c isn't supported, use smbus */ + if (adapter_func == CYAPA_ADAPTER_FUNC_SMBUS) + cyapa->smbus = true; + + cyapa->client = client; + i2c_set_clientdata(client, cyapa); + sprintf(cyapa->phys, "i2c-%d-%04x/input0", client->adapter->nr, + client->addr); + + cyapa->vcc = devm_regulator_get(dev, "vcc"); + if (IS_ERR(cyapa->vcc)) { + error = PTR_ERR(cyapa->vcc); + dev_err(dev, "failed to get vcc regulator: %d\n", error); + return error; + } + + error = regulator_enable(cyapa->vcc); + if (error) { + dev_err(dev, "failed to enable regulator: %d\n", error); + return error; + } + + error = devm_add_action(dev, cyapa_disable_regulator, cyapa); + if (error) { + cyapa_disable_regulator(cyapa); + dev_err(dev, "failed to add disable regulator action: %d\n", + error); + return error; + } + + error = cyapa_initialize(cyapa); + if (error) { + dev_err(dev, "failed to detect and initialize tp device.\n"); + return error; + } + + error = sysfs_create_group(&dev->kobj, &cyapa_sysfs_group); + if (error) { + dev_err(dev, "failed to create sysfs entries: %d\n", error); + return error; + } + + error = devm_add_action(dev, cyapa_remove_sysfs_group, cyapa); + if (error) { + cyapa_remove_sysfs_group(cyapa); + dev_err(dev, "failed to add sysfs cleanup action: %d\n", error); + return error; + } + + error = cyapa_prepare_wakeup_controls(cyapa); + if (error) { + dev_err(dev, "failed to prepare wakeup controls: %d\n", error); + return error; + } + + error = cyapa_start_runtime(cyapa); + if (error) { + dev_err(dev, "failed to start pm_runtime: %d\n", error); + return error; + } + + error = devm_request_threaded_irq(dev, client->irq, + NULL, cyapa_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "cyapa", cyapa); + if (error) { + dev_err(dev, "failed to request threaded irq: %d\n", error); + return error; + } + + /* Disable IRQ until the device is opened */ + disable_irq(client->irq); + + /* + * Register the device in the input subsystem when it's operational. + * Otherwise, keep in this driver, so it can be be recovered or updated + * through the sysfs mode and update_fw interfaces by user or apps. + */ + if (cyapa->operational) { + error = cyapa_create_input_dev(cyapa); + if (error) { + dev_err(dev, "create input_dev instance failed: %d\n", + error); + return error; + } + } + + return 0; +} + +static int __maybe_unused cyapa_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct cyapa *cyapa = i2c_get_clientdata(client); + u8 power_mode; + int error; + + error = mutex_lock_interruptible(&cyapa->state_sync_lock); + if (error) + return error; + + /* + * Runtime PM is enable only when device is in operational mode and + * users in use, so need check it before disable it to + * avoid unbalance warning. + */ + if (pm_runtime_enabled(dev)) + pm_runtime_disable(dev); + disable_irq(client->irq); + + /* + * Set trackpad device to idle mode if wakeup is allowed, + * otherwise turn off. + */ + if (cyapa->operational) { + power_mode = device_may_wakeup(dev) ? cyapa->suspend_power_mode + : PWR_MODE_OFF; + error = cyapa->ops->set_power_mode(cyapa, power_mode, + cyapa->suspend_sleep_time, CYAPA_PM_SUSPEND); + if (error) + dev_err(dev, "suspend set power mode failed: %d\n", + error); + } + + /* + * Disable proximity interrupt when system idle, want true touch to + * wake the system. + */ + if (cyapa->dev_pwr_mode != PWR_MODE_OFF) + cyapa->ops->set_proximity(cyapa, false); + + if (device_may_wakeup(dev)) + cyapa->irq_wake = (enable_irq_wake(client->irq) == 0); + + mutex_unlock(&cyapa->state_sync_lock); + return 0; +} + +static int __maybe_unused cyapa_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct cyapa *cyapa = i2c_get_clientdata(client); + int error; + + mutex_lock(&cyapa->state_sync_lock); + + if (device_may_wakeup(dev) && cyapa->irq_wake) { + disable_irq_wake(client->irq); + cyapa->irq_wake = false; + } + + /* + * Update device states and runtime PM states. + * Re-Enable proximity interrupt after enter operational mode. + */ + error = cyapa_reinitialize(cyapa); + if (error) + dev_warn(dev, "failed to reinitialize TP device: %d\n", error); + + enable_irq(client->irq); + + mutex_unlock(&cyapa->state_sync_lock); + return 0; +} + +static int __maybe_unused cyapa_runtime_suspend(struct device *dev) +{ + struct cyapa *cyapa = dev_get_drvdata(dev); + int error; + + error = cyapa->ops->set_power_mode(cyapa, + cyapa->runtime_suspend_power_mode, + cyapa->runtime_suspend_sleep_time, + CYAPA_PM_RUNTIME_SUSPEND); + if (error) + dev_warn(dev, "runtime suspend failed: %d\n", error); + + return 0; +} + +static int __maybe_unused cyapa_runtime_resume(struct device *dev) +{ + struct cyapa *cyapa = dev_get_drvdata(dev); + int error; + + error = cyapa->ops->set_power_mode(cyapa, + PWR_MODE_FULL_ACTIVE, 0, CYAPA_PM_RUNTIME_RESUME); + if (error) + dev_warn(dev, "runtime resume failed: %d\n", error); + + return 0; +} + +static const struct dev_pm_ops cyapa_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cyapa_suspend, cyapa_resume) + SET_RUNTIME_PM_OPS(cyapa_runtime_suspend, cyapa_runtime_resume, NULL) +}; + +static const struct i2c_device_id cyapa_id_table[] = { + { "cyapa", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, cyapa_id_table); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id cyapa_acpi_id[] = { + { "CYAP0000", 0 }, /* Gen3 trackpad with 0x67 I2C address. */ + { "CYAP0001", 0 }, /* Gen5 trackpad with 0x24 I2C address. */ + { "CYAP0002", 0 }, /* Gen6 trackpad with 0x24 I2C address. */ + { } +}; +MODULE_DEVICE_TABLE(acpi, cyapa_acpi_id); +#endif + +#ifdef CONFIG_OF +static const struct of_device_id cyapa_of_match[] = { + { .compatible = "cypress,cyapa" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, cyapa_of_match); +#endif + +static struct i2c_driver cyapa_driver = { + .driver = { + .name = "cyapa", + .pm = &cyapa_pm_ops, + .acpi_match_table = ACPI_PTR(cyapa_acpi_id), + .of_match_table = of_match_ptr(cyapa_of_match), + }, + + .probe = cyapa_probe, + .id_table = cyapa_id_table, +}; + +module_i2c_driver(cyapa_driver); + +MODULE_DESCRIPTION("Cypress APA I2C Trackpad Driver"); +MODULE_AUTHOR("Dudley Du <dudl@cypress.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/mouse/cyapa.h b/drivers/input/mouse/cyapa.h new file mode 100644 index 000000000..ce951fe45 --- /dev/null +++ b/drivers/input/mouse/cyapa.h @@ -0,0 +1,446 @@ +/* + * Cypress APA trackpad with I2C interface + * + * Author: Dudley Du <dudl@cypress.com> + * + * Copyright (C) 2014-2015 Cypress Semiconductor, Inc. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#ifndef _CYAPA_H +#define _CYAPA_H + +#include <linux/firmware.h> + +/* APA trackpad firmware generation number. */ +#define CYAPA_GEN_UNKNOWN 0x00 /* unknown protocol. */ +#define CYAPA_GEN3 0x03 /* support MT-protocol B with tracking ID. */ +#define CYAPA_GEN5 0x05 /* support TrueTouch GEN5 trackpad device. */ +#define CYAPA_GEN6 0x06 /* support TrueTouch GEN6 trackpad device. */ + +#define CYAPA_NAME "Cypress APA Trackpad (cyapa)" + +/* + * Macros for SMBus communication + */ +#define SMBUS_READ 0x01 +#define SMBUS_WRITE 0x00 +#define SMBUS_ENCODE_IDX(cmd, idx) ((cmd) | (((idx) & 0x03) << 1)) +#define SMBUS_ENCODE_RW(cmd, rw) ((cmd) | ((rw) & 0x01)) +#define SMBUS_BYTE_BLOCK_CMD_MASK 0x80 +#define SMBUS_GROUP_BLOCK_CMD_MASK 0x40 + +/* Commands for read/write registers of Cypress trackpad */ +#define CYAPA_CMD_SOFT_RESET 0x00 +#define CYAPA_CMD_POWER_MODE 0x01 +#define CYAPA_CMD_DEV_STATUS 0x02 +#define CYAPA_CMD_GROUP_DATA 0x03 +#define CYAPA_CMD_GROUP_CMD 0x04 +#define CYAPA_CMD_GROUP_QUERY 0x05 +#define CYAPA_CMD_BL_STATUS 0x06 +#define CYAPA_CMD_BL_HEAD 0x07 +#define CYAPA_CMD_BL_CMD 0x08 +#define CYAPA_CMD_BL_DATA 0x09 +#define CYAPA_CMD_BL_ALL 0x0a +#define CYAPA_CMD_BLK_PRODUCT_ID 0x0b +#define CYAPA_CMD_BLK_HEAD 0x0c +#define CYAPA_CMD_MAX_BASELINE 0x0d +#define CYAPA_CMD_MIN_BASELINE 0x0e + +#define BL_HEAD_OFFSET 0x00 +#define BL_DATA_OFFSET 0x10 + +#define BL_STATUS_SIZE 3 /* Length of gen3 bootloader status registers */ +#define CYAPA_REG_MAP_SIZE 256 + +/* + * Gen3 Operational Device Status Register + * + * bit 7: Valid interrupt source + * bit 6 - 4: Reserved + * bit 3 - 2: Power status + * bit 1 - 0: Device status + */ +#define REG_OP_STATUS 0x00 +#define OP_STATUS_SRC 0x80 +#define OP_STATUS_POWER 0x0c +#define OP_STATUS_DEV 0x03 +#define OP_STATUS_MASK (OP_STATUS_SRC | OP_STATUS_POWER | OP_STATUS_DEV) + +/* + * Operational Finger Count/Button Flags Register + * + * bit 7 - 4: Number of touched finger + * bit 3: Valid data + * bit 2: Middle Physical Button + * bit 1: Right Physical Button + * bit 0: Left physical Button + */ +#define REG_OP_DATA1 0x01 +#define OP_DATA_VALID 0x08 +#define OP_DATA_MIDDLE_BTN 0x04 +#define OP_DATA_RIGHT_BTN 0x02 +#define OP_DATA_LEFT_BTN 0x01 +#define OP_DATA_BTN_MASK (OP_DATA_MIDDLE_BTN | OP_DATA_RIGHT_BTN | \ + OP_DATA_LEFT_BTN) + +/* + * Write-only command file register used to issue commands and + * parameters to the bootloader. + * The default value read from it is always 0x00. + */ +#define REG_BL_FILE 0x00 +#define BL_FILE 0x00 + +/* + * Bootloader Status Register + * + * bit 7: Busy + * bit 6 - 5: Reserved + * bit 4: Bootloader running + * bit 3 - 2: Reserved + * bit 1: Watchdog Reset + * bit 0: Checksum valid + */ +#define REG_BL_STATUS 0x01 +#define BL_STATUS_REV_6_5 0x60 +#define BL_STATUS_BUSY 0x80 +#define BL_STATUS_RUNNING 0x10 +#define BL_STATUS_REV_3_2 0x0c +#define BL_STATUS_WATCHDOG 0x02 +#define BL_STATUS_CSUM_VALID 0x01 +#define BL_STATUS_REV_MASK (BL_STATUS_WATCHDOG | BL_STATUS_REV_3_2 | \ + BL_STATUS_REV_6_5) + +/* + * Bootloader Error Register + * + * bit 7: Invalid + * bit 6: Invalid security key + * bit 5: Bootloading + * bit 4: Command checksum + * bit 3: Flash protection error + * bit 2: Flash checksum error + * bit 1 - 0: Reserved + */ +#define REG_BL_ERROR 0x02 +#define BL_ERROR_INVALID 0x80 +#define BL_ERROR_INVALID_KEY 0x40 +#define BL_ERROR_BOOTLOADING 0x20 +#define BL_ERROR_CMD_CSUM 0x10 +#define BL_ERROR_FLASH_PROT 0x08 +#define BL_ERROR_FLASH_CSUM 0x04 +#define BL_ERROR_RESERVED 0x03 +#define BL_ERROR_NO_ERR_IDLE 0x00 +#define BL_ERROR_NO_ERR_ACTIVE (BL_ERROR_BOOTLOADING) + +#define CAPABILITY_BTN_SHIFT 3 +#define CAPABILITY_LEFT_BTN_MASK (0x01 << 3) +#define CAPABILITY_RIGHT_BTN_MASK (0x01 << 4) +#define CAPABILITY_MIDDLE_BTN_MASK (0x01 << 5) +#define CAPABILITY_BTN_MASK (CAPABILITY_LEFT_BTN_MASK | \ + CAPABILITY_RIGHT_BTN_MASK | \ + CAPABILITY_MIDDLE_BTN_MASK) + +#define PWR_MODE_MASK 0xfc +#define PWR_MODE_FULL_ACTIVE (0x3f << 2) +#define PWR_MODE_IDLE (0x03 << 2) /* Default rt suspend scanrate: 30ms */ +#define PWR_MODE_SLEEP (0x05 << 2) /* Default suspend scanrate: 50ms */ +#define PWR_MODE_BTN_ONLY (0x01 << 2) +#define PWR_MODE_OFF (0x00 << 2) + +#define PWR_STATUS_MASK 0x0c +#define PWR_STATUS_ACTIVE (0x03 << 2) +#define PWR_STATUS_IDLE (0x02 << 2) +#define PWR_STATUS_BTN_ONLY (0x01 << 2) +#define PWR_STATUS_OFF (0x00 << 2) + +#define AUTOSUSPEND_DELAY 2000 /* unit : ms */ + +#define BTN_ONLY_MODE_NAME "buttononly" +#define OFF_MODE_NAME "off" + +/* Common macros for PIP interface. */ +#define PIP_HID_DESCRIPTOR_ADDR 0x0001 +#define PIP_REPORT_DESCRIPTOR_ADDR 0x0002 +#define PIP_INPUT_REPORT_ADDR 0x0003 +#define PIP_OUTPUT_REPORT_ADDR 0x0004 +#define PIP_CMD_DATA_ADDR 0x0006 + +#define PIP_RETRIEVE_DATA_STRUCTURE 0x24 +#define PIP_CMD_CALIBRATE 0x28 +#define PIP_BL_CMD_VERIFY_APP_INTEGRITY 0x31 +#define PIP_BL_CMD_GET_BL_INFO 0x38 +#define PIP_BL_CMD_PROGRAM_VERIFY_ROW 0x39 +#define PIP_BL_CMD_LAUNCH_APP 0x3b +#define PIP_BL_CMD_INITIATE_BL 0x48 +#define PIP_INVALID_CMD 0xff + +#define PIP_HID_DESCRIPTOR_SIZE 32 +#define PIP_HID_APP_REPORT_ID 0xf7 +#define PIP_HID_BL_REPORT_ID 0xff + +#define PIP_BL_CMD_REPORT_ID 0x40 +#define PIP_BL_RESP_REPORT_ID 0x30 +#define PIP_APP_CMD_REPORT_ID 0x2f +#define PIP_APP_RESP_REPORT_ID 0x1f + +#define PIP_READ_SYS_INFO_CMD_LENGTH 7 +#define PIP_BL_READ_APP_INFO_CMD_LENGTH 13 +#define PIP_MIN_BL_CMD_LENGTH 13 +#define PIP_MIN_BL_RESP_LENGTH 11 +#define PIP_MIN_APP_CMD_LENGTH 7 +#define PIP_MIN_APP_RESP_LENGTH 5 +#define PIP_UNSUPPORTED_CMD_RESP_LENGTH 6 +#define PIP_READ_SYS_INFO_RESP_LENGTH 71 +#define PIP_BL_APP_INFO_RESP_LENGTH 30 +#define PIP_BL_GET_INFO_RESP_LENGTH 19 + +#define PIP_BL_PLATFORM_VER_SHIFT 4 +#define PIP_BL_PLATFORM_VER_MASK 0x0f + +#define PIP_PRODUCT_FAMILY_MASK 0xf000 +#define PIP_PRODUCT_FAMILY_TRACKPAD 0x1000 + +#define PIP_DEEP_SLEEP_STATE_ON 0x00 +#define PIP_DEEP_SLEEP_STATE_OFF 0x01 +#define PIP_DEEP_SLEEP_STATE_MASK 0x03 +#define PIP_APP_DEEP_SLEEP_REPORT_ID 0xf0 +#define PIP_DEEP_SLEEP_RESP_LENGTH 5 +#define PIP_DEEP_SLEEP_OPCODE 0x08 +#define PIP_DEEP_SLEEP_OPCODE_MASK 0x0f + +#define PIP_RESP_LENGTH_OFFSET 0 +#define PIP_RESP_LENGTH_SIZE 2 +#define PIP_RESP_REPORT_ID_OFFSET 2 +#define PIP_RESP_RSVD_OFFSET 3 +#define PIP_RESP_RSVD_KEY 0x00 +#define PIP_RESP_BL_SOP_OFFSET 4 +#define PIP_SOP_KEY 0x01 /* Start of Packet */ +#define PIP_EOP_KEY 0x17 /* End of Packet */ +#define PIP_RESP_APP_CMD_OFFSET 4 +#define GET_PIP_CMD_CODE(reg) ((reg) & 0x7f) +#define PIP_RESP_STATUS_OFFSET 5 + +#define VALID_CMD_RESP_HEADER(resp, cmd) \ + (((resp)[PIP_RESP_REPORT_ID_OFFSET] == PIP_APP_RESP_REPORT_ID) && \ + ((resp)[PIP_RESP_RSVD_OFFSET] == PIP_RESP_RSVD_KEY) && \ + (GET_PIP_CMD_CODE((resp)[PIP_RESP_APP_CMD_OFFSET]) == (cmd))) + +#define PIP_CMD_COMPLETE_SUCCESS(resp_data) \ + ((resp_data)[PIP_RESP_STATUS_OFFSET] == 0x00) + +/* Variables to record latest gen5 trackpad power states. */ +#define UNINIT_SLEEP_TIME 0xffff +#define UNINIT_PWR_MODE 0xff +#define PIP_DEV_SET_PWR_STATE(cyapa, s) ((cyapa)->dev_pwr_mode = (s)) +#define PIP_DEV_GET_PWR_STATE(cyapa) ((cyapa)->dev_pwr_mode) +#define PIP_DEV_SET_SLEEP_TIME(cyapa, t) ((cyapa)->dev_sleep_time = (t)) +#define PIP_DEV_GET_SLEEP_TIME(cyapa) ((cyapa)->dev_sleep_time) +#define PIP_DEV_UNINIT_SLEEP_TIME(cyapa) \ + (((cyapa)->dev_sleep_time) == UNINIT_SLEEP_TIME) + +/* The touch.id is used as the MT slot id, thus max MT slot is 15 */ +#define CYAPA_MAX_MT_SLOTS 15 + +struct cyapa; + +typedef bool (*cb_sort)(struct cyapa *, u8 *, int); + +enum cyapa_pm_stage { + CYAPA_PM_DEACTIVE, + CYAPA_PM_ACTIVE, + CYAPA_PM_SUSPEND, + CYAPA_PM_RESUME, + CYAPA_PM_RUNTIME_SUSPEND, + CYAPA_PM_RUNTIME_RESUME, +}; + +struct cyapa_dev_ops { + int (*check_fw)(struct cyapa *, const struct firmware *); + int (*bl_enter)(struct cyapa *); + int (*bl_activate)(struct cyapa *); + int (*bl_initiate)(struct cyapa *, const struct firmware *); + int (*update_fw)(struct cyapa *, const struct firmware *); + int (*bl_deactivate)(struct cyapa *); + + ssize_t (*show_baseline)(struct device *, + struct device_attribute *, char *); + ssize_t (*calibrate_store)(struct device *, + struct device_attribute *, const char *, size_t); + + int (*initialize)(struct cyapa *cyapa); + + int (*state_parse)(struct cyapa *cyapa, u8 *reg_status, int len); + int (*operational_check)(struct cyapa *cyapa); + + int (*irq_handler)(struct cyapa *); + bool (*irq_cmd_handler)(struct cyapa *); + int (*sort_empty_output_data)(struct cyapa *, + u8 *, int *, cb_sort); + + int (*set_power_mode)(struct cyapa *, u8, u16, enum cyapa_pm_stage); + + int (*set_proximity)(struct cyapa *, bool); +}; + +struct cyapa_pip_cmd_states { + struct mutex cmd_lock; + struct completion cmd_ready; + atomic_t cmd_issued; + u8 in_progress_cmd; + bool is_irq_mode; + + cb_sort resp_sort_func; + u8 *resp_data; + int *resp_len; + + enum cyapa_pm_stage pm_stage; + struct mutex pm_stage_lock; + + u8 irq_cmd_buf[CYAPA_REG_MAP_SIZE]; + u8 empty_buf[CYAPA_REG_MAP_SIZE]; +}; + +union cyapa_cmd_states { + struct cyapa_pip_cmd_states pip; +}; + +enum cyapa_state { + CYAPA_STATE_NO_DEVICE, + CYAPA_STATE_BL_BUSY, + CYAPA_STATE_BL_IDLE, + CYAPA_STATE_BL_ACTIVE, + CYAPA_STATE_OP, + CYAPA_STATE_GEN5_BL, + CYAPA_STATE_GEN5_APP, + CYAPA_STATE_GEN6_BL, + CYAPA_STATE_GEN6_APP, +}; + +struct gen6_interval_setting { + u16 active_interval; + u16 lp1_interval; + u16 lp2_interval; +}; + +/* The main device structure */ +struct cyapa { + enum cyapa_state state; + u8 status[BL_STATUS_SIZE]; + bool operational; /* true: ready for data reporting; false: not. */ + + struct regulator *vcc; + struct i2c_client *client; + struct input_dev *input; + char phys[32]; /* Device physical location */ + bool irq_wake; /* Irq wake is enabled */ + bool smbus; + + /* power mode settings */ + u8 suspend_power_mode; + u16 suspend_sleep_time; + u8 runtime_suspend_power_mode; + u16 runtime_suspend_sleep_time; + u8 dev_pwr_mode; + u16 dev_sleep_time; + struct gen6_interval_setting gen6_interval_setting; + + /* Read from query data region. */ + char product_id[16]; + u8 platform_ver; /* Platform version. */ + u8 fw_maj_ver; /* Firmware major version. */ + u8 fw_min_ver; /* Firmware minor version. */ + u8 btn_capability; + u8 gen; + int max_abs_x; + int max_abs_y; + int physical_size_x; + int physical_size_y; + + /* Used in ttsp and truetouch based trackpad devices. */ + u8 x_origin; /* X Axis Origin: 0 = left side; 1 = right side. */ + u8 y_origin; /* Y Axis Origin: 0 = top; 1 = bottom. */ + int electrodes_x; /* Number of electrodes on the X Axis*/ + int electrodes_y; /* Number of electrodes on the Y Axis*/ + int electrodes_rx; /* Number of Rx electrodes */ + int aligned_electrodes_rx; /* 4 aligned */ + int max_z; + + /* + * Used to synchronize the access or update the device state. + * And since update firmware and read firmware image process will take + * quite long time, maybe more than 10 seconds, so use mutex_lock + * to sync and wait other interface and detecting are done or ready. + */ + struct mutex state_sync_lock; + + const struct cyapa_dev_ops *ops; + + union cyapa_cmd_states cmd_states; +}; + + +ssize_t cyapa_i2c_reg_read_block(struct cyapa *cyapa, u8 reg, size_t len, + u8 *values); +ssize_t cyapa_smbus_read_block(struct cyapa *cyapa, u8 cmd, size_t len, + u8 *values); + +ssize_t cyapa_read_block(struct cyapa *cyapa, u8 cmd_idx, u8 *values); + +int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout); + +u8 cyapa_sleep_time_to_pwr_cmd(u16 sleep_time); +u16 cyapa_pwr_cmd_to_sleep_time(u8 pwr_mode); + +ssize_t cyapa_i2c_pip_read(struct cyapa *cyapa, u8 *buf, size_t size); +ssize_t cyapa_i2c_pip_write(struct cyapa *cyapa, u8 *buf, size_t size); +int cyapa_empty_pip_output_data(struct cyapa *cyapa, + u8 *buf, int *len, cb_sort func); +int cyapa_i2c_pip_cmd_irq_sync(struct cyapa *cyapa, + u8 *cmd, int cmd_len, + u8 *resp_data, int *resp_len, + unsigned long timeout, + cb_sort func, + bool irq_mode); +int cyapa_pip_state_parse(struct cyapa *cyapa, u8 *reg_data, int len); +bool cyapa_pip_sort_system_info_data(struct cyapa *cyapa, u8 *buf, int len); +bool cyapa_sort_tsg_pip_bl_resp_data(struct cyapa *cyapa, u8 *data, int len); +int cyapa_pip_deep_sleep(struct cyapa *cyapa, u8 state); +bool cyapa_sort_tsg_pip_app_resp_data(struct cyapa *cyapa, u8 *data, int len); +int cyapa_pip_bl_exit(struct cyapa *cyapa); +int cyapa_pip_bl_enter(struct cyapa *cyapa); + + +bool cyapa_is_pip_bl_mode(struct cyapa *cyapa); +bool cyapa_is_pip_app_mode(struct cyapa *cyapa); +int cyapa_pip_cmd_state_initialize(struct cyapa *cyapa); + +int cyapa_pip_resume_scanning(struct cyapa *cyapa); +int cyapa_pip_suspend_scanning(struct cyapa *cyapa); + +int cyapa_pip_check_fw(struct cyapa *cyapa, const struct firmware *fw); +int cyapa_pip_bl_initiate(struct cyapa *cyapa, const struct firmware *fw); +int cyapa_pip_do_fw_update(struct cyapa *cyapa, const struct firmware *fw); +int cyapa_pip_bl_activate(struct cyapa *cyapa); +int cyapa_pip_bl_deactivate(struct cyapa *cyapa); +ssize_t cyapa_pip_do_calibrate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); +int cyapa_pip_set_proximity(struct cyapa *cyapa, bool enable); + +bool cyapa_pip_irq_cmd_handler(struct cyapa *cyapa); +int cyapa_pip_irq_handler(struct cyapa *cyapa); + + +extern u8 pip_read_sys_info[]; +extern u8 pip_bl_read_app_info[]; +extern const char product_id[]; +extern const struct cyapa_dev_ops cyapa_gen3_ops; +extern const struct cyapa_dev_ops cyapa_gen5_ops; +extern const struct cyapa_dev_ops cyapa_gen6_ops; + +#endif diff --git a/drivers/input/mouse/cyapa_gen3.c b/drivers/input/mouse/cyapa_gen3.c new file mode 100644 index 000000000..076dda4a6 --- /dev/null +++ b/drivers/input/mouse/cyapa_gen3.c @@ -0,0 +1,1257 @@ +/* + * Cypress APA trackpad with I2C interface + * + * Author: Dudley Du <dudl@cypress.com> + * Further cleanup and restructuring by: + * Daniel Kurtz <djkurtz@chromium.org> + * Benson Leung <bleung@chromium.org> + * + * Copyright (C) 2011-2015 Cypress Semiconductor, Inc. + * Copyright (C) 2011-2012 Google, Inc. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <asm/unaligned.h> +#include "cyapa.h" + + +#define GEN3_MAX_FINGERS 5 +#define GEN3_FINGER_NUM(x) (((x) >> 4) & 0x07) + +#define BLK_HEAD_BYTES 32 + +/* Macro for register map group offset. */ +#define PRODUCT_ID_SIZE 16 +#define QUERY_DATA_SIZE 27 +#define REG_PROTOCOL_GEN_QUERY_OFFSET 20 + +#define REG_OFFSET_DATA_BASE 0x0000 +#define REG_OFFSET_COMMAND_BASE 0x0028 +#define REG_OFFSET_QUERY_BASE 0x002a + +#define CYAPA_OFFSET_SOFT_RESET REG_OFFSET_COMMAND_BASE +#define OP_RECALIBRATION_MASK 0x80 +#define OP_REPORT_BASELINE_MASK 0x40 +#define REG_OFFSET_MAX_BASELINE 0x0026 +#define REG_OFFSET_MIN_BASELINE 0x0027 + +#define REG_OFFSET_POWER_MODE (REG_OFFSET_COMMAND_BASE + 1) +#define SET_POWER_MODE_DELAY 10000 /* Unit: us */ +#define SET_POWER_MODE_TRIES 5 + +#define GEN3_BL_CMD_CHECKSUM_SEED 0xff +#define GEN3_BL_CMD_INITIATE_BL 0x38 +#define GEN3_BL_CMD_WRITE_BLOCK 0x39 +#define GEN3_BL_CMD_VERIFY_BLOCK 0x3a +#define GEN3_BL_CMD_TERMINATE_BL 0x3b +#define GEN3_BL_CMD_LAUNCH_APP 0xa5 + +/* + * CYAPA trackpad device states. + * Used in register 0x00, bit1-0, DeviceStatus field. + * Other values indicate device is in an abnormal state and must be reset. + */ +#define CYAPA_DEV_NORMAL 0x03 +#define CYAPA_DEV_BUSY 0x01 + +#define CYAPA_FW_BLOCK_SIZE 64 +#define CYAPA_FW_READ_SIZE 16 +#define CYAPA_FW_HDR_START 0x0780 +#define CYAPA_FW_HDR_BLOCK_COUNT 2 +#define CYAPA_FW_HDR_BLOCK_START (CYAPA_FW_HDR_START / CYAPA_FW_BLOCK_SIZE) +#define CYAPA_FW_HDR_SIZE (CYAPA_FW_HDR_BLOCK_COUNT * \ + CYAPA_FW_BLOCK_SIZE) +#define CYAPA_FW_DATA_START 0x0800 +#define CYAPA_FW_DATA_BLOCK_COUNT 480 +#define CYAPA_FW_DATA_BLOCK_START (CYAPA_FW_DATA_START / CYAPA_FW_BLOCK_SIZE) +#define CYAPA_FW_DATA_SIZE (CYAPA_FW_DATA_BLOCK_COUNT * \ + CYAPA_FW_BLOCK_SIZE) +#define CYAPA_FW_SIZE (CYAPA_FW_HDR_SIZE + CYAPA_FW_DATA_SIZE) +#define CYAPA_CMD_LEN 16 + +#define GEN3_BL_IDLE_FW_MAJ_VER_OFFSET 0x0b +#define GEN3_BL_IDLE_FW_MIN_VER_OFFSET (GEN3_BL_IDLE_FW_MAJ_VER_OFFSET + 1) + + +struct cyapa_touch { + /* + * high bits or x/y position value + * bit 7 - 4: high 4 bits of x position value + * bit 3 - 0: high 4 bits of y position value + */ + u8 xy_hi; + u8 x_lo; /* low 8 bits of x position value. */ + u8 y_lo; /* low 8 bits of y position value. */ + u8 pressure; + /* id range is 1 - 15. It is incremented with every new touch. */ + u8 id; +} __packed; + +struct cyapa_reg_data { + /* + * bit 0 - 1: device status + * bit 3 - 2: power mode + * bit 6 - 4: reserved + * bit 7: interrupt valid bit + */ + u8 device_status; + /* + * bit 7 - 4: number of fingers currently touching pad + * bit 3: valid data check bit + * bit 2: middle mechanism button state if exists + * bit 1: right mechanism button state if exists + * bit 0: left mechanism button state if exists + */ + u8 finger_btn; + /* CYAPA reports up to 5 touches per packet. */ + struct cyapa_touch touches[5]; +} __packed; + +struct gen3_write_block_cmd { + u8 checksum_seed; /* Always be 0xff */ + u8 cmd_code; /* command code: 0x39 */ + u8 key[8]; /* 8-byte security key */ + __be16 block_num; + u8 block_data[CYAPA_FW_BLOCK_SIZE]; + u8 block_checksum; /* Calculated using bytes 12 - 75 */ + u8 cmd_checksum; /* Calculated using bytes 0-76 */ +} __packed; + +static const u8 security_key[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 }; +static const u8 bl_activate[] = { 0x00, 0xff, 0x38, 0x00, 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, 0x07 }; +static const u8 bl_deactivate[] = { 0x00, 0xff, 0x3b, 0x00, 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, 0x07 }; +static const u8 bl_exit[] = { 0x00, 0xff, 0xa5, 0x00, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07 }; + + + /* for byte read/write command */ +#define CMD_RESET 0 +#define CMD_POWER_MODE 1 +#define CMD_DEV_STATUS 2 +#define CMD_REPORT_MAX_BASELINE 3 +#define CMD_REPORT_MIN_BASELINE 4 +#define SMBUS_BYTE_CMD(cmd) (((cmd) & 0x3f) << 1) +#define CYAPA_SMBUS_RESET SMBUS_BYTE_CMD(CMD_RESET) +#define CYAPA_SMBUS_POWER_MODE SMBUS_BYTE_CMD(CMD_POWER_MODE) +#define CYAPA_SMBUS_DEV_STATUS SMBUS_BYTE_CMD(CMD_DEV_STATUS) +#define CYAPA_SMBUS_MAX_BASELINE SMBUS_BYTE_CMD(CMD_REPORT_MAX_BASELINE) +#define CYAPA_SMBUS_MIN_BASELINE SMBUS_BYTE_CMD(CMD_REPORT_MIN_BASELINE) + + /* for group registers read/write command */ +#define REG_GROUP_DATA 0 +#define REG_GROUP_CMD 2 +#define REG_GROUP_QUERY 3 +#define SMBUS_GROUP_CMD(grp) (0x80 | (((grp) & 0x07) << 3)) +#define CYAPA_SMBUS_GROUP_DATA SMBUS_GROUP_CMD(REG_GROUP_DATA) +#define CYAPA_SMBUS_GROUP_CMD SMBUS_GROUP_CMD(REG_GROUP_CMD) +#define CYAPA_SMBUS_GROUP_QUERY SMBUS_GROUP_CMD(REG_GROUP_QUERY) + + /* for register block read/write command */ +#define CMD_BL_STATUS 0 +#define CMD_BL_HEAD 1 +#define CMD_BL_CMD 2 +#define CMD_BL_DATA 3 +#define CMD_BL_ALL 4 +#define CMD_BLK_PRODUCT_ID 5 +#define CMD_BLK_HEAD 6 +#define SMBUS_BLOCK_CMD(cmd) (0xc0 | (((cmd) & 0x1f) << 1)) + +/* register block read/write command in bootloader mode */ +#define CYAPA_SMBUS_BL_STATUS SMBUS_BLOCK_CMD(CMD_BL_STATUS) +#define CYAPA_SMBUS_BL_HEAD SMBUS_BLOCK_CMD(CMD_BL_HEAD) +#define CYAPA_SMBUS_BL_CMD SMBUS_BLOCK_CMD(CMD_BL_CMD) +#define CYAPA_SMBUS_BL_DATA SMBUS_BLOCK_CMD(CMD_BL_DATA) +#define CYAPA_SMBUS_BL_ALL SMBUS_BLOCK_CMD(CMD_BL_ALL) + +/* register block read/write command in operational mode */ +#define CYAPA_SMBUS_BLK_PRODUCT_ID SMBUS_BLOCK_CMD(CMD_BLK_PRODUCT_ID) +#define CYAPA_SMBUS_BLK_HEAD SMBUS_BLOCK_CMD(CMD_BLK_HEAD) + +struct cyapa_cmd_len { + u8 cmd; + u8 len; +}; + +/* maps generic CYAPA_CMD_* code to the I2C equivalent */ +static const struct cyapa_cmd_len cyapa_i2c_cmds[] = { + { CYAPA_OFFSET_SOFT_RESET, 1 }, /* CYAPA_CMD_SOFT_RESET */ + { REG_OFFSET_COMMAND_BASE + 1, 1 }, /* CYAPA_CMD_POWER_MODE */ + { REG_OFFSET_DATA_BASE, 1 }, /* CYAPA_CMD_DEV_STATUS */ + { REG_OFFSET_DATA_BASE, sizeof(struct cyapa_reg_data) }, + /* CYAPA_CMD_GROUP_DATA */ + { REG_OFFSET_COMMAND_BASE, 0 }, /* CYAPA_CMD_GROUP_CMD */ + { REG_OFFSET_QUERY_BASE, QUERY_DATA_SIZE }, /* CYAPA_CMD_GROUP_QUERY */ + { BL_HEAD_OFFSET, 3 }, /* CYAPA_CMD_BL_STATUS */ + { BL_HEAD_OFFSET, 16 }, /* CYAPA_CMD_BL_HEAD */ + { BL_HEAD_OFFSET, 16 }, /* CYAPA_CMD_BL_CMD */ + { BL_DATA_OFFSET, 16 }, /* CYAPA_CMD_BL_DATA */ + { BL_HEAD_OFFSET, 32 }, /* CYAPA_CMD_BL_ALL */ + { REG_OFFSET_QUERY_BASE, PRODUCT_ID_SIZE }, + /* CYAPA_CMD_BLK_PRODUCT_ID */ + { REG_OFFSET_DATA_BASE, 32 }, /* CYAPA_CMD_BLK_HEAD */ + { REG_OFFSET_MAX_BASELINE, 1 }, /* CYAPA_CMD_MAX_BASELINE */ + { REG_OFFSET_MIN_BASELINE, 1 }, /* CYAPA_CMD_MIN_BASELINE */ +}; + +static const struct cyapa_cmd_len cyapa_smbus_cmds[] = { + { CYAPA_SMBUS_RESET, 1 }, /* CYAPA_CMD_SOFT_RESET */ + { CYAPA_SMBUS_POWER_MODE, 1 }, /* CYAPA_CMD_POWER_MODE */ + { CYAPA_SMBUS_DEV_STATUS, 1 }, /* CYAPA_CMD_DEV_STATUS */ + { CYAPA_SMBUS_GROUP_DATA, sizeof(struct cyapa_reg_data) }, + /* CYAPA_CMD_GROUP_DATA */ + { CYAPA_SMBUS_GROUP_CMD, 2 }, /* CYAPA_CMD_GROUP_CMD */ + { CYAPA_SMBUS_GROUP_QUERY, QUERY_DATA_SIZE }, + /* CYAPA_CMD_GROUP_QUERY */ + { CYAPA_SMBUS_BL_STATUS, 3 }, /* CYAPA_CMD_BL_STATUS */ + { CYAPA_SMBUS_BL_HEAD, 16 }, /* CYAPA_CMD_BL_HEAD */ + { CYAPA_SMBUS_BL_CMD, 16 }, /* CYAPA_CMD_BL_CMD */ + { CYAPA_SMBUS_BL_DATA, 16 }, /* CYAPA_CMD_BL_DATA */ + { CYAPA_SMBUS_BL_ALL, 32 }, /* CYAPA_CMD_BL_ALL */ + { CYAPA_SMBUS_BLK_PRODUCT_ID, PRODUCT_ID_SIZE }, + /* CYAPA_CMD_BLK_PRODUCT_ID */ + { CYAPA_SMBUS_BLK_HEAD, 16 }, /* CYAPA_CMD_BLK_HEAD */ + { CYAPA_SMBUS_MAX_BASELINE, 1 }, /* CYAPA_CMD_MAX_BASELINE */ + { CYAPA_SMBUS_MIN_BASELINE, 1 }, /* CYAPA_CMD_MIN_BASELINE */ +}; + +static int cyapa_gen3_try_poll_handler(struct cyapa *cyapa); + +/* + * cyapa_smbus_read_block - perform smbus block read command + * @cyapa - private data structure of the driver + * @cmd - the properly encoded smbus command + * @len - expected length of smbus command result + * @values - buffer to store smbus command result + * + * Returns negative errno, else the number of bytes written. + * + * Note: + * In trackpad device, the memory block allocated for I2C register map + * is 256 bytes, so the max read block for I2C bus is 256 bytes. + */ +ssize_t cyapa_smbus_read_block(struct cyapa *cyapa, u8 cmd, size_t len, + u8 *values) +{ + ssize_t ret; + u8 index; + u8 smbus_cmd; + u8 *buf; + struct i2c_client *client = cyapa->client; + + if (!(SMBUS_BYTE_BLOCK_CMD_MASK & cmd)) + return -EINVAL; + + if (SMBUS_GROUP_BLOCK_CMD_MASK & cmd) { + /* read specific block registers command. */ + smbus_cmd = SMBUS_ENCODE_RW(cmd, SMBUS_READ); + ret = i2c_smbus_read_block_data(client, smbus_cmd, values); + goto out; + } + + ret = 0; + for (index = 0; index * I2C_SMBUS_BLOCK_MAX < len; index++) { + smbus_cmd = SMBUS_ENCODE_IDX(cmd, index); + smbus_cmd = SMBUS_ENCODE_RW(smbus_cmd, SMBUS_READ); + buf = values + I2C_SMBUS_BLOCK_MAX * index; + ret = i2c_smbus_read_block_data(client, smbus_cmd, buf); + if (ret < 0) + goto out; + } + +out: + return ret > 0 ? len : ret; +} + +static s32 cyapa_read_byte(struct cyapa *cyapa, u8 cmd_idx) +{ + u8 cmd; + + if (cyapa->smbus) { + cmd = cyapa_smbus_cmds[cmd_idx].cmd; + cmd = SMBUS_ENCODE_RW(cmd, SMBUS_READ); + } else { + cmd = cyapa_i2c_cmds[cmd_idx].cmd; + } + return i2c_smbus_read_byte_data(cyapa->client, cmd); +} + +static s32 cyapa_write_byte(struct cyapa *cyapa, u8 cmd_idx, u8 value) +{ + u8 cmd; + + if (cyapa->smbus) { + cmd = cyapa_smbus_cmds[cmd_idx].cmd; + cmd = SMBUS_ENCODE_RW(cmd, SMBUS_WRITE); + } else { + cmd = cyapa_i2c_cmds[cmd_idx].cmd; + } + return i2c_smbus_write_byte_data(cyapa->client, cmd, value); +} + +ssize_t cyapa_i2c_reg_read_block(struct cyapa *cyapa, u8 reg, size_t len, + u8 *values) +{ + return i2c_smbus_read_i2c_block_data(cyapa->client, reg, len, values); +} + +static ssize_t cyapa_i2c_reg_write_block(struct cyapa *cyapa, u8 reg, + size_t len, const u8 *values) +{ + return i2c_smbus_write_i2c_block_data(cyapa->client, reg, len, values); +} + +ssize_t cyapa_read_block(struct cyapa *cyapa, u8 cmd_idx, u8 *values) +{ + u8 cmd; + size_t len; + + if (cyapa->smbus) { + cmd = cyapa_smbus_cmds[cmd_idx].cmd; + len = cyapa_smbus_cmds[cmd_idx].len; + return cyapa_smbus_read_block(cyapa, cmd, len, values); + } + cmd = cyapa_i2c_cmds[cmd_idx].cmd; + len = cyapa_i2c_cmds[cmd_idx].len; + return cyapa_i2c_reg_read_block(cyapa, cmd, len, values); +} + +/* + * Determine the Gen3 trackpad device's current operating state. + * + */ +static int cyapa_gen3_state_parse(struct cyapa *cyapa, u8 *reg_data, int len) +{ + cyapa->state = CYAPA_STATE_NO_DEVICE; + + /* Parse based on Gen3 characteristic registers and bits */ + if (reg_data[REG_BL_FILE] == BL_FILE && + reg_data[REG_BL_ERROR] == BL_ERROR_NO_ERR_IDLE && + (reg_data[REG_BL_STATUS] == + (BL_STATUS_RUNNING | BL_STATUS_CSUM_VALID) || + reg_data[REG_BL_STATUS] == BL_STATUS_RUNNING)) { + /* + * Normal state after power on or reset, + * REG_BL_STATUS == 0x11, firmware image checksum is valid. + * REG_BL_STATUS == 0x10, firmware image checksum is invalid. + */ + cyapa->gen = CYAPA_GEN3; + cyapa->state = CYAPA_STATE_BL_IDLE; + } else if (reg_data[REG_BL_FILE] == BL_FILE && + (reg_data[REG_BL_STATUS] & BL_STATUS_RUNNING) == + BL_STATUS_RUNNING) { + cyapa->gen = CYAPA_GEN3; + if (reg_data[REG_BL_STATUS] & BL_STATUS_BUSY) { + cyapa->state = CYAPA_STATE_BL_BUSY; + } else { + if ((reg_data[REG_BL_ERROR] & BL_ERROR_BOOTLOADING) == + BL_ERROR_BOOTLOADING) + cyapa->state = CYAPA_STATE_BL_ACTIVE; + else + cyapa->state = CYAPA_STATE_BL_IDLE; + } + } else if ((reg_data[REG_OP_STATUS] & OP_STATUS_SRC) && + (reg_data[REG_OP_DATA1] & OP_DATA_VALID)) { + /* + * Normal state when running in operational mode, + * may also not in full power state or + * busying in command process. + */ + if (GEN3_FINGER_NUM(reg_data[REG_OP_DATA1]) <= + GEN3_MAX_FINGERS) { + /* Finger number data is valid. */ + cyapa->gen = CYAPA_GEN3; + cyapa->state = CYAPA_STATE_OP; + } + } else if (reg_data[REG_OP_STATUS] == 0x0C && + reg_data[REG_OP_DATA1] == 0x08) { + /* Op state when first two registers overwritten with 0x00 */ + cyapa->gen = CYAPA_GEN3; + cyapa->state = CYAPA_STATE_OP; + } else if (reg_data[REG_BL_STATUS] & + (BL_STATUS_RUNNING | BL_STATUS_BUSY)) { + cyapa->gen = CYAPA_GEN3; + cyapa->state = CYAPA_STATE_BL_BUSY; + } + + if (cyapa->gen == CYAPA_GEN3 && (cyapa->state == CYAPA_STATE_OP || + cyapa->state == CYAPA_STATE_BL_IDLE || + cyapa->state == CYAPA_STATE_BL_ACTIVE || + cyapa->state == CYAPA_STATE_BL_BUSY)) + return 0; + + return -EAGAIN; +} + +/* + * Enter bootloader by soft resetting the device. + * + * If device is already in the bootloader, the function just returns. + * Otherwise, reset the device; after reset, device enters bootloader idle + * state immediately. + * + * Returns: + * 0 on success + * -EAGAIN device was reset, but is not now in bootloader idle state + * < 0 if the device never responds within the timeout + */ +static int cyapa_gen3_bl_enter(struct cyapa *cyapa) +{ + int error; + int waiting_time; + + error = cyapa_poll_state(cyapa, 500); + if (error) + return error; + if (cyapa->state == CYAPA_STATE_BL_IDLE) { + /* Already in BL_IDLE. Skipping reset. */ + return 0; + } + + if (cyapa->state != CYAPA_STATE_OP) + return -EAGAIN; + + cyapa->operational = false; + cyapa->state = CYAPA_STATE_NO_DEVICE; + error = cyapa_write_byte(cyapa, CYAPA_CMD_SOFT_RESET, 0x01); + if (error) + return -EIO; + + usleep_range(25000, 50000); + waiting_time = 2000; /* For some shipset, max waiting time is 1~2s. */ + do { + error = cyapa_poll_state(cyapa, 500); + if (error) { + if (error == -ETIMEDOUT) { + waiting_time -= 500; + continue; + } + return error; + } + + if ((cyapa->state == CYAPA_STATE_BL_IDLE) && + !(cyapa->status[REG_BL_STATUS] & BL_STATUS_WATCHDOG)) + break; + + msleep(100); + waiting_time -= 100; + } while (waiting_time > 0); + + if ((cyapa->state != CYAPA_STATE_BL_IDLE) || + (cyapa->status[REG_BL_STATUS] & BL_STATUS_WATCHDOG)) + return -EAGAIN; + + return 0; +} + +static int cyapa_gen3_bl_activate(struct cyapa *cyapa) +{ + int error; + + error = cyapa_i2c_reg_write_block(cyapa, 0, sizeof(bl_activate), + bl_activate); + if (error) + return error; + + /* Wait for bootloader to activate; takes between 2 and 12 seconds */ + msleep(2000); + error = cyapa_poll_state(cyapa, 11000); + if (error) + return error; + if (cyapa->state != CYAPA_STATE_BL_ACTIVE) + return -EAGAIN; + + return 0; +} + +static int cyapa_gen3_bl_deactivate(struct cyapa *cyapa) +{ + int error; + + error = cyapa_i2c_reg_write_block(cyapa, 0, sizeof(bl_deactivate), + bl_deactivate); + if (error) + return error; + + /* Wait for bootloader to switch to idle state; should take < 100ms */ + msleep(100); + error = cyapa_poll_state(cyapa, 500); + if (error) + return error; + if (cyapa->state != CYAPA_STATE_BL_IDLE) + return -EAGAIN; + return 0; +} + +/* + * Exit bootloader + * + * Send bl_exit command, then wait 50 - 100 ms to let device transition to + * operational mode. If this is the first time the device's firmware is + * running, it can take up to 2 seconds to calibrate its sensors. So, poll + * the device's new state for up to 2 seconds. + * + * Returns: + * -EIO failure while reading from device + * -EAGAIN device is stuck in bootloader, b/c it has invalid firmware + * 0 device is supported and in operational mode + */ +static int cyapa_gen3_bl_exit(struct cyapa *cyapa) +{ + int error; + + error = cyapa_i2c_reg_write_block(cyapa, 0, sizeof(bl_exit), bl_exit); + if (error) + return error; + + /* + * Wait for bootloader to exit, and operation mode to start. + * Normally, this takes at least 50 ms. + */ + msleep(50); + /* + * In addition, when a device boots for the first time after being + * updated to new firmware, it must first calibrate its sensors, which + * can take up to an additional 2 seconds. If the device power is + * running low, this may take even longer. + */ + error = cyapa_poll_state(cyapa, 4000); + if (error < 0) + return error; + if (cyapa->state != CYAPA_STATE_OP) + return -EAGAIN; + + return 0; +} + +static u16 cyapa_gen3_csum(const u8 *buf, size_t count) +{ + int i; + u16 csum = 0; + + for (i = 0; i < count; i++) + csum += buf[i]; + + return csum; +} + +/* + * Verify the integrity of a CYAPA firmware image file. + * + * The firmware image file is 30848 bytes, composed of 482 64-byte blocks. + * + * The first 2 blocks are the firmware header. + * The next 480 blocks are the firmware image. + * + * The first two bytes of the header hold the header checksum, computed by + * summing the other 126 bytes of the header. + * The last two bytes of the header hold the firmware image checksum, computed + * by summing the 30720 bytes of the image modulo 0xffff. + * + * Both checksums are stored little-endian. + */ +static int cyapa_gen3_check_fw(struct cyapa *cyapa, const struct firmware *fw) +{ + struct device *dev = &cyapa->client->dev; + u16 csum; + u16 csum_expected; + + /* Firmware must match exact 30848 bytes = 482 64-byte blocks. */ + if (fw->size != CYAPA_FW_SIZE) { + dev_err(dev, "invalid firmware size = %zu, expected %u.\n", + fw->size, CYAPA_FW_SIZE); + return -EINVAL; + } + + /* Verify header block */ + csum_expected = (fw->data[0] << 8) | fw->data[1]; + csum = cyapa_gen3_csum(&fw->data[2], CYAPA_FW_HDR_SIZE - 2); + if (csum != csum_expected) { + dev_err(dev, "%s %04x, expected: %04x\n", + "invalid firmware header checksum = ", + csum, csum_expected); + return -EINVAL; + } + + /* Verify firmware image */ + csum_expected = (fw->data[CYAPA_FW_HDR_SIZE - 2] << 8) | + fw->data[CYAPA_FW_HDR_SIZE - 1]; + csum = cyapa_gen3_csum(&fw->data[CYAPA_FW_HDR_SIZE], + CYAPA_FW_DATA_SIZE); + if (csum != csum_expected) { + dev_err(dev, "%s %04x, expected: %04x\n", + "invalid firmware header checksum = ", + csum, csum_expected); + return -EINVAL; + } + return 0; +} + +/* + * Write a |len| byte long buffer |buf| to the device, by chopping it up into a + * sequence of smaller |CYAPA_CMD_LEN|-length write commands. + * + * The data bytes for a write command are prepended with the 1-byte offset + * of the data relative to the start of |buf|. + */ +static int cyapa_gen3_write_buffer(struct cyapa *cyapa, + const u8 *buf, size_t len) +{ + int error; + size_t i; + unsigned char cmd[CYAPA_CMD_LEN + 1]; + size_t cmd_len; + + for (i = 0; i < len; i += CYAPA_CMD_LEN) { + const u8 *payload = &buf[i]; + + cmd_len = (len - i >= CYAPA_CMD_LEN) ? CYAPA_CMD_LEN : len - i; + cmd[0] = i; + memcpy(&cmd[1], payload, cmd_len); + + error = cyapa_i2c_reg_write_block(cyapa, 0, cmd_len + 1, cmd); + if (error) + return error; + } + return 0; +} + +/* + * A firmware block write command writes 64 bytes of data to a single flash + * page in the device. The 78-byte block write command has the format: + * <0xff> <CMD> <Key> <Start> <Data> <Data-Checksum> <CMD Checksum> + * + * <0xff> - every command starts with 0xff + * <CMD> - the write command value is 0x39 + * <Key> - write commands include an 8-byte key: { 00 01 02 03 04 05 06 07 } + * <Block> - Memory Block number (address / 64) (16-bit, big-endian) + * <Data> - 64 bytes of firmware image data + * <Data Checksum> - sum of 64 <Data> bytes, modulo 0xff + * <CMD Checksum> - sum of 77 bytes, from 0xff to <Data Checksum> + * + * Each write command is split into 5 i2c write transactions of up to 16 bytes. + * Each transaction starts with an i2c register offset: (00, 10, 20, 30, 40). + */ +static int cyapa_gen3_write_fw_block(struct cyapa *cyapa, + u16 block, const u8 *data) +{ + int ret; + struct gen3_write_block_cmd write_block_cmd; + u8 status[BL_STATUS_SIZE]; + int tries; + u8 bl_status, bl_error; + + /* Set write command and security key bytes. */ + write_block_cmd.checksum_seed = GEN3_BL_CMD_CHECKSUM_SEED; + write_block_cmd.cmd_code = GEN3_BL_CMD_WRITE_BLOCK; + memcpy(write_block_cmd.key, security_key, sizeof(security_key)); + put_unaligned_be16(block, &write_block_cmd.block_num); + memcpy(write_block_cmd.block_data, data, CYAPA_FW_BLOCK_SIZE); + write_block_cmd.block_checksum = cyapa_gen3_csum( + write_block_cmd.block_data, CYAPA_FW_BLOCK_SIZE); + write_block_cmd.cmd_checksum = cyapa_gen3_csum((u8 *)&write_block_cmd, + sizeof(write_block_cmd) - 1); + + ret = cyapa_gen3_write_buffer(cyapa, (u8 *)&write_block_cmd, + sizeof(write_block_cmd)); + if (ret) + return ret; + + /* Wait for write to finish */ + tries = 11; /* Programming for one block can take about 100ms. */ + do { + usleep_range(10000, 20000); + + /* Check block write command result status. */ + ret = cyapa_i2c_reg_read_block(cyapa, BL_HEAD_OFFSET, + BL_STATUS_SIZE, status); + if (ret != BL_STATUS_SIZE) + return (ret < 0) ? ret : -EIO; + } while ((status[REG_BL_STATUS] & BL_STATUS_BUSY) && --tries); + + /* Ignore WATCHDOG bit and reserved bits. */ + bl_status = status[REG_BL_STATUS] & ~BL_STATUS_REV_MASK; + bl_error = status[REG_BL_ERROR] & ~BL_ERROR_RESERVED; + + if (bl_status & BL_STATUS_BUSY) + ret = -ETIMEDOUT; + else if (bl_status != BL_STATUS_RUNNING || + bl_error != BL_ERROR_BOOTLOADING) + ret = -EIO; + else + ret = 0; + + return ret; +} + +static int cyapa_gen3_write_blocks(struct cyapa *cyapa, + size_t start_block, size_t block_count, + const u8 *image_data) +{ + int error; + int i; + + for (i = 0; i < block_count; i++) { + size_t block = start_block + i; + size_t addr = i * CYAPA_FW_BLOCK_SIZE; + const u8 *data = &image_data[addr]; + + error = cyapa_gen3_write_fw_block(cyapa, block, data); + if (error) + return error; + } + return 0; +} + +static int cyapa_gen3_do_fw_update(struct cyapa *cyapa, + const struct firmware *fw) +{ + struct device *dev = &cyapa->client->dev; + int error; + + /* First write data, starting at byte 128 of fw->data */ + error = cyapa_gen3_write_blocks(cyapa, + CYAPA_FW_DATA_BLOCK_START, CYAPA_FW_DATA_BLOCK_COUNT, + &fw->data[CYAPA_FW_HDR_BLOCK_COUNT * CYAPA_FW_BLOCK_SIZE]); + if (error) { + dev_err(dev, "FW update aborted, write image: %d\n", error); + return error; + } + + /* Then write checksum */ + error = cyapa_gen3_write_blocks(cyapa, + CYAPA_FW_HDR_BLOCK_START, CYAPA_FW_HDR_BLOCK_COUNT, + &fw->data[0]); + if (error) { + dev_err(dev, "FW update aborted, write checksum: %d\n", error); + return error; + } + + return 0; +} + +static ssize_t cyapa_gen3_do_calibrate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cyapa *cyapa = dev_get_drvdata(dev); + unsigned long timeout; + int ret; + + ret = cyapa_read_byte(cyapa, CYAPA_CMD_DEV_STATUS); + if (ret < 0) { + dev_err(dev, "Error reading dev status: %d\n", ret); + goto out; + } + if ((ret & CYAPA_DEV_NORMAL) != CYAPA_DEV_NORMAL) { + dev_warn(dev, "Trackpad device is busy, device state: 0x%02x\n", + ret); + ret = -EAGAIN; + goto out; + } + + ret = cyapa_write_byte(cyapa, CYAPA_CMD_SOFT_RESET, + OP_RECALIBRATION_MASK); + if (ret < 0) { + dev_err(dev, "Failed to send calibrate command: %d\n", + ret); + goto out; + } + + /* max recalibration timeout 2s. */ + timeout = jiffies + 2 * HZ; + do { + /* + * For this recalibration, the max time will not exceed 2s. + * The average time is approximately 500 - 700 ms, and we + * will check the status every 100 - 200ms. + */ + msleep(100); + ret = cyapa_read_byte(cyapa, CYAPA_CMD_DEV_STATUS); + if (ret < 0) { + dev_err(dev, "Error reading dev status: %d\n", ret); + goto out; + } + if ((ret & CYAPA_DEV_NORMAL) == CYAPA_DEV_NORMAL) { + dev_dbg(dev, "Calibration successful.\n"); + goto out; + } + } while (time_is_after_jiffies(timeout)); + + dev_err(dev, "Failed to calibrate. Timeout.\n"); + ret = -ETIMEDOUT; + +out: + return ret < 0 ? ret : count; +} + +static ssize_t cyapa_gen3_show_baseline(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyapa *cyapa = dev_get_drvdata(dev); + int max_baseline, min_baseline; + int tries; + int ret; + + ret = cyapa_read_byte(cyapa, CYAPA_CMD_DEV_STATUS); + if (ret < 0) { + dev_err(dev, "Error reading dev status. err = %d\n", ret); + goto out; + } + if ((ret & CYAPA_DEV_NORMAL) != CYAPA_DEV_NORMAL) { + dev_warn(dev, "Trackpad device is busy. device state = 0x%x\n", + ret); + ret = -EAGAIN; + goto out; + } + + ret = cyapa_write_byte(cyapa, CYAPA_CMD_SOFT_RESET, + OP_REPORT_BASELINE_MASK); + if (ret < 0) { + dev_err(dev, "Failed to send report baseline command. %d\n", + ret); + goto out; + } + + tries = 3; /* Try for 30 to 60 ms */ + do { + usleep_range(10000, 20000); + + ret = cyapa_read_byte(cyapa, CYAPA_CMD_DEV_STATUS); + if (ret < 0) { + dev_err(dev, "Error reading dev status. err = %d\n", + ret); + goto out; + } + if ((ret & CYAPA_DEV_NORMAL) == CYAPA_DEV_NORMAL) + break; + } while (--tries); + + if (tries == 0) { + dev_err(dev, "Device timed out going to Normal state.\n"); + ret = -ETIMEDOUT; + goto out; + } + + ret = cyapa_read_byte(cyapa, CYAPA_CMD_MAX_BASELINE); + if (ret < 0) { + dev_err(dev, "Failed to read max baseline. err = %d\n", ret); + goto out; + } + max_baseline = ret; + + ret = cyapa_read_byte(cyapa, CYAPA_CMD_MIN_BASELINE); + if (ret < 0) { + dev_err(dev, "Failed to read min baseline. err = %d\n", ret); + goto out; + } + min_baseline = ret; + + dev_dbg(dev, "Baseline report successful. Max: %d Min: %d\n", + max_baseline, min_baseline); + ret = scnprintf(buf, PAGE_SIZE, "%d %d\n", max_baseline, min_baseline); + +out: + return ret; +} + +/* + * cyapa_get_wait_time_for_pwr_cmd + * + * Compute the amount of time we need to wait after updating the touchpad + * power mode. The touchpad needs to consume the incoming power mode set + * command at the current clock rate. + */ + +static u16 cyapa_get_wait_time_for_pwr_cmd(u8 pwr_mode) +{ + switch (pwr_mode) { + case PWR_MODE_FULL_ACTIVE: return 20; + case PWR_MODE_BTN_ONLY: return 20; + case PWR_MODE_OFF: return 20; + default: return cyapa_pwr_cmd_to_sleep_time(pwr_mode) + 50; + } +} + +/* + * Set device power mode + * + * Write to the field to configure power state. Power states include : + * Full : Max scans and report rate. + * Idle : Report rate set by user specified time. + * ButtonOnly : No scans for fingers. When the button is triggered, + * a slave interrupt is asserted to notify host to wake up. + * Off : Only awake for i2c commands from host. No function for button + * or touch sensors. + * + * The power_mode command should conform to the following : + * Full : 0x3f + * Idle : Configurable from 20 to 1000ms. See note below for + * cyapa_sleep_time_to_pwr_cmd and cyapa_pwr_cmd_to_sleep_time + * ButtonOnly : 0x01 + * Off : 0x00 + * + * Device power mode can only be set when device is in operational mode. + */ +static int cyapa_gen3_set_power_mode(struct cyapa *cyapa, u8 power_mode, + u16 always_unused, enum cyapa_pm_stage pm_stage) +{ + struct input_dev *input = cyapa->input; + u8 power; + int tries; + int sleep_time; + int interval; + int ret; + + if (cyapa->state != CYAPA_STATE_OP) + return 0; + + tries = SET_POWER_MODE_TRIES; + while (tries--) { + ret = cyapa_read_byte(cyapa, CYAPA_CMD_POWER_MODE); + if (ret >= 0) + break; + usleep_range(SET_POWER_MODE_DELAY, 2 * SET_POWER_MODE_DELAY); + } + if (ret < 0) + return ret; + + /* + * Return early if the power mode to set is the same as the current + * one. + */ + if ((ret & PWR_MODE_MASK) == power_mode) + return 0; + + sleep_time = (int)cyapa_get_wait_time_for_pwr_cmd(ret & PWR_MODE_MASK); + power = ret; + power &= ~PWR_MODE_MASK; + power |= power_mode & PWR_MODE_MASK; + tries = SET_POWER_MODE_TRIES; + while (tries--) { + ret = cyapa_write_byte(cyapa, CYAPA_CMD_POWER_MODE, power); + if (!ret) + break; + usleep_range(SET_POWER_MODE_DELAY, 2 * SET_POWER_MODE_DELAY); + } + + /* + * Wait for the newly set power command to go in at the previous + * clock speed (scanrate) used by the touchpad firmware. Not + * doing so before issuing the next command may result in errors + * depending on the command's content. + */ + if (cyapa->operational && input && input->users && + (pm_stage == CYAPA_PM_RUNTIME_SUSPEND || + pm_stage == CYAPA_PM_RUNTIME_RESUME)) { + /* Try to polling in 120Hz, read may fail, just ignore it. */ + interval = 1000 / 120; + while (sleep_time > 0) { + if (sleep_time > interval) + msleep(interval); + else + msleep(sleep_time); + sleep_time -= interval; + cyapa_gen3_try_poll_handler(cyapa); + } + } else { + msleep(sleep_time); + } + + return ret; +} + +static int cyapa_gen3_set_proximity(struct cyapa *cyapa, bool enable) +{ + return -EOPNOTSUPP; +} + +static int cyapa_gen3_get_query_data(struct cyapa *cyapa) +{ + u8 query_data[QUERY_DATA_SIZE]; + int ret; + + if (cyapa->state != CYAPA_STATE_OP) + return -EBUSY; + + ret = cyapa_read_block(cyapa, CYAPA_CMD_GROUP_QUERY, query_data); + if (ret != QUERY_DATA_SIZE) + return (ret < 0) ? ret : -EIO; + + memcpy(&cyapa->product_id[0], &query_data[0], 5); + cyapa->product_id[5] = '-'; + memcpy(&cyapa->product_id[6], &query_data[5], 6); + cyapa->product_id[12] = '-'; + memcpy(&cyapa->product_id[13], &query_data[11], 2); + cyapa->product_id[15] = '\0'; + + cyapa->fw_maj_ver = query_data[15]; + cyapa->fw_min_ver = query_data[16]; + + cyapa->btn_capability = query_data[19] & CAPABILITY_BTN_MASK; + + cyapa->gen = query_data[20] & 0x0f; + + cyapa->max_abs_x = ((query_data[21] & 0xf0) << 4) | query_data[22]; + cyapa->max_abs_y = ((query_data[21] & 0x0f) << 8) | query_data[23]; + + cyapa->physical_size_x = + ((query_data[24] & 0xf0) << 4) | query_data[25]; + cyapa->physical_size_y = + ((query_data[24] & 0x0f) << 8) | query_data[26]; + + cyapa->max_z = 255; + + return 0; +} + +static int cyapa_gen3_bl_query_data(struct cyapa *cyapa) +{ + u8 bl_data[CYAPA_CMD_LEN]; + int ret; + + ret = cyapa_i2c_reg_read_block(cyapa, 0, CYAPA_CMD_LEN, bl_data); + if (ret != CYAPA_CMD_LEN) + return (ret < 0) ? ret : -EIO; + + /* + * This value will be updated again when entered application mode. + * If TP failed to enter application mode, this fw version values + * can be used as a reference. + * This firmware version valid when fw image checksum is valid. + */ + if (bl_data[REG_BL_STATUS] == + (BL_STATUS_RUNNING | BL_STATUS_CSUM_VALID)) { + cyapa->fw_maj_ver = bl_data[GEN3_BL_IDLE_FW_MAJ_VER_OFFSET]; + cyapa->fw_min_ver = bl_data[GEN3_BL_IDLE_FW_MIN_VER_OFFSET]; + } + + return 0; +} + +/* + * Check if device is operational. + * + * An operational device is responding, has exited bootloader, and has + * firmware supported by this driver. + * + * Returns: + * -EBUSY no device or in bootloader + * -EIO failure while reading from device + * -EAGAIN device is still in bootloader + * if ->state = CYAPA_STATE_BL_IDLE, device has invalid firmware + * -EINVAL device is in operational mode, but not supported by this driver + * 0 device is supported + */ +static int cyapa_gen3_do_operational_check(struct cyapa *cyapa) +{ + struct device *dev = &cyapa->client->dev; + int error; + + switch (cyapa->state) { + case CYAPA_STATE_BL_ACTIVE: + error = cyapa_gen3_bl_deactivate(cyapa); + if (error) { + dev_err(dev, "failed to bl_deactivate: %d\n", error); + return error; + } + + /* Fallthrough state */ + case CYAPA_STATE_BL_IDLE: + /* Try to get firmware version in bootloader mode. */ + cyapa_gen3_bl_query_data(cyapa); + + error = cyapa_gen3_bl_exit(cyapa); + if (error) { + dev_err(dev, "failed to bl_exit: %d\n", error); + return error; + } + + /* Fallthrough state */ + case CYAPA_STATE_OP: + /* + * Reading query data before going back to the full mode + * may cause problems, so we set the power mode first here. + */ + error = cyapa_gen3_set_power_mode(cyapa, + PWR_MODE_FULL_ACTIVE, 0, CYAPA_PM_ACTIVE); + if (error) + dev_err(dev, "%s: set full power mode failed: %d\n", + __func__, error); + error = cyapa_gen3_get_query_data(cyapa); + if (error < 0) + return error; + + /* Only support firmware protocol gen3 */ + if (cyapa->gen != CYAPA_GEN3) { + dev_err(dev, "unsupported protocol version (%d)", + cyapa->gen); + return -EINVAL; + } + + /* Only support product ID starting with CYTRA */ + if (memcmp(cyapa->product_id, product_id, + strlen(product_id)) != 0) { + dev_err(dev, "unsupported product ID (%s)\n", + cyapa->product_id); + return -EINVAL; + } + + return 0; + + default: + return -EIO; + } + return 0; +} + +/* + * Return false, do not continue process + * Return true, continue process. + */ +static bool cyapa_gen3_irq_cmd_handler(struct cyapa *cyapa) +{ + /* Not gen3 irq command response, skip for continue. */ + if (cyapa->gen != CYAPA_GEN3) + return true; + + if (cyapa->operational) + return true; + + /* + * Driver in detecting or other interface function processing, + * so, stop cyapa_gen3_irq_handler to continue process to + * avoid unwanted to error detecting and processing. + * + * And also, avoid the periodically asserted interrupts to be processed + * as touch inputs when gen3 failed to launch into application mode, + * which will cause gen3 stays in bootloader mode. + */ + return false; +} + +static int cyapa_gen3_event_process(struct cyapa *cyapa, + struct cyapa_reg_data *data) +{ + struct input_dev *input = cyapa->input; + int num_fingers; + int i; + + num_fingers = (data->finger_btn >> 4) & 0x0f; + for (i = 0; i < num_fingers; i++) { + const struct cyapa_touch *touch = &data->touches[i]; + /* Note: touch->id range is 1 to 15; slots are 0 to 14. */ + int slot = touch->id - 1; + + input_mt_slot(input, slot); + input_mt_report_slot_state(input, MT_TOOL_FINGER, true); + input_report_abs(input, ABS_MT_POSITION_X, + ((touch->xy_hi & 0xf0) << 4) | touch->x_lo); + input_report_abs(input, ABS_MT_POSITION_Y, + ((touch->xy_hi & 0x0f) << 8) | touch->y_lo); + input_report_abs(input, ABS_MT_PRESSURE, touch->pressure); + } + + input_mt_sync_frame(input); + + if (cyapa->btn_capability & CAPABILITY_LEFT_BTN_MASK) + input_report_key(input, BTN_LEFT, + !!(data->finger_btn & OP_DATA_LEFT_BTN)); + if (cyapa->btn_capability & CAPABILITY_MIDDLE_BTN_MASK) + input_report_key(input, BTN_MIDDLE, + !!(data->finger_btn & OP_DATA_MIDDLE_BTN)); + if (cyapa->btn_capability & CAPABILITY_RIGHT_BTN_MASK) + input_report_key(input, BTN_RIGHT, + !!(data->finger_btn & OP_DATA_RIGHT_BTN)); + input_sync(input); + + return 0; +} + +static int cyapa_gen3_irq_handler(struct cyapa *cyapa) +{ + struct device *dev = &cyapa->client->dev; + struct cyapa_reg_data data; + int ret; + + ret = cyapa_read_block(cyapa, CYAPA_CMD_GROUP_DATA, (u8 *)&data); + if (ret != sizeof(data)) { + dev_err(dev, "failed to read report data, (%d)\n", ret); + return -EINVAL; + } + + if ((data.device_status & OP_STATUS_SRC) != OP_STATUS_SRC || + (data.device_status & OP_STATUS_DEV) != CYAPA_DEV_NORMAL || + (data.finger_btn & OP_DATA_VALID) != OP_DATA_VALID) { + dev_err(dev, "invalid device state bytes: %02x %02x\n", + data.device_status, data.finger_btn); + return -EINVAL; + } + + return cyapa_gen3_event_process(cyapa, &data); +} + +/* + * This function will be called in the cyapa_gen3_set_power_mode function, + * and it's known that it may failed in some situation after the set power + * mode command was sent. So this function is aimed to avoid the knwon + * and unwanted output I2C and data parse error messages. + */ +static int cyapa_gen3_try_poll_handler(struct cyapa *cyapa) +{ + struct cyapa_reg_data data; + int ret; + + ret = cyapa_read_block(cyapa, CYAPA_CMD_GROUP_DATA, (u8 *)&data); + if (ret != sizeof(data)) + return -EINVAL; + + if ((data.device_status & OP_STATUS_SRC) != OP_STATUS_SRC || + (data.device_status & OP_STATUS_DEV) != CYAPA_DEV_NORMAL || + (data.finger_btn & OP_DATA_VALID) != OP_DATA_VALID) + return -EINVAL; + + return cyapa_gen3_event_process(cyapa, &data); + +} + +static int cyapa_gen3_initialize(struct cyapa *cyapa) { return 0; } +static int cyapa_gen3_bl_initiate(struct cyapa *cyapa, + const struct firmware *fw) { return 0; } +static int cyapa_gen3_empty_output_data(struct cyapa *cyapa, + u8 *buf, int *len, cb_sort func) { return 0; } + +const struct cyapa_dev_ops cyapa_gen3_ops = { + .check_fw = cyapa_gen3_check_fw, + .bl_enter = cyapa_gen3_bl_enter, + .bl_activate = cyapa_gen3_bl_activate, + .update_fw = cyapa_gen3_do_fw_update, + .bl_deactivate = cyapa_gen3_bl_deactivate, + .bl_initiate = cyapa_gen3_bl_initiate, + + .show_baseline = cyapa_gen3_show_baseline, + .calibrate_store = cyapa_gen3_do_calibrate, + + .initialize = cyapa_gen3_initialize, + + .state_parse = cyapa_gen3_state_parse, + .operational_check = cyapa_gen3_do_operational_check, + + .irq_handler = cyapa_gen3_irq_handler, + .irq_cmd_handler = cyapa_gen3_irq_cmd_handler, + .sort_empty_output_data = cyapa_gen3_empty_output_data, + .set_power_mode = cyapa_gen3_set_power_mode, + + .set_proximity = cyapa_gen3_set_proximity, +}; diff --git a/drivers/input/mouse/cyapa_gen5.c b/drivers/input/mouse/cyapa_gen5.c new file mode 100644 index 000000000..14239fbd7 --- /dev/null +++ b/drivers/input/mouse/cyapa_gen5.c @@ -0,0 +1,2909 @@ +/* + * Cypress APA trackpad with I2C interface + * + * Author: Dudley Du <dudl@cypress.com> + * + * Copyright (C) 2014-2015 Cypress Semiconductor, Inc. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/mutex.h> +#include <linux/completion.h> +#include <linux/slab.h> +#include <asm/unaligned.h> +#include <linux/crc-itu-t.h> +#include <linux/pm_runtime.h> +#include "cyapa.h" + + +/* Macro of TSG firmware image */ +#define CYAPA_TSG_FLASH_MAP_BLOCK_SIZE 0x80 +#define CYAPA_TSG_IMG_FW_HDR_SIZE 13 +#define CYAPA_TSG_FW_ROW_SIZE (CYAPA_TSG_FLASH_MAP_BLOCK_SIZE) +#define CYAPA_TSG_IMG_START_ROW_NUM 0x002e +#define CYAPA_TSG_IMG_END_ROW_NUM 0x01fe +#define CYAPA_TSG_IMG_APP_INTEGRITY_ROW_NUM 0x01ff +#define CYAPA_TSG_IMG_MAX_RECORDS (CYAPA_TSG_IMG_END_ROW_NUM - \ + CYAPA_TSG_IMG_START_ROW_NUM + 1 + 1) +#define CYAPA_TSG_IMG_READ_SIZE (CYAPA_TSG_FLASH_MAP_BLOCK_SIZE / 2) +#define CYAPA_TSG_START_OF_APPLICATION 0x1700 +#define CYAPA_TSG_APP_INTEGRITY_SIZE 60 +#define CYAPA_TSG_FLASH_MAP_METADATA_SIZE 60 +#define CYAPA_TSG_BL_KEY_SIZE 8 + +#define CYAPA_TSG_MAX_CMD_SIZE 256 + +/* Macro of PIP interface */ +#define PIP_BL_INITIATE_RESP_LEN 11 +#define PIP_BL_FAIL_EXIT_RESP_LEN 11 +#define PIP_BL_FAIL_EXIT_STATUS_CODE 0x0c +#define PIP_BL_VERIFY_INTEGRITY_RESP_LEN 12 +#define PIP_BL_INTEGRITY_CHEKC_PASS 0x00 +#define PIP_BL_BLOCK_WRITE_RESP_LEN 11 + +#define PIP_TOUCH_REPORT_ID 0x01 +#define PIP_BTN_REPORT_ID 0x03 +#define PIP_WAKEUP_EVENT_REPORT_ID 0x04 +#define PIP_PUSH_BTN_REPORT_ID 0x06 +#define GEN5_OLD_PUSH_BTN_REPORT_ID 0x05 /* Special for old Gen5 TP. */ +#define PIP_PROXIMITY_REPORT_ID 0x07 + +#define PIP_PROXIMITY_REPORT_SIZE 6 +#define PIP_PROXIMITY_DISTANCE_OFFSET 0x05 +#define PIP_PROXIMITY_DISTANCE_MASK 0x01 + +#define PIP_TOUCH_REPORT_HEAD_SIZE 7 +#define PIP_TOUCH_REPORT_MAX_SIZE 127 +#define PIP_BTN_REPORT_HEAD_SIZE 6 +#define PIP_BTN_REPORT_MAX_SIZE 14 +#define PIP_WAKEUP_EVENT_SIZE 4 + +#define PIP_NUMBER_OF_TOUCH_OFFSET 5 +#define PIP_NUMBER_OF_TOUCH_MASK 0x1f +#define PIP_BUTTONS_OFFSET 5 +#define PIP_BUTTONS_MASK 0x0f +#define PIP_GET_EVENT_ID(reg) (((reg) >> 5) & 0x03) +#define PIP_GET_TOUCH_ID(reg) ((reg) & 0x1f) +#define PIP_TOUCH_TYPE_FINGER 0x00 +#define PIP_TOUCH_TYPE_PROXIMITY 0x01 +#define PIP_TOUCH_TYPE_HOVER 0x02 +#define PIP_GET_TOUCH_TYPE(reg) ((reg) & 0x07) + +#define RECORD_EVENT_NONE 0 +#define RECORD_EVENT_TOUCHDOWN 1 +#define RECORD_EVENT_DISPLACE 2 +#define RECORD_EVENT_LIFTOFF 3 + +#define PIP_SENSING_MODE_MUTUAL_CAP_FINE 0x00 +#define PIP_SENSING_MODE_SELF_CAP 0x02 + +#define PIP_SET_PROXIMITY 0x49 + +/* Macro of Gen5 */ +#define GEN5_BL_MAX_OUTPUT_LENGTH 0x0100 +#define GEN5_APP_MAX_OUTPUT_LENGTH 0x00fe + +#define GEN5_POWER_STATE_ACTIVE 0x01 +#define GEN5_POWER_STATE_LOOK_FOR_TOUCH 0x02 +#define GEN5_POWER_STATE_READY 0x03 +#define GEN5_POWER_STATE_IDLE 0x04 +#define GEN5_POWER_STATE_BTN_ONLY 0x05 +#define GEN5_POWER_STATE_OFF 0x06 + +#define GEN5_POWER_READY_MAX_INTRVL_TIME 50 /* Unit: ms */ +#define GEN5_POWER_IDLE_MAX_INTRVL_TIME 250 /* Unit: ms */ + +#define GEN5_CMD_GET_PARAMETER 0x05 +#define GEN5_CMD_SET_PARAMETER 0x06 +#define GEN5_PARAMETER_ACT_INTERVL_ID 0x4d +#define GEN5_PARAMETER_ACT_INTERVL_SIZE 1 +#define GEN5_PARAMETER_ACT_LFT_INTERVL_ID 0x4f +#define GEN5_PARAMETER_ACT_LFT_INTERVL_SIZE 2 +#define GEN5_PARAMETER_LP_INTRVL_ID 0x4c +#define GEN5_PARAMETER_LP_INTRVL_SIZE 2 + +#define GEN5_PARAMETER_DISABLE_PIP_REPORT 0x08 + +#define GEN5_BL_REPORT_DESCRIPTOR_SIZE 0x1d +#define GEN5_BL_REPORT_DESCRIPTOR_ID 0xfe +#define GEN5_APP_REPORT_DESCRIPTOR_SIZE 0xee +#define GEN5_APP_CONTRACT_REPORT_DESCRIPTOR_SIZE 0xfa +#define GEN5_APP_REPORT_DESCRIPTOR_ID 0xf6 + +#define GEN5_RETRIEVE_MUTUAL_PWC_DATA 0x00 +#define GEN5_RETRIEVE_SELF_CAP_PWC_DATA 0x01 + +#define GEN5_RETRIEVE_DATA_ELEMENT_SIZE_MASK 0x07 + +#define GEN5_CMD_EXECUTE_PANEL_SCAN 0x2a +#define GEN5_CMD_RETRIEVE_PANEL_SCAN 0x2b +#define GEN5_PANEL_SCAN_MUTUAL_RAW_DATA 0x00 +#define GEN5_PANEL_SCAN_MUTUAL_BASELINE 0x01 +#define GEN5_PANEL_SCAN_MUTUAL_DIFFCOUNT 0x02 +#define GEN5_PANEL_SCAN_SELF_RAW_DATA 0x03 +#define GEN5_PANEL_SCAN_SELF_BASELINE 0x04 +#define GEN5_PANEL_SCAN_SELF_DIFFCOUNT 0x05 + +/* The offset only valid for retrieve PWC and panel scan commands */ +#define GEN5_RESP_DATA_STRUCTURE_OFFSET 10 +#define GEN5_PWC_DATA_ELEMENT_SIZE_MASK 0x07 + + +struct cyapa_pip_touch_record { + /* + * Bit 7 - 3: reserved + * Bit 2 - 0: touch type; + * 0 : standard finger; + * 1 : proximity (Start supported in Gen5 TP). + * 2 : finger hover (defined, but not used yet.) + * 3 - 15 : reserved. + */ + u8 touch_type; + + /* + * Bit 7: indicates touch liftoff status. + * 0 : touch is currently on the panel. + * 1 : touch record indicates a liftoff. + * Bit 6 - 5: indicates an event associated with this touch instance + * 0 : no event + * 1 : touchdown + * 2 : significant displacement (> active distance) + * 3 : liftoff (record reports last known coordinates) + * Bit 4 - 0: An arbitrary ID tag associated with a finger + * to allow tracking a touch as it moves around the panel. + */ + u8 touch_tip_event_id; + + /* Bit 7 - 0 of X-axis coordinate of the touch in pixel. */ + u8 x_lo; + + /* Bit 15 - 8 of X-axis coordinate of the touch in pixel. */ + u8 x_hi; + + /* Bit 7 - 0 of Y-axis coordinate of the touch in pixel. */ + u8 y_lo; + + /* Bit 15 - 8 of Y-axis coordinate of the touch in pixel. */ + u8 y_hi; + + /* + * The meaning of this value is different when touch_type is different. + * For standard finger type: + * Touch intensity in counts, pressure value. + * For proximity type (Start supported in Gen5 TP): + * The distance, in surface units, between the contact and + * the surface. + **/ + u8 z; + + /* + * The length of the major axis of the ellipse of contact between + * the finger and the panel (ABS_MT_TOUCH_MAJOR). + */ + u8 major_axis_len; + + /* + * The length of the minor axis of the ellipse of contact between + * the finger and the panel (ABS_MT_TOUCH_MINOR). + */ + u8 minor_axis_len; + + /* + * The length of the major axis of the approaching tool. + * (ABS_MT_WIDTH_MAJOR) + */ + u8 major_tool_len; + + /* + * The length of the minor axis of the approaching tool. + * (ABS_MT_WIDTH_MINOR) + */ + u8 minor_tool_len; + + /* + * The angle between the panel vertical axis and + * the major axis of the contact ellipse. This value is an 8-bit + * signed integer. The range is -127 to +127 (corresponding to + * -90 degree and +90 degree respectively). + * The positive direction is clockwise from the vertical axis. + * If the ellipse of contact degenerates into a circle, + * orientation is reported as 0. + */ + u8 orientation; +} __packed; + +struct cyapa_pip_report_data { + u8 report_head[PIP_TOUCH_REPORT_HEAD_SIZE]; + struct cyapa_pip_touch_record touch_records[10]; +} __packed; + +struct cyapa_tsg_bin_image_head { + u8 head_size; /* Unit: bytes, including itself. */ + u8 ttda_driver_major_version; /* Reserved as 0. */ + u8 ttda_driver_minor_version; /* Reserved as 0. */ + u8 fw_major_version; + u8 fw_minor_version; + u8 fw_revision_control_number[8]; + u8 silicon_id_hi; + u8 silicon_id_lo; + u8 chip_revision; + u8 family_id; + u8 bl_ver_maj; + u8 bl_ver_min; +} __packed; + +struct cyapa_tsg_bin_image_data_record { + u8 flash_array_id; + __be16 row_number; + /* The number of bytes of flash data contained in this record. */ + __be16 record_len; + /* The flash program data. */ + u8 record_data[CYAPA_TSG_FW_ROW_SIZE]; +} __packed; + +struct cyapa_tsg_bin_image { + struct cyapa_tsg_bin_image_head image_head; + struct cyapa_tsg_bin_image_data_record records[0]; +} __packed; + +struct pip_bl_packet_start { + u8 sop; /* Start of packet, must be 01h */ + u8 cmd_code; + __le16 data_length; /* Size of data parameter start from data[0] */ +} __packed; + +struct pip_bl_packet_end { + __le16 crc; + u8 eop; /* End of packet, must be 17h */ +} __packed; + +struct pip_bl_cmd_head { + __le16 addr; /* Output report register address, must be 0004h */ + /* Size of packet not including output report register address */ + __le16 length; + u8 report_id; /* Bootloader output report id, must be 40h */ + u8 rsvd; /* Reserved, must be 0 */ + struct pip_bl_packet_start packet_start; + u8 data[0]; /* Command data variable based on commands */ +} __packed; + +/* Initiate bootload command data structure. */ +struct pip_bl_initiate_cmd_data { + /* Key must be "A5h 01h 02h 03h FFh FEh FDh 5Ah" */ + u8 key[CYAPA_TSG_BL_KEY_SIZE]; + u8 metadata_raw_parameter[CYAPA_TSG_FLASH_MAP_METADATA_SIZE]; + __le16 metadata_crc; +} __packed; + +struct tsg_bl_metadata_row_params { + __le16 size; + __le16 maximum_size; + __le32 app_start; + __le16 app_len; + __le16 app_crc; + __le32 app_entry; + __le32 upgrade_start; + __le16 upgrade_len; + __le16 entry_row_crc; + u8 padding[36]; /* Padding data must be 0 */ + __le16 metadata_crc; /* CRC starts at offset of 60 */ +} __packed; + +/* Bootload program and verify row command data structure */ +struct tsg_bl_flash_row_head { + u8 flash_array_id; + __le16 flash_row_id; + u8 flash_data[0]; +} __packed; + +struct pip_app_cmd_head { + __le16 addr; /* Output report register address, must be 0004h */ + /* Size of packet not including output report register address */ + __le16 length; + u8 report_id; /* Application output report id, must be 2Fh */ + u8 rsvd; /* Reserved, must be 0 */ + /* + * Bit 7: reserved, must be 0. + * Bit 6-0: command code. + */ + u8 cmd_code; + u8 parameter_data[0]; /* Parameter data variable based on cmd_code */ +} __packed; + +/* Application get/set parameter command data structure */ +struct gen5_app_set_parameter_data { + u8 parameter_id; + u8 parameter_size; + __le32 value; +} __packed; + +struct gen5_app_get_parameter_data { + u8 parameter_id; +} __packed; + +struct gen5_retrieve_panel_scan_data { + __le16 read_offset; + __le16 read_elements; + u8 data_id; +} __packed; + +u8 pip_read_sys_info[] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x02 }; +u8 pip_bl_read_app_info[] = { 0x04, 0x00, 0x0b, 0x00, 0x40, 0x00, + 0x01, 0x3c, 0x00, 0x00, 0xb0, 0x42, 0x17 + }; + +static u8 cyapa_pip_bl_cmd_key[] = { 0xa5, 0x01, 0x02, 0x03, + 0xff, 0xfe, 0xfd, 0x5a }; + +static int cyapa_pip_event_process(struct cyapa *cyapa, + struct cyapa_pip_report_data *report_data); + +int cyapa_pip_cmd_state_initialize(struct cyapa *cyapa) +{ + struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip; + + init_completion(&pip->cmd_ready); + atomic_set(&pip->cmd_issued, 0); + mutex_init(&pip->cmd_lock); + + mutex_init(&pip->pm_stage_lock); + pip->pm_stage = CYAPA_PM_DEACTIVE; + + pip->resp_sort_func = NULL; + pip->in_progress_cmd = PIP_INVALID_CMD; + pip->resp_data = NULL; + pip->resp_len = NULL; + + cyapa->dev_pwr_mode = UNINIT_PWR_MODE; + cyapa->dev_sleep_time = UNINIT_SLEEP_TIME; + + return 0; +} + +/* Return negative errno, or else the number of bytes read. */ +ssize_t cyapa_i2c_pip_read(struct cyapa *cyapa, u8 *buf, size_t size) +{ + int ret; + + if (size == 0) + return 0; + + if (!buf || size > CYAPA_REG_MAP_SIZE) + return -EINVAL; + + ret = i2c_master_recv(cyapa->client, buf, size); + + if (ret != size) + return (ret < 0) ? ret : -EIO; + return size; +} + +/** + * Return a negative errno code else zero on success. + */ +ssize_t cyapa_i2c_pip_write(struct cyapa *cyapa, u8 *buf, size_t size) +{ + int ret; + + if (!buf || !size) + return -EINVAL; + + ret = i2c_master_send(cyapa->client, buf, size); + + if (ret != size) + return (ret < 0) ? ret : -EIO; + + return 0; +} + +static void cyapa_set_pip_pm_state(struct cyapa *cyapa, + enum cyapa_pm_stage pm_stage) +{ + struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip; + + mutex_lock(&pip->pm_stage_lock); + pip->pm_stage = pm_stage; + mutex_unlock(&pip->pm_stage_lock); +} + +static void cyapa_reset_pip_pm_state(struct cyapa *cyapa) +{ + struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip; + + /* Indicates the pip->pm_stage is not valid. */ + mutex_lock(&pip->pm_stage_lock); + pip->pm_stage = CYAPA_PM_DEACTIVE; + mutex_unlock(&pip->pm_stage_lock); +} + +static enum cyapa_pm_stage cyapa_get_pip_pm_state(struct cyapa *cyapa) +{ + struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip; + enum cyapa_pm_stage pm_stage; + + mutex_lock(&pip->pm_stage_lock); + pm_stage = pip->pm_stage; + mutex_unlock(&pip->pm_stage_lock); + + return pm_stage; +} + +/** + * This function is aimed to dump all not read data in Gen5 trackpad + * before send any command, otherwise, the interrupt line will be blocked. + */ +int cyapa_empty_pip_output_data(struct cyapa *cyapa, + u8 *buf, int *len, cb_sort func) +{ + struct input_dev *input = cyapa->input; + struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip; + enum cyapa_pm_stage pm_stage = cyapa_get_pip_pm_state(cyapa); + int length; + int report_count; + int empty_count; + int buf_len; + int error; + + buf_len = 0; + if (len) { + buf_len = (*len < CYAPA_REG_MAP_SIZE) ? + *len : CYAPA_REG_MAP_SIZE; + *len = 0; + } + + report_count = 8; /* max 7 pending data before command response data */ + empty_count = 0; + do { + /* + * Depending on testing in cyapa driver, there are max 5 "02 00" + * packets between two valid buffered data report in firmware. + * So in order to dump all buffered data out and + * make interrupt line release for reassert again, + * we must set the empty_count check value bigger than 5 to + * make it work. Otherwise, in some situation, + * the interrupt line may unable to reactive again, + * which will cause trackpad device unable to + * report data any more. + * for example, it may happen in EFT and ESD testing. + */ + if (empty_count > 5) + return 0; + + error = cyapa_i2c_pip_read(cyapa, pip->empty_buf, + PIP_RESP_LENGTH_SIZE); + if (error < 0) + return error; + + length = get_unaligned_le16(pip->empty_buf); + if (length == PIP_RESP_LENGTH_SIZE) { + empty_count++; + continue; + } else if (length > CYAPA_REG_MAP_SIZE) { + /* Should not happen */ + return -EINVAL; + } else if (length == 0) { + /* Application or bootloader launch data polled out. */ + length = PIP_RESP_LENGTH_SIZE; + if (buf && buf_len && func && + func(cyapa, pip->empty_buf, length)) { + length = min(buf_len, length); + memcpy(buf, pip->empty_buf, length); + *len = length; + /* Response found, success. */ + return 0; + } + continue; + } + + error = cyapa_i2c_pip_read(cyapa, pip->empty_buf, length); + if (error < 0) + return error; + + report_count--; + empty_count = 0; + length = get_unaligned_le16(pip->empty_buf); + if (length <= PIP_RESP_LENGTH_SIZE) { + empty_count++; + } else if (buf && buf_len && func && + func(cyapa, pip->empty_buf, length)) { + length = min(buf_len, length); + memcpy(buf, pip->empty_buf, length); + *len = length; + /* Response found, success. */ + return 0; + } else if (cyapa->operational && input && input->users && + (pm_stage == CYAPA_PM_RUNTIME_RESUME || + pm_stage == CYAPA_PM_RUNTIME_SUSPEND)) { + /* Parse the data and report it if it's valid. */ + cyapa_pip_event_process(cyapa, + (struct cyapa_pip_report_data *)pip->empty_buf); + } + + error = -EINVAL; + } while (report_count); + + return error; +} + +static int cyapa_do_i2c_pip_cmd_irq_sync( + struct cyapa *cyapa, + u8 *cmd, size_t cmd_len, + unsigned long timeout) +{ + struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip; + int error; + + /* Wait for interrupt to set ready completion */ + init_completion(&pip->cmd_ready); + + atomic_inc(&pip->cmd_issued); + error = cyapa_i2c_pip_write(cyapa, cmd, cmd_len); + if (error) { + atomic_dec(&pip->cmd_issued); + return (error < 0) ? error : -EIO; + } + + /* Wait for interrupt to indicate command is completed. */ + timeout = wait_for_completion_timeout(&pip->cmd_ready, + msecs_to_jiffies(timeout)); + if (timeout == 0) { + atomic_dec(&pip->cmd_issued); + return -ETIMEDOUT; + } + + return 0; +} + +static int cyapa_do_i2c_pip_cmd_polling( + struct cyapa *cyapa, + u8 *cmd, size_t cmd_len, + u8 *resp_data, int *resp_len, + unsigned long timeout, + cb_sort func) +{ + struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip; + int tries; + int length; + int error; + + atomic_inc(&pip->cmd_issued); + error = cyapa_i2c_pip_write(cyapa, cmd, cmd_len); + if (error) { + atomic_dec(&pip->cmd_issued); + return error < 0 ? error : -EIO; + } + + length = resp_len ? *resp_len : 0; + if (resp_data && resp_len && length != 0 && func) { + tries = timeout / 5; + do { + usleep_range(3000, 5000); + *resp_len = length; + error = cyapa_empty_pip_output_data(cyapa, + resp_data, resp_len, func); + if (error || *resp_len == 0) + continue; + else + break; + } while (--tries > 0); + if ((error || *resp_len == 0) || tries <= 0) + error = error ? error : -ETIMEDOUT; + } + + atomic_dec(&pip->cmd_issued); + return error; +} + +int cyapa_i2c_pip_cmd_irq_sync( + struct cyapa *cyapa, + u8 *cmd, int cmd_len, + u8 *resp_data, int *resp_len, + unsigned long timeout, + cb_sort func, + bool irq_mode) +{ + struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip; + int error; + + if (!cmd || !cmd_len) + return -EINVAL; + + /* Commands must be serialized. */ + error = mutex_lock_interruptible(&pip->cmd_lock); + if (error) + return error; + + pip->resp_sort_func = func; + pip->resp_data = resp_data; + pip->resp_len = resp_len; + + if (cmd_len >= PIP_MIN_APP_CMD_LENGTH && + cmd[4] == PIP_APP_CMD_REPORT_ID) { + /* Application command */ + pip->in_progress_cmd = cmd[6] & 0x7f; + } else if (cmd_len >= PIP_MIN_BL_CMD_LENGTH && + cmd[4] == PIP_BL_CMD_REPORT_ID) { + /* Bootloader command */ + pip->in_progress_cmd = cmd[7]; + } + + /* Send command data, wait and read output response data's length. */ + if (irq_mode) { + pip->is_irq_mode = true; + error = cyapa_do_i2c_pip_cmd_irq_sync(cyapa, cmd, cmd_len, + timeout); + if (error == -ETIMEDOUT && resp_data && + resp_len && *resp_len != 0 && func) { + /* + * For some old version, there was no interrupt for + * the command response data, so need to poll here + * to try to get the response data. + */ + error = cyapa_empty_pip_output_data(cyapa, + resp_data, resp_len, func); + if (error || *resp_len == 0) + error = error ? error : -ETIMEDOUT; + } + } else { + pip->is_irq_mode = false; + error = cyapa_do_i2c_pip_cmd_polling(cyapa, cmd, cmd_len, + resp_data, resp_len, timeout, func); + } + + pip->resp_sort_func = NULL; + pip->resp_data = NULL; + pip->resp_len = NULL; + pip->in_progress_cmd = PIP_INVALID_CMD; + + mutex_unlock(&pip->cmd_lock); + return error; +} + +bool cyapa_sort_tsg_pip_bl_resp_data(struct cyapa *cyapa, + u8 *data, int len) +{ + if (!data || len < PIP_MIN_BL_RESP_LENGTH) + return false; + + /* Bootloader input report id 30h */ + if (data[PIP_RESP_REPORT_ID_OFFSET] == PIP_BL_RESP_REPORT_ID && + data[PIP_RESP_RSVD_OFFSET] == PIP_RESP_RSVD_KEY && + data[PIP_RESP_BL_SOP_OFFSET] == PIP_SOP_KEY) + return true; + + return false; +} + +bool cyapa_sort_tsg_pip_app_resp_data(struct cyapa *cyapa, + u8 *data, int len) +{ + struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip; + int resp_len; + + if (!data || len < PIP_MIN_APP_RESP_LENGTH) + return false; + + if (data[PIP_RESP_REPORT_ID_OFFSET] == PIP_APP_RESP_REPORT_ID && + data[PIP_RESP_RSVD_OFFSET] == PIP_RESP_RSVD_KEY) { + resp_len = get_unaligned_le16(&data[PIP_RESP_LENGTH_OFFSET]); + if (GET_PIP_CMD_CODE(data[PIP_RESP_APP_CMD_OFFSET]) == 0x00 && + resp_len == PIP_UNSUPPORTED_CMD_RESP_LENGTH && + data[5] == pip->in_progress_cmd) { + /* Unsupported command code */ + return false; + } else if (GET_PIP_CMD_CODE(data[PIP_RESP_APP_CMD_OFFSET]) == + pip->in_progress_cmd) { + /* Correct command response received */ + return true; + } + } + + return false; +} + +static bool cyapa_sort_pip_application_launch_data(struct cyapa *cyapa, + u8 *buf, int len) +{ + if (buf == NULL || len < PIP_RESP_LENGTH_SIZE) + return false; + + /* + * After reset or power on, trackpad device always sets to 0x00 0x00 + * to indicate a reset or power on event. + */ + if (buf[0] == 0 && buf[1] == 0) + return true; + + return false; +} + +static bool cyapa_sort_gen5_hid_descriptor_data(struct cyapa *cyapa, + u8 *buf, int len) +{ + int resp_len; + int max_output_len; + + /* Check hid descriptor. */ + if (len != PIP_HID_DESCRIPTOR_SIZE) + return false; + + resp_len = get_unaligned_le16(&buf[PIP_RESP_LENGTH_OFFSET]); + max_output_len = get_unaligned_le16(&buf[16]); + if (resp_len == PIP_HID_DESCRIPTOR_SIZE) { + if (buf[PIP_RESP_REPORT_ID_OFFSET] == PIP_HID_BL_REPORT_ID && + max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) { + /* BL mode HID Descriptor */ + return true; + } else if ((buf[PIP_RESP_REPORT_ID_OFFSET] == + PIP_HID_APP_REPORT_ID) && + max_output_len == GEN5_APP_MAX_OUTPUT_LENGTH) { + /* APP mode HID Descriptor */ + return true; + } + } + + return false; +} + +static bool cyapa_sort_pip_deep_sleep_data(struct cyapa *cyapa, + u8 *buf, int len) +{ + if (len == PIP_DEEP_SLEEP_RESP_LENGTH && + buf[PIP_RESP_REPORT_ID_OFFSET] == + PIP_APP_DEEP_SLEEP_REPORT_ID && + (buf[4] & PIP_DEEP_SLEEP_OPCODE_MASK) == + PIP_DEEP_SLEEP_OPCODE) + return true; + return false; +} + +static int gen5_idle_state_parse(struct cyapa *cyapa) +{ + u8 resp_data[PIP_HID_DESCRIPTOR_SIZE]; + int max_output_len; + int length; + u8 cmd[2]; + int ret; + int error; + + /* + * Dump all buffered data firstly for the situation + * when the trackpad is just power on the cyapa go here. + */ + cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL); + + memset(resp_data, 0, sizeof(resp_data)); + ret = cyapa_i2c_pip_read(cyapa, resp_data, 3); + if (ret != 3) + return ret < 0 ? ret : -EIO; + + length = get_unaligned_le16(&resp_data[PIP_RESP_LENGTH_OFFSET]); + if (length == PIP_RESP_LENGTH_SIZE) { + /* Normal state of Gen5 with no data to response */ + cyapa->gen = CYAPA_GEN5; + + cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL); + + /* Read description from trackpad device */ + cmd[0] = 0x01; + cmd[1] = 0x00; + length = PIP_HID_DESCRIPTOR_SIZE; + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + cmd, PIP_RESP_LENGTH_SIZE, + resp_data, &length, + 300, + cyapa_sort_gen5_hid_descriptor_data, + false); + if (error) + return error; + + length = get_unaligned_le16( + &resp_data[PIP_RESP_LENGTH_OFFSET]); + max_output_len = get_unaligned_le16(&resp_data[16]); + if ((length == PIP_HID_DESCRIPTOR_SIZE || + length == PIP_RESP_LENGTH_SIZE) && + (resp_data[PIP_RESP_REPORT_ID_OFFSET] == + PIP_HID_BL_REPORT_ID) && + max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) { + /* BL mode HID Description read */ + cyapa->state = CYAPA_STATE_GEN5_BL; + } else if ((length == PIP_HID_DESCRIPTOR_SIZE || + length == PIP_RESP_LENGTH_SIZE) && + (resp_data[PIP_RESP_REPORT_ID_OFFSET] == + PIP_HID_APP_REPORT_ID) && + max_output_len == GEN5_APP_MAX_OUTPUT_LENGTH) { + /* APP mode HID Description read */ + cyapa->state = CYAPA_STATE_GEN5_APP; + } else { + /* Should not happen!!! */ + cyapa->state = CYAPA_STATE_NO_DEVICE; + } + } + + return 0; +} + +static int gen5_hid_description_header_parse(struct cyapa *cyapa, u8 *reg_data) +{ + int length; + u8 resp_data[32]; + int max_output_len; + int ret; + + /* 0x20 0x00 0xF7 is Gen5 Application HID Description Header; + * 0x20 0x00 0xFF is Gen5 Bootloader HID Description Header. + * + * Must read HID Description content through out, + * otherwise Gen5 trackpad cannot response next command + * or report any touch or button data. + */ + ret = cyapa_i2c_pip_read(cyapa, resp_data, + PIP_HID_DESCRIPTOR_SIZE); + if (ret != PIP_HID_DESCRIPTOR_SIZE) + return ret < 0 ? ret : -EIO; + length = get_unaligned_le16(&resp_data[PIP_RESP_LENGTH_OFFSET]); + max_output_len = get_unaligned_le16(&resp_data[16]); + if (length == PIP_RESP_LENGTH_SIZE) { + if (reg_data[PIP_RESP_REPORT_ID_OFFSET] == + PIP_HID_BL_REPORT_ID) { + /* + * BL mode HID Description has been previously + * read out. + */ + cyapa->gen = CYAPA_GEN5; + cyapa->state = CYAPA_STATE_GEN5_BL; + } else { + /* + * APP mode HID Description has been previously + * read out. + */ + cyapa->gen = CYAPA_GEN5; + cyapa->state = CYAPA_STATE_GEN5_APP; + } + } else if (length == PIP_HID_DESCRIPTOR_SIZE && + resp_data[2] == PIP_HID_BL_REPORT_ID && + max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) { + /* BL mode HID Description read. */ + cyapa->gen = CYAPA_GEN5; + cyapa->state = CYAPA_STATE_GEN5_BL; + } else if (length == PIP_HID_DESCRIPTOR_SIZE && + (resp_data[PIP_RESP_REPORT_ID_OFFSET] == + PIP_HID_APP_REPORT_ID) && + max_output_len == GEN5_APP_MAX_OUTPUT_LENGTH) { + /* APP mode HID Description read. */ + cyapa->gen = CYAPA_GEN5; + cyapa->state = CYAPA_STATE_GEN5_APP; + } else { + /* Should not happen!!! */ + cyapa->state = CYAPA_STATE_NO_DEVICE; + } + + return 0; +} + +static int gen5_report_data_header_parse(struct cyapa *cyapa, u8 *reg_data) +{ + int length; + + length = get_unaligned_le16(®_data[PIP_RESP_LENGTH_OFFSET]); + switch (reg_data[PIP_RESP_REPORT_ID_OFFSET]) { + case PIP_TOUCH_REPORT_ID: + if (length < PIP_TOUCH_REPORT_HEAD_SIZE || + length > PIP_TOUCH_REPORT_MAX_SIZE) + return -EINVAL; + break; + case PIP_BTN_REPORT_ID: + case GEN5_OLD_PUSH_BTN_REPORT_ID: + case PIP_PUSH_BTN_REPORT_ID: + if (length < PIP_BTN_REPORT_HEAD_SIZE || + length > PIP_BTN_REPORT_MAX_SIZE) + return -EINVAL; + break; + case PIP_WAKEUP_EVENT_REPORT_ID: + if (length != PIP_WAKEUP_EVENT_SIZE) + return -EINVAL; + break; + default: + return -EINVAL; + } + + cyapa->gen = CYAPA_GEN5; + cyapa->state = CYAPA_STATE_GEN5_APP; + return 0; +} + +static int gen5_cmd_resp_header_parse(struct cyapa *cyapa, u8 *reg_data) +{ + struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip; + int length; + int ret; + + /* + * Must read report data through out, + * otherwise Gen5 trackpad cannot response next command + * or report any touch or button data. + */ + length = get_unaligned_le16(®_data[PIP_RESP_LENGTH_OFFSET]); + ret = cyapa_i2c_pip_read(cyapa, pip->empty_buf, length); + if (ret != length) + return ret < 0 ? ret : -EIO; + + if (length == PIP_RESP_LENGTH_SIZE) { + /* Previous command has read the data through out. */ + if (reg_data[PIP_RESP_REPORT_ID_OFFSET] == + PIP_BL_RESP_REPORT_ID) { + /* Gen5 BL command response data detected */ + cyapa->gen = CYAPA_GEN5; + cyapa->state = CYAPA_STATE_GEN5_BL; + } else { + /* Gen5 APP command response data detected */ + cyapa->gen = CYAPA_GEN5; + cyapa->state = CYAPA_STATE_GEN5_APP; + } + } else if ((pip->empty_buf[PIP_RESP_REPORT_ID_OFFSET] == + PIP_BL_RESP_REPORT_ID) && + (pip->empty_buf[PIP_RESP_RSVD_OFFSET] == + PIP_RESP_RSVD_KEY) && + (pip->empty_buf[PIP_RESP_BL_SOP_OFFSET] == + PIP_SOP_KEY) && + (pip->empty_buf[length - 1] == + PIP_EOP_KEY)) { + /* Gen5 BL command response data detected */ + cyapa->gen = CYAPA_GEN5; + cyapa->state = CYAPA_STATE_GEN5_BL; + } else if (pip->empty_buf[PIP_RESP_REPORT_ID_OFFSET] == + PIP_APP_RESP_REPORT_ID && + pip->empty_buf[PIP_RESP_RSVD_OFFSET] == + PIP_RESP_RSVD_KEY) { + /* Gen5 APP command response data detected */ + cyapa->gen = CYAPA_GEN5; + cyapa->state = CYAPA_STATE_GEN5_APP; + } else { + /* Should not happen!!! */ + cyapa->state = CYAPA_STATE_NO_DEVICE; + } + + return 0; +} + +static int cyapa_gen5_state_parse(struct cyapa *cyapa, u8 *reg_data, int len) +{ + int length; + + if (!reg_data || len < 3) + return -EINVAL; + + cyapa->state = CYAPA_STATE_NO_DEVICE; + + /* Parse based on Gen5 characteristic registers and bits */ + length = get_unaligned_le16(®_data[PIP_RESP_LENGTH_OFFSET]); + if (length == 0 || length == PIP_RESP_LENGTH_SIZE) { + gen5_idle_state_parse(cyapa); + } else if (length == PIP_HID_DESCRIPTOR_SIZE && + (reg_data[2] == PIP_HID_BL_REPORT_ID || + reg_data[2] == PIP_HID_APP_REPORT_ID)) { + gen5_hid_description_header_parse(cyapa, reg_data); + } else if ((length == GEN5_APP_REPORT_DESCRIPTOR_SIZE || + length == GEN5_APP_CONTRACT_REPORT_DESCRIPTOR_SIZE) && + reg_data[2] == GEN5_APP_REPORT_DESCRIPTOR_ID) { + /* 0xEE 0x00 0xF6 is Gen5 APP report description header. */ + cyapa->gen = CYAPA_GEN5; + cyapa->state = CYAPA_STATE_GEN5_APP; + } else if (length == GEN5_BL_REPORT_DESCRIPTOR_SIZE && + reg_data[2] == GEN5_BL_REPORT_DESCRIPTOR_ID) { + /* 0x1D 0x00 0xFE is Gen5 BL report descriptor header. */ + cyapa->gen = CYAPA_GEN5; + cyapa->state = CYAPA_STATE_GEN5_BL; + } else if (reg_data[2] == PIP_TOUCH_REPORT_ID || + reg_data[2] == PIP_BTN_REPORT_ID || + reg_data[2] == GEN5_OLD_PUSH_BTN_REPORT_ID || + reg_data[2] == PIP_PUSH_BTN_REPORT_ID || + reg_data[2] == PIP_WAKEUP_EVENT_REPORT_ID) { + gen5_report_data_header_parse(cyapa, reg_data); + } else if (reg_data[2] == PIP_BL_RESP_REPORT_ID || + reg_data[2] == PIP_APP_RESP_REPORT_ID) { + gen5_cmd_resp_header_parse(cyapa, reg_data); + } + + if (cyapa->gen == CYAPA_GEN5) { + /* + * Must read the content (e.g.: report description and so on) + * from trackpad device throughout. Otherwise, + * Gen5 trackpad cannot response to next command or + * report any touch or button data later. + */ + cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL); + + if (cyapa->state == CYAPA_STATE_GEN5_APP || + cyapa->state == CYAPA_STATE_GEN5_BL) + return 0; + } + + return -EAGAIN; +} + +static struct cyapa_tsg_bin_image_data_record * +cyapa_get_image_record_data_num(const struct firmware *fw, + int *record_num) +{ + int head_size; + + head_size = fw->data[0] + 1; + *record_num = (fw->size - head_size) / + sizeof(struct cyapa_tsg_bin_image_data_record); + return (struct cyapa_tsg_bin_image_data_record *)&fw->data[head_size]; +} + +int cyapa_pip_bl_initiate(struct cyapa *cyapa, const struct firmware *fw) +{ + struct cyapa_tsg_bin_image_data_record *image_records; + struct pip_bl_cmd_head *bl_cmd_head; + struct pip_bl_packet_start *bl_packet_start; + struct pip_bl_initiate_cmd_data *cmd_data; + struct pip_bl_packet_end *bl_packet_end; + u8 cmd[CYAPA_TSG_MAX_CMD_SIZE]; + int cmd_len; + u16 cmd_data_len; + u16 cmd_crc = 0; + u16 meta_data_crc = 0; + u8 resp_data[11]; + int resp_len; + int records_num; + u8 *data; + int error; + + /* Try to dump all buffered report data before any send command. */ + cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL); + + memset(cmd, 0, CYAPA_TSG_MAX_CMD_SIZE); + bl_cmd_head = (struct pip_bl_cmd_head *)cmd; + cmd_data_len = CYAPA_TSG_BL_KEY_SIZE + CYAPA_TSG_FLASH_MAP_BLOCK_SIZE; + cmd_len = sizeof(struct pip_bl_cmd_head) + cmd_data_len + + sizeof(struct pip_bl_packet_end); + + put_unaligned_le16(PIP_OUTPUT_REPORT_ADDR, &bl_cmd_head->addr); + put_unaligned_le16(cmd_len - 2, &bl_cmd_head->length); + bl_cmd_head->report_id = PIP_BL_CMD_REPORT_ID; + + bl_packet_start = &bl_cmd_head->packet_start; + bl_packet_start->sop = PIP_SOP_KEY; + bl_packet_start->cmd_code = PIP_BL_CMD_INITIATE_BL; + /* 8 key bytes and 128 bytes block size */ + put_unaligned_le16(cmd_data_len, &bl_packet_start->data_length); + + cmd_data = (struct pip_bl_initiate_cmd_data *)bl_cmd_head->data; + memcpy(cmd_data->key, cyapa_pip_bl_cmd_key, CYAPA_TSG_BL_KEY_SIZE); + + image_records = cyapa_get_image_record_data_num(fw, &records_num); + + /* APP_INTEGRITY row is always the last row block */ + data = image_records[records_num - 1].record_data; + memcpy(cmd_data->metadata_raw_parameter, data, + CYAPA_TSG_FLASH_MAP_METADATA_SIZE); + + meta_data_crc = crc_itu_t(0xffff, cmd_data->metadata_raw_parameter, + CYAPA_TSG_FLASH_MAP_METADATA_SIZE); + put_unaligned_le16(meta_data_crc, &cmd_data->metadata_crc); + + bl_packet_end = (struct pip_bl_packet_end *)(bl_cmd_head->data + + cmd_data_len); + cmd_crc = crc_itu_t(0xffff, (u8 *)bl_packet_start, + sizeof(struct pip_bl_packet_start) + cmd_data_len); + put_unaligned_le16(cmd_crc, &bl_packet_end->crc); + bl_packet_end->eop = PIP_EOP_KEY; + + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + cmd, cmd_len, + resp_data, &resp_len, 12000, + cyapa_sort_tsg_pip_bl_resp_data, true); + if (error || resp_len != PIP_BL_INITIATE_RESP_LEN || + resp_data[2] != PIP_BL_RESP_REPORT_ID || + !PIP_CMD_COMPLETE_SUCCESS(resp_data)) + return error ? error : -EAGAIN; + + return 0; +} + +static bool cyapa_sort_pip_bl_exit_data(struct cyapa *cyapa, u8 *buf, int len) +{ + if (buf == NULL || len < PIP_RESP_LENGTH_SIZE) + return false; + + if (buf[0] == 0 && buf[1] == 0) + return true; + + /* Exit bootloader failed for some reason. */ + if (len == PIP_BL_FAIL_EXIT_RESP_LEN && + buf[PIP_RESP_REPORT_ID_OFFSET] == + PIP_BL_RESP_REPORT_ID && + buf[PIP_RESP_RSVD_OFFSET] == PIP_RESP_RSVD_KEY && + buf[PIP_RESP_BL_SOP_OFFSET] == PIP_SOP_KEY && + buf[10] == PIP_EOP_KEY) + return true; + + return false; +} + +int cyapa_pip_bl_exit(struct cyapa *cyapa) +{ + + u8 bl_gen5_bl_exit[] = { 0x04, 0x00, + 0x0B, 0x00, 0x40, 0x00, 0x01, 0x3b, 0x00, 0x00, + 0x20, 0xc7, 0x17 + }; + u8 resp_data[11]; + int resp_len; + int error; + + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + bl_gen5_bl_exit, sizeof(bl_gen5_bl_exit), + resp_data, &resp_len, + 5000, cyapa_sort_pip_bl_exit_data, false); + if (error) + return error; + + if (resp_len == PIP_BL_FAIL_EXIT_RESP_LEN || + resp_data[PIP_RESP_REPORT_ID_OFFSET] == + PIP_BL_RESP_REPORT_ID) + return -EAGAIN; + + if (resp_data[0] == 0x00 && resp_data[1] == 0x00) + return 0; + + return -ENODEV; +} + +int cyapa_pip_bl_enter(struct cyapa *cyapa) +{ + u8 cmd[] = { 0x04, 0x00, 0x05, 0x00, 0x2F, 0x00, 0x01 }; + u8 resp_data[2]; + int resp_len; + int error; + + error = cyapa_poll_state(cyapa, 500); + if (error < 0) + return error; + + /* Already in bootloader mode, Skipping exit. */ + if (cyapa_is_pip_bl_mode(cyapa)) + return 0; + else if (!cyapa_is_pip_app_mode(cyapa)) + return -EINVAL; + + /* Try to dump all buffered report data before any send command. */ + cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL); + + /* + * Send bootloader enter command to trackpad device, + * after enter bootloader, the response data is two bytes of 0x00 0x00. + */ + resp_len = sizeof(resp_data); + memset(resp_data, 0, resp_len); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + cmd, sizeof(cmd), + resp_data, &resp_len, + 5000, cyapa_sort_pip_application_launch_data, + true); + if (error || resp_data[0] != 0x00 || resp_data[1] != 0x00) + return error < 0 ? error : -EAGAIN; + + cyapa->operational = false; + if (cyapa->gen == CYAPA_GEN5) + cyapa->state = CYAPA_STATE_GEN5_BL; + else if (cyapa->gen == CYAPA_GEN6) + cyapa->state = CYAPA_STATE_GEN6_BL; + return 0; +} + +static int cyapa_pip_fw_head_check(struct cyapa *cyapa, + struct cyapa_tsg_bin_image_head *image_head) +{ + if (image_head->head_size != 0x0C && image_head->head_size != 0x12) + return -EINVAL; + + switch (cyapa->gen) { + case CYAPA_GEN6: + if (image_head->family_id != 0x9B || + image_head->silicon_id_hi != 0x0B) + return -EINVAL; + break; + case CYAPA_GEN5: + /* Gen5 without proximity support. */ + if (cyapa->platform_ver < 2) { + if (image_head->head_size == 0x0C) + break; + return -EINVAL; + } + + if (image_head->family_id != 0x91 || + image_head->silicon_id_hi != 0x02) + return -EINVAL; + break; + default: + return -EINVAL; + } + + return 0; +} + +int cyapa_pip_check_fw(struct cyapa *cyapa, const struct firmware *fw) +{ + struct device *dev = &cyapa->client->dev; + struct cyapa_tsg_bin_image_data_record *image_records; + const struct cyapa_tsg_bin_image_data_record *app_integrity; + const struct tsg_bl_metadata_row_params *metadata; + int flash_records_count; + u32 fw_app_start, fw_upgrade_start; + u16 fw_app_len, fw_upgrade_len; + u16 app_crc; + u16 app_integrity_crc; + int i; + + /* Verify the firmware image not miss-used for Gen5 and Gen6. */ + if (cyapa_pip_fw_head_check(cyapa, + (struct cyapa_tsg_bin_image_head *)fw->data)) { + dev_err(dev, "%s: firmware image not match TP device.\n", + __func__); + return -EINVAL; + } + + image_records = + cyapa_get_image_record_data_num(fw, &flash_records_count); + + /* + * APP_INTEGRITY row is always the last row block, + * and the row id must be 0x01ff. + */ + app_integrity = &image_records[flash_records_count - 1]; + + if (app_integrity->flash_array_id != 0x00 || + get_unaligned_be16(&app_integrity->row_number) != 0x01ff) { + dev_err(dev, "%s: invalid app_integrity data.\n", __func__); + return -EINVAL; + } + + metadata = (const void *)app_integrity->record_data; + + /* Verify app_integrity crc */ + app_integrity_crc = crc_itu_t(0xffff, app_integrity->record_data, + CYAPA_TSG_APP_INTEGRITY_SIZE); + if (app_integrity_crc != get_unaligned_le16(&metadata->metadata_crc)) { + dev_err(dev, "%s: invalid app_integrity crc.\n", __func__); + return -EINVAL; + } + + fw_app_start = get_unaligned_le32(&metadata->app_start); + fw_app_len = get_unaligned_le16(&metadata->app_len); + fw_upgrade_start = get_unaligned_le32(&metadata->upgrade_start); + fw_upgrade_len = get_unaligned_le16(&metadata->upgrade_len); + + if (fw_app_start % CYAPA_TSG_FW_ROW_SIZE || + fw_app_len % CYAPA_TSG_FW_ROW_SIZE || + fw_upgrade_start % CYAPA_TSG_FW_ROW_SIZE || + fw_upgrade_len % CYAPA_TSG_FW_ROW_SIZE) { + dev_err(dev, "%s: invalid image alignment.\n", __func__); + return -EINVAL; + } + + /* Verify application image CRC. */ + app_crc = 0xffffU; + for (i = 0; i < fw_app_len / CYAPA_TSG_FW_ROW_SIZE; i++) { + const u8 *data = image_records[i].record_data; + + app_crc = crc_itu_t(app_crc, data, CYAPA_TSG_FW_ROW_SIZE); + } + + if (app_crc != get_unaligned_le16(&metadata->app_crc)) { + dev_err(dev, "%s: invalid firmware app crc check.\n", __func__); + return -EINVAL; + } + + return 0; +} + +static int cyapa_pip_write_fw_block(struct cyapa *cyapa, + struct cyapa_tsg_bin_image_data_record *flash_record) +{ + struct pip_bl_cmd_head *bl_cmd_head; + struct pip_bl_packet_start *bl_packet_start; + struct tsg_bl_flash_row_head *flash_row_head; + struct pip_bl_packet_end *bl_packet_end; + u8 cmd[CYAPA_TSG_MAX_CMD_SIZE]; + u16 cmd_len; + u8 flash_array_id; + u16 flash_row_id; + u16 record_len; + u8 *record_data; + u16 data_len; + u16 crc; + u8 resp_data[11]; + int resp_len; + int error; + + flash_array_id = flash_record->flash_array_id; + flash_row_id = get_unaligned_be16(&flash_record->row_number); + record_len = get_unaligned_be16(&flash_record->record_len); + record_data = flash_record->record_data; + + memset(cmd, 0, CYAPA_TSG_MAX_CMD_SIZE); + bl_cmd_head = (struct pip_bl_cmd_head *)cmd; + bl_packet_start = &bl_cmd_head->packet_start; + cmd_len = sizeof(struct pip_bl_cmd_head) + + sizeof(struct tsg_bl_flash_row_head) + + CYAPA_TSG_FLASH_MAP_BLOCK_SIZE + + sizeof(struct pip_bl_packet_end); + + put_unaligned_le16(PIP_OUTPUT_REPORT_ADDR, &bl_cmd_head->addr); + /* Don't include 2 bytes register address */ + put_unaligned_le16(cmd_len - 2, &bl_cmd_head->length); + bl_cmd_head->report_id = PIP_BL_CMD_REPORT_ID; + bl_packet_start->sop = PIP_SOP_KEY; + bl_packet_start->cmd_code = PIP_BL_CMD_PROGRAM_VERIFY_ROW; + + /* 1 (Flash Array ID) + 2 (Flash Row ID) + 128 (flash data) */ + data_len = sizeof(struct tsg_bl_flash_row_head) + record_len; + put_unaligned_le16(data_len, &bl_packet_start->data_length); + + flash_row_head = (struct tsg_bl_flash_row_head *)bl_cmd_head->data; + flash_row_head->flash_array_id = flash_array_id; + put_unaligned_le16(flash_row_id, &flash_row_head->flash_row_id); + memcpy(flash_row_head->flash_data, record_data, record_len); + + bl_packet_end = (struct pip_bl_packet_end *)(bl_cmd_head->data + + data_len); + crc = crc_itu_t(0xffff, (u8 *)bl_packet_start, + sizeof(struct pip_bl_packet_start) + data_len); + put_unaligned_le16(crc, &bl_packet_end->crc); + bl_packet_end->eop = PIP_EOP_KEY; + + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, cmd_len, + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_bl_resp_data, true); + if (error || resp_len != PIP_BL_BLOCK_WRITE_RESP_LEN || + resp_data[2] != PIP_BL_RESP_REPORT_ID || + !PIP_CMD_COMPLETE_SUCCESS(resp_data)) + return error < 0 ? error : -EAGAIN; + + return 0; +} + +int cyapa_pip_do_fw_update(struct cyapa *cyapa, + const struct firmware *fw) +{ + struct device *dev = &cyapa->client->dev; + struct cyapa_tsg_bin_image_data_record *image_records; + int flash_records_count; + int i; + int error; + + cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL); + + image_records = + cyapa_get_image_record_data_num(fw, &flash_records_count); + + /* + * The last flash row 0x01ff has been written through bl_initiate + * command, so DO NOT write flash 0x01ff to trackpad device. + */ + for (i = 0; i < (flash_records_count - 1); i++) { + error = cyapa_pip_write_fw_block(cyapa, &image_records[i]); + if (error) { + dev_err(dev, "%s: Gen5 FW update aborted: %d\n", + __func__, error); + return error; + } + } + + return 0; +} + +static int cyapa_gen5_change_power_state(struct cyapa *cyapa, u8 power_state) +{ + u8 cmd[8] = { 0x04, 0x00, 0x06, 0x00, 0x2f, 0x00, 0x08, 0x01 }; + u8 resp_data[6]; + int resp_len; + int error; + + cmd[7] = power_state; + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd), + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_app_resp_data, false); + if (error || !VALID_CMD_RESP_HEADER(resp_data, 0x08) || + !PIP_CMD_COMPLETE_SUCCESS(resp_data)) + return error < 0 ? error : -EINVAL; + + return 0; +} + +static int cyapa_gen5_set_interval_time(struct cyapa *cyapa, + u8 parameter_id, u16 interval_time) +{ + struct pip_app_cmd_head *app_cmd_head; + struct gen5_app_set_parameter_data *parameter_data; + u8 cmd[CYAPA_TSG_MAX_CMD_SIZE]; + int cmd_len; + u8 resp_data[7]; + int resp_len; + u8 parameter_size; + int error; + + memset(cmd, 0, CYAPA_TSG_MAX_CMD_SIZE); + app_cmd_head = (struct pip_app_cmd_head *)cmd; + parameter_data = (struct gen5_app_set_parameter_data *) + app_cmd_head->parameter_data; + cmd_len = sizeof(struct pip_app_cmd_head) + + sizeof(struct gen5_app_set_parameter_data); + + switch (parameter_id) { + case GEN5_PARAMETER_ACT_INTERVL_ID: + parameter_size = GEN5_PARAMETER_ACT_INTERVL_SIZE; + break; + case GEN5_PARAMETER_ACT_LFT_INTERVL_ID: + parameter_size = GEN5_PARAMETER_ACT_LFT_INTERVL_SIZE; + break; + case GEN5_PARAMETER_LP_INTRVL_ID: + parameter_size = GEN5_PARAMETER_LP_INTRVL_SIZE; + break; + default: + return -EINVAL; + } + + put_unaligned_le16(PIP_OUTPUT_REPORT_ADDR, &app_cmd_head->addr); + /* + * Don't include unused parameter value bytes and + * 2 bytes register address. + */ + put_unaligned_le16(cmd_len - (4 - parameter_size) - 2, + &app_cmd_head->length); + app_cmd_head->report_id = PIP_APP_CMD_REPORT_ID; + app_cmd_head->cmd_code = GEN5_CMD_SET_PARAMETER; + parameter_data->parameter_id = parameter_id; + parameter_data->parameter_size = parameter_size; + put_unaligned_le32((u32)interval_time, ¶meter_data->value); + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, cmd_len, + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_app_resp_data, false); + if (error || resp_data[5] != parameter_id || + resp_data[6] != parameter_size || + !VALID_CMD_RESP_HEADER(resp_data, GEN5_CMD_SET_PARAMETER)) + return error < 0 ? error : -EINVAL; + + return 0; +} + +static int cyapa_gen5_get_interval_time(struct cyapa *cyapa, + u8 parameter_id, u16 *interval_time) +{ + struct pip_app_cmd_head *app_cmd_head; + struct gen5_app_get_parameter_data *parameter_data; + u8 cmd[CYAPA_TSG_MAX_CMD_SIZE]; + int cmd_len; + u8 resp_data[11]; + int resp_len; + u8 parameter_size; + u16 mask, i; + int error; + + memset(cmd, 0, CYAPA_TSG_MAX_CMD_SIZE); + app_cmd_head = (struct pip_app_cmd_head *)cmd; + parameter_data = (struct gen5_app_get_parameter_data *) + app_cmd_head->parameter_data; + cmd_len = sizeof(struct pip_app_cmd_head) + + sizeof(struct gen5_app_get_parameter_data); + + *interval_time = 0; + switch (parameter_id) { + case GEN5_PARAMETER_ACT_INTERVL_ID: + parameter_size = GEN5_PARAMETER_ACT_INTERVL_SIZE; + break; + case GEN5_PARAMETER_ACT_LFT_INTERVL_ID: + parameter_size = GEN5_PARAMETER_ACT_LFT_INTERVL_SIZE; + break; + case GEN5_PARAMETER_LP_INTRVL_ID: + parameter_size = GEN5_PARAMETER_LP_INTRVL_SIZE; + break; + default: + return -EINVAL; + } + + put_unaligned_le16(PIP_OUTPUT_REPORT_ADDR, &app_cmd_head->addr); + /* Don't include 2 bytes register address */ + put_unaligned_le16(cmd_len - 2, &app_cmd_head->length); + app_cmd_head->report_id = PIP_APP_CMD_REPORT_ID; + app_cmd_head->cmd_code = GEN5_CMD_GET_PARAMETER; + parameter_data->parameter_id = parameter_id; + + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, cmd_len, + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_app_resp_data, false); + if (error || resp_data[5] != parameter_id || resp_data[6] == 0 || + !VALID_CMD_RESP_HEADER(resp_data, GEN5_CMD_GET_PARAMETER)) + return error < 0 ? error : -EINVAL; + + mask = 0; + for (i = 0; i < parameter_size; i++) + mask |= (0xff << (i * 8)); + *interval_time = get_unaligned_le16(&resp_data[7]) & mask; + + return 0; +} + +static int cyapa_gen5_disable_pip_report(struct cyapa *cyapa) +{ + struct pip_app_cmd_head *app_cmd_head; + u8 cmd[10]; + u8 resp_data[7]; + int resp_len; + int error; + + memset(cmd, 0, sizeof(cmd)); + app_cmd_head = (struct pip_app_cmd_head *)cmd; + + put_unaligned_le16(PIP_OUTPUT_REPORT_ADDR, &app_cmd_head->addr); + put_unaligned_le16(sizeof(cmd) - 2, &app_cmd_head->length); + app_cmd_head->report_id = PIP_APP_CMD_REPORT_ID; + app_cmd_head->cmd_code = GEN5_CMD_SET_PARAMETER; + app_cmd_head->parameter_data[0] = GEN5_PARAMETER_DISABLE_PIP_REPORT; + app_cmd_head->parameter_data[1] = 0x01; + app_cmd_head->parameter_data[2] = 0x01; + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd), + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_app_resp_data, false); + if (error || resp_data[5] != GEN5_PARAMETER_DISABLE_PIP_REPORT || + !VALID_CMD_RESP_HEADER(resp_data, GEN5_CMD_SET_PARAMETER) || + resp_data[6] != 0x01) + return error < 0 ? error : -EINVAL; + + return 0; +} + +int cyapa_pip_set_proximity(struct cyapa *cyapa, bool enable) +{ + u8 cmd[] = { 0x04, 0x00, 0x06, 0x00, 0x2f, 0x00, PIP_SET_PROXIMITY, + (u8)!!enable + }; + u8 resp_data[6]; + int resp_len; + int error; + + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd), + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_app_resp_data, false); + if (error || !VALID_CMD_RESP_HEADER(resp_data, PIP_SET_PROXIMITY) || + !PIP_CMD_COMPLETE_SUCCESS(resp_data)) { + error = (error == -ETIMEDOUT) ? -EOPNOTSUPP : error; + return error < 0 ? error : -EINVAL; + } + + return 0; +} + +int cyapa_pip_deep_sleep(struct cyapa *cyapa, u8 state) +{ + u8 cmd[] = { 0x05, 0x00, 0x00, 0x08}; + u8 resp_data[5]; + int resp_len; + int error; + + cmd[2] = state & PIP_DEEP_SLEEP_STATE_MASK; + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd), + resp_data, &resp_len, + 500, cyapa_sort_pip_deep_sleep_data, false); + if (error || ((resp_data[3] & PIP_DEEP_SLEEP_STATE_MASK) != state)) + return -EINVAL; + + return 0; +} + +static int cyapa_gen5_set_power_mode(struct cyapa *cyapa, + u8 power_mode, u16 sleep_time, enum cyapa_pm_stage pm_stage) +{ + struct device *dev = &cyapa->client->dev; + u8 power_state; + int error = 0; + + if (cyapa->state != CYAPA_STATE_GEN5_APP) + return 0; + + cyapa_set_pip_pm_state(cyapa, pm_stage); + + if (PIP_DEV_GET_PWR_STATE(cyapa) == UNINIT_PWR_MODE) { + /* + * Assume TP in deep sleep mode when driver is loaded, + * avoid driver unload and reload command IO issue caused by TP + * has been set into deep sleep mode when unloading. + */ + PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_OFF); + } + + if (PIP_DEV_UNINIT_SLEEP_TIME(cyapa) && + PIP_DEV_GET_PWR_STATE(cyapa) != PWR_MODE_OFF) + if (cyapa_gen5_get_interval_time(cyapa, + GEN5_PARAMETER_LP_INTRVL_ID, + &cyapa->dev_sleep_time) != 0) + PIP_DEV_SET_SLEEP_TIME(cyapa, UNINIT_SLEEP_TIME); + + if (PIP_DEV_GET_PWR_STATE(cyapa) == power_mode) { + if (power_mode == PWR_MODE_OFF || + power_mode == PWR_MODE_FULL_ACTIVE || + power_mode == PWR_MODE_BTN_ONLY || + PIP_DEV_GET_SLEEP_TIME(cyapa) == sleep_time) { + /* Has in correct power mode state, early return. */ + goto out; + } + } + + if (power_mode == PWR_MODE_OFF) { + error = cyapa_pip_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_OFF); + if (error) { + dev_err(dev, "enter deep sleep fail: %d\n", error); + goto out; + } + + PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_OFF); + goto out; + } + + /* + * When trackpad in power off mode, it cannot change to other power + * state directly, must be wake up from sleep firstly, then + * continue to do next power sate change. + */ + if (PIP_DEV_GET_PWR_STATE(cyapa) == PWR_MODE_OFF) { + error = cyapa_pip_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_ON); + if (error) { + dev_err(dev, "deep sleep wake fail: %d\n", error); + goto out; + } + } + + if (power_mode == PWR_MODE_FULL_ACTIVE) { + error = cyapa_gen5_change_power_state(cyapa, + GEN5_POWER_STATE_ACTIVE); + if (error) { + dev_err(dev, "change to active fail: %d\n", error); + goto out; + } + + PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_FULL_ACTIVE); + } else if (power_mode == PWR_MODE_BTN_ONLY) { + error = cyapa_gen5_change_power_state(cyapa, + GEN5_POWER_STATE_BTN_ONLY); + if (error) { + dev_err(dev, "fail to button only mode: %d\n", error); + goto out; + } + + PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_BTN_ONLY); + } else { + /* + * Continue to change power mode even failed to set + * interval time, it won't affect the power mode change. + * except the sleep interval time is not correct. + */ + if (PIP_DEV_UNINIT_SLEEP_TIME(cyapa) || + sleep_time != PIP_DEV_GET_SLEEP_TIME(cyapa)) + if (cyapa_gen5_set_interval_time(cyapa, + GEN5_PARAMETER_LP_INTRVL_ID, + sleep_time) == 0) + PIP_DEV_SET_SLEEP_TIME(cyapa, sleep_time); + + if (sleep_time <= GEN5_POWER_READY_MAX_INTRVL_TIME) + power_state = GEN5_POWER_STATE_READY; + else + power_state = GEN5_POWER_STATE_IDLE; + error = cyapa_gen5_change_power_state(cyapa, power_state); + if (error) { + dev_err(dev, "set power state to 0x%02x failed: %d\n", + power_state, error); + goto out; + } + + /* + * Disable pip report for a little time, firmware will + * re-enable it automatically. It's used to fix the issue + * that trackpad unable to report signal to wake system up + * in the special situation that system is in suspending, and + * at the same time, user touch trackpad to wake system up. + * This function can avoid the data to be buffered when system + * is suspending which may cause interrupt line unable to be + * asserted again. + */ + if (pm_stage == CYAPA_PM_SUSPEND) + cyapa_gen5_disable_pip_report(cyapa); + + PIP_DEV_SET_PWR_STATE(cyapa, + cyapa_sleep_time_to_pwr_cmd(sleep_time)); + } + +out: + cyapa_reset_pip_pm_state(cyapa); + return error; +} + +int cyapa_pip_resume_scanning(struct cyapa *cyapa) +{ + u8 cmd[] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x04 }; + u8 resp_data[6]; + int resp_len; + int error; + + /* Try to dump all buffered data before doing command. */ + cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL); + + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + cmd, sizeof(cmd), + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_app_resp_data, true); + if (error || !VALID_CMD_RESP_HEADER(resp_data, 0x04)) + return -EINVAL; + + /* Try to dump all buffered data when resuming scanning. */ + cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL); + + return 0; +} + +int cyapa_pip_suspend_scanning(struct cyapa *cyapa) +{ + u8 cmd[] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x03 }; + u8 resp_data[6]; + int resp_len; + int error; + + /* Try to dump all buffered data before doing command. */ + cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL); + + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + cmd, sizeof(cmd), + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_app_resp_data, true); + if (error || !VALID_CMD_RESP_HEADER(resp_data, 0x03)) + return -EINVAL; + + /* Try to dump all buffered data when suspending scanning. */ + cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL); + + return 0; +} + +static int cyapa_pip_calibrate_pwcs(struct cyapa *cyapa, + u8 calibrate_sensing_mode_type) +{ + struct pip_app_cmd_head *app_cmd_head; + u8 cmd[8]; + u8 resp_data[6]; + int resp_len; + int error; + + /* Try to dump all buffered data before doing command. */ + cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL); + + memset(cmd, 0, sizeof(cmd)); + app_cmd_head = (struct pip_app_cmd_head *)cmd; + put_unaligned_le16(PIP_OUTPUT_REPORT_ADDR, &app_cmd_head->addr); + put_unaligned_le16(sizeof(cmd) - 2, &app_cmd_head->length); + app_cmd_head->report_id = PIP_APP_CMD_REPORT_ID; + app_cmd_head->cmd_code = PIP_CMD_CALIBRATE; + app_cmd_head->parameter_data[0] = calibrate_sensing_mode_type; + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + cmd, sizeof(cmd), + resp_data, &resp_len, + 5000, cyapa_sort_tsg_pip_app_resp_data, true); + if (error || !VALID_CMD_RESP_HEADER(resp_data, PIP_CMD_CALIBRATE) || + !PIP_CMD_COMPLETE_SUCCESS(resp_data)) + return error < 0 ? error : -EAGAIN; + + return 0; +} + +ssize_t cyapa_pip_do_calibrate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cyapa *cyapa = dev_get_drvdata(dev); + int error, calibrate_error; + + /* 1. Suspend Scanning*/ + error = cyapa_pip_suspend_scanning(cyapa); + if (error) + return error; + + /* 2. Do mutual capacitance fine calibrate. */ + calibrate_error = cyapa_pip_calibrate_pwcs(cyapa, + PIP_SENSING_MODE_MUTUAL_CAP_FINE); + if (calibrate_error) + goto resume_scanning; + + /* 3. Do self capacitance calibrate. */ + calibrate_error = cyapa_pip_calibrate_pwcs(cyapa, + PIP_SENSING_MODE_SELF_CAP); + if (calibrate_error) + goto resume_scanning; + +resume_scanning: + /* 4. Resume Scanning*/ + error = cyapa_pip_resume_scanning(cyapa); + if (error || calibrate_error) + return error ? error : calibrate_error; + + return count; +} + +static s32 twos_complement_to_s32(s32 value, int num_bits) +{ + if (value >> (num_bits - 1)) + value |= -1 << num_bits; + return value; +} + +static s32 cyapa_parse_structure_data(u8 data_format, u8 *buf, int buf_len) +{ + int data_size; + bool big_endian; + bool unsigned_type; + s32 value; + + data_size = (data_format & 0x07); + big_endian = ((data_format & 0x10) == 0x00); + unsigned_type = ((data_format & 0x20) == 0x00); + + if (buf_len < data_size) + return 0; + + switch (data_size) { + case 1: + value = buf[0]; + break; + case 2: + if (big_endian) + value = get_unaligned_be16(buf); + else + value = get_unaligned_le16(buf); + break; + case 4: + if (big_endian) + value = get_unaligned_be32(buf); + else + value = get_unaligned_le32(buf); + break; + default: + /* Should not happen, just as default case here. */ + value = 0; + break; + } + + if (!unsigned_type) + value = twos_complement_to_s32(value, data_size * 8); + + return value; +} + +static void cyapa_gen5_guess_electrodes(struct cyapa *cyapa, + int *electrodes_rx, int *electrodes_tx) +{ + if (cyapa->electrodes_rx != 0) { + *electrodes_rx = cyapa->electrodes_rx; + *electrodes_tx = (cyapa->electrodes_x == *electrodes_rx) ? + cyapa->electrodes_y : cyapa->electrodes_x; + } else { + *electrodes_tx = min(cyapa->electrodes_x, cyapa->electrodes_y); + *electrodes_rx = max(cyapa->electrodes_x, cyapa->electrodes_y); + } +} + +/* + * Read all the global mutual or self idac data or mutual or self local PWC + * data based on the @idac_data_type. + * If the input value of @data_size is 0, then means read global mutual or + * self idac data. For read global mutual idac data, @idac_max, @idac_min and + * @idac_ave are in order used to return the max value of global mutual idac + * data, the min value of global mutual idac and the average value of the + * global mutual idac data. For read global self idac data, @idac_max is used + * to return the global self cap idac data in Rx direction, @idac_min is used + * to return the global self cap idac data in Tx direction. @idac_ave is not + * used. + * If the input value of @data_size is not 0, than means read the mutual or + * self local PWC data. The @idac_max, @idac_min and @idac_ave are used to + * return the max, min and average value of the mutual or self local PWC data. + * Note, in order to read mutual local PWC data, must read invoke this function + * to read the mutual global idac data firstly to set the correct Rx number + * value, otherwise, the read mutual idac and PWC data may not correct. + */ +static int cyapa_gen5_read_idac_data(struct cyapa *cyapa, + u8 cmd_code, u8 idac_data_type, int *data_size, + int *idac_max, int *idac_min, int *idac_ave) +{ + struct pip_app_cmd_head *cmd_head; + u8 cmd[12]; + u8 resp_data[256]; + int resp_len; + int read_len; + int value; + u16 offset; + int read_elements; + bool read_global_idac; + int sum, count, max_element_cnt; + int tmp_max, tmp_min, tmp_ave, tmp_sum, tmp_count; + int electrodes_rx, electrodes_tx; + int i; + int error; + + if (cmd_code != PIP_RETRIEVE_DATA_STRUCTURE || + (idac_data_type != GEN5_RETRIEVE_MUTUAL_PWC_DATA && + idac_data_type != GEN5_RETRIEVE_SELF_CAP_PWC_DATA) || + !data_size || !idac_max || !idac_min || !idac_ave) + return -EINVAL; + + *idac_max = INT_MIN; + *idac_min = INT_MAX; + sum = count = tmp_count = 0; + electrodes_rx = electrodes_tx = 0; + if (*data_size == 0) { + /* + * Read global idac values firstly. + * Currently, no idac data exceed 4 bytes. + */ + read_global_idac = true; + offset = 0; + *data_size = 4; + tmp_max = INT_MIN; + tmp_min = INT_MAX; + tmp_ave = tmp_sum = tmp_count = 0; + + if (idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA) { + if (cyapa->aligned_electrodes_rx == 0) { + cyapa_gen5_guess_electrodes(cyapa, + &electrodes_rx, &electrodes_tx); + cyapa->aligned_electrodes_rx = + (electrodes_rx + 3) & ~3u; + } + max_element_cnt = + (cyapa->aligned_electrodes_rx + 7) & ~7u; + } else { + max_element_cnt = 2; + } + } else { + read_global_idac = false; + if (*data_size > 4) + *data_size = 4; + /* Calculate the start offset in bytes of local PWC data. */ + if (idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA) { + offset = cyapa->aligned_electrodes_rx * (*data_size); + if (cyapa->electrodes_rx == cyapa->electrodes_x) + electrodes_tx = cyapa->electrodes_y; + else + electrodes_tx = cyapa->electrodes_x; + max_element_cnt = ((cyapa->aligned_electrodes_rx + 7) & + ~7u) * electrodes_tx; + } else { + offset = 2; + max_element_cnt = cyapa->electrodes_x + + cyapa->electrodes_y; + max_element_cnt = (max_element_cnt + 3) & ~3u; + } + } + + memset(cmd, 0, sizeof(cmd)); + cmd_head = (struct pip_app_cmd_head *)cmd; + put_unaligned_le16(PIP_OUTPUT_REPORT_ADDR, &cmd_head->addr); + put_unaligned_le16(sizeof(cmd) - 2, &cmd_head->length); + cmd_head->report_id = PIP_APP_CMD_REPORT_ID; + cmd_head->cmd_code = cmd_code; + do { + read_elements = (256 - GEN5_RESP_DATA_STRUCTURE_OFFSET) / + (*data_size); + read_elements = min(read_elements, max_element_cnt - count); + read_len = read_elements * (*data_size); + + put_unaligned_le16(offset, &cmd_head->parameter_data[0]); + put_unaligned_le16(read_len, &cmd_head->parameter_data[2]); + cmd_head->parameter_data[4] = idac_data_type; + resp_len = GEN5_RESP_DATA_STRUCTURE_OFFSET + read_len; + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + cmd, sizeof(cmd), + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_app_resp_data, + true); + if (error || resp_len < GEN5_RESP_DATA_STRUCTURE_OFFSET || + !VALID_CMD_RESP_HEADER(resp_data, cmd_code) || + !PIP_CMD_COMPLETE_SUCCESS(resp_data) || + resp_data[6] != idac_data_type) + return (error < 0) ? error : -EAGAIN; + read_len = get_unaligned_le16(&resp_data[7]); + if (read_len == 0) + break; + + *data_size = (resp_data[9] & GEN5_PWC_DATA_ELEMENT_SIZE_MASK); + if (read_len < *data_size) + return -EINVAL; + + if (read_global_idac && + idac_data_type == GEN5_RETRIEVE_SELF_CAP_PWC_DATA) { + /* Rx's self global idac data. */ + *idac_max = cyapa_parse_structure_data( + resp_data[9], + &resp_data[GEN5_RESP_DATA_STRUCTURE_OFFSET], + *data_size); + /* Tx's self global idac data. */ + *idac_min = cyapa_parse_structure_data( + resp_data[9], + &resp_data[GEN5_RESP_DATA_STRUCTURE_OFFSET + + *data_size], + *data_size); + break; + } + + /* Read mutual global idac or local mutual/self PWC data. */ + offset += read_len; + for (i = 10; i < (read_len + GEN5_RESP_DATA_STRUCTURE_OFFSET); + i += *data_size) { + value = cyapa_parse_structure_data(resp_data[9], + &resp_data[i], *data_size); + *idac_min = min(value, *idac_min); + *idac_max = max(value, *idac_max); + + if (idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA && + tmp_count < cyapa->aligned_electrodes_rx && + read_global_idac) { + /* + * The value gap between global and local mutual + * idac data must bigger than 50%. + * Normally, global value bigger than 50, + * local values less than 10. + */ + if (!tmp_ave || value > tmp_ave / 2) { + tmp_min = min(value, tmp_min); + tmp_max = max(value, tmp_max); + tmp_sum += value; + tmp_count++; + + tmp_ave = tmp_sum / tmp_count; + } + } + + sum += value; + count++; + + if (count >= max_element_cnt) + goto out; + } + } while (true); + +out: + *idac_ave = count ? (sum / count) : 0; + + if (read_global_idac && + idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA) { + if (tmp_count == 0) + return 0; + + if (tmp_count == cyapa->aligned_electrodes_rx) { + cyapa->electrodes_rx = cyapa->electrodes_rx ? + cyapa->electrodes_rx : electrodes_rx; + } else if (tmp_count == electrodes_rx) { + cyapa->electrodes_rx = cyapa->electrodes_rx ? + cyapa->electrodes_rx : electrodes_rx; + cyapa->aligned_electrodes_rx = electrodes_rx; + } else { + cyapa->electrodes_rx = cyapa->electrodes_rx ? + cyapa->electrodes_rx : electrodes_tx; + cyapa->aligned_electrodes_rx = tmp_count; + } + + *idac_min = tmp_min; + *idac_max = tmp_max; + *idac_ave = tmp_ave; + } + + return 0; +} + +static int cyapa_gen5_read_mutual_idac_data(struct cyapa *cyapa, + int *gidac_mutual_max, int *gidac_mutual_min, int *gidac_mutual_ave, + int *lidac_mutual_max, int *lidac_mutual_min, int *lidac_mutual_ave) +{ + int data_size; + int error; + + *gidac_mutual_max = *gidac_mutual_min = *gidac_mutual_ave = 0; + *lidac_mutual_max = *lidac_mutual_min = *lidac_mutual_ave = 0; + + data_size = 0; + error = cyapa_gen5_read_idac_data(cyapa, + PIP_RETRIEVE_DATA_STRUCTURE, + GEN5_RETRIEVE_MUTUAL_PWC_DATA, + &data_size, + gidac_mutual_max, gidac_mutual_min, gidac_mutual_ave); + if (error) + return error; + + error = cyapa_gen5_read_idac_data(cyapa, + PIP_RETRIEVE_DATA_STRUCTURE, + GEN5_RETRIEVE_MUTUAL_PWC_DATA, + &data_size, + lidac_mutual_max, lidac_mutual_min, lidac_mutual_ave); + return error; +} + +static int cyapa_gen5_read_self_idac_data(struct cyapa *cyapa, + int *gidac_self_rx, int *gidac_self_tx, + int *lidac_self_max, int *lidac_self_min, int *lidac_self_ave) +{ + int data_size; + int error; + + *gidac_self_rx = *gidac_self_tx = 0; + *lidac_self_max = *lidac_self_min = *lidac_self_ave = 0; + + data_size = 0; + error = cyapa_gen5_read_idac_data(cyapa, + PIP_RETRIEVE_DATA_STRUCTURE, + GEN5_RETRIEVE_SELF_CAP_PWC_DATA, + &data_size, + lidac_self_max, lidac_self_min, lidac_self_ave); + if (error) + return error; + *gidac_self_rx = *lidac_self_max; + *gidac_self_tx = *lidac_self_min; + + error = cyapa_gen5_read_idac_data(cyapa, + PIP_RETRIEVE_DATA_STRUCTURE, + GEN5_RETRIEVE_SELF_CAP_PWC_DATA, + &data_size, + lidac_self_max, lidac_self_min, lidac_self_ave); + return error; +} + +static ssize_t cyapa_gen5_execute_panel_scan(struct cyapa *cyapa) +{ + struct pip_app_cmd_head *app_cmd_head; + u8 cmd[7]; + u8 resp_data[6]; + int resp_len; + int error; + + memset(cmd, 0, sizeof(cmd)); + app_cmd_head = (struct pip_app_cmd_head *)cmd; + put_unaligned_le16(PIP_OUTPUT_REPORT_ADDR, &app_cmd_head->addr); + put_unaligned_le16(sizeof(cmd) - 2, &app_cmd_head->length); + app_cmd_head->report_id = PIP_APP_CMD_REPORT_ID; + app_cmd_head->cmd_code = GEN5_CMD_EXECUTE_PANEL_SCAN; + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + cmd, sizeof(cmd), + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_app_resp_data, true); + if (error || resp_len != sizeof(resp_data) || + !VALID_CMD_RESP_HEADER(resp_data, + GEN5_CMD_EXECUTE_PANEL_SCAN) || + !PIP_CMD_COMPLETE_SUCCESS(resp_data)) + return error ? error : -EAGAIN; + + return 0; +} + +static int cyapa_gen5_read_panel_scan_raw_data(struct cyapa *cyapa, + u8 cmd_code, u8 raw_data_type, int raw_data_max_num, + int *raw_data_max, int *raw_data_min, int *raw_data_ave, + u8 *buffer) +{ + struct pip_app_cmd_head *app_cmd_head; + struct gen5_retrieve_panel_scan_data *panel_sacn_data; + u8 cmd[12]; + u8 resp_data[256]; /* Max bytes can transfer one time. */ + int resp_len; + int read_elements; + int read_len; + u16 offset; + s32 value; + int sum, count; + int data_size; + s32 *intp; + int i; + int error; + + if (cmd_code != GEN5_CMD_RETRIEVE_PANEL_SCAN || + (raw_data_type > GEN5_PANEL_SCAN_SELF_DIFFCOUNT) || + !raw_data_max || !raw_data_min || !raw_data_ave) + return -EINVAL; + + intp = (s32 *)buffer; + *raw_data_max = INT_MIN; + *raw_data_min = INT_MAX; + sum = count = 0; + offset = 0; + /* Assume max element size is 4 currently. */ + read_elements = (256 - GEN5_RESP_DATA_STRUCTURE_OFFSET) / 4; + read_len = read_elements * 4; + app_cmd_head = (struct pip_app_cmd_head *)cmd; + put_unaligned_le16(PIP_OUTPUT_REPORT_ADDR, &app_cmd_head->addr); + put_unaligned_le16(sizeof(cmd) - 2, &app_cmd_head->length); + app_cmd_head->report_id = PIP_APP_CMD_REPORT_ID; + app_cmd_head->cmd_code = cmd_code; + panel_sacn_data = (struct gen5_retrieve_panel_scan_data *) + app_cmd_head->parameter_data; + do { + put_unaligned_le16(offset, &panel_sacn_data->read_offset); + put_unaligned_le16(read_elements, + &panel_sacn_data->read_elements); + panel_sacn_data->data_id = raw_data_type; + + resp_len = GEN5_RESP_DATA_STRUCTURE_OFFSET + read_len; + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + cmd, sizeof(cmd), + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_app_resp_data, true); + if (error || resp_len < GEN5_RESP_DATA_STRUCTURE_OFFSET || + !VALID_CMD_RESP_HEADER(resp_data, cmd_code) || + !PIP_CMD_COMPLETE_SUCCESS(resp_data) || + resp_data[6] != raw_data_type) + return error ? error : -EAGAIN; + + read_elements = get_unaligned_le16(&resp_data[7]); + if (read_elements == 0) + break; + + data_size = (resp_data[9] & GEN5_PWC_DATA_ELEMENT_SIZE_MASK); + offset += read_elements; + if (read_elements) { + for (i = GEN5_RESP_DATA_STRUCTURE_OFFSET; + i < (read_elements * data_size + + GEN5_RESP_DATA_STRUCTURE_OFFSET); + i += data_size) { + value = cyapa_parse_structure_data(resp_data[9], + &resp_data[i], data_size); + *raw_data_min = min(value, *raw_data_min); + *raw_data_max = max(value, *raw_data_max); + + if (intp) + put_unaligned_le32(value, &intp[count]); + + sum += value; + count++; + + } + } + + if (count >= raw_data_max_num) + break; + + read_elements = (sizeof(resp_data) - + GEN5_RESP_DATA_STRUCTURE_OFFSET) / data_size; + read_len = read_elements * data_size; + } while (true); + + *raw_data_ave = count ? (sum / count) : 0; + + return 0; +} + +static ssize_t cyapa_gen5_show_baseline(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyapa *cyapa = dev_get_drvdata(dev); + int gidac_mutual_max, gidac_mutual_min, gidac_mutual_ave; + int lidac_mutual_max, lidac_mutual_min, lidac_mutual_ave; + int gidac_self_rx, gidac_self_tx; + int lidac_self_max, lidac_self_min, lidac_self_ave; + int raw_cap_mutual_max, raw_cap_mutual_min, raw_cap_mutual_ave; + int raw_cap_self_max, raw_cap_self_min, raw_cap_self_ave; + int mutual_diffdata_max, mutual_diffdata_min, mutual_diffdata_ave; + int self_diffdata_max, self_diffdata_min, self_diffdata_ave; + int mutual_baseline_max, mutual_baseline_min, mutual_baseline_ave; + int self_baseline_max, self_baseline_min, self_baseline_ave; + int error, resume_error; + int size; + + if (!cyapa_is_pip_app_mode(cyapa)) + return -EBUSY; + + /* 1. Suspend Scanning*/ + error = cyapa_pip_suspend_scanning(cyapa); + if (error) + return error; + + /* 2. Read global and local mutual IDAC data. */ + gidac_self_rx = gidac_self_tx = 0; + error = cyapa_gen5_read_mutual_idac_data(cyapa, + &gidac_mutual_max, &gidac_mutual_min, + &gidac_mutual_ave, &lidac_mutual_max, + &lidac_mutual_min, &lidac_mutual_ave); + if (error) + goto resume_scanning; + + /* 3. Read global and local self IDAC data. */ + error = cyapa_gen5_read_self_idac_data(cyapa, + &gidac_self_rx, &gidac_self_tx, + &lidac_self_max, &lidac_self_min, + &lidac_self_ave); + if (error) + goto resume_scanning; + + /* 4. Execute panel scan. It must be executed before read data. */ + error = cyapa_gen5_execute_panel_scan(cyapa); + if (error) + goto resume_scanning; + + /* 5. Retrieve panel scan, mutual cap raw data. */ + error = cyapa_gen5_read_panel_scan_raw_data(cyapa, + GEN5_CMD_RETRIEVE_PANEL_SCAN, + GEN5_PANEL_SCAN_MUTUAL_RAW_DATA, + cyapa->electrodes_x * cyapa->electrodes_y, + &raw_cap_mutual_max, &raw_cap_mutual_min, + &raw_cap_mutual_ave, + NULL); + if (error) + goto resume_scanning; + + /* 6. Retrieve panel scan, self cap raw data. */ + error = cyapa_gen5_read_panel_scan_raw_data(cyapa, + GEN5_CMD_RETRIEVE_PANEL_SCAN, + GEN5_PANEL_SCAN_SELF_RAW_DATA, + cyapa->electrodes_x + cyapa->electrodes_y, + &raw_cap_self_max, &raw_cap_self_min, + &raw_cap_self_ave, + NULL); + if (error) + goto resume_scanning; + + /* 7. Retrieve panel scan, mutual cap diffcount raw data. */ + error = cyapa_gen5_read_panel_scan_raw_data(cyapa, + GEN5_CMD_RETRIEVE_PANEL_SCAN, + GEN5_PANEL_SCAN_MUTUAL_DIFFCOUNT, + cyapa->electrodes_x * cyapa->electrodes_y, + &mutual_diffdata_max, &mutual_diffdata_min, + &mutual_diffdata_ave, + NULL); + if (error) + goto resume_scanning; + + /* 8. Retrieve panel scan, self cap diffcount raw data. */ + error = cyapa_gen5_read_panel_scan_raw_data(cyapa, + GEN5_CMD_RETRIEVE_PANEL_SCAN, + GEN5_PANEL_SCAN_SELF_DIFFCOUNT, + cyapa->electrodes_x + cyapa->electrodes_y, + &self_diffdata_max, &self_diffdata_min, + &self_diffdata_ave, + NULL); + if (error) + goto resume_scanning; + + /* 9. Retrieve panel scan, mutual cap baseline raw data. */ + error = cyapa_gen5_read_panel_scan_raw_data(cyapa, + GEN5_CMD_RETRIEVE_PANEL_SCAN, + GEN5_PANEL_SCAN_MUTUAL_BASELINE, + cyapa->electrodes_x * cyapa->electrodes_y, + &mutual_baseline_max, &mutual_baseline_min, + &mutual_baseline_ave, + NULL); + if (error) + goto resume_scanning; + + /* 10. Retrieve panel scan, self cap baseline raw data. */ + error = cyapa_gen5_read_panel_scan_raw_data(cyapa, + GEN5_CMD_RETRIEVE_PANEL_SCAN, + GEN5_PANEL_SCAN_SELF_BASELINE, + cyapa->electrodes_x + cyapa->electrodes_y, + &self_baseline_max, &self_baseline_min, + &self_baseline_ave, + NULL); + if (error) + goto resume_scanning; + +resume_scanning: + /* 11. Resume Scanning*/ + resume_error = cyapa_pip_resume_scanning(cyapa); + if (resume_error || error) + return resume_error ? resume_error : error; + + /* 12. Output data strings */ + size = scnprintf(buf, PAGE_SIZE, "%d %d %d %d %d %d %d %d %d %d %d ", + gidac_mutual_min, gidac_mutual_max, gidac_mutual_ave, + lidac_mutual_min, lidac_mutual_max, lidac_mutual_ave, + gidac_self_rx, gidac_self_tx, + lidac_self_min, lidac_self_max, lidac_self_ave); + size += scnprintf(buf + size, PAGE_SIZE - size, + "%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n", + raw_cap_mutual_min, raw_cap_mutual_max, raw_cap_mutual_ave, + raw_cap_self_min, raw_cap_self_max, raw_cap_self_ave, + mutual_diffdata_min, mutual_diffdata_max, mutual_diffdata_ave, + self_diffdata_min, self_diffdata_max, self_diffdata_ave, + mutual_baseline_min, mutual_baseline_max, mutual_baseline_ave, + self_baseline_min, self_baseline_max, self_baseline_ave); + return size; +} + +bool cyapa_pip_sort_system_info_data(struct cyapa *cyapa, + u8 *buf, int len) +{ + /* Check the report id and command code */ + if (VALID_CMD_RESP_HEADER(buf, 0x02)) + return true; + + return false; +} + +static int cyapa_gen5_bl_query_data(struct cyapa *cyapa) +{ + u8 resp_data[PIP_BL_APP_INFO_RESP_LENGTH]; + int resp_len; + int error; + + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + pip_bl_read_app_info, PIP_BL_READ_APP_INFO_CMD_LENGTH, + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_bl_resp_data, false); + if (error || resp_len < PIP_BL_APP_INFO_RESP_LENGTH || + !PIP_CMD_COMPLETE_SUCCESS(resp_data)) + return error ? error : -EIO; + + memcpy(&cyapa->product_id[0], &resp_data[8], 5); + cyapa->product_id[5] = '-'; + memcpy(&cyapa->product_id[6], &resp_data[13], 6); + cyapa->product_id[12] = '-'; + memcpy(&cyapa->product_id[13], &resp_data[19], 2); + cyapa->product_id[15] = '\0'; + + cyapa->fw_maj_ver = resp_data[22]; + cyapa->fw_min_ver = resp_data[23]; + + cyapa->platform_ver = (resp_data[26] >> PIP_BL_PLATFORM_VER_SHIFT) & + PIP_BL_PLATFORM_VER_MASK; + + return 0; +} + +static int cyapa_gen5_get_query_data(struct cyapa *cyapa) +{ + u8 resp_data[PIP_READ_SYS_INFO_RESP_LENGTH]; + int resp_len; + u16 product_family; + int error; + + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + pip_read_sys_info, PIP_READ_SYS_INFO_CMD_LENGTH, + resp_data, &resp_len, + 2000, cyapa_pip_sort_system_info_data, false); + if (error || resp_len < sizeof(resp_data)) + return error ? error : -EIO; + + product_family = get_unaligned_le16(&resp_data[7]); + if ((product_family & PIP_PRODUCT_FAMILY_MASK) != + PIP_PRODUCT_FAMILY_TRACKPAD) + return -EINVAL; + + cyapa->platform_ver = (resp_data[49] >> PIP_BL_PLATFORM_VER_SHIFT) & + PIP_BL_PLATFORM_VER_MASK; + if (cyapa->gen == CYAPA_GEN5 && cyapa->platform_ver < 2) { + /* Gen5 firmware that does not support proximity. */ + cyapa->fw_maj_ver = resp_data[15]; + cyapa->fw_min_ver = resp_data[16]; + } else { + cyapa->fw_maj_ver = resp_data[9]; + cyapa->fw_min_ver = resp_data[10]; + } + + cyapa->electrodes_x = resp_data[52]; + cyapa->electrodes_y = resp_data[53]; + + cyapa->physical_size_x = get_unaligned_le16(&resp_data[54]) / 100; + cyapa->physical_size_y = get_unaligned_le16(&resp_data[56]) / 100; + + cyapa->max_abs_x = get_unaligned_le16(&resp_data[58]); + cyapa->max_abs_y = get_unaligned_le16(&resp_data[60]); + + cyapa->max_z = get_unaligned_le16(&resp_data[62]); + + cyapa->x_origin = resp_data[64] & 0x01; + cyapa->y_origin = resp_data[65] & 0x01; + + cyapa->btn_capability = (resp_data[70] << 3) & CAPABILITY_BTN_MASK; + + memcpy(&cyapa->product_id[0], &resp_data[33], 5); + cyapa->product_id[5] = '-'; + memcpy(&cyapa->product_id[6], &resp_data[38], 6); + cyapa->product_id[12] = '-'; + memcpy(&cyapa->product_id[13], &resp_data[44], 2); + cyapa->product_id[15] = '\0'; + + if (!cyapa->electrodes_x || !cyapa->electrodes_y || + !cyapa->physical_size_x || !cyapa->physical_size_y || + !cyapa->max_abs_x || !cyapa->max_abs_y || !cyapa->max_z) + return -EINVAL; + + return 0; +} + +static int cyapa_gen5_do_operational_check(struct cyapa *cyapa) +{ + struct device *dev = &cyapa->client->dev; + int error; + + if (cyapa->gen != CYAPA_GEN5) + return -ENODEV; + + switch (cyapa->state) { + case CYAPA_STATE_GEN5_BL: + error = cyapa_pip_bl_exit(cyapa); + if (error) { + /* Try to update trackpad product information. */ + cyapa_gen5_bl_query_data(cyapa); + goto out; + } + + cyapa->state = CYAPA_STATE_GEN5_APP; + /* fall through */ + + case CYAPA_STATE_GEN5_APP: + /* + * If trackpad device in deep sleep mode, + * the app command will fail. + * So always try to reset trackpad device to full active when + * the device state is required. + */ + error = cyapa_gen5_set_power_mode(cyapa, + PWR_MODE_FULL_ACTIVE, 0, CYAPA_PM_ACTIVE); + if (error) + dev_warn(dev, "%s: failed to set power active mode.\n", + __func__); + + /* By default, the trackpad proximity function is enabled. */ + if (cyapa->platform_ver >= 2) { + error = cyapa_pip_set_proximity(cyapa, true); + if (error) + dev_warn(dev, + "%s: failed to enable proximity.\n", + __func__); + } + + /* Get trackpad product information. */ + error = cyapa_gen5_get_query_data(cyapa); + if (error) + goto out; + /* Only support product ID starting with CYTRA */ + if (memcmp(cyapa->product_id, product_id, + strlen(product_id)) != 0) { + dev_err(dev, "%s: unknown product ID (%s)\n", + __func__, cyapa->product_id); + error = -EINVAL; + } + break; + default: + error = -EINVAL; + } + +out: + return error; +} + +/* + * Return false, do not continue process + * Return true, continue process. + */ +bool cyapa_pip_irq_cmd_handler(struct cyapa *cyapa) +{ + struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip; + int length; + + if (atomic_read(&pip->cmd_issued)) { + /* Polling command response data. */ + if (pip->is_irq_mode == false) + return false; + + /* + * Read out all none command response data. + * these output data may caused by user put finger on + * trackpad when host waiting the command response. + */ + cyapa_i2c_pip_read(cyapa, pip->irq_cmd_buf, + PIP_RESP_LENGTH_SIZE); + length = get_unaligned_le16(pip->irq_cmd_buf); + length = (length <= PIP_RESP_LENGTH_SIZE) ? + PIP_RESP_LENGTH_SIZE : length; + if (length > PIP_RESP_LENGTH_SIZE) + cyapa_i2c_pip_read(cyapa, + pip->irq_cmd_buf, length); + if (!(pip->resp_sort_func && + pip->resp_sort_func(cyapa, + pip->irq_cmd_buf, length))) { + /* + * Cover the Gen5 V1 firmware issue. + * The issue is no interrupt would be asserted from + * trackpad device to host for the command response + * ready event. Because when there was a finger touch + * on trackpad device, and the firmware output queue + * won't be empty (always with touch report data), so + * the interrupt signal won't be asserted again until + * the output queue was previous emptied. + * This issue would happen in the scenario that + * user always has his/her fingers touched on the + * trackpad device during system booting/rebooting. + */ + length = 0; + if (pip->resp_len) + length = *pip->resp_len; + cyapa_empty_pip_output_data(cyapa, + pip->resp_data, + &length, + pip->resp_sort_func); + if (pip->resp_len && length != 0) { + *pip->resp_len = length; + atomic_dec(&pip->cmd_issued); + complete(&pip->cmd_ready); + } + return false; + } + + if (pip->resp_data && pip->resp_len) { + *pip->resp_len = (*pip->resp_len < length) ? + *pip->resp_len : length; + memcpy(pip->resp_data, pip->irq_cmd_buf, + *pip->resp_len); + } + atomic_dec(&pip->cmd_issued); + complete(&pip->cmd_ready); + return false; + } + + return true; +} + +static void cyapa_pip_report_buttons(struct cyapa *cyapa, + const struct cyapa_pip_report_data *report_data) +{ + struct input_dev *input = cyapa->input; + u8 buttons = report_data->report_head[PIP_BUTTONS_OFFSET]; + + buttons = (buttons << CAPABILITY_BTN_SHIFT) & CAPABILITY_BTN_MASK; + + if (cyapa->btn_capability & CAPABILITY_LEFT_BTN_MASK) { + input_report_key(input, BTN_LEFT, + !!(buttons & CAPABILITY_LEFT_BTN_MASK)); + } + if (cyapa->btn_capability & CAPABILITY_MIDDLE_BTN_MASK) { + input_report_key(input, BTN_MIDDLE, + !!(buttons & CAPABILITY_MIDDLE_BTN_MASK)); + } + if (cyapa->btn_capability & CAPABILITY_RIGHT_BTN_MASK) { + input_report_key(input, BTN_RIGHT, + !!(buttons & CAPABILITY_RIGHT_BTN_MASK)); + } + + input_sync(input); +} + +static void cyapa_pip_report_proximity(struct cyapa *cyapa, + const struct cyapa_pip_report_data *report_data) +{ + struct input_dev *input = cyapa->input; + u8 distance = report_data->report_head[PIP_PROXIMITY_DISTANCE_OFFSET] & + PIP_PROXIMITY_DISTANCE_MASK; + + input_report_abs(input, ABS_DISTANCE, distance); + input_sync(input); +} + +static void cyapa_pip_report_slot_data(struct cyapa *cyapa, + const struct cyapa_pip_touch_record *touch) +{ + struct input_dev *input = cyapa->input; + u8 event_id = PIP_GET_EVENT_ID(touch->touch_tip_event_id); + int slot = PIP_GET_TOUCH_ID(touch->touch_tip_event_id); + int x, y; + + if (event_id == RECORD_EVENT_LIFTOFF) + return; + + input_mt_slot(input, slot); + input_mt_report_slot_state(input, MT_TOOL_FINGER, true); + x = (touch->x_hi << 8) | touch->x_lo; + if (cyapa->x_origin) + x = cyapa->max_abs_x - x; + y = (touch->y_hi << 8) | touch->y_lo; + if (cyapa->y_origin) + y = cyapa->max_abs_y - y; + input_report_abs(input, ABS_MT_POSITION_X, x); + input_report_abs(input, ABS_MT_POSITION_Y, y); + input_report_abs(input, ABS_DISTANCE, 0); + input_report_abs(input, ABS_MT_PRESSURE, + touch->z); + input_report_abs(input, ABS_MT_TOUCH_MAJOR, + touch->major_axis_len); + input_report_abs(input, ABS_MT_TOUCH_MINOR, + touch->minor_axis_len); + + input_report_abs(input, ABS_MT_WIDTH_MAJOR, + touch->major_tool_len); + input_report_abs(input, ABS_MT_WIDTH_MINOR, + touch->minor_tool_len); + + input_report_abs(input, ABS_MT_ORIENTATION, + touch->orientation); +} + +static void cyapa_pip_report_touches(struct cyapa *cyapa, + const struct cyapa_pip_report_data *report_data) +{ + struct input_dev *input = cyapa->input; + unsigned int touch_num; + int i; + + touch_num = report_data->report_head[PIP_NUMBER_OF_TOUCH_OFFSET] & + PIP_NUMBER_OF_TOUCH_MASK; + + for (i = 0; i < touch_num; i++) + cyapa_pip_report_slot_data(cyapa, + &report_data->touch_records[i]); + + input_mt_sync_frame(input); + input_sync(input); +} + +int cyapa_pip_irq_handler(struct cyapa *cyapa) +{ + struct device *dev = &cyapa->client->dev; + struct cyapa_pip_report_data report_data; + unsigned int report_len; + int ret; + + if (!cyapa_is_pip_app_mode(cyapa)) { + dev_err(dev, "invalid device state, gen=%d, state=0x%02x\n", + cyapa->gen, cyapa->state); + return -EINVAL; + } + + ret = cyapa_i2c_pip_read(cyapa, (u8 *)&report_data, + PIP_RESP_LENGTH_SIZE); + if (ret != PIP_RESP_LENGTH_SIZE) { + dev_err(dev, "failed to read length bytes, (%d)\n", ret); + return -EINVAL; + } + + report_len = get_unaligned_le16( + &report_data.report_head[PIP_RESP_LENGTH_OFFSET]); + if (report_len < PIP_RESP_LENGTH_SIZE) { + /* Invalid length or internal reset happened. */ + dev_err(dev, "invalid report_len=%d. bytes: %02x %02x\n", + report_len, report_data.report_head[0], + report_data.report_head[1]); + return -EINVAL; + } + + /* Idle, no data for report. */ + if (report_len == PIP_RESP_LENGTH_SIZE) + return 0; + + ret = cyapa_i2c_pip_read(cyapa, (u8 *)&report_data, report_len); + if (ret != report_len) { + dev_err(dev, "failed to read %d bytes report data, (%d)\n", + report_len, ret); + return -EINVAL; + } + + return cyapa_pip_event_process(cyapa, &report_data); +} + +static int cyapa_pip_event_process(struct cyapa *cyapa, + struct cyapa_pip_report_data *report_data) +{ + struct device *dev = &cyapa->client->dev; + unsigned int report_len; + u8 report_id; + + report_len = get_unaligned_le16( + &report_data->report_head[PIP_RESP_LENGTH_OFFSET]); + /* Idle, no data for report. */ + if (report_len == PIP_RESP_LENGTH_SIZE) + return 0; + + report_id = report_data->report_head[PIP_RESP_REPORT_ID_OFFSET]; + if (report_id == PIP_WAKEUP_EVENT_REPORT_ID && + report_len == PIP_WAKEUP_EVENT_SIZE) { + /* + * Device wake event from deep sleep mode for touch. + * This interrupt event is used to wake system up. + * + * Note: + * It will introduce about 20~40 ms additional delay + * time in receiving for first valid touch report data. + * The time is used to execute device runtime resume + * process. + */ + pm_runtime_get_sync(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_sync_autosuspend(dev); + return 0; + } else if (report_id != PIP_TOUCH_REPORT_ID && + report_id != PIP_BTN_REPORT_ID && + report_id != GEN5_OLD_PUSH_BTN_REPORT_ID && + report_id != PIP_PUSH_BTN_REPORT_ID && + report_id != PIP_PROXIMITY_REPORT_ID) { + /* Running in BL mode or unknown response data read. */ + dev_err(dev, "invalid report_id=0x%02x\n", report_id); + return -EINVAL; + } + + if (report_id == PIP_TOUCH_REPORT_ID && + (report_len < PIP_TOUCH_REPORT_HEAD_SIZE || + report_len > PIP_TOUCH_REPORT_MAX_SIZE)) { + /* Invalid report data length for finger packet. */ + dev_err(dev, "invalid touch packet length=%d\n", report_len); + return 0; + } + + if ((report_id == PIP_BTN_REPORT_ID || + report_id == GEN5_OLD_PUSH_BTN_REPORT_ID || + report_id == PIP_PUSH_BTN_REPORT_ID) && + (report_len < PIP_BTN_REPORT_HEAD_SIZE || + report_len > PIP_BTN_REPORT_MAX_SIZE)) { + /* Invalid report data length of button packet. */ + dev_err(dev, "invalid button packet length=%d\n", report_len); + return 0; + } + + if (report_id == PIP_PROXIMITY_REPORT_ID && + report_len != PIP_PROXIMITY_REPORT_SIZE) { + /* Invalid report data length of proximity packet. */ + dev_err(dev, "invalid proximity data, length=%d\n", report_len); + return 0; + } + + if (report_id == PIP_TOUCH_REPORT_ID) + cyapa_pip_report_touches(cyapa, report_data); + else if (report_id == PIP_PROXIMITY_REPORT_ID) + cyapa_pip_report_proximity(cyapa, report_data); + else + cyapa_pip_report_buttons(cyapa, report_data); + + return 0; +} + +int cyapa_pip_bl_activate(struct cyapa *cyapa) { return 0; } +int cyapa_pip_bl_deactivate(struct cyapa *cyapa) { return 0; } + + +const struct cyapa_dev_ops cyapa_gen5_ops = { + .check_fw = cyapa_pip_check_fw, + .bl_enter = cyapa_pip_bl_enter, + .bl_initiate = cyapa_pip_bl_initiate, + .update_fw = cyapa_pip_do_fw_update, + .bl_activate = cyapa_pip_bl_activate, + .bl_deactivate = cyapa_pip_bl_deactivate, + + .show_baseline = cyapa_gen5_show_baseline, + .calibrate_store = cyapa_pip_do_calibrate, + + .initialize = cyapa_pip_cmd_state_initialize, + + .state_parse = cyapa_gen5_state_parse, + .operational_check = cyapa_gen5_do_operational_check, + + .irq_handler = cyapa_pip_irq_handler, + .irq_cmd_handler = cyapa_pip_irq_cmd_handler, + .sort_empty_output_data = cyapa_empty_pip_output_data, + .set_power_mode = cyapa_gen5_set_power_mode, + + .set_proximity = cyapa_pip_set_proximity, +}; diff --git a/drivers/input/mouse/cyapa_gen6.c b/drivers/input/mouse/cyapa_gen6.c new file mode 100644 index 000000000..ba50f5713 --- /dev/null +++ b/drivers/input/mouse/cyapa_gen6.c @@ -0,0 +1,746 @@ +/* + * Cypress APA trackpad with I2C interface + * + * Author: Dudley Du <dudl@cypress.com> + * + * Copyright (C) 2015 Cypress Semiconductor, Inc. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/mutex.h> +#include <linux/completion.h> +#include <linux/slab.h> +#include <asm/unaligned.h> +#include <linux/crc-itu-t.h> +#include "cyapa.h" + + +#define GEN6_ENABLE_CMD_IRQ 0x41 +#define GEN6_DISABLE_CMD_IRQ 0x42 +#define GEN6_ENABLE_DEV_IRQ 0x43 +#define GEN6_DISABLE_DEV_IRQ 0x44 + +#define GEN6_POWER_MODE_ACTIVE 0x01 +#define GEN6_POWER_MODE_LP_MODE1 0x02 +#define GEN6_POWER_MODE_LP_MODE2 0x03 +#define GEN6_POWER_MODE_BTN_ONLY 0x04 + +#define GEN6_SET_POWER_MODE_INTERVAL 0x47 +#define GEN6_GET_POWER_MODE_INTERVAL 0x48 + +#define GEN6_MAX_RX_NUM 14 +#define GEN6_RETRIEVE_DATA_ID_RX_ATTENURATOR_IDAC 0x00 +#define GEN6_RETRIEVE_DATA_ID_ATTENURATOR_TRIM 0x12 + + +struct pip_app_cmd_head { + __le16 addr; + __le16 length; + u8 report_id; + u8 resv; /* Reserved, must be 0 */ + u8 cmd_code; /* bit7: resv, set to 0; bit6~0: command code.*/ +} __packed; + +struct pip_app_resp_head { + __le16 length; + u8 report_id; + u8 resv; /* Reserved, must be 0 */ + u8 cmd_code; /* bit7: TGL; bit6~0: command code.*/ + /* + * The value of data_status can be the first byte of data or + * the command status or the unsupported command code depending on the + * requested command code. + */ + u8 data_status; +} __packed; + +struct pip_fixed_info { + u8 silicon_id_high; + u8 silicon_id_low; + u8 family_id; +}; + +static u8 pip_get_bl_info[] = { + 0x04, 0x00, 0x0B, 0x00, 0x40, 0x00, 0x01, 0x38, + 0x00, 0x00, 0x70, 0x9E, 0x17 +}; + +static bool cyapa_sort_pip_hid_descriptor_data(struct cyapa *cyapa, + u8 *buf, int len) +{ + if (len != PIP_HID_DESCRIPTOR_SIZE) + return false; + + if (buf[PIP_RESP_REPORT_ID_OFFSET] == PIP_HID_APP_REPORT_ID || + buf[PIP_RESP_REPORT_ID_OFFSET] == PIP_HID_BL_REPORT_ID) + return true; + + return false; +} + +static int cyapa_get_pip_fixed_info(struct cyapa *cyapa, + struct pip_fixed_info *pip_info, bool is_bootloader) +{ + u8 resp_data[PIP_READ_SYS_INFO_RESP_LENGTH]; + int resp_len; + u16 product_family; + int error; + + if (is_bootloader) { + /* Read Bootloader Information to determine Gen5 or Gen6. */ + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + pip_get_bl_info, sizeof(pip_get_bl_info), + resp_data, &resp_len, + 2000, cyapa_sort_tsg_pip_bl_resp_data, + false); + if (error || resp_len < PIP_BL_GET_INFO_RESP_LENGTH) + return error ? error : -EIO; + + pip_info->family_id = resp_data[8]; + pip_info->silicon_id_low = resp_data[10]; + pip_info->silicon_id_high = resp_data[11]; + + return 0; + } + + /* Get App System Information to determine Gen5 or Gen6. */ + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + pip_read_sys_info, PIP_READ_SYS_INFO_CMD_LENGTH, + resp_data, &resp_len, + 2000, cyapa_pip_sort_system_info_data, false); + if (error || resp_len < PIP_READ_SYS_INFO_RESP_LENGTH) + return error ? error : -EIO; + + product_family = get_unaligned_le16(&resp_data[7]); + if ((product_family & PIP_PRODUCT_FAMILY_MASK) != + PIP_PRODUCT_FAMILY_TRACKPAD) + return -EINVAL; + + pip_info->family_id = resp_data[19]; + pip_info->silicon_id_low = resp_data[21]; + pip_info->silicon_id_high = resp_data[22]; + + return 0; + +} + +int cyapa_pip_state_parse(struct cyapa *cyapa, u8 *reg_data, int len) +{ + u8 cmd[] = { 0x01, 0x00}; + struct pip_fixed_info pip_info; + u8 resp_data[PIP_HID_DESCRIPTOR_SIZE]; + int resp_len; + bool is_bootloader; + int error; + + cyapa->state = CYAPA_STATE_NO_DEVICE; + + /* Try to wake from it deep sleep state if it is. */ + cyapa_pip_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_ON); + + /* Empty the buffer queue to get fresh data with later commands. */ + cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL); + + /* + * Read description info from trackpad device to determine running in + * APP mode or Bootloader mode. + */ + resp_len = PIP_HID_DESCRIPTOR_SIZE; + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + cmd, sizeof(cmd), + resp_data, &resp_len, + 300, + cyapa_sort_pip_hid_descriptor_data, + false); + if (error) + return error; + + if (resp_data[PIP_RESP_REPORT_ID_OFFSET] == PIP_HID_BL_REPORT_ID) + is_bootloader = true; + else if (resp_data[PIP_RESP_REPORT_ID_OFFSET] == PIP_HID_APP_REPORT_ID) + is_bootloader = false; + else + return -EAGAIN; + + /* Get PIP fixed information to determine Gen5 or Gen6. */ + memset(&pip_info, 0, sizeof(struct pip_fixed_info)); + error = cyapa_get_pip_fixed_info(cyapa, &pip_info, is_bootloader); + if (error) + return error; + + if (pip_info.family_id == 0x9B && pip_info.silicon_id_high == 0x0B) { + cyapa->gen = CYAPA_GEN6; + cyapa->state = is_bootloader ? CYAPA_STATE_GEN6_BL + : CYAPA_STATE_GEN6_APP; + } else if (pip_info.family_id == 0x91 && + pip_info.silicon_id_high == 0x02) { + cyapa->gen = CYAPA_GEN5; + cyapa->state = is_bootloader ? CYAPA_STATE_GEN5_BL + : CYAPA_STATE_GEN5_APP; + } + + return 0; +} + +static int cyapa_gen6_read_sys_info(struct cyapa *cyapa) +{ + u8 resp_data[PIP_READ_SYS_INFO_RESP_LENGTH]; + int resp_len; + u16 product_family; + u8 rotat_align; + int error; + + /* Get App System Information to determine Gen5 or Gen6. */ + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + pip_read_sys_info, PIP_READ_SYS_INFO_CMD_LENGTH, + resp_data, &resp_len, + 2000, cyapa_pip_sort_system_info_data, false); + if (error || resp_len < sizeof(resp_data)) + return error ? error : -EIO; + + product_family = get_unaligned_le16(&resp_data[7]); + if ((product_family & PIP_PRODUCT_FAMILY_MASK) != + PIP_PRODUCT_FAMILY_TRACKPAD) + return -EINVAL; + + cyapa->platform_ver = (resp_data[67] >> PIP_BL_PLATFORM_VER_SHIFT) & + PIP_BL_PLATFORM_VER_MASK; + cyapa->fw_maj_ver = resp_data[9]; + cyapa->fw_min_ver = resp_data[10]; + + cyapa->electrodes_x = resp_data[33]; + cyapa->electrodes_y = resp_data[34]; + + cyapa->physical_size_x = get_unaligned_le16(&resp_data[35]) / 100; + cyapa->physical_size_y = get_unaligned_le16(&resp_data[37]) / 100; + + cyapa->max_abs_x = get_unaligned_le16(&resp_data[39]); + cyapa->max_abs_y = get_unaligned_le16(&resp_data[41]); + + cyapa->max_z = get_unaligned_le16(&resp_data[43]); + + cyapa->x_origin = resp_data[45] & 0x01; + cyapa->y_origin = resp_data[46] & 0x01; + + cyapa->btn_capability = (resp_data[70] << 3) & CAPABILITY_BTN_MASK; + + memcpy(&cyapa->product_id[0], &resp_data[51], 5); + cyapa->product_id[5] = '-'; + memcpy(&cyapa->product_id[6], &resp_data[56], 6); + cyapa->product_id[12] = '-'; + memcpy(&cyapa->product_id[13], &resp_data[62], 2); + cyapa->product_id[15] = '\0'; + + /* Get the number of Rx electrodes. */ + rotat_align = resp_data[68]; + cyapa->electrodes_rx = + rotat_align ? cyapa->electrodes_y : cyapa->electrodes_x; + cyapa->aligned_electrodes_rx = (cyapa->electrodes_rx + 3) & ~3u; + + if (!cyapa->electrodes_x || !cyapa->electrodes_y || + !cyapa->physical_size_x || !cyapa->physical_size_y || + !cyapa->max_abs_x || !cyapa->max_abs_y || !cyapa->max_z) + return -EINVAL; + + return 0; +} + +static int cyapa_gen6_bl_read_app_info(struct cyapa *cyapa) +{ + u8 resp_data[PIP_BL_APP_INFO_RESP_LENGTH]; + int resp_len; + int error; + + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + pip_bl_read_app_info, PIP_BL_READ_APP_INFO_CMD_LENGTH, + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_bl_resp_data, false); + if (error || resp_len < PIP_BL_APP_INFO_RESP_LENGTH || + !PIP_CMD_COMPLETE_SUCCESS(resp_data)) + return error ? error : -EIO; + + cyapa->fw_maj_ver = resp_data[8]; + cyapa->fw_min_ver = resp_data[9]; + + cyapa->platform_ver = (resp_data[12] >> PIP_BL_PLATFORM_VER_SHIFT) & + PIP_BL_PLATFORM_VER_MASK; + + memcpy(&cyapa->product_id[0], &resp_data[13], 5); + cyapa->product_id[5] = '-'; + memcpy(&cyapa->product_id[6], &resp_data[18], 6); + cyapa->product_id[12] = '-'; + memcpy(&cyapa->product_id[13], &resp_data[24], 2); + cyapa->product_id[15] = '\0'; + + return 0; + +} + +static int cyapa_gen6_config_dev_irq(struct cyapa *cyapa, u8 cmd_code) +{ + u8 cmd[] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, cmd_code }; + u8 resp_data[6]; + int resp_len; + int error; + + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd), + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_app_resp_data, false); + if (error || !VALID_CMD_RESP_HEADER(resp_data, cmd_code) || + !PIP_CMD_COMPLETE_SUCCESS(resp_data) + ) + return error < 0 ? error : -EINVAL; + + return 0; +} + +static int cyapa_gen6_set_proximity(struct cyapa *cyapa, bool enable) +{ + int error; + + cyapa_gen6_config_dev_irq(cyapa, GEN6_DISABLE_CMD_IRQ); + error = cyapa_pip_set_proximity(cyapa, enable); + cyapa_gen6_config_dev_irq(cyapa, GEN6_ENABLE_CMD_IRQ); + + return error; +} + +static int cyapa_gen6_change_power_state(struct cyapa *cyapa, u8 power_mode) +{ + u8 cmd[] = { 0x04, 0x00, 0x06, 0x00, 0x2f, 0x00, 0x46, power_mode }; + u8 resp_data[6]; + int resp_len; + int error; + + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd), + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_app_resp_data, false); + if (error || !VALID_CMD_RESP_HEADER(resp_data, 0x46)) + return error < 0 ? error : -EINVAL; + + /* New power state applied in device not match the set power state. */ + if (resp_data[5] != power_mode) + return -EAGAIN; + + return 0; +} + +static int cyapa_gen6_set_interval_setting(struct cyapa *cyapa, + struct gen6_interval_setting *interval_setting) +{ + struct gen6_set_interval_cmd { + __le16 addr; + __le16 length; + u8 report_id; + u8 rsvd; /* Reserved, must be 0 */ + u8 cmd_code; + __le16 active_interval; + __le16 lp1_interval; + __le16 lp2_interval; + } __packed set_interval_cmd; + u8 resp_data[11]; + int resp_len; + int error; + + memset(&set_interval_cmd, 0, sizeof(set_interval_cmd)); + put_unaligned_le16(PIP_OUTPUT_REPORT_ADDR, &set_interval_cmd.addr); + put_unaligned_le16(sizeof(set_interval_cmd) - 2, + &set_interval_cmd.length); + set_interval_cmd.report_id = PIP_APP_CMD_REPORT_ID; + set_interval_cmd.cmd_code = GEN6_SET_POWER_MODE_INTERVAL; + put_unaligned_le16(interval_setting->active_interval, + &set_interval_cmd.active_interval); + put_unaligned_le16(interval_setting->lp1_interval, + &set_interval_cmd.lp1_interval); + put_unaligned_le16(interval_setting->lp2_interval, + &set_interval_cmd.lp2_interval); + + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + (u8 *)&set_interval_cmd, sizeof(set_interval_cmd), + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_app_resp_data, false); + if (error || + !VALID_CMD_RESP_HEADER(resp_data, GEN6_SET_POWER_MODE_INTERVAL)) + return error < 0 ? error : -EINVAL; + + /* Get the real set intervals from response. */ + interval_setting->active_interval = get_unaligned_le16(&resp_data[5]); + interval_setting->lp1_interval = get_unaligned_le16(&resp_data[7]); + interval_setting->lp2_interval = get_unaligned_le16(&resp_data[9]); + + return 0; +} + +static int cyapa_gen6_get_interval_setting(struct cyapa *cyapa, + struct gen6_interval_setting *interval_setting) +{ + u8 cmd[] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, + GEN6_GET_POWER_MODE_INTERVAL }; + u8 resp_data[11]; + int resp_len; + int error; + + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd), + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_app_resp_data, false); + if (error || + !VALID_CMD_RESP_HEADER(resp_data, GEN6_GET_POWER_MODE_INTERVAL)) + return error < 0 ? error : -EINVAL; + + interval_setting->active_interval = get_unaligned_le16(&resp_data[5]); + interval_setting->lp1_interval = get_unaligned_le16(&resp_data[7]); + interval_setting->lp2_interval = get_unaligned_le16(&resp_data[9]); + + return 0; +} + +static int cyapa_gen6_deep_sleep(struct cyapa *cyapa, u8 state) +{ + u8 ping[] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x00 }; + + if (state == PIP_DEEP_SLEEP_STATE_ON) + /* + * Send ping command to notify device prepare for wake up + * when it's in deep sleep mode. At this time, device will + * response nothing except an I2C NAK. + */ + cyapa_i2c_pip_write(cyapa, ping, sizeof(ping)); + + return cyapa_pip_deep_sleep(cyapa, state); +} + +static int cyapa_gen6_set_power_mode(struct cyapa *cyapa, + u8 power_mode, u16 sleep_time, enum cyapa_pm_stage pm_stage) +{ + struct device *dev = &cyapa->client->dev; + struct gen6_interval_setting *interval_setting = + &cyapa->gen6_interval_setting; + u8 lp_mode; + int error; + + if (cyapa->state != CYAPA_STATE_GEN6_APP) + return 0; + + if (PIP_DEV_GET_PWR_STATE(cyapa) == UNINIT_PWR_MODE) { + /* + * Assume TP in deep sleep mode when driver is loaded, + * avoid driver unload and reload command IO issue caused by TP + * has been set into deep sleep mode when unloading. + */ + PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_OFF); + } + + if (PIP_DEV_UNINIT_SLEEP_TIME(cyapa) && + PIP_DEV_GET_PWR_STATE(cyapa) != PWR_MODE_OFF) + PIP_DEV_SET_SLEEP_TIME(cyapa, UNINIT_SLEEP_TIME); + + if (PIP_DEV_GET_PWR_STATE(cyapa) == power_mode) { + if (power_mode == PWR_MODE_OFF || + power_mode == PWR_MODE_FULL_ACTIVE || + power_mode == PWR_MODE_BTN_ONLY || + PIP_DEV_GET_SLEEP_TIME(cyapa) == sleep_time) { + /* Has in correct power mode state, early return. */ + return 0; + } + } + + if (power_mode == PWR_MODE_OFF) { + cyapa_gen6_config_dev_irq(cyapa, GEN6_DISABLE_CMD_IRQ); + + error = cyapa_gen6_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_OFF); + if (error) { + dev_err(dev, "enter deep sleep fail: %d\n", error); + return error; + } + + PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_OFF); + return 0; + } + + /* + * When trackpad in power off mode, it cannot change to other power + * state directly, must be wake up from sleep firstly, then + * continue to do next power sate change. + */ + if (PIP_DEV_GET_PWR_STATE(cyapa) == PWR_MODE_OFF) { + error = cyapa_gen6_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_ON); + if (error) { + dev_err(dev, "deep sleep wake fail: %d\n", error); + return error; + } + } + + /* + * Disable device assert interrupts for command response to avoid + * disturbing system suspending or hibernating process. + */ + cyapa_gen6_config_dev_irq(cyapa, GEN6_DISABLE_CMD_IRQ); + + if (power_mode == PWR_MODE_FULL_ACTIVE) { + error = cyapa_gen6_change_power_state(cyapa, + GEN6_POWER_MODE_ACTIVE); + if (error) { + dev_err(dev, "change to active fail: %d\n", error); + goto out; + } + + PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_FULL_ACTIVE); + + /* Sync the interval setting from device. */ + cyapa_gen6_get_interval_setting(cyapa, interval_setting); + + } else if (power_mode == PWR_MODE_BTN_ONLY) { + error = cyapa_gen6_change_power_state(cyapa, + GEN6_POWER_MODE_BTN_ONLY); + if (error) { + dev_err(dev, "fail to button only mode: %d\n", error); + goto out; + } + + PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_BTN_ONLY); + } else { + /* + * Gen6 internally supports to 2 low power scan interval time, + * so can help to switch power mode quickly. + * such as runtime suspend and system suspend. + */ + if (interval_setting->lp1_interval == sleep_time) { + lp_mode = GEN6_POWER_MODE_LP_MODE1; + } else if (interval_setting->lp2_interval == sleep_time) { + lp_mode = GEN6_POWER_MODE_LP_MODE2; + } else { + if (interval_setting->lp1_interval == 0) { + interval_setting->lp1_interval = sleep_time; + lp_mode = GEN6_POWER_MODE_LP_MODE1; + } else { + interval_setting->lp2_interval = sleep_time; + lp_mode = GEN6_POWER_MODE_LP_MODE2; + } + cyapa_gen6_set_interval_setting(cyapa, + interval_setting); + } + + error = cyapa_gen6_change_power_state(cyapa, lp_mode); + if (error) { + dev_err(dev, "set power state to 0x%02x failed: %d\n", + lp_mode, error); + goto out; + } + + PIP_DEV_SET_SLEEP_TIME(cyapa, sleep_time); + PIP_DEV_SET_PWR_STATE(cyapa, + cyapa_sleep_time_to_pwr_cmd(sleep_time)); + } + +out: + cyapa_gen6_config_dev_irq(cyapa, GEN6_ENABLE_CMD_IRQ); + return error; +} + +static int cyapa_gen6_initialize(struct cyapa *cyapa) +{ + return 0; +} + +static int cyapa_pip_retrieve_data_structure(struct cyapa *cyapa, + u16 read_offset, u16 read_len, u8 data_id, + u8 *data, int *data_buf_lens) +{ + struct retrieve_data_struct_cmd { + struct pip_app_cmd_head head; + __le16 read_offset; + __le16 read_length; + u8 data_id; + } __packed cmd; + u8 resp_data[GEN6_MAX_RX_NUM + 10]; + int resp_len; + int error; + + memset(&cmd, 0, sizeof(cmd)); + put_unaligned_le16(PIP_OUTPUT_REPORT_ADDR, &cmd.head.addr); + put_unaligned_le16(sizeof(cmd) - 2, &cmd.head.length); + cmd.head.report_id = PIP_APP_CMD_REPORT_ID; + cmd.head.cmd_code = PIP_RETRIEVE_DATA_STRUCTURE; + put_unaligned_le16(read_offset, &cmd.read_offset); + put_unaligned_le16(read_len, &cmd.read_length); + cmd.data_id = data_id; + + resp_len = sizeof(resp_data); + error = cyapa_i2c_pip_cmd_irq_sync(cyapa, + (u8 *)&cmd, sizeof(cmd), + resp_data, &resp_len, + 500, cyapa_sort_tsg_pip_app_resp_data, + true); + if (error || !PIP_CMD_COMPLETE_SUCCESS(resp_data) || + resp_data[6] != data_id || + !VALID_CMD_RESP_HEADER(resp_data, PIP_RETRIEVE_DATA_STRUCTURE)) + return (error < 0) ? error : -EAGAIN; + + read_len = get_unaligned_le16(&resp_data[7]); + if (*data_buf_lens < read_len) { + *data_buf_lens = read_len; + return -ENOBUFS; + } + + memcpy(data, &resp_data[10], read_len); + *data_buf_lens = read_len; + return 0; +} + +static ssize_t cyapa_gen6_show_baseline(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyapa *cyapa = dev_get_drvdata(dev); + u8 data[GEN6_MAX_RX_NUM]; + int data_len; + int size = 0; + int i; + int error; + int resume_error; + + if (!cyapa_is_pip_app_mode(cyapa)) + return -EBUSY; + + /* 1. Suspend Scanning*/ + error = cyapa_pip_suspend_scanning(cyapa); + if (error) + return error; + + /* 2. IDAC and RX Attenuator Calibration Data (Center Frequency). */ + data_len = sizeof(data); + error = cyapa_pip_retrieve_data_structure(cyapa, 0, data_len, + GEN6_RETRIEVE_DATA_ID_RX_ATTENURATOR_IDAC, + data, &data_len); + if (error) + goto resume_scanning; + + size = scnprintf(buf, PAGE_SIZE, "%d %d %d %d %d %d ", + data[0], /* RX Attenuator Mutual */ + data[1], /* IDAC Mutual */ + data[2], /* RX Attenuator Self RX */ + data[3], /* IDAC Self RX */ + data[4], /* RX Attenuator Self TX */ + data[5] /* IDAC Self TX */ + ); + + /* 3. Read Attenuator Trim. */ + data_len = sizeof(data); + error = cyapa_pip_retrieve_data_structure(cyapa, 0, data_len, + GEN6_RETRIEVE_DATA_ID_ATTENURATOR_TRIM, + data, &data_len); + if (error) + goto resume_scanning; + + /* set attenuator trim values. */ + for (i = 0; i < data_len; i++) + size += scnprintf(buf + size, PAGE_SIZE - size, "%d ", data[i]); + size += scnprintf(buf + size, PAGE_SIZE - size, "\n"); + +resume_scanning: + /* 4. Resume Scanning*/ + resume_error = cyapa_pip_resume_scanning(cyapa); + if (resume_error || error) { + memset(buf, 0, PAGE_SIZE); + return resume_error ? resume_error : error; + } + + return size; +} + +static int cyapa_gen6_operational_check(struct cyapa *cyapa) +{ + struct device *dev = &cyapa->client->dev; + int error; + + if (cyapa->gen != CYAPA_GEN6) + return -ENODEV; + + switch (cyapa->state) { + case CYAPA_STATE_GEN6_BL: + error = cyapa_pip_bl_exit(cyapa); + if (error) { + /* Try to update trackpad product information. */ + cyapa_gen6_bl_read_app_info(cyapa); + goto out; + } + + cyapa->state = CYAPA_STATE_GEN6_APP; + /* fall through */ + + case CYAPA_STATE_GEN6_APP: + /* + * If trackpad device in deep sleep mode, + * the app command will fail. + * So always try to reset trackpad device to full active when + * the device state is required. + */ + error = cyapa_gen6_set_power_mode(cyapa, + PWR_MODE_FULL_ACTIVE, 0, CYAPA_PM_ACTIVE); + if (error) + dev_warn(dev, "%s: failed to set power active mode.\n", + __func__); + + /* By default, the trackpad proximity function is enabled. */ + error = cyapa_pip_set_proximity(cyapa, true); + if (error) + dev_warn(dev, "%s: failed to enable proximity.\n", + __func__); + + /* Get trackpad product information. */ + error = cyapa_gen6_read_sys_info(cyapa); + if (error) + goto out; + /* Only support product ID starting with CYTRA */ + if (memcmp(cyapa->product_id, product_id, + strlen(product_id)) != 0) { + dev_err(dev, "%s: unknown product ID (%s)\n", + __func__, cyapa->product_id); + error = -EINVAL; + } + break; + default: + error = -EINVAL; + } + +out: + return error; +} + +const struct cyapa_dev_ops cyapa_gen6_ops = { + .check_fw = cyapa_pip_check_fw, + .bl_enter = cyapa_pip_bl_enter, + .bl_initiate = cyapa_pip_bl_initiate, + .update_fw = cyapa_pip_do_fw_update, + .bl_activate = cyapa_pip_bl_activate, + .bl_deactivate = cyapa_pip_bl_deactivate, + + .show_baseline = cyapa_gen6_show_baseline, + .calibrate_store = cyapa_pip_do_calibrate, + + .initialize = cyapa_gen6_initialize, + + .state_parse = cyapa_pip_state_parse, + .operational_check = cyapa_gen6_operational_check, + + .irq_handler = cyapa_pip_irq_handler, + .irq_cmd_handler = cyapa_pip_irq_cmd_handler, + .sort_empty_output_data = cyapa_empty_pip_output_data, + .set_power_mode = cyapa_gen6_set_power_mode, + + .set_proximity = cyapa_gen6_set_proximity, +}; diff --git a/drivers/input/mouse/cypress_ps2.c b/drivers/input/mouse/cypress_ps2.c new file mode 100644 index 000000000..21bad3e75 --- /dev/null +++ b/drivers/input/mouse/cypress_ps2.c @@ -0,0 +1,710 @@ +/* + * 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> + * + * 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. + */ + +#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 unsigned char cytp_rate[] = {10, 20, 40, 60, 100, 200}; +static const unsigned char cytp_resolution[] = {0x00, 0x01, 0x02, 0x03}; + +static int cypress_ps2_sendbyte(struct psmouse *psmouse, int value) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + + if (ps2_sendbyte(ps2dev, value & 0xff, CYTP_CMD_TIMEOUT) < 0) { + psmouse_dbg(psmouse, + "sending command 0x%02x failed, resp 0x%02x\n", + value & 0xff, ps2dev->nak); + if (ps2dev->nak == CYTP_PS2_RETRY) + return CYTP_PS2_RETRY; + else + return CYTP_PS2_ERROR; + } + +#ifdef CYTP_DEBUG_VERBOSE + psmouse_dbg(psmouse, "sending command 0x%02x succeeded, resp 0xfa\n", + value & 0xff); +#endif + + return 0; +} + +static int cypress_ps2_ext_cmd(struct psmouse *psmouse, unsigned short cmd, + unsigned char data) +{ + 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, cmd & 0xff); + if (rc == CYTP_PS2_RETRY) { + rc = cypress_ps2_sendbyte(psmouse, 0x00); + if (rc == CYTP_PS2_RETRY) + rc = cypress_ps2_sendbyte(psmouse, 0x0a); + } + if (rc == CYTP_PS2_ERROR) + continue; + + rc = cypress_ps2_sendbyte(psmouse, data); + if (rc == CYTP_PS2_RETRY) + rc = cypress_ps2_sendbyte(psmouse, data); + if (rc == CYTP_PS2_ERROR) + continue; + else + break; + } while (--tries > 0); + + ps2_end_command(ps2dev); + + return rc; +} + +static int cypress_ps2_read_cmd_status(struct psmouse *psmouse, + unsigned char cmd, + unsigned char *param) +{ + int rc; + struct ps2dev *ps2dev = &psmouse->ps2dev; + enum psmouse_state old_state; + int pktsize; + + ps2_begin_command(ps2dev); + + old_state = psmouse->state; + psmouse->state = PSMOUSE_CMD_MODE; + psmouse->pktcnt = 0; + + pktsize = (cmd == CYTP_CMD_READ_TP_METRICS) ? 8 : 3; + memset(param, 0, pktsize); + + rc = cypress_ps2_sendbyte(psmouse, 0xe9); + if (rc < 0) + goto out; + + wait_event_timeout(ps2dev->wait, + (psmouse->pktcnt >= pktsize), + msecs_to_jiffies(CYTP_CMD_TIMEOUT)); + + memcpy(param, psmouse->packet, pktsize); + + psmouse_dbg(psmouse, "Command 0x%02x response data (0x): %*ph\n", + cmd, pktsize, param); + +out: + psmouse->state = old_state; + psmouse->pktcnt = 0; + + ps2_end_command(ps2dev); + + return rc; +} + +static bool cypress_verify_cmd_state(struct psmouse *psmouse, + unsigned char cmd, unsigned char *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, unsigned char cmd, + unsigned char *param) +{ + int tries = CYTP_PS2_CMD_TRIES; + int rc; + + 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, + PSMOUSE_CMD_SETRES, DECODE_CMD_DD(cmd)); + cypress_ps2_ext_cmd(psmouse, + PSMOUSE_CMD_SETRES, DECODE_CMD_CC(cmd)); + cypress_ps2_ext_cmd(psmouse, + PSMOUSE_CMD_SETRES, DECODE_CMD_BB(cmd)); + cypress_ps2_ext_cmd(psmouse, + PSMOUSE_CMD_SETRES, DECODE_CMD_AA(cmd)); + + rc = cypress_ps2_read_cmd_status(psmouse, cmd, param); + if (rc) + continue; + + if (cypress_verify_cmd_state(psmouse, cmd, param)) + return 0; + + } while (--tries > 0); + + return -EIO; +} + +int cypress_detect(struct psmouse *psmouse, bool set_properties) +{ + unsigned char 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; + unsigned char 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; + unsigned char 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 ret; + + ret = cypress_read_fw_version(psmouse); + if (ret) + return ret; + + ret = cypress_read_tp_metrics(psmouse); + if (ret) + return ret; + + return 0; +} + +static int cypress_set_absolute_mode(struct psmouse *psmouse) +{ + struct cytp_data *cytp = psmouse->private; + unsigned char param[3]; + + if (cypress_send_ext_cmd(psmouse, CYTP_CMD_ABS_WITH_PRESSURE_MODE, param) < 0) + return -1; + + 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 ret; + + 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); + + ret = input_mt_init_slots(input, CYTP_MAX_MT_SLOTS, + INPUT_MT_DROP_UNUSED|INPUT_MT_TRACK); + if (ret < 0) + return ret; + + __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(unsigned char header_byte) +{ + unsigned char 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) +{ + unsigned char *packet = psmouse->packet; + unsigned char 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; + unsigned char *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; + + if (rate >= 80) { + psmouse->rate = 80; + cytp->mode |= CYTP_BIT_HIGH_RATE; + } else { + psmouse->rate = 40; + cytp->mode &= ~CYTP_BIT_HIGH_RATE; + } + + ps2_command(&psmouse->ps2dev, (unsigned char *)&psmouse->rate, + 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 rc; + + do { + cypress_reset(psmouse); + rc = cypress_detect(psmouse, false); + } while (rc && (--tries > 0)); + + if (rc) { + psmouse_err(psmouse, "Reconnect: unable to detect trackpad.\n"); + return -1; + } + + if (cypress_set_absolute_mode(psmouse)) { + psmouse_err(psmouse, "Reconnect: Unable to initialize Cypress absolute mode.\n"); + return -1; + } + + return 0; +} + +int cypress_init(struct psmouse *psmouse) +{ + struct cytp_data *cytp; + + cytp = kzalloc(sizeof(struct cytp_data), GFP_KERNEL); + if (!cytp) + return -ENOMEM; + + psmouse->private = cytp; + psmouse->pktsize = 8; + + cypress_reset(psmouse); + + if (cypress_query_hardware(psmouse)) { + psmouse_err(psmouse, "Unable to query Trackpad hardware.\n"); + goto err_exit; + } + + if (cypress_set_absolute_mode(psmouse)) { + psmouse_err(psmouse, "init: Unable to initialize Cypress absolute mode.\n"); + goto err_exit; + } + + if (cypress_set_input_params(psmouse->dev, cytp) < 0) { + 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 commmunicating with it as default PS2 mouse. + */ + cypress_reset(psmouse); + + psmouse->private = NULL; + kfree(cytp); + + return -1; +} diff --git a/drivers/input/mouse/cypress_ps2.h b/drivers/input/mouse/cypress_ps2.h new file mode 100644 index 000000000..1eaddd818 --- /dev/null +++ b/drivers/input/mouse/cypress_ps2.h @@ -0,0 +1,187 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _CYPRESS_PS2_H +#define _CYPRESS_PS2_H + +#include "psmouse.h" + +#define CMD_BITS_MASK 0x03 +#define COMPOSIT(x, s) (((x) & CMD_BITS_MASK) << (s)) + +#define ENCODE_CMD(aa, bb, cc, dd) \ + (COMPOSIT((aa), 6) | COMPOSIT((bb), 4) | COMPOSIT((cc), 2) | COMPOSIT((dd), 0)) +#define CYTP_CMD_ABS_NO_PRESSURE_MODE ENCODE_CMD(0, 1, 0, 0) +#define CYTP_CMD_ABS_WITH_PRESSURE_MODE ENCODE_CMD(0, 1, 0, 1) +#define CYTP_CMD_SMBUS_MODE ENCODE_CMD(0, 1, 1, 0) +#define CYTP_CMD_STANDARD_MODE ENCODE_CMD(0, 2, 0, 0) /* not implemented yet. */ +#define CYTP_CMD_CYPRESS_REL_MODE ENCODE_CMD(1, 1, 1, 1) /* not implemented yet. */ +#define CYTP_CMD_READ_CYPRESS_ID ENCODE_CMD(0, 0, 0, 0) +#define CYTP_CMD_READ_TP_METRICS ENCODE_CMD(0, 0, 0, 1) +#define CYTP_CMD_SET_HSCROLL_WIDTH(w) ENCODE_CMD(1, 1, 0, (w)) +#define CYTP_CMD_SET_HSCROLL_MASK ENCODE_CMD(1, 1, 0, 0) +#define CYTP_CMD_SET_VSCROLL_WIDTH(w) ENCODE_CMD(1, 2, 0, (w)) +#define CYTP_CMD_SET_VSCROLL_MASK ENCODE_CMD(1, 2, 0, 0) +#define CYTP_CMD_SET_PALM_GEOMETRY(e) ENCODE_CMD(1, 2, 1, (e)) +#define CYTP_CMD_PALM_GEMMETRY_MASK ENCODE_CMD(1, 2, 1, 0) +#define CYTP_CMD_SET_PALM_SENSITIVITY(s) ENCODE_CMD(1, 2, 2, (s)) +#define CYTP_CMD_PALM_SENSITIVITY_MASK ENCODE_CMD(1, 2, 2, 0) +#define CYTP_CMD_SET_MOUSE_SENSITIVITY(s) ENCODE_CMD(1, 3, ((s) >> 2), (s)) +#define CYTP_CMD_MOUSE_SENSITIVITY_MASK ENCODE_CMD(1, 3, 0, 0) +#define CYTP_CMD_REQUEST_BASELINE_STATUS ENCODE_CMD(2, 0, 0, 1) +#define CYTP_CMD_REQUEST_RECALIBRATION ENCODE_CMD(2, 0, 0, 3) + +#define DECODE_CMD_AA(x) (((x) >> 6) & CMD_BITS_MASK) +#define DECODE_CMD_BB(x) (((x) >> 4) & CMD_BITS_MASK) +#define DECODE_CMD_CC(x) (((x) >> 2) & CMD_BITS_MASK) +#define DECODE_CMD_DD(x) ((x) & CMD_BITS_MASK) + +/* Cypress trackpad working mode. */ +#define CYTP_BIT_ABS_PRESSURE (1 << 3) +#define CYTP_BIT_ABS_NO_PRESSURE (1 << 2) +#define CYTP_BIT_CYPRESS_REL (1 << 1) +#define CYTP_BIT_STANDARD_REL (1 << 0) +#define CYTP_BIT_REL_MASK (CYTP_BIT_CYPRESS_REL | CYTP_BIT_STANDARD_REL) +#define CYTP_BIT_ABS_MASK (CYTP_BIT_ABS_PRESSURE | CYTP_BIT_ABS_NO_PRESSURE) +#define CYTP_BIT_ABS_REL_MASK (CYTP_BIT_ABS_MASK | CYTP_BIT_REL_MASK) + +#define CYTP_BIT_HIGH_RATE (1 << 4) +/* + * report mode bit is set, firmware working in Remote Mode. + * report mode bit is cleared, firmware working in Stream Mode. + */ +#define CYTP_BIT_REPORT_MODE (1 << 5) + +/* scrolling width values for set HSCROLL and VSCROLL width command. */ +#define SCROLL_WIDTH_NARROW 1 +#define SCROLL_WIDTH_NORMAL 2 +#define SCROLL_WIDTH_WIDE 3 + +#define PALM_GEOMETRY_ENABLE 1 +#define PALM_GEOMETRY_DISABLE 0 + +#define TP_METRICS_MASK 0x80 +#define FW_VERSION_MASX 0x7f +#define FW_VER_HIGH_MASK 0x70 +#define FW_VER_LOW_MASK 0x0f + +/* Times to retry a ps2_command and millisecond delay between tries. */ +#define CYTP_PS2_CMD_TRIES 3 +#define CYTP_PS2_CMD_DELAY 500 + +/* time out for PS/2 command only in milliseconds. */ +#define CYTP_CMD_TIMEOUT 200 +#define CYTP_DATA_TIMEOUT 30 + +#define CYTP_EXT_CMD 0xe8 +#define CYTP_PS2_RETRY 0xfe +#define CYTP_PS2_ERROR 0xfc + +#define CYTP_RESP_RETRY 0x01 +#define CYTP_RESP_ERROR 0xfe + + +#define CYTP_105001_WIDTH 97 /* Dell XPS 13 */ +#define CYTP_105001_HIGH 59 +#define CYTP_DEFAULT_WIDTH (CYTP_105001_WIDTH) +#define CYTP_DEFAULT_HIGH (CYTP_105001_HIGH) + +#define CYTP_ABS_MAX_X 1600 +#define CYTP_ABS_MAX_Y 900 +#define CYTP_MAX_PRESSURE 255 +#define CYTP_MIN_PRESSURE 0 + +/* header byte bits of relative package. */ +#define BTN_LEFT_BIT 0x01 +#define BTN_RIGHT_BIT 0x02 +#define BTN_MIDDLE_BIT 0x04 +#define REL_X_SIGN_BIT 0x10 +#define REL_Y_SIGN_BIT 0x20 + +/* header byte bits of absolute package. */ +#define ABS_VSCROLL_BIT 0x10 +#define ABS_HSCROLL_BIT 0x20 +#define ABS_MULTIFINGER_TAP 0x04 +#define ABS_EDGE_MOTION_MASK 0x80 + +#define DFLT_RESP_BITS_VALID 0x88 /* SMBus bit should not be set. */ +#define DFLT_RESP_SMBUS_BIT 0x80 +#define DFLT_SMBUS_MODE 0x80 +#define DFLT_PS2_MODE 0x00 +#define DFLT_RESP_BIT_MODE 0x40 +#define DFLT_RESP_REMOTE_MODE 0x40 +#define DFLT_RESP_STREAM_MODE 0x00 +#define DFLT_RESP_BIT_REPORTING 0x20 +#define DFLT_RESP_BIT_SCALING 0x10 + +#define TP_METRICS_BIT_PALM 0x80 +#define TP_METRICS_BIT_STUBBORN 0x40 +#define TP_METRICS_BIT_2F_JITTER 0x30 +#define TP_METRICS_BIT_1F_JITTER 0x0c +#define TP_METRICS_BIT_APA 0x02 +#define TP_METRICS_BIT_MTG 0x01 +#define TP_METRICS_BIT_ABS_PKT_FORMAT_SET 0xf0 +#define TP_METRICS_BIT_2F_SPIKE 0x0c +#define TP_METRICS_BIT_1F_SPIKE 0x03 + +/* bits of first byte response of E9h-Status Request command. */ +#define RESP_BTN_RIGHT_BIT 0x01 +#define RESP_BTN_MIDDLE_BIT 0x02 +#define RESP_BTN_LEFT_BIT 0x04 +#define RESP_SCALING_BIT 0x10 +#define RESP_ENABLE_BIT 0x20 +#define RESP_REMOTE_BIT 0x40 +#define RESP_SMBUS_BIT 0x80 + +#define CYTP_MAX_MT_SLOTS 2 + +struct cytp_contact { + int x; + int y; + int z; /* also named as touch pressure. */ +}; + +/* The structure of Cypress Trackpad event data. */ +struct cytp_report_data { + int contact_cnt; + struct cytp_contact contacts[CYTP_MAX_MT_SLOTS]; + unsigned int left:1; + unsigned int right:1; + unsigned int middle:1; + unsigned int tap:1; /* multi-finger tap detected. */ +}; + +/* The structure of Cypress Trackpad device private data. */ +struct cytp_data { + int fw_version; + + int pkt_size; + int mode; + + int tp_min_pressure; + int tp_max_pressure; + int tp_width; /* X direction physical size in mm. */ + int tp_high; /* Y direction physical size in mm. */ + int tp_max_abs_x; /* Max X absolute units that can be reported. */ + int tp_max_abs_y; /* Max Y absolute units that can be reported. */ + + int tp_res_x; /* X resolution in units/mm. */ + int tp_res_y; /* Y resolution in units/mm. */ + + int tp_metrics_supported; +}; + + +#ifdef CONFIG_MOUSE_PS2_CYPRESS +int cypress_detect(struct psmouse *psmouse, bool set_properties); +int cypress_init(struct psmouse *psmouse); +#else +inline int cypress_detect(struct psmouse *psmouse, bool set_properties) +{ + return -ENOSYS; +} +inline int cypress_init(struct psmouse *psmouse) +{ + return -ENOSYS; +} +#endif /* CONFIG_MOUSE_PS2_CYPRESS */ + +#endif /* _CYPRESS_PS2_H */ diff --git a/drivers/input/mouse/elan_i2c.h b/drivers/input/mouse/elan_i2c.h new file mode 100644 index 000000000..243e0fa6e --- /dev/null +++ b/drivers/input/mouse/elan_i2c.h @@ -0,0 +1,92 @@ +/* + * Elan I2C/SMBus Touchpad driver + * + * Copyright (c) 2013 ELAN Microelectronics Corp. + * + * Author: æž—æ”¿ç¶ (Duson Lin) <dusonlin@emc.com.tw> + * + * Based on cyapa driver: + * copyright (c) 2011-2012 Cypress Semiconductor, Inc. + * copyright (c) 2011-2012 Google, Inc. + * + * 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. + */ + +#ifndef _ELAN_I2C_H +#define _ELAN_I2C_H + +#include <linux/types.h> + +#define ETP_ENABLE_ABS 0x0001 +#define ETP_ENABLE_CALIBRATE 0x0002 +#define ETP_DISABLE_CALIBRATE 0x0000 +#define ETP_DISABLE_POWER 0x0001 +#define ETP_PRESSURE_OFFSET 25 + +#define ETP_CALIBRATE_MAX_LEN 3 + +/* IAP Firmware handling */ +#define ETP_PRODUCT_ID_FORMAT_STRING "%d.0" +#define ETP_FW_NAME "elan_i2c_" ETP_PRODUCT_ID_FORMAT_STRING ".bin" +#define ETP_IAP_START_ADDR 0x0083 +#define ETP_FW_IAP_PAGE_ERR (1 << 5) +#define ETP_FW_IAP_INTF_ERR (1 << 4) +#define ETP_FW_PAGE_SIZE 64 +#define ETP_FW_SIGNATURE_SIZE 6 + +struct i2c_client; +struct completion; + +enum tp_mode { + IAP_MODE = 1, + MAIN_MODE +}; + +struct elan_transport_ops { + int (*initialize)(struct i2c_client *client); + int (*sleep_control)(struct i2c_client *, bool sleep); + int (*power_control)(struct i2c_client *, bool enable); + int (*set_mode)(struct i2c_client *client, u8 mode); + + int (*calibrate)(struct i2c_client *client); + int (*calibrate_result)(struct i2c_client *client, u8 *val); + + int (*get_baseline_data)(struct i2c_client *client, + bool max_baseliune, u8 *value); + + int (*get_version)(struct i2c_client *client, bool iap, u8 *version); + int (*get_sm_version)(struct i2c_client *client, + u16 *ic_type, u8 *version, u8 *clickpad); + int (*get_checksum)(struct i2c_client *client, bool iap, u16 *csum); + int (*get_product_id)(struct i2c_client *client, u16 *id); + + int (*get_max)(struct i2c_client *client, + unsigned int *max_x, unsigned int *max_y); + int (*get_resolution)(struct i2c_client *client, + u8 *hw_res_x, u8 *hw_res_y); + int (*get_num_traces)(struct i2c_client *client, + unsigned int *x_tracenum, + unsigned int *y_tracenum); + + int (*iap_get_mode)(struct i2c_client *client, enum tp_mode *mode); + int (*iap_reset)(struct i2c_client *client); + + int (*prepare_fw_update)(struct i2c_client *client); + int (*write_fw_block)(struct i2c_client *client, + const u8 *page, u16 checksum, int idx); + int (*finish_fw_update)(struct i2c_client *client, + struct completion *reset_done); + + int (*get_report)(struct i2c_client *client, u8 *report); + int (*get_pressure_adjustment)(struct i2c_client *client, + int *adjustment); + int (*get_pattern)(struct i2c_client *client, u8 *pattern); +}; + +extern const struct elan_transport_ops elan_smbus_ops, elan_i2c_ops; + +#endif /* _ELAN_I2C_H */ diff --git a/drivers/input/mouse/elan_i2c_core.c b/drivers/input/mouse/elan_i2c_core.c new file mode 100644 index 000000000..cb0314acd --- /dev/null +++ b/drivers/input/mouse/elan_i2c_core.c @@ -0,0 +1,1394 @@ +/* + * Elan I2C/SMBus Touchpad driver + * + * Copyright (c) 2013 ELAN Microelectronics Corp. + * + * Author: æž—æ”¿ç¶ (Duson Lin) <dusonlin@emc.com.tw> + * Author: KT Liao <kt.liao@emc.com.tw> + * Version: 1.6.3 + * + * Based on cyapa driver: + * copyright (c) 2011-2012 Cypress Semiconductor, Inc. + * copyright (c) 2011-2012 Google, Inc. + * + * 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/acpi.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/input/mt.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/input.h> +#include <linux/uaccess.h> +#include <linux/jiffies.h> +#include <linux/completion.h> +#include <linux/of.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <asm/unaligned.h> + +#include "elan_i2c.h" + +#define DRIVER_NAME "elan_i2c" +#define ELAN_VENDOR_ID 0x04f3 +#define ETP_MAX_PRESSURE 255 +#define ETP_FWIDTH_REDUCE 90 +#define ETP_FINGER_WIDTH 15 +#define ETP_RETRY_COUNT 3 + +#define ETP_MAX_FINGERS 5 +#define ETP_FINGER_DATA_LEN 5 +#define ETP_REPORT_ID 0x5D +#define ETP_TP_REPORT_ID 0x5E +#define ETP_REPORT_ID_OFFSET 2 +#define ETP_TOUCH_INFO_OFFSET 3 +#define ETP_FINGER_DATA_OFFSET 4 +#define ETP_HOVER_INFO_OFFSET 30 +#define ETP_MAX_REPORT_LEN 34 + +/* The main device structure */ +struct elan_tp_data { + struct i2c_client *client; + struct input_dev *input; + struct input_dev *tp_input; /* trackpoint input node */ + struct regulator *vcc; + + const struct elan_transport_ops *ops; + + /* for fw update */ + struct completion fw_completion; + bool in_fw_update; + + struct mutex sysfs_mutex; + + unsigned int max_x; + unsigned int max_y; + unsigned int width_x; + unsigned int width_y; + unsigned int x_res; + unsigned int y_res; + + u8 pattern; + u16 product_id; + u8 fw_version; + u8 sm_version; + u8 iap_version; + u16 fw_checksum; + int pressure_adjustment; + u8 mode; + u16 ic_type; + u16 fw_validpage_count; + u16 fw_signature_address; + + bool irq_wake; + + u8 min_baseline; + u8 max_baseline; + bool baseline_ready; + u8 clickpad; +}; + +static int elan_get_fwinfo(u16 ic_type, u16 *validpage_count, + u16 *signature_address) +{ + switch (ic_type) { + case 0x00: + case 0x06: + case 0x08: + *validpage_count = 512; + break; + case 0x03: + case 0x07: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + *validpage_count = 768; + break; + case 0x0D: + *validpage_count = 896; + break; + case 0x0E: + *validpage_count = 640; + break; + case 0x10: + *validpage_count = 1024; + break; + default: + /* unknown ic type clear value */ + *validpage_count = 0; + *signature_address = 0; + return -ENXIO; + } + + *signature_address = + (*validpage_count * ETP_FW_PAGE_SIZE) - ETP_FW_SIGNATURE_SIZE; + + return 0; +} + +static int elan_set_power(struct elan_tp_data *data, bool on) +{ + int repeat = ETP_RETRY_COUNT; + int error; + + do { + error = data->ops->power_control(data->client, on); + if (error >= 0) + return 0; + + msleep(30); + } while (--repeat > 0); + + dev_err(&data->client->dev, "failed to set power %s: %d\n", + on ? "on" : "off", error); + return error; +} + +static int elan_sleep(struct elan_tp_data *data) +{ + int repeat = ETP_RETRY_COUNT; + int error; + + do { + error = data->ops->sleep_control(data->client, true); + if (!error) + return 0; + + msleep(30); + } while (--repeat > 0); + + return error; +} + +static int elan_query_product(struct elan_tp_data *data) +{ + int error; + + error = data->ops->get_product_id(data->client, &data->product_id); + if (error) + return error; + + error = data->ops->get_sm_version(data->client, &data->ic_type, + &data->sm_version, &data->clickpad); + if (error) + return error; + + return 0; +} + +static int elan_check_ASUS_special_fw(struct elan_tp_data *data) +{ + if (data->ic_type == 0x0E) { + switch (data->product_id) { + case 0x05 ... 0x07: + case 0x09: + case 0x13: + return true; + } + } else if (data->ic_type == 0x08 && data->product_id == 0x26) { + /* ASUS EeeBook X205TA */ + return true; + } + + return false; +} + +static int __elan_initialize(struct elan_tp_data *data) +{ + struct i2c_client *client = data->client; + bool woken_up = false; + int error; + + error = data->ops->initialize(client); + if (error) { + dev_err(&client->dev, "device initialize failed: %d\n", error); + return error; + } + + error = elan_query_product(data); + if (error) + return error; + + /* + * Some ASUS devices were shipped with firmware that requires + * touchpads to be woken up first, before attempting to switch + * them into absolute reporting mode. + */ + if (elan_check_ASUS_special_fw(data)) { + error = data->ops->sleep_control(client, false); + if (error) { + dev_err(&client->dev, + "failed to wake device up: %d\n", error); + return error; + } + + msleep(200); + woken_up = true; + } + + data->mode |= ETP_ENABLE_ABS; + error = data->ops->set_mode(client, data->mode); + if (error) { + dev_err(&client->dev, + "failed to switch to absolute mode: %d\n", error); + return error; + } + + if (!woken_up) { + error = data->ops->sleep_control(client, false); + if (error) { + dev_err(&client->dev, + "failed to wake device up: %d\n", error); + return error; + } + } + + return 0; +} + +static int elan_initialize(struct elan_tp_data *data) +{ + int repeat = ETP_RETRY_COUNT; + int error; + + do { + error = __elan_initialize(data); + if (!error) + return 0; + + msleep(30); + } while (--repeat > 0); + + return error; +} + +static int elan_query_device_info(struct elan_tp_data *data) +{ + int error; + u16 ic_type; + + error = data->ops->get_version(data->client, false, &data->fw_version); + if (error) + return error; + + error = data->ops->get_checksum(data->client, false, + &data->fw_checksum); + if (error) + return error; + + error = data->ops->get_version(data->client, true, &data->iap_version); + if (error) + return error; + + error = data->ops->get_pressure_adjustment(data->client, + &data->pressure_adjustment); + if (error) + return error; + + error = data->ops->get_pattern(data->client, &data->pattern); + if (error) + return error; + + if (data->pattern == 0x01) + ic_type = data->ic_type; + else + ic_type = data->iap_version; + + error = elan_get_fwinfo(ic_type, &data->fw_validpage_count, + &data->fw_signature_address); + if (error) + dev_warn(&data->client->dev, + "unexpected iap version %#04x (ic type: %#04x), firmware update will not work\n", + data->iap_version, data->ic_type); + + return 0; +} + +static unsigned int elan_convert_resolution(u8 val) +{ + /* + * (value from firmware) * 10 + 790 = dpi + * + * We also have to convert dpi to dots/mm (*10/254 to avoid floating + * point). + */ + + return ((int)(char)val * 10 + 790) * 10 / 254; +} + +static int elan_query_device_parameters(struct elan_tp_data *data) +{ + unsigned int x_traces, y_traces; + u8 hw_x_res, hw_y_res; + int error; + + error = data->ops->get_max(data->client, &data->max_x, &data->max_y); + if (error) + return error; + + error = data->ops->get_num_traces(data->client, &x_traces, &y_traces); + if (error) + return error; + + data->width_x = data->max_x / x_traces; + data->width_y = data->max_y / y_traces; + + error = data->ops->get_resolution(data->client, &hw_x_res, &hw_y_res); + if (error) + return error; + + data->x_res = elan_convert_resolution(hw_x_res); + data->y_res = elan_convert_resolution(hw_y_res); + + return 0; +} + +/* + ********************************************************** + * IAP firmware updater related routines + ********************************************************** + */ +static int elan_write_fw_block(struct elan_tp_data *data, + const u8 *page, u16 checksum, int idx) +{ + int retry = ETP_RETRY_COUNT; + int error; + + do { + error = data->ops->write_fw_block(data->client, + page, checksum, idx); + if (!error) + return 0; + + dev_dbg(&data->client->dev, + "IAP retrying page %d (error: %d)\n", idx, error); + } while (--retry > 0); + + return error; +} + +static int __elan_update_firmware(struct elan_tp_data *data, + const struct firmware *fw) +{ + struct i2c_client *client = data->client; + struct device *dev = &client->dev; + int i, j; + int error; + u16 iap_start_addr; + u16 boot_page_count; + u16 sw_checksum = 0, fw_checksum = 0; + + error = data->ops->prepare_fw_update(client); + if (error) + return error; + + iap_start_addr = get_unaligned_le16(&fw->data[ETP_IAP_START_ADDR * 2]); + + boot_page_count = (iap_start_addr * 2) / ETP_FW_PAGE_SIZE; + for (i = boot_page_count; i < data->fw_validpage_count; i++) { + u16 checksum = 0; + const u8 *page = &fw->data[i * ETP_FW_PAGE_SIZE]; + + for (j = 0; j < ETP_FW_PAGE_SIZE; j += 2) + checksum += ((page[j + 1] << 8) | page[j]); + + error = elan_write_fw_block(data, page, checksum, i); + if (error) { + dev_err(dev, "write page %d fail: %d\n", i, error); + return error; + } + + sw_checksum += checksum; + } + + /* Wait WDT reset and power on reset */ + msleep(600); + + error = data->ops->finish_fw_update(client, &data->fw_completion); + if (error) + return error; + + error = data->ops->get_checksum(client, true, &fw_checksum); + if (error) + return error; + + if (sw_checksum != fw_checksum) { + dev_err(dev, "checksum diff sw=[%04X], fw=[%04X]\n", + sw_checksum, fw_checksum); + return -EIO; + } + + return 0; +} + +static int elan_update_firmware(struct elan_tp_data *data, + const struct firmware *fw) +{ + struct i2c_client *client = data->client; + int retval; + + dev_dbg(&client->dev, "Starting firmware update....\n"); + + disable_irq(client->irq); + data->in_fw_update = true; + + retval = __elan_update_firmware(data, fw); + if (retval) { + dev_err(&client->dev, "firmware update failed: %d\n", retval); + data->ops->iap_reset(client); + } else { + /* Reinitialize TP after fw is updated */ + elan_initialize(data); + elan_query_device_info(data); + } + + data->in_fw_update = false; + enable_irq(client->irq); + + return retval; +} + +/* + ******************************************************************* + * SYSFS attributes + ******************************************************************* + */ +static ssize_t elan_sysfs_read_fw_checksum(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct elan_tp_data *data = i2c_get_clientdata(client); + + return sprintf(buf, "0x%04x\n", data->fw_checksum); +} + +static ssize_t elan_sysfs_read_product_id(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct elan_tp_data *data = i2c_get_clientdata(client); + + return sprintf(buf, ETP_PRODUCT_ID_FORMAT_STRING "\n", + data->product_id); +} + +static ssize_t elan_sysfs_read_fw_ver(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct elan_tp_data *data = i2c_get_clientdata(client); + + return sprintf(buf, "%d.0\n", data->fw_version); +} + +static ssize_t elan_sysfs_read_sm_ver(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct elan_tp_data *data = i2c_get_clientdata(client); + + return sprintf(buf, "%d.0\n", data->sm_version); +} + +static ssize_t elan_sysfs_read_iap_ver(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct elan_tp_data *data = i2c_get_clientdata(client); + + return sprintf(buf, "%d.0\n", data->iap_version); +} + +static ssize_t elan_sysfs_update_fw(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct elan_tp_data *data = dev_get_drvdata(dev); + const struct firmware *fw; + char *fw_name; + int error; + const u8 *fw_signature; + static const u8 signature[] = {0xAA, 0x55, 0xCC, 0x33, 0xFF, 0xFF}; + + if (data->fw_validpage_count == 0) + return -EINVAL; + + /* Look for a firmware with the product id appended. */ + fw_name = kasprintf(GFP_KERNEL, ETP_FW_NAME, data->product_id); + if (!fw_name) { + dev_err(dev, "failed to allocate memory for firmware name\n"); + return -ENOMEM; + } + + dev_info(dev, "requesting fw '%s'\n", fw_name); + error = request_firmware(&fw, fw_name, dev); + kfree(fw_name); + if (error) { + dev_err(dev, "failed to request firmware: %d\n", error); + return error; + } + + /* Firmware file must match signature data */ + fw_signature = &fw->data[data->fw_signature_address]; + if (memcmp(fw_signature, signature, sizeof(signature)) != 0) { + dev_err(dev, "signature mismatch (expected %*ph, got %*ph)\n", + (int)sizeof(signature), signature, + (int)sizeof(signature), fw_signature); + error = -EBADF; + goto out_release_fw; + } + + error = mutex_lock_interruptible(&data->sysfs_mutex); + if (error) + goto out_release_fw; + + error = elan_update_firmware(data, fw); + + mutex_unlock(&data->sysfs_mutex); + +out_release_fw: + release_firmware(fw); + return error ?: count; +} + +static ssize_t calibrate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct elan_tp_data *data = i2c_get_clientdata(client); + int tries = 20; + int retval; + int error; + u8 val[ETP_CALIBRATE_MAX_LEN]; + + retval = mutex_lock_interruptible(&data->sysfs_mutex); + if (retval) + return retval; + + disable_irq(client->irq); + + data->mode |= ETP_ENABLE_CALIBRATE; + retval = data->ops->set_mode(client, data->mode); + if (retval) { + dev_err(dev, "failed to enable calibration mode: %d\n", + retval); + goto out; + } + + retval = data->ops->calibrate(client); + if (retval) { + dev_err(dev, "failed to start calibration: %d\n", + retval); + goto out_disable_calibrate; + } + + val[0] = 0xff; + do { + /* Wait 250ms before checking if calibration has completed. */ + msleep(250); + + retval = data->ops->calibrate_result(client, val); + if (retval) + dev_err(dev, "failed to check calibration result: %d\n", + retval); + else if (val[0] == 0) + break; /* calibration done */ + + } while (--tries); + + if (tries == 0) { + dev_err(dev, "failed to calibrate. Timeout.\n"); + retval = -ETIMEDOUT; + } + +out_disable_calibrate: + data->mode &= ~ETP_ENABLE_CALIBRATE; + error = data->ops->set_mode(data->client, data->mode); + if (error) { + dev_err(dev, "failed to disable calibration mode: %d\n", + error); + if (!retval) + retval = error; + } +out: + enable_irq(client->irq); + mutex_unlock(&data->sysfs_mutex); + return retval ?: count; +} + +static ssize_t elan_sysfs_read_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct elan_tp_data *data = i2c_get_clientdata(client); + int error; + enum tp_mode mode; + + error = mutex_lock_interruptible(&data->sysfs_mutex); + if (error) + return error; + + error = data->ops->iap_get_mode(data->client, &mode); + + mutex_unlock(&data->sysfs_mutex); + + if (error) + return error; + + return sprintf(buf, "%d\n", (int)mode); +} + +static DEVICE_ATTR(product_id, S_IRUGO, elan_sysfs_read_product_id, NULL); +static DEVICE_ATTR(firmware_version, S_IRUGO, elan_sysfs_read_fw_ver, NULL); +static DEVICE_ATTR(sample_version, S_IRUGO, elan_sysfs_read_sm_ver, NULL); +static DEVICE_ATTR(iap_version, S_IRUGO, elan_sysfs_read_iap_ver, NULL); +static DEVICE_ATTR(fw_checksum, S_IRUGO, elan_sysfs_read_fw_checksum, NULL); +static DEVICE_ATTR(mode, S_IRUGO, elan_sysfs_read_mode, NULL); +static DEVICE_ATTR(update_fw, S_IWUSR, NULL, elan_sysfs_update_fw); + +static DEVICE_ATTR_WO(calibrate); + +static struct attribute *elan_sysfs_entries[] = { + &dev_attr_product_id.attr, + &dev_attr_firmware_version.attr, + &dev_attr_sample_version.attr, + &dev_attr_iap_version.attr, + &dev_attr_fw_checksum.attr, + &dev_attr_calibrate.attr, + &dev_attr_mode.attr, + &dev_attr_update_fw.attr, + NULL, +}; + +static const struct attribute_group elan_sysfs_group = { + .attrs = elan_sysfs_entries, +}; + +static ssize_t acquire_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct elan_tp_data *data = i2c_get_clientdata(client); + int error; + int retval; + + retval = mutex_lock_interruptible(&data->sysfs_mutex); + if (retval) + return retval; + + disable_irq(client->irq); + + data->baseline_ready = false; + + data->mode |= ETP_ENABLE_CALIBRATE; + retval = data->ops->set_mode(data->client, data->mode); + if (retval) { + dev_err(dev, "Failed to enable calibration mode to get baseline: %d\n", + retval); + goto out; + } + + msleep(250); + + retval = data->ops->get_baseline_data(data->client, true, + &data->max_baseline); + if (retval) { + dev_err(dev, "Failed to read max baseline form device: %d\n", + retval); + goto out_disable_calibrate; + } + + retval = data->ops->get_baseline_data(data->client, false, + &data->min_baseline); + if (retval) { + dev_err(dev, "Failed to read min baseline form device: %d\n", + retval); + goto out_disable_calibrate; + } + + data->baseline_ready = true; + +out_disable_calibrate: + data->mode &= ~ETP_ENABLE_CALIBRATE; + error = data->ops->set_mode(data->client, data->mode); + if (error) { + dev_err(dev, "Failed to disable calibration mode after acquiring baseline: %d\n", + error); + if (!retval) + retval = error; + } +out: + enable_irq(client->irq); + mutex_unlock(&data->sysfs_mutex); + return retval ?: count; +} + +static ssize_t min_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct elan_tp_data *data = i2c_get_clientdata(client); + int retval; + + retval = mutex_lock_interruptible(&data->sysfs_mutex); + if (retval) + return retval; + + if (!data->baseline_ready) { + retval = -ENODATA; + goto out; + } + + retval = snprintf(buf, PAGE_SIZE, "%d", data->min_baseline); + +out: + mutex_unlock(&data->sysfs_mutex); + return retval; +} + +static ssize_t max_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct elan_tp_data *data = i2c_get_clientdata(client); + int retval; + + retval = mutex_lock_interruptible(&data->sysfs_mutex); + if (retval) + return retval; + + if (!data->baseline_ready) { + retval = -ENODATA; + goto out; + } + + retval = snprintf(buf, PAGE_SIZE, "%d", data->max_baseline); + +out: + mutex_unlock(&data->sysfs_mutex); + return retval; +} + + +static DEVICE_ATTR_WO(acquire); +static DEVICE_ATTR_RO(min); +static DEVICE_ATTR_RO(max); + +static struct attribute *elan_baseline_sysfs_entries[] = { + &dev_attr_acquire.attr, + &dev_attr_min.attr, + &dev_attr_max.attr, + NULL, +}; + +static const struct attribute_group elan_baseline_sysfs_group = { + .name = "baseline", + .attrs = elan_baseline_sysfs_entries, +}; + +static const struct attribute_group *elan_sysfs_groups[] = { + &elan_sysfs_group, + &elan_baseline_sysfs_group, + NULL +}; + +/* + ****************************************************************** + * Elan isr functions + ****************************************************************** + */ +static void elan_report_contact(struct elan_tp_data *data, + int contact_num, bool contact_valid, + u8 *finger_data) +{ + struct input_dev *input = data->input; + unsigned int pos_x, pos_y; + unsigned int pressure, mk_x, mk_y; + unsigned int area_x, area_y, major, minor; + unsigned int scaled_pressure; + + if (contact_valid) { + pos_x = ((finger_data[0] & 0xf0) << 4) | + finger_data[1]; + pos_y = ((finger_data[0] & 0x0f) << 8) | + finger_data[2]; + mk_x = (finger_data[3] & 0x0f); + mk_y = (finger_data[3] >> 4); + pressure = finger_data[4]; + + if (pos_x > data->max_x || pos_y > data->max_y) { + dev_dbg(input->dev.parent, + "[%d] x=%d y=%d over max (%d, %d)", + contact_num, pos_x, pos_y, + data->max_x, data->max_y); + return; + } + + /* + * To avoid treating large finger as palm, let's reduce the + * width x and y per trace. + */ + area_x = mk_x * (data->width_x - ETP_FWIDTH_REDUCE); + area_y = mk_y * (data->width_y - ETP_FWIDTH_REDUCE); + + major = max(area_x, area_y); + minor = min(area_x, area_y); + + scaled_pressure = pressure + data->pressure_adjustment; + + if (scaled_pressure > ETP_MAX_PRESSURE) + scaled_pressure = ETP_MAX_PRESSURE; + + input_mt_slot(input, contact_num); + input_mt_report_slot_state(input, MT_TOOL_FINGER, true); + input_report_abs(input, ABS_MT_POSITION_X, pos_x); + input_report_abs(input, ABS_MT_POSITION_Y, data->max_y - pos_y); + input_report_abs(input, ABS_MT_PRESSURE, scaled_pressure); + input_report_abs(input, ABS_TOOL_WIDTH, mk_x); + input_report_abs(input, ABS_MT_TOUCH_MAJOR, major); + input_report_abs(input, ABS_MT_TOUCH_MINOR, minor); + } else { + input_mt_slot(input, contact_num); + input_mt_report_slot_state(input, MT_TOOL_FINGER, false); + } +} + +static void elan_report_absolute(struct elan_tp_data *data, u8 *packet) +{ + struct input_dev *input = data->input; + u8 *finger_data = &packet[ETP_FINGER_DATA_OFFSET]; + int i; + u8 tp_info = packet[ETP_TOUCH_INFO_OFFSET]; + u8 hover_info = packet[ETP_HOVER_INFO_OFFSET]; + bool contact_valid, hover_event; + + hover_event = hover_info & 0x40; + for (i = 0; i < ETP_MAX_FINGERS; i++) { + contact_valid = tp_info & (1U << (3 + i)); + elan_report_contact(data, i, contact_valid, finger_data); + + if (contact_valid) + finger_data += ETP_FINGER_DATA_LEN; + } + + input_report_key(input, BTN_LEFT, tp_info & 0x01); + input_report_key(input, BTN_RIGHT, tp_info & 0x02); + input_report_abs(input, ABS_DISTANCE, hover_event != 0); + input_mt_report_pointer_emulation(input, true); + input_sync(input); +} + +static void elan_report_trackpoint(struct elan_tp_data *data, u8 *report) +{ + struct input_dev *input = data->tp_input; + u8 *packet = &report[ETP_REPORT_ID_OFFSET + 1]; + int x, y; + + if (!data->tp_input) { + dev_warn_once(&data->client->dev, + "received a trackpoint report while no trackpoint device has been created. Please report upstream.\n"); + return; + } + + input_report_key(input, BTN_LEFT, packet[0] & 0x01); + input_report_key(input, BTN_RIGHT, packet[0] & 0x02); + input_report_key(input, BTN_MIDDLE, packet[0] & 0x04); + + if ((packet[3] & 0x0F) == 0x06) { + x = packet[4] - (int)((packet[1] ^ 0x80) << 1); + y = (int)((packet[2] ^ 0x80) << 1) - packet[5]; + + input_report_rel(input, REL_X, x); + input_report_rel(input, REL_Y, y); + } + + input_sync(input); +} + +static irqreturn_t elan_isr(int irq, void *dev_id) +{ + struct elan_tp_data *data = dev_id; + struct device *dev = &data->client->dev; + int error; + u8 report[ETP_MAX_REPORT_LEN]; + + /* + * When device is connected to i2c bus, when all IAP page writes + * complete, the driver will receive interrupt and must read + * 0000 to confirm that IAP is finished. + */ + if (data->in_fw_update) { + complete(&data->fw_completion); + goto out; + } + + error = data->ops->get_report(data->client, report); + if (error) + goto out; + + switch (report[ETP_REPORT_ID_OFFSET]) { + case ETP_REPORT_ID: + elan_report_absolute(data, report); + break; + case ETP_TP_REPORT_ID: + elan_report_trackpoint(data, report); + break; + default: + dev_err(dev, "invalid report id data (%x)\n", + report[ETP_REPORT_ID_OFFSET]); + } + +out: + return IRQ_HANDLED; +} + +/* + ****************************************************************** + * Elan initialization functions + ****************************************************************** + */ + +static int elan_setup_trackpoint_input_device(struct elan_tp_data *data) +{ + struct device *dev = &data->client->dev; + struct input_dev *input; + + input = devm_input_allocate_device(dev); + if (!input) + return -ENOMEM; + + input->name = "Elan TrackPoint"; + input->id.bustype = BUS_I2C; + input->id.vendor = ELAN_VENDOR_ID; + input->id.product = data->product_id; + input_set_drvdata(input, data); + + input_set_capability(input, EV_REL, REL_X); + input_set_capability(input, EV_REL, REL_Y); + input_set_capability(input, EV_KEY, BTN_LEFT); + input_set_capability(input, EV_KEY, BTN_RIGHT); + input_set_capability(input, EV_KEY, BTN_MIDDLE); + + __set_bit(INPUT_PROP_POINTER, input->propbit); + __set_bit(INPUT_PROP_POINTING_STICK, input->propbit); + + data->tp_input = input; + + return 0; +} + +static int elan_setup_input_device(struct elan_tp_data *data) +{ + struct device *dev = &data->client->dev; + struct input_dev *input; + unsigned int max_width = max(data->width_x, data->width_y); + unsigned int min_width = min(data->width_x, data->width_y); + int error; + + input = devm_input_allocate_device(dev); + if (!input) + return -ENOMEM; + + input->name = "Elan Touchpad"; + input->id.bustype = BUS_I2C; + input->id.vendor = ELAN_VENDOR_ID; + input->id.product = data->product_id; + input_set_drvdata(input, data); + + error = input_mt_init_slots(input, ETP_MAX_FINGERS, + INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED); + if (error) { + dev_err(dev, "failed to initialize MT slots: %d\n", error); + return error; + } + + __set_bit(EV_ABS, input->evbit); + __set_bit(INPUT_PROP_POINTER, input->propbit); + if (data->clickpad) + __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); + else + __set_bit(BTN_RIGHT, input->keybit); + __set_bit(BTN_LEFT, input->keybit); + + /* Set up ST parameters */ + input_set_abs_params(input, ABS_X, 0, data->max_x, 0, 0); + input_set_abs_params(input, ABS_Y, 0, data->max_y, 0, 0); + input_abs_set_res(input, ABS_X, data->x_res); + input_abs_set_res(input, ABS_Y, data->y_res); + input_set_abs_params(input, ABS_PRESSURE, 0, ETP_MAX_PRESSURE, 0, 0); + input_set_abs_params(input, ABS_TOOL_WIDTH, 0, ETP_FINGER_WIDTH, 0, 0); + input_set_abs_params(input, ABS_DISTANCE, 0, 1, 0, 0); + + /* And MT parameters */ + input_set_abs_params(input, ABS_MT_POSITION_X, 0, data->max_x, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, data->max_y, 0, 0); + input_abs_set_res(input, ABS_MT_POSITION_X, data->x_res); + input_abs_set_res(input, ABS_MT_POSITION_Y, data->y_res); + input_set_abs_params(input, ABS_MT_PRESSURE, 0, + ETP_MAX_PRESSURE, 0, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, + ETP_FINGER_WIDTH * max_width, 0, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, + ETP_FINGER_WIDTH * min_width, 0, 0); + + data->input = input; + + return 0; +} + +static void elan_disable_regulator(void *_data) +{ + struct elan_tp_data *data = _data; + + regulator_disable(data->vcc); +} + +static void elan_remove_sysfs_groups(void *_data) +{ + struct elan_tp_data *data = _data; + + sysfs_remove_groups(&data->client->dev.kobj, elan_sysfs_groups); +} + +static int elan_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + const struct elan_transport_ops *transport_ops; + struct device *dev = &client->dev; + struct elan_tp_data *data; + unsigned long irqflags; + int error; + + if (IS_ENABLED(CONFIG_MOUSE_ELAN_I2C_I2C) && + i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + transport_ops = &elan_i2c_ops; + } else if (IS_ENABLED(CONFIG_MOUSE_ELAN_I2C_SMBUS) && + i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK)) { + transport_ops = &elan_smbus_ops; + } else { + dev_err(dev, "not a supported I2C/SMBus adapter\n"); + return -EIO; + } + + data = devm_kzalloc(dev, sizeof(struct elan_tp_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + + data->ops = transport_ops; + data->client = client; + init_completion(&data->fw_completion); + mutex_init(&data->sysfs_mutex); + + data->vcc = devm_regulator_get(dev, "vcc"); + if (IS_ERR(data->vcc)) { + error = PTR_ERR(data->vcc); + if (error != -EPROBE_DEFER) + dev_err(dev, "Failed to get 'vcc' regulator: %d\n", + error); + return error; + } + + error = regulator_enable(data->vcc); + if (error) { + dev_err(dev, "Failed to enable regulator: %d\n", error); + return error; + } + + error = devm_add_action(dev, elan_disable_regulator, data); + if (error) { + regulator_disable(data->vcc); + dev_err(dev, "Failed to add disable regulator action: %d\n", + error); + return error; + } + + /* Make sure there is something at this address */ + error = i2c_smbus_read_byte(client); + if (error < 0) { + dev_dbg(&client->dev, "nothing at this address: %d\n", error); + return -ENXIO; + } + + /* Initialize the touchpad. */ + error = elan_initialize(data); + if (error) + return error; + + error = elan_query_device_info(data); + if (error) + return error; + + error = elan_query_device_parameters(data); + if (error) + return error; + + dev_info(dev, + "Elan Touchpad: Module ID: 0x%04x, Firmware: 0x%04x, Sample: 0x%04x, IAP: 0x%04x\n", + data->product_id, + data->fw_version, + data->sm_version, + data->iap_version); + + dev_dbg(dev, + "Elan Touchpad Extra Information:\n" + " Max ABS X,Y: %d,%d\n" + " Width X,Y: %d,%d\n" + " Resolution X,Y: %d,%d (dots/mm)\n" + " ic type: 0x%x\n" + " info pattern: 0x%x\n", + data->max_x, data->max_y, + data->width_x, data->width_y, + data->x_res, data->y_res, + data->ic_type, data->pattern); + + /* Set up input device properties based on queried parameters. */ + error = elan_setup_input_device(data); + if (error) + return error; + + if (device_property_read_bool(&client->dev, "elan,trackpoint")) { + error = elan_setup_trackpoint_input_device(data); + if (error) + return error; + } + + /* + * Platform code (ACPI, DTS) should normally set up interrupt + * for us, but in case it did not let's fall back to using falling + * edge to be compatible with older Chromebooks. + */ + irqflags = irq_get_trigger_type(client->irq); + if (!irqflags) + irqflags = IRQF_TRIGGER_FALLING; + + error = devm_request_threaded_irq(dev, client->irq, NULL, elan_isr, + irqflags | IRQF_ONESHOT, + client->name, data); + if (error) { + dev_err(dev, "cannot register irq=%d\n", client->irq); + return error; + } + + error = sysfs_create_groups(&dev->kobj, elan_sysfs_groups); + if (error) { + dev_err(dev, "failed to create sysfs attributes: %d\n", error); + return error; + } + + error = devm_add_action(dev, elan_remove_sysfs_groups, data); + if (error) { + elan_remove_sysfs_groups(data); + dev_err(dev, "Failed to add sysfs cleanup action: %d\n", + error); + return error; + } + + error = input_register_device(data->input); + if (error) { + dev_err(dev, "failed to register input device: %d\n", error); + return error; + } + + if (data->tp_input) { + error = input_register_device(data->tp_input); + if (error) { + dev_err(&client->dev, + "failed to register TrackPoint input device: %d\n", + error); + return error; + } + } + + /* + * Systems using device tree should set up wakeup via DTS, + * the rest will configure device as wakeup source by default. + */ + if (!dev->of_node) + device_init_wakeup(dev, true); + + return 0; +} + +static int __maybe_unused elan_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct elan_tp_data *data = i2c_get_clientdata(client); + int ret; + + /* + * We are taking the mutex to make sure sysfs operations are + * complete before we attempt to bring the device into low[er] + * power mode. + */ + ret = mutex_lock_interruptible(&data->sysfs_mutex); + if (ret) + return ret; + + disable_irq(client->irq); + + if (device_may_wakeup(dev)) { + ret = elan_sleep(data); + /* Enable wake from IRQ */ + data->irq_wake = (enable_irq_wake(client->irq) == 0); + } else { + ret = elan_set_power(data, false); + if (ret) + goto err; + + ret = regulator_disable(data->vcc); + if (ret) { + dev_err(dev, "error %d disabling regulator\n", ret); + /* Attempt to power the chip back up */ + elan_set_power(data, true); + } + } + +err: + mutex_unlock(&data->sysfs_mutex); + return ret; +} + +static int __maybe_unused elan_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct elan_tp_data *data = i2c_get_clientdata(client); + int error; + + if (!device_may_wakeup(dev)) { + error = regulator_enable(data->vcc); + if (error) { + dev_err(dev, "error %d enabling regulator\n", error); + goto err; + } + } else if (data->irq_wake) { + disable_irq_wake(client->irq); + data->irq_wake = false; + } + + error = elan_set_power(data, true); + if (error) { + dev_err(dev, "power up when resuming failed: %d\n", error); + goto err; + } + + error = elan_initialize(data); + if (error) + dev_err(dev, "initialize when resuming failed: %d\n", error); + +err: + enable_irq(data->client->irq); + return error; +} + +static SIMPLE_DEV_PM_OPS(elan_pm_ops, elan_suspend, elan_resume); + +static const struct i2c_device_id elan_id[] = { + { DRIVER_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, elan_id); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id elan_acpi_id[] = { + { "ELAN0000", 0 }, + { "ELAN0100", 0 }, + { "ELAN0600", 0 }, + { "ELAN0601", 0 }, + { "ELAN0602", 0 }, + { "ELAN0603", 0 }, + { "ELAN0604", 0 }, + { "ELAN0605", 0 }, + { "ELAN0606", 0 }, + { "ELAN0607", 0 }, + { "ELAN0608", 0 }, + { "ELAN0609", 0 }, + { "ELAN060B", 0 }, + { "ELAN060C", 0 }, + { "ELAN060F", 0 }, + { "ELAN0610", 0 }, + { "ELAN0611", 0 }, + { "ELAN0612", 0 }, + { "ELAN0615", 0 }, + { "ELAN0616", 0 }, + { "ELAN0617", 0 }, + { "ELAN0618", 0 }, + { "ELAN0619", 0 }, + { "ELAN061A", 0 }, +/* { "ELAN061B", 0 }, not working on the Lenovo Legion Y7000 */ + { "ELAN061C", 0 }, + { "ELAN061D", 0 }, + { "ELAN061E", 0 }, + { "ELAN061F", 0 }, + { "ELAN0620", 0 }, + { "ELAN0621", 0 }, + { "ELAN0622", 0 }, + { "ELAN0623", 0 }, + { "ELAN0624", 0 }, + { "ELAN0625", 0 }, + { "ELAN0626", 0 }, + { "ELAN0627", 0 }, + { "ELAN0628", 0 }, + { "ELAN0629", 0 }, + { "ELAN062A", 0 }, + { "ELAN062B", 0 }, + { "ELAN062C", 0 }, + { "ELAN062D", 0 }, + { "ELAN0631", 0 }, + { "ELAN0632", 0 }, + { "ELAN1000", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, elan_acpi_id); +#endif + +#ifdef CONFIG_OF +static const struct of_device_id elan_of_match[] = { + { .compatible = "elan,ekth3000" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, elan_of_match); +#endif + +static struct i2c_driver elan_driver = { + .driver = { + .name = DRIVER_NAME, + .pm = &elan_pm_ops, + .acpi_match_table = ACPI_PTR(elan_acpi_id), + .of_match_table = of_match_ptr(elan_of_match), + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = elan_probe, + .id_table = elan_id, +}; + +module_i2c_driver(elan_driver); + +MODULE_AUTHOR("Duson Lin <dusonlin@emc.com.tw>"); +MODULE_DESCRIPTION("Elan I2C/SMBus Touchpad driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/mouse/elan_i2c_i2c.c b/drivers/input/mouse/elan_i2c_i2c.c new file mode 100644 index 000000000..e19eb60b3 --- /dev/null +++ b/drivers/input/mouse/elan_i2c_i2c.c @@ -0,0 +1,713 @@ +/* + * Elan I2C/SMBus Touchpad driver - I2C interface + * + * Copyright (c) 2013 ELAN Microelectronics Corp. + * + * Author: æž—æ”¿ç¶ (Duson Lin) <dusonlin@emc.com.tw> + * + * Based on cyapa driver: + * copyright (c) 2011-2012 Cypress Semiconductor, Inc. + * copyright (c) 2011-2012 Google, Inc. + * + * 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/completion.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <asm/unaligned.h> + +#include "elan_i2c.h" + +/* Elan i2c commands */ +#define ETP_I2C_RESET 0x0100 +#define ETP_I2C_WAKE_UP 0x0800 +#define ETP_I2C_SLEEP 0x0801 +#define ETP_I2C_DESC_CMD 0x0001 +#define ETP_I2C_REPORT_DESC_CMD 0x0002 +#define ETP_I2C_STAND_CMD 0x0005 +#define ETP_I2C_PATTERN_CMD 0x0100 +#define ETP_I2C_UNIQUEID_CMD 0x0101 +#define ETP_I2C_FW_VERSION_CMD 0x0102 +#define ETP_I2C_IC_TYPE_CMD 0x0103 +#define ETP_I2C_OSM_VERSION_CMD 0x0103 +#define ETP_I2C_NSM_VERSION_CMD 0x0104 +#define ETP_I2C_XY_TRACENUM_CMD 0x0105 +#define ETP_I2C_MAX_X_AXIS_CMD 0x0106 +#define ETP_I2C_MAX_Y_AXIS_CMD 0x0107 +#define ETP_I2C_RESOLUTION_CMD 0x0108 +#define ETP_I2C_PRESSURE_CMD 0x010A +#define ETP_I2C_IAP_VERSION_CMD 0x0110 +#define ETP_I2C_SET_CMD 0x0300 +#define ETP_I2C_POWER_CMD 0x0307 +#define ETP_I2C_FW_CHECKSUM_CMD 0x030F +#define ETP_I2C_IAP_CTRL_CMD 0x0310 +#define ETP_I2C_IAP_CMD 0x0311 +#define ETP_I2C_IAP_RESET_CMD 0x0314 +#define ETP_I2C_IAP_CHECKSUM_CMD 0x0315 +#define ETP_I2C_CALIBRATE_CMD 0x0316 +#define ETP_I2C_MAX_BASELINE_CMD 0x0317 +#define ETP_I2C_MIN_BASELINE_CMD 0x0318 + +#define ETP_I2C_REPORT_LEN 34 +#define ETP_I2C_DESC_LENGTH 30 +#define ETP_I2C_REPORT_DESC_LENGTH 158 +#define ETP_I2C_INF_LENGTH 2 +#define ETP_I2C_IAP_PASSWORD 0x1EA5 +#define ETP_I2C_IAP_RESET 0xF0F0 +#define ETP_I2C_MAIN_MODE_ON (1 << 9) +#define ETP_I2C_IAP_REG_L 0x01 +#define ETP_I2C_IAP_REG_H 0x06 + +static int elan_i2c_read_block(struct i2c_client *client, + u16 reg, u8 *val, u16 len) +{ + __le16 buf[] = { + cpu_to_le16(reg), + }; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = client->flags & I2C_M_TEN, + .len = sizeof(buf), + .buf = (u8 *)buf, + }, + { + .addr = client->addr, + .flags = (client->flags & I2C_M_TEN) | I2C_M_RD, + .len = len, + .buf = val, + } + }; + int ret; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + return ret == ARRAY_SIZE(msgs) ? 0 : (ret < 0 ? ret : -EIO); +} + +static int elan_i2c_read_cmd(struct i2c_client *client, u16 reg, u8 *val) +{ + int retval; + + retval = elan_i2c_read_block(client, reg, val, ETP_I2C_INF_LENGTH); + if (retval < 0) { + dev_err(&client->dev, "reading cmd (0x%04x) fail.\n", reg); + return retval; + } + + return 0; +} + +static int elan_i2c_write_cmd(struct i2c_client *client, u16 reg, u16 cmd) +{ + __le16 buf[] = { + cpu_to_le16(reg), + cpu_to_le16(cmd), + }; + struct i2c_msg msg = { + .addr = client->addr, + .flags = client->flags & I2C_M_TEN, + .len = sizeof(buf), + .buf = (u8 *)buf, + }; + int ret; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret != 1) { + if (ret >= 0) + ret = -EIO; + dev_err(&client->dev, "writing cmd (0x%04x) failed: %d\n", + reg, ret); + return ret; + } + + return 0; +} + +static int elan_i2c_initialize(struct i2c_client *client) +{ + struct device *dev = &client->dev; + int error; + u8 val[256]; + + error = elan_i2c_write_cmd(client, ETP_I2C_STAND_CMD, ETP_I2C_RESET); + if (error) { + dev_err(dev, "device reset failed: %d\n", error); + return error; + } + + /* Wait for the device to reset */ + msleep(100); + + /* get reset acknowledgement 0000 */ + error = i2c_master_recv(client, val, ETP_I2C_INF_LENGTH); + if (error < 0) { + dev_err(dev, "failed to read reset response: %d\n", error); + return error; + } + + error = elan_i2c_read_block(client, ETP_I2C_DESC_CMD, + val, ETP_I2C_DESC_LENGTH); + if (error) { + dev_err(dev, "cannot get device descriptor: %d\n", error); + return error; + } + + error = elan_i2c_read_block(client, ETP_I2C_REPORT_DESC_CMD, + val, ETP_I2C_REPORT_DESC_LENGTH); + if (error) { + dev_err(dev, "fetching report descriptor failed.: %d\n", error); + return error; + } + + return 0; +} + +static int elan_i2c_sleep_control(struct i2c_client *client, bool sleep) +{ + return elan_i2c_write_cmd(client, ETP_I2C_STAND_CMD, + sleep ? ETP_I2C_SLEEP : ETP_I2C_WAKE_UP); +} + +static int elan_i2c_power_control(struct i2c_client *client, bool enable) +{ + u8 val[2]; + u16 reg; + int error; + + error = elan_i2c_read_cmd(client, ETP_I2C_POWER_CMD, val); + if (error) { + dev_err(&client->dev, + "failed to read current power state: %d\n", + error); + return error; + } + + reg = le16_to_cpup((__le16 *)val); + if (enable) + reg &= ~ETP_DISABLE_POWER; + else + reg |= ETP_DISABLE_POWER; + + error = elan_i2c_write_cmd(client, ETP_I2C_POWER_CMD, reg); + if (error) { + dev_err(&client->dev, + "failed to write current power state: %d\n", + error); + return error; + } + + return 0; +} + +static int elan_i2c_set_mode(struct i2c_client *client, u8 mode) +{ + return elan_i2c_write_cmd(client, ETP_I2C_SET_CMD, mode); +} + + +static int elan_i2c_calibrate(struct i2c_client *client) +{ + return elan_i2c_write_cmd(client, ETP_I2C_CALIBRATE_CMD, 1); +} + +static int elan_i2c_calibrate_result(struct i2c_client *client, u8 *val) +{ + return elan_i2c_read_block(client, ETP_I2C_CALIBRATE_CMD, val, 1); +} + +static int elan_i2c_get_baseline_data(struct i2c_client *client, + bool max_baseline, u8 *value) +{ + int error; + u8 val[3]; + + error = elan_i2c_read_cmd(client, + max_baseline ? ETP_I2C_MAX_BASELINE_CMD : + ETP_I2C_MIN_BASELINE_CMD, + val); + if (error) + return error; + + *value = le16_to_cpup((__le16 *)val); + + return 0; +} + +static int elan_i2c_get_pattern(struct i2c_client *client, u8 *pattern) +{ + int error; + u8 val[3]; + + error = elan_i2c_read_cmd(client, ETP_I2C_PATTERN_CMD, val); + if (error) { + dev_err(&client->dev, "failed to get pattern: %d\n", error); + return error; + } + *pattern = val[1]; + + return 0; +} + +static int elan_i2c_get_version(struct i2c_client *client, + bool iap, u8 *version) +{ + int error; + u8 pattern_ver; + u8 val[3]; + + error = elan_i2c_get_pattern(client, &pattern_ver); + if (error) { + dev_err(&client->dev, "failed to get pattern version\n"); + return error; + } + + error = elan_i2c_read_cmd(client, + iap ? ETP_I2C_IAP_VERSION_CMD : + ETP_I2C_FW_VERSION_CMD, + val); + if (error) { + dev_err(&client->dev, "failed to get %s version: %d\n", + iap ? "IAP" : "FW", error); + return error; + } + + if (pattern_ver == 0x01) + *version = iap ? val[1] : val[0]; + else + *version = val[0]; + return 0; +} + +static int elan_i2c_get_sm_version(struct i2c_client *client, + u16 *ic_type, u8 *version, + u8 *clickpad) +{ + int error; + u8 pattern_ver; + u8 val[3]; + + error = elan_i2c_get_pattern(client, &pattern_ver); + if (error) { + dev_err(&client->dev, "failed to get pattern version\n"); + return error; + } + + if (pattern_ver == 0x01) { + error = elan_i2c_read_cmd(client, ETP_I2C_IC_TYPE_CMD, val); + if (error) { + dev_err(&client->dev, "failed to get ic type: %d\n", + error); + return error; + } + *ic_type = be16_to_cpup((__be16 *)val); + + error = elan_i2c_read_cmd(client, ETP_I2C_NSM_VERSION_CMD, + val); + if (error) { + dev_err(&client->dev, "failed to get SM version: %d\n", + error); + return error; + } + *version = val[1]; + *clickpad = val[0] & 0x10; + } else { + error = elan_i2c_read_cmd(client, ETP_I2C_OSM_VERSION_CMD, val); + if (error) { + dev_err(&client->dev, "failed to get SM version: %d\n", + error); + return error; + } + *version = val[0]; + *ic_type = val[1]; + + error = elan_i2c_read_cmd(client, ETP_I2C_NSM_VERSION_CMD, + val); + if (error) { + dev_err(&client->dev, "failed to get SM version: %d\n", + error); + return error; + } + *clickpad = val[0] & 0x10; + } + + return 0; +} + +static int elan_i2c_get_product_id(struct i2c_client *client, u16 *id) +{ + int error; + u8 val[3]; + + error = elan_i2c_read_cmd(client, ETP_I2C_UNIQUEID_CMD, val); + if (error) { + dev_err(&client->dev, "failed to get product ID: %d\n", error); + return error; + } + + *id = le16_to_cpup((__le16 *)val); + return 0; +} + +static int elan_i2c_get_checksum(struct i2c_client *client, + bool iap, u16 *csum) +{ + int error; + u8 val[3]; + + error = elan_i2c_read_cmd(client, + iap ? ETP_I2C_IAP_CHECKSUM_CMD : + ETP_I2C_FW_CHECKSUM_CMD, + val); + if (error) { + dev_err(&client->dev, "failed to get %s checksum: %d\n", + iap ? "IAP" : "FW", error); + return error; + } + + *csum = le16_to_cpup((__le16 *)val); + return 0; +} + +static int elan_i2c_get_max(struct i2c_client *client, + unsigned int *max_x, unsigned int *max_y) +{ + int error; + u8 val[3]; + + error = elan_i2c_read_cmd(client, ETP_I2C_MAX_X_AXIS_CMD, val); + if (error) { + dev_err(&client->dev, "failed to get X dimension: %d\n", error); + return error; + } + + *max_x = le16_to_cpup((__le16 *)val) & 0x0fff; + + error = elan_i2c_read_cmd(client, ETP_I2C_MAX_Y_AXIS_CMD, val); + if (error) { + dev_err(&client->dev, "failed to get Y dimension: %d\n", error); + return error; + } + + *max_y = le16_to_cpup((__le16 *)val) & 0x0fff; + + return 0; +} + +static int elan_i2c_get_resolution(struct i2c_client *client, + u8 *hw_res_x, u8 *hw_res_y) +{ + int error; + u8 val[3]; + + error = elan_i2c_read_cmd(client, ETP_I2C_RESOLUTION_CMD, val); + if (error) { + dev_err(&client->dev, "failed to get resolution: %d\n", error); + return error; + } + + *hw_res_x = val[0]; + *hw_res_y = val[1]; + + return 0; +} + +static int elan_i2c_get_num_traces(struct i2c_client *client, + unsigned int *x_traces, + unsigned int *y_traces) +{ + int error; + u8 val[3]; + + error = elan_i2c_read_cmd(client, ETP_I2C_XY_TRACENUM_CMD, val); + if (error) { + dev_err(&client->dev, "failed to get trace info: %d\n", error); + return error; + } + + *x_traces = val[0]; + *y_traces = val[1]; + + return 0; +} + +static int elan_i2c_get_pressure_adjustment(struct i2c_client *client, + int *adjustment) +{ + int error; + u8 val[3]; + + error = elan_i2c_read_cmd(client, ETP_I2C_PRESSURE_CMD, val); + if (error) { + dev_err(&client->dev, "failed to get pressure format: %d\n", + error); + return error; + } + + if ((val[0] >> 4) & 0x1) + *adjustment = 0; + else + *adjustment = ETP_PRESSURE_OFFSET; + + return 0; +} + +static int elan_i2c_iap_get_mode(struct i2c_client *client, enum tp_mode *mode) +{ + int error; + u16 constant; + u8 val[3]; + + error = elan_i2c_read_cmd(client, ETP_I2C_IAP_CTRL_CMD, val); + if (error) { + dev_err(&client->dev, + "failed to read iap control register: %d\n", + error); + return error; + } + + constant = le16_to_cpup((__le16 *)val); + dev_dbg(&client->dev, "iap control reg: 0x%04x.\n", constant); + + *mode = (constant & ETP_I2C_MAIN_MODE_ON) ? MAIN_MODE : IAP_MODE; + + return 0; +} + +static int elan_i2c_iap_reset(struct i2c_client *client) +{ + int error; + + error = elan_i2c_write_cmd(client, ETP_I2C_IAP_RESET_CMD, + ETP_I2C_IAP_RESET); + if (error) { + dev_err(&client->dev, "cannot reset IC: %d\n", error); + return error; + } + + return 0; +} + +static int elan_i2c_set_flash_key(struct i2c_client *client) +{ + int error; + + error = elan_i2c_write_cmd(client, ETP_I2C_IAP_CMD, + ETP_I2C_IAP_PASSWORD); + if (error) { + dev_err(&client->dev, "cannot set flash key: %d\n", error); + return error; + } + + return 0; +} + +static int elan_i2c_prepare_fw_update(struct i2c_client *client) +{ + struct device *dev = &client->dev; + int error; + enum tp_mode mode; + u8 val[3]; + u16 password; + + /* Get FW in which mode (IAP_MODE/MAIN_MODE) */ + error = elan_i2c_iap_get_mode(client, &mode); + if (error) + return error; + + if (mode == IAP_MODE) { + /* Reset IC */ + error = elan_i2c_iap_reset(client); + if (error) + return error; + + msleep(30); + } + + /* Set flash key*/ + error = elan_i2c_set_flash_key(client); + if (error) + return error; + + /* Wait for F/W IAP initialization */ + msleep(mode == MAIN_MODE ? 100 : 30); + + /* Check if we are in IAP mode or not */ + error = elan_i2c_iap_get_mode(client, &mode); + if (error) + return error; + + if (mode == MAIN_MODE) { + dev_err(dev, "wrong mode: %d\n", mode); + return -EIO; + } + + /* Set flash key again */ + error = elan_i2c_set_flash_key(client); + if (error) + return error; + + /* Wait for F/W IAP initialization */ + msleep(30); + + /* read back to check we actually enabled successfully. */ + error = elan_i2c_read_cmd(client, ETP_I2C_IAP_CMD, val); + if (error) { + dev_err(dev, "cannot read iap password: %d\n", + error); + return error; + } + + password = le16_to_cpup((__le16 *)val); + if (password != ETP_I2C_IAP_PASSWORD) { + dev_err(dev, "wrong iap password: 0x%X\n", password); + return -EIO; + } + + return 0; +} + +static int elan_i2c_write_fw_block(struct i2c_client *client, + const u8 *page, u16 checksum, int idx) +{ + struct device *dev = &client->dev; + u8 page_store[ETP_FW_PAGE_SIZE + 4]; + u8 val[3]; + u16 result; + int ret, error; + + page_store[0] = ETP_I2C_IAP_REG_L; + page_store[1] = ETP_I2C_IAP_REG_H; + memcpy(&page_store[2], page, ETP_FW_PAGE_SIZE); + /* recode checksum at last two bytes */ + put_unaligned_le16(checksum, &page_store[ETP_FW_PAGE_SIZE + 2]); + + ret = i2c_master_send(client, page_store, sizeof(page_store)); + if (ret != sizeof(page_store)) { + error = ret < 0 ? ret : -EIO; + dev_err(dev, "Failed to write page %d: %d\n", idx, error); + return error; + } + + /* Wait for F/W to update one page ROM data. */ + msleep(35); + + error = elan_i2c_read_cmd(client, ETP_I2C_IAP_CTRL_CMD, val); + if (error) { + dev_err(dev, "Failed to read IAP write result: %d\n", error); + return error; + } + + result = le16_to_cpup((__le16 *)val); + if (result & (ETP_FW_IAP_PAGE_ERR | ETP_FW_IAP_INTF_ERR)) { + dev_err(dev, "IAP reports failed write: %04hx\n", + result); + return -EIO; + } + + return 0; +} + +static int elan_i2c_finish_fw_update(struct i2c_client *client, + struct completion *completion) +{ + struct device *dev = &client->dev; + int error; + int len; + u8 buffer[ETP_I2C_REPORT_LEN]; + + len = i2c_master_recv(client, buffer, ETP_I2C_REPORT_LEN); + if (len != ETP_I2C_REPORT_LEN) { + error = len < 0 ? len : -EIO; + dev_warn(dev, "failed to read I2C data after FW WDT reset: %d (%d)\n", + error, len); + } + + reinit_completion(completion); + enable_irq(client->irq); + + error = elan_i2c_write_cmd(client, ETP_I2C_STAND_CMD, ETP_I2C_RESET); + if (error) { + dev_err(dev, "device reset failed: %d\n", error); + } else if (!wait_for_completion_timeout(completion, + msecs_to_jiffies(300))) { + dev_err(dev, "timeout waiting for device reset\n"); + error = -ETIMEDOUT; + } + + disable_irq(client->irq); + + if (error) + return error; + + len = i2c_master_recv(client, buffer, ETP_I2C_INF_LENGTH); + if (len != ETP_I2C_INF_LENGTH) { + error = len < 0 ? len : -EIO; + dev_err(dev, "failed to read INT signal: %d (%d)\n", + error, len); + return error; + } + + return 0; +} + +static int elan_i2c_get_report(struct i2c_client *client, u8 *report) +{ + int len; + + len = i2c_master_recv(client, report, ETP_I2C_REPORT_LEN); + if (len < 0) { + dev_err(&client->dev, "failed to read report data: %d\n", len); + return len; + } + + if (len != ETP_I2C_REPORT_LEN) { + dev_err(&client->dev, + "wrong report length (%d vs %d expected)\n", + len, ETP_I2C_REPORT_LEN); + return -EIO; + } + + return 0; +} + +const struct elan_transport_ops elan_i2c_ops = { + .initialize = elan_i2c_initialize, + .sleep_control = elan_i2c_sleep_control, + .power_control = elan_i2c_power_control, + .set_mode = elan_i2c_set_mode, + + .calibrate = elan_i2c_calibrate, + .calibrate_result = elan_i2c_calibrate_result, + + .get_baseline_data = elan_i2c_get_baseline_data, + + .get_version = elan_i2c_get_version, + .get_sm_version = elan_i2c_get_sm_version, + .get_product_id = elan_i2c_get_product_id, + .get_checksum = elan_i2c_get_checksum, + .get_pressure_adjustment = elan_i2c_get_pressure_adjustment, + + .get_max = elan_i2c_get_max, + .get_resolution = elan_i2c_get_resolution, + .get_num_traces = elan_i2c_get_num_traces, + + .iap_get_mode = elan_i2c_iap_get_mode, + .iap_reset = elan_i2c_iap_reset, + + .prepare_fw_update = elan_i2c_prepare_fw_update, + .write_fw_block = elan_i2c_write_fw_block, + .finish_fw_update = elan_i2c_finish_fw_update, + + .get_pattern = elan_i2c_get_pattern, + + .get_report = elan_i2c_get_report, +}; diff --git a/drivers/input/mouse/elan_i2c_smbus.c b/drivers/input/mouse/elan_i2c_smbus.c new file mode 100644 index 000000000..88e315d2c --- /dev/null +++ b/drivers/input/mouse/elan_i2c_smbus.c @@ -0,0 +1,542 @@ +/* + * Elan I2C/SMBus Touchpad driver - SMBus interface + * + * Copyright (c) 2013 ELAN Microelectronics Corp. + * + * Author: æž—æ”¿ç¶ (Duson Lin) <dusonlin@emc.com.tw> + * + * Based on cyapa driver: + * copyright (c) 2011-2012 Cypress Semiconductor, Inc. + * copyright (c) 2011-2012 Google, Inc. + * + * 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/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> + +#include "elan_i2c.h" + +/* Elan SMbus commands */ +#define ETP_SMBUS_IAP_CMD 0x00 +#define ETP_SMBUS_ENABLE_TP 0x20 +#define ETP_SMBUS_SLEEP_CMD 0x21 +#define ETP_SMBUS_IAP_PASSWORD_WRITE 0x29 +#define ETP_SMBUS_IAP_PASSWORD_READ 0x80 +#define ETP_SMBUS_WRITE_FW_BLOCK 0x2A +#define ETP_SMBUS_IAP_RESET_CMD 0x2B +#define ETP_SMBUS_RANGE_CMD 0xA0 +#define ETP_SMBUS_FW_VERSION_CMD 0xA1 +#define ETP_SMBUS_XY_TRACENUM_CMD 0xA2 +#define ETP_SMBUS_SM_VERSION_CMD 0xA3 +#define ETP_SMBUS_UNIQUEID_CMD 0xA3 +#define ETP_SMBUS_RESOLUTION_CMD 0xA4 +#define ETP_SMBUS_HELLOPACKET_CMD 0xA7 +#define ETP_SMBUS_PACKET_QUERY 0xA8 +#define ETP_SMBUS_IAP_VERSION_CMD 0xAC +#define ETP_SMBUS_IAP_CTRL_CMD 0xAD +#define ETP_SMBUS_IAP_CHECKSUM_CMD 0xAE +#define ETP_SMBUS_FW_CHECKSUM_CMD 0xAF +#define ETP_SMBUS_MAX_BASELINE_CMD 0xC3 +#define ETP_SMBUS_MIN_BASELINE_CMD 0xC4 +#define ETP_SMBUS_CALIBRATE_QUERY 0xC5 + +#define ETP_SMBUS_REPORT_LEN 32 +#define ETP_SMBUS_REPORT_OFFSET 2 +#define ETP_SMBUS_HELLOPACKET_LEN 5 +#define ETP_SMBUS_IAP_PASSWORD 0x1234 +#define ETP_SMBUS_IAP_MODE_ON (1 << 6) + +static int elan_smbus_initialize(struct i2c_client *client) +{ + u8 check[ETP_SMBUS_HELLOPACKET_LEN] = { 0x55, 0x55, 0x55, 0x55, 0x55 }; + u8 values[I2C_SMBUS_BLOCK_MAX] = {0}; + int len, error; + + /* Get hello packet */ + len = i2c_smbus_read_block_data(client, + ETP_SMBUS_HELLOPACKET_CMD, values); + if (len != ETP_SMBUS_HELLOPACKET_LEN) { + dev_err(&client->dev, "hello packet length fail: %d\n", len); + error = len < 0 ? len : -EIO; + return error; + } + + /* compare hello packet */ + if (memcmp(values, check, ETP_SMBUS_HELLOPACKET_LEN)) { + dev_err(&client->dev, "hello packet fail [%*ph]\n", + ETP_SMBUS_HELLOPACKET_LEN, values); + return -ENXIO; + } + + /* enable tp */ + error = i2c_smbus_write_byte(client, ETP_SMBUS_ENABLE_TP); + if (error) { + dev_err(&client->dev, "failed to enable touchpad: %d\n", error); + return error; + } + + return 0; +} + +static int elan_smbus_set_mode(struct i2c_client *client, u8 mode) +{ + u8 cmd[4] = { 0x00, 0x07, 0x00, mode }; + + return i2c_smbus_write_block_data(client, ETP_SMBUS_IAP_CMD, + sizeof(cmd), cmd); +} + +static int elan_smbus_sleep_control(struct i2c_client *client, bool sleep) +{ + if (sleep) + return i2c_smbus_write_byte(client, ETP_SMBUS_SLEEP_CMD); + else + return 0; /* XXX should we send ETP_SMBUS_ENABLE_TP here? */ +} + +static int elan_smbus_power_control(struct i2c_client *client, bool enable) +{ + return 0; /* A no-op */ +} + +static int elan_smbus_calibrate(struct i2c_client *client) +{ + u8 cmd[4] = { 0x00, 0x08, 0x00, 0x01 }; + + return i2c_smbus_write_block_data(client, ETP_SMBUS_IAP_CMD, + sizeof(cmd), cmd); +} + +static int elan_smbus_calibrate_result(struct i2c_client *client, u8 *val) +{ + int error; + u8 buf[I2C_SMBUS_BLOCK_MAX] = {0}; + + BUILD_BUG_ON(ETP_CALIBRATE_MAX_LEN > sizeof(buf)); + + error = i2c_smbus_read_block_data(client, + ETP_SMBUS_CALIBRATE_QUERY, buf); + if (error < 0) + return error; + + memcpy(val, buf, ETP_CALIBRATE_MAX_LEN); + return 0; +} + +static int elan_smbus_get_baseline_data(struct i2c_client *client, + bool max_baseline, u8 *value) +{ + int error; + u8 val[I2C_SMBUS_BLOCK_MAX] = {0}; + + error = i2c_smbus_read_block_data(client, + max_baseline ? + ETP_SMBUS_MAX_BASELINE_CMD : + ETP_SMBUS_MIN_BASELINE_CMD, + val); + if (error < 0) + return error; + + *value = be16_to_cpup((__be16 *)val); + + return 0; +} + +static int elan_smbus_get_version(struct i2c_client *client, + bool iap, u8 *version) +{ + int error; + u8 val[I2C_SMBUS_BLOCK_MAX] = {0}; + + error = i2c_smbus_read_block_data(client, + iap ? ETP_SMBUS_IAP_VERSION_CMD : + ETP_SMBUS_FW_VERSION_CMD, + val); + if (error < 0) { + dev_err(&client->dev, "failed to get %s version: %d\n", + iap ? "IAP" : "FW", error); + return error; + } + + *version = val[2]; + return 0; +} + +static int elan_smbus_get_sm_version(struct i2c_client *client, + u16 *ic_type, u8 *version, + u8 *clickpad) +{ + int error; + u8 val[I2C_SMBUS_BLOCK_MAX] = {0}; + + error = i2c_smbus_read_block_data(client, + ETP_SMBUS_SM_VERSION_CMD, val); + if (error < 0) { + dev_err(&client->dev, "failed to get SM version: %d\n", error); + return error; + } + + *version = val[0]; + *ic_type = val[1]; + *clickpad = val[0] & 0x10; + return 0; +} + +static int elan_smbus_get_product_id(struct i2c_client *client, u16 *id) +{ + int error; + u8 val[I2C_SMBUS_BLOCK_MAX] = {0}; + + error = i2c_smbus_read_block_data(client, + ETP_SMBUS_UNIQUEID_CMD, val); + if (error < 0) { + dev_err(&client->dev, "failed to get product ID: %d\n", error); + return error; + } + + *id = be16_to_cpup((__be16 *)val); + return 0; +} + +static int elan_smbus_get_checksum(struct i2c_client *client, + bool iap, u16 *csum) +{ + int error; + u8 val[I2C_SMBUS_BLOCK_MAX] = {0}; + + error = i2c_smbus_read_block_data(client, + iap ? ETP_SMBUS_FW_CHECKSUM_CMD : + ETP_SMBUS_IAP_CHECKSUM_CMD, + val); + if (error < 0) { + dev_err(&client->dev, "failed to get %s checksum: %d\n", + iap ? "IAP" : "FW", error); + return error; + } + + *csum = be16_to_cpup((__be16 *)val); + return 0; +} + +static int elan_smbus_get_max(struct i2c_client *client, + unsigned int *max_x, unsigned int *max_y) +{ + int ret; + int error; + u8 val[I2C_SMBUS_BLOCK_MAX] = {0}; + + ret = i2c_smbus_read_block_data(client, ETP_SMBUS_RANGE_CMD, val); + if (ret != 3) { + error = ret < 0 ? ret : -EIO; + dev_err(&client->dev, "failed to get dimensions: %d\n", error); + return error; + } + + *max_x = (0x0f & val[0]) << 8 | val[1]; + *max_y = (0xf0 & val[0]) << 4 | val[2]; + + return 0; +} + +static int elan_smbus_get_resolution(struct i2c_client *client, + u8 *hw_res_x, u8 *hw_res_y) +{ + int ret; + int error; + u8 val[I2C_SMBUS_BLOCK_MAX] = {0}; + + ret = i2c_smbus_read_block_data(client, ETP_SMBUS_RESOLUTION_CMD, val); + if (ret != 3) { + error = ret < 0 ? ret : -EIO; + dev_err(&client->dev, "failed to get resolution: %d\n", error); + return error; + } + + *hw_res_x = val[1] & 0x0F; + *hw_res_y = (val[1] & 0xF0) >> 4; + + return 0; +} + +static int elan_smbus_get_num_traces(struct i2c_client *client, + unsigned int *x_traces, + unsigned int *y_traces) +{ + int ret; + int error; + u8 val[I2C_SMBUS_BLOCK_MAX] = {0}; + + ret = i2c_smbus_read_block_data(client, ETP_SMBUS_XY_TRACENUM_CMD, val); + if (ret != 3) { + error = ret < 0 ? ret : -EIO; + dev_err(&client->dev, "failed to get trace info: %d\n", error); + return error; + } + + *x_traces = val[1]; + *y_traces = val[2]; + + return 0; +} + +static int elan_smbus_get_pressure_adjustment(struct i2c_client *client, + int *adjustment) +{ + *adjustment = ETP_PRESSURE_OFFSET; + return 0; +} + +static int elan_smbus_iap_get_mode(struct i2c_client *client, + enum tp_mode *mode) +{ + int error; + u16 constant; + u8 val[I2C_SMBUS_BLOCK_MAX] = {0}; + + error = i2c_smbus_read_block_data(client, ETP_SMBUS_IAP_CTRL_CMD, val); + if (error < 0) { + dev_err(&client->dev, "failed to read iap ctrol register: %d\n", + error); + return error; + } + + constant = be16_to_cpup((__be16 *)val); + dev_dbg(&client->dev, "iap control reg: 0x%04x.\n", constant); + + *mode = (constant & ETP_SMBUS_IAP_MODE_ON) ? IAP_MODE : MAIN_MODE; + + return 0; +} + +static int elan_smbus_iap_reset(struct i2c_client *client) +{ + int error; + + error = i2c_smbus_write_byte(client, ETP_SMBUS_IAP_RESET_CMD); + if (error) { + dev_err(&client->dev, "cannot reset IC: %d\n", error); + return error; + } + + return 0; +} + +static int elan_smbus_set_flash_key(struct i2c_client *client) +{ + int error; + u8 cmd[4] = { 0x00, 0x0B, 0x00, 0x5A }; + + error = i2c_smbus_write_block_data(client, ETP_SMBUS_IAP_CMD, + sizeof(cmd), cmd); + if (error) { + dev_err(&client->dev, "cannot set flash key: %d\n", error); + return error; + } + + return 0; +} + +static int elan_smbus_prepare_fw_update(struct i2c_client *client) +{ + struct device *dev = &client->dev; + int len; + int error; + enum tp_mode mode; + u8 val[I2C_SMBUS_BLOCK_MAX] = {0}; + u8 cmd[4] = {0x0F, 0x78, 0x00, 0x06}; + u16 password; + + /* Get FW in which mode (IAP_MODE/MAIN_MODE) */ + error = elan_smbus_iap_get_mode(client, &mode); + if (error) + return error; + + if (mode == MAIN_MODE) { + + /* set flash key */ + error = elan_smbus_set_flash_key(client); + if (error) + return error; + + /* write iap password */ + if (i2c_smbus_write_byte(client, + ETP_SMBUS_IAP_PASSWORD_WRITE) < 0) { + dev_err(dev, "cannot write iap password\n"); + return -EIO; + } + + error = i2c_smbus_write_block_data(client, ETP_SMBUS_IAP_CMD, + sizeof(cmd), cmd); + if (error) { + dev_err(dev, "failed to write iap password: %d\n", + error); + return error; + } + + /* + * Read back password to make sure we enabled flash + * successfully. + */ + len = i2c_smbus_read_block_data(client, + ETP_SMBUS_IAP_PASSWORD_READ, + val); + if (len < (int)sizeof(u16)) { + error = len < 0 ? len : -EIO; + dev_err(dev, "failed to read iap password: %d\n", + error); + return error; + } + + password = be16_to_cpup((__be16 *)val); + if (password != ETP_SMBUS_IAP_PASSWORD) { + dev_err(dev, "wrong iap password = 0x%X\n", password); + return -EIO; + } + + /* Wait 30ms for MAIN_MODE change to IAP_MODE */ + msleep(30); + } + + error = elan_smbus_set_flash_key(client); + if (error) + return error; + + /* Reset IC */ + error = elan_smbus_iap_reset(client); + if (error) + return error; + + return 0; +} + + +static int elan_smbus_write_fw_block(struct i2c_client *client, + const u8 *page, u16 checksum, int idx) +{ + struct device *dev = &client->dev; + int error; + u16 result; + u8 val[I2C_SMBUS_BLOCK_MAX] = {0}; + + /* + * Due to the limitation of smbus protocol limiting + * transfer to 32 bytes at a time, we must split block + * in 2 transfers. + */ + error = i2c_smbus_write_block_data(client, + ETP_SMBUS_WRITE_FW_BLOCK, + ETP_FW_PAGE_SIZE / 2, + page); + if (error) { + dev_err(dev, "Failed to write page %d (part %d): %d\n", + idx, 1, error); + return error; + } + + error = i2c_smbus_write_block_data(client, + ETP_SMBUS_WRITE_FW_BLOCK, + ETP_FW_PAGE_SIZE / 2, + page + ETP_FW_PAGE_SIZE / 2); + if (error) { + dev_err(dev, "Failed to write page %d (part %d): %d\n", + idx, 2, error); + return error; + } + + + /* Wait for F/W to update one page ROM data. */ + usleep_range(8000, 10000); + + error = i2c_smbus_read_block_data(client, + ETP_SMBUS_IAP_CTRL_CMD, val); + if (error < 0) { + dev_err(dev, "Failed to read IAP write result: %d\n", + error); + return error; + } + + result = be16_to_cpup((__be16 *)val); + if (result & (ETP_FW_IAP_PAGE_ERR | ETP_FW_IAP_INTF_ERR)) { + dev_err(dev, "IAP reports failed write: %04hx\n", + result); + return -EIO; + } + + return 0; +} + +static int elan_smbus_get_report(struct i2c_client *client, u8 *report) +{ + int len; + + BUILD_BUG_ON(I2C_SMBUS_BLOCK_MAX > ETP_SMBUS_REPORT_LEN); + + len = i2c_smbus_read_block_data(client, + ETP_SMBUS_PACKET_QUERY, + &report[ETP_SMBUS_REPORT_OFFSET]); + if (len < 0) { + dev_err(&client->dev, "failed to read report data: %d\n", len); + return len; + } + + if (len != ETP_SMBUS_REPORT_LEN) { + dev_err(&client->dev, + "wrong report length (%d vs %d expected)\n", + len, ETP_SMBUS_REPORT_LEN); + return -EIO; + } + + return 0; +} + +static int elan_smbus_finish_fw_update(struct i2c_client *client, + struct completion *fw_completion) +{ + /* No special handling unlike I2C transport */ + return 0; +} + +static int elan_smbus_get_pattern(struct i2c_client *client, u8 *pattern) +{ + *pattern = 0; + return 0; +} + +const struct elan_transport_ops elan_smbus_ops = { + .initialize = elan_smbus_initialize, + .sleep_control = elan_smbus_sleep_control, + .power_control = elan_smbus_power_control, + .set_mode = elan_smbus_set_mode, + + .calibrate = elan_smbus_calibrate, + .calibrate_result = elan_smbus_calibrate_result, + + .get_baseline_data = elan_smbus_get_baseline_data, + + .get_version = elan_smbus_get_version, + .get_sm_version = elan_smbus_get_sm_version, + .get_product_id = elan_smbus_get_product_id, + .get_checksum = elan_smbus_get_checksum, + .get_pressure_adjustment = elan_smbus_get_pressure_adjustment, + + .get_max = elan_smbus_get_max, + .get_resolution = elan_smbus_get_resolution, + .get_num_traces = elan_smbus_get_num_traces, + + .iap_get_mode = elan_smbus_iap_get_mode, + .iap_reset = elan_smbus_iap_reset, + + .prepare_fw_update = elan_smbus_prepare_fw_update, + .write_fw_block = elan_smbus_write_fw_block, + .finish_fw_update = elan_smbus_finish_fw_update, + + .get_report = elan_smbus_get_report, + .get_pattern = elan_smbus_get_pattern, +}; diff --git a/drivers/input/mouse/elantech.c b/drivers/input/mouse/elantech.c new file mode 100644 index 000000000..a18d17f7e --- /dev/null +++ b/drivers/input/mouse/elantech.c @@ -0,0 +1,2073 @@ +/* + * 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/i2c.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/platform_device.h> +#include <linux/serio.h> +#include <linux/libps2.h> +#include <asm/unaligned.h> +#include "psmouse.h" +#include "elantech.h" +#include "elan_i2c.h" + +#define elantech_debug(fmt, ...) \ + do { \ + if (etd->info.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 (ps2_sliced_command(&psmouse->ps2dev, c) || + ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETINFO)) { + psmouse_err(psmouse, "%s query 0x%02x failed.\n", __func__, c); + return -1; + } + + return 0; +} + +/* + * 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; +} + +/* + * 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; +} + +/* + * 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->info.hw_version) { + case 1: + if (ps2_sliced_command(&psmouse->ps2dev, ETP_REGISTER_READ) || + ps2_sliced_command(&psmouse->ps2dev, 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->info.hw_version != 4) + *val = param[0]; + else + *val = param[1]; + + return rc; +} + +/* + * 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->info.hw_version) { + case 1: + if (ps2_sliced_command(&psmouse->ps2dev, ETP_REGISTER_WRITE) || + ps2_sliced_command(&psmouse->ps2dev, reg) || + ps2_sliced_command(&psmouse->ps2dev, 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; +} + +/* + * 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); +} + +/* + * 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->info.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->info.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); + + psmouse_report_standard_buttons(dev, packet[0]); + + if (etd->info.fw_version < 0x020000 && + (etd->info.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); +} + +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); + } +} + +/* 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); +} + +/* + * 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; + /* fall 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); + psmouse_report_standard_buttons(dev, packet[0]); + if (etd->info.reports_pressure) { + input_report_abs(dev, ABS_PRESSURE, pres); + input_report_abs(dev, ABS_TOOL_WIDTH, width); + } + + input_sync(dev); +} + +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: + + /* + * This firmware misreport coordinates for trackpoint + * occasionally. Discard packets outside of [-127, 127] range + * to prevent cursor jumps. + */ + if (packet[4] == 0x80 || packet[5] == 0x80 || + packet[1] >> 7 == packet[4] >> 7 || + packet[2] >> 7 == packet[5] >> 7) { + elantech_debug("discarding packet [%6ph]\n", packet); + break; + + } + x = packet[4] - (int)((packet[1]^0x80) << 1); + y = (int)((packet[2]^0x80) << 1) - packet[5]; + + psmouse_report_standard_buttons(tp_dev, packet[0]); + + 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->info.debug == 1) + elantech_packet_dump(psmouse); + + break; + } +} + +/* + * 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->info.fw_version & 0x001000) + input_report_key(dev, BTN_LEFT, packet[0] & 0x03); + else + psmouse_report_standard_buttons(dev, packet[0]); + + input_report_abs(dev, ABS_PRESSURE, pres); + input_report_abs(dev, ABS_TOOL_WIDTH, width); + + input_sync(dev); +} + +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->info.fw_version & 0x001000) + input_report_key(dev, BTN_LEFT, packet[0] & 0x03); + else + psmouse_report_standard_buttons(dev, packet[0]); + + input_mt_report_pointer_emulation(dev, true); + input_sync(dev); +} + +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); +} + +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) >> 4; + + input_mt_slot(dev, id); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, true); + + input_report_abs(dev, ABS_MT_POSITION_X, etd->mt[id].x); + input_report_abs(dev, ABS_MT_POSITION_Y, etd->mt[id].y); + input_report_abs(dev, ABS_MT_PRESSURE, pres); + input_report_abs(dev, ABS_MT_TOUCH_MAJOR, traces * etd->width); + /* report this for backwards compatibility */ + input_report_abs(dev, ABS_TOOL_WIDTH, traces); + + elantech_input_sync_v4(psmouse); +} + +static void process_packet_motion_v4(struct psmouse *psmouse) +{ + struct input_dev *dev = psmouse->dev; + struct elantech_data *etd = psmouse->private; + unsigned char *packet = psmouse->packet; + int weight, delta_x1 = 0, delta_y1 = 0, delta_x2 = 0, delta_y2 = 0; + int id, sid; + + id = ((packet[0] & 0xe0) >> 5) - 1; + if (id < 0) + return; + + sid = ((packet[3] & 0xe0) >> 5) - 1; + weight = (packet[0] & 0x10) ? ETP_WEIGHT_VALUE : 1; + /* + * Motion packets give us the delta of x, y values of specific fingers, + * but in two's complement. Let the compiler do the conversion for us. + * Also _enlarge_ the numbers to int, in case of overflow. + */ + delta_x1 = (signed char)packet[1]; + delta_y1 = (signed char)packet[2]; + delta_x2 = (signed char)packet[4]; + delta_y2 = (signed char)packet[5]; + + etd->mt[id].x += delta_x1 * weight; + etd->mt[id].y -= delta_y1 * weight; + input_mt_slot(dev, id); + input_report_abs(dev, ABS_MT_POSITION_X, etd->mt[id].x); + input_report_abs(dev, ABS_MT_POSITION_Y, etd->mt[id].y); + + if (sid >= 0) { + etd->mt[sid].x += delta_x2 * weight; + etd->mt[sid].y -= delta_y2 * weight; + input_mt_slot(dev, sid); + input_report_abs(dev, ABS_MT_POSITION_X, etd->mt[sid].x); + input_report_abs(dev, ABS_MT_POSITION_Y, etd->mt[sid].y); + } + + elantech_input_sync_v4(psmouse); +} + +static void elantech_report_absolute_v4(struct psmouse *psmouse, + int packet_type) +{ + switch (packet_type) { + case PACKET_V4_STATUS: + process_packet_status_v4(psmouse); + break; + + case PACKET_V4_HEAD: + process_packet_head_v4(psmouse); + break; + + case PACKET_V4_MOTION: + process_packet_motion_v4(psmouse); + break; + + case PACKET_UNKNOWN: + default: + /* impossible to get here */ + break; + } +} + +static int elantech_packet_check_v1(struct psmouse *psmouse) +{ + struct elantech_data *etd = psmouse->private; + unsigned char *packet = psmouse->packet; + unsigned char p1, p2, p3; + + /* Parity bits are placed differently */ + if (etd->info.fw_version < 0x020000) { + /* byte 0: D U p1 p2 1 p3 R L */ + p1 = (packet[0] & 0x20) >> 5; + p2 = (packet[0] & 0x10) >> 4; + } else { + /* byte 0: n1 n0 p2 p1 1 p3 R L */ + p1 = (packet[0] & 0x10) >> 4; + p2 = (packet[0] & 0x20) >> 5; + } + + p3 = (packet[0] & 0x04) >> 2; + + return etd->parity[packet[1]] == p1 && + etd->parity[packet[2]] == p2 && + etd->parity[packet[3]] == p3; +} + +static int elantech_debounce_check_v2(struct psmouse *psmouse) +{ + /* + * When we encounter packet that matches this exactly, it means the + * hardware is in debounce status. Just ignore the whole packet. + */ + static const u8 debounce_packet[] = { + 0x84, 0xff, 0xff, 0x02, 0xff, 0xff + }; + unsigned char *packet = psmouse->packet; + + return !memcmp(packet, debounce_packet, sizeof(debounce_packet)); +} + +static int elantech_packet_check_v2(struct psmouse *psmouse) +{ + struct elantech_data *etd = psmouse->private; + unsigned char *packet = psmouse->packet; + + /* + * V2 hardware has two flavors. Older ones that do not report pressure, + * and newer ones that reports pressure and width. With newer ones, all + * packets (1, 2, 3 finger touch) have the same constant bits. With + * older ones, 1/3 finger touch packets and 2 finger touch packets + * have different constant bits. + * With all three cases, if the constant bits are not exactly what I + * expected, I consider them invalid. + */ + if (etd->info.reports_pressure) + return (packet[0] & 0x0c) == 0x04 && + (packet[3] & 0x0f) == 0x02; + + if ((packet[0] & 0xc0) == 0x80) + return (packet[0] & 0x0c) == 0x0c && + (packet[3] & 0x0e) == 0x08; + + return (packet[0] & 0x3c) == 0x3c && + (packet[1] & 0xf0) == 0x00 && + (packet[3] & 0x3e) == 0x38 && + (packet[4] & 0xf0) == 0x00; +} + +/* + * We check the constant bits to determine what packet type we get, + * so packet checking is mandatory for v3 and later hardware. + */ +static int elantech_packet_check_v3(struct psmouse *psmouse) +{ + struct elantech_data *etd = psmouse->private; + static const u8 debounce_packet[] = { + 0xc4, 0xff, 0xff, 0x02, 0xff, 0xff + }; + unsigned char *packet = psmouse->packet; + + /* + * check debounce first, it has the same signature in byte 0 + * and byte 3 as PACKET_V3_HEAD. + */ + if (!memcmp(packet, debounce_packet, sizeof(debounce_packet))) + return PACKET_DEBOUNCE; + + /* + * If the hardware flag 'crc_enabled' is set the packets have + * different signatures. + */ + if (etd->info.crc_enabled) { + if ((packet[3] & 0x09) == 0x08) + return PACKET_V3_HEAD; + + if ((packet[3] & 0x09) == 0x09) + return PACKET_V3_TAIL; + } else { + if ((packet[0] & 0x0c) == 0x04 && (packet[3] & 0xcf) == 0x02) + return PACKET_V3_HEAD; + + if ((packet[0] & 0x0c) == 0x0c && (packet[3] & 0xce) == 0x0c) + return PACKET_V3_TAIL; + if ((packet[3] & 0x0f) == 0x06) + return PACKET_TRACKPOINT; + } + + return PACKET_UNKNOWN; +} + +static int elantech_packet_check_v4(struct psmouse *psmouse) +{ + struct elantech_data *etd = psmouse->private; + unsigned char *packet = psmouse->packet; + unsigned char packet_type = packet[3] & 0x03; + unsigned int ic_version; + bool sanity_check; + + if (etd->tp_dev && (packet[3] & 0x0f) == 0x06) + return PACKET_TRACKPOINT; + + /* This represents the version of IC body. */ + ic_version = (etd->info.fw_version & 0x0f0000) >> 16; + + /* + * Sanity check based on the constant bits of a packet. + * The constant bits change depending on the value of + * the hardware flag 'crc_enabled' and the version of + * the IC body, but are the same for every packet, + * regardless of the type. + */ + if (etd->info.crc_enabled) + sanity_check = ((packet[3] & 0x08) == 0x00); + else if (ic_version == 7 && etd->info.samples[1] == 0x2A) + sanity_check = ((packet[3] & 0x1c) == 0x10); + else + sanity_check = ((packet[0] & 0x08) == 0x00 && + (packet[3] & 0x1c) == 0x10); + + if (!sanity_check) + return PACKET_UNKNOWN; + + switch (packet_type) { + case 0: + return PACKET_V4_STATUS; + + case 1: + return PACKET_V4_HEAD; + + case 2: + return PACKET_V4_MOTION; + } + + return PACKET_UNKNOWN; +} + +/* + * Process byte stream from mouse and handle complete packets + */ +static psmouse_ret_t elantech_process_byte(struct psmouse *psmouse) +{ + struct elantech_data *etd = psmouse->private; + int packet_type; + + if (psmouse->pktcnt < psmouse->pktsize) + return PSMOUSE_GOOD_DATA; + + if (etd->info.debug > 1) + elantech_packet_dump(psmouse); + + switch (etd->info.hw_version) { + case 1: + if (etd->info.paritycheck && !elantech_packet_check_v1(psmouse)) + return PSMOUSE_BAD_DATA; + + elantech_report_absolute_v1(psmouse); + break; + + case 2: + /* ignore debounce */ + if (elantech_debounce_check_v2(psmouse)) + return PSMOUSE_FULL_PACKET; + + if (etd->info.paritycheck && !elantech_packet_check_v2(psmouse)) + return PSMOUSE_BAD_DATA; + + elantech_report_absolute_v2(psmouse); + break; + + case 3: + packet_type = elantech_packet_check_v3(psmouse); + switch (packet_type) { + case PACKET_UNKNOWN: + return PSMOUSE_BAD_DATA; + + case PACKET_DEBOUNCE: + /* ignore debounce */ + break; + + case PACKET_TRACKPOINT: + elantech_report_trackpoint(psmouse, packet_type); + break; + + default: + elantech_report_absolute_v3(psmouse, packet_type); + break; + } + + break; + + case 4: + packet_type = elantech_packet_check_v4(psmouse); + switch (packet_type) { + case PACKET_UNKNOWN: + return PSMOUSE_BAD_DATA; + + case PACKET_TRACKPOINT: + elantech_report_trackpoint(psmouse, packet_type); + break; + + default: + elantech_report_absolute_v4(psmouse, packet_type); + break; + } + + break; + } + + return PSMOUSE_FULL_PACKET; +} + +/* + * This writes the reg_07 value again to the hardware at the end of every + * set_rate call because the register loses its value. reg_07 allows setting + * absolute mode on v4 hardware + */ +static void elantech_set_rate_restore_reg_07(struct psmouse *psmouse, + unsigned int rate) +{ + struct elantech_data *etd = psmouse->private; + + etd->original_set_rate(psmouse, rate); + if (elantech_write_reg(psmouse, 0x07, etd->reg_07)) + psmouse_err(psmouse, "restoring reg_07 failed\n"); +} + +/* + * Put the touchpad into absolute mode + */ +static int elantech_set_absolute_mode(struct psmouse *psmouse) +{ + struct elantech_data *etd = psmouse->private; + unsigned char val; + int tries = ETP_READ_BACK_TRIES; + int rc = 0; + + switch (etd->info.hw_version) { + case 1: + etd->reg_10 = 0x16; + etd->reg_11 = 0x8f; + if (elantech_write_reg(psmouse, 0x10, etd->reg_10) || + elantech_write_reg(psmouse, 0x11, etd->reg_11)) { + rc = -1; + } + break; + + case 2: + /* Windows driver values */ + etd->reg_10 = 0x54; + etd->reg_11 = 0x88; /* 0x8a */ + etd->reg_21 = 0x60; /* 0x00 */ + if (elantech_write_reg(psmouse, 0x10, etd->reg_10) || + elantech_write_reg(psmouse, 0x11, etd->reg_11) || + elantech_write_reg(psmouse, 0x21, etd->reg_21)) { + rc = -1; + } + break; + + case 3: + if (etd->info.set_hw_resolution) + etd->reg_10 = 0x0b; + else + etd->reg_10 = 0x01; + + if (elantech_write_reg(psmouse, 0x10, etd->reg_10)) + rc = -1; + + break; + + case 4: + etd->reg_07 = 0x01; + if (elantech_write_reg(psmouse, 0x07, etd->reg_07)) + rc = -1; + + goto skip_readback_reg_10; /* v4 has no reg 0x10 to read */ + } + + if (rc == 0) { + /* + * Read back reg 0x10. For hardware version 1 we must make + * sure the absolute mode bit is set. For hardware version 2 + * the touchpad is probably initializing and not ready until + * we read back the value we just wrote. + */ + do { + rc = elantech_read_reg(psmouse, 0x10, &val); + if (rc == 0) + break; + tries--; + elantech_debug("retrying read (%d).\n", tries); + msleep(ETP_READ_BACK_DELAY); + } while (tries > 0); + + if (rc) { + psmouse_err(psmouse, + "failed to read back register 0x10.\n"); + } else if (etd->info.hw_version == 1 && + !(val & ETP_R10_ABSOLUTE_MODE)) { + psmouse_err(psmouse, + "touchpad refuses to switch to absolute mode.\n"); + rc = -1; + } + } + + skip_readback_reg_10: + if (rc) + psmouse_err(psmouse, "failed to initialise registers.\n"); + + return rc; +} + +static int elantech_set_range(struct psmouse *psmouse, + unsigned int *x_min, unsigned int *y_min, + unsigned int *x_max, unsigned int *y_max, + unsigned int *width) +{ + struct elantech_data *etd = psmouse->private; + struct elantech_device_info *info = &etd->info; + unsigned char param[3]; + unsigned char traces; + + switch (info->hw_version) { + case 1: + *x_min = ETP_XMIN_V1; + *y_min = ETP_YMIN_V1; + *x_max = ETP_XMAX_V1; + *y_max = ETP_YMAX_V1; + break; + + case 2: + if (info->fw_version == 0x020800 || + info->fw_version == 0x020b00 || + info->fw_version == 0x020030) { + *x_min = ETP_XMIN_V2; + *y_min = ETP_YMIN_V2; + *x_max = ETP_XMAX_V2; + *y_max = ETP_YMAX_V2; + } else { + int i; + int fixed_dpi; + + i = (info->fw_version > 0x020800 && + info->fw_version < 0x020900) ? 1 : 2; + + if (info->send_cmd(psmouse, ETP_FW_ID_QUERY, param)) + return -1; + + fixed_dpi = param[1] & 0x10; + + if (((info->fw_version >> 16) == 0x14) && fixed_dpi) { + if (info->send_cmd(psmouse, ETP_SAMPLE_QUERY, param)) + return -1; + + *x_max = (info->capabilities[1] - i) * param[1] / 2; + *y_max = (info->capabilities[2] - i) * param[2] / 2; + } else if (info->fw_version == 0x040216) { + *x_max = 819; + *y_max = 405; + } else if (info->fw_version == 0x040219 || info->fw_version == 0x040215) { + *x_max = 900; + *y_max = 500; + } else { + *x_max = (info->capabilities[1] - i) * 64; + *y_max = (info->capabilities[2] - i) * 64; + } + } + break; + + case 3: + if (info->send_cmd(psmouse, ETP_FW_ID_QUERY, param)) + return -1; + + *x_max = (0x0f & param[0]) << 8 | param[1]; + *y_max = (0xf0 & param[0]) << 4 | param[2]; + break; + + case 4: + if (info->send_cmd(psmouse, ETP_FW_ID_QUERY, param)) + return -1; + + *x_max = (0x0f & param[0]) << 8 | param[1]; + *y_max = (0xf0 & param[0]) << 4 | param[2]; + traces = info->capabilities[1]; + if ((traces < 2) || (traces > *x_max)) + return -1; + + *width = *x_max / (traces - 1); + break; + } + + return 0; +} + +/* + * (value from firmware) * 10 + 790 = dpi + * we also have to convert dpi to dots/mm (*10/254 to avoid floating point) + */ +static unsigned int elantech_convert_res(unsigned int val) +{ + return (val * 10 + 790) * 10 / 254; +} + +static int elantech_get_resolution_v4(struct psmouse *psmouse, + unsigned int *x_res, + unsigned int *y_res, + unsigned int *bus) +{ + unsigned char param[3]; + + if (elantech_send_cmd(psmouse, ETP_RESOLUTION_QUERY, param)) + return -1; + + *x_res = elantech_convert_res(param[1] & 0x0f); + *y_res = elantech_convert_res((param[1] & 0xf0) >> 4); + *bus = param[2]; + + return 0; +} + +/* + * Advertise INPUT_PROP_BUTTONPAD for clickpads. The testing of bit 12 in + * fw_version for this is based on the following fw_version & caps table: + * + * Laptop-model: fw_version: caps: buttons: + * Acer S3 0x461f00 10, 13, 0e clickpad + * Acer S7-392 0x581f01 50, 17, 0d clickpad + * Acer V5-131 0x461f02 01, 16, 0c clickpad + * Acer V5-551 0x461f00 ? clickpad + * Asus K53SV 0x450f01 78, 15, 0c 2 hw buttons + * Asus G46VW 0x460f02 00, 18, 0c 2 hw buttons + * Asus G750JX 0x360f00 00, 16, 0c 2 hw buttons + * Asus TP500LN 0x381f17 10, 14, 0e clickpad + * Asus X750JN 0x381f17 10, 14, 0e clickpad + * Asus UX31 0x361f00 20, 15, 0e clickpad + * Asus UX32VD 0x361f02 00, 15, 0e clickpad + * Avatar AVIU-145A2 0x361f00 ? clickpad + * Fujitsu CELSIUS H760 0x570f02 40, 14, 0c 3 hw buttons (**) + * Fujitsu CELSIUS H780 0x5d0f02 41, 16, 0d 3 hw buttons (**) + * Fujitsu LIFEBOOK E544 0x470f00 d0, 12, 09 2 hw buttons + * Fujitsu LIFEBOOK E546 0x470f00 50, 12, 09 2 hw buttons + * Fujitsu LIFEBOOK E547 0x470f00 50, 12, 09 2 hw buttons + * Fujitsu LIFEBOOK E554 0x570f01 40, 14, 0c 2 hw buttons + * Fujitsu LIFEBOOK E557 0x570f01 40, 14, 0c 2 hw buttons + * Fujitsu T725 0x470f01 05, 12, 09 2 hw buttons + * Fujitsu H730 0x570f00 c0, 14, 0c 3 hw buttons (**) + * Gigabyte U2442 0x450f01 58, 17, 0c 2 hw buttons + * Lenovo L430 0x350f02 b9, 15, 0c 2 hw buttons (*) + * Lenovo L530 0x350f02 b9, 15, 0c 2 hw buttons (*) + * Samsung NF210 0x150b00 78, 14, 0a 2 hw buttons + * Samsung NP770Z5E 0x575f01 10, 15, 0f clickpad + * Samsung NP700Z5B 0x361f06 21, 15, 0f clickpad + * Samsung NP900X3E-A02 0x575f03 ? clickpad + * Samsung NP-QX410 0x851b00 19, 14, 0c clickpad + * Samsung RC512 0x450f00 08, 15, 0c 2 hw buttons + * Samsung RF710 0x450f00 ? 2 hw buttons + * System76 Pangolin 0x250f01 ? 2 hw buttons + * (*) + 3 trackpoint buttons + * (**) + 0 trackpoint buttons + * Note: Lenovo L430 and Lenovo L530 have the same fw_version/caps + */ +static void elantech_set_buttonpad_prop(struct psmouse *psmouse) +{ + struct input_dev *dev = psmouse->dev; + struct elantech_data *etd = psmouse->private; + + if (etd->info.fw_version & 0x001000) { + __set_bit(INPUT_PROP_BUTTONPAD, dev->propbit); + __clear_bit(BTN_RIGHT, dev->keybit); + } +} + +/* + * Some hw_version 4 models do have a middle button + */ +static const struct dmi_system_id elantech_dmi_has_middle_button[] = { +#if defined(CONFIG_DMI) && defined(CONFIG_X86) + { + /* Fujitsu H730 has a middle button */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "CELSIUS H730"), + }, + }, + { + /* Fujitsu H760 also has a middle button */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "CELSIUS H760"), + }, + }, + { + /* Fujitsu H780 also has a middle button */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "CELSIUS H780"), + }, + }, +#endif + { } +}; + +static const char * const middle_button_pnp_ids[] = { + "LEN2131", /* ThinkPad P52 w/ NFC */ + "LEN2132", /* ThinkPad P52 */ + "LEN2133", /* ThinkPad P72 w/ NFC */ + "LEN2134", /* ThinkPad P72 */ + "LEN0407", + "LEN0408", + NULL +}; + +/* + * Set the appropriate event bits for the input subsystem + */ +static int elantech_set_input_params(struct psmouse *psmouse) +{ + struct input_dev *dev = psmouse->dev; + struct elantech_data *etd = psmouse->private; + struct elantech_device_info *info = &etd->info; + unsigned int x_min = 0, y_min = 0, x_max = 0, y_max = 0, width = 0; + + if (elantech_set_range(psmouse, &x_min, &y_min, &x_max, &y_max, &width)) + return -1; + + __set_bit(INPUT_PROP_POINTER, dev->propbit); + __set_bit(EV_KEY, dev->evbit); + __set_bit(EV_ABS, dev->evbit); + __clear_bit(EV_REL, dev->evbit); + + __set_bit(BTN_LEFT, dev->keybit); + if (dmi_check_system(elantech_dmi_has_middle_button) || + psmouse_matches_pnp_id(psmouse, middle_button_pnp_ids)) + __set_bit(BTN_MIDDLE, dev->keybit); + __set_bit(BTN_RIGHT, dev->keybit); + + __set_bit(BTN_TOUCH, dev->keybit); + __set_bit(BTN_TOOL_FINGER, dev->keybit); + __set_bit(BTN_TOOL_DOUBLETAP, dev->keybit); + __set_bit(BTN_TOOL_TRIPLETAP, dev->keybit); + + switch (info->hw_version) { + case 1: + /* Rocker button */ + if (info->fw_version < 0x020000 && + (info->capabilities[0] & ETP_CAP_HAS_ROCKER)) { + __set_bit(BTN_FORWARD, dev->keybit); + __set_bit(BTN_BACK, dev->keybit); + } + input_set_abs_params(dev, ABS_X, x_min, x_max, 0, 0); + input_set_abs_params(dev, ABS_Y, y_min, y_max, 0, 0); + break; + + case 2: + __set_bit(BTN_TOOL_QUADTAP, dev->keybit); + __set_bit(INPUT_PROP_SEMI_MT, dev->propbit); + /* fall through */ + case 3: + if (info->hw_version == 3) + elantech_set_buttonpad_prop(psmouse); + input_set_abs_params(dev, ABS_X, x_min, x_max, 0, 0); + input_set_abs_params(dev, ABS_Y, y_min, y_max, 0, 0); + if (info->reports_pressure) { + input_set_abs_params(dev, ABS_PRESSURE, ETP_PMIN_V2, + ETP_PMAX_V2, 0, 0); + input_set_abs_params(dev, ABS_TOOL_WIDTH, ETP_WMIN_V2, + ETP_WMAX_V2, 0, 0); + } + input_mt_init_slots(dev, 2, INPUT_MT_SEMI_MT); + input_set_abs_params(dev, ABS_MT_POSITION_X, x_min, x_max, 0, 0); + input_set_abs_params(dev, ABS_MT_POSITION_Y, y_min, y_max, 0, 0); + break; + + case 4: + elantech_set_buttonpad_prop(psmouse); + __set_bit(BTN_TOOL_QUADTAP, dev->keybit); + /* For X to recognize me as touchpad. */ + input_set_abs_params(dev, ABS_X, x_min, x_max, 0, 0); + input_set_abs_params(dev, ABS_Y, y_min, y_max, 0, 0); + /* + * range of pressure and width is the same as v2, + * report ABS_PRESSURE, ABS_TOOL_WIDTH for compatibility. + */ + input_set_abs_params(dev, ABS_PRESSURE, ETP_PMIN_V2, + ETP_PMAX_V2, 0, 0); + input_set_abs_params(dev, ABS_TOOL_WIDTH, ETP_WMIN_V2, + ETP_WMAX_V2, 0, 0); + /* Multitouch capable pad, up to 5 fingers. */ + input_mt_init_slots(dev, ETP_MAX_FINGERS, 0); + input_set_abs_params(dev, ABS_MT_POSITION_X, x_min, x_max, 0, 0); + input_set_abs_params(dev, ABS_MT_POSITION_Y, y_min, y_max, 0, 0); + input_set_abs_params(dev, ABS_MT_PRESSURE, ETP_PMIN_V2, + ETP_PMAX_V2, 0, 0); + /* + * The firmware reports how many trace lines the finger spans, + * convert to surface unit as Protocol-B requires. + */ + input_set_abs_params(dev, ABS_MT_TOUCH_MAJOR, 0, + ETP_WMAX_V2 * width, 0, 0); + break; + } + + input_abs_set_res(dev, ABS_X, info->x_res); + input_abs_set_res(dev, ABS_Y, info->y_res); + if (info->hw_version > 1) { + input_abs_set_res(dev, ABS_MT_POSITION_X, info->x_res); + input_abs_set_res(dev, ABS_MT_POSITION_Y, info->y_res); + } + + etd->y_max = y_max; + etd->width = width; + + return 0; +} + +struct elantech_attr_data { + size_t field_offset; + unsigned char reg; +}; + +/* + * Display a register value by reading a sysfs entry + */ +static ssize_t elantech_show_int_attr(struct psmouse *psmouse, void *data, + char *buf) +{ + struct elantech_data *etd = psmouse->private; + struct elantech_attr_data *attr = data; + unsigned char *reg = (unsigned char *) etd + attr->field_offset; + int rc = 0; + + if (attr->reg) + rc = elantech_read_reg(psmouse, attr->reg, reg); + + return sprintf(buf, "0x%02x\n", (attr->reg && rc) ? -1 : *reg); +} + +/* + * Write a register value by writing a sysfs entry + */ +static ssize_t elantech_set_int_attr(struct psmouse *psmouse, + void *data, const char *buf, size_t count) +{ + struct elantech_data *etd = psmouse->private; + struct elantech_attr_data *attr = data; + unsigned char *reg = (unsigned char *) etd + attr->field_offset; + unsigned char value; + int err; + + err = kstrtou8(buf, 16, &value); + if (err) + return err; + + /* Do we need to preserve some bits for version 2 hardware too? */ + if (etd->info.hw_version == 1) { + if (attr->reg == 0x10) + /* Force absolute mode always on */ + value |= ETP_R10_ABSOLUTE_MODE; + else if (attr->reg == 0x11) + /* Force 4 byte mode always on */ + value |= ETP_R11_4_BYTE_MODE; + } + + if (!attr->reg || elantech_write_reg(psmouse, attr->reg, value) == 0) + *reg = value; + + return count; +} + +#define ELANTECH_INT_ATTR(_name, _register) \ + static struct elantech_attr_data elantech_attr_##_name = { \ + .field_offset = offsetof(struct elantech_data, _name), \ + .reg = _register, \ + }; \ + PSMOUSE_DEFINE_ATTR(_name, 0644, \ + &elantech_attr_##_name, \ + elantech_show_int_attr, \ + elantech_set_int_attr) + +#define ELANTECH_INFO_ATTR(_name) \ + static struct elantech_attr_data elantech_attr_##_name = { \ + .field_offset = offsetof(struct elantech_data, info) + \ + offsetof(struct elantech_device_info, _name), \ + .reg = 0, \ + }; \ + PSMOUSE_DEFINE_ATTR(_name, 0644, \ + &elantech_attr_##_name, \ + elantech_show_int_attr, \ + elantech_set_int_attr) + +ELANTECH_INT_ATTR(reg_07, 0x07); +ELANTECH_INT_ATTR(reg_10, 0x10); +ELANTECH_INT_ATTR(reg_11, 0x11); +ELANTECH_INT_ATTR(reg_20, 0x20); +ELANTECH_INT_ATTR(reg_21, 0x21); +ELANTECH_INT_ATTR(reg_22, 0x22); +ELANTECH_INT_ATTR(reg_23, 0x23); +ELANTECH_INT_ATTR(reg_24, 0x24); +ELANTECH_INT_ATTR(reg_25, 0x25); +ELANTECH_INT_ATTR(reg_26, 0x26); +ELANTECH_INFO_ATTR(debug); +ELANTECH_INFO_ATTR(paritycheck); +ELANTECH_INFO_ATTR(crc_enabled); + +static struct attribute *elantech_attrs[] = { + &psmouse_attr_reg_07.dattr.attr, + &psmouse_attr_reg_10.dattr.attr, + &psmouse_attr_reg_11.dattr.attr, + &psmouse_attr_reg_20.dattr.attr, + &psmouse_attr_reg_21.dattr.attr, + &psmouse_attr_reg_22.dattr.attr, + &psmouse_attr_reg_23.dattr.attr, + &psmouse_attr_reg_24.dattr.attr, + &psmouse_attr_reg_25.dattr.attr, + &psmouse_attr_reg_26.dattr.attr, + &psmouse_attr_debug.dattr.attr, + &psmouse_attr_paritycheck.dattr.attr, + &psmouse_attr_crc_enabled.dattr.attr, + NULL +}; + +static const struct attribute_group elantech_attr_group = { + .attrs = elantech_attrs, +}; + +static bool elantech_is_signature_valid(const unsigned char *param) +{ + static const unsigned char rates[] = { 200, 100, 80, 60, 40, 20, 10 }; + int i; + + if (param[0] == 0) + return false; + + if (param[1] == 0) + return true; + + /* + * Some hw_version >= 4 models have a revision higher then 20. Meaning + * that param[2] may be 10 or 20, skip the rates check for these. + */ + if ((param[0] & 0x0f) >= 0x06 && (param[1] & 0xaf) == 0x0f && + param[2] < 40) + return true; + + for (i = 0; i < ARRAY_SIZE(rates); i++) + if (param[2] == rates[i]) + return false; + + return true; +} + +/* + * Use magic knock to detect Elantech touchpad + */ +int elantech_detect(struct psmouse *psmouse, bool set_properties) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[3]; + + ps2_command(ps2dev, NULL, PSMOUSE_CMD_RESET_DIS); + + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) || + ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) { + psmouse_dbg(psmouse, "sending Elantech magic knock failed.\n"); + return -1; + } + + /* + * Report this in case there are Elantech models that use a different + * set of magic numbers + */ + if (param[0] != 0x3c || param[1] != 0x03 || + (param[2] != 0xc8 && param[2] != 0x00)) { + psmouse_dbg(psmouse, + "unexpected magic knock result 0x%02x, 0x%02x, 0x%02x.\n", + param[0], param[1], param[2]); + return -1; + } + + /* + * Query touchpad's firmware version and see if it reports known + * value to avoid mis-detection. Logitech mice are known to respond + * to Elantech magic knock and there might be more. + */ + if (synaptics_send_cmd(psmouse, ETP_FW_VERSION_QUERY, param)) { + psmouse_dbg(psmouse, "failed to query firmware version.\n"); + return -1; + } + + psmouse_dbg(psmouse, + "Elantech version query result 0x%02x, 0x%02x, 0x%02x.\n", + param[0], param[1], param[2]); + + if (!elantech_is_signature_valid(param)) { + psmouse_dbg(psmouse, + "Probably not a real Elantech touchpad. Aborting.\n"); + return -1; + } + + if (set_properties) { + psmouse->vendor = "Elantech"; + psmouse->name = "Touchpad"; + } + + return 0; +} + +/* + * Clean up sysfs entries when disconnecting + */ +static void elantech_disconnect(struct psmouse *psmouse) +{ + struct elantech_data *etd = psmouse->private; + + /* + * We might have left a breadcrumb when trying to + * set up SMbus companion. + */ + psmouse_smbus_cleanup(psmouse); + + if (etd->tp_dev) + input_unregister_device(etd->tp_dev); + sysfs_remove_group(&psmouse->ps2dev.serio->dev.kobj, + &elantech_attr_group); + kfree(psmouse->private); + psmouse->private = NULL; +} + +/* + * Put the touchpad back into absolute mode when reconnecting + */ +static int elantech_reconnect(struct psmouse *psmouse) +{ + psmouse_reset(psmouse); + + if (elantech_detect(psmouse, 0)) + return -1; + + if (elantech_set_absolute_mode(psmouse)) { + psmouse_err(psmouse, + "failed to put touchpad back into absolute mode.\n"); + return -1; + } + + return 0; +} + +/* + * Some hw_version 4 models do not work with crc_disabled + */ +static const struct dmi_system_id elantech_dmi_force_crc_enabled[] = { +#if defined(CONFIG_DMI) && defined(CONFIG_X86) + { + /* Fujitsu H730 does not work with crc_enabled == 0 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "CELSIUS H730"), + }, + }, + { + /* Fujitsu H760 does not work with crc_enabled == 0 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "CELSIUS H760"), + }, + }, + { + /* Fujitsu LIFEBOOK E544 does not work with crc_enabled == 0 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK E544"), + }, + }, + { + /* Fujitsu LIFEBOOK E546 does not work with crc_enabled == 0 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK E546"), + }, + }, + { + /* Fujitsu LIFEBOOK E547 does not work with crc_enabled == 0 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK E547"), + }, + }, + { + /* Fujitsu LIFEBOOK E554 does not work with crc_enabled == 0 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK E554"), + }, + }, + { + /* Fujitsu LIFEBOOK E556 does not work with crc_enabled == 0 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK E556"), + }, + }, + { + /* Fujitsu LIFEBOOK E557 does not work with crc_enabled == 0 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK E557"), + }, + }, + { + /* Fujitsu LIFEBOOK U745 does not work with crc_enabled == 0 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK U745"), + }, + }, +#endif + { } +}; + +/* + * Some hw_version 3 models go into error state when we try to set + * bit 3 and/or bit 1 of r10. + */ +static const struct dmi_system_id no_hw_res_dmi_table[] = { +#if defined(CONFIG_DMI) && defined(CONFIG_X86) + { + /* Gigabyte U2442 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GIGABYTE"), + DMI_MATCH(DMI_PRODUCT_NAME, "U2442"), + }, + }, +#endif + { } +}; + +/* + * determine hardware version and set some properties according to it. + */ +static int elantech_set_properties(struct elantech_device_info *info) +{ + /* This represents the version of IC body. */ + int ver = (info->fw_version & 0x0f0000) >> 16; + + /* Early version of Elan touchpads doesn't obey the rule. */ + if (info->fw_version < 0x020030 || info->fw_version == 0x020600) + info->hw_version = 1; + else { + switch (ver) { + case 2: + case 4: + info->hw_version = 2; + break; + case 5: + info->hw_version = 3; + break; + case 6 ... 15: + info->hw_version = 4; + break; + default: + return -1; + } + } + + /* decide which send_cmd we're gonna use early */ + info->send_cmd = info->hw_version >= 3 ? elantech_send_cmd : + synaptics_send_cmd; + + /* Turn on packet checking by default */ + info->paritycheck = 1; + + /* + * This firmware suffers from misreporting coordinates when + * a touch action starts causing the mouse cursor or scrolled page + * to jump. Enable a workaround. + */ + info->jumpy_cursor = + (info->fw_version == 0x020022 || info->fw_version == 0x020600); + + if (info->hw_version > 1) { + /* For now show extra debug information */ + info->debug = 1; + + if (info->fw_version >= 0x020800) + info->reports_pressure = true; + } + + /* + * The signatures of v3 and v4 packets change depending on the + * value of this hardware flag. + */ + info->crc_enabled = (info->fw_version & 0x4000) == 0x4000 || + dmi_check_system(elantech_dmi_force_crc_enabled); + + /* Enable real hardware resolution on hw_version 3 ? */ + info->set_hw_resolution = !dmi_check_system(no_hw_res_dmi_table); + + return 0; +} + +static int elantech_query_info(struct psmouse *psmouse, + struct elantech_device_info *info) +{ + unsigned char param[3]; + + memset(info, 0, sizeof(*info)); + + /* + * Do the version query again so we can store the result + */ + if (synaptics_send_cmd(psmouse, ETP_FW_VERSION_QUERY, param)) { + psmouse_err(psmouse, "failed to query firmware version.\n"); + return -EINVAL; + } + info->fw_version = (param[0] << 16) | (param[1] << 8) | param[2]; + + if (elantech_set_properties(info)) { + psmouse_err(psmouse, "unknown hardware version, aborting...\n"); + return -EINVAL; + } + psmouse_info(psmouse, + "assuming hardware version %d (with firmware version 0x%02x%02x%02x)\n", + info->hw_version, param[0], param[1], param[2]); + + if (info->send_cmd(psmouse, ETP_CAPABILITIES_QUERY, + info->capabilities)) { + psmouse_err(psmouse, "failed to query capabilities.\n"); + return -EINVAL; + } + psmouse_info(psmouse, + "Synaptics capabilities query result 0x%02x, 0x%02x, 0x%02x.\n", + info->capabilities[0], info->capabilities[1], + info->capabilities[2]); + + if (info->hw_version != 1) { + if (info->send_cmd(psmouse, ETP_SAMPLE_QUERY, info->samples)) { + psmouse_err(psmouse, "failed to query sample data\n"); + return -EINVAL; + } + psmouse_info(psmouse, + "Elan sample query result %02x, %02x, %02x\n", + info->samples[0], + info->samples[1], + info->samples[2]); + } + + if (info->samples[1] == 0x74 && info->hw_version == 0x03) { + /* + * This module has a bug which makes absolute mode + * unusable, so let's abort so we'll be using standard + * PS/2 protocol. + */ + psmouse_info(psmouse, + "absolute mode broken, forcing standard PS/2 protocol\n"); + return -ENODEV; + } + + /* The MSB indicates the presence of the trackpoint */ + info->has_trackpoint = (info->capabilities[0] & 0x80) == 0x80; + + info->x_res = 31; + info->y_res = 31; + if (info->hw_version == 4) { + if (elantech_get_resolution_v4(psmouse, + &info->x_res, + &info->y_res, + &info->bus)) { + psmouse_warn(psmouse, + "failed to query resolution data.\n"); + } + } + + return 0; +} + +#if defined(CONFIG_MOUSE_PS2_ELANTECH_SMBUS) + +/* + * The newest Elantech device can use a secondary bus (over SMBus) which + * provides a better bandwidth and allow a better control of the touchpads. + * This is used to decide if we need to use this bus or not. + */ +enum { + ELANTECH_SMBUS_NOT_SET = -1, + ELANTECH_SMBUS_OFF, + ELANTECH_SMBUS_ON, +}; + +static int elantech_smbus = IS_ENABLED(CONFIG_MOUSE_ELAN_I2C_SMBUS) ? + ELANTECH_SMBUS_NOT_SET : ELANTECH_SMBUS_OFF; +module_param_named(elantech_smbus, elantech_smbus, int, 0644); +MODULE_PARM_DESC(elantech_smbus, "Use a secondary bus for the Elantech device."); + +static const char * const i2c_blacklist_pnp_ids[] = { + /* + * These are known to not be working properly as bits are missing + * in elan_i2c. + */ + "LEN2131", /* ThinkPad P52 w/ NFC */ + "LEN2132", /* ThinkPad P52 */ + "LEN2133", /* ThinkPad P72 w/ NFC */ + "LEN2134", /* ThinkPad P72 */ + NULL +}; + +static int elantech_create_smbus(struct psmouse *psmouse, + struct elantech_device_info *info, + bool leave_breadcrumbs) +{ + const struct property_entry i2c_properties[] = { + PROPERTY_ENTRY_BOOL("elan,trackpoint"), + { }, + }; + struct i2c_board_info smbus_board = { + I2C_BOARD_INFO("elan_i2c", 0x15), + .flags = I2C_CLIENT_HOST_NOTIFY, + }; + + if (info->has_trackpoint) + smbus_board.properties = i2c_properties; + + return psmouse_smbus_init(psmouse, &smbus_board, NULL, 0, false, + leave_breadcrumbs); +} + +/** + * elantech_setup_smbus - called once the PS/2 devices are enumerated + * and decides to instantiate a SMBus InterTouch device. + */ +static int elantech_setup_smbus(struct psmouse *psmouse, + struct elantech_device_info *info, + bool leave_breadcrumbs) +{ + int error; + + if (elantech_smbus == ELANTECH_SMBUS_OFF) + return -ENXIO; + + if (elantech_smbus == ELANTECH_SMBUS_NOT_SET) { + /* + * New ICs are enabled by default, unless mentioned in + * i2c_blacklist_pnp_ids. + * Old ICs are up to the user to decide. + */ + if (!ETP_NEW_IC_SMBUS_HOST_NOTIFY(info->fw_version) || + psmouse_matches_pnp_id(psmouse, i2c_blacklist_pnp_ids)) + return -ENXIO; + } + + psmouse_info(psmouse, "Trying to set up SMBus access\n"); + + error = elantech_create_smbus(psmouse, info, leave_breadcrumbs); + if (error) { + if (error == -EAGAIN) + psmouse_info(psmouse, "SMbus companion is not ready yet\n"); + else + psmouse_err(psmouse, "unable to create intertouch device\n"); + + return error; + } + + return 0; +} + +static bool elantech_use_host_notify(struct psmouse *psmouse, + struct elantech_device_info *info) +{ + if (ETP_NEW_IC_SMBUS_HOST_NOTIFY(info->fw_version)) + return true; + + switch (info->bus) { + case ETP_BUS_PS2_ONLY: + /* expected case */ + break; + case ETP_BUS_SMB_ALERT_ONLY: + /* fall-through */ + case ETP_BUS_PS2_SMB_ALERT: + psmouse_dbg(psmouse, "Ignoring SMBus provider through alert protocol.\n"); + break; + case ETP_BUS_SMB_HST_NTFY_ONLY: + /* fall-through */ + case ETP_BUS_PS2_SMB_HST_NTFY: + return true; + default: + psmouse_dbg(psmouse, + "Ignoring SMBus bus provider %d.\n", + info->bus); + } + + return false; +} + +int elantech_init_smbus(struct psmouse *psmouse) +{ + struct elantech_device_info info; + int error = -EINVAL; + + psmouse_reset(psmouse); + + error = elantech_query_info(psmouse, &info); + if (error) + goto init_fail; + + if (info.hw_version < 4) { + error = -ENXIO; + goto init_fail; + } + + return elantech_create_smbus(psmouse, &info, false); + init_fail: + psmouse_reset(psmouse); + return error; +} +#endif /* CONFIG_MOUSE_PS2_ELANTECH_SMBUS */ + +/* + * Initialize the touchpad and create sysfs entries + */ +static int elantech_setup_ps2(struct psmouse *psmouse, + struct elantech_device_info *info) +{ + struct elantech_data *etd; + int i; + int error = -EINVAL; + struct input_dev *tp_dev; + + psmouse->private = etd = kzalloc(sizeof(*etd), GFP_KERNEL); + if (!etd) + return -ENOMEM; + + etd->info = *info; + + etd->parity[0] = 1; + for (i = 1; i < 256; i++) + etd->parity[i] = etd->parity[i & (i - 1)] ^ 1; + + if (elantech_set_absolute_mode(psmouse)) { + psmouse_err(psmouse, + "failed to put touchpad into absolute mode.\n"); + goto init_fail; + } + + if (info->fw_version == 0x381f17) { + etd->original_set_rate = psmouse->set_rate; + psmouse->set_rate = elantech_set_rate_restore_reg_07; + } + + if (elantech_set_input_params(psmouse)) { + psmouse_err(psmouse, "failed to query touchpad range.\n"); + goto init_fail; + } + + error = sysfs_create_group(&psmouse->ps2dev.serio->dev.kobj, + &elantech_attr_group); + if (error) { + psmouse_err(psmouse, + "failed to create sysfs attributes, error: %d.\n", + error); + goto init_fail; + } + + if (info->has_trackpoint) { + tp_dev = input_allocate_device(); + + if (!tp_dev) { + error = -ENOMEM; + goto init_fail_tp_alloc; + } + + etd->tp_dev = tp_dev; + snprintf(etd->tp_phys, sizeof(etd->tp_phys), "%s/input1", + psmouse->ps2dev.serio->phys); + tp_dev->phys = etd->tp_phys; + tp_dev->name = "ETPS/2 Elantech TrackPoint"; + tp_dev->id.bustype = BUS_I8042; + tp_dev->id.vendor = 0x0002; + tp_dev->id.product = PSMOUSE_ELANTECH; + tp_dev->id.version = 0x0000; + tp_dev->dev.parent = &psmouse->ps2dev.serio->dev; + tp_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + tp_dev->relbit[BIT_WORD(REL_X)] = + BIT_MASK(REL_X) | BIT_MASK(REL_Y); + tp_dev->keybit[BIT_WORD(BTN_LEFT)] = + BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_MIDDLE) | + BIT_MASK(BTN_RIGHT); + + __set_bit(INPUT_PROP_POINTER, tp_dev->propbit); + __set_bit(INPUT_PROP_POINTING_STICK, tp_dev->propbit); + + error = input_register_device(etd->tp_dev); + if (error < 0) + goto init_fail_tp_reg; + } + + psmouse->protocol_handler = elantech_process_byte; + psmouse->disconnect = elantech_disconnect; + psmouse->reconnect = elantech_reconnect; + psmouse->pktsize = info->hw_version > 1 ? 6 : 4; + + return 0; + init_fail_tp_reg: + input_free_device(tp_dev); + init_fail_tp_alloc: + sysfs_remove_group(&psmouse->ps2dev.serio->dev.kobj, + &elantech_attr_group); + init_fail: + kfree(etd); + return error; +} + +int elantech_init_ps2(struct psmouse *psmouse) +{ + struct elantech_device_info info; + int error = -EINVAL; + + psmouse_reset(psmouse); + + error = elantech_query_info(psmouse, &info); + if (error) + goto init_fail; + + error = elantech_setup_ps2(psmouse, &info); + if (error) + goto init_fail; + + return 0; + init_fail: + psmouse_reset(psmouse); + return error; +} + +int elantech_init(struct psmouse *psmouse) +{ + struct elantech_device_info info; + int error = -EINVAL; + + psmouse_reset(psmouse); + + error = elantech_query_info(psmouse, &info); + if (error) + goto init_fail; + +#if defined(CONFIG_MOUSE_PS2_ELANTECH_SMBUS) + + if (elantech_use_host_notify(psmouse, &info)) { + if (!IS_ENABLED(CONFIG_MOUSE_ELAN_I2C_SMBUS) || + !IS_ENABLED(CONFIG_MOUSE_PS2_ELANTECH_SMBUS)) { + psmouse_warn(psmouse, + "The touchpad can support a better bus than the too old PS/2 protocol. " + "Make sure MOUSE_PS2_ELANTECH_SMBUS and MOUSE_ELAN_I2C_SMBUS are enabled to get a better touchpad experience.\n"); + } + error = elantech_setup_smbus(psmouse, &info, true); + if (!error) + return PSMOUSE_ELANTECH_SMBUS; + } + +#endif /* CONFIG_MOUSE_PS2_ELANTECH_SMBUS */ + + error = elantech_setup_ps2(psmouse, &info); + if (error < 0) { + /* + * Not using any flavor of Elantech support, so clean up + * SMbus breadcrumbs, if any. + */ + psmouse_smbus_cleanup(psmouse); + goto init_fail; + } + + return PSMOUSE_ELANTECH; + init_fail: + psmouse_reset(psmouse); + return error; +} diff --git a/drivers/input/mouse/elantech.h b/drivers/input/mouse/elantech.h new file mode 100644 index 000000000..119727085 --- /dev/null +++ b/drivers/input/mouse/elantech.h @@ -0,0 +1,210 @@ +/* + * 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. + */ + +#ifndef _ELANTECH_H +#define _ELANTECH_H + +/* + * Command values for Synaptics style queries + */ +#define ETP_FW_ID_QUERY 0x00 +#define ETP_FW_VERSION_QUERY 0x01 +#define ETP_CAPABILITIES_QUERY 0x02 +#define ETP_SAMPLE_QUERY 0x03 +#define ETP_RESOLUTION_QUERY 0x04 + +/* + * Command values for register reading or writing + */ +#define ETP_REGISTER_READ 0x10 +#define ETP_REGISTER_WRITE 0x11 +#define ETP_REGISTER_READWRITE 0x00 + +/* + * Hardware version 2 custom PS/2 command value + */ +#define ETP_PS2_CUSTOM_COMMAND 0xf8 + +/* + * Times to retry a ps2_command and millisecond delay between tries + */ +#define ETP_PS2_COMMAND_TRIES 3 +#define ETP_PS2_COMMAND_DELAY 500 + +/* + * Times to try to read back a register and millisecond delay between tries + */ +#define ETP_READ_BACK_TRIES 5 +#define ETP_READ_BACK_DELAY 2000 + +/* + * Register bitmasks for hardware version 1 + */ +#define ETP_R10_ABSOLUTE_MODE 0x04 +#define ETP_R11_4_BYTE_MODE 0x02 + +/* + * Capability bitmasks + */ +#define ETP_CAP_HAS_ROCKER 0x04 + +/* + * One hard to find application note states that X axis range is 0 to 576 + * and Y axis range is 0 to 384 for harware version 1. + * Edge fuzz might be necessary because of bezel around the touchpad + */ +#define ETP_EDGE_FUZZ_V1 32 + +#define ETP_XMIN_V1 ( 0 + ETP_EDGE_FUZZ_V1) +#define ETP_XMAX_V1 (576 - ETP_EDGE_FUZZ_V1) +#define ETP_YMIN_V1 ( 0 + ETP_EDGE_FUZZ_V1) +#define ETP_YMAX_V1 (384 - ETP_EDGE_FUZZ_V1) + +/* + * The resolution for older v2 hardware doubled. + * (newer v2's firmware provides command so we can query) + */ +#define ETP_XMIN_V2 0 +#define ETP_XMAX_V2 1152 +#define ETP_YMIN_V2 0 +#define ETP_YMAX_V2 768 + +#define ETP_PMIN_V2 0 +#define ETP_PMAX_V2 255 +#define ETP_WMIN_V2 0 +#define ETP_WMAX_V2 15 + +/* + * v3 hardware has 2 kinds of packet types, + * v4 hardware has 3. + */ +#define PACKET_UNKNOWN 0x01 +#define PACKET_DEBOUNCE 0x02 +#define PACKET_V3_HEAD 0x03 +#define PACKET_V3_TAIL 0x04 +#define PACKET_V4_HEAD 0x05 +#define PACKET_V4_MOTION 0x06 +#define PACKET_V4_STATUS 0x07 +#define PACKET_TRACKPOINT 0x08 + +/* + * track up to 5 fingers for v4 hardware + */ +#define ETP_MAX_FINGERS 5 + +/* + * weight value for v4 hardware + */ +#define ETP_WEIGHT_VALUE 5 + +/* + * Bus information on 3rd byte of query ETP_RESOLUTION_QUERY(0x04) + */ +#define ETP_BUS_PS2_ONLY 0 +#define ETP_BUS_SMB_ALERT_ONLY 1 +#define ETP_BUS_SMB_HST_NTFY_ONLY 2 +#define ETP_BUS_PS2_SMB_ALERT 3 +#define ETP_BUS_PS2_SMB_HST_NTFY 4 + +/* + * New ICs are either using SMBus Host Notify or just plain PS2. + * + * ETP_FW_VERSION_QUERY is: + * Byte 1: + * - bit 0..3: IC BODY + * Byte 2: + * - bit 4: HiddenButton + * - bit 5: PS2_SMBUS_NOTIFY + * - bit 6: PS2CRCCheck + */ +#define ETP_NEW_IC_SMBUS_HOST_NOTIFY(fw_version) \ + ((((fw_version) & 0x0f2000) == 0x0f2000) && \ + ((fw_version) & 0x0000ff) > 0) + +/* + * The base position for one finger, v4 hardware + */ +struct finger_pos { + unsigned int x; + unsigned int y; +}; + +struct elantech_device_info { + unsigned char capabilities[3]; + unsigned char samples[3]; + unsigned char debug; + unsigned char hw_version; + unsigned int fw_version; + unsigned int x_res; + unsigned int y_res; + unsigned int bus; + bool paritycheck; + bool jumpy_cursor; + bool reports_pressure; + bool crc_enabled; + bool set_hw_resolution; + bool has_trackpoint; + int (*send_cmd)(struct psmouse *psmouse, unsigned char c, + unsigned char *param); +}; + +struct elantech_data { + struct input_dev *tp_dev; /* Relative device for trackpoint */ + char tp_phys[32]; + unsigned char reg_07; + unsigned char reg_10; + unsigned char reg_11; + unsigned char reg_20; + unsigned char reg_21; + unsigned char reg_22; + unsigned char reg_23; + unsigned char reg_24; + unsigned char reg_25; + unsigned char reg_26; + unsigned int single_finger_reports; + unsigned int y_max; + unsigned int width; + struct finger_pos mt[ETP_MAX_FINGERS]; + unsigned char parity[256]; + struct elantech_device_info info; + void (*original_set_rate)(struct psmouse *psmouse, unsigned int rate); +}; + +#ifdef CONFIG_MOUSE_PS2_ELANTECH +int elantech_detect(struct psmouse *psmouse, bool set_properties); +int elantech_init_ps2(struct psmouse *psmouse); +int elantech_init(struct psmouse *psmouse); +#else +static inline int elantech_detect(struct psmouse *psmouse, bool set_properties) +{ + return -ENOSYS; +} +static inline int elantech_init(struct psmouse *psmouse) +{ + return -ENOSYS; +} +static inline int elantech_init_ps2(struct psmouse *psmouse) +{ + return -ENOSYS; +} +#endif /* CONFIG_MOUSE_PS2_ELANTECH */ + +#if defined(CONFIG_MOUSE_PS2_ELANTECH_SMBUS) +int elantech_init_smbus(struct psmouse *psmouse); +#else +static inline int elantech_init_smbus(struct psmouse *psmouse) +{ + return -ENOSYS; +} +#endif /* CONFIG_MOUSE_PS2_ELANTECH_SMBUS */ + +#endif diff --git a/drivers/input/mouse/focaltech.c b/drivers/input/mouse/focaltech.c new file mode 100644 index 000000000..a7d39689b --- /dev/null +++ b/drivers/input/mouse/focaltech.c @@ -0,0 +1,460 @@ +/* + * Focaltech TouchPad PS/2 mouse driver + * + * Copyright (c) 2014 Red Hat Inc. + * Copyright (c) 2014 Mathias Gottschlag <mgottschlag@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Red Hat authors: + * + * Hans de Goede <hdegoede@redhat.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.h" + +static const char * const focaltech_pnp_ids[] = { + "FLT0101", + "FLT0102", + "FLT0103", + NULL +}; + +/* + * Even if the kernel is built without support for Focaltech PS/2 touchpads (or + * when the real driver fails to recognize the device), we still have to detect + * them in order to avoid further detection attempts confusing the touchpad. + * This way it at least works in PS/2 mouse compatibility mode. + */ +int focaltech_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"; + } + + return 0; +} + +#ifdef CONFIG_MOUSE_PS2_FOCALTECH + +/* + * Packet types - the numbers are not consecutive, so we might be missing + * something here. + */ +#define FOC_TOUCH 0x3 /* bitmap of active fingers */ +#define FOC_ABS 0x6 /* absolute position of one finger */ +#define FOC_REL 0x9 /* relative position of 1-2 fingers */ + +#define FOC_MAX_FINGERS 5 + +/* + * Current state of a single finger on the touchpad. + */ +struct focaltech_finger_state { + /* The touchpad has generated a touch event for the finger */ + bool active; + + /* + * The touchpad has sent position data for the finger. The + * flag is 0 when the finger is not active, and there is a + * time between the first touch event for the finger and the + * following absolute position packet for the finger where the + * touchpad has declared the finger to be valid, but we do not + * have any valid position yet. + */ + bool valid; + + /* + * Absolute position (from the bottom left corner) of the + * finger. + */ + unsigned int x; + unsigned int y; +}; + +/* + * Description of the current state of the touchpad hardware. + */ +struct focaltech_hw_state { + /* + * The touchpad tracks the positions of the fingers for us, + * the array indices correspond to the finger indices returned + * in the report packages. + */ + struct focaltech_finger_state fingers[FOC_MAX_FINGERS]; + + /* + * Finger width 0-7 and 15 for a very big contact area. + * 15 value stays until the finger is released. + * Width is reported only in absolute packets. + * Since hardware reports width only for last touching finger, + * there is no need to store width for every specific finger, + * so we keep only last value reported. + */ + unsigned int width; + + /* True if the clickpad has been pressed. */ + bool pressed; +}; + +struct focaltech_data { + unsigned int x_max, y_max; + struct focaltech_hw_state state; +}; + +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 < FOC_MAX_FINGERS; i++) { + struct focaltech_finger_state *finger = &state->fingers[i]; + bool active = finger->active && finger->valid; + + input_mt_slot(dev, i); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, active); + if (active) { + unsigned int clamped_x, clamped_y; + /* + * The touchpad might report invalid data, so we clamp + * the resulting values so that we do not confuse + * userspace. + */ + clamped_x = clamp(finger->x, 0U, priv->x_max); + clamped_y = clamp(finger->y, 0U, priv->y_max); + input_report_abs(dev, ABS_MT_POSITION_X, clamped_x); + input_report_abs(dev, ABS_MT_POSITION_Y, + priv->y_max - clamped_y); + input_report_abs(dev, ABS_TOOL_WIDTH, state->width); + } + } + input_mt_report_pointer_emulation(dev, true); + + input_report_key(dev, BTN_LEFT, state->pressed); + input_sync(dev); +} + +static void focaltech_process_touch_packet(struct psmouse *psmouse, + unsigned char *packet) +{ + struct focaltech_data *priv = psmouse->private; + struct focaltech_hw_state *state = &priv->state; + unsigned char fingers = packet[1]; + int i; + + state->pressed = (packet[0] >> 4) & 1; + + /* the second byte contains a bitmap of all fingers touching the pad */ + for (i = 0; i < FOC_MAX_FINGERS; i++) { + state->fingers[i].active = fingers & 0x1; + if (!state->fingers[i].active) { + /* + * Even when the finger becomes active again, we still + * will have to wait for the first valid position. + */ + state->fingers[i].valid = false; + } + fingers >>= 1; + } +} + +static void focaltech_process_abs_packet(struct psmouse *psmouse, + unsigned char *packet) +{ + struct focaltech_data *priv = psmouse->private; + struct focaltech_hw_state *state = &priv->state; + unsigned int finger; + + finger = (packet[1] >> 4) - 1; + if (finger >= FOC_MAX_FINGERS) { + psmouse_err(psmouse, "Invalid finger in abs packet: %d\n", + finger); + return; + } + + state->pressed = (packet[0] >> 4) & 1; + + state->fingers[finger].x = ((packet[1] & 0xf) << 8) | packet[2]; + state->fingers[finger].y = (packet[3] << 8) | packet[4]; + state->width = packet[5] >> 4; + state->fingers[finger].valid = true; +} + +static void focaltech_process_rel_packet(struct psmouse *psmouse, + unsigned char *packet) +{ + struct focaltech_data *priv = psmouse->private; + struct focaltech_hw_state *state = &priv->state; + int finger1, finger2; + + state->pressed = packet[0] >> 7; + finger1 = ((packet[0] >> 4) & 0x7) - 1; + if (finger1 < FOC_MAX_FINGERS) { + state->fingers[finger1].x += (char)packet[1]; + state->fingers[finger1].y += (char)packet[2]; + } else { + psmouse_err(psmouse, "First finger in rel packet invalid: %d\n", + finger1); + } + + /* + * If there is an odd number of fingers, the last relative + * packet only contains one finger. In this case, the second + * finger index in the packet is 0 (we subtract 1 in the lines + * above to create array indices, so the finger will overflow + * and be above FOC_MAX_FINGERS). + */ + finger2 = ((packet[3] >> 4) & 0x7) - 1; + if (finger2 < FOC_MAX_FINGERS) { + state->fingers[finger2].x += (char)packet[4]; + state->fingers[finger2].y += (char)packet[5]; + } +} + +static void focaltech_process_packet(struct psmouse *psmouse) +{ + unsigned char *packet = psmouse->packet; + + switch (packet[0] & 0xf) { + case FOC_TOUCH: + focaltech_process_touch_packet(psmouse, packet); + break; + + case FOC_ABS: + focaltech_process_abs_packet(psmouse, packet); + break; + + case FOC_REL: + focaltech_process_rel_packet(psmouse, packet); + break; + + default: + psmouse_err(psmouse, "Unknown packet type: %02x\n", packet[0]); + break; + } + + focaltech_report_state(psmouse); +} + +static psmouse_ret_t focaltech_process_byte(struct psmouse *psmouse) +{ + if (psmouse->pktcnt >= 6) { /* Full packet received */ + focaltech_process_packet(psmouse); + return PSMOUSE_FULL_PACKET; + } + + /* + * We might want to do some validation of the data here, but + * we do not know the protocol well enough + */ + return PSMOUSE_GOOD_DATA; +} + +static int focaltech_switch_protocol(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[3]; + + param[0] = 0; + if (ps2_command(ps2dev, param, 0x10f8)) + return -EIO; + + if (ps2_command(ps2dev, param, 0x10f8)) + return -EIO; + + if (ps2_command(ps2dev, param, 0x10f8)) + return -EIO; + + param[0] = 1; + if (ps2_command(ps2dev, param, 0x10f8)) + return -EIO; + + if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETSCALE11)) + return -EIO; + + if (ps2_command(ps2dev, param, PSMOUSE_CMD_ENABLE)) + 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; + struct focaltech_data *priv = psmouse->private; + + /* + * 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); + __clear_bit(BTN_RIGHT, dev->keybit); + __clear_bit(BTN_MIDDLE, dev->keybit); + + /* + * Now set up our capabilities. + */ + __set_bit(EV_ABS, dev->evbit); + input_set_abs_params(dev, ABS_MT_POSITION_X, 0, priv->x_max, 0, 0); + input_set_abs_params(dev, ABS_MT_POSITION_Y, 0, priv->y_max, 0, 0); + input_set_abs_params(dev, ABS_TOOL_WIDTH, 0, 15, 0, 0); + input_mt_init_slots(dev, 5, INPUT_MT_POINTER); + __set_bit(INPUT_PROP_BUTTONPAD, dev->propbit); +} + +static int focaltech_read_register(struct ps2dev *ps2dev, int reg, + unsigned char *param) +{ + if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETSCALE11)) + return -EIO; + + param[0] = 0; + if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES)) + return -EIO; + + if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES)) + return -EIO; + + if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES)) + return -EIO; + + param[0] = reg; + if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES)) + return -EIO; + + if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) + return -EIO; + + return 0; +} + +static int focaltech_read_size(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + struct focaltech_data *priv = psmouse->private; + char param[3]; + + if (focaltech_read_register(ps2dev, 2, param)) + return -EIO; + + /* not sure whether this is 100% correct */ + priv->x_max = (unsigned char)param[1] * 128; + priv->y_max = (unsigned char)param[2] * 128; + + return 0; +} + +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_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_read_size(psmouse); + if (error) { + psmouse_err(psmouse, + "Unable to read the size of the touchpad\n"); + goto fail; + } + + 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 = 6; + 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 */ diff --git a/drivers/input/mouse/focaltech.h b/drivers/input/mouse/focaltech.h new file mode 100644 index 000000000..783b28e8e --- /dev/null +++ b/drivers/input/mouse/focaltech.h @@ -0,0 +1,31 @@ +/* + * Focaltech TouchPad PS/2 mouse driver + * + * Copyright (c) 2014 Red Hat Inc. + * Copyright (c) 2014 Mathias Gottschlag <mgottschlag@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Red Hat authors: + * + * Hans de Goede <hdegoede@redhat.com> + */ + +#ifndef _FOCALTECH_H +#define _FOCALTECH_H + +int focaltech_detect(struct psmouse *psmouse, bool set_properties); + +#ifdef CONFIG_MOUSE_PS2_FOCALTECH +int focaltech_init(struct psmouse *psmouse); +#else +static inline int focaltech_init(struct psmouse *psmouse) +{ + return -ENOSYS; +} +#endif + +#endif diff --git a/drivers/input/mouse/gpio_mouse.c b/drivers/input/mouse/gpio_mouse.c new file mode 100644 index 000000000..a26d8be6f --- /dev/null +++ b/drivers/input/mouse/gpio_mouse.c @@ -0,0 +1,178 @@ +/* + * Driver for simulating a mouse on GPIO lines. + * + * Copyright (C) 2007 Atmel Corporation + * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/input-polldev.h> +#include <linux/gpio/consumer.h> +#include <linux/property.h> +#include <linux/of.h> + +/** + * struct gpio_mouse + * @scan_ms: the scan interval in milliseconds. + * @up: GPIO line for up value. + * @down: GPIO line for down value. + * @left: GPIO line for left value. + * @right: GPIO line for right value. + * @bleft: GPIO line for left button. + * @bmiddle: GPIO line for middle button. + * @bright: GPIO line for right button. + * + * This struct must be added to the platform_device in the board code. + * It is used by the gpio_mouse driver to setup GPIO lines and to + * calculate mouse movement. + */ +struct gpio_mouse { + u32 scan_ms; + struct gpio_desc *up; + struct gpio_desc *down; + struct gpio_desc *left; + struct gpio_desc *right; + struct gpio_desc *bleft; + struct gpio_desc *bmiddle; + struct gpio_desc *bright; +}; + +/* + * Timer function which is run every scan_ms ms when the device is opened. + * The dev input variable is set to the the input_dev pointer. + */ +static void gpio_mouse_scan(struct input_polled_dev *dev) +{ + struct gpio_mouse *gpio = dev->private; + struct input_dev *input = dev->input; + int x, y; + + if (gpio->bleft) + input_report_key(input, BTN_LEFT, + gpiod_get_value(gpio->bleft)); + if (gpio->bmiddle) + input_report_key(input, BTN_MIDDLE, + gpiod_get_value(gpio->bmiddle)); + if (gpio->bright) + input_report_key(input, BTN_RIGHT, + gpiod_get_value(gpio->bright)); + + x = gpiod_get_value(gpio->right) - gpiod_get_value(gpio->left); + y = gpiod_get_value(gpio->down) - gpiod_get_value(gpio->up); + + input_report_rel(input, REL_X, x); + input_report_rel(input, REL_Y, y); + input_sync(input); +} + +static int gpio_mouse_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gpio_mouse *gmouse; + struct input_polled_dev *input_poll; + struct input_dev *input; + int ret; + + gmouse = devm_kzalloc(dev, sizeof(*gmouse), GFP_KERNEL); + if (!gmouse) + return -ENOMEM; + + /* Assign some default scanning time */ + ret = device_property_read_u32(dev, "scan-interval-ms", + &gmouse->scan_ms); + if (ret || gmouse->scan_ms == 0) { + dev_warn(dev, "invalid scan time, set to 50 ms\n"); + gmouse->scan_ms = 50; + } + + gmouse->up = devm_gpiod_get(dev, "up", GPIOD_IN); + if (IS_ERR(gmouse->up)) + return PTR_ERR(gmouse->up); + gmouse->down = devm_gpiod_get(dev, "down", GPIOD_IN); + if (IS_ERR(gmouse->down)) + return PTR_ERR(gmouse->down); + gmouse->left = devm_gpiod_get(dev, "left", GPIOD_IN); + if (IS_ERR(gmouse->left)) + return PTR_ERR(gmouse->left); + gmouse->right = devm_gpiod_get(dev, "right", GPIOD_IN); + if (IS_ERR(gmouse->right)) + return PTR_ERR(gmouse->right); + + gmouse->bleft = devm_gpiod_get_optional(dev, "button-left", GPIOD_IN); + if (IS_ERR(gmouse->bleft)) + return PTR_ERR(gmouse->bleft); + gmouse->bmiddle = devm_gpiod_get_optional(dev, "button-middle", + GPIOD_IN); + if (IS_ERR(gmouse->bmiddle)) + return PTR_ERR(gmouse->bmiddle); + gmouse->bright = devm_gpiod_get_optional(dev, "button-right", + GPIOD_IN); + if (IS_ERR(gmouse->bright)) + return PTR_ERR(gmouse->bright); + + input_poll = devm_input_allocate_polled_device(dev); + if (!input_poll) { + dev_err(dev, "not enough memory for input device\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, input_poll); + + /* set input-polldev handlers */ + input_poll->private = gmouse; + input_poll->poll = gpio_mouse_scan; + input_poll->poll_interval = gmouse->scan_ms; + + input = input_poll->input; + input->name = pdev->name; + input->id.bustype = BUS_HOST; + input->dev.parent = &pdev->dev; + + input_set_capability(input, EV_REL, REL_X); + input_set_capability(input, EV_REL, REL_Y); + if (gmouse->bleft) + input_set_capability(input, EV_KEY, BTN_LEFT); + if (gmouse->bmiddle) + input_set_capability(input, EV_KEY, BTN_MIDDLE); + if (gmouse->bright) + input_set_capability(input, EV_KEY, BTN_RIGHT); + + ret = input_register_polled_device(input_poll); + if (ret) { + dev_err(dev, "could not register input device\n"); + return ret; + } + + dev_dbg(dev, "%d ms scan time, buttons: %s%s%s\n", + gmouse->scan_ms, + gmouse->bleft ? "" : "left ", + gmouse->bmiddle ? "" : "middle ", + gmouse->bright ? "" : "right"); + + return 0; +} + +static const struct of_device_id gpio_mouse_of_match[] = { + { .compatible = "gpio-mouse", }, + { }, +}; +MODULE_DEVICE_TABLE(of, gpio_mouse_of_match); + +static struct platform_driver gpio_mouse_device_driver = { + .probe = gpio_mouse_probe, + .driver = { + .name = "gpio_mouse", + .of_match_table = gpio_mouse_of_match, + } +}; +module_platform_driver(gpio_mouse_device_driver); + +MODULE_AUTHOR("Hans-Christian Egtvedt <egtvedt@samfundet.no>"); +MODULE_DESCRIPTION("GPIO mouse driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:gpio_mouse"); /* work with hotplug and coldplug */ diff --git a/drivers/input/mouse/hgpk.c b/drivers/input/mouse/hgpk.c new file mode 100644 index 000000000..015509e0b --- /dev/null +++ b/drivers/input/mouse/hgpk.c @@ -0,0 +1,1066 @@ +/* + * OLPC HGPK (XO-1) touchpad PS/2 mouse driver + * + * Copyright (c) 2006-2008 One Laptop Per Child + * Authors: + * Zephaniah E. Hull + * Andres Salomon <dilinger@debian.org> + * + * This driver is partly based on the ALPS driver, which is: + * + * Copyright (c) 2003 Neil Brown <neilb@cse.unsw.edu.au> + * Copyright (c) 2003-2005 Peter Osterlund <petero2@telia.com> + * Copyright (c) 2004 Dmitry Torokhov <dtor@mail.ru> + * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz> + * + * 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. + */ + +/* + * The spec from ALPS is available from + * <http://wiki.laptop.org/go/Touch_Pad/Tablet>. It refers to this + * device as HGPK (Hybrid GS, PT, and Keymatrix). + * + * The earliest versions of the device had simultaneous reporting; that + * was removed. After that, the device used the Advanced Mode GS/PT streaming + * stuff. That turned out to be too buggy to support, so we've finally + * switched to Mouse Mode (which utilizes only the center 1/3 of the touchpad). + */ + +#define DEBUG +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/serio.h> +#include <linux/libps2.h> +#include <linux/delay.h> +#include <asm/olpc.h> + +#include "psmouse.h" +#include "hgpk.h" + +#define ILLEGAL_XY 999999 + +static bool tpdebug; +module_param(tpdebug, bool, 0644); +MODULE_PARM_DESC(tpdebug, "enable debugging, dumping packets to KERN_DEBUG."); + +static int recalib_delta = 100; +module_param(recalib_delta, int, 0644); +MODULE_PARM_DESC(recalib_delta, + "packets containing a delta this large will be discarded, and a " + "recalibration may be scheduled."); + +static int jumpy_delay = 20; +module_param(jumpy_delay, int, 0644); +MODULE_PARM_DESC(jumpy_delay, + "delay (ms) before recal after jumpiness detected"); + +static int spew_delay = 1; +module_param(spew_delay, int, 0644); +MODULE_PARM_DESC(spew_delay, + "delay (ms) before recal after packet spew detected"); + +static int recal_guard_time; +module_param(recal_guard_time, int, 0644); +MODULE_PARM_DESC(recal_guard_time, + "interval (ms) during which recal will be restarted if packet received"); + +static int post_interrupt_delay = 40; +module_param(post_interrupt_delay, int, 0644); +MODULE_PARM_DESC(post_interrupt_delay, + "delay (ms) before recal after recal interrupt detected"); + +static bool autorecal = true; +module_param(autorecal, bool, 0644); +MODULE_PARM_DESC(autorecal, "enable recalibration in the driver"); + +static char hgpk_mode_name[16]; +module_param_string(hgpk_mode, hgpk_mode_name, sizeof(hgpk_mode_name), 0644); +MODULE_PARM_DESC(hgpk_mode, + "default hgpk mode: mouse, glidesensor or pentablet"); + +static int hgpk_default_mode = HGPK_MODE_MOUSE; + +static const char * const hgpk_mode_names[] = { + [HGPK_MODE_MOUSE] = "Mouse", + [HGPK_MODE_GLIDESENSOR] = "GlideSensor", + [HGPK_MODE_PENTABLET] = "PenTablet", +}; + +static int hgpk_mode_from_name(const char *buf, int len) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(hgpk_mode_names); i++) { + const char *name = hgpk_mode_names[i]; + if (strlen(name) == len && !strncasecmp(name, buf, len)) + return i; + } + + return HGPK_MODE_INVALID; +} + +/* + * see if new value is within 20% of half of old value + */ +static int approx_half(int curr, int prev) +{ + int belowhalf, abovehalf; + + if (curr < 5 || prev < 5) + return 0; + + belowhalf = (prev * 8) / 20; + abovehalf = (prev * 12) / 20; + + return belowhalf < curr && curr <= abovehalf; +} + +/* + * Throw out oddly large delta packets, and any that immediately follow whose + * values are each approximately half of the previous. It seems that the ALPS + * firmware emits errant packets, and they get averaged out slowly. + */ +static int hgpk_discard_decay_hack(struct psmouse *psmouse, int x, int y) +{ + struct hgpk_data *priv = psmouse->private; + int avx, avy; + bool do_recal = false; + + avx = abs(x); + avy = abs(y); + + /* discard if too big, or half that but > 4 times the prev delta */ + if (avx > recalib_delta || + (avx > recalib_delta / 2 && ((avx / 4) > priv->xlast))) { + psmouse_warn(psmouse, "detected %dpx jump in x\n", x); + priv->xbigj = avx; + } else if (approx_half(avx, priv->xbigj)) { + psmouse_warn(psmouse, "detected secondary %dpx jump in x\n", x); + priv->xbigj = avx; + priv->xsaw_secondary++; + } else { + if (priv->xbigj && priv->xsaw_secondary > 1) + do_recal = true; + priv->xbigj = 0; + priv->xsaw_secondary = 0; + } + + if (avy > recalib_delta || + (avy > recalib_delta / 2 && ((avy / 4) > priv->ylast))) { + psmouse_warn(psmouse, "detected %dpx jump in y\n", y); + priv->ybigj = avy; + } else if (approx_half(avy, priv->ybigj)) { + psmouse_warn(psmouse, "detected secondary %dpx jump in y\n", y); + priv->ybigj = avy; + priv->ysaw_secondary++; + } else { + if (priv->ybigj && priv->ysaw_secondary > 1) + do_recal = true; + priv->ybigj = 0; + priv->ysaw_secondary = 0; + } + + priv->xlast = avx; + priv->ylast = avy; + + if (do_recal && jumpy_delay) { + psmouse_warn(psmouse, "scheduling recalibration\n"); + psmouse_queue_work(psmouse, &priv->recalib_wq, + msecs_to_jiffies(jumpy_delay)); + } + + return priv->xbigj || priv->ybigj; +} + +static void hgpk_reset_spew_detection(struct hgpk_data *priv) +{ + priv->spew_count = 0; + priv->dupe_count = 0; + priv->x_tally = 0; + priv->y_tally = 0; + priv->spew_flag = NO_SPEW; +} + +static void hgpk_reset_hack_state(struct psmouse *psmouse) +{ + struct hgpk_data *priv = psmouse->private; + + priv->abs_x = priv->abs_y = -1; + priv->xlast = priv->ylast = ILLEGAL_XY; + priv->xbigj = priv->ybigj = 0; + priv->xsaw_secondary = priv->ysaw_secondary = 0; + hgpk_reset_spew_detection(priv); +} + +/* + * We have no idea why this particular hardware bug occurs. The touchpad + * will randomly start spewing packets without anything touching the + * pad. This wouldn't necessarily be bad, but it's indicative of a + * severely miscalibrated pad; attempting to use the touchpad while it's + * spewing means the cursor will jump all over the place, and act "drunk". + * + * The packets that are spewed tend to all have deltas between -2 and 2, and + * the cursor will move around without really going very far. It will + * tend to end up in the same location; if we tally up the changes over + * 100 packets, we end up w/ a final delta of close to 0. This happens + * pretty regularly when the touchpad is spewing, and is pretty hard to + * manually trigger (at least for *my* fingers). So, it makes a perfect + * scheme for detecting spews. + */ +static void hgpk_spewing_hack(struct psmouse *psmouse, + int l, int r, int x, int y) +{ + struct hgpk_data *priv = psmouse->private; + + /* ignore button press packets; many in a row could trigger + * a false-positive! */ + if (l || r) + return; + + /* don't track spew if the workaround feature has been turned off */ + if (!spew_delay) + return; + + if (abs(x) > 3 || abs(y) > 3) { + /* no spew, or spew ended */ + hgpk_reset_spew_detection(priv); + return; + } + + /* Keep a tally of the overall delta to the cursor position caused by + * the spew */ + priv->x_tally += x; + priv->y_tally += y; + + switch (priv->spew_flag) { + case NO_SPEW: + /* we're not spewing, but this packet might be the start */ + priv->spew_flag = MAYBE_SPEWING; + + /* fall-through */ + + case MAYBE_SPEWING: + priv->spew_count++; + + if (priv->spew_count < SPEW_WATCH_COUNT) + break; + + /* excessive spew detected, request recalibration */ + priv->spew_flag = SPEW_DETECTED; + + /* fall-through */ + + case SPEW_DETECTED: + /* only recalibrate when the overall delta to the cursor + * is really small. if the spew is causing significant cursor + * movement, it is probably a case of the user moving the + * cursor very slowly across the screen. */ + if (abs(priv->x_tally) < 3 && abs(priv->y_tally) < 3) { + psmouse_warn(psmouse, "packet spew detected (%d,%d)\n", + priv->x_tally, priv->y_tally); + priv->spew_flag = RECALIBRATING; + psmouse_queue_work(psmouse, &priv->recalib_wq, + msecs_to_jiffies(spew_delay)); + } + + break; + case RECALIBRATING: + /* we already detected a spew and requested a recalibration, + * just wait for the queue to kick into action. */ + break; + } +} + +/* + * HGPK Mouse Mode format (standard mouse format, sans middle button) + * + * byte 0: y-over x-over y-neg x-neg 1 0 swr swl + * byte 1: x7 x6 x5 x4 x3 x2 x1 x0 + * byte 2: y7 y6 y5 y4 y3 y2 y1 y0 + * + * swr/swl are the left/right buttons. + * x-neg/y-neg are the x and y delta negative bits + * x-over/y-over are the x and y overflow bits + * + * --- + * + * HGPK Advanced Mode - single-mode format + * + * byte 0(PT): 1 1 0 0 1 1 1 1 + * byte 0(GS): 1 1 1 1 1 1 1 1 + * byte 1: 0 x6 x5 x4 x3 x2 x1 x0 + * byte 2(PT): 0 0 x9 x8 x7 ? pt-dsw 0 + * byte 2(GS): 0 x10 x9 x8 x7 ? gs-dsw pt-dsw + * byte 3: 0 y9 y8 y7 1 0 swr swl + * byte 4: 0 y6 y5 y4 y3 y2 y1 y0 + * byte 5: 0 z6 z5 z4 z3 z2 z1 z0 + * + * ?'s are not defined in the protocol spec, may vary between models. + * + * swr/swl are the left/right buttons. + * + * pt-dsw/gs-dsw indicate that the pt/gs sensor is detecting a + * pen/finger + */ +static bool hgpk_is_byte_valid(struct psmouse *psmouse, unsigned char *packet) +{ + struct hgpk_data *priv = psmouse->private; + int pktcnt = psmouse->pktcnt; + bool valid; + + switch (priv->mode) { + case HGPK_MODE_MOUSE: + valid = (packet[0] & 0x0C) == 0x08; + break; + + case HGPK_MODE_GLIDESENSOR: + valid = pktcnt == 1 ? + packet[0] == HGPK_GS : !(packet[pktcnt - 1] & 0x80); + break; + + case HGPK_MODE_PENTABLET: + valid = pktcnt == 1 ? + packet[0] == HGPK_PT : !(packet[pktcnt - 1] & 0x80); + break; + + default: + valid = false; + break; + } + + if (!valid) + psmouse_dbg(psmouse, + "bad data, mode %d (%d) %*ph\n", + priv->mode, pktcnt, 6, psmouse->packet); + + return valid; +} + +static void hgpk_process_advanced_packet(struct psmouse *psmouse) +{ + struct hgpk_data *priv = psmouse->private; + struct input_dev *idev = psmouse->dev; + unsigned char *packet = psmouse->packet; + int down = !!(packet[2] & 2); + int left = !!(packet[3] & 1); + int right = !!(packet[3] & 2); + int x = packet[1] | ((packet[2] & 0x78) << 4); + int y = packet[4] | ((packet[3] & 0x70) << 3); + + if (priv->mode == HGPK_MODE_GLIDESENSOR) { + int pt_down = !!(packet[2] & 1); + int finger_down = !!(packet[2] & 2); + int z = packet[5]; + + input_report_abs(idev, ABS_PRESSURE, z); + if (tpdebug) + psmouse_dbg(psmouse, "pd=%d fd=%d z=%d", + pt_down, finger_down, z); + } else { + /* + * PenTablet mode does not report pressure, so we don't + * report it here + */ + if (tpdebug) + psmouse_dbg(psmouse, "pd=%d ", down); + } + + if (tpdebug) + psmouse_dbg(psmouse, "l=%d r=%d x=%d y=%d\n", + left, right, x, y); + + input_report_key(idev, BTN_TOUCH, down); + input_report_key(idev, BTN_LEFT, left); + input_report_key(idev, BTN_RIGHT, right); + + /* + * If this packet says that the finger was removed, reset our position + * tracking so that we don't erroneously detect a jump on next press. + */ + if (!down) { + hgpk_reset_hack_state(psmouse); + goto done; + } + + /* + * Weed out duplicate packets (we get quite a few, and they mess up + * our jump detection) + */ + if (x == priv->abs_x && y == priv->abs_y) { + if (++priv->dupe_count > SPEW_WATCH_COUNT) { + if (tpdebug) + psmouse_dbg(psmouse, "hard spew detected\n"); + priv->spew_flag = RECALIBRATING; + psmouse_queue_work(psmouse, &priv->recalib_wq, + msecs_to_jiffies(spew_delay)); + } + goto done; + } + + /* not a duplicate, continue with position reporting */ + priv->dupe_count = 0; + + /* Don't apply hacks in PT mode, it seems reliable */ + if (priv->mode != HGPK_MODE_PENTABLET && priv->abs_x != -1) { + int x_diff = priv->abs_x - x; + int y_diff = priv->abs_y - y; + if (hgpk_discard_decay_hack(psmouse, x_diff, y_diff)) { + if (tpdebug) + psmouse_dbg(psmouse, "discarding\n"); + goto done; + } + hgpk_spewing_hack(psmouse, left, right, x_diff, y_diff); + } + + input_report_abs(idev, ABS_X, x); + input_report_abs(idev, ABS_Y, y); + priv->abs_x = x; + priv->abs_y = y; + +done: + input_sync(idev); +} + +static void hgpk_process_simple_packet(struct psmouse *psmouse) +{ + struct input_dev *dev = psmouse->dev; + unsigned char *packet = psmouse->packet; + int left = packet[0] & 1; + int right = (packet[0] >> 1) & 1; + int x = packet[1] - ((packet[0] << 4) & 0x100); + int y = ((packet[0] << 3) & 0x100) - packet[2]; + + if (packet[0] & 0xc0) + psmouse_dbg(psmouse, + "overflow -- 0x%02x 0x%02x 0x%02x\n", + packet[0], packet[1], packet[2]); + + if (hgpk_discard_decay_hack(psmouse, x, y)) { + if (tpdebug) + psmouse_dbg(psmouse, "discarding\n"); + return; + } + + hgpk_spewing_hack(psmouse, left, right, x, y); + + if (tpdebug) + psmouse_dbg(psmouse, "l=%d r=%d x=%d y=%d\n", + left, right, x, y); + + input_report_key(dev, BTN_LEFT, left); + input_report_key(dev, BTN_RIGHT, right); + + input_report_rel(dev, REL_X, x); + input_report_rel(dev, REL_Y, y); + + input_sync(dev); +} + +static psmouse_ret_t hgpk_process_byte(struct psmouse *psmouse) +{ + struct hgpk_data *priv = psmouse->private; + + if (!hgpk_is_byte_valid(psmouse, psmouse->packet)) + return PSMOUSE_BAD_DATA; + + if (psmouse->pktcnt >= psmouse->pktsize) { + if (priv->mode == HGPK_MODE_MOUSE) + hgpk_process_simple_packet(psmouse); + else + hgpk_process_advanced_packet(psmouse); + return PSMOUSE_FULL_PACKET; + } + + if (priv->recalib_window) { + if (time_before(jiffies, priv->recalib_window)) { + /* + * ugh, got a packet inside our recalibration + * window, schedule another recalibration. + */ + psmouse_dbg(psmouse, + "packet inside calibration window, queueing another recalibration\n"); + psmouse_queue_work(psmouse, &priv->recalib_wq, + msecs_to_jiffies(post_interrupt_delay)); + } + priv->recalib_window = 0; + } + + return PSMOUSE_GOOD_DATA; +} + +static int hgpk_select_mode(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + struct hgpk_data *priv = psmouse->private; + int i; + int cmd; + + /* + * 4 disables to enable advanced mode + * then 3 0xf2 bytes as the preamble for GS/PT selection + */ + const int advanced_init[] = { + PSMOUSE_CMD_DISABLE, PSMOUSE_CMD_DISABLE, + PSMOUSE_CMD_DISABLE, PSMOUSE_CMD_DISABLE, + 0xf2, 0xf2, 0xf2, + }; + + switch (priv->mode) { + case HGPK_MODE_MOUSE: + psmouse->pktsize = 3; + break; + + case HGPK_MODE_GLIDESENSOR: + case HGPK_MODE_PENTABLET: + psmouse->pktsize = 6; + + /* Switch to 'Advanced mode.', four disables in a row. */ + for (i = 0; i < ARRAY_SIZE(advanced_init); i++) + if (ps2_command(ps2dev, NULL, advanced_init[i])) + return -EIO; + + /* select between GlideSensor (mouse) or PenTablet */ + cmd = priv->mode == HGPK_MODE_GLIDESENSOR ? + PSMOUSE_CMD_SETSCALE11 : PSMOUSE_CMD_SETSCALE21; + + if (ps2_command(ps2dev, NULL, cmd)) + return -EIO; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static void hgpk_setup_input_device(struct input_dev *input, + struct input_dev *old_input, + enum hgpk_mode mode) +{ + if (old_input) { + input->name = old_input->name; + input->phys = old_input->phys; + input->id = old_input->id; + input->dev.parent = old_input->dev.parent; + } + + memset(input->evbit, 0, sizeof(input->evbit)); + memset(input->relbit, 0, sizeof(input->relbit)); + memset(input->keybit, 0, sizeof(input->keybit)); + + /* All modes report left and right buttons */ + __set_bit(EV_KEY, input->evbit); + __set_bit(BTN_LEFT, input->keybit); + __set_bit(BTN_RIGHT, input->keybit); + + switch (mode) { + case HGPK_MODE_MOUSE: + __set_bit(EV_REL, input->evbit); + __set_bit(REL_X, input->relbit); + __set_bit(REL_Y, input->relbit); + break; + + case HGPK_MODE_GLIDESENSOR: + __set_bit(BTN_TOUCH, input->keybit); + __set_bit(BTN_TOOL_FINGER, input->keybit); + + __set_bit(EV_ABS, input->evbit); + + /* GlideSensor has pressure sensor, PenTablet does not */ + input_set_abs_params(input, ABS_PRESSURE, 0, 15, 0, 0); + + /* From device specs */ + input_set_abs_params(input, ABS_X, 0, 399, 0, 0); + input_set_abs_params(input, ABS_Y, 0, 290, 0, 0); + + /* Calculated by hand based on usable size (52mm x 38mm) */ + input_abs_set_res(input, ABS_X, 8); + input_abs_set_res(input, ABS_Y, 8); + break; + + case HGPK_MODE_PENTABLET: + __set_bit(BTN_TOUCH, input->keybit); + __set_bit(BTN_TOOL_FINGER, input->keybit); + + __set_bit(EV_ABS, input->evbit); + + /* From device specs */ + input_set_abs_params(input, ABS_X, 0, 999, 0, 0); + input_set_abs_params(input, ABS_Y, 5, 239, 0, 0); + + /* Calculated by hand based on usable size (156mm x 38mm) */ + input_abs_set_res(input, ABS_X, 6); + input_abs_set_res(input, ABS_Y, 8); + break; + + default: + BUG(); + } +} + +static int hgpk_reset_device(struct psmouse *psmouse, bool recalibrate) +{ + int err; + + psmouse_reset(psmouse); + + if (recalibrate) { + struct ps2dev *ps2dev = &psmouse->ps2dev; + + /* send the recalibrate request */ + if (ps2_command(ps2dev, NULL, 0xf5) || + ps2_command(ps2dev, NULL, 0xf5) || + ps2_command(ps2dev, NULL, 0xe6) || + ps2_command(ps2dev, NULL, 0xf5)) { + return -1; + } + + /* according to ALPS, 150mS is required for recalibration */ + msleep(150); + } + + err = hgpk_select_mode(psmouse); + if (err) { + psmouse_err(psmouse, "failed to select mode\n"); + return err; + } + + hgpk_reset_hack_state(psmouse); + + return 0; +} + +static int hgpk_force_recalibrate(struct psmouse *psmouse) +{ + struct hgpk_data *priv = psmouse->private; + int err; + + /* C-series touchpads added the recalibrate command */ + if (psmouse->model < HGPK_MODEL_C) + return 0; + + if (!autorecal) { + psmouse_dbg(psmouse, "recalibration disabled, ignoring\n"); + return 0; + } + + psmouse_dbg(psmouse, "recalibrating touchpad..\n"); + + /* we don't want to race with the irq handler, nor with resyncs */ + psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); + + /* start by resetting the device */ + err = hgpk_reset_device(psmouse, true); + if (err) + return err; + + /* + * XXX: If a finger is down during this delay, recalibration will + * detect capacitance incorrectly. This is a hardware bug, and + * we don't have a good way to deal with it. The 2s window stuff + * (below) is our best option for now. + */ + if (psmouse_activate(psmouse)) + return -1; + + if (tpdebug) + psmouse_dbg(psmouse, "touchpad reactivated\n"); + + /* + * If we get packets right away after recalibrating, it's likely + * that a finger was on the touchpad. If so, it's probably + * miscalibrated, so we optionally schedule another. + */ + if (recal_guard_time) + priv->recalib_window = jiffies + + msecs_to_jiffies(recal_guard_time); + + return 0; +} + +/* + * This puts the touchpad in a power saving mode; according to ALPS, current + * consumption goes down to 50uA after running this. To turn power back on, + * we drive MS-DAT low. Measuring with a 1mA resolution ammeter says that + * the current on the SUS_3.3V rail drops from 3mA or 4mA to 0 when we do this. + * + * We have no formal spec that details this operation -- the low-power + * sequence came from a long-lost email trail. + */ +static int hgpk_toggle_powersave(struct psmouse *psmouse, int enable) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + int timeo; + int err; + + /* Added on D-series touchpads */ + if (psmouse->model < HGPK_MODEL_D) + return 0; + + if (enable) { + psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); + + /* + * Sending a byte will drive MS-DAT low; this will wake up + * the controller. Once we get an ACK back from it, it + * means we can continue with the touchpad re-init. ALPS + * tells us that 1s should be long enough, so set that as + * the upper bound. (in practice, it takes about 3 loops.) + */ + for (timeo = 20; timeo > 0; timeo--) { + if (!ps2_sendbyte(ps2dev, PSMOUSE_CMD_DISABLE, 20)) + break; + msleep(25); + } + + err = hgpk_reset_device(psmouse, false); + if (err) { + psmouse_err(psmouse, "Failed to reset device!\n"); + return err; + } + + /* should be all set, enable the touchpad */ + psmouse_activate(psmouse); + psmouse_dbg(psmouse, "Touchpad powered up.\n"); + } else { + psmouse_dbg(psmouse, "Powering off touchpad.\n"); + + if (ps2_command(ps2dev, NULL, 0xec) || + ps2_command(ps2dev, NULL, 0xec) || + ps2_command(ps2dev, NULL, 0xea)) { + return -1; + } + + psmouse_set_state(psmouse, PSMOUSE_IGNORE); + + /* probably won't see an ACK, the touchpad will be off */ + ps2_sendbyte(ps2dev, 0xec, 20); + } + + return 0; +} + +static int hgpk_poll(struct psmouse *psmouse) +{ + /* We can't poll, so always return failure. */ + return -1; +} + +static int hgpk_reconnect(struct psmouse *psmouse) +{ + struct hgpk_data *priv = psmouse->private; + + /* + * During suspend/resume the ps2 rails remain powered. We don't want + * to do a reset because it's flush data out of buffers; however, + * earlier prototypes (B1) had some brokenness that required a reset. + */ + if (olpc_board_at_least(olpc_board(0xb2))) + if (psmouse->ps2dev.serio->dev.power.power_state.event != + PM_EVENT_ON) + return 0; + + priv->powered = 1; + return hgpk_reset_device(psmouse, false); +} + +static ssize_t hgpk_show_powered(struct psmouse *psmouse, void *data, char *buf) +{ + struct hgpk_data *priv = psmouse->private; + + return sprintf(buf, "%d\n", priv->powered); +} + +static ssize_t hgpk_set_powered(struct psmouse *psmouse, void *data, + const char *buf, size_t count) +{ + struct hgpk_data *priv = psmouse->private; + unsigned int value; + int err; + + err = kstrtouint(buf, 10, &value); + if (err) + return err; + + if (value > 1) + return -EINVAL; + + if (value != priv->powered) { + /* + * hgpk_toggle_power will deal w/ state so + * we're not racing w/ irq + */ + err = hgpk_toggle_powersave(psmouse, value); + if (!err) + priv->powered = value; + } + + return err ? err : count; +} + +__PSMOUSE_DEFINE_ATTR(powered, S_IWUSR | S_IRUGO, NULL, + hgpk_show_powered, hgpk_set_powered, false); + +static ssize_t attr_show_mode(struct psmouse *psmouse, void *data, char *buf) +{ + struct hgpk_data *priv = psmouse->private; + + return sprintf(buf, "%s\n", hgpk_mode_names[priv->mode]); +} + +static ssize_t attr_set_mode(struct psmouse *psmouse, void *data, + const char *buf, size_t len) +{ + struct hgpk_data *priv = psmouse->private; + enum hgpk_mode old_mode = priv->mode; + enum hgpk_mode new_mode = hgpk_mode_from_name(buf, len); + struct input_dev *old_dev = psmouse->dev; + struct input_dev *new_dev; + int err; + + if (new_mode == HGPK_MODE_INVALID) + return -EINVAL; + + if (old_mode == new_mode) + return len; + + new_dev = input_allocate_device(); + if (!new_dev) + return -ENOMEM; + + psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); + + /* Switch device into the new mode */ + priv->mode = new_mode; + err = hgpk_reset_device(psmouse, false); + if (err) + goto err_try_restore; + + hgpk_setup_input_device(new_dev, old_dev, new_mode); + + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + + err = input_register_device(new_dev); + if (err) + goto err_try_restore; + + psmouse->dev = new_dev; + input_unregister_device(old_dev); + + return len; + +err_try_restore: + input_free_device(new_dev); + priv->mode = old_mode; + hgpk_reset_device(psmouse, false); + + return err; +} + +PSMOUSE_DEFINE_ATTR(hgpk_mode, S_IWUSR | S_IRUGO, NULL, + attr_show_mode, attr_set_mode); + +static ssize_t hgpk_trigger_recal_show(struct psmouse *psmouse, + void *data, char *buf) +{ + return -EINVAL; +} + +static ssize_t hgpk_trigger_recal(struct psmouse *psmouse, void *data, + const char *buf, size_t count) +{ + struct hgpk_data *priv = psmouse->private; + unsigned int value; + int err; + + err = kstrtouint(buf, 10, &value); + if (err) + return err; + + if (value != 1) + return -EINVAL; + + /* + * We queue work instead of doing recalibration right here + * to avoid adding locking to to hgpk_force_recalibrate() + * since workqueue provides serialization. + */ + psmouse_queue_work(psmouse, &priv->recalib_wq, 0); + return count; +} + +__PSMOUSE_DEFINE_ATTR(recalibrate, S_IWUSR | S_IRUGO, NULL, + hgpk_trigger_recal_show, hgpk_trigger_recal, false); + +static void hgpk_disconnect(struct psmouse *psmouse) +{ + struct hgpk_data *priv = psmouse->private; + + device_remove_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_powered.dattr); + device_remove_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_hgpk_mode.dattr); + + if (psmouse->model >= HGPK_MODEL_C) + device_remove_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_recalibrate.dattr); + + psmouse_reset(psmouse); + kfree(priv); +} + +static void hgpk_recalib_work(struct work_struct *work) +{ + struct delayed_work *w = to_delayed_work(work); + struct hgpk_data *priv = container_of(w, struct hgpk_data, recalib_wq); + struct psmouse *psmouse = priv->psmouse; + + if (hgpk_force_recalibrate(psmouse)) + psmouse_err(psmouse, "recalibration failed!\n"); +} + +static int hgpk_register(struct psmouse *psmouse) +{ + struct hgpk_data *priv = psmouse->private; + int err; + + /* register handlers */ + psmouse->protocol_handler = hgpk_process_byte; + psmouse->poll = hgpk_poll; + psmouse->disconnect = hgpk_disconnect; + psmouse->reconnect = hgpk_reconnect; + + /* Disable the idle resync. */ + psmouse->resync_time = 0; + /* Reset after a lot of bad bytes. */ + psmouse->resetafter = 1024; + + hgpk_setup_input_device(psmouse->dev, NULL, priv->mode); + + err = device_create_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_powered.dattr); + if (err) { + psmouse_err(psmouse, "Failed creating 'powered' sysfs node\n"); + return err; + } + + err = device_create_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_hgpk_mode.dattr); + if (err) { + psmouse_err(psmouse, + "Failed creating 'hgpk_mode' sysfs node\n"); + goto err_remove_powered; + } + + /* C-series touchpads added the recalibrate command */ + if (psmouse->model >= HGPK_MODEL_C) { + err = device_create_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_recalibrate.dattr); + if (err) { + psmouse_err(psmouse, + "Failed creating 'recalibrate' sysfs node\n"); + goto err_remove_mode; + } + } + + return 0; + +err_remove_mode: + device_remove_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_hgpk_mode.dattr); +err_remove_powered: + device_remove_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_powered.dattr); + return err; +} + +int hgpk_init(struct psmouse *psmouse) +{ + struct hgpk_data *priv; + int err; + + priv = kzalloc(sizeof(struct hgpk_data), GFP_KERNEL); + if (!priv) { + err = -ENOMEM; + goto alloc_fail; + } + + psmouse->private = priv; + + priv->psmouse = psmouse; + priv->powered = true; + priv->mode = hgpk_default_mode; + INIT_DELAYED_WORK(&priv->recalib_wq, hgpk_recalib_work); + + err = hgpk_reset_device(psmouse, false); + if (err) + goto init_fail; + + err = hgpk_register(psmouse); + if (err) + goto init_fail; + + return 0; + +init_fail: + kfree(priv); +alloc_fail: + return err; +} + +static enum hgpk_model_t hgpk_get_model(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[3]; + + /* E7, E7, E7, E9 gets us a 3 byte identifier */ + if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) || + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) || + ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) { + return -EIO; + } + + psmouse_dbg(psmouse, "ID: %*ph\n", 3, param); + + /* HGPK signature: 0x67, 0x00, 0x<model> */ + if (param[0] != 0x67 || param[1] != 0x00) + return -ENODEV; + + psmouse_info(psmouse, "OLPC touchpad revision 0x%x\n", param[2]); + + return param[2]; +} + +int hgpk_detect(struct psmouse *psmouse, bool set_properties) +{ + int version; + + version = hgpk_get_model(psmouse); + if (version < 0) + return version; + + if (set_properties) { + psmouse->vendor = "ALPS"; + psmouse->name = "HGPK"; + psmouse->model = version; + } + + return 0; +} + +void hgpk_module_init(void) +{ + hgpk_default_mode = hgpk_mode_from_name(hgpk_mode_name, + strlen(hgpk_mode_name)); + if (hgpk_default_mode == HGPK_MODE_INVALID) { + hgpk_default_mode = HGPK_MODE_MOUSE; + strlcpy(hgpk_mode_name, hgpk_mode_names[HGPK_MODE_MOUSE], + sizeof(hgpk_mode_name)); + } +} diff --git a/drivers/input/mouse/hgpk.h b/drivers/input/mouse/hgpk.h new file mode 100644 index 000000000..98b7b3842 --- /dev/null +++ b/drivers/input/mouse/hgpk.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * OLPC HGPK (XO-1) touchpad PS/2 mouse driver + */ + +#ifndef _HGPK_H +#define _HGPK_H + +#define HGPK_GS 0xff /* The GlideSensor */ +#define HGPK_PT 0xcf /* The PenTablet */ + +enum hgpk_model_t { + HGPK_MODEL_PREA = 0x0a, /* pre-B1s */ + HGPK_MODEL_A = 0x14, /* found on B1s, PT disabled in hardware */ + HGPK_MODEL_B = 0x28, /* B2s, has capacitance issues */ + HGPK_MODEL_C = 0x3c, + HGPK_MODEL_D = 0x50, /* C1, mass production */ +}; + +enum hgpk_spew_flag { + NO_SPEW, + MAYBE_SPEWING, + SPEW_DETECTED, + RECALIBRATING, +}; + +#define SPEW_WATCH_COUNT 42 /* at 12ms/packet, this is 1/2 second */ + +enum hgpk_mode { + HGPK_MODE_MOUSE, + HGPK_MODE_GLIDESENSOR, + HGPK_MODE_PENTABLET, + HGPK_MODE_INVALID +}; + +struct hgpk_data { + struct psmouse *psmouse; + enum hgpk_mode mode; + bool powered; + enum hgpk_spew_flag spew_flag; + int spew_count, x_tally, y_tally; /* spew detection */ + unsigned long recalib_window; + struct delayed_work recalib_wq; + int abs_x, abs_y; + int dupe_count; + int xbigj, ybigj, xlast, ylast; /* jumpiness detection */ + int xsaw_secondary, ysaw_secondary; /* jumpiness detection */ +}; + +#ifdef CONFIG_MOUSE_PS2_OLPC +void hgpk_module_init(void); +int hgpk_detect(struct psmouse *psmouse, bool set_properties); +int hgpk_init(struct psmouse *psmouse); +#else +static inline void hgpk_module_init(void) +{ +} +static inline int hgpk_detect(struct psmouse *psmouse, bool set_properties) +{ + return -ENODEV; +} +static inline int hgpk_init(struct psmouse *psmouse) +{ + return -ENODEV; +} +#endif + +#endif diff --git a/drivers/input/mouse/inport.c b/drivers/input/mouse/inport.c new file mode 100644 index 000000000..b9e68606c --- /dev/null +++ b/drivers/input/mouse/inport.c @@ -0,0 +1,192 @@ +/* + * Copyright (c) 1999-2001 Vojtech Pavlik + * + * Based on the work of: + * Teemu Rantanen Derrick Cole + * Peter Cervasio Christoph Niemann + * Philip Blundell Russell King + * Bob Harris + */ + +/* + * Inport (ATI XL and Microsoft) busmouse driver for Linux + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/input.h> + +#include <asm/io.h> +#include <asm/irq.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Inport (ATI XL and Microsoft) busmouse driver"); +MODULE_LICENSE("GPL"); + +#define INPORT_BASE 0x23c +#define INPORT_EXTENT 4 + +#define INPORT_CONTROL_PORT INPORT_BASE + 0 +#define INPORT_DATA_PORT INPORT_BASE + 1 +#define INPORT_SIGNATURE_PORT INPORT_BASE + 2 + +#define INPORT_REG_BTNS 0x00 +#define INPORT_REG_X 0x01 +#define INPORT_REG_Y 0x02 +#define INPORT_REG_MODE 0x07 +#define INPORT_RESET 0x80 + +#ifdef CONFIG_MOUSE_ATIXL +#define INPORT_NAME "ATI XL Mouse" +#define INPORT_VENDOR 0x0002 +#define INPORT_SPEED_30HZ 0x01 +#define INPORT_SPEED_50HZ 0x02 +#define INPORT_SPEED_100HZ 0x03 +#define INPORT_SPEED_200HZ 0x04 +#define INPORT_MODE_BASE INPORT_SPEED_100HZ +#define INPORT_MODE_IRQ 0x08 +#else +#define INPORT_NAME "Microsoft InPort Mouse" +#define INPORT_VENDOR 0x0001 +#define INPORT_MODE_BASE 0x10 +#define INPORT_MODE_IRQ 0x01 +#endif +#define INPORT_MODE_HOLD 0x20 + +#define INPORT_IRQ 5 + +static int inport_irq = INPORT_IRQ; +module_param_hw_named(irq, inport_irq, uint, irq, 0); +MODULE_PARM_DESC(irq, "IRQ number (5=default)"); + +static struct input_dev *inport_dev; + +static irqreturn_t inport_interrupt(int irq, void *dev_id) +{ + unsigned char buttons; + + outb(INPORT_REG_MODE, INPORT_CONTROL_PORT); + outb(INPORT_MODE_HOLD | INPORT_MODE_IRQ | INPORT_MODE_BASE, INPORT_DATA_PORT); + + outb(INPORT_REG_X, INPORT_CONTROL_PORT); + input_report_rel(inport_dev, REL_X, inb(INPORT_DATA_PORT)); + + outb(INPORT_REG_Y, INPORT_CONTROL_PORT); + input_report_rel(inport_dev, REL_Y, inb(INPORT_DATA_PORT)); + + outb(INPORT_REG_BTNS, INPORT_CONTROL_PORT); + buttons = inb(INPORT_DATA_PORT); + + input_report_key(inport_dev, BTN_MIDDLE, buttons & 1); + input_report_key(inport_dev, BTN_LEFT, buttons & 2); + input_report_key(inport_dev, BTN_RIGHT, buttons & 4); + + outb(INPORT_REG_MODE, INPORT_CONTROL_PORT); + outb(INPORT_MODE_IRQ | INPORT_MODE_BASE, INPORT_DATA_PORT); + + input_sync(inport_dev); + return IRQ_HANDLED; +} + +static int inport_open(struct input_dev *dev) +{ + if (request_irq(inport_irq, inport_interrupt, 0, "inport", NULL)) + return -EBUSY; + outb(INPORT_REG_MODE, INPORT_CONTROL_PORT); + outb(INPORT_MODE_IRQ | INPORT_MODE_BASE, INPORT_DATA_PORT); + + return 0; +} + +static void inport_close(struct input_dev *dev) +{ + outb(INPORT_REG_MODE, INPORT_CONTROL_PORT); + outb(INPORT_MODE_BASE, INPORT_DATA_PORT); + free_irq(inport_irq, NULL); +} + +static int __init inport_init(void) +{ + unsigned char a, b, c; + int err; + + if (!request_region(INPORT_BASE, INPORT_EXTENT, "inport")) { + printk(KERN_ERR "inport.c: Can't allocate ports at %#x\n", INPORT_BASE); + return -EBUSY; + } + + a = inb(INPORT_SIGNATURE_PORT); + b = inb(INPORT_SIGNATURE_PORT); + c = inb(INPORT_SIGNATURE_PORT); + if (a == b || a != c) { + printk(KERN_INFO "inport.c: Didn't find InPort mouse at %#x\n", INPORT_BASE); + err = -ENODEV; + goto err_release_region; + } + + inport_dev = input_allocate_device(); + if (!inport_dev) { + printk(KERN_ERR "inport.c: Not enough memory for input device\n"); + err = -ENOMEM; + goto err_release_region; + } + + inport_dev->name = INPORT_NAME; + inport_dev->phys = "isa023c/input0"; + inport_dev->id.bustype = BUS_ISA; + inport_dev->id.vendor = INPORT_VENDOR; + inport_dev->id.product = 0x0001; + inport_dev->id.version = 0x0100; + + inport_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + inport_dev->keybit[BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT); + inport_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + + inport_dev->open = inport_open; + inport_dev->close = inport_close; + + outb(INPORT_RESET, INPORT_CONTROL_PORT); + outb(INPORT_REG_MODE, INPORT_CONTROL_PORT); + outb(INPORT_MODE_BASE, INPORT_DATA_PORT); + + err = input_register_device(inport_dev); + if (err) + goto err_free_dev; + + return 0; + + err_free_dev: + input_free_device(inport_dev); + err_release_region: + release_region(INPORT_BASE, INPORT_EXTENT); + + return err; +} + +static void __exit inport_exit(void) +{ + input_unregister_device(inport_dev); + release_region(INPORT_BASE, INPORT_EXTENT); +} + +module_init(inport_init); +module_exit(inport_exit); diff --git a/drivers/input/mouse/lifebook.c b/drivers/input/mouse/lifebook.c new file mode 100644 index 000000000..a5765f747 --- /dev/null +++ b/drivers/input/mouse/lifebook.c @@ -0,0 +1,356 @@ +/* + * Fujitsu B-series Lifebook PS/2 TouchScreen driver + * + * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz> + * Copyright (c) 2005 Kenan Esau <kenan.esau@conan.de> + * + * TouchScreen detection, absolute mode setting and packet layout is taken from + * Harald Hoyer's description of the device. + * + * 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. + */ + +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/libps2.h> +#include <linux/dmi.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include "psmouse.h" +#include "lifebook.h" + +struct lifebook_data { + struct input_dev *dev2; /* Relative device */ + char phys[32]; +}; + +static bool lifebook_present; + +static const char *desired_serio_phys; + +static int lifebook_limit_serio3(const struct dmi_system_id *d) +{ + desired_serio_phys = "isa0060/serio3"; + return 1; +} + +static bool lifebook_use_6byte_proto; + +static int lifebook_set_6byte_proto(const struct dmi_system_id *d) +{ + lifebook_use_6byte_proto = true; + return 1; +} + +static const struct dmi_system_id lifebook_dmi_table[] __initconst = { + { + /* FLORA-ie 55mi */ + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "FLORA-ie 55mi"), + }, + }, + { + /* LifeBook B */ + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Lifebook B Series"), + }, + }, + { + /* LifeBook B */ + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook B Series"), + }, + }, + { + /* Lifebook B */ + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK B Series"), + }, + }, + { + /* Lifebook B-2130 */ + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "ZEPHYR"), + }, + }, + { + /* Lifebook B213x/B2150 */ + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook B2131/B2133/B2150"), + }, + }, + { + /* Zephyr */ + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "ZEPHYR"), + }, + }, + { + /* Panasonic CF-18 */ + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "CF-18"), + }, + .callback = lifebook_limit_serio3, + }, + { + /* Panasonic CF-28 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Matsushita"), + DMI_MATCH(DMI_PRODUCT_NAME, "CF-28"), + }, + .callback = lifebook_set_6byte_proto, + }, + { + /* Panasonic CF-29 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Matsushita"), + DMI_MATCH(DMI_PRODUCT_NAME, "CF-29"), + }, + .callback = lifebook_set_6byte_proto, + }, + { + /* Panasonic CF-72 */ + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "CF-72"), + }, + .callback = lifebook_set_6byte_proto, + }, + { + /* Lifebook B142 */ + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook B142"), + }, + }, + { } +}; + +void __init lifebook_module_init(void) +{ + lifebook_present = dmi_check_system(lifebook_dmi_table); +} + +static psmouse_ret_t lifebook_process_byte(struct psmouse *psmouse) +{ + struct lifebook_data *priv = psmouse->private; + struct input_dev *dev1 = psmouse->dev; + struct input_dev *dev2 = priv ? priv->dev2 : NULL; + u8 *packet = psmouse->packet; + bool relative_packet = packet[0] & 0x08; + + if (relative_packet || !lifebook_use_6byte_proto) { + if (psmouse->pktcnt != 3) + return PSMOUSE_GOOD_DATA; + } else { + switch (psmouse->pktcnt) { + case 1: + return (packet[0] & 0xf8) == 0x00 ? + PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA; + case 2: + return PSMOUSE_GOOD_DATA; + case 3: + return ((packet[2] & 0x30) << 2) == (packet[2] & 0xc0) ? + PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA; + case 4: + return (packet[3] & 0xf8) == 0xc0 ? + PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA; + case 5: + return (packet[4] & 0xc0) == (packet[2] & 0xc0) ? + PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA; + case 6: + if (((packet[5] & 0x30) << 2) != (packet[5] & 0xc0)) + return PSMOUSE_BAD_DATA; + if ((packet[5] & 0xc0) != (packet[1] & 0xc0)) + return PSMOUSE_BAD_DATA; + break; /* report data */ + } + } + + if (relative_packet) { + if (!dev2) + psmouse_warn(psmouse, + "got relative packet but no relative device set up\n"); + } else { + if (lifebook_use_6byte_proto) { + input_report_abs(dev1, ABS_X, + ((packet[1] & 0x3f) << 6) | (packet[2] & 0x3f)); + input_report_abs(dev1, ABS_Y, + 4096 - (((packet[4] & 0x3f) << 6) | (packet[5] & 0x3f))); + } else { + input_report_abs(dev1, ABS_X, + (packet[1] | ((packet[0] & 0x30) << 4))); + input_report_abs(dev1, ABS_Y, + 1024 - (packet[2] | ((packet[0] & 0xC0) << 2))); + } + input_report_key(dev1, BTN_TOUCH, packet[0] & 0x04); + input_sync(dev1); + } + + if (dev2) { + if (relative_packet) + psmouse_report_standard_motion(dev2, packet); + + psmouse_report_standard_buttons(dev2, packet[0]); + input_sync(dev2); + } + + return PSMOUSE_FULL_PACKET; +} + +static int lifebook_absolute_mode(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + u8 param; + int error; + + error = psmouse_reset(psmouse); + if (error) + return error; + + /* + * Enable absolute output -- ps2_command fails always but if + * you leave this call out the touchscreen will never send + * absolute coordinates + */ + param = lifebook_use_6byte_proto ? 0x08 : 0x07; + ps2_command(ps2dev, ¶m, PSMOUSE_CMD_SETRES); + + return 0; +} + +static void lifebook_relative_mode(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + u8 param = 0x06; + + ps2_command(ps2dev, ¶m, PSMOUSE_CMD_SETRES); +} + +static void lifebook_set_resolution(struct psmouse *psmouse, unsigned int resolution) +{ + static const u8 params[] = { 0, 1, 2, 2, 3 }; + u8 p; + + if (resolution == 0 || resolution > 400) + resolution = 400; + + p = params[resolution / 100]; + ps2_command(&psmouse->ps2dev, &p, PSMOUSE_CMD_SETRES); + psmouse->resolution = 50 << p; +} + +static void lifebook_disconnect(struct psmouse *psmouse) +{ + struct lifebook_data *priv = psmouse->private; + + psmouse_reset(psmouse); + if (priv) { + input_unregister_device(priv->dev2); + kfree(priv); + } + psmouse->private = NULL; +} + +int lifebook_detect(struct psmouse *psmouse, bool set_properties) +{ + if (!lifebook_present) + return -ENXIO; + + if (desired_serio_phys && + strcmp(psmouse->ps2dev.serio->phys, desired_serio_phys)) + return -ENXIO; + + if (set_properties) { + psmouse->vendor = "Fujitsu"; + psmouse->name = "Lifebook TouchScreen"; + } + + return 0; +} + +static int lifebook_create_relative_device(struct psmouse *psmouse) +{ + struct input_dev *dev2; + struct lifebook_data *priv; + int error = -ENOMEM; + + priv = kzalloc(sizeof(struct lifebook_data), GFP_KERNEL); + dev2 = input_allocate_device(); + if (!priv || !dev2) + goto err_out; + + priv->dev2 = dev2; + snprintf(priv->phys, sizeof(priv->phys), + "%s/input1", psmouse->ps2dev.serio->phys); + + dev2->phys = priv->phys; + dev2->name = "LBPS/2 Fujitsu Lifebook Touchpad"; + dev2->id.bustype = BUS_I8042; + dev2->id.vendor = 0x0002; + dev2->id.product = PSMOUSE_LIFEBOOK; + dev2->id.version = 0x0000; + dev2->dev.parent = &psmouse->ps2dev.serio->dev; + + input_set_capability(dev2, EV_REL, REL_X); + input_set_capability(dev2, EV_REL, REL_Y); + input_set_capability(dev2, EV_KEY, BTN_LEFT); + input_set_capability(dev2, EV_KEY, BTN_RIGHT); + + error = input_register_device(priv->dev2); + if (error) + goto err_out; + + psmouse->private = priv; + return 0; + + err_out: + input_free_device(dev2); + kfree(priv); + return error; +} + +int lifebook_init(struct psmouse *psmouse) +{ + struct input_dev *dev1 = psmouse->dev; + int max_coord = lifebook_use_6byte_proto ? 4096 : 1024; + int error; + + error = lifebook_absolute_mode(psmouse); + if (error) + return error; + + /* Clear default capabilities */ + bitmap_zero(dev1->evbit, EV_CNT); + bitmap_zero(dev1->relbit, REL_CNT); + bitmap_zero(dev1->keybit, KEY_CNT); + + input_set_capability(dev1, EV_KEY, BTN_TOUCH); + input_set_abs_params(dev1, ABS_X, 0, max_coord, 0, 0); + input_set_abs_params(dev1, ABS_Y, 0, max_coord, 0, 0); + + if (!desired_serio_phys) { + error = lifebook_create_relative_device(psmouse); + if (error) { + lifebook_relative_mode(psmouse); + return error; + } + } + + psmouse->protocol_handler = lifebook_process_byte; + psmouse->set_resolution = lifebook_set_resolution; + psmouse->disconnect = lifebook_disconnect; + psmouse->reconnect = lifebook_absolute_mode; + + psmouse->model = lifebook_use_6byte_proto ? 6 : 3; + + /* + * Use packet size = 3 even when using 6-byte protocol because + * that's what POLL will return on Lifebooks (according to spec). + */ + psmouse->pktsize = 3; + + return 0; +} + diff --git a/drivers/input/mouse/lifebook.h b/drivers/input/mouse/lifebook.h new file mode 100644 index 000000000..0baf02a70 --- /dev/null +++ b/drivers/input/mouse/lifebook.h @@ -0,0 +1,32 @@ +/* + * Fujitsu B-series Lifebook PS/2 TouchScreen driver + * + * Copyright (c) 2005 Vojtech Pavlik + * + * 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. + */ + +#ifndef _LIFEBOOK_H +#define _LIFEBOOK_H + +#ifdef CONFIG_MOUSE_PS2_LIFEBOOK +void lifebook_module_init(void); +int lifebook_detect(struct psmouse *psmouse, bool set_properties); +int lifebook_init(struct psmouse *psmouse); +#else +static inline void lifebook_module_init(void) +{ +} +static inline int lifebook_detect(struct psmouse *psmouse, bool set_properties) +{ + return -ENOSYS; +} +static inline int lifebook_init(struct psmouse *psmouse) +{ + return -ENOSYS; +} +#endif + +#endif diff --git a/drivers/input/mouse/logibm.c b/drivers/input/mouse/logibm.c new file mode 100644 index 000000000..2fd6c84cd --- /dev/null +++ b/drivers/input/mouse/logibm.c @@ -0,0 +1,181 @@ +/* + * Copyright (c) 1999-2001 Vojtech Pavlik + * + * Based on the work of: + * James Banks Matthew Dillon + * David Giller Nathan Laredo + * Linus Torvalds Johan Myreen + * Cliff Matthews Philip Blundell + * Russell King + */ + +/* + * Logitech Bus Mouse Driver for Linux + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> + +#include <asm/io.h> +#include <asm/irq.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Logitech busmouse driver"); +MODULE_LICENSE("GPL"); + +#define LOGIBM_BASE 0x23c +#define LOGIBM_EXTENT 4 + +#define LOGIBM_DATA_PORT LOGIBM_BASE + 0 +#define LOGIBM_SIGNATURE_PORT LOGIBM_BASE + 1 +#define LOGIBM_CONTROL_PORT LOGIBM_BASE + 2 +#define LOGIBM_CONFIG_PORT LOGIBM_BASE + 3 + +#define LOGIBM_ENABLE_IRQ 0x00 +#define LOGIBM_DISABLE_IRQ 0x10 +#define LOGIBM_READ_X_LOW 0x80 +#define LOGIBM_READ_X_HIGH 0xa0 +#define LOGIBM_READ_Y_LOW 0xc0 +#define LOGIBM_READ_Y_HIGH 0xe0 + +#define LOGIBM_DEFAULT_MODE 0x90 +#define LOGIBM_CONFIG_BYTE 0x91 +#define LOGIBM_SIGNATURE_BYTE 0xa5 + +#define LOGIBM_IRQ 5 + +static int logibm_irq = LOGIBM_IRQ; +module_param_hw_named(irq, logibm_irq, uint, irq, 0); +MODULE_PARM_DESC(irq, "IRQ number (5=default)"); + +static struct input_dev *logibm_dev; + +static irqreturn_t logibm_interrupt(int irq, void *dev_id) +{ + char dx, dy; + unsigned char buttons; + + outb(LOGIBM_READ_X_LOW, LOGIBM_CONTROL_PORT); + dx = (inb(LOGIBM_DATA_PORT) & 0xf); + outb(LOGIBM_READ_X_HIGH, LOGIBM_CONTROL_PORT); + dx |= (inb(LOGIBM_DATA_PORT) & 0xf) << 4; + outb(LOGIBM_READ_Y_LOW, LOGIBM_CONTROL_PORT); + dy = (inb(LOGIBM_DATA_PORT) & 0xf); + outb(LOGIBM_READ_Y_HIGH, LOGIBM_CONTROL_PORT); + buttons = inb(LOGIBM_DATA_PORT); + dy |= (buttons & 0xf) << 4; + buttons = ~buttons >> 5; + + input_report_rel(logibm_dev, REL_X, dx); + input_report_rel(logibm_dev, REL_Y, dy); + input_report_key(logibm_dev, BTN_RIGHT, buttons & 1); + input_report_key(logibm_dev, BTN_MIDDLE, buttons & 2); + input_report_key(logibm_dev, BTN_LEFT, buttons & 4); + input_sync(logibm_dev); + + outb(LOGIBM_ENABLE_IRQ, LOGIBM_CONTROL_PORT); + return IRQ_HANDLED; +} + +static int logibm_open(struct input_dev *dev) +{ + if (request_irq(logibm_irq, logibm_interrupt, 0, "logibm", NULL)) { + printk(KERN_ERR "logibm.c: Can't allocate irq %d\n", logibm_irq); + return -EBUSY; + } + outb(LOGIBM_ENABLE_IRQ, LOGIBM_CONTROL_PORT); + return 0; +} + +static void logibm_close(struct input_dev *dev) +{ + outb(LOGIBM_DISABLE_IRQ, LOGIBM_CONTROL_PORT); + free_irq(logibm_irq, NULL); +} + +static int __init logibm_init(void) +{ + int err; + + if (!request_region(LOGIBM_BASE, LOGIBM_EXTENT, "logibm")) { + printk(KERN_ERR "logibm.c: Can't allocate ports at %#x\n", LOGIBM_BASE); + return -EBUSY; + } + + outb(LOGIBM_CONFIG_BYTE, LOGIBM_CONFIG_PORT); + outb(LOGIBM_SIGNATURE_BYTE, LOGIBM_SIGNATURE_PORT); + udelay(100); + + if (inb(LOGIBM_SIGNATURE_PORT) != LOGIBM_SIGNATURE_BYTE) { + printk(KERN_INFO "logibm.c: Didn't find Logitech busmouse at %#x\n", LOGIBM_BASE); + err = -ENODEV; + goto err_release_region; + } + + outb(LOGIBM_DEFAULT_MODE, LOGIBM_CONFIG_PORT); + outb(LOGIBM_DISABLE_IRQ, LOGIBM_CONTROL_PORT); + + logibm_dev = input_allocate_device(); + if (!logibm_dev) { + printk(KERN_ERR "logibm.c: Not enough memory for input device\n"); + err = -ENOMEM; + goto err_release_region; + } + + logibm_dev->name = "Logitech bus mouse"; + logibm_dev->phys = "isa023c/input0"; + logibm_dev->id.bustype = BUS_ISA; + logibm_dev->id.vendor = 0x0003; + logibm_dev->id.product = 0x0001; + logibm_dev->id.version = 0x0100; + + logibm_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + logibm_dev->keybit[BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT); + logibm_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + + logibm_dev->open = logibm_open; + logibm_dev->close = logibm_close; + + err = input_register_device(logibm_dev); + if (err) + goto err_free_dev; + + return 0; + + err_free_dev: + input_free_device(logibm_dev); + err_release_region: + release_region(LOGIBM_BASE, LOGIBM_EXTENT); + + return err; +} + +static void __exit logibm_exit(void) +{ + input_unregister_device(logibm_dev); + release_region(LOGIBM_BASE, LOGIBM_EXTENT); +} + +module_init(logibm_init); +module_exit(logibm_exit); diff --git a/drivers/input/mouse/logips2pp.c b/drivers/input/mouse/logips2pp.c new file mode 100644 index 000000000..3d5637e6f --- /dev/null +++ b/drivers/input/mouse/logips2pp.c @@ -0,0 +1,447 @@ +/* + * Logitech PS/2++ mouse driver + * + * Copyright (c) 1999-2003 Vojtech Pavlik <vojtech@suse.cz> + * Copyright (c) 2003 Eric Wong <eric@yhbt.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. + */ + +#include <linux/bitops.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/libps2.h> +#include <linux/types.h> +#include "psmouse.h" +#include "logips2pp.h" + +/* Logitech mouse types */ +#define PS2PP_KIND_WHEEL 1 +#define PS2PP_KIND_MX 2 +#define PS2PP_KIND_TP3 3 +#define PS2PP_KIND_TRACKMAN 4 + +/* Logitech mouse features */ +#define PS2PP_WHEEL BIT(0) +#define PS2PP_HWHEEL BIT(1) +#define PS2PP_SIDE_BTN BIT(2) +#define PS2PP_EXTRA_BTN BIT(3) +#define PS2PP_TASK_BTN BIT(4) +#define PS2PP_NAV_BTN BIT(5) + +struct ps2pp_info { + u8 model; + u8 kind; + u16 features; +}; + +/* + * Process a PS2++ or PS2T++ packet. + */ + +static psmouse_ret_t ps2pp_process_byte(struct psmouse *psmouse) +{ + struct input_dev *dev = psmouse->dev; + u8 *packet = psmouse->packet; + + if (psmouse->pktcnt < 3) + return PSMOUSE_GOOD_DATA; + +/* + * Full packet accumulated, process it + */ + + if ((packet[0] & 0x48) == 0x48 && (packet[1] & 0x02) == 0x02) { + + /* Logitech extended packet */ + switch ((packet[1] >> 4) | (packet[0] & 0x30)) { + + case 0x0d: /* Mouse extra info */ + + input_report_rel(dev, + packet[2] & 0x80 ? REL_HWHEEL : REL_WHEEL, + -sign_extend32(packet[2], 3)); + input_report_key(dev, BTN_SIDE, packet[2] & BIT(4)); + input_report_key(dev, BTN_EXTRA, packet[2] & BIT(5)); + + break; + + case 0x0e: /* buttons 4, 5, 6, 7, 8, 9, 10 info */ + + input_report_key(dev, BTN_SIDE, packet[2] & BIT(0)); + input_report_key(dev, BTN_EXTRA, packet[2] & BIT(1)); + input_report_key(dev, BTN_TASK, packet[2] & BIT(2)); + input_report_key(dev, BTN_BACK, packet[2] & BIT(3)); + input_report_key(dev, BTN_FORWARD, packet[2] & BIT(4)); + + break; + + case 0x0f: /* TouchPad extra info */ + + input_report_rel(dev, + packet[2] & 0x08 ? REL_HWHEEL : REL_WHEEL, + -sign_extend32(packet[2] >> 4, 3)); + packet[0] = packet[2] | BIT(3); + break; + + default: + psmouse_dbg(psmouse, + "Received PS2++ packet #%x, but don't know how to handle.\n", + (packet[1] >> 4) | (packet[0] & 0x30)); + break; + } + + psmouse_report_standard_buttons(dev, packet[0]); + + } else { + /* Standard PS/2 motion data */ + psmouse_report_standard_packet(dev, packet); + } + + input_sync(dev); + + return PSMOUSE_FULL_PACKET; + +} + +/* + * ps2pp_cmd() sends a PS2++ command, sliced into two bit + * pieces through the SETRES command. This is needed to send extended + * commands to mice on notebooks that try to understand the PS/2 protocol + * Ugly. + */ + +static int ps2pp_cmd(struct psmouse *psmouse, u8 *param, u8 command) +{ + int error; + + error = ps2_sliced_command(&psmouse->ps2dev, command); + if (error) + return error; + + error = ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_POLL | 0x0300); + if (error) + return error; + + return 0; +} + +/* + * SmartScroll / CruiseControl for some newer Logitech mice Defaults to + * enabled if we do nothing to it. Of course I put this in because I want it + * disabled :P + * 1 - enabled (if previously disabled, also default) + * 0 - disabled + */ + +static void ps2pp_set_smartscroll(struct psmouse *psmouse, bool smartscroll) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + u8 param[4]; + + ps2pp_cmd(psmouse, param, 0x32); + + param[0] = 0; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + + param[0] = smartscroll; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); +} + +static ssize_t ps2pp_attr_show_smartscroll(struct psmouse *psmouse, + void *data, char *buf) +{ + return sprintf(buf, "%d\n", psmouse->smartscroll); +} + +static ssize_t ps2pp_attr_set_smartscroll(struct psmouse *psmouse, void *data, + const char *buf, size_t count) +{ + unsigned int value; + int err; + + err = kstrtouint(buf, 10, &value); + if (err) + return err; + + if (value > 1) + return -EINVAL; + + ps2pp_set_smartscroll(psmouse, value); + psmouse->smartscroll = value; + return count; +} + +PSMOUSE_DEFINE_ATTR(smartscroll, S_IWUSR | S_IRUGO, NULL, + ps2pp_attr_show_smartscroll, ps2pp_attr_set_smartscroll); + +/* + * Support 800 dpi resolution _only_ if the user wants it (there are good + * reasons to not use it even if the mouse supports it, and of course there are + * also good reasons to use it, let the user decide). + */ + +static void ps2pp_set_resolution(struct psmouse *psmouse, + unsigned int resolution) +{ + if (resolution > 400) { + struct ps2dev *ps2dev = &psmouse->ps2dev; + u8 param = 3; + + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + ps2_command(ps2dev, ¶m, PSMOUSE_CMD_SETRES); + psmouse->resolution = 800; + } else + psmouse_set_resolution(psmouse, resolution); +} + +static void ps2pp_disconnect(struct psmouse *psmouse) +{ + device_remove_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_smartscroll.dattr); +} + +static const struct ps2pp_info *get_model_info(unsigned char model) +{ + static const struct ps2pp_info ps2pp_list[] = { + { 1, 0, 0 }, /* Simple 2-button mouse */ + { 12, 0, PS2PP_SIDE_BTN}, + { 13, 0, 0 }, + { 15, PS2PP_KIND_MX, /* MX1000 */ + PS2PP_WHEEL | PS2PP_SIDE_BTN | PS2PP_TASK_BTN | + PS2PP_EXTRA_BTN | PS2PP_NAV_BTN | PS2PP_HWHEEL }, + { 40, 0, PS2PP_SIDE_BTN }, + { 41, 0, PS2PP_SIDE_BTN }, + { 42, 0, PS2PP_SIDE_BTN }, + { 43, 0, PS2PP_SIDE_BTN }, + { 50, 0, 0 }, + { 51, 0, 0 }, + { 52, PS2PP_KIND_WHEEL, PS2PP_SIDE_BTN | PS2PP_WHEEL }, + { 53, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 56, PS2PP_KIND_WHEEL, PS2PP_SIDE_BTN | PS2PP_WHEEL }, /* Cordless MouseMan Wheel */ + { 61, PS2PP_KIND_MX, /* MX700 */ + PS2PP_WHEEL | PS2PP_SIDE_BTN | PS2PP_TASK_BTN | + PS2PP_EXTRA_BTN | PS2PP_NAV_BTN }, + { 66, PS2PP_KIND_MX, /* MX3100 receiver */ + PS2PP_WHEEL | PS2PP_SIDE_BTN | PS2PP_TASK_BTN | + PS2PP_EXTRA_BTN | PS2PP_NAV_BTN | PS2PP_HWHEEL }, + { 72, PS2PP_KIND_TRACKMAN, 0 }, /* T-CH11: TrackMan Marble */ + { 73, PS2PP_KIND_TRACKMAN, PS2PP_SIDE_BTN }, /* TrackMan FX */ + { 75, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 76, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 79, PS2PP_KIND_TRACKMAN, PS2PP_WHEEL }, /* TrackMan with wheel */ + { 80, PS2PP_KIND_WHEEL, PS2PP_SIDE_BTN | PS2PP_WHEEL }, + { 81, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 83, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 85, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 86, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 87, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 88, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 96, 0, 0 }, + { 97, PS2PP_KIND_TP3, PS2PP_WHEEL | PS2PP_HWHEEL }, + { 99, PS2PP_KIND_WHEEL, PS2PP_WHEEL }, + { 100, PS2PP_KIND_MX, /* MX510 */ + PS2PP_WHEEL | PS2PP_SIDE_BTN | PS2PP_TASK_BTN | + PS2PP_EXTRA_BTN | PS2PP_NAV_BTN }, + { 111, PS2PP_KIND_MX, PS2PP_WHEEL | PS2PP_SIDE_BTN }, /* MX300 reports task button as side */ + { 112, PS2PP_KIND_MX, /* MX500 */ + PS2PP_WHEEL | PS2PP_SIDE_BTN | PS2PP_TASK_BTN | + PS2PP_EXTRA_BTN | PS2PP_NAV_BTN }, + { 114, PS2PP_KIND_MX, /* MX310 */ + PS2PP_WHEEL | PS2PP_SIDE_BTN | + PS2PP_TASK_BTN | PS2PP_EXTRA_BTN } + }; + int i; + + for (i = 0; i < ARRAY_SIZE(ps2pp_list); i++) + if (model == ps2pp_list[i].model) + return &ps2pp_list[i]; + + return NULL; +} + +/* + * Set up input device's properties based on the detected mouse model. + */ + +static void ps2pp_set_model_properties(struct psmouse *psmouse, + const struct ps2pp_info *model_info, + bool using_ps2pp) +{ + struct input_dev *input_dev = psmouse->dev; + + if (model_info->features & PS2PP_SIDE_BTN) + input_set_capability(input_dev, EV_KEY, BTN_SIDE); + + if (model_info->features & PS2PP_EXTRA_BTN) + input_set_capability(input_dev, EV_KEY, BTN_EXTRA); + + if (model_info->features & PS2PP_TASK_BTN) + input_set_capability(input_dev, EV_KEY, BTN_TASK); + + if (model_info->features & PS2PP_NAV_BTN) { + input_set_capability(input_dev, EV_KEY, BTN_FORWARD); + input_set_capability(input_dev, EV_KEY, BTN_BACK); + } + + if (model_info->features & PS2PP_WHEEL) + input_set_capability(input_dev, EV_REL, REL_WHEEL); + + if (model_info->features & PS2PP_HWHEEL) + input_set_capability(input_dev, EV_REL, REL_HWHEEL); + + switch (model_info->kind) { + + case PS2PP_KIND_WHEEL: + psmouse->name = "Wheel Mouse"; + break; + + case PS2PP_KIND_MX: + psmouse->name = "MX Mouse"; + break; + + case PS2PP_KIND_TP3: + psmouse->name = "TouchPad 3"; + break; + + case PS2PP_KIND_TRACKMAN: + psmouse->name = "TrackMan"; + break; + + default: + /* + * Set name to "Mouse" only when using PS2++, + * otherwise let other protocols define suitable + * name + */ + if (using_ps2pp) + psmouse->name = "Mouse"; + break; + } +} + +static int ps2pp_setup_protocol(struct psmouse *psmouse, + const struct ps2pp_info *model_info) +{ + int error; + + psmouse->protocol_handler = ps2pp_process_byte; + psmouse->pktsize = 3; + + if (model_info->kind != PS2PP_KIND_TP3) { + psmouse->set_resolution = ps2pp_set_resolution; + psmouse->disconnect = ps2pp_disconnect; + + error = device_create_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_smartscroll.dattr); + if (error) { + psmouse_err(psmouse, + "failed to create smartscroll sysfs attribute, error: %d\n", + error); + return error; + } + } + + return 0; +} + +/* + * Logitech magic init. Detect whether the mouse is a Logitech one + * and its exact model and try turning on extended protocol for ones + * that support it. + */ + +int ps2pp_detect(struct psmouse *psmouse, bool set_properties) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + const struct ps2pp_info *model_info; + u8 param[4]; + u8 model, buttons; + bool use_ps2pp = false; + int error; + + param[0] = 0; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + param[1] = 0; + ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO); + + model = ((param[0] >> 4) & 0x07) | ((param[0] << 3) & 0x78); + buttons = param[1]; + + if (!model || !buttons) + return -ENXIO; + + model_info = get_model_info(model); + if (model_info) { + +/* + * Do Logitech PS2++ / PS2T++ magic init. + */ + if (model_info->kind == PS2PP_KIND_TP3) { /* Touch Pad 3 */ + + /* Unprotect RAM */ + param[0] = 0x11; param[1] = 0x04; param[2] = 0x68; + ps2_command(ps2dev, param, 0x30d1); + /* Enable features */ + param[0] = 0x11; param[1] = 0x05; param[2] = 0x0b; + ps2_command(ps2dev, param, 0x30d1); + /* Enable PS2++ */ + param[0] = 0x11; param[1] = 0x09; param[2] = 0xc3; + ps2_command(ps2dev, param, 0x30d1); + + param[0] = 0; + if (!ps2_command(ps2dev, param, 0x13d1) && + param[0] == 0x06 && param[1] == 0x00 && + param[2] == 0x14) { + use_ps2pp = true; + } + + } else { + + param[0] = param[1] = param[2] = 0; + ps2pp_cmd(psmouse, param, 0x39); /* Magic knock */ + ps2pp_cmd(psmouse, param, 0xDB); + + if ((param[0] & 0x78) == 0x48 && + (param[1] & 0xf3) == 0xc2 && + (param[2] & 0x03) == ((param[1] >> 2) & 3)) { + ps2pp_set_smartscroll(psmouse, false); + use_ps2pp = true; + } + } + + } else { + psmouse_warn(psmouse, + "Detected unknown Logitech mouse model %d\n", + model); + } + + if (set_properties) { + psmouse->vendor = "Logitech"; + psmouse->model = model; + + if (use_ps2pp) { + error = ps2pp_setup_protocol(psmouse, model_info); + if (error) + return error; + } + + if (buttons >= 3) + input_set_capability(psmouse->dev, EV_KEY, BTN_MIDDLE); + + if (model_info) + ps2pp_set_model_properties(psmouse, model_info, use_ps2pp); + } + + return use_ps2pp ? 0 : -ENXIO; +} + diff --git a/drivers/input/mouse/logips2pp.h b/drivers/input/mouse/logips2pp.h new file mode 100644 index 000000000..bf629453e --- /dev/null +++ b/drivers/input/mouse/logips2pp.h @@ -0,0 +1,23 @@ +/* + * Logitech PS/2++ mouse driver header + * + * Copyright (c) 2003 Vojtech Pavlik <vojtech@suse.cz> + * + * 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. + */ + +#ifndef _LOGIPS2PP_H +#define _LOGIPS2PP_H + +#ifdef CONFIG_MOUSE_PS2_LOGIPS2PP +int ps2pp_detect(struct psmouse *psmouse, bool set_properties); +#else +static inline int ps2pp_detect(struct psmouse *psmouse, bool set_properties) +{ + return -ENOSYS; +} +#endif /* CONFIG_MOUSE_PS2_LOGIPS2PP */ + +#endif diff --git a/drivers/input/mouse/maplemouse.c b/drivers/input/mouse/maplemouse.c new file mode 100644 index 000000000..25f0ecb90 --- /dev/null +++ b/drivers/input/mouse/maplemouse.c @@ -0,0 +1,149 @@ +/* + * SEGA Dreamcast mouse driver + * Based on drivers/usb/usbmouse.c + * + * Copyright (c) Yaegashi Takeshi, 2001 + * Copyright (c) Adrian McMenamin, 2008 - 2009 + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/maple.h> + +MODULE_AUTHOR("Adrian McMenamin <adrian@mcmen.demon.co.uk>"); +MODULE_DESCRIPTION("SEGA Dreamcast mouse driver"); +MODULE_LICENSE("GPL"); + +struct dc_mouse { + struct input_dev *dev; + struct maple_device *mdev; +}; + +static void dc_mouse_callback(struct mapleq *mq) +{ + int buttons, relx, rely, relz; + struct maple_device *mapledev = mq->dev; + struct dc_mouse *mse = maple_get_drvdata(mapledev); + struct input_dev *dev = mse->dev; + unsigned char *res = mq->recvbuf->buf; + + buttons = ~res[8]; + relx = *(unsigned short *)(res + 12) - 512; + rely = *(unsigned short *)(res + 14) - 512; + relz = *(unsigned short *)(res + 16) - 512; + + input_report_key(dev, BTN_LEFT, buttons & 4); + input_report_key(dev, BTN_MIDDLE, buttons & 9); + input_report_key(dev, BTN_RIGHT, buttons & 2); + input_report_rel(dev, REL_X, relx); + input_report_rel(dev, REL_Y, rely); + input_report_rel(dev, REL_WHEEL, relz); + input_sync(dev); +} + +static int dc_mouse_open(struct input_dev *dev) +{ + struct dc_mouse *mse = maple_get_drvdata(to_maple_dev(&dev->dev)); + + maple_getcond_callback(mse->mdev, dc_mouse_callback, HZ/50, + MAPLE_FUNC_MOUSE); + + return 0; +} + +static void dc_mouse_close(struct input_dev *dev) +{ + struct dc_mouse *mse = maple_get_drvdata(to_maple_dev(&dev->dev)); + + maple_getcond_callback(mse->mdev, dc_mouse_callback, 0, + MAPLE_FUNC_MOUSE); +} + +/* allow the mouse to be used */ +static int probe_maple_mouse(struct device *dev) +{ + struct maple_device *mdev = to_maple_dev(dev); + struct maple_driver *mdrv = to_maple_driver(dev->driver); + int error; + struct input_dev *input_dev; + struct dc_mouse *mse; + + mse = kzalloc(sizeof(struct dc_mouse), GFP_KERNEL); + if (!mse) { + error = -ENOMEM; + goto fail; + } + + input_dev = input_allocate_device(); + if (!input_dev) { + error = -ENOMEM; + goto fail_nomem; + } + + mse->dev = input_dev; + mse->mdev = mdev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE); + input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y) | + BIT_MASK(REL_WHEEL); + input_dev->open = dc_mouse_open; + input_dev->close = dc_mouse_close; + input_dev->name = mdev->product_name; + input_dev->id.bustype = BUS_HOST; + error = input_register_device(input_dev); + if (error) + goto fail_register; + + mdev->driver = mdrv; + maple_set_drvdata(mdev, mse); + + return error; + +fail_register: + input_free_device(input_dev); +fail_nomem: + kfree(mse); +fail: + return error; +} + +static int remove_maple_mouse(struct device *dev) +{ + struct maple_device *mdev = to_maple_dev(dev); + struct dc_mouse *mse = maple_get_drvdata(mdev); + + mdev->callback = NULL; + input_unregister_device(mse->dev); + maple_set_drvdata(mdev, NULL); + kfree(mse); + + return 0; +} + +static struct maple_driver dc_mouse_driver = { + .function = MAPLE_FUNC_MOUSE, + .drv = { + .name = "Dreamcast_mouse", + .probe = probe_maple_mouse, + .remove = remove_maple_mouse, + }, +}; + +static int __init dc_mouse_init(void) +{ + return maple_driver_register(&dc_mouse_driver); +} + +static void __exit dc_mouse_exit(void) +{ + maple_driver_unregister(&dc_mouse_driver); +} + +module_init(dc_mouse_init); +module_exit(dc_mouse_exit); diff --git a/drivers/input/mouse/navpoint.c b/drivers/input/mouse/navpoint.c new file mode 100644 index 000000000..d6e8f58a1 --- /dev/null +++ b/drivers/input/mouse/navpoint.c @@ -0,0 +1,365 @@ +/* + * Synaptics NavPoint (PXA27x SSP/SPI) driver. + * + * Copyright (C) 2012 Paul Parsons <lost.distance@yahoo.com> + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/input.h> +#include <linux/input/navpoint.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/pxa2xx_ssp.h> +#include <linux/slab.h> + +/* + * Synaptics Modular Embedded Protocol: Module Packet Format. + * Module header byte 2:0 = Length (# bytes that follow) + * Module header byte 4:3 = Control + * Module header byte 7:5 = Module Address + */ +#define HEADER_LENGTH(byte) ((byte) & 0x07) +#define HEADER_CONTROL(byte) (((byte) >> 3) & 0x03) +#define HEADER_ADDRESS(byte) ((byte) >> 5) + +struct navpoint { + struct ssp_device *ssp; + struct input_dev *input; + struct device *dev; + int gpio; + int index; + u8 data[1 + HEADER_LENGTH(0xff)]; +}; + +/* + * Initialization values for SSCR0_x, SSCR1_x, SSSR_x. + */ +static const u32 sscr0 = 0 + | SSCR0_TUM /* TIM = 1; No TUR interrupts */ + | SSCR0_RIM /* RIM = 1; No ROR interrupts */ + | SSCR0_SSE /* SSE = 1; SSP enabled */ + | SSCR0_Motorola /* FRF = 0; Motorola SPI */ + | SSCR0_DataSize(16) /* DSS = 15; Data size = 16-bit */ + ; +static const u32 sscr1 = 0 + | SSCR1_SCFR /* SCFR = 1; SSPSCLK only during transfers */ + | SSCR1_SCLKDIR /* SCLKDIR = 1; Slave mode */ + | SSCR1_SFRMDIR /* SFRMDIR = 1; Slave mode */ + | SSCR1_RWOT /* RWOT = 1; Receive without transmit mode */ + | SSCR1_RxTresh(1) /* RFT = 0; Receive FIFO threshold = 1 */ + | SSCR1_SPH /* SPH = 1; SSPSCLK inactive 0.5 + 1 cycles */ + | SSCR1_RIE /* RIE = 1; Receive FIFO interrupt enabled */ + ; +static const u32 sssr = 0 + | SSSR_BCE /* BCE = 1; Clear BCE */ + | SSSR_TUR /* TUR = 1; Clear TUR */ + | SSSR_EOC /* EOC = 1; Clear EOC */ + | SSSR_TINT /* TINT = 1; Clear TINT */ + | SSSR_PINT /* PINT = 1; Clear PINT */ + | SSSR_ROR /* ROR = 1; Clear ROR */ + ; + +/* + * MEP Query $22: Touchpad Coordinate Range Query is not supported by + * the NavPoint module, so sampled values provide the default limits. + */ +#define NAVPOINT_X_MIN 1278 +#define NAVPOINT_X_MAX 5340 +#define NAVPOINT_Y_MIN 1572 +#define NAVPOINT_Y_MAX 4396 +#define NAVPOINT_PRESSURE_MIN 0 +#define NAVPOINT_PRESSURE_MAX 255 + +static void navpoint_packet(struct navpoint *navpoint) +{ + int finger; + int gesture; + int x, y, z; + + switch (navpoint->data[0]) { + case 0xff: /* Garbage (packet?) between reset and Hello packet */ + case 0x00: /* Module 0, NULL packet */ + break; + + case 0x0e: /* Module 0, Absolute packet */ + finger = (navpoint->data[1] & 0x01); + gesture = (navpoint->data[1] & 0x02); + x = ((navpoint->data[2] & 0x1f) << 8) | navpoint->data[3]; + y = ((navpoint->data[4] & 0x1f) << 8) | navpoint->data[5]; + z = navpoint->data[6]; + input_report_key(navpoint->input, BTN_TOUCH, finger); + input_report_abs(navpoint->input, ABS_X, x); + input_report_abs(navpoint->input, ABS_Y, y); + input_report_abs(navpoint->input, ABS_PRESSURE, z); + input_report_key(navpoint->input, BTN_TOOL_FINGER, finger); + input_report_key(navpoint->input, BTN_LEFT, gesture); + input_sync(navpoint->input); + break; + + case 0x19: /* Module 0, Hello packet */ + if ((navpoint->data[1] & 0xf0) == 0x10) + break; + /* FALLTHROUGH */ + default: + dev_warn(navpoint->dev, + "spurious packet: data=0x%02x,0x%02x,...\n", + navpoint->data[0], navpoint->data[1]); + break; + } +} + +static irqreturn_t navpoint_irq(int irq, void *dev_id) +{ + struct navpoint *navpoint = dev_id; + struct ssp_device *ssp = navpoint->ssp; + irqreturn_t ret = IRQ_NONE; + u32 status; + + status = pxa_ssp_read_reg(ssp, SSSR); + if (status & sssr) { + dev_warn(navpoint->dev, + "unexpected interrupt: status=0x%08x\n", status); + pxa_ssp_write_reg(ssp, SSSR, (status & sssr)); + ret = IRQ_HANDLED; + } + + while (status & SSSR_RNE) { + u32 data; + + data = pxa_ssp_read_reg(ssp, SSDR); + navpoint->data[navpoint->index + 0] = (data >> 8); + navpoint->data[navpoint->index + 1] = data; + navpoint->index += 2; + if (HEADER_LENGTH(navpoint->data[0]) < navpoint->index) { + navpoint_packet(navpoint); + navpoint->index = 0; + } + status = pxa_ssp_read_reg(ssp, SSSR); + ret = IRQ_HANDLED; + } + + return ret; +} + +static void navpoint_up(struct navpoint *navpoint) +{ + struct ssp_device *ssp = navpoint->ssp; + int timeout; + + clk_prepare_enable(ssp->clk); + + pxa_ssp_write_reg(ssp, SSCR1, sscr1); + pxa_ssp_write_reg(ssp, SSSR, sssr); + pxa_ssp_write_reg(ssp, SSTO, 0); + pxa_ssp_write_reg(ssp, SSCR0, sscr0); /* SSCR0_SSE written last */ + + /* Wait until SSP port is ready for slave clock operations */ + for (timeout = 100; timeout != 0; --timeout) { + if (!(pxa_ssp_read_reg(ssp, SSSR) & SSSR_CSS)) + break; + msleep(1); + } + + if (timeout == 0) + dev_err(navpoint->dev, + "timeout waiting for SSSR[CSS] to clear\n"); + + if (gpio_is_valid(navpoint->gpio)) + gpio_set_value(navpoint->gpio, 1); +} + +static void navpoint_down(struct navpoint *navpoint) +{ + struct ssp_device *ssp = navpoint->ssp; + + if (gpio_is_valid(navpoint->gpio)) + gpio_set_value(navpoint->gpio, 0); + + pxa_ssp_write_reg(ssp, SSCR0, 0); + + clk_disable_unprepare(ssp->clk); +} + +static int navpoint_open(struct input_dev *input) +{ + struct navpoint *navpoint = input_get_drvdata(input); + + navpoint_up(navpoint); + + return 0; +} + +static void navpoint_close(struct input_dev *input) +{ + struct navpoint *navpoint = input_get_drvdata(input); + + navpoint_down(navpoint); +} + +static int navpoint_probe(struct platform_device *pdev) +{ + const struct navpoint_platform_data *pdata = + dev_get_platdata(&pdev->dev); + struct ssp_device *ssp; + struct input_dev *input; + struct navpoint *navpoint; + int error; + + if (!pdata) { + dev_err(&pdev->dev, "no platform data\n"); + return -EINVAL; + } + + if (gpio_is_valid(pdata->gpio)) { + error = gpio_request_one(pdata->gpio, GPIOF_OUT_INIT_LOW, + "SYNAPTICS_ON"); + if (error) + return error; + } + + ssp = pxa_ssp_request(pdata->port, pdev->name); + if (!ssp) { + error = -ENODEV; + goto err_free_gpio; + } + + /* HaRET does not disable devices before jumping into Linux */ + if (pxa_ssp_read_reg(ssp, SSCR0) & SSCR0_SSE) { + pxa_ssp_write_reg(ssp, SSCR0, 0); + dev_warn(&pdev->dev, "ssp%d already enabled\n", pdata->port); + } + + navpoint = kzalloc(sizeof(*navpoint), GFP_KERNEL); + input = input_allocate_device(); + if (!navpoint || !input) { + error = -ENOMEM; + goto err_free_mem; + } + + navpoint->ssp = ssp; + navpoint->input = input; + navpoint->dev = &pdev->dev; + navpoint->gpio = pdata->gpio; + + input->name = pdev->name; + input->dev.parent = &pdev->dev; + + __set_bit(EV_KEY, input->evbit); + __set_bit(EV_ABS, input->evbit); + __set_bit(BTN_LEFT, input->keybit); + __set_bit(BTN_TOUCH, input->keybit); + __set_bit(BTN_TOOL_FINGER, input->keybit); + + input_set_abs_params(input, ABS_X, + NAVPOINT_X_MIN, NAVPOINT_X_MAX, 0, 0); + input_set_abs_params(input, ABS_Y, + NAVPOINT_Y_MIN, NAVPOINT_Y_MAX, 0, 0); + input_set_abs_params(input, ABS_PRESSURE, + NAVPOINT_PRESSURE_MIN, NAVPOINT_PRESSURE_MAX, + 0, 0); + + input->open = navpoint_open; + input->close = navpoint_close; + + input_set_drvdata(input, navpoint); + + error = request_irq(ssp->irq, navpoint_irq, 0, pdev->name, navpoint); + if (error) + goto err_free_mem; + + error = input_register_device(input); + if (error) + goto err_free_irq; + + platform_set_drvdata(pdev, navpoint); + dev_dbg(&pdev->dev, "ssp%d, irq %d\n", pdata->port, ssp->irq); + + return 0; + +err_free_irq: + free_irq(ssp->irq, navpoint); +err_free_mem: + input_free_device(input); + kfree(navpoint); + pxa_ssp_free(ssp); +err_free_gpio: + if (gpio_is_valid(pdata->gpio)) + gpio_free(pdata->gpio); + + return error; +} + +static int navpoint_remove(struct platform_device *pdev) +{ + const struct navpoint_platform_data *pdata = + dev_get_platdata(&pdev->dev); + struct navpoint *navpoint = platform_get_drvdata(pdev); + struct ssp_device *ssp = navpoint->ssp; + + free_irq(ssp->irq, navpoint); + + input_unregister_device(navpoint->input); + kfree(navpoint); + + pxa_ssp_free(ssp); + + if (gpio_is_valid(pdata->gpio)) + gpio_free(pdata->gpio); + + return 0; +} + +static int __maybe_unused navpoint_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct navpoint *navpoint = platform_get_drvdata(pdev); + struct input_dev *input = navpoint->input; + + mutex_lock(&input->mutex); + if (input->users) + navpoint_down(navpoint); + mutex_unlock(&input->mutex); + + return 0; +} + +static int __maybe_unused navpoint_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct navpoint *navpoint = platform_get_drvdata(pdev); + struct input_dev *input = navpoint->input; + + mutex_lock(&input->mutex); + if (input->users) + navpoint_up(navpoint); + mutex_unlock(&input->mutex); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(navpoint_pm_ops, navpoint_suspend, navpoint_resume); + +static struct platform_driver navpoint_driver = { + .probe = navpoint_probe, + .remove = navpoint_remove, + .driver = { + .name = "navpoint", + .pm = &navpoint_pm_ops, + }, +}; + +module_platform_driver(navpoint_driver); + +MODULE_AUTHOR("Paul Parsons <lost.distance@yahoo.com>"); +MODULE_DESCRIPTION("Synaptics NavPoint (PXA27x SSP/SPI) driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:navpoint"); diff --git a/drivers/input/mouse/pc110pad.c b/drivers/input/mouse/pc110pad.c new file mode 100644 index 000000000..b8965e6bc --- /dev/null +++ b/drivers/input/mouse/pc110pad.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2000-2001 Vojtech Pavlik + * + * Based on the work of: + * Alan Cox Robin O'Leary + */ + +/* + * IBM PC110 touchpad driver for Linux + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/delay.h> + +#include <asm/io.h> +#include <asm/irq.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("IBM PC110 touchpad driver"); +MODULE_LICENSE("GPL"); + +#define PC110PAD_OFF 0x30 +#define PC110PAD_ON 0x38 + +static int pc110pad_irq = 10; +static int pc110pad_io = 0x15e0; + +static struct input_dev *pc110pad_dev; +static int pc110pad_data[3]; +static int pc110pad_count; + +static irqreturn_t pc110pad_interrupt(int irq, void *ptr) +{ + int value = inb_p(pc110pad_io); + int handshake = inb_p(pc110pad_io + 2); + + outb(handshake | 1, pc110pad_io + 2); + udelay(2); + outb(handshake & ~1, pc110pad_io + 2); + udelay(2); + inb_p(0x64); + + pc110pad_data[pc110pad_count++] = value; + + if (pc110pad_count < 3) + return IRQ_HANDLED; + + input_report_key(pc110pad_dev, BTN_TOUCH, + pc110pad_data[0] & 0x01); + input_report_abs(pc110pad_dev, ABS_X, + pc110pad_data[1] | ((pc110pad_data[0] << 3) & 0x80) | ((pc110pad_data[0] << 1) & 0x100)); + input_report_abs(pc110pad_dev, ABS_Y, + pc110pad_data[2] | ((pc110pad_data[0] << 4) & 0x80)); + input_sync(pc110pad_dev); + + pc110pad_count = 0; + return IRQ_HANDLED; +} + +static void pc110pad_close(struct input_dev *dev) +{ + outb(PC110PAD_OFF, pc110pad_io + 2); +} + +static int pc110pad_open(struct input_dev *dev) +{ + pc110pad_interrupt(0, NULL); + pc110pad_interrupt(0, NULL); + pc110pad_interrupt(0, NULL); + outb(PC110PAD_ON, pc110pad_io + 2); + pc110pad_count = 0; + + return 0; +} + +/* + * We try to avoid enabling the hardware if it's not + * there, but we don't know how to test. But we do know + * that the PC110 is not a PCI system. So if we find any + * PCI devices in the machine, we don't have a PC110. + */ +static int __init pc110pad_init(void) +{ + int err; + + if (!no_pci_devices()) + return -ENODEV; + + if (!request_region(pc110pad_io, 4, "pc110pad")) { + printk(KERN_ERR "pc110pad: I/O area %#x-%#x in use.\n", + pc110pad_io, pc110pad_io + 4); + return -EBUSY; + } + + outb(PC110PAD_OFF, pc110pad_io + 2); + + if (request_irq(pc110pad_irq, pc110pad_interrupt, 0, "pc110pad", NULL)) { + printk(KERN_ERR "pc110pad: Unable to get irq %d.\n", pc110pad_irq); + err = -EBUSY; + goto err_release_region; + } + + pc110pad_dev = input_allocate_device(); + if (!pc110pad_dev) { + printk(KERN_ERR "pc110pad: Not enough memory.\n"); + err = -ENOMEM; + goto err_free_irq; + } + + pc110pad_dev->name = "IBM PC110 TouchPad"; + pc110pad_dev->phys = "isa15e0/input0"; + pc110pad_dev->id.bustype = BUS_ISA; + pc110pad_dev->id.vendor = 0x0003; + pc110pad_dev->id.product = 0x0001; + pc110pad_dev->id.version = 0x0100; + + pc110pad_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + pc110pad_dev->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y); + pc110pad_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + input_abs_set_max(pc110pad_dev, ABS_X, 0x1ff); + input_abs_set_max(pc110pad_dev, ABS_Y, 0x0ff); + + pc110pad_dev->open = pc110pad_open; + pc110pad_dev->close = pc110pad_close; + + err = input_register_device(pc110pad_dev); + if (err) + goto err_free_dev; + + return 0; + + err_free_dev: + input_free_device(pc110pad_dev); + err_free_irq: + free_irq(pc110pad_irq, NULL); + err_release_region: + release_region(pc110pad_io, 4); + + return err; +} + +static void __exit pc110pad_exit(void) +{ + outb(PC110PAD_OFF, pc110pad_io + 2); + free_irq(pc110pad_irq, NULL); + input_unregister_device(pc110pad_dev); + release_region(pc110pad_io, 4); +} + +module_init(pc110pad_init); +module_exit(pc110pad_exit); diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c new file mode 100644 index 000000000..a9040c0fb --- /dev/null +++ b/drivers/input/mouse/psmouse-base.c @@ -0,0 +1,2090 @@ +/* + * PS/2 mouse driver + * + * Copyright (c) 1999-2002 Vojtech Pavlik + * Copyright (c) 2003-2004 Dmitry Torokhov + */ + +/* + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define psmouse_fmt(fmt) fmt + +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/libps2.h> +#include <linux/mutex.h> +#include <linux/types.h> + +#include "psmouse.h" +#include "synaptics.h" +#include "logips2pp.h" +#include "alps.h" +#include "hgpk.h" +#include "lifebook.h" +#include "trackpoint.h" +#include "touchkit_ps2.h" +#include "elantech.h" +#include "sentelic.h" +#include "cypress_ps2.h" +#include "focaltech.h" +#include "vmmouse.h" +#include "byd.h" + +#define DRIVER_DESC "PS/2 mouse driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static unsigned int psmouse_max_proto = PSMOUSE_AUTO; +static int psmouse_set_maxproto(const char *val, const struct kernel_param *); +static int psmouse_get_maxproto(char *buffer, const struct kernel_param *kp); +static const struct kernel_param_ops param_ops_proto_abbrev = { + .set = psmouse_set_maxproto, + .get = psmouse_get_maxproto, +}; +#define param_check_proto_abbrev(name, p) __param_check(name, p, unsigned int) +module_param_named(proto, psmouse_max_proto, proto_abbrev, 0644); +MODULE_PARM_DESC(proto, "Highest protocol extension to probe (bare, imps, exps, any). Useful for KVM switches."); + +static unsigned int psmouse_resolution = 200; +module_param_named(resolution, psmouse_resolution, uint, 0644); +MODULE_PARM_DESC(resolution, "Resolution, in dpi."); + +static unsigned int psmouse_rate = 100; +module_param_named(rate, psmouse_rate, uint, 0644); +MODULE_PARM_DESC(rate, "Report rate, in reports per second."); + +static bool psmouse_smartscroll = true; +module_param_named(smartscroll, psmouse_smartscroll, bool, 0644); +MODULE_PARM_DESC(smartscroll, "Logitech Smartscroll autorepeat, 1 = enabled (default), 0 = disabled."); + +static bool psmouse_a4tech_2wheels; +module_param_named(a4tech_workaround, psmouse_a4tech_2wheels, bool, 0644); +MODULE_PARM_DESC(a4tech_workaround, "A4Tech second scroll wheel workaround, 1 = enabled, 0 = disabled (default)."); + +static unsigned int psmouse_resetafter = 5; +module_param_named(resetafter, psmouse_resetafter, uint, 0644); +MODULE_PARM_DESC(resetafter, "Reset device after so many bad packets (0 = never)."); + +static unsigned int psmouse_resync_time; +module_param_named(resync_time, psmouse_resync_time, uint, 0644); +MODULE_PARM_DESC(resync_time, "How long can mouse stay idle before forcing resync (in seconds, 0 = never)."); + +PSMOUSE_DEFINE_ATTR(protocol, S_IWUSR | S_IRUGO, + NULL, + psmouse_attr_show_protocol, psmouse_attr_set_protocol); +PSMOUSE_DEFINE_ATTR(rate, S_IWUSR | S_IRUGO, + (void *) offsetof(struct psmouse, rate), + psmouse_show_int_attr, psmouse_attr_set_rate); +PSMOUSE_DEFINE_ATTR(resolution, S_IWUSR | S_IRUGO, + (void *) offsetof(struct psmouse, resolution), + psmouse_show_int_attr, psmouse_attr_set_resolution); +PSMOUSE_DEFINE_ATTR(resetafter, S_IWUSR | S_IRUGO, + (void *) offsetof(struct psmouse, resetafter), + psmouse_show_int_attr, psmouse_set_int_attr); +PSMOUSE_DEFINE_ATTR(resync_time, S_IWUSR | S_IRUGO, + (void *) offsetof(struct psmouse, resync_time), + psmouse_show_int_attr, psmouse_set_int_attr); + +static struct attribute *psmouse_attributes[] = { + &psmouse_attr_protocol.dattr.attr, + &psmouse_attr_rate.dattr.attr, + &psmouse_attr_resolution.dattr.attr, + &psmouse_attr_resetafter.dattr.attr, + &psmouse_attr_resync_time.dattr.attr, + NULL +}; + +static const struct attribute_group psmouse_attribute_group = { + .attrs = psmouse_attributes, +}; + +/* + * psmouse_mutex protects all operations changing state of mouse + * (connecting, disconnecting, changing rate or resolution via + * sysfs). We could use a per-device semaphore but since there + * rarely more than one PS/2 mouse connected and since semaphore + * is taken in "slow" paths it is not worth it. + */ +static DEFINE_MUTEX(psmouse_mutex); + +static struct workqueue_struct *kpsmoused_wq; + +void psmouse_report_standard_buttons(struct input_dev *dev, u8 buttons) +{ + input_report_key(dev, BTN_LEFT, buttons & BIT(0)); + input_report_key(dev, BTN_MIDDLE, buttons & BIT(2)); + input_report_key(dev, BTN_RIGHT, buttons & BIT(1)); +} + +void psmouse_report_standard_motion(struct input_dev *dev, u8 *packet) +{ + int x, y; + + x = packet[1] ? packet[1] - ((packet[0] << 4) & 0x100) : 0; + y = packet[2] ? packet[2] - ((packet[0] << 3) & 0x100) : 0; + + input_report_rel(dev, REL_X, x); + input_report_rel(dev, REL_Y, -y); +} + +void psmouse_report_standard_packet(struct input_dev *dev, u8 *packet) +{ + psmouse_report_standard_buttons(dev, packet[0]); + psmouse_report_standard_motion(dev, packet); +} + +/* + * psmouse_process_byte() analyzes the PS/2 data stream and reports + * relevant events to the input module once full packet has arrived. + */ +psmouse_ret_t psmouse_process_byte(struct psmouse *psmouse) +{ + struct input_dev *dev = psmouse->dev; + u8 *packet = psmouse->packet; + int wheel; + + if (psmouse->pktcnt < psmouse->pktsize) + return PSMOUSE_GOOD_DATA; + + /* Full packet accumulated, process it */ + + switch (psmouse->protocol->type) { + case PSMOUSE_IMPS: + /* IntelliMouse has scroll wheel */ + input_report_rel(dev, REL_WHEEL, -(s8) packet[3]); + break; + + case PSMOUSE_IMEX: + /* Scroll wheel and buttons on IntelliMouse Explorer */ + switch (packet[3] & 0xC0) { + case 0x80: /* vertical scroll on IntelliMouse Explorer 4.0 */ + input_report_rel(dev, REL_WHEEL, + -sign_extend32(packet[3], 5)); + break; + case 0x40: /* horizontal scroll on IntelliMouse Explorer 4.0 */ + input_report_rel(dev, REL_HWHEEL, + -sign_extend32(packet[3], 5)); + break; + case 0x00: + case 0xC0: + wheel = sign_extend32(packet[3], 3); + + /* + * Some A4Tech mice have two scroll wheels, with first + * one reporting +/-1 in the lower nibble, and second + * one reporting +/-2. + */ + if (psmouse_a4tech_2wheels && abs(wheel) > 1) + input_report_rel(dev, REL_HWHEEL, wheel / 2); + else + input_report_rel(dev, REL_WHEEL, -wheel); + + input_report_key(dev, BTN_SIDE, packet[3] & BIT(4)); + input_report_key(dev, BTN_EXTRA, packet[3] & BIT(5)); + break; + } + break; + + case PSMOUSE_GENPS: + /* Report scroll buttons on NetMice */ + input_report_rel(dev, REL_WHEEL, -(s8) packet[3]); + + /* Extra buttons on Genius NewNet 3D */ + input_report_key(dev, BTN_SIDE, packet[0] & BIT(6)); + input_report_key(dev, BTN_EXTRA, packet[0] & BIT(7)); + break; + + case PSMOUSE_THINKPS: + /* Extra button on ThinkingMouse */ + input_report_key(dev, BTN_EXTRA, packet[0] & BIT(3)); + + /* + * Without this bit of weirdness moving up gives wildly + * high Y changes. + */ + packet[1] |= (packet[0] & 0x40) << 1; + break; + + case PSMOUSE_CORTRON: + /* + * Cortron PS2 Trackball reports SIDE button in the + * 4th bit of the first byte. + */ + input_report_key(dev, BTN_SIDE, packet[0] & BIT(3)); + packet[0] |= BIT(3); + break; + + default: + break; + } + + /* Generic PS/2 Mouse */ + packet[0] |= psmouse->extra_buttons; + psmouse_report_standard_packet(dev, packet); + + input_sync(dev); + + return PSMOUSE_FULL_PACKET; +} + +void psmouse_queue_work(struct psmouse *psmouse, struct delayed_work *work, + unsigned long delay) +{ + queue_delayed_work(kpsmoused_wq, work, delay); +} + +/* + * __psmouse_set_state() sets new psmouse state and resets all flags. + */ +static inline void __psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state) +{ + psmouse->state = new_state; + psmouse->pktcnt = psmouse->out_of_sync_cnt = 0; + psmouse->ps2dev.flags = 0; + psmouse->last = jiffies; +} + +/* + * psmouse_set_state() sets new psmouse state and resets all flags and + * counters while holding serio lock so fighting with interrupt handler + * is not a concern. + */ +void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state) +{ + serio_pause_rx(psmouse->ps2dev.serio); + __psmouse_set_state(psmouse, new_state); + serio_continue_rx(psmouse->ps2dev.serio); +} + +/* + * psmouse_handle_byte() processes one byte of the input data stream + * by calling corresponding protocol handler. + */ +static int psmouse_handle_byte(struct psmouse *psmouse) +{ + psmouse_ret_t rc = psmouse->protocol_handler(psmouse); + + switch (rc) { + case PSMOUSE_BAD_DATA: + if (psmouse->state == PSMOUSE_ACTIVATED) { + psmouse_warn(psmouse, + "%s at %s lost sync at byte %d\n", + psmouse->name, psmouse->phys, + psmouse->pktcnt); + if (++psmouse->out_of_sync_cnt == psmouse->resetafter) { + __psmouse_set_state(psmouse, PSMOUSE_IGNORE); + psmouse_notice(psmouse, + "issuing reconnect request\n"); + serio_reconnect(psmouse->ps2dev.serio); + return -EIO; + } + } + psmouse->pktcnt = 0; + break; + + case PSMOUSE_FULL_PACKET: + psmouse->pktcnt = 0; + if (psmouse->out_of_sync_cnt) { + psmouse->out_of_sync_cnt = 0; + psmouse_notice(psmouse, + "%s at %s - driver resynced.\n", + psmouse->name, psmouse->phys); + } + break; + + case PSMOUSE_GOOD_DATA: + break; + } + return 0; +} + +static void psmouse_handle_oob_data(struct psmouse *psmouse, u8 data) +{ + switch (psmouse->oob_data_type) { + case PSMOUSE_OOB_NONE: + psmouse->oob_data_type = data; + break; + + case PSMOUSE_OOB_EXTRA_BTNS: + psmouse_report_standard_buttons(psmouse->dev, data); + input_sync(psmouse->dev); + + psmouse->extra_buttons = data; + psmouse->oob_data_type = PSMOUSE_OOB_NONE; + break; + + default: + psmouse_warn(psmouse, + "unknown OOB_DATA type: 0x%02x\n", + psmouse->oob_data_type); + psmouse->oob_data_type = PSMOUSE_OOB_NONE; + break; + } +} + +/* + * psmouse_interrupt() handles incoming characters, either passing them + * for normal processing or gathering them as command response. + */ +static irqreturn_t psmouse_interrupt(struct serio *serio, + u8 data, unsigned int flags) +{ + struct psmouse *psmouse = serio_get_drvdata(serio); + + if (psmouse->state == PSMOUSE_IGNORE) + goto out; + + if (unlikely((flags & SERIO_TIMEOUT) || + ((flags & SERIO_PARITY) && + !psmouse->protocol->ignore_parity))) { + + if (psmouse->state == PSMOUSE_ACTIVATED) + psmouse_warn(psmouse, + "bad data from KBC -%s%s\n", + flags & SERIO_TIMEOUT ? " timeout" : "", + flags & SERIO_PARITY ? " bad parity" : ""); + ps2_cmd_aborted(&psmouse->ps2dev); + goto out; + } + + if (flags & SERIO_OOB_DATA) { + psmouse_handle_oob_data(psmouse, data); + goto out; + } + + if (unlikely(psmouse->ps2dev.flags & PS2_FLAG_ACK)) + if (ps2_handle_ack(&psmouse->ps2dev, data)) + goto out; + + if (unlikely(psmouse->ps2dev.flags & PS2_FLAG_CMD)) + if (ps2_handle_response(&psmouse->ps2dev, data)) + goto out; + + if (psmouse->state <= PSMOUSE_RESYNCING) + goto out; + + if (psmouse->state == PSMOUSE_ACTIVATED && + psmouse->pktcnt && time_after(jiffies, psmouse->last + HZ/2)) { + psmouse_info(psmouse, "%s at %s lost synchronization, throwing %d bytes away.\n", + psmouse->name, psmouse->phys, psmouse->pktcnt); + psmouse->badbyte = psmouse->packet[0]; + __psmouse_set_state(psmouse, PSMOUSE_RESYNCING); + psmouse_queue_work(psmouse, &psmouse->resync_work, 0); + goto out; + } + + psmouse->packet[psmouse->pktcnt++] = data; + + /* Check if this is a new device announcement (0xAA 0x00) */ + if (unlikely(psmouse->packet[0] == PSMOUSE_RET_BAT && psmouse->pktcnt <= 2)) { + if (psmouse->pktcnt == 1) { + psmouse->last = jiffies; + goto out; + } + + if (psmouse->packet[1] == PSMOUSE_RET_ID || + (psmouse->protocol->type == PSMOUSE_HGPK && + psmouse->packet[1] == PSMOUSE_RET_BAT)) { + __psmouse_set_state(psmouse, PSMOUSE_IGNORE); + serio_reconnect(serio); + goto out; + } + + /* Not a new device, try processing first byte normally */ + psmouse->pktcnt = 1; + if (psmouse_handle_byte(psmouse)) + goto out; + + psmouse->packet[psmouse->pktcnt++] = data; + } + + /* + * See if we need to force resync because mouse was idle for + * too long. + */ + if (psmouse->state == PSMOUSE_ACTIVATED && + psmouse->pktcnt == 1 && psmouse->resync_time && + time_after(jiffies, psmouse->last + psmouse->resync_time * HZ)) { + psmouse->badbyte = psmouse->packet[0]; + __psmouse_set_state(psmouse, PSMOUSE_RESYNCING); + psmouse_queue_work(psmouse, &psmouse->resync_work, 0); + goto out; + } + + psmouse->last = jiffies; + psmouse_handle_byte(psmouse); + + out: + return IRQ_HANDLED; +} + +/* + * psmouse_reset() resets the mouse into power-on state. + */ +int psmouse_reset(struct psmouse *psmouse) +{ + u8 param[2]; + int error; + + error = ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_RESET_BAT); + if (error) + return error; + + if (param[0] != PSMOUSE_RET_BAT && param[1] != PSMOUSE_RET_ID) + return -EIO; + + return 0; +} + +/* + * Here we set the mouse resolution. + */ +void psmouse_set_resolution(struct psmouse *psmouse, unsigned int resolution) +{ + static const u8 params[] = { 0, 1, 2, 2, 3 }; + u8 p; + + if (resolution == 0 || resolution > 200) + resolution = 200; + + p = params[resolution / 50]; + ps2_command(&psmouse->ps2dev, &p, PSMOUSE_CMD_SETRES); + psmouse->resolution = 25 << p; +} + +/* + * Here we set the mouse report rate. + */ +static void psmouse_set_rate(struct psmouse *psmouse, unsigned int rate) +{ + static const u8 rates[] = { 200, 100, 80, 60, 40, 20, 10, 0 }; + u8 r; + int i = 0; + + while (rates[i] > rate) + i++; + r = rates[i]; + ps2_command(&psmouse->ps2dev, &r, PSMOUSE_CMD_SETRATE); + psmouse->rate = r; +} + +/* + * Here we set the mouse scaling. + */ +static void psmouse_set_scale(struct psmouse *psmouse, enum psmouse_scale scale) +{ + ps2_command(&psmouse->ps2dev, NULL, + scale == PSMOUSE_SCALE21 ? PSMOUSE_CMD_SETSCALE21 : + PSMOUSE_CMD_SETSCALE11); +} + +/* + * psmouse_poll() - default poll handler. Everyone except for ALPS uses it. + */ +static int psmouse_poll(struct psmouse *psmouse) +{ + return ps2_command(&psmouse->ps2dev, psmouse->packet, + PSMOUSE_CMD_POLL | (psmouse->pktsize << 8)); +} + +static bool psmouse_check_pnp_id(const char *id, const char * const ids[]) +{ + int i; + + for (i = 0; ids[i]; i++) + if (!strcasecmp(id, ids[i])) + return true; + + return false; +} + +/* + * psmouse_matches_pnp_id - check if psmouse matches one of the passed in ids. + */ +bool psmouse_matches_pnp_id(struct psmouse *psmouse, const char * const ids[]) +{ + struct serio *serio = psmouse->ps2dev.serio; + char *p, *fw_id_copy, *save_ptr; + bool found = false; + + if (strncmp(serio->firmware_id, "PNP: ", 5)) + return false; + + fw_id_copy = kstrndup(&serio->firmware_id[5], + sizeof(serio->firmware_id) - 5, + GFP_KERNEL); + if (!fw_id_copy) + return false; + + save_ptr = fw_id_copy; + while ((p = strsep(&fw_id_copy, " ")) != NULL) { + if (psmouse_check_pnp_id(p, ids)) { + found = true; + break; + } + } + + kfree(save_ptr); + return found; +} + +/* + * Genius NetMouse magic init. + */ +static int genius_detect(struct psmouse *psmouse, bool set_properties) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + u8 param[4]; + + param[0] = 3; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11); + ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO); + + if (param[0] != 0x00 || param[1] != 0x33 || param[2] != 0x55) + return -ENODEV; + + if (set_properties) { + __set_bit(BTN_MIDDLE, psmouse->dev->keybit); + __set_bit(BTN_EXTRA, psmouse->dev->keybit); + __set_bit(BTN_SIDE, psmouse->dev->keybit); + __set_bit(REL_WHEEL, psmouse->dev->relbit); + + psmouse->vendor = "Genius"; + psmouse->name = "Mouse"; + psmouse->pktsize = 4; + } + + return 0; +} + +/* + * IntelliMouse magic init. + */ +static int intellimouse_detect(struct psmouse *psmouse, bool set_properties) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + u8 param[2]; + + param[0] = 200; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + param[0] = 100; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + param[0] = 80; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + ps2_command(ps2dev, param, PSMOUSE_CMD_GETID); + + if (param[0] != 3) + return -ENODEV; + + if (set_properties) { + __set_bit(BTN_MIDDLE, psmouse->dev->keybit); + __set_bit(REL_WHEEL, psmouse->dev->relbit); + + if (!psmouse->vendor) + psmouse->vendor = "Generic"; + if (!psmouse->name) + psmouse->name = "Wheel Mouse"; + psmouse->pktsize = 4; + } + + return 0; +} + +/* + * Try IntelliMouse/Explorer magic init. + */ +static int im_explorer_detect(struct psmouse *psmouse, bool set_properties) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + u8 param[2]; + + intellimouse_detect(psmouse, 0); + + param[0] = 200; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + param[0] = 200; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + param[0] = 80; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + ps2_command(ps2dev, param, PSMOUSE_CMD_GETID); + + if (param[0] != 4) + return -ENODEV; + + /* Magic to enable horizontal scrolling on IntelliMouse 4.0 */ + param[0] = 200; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + param[0] = 80; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + param[0] = 40; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + + if (set_properties) { + __set_bit(BTN_MIDDLE, psmouse->dev->keybit); + __set_bit(REL_WHEEL, psmouse->dev->relbit); + __set_bit(REL_HWHEEL, psmouse->dev->relbit); + __set_bit(BTN_SIDE, psmouse->dev->keybit); + __set_bit(BTN_EXTRA, psmouse->dev->keybit); + + if (!psmouse->vendor) + psmouse->vendor = "Generic"; + if (!psmouse->name) + psmouse->name = "Explorer Mouse"; + psmouse->pktsize = 4; + } + + return 0; +} + +/* + * Kensington ThinkingMouse / ExpertMouse magic init. + */ +static int thinking_detect(struct psmouse *psmouse, bool set_properties) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + u8 param[2]; + static const u8 seq[] = { 20, 60, 40, 20, 20, 60, 40, 20, 20 }; + int i; + + param[0] = 10; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + param[0] = 0; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + for (i = 0; i < ARRAY_SIZE(seq); i++) { + param[0] = seq[i]; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + } + ps2_command(ps2dev, param, PSMOUSE_CMD_GETID); + + if (param[0] != 2) + return -ENODEV; + + if (set_properties) { + __set_bit(BTN_MIDDLE, psmouse->dev->keybit); + __set_bit(BTN_EXTRA, psmouse->dev->keybit); + + psmouse->vendor = "Kensington"; + psmouse->name = "ThinkingMouse"; + } + + return 0; +} + +/* + * Bare PS/2 protocol "detection". Always succeeds. + */ +static int ps2bare_detect(struct psmouse *psmouse, bool set_properties) +{ + if (set_properties) { + if (!psmouse->vendor) + psmouse->vendor = "Generic"; + if (!psmouse->name) + psmouse->name = "Mouse"; + + /* + * We have no way of figuring true number of buttons so let's + * assume that the device has 3. + */ + input_set_capability(psmouse->dev, EV_KEY, BTN_MIDDLE); + } + + return 0; +} + +/* + * Cortron PS/2 protocol detection. There's no special way to detect it, so it + * must be forced by sysfs protocol writing. + */ +static int cortron_detect(struct psmouse *psmouse, bool set_properties) +{ + if (set_properties) { + psmouse->vendor = "Cortron"; + psmouse->name = "PS/2 Trackball"; + + __set_bit(BTN_MIDDLE, psmouse->dev->keybit); + __set_bit(BTN_SIDE, psmouse->dev->keybit); + } + + return 0; +} + +static const struct psmouse_protocol psmouse_protocols[] = { + { + .type = PSMOUSE_PS2, + .name = "PS/2", + .alias = "bare", + .maxproto = true, + .ignore_parity = true, + .detect = ps2bare_detect, + .try_passthru = true, + }, +#ifdef CONFIG_MOUSE_PS2_LOGIPS2PP + { + .type = PSMOUSE_PS2PP, + .name = "PS2++", + .alias = "logitech", + .detect = ps2pp_detect, + }, +#endif + { + .type = PSMOUSE_THINKPS, + .name = "ThinkPS/2", + .alias = "thinkps", + .detect = thinking_detect, + }, +#ifdef CONFIG_MOUSE_PS2_CYPRESS + { + .type = PSMOUSE_CYPRESS, + .name = "CyPS/2", + .alias = "cypress", + .detect = cypress_detect, + .init = cypress_init, + }, +#endif + { + .type = PSMOUSE_GENPS, + .name = "GenPS/2", + .alias = "genius", + .detect = genius_detect, + }, + { + .type = PSMOUSE_IMPS, + .name = "ImPS/2", + .alias = "imps", + .maxproto = true, + .ignore_parity = true, + .detect = intellimouse_detect, + .try_passthru = true, + }, + { + .type = PSMOUSE_IMEX, + .name = "ImExPS/2", + .alias = "exps", + .maxproto = true, + .ignore_parity = true, + .detect = im_explorer_detect, + .try_passthru = true, + }, +#ifdef CONFIG_MOUSE_PS2_SYNAPTICS + { + .type = PSMOUSE_SYNAPTICS, + .name = "SynPS/2", + .alias = "synaptics", + .detect = synaptics_detect, + .init = synaptics_init_absolute, + }, + { + .type = PSMOUSE_SYNAPTICS_RELATIVE, + .name = "SynRelPS/2", + .alias = "synaptics-relative", + .detect = synaptics_detect, + .init = synaptics_init_relative, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_SYNAPTICS_SMBUS + { + .type = PSMOUSE_SYNAPTICS_SMBUS, + .name = "SynSMBus", + .alias = "synaptics-smbus", + .detect = synaptics_detect, + .init = synaptics_init_smbus, + .smbus_companion = true, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_ALPS + { + .type = PSMOUSE_ALPS, + .name = "AlpsPS/2", + .alias = "alps", + .detect = alps_detect, + .init = alps_init, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_LIFEBOOK + { + .type = PSMOUSE_LIFEBOOK, + .name = "LBPS/2", + .alias = "lifebook", + .detect = lifebook_detect, + .init = lifebook_init, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_TRACKPOINT + { + .type = PSMOUSE_TRACKPOINT, + .name = "TPPS/2", + .alias = "trackpoint", + .detect = trackpoint_detect, + .try_passthru = true, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_TOUCHKIT + { + .type = PSMOUSE_TOUCHKIT_PS2, + .name = "touchkitPS/2", + .alias = "touchkit", + .detect = touchkit_ps2_detect, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_OLPC + { + .type = PSMOUSE_HGPK, + .name = "OLPC HGPK", + .alias = "hgpk", + .detect = hgpk_detect, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_ELANTECH + { + .type = PSMOUSE_ELANTECH, + .name = "ETPS/2", + .alias = "elantech", + .detect = elantech_detect, + .init = elantech_init_ps2, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_ELANTECH_SMBUS + { + .type = PSMOUSE_ELANTECH_SMBUS, + .name = "ETSMBus", + .alias = "elantech-smbus", + .detect = elantech_detect, + .init = elantech_init_smbus, + .smbus_companion = true, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_SENTELIC + { + .type = PSMOUSE_FSP, + .name = "FSPPS/2", + .alias = "fsp", + .detect = fsp_detect, + .init = fsp_init, + }, +#endif + { + .type = PSMOUSE_CORTRON, + .name = "CortronPS/2", + .alias = "cortps", + .detect = cortron_detect, + }, +#ifdef CONFIG_MOUSE_PS2_FOCALTECH + { + .type = PSMOUSE_FOCALTECH, + .name = "FocalTechPS/2", + .alias = "focaltech", + .detect = focaltech_detect, + .init = focaltech_init, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_VMMOUSE + { + .type = PSMOUSE_VMMOUSE, + .name = VMMOUSE_PSNAME, + .alias = "vmmouse", + .detect = vmmouse_detect, + .init = vmmouse_init, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_BYD + { + .type = PSMOUSE_BYD, + .name = "BYDPS/2", + .alias = "byd", + .detect = byd_detect, + .init = byd_init, + }, +#endif + { + .type = PSMOUSE_AUTO, + .name = "auto", + .alias = "any", + .maxproto = true, + }, +}; + +static const struct psmouse_protocol *__psmouse_protocol_by_type(enum psmouse_type type) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(psmouse_protocols); i++) + if (psmouse_protocols[i].type == type) + return &psmouse_protocols[i]; + + return NULL; +} + +static const struct psmouse_protocol *psmouse_protocol_by_type(enum psmouse_type type) +{ + const struct psmouse_protocol *proto; + + proto = __psmouse_protocol_by_type(type); + if (proto) + return proto; + + WARN_ON(1); + return &psmouse_protocols[0]; +} + +static const struct psmouse_protocol *psmouse_protocol_by_name(const char *name, size_t len) +{ + const struct psmouse_protocol *p; + int i; + + for (i = 0; i < ARRAY_SIZE(psmouse_protocols); i++) { + p = &psmouse_protocols[i]; + + if ((strlen(p->name) == len && !strncmp(p->name, name, len)) || + (strlen(p->alias) == len && !strncmp(p->alias, name, len))) + return &psmouse_protocols[i]; + } + + return NULL; +} + +/* + * Apply default settings to the psmouse structure. Most of them will + * be overridden by individual protocol initialization routines. + */ +static void psmouse_apply_defaults(struct psmouse *psmouse) +{ + struct input_dev *input_dev = psmouse->dev; + + bitmap_zero(input_dev->evbit, EV_CNT); + bitmap_zero(input_dev->keybit, KEY_CNT); + bitmap_zero(input_dev->relbit, REL_CNT); + bitmap_zero(input_dev->absbit, ABS_CNT); + bitmap_zero(input_dev->mscbit, MSC_CNT); + + input_set_capability(input_dev, EV_KEY, BTN_LEFT); + input_set_capability(input_dev, EV_KEY, BTN_RIGHT); + + input_set_capability(input_dev, EV_REL, REL_X); + input_set_capability(input_dev, EV_REL, REL_Y); + + __set_bit(INPUT_PROP_POINTER, input_dev->propbit); + + psmouse->protocol = &psmouse_protocols[0]; + + psmouse->set_rate = psmouse_set_rate; + psmouse->set_resolution = psmouse_set_resolution; + psmouse->set_scale = psmouse_set_scale; + psmouse->poll = psmouse_poll; + psmouse->protocol_handler = psmouse_process_byte; + psmouse->pktsize = 3; + psmouse->reconnect = NULL; + psmouse->fast_reconnect = NULL; + psmouse->disconnect = NULL; + psmouse->cleanup = NULL; + psmouse->pt_activate = NULL; + psmouse->pt_deactivate = NULL; +} + +static bool psmouse_do_detect(int (*detect)(struct psmouse *, bool), + struct psmouse *psmouse, bool allow_passthrough, + bool set_properties) +{ + if (psmouse->ps2dev.serio->id.type == SERIO_PS_PSTHRU && + !allow_passthrough) { + return false; + } + + if (set_properties) + psmouse_apply_defaults(psmouse); + + return detect(psmouse, set_properties) == 0; +} + +static bool psmouse_try_protocol(struct psmouse *psmouse, + enum psmouse_type type, + unsigned int *max_proto, + bool set_properties, bool init_allowed) +{ + const struct psmouse_protocol *proto; + + proto = __psmouse_protocol_by_type(type); + if (!proto) + return false; + + if (!psmouse_do_detect(proto->detect, psmouse, proto->try_passthru, + set_properties)) + return false; + + if (set_properties && proto->init && init_allowed) { + if (proto->init(psmouse) != 0) { + /* + * We detected device, but init failed. Adjust + * max_proto so we only try standard protocols. + */ + if (*max_proto > PSMOUSE_IMEX) + *max_proto = PSMOUSE_IMEX; + + return false; + } + } + + return true; +} + +/* + * psmouse_extensions() probes for any extensions to the basic PS/2 protocol + * the mouse may have. + */ +static int psmouse_extensions(struct psmouse *psmouse, + unsigned int max_proto, bool set_properties) +{ + bool synaptics_hardware = false; + int ret; + + /* + * Always check for focaltech, this is safe as it uses pnp-id + * matching. + */ + if (psmouse_do_detect(focaltech_detect, + psmouse, false, set_properties)) { + if (max_proto > PSMOUSE_IMEX && + IS_ENABLED(CONFIG_MOUSE_PS2_FOCALTECH) && + (!set_properties || focaltech_init(psmouse) == 0)) { + return PSMOUSE_FOCALTECH; + } + /* + * Restrict psmouse_max_proto so that psmouse_initialize() + * does not try to reset rate and resolution, because even + * that upsets the device. + * This also causes us to basically fall through to basic + * protocol detection, where we fully reset the mouse, + * and set it up as bare PS/2 protocol device. + */ + psmouse_max_proto = max_proto = PSMOUSE_PS2; + } + + /* + * We always check for LifeBook because it does not disturb mouse + * (it only checks DMI information). + */ + if (psmouse_try_protocol(psmouse, PSMOUSE_LIFEBOOK, &max_proto, + set_properties, max_proto > PSMOUSE_IMEX)) + return PSMOUSE_LIFEBOOK; + + if (psmouse_try_protocol(psmouse, PSMOUSE_VMMOUSE, &max_proto, + set_properties, max_proto > PSMOUSE_IMEX)) + return PSMOUSE_VMMOUSE; + + /* + * Try Kensington ThinkingMouse (we try first, because Synaptics + * probe upsets the ThinkingMouse). + */ + if (max_proto > PSMOUSE_IMEX && + psmouse_try_protocol(psmouse, PSMOUSE_THINKPS, &max_proto, + set_properties, true)) { + return PSMOUSE_THINKPS; + } + + /* + * Try Synaptics TouchPad. Note that probing is done even if + * Synaptics protocol support is disabled in config - we need to + * know if it is Synaptics so we can reset it properly after + * probing for IntelliMouse. + */ + if (max_proto > PSMOUSE_PS2 && + psmouse_do_detect(synaptics_detect, + psmouse, false, set_properties)) { + synaptics_hardware = true; + + if (max_proto > PSMOUSE_IMEX) { + /* + * Try activating protocol, but check if support is + * enabled first, since we try detecting Synaptics + * even when protocol is disabled. + */ + if (IS_ENABLED(CONFIG_MOUSE_PS2_SYNAPTICS) || + IS_ENABLED(CONFIG_MOUSE_PS2_SYNAPTICS_SMBUS)) { + if (!set_properties) + return PSMOUSE_SYNAPTICS; + + ret = synaptics_init(psmouse); + if (ret >= 0) + return ret; + } + + /* + * Some Synaptics touchpads can emulate extended + * protocols (like IMPS/2). Unfortunately + * Logitech/Genius probes confuse some firmware + * versions so we'll have to skip them. + */ + max_proto = PSMOUSE_IMEX; + } + + /* + * Make sure that touchpad is in relative mode, gestures + * (taps) are enabled. + */ + synaptics_reset(psmouse); + } + + /* + * Try Cypress Trackpad. We must try it before Finger Sensing Pad + * because Finger Sensing Pad probe upsets some modules of Cypress + * Trackpads. + */ + if (max_proto > PSMOUSE_IMEX && + psmouse_try_protocol(psmouse, PSMOUSE_CYPRESS, &max_proto, + set_properties, true)) { + return PSMOUSE_CYPRESS; + } + + /* Try ALPS TouchPad */ + if (max_proto > PSMOUSE_IMEX) { + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS); + if (psmouse_try_protocol(psmouse, PSMOUSE_ALPS, + &max_proto, set_properties, true)) + return PSMOUSE_ALPS; + } + + /* Try OLPC HGPK touchpad */ + if (max_proto > PSMOUSE_IMEX && + psmouse_try_protocol(psmouse, PSMOUSE_HGPK, &max_proto, + set_properties, true)) { + return PSMOUSE_HGPK; + } + + /* Try Elantech touchpad */ + if (max_proto > PSMOUSE_IMEX && + psmouse_try_protocol(psmouse, PSMOUSE_ELANTECH, + &max_proto, set_properties, false)) { + if (!set_properties) + return PSMOUSE_ELANTECH; + + ret = elantech_init(psmouse); + if (ret >= 0) + return ret; + } + + if (max_proto > PSMOUSE_IMEX) { + if (psmouse_try_protocol(psmouse, PSMOUSE_GENPS, + &max_proto, set_properties, true)) + return PSMOUSE_GENPS; + + if (psmouse_try_protocol(psmouse, PSMOUSE_PS2PP, + &max_proto, set_properties, true)) + return PSMOUSE_PS2PP; + + if (psmouse_try_protocol(psmouse, PSMOUSE_TRACKPOINT, + &max_proto, set_properties, true)) + return PSMOUSE_TRACKPOINT; + + if (psmouse_try_protocol(psmouse, PSMOUSE_TOUCHKIT_PS2, + &max_proto, set_properties, true)) + return PSMOUSE_TOUCHKIT_PS2; + } + + /* + * Try Finger Sensing Pad. We do it here because its probe upsets + * Trackpoint devices (causing TP_READ_ID command to time out). + */ + if (max_proto > PSMOUSE_IMEX && + psmouse_try_protocol(psmouse, PSMOUSE_FSP, + &max_proto, set_properties, true)) { + return PSMOUSE_FSP; + } + + /* + * Reset to defaults in case the device got confused by extended + * protocol probes. Note that we follow up with full reset because + * some mice put themselves to sleep when they see PSMOUSE_RESET_DIS. + */ + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS); + psmouse_reset(psmouse); + + if (max_proto >= PSMOUSE_IMEX && + psmouse_try_protocol(psmouse, PSMOUSE_IMEX, + &max_proto, set_properties, true)) { + return PSMOUSE_IMEX; + } + + if (max_proto >= PSMOUSE_IMPS && + psmouse_try_protocol(psmouse, PSMOUSE_IMPS, + &max_proto, set_properties, true)) { + return PSMOUSE_IMPS; + } + + /* + * Okay, all failed, we have a standard mouse here. The number of + * the buttons is still a question, though. We assume 3. + */ + psmouse_try_protocol(psmouse, PSMOUSE_PS2, + &max_proto, set_properties, true); + + if (synaptics_hardware) { + /* + * We detected Synaptics hardware but it did not respond to + * IMPS/2 probes. We need to reset the touchpad because if + * there is a track point on the pass through port it could + * get disabled while probing for protocol extensions. + */ + psmouse_reset(psmouse); + } + + return PSMOUSE_PS2; +} + +/* + * psmouse_probe() probes for a PS/2 mouse. + */ +static int psmouse_probe(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + u8 param[2]; + int error; + + /* + * First, we check if it's a mouse. It should send 0x00 or 0x03 in + * case of an IntelliMouse in 4-byte mode or 0x04 for IM Explorer. + * Sunrex K8561 IR Keyboard/Mouse reports 0xff on second and + * subsequent ID queries, probably due to a firmware bug. + */ + param[0] = 0xa5; + error = ps2_command(ps2dev, param, PSMOUSE_CMD_GETID); + if (error) + return error; + + if (param[0] != 0x00 && param[0] != 0x03 && + param[0] != 0x04 && param[0] != 0xff) + return -ENODEV; + + /* + * Then we reset and disable the mouse so that it doesn't generate + * events. + */ + error = ps2_command(ps2dev, NULL, PSMOUSE_CMD_RESET_DIS); + if (error) + psmouse_warn(psmouse, "Failed to reset mouse on %s: %d\n", + ps2dev->serio->phys, error); + + return 0; +} + +/* + * psmouse_initialize() initializes the mouse to a sane state. + */ +static void psmouse_initialize(struct psmouse *psmouse) +{ + /* + * We set the mouse report rate, resolution and scaling. + */ + if (psmouse_max_proto != PSMOUSE_PS2) { + psmouse->set_rate(psmouse, psmouse->rate); + psmouse->set_resolution(psmouse, psmouse->resolution); + psmouse->set_scale(psmouse, PSMOUSE_SCALE11); + } +} + +/* + * psmouse_activate() enables the mouse so that we get motion reports from it. + */ +int psmouse_activate(struct psmouse *psmouse) +{ + if (ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_ENABLE)) { + psmouse_warn(psmouse, "Failed to enable mouse on %s\n", + psmouse->ps2dev.serio->phys); + return -1; + } + + psmouse_set_state(psmouse, PSMOUSE_ACTIVATED); + return 0; +} + +/* + * psmouse_deactivate() puts the mouse into poll mode so that we don't get + * motion reports from it unless we explicitly request it. + */ +int psmouse_deactivate(struct psmouse *psmouse) +{ + int error; + + error = ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_DISABLE); + if (error) { + psmouse_warn(psmouse, "Failed to deactivate mouse on %s: %d\n", + psmouse->ps2dev.serio->phys, error); + return error; + } + + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + return 0; +} + +/* + * psmouse_resync() attempts to re-validate current protocol. + */ +static void psmouse_resync(struct work_struct *work) +{ + struct psmouse *parent = NULL, *psmouse = + container_of(work, struct psmouse, resync_work.work); + struct serio *serio = psmouse->ps2dev.serio; + psmouse_ret_t rc = PSMOUSE_GOOD_DATA; + bool failed = false, enabled = false; + int i; + + mutex_lock(&psmouse_mutex); + + if (psmouse->state != PSMOUSE_RESYNCING) + goto out; + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = serio_get_drvdata(serio->parent); + psmouse_deactivate(parent); + } + + /* + * Some mice don't ACK commands sent while they are in the middle of + * transmitting motion packet. To avoid delay we use ps2_sendbyte() + * instead of ps2_command() which would wait for 200ms for an ACK + * that may never come. + * As an additional quirk ALPS touchpads may not only forget to ACK + * disable command but will stop reporting taps, so if we see that + * mouse at least once ACKs disable we will do full reconnect if ACK + * is missing. + */ + psmouse->num_resyncs++; + + if (ps2_sendbyte(&psmouse->ps2dev, PSMOUSE_CMD_DISABLE, 20)) { + if (psmouse->num_resyncs < 3 || psmouse->acks_disable_command) + failed = true; + } else + psmouse->acks_disable_command = true; + + /* + * Poll the mouse. If it was reset the packet will be shorter than + * psmouse->pktsize and ps2_command will fail. We do not expect and + * do not handle scenario when mouse "upgrades" its protocol while + * disconnected since it would require additional delay. If we ever + * see a mouse that does it we'll adjust the code. + */ + if (!failed) { + if (psmouse->poll(psmouse)) + failed = true; + else { + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + for (i = 0; i < psmouse->pktsize; i++) { + psmouse->pktcnt++; + rc = psmouse->protocol_handler(psmouse); + if (rc != PSMOUSE_GOOD_DATA) + break; + } + if (rc != PSMOUSE_FULL_PACKET) + failed = true; + psmouse_set_state(psmouse, PSMOUSE_RESYNCING); + } + } + + /* + * Now try to enable mouse. We try to do that even if poll failed + * and also repeat our attempts 5 times, otherwise we may be left + * out with disabled mouse. + */ + for (i = 0; i < 5; i++) { + if (!ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_ENABLE)) { + enabled = true; + break; + } + msleep(200); + } + + if (!enabled) { + psmouse_warn(psmouse, "failed to re-enable mouse on %s\n", + psmouse->ps2dev.serio->phys); + failed = true; + } + + if (failed) { + psmouse_set_state(psmouse, PSMOUSE_IGNORE); + psmouse_info(psmouse, + "resync failed, issuing reconnect request\n"); + serio_reconnect(serio); + } else + psmouse_set_state(psmouse, PSMOUSE_ACTIVATED); + + if (parent) + psmouse_activate(parent); + out: + mutex_unlock(&psmouse_mutex); +} + +/* + * psmouse_cleanup() resets the mouse into power-on state. + */ +static void psmouse_cleanup(struct serio *serio) +{ + struct psmouse *psmouse = serio_get_drvdata(serio); + struct psmouse *parent = NULL; + + mutex_lock(&psmouse_mutex); + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = serio_get_drvdata(serio->parent); + psmouse_deactivate(parent); + } + + psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); + + /* + * Disable stream mode so cleanup routine can proceed undisturbed. + */ + if (ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_DISABLE)) + psmouse_warn(psmouse, "Failed to disable mouse on %s\n", + psmouse->ps2dev.serio->phys); + + if (psmouse->cleanup) + psmouse->cleanup(psmouse); + + /* + * Reset the mouse to defaults (bare PS/2 protocol). + */ + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS); + + /* + * Some boxes, such as HP nx7400, get terribly confused if mouse + * is not fully enabled before suspending/shutting down. + */ + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_ENABLE); + + if (parent) { + if (parent->pt_deactivate) + parent->pt_deactivate(parent); + + psmouse_activate(parent); + } + + mutex_unlock(&psmouse_mutex); +} + +/* + * psmouse_disconnect() closes and frees. + */ +static void psmouse_disconnect(struct serio *serio) +{ + struct psmouse *psmouse = serio_get_drvdata(serio); + struct psmouse *parent = NULL; + + sysfs_remove_group(&serio->dev.kobj, &psmouse_attribute_group); + + mutex_lock(&psmouse_mutex); + + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + + /* make sure we don't have a resync in progress */ + mutex_unlock(&psmouse_mutex); + flush_workqueue(kpsmoused_wq); + mutex_lock(&psmouse_mutex); + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = serio_get_drvdata(serio->parent); + psmouse_deactivate(parent); + } + + if (psmouse->disconnect) + psmouse->disconnect(psmouse); + + if (parent && parent->pt_deactivate) + parent->pt_deactivate(parent); + + psmouse_set_state(psmouse, PSMOUSE_IGNORE); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + + if (psmouse->dev) + input_unregister_device(psmouse->dev); + + kfree(psmouse); + + if (parent) + psmouse_activate(parent); + + mutex_unlock(&psmouse_mutex); +} + +static int psmouse_switch_protocol(struct psmouse *psmouse, + const struct psmouse_protocol *proto) +{ + const struct psmouse_protocol *selected_proto; + struct input_dev *input_dev = psmouse->dev; + enum psmouse_type type; + + input_dev->dev.parent = &psmouse->ps2dev.serio->dev; + + if (proto && (proto->detect || proto->init)) { + psmouse_apply_defaults(psmouse); + + if (proto->detect && proto->detect(psmouse, true) < 0) + return -1; + + if (proto->init && proto->init(psmouse) < 0) + return -1; + + selected_proto = proto; + } else { + type = psmouse_extensions(psmouse, psmouse_max_proto, true); + selected_proto = psmouse_protocol_by_type(type); + } + + psmouse->protocol = selected_proto; + + /* + * If mouse's packet size is 3 there is no point in polling the + * device in hopes to detect protocol reset - we won't get less + * than 3 bytes response anyhow. + */ + if (psmouse->pktsize == 3) + psmouse->resync_time = 0; + + /* + * Some smart KVMs fake response to POLL command returning just + * 3 bytes and messing up our resync logic, so if initial poll + * fails we won't try polling the device anymore. Hopefully + * such KVM will maintain initially selected protocol. + */ + if (psmouse->resync_time && psmouse->poll(psmouse)) + psmouse->resync_time = 0; + + snprintf(psmouse->devname, sizeof(psmouse->devname), "%s %s %s", + selected_proto->name, psmouse->vendor, psmouse->name); + + input_dev->name = psmouse->devname; + input_dev->phys = psmouse->phys; + input_dev->id.bustype = BUS_I8042; + input_dev->id.vendor = 0x0002; + input_dev->id.product = psmouse->protocol->type; + input_dev->id.version = psmouse->model; + + return 0; +} + +/* + * psmouse_connect() is a callback from the serio module when + * an unhandled serio port is found. + */ +static int psmouse_connect(struct serio *serio, struct serio_driver *drv) +{ + struct psmouse *psmouse, *parent = NULL; + struct input_dev *input_dev; + int retval = 0, error = -ENOMEM; + + mutex_lock(&psmouse_mutex); + + /* + * If this is a pass-through port deactivate parent so the device + * connected to this port can be successfully identified + */ + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = serio_get_drvdata(serio->parent); + psmouse_deactivate(parent); + } + + psmouse = kzalloc(sizeof(struct psmouse), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!psmouse || !input_dev) + goto err_free; + + ps2_init(&psmouse->ps2dev, serio); + INIT_DELAYED_WORK(&psmouse->resync_work, psmouse_resync); + psmouse->dev = input_dev; + snprintf(psmouse->phys, sizeof(psmouse->phys), "%s/input0", serio->phys); + + psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); + + serio_set_drvdata(serio, psmouse); + + error = serio_open(serio, drv); + if (error) + goto err_clear_drvdata; + + /* give PT device some time to settle down before probing */ + if (serio->id.type == SERIO_PS_PSTHRU) + usleep_range(10000, 15000); + + if (psmouse_probe(psmouse) < 0) { + error = -ENODEV; + goto err_close_serio; + } + + psmouse->rate = psmouse_rate; + psmouse->resolution = psmouse_resolution; + psmouse->resetafter = psmouse_resetafter; + psmouse->resync_time = parent ? 0 : psmouse_resync_time; + psmouse->smartscroll = psmouse_smartscroll; + + psmouse_switch_protocol(psmouse, NULL); + + if (!psmouse->protocol->smbus_companion) { + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + psmouse_initialize(psmouse); + + error = input_register_device(input_dev); + if (error) + goto err_protocol_disconnect; + } else { + /* Smbus companion will be reporting events, not us. */ + input_free_device(input_dev); + psmouse->dev = input_dev = NULL; + } + + if (parent && parent->pt_activate) + parent->pt_activate(parent); + + error = sysfs_create_group(&serio->dev.kobj, &psmouse_attribute_group); + if (error) + goto err_pt_deactivate; + + /* + * PS/2 devices having SMBus companions should stay disabled + * on PS/2 side, in order to have SMBus part operable. + */ + if (!psmouse->protocol->smbus_companion) + psmouse_activate(psmouse); + + out: + /* If this is a pass-through port the parent needs to be re-activated */ + if (parent) + psmouse_activate(parent); + + mutex_unlock(&psmouse_mutex); + return retval; + + err_pt_deactivate: + if (parent && parent->pt_deactivate) + parent->pt_deactivate(parent); + if (input_dev) { + input_unregister_device(input_dev); + input_dev = NULL; /* so we don't try to free it below */ + } + err_protocol_disconnect: + if (psmouse->disconnect) + psmouse->disconnect(psmouse); + psmouse_set_state(psmouse, PSMOUSE_IGNORE); + err_close_serio: + serio_close(serio); + err_clear_drvdata: + serio_set_drvdata(serio, NULL); + err_free: + input_free_device(input_dev); + kfree(psmouse); + + retval = error; + goto out; +} + +static int __psmouse_reconnect(struct serio *serio, bool fast_reconnect) +{ + struct psmouse *psmouse = serio_get_drvdata(serio); + struct psmouse *parent = NULL; + int (*reconnect_handler)(struct psmouse *); + enum psmouse_type type; + int rc = -1; + + mutex_lock(&psmouse_mutex); + + if (fast_reconnect) { + reconnect_handler = psmouse->fast_reconnect; + if (!reconnect_handler) { + rc = -ENOENT; + goto out_unlock; + } + } else { + reconnect_handler = psmouse->reconnect; + } + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = serio_get_drvdata(serio->parent); + psmouse_deactivate(parent); + } + + psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); + + if (reconnect_handler) { + if (reconnect_handler(psmouse)) + goto out; + } else { + psmouse_reset(psmouse); + + if (psmouse_probe(psmouse) < 0) + goto out; + + type = psmouse_extensions(psmouse, psmouse_max_proto, false); + if (psmouse->protocol->type != type) + goto out; + } + + /* + * OK, the device type (and capabilities) match the old one, + * we can continue using it, complete initialization + */ + if (!psmouse->protocol->smbus_companion) { + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + psmouse_initialize(psmouse); + } + + if (parent && parent->pt_activate) + parent->pt_activate(parent); + + /* + * PS/2 devices having SMBus companions should stay disabled + * on PS/2 side, in order to have SMBus part operable. + */ + if (!psmouse->protocol->smbus_companion) + psmouse_activate(psmouse); + + rc = 0; + +out: + /* If this is a pass-through port the parent waits to be activated */ + if (parent) + psmouse_activate(parent); + +out_unlock: + mutex_unlock(&psmouse_mutex); + return rc; +} + +static int psmouse_reconnect(struct serio *serio) +{ + return __psmouse_reconnect(serio, false); +} + +static int psmouse_fast_reconnect(struct serio *serio) +{ + return __psmouse_reconnect(serio, true); +} + +static struct serio_device_id psmouse_serio_ids[] = { + { + .type = SERIO_8042, + .proto = SERIO_ANY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_PS_PSTHRU, + .proto = SERIO_ANY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, psmouse_serio_ids); + +static struct serio_driver psmouse_drv = { + .driver = { + .name = "psmouse", + }, + .description = DRIVER_DESC, + .id_table = psmouse_serio_ids, + .interrupt = psmouse_interrupt, + .connect = psmouse_connect, + .reconnect = psmouse_reconnect, + .fast_reconnect = psmouse_fast_reconnect, + .disconnect = psmouse_disconnect, + .cleanup = psmouse_cleanup, +}; + +ssize_t psmouse_attr_show_helper(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct serio *serio = to_serio_port(dev); + struct psmouse_attribute *attr = to_psmouse_attr(devattr); + struct psmouse *psmouse = serio_get_drvdata(serio); + + if (psmouse->protocol->smbus_companion && + devattr != &psmouse_attr_protocol.dattr) + return -ENOENT; + + return attr->show(psmouse, attr->data, buf); +} + +ssize_t psmouse_attr_set_helper(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct serio *serio = to_serio_port(dev); + struct psmouse_attribute *attr = to_psmouse_attr(devattr); + struct psmouse *psmouse, *parent = NULL; + int retval; + + retval = mutex_lock_interruptible(&psmouse_mutex); + if (retval) + goto out; + + psmouse = serio_get_drvdata(serio); + + if (psmouse->protocol->smbus_companion && + devattr != &psmouse_attr_protocol.dattr) { + retval = -ENOENT; + goto out_unlock; + } + + if (attr->protect) { + if (psmouse->state == PSMOUSE_IGNORE) { + retval = -ENODEV; + goto out_unlock; + } + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = serio_get_drvdata(serio->parent); + psmouse_deactivate(parent); + } + + if (!psmouse->protocol->smbus_companion) + psmouse_deactivate(psmouse); + } + + retval = attr->set(psmouse, attr->data, buf, count); + + if (attr->protect) { + if (retval != -ENODEV && !psmouse->protocol->smbus_companion) + psmouse_activate(psmouse); + + if (parent) + psmouse_activate(parent); + } + + out_unlock: + mutex_unlock(&psmouse_mutex); + out: + return retval; +} + +static ssize_t psmouse_show_int_attr(struct psmouse *psmouse, void *offset, char *buf) +{ + unsigned int *field = (unsigned int *)((char *)psmouse + (size_t)offset); + + return sprintf(buf, "%u\n", *field); +} + +static ssize_t psmouse_set_int_attr(struct psmouse *psmouse, void *offset, const char *buf, size_t count) +{ + unsigned int *field = (unsigned int *)((char *)psmouse + (size_t)offset); + unsigned int value; + int err; + + err = kstrtouint(buf, 10, &value); + if (err) + return err; + + *field = value; + + return count; +} + +static ssize_t psmouse_attr_show_protocol(struct psmouse *psmouse, void *data, char *buf) +{ + return sprintf(buf, "%s\n", psmouse->protocol->name); +} + +static ssize_t psmouse_attr_set_protocol(struct psmouse *psmouse, void *data, const char *buf, size_t count) +{ + struct serio *serio = psmouse->ps2dev.serio; + struct psmouse *parent = NULL; + struct input_dev *old_dev, *new_dev; + const struct psmouse_protocol *proto, *old_proto; + int error; + int retry = 0; + + proto = psmouse_protocol_by_name(buf, count); + if (!proto) + return -EINVAL; + + if (psmouse->protocol == proto) + return count; + + new_dev = input_allocate_device(); + if (!new_dev) + return -ENOMEM; + + while (!list_empty(&serio->children)) { + if (++retry > 3) { + psmouse_warn(psmouse, + "failed to destroy children ports, protocol change aborted.\n"); + input_free_device(new_dev); + return -EIO; + } + + mutex_unlock(&psmouse_mutex); + serio_unregister_child_port(serio); + mutex_lock(&psmouse_mutex); + + if (serio->drv != &psmouse_drv) { + input_free_device(new_dev); + return -ENODEV; + } + + if (psmouse->protocol == proto) { + input_free_device(new_dev); + return count; /* switched by other thread */ + } + } + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = serio_get_drvdata(serio->parent); + if (parent->pt_deactivate) + parent->pt_deactivate(parent); + } + + old_dev = psmouse->dev; + old_proto = psmouse->protocol; + + if (psmouse->disconnect) + psmouse->disconnect(psmouse); + + psmouse_set_state(psmouse, PSMOUSE_IGNORE); + + psmouse->dev = new_dev; + psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); + + if (psmouse_switch_protocol(psmouse, proto) < 0) { + psmouse_reset(psmouse); + /* default to PSMOUSE_PS2 */ + psmouse_switch_protocol(psmouse, &psmouse_protocols[0]); + } + + psmouse_initialize(psmouse); + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + + if (psmouse->protocol->smbus_companion) { + input_free_device(psmouse->dev); + psmouse->dev = NULL; + } else { + error = input_register_device(psmouse->dev); + if (error) { + if (psmouse->disconnect) + psmouse->disconnect(psmouse); + + psmouse_set_state(psmouse, PSMOUSE_IGNORE); + input_free_device(new_dev); + psmouse->dev = old_dev; + psmouse_set_state(psmouse, PSMOUSE_INITIALIZING); + psmouse_switch_protocol(psmouse, old_proto); + psmouse_initialize(psmouse); + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + + return error; + } + } + + if (old_dev) + input_unregister_device(old_dev); + + if (parent && parent->pt_activate) + parent->pt_activate(parent); + + return count; +} + +static ssize_t psmouse_attr_set_rate(struct psmouse *psmouse, void *data, const char *buf, size_t count) +{ + unsigned int value; + int err; + + err = kstrtouint(buf, 10, &value); + if (err) + return err; + + psmouse->set_rate(psmouse, value); + return count; +} + +static ssize_t psmouse_attr_set_resolution(struct psmouse *psmouse, void *data, const char *buf, size_t count) +{ + unsigned int value; + int err; + + err = kstrtouint(buf, 10, &value); + if (err) + return err; + + psmouse->set_resolution(psmouse, value); + return count; +} + + +static int psmouse_set_maxproto(const char *val, const struct kernel_param *kp) +{ + const struct psmouse_protocol *proto; + + if (!val) + return -EINVAL; + + proto = psmouse_protocol_by_name(val, strlen(val)); + + if (!proto || !proto->maxproto) + return -EINVAL; + + *((unsigned int *)kp->arg) = proto->type; + + return 0; +} + +static int psmouse_get_maxproto(char *buffer, const struct kernel_param *kp) +{ + int type = *((unsigned int *)kp->arg); + + return sprintf(buffer, "%s\n", psmouse_protocol_by_type(type)->name); +} + +static int __init psmouse_init(void) +{ + int err; + + lifebook_module_init(); + synaptics_module_init(); + hgpk_module_init(); + + err = psmouse_smbus_module_init(); + if (err) + return err; + + kpsmoused_wq = alloc_ordered_workqueue("kpsmoused", 0); + if (!kpsmoused_wq) { + pr_err("failed to create kpsmoused workqueue\n"); + err = -ENOMEM; + goto err_smbus_exit; + } + + err = serio_register_driver(&psmouse_drv); + if (err) + goto err_destroy_wq; + + return 0; + +err_destroy_wq: + destroy_workqueue(kpsmoused_wq); +err_smbus_exit: + psmouse_smbus_module_exit(); + return err; +} + +static void __exit psmouse_exit(void) +{ + serio_unregister_driver(&psmouse_drv); + destroy_workqueue(kpsmoused_wq); + psmouse_smbus_module_exit(); +} + +module_init(psmouse_init); +module_exit(psmouse_exit); diff --git a/drivers/input/mouse/psmouse-smbus.c b/drivers/input/mouse/psmouse-smbus.c new file mode 100644 index 000000000..852d4b486 --- /dev/null +++ b/drivers/input/mouse/psmouse-smbus.c @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2017 Red Hat, Inc + * + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/libps2.h> +#include <linux/i2c.h> +#include <linux/serio.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include "psmouse.h" + +struct psmouse_smbus_dev { + struct i2c_board_info board; + struct psmouse *psmouse; + struct i2c_client *client; + struct list_head node; + bool dead; + bool need_deactivate; +}; + +static LIST_HEAD(psmouse_smbus_list); +static DEFINE_MUTEX(psmouse_smbus_mutex); + +static void psmouse_smbus_check_adapter(struct i2c_adapter *adapter) +{ + struct psmouse_smbus_dev *smbdev; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_HOST_NOTIFY)) + return; + + mutex_lock(&psmouse_smbus_mutex); + + list_for_each_entry(smbdev, &psmouse_smbus_list, node) { + if (smbdev->dead) + continue; + + if (smbdev->client) + continue; + + /* + * Here would be a good place to check if device is actually + * present, but it seems that SMBus will not respond unless we + * fully reset PS/2 connection. So cross our fingers, and try + * to switch over, hopefully our system will not have too many + * "host notify" I2C adapters. + */ + psmouse_dbg(smbdev->psmouse, + "SMBus candidate adapter appeared, triggering rescan\n"); + serio_rescan(smbdev->psmouse->ps2dev.serio); + } + + mutex_unlock(&psmouse_smbus_mutex); +} + +static void psmouse_smbus_detach_i2c_client(struct i2c_client *client) +{ + struct psmouse_smbus_dev *smbdev, *tmp; + + mutex_lock(&psmouse_smbus_mutex); + + list_for_each_entry_safe(smbdev, tmp, &psmouse_smbus_list, node) { + if (smbdev->client != client) + continue; + + kfree(client->dev.platform_data); + client->dev.platform_data = NULL; + + if (!smbdev->dead) { + psmouse_dbg(smbdev->psmouse, + "Marking SMBus companion %s as gone\n", + dev_name(&smbdev->client->dev)); + smbdev->dead = true; + serio_rescan(smbdev->psmouse->ps2dev.serio); + } else { + list_del(&smbdev->node); + kfree(smbdev); + } + } + + mutex_unlock(&psmouse_smbus_mutex); +} + +static int psmouse_smbus_notifier_call(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct device *dev = data; + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + if (dev->type == &i2c_adapter_type) + psmouse_smbus_check_adapter(to_i2c_adapter(dev)); + break; + + case BUS_NOTIFY_REMOVED_DEVICE: + if (dev->type == &i2c_client_type) + psmouse_smbus_detach_i2c_client(to_i2c_client(dev)); + break; + } + + return 0; +} + +static struct notifier_block psmouse_smbus_notifier = { + .notifier_call = psmouse_smbus_notifier_call, +}; + +static psmouse_ret_t psmouse_smbus_process_byte(struct psmouse *psmouse) +{ + return PSMOUSE_FULL_PACKET; +} + +static int psmouse_smbus_reconnect(struct psmouse *psmouse) +{ + struct psmouse_smbus_dev *smbdev = psmouse->private; + + if (smbdev->need_deactivate) + psmouse_deactivate(psmouse); + + return 0; +} + +struct psmouse_smbus_removal_work { + struct work_struct work; + struct i2c_client *client; +}; + +static void psmouse_smbus_remove_i2c_device(struct work_struct *work) +{ + struct psmouse_smbus_removal_work *rwork = + container_of(work, struct psmouse_smbus_removal_work, work); + + dev_dbg(&rwork->client->dev, "destroying SMBus companion device\n"); + i2c_unregister_device(rwork->client); + + kfree(rwork); +} + +/* + * This schedules removal of SMBus companion device. We have to do + * it in a separate tread to avoid deadlocking on psmouse_mutex in + * case the device has a trackstick (which is also driven by psmouse). + * + * Note that this may be racing with i2c adapter removal, but we + * can't do anything about that: i2c automatically destroys clients + * attached to an adapter that is being removed. This has to be + * fixed in i2c core. + */ +static void psmouse_smbus_schedule_remove(struct i2c_client *client) +{ + struct psmouse_smbus_removal_work *rwork; + + rwork = kzalloc(sizeof(*rwork), GFP_KERNEL); + if (rwork) { + INIT_WORK(&rwork->work, psmouse_smbus_remove_i2c_device); + rwork->client = client; + + schedule_work(&rwork->work); + } +} + +static void psmouse_smbus_disconnect(struct psmouse *psmouse) +{ + struct psmouse_smbus_dev *smbdev = psmouse->private; + + mutex_lock(&psmouse_smbus_mutex); + + if (smbdev->dead) { + list_del(&smbdev->node); + kfree(smbdev); + } else { + smbdev->dead = true; + psmouse_dbg(smbdev->psmouse, + "posting removal request for SMBus companion %s\n", + dev_name(&smbdev->client->dev)); + psmouse_smbus_schedule_remove(smbdev->client); + } + + mutex_unlock(&psmouse_smbus_mutex); + + psmouse->private = NULL; +} + +static int psmouse_smbus_create_companion(struct device *dev, void *data) +{ + struct psmouse_smbus_dev *smbdev = data; + unsigned short addr_list[] = { smbdev->board.addr, I2C_CLIENT_END }; + struct i2c_adapter *adapter; + + adapter = i2c_verify_adapter(dev); + if (!adapter) + return 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_HOST_NOTIFY)) + return 0; + + smbdev->client = i2c_new_probed_device(adapter, &smbdev->board, + addr_list, NULL); + if (!smbdev->client) + return 0; + + /* We have our(?) device, stop iterating i2c bus. */ + return 1; +} + +void psmouse_smbus_cleanup(struct psmouse *psmouse) +{ + struct psmouse_smbus_dev *smbdev, *tmp; + + mutex_lock(&psmouse_smbus_mutex); + + list_for_each_entry_safe(smbdev, tmp, &psmouse_smbus_list, node) { + if (psmouse == smbdev->psmouse) { + list_del(&smbdev->node); + kfree(smbdev); + } + } + + mutex_unlock(&psmouse_smbus_mutex); +} + +int psmouse_smbus_init(struct psmouse *psmouse, + const struct i2c_board_info *board, + const void *pdata, size_t pdata_size, + bool need_deactivate, + bool leave_breadcrumbs) +{ + struct psmouse_smbus_dev *smbdev; + int error; + + smbdev = kzalloc(sizeof(*smbdev), GFP_KERNEL); + if (!smbdev) + return -ENOMEM; + + smbdev->psmouse = psmouse; + smbdev->board = *board; + smbdev->need_deactivate = need_deactivate; + + if (pdata) { + smbdev->board.platform_data = kmemdup(pdata, pdata_size, + GFP_KERNEL); + if (!smbdev->board.platform_data) { + kfree(smbdev); + return -ENOMEM; + } + } + + if (need_deactivate) + psmouse_deactivate(psmouse); + + psmouse->private = smbdev; + psmouse->protocol_handler = psmouse_smbus_process_byte; + psmouse->reconnect = psmouse_smbus_reconnect; + psmouse->fast_reconnect = psmouse_smbus_reconnect; + psmouse->disconnect = psmouse_smbus_disconnect; + psmouse->resync_time = 0; + + mutex_lock(&psmouse_smbus_mutex); + list_add_tail(&smbdev->node, &psmouse_smbus_list); + mutex_unlock(&psmouse_smbus_mutex); + + /* Bind to already existing adapters right away */ + error = i2c_for_each_dev(smbdev, psmouse_smbus_create_companion); + + if (smbdev->client) { + /* We have our companion device */ + return 0; + } + + /* + * If we did not create i2c device we will not need platform + * data even if we are leaving breadcrumbs. + */ + kfree(smbdev->board.platform_data); + smbdev->board.platform_data = NULL; + + if (error < 0 || !leave_breadcrumbs) { + mutex_lock(&psmouse_smbus_mutex); + list_del(&smbdev->node); + mutex_unlock(&psmouse_smbus_mutex); + + kfree(smbdev); + } + + return error < 0 ? error : -EAGAIN; +} + +int __init psmouse_smbus_module_init(void) +{ + int error; + + error = bus_register_notifier(&i2c_bus_type, &psmouse_smbus_notifier); + if (error) { + pr_err("failed to register i2c bus notifier: %d\n", error); + return error; + } + + return 0; +} + +void psmouse_smbus_module_exit(void) +{ + bus_unregister_notifier(&i2c_bus_type, &psmouse_smbus_notifier); + flush_scheduled_work(); +} diff --git a/drivers/input/mouse/psmouse.h b/drivers/input/mouse/psmouse.h new file mode 100644 index 000000000..64c3a5d3f --- /dev/null +++ b/drivers/input/mouse/psmouse.h @@ -0,0 +1,249 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _PSMOUSE_H +#define _PSMOUSE_H + +#define PSMOUSE_OOB_NONE 0x00 +#define PSMOUSE_OOB_EXTRA_BTNS 0x01 + +#define PSMOUSE_CMD_SETSCALE11 0x00e6 +#define PSMOUSE_CMD_SETSCALE21 0x00e7 +#define PSMOUSE_CMD_SETRES 0x10e8 +#define PSMOUSE_CMD_GETINFO 0x03e9 +#define PSMOUSE_CMD_SETSTREAM 0x00ea +#define PSMOUSE_CMD_SETPOLL 0x00f0 +#define PSMOUSE_CMD_POLL 0x00eb /* caller sets number of bytes to receive */ +#define PSMOUSE_CMD_RESET_WRAP 0x00ec +#define PSMOUSE_CMD_GETID 0x02f2 +#define PSMOUSE_CMD_SETRATE 0x10f3 +#define PSMOUSE_CMD_ENABLE 0x00f4 +#define PSMOUSE_CMD_DISABLE 0x00f5 +#define PSMOUSE_CMD_RESET_DIS 0x00f6 +#define PSMOUSE_CMD_RESET_BAT 0x02ff + +#define PSMOUSE_RET_BAT 0xaa +#define PSMOUSE_RET_ID 0x00 +#define PSMOUSE_RET_ACK 0xfa +#define PSMOUSE_RET_NAK 0xfe + +enum psmouse_state { + PSMOUSE_IGNORE, + PSMOUSE_INITIALIZING, + PSMOUSE_RESYNCING, + PSMOUSE_CMD_MODE, + PSMOUSE_ACTIVATED, +}; + +/* psmouse protocol handler return codes */ +typedef enum { + PSMOUSE_BAD_DATA, + PSMOUSE_GOOD_DATA, + PSMOUSE_FULL_PACKET +} psmouse_ret_t; + +enum psmouse_scale { + PSMOUSE_SCALE11, + PSMOUSE_SCALE21 +}; + +enum psmouse_type { + PSMOUSE_NONE, + PSMOUSE_PS2, + PSMOUSE_PS2PP, + PSMOUSE_THINKPS, + PSMOUSE_GENPS, + PSMOUSE_IMPS, + PSMOUSE_IMEX, + PSMOUSE_SYNAPTICS, + PSMOUSE_ALPS, + PSMOUSE_LIFEBOOK, + PSMOUSE_TRACKPOINT, + PSMOUSE_TOUCHKIT_PS2, + PSMOUSE_CORTRON, + PSMOUSE_HGPK, + PSMOUSE_ELANTECH, + PSMOUSE_FSP, + PSMOUSE_SYNAPTICS_RELATIVE, + PSMOUSE_CYPRESS, + PSMOUSE_FOCALTECH, + PSMOUSE_VMMOUSE, + PSMOUSE_BYD, + PSMOUSE_SYNAPTICS_SMBUS, + PSMOUSE_ELANTECH_SMBUS, + PSMOUSE_AUTO /* This one should always be last */ +}; + +struct psmouse; + +struct psmouse_protocol { + enum psmouse_type type; + bool maxproto; + bool ignore_parity; /* Protocol should ignore parity errors from KBC */ + bool try_passthru; /* Try protocol also on passthrough ports */ + bool smbus_companion; /* "Protocol" is a stub, device is on SMBus */ + const char *name; + const char *alias; + int (*detect)(struct psmouse *, bool); + int (*init)(struct psmouse *); +}; + +struct psmouse { + void *private; + struct input_dev *dev; + struct ps2dev ps2dev; + struct delayed_work resync_work; + const char *vendor; + const char *name; + const struct psmouse_protocol *protocol; + unsigned char packet[8]; + unsigned char badbyte; + unsigned char pktcnt; + unsigned char pktsize; + unsigned char oob_data_type; + unsigned char extra_buttons; + bool acks_disable_command; + unsigned int model; + unsigned long last; + unsigned long out_of_sync_cnt; + unsigned long num_resyncs; + enum psmouse_state state; + char devname[64]; + char phys[32]; + + unsigned int rate; + unsigned int resolution; + unsigned int resetafter; + unsigned int resync_time; + bool smartscroll; /* Logitech only */ + + psmouse_ret_t (*protocol_handler)(struct psmouse *psmouse); + void (*set_rate)(struct psmouse *psmouse, unsigned int rate); + void (*set_resolution)(struct psmouse *psmouse, unsigned int resolution); + void (*set_scale)(struct psmouse *psmouse, enum psmouse_scale scale); + + int (*reconnect)(struct psmouse *psmouse); + int (*fast_reconnect)(struct psmouse *psmouse); + void (*disconnect)(struct psmouse *psmouse); + void (*cleanup)(struct psmouse *psmouse); + int (*poll)(struct psmouse *psmouse); + + void (*pt_activate)(struct psmouse *psmouse); + void (*pt_deactivate)(struct psmouse *psmouse); +}; + +void psmouse_queue_work(struct psmouse *psmouse, struct delayed_work *work, + unsigned long delay); +int psmouse_reset(struct psmouse *psmouse); +void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state); +void psmouse_set_resolution(struct psmouse *psmouse, unsigned int resolution); +psmouse_ret_t psmouse_process_byte(struct psmouse *psmouse); +int psmouse_activate(struct psmouse *psmouse); +int psmouse_deactivate(struct psmouse *psmouse); +bool psmouse_matches_pnp_id(struct psmouse *psmouse, const char * const ids[]); + +void psmouse_report_standard_buttons(struct input_dev *, u8 buttons); +void psmouse_report_standard_motion(struct input_dev *, u8 *packet); +void psmouse_report_standard_packet(struct input_dev *, u8 *packet); + +struct psmouse_attribute { + struct device_attribute dattr; + void *data; + ssize_t (*show)(struct psmouse *psmouse, void *data, char *buf); + ssize_t (*set)(struct psmouse *psmouse, void *data, + const char *buf, size_t count); + bool protect; +}; +#define to_psmouse_attr(a) container_of((a), struct psmouse_attribute, dattr) + +ssize_t psmouse_attr_show_helper(struct device *dev, struct device_attribute *attr, + char *buf); +ssize_t psmouse_attr_set_helper(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); + +#define __PSMOUSE_DEFINE_ATTR_VAR(_name, _mode, _data, _show, _set, _protect) \ +static struct psmouse_attribute psmouse_attr_##_name = { \ + .dattr = { \ + .attr = { \ + .name = __stringify(_name), \ + .mode = _mode, \ + }, \ + .show = psmouse_attr_show_helper, \ + .store = psmouse_attr_set_helper, \ + }, \ + .data = _data, \ + .show = _show, \ + .set = _set, \ + .protect = _protect, \ +} + +#define __PSMOUSE_DEFINE_ATTR(_name, _mode, _data, _show, _set, _protect) \ + static ssize_t _show(struct psmouse *, void *, char *); \ + static ssize_t _set(struct psmouse *, void *, const char *, size_t); \ + __PSMOUSE_DEFINE_ATTR_VAR(_name, _mode, _data, _show, _set, _protect) + +#define PSMOUSE_DEFINE_ATTR(_name, _mode, _data, _show, _set) \ + __PSMOUSE_DEFINE_ATTR(_name, _mode, _data, _show, _set, true) + +#define PSMOUSE_DEFINE_RO_ATTR(_name, _mode, _data, _show) \ + static ssize_t _show(struct psmouse *, void *, char *); \ + __PSMOUSE_DEFINE_ATTR_VAR(_name, _mode, _data, _show, NULL, true) + +#define PSMOUSE_DEFINE_WO_ATTR(_name, _mode, _data, _set) \ + static ssize_t _set(struct psmouse *, void *, const char *, size_t); \ + __PSMOUSE_DEFINE_ATTR_VAR(_name, _mode, _data, NULL, _set, true) + +#ifndef psmouse_fmt +#define psmouse_fmt(fmt) KBUILD_BASENAME ": " fmt +#endif + +#define psmouse_dbg(psmouse, format, ...) \ + dev_dbg(&(psmouse)->ps2dev.serio->dev, \ + psmouse_fmt(format), ##__VA_ARGS__) +#define psmouse_info(psmouse, format, ...) \ + dev_info(&(psmouse)->ps2dev.serio->dev, \ + psmouse_fmt(format), ##__VA_ARGS__) +#define psmouse_warn(psmouse, format, ...) \ + dev_warn(&(psmouse)->ps2dev.serio->dev, \ + psmouse_fmt(format), ##__VA_ARGS__) +#define psmouse_err(psmouse, format, ...) \ + dev_err(&(psmouse)->ps2dev.serio->dev, \ + psmouse_fmt(format), ##__VA_ARGS__) +#define psmouse_notice(psmouse, format, ...) \ + dev_notice(&(psmouse)->ps2dev.serio->dev, \ + psmouse_fmt(format), ##__VA_ARGS__) +#define psmouse_printk(level, psmouse, format, ...) \ + dev_printk(level, \ + &(psmouse)->ps2dev.serio->dev, \ + psmouse_fmt(format), ##__VA_ARGS__) + +#ifdef CONFIG_MOUSE_PS2_SMBUS + +int psmouse_smbus_module_init(void); +void psmouse_smbus_module_exit(void); + +struct i2c_board_info; + +int psmouse_smbus_init(struct psmouse *psmouse, + const struct i2c_board_info *board, + const void *pdata, size_t pdata_size, + bool need_deactivate, + bool leave_breadcrumbs); +void psmouse_smbus_cleanup(struct psmouse *psmouse); + +#else /* !CONFIG_MOUSE_PS2_SMBUS */ + +static inline int psmouse_smbus_module_init(void) +{ + return 0; +} + +static inline void psmouse_smbus_module_exit(void) +{ +} + +static inline void psmouse_smbus_cleanup(struct psmouse *psmouse) +{ +} + +#endif /* CONFIG_MOUSE_PS2_SMBUS */ + +#endif /* _PSMOUSE_H */ diff --git a/drivers/input/mouse/pxa930_trkball.c b/drivers/input/mouse/pxa930_trkball.c new file mode 100644 index 000000000..9b4d9a59e --- /dev/null +++ b/drivers/input/mouse/pxa930_trkball.c @@ -0,0 +1,256 @@ +/* + * PXA930 track ball mouse driver + * + * Copyright (C) 2007 Marvell International Ltd. + * 2008-02-28: Yong Yao <yaoyong@marvell.com> + * initial version + * + * 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. + */ + +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/slab.h> + +#include <mach/hardware.h> +#include <linux/platform_data/mouse-pxa930_trkball.h> + +/* Trackball Controller Register Definitions */ +#define TBCR (0x000C) +#define TBCNTR (0x0010) +#define TBSBC (0x0014) + +#define TBCR_TBRST (1 << 1) +#define TBCR_TBSB (1 << 10) + +#define TBCR_Y_FLT(n) (((n) & 0xf) << 6) +#define TBCR_X_FLT(n) (((n) & 0xf) << 2) + +#define TBCNTR_YM(n) (((n) >> 24) & 0xff) +#define TBCNTR_YP(n) (((n) >> 16) & 0xff) +#define TBCNTR_XM(n) (((n) >> 8) & 0xff) +#define TBCNTR_XP(n) ((n) & 0xff) + +#define TBSBC_TBSBC (0x1) + +struct pxa930_trkball { + struct pxa930_trkball_platform_data *pdata; + + /* Memory Mapped Register */ + struct resource *mem; + void __iomem *mmio_base; + + struct input_dev *input; +}; + +static irqreturn_t pxa930_trkball_interrupt(int irq, void *dev_id) +{ + struct pxa930_trkball *trkball = dev_id; + struct input_dev *input = trkball->input; + int tbcntr, x, y; + + /* According to the spec software must read TBCNTR twice: + * if the read value is the same, the reading is valid + */ + tbcntr = __raw_readl(trkball->mmio_base + TBCNTR); + + if (tbcntr == __raw_readl(trkball->mmio_base + TBCNTR)) { + x = (TBCNTR_XP(tbcntr) - TBCNTR_XM(tbcntr)) / 2; + y = (TBCNTR_YP(tbcntr) - TBCNTR_YM(tbcntr)) / 2; + + input_report_rel(input, REL_X, x); + input_report_rel(input, REL_Y, y); + input_sync(input); + } + + __raw_writel(TBSBC_TBSBC, trkball->mmio_base + TBSBC); + __raw_writel(0, trkball->mmio_base + TBSBC); + + return IRQ_HANDLED; +} + +/* For TBCR, we need to wait for a while to make sure it has been modified. */ +static int write_tbcr(struct pxa930_trkball *trkball, int v) +{ + int i = 100; + + __raw_writel(v, trkball->mmio_base + TBCR); + + while (--i) { + if (__raw_readl(trkball->mmio_base + TBCR) == v) + break; + msleep(1); + } + + if (i == 0) { + pr_err("%s: timed out writing TBCR(%x)!\n", __func__, v); + return -ETIMEDOUT; + } + + return 0; +} + +static void pxa930_trkball_config(struct pxa930_trkball *trkball) +{ + uint32_t tbcr; + + /* According to spec, need to write the filters of x,y to 0xf first! */ + tbcr = __raw_readl(trkball->mmio_base + TBCR); + write_tbcr(trkball, tbcr | TBCR_X_FLT(0xf) | TBCR_Y_FLT(0xf)); + write_tbcr(trkball, TBCR_X_FLT(trkball->pdata->x_filter) | + TBCR_Y_FLT(trkball->pdata->y_filter)); + + /* According to spec, set TBCR_TBRST first, before clearing it! */ + tbcr = __raw_readl(trkball->mmio_base + TBCR); + write_tbcr(trkball, tbcr | TBCR_TBRST); + write_tbcr(trkball, tbcr & ~TBCR_TBRST); + + __raw_writel(TBSBC_TBSBC, trkball->mmio_base + TBSBC); + __raw_writel(0, trkball->mmio_base + TBSBC); + + pr_debug("%s: final TBCR=%x!\n", __func__, + __raw_readl(trkball->mmio_base + TBCR)); +} + +static int pxa930_trkball_open(struct input_dev *dev) +{ + struct pxa930_trkball *trkball = input_get_drvdata(dev); + + pxa930_trkball_config(trkball); + + return 0; +} + +static void pxa930_trkball_disable(struct pxa930_trkball *trkball) +{ + uint32_t tbcr = __raw_readl(trkball->mmio_base + TBCR); + + /* Held in reset, gate the 32-KHz input clock off */ + write_tbcr(trkball, tbcr | TBCR_TBRST); +} + +static void pxa930_trkball_close(struct input_dev *dev) +{ + struct pxa930_trkball *trkball = input_get_drvdata(dev); + + pxa930_trkball_disable(trkball); +} + +static int pxa930_trkball_probe(struct platform_device *pdev) +{ + struct pxa930_trkball *trkball; + struct input_dev *input; + struct resource *res; + int irq, error; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "failed to get trkball irq\n"); + return -ENXIO; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get register memory\n"); + return -ENXIO; + } + + trkball = kzalloc(sizeof(struct pxa930_trkball), GFP_KERNEL); + if (!trkball) + return -ENOMEM; + + trkball->pdata = dev_get_platdata(&pdev->dev); + if (!trkball->pdata) { + dev_err(&pdev->dev, "no platform data defined\n"); + error = -EINVAL; + goto failed; + } + + trkball->mmio_base = ioremap_nocache(res->start, resource_size(res)); + if (!trkball->mmio_base) { + dev_err(&pdev->dev, "failed to ioremap registers\n"); + error = -ENXIO; + goto failed; + } + + /* held the module in reset, will be enabled in open() */ + pxa930_trkball_disable(trkball); + + error = request_irq(irq, pxa930_trkball_interrupt, 0, + pdev->name, trkball); + if (error) { + dev_err(&pdev->dev, "failed to request irq: %d\n", error); + goto failed_free_io; + } + + platform_set_drvdata(pdev, trkball); + + input = input_allocate_device(); + if (!input) { + dev_err(&pdev->dev, "failed to allocate input device\n"); + error = -ENOMEM; + goto failed_free_irq; + } + + input->name = pdev->name; + input->id.bustype = BUS_HOST; + input->open = pxa930_trkball_open; + input->close = pxa930_trkball_close; + input->dev.parent = &pdev->dev; + input_set_drvdata(input, trkball); + + trkball->input = input; + + input_set_capability(input, EV_REL, REL_X); + input_set_capability(input, EV_REL, REL_Y); + + error = input_register_device(input); + if (error) { + dev_err(&pdev->dev, "unable to register input device\n"); + goto failed_free_input; + } + + return 0; + +failed_free_input: + input_free_device(input); +failed_free_irq: + free_irq(irq, trkball); +failed_free_io: + iounmap(trkball->mmio_base); +failed: + kfree(trkball); + return error; +} + +static int pxa930_trkball_remove(struct platform_device *pdev) +{ + struct pxa930_trkball *trkball = platform_get_drvdata(pdev); + int irq = platform_get_irq(pdev, 0); + + input_unregister_device(trkball->input); + free_irq(irq, trkball); + iounmap(trkball->mmio_base); + kfree(trkball); + + return 0; +} + +static struct platform_driver pxa930_trkball_driver = { + .driver = { + .name = "pxa930-trkball", + }, + .probe = pxa930_trkball_probe, + .remove = pxa930_trkball_remove, +}; +module_platform_driver(pxa930_trkball_driver); + +MODULE_AUTHOR("Yong Yao <yaoyong@marvell.com>"); +MODULE_DESCRIPTION("PXA930 Trackball Mouse Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/mouse/rpcmouse.c b/drivers/input/mouse/rpcmouse.c new file mode 100644 index 000000000..21c60fea5 --- /dev/null +++ b/drivers/input/mouse/rpcmouse.c @@ -0,0 +1,116 @@ +/* + * Acorn RiscPC mouse driver for Linux/ARM + * + * Copyright (c) 2000-2002 Vojtech Pavlik + * Copyright (C) 1996-2002 Russell King + * + */ + +/* + * 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. + * + * This handles the Acorn RiscPCs mouse. We basically have a couple of + * hardware registers that track the sensor count for the X-Y movement and + * another register holding the button state. On every VSYNC interrupt we read + * the complete state and then work out if something has changed. + */ + +#include <linux/module.h> +#include <linux/ptrace.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/io.h> + +#include <mach/hardware.h> +#include <asm/irq.h> +#include <asm/hardware/iomd.h> + +MODULE_AUTHOR("Vojtech Pavlik, Russell King"); +MODULE_DESCRIPTION("Acorn RiscPC mouse driver"); +MODULE_LICENSE("GPL"); + +static short rpcmouse_lastx, rpcmouse_lasty; +static struct input_dev *rpcmouse_dev; + +static irqreturn_t rpcmouse_irq(int irq, void *dev_id) +{ + struct input_dev *dev = dev_id; + short x, y, dx, dy, b; + + x = (short) iomd_readl(IOMD_MOUSEX); + y = (short) iomd_readl(IOMD_MOUSEY); + b = (short) (__raw_readl(IOMEM(0xe0310000)) ^ 0x70); + + dx = x - rpcmouse_lastx; + dy = y - rpcmouse_lasty; + + rpcmouse_lastx = x; + rpcmouse_lasty = y; + + input_report_rel(dev, REL_X, dx); + input_report_rel(dev, REL_Y, -dy); + + input_report_key(dev, BTN_LEFT, b & 0x40); + input_report_key(dev, BTN_MIDDLE, b & 0x20); + input_report_key(dev, BTN_RIGHT, b & 0x10); + + input_sync(dev); + + return IRQ_HANDLED; +} + + +static int __init rpcmouse_init(void) +{ + int err; + + rpcmouse_dev = input_allocate_device(); + if (!rpcmouse_dev) + return -ENOMEM; + + rpcmouse_dev->name = "Acorn RiscPC Mouse"; + rpcmouse_dev->phys = "rpcmouse/input0"; + rpcmouse_dev->id.bustype = BUS_HOST; + rpcmouse_dev->id.vendor = 0x0005; + rpcmouse_dev->id.product = 0x0001; + rpcmouse_dev->id.version = 0x0100; + + rpcmouse_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + rpcmouse_dev->keybit[BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT); + rpcmouse_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + + rpcmouse_lastx = (short) iomd_readl(IOMD_MOUSEX); + rpcmouse_lasty = (short) iomd_readl(IOMD_MOUSEY); + + if (request_irq(IRQ_VSYNCPULSE, rpcmouse_irq, IRQF_SHARED, "rpcmouse", rpcmouse_dev)) { + printk(KERN_ERR "rpcmouse: unable to allocate VSYNC interrupt\n"); + err = -EBUSY; + goto err_free_dev; + } + + err = input_register_device(rpcmouse_dev); + if (err) + goto err_free_irq; + + return 0; + + err_free_irq: + free_irq(IRQ_VSYNCPULSE, rpcmouse_dev); + err_free_dev: + input_free_device(rpcmouse_dev); + + return err; +} + +static void __exit rpcmouse_exit(void) +{ + free_irq(IRQ_VSYNCPULSE, rpcmouse_dev); + input_unregister_device(rpcmouse_dev); +} + +module_init(rpcmouse_init); +module_exit(rpcmouse_exit); diff --git a/drivers/input/mouse/sentelic.c b/drivers/input/mouse/sentelic.c new file mode 100644 index 000000000..022a8cb58 --- /dev/null +++ b/drivers/input/mouse/sentelic.c @@ -0,0 +1,1080 @@ +/*- + * Finger Sensing Pad PS/2 mouse driver. + * + * Copyright (C) 2005-2007 Asia Vital Components Co., Ltd. + * Copyright (C) 2005-2012 Tai-hwa Liang, Sentelic Corporation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/ctype.h> +#include <linux/libps2.h> +#include <linux/serio.h> +#include <linux/jiffies.h> +#include <linux/slab.h> + +#include "psmouse.h" +#include "sentelic.h" + +/* + * Timeout for FSP PS/2 command only (in milliseconds). + */ +#define FSP_CMD_TIMEOUT 200 +#define FSP_CMD_TIMEOUT2 30 + +#define GET_ABS_X(packet) ((packet[1] << 2) | ((packet[3] >> 2) & 0x03)) +#define GET_ABS_Y(packet) ((packet[2] << 2) | (packet[3] & 0x03)) + +/** Driver version. */ +static const char fsp_drv_ver[] = "1.1.0-K"; + +/* + * Make sure that the value being sent to FSP will not conflict with + * possible sample rate values. + */ +static unsigned char fsp_test_swap_cmd(unsigned char reg_val) +{ + switch (reg_val) { + case 10: case 20: case 40: case 60: case 80: case 100: case 200: + /* + * The requested value being sent to FSP matched to possible + * sample rates, swap the given value such that the hardware + * wouldn't get confused. + */ + return (reg_val >> 4) | (reg_val << 4); + default: + return reg_val; /* swap isn't necessary */ + } +} + +/* + * Make sure that the value being sent to FSP will not conflict with certain + * commands. + */ +static unsigned char fsp_test_invert_cmd(unsigned char reg_val) +{ + switch (reg_val) { + case 0xe9: case 0xee: case 0xf2: case 0xff: + /* + * The requested value being sent to FSP matched to certain + * commands, inverse the given value such that the hardware + * wouldn't get confused. + */ + return ~reg_val; + default: + return reg_val; /* inversion isn't necessary */ + } +} + +static int fsp_reg_read(struct psmouse *psmouse, int reg_addr, int *reg_val) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[3]; + unsigned char addr; + int rc = -1; + + /* + * We need to shut off the device and switch it into command + * mode so we don't confuse our protocol handler. We don't need + * to do that for writes because sysfs set helper does this for + * us. + */ + psmouse_deactivate(psmouse); + + ps2_begin_command(ps2dev); + + if (ps2_sendbyte(ps2dev, 0xf3, FSP_CMD_TIMEOUT) < 0) + goto out; + + /* should return 0xfe(request for resending) */ + ps2_sendbyte(ps2dev, 0x66, FSP_CMD_TIMEOUT2); + /* should return 0xfc(failed) */ + ps2_sendbyte(ps2dev, 0x88, FSP_CMD_TIMEOUT2); + + if (ps2_sendbyte(ps2dev, 0xf3, FSP_CMD_TIMEOUT) < 0) + goto out; + + if ((addr = fsp_test_invert_cmd(reg_addr)) != reg_addr) { + ps2_sendbyte(ps2dev, 0x68, FSP_CMD_TIMEOUT2); + } else if ((addr = fsp_test_swap_cmd(reg_addr)) != reg_addr) { + /* swapping is required */ + ps2_sendbyte(ps2dev, 0xcc, FSP_CMD_TIMEOUT2); + /* expect 0xfe */ + } else { + /* swapping isn't necessary */ + ps2_sendbyte(ps2dev, 0x66, FSP_CMD_TIMEOUT2); + /* expect 0xfe */ + } + /* should return 0xfc(failed) */ + ps2_sendbyte(ps2dev, addr, FSP_CMD_TIMEOUT); + + if (__ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO) < 0) + goto out; + + *reg_val = param[2]; + rc = 0; + + out: + ps2_end_command(ps2dev); + psmouse_activate(psmouse); + psmouse_dbg(psmouse, + "READ REG: 0x%02x is 0x%02x (rc = %d)\n", + reg_addr, *reg_val, rc); + return rc; +} + +static int fsp_reg_write(struct psmouse *psmouse, int reg_addr, int reg_val) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char v; + int rc = -1; + + ps2_begin_command(ps2dev); + + if (ps2_sendbyte(ps2dev, 0xf3, FSP_CMD_TIMEOUT) < 0) + goto out; + + if ((v = fsp_test_invert_cmd(reg_addr)) != reg_addr) { + /* inversion is required */ + ps2_sendbyte(ps2dev, 0x74, FSP_CMD_TIMEOUT2); + } else { + if ((v = fsp_test_swap_cmd(reg_addr)) != reg_addr) { + /* swapping is required */ + ps2_sendbyte(ps2dev, 0x77, FSP_CMD_TIMEOUT2); + } else { + /* swapping isn't necessary */ + ps2_sendbyte(ps2dev, 0x55, FSP_CMD_TIMEOUT2); + } + } + /* write the register address in correct order */ + ps2_sendbyte(ps2dev, v, FSP_CMD_TIMEOUT2); + + if (ps2_sendbyte(ps2dev, 0xf3, FSP_CMD_TIMEOUT) < 0) + goto out; + + if ((v = fsp_test_invert_cmd(reg_val)) != reg_val) { + /* inversion is required */ + ps2_sendbyte(ps2dev, 0x47, FSP_CMD_TIMEOUT2); + } else if ((v = fsp_test_swap_cmd(reg_val)) != reg_val) { + /* swapping is required */ + ps2_sendbyte(ps2dev, 0x44, FSP_CMD_TIMEOUT2); + } else { + /* swapping isn't necessary */ + ps2_sendbyte(ps2dev, 0x33, FSP_CMD_TIMEOUT2); + } + + /* write the register value in correct order */ + ps2_sendbyte(ps2dev, v, FSP_CMD_TIMEOUT2); + rc = 0; + + out: + ps2_end_command(ps2dev); + psmouse_dbg(psmouse, + "WRITE REG: 0x%02x to 0x%02x (rc = %d)\n", + reg_addr, reg_val, rc); + return rc; +} + +/* Enable register clock gating for writing certain registers */ +static int fsp_reg_write_enable(struct psmouse *psmouse, bool enable) +{ + int v, nv; + + if (fsp_reg_read(psmouse, FSP_REG_SYSCTL1, &v) == -1) + return -1; + + if (enable) + nv = v | FSP_BIT_EN_REG_CLK; + else + nv = v & ~FSP_BIT_EN_REG_CLK; + + /* only write if necessary */ + if (nv != v) + if (fsp_reg_write(psmouse, FSP_REG_SYSCTL1, nv) == -1) + return -1; + + return 0; +} + +static int fsp_page_reg_read(struct psmouse *psmouse, int *reg_val) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[3]; + int rc = -1; + + psmouse_deactivate(psmouse); + + ps2_begin_command(ps2dev); + + if (ps2_sendbyte(ps2dev, 0xf3, FSP_CMD_TIMEOUT) < 0) + goto out; + + ps2_sendbyte(ps2dev, 0x66, FSP_CMD_TIMEOUT2); + ps2_sendbyte(ps2dev, 0x88, FSP_CMD_TIMEOUT2); + + if (ps2_sendbyte(ps2dev, 0xf3, FSP_CMD_TIMEOUT) < 0) + goto out; + + ps2_sendbyte(ps2dev, 0x83, FSP_CMD_TIMEOUT2); + ps2_sendbyte(ps2dev, 0x88, FSP_CMD_TIMEOUT2); + + /* get the returned result */ + if (__ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) + goto out; + + *reg_val = param[2]; + rc = 0; + + out: + ps2_end_command(ps2dev); + psmouse_activate(psmouse); + psmouse_dbg(psmouse, + "READ PAGE REG: 0x%02x (rc = %d)\n", + *reg_val, rc); + return rc; +} + +static int fsp_page_reg_write(struct psmouse *psmouse, int reg_val) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char v; + int rc = -1; + + ps2_begin_command(ps2dev); + + if (ps2_sendbyte(ps2dev, 0xf3, FSP_CMD_TIMEOUT) < 0) + goto out; + + ps2_sendbyte(ps2dev, 0x38, FSP_CMD_TIMEOUT2); + ps2_sendbyte(ps2dev, 0x88, FSP_CMD_TIMEOUT2); + + if (ps2_sendbyte(ps2dev, 0xf3, FSP_CMD_TIMEOUT) < 0) + goto out; + + if ((v = fsp_test_invert_cmd(reg_val)) != reg_val) { + ps2_sendbyte(ps2dev, 0x47, FSP_CMD_TIMEOUT2); + } else if ((v = fsp_test_swap_cmd(reg_val)) != reg_val) { + /* swapping is required */ + ps2_sendbyte(ps2dev, 0x44, FSP_CMD_TIMEOUT2); + } else { + /* swapping isn't necessary */ + ps2_sendbyte(ps2dev, 0x33, FSP_CMD_TIMEOUT2); + } + + ps2_sendbyte(ps2dev, v, FSP_CMD_TIMEOUT2); + rc = 0; + + out: + ps2_end_command(ps2dev); + psmouse_dbg(psmouse, + "WRITE PAGE REG: to 0x%02x (rc = %d)\n", + reg_val, rc); + return rc; +} + +static int fsp_get_version(struct psmouse *psmouse, int *version) +{ + if (fsp_reg_read(psmouse, FSP_REG_VERSION, version)) + return -EIO; + + return 0; +} + +static int fsp_get_revision(struct psmouse *psmouse, int *rev) +{ + if (fsp_reg_read(psmouse, FSP_REG_REVISION, rev)) + return -EIO; + + return 0; +} + +static int fsp_get_sn(struct psmouse *psmouse, int *sn) +{ + int v0, v1, v2; + int rc = -EIO; + + /* production number since Cx is available at: 0x0b40 ~ 0x0b42 */ + if (fsp_page_reg_write(psmouse, FSP_PAGE_0B)) + goto out; + if (fsp_reg_read(psmouse, FSP_REG_SN0, &v0)) + goto out; + if (fsp_reg_read(psmouse, FSP_REG_SN1, &v1)) + goto out; + if (fsp_reg_read(psmouse, FSP_REG_SN2, &v2)) + goto out; + *sn = (v0 << 16) | (v1 << 8) | v2; + rc = 0; +out: + fsp_page_reg_write(psmouse, FSP_PAGE_DEFAULT); + return rc; +} + +static int fsp_get_buttons(struct psmouse *psmouse, int *btn) +{ + static const int buttons[] = { + 0x16, /* Left/Middle/Right/Forward/Backward & Scroll Up/Down */ + 0x06, /* Left/Middle/Right & Scroll Up/Down/Right/Left */ + 0x04, /* Left/Middle/Right & Scroll Up/Down */ + 0x02, /* Left/Middle/Right */ + }; + int val; + + if (fsp_reg_read(psmouse, FSP_REG_TMOD_STATUS, &val) == -1) + return -EIO; + + *btn = buttons[(val & 0x30) >> 4]; + return 0; +} + +/* Enable on-pad command tag output */ +static int fsp_opc_tag_enable(struct psmouse *psmouse, bool enable) +{ + int v, nv; + int res = 0; + + if (fsp_reg_read(psmouse, FSP_REG_OPC_QDOWN, &v) == -1) { + psmouse_err(psmouse, "Unable get OPC state.\n"); + return -EIO; + } + + if (enable) + nv = v | FSP_BIT_EN_OPC_TAG; + else + nv = v & ~FSP_BIT_EN_OPC_TAG; + + /* only write if necessary */ + if (nv != v) { + fsp_reg_write_enable(psmouse, true); + res = fsp_reg_write(psmouse, FSP_REG_OPC_QDOWN, nv); + fsp_reg_write_enable(psmouse, false); + } + + if (res != 0) { + psmouse_err(psmouse, "Unable to enable OPC tag.\n"); + res = -EIO; + } + + return res; +} + +static int fsp_onpad_vscr(struct psmouse *psmouse, bool enable) +{ + struct fsp_data *pad = psmouse->private; + int val; + + if (fsp_reg_read(psmouse, FSP_REG_ONPAD_CTL, &val)) + return -EIO; + + pad->vscroll = enable; + + if (enable) + val |= (FSP_BIT_FIX_VSCR | FSP_BIT_ONPAD_ENABLE); + else + val &= ~FSP_BIT_FIX_VSCR; + + if (fsp_reg_write(psmouse, FSP_REG_ONPAD_CTL, val)) + return -EIO; + + return 0; +} + +static int fsp_onpad_hscr(struct psmouse *psmouse, bool enable) +{ + struct fsp_data *pad = psmouse->private; + int val, v2; + + if (fsp_reg_read(psmouse, FSP_REG_ONPAD_CTL, &val)) + return -EIO; + + if (fsp_reg_read(psmouse, FSP_REG_SYSCTL5, &v2)) + return -EIO; + + pad->hscroll = enable; + + if (enable) { + val |= (FSP_BIT_FIX_HSCR | FSP_BIT_ONPAD_ENABLE); + v2 |= FSP_BIT_EN_MSID6; + } else { + val &= ~FSP_BIT_FIX_HSCR; + v2 &= ~(FSP_BIT_EN_MSID6 | FSP_BIT_EN_MSID7 | FSP_BIT_EN_MSID8); + } + + if (fsp_reg_write(psmouse, FSP_REG_ONPAD_CTL, val)) + return -EIO; + + /* reconfigure horizontal scrolling packet output */ + if (fsp_reg_write(psmouse, FSP_REG_SYSCTL5, v2)) + return -EIO; + + return 0; +} + +/* + * Write device specific initial parameters. + * + * ex: 0xab 0xcd - write oxcd into register 0xab + */ +static ssize_t fsp_attr_set_setreg(struct psmouse *psmouse, void *data, + const char *buf, size_t count) +{ + unsigned int reg, val; + char *rest; + ssize_t retval; + + reg = simple_strtoul(buf, &rest, 16); + if (rest == buf || *rest != ' ' || reg > 0xff) + return -EINVAL; + + retval = kstrtouint(rest + 1, 16, &val); + if (retval) + return retval; + + if (val > 0xff) + return -EINVAL; + + if (fsp_reg_write_enable(psmouse, true)) + return -EIO; + + retval = fsp_reg_write(psmouse, reg, val) < 0 ? -EIO : count; + + fsp_reg_write_enable(psmouse, false); + + return retval; +} + +PSMOUSE_DEFINE_WO_ATTR(setreg, S_IWUSR, NULL, fsp_attr_set_setreg); + +static ssize_t fsp_attr_show_getreg(struct psmouse *psmouse, + void *data, char *buf) +{ + struct fsp_data *pad = psmouse->private; + + return sprintf(buf, "%02x%02x\n", pad->last_reg, pad->last_val); +} + +/* + * Read a register from device. + * + * ex: 0xab -- read content from register 0xab + */ +static ssize_t fsp_attr_set_getreg(struct psmouse *psmouse, void *data, + const char *buf, size_t count) +{ + struct fsp_data *pad = psmouse->private; + unsigned int reg, val; + int err; + + err = kstrtouint(buf, 16, ®); + if (err) + return err; + + if (reg > 0xff) + return -EINVAL; + + if (fsp_reg_read(psmouse, reg, &val)) + return -EIO; + + pad->last_reg = reg; + pad->last_val = val; + + return count; +} + +PSMOUSE_DEFINE_ATTR(getreg, S_IWUSR | S_IRUGO, NULL, + fsp_attr_show_getreg, fsp_attr_set_getreg); + +static ssize_t fsp_attr_show_pagereg(struct psmouse *psmouse, + void *data, char *buf) +{ + int val = 0; + + if (fsp_page_reg_read(psmouse, &val)) + return -EIO; + + return sprintf(buf, "%02x\n", val); +} + +static ssize_t fsp_attr_set_pagereg(struct psmouse *psmouse, void *data, + const char *buf, size_t count) +{ + unsigned int val; + int err; + + err = kstrtouint(buf, 16, &val); + if (err) + return err; + + if (val > 0xff) + return -EINVAL; + + if (fsp_page_reg_write(psmouse, val)) + return -EIO; + + return count; +} + +PSMOUSE_DEFINE_ATTR(page, S_IWUSR | S_IRUGO, NULL, + fsp_attr_show_pagereg, fsp_attr_set_pagereg); + +static ssize_t fsp_attr_show_vscroll(struct psmouse *psmouse, + void *data, char *buf) +{ + struct fsp_data *pad = psmouse->private; + + return sprintf(buf, "%d\n", pad->vscroll); +} + +static ssize_t fsp_attr_set_vscroll(struct psmouse *psmouse, void *data, + const char *buf, size_t count) +{ + unsigned int val; + int err; + + err = kstrtouint(buf, 10, &val); + if (err) + return err; + + if (val > 1) + return -EINVAL; + + fsp_onpad_vscr(psmouse, val); + + return count; +} + +PSMOUSE_DEFINE_ATTR(vscroll, S_IWUSR | S_IRUGO, NULL, + fsp_attr_show_vscroll, fsp_attr_set_vscroll); + +static ssize_t fsp_attr_show_hscroll(struct psmouse *psmouse, + void *data, char *buf) +{ + struct fsp_data *pad = psmouse->private; + + return sprintf(buf, "%d\n", pad->hscroll); +} + +static ssize_t fsp_attr_set_hscroll(struct psmouse *psmouse, void *data, + const char *buf, size_t count) +{ + unsigned int val; + int err; + + err = kstrtouint(buf, 10, &val); + if (err) + return err; + + if (val > 1) + return -EINVAL; + + fsp_onpad_hscr(psmouse, val); + + return count; +} + +PSMOUSE_DEFINE_ATTR(hscroll, S_IWUSR | S_IRUGO, NULL, + fsp_attr_show_hscroll, fsp_attr_set_hscroll); + +static ssize_t fsp_attr_show_flags(struct psmouse *psmouse, + void *data, char *buf) +{ + struct fsp_data *pad = psmouse->private; + + return sprintf(buf, "%c\n", + pad->flags & FSPDRV_FLAG_EN_OPC ? 'C' : 'c'); +} + +static ssize_t fsp_attr_set_flags(struct psmouse *psmouse, void *data, + const char *buf, size_t count) +{ + struct fsp_data *pad = psmouse->private; + size_t i; + + for (i = 0; i < count; i++) { + switch (buf[i]) { + case 'C': + pad->flags |= FSPDRV_FLAG_EN_OPC; + break; + case 'c': + pad->flags &= ~FSPDRV_FLAG_EN_OPC; + break; + default: + return -EINVAL; + } + } + return count; +} + +PSMOUSE_DEFINE_ATTR(flags, S_IWUSR | S_IRUGO, NULL, + fsp_attr_show_flags, fsp_attr_set_flags); + +static ssize_t fsp_attr_show_ver(struct psmouse *psmouse, + void *data, char *buf) +{ + return sprintf(buf, "Sentelic FSP kernel module %s\n", fsp_drv_ver); +} + +PSMOUSE_DEFINE_RO_ATTR(ver, S_IRUGO, NULL, fsp_attr_show_ver); + +static struct attribute *fsp_attributes[] = { + &psmouse_attr_setreg.dattr.attr, + &psmouse_attr_getreg.dattr.attr, + &psmouse_attr_page.dattr.attr, + &psmouse_attr_vscroll.dattr.attr, + &psmouse_attr_hscroll.dattr.attr, + &psmouse_attr_flags.dattr.attr, + &psmouse_attr_ver.dattr.attr, + NULL +}; + +static struct attribute_group fsp_attribute_group = { + .attrs = fsp_attributes, +}; + +#ifdef FSP_DEBUG +static void fsp_packet_debug(struct psmouse *psmouse, unsigned char packet[]) +{ + static unsigned int ps2_packet_cnt; + static unsigned int ps2_last_second; + unsigned int jiffies_msec; + const char *packet_type = "UNKNOWN"; + unsigned short abs_x = 0, abs_y = 0; + + /* Interpret & dump the packet data. */ + switch (packet[0] >> FSP_PKT_TYPE_SHIFT) { + case FSP_PKT_TYPE_ABS: + packet_type = "Absolute"; + abs_x = GET_ABS_X(packet); + abs_y = GET_ABS_Y(packet); + break; + case FSP_PKT_TYPE_NORMAL: + packet_type = "Normal"; + break; + case FSP_PKT_TYPE_NOTIFY: + packet_type = "Notify"; + break; + case FSP_PKT_TYPE_NORMAL_OPC: + packet_type = "Normal-OPC"; + break; + } + + ps2_packet_cnt++; + jiffies_msec = jiffies_to_msecs(jiffies); + psmouse_dbg(psmouse, + "%08dms %s packets: %02x, %02x, %02x, %02x; " + "abs_x: %d, abs_y: %d\n", + jiffies_msec, packet_type, + packet[0], packet[1], packet[2], packet[3], abs_x, abs_y); + + if (jiffies_msec - ps2_last_second > 1000) { + psmouse_dbg(psmouse, "PS/2 packets/sec = %d\n", ps2_packet_cnt); + ps2_packet_cnt = 0; + ps2_last_second = jiffies_msec; + } +} +#else +static void fsp_packet_debug(struct psmouse *psmouse, unsigned char packet[]) +{ +} +#endif + +static void fsp_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); + } +} + +static psmouse_ret_t fsp_process_byte(struct psmouse *psmouse) +{ + struct input_dev *dev = psmouse->dev; + struct fsp_data *ad = psmouse->private; + unsigned char *packet = psmouse->packet; + unsigned char button_status = 0, lscroll = 0, rscroll = 0; + unsigned short abs_x, abs_y, fgrs = 0; + + if (psmouse->pktcnt < 4) + return PSMOUSE_GOOD_DATA; + + /* + * Full packet accumulated, process it + */ + + fsp_packet_debug(psmouse, packet); + + switch (psmouse->packet[0] >> FSP_PKT_TYPE_SHIFT) { + case FSP_PKT_TYPE_ABS: + + if ((packet[0] == 0x48 || packet[0] == 0x49) && + packet[1] == 0 && packet[2] == 0) { + /* + * Ignore coordinate noise when finger leaving the + * surface, otherwise cursor may jump to upper-left + * corner. + */ + packet[3] &= 0xf0; + } + + abs_x = GET_ABS_X(packet); + abs_y = GET_ABS_Y(packet); + + if (packet[0] & FSP_PB0_MFMC) { + /* + * MFMC packet: assume that there are two fingers on + * pad + */ + fgrs = 2; + + /* MFMC packet */ + if (packet[0] & FSP_PB0_MFMC_FGR2) { + /* 2nd finger */ + if (ad->last_mt_fgr == 2) { + /* + * workaround for buggy firmware + * which doesn't clear MFMC bit if + * the 1st finger is up + */ + fgrs = 1; + fsp_set_slot(dev, 0, false, 0, 0); + } + ad->last_mt_fgr = 2; + + fsp_set_slot(dev, 1, fgrs == 2, abs_x, abs_y); + } else { + /* 1st finger */ + if (ad->last_mt_fgr == 1) { + /* + * workaround for buggy firmware + * which doesn't clear MFMC bit if + * the 2nd finger is up + */ + fgrs = 1; + fsp_set_slot(dev, 1, false, 0, 0); + } + ad->last_mt_fgr = 1; + fsp_set_slot(dev, 0, fgrs != 0, abs_x, abs_y); + } + } else { + /* SFAC packet */ + if ((packet[0] & (FSP_PB0_LBTN|FSP_PB0_PHY_BTN)) == + FSP_PB0_LBTN) { + /* On-pad click in SFAC mode should be handled + * by userspace. On-pad clicks in MFMC mode + * are real clickpad clicks, and not ignored. + */ + packet[0] &= ~FSP_PB0_LBTN; + } + + /* no multi-finger information */ + ad->last_mt_fgr = 0; + + if (abs_x != 0 && abs_y != 0) + fgrs = 1; + + fsp_set_slot(dev, 0, fgrs > 0, abs_x, abs_y); + fsp_set_slot(dev, 1, false, 0, 0); + } + if (fgrs == 1 || (fgrs == 2 && !(packet[0] & FSP_PB0_MFMC_FGR2))) { + input_report_abs(dev, ABS_X, abs_x); + input_report_abs(dev, ABS_Y, abs_y); + } + input_report_key(dev, BTN_LEFT, packet[0] & 0x01); + input_report_key(dev, BTN_RIGHT, packet[0] & 0x02); + input_report_key(dev, BTN_TOUCH, fgrs); + input_report_key(dev, BTN_TOOL_FINGER, fgrs == 1); + input_report_key(dev, BTN_TOOL_DOUBLETAP, fgrs == 2); + break; + + case FSP_PKT_TYPE_NORMAL_OPC: + /* on-pad click, filter it if necessary */ + if ((ad->flags & FSPDRV_FLAG_EN_OPC) != FSPDRV_FLAG_EN_OPC) + packet[0] &= ~FSP_PB0_LBTN; + /* fall through */ + + case FSP_PKT_TYPE_NORMAL: + /* normal packet */ + /* special packet data translation from on-pad packets */ + if (packet[3] != 0) { + if (packet[3] & BIT(0)) + button_status |= 0x01; /* wheel down */ + if (packet[3] & BIT(1)) + button_status |= 0x0f; /* wheel up */ + if (packet[3] & BIT(2)) + button_status |= BIT(4);/* horizontal left */ + if (packet[3] & BIT(3)) + button_status |= BIT(5);/* horizontal right */ + /* push back to packet queue */ + if (button_status != 0) + packet[3] = button_status; + rscroll = (packet[3] >> 4) & 1; + lscroll = (packet[3] >> 5) & 1; + } + /* + * Processing wheel up/down and extra button events + */ + input_report_rel(dev, REL_WHEEL, + (int)(packet[3] & 8) - (int)(packet[3] & 7)); + input_report_rel(dev, REL_HWHEEL, lscroll - rscroll); + input_report_key(dev, BTN_BACK, lscroll); + input_report_key(dev, BTN_FORWARD, rscroll); + + /* + * Standard PS/2 Mouse + */ + psmouse_report_standard_packet(dev, packet); + break; + } + + input_sync(dev); + + return PSMOUSE_FULL_PACKET; +} + +static int fsp_activate_protocol(struct psmouse *psmouse) +{ + struct fsp_data *pad = psmouse->private; + struct ps2dev *ps2dev = &psmouse->ps2dev; + unsigned char param[2]; + int val; + + /* + * Standard procedure to enter FSP Intellimouse mode + * (scrolling wheel, 4th and 5th buttons) + */ + param[0] = 200; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + param[0] = 200; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + param[0] = 80; + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE); + + ps2_command(ps2dev, param, PSMOUSE_CMD_GETID); + if (param[0] != 0x04) { + psmouse_err(psmouse, + "Unable to enable 4 bytes packet format.\n"); + return -EIO; + } + + if (pad->ver < FSP_VER_STL3888_C0) { + /* Preparing relative coordinates output for older hardware */ + if (fsp_reg_read(psmouse, FSP_REG_SYSCTL5, &val)) { + psmouse_err(psmouse, + "Unable to read SYSCTL5 register.\n"); + return -EIO; + } + + if (fsp_get_buttons(psmouse, &pad->buttons)) { + psmouse_err(psmouse, + "Unable to retrieve number of buttons.\n"); + return -EIO; + } + + val &= ~(FSP_BIT_EN_MSID7 | FSP_BIT_EN_MSID8 | FSP_BIT_EN_AUTO_MSID8); + /* Ensure we are not in absolute mode */ + val &= ~FSP_BIT_EN_PKT_G0; + if (pad->buttons == 0x06) { + /* Left/Middle/Right & Scroll Up/Down/Right/Left */ + val |= FSP_BIT_EN_MSID6; + } + + if (fsp_reg_write(psmouse, FSP_REG_SYSCTL5, val)) { + psmouse_err(psmouse, + "Unable to set up required mode bits.\n"); + return -EIO; + } + + /* + * Enable OPC tags such that driver can tell the difference + * between on-pad and real button click + */ + if (fsp_opc_tag_enable(psmouse, true)) + psmouse_warn(psmouse, + "Failed to enable OPC tag mode.\n"); + /* enable on-pad click by default */ + pad->flags |= FSPDRV_FLAG_EN_OPC; + + /* Enable on-pad vertical and horizontal scrolling */ + fsp_onpad_vscr(psmouse, true); + fsp_onpad_hscr(psmouse, true); + } else { + /* Enable absolute coordinates output for Cx/Dx hardware */ + if (fsp_reg_write(psmouse, FSP_REG_SWC1, + FSP_BIT_SWC1_EN_ABS_1F | + FSP_BIT_SWC1_EN_ABS_2F | + FSP_BIT_SWC1_EN_FUP_OUT | + FSP_BIT_SWC1_EN_ABS_CON)) { + psmouse_err(psmouse, + "Unable to enable absolute coordinates output.\n"); + return -EIO; + } + } + + return 0; +} + +static int fsp_set_input_params(struct psmouse *psmouse) +{ + struct input_dev *dev = psmouse->dev; + struct fsp_data *pad = psmouse->private; + + if (pad->ver < FSP_VER_STL3888_C0) { + __set_bit(BTN_MIDDLE, dev->keybit); + __set_bit(BTN_BACK, dev->keybit); + __set_bit(BTN_FORWARD, dev->keybit); + __set_bit(REL_WHEEL, dev->relbit); + __set_bit(REL_HWHEEL, dev->relbit); + } else { + /* + * Hardware prior to Cx performs much better in relative mode; + * hence, only enable absolute coordinates output as well as + * multi-touch output for the newer hardware. + * + * Maximum coordinates can be computed as: + * + * number of scanlines * 64 - 57 + * + * where number of X/Y scanline lines are 16/12. + */ + int abs_x = 967, abs_y = 711; + + __set_bit(EV_ABS, dev->evbit); + __clear_bit(EV_REL, dev->evbit); + __set_bit(BTN_TOUCH, dev->keybit); + __set_bit(BTN_TOOL_FINGER, dev->keybit); + __set_bit(BTN_TOOL_DOUBLETAP, dev->keybit); + __set_bit(INPUT_PROP_SEMI_MT, dev->propbit); + + input_set_abs_params(dev, ABS_X, 0, abs_x, 0, 0); + input_set_abs_params(dev, ABS_Y, 0, abs_y, 0, 0); + input_mt_init_slots(dev, 2, 0); + input_set_abs_params(dev, ABS_MT_POSITION_X, 0, abs_x, 0, 0); + input_set_abs_params(dev, ABS_MT_POSITION_Y, 0, abs_y, 0, 0); + } + + return 0; +} + +int fsp_detect(struct psmouse *psmouse, bool set_properties) +{ + int id; + + if (fsp_reg_read(psmouse, FSP_REG_DEVICE_ID, &id)) + return -EIO; + + if (id != 0x01) + return -ENODEV; + + if (set_properties) { + psmouse->vendor = "Sentelic"; + psmouse->name = "FingerSensingPad"; + } + + return 0; +} + +static void fsp_reset(struct psmouse *psmouse) +{ + fsp_opc_tag_enable(psmouse, false); + fsp_onpad_vscr(psmouse, false); + fsp_onpad_hscr(psmouse, false); +} + +static void fsp_disconnect(struct psmouse *psmouse) +{ + sysfs_remove_group(&psmouse->ps2dev.serio->dev.kobj, + &fsp_attribute_group); + + fsp_reset(psmouse); + kfree(psmouse->private); +} + +static int fsp_reconnect(struct psmouse *psmouse) +{ + int version; + + if (fsp_detect(psmouse, 0)) + return -ENODEV; + + if (fsp_get_version(psmouse, &version)) + return -ENODEV; + + if (fsp_activate_protocol(psmouse)) + return -EIO; + + return 0; +} + +int fsp_init(struct psmouse *psmouse) +{ + struct fsp_data *priv; + int ver, rev, sn = 0; + int error; + + if (fsp_get_version(psmouse, &ver) || + fsp_get_revision(psmouse, &rev)) { + return -ENODEV; + } + if (ver >= FSP_VER_STL3888_C0) { + /* firmware information is only available since C0 */ + fsp_get_sn(psmouse, &sn); + } + + psmouse_info(psmouse, + "Finger Sensing Pad, hw: %d.%d.%d, sn: %x, sw: %s\n", + ver >> 4, ver & 0x0F, rev, sn, fsp_drv_ver); + + psmouse->private = priv = kzalloc(sizeof(struct fsp_data), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->ver = ver; + priv->rev = rev; + + psmouse->protocol_handler = fsp_process_byte; + psmouse->disconnect = fsp_disconnect; + psmouse->reconnect = fsp_reconnect; + psmouse->cleanup = fsp_reset; + psmouse->pktsize = 4; + + error = fsp_activate_protocol(psmouse); + if (error) + goto err_out; + + /* Set up various supported input event bits */ + error = fsp_set_input_params(psmouse); + if (error) + goto err_out; + + error = sysfs_create_group(&psmouse->ps2dev.serio->dev.kobj, + &fsp_attribute_group); + if (error) { + psmouse_err(psmouse, + "Failed to create sysfs attributes (%d)", error); + goto err_out; + } + + return 0; + + err_out: + kfree(psmouse->private); + psmouse->private = NULL; + return error; +} diff --git a/drivers/input/mouse/sentelic.h b/drivers/input/mouse/sentelic.h new file mode 100644 index 000000000..42df9e3be --- /dev/null +++ b/drivers/input/mouse/sentelic.h @@ -0,0 +1,138 @@ +/*- + * Finger Sensing Pad PS/2 mouse driver. + * + * Copyright (C) 2005-2007 Asia Vital Components Co., Ltd. + * Copyright (C) 2005-2012 Tai-hwa Liang, Sentelic Corporation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __SENTELIC_H +#define __SENTELIC_H + +/* Finger-sensing Pad information registers */ +#define FSP_REG_DEVICE_ID 0x00 +#define FSP_REG_VERSION 0x01 +#define FSP_REG_REVISION 0x04 +#define FSP_REG_TMOD_STATUS1 0x0B +#define FSP_BIT_NO_ROTATION BIT(3) +#define FSP_REG_PAGE_CTRL 0x0F + +/* Finger-sensing Pad control registers */ +#define FSP_REG_SYSCTL1 0x10 +#define FSP_BIT_EN_REG_CLK BIT(5) +#define FSP_REG_TMOD_STATUS 0x20 +#define FSP_REG_OPC_QDOWN 0x31 +#define FSP_BIT_EN_OPC_TAG BIT(7) +#define FSP_REG_OPTZ_XLO 0x34 +#define FSP_REG_OPTZ_XHI 0x35 +#define FSP_REG_OPTZ_YLO 0x36 +#define FSP_REG_OPTZ_YHI 0x37 +#define FSP_REG_SYSCTL5 0x40 +#define FSP_BIT_90_DEGREE BIT(0) +#define FSP_BIT_EN_MSID6 BIT(1) +#define FSP_BIT_EN_MSID7 BIT(2) +#define FSP_BIT_EN_MSID8 BIT(3) +#define FSP_BIT_EN_AUTO_MSID8 BIT(5) +#define FSP_BIT_EN_PKT_G0 BIT(6) + +#define FSP_REG_ONPAD_CTL 0x43 +#define FSP_BIT_ONPAD_ENABLE BIT(0) +#define FSP_BIT_ONPAD_FBBB BIT(1) +#define FSP_BIT_FIX_VSCR BIT(3) +#define FSP_BIT_FIX_HSCR BIT(5) +#define FSP_BIT_DRAG_LOCK BIT(6) + +#define FSP_REG_SWC1 (0x90) +#define FSP_BIT_SWC1_EN_ABS_1F BIT(0) +#define FSP_BIT_SWC1_EN_GID BIT(1) +#define FSP_BIT_SWC1_EN_ABS_2F BIT(2) +#define FSP_BIT_SWC1_EN_FUP_OUT BIT(3) +#define FSP_BIT_SWC1_EN_ABS_CON BIT(4) +#define FSP_BIT_SWC1_GST_GRP0 BIT(5) +#define FSP_BIT_SWC1_GST_GRP1 BIT(6) +#define FSP_BIT_SWC1_BX_COMPAT BIT(7) + +#define FSP_PAGE_0B (0x0b) +#define FSP_PAGE_82 (0x82) +#define FSP_PAGE_DEFAULT FSP_PAGE_82 + +#define FSP_REG_SN0 (0x40) +#define FSP_REG_SN1 (0x41) +#define FSP_REG_SN2 (0x42) + +/* Finger-sensing Pad packet formating related definitions */ + +/* absolute packet type */ +#define FSP_PKT_TYPE_NORMAL (0x00) +#define FSP_PKT_TYPE_ABS (0x01) +#define FSP_PKT_TYPE_NOTIFY (0x02) +#define FSP_PKT_TYPE_NORMAL_OPC (0x03) +#define FSP_PKT_TYPE_SHIFT (6) + +/* bit definitions for the first byte of report packet */ +#define FSP_PB0_LBTN BIT(0) +#define FSP_PB0_RBTN BIT(1) +#define FSP_PB0_MBTN BIT(2) +#define FSP_PB0_MFMC_FGR2 FSP_PB0_MBTN +#define FSP_PB0_MUST_SET BIT(3) +#define FSP_PB0_PHY_BTN BIT(4) +#define FSP_PB0_MFMC BIT(5) + +/* hardware revisions */ +#define FSP_VER_STL3888_A4 (0xC1) +#define FSP_VER_STL3888_B0 (0xD0) +#define FSP_VER_STL3888_B1 (0xD1) +#define FSP_VER_STL3888_B2 (0xD2) +#define FSP_VER_STL3888_C0 (0xE0) +#define FSP_VER_STL3888_C1 (0xE1) +#define FSP_VER_STL3888_D0 (0xE2) +#define FSP_VER_STL3888_D1 (0xE3) +#define FSP_VER_STL3888_E0 (0xE4) + +#ifdef __KERNEL__ + +struct fsp_data { + unsigned char ver; /* hardware version */ + unsigned char rev; /* hardware revison */ + unsigned int buttons; /* Number of buttons */ + unsigned int flags; +#define FSPDRV_FLAG_EN_OPC (0x001) /* enable on-pad clicking */ + + bool vscroll; /* Vertical scroll zone enabled */ + bool hscroll; /* Horizontal scroll zone enabled */ + + unsigned char last_reg; /* Last register we requested read from */ + unsigned char last_val; + unsigned int last_mt_fgr; /* Last seen finger(multitouch) */ +}; + +#ifdef CONFIG_MOUSE_PS2_SENTELIC +extern int fsp_detect(struct psmouse *psmouse, bool set_properties); +extern int fsp_init(struct psmouse *psmouse); +#else +static inline int fsp_detect(struct psmouse *psmouse, bool set_properties) +{ + return -ENOSYS; +} +static inline int fsp_init(struct psmouse *psmouse) +{ + return -ENOSYS; +} +#endif + +#endif /* __KERNEL__ */ + +#endif /* !__SENTELIC_H */ diff --git a/drivers/input/mouse/sermouse.c b/drivers/input/mouse/sermouse.c new file mode 100644 index 000000000..3e8fb8136 --- /dev/null +++ b/drivers/input/mouse/sermouse.c @@ -0,0 +1,355 @@ +/* + * Copyright (c) 1999-2001 Vojtech Pavlik + */ + +/* + * Serial mouse driver for Linux + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/serio.h> + +#define DRIVER_DESC "Serial mouse driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static const char *sermouse_protocols[] = { "None", "Mouse Systems Mouse", "Sun Mouse", "Microsoft Mouse", + "Logitech M+ Mouse", "Microsoft MZ Mouse", "Logitech MZ+ Mouse", + "Logitech MZ++ Mouse"}; + +struct sermouse { + struct input_dev *dev; + signed char buf[8]; + unsigned char count; + unsigned char type; + unsigned long last; + char phys[32]; +}; + +/* + * sermouse_process_msc() analyzes the incoming MSC/Sun bytestream and + * applies some prediction to the data, resulting in 96 updates per + * second, which is as good as a PS/2 or USB mouse. + */ + +static void sermouse_process_msc(struct sermouse *sermouse, signed char data) +{ + struct input_dev *dev = sermouse->dev; + signed char *buf = sermouse->buf; + + switch (sermouse->count) { + + case 0: + if ((data & 0xf8) != 0x80) + return; + input_report_key(dev, BTN_LEFT, !(data & 4)); + input_report_key(dev, BTN_RIGHT, !(data & 1)); + input_report_key(dev, BTN_MIDDLE, !(data & 2)); + break; + + case 1: + case 3: + input_report_rel(dev, REL_X, data / 2); + input_report_rel(dev, REL_Y, -buf[1]); + buf[0] = data - data / 2; + break; + + case 2: + case 4: + input_report_rel(dev, REL_X, buf[0]); + input_report_rel(dev, REL_Y, buf[1] - data); + buf[1] = data / 2; + break; + } + + input_sync(dev); + + if (++sermouse->count == 5) + sermouse->count = 0; +} + +/* + * sermouse_process_ms() anlyzes the incoming MS(Z/+/++) bytestream and + * generates events. With prediction it gets 80 updates/sec, assuming + * standard 3-byte packets and 1200 bps. + */ + +static void sermouse_process_ms(struct sermouse *sermouse, signed char data) +{ + struct input_dev *dev = sermouse->dev; + signed char *buf = sermouse->buf; + + if (data & 0x40) + sermouse->count = 0; + else if (sermouse->count == 0) + return; + + switch (sermouse->count) { + + case 0: + buf[1] = data; + input_report_key(dev, BTN_LEFT, (data >> 5) & 1); + input_report_key(dev, BTN_RIGHT, (data >> 4) & 1); + break; + + case 1: + buf[2] = data; + data = (signed char) (((buf[1] << 6) & 0xc0) | (data & 0x3f)); + input_report_rel(dev, REL_X, data / 2); + input_report_rel(dev, REL_Y, buf[4]); + buf[3] = data - data / 2; + break; + + case 2: + /* Guessing the state of the middle button on 3-button MS-protocol mice - ugly. */ + if ((sermouse->type == SERIO_MS) && !data && !buf[2] && !((buf[0] & 0xf0) ^ buf[1])) + input_report_key(dev, BTN_MIDDLE, !test_bit(BTN_MIDDLE, dev->key)); + buf[0] = buf[1]; + + data = (signed char) (((buf[1] << 4) & 0xc0) | (data & 0x3f)); + input_report_rel(dev, REL_X, buf[3]); + input_report_rel(dev, REL_Y, data - buf[4]); + buf[4] = data / 2; + break; + + case 3: + + switch (sermouse->type) { + + case SERIO_MS: + sermouse->type = SERIO_MP; + /* fall through */ + + case SERIO_MP: + if ((data >> 2) & 3) break; /* M++ Wireless Extension packet. */ + input_report_key(dev, BTN_MIDDLE, (data >> 5) & 1); + input_report_key(dev, BTN_SIDE, (data >> 4) & 1); + break; + + case SERIO_MZP: + case SERIO_MZPP: + input_report_key(dev, BTN_SIDE, (data >> 5) & 1); + /* fall through */ + + case SERIO_MZ: + input_report_key(dev, BTN_MIDDLE, (data >> 4) & 1); + input_report_rel(dev, REL_WHEEL, (data & 8) - (data & 7)); + break; + } + + break; + + case 4: + case 6: /* MZ++ packet type. We can get these bytes for M++ too but we ignore them later. */ + buf[1] = (data >> 2) & 0x0f; + break; + + case 5: + case 7: /* Ignore anything besides MZ++ */ + if (sermouse->type != SERIO_MZPP) + break; + + switch (buf[1]) { + + case 1: /* Extra mouse info */ + + input_report_key(dev, BTN_SIDE, (data >> 4) & 1); + input_report_key(dev, BTN_EXTRA, (data >> 5) & 1); + input_report_rel(dev, data & 0x80 ? REL_HWHEEL : REL_WHEEL, (data & 7) - (data & 8)); + + break; + + default: /* We don't decode anything else yet. */ + + printk(KERN_WARNING + "sermouse.c: Received MZ++ packet %x, don't know how to handle.\n", buf[1]); + break; + } + + break; + } + + input_sync(dev); + + sermouse->count++; +} + +/* + * sermouse_interrupt() handles incoming characters, either gathering them into + * packets or passing them to the command routine as command output. + */ + +static irqreturn_t sermouse_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct sermouse *sermouse = serio_get_drvdata(serio); + + if (time_after(jiffies, sermouse->last + HZ/10)) + sermouse->count = 0; + + sermouse->last = jiffies; + + if (sermouse->type > SERIO_SUN) + sermouse_process_ms(sermouse, data); + else + sermouse_process_msc(sermouse, data); + + return IRQ_HANDLED; +} + +/* + * sermouse_disconnect() cleans up after we don't want talk + * to the mouse anymore. + */ + +static void sermouse_disconnect(struct serio *serio) +{ + struct sermouse *sermouse = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(sermouse->dev); + kfree(sermouse); +} + +/* + * sermouse_connect() is a callback form the serio module when + * an unhandled serio port is found. + */ + +static int sermouse_connect(struct serio *serio, struct serio_driver *drv) +{ + struct sermouse *sermouse; + struct input_dev *input_dev; + unsigned char c = serio->id.extra; + int err = -ENOMEM; + + sermouse = kzalloc(sizeof(struct sermouse), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!sermouse || !input_dev) + goto fail1; + + sermouse->dev = input_dev; + snprintf(sermouse->phys, sizeof(sermouse->phys), "%s/input0", serio->phys); + sermouse->type = serio->id.proto; + + input_dev->name = sermouse_protocols[sermouse->type]; + input_dev->phys = sermouse->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = sermouse->type; + input_dev->id.product = c; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_RIGHT); + input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + + if (c & 0x01) set_bit(BTN_MIDDLE, input_dev->keybit); + if (c & 0x02) set_bit(BTN_SIDE, input_dev->keybit); + if (c & 0x04) set_bit(BTN_EXTRA, input_dev->keybit); + if (c & 0x10) set_bit(REL_WHEEL, input_dev->relbit); + if (c & 0x20) set_bit(REL_HWHEEL, input_dev->relbit); + + serio_set_drvdata(serio, sermouse); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(sermouse->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(sermouse); + return err; +} + +static struct serio_device_id sermouse_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_MSC, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_RS232, + .proto = SERIO_SUN, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_RS232, + .proto = SERIO_MS, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_RS232, + .proto = SERIO_MP, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_RS232, + .proto = SERIO_MZ, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_RS232, + .proto = SERIO_MZP, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { + .type = SERIO_RS232, + .proto = SERIO_MZPP, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, sermouse_serio_ids); + +static struct serio_driver sermouse_drv = { + .driver = { + .name = "sermouse", + }, + .description = DRIVER_DESC, + .id_table = sermouse_serio_ids, + .interrupt = sermouse_interrupt, + .connect = sermouse_connect, + .disconnect = sermouse_disconnect, +}; + +module_serio_driver(sermouse_drv); diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c new file mode 100644 index 000000000..c6d393114 --- /dev/null +++ b/drivers/input/mouse/synaptics.c @@ -0,0 +1,1903 @@ +/* + * Synaptics TouchPad PS/2 mouse driver + * + * 2003 Dmitry Torokhov <dtor@mail.ru> + * Added support for pass-through port. Special thanks to Peter Berg Larsen + * for explaining various Synaptics quirks. + * + * 2003 Peter Osterlund <petero2@telia.com> + * Ported to 2.5 input device infrastructure. + * + * Copyright (C) 2001 Stefan Gmeiner <riddlebox@freesurf.ch> + * start merging tpconfig and gpm code to a xfree-input module + * adding some changes and extensions (ex. 3rd and 4th button) + * + * Copyright (c) 1997 C. Scott Ananian <cananian@alumni.priceton.edu> + * Copyright (c) 1998-2000 Bruce Kalk <kall@compass.com> + * code for the special synaptics commands (from the tpconfig-source) + * + * 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/module.h> +#include <linux/delay.h> +#include <linux/dmi.h> +#include <linux/input/mt.h> +#include <linux/serio.h> +#include <linux/libps2.h> +#include <linux/rmi.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include "psmouse.h" +#include "synaptics.h" + +/* + * The x/y limits are taken from the Synaptics TouchPad interfacing Guide, + * section 2.3.2, which says that they should be valid regardless of the + * actual size of the sensor. + * Note that newer firmware allows querying device for maximum useable + * coordinates. + */ +#define XMIN 0 +#define XMAX 6143 +#define YMIN 0 +#define YMAX 6143 +#define XMIN_NOMINAL 1472 +#define XMAX_NOMINAL 5472 +#define YMIN_NOMINAL 1408 +#define YMAX_NOMINAL 4448 + +/* Size in bits of absolute position values reported by the hardware */ +#define ABS_POS_BITS 13 + +/* + * These values should represent the absolute maximum value that will + * be reported for a positive position value. Some Synaptics firmware + * uses this value to indicate a finger near the edge of the touchpad + * whose precise position cannot be determined. + * + * At least one touchpad is known to report positions in excess of this + * value which are actually negative values truncated to the 13-bit + * reporting range. These values have never been observed to be lower + * than 8184 (i.e. -8), so we treat all values greater than 8176 as + * negative and any other value as positive. + */ +#define X_MAX_POSITIVE 8176 +#define Y_MAX_POSITIVE 8176 + +/* maximum ABS_MT_POSITION displacement (in mm) */ +#define DMAX 10 + +/***************************************************************************** + * Stuff we need even when we do not want native Synaptics support + ****************************************************************************/ + +/* + * Set the synaptics touchpad mode byte by special commands + */ +static int synaptics_mode_cmd(struct psmouse *psmouse, u8 mode) +{ + u8 param[1]; + int error; + + error = ps2_sliced_command(&psmouse->ps2dev, mode); + if (error) + return error; + + param[0] = SYN_PS_SET_MODE2; + error = ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_SETRATE); + if (error) + return error; + + return 0; +} + +int synaptics_detect(struct psmouse *psmouse, bool set_properties) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + u8 param[4] = { 0 }; + + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES); + ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO); + + if (param[1] != 0x47) + return -ENODEV; + + if (set_properties) { + psmouse->vendor = "Synaptics"; + psmouse->name = "TouchPad"; + } + + return 0; +} + +void synaptics_reset(struct psmouse *psmouse) +{ + /* reset touchpad back to relative mode, gestures enabled */ + synaptics_mode_cmd(psmouse, 0); +} + +#if defined(CONFIG_MOUSE_PS2_SYNAPTICS) || \ + defined(CONFIG_MOUSE_PS2_SYNAPTICS_SMBUS) + +/* This list has been kindly provided by Synaptics. */ +static const char * const topbuttonpad_pnp_ids[] = { + "LEN0017", + "LEN0018", + "LEN0019", + "LEN0023", + "LEN002A", + "LEN002B", + "LEN002C", + "LEN002D", + "LEN002E", + "LEN0033", /* Helix */ + "LEN0034", /* T431s, L440, L540, T540, W540, X1 Carbon 2nd */ + "LEN0035", /* X240 */ + "LEN0036", /* T440 */ + "LEN0037", /* X1 Carbon 2nd */ + "LEN0038", + "LEN0039", /* T440s */ + "LEN0041", + "LEN0042", /* Yoga */ + "LEN0045", + "LEN0047", + "LEN2000", /* S540 */ + "LEN2001", /* Edge E431 */ + "LEN2002", /* Edge E531 */ + "LEN2003", + "LEN2004", /* L440 */ + "LEN2005", + "LEN2006", /* Edge E440/E540 */ + "LEN2007", + "LEN2008", + "LEN2009", + "LEN200A", + "LEN200B", + NULL +}; + +static const char * const smbus_pnp_ids[] = { + /* all of the topbuttonpad_pnp_ids are valid, we just add some extras */ + "LEN0048", /* X1 Carbon 3 */ + "LEN0046", /* X250 */ + "LEN0049", /* Yoga 11e */ + "LEN004a", /* W541 */ + "LEN005b", /* P50 */ + "LEN005e", /* T560 */ + "LEN006c", /* T470s */ + "LEN007a", /* T470s */ + "LEN0071", /* T480 */ + "LEN0072", /* X1 Carbon Gen 5 (2017) - Elan/ALPS trackpoint */ + "LEN0073", /* X1 Carbon G5 (Elantech) */ + "LEN0091", /* X1 Carbon 6 */ + "LEN0092", /* X1 Carbon 6 */ + "LEN0093", /* T480 */ + "LEN0096", /* X280 */ + "LEN0097", /* X280 -> ALPS trackpoint */ + "LEN0099", /* X1 Extreme 1st */ + "LEN009b", /* T580 */ + "LEN200f", /* T450s */ + "LEN2044", /* L470 */ + "LEN2054", /* E480 */ + "LEN2055", /* E580 */ + "SYN3052", /* HP EliteBook 840 G4 */ + "SYN3221", /* HP 15-ay000 */ + "SYN323d", /* HP Spectre X360 13-w013dx */ + "SYN3257", /* HP Envy 13-ad105ng */ + NULL +}; + +static const char * const forcepad_pnp_ids[] = { + "SYN300D", + "SYN3014", + NULL +}; + +/* + * Send a command to the synpatics touchpad by special commands + */ +static int synaptics_send_cmd(struct psmouse *psmouse, u8 cmd, u8 *param) +{ + int error; + + error = ps2_sliced_command(&psmouse->ps2dev, cmd); + if (error) + return error; + + error = ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETINFO); + if (error) + return error; + + return 0; +} + +static int synaptics_query_int(struct psmouse *psmouse, u8 query_cmd, u32 *val) +{ + int error; + union { + __be32 be_val; + char buf[4]; + } resp = { 0 }; + + error = synaptics_send_cmd(psmouse, query_cmd, resp.buf + 1); + if (error) + return error; + + *val = be32_to_cpu(resp.be_val); + return 0; +} + +/* + * Identify Touchpad + * See also the SYN_ID_* macros + */ +static int synaptics_identify(struct psmouse *psmouse, + struct synaptics_device_info *info) +{ + int error; + + error = synaptics_query_int(psmouse, SYN_QUE_IDENTIFY, &info->identity); + if (error) + return error; + + return SYN_ID_IS_SYNAPTICS(info->identity) ? 0 : -ENXIO; +} + +/* + * Read the model-id bytes from the touchpad + * see also SYN_MODEL_* macros + */ +static int synaptics_model_id(struct psmouse *psmouse, + struct synaptics_device_info *info) +{ + return synaptics_query_int(psmouse, SYN_QUE_MODEL, &info->model_id); +} + +/* + * Read the firmware id from the touchpad + */ +static int synaptics_firmware_id(struct psmouse *psmouse, + struct synaptics_device_info *info) +{ + return synaptics_query_int(psmouse, SYN_QUE_FIRMWARE_ID, + &info->firmware_id); +} + +/* + * Read the board id and the "More Extended Queries" from the touchpad + * The board id is encoded in the "QUERY MODES" response + */ +static int synaptics_query_modes(struct psmouse *psmouse, + struct synaptics_device_info *info) +{ + u8 bid[3]; + int error; + + /* firmwares prior 7.5 have no board_id encoded */ + if (SYN_ID_FULL(info->identity) < 0x705) + return 0; + + error = synaptics_send_cmd(psmouse, SYN_QUE_MODES, bid); + if (error) + return error; + + info->board_id = ((bid[0] & 0xfc) << 6) | bid[1]; + + if (SYN_MEXT_CAP_BIT(bid[0])) + return synaptics_query_int(psmouse, SYN_QUE_MEXT_CAPAB_10, + &info->ext_cap_10); + + return 0; +} + +/* + * Read the capability-bits from the touchpad + * see also the SYN_CAP_* macros + */ +static int synaptics_capability(struct psmouse *psmouse, + struct synaptics_device_info *info) +{ + int error; + + error = synaptics_query_int(psmouse, SYN_QUE_CAPABILITIES, + &info->capabilities); + if (error) + return error; + + info->ext_cap = info->ext_cap_0c = 0; + + /* + * Older firmwares had submodel ID fixed to 0x47 + */ + if (SYN_ID_FULL(info->identity) < 0x705 && + SYN_CAP_SUBMODEL_ID(info->capabilities) != 0x47) { + return -ENXIO; + } + + /* + * Unless capExtended is set the rest of the flags should be ignored + */ + if (!SYN_CAP_EXTENDED(info->capabilities)) + info->capabilities = 0; + + if (SYN_EXT_CAP_REQUESTS(info->capabilities) >= 1) { + error = synaptics_query_int(psmouse, SYN_QUE_EXT_CAPAB, + &info->ext_cap); + if (error) { + psmouse_warn(psmouse, + "device claims to have extended capabilities, but I'm not able to read them.\n"); + } else { + /* + * if nExtBtn is greater than 8 it should be considered + * invalid and treated as 0 + */ + if (SYN_CAP_MULTI_BUTTON_NO(info->ext_cap) > 8) + info->ext_cap &= ~SYN_CAP_MB_MASK; + } + } + + if (SYN_EXT_CAP_REQUESTS(info->capabilities) >= 4) { + error = synaptics_query_int(psmouse, SYN_QUE_EXT_CAPAB_0C, + &info->ext_cap_0c); + if (error) + psmouse_warn(psmouse, + "device claims to have extended capability 0x0c, but I'm not able to read it.\n"); + } + + return 0; +} + +/* + * Read touchpad resolution and maximum reported coordinates + * Resolution is left zero if touchpad does not support the query + */ +static int synaptics_resolution(struct psmouse *psmouse, + struct synaptics_device_info *info) +{ + u8 resp[3]; + int error; + + if (SYN_ID_MAJOR(info->identity) < 4) + return 0; + + error = synaptics_send_cmd(psmouse, SYN_QUE_RESOLUTION, resp); + if (!error) { + if (resp[0] != 0 && (resp[1] & 0x80) && resp[2] != 0) { + info->x_res = resp[0]; /* x resolution in units/mm */ + info->y_res = resp[2]; /* y resolution in units/mm */ + } + } + + if (SYN_EXT_CAP_REQUESTS(info->capabilities) >= 5 && + SYN_CAP_MAX_DIMENSIONS(info->ext_cap_0c)) { + error = synaptics_send_cmd(psmouse, + SYN_QUE_EXT_MAX_COORDS, resp); + if (error) { + psmouse_warn(psmouse, + "device claims to have max coordinates query, but I'm not able to read it.\n"); + } else { + info->x_max = (resp[0] << 5) | ((resp[1] & 0x0f) << 1); + info->y_max = (resp[2] << 5) | ((resp[1] & 0xf0) >> 3); + psmouse_info(psmouse, + "queried max coordinates: x [..%d], y [..%d]\n", + info->x_max, info->y_max); + } + } + + if (SYN_CAP_MIN_DIMENSIONS(info->ext_cap_0c) && + (SYN_EXT_CAP_REQUESTS(info->capabilities) >= 7 || + /* + * Firmware v8.1 does not report proper number of extended + * capabilities, but has been proven to report correct min + * coordinates. + */ + SYN_ID_FULL(info->identity) == 0x801)) { + error = synaptics_send_cmd(psmouse, + SYN_QUE_EXT_MIN_COORDS, resp); + if (error) { + psmouse_warn(psmouse, + "device claims to have min coordinates query, but I'm not able to read it.\n"); + } else { + info->x_min = (resp[0] << 5) | ((resp[1] & 0x0f) << 1); + info->y_min = (resp[2] << 5) | ((resp[1] & 0xf0) >> 3); + psmouse_info(psmouse, + "queried min coordinates: x [%d..], y [%d..]\n", + info->x_min, info->y_min); + } + } + + return 0; +} + +static int synaptics_query_hardware(struct psmouse *psmouse, + struct synaptics_device_info *info) +{ + int error; + + memset(info, 0, sizeof(*info)); + + error = synaptics_identify(psmouse, info); + if (error) + return error; + + error = synaptics_model_id(psmouse, info); + if (error) + return error; + + error = synaptics_firmware_id(psmouse, info); + if (error) + return error; + + error = synaptics_query_modes(psmouse, info); + if (error) + return error; + + error = synaptics_capability(psmouse, info); + if (error) + return error; + + error = synaptics_resolution(psmouse, info); + if (error) + return error; + + return 0; +} + +#endif /* CONFIG_MOUSE_PS2_SYNAPTICS || CONFIG_MOUSE_PS2_SYNAPTICS_SMBUS */ + +#ifdef CONFIG_MOUSE_PS2_SYNAPTICS + +static bool cr48_profile_sensor; + +#define ANY_BOARD_ID 0 +struct min_max_quirk { + const char * const *pnp_ids; + struct { + u32 min, max; + } board_id; + u32 x_min, x_max, y_min, y_max; +}; + +static const struct min_max_quirk min_max_pnpid_table[] = { + { + (const char * const []){"LEN0033", NULL}, + {ANY_BOARD_ID, ANY_BOARD_ID}, + 1024, 5052, 2258, 4832 + }, + { + (const char * const []){"LEN0042", NULL}, + {ANY_BOARD_ID, ANY_BOARD_ID}, + 1232, 5710, 1156, 4696 + }, + { + (const char * const []){"LEN0034", "LEN0036", "LEN0037", + "LEN0039", "LEN2002", "LEN2004", + NULL}, + {ANY_BOARD_ID, 2961}, + 1024, 5112, 2024, 4832 + }, + { + (const char * const []){"LEN2000", NULL}, + {ANY_BOARD_ID, ANY_BOARD_ID}, + 1024, 5113, 2021, 4832 + }, + { + (const char * const []){"LEN2001", NULL}, + {ANY_BOARD_ID, ANY_BOARD_ID}, + 1024, 5022, 2508, 4832 + }, + { + (const char * const []){"LEN2006", NULL}, + {2691, 2691}, + 1024, 5045, 2457, 4832 + }, + { + (const char * const []){"LEN2006", NULL}, + {ANY_BOARD_ID, ANY_BOARD_ID}, + 1264, 5675, 1171, 4688 + }, + { } +}; + +/***************************************************************************** + * Synaptics communications functions + ****************************************************************************/ + +/* + * Synaptics touchpads report the y coordinate from bottom to top, which is + * opposite from what userspace expects. + * This function is used to invert y before reporting. + */ +static int synaptics_invert_y(int y) +{ + return YMAX_NOMINAL + YMIN_NOMINAL - y; +} + +/* + * Apply quirk(s) if the hardware matches + */ +static void synaptics_apply_quirks(struct psmouse *psmouse, + struct synaptics_device_info *info) +{ + int i; + + for (i = 0; min_max_pnpid_table[i].pnp_ids; i++) { + if (!psmouse_matches_pnp_id(psmouse, + min_max_pnpid_table[i].pnp_ids)) + continue; + + if (min_max_pnpid_table[i].board_id.min != ANY_BOARD_ID && + info->board_id < min_max_pnpid_table[i].board_id.min) + continue; + + if (min_max_pnpid_table[i].board_id.max != ANY_BOARD_ID && + info->board_id > min_max_pnpid_table[i].board_id.max) + continue; + + info->x_min = min_max_pnpid_table[i].x_min; + info->x_max = min_max_pnpid_table[i].x_max; + info->y_min = min_max_pnpid_table[i].y_min; + info->y_max = min_max_pnpid_table[i].y_max; + psmouse_info(psmouse, + "quirked min/max coordinates: x [%d..%d], y [%d..%d]\n", + info->x_min, info->x_max, + info->y_min, info->y_max); + break; + } +} + +static bool synaptics_has_agm(struct synaptics_data *priv) +{ + return (SYN_CAP_ADV_GESTURE(priv->info.ext_cap_0c) || + SYN_CAP_IMAGE_SENSOR(priv->info.ext_cap_0c)); +} + +static int synaptics_set_advanced_gesture_mode(struct psmouse *psmouse) +{ + static u8 param = 0xc8; + int error; + + error = ps2_sliced_command(&psmouse->ps2dev, SYN_QUE_MODEL); + if (error) + return error; + + error = ps2_command(&psmouse->ps2dev, ¶m, PSMOUSE_CMD_SETRATE); + if (error) + return error; + + return 0; +} + +static int synaptics_set_mode(struct psmouse *psmouse) +{ + struct synaptics_data *priv = psmouse->private; + int error; + + priv->mode = 0; + if (priv->absolute_mode) + priv->mode |= SYN_BIT_ABSOLUTE_MODE; + if (priv->disable_gesture) + priv->mode |= SYN_BIT_DISABLE_GESTURE; + if (psmouse->rate >= 80) + priv->mode |= SYN_BIT_HIGH_RATE; + if (SYN_CAP_EXTENDED(priv->info.capabilities)) + priv->mode |= SYN_BIT_W_MODE; + + error = synaptics_mode_cmd(psmouse, priv->mode); + if (error) + return error; + + if (priv->absolute_mode && synaptics_has_agm(priv)) { + error = synaptics_set_advanced_gesture_mode(psmouse); + if (error) { + psmouse_err(psmouse, + "Advanced gesture mode init failed: %d\n", + error); + return error; + } + } + + return 0; +} + +static void synaptics_set_rate(struct psmouse *psmouse, unsigned int rate) +{ + struct synaptics_data *priv = psmouse->private; + + if (rate >= 80) { + priv->mode |= SYN_BIT_HIGH_RATE; + psmouse->rate = 80; + } else { + priv->mode &= ~SYN_BIT_HIGH_RATE; + psmouse->rate = 40; + } + + synaptics_mode_cmd(psmouse, priv->mode); +} + +/***************************************************************************** + * Synaptics pass-through PS/2 port support + ****************************************************************************/ +static int synaptics_pt_write(struct serio *serio, u8 c) +{ + struct psmouse *parent = serio_get_drvdata(serio->parent); + u8 rate_param = SYN_PS_CLIENT_CMD; /* indicates that we want pass-through port */ + int error; + + error = ps2_sliced_command(&parent->ps2dev, c); + if (error) + return error; + + error = ps2_command(&parent->ps2dev, &rate_param, PSMOUSE_CMD_SETRATE); + if (error) + return error; + + return 0; +} + +static int synaptics_pt_start(struct serio *serio) +{ + struct psmouse *parent = serio_get_drvdata(serio->parent); + struct synaptics_data *priv = parent->private; + + serio_pause_rx(parent->ps2dev.serio); + priv->pt_port = serio; + serio_continue_rx(parent->ps2dev.serio); + + return 0; +} + +static void synaptics_pt_stop(struct serio *serio) +{ + struct psmouse *parent = serio_get_drvdata(serio->parent); + struct synaptics_data *priv = parent->private; + + serio_pause_rx(parent->ps2dev.serio); + priv->pt_port = NULL; + serio_continue_rx(parent->ps2dev.serio); +} + +static int synaptics_is_pt_packet(u8 *buf) +{ + return (buf[0] & 0xFC) == 0x84 && (buf[3] & 0xCC) == 0xC4; +} + +static void synaptics_pass_pt_packet(struct serio *ptport, u8 *packet) +{ + struct psmouse *child = serio_get_drvdata(ptport); + + if (child && child->state == PSMOUSE_ACTIVATED) { + serio_interrupt(ptport, packet[1], 0); + serio_interrupt(ptport, packet[4], 0); + serio_interrupt(ptport, packet[5], 0); + if (child->pktsize == 4) + serio_interrupt(ptport, packet[2], 0); + } else { + serio_interrupt(ptport, packet[1], 0); + } +} + +static void synaptics_pt_activate(struct psmouse *psmouse) +{ + struct synaptics_data *priv = psmouse->private; + struct psmouse *child = serio_get_drvdata(priv->pt_port); + + /* adjust the touchpad to child's choice of protocol */ + if (child) { + if (child->pktsize == 4) + priv->mode |= SYN_BIT_FOUR_BYTE_CLIENT; + else + priv->mode &= ~SYN_BIT_FOUR_BYTE_CLIENT; + + if (synaptics_mode_cmd(psmouse, priv->mode)) + psmouse_warn(psmouse, + "failed to switch guest protocol\n"); + } +} + +static void synaptics_pt_create(struct psmouse *psmouse) +{ + struct serio *serio; + + serio = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (!serio) { + psmouse_err(psmouse, + "not enough memory for pass-through port\n"); + return; + } + + serio->id.type = SERIO_PS_PSTHRU; + strlcpy(serio->name, "Synaptics pass-through", sizeof(serio->name)); + strlcpy(serio->phys, "synaptics-pt/serio0", sizeof(serio->name)); + serio->write = synaptics_pt_write; + serio->start = synaptics_pt_start; + serio->stop = synaptics_pt_stop; + serio->parent = psmouse->ps2dev.serio; + + psmouse->pt_activate = synaptics_pt_activate; + + psmouse_info(psmouse, "serio: %s port at %s\n", + serio->name, psmouse->phys); + serio_register_port(serio); +} + +/***************************************************************************** + * Functions to interpret the absolute mode packets + ****************************************************************************/ + +static void synaptics_parse_agm(const u8 buf[], + struct synaptics_data *priv, + struct synaptics_hw_state *hw) +{ + struct synaptics_hw_state *agm = &priv->agm; + int agm_packet_type; + + agm_packet_type = (buf[5] & 0x30) >> 4; + switch (agm_packet_type) { + case 1: + /* Gesture packet: (x, y, z) half resolution */ + agm->w = hw->w; + agm->x = (((buf[4] & 0x0f) << 8) | buf[1]) << 1; + agm->y = (((buf[4] & 0xf0) << 4) | buf[2]) << 1; + agm->z = ((buf[3] & 0x30) | (buf[5] & 0x0f)) << 1; + break; + + case 2: + /* AGM-CONTACT packet: we are only interested in the count */ + priv->agm_count = buf[1]; + break; + + default: + break; + } +} + +static void synaptics_parse_ext_buttons(const u8 buf[], + struct synaptics_data *priv, + struct synaptics_hw_state *hw) +{ + unsigned int ext_bits = + (SYN_CAP_MULTI_BUTTON_NO(priv->info.ext_cap) + 1) >> 1; + unsigned int ext_mask = GENMASK(ext_bits - 1, 0); + + hw->ext_buttons = buf[4] & ext_mask; + hw->ext_buttons |= (buf[5] & ext_mask) << ext_bits; +} + +static int synaptics_parse_hw_state(const u8 buf[], + struct synaptics_data *priv, + struct synaptics_hw_state *hw) +{ + memset(hw, 0, sizeof(struct synaptics_hw_state)); + + if (SYN_MODEL_NEWABS(priv->info.model_id)) { + hw->w = (((buf[0] & 0x30) >> 2) | + ((buf[0] & 0x04) >> 1) | + ((buf[3] & 0x04) >> 2)); + + if (synaptics_has_agm(priv) && hw->w == 2) { + synaptics_parse_agm(buf, priv, hw); + return 1; + } + + hw->x = (((buf[3] & 0x10) << 8) | + ((buf[1] & 0x0f) << 8) | + buf[4]); + hw->y = (((buf[3] & 0x20) << 7) | + ((buf[1] & 0xf0) << 4) | + buf[5]); + hw->z = buf[2]; + + hw->left = (buf[0] & 0x01) ? 1 : 0; + hw->right = (buf[0] & 0x02) ? 1 : 0; + + if (priv->is_forcepad) { + /* + * ForcePads, like Clickpads, use middle button + * bits to report primary button clicks. + * Unfortunately they report primary button not + * only when user presses on the pad above certain + * threshold, but also when there are more than one + * finger on the touchpad, which interferes with + * out multi-finger gestures. + */ + if (hw->z == 0) { + /* No contacts */ + priv->press = priv->report_press = false; + } else if (hw->w >= 4 && ((buf[0] ^ buf[3]) & 0x01)) { + /* + * Single-finger touch with pressure above + * the threshold. If pressure stays long + * enough, we'll start reporting primary + * button. We rely on the device continuing + * sending data even if finger does not + * move. + */ + if (!priv->press) { + priv->press_start = jiffies; + priv->press = true; + } else if (time_after(jiffies, + priv->press_start + + msecs_to_jiffies(50))) { + priv->report_press = true; + } + } else { + priv->press = false; + } + + hw->left = priv->report_press; + + } else if (SYN_CAP_CLICKPAD(priv->info.ext_cap_0c)) { + /* + * Clickpad's button is transmitted as middle button, + * however, since it is primary button, we will report + * it as BTN_LEFT. + */ + hw->left = ((buf[0] ^ buf[3]) & 0x01) ? 1 : 0; + + } else if (SYN_CAP_MIDDLE_BUTTON(priv->info.capabilities)) { + hw->middle = ((buf[0] ^ buf[3]) & 0x01) ? 1 : 0; + if (hw->w == 2) + hw->scroll = (s8)buf[1]; + } + + if (SYN_CAP_FOUR_BUTTON(priv->info.capabilities)) { + hw->up = ((buf[0] ^ buf[3]) & 0x01) ? 1 : 0; + hw->down = ((buf[0] ^ buf[3]) & 0x02) ? 1 : 0; + } + + if (SYN_CAP_MULTI_BUTTON_NO(priv->info.ext_cap) > 0 && + ((buf[0] ^ buf[3]) & 0x02)) { + synaptics_parse_ext_buttons(buf, priv, hw); + } + } else { + hw->x = (((buf[1] & 0x1f) << 8) | buf[2]); + hw->y = (((buf[4] & 0x1f) << 8) | buf[5]); + + hw->z = (((buf[0] & 0x30) << 2) | (buf[3] & 0x3F)); + hw->w = (((buf[1] & 0x80) >> 4) | ((buf[0] & 0x04) >> 1)); + + hw->left = (buf[0] & 0x01) ? 1 : 0; + hw->right = (buf[0] & 0x02) ? 1 : 0; + } + + /* + * Convert wrap-around values to negative. (X|Y)_MAX_POSITIVE + * is used by some firmware to indicate a finger at the edge of + * the touchpad whose precise position cannot be determined, so + * convert these values to the maximum axis value. + */ + if (hw->x > X_MAX_POSITIVE) + hw->x -= 1 << ABS_POS_BITS; + else if (hw->x == X_MAX_POSITIVE) + hw->x = XMAX; + + if (hw->y > Y_MAX_POSITIVE) + hw->y -= 1 << ABS_POS_BITS; + else if (hw->y == Y_MAX_POSITIVE) + hw->y = YMAX; + + return 0; +} + +static void synaptics_report_semi_mt_slot(struct input_dev *dev, int slot, + bool active, int x, 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, synaptics_invert_y(y)); + } +} + +static void synaptics_report_semi_mt_data(struct input_dev *dev, + const struct synaptics_hw_state *a, + const struct synaptics_hw_state *b, + int num_fingers) +{ + if (num_fingers >= 2) { + synaptics_report_semi_mt_slot(dev, 0, true, min(a->x, b->x), + min(a->y, b->y)); + synaptics_report_semi_mt_slot(dev, 1, true, max(a->x, b->x), + max(a->y, b->y)); + } else if (num_fingers == 1) { + synaptics_report_semi_mt_slot(dev, 0, true, a->x, a->y); + synaptics_report_semi_mt_slot(dev, 1, false, 0, 0); + } else { + synaptics_report_semi_mt_slot(dev, 0, false, 0, 0); + synaptics_report_semi_mt_slot(dev, 1, false, 0, 0); + } +} + +static void synaptics_report_ext_buttons(struct psmouse *psmouse, + const struct synaptics_hw_state *hw) +{ + struct input_dev *dev = psmouse->dev; + struct synaptics_data *priv = psmouse->private; + int ext_bits = (SYN_CAP_MULTI_BUTTON_NO(priv->info.ext_cap) + 1) >> 1; + int i; + + if (!SYN_CAP_MULTI_BUTTON_NO(priv->info.ext_cap)) + return; + + /* Bug in FW 8.1 & 8.2, buttons are reported only when ExtBit is 1 */ + if ((SYN_ID_FULL(priv->info.identity) == 0x801 || + SYN_ID_FULL(priv->info.identity) == 0x802) && + !((psmouse->packet[0] ^ psmouse->packet[3]) & 0x02)) + return; + + if (!SYN_CAP_EXT_BUTTONS_STICK(priv->info.ext_cap_10)) { + for (i = 0; i < ext_bits; i++) { + input_report_key(dev, BTN_0 + 2 * i, + hw->ext_buttons & BIT(i)); + input_report_key(dev, BTN_1 + 2 * i, + hw->ext_buttons & BIT(i + ext_bits)); + } + return; + } + + /* + * This generation of touchpads has the trackstick buttons + * physically wired to the touchpad. Re-route them through + * the pass-through interface. + */ + if (priv->pt_port) { + u8 pt_buttons; + + /* The trackstick expects at most 3 buttons */ + pt_buttons = SYN_EXT_BUTTON_STICK_L(hw->ext_buttons) | + SYN_EXT_BUTTON_STICK_R(hw->ext_buttons) << 1 | + SYN_EXT_BUTTON_STICK_M(hw->ext_buttons) << 2; + + serio_interrupt(priv->pt_port, + PSMOUSE_OOB_EXTRA_BTNS, SERIO_OOB_DATA); + serio_interrupt(priv->pt_port, pt_buttons, SERIO_OOB_DATA); + } +} + +static void synaptics_report_buttons(struct psmouse *psmouse, + const struct synaptics_hw_state *hw) +{ + struct input_dev *dev = psmouse->dev; + struct synaptics_data *priv = psmouse->private; + + input_report_key(dev, BTN_LEFT, hw->left); + input_report_key(dev, BTN_RIGHT, hw->right); + + if (SYN_CAP_MIDDLE_BUTTON(priv->info.capabilities)) + input_report_key(dev, BTN_MIDDLE, hw->middle); + + if (SYN_CAP_FOUR_BUTTON(priv->info.capabilities)) { + input_report_key(dev, BTN_FORWARD, hw->up); + input_report_key(dev, BTN_BACK, hw->down); + } + + synaptics_report_ext_buttons(psmouse, hw); +} + +static void synaptics_report_mt_data(struct psmouse *psmouse, + const struct synaptics_hw_state *sgm, + int num_fingers) +{ + struct input_dev *dev = psmouse->dev; + struct synaptics_data *priv = psmouse->private; + const struct synaptics_hw_state *hw[2] = { sgm, &priv->agm }; + struct input_mt_pos pos[2]; + int slot[2], nsemi, i; + + nsemi = clamp_val(num_fingers, 0, 2); + + for (i = 0; i < nsemi; i++) { + pos[i].x = hw[i]->x; + pos[i].y = synaptics_invert_y(hw[i]->y); + } + + input_mt_assign_slots(dev, slot, pos, nsemi, DMAX * priv->info.x_res); + + for (i = 0; i < nsemi; i++) { + input_mt_slot(dev, slot[i]); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, true); + input_report_abs(dev, ABS_MT_POSITION_X, pos[i].x); + input_report_abs(dev, ABS_MT_POSITION_Y, pos[i].y); + input_report_abs(dev, ABS_MT_PRESSURE, hw[i]->z); + } + + input_mt_drop_unused(dev); + + /* Don't use active slot count to generate BTN_TOOL events. */ + input_mt_report_pointer_emulation(dev, false); + + /* Send the number of fingers reported by touchpad itself. */ + input_mt_report_finger_count(dev, num_fingers); + + synaptics_report_buttons(psmouse, sgm); + + input_sync(dev); +} + +static void synaptics_image_sensor_process(struct psmouse *psmouse, + struct synaptics_hw_state *sgm) +{ + struct synaptics_data *priv = psmouse->private; + int num_fingers; + + /* + * Update mt_state using the new finger count and current mt_state. + */ + if (sgm->z == 0) + num_fingers = 0; + else if (sgm->w >= 4) + num_fingers = 1; + else if (sgm->w == 0) + num_fingers = 2; + else if (sgm->w == 1) + num_fingers = priv->agm_count ? priv->agm_count : 3; + else + num_fingers = 4; + + /* Send resulting input events to user space */ + synaptics_report_mt_data(psmouse, sgm, num_fingers); +} + +static bool synaptics_has_multifinger(struct synaptics_data *priv) +{ + if (SYN_CAP_MULTIFINGER(priv->info.capabilities)) + return true; + + /* Advanced gesture mode also sends multi finger data */ + return synaptics_has_agm(priv); +} + +/* + * called for each full received packet from the touchpad + */ +static void synaptics_process_packet(struct psmouse *psmouse) +{ + struct input_dev *dev = psmouse->dev; + struct synaptics_data *priv = psmouse->private; + struct synaptics_device_info *info = &priv->info; + struct synaptics_hw_state hw; + int num_fingers; + int finger_width; + + if (synaptics_parse_hw_state(psmouse->packet, priv, &hw)) + return; + + if (SYN_CAP_IMAGE_SENSOR(info->ext_cap_0c)) { + synaptics_image_sensor_process(psmouse, &hw); + return; + } + + if (hw.scroll) { + priv->scroll += hw.scroll; + + while (priv->scroll >= 4) { + input_report_key(dev, BTN_BACK, !hw.down); + input_sync(dev); + input_report_key(dev, BTN_BACK, hw.down); + input_sync(dev); + priv->scroll -= 4; + } + while (priv->scroll <= -4) { + input_report_key(dev, BTN_FORWARD, !hw.up); + input_sync(dev); + input_report_key(dev, BTN_FORWARD, hw.up); + input_sync(dev); + priv->scroll += 4; + } + return; + } + + if (hw.z > 0 && hw.x > 1) { + num_fingers = 1; + finger_width = 5; + if (SYN_CAP_EXTENDED(info->capabilities)) { + switch (hw.w) { + case 0 ... 1: + if (synaptics_has_multifinger(priv)) + num_fingers = hw.w + 2; + break; + case 2: + if (SYN_MODEL_PEN(info->model_id)) + ; /* Nothing, treat a pen as a single finger */ + break; + case 4 ... 15: + if (SYN_CAP_PALMDETECT(info->capabilities)) + finger_width = hw.w; + break; + } + } + } else { + num_fingers = 0; + finger_width = 0; + } + + if (cr48_profile_sensor) { + synaptics_report_mt_data(psmouse, &hw, num_fingers); + return; + } + + if (SYN_CAP_ADV_GESTURE(info->ext_cap_0c)) + synaptics_report_semi_mt_data(dev, &hw, &priv->agm, + num_fingers); + + /* Post events + * BTN_TOUCH has to be first as mousedev relies on it when doing + * absolute -> relative conversion + */ + if (hw.z > 30) input_report_key(dev, BTN_TOUCH, 1); + if (hw.z < 25) input_report_key(dev, BTN_TOUCH, 0); + + if (num_fingers > 0) { + input_report_abs(dev, ABS_X, hw.x); + input_report_abs(dev, ABS_Y, synaptics_invert_y(hw.y)); + } + input_report_abs(dev, ABS_PRESSURE, hw.z); + + if (SYN_CAP_PALMDETECT(info->capabilities)) + input_report_abs(dev, ABS_TOOL_WIDTH, finger_width); + + input_report_key(dev, BTN_TOOL_FINGER, num_fingers == 1); + if (synaptics_has_multifinger(priv)) { + input_report_key(dev, BTN_TOOL_DOUBLETAP, num_fingers == 2); + input_report_key(dev, BTN_TOOL_TRIPLETAP, num_fingers == 3); + } + + synaptics_report_buttons(psmouse, &hw); + + input_sync(dev); +} + +static bool synaptics_validate_byte(struct psmouse *psmouse, + int idx, enum synaptics_pkt_type pkt_type) +{ + static const u8 newabs_mask[] = { 0xC8, 0x00, 0x00, 0xC8, 0x00 }; + static const u8 newabs_rel_mask[] = { 0xC0, 0x00, 0x00, 0xC0, 0x00 }; + static const u8 newabs_rslt[] = { 0x80, 0x00, 0x00, 0xC0, 0x00 }; + static const u8 oldabs_mask[] = { 0xC0, 0x60, 0x00, 0xC0, 0x60 }; + static const u8 oldabs_rslt[] = { 0xC0, 0x00, 0x00, 0x80, 0x00 }; + const u8 *packet = psmouse->packet; + + if (idx < 0 || idx > 4) + return false; + + switch (pkt_type) { + + case SYN_NEWABS: + case SYN_NEWABS_RELAXED: + return (packet[idx] & newabs_rel_mask[idx]) == newabs_rslt[idx]; + + case SYN_NEWABS_STRICT: + return (packet[idx] & newabs_mask[idx]) == newabs_rslt[idx]; + + case SYN_OLDABS: + return (packet[idx] & oldabs_mask[idx]) == oldabs_rslt[idx]; + + default: + psmouse_err(psmouse, "unknown packet type %d\n", pkt_type); + return false; + } +} + +static enum synaptics_pkt_type +synaptics_detect_pkt_type(struct psmouse *psmouse) +{ + int i; + + for (i = 0; i < 5; i++) { + if (!synaptics_validate_byte(psmouse, i, SYN_NEWABS_STRICT)) { + psmouse_info(psmouse, "using relaxed packet validation\n"); + return SYN_NEWABS_RELAXED; + } + } + + return SYN_NEWABS_STRICT; +} + +static psmouse_ret_t synaptics_process_byte(struct psmouse *psmouse) +{ + struct synaptics_data *priv = psmouse->private; + + if (psmouse->pktcnt >= 6) { /* Full packet received */ + if (unlikely(priv->pkt_type == SYN_NEWABS)) + priv->pkt_type = synaptics_detect_pkt_type(psmouse); + + if (SYN_CAP_PASS_THROUGH(priv->info.capabilities) && + synaptics_is_pt_packet(psmouse->packet)) { + if (priv->pt_port) + synaptics_pass_pt_packet(priv->pt_port, + psmouse->packet); + } else + synaptics_process_packet(psmouse); + + return PSMOUSE_FULL_PACKET; + } + + return synaptics_validate_byte(psmouse, psmouse->pktcnt - 1, priv->pkt_type) ? + PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA; +} + +/***************************************************************************** + * Driver initialization/cleanup functions + ****************************************************************************/ +static void set_abs_position_params(struct input_dev *dev, + struct synaptics_device_info *info, + int x_code, int y_code) +{ + int x_min = info->x_min ?: XMIN_NOMINAL; + int x_max = info->x_max ?: XMAX_NOMINAL; + int y_min = info->y_min ?: YMIN_NOMINAL; + int y_max = info->y_max ?: YMAX_NOMINAL; + int fuzz = SYN_CAP_REDUCED_FILTERING(info->ext_cap_0c) ? + SYN_REDUCED_FILTER_FUZZ : 0; + + input_set_abs_params(dev, x_code, x_min, x_max, fuzz, 0); + input_set_abs_params(dev, y_code, y_min, y_max, fuzz, 0); + input_abs_set_res(dev, x_code, info->x_res); + input_abs_set_res(dev, y_code, info->y_res); +} + +static int set_input_params(struct psmouse *psmouse, + struct synaptics_data *priv) +{ + struct input_dev *dev = psmouse->dev; + struct synaptics_device_info *info = &priv->info; + int i; + int error; + + /* Reset default psmouse capabilities */ + __clear_bit(EV_REL, dev->evbit); + bitmap_zero(dev->relbit, REL_CNT); + bitmap_zero(dev->keybit, KEY_CNT); + + /* Things that apply to both modes */ + __set_bit(INPUT_PROP_POINTER, dev->propbit); + + input_set_capability(dev, EV_KEY, BTN_LEFT); + + /* Clickpads report only left button */ + if (!SYN_CAP_CLICKPAD(info->ext_cap_0c)) { + input_set_capability(dev, EV_KEY, BTN_RIGHT); + if (SYN_CAP_MIDDLE_BUTTON(info->capabilities)) + input_set_capability(dev, EV_KEY, BTN_MIDDLE); + } + + if (!priv->absolute_mode) { + /* Relative mode */ + input_set_capability(dev, EV_REL, REL_X); + input_set_capability(dev, EV_REL, REL_Y); + return 0; + } + + /* Absolute mode */ + set_abs_position_params(dev, &priv->info, ABS_X, ABS_Y); + input_set_abs_params(dev, ABS_PRESSURE, 0, 255, 0, 0); + + if (cr48_profile_sensor) + input_set_abs_params(dev, ABS_MT_PRESSURE, 0, 255, 0, 0); + + if (SYN_CAP_IMAGE_SENSOR(info->ext_cap_0c)) { + set_abs_position_params(dev, info, + ABS_MT_POSITION_X, ABS_MT_POSITION_Y); + /* Image sensors can report per-contact pressure */ + input_set_abs_params(dev, ABS_MT_PRESSURE, 0, 255, 0, 0); + + error = input_mt_init_slots(dev, 2, + INPUT_MT_POINTER | INPUT_MT_TRACK); + if (error) + return error; + + /* Image sensors can signal 4 and 5 finger clicks */ + input_set_capability(dev, EV_KEY, BTN_TOOL_QUADTAP); + input_set_capability(dev, EV_KEY, BTN_TOOL_QUINTTAP); + } else if (SYN_CAP_ADV_GESTURE(info->ext_cap_0c)) { + set_abs_position_params(dev, info, + ABS_MT_POSITION_X, ABS_MT_POSITION_Y); + /* + * Profile sensor in CR-48 tracks contacts reasonably well, + * other non-image sensors with AGM use semi-mt. + */ + error = input_mt_init_slots(dev, 2, + INPUT_MT_POINTER | + (cr48_profile_sensor ? + INPUT_MT_TRACK : + INPUT_MT_SEMI_MT)); + if (error) + return error; + + /* + * For semi-mt devices we send ABS_X/Y ourselves instead of + * input_mt_report_pointer_emulation. But + * input_mt_init_slots() resets the fuzz to 0, leading to a + * filtered ABS_MT_POSITION_X but an unfiltered ABS_X + * position. Let's re-initialize ABS_X/Y here. + */ + if (!cr48_profile_sensor) + set_abs_position_params(dev, &priv->info, ABS_X, ABS_Y); + } + + if (SYN_CAP_PALMDETECT(info->capabilities)) + input_set_abs_params(dev, ABS_TOOL_WIDTH, 0, 15, 0, 0); + + input_set_capability(dev, EV_KEY, BTN_TOUCH); + input_set_capability(dev, EV_KEY, BTN_TOOL_FINGER); + + if (synaptics_has_multifinger(priv)) { + input_set_capability(dev, EV_KEY, BTN_TOOL_DOUBLETAP); + input_set_capability(dev, EV_KEY, BTN_TOOL_TRIPLETAP); + } + + if (SYN_CAP_FOUR_BUTTON(info->capabilities) || + SYN_CAP_MIDDLE_BUTTON(info->capabilities)) { + input_set_capability(dev, EV_KEY, BTN_FORWARD); + input_set_capability(dev, EV_KEY, BTN_BACK); + } + + if (!SYN_CAP_EXT_BUTTONS_STICK(info->ext_cap_10)) + for (i = 0; i < SYN_CAP_MULTI_BUTTON_NO(info->ext_cap); i++) + input_set_capability(dev, EV_KEY, BTN_0 + i); + + if (SYN_CAP_CLICKPAD(info->ext_cap_0c)) { + __set_bit(INPUT_PROP_BUTTONPAD, dev->propbit); + if (psmouse_matches_pnp_id(psmouse, topbuttonpad_pnp_ids) && + !SYN_CAP_EXT_BUTTONS_STICK(info->ext_cap_10)) + __set_bit(INPUT_PROP_TOPBUTTONPAD, dev->propbit); + } + + return 0; +} + +static ssize_t synaptics_show_disable_gesture(struct psmouse *psmouse, + void *data, char *buf) +{ + struct synaptics_data *priv = psmouse->private; + + return sprintf(buf, "%c\n", priv->disable_gesture ? '1' : '0'); +} + +static ssize_t synaptics_set_disable_gesture(struct psmouse *psmouse, + void *data, const char *buf, + size_t len) +{ + struct synaptics_data *priv = psmouse->private; + unsigned int value; + int err; + + err = kstrtouint(buf, 10, &value); + if (err) + return err; + + if (value > 1) + return -EINVAL; + + if (value == priv->disable_gesture) + return len; + + priv->disable_gesture = value; + if (value) + priv->mode |= SYN_BIT_DISABLE_GESTURE; + else + priv->mode &= ~SYN_BIT_DISABLE_GESTURE; + + if (synaptics_mode_cmd(psmouse, priv->mode)) + return -EIO; + + return len; +} + +PSMOUSE_DEFINE_ATTR(disable_gesture, S_IWUSR | S_IRUGO, NULL, + synaptics_show_disable_gesture, + synaptics_set_disable_gesture); + +static void synaptics_disconnect(struct psmouse *psmouse) +{ + struct synaptics_data *priv = psmouse->private; + + /* + * We might have left a breadcrumb when trying to + * set up SMbus companion. + */ + psmouse_smbus_cleanup(psmouse); + + if (!priv->absolute_mode && + SYN_ID_DISGEST_SUPPORTED(priv->info.identity)) + device_remove_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_disable_gesture.dattr); + + synaptics_reset(psmouse); + kfree(priv); + psmouse->private = NULL; +} + +static int synaptics_reconnect(struct psmouse *psmouse) +{ + struct synaptics_data *priv = psmouse->private; + struct synaptics_device_info info; + u8 param[2]; + int retry = 0; + int error; + + do { + psmouse_reset(psmouse); + if (retry) { + /* + * On some boxes, right after resuming, the touchpad + * needs some time to finish initializing (I assume + * it needs time to calibrate) and start responding + * to Synaptics-specific queries, so let's wait a + * bit. + */ + ssleep(1); + } + ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETID); + error = synaptics_detect(psmouse, 0); + } while (error && ++retry < 3); + + if (error) + return error; + + if (retry > 1) + psmouse_dbg(psmouse, "reconnected after %d tries\n", retry); + + error = synaptics_query_hardware(psmouse, &info); + if (error) { + psmouse_err(psmouse, "Unable to query device.\n"); + return error; + } + + error = synaptics_set_mode(psmouse); + if (error) { + psmouse_err(psmouse, "Unable to initialize device.\n"); + return error; + } + + if (info.identity != priv->info.identity || + info.model_id != priv->info.model_id || + info.capabilities != priv->info.capabilities || + info.ext_cap != priv->info.ext_cap) { + psmouse_err(psmouse, + "hardware appears to be different: id(%u-%u), model(%u-%u), caps(%x-%x), ext(%x-%x).\n", + priv->info.identity, info.identity, + priv->info.model_id, info.model_id, + priv->info.capabilities, info.capabilities, + priv->info.ext_cap, info.ext_cap); + return -ENXIO; + } + + return 0; +} + +static bool impaired_toshiba_kbc; + +static const struct dmi_system_id toshiba_dmi_table[] __initconst = { +#if defined(CONFIG_DMI) && defined(CONFIG_X86) + { + /* Toshiba Satellite */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_MATCH(DMI_PRODUCT_NAME, "Satellite"), + }, + }, + { + /* Toshiba Dynabook */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_MATCH(DMI_PRODUCT_NAME, "dynabook"), + }, + }, + { + /* Toshiba Portege M300 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_MATCH(DMI_PRODUCT_NAME, "PORTEGE M300"), + }, + + }, + { + /* Toshiba Portege M300 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_MATCH(DMI_PRODUCT_NAME, "Portable PC"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Version 1.0"), + }, + + }, +#endif + { } +}; + +static bool broken_olpc_ec; + +static const struct dmi_system_id olpc_dmi_table[] __initconst = { +#if defined(CONFIG_DMI) && defined(CONFIG_OLPC) + { + /* OLPC XO-1 or XO-1.5 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "OLPC"), + DMI_MATCH(DMI_PRODUCT_NAME, "XO"), + }, + }, +#endif + { } +}; + +static const struct dmi_system_id __initconst cr48_dmi_table[] = { +#if defined(CONFIG_DMI) && defined(CONFIG_X86) + { + /* Cr-48 Chromebook (Codename Mario) */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "IEC"), + DMI_MATCH(DMI_PRODUCT_NAME, "Mario"), + }, + }, +#endif + { } +}; + +void __init synaptics_module_init(void) +{ + impaired_toshiba_kbc = dmi_check_system(toshiba_dmi_table); + broken_olpc_ec = dmi_check_system(olpc_dmi_table); + cr48_profile_sensor = dmi_check_system(cr48_dmi_table); +} + +static int synaptics_init_ps2(struct psmouse *psmouse, + struct synaptics_device_info *info, + bool absolute_mode) +{ + struct synaptics_data *priv; + int err; + + synaptics_apply_quirks(psmouse, info); + + psmouse->private = priv = kzalloc(sizeof(struct synaptics_data), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->info = *info; + priv->absolute_mode = absolute_mode; + if (SYN_ID_DISGEST_SUPPORTED(info->identity)) + priv->disable_gesture = true; + + /* + * Unfortunately ForcePad capability is not exported over PS/2, + * so we have to resort to checking PNP IDs. + */ + priv->is_forcepad = psmouse_matches_pnp_id(psmouse, forcepad_pnp_ids); + + err = synaptics_set_mode(psmouse); + if (err) { + psmouse_err(psmouse, "Unable to initialize device.\n"); + goto init_fail; + } + + priv->pkt_type = SYN_MODEL_NEWABS(info->model_id) ? + SYN_NEWABS : SYN_OLDABS; + + psmouse_info(psmouse, + "Touchpad model: %lu, fw: %lu.%lu, id: %#x, caps: %#x/%#x/%#x/%#x, board id: %u, fw id: %u\n", + SYN_ID_MODEL(info->identity), + SYN_ID_MAJOR(info->identity), SYN_ID_MINOR(info->identity), + info->model_id, + info->capabilities, info->ext_cap, info->ext_cap_0c, + info->ext_cap_10, info->board_id, info->firmware_id); + + err = set_input_params(psmouse, priv); + if (err) { + psmouse_err(psmouse, + "failed to set up capabilities: %d\n", err); + goto init_fail; + } + + /* + * Encode touchpad model so that it can be used to set + * input device->id.version and be visible to userspace. + * Because version is __u16 we have to drop something. + * Hardware info bits seem to be good candidates as they + * are documented to be for Synaptics corp. internal use. + */ + psmouse->model = ((info->model_id & 0x00ff0000) >> 8) | + (info->model_id & 0x000000ff); + + if (absolute_mode) { + psmouse->protocol_handler = synaptics_process_byte; + psmouse->pktsize = 6; + } else { + /* Relative mode follows standard PS/2 mouse protocol */ + psmouse->protocol_handler = psmouse_process_byte; + psmouse->pktsize = 3; + } + + psmouse->set_rate = synaptics_set_rate; + psmouse->disconnect = synaptics_disconnect; + psmouse->reconnect = synaptics_reconnect; + psmouse->cleanup = synaptics_reset; + /* Synaptics can usually stay in sync without extra help */ + psmouse->resync_time = 0; + + if (SYN_CAP_PASS_THROUGH(info->capabilities)) + synaptics_pt_create(psmouse); + + /* + * Toshiba's KBC seems to have trouble handling data from + * Synaptics at full rate. Switch to a lower rate (roughly + * the same rate as a standard PS/2 mouse). + */ + if (psmouse->rate >= 80 && impaired_toshiba_kbc) { + psmouse_info(psmouse, + "Toshiba %s detected, limiting rate to 40pps.\n", + dmi_get_system_info(DMI_PRODUCT_NAME)); + psmouse->rate = 40; + } + + if (!priv->absolute_mode && SYN_ID_DISGEST_SUPPORTED(info->identity)) { + err = device_create_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_disable_gesture.dattr); + if (err) { + psmouse_err(psmouse, + "Failed to create disable_gesture attribute (%d)", + err); + goto init_fail; + } + } + + return 0; + + init_fail: + kfree(priv); + return err; +} + +static int __synaptics_init(struct psmouse *psmouse, bool absolute_mode) +{ + struct synaptics_device_info info; + int error; + + psmouse_reset(psmouse); + + error = synaptics_query_hardware(psmouse, &info); + if (error) { + psmouse_err(psmouse, "Unable to query device: %d\n", error); + return error; + } + + return synaptics_init_ps2(psmouse, &info, absolute_mode); +} + +int synaptics_init_absolute(struct psmouse *psmouse) +{ + return __synaptics_init(psmouse, true); +} + +int synaptics_init_relative(struct psmouse *psmouse) +{ + return __synaptics_init(psmouse, false); +} + +static int synaptics_setup_ps2(struct psmouse *psmouse, + struct synaptics_device_info *info) +{ + bool absolute_mode = true; + int error; + + /* + * The OLPC XO has issues with Synaptics' absolute mode; the constant + * packet spew overloads the EC such that key presses on the keyboard + * are missed. Given that, don't even attempt to use Absolute mode. + * Relative mode seems to work just fine. + */ + if (broken_olpc_ec) { + psmouse_info(psmouse, + "OLPC XO detected, forcing relative protocol.\n"); + absolute_mode = false; + } + + error = synaptics_init_ps2(psmouse, info, absolute_mode); + if (error) + return error; + + return absolute_mode ? PSMOUSE_SYNAPTICS : PSMOUSE_SYNAPTICS_RELATIVE; +} + +#else /* CONFIG_MOUSE_PS2_SYNAPTICS */ + +void __init synaptics_module_init(void) +{ +} + +static int __maybe_unused +synaptics_setup_ps2(struct psmouse *psmouse, + struct synaptics_device_info *info) +{ + return -ENOSYS; +} + +#endif /* CONFIG_MOUSE_PS2_SYNAPTICS */ + +#ifdef CONFIG_MOUSE_PS2_SYNAPTICS_SMBUS + +/* + * The newest Synaptics device can use a secondary bus (called InterTouch) which + * provides a better bandwidth and allow a better control of the touchpads. + * This is used to decide if we need to use this bus or not. + */ +enum { + SYNAPTICS_INTERTOUCH_NOT_SET = -1, + SYNAPTICS_INTERTOUCH_OFF, + SYNAPTICS_INTERTOUCH_ON, +}; + +static int synaptics_intertouch = IS_ENABLED(CONFIG_RMI4_SMB) ? + SYNAPTICS_INTERTOUCH_NOT_SET : SYNAPTICS_INTERTOUCH_OFF; +module_param_named(synaptics_intertouch, synaptics_intertouch, int, 0644); +MODULE_PARM_DESC(synaptics_intertouch, "Use a secondary bus for the Synaptics device."); + +static int synaptics_create_intertouch(struct psmouse *psmouse, + struct synaptics_device_info *info, + bool leave_breadcrumbs) +{ + bool topbuttonpad = + psmouse_matches_pnp_id(psmouse, topbuttonpad_pnp_ids) && + !SYN_CAP_EXT_BUTTONS_STICK(info->ext_cap_10); + const struct rmi_device_platform_data pdata = { + .sensor_pdata = { + .sensor_type = rmi_sensor_touchpad, + .axis_align.flip_y = true, + .kernel_tracking = false, + .topbuttonpad = topbuttonpad, + }, + .f30_data = { + .buttonpad = SYN_CAP_CLICKPAD(info->ext_cap_0c), + .trackstick_buttons = + !!SYN_CAP_EXT_BUTTONS_STICK(info->ext_cap_10), + }, + }; + const struct i2c_board_info intertouch_board = { + I2C_BOARD_INFO("rmi4_smbus", 0x2c), + .flags = I2C_CLIENT_HOST_NOTIFY, + }; + + return psmouse_smbus_init(psmouse, &intertouch_board, + &pdata, sizeof(pdata), true, + leave_breadcrumbs); +} + +/** + * synaptics_setup_intertouch - called once the PS/2 devices are enumerated + * and decides to instantiate a SMBus InterTouch device. + */ +static int synaptics_setup_intertouch(struct psmouse *psmouse, + struct synaptics_device_info *info, + bool leave_breadcrumbs) +{ + int error; + + if (synaptics_intertouch == SYNAPTICS_INTERTOUCH_OFF) + return -ENXIO; + + if (synaptics_intertouch == SYNAPTICS_INTERTOUCH_NOT_SET) { + if (!psmouse_matches_pnp_id(psmouse, topbuttonpad_pnp_ids) && + !psmouse_matches_pnp_id(psmouse, smbus_pnp_ids)) { + + if (!psmouse_matches_pnp_id(psmouse, forcepad_pnp_ids)) + psmouse_info(psmouse, + "Your touchpad (%s) says it can support a different bus. " + "If i2c-hid and hid-rmi are not used, you might want to try setting psmouse.synaptics_intertouch to 1 and report this to linux-input@vger.kernel.org.\n", + psmouse->ps2dev.serio->firmware_id); + + return -ENXIO; + } + } + + psmouse_info(psmouse, "Trying to set up SMBus access\n"); + + error = synaptics_create_intertouch(psmouse, info, leave_breadcrumbs); + if (error) { + if (error == -EAGAIN) + psmouse_info(psmouse, "SMbus companion is not ready yet\n"); + else + psmouse_err(psmouse, "unable to create intertouch device\n"); + + return error; + } + + return 0; +} + +int synaptics_init_smbus(struct psmouse *psmouse) +{ + struct synaptics_device_info info; + int error; + + psmouse_reset(psmouse); + + error = synaptics_query_hardware(psmouse, &info); + if (error) { + psmouse_err(psmouse, "Unable to query device: %d\n", error); + return error; + } + + if (!SYN_CAP_INTERTOUCH(info.ext_cap_0c)) + return -ENXIO; + + return synaptics_create_intertouch(psmouse, &info, false); +} + +#else /* CONFIG_MOUSE_PS2_SYNAPTICS_SMBUS */ + +static int __maybe_unused +synaptics_setup_intertouch(struct psmouse *psmouse, + struct synaptics_device_info *info, + bool leave_breadcrumbs) +{ + return -ENOSYS; +} + +int synaptics_init_smbus(struct psmouse *psmouse) +{ + return -ENOSYS; +} + +#endif /* CONFIG_MOUSE_PS2_SYNAPTICS_SMBUS */ + +#if defined(CONFIG_MOUSE_PS2_SYNAPTICS) || \ + defined(CONFIG_MOUSE_PS2_SYNAPTICS_SMBUS) + +int synaptics_init(struct psmouse *psmouse) +{ + struct synaptics_device_info info; + int error; + int retval; + + psmouse_reset(psmouse); + + error = synaptics_query_hardware(psmouse, &info); + if (error) { + psmouse_err(psmouse, "Unable to query device: %d\n", error); + return error; + } + + if (SYN_CAP_INTERTOUCH(info.ext_cap_0c)) { + if ((!IS_ENABLED(CONFIG_RMI4_SMB) || + !IS_ENABLED(CONFIG_MOUSE_PS2_SYNAPTICS_SMBUS)) && + /* Forcepads need F21, which is not ready */ + !psmouse_matches_pnp_id(psmouse, forcepad_pnp_ids)) { + psmouse_warn(psmouse, + "The touchpad can support a better bus than the too old PS/2 protocol. " + "Make sure MOUSE_PS2_SYNAPTICS_SMBUS and RMI4_SMB are enabled to get a better touchpad experience.\n"); + } + + error = synaptics_setup_intertouch(psmouse, &info, true); + if (!error) + return PSMOUSE_SYNAPTICS_SMBUS; + } + + retval = synaptics_setup_ps2(psmouse, &info); + if (retval < 0) { + /* + * Not using any flavor of Synaptics support, so clean up + * SMbus breadcrumbs, if any. + */ + psmouse_smbus_cleanup(psmouse); + } + + return retval; +} + +#else /* CONFIG_MOUSE_PS2_SYNAPTICS || CONFIG_MOUSE_PS2_SYNAPTICS_SMBUS */ + +int synaptics_init(struct psmouse *psmouse) +{ + return -ENOSYS; +} + +#endif /* CONFIG_MOUSE_PS2_SYNAPTICS || CONFIG_MOUSE_PS2_SYNAPTICS_SMBUS */ diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h new file mode 100644 index 000000000..fc00e005c --- /dev/null +++ b/drivers/input/mouse/synaptics.h @@ -0,0 +1,217 @@ +/* + * Synaptics TouchPad PS/2 mouse driver + * + * 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. + */ + +#ifndef _SYNAPTICS_H +#define _SYNAPTICS_H + +/* synaptics queries */ +#define SYN_QUE_IDENTIFY 0x00 +#define SYN_QUE_MODES 0x01 +#define SYN_QUE_CAPABILITIES 0x02 +#define SYN_QUE_MODEL 0x03 +#define SYN_QUE_SERIAL_NUMBER_PREFIX 0x06 +#define SYN_QUE_SERIAL_NUMBER_SUFFIX 0x07 +#define SYN_QUE_RESOLUTION 0x08 +#define SYN_QUE_EXT_CAPAB 0x09 +#define SYN_QUE_FIRMWARE_ID 0x0a +#define SYN_QUE_EXT_CAPAB_0C 0x0c +#define SYN_QUE_EXT_MAX_COORDS 0x0d +#define SYN_QUE_EXT_MIN_COORDS 0x0f +#define SYN_QUE_MEXT_CAPAB_10 0x10 + +/* synatics modes */ +#define SYN_BIT_ABSOLUTE_MODE BIT(7) +#define SYN_BIT_HIGH_RATE BIT(6) +#define SYN_BIT_SLEEP_MODE BIT(3) +#define SYN_BIT_DISABLE_GESTURE BIT(2) +#define SYN_BIT_FOUR_BYTE_CLIENT BIT(1) +#define SYN_BIT_W_MODE BIT(0) + +/* synaptics model ID bits */ +#define SYN_MODEL_ROT180(m) ((m) & BIT(23)) +#define SYN_MODEL_PORTRAIT(m) ((m) & BIT(22)) +#define SYN_MODEL_SENSOR(m) (((m) & GENMASK(21, 16)) >> 16) +#define SYN_MODEL_HARDWARE(m) (((m) & GENMASK(15, 9)) >> 9) +#define SYN_MODEL_NEWABS(m) ((m) & BIT(7)) +#define SYN_MODEL_PEN(m) ((m) & BIT(6)) +#define SYN_MODEL_SIMPLIC(m) ((m) & BIT(5)) +#define SYN_MODEL_GEOMETRY(m) ((m) & GENMASK(3, 0)) + +/* synaptics capability bits */ +#define SYN_CAP_EXTENDED(c) ((c) & BIT(23)) +#define SYN_CAP_MIDDLE_BUTTON(c) ((c) & BIT(18)) +#define SYN_CAP_PASS_THROUGH(c) ((c) & BIT(7)) +#define SYN_CAP_SLEEP(c) ((c) & BIT(4)) +#define SYN_CAP_FOUR_BUTTON(c) ((c) & BIT(3)) +#define SYN_CAP_MULTIFINGER(c) ((c) & BIT(1)) +#define SYN_CAP_PALMDETECT(c) ((c) & BIT(0)) +#define SYN_CAP_SUBMODEL_ID(c) (((c) & GENMASK(15, 8)) >> 8) +#define SYN_EXT_CAP_REQUESTS(c) (((c) & GENMASK(22, 20)) >> 20) +#define SYN_CAP_MB_MASK GENMASK(15, 12) +#define SYN_CAP_MULTI_BUTTON_NO(ec) (((ec) & SYN_CAP_MB_MASK) >> 12) +#define SYN_CAP_PRODUCT_ID(ec) (((ec) & GENMASK(23, 16)) >> 16) +#define SYN_MEXT_CAP_BIT(m) ((m) & BIT(1)) + +/* + * The following describes response for the 0x0c query. + * + * byte mask name meaning + * ---- ---- ------- ------------ + * 1 0x01 adjustable threshold capacitive button sensitivity + * can be adjusted + * 1 0x02 report max query 0x0d gives max coord reported + * 1 0x04 clearpad sensor is ClearPad product + * 1 0x08 advanced gesture not particularly meaningful + * 1 0x10 clickpad bit 0 1-button ClickPad + * 1 0x60 multifinger mode identifies firmware finger counting + * (not reporting!) algorithm. + * Not particularly meaningful + * 1 0x80 covered pad W clipped to 14, 15 == pad mostly covered + * 2 0x01 clickpad bit 1 2-button ClickPad + * 2 0x02 deluxe LED controls touchpad support LED commands + * ala multimedia control bar + * 2 0x04 reduced filtering firmware does less filtering on + * position data, driver should watch + * for noise. + * 2 0x08 image sensor image sensor tracks 5 fingers, but only + * reports 2. + * 2 0x01 uniform clickpad whole clickpad moves instead of being + * hinged at the top. + * 2 0x20 report min query 0x0f gives min coord reported + */ +#define SYN_CAP_CLICKPAD(ex0c) ((ex0c) & BIT(20)) /* 1-button ClickPad */ +#define SYN_CAP_CLICKPAD2BTN(ex0c) ((ex0c) & BIT(8)) /* 2-button ClickPad */ +#define SYN_CAP_MAX_DIMENSIONS(ex0c) ((ex0c) & BIT(17)) +#define SYN_CAP_MIN_DIMENSIONS(ex0c) ((ex0c) & BIT(13)) +#define SYN_CAP_ADV_GESTURE(ex0c) ((ex0c) & BIT(19)) +#define SYN_CAP_REDUCED_FILTERING(ex0c) ((ex0c) & BIT(10)) +#define SYN_CAP_IMAGE_SENSOR(ex0c) ((ex0c) & BIT(11)) +#define SYN_CAP_INTERTOUCH(ex0c) ((ex0c) & BIT(14)) + +/* + * The following descibes response for the 0x10 query. + * + * byte mask name meaning + * ---- ---- ------- ------------ + * 1 0x01 ext buttons are stick buttons exported in the extended + * capability are actually meant to be used + * by the tracktick (pass-through). + * 1 0x02 SecurePad the touchpad is a SecurePad, so it + * contains a built-in fingerprint reader. + * 1 0xe0 more ext count how many more extented queries are + * available after this one. + * 2 0xff SecurePad width the width of the SecurePad fingerprint + * reader. + * 3 0xff SecurePad height the height of the SecurePad fingerprint + * reader. + */ +#define SYN_CAP_EXT_BUTTONS_STICK(ex10) ((ex10) & BIT(16)) +#define SYN_CAP_SECUREPAD(ex10) ((ex10) & BIT(17)) + +#define SYN_EXT_BUTTON_STICK_L(eb) (((eb) & BIT(0)) >> 0) +#define SYN_EXT_BUTTON_STICK_M(eb) (((eb) & BIT(1)) >> 1) +#define SYN_EXT_BUTTON_STICK_R(eb) (((eb) & BIT(2)) >> 2) + +/* synaptics modes query bits */ +#define SYN_MODE_ABSOLUTE(m) ((m) & BIT(7)) +#define SYN_MODE_RATE(m) ((m) & BIT(6)) +#define SYN_MODE_BAUD_SLEEP(m) ((m) & BIT(3)) +#define SYN_MODE_DISABLE_GESTURE(m) ((m) & BIT(2)) +#define SYN_MODE_PACKSIZE(m) ((m) & BIT(1)) +#define SYN_MODE_WMODE(m) ((m) & BIT(0)) + +/* synaptics identify query bits */ +#define SYN_ID_MODEL(i) (((i) & GENMASK(7, 4)) >> 4) +#define SYN_ID_MAJOR(i) (((i) & GENMASK(3, 0)) >> 0) +#define SYN_ID_MINOR(i) (((i) & GENMASK(23, 16)) >> 16) +#define SYN_ID_FULL(i) ((SYN_ID_MAJOR(i) << 8) | SYN_ID_MINOR(i)) +#define SYN_ID_IS_SYNAPTICS(i) (((i) & GENMASK(15, 8)) == 0x004700U) +#define SYN_ID_DISGEST_SUPPORTED(i) (SYN_ID_MAJOR(i) >= 4) + +/* synaptics special commands */ +#define SYN_PS_SET_MODE2 0x14 +#define SYN_PS_CLIENT_CMD 0x28 + +/* amount to fuzz position data when touchpad reports reduced filtering */ +#define SYN_REDUCED_FILTER_FUZZ 8 + +/* synaptics packet types */ +enum synaptics_pkt_type { + SYN_NEWABS, + SYN_NEWABS_STRICT, + SYN_NEWABS_RELAXED, + SYN_OLDABS, +}; + +/* + * A structure to describe the state of the touchpad hardware (buttons and pad) + */ +struct synaptics_hw_state { + int x; + int y; + int z; + int w; + unsigned int left:1; + unsigned int right:1; + unsigned int middle:1; + unsigned int up:1; + unsigned int down:1; + u8 ext_buttons; + s8 scroll; +}; + +/* Data read from the touchpad */ +struct synaptics_device_info { + u32 model_id; /* Model-ID */ + u32 firmware_id; /* Firmware-ID */ + u32 board_id; /* Board-ID */ + u32 capabilities; /* Capabilities */ + u32 ext_cap; /* Extended Capabilities */ + u32 ext_cap_0c; /* Ext Caps from 0x0c query */ + u32 ext_cap_10; /* Ext Caps from 0x10 query */ + u32 identity; /* Identification */ + u32 x_res, y_res; /* X/Y resolution in units/mm */ + u32 x_max, y_max; /* Max coordinates (from FW) */ + u32 x_min, y_min; /* Min coordinates (from FW) */ +}; + +struct synaptics_data { + struct synaptics_device_info info; + + enum synaptics_pkt_type pkt_type; /* packet type - old, new, etc */ + u8 mode; /* current mode byte */ + int scroll; + + bool absolute_mode; /* run in Absolute mode */ + bool disable_gesture; /* disable gestures */ + + struct serio *pt_port; /* Pass-through serio port */ + + /* + * Last received Advanced Gesture Mode (AGM) packet. An AGM packet + * contains position data for a second contact, at half resolution. + */ + struct synaptics_hw_state agm; + unsigned int agm_count; /* finger count reported by agm */ + + /* ForcePad handling */ + unsigned long press_start; + bool press; + bool report_press; + bool is_forcepad; +}; + +void synaptics_module_init(void); +int synaptics_detect(struct psmouse *psmouse, bool set_properties); +int synaptics_init_absolute(struct psmouse *psmouse); +int synaptics_init_relative(struct psmouse *psmouse); +int synaptics_init_smbus(struct psmouse *psmouse); +int synaptics_init(struct psmouse *psmouse); +void synaptics_reset(struct psmouse *psmouse); + +#endif /* _SYNAPTICS_H */ diff --git a/drivers/input/mouse/synaptics_i2c.c b/drivers/input/mouse/synaptics_i2c.c new file mode 100644 index 000000000..8538318d3 --- /dev/null +++ b/drivers/input/mouse/synaptics_i2c.c @@ -0,0 +1,681 @@ +/* + * Synaptics touchpad with I2C interface + * + * Copyright (C) 2009 Compulab, Ltd. + * Mike Rapoport <mike@compulab.co.il> + * Igor Grinberg <grinberg@compulab.co.il> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/slab.h> +#include <linux/pm.h> + +#define DRIVER_NAME "synaptics_i2c" +/* maximum product id is 15 characters */ +#define PRODUCT_ID_LENGTH 15 +#define REGISTER_LENGTH 8 + +/* + * after soft reset, we should wait for 1 ms + * before the device becomes operational + */ +#define SOFT_RESET_DELAY_US 3000 +/* and after hard reset, we should wait for max 500ms */ +#define HARD_RESET_DELAY_MS 500 + +/* Registers by SMBus address */ +#define PAGE_SEL_REG 0xff +#define DEVICE_STATUS_REG 0x09 + +/* Registers by RMI address */ +#define DEV_CONTROL_REG 0x0000 +#define INTERRUPT_EN_REG 0x0001 +#define ERR_STAT_REG 0x0002 +#define INT_REQ_STAT_REG 0x0003 +#define DEV_COMMAND_REG 0x0004 + +#define RMI_PROT_VER_REG 0x0200 +#define MANUFACT_ID_REG 0x0201 +#define PHYS_INT_VER_REG 0x0202 +#define PROD_PROPERTY_REG 0x0203 +#define INFO_QUERY_REG0 0x0204 +#define INFO_QUERY_REG1 (INFO_QUERY_REG0 + 1) +#define INFO_QUERY_REG2 (INFO_QUERY_REG0 + 2) +#define INFO_QUERY_REG3 (INFO_QUERY_REG0 + 3) + +#define PRODUCT_ID_REG0 0x0210 +#define PRODUCT_ID_REG1 (PRODUCT_ID_REG0 + 1) +#define PRODUCT_ID_REG2 (PRODUCT_ID_REG0 + 2) +#define PRODUCT_ID_REG3 (PRODUCT_ID_REG0 + 3) +#define PRODUCT_ID_REG4 (PRODUCT_ID_REG0 + 4) +#define PRODUCT_ID_REG5 (PRODUCT_ID_REG0 + 5) +#define PRODUCT_ID_REG6 (PRODUCT_ID_REG0 + 6) +#define PRODUCT_ID_REG7 (PRODUCT_ID_REG0 + 7) +#define PRODUCT_ID_REG8 (PRODUCT_ID_REG0 + 8) +#define PRODUCT_ID_REG9 (PRODUCT_ID_REG0 + 9) +#define PRODUCT_ID_REG10 (PRODUCT_ID_REG0 + 10) +#define PRODUCT_ID_REG11 (PRODUCT_ID_REG0 + 11) +#define PRODUCT_ID_REG12 (PRODUCT_ID_REG0 + 12) +#define PRODUCT_ID_REG13 (PRODUCT_ID_REG0 + 13) +#define PRODUCT_ID_REG14 (PRODUCT_ID_REG0 + 14) +#define PRODUCT_ID_REG15 (PRODUCT_ID_REG0 + 15) + +#define DATA_REG0 0x0400 +#define ABS_PRESSURE_REG 0x0401 +#define ABS_MSB_X_REG 0x0402 +#define ABS_LSB_X_REG (ABS_MSB_X_REG + 1) +#define ABS_MSB_Y_REG 0x0404 +#define ABS_LSB_Y_REG (ABS_MSB_Y_REG + 1) +#define REL_X_REG 0x0406 +#define REL_Y_REG 0x0407 + +#define DEV_QUERY_REG0 0x1000 +#define DEV_QUERY_REG1 (DEV_QUERY_REG0 + 1) +#define DEV_QUERY_REG2 (DEV_QUERY_REG0 + 2) +#define DEV_QUERY_REG3 (DEV_QUERY_REG0 + 3) +#define DEV_QUERY_REG4 (DEV_QUERY_REG0 + 4) +#define DEV_QUERY_REG5 (DEV_QUERY_REG0 + 5) +#define DEV_QUERY_REG6 (DEV_QUERY_REG0 + 6) +#define DEV_QUERY_REG7 (DEV_QUERY_REG0 + 7) +#define DEV_QUERY_REG8 (DEV_QUERY_REG0 + 8) + +#define GENERAL_2D_CONTROL_REG 0x1041 +#define SENSOR_SENSITIVITY_REG 0x1044 +#define SENS_MAX_POS_MSB_REG 0x1046 +#define SENS_MAX_POS_LSB_REG (SENS_MAX_POS_UPPER_REG + 1) + +/* Register bits */ +/* Device Control Register Bits */ +#define REPORT_RATE_1ST_BIT 6 + +/* Interrupt Enable Register Bits (INTERRUPT_EN_REG) */ +#define F10_ABS_INT_ENA 0 +#define F10_REL_INT_ENA 1 +#define F20_INT_ENA 2 + +/* Interrupt Request Register Bits (INT_REQ_STAT_REG | DEVICE_STATUS_REG) */ +#define F10_ABS_INT_REQ 0 +#define F10_REL_INT_REQ 1 +#define F20_INT_REQ 2 +/* Device Status Register Bits (DEVICE_STATUS_REG) */ +#define STAT_CONFIGURED 6 +#define STAT_ERROR 7 + +/* Device Command Register Bits (DEV_COMMAND_REG) */ +#define RESET_COMMAND 0x01 +#define REZERO_COMMAND 0x02 + +/* Data Register 0 Bits (DATA_REG0) */ +#define GESTURE 3 + +/* Device Query Registers Bits */ +/* DEV_QUERY_REG3 */ +#define HAS_PALM_DETECT 1 +#define HAS_MULTI_FING 2 +#define HAS_SCROLLER 4 +#define HAS_2D_SCROLL 5 + +/* General 2D Control Register Bits (GENERAL_2D_CONTROL_REG) */ +#define NO_DECELERATION 1 +#define REDUCE_REPORTING 3 +#define NO_FILTER 5 + +/* Function Masks */ +/* Device Control Register Masks (DEV_CONTROL_REG) */ +#define REPORT_RATE_MSK 0xc0 +#define SLEEP_MODE_MSK 0x07 + +/* Device Sleep Modes */ +#define FULL_AWAKE 0x0 +#define NORMAL_OP 0x1 +#define LOW_PWR_OP 0x2 +#define VERY_LOW_PWR_OP 0x3 +#define SENS_SLEEP 0x4 +#define SLEEP_MOD 0x5 +#define DEEP_SLEEP 0x6 +#define HIBERNATE 0x7 + +/* Interrupt Register Mask */ +/* (INT_REQ_STAT_REG | DEVICE_STATUS_REG | INTERRUPT_EN_REG) */ +#define INT_ENA_REQ_MSK 0x07 +#define INT_ENA_ABS_MSK 0x01 +#define INT_ENA_REL_MSK 0x02 +#define INT_ENA_F20_MSK 0x04 + +/* Device Status Register Masks (DEVICE_STATUS_REG) */ +#define CONFIGURED_MSK 0x40 +#define ERROR_MSK 0x80 + +/* Data Register 0 Masks */ +#define FINGER_WIDTH_MSK 0xf0 +#define GESTURE_MSK 0x08 +#define SENSOR_STATUS_MSK 0x07 + +/* + * MSB Position Register Masks + * ABS_MSB_X_REG | ABS_MSB_Y_REG | SENS_MAX_POS_MSB_REG | + * DEV_QUERY_REG3 | DEV_QUERY_REG5 + */ +#define MSB_POSITION_MSK 0x1f + +/* Device Query Registers Masks */ + +/* DEV_QUERY_REG2 */ +#define NUM_EXTRA_POS_MSK 0x07 + +/* When in IRQ mode read the device every THREAD_IRQ_SLEEP_SECS */ +#define THREAD_IRQ_SLEEP_SECS 2 +#define THREAD_IRQ_SLEEP_MSECS (THREAD_IRQ_SLEEP_SECS * MSEC_PER_SEC) + +/* + * When in Polling mode and no data received for NO_DATA_THRES msecs + * reduce the polling rate to NO_DATA_SLEEP_MSECS + */ +#define NO_DATA_THRES (MSEC_PER_SEC) +#define NO_DATA_SLEEP_MSECS (MSEC_PER_SEC / 4) + +/* Control touchpad's No Deceleration option */ +static bool no_decel = true; +module_param(no_decel, bool, 0644); +MODULE_PARM_DESC(no_decel, "No Deceleration. Default = 1 (on)"); + +/* Control touchpad's Reduced Reporting option */ +static bool reduce_report; +module_param(reduce_report, bool, 0644); +MODULE_PARM_DESC(reduce_report, "Reduced Reporting. Default = 0 (off)"); + +/* Control touchpad's No Filter option */ +static bool no_filter; +module_param(no_filter, bool, 0644); +MODULE_PARM_DESC(no_filter, "No Filter. Default = 0 (off)"); + +/* + * touchpad Attention line is Active Low and Open Drain, + * therefore should be connected to pulled up line + * and the irq configuration should be set to Falling Edge Trigger + */ +/* Control IRQ / Polling option */ +static bool polling_req; +module_param(polling_req, bool, 0444); +MODULE_PARM_DESC(polling_req, "Request Polling. Default = 0 (use irq)"); + +/* Control Polling Rate */ +static int scan_rate = 80; +module_param(scan_rate, int, 0644); +MODULE_PARM_DESC(scan_rate, "Polling rate in times/sec. Default = 80"); + +/* The main device structure */ +struct synaptics_i2c { + struct i2c_client *client; + struct input_dev *input; + struct delayed_work dwork; + spinlock_t lock; + int no_data_count; + int no_decel_param; + int reduce_report_param; + int no_filter_param; + int scan_rate_param; + int scan_ms; +}; + +static inline void set_scan_rate(struct synaptics_i2c *touch, int scan_rate) +{ + touch->scan_ms = MSEC_PER_SEC / scan_rate; + touch->scan_rate_param = scan_rate; +} + +/* + * Driver's initial design makes no race condition possible on i2c bus, + * so there is no need in any locking. + * Keep it in mind, while playing with the code. + */ +static s32 synaptics_i2c_reg_get(struct i2c_client *client, u16 reg) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, PAGE_SEL_REG, reg >> 8); + if (ret == 0) + ret = i2c_smbus_read_byte_data(client, reg & 0xff); + + return ret; +} + +static s32 synaptics_i2c_reg_set(struct i2c_client *client, u16 reg, u8 val) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, PAGE_SEL_REG, reg >> 8); + if (ret == 0) + ret = i2c_smbus_write_byte_data(client, reg & 0xff, val); + + return ret; +} + +static s32 synaptics_i2c_word_get(struct i2c_client *client, u16 reg) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, PAGE_SEL_REG, reg >> 8); + if (ret == 0) + ret = i2c_smbus_read_word_data(client, reg & 0xff); + + return ret; +} + +static int synaptics_i2c_config(struct i2c_client *client) +{ + int ret, control; + u8 int_en; + + /* set Report Rate to Device Highest (>=80) and Sleep to normal */ + ret = synaptics_i2c_reg_set(client, DEV_CONTROL_REG, 0xc1); + if (ret) + return ret; + + /* set Interrupt Disable to Func20 / Enable to Func10) */ + int_en = (polling_req) ? 0 : INT_ENA_ABS_MSK | INT_ENA_REL_MSK; + ret = synaptics_i2c_reg_set(client, INTERRUPT_EN_REG, int_en); + if (ret) + return ret; + + control = synaptics_i2c_reg_get(client, GENERAL_2D_CONTROL_REG); + /* No Deceleration */ + control |= no_decel ? 1 << NO_DECELERATION : 0; + /* Reduced Reporting */ + control |= reduce_report ? 1 << REDUCE_REPORTING : 0; + /* No Filter */ + control |= no_filter ? 1 << NO_FILTER : 0; + ret = synaptics_i2c_reg_set(client, GENERAL_2D_CONTROL_REG, control); + if (ret) + return ret; + + return 0; +} + +static int synaptics_i2c_reset_config(struct i2c_client *client) +{ + int ret; + + /* Reset the Touchpad */ + ret = synaptics_i2c_reg_set(client, DEV_COMMAND_REG, RESET_COMMAND); + if (ret) { + dev_err(&client->dev, "Unable to reset device\n"); + } else { + usleep_range(SOFT_RESET_DELAY_US, SOFT_RESET_DELAY_US + 100); + ret = synaptics_i2c_config(client); + if (ret) + dev_err(&client->dev, "Unable to config device\n"); + } + + return ret; +} + +static int synaptics_i2c_check_error(struct i2c_client *client) +{ + int status, ret = 0; + + status = i2c_smbus_read_byte_data(client, DEVICE_STATUS_REG) & + (CONFIGURED_MSK | ERROR_MSK); + + if (status != CONFIGURED_MSK) + ret = synaptics_i2c_reset_config(client); + + return ret; +} + +static bool synaptics_i2c_get_input(struct synaptics_i2c *touch) +{ + struct input_dev *input = touch->input; + int xy_delta, gesture; + s32 data; + s8 x_delta, y_delta; + + /* Deal with spontaneous resets and errors */ + if (synaptics_i2c_check_error(touch->client)) + return false; + + /* Get Gesture Bit */ + data = synaptics_i2c_reg_get(touch->client, DATA_REG0); + gesture = (data >> GESTURE) & 0x1; + + /* + * Get Relative axes. we have to get them in one shot, + * so we get 2 bytes starting from REL_X_REG. + */ + xy_delta = synaptics_i2c_word_get(touch->client, REL_X_REG) & 0xffff; + + /* Separate X from Y */ + x_delta = xy_delta & 0xff; + y_delta = (xy_delta >> REGISTER_LENGTH) & 0xff; + + /* Report the button event */ + input_report_key(input, BTN_LEFT, gesture); + + /* Report the deltas */ + input_report_rel(input, REL_X, x_delta); + input_report_rel(input, REL_Y, -y_delta); + input_sync(input); + + return xy_delta || gesture; +} + +static void synaptics_i2c_reschedule_work(struct synaptics_i2c *touch, + unsigned long delay) +{ + unsigned long flags; + + spin_lock_irqsave(&touch->lock, flags); + + mod_delayed_work(system_wq, &touch->dwork, delay); + + spin_unlock_irqrestore(&touch->lock, flags); +} + +static irqreturn_t synaptics_i2c_irq(int irq, void *dev_id) +{ + struct synaptics_i2c *touch = dev_id; + + synaptics_i2c_reschedule_work(touch, 0); + + return IRQ_HANDLED; +} + +static void synaptics_i2c_check_params(struct synaptics_i2c *touch) +{ + bool reset = false; + + if (scan_rate != touch->scan_rate_param) + set_scan_rate(touch, scan_rate); + + if (no_decel != touch->no_decel_param) { + touch->no_decel_param = no_decel; + reset = true; + } + + if (no_filter != touch->no_filter_param) { + touch->no_filter_param = no_filter; + reset = true; + } + + if (reduce_report != touch->reduce_report_param) { + touch->reduce_report_param = reduce_report; + reset = true; + } + + if (reset) + synaptics_i2c_reset_config(touch->client); +} + +/* Control the Device polling rate / Work Handler sleep time */ +static unsigned long synaptics_i2c_adjust_delay(struct synaptics_i2c *touch, + bool have_data) +{ + unsigned long delay, nodata_count_thres; + + if (polling_req) { + delay = touch->scan_ms; + if (have_data) { + touch->no_data_count = 0; + } else { + nodata_count_thres = NO_DATA_THRES / touch->scan_ms; + if (touch->no_data_count < nodata_count_thres) + touch->no_data_count++; + else + delay = NO_DATA_SLEEP_MSECS; + } + return msecs_to_jiffies(delay); + } else { + delay = msecs_to_jiffies(THREAD_IRQ_SLEEP_MSECS); + return round_jiffies_relative(delay); + } +} + +/* Work Handler */ +static void synaptics_i2c_work_handler(struct work_struct *work) +{ + bool have_data; + struct synaptics_i2c *touch = + container_of(work, struct synaptics_i2c, dwork.work); + unsigned long delay; + + synaptics_i2c_check_params(touch); + + have_data = synaptics_i2c_get_input(touch); + delay = synaptics_i2c_adjust_delay(touch, have_data); + + /* + * While interrupt driven, there is no real need to poll the device. + * But touchpads are very sensitive, so there could be errors + * related to physical environment and the attention line isn't + * necessarily asserted. In such case we can lose the touchpad. + * We poll the device once in THREAD_IRQ_SLEEP_SECS and + * if error is detected, we try to reset and reconfigure the touchpad. + */ + synaptics_i2c_reschedule_work(touch, delay); +} + +static int synaptics_i2c_open(struct input_dev *input) +{ + struct synaptics_i2c *touch = input_get_drvdata(input); + int ret; + + ret = synaptics_i2c_reset_config(touch->client); + if (ret) + return ret; + + if (polling_req) + synaptics_i2c_reschedule_work(touch, + msecs_to_jiffies(NO_DATA_SLEEP_MSECS)); + + return 0; +} + +static void synaptics_i2c_close(struct input_dev *input) +{ + struct synaptics_i2c *touch = input_get_drvdata(input); + + if (!polling_req) + synaptics_i2c_reg_set(touch->client, INTERRUPT_EN_REG, 0); + + cancel_delayed_work_sync(&touch->dwork); + + /* Save some power */ + synaptics_i2c_reg_set(touch->client, DEV_CONTROL_REG, DEEP_SLEEP); +} + +static void synaptics_i2c_set_input_params(struct synaptics_i2c *touch) +{ + struct input_dev *input = touch->input; + + input->name = touch->client->name; + input->phys = touch->client->adapter->name; + input->id.bustype = BUS_I2C; + input->id.version = synaptics_i2c_word_get(touch->client, + INFO_QUERY_REG0); + input->dev.parent = &touch->client->dev; + input->open = synaptics_i2c_open; + input->close = synaptics_i2c_close; + input_set_drvdata(input, touch); + + /* Register the device as mouse */ + __set_bit(EV_REL, input->evbit); + __set_bit(REL_X, input->relbit); + __set_bit(REL_Y, input->relbit); + + /* Register device's buttons and keys */ + __set_bit(EV_KEY, input->evbit); + __set_bit(BTN_LEFT, input->keybit); +} + +static struct synaptics_i2c *synaptics_i2c_touch_create(struct i2c_client *client) +{ + struct synaptics_i2c *touch; + + touch = kzalloc(sizeof(struct synaptics_i2c), GFP_KERNEL); + if (!touch) + return NULL; + + touch->client = client; + touch->no_decel_param = no_decel; + touch->scan_rate_param = scan_rate; + set_scan_rate(touch, scan_rate); + INIT_DELAYED_WORK(&touch->dwork, synaptics_i2c_work_handler); + spin_lock_init(&touch->lock); + + return touch; +} + +static int synaptics_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + int ret; + struct synaptics_i2c *touch; + + touch = synaptics_i2c_touch_create(client); + if (!touch) + return -ENOMEM; + + ret = synaptics_i2c_reset_config(client); + if (ret) + goto err_mem_free; + + if (client->irq < 1) + polling_req = true; + + touch->input = input_allocate_device(); + if (!touch->input) { + ret = -ENOMEM; + goto err_mem_free; + } + + synaptics_i2c_set_input_params(touch); + + if (!polling_req) { + dev_dbg(&touch->client->dev, + "Requesting IRQ: %d\n", touch->client->irq); + + ret = request_irq(touch->client->irq, synaptics_i2c_irq, + IRQ_TYPE_EDGE_FALLING, + DRIVER_NAME, touch); + if (ret) { + dev_warn(&touch->client->dev, + "IRQ request failed: %d, " + "falling back to polling\n", ret); + polling_req = true; + synaptics_i2c_reg_set(touch->client, + INTERRUPT_EN_REG, 0); + } + } + + if (polling_req) + dev_dbg(&touch->client->dev, + "Using polling at rate: %d times/sec\n", scan_rate); + + /* Register the device in input subsystem */ + ret = input_register_device(touch->input); + if (ret) { + dev_err(&client->dev, + "Input device register failed: %d\n", ret); + goto err_input_free; + } + + i2c_set_clientdata(client, touch); + + return 0; + +err_input_free: + input_free_device(touch->input); +err_mem_free: + kfree(touch); + + return ret; +} + +static int synaptics_i2c_remove(struct i2c_client *client) +{ + struct synaptics_i2c *touch = i2c_get_clientdata(client); + + if (!polling_req) + free_irq(client->irq, touch); + + input_unregister_device(touch->input); + kfree(touch); + + return 0; +} + +static int __maybe_unused synaptics_i2c_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct synaptics_i2c *touch = i2c_get_clientdata(client); + + cancel_delayed_work_sync(&touch->dwork); + + /* Save some power */ + synaptics_i2c_reg_set(touch->client, DEV_CONTROL_REG, DEEP_SLEEP); + + return 0; +} + +static int __maybe_unused synaptics_i2c_resume(struct device *dev) +{ + int ret; + struct i2c_client *client = to_i2c_client(dev); + struct synaptics_i2c *touch = i2c_get_clientdata(client); + + ret = synaptics_i2c_reset_config(client); + if (ret) + return ret; + + synaptics_i2c_reschedule_work(touch, + msecs_to_jiffies(NO_DATA_SLEEP_MSECS)); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(synaptics_i2c_pm, synaptics_i2c_suspend, + synaptics_i2c_resume); + +static const struct i2c_device_id synaptics_i2c_id_table[] = { + { "synaptics_i2c", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, synaptics_i2c_id_table); + +#ifdef CONFIG_OF +static const struct of_device_id synaptics_i2c_of_match[] = { + { .compatible = "synaptics,synaptics_i2c", }, + { }, +}; +MODULE_DEVICE_TABLE(of, synaptics_i2c_of_match); +#endif + +static struct i2c_driver synaptics_i2c_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(synaptics_i2c_of_match), + .pm = &synaptics_i2c_pm, + }, + + .probe = synaptics_i2c_probe, + .remove = synaptics_i2c_remove, + + .id_table = synaptics_i2c_id_table, +}; + +module_i2c_driver(synaptics_i2c_driver); + +MODULE_DESCRIPTION("Synaptics I2C touchpad driver"); +MODULE_AUTHOR("Mike Rapoport, Igor Grinberg, Compulab"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/mouse/synaptics_usb.c b/drivers/input/mouse/synaptics_usb.c new file mode 100644 index 000000000..83d2412a6 --- /dev/null +++ b/drivers/input/mouse/synaptics_usb.c @@ -0,0 +1,569 @@ +/* + * USB Synaptics device driver + * + * Copyright (c) 2002 Rob Miller (rob@inpharmatica . co . uk) + * Copyright (c) 2003 Ron Lee (ron@debian.org) + * cPad driver for kernel 2.4 + * + * Copyright (c) 2004 Jan Steinhoff (cpad@jan-steinhoff . de) + * Copyright (c) 2004 Ron Lee (ron@debian.org) + * rewritten for kernel 2.6 + * + * cPad display character device part is not included. It can be found at + * http://jan-steinhoff.de/linux/synaptics-usb.html + * + * Bases on: usb_skeleton.c v2.2 by Greg Kroah-Hartman + * drivers/hid/usbhid/usbmouse.c by Vojtech Pavlik + * drivers/input/mouse/synaptics.c by Peter Osterlund + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * Trademarks are the property of their respective owners. + */ + +/* + * There are three different types of Synaptics USB devices: Touchpads, + * touchsticks (or trackpoints), and touchscreens. Touchpads are well supported + * by this driver, touchstick support has not been tested much yet, and + * touchscreens have not been tested at all. + * + * Up to three alternate settings are possible: + * setting 0: one int endpoint for relative movement (used by usbhid.ko) + * setting 1: one int endpoint for absolute finger position + * setting 2 (cPad only): one int endpoint for absolute finger position and + * two bulk endpoints for the display (in/out) + * This driver uses setting 1. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/usb.h> +#include <linux/input.h> +#include <linux/usb/input.h> + +#define USB_VENDOR_ID_SYNAPTICS 0x06cb +#define USB_DEVICE_ID_SYNAPTICS_TP 0x0001 /* Synaptics USB TouchPad */ +#define USB_DEVICE_ID_SYNAPTICS_INT_TP 0x0002 /* Integrated USB TouchPad */ +#define USB_DEVICE_ID_SYNAPTICS_CPAD 0x0003 /* Synaptics cPad */ +#define USB_DEVICE_ID_SYNAPTICS_TS 0x0006 /* Synaptics TouchScreen */ +#define USB_DEVICE_ID_SYNAPTICS_STICK 0x0007 /* Synaptics USB Styk */ +#define USB_DEVICE_ID_SYNAPTICS_WP 0x0008 /* Synaptics USB WheelPad */ +#define USB_DEVICE_ID_SYNAPTICS_COMP_TP 0x0009 /* Composite USB TouchPad */ +#define USB_DEVICE_ID_SYNAPTICS_WTP 0x0010 /* Wireless TouchPad */ +#define USB_DEVICE_ID_SYNAPTICS_DPAD 0x0013 /* DisplayPad */ + +#define SYNUSB_TOUCHPAD (1 << 0) +#define SYNUSB_STICK (1 << 1) +#define SYNUSB_TOUCHSCREEN (1 << 2) +#define SYNUSB_AUXDISPLAY (1 << 3) /* For cPad */ +#define SYNUSB_COMBO (1 << 4) /* Composite device (TP + stick) */ +#define SYNUSB_IO_ALWAYS (1 << 5) + +#define USB_DEVICE_SYNAPTICS(prod, kind) \ + USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, \ + USB_DEVICE_ID_SYNAPTICS_##prod), \ + .driver_info = (kind), + +#define SYNUSB_RECV_SIZE 8 + +#define XMIN_NOMINAL 1472 +#define XMAX_NOMINAL 5472 +#define YMIN_NOMINAL 1408 +#define YMAX_NOMINAL 4448 + +struct synusb { + struct usb_device *udev; + struct usb_interface *intf; + struct urb *urb; + unsigned char *data; + + /* serialize access to open/suspend */ + struct mutex pm_mutex; + bool is_open; + + /* input device related data structures */ + struct input_dev *input; + char name[128]; + char phys[64]; + + /* characteristics of the device */ + unsigned long flags; +}; + +static void synusb_report_buttons(struct synusb *synusb) +{ + struct input_dev *input_dev = synusb->input; + + input_report_key(input_dev, BTN_LEFT, synusb->data[1] & 0x04); + input_report_key(input_dev, BTN_RIGHT, synusb->data[1] & 0x01); + input_report_key(input_dev, BTN_MIDDLE, synusb->data[1] & 0x02); +} + +static void synusb_report_stick(struct synusb *synusb) +{ + struct input_dev *input_dev = synusb->input; + int x, y; + unsigned int pressure; + + pressure = synusb->data[6]; + x = (s16)(be16_to_cpup((__be16 *)&synusb->data[2]) << 3) >> 7; + y = (s16)(be16_to_cpup((__be16 *)&synusb->data[4]) << 3) >> 7; + + if (pressure > 0) { + input_report_rel(input_dev, REL_X, x); + input_report_rel(input_dev, REL_Y, -y); + } + + input_report_abs(input_dev, ABS_PRESSURE, pressure); + + synusb_report_buttons(synusb); + + input_sync(input_dev); +} + +static void synusb_report_touchpad(struct synusb *synusb) +{ + struct input_dev *input_dev = synusb->input; + unsigned int num_fingers, tool_width; + unsigned int x, y; + unsigned int pressure, w; + + pressure = synusb->data[6]; + x = be16_to_cpup((__be16 *)&synusb->data[2]); + y = be16_to_cpup((__be16 *)&synusb->data[4]); + w = synusb->data[0] & 0x0f; + + if (pressure > 0) { + num_fingers = 1; + tool_width = 5; + switch (w) { + case 0 ... 1: + num_fingers = 2 + w; + break; + + case 2: /* pen, pretend its a finger */ + break; + + case 4 ... 15: + tool_width = w; + break; + } + } else { + num_fingers = 0; + tool_width = 0; + } + + /* + * Post events + * BTN_TOUCH has to be first as mousedev relies on it when doing + * absolute -> relative conversion + */ + + if (pressure > 30) + input_report_key(input_dev, BTN_TOUCH, 1); + if (pressure < 25) + input_report_key(input_dev, BTN_TOUCH, 0); + + if (num_fingers > 0) { + input_report_abs(input_dev, ABS_X, x); + input_report_abs(input_dev, ABS_Y, + YMAX_NOMINAL + YMIN_NOMINAL - y); + } + + input_report_abs(input_dev, ABS_PRESSURE, pressure); + input_report_abs(input_dev, ABS_TOOL_WIDTH, tool_width); + + input_report_key(input_dev, BTN_TOOL_FINGER, num_fingers == 1); + input_report_key(input_dev, BTN_TOOL_DOUBLETAP, num_fingers == 2); + input_report_key(input_dev, BTN_TOOL_TRIPLETAP, num_fingers == 3); + + synusb_report_buttons(synusb); + if (synusb->flags & SYNUSB_AUXDISPLAY) + input_report_key(input_dev, BTN_MIDDLE, synusb->data[1] & 0x08); + + input_sync(input_dev); +} + +static void synusb_irq(struct urb *urb) +{ + struct synusb *synusb = urb->context; + int error; + + /* Check our status in case we need to bail out early. */ + switch (urb->status) { + case 0: + usb_mark_last_busy(synusb->udev); + break; + + /* Device went away so don't keep trying to read from it. */ + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + return; + + default: + goto resubmit; + break; + } + + if (synusb->flags & SYNUSB_STICK) + synusb_report_stick(synusb); + else + synusb_report_touchpad(synusb); + +resubmit: + error = usb_submit_urb(urb, GFP_ATOMIC); + if (error && error != -EPERM) + dev_err(&synusb->intf->dev, + "%s - usb_submit_urb failed with result: %d", + __func__, error); +} + +static struct usb_endpoint_descriptor * +synusb_get_in_endpoint(struct usb_host_interface *iface) +{ + + struct usb_endpoint_descriptor *endpoint; + int i; + + for (i = 0; i < iface->desc.bNumEndpoints; ++i) { + endpoint = &iface->endpoint[i].desc; + + if (usb_endpoint_is_int_in(endpoint)) { + /* we found our interrupt in endpoint */ + return endpoint; + } + } + + return NULL; +} + +static int synusb_open(struct input_dev *dev) +{ + struct synusb *synusb = input_get_drvdata(dev); + int retval; + + retval = usb_autopm_get_interface(synusb->intf); + if (retval) { + dev_err(&synusb->intf->dev, + "%s - usb_autopm_get_interface failed, error: %d\n", + __func__, retval); + return retval; + } + + mutex_lock(&synusb->pm_mutex); + retval = usb_submit_urb(synusb->urb, GFP_KERNEL); + if (retval) { + dev_err(&synusb->intf->dev, + "%s - usb_submit_urb failed, error: %d\n", + __func__, retval); + retval = -EIO; + goto out; + } + + synusb->intf->needs_remote_wakeup = 1; + synusb->is_open = true; + +out: + mutex_unlock(&synusb->pm_mutex); + usb_autopm_put_interface(synusb->intf); + return retval; +} + +static void synusb_close(struct input_dev *dev) +{ + struct synusb *synusb = input_get_drvdata(dev); + int autopm_error; + + autopm_error = usb_autopm_get_interface(synusb->intf); + + mutex_lock(&synusb->pm_mutex); + usb_kill_urb(synusb->urb); + synusb->intf->needs_remote_wakeup = 0; + synusb->is_open = false; + mutex_unlock(&synusb->pm_mutex); + + if (!autopm_error) + usb_autopm_put_interface(synusb->intf); +} + +static int synusb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_endpoint_descriptor *ep; + struct synusb *synusb; + struct input_dev *input_dev; + unsigned int intf_num = intf->cur_altsetting->desc.bInterfaceNumber; + unsigned int altsetting = min(intf->num_altsetting, 1U); + int error; + + error = usb_set_interface(udev, intf_num, altsetting); + if (error) { + dev_err(&udev->dev, + "Can not set alternate setting to %i, error: %i", + altsetting, error); + return error; + } + + ep = synusb_get_in_endpoint(intf->cur_altsetting); + if (!ep) + return -ENODEV; + + synusb = kzalloc(sizeof(*synusb), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!synusb || !input_dev) { + error = -ENOMEM; + goto err_free_mem; + } + + synusb->udev = udev; + synusb->intf = intf; + synusb->input = input_dev; + mutex_init(&synusb->pm_mutex); + + synusb->flags = id->driver_info; + if (synusb->flags & SYNUSB_COMBO) { + /* + * This is a combo device, we need to set proper + * capability, depending on the interface. + */ + synusb->flags |= intf_num == 1 ? + SYNUSB_STICK : SYNUSB_TOUCHPAD; + } + + synusb->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!synusb->urb) { + error = -ENOMEM; + goto err_free_mem; + } + + synusb->data = usb_alloc_coherent(udev, SYNUSB_RECV_SIZE, GFP_KERNEL, + &synusb->urb->transfer_dma); + if (!synusb->data) { + error = -ENOMEM; + goto err_free_urb; + } + + usb_fill_int_urb(synusb->urb, udev, + usb_rcvintpipe(udev, ep->bEndpointAddress), + synusb->data, SYNUSB_RECV_SIZE, + synusb_irq, synusb, + ep->bInterval); + synusb->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + if (udev->manufacturer) + strlcpy(synusb->name, udev->manufacturer, + sizeof(synusb->name)); + + if (udev->product) { + if (udev->manufacturer) + strlcat(synusb->name, " ", sizeof(synusb->name)); + strlcat(synusb->name, udev->product, sizeof(synusb->name)); + } + + if (!strlen(synusb->name)) + snprintf(synusb->name, sizeof(synusb->name), + "USB Synaptics Device %04x:%04x", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + + if (synusb->flags & SYNUSB_STICK) + strlcat(synusb->name, " (Stick)", sizeof(synusb->name)); + + usb_make_path(udev, synusb->phys, sizeof(synusb->phys)); + strlcat(synusb->phys, "/input0", sizeof(synusb->phys)); + + input_dev->name = synusb->name; + input_dev->phys = synusb->phys; + usb_to_input_id(udev, &input_dev->id); + input_dev->dev.parent = &synusb->intf->dev; + + if (!(synusb->flags & SYNUSB_IO_ALWAYS)) { + input_dev->open = synusb_open; + input_dev->close = synusb_close; + } + + input_set_drvdata(input_dev, synusb); + + __set_bit(EV_ABS, input_dev->evbit); + __set_bit(EV_KEY, input_dev->evbit); + + if (synusb->flags & SYNUSB_STICK) { + __set_bit(EV_REL, input_dev->evbit); + __set_bit(REL_X, input_dev->relbit); + __set_bit(REL_Y, input_dev->relbit); + __set_bit(INPUT_PROP_POINTING_STICK, input_dev->propbit); + input_set_abs_params(input_dev, ABS_PRESSURE, 0, 127, 0, 0); + } else { + input_set_abs_params(input_dev, ABS_X, + XMIN_NOMINAL, XMAX_NOMINAL, 0, 0); + input_set_abs_params(input_dev, ABS_Y, + YMIN_NOMINAL, YMAX_NOMINAL, 0, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, 0, 255, 0, 0); + input_set_abs_params(input_dev, ABS_TOOL_WIDTH, 0, 15, 0, 0); + __set_bit(BTN_TOUCH, input_dev->keybit); + __set_bit(BTN_TOOL_FINGER, input_dev->keybit); + __set_bit(BTN_TOOL_DOUBLETAP, input_dev->keybit); + __set_bit(BTN_TOOL_TRIPLETAP, input_dev->keybit); + } + + if (synusb->flags & SYNUSB_TOUCHSCREEN) + __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + else + __set_bit(INPUT_PROP_POINTER, input_dev->propbit); + + __set_bit(BTN_LEFT, input_dev->keybit); + __set_bit(BTN_RIGHT, input_dev->keybit); + __set_bit(BTN_MIDDLE, input_dev->keybit); + + usb_set_intfdata(intf, synusb); + + if (synusb->flags & SYNUSB_IO_ALWAYS) { + error = synusb_open(input_dev); + if (error) + goto err_free_dma; + } + + error = input_register_device(input_dev); + if (error) { + dev_err(&udev->dev, + "Failed to register input device, error %d\n", + error); + goto err_stop_io; + } + + return 0; + +err_stop_io: + if (synusb->flags & SYNUSB_IO_ALWAYS) + synusb_close(synusb->input); +err_free_dma: + usb_free_coherent(udev, SYNUSB_RECV_SIZE, synusb->data, + synusb->urb->transfer_dma); +err_free_urb: + usb_free_urb(synusb->urb); +err_free_mem: + input_free_device(input_dev); + kfree(synusb); + usb_set_intfdata(intf, NULL); + + return error; +} + +static void synusb_disconnect(struct usb_interface *intf) +{ + struct synusb *synusb = usb_get_intfdata(intf); + struct usb_device *udev = interface_to_usbdev(intf); + + if (synusb->flags & SYNUSB_IO_ALWAYS) + synusb_close(synusb->input); + + input_unregister_device(synusb->input); + + usb_free_coherent(udev, SYNUSB_RECV_SIZE, synusb->data, + synusb->urb->transfer_dma); + usb_free_urb(synusb->urb); + kfree(synusb); + + usb_set_intfdata(intf, NULL); +} + +static int synusb_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct synusb *synusb = usb_get_intfdata(intf); + + mutex_lock(&synusb->pm_mutex); + usb_kill_urb(synusb->urb); + mutex_unlock(&synusb->pm_mutex); + + return 0; +} + +static int synusb_resume(struct usb_interface *intf) +{ + struct synusb *synusb = usb_get_intfdata(intf); + int retval = 0; + + mutex_lock(&synusb->pm_mutex); + + if ((synusb->is_open || (synusb->flags & SYNUSB_IO_ALWAYS)) && + usb_submit_urb(synusb->urb, GFP_NOIO) < 0) { + retval = -EIO; + } + + mutex_unlock(&synusb->pm_mutex); + + return retval; +} + +static int synusb_pre_reset(struct usb_interface *intf) +{ + struct synusb *synusb = usb_get_intfdata(intf); + + mutex_lock(&synusb->pm_mutex); + usb_kill_urb(synusb->urb); + + return 0; +} + +static int synusb_post_reset(struct usb_interface *intf) +{ + struct synusb *synusb = usb_get_intfdata(intf); + int retval = 0; + + if ((synusb->is_open || (synusb->flags & SYNUSB_IO_ALWAYS)) && + usb_submit_urb(synusb->urb, GFP_NOIO) < 0) { + retval = -EIO; + } + + mutex_unlock(&synusb->pm_mutex); + + return retval; +} + +static int synusb_reset_resume(struct usb_interface *intf) +{ + return synusb_resume(intf); +} + +static const struct usb_device_id synusb_idtable[] = { + { USB_DEVICE_SYNAPTICS(TP, SYNUSB_TOUCHPAD) }, + { USB_DEVICE_SYNAPTICS(INT_TP, SYNUSB_TOUCHPAD) }, + { USB_DEVICE_SYNAPTICS(CPAD, + SYNUSB_TOUCHPAD | SYNUSB_AUXDISPLAY | SYNUSB_IO_ALWAYS) }, + { USB_DEVICE_SYNAPTICS(TS, SYNUSB_TOUCHSCREEN) }, + { USB_DEVICE_SYNAPTICS(STICK, SYNUSB_STICK) }, + { USB_DEVICE_SYNAPTICS(WP, SYNUSB_TOUCHPAD) }, + { USB_DEVICE_SYNAPTICS(COMP_TP, SYNUSB_COMBO) }, + { USB_DEVICE_SYNAPTICS(WTP, SYNUSB_TOUCHPAD) }, + { USB_DEVICE_SYNAPTICS(DPAD, SYNUSB_TOUCHPAD) }, + { } +}; +MODULE_DEVICE_TABLE(usb, synusb_idtable); + +static struct usb_driver synusb_driver = { + .name = "synaptics_usb", + .probe = synusb_probe, + .disconnect = synusb_disconnect, + .id_table = synusb_idtable, + .suspend = synusb_suspend, + .resume = synusb_resume, + .pre_reset = synusb_pre_reset, + .post_reset = synusb_post_reset, + .reset_resume = synusb_reset_resume, + .supports_autosuspend = 1, +}; + +module_usb_driver(synusb_driver); + +MODULE_AUTHOR("Rob Miller <rob@inpharmatica.co.uk>, " + "Ron Lee <ron@debian.org>, " + "Jan Steinhoff <cpad@jan-steinhoff.de>"); +MODULE_DESCRIPTION("Synaptics USB device driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/mouse/touchkit_ps2.c b/drivers/input/mouse/touchkit_ps2.c new file mode 100644 index 000000000..1fd8f5e19 --- /dev/null +++ b/drivers/input/mouse/touchkit_ps2.c @@ -0,0 +1,100 @@ +/* ---------------------------------------------------------------------------- + * touchkit_ps2.c -- Driver for eGalax TouchKit PS/2 Touchscreens + * + * Copyright (C) 2005 by Stefan Lucke + * Copyright (C) 2004 by Daniel Ritz + * Copyright (C) by Todd E. Johnson (mtouchusb.c) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Based upon touchkitusb.c + * + * Vendor documentation is available at: + * http://home.eeti.com.tw/web20/drivers/Software%20Programming%20Guide_v2.0.pdf + */ + +#include <linux/kernel.h> + +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/libps2.h> + +#include "psmouse.h" +#include "touchkit_ps2.h" + +#define TOUCHKIT_MAX_XC 0x07ff +#define TOUCHKIT_MAX_YC 0x07ff + +#define TOUCHKIT_CMD 0x0a +#define TOUCHKIT_CMD_LENGTH 1 + +#define TOUCHKIT_CMD_ACTIVE 'A' +#define TOUCHKIT_CMD_FIRMWARE_VERSION 'D' +#define TOUCHKIT_CMD_CONTROLLER_TYPE 'E' + +#define TOUCHKIT_SEND_PARMS(s, r, c) ((s) << 12 | (r) << 8 | (c)) + +#define TOUCHKIT_GET_TOUCHED(packet) (((packet)[0]) & 0x01) +#define TOUCHKIT_GET_X(packet) (((packet)[1] << 7) | (packet)[2]) +#define TOUCHKIT_GET_Y(packet) (((packet)[3] << 7) | (packet)[4]) + +static psmouse_ret_t touchkit_ps2_process_byte(struct psmouse *psmouse) +{ + unsigned char *packet = psmouse->packet; + struct input_dev *dev = psmouse->dev; + + if (psmouse->pktcnt != 5) + return PSMOUSE_GOOD_DATA; + + input_report_abs(dev, ABS_X, TOUCHKIT_GET_X(packet)); + input_report_abs(dev, ABS_Y, TOUCHKIT_GET_Y(packet)); + input_report_key(dev, BTN_TOUCH, TOUCHKIT_GET_TOUCHED(packet)); + input_sync(dev); + + return PSMOUSE_FULL_PACKET; +} + +int touchkit_ps2_detect(struct psmouse *psmouse, bool set_properties) +{ + struct input_dev *dev = psmouse->dev; + unsigned char param[3]; + int command; + + param[0] = TOUCHKIT_CMD_LENGTH; + param[1] = TOUCHKIT_CMD_ACTIVE; + command = TOUCHKIT_SEND_PARMS(2, 3, TOUCHKIT_CMD); + + if (ps2_command(&psmouse->ps2dev, param, command)) + return -ENODEV; + + if (param[0] != TOUCHKIT_CMD || param[1] != 0x01 || + param[2] != TOUCHKIT_CMD_ACTIVE) + return -ENODEV; + + if (set_properties) { + dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + dev->keybit[BIT_WORD(BTN_MOUSE)] = 0; + dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(dev, ABS_X, 0, TOUCHKIT_MAX_XC, 0, 0); + input_set_abs_params(dev, ABS_Y, 0, TOUCHKIT_MAX_YC, 0, 0); + + psmouse->vendor = "eGalax"; + psmouse->name = "Touchscreen"; + psmouse->protocol_handler = touchkit_ps2_process_byte; + psmouse->pktsize = 5; + } + + return 0; +} diff --git a/drivers/input/mouse/touchkit_ps2.h b/drivers/input/mouse/touchkit_ps2.h new file mode 100644 index 000000000..2efe9ea29 --- /dev/null +++ b/drivers/input/mouse/touchkit_ps2.h @@ -0,0 +1,25 @@ +/* ---------------------------------------------------------------------------- + * touchkit_ps2.h -- Driver for eGalax TouchKit PS/2 Touchscreens + * + * Copyright (C) 2005 by Stefan Lucke + * Copyright (c) 2005 Vojtech Pavlik + * + * 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. + */ + +#ifndef _TOUCHKIT_PS2_H +#define _TOUCHKIT_PS2_H + +#ifdef CONFIG_MOUSE_PS2_TOUCHKIT +int touchkit_ps2_detect(struct psmouse *psmouse, bool set_properties); +#else +static inline int touchkit_ps2_detect(struct psmouse *psmouse, + bool set_properties) +{ + return -ENOSYS; +} +#endif /* CONFIG_MOUSE_PS2_TOUCHKIT */ + +#endif diff --git a/drivers/input/mouse/trackpoint.c b/drivers/input/mouse/trackpoint.c new file mode 100644 index 000000000..e46865785 --- /dev/null +++ b/drivers/input/mouse/trackpoint.c @@ -0,0 +1,478 @@ +/* + * Stephen Evanchik <evanchsa@gmail.com> + * + * 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/slab.h> +#include <linux/delay.h> +#include <linux/serio.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/libps2.h> +#include <linux/proc_fs.h> +#include <linux/uaccess.h> +#include "psmouse.h" +#include "trackpoint.h" + +static const char * const trackpoint_variants[] = { + [TP_VARIANT_IBM] = "IBM", + [TP_VARIANT_ALPS] = "ALPS", + [TP_VARIANT_ELAN] = "Elan", + [TP_VARIANT_NXP] = "NXP", + [TP_VARIANT_JYT_SYNAPTICS] = "JYT_Synaptics", + [TP_VARIANT_SYNAPTICS] = "Synaptics", +}; + +/* + * Power-on Reset: Resets all trackpoint parameters, including RAM values, + * to defaults. + * Returns zero on success, non-zero on failure. + */ +static int trackpoint_power_on_reset(struct ps2dev *ps2dev) +{ + u8 param[2] = { TP_POR }; + int err; + + err = ps2_command(ps2dev, param, MAKE_PS2_CMD(1, 2, TP_COMMAND)); + if (err) + return err; + + /* Check for success response -- 0xAA00 */ + if (param[0] != 0xAA || param[1] != 0x00) + return -ENODEV; + + return 0; +} + +/* + * Device IO: read, write and toggle bit + */ +static int trackpoint_read(struct ps2dev *ps2dev, u8 loc, u8 *results) +{ + results[0] = loc; + + return ps2_command(ps2dev, results, MAKE_PS2_CMD(1, 1, TP_COMMAND)); +} + +static int trackpoint_write(struct ps2dev *ps2dev, u8 loc, u8 val) +{ + u8 param[3] = { TP_WRITE_MEM, loc, val }; + + return ps2_command(ps2dev, param, MAKE_PS2_CMD(3, 0, TP_COMMAND)); +} + +static int trackpoint_toggle_bit(struct ps2dev *ps2dev, u8 loc, u8 mask) +{ + u8 param[3] = { TP_TOGGLE, loc, mask }; + + /* Bad things will happen if the loc param isn't in this range */ + if (loc < 0x20 || loc >= 0x2F) + return -EINVAL; + + return ps2_command(ps2dev, param, MAKE_PS2_CMD(3, 0, TP_COMMAND)); +} + +static int trackpoint_update_bit(struct ps2dev *ps2dev, + u8 loc, u8 mask, u8 value) +{ + int retval; + u8 data; + + retval = trackpoint_read(ps2dev, loc, &data); + if (retval) + return retval; + + if (((data & mask) == mask) != !!value) + retval = trackpoint_toggle_bit(ps2dev, loc, mask); + + return retval; +} + +/* + * Trackpoint-specific attributes + */ +struct trackpoint_attr_data { + size_t field_offset; + u8 command; + u8 mask; + bool inverted; + u8 power_on_default; +}; + +static ssize_t trackpoint_show_int_attr(struct psmouse *psmouse, + void *data, char *buf) +{ + struct trackpoint_data *tp = psmouse->private; + struct trackpoint_attr_data *attr = data; + u8 value = *(u8 *)((void *)tp + attr->field_offset); + + if (attr->inverted) + value = !value; + + return sprintf(buf, "%u\n", value); +} + +static ssize_t trackpoint_set_int_attr(struct psmouse *psmouse, void *data, + const char *buf, size_t count) +{ + struct trackpoint_data *tp = psmouse->private; + struct trackpoint_attr_data *attr = data; + u8 *field = (void *)tp + attr->field_offset; + u8 value; + int err; + + err = kstrtou8(buf, 10, &value); + if (err) + return err; + + *field = value; + err = trackpoint_write(&psmouse->ps2dev, attr->command, value); + + return err ?: count; +} + +#define TRACKPOINT_INT_ATTR(_name, _command, _default) \ + static struct trackpoint_attr_data trackpoint_attr_##_name = { \ + .field_offset = offsetof(struct trackpoint_data, _name), \ + .command = _command, \ + .power_on_default = _default, \ + }; \ + PSMOUSE_DEFINE_ATTR(_name, S_IWUSR | S_IRUGO, \ + &trackpoint_attr_##_name, \ + trackpoint_show_int_attr, trackpoint_set_int_attr) + +static ssize_t trackpoint_set_bit_attr(struct psmouse *psmouse, void *data, + const char *buf, size_t count) +{ + struct trackpoint_data *tp = psmouse->private; + struct trackpoint_attr_data *attr = data; + bool *field = (void *)tp + attr->field_offset; + bool value; + int err; + + err = kstrtobool(buf, &value); + if (err) + return err; + + if (attr->inverted) + value = !value; + + if (*field != value) { + *field = value; + err = trackpoint_toggle_bit(&psmouse->ps2dev, + attr->command, attr->mask); + } + + return err ?: count; +} + + +#define TRACKPOINT_BIT_ATTR(_name, _command, _mask, _inv, _default) \ +static struct trackpoint_attr_data trackpoint_attr_##_name = { \ + .field_offset = offsetof(struct trackpoint_data, \ + _name), \ + .command = _command, \ + .mask = _mask, \ + .inverted = _inv, \ + .power_on_default = _default, \ + }; \ +PSMOUSE_DEFINE_ATTR(_name, S_IWUSR | S_IRUGO, \ + &trackpoint_attr_##_name, \ + trackpoint_show_int_attr, trackpoint_set_bit_attr) + +TRACKPOINT_INT_ATTR(sensitivity, TP_SENS, TP_DEF_SENS); +TRACKPOINT_INT_ATTR(speed, TP_SPEED, TP_DEF_SPEED); +TRACKPOINT_INT_ATTR(inertia, TP_INERTIA, TP_DEF_INERTIA); +TRACKPOINT_INT_ATTR(reach, TP_REACH, TP_DEF_REACH); +TRACKPOINT_INT_ATTR(draghys, TP_DRAGHYS, TP_DEF_DRAGHYS); +TRACKPOINT_INT_ATTR(mindrag, TP_MINDRAG, TP_DEF_MINDRAG); +TRACKPOINT_INT_ATTR(thresh, TP_THRESH, TP_DEF_THRESH); +TRACKPOINT_INT_ATTR(upthresh, TP_UP_THRESH, TP_DEF_UP_THRESH); +TRACKPOINT_INT_ATTR(ztime, TP_Z_TIME, TP_DEF_Z_TIME); +TRACKPOINT_INT_ATTR(jenks, TP_JENKS_CURV, TP_DEF_JENKS_CURV); +TRACKPOINT_INT_ATTR(drift_time, TP_DRIFT_TIME, TP_DEF_DRIFT_TIME); + +TRACKPOINT_BIT_ATTR(press_to_select, TP_TOGGLE_PTSON, TP_MASK_PTSON, false, + TP_DEF_PTSON); +TRACKPOINT_BIT_ATTR(skipback, TP_TOGGLE_SKIPBACK, TP_MASK_SKIPBACK, false, + TP_DEF_SKIPBACK); +TRACKPOINT_BIT_ATTR(ext_dev, TP_TOGGLE_EXT_DEV, TP_MASK_EXT_DEV, true, + TP_DEF_EXT_DEV); + +static bool trackpoint_is_attr_available(struct psmouse *psmouse, + struct attribute *attr) +{ + struct trackpoint_data *tp = psmouse->private; + + return tp->variant_id == TP_VARIANT_IBM || + attr == &psmouse_attr_sensitivity.dattr.attr || + attr == &psmouse_attr_press_to_select.dattr.attr; +} + +static umode_t trackpoint_is_attr_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct serio *serio = to_serio_port(dev); + struct psmouse *psmouse = serio_get_drvdata(serio); + + return trackpoint_is_attr_available(psmouse, attr) ? attr->mode : 0; +} + +static struct attribute *trackpoint_attrs[] = { + &psmouse_attr_sensitivity.dattr.attr, + &psmouse_attr_speed.dattr.attr, + &psmouse_attr_inertia.dattr.attr, + &psmouse_attr_reach.dattr.attr, + &psmouse_attr_draghys.dattr.attr, + &psmouse_attr_mindrag.dattr.attr, + &psmouse_attr_thresh.dattr.attr, + &psmouse_attr_upthresh.dattr.attr, + &psmouse_attr_ztime.dattr.attr, + &psmouse_attr_jenks.dattr.attr, + &psmouse_attr_drift_time.dattr.attr, + &psmouse_attr_press_to_select.dattr.attr, + &psmouse_attr_skipback.dattr.attr, + &psmouse_attr_ext_dev.dattr.attr, + NULL +}; + +static struct attribute_group trackpoint_attr_group = { + .is_visible = trackpoint_is_attr_visible, + .attrs = trackpoint_attrs, +}; + +#define TRACKPOINT_UPDATE(_power_on, _psmouse, _tp, _name) \ +do { \ + struct trackpoint_attr_data *_attr = &trackpoint_attr_##_name; \ + \ + if ((!_power_on || _tp->_name != _attr->power_on_default) && \ + trackpoint_is_attr_available(_psmouse, \ + &psmouse_attr_##_name.dattr.attr)) { \ + if (!_attr->mask) \ + trackpoint_write(&_psmouse->ps2dev, \ + _attr->command, _tp->_name); \ + else \ + trackpoint_update_bit(&_psmouse->ps2dev, \ + _attr->command, _attr->mask, \ + _tp->_name); \ + } \ +} while (0) + +#define TRACKPOINT_SET_POWER_ON_DEFAULT(_tp, _name) \ +do { \ + _tp->_name = trackpoint_attr_##_name.power_on_default; \ +} while (0) + +static int trackpoint_start_protocol(struct psmouse *psmouse, + u8 *variant_id, u8 *firmware_id) +{ + u8 param[2] = { 0 }; + int error; + + error = ps2_command(&psmouse->ps2dev, + param, MAKE_PS2_CMD(0, 2, TP_READ_ID)); + if (error) + return error; + + switch (param[0]) { + case TP_VARIANT_IBM: + case TP_VARIANT_ALPS: + case TP_VARIANT_ELAN: + case TP_VARIANT_NXP: + case TP_VARIANT_JYT_SYNAPTICS: + case TP_VARIANT_SYNAPTICS: + if (variant_id) + *variant_id = param[0]; + if (firmware_id) + *firmware_id = param[1]; + return 0; + } + + return -ENODEV; +} + +/* + * Write parameters to trackpad. + * in_power_on_state: Set to true if TP is in default / power-on state (ex. if + * power-on reset was run). If so, values will only be + * written to TP if they differ from power-on default. + */ +static int trackpoint_sync(struct psmouse *psmouse, bool in_power_on_state) +{ + struct trackpoint_data *tp = psmouse->private; + + if (!in_power_on_state && tp->variant_id == TP_VARIANT_IBM) { + /* + * Disable features that may make device unusable + * with this driver. + */ + trackpoint_update_bit(&psmouse->ps2dev, TP_TOGGLE_TWOHAND, + TP_MASK_TWOHAND, TP_DEF_TWOHAND); + + trackpoint_update_bit(&psmouse->ps2dev, TP_TOGGLE_SOURCE_TAG, + TP_MASK_SOURCE_TAG, TP_DEF_SOURCE_TAG); + + trackpoint_update_bit(&psmouse->ps2dev, TP_TOGGLE_MB, + TP_MASK_MB, TP_DEF_MB); + } + + /* + * These properties can be changed in this driver. Only + * configure them if the values are non-default or if the TP is in + * an unknown state. + */ + TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, sensitivity); + TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, inertia); + TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, speed); + TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, reach); + TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, draghys); + TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, mindrag); + TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, thresh); + TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, upthresh); + TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, ztime); + TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, jenks); + TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, drift_time); + + /* toggles */ + TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, press_to_select); + TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, skipback); + TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, ext_dev); + + return 0; +} + +static void trackpoint_defaults(struct trackpoint_data *tp) +{ + TRACKPOINT_SET_POWER_ON_DEFAULT(tp, sensitivity); + TRACKPOINT_SET_POWER_ON_DEFAULT(tp, speed); + TRACKPOINT_SET_POWER_ON_DEFAULT(tp, reach); + TRACKPOINT_SET_POWER_ON_DEFAULT(tp, draghys); + TRACKPOINT_SET_POWER_ON_DEFAULT(tp, mindrag); + TRACKPOINT_SET_POWER_ON_DEFAULT(tp, thresh); + TRACKPOINT_SET_POWER_ON_DEFAULT(tp, upthresh); + TRACKPOINT_SET_POWER_ON_DEFAULT(tp, ztime); + TRACKPOINT_SET_POWER_ON_DEFAULT(tp, jenks); + TRACKPOINT_SET_POWER_ON_DEFAULT(tp, drift_time); + TRACKPOINT_SET_POWER_ON_DEFAULT(tp, inertia); + + /* toggles */ + TRACKPOINT_SET_POWER_ON_DEFAULT(tp, press_to_select); + TRACKPOINT_SET_POWER_ON_DEFAULT(tp, skipback); + TRACKPOINT_SET_POWER_ON_DEFAULT(tp, ext_dev); +} + +static void trackpoint_disconnect(struct psmouse *psmouse) +{ + device_remove_group(&psmouse->ps2dev.serio->dev, + &trackpoint_attr_group); + + kfree(psmouse->private); + psmouse->private = NULL; +} + +static int trackpoint_reconnect(struct psmouse *psmouse) +{ + struct trackpoint_data *tp = psmouse->private; + int error; + bool was_reset; + + error = trackpoint_start_protocol(psmouse, NULL, NULL); + if (error) + return error; + + was_reset = tp->variant_id == TP_VARIANT_IBM && + trackpoint_power_on_reset(&psmouse->ps2dev) == 0; + + error = trackpoint_sync(psmouse, was_reset); + if (error) + return error; + + return 0; +} + +int trackpoint_detect(struct psmouse *psmouse, bool set_properties) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + struct trackpoint_data *tp; + u8 variant_id; + u8 firmware_id; + u8 button_info; + int error; + + error = trackpoint_start_protocol(psmouse, &variant_id, &firmware_id); + if (error) + return error; + + if (!set_properties) + return 0; + + tp = kzalloc(sizeof(*tp), GFP_KERNEL); + if (!tp) + return -ENOMEM; + + trackpoint_defaults(tp); + tp->variant_id = variant_id; + tp->firmware_id = firmware_id; + + psmouse->private = tp; + + psmouse->vendor = trackpoint_variants[variant_id]; + psmouse->name = "TrackPoint"; + + psmouse->reconnect = trackpoint_reconnect; + psmouse->disconnect = trackpoint_disconnect; + + if (variant_id != TP_VARIANT_IBM) { + /* Newer variants do not support extended button query. */ + button_info = 0x33; + } else { + error = trackpoint_read(ps2dev, TP_EXT_BTN, &button_info); + if (error) { + psmouse_warn(psmouse, + "failed to get extended button data, assuming 3 buttons\n"); + button_info = 0x33; + } else if (!button_info) { + psmouse_warn(psmouse, + "got 0 in extended button data, assuming 3 buttons\n"); + button_info = 0x33; + } + } + + if ((button_info & 0x0f) >= 3) + input_set_capability(psmouse->dev, EV_KEY, BTN_MIDDLE); + + __set_bit(INPUT_PROP_POINTER, psmouse->dev->propbit); + __set_bit(INPUT_PROP_POINTING_STICK, psmouse->dev->propbit); + + if (variant_id != TP_VARIANT_IBM || + trackpoint_power_on_reset(ps2dev) != 0) { + /* + * Write defaults to TP if we did not reset the trackpoint. + */ + trackpoint_sync(psmouse, false); + } + + error = device_add_group(&ps2dev->serio->dev, &trackpoint_attr_group); + if (error) { + psmouse_err(psmouse, + "failed to create sysfs attributes, error: %d\n", + error); + kfree(psmouse->private); + psmouse->private = NULL; + return -1; + } + + psmouse_info(psmouse, + "%s TrackPoint firmware: 0x%02x, buttons: %d/%d\n", + psmouse->vendor, firmware_id, + (button_info & 0xf0) >> 4, button_info & 0x0f); + + return 0; +} + diff --git a/drivers/input/mouse/trackpoint.h b/drivers/input/mouse/trackpoint.h new file mode 100644 index 000000000..4ebcdf802 --- /dev/null +++ b/drivers/input/mouse/trackpoint.h @@ -0,0 +1,173 @@ +/* + * IBM TrackPoint PS/2 mouse driver + * + * Stephen Evanchik <evanchsa@gmail.com> + * + * 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. + */ + +#ifndef _TRACKPOINT_H +#define _TRACKPOINT_H + +/* + * These constants are from the TrackPoint System + * Engineering documentation Version 4 from IBM Watson + * research: + * http://wwwcssrv.almaden.ibm.com/trackpoint/download.html + */ + +#define TP_COMMAND 0xE2 /* Commands start with this */ + +#define TP_READ_ID 0xE1 /* Sent for device identification */ + +/* + * Valid first byte responses to the "Read Secondary ID" (0xE1) command. + * 0x01 was the original IBM trackpoint, others implement very limited + * subset of trackpoint features. + */ +#define TP_VARIANT_IBM 0x01 +#define TP_VARIANT_ALPS 0x02 +#define TP_VARIANT_ELAN 0x03 +#define TP_VARIANT_NXP 0x04 +#define TP_VARIANT_JYT_SYNAPTICS 0x05 +#define TP_VARIANT_SYNAPTICS 0x06 + +/* + * Commands + */ +#define TP_RECALIB 0x51 /* Recalibrate */ +#define TP_POWER_DOWN 0x44 /* Can only be undone through HW reset */ +#define TP_EXT_DEV 0x21 /* Determines if external device is connected (RO) */ +#define TP_EXT_BTN 0x4B /* Read extended button status */ +#define TP_POR 0x7F /* Execute Power on Reset */ +#define TP_POR_RESULTS 0x25 /* Read Power on Self test results */ +#define TP_DISABLE_EXT 0x40 /* Disable external pointing device */ +#define TP_ENABLE_EXT 0x41 /* Enable external pointing device */ + +/* + * Mode manipulation + */ +#define TP_SET_SOFT_TRANS 0x4E /* Set mode */ +#define TP_CANCEL_SOFT_TRANS 0xB9 /* Cancel mode */ +#define TP_SET_HARD_TRANS 0x45 /* Mode can only be set */ + + +/* + * Register oriented commands/properties + */ +#define TP_WRITE_MEM 0x81 +#define TP_READ_MEM 0x80 /* Not used in this implementation */ + +/* +* RAM Locations for properties + */ +#define TP_SENS 0x4A /* Sensitivity */ +#define TP_MB 0x4C /* Read Middle Button Status (RO) */ +#define TP_INERTIA 0x4D /* Negative Inertia */ +#define TP_SPEED 0x60 /* Speed of TP Cursor */ +#define TP_REACH 0x57 /* Backup for Z-axis press */ +#define TP_DRAGHYS 0x58 /* Drag Hysteresis */ + /* (how hard it is to drag */ + /* with Z-axis pressed) */ + +#define TP_MINDRAG 0x59 /* Minimum amount of force needed */ + /* to trigger dragging */ + +#define TP_THRESH 0x5C /* Minimum value for a Z-axis press */ +#define TP_UP_THRESH 0x5A /* Used to generate a 'click' on Z-axis */ +#define TP_Z_TIME 0x5E /* How sharp of a press */ +#define TP_JENKS_CURV 0x5D /* Minimum curvature for double click */ +#define TP_DRIFT_TIME 0x5F /* How long a 'hands off' condition */ + /* must last (x*107ms) for drift */ + /* correction to occur */ + +/* + * Toggling Flag bits + */ +#define TP_TOGGLE 0x47 /* Toggle command */ + +#define TP_TOGGLE_MB 0x23 /* Disable/Enable Middle Button */ +#define TP_MASK_MB 0x01 +#define TP_TOGGLE_EXT_DEV 0x23 /* Disable external device */ +#define TP_MASK_EXT_DEV 0x02 +#define TP_TOGGLE_DRIFT 0x23 /* Drift Correction */ +#define TP_MASK_DRIFT 0x80 +#define TP_TOGGLE_BURST 0x28 /* Burst Mode */ +#define TP_MASK_BURST 0x80 +#define TP_TOGGLE_PTSON 0x2C /* Press to Select */ +#define TP_MASK_PTSON 0x01 +#define TP_TOGGLE_HARD_TRANS 0x2C /* Alternate method to set Hard Transparency */ +#define TP_MASK_HARD_TRANS 0x80 +#define TP_TOGGLE_TWOHAND 0x2D /* Two handed */ +#define TP_MASK_TWOHAND 0x01 +#define TP_TOGGLE_STICKY_TWO 0x2D /* Sticky two handed */ +#define TP_MASK_STICKY_TWO 0x04 +#define TP_TOGGLE_SKIPBACK 0x2D /* Suppress movement after drag release */ +#define TP_MASK_SKIPBACK 0x08 +#define TP_TOGGLE_SOURCE_TAG 0x20 /* Bit 3 of the first packet will be set to + to the origin of the packet (external or TP) */ +#define TP_MASK_SOURCE_TAG 0x80 +#define TP_TOGGLE_EXT_TAG 0x22 /* Bit 3 of the first packet coming from the + external device will be forced to 1 */ +#define TP_MASK_EXT_TAG 0x04 + + +/* Power on Self Test Results */ +#define TP_POR_SUCCESS 0x3B + +/* + * Default power on values + */ +#define TP_DEF_SENS 0x80 +#define TP_DEF_INERTIA 0x06 +#define TP_DEF_SPEED 0x61 +#define TP_DEF_REACH 0x0A + +#define TP_DEF_DRAGHYS 0xFF +#define TP_DEF_MINDRAG 0x14 + +#define TP_DEF_THRESH 0x08 +#define TP_DEF_UP_THRESH 0xFF +#define TP_DEF_Z_TIME 0x26 +#define TP_DEF_JENKS_CURV 0x87 +#define TP_DEF_DRIFT_TIME 0x05 + +/* Toggles */ +#define TP_DEF_MB 0x00 +#define TP_DEF_PTSON 0x00 +#define TP_DEF_SKIPBACK 0x00 +#define TP_DEF_EXT_DEV 0x00 /* 0 means enabled */ +#define TP_DEF_TWOHAND 0x00 +#define TP_DEF_SOURCE_TAG 0x00 + +#define MAKE_PS2_CMD(params, results, cmd) ((params<<12) | (results<<8) | (cmd)) + +struct trackpoint_data { + u8 variant_id; + u8 firmware_id; + + u8 sensitivity, speed, inertia, reach; + u8 draghys, mindrag; + u8 thresh, upthresh; + u8 ztime, jenks; + u8 drift_time; + + /* toggles */ + bool press_to_select; + bool skipback; + bool ext_dev; +}; + +#ifdef CONFIG_MOUSE_PS2_TRACKPOINT +int trackpoint_detect(struct psmouse *psmouse, bool set_properties); +#else +static inline int trackpoint_detect(struct psmouse *psmouse, + bool set_properties) +{ + return -ENOSYS; +} +#endif /* CONFIG_MOUSE_PS2_TRACKPOINT */ + +#endif /* _TRACKPOINT_H */ diff --git a/drivers/input/mouse/vmmouse.c b/drivers/input/mouse/vmmouse.c new file mode 100644 index 000000000..1ae5c1ef3 --- /dev/null +++ b/drivers/input/mouse/vmmouse.c @@ -0,0 +1,489 @@ +/* + * Driver for Virtual PS/2 Mouse on VMware and QEMU hypervisors. + * + * Copyright (C) 2014, VMware, Inc. All Rights Reserved. + * + * 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. + * + * Twin device code is hugely inspired by the ALPS driver. + * Authors: + * Dmitry Torokhov <dmitry.torokhov@gmail.com> + * Thomas Hellstrom <thellstrom@vmware.com> + */ + +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/libps2.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <asm/hypervisor.h> + +#include "psmouse.h" +#include "vmmouse.h" + +#define VMMOUSE_PROTO_MAGIC 0x564D5868U +#define VMMOUSE_PROTO_PORT 0x5658 + +/* + * Main commands supported by the vmmouse hypervisor port. + */ +#define VMMOUSE_PROTO_CMD_GETVERSION 10 +#define VMMOUSE_PROTO_CMD_ABSPOINTER_DATA 39 +#define VMMOUSE_PROTO_CMD_ABSPOINTER_STATUS 40 +#define VMMOUSE_PROTO_CMD_ABSPOINTER_COMMAND 41 +#define VMMOUSE_PROTO_CMD_ABSPOINTER_RESTRICT 86 + +/* + * Subcommands for VMMOUSE_PROTO_CMD_ABSPOINTER_COMMAND + */ +#define VMMOUSE_CMD_ENABLE 0x45414552U +#define VMMOUSE_CMD_DISABLE 0x000000f5U +#define VMMOUSE_CMD_REQUEST_RELATIVE 0x4c455252U +#define VMMOUSE_CMD_REQUEST_ABSOLUTE 0x53424152U + +#define VMMOUSE_ERROR 0xffff0000U + +#define VMMOUSE_VERSION_ID 0x3442554aU + +#define VMMOUSE_RELATIVE_PACKET 0x00010000U + +#define VMMOUSE_LEFT_BUTTON 0x20 +#define VMMOUSE_RIGHT_BUTTON 0x10 +#define VMMOUSE_MIDDLE_BUTTON 0x08 + +/* + * VMMouse Restrict command + */ +#define VMMOUSE_RESTRICT_ANY 0x00 +#define VMMOUSE_RESTRICT_CPL0 0x01 +#define VMMOUSE_RESTRICT_IOPL 0x02 + +#define VMMOUSE_MAX_X 0xFFFF +#define VMMOUSE_MAX_Y 0xFFFF + +#define VMMOUSE_VENDOR "VMware" +#define VMMOUSE_NAME "VMMouse" + +/** + * struct vmmouse_data - private data structure for the vmmouse driver + * + * @abs_dev: "Absolute" device used to report absolute mouse movement. + * @phys: Physical path for the absolute device. + * @dev_name: Name attribute name for the absolute device. + */ +struct vmmouse_data { + struct input_dev *abs_dev; + char phys[32]; + char dev_name[128]; +}; + +/** + * Hypervisor-specific bi-directional communication channel + * implementing the vmmouse protocol. Should never execute on + * bare metal hardware. + */ +#define VMMOUSE_CMD(cmd, in1, out1, out2, out3, out4) \ +({ \ + unsigned long __dummy1, __dummy2; \ + __asm__ __volatile__ ("inl %%dx" : \ + "=a"(out1), \ + "=b"(out2), \ + "=c"(out3), \ + "=d"(out4), \ + "=S"(__dummy1), \ + "=D"(__dummy2) : \ + "a"(VMMOUSE_PROTO_MAGIC), \ + "b"(in1), \ + "c"(VMMOUSE_PROTO_CMD_##cmd), \ + "d"(VMMOUSE_PROTO_PORT) : \ + "memory"); \ +}) + +/** + * vmmouse_report_button - report button state on the correct input device + * + * @psmouse: Pointer to the psmouse struct + * @abs_dev: The absolute input device + * @rel_dev: The relative input device + * @pref_dev: The preferred device for reporting + * @code: Button code + * @value: Button value + * + * Report @value and @code on @pref_dev, unless the button is already + * pressed on the other device, in which case the state is reported on that + * device. + */ +static void vmmouse_report_button(struct psmouse *psmouse, + struct input_dev *abs_dev, + struct input_dev *rel_dev, + struct input_dev *pref_dev, + unsigned int code, int value) +{ + if (test_bit(code, abs_dev->key)) + pref_dev = abs_dev; + else if (test_bit(code, rel_dev->key)) + pref_dev = rel_dev; + + input_report_key(pref_dev, code, value); +} + +/** + * vmmouse_report_events - process events on the vmmouse communications channel + * + * @psmouse: Pointer to the psmouse struct + * + * This function pulls events from the vmmouse communications channel and + * reports them on the correct (absolute or relative) input device. When the + * communications channel is drained, or if we've processed more than 255 + * psmouse commands, the function returns PSMOUSE_FULL_PACKET. If there is a + * host- or synchronization error, the function returns PSMOUSE_BAD_DATA in + * the hope that the caller will reset the communications channel. + */ +static psmouse_ret_t vmmouse_report_events(struct psmouse *psmouse) +{ + struct input_dev *rel_dev = psmouse->dev; + struct vmmouse_data *priv = psmouse->private; + struct input_dev *abs_dev = priv->abs_dev; + struct input_dev *pref_dev; + u32 status, x, y, z; + u32 dummy1, dummy2, dummy3; + unsigned int queue_length; + unsigned int count = 255; + + while (count--) { + /* See if we have motion data. */ + VMMOUSE_CMD(ABSPOINTER_STATUS, 0, + status, dummy1, dummy2, dummy3); + if ((status & VMMOUSE_ERROR) == VMMOUSE_ERROR) { + psmouse_err(psmouse, "failed to fetch status data\n"); + /* + * After a few attempts this will result in + * reconnect. + */ + return PSMOUSE_BAD_DATA; + } + + queue_length = status & 0xffff; + if (queue_length == 0) + break; + + if (queue_length % 4) { + psmouse_err(psmouse, "invalid queue length\n"); + return PSMOUSE_BAD_DATA; + } + + /* Now get it */ + VMMOUSE_CMD(ABSPOINTER_DATA, 4, status, x, y, z); + + /* + * And report what we've got. Prefer to report button + * events on the same device where we report motion events. + * This doesn't work well with the mouse wheel, though. See + * below. Ideally we would want to report that on the + * preferred device as well. + */ + if (status & VMMOUSE_RELATIVE_PACKET) { + pref_dev = rel_dev; + input_report_rel(rel_dev, REL_X, (s32)x); + input_report_rel(rel_dev, REL_Y, -(s32)y); + } else { + pref_dev = abs_dev; + input_report_abs(abs_dev, ABS_X, x); + input_report_abs(abs_dev, ABS_Y, y); + } + + /* Xorg seems to ignore wheel events on absolute devices */ + input_report_rel(rel_dev, REL_WHEEL, -(s8)((u8) z)); + + vmmouse_report_button(psmouse, abs_dev, rel_dev, + pref_dev, BTN_LEFT, + status & VMMOUSE_LEFT_BUTTON); + vmmouse_report_button(psmouse, abs_dev, rel_dev, + pref_dev, BTN_RIGHT, + status & VMMOUSE_RIGHT_BUTTON); + vmmouse_report_button(psmouse, abs_dev, rel_dev, + pref_dev, BTN_MIDDLE, + status & VMMOUSE_MIDDLE_BUTTON); + input_sync(abs_dev); + input_sync(rel_dev); + } + + return PSMOUSE_FULL_PACKET; +} + +/** + * vmmouse_process_byte - process data on the ps/2 channel + * + * @psmouse: Pointer to the psmouse struct + * + * When the ps/2 channel indicates that there is vmmouse data available, + * call vmmouse channel processing. Otherwise, continue to accept bytes. If + * there is a synchronization or communication data error, return + * PSMOUSE_BAD_DATA in the hope that the caller will reset the mouse. + */ +static psmouse_ret_t vmmouse_process_byte(struct psmouse *psmouse) +{ + unsigned char *packet = psmouse->packet; + + switch (psmouse->pktcnt) { + case 1: + return (packet[0] & 0x8) == 0x8 ? + PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA; + + case 2: + return PSMOUSE_GOOD_DATA; + + default: + return vmmouse_report_events(psmouse); + } +} + +/** + * vmmouse_disable - Disable vmmouse + * + * @psmouse: Pointer to the psmouse struct + * + * Tries to disable vmmouse mode. + */ +static void vmmouse_disable(struct psmouse *psmouse) +{ + u32 status; + u32 dummy1, dummy2, dummy3, dummy4; + + VMMOUSE_CMD(ABSPOINTER_COMMAND, VMMOUSE_CMD_DISABLE, + dummy1, dummy2, dummy3, dummy4); + + VMMOUSE_CMD(ABSPOINTER_STATUS, 0, + status, dummy1, dummy2, dummy3); + + if ((status & VMMOUSE_ERROR) != VMMOUSE_ERROR) + psmouse_warn(psmouse, "failed to disable vmmouse device\n"); +} + +/** + * vmmouse_enable - Enable vmmouse and request absolute mode. + * + * @psmouse: Pointer to the psmouse struct + * + * Tries to enable vmmouse mode. Performs basic checks and requests + * absolute vmmouse mode. + * Returns 0 on success, -ENODEV on failure. + */ +static int vmmouse_enable(struct psmouse *psmouse) +{ + u32 status, version; + u32 dummy1, dummy2, dummy3, dummy4; + + /* + * Try enabling the device. If successful, we should be able to + * read valid version ID back from it. + */ + VMMOUSE_CMD(ABSPOINTER_COMMAND, VMMOUSE_CMD_ENABLE, + dummy1, dummy2, dummy3, dummy4); + + /* + * See if version ID can be retrieved. + */ + VMMOUSE_CMD(ABSPOINTER_STATUS, 0, status, dummy1, dummy2, dummy3); + if ((status & 0x0000ffff) == 0) { + psmouse_dbg(psmouse, "empty flags - assuming no device\n"); + return -ENXIO; + } + + VMMOUSE_CMD(ABSPOINTER_DATA, 1 /* single item */, + version, dummy1, dummy2, dummy3); + if (version != VMMOUSE_VERSION_ID) { + psmouse_dbg(psmouse, "Unexpected version value: %u vs %u\n", + (unsigned) version, VMMOUSE_VERSION_ID); + vmmouse_disable(psmouse); + return -ENXIO; + } + + /* + * Restrict ioport access, if possible. + */ + VMMOUSE_CMD(ABSPOINTER_RESTRICT, VMMOUSE_RESTRICT_CPL0, + dummy1, dummy2, dummy3, dummy4); + + VMMOUSE_CMD(ABSPOINTER_COMMAND, VMMOUSE_CMD_REQUEST_ABSOLUTE, + dummy1, dummy2, dummy3, dummy4); + + return 0; +} + +/* + * Array of supported hypervisors. + */ +static enum x86_hypervisor_type vmmouse_supported_hypervisors[] = { + X86_HYPER_VMWARE, + X86_HYPER_KVM, +}; + +/** + * vmmouse_check_hypervisor - Check if we're running on a supported hypervisor + */ +static bool vmmouse_check_hypervisor(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(vmmouse_supported_hypervisors); i++) + if (vmmouse_supported_hypervisors[i] == x86_hyper_type) + return true; + + return false; +} + +/** + * vmmouse_detect - Probe whether vmmouse is available + * + * @psmouse: Pointer to the psmouse struct + * @set_properties: Whether to set psmouse name and vendor + * + * Returns 0 if vmmouse channel is available. Negative error code if not. + */ +int vmmouse_detect(struct psmouse *psmouse, bool set_properties) +{ + u32 response, version, dummy1, dummy2; + + if (!vmmouse_check_hypervisor()) { + psmouse_dbg(psmouse, + "VMMouse not running on supported hypervisor.\n"); + return -ENXIO; + } + + /* Check if the device is present */ + response = ~VMMOUSE_PROTO_MAGIC; + VMMOUSE_CMD(GETVERSION, 0, version, response, dummy1, dummy2); + if (response != VMMOUSE_PROTO_MAGIC || version == 0xffffffffU) + return -ENXIO; + + if (set_properties) { + psmouse->vendor = VMMOUSE_VENDOR; + psmouse->name = VMMOUSE_NAME; + psmouse->model = version; + } + + return 0; +} + +/** + * vmmouse_disconnect - Take down vmmouse driver + * + * @psmouse: Pointer to the psmouse struct + * + * Takes down vmmouse driver and frees resources set up in vmmouse_init(). + */ +static void vmmouse_disconnect(struct psmouse *psmouse) +{ + struct vmmouse_data *priv = psmouse->private; + + vmmouse_disable(psmouse); + psmouse_reset(psmouse); + input_unregister_device(priv->abs_dev); + kfree(priv); +} + +/** + * vmmouse_reconnect - Reset the ps/2 - and vmmouse connections + * + * @psmouse: Pointer to the psmouse struct + * + * Attempts to reset the mouse connections. Returns 0 on success and + * -1 on failure. + */ +static int vmmouse_reconnect(struct psmouse *psmouse) +{ + int error; + + psmouse_reset(psmouse); + vmmouse_disable(psmouse); + error = vmmouse_enable(psmouse); + if (error) { + psmouse_err(psmouse, + "Unable to re-enable mouse when reconnecting, err: %d\n", + error); + return error; + } + + return 0; +} + +/** + * vmmouse_init - Initialize the vmmouse driver + * + * @psmouse: Pointer to the psmouse struct + * + * Requests the device and tries to enable vmmouse mode. + * If successful, sets up the input device for relative movement events. + * It also allocates another input device and sets it up for absolute motion + * events. Returns 0 on success and -1 on failure. + */ +int vmmouse_init(struct psmouse *psmouse) +{ + struct vmmouse_data *priv; + struct input_dev *rel_dev = psmouse->dev, *abs_dev; + int error; + + psmouse_reset(psmouse); + error = vmmouse_enable(psmouse); + if (error) + return error; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + abs_dev = input_allocate_device(); + if (!priv || !abs_dev) { + error = -ENOMEM; + goto init_fail; + } + + priv->abs_dev = abs_dev; + psmouse->private = priv; + + /* Set up and register absolute device */ + snprintf(priv->phys, sizeof(priv->phys), "%s/input1", + psmouse->ps2dev.serio->phys); + + /* Mimic name setup for relative device in psmouse-base.c */ + snprintf(priv->dev_name, sizeof(priv->dev_name), "%s %s %s", + VMMOUSE_PSNAME, VMMOUSE_VENDOR, VMMOUSE_NAME); + abs_dev->phys = priv->phys; + abs_dev->name = priv->dev_name; + abs_dev->id.bustype = BUS_I8042; + abs_dev->id.vendor = 0x0002; + abs_dev->id.product = PSMOUSE_VMMOUSE; + abs_dev->id.version = psmouse->model; + abs_dev->dev.parent = &psmouse->ps2dev.serio->dev; + + /* Set absolute device capabilities */ + input_set_capability(abs_dev, EV_KEY, BTN_LEFT); + input_set_capability(abs_dev, EV_KEY, BTN_RIGHT); + input_set_capability(abs_dev, EV_KEY, BTN_MIDDLE); + input_set_capability(abs_dev, EV_ABS, ABS_X); + input_set_capability(abs_dev, EV_ABS, ABS_Y); + input_set_abs_params(abs_dev, ABS_X, 0, VMMOUSE_MAX_X, 0, 0); + input_set_abs_params(abs_dev, ABS_Y, 0, VMMOUSE_MAX_Y, 0, 0); + + error = input_register_device(priv->abs_dev); + if (error) + goto init_fail; + + /* Add wheel capability to the relative device */ + input_set_capability(rel_dev, EV_REL, REL_WHEEL); + + psmouse->protocol_handler = vmmouse_process_byte; + psmouse->disconnect = vmmouse_disconnect; + psmouse->reconnect = vmmouse_reconnect; + + return 0; + +init_fail: + vmmouse_disable(psmouse); + psmouse_reset(psmouse); + input_free_device(abs_dev); + kfree(priv); + psmouse->private = NULL; + + return error; +} diff --git a/drivers/input/mouse/vmmouse.h b/drivers/input/mouse/vmmouse.h new file mode 100644 index 000000000..6f126017a --- /dev/null +++ b/drivers/input/mouse/vmmouse.h @@ -0,0 +1,30 @@ +/* + * Driver for Virtual PS/2 Mouse on VMware and QEMU hypervisors. + * + * Copyright (C) 2014, VMware, Inc. All Rights Reserved. + * + * 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. + */ + +#ifndef _VMMOUSE_H +#define _VMMOUSE_H + +#ifdef CONFIG_MOUSE_PS2_VMMOUSE +#define VMMOUSE_PSNAME "VirtualPS/2" + +int vmmouse_detect(struct psmouse *psmouse, bool set_properties); +int vmmouse_init(struct psmouse *psmouse); +#else +static inline int vmmouse_detect(struct psmouse *psmouse, bool set_properties) +{ + return -ENOSYS; +} +static inline int vmmouse_init(struct psmouse *psmouse) +{ + return -ENOSYS; +} +#endif + +#endif diff --git a/drivers/input/mouse/vsxxxaa.c b/drivers/input/mouse/vsxxxaa.c new file mode 100644 index 000000000..abd494411 --- /dev/null +++ b/drivers/input/mouse/vsxxxaa.c @@ -0,0 +1,550 @@ +/* + * Driver for DEC VSXXX-AA mouse (hockey-puck mouse, ball or two rollers) + * DEC VSXXX-GA mouse (rectangular mouse, with ball) + * DEC VSXXX-AB tablet (digitizer with hair cross or stylus) + * + * Copyright (C) 2003-2004 by Jan-Benedict Glaw <jbglaw@lug-owl.de> + * + * The packet format was initially taken from a patch to GPM which is (C) 2001 + * by Karsten Merker <merker@linuxtag.org> + * and Maciej W. Rozycki <macro@ds2.pg.gda.pl> + * Later on, I had access to the device's documentation (referenced below). + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Building an adaptor to DE9 / DB25 RS232 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * DISCLAIMER: Use this description AT YOUR OWN RISK! I'll not pay for + * anything if you break your mouse, your computer or whatever! + * + * In theory, this mouse is a simple RS232 device. In practice, it has got + * a quite uncommon plug and the requirement to additionally get a power + * supply at +5V and -12V. + * + * If you look at the socket/jack (_not_ at the plug), we use this pin + * numbering: + * _______ + * / 7 6 5 \ + * | 4 --- 3 | + * \ 2 1 / + * ------- + * + * DEC socket DE9 DB25 Note + * 1 (GND) 5 7 - + * 2 (RxD) 2 3 - + * 3 (TxD) 3 2 - + * 4 (-12V) - - Somewhere from the PSU. At ATX, it's + * the thin blue wire at pin 12 of the + * ATX power connector. Only required for + * VSXXX-AA/-GA mice. + * 5 (+5V) - - PSU (red wires of ATX power connector + * on pin 4, 6, 19 or 20) or HDD power + * connector (also red wire). + * 6 (+12V) - - HDD power connector, yellow wire. Only + * required for VSXXX-AB digitizer. + * 7 (dev. avail.) - - The mouse shorts this one to pin 1. + * This way, the host computer can detect + * the mouse. To use it with the adaptor, + * simply don't connect this pin. + * + * So to get a working adaptor, you need to connect the mouse with three + * wires to a RS232 port and two or three additional wires for +5V, +12V and + * -12V to the PSU. + * + * Flow specification for the link is 4800, 8o1. + * + * The mice and tablet are described in "VCB02 Video Subsystem - Technical + * Manual", DEC EK-104AA-TM-001. You'll find it at MANX, a search engine + * specific for DEC documentation. Try + * http://www.vt100.net/manx/details?pn=EK-104AA-TM-001;id=21;cp=1 + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/serio.h> + +#define DRIVER_DESC "Driver for DEC VSXXX-AA and -GA mice and VSXXX-AB tablet" + +MODULE_AUTHOR("Jan-Benedict Glaw <jbglaw@lug-owl.de>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#undef VSXXXAA_DEBUG +#ifdef VSXXXAA_DEBUG +#define DBG(x...) printk(x) +#else +#define DBG(x...) do {} while (0) +#endif + +#define VSXXXAA_INTRO_MASK 0x80 +#define VSXXXAA_INTRO_HEAD 0x80 +#define IS_HDR_BYTE(x) \ + (((x) & VSXXXAA_INTRO_MASK) == VSXXXAA_INTRO_HEAD) + +#define VSXXXAA_PACKET_MASK 0xe0 +#define VSXXXAA_PACKET_REL 0x80 +#define VSXXXAA_PACKET_ABS 0xc0 +#define VSXXXAA_PACKET_POR 0xa0 +#define MATCH_PACKET_TYPE(data, type) \ + (((data) & VSXXXAA_PACKET_MASK) == (type)) + + + +struct vsxxxaa { + struct input_dev *dev; + struct serio *serio; +#define BUFLEN 15 /* At least 5 is needed for a full tablet packet */ + unsigned char buf[BUFLEN]; + unsigned char count; + unsigned char version; + unsigned char country; + unsigned char type; + char name[64]; + char phys[32]; +}; + +static void vsxxxaa_drop_bytes(struct vsxxxaa *mouse, int num) +{ + if (num >= mouse->count) { + mouse->count = 0; + } else { + memmove(mouse->buf, mouse->buf + num, BUFLEN - num); + mouse->count -= num; + } +} + +static void vsxxxaa_queue_byte(struct vsxxxaa *mouse, unsigned char byte) +{ + if (mouse->count == BUFLEN) { + printk(KERN_ERR "%s on %s: Dropping a byte of full buffer.\n", + mouse->name, mouse->phys); + vsxxxaa_drop_bytes(mouse, 1); + } + + DBG(KERN_INFO "Queueing byte 0x%02x\n", byte); + + mouse->buf[mouse->count++] = byte; +} + +static void vsxxxaa_detection_done(struct vsxxxaa *mouse) +{ + switch (mouse->type) { + case 0x02: + strlcpy(mouse->name, "DEC VSXXX-AA/-GA mouse", + sizeof(mouse->name)); + break; + + case 0x04: + strlcpy(mouse->name, "DEC VSXXX-AB digitizer", + sizeof(mouse->name)); + break; + + default: + snprintf(mouse->name, sizeof(mouse->name), + "unknown DEC pointer device (type = 0x%02x)", + mouse->type); + break; + } + + printk(KERN_INFO + "Found %s version 0x%02x from country 0x%02x on port %s\n", + mouse->name, mouse->version, mouse->country, mouse->phys); +} + +/* + * Returns number of bytes to be dropped, 0 if packet is okay. + */ +static int vsxxxaa_check_packet(struct vsxxxaa *mouse, int packet_len) +{ + int i; + + /* First byte must be a header byte */ + if (!IS_HDR_BYTE(mouse->buf[0])) { + DBG("vsck: len=%d, 1st=0x%02x\n", packet_len, mouse->buf[0]); + return 1; + } + + /* Check all following bytes */ + for (i = 1; i < packet_len; i++) { + if (IS_HDR_BYTE(mouse->buf[i])) { + printk(KERN_ERR + "Need to drop %d bytes of a broken packet.\n", + i - 1); + DBG(KERN_INFO "check: len=%d, b[%d]=0x%02x\n", + packet_len, i, mouse->buf[i]); + return i - 1; + } + } + + return 0; +} + +static inline int vsxxxaa_smells_like_packet(struct vsxxxaa *mouse, + unsigned char type, size_t len) +{ + return mouse->count >= len && MATCH_PACKET_TYPE(mouse->buf[0], type); +} + +static void vsxxxaa_handle_REL_packet(struct vsxxxaa *mouse) +{ + struct input_dev *dev = mouse->dev; + unsigned char *buf = mouse->buf; + int left, middle, right; + int dx, dy; + + /* + * Check for normal stream packets. This is three bytes, + * with the first byte's 3 MSB set to 100. + * + * [0]: 1 0 0 SignX SignY Left Middle Right + * [1]: 0 dx dx dx dx dx dx dx + * [2]: 0 dy dy dy dy dy dy dy + */ + + /* + * Low 7 bit of byte 1 are abs(dx), bit 7 is + * 0, bit 4 of byte 0 is direction. + */ + dx = buf[1] & 0x7f; + dx *= ((buf[0] >> 4) & 0x01) ? 1 : -1; + + /* + * Low 7 bit of byte 2 are abs(dy), bit 7 is + * 0, bit 3 of byte 0 is direction. + */ + dy = buf[2] & 0x7f; + dy *= ((buf[0] >> 3) & 0x01) ? -1 : 1; + + /* + * Get button state. It's the low three bits + * (for three buttons) of byte 0. + */ + left = buf[0] & 0x04; + middle = buf[0] & 0x02; + right = buf[0] & 0x01; + + vsxxxaa_drop_bytes(mouse, 3); + + DBG(KERN_INFO "%s on %s: dx=%d, dy=%d, buttons=%s%s%s\n", + mouse->name, mouse->phys, dx, dy, + left ? "L" : "l", middle ? "M" : "m", right ? "R" : "r"); + + /* + * Report what we've found so far... + */ + input_report_key(dev, BTN_LEFT, left); + input_report_key(dev, BTN_MIDDLE, middle); + input_report_key(dev, BTN_RIGHT, right); + input_report_key(dev, BTN_TOUCH, 0); + input_report_rel(dev, REL_X, dx); + input_report_rel(dev, REL_Y, dy); + input_sync(dev); +} + +static void vsxxxaa_handle_ABS_packet(struct vsxxxaa *mouse) +{ + struct input_dev *dev = mouse->dev; + unsigned char *buf = mouse->buf; + int left, middle, right, touch; + int x, y; + + /* + * Tablet position / button packet + * + * [0]: 1 1 0 B4 B3 B2 B1 Pr + * [1]: 0 0 X5 X4 X3 X2 X1 X0 + * [2]: 0 0 X11 X10 X9 X8 X7 X6 + * [3]: 0 0 Y5 Y4 Y3 Y2 Y1 Y0 + * [4]: 0 0 Y11 Y10 Y9 Y8 Y7 Y6 + */ + + /* + * Get X/Y position. Y axis needs to be inverted since VSXXX-AB + * counts down->top while monitor counts top->bottom. + */ + x = ((buf[2] & 0x3f) << 6) | (buf[1] & 0x3f); + y = ((buf[4] & 0x3f) << 6) | (buf[3] & 0x3f); + y = 1023 - y; + + /* + * Get button state. It's bits <4..1> of byte 0. + */ + left = buf[0] & 0x02; + middle = buf[0] & 0x04; + right = buf[0] & 0x08; + touch = buf[0] & 0x10; + + vsxxxaa_drop_bytes(mouse, 5); + + DBG(KERN_INFO "%s on %s: x=%d, y=%d, buttons=%s%s%s%s\n", + mouse->name, mouse->phys, x, y, + left ? "L" : "l", middle ? "M" : "m", + right ? "R" : "r", touch ? "T" : "t"); + + /* + * Report what we've found so far... + */ + input_report_key(dev, BTN_LEFT, left); + input_report_key(dev, BTN_MIDDLE, middle); + input_report_key(dev, BTN_RIGHT, right); + input_report_key(dev, BTN_TOUCH, touch); + input_report_abs(dev, ABS_X, x); + input_report_abs(dev, ABS_Y, y); + input_sync(dev); +} + +static void vsxxxaa_handle_POR_packet(struct vsxxxaa *mouse) +{ + struct input_dev *dev = mouse->dev; + unsigned char *buf = mouse->buf; + int left, middle, right; + unsigned char error; + + /* + * Check for Power-On-Reset packets. These are sent out + * after plugging the mouse in, or when explicitly + * requested by sending 'T'. + * + * [0]: 1 0 1 0 R3 R2 R1 R0 + * [1]: 0 M2 M1 M0 D3 D2 D1 D0 + * [2]: 0 E6 E5 E4 E3 E2 E1 E0 + * [3]: 0 0 0 0 0 Left Middle Right + * + * M: manufacturer location code + * R: revision code + * E: Error code. If it's in the range of 0x00..0x1f, only some + * minor problem occurred. Errors >= 0x20 are considered bad + * and the device may not work properly... + * D: <0010> == mouse, <0100> == tablet + */ + + mouse->version = buf[0] & 0x0f; + mouse->country = (buf[1] >> 4) & 0x07; + mouse->type = buf[1] & 0x0f; + error = buf[2] & 0x7f; + + /* + * Get button state. It's the low three bits + * (for three buttons) of byte 0. Maybe even the bit <3> + * has some meaning if a tablet is attached. + */ + left = buf[0] & 0x04; + middle = buf[0] & 0x02; + right = buf[0] & 0x01; + + vsxxxaa_drop_bytes(mouse, 4); + vsxxxaa_detection_done(mouse); + + if (error <= 0x1f) { + /* No (serious) error. Report buttons */ + input_report_key(dev, BTN_LEFT, left); + input_report_key(dev, BTN_MIDDLE, middle); + input_report_key(dev, BTN_RIGHT, right); + input_report_key(dev, BTN_TOUCH, 0); + input_sync(dev); + + if (error != 0) + printk(KERN_INFO "Your %s on %s reports error=0x%02x\n", + mouse->name, mouse->phys, error); + + } + + /* + * If the mouse was hot-plugged, we need to force differential mode + * now... However, give it a second to recover from it's reset. + */ + printk(KERN_NOTICE + "%s on %s: Forcing standard packet format, " + "incremental streaming mode and 72 samples/sec\n", + mouse->name, mouse->phys); + serio_write(mouse->serio, 'S'); /* Standard format */ + mdelay(50); + serio_write(mouse->serio, 'R'); /* Incremental */ + mdelay(50); + serio_write(mouse->serio, 'L'); /* 72 samples/sec */ +} + +static void vsxxxaa_parse_buffer(struct vsxxxaa *mouse) +{ + unsigned char *buf = mouse->buf; + int stray_bytes; + + /* + * Parse buffer to death... + */ + do { + /* + * Out of sync? Throw away what we don't understand. Each + * packet starts with a byte whose bit 7 is set. Unhandled + * packets (ie. which we don't know about or simply b0rk3d + * data...) will get shifted out of the buffer after some + * activity on the mouse. + */ + while (mouse->count > 0 && !IS_HDR_BYTE(buf[0])) { + printk(KERN_ERR "%s on %s: Dropping a byte to regain " + "sync with mouse data stream...\n", + mouse->name, mouse->phys); + vsxxxaa_drop_bytes(mouse, 1); + } + + /* + * Check for packets we know about. + */ + + if (vsxxxaa_smells_like_packet(mouse, VSXXXAA_PACKET_REL, 3)) { + /* Check for broken packet */ + stray_bytes = vsxxxaa_check_packet(mouse, 3); + if (!stray_bytes) + vsxxxaa_handle_REL_packet(mouse); + + } else if (vsxxxaa_smells_like_packet(mouse, + VSXXXAA_PACKET_ABS, 5)) { + /* Check for broken packet */ + stray_bytes = vsxxxaa_check_packet(mouse, 5); + if (!stray_bytes) + vsxxxaa_handle_ABS_packet(mouse); + + } else if (vsxxxaa_smells_like_packet(mouse, + VSXXXAA_PACKET_POR, 4)) { + /* Check for broken packet */ + stray_bytes = vsxxxaa_check_packet(mouse, 4); + if (!stray_bytes) + vsxxxaa_handle_POR_packet(mouse); + + } else { + break; /* No REL, ABS or POR packet found */ + } + + if (stray_bytes > 0) { + printk(KERN_ERR "Dropping %d bytes now...\n", + stray_bytes); + vsxxxaa_drop_bytes(mouse, stray_bytes); + } + + } while (1); +} + +static irqreturn_t vsxxxaa_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct vsxxxaa *mouse = serio_get_drvdata(serio); + + vsxxxaa_queue_byte(mouse, data); + vsxxxaa_parse_buffer(mouse); + + return IRQ_HANDLED; +} + +static void vsxxxaa_disconnect(struct serio *serio) +{ + struct vsxxxaa *mouse = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(mouse->dev); + kfree(mouse); +} + +static int vsxxxaa_connect(struct serio *serio, struct serio_driver *drv) +{ + struct vsxxxaa *mouse; + struct input_dev *input_dev; + int err = -ENOMEM; + + mouse = kzalloc(sizeof(struct vsxxxaa), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!mouse || !input_dev) + goto fail1; + + mouse->dev = input_dev; + mouse->serio = serio; + strlcat(mouse->name, "DEC VSXXX-AA/-GA mouse or VSXXX-AB digitizer", + sizeof(mouse->name)); + snprintf(mouse->phys, sizeof(mouse->phys), "%s/input0", serio->phys); + + input_dev->name = mouse->name; + input_dev->phys = mouse->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->dev.parent = &serio->dev; + + __set_bit(EV_KEY, input_dev->evbit); /* We have buttons */ + __set_bit(EV_REL, input_dev->evbit); + __set_bit(EV_ABS, input_dev->evbit); + __set_bit(BTN_LEFT, input_dev->keybit); /* We have 3 buttons */ + __set_bit(BTN_MIDDLE, input_dev->keybit); + __set_bit(BTN_RIGHT, input_dev->keybit); + __set_bit(BTN_TOUCH, input_dev->keybit); /* ...and Tablet */ + __set_bit(REL_X, input_dev->relbit); + __set_bit(REL_Y, input_dev->relbit); + input_set_abs_params(input_dev, ABS_X, 0, 1023, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 0, 1023, 0, 0); + + serio_set_drvdata(serio, mouse); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + /* + * Request selftest. Standard packet format and differential + * mode will be requested after the device ID'ed successfully. + */ + serio_write(serio, 'T'); /* Test */ + + err = input_register_device(input_dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(mouse); + return err; +} + +static struct serio_device_id vsxxaa_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_VSXXXAA, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, vsxxaa_serio_ids); + +static struct serio_driver vsxxxaa_drv = { + .driver = { + .name = "vsxxxaa", + }, + .description = DRIVER_DESC, + .id_table = vsxxaa_serio_ids, + .connect = vsxxxaa_connect, + .interrupt = vsxxxaa_interrupt, + .disconnect = vsxxxaa_disconnect, +}; + +module_serio_driver(vsxxxaa_drv); |