Linux Input/HID development
 help / color / mirror / Atom feed
* [PATCH 1/2] Input: elantech - enable middle button support on 2 ThinkPads
From: Aaron Ma @ 2019-05-19  7:27 UTC (permalink / raw)
  To: aaron.ma, linux-input, linux-kernel, dmitry.torokhov,
	benjamin.tissoires

Adding 2 new touchpad PNPIDs to enable middle button support.

Cc: stable@vger.kernel.org
Signed-off-by: Aaron Ma <aaron.ma@canonical.com>
---
 drivers/input/mouse/elantech.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/input/mouse/elantech.c b/drivers/input/mouse/elantech.c
index a7f8b1614559..530142b5a115 100644
--- a/drivers/input/mouse/elantech.c
+++ b/drivers/input/mouse/elantech.c
@@ -1189,6 +1189,8 @@ static const char * const middle_button_pnp_ids[] = {
 	"LEN2132", /* ThinkPad P52 */
 	"LEN2133", /* ThinkPad P72 w/ NFC */
 	"LEN2134", /* ThinkPad P72 */
+	"LEN0407",
+	"LEN0408",
 	NULL
 };
 
-- 
2.17.1

^ permalink raw reply related

* Re: [PATCH 1/2] Input: atmel_mxt_ts - add wakeup support
From: stefano.manni @ 2019-05-18 16:55 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: nick, robh+dt, mark.rutland, linux-input, devicetree,
	linux-kernel
In-Reply-To: <20190517213016.GA93581@dtor-ws>

Hi Dmitry,

On Fri, 2019-05-17 at 14:30 -0700, Dmitry Torokhov wrote:
> Hi Sefano,
> 
> On Fri, May 17, 2019 at 11:17:40PM +0200, Stefano Manni wrote:
> > Add wakeup support to the maxtouch driver.
> > The device can wake up the system from suspend,
> > mark the IRQ as wakeup capable, so that device
> > irq is not disabled during system suspend.
> 
> This should already be handled by I2C core, see lines after "if
> (client->flags & I2C_CLIENT_WAKE)" in drivers/i2c/i2c-core-base.c.
> 
> Unless there is dedicated wakeup interrupt we configure main
> interrupt
> as wake source.
> 

what's about the other drivers (e.g. ili210x.c) doing like this?
Shall they be purged?

Thank you.

^ permalink raw reply

* Re: [PATCH v3] HID: fix A4Tech horizontal scrolling
From: Igor Kushnir @ 2019-05-18  9:08 UTC (permalink / raw)
  To: Błażej Szczygieł
  Cc: peter.hutterer, Jiri Kosina, Benjamin Tissoires, linux-input,
	linux-kernel
In-Reply-To: <20190512203313.18756-1-spaz16@wp.pl>

Hi!

I have verified that the PATCH v3 applied to kernel 5.0.15 fixes 
horizontal scrolling for my A4Tech WOP-49Z mouse just as well as the 
previous patch did.

Thank you,
Igor

On 5/12/19 11:33 PM, Błażej Szczygieł wrote:
> Since recent high resolution scrolling changes the A4Tech driver must
> check for the "REL_WHEEL_HI_RES" usage code.
> 
> Link: https://bugzilla.kernel.org/show_bug.cgi?id=203369
> Fixes: 2dc702c991e3774af9d7ce410eef410ca9e2357e ("HID: input: use the
> Resolution Multiplier for high-resolution scrolling")
> 
> Signed-off-by: Błażej Szczygieł <spaz16@wp.pl>
> ---
> Changes in v2:
> - changed commit message
> 
> Changes in v3:
> - send also high resolution events
> 
>   drivers/hid/hid-a4tech.c | 11 ++++++++---
>   1 file changed, 8 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/hid/hid-a4tech.c b/drivers/hid/hid-a4tech.c
> index 9428ea7cdf8a..c3a6ce3613fe 100644
> --- a/drivers/hid/hid-a4tech.c
> +++ b/drivers/hid/hid-a4tech.c
> @@ -38,8 +38,10 @@ static int a4_input_mapped(struct hid_device *hdev, struct hid_input *hi,
>   {
>   	struct a4tech_sc *a4 = hid_get_drvdata(hdev);
>   
> -	if (usage->type == EV_REL && usage->code == REL_WHEEL)
> +	if (usage->type == EV_REL && usage->code == REL_WHEEL_HI_RES) {
>   		set_bit(REL_HWHEEL, *bit);
> +		set_bit(REL_HWHEEL_HI_RES, *bit);
> +	}
>   
>   	if ((a4->quirks & A4_2WHEEL_MOUSE_HACK_7) && usage->hid == 0x00090007)
>   		return -1;
> @@ -60,7 +62,7 @@ static int a4_event(struct hid_device *hdev, struct hid_field *field,
>   	input = field->hidinput->input;
>   
>   	if (a4->quirks & A4_2WHEEL_MOUSE_HACK_B8) {
> -		if (usage->type == EV_REL && usage->code == REL_WHEEL) {
> +		if (usage->type == EV_REL && usage->code == REL_WHEEL_HI_RES) {
>   			a4->delayed_value = value;
>   			return 1;
>   		}
> @@ -68,6 +70,8 @@ static int a4_event(struct hid_device *hdev, struct hid_field *field,
>   		if (usage->hid == 0x000100b8) {
>   			input_event(input, EV_REL, value ? REL_HWHEEL :
>   					REL_WHEEL, a4->delayed_value);
> +			input_event(input, EV_REL, value ? REL_HWHEEL_HI_RES :
> +					REL_WHEEL_HI_RES, a4->delayed_value * 120);
>   			return 1;
>   		}
>   	}
> @@ -77,8 +81,9 @@ static int a4_event(struct hid_device *hdev, struct hid_field *field,
>   		return 1;
>   	}
>   
> -	if (usage->code == REL_WHEEL && a4->hw_wheel) {
> +	if (usage->code == REL_WHEEL_HI_RES && a4->hw_wheel) {
>   		input_event(input, usage->type, REL_HWHEEL, value);
> +		input_event(input, usage->type, REL_HWHEEL_HI_RES, value * 120);
>   		return 1;
>   	}
>   
> 

^ permalink raw reply

* Re: [PATCH 1/2] Input: atmel_mxt_ts - add wakeup support
From: Dmitry Torokhov @ 2019-05-17 21:30 UTC (permalink / raw)
  To: Stefano Manni
  Cc: nick, robh+dt, mark.rutland, linux-input, devicetree,
	linux-kernel
In-Reply-To: <20190517211741.8906-1-stefano.manni@gmail.com>

Hi Sefano,

On Fri, May 17, 2019 at 11:17:40PM +0200, Stefano Manni wrote:
> Add wakeup support to the maxtouch driver.
> The device can wake up the system from suspend,
> mark the IRQ as wakeup capable, so that device
> irq is not disabled during system suspend.

This should already be handled by I2C core, see lines after "if
(client->flags & I2C_CLIENT_WAKE)" in drivers/i2c/i2c-core-base.c.

Unless there is dedicated wakeup interrupt we configure main interrupt
as wake source.

Thanks.

-- 
Dmitry

^ permalink raw reply

* [PATCH 2/2] dt-bindings: input: Add wakeup-source for atmel,maxtouch
From: Stefano Manni @ 2019-05-17 21:17 UTC (permalink / raw)
  To: nick, dmitry.torokhov, robh+dt, mark.rutland
  Cc: Stefano Manni, linux-input, devicetree, linux-kernel
In-Reply-To: <20190517211741.8906-1-stefano.manni@gmail.com>

Add wakeup support to the maxtouch driver.

Signed-off-by: Stefano Manni <stefano.manni@gmail.com>
---
 Documentation/devicetree/bindings/input/atmel,maxtouch.txt | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/Documentation/devicetree/bindings/input/atmel,maxtouch.txt b/Documentation/devicetree/bindings/input/atmel,maxtouch.txt
index c88919480d37..7491a8843c85 100644
--- a/Documentation/devicetree/bindings/input/atmel,maxtouch.txt
+++ b/Documentation/devicetree/bindings/input/atmel,maxtouch.txt
@@ -31,6 +31,10 @@ Optional properties for main touchpad device:
 
 - reset-gpios: GPIO specifier for the touchscreen's reset pin (active low)
 
+- wakeup-source: use any event on touchscreen as wakeup event.
+    (Legacy property support: "linux,wakeup")
+
+
 Example:
 
 	touch@4b {
@@ -38,4 +42,5 @@ Example:
 		reg = <0x4b>;
 		interrupt-parent = <&gpio>;
 		interrupts = <TEGRA_GPIO(W, 3) IRQ_TYPE_LEVEL_LOW>;
+		wakeup-source;
 	};
-- 
2.20.1

^ permalink raw reply related

* [PATCH 1/2] Input: atmel_mxt_ts - add wakeup support
From: Stefano Manni @ 2019-05-17 21:17 UTC (permalink / raw)
  To: nick, dmitry.torokhov, robh+dt, mark.rutland
  Cc: Stefano Manni, linux-input, devicetree, linux-kernel

Add wakeup support to the maxtouch driver.
The device can wake up the system from suspend,
mark the IRQ as wakeup capable, so that device
irq is not disabled during system suspend.

Signed-off-by: Stefano Manni <stefano.manni@gmail.com>
---
 drivers/input/touchscreen/atmel_mxt_ts.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c
index 5c63d25ce84e..2e0abc0b665d 100644
--- a/drivers/input/touchscreen/atmel_mxt_ts.c
+++ b/drivers/input/touchscreen/atmel_mxt_ts.c
@@ -3041,6 +3041,7 @@ static const struct dmi_system_id chromebook_T9_suspend_dmi[] = {
 static int mxt_probe(struct i2c_client *client, const struct i2c_device_id *id)
 {
 	struct mxt_data *data;
+	struct device_node *node = client->dev.of_node;
 	int error;
 
 	/*
@@ -3125,6 +3126,12 @@ static int mxt_probe(struct i2c_client *client, const struct i2c_device_id *id)
 		goto err_free_object;
 	}
 
+	if (node) {
+		if (of_property_read_bool(node, "wakeup-source") ||
+			of_property_read_bool(node, "linux,wakeup"))
+			device_init_wakeup(&client->dev, true);
+	}
+
 	return 0;
 
 err_free_object:
@@ -3156,6 +3163,9 @@ static int __maybe_unused mxt_suspend(struct device *dev)
 
 	mutex_lock(&input_dev->mutex);
 
+	if (device_may_wakeup(&client->dev))
+		enable_irq_wake(client->irq);
+
 	if (input_dev->users)
 		mxt_stop(data);
 
@@ -3175,6 +3185,9 @@ static int __maybe_unused mxt_resume(struct device *dev)
 
 	mutex_lock(&input_dev->mutex);
 
+	if (device_may_wakeup(&client->dev))
+		disable_irq_wake(client->irq);
+
 	if (input_dev->users)
 		mxt_start(data);
 
-- 
2.20.1

^ permalink raw reply related

* Re: [PATCH 3/3] HID: wacom: Sync INTUOSP2_BT touch state after each frame if necessary
From: Benjamin Tissoires @ 2019-05-17 16:24 UTC (permalink / raw)
  To: Gerecke, Jason
  Cc: open list:HID CORE LAYER, Ping Cheng, Aaron Armstrong Skomra,
	Jason Gerecke, 3.8+
In-Reply-To: <20190507185322.7168-3-jason.gerecke@wacom.com>

On Tue, May 7, 2019 at 8:53 PM Gerecke, Jason <killertofu@gmail.com> wrote:
>
> From: Jason Gerecke <jason.gerecke@wacom.com>
>
> The Bluetooth interface of the 2nd-gen Intuos Pro batches together four
> independent "frames" of finger data into a single report. Each frame
> is essentially equivalent to a single USB report, with the up-to-10
> fingers worth of information being spread across two frames. At the
> moment the driver only calls `input_sync` after processing all four
> frames have been processed, which can result in the driver sending
> multiple updates for a single slot within the same SYN_REPORT. This
> can confuse userspace, so modify the driver to sync more often if
> necessary (i.e., after reporting the state of all fingers).
>
> Fixes: 4922cd26f0 ("HID: wacom: Support 2nd-gen Intuos Pro's Bluetooth classic interface")
> Cc: <stable@vger.kernel.org> # 4.11+
> Signed-off-by: Jason Gerecke <jason.gerecke@wacom.com>
> ---

series applied to for-5.2/upstream-fixes

Cheers,
Benjamin

>  drivers/hid/wacom_wac.c | 10 ++++++++--
>  1 file changed, 8 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c
> index e848445236d8..09b8e4aac82f 100644
> --- a/drivers/hid/wacom_wac.c
> +++ b/drivers/hid/wacom_wac.c
> @@ -1371,11 +1371,17 @@ static void wacom_intuos_pro2_bt_touch(struct wacom_wac *wacom)
>                 if (wacom->num_contacts_left <= 0) {
>                         wacom->num_contacts_left = 0;
>                         wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
> +                       input_sync(touch_input);
>                 }
>         }
>
> -       input_report_switch(touch_input, SW_MUTE_DEVICE, !(data[281] >> 7));
> -       input_sync(touch_input);
> +       if (wacom->num_contacts_left == 0) {
> +               // Be careful that we don't accidentally call input_sync with
> +               // only a partial set of fingers of processed
> +               input_report_switch(touch_input, SW_MUTE_DEVICE, !(data[281] >> 7));
> +               input_sync(touch_input);
> +       }
> +
>  }
>
>  static void wacom_intuos_pro2_bt_pad(struct wacom_wac *wacom)
> --
> 2.21.0
>

^ permalink raw reply

* Re: [PATCH 1/2] HID: wacom: Don't set tool type until we're in range
From: Benjamin Tissoires @ 2019-05-17 14:27 UTC (permalink / raw)
  To: Gerecke, Jason
  Cc: open list:HID CORE LAYER, Ping Cheng, Aaron Armstrong Skomra,
	Jason Gerecke, 3.8+, Aaron Armstrong Skomra
In-Reply-To: <20190424221258.19992-1-jason.gerecke@wacom.com>

On Thu, Apr 25, 2019 at 12:13 AM Gerecke, Jason <killertofu@gmail.com> wrote:
>
> From: Jason Gerecke <jason.gerecke@wacom.com>
>
> The serial number and tool type information that is reported by the tablet
> while a pen is merely "in prox" instead of fully "in range" can be stale
> and cause us to report incorrect tool information. Serial number, tool
> type, and other information is only valid once the pen comes fully in range
> so we should be careful to not use this information until that point.
>
> In particular, this issue may cause the driver to incorectly report
> BTN_TOOL_RUBBER after switching from the eraser tool back to the pen.
>
> Fixes: a48324de6d ("HID: wacom: Bluetooth IRQ for Intuos Pro should handle prox/range")
> Cc: <stable@vger.kernel.org> # 4.11+
> Signed-off-by: Jason Gerecke <jason.gerecke@wacom.com>
> Reviewed-by: Aaron Armstrong Skomra <aaron.skomra@wacom.com>
> ---

Series applied to for-5.2/fixes

Sorry for the delay

Cheers,
Benjamin

>  drivers/hid/wacom_wac.c | 17 ++++++++++++++++-
>  1 file changed, 16 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c
> index 747730d32ab6..4c1bc239207e 100644
> --- a/drivers/hid/wacom_wac.c
> +++ b/drivers/hid/wacom_wac.c
> @@ -1236,13 +1236,13 @@ static void wacom_intuos_pro2_bt_pen(struct wacom_wac *wacom)
>                 /* Add back in missing bits of ID for non-USI pens */
>                 wacom->id[0] |= (wacom->serial[0] >> 32) & 0xFFFFF;
>         }
> -       wacom->tool[0]   = wacom_intuos_get_tool_type(wacom_intuos_id_mangle(wacom->id[0]));
>
>         for (i = 0; i < pen_frames; i++) {
>                 unsigned char *frame = &data[i*pen_frame_len + 1];
>                 bool valid = frame[0] & 0x80;
>                 bool prox = frame[0] & 0x40;
>                 bool range = frame[0] & 0x20;
> +               bool invert = frame[0] & 0x10;
>
>                 if (!valid)
>                         continue;
> @@ -1251,9 +1251,24 @@ static void wacom_intuos_pro2_bt_pen(struct wacom_wac *wacom)
>                         wacom->shared->stylus_in_proximity = false;
>                         wacom_exit_report(wacom);
>                         input_sync(pen_input);
> +
> +                       wacom->tool[0] = 0;
> +                       wacom->id[0] = 0;
> +                       wacom->serial[0] = 0;
>                         return;
>                 }
> +
>                 if (range) {
> +                       if (!wacom->tool[0]) { /* first in range */
> +                               /* Going into range select tool */
> +                               if (invert)
> +                                       wacom->tool[0] = BTN_TOOL_RUBBER;
> +                               else if (wacom->id[0])
> +                                       wacom->tool[0] = wacom_intuos_get_tool_type(wacom->id[0]);
> +                               else
> +                                       wacom->tool[0] = BTN_TOOL_PEN;
> +                       }
> +
>                         input_report_abs(pen_input, ABS_X, get_unaligned_le16(&frame[1]));
>                         input_report_abs(pen_input, ABS_Y, get_unaligned_le16(&frame[3]));
>
> --
> 2.21.0
>

^ permalink raw reply

* [RFC PATCH v2 4/4] ARM: dts: imx6dl-yapp4: Enable MPR121 touch keypad on Hydra
From: Michal Vokáč @ 2019-05-17 13:12 UTC (permalink / raw)
  To: Dmitry Torokhov, Rob Herring
  Cc: Mark Rutland, Shawn Guo, Sascha Hauer, Fabio Estevam, linux-input,
	devicetree, linux-kernel, Pengutronix Kernel Team,
	Michal Vokáč
In-Reply-To: <1558098773-47416-1-git-send-email-michal.vokac@ysoft.com>

Enable the I2C connected touch keypad on Hydra board.
Use the polled binding as the interrupt line is not available.

Signed-off-by: Michal Vokáč <michal.vokac@ysoft.com>
---
 arch/arm/boot/dts/imx6dl-yapp4-common.dtsi | 12 ++++++++++++
 arch/arm/boot/dts/imx6dl-yapp4-hydra.dts   |  4 ++++
 2 files changed, 16 insertions(+)

diff --git a/arch/arm/boot/dts/imx6dl-yapp4-common.dtsi b/arch/arm/boot/dts/imx6dl-yapp4-common.dtsi
index e8d800fec637..65a670e5bd4f 100644
--- a/arch/arm/boot/dts/imx6dl-yapp4-common.dtsi
+++ b/arch/arm/boot/dts/imx6dl-yapp4-common.dtsi
@@ -4,6 +4,7 @@
 
 #include <dt-bindings/gpio/gpio.h>
 #include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/input/input.h>
 #include <dt-bindings/pwm/pwm.h>
 
 / {
@@ -330,6 +331,17 @@
 		vcc-supply = <&sw2_reg>;
 		status = "disabled";
 	};
+
+	touchkeys: keys@5a {
+		compatible = "fsl,mpr121-touchkey-polled";
+		reg = <0x5a>;
+		vdd-supply = <&sw2_reg>;
+		autorepeat;
+		linux,keycodes = <KEY_1>, <KEY_2>, <KEY_3>, <KEY_4>, <KEY_5>,
+				<KEY_6>, <KEY_7>, <KEY_8>, <KEY_9>,
+				<KEY_BACKSPACE>, <KEY_0>, <KEY_ENTER>;
+		status = "disabled";
+	};
 };
 
 &iomuxc {
diff --git a/arch/arm/boot/dts/imx6dl-yapp4-hydra.dts b/arch/arm/boot/dts/imx6dl-yapp4-hydra.dts
index f97927064750..84c275bfdd38 100644
--- a/arch/arm/boot/dts/imx6dl-yapp4-hydra.dts
+++ b/arch/arm/boot/dts/imx6dl-yapp4-hydra.dts
@@ -45,6 +45,10 @@
 	status = "okay";
 };
 
+&touchkeys {
+	status = "okay";
+};
+
 &usdhc3 {
 	status = "okay";
 };
-- 
2.1.4

^ permalink raw reply related

* [RFC PATCH v2 3/4] Input: mpr121-polled: Add write-through cache to detect corrupted registers
From: Michal Vokáč @ 2019-05-17 13:12 UTC (permalink / raw)
  To: Dmitry Torokhov, Rob Herring
  Cc: Mark Rutland, Shawn Guo, Sascha Hauer, Fabio Estevam, linux-input,
	devicetree, linux-kernel, Pengutronix Kernel Team,
	Michal Vokáč
In-Reply-To: <1558098773-47416-1-git-send-email-michal.vokac@ysoft.com>

The MPR121 chip (and I2C bus in general) is quite sensitive to ESD.
An electrostatic discharge can easily cause a reset of the MPR121 chip.
Even though the chip then recovers and respond to read/write commands,
it is not properly initialized.

This state can be detected using a write-through cache of the internal
registers. Each time a register is written to, its value is stored in
the cache and marked as valid. Once per MPR121_REG_CACHE_CHECK_LIMIT
polls one valid cache value is compared with its corresponding register
value. In case of difference an error counter is increased. If the error
counter limit is exceeded, the chip is re-initialized.

Signed-off-by: Michal Vokáč <michal.vokac@ysoft.com>
---
 drivers/input/keyboard/mpr121_touchkey_polled.c | 100 +++++++++++++++++++++---
 1 file changed, 88 insertions(+), 12 deletions(-)

diff --git a/drivers/input/keyboard/mpr121_touchkey_polled.c b/drivers/input/keyboard/mpr121_touchkey_polled.c
index e5e80530c9d8..6536d9b2eeb8 100644
--- a/drivers/input/keyboard/mpr121_touchkey_polled.c
+++ b/drivers/input/keyboard/mpr121_touchkey_polled.c
@@ -67,6 +67,19 @@
 #define MPR121_POLL_INTERVAL_REINIT	500
 #define MPR121_POLL_RETRY_MAX		4
 
+#define MPR121_REG_CACHE_MIN_ADDR	0x2b
+#define MPR121_REG_CACHE_MAX_ADDR	0x7f
+#define MPR121_REG_CACHE_SIZE		\
+		(MPR121_REG_CACHE_MAX_ADDR - MPR121_REG_CACHE_MIN_ADDR + 1)
+#define MPR121_REG_CACHE_CHECK_LIMIT	8
+#define mpr121_addr_to_cache_idx(addr)	(addr - MPR121_REG_CACHE_MIN_ADDR)
+#define mpr121_cache_idx_to_addr(idx)	(idx + MPR121_REG_CACHE_MIN_ADDR)
+
+struct mpr121_polled_reg_cache {
+	bool valid;
+	u8 value;
+};
+
 struct mpr121_polled {
 	struct i2c_client *client;
 	struct input_dev *input_dev;
@@ -76,6 +89,9 @@ struct mpr121_polled {
 	u32 keycodes[MPR121_MAX_KEY_COUNT];
 	u8 read_errors;
 	int vdd_uv;
+	struct mpr121_polled_reg_cache reg_cache[MPR121_REG_CACHE_SIZE];
+	u8 reg_cache_check_count;
+	u8 reg_cache_next_check_item;
 };
 
 struct mpr121_polled_init_register {
@@ -95,6 +111,29 @@ static const struct mpr121_polled_init_register init_reg_table[] = {
 	{ AUTO_CONFIG_CTRL_ADDR, 0x0b },
 };
 
+static int mpr121_polled_write_reg(struct mpr121_polled *mpr121, u8 addr,
+				   u8 value)
+{
+	struct i2c_client *client = mpr121->client;
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(client, addr, value);
+	if (ret < 0) {
+		dev_err(&client->dev, "i2c write error: %d\n", ret);
+		return ret;
+	}
+
+	if (addr >= MPR121_REG_CACHE_MIN_ADDR &&
+	    addr <= MPR121_REG_CACHE_MAX_ADDR) {
+		u8 i = mpr121_addr_to_cache_idx(addr);
+
+		mpr121->reg_cache[i].valid = 1;
+		mpr121->reg_cache[i].value = value;
+	}
+
+	return 0;
+}
+
 static void mpr121_polled_vdd_supply_disable(void *data)
 {
 	struct regulator *vdd_supply = data;
@@ -140,18 +179,18 @@ static int mpr121_polled_phys_init(struct mpr121_polled *mpr121,
 	int i, t, vdd, ret;
 
 	/* Set stop mode prior to writing any register */
-	ret = i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, 0x00);
+	ret = mpr121_polled_write_reg(mpr121, ELECTRODE_CONF_ADDR, 0x00);
 	if (ret < 0)
 		goto err_i2c_write;
 
 	/* Set up touch/release threshold for ele0-ele11 */
 	for (i = 0; i <= MPR121_MAX_KEY_COUNT; i++) {
 		t = ELE0_TOUCH_THRESHOLD_ADDR + (i * 2);
-		ret = i2c_smbus_write_byte_data(client, t, TOUCH_THRESHOLD);
+		ret = mpr121_polled_write_reg(mpr121, t, TOUCH_THRESHOLD);
 		if (ret < 0)
 			goto err_i2c_write;
-		ret = i2c_smbus_write_byte_data(client, t + 1,
-						RELEASE_THRESHOLD);
+		ret = mpr121_polled_write_reg(mpr121, t + 1,
+					      RELEASE_THRESHOLD);
 		if (ret < 0)
 			goto err_i2c_write;
 	}
@@ -159,7 +198,7 @@ static int mpr121_polled_phys_init(struct mpr121_polled *mpr121,
 	/* Set up init register */
 	for (i = 0; i < ARRAY_SIZE(init_reg_table); i++) {
 		reg = &init_reg_table[i];
-		ret = i2c_smbus_write_byte_data(client, reg->addr, reg->val);
+		ret = mpr121_polled_write_reg(mpr121, reg->addr, reg->val);
 		if (ret < 0)
 			goto err_i2c_write;
 	}
@@ -173,9 +212,9 @@ static int mpr121_polled_phys_init(struct mpr121_polled *mpr121,
 	usl = ((vdd - 700) * 256) / vdd;
 	lsl = (usl * 65) / 100;
 	tl = (usl * 90) / 100;
-	ret = i2c_smbus_write_byte_data(client, AUTO_CONFIG_USL_ADDR, usl);
-	ret |= i2c_smbus_write_byte_data(client, AUTO_CONFIG_LSL_ADDR, lsl);
-	ret |= i2c_smbus_write_byte_data(client, AUTO_CONFIG_TL_ADDR, tl);
+	ret = mpr121_polled_write_reg(mpr121, AUTO_CONFIG_USL_ADDR, usl);
+	ret |= mpr121_polled_write_reg(mpr121, AUTO_CONFIG_LSL_ADDR, lsl);
+	ret |= mpr121_polled_write_reg(mpr121, AUTO_CONFIG_TL_ADDR, tl);
 
 	/*
 	 * Quick charge bit will let the capacitive charge to ready
@@ -183,7 +222,7 @@ static int mpr121_polled_phys_init(struct mpr121_polled *mpr121,
 	 * boot.
 	 */
 	eleconf = mpr121->keycount | ELECTRODE_CONF_QUICK_CHARGE;
-	ret |= i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR,
+	ret |= mpr121_polled_write_reg(mpr121, ELECTRODE_CONF_ADDR,
 					 eleconf);
 	if (ret != 0)
 		goto err_i2c_write;
@@ -256,6 +295,36 @@ static int mpr121_polled_process_keys(struct mpr121_polled *mpr121)
 
 	return 0;
 }
+
+static int mpr121_polled_check_regs(struct mpr121_polled *mpr121)
+{
+	struct i2c_client *client = mpr121->client;
+	int i, reg;
+
+	/* Skip registers that were never written to (have invalid cache) */
+	i = mpr121->reg_cache_next_check_item;
+	for (; i < MPR121_REG_CACHE_SIZE; i++)
+		if (mpr121->reg_cache[i].valid)
+			break;
+
+	if (i == MPR121_REG_CACHE_SIZE) {
+		mpr121->reg_cache_next_check_item = 0;
+		return 0;
+	}
+
+	reg = i2c_smbus_read_byte_data(client, mpr121_cache_idx_to_addr(i));
+	if (reg < 0) {
+		dev_err(&client->dev, "i2c read error: %d\n", reg);
+		return -1;
+	}
+
+	if (reg != mpr121->reg_cache[i].value)
+		return -1;
+
+	mpr121->reg_cache_next_check_item = i + 1;
+	return 0;
+}
+
 static void mpr121_poll(struct input_polled_dev *dev)
 {
 	struct mpr121_polled *mpr121 = dev->private;
@@ -282,6 +351,13 @@ static void mpr121_poll(struct input_polled_dev *dev)
 	}
 
 	mpr121->read_errors = 0;
+	mpr121->reg_cache_check_count++;
+	if (mpr121->reg_cache_check_count > MPR121_REG_CACHE_CHECK_LIMIT) {
+		mpr121->reg_cache_check_count = 0;
+		ret = mpr121_polled_check_regs(mpr121);
+		if (ret < 0)
+			mpr121->read_errors++;
+	}
 }
 
 static int mpr121_polled_probe(struct i2c_client *client,
@@ -366,8 +442,9 @@ static int mpr121_polled_probe(struct i2c_client *client,
 static int __maybe_unused mpr121_polled_suspend(struct device *dev)
 {
 	struct i2c_client *client = to_i2c_client(dev);
+	struct mpr121_polled *mpr121 = i2c_get_clientdata(client);
 
-	i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, 0x00);
+	mpr121_polled_write_reg(mpr121, ELECTRODE_CONF_ADDR, 0x00);
 
 	return 0;
 }
@@ -377,8 +454,7 @@ static int __maybe_unused mpr121_polled_resume(struct device *dev)
 	struct i2c_client *client = to_i2c_client(dev);
 	struct mpr121_polled *mpr121 = i2c_get_clientdata(client);
 
-	i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR,
-				  mpr121->keycount);
+	mpr121_polled_write_reg(mpr121, ELECTRODE_CONF_ADDR, mpr121->keycount);
 
 	return 0;
 }
-- 
2.1.4

^ permalink raw reply related

* [RFC PATCH v2 2/4] Input: mpr121-polled: Add polling variant of the MPR121 touchkey driver
From: Michal Vokáč @ 2019-05-17 13:12 UTC (permalink / raw)
  To: Dmitry Torokhov, Rob Herring
  Cc: Mark Rutland, Shawn Guo, Sascha Hauer, Fabio Estevam, linux-input,
	devicetree, linux-kernel, Pengutronix Kernel Team,
	Michal Vokáč
In-Reply-To: <1558098773-47416-1-git-send-email-michal.vokac@ysoft.com>

This driver is based on the original driver with interrupts. Polling
driver may be used in cases where the MPR121 chip is connected using
only the I2C interface and the interrupt line is not available.

Signed-off-by: Michal Vokáč <michal.vokac@ysoft.com>
---
 drivers/input/keyboard/Kconfig                  |  13 +
 drivers/input/keyboard/Makefile                 |   1 +
 drivers/input/keyboard/mpr121_touchkey_polled.c | 417 ++++++++++++++++++++++++
 3 files changed, 431 insertions(+)
 create mode 100644 drivers/input/keyboard/mpr121_touchkey_polled.c

diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 52d7f55fca32..b61cf6e4f1ba 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -418,6 +418,19 @@ config KEYBOARD_MPR121
 	  To compile this driver as a module, choose M here: the
 	  module will be called mpr121_touchkey.
 
+config KEYBOARD_MPR121_POLLED
+	tristate "Polled Freescale MPR121 Touchkey"
+	depends on I2C
+	help
+	  Say Y here if you have Freescale MPR121 touchkey controller
+	  chip in your system connected only using the I2C line without
+	  the interrupt line.
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mpr121_touchkey_polled.
+
 config KEYBOARD_SNVS_PWRKEY
 	tristate "IMX SNVS Power Key Driver"
 	depends on ARCH_MXC || COMPILE_TEST
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 182e92985dbf..903f50842844 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_KEYBOARD_MATRIX)		+= matrix_keypad.o
 obj-$(CONFIG_KEYBOARD_MAX7359)		+= max7359_keypad.o
 obj-$(CONFIG_KEYBOARD_MCS)		+= mcs_touchkey.o
 obj-$(CONFIG_KEYBOARD_MPR121)		+= mpr121_touchkey.o
+obj-$(CONFIG_KEYBOARD_MPR121_POLLED)	+= mpr121_touchkey_polled.o
 obj-$(CONFIG_KEYBOARD_MTK_PMIC) 	+= mtk-pmic-keys.o
 obj-$(CONFIG_KEYBOARD_NEWTON)		+= newtonkbd.o
 obj-$(CONFIG_KEYBOARD_NOMADIK)		+= nomadik-ske-keypad.o
diff --git a/drivers/input/keyboard/mpr121_touchkey_polled.c b/drivers/input/keyboard/mpr121_touchkey_polled.c
new file mode 100644
index 000000000000..e5e80530c9d8
--- /dev/null
+++ b/drivers/input/keyboard/mpr121_touchkey_polled.c
@@ -0,0 +1,417 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Touchkey driver for Freescale MPR121 Controllor
+//
+// Copyright (C) 2011 Freescale Semiconductor, Inc.
+// Author: Zhang Jiejing <jiejing.zhang@freescale.com>
+//
+// Based on mcs_touchkey.c
+//
+// Copyright (C) 2019 Y Soft Corporation, a.s.
+// Author: Pavel Staněk <pavel.stanek@ysoft.com>
+// Author: Michal Vokáč <michal.vokac@ysoft.com>
+//
+// Reworked into polled driver based on mpr121_touchkey.c
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input-polldev.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+/* Register definitions */
+#define ELE_TOUCH_STATUS_0_ADDR	0x0
+#define ELE_TOUCH_STATUS_1_ADDR	0X1
+#define MHD_RISING_ADDR		0x2b
+#define NHD_RISING_ADDR		0x2c
+#define NCL_RISING_ADDR		0x2d
+#define FDL_RISING_ADDR		0x2e
+#define MHD_FALLING_ADDR	0x2f
+#define NHD_FALLING_ADDR	0x30
+#define NCL_FALLING_ADDR	0x31
+#define FDL_FALLING_ADDR	0x32
+#define ELE0_TOUCH_THRESHOLD_ADDR	0x41
+#define ELE0_RELEASE_THRESHOLD_ADDR	0x42
+#define AFE_CONF_ADDR			0x5c
+#define FILTER_CONF_ADDR		0x5d
+
+/*
+ * ELECTRODE_CONF_ADDR: This register configures the number of
+ * enabled capacitance sensing inputs and its run/suspend mode.
+ */
+#define ELECTRODE_CONF_ADDR		0x5e
+#define ELECTRODE_CONF_QUICK_CHARGE	0x80
+#define AUTO_CONFIG_CTRL_ADDR		0x7b
+#define AUTO_CONFIG_USL_ADDR		0x7d
+#define AUTO_CONFIG_LSL_ADDR		0x7e
+#define AUTO_CONFIG_TL_ADDR		0x7f
+
+/* Threshold of touch/release trigger */
+#define TOUCH_THRESHOLD			0x08
+#define RELEASE_THRESHOLD		0x05
+/* Masks for touch and release triggers */
+#define TOUCH_STATUS_MASK		0xfff
+/* MPR121 has 12 keys */
+#define MPR121_MAX_KEY_COUNT		12
+
+#define MPR121_POLL_INTERVAL		50
+#define MPR121_POLL_INTERVAL_MIN	10
+#define MPR121_POLL_INTERVAL_MAX	200
+#define MPR121_POLL_INTERVAL_REINIT	500
+#define MPR121_POLL_RETRY_MAX		4
+
+struct mpr121_polled {
+	struct i2c_client *client;
+	struct input_dev *input_dev;
+	struct input_polled_dev	*poll_dev;
+	unsigned int statusbits;
+	unsigned int keycount;
+	u32 keycodes[MPR121_MAX_KEY_COUNT];
+	u8 read_errors;
+	int vdd_uv;
+};
+
+struct mpr121_polled_init_register {
+	int addr;
+	u8 val;
+};
+
+static const struct mpr121_polled_init_register init_reg_table[] = {
+	{ MHD_RISING_ADDR,	0x1 },
+	{ NHD_RISING_ADDR,	0x1 },
+	{ MHD_FALLING_ADDR,	0x1 },
+	{ NHD_FALLING_ADDR,	0x1 },
+	{ NCL_FALLING_ADDR,	0xff },
+	{ FDL_FALLING_ADDR,	0x02 },
+	{ FILTER_CONF_ADDR,	0x04 },
+	{ AFE_CONF_ADDR,	0x0b },
+	{ AUTO_CONFIG_CTRL_ADDR, 0x0b },
+};
+
+static void mpr121_polled_vdd_supply_disable(void *data)
+{
+	struct regulator *vdd_supply = data;
+
+	regulator_disable(vdd_supply);
+}
+
+static struct regulator *mpr121_polled_vdd_supply_init(struct device *dev)
+{
+	struct regulator *vdd_supply;
+	int err;
+
+	vdd_supply = devm_regulator_get(dev, "vdd");
+	if (IS_ERR(vdd_supply)) {
+		dev_err(dev, "failed to get vdd regulator: %ld\n",
+			PTR_ERR(vdd_supply));
+		return vdd_supply;
+	}
+
+	err = regulator_enable(vdd_supply);
+	if (err) {
+		dev_err(dev, "failed to enable vdd regulator: %d\n", err);
+		return ERR_PTR(err);
+	}
+
+	err = devm_add_action(dev, mpr121_polled_vdd_supply_disable,
+			      vdd_supply);
+	if (err) {
+		regulator_disable(vdd_supply);
+		dev_err(dev, "failed to add disable regulator action: %d\n",
+			err);
+		return ERR_PTR(err);
+	}
+
+	return vdd_supply;
+}
+
+static int mpr121_polled_phys_init(struct mpr121_polled *mpr121,
+				   struct i2c_client *client)
+{
+	const struct mpr121_polled_init_register *reg;
+	unsigned char usl, lsl, tl, eleconf;
+	int i, t, vdd, ret;
+
+	/* Set stop mode prior to writing any register */
+	ret = i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, 0x00);
+	if (ret < 0)
+		goto err_i2c_write;
+
+	/* Set up touch/release threshold for ele0-ele11 */
+	for (i = 0; i <= MPR121_MAX_KEY_COUNT; i++) {
+		t = ELE0_TOUCH_THRESHOLD_ADDR + (i * 2);
+		ret = i2c_smbus_write_byte_data(client, t, TOUCH_THRESHOLD);
+		if (ret < 0)
+			goto err_i2c_write;
+		ret = i2c_smbus_write_byte_data(client, t + 1,
+						RELEASE_THRESHOLD);
+		if (ret < 0)
+			goto err_i2c_write;
+	}
+
+	/* Set up init register */
+	for (i = 0; i < ARRAY_SIZE(init_reg_table); i++) {
+		reg = &init_reg_table[i];
+		ret = i2c_smbus_write_byte_data(client, reg->addr, reg->val);
+		if (ret < 0)
+			goto err_i2c_write;
+	}
+
+	/*
+	 * Capacitance on sensing input varies and needs to be compensated.
+	 * The internal MPR121-auto-configuration can do this if it's
+	 * registers are set properly (based on vdd_uv).
+	 */
+	vdd = mpr121->vdd_uv / 1000;
+	usl = ((vdd - 700) * 256) / vdd;
+	lsl = (usl * 65) / 100;
+	tl = (usl * 90) / 100;
+	ret = i2c_smbus_write_byte_data(client, AUTO_CONFIG_USL_ADDR, usl);
+	ret |= i2c_smbus_write_byte_data(client, AUTO_CONFIG_LSL_ADDR, lsl);
+	ret |= i2c_smbus_write_byte_data(client, AUTO_CONFIG_TL_ADDR, tl);
+
+	/*
+	 * Quick charge bit will let the capacitive charge to ready
+	 * state quickly, or the buttons may not function after system
+	 * boot.
+	 */
+	eleconf = mpr121->keycount | ELECTRODE_CONF_QUICK_CHARGE;
+	ret |= i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR,
+					 eleconf);
+	if (ret != 0)
+		goto err_i2c_write;
+
+	dev_dbg(&client->dev, "set up with %x keys.\n", mpr121->keycount);
+
+	return 0;
+
+err_i2c_write:
+	dev_err(&client->dev, "i2c write error: %d\n", ret);
+	return ret;
+}
+
+static void mpr121_polled_release_keys(struct mpr121_polled *mpr121)
+{
+	struct input_dev *input = mpr121->input_dev;
+	struct i2c_client *client = mpr121->client;
+	unsigned long statusbits;
+	unsigned int key_num;
+
+	if (!mpr121->statusbits)
+		return;
+
+	statusbits = mpr121->statusbits;
+	mpr121->statusbits = 0;
+	for_each_set_bit(key_num, &statusbits, mpr121->keycount) {
+		unsigned int key_val;
+
+		key_val = mpr121->keycodes[key_num];
+
+		input_event(input, EV_MSC, MSC_SCAN, key_num);
+		input_report_key(input, key_val, 0);
+
+		dev_dbg(&client->dev, "key %d %d %s\n", key_num, key_val,
+			"released");
+	}
+	input_sync(input);
+}
+
+static int mpr121_polled_process_keys(struct mpr121_polled *mpr121)
+{
+	struct input_dev *input = mpr121->input_dev;
+	struct i2c_client *client = mpr121->client;
+	unsigned long bit_changed;
+	unsigned int key_num;
+	int reg;
+
+	reg = i2c_smbus_read_word_data(client, ELE_TOUCH_STATUS_0_ADDR);
+	if (reg < 0) {
+		dev_err(&client->dev, "i2c read error: %d\n", reg);
+		return reg;
+	}
+
+	reg &= TOUCH_STATUS_MASK;
+	bit_changed = reg ^ mpr121->statusbits;
+	mpr121->statusbits = reg;
+	for_each_set_bit(key_num, &bit_changed, mpr121->keycount) {
+		unsigned int key_val, pressed;
+
+		pressed = reg & BIT(key_num);
+		key_val = mpr121->keycodes[key_num];
+
+		input_event(input, EV_MSC, MSC_SCAN, key_num);
+		input_report_key(input, key_val, pressed);
+
+		dev_dbg(&client->dev, "key %d %d %s\n", key_num, key_val,
+			pressed ? "pressed" : "released");
+	}
+	input_sync(input);
+
+	return 0;
+}
+static void mpr121_poll(struct input_polled_dev *dev)
+{
+	struct mpr121_polled *mpr121 = dev->private;
+	struct i2c_client *client = mpr121->client;
+	int ret;
+
+	if (mpr121->read_errors > MPR121_POLL_RETRY_MAX) {
+		dev_warn(&client->dev,
+			 "device does not respond, re-initializing\n");
+		mpr121_polled_release_keys(mpr121);
+		ret = mpr121_polled_phys_init(mpr121, client);
+		if (ret >= 0) {
+			mpr121->read_errors = 0;
+			dev->poll_interval = MPR121_POLL_INTERVAL;
+		} else {
+			dev->poll_interval = MPR121_POLL_INTERVAL_REINIT;
+		}
+	}
+
+	ret = mpr121_polled_process_keys(mpr121);
+	if (ret < 0) {
+		mpr121->read_errors++;
+		return;
+	}
+
+	mpr121->read_errors = 0;
+}
+
+static int mpr121_polled_probe(struct i2c_client *client,
+			       const struct i2c_device_id *id)
+{
+	struct device *dev = &client->dev;
+	struct regulator *vdd_supply;
+	struct mpr121_polled *mpr121;
+	struct input_dev *input_dev;
+	struct input_polled_dev *poll_dev;
+	int error;
+	int i;
+
+	vdd_supply = mpr121_polled_vdd_supply_init(dev);
+	if (IS_ERR(vdd_supply))
+		return PTR_ERR(vdd_supply);
+
+	mpr121 = devm_kzalloc(dev, sizeof(*mpr121), GFP_KERNEL);
+	if (!mpr121)
+		return -ENOMEM;
+
+	poll_dev = devm_input_allocate_polled_device(dev);
+	if (!poll_dev)
+		return -ENOMEM;
+
+	mpr121->vdd_uv = regulator_get_voltage(vdd_supply);
+	mpr121->client = client;
+	mpr121->input_dev = poll_dev->input;
+	mpr121->poll_dev = poll_dev;
+	mpr121->keycount = device_property_read_u32_array(dev, "linux,keycodes",
+							  NULL, 0);
+	if (mpr121->keycount > MPR121_MAX_KEY_COUNT) {
+		dev_err(dev, "too many keys defined (%d)\n", mpr121->keycount);
+		return -EINVAL;
+	}
+
+	error = device_property_read_u32_array(dev, "linux,keycodes",
+					       mpr121->keycodes,
+					       mpr121->keycount);
+	if (error) {
+		dev_err(dev,
+			"failed to read linux,keycode property: %d\n", error);
+		return error;
+	}
+
+	poll_dev->private = mpr121;
+	poll_dev->poll = mpr121_poll;
+	poll_dev->poll_interval = MPR121_POLL_INTERVAL;
+	poll_dev->poll_interval_max = MPR121_POLL_INTERVAL_MAX;
+	poll_dev->poll_interval_min = MPR121_POLL_INTERVAL_MIN;
+
+	input_dev = poll_dev->input;
+	input_dev->name = "Freescale MPR121 Polled Touchkey";
+	input_dev->id.bustype = BUS_I2C;
+	input_dev->dev.parent = dev;
+	if (device_property_read_bool(dev, "autorepeat"))
+		__set_bit(EV_REP, input_dev->evbit);
+	input_set_capability(input_dev, EV_MSC, MSC_SCAN);
+
+	input_dev->keycode = mpr121->keycodes;
+	input_dev->keycodesize = sizeof(mpr121->keycodes[0]);
+	input_dev->keycodemax = mpr121->keycount;
+
+	for (i = 0; i < mpr121->keycount; i++)
+		input_set_capability(input_dev, EV_KEY, mpr121->keycodes[i]);
+
+	error = mpr121_polled_phys_init(mpr121, client);
+	if (error) {
+		dev_err(dev, "Failed to init register\n");
+		return error;
+	}
+
+	error = input_register_polled_device(poll_dev);
+	if (error)
+		return error;
+
+	i2c_set_clientdata(client, mpr121);
+
+	return 0;
+}
+
+static int __maybe_unused mpr121_polled_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, 0x00);
+
+	return 0;
+}
+
+static int __maybe_unused mpr121_polled_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct mpr121_polled *mpr121 = i2c_get_clientdata(client);
+
+	i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR,
+				  mpr121->keycount);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(mpr121_polled_pm_ops, mpr121_polled_suspend,
+			 mpr121_polled_resume);
+
+static const struct i2c_device_id mpr121_polled_id[] = {
+	{ "mpr121_polled", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, mpr121_polled_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id mpr121_polled_dt_match_table[] = {
+	{ .compatible = "fsl,mpr121-touchkey-polled" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, mpr121_polled_dt_match_table);
+#endif
+
+static struct i2c_driver mpr121_polled_driver = {
+	.driver = {
+		.name	= "mpr121-polled",
+		.pm	= &mpr121_polled_pm_ops,
+		.of_match_table = of_match_ptr(mpr121_polled_dt_match_table),
+	},
+	.id_table	= mpr121_polled_id,
+	.probe		= mpr121_polled_probe,
+};
+
+module_i2c_driver(mpr121_polled_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Michal Vokáč <michal.vokac@ysoft.com>");
+MODULE_DESCRIPTION("Polled driver for Freescale MPR121 chip");
-- 
2.1.4

^ permalink raw reply related

* [RFC PATCH v2 1/4] dt-bindings: input: Add support for the MPR121 without interrupt line
From: Michal Vokáč @ 2019-05-17 13:12 UTC (permalink / raw)
  To: Dmitry Torokhov, Rob Herring
  Cc: Mark Rutland, Shawn Guo, Sascha Hauer, Fabio Estevam, linux-input,
	devicetree, linux-kernel, Pengutronix Kernel Team,
	Michal Vokáč
In-Reply-To: <1558098773-47416-1-git-send-email-michal.vokac@ysoft.com>

Normally, the MPR121 controller uses separate interrupt line to notify
the I2C host that a key was touched/released. To support platforms that
can not use the interrupt line, polling of the MPR121 registers can be
used.

Signed-off-by: Michal Vokáč <michal.vokac@ysoft.com>
---
Changes since v1:
- Document the polled binding in the original file, do not create a new one.
  (Rob)

 Documentation/devicetree/bindings/input/mpr121-touchkey.txt | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/input/mpr121-touchkey.txt b/Documentation/devicetree/bindings/input/mpr121-touchkey.txt
index b7c61ee5841b..97f55273d473 100644
--- a/Documentation/devicetree/bindings/input/mpr121-touchkey.txt
+++ b/Documentation/devicetree/bindings/input/mpr121-touchkey.txt
@@ -1,9 +1,14 @@
-* Freescale MPR121 Controllor
+* Freescale MPR121 Controller
 
 Required Properties:
-- compatible:		Should be "fsl,mpr121-touchkey"
+- compatible:		Should be one of:
+			- "fsl,mpr121-touchkey" - MPR121 with interrupt line
+			- "fsl,mpr121-touchkey-polled" - MPR121 with polling
 - reg:			The I2C slave address of the device.
 - interrupts:		The interrupt number to the cpu.
+			In case of "fsl,mpr121-touchkey-polled" the interrupt
+			line is not used and hence the interrupts property is
+			not required.
 - vdd-supply:		Phandle to the Vdd power supply.
 - linux,keycodes:	Specifies an array of numeric keycode values to
 			be used for reporting button presses. The array can
-- 
2.1.4

^ permalink raw reply related

* [RFC PATCH v2 0/4] Input: mpr121-polled: Add polled driver for MPR121
From: Michal Vokáč @ 2019-05-17 13:12 UTC (permalink / raw)
  To: Dmitry Torokhov, Rob Herring
  Cc: Mark Rutland, Shawn Guo, Sascha Hauer, Fabio Estevam, linux-input,
	devicetree, linux-kernel, Pengutronix Kernel Team,
	Michal Vokáč

Hi,

I have to deal with a situation where we have a custom i.MX6 based
platform in production that uses the MPR121 touchkey controller.
Unfortunately the chip is connected using only the I2C interface.
The interrupt line is not used. Back in 2015 (Linux v3.14), my
colleague modded the existing mpr121_touchkey.c driver to use polling
instead of interrupt.

For quite some time yet I am in a process of updating the product from
the ancient Freescale v3.14 kernel to the latest mainline and pushing
any needed changes upstream. The DT files for our imx6dl-yapp4 platform
already made it into v5.1-rc.

I rebased and updated our mpr121 patch to the latest mainline.
It is created as a separate driver, similarly to gpio_keys_polled.

The I2C device is quite susceptible to ESD. An ESD test quite often
causes reset of the chip or some register randomly changes its value.
The [PATCH 3/4] adds a write-through register cache. With the cache
this state can be detected and the device can be re-initialied.

The main question is: Is there any chance that such a polled driver
could be accepted? Is it correct to implement it as a separate driver
or should it be done as an option in the existing driver? I can not
really imagine how I would do that though..

There are also certain worries that the MPR121 chip may no longer be
available in nonspecifically distant future. In case of EOL I will need
to add a polled driver for an other touchkey chip. May it be already
in mainline or a completely new one.

I will appreciate any comments. Thank you in advance,
Michal


Michal Vokáč (4):
  dt-bindings: input: Add support for the MPR121 without interrupt line
  Input: mpr121-polled: Add polling variant of the MPR121 touchkey
    driver
  Input: mpr121-polled: Add write-through cache to detect corrupted
    registers
  ARM: dts: imx6dl-yapp4: Enable MPR121 touch keypad on Hydra

 .../bindings/input/mpr121-touchkey-polled.txt      |  26 ++
 arch/arm/boot/dts/imx6dl-yapp4-common.dtsi         |  12 +
 arch/arm/boot/dts/imx6dl-yapp4-hydra.dts           |   4 +
 drivers/input/keyboard/Kconfig                     |  13 +
 drivers/input/keyboard/Makefile                    |   1 +
 drivers/input/keyboard/mpr121_touchkey_polled.c    | 493 +++++++++++++++++++++
 6 files changed, 549 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/mpr121-touchkey-polled.txt
 create mode 100644 drivers/input/keyboard/mpr121_touchkey_polled.c

-- 
2.1.4

^ permalink raw reply

* Re: [PATCH] Input: walkera0701 - Fix possible NULL pointer dereference in walkera0701_detach
From: YueHaibing @ 2019-05-17  2:38 UTC (permalink / raw)
  To: dmitry.torokhov, sudip; +Cc: linux-kernel, linux-input
In-Reply-To: <20190423145637.35004-1-yuehaibing@huawei.com>

ping...

On 2019/4/23 22:56, Yue Haibing wrote:
> From: YueHaibing <yuehaibing@huawei.com>
> 
> KASAN report this:
> 
> walkera0701: failed to allocate input device
> kasan: CONFIG_KASAN_INLINE enabled
> kasan: GPF could be caused by NULL-ptr deref or user memory access
> general protection fault: 0000 [#1] SMP KASAN PTI
> CPU: 1 PID: 5324 Comm: syz-executor.0 Tainted: G         C        5.1.0-rc3+ #8
> Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1ubuntu1 04/01/2014
> RIP: 0010:input_unregister_device+0x21/0xe0 drivers/input/input.c:2192
> Code: 2e 0f 1f 84 00 00 00 00 00 53 48 89 fb e8 07 41 f6 fe 48 8d bb 20 07 00 00 48 b8 00 00 00 00 00 fc ff df 48 89 fa 48 c1 ea 03 <0f> b6 04 02 84 c0 74 08 84 c0 0f 8e 92 00 00 00 80 bb 20 07 00 00
> RSP: 0018:ffff8881f58dfd30 EFLAGS: 00010206
> RAX: dffffc0000000000 RBX: 0000000000000000 RCX: ffffffff82460ca9
> RDX: 00000000000000e4 RSI: ffffc900013d3000 RDI: 0000000000000720
> RBP: 0000000000000000 R08: ffffed103d30caf7 R09: 0000000000000000
> R10: 0000000000000000 R11: 0000000000000000 R12: dffffc0000000000
> R13: ffffffffc1633000 R14: ffffffffc086b320 R15: 1ffff1103eb1bfaf
> FS:  00007fa407200700(0000) GS:ffff8881f7300000(0000) knlGS:0000000000000000
> CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> CR2: 0000001b33924000 CR3: 00000001e270c006 CR4: 00000000007606e0
> DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
> DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
> PKRU: 55555554
> Call Trace:
>  walkera0701_detach+0x8e/0xba [walkera0701]
>  port_detach+0x73/0x90 [parport]
>  bus_for_each_dev+0x154/0x1e0 drivers/base/bus.c:304
>  parport_unregister_driver+0x1f8/0x270 [parport]
>  __do_sys_delete_module kernel/module.c:1018 [inline]
>  __se_sys_delete_module kernel/module.c:961 [inline]
>  __x64_sys_delete_module+0x30c/0x480 kernel/module.c:961
>  do_syscall_64+0x9f/0x450 arch/x86/entry/common.c:290
>  entry_SYSCALL_64_after_hwframe+0x49/0xbe
> RIP: 0033:0x462e99
> Code: f7 d8 64 89 02 b8 ff ff ff ff c3 66 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 bc ff ff ff f7 d8 64 89 01 48
> RSP: 002b:00007fa4071ffc58 EFLAGS: 00000246 ORIG_RAX: 00000000000000b0
> RAX: ffffffffffffffda RBX: 000000000073bf00 RCX: 0000000000462e99
> RDX: 0000000000000000 RSI: 0000000000000000 RDI: 00000000200001c0
> RBP: 0000000000000002 R08: 0000000000000000 R09: 0000000000000000
> R10: 0000000000000000 R11: 0000000000000246 R12: 00007fa4072006bc
> R13: 00000000004bcca9 R14: 00000000006f6b48 R15: 00000000ffffffff
> Modules linked in: walkera0701(-) tps65090_regulator intel_th mptbase adm1031 snd_soc_wm8753 snd_soc_core snd_pcm_dmaengine snd_pcm ac97_bus snd_compress rtc_ds1286 snd_seq_dummy snd_seq snd_timer snd_seq_device snd soundcore comedi(C) i2c_mux_ltc4306 i2c_mux max14577_regulator max14577 usbcore hid cmac mc13783_regulator mc13xxx_regulator_core mc13xxx_core of_mdio fixed_phy libphy iptable_security iptable_raw iptable_mangle iptable_nat nf_nat nf_conntrack nf_defrag_ipv6 nf_defrag_ipv4 iptable_filter bpfilter ip6_vti ip_vti ip_gre ipip sit tunnel4 ip_tunnel hsr veth netdevsim vxcan batman_adv cfg80211 rfkill chnl_net caif nlmon dummy team bonding vcan bridge stp llc ip6_gre gre ip6_tunnel tunnel6 tun joydev mousedev ppdev tpm kvm_intel kvm irqbypass crct10dif_pclmul crc32_pclmul crc32c_in
 tel ghash_clmulni_intel ide_pci_generic aesni_intel aes_x86_64 piix crypto_simd cryptd input_leds ide_core psmouse glue_helper intel_agp serio_raw intel_gtt ata_generic agpgart i2c_piix4
>  pata_acpi parport_pc parport floppy rtc_cmos sch_fq_codel ip_tables x_tables sha1_ssse3 sha1_generic ipv6 [last unloaded: walkera0701]
> Dumping ftrace buffer:
>    (ftrace buffer empty)
> ---[ end trace 17f6dd401f34af3e ]---
> 
> In walkera0701_attach(), if input_allocate_device failed,
> w->input_dev is set to NULL. But in walkera0701_detach it
> is not checked while passing to input_unregister_device(),
> this will trigger a NULL pointer dereference issue.
> 
> There is also another possible use-after-free issue, when
> input_register_device() fails, input_free_device be
> called to free input dev, then in walkera0701_detach()
> calling input_unregister_device will trigger use-after-free
> while accessing input dev
> 
> This patch set w->parport to NULL on walkera0701_attach failed,
> and only do detach in case attach success.
> 
> Reported-by: Hulk Robot <hulkci@huawei.com>
> Fixes: 221bcb24c653 ("Input: walkera0701 - use parallel port device model")
> Signed-off-by: YueHaibing <yuehaibing@huawei.com>
> ---
>  drivers/input/joystick/walkera0701.c | 10 ++++++----
>  1 file changed, 6 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/input/joystick/walkera0701.c b/drivers/input/joystick/walkera0701.c
> index dce313d..852b8c5 100644
> --- a/drivers/input/joystick/walkera0701.c
> +++ b/drivers/input/joystick/walkera0701.c
> @@ -207,13 +207,13 @@ static void walkera0701_attach(struct parport *pp)
>  
>  	if (pp->number != walkera0701_pp_no) {
>  		pr_debug("Not using parport%d.\n", pp->number);
> -		return;
> +		goto err_out;
>  	}
>  
>  	if (pp->irq == -1) {
>  		pr_err("parport %d does not have interrupt assigned\n",
>  			pp->number);
> -		return;
> +		goto err_out;
>  	}
>  
>  	w->parport = pp;
> @@ -228,7 +228,7 @@ static void walkera0701_attach(struct parport *pp)
>  
>  	if (!w->pardevice) {
>  		pr_err("failed to register parport device\n");
> -		return;
> +		goto err_out;
>  	}
>  
>  	if (parport_negotiate(w->pardevice->port, IEEE1284_MODE_COMPAT)) {
> @@ -279,13 +279,15 @@ static void walkera0701_attach(struct parport *pp)
>  	input_free_device(w->input_dev);
>  err_unregister_device:
>  	parport_unregister_device(w->pardevice);
> +err_out:
> +	w->parport = NULL;
>  }
>  
>  static void walkera0701_detach(struct parport *port)
>  {
>  	struct walkera_dev *w = &w_dev;
>  
> -	if (!w->pardevice || w->parport->number != port->number)
> +	if (!w->parport)
>  		return;
>  
>  	input_unregister_device(w->input_dev);
> 

^ permalink raw reply

* [RFC 2/2] input: soc_button_array for newer surface devices
From: Maximilian Luz @ 2019-05-16 14:25 UTC (permalink / raw)
  Cc: linux-kernel, linux-input, platform-driver-x86, Dmitry Torokhov,
	Hans de Goede, Chen Yu, Darren Hart, Andy Shevchenko,
	Maximilian Luz
In-Reply-To: <20190516142523.117978-1-luzmaximilian@gmail.com>

Power and volume button support for 5th and 6th genration Microsoft
Surface devices via soc_button_array.

Note that these devices use the same MSHW0040 device as on the Surface
Pro 4, however the implementation is different (GPIOs vs. ACPI
notifications). Thus some checking is required to ensure we only load
this driver on the correct devices.

Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
---
 drivers/input/misc/soc_button_array.c | 134 +++++++++++++++++++++++---
 1 file changed, 122 insertions(+), 12 deletions(-)

diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c
index bb458beecb43..dd67753bbf6e 100644
--- a/drivers/input/misc/soc_button_array.c
+++ b/drivers/input/misc/soc_button_array.c
@@ -29,6 +29,17 @@ struct soc_button_info {
 	bool wakeup;
 };
 
+/**
+ * struct soc_device_data - driver data for different device types
+ * @button_info: specifications of buttons, if NULL specification is assumed to
+ *               be present in _DSD
+ * @check: device-specific check (NULL means all will be accepted)
+ */
+struct soc_device_data {
+	struct soc_button_info *button_info;
+	int (*check)(struct device *dev);
+};
+
 /*
  * Some of the buttons like volume up/down are auto repeat, while others
  * are not. To support both, we register two platform devices, and put
@@ -314,6 +325,7 @@ static int soc_button_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
 	const struct acpi_device_id *id;
+	struct soc_device_data *device_data;
 	struct soc_button_info *button_info;
 	struct soc_button_data *priv;
 	struct platform_device *pd;
@@ -324,18 +336,20 @@ static int soc_button_probe(struct platform_device *pdev)
 	if (!id)
 		return -ENODEV;
 
-	if (!id->driver_data) {
+	device_data = (struct soc_device_data *)id->driver_data;
+	if (device_data && device_data->check) {
+		error = device_data->check(dev);
+		if (error)
+			return error;
+	}
+
+	if (device_data && device_data->button_info) {
+		button_info = (struct soc_button_info *)
+				device_data->button_info;
+	} else {
 		button_info = soc_button_get_button_info(dev);
 		if (IS_ERR(button_info))
 			return PTR_ERR(button_info);
-	} else {
-		button_info = (struct soc_button_info *)id->driver_data;
-	}
-
-	error = gpiod_count(dev, NULL);
-	if (error < 0) {
-		dev_dbg(dev, "no GPIO attached, ignoring...\n");
-		return -ENODEV;
 	}
 
 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
@@ -361,12 +375,32 @@ static int soc_button_probe(struct platform_device *pdev)
 	if (!priv->children[0] && !priv->children[1])
 		return -ENODEV;
 
-	if (!id->driver_data)
+	if (!device_data || !device_data->button_info)
 		devm_kfree(dev, button_info);
 
 	return 0;
 }
 
+
+static int soc_device_check_generic(struct device *dev)
+{
+	int gpios;
+
+	gpios = gpiod_count(dev, NULL);
+	if (gpios < 0) {
+		dev_dbg(dev, "no GPIO attached, ignoring...\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static struct soc_device_data soc_device_ACPI0011 = {
+	.button_info = NULL,
+	.check = soc_device_check_generic,
+};
+
+
 /*
  * Definition of buttons on the tablet. The ACPI index of each button
  * is defined in section 2.8.7.2 of "Windows ACPI Design Guide for SoC
@@ -381,9 +415,85 @@ static struct soc_button_info soc_button_PNP0C40[] = {
 	{ }
 };
 
+static struct soc_device_data soc_device_PNP0C40 = {
+	.button_info = soc_button_PNP0C40,
+	.check = soc_device_check_generic,
+};
+
+
+/*
+ * Special device check for Surface Book 2 and Surface Pro (2017).
+ * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned
+ * devices use MSHW0040 for power and volume buttons, however the way they
+ * have to be addressed differs. Make sure that we only load this drivers
+ * for the correct devices by checking the OEM Platform Revision provided by
+ * the _DSM method.
+ */
+#define MSHW0040_DSM_REVISION		0x01
+#define MSHW0040_DSM_GET_OMPR		0x02	// get OEM Platform Revision
+static const guid_t MSHW0040_DSM_UUID =
+	GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, 0x95, 0xed, 0xab, 0x16, 0x65,
+		  0x49, 0x80, 0x35);
+
+static int soc_device_check_MSHW0040(struct device *dev)
+{
+	acpi_handle handle = ACPI_HANDLE(dev);
+	union acpi_object *result;
+	u64 oem_platform_rev = 0;
+	int gpios;
+
+	// get OEM platform revision
+	result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID,
+					 MSHW0040_DSM_REVISION,
+					 MSHW0040_DSM_GET_OMPR, NULL,
+					 ACPI_TYPE_INTEGER);
+
+	if (result) {
+		oem_platform_rev = result->integer.value;
+		ACPI_FREE(result);
+	}
+
+	if (oem_platform_rev == 0)
+		return -ENODEV;
+
+	dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev);
+
+	/*
+	 * We are _really_ expecting GPIOs here. If we do not get any, this
+	 * means the GPIO driver has not been loaded yet (which can happen).
+	 * Try again later.
+	 */
+	gpios = gpiod_count(dev, NULL);
+	if (gpios < 0)
+		return -EAGAIN;
+
+	return 0;
+}
+
+/*
+ * Button infos for Microsoft Surface Book 2 and Surface Pro (2017).
+ * Obtained from DSDT/testing.
+ */
+static struct soc_button_info soc_button_MSHW0040[] = {
+	{ "power", 0, EV_KEY, KEY_POWER, false, true },
+	{ "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false },
+	{ "volume_down", 4, EV_KEY, KEY_VOLUMEDOWN, true, false },
+	{ }
+};
+
+static struct soc_device_data soc_device_MSHW0040 = {
+	.button_info = soc_button_MSHW0040,
+	.check = soc_device_check_MSHW0040,
+};
+
+
 static const struct acpi_device_id soc_button_acpi_match[] = {
-	{ "PNP0C40", (unsigned long)soc_button_PNP0C40 },
-	{ "ACPI0011", 0 },
+	{ "PNP0C40", (unsigned long)&soc_device_PNP0C40 },
+	{ "ACPI0011", (unsigned long)&soc_device_ACPI0011 },
+
+	/* Microsoft Surface Devices (5th and 6th generation) */
+	{ "MSHW0040", (unsigned long)&soc_device_MSHW0040 },
+
 	{ }
 };
 
-- 
2.21.0

^ permalink raw reply related

* [RFC 1/2] platform: Fix device check for surfacepro3_button
From: Maximilian Luz @ 2019-05-16 14:25 UTC (permalink / raw)
  Cc: linux-kernel, linux-input, platform-driver-x86, Dmitry Torokhov,
	Hans de Goede, Chen Yu, Darren Hart, Andy Shevchenko,
	Maximilian Luz
In-Reply-To: <20190516142523.117978-1-luzmaximilian@gmail.com>

Do not use the surfacepro3_button driver on newer Microsoft Surface
models, only use it on the Surface Pro 3 and 4. Newer models (5th, 6th
and possibly future generations) use the same device as the Surface Pro
4 to represent their volume and power buttons (MSHW0040), but their
acutal implementation is significantly different. This patch ensures
that the surfacepro3_button driver is only used on the Pro 3 and 4
models, allowing a different driver to bind on other models.

Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
---
 drivers/platform/x86/surfacepro3_button.c | 38 +++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/drivers/platform/x86/surfacepro3_button.c b/drivers/platform/x86/surfacepro3_button.c
index 1b491690ce07..eaec30380b11 100644
--- a/drivers/platform/x86/surfacepro3_button.c
+++ b/drivers/platform/x86/surfacepro3_button.c
@@ -24,6 +24,12 @@
 #define SURFACE_BUTTON_OBJ_NAME		"VGBI"
 #define SURFACE_BUTTON_DEVICE_NAME	"Surface Pro 3/4 Buttons"
 
+#define MSHW0040_DSM_REVISION		0x01
+#define MSHW0040_DSM_GET_OMPR		0x02	// get OEM Platform Revision
+static const guid_t MSHW0040_DSM_UUID =
+	GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, 0x95, 0xed, 0xab, 0x16, 0x65,
+		  0x49, 0x80, 0x35);
+
 #define SURFACE_BUTTON_NOTIFY_TABLET_MODE	0xc8
 
 #define SURFACE_BUTTON_NOTIFY_PRESS_POWER	0xc6
@@ -146,6 +152,34 @@ static int surface_button_resume(struct device *dev)
 }
 #endif
 
+/*
+ * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device
+ * ID (MSHW0040) for the power/volume buttons. Make sure this is the right
+ * device by checking for the _DSM method and OEM Platform Revision.
+ */
+static int surface_button_check_MSHW0040(struct acpi_device *dev)
+{
+	acpi_handle handle = dev->handle;
+	union acpi_object *result;
+	u64 oem_platform_rev = 0;
+
+	// get OEM platform revision
+	result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID,
+					 MSHW0040_DSM_REVISION,
+					 MSHW0040_DSM_GET_OMPR,
+					 NULL, ACPI_TYPE_INTEGER);
+
+	if (result) {
+		oem_platform_rev = result->integer.value;
+		ACPI_FREE(result);
+	}
+
+	dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev);
+
+	return oem_platform_rev == 0 ? 0 : -ENODEV;
+}
+
+
 static int surface_button_add(struct acpi_device *device)
 {
 	struct surface_button *button;
@@ -158,6 +192,10 @@ static int surface_button_add(struct acpi_device *device)
 	    strlen(SURFACE_BUTTON_OBJ_NAME)))
 		return -ENODEV;
 
+	error = surface_button_check_MSHW0040(device);
+	if (error)
+		return error;
+
 	button = kzalloc(sizeof(struct surface_button), GFP_KERNEL);
 	if (!button)
 		return -ENOMEM;
-- 
2.21.0

^ permalink raw reply related

* [RFC 0/2] Support for buttons on newer MS Surface devices
From: Maximilian Luz @ 2019-05-16 14:25 UTC (permalink / raw)
  Cc: linux-kernel, linux-input, platform-driver-x86, Dmitry Torokhov,
	Hans de Goede, Chen Yu, Darren Hart, Andy Shevchenko,
	Maximilian Luz

This series adds suport for power and volume buttons on 5th and 6th
generation Microsoft Surface devices. Specifically, it adds support for
the power-button on the Surface Laptop 1 and Laptop 2, as well as
support for power- and (on-device) volume-buttons on the Surface Pro 5
(2017), Pro 6, and Book 2.

These devices use the same MSHW0040 device as on the Surface Pro 4,
however, whereas the Pro 4 uses an ACPI notify handler, the newer
devices use GPIO interrupts to signal these events.

The first patch of this series ensures that the surfacepro3_button
driver, used for MSHW0040 on the Pro 4, does not probe for the newer
devices. The second patch adapts soc_button_array to implement the
actual button support.

I think the changes to soc_button_array in the second patch warrant a
thorough review. I've tried to make things a bit more generic to be able
to integrate arbitrary ACPI GPIO power-/volume-button devices more
easily, I'm not sure if there may be reasons against this. 

Maximilian Luz (2):
  platform: Fix device check for surfacepro3_button
  input: soc_button_array for newer surface devices

 drivers/input/misc/soc_button_array.c     | 134 ++++++++++++++++++++--
 drivers/platform/x86/surfacepro3_button.c |  38 ++++++
 2 files changed, 160 insertions(+), 12 deletions(-)

-- 
2.21.0

^ permalink raw reply

* Re: [PATCH v5] platform: chrome: Add ChromeOS EC ISHTP driver
From: Rushikesh S Kadam @ 2019-05-16  2:46 UTC (permalink / raw)
  To: Enric Balletbo Serra
  Cc: Enric Balletbo i Serra, benjamin.tissoires, jikos, Benson Leung,
	Guenter Roeck, Srinivas Pandruvada, linux-kernel, linux-input,
	Nick Crews, Jett Rink, Gwendal Grignou
In-Reply-To: <CAFqH_502a9rYYhUXFjUq5gmFH98JPxm-1CA3pW3XDtudnz-0tA@mail.gmail.com>

Hi Enric

On Wed, May 15, 2019 at 11:23:13PM +0200, Enric Balletbo Serra wrote:
> Missatge de Enric Balletbo i Serra <enric.balletbo@collabora.com> del
> dia dc., 15 de maig 2019 a les 15:00:
> >
> > Hi,
> >
> > On 4/5/19 15:34, Rushikesh S Kadam wrote:
> > > This driver implements a slim layer to enable the ChromeOS
> > > EC kernel stack (cros_ec) to communicate with ChromeOS EC
> > > firmware running on the Intel Integrated Sensor Hub (ISH).
> > >
> > > The driver registers a ChromeOS EC MFD device to connect
> > > with cros_ec kernel stack (upper layer), and it registers a
> > > client with the ISH Transport Protocol bus (lower layer) to
> > > talk with the ISH firwmare. See description of the ISHTP
> > > protocol at Documentation/hid/intel-ish-hid.txt
> > >
> > > Signed-off-by: Rushikesh S Kadam <rushikesh.s.kadam@intel.com>
> > > Acked-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> > > Acked-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
> > > Reviewed-by: Jett Rink <jettrink@chromium.org>
> > > Tested-by: Jett Rink <jettrink@chromium.org>
> > > ---
> >
> > The following patch is queued to the for-next branch for the autobuilders to
> > play with, if all goes well I'll add the patch for 5.3 when current merge window
> > closes.
> >
> 
> Actually, I reverted this patch and applied v6.
> 

Thanks for your help! sorry for the extra effort.

Regards
Rushikesh


> 
> > Thanks,
> >  Enric
> >
> > >
> > > Submitting the patch to linux-input@ per the discussion here
> > > https://lkml.org/lkml/2019/5/2/339
> > >
> > > The patch is baselined to hid git tree, branch for-5.2/ish
> > > https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git/log/?h=for-5.2/ish
> > >
> > > v5
> > >  - Submitting with all Acked-by & Tested-bys. No other changes.
> > >
> > > v4
> > >  - Coding style related changes. No functional changes. Addresses
> > >    review comments on v3.
> > >
> > > v3
> > >  - Made several changes to improve code readability. Replaced
> > >    multiple cl_data_to_dev(client_data) with dev variable. Use
> > >    reverse Xmas tree for variable defintion where it made sense.
> > >    Dropped few debug prints. Add docstring for function
> > >    prepare_cros_ec_rx().
> > >  - Fix code in function prepare_cros_ec_rx() under label
> > >    end_cros_ec_dev_init_error.
> > >  - Recycle buffer in process_recv() on failing to obtain the
> > >    semaphore.
> > >  - Increase ISHTP TX/RX ring buffer size to 8.
> > >  - Alphabetically ordered CROS_EC_ISHTP entries in Makefile and
> > >    Kconfig.
> > >  - Updated commit message.
> > >
> > > v2
> > >  - Dropped unused "reset" parameter in function cros_ec_init()
> > >  - Change driver name to cros_ec_ishtp to be consistent with other
> > >    references in the code.
> > >  - Fixed a few typos.
> > >
> > > v1
> > >  - Initial version
> > >
> > >  drivers/platform/chrome/Kconfig         |  13 +
> > >  drivers/platform/chrome/Makefile        |   1 +
> > >  drivers/platform/chrome/cros_ec_ishtp.c | 763 ++++++++++++++++++++++++++++++++
> > >  3 files changed, 777 insertions(+)
> > >  create mode 100644 drivers/platform/chrome/cros_ec_ishtp.c
> > >
> > > diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig
> > > index 16b1615..5848179 100644
> > > --- a/drivers/platform/chrome/Kconfig
> > > +++ b/drivers/platform/chrome/Kconfig
> > > @@ -62,6 +62,19 @@ config CROS_EC_I2C
> > >         a checksum. Failing accesses will be retried three times to
> > >         improve reliability.
> > >
> > > +config CROS_EC_ISHTP
> > > +     tristate "ChromeOS Embedded Controller (ISHTP)"
> > > +     depends on MFD_CROS_EC
> > > +     depends on INTEL_ISH_HID
> > > +     help
> > > +       If you say Y here, you get support for talking to the ChromeOS EC
> > > +       firmware running on Intel Integrated Sensor Hub (ISH), using the
> > > +       ISH Transport protocol (ISH-TP). This uses a simple byte-level
> > > +       protocol with a checksum.
> > > +
> > > +       To compile this driver as a module, choose M here: the
> > > +       module will be called cros_ec_ishtp.
> > > +
> > >  config CROS_EC_SPI
> > >       tristate "ChromeOS Embedded Controller (SPI)"
> > >       depends on MFD_CROS_EC && SPI
> > > diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile
> > > index cd591bf..4efe102 100644
> > > --- a/drivers/platform/chrome/Makefile
> > > +++ b/drivers/platform/chrome/Makefile
> > > @@ -7,6 +7,7 @@ cros_ec_ctl-objs                      := cros_ec_sysfs.o cros_ec_lightbar.o \
> > >                                          cros_ec_vbc.o cros_ec_debugfs.o
> > >  obj-$(CONFIG_CROS_EC_CTL)            += cros_ec_ctl.o
> > >  obj-$(CONFIG_CROS_EC_I2C)            += cros_ec_i2c.o
> > > +obj-$(CONFIG_CROS_EC_ISHTP)          += cros_ec_ishtp.o
> > >  obj-$(CONFIG_CROS_EC_SPI)            += cros_ec_spi.o
> > >  cros_ec_lpcs-objs                    := cros_ec_lpc.o cros_ec_lpc_reg.o
> > >  cros_ec_lpcs-$(CONFIG_CROS_EC_LPC_MEC)       += cros_ec_lpc_mec.o
> > > diff --git a/drivers/platform/chrome/cros_ec_ishtp.c b/drivers/platform/chrome/cros_ec_ishtp.c
> > > new file mode 100644
> > > index 0000000..997503d
> > > --- /dev/null
> > > +++ b/drivers/platform/chrome/cros_ec_ishtp.c
> > > @@ -0,0 +1,763 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +// ISHTP interface for ChromeOS Embedded Controller
> > > +//
> > > +// Copyright (c) 2019, Intel Corporation.
> > > +//
> > > +// ISHTP client driver for talking to the Chrome OS EC firmware running
> > > +// on Intel Integrated Sensor Hub (ISH) using the ISH Transport protocol
> > > +// (ISH-TP).
> > > +
> > > +#include <linux/delay.h>
> > > +#include <linux/mfd/core.h>
> > > +#include <linux/mfd/cros_ec.h>
> > > +#include <linux/mfd/cros_ec_commands.h>
> > > +#include <linux/module.h>
> > > +#include <linux/pci.h>
> > > +#include <linux/intel-ish-client-if.h>
> > > +
> > > +/*
> > > + * ISH TX/RX ring buffer pool size
> > > + *
> > > + * The AP->ISH messages and corresponding ISH->AP responses are
> > > + * serialized. We need 1 TX and 1 RX buffer for these.
> > > + *
> > > + * The MKBP ISH->AP events are serialized. We need one additional RX
> > > + * buffer for them.
> > > + */
> > > +#define CROS_ISH_CL_TX_RING_SIZE             8
> > > +#define CROS_ISH_CL_RX_RING_SIZE             8
> > > +
> > > +/* ISH CrOS EC Host Commands */
> > > +enum cros_ec_ish_channel {
> > > +     CROS_EC_COMMAND = 1,                    /* AP->ISH message */
> > > +     CROS_MKBP_EVENT = 2,                    /* ISH->AP events */
> > > +};
> > > +
> > > +/*
> > > + * ISH firmware timeout for 1 message send failure is 1Hz, and the
> > > + * firmware will retry 2 times, so 3Hz is used for timeout.
> > > + */
> > > +#define ISHTP_SEND_TIMEOUT                   (3 * HZ)
> > > +
> > > +/* ISH Transport CrOS EC ISH client unique GUID */
> > > +static const guid_t cros_ish_guid =
> > > +     GUID_INIT(0x7b7154d0, 0x56f4, 0x4bdc,
> > > +               0xb0, 0xd8, 0x9e, 0x7c, 0xda, 0xe0, 0xd6, 0xa0);
> > > +
> > > +struct header {
> > > +     u8 channel;
> > > +     u8 status;
> > > +     u8 reserved[2];
> > > +} __packed;
> > > +
> > > +struct cros_ish_out_msg {
> > > +     struct header hdr;
> > > +     struct ec_host_request ec_request;
> > > +} __packed;
> > > +
> > > +struct cros_ish_in_msg {
> > > +     struct header hdr;
> > > +     struct ec_host_response ec_response;
> > > +} __packed;
> > > +
> > > +#define IN_MSG_EC_RESPONSE_PREAMBLE                                  \
> > > +     offsetof(struct cros_ish_in_msg, ec_response)
> > > +
> > > +#define OUT_MSG_EC_REQUEST_PREAMBLE                                  \
> > > +     offsetof(struct cros_ish_out_msg, ec_request)
> > > +
> > > +#define cl_data_to_dev(client_data) ishtp_device((client_data)->cl_device)
> > > +
> > > +/*
> > > + * The Read-Write Semaphore is used to prevent message TX or RX while
> > > + * the ishtp client is being initialized or undergoing reset.
> > > + *
> > > + * The readers are the kernel function calls responsible for IA->ISH
> > > + * and ISH->AP messaging.
> > > + *
> > > + * The writers are .reset() and .probe() function.
> > > + */
> > > +DECLARE_RWSEM(init_lock);
> > > +
> > > +/**
> > > + * struct response_info - Encapsulate firmware response related
> > > + * information for passing between function ish_send() and
> > > + * process_recv() callback.
> > > + *
> > > + * @data: Copy the data received from firmware here.
> > > + * @max_size: Max size allocated for the @data buffer. If the received
> > > + * data exceeds this value, we log an error.
> > > + * @size: Actual size of data received from firmware.
> > > + * @error: 0 for success, negative error code for a failure in process_recv().
> > > + * @received: Set to true on receiving a valid firmware      response to host command
> > > + * @wait_queue: Wait queue for host to wait for firmware response.
> > > + */
> > > +struct response_info {
> > > +     void *data;
> > > +     size_t max_size;
> > > +     size_t size;
> > > +     int error;
> > > +     bool received;
> > > +     wait_queue_head_t wait_queue;
> > > +};
> > > +
> > > +/**
> > > + * struct ishtp_cl_data - Encapsulate per ISH TP Client.
> > > + *
> > > + * @cros_ish_cl: ISHTP firmware client instance.
> > > + * @cl_device: ISHTP client device instance.
> > > + * @response: Response info passing between ish_send() and process_recv().
> > > + * @work_ishtp_reset: Work queue reset handling.
> > > + * @work_ec_evt: Work queue for EC events.
> > > + * @ec_dev: CrOS EC MFD device.
> > > + *
> > > + * This structure is used to store per client data.
> > > + */
> > > +struct ishtp_cl_data {
> > > +     struct ishtp_cl *cros_ish_cl;
> > > +     struct ishtp_cl_device *cl_device;
> > > +
> > > +     /*
> > > +      * Used for passing firmware response information between
> > > +      * ish_send() and process_recv() callback.
> > > +      */
> > > +     struct response_info response;
> > > +
> > > +     struct work_struct work_ishtp_reset;
> > > +     struct work_struct work_ec_evt;
> > > +     struct cros_ec_device *ec_dev;
> > > +};
> > > +
> > > +/**
> > > + * ish_evt_handler - ISH to AP event handler
> > > + * @work: Work struct
> > > + */
> > > +static void ish_evt_handler(struct work_struct *work)
> > > +{
> > > +     struct ishtp_cl_data *client_data =
> > > +             container_of(work, struct ishtp_cl_data, work_ec_evt);
> > > +     struct cros_ec_device *ec_dev = client_data->ec_dev;
> > > +
> > > +     if (cros_ec_get_next_event(ec_dev, NULL) > 0) {
> > > +             blocking_notifier_call_chain(&ec_dev->event_notifier,
> > > +                                          0, ec_dev);
> > > +     }
> > > +}
> > > +
> > > +/**
> > > + * ish_send() - Send message from host to firmware
> > > + *
> > > + * @client_data: Client data instance
> > > + * @out_msg: Message buffer to be sent to firmware
> > > + * @out_size: Size of out going message
> > > + * @in_msg: Message buffer where the incoming data is copied. This buffer
> > > + * is allocated by calling
> > > + * @in_size: Max size of incoming message
> > > + *
> > > + * Return: Number of bytes copied in the in_msg on success, negative
> > > + * error code on failure.
> > > + */
> > > +static int ish_send(struct ishtp_cl_data *client_data,
> > > +                 u8 *out_msg, size_t out_size,
> > > +                 u8 *in_msg, size_t in_size)
> > > +{
> > > +     int rv;
> > > +     struct header *out_hdr = (struct header *)out_msg;
> > > +     struct ishtp_cl *cros_ish_cl = client_data->cros_ish_cl;
> > > +
> > > +     dev_dbg(cl_data_to_dev(client_data),
> > > +             "%s: channel=%02u status=%02u\n",
> > > +             __func__, out_hdr->channel, out_hdr->status);
> > > +
> > > +     /* Setup for incoming response */
> > > +     client_data->response.data = in_msg;
> > > +     client_data->response.max_size = in_size;
> > > +     client_data->response.error = 0;
> > > +     client_data->response.received = false;
> > > +
> > > +     rv = ishtp_cl_send(cros_ish_cl, out_msg, out_size);
> > > +     if (rv) {
> > > +             dev_err(cl_data_to_dev(client_data),
> > > +                     "ishtp_cl_send error %d\n", rv);
> > > +             return rv;
> > > +     }
> > > +
> > > +     wait_event_interruptible_timeout(client_data->response.wait_queue,
> > > +                                      client_data->response.received,
> > > +                                      ISHTP_SEND_TIMEOUT);
> > > +     if (!client_data->response.received) {
> > > +             dev_err(cl_data_to_dev(client_data),
> > > +                     "Timed out for response to host message\n");
> > > +             return -ETIMEDOUT;
> > > +     }
> > > +
> > > +     if (client_data->response.error < 0)
> > > +             return client_data->response.error;
> > > +
> > > +     return client_data->response.size;
> > > +}
> > > +
> > > +/**
> > > + * process_recv() - Received and parse incoming packet
> > > + * @cros_ish_cl: Client instance to get stats
> > > + * @rb_in_proc: Host interface message buffer
> > > + *
> > > + * Parse the incoming packet. If it is a response packet then it will
> > > + * update per instance flags and wake up the caller waiting to for the
> > > + * response. If it is an event packet then it will schedule event work.
> > > + */
> > > +static void process_recv(struct ishtp_cl *cros_ish_cl,
> > > +                      struct ishtp_cl_rb *rb_in_proc)
> > > +{
> > > +     size_t data_len = rb_in_proc->buf_idx;
> > > +     struct ishtp_cl_data *client_data =
> > > +             ishtp_get_client_data(cros_ish_cl);
> > > +     struct device *dev = cl_data_to_dev(client_data);
> > > +     struct cros_ish_in_msg *in_msg =
> > > +             (struct cros_ish_in_msg *)rb_in_proc->buffer.data;
> > > +
> > > +     /* Proceed only if reset or init is not in progress */
> > > +     if (!down_read_trylock(&init_lock)) {
> > > +             /* Free the buffer */
> > > +             ishtp_cl_io_rb_recycle(rb_in_proc);
> > > +             dev_warn(dev,
> > > +                      "Host is not ready to receive incoming messages\n");
> > > +             return;
> > > +     }
> > > +
> > > +     /*
> > > +      * All firmware messages contain a header. Check the buffer size
> > > +      * before accessing elements inside.
> > > +      */
> > > +     if (!rb_in_proc->buffer.data) {
> > > +             dev_warn(dev, "rb_in_proc->buffer.data returned null");
> > > +             client_data->response.error = -EBADMSG;
> > > +             goto end_error;
> > > +     }
> > > +
> > > +     if (data_len < sizeof(struct header)) {
> > > +             dev_err(dev, "data size %zu is less than header %zu\n",
> > > +                     data_len, sizeof(struct header));
> > > +             client_data->response.error = -EMSGSIZE;
> > > +             goto end_error;
> > > +     }
> > > +
> > > +     dev_dbg(dev, "channel=%02u status=%02u\n",
> > > +             in_msg->hdr.channel, in_msg->hdr.status);
> > > +
> > > +     switch (in_msg->hdr.channel) {
> > > +     case CROS_EC_COMMAND:
> > > +             /* Sanity check */
> > > +             if (!client_data->response.data) {
> > > +                     dev_err(dev,
> > > +                             "Receiving buffer is null. Should be allocated by calling function\n");
> > > +                     client_data->response.error = -EINVAL;
> > > +                     goto error_wake_up;
> > > +             }
> > > +
> > > +             if (client_data->response.received) {
> > > +                     dev_err(dev,
> > > +                             "Previous firmware message not yet processed\n");
> > > +                     client_data->response.error = -EINVAL;
> > > +                     goto error_wake_up;
> > > +             }
> > > +
> > > +             if (data_len > client_data->response.max_size) {
> > > +                     dev_err(dev,
> > > +                             "Received buffer size %zu is larger than allocated buffer %zu\n",
> > > +                             data_len, client_data->response.max_size);
> > > +                     client_data->response.error = -EMSGSIZE;
> > > +                     goto error_wake_up;
> > > +             }
> > > +
> > > +             if (in_msg->hdr.status) {
> > > +                     dev_err(dev, "firmware returned status %d\n",
> > > +                             in_msg->hdr.status);
> > > +                     client_data->response.error = -EIO;
> > > +                     goto error_wake_up;
> > > +             }
> > > +
> > > +             /* Update the actual received buffer size */
> > > +             client_data->response.size = data_len;
> > > +
> > > +             /*
> > > +              * Copy the buffer received in firmware response for the
> > > +              * calling thread.
> > > +              */
> > > +             memcpy(client_data->response.data,
> > > +                    rb_in_proc->buffer.data, data_len);
> > > +
> > > +             /* Set flag before waking up the caller */
> > > +             client_data->response.received = true;
> > > +error_wake_up:
> > > +             /* Wake the calling thread */
> > > +             wake_up_interruptible(&client_data->response.wait_queue);
> > > +
> > > +             break;
> > > +
> > > +     case CROS_MKBP_EVENT:
> > > +             /* The event system doesn't send any data in buffer */
> > > +             schedule_work(&client_data->work_ec_evt);
> > > +
> > > +             break;
> > > +
> > > +     default:
> > > +             dev_err(dev, "Invalid channel=%02d\n", in_msg->hdr.channel);
> > > +     }
> > > +
> > > +end_error:
> > > +     /* Free the buffer */
> > > +     ishtp_cl_io_rb_recycle(rb_in_proc);
> > > +
> > > +     up_read(&init_lock);
> > > +}
> > > +
> > > +/**
> > > + * ish_event_cb() - bus driver callback for incoming message
> > > + * @cl_device: ISHTP client device for which this message is targeted.
> > > + *
> > > + * Remove the packet from the list and process the message by calling
> > > + * process_recv.
> > > + */
> > > +static void ish_event_cb(struct ishtp_cl_device *cl_device)
> > > +{
> > > +     struct ishtp_cl_rb *rb_in_proc;
> > > +     struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
> > > +
> > > +     while ((rb_in_proc = ishtp_cl_rx_get_rb(cros_ish_cl)) != NULL) {
> > > +             /* Decide what to do with received data */
> > > +             process_recv(cros_ish_cl, rb_in_proc);
> > > +     }
> > > +}
> > > +
> > > +/**
> > > + * cros_ish_init() - Init function for ISHTP client
> > > + * @cros_ish_cl: ISHTP client instance
> > > + *
> > > + * This function complete the initializtion of the client.
> > > + *
> > > + * Return: 0 for success, negative error code for failure.
> > > + */
> > > +static int cros_ish_init(struct ishtp_cl *cros_ish_cl)
> > > +{
> > > +     int rv;
> > > +     struct ishtp_device *dev;
> > > +     struct ishtp_fw_client *fw_client;
> > > +     struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> > > +
> > > +     rv = ishtp_cl_link(cros_ish_cl);
> > > +     if (rv) {
> > > +             dev_err(cl_data_to_dev(client_data),
> > > +                     "ishtp_cl_link failed\n");
> > > +             return rv;
> > > +     }
> > > +
> > > +     dev = ishtp_get_ishtp_device(cros_ish_cl);
> > > +
> > > +     /* Connect to firmware client */
> > > +     ishtp_set_tx_ring_size(cros_ish_cl, CROS_ISH_CL_TX_RING_SIZE);
> > > +     ishtp_set_rx_ring_size(cros_ish_cl, CROS_ISH_CL_RX_RING_SIZE);
> > > +
> > > +     fw_client = ishtp_fw_cl_get_client(dev, &cros_ish_guid);
> > > +     if (!fw_client) {
> > > +             dev_err(cl_data_to_dev(client_data),
> > > +                     "ish client uuid not found\n");
> > > +             rv = -ENOENT;
> > > +             goto err_cl_unlink;
> > > +     }
> > > +
> > > +     ishtp_cl_set_fw_client_id(cros_ish_cl,
> > > +                               ishtp_get_fw_client_id(fw_client));
> > > +     ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_CONNECTING);
> > > +
> > > +     rv = ishtp_cl_connect(cros_ish_cl);
> > > +     if (rv) {
> > > +             dev_err(cl_data_to_dev(client_data),
> > > +                     "client connect fail\n");
> > > +             goto err_cl_unlink;
> > > +     }
> > > +
> > > +     ishtp_register_event_cb(client_data->cl_device, ish_event_cb);
> > > +     return 0;
> > > +
> > > +err_cl_unlink:
> > > +     ishtp_cl_unlink(cros_ish_cl);
> > > +     return rv;
> > > +}
> > > +
> > > +/**
> > > + * cros_ish_deinit() - Deinit function for ISHTP client
> > > + * @cros_ish_cl: ISHTP client instance
> > > + *
> > > + * Unlink and free cros_ec client
> > > + */
> > > +static void cros_ish_deinit(struct ishtp_cl *cros_ish_cl)
> > > +{
> > > +     ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING);
> > > +     ishtp_cl_disconnect(cros_ish_cl);
> > > +     ishtp_cl_unlink(cros_ish_cl);
> > > +     ishtp_cl_flush_queues(cros_ish_cl);
> > > +
> > > +     /* Disband and free all Tx and Rx client-level rings */
> > > +     ishtp_cl_free(cros_ish_cl);
> > > +}
> > > +
> > > +/**
> > > + * prepare_cros_ec_rx() - Check & prepare receive buffer
> > > + * @ec_dev: CrOS EC MFD device.
> > > + * @in_msg: Incoming message buffer
> > > + * @msg: cros_ec command used to send & receive data
> > > + *
> > > + * Return: 0 for success, negative error code for failure.
> > > + *
> > > + * Check the received buffer. Convert to cros_ec_command format.
> > > + */
> > > +static int prepare_cros_ec_rx(struct cros_ec_device *ec_dev,
> > > +                           const struct cros_ish_in_msg *in_msg,
> > > +                           struct cros_ec_command *msg)
> > > +{
> > > +     u8 sum = 0;
> > > +     int i, rv, offset;
> > > +
> > > +     /* Check response error code */
> > > +     msg->result = in_msg->ec_response.result;
> > > +     rv = cros_ec_check_result(ec_dev, msg);
> > > +     if (rv < 0)
> > > +             return rv;
> > > +
> > > +     if (in_msg->ec_response.data_len > msg->insize) {
> > > +             dev_err(ec_dev->dev, "Packet too long (%d bytes, expected %d)",
> > > +                     in_msg->ec_response.data_len, msg->insize);
> > > +             return -ENOSPC;
> > > +     }
> > > +
> > > +     /* Copy response packet payload and compute checksum */
> > > +     for (i = 0; i < sizeof(struct ec_host_response); i++)
> > > +             sum += ((u8 *)in_msg)[IN_MSG_EC_RESPONSE_PREAMBLE + i];
> > > +
> > > +     offset = sizeof(struct cros_ish_in_msg);
> > > +     for (i = 0; i < in_msg->ec_response.data_len; i++)
> > > +             sum += msg->data[i] = ((u8 *)in_msg)[offset + i];
> > > +
> > > +     if (sum) {
> > > +             dev_dbg(ec_dev->dev, "Bad received packet checksum %d\n", sum);
> > > +             return -EBADMSG;
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int cros_ec_pkt_xfer_ish(struct cros_ec_device *ec_dev,
> > > +                             struct cros_ec_command *msg)
> > > +{
> > > +     int rv;
> > > +     struct ishtp_cl *cros_ish_cl = ec_dev->priv;
> > > +     struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> > > +     struct device *dev = cl_data_to_dev(client_data);
> > > +     struct cros_ish_in_msg *in_msg = (struct cros_ish_in_msg *)ec_dev->din;
> > > +     struct cros_ish_out_msg *out_msg =
> > > +             (struct cros_ish_out_msg *)ec_dev->dout;
> > > +     size_t in_size = sizeof(struct cros_ish_in_msg) + msg->insize;
> > > +     size_t out_size = sizeof(struct cros_ish_out_msg) + msg->outsize;
> > > +
> > > +     /* Proceed only if reset-init is not in progress */
> > > +     if (!down_read_trylock(&init_lock)) {
> > > +             dev_warn(dev,
> > > +                      "Host is not ready to send messages to ISH. Try again\n");
> > > +             return -EAGAIN;
> > > +     }
> > > +
> > > +     /* Sanity checks */
> > > +     if (in_size > ec_dev->din_size) {
> > > +             dev_err(dev,
> > > +                     "Incoming payload size %zu is too large for ec_dev->din_size %d\n",
> > > +                     in_size, ec_dev->din_size);
> > > +             return -EMSGSIZE;
> > > +     }
> > > +
> > > +     if (out_size > ec_dev->dout_size) {
> > > +             dev_err(dev,
> > > +                     "Outgoing payload size %zu is too large for ec_dev->dout_size %d\n",
> > > +                     out_size, ec_dev->dout_size);
> > > +             return -EMSGSIZE;
> > > +     }
> > > +
> > > +     /* Prepare the package to be sent over ISH TP */
> > > +     out_msg->hdr.channel = CROS_EC_COMMAND;
> > > +     out_msg->hdr.status = 0;
> > > +
> > > +     ec_dev->dout += OUT_MSG_EC_REQUEST_PREAMBLE;
> > > +     cros_ec_prepare_tx(ec_dev, msg);
> > > +     ec_dev->dout -= OUT_MSG_EC_REQUEST_PREAMBLE;
> > > +
> > > +     dev_dbg(dev,
> > > +             "out_msg: struct_ver=0x%x checksum=0x%x command=0x%x command_ver=0x%x data_len=0x%x\n",
> > > +             out_msg->ec_request.struct_version,
> > > +             out_msg->ec_request.checksum,
> > > +             out_msg->ec_request.command,
> > > +             out_msg->ec_request.command_version,
> > > +             out_msg->ec_request.data_len);
> > > +
> > > +     /* Send command to ISH EC firmware and read response */
> > > +     rv = ish_send(client_data,
> > > +                   (u8 *)out_msg, out_size,
> > > +                   (u8 *)in_msg, in_size);
> > > +     if (rv < 0)
> > > +             goto end_error;
> > > +
> > > +     rv = prepare_cros_ec_rx(ec_dev, in_msg, msg);
> > > +     if (rv)
> > > +             goto end_error;
> > > +
> > > +     rv = in_msg->ec_response.data_len;
> > > +
> > > +     dev_dbg(dev,
> > > +             "in_msg: struct_ver=0x%x checksum=0x%x result=0x%x data_len=0x%x\n",
> > > +             in_msg->ec_response.struct_version,
> > > +             in_msg->ec_response.checksum,
> > > +             in_msg->ec_response.result,
> > > +             in_msg->ec_response.data_len);
> > > +
> > > +end_error:
> > > +     if (msg->command == EC_CMD_REBOOT_EC)
> > > +             msleep(EC_REBOOT_DELAY_MS);
> > > +
> > > +     up_read(&init_lock);
> > > +
> > > +     return rv;
> > > +}
> > > +
> > > +static int cros_ec_dev_init(struct ishtp_cl_data *client_data)
> > > +{
> > > +     struct cros_ec_device *ec_dev;
> > > +     struct device *dev = cl_data_to_dev(client_data);
> > > +
> > > +     ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL);
> > > +     if (!ec_dev)
> > > +             return -ENOMEM;
> > > +
> > > +     client_data->ec_dev = ec_dev;
> > > +     dev->driver_data = ec_dev;
> > > +
> > > +     ec_dev->dev = dev;
> > > +     ec_dev->priv = client_data->cros_ish_cl;
> > > +     ec_dev->cmd_xfer = NULL;
> > > +     ec_dev->pkt_xfer = cros_ec_pkt_xfer_ish;
> > > +     ec_dev->phys_name = dev_name(dev);
> > > +     ec_dev->din_size = sizeof(struct cros_ish_in_msg) +
> > > +                        sizeof(struct ec_response_get_protocol_info);
> > > +     ec_dev->dout_size = sizeof(struct cros_ish_out_msg);
> > > +
> > > +     return cros_ec_register(ec_dev);
> > > +}
> > > +
> > > +static void reset_handler(struct work_struct *work)
> > > +{
> > > +     int rv;
> > > +     struct device *dev;
> > > +     struct ishtp_cl *cros_ish_cl;
> > > +     struct ishtp_cl_device *cl_device;
> > > +     struct ishtp_cl_data *client_data =
> > > +             container_of(work, struct ishtp_cl_data, work_ishtp_reset);
> > > +
> > > +     /* Lock for reset to complete */
> > > +     down_write(&init_lock);
> > > +
> > > +     cros_ish_cl = client_data->cros_ish_cl;
> > > +     cl_device = client_data->cl_device;
> > > +
> > > +     /* Unlink, flush queues & start again */
> > > +     ishtp_cl_unlink(cros_ish_cl);
> > > +     ishtp_cl_flush_queues(cros_ish_cl);
> > > +     ishtp_cl_free(cros_ish_cl);
> > > +
> > > +     cros_ish_cl = ishtp_cl_allocate(cl_device);
> > > +     if (!cros_ish_cl) {
> > > +             up_write(&init_lock);
> > > +             return;
> > > +     }
> > > +
> > > +     ishtp_set_drvdata(cl_device, cros_ish_cl);
> > > +     ishtp_set_client_data(cros_ish_cl, client_data);
> > > +     client_data->cros_ish_cl = cros_ish_cl;
> > > +
> > > +     rv = cros_ish_init(cros_ish_cl);
> > > +     if (rv) {
> > > +             ishtp_cl_free(cros_ish_cl);
> > > +             dev_err(cl_data_to_dev(client_data), "Reset Failed\n");
> > > +             up_write(&init_lock);
> > > +             return;
> > > +     }
> > > +
> > > +     /* Refresh ec_dev device pointers */
> > > +     client_data->ec_dev->priv = client_data->cros_ish_cl;
> > > +     dev = cl_data_to_dev(client_data);
> > > +     dev->driver_data = client_data->ec_dev;
> > > +
> > > +     dev_info(cl_data_to_dev(client_data), "Chrome EC ISH reset done\n");
> > > +
> > > +     up_write(&init_lock);
> > > +}
> > > +
> > > +/**
> > > + * cros_ec_ishtp_probe() - ISHTP client driver probe callback
> > > + * @cl_device: ISHTP client device instance
> > > + *
> > > + * Return: 0 for success, negative error code for failure.
> > > + */
> > > +static int cros_ec_ishtp_probe(struct ishtp_cl_device *cl_device)
> > > +{
> > > +     int rv;
> > > +     struct ishtp_cl *cros_ish_cl;
> > > +     struct ishtp_cl_data *client_data =
> > > +             devm_kzalloc(ishtp_device(cl_device),
> > > +                          sizeof(*client_data), GFP_KERNEL);
> > > +     if (!client_data)
> > > +             return -ENOMEM;
> > > +
> > > +     /* Lock for initialization to complete */
> > > +     down_write(&init_lock);
> > > +
> > > +     cros_ish_cl = ishtp_cl_allocate(cl_device);
> > > +     if (!cros_ish_cl) {
> > > +             rv = -ENOMEM;
> > > +             goto end_ishtp_cl_alloc_error;
> > > +     }
> > > +
> > > +     ishtp_set_drvdata(cl_device, cros_ish_cl);
> > > +     ishtp_set_client_data(cros_ish_cl, client_data);
> > > +     client_data->cros_ish_cl = cros_ish_cl;
> > > +     client_data->cl_device = cl_device;
> > > +
> > > +     init_waitqueue_head(&client_data->response.wait_queue);
> > > +
> > > +     INIT_WORK(&client_data->work_ishtp_reset,
> > > +               reset_handler);
> > > +     INIT_WORK(&client_data->work_ec_evt,
> > > +               ish_evt_handler);
> > > +
> > > +     rv = cros_ish_init(cros_ish_cl);
> > > +     if (rv)
> > > +             goto end_ishtp_cl_init_error;
> > > +
> > > +     ishtp_get_device(cl_device);
> > > +
> > > +     up_write(&init_lock);
> > > +
> > > +     /* Register croc_ec_dev mfd */
> > > +     rv = cros_ec_dev_init(client_data);
> > > +     if (rv)
> > > +             goto end_cros_ec_dev_init_error;
> > > +
> > > +     return 0;
> > > +
> > > +end_cros_ec_dev_init_error:
> > > +     ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING);
> > > +     ishtp_cl_disconnect(cros_ish_cl);
> > > +     ishtp_cl_unlink(cros_ish_cl);
> > > +     ishtp_cl_flush_queues(cros_ish_cl);
> > > +     ishtp_put_device(cl_device);
> > > +end_ishtp_cl_init_error:
> > > +     ishtp_cl_free(cros_ish_cl);
> > > +end_ishtp_cl_alloc_error:
> > > +     up_write(&init_lock);
> > > +     return rv;
> > > +}
> > > +
> > > +/**
> > > + * cros_ec_ishtp_remove() - ISHTP client driver remove callback
> > > + * @cl_device: ISHTP client device instance
> > > + *
> > > + * Return: 0
> > > + */
> > > +static int cros_ec_ishtp_remove(struct ishtp_cl_device *cl_device)
> > > +{
> > > +     struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
> > > +     struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> > > +
> > > +     cancel_work_sync(&client_data->work_ishtp_reset);
> > > +     cancel_work_sync(&client_data->work_ec_evt);
> > > +     cros_ish_deinit(cros_ish_cl);
> > > +     ishtp_put_device(cl_device);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +/**
> > > + * cros_ec_ishtp_reset() - ISHTP client driver reset callback
> > > + * @cl_device: ISHTP client device instance
> > > + *
> > > + * Return: 0
> > > + */
> > > +static int cros_ec_ishtp_reset(struct ishtp_cl_device *cl_device)
> > > +{
> > > +     struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
> > > +     struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> > > +
> > > +     schedule_work(&client_data->work_ishtp_reset);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +/**
> > > + * cros_ec_ishtp_suspend() - ISHTP client driver suspend callback
> > > + * @device: device instance
> > > + *
> > > + * Return: 0 for success, negative error code for failure.
> > > + */
> > > +static int __maybe_unused cros_ec_ishtp_suspend(struct device *device)
> > > +{
> > > +     struct ishtp_cl_device *cl_device = dev_get_drvdata(device);
> > > +     struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
> > > +     struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> > > +
> > > +     return cros_ec_suspend(client_data->ec_dev);
> > > +}
> > > +
> > > +/**
> > > + * cros_ec_ishtp_resume() - ISHTP client driver resume callback
> > > + * @device: device instance
> > > + *
> > > + * Return: 0 for success, negative error code for failure.
> > > + */
> > > +static int __maybe_unused cros_ec_ishtp_resume(struct device *device)
> > > +{
> > > +     struct ishtp_cl_device *cl_device = dev_get_drvdata(device);
> > > +     struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
> > > +     struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> > > +
> > > +     return cros_ec_resume(client_data->ec_dev);
> > > +}
> > > +
> > > +static SIMPLE_DEV_PM_OPS(cros_ec_ishtp_pm_ops, cros_ec_ishtp_suspend,
> > > +                      cros_ec_ishtp_resume);
> > > +
> > > +static struct ishtp_cl_driver        cros_ec_ishtp_driver = {
> > > +     .name = "cros_ec_ishtp",
> > > +     .guid = &cros_ish_guid,
> > > +     .probe = cros_ec_ishtp_probe,
> > > +     .remove = cros_ec_ishtp_remove,
> > > +     .reset = cros_ec_ishtp_reset,
> > > +     .driver = {
> > > +             .pm = &cros_ec_ishtp_pm_ops,
> > > +     },
> > > +};
> > > +
> > > +static int __init cros_ec_ishtp_mod_init(void)
> > > +{
> > > +     return ishtp_cl_driver_register(&cros_ec_ishtp_driver, THIS_MODULE);
> > > +}
> > > +
> > > +static void __exit cros_ec_ishtp_mod_exit(void)
> > > +{
> > > +     ishtp_cl_driver_unregister(&cros_ec_ishtp_driver);
> > > +}
> > > +
> > > +module_init(cros_ec_ishtp_mod_init);
> > > +module_exit(cros_ec_ishtp_mod_exit);
> > > +
> > > +MODULE_DESCRIPTION("ChromeOS EC ISHTP Client Driver");
> > > +MODULE_AUTHOR("Rushikesh S Kadam <rushikesh.s.kadam@intel.com>");
> > > +
> > > +MODULE_LICENSE("GPL v2");
> > > +MODULE_ALIAS("ishtp:*");
> > >

-- 

^ permalink raw reply

* Re: [PATCH V1] elan_i2c: Increment wakeup count if wake source.
From: Dmitry Torokhov @ 2019-05-15 23:15 UTC (permalink / raw)
  To: Ravi Chandra Sadineni
  Cc: 廖崇榮, Benjamin Tissoires, Abhishek Bhardwaj,
	Todd Broch, lkml, linux-input@vger.kernel.org
In-Reply-To: <CAEZbON4Z5GKYvMZJ8ojko_f1xzv2rf4uR6cDz2LMxu+XvzTzog@mail.gmail.com>

On Wed, May 15, 2019 at 09:17:59AM -0700, Ravi Chandra Sadineni wrote:
> Hi Dmitry,
> 
> On Mon, May 13, 2019 at 4:29 PM Dmitry Torokhov
> <dmitry.torokhov@gmail.com> wrote:
> >
> > Hi Ravi,
> >
> > On Mon, May 13, 2019 at 3:06 PM Ravi Chandra Sadineni
> > <ravisadineni@chromium.org> wrote:
> > >
> > > Notify the PM core that this dev is the wake source. This helps
> > > userspace daemon tracking the wake source to identify the origin of the
> > > wake.
> >
> > I wonder if we could do that form the i2c core instead of individual drivers?
> I am sorry, I don't see a way how this could be done.

Sorry, brain fart on my part. Applied, thank you.

> >
> > >
> > > Signed-off-by: Ravi Chandra Sadineni <ravisadineni@chromium.org>
> > > ---
> > >  drivers/input/mouse/elan_i2c_core.c | 2 ++
> > >  1 file changed, 2 insertions(+)
> > >
> > > diff --git a/drivers/input/mouse/elan_i2c_core.c b/drivers/input/mouse/elan_i2c_core.c
> > > index f9525d6f0bfe..2c0561e20b7f 100644
> > > --- a/drivers/input/mouse/elan_i2c_core.c
> > > +++ b/drivers/input/mouse/elan_i2c_core.c
> > > @@ -981,6 +981,8 @@ static irqreturn_t elan_isr(int irq, void *dev_id)
> > >         if (error)
> > >                 goto out;
> > >
> > > +       pm_wakeup_event(dev, 0);
> > > +
> > >         switch (report[ETP_REPORT_ID_OFFSET]) {
> > >         case ETP_REPORT_ID:
> > >                 elan_report_absolute(data, report);
> > > --
> > > 2.20.1
> > >
> >
> > Thanks.
> >
> > --
> > Dmitry
> 
> Thanks,
> Ravi

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH v6] platform: chrome: Add ChromeOS EC ISHTP driver
From: Enric Balletbo Serra @ 2019-05-15 21:24 UTC (permalink / raw)
  To: Rushikesh S Kadam
  Cc: benjamin.tissoires, jikos, Benson Leung, Enric Balletbo i Serra,
	Guenter Roeck, Srinivas Pandruvada, linux-kernel, linux-input,
	Nick Crews, Jett Rink, Gwendal Grignou
In-Reply-To: <1557916721-31315-1-git-send-email-rushikesh.s.kadam@intel.com>

Hi,

Missatge de Rushikesh S Kadam <rushikesh.s.kadam@intel.com> del dia
dc., 15 de maig 2019 a les 12:41:
>
> This driver implements a slim layer to enable the ChromeOS
> EC kernel stack (cros_ec) to communicate with ChromeOS EC
> firmware running on the Intel Integrated Sensor Hub (ISH).
>
> The driver registers a ChromeOS EC MFD device to connect
> with cros_ec kernel stack (upper layer), and it registers a
> client with the ISH Transport Protocol bus (lower layer) to
> talk with the ISH firwmare. See description of the ISHTP
> protocol at Documentation/hid/intel-ish-hid.txt
>
> Signed-off-by: Rushikesh S Kadam <rushikesh.s.kadam@intel.com>
> Acked-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
> Acked-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Reviewed-by: Jett Rink <jettrink@chromium.org>
> Tested-by: Jett Rink <jettrink@chromium.org>
> ---

The following patch is queued to the for-next branch for the autobuilders to
play with, if all goes well I'll add the patch for 5.3 when current merge window
closes.

Thanks,
 Enric

>
> Submitting the patch to linux-input@ per the discussion here
> https://lkml.org/lkml/2019/5/2/339
>
> The patch is baselined to hid git tree, branch for-5.2/ish
> https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git/log/?h=for-5.2/ish
>
> v6
>  - Moved the sanity checks in cros_ec_pkt_xfer_ish() to before
>    the point we take the lock (Bug fix).
>
> v5
>  - Submitting with all Acked-by & Tested-bys. No other changes.
>
> v4
>  - Coding style related changes. No functional changes. Addresses
>    review comments on v3.
>
> v3
>  - Made several changes to improve code readability. Replaced
>    multiple cl_data_to_dev(client_data) with dev variable. Use
>    reverse Xmas tree for variable defintion where it made sense.
>    Dropped few debug prints. Add docstring for function
>    prepare_cros_ec_rx().
>  - Fix code in function prepare_cros_ec_rx() under label
>    end_cros_ec_dev_init_error.
>  - Recycle buffer in process_recv() on failing to obtain the
>    semaphore.
>  - Increase ISHTP TX/RX ring buffer size to 8.
>  - Alphabetically ordered CROS_EC_ISHTP entries in Makefile and
>    Kconfig.
>  - Updated commit message.
>
> v2
>  - Dropped unused "reset" parameter in function cros_ec_init()
>  - Change driver name to cros_ec_ishtp to be consistent with other
>    references in the code.
>  - Fixed a few typos.
>
> v1
>  - Initial version
>
>  drivers/platform/chrome/Kconfig         |  13 +
>  drivers/platform/chrome/Makefile        |   1 +
>  drivers/platform/chrome/cros_ec_ishtp.c | 763 ++++++++++++++++++++++++++++++++
>  3 files changed, 777 insertions(+)
>  create mode 100644 drivers/platform/chrome/cros_ec_ishtp.c
>
> diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig
> index 16b1615..5848179 100644
> --- a/drivers/platform/chrome/Kconfig
> +++ b/drivers/platform/chrome/Kconfig
> @@ -62,6 +62,19 @@ config CROS_EC_I2C
>           a checksum. Failing accesses will be retried three times to
>           improve reliability.
>
> +config CROS_EC_ISHTP
> +       tristate "ChromeOS Embedded Controller (ISHTP)"
> +       depends on MFD_CROS_EC
> +       depends on INTEL_ISH_HID
> +       help
> +         If you say Y here, you get support for talking to the ChromeOS EC
> +         firmware running on Intel Integrated Sensor Hub (ISH), using the
> +         ISH Transport protocol (ISH-TP). This uses a simple byte-level
> +         protocol with a checksum.
> +
> +         To compile this driver as a module, choose M here: the
> +         module will be called cros_ec_ishtp.
> +
>  config CROS_EC_SPI
>         tristate "ChromeOS Embedded Controller (SPI)"
>         depends on MFD_CROS_EC && SPI
> diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile
> index cd591bf..4efe102 100644
> --- a/drivers/platform/chrome/Makefile
> +++ b/drivers/platform/chrome/Makefile
> @@ -7,6 +7,7 @@ cros_ec_ctl-objs                        := cros_ec_sysfs.o cros_ec_lightbar.o \
>                                            cros_ec_vbc.o cros_ec_debugfs.o
>  obj-$(CONFIG_CROS_EC_CTL)              += cros_ec_ctl.o
>  obj-$(CONFIG_CROS_EC_I2C)              += cros_ec_i2c.o
> +obj-$(CONFIG_CROS_EC_ISHTP)            += cros_ec_ishtp.o
>  obj-$(CONFIG_CROS_EC_SPI)              += cros_ec_spi.o
>  cros_ec_lpcs-objs                      := cros_ec_lpc.o cros_ec_lpc_reg.o
>  cros_ec_lpcs-$(CONFIG_CROS_EC_LPC_MEC) += cros_ec_lpc_mec.o
> diff --git a/drivers/platform/chrome/cros_ec_ishtp.c b/drivers/platform/chrome/cros_ec_ishtp.c
> new file mode 100644
> index 0000000..e504d25
> --- /dev/null
> +++ b/drivers/platform/chrome/cros_ec_ishtp.c
> @@ -0,0 +1,763 @@
> +// SPDX-License-Identifier: GPL-2.0
> +// ISHTP interface for ChromeOS Embedded Controller
> +//
> +// Copyright (c) 2019, Intel Corporation.
> +//
> +// ISHTP client driver for talking to the Chrome OS EC firmware running
> +// on Intel Integrated Sensor Hub (ISH) using the ISH Transport protocol
> +// (ISH-TP).
> +
> +#include <linux/delay.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/cros_ec.h>
> +#include <linux/mfd/cros_ec_commands.h>
> +#include <linux/module.h>
> +#include <linux/pci.h>
> +#include <linux/intel-ish-client-if.h>
> +
> +/*
> + * ISH TX/RX ring buffer pool size
> + *
> + * The AP->ISH messages and corresponding ISH->AP responses are
> + * serialized. We need 1 TX and 1 RX buffer for these.
> + *
> + * The MKBP ISH->AP events are serialized. We need one additional RX
> + * buffer for them.
> + */
> +#define CROS_ISH_CL_TX_RING_SIZE               8
> +#define CROS_ISH_CL_RX_RING_SIZE               8
> +
> +/* ISH CrOS EC Host Commands */
> +enum cros_ec_ish_channel {
> +       CROS_EC_COMMAND = 1,                    /* AP->ISH message */
> +       CROS_MKBP_EVENT = 2,                    /* ISH->AP events */
> +};
> +
> +/*
> + * ISH firmware timeout for 1 message send failure is 1Hz, and the
> + * firmware will retry 2 times, so 3Hz is used for timeout.
> + */
> +#define ISHTP_SEND_TIMEOUT                     (3 * HZ)
> +
> +/* ISH Transport CrOS EC ISH client unique GUID */
> +static const guid_t cros_ish_guid =
> +       GUID_INIT(0x7b7154d0, 0x56f4, 0x4bdc,
> +                 0xb0, 0xd8, 0x9e, 0x7c, 0xda, 0xe0, 0xd6, 0xa0);
> +
> +struct header {
> +       u8 channel;
> +       u8 status;
> +       u8 reserved[2];
> +} __packed;
> +
> +struct cros_ish_out_msg {
> +       struct header hdr;
> +       struct ec_host_request ec_request;
> +} __packed;
> +
> +struct cros_ish_in_msg {
> +       struct header hdr;
> +       struct ec_host_response ec_response;
> +} __packed;
> +
> +#define IN_MSG_EC_RESPONSE_PREAMBLE                                    \
> +       offsetof(struct cros_ish_in_msg, ec_response)
> +
> +#define OUT_MSG_EC_REQUEST_PREAMBLE                                    \
> +       offsetof(struct cros_ish_out_msg, ec_request)
> +
> +#define cl_data_to_dev(client_data) ishtp_device((client_data)->cl_device)
> +
> +/*
> + * The Read-Write Semaphore is used to prevent message TX or RX while
> + * the ishtp client is being initialized or undergoing reset.
> + *
> + * The readers are the kernel function calls responsible for IA->ISH
> + * and ISH->AP messaging.
> + *
> + * The writers are .reset() and .probe() function.
> + */
> +DECLARE_RWSEM(init_lock);
> +
> +/**
> + * struct response_info - Encapsulate firmware response related
> + * information for passing between function ish_send() and
> + * process_recv() callback.
> + *
> + * @data: Copy the data received from firmware here.
> + * @max_size: Max size allocated for the @data buffer. If the received
> + * data exceeds this value, we log an error.
> + * @size: Actual size of data received from firmware.
> + * @error: 0 for success, negative error code for a failure in process_recv().
> + * @received: Set to true on receiving a valid firmware        response to host command
> + * @wait_queue: Wait queue for host to wait for firmware response.
> + */
> +struct response_info {
> +       void *data;
> +       size_t max_size;
> +       size_t size;
> +       int error;
> +       bool received;
> +       wait_queue_head_t wait_queue;
> +};
> +
> +/**
> + * struct ishtp_cl_data - Encapsulate per ISH TP Client.
> + *
> + * @cros_ish_cl: ISHTP firmware client instance.
> + * @cl_device: ISHTP client device instance.
> + * @response: Response info passing between ish_send() and process_recv().
> + * @work_ishtp_reset: Work queue reset handling.
> + * @work_ec_evt: Work queue for EC events.
> + * @ec_dev: CrOS EC MFD device.
> + *
> + * This structure is used to store per client data.
> + */
> +struct ishtp_cl_data {
> +       struct ishtp_cl *cros_ish_cl;
> +       struct ishtp_cl_device *cl_device;
> +
> +       /*
> +        * Used for passing firmware response information between
> +        * ish_send() and process_recv() callback.
> +        */
> +       struct response_info response;
> +
> +       struct work_struct work_ishtp_reset;
> +       struct work_struct work_ec_evt;
> +       struct cros_ec_device *ec_dev;
> +};
> +
> +/**
> + * ish_evt_handler - ISH to AP event handler
> + * @work: Work struct
> + */
> +static void ish_evt_handler(struct work_struct *work)
> +{
> +       struct ishtp_cl_data *client_data =
> +               container_of(work, struct ishtp_cl_data, work_ec_evt);
> +       struct cros_ec_device *ec_dev = client_data->ec_dev;
> +
> +       if (cros_ec_get_next_event(ec_dev, NULL) > 0) {
> +               blocking_notifier_call_chain(&ec_dev->event_notifier,
> +                                            0, ec_dev);
> +       }
> +}
> +
> +/**
> + * ish_send() - Send message from host to firmware
> + *
> + * @client_data: Client data instance
> + * @out_msg: Message buffer to be sent to firmware
> + * @out_size: Size of out going message
> + * @in_msg: Message buffer where the incoming data is copied. This buffer
> + * is allocated by calling
> + * @in_size: Max size of incoming message
> + *
> + * Return: Number of bytes copied in the in_msg on success, negative
> + * error code on failure.
> + */
> +static int ish_send(struct ishtp_cl_data *client_data,
> +                   u8 *out_msg, size_t out_size,
> +                   u8 *in_msg, size_t in_size)
> +{
> +       int rv;
> +       struct header *out_hdr = (struct header *)out_msg;
> +       struct ishtp_cl *cros_ish_cl = client_data->cros_ish_cl;
> +
> +       dev_dbg(cl_data_to_dev(client_data),
> +               "%s: channel=%02u status=%02u\n",
> +               __func__, out_hdr->channel, out_hdr->status);
> +
> +       /* Setup for incoming response */
> +       client_data->response.data = in_msg;
> +       client_data->response.max_size = in_size;
> +       client_data->response.error = 0;
> +       client_data->response.received = false;
> +
> +       rv = ishtp_cl_send(cros_ish_cl, out_msg, out_size);
> +       if (rv) {
> +               dev_err(cl_data_to_dev(client_data),
> +                       "ishtp_cl_send error %d\n", rv);
> +               return rv;
> +       }
> +
> +       wait_event_interruptible_timeout(client_data->response.wait_queue,
> +                                        client_data->response.received,
> +                                        ISHTP_SEND_TIMEOUT);
> +       if (!client_data->response.received) {
> +               dev_err(cl_data_to_dev(client_data),
> +                       "Timed out for response to host message\n");
> +               return -ETIMEDOUT;
> +       }
> +
> +       if (client_data->response.error < 0)
> +               return client_data->response.error;
> +
> +       return client_data->response.size;
> +}
> +
> +/**
> + * process_recv() - Received and parse incoming packet
> + * @cros_ish_cl: Client instance to get stats
> + * @rb_in_proc: Host interface message buffer
> + *
> + * Parse the incoming packet. If it is a response packet then it will
> + * update per instance flags and wake up the caller waiting to for the
> + * response. If it is an event packet then it will schedule event work.
> + */
> +static void process_recv(struct ishtp_cl *cros_ish_cl,
> +                        struct ishtp_cl_rb *rb_in_proc)
> +{
> +       size_t data_len = rb_in_proc->buf_idx;
> +       struct ishtp_cl_data *client_data =
> +               ishtp_get_client_data(cros_ish_cl);
> +       struct device *dev = cl_data_to_dev(client_data);
> +       struct cros_ish_in_msg *in_msg =
> +               (struct cros_ish_in_msg *)rb_in_proc->buffer.data;
> +
> +       /* Proceed only if reset or init is not in progress */
> +       if (!down_read_trylock(&init_lock)) {
> +               /* Free the buffer */
> +               ishtp_cl_io_rb_recycle(rb_in_proc);
> +               dev_warn(dev,
> +                        "Host is not ready to receive incoming messages\n");
> +               return;
> +       }
> +
> +       /*
> +        * All firmware messages contain a header. Check the buffer size
> +        * before accessing elements inside.
> +        */
> +       if (!rb_in_proc->buffer.data) {
> +               dev_warn(dev, "rb_in_proc->buffer.data returned null");
> +               client_data->response.error = -EBADMSG;
> +               goto end_error;
> +       }
> +
> +       if (data_len < sizeof(struct header)) {
> +               dev_err(dev, "data size %zu is less than header %zu\n",
> +                       data_len, sizeof(struct header));
> +               client_data->response.error = -EMSGSIZE;
> +               goto end_error;
> +       }
> +
> +       dev_dbg(dev, "channel=%02u status=%02u\n",
> +               in_msg->hdr.channel, in_msg->hdr.status);
> +
> +       switch (in_msg->hdr.channel) {
> +       case CROS_EC_COMMAND:
> +               /* Sanity check */
> +               if (!client_data->response.data) {
> +                       dev_err(dev,
> +                               "Receiving buffer is null. Should be allocated by calling function\n");
> +                       client_data->response.error = -EINVAL;
> +                       goto error_wake_up;
> +               }
> +
> +               if (client_data->response.received) {
> +                       dev_err(dev,
> +                               "Previous firmware message not yet processed\n");
> +                       client_data->response.error = -EINVAL;
> +                       goto error_wake_up;
> +               }
> +
> +               if (data_len > client_data->response.max_size) {
> +                       dev_err(dev,
> +                               "Received buffer size %zu is larger than allocated buffer %zu\n",
> +                               data_len, client_data->response.max_size);
> +                       client_data->response.error = -EMSGSIZE;
> +                       goto error_wake_up;
> +               }
> +
> +               if (in_msg->hdr.status) {
> +                       dev_err(dev, "firmware returned status %d\n",
> +                               in_msg->hdr.status);
> +                       client_data->response.error = -EIO;
> +                       goto error_wake_up;
> +               }
> +
> +               /* Update the actual received buffer size */
> +               client_data->response.size = data_len;
> +
> +               /*
> +                * Copy the buffer received in firmware response for the
> +                * calling thread.
> +                */
> +               memcpy(client_data->response.data,
> +                      rb_in_proc->buffer.data, data_len);
> +
> +               /* Set flag before waking up the caller */
> +               client_data->response.received = true;
> +error_wake_up:
> +               /* Wake the calling thread */
> +               wake_up_interruptible(&client_data->response.wait_queue);
> +
> +               break;
> +
> +       case CROS_MKBP_EVENT:
> +               /* The event system doesn't send any data in buffer */
> +               schedule_work(&client_data->work_ec_evt);
> +
> +               break;
> +
> +       default:
> +               dev_err(dev, "Invalid channel=%02d\n", in_msg->hdr.channel);
> +       }
> +
> +end_error:
> +       /* Free the buffer */
> +       ishtp_cl_io_rb_recycle(rb_in_proc);
> +
> +       up_read(&init_lock);
> +}
> +
> +/**
> + * ish_event_cb() - bus driver callback for incoming message
> + * @cl_device: ISHTP client device for which this message is targeted.
> + *
> + * Remove the packet from the list and process the message by calling
> + * process_recv.
> + */
> +static void ish_event_cb(struct ishtp_cl_device *cl_device)
> +{
> +       struct ishtp_cl_rb *rb_in_proc;
> +       struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
> +
> +       while ((rb_in_proc = ishtp_cl_rx_get_rb(cros_ish_cl)) != NULL) {
> +               /* Decide what to do with received data */
> +               process_recv(cros_ish_cl, rb_in_proc);
> +       }
> +}
> +
> +/**
> + * cros_ish_init() - Init function for ISHTP client
> + * @cros_ish_cl: ISHTP client instance
> + *
> + * This function complete the initializtion of the client.
> + *
> + * Return: 0 for success, negative error code for failure.
> + */
> +static int cros_ish_init(struct ishtp_cl *cros_ish_cl)
> +{
> +       int rv;
> +       struct ishtp_device *dev;
> +       struct ishtp_fw_client *fw_client;
> +       struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> +
> +       rv = ishtp_cl_link(cros_ish_cl);
> +       if (rv) {
> +               dev_err(cl_data_to_dev(client_data),
> +                       "ishtp_cl_link failed\n");
> +               return rv;
> +       }
> +
> +       dev = ishtp_get_ishtp_device(cros_ish_cl);
> +
> +       /* Connect to firmware client */
> +       ishtp_set_tx_ring_size(cros_ish_cl, CROS_ISH_CL_TX_RING_SIZE);
> +       ishtp_set_rx_ring_size(cros_ish_cl, CROS_ISH_CL_RX_RING_SIZE);
> +
> +       fw_client = ishtp_fw_cl_get_client(dev, &cros_ish_guid);
> +       if (!fw_client) {
> +               dev_err(cl_data_to_dev(client_data),
> +                       "ish client uuid not found\n");
> +               rv = -ENOENT;
> +               goto err_cl_unlink;
> +       }
> +
> +       ishtp_cl_set_fw_client_id(cros_ish_cl,
> +                                 ishtp_get_fw_client_id(fw_client));
> +       ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_CONNECTING);
> +
> +       rv = ishtp_cl_connect(cros_ish_cl);
> +       if (rv) {
> +               dev_err(cl_data_to_dev(client_data),
> +                       "client connect fail\n");
> +               goto err_cl_unlink;
> +       }
> +
> +       ishtp_register_event_cb(client_data->cl_device, ish_event_cb);
> +       return 0;
> +
> +err_cl_unlink:
> +       ishtp_cl_unlink(cros_ish_cl);
> +       return rv;
> +}
> +
> +/**
> + * cros_ish_deinit() - Deinit function for ISHTP client
> + * @cros_ish_cl: ISHTP client instance
> + *
> + * Unlink and free cros_ec client
> + */
> +static void cros_ish_deinit(struct ishtp_cl *cros_ish_cl)
> +{
> +       ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING);
> +       ishtp_cl_disconnect(cros_ish_cl);
> +       ishtp_cl_unlink(cros_ish_cl);
> +       ishtp_cl_flush_queues(cros_ish_cl);
> +
> +       /* Disband and free all Tx and Rx client-level rings */
> +       ishtp_cl_free(cros_ish_cl);
> +}
> +
> +/**
> + * prepare_cros_ec_rx() - Check & prepare receive buffer
> + * @ec_dev: CrOS EC MFD device.
> + * @in_msg: Incoming message buffer
> + * @msg: cros_ec command used to send & receive data
> + *
> + * Return: 0 for success, negative error code for failure.
> + *
> + * Check the received buffer. Convert to cros_ec_command format.
> + */
> +static int prepare_cros_ec_rx(struct cros_ec_device *ec_dev,
> +                             const struct cros_ish_in_msg *in_msg,
> +                             struct cros_ec_command *msg)
> +{
> +       u8 sum = 0;
> +       int i, rv, offset;
> +
> +       /* Check response error code */
> +       msg->result = in_msg->ec_response.result;
> +       rv = cros_ec_check_result(ec_dev, msg);
> +       if (rv < 0)
> +               return rv;
> +
> +       if (in_msg->ec_response.data_len > msg->insize) {
> +               dev_err(ec_dev->dev, "Packet too long (%d bytes, expected %d)",
> +                       in_msg->ec_response.data_len, msg->insize);
> +               return -ENOSPC;
> +       }
> +
> +       /* Copy response packet payload and compute checksum */
> +       for (i = 0; i < sizeof(struct ec_host_response); i++)
> +               sum += ((u8 *)in_msg)[IN_MSG_EC_RESPONSE_PREAMBLE + i];
> +
> +       offset = sizeof(struct cros_ish_in_msg);
> +       for (i = 0; i < in_msg->ec_response.data_len; i++)
> +               sum += msg->data[i] = ((u8 *)in_msg)[offset + i];
> +
> +       if (sum) {
> +               dev_dbg(ec_dev->dev, "Bad received packet checksum %d\n", sum);
> +               return -EBADMSG;
> +       }
> +
> +       return 0;
> +}
> +
> +static int cros_ec_pkt_xfer_ish(struct cros_ec_device *ec_dev,
> +                               struct cros_ec_command *msg)
> +{
> +       int rv;
> +       struct ishtp_cl *cros_ish_cl = ec_dev->priv;
> +       struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> +       struct device *dev = cl_data_to_dev(client_data);
> +       struct cros_ish_in_msg *in_msg = (struct cros_ish_in_msg *)ec_dev->din;
> +       struct cros_ish_out_msg *out_msg =
> +               (struct cros_ish_out_msg *)ec_dev->dout;
> +       size_t in_size = sizeof(struct cros_ish_in_msg) + msg->insize;
> +       size_t out_size = sizeof(struct cros_ish_out_msg) + msg->outsize;
> +
> +       /* Sanity checks */
> +       if (in_size > ec_dev->din_size) {
> +               dev_err(dev,
> +                       "Incoming payload size %zu is too large for ec_dev->din_size %d\n",
> +                       in_size, ec_dev->din_size);
> +               return -EMSGSIZE;
> +       }
> +
> +       if (out_size > ec_dev->dout_size) {
> +               dev_err(dev,
> +                       "Outgoing payload size %zu is too large for ec_dev->dout_size %d\n",
> +                       out_size, ec_dev->dout_size);
> +               return -EMSGSIZE;
> +       }
> +
> +       /* Proceed only if reset-init is not in progress */
> +       if (!down_read_trylock(&init_lock)) {
> +               dev_warn(dev,
> +                        "Host is not ready to send messages to ISH. Try again\n");
> +               return -EAGAIN;
> +       }
> +
> +       /* Prepare the package to be sent over ISH TP */
> +       out_msg->hdr.channel = CROS_EC_COMMAND;
> +       out_msg->hdr.status = 0;
> +
> +       ec_dev->dout += OUT_MSG_EC_REQUEST_PREAMBLE;
> +       cros_ec_prepare_tx(ec_dev, msg);
> +       ec_dev->dout -= OUT_MSG_EC_REQUEST_PREAMBLE;
> +
> +       dev_dbg(dev,
> +               "out_msg: struct_ver=0x%x checksum=0x%x command=0x%x command_ver=0x%x data_len=0x%x\n",
> +               out_msg->ec_request.struct_version,
> +               out_msg->ec_request.checksum,
> +               out_msg->ec_request.command,
> +               out_msg->ec_request.command_version,
> +               out_msg->ec_request.data_len);
> +
> +       /* Send command to ISH EC firmware and read response */
> +       rv = ish_send(client_data,
> +                     (u8 *)out_msg, out_size,
> +                     (u8 *)in_msg, in_size);
> +       if (rv < 0)
> +               goto end_error;
> +
> +       rv = prepare_cros_ec_rx(ec_dev, in_msg, msg);
> +       if (rv)
> +               goto end_error;
> +
> +       rv = in_msg->ec_response.data_len;
> +
> +       dev_dbg(dev,
> +               "in_msg: struct_ver=0x%x checksum=0x%x result=0x%x data_len=0x%x\n",
> +               in_msg->ec_response.struct_version,
> +               in_msg->ec_response.checksum,
> +               in_msg->ec_response.result,
> +               in_msg->ec_response.data_len);
> +
> +end_error:
> +       if (msg->command == EC_CMD_REBOOT_EC)
> +               msleep(EC_REBOOT_DELAY_MS);
> +
> +       up_read(&init_lock);
> +
> +       return rv;
> +}
> +
> +static int cros_ec_dev_init(struct ishtp_cl_data *client_data)
> +{
> +       struct cros_ec_device *ec_dev;
> +       struct device *dev = cl_data_to_dev(client_data);
> +
> +       ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL);
> +       if (!ec_dev)
> +               return -ENOMEM;
> +
> +       client_data->ec_dev = ec_dev;
> +       dev->driver_data = ec_dev;
> +
> +       ec_dev->dev = dev;
> +       ec_dev->priv = client_data->cros_ish_cl;
> +       ec_dev->cmd_xfer = NULL;
> +       ec_dev->pkt_xfer = cros_ec_pkt_xfer_ish;
> +       ec_dev->phys_name = dev_name(dev);
> +       ec_dev->din_size = sizeof(struct cros_ish_in_msg) +
> +                          sizeof(struct ec_response_get_protocol_info);
> +       ec_dev->dout_size = sizeof(struct cros_ish_out_msg);
> +
> +       return cros_ec_register(ec_dev);
> +}
> +
> +static void reset_handler(struct work_struct *work)
> +{
> +       int rv;
> +       struct device *dev;
> +       struct ishtp_cl *cros_ish_cl;
> +       struct ishtp_cl_device *cl_device;
> +       struct ishtp_cl_data *client_data =
> +               container_of(work, struct ishtp_cl_data, work_ishtp_reset);
> +
> +       /* Lock for reset to complete */
> +       down_write(&init_lock);
> +
> +       cros_ish_cl = client_data->cros_ish_cl;
> +       cl_device = client_data->cl_device;
> +
> +       /* Unlink, flush queues & start again */
> +       ishtp_cl_unlink(cros_ish_cl);
> +       ishtp_cl_flush_queues(cros_ish_cl);
> +       ishtp_cl_free(cros_ish_cl);
> +
> +       cros_ish_cl = ishtp_cl_allocate(cl_device);
> +       if (!cros_ish_cl) {
> +               up_write(&init_lock);
> +               return;
> +       }
> +
> +       ishtp_set_drvdata(cl_device, cros_ish_cl);
> +       ishtp_set_client_data(cros_ish_cl, client_data);
> +       client_data->cros_ish_cl = cros_ish_cl;
> +
> +       rv = cros_ish_init(cros_ish_cl);
> +       if (rv) {
> +               ishtp_cl_free(cros_ish_cl);
> +               dev_err(cl_data_to_dev(client_data), "Reset Failed\n");
> +               up_write(&init_lock);
> +               return;
> +       }
> +
> +       /* Refresh ec_dev device pointers */
> +       client_data->ec_dev->priv = client_data->cros_ish_cl;
> +       dev = cl_data_to_dev(client_data);
> +       dev->driver_data = client_data->ec_dev;
> +
> +       dev_info(cl_data_to_dev(client_data), "Chrome EC ISH reset done\n");
> +
> +       up_write(&init_lock);
> +}
> +
> +/**
> + * cros_ec_ishtp_probe() - ISHTP client driver probe callback
> + * @cl_device: ISHTP client device instance
> + *
> + * Return: 0 for success, negative error code for failure.
> + */
> +static int cros_ec_ishtp_probe(struct ishtp_cl_device *cl_device)
> +{
> +       int rv;
> +       struct ishtp_cl *cros_ish_cl;
> +       struct ishtp_cl_data *client_data =
> +               devm_kzalloc(ishtp_device(cl_device),
> +                            sizeof(*client_data), GFP_KERNEL);
> +       if (!client_data)
> +               return -ENOMEM;
> +
> +       /* Lock for initialization to complete */
> +       down_write(&init_lock);
> +
> +       cros_ish_cl = ishtp_cl_allocate(cl_device);
> +       if (!cros_ish_cl) {
> +               rv = -ENOMEM;
> +               goto end_ishtp_cl_alloc_error;
> +       }
> +
> +       ishtp_set_drvdata(cl_device, cros_ish_cl);
> +       ishtp_set_client_data(cros_ish_cl, client_data);
> +       client_data->cros_ish_cl = cros_ish_cl;
> +       client_data->cl_device = cl_device;
> +
> +       init_waitqueue_head(&client_data->response.wait_queue);
> +
> +       INIT_WORK(&client_data->work_ishtp_reset,
> +                 reset_handler);
> +       INIT_WORK(&client_data->work_ec_evt,
> +                 ish_evt_handler);
> +
> +       rv = cros_ish_init(cros_ish_cl);
> +       if (rv)
> +               goto end_ishtp_cl_init_error;
> +
> +       ishtp_get_device(cl_device);
> +
> +       up_write(&init_lock);
> +
> +       /* Register croc_ec_dev mfd */
> +       rv = cros_ec_dev_init(client_data);
> +       if (rv)
> +               goto end_cros_ec_dev_init_error;
> +
> +       return 0;
> +
> +end_cros_ec_dev_init_error:
> +       ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING);
> +       ishtp_cl_disconnect(cros_ish_cl);
> +       ishtp_cl_unlink(cros_ish_cl);
> +       ishtp_cl_flush_queues(cros_ish_cl);
> +       ishtp_put_device(cl_device);
> +end_ishtp_cl_init_error:
> +       ishtp_cl_free(cros_ish_cl);
> +end_ishtp_cl_alloc_error:
> +       up_write(&init_lock);
> +       return rv;
> +}
> +
> +/**
> + * cros_ec_ishtp_remove() - ISHTP client driver remove callback
> + * @cl_device: ISHTP client device instance
> + *
> + * Return: 0
> + */
> +static int cros_ec_ishtp_remove(struct ishtp_cl_device *cl_device)
> +{
> +       struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
> +       struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> +
> +       cancel_work_sync(&client_data->work_ishtp_reset);
> +       cancel_work_sync(&client_data->work_ec_evt);
> +       cros_ish_deinit(cros_ish_cl);
> +       ishtp_put_device(cl_device);
> +
> +       return 0;
> +}
> +
> +/**
> + * cros_ec_ishtp_reset() - ISHTP client driver reset callback
> + * @cl_device: ISHTP client device instance
> + *
> + * Return: 0
> + */
> +static int cros_ec_ishtp_reset(struct ishtp_cl_device *cl_device)
> +{
> +       struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
> +       struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> +
> +       schedule_work(&client_data->work_ishtp_reset);
> +
> +       return 0;
> +}
> +
> +/**
> + * cros_ec_ishtp_suspend() - ISHTP client driver suspend callback
> + * @device: device instance
> + *
> + * Return: 0 for success, negative error code for failure.
> + */
> +static int __maybe_unused cros_ec_ishtp_suspend(struct device *device)
> +{
> +       struct ishtp_cl_device *cl_device = dev_get_drvdata(device);
> +       struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
> +       struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> +
> +       return cros_ec_suspend(client_data->ec_dev);
> +}
> +
> +/**
> + * cros_ec_ishtp_resume() - ISHTP client driver resume callback
> + * @device: device instance
> + *
> + * Return: 0 for success, negative error code for failure.
> + */
> +static int __maybe_unused cros_ec_ishtp_resume(struct device *device)
> +{
> +       struct ishtp_cl_device *cl_device = dev_get_drvdata(device);
> +       struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
> +       struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> +
> +       return cros_ec_resume(client_data->ec_dev);
> +}
> +
> +static SIMPLE_DEV_PM_OPS(cros_ec_ishtp_pm_ops, cros_ec_ishtp_suspend,
> +                        cros_ec_ishtp_resume);
> +
> +static struct ishtp_cl_driver  cros_ec_ishtp_driver = {
> +       .name = "cros_ec_ishtp",
> +       .guid = &cros_ish_guid,
> +       .probe = cros_ec_ishtp_probe,
> +       .remove = cros_ec_ishtp_remove,
> +       .reset = cros_ec_ishtp_reset,
> +       .driver = {
> +               .pm = &cros_ec_ishtp_pm_ops,
> +       },
> +};
> +
> +static int __init cros_ec_ishtp_mod_init(void)
> +{
> +       return ishtp_cl_driver_register(&cros_ec_ishtp_driver, THIS_MODULE);
> +}
> +
> +static void __exit cros_ec_ishtp_mod_exit(void)
> +{
> +       ishtp_cl_driver_unregister(&cros_ec_ishtp_driver);
> +}
> +
> +module_init(cros_ec_ishtp_mod_init);
> +module_exit(cros_ec_ishtp_mod_exit);
> +
> +MODULE_DESCRIPTION("ChromeOS EC ISHTP Client Driver");
> +MODULE_AUTHOR("Rushikesh S Kadam <rushikesh.s.kadam@intel.com>");
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("ishtp:*");
> --
> 1.9.1
>

^ permalink raw reply

* Re: [PATCH v5] platform: chrome: Add ChromeOS EC ISHTP driver
From: Enric Balletbo Serra @ 2019-05-15 21:23 UTC (permalink / raw)
  To: Enric Balletbo i Serra
  Cc: Rushikesh S Kadam, benjamin.tissoires, jikos, Benson Leung,
	Guenter Roeck, Srinivas Pandruvada, linux-kernel, linux-input,
	Nick Crews, Jett Rink, Gwendal Grignou
In-Reply-To: <ce1c6b1e-7a08-057e-898a-2ed506619cc2@collabora.com>

Missatge de Enric Balletbo i Serra <enric.balletbo@collabora.com> del
dia dc., 15 de maig 2019 a les 15:00:
>
> Hi,
>
> On 4/5/19 15:34, Rushikesh S Kadam wrote:
> > This driver implements a slim layer to enable the ChromeOS
> > EC kernel stack (cros_ec) to communicate with ChromeOS EC
> > firmware running on the Intel Integrated Sensor Hub (ISH).
> >
> > The driver registers a ChromeOS EC MFD device to connect
> > with cros_ec kernel stack (upper layer), and it registers a
> > client with the ISH Transport Protocol bus (lower layer) to
> > talk with the ISH firwmare. See description of the ISHTP
> > protocol at Documentation/hid/intel-ish-hid.txt
> >
> > Signed-off-by: Rushikesh S Kadam <rushikesh.s.kadam@intel.com>
> > Acked-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> > Acked-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
> > Reviewed-by: Jett Rink <jettrink@chromium.org>
> > Tested-by: Jett Rink <jettrink@chromium.org>
> > ---
>
> The following patch is queued to the for-next branch for the autobuilders to
> play with, if all goes well I'll add the patch for 5.3 when current merge window
> closes.
>

Actually, I reverted this patch and applied v6.


> Thanks,
>  Enric
>
> >
> > Submitting the patch to linux-input@ per the discussion here
> > https://lkml.org/lkml/2019/5/2/339
> >
> > The patch is baselined to hid git tree, branch for-5.2/ish
> > https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git/log/?h=for-5.2/ish
> >
> > v5
> >  - Submitting with all Acked-by & Tested-bys. No other changes.
> >
> > v4
> >  - Coding style related changes. No functional changes. Addresses
> >    review comments on v3.
> >
> > v3
> >  - Made several changes to improve code readability. Replaced
> >    multiple cl_data_to_dev(client_data) with dev variable. Use
> >    reverse Xmas tree for variable defintion where it made sense.
> >    Dropped few debug prints. Add docstring for function
> >    prepare_cros_ec_rx().
> >  - Fix code in function prepare_cros_ec_rx() under label
> >    end_cros_ec_dev_init_error.
> >  - Recycle buffer in process_recv() on failing to obtain the
> >    semaphore.
> >  - Increase ISHTP TX/RX ring buffer size to 8.
> >  - Alphabetically ordered CROS_EC_ISHTP entries in Makefile and
> >    Kconfig.
> >  - Updated commit message.
> >
> > v2
> >  - Dropped unused "reset" parameter in function cros_ec_init()
> >  - Change driver name to cros_ec_ishtp to be consistent with other
> >    references in the code.
> >  - Fixed a few typos.
> >
> > v1
> >  - Initial version
> >
> >  drivers/platform/chrome/Kconfig         |  13 +
> >  drivers/platform/chrome/Makefile        |   1 +
> >  drivers/platform/chrome/cros_ec_ishtp.c | 763 ++++++++++++++++++++++++++++++++
> >  3 files changed, 777 insertions(+)
> >  create mode 100644 drivers/platform/chrome/cros_ec_ishtp.c
> >
> > diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig
> > index 16b1615..5848179 100644
> > --- a/drivers/platform/chrome/Kconfig
> > +++ b/drivers/platform/chrome/Kconfig
> > @@ -62,6 +62,19 @@ config CROS_EC_I2C
> >         a checksum. Failing accesses will be retried three times to
> >         improve reliability.
> >
> > +config CROS_EC_ISHTP
> > +     tristate "ChromeOS Embedded Controller (ISHTP)"
> > +     depends on MFD_CROS_EC
> > +     depends on INTEL_ISH_HID
> > +     help
> > +       If you say Y here, you get support for talking to the ChromeOS EC
> > +       firmware running on Intel Integrated Sensor Hub (ISH), using the
> > +       ISH Transport protocol (ISH-TP). This uses a simple byte-level
> > +       protocol with a checksum.
> > +
> > +       To compile this driver as a module, choose M here: the
> > +       module will be called cros_ec_ishtp.
> > +
> >  config CROS_EC_SPI
> >       tristate "ChromeOS Embedded Controller (SPI)"
> >       depends on MFD_CROS_EC && SPI
> > diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile
> > index cd591bf..4efe102 100644
> > --- a/drivers/platform/chrome/Makefile
> > +++ b/drivers/platform/chrome/Makefile
> > @@ -7,6 +7,7 @@ cros_ec_ctl-objs                      := cros_ec_sysfs.o cros_ec_lightbar.o \
> >                                          cros_ec_vbc.o cros_ec_debugfs.o
> >  obj-$(CONFIG_CROS_EC_CTL)            += cros_ec_ctl.o
> >  obj-$(CONFIG_CROS_EC_I2C)            += cros_ec_i2c.o
> > +obj-$(CONFIG_CROS_EC_ISHTP)          += cros_ec_ishtp.o
> >  obj-$(CONFIG_CROS_EC_SPI)            += cros_ec_spi.o
> >  cros_ec_lpcs-objs                    := cros_ec_lpc.o cros_ec_lpc_reg.o
> >  cros_ec_lpcs-$(CONFIG_CROS_EC_LPC_MEC)       += cros_ec_lpc_mec.o
> > diff --git a/drivers/platform/chrome/cros_ec_ishtp.c b/drivers/platform/chrome/cros_ec_ishtp.c
> > new file mode 100644
> > index 0000000..997503d
> > --- /dev/null
> > +++ b/drivers/platform/chrome/cros_ec_ishtp.c
> > @@ -0,0 +1,763 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +// ISHTP interface for ChromeOS Embedded Controller
> > +//
> > +// Copyright (c) 2019, Intel Corporation.
> > +//
> > +// ISHTP client driver for talking to the Chrome OS EC firmware running
> > +// on Intel Integrated Sensor Hub (ISH) using the ISH Transport protocol
> > +// (ISH-TP).
> > +
> > +#include <linux/delay.h>
> > +#include <linux/mfd/core.h>
> > +#include <linux/mfd/cros_ec.h>
> > +#include <linux/mfd/cros_ec_commands.h>
> > +#include <linux/module.h>
> > +#include <linux/pci.h>
> > +#include <linux/intel-ish-client-if.h>
> > +
> > +/*
> > + * ISH TX/RX ring buffer pool size
> > + *
> > + * The AP->ISH messages and corresponding ISH->AP responses are
> > + * serialized. We need 1 TX and 1 RX buffer for these.
> > + *
> > + * The MKBP ISH->AP events are serialized. We need one additional RX
> > + * buffer for them.
> > + */
> > +#define CROS_ISH_CL_TX_RING_SIZE             8
> > +#define CROS_ISH_CL_RX_RING_SIZE             8
> > +
> > +/* ISH CrOS EC Host Commands */
> > +enum cros_ec_ish_channel {
> > +     CROS_EC_COMMAND = 1,                    /* AP->ISH message */
> > +     CROS_MKBP_EVENT = 2,                    /* ISH->AP events */
> > +};
> > +
> > +/*
> > + * ISH firmware timeout for 1 message send failure is 1Hz, and the
> > + * firmware will retry 2 times, so 3Hz is used for timeout.
> > + */
> > +#define ISHTP_SEND_TIMEOUT                   (3 * HZ)
> > +
> > +/* ISH Transport CrOS EC ISH client unique GUID */
> > +static const guid_t cros_ish_guid =
> > +     GUID_INIT(0x7b7154d0, 0x56f4, 0x4bdc,
> > +               0xb0, 0xd8, 0x9e, 0x7c, 0xda, 0xe0, 0xd6, 0xa0);
> > +
> > +struct header {
> > +     u8 channel;
> > +     u8 status;
> > +     u8 reserved[2];
> > +} __packed;
> > +
> > +struct cros_ish_out_msg {
> > +     struct header hdr;
> > +     struct ec_host_request ec_request;
> > +} __packed;
> > +
> > +struct cros_ish_in_msg {
> > +     struct header hdr;
> > +     struct ec_host_response ec_response;
> > +} __packed;
> > +
> > +#define IN_MSG_EC_RESPONSE_PREAMBLE                                  \
> > +     offsetof(struct cros_ish_in_msg, ec_response)
> > +
> > +#define OUT_MSG_EC_REQUEST_PREAMBLE                                  \
> > +     offsetof(struct cros_ish_out_msg, ec_request)
> > +
> > +#define cl_data_to_dev(client_data) ishtp_device((client_data)->cl_device)
> > +
> > +/*
> > + * The Read-Write Semaphore is used to prevent message TX or RX while
> > + * the ishtp client is being initialized or undergoing reset.
> > + *
> > + * The readers are the kernel function calls responsible for IA->ISH
> > + * and ISH->AP messaging.
> > + *
> > + * The writers are .reset() and .probe() function.
> > + */
> > +DECLARE_RWSEM(init_lock);
> > +
> > +/**
> > + * struct response_info - Encapsulate firmware response related
> > + * information for passing between function ish_send() and
> > + * process_recv() callback.
> > + *
> > + * @data: Copy the data received from firmware here.
> > + * @max_size: Max size allocated for the @data buffer. If the received
> > + * data exceeds this value, we log an error.
> > + * @size: Actual size of data received from firmware.
> > + * @error: 0 for success, negative error code for a failure in process_recv().
> > + * @received: Set to true on receiving a valid firmware      response to host command
> > + * @wait_queue: Wait queue for host to wait for firmware response.
> > + */
> > +struct response_info {
> > +     void *data;
> > +     size_t max_size;
> > +     size_t size;
> > +     int error;
> > +     bool received;
> > +     wait_queue_head_t wait_queue;
> > +};
> > +
> > +/**
> > + * struct ishtp_cl_data - Encapsulate per ISH TP Client.
> > + *
> > + * @cros_ish_cl: ISHTP firmware client instance.
> > + * @cl_device: ISHTP client device instance.
> > + * @response: Response info passing between ish_send() and process_recv().
> > + * @work_ishtp_reset: Work queue reset handling.
> > + * @work_ec_evt: Work queue for EC events.
> > + * @ec_dev: CrOS EC MFD device.
> > + *
> > + * This structure is used to store per client data.
> > + */
> > +struct ishtp_cl_data {
> > +     struct ishtp_cl *cros_ish_cl;
> > +     struct ishtp_cl_device *cl_device;
> > +
> > +     /*
> > +      * Used for passing firmware response information between
> > +      * ish_send() and process_recv() callback.
> > +      */
> > +     struct response_info response;
> > +
> > +     struct work_struct work_ishtp_reset;
> > +     struct work_struct work_ec_evt;
> > +     struct cros_ec_device *ec_dev;
> > +};
> > +
> > +/**
> > + * ish_evt_handler - ISH to AP event handler
> > + * @work: Work struct
> > + */
> > +static void ish_evt_handler(struct work_struct *work)
> > +{
> > +     struct ishtp_cl_data *client_data =
> > +             container_of(work, struct ishtp_cl_data, work_ec_evt);
> > +     struct cros_ec_device *ec_dev = client_data->ec_dev;
> > +
> > +     if (cros_ec_get_next_event(ec_dev, NULL) > 0) {
> > +             blocking_notifier_call_chain(&ec_dev->event_notifier,
> > +                                          0, ec_dev);
> > +     }
> > +}
> > +
> > +/**
> > + * ish_send() - Send message from host to firmware
> > + *
> > + * @client_data: Client data instance
> > + * @out_msg: Message buffer to be sent to firmware
> > + * @out_size: Size of out going message
> > + * @in_msg: Message buffer where the incoming data is copied. This buffer
> > + * is allocated by calling
> > + * @in_size: Max size of incoming message
> > + *
> > + * Return: Number of bytes copied in the in_msg on success, negative
> > + * error code on failure.
> > + */
> > +static int ish_send(struct ishtp_cl_data *client_data,
> > +                 u8 *out_msg, size_t out_size,
> > +                 u8 *in_msg, size_t in_size)
> > +{
> > +     int rv;
> > +     struct header *out_hdr = (struct header *)out_msg;
> > +     struct ishtp_cl *cros_ish_cl = client_data->cros_ish_cl;
> > +
> > +     dev_dbg(cl_data_to_dev(client_data),
> > +             "%s: channel=%02u status=%02u\n",
> > +             __func__, out_hdr->channel, out_hdr->status);
> > +
> > +     /* Setup for incoming response */
> > +     client_data->response.data = in_msg;
> > +     client_data->response.max_size = in_size;
> > +     client_data->response.error = 0;
> > +     client_data->response.received = false;
> > +
> > +     rv = ishtp_cl_send(cros_ish_cl, out_msg, out_size);
> > +     if (rv) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "ishtp_cl_send error %d\n", rv);
> > +             return rv;
> > +     }
> > +
> > +     wait_event_interruptible_timeout(client_data->response.wait_queue,
> > +                                      client_data->response.received,
> > +                                      ISHTP_SEND_TIMEOUT);
> > +     if (!client_data->response.received) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "Timed out for response to host message\n");
> > +             return -ETIMEDOUT;
> > +     }
> > +
> > +     if (client_data->response.error < 0)
> > +             return client_data->response.error;
> > +
> > +     return client_data->response.size;
> > +}
> > +
> > +/**
> > + * process_recv() - Received and parse incoming packet
> > + * @cros_ish_cl: Client instance to get stats
> > + * @rb_in_proc: Host interface message buffer
> > + *
> > + * Parse the incoming packet. If it is a response packet then it will
> > + * update per instance flags and wake up the caller waiting to for the
> > + * response. If it is an event packet then it will schedule event work.
> > + */
> > +static void process_recv(struct ishtp_cl *cros_ish_cl,
> > +                      struct ishtp_cl_rb *rb_in_proc)
> > +{
> > +     size_t data_len = rb_in_proc->buf_idx;
> > +     struct ishtp_cl_data *client_data =
> > +             ishtp_get_client_data(cros_ish_cl);
> > +     struct device *dev = cl_data_to_dev(client_data);
> > +     struct cros_ish_in_msg *in_msg =
> > +             (struct cros_ish_in_msg *)rb_in_proc->buffer.data;
> > +
> > +     /* Proceed only if reset or init is not in progress */
> > +     if (!down_read_trylock(&init_lock)) {
> > +             /* Free the buffer */
> > +             ishtp_cl_io_rb_recycle(rb_in_proc);
> > +             dev_warn(dev,
> > +                      "Host is not ready to receive incoming messages\n");
> > +             return;
> > +     }
> > +
> > +     /*
> > +      * All firmware messages contain a header. Check the buffer size
> > +      * before accessing elements inside.
> > +      */
> > +     if (!rb_in_proc->buffer.data) {
> > +             dev_warn(dev, "rb_in_proc->buffer.data returned null");
> > +             client_data->response.error = -EBADMSG;
> > +             goto end_error;
> > +     }
> > +
> > +     if (data_len < sizeof(struct header)) {
> > +             dev_err(dev, "data size %zu is less than header %zu\n",
> > +                     data_len, sizeof(struct header));
> > +             client_data->response.error = -EMSGSIZE;
> > +             goto end_error;
> > +     }
> > +
> > +     dev_dbg(dev, "channel=%02u status=%02u\n",
> > +             in_msg->hdr.channel, in_msg->hdr.status);
> > +
> > +     switch (in_msg->hdr.channel) {
> > +     case CROS_EC_COMMAND:
> > +             /* Sanity check */
> > +             if (!client_data->response.data) {
> > +                     dev_err(dev,
> > +                             "Receiving buffer is null. Should be allocated by calling function\n");
> > +                     client_data->response.error = -EINVAL;
> > +                     goto error_wake_up;
> > +             }
> > +
> > +             if (client_data->response.received) {
> > +                     dev_err(dev,
> > +                             "Previous firmware message not yet processed\n");
> > +                     client_data->response.error = -EINVAL;
> > +                     goto error_wake_up;
> > +             }
> > +
> > +             if (data_len > client_data->response.max_size) {
> > +                     dev_err(dev,
> > +                             "Received buffer size %zu is larger than allocated buffer %zu\n",
> > +                             data_len, client_data->response.max_size);
> > +                     client_data->response.error = -EMSGSIZE;
> > +                     goto error_wake_up;
> > +             }
> > +
> > +             if (in_msg->hdr.status) {
> > +                     dev_err(dev, "firmware returned status %d\n",
> > +                             in_msg->hdr.status);
> > +                     client_data->response.error = -EIO;
> > +                     goto error_wake_up;
> > +             }
> > +
> > +             /* Update the actual received buffer size */
> > +             client_data->response.size = data_len;
> > +
> > +             /*
> > +              * Copy the buffer received in firmware response for the
> > +              * calling thread.
> > +              */
> > +             memcpy(client_data->response.data,
> > +                    rb_in_proc->buffer.data, data_len);
> > +
> > +             /* Set flag before waking up the caller */
> > +             client_data->response.received = true;
> > +error_wake_up:
> > +             /* Wake the calling thread */
> > +             wake_up_interruptible(&client_data->response.wait_queue);
> > +
> > +             break;
> > +
> > +     case CROS_MKBP_EVENT:
> > +             /* The event system doesn't send any data in buffer */
> > +             schedule_work(&client_data->work_ec_evt);
> > +
> > +             break;
> > +
> > +     default:
> > +             dev_err(dev, "Invalid channel=%02d\n", in_msg->hdr.channel);
> > +     }
> > +
> > +end_error:
> > +     /* Free the buffer */
> > +     ishtp_cl_io_rb_recycle(rb_in_proc);
> > +
> > +     up_read(&init_lock);
> > +}
> > +
> > +/**
> > + * ish_event_cb() - bus driver callback for incoming message
> > + * @cl_device: ISHTP client device for which this message is targeted.
> > + *
> > + * Remove the packet from the list and process the message by calling
> > + * process_recv.
> > + */
> > +static void ish_event_cb(struct ishtp_cl_device *cl_device)
> > +{
> > +     struct ishtp_cl_rb *rb_in_proc;
> > +     struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
> > +
> > +     while ((rb_in_proc = ishtp_cl_rx_get_rb(cros_ish_cl)) != NULL) {
> > +             /* Decide what to do with received data */
> > +             process_recv(cros_ish_cl, rb_in_proc);
> > +     }
> > +}
> > +
> > +/**
> > + * cros_ish_init() - Init function for ISHTP client
> > + * @cros_ish_cl: ISHTP client instance
> > + *
> > + * This function complete the initializtion of the client.
> > + *
> > + * Return: 0 for success, negative error code for failure.
> > + */
> > +static int cros_ish_init(struct ishtp_cl *cros_ish_cl)
> > +{
> > +     int rv;
> > +     struct ishtp_device *dev;
> > +     struct ishtp_fw_client *fw_client;
> > +     struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> > +
> > +     rv = ishtp_cl_link(cros_ish_cl);
> > +     if (rv) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "ishtp_cl_link failed\n");
> > +             return rv;
> > +     }
> > +
> > +     dev = ishtp_get_ishtp_device(cros_ish_cl);
> > +
> > +     /* Connect to firmware client */
> > +     ishtp_set_tx_ring_size(cros_ish_cl, CROS_ISH_CL_TX_RING_SIZE);
> > +     ishtp_set_rx_ring_size(cros_ish_cl, CROS_ISH_CL_RX_RING_SIZE);
> > +
> > +     fw_client = ishtp_fw_cl_get_client(dev, &cros_ish_guid);
> > +     if (!fw_client) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "ish client uuid not found\n");
> > +             rv = -ENOENT;
> > +             goto err_cl_unlink;
> > +     }
> > +
> > +     ishtp_cl_set_fw_client_id(cros_ish_cl,
> > +                               ishtp_get_fw_client_id(fw_client));
> > +     ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_CONNECTING);
> > +
> > +     rv = ishtp_cl_connect(cros_ish_cl);
> > +     if (rv) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "client connect fail\n");
> > +             goto err_cl_unlink;
> > +     }
> > +
> > +     ishtp_register_event_cb(client_data->cl_device, ish_event_cb);
> > +     return 0;
> > +
> > +err_cl_unlink:
> > +     ishtp_cl_unlink(cros_ish_cl);
> > +     return rv;
> > +}
> > +
> > +/**
> > + * cros_ish_deinit() - Deinit function for ISHTP client
> > + * @cros_ish_cl: ISHTP client instance
> > + *
> > + * Unlink and free cros_ec client
> > + */
> > +static void cros_ish_deinit(struct ishtp_cl *cros_ish_cl)
> > +{
> > +     ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING);
> > +     ishtp_cl_disconnect(cros_ish_cl);
> > +     ishtp_cl_unlink(cros_ish_cl);
> > +     ishtp_cl_flush_queues(cros_ish_cl);
> > +
> > +     /* Disband and free all Tx and Rx client-level rings */
> > +     ishtp_cl_free(cros_ish_cl);
> > +}
> > +
> > +/**
> > + * prepare_cros_ec_rx() - Check & prepare receive buffer
> > + * @ec_dev: CrOS EC MFD device.
> > + * @in_msg: Incoming message buffer
> > + * @msg: cros_ec command used to send & receive data
> > + *
> > + * Return: 0 for success, negative error code for failure.
> > + *
> > + * Check the received buffer. Convert to cros_ec_command format.
> > + */
> > +static int prepare_cros_ec_rx(struct cros_ec_device *ec_dev,
> > +                           const struct cros_ish_in_msg *in_msg,
> > +                           struct cros_ec_command *msg)
> > +{
> > +     u8 sum = 0;
> > +     int i, rv, offset;
> > +
> > +     /* Check response error code */
> > +     msg->result = in_msg->ec_response.result;
> > +     rv = cros_ec_check_result(ec_dev, msg);
> > +     if (rv < 0)
> > +             return rv;
> > +
> > +     if (in_msg->ec_response.data_len > msg->insize) {
> > +             dev_err(ec_dev->dev, "Packet too long (%d bytes, expected %d)",
> > +                     in_msg->ec_response.data_len, msg->insize);
> > +             return -ENOSPC;
> > +     }
> > +
> > +     /* Copy response packet payload and compute checksum */
> > +     for (i = 0; i < sizeof(struct ec_host_response); i++)
> > +             sum += ((u8 *)in_msg)[IN_MSG_EC_RESPONSE_PREAMBLE + i];
> > +
> > +     offset = sizeof(struct cros_ish_in_msg);
> > +     for (i = 0; i < in_msg->ec_response.data_len; i++)
> > +             sum += msg->data[i] = ((u8 *)in_msg)[offset + i];
> > +
> > +     if (sum) {
> > +             dev_dbg(ec_dev->dev, "Bad received packet checksum %d\n", sum);
> > +             return -EBADMSG;
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > +static int cros_ec_pkt_xfer_ish(struct cros_ec_device *ec_dev,
> > +                             struct cros_ec_command *msg)
> > +{
> > +     int rv;
> > +     struct ishtp_cl *cros_ish_cl = ec_dev->priv;
> > +     struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> > +     struct device *dev = cl_data_to_dev(client_data);
> > +     struct cros_ish_in_msg *in_msg = (struct cros_ish_in_msg *)ec_dev->din;
> > +     struct cros_ish_out_msg *out_msg =
> > +             (struct cros_ish_out_msg *)ec_dev->dout;
> > +     size_t in_size = sizeof(struct cros_ish_in_msg) + msg->insize;
> > +     size_t out_size = sizeof(struct cros_ish_out_msg) + msg->outsize;
> > +
> > +     /* Proceed only if reset-init is not in progress */
> > +     if (!down_read_trylock(&init_lock)) {
> > +             dev_warn(dev,
> > +                      "Host is not ready to send messages to ISH. Try again\n");
> > +             return -EAGAIN;
> > +     }
> > +
> > +     /* Sanity checks */
> > +     if (in_size > ec_dev->din_size) {
> > +             dev_err(dev,
> > +                     "Incoming payload size %zu is too large for ec_dev->din_size %d\n",
> > +                     in_size, ec_dev->din_size);
> > +             return -EMSGSIZE;
> > +     }
> > +
> > +     if (out_size > ec_dev->dout_size) {
> > +             dev_err(dev,
> > +                     "Outgoing payload size %zu is too large for ec_dev->dout_size %d\n",
> > +                     out_size, ec_dev->dout_size);
> > +             return -EMSGSIZE;
> > +     }
> > +
> > +     /* Prepare the package to be sent over ISH TP */
> > +     out_msg->hdr.channel = CROS_EC_COMMAND;
> > +     out_msg->hdr.status = 0;
> > +
> > +     ec_dev->dout += OUT_MSG_EC_REQUEST_PREAMBLE;
> > +     cros_ec_prepare_tx(ec_dev, msg);
> > +     ec_dev->dout -= OUT_MSG_EC_REQUEST_PREAMBLE;
> > +
> > +     dev_dbg(dev,
> > +             "out_msg: struct_ver=0x%x checksum=0x%x command=0x%x command_ver=0x%x data_len=0x%x\n",
> > +             out_msg->ec_request.struct_version,
> > +             out_msg->ec_request.checksum,
> > +             out_msg->ec_request.command,
> > +             out_msg->ec_request.command_version,
> > +             out_msg->ec_request.data_len);
> > +
> > +     /* Send command to ISH EC firmware and read response */
> > +     rv = ish_send(client_data,
> > +                   (u8 *)out_msg, out_size,
> > +                   (u8 *)in_msg, in_size);
> > +     if (rv < 0)
> > +             goto end_error;
> > +
> > +     rv = prepare_cros_ec_rx(ec_dev, in_msg, msg);
> > +     if (rv)
> > +             goto end_error;
> > +
> > +     rv = in_msg->ec_response.data_len;
> > +
> > +     dev_dbg(dev,
> > +             "in_msg: struct_ver=0x%x checksum=0x%x result=0x%x data_len=0x%x\n",
> > +             in_msg->ec_response.struct_version,
> > +             in_msg->ec_response.checksum,
> > +             in_msg->ec_response.result,
> > +             in_msg->ec_response.data_len);
> > +
> > +end_error:
> > +     if (msg->command == EC_CMD_REBOOT_EC)
> > +             msleep(EC_REBOOT_DELAY_MS);
> > +
> > +     up_read(&init_lock);
> > +
> > +     return rv;
> > +}
> > +
> > +static int cros_ec_dev_init(struct ishtp_cl_data *client_data)
> > +{
> > +     struct cros_ec_device *ec_dev;
> > +     struct device *dev = cl_data_to_dev(client_data);
> > +
> > +     ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL);
> > +     if (!ec_dev)
> > +             return -ENOMEM;
> > +
> > +     client_data->ec_dev = ec_dev;
> > +     dev->driver_data = ec_dev;
> > +
> > +     ec_dev->dev = dev;
> > +     ec_dev->priv = client_data->cros_ish_cl;
> > +     ec_dev->cmd_xfer = NULL;
> > +     ec_dev->pkt_xfer = cros_ec_pkt_xfer_ish;
> > +     ec_dev->phys_name = dev_name(dev);
> > +     ec_dev->din_size = sizeof(struct cros_ish_in_msg) +
> > +                        sizeof(struct ec_response_get_protocol_info);
> > +     ec_dev->dout_size = sizeof(struct cros_ish_out_msg);
> > +
> > +     return cros_ec_register(ec_dev);
> > +}
> > +
> > +static void reset_handler(struct work_struct *work)
> > +{
> > +     int rv;
> > +     struct device *dev;
> > +     struct ishtp_cl *cros_ish_cl;
> > +     struct ishtp_cl_device *cl_device;
> > +     struct ishtp_cl_data *client_data =
> > +             container_of(work, struct ishtp_cl_data, work_ishtp_reset);
> > +
> > +     /* Lock for reset to complete */
> > +     down_write(&init_lock);
> > +
> > +     cros_ish_cl = client_data->cros_ish_cl;
> > +     cl_device = client_data->cl_device;
> > +
> > +     /* Unlink, flush queues & start again */
> > +     ishtp_cl_unlink(cros_ish_cl);
> > +     ishtp_cl_flush_queues(cros_ish_cl);
> > +     ishtp_cl_free(cros_ish_cl);
> > +
> > +     cros_ish_cl = ishtp_cl_allocate(cl_device);
> > +     if (!cros_ish_cl) {
> > +             up_write(&init_lock);
> > +             return;
> > +     }
> > +
> > +     ishtp_set_drvdata(cl_device, cros_ish_cl);
> > +     ishtp_set_client_data(cros_ish_cl, client_data);
> > +     client_data->cros_ish_cl = cros_ish_cl;
> > +
> > +     rv = cros_ish_init(cros_ish_cl);
> > +     if (rv) {
> > +             ishtp_cl_free(cros_ish_cl);
> > +             dev_err(cl_data_to_dev(client_data), "Reset Failed\n");
> > +             up_write(&init_lock);
> > +             return;
> > +     }
> > +
> > +     /* Refresh ec_dev device pointers */
> > +     client_data->ec_dev->priv = client_data->cros_ish_cl;
> > +     dev = cl_data_to_dev(client_data);
> > +     dev->driver_data = client_data->ec_dev;
> > +
> > +     dev_info(cl_data_to_dev(client_data), "Chrome EC ISH reset done\n");
> > +
> > +     up_write(&init_lock);
> > +}
> > +
> > +/**
> > + * cros_ec_ishtp_probe() - ISHTP client driver probe callback
> > + * @cl_device: ISHTP client device instance
> > + *
> > + * Return: 0 for success, negative error code for failure.
> > + */
> > +static int cros_ec_ishtp_probe(struct ishtp_cl_device *cl_device)
> > +{
> > +     int rv;
> > +     struct ishtp_cl *cros_ish_cl;
> > +     struct ishtp_cl_data *client_data =
> > +             devm_kzalloc(ishtp_device(cl_device),
> > +                          sizeof(*client_data), GFP_KERNEL);
> > +     if (!client_data)
> > +             return -ENOMEM;
> > +
> > +     /* Lock for initialization to complete */
> > +     down_write(&init_lock);
> > +
> > +     cros_ish_cl = ishtp_cl_allocate(cl_device);
> > +     if (!cros_ish_cl) {
> > +             rv = -ENOMEM;
> > +             goto end_ishtp_cl_alloc_error;
> > +     }
> > +
> > +     ishtp_set_drvdata(cl_device, cros_ish_cl);
> > +     ishtp_set_client_data(cros_ish_cl, client_data);
> > +     client_data->cros_ish_cl = cros_ish_cl;
> > +     client_data->cl_device = cl_device;
> > +
> > +     init_waitqueue_head(&client_data->response.wait_queue);
> > +
> > +     INIT_WORK(&client_data->work_ishtp_reset,
> > +               reset_handler);
> > +     INIT_WORK(&client_data->work_ec_evt,
> > +               ish_evt_handler);
> > +
> > +     rv = cros_ish_init(cros_ish_cl);
> > +     if (rv)
> > +             goto end_ishtp_cl_init_error;
> > +
> > +     ishtp_get_device(cl_device);
> > +
> > +     up_write(&init_lock);
> > +
> > +     /* Register croc_ec_dev mfd */
> > +     rv = cros_ec_dev_init(client_data);
> > +     if (rv)
> > +             goto end_cros_ec_dev_init_error;
> > +
> > +     return 0;
> > +
> > +end_cros_ec_dev_init_error:
> > +     ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING);
> > +     ishtp_cl_disconnect(cros_ish_cl);
> > +     ishtp_cl_unlink(cros_ish_cl);
> > +     ishtp_cl_flush_queues(cros_ish_cl);
> > +     ishtp_put_device(cl_device);
> > +end_ishtp_cl_init_error:
> > +     ishtp_cl_free(cros_ish_cl);
> > +end_ishtp_cl_alloc_error:
> > +     up_write(&init_lock);
> > +     return rv;
> > +}
> > +
> > +/**
> > + * cros_ec_ishtp_remove() - ISHTP client driver remove callback
> > + * @cl_device: ISHTP client device instance
> > + *
> > + * Return: 0
> > + */
> > +static int cros_ec_ishtp_remove(struct ishtp_cl_device *cl_device)
> > +{
> > +     struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
> > +     struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> > +
> > +     cancel_work_sync(&client_data->work_ishtp_reset);
> > +     cancel_work_sync(&client_data->work_ec_evt);
> > +     cros_ish_deinit(cros_ish_cl);
> > +     ishtp_put_device(cl_device);
> > +
> > +     return 0;
> > +}
> > +
> > +/**
> > + * cros_ec_ishtp_reset() - ISHTP client driver reset callback
> > + * @cl_device: ISHTP client device instance
> > + *
> > + * Return: 0
> > + */
> > +static int cros_ec_ishtp_reset(struct ishtp_cl_device *cl_device)
> > +{
> > +     struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
> > +     struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> > +
> > +     schedule_work(&client_data->work_ishtp_reset);
> > +
> > +     return 0;
> > +}
> > +
> > +/**
> > + * cros_ec_ishtp_suspend() - ISHTP client driver suspend callback
> > + * @device: device instance
> > + *
> > + * Return: 0 for success, negative error code for failure.
> > + */
> > +static int __maybe_unused cros_ec_ishtp_suspend(struct device *device)
> > +{
> > +     struct ishtp_cl_device *cl_device = dev_get_drvdata(device);
> > +     struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
> > +     struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> > +
> > +     return cros_ec_suspend(client_data->ec_dev);
> > +}
> > +
> > +/**
> > + * cros_ec_ishtp_resume() - ISHTP client driver resume callback
> > + * @device: device instance
> > + *
> > + * Return: 0 for success, negative error code for failure.
> > + */
> > +static int __maybe_unused cros_ec_ishtp_resume(struct device *device)
> > +{
> > +     struct ishtp_cl_device *cl_device = dev_get_drvdata(device);
> > +     struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
> > +     struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
> > +
> > +     return cros_ec_resume(client_data->ec_dev);
> > +}
> > +
> > +static SIMPLE_DEV_PM_OPS(cros_ec_ishtp_pm_ops, cros_ec_ishtp_suspend,
> > +                      cros_ec_ishtp_resume);
> > +
> > +static struct ishtp_cl_driver        cros_ec_ishtp_driver = {
> > +     .name = "cros_ec_ishtp",
> > +     .guid = &cros_ish_guid,
> > +     .probe = cros_ec_ishtp_probe,
> > +     .remove = cros_ec_ishtp_remove,
> > +     .reset = cros_ec_ishtp_reset,
> > +     .driver = {
> > +             .pm = &cros_ec_ishtp_pm_ops,
> > +     },
> > +};
> > +
> > +static int __init cros_ec_ishtp_mod_init(void)
> > +{
> > +     return ishtp_cl_driver_register(&cros_ec_ishtp_driver, THIS_MODULE);
> > +}
> > +
> > +static void __exit cros_ec_ishtp_mod_exit(void)
> > +{
> > +     ishtp_cl_driver_unregister(&cros_ec_ishtp_driver);
> > +}
> > +
> > +module_init(cros_ec_ishtp_mod_init);
> > +module_exit(cros_ec_ishtp_mod_exit);
> > +
> > +MODULE_DESCRIPTION("ChromeOS EC ISHTP Client Driver");
> > +MODULE_AUTHOR("Rushikesh S Kadam <rushikesh.s.kadam@intel.com>");
> > +
> > +MODULE_LICENSE("GPL v2");
> > +MODULE_ALIAS("ishtp:*");
> >

^ permalink raw reply

* Re: [PATCH V1] elan_i2c: Increment wakeup count if wake source.
From: Ravi Chandra Sadineni @ 2019-05-15 16:17 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: 廖崇榮, Benjamin Tissoires, Abhishek Bhardwaj,
	Todd Broch, lkml, linux-input@vger.kernel.org
In-Reply-To: <CAKdAkRQ_J6QWxtWpoRQnNWKcJpXox6xVDZWcWYOXkBhPSn99Rw@mail.gmail.com>

Hi Dmitry,

On Mon, May 13, 2019 at 4:29 PM Dmitry Torokhov
<dmitry.torokhov@gmail.com> wrote:
>
> Hi Ravi,
>
> On Mon, May 13, 2019 at 3:06 PM Ravi Chandra Sadineni
> <ravisadineni@chromium.org> wrote:
> >
> > Notify the PM core that this dev is the wake source. This helps
> > userspace daemon tracking the wake source to identify the origin of the
> > wake.
>
> I wonder if we could do that form the i2c core instead of individual drivers?
I am sorry, I don't see a way how this could be done.
>
> >
> > Signed-off-by: Ravi Chandra Sadineni <ravisadineni@chromium.org>
> > ---
> >  drivers/input/mouse/elan_i2c_core.c | 2 ++
> >  1 file changed, 2 insertions(+)
> >
> > diff --git a/drivers/input/mouse/elan_i2c_core.c b/drivers/input/mouse/elan_i2c_core.c
> > index f9525d6f0bfe..2c0561e20b7f 100644
> > --- a/drivers/input/mouse/elan_i2c_core.c
> > +++ b/drivers/input/mouse/elan_i2c_core.c
> > @@ -981,6 +981,8 @@ static irqreturn_t elan_isr(int irq, void *dev_id)
> >         if (error)
> >                 goto out;
> >
> > +       pm_wakeup_event(dev, 0);
> > +
> >         switch (report[ETP_REPORT_ID_OFFSET]) {
> >         case ETP_REPORT_ID:
> >                 elan_report_absolute(data, report);
> > --
> > 2.20.1
> >
>
> Thanks.
>
> --
> Dmitry

Thanks,
Ravi

^ permalink raw reply

* [PATCH v2 5/5] input: goodix - Call of_device_links_add() to create links
From: Benjamin Gaignard @ 2019-05-15 13:11 UTC (permalink / raw)
  To: rafael.j.wysocki, dmitry.torokhov, robh+dt, mark.rutland, hadess,
	frowand.list, m.felsch, agx, arnd
  Cc: linux-input, devicetree, linux-kernel, linux-stm32, broonie,
	Benjamin Gaignard
In-Reply-To: <20190515131154.18373-1-benjamin.gaignard@st.com>

Add a call to of_device_links_add() to create links with
suspend dependencies at probe time.

Signed-off-by: Benjamin Gaignard <benjamin.gaignard@st.com>
---
 drivers/input/touchscreen/goodix.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/input/touchscreen/goodix.c b/drivers/input/touchscreen/goodix.c
index f57d82220a88..49fd4763f17b 100644
--- a/drivers/input/touchscreen/goodix.c
+++ b/drivers/input/touchscreen/goodix.c
@@ -30,6 +30,7 @@
 #include <linux/slab.h>
 #include <linux/acpi.h>
 #include <linux/of.h>
+#include <linux/of_device.h>
 #include <asm/unaligned.h>
 
 struct goodix_ts_data;
@@ -812,6 +813,8 @@ static int goodix_ts_probe(struct i2c_client *client,
 
 	ts->chip = goodix_get_chip_data(ts->id);
 
+	of_device_links_add(&client->dev);
+
 	if (ts->gpiod_int && ts->gpiod_rst) {
 		/* update device config */
 		ts->cfg_name = devm_kasprintf(&client->dev, GFP_KERNEL,
-- 
2.15.0

^ permalink raw reply related

* [PATCH v2 4/5] Input: goodix: Document suspend-dependencies property
From: Benjamin Gaignard @ 2019-05-15 13:11 UTC (permalink / raw)
  To: rafael.j.wysocki, dmitry.torokhov, robh+dt, mark.rutland, hadess,
	frowand.list, m.felsch, agx, arnd
  Cc: linux-input, devicetree, linux-kernel, linux-stm32, broonie,
	Benjamin Gaignard
In-Reply-To: <20190515131154.18373-1-benjamin.gaignard@st.com>

Explain the purpose of suspend-dependencies property.

Signed-off-by: Benjamin Gaignard <benjamin.gaignard@st.com>
---
 Documentation/devicetree/bindings/input/touchscreen/goodix.txt | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/input/touchscreen/goodix.txt b/Documentation/devicetree/bindings/input/touchscreen/goodix.txt
index 8cf0b4d38a7e..5527952054d2 100644
--- a/Documentation/devicetree/bindings/input/touchscreen/goodix.txt
+++ b/Documentation/devicetree/bindings/input/touchscreen/goodix.txt
@@ -24,6 +24,8 @@ Optional properties:
  - touchscreen-size-x
  - touchscreen-size-y
  - touchscreen-swapped-x-y
+ - suspend-dependencies	: Phandle list of devices which have to be suspended
+			  after goodix device and resumed before it.
 
 The touchscreen-* properties are documented in touchscreen.txt in this
 directory.
-- 
2.15.0

^ permalink raw reply related

* [PATCH v2 3/5] input: edt-ft5x06 - Call of_device_links_add() to create links
From: Benjamin Gaignard @ 2019-05-15 13:11 UTC (permalink / raw)
  To: rafael.j.wysocki, dmitry.torokhov, robh+dt, mark.rutland, hadess,
	frowand.list, m.felsch, agx, arnd
  Cc: linux-input, devicetree, linux-kernel, linux-stm32, broonie,
	Benjamin Gaignard
In-Reply-To: <20190515131154.18373-1-benjamin.gaignard@st.com>

Add a call to of_device_links_add() to create links with suspend
dependencies at probe time.

Signed-off-by: Benjamin Gaignard <benjamin.gaignard@st.com>
---
 drivers/input/touchscreen/edt-ft5x06.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
index 702bfda7ee77..65053be10d4e 100644
--- a/drivers/input/touchscreen/edt-ft5x06.c
+++ b/drivers/input/touchscreen/edt-ft5x06.c
@@ -1167,6 +1167,8 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client,
 
 	i2c_set_clientdata(client, tsdata);
 
+	of_device_links_add(&client->dev);
+
 	irq_flags = irq_get_trigger_type(client->irq);
 	if (irq_flags == IRQF_TRIGGER_NONE)
 		irq_flags = IRQF_TRIGGER_FALLING;
-- 
2.15.0

^ permalink raw reply related


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