Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 11 Feb 2015 17:32:08 +0000 (09:32 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 11 Feb 2015 17:32:08 +0000 (09:32 -0800)
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()
  ...

61 files changed:
Documentation/ABI/testing/sysfs-driver-input-axp-pek [new file with mode: 0644]
Documentation/devicetree/bindings/input/e3x0-button.txt [new file with mode: 0644]
Documentation/devicetree/bindings/input/regulator-haptic.txt [new file with mode: 0644]
Documentation/devicetree/bindings/input/sun4i-lradc-keys.txt [new file with mode: 0644]
Documentation/devicetree/bindings/input/touchscreen/sun4i.txt
Documentation/devicetree/bindings/input/touchscreen/ti-tsc-adc.txt
Documentation/devicetree/bindings/input/tps65218-pwrbutton.txt [new file with mode: 0644]
Documentation/devicetree/bindings/serio/allwinner,sun4i-ps2.txt [new file with mode: 0644]
Documentation/devicetree/bindings/vendor-prefixes.txt
MAINTAINERS
arch/arm/boot/dts/am335x-evm.dts
drivers/iio/adc/ti_am335x_adc.c
drivers/input/evdev.c
drivers/input/input-mt.c
drivers/input/input.c
drivers/input/keyboard/Kconfig
drivers/input/keyboard/Makefile
drivers/input/keyboard/atakbd.c
drivers/input/keyboard/cap11xx.c
drivers/input/keyboard/imx_keypad.c
drivers/input/keyboard/pxa27x_keypad.c
drivers/input/keyboard/sun4i-lradc-keys.c [new file with mode: 0644]
drivers/input/misc/Kconfig
drivers/input/misc/Makefile
drivers/input/misc/axp20x-pek.c [new file with mode: 0644]
drivers/input/misc/drv260x.c
drivers/input/misc/drv2667.c
drivers/input/misc/e3x0-button.c [new file with mode: 0644]
drivers/input/misc/regulator-haptic.c [new file with mode: 0644]
drivers/input/misc/tps65218-pwrbutton.c [new file with mode: 0644]
drivers/input/mouse/Kconfig
drivers/input/mouse/Makefile
drivers/input/mouse/alps.c
drivers/input/mouse/bcm5974.c
drivers/input/mouse/cyapa.c
drivers/input/mouse/cyapa.h [new file with mode: 0644]
drivers/input/mouse/cyapa_gen3.c [new file with mode: 0644]
drivers/input/mouse/cyapa_gen5.c [new file with mode: 0644]
drivers/input/mouse/cypress_ps2.c
drivers/input/mouse/elan_i2c.h
drivers/input/mouse/elan_i2c_core.c
drivers/input/mouse/elan_i2c_i2c.c
drivers/input/mouse/elan_i2c_smbus.c
drivers/input/mouse/focaltech.c
drivers/input/mouse/focaltech.h
drivers/input/mouse/psmouse-base.c
drivers/input/mouse/psmouse.h
drivers/input/mouse/synaptics.c
drivers/input/mouse/synaptics.h
drivers/input/serio/Kconfig
drivers/input/serio/Makefile
drivers/input/serio/gscps2.c
drivers/input/serio/sun4i-ps2.c [new file with mode: 0644]
drivers/input/tablet/gtco.c
drivers/input/touchscreen/elants_i2c.c
drivers/input/touchscreen/pixcir_i2c_ts.c
drivers/input/touchscreen/sun4i-ts.c
drivers/input/touchscreen/ti_am335x_tsc.c
include/linux/input/mt.h
include/linux/mfd/ti_am335x_tscadc.h
include/linux/platform_data/regulator-haptic.h [new file with mode: 0644]

diff --git a/Documentation/ABI/testing/sysfs-driver-input-axp-pek b/Documentation/ABI/testing/sysfs-driver-input-axp-pek
new file mode 100644 (file)
index 0000000..a5e671b
--- /dev/null
@@ -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 (file)
index 0000000..751665e
--- /dev/null
@@ -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 (file)
index 0000000..3ed1c7e
--- /dev/null
@@ -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 (file)
index 0000000..b9c32f6
--- /dev/null
@@ -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>;
+               };
+       };
index aef5779..433332d 100644 (file)
@@ -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>;
        };
index 878549b..6c4fb34 100644 (file)
@@ -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 (file)
index 0000000..e30e0b9
--- /dev/null
@@ -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 (file)
index 0000000..362a769
--- /dev/null
@@ -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";
+       };
index d443279..96a1754 100644 (file)
@@ -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
index 2494988..9c63254 100644 (file)
@@ -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
index 54f118c..6634251 100644 (file)
                ti,x-plate-resistance = <200>;
                ti,coordinate-readouts = <5>;
                ti,wire-config = <0x00 0x11 0x22 0x33>;
+               ti,charge-delay = <0x400>;
        };
 
        adc {
index b730864..adba232 100644 (file)
@@ -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;
index 18d4b2c..a18f41b 100644 (file)
@@ -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,
index fbe29fc..cb150a1 100644 (file)
@@ -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;
index 213e3a1..cc357f1 100644 (file)
@@ -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);
+                       }
                }
        }
 }
index a5d9b3f..a89ba7c 100644 (file)
@@ -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
index febafa5..4707678 100644 (file)
@@ -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
index 10bcd4a..f123583 100644 (file)
@@ -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,
index 4f59f0b..f07461a 100644 (file)
@@ -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");
index e53f232..2e855e6 100644 (file)
@@ -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;
index a90d6bd..a89488a 100644 (file)
@@ -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>
 #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 (file)
index 0000000..cc8f7dd
--- /dev/null
@@ -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");
index 23297ab..6deb8da 100644 (file)
@@ -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
index 19c7603..403a1a5 100644 (file)
@@ -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 (file)
index 0000000..f1c8447
--- /dev/null
@@ -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");
index a364e10..5995780 100644 (file)
@@ -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>");
index a021744..fc0fddf 100644 (file)
@@ -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 (file)
index 0000000..13bfca8
--- /dev/null
@@ -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 (file)
index 0000000..132eb91
--- /dev/null
@@ -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 (file)
index 0000000..54508de
--- /dev/null
@@ -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>");
index d8b46b0..4658b5d 100644 (file)
@@ -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.
index 560003d..8a9c98e 100644 (file)
@@ -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
index d88d73d..f205b8b 100644 (file)
@@ -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);
index c329cdb..b10709f 100644 (file)
@@ -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],
index 1bece8c..58f4f6f 100644 (file)
 #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,143 +240,23 @@ 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;
-}
-
-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;
-
-       /* 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_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.
-        */
-       error = cyapa_poll_state(cyapa, 2000);
-       if (error < 0)
-               return error;
-       if (cyapa->state != CYAPA_STATE_OP)
-               return -EAGAIN;
-
-       return 0;
-}
-
-/*
- * Set device power mode
- *
- */
-static int cyapa_set_power_mode(struct cyapa *cyapa, u8 power_mode)
-{
-       struct device *dev = &cyapa->client->dev;
-       int ret;
-       u8 power;
-
-       if (cyapa->state != CYAPA_STATE_OP)
-               return 0;
-
-       ret = cyapa_read_byte(cyapa, CYAPA_CMD_POWER_MODE);
-       if (ret < 0)
-               return ret;
-
-       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;
-       }
-
-       return 0;
-}
-
-static int cyapa_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 < 0)
-               return ret;
-       if (ret != QUERY_DATA_SIZE)
-               return -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->btn_capability = query_data[19] & CAPABILITY_BTN_MASK;
-
-       cyapa->gen = query_data[20] & 0x0f;
+               if (!error && cyapa->state > CYAPA_STATE_BL_BUSY)
+                       return 0;
 
-       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];
+               msleep(100);
+       } while (tries--);
 
-       return 0;
+       return (error == -EAGAIN || error == -ETIMEDOUT) ? -ETIMEDOUT : error;
 }
 
 /*
@@ -628,8 +266,10 @@ static int cyapa_get_query_data(struct cyapa *cyapa)
  * firmware supported by this driver.
  *
  * Returns:
+ *   -ENODEV no device
  *   -EBUSY  no device or in bootloader
  *   -EIO    failure while reading from device
+ *   -ETIMEDOUT timeout failure for bus idle or bus no response
  *   -EAGAIN device is still in bootloader
  *           if ->state = CYAPA_STATE_BL_IDLE, device has invalid firmware
  *   -EINVAL device is in operational mode, but not supported by this driver
@@ -637,122 +277,56 @@ static int cyapa_get_query_data(struct cyapa *cyapa)
  */
 static int cyapa_check_is_operational(struct cyapa *cyapa)
 {
-       struct device *dev = &cyapa->client->dev;
-       static const char unique_str[] = "CYTRA";
        int error;
 
-       error = cyapa_poll_state(cyapa, 2000);
+       error = cyapa_poll_state(cyapa, 4000);
        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;
-
-       /* Fallthrough state */
-       case CYAPA_STATE_OP:
-               error = cyapa_get_query_data(cyapa);
-               if (error)
-                       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, unique_str,
-                          sizeof(unique_str) - 1) != 0) {
-                       dev_err(dev, "unsupported product ID (%s)\n",
-                               cyapa->product_id);
-                       return -EINVAL;
-               }
-               return 0;
 
+       switch (cyapa->gen) {
+       case CYAPA_GEN5:
+               cyapa->ops = &cyapa_gen5_ops;
+               break;
+       case CYAPA_GEN3:
+               cyapa->ops = &cyapa_gen3_ops;
+               break;
        default:
-               return -EIO;
+               return -ENODEV;
        }
-       return 0;
-}
 
-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;
+       error = cyapa->ops->operational_check(cyapa);
+       if (!error && cyapa_is_operational_mode(cyapa))
+               cyapa->operational = true;
+       else
+               cyapa->operational = false;
 
-       if (device_may_wakeup(dev))
-               pm_wakeup_event(dev, 0);
+       return error;
+}
 
-       ret = cyapa_read_block(cyapa, CYAPA_CMD_GROUP_DATA, (u8 *)&data);
-       if (ret != sizeof(data))
-               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;
-       }
+/*
+ * 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_detect(struct cyapa *cyapa)
+{
+       struct device *dev = &cyapa->client->dev;
+       int error;
 
-       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;
+       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;
+               }
 
-               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);
+               dev_err(dev, "no device detected: %d\n", error);
+               return error;
        }
 
-       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);
-
-out:
-       return IRQ_HANDLED;
-}
-
-static u8 cyapa_check_adapter_functionality(struct i2c_client *client)
-{
-       u8 ret = CYAPA_ADAPTER_FUNC_NONE;
-
-       if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
-               ret |= CYAPA_ADAPTER_FUNC_I2C;
-       if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
-                                    I2C_FUNC_SMBUS_BLOCK_DATA |
-                                    I2C_FUNC_SMBUS_I2C_BLOCK))
-               ret |= CYAPA_ADAPTER_FUNC_SMBUS;
-       return ret;
+       return 0;
 }
 
 static int cyapa_open(struct input_dev *input)
@@ -761,22 +335,57 @@ static int cyapa_open(struct input_dev *input)
        struct i2c_client *client = cyapa->client;
        int error;
 
-       error = cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE);
-       if (error) {
-               dev_err(&client->dev, "set active power failed: %d\n", error);
+       error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+       if (error)
                return error;
+
+       if (cyapa->operational) {
+               /*
+                * though failed to set active power mode,
+                * but still may be able to work in lower scan rate
+                * when in operational mode.
+                */
+               error = cyapa->ops->set_power_mode(cyapa,
+                               PWR_MODE_FULL_ACTIVE, 0);
+               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);
-       return 0;
+       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);
 
-       disable_irq(cyapa->client->irq);
-       cyapa_set_power_mode(cyapa, PWR_MODE_OFF);
+       if (cyapa->operational)
+               cyapa->ops->set_power_mode(cyapa, PWR_MODE_OFF, 0);
+
+       mutex_unlock(&cyapa->state_sync_lock);
 }
 
 static int cyapa_create_input_dev(struct cyapa *cyapa)
@@ -813,7 +422,28 @@ static int cyapa_create_input_dev(struct cyapa *cyapa)
                             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);
+       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);
@@ -838,16 +468,720 @@ static int cyapa_create_input_dev(struct cyapa *cyapa)
                return error;
        }
 
+       /* Register the device in input subsystem */
+       error = input_register_device(input);
+       if (error) {
+               dev_err(dev, "failed to register input device: %d\n", error);
+               return error;
+       }
+
        cyapa->input = input;
        return 0;
 }
 
-static int cyapa_probe(struct i2c_client *client,
-                      const struct i2c_device_id *dev_id)
+static void cyapa_enable_irq_for_cmd(struct cyapa *cyapa)
 {
-       struct device *dev = &client->dev;
+       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);
+       }
+}
+
+/*
+ * cyapa_sleep_time_to_pwr_cmd and cyapa_pwr_cmd_to_sleep_time
+ *
+ * These are helper functions that convert to and from integer idle
+ * times and register settings to write to the PowerMode register.
+ * The trackpad supports between 20ms to 1000ms scan intervals.
+ * The time will be increased in increments of 10ms from 20ms to 100ms.
+ * From 100ms to 1000ms, time will be increased in increments of 20ms.
+ *
+ * When Idle_Time < 100, the format to convert Idle_Time to Idle_Command is:
+ *   Idle_Command = Idle Time / 10;
+ * When Idle_Time >= 100, the format to convert Idle_Time to Idle_Command is:
+ *   Idle_Command = Idle Time / 20 + 5;
+ */
+u8 cyapa_sleep_time_to_pwr_cmd(u16 sleep_time)
+{
+       u16 encoded_time;
+
+       sleep_time = clamp_val(sleep_time, 20, 1000);
+       encoded_time = sleep_time < 100 ? sleep_time / 10 : sleep_time / 20 + 5;
+       return (encoded_time << 2) & PWR_MODE_MASK;
+}
+
+u16 cyapa_pwr_cmd_to_sleep_time(u8 pwr_mode)
+{
+       u8 encoded_time = pwr_mode >> 2;
+
+       return (encoded_time < 10) ? encoded_time * 10
+                                  : (encoded_time - 5) * 20;
+}
+
+/* 0 on driver initialize and detected successfully, negative on failure. */
+static int cyapa_initialize(struct cyapa *cyapa)
+{
+       int error = 0;
+
+       cyapa->state = CYAPA_STATE_NO_DEVICE;
+       cyapa->gen = CYAPA_GEN_UNKNOWN;
+       mutex_init(&cyapa->state_sync_lock);
+
+       /*
+        * Set to hard code default, they will be updated with trackpad set
+        * default values after probe and initialized.
+        */
+       cyapa->suspend_power_mode = PWR_MODE_SLEEP;
+       cyapa->suspend_sleep_time =
+               cyapa_pwr_cmd_to_sleep_time(cyapa->suspend_power_mode);
+
+       /* ops.initialize() is aimed to prepare for module communications. */
+       error = cyapa_gen3_ops.initialize(cyapa);
+       if (!error)
+               error = cyapa_gen5_ops.initialize(cyapa);
+       if (error)
+               return error;
+
+       error = cyapa_detect(cyapa);
+       if (error)
+               return error;
+
+       /* Power down the device until we need it. */
+       if (cyapa->operational)
+               cyapa->ops->set_power_mode(cyapa, PWR_MODE_OFF, 0);
+
+       return 0;
+}
+
+static int cyapa_reinitialize(struct cyapa *cyapa)
+{
+       struct device *dev = &cyapa->client->dev;
+       struct input_dev *input = cyapa->input;
+       int error;
+
+       if (pm_runtime_enabled(dev))
+               pm_runtime_disable(dev);
+
+       /* Avoid command failures when TP was in OFF state. */
+       if (cyapa->operational)
+               cyapa->ops->set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE, 0);
+
+       error = cyapa_detect(cyapa);
+       if (error)
+               goto out;
+
+       if (!input && cyapa->operational) {
+               error = cyapa_create_input_dev(cyapa);
+               if (error) {
+                       dev_err(dev, "create input_dev instance failed: %d\n",
+                                       error);
+                       goto out;
+               }
+       }
+
+out:
+       if (!input || !input->users) {
+               /* Reset to power OFF state to save power when no user open. */
+               if (cyapa->operational)
+                       cyapa->ops->set_power_mode(cyapa, PWR_MODE_OFF, 0);
+       } 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 error;
+}
+
+static irqreturn_t cyapa_irq(int irq, void *dev_id)
+{
+       struct cyapa *cyapa = dev_id;
+       struct device *dev = &cyapa->client->dev;
+
+       pm_runtime_get_sync(dev);
+       if (device_may_wakeup(dev))
+               pm_wakeup_event(dev, 0);
+
+       /* 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 (!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);
+               }
+       }
+
+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;
+
+       case PWR_MODE_OFF:
+               len = scnprintf(buf, PAGE_SIZE, "%s\n", OFF_MODE_NAME);
+               break;
+
+       default:
+               len = scnprintf(buf, PAGE_SIZE, "%u\n",
+                               cyapa->gen == CYAPA_GEN3 ?
+                                       cyapa_pwr_cmd_to_sleep_time(pwr_cmd) :
+                                       sleep_time);
+               break;
+       }
+
+       return len;
+}
+
+static ssize_t cyapa_update_suspend_scanrate(struct device *dev,
+                                            struct device_attribute *attr,
+                                            const char *buf, size_t count)
+{
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+       u16 sleep_time;
+       int error;
+
+       error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+       if (error)
+               return error;
+
+       if (sysfs_streq(buf, BTN_ONLY_MODE_NAME)) {
+               cyapa->suspend_power_mode = PWR_MODE_BTN_ONLY;
+       } else if (sysfs_streq(buf, OFF_MODE_NAME)) {
+               cyapa->suspend_power_mode = PWR_MODE_OFF;
+       } else if (!kstrtou16(buf, 10, &sleep_time)) {
+               cyapa->suspend_sleep_time = max_t(u16, sleep_time, 1000);
+               cyapa->suspend_power_mode =
+                       cyapa_sleep_time_to_pwr_cmd(cyapa->suspend_sleep_time);
+       } else {
+               count = -EINVAL;
+       }
+
+       mutex_unlock(&cyapa->state_sync_lock);
+
+       return count;
+}
+
+static DEVICE_ATTR(suspend_scanrate_ms, S_IRUGO|S_IWUSR,
+                  cyapa_show_suspend_scanrate,
+                  cyapa_update_suspend_scanrate);
+
+static struct attribute *cyapa_power_wakeup_entries[] = {
+       &dev_attr_suspend_scanrate_ms.attr,
+       NULL,
+};
+
+static const struct attribute_group cyapa_power_wakeup_group = {
+       .name = power_group_name,
+       .attrs = cyapa_power_wakeup_entries,
+};
+
+static void cyapa_remove_power_wakeup_group(void *data)
+{
+       struct cyapa *cyapa = data;
+
+       sysfs_unmerge_group(&cyapa->client->dev.kobj,
+                               &cyapa_power_wakeup_group);
+}
+
+static int cyapa_prepare_wakeup_controls(struct cyapa *cyapa)
+{
+       struct i2c_client *client = cyapa->client;
+       struct device *dev = &client->dev;
+       int error;
+
+       if (device_can_wakeup(dev)) {
+               error = sysfs_merge_group(&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;
+
+       cyapa->runtime_suspend_power_mode = PWR_MODE_IDLE;
+       cyapa->runtime_suspend_sleep_time =
+               cyapa_pwr_cmd_to_sleep_time(cyapa->runtime_suspend_power_mode);
+
+       error = sysfs_merge_group(&dev->kobj, &cyapa_power_runtime_group);
+       if (error) {
+               dev_err(dev,
+                       "failed to create power runtime group: %d\n", error);
+               return error;
+       }
+
+       error = devm_add_action(dev, cyapa_remove_power_runtime_group, cyapa);
+       if (error) {
+               cyapa_remove_power_runtime_group(cyapa);
+               dev_err(dev,
+                       "failed to add power runtime cleanup action: %d\n",
+                       error);
+               return error;
+       }
+
+       /* runtime is enabled until device is operational and opened. */
+       pm_runtime_set_suspended(dev);
+       pm_runtime_use_autosuspend(dev);
+       pm_runtime_set_autosuspend_delay(dev, AUTOSUSPEND_DELAY);
+
+       return 0;
+}
+#else
+static inline int cyapa_start_runtime(struct cyapa *cyapa)
+{
+       return 0;
+}
+#endif /* CONFIG_PM */
+
+static ssize_t cyapa_show_fm_ver(struct device *dev,
+                                struct device_attribute *attr, char *buf)
+{
+       int error;
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+
+       error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+       if (error)
+               return error;
+       error = scnprintf(buf, PAGE_SIZE, "%d.%d\n", cyapa->fw_maj_ver,
+                        cyapa->fw_min_ver);
+       mutex_unlock(&cyapa->state_sync_lock);
+       return error;
+}
+
+static ssize_t cyapa_show_product_id(struct device *dev,
+                                    struct device_attribute *attr, char *buf)
+{
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+       int size;
+       int error;
+
+       error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+       if (error)
+               return error;
+       size = scnprintf(buf, PAGE_SIZE, "%s\n", cyapa->product_id);
+       mutex_unlock(&cyapa->state_sync_lock);
+       return size;
+}
+
+static int cyapa_firmware(struct cyapa *cyapa, const char *fw_name)
+{
+       struct device *dev = &cyapa->client->dev;
+       const struct firmware *fw;
+       int error;
+
+       error = request_firmware(&fw, fw_name, dev);
+       if (error) {
+               dev_err(dev, "Could not load firmware from %s: %d\n",
+                       fw_name, error);
+               return error;
+       }
+
+       error = cyapa->ops->check_fw(cyapa, fw);
+       if (error) {
+               dev_err(dev, "Invalid CYAPA firmware image: %s\n",
+                               fw_name);
+               goto done;
+       }
+
+       /*
+        * Resume the potentially suspended device because doing FW
+        * update on a device not in the FULL mode has a chance to
+        * fail.
+        */
+       pm_runtime_get_sync(dev);
+
+       /* Require IRQ support for firmware update commands. */
+       cyapa_enable_irq_for_cmd(cyapa);
+
+       error = cyapa->ops->bl_enter(cyapa);
+       if (error) {
+               dev_err(dev, "bl_enter failed, %d\n", error);
+               goto err_detect;
+       }
+
+       error = cyapa->ops->bl_activate(cyapa);
+       if (error) {
+               dev_err(dev, "bl_activate failed, %d\n", error);
+               goto err_detect;
+       }
+
+       error = cyapa->ops->bl_initiate(cyapa, fw);
+       if (error) {
+               dev_err(dev, "bl_initiate failed, %d\n", error);
+               goto err_detect;
+       }
+
+       error = cyapa->ops->update_fw(cyapa, fw);
+       if (error) {
+               dev_err(dev, "update_fw failed, %d\n", error);
+               goto err_detect;
+       }
+
+err_detect:
+       cyapa_disable_irq_for_cmd(cyapa);
+       pm_runtime_put_noidle(dev);
+
+done:
+       release_firmware(fw);
+       return error;
+}
+
+static ssize_t cyapa_update_fw_store(struct device *dev,
+                                    struct device_attribute *attr,
+                                    const char *buf, size_t count)
+{
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+       char fw_name[NAME_MAX];
+       int ret, error;
+
+       if (count >= NAME_MAX) {
+               dev_err(dev, "File name too long\n");
+               return -EINVAL;
+       }
+
+       memcpy(fw_name, buf, count);
+       if (fw_name[count - 1] == '\n')
+               fw_name[count - 1] = '\0';
+       else
+               fw_name[count] = '\0';
+
+       if (cyapa->input) {
+               /*
+                * Force the input device to be registered after the firmware
+                * image is updated, so if the corresponding parameters updated
+                * in the new firmware image can taken effect immediately.
+                */
+               input_unregister_device(cyapa->input);
+               cyapa->input = NULL;
+       }
+
+       error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+       if (error) {
+               /*
+                * Whatever, do reinitialize to try to recover TP state to
+                * previous state just as it entered fw update entrance.
+                */
+               cyapa_reinitialize(cyapa);
+               return error;
+       }
+
+       error = cyapa_firmware(cyapa, fw_name);
+       if (error)
+               dev_err(dev, "firmware update failed: %d\n", error);
+       else
+               dev_dbg(dev, "firmware update successfully done.\n");
+
+       /*
+        * 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,
+                      const struct i2c_device_id *dev_id)
+{
+       struct device *dev = &client->dev;
        struct cyapa *cyapa;
        u8 adapter_func;
+       union i2c_smbus_data dummy;
        int error;
 
        adapter_func = cyapa_check_adapter_functionality(client);
@@ -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 (file)
index 0000000..adc9ed5
--- /dev/null
@@ -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 (file)
index 0000000..77e9d70
--- /dev/null
@@ -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 (file)
index 0000000..ddf5393
--- /dev/null
@@ -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,
+};
index 8af34ff..9118a18 100644 (file)
@@ -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];
index 2e83862..e100c1b 100644 (file)
@@ -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;
index 0cb2be4..7ce8bfe 100644 (file)
@@ -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;
        }
index 97d4937..029941f 100644 (file)
@@ -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.
index 359bf85..06a2bcd 100644 (file)
@@ -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;
        }
index f4d657e..fca38ba 100644 (file)
@@ -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
  * 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 */
index 498650c..71870a9 100644 (file)
@@ -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
index 95a3a6e..68469fe 100644 (file)
@@ -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",
index f4cf664..c2ff137 100644 (file)
@@ -96,6 +96,7 @@ enum psmouse_type {
        PSMOUSE_FSP,
        PSMOUSE_SYNAPTICS_RELATIVE,
        PSMOUSE_CYPRESS,
+       PSMOUSE_FOCALTECH,
        PSMOUSE_AUTO            /* This one should always be last */
 };
 
index 23e26e0..7e705ee 100644 (file)
@@ -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);
index 1bd01f2..6faf9bb 100644 (file)
 /* amount to fuzz position data when touchpad reports reduced filtering */
 #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)
  */
@@ -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;
index bc2d474..77833d7 100644 (file)
@@ -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
index 815d874..c600089 100644 (file)
@@ -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
index 8d9ba0c..94ab494 100644 (file)
@@ -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 (file)
index 0000000..04b96fe
--- /dev/null
@@ -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");
index 8580456..3a7f3a4 100644 (file)
@@ -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:
index a510f7e..926c58e 100644 (file)
 #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>
index 4fb5537..2c21071 100644 (file)
@@ -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++) {
index 28a0674..b93a28b 100644 (file)
@@ -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 */
 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);
index 004f134..191a1b8 100644 (file)
@@ -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");
index f583ff6..d7188de 100644 (file)
@@ -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);
 
index e2e7005..3f4e994 100644 (file)
@@ -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)
 /* 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 (file)
index 0000000..5658e58
--- /dev/null
@@ -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 */