summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2015-02-11 09:32:08 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2015-02-11 09:32:08 -0800
commit718749d56214aa97015fe01b76b6d6dd0c171796 (patch)
tree2f7355a3527c5579dc76721203d8a0011a40b19b
parente0c8453769fcaec654cd5870e84c63175658c842 (diff)
parent4ba24fef3eb3b142197135223b90ced2f319cd53 (diff)
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input
Pull input updates from Dmitry Torokhov: "The first round of updates for the input subsystem. A few new drivers (power button handler for AXP20x PMIC, tps65218 power button driver, sun4i keys driver, regulator haptic driver, NI Ettus Research USRP E3x0 button, Alwinner A10/A20 PS/2 controller). Updates to Synaptics and ALPS touchpad drivers (with more to come later), brand new Focaltech PS/2 support, update to Cypress driver to handle Gen5 (in addition to Gen3) devices, and number of other fixups to various drivers as well as input core" * 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input: (54 commits) Input: elan_i2c - fix wrong %p extension Input: evdev - do not queue SYN_DROPPED if queue is empty Input: gscps2 - fix MODULE_DEVICE_TABLE invocation Input: synaptics - use dmax in input_mt_assign_slots Input: pxa27x_keypad - remove unnecessary ARM includes Input: ti_am335x_tsc - replace delta filtering with median filtering ARM: dts: AM335x: Make charge delay a DT parameter for TSC Input: ti_am335x_tsc - read charge delay from DT Input: ti_am335x_tsc - remove udelay in interrupt handler Input: ti_am335x_tsc - interchange touchscreen and ADC steps Input: MT - add support for balanced slot assignment Input: drv2667 - remove wrong and unneeded drv2667-haptics modalias Input: drv260x - remove wrong and unneeded drv260x-haptics modalias Input: cap11xx - remove wrong and unneeded cap11xx modalias Input: sun4i-ts - add support for touchpanel controller on A31 Input: serio - add support for Alwinner A10/A20 PS/2 controller Input: gtco - use sign_extend32() for sign extension Input: elan_i2c - verify firmware signature applying it Input: elantech - remove stale comment from Kconfig Input: cyapa - off by one in cyapa_update_fw_store() ...
-rw-r--r--Documentation/ABI/testing/sysfs-driver-input-axp-pek11
-rw-r--r--Documentation/devicetree/bindings/input/e3x0-button.txt25
-rw-r--r--Documentation/devicetree/bindings/input/regulator-haptic.txt21
-rw-r--r--Documentation/devicetree/bindings/input/sun4i-lradc-keys.txt62
-rw-r--r--Documentation/devicetree/bindings/input/touchscreen/sun4i.txt4
-rw-r--r--Documentation/devicetree/bindings/input/touchscreen/ti-tsc-adc.txt15
-rw-r--r--Documentation/devicetree/bindings/input/tps65218-pwrbutton.txt17
-rw-r--r--Documentation/devicetree/bindings/serio/allwinner,sun4i-ps2.txt23
-rw-r--r--Documentation/devicetree/bindings/vendor-prefixes.txt1
-rw-r--r--MAINTAINERS15
-rw-r--r--arch/arm/boot/dts/am335x-evm.dts1
-rw-r--r--drivers/iio/adc/ti_am335x_adc.c5
-rw-r--r--drivers/input/evdev.c71
-rw-r--r--drivers/input/input-mt.c31
-rw-r--r--drivers/input/input.c38
-rw-r--r--drivers/input/keyboard/Kconfig10
-rw-r--r--drivers/input/keyboard/Makefile1
-rw-r--r--drivers/input/keyboard/atakbd.c2
-rw-r--r--drivers/input/keyboard/cap11xx.c1
-rw-r--r--drivers/input/keyboard/imx_keypad.c3
-rw-r--r--drivers/input/keyboard/pxa27x_keypad.c5
-rw-r--r--drivers/input/keyboard/sun4i-lradc-keys.c286
-rw-r--r--drivers/input/misc/Kconfig43
-rw-r--r--drivers/input/misc/Makefile4
-rw-r--r--drivers/input/misc/axp20x-pek.c290
-rw-r--r--drivers/input/misc/drv260x.c1
-rw-r--r--drivers/input/misc/drv2667.c1
-rw-r--r--drivers/input/misc/e3x0-button.c157
-rw-r--r--drivers/input/misc/regulator-haptic.c266
-rw-r--r--drivers/input/misc/tps65218-pwrbutton.c126
-rw-r--r--drivers/input/mouse/Kconfig22
-rw-r--r--drivers/input/mouse/Makefile3
-rw-r--r--drivers/input/mouse/alps.c42
-rw-r--r--drivers/input/mouse/bcm5974.c2
-rw-r--r--drivers/input/mouse/cyapa.c1710
-rw-r--r--drivers/input/mouse/cyapa.h301
-rw-r--r--drivers/input/mouse/cyapa_gen3.c1247
-rw-r--r--drivers/input/mouse/cyapa_gen5.c2777
-rw-r--r--drivers/input/mouse/cypress_ps2.c2
-rw-r--r--drivers/input/mouse/elan_i2c.h6
-rw-r--r--drivers/input/mouse/elan_i2c_core.c21
-rw-r--r--drivers/input/mouse/elan_i2c_i2c.c1
-rw-r--r--drivers/input/mouse/elan_i2c_smbus.c3
-rw-r--r--drivers/input/mouse/focaltech.c408
-rw-r--r--drivers/input/mouse/focaltech.h2
-rw-r--r--drivers/input/mouse/psmouse-base.c32
-rw-r--r--drivers/input/mouse/psmouse.h1
-rw-r--r--drivers/input/mouse/synaptics.c438
-rw-r--r--drivers/input/mouse/synaptics.h18
-rw-r--r--drivers/input/serio/Kconfig10
-rw-r--r--drivers/input/serio/Makefile1
-rw-r--r--drivers/input/serio/gscps2.c2
-rw-r--r--drivers/input/serio/sun4i-ps2.c340
-rw-r--r--drivers/input/tablet/gtco.c20
-rw-r--r--drivers/input/touchscreen/elants_i2c.c2
-rw-r--r--drivers/input/touchscreen/pixcir_i2c_ts.c2
-rw-r--r--drivers/input/touchscreen/sun4i-ts.c78
-rw-r--r--drivers/input/touchscreen/ti_am335x_tsc.c197
-rw-r--r--include/linux/input/mt.h3
-rw-r--r--include/linux/mfd/ti_am335x_tscadc.h3
-rw-r--r--include/linux/platform_data/regulator-haptic.h29
61 files changed, 7957 insertions, 1302 deletions
diff --git a/Documentation/ABI/testing/sysfs-driver-input-axp-pek b/Documentation/ABI/testing/sysfs-driver-input-axp-pek
new file mode 100644
index 000000000000..a5e671b9fa79
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-input-axp-pek
@@ -0,0 +1,11 @@
+What: /sys/class/input/input(x)/device/startup
+Date: March 2014
+Contact: Carlo Caione <carlo@caione.org>
+Description: Startup time in us. Board is powered on if the button is pressed
+ for more than <startup_time>
+
+What: /sys/class/input/input(x)/device/shutdown
+Date: March 2014
+Contact: Carlo Caione <carlo@caione.org>
+Description: Shutdown time in us. Board is powered off if the button is pressed
+ for more than <shutdown_time>
diff --git a/Documentation/devicetree/bindings/input/e3x0-button.txt b/Documentation/devicetree/bindings/input/e3x0-button.txt
new file mode 100644
index 000000000000..751665e8e47a
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/e3x0-button.txt
@@ -0,0 +1,25 @@
+National Instruments Ettus Research USRP E3x0 button driver
+
+This module is part of the NI Ettus Research USRP E3x0 SDR.
+
+This module provides a simple power button event via two interrupts.
+
+Required properties:
+- compatible: should be one of the following
+ - "ettus,e3x0-button": For devices such as the NI Ettus Research USRP E3x0
+- interrupt-parent:
+ - a phandle to the interrupt controller that it is attached to.
+- interrupts: should be one of the following
+ - <0 30 1>, <0 31 1>: For devices such as the NI Ettus Research USRP E3x0
+- interrupt-names: should be one of the following
+ - "press", "release": For devices such as the NI Ettus Research USRP E3x0
+
+Note: Interrupt numbers might vary depending on the FPGA configuration.
+
+Example:
+ button {
+ compatible = "ettus,e3x0-button";
+ interrupt-parent = <&intc>;
+ interrupts = <0 30 1>, <0 31 1>;
+ interrupt-names = "press", "release";
+ }
diff --git a/Documentation/devicetree/bindings/input/regulator-haptic.txt b/Documentation/devicetree/bindings/input/regulator-haptic.txt
new file mode 100644
index 000000000000..3ed1c7eb2f97
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/regulator-haptic.txt
@@ -0,0 +1,21 @@
+* Regulator Haptic Device Tree Bindings
+
+Required Properties:
+ - compatible : Should be "regulator-haptic"
+ - haptic-supply : Power supply to the haptic motor.
+ [*] refer Documentation/devicetree/bindings/regulator/regulator.txt
+
+ - max-microvolt : The maximum voltage value supplied to the haptic motor.
+ [The unit of the voltage is a micro]
+
+ - min-microvolt : The minimum voltage value supplied to the haptic motor.
+ [The unit of the voltage is a micro]
+
+Example:
+
+ haptics {
+ compatible = "regulator-haptic";
+ haptic-supply = <&motor_regulator>;
+ max-microvolt = <2700000>;
+ min-microvolt = <1100000>;
+ };
diff --git a/Documentation/devicetree/bindings/input/sun4i-lradc-keys.txt b/Documentation/devicetree/bindings/input/sun4i-lradc-keys.txt
new file mode 100644
index 000000000000..b9c32f6fd687
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/sun4i-lradc-keys.txt
@@ -0,0 +1,62 @@
+Allwinner sun4i low res adc attached tablet keys
+------------------------------------------------
+
+Required properties:
+ - compatible: "allwinner,sun4i-a10-lradc-keys"
+ - reg: mmio address range of the chip
+ - interrupts: interrupt to which the chip is connected
+ - vref-supply: powersupply for the lradc reference voltage
+
+Each key is represented as a sub-node of "allwinner,sun4i-a10-lradc-keys":
+
+Required subnode-properties:
+ - label: Descriptive name of the key.
+ - linux,code: Keycode to emit.
+ - channel: Channel this key is attached to, mut be 0 or 1.
+ - voltage: Voltage in µV at lradc input when this key is pressed.
+
+Example:
+
+#include <dt-bindings/input/input.h>
+
+ lradc: lradc@01c22800 {
+ compatible = "allwinner,sun4i-a10-lradc-keys";
+ reg = <0x01c22800 0x100>;
+ interrupts = <31>;
+ vref-supply = <&reg_vcc3v0>;
+
+ button@191 {
+ label = "Volume Up";
+ linux,code = <KEY_VOLUMEUP>;
+ channel = <0>;
+ voltage = <191274>;
+ };
+
+ button@392 {
+ label = "Volume Down";
+ linux,code = <KEY_VOLUMEDOWN>;
+ channel = <0>;
+ voltage = <392644>;
+ };
+
+ button@601 {
+ label = "Menu";
+ linux,code = <KEY_MENU>;
+ channel = <0>;
+ voltage = <601151>;
+ };
+
+ button@795 {
+ label = "Enter";
+ linux,code = <KEY_ENTER>;
+ channel = <0>;
+ voltage = <795090>;
+ };
+
+ button@987 {
+ label = "Home";
+ linux,code = <KEY_HOMEPAGE>;
+ channel = <0>;
+ voltage = <987387>;
+ };
+ };
diff --git a/Documentation/devicetree/bindings/input/touchscreen/sun4i.txt b/Documentation/devicetree/bindings/input/touchscreen/sun4i.txt
index aef57791f40b..433332d3b2ba 100644
--- a/Documentation/devicetree/bindings/input/touchscreen/sun4i.txt
+++ b/Documentation/devicetree/bindings/input/touchscreen/sun4i.txt
@@ -2,9 +2,10 @@ sun4i resistive touchscreen controller
--------------------------------------
Required properties:
- - compatible: "allwinner,sun4i-a10-ts"
+ - compatible: "allwinner,sun4i-a10-ts" or "allwinner,sun6i-a31-ts"
- reg: mmio address range of the chip
- interrupts: interrupt to which the chip is connected
+ - #thermal-sensor-cells: shall be 0
Optional properties:
- allwinner,ts-attached: boolean indicating that an actual touchscreen is
@@ -17,4 +18,5 @@ Example:
reg = <0x01c25000 0x100>;
interrupts = <29>;
allwinner,ts-attached;
+ #thermal-sensor-cells = <0>;
};
diff --git a/Documentation/devicetree/bindings/input/touchscreen/ti-tsc-adc.txt b/Documentation/devicetree/bindings/input/touchscreen/ti-tsc-adc.txt
index 878549ba814d..6c4fb34823d3 100644
--- a/Documentation/devicetree/bindings/input/touchscreen/ti-tsc-adc.txt
+++ b/Documentation/devicetree/bindings/input/touchscreen/ti-tsc-adc.txt
@@ -28,6 +28,20 @@ Required properties:
ti,adc-channels: List of analog inputs available for ADC.
AIN0 = 0, AIN1 = 1 and so on till AIN7 = 7.
+Optional properties:
+- child "tsc"
+ ti,charge-delay: Length of touch screen charge delay step in terms of
+ ADC clock cycles. Charge delay value should be large
+ in order to avoid false pen-up events. This value
+ effects the overall sampling speed, hence need to be
+ kept as low as possible, while avoiding false pen-up
+ event. Start from a lower value, say 0x400, and
+ increase value until false pen-up events are avoided.
+ The pen-up detection happens immediately after the
+ charge step, so this does in fact function as a
+ hardware knob for adjusting the amount of "settling
+ time".
+
Example:
tscadc: tscadc@44e0d000 {
compatible = "ti,am3359-tscadc";
@@ -36,6 +50,7 @@ Example:
ti,x-plate-resistance = <200>;
ti,coordiante-readouts = <5>;
ti,wire-config = <0x00 0x11 0x22 0x33>;
+ ti,charge-delay = <0x400>;
};
adc {
diff --git a/Documentation/devicetree/bindings/input/tps65218-pwrbutton.txt b/Documentation/devicetree/bindings/input/tps65218-pwrbutton.txt
new file mode 100644
index 000000000000..e30e0b93f2b3
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/tps65218-pwrbutton.txt
@@ -0,0 +1,17 @@
+Texas Instruments TPS65218 power button
+
+This driver provides a simple power button event via an Interrupt.
+
+Required properties:
+- compatible: should be "ti,tps65218-pwrbutton"
+- interrupts: should be one of the following
+ - <3 IRQ_TYPE_EDGE_BOTH>: For controllers compatible with tps65218
+
+Example:
+
+&tps {
+ power-button {
+ compatible = "ti,tps65218-pwrbutton";
+ interrupts = <3 IRQ_TYPE_EDGE_BOTH>;
+ };
+};
diff --git a/Documentation/devicetree/bindings/serio/allwinner,sun4i-ps2.txt b/Documentation/devicetree/bindings/serio/allwinner,sun4i-ps2.txt
new file mode 100644
index 000000000000..362a76925bcd
--- /dev/null
+++ b/Documentation/devicetree/bindings/serio/allwinner,sun4i-ps2.txt
@@ -0,0 +1,23 @@
+* Device tree bindings for Allwinner A10, A20 PS2 host controller
+
+A20 PS2 is dual role controller (PS2 host and PS2 device). These bindings are
+for PS2 A10/A20 host controller. IBM compliant IBM PS2 and AT-compatible keyboard
+and mouse can be connected.
+
+Required properties:
+
+ - reg : Offset and length of the register set for the device.
+ - compatible : Should be as of the following:
+ - "allwinner,sun4i-a10-ps2"
+ - interrupts : The interrupt line connected to the PS2.
+ - clocks : The gate clk connected to the PS2.
+
+
+Example:
+ ps20: ps2@0x01c2a000 {
+ compatible = "allwinner,sun4i-a10-ps2";
+ reg = <0x01c2a000 0x400>;
+ interrupts = <0 62 4>;
+ clocks = <&apb1_gates 6>;
+ status = "disabled";
+ };
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index d443279c95dc..96a17541391e 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -54,6 +54,7 @@ epcos EPCOS AG
epfl Ecole Polytechnique Fédérale de Lausanne
epson Seiko Epson Corp.
est ESTeem Wireless Modems
+ettus NI Ettus Research
eukrea Eukréa Electromatique
everest Everest Semiconductor Co. Ltd.
excito Excito
diff --git a/MAINTAINERS b/MAINTAINERS
index 249498844a43..9c632548d3bd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3479,6 +3479,14 @@ M: "Maciej W. Rozycki" <macro@linux-mips.org>
S: Maintained
F: drivers/tty/serial/dz.*
+E3X0 POWER BUTTON DRIVER
+M: Moritz Fischer <moritz.fischer@ettus.com>
+L: usrp-users@lists.ettus.com
+W: http://www.ettus.com
+S: Supported
+F: drivers/input/misc/e3x0-button.c
+F: Documentation/devicetree/bindings/input/e3x0-button.txt
+
E4000 MEDIA DRIVER
M: Antti Palosaari <crope@iki.fi>
L: linux-media@vger.kernel.org
@@ -9281,6 +9289,13 @@ F: arch/m68k/sun3*/
F: arch/m68k/include/asm/sun3*
F: drivers/net/ethernet/i825xx/sun3*
+SUN4I LOW RES ADC ATTACHED TABLET KEYS DRIVER
+M: Hans de Goede <hdegoede@redhat.com>
+L: linux-input@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/input/sun4i-lradc-keys.txt
+F: drivers/input/keyboard/sun4i-lradc-keys.c
+
SUNDANCE NETWORK DRIVER
M: Denis Kirjanov <kda@linux-powerpc.org>
L: netdev@vger.kernel.org
diff --git a/arch/arm/boot/dts/am335x-evm.dts b/arch/arm/boot/dts/am335x-evm.dts
index 54f118c08db8..66342515df20 100644
--- a/arch/arm/boot/dts/am335x-evm.dts
+++ b/arch/arm/boot/dts/am335x-evm.dts
@@ -648,6 +648,7 @@
ti,x-plate-resistance = <200>;
ti,coordinate-readouts = <5>;
ti,wire-config = <0x00 0x11 0x22 0x33>;
+ ti,charge-delay = <0x400>;
};
adc {
diff --git a/drivers/iio/adc/ti_am335x_adc.c b/drivers/iio/adc/ti_am335x_adc.c
index b730864731e8..adba23246474 100644
--- a/drivers/iio/adc/ti_am335x_adc.c
+++ b/drivers/iio/adc/ti_am335x_adc.c
@@ -86,19 +86,18 @@ static void tiadc_step_config(struct iio_dev *indio_dev)
{
struct tiadc_device *adc_dev = iio_priv(indio_dev);
unsigned int stepconfig;
- int i, steps;
+ int i, steps = 0;
/*
* There are 16 configurable steps and 8 analog input
* lines available which are shared between Touchscreen and ADC.
*
- * Steps backwards i.e. from 16 towards 0 are used by ADC
+ * Steps forwards i.e. from 0 towards 16 are used by ADC
* depending on number of input lines needed.
* Channel would represent which analog input
* needs to be given to ADC to digitalize data.
*/
- steps = TOTAL_STEPS - adc_dev->channels;
if (iio_buffer_enabled(indio_dev))
stepconfig = STEPCONFIG_AVG_16 | STEPCONFIG_FIFO1
| STEPCONFIG_MODE_SWCNT;
diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
index 18d4b2c8fe55..a18f41b89b6a 100644
--- a/drivers/input/evdev.c
+++ b/drivers/input/evdev.c
@@ -62,26 +62,6 @@ struct evdev_client {
struct input_event buffer[];
};
-static int evdev_set_clk_type(struct evdev_client *client, unsigned int clkid)
-{
- switch (clkid) {
-
- case CLOCK_REALTIME:
- client->clk_type = EV_CLK_REAL;
- break;
- case CLOCK_MONOTONIC:
- client->clk_type = EV_CLK_MONO;
- break;
- case CLOCK_BOOTTIME:
- client->clk_type = EV_CLK_BOOT;
- break;
- default:
- return -EINVAL;
- }
-
- return 0;
-}
-
/* flush queued events of type @type, caller must hold client->buffer_lock */
static void __evdev_flush_queue(struct evdev_client *client, unsigned int type)
{
@@ -128,10 +108,8 @@ static void __evdev_flush_queue(struct evdev_client *client, unsigned int type)
client->head = head;
}
-/* queue SYN_DROPPED event */
-static void evdev_queue_syn_dropped(struct evdev_client *client)
+static void __evdev_queue_syn_dropped(struct evdev_client *client)
{
- unsigned long flags;
struct input_event ev;
ktime_t time;
@@ -146,8 +124,6 @@ static void evdev_queue_syn_dropped(struct evdev_client *client)
ev.code = SYN_DROPPED;
ev.value = 0;
- spin_lock_irqsave(&client->buffer_lock, flags);
-
client->buffer[client->head++] = ev;
client->head &= client->bufsize - 1;
@@ -156,8 +132,53 @@ static void evdev_queue_syn_dropped(struct evdev_client *client)
client->tail = (client->head - 1) & (client->bufsize - 1);
client->packet_head = client->tail;
}
+}
+
+static void evdev_queue_syn_dropped(struct evdev_client *client)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&client->buffer_lock, flags);
+ __evdev_queue_syn_dropped(client);
+ spin_unlock_irqrestore(&client->buffer_lock, flags);
+}
+
+static int evdev_set_clk_type(struct evdev_client *client, unsigned int clkid)
+{
+ unsigned long flags;
+
+ if (client->clk_type == clkid)
+ return 0;
+
+ switch (clkid) {
+
+ case CLOCK_REALTIME:
+ client->clk_type = EV_CLK_REAL;
+ break;
+ case CLOCK_MONOTONIC:
+ client->clk_type = EV_CLK_MONO;
+ break;
+ case CLOCK_BOOTTIME:
+ client->clk_type = EV_CLK_BOOT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * Flush pending events and queue SYN_DROPPED event,
+ * but only if the queue is not empty.
+ */
+ spin_lock_irqsave(&client->buffer_lock, flags);
+
+ if (client->head != client->tail) {
+ client->packet_head = client->head = client->tail;
+ __evdev_queue_syn_dropped(client);
+ }
spin_unlock_irqrestore(&client->buffer_lock, flags);
+
+ return 0;
}
static void __pass_event(struct evdev_client *client,
diff --git a/drivers/input/input-mt.c b/drivers/input/input-mt.c
index fbe29fcb15c5..cb150a1dbaff 100644
--- a/drivers/input/input-mt.c
+++ b/drivers/input/input-mt.c
@@ -293,7 +293,7 @@ void input_mt_sync_frame(struct input_dev *dev)
}
EXPORT_SYMBOL(input_mt_sync_frame);
-static int adjust_dual(int *begin, int step, int *end, int eq)
+static int adjust_dual(int *begin, int step, int *end, int eq, int mu)
{
int f, *p, s, c;
@@ -311,9 +311,10 @@ static int adjust_dual(int *begin, int step, int *end, int eq)
s = *p;
c = (f + s + 1) / 2;
- if (c == 0 || (c > 0 && !eq))
+ if (c == 0 || (c > mu && (!eq || mu > 0)))
return 0;
- if (s < 0)
+ /* Improve convergence for positive matrices by penalizing overcovers */
+ if (s < 0 && mu <= 0)
c *= 2;
for (p = begin; p != end; p += step)
@@ -322,23 +323,24 @@ static int adjust_dual(int *begin, int step, int *end, int eq)
return (c < s && s <= 0) || (f >= 0 && f < c);
}
-static void find_reduced_matrix(int *w, int nr, int nc, int nrc)
+static void find_reduced_matrix(int *w, int nr, int nc, int nrc, int mu)
{
int i, k, sum;
for (k = 0; k < nrc; k++) {
for (i = 0; i < nr; i++)
- adjust_dual(w + i, nr, w + i + nrc, nr <= nc);
+ adjust_dual(w + i, nr, w + i + nrc, nr <= nc, mu);
sum = 0;
for (i = 0; i < nrc; i += nr)
- sum += adjust_dual(w + i, 1, w + i + nr, nc <= nr);
+ sum += adjust_dual(w + i, 1, w + i + nr, nc <= nr, mu);
if (!sum)
break;
}
}
static int input_mt_set_matrix(struct input_mt *mt,
- const struct input_mt_pos *pos, int num_pos)
+ const struct input_mt_pos *pos, int num_pos,
+ int mu)
{
const struct input_mt_pos *p;
struct input_mt_slot *s;
@@ -352,7 +354,7 @@ static int input_mt_set_matrix(struct input_mt *mt,
y = input_mt_get_value(s, ABS_MT_POSITION_Y);
for (p = pos; p != pos + num_pos; p++) {
int dx = x - p->x, dy = y - p->y;
- *w++ = dx * dx + dy * dy;
+ *w++ = dx * dx + dy * dy - mu;
}
}
@@ -393,17 +395,24 @@ static void input_mt_set_slots(struct input_mt *mt,
* @slots: the slot assignment to be filled
* @pos: the position array to match
* @num_pos: number of positions
+ * @dmax: maximum ABS_MT_POSITION displacement (zero for infinite)
*
* Performs a best match against the current contacts and returns
* the slot assignment list. New contacts are assigned to unused
* slots.
*
+ * The assignments are balanced so that all coordinate displacements are
+ * below the euclidian distance dmax. If no such assignment can be found,
+ * some contacts are assigned to unused slots.
+ *
* Returns zero on success, or negative error in case of failure.
*/
int input_mt_assign_slots(struct input_dev *dev, int *slots,
- const struct input_mt_pos *pos, int num_pos)
+ const struct input_mt_pos *pos, int num_pos,
+ int dmax)
{
struct input_mt *mt = dev->mt;
+ int mu = 2 * dmax * dmax;
int nrc;
if (!mt || !mt->red)
@@ -413,8 +422,8 @@ int input_mt_assign_slots(struct input_dev *dev, int *slots,
if (num_pos < 1)
return 0;
- nrc = input_mt_set_matrix(mt, pos, num_pos);
- find_reduced_matrix(mt->red, num_pos, nrc / num_pos, nrc);
+ nrc = input_mt_set_matrix(mt, pos, num_pos, mu);
+ find_reduced_matrix(mt->red, num_pos, nrc / num_pos, nrc, mu);
input_mt_set_slots(mt, slots, num_pos);
return 0;
diff --git a/drivers/input/input.c b/drivers/input/input.c
index 213e3a1903ee..cc357f1516a7 100644
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -100,23 +100,24 @@ static unsigned int input_to_handler(struct input_handle *handle,
struct input_value *end = vals;
struct input_value *v;
- for (v = vals; v != vals + count; v++) {
- if (handler->filter &&
- handler->filter(handle, v->type, v->code, v->value))
- continue;
- if (end != v)
- *end = *v;
- end++;
+ if (handler->filter) {
+ for (v = vals; v != vals + count; v++) {
+ if (handler->filter(handle, v->type, v->code, v->value))
+ continue;
+ if (end != v)
+ *end = *v;
+ end++;
+ }
+ count = end - vals;
}
- count = end - vals;
if (!count)
return 0;
if (handler->events)
handler->events(handle, vals, count);
else if (handler->event)
- for (v = vals; v != end; v++)
+ for (v = vals; v != vals + count; v++)
handler->event(handle, v->type, v->code, v->value);
return count;
@@ -143,8 +144,11 @@ static void input_pass_values(struct input_dev *dev,
count = input_to_handler(handle, vals, count);
} else {
list_for_each_entry_rcu(handle, &dev->h_list, d_node)
- if (handle->open)
+ if (handle->open) {
count = input_to_handler(handle, vals, count);
+ if (!count)
+ break;
+ }
}
rcu_read_unlock();
@@ -152,12 +156,14 @@ static void input_pass_values(struct input_dev *dev,
add_input_randomness(vals->type, vals->code, vals->value);
/* trigger auto repeat for key events */
- for (v = vals; v != vals + count; v++) {
- if (v->type == EV_KEY && v->value != 2) {
- if (v->value)
- input_start_autorepeat(dev, v->code);
- else
- input_stop_autorepeat(dev);
+ if (test_bit(EV_REP, dev->evbit) && test_bit(EV_KEY, dev->evbit)) {
+ for (v = vals; v != vals + count; v++) {
+ if (v->type == EV_KEY && v->value != 2) {
+ if (v->value)
+ input_start_autorepeat(dev, v->code);
+ else
+ input_stop_autorepeat(dev);
+ }
}
}
}
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index a5d9b3f3c871..a89ba7cb96f1 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -568,6 +568,16 @@ config KEYBOARD_STMPE
To compile this driver as a module, choose M here: the module will be
called stmpe-keypad.
+config KEYBOARD_SUN4I_LRADC
+ tristate "Allwinner sun4i low res adc attached tablet keys support"
+ depends on ARCH_SUNXI
+ help
+ This selects support for the Allwinner low res adc attached tablet
+ keys found on Allwinner sunxi SoCs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sun4i-lradc-keys.
+
config KEYBOARD_DAVINCI
tristate "TI DaVinci Key Scan"
depends on ARCH_DAVINCI_DM365
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index febafa527eb6..470767884bd8 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -53,6 +53,7 @@ obj-$(CONFIG_KEYBOARD_SPEAR) += spear-keyboard.o
obj-$(CONFIG_KEYBOARD_STMPE) += stmpe-keypad.o
obj-$(CONFIG_KEYBOARD_STOWAWAY) += stowaway.o
obj-$(CONFIG_KEYBOARD_ST_KEYSCAN) += st-keyscan.o
+obj-$(CONFIG_KEYBOARD_SUN4I_LRADC) += sun4i-lradc-keys.o
obj-$(CONFIG_KEYBOARD_SUNKBD) += sunkbd.o
obj-$(CONFIG_KEYBOARD_TC3589X) += tc3589x-keypad.o
obj-$(CONFIG_KEYBOARD_TEGRA) += tegra-kbc.o
diff --git a/drivers/input/keyboard/atakbd.c b/drivers/input/keyboard/atakbd.c
index 10bcd4ae5402..f1235831283d 100644
--- a/drivers/input/keyboard/atakbd.c
+++ b/drivers/input/keyboard/atakbd.c
@@ -170,7 +170,7 @@ static unsigned char atakbd_keycode[0x72] = { /* American layout */
[93] = KEY_KPASTERISK,
[94] = KEY_KPPLUS,
[95] = KEY_HELP,
- [96] = KEY_BACKSLASH, /* FIXME: '<' */
+ [96] = KEY_102ND,
[97] = KEY_KPASTERISK, /* FIXME */
[98] = KEY_KPSLASH,
[99] = KEY_KPLEFTPAREN,
diff --git a/drivers/input/keyboard/cap11xx.c b/drivers/input/keyboard/cap11xx.c
index 4f59f0bab28f..f07461a64d85 100644
--- a/drivers/input/keyboard/cap11xx.c
+++ b/drivers/input/keyboard/cap11xx.c
@@ -370,7 +370,6 @@ static struct i2c_driver cap11xx_i2c_driver = {
module_i2c_driver(cap11xx_i2c_driver);
-MODULE_ALIAS("platform:cap11xx");
MODULE_DESCRIPTION("Microchip CAP11XX driver");
MODULE_AUTHOR("Daniel Mack <linux@zonque.org>");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/keyboard/imx_keypad.c b/drivers/input/keyboard/imx_keypad.c
index e53f232eda0e..2e855e6f3565 100644
--- a/drivers/input/keyboard/imx_keypad.c
+++ b/drivers/input/keyboard/imx_keypad.c
@@ -448,8 +448,7 @@ static int imx_keypad_probe(struct platform_device *pdev)
return -ENOMEM;
}
- keypad = devm_kzalloc(&pdev->dev, sizeof(struct imx_keypad),
- GFP_KERNEL);
+ keypad = devm_kzalloc(&pdev->dev, sizeof(*keypad), GFP_KERNEL);
if (!keypad) {
dev_err(&pdev->dev, "not enough memory for driver data\n");
return -ENOMEM;
diff --git a/drivers/input/keyboard/pxa27x_keypad.c b/drivers/input/keyboard/pxa27x_keypad.c
index a90d6bdc499e..a89488aa1aa4 100644
--- a/drivers/input/keyboard/pxa27x_keypad.c
+++ b/drivers/input/keyboard/pxa27x_keypad.c
@@ -20,6 +20,7 @@
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/input.h>
+#include <linux/io.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
@@ -28,10 +29,6 @@
#include <linux/slab.h>
#include <linux/of.h>
-#include <asm/mach/arch.h>
-#include <asm/mach/map.h>
-
-#include <mach/hardware.h>
#include <linux/platform_data/keypad-pxa27x.h>
/*
* Keypad Controller registers
diff --git a/drivers/input/keyboard/sun4i-lradc-keys.c b/drivers/input/keyboard/sun4i-lradc-keys.c
new file mode 100644
index 000000000000..cc8f7ddcee53
--- /dev/null
+++ b/drivers/input/keyboard/sun4i-lradc-keys.c
@@ -0,0 +1,286 @@
+/*
+ * Allwinner sun4i low res adc attached tablet keys driver
+ *
+ * Copyright (C) 2014 Hans de Goede <hdegoede@redhat.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.
+ *
+ * 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.
+ */
+
+/*
+ * Allwinnner sunxi SoCs have a lradc which is specifically designed to have
+ * various (tablet) keys (ie home, back, search, etc). attached to it using
+ * a resistor network. This driver is for the keys on such boards.
+ *
+ * There are 2 channels, currently this driver only supports channel 0 since
+ * there are no boards known to use channel 1.
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#define LRADC_CTRL 0x00
+#define LRADC_INTC 0x04
+#define LRADC_INTS 0x08
+#define LRADC_DATA0 0x0c
+#define LRADC_DATA1 0x10
+
+/* LRADC_CTRL bits */
+#define FIRST_CONVERT_DLY(x) ((x) << 24) /* 8 bits */
+#define CHAN_SELECT(x) ((x) << 22) /* 2 bits */
+#define CONTINUE_TIME_SEL(x) ((x) << 16) /* 4 bits */
+#define KEY_MODE_SEL(x) ((x) << 12) /* 2 bits */
+#define LEVELA_B_CNT(x) ((x) << 8) /* 4 bits */
+#define HOLD_EN(x) ((x) << 6)
+#define LEVELB_VOL(x) ((x) << 4) /* 2 bits */
+#define SAMPLE_RATE(x) ((x) << 2) /* 2 bits */
+#define ENABLE(x) ((x) << 0)
+
+/* LRADC_INTC and LRADC_INTS bits */
+#define CHAN1_KEYUP_IRQ BIT(12)
+#define CHAN1_ALRDY_HOLD_IRQ BIT(11)
+#define CHAN1_HOLD_IRQ BIT(10)
+#define CHAN1_KEYDOWN_IRQ BIT(9)
+#define CHAN1_DATA_IRQ BIT(8)
+#define CHAN0_KEYUP_IRQ BIT(4)
+#define CHAN0_ALRDY_HOLD_IRQ BIT(3)
+#define CHAN0_HOLD_IRQ BIT(2)
+#define CHAN0_KEYDOWN_IRQ BIT(1)
+#define CHAN0_DATA_IRQ BIT(0)
+
+struct sun4i_lradc_keymap {
+ u32 voltage;
+ u32 keycode;
+};
+
+struct sun4i_lradc_data {
+ struct device *dev;
+ struct input_dev *input;
+ void __iomem *base;
+ struct regulator *vref_supply;
+ struct sun4i_lradc_keymap *chan0_map;
+ u32 chan0_map_count;
+ u32 chan0_keycode;
+ u32 vref;
+};
+
+static irqreturn_t sun4i_lradc_irq(int irq, void *dev_id)
+{
+ struct sun4i_lradc_data *lradc = dev_id;
+ u32 i, ints, val, voltage, diff, keycode = 0, closest = 0xffffffff;
+
+ ints = readl(lradc->base + LRADC_INTS);
+
+ /*
+ * lradc supports only one keypress at a time, release does not give
+ * any info as to which key was released, so we cache the keycode.
+ */
+
+ if (ints & CHAN0_KEYUP_IRQ) {
+ input_report_key(lradc->input, lradc->chan0_keycode, 0);
+ lradc->chan0_keycode = 0;
+ }
+
+ if ((ints & CHAN0_KEYDOWN_IRQ) && lradc->chan0_keycode == 0) {
+ val = readl(lradc->base + LRADC_DATA0) & 0x3f;
+ voltage = val * lradc->vref / 63;
+
+ for (i = 0; i < lradc->chan0_map_count; i++) {
+ diff = abs(lradc->chan0_map[i].voltage - voltage);
+ if (diff < closest) {
+ closest = diff;
+ keycode = lradc->chan0_map[i].keycode;
+ }
+ }
+
+ lradc->chan0_keycode = keycode;
+ input_report_key(lradc->input, lradc->chan0_keycode, 1);
+ }
+
+ input_sync(lradc->input);
+
+ writel(ints, lradc->base + LRADC_INTS);
+
+ return IRQ_HANDLED;
+}
+
+static int sun4i_lradc_open(struct input_dev *dev)
+{
+ struct sun4i_lradc_data *lradc = input_get_drvdata(dev);
+ int error;
+
+ error = regulator_enable(lradc->vref_supply);
+ if (error)
+ return error;
+
+ /* lradc Vref internally is divided by 2/3 */
+ lradc->vref = regulator_get_voltage(lradc->vref_supply) * 2 / 3;
+
+ /*
+ * Set sample time to 4 ms / 250 Hz. Wait 2 * 4 ms for key to
+ * stabilize on press, wait (1 + 1) * 4 ms for key release
+ */
+ writel(FIRST_CONVERT_DLY(2) | LEVELA_B_CNT(1) | HOLD_EN(1) |
+ SAMPLE_RATE(0) | ENABLE(1), lradc->base + LRADC_CTRL);
+
+ writel(CHAN0_KEYUP_IRQ | CHAN0_KEYDOWN_IRQ, lradc->base + LRADC_INTC);
+
+ return 0;
+}
+
+static void sun4i_lradc_close(struct input_dev *dev)
+{
+ struct sun4i_lradc_data *lradc = input_get_drvdata(dev);
+
+ /* Disable lradc, leave other settings unchanged */
+ writel(FIRST_CONVERT_DLY(2) | LEVELA_B_CNT(1) | HOLD_EN(1) |
+ SAMPLE_RATE(2), lradc->base + LRADC_CTRL);
+ writel(0, lradc->base + LRADC_INTC);
+
+ regulator_disable(lradc->vref_supply);
+}
+
+static int sun4i_lradc_load_dt_keymap(struct device *dev,
+ struct sun4i_lradc_data *lradc)
+{
+ struct device_node *np, *pp;
+ int i;
+ int error;
+
+ np = dev->of_node;
+ if (!np)
+ return -EINVAL;
+
+ lradc->chan0_map_count = of_get_child_count(np);
+ if (lradc->chan0_map_count == 0) {
+ dev_err(dev, "keymap is missing in device tree\n");
+ return -EINVAL;
+ }
+
+ lradc->chan0_map = devm_kmalloc_array(dev, lradc->chan0_map_count,
+ sizeof(struct sun4i_lradc_keymap),
+ GFP_KERNEL);
+ if (!lradc->chan0_map)
+ return -ENOMEM;
+
+ i = 0;
+ for_each_child_of_node(np, pp) {
+ struct sun4i_lradc_keymap *map = &lradc->chan0_map[i];
+ u32 channel;
+
+ error = of_property_read_u32(pp, "channel", &channel);
+ if (error || channel != 0) {
+ dev_err(dev, "%s: Inval channel prop\n", pp->name);
+ return -EINVAL;
+ }
+
+ error = of_property_read_u32(pp, "voltage", &map->voltage);
+ if (error) {
+ dev_err(dev, "%s: Inval voltage prop\n", pp->name);
+ return -EINVAL;
+ }
+
+ error = of_property_read_u32(pp, "linux,code", &map->keycode);
+ if (error) {
+ dev_err(dev, "%s: Inval linux,code prop\n", pp->name);
+ return -EINVAL;
+ }
+
+ i++;
+ }
+
+ return 0;
+}
+
+static int sun4i_lradc_probe(struct platform_device *pdev)
+{
+ struct sun4i_lradc_data *lradc;
+ struct device *dev = &pdev->dev;
+ int i;
+ int error;
+
+ lradc = devm_kzalloc(dev, sizeof(struct sun4i_lradc_data), GFP_KERNEL);
+ if (!lradc)
+ return -ENOMEM;
+
+ error = sun4i_lradc_load_dt_keymap(dev, lradc);
+ if (error)
+ return error;
+
+ lradc->vref_supply = devm_regulator_get(dev, "vref");
+ if (IS_ERR(lradc->vref_supply))
+ return PTR_ERR(lradc->vref_supply);
+
+ lradc->dev = dev;
+ lradc->input = devm_input_allocate_device(dev);
+ if (!lradc->input)
+ return -ENOMEM;
+
+ lradc->input->name = pdev->name;
+ lradc->input->phys = "sun4i_lradc/input0";
+ lradc->input->open = sun4i_lradc_open;
+ lradc->input->close = sun4i_lradc_close;
+ lradc->input->id.bustype = BUS_HOST;
+ lradc->input->id.vendor = 0x0001;
+ lradc->input->id.product = 0x0001;
+ lradc->input->id.version = 0x0100;
+
+ __set_bit(EV_KEY, lradc->input->evbit);
+ for (i = 0; i < lradc->chan0_map_count; i++)
+ __set_bit(lradc->chan0_map[i].keycode, lradc->input->keybit);
+
+ input_set_drvdata(lradc->input, lradc);
+
+ lradc->base = devm_ioremap_resource(dev,
+ platform_get_resource(pdev, IORESOURCE_MEM, 0));
+ if (IS_ERR(lradc->base))
+ return PTR_ERR(lradc->base);
+
+ error = devm_request_irq(dev, platform_get_irq(pdev, 0),
+ sun4i_lradc_irq, 0,
+ "sun4i-a10-lradc-keys", lradc);
+ if (error)
+ return error;
+
+ error = input_register_device(lradc->input);
+ if (error)
+ return error;
+
+ platform_set_drvdata(pdev, lradc);
+ return 0;
+}
+
+static const struct of_device_id sun4i_lradc_of_match[] = {
+ { .compatible = "allwinner,sun4i-a10-lradc-keys", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sun4i_lradc_of_match);
+
+static struct platform_driver sun4i_lradc_driver = {
+ .driver = {
+ .name = "sun4i-a10-lradc-keys",
+ .of_match_table = of_match_ptr(sun4i_lradc_of_match),
+ },
+ .probe = sun4i_lradc_probe,
+};
+
+module_platform_driver(sun4i_lradc_driver);
+
+MODULE_DESCRIPTION("Allwinner sun4i low res adc attached tablet keys driver");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 23297ab6163f..6deb8dae3205 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -93,6 +93,16 @@ config INPUT_BMA150
To compile this driver as a module, choose M here: the
module will be called bma150.
+config INPUT_E3X0_BUTTON
+ tristate "NI Ettus Research USRP E3x0 Button support."
+ default n
+ help
+ Say Y here to enable support for the NI Ettus Research
+ USRP E3x0 Button.
+
+ To compile this driver as a module, choose M here: the
+ module will be called e3x0_button.
+
config INPUT_PCSPKR
tristate "PC Speaker support"
depends on PCSPKR_PLATFORM
@@ -394,6 +404,18 @@ config INPUT_CM109
To compile this driver as a module, choose M here: the module will be
called cm109.
+config INPUT_REGULATOR_HAPTIC
+ tristate "Regulator haptics support"
+ depends on REGULATOR
+ select INPUT_FF_MEMLESS
+ help
+ This option enables device driver support for the haptic controlled
+ by a regulator. This driver supports ff-memless interface
+ from input framework.
+
+ To compile this driver as a module, choose M here: the
+ module will be called regulator-haptic.
+
config INPUT_RETU_PWRBUTTON
tristate "Retu Power button Driver"
depends on MFD_RETU
@@ -404,6 +426,27 @@ config INPUT_RETU_PWRBUTTON
To compile this driver as a module, choose M here. The module will
be called retu-pwrbutton.
+config INPUT_TPS65218_PWRBUTTON
+ tristate "TPS65218 Power button driver"
+ depends on MFD_TPS65218
+ help
+ Say Y here if you want to enable power buttong reporting for
+ the TPS65218 Power Management IC device.
+
+ To compile this driver as a module, choose M here. The module will
+ be called tps65218-pwrbutton.
+
+config INPUT_AXP20X_PEK
+ tristate "X-Powers AXP20X power button driver"
+ depends on MFD_AXP20X
+ help
+ Say Y here if you want to enable power key reporting via the
+ AXP20X PMIC.
+
+ To compile this driver as a module, choose M here. The module will
+ be called axp20x-pek.
+
+
config INPUT_TWL4030_PWRBUTTON
tristate "TWL4030 Power button Driver"
depends on TWL4030_CORE
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 19c760361f80..403a1a54a76c 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o
obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o
obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o
obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o
+obj-$(CONFIG_INPUT_E3X0_BUTTON) += e3x0-button.o
obj-$(CONFIG_INPUT_DRV260X_HAPTICS) += drv260x.o
obj-$(CONFIG_INPUT_DRV2667_HAPTICS) += drv2667.o
obj-$(CONFIG_INPUT_GP2A) += gp2ap002a00f.o
@@ -53,12 +54,15 @@ obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY) += pmic8xxx-pwrkey.o
obj-$(CONFIG_INPUT_POWERMATE) += powermate.o
obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o
obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o
+obj-$(CONFIG_INPUT_REGULATOR_HAPTIC) += regulator-haptic.o
obj-$(CONFIG_INPUT_RETU_PWRBUTTON) += retu-pwrbutton.o
+obj-$(CONFIG_INPUT_AXP20X_PEK) += axp20x-pek.o
obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER) += rotary_encoder.o
obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o
obj-$(CONFIG_INPUT_SIRFSOC_ONKEY) += sirfsoc-onkey.o
obj-$(CONFIG_INPUT_SOC_BUTTON_ARRAY) += soc_button_array.o
obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o
+obj-$(CONFIG_INPUT_TPS65218_PWRBUTTON) += tps65218-pwrbutton.o
obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o
obj-$(CONFIG_INPUT_TWL4030_VIBRA) += twl4030-vibra.o
obj-$(CONFIG_INPUT_TWL6040_VIBRA) += twl6040-vibra.o
diff --git a/drivers/input/misc/axp20x-pek.c b/drivers/input/misc/axp20x-pek.c
new file mode 100644
index 000000000000..f1c844739cd7
--- /dev/null
+++ b/drivers/input/misc/axp20x-pek.c
@@ -0,0 +1,290 @@
+/*
+ * axp20x power button driver.
+ *
+ * Copyright (C) 2013 Carlo Caione <carlo@caione.org>
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+#include <linux/errno.h>
+#include <linux/irq.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/axp20x.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define AXP20X_PEK_STARTUP_MASK (0xc0)
+#define AXP20X_PEK_SHUTDOWN_MASK (0x03)
+
+struct axp20x_pek {
+ struct axp20x_dev *axp20x;
+ struct input_dev *input;
+ int irq_dbr;
+ int irq_dbf;
+};
+
+struct axp20x_time {
+ unsigned int time;
+ unsigned int idx;
+};
+
+static const struct axp20x_time startup_time[] = {
+ { .time = 128, .idx = 0 },
+ { .time = 1000, .idx = 2 },
+ { .time = 3000, .idx = 1 },
+ { .time = 2000, .idx = 3 },
+};
+
+static const struct axp20x_time shutdown_time[] = {
+ { .time = 4000, .idx = 0 },
+ { .time = 6000, .idx = 1 },
+ { .time = 8000, .idx = 2 },
+ { .time = 10000, .idx = 3 },
+};
+
+struct axp20x_pek_ext_attr {
+ const struct axp20x_time *p_time;
+ unsigned int mask;
+};
+
+static struct axp20x_pek_ext_attr axp20x_pek_startup_ext_attr = {
+ .p_time = startup_time,
+ .mask = AXP20X_PEK_STARTUP_MASK,
+};
+
+static struct axp20x_pek_ext_attr axp20x_pek_shutdown_ext_attr = {
+ .p_time = shutdown_time,
+ .mask = AXP20X_PEK_SHUTDOWN_MASK,
+};
+
+static struct axp20x_pek_ext_attr *get_axp_ext_attr(struct device_attribute *attr)
+{
+ return container_of(attr, struct dev_ext_attribute, attr)->var;
+}
+
+static ssize_t axp20x_show_ext_attr(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
+ struct axp20x_pek_ext_attr *axp20x_ea = get_axp_ext_attr(attr);
+ unsigned int val;
+ int ret, i;
+
+ ret = regmap_read(axp20x_pek->axp20x->regmap, AXP20X_PEK_KEY, &val);
+ if (ret != 0)
+ return ret;
+
+ val &= axp20x_ea->mask;
+ val >>= ffs(axp20x_ea->mask) - 1;
+
+ for (i = 0; i < 4; i++)
+ if (val == axp20x_ea->p_time[i].idx)
+ val = axp20x_ea->p_time[i].time;
+
+ return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t axp20x_store_ext_attr(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
+ struct axp20x_pek_ext_attr *axp20x_ea = get_axp_ext_attr(attr);
+ char val_str[20];
+ size_t len;
+ int ret, i;
+ unsigned int val, idx = 0;
+ unsigned int best_err = UINT_MAX;
+
+ val_str[sizeof(val_str) - 1] = '\0';
+ strncpy(val_str, buf, sizeof(val_str) - 1);
+ len = strlen(val_str);
+
+ if (len && val_str[len - 1] == '\n')
+ val_str[len - 1] = '\0';
+
+ ret = kstrtouint(val_str, 10, &val);
+ if (ret)
+ return ret;
+
+ for (i = 3; i >= 0; i--) {
+ unsigned int err;
+
+ err = abs(axp20x_ea->p_time[i].time - val);
+ if (err < best_err) {
+ best_err = err;
+ idx = axp20x_ea->p_time[i].idx;
+ }
+
+ if (!err)
+ break;
+ }
+
+ idx <<= ffs(axp20x_ea->mask) - 1;
+ ret = regmap_update_bits(axp20x_pek->axp20x->regmap,
+ AXP20X_PEK_KEY,
+ axp20x_ea->mask, idx);
+ if (ret != 0)
+ return -EINVAL;
+
+ return count;
+}
+
+static struct dev_ext_attribute axp20x_dev_attr_startup = {
+ .attr = __ATTR(startup, 0644, axp20x_show_ext_attr, axp20x_store_ext_attr),
+ .var = &axp20x_pek_startup_ext_attr,
+};
+
+static struct dev_ext_attribute axp20x_dev_attr_shutdown = {
+ .attr = __ATTR(shutdown, 0644, axp20x_show_ext_attr, axp20x_store_ext_attr),
+ .var = &axp20x_pek_shutdown_ext_attr,
+};
+
+static struct attribute *axp20x_attributes[] = {
+ &axp20x_dev_attr_startup.attr.attr,
+ &axp20x_dev_attr_shutdown.attr.attr,
+ NULL,
+};
+
+static const struct attribute_group axp20x_attribute_group = {
+ .attrs = axp20x_attributes,
+};
+
+static irqreturn_t axp20x_pek_irq(int irq, void *pwr)
+{
+ struct input_dev *idev = pwr;
+ struct axp20x_pek *axp20x_pek = input_get_drvdata(idev);
+
+ if (irq == axp20x_pek->irq_dbr)
+ input_report_key(idev, KEY_POWER, true);
+ else if (irq == axp20x_pek->irq_dbf)
+ input_report_key(idev, KEY_POWER, false);
+
+ input_sync(idev);
+
+ return IRQ_HANDLED;
+}
+
+static void axp20x_remove_sysfs_group(void *_data)
+{
+ struct device *dev = _data;
+
+ sysfs_remove_group(&dev->kobj, &axp20x_attribute_group);
+}
+
+static int axp20x_pek_probe(struct platform_device *pdev)
+{
+ struct axp20x_pek *axp20x_pek;
+ struct axp20x_dev *axp20x;
+ struct input_dev *idev;
+ int error;
+
+ axp20x_pek = devm_kzalloc(&pdev->dev, sizeof(struct axp20x_pek),
+ GFP_KERNEL);
+ if (!axp20x_pek)
+ return -ENOMEM;
+
+ axp20x_pek->axp20x = dev_get_drvdata(pdev->dev.parent);
+ axp20x = axp20x_pek->axp20x;
+
+ axp20x_pek->irq_dbr = platform_get_irq_byname(pdev, "PEK_DBR");
+ if (axp20x_pek->irq_dbr < 0) {
+ dev_err(&pdev->dev, "No IRQ for PEK_DBR, error=%d\n",
+ axp20x_pek->irq_dbr);
+ return axp20x_pek->irq_dbr;
+ }
+ axp20x_pek->irq_dbr = regmap_irq_get_virq(axp20x->regmap_irqc,
+ axp20x_pek->irq_dbr);
+
+ axp20x_pek->irq_dbf = platform_get_irq_byname(pdev, "PEK_DBF");
+ if (axp20x_pek->irq_dbf < 0) {
+ dev_err(&pdev->dev, "No IRQ for PEK_DBF, error=%d\n",
+ axp20x_pek->irq_dbf);
+ return axp20x_pek->irq_dbf;
+ }
+ axp20x_pek->irq_dbf = regmap_irq_get_virq(axp20x->regmap_irqc,
+ axp20x_pek->irq_dbf);
+
+ axp20x_pek->input = devm_input_allocate_device(&pdev->dev);
+ if (!axp20x_pek->input)
+ return -ENOMEM;
+
+ idev = axp20x_pek->input;
+
+ idev->name = "axp20x-pek";
+ idev->phys = "m1kbd/input2";
+ idev->dev.parent = &pdev->dev;
+
+ input_set_capability(idev, EV_KEY, KEY_POWER);
+
+ input_set_drvdata(idev, axp20x_pek);
+
+ error = devm_request_any_context_irq(&pdev->dev, axp20x_pek->irq_dbr,
+ axp20x_pek_irq, 0,
+ "axp20x-pek-dbr", idev);
+ if (error < 0) {
+ dev_err(axp20x->dev, "Failed to request dbr IRQ#%d: %d\n",
+ axp20x_pek->irq_dbr, error);
+ return error;
+ }
+
+ error = devm_request_any_context_irq(&pdev->dev, axp20x_pek->irq_dbf,
+ axp20x_pek_irq, 0,
+ "axp20x-pek-dbf", idev);
+ if (error < 0) {
+ dev_err(axp20x->dev, "Failed to request dbf IRQ#%d: %d\n",
+ axp20x_pek->irq_dbf, error);
+ return error;
+ }
+
+ error = sysfs_create_group(&pdev->dev.kobj, &axp20x_attribute_group);
+ if (error) {
+ dev_err(axp20x->dev, "Failed to create sysfs attributes: %d\n",
+ error);
+ return error;
+ }
+
+ error = devm_add_action(&pdev->dev,
+ axp20x_remove_sysfs_group, &pdev->dev);
+ if (error) {
+ axp20x_remove_sysfs_group(&pdev->dev);
+ dev_err(&pdev->dev, "Failed to add sysfs cleanup action: %d\n",
+ error);
+ return error;
+ }
+
+ error = input_register_device(idev);
+ if (error) {
+ dev_err(axp20x->dev, "Can't register input device: %d\n",
+ error);
+ return error;
+ }
+
+ platform_set_drvdata(pdev, axp20x_pek);
+
+ return 0;
+}
+
+static struct platform_driver axp20x_pek_driver = {
+ .probe = axp20x_pek_probe,
+ .driver = {
+ .name = "axp20x-pek",
+ },
+};
+module_platform_driver(axp20x_pek_driver);
+
+MODULE_DESCRIPTION("axp20x Power Button");
+MODULE_AUTHOR("Carlo Caione <carlo@caione.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/drv260x.c b/drivers/input/misc/drv260x.c
index a364e109ca7c..599578042ea0 100644
--- a/drivers/input/misc/drv260x.c
+++ b/drivers/input/misc/drv260x.c
@@ -733,7 +733,6 @@ static struct i2c_driver drv260x_driver = {
};
module_i2c_driver(drv260x_driver);
-MODULE_ALIAS("platform:drv260x-haptics");
MODULE_DESCRIPTION("TI DRV260x haptics driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
diff --git a/drivers/input/misc/drv2667.c b/drivers/input/misc/drv2667.c
index a021744e608c..fc0fddf0896a 100644
--- a/drivers/input/misc/drv2667.c
+++ b/drivers/input/misc/drv2667.c
@@ -492,7 +492,6 @@ static struct i2c_driver drv2667_driver = {
};
module_i2c_driver(drv2667_driver);
-MODULE_ALIAS("platform:drv2667-haptics");
MODULE_DESCRIPTION("TI DRV2667 haptics driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
diff --git a/drivers/input/misc/e3x0-button.c b/drivers/input/misc/e3x0-button.c
new file mode 100644
index 000000000000..13bfca8a7b16
--- /dev/null
+++ b/drivers/input/misc/e3x0-button.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2014, National Instruments Corp. All rights reserved.
+ *
+ * Driver for NI Ettus Research USRP E3x0 Button Driver
+ *
+ * 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; version 2 of the License.
+ *
+ * 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.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+
+static irqreturn_t e3x0_button_release_handler(int irq, void *data)
+{
+ struct input_dev *idev = data;
+
+ input_report_key(idev, KEY_POWER, 0);
+ input_sync(idev);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t e3x0_button_press_handler(int irq, void *data)
+{
+ struct input_dev *idev = data;
+
+ input_report_key(idev, KEY_POWER, 1);
+ pm_wakeup_event(idev->dev.parent, 0);
+ input_sync(idev);
+
+ return IRQ_HANDLED;
+}
+
+static int __maybe_unused e3x0_button_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(platform_get_irq_byname(pdev, "press"));
+
+ return 0;
+}
+
+static int __maybe_unused e3x0_button_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(platform_get_irq_byname(pdev, "press"));
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(e3x0_button_pm_ops,
+ e3x0_button_suspend, e3x0_button_resume);
+
+static int e3x0_button_probe(struct platform_device *pdev)
+{
+ struct input_dev *input;
+ int irq_press, irq_release;
+ int error;
+
+ irq_press = platform_get_irq_byname(pdev, "press");
+ if (irq_press < 0) {
+ dev_err(&pdev->dev, "No IRQ for 'press', error=%d\n",
+ irq_press);
+ return irq_press;
+ }
+
+ irq_release = platform_get_irq_byname(pdev, "release");
+ if (irq_release < 0) {
+ dev_err(&pdev->dev, "No IRQ for 'release', error=%d\n",
+ irq_release);
+ return irq_release;
+ }
+
+ input = devm_input_allocate_device(&pdev->dev);
+ if (!input)
+ return -ENOMEM;
+
+ input->name = "NI Ettus Research USRP E3x0 Button Driver";
+ input->phys = "e3x0_button/input0";
+ input->dev.parent = &pdev->dev;
+
+ input_set_capability(input, EV_KEY, KEY_POWER);
+
+ error = devm_request_irq(&pdev->dev, irq_press,
+ e3x0_button_press_handler, 0,
+ "e3x0-button", input);
+ if (error) {
+ dev_err(&pdev->dev, "Failed to request 'press' IRQ#%d: %d\n",
+ irq_press, error);
+ return error;
+ }
+
+ error = devm_request_irq(&pdev->dev, irq_release,
+ e3x0_button_release_handler, 0,
+ "e3x0-button", input);
+ if (error) {
+ dev_err(&pdev->dev, "Failed to request 'release' IRQ#%d: %d\n",
+ irq_release, error);
+ return error;
+ }
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(&pdev->dev, "Can't register input device: %d\n", error);
+ return error;
+ }
+
+ platform_set_drvdata(pdev, input);
+ device_init_wakeup(&pdev->dev, 1);
+ return 0;
+}
+
+static int e3x0_button_remove(struct platform_device *pdev)
+{
+ device_init_wakeup(&pdev->dev, 0);
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id e3x0_button_match[] = {
+ { .compatible = "ettus,e3x0-button", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, e3x0_button_match);
+#endif
+
+static struct platform_driver e3x0_button_driver = {
+ .driver = {
+ .name = "e3x0-button",
+ .of_match_table = of_match_ptr(e3x0_button_match),
+ .pm = &e3x0_button_pm_ops,
+ },
+ .probe = e3x0_button_probe,
+ .remove = e3x0_button_remove,
+};
+
+module_platform_driver(e3x0_button_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Moritz Fischer <moritz.fischer@ettus.com>");
+MODULE_DESCRIPTION("NI Ettus Research USRP E3x0 Button driver");
+MODULE_ALIAS("platform:e3x0-button");
diff --git a/drivers/input/misc/regulator-haptic.c b/drivers/input/misc/regulator-haptic.c
new file mode 100644
index 000000000000..132eb914ea3e
--- /dev/null
+++ b/drivers/input/misc/regulator-haptic.c
@@ -0,0 +1,266 @@
+/*
+ * Regulator haptic driver
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Author: Jaewon Kim <jaewon02.kim@samsung.com>
+ * Author: Hyunhee Kim <hyunhee.kim@samsung.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/input.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_data/regulator-haptic.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#define MAX_MAGNITUDE_SHIFT 16
+
+struct regulator_haptic {
+ struct device *dev;
+ struct input_dev *input_dev;
+ struct regulator *regulator;
+
+ struct work_struct work;
+ struct mutex mutex;
+
+ bool active;
+ bool suspended;
+
+ unsigned int max_volt;
+ unsigned int min_volt;
+ unsigned int magnitude;
+};
+
+static int regulator_haptic_toggle(struct regulator_haptic *haptic, bool on)
+{
+ int error;
+
+ if (haptic->active != on) {
+
+ error = on ? regulator_enable(haptic->regulator) :
+ regulator_disable(haptic->regulator);
+ if (error) {
+ dev_err(haptic->dev,
+ "failed to switch regulator %s: %d\n",
+ on ? "on" : "off", error);
+ return error;
+ }
+
+ haptic->active = on;
+ }
+
+ return 0;
+}
+
+static int regulator_haptic_set_voltage(struct regulator_haptic *haptic,
+ unsigned int magnitude)
+{
+ u64 volt_mag_multi;
+ unsigned int intensity;
+ int error;
+
+ volt_mag_multi = (u64)(haptic->max_volt - haptic->min_volt) * magnitude;
+ intensity = (unsigned int)(volt_mag_multi >> MAX_MAGNITUDE_SHIFT);
+
+ error = regulator_set_voltage(haptic->regulator,
+ intensity + haptic->min_volt,
+ haptic->max_volt);
+ if (error) {
+ dev_err(haptic->dev, "cannot set regulator voltage to %d: %d\n",
+ intensity + haptic->min_volt, error);
+ return error;
+ }
+
+ regulator_haptic_toggle(haptic, !!magnitude);
+
+ return 0;
+}
+
+static void regulator_haptic_work(struct work_struct *work)
+{
+ struct regulator_haptic *haptic = container_of(work,
+ struct regulator_haptic, work);
+
+ mutex_lock(&haptic->mutex);
+
+ if (!haptic->suspended)
+ regulator_haptic_set_voltage(haptic, haptic->magnitude);
+
+ mutex_unlock(&haptic->mutex);
+}
+
+static int regulator_haptic_play_effect(struct input_dev *input, void *data,
+ struct ff_effect *effect)
+{
+ struct regulator_haptic *haptic = input_get_drvdata(input);
+
+ haptic->magnitude = effect->u.rumble.strong_magnitude;
+ if (!haptic->magnitude)
+ haptic->magnitude = effect->u.rumble.weak_magnitude;
+
+ schedule_work(&haptic->work);
+
+ return 0;
+}
+
+static void regulator_haptic_close(struct input_dev *input)
+{
+ struct regulator_haptic *haptic = input_get_drvdata(input);
+
+ cancel_work_sync(&haptic->work);
+ regulator_haptic_set_voltage(haptic, 0);
+}
+
+static int __maybe_unused
+regulator_haptic_parse_dt(struct device *dev, struct regulator_haptic *haptic)
+{
+ struct device_node *node;
+ int error;
+
+ node = dev->of_node;
+ if(!node) {
+ dev_err(dev, "Missing dveice tree data\n");
+ return -EINVAL;
+ }
+
+ error = of_property_read_u32(node, "max-microvolt", &haptic->max_volt);
+ if (error) {
+ dev_err(dev, "cannot parse max-microvolt\n");
+ return error;
+ }
+
+ error = of_property_read_u32(node, "min-microvolt", &haptic->min_volt);
+ if (error) {
+ dev_err(dev, "cannot parse min-microvolt\n");
+ return error;
+ }
+
+ return 0;
+}
+
+static int regulator_haptic_probe(struct platform_device *pdev)
+{
+ const struct regulator_haptic_data *pdata = dev_get_platdata(&pdev->dev);
+ struct regulator_haptic *haptic;
+ struct input_dev *input_dev;
+ int error;
+
+ haptic = devm_kzalloc(&pdev->dev, sizeof(*haptic), GFP_KERNEL);
+ if (!haptic)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, haptic);
+ haptic->dev = &pdev->dev;
+ mutex_init(&haptic->mutex);
+ INIT_WORK(&haptic->work, regulator_haptic_work);
+
+ if (pdata) {
+ haptic->max_volt = pdata->max_volt;
+ haptic->min_volt = pdata->min_volt;
+ } else if (IS_ENABLED(CONFIG_OF)) {
+ error = regulator_haptic_parse_dt(&pdev->dev, haptic);
+ if (error)
+ return error;
+ } else {
+ dev_err(&pdev->dev, "Missing platform data\n");
+ return -EINVAL;
+ }
+
+ haptic->regulator = devm_regulator_get_exclusive(&pdev->dev, "haptic");
+ if (IS_ERR(haptic->regulator)) {
+ dev_err(&pdev->dev, "failed to get regulator\n");
+ return PTR_ERR(haptic->regulator);
+ }
+
+ input_dev = devm_input_allocate_device(&pdev->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ haptic->input_dev = input_dev;
+ haptic->input_dev->name = "regulator-haptic";
+ haptic->input_dev->dev.parent = &pdev->dev;
+ haptic->input_dev->close = regulator_haptic_close;
+ input_set_drvdata(haptic->input_dev, haptic);
+ input_set_capability(haptic->input_dev, EV_FF, FF_RUMBLE);
+
+ error = input_ff_create_memless(input_dev, NULL,
+ regulator_haptic_play_effect);
+ if (error) {
+ dev_err(&pdev->dev, "failed to create force-feedback\n");
+ return error;
+ }
+
+ error = input_register_device(haptic->input_dev);
+ if (error) {
+ dev_err(&pdev->dev, "failed to register input device\n");
+ return error;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused regulator_haptic_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct regulator_haptic *haptic = platform_get_drvdata(pdev);
+ int error;
+
+ error = mutex_lock_interruptible(&haptic->mutex);
+ if (error)
+ return error;
+
+ regulator_haptic_set_voltage(haptic, 0);
+
+ haptic->suspended = true;
+
+ mutex_unlock(&haptic->mutex);
+
+ return 0;
+}
+
+static int __maybe_unused regulator_haptic_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct regulator_haptic *haptic = platform_get_drvdata(pdev);
+ unsigned int magnitude;
+
+ mutex_lock(&haptic->mutex);
+
+ haptic->suspended = false;
+
+ magnitude = ACCESS_ONCE(haptic->magnitude);
+ if (magnitude)
+ regulator_haptic_set_voltage(haptic, magnitude);
+
+ mutex_unlock(&haptic->mutex);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(regulator_haptic_pm_ops,
+ regulator_haptic_suspend, regulator_haptic_resume);
+
+static struct of_device_id regulator_haptic_dt_match[] = {
+ { .compatible = "regulator-haptic" },
+ { /* sentinel */ },
+};
+
+static struct platform_driver regulator_haptic_driver = {
+ .probe = regulator_haptic_probe,
+ .driver = {
+ .name = "regulator-haptic",
+ .of_match_table = regulator_haptic_dt_match,
+ .pm = &regulator_haptic_pm_ops,
+ },
+};
+module_platform_driver(regulator_haptic_driver);
+
+MODULE_AUTHOR("Jaewon Kim <jaewon02.kim@samsung.com>");
+MODULE_AUTHOR("Hyunhee Kim <hyunhee.kim@samsung.com>");
+MODULE_DESCRIPTION("Regulator haptic driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/tps65218-pwrbutton.c b/drivers/input/misc/tps65218-pwrbutton.c
new file mode 100644
index 000000000000..54508dec4eb3
--- /dev/null
+++ b/drivers/input/misc/tps65218-pwrbutton.c
@@ -0,0 +1,126 @@
+/*
+ * Texas Instruments' TPS65218 Power Button Input Driver
+ *
+ * Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Felipe Balbi <balbi@ti.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.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/tps65218.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+struct tps65218_pwrbutton {
+ struct device *dev;
+ struct tps65218 *tps;
+ struct input_dev *idev;
+};
+
+static irqreturn_t tps65218_pwr_irq(int irq, void *_pwr)
+{
+ struct tps65218_pwrbutton *pwr = _pwr;
+ unsigned int reg;
+ int error;
+
+ error = tps65218_reg_read(pwr->tps, TPS65218_REG_STATUS, &reg);
+ if (error) {
+ dev_err(pwr->dev, "can't read register: %d\n", error);
+ goto out;
+ }
+
+ if (reg & TPS65218_STATUS_PB_STATE) {
+ input_report_key(pwr->idev, KEY_POWER, 1);
+ pm_wakeup_event(pwr->dev, 0);
+ } else {
+ input_report_key(pwr->idev, KEY_POWER, 0);
+ }
+
+ input_sync(pwr->idev);
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int tps65218_pwron_probe(struct platform_device *pdev)
+{
+ struct tps65218 *tps = dev_get_drvdata(pdev->dev.parent);
+ struct device *dev = &pdev->dev;
+ struct tps65218_pwrbutton *pwr;
+ struct input_dev *idev;
+ int error;
+ int irq;
+
+ pwr = devm_kzalloc(dev, sizeof(*pwr), GFP_KERNEL);
+ if (!pwr)
+ return -ENOMEM;
+
+ idev = devm_input_allocate_device(dev);
+ if (!idev)
+ return -ENOMEM;
+
+ idev->name = "tps65218_pwrbutton";
+ idev->phys = "tps65218_pwrbutton/input0";
+ idev->dev.parent = dev;
+ idev->id.bustype = BUS_I2C;
+
+ input_set_capability(idev, EV_KEY, KEY_POWER);
+
+ pwr->tps = tps;
+ pwr->dev = dev;
+ pwr->idev = idev;
+ platform_set_drvdata(pdev, pwr);
+ device_init_wakeup(dev, true);
+
+ irq = platform_get_irq(pdev, 0);
+ error = devm_request_threaded_irq(dev, irq, NULL, tps65218_pwr_irq,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ "tps65218-pwrbutton", pwr);
+ if (error) {
+ dev_err(dev, "failed to request IRQ #%d: %d\n",
+ irq, error);
+ return error;
+ }
+
+ error= input_register_device(idev);
+ if (error) {
+ dev_err(dev, "Can't register power button: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static struct of_device_id of_tps65218_pwr_match[] = {
+ { .compatible = "ti,tps65218-pwrbutton" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, of_tps65218_pwr_match);
+
+static struct platform_driver tps65218_pwron_driver = {
+ .probe = tps65218_pwron_probe,
+ .driver = {
+ .name = "tps65218_pwrbutton",
+ .of_match_table = of_tps65218_pwr_match,
+ },
+};
+module_platform_driver(tps65218_pwron_driver);
+
+MODULE_DESCRIPTION("TPS65218 Power Button");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>");
diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
index d8b46b0f2dbe..4658b5d41dd7 100644
--- a/drivers/input/mouse/Kconfig
+++ b/drivers/input/mouse/Kconfig
@@ -105,19 +105,12 @@ config MOUSE_PS2_ELANTECH
Say Y here if you have an Elantech PS/2 touchpad connected
to your system.
- Note that if you enable this driver you will need an updated
- X.org Synaptics driver that does not require ABS_PRESSURE
- reports from the touchpad (i.e. post 1.5.0 version). You can
- grab a patch for the driver here:
-
- http://userweb.kernel.org/~dtor/synaptics-no-abspressure.patch
-
- If unsure, say N.
-
This driver exposes some configuration registers via sysfs
entries. For further information,
see <file:Documentation/input/elantech.txt>.
+ If unsure, say N.
+
config MOUSE_PS2_SENTELIC
bool "Sentelic Finger Sensing Pad PS/2 protocol extension"
depends on MOUSE_PS2
@@ -146,6 +139,16 @@ config MOUSE_PS2_OLPC
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_SERIAL
tristate "Serial mouse"
select SERIO
@@ -206,6 +209,7 @@ config MOUSE_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.
diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile
index 560003dcac37..8a9c98e76d9c 100644
--- a/drivers/input/mouse/Makefile
+++ b/drivers/input/mouse/Makefile
@@ -8,7 +8,7 @@ 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) += cyapa.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
@@ -24,6 +24,7 @@ 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
psmouse-objs := psmouse-base.o synaptics.o focaltech.o
psmouse-$(CONFIG_MOUSE_PS2_ALPS) += alps.o
diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c
index d88d73d83552..f205b8be2ce4 100644
--- a/drivers/input/mouse/alps.c
+++ b/drivers/input/mouse/alps.c
@@ -435,7 +435,7 @@ static void alps_report_mt_data(struct psmouse *psmouse, int n)
struct alps_fields *f = &priv->f;
int i, slot[MAX_TOUCHES];
- input_mt_assign_slots(dev, slot, f->mt, n);
+ 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);
@@ -475,6 +475,13 @@ static void alps_process_trackstick_packet_v3(struct psmouse *psmouse)
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");
@@ -699,7 +706,8 @@ static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
alps_report_semi_mt_data(psmouse, fingers);
- if (!(priv->quirks & ALPS_QUIRK_TRACKSTICK_BUTTONS)) {
+ 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);
@@ -743,8 +751,11 @@ static void alps_process_packet_v6(struct psmouse *psmouse)
*/
if (packet[5] == 0x7F) {
/* It should be a DualPoint when received Trackpoint packet */
- if (!(priv->flags & ALPS_DUALPOINT))
+ 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);
@@ -1026,6 +1037,13 @@ static void alps_process_trackstick_packet_v7(struct psmouse *psmouse)
struct input_dev *dev2 = 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;
+ }
+
/*
* b7 b6 b5 b4 b3 b2 b1 b0
* Byte0 0 1 0 0 1 0 0 0
@@ -2443,14 +2461,24 @@ int alps_init(struct psmouse *psmouse)
dev1->keybit[BIT_WORD(BTN_MIDDLE)] |= BIT_MASK(BTN_MIDDLE);
}
+ if (priv->flags & ALPS_DUALPOINT) {
+ /*
+ * 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.product = PSMOUSE_ALPS;
+ dev2->id.version = priv->proto_version;
+ } else {
+ dev2->name = "PS/2 ALPS Mouse";
+ dev2->id.product = PSMOUSE_PS2;
+ dev2->id.version = 0x0000;
+ }
+
snprintf(priv->phys, sizeof(priv->phys), "%s/input1", psmouse->ps2dev.serio->phys);
dev2->phys = priv->phys;
- dev2->name = (priv->flags & ALPS_DUALPOINT) ?
- "DualPoint Stick" : "ALPS PS/2 Device";
dev2->id.bustype = BUS_I8042;
dev2->id.vendor = 0x0002;
- dev2->id.product = PSMOUSE_ALPS;
- dev2->id.version = 0x0000;
dev2->dev.parent = &psmouse->ps2dev.serio->dev;
dev2->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
diff --git a/drivers/input/mouse/bcm5974.c b/drivers/input/mouse/bcm5974.c
index c329cdb0b91a..b10709f04615 100644
--- a/drivers/input/mouse/bcm5974.c
+++ b/drivers/input/mouse/bcm5974.c
@@ -564,7 +564,7 @@ static int report_tp_state(struct bcm5974 *dev, int size)
dev->index[n++] = &f[i];
}
- input_mt_assign_slots(input, dev->slots, dev->pos, n);
+ input_mt_assign_slots(input, dev->slots, dev->pos, n, 0);
for (i = 0; i < n; i++)
report_finger_data(input, dev->slots[i],
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 1bece8cad46f..58f4f6fa4857 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -20,408 +20,131 @@
#include <linux/input/mt.h>
#include <linux/interrupt.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/pm_runtime.h>
+#include <linux/acpi.h>
+#include "cyapa.h"
-/* APA trackpad firmware generation */
-#define CYAPA_GEN3 0x03 /* support MT-protocol B with tracking ID. */
-
-#define CYAPA_NAME "Cypress APA Trackpad (cyapa)"
-
-/* 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
-
-/* report data start reg offset address. */
-#define DATA_REG_START_OFFSET 0x0000
-
-#define BL_HEAD_OFFSET 0x00
-#define BL_DATA_OFFSET 0x10
-
-/*
- * 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)
-
-/*
- * Bootloader Status Register
- *
- * bit 7: Busy
- * bit 6 - 5: Reserved
- * bit 4: Bootloader running
- * bit 3 - 1: Reserved
- * bit 0: Checksum valid
- */
-#define REG_BL_STATUS 0x01
-#define BL_STATUS_BUSY 0x80
-#define BL_STATUS_RUNNING 0x10
-#define BL_STATUS_DATA_VALID 0x08
-#define BL_STATUS_CSUM_VALID 0x01
-
-/*
- * 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_STATUS_SIZE 3 /* length of bootloader status registers */
-#define BLK_HEAD_BYTES 32
-
-#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 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 CYAPA_OFFSET_SOFT_RESET REG_OFFSET_COMMAND_BASE
-
-#define REG_OFFSET_POWER_MODE (REG_OFFSET_COMMAND_BASE + 1)
-
-#define PWR_MODE_MASK 0xfc
-#define PWR_MODE_FULL_ACTIVE (0x3f << 2)
-#define PWR_MODE_IDLE (0x05 << 2) /* default sleep time is 50 ms. */
-#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_OFF (0x00 << 2)
-
-/*
- * 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
-
-enum cyapa_state {
- CYAPA_STATE_OP,
- CYAPA_STATE_BL_IDLE,
- CYAPA_STATE_BL_ACTIVE,
- CYAPA_STATE_BL_BUSY,
- CYAPA_STATE_NO_DEVICE,
-};
-
-
-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;
-
-/* The touch.id is used as the MT slot id, thus max MT slot is 15 */
-#define CYAPA_MAX_MT_SLOTS 15
-
-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;
-
-/* The main device structure */
-struct cyapa {
- enum cyapa_state state;
-
- struct i2c_client *client;
- struct input_dev *input;
- char phys[32]; /* device physical location */
- bool irq_wake; /* irq wake is enabled */
- bool smbus;
-
- /* read from query data region. */
- char product_id[16];
- u8 btn_capability;
- u8 gen;
- int max_abs_x;
- int max_abs_y;
- int physical_size_x;
- int physical_size_y;
-};
-
-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 };
-
-struct cyapa_cmd_len {
- u8 cmd;
- u8 len;
-};
#define CYAPA_ADAPTER_FUNC_NONE 0
#define CYAPA_ADAPTER_FUNC_I2C 1
#define CYAPA_ADAPTER_FUNC_SMBUS 2
#define CYAPA_ADAPTER_FUNC_BOTH 3
-/*
- * 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
-
- /* for byte read/write command */
-#define CMD_RESET 0
-#define CMD_POWER_MODE 1
-#define CMD_DEV_STATUS 2
-#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)
-
- /* 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)
-
-static const struct cyapa_cmd_len cyapa_i2c_cmds[] = {
- { CYAPA_OFFSET_SOFT_RESET, 1 },
- { REG_OFFSET_COMMAND_BASE + 1, 1 },
- { REG_OFFSET_DATA_BASE, 1 },
- { REG_OFFSET_DATA_BASE, sizeof(struct cyapa_reg_data) },
- { REG_OFFSET_COMMAND_BASE, 0 },
- { REG_OFFSET_QUERY_BASE, QUERY_DATA_SIZE },
- { BL_HEAD_OFFSET, 3 },
- { BL_HEAD_OFFSET, 16 },
- { BL_HEAD_OFFSET, 16 },
- { BL_DATA_OFFSET, 16 },
- { BL_HEAD_OFFSET, 32 },
- { REG_OFFSET_QUERY_BASE, PRODUCT_ID_SIZE },
- { REG_OFFSET_DATA_BASE, 32 }
-};
+#define CYAPA_FW_NAME "cyapa.bin"
-static const struct cyapa_cmd_len cyapa_smbus_cmds[] = {
- { CYAPA_SMBUS_RESET, 1 },
- { CYAPA_SMBUS_POWER_MODE, 1 },
- { CYAPA_SMBUS_DEV_STATUS, 1 },
- { CYAPA_SMBUS_GROUP_DATA, sizeof(struct cyapa_reg_data) },
- { CYAPA_SMBUS_GROUP_CMD, 2 },
- { CYAPA_SMBUS_GROUP_QUERY, QUERY_DATA_SIZE },
- { CYAPA_SMBUS_BL_STATUS, 3 },
- { CYAPA_SMBUS_BL_HEAD, 16 },
- { CYAPA_SMBUS_BL_CMD, 16 },
- { CYAPA_SMBUS_BL_DATA, 16 },
- { CYAPA_SMBUS_BL_ALL, 32 },
- { CYAPA_SMBUS_BLK_PRODUCT_ID, PRODUCT_ID_SIZE },
- { CYAPA_SMBUS_BLK_HEAD, 16 },
-};
+const char product_id[] = "CYTRA";
-static ssize_t cyapa_i2c_reg_read_block(struct cyapa *cyapa, u8 reg, size_t len,
- u8 *values)
+static int cyapa_reinitialize(struct cyapa *cyapa);
+
+static inline bool cyapa_is_bootloader_mode(struct cyapa *cyapa)
{
- return i2c_smbus_read_i2c_block_data(cyapa->client, reg, len, values);
+ if (cyapa->gen == CYAPA_GEN5 && cyapa->state == CYAPA_STATE_GEN5_BL)
+ return true;
+
+ if (cyapa->gen == CYAPA_GEN3 &&
+ cyapa->state >= CYAPA_STATE_BL_BUSY &&
+ cyapa->state <= CYAPA_STATE_BL_ACTIVE)
+ return true;
+
+ return false;
}
-static ssize_t cyapa_i2c_reg_write_block(struct cyapa *cyapa, u8 reg,
- size_t len, const u8 *values)
+static inline bool cyapa_is_operational_mode(struct cyapa *cyapa)
{
- return i2c_smbus_write_i2c_block_data(cyapa->client, reg, len, values);
+ if (cyapa->gen == CYAPA_GEN5 && cyapa->state == CYAPA_STATE_GEN5_APP)
+ return true;
+
+ if (cyapa->gen == CYAPA_GEN3 && cyapa->state == CYAPA_STATE_OP)
+ return true;
+
+ return false;
}
-/*
- * 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.
- */
-static ssize_t cyapa_smbus_read_block(struct cyapa *cyapa, u8 cmd, size_t len,
- u8 *values)
+/* 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)
{
- ssize_t ret;
- u8 index;
- u8 smbus_cmd;
- u8 *buf;
struct i2c_client *client = cyapa->client;
+ struct i2c_msg msgs[] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .len = 1,
+ .buf = &reg,
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = len,
+ .buf = values,
+ },
+ };
+ int ret;
- 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 = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
- 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;
- }
+ if (ret != ARRAY_SIZE(msgs))
+ return ret < 0 ? ret : -EIO;
-out:
- return ret > 0 ? len : ret;
+ return 0;
}
-static s32 cyapa_read_byte(struct cyapa *cyapa, u8 cmd_idx)
+/**
+ * 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)
{
- u8 cmd;
+ struct i2c_client *client = cyapa->client;
+ char buf[32];
+ int ret;
- 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);
-}
+ if (len > sizeof(buf) - 1)
+ return -ENOMEM;
-static s32 cyapa_write_byte(struct cyapa *cyapa, u8 cmd_idx, u8 value)
-{
- u8 cmd;
+ buf[0] = reg;
+ memcpy(&buf[1], values, len);
- 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);
+ ret = i2c_master_send(client, buf, len + 1);
+ if (ret != len + 1)
+ return ret < 0 ? ret : -EIO;
+
+ return 0;
}
-static ssize_t cyapa_read_block(struct cyapa *cyapa, u8 cmd_idx, u8 *values)
+static u8 cyapa_check_adapter_functionality(struct i2c_client *client)
{
- u8 cmd;
- size_t len;
+ u8 ret = CYAPA_ADAPTER_FUNC_NONE;
- 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);
- } else {
- cmd = cyapa_i2c_cmds[cmd_idx].cmd;
- len = cyapa_i2c_cmds[cmd_idx].len;
- return cyapa_i2c_reg_read_block(cyapa, cmd, len, values);
- }
+ 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;
@@ -433,39 +156,74 @@ static int cyapa_get_state(struct cyapa *cyapa)
*
*/
error = cyapa_i2c_reg_read_block(cyapa, BL_HEAD_OFFSET, BL_STATUS_SIZE,
- status);
+ 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))
- error = cyapa_read_block(cyapa, CYAPA_CMD_BL_STATUS, status);
+ 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;
- if ((status[REG_OP_STATUS] & OP_STATUS_SRC) == OP_STATUS_SRC) {
- switch (status[REG_OP_STATUS] & OP_STATUS_DEV) {
- case CYAPA_DEV_NORMAL:
- case CYAPA_DEV_BUSY:
- cyapa->state = CYAPA_STATE_OP;
- break;
- default:
- error = -EAGAIN;
- 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;
}
- } else {
- if (status[REG_BL_STATUS] & BL_STATUS_BUSY)
- cyapa->state = CYAPA_STATE_BL_BUSY;
- else if (status[REG_BL_ERROR] & BL_ERROR_BOOTLOADING)
- cyapa->state = CYAPA_STATE_BL_ACTIVE;
- else
- cyapa->state = CYAPA_STATE_BL_IDLE;
- }
+ 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;
}
@@ -482,364 +240,939 @@ error:
* Returns:
* 0 when the device eventually responds with a valid non-busy state.
* -ETIMEDOUT if device never responds (too many -EAGAIN)
- * < 0 other errors
+ * -EAGAIN if bootload is busy, or unknown state.
+ * < 0 other errors
*/
-static int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout)
+int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout)
{
int error;
int tries = timeout / 100;
- error = cyapa_get_state(cyapa);
- while ((error || cyapa->state >= CYAPA_STATE_BL_BUSY) && tries--) {
- msleep(100);
+ do {
error = cyapa_get_state(cyapa);
- }
- return (error == -EAGAIN || error == -ETIMEDOUT) ? -ETIMEDOUT : error;
-}
+ if (!error && cyapa->state > CYAPA_STATE_BL_BUSY)
+ return 0;
-static int cyapa_bl_deactivate(struct cyapa *cyapa)
-{
- int error;
-
- error = cyapa_i2c_reg_write_block(cyapa, 0, sizeof(bl_deactivate),
- bl_deactivate);
- if (error)
- return error;
+ msleep(100);
+ } while (tries--);
- /* 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;
+ return (error == -EAGAIN || error == -ETIMEDOUT) ? -ETIMEDOUT : error;
}
/*
- * Exit bootloader
+ * Check if device is operational.
*
- * 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.
+ * 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
- * -EAGAIN device is stuck in bootloader, b/c it has invalid firmware
- * 0 device is supported and in operational mode
+ * -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_bl_exit(struct cyapa *cyapa)
+static int cyapa_check_is_operational(struct cyapa *cyapa)
{
int error;
- error = cyapa_i2c_reg_write_block(cyapa, 0, sizeof(bl_exit), bl_exit);
+ error = cyapa_poll_state(cyapa, 4000);
if (error)
return error;
- /*
- * Wait for bootloader to exit, and operation mode to start.
- * Normally, this takes at least 50 ms.
- */
- usleep_range(50000, 100000);
- /*
- * 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.
- */
- error = cyapa_poll_state(cyapa, 2000);
- if (error < 0)
- return error;
- if (cyapa->state != CYAPA_STATE_OP)
- return -EAGAIN;
+ switch (cyapa->gen) {
+ case CYAPA_GEN5:
+ cyapa->ops = &cyapa_gen5_ops;
+ break;
+ case CYAPA_GEN3:
+ cyapa->ops = &cyapa_gen3_ops;
+ break;
+ default:
+ return -ENODEV;
+ }
- return 0;
+ error = cyapa->ops->operational_check(cyapa);
+ if (!error && cyapa_is_operational_mode(cyapa))
+ cyapa->operational = true;
+ else
+ cyapa->operational = false;
+
+ return error;
}
+
/*
- * Set device power mode
- *
+ * Returns 0 on device detected, negative errno on no device detected.
+ * And when the device is detected and opertaional, it will be reset to
+ * full power active mode automatically.
*/
-static int cyapa_set_power_mode(struct cyapa *cyapa, u8 power_mode)
+static int cyapa_detect(struct cyapa *cyapa)
{
struct device *dev = &cyapa->client->dev;
- int ret;
- u8 power;
-
- if (cyapa->state != CYAPA_STATE_OP)
- return 0;
+ int error;
- ret = cyapa_read_byte(cyapa, CYAPA_CMD_POWER_MODE);
- if (ret < 0)
- return ret;
+ 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;
+ }
- power = ret & ~PWR_MODE_MASK;
- power |= power_mode & PWR_MODE_MASK;
- ret = cyapa_write_byte(cyapa, CYAPA_CMD_POWER_MODE, power);
- if (ret < 0) {
- dev_err(dev, "failed to set power_mode 0x%02x err = %d\n",
- power_mode, ret);
- return ret;
+ dev_err(dev, "no device detected: %d\n", error);
+ return error;
}
return 0;
}
-static int cyapa_get_query_data(struct cyapa *cyapa)
+static int cyapa_open(struct input_dev *input)
{
- u8 query_data[QUERY_DATA_SIZE];
- int ret;
+ struct cyapa *cyapa = input_get_drvdata(input);
+ struct i2c_client *client = cyapa->client;
+ int error;
- if (cyapa->state != CYAPA_STATE_OP)
- return -EBUSY;
+ error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+ if (error)
+ return error;
- ret = cyapa_read_block(cyapa, CYAPA_CMD_GROUP_QUERY, query_data);
- if (ret < 0)
- return ret;
- if (ret != QUERY_DATA_SIZE)
- return -EIO;
+ 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);
+ if (error) {
+ dev_warn(&client->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(&client->dev)) {
+ pm_runtime_set_active(&client->dev);
+ pm_runtime_enable(&client->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;
+
+ mutex_lock(&cyapa->state_sync_lock);
+
+ disable_irq(client->irq);
+ if (pm_runtime_enabled(&client->dev))
+ pm_runtime_disable(&client->dev);
+ pm_runtime_set_suspended(&client->dev);
+
+ if (cyapa->operational)
+ cyapa->ops->set_power_mode(cyapa, PWR_MODE_OFF, 0);
- 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';
+ mutex_unlock(&cyapa->state_sync_lock);
+}
- cyapa->btn_capability = query_data[19] & CAPABILITY_BTN_MASK;
+static int cyapa_create_input_dev(struct cyapa *cyapa)
+{
+ struct device *dev = &cyapa->client->dev;
+ struct input_dev *input;
+ int error;
- cyapa->gen = query_data[20] & 0x0f;
+ if (!cyapa->physical_size_x || !cyapa->physical_size_y)
+ return -EINVAL;
- cyapa->max_abs_x = ((query_data[21] & 0xf0) << 4) | query_data[22];
- cyapa->max_abs_y = ((query_data[21] & 0x0f) << 8) | query_data[23];
+ 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_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;
+ }
- cyapa->physical_size_x =
- ((query_data[24] & 0xf0) << 4) | query_data[25];
- cyapa->physical_size_y =
- ((query_data[24] & 0x0f) << 8) | query_data[26];
+ /* 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);
+ /* 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);
+ }
+}
+
/*
- * Check if device is operational.
+ * cyapa_sleep_time_to_pwr_cmd and cyapa_pwr_cmd_to_sleep_time
*
- * An operational device is responding, has exited bootloader, and has
- * firmware supported by this driver.
+ * 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.
*
- * 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
+ * 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;
*/
-static int cyapa_check_is_operational(struct cyapa *cyapa)
+u8 cyapa_sleep_time_to_pwr_cmd(u16 sleep_time)
{
- struct device *dev = &cyapa->client->dev;
- static const char unique_str[] = "CYTRA";
- int error;
+ u16 encoded_time;
- error = cyapa_poll_state(cyapa, 2000);
+ 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)
return error;
- switch (cyapa->state) {
- case CYAPA_STATE_BL_ACTIVE:
- error = cyapa_bl_deactivate(cyapa);
- if (error)
- return error;
- /* Fallthrough state */
- case CYAPA_STATE_BL_IDLE:
- error = cyapa_bl_exit(cyapa);
- if (error)
- return error;
+ error = cyapa_detect(cyapa);
+ if (error)
+ return error;
- /* Fallthrough state */
- case CYAPA_STATE_OP:
- error = cyapa_get_query_data(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);
- /* only support firmware protocol gen3 */
- if (cyapa->gen != CYAPA_GEN3) {
- dev_err(dev, "unsupported protocol version (%d)",
- cyapa->gen);
- return -EINVAL;
- }
+ 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);
- /* only support product ID starting with CYTRA */
- if (memcmp(cyapa->product_id, unique_str,
- sizeof(unique_str) - 1) != 0) {
- dev_err(dev, "unsupported product ID (%s)\n",
- cyapa->product_id);
- return -EINVAL;
+ /* Avoid command failures when TP was in OFF state. */
+ if (cyapa->operational)
+ cyapa->ops->set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE, 0);
+
+ 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;
}
- return 0;
+ }
- default:
- return -EIO;
+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);
+ } 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);
}
- return 0;
+
+ return error;
}
static irqreturn_t cyapa_irq(int irq, void *dev_id)
{
struct cyapa *cyapa = dev_id;
struct device *dev = &cyapa->client->dev;
- struct input_dev *input = cyapa->input;
- struct cyapa_reg_data data;
- int i;
- int ret;
- int num_fingers;
+ pm_runtime_get_sync(dev);
if (device_may_wakeup(dev))
pm_wakeup_event(dev, 0);
- ret = cyapa_read_block(cyapa, CYAPA_CMD_GROUP_DATA, (u8 *)&data);
- if (ret != sizeof(data))
- goto out;
+ /* Interrupt event maybe cuased 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 probling or in firware image
+ * udpating or reading.
+ */
+ cyapa->ops->sort_empty_output_data(cyapa,
+ NULL, NULL, NULL);
+ goto out;
+ }
- 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) {
- goto out;
+ if (!cyapa->operational || cyapa->ops->irq_handler(cyapa)) {
+ 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);
+ }
}
- 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;
+out:
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_sync_autosuspend(dev);
+ 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 = cyapa->suspend_power_mode;
+ 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;
- 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);
+ 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;
}
- input_mt_sync_frame(input);
+ return len;
+}
- if (cyapa->btn_capability & CAPABILITY_LEFT_BTN_MASK)
- input_report_key(input, BTN_LEFT,
- data.finger_btn & OP_DATA_LEFT_BTN);
+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;
- if (cyapa->btn_capability & CAPABILITY_MIDDLE_BTN_MASK)
- input_report_key(input, BTN_MIDDLE,
- data.finger_btn & OP_DATA_MIDDLE_BTN);
+ error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+ if (error)
+ return error;
- if (cyapa->btn_capability & CAPABILITY_RIGHT_BTN_MASK)
- input_report_key(input, BTN_RIGHT,
- data.finger_btn & OP_DATA_RIGHT_BTN);
+ 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 = max_t(u16, sleep_time, 1000);
+ cyapa->suspend_power_mode =
+ cyapa_sleep_time_to_pwr_cmd(cyapa->suspend_sleep_time);
+ } else {
+ count = -EINVAL;
+ }
- input_sync(input);
+ mutex_unlock(&cyapa->state_sync_lock);
-out:
- return IRQ_HANDLED;
+ return count;
}
-static u8 cyapa_check_adapter_functionality(struct i2c_client *client)
+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)
{
- u8 ret = CYAPA_ADAPTER_FUNC_NONE;
+ struct cyapa *cyapa = data;
- 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;
+ sysfs_unmerge_group(&cyapa->client->dev.kobj,
+ &cyapa_power_wakeup_group);
}
-static int cyapa_open(struct input_dev *input)
+static int cyapa_prepare_wakeup_controls(struct cyapa *cyapa)
{
- struct cyapa *cyapa = input_get_drvdata(input);
struct i2c_client *client = cyapa->client;
+ struct device *dev = &client->dev;
+ int error;
+
+ if (device_can_wakeup(dev)) {
+ error = sysfs_merge_group(&client->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 = max_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;
- error = cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE);
+ 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(&client->dev, "set active power failed: %d\n", error);
+ dev_err(dev,
+ "failed to create power runtime group: %d\n", error);
return error;
}
- enable_irq(client->irq);
+ 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 void cyapa_close(struct input_dev *input)
+static ssize_t cyapa_show_fm_ver(struct device *dev,
+ struct device_attribute *attr, char *buf)
{
- struct cyapa *cyapa = input_get_drvdata(input);
+ int error;
+ struct cyapa *cyapa = dev_get_drvdata(dev);
- disable_irq(cyapa->client->irq);
- cyapa_set_power_mode(cyapa, PWR_MODE_OFF);
+ 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 int cyapa_create_input_dev(struct cyapa *cyapa)
+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;
- struct input_dev *input;
+ const struct firmware *fw;
int error;
- if (!cyapa->physical_size_x || !cyapa->physical_size_y)
- return -EINVAL;
+ 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;
+ }
- input = devm_input_allocate_device(dev);
- if (!input) {
- dev_err(dev, "failed to allocate memory for input device.\n");
- return -ENOMEM;
+ error = cyapa->ops->check_fw(cyapa, fw);
+ if (error) {
+ dev_err(dev, "Invalid CYAPA firmware image: %s\n",
+ fw_name);
+ goto done;
}
- 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;
+ /*
+ * 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);
- input->open = cyapa_open;
- input->close = cyapa_close;
+ /* Require IRQ support for firmware update commands. */
+ cyapa_enable_irq_for_cmd(cyapa);
- input_set_drvdata(input, cyapa);
+ error = cyapa->ops->bl_enter(cyapa);
+ if (error) {
+ dev_err(dev, "bl_enter failed, %d\n", error);
+ goto err_detect;
+ }
- __set_bit(EV_ABS, input->evbit);
+ error = cyapa->ops->bl_activate(cyapa);
+ if (error) {
+ dev_err(dev, "bl_activate failed, %d\n", error);
+ goto err_detect;
+ }
- /* 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, 255, 0, 0);
+ error = cyapa->ops->bl_initiate(cyapa, fw);
+ if (error) {
+ dev_err(dev, "bl_initiate failed, %d\n", error);
+ goto err_detect;
+ }
- 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);
+ error = cyapa->ops->update_fw(cyapa, fw);
+ if (error) {
+ dev_err(dev, "update_fw failed, %d\n", error);
+ goto err_detect;
+ }
- 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);
+err_detect:
+ cyapa_disable_irq_for_cmd(cyapa);
+ pm_runtime_put_noidle(dev);
- if (cyapa->btn_capability == CAPABILITY_LEFT_BTN_MASK)
- __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+done:
+ release_firmware(fw);
+ return error;
+}
- /* 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);
+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) {
- dev_err(dev, "failed to initialize MT slots: %d\n", 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;
}
- cyapa->input = input;
- return 0;
+ 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");
+
+ /*
+ * Redetect 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 redetect 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:
+ return "bootloader";
+ case CYAPA_STATE_OP:
+ case CYAPA_STATE_GEN5_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 int cyapa_probe(struct i2c_client *client,
@@ -848,6 +1181,7 @@ static int cyapa_probe(struct i2c_client *client,
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);
@@ -856,38 +1190,54 @@ static int cyapa_probe(struct i2c_client *client,
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;
- cyapa->gen = CYAPA_GEN3;
+ /* 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);
- /* i2c isn't supported, use smbus */
- if (adapter_func == CYAPA_ADAPTER_FUNC_SMBUS)
- cyapa->smbus = true;
+ error = cyapa_initialize(cyapa);
+ if (error) {
+ dev_err(dev, "failed to detect and initialize tp device.\n");
+ return error;
+ }
- cyapa->state = CYAPA_STATE_NO_DEVICE;
+ error = sysfs_create_group(&client->dev.kobj, &cyapa_sysfs_group);
+ if (error) {
+ dev_err(dev, "failed to create sysfs entries: %d\n", error);
+ return error;
+ }
- error = cyapa_check_is_operational(cyapa);
+ error = devm_add_action(dev, cyapa_remove_sysfs_group, cyapa);
if (error) {
- dev_err(dev, "device not operational, %d\n", error);
+ cyapa_remove_sysfs_group(cyapa);
+ dev_err(dev, "failed to add sysfs cleanup action: %d\n", error);
return error;
}
- /* Power down the device until we need it */
- error = cyapa_set_power_mode(cyapa, PWR_MODE_OFF);
+ error = cyapa_prepare_wakeup_controls(cyapa);
if (error) {
- dev_err(dev, "failed to quiesce the device: %d\n", error);
+ dev_err(dev, "failed to prepare wakeup controls: %d\n", error);
return error;
}
- error = cyapa_create_input_dev(cyapa);
- if (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,
@@ -901,11 +1251,18 @@ static int cyapa_probe(struct i2c_client *client,
/* Disable IRQ until the device is opened */
disable_irq(client->irq);
- /* Register the device in input subsystem */
- error = input_register_device(cyapa->input);
- if (error) {
- dev_err(dev, "failed to register input device: %d\n", error);
- return error;
+ /*
+ * 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;
@@ -915,32 +1272,40 @@ static int __maybe_unused cyapa_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct cyapa *cyapa = i2c_get_clientdata(client);
- struct input_dev *input = cyapa->input;
u8 power_mode;
int error;
- error = mutex_lock_interruptible(&input->mutex);
+ 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.
*/
- power_mode = device_may_wakeup(dev) ? PWR_MODE_IDLE
- : PWR_MODE_OFF;
- error = cyapa_set_power_mode(cyapa, power_mode);
- if (error)
- dev_err(dev, "resume: set power mode to %d failed: %d\n",
- power_mode, error);
+ 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);
+ if (error)
+ dev_err(dev, "suspend set power mode failed: %d\n",
+ error);
+ }
if (device_may_wakeup(dev))
cyapa->irq_wake = (enable_irq_wake(client->irq) == 0);
- mutex_unlock(&input->mutex);
-
+ mutex_unlock(&cyapa->state_sync_lock);
return 0;
}
@@ -948,29 +1313,56 @@ static int __maybe_unused cyapa_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct cyapa *cyapa = i2c_get_clientdata(client);
- struct input_dev *input = cyapa->input;
- u8 power_mode;
int error;
- mutex_lock(&input->mutex);
+ mutex_lock(&cyapa->state_sync_lock);
- if (device_may_wakeup(dev) && cyapa->irq_wake)
+ if (device_may_wakeup(dev) && cyapa->irq_wake) {
disable_irq_wake(client->irq);
+ cyapa->irq_wake = false;
+ }
- power_mode = input->users ? PWR_MODE_FULL_ACTIVE : PWR_MODE_OFF;
- error = cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE);
+ /* Update device states and runtime PM states. */
+ error = cyapa_reinitialize(cyapa);
if (error)
- dev_warn(dev, "resume: set power mode to %d failed: %d\n",
- power_mode, error);
+ dev_warn(dev, "failed to reinitialize TP device: %d\n", error);
enable_irq(client->irq);
- mutex_unlock(&input->mutex);
+ 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);
+ 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);
+ if (error)
+ dev_warn(dev, "runtime resume failed: %d\n", error);
return 0;
}
-static SIMPLE_DEV_PM_OPS(cyapa_pm_ops, cyapa_suspend, cyapa_resume);
+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 },
@@ -978,11 +1370,21 @@ static const struct i2c_device_id cyapa_id_table[] = {
};
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. */
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, cyapa_acpi_id);
+#endif
+
static struct i2c_driver cyapa_driver = {
.driver = {
.name = "cyapa",
.owner = THIS_MODULE,
.pm = &cyapa_pm_ops,
+ .acpi_match_table = ACPI_PTR(cyapa_acpi_id),
},
.probe = cyapa_probe,
diff --git a/drivers/input/mouse/cyapa.h b/drivers/input/mouse/cyapa.h
new file mode 100644
index 000000000000..adc9ed5dcb0e
--- /dev/null
+++ b/drivers/input/mouse/cyapa.h
@@ -0,0 +1,301 @@
+/*
+ * Cypress APA trackpad with I2C interface
+ *
+ * Author: Dudley Du <dudl@cypress.com>
+ *
+ * Copyright (C) 2014 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_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 UNINIT_SLEEP_TIME 0xFFFF
+#define UNINIT_PWR_MODE 0xFF
+
+#define BTN_ONLY_MODE_NAME "buttononly"
+#define OFF_MODE_NAME "off"
+
+/* 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);
+
+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);
+};
+
+struct cyapa_gen5_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;
+
+ u8 irq_cmd_buf[CYAPA_REG_MAP_SIZE];
+ u8 empty_buf[CYAPA_REG_MAP_SIZE];
+};
+
+union cyapa_cmd_states {
+ struct cyapa_gen5_cmd_states gen5;
+};
+
+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,
+};
+
+/* 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 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;
+
+ /* Read from query data region. */
+ char product_id[16];
+ 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 = rigth 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);
+
+
+extern const char product_id[];
+extern const struct cyapa_dev_ops cyapa_gen3_ops;
+extern const struct cyapa_dev_ops cyapa_gen5_ops;
+
+#endif
diff --git a/drivers/input/mouse/cyapa_gen3.c b/drivers/input/mouse/cyapa_gen3.c
new file mode 100644
index 000000000000..77e9d70a986b
--- /dev/null
+++ b/drivers/input/mouse/cyapa_gen3.c
@@ -0,0 +1,1247 @@
+/*
+ * 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-2014 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 <linux/unaligned/access_ok.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)
+
+ /* 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 */
+};
+
+
+/*
+ * 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.
+ */
+ usleep_range(50000, 100000);
+ /*
+ * 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);
+ int tries;
+ 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;
+ }
+
+ tries = 20; /* max recalibration timeout 2s. */
+ 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.
+ */
+ usleep_range(100000, 200000);
+
+ 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)
+ break;
+ } while (--tries);
+
+ if (tries == 0) {
+ dev_err(dev, "Failed to calibrate. Timeout.\n");
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ dev_dbg(dev, "Calibration successful.\n");
+
+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)
+{
+ int ret;
+ u8 power;
+ int tries;
+ u16 sleep_time;
+
+ always_unused = 0;
+ 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 = 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.
+ */
+ msleep(sleep_time);
+ return ret;
+}
+
+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);
+ 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 periodicly accerted 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_irq_handler(struct cyapa *cyapa)
+{
+ struct input_dev *input = cyapa->input;
+ struct device *dev = &cyapa->client->dev;
+ struct cyapa_reg_data data;
+ int num_fingers;
+ int ret;
+ int i;
+
+ 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;
+ }
+
+ 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_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,
+};
diff --git a/drivers/input/mouse/cyapa_gen5.c b/drivers/input/mouse/cyapa_gen5.c
new file mode 100644
index 000000000000..ddf5393a1180
--- /dev/null
+++ b/drivers/input/mouse/cyapa_gen5.c
@@ -0,0 +1,2777 @@
+/*
+ * Cypress APA trackpad with I2C interface
+ *
+ * Author: Dudley Du <dudl@cypress.com>
+ *
+ * Copyright (C) 2014 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 <linux/unaligned/access_ok.h>
+#include <linux/crc-itu-t.h>
+#include "cyapa.h"
+
+
+/* Macro of Gen5 */
+#define RECORD_EVENT_NONE 0
+#define RECORD_EVENT_TOUCHDOWN 1
+#define RECORD_EVENT_DISPLACE 2
+#define RECORD_EVENT_LIFTOFF 3
+
+#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
+
+#define GEN5_BL_CMD_VERIFY_APP_INTEGRITY 0x31
+#define GEN5_BL_CMD_GET_BL_INFO 0x38
+#define GEN5_BL_CMD_PROGRAM_VERIFY_ROW 0x39
+#define GEN5_BL_CMD_LAUNCH_APP 0x3b
+#define GEN5_BL_CMD_INITIATE_BL 0x48
+
+#define GEN5_HID_DESCRIPTOR_ADDR 0x0001
+#define GEN5_REPORT_DESCRIPTOR_ADDR 0x0002
+#define GEN5_INPUT_REPORT_ADDR 0x0003
+#define GEN5_OUTPUT_REPORT_ADDR 0x0004
+#define GEN5_CMD_DATA_ADDR 0x0006
+
+#define GEN5_TOUCH_REPORT_HEAD_SIZE 7
+#define GEN5_TOUCH_REPORT_MAX_SIZE 127
+#define GEN5_BTN_REPORT_HEAD_SIZE 6
+#define GEN5_BTN_REPORT_MAX_SIZE 14
+#define GEN5_WAKEUP_EVENT_SIZE 4
+#define GEN5_RAW_DATA_HEAD_SIZE 24
+
+#define GEN5_BL_CMD_REPORT_ID 0x40
+#define GEN5_BL_RESP_REPORT_ID 0x30
+#define GEN5_APP_CMD_REPORT_ID 0x2f
+#define GEN5_APP_RESP_REPORT_ID 0x1f
+
+#define GEN5_APP_DEEP_SLEEP_REPORT_ID 0xf0
+#define GEN5_DEEP_SLEEP_RESP_LENGTH 5
+
+#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_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_DEEP_SLEEP_STATE_MASK 0x03
+#define GEN5_DEEP_SLEEP_STATE_ON 0x00
+#define GEN5_DEEP_SLEEP_STATE_OFF 0x01
+
+#define GEN5_DEEP_SLEEP_OPCODE 0x08
+#define GEN5_DEEP_SLEEP_OPCODE_MASK 0x0f
+
+#define GEN5_POWER_READY_MAX_INTRVL_TIME 50 /* Unit: ms */
+#define GEN5_POWER_IDLE_MAX_INTRVL_TIME 250 /* Unit: ms */
+
+#define GEN5_CMD_REPORT_ID_OFFSET 4
+
+#define GEN5_RESP_REPORT_ID_OFFSET 2
+#define GEN5_RESP_RSVD_OFFSET 3
+#define GEN5_RESP_RSVD_KEY 0x00
+#define GEN5_RESP_BL_SOP_OFFSET 4
+#define GEN5_SOP_KEY 0x01 /* Start of Packet */
+#define GEN5_EOP_KEY 0x17 /* End of Packet */
+#define GEN5_RESP_APP_CMD_OFFSET 4
+#define GET_GEN5_CMD_CODE(reg) ((reg) & 0x7f)
+
+#define VALID_CMD_RESP_HEADER(resp, cmd) \
+ (((resp)[GEN5_RESP_REPORT_ID_OFFSET] == GEN5_APP_RESP_REPORT_ID) && \
+ ((resp)[GEN5_RESP_RSVD_OFFSET] == GEN5_RESP_RSVD_KEY) && \
+ (GET_GEN5_CMD_CODE((resp)[GEN5_RESP_APP_CMD_OFFSET]) == (cmd)))
+
+#define GEN5_MIN_BL_CMD_LENGTH 13
+#define GEN5_MIN_BL_RESP_LENGTH 11
+#define GEN5_MIN_APP_CMD_LENGTH 7
+#define GEN5_MIN_APP_RESP_LENGTH 5
+#define GEN5_UNSUPPORTED_CMD_RESP_LENGTH 6
+
+#define GEN5_RESP_LENGTH_OFFSET 0x00
+#define GEN5_RESP_LENGTH_SIZE 2
+
+#define GEN5_HID_DESCRIPTOR_SIZE 32
+#define GEN5_BL_HID_REPORT_ID 0xff
+#define GEN5_APP_HID_REPORT_ID 0xf7
+#define GEN5_BL_MAX_OUTPUT_LENGTH 0x0100
+#define GEN5_APP_MAX_OUTPUT_LENGTH 0x00fe
+
+#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_TOUCH_REPORT_ID 0x01
+#define GEN5_BTN_REPORT_ID 0x03
+#define GEN5_WAKEUP_EVENT_REPORT_ID 0x04
+#define GEN5_OLD_PUSH_BTN_REPORT_ID 0x05
+#define GEN5_PUSH_BTN_REPORT_ID 0x06
+
+#define GEN5_CMD_COMPLETE_SUCCESS(status) ((status) == 0x00)
+
+#define GEN5_BL_INITIATE_RESP_LEN 11
+#define GEN5_BL_FAIL_EXIT_RESP_LEN 11
+#define GEN5_BL_FAIL_EXIT_STATUS_CODE 0x0c
+#define GEN5_BL_VERIFY_INTEGRITY_RESP_LEN 12
+#define GEN5_BL_INTEGRITY_CHEKC_PASS 0x00
+#define GEN5_BL_BLOCK_WRITE_RESP_LEN 11
+#define GEN5_BL_READ_APP_INFO_RESP_LEN 31
+#define GEN5_CMD_CALIBRATE 0x28
+#define CYAPA_SENSING_MODE_MUTUAL_CAP_FINE 0x00
+#define CYAPA_SENSING_MODE_SELF_CAP 0x02
+
+#define GEN5_CMD_RETRIEVE_DATA_STRUCTURE 0x24
+#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 reterive PWC and panel scan commands */
+#define GEN5_RESP_DATA_STRUCTURE_OFFSET 10
+#define GEN5_PWC_DATA_ELEMENT_SIZE_MASK 0x07
+
+#define GEN5_NUMBER_OF_TOUCH_OFFSET 5
+#define GEN5_NUMBER_OF_TOUCH_MASK 0x1f
+#define GEN5_BUTTONS_OFFSET 5
+#define GEN5_BUTTONS_MASK 0x0f
+#define GEN5_GET_EVENT_ID(reg) (((reg) >> 5) & 0x03)
+#define GEN5_GET_TOUCH_ID(reg) ((reg) & 0x1f)
+
+#define GEN5_PRODUCT_FAMILY_MASK 0xf000
+#define GEN5_PRODUCT_FAMILY_TRACKPAD 0x1000
+
+#define TSG_INVALID_CMD 0xff
+
+struct cyapa_gen5_touch_record {
+ /*
+ * Bit 7 - 3: reserved
+ * Bit 2 - 0: touch type;
+ * 0 : standard finger;
+ * 1 - 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;
+
+ /* Touch intensity in counts, pressure value. */
+ 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_gen5_report_data {
+ u8 report_head[GEN5_TOUCH_REPORT_HEAD_SIZE];
+ struct cyapa_gen5_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];
+} __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 gen5_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 gen5_bl_packet_end {
+ __le16 crc;
+ u8 eop; /* End of packet, must be 17h */
+} __packed;
+
+struct gen5_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 gen5_bl_packet_start packet_start;
+ u8 data[0]; /* Command data variable based on commands */
+} __packed;
+
+/* Initiate bootload command data structure. */
+struct gen5_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 gen5_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 gen5_bl_flash_row_head {
+ u8 flash_array_id;
+ __le16 flash_row_id;
+ u8 flash_data[0];
+} __packed;
+
+struct gen5_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;
+
+/* Applicaton 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;
+
+/* Variables to record latest gen5 trackpad power states. */
+#define GEN5_DEV_SET_PWR_STATE(cyapa, s) ((cyapa)->dev_pwr_mode = (s))
+#define GEN5_DEV_GET_PWR_STATE(cyapa) ((cyapa)->dev_pwr_mode)
+#define GEN5_DEV_SET_SLEEP_TIME(cyapa, t) ((cyapa)->dev_sleep_time = (t))
+#define GEN5_DEV_GET_SLEEP_TIME(cyapa) ((cyapa)->dev_sleep_time)
+#define GEN5_DEV_UNINIT_SLEEP_TIME(cyapa) \
+ (((cyapa)->dev_sleep_time) == UNINIT_SLEEP_TIME)
+
+
+static u8 cyapa_gen5_bl_cmd_key[] = { 0xa5, 0x01, 0x02, 0x03,
+ 0xff, 0xfe, 0xfd, 0x5a };
+
+static int cyapa_gen5_initialize(struct cyapa *cyapa)
+{
+ struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+
+ init_completion(&gen5_pip->cmd_ready);
+ atomic_set(&gen5_pip->cmd_issued, 0);
+ mutex_init(&gen5_pip->cmd_lock);
+
+ gen5_pip->resp_sort_func = NULL;
+ gen5_pip->in_progress_cmd = TSG_INVALID_CMD;
+ gen5_pip->resp_data = NULL;
+ gen5_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. */
+static 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.
+ */
+static 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;
+}
+
+/**
+ * This function is aimed to dump all not read data in Gen5 trackpad
+ * before send any command, otherwise, the interrupt line will be blocked.
+ */
+static int cyapa_empty_pip_output_data(struct cyapa *cyapa,
+ u8 *buf, int *len, cb_sort func)
+{
+ struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+ 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, gen5_pip->empty_buf,
+ GEN5_RESP_LENGTH_SIZE);
+ if (error < 0)
+ return error;
+
+ length = get_unaligned_le16(gen5_pip->empty_buf);
+ if (length == GEN5_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 = GEN5_RESP_LENGTH_SIZE;
+ if (buf && buf_len && func &&
+ func(cyapa, gen5_pip->empty_buf, length)) {
+ length = min(buf_len, length);
+ memcpy(buf, gen5_pip->empty_buf, length);
+ *len = length;
+ /* Response found, success. */
+ return 0;
+ }
+ continue;
+ }
+
+ error = cyapa_i2c_pip_read(cyapa, gen5_pip->empty_buf, length);
+ if (error < 0)
+ return error;
+
+ report_count--;
+ empty_count = 0;
+ length = get_unaligned_le16(gen5_pip->empty_buf);
+ if (length <= GEN5_RESP_LENGTH_SIZE) {
+ empty_count++;
+ } else if (buf && buf_len && func &&
+ func(cyapa, gen5_pip->empty_buf, length)) {
+ length = min(buf_len, length);
+ memcpy(buf, gen5_pip->empty_buf, length);
+ *len = length;
+ /* Response found, success. */
+ return 0;
+ }
+
+ 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_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+ int error;
+
+ /* Wait for interrupt to set ready completion */
+ init_completion(&gen5_pip->cmd_ready);
+
+ atomic_inc(&gen5_pip->cmd_issued);
+ error = cyapa_i2c_pip_write(cyapa, cmd, cmd_len);
+ if (error) {
+ atomic_dec(&gen5_pip->cmd_issued);
+ return (error < 0) ? error : -EIO;
+ }
+
+ /* Wait for interrupt to indicate command is completed. */
+ timeout = wait_for_completion_timeout(&gen5_pip->cmd_ready,
+ msecs_to_jiffies(timeout));
+ if (timeout == 0) {
+ atomic_dec(&gen5_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_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+ int tries;
+ int length;
+ int error;
+
+ atomic_inc(&gen5_pip->cmd_issued);
+ error = cyapa_i2c_pip_write(cyapa, cmd, cmd_len);
+ if (error) {
+ atomic_dec(&gen5_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(&gen5_pip->cmd_issued);
+ return error;
+}
+
+static 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_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+ int error;
+
+ if (!cmd || !cmd_len)
+ return -EINVAL;
+
+ /* Commands must be serialized. */
+ error = mutex_lock_interruptible(&gen5_pip->cmd_lock);
+ if (error)
+ return error;
+
+ gen5_pip->resp_sort_func = func;
+ gen5_pip->resp_data = resp_data;
+ gen5_pip->resp_len = resp_len;
+
+ if (cmd_len >= GEN5_MIN_APP_CMD_LENGTH &&
+ cmd[4] == GEN5_APP_CMD_REPORT_ID) {
+ /* Application command */
+ gen5_pip->in_progress_cmd = cmd[6] & 0x7f;
+ } else if (cmd_len >= GEN5_MIN_BL_CMD_LENGTH &&
+ cmd[4] == GEN5_BL_CMD_REPORT_ID) {
+ /* Bootloader command */
+ gen5_pip->in_progress_cmd = cmd[7];
+ }
+
+ /* Send command data, wait and read output response data's length. */
+ if (irq_mode) {
+ gen5_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 {
+ gen5_pip->is_irq_mode = false;
+ error = cyapa_do_i2c_pip_cmd_polling(cyapa, cmd, cmd_len,
+ resp_data, resp_len, timeout, func);
+ }
+
+ gen5_pip->resp_sort_func = NULL;
+ gen5_pip->resp_data = NULL;
+ gen5_pip->resp_len = NULL;
+ gen5_pip->in_progress_cmd = TSG_INVALID_CMD;
+
+ mutex_unlock(&gen5_pip->cmd_lock);
+ return error;
+}
+
+static bool cyapa_gen5_sort_tsg_pip_bl_resp_data(struct cyapa *cyapa,
+ u8 *data, int len)
+{
+ if (!data || len < GEN5_MIN_BL_RESP_LENGTH)
+ return false;
+
+ /* Bootloader input report id 30h */
+ if (data[GEN5_RESP_REPORT_ID_OFFSET] == GEN5_BL_RESP_REPORT_ID &&
+ data[GEN5_RESP_RSVD_OFFSET] == GEN5_RESP_RSVD_KEY &&
+ data[GEN5_RESP_BL_SOP_OFFSET] == GEN5_SOP_KEY)
+ return true;
+
+ return false;
+}
+
+static bool cyapa_gen5_sort_tsg_pip_app_resp_data(struct cyapa *cyapa,
+ u8 *data, int len)
+{
+ struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+ int resp_len;
+
+ if (!data || len < GEN5_MIN_APP_RESP_LENGTH)
+ return false;
+
+ if (data[GEN5_RESP_REPORT_ID_OFFSET] == GEN5_APP_RESP_REPORT_ID &&
+ data[GEN5_RESP_RSVD_OFFSET] == GEN5_RESP_RSVD_KEY) {
+ resp_len = get_unaligned_le16(&data[GEN5_RESP_LENGTH_OFFSET]);
+ if (GET_GEN5_CMD_CODE(data[GEN5_RESP_APP_CMD_OFFSET]) == 0x00 &&
+ resp_len == GEN5_UNSUPPORTED_CMD_RESP_LENGTH &&
+ data[5] == gen5_pip->in_progress_cmd) {
+ /* Unsupported command code */
+ return false;
+ } else if (GET_GEN5_CMD_CODE(data[GEN5_RESP_APP_CMD_OFFSET]) ==
+ gen5_pip->in_progress_cmd) {
+ /* Correct command response received */
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool cyapa_gen5_sort_application_launch_data(struct cyapa *cyapa,
+ u8 *buf, int len)
+{
+ if (buf == NULL || len < GEN5_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_gen5_sort_hid_descriptor_data(struct cyapa *cyapa,
+ u8 *buf, int len)
+{
+ int resp_len;
+ int max_output_len;
+
+ /* Check hid descriptor. */
+ if (len != GEN5_HID_DESCRIPTOR_SIZE)
+ return false;
+
+ resp_len = get_unaligned_le16(&buf[GEN5_RESP_LENGTH_OFFSET]);
+ max_output_len = get_unaligned_le16(&buf[16]);
+ if (resp_len == GEN5_HID_DESCRIPTOR_SIZE) {
+ if (buf[GEN5_RESP_REPORT_ID_OFFSET] == GEN5_BL_HID_REPORT_ID &&
+ max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) {
+ /* BL mode HID Descriptor */
+ return true;
+ } else if ((buf[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_APP_HID_REPORT_ID) &&
+ max_output_len == GEN5_APP_MAX_OUTPUT_LENGTH) {
+ /* APP mode HID Descriptor */
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool cyapa_gen5_sort_deep_sleep_data(struct cyapa *cyapa,
+ u8 *buf, int len)
+{
+ if (len == GEN5_DEEP_SLEEP_RESP_LENGTH &&
+ buf[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_APP_DEEP_SLEEP_REPORT_ID &&
+ (buf[4] & GEN5_DEEP_SLEEP_OPCODE_MASK) ==
+ GEN5_DEEP_SLEEP_OPCODE)
+ return true;
+ return false;
+}
+
+static int gen5_idle_state_parse(struct cyapa *cyapa)
+{
+ u8 resp_data[GEN5_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[GEN5_RESP_LENGTH_OFFSET]);
+ if (length == GEN5_RESP_LENGTH_SIZE) {
+ /* Normal state of Gen5 with no data to respose */
+ 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 = GEN5_HID_DESCRIPTOR_SIZE;
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, GEN5_RESP_LENGTH_SIZE,
+ resp_data, &length,
+ 300,
+ cyapa_gen5_sort_hid_descriptor_data,
+ false);
+ if (error)
+ return error;
+
+ length = get_unaligned_le16(
+ &resp_data[GEN5_RESP_LENGTH_OFFSET]);
+ max_output_len = get_unaligned_le16(&resp_data[16]);
+ if ((length == GEN5_HID_DESCRIPTOR_SIZE ||
+ length == GEN5_RESP_LENGTH_SIZE) &&
+ (resp_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_BL_HID_REPORT_ID) &&
+ max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) {
+ /* BL mode HID Description read */
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ } else if ((length == GEN5_HID_DESCRIPTOR_SIZE ||
+ length == GEN5_RESP_LENGTH_SIZE) &&
+ (resp_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_APP_HID_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 Booloader 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,
+ GEN5_HID_DESCRIPTOR_SIZE);
+ if (ret != GEN5_HID_DESCRIPTOR_SIZE)
+ return ret < 0 ? ret : -EIO;
+ length = get_unaligned_le16(&resp_data[GEN5_RESP_LENGTH_OFFSET]);
+ max_output_len = get_unaligned_le16(&resp_data[16]);
+ if (length == GEN5_RESP_LENGTH_SIZE) {
+ if (reg_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_BL_HID_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 == GEN5_HID_DESCRIPTOR_SIZE &&
+ resp_data[2] == GEN5_BL_HID_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 == GEN5_HID_DESCRIPTOR_SIZE &&
+ (resp_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_APP_HID_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(&reg_data[GEN5_RESP_LENGTH_OFFSET]);
+ switch (reg_data[GEN5_RESP_REPORT_ID_OFFSET]) {
+ case GEN5_TOUCH_REPORT_ID:
+ if (length < GEN5_TOUCH_REPORT_HEAD_SIZE ||
+ length > GEN5_TOUCH_REPORT_MAX_SIZE)
+ return -EINVAL;
+ break;
+ case GEN5_BTN_REPORT_ID:
+ case GEN5_OLD_PUSH_BTN_REPORT_ID:
+ case GEN5_PUSH_BTN_REPORT_ID:
+ if (length < GEN5_BTN_REPORT_HEAD_SIZE ||
+ length > GEN5_BTN_REPORT_MAX_SIZE)
+ return -EINVAL;
+ break;
+ case GEN5_WAKEUP_EVENT_REPORT_ID:
+ if (length != GEN5_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_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+ 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(&reg_data[GEN5_RESP_LENGTH_OFFSET]);
+ ret = cyapa_i2c_pip_read(cyapa, gen5_pip->empty_buf, length);
+ if (ret != length)
+ return ret < 0 ? ret : -EIO;
+
+ if (length == GEN5_RESP_LENGTH_SIZE) {
+ /* Previous command has read the data through out. */
+ if (reg_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_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 ((gen5_pip->empty_buf[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_BL_RESP_REPORT_ID) &&
+ (gen5_pip->empty_buf[GEN5_RESP_RSVD_OFFSET] ==
+ GEN5_RESP_RSVD_KEY) &&
+ (gen5_pip->empty_buf[GEN5_RESP_BL_SOP_OFFSET] ==
+ GEN5_SOP_KEY) &&
+ (gen5_pip->empty_buf[length - 1] ==
+ GEN5_EOP_KEY)) {
+ /* Gen5 BL command response data detected */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ } else if (gen5_pip->empty_buf[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_APP_RESP_REPORT_ID &&
+ gen5_pip->empty_buf[GEN5_RESP_RSVD_OFFSET] ==
+ GEN5_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(&reg_data[GEN5_RESP_LENGTH_OFFSET]);
+ if (length == 0 || length == GEN5_RESP_LENGTH_SIZE) {
+ gen5_idle_state_parse(cyapa);
+ } else if (length == GEN5_HID_DESCRIPTOR_SIZE &&
+ (reg_data[2] == GEN5_BL_HID_REPORT_ID ||
+ reg_data[2] == GEN5_APP_HID_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 descriptior header. */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ } else if (reg_data[2] == GEN5_TOUCH_REPORT_ID ||
+ reg_data[2] == GEN5_BTN_REPORT_ID ||
+ reg_data[2] == GEN5_OLD_PUSH_BTN_REPORT_ID ||
+ reg_data[2] == GEN5_PUSH_BTN_REPORT_ID ||
+ reg_data[2] == GEN5_WAKEUP_EVENT_REPORT_ID) {
+ gen5_report_data_header_parse(cyapa, reg_data);
+ } else if (reg_data[2] == GEN5_BL_RESP_REPORT_ID ||
+ reg_data[2] == GEN5_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 int cyapa_gen5_bl_initiate(struct cyapa *cyapa,
+ const struct firmware *fw)
+{
+ struct cyapa_tsg_bin_image *image;
+ struct gen5_bl_cmd_head *bl_cmd_head;
+ struct gen5_bl_packet_start *bl_packet_start;
+ struct gen5_bl_initiate_cmd_data *cmd_data;
+ struct gen5_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 gen5_bl_cmd_head *)cmd;
+ cmd_data_len = CYAPA_TSG_BL_KEY_SIZE + CYAPA_TSG_FLASH_MAP_BLOCK_SIZE;
+ cmd_len = sizeof(struct gen5_bl_cmd_head) + cmd_data_len +
+ sizeof(struct gen5_bl_packet_end);
+
+ put_unaligned_le16(GEN5_OUTPUT_REPORT_ADDR, &bl_cmd_head->addr);
+ put_unaligned_le16(cmd_len - 2, &bl_cmd_head->length);
+ bl_cmd_head->report_id = GEN5_BL_CMD_REPORT_ID;
+
+ bl_packet_start = &bl_cmd_head->packet_start;
+ bl_packet_start->sop = GEN5_SOP_KEY;
+ bl_packet_start->cmd_code = GEN5_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 gen5_bl_initiate_cmd_data *)bl_cmd_head->data;
+ memcpy(cmd_data->key, cyapa_gen5_bl_cmd_key, CYAPA_TSG_BL_KEY_SIZE);
+
+ /* Copy 60 bytes Meta Data Row Parameters */
+ image = (struct cyapa_tsg_bin_image *)fw->data;
+ records_num = (fw->size - sizeof(struct cyapa_tsg_bin_image_head)) /
+ sizeof(struct cyapa_tsg_bin_image_data_record);
+ /* 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 gen5_bl_packet_end *)(bl_cmd_head->data +
+ cmd_data_len);
+ cmd_crc = crc_itu_t(0xffff, (u8 *)bl_packet_start,
+ sizeof(struct gen5_bl_packet_start) + cmd_data_len);
+ put_unaligned_le16(cmd_crc, &bl_packet_end->crc);
+ bl_packet_end->eop = GEN5_EOP_KEY;
+
+ resp_len = sizeof(resp_data);
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, cmd_len,
+ resp_data, &resp_len, 12000,
+ cyapa_gen5_sort_tsg_pip_bl_resp_data, true);
+ if (error || resp_len != GEN5_BL_INITIATE_RESP_LEN ||
+ resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return error ? error : -EAGAIN;
+
+ return 0;
+}
+
+static bool cyapa_gen5_sort_bl_exit_data(struct cyapa *cyapa, u8 *buf, int len)
+{
+ if (buf == NULL || len < GEN5_RESP_LENGTH_SIZE)
+ return false;
+
+ if (buf[0] == 0 && buf[1] == 0)
+ return true;
+
+ /* Exit bootloader failed for some reason. */
+ if (len == GEN5_BL_FAIL_EXIT_RESP_LEN &&
+ buf[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_BL_RESP_REPORT_ID &&
+ buf[GEN5_RESP_RSVD_OFFSET] == GEN5_RESP_RSVD_KEY &&
+ buf[GEN5_RESP_BL_SOP_OFFSET] == GEN5_SOP_KEY &&
+ buf[10] == GEN5_EOP_KEY)
+ return true;
+
+ return false;
+}
+
+static int cyapa_gen5_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_gen5_sort_bl_exit_data, false);
+ if (error)
+ return error;
+
+ if (resp_len == GEN5_BL_FAIL_EXIT_RESP_LEN ||
+ resp_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_BL_RESP_REPORT_ID)
+ return -EAGAIN;
+
+ if (resp_data[0] == 0x00 && resp_data[1] == 0x00)
+ return 0;
+
+ return -ENODEV;
+}
+
+static int cyapa_gen5_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;
+ if (cyapa->gen != CYAPA_GEN5)
+ return -EINVAL;
+
+ /* Already in Gen5 BL. Skipping exit. */
+ if (cyapa->state == CYAPA_STATE_GEN5_BL)
+ return 0;
+
+ if (cyapa->state != CYAPA_STATE_GEN5_APP)
+ return -EAGAIN;
+
+ /* 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_gen5_sort_application_launch_data,
+ true);
+ if (error || resp_data[0] != 0x00 || resp_data[1] != 0x00)
+ return error < 0 ? error : -EAGAIN;
+
+ cyapa->operational = false;
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ return 0;
+}
+
+static int cyapa_gen5_check_fw(struct cyapa *cyapa, const struct firmware *fw)
+{
+ struct device *dev = &cyapa->client->dev;
+ const struct cyapa_tsg_bin_image *image = (const void *)fw->data;
+ const struct cyapa_tsg_bin_image_data_record *app_integrity;
+ const struct gen5_bl_metadata_row_params *metadata;
+ size_t 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 record_index;
+ int i;
+
+ flash_records_count = (fw->size -
+ sizeof(struct cyapa_tsg_bin_image_head)) /
+ sizeof(struct cyapa_tsg_bin_image_data_record);
+
+ /*
+ * 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
+ */
+ record_index = fw_app_start / CYAPA_TSG_FW_ROW_SIZE -
+ CYAPA_TSG_IMG_START_ROW_NUM;
+ app_crc = 0xffffU;
+ for (i = 0; i < fw_app_len / CYAPA_TSG_FW_ROW_SIZE; i++) {
+ const u8 *data = image->records[record_index + 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_gen5_write_fw_block(struct cyapa *cyapa,
+ struct cyapa_tsg_bin_image_data_record *flash_record)
+{
+ struct gen5_bl_cmd_head *bl_cmd_head;
+ struct gen5_bl_packet_start *bl_packet_start;
+ struct gen5_bl_flash_row_head *flash_row_head;
+ struct gen5_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 gen5_bl_cmd_head *)cmd;
+ bl_packet_start = &bl_cmd_head->packet_start;
+ cmd_len = sizeof(struct gen5_bl_cmd_head) +
+ sizeof(struct gen5_bl_flash_row_head) +
+ CYAPA_TSG_FLASH_MAP_BLOCK_SIZE +
+ sizeof(struct gen5_bl_packet_end);
+
+ put_unaligned_le16(GEN5_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 = GEN5_BL_CMD_REPORT_ID;
+ bl_packet_start->sop = GEN5_SOP_KEY;
+ bl_packet_start->cmd_code = GEN5_BL_CMD_PROGRAM_VERIFY_ROW;
+
+ /* 1 (Flash Array ID) + 2 (Flash Row ID) + 128 (flash data) */
+ data_len = sizeof(struct gen5_bl_flash_row_head) + record_len;
+ put_unaligned_le16(data_len, &bl_packet_start->data_length);
+
+ flash_row_head = (struct gen5_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 gen5_bl_packet_end *)(bl_cmd_head->data +
+ data_len);
+ crc = crc_itu_t(0xffff, (u8 *)bl_packet_start,
+ sizeof(struct gen5_bl_packet_start) + data_len);
+ put_unaligned_le16(crc, &bl_packet_end->crc);
+ bl_packet_end->eop = GEN5_EOP_KEY;
+
+ resp_len = sizeof(resp_data);
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, cmd_len,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_bl_resp_data, true);
+ if (error || resp_len != GEN5_BL_BLOCK_WRITE_RESP_LEN ||
+ resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return error < 0 ? error : -EAGAIN;
+
+ return 0;
+}
+
+static int cyapa_gen5_do_fw_update(struct cyapa *cyapa,
+ const struct firmware *fw)
+{
+ struct device *dev = &cyapa->client->dev;
+ struct cyapa_tsg_bin_image_data_record *flash_record;
+ struct cyapa_tsg_bin_image *image =
+ (struct cyapa_tsg_bin_image *)fw->data;
+ int flash_records_count;
+ int i;
+ int error;
+
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ flash_records_count =
+ (fw->size - sizeof(struct cyapa_tsg_bin_image_head)) /
+ sizeof(struct cyapa_tsg_bin_image_data_record);
+ /*
+ * 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++) {
+ flash_record = &image->records[i];
+ error = cyapa_gen5_write_fw_block(cyapa, flash_record);
+ 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_gen5_sort_tsg_pip_app_resp_data, false);
+ if (error || !VALID_CMD_RESP_HEADER(resp_data, 0x08) ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return error < 0 ? error : -EINVAL;
+
+ return 0;
+}
+
+static int cyapa_gen5_set_interval_time(struct cyapa *cyapa,
+ u8 parameter_id, u16 interval_time)
+{
+ struct gen5_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 gen5_app_cmd_head *)cmd;
+ parameter_data = (struct gen5_app_set_parameter_data *)
+ app_cmd_head->parameter_data;
+ cmd_len = sizeof(struct gen5_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(GEN5_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 = GEN5_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, &parameter_data->value);
+ resp_len = sizeof(resp_data);
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, cmd_len,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_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 gen5_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 gen5_app_cmd_head *)cmd;
+ parameter_data = (struct gen5_app_get_parameter_data *)
+ app_cmd_head->parameter_data;
+ cmd_len = sizeof(struct gen5_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(GEN5_HID_DESCRIPTOR_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 = GEN5_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_gen5_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 gen5_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 gen5_app_cmd_head *)cmd;
+
+ put_unaligned_le16(GEN5_HID_DESCRIPTOR_ADDR, &app_cmd_head->addr);
+ put_unaligned_le16(sizeof(cmd) - 2, &app_cmd_head->length);
+ app_cmd_head->report_id = GEN5_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_gen5_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;
+}
+
+static int cyapa_gen5_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 & GEN5_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_gen5_sort_deep_sleep_data, false);
+ if (error || ((resp_data[3] & GEN5_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)
+{
+ struct device *dev = &cyapa->client->dev;
+ u8 power_state;
+ int error;
+
+ if (cyapa->state != CYAPA_STATE_GEN5_APP)
+ return 0;
+
+ /* Dump all the report data before do power mode commmands. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ if (GEN5_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.
+ */
+ GEN5_DEV_SET_PWR_STATE(cyapa, PWR_MODE_OFF);
+ }
+
+ if (GEN5_DEV_UNINIT_SLEEP_TIME(cyapa) &&
+ GEN5_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)
+ GEN5_DEV_SET_SLEEP_TIME(cyapa, UNINIT_SLEEP_TIME);
+
+ if (GEN5_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 ||
+ GEN5_DEV_GET_SLEEP_TIME(cyapa) == sleep_time) {
+ /* Has in correct power mode state, early return. */
+ return 0;
+ }
+ }
+
+ if (power_mode == PWR_MODE_OFF) {
+ error = cyapa_gen5_deep_sleep(cyapa, GEN5_DEEP_SLEEP_STATE_OFF);
+ if (error) {
+ dev_err(dev, "enter deep sleep fail: %d\n", error);
+ return error;
+ }
+
+ GEN5_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 (GEN5_DEV_GET_PWR_STATE(cyapa) == PWR_MODE_OFF) {
+ error = cyapa_gen5_deep_sleep(cyapa, GEN5_DEEP_SLEEP_STATE_ON);
+ if (error) {
+ dev_err(dev, "deep sleep wake fail: %d\n", error);
+ return error;
+ }
+ }
+
+ 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);
+ return error;
+ }
+
+ GEN5_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);
+ return error;
+ }
+
+ GEN5_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 (GEN5_DEV_UNINIT_SLEEP_TIME(cyapa) ||
+ sleep_time != GEN5_DEV_GET_SLEEP_TIME(cyapa))
+ if (cyapa_gen5_set_interval_time(cyapa,
+ GEN5_PARAMETER_LP_INTRVL_ID,
+ sleep_time) == 0)
+ GEN5_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);
+ return error;
+ }
+
+ /*
+ * 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 buffured when system
+ * is suspending which may cause interrput line unable to be
+ * asserted again.
+ */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+ cyapa_gen5_disable_pip_report(cyapa);
+
+ GEN5_DEV_SET_PWR_STATE(cyapa,
+ cyapa_sleep_time_to_pwr_cmd(sleep_time));
+ }
+
+ return 0;
+}
+
+static int cyapa_gen5_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_gen5_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;
+}
+
+static int cyapa_gen5_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_gen5_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_gen5_calibrate_pwcs(struct cyapa *cyapa,
+ u8 calibrate_sensing_mode_type)
+{
+ struct gen5_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 gen5_app_cmd_head *)cmd;
+ put_unaligned_le16(GEN5_OUTPUT_REPORT_ADDR, &app_cmd_head->addr);
+ put_unaligned_le16(sizeof(cmd) - 2, &app_cmd_head->length);
+ app_cmd_head->report_id = GEN5_APP_CMD_REPORT_ID;
+ app_cmd_head->cmd_code = GEN5_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_gen5_sort_tsg_pip_app_resp_data, true);
+ if (error || !VALID_CMD_RESP_HEADER(resp_data, GEN5_CMD_CALIBRATE) ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return error < 0 ? error : -EAGAIN;
+
+ return 0;
+}
+
+static ssize_t cyapa_gen5_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_gen5_suspend_scanning(cyapa);
+ if (error)
+ return error;
+
+ /* 2. Do mutual capacitance fine calibrate. */
+ calibrate_error = cyapa_gen5_calibrate_pwcs(cyapa,
+ CYAPA_SENSING_MODE_MUTUAL_CAP_FINE);
+ if (calibrate_error)
+ goto resume_scanning;
+
+ /* 3. Do self capacitance calibrate. */
+ calibrate_error = cyapa_gen5_calibrate_pwcs(cyapa,
+ CYAPA_SENSING_MODE_SELF_CAP);
+ if (calibrate_error)
+ goto resume_scanning;
+
+resume_scanning:
+ /* 4. Resume Scanning*/
+ error = cyapa_gen5_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 raed 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 gen5_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 != GEN5_CMD_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 if (idac_data_type == GEN5_RETRIEVE_SELF_CAP_PWC_DATA) {
+ 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 gen5_app_cmd_head *)cmd;
+ put_unaligned_le16(GEN5_OUTPUT_REPORT_ADDR, &cmd_head->addr);
+ put_unaligned_le16(sizeof(cmd) - 2, &cmd_head->length);
+ cmd_head->report_id = GEN5_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_gen5_sort_tsg_pip_app_resp_data,
+ true);
+ if (error || resp_len < GEN5_RESP_DATA_STRUCTURE_OFFSET ||
+ !VALID_CMD_RESP_HEADER(resp_data, cmd_code) ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]) ||
+ 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 betwen 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,
+ GEN5_CMD_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,
+ GEN5_CMD_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,
+ GEN5_CMD_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,
+ GEN5_CMD_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 gen5_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 gen5_app_cmd_head *)cmd;
+ put_unaligned_le16(GEN5_OUTPUT_REPORT_ADDR, &app_cmd_head->addr);
+ put_unaligned_le16(sizeof(cmd) - 2, &app_cmd_head->length);
+ app_cmd_head->report_id = GEN5_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_gen5_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) ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ 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 gen5_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 gen5_app_cmd_head *)cmd;
+ put_unaligned_le16(GEN5_OUTPUT_REPORT_ADDR, &app_cmd_head->addr);
+ put_unaligned_le16(sizeof(cmd) - 2, &app_cmd_head->length);
+ app_cmd_head->report_id = GEN5_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_gen5_sort_tsg_pip_app_resp_data, true);
+ if (error || resp_len < GEN5_RESP_DATA_STRUCTURE_OFFSET ||
+ !VALID_CMD_RESP_HEADER(resp_data, cmd_code) ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]) ||
+ 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->state != CYAPA_STATE_GEN5_APP)
+ return -EBUSY;
+
+ /* 1. Suspend Scanning*/
+ error = cyapa_gen5_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. Execuate 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_gen5_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;
+}
+
+static bool cyapa_gen5_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 bl_query_data_cmd[] = { 0x04, 0x00, 0x0b, 0x00, 0x40, 0x00,
+ 0x01, 0x3c, 0x00, 0x00, 0xb0, 0x42, 0x17
+ };
+ u8 resp_data[GEN5_BL_READ_APP_INFO_RESP_LEN];
+ int resp_len;
+ int error;
+
+ resp_len = GEN5_BL_READ_APP_INFO_RESP_LEN;
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ bl_query_data_cmd, sizeof(bl_query_data_cmd),
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_bl_resp_data, false);
+ if (error || resp_len != GEN5_BL_READ_APP_INFO_RESP_LEN ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ 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];
+
+ return 0;
+}
+
+static int cyapa_gen5_get_query_data(struct cyapa *cyapa)
+{
+ u8 get_system_information[] = {
+ 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x02
+ };
+ u8 resp_data[71];
+ int resp_len;
+ u16 product_family;
+ int error;
+
+ resp_len = sizeof(resp_data);
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ get_system_information, sizeof(get_system_information),
+ resp_data, &resp_len,
+ 2000, cyapa_gen5_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 & GEN5_PRODUCT_FAMILY_MASK) !=
+ GEN5_PRODUCT_FAMILY_TRACKPAD)
+ return -EINVAL;
+
+ cyapa->fw_maj_ver = resp_data[15];
+ cyapa->fw_min_ver = resp_data[16];
+
+ 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_gen5_bl_exit(cyapa);
+ if (error) {
+ /* Rry to update trackpad product information. */
+ cyapa_gen5_bl_query_data(cyapa);
+ goto out;
+ }
+
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+
+ 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 requeried.
+ */
+ error = cyapa_gen5_set_power_mode(cyapa,
+ PWR_MODE_FULL_ACTIVE, 0);
+ if (error)
+ dev_warn(dev, "%s: failed to set power active mode.\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.
+ */
+static bool cyapa_gen5_irq_cmd_handler(struct cyapa *cyapa)
+{
+ struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+ int length;
+
+ if (atomic_read(&gen5_pip->cmd_issued)) {
+ /* Polling command response data. */
+ if (gen5_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, gen5_pip->irq_cmd_buf,
+ GEN5_RESP_LENGTH_SIZE);
+ length = get_unaligned_le16(gen5_pip->irq_cmd_buf);
+ length = (length <= GEN5_RESP_LENGTH_SIZE) ?
+ GEN5_RESP_LENGTH_SIZE : length;
+ if (length > GEN5_RESP_LENGTH_SIZE)
+ cyapa_i2c_pip_read(cyapa,
+ gen5_pip->irq_cmd_buf, length);
+
+ if (!(gen5_pip->resp_sort_func &&
+ gen5_pip->resp_sort_func(cyapa,
+ gen5_pip->irq_cmd_buf, length))) {
+ /*
+ * Cover the Gen5 V1 firmware issue.
+ * The issue is there is no interrut will be
+ * asserted to notityf host to read a command
+ * data out when always has finger touch on
+ * trackpad during the command is issued to
+ * trackad device.
+ * This issue has the scenario is that,
+ * user always has his fingers touched on
+ * trackpad device when booting/rebooting
+ * their chrome book.
+ */
+ length = 0;
+ if (gen5_pip->resp_len)
+ length = *gen5_pip->resp_len;
+ cyapa_empty_pip_output_data(cyapa,
+ gen5_pip->resp_data,
+ &length,
+ gen5_pip->resp_sort_func);
+ if (gen5_pip->resp_len && length != 0) {
+ *gen5_pip->resp_len = length;
+ atomic_dec(&gen5_pip->cmd_issued);
+ complete(&gen5_pip->cmd_ready);
+ }
+ return false;
+ }
+
+ if (gen5_pip->resp_data && gen5_pip->resp_len) {
+ *gen5_pip->resp_len = (*gen5_pip->resp_len < length) ?
+ *gen5_pip->resp_len : length;
+ memcpy(gen5_pip->resp_data, gen5_pip->irq_cmd_buf,
+ *gen5_pip->resp_len);
+ }
+ atomic_dec(&gen5_pip->cmd_issued);
+ complete(&gen5_pip->cmd_ready);
+ return false;
+ }
+
+ return true;
+}
+
+static void cyapa_gen5_report_buttons(struct cyapa *cyapa,
+ const struct cyapa_gen5_report_data *report_data)
+{
+ struct input_dev *input = cyapa->input;
+ u8 buttons = report_data->report_head[GEN5_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_gen5_report_slot_data(struct cyapa *cyapa,
+ const struct cyapa_gen5_touch_record *touch)
+{
+ struct input_dev *input = cyapa->input;
+ u8 event_id = GEN5_GET_EVENT_ID(touch->touch_tip_event_id);
+ int slot = GEN5_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;
+ input_report_abs(input, ABS_MT_POSITION_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_Y, y);
+ 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_gen5_report_touches(struct cyapa *cyapa,
+ const struct cyapa_gen5_report_data *report_data)
+{
+ struct input_dev *input = cyapa->input;
+ unsigned int touch_num;
+ int i;
+
+ touch_num = report_data->report_head[GEN5_NUMBER_OF_TOUCH_OFFSET] &
+ GEN5_NUMBER_OF_TOUCH_MASK;
+
+ for (i = 0; i < touch_num; i++)
+ cyapa_gen5_report_slot_data(cyapa,
+ &report_data->touch_records[i]);
+
+ input_mt_sync_frame(input);
+ input_sync(input);
+}
+
+static int cyapa_gen5_irq_handler(struct cyapa *cyapa)
+{
+ struct device *dev = &cyapa->client->dev;
+ struct cyapa_gen5_report_data report_data;
+ int ret;
+ u8 report_id;
+ unsigned int report_len;
+
+ if (cyapa->gen != CYAPA_GEN5 ||
+ cyapa->state != CYAPA_STATE_GEN5_APP) {
+ 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,
+ GEN5_RESP_LENGTH_SIZE);
+ if (ret != GEN5_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[GEN5_RESP_LENGTH_OFFSET]);
+ if (report_len < GEN5_RESP_LENGTH_SIZE) {
+ /* Invliad 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 == GEN5_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;
+ }
+
+ report_id = report_data.report_head[GEN5_RESP_REPORT_ID_OFFSET];
+ if (report_id == GEN5_WAKEUP_EVENT_REPORT_ID &&
+ report_len == GEN5_WAKEUP_EVENT_SIZE) {
+ /*
+ * Device wake event from deep sleep mode for touch.
+ * This interrupt event is used to wake system up.
+ */
+ return 0;
+ } else if (report_id != GEN5_TOUCH_REPORT_ID &&
+ report_id != GEN5_BTN_REPORT_ID &&
+ report_id != GEN5_OLD_PUSH_BTN_REPORT_ID &&
+ report_id != GEN5_PUSH_BTN_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 == GEN5_TOUCH_REPORT_ID &&
+ (report_len < GEN5_TOUCH_REPORT_HEAD_SIZE ||
+ report_len > GEN5_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 == GEN5_BTN_REPORT_ID ||
+ report_id == GEN5_OLD_PUSH_BTN_REPORT_ID ||
+ report_id == GEN5_PUSH_BTN_REPORT_ID) &&
+ (report_len < GEN5_BTN_REPORT_HEAD_SIZE ||
+ report_len > GEN5_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 == GEN5_TOUCH_REPORT_ID)
+ cyapa_gen5_report_touches(cyapa, &report_data);
+ else
+ cyapa_gen5_report_buttons(cyapa, &report_data);
+
+ return 0;
+}
+
+static int cyapa_gen5_bl_activate(struct cyapa *cyapa) { return 0; }
+static int cyapa_gen5_bl_deactivate(struct cyapa *cyapa) { return 0; }
+
+const struct cyapa_dev_ops cyapa_gen5_ops = {
+ .check_fw = cyapa_gen5_check_fw,
+ .bl_enter = cyapa_gen5_bl_enter,
+ .bl_initiate = cyapa_gen5_bl_initiate,
+ .update_fw = cyapa_gen5_do_fw_update,
+ .bl_activate = cyapa_gen5_bl_activate,
+ .bl_deactivate = cyapa_gen5_bl_deactivate,
+
+ .show_baseline = cyapa_gen5_show_baseline,
+ .calibrate_store = cyapa_gen5_do_calibrate,
+
+ .initialize = cyapa_gen5_initialize,
+
+ .state_parse = cyapa_gen5_state_parse,
+ .operational_check = cyapa_gen5_do_operational_check,
+
+ .irq_handler = cyapa_gen5_irq_handler,
+ .irq_cmd_handler = cyapa_gen5_irq_cmd_handler,
+ .sort_empty_output_data = cyapa_empty_pip_output_data,
+ .set_power_mode = cyapa_gen5_set_power_mode,
+};
diff --git a/drivers/input/mouse/cypress_ps2.c b/drivers/input/mouse/cypress_ps2.c
index 8af34ffe208b..9118a1861a45 100644
--- a/drivers/input/mouse/cypress_ps2.c
+++ b/drivers/input/mouse/cypress_ps2.c
@@ -538,7 +538,7 @@ static void cypress_process_packet(struct psmouse *psmouse, bool zero_pkt)
pos[i].y = contact->y;
}
- input_mt_assign_slots(input, slots, pos, n);
+ input_mt_assign_slots(input, slots, pos, n, 0);
for (i = 0; i < n; i++) {
contact = &report_data.contacts[i];
diff --git a/drivers/input/mouse/elan_i2c.h b/drivers/input/mouse/elan_i2c.h
index 2e838626205f..e100c1b31597 100644
--- a/drivers/input/mouse/elan_i2c.h
+++ b/drivers/input/mouse/elan_i2c.h
@@ -4,7 +4,6 @@
* Copyright (c) 2013 ELAN Microelectronics Corp.
*
* Author: 林政維 (Duson Lin) <dusonlin@emc.com.tw>
- * Version: 1.5.5
*
* Based on cyapa driver:
* copyright (c) 2011-2012 Cypress Semiconductor, Inc.
@@ -33,8 +32,9 @@
#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_PAGE_COUNT 768
-#define ETP_FW_SIZE (ETP_FW_PAGE_SIZE * ETP_FW_PAGE_COUNT)
+#define ETP_FW_VAILDPAGE_COUNT 768
+#define ETP_FW_SIGNATURE_SIZE 6
+#define ETP_FW_SIGNATURE_ADDRESS 0xBFFA
struct i2c_client;
struct completion;
diff --git a/drivers/input/mouse/elan_i2c_core.c b/drivers/input/mouse/elan_i2c_core.c
index 0cb2be48d537..7ce8bfe22d7e 100644
--- a/drivers/input/mouse/elan_i2c_core.c
+++ b/drivers/input/mouse/elan_i2c_core.c
@@ -4,7 +4,7 @@
* Copyright (c) 2013 ELAN Microelectronics Corp.
*
* Author: 林政維 (Duson Lin) <dusonlin@emc.com.tw>
- * Version: 1.5.5
+ * Version: 1.5.6
*
* Based on cyapa driver:
* copyright (c) 2011-2012 Cypress Semiconductor, Inc.
@@ -40,7 +40,7 @@
#include "elan_i2c.h"
#define DRIVER_NAME "elan_i2c"
-#define ELAN_DRIVER_VERSION "1.5.5"
+#define ELAN_DRIVER_VERSION "1.5.6"
#define ETP_PRESSURE_OFFSET 25
#define ETP_MAX_PRESSURE 255
#define ETP_FWIDTH_REDUCE 90
@@ -312,7 +312,7 @@ static int __elan_update_firmware(struct elan_tp_data *data,
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 < ETP_FW_PAGE_COUNT; i++) {
+ for (i = boot_page_count; i < ETP_FW_VAILDPAGE_COUNT; i++) {
u16 checksum = 0;
const u8 *page = &fw->data[i * ETP_FW_PAGE_SIZE];
@@ -434,10 +434,11 @@ static ssize_t elan_sysfs_update_fw(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);
+ struct elan_tp_data *data = dev_get_drvdata(dev);
const struct firmware *fw;
int error;
+ const u8 *fw_signature;
+ static const u8 signature[] = {0xAA, 0x55, 0xCC, 0x33, 0xFF, 0xFF};
error = request_firmware(&fw, ETP_FW_NAME, dev);
if (error) {
@@ -446,10 +447,12 @@ static ssize_t elan_sysfs_update_fw(struct device *dev,
return error;
}
- /* Firmware must be exactly PAGE_NUM * PAGE_SIZE bytes */
- if (fw->size != ETP_FW_SIZE) {
- dev_err(dev, "invalid firmware size = %zu, expected %d.\n",
- fw->size, ETP_FW_SIZE);
+ /* Firmware file must match signature data */
+ fw_signature = &fw->data[ETP_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;
}
diff --git a/drivers/input/mouse/elan_i2c_i2c.c b/drivers/input/mouse/elan_i2c_i2c.c
index 97d4937fc244..029941f861af 100644
--- a/drivers/input/mouse/elan_i2c_i2c.c
+++ b/drivers/input/mouse/elan_i2c_i2c.c
@@ -4,7 +4,6 @@
* Copyright (c) 2013 ELAN Microelectronics Corp.
*
* Author: 林政維 (Duson Lin) <dusonlin@emc.com.tw>
- * Version: 1.5.5
*
* Based on cyapa driver:
* copyright (c) 2011-2012 Cypress Semiconductor, Inc.
diff --git a/drivers/input/mouse/elan_i2c_smbus.c b/drivers/input/mouse/elan_i2c_smbus.c
index 359bf8583d54..06a2bcd1cda2 100644
--- a/drivers/input/mouse/elan_i2c_smbus.c
+++ b/drivers/input/mouse/elan_i2c_smbus.c
@@ -4,7 +4,6 @@
* Copyright (c) 2013 ELAN Microelectronics Corp.
*
* Author: 林政維 (Duson Lin) <dusonlin@emc.com.tw>
- * Version: 1.5.5
*
* Based on cyapa driver:
* copyright (c) 2011-2012 Cypress Semiconductor, Inc.
@@ -71,7 +70,7 @@ static int elan_smbus_initialize(struct i2c_client *client)
/* compare hello packet */
if (memcmp(values, check, ETP_SMBUS_HELLOPACKET_LEN)) {
- dev_err(&client->dev, "hello packet fail [%*px]\n",
+ dev_err(&client->dev, "hello packet fail [%*ph]\n",
ETP_SMBUS_HELLOPACKET_LEN, values);
return -ENXIO;
}
diff --git a/drivers/input/mouse/focaltech.c b/drivers/input/mouse/focaltech.c
index f4d657ee1cc0..fca38ba63bbe 100644
--- a/drivers/input/mouse/focaltech.c
+++ b/drivers/input/mouse/focaltech.c
@@ -2,6 +2,7 @@
* 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
@@ -13,15 +14,14 @@
* Hans de Goede <hdegoede@redhat.com>
*/
-/*
- * The Focaltech PS/2 touchpad protocol is unknown. This drivers deals with
- * detection only, to avoid further detection attempts confusing the touchpad
- * this way it at least works in PS/2 mouse compatibility mode.
- */
#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",
@@ -30,6 +30,12 @@ static const char * const focaltech_pnp_ids[] = {
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))
@@ -37,16 +43,404 @@ int focaltech_detect(struct psmouse *psmouse, bool set_properties)
if (set_properties) {
psmouse->vendor = "FocalTech";
- psmouse->name = "FocalTech Touchpad in mouse emulation mode";
+ psmouse->name = "FocalTech Touchpad";
}
return 0;
}
-int focaltech_init(struct psmouse *psmouse)
+static void focaltech_reset(struct psmouse *psmouse)
{
ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS);
psmouse_reset(psmouse);
+}
+
+#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
+
+#define FOC_MAX_X 2431
+#define FOC_MAX_Y 1663
+
+/*
+ * 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];
+
+ /* 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) {
+ input_report_abs(dev, ABS_MT_POSITION_X, finger->x);
+ input_report_abs(dev, ABS_MT_POSITION_Y,
+ FOC_MAX_Y - finger->y);
+ }
+ }
+ input_mt_report_pointer_emulation(dev, true);
+
+ input_report_key(psmouse->dev, BTN_LEFT, state->pressed);
+ input_sync(psmouse->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;
+
+ /*
+ * packet[5] contains some kind of tool size in the most
+ * significant nibble. 0xff is a special value (latching) that
+ * signals a large contact area.
+ */
+ if (packet[5] == 0xff) {
+ state->fingers[finger].valid = false;
+ return;
+ }
+
+ state->fingers[finger].x = ((packet[1] & 0xf) << 8) | packet[2];
+ state->fingers[finger].y = (packet[3] << 8) | packet[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_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_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;
+}
+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;
return 0;
+
+fail:
+ focaltech_reset(psmouse);
+ kfree(priv);
+ return error;
}
+
+bool focaltech_supported(void)
+{
+ return true;
+}
+
+#else /* CONFIG_MOUSE_PS2_FOCALTECH */
+
+int focaltech_init(struct psmouse *psmouse)
+{
+ focaltech_reset(psmouse);
+
+ return 0;
+}
+
+bool focaltech_supported(void)
+{
+ return false;
+}
+
+#endif /* CONFIG_MOUSE_PS2_FOCALTECH */
diff --git a/drivers/input/mouse/focaltech.h b/drivers/input/mouse/focaltech.h
index 498650c61e28..71870a9b548a 100644
--- a/drivers/input/mouse/focaltech.h
+++ b/drivers/input/mouse/focaltech.h
@@ -2,6 +2,7 @@
* 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
@@ -18,5 +19,6 @@
int focaltech_detect(struct psmouse *psmouse, bool set_properties);
int focaltech_init(struct psmouse *psmouse);
+bool focaltech_supported(void);
#endif
diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c
index 95a3a6e2faf6..68469feda470 100644
--- a/drivers/input/mouse/psmouse-base.c
+++ b/drivers/input/mouse/psmouse-base.c
@@ -725,16 +725,19 @@ static int psmouse_extensions(struct psmouse *psmouse,
/* Always check for focaltech, this is safe as it uses pnp-id matching */
if (psmouse_do_detect(focaltech_detect, psmouse, set_properties) == 0) {
- if (!set_properties || focaltech_init(psmouse) == 0) {
- /*
- * Not supported yet, use bare protocol.
- * Note that we need to also restrict
- * psmouse_max_proto so that psmouse_initialize()
- * does not try to reset rate and resolution,
- * because even that upsets the device.
- */
- psmouse_max_proto = PSMOUSE_PS2;
- return PSMOUSE_PS2;
+ if (max_proto > PSMOUSE_IMEX) {
+ if (!set_properties || focaltech_init(psmouse) == 0) {
+ if (focaltech_supported())
+ return PSMOUSE_FOCALTECH;
+ /*
+ * Note that we need to also restrict
+ * psmouse_max_proto so that psmouse_initialize()
+ * does not try to reset rate and resolution,
+ * because even that upsets the device.
+ */
+ psmouse_max_proto = PSMOUSE_PS2;
+ return PSMOUSE_PS2;
+ }
}
}
@@ -1063,6 +1066,15 @@ static const struct psmouse_protocol psmouse_protocols[] = {
.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
{
.type = PSMOUSE_AUTO,
.name = "auto",
diff --git a/drivers/input/mouse/psmouse.h b/drivers/input/mouse/psmouse.h
index f4cf664c7db3..c2ff137ecbdb 100644
--- a/drivers/input/mouse/psmouse.h
+++ b/drivers/input/mouse/psmouse.h
@@ -96,6 +96,7 @@ enum psmouse_type {
PSMOUSE_FSP,
PSMOUSE_SYNAPTICS_RELATIVE,
PSMOUSE_CYPRESS,
+ PSMOUSE_FOCALTECH,
PSMOUSE_AUTO /* This one should always be last */
};
diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c
index 23e26e0768b5..7e705ee90b86 100644
--- a/drivers/input/mouse/synaptics.c
+++ b/drivers/input/mouse/synaptics.c
@@ -67,6 +67,9 @@
#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
****************************************************************************/
@@ -575,14 +578,6 @@ static void synaptics_pt_create(struct psmouse *psmouse)
* Functions to interpret the absolute mode packets
****************************************************************************/
-static void synaptics_mt_state_set(struct synaptics_mt_state *state, int count,
- int sgm, int agm)
-{
- state->count = count;
- state->sgm = sgm;
- state->agm = agm;
-}
-
static void synaptics_parse_agm(const unsigned char buf[],
struct synaptics_data *priv,
struct synaptics_hw_state *hw)
@@ -601,16 +596,13 @@ static void synaptics_parse_agm(const unsigned char buf[],
break;
case 2:
- /* AGM-CONTACT packet: (count, sgm, agm) */
- synaptics_mt_state_set(&agm->mt_state, buf[1], buf[2], buf[4]);
+ /* AGM-CONTACT packet: we are only interested in the count */
+ priv->agm_count = buf[1];
break;
default:
break;
}
-
- /* Record that at least one AGM has been received since last SGM */
- priv->agm_pending = true;
}
static bool is_forcepad;
@@ -804,424 +796,68 @@ static void synaptics_report_buttons(struct psmouse *psmouse,
input_report_key(dev, BTN_0 + i, hw->ext_buttons & (1 << i));
}
-static void synaptics_report_slot(struct input_dev *dev, int slot,
- const struct synaptics_hw_state *hw)
-{
- input_mt_slot(dev, slot);
- input_mt_report_slot_state(dev, MT_TOOL_FINGER, (hw != NULL));
- if (!hw)
- return;
-
- input_report_abs(dev, ABS_MT_POSITION_X, hw->x);
- input_report_abs(dev, ABS_MT_POSITION_Y, synaptics_invert_y(hw->y));
- input_report_abs(dev, ABS_MT_PRESSURE, hw->z);
-}
-
static void synaptics_report_mt_data(struct psmouse *psmouse,
- struct synaptics_mt_state *mt_state,
- const struct synaptics_hw_state *sgm)
+ const struct synaptics_hw_state *sgm,
+ int num_fingers)
{
struct input_dev *dev = psmouse->dev;
struct synaptics_data *priv = psmouse->private;
- struct synaptics_hw_state *agm = &priv->agm;
- struct synaptics_mt_state *old = &priv->mt_state;
+ const struct synaptics_hw_state *hw[2] = { sgm, &priv->agm };
+ struct input_mt_pos pos[2];
+ int slot[2], nsemi, i;
- switch (mt_state->count) {
- case 0:
- synaptics_report_slot(dev, 0, NULL);
- synaptics_report_slot(dev, 1, NULL);
- break;
- case 1:
- if (mt_state->sgm == -1) {
- synaptics_report_slot(dev, 0, NULL);
- synaptics_report_slot(dev, 1, NULL);
- } else if (mt_state->sgm == 0) {
- synaptics_report_slot(dev, 0, sgm);
- synaptics_report_slot(dev, 1, NULL);
- } else {
- synaptics_report_slot(dev, 0, NULL);
- synaptics_report_slot(dev, 1, sgm);
- }
- break;
- default:
- /*
- * If the finger slot contained in SGM is valid, and either
- * hasn't changed, or is new, or the old SGM has now moved to
- * AGM, then report SGM in MTB slot 0.
- * Otherwise, empty MTB slot 0.
- */
- if (mt_state->sgm != -1 &&
- (mt_state->sgm == old->sgm ||
- old->sgm == -1 || mt_state->agm == old->sgm))
- synaptics_report_slot(dev, 0, sgm);
- else
- synaptics_report_slot(dev, 0, NULL);
+ nsemi = clamp_val(num_fingers, 0, 2);
- /*
- * If the finger slot contained in AGM is valid, and either
- * hasn't changed, or is new, then report AGM in MTB slot 1.
- * Otherwise, empty MTB slot 1.
- *
- * However, in the case where the AGM is new, make sure that
- * that it is either the same as the old SGM, or there was no
- * SGM.
- *
- * Otherwise, if the SGM was just 1, and the new AGM is 2, then
- * the new AGM will keep the old SGM's tracking ID, which can
- * cause apparent drumroll. This happens if in the following
- * valid finger sequence:
- *
- * Action SGM AGM (MTB slot:Contact)
- * 1. Touch contact 0 (0:0)
- * 2. Touch contact 1 (0:0, 1:1)
- * 3. Lift contact 0 (1:1)
- * 4. Touch contacts 2,3 (0:2, 1:3)
- *
- * In step 4, contact 3, in AGM must not be given the same
- * tracking ID as contact 1 had in step 3. To avoid this,
- * the first agm with contact 3 is dropped and slot 1 is
- * invalidated (tracking ID = -1).
- */
- if (mt_state->agm != -1 &&
- (mt_state->agm == old->agm ||
- (old->agm == -1 &&
- (old->sgm == -1 || mt_state->agm == old->sgm))))
- synaptics_report_slot(dev, 1, agm);
- else
- synaptics_report_slot(dev, 1, NULL);
- break;
+ 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->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, mt_state->count);
+ input_mt_report_finger_count(dev, num_fingers);
synaptics_report_buttons(psmouse, sgm);
input_sync(dev);
}
-/* Handle case where mt_state->count = 0 */
-static void synaptics_image_sensor_0f(struct synaptics_data *priv,
- struct synaptics_mt_state *mt_state)
-{
- synaptics_mt_state_set(mt_state, 0, -1, -1);
- priv->mt_state_lost = false;
-}
-
-/* Handle case where mt_state->count = 1 */
-static void synaptics_image_sensor_1f(struct synaptics_data *priv,
- struct synaptics_mt_state *mt_state)
-{
- struct synaptics_hw_state *agm = &priv->agm;
- struct synaptics_mt_state *old = &priv->mt_state;
-
- /*
- * If the last AGM was (0,0,0), and there is only one finger left,
- * then we absolutely know that SGM contains slot 0, and all other
- * fingers have been removed.
- */
- if (priv->agm_pending && agm->z == 0) {
- synaptics_mt_state_set(mt_state, 1, 0, -1);
- priv->mt_state_lost = false;
- return;
- }
-
- switch (old->count) {
- case 0:
- synaptics_mt_state_set(mt_state, 1, 0, -1);
- break;
- case 1:
- /*
- * If mt_state_lost, then the previous transition was 3->1,
- * and SGM now contains either slot 0 or 1, but we don't know
- * which. So, we just assume that the SGM now contains slot 1.
- *
- * If pending AGM and either:
- * (a) the previous SGM slot contains slot 0, or
- * (b) there was no SGM slot
- * then, the SGM now contains slot 1
- *
- * Case (a) happens with very rapid "drum roll" gestures, where
- * slot 0 finger is lifted and a new slot 1 finger touches
- * within one reporting interval.
- *
- * Case (b) happens if initially two or more fingers tap
- * briefly, and all but one lift before the end of the first
- * reporting interval.
- *
- * (In both these cases, slot 0 will becomes empty, so SGM
- * contains slot 1 with the new finger)
- *
- * Else, if there was no previous SGM, it now contains slot 0.
- *
- * Otherwise, SGM still contains the same slot.
- */
- if (priv->mt_state_lost ||
- (priv->agm_pending && old->sgm <= 0))
- synaptics_mt_state_set(mt_state, 1, 1, -1);
- else if (old->sgm == -1)
- synaptics_mt_state_set(mt_state, 1, 0, -1);
- break;
- case 2:
- /*
- * If mt_state_lost, we don't know which finger SGM contains.
- *
- * So, report 1 finger, but with both slots empty.
- * We will use slot 1 on subsequent 1->1
- */
- if (priv->mt_state_lost) {
- synaptics_mt_state_set(mt_state, 1, -1, -1);
- break;
- }
- /*
- * Since the last AGM was NOT (0,0,0), it was the finger in
- * slot 0 that has been removed.
- * So, SGM now contains previous AGM's slot, and AGM is now
- * empty.
- */
- synaptics_mt_state_set(mt_state, 1, old->agm, -1);
- break;
- case 3:
- /*
- * Since last AGM was not (0,0,0), we don't know which finger
- * is left.
- *
- * So, report 1 finger, but with both slots empty.
- * We will use slot 1 on subsequent 1->1
- */
- synaptics_mt_state_set(mt_state, 1, -1, -1);
- priv->mt_state_lost = true;
- break;
- case 4:
- case 5:
- /* mt_state was updated by AGM-CONTACT packet */
- break;
- }
-}
-
-/* Handle case where mt_state->count = 2 */
-static void synaptics_image_sensor_2f(struct synaptics_data *priv,
- struct synaptics_mt_state *mt_state)
-{
- struct synaptics_mt_state *old = &priv->mt_state;
-
- switch (old->count) {
- case 0:
- synaptics_mt_state_set(mt_state, 2, 0, 1);
- break;
- case 1:
- /*
- * If previous SGM contained slot 1 or higher, SGM now contains
- * slot 0 (the newly touching finger) and AGM contains SGM's
- * previous slot.
- *
- * Otherwise, SGM still contains slot 0 and AGM now contains
- * slot 1.
- */
- if (old->sgm >= 1)
- synaptics_mt_state_set(mt_state, 2, 0, old->sgm);
- else
- synaptics_mt_state_set(mt_state, 2, 0, 1);
- break;
- case 2:
- /*
- * If mt_state_lost, SGM now contains either finger 1 or 2, but
- * we don't know which.
- * So, we just assume that the SGM contains slot 0 and AGM 1.
- */
- if (priv->mt_state_lost)
- synaptics_mt_state_set(mt_state, 2, 0, 1);
- /*
- * Otherwise, use the same mt_state, since it either hasn't
- * changed, or was updated by a recently received AGM-CONTACT
- * packet.
- */
- break;
- case 3:
- /*
- * 3->2 transitions have two unsolvable problems:
- * 1) no indication is given which finger was removed
- * 2) no way to tell if agm packet was for finger 3
- * before 3->2, or finger 2 after 3->2.
- *
- * So, report 2 fingers, but empty all slots.
- * We will guess slots [0,1] on subsequent 2->2.
- */
- synaptics_mt_state_set(mt_state, 2, -1, -1);
- priv->mt_state_lost = true;
- break;
- case 4:
- case 5:
- /* mt_state was updated by AGM-CONTACT packet */
- break;
- }
-}
-
-/* Handle case where mt_state->count = 3 */
-static void synaptics_image_sensor_3f(struct synaptics_data *priv,
- struct synaptics_mt_state *mt_state)
-{
- struct synaptics_mt_state *old = &priv->mt_state;
-
- switch (old->count) {
- case 0:
- synaptics_mt_state_set(mt_state, 3, 0, 2);
- break;
- case 1:
- /*
- * If previous SGM contained slot 2 or higher, SGM now contains
- * slot 0 (one of the newly touching fingers) and AGM contains
- * SGM's previous slot.
- *
- * Otherwise, SGM now contains slot 0 and AGM contains slot 2.
- */
- if (old->sgm >= 2)
- synaptics_mt_state_set(mt_state, 3, 0, old->sgm);
- else
- synaptics_mt_state_set(mt_state, 3, 0, 2);
- break;
- case 2:
- /*
- * If the AGM previously contained slot 3 or higher, then the
- * newly touching finger is in the lowest available slot.
- *
- * If SGM was previously 1 or higher, then the new SGM is
- * now slot 0 (with a new finger), otherwise, the new finger
- * is now in a hidden slot between 0 and AGM's slot.
- *
- * In all such cases, the SGM now contains slot 0, and the AGM
- * continues to contain the same slot as before.
- */
- if (old->agm >= 3) {
- synaptics_mt_state_set(mt_state, 3, 0, old->agm);
- break;
- }
-
- /*
- * After some 3->1 and all 3->2 transitions, we lose track
- * of which slot is reported by SGM and AGM.
- *
- * For 2->3 in this state, report 3 fingers, but empty all
- * slots, and we will guess (0,2) on a subsequent 0->3.
- *
- * To userspace, the resulting transition will look like:
- * 2:[0,1] -> 3:[-1,-1] -> 3:[0,2]
- */
- if (priv->mt_state_lost) {
- synaptics_mt_state_set(mt_state, 3, -1, -1);
- break;
- }
-
- /*
- * If the (SGM,AGM) really previously contained slots (0, 1),
- * then we cannot know what slot was just reported by the AGM,
- * because the 2->3 transition can occur either before or after
- * the AGM packet. Thus, this most recent AGM could contain
- * either the same old slot 1 or the new slot 2.
- * Subsequent AGMs will be reporting slot 2.
- *
- * To userspace, the resulting transition will look like:
- * 2:[0,1] -> 3:[0,-1] -> 3:[0,2]
- */
- synaptics_mt_state_set(mt_state, 3, 0, -1);
- break;
- case 3:
- /*
- * If, for whatever reason, the previous agm was invalid,
- * Assume SGM now contains slot 0, AGM now contains slot 2.
- */
- if (old->agm <= 2)
- synaptics_mt_state_set(mt_state, 3, 0, 2);
- /*
- * mt_state either hasn't changed, or was updated by a recently
- * received AGM-CONTACT packet.
- */
- break;
-
- case 4:
- case 5:
- /* mt_state was updated by AGM-CONTACT packet */
- break;
- }
-}
-
-/* Handle case where mt_state->count = 4, or = 5 */
-static void synaptics_image_sensor_45f(struct synaptics_data *priv,
- struct synaptics_mt_state *mt_state)
-{
- /* mt_state was updated correctly by AGM-CONTACT packet */
- priv->mt_state_lost = false;
-}
-
static void synaptics_image_sensor_process(struct psmouse *psmouse,
struct synaptics_hw_state *sgm)
{
struct synaptics_data *priv = psmouse->private;
- struct synaptics_hw_state *agm = &priv->agm;
- struct synaptics_mt_state mt_state;
-
- /* Initialize using current mt_state (as updated by last agm) */
- mt_state = agm->mt_state;
+ int num_fingers;
/*
* Update mt_state using the new finger count and current mt_state.
*/
if (sgm->z == 0)
- synaptics_image_sensor_0f(priv, &mt_state);
+ num_fingers = 0;
else if (sgm->w >= 4)
- synaptics_image_sensor_1f(priv, &mt_state);
+ num_fingers = 1;
else if (sgm->w == 0)
- synaptics_image_sensor_2f(priv, &mt_state);
- else if (sgm->w == 1 && mt_state.count <= 3)
- synaptics_image_sensor_3f(priv, &mt_state);
+ num_fingers = 2;
+ else if (sgm->w == 1)
+ num_fingers = priv->agm_count ? priv->agm_count : 3;
else
- synaptics_image_sensor_45f(priv, &mt_state);
+ num_fingers = 4;
/* Send resulting input events to user space */
- synaptics_report_mt_data(psmouse, &mt_state, sgm);
-
- /* Store updated mt_state */
- priv->mt_state = agm->mt_state = mt_state;
- priv->agm_pending = false;
-}
-
-static void synaptics_profile_sensor_process(struct psmouse *psmouse,
- struct synaptics_hw_state *sgm,
- int num_fingers)
-{
- struct input_dev *dev = psmouse->dev;
- struct synaptics_data *priv = psmouse->private;
- 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);
-
- 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);
- input_mt_report_pointer_emulation(dev, false);
- input_mt_report_finger_count(dev, num_fingers);
-
- synaptics_report_buttons(psmouse, sgm);
-
- input_sync(dev);
+ synaptics_report_mt_data(psmouse, sgm, num_fingers);
}
/*
@@ -1288,7 +924,7 @@ static void synaptics_process_packet(struct psmouse *psmouse)
}
if (cr48_profile_sensor) {
- synaptics_profile_sensor_process(psmouse, &hw, num_fingers);
+ synaptics_report_mt_data(psmouse, &hw, num_fingers);
return;
}
@@ -1445,7 +1081,7 @@ static void set_input_params(struct psmouse *psmouse,
ABS_MT_POSITION_Y);
/* Image sensors can report per-contact pressure */
input_set_abs_params(dev, ABS_MT_PRESSURE, 0, 255, 0, 0);
- input_mt_init_slots(dev, 2, INPUT_MT_POINTER);
+ input_mt_init_slots(dev, 2, INPUT_MT_POINTER | INPUT_MT_TRACK);
/* Image sensors can signal 4 and 5 finger clicks */
__set_bit(BTN_TOOL_QUADTAP, dev->keybit);
diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h
index 1bd01f21783b..6faf9bb7c117 100644
--- a/drivers/input/mouse/synaptics.h
+++ b/drivers/input/mouse/synaptics.h
@@ -119,16 +119,6 @@
#define SYN_REDUCED_FILTER_FUZZ 8
/*
- * A structure to describe which internal touchpad finger slots are being
- * reported in raw packets.
- */
-struct synaptics_mt_state {
- int count; /* num fingers being tracked */
- int sgm; /* which slot is reported by sgm pkt */
- int agm; /* which slot is reported by agm pkt*/
-};
-
-/*
* A structure to describe the state of the touchpad hardware (buttons and pad)
*/
struct synaptics_hw_state {
@@ -143,9 +133,6 @@ struct synaptics_hw_state {
unsigned int down:1;
unsigned char ext_buttons;
signed char scroll;
-
- /* As reported in last AGM-CONTACT packets */
- struct synaptics_mt_state mt_state;
};
struct synaptics_data {
@@ -170,15 +157,12 @@ struct synaptics_data {
struct serio *pt_port; /* Pass-through serio port */
- struct synaptics_mt_state mt_state; /* Current mt finger state */
- bool mt_state_lost; /* mt_state may be incorrect */
-
/*
* 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;
- bool agm_pending; /* new AGM packet received */
+ unsigned int agm_count; /* finger count reported by agm */
/* ForcePad handling */
unsigned long press_start;
diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
index bc2d47431bdc..77833d7a004b 100644
--- a/drivers/input/serio/Kconfig
+++ b/drivers/input/serio/Kconfig
@@ -281,4 +281,14 @@ config HYPERV_KEYBOARD
To compile this driver as a module, choose M here: the module will
be called hyperv_keyboard.
+config SERIO_SUN4I_PS2
+ tristate "Allwinner A10 PS/2 controller support"
+ depends on ARCH_SUNXI || COMPILE_TEST
+ help
+ This selects support for the PS/2 Host Controller on
+ Allwinner A10.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sun4i-ps2.
+
endif
diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile
index 815d874fe724..c600089b7a34 100644
--- a/drivers/input/serio/Makefile
+++ b/drivers/input/serio/Makefile
@@ -29,3 +29,4 @@ obj-$(CONFIG_SERIO_ARC_PS2) += arc_ps2.o
obj-$(CONFIG_SERIO_APBPS2) += apbps2.o
obj-$(CONFIG_SERIO_OLPC_APSP) += olpc_apsp.o
obj-$(CONFIG_HYPERV_KEYBOARD) += hyperv-keyboard.o
+obj-$(CONFIG_SERIO_SUN4I_PS2) += sun4i-ps2.o
diff --git a/drivers/input/serio/gscps2.c b/drivers/input/serio/gscps2.c
index 8d9ba0c3827c..94ab494a6ade 100644
--- a/drivers/input/serio/gscps2.c
+++ b/drivers/input/serio/gscps2.c
@@ -40,7 +40,6 @@
MODULE_AUTHOR("Laurent Canet <canetl@esiee.fr>, Thibaut Varene <varenet@parisc-linux.org>, Helge Deller <deller@gmx.de>");
MODULE_DESCRIPTION("HP GSC PS2 port driver");
MODULE_LICENSE("GPL");
-MODULE_DEVICE_TABLE(parisc, gscps2_device_tbl);
#define PFX "gscps2.c: "
@@ -439,6 +438,7 @@ static struct parisc_device_id gscps2_device_tbl[] = {
#endif
{ 0, } /* 0 terminated list */
};
+MODULE_DEVICE_TABLE(parisc, gscps2_device_tbl);
static struct parisc_driver parisc_ps2_driver = {
.name = "gsc_ps2",
diff --git a/drivers/input/serio/sun4i-ps2.c b/drivers/input/serio/sun4i-ps2.c
new file mode 100644
index 000000000000..04b96fe39339
--- /dev/null
+++ b/drivers/input/serio/sun4i-ps2.c
@@ -0,0 +1,340 @@
+/*
+ * Driver for Allwinner A10 PS2 host controller
+ *
+ * Author: Vishnu Patekar <vishnupatekar0510@gmail.com>
+ * Aaron.maoye <leafy.myeh@newbietech.com>
+ */
+
+#include <linux/module.h>
+#include <linux/serio.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+
+#define DRIVER_NAME "sun4i-ps2"
+
+/* register offset definitions */
+#define PS2_REG_GCTL 0x00 /* PS2 Module Global Control Reg */
+#define PS2_REG_DATA 0x04 /* PS2 Module Data Reg */
+#define PS2_REG_LCTL 0x08 /* PS2 Module Line Control Reg */
+#define PS2_REG_LSTS 0x0C /* PS2 Module Line Status Reg */
+#define PS2_REG_FCTL 0x10 /* PS2 Module FIFO Control Reg */
+#define PS2_REG_FSTS 0x14 /* PS2 Module FIFO Status Reg */
+#define PS2_REG_CLKDR 0x18 /* PS2 Module Clock Divider Reg*/
+
+/* PS2 GLOBAL CONTROL REGISTER PS2_GCTL */
+#define PS2_GCTL_INTFLAG BIT(4)
+#define PS2_GCTL_INTEN BIT(3)
+#define PS2_GCTL_RESET BIT(2)
+#define PS2_GCTL_MASTER BIT(1)
+#define PS2_GCTL_BUSEN BIT(0)
+
+/* PS2 LINE CONTROL REGISTER */
+#define PS2_LCTL_NOACK BIT(18)
+#define PS2_LCTL_TXDTOEN BIT(8)
+#define PS2_LCTL_STOPERREN BIT(3)
+#define PS2_LCTL_ACKERREN BIT(2)
+#define PS2_LCTL_PARERREN BIT(1)
+#define PS2_LCTL_RXDTOEN BIT(0)
+
+/* PS2 LINE STATUS REGISTER */
+#define PS2_LSTS_TXTDO BIT(8)
+#define PS2_LSTS_STOPERR BIT(3)
+#define PS2_LSTS_ACKERR BIT(2)
+#define PS2_LSTS_PARERR BIT(1)
+#define PS2_LSTS_RXTDO BIT(0)
+
+#define PS2_LINE_ERROR_BIT \
+ (PS2_LSTS_TXTDO | PS2_LSTS_STOPERR | PS2_LSTS_ACKERR | \
+ PS2_LSTS_PARERR | PS2_LSTS_RXTDO)
+
+/* PS2 FIFO CONTROL REGISTER */
+#define PS2_FCTL_TXRST BIT(17)
+#define PS2_FCTL_RXRST BIT(16)
+#define PS2_FCTL_TXUFIEN BIT(10)
+#define PS2_FCTL_TXOFIEN BIT(9)
+#define PS2_FCTL_TXRDYIEN BIT(8)
+#define PS2_FCTL_RXUFIEN BIT(2)
+#define PS2_FCTL_RXOFIEN BIT(1)
+#define PS2_FCTL_RXRDYIEN BIT(0)
+
+/* PS2 FIFO STATUS REGISTER */
+#define PS2_FSTS_TXUF BIT(10)
+#define PS2_FSTS_TXOF BIT(9)
+#define PS2_FSTS_TXRDY BIT(8)
+#define PS2_FSTS_RXUF BIT(2)
+#define PS2_FSTS_RXOF BIT(1)
+#define PS2_FSTS_RXRDY BIT(0)
+
+#define PS2_FIFO_ERROR_BIT \
+ (PS2_FSTS_TXUF | PS2_FSTS_TXOF | PS2_FSTS_RXUF | PS2_FSTS_RXOF)
+
+#define PS2_SAMPLE_CLK 1000000
+#define PS2_SCLK 125000
+
+struct sun4i_ps2data {
+ struct serio *serio;
+ struct device *dev;
+
+ /* IO mapping base */
+ void __iomem *reg_base;
+
+ /* clock management */
+ struct clk *clk;
+
+ /* irq */
+ spinlock_t lock;
+ int irq;
+};
+
+static irqreturn_t sun4i_ps2_interrupt(int irq, void *dev_id)
+{
+ struct sun4i_ps2data *drvdata = dev_id;
+ u32 intr_status;
+ u32 fifo_status;
+ unsigned char byte;
+ unsigned int rxflags = 0;
+ u32 rval;
+
+ spin_lock(&drvdata->lock);
+
+ /* Get the PS/2 interrupts and clear them */
+ intr_status = readl(drvdata->reg_base + PS2_REG_LSTS);
+ fifo_status = readl(drvdata->reg_base + PS2_REG_FSTS);
+
+ /* Check line status register */
+ if (intr_status & PS2_LINE_ERROR_BIT) {
+ rxflags = (intr_status & PS2_LINE_ERROR_BIT) ? SERIO_FRAME : 0;
+ rxflags |= (intr_status & PS2_LSTS_PARERR) ? SERIO_PARITY : 0;
+ rxflags |= (intr_status & PS2_LSTS_PARERR) ? SERIO_TIMEOUT : 0;
+
+ rval = PS2_LSTS_TXTDO | PS2_LSTS_STOPERR | PS2_LSTS_ACKERR |
+ PS2_LSTS_PARERR | PS2_LSTS_RXTDO;
+ writel(rval, drvdata->reg_base + PS2_REG_LSTS);
+ }
+
+ /* Check FIFO status register */
+ if (fifo_status & PS2_FIFO_ERROR_BIT) {
+ rval = PS2_FSTS_TXUF | PS2_FSTS_TXOF | PS2_FSTS_TXRDY |
+ PS2_FSTS_RXUF | PS2_FSTS_RXOF | PS2_FSTS_RXRDY;
+ writel(rval, drvdata->reg_base + PS2_REG_FSTS);
+ }
+
+ rval = (fifo_status >> 16) & 0x3;
+ while (rval--) {
+ byte = readl(drvdata->reg_base + PS2_REG_DATA) & 0xff;
+ serio_interrupt(drvdata->serio, byte, rxflags);
+ }
+
+ writel(intr_status, drvdata->reg_base + PS2_REG_LSTS);
+ writel(fifo_status, drvdata->reg_base + PS2_REG_FSTS);
+
+ spin_unlock(&drvdata->lock);
+
+ return IRQ_HANDLED;
+}
+
+static int sun4i_ps2_open(struct serio *serio)
+{
+ struct sun4i_ps2data *drvdata = serio->port_data;
+ u32 src_clk = 0;
+ u32 clk_scdf;
+ u32 clk_pcdf;
+ u32 rval;
+ unsigned long flags;
+
+ /* Set line control and enable interrupt */
+ rval = PS2_LCTL_STOPERREN | PS2_LCTL_ACKERREN
+ | PS2_LCTL_PARERREN | PS2_LCTL_RXDTOEN;
+ writel(rval, drvdata->reg_base + PS2_REG_LCTL);
+
+ /* Reset FIFO */
+ rval = PS2_FCTL_TXRST | PS2_FCTL_RXRST | PS2_FCTL_TXUFIEN
+ | PS2_FCTL_TXOFIEN | PS2_FCTL_RXUFIEN
+ | PS2_FCTL_RXOFIEN | PS2_FCTL_RXRDYIEN;
+
+ writel(rval, drvdata->reg_base + PS2_REG_FCTL);
+
+ src_clk = clk_get_rate(drvdata->clk);
+ /* Set clock divider register */
+ clk_scdf = src_clk / PS2_SAMPLE_CLK - 1;
+ clk_pcdf = PS2_SAMPLE_CLK / PS2_SCLK - 1;
+ rval = (clk_scdf << 8) | clk_pcdf;
+ writel(rval, drvdata->reg_base + PS2_REG_CLKDR);
+
+ /* Set global control register */
+ rval = PS2_GCTL_RESET | PS2_GCTL_INTEN | PS2_GCTL_MASTER
+ | PS2_GCTL_BUSEN;
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+ writel(rval, drvdata->reg_base + PS2_REG_GCTL);
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+
+ return 0;
+}
+
+static void sun4i_ps2_close(struct serio *serio)
+{
+ struct sun4i_ps2data *drvdata = serio->port_data;
+ u32 rval;
+
+ /* Shut off the interrupt */
+ rval = readl(drvdata->reg_base + PS2_REG_GCTL);
+ writel(rval & ~(PS2_GCTL_INTEN), drvdata->reg_base + PS2_REG_GCTL);
+
+ synchronize_irq(drvdata->irq);
+}
+
+static int sun4i_ps2_write(struct serio *serio, unsigned char val)
+{
+ unsigned long expire = jiffies + msecs_to_jiffies(10000);
+ struct sun4i_ps2data *drvdata = serio->port_data;
+
+ do {
+ if (readl(drvdata->reg_base + PS2_REG_FSTS) & PS2_FSTS_TXRDY) {
+ writel(val, drvdata->reg_base + PS2_REG_DATA);
+ return 0;
+ }
+ } while (time_before(jiffies, expire));
+
+ return SERIO_TIMEOUT;
+}
+
+static int sun4i_ps2_probe(struct platform_device *pdev)
+{
+ struct resource *res; /* IO mem resources */
+ struct sun4i_ps2data *drvdata;
+ struct serio *serio;
+ struct device *dev = &pdev->dev;
+ unsigned int irq;
+ int error;
+
+ drvdata = kzalloc(sizeof(struct sun4i_ps2data), GFP_KERNEL);
+ serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ if (!drvdata || !serio) {
+ error = -ENOMEM;
+ goto err_free_mem;
+ }
+
+ spin_lock_init(&drvdata->lock);
+
+ /* IO */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "failed to locate registers\n");
+ error = -ENXIO;
+ goto err_free_mem;
+ }
+
+ drvdata->reg_base = ioremap(res->start, resource_size(res));
+ if (!drvdata->reg_base) {
+ dev_err(dev, "failed to map registers\n");
+ error = -ENOMEM;
+ goto err_free_mem;
+ }
+
+ drvdata->clk = clk_get(dev, NULL);
+ if (IS_ERR(drvdata->clk)) {
+ error = PTR_ERR(drvdata->clk);
+ dev_err(dev, "couldn't get clock %d\n", error);
+ goto err_ioremap;
+ }
+
+ error = clk_prepare_enable(drvdata->clk);
+ if (error) {
+ dev_err(dev, "failed to enable clock %d\n", error);
+ goto err_clk;
+ }
+
+ serio->id.type = SERIO_8042;
+ serio->write = sun4i_ps2_write;
+ serio->open = sun4i_ps2_open;
+ serio->close = sun4i_ps2_close;
+ serio->port_data = drvdata;
+ serio->dev.parent = dev;
+ strlcpy(serio->name, dev_name(dev), sizeof(serio->name));
+ strlcpy(serio->phys, dev_name(dev), sizeof(serio->phys));
+
+ /* shutoff interrupt */
+ writel(0, drvdata->reg_base + PS2_REG_GCTL);
+
+ /* Get IRQ for the device */
+ irq = platform_get_irq(pdev, 0);
+ if (!irq) {
+ dev_err(dev, "no IRQ found\n");
+ error = -ENXIO;
+ goto err_disable_clk;
+ }
+
+ drvdata->irq = irq;
+ drvdata->serio = serio;
+ drvdata->dev = dev;
+
+ error = request_irq(drvdata->irq, sun4i_ps2_interrupt, 0,
+ DRIVER_NAME, drvdata);
+ if (error) {
+ dev_err(drvdata->dev, "failed to allocate interrupt %d: %d\n",
+ drvdata->irq, error);
+ goto err_disable_clk;
+ }
+
+ serio_register_port(serio);
+ platform_set_drvdata(pdev, drvdata);
+
+ return 0; /* success */
+
+err_disable_clk:
+ clk_disable_unprepare(drvdata->clk);
+err_clk:
+ clk_put(drvdata->clk);
+err_ioremap:
+ iounmap(drvdata->reg_base);
+err_free_mem:
+ kfree(serio);
+ kfree(drvdata);
+ return error;
+}
+
+static int sun4i_ps2_remove(struct platform_device *pdev)
+{
+ struct sun4i_ps2data *drvdata = platform_get_drvdata(pdev);
+
+ serio_unregister_port(drvdata->serio);
+
+ free_irq(drvdata->irq, drvdata);
+
+ clk_disable_unprepare(drvdata->clk);
+ clk_put(drvdata->clk);
+
+ iounmap(drvdata->reg_base);
+
+ kfree(drvdata);
+
+ return 0;
+}
+
+static const struct of_device_id sun4i_ps2_match[] = {
+ { .compatible = "allwinner,sun4i-a10-ps2", },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, sun4i_ps2_match);
+
+static struct platform_driver sun4i_ps2_driver = {
+ .probe = sun4i_ps2_probe,
+ .remove = sun4i_ps2_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = sun4i_ps2_match,
+ },
+};
+module_platform_driver(sun4i_ps2_driver);
+
+MODULE_AUTHOR("Vishnu Patekar <vishnupatekar0510@gmail.com>");
+MODULE_AUTHOR("Aaron.maoye <leafy.myeh@newbietech.com>");
+MODULE_DESCRIPTION("Allwinner A10/Sun4i PS/2 driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/tablet/gtco.c b/drivers/input/tablet/gtco.c
index 858045694e9d..3a7f3a4a4396 100644
--- a/drivers/input/tablet/gtco.c
+++ b/drivers/input/tablet/gtco.c
@@ -59,7 +59,7 @@ Scott Hill shill@gtcocalcomp.com
#include <asm/uaccess.h>
#include <asm/unaligned.h>
#include <asm/byteorder.h>
-
+#include <linux/bitops.h>
#include <linux/usb/input.h>
@@ -614,7 +614,6 @@ static void gtco_urb_callback(struct urb *urbinfo)
struct input_dev *inputdev;
int rc;
u32 val = 0;
- s8 valsigned = 0;
char le_buffer[2];
inputdev = device->inputdevice;
@@ -665,20 +664,11 @@ static void gtco_urb_callback(struct urb *urbinfo)
/* Fall thru */
case 4:
/* Tilt */
+ input_report_abs(inputdev, ABS_TILT_X,
+ sign_extend32(device->buffer[6], 6));
- /* Sign extend these 7 bit numbers. */
- if (device->buffer[6] & 0x40)
- device->buffer[6] |= 0x80;
-
- if (device->buffer[7] & 0x40)
- device->buffer[7] |= 0x80;
-
-
- valsigned = (device->buffer[6]);
- input_report_abs(inputdev, ABS_TILT_X, (s32)valsigned);
-
- valsigned = (device->buffer[7]);
- input_report_abs(inputdev, ABS_TILT_Y, (s32)valsigned);
+ input_report_abs(inputdev, ABS_TILT_Y,
+ sign_extend32(device->buffer[7], 6));
/* Fall thru */
case 2:
diff --git a/drivers/input/touchscreen/elants_i2c.c b/drivers/input/touchscreen/elants_i2c.c
index a510f7ef9b66..926c58e540c0 100644
--- a/drivers/input/touchscreen/elants_i2c.c
+++ b/drivers/input/touchscreen/elants_i2c.c
@@ -33,10 +33,8 @@
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/buffer_head.h>
-#include <linux/version.h>
#include <linux/slab.h>
#include <linux/firmware.h>
-#include <linux/version.h>
#include <linux/input/mt.h>
#include <linux/acpi.h>
#include <linux/of.h>
diff --git a/drivers/input/touchscreen/pixcir_i2c_ts.c b/drivers/input/touchscreen/pixcir_i2c_ts.c
index 4fb5537fdd42..2c2107147319 100644
--- a/drivers/input/touchscreen/pixcir_i2c_ts.c
+++ b/drivers/input/touchscreen/pixcir_i2c_ts.c
@@ -126,7 +126,7 @@ static void pixcir_ts_report(struct pixcir_i2c_ts_data *ts,
pos[i].y = touch->y;
}
- input_mt_assign_slots(ts->input, slots, pos, n);
+ input_mt_assign_slots(ts->input, slots, pos, n, 0);
}
for (i = 0; i < n; i++) {
diff --git a/drivers/input/touchscreen/sun4i-ts.c b/drivers/input/touchscreen/sun4i-ts.c
index 28a06749ae42..b93a28b955fd 100644
--- a/drivers/input/touchscreen/sun4i-ts.c
+++ b/drivers/input/touchscreen/sun4i-ts.c
@@ -34,6 +34,7 @@
#include <linux/err.h>
#include <linux/hwmon.h>
+#include <linux/thermal.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/interrupt.h>
@@ -71,6 +72,9 @@
#define TP_ADC_SELECT(x) ((x) << 3)
#define ADC_CHAN_SELECT(x) ((x) << 0) /* 3 bits */
+/* on sun6i, bits 3~6 are left shifted by 1 to 4~7 */
+#define SUN6I_TP_MODE_EN(x) ((x) << 5)
+
/* TP_CTRL2 bits */
#define TP_SENSITIVE_ADJUST(x) ((x) << 28) /* 4 bits */
#define TP_MODE_SELECT(x) ((x) << 26) /* 2 bits */
@@ -107,10 +111,13 @@
struct sun4i_ts_data {
struct device *dev;
struct input_dev *input;
+ struct thermal_zone_device *tz;
void __iomem *base;
unsigned int irq;
bool ignore_fifo_data;
int temp_data;
+ int temp_offset;
+ int temp_step;
};
static void sun4i_ts_irq_handle_input(struct sun4i_ts_data *ts, u32 reg_val)
@@ -180,16 +187,38 @@ static void sun4i_ts_close(struct input_dev *dev)
writel(TEMP_IRQ_EN(1), ts->base + TP_INT_FIFOC);
}
+static int sun4i_get_temp(const struct sun4i_ts_data *ts, long *temp)
+{
+ /* No temp_data until the first irq */
+ if (ts->temp_data == -1)
+ return -EAGAIN;
+
+ *temp = (ts->temp_data - ts->temp_offset) * ts->temp_step;
+
+ return 0;
+}
+
+static int sun4i_get_tz_temp(void *data, long *temp)
+{
+ return sun4i_get_temp(data, temp);
+}
+
+static struct thermal_zone_of_device_ops sun4i_ts_tz_ops = {
+ .get_temp = sun4i_get_tz_temp,
+};
+
static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
char *buf)
{
struct sun4i_ts_data *ts = dev_get_drvdata(dev);
+ long temp;
+ int error;
- /* No temp_data until the first irq */
- if (ts->temp_data == -1)
- return -EAGAIN;
+ error = sun4i_get_temp(ts, &temp);
+ if (error)
+ return error;
- return sprintf(buf, "%d\n", (ts->temp_data - 1447) * 100);
+ return sprintf(buf, "%ld\n", temp);
}
static ssize_t show_temp_label(struct device *dev,
@@ -215,6 +244,7 @@ static int sun4i_ts_probe(struct platform_device *pdev)
struct device_node *np = dev->of_node;
struct device *hwmon;
int error;
+ u32 reg;
bool ts_attached;
ts = devm_kzalloc(dev, sizeof(struct sun4i_ts_data), GFP_KERNEL);
@@ -224,6 +254,25 @@ static int sun4i_ts_probe(struct platform_device *pdev)
ts->dev = dev;
ts->ignore_fifo_data = true;
ts->temp_data = -1;
+ if (of_device_is_compatible(np, "allwinner,sun6i-a31-ts")) {
+ /* Allwinner SDK has temperature = -271 + (value / 6) (C) */
+ ts->temp_offset = 1626;
+ ts->temp_step = 167;
+ } else {
+ /*
+ * The user manuals do not contain the formula for calculating
+ * the temperature. The formula used here is from the AXP209,
+ * which is designed by X-Powers, an affiliate of Allwinner:
+ *
+ * temperature = -144.7 + (value * 0.1)
+ *
+ * Allwinner does not have any documentation whatsoever for
+ * this hardware. Moreover, it is claimed that the sensor
+ * is inaccurate and cannot work properly.
+ */
+ ts->temp_offset = 1447;
+ ts->temp_step = 100;
+ }
ts_attached = of_property_read_bool(np, "allwinner,ts-attached");
if (ts_attached) {
@@ -280,20 +329,34 @@ static int sun4i_ts_probe(struct platform_device *pdev)
* Set stylus up debounce to aprox 10 ms, enable debounce, and
* finally enable tp mode.
*/
- writel(STYLUS_UP_DEBOUN(5) | STYLUS_UP_DEBOUN_EN(1) | TP_MODE_EN(1),
- ts->base + TP_CTRL1);
+ reg = STYLUS_UP_DEBOUN(5) | STYLUS_UP_DEBOUN_EN(1);
+ if (of_device_is_compatible(np, "allwinner,sun4i-a10-ts"))
+ reg |= TP_MODE_EN(1);
+ else
+ reg |= SUN6I_TP_MODE_EN(1);
+ writel(reg, ts->base + TP_CTRL1);
+ /*
+ * The thermal core does not register hwmon devices for DT-based
+ * thermal zone sensors, such as this one.
+ */
hwmon = devm_hwmon_device_register_with_groups(ts->dev, "sun4i_ts",
ts, sun4i_ts_groups);
if (IS_ERR(hwmon))
return PTR_ERR(hwmon);
+ ts->tz = thermal_zone_of_sensor_register(ts->dev, 0, ts,
+ &sun4i_ts_tz_ops);
+ if (IS_ERR(ts->tz))
+ ts->tz = NULL;
+
writel(TEMP_IRQ_EN(1), ts->base + TP_INT_FIFOC);
if (ts_attached) {
error = input_register_device(ts->input);
if (error) {
writel(0, ts->base + TP_INT_FIFOC);
+ thermal_zone_of_sensor_unregister(ts->dev, ts->tz);
return error;
}
}
@@ -310,6 +373,8 @@ static int sun4i_ts_remove(struct platform_device *pdev)
if (ts->input)
input_unregister_device(ts->input);
+ thermal_zone_of_sensor_unregister(ts->dev, ts->tz);
+
/* Deactivate all IRQs */
writel(0, ts->base + TP_INT_FIFOC);
@@ -318,6 +383,7 @@ static int sun4i_ts_remove(struct platform_device *pdev)
static const struct of_device_id sun4i_ts_of_match[] = {
{ .compatible = "allwinner,sun4i-a10-ts", },
+ { .compatible = "allwinner,sun6i-a31-ts", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sun4i_ts_of_match);
diff --git a/drivers/input/touchscreen/ti_am335x_tsc.c b/drivers/input/touchscreen/ti_am335x_tsc.c
index 004f1346a957..191a1b87895f 100644
--- a/drivers/input/touchscreen/ti_am335x_tsc.c
+++ b/drivers/input/touchscreen/ti_am335x_tsc.c
@@ -26,6 +26,7 @@
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/of_device.h>
+#include <linux/sort.h>
#include <linux/mfd/ti_am335x_tscadc.h>
@@ -52,6 +53,7 @@ struct titsc {
u32 bit_xp, bit_xn, bit_yp, bit_yn;
u32 inp_xp, inp_xn, inp_yp, inp_yn;
u32 step_mask;
+ u32 charge_delay;
};
static unsigned int titsc_readl(struct titsc *ts, unsigned int reg)
@@ -121,7 +123,7 @@ static void titsc_step_config(struct titsc *ts_dev)
{
unsigned int config;
int i;
- int end_step;
+ int end_step, first_step, tsc_steps;
u32 stepenable;
config = STEPCONFIG_MODE_HWSYNC |
@@ -140,9 +142,11 @@ static void titsc_step_config(struct titsc *ts_dev)
break;
}
- /* 1 … coordinate_readouts is for X */
- end_step = ts_dev->coordinate_readouts;
- for (i = 0; i < end_step; i++) {
+ tsc_steps = ts_dev->coordinate_readouts * 2 + 2;
+ first_step = TOTAL_STEPS - tsc_steps;
+ /* Steps 16 to 16-coordinate_readouts is for X */
+ end_step = first_step + tsc_steps;
+ for (i = end_step - ts_dev->coordinate_readouts; i < end_step; i++) {
titsc_writel(ts_dev, REG_STEPCONFIG(i), config);
titsc_writel(ts_dev, REG_STEPDELAY(i), STEPCONFIG_OPENDLY);
}
@@ -164,22 +168,20 @@ static void titsc_step_config(struct titsc *ts_dev)
break;
}
- /* coordinate_readouts … coordinate_readouts * 2 is for Y */
- end_step = ts_dev->coordinate_readouts * 2;
- for (i = ts_dev->coordinate_readouts; i < end_step; i++) {
+ /* 1 ... coordinate_readouts is for Y */
+ end_step = first_step + ts_dev->coordinate_readouts;
+ for (i = first_step; i < end_step; i++) {
titsc_writel(ts_dev, REG_STEPCONFIG(i), config);
titsc_writel(ts_dev, REG_STEPDELAY(i), STEPCONFIG_OPENDLY);
}
- /* Charge step configuration */
- config = ts_dev->bit_xp | ts_dev->bit_yn |
- STEPCHARGE_RFP_XPUL | STEPCHARGE_RFM_XNUR |
- STEPCHARGE_INM_AN1 | STEPCHARGE_INP(ts_dev->inp_yp);
+ /* Make CHARGECONFIG same as IDLECONFIG */
+ config = titsc_readl(ts_dev, REG_IDLECONFIG);
titsc_writel(ts_dev, REG_CHARGECONFIG, config);
- titsc_writel(ts_dev, REG_CHARGEDELAY, CHARGEDLY_OPENDLY);
+ titsc_writel(ts_dev, REG_CHARGEDELAY, ts_dev->charge_delay);
- /* coordinate_readouts * 2 … coordinate_readouts * 2 + 2 is for Z */
+ /* coordinate_readouts + 1 ... coordinate_readouts + 2 is for Z */
config = STEPCONFIG_MODE_HWSYNC |
STEPCONFIG_AVG_16 | ts_dev->bit_yp |
ts_dev->bit_xn | STEPCONFIG_INM_ADCREFM |
@@ -194,73 +196,104 @@ static void titsc_step_config(struct titsc *ts_dev)
titsc_writel(ts_dev, REG_STEPDELAY(end_step),
STEPCONFIG_OPENDLY);
- /* The steps1 … end and bit 0 for TS_Charge */
- stepenable = (1 << (end_step + 2)) - 1;
+ /* The steps end ... end - readouts * 2 + 2 and bit 0 for TS_Charge */
+ stepenable = 1;
+ for (i = 0; i < tsc_steps; i++)
+ stepenable |= 1 << (first_step + i + 1);
+
ts_dev->step_mask = stepenable;
am335x_tsc_se_set_cache(ts_dev->mfd_tscadc, ts_dev->step_mask);
}
+static int titsc_cmp_coord(const void *a, const void *b)
+{
+ return *(int *)a - *(int *)b;
+}
+
static void titsc_read_coordinates(struct titsc *ts_dev,
u32 *x, u32 *y, u32 *z1, u32 *z2)
{
- unsigned int fifocount = titsc_readl(ts_dev, REG_FIFO0CNT);
- unsigned int prev_val_x = ~0, prev_val_y = ~0;
- unsigned int prev_diff_x = ~0, prev_diff_y = ~0;
- unsigned int read, diff;
- unsigned int i, channel;
+ unsigned int yvals[7], xvals[7];
+ unsigned int i, xsum = 0, ysum = 0;
unsigned int creads = ts_dev->coordinate_readouts;
- *z1 = *z2 = 0;
- if (fifocount % (creads * 2 + 2))
- fifocount -= fifocount % (creads * 2 + 2);
- /*
- * Delta filter is used to remove large variations in sampled
- * values from ADC. The filter tries to predict where the next
- * coordinate could be. This is done by taking a previous
- * coordinate and subtracting it form current one. Further the
- * algorithm compares the difference with that of a present value,
- * if true the value is reported to the sub system.
- */
- for (i = 0; i < fifocount; i++) {
- read = titsc_readl(ts_dev, REG_FIFO0);
-
- channel = (read & 0xf0000) >> 16;
- read &= 0xfff;
- if (channel < creads) {
- diff = abs(read - prev_val_x);
- if (diff < prev_diff_x) {
- prev_diff_x = diff;
- *x = read;
- }
- prev_val_x = read;
+ for (i = 0; i < creads; i++) {
+ yvals[i] = titsc_readl(ts_dev, REG_FIFO0);
+ yvals[i] &= 0xfff;
+ }
- } else if (channel < creads * 2) {
- diff = abs(read - prev_val_y);
- if (diff < prev_diff_y) {
- prev_diff_y = diff;
- *y = read;
- }
- prev_val_y = read;
+ *z1 = titsc_readl(ts_dev, REG_FIFO0);
+ *z1 &= 0xfff;
+ *z2 = titsc_readl(ts_dev, REG_FIFO0);
+ *z2 &= 0xfff;
- } else if (channel < creads * 2 + 1) {
- *z1 = read;
+ for (i = 0; i < creads; i++) {
+ xvals[i] = titsc_readl(ts_dev, REG_FIFO0);
+ xvals[i] &= 0xfff;
+ }
- } else if (channel < creads * 2 + 2) {
- *z2 = read;
+ /*
+ * If co-ordinates readouts is less than 4 then
+ * report the average. In case of 4 or more
+ * readouts, sort the co-ordinate samples, drop
+ * min and max values and report the average of
+ * remaining values.
+ */
+ if (creads <= 3) {
+ for (i = 0; i < creads; i++) {
+ ysum += yvals[i];
+ xsum += xvals[i];
}
+ ysum /= creads;
+ xsum /= creads;
+ } else {
+ sort(yvals, creads, sizeof(unsigned int),
+ titsc_cmp_coord, NULL);
+ sort(xvals, creads, sizeof(unsigned int),
+ titsc_cmp_coord, NULL);
+ for (i = 1; i < creads - 1; i++) {
+ ysum += yvals[i];
+ xsum += xvals[i];
+ }
+ ysum /= creads - 2;
+ xsum /= creads - 2;
}
+ *y = ysum;
+ *x = xsum;
}
static irqreturn_t titsc_irq(int irq, void *dev)
{
struct titsc *ts_dev = dev;
struct input_dev *input_dev = ts_dev->input;
- unsigned int status, irqclr = 0;
+ unsigned int fsm, status, irqclr = 0;
unsigned int x = 0, y = 0;
unsigned int z1, z2, z;
- unsigned int fsm;
- status = titsc_readl(ts_dev, REG_IRQSTATUS);
+ status = titsc_readl(ts_dev, REG_RAWIRQSTATUS);
+ if (status & IRQENB_HW_PEN) {
+ ts_dev->pen_down = true;
+ titsc_writel(ts_dev, REG_IRQWAKEUP, 0x00);
+ titsc_writel(ts_dev, REG_IRQCLR, IRQENB_HW_PEN);
+ irqclr |= IRQENB_HW_PEN;
+ }
+
+ if (status & IRQENB_PENUP) {
+ fsm = titsc_readl(ts_dev, REG_ADCFSM);
+ if (fsm == ADCFSM_STEPID) {
+ ts_dev->pen_down = false;
+ input_report_key(input_dev, BTN_TOUCH, 0);
+ input_report_abs(input_dev, ABS_PRESSURE, 0);
+ input_sync(input_dev);
+ } else {
+ ts_dev->pen_down = true;
+ }
+ irqclr |= IRQENB_PENUP;
+ }
+
+ if (status & IRQENB_EOS)
+ irqclr |= IRQENB_EOS;
+
/*
* ADC and touchscreen share the IRQ line.
* FIFO1 interrupts are used by ADC. Handle FIFO0 IRQs here only
@@ -291,37 +324,11 @@ static irqreturn_t titsc_irq(int irq, void *dev)
}
irqclr |= IRQENB_FIFO0THRES;
}
-
- /*
- * Time for sequencer to settle, to read
- * correct state of the sequencer.
- */
- udelay(SEQ_SETTLE);
-
- status = titsc_readl(ts_dev, REG_RAWIRQSTATUS);
- if (status & IRQENB_PENUP) {
- /* Pen up event */
- fsm = titsc_readl(ts_dev, REG_ADCFSM);
- if (fsm == ADCFSM_STEPID) {
- ts_dev->pen_down = false;
- input_report_key(input_dev, BTN_TOUCH, 0);
- input_report_abs(input_dev, ABS_PRESSURE, 0);
- input_sync(input_dev);
- } else {
- ts_dev->pen_down = true;
- }
- irqclr |= IRQENB_PENUP;
- }
-
- if (status & IRQENB_HW_PEN) {
-
- titsc_writel(ts_dev, REG_IRQWAKEUP, 0x00);
- titsc_writel(ts_dev, REG_IRQCLR, IRQENB_HW_PEN);
- }
-
if (irqclr) {
titsc_writel(ts_dev, REG_IRQSTATUS, irqclr);
- am335x_tsc_se_set_cache(ts_dev->mfd_tscadc, ts_dev->step_mask);
+ if (status & IRQENB_EOS)
+ am335x_tsc_se_set_cache(ts_dev->mfd_tscadc,
+ ts_dev->step_mask);
return IRQ_HANDLED;
}
return IRQ_NONE;
@@ -368,6 +375,23 @@ static int titsc_parse_dt(struct platform_device *pdev,
if (err < 0)
return err;
+ if (ts_dev->coordinate_readouts <= 0) {
+ dev_warn(&pdev->dev,
+ "invalid co-ordinate readouts, resetting it to 5\n");
+ ts_dev->coordinate_readouts = 5;
+ }
+
+ err = of_property_read_u32(node, "ti,charge-delay",
+ &ts_dev->charge_delay);
+ /*
+ * If ti,charge-delay value is not specified, then use
+ * CHARGEDLY_OPENDLY as the default value.
+ */
+ if (err < 0) {
+ ts_dev->charge_delay = CHARGEDLY_OPENDLY;
+ dev_warn(&pdev->dev, "ti,charge-delay not specified\n");
+ }
+
return of_property_read_u32_array(node, "ti,wire-config",
ts_dev->config_inp, ARRAY_SIZE(ts_dev->config_inp));
}
@@ -411,6 +435,7 @@ static int titsc_probe(struct platform_device *pdev)
}
titsc_writel(ts_dev, REG_IRQENABLE, IRQENB_FIFO0THRES);
+ titsc_writel(ts_dev, REG_IRQENABLE, IRQENB_EOS);
err = titsc_config_wires(ts_dev);
if (err) {
dev_err(&pdev->dev, "wrong i/p wire configuration\n");
diff --git a/include/linux/input/mt.h b/include/linux/input/mt.h
index f583ff639776..d7188de4db96 100644
--- a/include/linux/input/mt.h
+++ b/include/linux/input/mt.h
@@ -119,7 +119,8 @@ struct input_mt_pos {
};
int input_mt_assign_slots(struct input_dev *dev, int *slots,
- const struct input_mt_pos *pos, int num_pos);
+ const struct input_mt_pos *pos, int num_pos,
+ int dmax);
int input_mt_get_slot_by_key(struct input_dev *dev, int key);
diff --git a/include/linux/mfd/ti_am335x_tscadc.h b/include/linux/mfd/ti_am335x_tscadc.h
index e2e70053470e..3f4e994ace2b 100644
--- a/include/linux/mfd/ti_am335x_tscadc.h
+++ b/include/linux/mfd/ti_am335x_tscadc.h
@@ -52,6 +52,7 @@
/* IRQ enable */
#define IRQENB_HW_PEN BIT(0)
+#define IRQENB_EOS BIT(1)
#define IRQENB_FIFO0THRES BIT(2)
#define IRQENB_FIFO0OVRRUN BIT(3)
#define IRQENB_FIFO0UNDRFLW BIT(4)
@@ -107,7 +108,7 @@
/* Charge delay */
#define CHARGEDLY_OPEN_MASK (0x3FFFF << 0)
#define CHARGEDLY_OPEN(val) ((val) << 0)
-#define CHARGEDLY_OPENDLY CHARGEDLY_OPEN(1)
+#define CHARGEDLY_OPENDLY CHARGEDLY_OPEN(0x400)
/* Control register */
#define CNTRLREG_TSCSSENB BIT(0)
diff --git a/include/linux/platform_data/regulator-haptic.h b/include/linux/platform_data/regulator-haptic.h
new file mode 100644
index 000000000000..5658e58e0738
--- /dev/null
+++ b/include/linux/platform_data/regulator-haptic.h
@@ -0,0 +1,29 @@
+/*
+ * Regulator Haptic Platform Data
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Author: Jaewon Kim <jaewon02.kim@samsung.com>
+ * Author: Hyunhee Kim <hyunhee.kim@samsung.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 _REGULATOR_HAPTIC_H
+#define _REGULATOR_HAPTIC_H
+
+/*
+ * struct regulator_haptic_data - Platform device data
+ *
+ * @max_volt: maximum voltage value supplied to the haptic motor.
+ * <The unit of the voltage is a micro>
+ * @min_volt: minimum voltage value supplied to the haptic motor.
+ * <The unit of the voltage is a micro>
+ */
+struct regulator_haptic_data {
+ unsigned int max_volt;
+ unsigned int min_volt;
+};
+
+#endif /* _REGULATOR_HAPTIC_H */