Linux Input/HID development
 help / color / mirror / Atom feed
* Bisected two-finger scrolling regression on Lenovo Y50 (Re: [PATCH 1/5] Input: elantech - use elantech_report_trackpoint for hardware v4 too)
From: Anders Kaseorg @ 2014-11-20  6:58 UTC (permalink / raw)
  To: Ulrik De Bie, Dmitry Torokhov; +Cc: linux-input, Hans de Goede, David Herrmann
In-Reply-To: <1409407846-15449-2-git-send-email-ulrik.debie-os@e2big.org>

On 08/30/2014 10:10 AM, Ulrik De Bie wrote:
> The Fujitsu H730 has hardware v4 with a trackpoint. This enables
> the elantech_report_trackpoint for v4.

Kernel 3.18-rc5 has made two-finger scrolling unusably glitchy on my 
Lenovo Y50-70 Touch, and caused an associated kernel warning.  I 
bisected the regression to this commit (caeb0d37).

This may be the same as https://bugzilla.redhat.com/1165390, although 
that only reports the warning, not the glitchy scrolling.

Anders

psmouse serio1: elantech: assuming hardware version 4 (with firmware 
version 0x495f01)
psmouse serio1: elantech: Synaptics capabilities query result 0x70, 
0x15, 0x0e.
input: ETPS/2 Elantech Touchpad as 
/devices/platform/i8042/serio1/input/input6

[…]

------------[ cut here ]------------
WARNING: CPU: 0 PID: 0 at drivers/input/mouse/elantech.c:433 
elantech_report_trackpoint.isra.5+0x199/0x1b0 [psmouse]()
psmouse serio1: elantech: Unexpected trackpoint message
Modules linked in: xt_CHECKSUM iptable_mangle ipt_MASQUERADE iptable_nat 
nf_nat_ipv4 nf_nat nf_conntrack_ipv4 nf_defrag_ipv4 xt_conntrack 
nf_conntrack ipt_REJECT xt_tcpudp iptable_filter ip_tables x_tables 
bridge stp llc ctr ccm binfmt_misc bbswitch(OE) arc4 nls_iso8859_1 
iwlmvm snd_hda_codec_hdmi mac80211 snd_hda_codec_realtek 
snd_hda_codec_generic snd_hda_intel snd_hda_controller snd_hda_codec 
intel_rapl uvcvideo snd_hwdep x86_pkg_temp_thermal videobuf2_vmalloc 
intel_powerclamp snd_pcm videobuf2_memops videobuf2_core coretemp 
v4l2_common snd_seq_midi videodev kvm_intel snd_seq_midi_event iwlwifi 
media kvm joydev btusb snd_rawmidi serio_raw bluetooth cfg80211 
hid_multitouch rtsx_pci_ms memstick snd_seq snd_seq_device snd_timer 
ideapad_laptop mei_me sparse_keymap mei snd ie31200_edac
  lpc_ich mac_hid edac_core soundcore shpchp parport_pc ppdev lp parport 
autofs4 btrfs xor raid6_pq dm_crypt hid_generic usbhid hid 
rtsx_pci_sdmmc crct10dif_pclmul crc32_pclmul ghash_clmulni_intel 
aesni_intel i915 aes_x86_64 lrw gf128mul i2c_algo_bit glue_helper 
ablk_helper drm_kms_helper cryptd psmouse ahci r8169 drm rtsx_pci 
libahci mii wmi video
CPU: 0 PID: 0 Comm: swapper/0 Tainted: G           OE  3.17.0+ #2
Hardware name: LENOVO 20349/Lenovo Y50-70 Touch, BIOS 9ECN30WW(V1.13) 
07/16/2014
  0000000000000009 ffff88045f203cb0 ffffffff8277a436 ffff88045f203cf8
  ffff88045f203ce8 ffffffff8207144d ffff88044352ea00 ffff880448b044f0
  ffff880035da6400 0000000000000002 ffff88044352ea00 ffff88045f203d48
Call Trace:
  <IRQ>  [<ffffffff8277a436>] dump_stack+0x45/0x56
  [<ffffffff8207144d>] warn_slowpath_common+0x7d/0xa0
  [<ffffffff820714bc>] warn_slowpath_fmt+0x4c/0x50
  [<ffffffffc0185309>] elantech_report_trackpoint.isra.5+0x199/0x1b0 
[psmouse]
  [<ffffffffc0185af8>] elantech_process_byte+0x6e8/0xf90 [psmouse]
  [<ffffffffc017bd45>] psmouse_handle_byte+0x15/0x150 [psmouse]
  [<ffffffffc017bf1f>] psmouse_interrupt+0x9f/0x360 [psmouse]
  [<ffffffff825c2d06>] serio_interrupt+0x46/0x90
  [<ffffffff825c44f7>] i8042_interrupt+0x197/0x3a0
  [<ffffffff820c8477>] handle_irq_event_percpu+0x77/0x1a0
  [<ffffffff820c85dd>] handle_irq_event+0x3d/0x60
  [<ffffffff820cb316>] handle_edge_irq+0x66/0x130
  [<ffffffff8201576e>] handle_irq+0x1e/0x40
  [<ffffffff8278522d>] do_IRQ+0x4d/0xe0
  [<ffffffff827830ad>] common_interrupt+0x6d/0x6d
  <EOI>  [<ffffffff82614965>] ? cpuidle_enter_state+0x65/0x160
  [<ffffffff82614b47>] cpuidle_enter+0x17/0x20
  [<ffffffff820b1dc7>] cpu_startup_entry+0x387/0x3c0
  [<ffffffff8276f5f7>] rest_init+0x77/0x80
  [<ffffffff82d45f9b>] start_kernel+0x461/0x46e
  [<ffffffff82d45120>] ? early_idt_handlers+0x120/0x120
  [<ffffffff82d454d7>] x86_64_start_reservations+0x2a/0x2c
  [<ffffffff82d4561c>] x86_64_start_kernel+0x143/0x152
---[ end trace b67972f4dffebd79 ]---
psmouse serio1: elantech: PS/2 packet [
  0x44 , 0x06 , 0xe3 , 0x26 , 0x01 , 0xf3 ]
psmouse serio1: elantech: PS/2 packet [
  0x44 , 0x09 , 0xbd , 0x26 , 0x00 , 0xe5 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x0b , 0x90 , 0x36 , 0xfb , 0xb7 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x0c , 0x84 , 0x36 , 0xfe , 0x9e ]
psmouse serio1: elantech: PS/2 packet [
  0x44 , 0x0c , 0x94 , 0x26 , 0x00 , 0x99 ]
psmouse serio1: elantech: PS/2 packet [
  0x44 , 0x05 , 0x98 , 0x26 , 0x00 , 0x99 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x0d , 0xba , 0x36 , 0xfd , 0xb3 ]
psmouse serio1: elantech: PS/2 packet [
  0x50 , 0x00 , 0x19 , 0x26 , 0x00 , 0x1c ]
psmouse serio1: elantech: PS/2 packet [
  0x50 , 0x04 , 0x1b , 0x26 , 0x00 , 0x19 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x11 , 0x46 , 0x36 , 0x01 , 0x53 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x09 , 0x48 , 0x36 , 0x0d , 0x39 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x0b , 0x30 , 0x36 , 0x06 , 0x30 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x0d , 0x2e , 0x36 , 0x06 , 0x2c ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x0a , 0x1c , 0x36 , 0x01 , 0x18 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x07 , 0x17 , 0x36 , 0x06 , 0x11 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x01 , 0x0b , 0x36 , 0x04 , 0x07 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x02 , 0x03 , 0x36 , 0x02 , 0x02 ]
psmouse serio1: elantech: PS/2 packet [
  0x44 , 0x03 , 0xfe , 0x26 , 0x02 , 0xfd ]
psmouse serio1: elantech: PS/2 packet [
  0x44 , 0x00 , 0xf8 , 0x26 , 0x01 , 0xfb ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x00 , 0x02 , 0x36 , 0x00 , 0x01 ]
psmouse serio1: elantech: PS/2 packet [
  0x44 , 0x01 , 0x09 , 0x26 , 0xff , 0x02 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x07 , 0x18 , 0x36 , 0x00 , 0x07 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x0e , 0x3d , 0x36 , 0x0b , 0x29 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x1d , 0x6f , 0x36 , 0x19 , 0x5f ]
psmouse serio1: elantech: PS/2 packet [
  0x50 , 0x06 , 0x19 , 0x26 , 0x05 , 0x1a ]
psmouse serio1: elantech: PS/2 packet [
  0x50 , 0x07 , 0x1a , 0x26 , 0x06 , 0x18 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x26 , 0x6d , 0x36 , 0x1e , 0x6f ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x27 , 0x5f , 0x36 , 0x1f , 0x62 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x18 , 0x36 , 0x36 , 0x1c , 0x38 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x19 , 0x2a , 0x36 , 0x1b , 0x27 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x13 , 0x19 , 0x36 , 0x11 , 0x14 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x09 , 0x06 , 0x36 , 0x09 , 0x06 ]
psmouse serio1: elantech: PS/2 packet [
  0x44 , 0x01 , 0xfd , 0x26 , 0x07 , 0xf5 ]
psmouse serio1: elantech: PS/2 packet [
  0x44 , 0x00 , 0xf9 , 0x26 , 0x0a , 0xf1 ]
psmouse serio1: elantech: PS/2 packet [
  0x44 , 0x03 , 0xd1 , 0x26 , 0x07 , 0xd0 ]
psmouse serio1: elantech: PS/2 packet [
  0x44 , 0x06 , 0xe5 , 0x26 , 0x08 , 0xd6 ]
psmouse serio1: elantech: PS/2 packet [
  0x44 , 0x04 , 0xf6 , 0x26 , 0x06 , 0xe8 ]
psmouse serio1: elantech: PS/2 packet [
  0x44 , 0x02 , 0xfd , 0x26 , 0x04 , 0xf1 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x11 , 0x1d , 0x36 , 0x0e , 0x0b ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x16 , 0x2b , 0x36 , 0x18 , 0x18 ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x24 , 0x5a , 0x36 , 0x25 , 0x3c ]
psmouse serio1: elantech: PS/2 packet [
  0x40 , 0x28 , 0x53 , 0x36 , 0x1d , 0x4e ]

> Reported-by: Stefan Valouch <stefan@valouch.com>
> Tested-by: Stefan Valouch <stefan@valouch.com>
> Tested-by: Alfredo Gemma <alfredo.gemma@gmail.com>
> Signed-off-by: Ulrik De Bie <ulrik.debie-os@e2big.org>
> ---
>   drivers/input/mouse/elantech.c | 16 ++++++++++++++--
>   1 file changed, 14 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/input/mouse/elantech.c b/drivers/input/mouse/elantech.c
> index da51738..f0a55b4d 100644
> --- a/drivers/input/mouse/elantech.c
> +++ b/drivers/input/mouse/elantech.c
> @@ -792,6 +792,9 @@ static int elantech_packet_check_v4(struct psmouse *psmouse)
>   	unsigned char packet_type = packet[3] & 0x03;
>   	bool sanity_check;
>
> +	if ((packet[3]&0x0f) == 0x06)
> +		return PACKET_TRACKPOINT;
> +
>   	/*
>   	 * Sanity check based on the constant bits of a packet.
>   	 * The constant bits change depending on the value of
> @@ -877,10 +880,19 @@ static psmouse_ret_t elantech_process_byte(struct psmouse *psmouse)
>
>   	case 4:
>   		packet_type = elantech_packet_check_v4(psmouse);
> -		if (packet_type == PACKET_UNKNOWN)
> +		switch (packet_type) {
> +		case PACKET_UNKNOWN:
>   			return PSMOUSE_BAD_DATA;
>
> -		elantech_report_absolute_v4(psmouse, packet_type);
> +		case PACKET_TRACKPOINT:
> +			elantech_report_trackpoint(psmouse, packet_type);
> +			break;
> +
> +		default:
> +			elantech_report_absolute_v4(psmouse, packet_type);
> +			break;
> +		}
> +
>   		break;
>   	}
>
>

--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: Bisected two-finger scrolling regression on Lenovo Y50 (Re: [PATCH 1/5] Input: elantech - use elantech_report_trackpoint for hardware v4 too)
From: Dmitry Torokhov @ 2014-11-20  7:42 UTC (permalink / raw)
  To: Anders Kaseorg
  Cc: Ulrik De Bie, linux-input, Hans de Goede, David Herrmann,
	Marcus Overhagen
In-Reply-To: <546D9120.1040703@mit.edu>

Hi Anders,

On Thu, Nov 20, 2014 at 01:58:40AM -0500, Anders Kaseorg wrote:
> On 08/30/2014 10:10 AM, Ulrik De Bie wrote:
> >The Fujitsu H730 has hardware v4 with a trackpoint. This enables
> >the elantech_report_trackpoint for v4.
> 
> Kernel 3.18-rc5 has made two-finger scrolling unusably glitchy on my
> Lenovo Y50-70 Touch, and caused an associated kernel warning.  I
> bisected the regression to this commit (caeb0d37).
> 
> This may be the same as https://bugzilla.redhat.com/1165390,
> although that only reports the warning, not the glitchy scrolling.
> 

Thank you for your report. Please try the patch below.

Markus, I put you as tested-by since you tried basically the same patch
earlier.

-- 
Dmitry


Input: elantech - trust firmware about trackpoint presence

From: Dmitry Torokhov <dmitry.torokhov@gmail.com>

Only try to parse data as coming from trackpoint if firmware told us that
trackpoint is present.

Fixes commit caeb0d37fa3e387eb0dd22e5d497523c002033d1

Reported-and-tested-by: Marcus Overhagen <marcus.overhagen@gmail.com>
Reported-by: Anders Kaseorg <andersk@mit.edu>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/input/mouse/elantech.c |   10 +---------
 1 file changed, 1 insertion(+), 9 deletions(-)

diff --git a/drivers/input/mouse/elantech.c b/drivers/input/mouse/elantech.c
index 3fcb6b3..f2b9780 100644
--- a/drivers/input/mouse/elantech.c
+++ b/drivers/input/mouse/elantech.c
@@ -428,14 +428,6 @@ static void elantech_report_trackpoint(struct psmouse *psmouse,
 	int x, y;
 	u32 t;
 
-	if (dev_WARN_ONCE(&psmouse->ps2dev.serio->dev,
-			  !tp_dev,
-			  psmouse_fmt("Unexpected trackpoint message\n"))) {
-		if (etd->debug == 1)
-			elantech_packet_dump(psmouse);
-		return;
-	}
-
 	t = get_unaligned_le32(&packet[0]);
 
 	switch (t & ~7U) {
@@ -793,7 +785,7 @@ static int elantech_packet_check_v4(struct psmouse *psmouse)
 	unsigned char packet_type = packet[3] & 0x03;
 	bool sanity_check;
 
-	if ((packet[3] & 0x0f) == 0x06)
+	if (etd->tp_dev && (packet[3] & 0x0f) == 0x06)
 		return PACKET_TRACKPOINT;
 
 	/*

^ permalink raw reply related

* Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
From: Dmitry Torokhov @ 2014-11-20  8:11 UTC (permalink / raw)
  To: scott
  Cc: linux-kernel, linux-input, Vincent Wang, Jeff Chuang,
	Benson Leung, Benjamin Tissoires, Henrik Rydberg, David Herrmann
In-Reply-To: <1388146515-19481-1-git-send-email-scott.liu@emc.com.tw>

Hi Scott,

On Fri, Dec 27, 2013 at 08:15:15PM +0800, scott wrote:
> From: Scott Liu <scott.liu@emc.com.tw>
> 
> This patch is for Elan eKTH Touchscreen product, I2C adpater module.
> 

As you know I have been making some changes to the driver in Chromium
tree, here is the latest version of the driver. I'd like to get it in
3.19 (pending community review of course).

Thanks! 

-- 
Dmitry

Input: add support for Elan eKTH I2C touchscreens

From: Scott Liu <scott.liu@emc.com.tw>

This driver supports Elan eKTH I2C touchscreen controllers. Note that these
are using custom protocol, as opposed to other Elan parts that use
HID-over-I2C and are supported by the standard HID-multitouch driver.

Signed-off-by: Scott Liu <scott.liu@emc.com.tw>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 .../devicetree/bindings/input/elants_i2c.txt       |   33 +
 drivers/input/touchscreen/Kconfig                  |   12 
 drivers/input/touchscreen/Makefile                 |    1 
 drivers/input/touchscreen/elants_i2c.c             | 1271 ++++++++++++++++++++
 4 files changed, 1317 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/elants_i2c.txt
 create mode 100644 drivers/input/touchscreen/elants_i2c.c

diff --git a/Documentation/devicetree/bindings/input/elants_i2c.txt b/Documentation/devicetree/bindings/input/elants_i2c.txt
new file mode 100644
index 0000000..a765232
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/elants_i2c.txt
@@ -0,0 +1,33 @@
+Elantech I2C Touchscreen
+
+Required properties:
+- compatible: must be "elan,ekth3500".
+- reg: I2C address of the chip.
+- interrupt-parent: a phandle for the interrupt controller (see interrupt
+  binding[0]).
+- interrupts: interrupt to which the chip is connected (see interrupt
+  binding[0]).
+
+Optional properties:
+- wakeup-source: touchscreen can be used as a wakeup source.
+- pinctrl-names: should be "default" (see pinctrl binding [1]).
+- pinctrl-0: a phandle pointing to the pin settings for the device (see
+  pinctrl binding [1]).
+
+[0]: Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
+[1]: Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
+
+Example:
+	&i2c1 {
+		/* ... */
+
+		touchscreen@10 {
+			compatible = "elan,ekth3500";
+			reg = <0x10>;
+			interrupt-parent = <&gpio4>;
+			interrupts = <0x0 IRQ_TYPE_EDGE_FALLING>;
+			wakeup-source;
+		};
+
+		/* ... */
+	};
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 568a020..5891752 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -347,6 +347,18 @@ config TOUCHSCREEN_GUNZE
 	  To compile this driver as a module, choose M here: the
 	  module will be called gunze.
 
+config TOUCHSCREEN_ELAN
+	tristate "Elan eKTH I2C touchscreen"
+	depends on I2C
+	help
+	  Say Y here if you have an Elan eKTH I2C touchscreen
+	  connected to your system.
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called elants_i2c.
+
 config TOUCHSCREEN_ELO
 	tristate "Elo serial touchscreens"
 	select SERIO
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index dab4a56..0242fea 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_TOUCHSCREEN_EDT_FT5X06)	+= edt-ft5x06.o
 obj-$(CONFIG_TOUCHSCREEN_HAMPSHIRE)	+= hampshire.o
 obj-$(CONFIG_TOUCHSCREEN_GUNZE)		+= gunze.o
 obj-$(CONFIG_TOUCHSCREEN_EETI)		+= eeti_ts.o
+obj-$(CONFIG_TOUCHSCREEN_ELAN)		+= elants_i2c.o
 obj-$(CONFIG_TOUCHSCREEN_ELO)		+= elo.o
 obj-$(CONFIG_TOUCHSCREEN_EGALAX)	+= egalax_ts.o
 obj-$(CONFIG_TOUCHSCREEN_FUJITSU)	+= fujitsu_ts.o
diff --git a/drivers/input/touchscreen/elants_i2c.c b/drivers/input/touchscreen/elants_i2c.c
new file mode 100644
index 0000000..a510f7e
--- /dev/null
+++ b/drivers/input/touchscreen/elants_i2c.c
@@ -0,0 +1,1271 @@
+/*
+ * Elan Microelectronics touch panels with I2C interface
+ *
+ * Copyright (C) 2014 Elan Microelectronics Corporation.
+ * Scott Liu <scott.liu@emc.com.tw>
+ *
+ * This code is partly based on hid-multitouch.c:
+ *
+ *  Copyright (c) 2010-2012 Stephane Chatty <chatty@enac.fr>
+ *  Copyright (c) 2010-2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ *  Copyright (c) 2010-2012 Ecole Nationale de l'Aviation Civile, France
+ *
+ *
+ * This code is partly based on i2c-hid.c:
+ *
+ * Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France
+ * Copyright (c) 2012 Red Hat, Inc
+ */
+
+/*
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ */
+
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/async.h>
+#include <linux/i2c.h>
+#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>
+#include <asm/unaligned.h>
+
+/* Device, Driver information */
+#define DEVICE_NAME	"elants_i2c"
+#define DRV_VERSION	"1.0.9"
+
+/* Convert from rows or columns into resolution */
+#define ELAN_TS_RESOLUTION(n, m)   (((n) - 1) * (m))
+
+/* FW header data */
+#define HEADER_SIZE		4
+#define FW_HDR_TYPE		0
+#define FW_HDR_COUNT		1
+#define FW_HDR_LENGTH		2
+
+/* Buffer mode Queue Header information */
+#define QUEUE_HEADER_SINGLE	0x62
+#define QUEUE_HEADER_NORMAL	0X63
+#define QUEUE_HEADER_WAIT	0x64
+
+/* Command header definition */
+#define CMD_HEADER_WRITE	0x54
+#define CMD_HEADER_READ		0x53
+#define CMD_HEADER_6B_READ	0x5B
+#define CMD_HEADER_RESP		0x52
+#define CMD_HEADER_6B_RESP	0x9B
+#define CMD_HEADER_HELLO	0x55
+#define CMD_HEADER_REK		0x66
+
+/* FW position data */
+#define PACKET_SIZE		55
+#define MAX_CONTACT_NUM		10
+#define FW_POS_HEADER		0
+#define FW_POS_STATE		1
+#define FW_POS_TOTAL		2
+#define FW_POS_XY		3
+#define FW_POS_CHECKSUM		34
+#define FW_POS_WIDTH		35
+#define FW_POS_PRESSURE		45
+
+#define HEADER_REPORT_10_FINGER	0x62
+
+/* Header (4 bytes) plus 3 fill 10-finger packets */
+#define MAX_PACKET_SIZE		169
+
+#define BOOT_TIME_DELAY_MS	50
+
+/* FW read command, 0x53 0x?? 0x0, 0x01 */
+#define E_ELAN_INFO_FW_VER	0x00
+#define E_ELAN_INFO_BC_VER	0x10
+#define E_ELAN_INFO_TEST_VER	0xE0
+#define E_ELAN_INFO_FW_ID	0xF0
+#define E_INFO_OSR		0xD6
+#define E_INFO_PHY_SCAN		0xD7
+#define E_INFO_PHY_DRIVER	0xD8
+
+#define MAX_RETRIES		3
+#define MAX_FW_UPDATE_RETRIES	30
+
+#define ELAN_FW_PAGESIZE	132
+#define ELAN_FW_FILENAME	"elants_i2c.bin"
+
+/* calibration timeout definition */
+#define ELAN_CALI_TIMEOUT_MSEC	10000
+
+enum elants_state {
+	ELAN_STATE_NORMAL,
+	ELAN_WAIT_QUEUE_HEADER,
+	ELAN_WAIT_RECALIBRATION,
+};
+
+enum elants_iap_mode {
+	ELAN_IAP_OPERATIONAL,
+	ELAN_IAP_RECOVERY,
+};
+
+/* struct elants_data - represents state of Elan touchscreen device */
+struct elants_data {
+	struct i2c_client *client;
+	struct input_dev *input;
+
+	u16 fw_version;
+	u8 test_version;
+	u8 solution_version;
+	u8 bc_version;
+	u8 iap_version;
+	u16 hw_version;
+	unsigned int x_res;	/* resolution in units/mm */
+	unsigned int y_res;
+	unsigned int x_max;
+	unsigned int y_max;
+
+	enum elants_state state;
+	enum elants_iap_mode iap_mode;
+
+	/* Guards against concurrent access to the device via sysfs */
+	struct mutex sysfs_mutex;
+
+	u8 cmd_resp[HEADER_SIZE];
+	struct completion cmd_done;
+
+	u8 buf[MAX_PACKET_SIZE];
+
+	bool wake_irq_enabled;
+};
+
+static int elants_i2c_send(struct i2c_client *client,
+			   const void *data, size_t size)
+{
+	int ret;
+
+	ret = i2c_master_send(client, data, size);
+	if (ret == size)
+		return 0;
+
+	if (ret >= 0)
+		ret = -EIO;
+
+	dev_err(&client->dev, "%s failed (%*ph): %d\n",
+		__func__, (int)size, data, ret);
+
+	return ret;
+}
+
+static int elants_i2c_read(struct i2c_client *client, void *data, size_t size)
+{
+	int ret;
+
+	ret = i2c_master_recv(client, data, size);
+	if (ret == size)
+		return 0;
+
+	if (ret >= 0)
+		ret = -EIO;
+
+	dev_err(&client->dev, "%s failed: %d\n", __func__, ret);
+
+	return ret;
+}
+
+static int elants_i2c_execute_command(struct i2c_client *client,
+				      const u8 *cmd, size_t cmd_size,
+				      u8 *resp, size_t resp_size)
+{
+	struct i2c_msg msgs[2];
+	int ret;
+	u8 expected_response;
+
+	switch (cmd[0]) {
+	case CMD_HEADER_READ:
+		expected_response = CMD_HEADER_RESP;
+		break;
+
+	case CMD_HEADER_6B_READ:
+		expected_response = CMD_HEADER_6B_RESP;
+		break;
+
+	default:
+		dev_err(&client->dev, "%s: invalid command %*ph\n",
+			__func__, (int)cmd_size, cmd);
+		return -EINVAL;
+	}
+
+	msgs[0].addr = client->addr;
+	msgs[0].flags = client->flags & I2C_M_TEN;
+	msgs[0].len = cmd_size;
+	msgs[0].buf = (u8 *)cmd;
+
+	msgs[1].addr = client->addr;
+	msgs[1].flags = client->flags & I2C_M_TEN;
+	msgs[1].flags |= I2C_M_RD;
+	msgs[1].len = resp_size;
+	msgs[1].buf = resp;
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret < 0)
+		return ret;
+
+	if (ret != ARRAY_SIZE(msgs) || resp[FW_HDR_TYPE] != expected_response)
+		return -EIO;
+
+	return 0;
+}
+
+static int elants_i2c_calibrate(struct elants_data *ts)
+{
+	struct i2c_client *client = ts->client;
+	int ret, error;
+	static const u8 w_flashkey[] = { 0x54, 0xC0, 0xE1, 0x5A };
+	static const u8 rek[] = { 0x54, 0x29, 0x00, 0x01 };
+	static const u8 rek_resp[] = { CMD_HEADER_REK, 0x66, 0x66, 0x66 };
+
+	disable_irq(client->irq);
+
+	ts->state = ELAN_WAIT_RECALIBRATION;
+	reinit_completion(&ts->cmd_done);
+
+	elants_i2c_send(client, w_flashkey, sizeof(w_flashkey));
+	elants_i2c_send(client, rek, sizeof(rek));
+
+	enable_irq(client->irq);
+
+	ret = wait_for_completion_interruptible_timeout(&ts->cmd_done,
+				msecs_to_jiffies(ELAN_CALI_TIMEOUT_MSEC));
+
+	ts->state = ELAN_STATE_NORMAL;
+
+	if (ret <= 0) {
+		error = ret < 0 ? ret : -ETIMEDOUT;
+		dev_err(&client->dev,
+			"error while waiting for calibration to complete: %d\n",
+			error);
+		return error;
+	}
+
+	if (memcmp(rek_resp, ts->cmd_resp, sizeof(rek_resp))) {
+		dev_err(&client->dev,
+			"unexpected calibration response: %*ph\n",
+			(int)sizeof(ts->cmd_resp), ts->cmd_resp);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int elants_i2c_sw_reset(struct i2c_client *client)
+{
+	const u8 soft_rst_cmd[] = { 0x77, 0x77, 0x77, 0x77 };
+	int error;
+
+	error = elants_i2c_send(client, soft_rst_cmd,
+				sizeof(soft_rst_cmd));
+	if (error) {
+		dev_err(&client->dev, "software reset failed: %d\n", error);
+		return error;
+	}
+
+	/*
+	 * We should wait at least 10 msec (but no more than 40) before
+	 * sending fastboot or IAP command to the device.
+	 */
+	msleep(30);
+
+	return 0;
+}
+
+static u16 elants_i2c_parse_version(u8 *buf)
+{
+	return get_unaligned_be32(buf) >> 4;
+}
+
+static int elants_i2c_query_fw_id(struct elants_data *ts)
+{
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_FW_ID, 0x00, 0x01 };
+	u8 resp[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+						   resp, sizeof(resp));
+		if (!error) {
+			ts->hw_version = elants_i2c_parse_version(resp);
+			if (ts->hw_version != 0xffff)
+				return 0;
+		}
+
+		dev_dbg(&client->dev, "read fw id error=%d, buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+	}
+
+	dev_err(&client->dev,
+		"Failed to read fw id or fw id is invalid\n");
+
+	return -EINVAL;
+}
+
+static int elants_i2c_query_fw_version(struct elants_data *ts)
+{
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_FW_VER, 0x00, 0x01 };
+	u8 resp[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+						   resp, sizeof(resp));
+		if (!error) {
+			ts->fw_version = elants_i2c_parse_version(resp);
+			if (ts->fw_version != 0x0000 &&
+			    ts->fw_version != 0xffff)
+				return 0;
+		}
+
+		dev_dbg(&client->dev, "read fw version error=%d, buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+	}
+
+	dev_err(&client->dev,
+		"Failed to read fw version or fw version is invalid\n");
+
+	return -EINVAL;
+}
+
+static int elants_i2c_query_test_version(struct elants_data *ts)
+{
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	u16 version;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_TEST_VER, 0x00, 0x01 };
+	u8 resp[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+						   resp, sizeof(resp));
+		if (!error) {
+			version = elants_i2c_parse_version(resp);
+			ts->test_version = version >> 8;
+			ts->solution_version = version & 0xff;
+
+			return 0;
+		}
+
+		dev_dbg(&client->dev,
+			"read test version error rc=%d, buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+	}
+
+	dev_err(&client->dev, "Failed to read test version\n");
+
+	return -EINVAL;
+}
+
+static int elants_i2c_query_bc_version(struct elants_data *ts)
+{
+	struct i2c_client *client = ts->client;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_BC_VER, 0x00, 0x01 };
+	u8 resp[HEADER_SIZE];
+	u16 version;
+	int error;
+
+	error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev,
+			"read BC version error=%d, buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+		return error;
+	}
+
+	version = elants_i2c_parse_version(resp);
+	ts->bc_version = version >> 8;
+	ts->iap_version = version & 0xff;
+
+	return 0;
+}
+
+static int elants_i2c_query_ts_info(struct elants_data *ts)
+{
+	struct i2c_client *client = ts->client;
+	int error;
+	u8 resp[17];
+	u16 phy_x, phy_y, rows, cols, osr;
+	const u8 get_resolution_cmd[] = {
+		CMD_HEADER_6B_READ, 0x00, 0x00, 0x00, 0x00, 0x00
+	};
+	const u8 get_osr_cmd[] = {
+		CMD_HEADER_READ, E_INFO_OSR, 0x00, 0x01
+	};
+	const u8 get_physical_scan_cmd[] = {
+		CMD_HEADER_READ, E_INFO_PHY_SCAN, 0x00, 0x01
+	};
+	const u8 get_physical_drive_cmd[] = {
+		CMD_HEADER_READ, E_INFO_PHY_DRIVER, 0x00, 0x01
+	};
+
+	/* Get trace number */
+	error = elants_i2c_execute_command(client,
+					   get_resolution_cmd,
+					   sizeof(get_resolution_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get resolution command failed: %d\n",
+			error);
+		return error;
+	}
+
+	rows = resp[2] + resp[6] + resp[10];
+	cols = resp[3] + resp[7] + resp[11];
+
+	/* Process mm_to_pixel information */
+	error = elants_i2c_execute_command(client,
+					   get_osr_cmd, sizeof(get_osr_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get osr command failed: %d\n",
+			error);
+		return error;
+	}
+
+	osr = resp[3];
+
+	error = elants_i2c_execute_command(client,
+					   get_physical_scan_cmd,
+					   sizeof(get_physical_scan_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get physical scan command failed: %d\n",
+			error);
+		return error;
+	}
+
+	phy_x = get_unaligned_be16(&resp[2]);
+
+	error = elants_i2c_execute_command(client,
+					   get_physical_drive_cmd,
+					   sizeof(get_physical_drive_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get physical drive command failed: %d\n",
+			error);
+		return error;
+	}
+
+	phy_y = get_unaligned_be16(&resp[2]);
+
+	dev_dbg(&client->dev, "phy_x=%d, phy_y=%d\n", phy_x, phy_y);
+
+	if (rows == 0 || cols == 0 || osr == 0) {
+		dev_warn(&client->dev,
+			 "invalid trace number data: %d, %d, %d\n",
+			 rows, cols, osr);
+	} else {
+		/* translate trace number to TS resolution */
+		ts->x_max = ELAN_TS_RESOLUTION(rows, osr);
+		ts->x_res = DIV_ROUND_CLOSEST(ts->x_max, phy_x);
+		ts->y_max = ELAN_TS_RESOLUTION(cols, osr);
+		ts->y_res = DIV_ROUND_CLOSEST(ts->y_max, phy_y);
+	}
+
+	return 0;
+}
+
+static int elants_i2c_fastboot(struct i2c_client *client)
+{
+	const u8 boot_cmd[] = { 0x4D, 0x61, 0x69, 0x6E };
+	int error;
+
+	error = elants_i2c_send(client, boot_cmd, sizeof(boot_cmd));
+	if (error) {
+		dev_err(&client->dev, "boot failed: %d\n", error);
+		return error;
+	}
+
+	dev_dbg(&client->dev, "boot success -- 0x%x\n", client->addr);
+	return 0;
+}
+
+static int elants_i2c_initialize(struct elants_data *ts)
+{
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	const u8 hello_packet[] = { 0x55, 0x55, 0x55, 0x55 };
+	const u8 recov_packet[] = { 0x55, 0x55, 0x80, 0x80 };
+	u8 buf[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_sw_reset(client);
+		if (error) {
+			/* Continue initializing if it's the last try */
+			if (retry_cnt < MAX_RETRIES - 1)
+				continue;
+		}
+
+		error = elants_i2c_fastboot(client);
+		if (error) {
+			/* Continue initializing if it's the last try */
+			if (retry_cnt < MAX_RETRIES - 1)
+				continue;
+		}
+
+		/* Wait for Hello packet */
+		msleep(BOOT_TIME_DELAY_MS);
+
+		error = elants_i2c_read(client, buf, sizeof(buf));
+		if (error) {
+			dev_err(&client->dev,
+				"failed to read 'hello' packet: %d\n", error);
+		} else if (!memcmp(buf, hello_packet, sizeof(hello_packet))) {
+			ts->iap_mode = ELAN_IAP_OPERATIONAL;
+			break;
+		} else if (!memcmp(buf, recov_packet, sizeof(recov_packet))) {
+			/*
+			 * Setting error code will mark device
+			 * in recovery mode below.
+			 */
+			error = -EIO;
+			break;
+		} else {
+			error = -EINVAL;
+			dev_err(&client->dev,
+				"invalid 'hello' packet: %*ph\n",
+				(int)sizeof(buf), buf);
+		}
+	}
+
+	if (!error)
+		error = elants_i2c_query_fw_id(ts);
+	if (!error)
+		error = elants_i2c_query_fw_version(ts);
+
+	if (error) {
+		ts->iap_mode = ELAN_IAP_RECOVERY;
+	} else {
+		elants_i2c_query_test_version(ts);
+		elants_i2c_query_bc_version(ts);
+		elants_i2c_query_ts_info(ts);
+	}
+
+	return 0;
+}
+
+/*
+ * Firmware update interface.
+ */
+
+static int elants_i2c_fw_write_page(struct i2c_client *client,
+				    const void *page)
+{
+	const u8 ack_ok[] = { 0xaa, 0xaa };
+	u8 buf[2];
+	int retry;
+	int error;
+
+	for (retry = 0; retry < MAX_FW_UPDATE_RETRIES; retry++) {
+		error = elants_i2c_send(client, page, ELAN_FW_PAGESIZE);
+		if (error) {
+			dev_err(&client->dev,
+				"IAP Write Page failed: %d\n", error);
+			continue;
+		}
+
+		error = elants_i2c_read(client, buf, 2);
+		if (error) {
+			dev_err(&client->dev,
+				"IAP Ack read failed: %d\n", error);
+			return error;
+		}
+
+		if (!memcmp(buf, ack_ok, sizeof(ack_ok)))
+			return 0;
+
+		error = -EIO;
+		dev_err(&client->dev,
+			"IAP Get Ack Error [%02x:%02x]\n",
+			buf[0], buf[1]);
+	}
+
+	return error;
+}
+
+static int elants_i2c_do_update_firmware(struct i2c_client *client,
+					 const struct firmware *fw,
+					 bool force)
+{
+	const u8 enter_iap[] = { 0x45, 0x49, 0x41, 0x50 };
+	const u8 enter_iap2[] = { 0x54, 0x00, 0x12, 0x34 };
+	const u8 iap_ack[] = { 0x55, 0xaa, 0x33, 0xcc };
+	u8 buf[HEADER_SIZE];
+	u16 send_id;
+	int page, n_fw_pages;
+	int error;
+
+	/* Recovery mode detection! */
+	if (force) {
+		dev_dbg(&client->dev, "Recovery mode procedure\n");
+		error = elants_i2c_send(client, enter_iap2, sizeof(enter_iap2));
+	} else {
+		/* Start IAP Procedure */
+		dev_dbg(&client->dev, "Normal IAP procedure\n");
+		elants_i2c_sw_reset(client);
+
+		error = elants_i2c_send(client, enter_iap, sizeof(enter_iap));
+	}
+
+	if (error) {
+		dev_err(&client->dev, "failed to enter IAP mode: %d\n", error);
+		return error;
+	}
+
+	msleep(20);
+
+	/* check IAP state */
+	error = elants_i2c_read(client, buf, 4);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to read IAP acknowledgement: %d\n",
+			error);
+		return error;
+	}
+
+	if (memcmp(buf, iap_ack, sizeof(iap_ack))) {
+		dev_err(&client->dev,
+			"failed to enter IAP: %*ph (expected %*ph)\n",
+			(int)sizeof(buf), buf, (int)sizeof(iap_ack), iap_ack);
+		return -EIO;
+	}
+
+	dev_info(&client->dev, "successfully entered IAP mode");
+
+	send_id = client->addr;
+	error = elants_i2c_send(client, &send_id, 1);
+	if (error) {
+		dev_err(&client->dev, "sending dummy byte failed: %d\n",
+			error);
+		return error;
+	}
+
+	/* Clear the last page of Master */
+	error = elants_i2c_send(client, fw->data, ELAN_FW_PAGESIZE);
+	if (error) {
+		dev_err(&client->dev, "clearing of the last page failed: %d\n",
+			error);
+		return error;
+	}
+
+	error = elants_i2c_read(client, buf, 2);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to read ACK for clearing the last page: %d\n",
+			error);
+		return error;
+	}
+
+	n_fw_pages = fw->size / ELAN_FW_PAGESIZE;
+	dev_dbg(&client->dev, "IAP Pages = %d\n", n_fw_pages);
+
+	for (page = 0; page < n_fw_pages; page++) {
+		error = elants_i2c_fw_write_page(client,
+					fw->data + page * ELAN_FW_PAGESIZE);
+		if (error) {
+			dev_err(&client->dev,
+				"failed to write FW page %d: %d\n",
+				page, error);
+			return error;
+		}
+	}
+
+	/* Old iap needs to wait 200ms for WDT and rest is for hello packets */
+	msleep(300);
+
+	dev_info(&client->dev, "firmware update completed\n");
+	return 0;
+}
+
+static int elants_i2c_fw_update(struct elants_data *ts)
+{
+	struct i2c_client *client = ts->client;
+	const struct firmware *fw;
+	int error;
+
+	error = request_firmware(&fw, ELAN_FW_FILENAME, &client->dev);
+	if (error) {
+		dev_err(&client->dev, "failed to request firmware %s: %d\n",
+			ELAN_FW_FILENAME, error);
+		return error;
+	}
+
+	if (fw->size % ELAN_FW_PAGESIZE) {
+		dev_err(&client->dev, "invalid firmware length: %zu\n",
+			fw->size);
+		error = -EINVAL;
+		goto out;
+	}
+
+	disable_irq(client->irq);
+
+	error = elants_i2c_do_update_firmware(client, fw,
+					ts->iap_mode == ELAN_IAP_RECOVERY);
+	if (error) {
+		dev_err(&client->dev, "firmware update failed: %d\n", error);
+		ts->iap_mode = ELAN_IAP_RECOVERY;
+		goto out_enable_irq;
+	}
+
+	error = elants_i2c_initialize(ts);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to initialize device after firmware update: %d\n",
+			error);
+		ts->iap_mode = ELAN_IAP_RECOVERY;
+		goto out_enable_irq;
+	}
+
+	ts->iap_mode = ELAN_IAP_OPERATIONAL;
+
+out_enable_irq:
+	ts->state = ELAN_STATE_NORMAL;
+	enable_irq(client->irq);
+	msleep(100);
+
+	if (!error)
+		elants_i2c_calibrate(ts);
+out:
+	release_firmware(fw);
+	return error;
+}
+
+/*
+ * Event reporting.
+ */
+
+static void elants_i2c_mt_event(struct elants_data *ts, u8 *buf)
+{
+	struct input_dev *input = ts->input;
+	unsigned int n_fingers;
+	u16 finger_state;
+	int i;
+
+	n_fingers = buf[FW_POS_STATE + 1] & 0x0f;
+	finger_state = ((buf[FW_POS_STATE + 1] & 0x30) << 4) |
+			buf[FW_POS_STATE];
+
+	dev_dbg(&ts->client->dev,
+		"n_fingers: %u, state: %04x\n",  n_fingers, finger_state);
+
+	for (i = 0; i < MAX_CONTACT_NUM && n_fingers; i++) {
+		if (finger_state & 1) {
+			unsigned int x, y, p, w;
+			u8 *pos;
+
+			pos = &buf[FW_POS_XY + i * 3];
+			x = (((u16)pos[0] & 0xf0) << 4) | pos[1];
+			y = (((u16)pos[0] & 0x0f) << 8) | pos[2];
+			p = buf[FW_POS_PRESSURE + i];
+			w = buf[FW_POS_WIDTH + i];
+
+			dev_dbg(&ts->client->dev, "i=%d x=%d y=%d p=%d w=%d\n",
+				i, x, y, p, w);
+
+			input_mt_slot(input, i);
+			input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+			input_event(input, EV_ABS, ABS_MT_POSITION_X, x);
+			input_event(input, EV_ABS, ABS_MT_POSITION_Y, y);
+			input_event(input, EV_ABS, ABS_MT_PRESSURE, p);
+			input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, w);
+
+			n_fingers--;
+		}
+
+		finger_state >>= 1;
+	}
+
+	input_mt_sync_frame(input);
+	input_sync(input);
+}
+
+static u8 elants_i2c_calculate_checksum(u8 *buf)
+{
+	u8 checksum = 0;
+	u8 i;
+
+	for (i = 0; i < FW_POS_CHECKSUM; i++)
+		checksum += buf[i];
+
+	return checksum;
+}
+
+static void elants_i2c_event(struct elants_data *ts, u8 *buf)
+{
+	u8 checksum = elants_i2c_calculate_checksum(buf);
+
+	if (unlikely(buf[FW_POS_CHECKSUM] != checksum))
+		dev_warn(&ts->client->dev,
+			 "%s: invalid checksum for packet %02x: %02x vs. %02x\n",
+			 __func__, buf[FW_POS_HEADER],
+			 checksum, buf[FW_POS_CHECKSUM]);
+	else if (unlikely(buf[FW_POS_HEADER] != HEADER_REPORT_10_FINGER))
+		dev_warn(&ts->client->dev,
+			 "%s: unknown packet type: %02x\n",
+			 __func__, buf[FW_POS_HEADER]);
+	else
+		elants_i2c_mt_event(ts, buf);
+}
+
+static irqreturn_t elants_i2c_irq(int irq, void *_dev)
+{
+	const u8 wait_packet[] = { 0x64, 0x64, 0x64, 0x64 };
+	struct elants_data *ts = _dev;
+	struct i2c_client *client = ts->client;
+	int report_count, report_len;
+	int i;
+	int len;
+
+	len = i2c_master_recv(client, ts->buf, sizeof(ts->buf));
+	if (len < 0) {
+		dev_err(&client->dev, "%s: failed to read data: %d\n",
+			__func__, len);
+		goto out;
+	}
+
+	dev_dbg(&client->dev, "%s: packet %*ph\n",
+		__func__, HEADER_SIZE, ts->buf);
+
+	switch (ts->state) {
+	case ELAN_WAIT_RECALIBRATION:
+		if (ts->buf[FW_HDR_TYPE] == CMD_HEADER_REK) {
+			memcpy(ts->cmd_resp, ts->buf, sizeof(ts->cmd_resp));
+			complete(&ts->cmd_done);
+			ts->state = ELAN_STATE_NORMAL;
+		}
+		break;
+
+	case ELAN_WAIT_QUEUE_HEADER:
+		if (ts->buf[FW_HDR_TYPE] != QUEUE_HEADER_NORMAL)
+			break;
+
+		ts->state = ELAN_STATE_NORMAL;
+		/* fall through */
+
+	case ELAN_STATE_NORMAL:
+
+		switch (ts->buf[FW_HDR_TYPE]) {
+		case CMD_HEADER_HELLO:
+		case CMD_HEADER_RESP:
+		case CMD_HEADER_REK:
+			break;
+
+		case QUEUE_HEADER_WAIT:
+			if (memcmp(ts->buf, wait_packet, sizeof(wait_packet))) {
+				dev_err(&client->dev,
+					"invalid wait packet %*ph\n",
+					HEADER_SIZE, ts->buf);
+			} else {
+				ts->state = ELAN_WAIT_QUEUE_HEADER;
+				udelay(30);
+			}
+			break;
+
+		case QUEUE_HEADER_SINGLE:
+			elants_i2c_event(ts, &ts->buf[HEADER_SIZE]);
+			break;
+
+		case QUEUE_HEADER_NORMAL:
+			report_count = ts->buf[FW_HDR_COUNT];
+			if (report_count > 3) {
+				dev_err(&client->dev,
+					"too large report count: %*ph\n",
+					HEADER_SIZE, ts->buf);
+				break;
+			}
+
+			report_len = ts->buf[FW_HDR_LENGTH] / report_count;
+			if (report_len != PACKET_SIZE) {
+				dev_err(&client->dev,
+					"mismatching report length: %*ph\n",
+					HEADER_SIZE, ts->buf);
+				break;
+			}
+
+			for (i = 0; i < report_count; i++) {
+				u8 *buf = ts->buf + HEADER_SIZE +
+							i * PACKET_SIZE;
+				elants_i2c_event(ts, buf);
+			}
+			break;
+
+		default:
+			dev_err(&client->dev, "unknown packet %*ph\n",
+				HEADER_SIZE, ts->buf);
+			break;
+		}
+		break;
+	}
+
+out:
+	return IRQ_HANDLED;
+}
+
+/*
+ * sysfs interface
+ */
+static ssize_t calibrate_store(struct device *dev,
+			       struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	int error;
+
+	error = mutex_lock_interruptible(&ts->sysfs_mutex);
+	if (error)
+		return error;
+
+	error = elants_i2c_calibrate(ts);
+
+	mutex_unlock(&ts->sysfs_mutex);
+	return error ?: count;
+}
+
+static ssize_t write_update_fw(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	int error;
+
+	error = mutex_lock_interruptible(&ts->sysfs_mutex);
+	if (error)
+		return error;
+
+	error = elants_i2c_fw_update(ts);
+	dev_dbg(dev, "firmware update result: %d\n", error);
+
+	mutex_unlock(&ts->sysfs_mutex);
+	return error ?: count;
+}
+
+static ssize_t show_iap_mode(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+
+	return sprintf(buf, "%s\n",
+		       ts->iap_mode == ELAN_IAP_OPERATIONAL ?
+				"Normal" : "Recovery");
+}
+
+static DEVICE_ATTR(calibrate, S_IWUSR, NULL, calibrate_store);
+static DEVICE_ATTR(iap_mode, S_IRUGO, show_iap_mode, NULL);
+static DEVICE_ATTR(update_fw, S_IWUSR, NULL, write_update_fw);
+
+struct elants_version_attribute {
+	struct device_attribute dattr;
+	size_t field_offset;
+	size_t field_size;
+};
+
+#define __ELANTS_FIELD_SIZE(_field)					\
+	sizeof(((struct elants_data *)NULL)->_field)
+#define __ELANTS_VERIFY_SIZE(_field)					\
+	(BUILD_BUG_ON_ZERO(__ELANTS_FIELD_SIZE(_field) > 2) +		\
+	 __ELANTS_FIELD_SIZE(_field))
+#define ELANTS_VERSION_ATTR(_field)					\
+	struct elants_version_attribute elants_ver_attr_##_field = {	\
+		.dattr = __ATTR(_field, S_IRUGO,			\
+				elants_version_attribute_show, NULL),	\
+		.field_offset = offsetof(struct elants_data, _field),	\
+		.field_size = __ELANTS_VERIFY_SIZE(_field),		\
+	}
+
+static ssize_t elants_version_attribute_show(struct device *dev,
+					     struct device_attribute *dattr,
+					     char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	struct elants_version_attribute *attr =
+		container_of(dattr, struct elants_version_attribute, dattr);
+	u8 *field = (u8 *)((char *)ts + attr->field_offset);
+	unsigned int fmt_size;
+	unsigned int val;
+
+	if (attr->field_size == 1) {
+		val = *field;
+		fmt_size = 2; /* 2 HEX digits */
+	} else {
+		val = *(u16 *)field;
+		fmt_size = 4; /* 4 HEX digits */
+	}
+
+	return sprintf(buf, "%0*x\n", fmt_size, val);
+}
+
+static ELANTS_VERSION_ATTR(fw_version);
+static ELANTS_VERSION_ATTR(hw_version);
+static ELANTS_VERSION_ATTR(test_version);
+static ELANTS_VERSION_ATTR(solution_version);
+static ELANTS_VERSION_ATTR(bc_version);
+static ELANTS_VERSION_ATTR(iap_version);
+
+static struct attribute *elants_attributes[] = {
+	&dev_attr_calibrate.attr,
+	&dev_attr_update_fw.attr,
+	&dev_attr_iap_mode.attr,
+
+	&elants_ver_attr_fw_version.dattr.attr,
+	&elants_ver_attr_hw_version.dattr.attr,
+	&elants_ver_attr_test_version.dattr.attr,
+	&elants_ver_attr_solution_version.dattr.attr,
+	&elants_ver_attr_bc_version.dattr.attr,
+	&elants_ver_attr_iap_version.dattr.attr,
+	NULL
+};
+
+static struct attribute_group elants_attribute_group = {
+	.attrs = elants_attributes,
+};
+
+static void elants_i2c_remove_sysfs_group(void *_data)
+{
+	struct elants_data *ts = _data;
+
+	sysfs_remove_group(&ts->client->dev.kobj, &elants_attribute_group);
+}
+
+static int elants_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	union i2c_smbus_data dummy;
+	struct elants_data *ts;
+	unsigned long irqflags;
+	int error;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		dev_err(&client->dev,
+			"%s: i2c check functionality error\n", DEVICE_NAME);
+		return -ENXIO;
+	}
+
+	/* 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) {
+		dev_err(&client->dev, "nothing at this address\n");
+		return -ENXIO;
+	}
+
+	ts = devm_kzalloc(&client->dev, sizeof(struct elants_data), GFP_KERNEL);
+	if (!ts)
+		return -ENOMEM;
+
+	mutex_init(&ts->sysfs_mutex);
+	init_completion(&ts->cmd_done);
+
+	ts->client = client;
+	i2c_set_clientdata(client, ts);
+
+	error = elants_i2c_initialize(ts);
+	if (error) {
+		dev_err(&client->dev, "failed to initialize: %d\n", error);
+		return error;
+	}
+
+	ts->input = devm_input_allocate_device(&client->dev);
+	if (!ts->input) {
+		dev_err(&client->dev, "Failed to allocate input device\n");
+		return -ENOMEM;
+	}
+
+	ts->input->name = "Elan Touchscreen";
+	ts->input->id.bustype = BUS_I2C;
+
+	__set_bit(BTN_TOUCH, ts->input->keybit);
+	__set_bit(EV_ABS, ts->input->evbit);
+	__set_bit(EV_KEY, ts->input->evbit);
+
+	/* Single touch input params setup */
+	input_set_abs_params(ts->input, ABS_X, 0, ts->x_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_Y, 0, ts->y_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_PRESSURE, 0, 255, 0, 0);
+	input_abs_set_res(ts->input, ABS_X, ts->x_res);
+	input_abs_set_res(ts->input, ABS_Y, ts->y_res);
+
+	/* Multitouch input params setup */
+	error = input_mt_init_slots(ts->input, MAX_CONTACT_NUM,
+				    INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to initialize MT slots: %d\n", error);
+		return error;
+	}
+
+	input_set_abs_params(ts->input, ABS_MT_POSITION_X, 0, ts->x_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_MT_POSITION_Y, 0, ts->y_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+	input_set_abs_params(ts->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
+	input_abs_set_res(ts->input, ABS_MT_POSITION_X, ts->x_res);
+	input_abs_set_res(ts->input, ABS_MT_POSITION_Y, ts->y_res);
+
+	input_set_drvdata(ts->input, ts);
+
+	error = input_register_device(ts->input);
+	if (error) {
+		dev_err(&client->dev,
+			"unable to register input device: %d\n", error);
+		return error;
+	}
+
+	/*
+	 * Systems using device tree should set up interrupt via DTS,
+	 * the rest will use the default falling edge interrupts.
+	 */
+	irqflags = client->dev.of_node ? 0 : IRQF_TRIGGER_FALLING;
+
+	error = devm_request_threaded_irq(&client->dev, client->irq,
+					  NULL, elants_i2c_irq,
+					  irqflags | IRQF_ONESHOT,
+					  client->name, ts);
+	if (error) {
+		dev_err(&client->dev, "Failed to register interrupt\n");
+		return error;
+	}
+
+	/*
+	 * Systems using device tree should set up wakeup via DTS,
+	 * the rest will configure device as wakeup source by default.
+	 */
+	if (!client->dev.of_node)
+		device_init_wakeup(&client->dev, true);
+
+	error = sysfs_create_group(&client->dev.kobj, &elants_attribute_group);
+	if (error) {
+		dev_err(&client->dev, "failed to create sysfs attributes: %d\n",
+			error);
+		return error;
+	}
+
+	error = devm_add_action(&client->dev,
+				elants_i2c_remove_sysfs_group, ts);
+	if (error) {
+		elants_i2c_remove_sysfs_group(ts);
+		dev_err(&client->dev,
+			"Failed to add sysfs cleanup action: %d\n",
+			error);
+		return error;
+	}
+
+	return 0;
+}
+
+static int __maybe_unused elants_i2c_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	const u8 set_sleep_cmd[] = { 0x54, 0x50, 0x00, 0x01 };
+	int retry_cnt;
+	int error;
+
+	/* Command not support in IAP recovery mode */
+	if (ts->iap_mode != ELAN_IAP_OPERATIONAL)
+		return -EBUSY;
+
+	disable_irq(client->irq);
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_send(client, set_sleep_cmd,
+					sizeof(set_sleep_cmd));
+		if (!error)
+			break;
+
+		dev_err(&client->dev, "suspend command failed: %d\n", error);
+	}
+
+	if (device_may_wakeup(dev))
+		ts->wake_irq_enabled = (enable_irq_wake(client->irq) == 0);
+
+	return 0;
+}
+
+static int __maybe_unused elants_i2c_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	const u8 set_active_cmd[] = { 0x54, 0x58, 0x00, 0x01 };
+	int retry_cnt;
+	int error;
+
+	if (device_may_wakeup(dev) && ts->wake_irq_enabled)
+		disable_irq_wake(client->irq);
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_send(client, set_active_cmd,
+					sizeof(set_active_cmd));
+		if (!error)
+			break;
+
+		dev_err(&client->dev, "resume command failed: %d\n", error);
+	}
+
+	ts->state = ELAN_STATE_NORMAL;
+	enable_irq(client->irq);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(elants_i2c_pm_ops,
+			 elants_i2c_suspend, elants_i2c_resume);
+
+static const struct i2c_device_id elants_i2c_id[] = {
+	{ DEVICE_NAME, 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, elants_i2c_id);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id elants_acpi_id[] = {
+	{ "ELAN0001", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(acpi, elants_acpi_id);
+#endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id elants_of_match[] = {
+	{ .compatible = "elan,ekth3500" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, elants_of_match);
+#endif
+
+static struct i2c_driver elants_i2c_driver = {
+	.probe = elants_i2c_probe,
+	.id_table = elants_i2c_id,
+	.driver = {
+		.name = DEVICE_NAME,
+		.owner = THIS_MODULE,
+		.pm = &elants_i2c_pm_ops,
+		.acpi_match_table = ACPI_PTR(elants_acpi_id),
+		.of_match_table = of_match_ptr(elants_of_match),
+	},
+};
+module_i2c_driver(elants_i2c_driver);
+
+MODULE_AUTHOR("Scott Liu <scott.liu@emc.com.tw>");
+MODULE_DESCRIPTION("Elan I2c Touchscreen driver");
+MODULE_VERSION(DRV_VERSION);
+MODULE_LICENSE("GPL");

^ permalink raw reply related

* Re: Bisected two-finger scrolling regression on Lenovo Y50 (Re: [PATCH 1/5] Input: elantech - use elantech_report_trackpoint for hardware v4 too)
From: Anders Kaseorg @ 2014-11-20  8:21 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Ulrik De Bie, linux-input, Hans de Goede, David Herrmann,
	Marcus Overhagen
In-Reply-To: <20141120074207.GA30434@dtor-ws>

On Thu, 20 Nov 2014, Dmitry Torokhov wrote:
> Thank you for your report. Please try the patch below.
> 
> […]
> 
> Input: elantech - trust firmware about trackpoint presence
> 
> From: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> 
> Only try to parse data as coming from trackpoint if firmware told us that
> trackpoint is present.
> 
> Fixes commit caeb0d37fa3e387eb0dd22e5d497523c002033d1
> 
> Reported-and-tested-by: Marcus Overhagen <marcus.overhagen@gmail.com>
> Reported-by: Anders Kaseorg <andersk@mit.edu>
> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>

That fixes it for me too, thanks.

Anders
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* RE: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
From: ELAN 劉嘉駿 @ 2014-11-20  8:55 UTC (permalink / raw)
  To: 'Dmitry Torokhov'
  Cc: linux-kernel, linux-input, 'Vincent Wang',
	'Jeff Chuang', 'Benson Leung',
	'Benjamin Tissoires', 'Henrik Rydberg',
	'David Herrmann'
In-Reply-To: <20141120081113.GA37534@dtor-ws>

Hi Dmitry:
	Good to hear this, we will add more feature based on this version.

Thanks.
--
Scott


-----Original Message-----
From: Dmitry Torokhov [mailto:dmitry.torokhov@gmail.com] 
Sent: Thursday, November 20, 2014 4:11 PM
To: scott
Cc: linux-kernel@vger.kernel.org; linux-input@vger.kernel.org; Vincent Wang;
Jeff Chuang; Benson Leung; Benjamin Tissoires; Henrik Rydberg; David
Herrmann
Subject: Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support

Hi Scott,

On Fri, Dec 27, 2013 at 08:15:15PM +0800, scott wrote:
> From: Scott Liu <scott.liu@emc.com.tw>
> 
> This patch is for Elan eKTH Touchscreen product, I2C adpater module.
> 

As you know I have been making some changes to the driver in Chromium tree,
here is the latest version of the driver. I'd like to get it in
3.19 (pending community review of course).

Thanks! 

--
Dmitry

Input: add support for Elan eKTH I2C touchscreens

From: Scott Liu <scott.liu@emc.com.tw>

This driver supports Elan eKTH I2C touchscreen controllers. Note that these
are using custom protocol, as opposed to other Elan parts that use
HID-over-I2C and are supported by the standard HID-multitouch driver.

Signed-off-by: Scott Liu <scott.liu@emc.com.tw>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 .../devicetree/bindings/input/elants_i2c.txt       |   33 +
 drivers/input/touchscreen/Kconfig                  |   12 
 drivers/input/touchscreen/Makefile                 |    1 
 drivers/input/touchscreen/elants_i2c.c             | 1271
++++++++++++++++++++
 4 files changed, 1317 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/elants_i2c.txt
 create mode 100644 drivers/input/touchscreen/elants_i2c.c

diff --git a/Documentation/devicetree/bindings/input/elants_i2c.txt
b/Documentation/devicetree/bindings/input/elants_i2c.txt
new file mode 100644
index 0000000..a765232
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/elants_i2c.txt
@@ -0,0 +1,33 @@
+Elantech I2C Touchscreen
+
+Required properties:
+- compatible: must be "elan,ekth3500".
+- reg: I2C address of the chip.
+- interrupt-parent: a phandle for the interrupt controller (see 
+interrupt
+  binding[0]).
+- interrupts: interrupt to which the chip is connected (see interrupt
+  binding[0]).
+
+Optional properties:
+- wakeup-source: touchscreen can be used as a wakeup source.
+- pinctrl-names: should be "default" (see pinctrl binding [1]).
+- pinctrl-0: a phandle pointing to the pin settings for the device (see
+  pinctrl binding [1]).
+
+[0]: 
+Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
+[1]: Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
+
+Example:
+	&i2c1 {
+		/* ... */
+
+		touchscreen@10 {
+			compatible = "elan,ekth3500";
+			reg = <0x10>;
+			interrupt-parent = <&gpio4>;
+			interrupts = <0x0 IRQ_TYPE_EDGE_FALLING>;
+			wakeup-source;
+		};
+
+		/* ... */
+	};
diff --git a/drivers/input/touchscreen/Kconfig
b/drivers/input/touchscreen/Kconfig
index 568a020..5891752 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -347,6 +347,18 @@ config TOUCHSCREEN_GUNZE
 	  To compile this driver as a module, choose M here: the
 	  module will be called gunze.
 
+config TOUCHSCREEN_ELAN
+	tristate "Elan eKTH I2C touchscreen"
+	depends on I2C
+	help
+	  Say Y here if you have an Elan eKTH I2C touchscreen
+	  connected to your system.
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called elants_i2c.
+
 config TOUCHSCREEN_ELO
 	tristate "Elo serial touchscreens"
 	select SERIO
diff --git a/drivers/input/touchscreen/Makefile
b/drivers/input/touchscreen/Makefile
index dab4a56..0242fea 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_TOUCHSCREEN_EDT_FT5X06)	+= edt-ft5x06.o
 obj-$(CONFIG_TOUCHSCREEN_HAMPSHIRE)	+= hampshire.o
 obj-$(CONFIG_TOUCHSCREEN_GUNZE)		+= gunze.o
 obj-$(CONFIG_TOUCHSCREEN_EETI)		+= eeti_ts.o
+obj-$(CONFIG_TOUCHSCREEN_ELAN)		+= elants_i2c.o
 obj-$(CONFIG_TOUCHSCREEN_ELO)		+= elo.o
 obj-$(CONFIG_TOUCHSCREEN_EGALAX)	+= egalax_ts.o
 obj-$(CONFIG_TOUCHSCREEN_FUJITSU)	+= fujitsu_ts.o
diff --git a/drivers/input/touchscreen/elants_i2c.c
b/drivers/input/touchscreen/elants_i2c.c
new file mode 100644
index 0000000..a510f7e
--- /dev/null
+++ b/drivers/input/touchscreen/elants_i2c.c
@@ -0,0 +1,1271 @@
+/*
+ * Elan Microelectronics touch panels with I2C interface
+ *
+ * Copyright (C) 2014 Elan Microelectronics Corporation.
+ * Scott Liu <scott.liu@emc.com.tw>
+ *
+ * This code is partly based on hid-multitouch.c:
+ *
+ *  Copyright (c) 2010-2012 Stephane Chatty <chatty@enac.fr>
+ *  Copyright (c) 2010-2012 Benjamin Tissoires 
+<benjamin.tissoires@gmail.com>
+ *  Copyright (c) 2010-2012 Ecole Nationale de l'Aviation Civile, 
+France
+ *
+ *
+ * This code is partly based on i2c-hid.c:
+ *
+ * Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France
+ * Copyright (c) 2012 Red Hat, Inc
+ */
+
+/*
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ */
+
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/async.h>
+#include <linux/i2c.h>
+#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>
+#include <asm/unaligned.h>
+
+/* Device, Driver information */
+#define DEVICE_NAME	"elants_i2c"
+#define DRV_VERSION	"1.0.9"
+
+/* Convert from rows or columns into resolution */
+#define ELAN_TS_RESOLUTION(n, m)   (((n) - 1) * (m))
+
+/* FW header data */
+#define HEADER_SIZE		4
+#define FW_HDR_TYPE		0
+#define FW_HDR_COUNT		1
+#define FW_HDR_LENGTH		2
+
+/* Buffer mode Queue Header information */
+#define QUEUE_HEADER_SINGLE	0x62
+#define QUEUE_HEADER_NORMAL	0X63
+#define QUEUE_HEADER_WAIT	0x64
+
+/* Command header definition */
+#define CMD_HEADER_WRITE	0x54
+#define CMD_HEADER_READ		0x53
+#define CMD_HEADER_6B_READ	0x5B
+#define CMD_HEADER_RESP		0x52
+#define CMD_HEADER_6B_RESP	0x9B
+#define CMD_HEADER_HELLO	0x55
+#define CMD_HEADER_REK		0x66
+
+/* FW position data */
+#define PACKET_SIZE		55
+#define MAX_CONTACT_NUM		10
+#define FW_POS_HEADER		0
+#define FW_POS_STATE		1
+#define FW_POS_TOTAL		2
+#define FW_POS_XY		3
+#define FW_POS_CHECKSUM		34
+#define FW_POS_WIDTH		35
+#define FW_POS_PRESSURE		45
+
+#define HEADER_REPORT_10_FINGER	0x62
+
+/* Header (4 bytes) plus 3 fill 10-finger packets */
+#define MAX_PACKET_SIZE		169
+
+#define BOOT_TIME_DELAY_MS	50
+
+/* FW read command, 0x53 0x?? 0x0, 0x01 */
+#define E_ELAN_INFO_FW_VER	0x00
+#define E_ELAN_INFO_BC_VER	0x10
+#define E_ELAN_INFO_TEST_VER	0xE0
+#define E_ELAN_INFO_FW_ID	0xF0
+#define E_INFO_OSR		0xD6
+#define E_INFO_PHY_SCAN		0xD7
+#define E_INFO_PHY_DRIVER	0xD8
+
+#define MAX_RETRIES		3
+#define MAX_FW_UPDATE_RETRIES	30
+
+#define ELAN_FW_PAGESIZE	132
+#define ELAN_FW_FILENAME	"elants_i2c.bin"
+
+/* calibration timeout definition */
+#define ELAN_CALI_TIMEOUT_MSEC	10000
+
+enum elants_state {
+	ELAN_STATE_NORMAL,
+	ELAN_WAIT_QUEUE_HEADER,
+	ELAN_WAIT_RECALIBRATION,
+};
+
+enum elants_iap_mode {
+	ELAN_IAP_OPERATIONAL,
+	ELAN_IAP_RECOVERY,
+};
+
+/* struct elants_data - represents state of Elan touchscreen device */ 
+struct elants_data {
+	struct i2c_client *client;
+	struct input_dev *input;
+
+	u16 fw_version;
+	u8 test_version;
+	u8 solution_version;
+	u8 bc_version;
+	u8 iap_version;
+	u16 hw_version;
+	unsigned int x_res;	/* resolution in units/mm */
+	unsigned int y_res;
+	unsigned int x_max;
+	unsigned int y_max;
+
+	enum elants_state state;
+	enum elants_iap_mode iap_mode;
+
+	/* Guards against concurrent access to the device via sysfs */
+	struct mutex sysfs_mutex;
+
+	u8 cmd_resp[HEADER_SIZE];
+	struct completion cmd_done;
+
+	u8 buf[MAX_PACKET_SIZE];
+
+	bool wake_irq_enabled;
+};
+
+static int elants_i2c_send(struct i2c_client *client,
+			   const void *data, size_t size)
+{
+	int ret;
+
+	ret = i2c_master_send(client, data, size);
+	if (ret == size)
+		return 0;
+
+	if (ret >= 0)
+		ret = -EIO;
+
+	dev_err(&client->dev, "%s failed (%*ph): %d\n",
+		__func__, (int)size, data, ret);
+
+	return ret;
+}
+
+static int elants_i2c_read(struct i2c_client *client, void *data, 
+size_t size) {
+	int ret;
+
+	ret = i2c_master_recv(client, data, size);
+	if (ret == size)
+		return 0;
+
+	if (ret >= 0)
+		ret = -EIO;
+
+	dev_err(&client->dev, "%s failed: %d\n", __func__, ret);
+
+	return ret;
+}
+
+static int elants_i2c_execute_command(struct i2c_client *client,
+				      const u8 *cmd, size_t cmd_size,
+				      u8 *resp, size_t resp_size)
+{
+	struct i2c_msg msgs[2];
+	int ret;
+	u8 expected_response;
+
+	switch (cmd[0]) {
+	case CMD_HEADER_READ:
+		expected_response = CMD_HEADER_RESP;
+		break;
+
+	case CMD_HEADER_6B_READ:
+		expected_response = CMD_HEADER_6B_RESP;
+		break;
+
+	default:
+		dev_err(&client->dev, "%s: invalid command %*ph\n",
+			__func__, (int)cmd_size, cmd);
+		return -EINVAL;
+	}
+
+	msgs[0].addr = client->addr;
+	msgs[0].flags = client->flags & I2C_M_TEN;
+	msgs[0].len = cmd_size;
+	msgs[0].buf = (u8 *)cmd;
+
+	msgs[1].addr = client->addr;
+	msgs[1].flags = client->flags & I2C_M_TEN;
+	msgs[1].flags |= I2C_M_RD;
+	msgs[1].len = resp_size;
+	msgs[1].buf = resp;
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret < 0)
+		return ret;
+
+	if (ret != ARRAY_SIZE(msgs) || resp[FW_HDR_TYPE] !=
expected_response)
+		return -EIO;
+
+	return 0;
+}
+
+static int elants_i2c_calibrate(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int ret, error;
+	static const u8 w_flashkey[] = { 0x54, 0xC0, 0xE1, 0x5A };
+	static const u8 rek[] = { 0x54, 0x29, 0x00, 0x01 };
+	static const u8 rek_resp[] = { CMD_HEADER_REK, 0x66, 0x66, 0x66 };
+
+	disable_irq(client->irq);
+
+	ts->state = ELAN_WAIT_RECALIBRATION;
+	reinit_completion(&ts->cmd_done);
+
+	elants_i2c_send(client, w_flashkey, sizeof(w_flashkey));
+	elants_i2c_send(client, rek, sizeof(rek));
+
+	enable_irq(client->irq);
+
+	ret = wait_for_completion_interruptible_timeout(&ts->cmd_done,
+				msecs_to_jiffies(ELAN_CALI_TIMEOUT_MSEC));
+
+	ts->state = ELAN_STATE_NORMAL;
+
+	if (ret <= 0) {
+		error = ret < 0 ? ret : -ETIMEDOUT;
+		dev_err(&client->dev,
+			"error while waiting for calibration to complete:
%d\n",
+			error);
+		return error;
+	}
+
+	if (memcmp(rek_resp, ts->cmd_resp, sizeof(rek_resp))) {
+		dev_err(&client->dev,
+			"unexpected calibration response: %*ph\n",
+			(int)sizeof(ts->cmd_resp), ts->cmd_resp);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int elants_i2c_sw_reset(struct i2c_client *client) {
+	const u8 soft_rst_cmd[] = { 0x77, 0x77, 0x77, 0x77 };
+	int error;
+
+	error = elants_i2c_send(client, soft_rst_cmd,
+				sizeof(soft_rst_cmd));
+	if (error) {
+		dev_err(&client->dev, "software reset failed: %d\n", error);
+		return error;
+	}
+
+	/*
+	 * We should wait at least 10 msec (but no more than 40) before
+	 * sending fastboot or IAP command to the device.
+	 */
+	msleep(30);
+
+	return 0;
+}
+
+static u16 elants_i2c_parse_version(u8 *buf) {
+	return get_unaligned_be32(buf) >> 4;
+}
+
+static int elants_i2c_query_fw_id(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_FW_ID, 0x00, 0x01 };
+	u8 resp[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+						   resp, sizeof(resp));
+		if (!error) {
+			ts->hw_version = elants_i2c_parse_version(resp);
+			if (ts->hw_version != 0xffff)
+				return 0;
+		}
+
+		dev_dbg(&client->dev, "read fw id error=%d, buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+	}
+
+	dev_err(&client->dev,
+		"Failed to read fw id or fw id is invalid\n");
+
+	return -EINVAL;
+}
+
+static int elants_i2c_query_fw_version(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_FW_VER, 0x00, 0x01
};
+	u8 resp[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+						   resp, sizeof(resp));
+		if (!error) {
+			ts->fw_version = elants_i2c_parse_version(resp);
+			if (ts->fw_version != 0x0000 &&
+			    ts->fw_version != 0xffff)
+				return 0;
+		}
+
+		dev_dbg(&client->dev, "read fw version error=%d,
buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+	}
+
+	dev_err(&client->dev,
+		"Failed to read fw version or fw version is invalid\n");
+
+	return -EINVAL;
+}
+
+static int elants_i2c_query_test_version(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	u16 version;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_TEST_VER, 0x00, 0x01
};
+	u8 resp[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+						   resp, sizeof(resp));
+		if (!error) {
+			version = elants_i2c_parse_version(resp);
+			ts->test_version = version >> 8;
+			ts->solution_version = version & 0xff;
+
+			return 0;
+		}
+
+		dev_dbg(&client->dev,
+			"read test version error rc=%d, buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+	}
+
+	dev_err(&client->dev, "Failed to read test version\n");
+
+	return -EINVAL;
+}
+
+static int elants_i2c_query_bc_version(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_BC_VER, 0x00, 0x01
};
+	u8 resp[HEADER_SIZE];
+	u16 version;
+	int error;
+
+	error = elants_i2c_execute_command(client, cmd, sizeof(cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev,
+			"read BC version error=%d, buf=%*phC\n",
+			error, (int)sizeof(resp), resp);
+		return error;
+	}
+
+	version = elants_i2c_parse_version(resp);
+	ts->bc_version = version >> 8;
+	ts->iap_version = version & 0xff;
+
+	return 0;
+}
+
+static int elants_i2c_query_ts_info(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int error;
+	u8 resp[17];
+	u16 phy_x, phy_y, rows, cols, osr;
+	const u8 get_resolution_cmd[] = {
+		CMD_HEADER_6B_READ, 0x00, 0x00, 0x00, 0x00, 0x00
+	};
+	const u8 get_osr_cmd[] = {
+		CMD_HEADER_READ, E_INFO_OSR, 0x00, 0x01
+	};
+	const u8 get_physical_scan_cmd[] = {
+		CMD_HEADER_READ, E_INFO_PHY_SCAN, 0x00, 0x01
+	};
+	const u8 get_physical_drive_cmd[] = {
+		CMD_HEADER_READ, E_INFO_PHY_DRIVER, 0x00, 0x01
+	};
+
+	/* Get trace number */
+	error = elants_i2c_execute_command(client,
+					   get_resolution_cmd,
+					   sizeof(get_resolution_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get resolution command failed: %d\n",
+			error);
+		return error;
+	}
+
+	rows = resp[2] + resp[6] + resp[10];
+	cols = resp[3] + resp[7] + resp[11];
+
+	/* Process mm_to_pixel information */
+	error = elants_i2c_execute_command(client,
+					   get_osr_cmd, sizeof(get_osr_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get osr command failed: %d\n",
+			error);
+		return error;
+	}
+
+	osr = resp[3];
+
+	error = elants_i2c_execute_command(client,
+					   get_physical_scan_cmd,
+					   sizeof(get_physical_scan_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get physical scan command failed:
%d\n",
+			error);
+		return error;
+	}
+
+	phy_x = get_unaligned_be16(&resp[2]);
+
+	error = elants_i2c_execute_command(client,
+					   get_physical_drive_cmd,
+					   sizeof(get_physical_drive_cmd),
+					   resp, sizeof(resp));
+	if (error) {
+		dev_err(&client->dev, "get physical drive command failed:
%d\n",
+			error);
+		return error;
+	}
+
+	phy_y = get_unaligned_be16(&resp[2]);
+
+	dev_dbg(&client->dev, "phy_x=%d, phy_y=%d\n", phy_x, phy_y);
+
+	if (rows == 0 || cols == 0 || osr == 0) {
+		dev_warn(&client->dev,
+			 "invalid trace number data: %d, %d, %d\n",
+			 rows, cols, osr);
+	} else {
+		/* translate trace number to TS resolution */
+		ts->x_max = ELAN_TS_RESOLUTION(rows, osr);
+		ts->x_res = DIV_ROUND_CLOSEST(ts->x_max, phy_x);
+		ts->y_max = ELAN_TS_RESOLUTION(cols, osr);
+		ts->y_res = DIV_ROUND_CLOSEST(ts->y_max, phy_y);
+	}
+
+	return 0;
+}
+
+static int elants_i2c_fastboot(struct i2c_client *client) {
+	const u8 boot_cmd[] = { 0x4D, 0x61, 0x69, 0x6E };
+	int error;
+
+	error = elants_i2c_send(client, boot_cmd, sizeof(boot_cmd));
+	if (error) {
+		dev_err(&client->dev, "boot failed: %d\n", error);
+		return error;
+	}
+
+	dev_dbg(&client->dev, "boot success -- 0x%x\n", client->addr);
+	return 0;
+}
+
+static int elants_i2c_initialize(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	int error, retry_cnt;
+	const u8 hello_packet[] = { 0x55, 0x55, 0x55, 0x55 };
+	const u8 recov_packet[] = { 0x55, 0x55, 0x80, 0x80 };
+	u8 buf[HEADER_SIZE];
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_sw_reset(client);
+		if (error) {
+			/* Continue initializing if it's the last try */
+			if (retry_cnt < MAX_RETRIES - 1)
+				continue;
+		}
+
+		error = elants_i2c_fastboot(client);
+		if (error) {
+			/* Continue initializing if it's the last try */
+			if (retry_cnt < MAX_RETRIES - 1)
+				continue;
+		}
+
+		/* Wait for Hello packet */
+		msleep(BOOT_TIME_DELAY_MS);
+
+		error = elants_i2c_read(client, buf, sizeof(buf));
+		if (error) {
+			dev_err(&client->dev,
+				"failed to read 'hello' packet: %d\n",
error);
+		} else if (!memcmp(buf, hello_packet, sizeof(hello_packet)))
{
+			ts->iap_mode = ELAN_IAP_OPERATIONAL;
+			break;
+		} else if (!memcmp(buf, recov_packet, sizeof(recov_packet)))
{
+			/*
+			 * Setting error code will mark device
+			 * in recovery mode below.
+			 */
+			error = -EIO;
+			break;
+		} else {
+			error = -EINVAL;
+			dev_err(&client->dev,
+				"invalid 'hello' packet: %*ph\n",
+				(int)sizeof(buf), buf);
+		}
+	}
+
+	if (!error)
+		error = elants_i2c_query_fw_id(ts);
+	if (!error)
+		error = elants_i2c_query_fw_version(ts);
+
+	if (error) {
+		ts->iap_mode = ELAN_IAP_RECOVERY;
+	} else {
+		elants_i2c_query_test_version(ts);
+		elants_i2c_query_bc_version(ts);
+		elants_i2c_query_ts_info(ts);
+	}
+
+	return 0;
+}
+
+/*
+ * Firmware update interface.
+ */
+
+static int elants_i2c_fw_write_page(struct i2c_client *client,
+				    const void *page)
+{
+	const u8 ack_ok[] = { 0xaa, 0xaa };
+	u8 buf[2];
+	int retry;
+	int error;
+
+	for (retry = 0; retry < MAX_FW_UPDATE_RETRIES; retry++) {
+		error = elants_i2c_send(client, page, ELAN_FW_PAGESIZE);
+		if (error) {
+			dev_err(&client->dev,
+				"IAP Write Page failed: %d\n", error);
+			continue;
+		}
+
+		error = elants_i2c_read(client, buf, 2);
+		if (error) {
+			dev_err(&client->dev,
+				"IAP Ack read failed: %d\n", error);
+			return error;
+		}
+
+		if (!memcmp(buf, ack_ok, sizeof(ack_ok)))
+			return 0;
+
+		error = -EIO;
+		dev_err(&client->dev,
+			"IAP Get Ack Error [%02x:%02x]\n",
+			buf[0], buf[1]);
+	}
+
+	return error;
+}
+
+static int elants_i2c_do_update_firmware(struct i2c_client *client,
+					 const struct firmware *fw,
+					 bool force)
+{
+	const u8 enter_iap[] = { 0x45, 0x49, 0x41, 0x50 };
+	const u8 enter_iap2[] = { 0x54, 0x00, 0x12, 0x34 };
+	const u8 iap_ack[] = { 0x55, 0xaa, 0x33, 0xcc };
+	u8 buf[HEADER_SIZE];
+	u16 send_id;
+	int page, n_fw_pages;
+	int error;
+
+	/* Recovery mode detection! */
+	if (force) {
+		dev_dbg(&client->dev, "Recovery mode procedure\n");
+		error = elants_i2c_send(client, enter_iap2,
sizeof(enter_iap2));
+	} else {
+		/* Start IAP Procedure */
+		dev_dbg(&client->dev, "Normal IAP procedure\n");
+		elants_i2c_sw_reset(client);
+
+		error = elants_i2c_send(client, enter_iap,
sizeof(enter_iap));
+	}
+
+	if (error) {
+		dev_err(&client->dev, "failed to enter IAP mode: %d\n",
error);
+		return error;
+	}
+
+	msleep(20);
+
+	/* check IAP state */
+	error = elants_i2c_read(client, buf, 4);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to read IAP acknowledgement: %d\n",
+			error);
+		return error;
+	}
+
+	if (memcmp(buf, iap_ack, sizeof(iap_ack))) {
+		dev_err(&client->dev,
+			"failed to enter IAP: %*ph (expected %*ph)\n",
+			(int)sizeof(buf), buf, (int)sizeof(iap_ack),
iap_ack);
+		return -EIO;
+	}
+
+	dev_info(&client->dev, "successfully entered IAP mode");
+
+	send_id = client->addr;
+	error = elants_i2c_send(client, &send_id, 1);
+	if (error) {
+		dev_err(&client->dev, "sending dummy byte failed: %d\n",
+			error);
+		return error;
+	}
+
+	/* Clear the last page of Master */
+	error = elants_i2c_send(client, fw->data, ELAN_FW_PAGESIZE);
+	if (error) {
+		dev_err(&client->dev, "clearing of the last page failed:
%d\n",
+			error);
+		return error;
+	}
+
+	error = elants_i2c_read(client, buf, 2);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to read ACK for clearing the last page:
%d\n",
+			error);
+		return error;
+	}
+
+	n_fw_pages = fw->size / ELAN_FW_PAGESIZE;
+	dev_dbg(&client->dev, "IAP Pages = %d\n", n_fw_pages);
+
+	for (page = 0; page < n_fw_pages; page++) {
+		error = elants_i2c_fw_write_page(client,
+					fw->data + page * ELAN_FW_PAGESIZE);
+		if (error) {
+			dev_err(&client->dev,
+				"failed to write FW page %d: %d\n",
+				page, error);
+			return error;
+		}
+	}
+
+	/* Old iap needs to wait 200ms for WDT and rest is for hello packets
*/
+	msleep(300);
+
+	dev_info(&client->dev, "firmware update completed\n");
+	return 0;
+}
+
+static int elants_i2c_fw_update(struct elants_data *ts) {
+	struct i2c_client *client = ts->client;
+	const struct firmware *fw;
+	int error;
+
+	error = request_firmware(&fw, ELAN_FW_FILENAME, &client->dev);
+	if (error) {
+		dev_err(&client->dev, "failed to request firmware %s: %d\n",
+			ELAN_FW_FILENAME, error);
+		return error;
+	}
+
+	if (fw->size % ELAN_FW_PAGESIZE) {
+		dev_err(&client->dev, "invalid firmware length: %zu\n",
+			fw->size);
+		error = -EINVAL;
+		goto out;
+	}
+
+	disable_irq(client->irq);
+
+	error = elants_i2c_do_update_firmware(client, fw,
+					ts->iap_mode == ELAN_IAP_RECOVERY);
+	if (error) {
+		dev_err(&client->dev, "firmware update failed: %d\n",
error);
+		ts->iap_mode = ELAN_IAP_RECOVERY;
+		goto out_enable_irq;
+	}
+
+	error = elants_i2c_initialize(ts);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to initialize device after firmware update:
%d\n",
+			error);
+		ts->iap_mode = ELAN_IAP_RECOVERY;
+		goto out_enable_irq;
+	}
+
+	ts->iap_mode = ELAN_IAP_OPERATIONAL;
+
+out_enable_irq:
+	ts->state = ELAN_STATE_NORMAL;
+	enable_irq(client->irq);
+	msleep(100);
+
+	if (!error)
+		elants_i2c_calibrate(ts);
+out:
+	release_firmware(fw);
+	return error;
+}
+
+/*
+ * Event reporting.
+ */
+
+static void elants_i2c_mt_event(struct elants_data *ts, u8 *buf) {
+	struct input_dev *input = ts->input;
+	unsigned int n_fingers;
+	u16 finger_state;
+	int i;
+
+	n_fingers = buf[FW_POS_STATE + 1] & 0x0f;
+	finger_state = ((buf[FW_POS_STATE + 1] & 0x30) << 4) |
+			buf[FW_POS_STATE];
+
+	dev_dbg(&ts->client->dev,
+		"n_fingers: %u, state: %04x\n",  n_fingers, finger_state);
+
+	for (i = 0; i < MAX_CONTACT_NUM && n_fingers; i++) {
+		if (finger_state & 1) {
+			unsigned int x, y, p, w;
+			u8 *pos;
+
+			pos = &buf[FW_POS_XY + i * 3];
+			x = (((u16)pos[0] & 0xf0) << 4) | pos[1];
+			y = (((u16)pos[0] & 0x0f) << 8) | pos[2];
+			p = buf[FW_POS_PRESSURE + i];
+			w = buf[FW_POS_WIDTH + i];
+
+			dev_dbg(&ts->client->dev, "i=%d x=%d y=%d p=%d
w=%d\n",
+				i, x, y, p, w);
+
+			input_mt_slot(input, i);
+			input_mt_report_slot_state(input, MT_TOOL_FINGER,
true);
+			input_event(input, EV_ABS, ABS_MT_POSITION_X, x);
+			input_event(input, EV_ABS, ABS_MT_POSITION_Y, y);
+			input_event(input, EV_ABS, ABS_MT_PRESSURE, p);
+			input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, w);
+
+			n_fingers--;
+		}
+
+		finger_state >>= 1;
+	}
+
+	input_mt_sync_frame(input);
+	input_sync(input);
+}
+
+static u8 elants_i2c_calculate_checksum(u8 *buf) {
+	u8 checksum = 0;
+	u8 i;
+
+	for (i = 0; i < FW_POS_CHECKSUM; i++)
+		checksum += buf[i];
+
+	return checksum;
+}
+
+static void elants_i2c_event(struct elants_data *ts, u8 *buf) {
+	u8 checksum = elants_i2c_calculate_checksum(buf);
+
+	if (unlikely(buf[FW_POS_CHECKSUM] != checksum))
+		dev_warn(&ts->client->dev,
+			 "%s: invalid checksum for packet %02x: %02x vs.
%02x\n",
+			 __func__, buf[FW_POS_HEADER],
+			 checksum, buf[FW_POS_CHECKSUM]);
+	else if (unlikely(buf[FW_POS_HEADER] != HEADER_REPORT_10_FINGER))
+		dev_warn(&ts->client->dev,
+			 "%s: unknown packet type: %02x\n",
+			 __func__, buf[FW_POS_HEADER]);
+	else
+		elants_i2c_mt_event(ts, buf);
+}
+
+static irqreturn_t elants_i2c_irq(int irq, void *_dev) {
+	const u8 wait_packet[] = { 0x64, 0x64, 0x64, 0x64 };
+	struct elants_data *ts = _dev;
+	struct i2c_client *client = ts->client;
+	int report_count, report_len;
+	int i;
+	int len;
+
+	len = i2c_master_recv(client, ts->buf, sizeof(ts->buf));
+	if (len < 0) {
+		dev_err(&client->dev, "%s: failed to read data: %d\n",
+			__func__, len);
+		goto out;
+	}
+
+	dev_dbg(&client->dev, "%s: packet %*ph\n",
+		__func__, HEADER_SIZE, ts->buf);
+
+	switch (ts->state) {
+	case ELAN_WAIT_RECALIBRATION:
+		if (ts->buf[FW_HDR_TYPE] == CMD_HEADER_REK) {
+			memcpy(ts->cmd_resp, ts->buf, sizeof(ts->cmd_resp));
+			complete(&ts->cmd_done);
+			ts->state = ELAN_STATE_NORMAL;
+		}
+		break;
+
+	case ELAN_WAIT_QUEUE_HEADER:
+		if (ts->buf[FW_HDR_TYPE] != QUEUE_HEADER_NORMAL)
+			break;
+
+		ts->state = ELAN_STATE_NORMAL;
+		/* fall through */
+
+	case ELAN_STATE_NORMAL:
+
+		switch (ts->buf[FW_HDR_TYPE]) {
+		case CMD_HEADER_HELLO:
+		case CMD_HEADER_RESP:
+		case CMD_HEADER_REK:
+			break;
+
+		case QUEUE_HEADER_WAIT:
+			if (memcmp(ts->buf, wait_packet,
sizeof(wait_packet))) {
+				dev_err(&client->dev,
+					"invalid wait packet %*ph\n",
+					HEADER_SIZE, ts->buf);
+			} else {
+				ts->state = ELAN_WAIT_QUEUE_HEADER;
+				udelay(30);
+			}
+			break;
+
+		case QUEUE_HEADER_SINGLE:
+			elants_i2c_event(ts, &ts->buf[HEADER_SIZE]);
+			break;
+
+		case QUEUE_HEADER_NORMAL:
+			report_count = ts->buf[FW_HDR_COUNT];
+			if (report_count > 3) {
+				dev_err(&client->dev,
+					"too large report count: %*ph\n",
+					HEADER_SIZE, ts->buf);
+				break;
+			}
+
+			report_len = ts->buf[FW_HDR_LENGTH] / report_count;
+			if (report_len != PACKET_SIZE) {
+				dev_err(&client->dev,
+					"mismatching report length: %*ph\n",
+					HEADER_SIZE, ts->buf);
+				break;
+			}
+
+			for (i = 0; i < report_count; i++) {
+				u8 *buf = ts->buf + HEADER_SIZE +
+							i * PACKET_SIZE;
+				elants_i2c_event(ts, buf);
+			}
+			break;
+
+		default:
+			dev_err(&client->dev, "unknown packet %*ph\n",
+				HEADER_SIZE, ts->buf);
+			break;
+		}
+		break;
+	}
+
+out:
+	return IRQ_HANDLED;
+}
+
+/*
+ * sysfs interface
+ */
+static ssize_t calibrate_store(struct device *dev,
+			       struct device_attribute *attr,
+			      const char *buf, size_t count) {
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	int error;
+
+	error = mutex_lock_interruptible(&ts->sysfs_mutex);
+	if (error)
+		return error;
+
+	error = elants_i2c_calibrate(ts);
+
+	mutex_unlock(&ts->sysfs_mutex);
+	return error ?: count;
+}
+
+static ssize_t write_update_fw(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count) {
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	int error;
+
+	error = mutex_lock_interruptible(&ts->sysfs_mutex);
+	if (error)
+		return error;
+
+	error = elants_i2c_fw_update(ts);
+	dev_dbg(dev, "firmware update result: %d\n", error);
+
+	mutex_unlock(&ts->sysfs_mutex);
+	return error ?: count;
+}
+
+static ssize_t show_iap_mode(struct device *dev,
+			     struct device_attribute *attr, char *buf) {
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+
+	return sprintf(buf, "%s\n",
+		       ts->iap_mode == ELAN_IAP_OPERATIONAL ?
+				"Normal" : "Recovery");
+}
+
+static DEVICE_ATTR(calibrate, S_IWUSR, NULL, calibrate_store); static 
+DEVICE_ATTR(iap_mode, S_IRUGO, show_iap_mode, NULL); static 
+DEVICE_ATTR(update_fw, S_IWUSR, NULL, write_update_fw);
+
+struct elants_version_attribute {
+	struct device_attribute dattr;
+	size_t field_offset;
+	size_t field_size;
+};
+
+#define __ELANTS_FIELD_SIZE(_field)					\
+	sizeof(((struct elants_data *)NULL)->_field)
+#define __ELANTS_VERIFY_SIZE(_field)					\
+	(BUILD_BUG_ON_ZERO(__ELANTS_FIELD_SIZE(_field) > 2) +		\
+	 __ELANTS_FIELD_SIZE(_field))
+#define ELANTS_VERSION_ATTR(_field)					\
+	struct elants_version_attribute elants_ver_attr_##_field = {	\
+		.dattr = __ATTR(_field, S_IRUGO,			\
+				elants_version_attribute_show, NULL),	\
+		.field_offset = offsetof(struct elants_data, _field),	\
+		.field_size = __ELANTS_VERIFY_SIZE(_field),		\
+	}
+
+static ssize_t elants_version_attribute_show(struct device *dev,
+					     struct device_attribute *dattr,
+					     char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	struct elants_version_attribute *attr =
+		container_of(dattr, struct elants_version_attribute, dattr);
+	u8 *field = (u8 *)((char *)ts + attr->field_offset);
+	unsigned int fmt_size;
+	unsigned int val;
+
+	if (attr->field_size == 1) {
+		val = *field;
+		fmt_size = 2; /* 2 HEX digits */
+	} else {
+		val = *(u16 *)field;
+		fmt_size = 4; /* 4 HEX digits */
+	}
+
+	return sprintf(buf, "%0*x\n", fmt_size, val); }
+
+static ELANTS_VERSION_ATTR(fw_version); static 
+ELANTS_VERSION_ATTR(hw_version); static 
+ELANTS_VERSION_ATTR(test_version);
+static ELANTS_VERSION_ATTR(solution_version);
+static ELANTS_VERSION_ATTR(bc_version); static 
+ELANTS_VERSION_ATTR(iap_version);
+
+static struct attribute *elants_attributes[] = {
+	&dev_attr_calibrate.attr,
+	&dev_attr_update_fw.attr,
+	&dev_attr_iap_mode.attr,
+
+	&elants_ver_attr_fw_version.dattr.attr,
+	&elants_ver_attr_hw_version.dattr.attr,
+	&elants_ver_attr_test_version.dattr.attr,
+	&elants_ver_attr_solution_version.dattr.attr,
+	&elants_ver_attr_bc_version.dattr.attr,
+	&elants_ver_attr_iap_version.dattr.attr,
+	NULL
+};
+
+static struct attribute_group elants_attribute_group = {
+	.attrs = elants_attributes,
+};
+
+static void elants_i2c_remove_sysfs_group(void *_data) {
+	struct elants_data *ts = _data;
+
+	sysfs_remove_group(&ts->client->dev.kobj, &elants_attribute_group);
}
+
+static int elants_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	union i2c_smbus_data dummy;
+	struct elants_data *ts;
+	unsigned long irqflags;
+	int error;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		dev_err(&client->dev,
+			"%s: i2c check functionality error\n", DEVICE_NAME);
+		return -ENXIO;
+	}
+
+	/* 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) {
+		dev_err(&client->dev, "nothing at this address\n");
+		return -ENXIO;
+	}
+
+	ts = devm_kzalloc(&client->dev, sizeof(struct elants_data),
GFP_KERNEL);
+	if (!ts)
+		return -ENOMEM;
+
+	mutex_init(&ts->sysfs_mutex);
+	init_completion(&ts->cmd_done);
+
+	ts->client = client;
+	i2c_set_clientdata(client, ts);
+
+	error = elants_i2c_initialize(ts);
+	if (error) {
+		dev_err(&client->dev, "failed to initialize: %d\n", error);
+		return error;
+	}
+
+	ts->input = devm_input_allocate_device(&client->dev);
+	if (!ts->input) {
+		dev_err(&client->dev, "Failed to allocate input device\n");
+		return -ENOMEM;
+	}
+
+	ts->input->name = "Elan Touchscreen";
+	ts->input->id.bustype = BUS_I2C;
+
+	__set_bit(BTN_TOUCH, ts->input->keybit);
+	__set_bit(EV_ABS, ts->input->evbit);
+	__set_bit(EV_KEY, ts->input->evbit);
+
+	/* Single touch input params setup */
+	input_set_abs_params(ts->input, ABS_X, 0, ts->x_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_Y, 0, ts->y_max, 0, 0);
+	input_set_abs_params(ts->input, ABS_PRESSURE, 0, 255, 0, 0);
+	input_abs_set_res(ts->input, ABS_X, ts->x_res);
+	input_abs_set_res(ts->input, ABS_Y, ts->y_res);
+
+	/* Multitouch input params setup */
+	error = input_mt_init_slots(ts->input, MAX_CONTACT_NUM,
+				    INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+	if (error) {
+		dev_err(&client->dev,
+			"failed to initialize MT slots: %d\n", error);
+		return error;
+	}
+
+	input_set_abs_params(ts->input, ABS_MT_POSITION_X, 0, ts->x_max, 0,
0);
+	input_set_abs_params(ts->input, ABS_MT_POSITION_Y, 0, ts->y_max, 0,
0);
+	input_set_abs_params(ts->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+	input_set_abs_params(ts->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
+	input_abs_set_res(ts->input, ABS_MT_POSITION_X, ts->x_res);
+	input_abs_set_res(ts->input, ABS_MT_POSITION_Y, ts->y_res);
+
+	input_set_drvdata(ts->input, ts);
+
+	error = input_register_device(ts->input);
+	if (error) {
+		dev_err(&client->dev,
+			"unable to register input device: %d\n", error);
+		return error;
+	}
+
+	/*
+	 * Systems using device tree should set up interrupt via DTS,
+	 * the rest will use the default falling edge interrupts.
+	 */
+	irqflags = client->dev.of_node ? 0 : IRQF_TRIGGER_FALLING;
+
+	error = devm_request_threaded_irq(&client->dev, client->irq,
+					  NULL, elants_i2c_irq,
+					  irqflags | IRQF_ONESHOT,
+					  client->name, ts);
+	if (error) {
+		dev_err(&client->dev, "Failed to register interrupt\n");
+		return error;
+	}
+
+	/*
+	 * Systems using device tree should set up wakeup via DTS,
+	 * the rest will configure device as wakeup source by default.
+	 */
+	if (!client->dev.of_node)
+		device_init_wakeup(&client->dev, true);
+
+	error = sysfs_create_group(&client->dev.kobj,
&elants_attribute_group);
+	if (error) {
+		dev_err(&client->dev, "failed to create sysfs attributes:
%d\n",
+			error);
+		return error;
+	}
+
+	error = devm_add_action(&client->dev,
+				elants_i2c_remove_sysfs_group, ts);
+	if (error) {
+		elants_i2c_remove_sysfs_group(ts);
+		dev_err(&client->dev,
+			"Failed to add sysfs cleanup action: %d\n",
+			error);
+		return error;
+	}
+
+	return 0;
+}
+
+static int __maybe_unused elants_i2c_suspend(struct device *dev) {
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	const u8 set_sleep_cmd[] = { 0x54, 0x50, 0x00, 0x01 };
+	int retry_cnt;
+	int error;
+
+	/* Command not support in IAP recovery mode */
+	if (ts->iap_mode != ELAN_IAP_OPERATIONAL)
+		return -EBUSY;
+
+	disable_irq(client->irq);
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_send(client, set_sleep_cmd,
+					sizeof(set_sleep_cmd));
+		if (!error)
+			break;
+
+		dev_err(&client->dev, "suspend command failed: %d\n",
error);
+	}
+
+	if (device_may_wakeup(dev))
+		ts->wake_irq_enabled = (enable_irq_wake(client->irq) == 0);
+
+	return 0;
+}
+
+static int __maybe_unused elants_i2c_resume(struct device *dev) {
+	struct i2c_client *client = to_i2c_client(dev);
+	struct elants_data *ts = i2c_get_clientdata(client);
+	const u8 set_active_cmd[] = { 0x54, 0x58, 0x00, 0x01 };
+	int retry_cnt;
+	int error;
+
+	if (device_may_wakeup(dev) && ts->wake_irq_enabled)
+		disable_irq_wake(client->irq);
+
+	for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
+		error = elants_i2c_send(client, set_active_cmd,
+					sizeof(set_active_cmd));
+		if (!error)
+			break;
+
+		dev_err(&client->dev, "resume command failed: %d\n", error);
+	}
+
+	ts->state = ELAN_STATE_NORMAL;
+	enable_irq(client->irq);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(elants_i2c_pm_ops,
+			 elants_i2c_suspend, elants_i2c_resume);
+
+static const struct i2c_device_id elants_i2c_id[] = {
+	{ DEVICE_NAME, 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, elants_i2c_id);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id elants_acpi_id[] = {
+	{ "ELAN0001", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(acpi, elants_acpi_id); #endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id elants_of_match[] = {
+	{ .compatible = "elan,ekth3500" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, elants_of_match); #endif
+
+static struct i2c_driver elants_i2c_driver = {
+	.probe = elants_i2c_probe,
+	.id_table = elants_i2c_id,
+	.driver = {
+		.name = DEVICE_NAME,
+		.owner = THIS_MODULE,
+		.pm = &elants_i2c_pm_ops,
+		.acpi_match_table = ACPI_PTR(elants_acpi_id),
+		.of_match_table = of_match_ptr(elants_of_match),
+	},
+};
+module_i2c_driver(elants_i2c_driver);
+
+MODULE_AUTHOR("Scott Liu <scott.liu@emc.com.tw>"); 
+MODULE_DESCRIPTION("Elan I2c Touchscreen driver"); 
+MODULE_VERSION(DRV_VERSION); MODULE_LICENSE("GPL");


^ permalink raw reply related

* Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
From: Oliver Neukum @ 2014-11-20 10:31 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: scott, linux-kernel, linux-input, Vincent Wang, Jeff Chuang,
	Benson Leung, Benjamin Tissoires, Henrik Rydberg, David Herrmann
In-Reply-To: <20141120081113.GA37534@dtor-ws>



> +static int elants_i2c_sw_reset(struct i2c_client *client)
> +{
> +       const u8 soft_rst_cmd[] = { 0x77, 0x77, 0x77, 0x77 };
> +       int error;
> +
> +       error = elants_i2c_send(client, soft_rst_cmd,
> +                               sizeof(soft_rst_cmd));
> +       if (error) {
> +               dev_err(&client->dev, "software reset failed: %d\n",
> error);
> +               return error;
> +       }
> +
> +       /*
> +        * We should wait at least 10 msec (but no more than 40)
> before
> +        * sending fastboot or IAP command to the device.
> +        */
> +       msleep(30);

If timing is critical in both ways, you should take our guarantee
if sufficient sleep. I suggest you sleep 11msecs.


> +static int elants_i2c_initialize(struct elants_data *ts)
> +{
> +       struct i2c_client *client = ts->client;
> +       int error, retry_cnt;
> +       const u8 hello_packet[] = { 0x55, 0x55, 0x55, 0x55 };
> +       const u8 recov_packet[] = { 0x55, 0x55, 0x80, 0x80 };
> +       u8 buf[HEADER_SIZE];
> +

Strictly speaking you should disable preemption here.

> +       for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
> +               error = elants_i2c_sw_reset(client);
> +               if (error) {
> +                       /* Continue initializing if it's the last try
> */
> +                       if (retry_cnt < MAX_RETRIES - 1)
> +                               continue;
> +               }

	Regards
		Oliver

-- 
Oliver Neukum <oneukum@suse.de>


^ permalink raw reply

* RE: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
From: ELAN 劉嘉駿 @ 2014-11-20 12:25 UTC (permalink / raw)
  To: 'Oliver Neukum', 'Dmitry Torokhov'
  Cc: linux-kernel, linux-input, 'Vincent Wang',
	'Jeff Chuang', 'Benson Leung',
	'Benjamin Tissoires', 'Henrik Rydberg',
	'David Herrmann'
In-Reply-To: <1416479490.28599.13.camel@linux-0dmf.site>


Hi Oliver:

-----Original Message-----
From: Oliver Neukum [mailto:oneukum@suse.de] 
Sent: Thursday, November 20, 2014 6:32 PM
To: Dmitry Torokhov
Cc: scott; linux-kernel@vger.kernel.org; linux-input@vger.kernel.org; Vincent Wang; Jeff Chuang; Benson Leung; Benjamin Tissoires; Henrik Rydberg; David Herrmann
Subject: Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support



> +static int elants_i2c_sw_reset(struct i2c_client *client) {
> +       const u8 soft_rst_cmd[] = { 0x77, 0x77, 0x77, 0x77 };
> +       int error;
> +
> +       error = elants_i2c_send(client, soft_rst_cmd,
> +                               sizeof(soft_rst_cmd));
> +       if (error) {
> +               dev_err(&client->dev, "software reset failed: %d\n",
> error);
> +               return error;
> +       }
> +
> +       /*
> +        * We should wait at least 10 msec (but no more than 40)
> before
> +        * sending fastboot or IAP command to the device.
> +        */
> +       msleep(30);

If timing is critical in both ways, you should take our guarantee if sufficient sleep. I suggest you sleep 11msecs.

Sorry, this is our firmware tricky but it should be sleep longer than 30ms. Actually 70ms.
(Dmitry, I need to modify this delay time to 70ms. Sorry for the changed over and over again; 
	because we consider big sensor solution which need to use more ICs and need more longer delay)

thanks,
Scott

> +static int elants_i2c_initialize(struct elants_data *ts) {
> +       struct i2c_client *client = ts->client;
> +       int error, retry_cnt;
> +       const u8 hello_packet[] = { 0x55, 0x55, 0x55, 0x55 };
> +       const u8 recov_packet[] = { 0x55, 0x55, 0x80, 0x80 };
> +       u8 buf[HEADER_SIZE];
> +

Strictly speaking you should disable preemption here.


> +       for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
> +               error = elants_i2c_sw_reset(client);
> +               if (error) {
> +                       /* Continue initializing if it's the last try
> */
> +                       if (retry_cnt < MAX_RETRIES - 1)
> +                               continue;
> +               }

	Regards
		Oliver

--
Oliver Neukum <oneukum@suse.de>



^ permalink raw reply

* Re: [PATCH] hid-multitouch: Add quirk for VTL touch panels
From: Jiri Kosina @ 2014-11-20 13:21 UTC (permalink / raw)
  To: Benjamin Tissoires
  Cc: Mathieu Magnaudet, Benjamin Tissoires, linux-input,
	linux-kernel@vger.kernel.org
In-Reply-To: <CAN+gG=GN4dwXr_qEHMfMddGHkMKECZMUwTM+xbNmY_gibthLmA@mail.gmail.com>

On Wed, 19 Nov 2014, Benjamin Tissoires wrote:

> though this patch works for this particular device, I just thought at
> something which may solve the problem in a different way. I asked
> Mathieu to test the different solution, so I'd rather you to wait for
> final confirmation before merging this patch.

Thanks for the heads up, I am putting this patch on hold until further 
notice from you or Mathieu.

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* [PATCH 0/2] Add regulator-haptic driver
From: Jaewon Kim @ 2014-11-20 13:31 UTC (permalink / raw)
  To: Kukjin Kim, Dmitry Torokhov
  Cc: Chanwoo Choi, linux-kernel, linux-input, linux-samsung-soc,
	Jaewon Kim

This patch series adds regulator-haptic driver.
The regulator-haptic has haptic motor and it is controlled by
voltage of regulator via force feedback framework.

Jaewon Kim (2):
  Input: add regulator haptic driver
  ARM: dts: Add regulator-haptic device node for exynos3250-rinato

 .../devicetree/bindings/input/regulator-haptic.txt |   24 ++
 arch/arm/boot/dts/exynos3250-rinato.dts            |    7 +
 drivers/input/misc/Kconfig                         |   11 +
 drivers/input/misc/Makefile                        |    1 +
 drivers/input/misc/regulator-haptic.c              |  241 ++++++++++++++++++++
 5 files changed, 284 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/regulator-haptic.txt
 create mode 100644 drivers/input/misc/regulator-haptic.c

-- 
1.7.9.5

^ permalink raw reply

* [PATCH 1/2] Input: add regulator haptic driver
From: Jaewon Kim @ 2014-11-20 13:31 UTC (permalink / raw)
  To: Kukjin Kim, Dmitry Torokhov
  Cc: Chanwoo Choi, linux-kernel, linux-input, linux-samsung-soc,
	Jaewon Kim, Hyunhee Kim
In-Reply-To: <1416490300-28865-1-git-send-email-jaewon02.kim@samsung.com>

This patch adds support for haptic driver controlled by
voltage of regulator. And this driver support for
Force Feedback interface from input framework

Signed-off-by: Jaewon Kim <jaewon02.kim@samsung.com>
Signed-off-by: Hyunhee Kim <hyunhee.kim@samsung.com>
Acked-by: Kyungmin Park <kyungmin.park@samsung.com>
---
 .../devicetree/bindings/input/regulator-haptic.txt |   24 ++
 drivers/input/misc/Kconfig                         |   11 +
 drivers/input/misc/Makefile                        |    1 +
 drivers/input/misc/regulator-haptic.c              |  241 ++++++++++++++++++++
 4 files changed, 277 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/regulator-haptic.txt
 create mode 100644 drivers/input/misc/regulator-haptic.c

diff --git a/Documentation/devicetree/bindings/input/regulator-haptic.txt b/Documentation/devicetree/bindings/input/regulator-haptic.txt
new file mode 100644
index 0000000..9f60e17
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/regulator-haptic.txt
@@ -0,0 +1,24 @@
+* Requlator Haptic Device Tree Bindings
+
+The regulator haptic driver controlled by voltage of regulator.
+This driver implemented via Force Feedback interface.
+
+Required Properties:
+ - compatible : Should be "regulator-haptic"
+ - haptic-supply : Power supply for the haptic motor.
+	[*] refer Documentation/devicetree/bindings/regulator/regulator.txt
+
+ - max-microvolt : The maximum voltage value supplied to haptic motor.
+		[The unit of the voltage is a micro]
+
+ - min-microvolt : The minimum voltage value in which haptic motor reacts.
+		[The unit of the voltage is a micro]
+
+Example:
+
+	regulator-haptic {
+		compatible = "regulator-haptic";
+		haptic-supply = <&motor_regulator>;
+		max-microvolt = <2700000>;
+		min-microvolt = <1100000>;
+	};
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 23297ab..e5e556d 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -394,6 +394,17 @@ 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"
+	select INPUT_FF_MEMLESS
+	help
+	  This option enables device driver support for the haptic controlled
+	  by 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
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 19c7603..1f135af 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -53,6 +53,7 @@ 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_GPIO_ROTARY_ENCODER)	+= rotary_encoder.o
 obj-$(CONFIG_INPUT_SGI_BTNS)		+= sgi_btns.o
diff --git a/drivers/input/misc/regulator-haptic.c b/drivers/input/misc/regulator-haptic.c
new file mode 100644
index 0000000..1a83ecb
--- /dev/null
+++ b/drivers/input/misc/regulator-haptic.c
@@ -0,0 +1,241 @@
+/*
+ * 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/module.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of.h>
+
+#define MAX_MAGNITUDE_SHIFT	16
+
+struct regulator_haptic {
+	struct device *dev;
+	struct input_dev *input_dev;
+	struct regulator *regulator;
+	struct work_struct work;
+
+	bool enabled;
+	bool suspend_state;
+	unsigned int max_volt;
+	unsigned int min_volt;
+	unsigned int intensity;
+	unsigned int magnitude;
+};
+
+static void regulator_haptic_enable(struct regulator_haptic *haptic)
+{
+	int error;
+
+	if (haptic->enabled)
+		return;
+
+	error = regulator_enable(haptic->regulator);
+	if (error) {
+		dev_err(haptic->dev, "cannot enable regulator\n");
+		return;
+	}
+
+	haptic->enabled = true;
+}
+
+static void regulator_haptic_disable(struct regulator_haptic *haptic)
+{
+	int error;
+
+	if (!haptic->enabled)
+		return;
+
+	error = regulator_disable(haptic->regulator);
+	if (error) {
+		dev_err(haptic->dev, "cannot disable regulator\n");
+		return;
+	}
+
+	haptic->enabled = false;
+}
+
+static void regulator_haptic_work(struct work_struct *work)
+{
+	struct regulator_haptic *haptic = container_of(work,
+					struct regulator_haptic, work);
+	int error;
+
+	error = regulator_set_voltage(haptic->regulator,
+			haptic->intensity + haptic->min_volt, haptic->max_volt);
+	if (error) {
+		dev_err(haptic->dev, "cannot set regulator voltage\n");
+		return;
+	}
+
+	if (haptic->magnitude)
+		regulator_haptic_enable(haptic);
+	else
+		regulator_haptic_disable(haptic);
+}
+
+static int regulator_haptic_play_effect(struct input_dev *input, void *data,
+					struct ff_effect *effect)
+{
+	struct regulator_haptic *haptic = input_get_drvdata(input);
+	u64 volt_mag_multi;
+
+	haptic->magnitude = effect->u.rumble.strong_magnitude;
+	if (!haptic->magnitude)
+		haptic->magnitude = effect->u.rumble.weak_magnitude;
+
+
+	volt_mag_multi = (u64)(haptic->max_volt - haptic->min_volt) *
+					haptic->magnitude;
+	haptic->intensity = (unsigned int)(volt_mag_multi >>
+					MAX_MAGNITUDE_SHIFT);
+
+	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_disable(haptic);
+}
+
+static int regulator_haptic_parse_dt(struct regulator_haptic *haptic)
+{
+	struct device_node *node = haptic->dev->of_node;
+	int error;
+
+	error = of_property_read_u32(node, "max-microvolt", &haptic->max_volt);
+	if (error) {
+		dev_err(haptic->dev, "cannot parse max-microvolt\n");
+		return error;
+	}
+
+	error = of_property_read_u32(node, "min-microvolt", &haptic->min_volt);
+	if (error) {
+		dev_err(haptic->dev, "cannot parse min-microvolt\n");
+		return error;
+	}
+
+	return 0;
+}
+
+static int regulator_haptic_probe(struct platform_device *pdev)
+{
+	struct regulator_haptic *haptic;
+	struct input_dev *input_dev;
+	int error;
+
+	haptic = devm_kzalloc(&pdev->dev, sizeof(*haptic), GFP_KERNEL);
+	if (!haptic)
+		return -ENOMEM;
+
+	haptic->dev = &pdev->dev;
+	haptic->enabled = false;
+	haptic->suspend_state = false;
+	INIT_WORK(&haptic->work, regulator_haptic_work);
+
+	error = regulator_haptic_parse_dt(haptic);
+	if (error) {
+		dev_err(&pdev->dev, "failed to parse device tree\n");
+		return error;
+	}
+
+	haptic->regulator = devm_regulator_get(&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;
+	}
+
+	platform_set_drvdata(pdev, haptic);
+
+	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);
+
+	if (haptic->enabled) {
+		regulator_haptic_disable(haptic);
+		haptic->suspend_state = true;
+	}
+
+	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);
+
+	if (haptic->enabled) {
+		regulator_haptic_enable(haptic);
+		haptic->suspend_state = false;
+	}
+
+	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" },
+	{},
+};
+
+static struct platform_driver regulator_haptic_driver = {
+	.probe		= regulator_haptic_probe,
+	.driver		= {
+		.name		= "regulator-haptic",
+		.owner		= THIS_MODULE,
+		.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_ALIAS("platform:regulator-haptic");
+MODULE_DESCRIPTION("Regulator haptic driver");
+MODULE_LICENSE("GPL");
-- 
1.7.9.5

^ permalink raw reply related

* [PATCH 2/2] ARM: dts: Add regulator-haptic device node for exynos3250-rinato
From: Jaewon Kim @ 2014-11-20 13:31 UTC (permalink / raw)
  To: Kukjin Kim, Dmitry Torokhov
  Cc: Chanwoo Choi, linux-kernel, linux-input, linux-samsung-soc,
	Jaewon Kim
In-Reply-To: <1416490300-28865-1-git-send-email-jaewon02.kim@samsung.com>

This patch adds regulator-haptic device node controlled by regulator.

Signed-off-by: Jaewon Kim <jaewon02.kim@samsung.com>
---
 arch/arm/boot/dts/exynos3250-rinato.dts |    7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/arch/arm/boot/dts/exynos3250-rinato.dts b/arch/arm/boot/dts/exynos3250-rinato.dts
index 84380fa..8172815 100644
--- a/arch/arm/boot/dts/exynos3250-rinato.dts
+++ b/arch/arm/boot/dts/exynos3250-rinato.dts
@@ -104,6 +104,13 @@
 			};
 		};
 	};
+
+	regulator-haptic {
+		compatible = "regulator-haptic";
+		haptic-supply = <&motor_reg>;
+		min-microvolt = <1100000>;
+		max-microvolt = <2700000>;
+	};
 };
 
 &adc {
-- 
1.7.9.5


^ permalink raw reply related

* Re: [PATCH v4 3/6] mfd: ti_am335x_tscadc: Remove unwanted reg_se_cache save
From: Sekhar Nori @ 2014-11-20 13:56 UTC (permalink / raw)
  To: Dmitry Torokhov, Lee Jones
  Cc: Vignesh R, Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala, Benoit Cousson, Tony Lindgren, Russell King,
	Jonathan Cameron, Hartmut Knaack, richardcochran,
	Sebastian Andrzej Siewior, Lars-Peter Clausen, Peter Meerwald,
	Samuel Ortiz, Felipe Balbi, Brad Griffis, Sanjeev Sharma,
	Paul Gortmaker, Jan Kardell, devicetree, linux-kernel, linux-omap
In-Reply-To: <20141118171240.GB3003@dtor-ws>

On Tuesday 18 November 2014 10:42 PM, Dmitry Torokhov wrote:
> On Tue, Nov 18, 2014 at 02:30:05PM +0000, Lee Jones wrote:
>> On Fri, 14 Nov 2014, Vignesh R wrote:
>>
>>> In one shot mode, sequencer automatically disables all enabled steps at
>>> the end of each cycle. (both ADC steps and TSC steps) Hence these steps
>>> need not be saved in reg_se_cache for clearing these steps at a later
>>> stage.
>>> Also, when ADC wakes up Sequencer should not be busy executing any of the
>>> config steps except for the charge step. Previously charge step was 1 ADC
>>> clock cycle and hence it was ignored.
>>>
>>> Signed-off-by: Vignesh R <vigneshr@ti.com>
>>> ---
>>>  drivers/mfd/ti_am335x_tscadc.c       | 7 +++++--
>>>  include/linux/mfd/ti_am335x_tscadc.h | 1 +
>>>  2 files changed, 6 insertions(+), 2 deletions(-)
>>
>> Code looks better now.
>>
>> Acked-by: Lee Jones <lee.jones@linaro.org>
>>
>> What's the plan for this series?
> 
> I am waiting for the interested parties to provide more feedback. So far
> I have seen a few reports saying that they see issues with the series
> applied.

I tested this using lcd7 cape connected to beaglebone black. The latest
kernel I could find on this board was a TI BSP based v3.14 kernel. So I
had to port these patches to that kernel. Cc Robert Nelson to see if he
knows about a more recent kernel supporting that cape.

I did not see any breakage with these patches applied although I did not
see any noticeable performance improvement as well.

I also tested along with continuous loop reading from
/sys/bus/iio/devices/iio:device0/in_voltage5_raw

I have pushed the kernel I used here in case anyone wants to take a look
at my porting.

http://git.ti.com/cgit/cgit.cgi/~nsekhar/ti-linux-kernel/nsekhar-ti-linux-kernel.git/log/?h=am335x-tsc-test

I also tested this series on AM335x EVM using the v3.18-rc5 kernel.
Again, no breakage but no improvement as well.

All testing was done using ts_test

FWIW, you can add:

Tested-by: Sekhar Nori <nsekhar@ti.com>

Thanks,
Sekhar

^ permalink raw reply

* Re: [PATCH] hid: sony: Add support for the third-party SMK PS3 Bluetooth Remote
From: Jiri Kosina @ 2014-11-20 14:05 UTC (permalink / raw)
  To: Frank Praznik; +Cc: linux-input
In-Reply-To: <1415821948-8468-1-git-send-email-frank.praznik@oh.rr.com>

On Wed, 12 Nov 2014, Frank Praznik wrote:

> Add vid/pid for the SMK branded third-party PS3 Bluetooth remote and enable
> support in the hid-sony driver.
> 
> Signed-off-by: Frank Praznik <frank.praznik@oh.rr.com>
> ---
>  drivers/hid/hid-core.c | 1 +
>  drivers/hid/hid-ids.h  | 3 +++
>  drivers/hid/hid-sony.c | 3 +++
>  3 files changed, 7 insertions(+)
> 
> diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
> index 73bd9e2..c8b8691 100644
> --- a/drivers/hid/hid-core.c
> +++ b/drivers/hid/hid-core.c
> @@ -1913,6 +1913,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
>  	{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) },
>  	{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) },
>  	{ HID_USB_DEVICE(USB_VENDOR_ID_SKYCABLE, USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER) },
> +	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_PS3_BDREMOTE) },
>  	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_BUZZ_CONTROLLER) },
>  	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER) },
>  	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_BDREMOTE) },
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index e23ab8b..b2c8a6a 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -812,6 +812,9 @@
>  #define USB_VENDOR_ID_SKYCABLE			0x1223
>  #define	USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER	0x3F07
>  
> +#define USB_VENDOR_ID_SMK		0x0609
> +#define USB_DEVICE_ID_SMK_PS3_BDREMOTE	0x0306
> +
>  #define USB_VENDOR_ID_SONY			0x054c
>  #define USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE	0x024b
>  #define USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE	0x0374
> diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
> index b6e6102..31e9d25 100644
> --- a/drivers/hid/hid-sony.c
> +++ b/drivers/hid/hid-sony.c
> @@ -2113,6 +2113,9 @@ static const struct hid_device_id sony_devices[] = {
>  	/* Logitech Harmony Adapter for PS3 */
>  	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_PS3),
>  		.driver_data = PS3REMOTE },
> +	/* SMK-Link PS3 BD Remote Control */
> +	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_PS3_BDREMOTE),
> +		.driver_data = PS3REMOTE },
>  	/* Sony Dualshock 4 controllers for PS4 */
>  	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER),
>  		.driver_data = DUALSHOCK4_CONTROLLER_USB },

Applied, thanks.

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* RE: [PATCH v4 3/6] mfd: ti_am335x_tscadc: Remove unwanted reg_se_cache save
From: Griffis, Brad @ 2014-11-20 14:23 UTC (permalink / raw)
  To: Nori, Sekhar, Dmitry Torokhov, Lee Jones
  Cc: R, Vignesh, Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala, Benoit Cousson, Tony Lindgren, Russell King,
	Jonathan Cameron, Hartmut Knaack,
	richardcochran-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org,
	Sebastian Andrzej Siewior, Lars-Peter Clausen, Peter Meerwald,
	Samuel Ortiz, Balbi, Felipe, Sanjeev Sharma, Paul Gortmaker,
	Jan Kardell, devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <546DF2F0.9080405-l0cyMroinI0@public.gmane.org>

> -----Original Message-----
> From: Nori, Sekhar
> Sent: Thursday, November 20, 2014 7:56 AM
>
> I also tested this series on AM335x EVM using the v3.18-rc5 kernel.
> Again, no breakage but no improvement as well.

The primary goal was not necessarily to improve performance of the touchscreen itself.  It was to reduce unnecessary CPU overhead introduced by the 275us udelay in the ISR.  On a related note, that 275us udelay is not something that worked for all boards.  For example, see the following forum thread:

http://e2e.ti.com/support/arm/sitara_arm/f/791/p/217587/775152.aspx#775152

In that thread the user was registering multiple press events for a single press.  By increasing the udelay to 1.5ms they were able to solve the problem.  Of course, having a 1.5ms delay in your ISR is a really bad thing to do...

I have another customer that was experiencing the same issue of registering multiple press events, and sure enough the 1.5ms delay "fixed" their problem too.  The patches allowed them to remove that gigantic delay from the ISR because that software delay has now become a hardware delay via CHARGECONFIG.  That customer is the one that needed 0xB000 which is MUCH larger than the value of 0x400 that was working fine for me on the EVM.
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: [PATCH 1/2] Input: add regulator haptic driver
From: Pankaj Dubey @ 2014-11-20 14:33 UTC (permalink / raw)
  To: Jaewon Kim
  Cc: Kukjin Kim, Dmitry Torokhov, Chanwoo Choi, linux-kernel,
	linux-input, linux-samsung-soc, Hyunhee Kim
In-Reply-To: <1416490300-28865-2-git-send-email-jaewon02.kim@samsung.com>

Hi Jaewon,

On 20 November 2014 19:01, Jaewon Kim <jaewon02.kim@samsung.com> wrote:
> This patch adds support for haptic driver controlled by
> voltage of regulator. And this driver support for
> Force Feedback interface from input framework
>
> Signed-off-by: Jaewon Kim <jaewon02.kim@samsung.com>
> Signed-off-by: Hyunhee Kim <hyunhee.kim@samsung.com>
> Acked-by: Kyungmin Park <kyungmin.park@samsung.com>
> ---
>  .../devicetree/bindings/input/regulator-haptic.txt |   24 ++
>  drivers/input/misc/Kconfig                         |   11 +
>  drivers/input/misc/Makefile                        |    1 +
>  drivers/input/misc/regulator-haptic.c              |  241 ++++++++++++++++++++
>  4 files changed, 277 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/input/regulator-haptic.txt
>  create mode 100644 drivers/input/misc/regulator-haptic.c
>
> diff --git a/Documentation/devicetree/bindings/input/regulator-haptic.txt b/Documentation/devicetree/bindings/input/regulator-haptic.txt
> new file mode 100644
> index 0000000..9f60e17
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/regulator-haptic.txt
> @@ -0,0 +1,24 @@
> +* Requlator Haptic Device Tree Bindings
> +
> +The regulator haptic driver controlled by voltage of regulator.
> +This driver implemented via Force Feedback interface.
> +
> +Required Properties:
> + - compatible : Should be "regulator-haptic"
> + - haptic-supply : Power supply for the haptic motor.
> +       [*] refer Documentation/devicetree/bindings/regulator/regulator.txt
> +
> + - max-microvolt : The maximum voltage value supplied to haptic motor.
> +               [The unit of the voltage is a micro]
> +
> + - min-microvolt : The minimum voltage value in which haptic motor reacts.
> +               [The unit of the voltage is a micro]
> +
> +Example:
> +
> +       regulator-haptic {
> +               compatible = "regulator-haptic";
> +               haptic-supply = <&motor_regulator>;
> +               max-microvolt = <2700000>;
> +               min-microvolt = <1100000>;
> +       };
> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
> index 23297ab..e5e556d 100644
> --- a/drivers/input/misc/Kconfig
> +++ b/drivers/input/misc/Kconfig
> @@ -394,6 +394,17 @@ 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"
> +       select INPUT_FF_MEMLESS
> +       help
> +         This option enables device driver support for the haptic controlled
> +         by 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
> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
> index 19c7603..1f135af 100644
> --- a/drivers/input/misc/Makefile
> +++ b/drivers/input/misc/Makefile
> @@ -53,6 +53,7 @@ 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_GPIO_ROTARY_ENCODER)        += rotary_encoder.o
>  obj-$(CONFIG_INPUT_SGI_BTNS)           += sgi_btns.o
> diff --git a/drivers/input/misc/regulator-haptic.c b/drivers/input/misc/regulator-haptic.c
> new file mode 100644
> index 0000000..1a83ecb
> --- /dev/null
> +++ b/drivers/input/misc/regulator-haptic.c
> @@ -0,0 +1,241 @@
> +/*
> + * 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/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/input.h>
> +#include <linux/slab.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/of.h>
> +
> +#define MAX_MAGNITUDE_SHIFT    16
> +
> +struct regulator_haptic {
> +       struct device *dev;
> +       struct input_dev *input_dev;
> +       struct regulator *regulator;
> +       struct work_struct work;
> +
> +       bool enabled;
> +       bool suspend_state;
> +       unsigned int max_volt;
> +       unsigned int min_volt;
> +       unsigned int intensity;
> +       unsigned int magnitude;
> +};
> +
> +static void regulator_haptic_enable(struct regulator_haptic *haptic)
> +{
> +       int error;
> +
> +       if (haptic->enabled)
> +               return;
> +
> +       error = regulator_enable(haptic->regulator);
> +       if (error) {
> +               dev_err(haptic->dev, "cannot enable regulator\n");
> +               return;
> +       }
> +
> +       haptic->enabled = true;
> +}
> +
> +static void regulator_haptic_disable(struct regulator_haptic *haptic)
> +{
> +       int error;
> +
> +       if (!haptic->enabled)
> +               return;
> +
> +       error = regulator_disable(haptic->regulator);
> +       if (error) {
> +               dev_err(haptic->dev, "cannot disable regulator\n");
> +               return;
> +       }
> +
> +       haptic->enabled = false;
> +}
> +
> +static void regulator_haptic_work(struct work_struct *work)
> +{
> +       struct regulator_haptic *haptic = container_of(work,
> +                                       struct regulator_haptic, work);
> +       int error;
> +
> +       error = regulator_set_voltage(haptic->regulator,
> +                       haptic->intensity + haptic->min_volt, haptic->max_volt);
> +       if (error) {
> +               dev_err(haptic->dev, "cannot set regulator voltage\n");
> +               return;
> +       }
> +
> +       if (haptic->magnitude)
> +               regulator_haptic_enable(haptic);
> +       else
> +               regulator_haptic_disable(haptic);
> +}
> +
> +static int regulator_haptic_play_effect(struct input_dev *input, void *data,
> +                                       struct ff_effect *effect)
> +{
> +       struct regulator_haptic *haptic = input_get_drvdata(input);
> +       u64 volt_mag_multi;
> +
> +       haptic->magnitude = effect->u.rumble.strong_magnitude;
> +       if (!haptic->magnitude)
> +               haptic->magnitude = effect->u.rumble.weak_magnitude;
> +
> +
> +       volt_mag_multi = (u64)(haptic->max_volt - haptic->min_volt) *
> +                                       haptic->magnitude;
> +       haptic->intensity = (unsigned int)(volt_mag_multi >>
> +                                       MAX_MAGNITUDE_SHIFT);
> +
> +       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_disable(haptic);
> +}
> +
> +static int regulator_haptic_parse_dt(struct regulator_haptic *haptic)
> +{
> +       struct device_node *node = haptic->dev->of_node;
> +       int error;
> +
> +       error = of_property_read_u32(node, "max-microvolt", &haptic->max_volt);
> +       if (error) {
> +               dev_err(haptic->dev, "cannot parse max-microvolt\n");
> +               return error;
> +       }
> +
> +       error = of_property_read_u32(node, "min-microvolt", &haptic->min_volt);
> +       if (error) {
> +               dev_err(haptic->dev, "cannot parse min-microvolt\n");
> +               return error;
> +       }
> +
> +       return 0;
> +}
> +
> +static int regulator_haptic_probe(struct platform_device *pdev)
> +{
> +       struct regulator_haptic *haptic;
> +       struct input_dev *input_dev;
> +       int error;
> +
> +       haptic = devm_kzalloc(&pdev->dev, sizeof(*haptic), GFP_KERNEL);
> +       if (!haptic)
> +               return -ENOMEM;
> +
> +       haptic->dev = &pdev->dev;
> +       haptic->enabled = false;
> +       haptic->suspend_state = false;
> +       INIT_WORK(&haptic->work, regulator_haptic_work);
> +
> +       error = regulator_haptic_parse_dt(haptic);
> +       if (error) {
> +               dev_err(&pdev->dev, "failed to parse device tree\n");
> +               return error;
> +       }
> +
> +       haptic->regulator = devm_regulator_get(&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;
> +       }
> +
> +       platform_set_drvdata(pdev, haptic);
> +
> +       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);
> +
> +       if (haptic->enabled) {
> +               regulator_haptic_disable(haptic);
> +               haptic->suspend_state = true;
> +       }
> +
> +       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);
> +
> +       if (haptic->enabled) {
> +               regulator_haptic_enable(haptic);
> +               haptic->suspend_state = false;
> +       }
> +
> +       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" },
> +       {},
> +};
> +
> +static struct platform_driver regulator_haptic_driver = {
> +       .probe          = regulator_haptic_probe,
> +       .driver         = {
> +               .name           = "regulator-haptic",
> +               .owner          = THIS_MODULE,

.owner is no more required for drivers using module_platform_driver()

Thanks,
Pankaj Dubey

> +               .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_ALIAS("platform:regulator-haptic");
> +MODULE_DESCRIPTION("Regulator haptic driver");
> +MODULE_LICENSE("GPL");
> --
> 1.7.9.5
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: [PATCH v4 3/6] mfd: ti_am335x_tscadc: Remove unwanted reg_se_cache save
From: Richard Cochran @ 2014-11-20 14:34 UTC (permalink / raw)
  To: Griffis, Brad
  Cc: Nori, Sekhar, Dmitry Torokhov, Lee Jones, R, Vignesh, Rob Herring,
	Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Benoit Cousson, Tony Lindgren, Russell King, Jonathan Cameron,
	Hartmut Knaack, Sebastian Andrzej Siewior, Lars-Peter Clausen,
	Peter Meerwald, Samuel Ortiz, Balbi, Felipe, Sanjeev Sharma,
	Paul Gortmaker, Jan Kardell
In-Reply-To: <912A29987EAE174BA6CF187D7CDFA9CE26F69163@DLEE08.ent.ti.com>

Brad,

What you wrote is just the kind of thing one would like to see in the
cover letter or change log...

On Thu, Nov 20, 2014 at 02:23:30PM +0000, Griffis, Brad wrote:
> In that thread the user was registering multiple press events for a single press.  By increasing the udelay to 1.5ms they were able to solve the problem.  Of course, having a 1.5ms delay in your ISR is a really bad thing to do...

I fully support removing the aweful ISR delay, as long as the result
works!
 
> I have another customer that was experiencing the same issue of registering multiple press events, and sure enough the 1.5ms delay "fixed" their problem too.  The patches allowed them to remove that gigantic delay from the ISR because that software delay has now become a hardware delay via CHARGECONFIG.  That customer is the one that needed 0xB000 which is MUCH larger than the value of 0x400 that was working fine for me on the EVM.

It would be really nice for TI to explain to board designers how to
calculate the proper value.

Thanks,
Richard

^ permalink raw reply

* Re: [PATCH v4 3/6] mfd: ti_am335x_tscadc: Remove unwanted reg_se_cache save
From: Richard Cochran @ 2014-11-20 14:40 UTC (permalink / raw)
  To: Sekhar Nori
  Cc: Dmitry Torokhov, Lee Jones, Vignesh R, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Benoit Cousson,
	Tony Lindgren, Russell King, Jonathan Cameron, Hartmut Knaack,
	Sebastian Andrzej Siewior, Lars-Peter Clausen, Peter Meerwald,
	Samuel Ortiz, Felipe Balbi, Brad Griffis, Sanjeev Sharma,
	Paul Gortmaker, Jan Kardell, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel
In-Reply-To: <546DF2F0.9080405-l0cyMroinI0@public.gmane.org>

On Thu, Nov 20, 2014 at 07:26:00PM +0530, Sekhar Nori wrote:
> I tested this using lcd7 cape connected to beaglebone black. The latest
> kernel I could find on this board was a TI BSP based v3.14 kernel. So I
> had to port these patches to that kernel. Cc Robert Nelson to see if he
> knows about a more recent kernel supporting that cape.

What is missing from mainline for the black?

(I thought it was fully supported by now.)
 
> I did not see any breakage with these patches applied although I did not
> see any noticeable performance improvement as well.

So, the jumping on pen-up persists, right?  That means the patch
series does not provide a general fix for that issue.
 
> I also tested this series on AM335x EVM using the v3.18-rc5 kernel.
> Again, no breakage but no improvement as well.

You still have jumps on a pen up event?

Thanks,
Richard

^ permalink raw reply

* Re: [git pull] Input updates for 3.18-rc4
From: Benjamin Tissoires @ 2014-11-20 14:44 UTC (permalink / raw)
  To: Ulrik De Bie
  Cc: Marcus Overhagen, Dmitry Torokhov, Hans de Goede,
	linux-kernel@vger.kernel.org, linux-input, Jiri Kosina
In-Reply-To: <20141119210552.GA5464@lantern>

On Wed, Nov 19, 2014 at 4:05 PM,  <ulrik.debie-os@e2big.org> wrote:
> Hi,
>
> Thanks Dmitry, the fix you provided will mitigate the regression. But
> there might be more that is going on for v4 hardware. The detection
> of PACKET_TRACKPOINT can be made more strict.
> Thank you Marcus and Benjamin for the reports.
>
> Sorry for the regression, it was not expected that lowest nibble of packet[3]
> is 0x6 for non-trackpoint packets.
>
> So we have two laptops where
> 1) according to the msb of the capabilities has no trackpoint
> Benjamin, can you give feedback if the laptop has actually a trackpoint/stick/
> how else one calls that strange but convenient thing in the middle of the
> keyboard ?


Ulrik, the reporter said that there is no trackstick on his device.
The exact reference is Samsung Notebook NP900X3E-A01SE.

Cheers,
Benjamin

^ permalink raw reply

* Re: [PATCH 1/2] Input: add regulator haptic driver
From: Dan Murphy @ 2014-11-20 15:09 UTC (permalink / raw)
  To: Pankaj Dubey, Jaewon Kim
  Cc: Kukjin Kim, Dmitry Torokhov, Chanwoo Choi, linux-kernel,
	linux-input, linux-samsung-soc, Hyunhee Kim
In-Reply-To: <CAGcde9HNGDbm8sNHjyJ-PW75o0eaH1w6KtsOsQx6kDjVqxch5Q@mail.gmail.com>

Hi

On 11/20/2014 08:33 AM, Pankaj Dubey wrote:
> Hi Jaewon,
>
> On 20 November 2014 19:01, Jaewon Kim <jaewon02.kim@samsung.com> wrote:
>> This patch adds support for haptic driver controlled by
>> voltage of regulator. And this driver support for
>> Force Feedback interface from input framework
>>
>> Signed-off-by: Jaewon Kim <jaewon02.kim@samsung.com>
>> Signed-off-by: Hyunhee Kim <hyunhee.kim@samsung.com>
>> Acked-by: Kyungmin Park <kyungmin.park@samsung.com>
>> ---
>>  .../devicetree/bindings/input/regulator-haptic.txt |   24 ++
>>  drivers/input/misc/Kconfig                         |   11 +
>>  drivers/input/misc/Makefile                        |    1 +
>>  drivers/input/misc/regulator-haptic.c              |  241 ++++++++++++++++++++
>>  4 files changed, 277 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/input/regulator-haptic.txt
>>  create mode 100644 drivers/input/misc/regulator-haptic.c
>>
>> diff --git a/Documentation/devicetree/bindings/input/regulator-haptic.txt b/Documentation/devicetree/bindings/input/regulator-haptic.txt
>> new file mode 100644
>> index 0000000..9f60e17
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/input/regulator-haptic.txt
>> @@ -0,0 +1,24 @@
>> +* Requlator Haptic Device Tree Bindings
>> +
>> +The regulator haptic driver controlled by voltage of regulator.
>> +This driver implemented via Force Feedback interface.
>> +
>> +Required Properties:
>> + - compatible : Should be "regulator-haptic"
>> + - haptic-supply : Power supply for the haptic motor.
>> +       [*] refer Documentation/devicetree/bindings/regulator/regulator.txt
>> +
>> + - max-microvolt : The maximum voltage value supplied to haptic motor.
>> +               [The unit of the voltage is a micro]
>> +
>> + - min-microvolt : The minimum voltage value in which haptic motor reacts.
>> +               [The unit of the voltage is a micro]
>> +
>> +Example:
>> +
>> +       regulator-haptic {

Should this be more about the function and not the driver name?
Somehting like

haptics {

>> +               compatible = "regulator-haptic";
>> +               haptic-supply = <&motor_regulator>;
>> +               max-microvolt = <2700000>;
>> +               min-microvolt = <1100000>;
>> +       };
>> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
>> index 23297ab..e5e556d 100644
>> --- a/drivers/input/misc/Kconfig
>> +++ b/drivers/input/misc/Kconfig
>> @@ -394,6 +394,17 @@ 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"
>> +       select INPUT_FF_MEMLESS
>> +       help
>> +         This option enables device driver support for the haptic controlled
>> +         by 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
>> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
>> index 19c7603..1f135af 100644
>> --- a/drivers/input/misc/Makefile
>> +++ b/drivers/input/misc/Makefile
>> @@ -53,6 +53,7 @@ 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_GPIO_ROTARY_ENCODER)        += rotary_encoder.o
>>  obj-$(CONFIG_INPUT_SGI_BTNS)           += sgi_btns.o
>> diff --git a/drivers/input/misc/regulator-haptic.c b/drivers/input/misc/regulator-haptic.c
>> new file mode 100644
>> index 0000000..1a83ecb
>> --- /dev/null
>> +++ b/drivers/input/misc/regulator-haptic.c
>> @@ -0,0 +1,241 @@
>> +/*
>> + * 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/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/input.h>
>> +#include <linux/slab.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/of.h>
>> +
>> +#define MAX_MAGNITUDE_SHIFT    16
>> +
>> +struct regulator_haptic {
>> +       struct device *dev;
>> +       struct input_dev *input_dev;
>> +       struct regulator *regulator;
>> +       struct work_struct work;
>> +
>> +       bool enabled;
>> +       bool suspend_state;

I don't understand what you are tracking here.  You only set it and unset
this value but never do any checking

>> +       unsigned int max_volt;
>> +       unsigned int min_volt;
>> +       unsigned int intensity;
>> +       unsigned int magnitude;
>> +};
>> +
>> +static void regulator_haptic_enable(struct regulator_haptic *haptic)
>> +{
>> +       int error;
>> +
>> +       if (haptic->enabled)
>> +               return;
>> +
>> +       error = regulator_enable(haptic->regulator);
>> +       if (error) {
>> +               dev_err(haptic->dev, "cannot enable regulator\n");
>> +               return;
>> +       }
>> +
>> +       haptic->enabled = true;
>> +}
>> +
>> +static void regulator_haptic_disable(struct regulator_haptic *haptic)
>> +{
>> +       int error;
>> +
>> +       if (!haptic->enabled)
>> +               return;
>> +
>> +       error = regulator_disable(haptic->regulator);
>> +       if (error) {
>> +               dev_err(haptic->dev, "cannot disable regulator\n");
>> +               return;
>> +       }
>> +
>> +       haptic->enabled = false;
>> +}

Can the enable/disable just be one function with an argument?
Then you can centralize the check and reduce the code footprint.

>> +
>> +static void regulator_haptic_work(struct work_struct *work)
>> +{
>> +       struct regulator_haptic *haptic = container_of(work,
>> +                                       struct regulator_haptic, work);
>> +       int error;
>> +
>> +       error = regulator_set_voltage(haptic->regulator,
>> +                       haptic->intensity + haptic->min_volt, haptic->max_volt);
>> +       if (error) {
>> +               dev_err(haptic->dev, "cannot set regulator voltage\n");
>> +               return;
>> +       }
>> +
>> +       if (haptic->magnitude)
>> +               regulator_haptic_enable(haptic);
>> +       else
>> +               regulator_haptic_disable(haptic);
>> +}
>> +
>> +static int regulator_haptic_play_effect(struct input_dev *input, void *data,
>> +                                       struct ff_effect *effect)
>> +{
>> +       struct regulator_haptic *haptic = input_get_drvdata(input);
>> +       u64 volt_mag_multi;
>> +
>> +       haptic->magnitude = effect->u.rumble.strong_magnitude;
>> +       if (!haptic->magnitude)
>> +               haptic->magnitude = effect->u.rumble.weak_magnitude;
>> +
>> +
>> +       volt_mag_multi = (u64)(haptic->max_volt - haptic->min_volt) *
>> +                                       haptic->magnitude;
>> +       haptic->intensity = (unsigned int)(volt_mag_multi >>
>> +                                       MAX_MAGNITUDE_SHIFT);
>> +
>> +       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_disable(haptic);
>> +}
>> +
>> +static int regulator_haptic_parse_dt(struct regulator_haptic *haptic)
>> +{
>> +       struct device_node *node = haptic->dev->of_node;
>> +       int error;
>> +
>> +       error = of_property_read_u32(node, "max-microvolt", &haptic->max_volt);
>> +       if (error) {
>> +               dev_err(haptic->dev, "cannot parse max-microvolt\n");
>> +               return error;
>> +       }
>> +
>> +       error = of_property_read_u32(node, "min-microvolt", &haptic->min_volt);
>> +       if (error) {
>> +               dev_err(haptic->dev, "cannot parse min-microvolt\n");
>> +               return error;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static int regulator_haptic_probe(struct platform_device *pdev)
>> +{
>> +       struct regulator_haptic *haptic;
>> +       struct input_dev *input_dev;
>> +       int error;
>> +
>> +       haptic = devm_kzalloc(&pdev->dev, sizeof(*haptic), GFP_KERNEL);
>> +       if (!haptic)
>> +               return -ENOMEM;
>> +
>> +       haptic->dev = &pdev->dev;
>> +       haptic->enabled = false;
>> +       haptic->suspend_state = false;
>> +       INIT_WORK(&haptic->work, regulator_haptic_work);
>> +
>> +       error = regulator_haptic_parse_dt(haptic);

Do you need platform data support for platforms that do not currently support
dt?

>> +       if (error) {
>> +               dev_err(&pdev->dev, "failed to parse device tree\n");
>> +               return error;
>> +       }
>> +
>> +       haptic->regulator = devm_regulator_get(&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;
>> +       }
>> +
>> +       platform_set_drvdata(pdev, haptic);
>> +
>> +       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);
>> +
>> +       if (haptic->enabled) {
>> +               regulator_haptic_disable(haptic);
>> +               haptic->suspend_state = true;



>> +       }
>> +
>> +       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);
>> +
>> +       if (haptic->enabled) {
>> +               regulator_haptic_enable(haptic);

We enable the regulator when we do the haptics work if magnitude is > 0.
And during suspend haptic->enabled may get set to false if it is true
so wouldn't haptic->enabled always be false here?

Plus do we want to continue the haptic response when coming out of suspend state?

>> +               haptic->suspend_state = false;
>> +       }
>> +
>> +       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" },
>> +       {},
>> +};
>> +
>> +static struct platform_driver regulator_haptic_driver = {
>> +       .probe          = regulator_haptic_probe,
>> +       .driver         = {
>> +               .name           = "regulator-haptic",
>> +               .owner          = THIS_MODULE,
> .owner is no more required for drivers using module_platform_driver()
>
> Thanks,
> Pankaj Dubey
>
>> +               .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_ALIAS("platform:regulator-haptic");
>> +MODULE_DESCRIPTION("Regulator haptic driver");
>> +MODULE_LICENSE("GPL");
>> --
>> 1.7.9.5
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> --
> To unsubscribe from this list: send the line "unsubscribe linux-input" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html


-- 
------------------
Dan Murphy


^ permalink raw reply

* Re: [PATCH] hid-multitouch: Add quirk for VTL touch panels
From: Benjamin Tissoires @ 2014-11-20 15:27 UTC (permalink / raw)
  To: Jiri Kosina
  Cc: Mathieu Magnaudet, Benjamin Tissoires, linux-input,
	linux-kernel@vger.kernel.org
In-Reply-To: <alpine.LNX.2.00.1411201421100.4454@pobox.suse.cz>

On Thu, Nov 20, 2014 at 8:21 AM, Jiri Kosina <jkosina@suse.cz> wrote:
> On Wed, 19 Nov 2014, Benjamin Tissoires wrote:
>
>> though this patch works for this particular device, I just thought at
>> something which may solve the problem in a different way. I asked
>> Mathieu to test the different solution, so I'd rather you to wait for
>> final confirmation before merging this patch.
>
> Thanks for the heads up, I am putting this patch on hold until further
> notice from you or Mathieu.
>

Mathieu confirmed this morning that using hid_hw_raw_request instead
of hid_hw_request while _setting_ the input_mode works for this
particular touchscreen. So I think we will simply switch to use this
sync operation instead of relying on the async one. We will need to
test this change a little bit, but it should be safe for BT and I2C at
least.

So, yes, we will drop this patch and have a better one in the next few
days. Thanks for holding this one :)

Cheers,
Benjamin

^ permalink raw reply

* Re: [PATCH 1/3] HID: wacom - return ENODEV for failed wacom_setup_pad_input_capabilities
From: Jiri Kosina @ 2014-11-20 16:30 UTC (permalink / raw)
  To: Ping Cheng; +Cc: linux-input, benjamin.tissoires, Ping Cheng
In-Reply-To: <1416346068-11266-1-git-send-email-pingc@wacom.com>

On Tue, 18 Nov 2014, Ping Cheng wrote:

> ENODEV indicates no device is added. Hence, the associated pad input device
> is simply freed.
> 
> Signed-off-by: Ping Cheng <pingc@wacom.com>
> Reviewed-by:  Benjamin Tissoires <benjamin.tissoires@redhat.com>

I have now queued the series for 3.19. Thanks,

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: PATCH change for HID_BATTERY_STRENGTH kconfig
From: Daniel Nicoletti @ 2014-11-20 17:01 UTC (permalink / raw)
  To: Jiri Kosina; +Cc: linux-input
In-Reply-To: <1415617019.16803.2.camel@bart>

Hi,

was the patch fine?

Thanks,

2014-11-10 8:56 GMT-02:00 Daniel Nicoletti <dantti12@gmail.com>:
> Em Ter, 2014-07-29 às 13:39 +0200, Jiri Kosina escreveu:
>> On Mon, 23 Jun 2014, Daniel Nicoletti wrote:
>>
>> > 2013-05-23 19:16 GMT-03:00 Jiri Kosina <jkosina@suse.cz>:
>> > > On Mon, 13 May 2013, Daniel Nicoletti wrote:
>> > >
>> > >> Hi,
>> > >> I'd like to propose this patch while removes the need for
>> > >> hid to be compiled build-in, as this is the same behavior
>> > >> that WiiMote and Wacom have.
>> > >> Is this ok?
>> > >
>> > > Makes sense to me. Could you please resend with proper changelog and
>> > > Signed-off-by: line?
>> > >
>> > > Thanks.
>> >
>> > Sorry it took more than a year for me to do this,
>> > so I called git send-email, and so far the CC and
>> > linux-input didn't get it, tho it said result OK.
>> > git send-email --to "Jiri Kosina <jkosina@suse.cz>" --cc
>> > "linux-input@vger.kernel.org"
>> > 0001-HID-Allows-for-HID_BATTERY_STRENGTH-config-be-enable.patch
>> >
>> > I'm pasting it here hoping gmail doesn't screw
>> > it, if you prefer that I send it using the above
>> > command mind you telling what I did wrong?
>> >
>> > Thanks.
>> >
>> > >From df740fd81dffbb74e13c622392fe9a945006fd3c Mon Sep 17 00:00:00 2001
>> > From: Daniel Nicoletti <dantti12@gmail.com>
>> > Date: Mon, 23 Jun 2014 10:31:47 -0300
>> > Subject: [PATCH] HID: Allows for HID_BATTERY_STRENGTH config be enabled
>> >  without the need for HID module to be built-in.
>> >
>>
>> It'd be nice to insert a few sentences of changelog here.
>>
>> Thanks.
>>
>> > Signed-off-by: Daniel Nicoletti <dantti12@gmail.com>
>> > ---
>> >  drivers/hid/Kconfig | 3 ++-
>> >  1 file changed, 2 insertions(+), 1 deletion(-)
>> >
>> > diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
>> > index fb52f3f..d6d85ad 100644
>> > --- a/drivers/hid/Kconfig
>> > +++ b/drivers/hid/Kconfig
>> > @@ -27,7 +27,8 @@ if HID
>> >
>> >  config HID_BATTERY_STRENGTH
>> >         bool "Battery level reporting for HID devices"
>> > -       depends on HID && POWER_SUPPLY && HID = POWER_SUPPLY
>> > +       depends on HID
>> > +       select POWER_SUPPLY
>> >         default n
>> >         ---help---
>> >         This option adds support of reporting battery strength (for HID devices
>> > --
>> > 2.0.0
>> >
>>
>> --
>> Jiri Kosina
>> SUSE Labs
>
> Sorry for yet another delay here is the patch with
> a better changelog (I hope so)...
>
>
> From 21608873917dcec012c7f9f1b84dd44774069de2 Mon Sep 17 00:00:00 2001
> From: Daniel Nicoletti <dantti12@gmail.com>
> Date: Mon, 23 Jun 2014 10:31:47 -0300
> Subject: [PATCH] HID: Allow HID_BATTERY_STRENGTH to be enabled
>
> Allows for HID_BATTERY_STRENGTH config be enabled
> without the need for HID module to be built-in,
> prior to this HID and POWER_SUPPLY had to be
> equal, and now we only select POWER_SUPPLY and
> depend on the HID module.
>
> Signed-off-by: Daniel Nicoletti <dantti12@gmail.com>
> ---
>  drivers/hid/Kconfig | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index f42df4d..602ac07 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -27,7 +27,8 @@ if HID
>
>  config HID_BATTERY_STRENGTH
>         bool "Battery level reporting for HID devices"
> -       depends on HID && POWER_SUPPLY && HID = POWER_SUPPLY
> +       depends on HID
> +       select POWER_SUPPLY
>         default n
>         ---help---
>         This option adds support of reporting battery strength (for HID
> devices
> --
> 2.1.0
>
>
>



-- 
Daniel Nicoletti

KDE Developer - http://dantti.wordpress.com
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
From: Dmitry Torokhov @ 2014-11-20 17:47 UTC (permalink / raw)
  To: Oliver Neukum
  Cc: scott, linux-kernel, linux-input, Vincent Wang, Jeff Chuang,
	Benson Leung, Benjamin Tissoires, Henrik Rydberg, David Herrmann
In-Reply-To: <1416479490.28599.13.camel@linux-0dmf.site>

Hi Oliver,

On Thu, Nov 20, 2014 at 11:31:30AM +0100, Oliver Neukum wrote:
> > +static int elants_i2c_initialize(struct elants_data *ts)
> > +{
> > +       struct i2c_client *client = ts->client;
> > +       int error, retry_cnt;
> > +       const u8 hello_packet[] = { 0x55, 0x55, 0x55, 0x55 };
> > +       const u8 recov_packet[] = { 0x55, 0x55, 0x80, 0x80 };
> > +       u8 buf[HEADER_SIZE];
> > +
> 
> Strictly speaking you should disable preemption here.

Umm, why?

> 
> > +       for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) {
> > +               error = elants_i2c_sw_reset(client);
> > +               if (error) {
> > +                       /* Continue initializing if it's the last try
> > */
> > +                       if (retry_cnt < MAX_RETRIES - 1)
> > +                               continue;
> > +               }

Thanks.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
From: 'Dmitry Torokhov' @ 2014-11-20 17:51 UTC (permalink / raw)
  To: ELAN 劉嘉駿
  Cc: 'Oliver Neukum', linux-kernel, linux-input,
	'Vincent Wang', 'Jeff Chuang',
	'Benson Leung', 'Benjamin Tissoires',
	'Henrik Rydberg', 'David Herrmann'
In-Reply-To: <000001d004bd$0ad90830$208b1890$@emc.com.tw>

On Thu, Nov 20, 2014 at 08:25:15PM +0800, ELAN 劉嘉駿 wrote:
> 
> Hi Oliver:
> 
> -----Original Message-----
> From: Oliver Neukum [mailto:oneukum@suse.de] 
> Sent: Thursday, November 20, 2014 6:32 PM
> To: Dmitry Torokhov
> Cc: scott; linux-kernel@vger.kernel.org; linux-input@vger.kernel.org; Vincent Wang; Jeff Chuang; Benson Leung; Benjamin Tissoires; Henrik Rydberg; David Herrmann
> Subject: Re: [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
> 
> 
> 
> > +static int elants_i2c_sw_reset(struct i2c_client *client) {
> > +       const u8 soft_rst_cmd[] = { 0x77, 0x77, 0x77, 0x77 };
> > +       int error;
> > +
> > +       error = elants_i2c_send(client, soft_rst_cmd,
> > +                               sizeof(soft_rst_cmd));
> > +       if (error) {
> > +               dev_err(&client->dev, "software reset failed: %d\n",
> > error);
> > +               return error;
> > +       }
> > +
> > +       /*
> > +        * We should wait at least 10 msec (but no more than 40)
> > before
> > +        * sending fastboot or IAP command to the device.
> > +        */
> > +       msleep(30);
> 
> If timing is critical in both ways, you should take our guarantee if sufficient sleep. I suggest you sleep 11msecs.
> 
> Sorry, this is our firmware tricky but it should be sleep longer than 30ms. Actually 70ms.
> (Dmitry, I need to modify this delay time to 70ms. Sorry for the changed over and over again; 
> 	because we consider big sensor solution which need to use more ICs and need more longer delay)
> 

Wait, before you said that upper bound is critical, otherwise the device will
ignore the next command and will not enter IAP mode. So what will happen if we
increase the wait to 70 msecs and use the driver with smaller sensors?

Thanks.

-- 
Dmitry
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: [Question: drivers/input/input.c] Why input_no is initialized to 0 ?
From: Dmitry Torokhov @ 2014-11-20 19:29 UTC (permalink / raw)
  To: Aniroop Mathur; +Cc: linux-input@vger.kernel.org
In-Reply-To: <CADYu30_O1CPKyAkQb+dQfQViDurej5Y37AbR-bbWd3TpBg=HKw@mail.gmail.com>

On Thursday, November 20, 2014 02:49:24 AM Aniroop Mathur wrote:
> Hello Mr. Torokhov,
> 
> Thank you for your answer.
> 
> As it is not a problem now, How about changing it to -1 ?
> Although extra subtraction is not expensive but still we could optimize it
> as we want kernel to optimized as much as possible.

Send patches. I think we have the same pattern in several subsystems.

Thanks.

-- 
Dmitry

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox