* [PATCH v2 1/2] uapi/input.h: Add INPUT_PROP_TOPBUTTONPAD device property
From: Hans de Goede @ 2014-04-14 10:37 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Benjamin Tissoires, Peter Hutterer, linux-input, Hans de Goede
In-Reply-To: <1397471875-31400-1-git-send-email-hdegoede@redhat.com>
On some newer laptops with a trackpoint the physical buttons for the
trackpoint have been removed to allow for a larger touchpad. On these
laptops the buttonpad has clearly marked areas on the top which are to be
used as trackpad buttons.
Users of the event device-node need to know about this, so that they can
properly interpret BTN_LEFT events as being a left / right / middle click
depending on where on the button pad the clicking finger is.
This commits adds a INPUT_PROP_TOPBUTTONPAD device property which drivers
for such buttonpads will use to signal to the user that this buttonpad not
only has the normal bottom button area, but also a top button area.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
include/uapi/linux/input.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h
index bd24470..f484952 100644
--- a/include/uapi/linux/input.h
+++ b/include/uapi/linux/input.h
@@ -164,6 +164,7 @@ struct input_keymap_entry {
#define INPUT_PROP_DIRECT 0x01 /* direct input devices */
#define INPUT_PROP_BUTTONPAD 0x02 /* has button(s) under pad */
#define INPUT_PROP_SEMI_MT 0x03 /* touch rectangle only */
+#define INPUT_PROP_TOPBUTTONPAD 0x04 /* softbuttons at top of pad */
#define INPUT_PROP_MAX 0x1f
#define INPUT_PROP_CNT (INPUT_PROP_MAX + 1)
--
1.9.0
^ permalink raw reply related
* [PATCH v2 0/2] Add INPUT_PROP_TOPBUTTONPAD device property
From: Hans de Goede @ 2014-04-14 10:37 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: Benjamin Tissoires, Peter Hutterer, linux-input
Hi All,
Here is v2 of my INPUT_PROP_TOPBUTTONPAD patch-set.
Changes in v2:
-Drop adding of pnp_id to struct serio (drop patches 2/4 and 3/4 of v1)
-Modify the synaptics patch to use firmware_id instead of pnp_id
Regards,
Hans
^ permalink raw reply
* [PATCH v3 2/2] input/serio/8042: Add firmware_id support
From: Hans de Goede @ 2014-04-14 10:34 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Benjamin Tissoires, Peter Hutterer, linux-input, Hans de Goede
In-Reply-To: <1397471419-31224-1-git-send-email-hdegoede@redhat.com>
Fill in the new serio firmware_id sysfs attribute for pnp instantiated
8042 serio ports.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Acked-by: Peter Hutterer <peter.hutterer@who-t.net>
---
drivers/input/serio/i8042-x86ia64io.h | 15 +++++++++++++++
drivers/input/serio/i8042.c | 6 ++++++
2 files changed, 21 insertions(+)
diff --git a/drivers/input/serio/i8042-x86ia64io.h b/drivers/input/serio/i8042-x86ia64io.h
index 0ec9abb..381b20d 100644
--- a/drivers/input/serio/i8042-x86ia64io.h
+++ b/drivers/input/serio/i8042-x86ia64io.h
@@ -702,6 +702,17 @@ static int i8042_pnp_aux_irq;
static char i8042_pnp_kbd_name[32];
static char i8042_pnp_aux_name[32];
+static void i8042_pnp_id_to_string(struct pnp_id *id, char *dst, int dst_size)
+{
+ strlcpy(dst, "PNP:", dst_size);
+
+ while (id) {
+ strlcat(dst, " ", dst_size);
+ strlcat(dst, id->id, dst_size);
+ id = id->next;
+ }
+}
+
static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id *did)
{
if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1)
@@ -718,6 +729,8 @@ static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id *
strlcat(i8042_pnp_kbd_name, ":", sizeof(i8042_pnp_kbd_name));
strlcat(i8042_pnp_kbd_name, pnp_dev_name(dev), sizeof(i8042_pnp_kbd_name));
}
+ i8042_pnp_id_to_string(dev->id, i8042_kbd_firmware_id,
+ sizeof(i8042_kbd_firmware_id));
/* Keyboard ports are always supposed to be wakeup-enabled */
device_set_wakeup_enable(&dev->dev, true);
@@ -742,6 +755,8 @@ static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id *
strlcat(i8042_pnp_aux_name, ":", sizeof(i8042_pnp_aux_name));
strlcat(i8042_pnp_aux_name, pnp_dev_name(dev), sizeof(i8042_pnp_aux_name));
}
+ i8042_pnp_id_to_string(dev->id, i8042_aux_firmware_id,
+ sizeof(i8042_aux_firmware_id));
i8042_pnp_aux_devices++;
return 0;
diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c
index 020053f..3807c3e 100644
--- a/drivers/input/serio/i8042.c
+++ b/drivers/input/serio/i8042.c
@@ -87,6 +87,8 @@ MODULE_PARM_DESC(debug, "Turn i8042 debugging mode on and off");
#endif
static bool i8042_bypass_aux_irq_test;
+static char i8042_kbd_firmware_id[128];
+static char i8042_aux_firmware_id[128];
#include "i8042.h"
@@ -1218,6 +1220,8 @@ static int __init i8042_create_kbd_port(void)
serio->dev.parent = &i8042_platform_device->dev;
strlcpy(serio->name, "i8042 KBD port", sizeof(serio->name));
strlcpy(serio->phys, I8042_KBD_PHYS_DESC, sizeof(serio->phys));
+ strlcpy(serio->firmware_id, i8042_kbd_firmware_id,
+ sizeof(serio->firmware_id));
port->serio = serio;
port->irq = I8042_KBD_IRQ;
@@ -1244,6 +1248,8 @@ static int __init i8042_create_aux_port(int idx)
if (idx < 0) {
strlcpy(serio->name, "i8042 AUX port", sizeof(serio->name));
strlcpy(serio->phys, I8042_AUX_PHYS_DESC, sizeof(serio->phys));
+ strlcpy(serio->firmware_id, i8042_aux_firmware_id,
+ sizeof(serio->firmware_id));
serio->close = i8042_port_close;
} else {
snprintf(serio->name, sizeof(serio->name), "i8042 AUX%d port", idx);
--
1.9.0
^ permalink raw reply related
* [PATCH v3 1/2] input/serio: Add a firmware_id sysfs attribute
From: Hans de Goede @ 2014-04-14 10:30 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Benjamin Tissoires, Peter Hutterer, linux-input, Hans de Goede
In-Reply-To: <1397471419-31224-1-git-send-email-hdegoede@redhat.com>
serio devices exposed via platform firmware interfaces such as ACPI
may provide additional identifying information of use to userspace.
We don't associate the serio devices with the firmware device (we don't
set it as parent), so there's no way for userspace to make use of this
information.
We cannot change the parent for serio devices instantiated though a firmware
interface as that would break suspend / resume ordering.
Therefor this patch adds a new firmware_id sysfs attribute so that userspace
can get a string from there with any additional identifying information the
firmware interface may provide.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Acked-by: Peter Hutterer <peter.hutterer@who-t.net>
---
drivers/input/serio/serio.c | 12 ++++++++++++
include/linux/serio.h | 1 +
2 files changed, 13 insertions(+)
diff --git a/drivers/input/serio/serio.c b/drivers/input/serio/serio.c
index 8f4c4ab..1788a4d 100644
--- a/drivers/input/serio/serio.c
+++ b/drivers/input/serio/serio.c
@@ -451,6 +451,13 @@ static ssize_t serio_set_bind_mode(struct device *dev, struct device_attribute *
return retval;
}
+static ssize_t firmware_id_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct serio *serio = to_serio_port(dev);
+
+ return sprintf(buf, "%s\n", serio->firmware_id);
+}
+
static DEVICE_ATTR_RO(type);
static DEVICE_ATTR_RO(proto);
static DEVICE_ATTR_RO(id);
@@ -473,12 +480,14 @@ static DEVICE_ATTR_RO(modalias);
static DEVICE_ATTR_WO(drvctl);
static DEVICE_ATTR(description, S_IRUGO, serio_show_description, NULL);
static DEVICE_ATTR(bind_mode, S_IWUSR | S_IRUGO, serio_show_bind_mode, serio_set_bind_mode);
+static DEVICE_ATTR_RO(firmware_id);
static struct attribute *serio_device_attrs[] = {
&dev_attr_modalias.attr,
&dev_attr_description.attr,
&dev_attr_drvctl.attr,
&dev_attr_bind_mode.attr,
+ &dev_attr_firmware_id.attr,
NULL
};
@@ -923,6 +932,9 @@ static int serio_uevent(struct device *dev, struct kobj_uevent_env *env)
SERIO_ADD_UEVENT_VAR("SERIO_EXTRA=%02x", serio->id.extra);
SERIO_ADD_UEVENT_VAR("MODALIAS=serio:ty%02Xpr%02Xid%02Xex%02X",
serio->id.type, serio->id.proto, serio->id.id, serio->id.extra);
+ if (serio->firmware_id[0])
+ SERIO_ADD_UEVENT_VAR("SERIO_FIRMWARE_ID=%s",
+ serio->firmware_id);
return 0;
}
diff --git a/include/linux/serio.h b/include/linux/serio.h
index 36aac73..9f779c7 100644
--- a/include/linux/serio.h
+++ b/include/linux/serio.h
@@ -23,6 +23,7 @@ struct serio {
char name[32];
char phys[32];
+ char firmware_id[128];
bool manual_bind;
--
1.9.0
^ permalink raw reply related
* Re: Re: [PATCH v4 7/9] ARM: sun7i/sun4i: dt: Add AXP209 support to various boards
From: Hans de Goede @ 2014-04-14 10:20 UTC (permalink / raw)
To: linux-sunxi-/JYPxA39Uh5TLH3MbocFFw, Maxime Ripard
Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
emilio-0Z03zUJReD5OxF6Tv1QG9Q, wens-jdAy2FN1RRM,
sameo-VuQAYsv1563Yd54FQh9/CA,
dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
linux-input-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA,
lgirdwood-Re5JQEeQqe8AvxtiuMwx3w, broonie-DgEjT+Ai2ygdnm+yROfE0A,
lee.jones-QSEj5FYQhm4dnm+yROfE0A,
boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8
In-Reply-To: <CAOQ7t2ZybF=dm-NBQ3niN7gb-Asgwz5CW-VSkOzWR6t2NCaA8w-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
Hi,
On 04/14/2014 12:02 PM, Carlo Caione wrote:
> On Mon, Apr 14, 2014 at 11:52 AM, Maxime Ripard
> <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
>> Hi Carlo,
>
> Hi Maxime,
>
>> On Fri, Apr 11, 2014 at 11:38:11AM +0200, Carlo Caione wrote:
>>> Signed-off-by: Hans de Goede <hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
>>> Signed-off-by: Carlo Caione <carlo-KA+7E9HrN00dnm+yROfE0A@public.gmane.org>
>>> ---
>>>
>>> In all the DTs the min and max microvolt allowed for each regulator are actually
>>> the min and max voltage possible for the regulator itself. This is not safe but
>>> we do not have the ranges allowed for each board and the original Allwinner
>>> driver does exactly this way.
>>>
>>> AXP20x has the so called Power Path Management (IPS) that can select the proper
>>> power supply according to the status of the external power and the Li-battery
>>> status. The output of the IPS block is usually a 5V fixed voltage used as
>>> input supply for all the other regulators. This fixed voltage is represented
>>> in the DT as a fixed voltage regulator in the "regulator" subnode.
>>>
>>> arch/arm/boot/dts/sun4i-a10-a1000.dts | 69 +++++++++++++++++++++++
>>> arch/arm/boot/dts/sun4i-a10-cubieboard.dts | 69 +++++++++++++++++++++++
>>> arch/arm/boot/dts/sun4i-a10-hackberry.dts | 75 +++++++++++++++++++++++++
>>> arch/arm/boot/dts/sun4i-a10-inet97fv2.dts | 69 +++++++++++++++++++++++
>>> arch/arm/boot/dts/sun4i-a10-mini-xplus.dts | 75 +++++++++++++++++++++++++
>>> arch/arm/boot/dts/sun4i-a10-olinuxino-lime.dts | 75 +++++++++++++++++++++++++
>>> arch/arm/boot/dts/sun4i-a10-pcduino.dts | 69 +++++++++++++++++++++++
>>> arch/arm/boot/dts/sun7i-a20-cubieboard2.dts | 70 +++++++++++++++++++++++
>>> arch/arm/boot/dts/sun7i-a20-cubietruck.dts | 70 +++++++++++++++++++++++
>>> arch/arm/boot/dts/sun7i-a20-olinuxino-micro.dts | 70 +++++++++++++++++++++++
>>
>> That looks like a lot of them. Did you test all of them?
>> Are all those regulators you define used on all these boards?
>
> I tested it only on cubieboard2, all the other boards are contributed by Hans.
> I'll double check with Hans.
Well my contribution stems from the time when we still had a dtsi for the regulators,
if were going to do them per board, then we should be more precise IMHO.
As Mark has also mentioned we should probably pin the regulators to a certain
voltage, except for those which we expect to be controlled by a driver, so basically
all of them should be pinned to a certain voltage except for DCDC2 which gets used
for the cpu voltage which we will want to scale as soon as we've a cpufreq driver.
While testing the latest revision of your code I also noticed that the kernel ends
up disabling LDO3 and LDO4, which could be fine on some boards and a problem on
other boards.
I think we need to be careful here. For now it may be best to only add the DCDC2 regulator
to the dts, as we know that dcdc2 is used for the cpu voltage everywhere, and we will
actually want to control that later on.
For the others, for the boards where we've schematics (*) it would be good to add the other
regulators with fixed voltages as specified in the schematics. For the rest it may be
best to simply leave the regulators alone / at their default settings.
>
>>> 10 files changed, 711 insertions(+)
>>>
>>> diff --git a/arch/arm/boot/dts/sun4i-a10-a1000.dts b/arch/arm/boot/dts/sun4i-a10-a1000.dts
>>> index fa746aea..029a880 100644
>>> --- a/arch/arm/boot/dts/sun4i-a10-a1000.dts
>>> +++ b/arch/arm/boot/dts/sun4i-a10-a1000.dts
>>> @@ -88,6 +88,75 @@
>>> pinctrl-names = "default";
>>> pinctrl-0 = <&i2c0_pins_a>;
>>> status = "okay";
>>> + #address-cells = <1>;
>>> + #size-cells = <0>;
>>
>> That should be in the DTSI.
>
> Agree.
Note I've just send a patch-series for that and Maxime has added that series to his
sunxi/dt-for-3.16 branch.
<snip>
Regards,
Hans
^ permalink raw reply
* Re: Re: [PATCH v4 7/9] ARM: sun7i/sun4i: dt: Add AXP209 support to various boards
From: Carlo Caione @ 2014-04-14 10:02 UTC (permalink / raw)
To: Maxime Ripard
Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
hdegoede-H+wXaHxf7aLQT0dZR+AlfA, emilio-0Z03zUJReD5OxF6Tv1QG9Q,
wens-jdAy2FN1RRM, sameo-VuQAYsv1563Yd54FQh9/CA,
dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
linux-input-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA,
lgirdwood-Re5JQEeQqe8AvxtiuMwx3w, broonie-DgEjT+Ai2ygdnm+yROfE0A,
lee.jones-QSEj5FYQhm4dnm+yROfE0A,
boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
In-Reply-To: <20140414095256.GA28585@lukather>
On Mon, Apr 14, 2014 at 11:52 AM, Maxime Ripard
<maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
> Hi Carlo,
Hi Maxime,
> On Fri, Apr 11, 2014 at 11:38:11AM +0200, Carlo Caione wrote:
>> Signed-off-by: Hans de Goede <hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
>> Signed-off-by: Carlo Caione <carlo-KA+7E9HrN00dnm+yROfE0A@public.gmane.org>
>> ---
>>
>> In all the DTs the min and max microvolt allowed for each regulator are actually
>> the min and max voltage possible for the regulator itself. This is not safe but
>> we do not have the ranges allowed for each board and the original Allwinner
>> driver does exactly this way.
>>
>> AXP20x has the so called Power Path Management (IPS) that can select the proper
>> power supply according to the status of the external power and the Li-battery
>> status. The output of the IPS block is usually a 5V fixed voltage used as
>> input supply for all the other regulators. This fixed voltage is represented
>> in the DT as a fixed voltage regulator in the "regulator" subnode.
>>
>> arch/arm/boot/dts/sun4i-a10-a1000.dts | 69 +++++++++++++++++++++++
>> arch/arm/boot/dts/sun4i-a10-cubieboard.dts | 69 +++++++++++++++++++++++
>> arch/arm/boot/dts/sun4i-a10-hackberry.dts | 75 +++++++++++++++++++++++++
>> arch/arm/boot/dts/sun4i-a10-inet97fv2.dts | 69 +++++++++++++++++++++++
>> arch/arm/boot/dts/sun4i-a10-mini-xplus.dts | 75 +++++++++++++++++++++++++
>> arch/arm/boot/dts/sun4i-a10-olinuxino-lime.dts | 75 +++++++++++++++++++++++++
>> arch/arm/boot/dts/sun4i-a10-pcduino.dts | 69 +++++++++++++++++++++++
>> arch/arm/boot/dts/sun7i-a20-cubieboard2.dts | 70 +++++++++++++++++++++++
>> arch/arm/boot/dts/sun7i-a20-cubietruck.dts | 70 +++++++++++++++++++++++
>> arch/arm/boot/dts/sun7i-a20-olinuxino-micro.dts | 70 +++++++++++++++++++++++
>
> That looks like a lot of them. Did you test all of them?
> Are all those regulators you define used on all these boards?
I tested it only on cubieboard2, all the other boards are contributed by Hans.
I'll double check with Hans.
>> 10 files changed, 711 insertions(+)
>>
>> diff --git a/arch/arm/boot/dts/sun4i-a10-a1000.dts b/arch/arm/boot/dts/sun4i-a10-a1000.dts
>> index fa746aea..029a880 100644
>> --- a/arch/arm/boot/dts/sun4i-a10-a1000.dts
>> +++ b/arch/arm/boot/dts/sun4i-a10-a1000.dts
>> @@ -88,6 +88,75 @@
>> pinctrl-names = "default";
>> pinctrl-0 = <&i2c0_pins_a>;
>> status = "okay";
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>
> That should be in the DTSI.
Agree.
>> + axp209: pmic@34 {
>> + compatible = "x-powers,axp209";
>> + reg = <0x34>;
>> + interrupts = <0>;
>> +
>> + interrupt-controller;
>> + #interrupt-cells = <1>;
>> +
>> + acin-supply = <&axp_ipsout_reg>;
>> + vin2-supply = <&axp_ipsout_reg>;
>> + vin3-supply = <&axp_ipsout_reg>;
>> + ldo24in-supply = <&axp_ipsout_reg>;
>> + ldo3in-supply = <&axp_ipsout_reg>;
>> + ldo5in-supply = <&axp_ipsout_reg>;
>> +
>> + regulators {
>> + compatible = "x-powers,axp20x-reg";
>
> I told you a few times already, but don't introduce pattern-matching
> compatibles.
Probably that compatible will be deleted in the next version using
regulator_bulk_register_supply_alias() as suggested by Mark.
>> +
>> + x-powers,dcdc-freq = <1500>;
>> +
>> + axp_ipsout_reg: axp_ipsout {
>> + compatible = "regulator-fixed";
>> + regulator-name = "axp-ipsout";
>> + regulator-min-microvolt = <5000000>;
>> + regulator-max-microvolt = <5000000>;
>> + regulator-always-on;
>> + };
>> +
>> + axp_vcore_reg: dcdc2 {
>> + regulator-min-microvolt = <700000>;
>> + regulator-max-microvolt = <2275000>;
>> + regulator-always-on;
>> + };
>> +
>> + axp_ddr_reg: dcdc3 {
>> + regulator-min-microvolt = <700000>;
>> + regulator-max-microvolt = <3500000>;
>> + regulator-always-on;
>> + };
>> +
>> + axp_rtc_reg: ldo1 {
>> + regulator-always-on;
>> + };
>> +
>> + axp_analog_reg: ldo2 {
>> + regulator-min-microvolt = <1800000>;
>> + regulator-max-microvolt = <3300000>;
>> + regulator-always-on;
>> + };
>> +
>> + axp_pll_reg: ldo3 {
>> + regulator-min-microvolt = <700000>;
>> + regulator-max-microvolt = <3500000>;
>> + };
>> +
>> + axp_hdmi_reg: ldo4 {
>> + regulator-min-microvolt = <1250000>;
>> + regulator-max-microvolt = <3300000>;
>> + };
>> +
>> + axp_mic_reg: ldo5 {
>> + regulator-min-microvolt = <1800000>;
>> + regulator-max-microvolt = <3300000>;
>> + };
>> + };
>> + };
>> };
>> };
>>
>> diff --git a/arch/arm/boot/dts/sun4i-a10-cubieboard.dts b/arch/arm/boot/dts/sun4i-a10-cubieboard.dts
>> index 4684cbe..635fd4b 100644
>> --- a/arch/arm/boot/dts/sun4i-a10-cubieboard.dts
>> +++ b/arch/arm/boot/dts/sun4i-a10-cubieboard.dts
>> @@ -80,6 +80,75 @@
>> pinctrl-names = "default";
>> pinctrl-0 = <&i2c0_pins_a>;
>> status = "okay";
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> +
>> + axp209: pmic@34 {
>> + compatible = "x-powers,axp209";
>> + reg = <0x34>;
>> + interrupts = <0>;
>> +
>> + interrupt-controller;
>> + #interrupt-cells = <1>;
>> +
>> + acin-supply = <&axp_ipsout_reg>;
>> + vin2-supply = <&axp_ipsout_reg>;
>> + vin3-supply = <&axp_ipsout_reg>;
>> + ldo24in-supply = <&axp_ipsout_reg>;
>> + ldo3in-supply = <&axp_ipsout_reg>;
>> + ldo5in-supply = <&axp_ipsout_reg>;
>> +
>> + regulators {
>> + compatible = "x-powers,axp20x-reg";
>> +
>> + x-powers,dcdc-freq = <1500>;
>> +
>> + axp_ipsout_reg: axp_ipsout {
>> + compatible = "regulator-fixed";
>> + regulator-name = "axp-ipsout";
>> + regulator-min-microvolt = <5000000>;
>> + regulator-max-microvolt = <5000000>;
>> + regulator-always-on;
>> + };
>> +
>> + axp_vcore_reg: dcdc2 {
>> + regulator-min-microvolt = <700000>;
>> + regulator-max-microvolt = <2275000>;
>> + regulator-always-on;
>> + };
>> +
>> + axp_ddr_reg: dcdc3 {
>> + regulator-min-microvolt = <700000>;
>> + regulator-max-microvolt = <3500000>;
>> + regulator-always-on;
>> + };
>> +
>> + axp_rtc_reg: ldo1 {
>> + regulator-always-on;
>> + };
>> +
>> + axp_analog_reg: ldo2 {
>> + regulator-min-microvolt = <1800000>;
>> + regulator-max-microvolt = <3300000>;
>> + regulator-always-on;
>> + };
>> +
>> + axp_pll_reg: ldo3 {
>> + regulator-min-microvolt = <700000>;
>> + regulator-max-microvolt = <3500000>;
>> + };
>> +
>> + axp_hdmi_reg: ldo4 {
>> + regulator-min-microvolt = <1250000>;
>> + regulator-max-microvolt = <3300000>;
>> + };
>> +
>> + axp_mic_reg: ldo5 {
>> + regulator-min-microvolt = <1800000>;
>> + regulator-max-microvolt = <3300000>;
>> + };
>> + };
>> + };
>> };
>>
>> i2c1: i2c@01c2b000 {
>> diff --git a/arch/arm/boot/dts/sun4i-a10-hackberry.dts b/arch/arm/boot/dts/sun4i-a10-hackberry.dts
>> index d7c17e4..8f2db9c 100644
>> --- a/arch/arm/boot/dts/sun4i-a10-hackberry.dts
>> +++ b/arch/arm/boot/dts/sun4i-a10-hackberry.dts
>> @@ -82,6 +82,81 @@
>> pinctrl-0 = <&uart0_pins_a>;
>> status = "okay";
>> };
>> +
>> + i2c0: i2c@01c2ac00 {
>> + pinctrl-names = "default";
>> + pinctrl-0 = <&i2c0_pins_a>;
>> + status = "okay";
>
> That should be in a separate patch.
>
> These comments apply to most of the changes here. Make sure you edit
> all of them.
Ok. Thanks.
--
Carlo Caione
^ permalink raw reply
* Re: HID vendor access from user space
From: Nestor Lopez Casado @ 2014-04-14 10:00 UTC (permalink / raw)
To: David Herrmann
Cc: Jiri Kosina, Dmitry Torokhov, open list:HID CORE LAYER,
Olivier Gay, Benjamin Tissoires, Mathieu Meisser
In-Reply-To: <CAE7qMrq5if+OovWSq3q6JQMe46VAN9eBZZEDyvcrrQS27e_7eQ@mail.gmail.com>
David, Jiri,
After considering our discussion here, which is your position on
extending the hidraw nodes to include separate nodes for top level
vendor collections ?
Cheers,
-nestor
On Tue, Apr 8, 2014 at 5:53 PM, Nestor Lopez Casado
<nlopezcasad@logitech.com> wrote:
> On Mon, Apr 7, 2014 at 6:27 PM, David Herrmann <dh.herrmann@gmail.com> wrote:
>> Hi
>>
>> On Mon, Apr 7, 2014 at 6:15 PM, Nestor Lopez Casado
>> <nlopezcasad@logitech.com> wrote:
>>> We consider this a potential security problem and we are looking into
>>> a solution that would enable the currently logged local user to access
>>> the vendor collections of a hid device without requiring any special
>>> permissions.
>>>
>>> Maybe the solution is not within the kernel itself, but rather on
>>> using a user mode component, maybe the X server or even udev. Do you
>>> get my point now ?
>>
>> There is a lot of work going on to make Xorg (and also Wayland
>> compositors) run as non-root. Many people, however, seem to ignore
>> that this means the compositor runs with the _same_ privileges as the
>> applications. At least that is the security model we are going for.
>> Therefore, if a compositor can access input devices, all applications
>> can do so, too. Of course, you can introduce new privilege-levels and
>> or run applications with less privileges than normal user processes.
>> But I guess you get the point.
>>
>> Therefore, I really doubt there is much need to split the
>> access-rights here. What you wanna do is to provide this
>> privilege-separation on the kernel level. However, I think this is the
>> wrong way to do it. Imagine an HID device that has several
>> vendor-commands, some of them rather trivial and un-privileged, others
>> not. Do you expect the kernel to provide one device-node for each? How
>> do you expect the kernel to know which vender-extensions are _safe_ to
>> be grouped together?
>
> I do not expect the kernel to know whether vendor extensions are safe
> to be grouped together, I would simply provide them as a separate
> -user accessible- interface whose access rights can be assigned
> separately from the classic input collections.
>
> Assignment of access rights would then be up to udev rules. Which each
> manufacturer/user can define for each of its devices. By default
> access rights would stay for root only.
>
>>
>> Yes, the kernel should provide different interfaces for different
>> hw-features so user-space can apply fine-grained access-modes.
>> However, if we don't know what hardware feature a specific command
>> represents, I don't think we should apply some kind of random
>> interface separation. This is why Jiri and I recommend writing an
>> in-kernel driver. That driver can be small and all it does it provide
>> a separate interface for your vendor-extensions. This can be as easy
>> as setting HID_QUIRK_MULTI_INPUT for given devices (or an equivalent
>> HID_QUIRK_MULTI_HIDRAW, which doesn't exist, yet). I still think it
>> would be nicer if the kernel provides a proper interface-abstraction,
>> but I'd be fine with such a quirk, too.
>
> The problem I see with a specific kernel driver for a specific vendor
> extension is two fold:
> 1) It creates some potential for code bloat/duplication in the kernel
> across multiple drivers for different manufacturers, while the only
> purpose of this driver would be to expose some raw vendor
> functionality to user programs.
>
> 2) The interface provided by this driver to user programs would
> probably diverge from driver to driver, making it impossible to have a
> generic user component (think signal11's HidApi lib for instance) to
> provide a consistent hid access library across operating systems.
>
>>
>> Maybe you can describe one specific example? Otherwise, it's hard to
>> imagine hid devices that provide two totally separate interfaces that
>> we had the need to split them. All examples I know already provide
>> different virtual HID devices and thus don't suffer from this problem.
>
> You can look at the hid device descriptor that I posted in a previous
> message of this thread. This device descriptor actually comes from one
> of our devices: The Logitech BT Illuminated kbd K810. Most of our
> input devices follow this pattern:
>
> - Top level "classic" collection types (mouse, keyboard, etc)
> - Top level vendor collections.
>
> The vendor collections are the method we use to access extended
> functionality of the device, (button reprogrammability, dpi settings,
> extended battery status, firmware update and many others)
>
>> Furthermore, did you do some research how other platforms deal with
>> it?
>
> Neither Microsoft Windows nor Apple Mac OS provide a posix-style
> filesystem based device access model for HID devices. Nevertheless,
> Windows does provide "physical device objects" for each top level
> collection found in a hid device. From msdn:
>
> "A report descriptor can include more than one top-level collection.
> The HID class driver enumerates the top-level collections of an input
> device and creates a physical device object (PDO) for each top-level
> collection. User-mode applications or kernel-mode drivers can access a
> top-level collection by opening its PDO and using the HIDClass support
> routines and the HID class driver IOCTLs"
>
> Access rights to vendor collection PDOs is granted to any logged on user.
>
> Apple's Mac OS HidManager client API, provides a mechanism to select
> the parent device of any top level hid collection, then Set/Get
> report-style APIs to address each of the vendor collections.
>
> Access rights seem to be given to any logged on user.
>
>>
>> Thanks
>> David
^ permalink raw reply
* Re: [PATCH v4 7/9] ARM: sun7i/sun4i: dt: Add AXP209 support to various boards
From: Maxime Ripard @ 2014-04-14 9:52 UTC (permalink / raw)
To: Carlo Caione
Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw,
hdegoede-H+wXaHxf7aLQT0dZR+AlfA, emilio-0Z03zUJReD5OxF6Tv1QG9Q,
wens-jdAy2FN1RRM, sameo-VuQAYsv1563Yd54FQh9/CA,
dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
linux-input-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA,
lgirdwood-Re5JQEeQqe8AvxtiuMwx3w, broonie-DgEjT+Ai2ygdnm+yROfE0A,
lee.jones-QSEj5FYQhm4dnm+yROfE0A,
boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8
In-Reply-To: <1397209093-10077-8-git-send-email-carlo-KA+7E9HrN00dnm+yROfE0A@public.gmane.org>
[-- Attachment #1: Type: text/plain, Size: 7318 bytes --]
Hi Carlo,
On Fri, Apr 11, 2014 at 11:38:11AM +0200, Carlo Caione wrote:
> Signed-off-by: Hans de Goede <hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> Signed-off-by: Carlo Caione <carlo-KA+7E9HrN00dnm+yROfE0A@public.gmane.org>
> ---
>
> In all the DTs the min and max microvolt allowed for each regulator are actually
> the min and max voltage possible for the regulator itself. This is not safe but
> we do not have the ranges allowed for each board and the original Allwinner
> driver does exactly this way.
>
> AXP20x has the so called Power Path Management (IPS) that can select the proper
> power supply according to the status of the external power and the Li-battery
> status. The output of the IPS block is usually a 5V fixed voltage used as
> input supply for all the other regulators. This fixed voltage is represented
> in the DT as a fixed voltage regulator in the "regulator" subnode.
>
> arch/arm/boot/dts/sun4i-a10-a1000.dts | 69 +++++++++++++++++++++++
> arch/arm/boot/dts/sun4i-a10-cubieboard.dts | 69 +++++++++++++++++++++++
> arch/arm/boot/dts/sun4i-a10-hackberry.dts | 75 +++++++++++++++++++++++++
> arch/arm/boot/dts/sun4i-a10-inet97fv2.dts | 69 +++++++++++++++++++++++
> arch/arm/boot/dts/sun4i-a10-mini-xplus.dts | 75 +++++++++++++++++++++++++
> arch/arm/boot/dts/sun4i-a10-olinuxino-lime.dts | 75 +++++++++++++++++++++++++
> arch/arm/boot/dts/sun4i-a10-pcduino.dts | 69 +++++++++++++++++++++++
> arch/arm/boot/dts/sun7i-a20-cubieboard2.dts | 70 +++++++++++++++++++++++
> arch/arm/boot/dts/sun7i-a20-cubietruck.dts | 70 +++++++++++++++++++++++
> arch/arm/boot/dts/sun7i-a20-olinuxino-micro.dts | 70 +++++++++++++++++++++++
That looks like a lot of them. Did you test all of them?
Are all those regulators you define used on all these boards?
> 10 files changed, 711 insertions(+)
>
> diff --git a/arch/arm/boot/dts/sun4i-a10-a1000.dts b/arch/arm/boot/dts/sun4i-a10-a1000.dts
> index fa746aea..029a880 100644
> --- a/arch/arm/boot/dts/sun4i-a10-a1000.dts
> +++ b/arch/arm/boot/dts/sun4i-a10-a1000.dts
> @@ -88,6 +88,75 @@
> pinctrl-names = "default";
> pinctrl-0 = <&i2c0_pins_a>;
> status = "okay";
> + #address-cells = <1>;
> + #size-cells = <0>;
That should be in the DTSI.
> + axp209: pmic@34 {
> + compatible = "x-powers,axp209";
> + reg = <0x34>;
> + interrupts = <0>;
> +
> + interrupt-controller;
> + #interrupt-cells = <1>;
> +
> + acin-supply = <&axp_ipsout_reg>;
> + vin2-supply = <&axp_ipsout_reg>;
> + vin3-supply = <&axp_ipsout_reg>;
> + ldo24in-supply = <&axp_ipsout_reg>;
> + ldo3in-supply = <&axp_ipsout_reg>;
> + ldo5in-supply = <&axp_ipsout_reg>;
> +
> + regulators {
> + compatible = "x-powers,axp20x-reg";
I told you a few times already, but don't introduce pattern-matching
compatibles.
> +
> + x-powers,dcdc-freq = <1500>;
> +
> + axp_ipsout_reg: axp_ipsout {
> + compatible = "regulator-fixed";
> + regulator-name = "axp-ipsout";
> + regulator-min-microvolt = <5000000>;
> + regulator-max-microvolt = <5000000>;
> + regulator-always-on;
> + };
> +
> + axp_vcore_reg: dcdc2 {
> + regulator-min-microvolt = <700000>;
> + regulator-max-microvolt = <2275000>;
> + regulator-always-on;
> + };
> +
> + axp_ddr_reg: dcdc3 {
> + regulator-min-microvolt = <700000>;
> + regulator-max-microvolt = <3500000>;
> + regulator-always-on;
> + };
> +
> + axp_rtc_reg: ldo1 {
> + regulator-always-on;
> + };
> +
> + axp_analog_reg: ldo2 {
> + regulator-min-microvolt = <1800000>;
> + regulator-max-microvolt = <3300000>;
> + regulator-always-on;
> + };
> +
> + axp_pll_reg: ldo3 {
> + regulator-min-microvolt = <700000>;
> + regulator-max-microvolt = <3500000>;
> + };
> +
> + axp_hdmi_reg: ldo4 {
> + regulator-min-microvolt = <1250000>;
> + regulator-max-microvolt = <3300000>;
> + };
> +
> + axp_mic_reg: ldo5 {
> + regulator-min-microvolt = <1800000>;
> + regulator-max-microvolt = <3300000>;
> + };
> + };
> + };
> };
> };
>
> diff --git a/arch/arm/boot/dts/sun4i-a10-cubieboard.dts b/arch/arm/boot/dts/sun4i-a10-cubieboard.dts
> index 4684cbe..635fd4b 100644
> --- a/arch/arm/boot/dts/sun4i-a10-cubieboard.dts
> +++ b/arch/arm/boot/dts/sun4i-a10-cubieboard.dts
> @@ -80,6 +80,75 @@
> pinctrl-names = "default";
> pinctrl-0 = <&i2c0_pins_a>;
> status = "okay";
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + axp209: pmic@34 {
> + compatible = "x-powers,axp209";
> + reg = <0x34>;
> + interrupts = <0>;
> +
> + interrupt-controller;
> + #interrupt-cells = <1>;
> +
> + acin-supply = <&axp_ipsout_reg>;
> + vin2-supply = <&axp_ipsout_reg>;
> + vin3-supply = <&axp_ipsout_reg>;
> + ldo24in-supply = <&axp_ipsout_reg>;
> + ldo3in-supply = <&axp_ipsout_reg>;
> + ldo5in-supply = <&axp_ipsout_reg>;
> +
> + regulators {
> + compatible = "x-powers,axp20x-reg";
> +
> + x-powers,dcdc-freq = <1500>;
> +
> + axp_ipsout_reg: axp_ipsout {
> + compatible = "regulator-fixed";
> + regulator-name = "axp-ipsout";
> + regulator-min-microvolt = <5000000>;
> + regulator-max-microvolt = <5000000>;
> + regulator-always-on;
> + };
> +
> + axp_vcore_reg: dcdc2 {
> + regulator-min-microvolt = <700000>;
> + regulator-max-microvolt = <2275000>;
> + regulator-always-on;
> + };
> +
> + axp_ddr_reg: dcdc3 {
> + regulator-min-microvolt = <700000>;
> + regulator-max-microvolt = <3500000>;
> + regulator-always-on;
> + };
> +
> + axp_rtc_reg: ldo1 {
> + regulator-always-on;
> + };
> +
> + axp_analog_reg: ldo2 {
> + regulator-min-microvolt = <1800000>;
> + regulator-max-microvolt = <3300000>;
> + regulator-always-on;
> + };
> +
> + axp_pll_reg: ldo3 {
> + regulator-min-microvolt = <700000>;
> + regulator-max-microvolt = <3500000>;
> + };
> +
> + axp_hdmi_reg: ldo4 {
> + regulator-min-microvolt = <1250000>;
> + regulator-max-microvolt = <3300000>;
> + };
> +
> + axp_mic_reg: ldo5 {
> + regulator-min-microvolt = <1800000>;
> + regulator-max-microvolt = <3300000>;
> + };
> + };
> + };
> };
>
> i2c1: i2c@01c2b000 {
> diff --git a/arch/arm/boot/dts/sun4i-a10-hackberry.dts b/arch/arm/boot/dts/sun4i-a10-hackberry.dts
> index d7c17e4..8f2db9c 100644
> --- a/arch/arm/boot/dts/sun4i-a10-hackberry.dts
> +++ b/arch/arm/boot/dts/sun4i-a10-hackberry.dts
> @@ -82,6 +82,81 @@
> pinctrl-0 = <&uart0_pins_a>;
> status = "okay";
> };
> +
> + i2c0: i2c@01c2ac00 {
> + pinctrl-names = "default";
> + pinctrl-0 = <&i2c0_pins_a>;
> + status = "okay";
That should be in a separate patch.
These comments apply to most of the changes here. Make sure you edit
all of them.
Thanks!
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply
* [PATCH 5/6] input: cyapa: add sysfs interfaces supported for gen3 trackpad device
From: Dudley Du @ 2014-04-14 7:54 UTC (permalink / raw)
To: Dmitry Torokhov (dmitry.torokhov@gmail.com)
Cc: Benson Leung, Daniel Kurtz, linux-input@vger.kernel.org,
linux-kernel@vger.kernel.org
[-- Attachment #1: Type: text/plain, Size: 38278 bytes --]
Add sysfs interfaces for gen3 trackpad devices that required in production,
including read and update firmware image, report baselines, sensors calibrate,
read product id, read firmware version.
TEST=test on Chomebooks.
Signed-off-by: Du, Dudley <dudl@cypress.com>
---
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index da03427..917eabe 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -15,6 +15,7 @@
*/
#include <linux/async.h>
+#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
@@ -29,6 +30,7 @@
#include <linux/unaligned/access_ok.h>
#include <linux/pm_runtime.h>
+
/* APA trackpad firmware generation */
#define CYAPA_GEN_UNKNOWN 0x00 /* unknown protocol. */
#define CYAPA_GEN3 0x03 /* support MT-protocol B with tracking ID. */
@@ -50,6 +52,8 @@
#define CYAPA_CMD_BL_ALL 0x0a
#define CYAPA_CMD_BLK_PRODUCT_ID 0x0b
#define CYAPA_CMD_BLK_HEAD 0x0c
+#define CYAPA_CMD_MAX_BASELINE 0x0d
+#define CYAPA_CMD_MIN_BASELINE 0x0e
/* report data start reg offset address. */
#define DATA_REG_START_OFFSET 0x0000
@@ -150,6 +154,10 @@
CAPABILITY_MIDDLE_BTN_MASK)
#define CYAPA_OFFSET_SOFT_RESET REG_OFFSET_COMMAND_BASE
+#define OP_RECALIBRATION_MASK 0x80
+#define OP_REPORT_BASELINE_MASK 0x40
+#define REG_OFFSET_MAX_BASELINE 0x0026
+#define REG_OFFSET_MIN_BASELINE 0x0027
#define REG_OFFSET_POWER_MODE (REG_OFFSET_COMMAND_BASE + 1)
#define SET_POWER_MODE_DELAY 10000 /* unit: us */
@@ -493,6 +501,7 @@ struct cyapa_tsg_bin_image {
/* The main device structure */
struct cyapa {
enum cyapa_state state;
+ u8 status[BL_STATUS_SIZE];
struct i2c_client *client;
struct input_dev *input;
@@ -569,13 +578,29 @@ struct cyapa {
bl_read_fw_func cyapa_read_fw;
read_raw_data_func cyapa_read_raw_data;
+ size_t read_fw_image_size;
+ size_t tp_raw_data_size;
struct cyapa_tsg_bin_image_head fw_img_head;
+
+ struct mutex debugfs_mutex;
+
+ /* per-instance debugfs root */
+ struct dentry *dentry_dev;
+
+ /* Buffer to store firmware read using debugfs */
+ u8 *read_fw_image;
+ /* Buffer to store sensors' raw data */
+ u8 *tp_raw_data;
};
+static const u8 bl_activate[] = { 0x00, 0xff, 0x38, 0x00, 0x01, 0x02, 0x03,
+ 0x04, 0x05, 0x06, 0x07 };
static const u8 bl_deactivate[] = { 0x00, 0xff, 0x3b, 0x00, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07 };
static const u8 bl_exit[] = { 0x00, 0xff, 0xa5, 0x00, 0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07 };
+/* global root node of the cyapa debugfs directory. */
+static struct dentry *cyapa_debugfs_root;
struct cyapa_cmd_len {
u8 cmd;
@@ -601,10 +626,14 @@ struct cyapa_cmd_len {
#define CMD_RESET 0
#define CMD_POWER_MODE 1
#define CMD_DEV_STATUS 2
+#define CMD_REPORT_MAX_BASELINE 3
+#define CMD_REPORT_MIN_BASELINE 4
#define SMBUS_BYTE_CMD(cmd) (((cmd) & 0x3f) << 1)
#define CYAPA_SMBUS_RESET SMBUS_BYTE_CMD(CMD_RESET)
#define CYAPA_SMBUS_POWER_MODE SMBUS_BYTE_CMD(CMD_POWER_MODE)
#define CYAPA_SMBUS_DEV_STATUS SMBUS_BYTE_CMD(CMD_DEV_STATUS)
+#define CYAPA_SMBUS_MAX_BASELINE SMBUS_BYTE_CMD(CMD_REPORT_MAX_BASELINE)
+#define CYAPA_SMBUS_MIN_BASELINE SMBUS_BYTE_CMD(CMD_REPORT_MIN_BASELINE)
/* for group registers read/write command */
#define REG_GROUP_DATA 0
@@ -649,7 +678,9 @@ static const struct cyapa_cmd_len cyapa_i2c_cmds[] = {
{ BL_DATA_OFFSET, 16 },
{ BL_HEAD_OFFSET, 32 },
{ REG_OFFSET_QUERY_BASE, PRODUCT_ID_SIZE },
- { REG_OFFSET_DATA_BASE, 32 }
+ { REG_OFFSET_DATA_BASE, 32 },
+ { REG_OFFSET_MAX_BASELINE, 1 },
+ { REG_OFFSET_MIN_BASELINE, 1 },
};
static const struct cyapa_cmd_len cyapa_smbus_cmds[] = {
@@ -666,10 +697,30 @@ static const struct cyapa_cmd_len cyapa_smbus_cmds[] = {
{ CYAPA_SMBUS_BL_ALL, 32 },
{ CYAPA_SMBUS_BLK_PRODUCT_ID, PRODUCT_ID_SIZE },
{ CYAPA_SMBUS_BLK_HEAD, 16 },
+ { CYAPA_SMBUS_MAX_BASELINE, 1 },
+ { CYAPA_SMBUS_MIN_BASELINE, 1 },
};
static const char unique_str[] = "CYTRA";
+#define CYAPA_DEBUGFS_READ_FW "read_fw"
+#define CYAPA_DEBUGFS_RAW_DATA "raw_data"
+#define CYAPA_FW_NAME "cyapa.bin"
+#define CYAPA_FW_BLOCK_SIZE 64
+#define CYAPA_FW_READ_SIZE 16
+#define CYAPA_FW_HDR_START 0x0780
+#define CYAPA_FW_HDR_BLOCK_COUNT 2
+#define CYAPA_FW_HDR_BLOCK_START (CYAPA_FW_HDR_START / CYAPA_FW_BLOCK_SIZE)
+#define CYAPA_FW_HDR_SIZE (CYAPA_FW_HDR_BLOCK_COUNT * \
+ CYAPA_FW_BLOCK_SIZE)
+#define CYAPA_FW_DATA_START 0x0800
+#define CYAPA_FW_DATA_BLOCK_COUNT 480
+#define CYAPA_FW_DATA_BLOCK_START (CYAPA_FW_DATA_START / CYAPA_FW_BLOCK_SIZE)
+#define CYAPA_FW_DATA_SIZE (CYAPA_FW_DATA_BLOCK_COUNT * \
+ CYAPA_FW_BLOCK_SIZE)
+#define CYAPA_FW_SIZE (CYAPA_FW_HDR_SIZE + CYAPA_FW_DATA_SIZE)
+#define CYAPA_CMD_LEN 16
+
static int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout);
static void cyapa_detect(struct cyapa *cyapa);
static void cyapa_detect_async(void *data, async_cookie_t cookie);
@@ -890,6 +941,78 @@ static int cyapa_gen3_state_parse(struct cyapa *cyapa, u8 *reg_data, int len)
return -EAGAIN;
}
+/*
+ * Enter bootloader by soft resetting the device.
+ *
+ * If device is already in the bootloader, the function just returns.
+ * Otherwise, reset the device; after reset, device enters bootloader idle
+ * state immediately.
+ *
+ * Also, if device was unregister device from input core. Device will
+ * re-register after it is detected following resumption of operational mode.
+ *
+ * Returns:
+ * 0 on success
+ * -EAGAIN device was reset, but is not now in bootloader idle state
+ * < 0 if the device never responds within the timeout
+ */
+static int cyapa_gen3_bl_enter(struct cyapa *cyapa)
+{
+ int ret;
+
+ if (cyapa->input) {
+ cyapa_disable_irq(cyapa);
+ input_unregister_device(cyapa->input);
+ cyapa->input = NULL;
+ }
+
+ ret = cyapa_poll_state(cyapa, 500);
+ if (ret < 0)
+ return ret;
+ if (cyapa->state == CYAPA_STATE_BL_IDLE) {
+ /* Already in BL_IDLE. Skipping exit. */
+ return 0;
+ }
+
+ if (cyapa->state != CYAPA_STATE_OP)
+ return -EAGAIN;
+
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+ ret = cyapa_write_byte(cyapa, CYAPA_CMD_SOFT_RESET, 0x01);
+ if (ret < 0)
+ return -EIO;
+
+ usleep_range(25000, 50000);
+ ret = cyapa_poll_state(cyapa, 500);
+ if (ret < 0)
+ return ret;
+ if ((cyapa->state != CYAPA_STATE_BL_IDLE) ||
+ (cyapa->status[REG_BL_STATUS] & BL_STATUS_WATCHDOG))
+ return -EAGAIN;
+
+ return 0;
+}
+
+static int cyapa_gen3_bl_activate(struct cyapa *cyapa)
+{
+ int ret;
+
+ ret = cyapa_i2c_reg_write_block(cyapa, 0, sizeof(bl_activate),
+ bl_activate);
+ if (ret < 0)
+ return ret;
+
+ /* Wait for bootloader to activate; takes between 2 and 12 seconds */
+ msleep(2000);
+ ret = cyapa_poll_state(cyapa, 11000);
+ if (ret < 0)
+ return ret;
+ if (cyapa->state != CYAPA_STATE_BL_ACTIVE)
+ return -EAGAIN;
+
+ return 0;
+}
+
static int cyapa_gen3_bl_deactivate(struct cyapa *cyapa)
{
int ret;
@@ -950,6 +1073,412 @@ static int cyapa_gen3_bl_exit(struct cyapa *cyapa)
return 0;
}
+/* Used in gen3 bootloader commands. */
+static u16 cyapa_gen3_csum(const u8 *buf, size_t count)
+{
+ int i;
+ u16 csum = 0;
+
+ for (i = 0; i < count; i++)
+ csum += buf[i];
+
+ return csum;
+}
+
+/*
+ * Verify the integrity of a CYAPA firmware image file.
+ *
+ * The firmware image file is 30848 bytes, composed of 482 64-byte blocks.
+ *
+ * The first 2 blocks are the firmware header.
+ * The next 480 blocks are the firmware image.
+ *
+ * The first two bytes of the header hold the header checksum, computed by
+ * summing the other 126 bytes of the header.
+ * The last two bytes of the header hold the firmware image checksum, computed
+ * by summing the 30720 bytes of the image modulo 0xffff.
+ *
+ * Both checksums are stored little-endian.
+ */
+static int cyapa_gen3_check_fw(struct cyapa *cyapa, const struct firmware *fw)
+{
+ struct device *dev = &cyapa->client->dev;
+ u16 csum;
+ u16 csum_expected;
+
+ /* Firmware must match exact 30848 bytes = 482 64-byte blocks. */
+ if (fw->size != CYAPA_FW_SIZE) {
+ dev_err(dev, "invalid firmware size = %zu, expected %u.\n",
+ fw->size, CYAPA_FW_SIZE);
+ return -EINVAL;
+ }
+
+ /* Verify header block */
+ csum_expected = (fw->data[0] << 8) | fw->data[1];
+ csum = cyapa_gen3_csum(&fw->data[2], CYAPA_FW_HDR_SIZE - 2);
+ if (csum != csum_expected) {
+ dev_err(dev, "%s %04x, expected: %04x\n",
+ "invalid firmware header checksum = ",
+ csum, csum_expected);
+ return -EINVAL;
+ }
+
+ /* Verify firmware image */
+ csum_expected = (fw->data[CYAPA_FW_HDR_SIZE - 2] << 8) |
+ fw->data[CYAPA_FW_HDR_SIZE - 1];
+ csum = cyapa_gen3_csum(&fw->data[CYAPA_FW_HDR_SIZE],
+ CYAPA_FW_DATA_SIZE);
+ if (csum != csum_expected) {
+ dev_err(dev, "%s %04x, expected: %04x\n",
+ "invalid firmware header checksum = ",
+ csum, csum_expected);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ * Write a |len| byte long buffer |buf| to the device, by chopping it up into a
+ * sequence of smaller |CYAPA_CMD_LEN|-length write commands.
+ *
+ * The data bytes for a write command are prepended with the 1-byte offset
+ * of the data relative to the start of |buf|.
+ */
+static int cyapa_gen3_write_buffer(struct cyapa *cyapa,
+ const u8 *buf, size_t len)
+{
+ int ret;
+ size_t i;
+ unsigned char cmd[CYAPA_CMD_LEN + 1];
+ size_t cmd_len;
+
+ for (i = 0; i < len; i += CYAPA_CMD_LEN) {
+ const u8 *payload = &buf[i];
+ cmd_len = (len - i >= CYAPA_CMD_LEN) ? CYAPA_CMD_LEN : len - i;
+ cmd[0] = i;
+ memcpy(&cmd[1], payload, cmd_len);
+
+ ret = cyapa_i2c_reg_write_block(cyapa, 0, cmd_len + 1, cmd);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+/*
+ * A firmware block write command writes 64 bytes of data to a single flash
+ * page in the device. The 78-byte block write command has the format:
+ * <0xff> <CMD> <Key> <Start> <Data> <Data-Checksum> <CMD Checksum>
+ *
+ * <0xff> - every command starts with 0xff
+ * <CMD> - the write command value is 0x39
+ * <Key> - write commands include an 8-byte key: { 00 01 02 03 04 05 06 07 }
+ * <Block> - Memory Block number (address / 64) (16-bit, big-endian)
+ * <Data> - 64 bytes of firmware image data
+ * <Data Checksum> - sum of 64 <Data> bytes, modulo 0xff
+ * <CMD Checksum> - sum of 77 bytes, from 0xff to <Data Checksum>
+ *
+ * Each write command is split into 5 i2c write transactions of up to 16 bytes.
+ * Each transaction starts with an i2c register offset: (00, 10, 20, 30, 40).
+ */
+static int cyapa_gen3_write_fw_block(struct cyapa *cyapa,
+ u16 block, const u8 *data)
+{
+ int ret;
+ u8 cmd[78];
+ u8 status[BL_STATUS_SIZE];
+ /* Programming for one block can take about 100ms. */
+ int tries = 11;
+ u8 bl_status, bl_error;
+
+ /* set write command and security key bytes. */
+ cmd[0] = 0xff;
+ cmd[1] = 0x39;
+ cmd[2] = 0x00;
+ cmd[3] = 0x01;
+ cmd[4] = 0x02;
+ cmd[5] = 0x03;
+ cmd[6] = 0x04;
+ cmd[7] = 0x05;
+ cmd[8] = 0x06;
+ cmd[9] = 0x07;
+ cmd[10] = block >> 8;
+ cmd[11] = block;
+ memcpy(&cmd[12], data, CYAPA_FW_BLOCK_SIZE);
+ cmd[76] = cyapa_gen3_csum(data, CYAPA_FW_BLOCK_SIZE);
+ cmd[77] = cyapa_gen3_csum(cmd, sizeof(cmd) - 1);
+
+ ret = cyapa_gen3_write_buffer(cyapa, cmd, sizeof(cmd));
+ if (ret)
+ return ret;
+
+ /* wait for write to finish */
+ do {
+ usleep_range(10000, 20000);
+
+ /* check block write command result status. */
+ ret = cyapa_i2c_reg_read_block(cyapa, BL_HEAD_OFFSET,
+ BL_STATUS_SIZE, status);
+ if (ret != BL_STATUS_SIZE)
+ return (ret < 0) ? ret : -EIO;
+ } while ((status[1] & BL_STATUS_BUSY) && --tries);
+
+ /* ignore WATCHDOG bit and reserved bits. */
+ bl_status = status[1] & ~BL_STATUS_REV_MASK;
+ bl_error = status[2] & ~BL_ERROR_RESERVED;
+
+ if (status[1] & BL_STATUS_BUSY)
+ ret = -ETIMEDOUT;
+ else if (bl_status != BL_STATUS_RUNNING ||
+ bl_error != BL_ERROR_BOOTLOADING)
+ ret = -EIO;
+ else
+ ret = 0;
+
+ return ret;
+}
+
+/*
+ * A firmware block read command reads 16 bytes of data from flash starting
+ * from a given address. The 12-byte block read command has the format:
+ * <0xff> <CMD> <Key> <Addr>
+ *
+ * <0xff> - every command starts with 0xff
+ * <CMD> - the read command value is 0x3c
+ * <Key> - read commands include an 8-byte key: { 00 01 02 03 04 05 06 07 }
+ * <Addr> - Memory address (16-bit, big-endian)
+ *
+ * The command is followed by an i2c block read to read the 16 bytes of data.
+ */
+static int cyapa_gen3_read_fw_bytes(struct cyapa *cyapa, u16 addr, u8 *data)
+{
+ int ret;
+ u8 cmd[] = { 0xff, 0x3c, 0, 1, 2, 3, 4, 5, 6, 7, addr >> 8, addr };
+
+ ret = cyapa_gen3_write_buffer(cyapa, cmd, sizeof(cmd));
+ if (ret)
+ return ret;
+
+ /* read data buffer starting from offset 16 */
+ ret = cyapa_i2c_reg_read_block(cyapa, 16, CYAPA_FW_READ_SIZE, data);
+ if (ret != CYAPA_FW_READ_SIZE)
+ return (ret < 0) ? ret : -EIO;
+
+ return 0;
+}
+
+static int cyapa_gen3_do_fw_update(struct cyapa *cyapa,
+ const struct firmware *fw)
+{
+ struct device *dev = &cyapa->client->dev;
+ int ret;
+ int i;
+
+ /* First write data, starting at byte 128 of fw->data */
+ for (i = 0; i < CYAPA_FW_DATA_BLOCK_COUNT; i++) {
+ size_t block = CYAPA_FW_DATA_BLOCK_START + i;
+ size_t addr = (i + CYAPA_FW_HDR_BLOCK_COUNT) *
+ CYAPA_FW_BLOCK_SIZE;
+ const u8 *data = &fw->data[addr];
+ ret = cyapa_gen3_write_fw_block(cyapa, block, data);
+ if (ret) {
+ dev_err(dev, "FW update aborted, %d\n", ret);
+ return ret;
+ }
+ }
+
+ /* Then write checksum */
+ for (i = 0; i < CYAPA_FW_HDR_BLOCK_COUNT; i++) {
+ size_t block = CYAPA_FW_HDR_BLOCK_START + i;
+ size_t addr = i * CYAPA_FW_BLOCK_SIZE;
+ const u8 *data = &fw->data[addr];
+ ret = cyapa_gen3_write_fw_block(cyapa, block, data);
+ if (ret) {
+ dev_err(dev, "FW update aborted, %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Read the entire firmware image into ->read_fw_image.
+ * If the ->read_fw_image has already been allocated, then this function
+ * doesn't do anything and just returns 0.
+ * If an error occurs while reading the image, ->read_fw_image is freed, and
+ * the error is returned.
+ *
+ * The firmware is a fixed size (CYAPA_FW_SIZE), and is read out in
+ * fixed length (CYAPA_FW_READ_SIZE) chunks.
+ */
+static int cyapa_gen3_read_fw(struct cyapa *cyapa)
+{
+ int ret;
+ int addr;
+
+ if (cyapa->read_fw_image)
+ return 0;
+
+ ret = cyapa_gen3_bl_enter(cyapa);
+ if (ret)
+ goto err_detect;
+
+ cyapa->read_fw_image = kmalloc(CYAPA_FW_SIZE, GFP_KERNEL);
+ if (!cyapa->read_fw_image) {
+ ret = -ENOMEM;
+ goto err_detect;
+ }
+
+ for (addr = 0; addr < CYAPA_FW_SIZE; addr += CYAPA_FW_READ_SIZE) {
+ ret = cyapa_gen3_read_fw_bytes(cyapa, CYAPA_FW_HDR_START + addr,
+ &cyapa->read_fw_image[addr]);
+ if (ret) {
+ kfree(cyapa->read_fw_image);
+ cyapa->read_fw_image = NULL;
+ break;
+ }
+ }
+
+err_detect:
+ if (cyapa->read_fw_image)
+ cyapa->read_fw_image_size = CYAPA_FW_SIZE;
+ cyapa_detect_async(cyapa, 0);
+ return ret;
+}
+
+static ssize_t cyapa_gen3_do_calibrate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ int tries = 20; /* max recalibration timeout 2s. */
+ int ret;
+
+ cyapa_disable_irq(cyapa);
+
+ ret = cyapa_read_byte(cyapa, CYAPA_CMD_DEV_STATUS);
+ if (ret < 0) {
+ dev_err(dev, "Error reading dev status. err = %d\n", ret);
+ goto out;
+ }
+ if ((ret & CYAPA_DEV_NORMAL) != CYAPA_DEV_NORMAL) {
+ dev_warn(dev, "Trackpad device is busy. device state = 0x%x\n",
+ ret);
+ ret = -EAGAIN;
+ goto out;
+ }
+
+ ret = cyapa_write_byte(cyapa, CYAPA_CMD_SOFT_RESET,
+ OP_RECALIBRATION_MASK);
+ if (ret < 0) {
+ dev_err(dev, "Failed to send calibrate command. ret = %d\n",
+ ret);
+ goto out;
+ }
+
+ do {
+ /*
+ * For this recalibration, the max time will not exceed 2s.
+ * The average time is approximately 500 - 700 ms, and we
+ * will check the status every 100 - 200ms.
+ */
+ usleep_range(100000, 200000);
+
+ ret = cyapa_read_byte(cyapa, CYAPA_CMD_DEV_STATUS);
+ if (ret < 0) {
+ dev_err(dev, "Error reading dev status. err = %d\n",
+ ret);
+ goto out;
+ }
+ if ((ret & CYAPA_DEV_NORMAL) == CYAPA_DEV_NORMAL)
+ break;
+ } while (--tries);
+
+ if (tries == 0) {
+ dev_err(dev, "Failed to calibrate. Timeout.\n");
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ dev_dbg(dev, "Calibration successful.\n");
+
+out:
+ cyapa_enable_irq(cyapa);
+ return ret < 0 ? ret : count;
+}
+
+static ssize_t cyapa_gen3_show_baseline(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ int max_baseline, min_baseline;
+ int tries = 3;
+ int ret;
+
+ cyapa_disable_irq(cyapa);
+
+ ret = cyapa_read_byte(cyapa, CYAPA_CMD_DEV_STATUS);
+ if (ret < 0) {
+ dev_err(dev, "Error reading dev status. err = %d\n", ret);
+ goto out;
+ }
+ if ((ret & CYAPA_DEV_NORMAL) != CYAPA_DEV_NORMAL) {
+ dev_warn(dev, "Trackpad device is busy. device state = 0x%x\n",
+ ret);
+ ret = -EAGAIN;
+ goto out;
+ }
+
+ ret = cyapa_write_byte(cyapa, CYAPA_CMD_SOFT_RESET,
+ OP_REPORT_BASELINE_MASK);
+ if (ret < 0) {
+ dev_err(dev, "Failed to send report baseline command. %d\n",
+ ret);
+ goto out;
+ }
+
+ do {
+ usleep_range(10000, 20000);
+
+ ret = cyapa_read_byte(cyapa, CYAPA_CMD_DEV_STATUS);
+ if (ret < 0) {
+ dev_err(dev, "Error reading dev status. err = %d\n",
+ ret);
+ goto out;
+ }
+ if ((ret & CYAPA_DEV_NORMAL) == CYAPA_DEV_NORMAL)
+ break;
+ } while (--tries);
+
+ if (tries == 0) {
+ dev_err(dev, "Device timed out going to Normal state.\n");
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+
+ ret = cyapa_read_byte(cyapa, CYAPA_CMD_MAX_BASELINE);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read max baseline. err = %d\n", ret);
+ goto out;
+ }
+ max_baseline = ret;
+
+ ret = cyapa_read_byte(cyapa, CYAPA_CMD_MIN_BASELINE);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read min baseline. err = %d\n", ret);
+ goto out;
+ }
+ min_baseline = ret;
+
+ dev_dbg(dev, "Baseline report successful. Max: %d Min: %d\n",
+ max_baseline, min_baseline);
+ ret = scnprintf(buf, PAGE_SIZE, "%d %d\n", max_baseline, min_baseline);
+
+out:
+ cyapa_enable_irq(cyapa);
+ return ret;
+}
+
/*
* cyapa_sleep_time_to_pwr_cmd and cyapa_pwr_cmd_to_sleep_time
*
@@ -2541,18 +3070,18 @@ static int cyapa_check_is_operational(struct cyapa *cyapa)
break;
case CYAPA_GEN3:
- cyapa->cyapa_check_fw = NULL;
- cyapa->cyapa_bl_enter = NULL;
- cyapa->cyapa_bl_activate = NULL;
+ cyapa->cyapa_check_fw = cyapa_gen3_check_fw;
+ cyapa->cyapa_bl_enter = cyapa_gen3_bl_enter;
+ cyapa->cyapa_bl_activate = cyapa_gen3_bl_activate;
cyapa->cyapa_bl_initiate = NULL;
- cyapa->cyapa_update_fw = NULL;
+ cyapa->cyapa_update_fw = cyapa_gen3_do_fw_update;
cyapa->cyapa_bl_verify_app_integrity = NULL;
cyapa->cyapa_bl_deactivate = cyapa_gen3_bl_deactivate;
- cyapa->cyapa_show_baseline = NULL;
- cyapa->cyapa_calibrate_store = NULL;
+ cyapa->cyapa_show_baseline = cyapa_gen3_show_baseline;
+ cyapa->cyapa_calibrate_store = cyapa_gen3_do_calibrate;
cyapa->cyapa_irq_handler = cyapa_gen3_irq_handler;
cyapa->cyapa_set_power_mode = cyapa_gen3_set_power_mode;
- cyapa->cyapa_read_fw = NULL;
+ cyapa->cyapa_read_fw = cyapa_gen3_read_fw;
cyapa->cyapa_read_raw_data = NULL;
ret = cyapa_gen3_do_operational_check(cyapa);
@@ -2713,6 +3242,10 @@ static int cyapa_get_state(struct cyapa *cyapa)
* detect trackpad protocol based on characristic registers and bits.
*/
do {
+ cyapa->status[REG_OP_STATUS] = status[REG_OP_STATUS];
+ cyapa->status[REG_BL_STATUS] = status[REG_BL_STATUS];
+ cyapa->status[REG_BL_ERROR] = status[REG_BL_ERROR];
+
if (cyapa->gen == CYAPA_GEN_UNKNOWN ||
cyapa->gen == CYAPA_GEN3) {
cyapa->gen_detecting = CYAPA_GEN3;
@@ -2964,6 +3497,286 @@ static void cyapa_detect(struct cyapa *cyapa)
cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
}
+static int cyapa_firmware(struct cyapa *cyapa, const char *fw_name)
+{
+ struct device *dev = &cyapa->client->dev;
+ int ret;
+ const struct firmware *fw;
+
+ ret = request_firmware(&fw, fw_name, dev);
+ if (ret) {
+ dev_err(dev, "Could not load firmware from %s, %d\n",
+ fw_name, ret);
+ return ret;
+ }
+
+ if (cyapa->cyapa_check_fw) {
+ ret = cyapa->cyapa_check_fw(cyapa, fw);
+ if (ret) {
+ dev_err(dev, "Invalid CYAPA firmware image: %s\n",
+ fw_name);
+ goto done;
+ }
+ } else {
+ dev_err(dev, "Unknown status, operation forbidden, gen=%d\n",
+ cyapa->gen);
+ ret = -EPERM;
+ goto done;
+ }
+
+ /*
+ * Resume the potentially suspended device because doing FW
+ * update on a device not in the FULL mode has a chance to
+ * fail.
+ */
+ pm_runtime_get_sync(dev);
+
+ if (cyapa->cyapa_bl_enter) {
+ ret = cyapa->cyapa_bl_enter(cyapa);
+ if (ret)
+ goto err_detect;
+ }
+
+ if (cyapa->cyapa_bl_activate) {
+ ret = cyapa->cyapa_bl_activate(cyapa);
+ if (ret)
+ goto err_detect;
+ }
+
+ if (cyapa->cyapa_bl_initiate) {
+ ret = cyapa->cyapa_bl_initiate(cyapa, fw);
+ if (ret)
+ goto err_detect;
+ }
+
+ if (cyapa->cyapa_update_fw) {
+ ret = cyapa->cyapa_update_fw(cyapa, fw);
+ if (ret)
+ goto err_detect;
+ }
+
+ if (cyapa->cyapa_bl_verify_app_integrity) {
+ ret = cyapa->cyapa_bl_verify_app_integrity(cyapa);
+ if (ret)
+ goto err_detect;
+ }
+
+err_detect:
+ pm_runtime_put_noidle(dev);
+ cyapa_detect_async(cyapa, 0);
+
+done:
+ release_firmware(fw);
+ return ret;
+}
+
+/*
+ **************************************************************
+ * debugfs interface
+ **************************************************************
+*/
+static int cyapa_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct cyapa *cyapa = inode->i_private;
+ int ret;
+
+ if (!cyapa)
+ return -ENODEV;
+
+ ret = mutex_lock_interruptible(&cyapa->debugfs_mutex);
+ if (ret)
+ return ret;
+
+ if (!kobject_get(&cyapa->client->dev.kobj)) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ file->private_data = cyapa;
+
+ /*
+ * If firmware hasn't been read yet, read it all in one pass.
+ * Subsequent opens will reuse the data in this same buffer.
+ */
+ if (!cyapa->cyapa_read_fw) {
+ ret = -EPERM;
+ goto out;
+ }
+ ret = cyapa->cyapa_read_fw(cyapa);
+
+out:
+ mutex_unlock(&cyapa->debugfs_mutex);
+ return ret;
+}
+
+static int cyapa_debugfs_release(struct inode *inode, struct file *file)
+{
+ struct cyapa *cyapa = file->private_data;
+ int ret;
+
+ if (!cyapa)
+ return 0;
+
+ ret = mutex_lock_interruptible(&cyapa->debugfs_mutex);
+ if (ret)
+ return ret;
+ file->private_data = NULL;
+ kobject_put(&cyapa->client->dev.kobj);
+ mutex_unlock(&cyapa->debugfs_mutex);
+
+ return 0;
+}
+
+/* Return some bytes from the buffered firmware image, starting from *ppos */
+static ssize_t cyapa_debugfs_read_fw(struct file *file, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct cyapa *cyapa = file->private_data;
+
+ if (!cyapa->read_fw_image)
+ return -EINVAL;
+
+ if (*ppos >= cyapa->read_fw_image_size)
+ return 0;
+
+ if (count + *ppos > cyapa->read_fw_image_size)
+ count = cyapa->read_fw_image_size - *ppos;
+
+ if (copy_to_user(buffer, &cyapa->read_fw_image[*ppos], count))
+ return -EFAULT;
+
+ *ppos += count;
+ return count;
+}
+
+static const struct file_operations cyapa_read_fw_fops = {
+ .open = cyapa_debugfs_open,
+ .release = cyapa_debugfs_release,
+ .read = cyapa_debugfs_read_fw
+};
+
+static int cyapa_debugfs_raw_data_open(struct inode *inode, struct file *file)
+{
+ struct cyapa *cyapa = inode->i_private;
+ int ret;
+
+ if (!cyapa)
+ return -ENODEV;
+
+ ret = mutex_lock_interruptible(&cyapa->debugfs_mutex);
+ if (ret)
+ return ret;
+
+ if (!kobject_get(&cyapa->client->dev.kobj)) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ file->private_data = cyapa;
+
+ if (!cyapa->tp_raw_data) {
+ if (cyapa->state != CYAPA_STATE_GEN5_APP ||
+ !cyapa->electrodes_x || !cyapa->electrodes_y) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ cyapa->tp_raw_data_size = sizeof(s32) * (cyapa->electrodes_x *
+ cyapa->electrodes_y + cyapa->electrodes_x +
+ cyapa->electrodes_y) + GEN5_RAW_DATA_HEAD_SIZE;
+ /* This buffer will be hold after used until the driver is
+ * unloaded, the purpose of it is to improve the performace
+ * to avoid frequently allocate and release the buffer. */
+ cyapa->tp_raw_data =
+ kmalloc(cyapa->tp_raw_data_size, GFP_KERNEL);
+ if (!cyapa->tp_raw_data) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ memset(cyapa->tp_raw_data, 0, cyapa->tp_raw_data_size);
+ }
+
+ if (!cyapa->cyapa_read_raw_data) {
+ ret = -EPERM;
+ goto out;
+ }
+ ret = cyapa->cyapa_read_raw_data(cyapa);
+
+out:
+ mutex_unlock(&cyapa->debugfs_mutex);
+ return ret;
+}
+
+static int cyapa_debugfs_raw_data_release(struct inode *inode,
+ struct file *file)
+{
+ struct cyapa *cyapa = file->private_data;
+ int ret;
+
+ if (!cyapa)
+ return 0;
+
+ ret = mutex_lock_interruptible(&cyapa->debugfs_mutex);
+ if (ret)
+ return ret;
+ file->private_data = NULL;
+ kobject_put(&cyapa->client->dev.kobj);
+ mutex_unlock(&cyapa->debugfs_mutex);
+
+ return 0;
+}
+
+/* Always return the sensors' latest raw data from trackpad device. */
+static ssize_t cyapa_debugfs_read_raw_data(struct file *file,
+ char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct cyapa *cyapa = file->private_data;
+
+ if (!cyapa->tp_raw_data)
+ return -EINVAL;
+
+ if (*ppos >= cyapa->tp_raw_data_size)
+ return 0;
+
+ if (count + *ppos > cyapa->tp_raw_data_size)
+ count = cyapa->tp_raw_data_size - *ppos;
+
+ if (copy_to_user(buffer, &cyapa->tp_raw_data[*ppos], count))
+ return -EFAULT;
+
+ *ppos += count;
+ return count;
+}
+
+static const struct file_operations cyapa_read_raw_data_fops = {
+ .open = cyapa_debugfs_raw_data_open,
+ .release = cyapa_debugfs_raw_data_release,
+ .read = cyapa_debugfs_read_raw_data
+};
+
+static int cyapa_debugfs_init(struct cyapa *cyapa)
+{
+ struct device *dev = &cyapa->client->dev;
+
+ if (!cyapa_debugfs_root)
+ return -ENODEV;
+
+ cyapa->dentry_dev = debugfs_create_dir(kobject_name(&dev->kobj),
+ cyapa_debugfs_root);
+
+ if (!cyapa->dentry_dev)
+ return -ENODEV;
+
+ mutex_init(&cyapa->debugfs_mutex);
+
+ debugfs_create_file(CYAPA_DEBUGFS_READ_FW, S_IRUSR, cyapa->dentry_dev,
+ cyapa, &cyapa_read_fw_fops);
+
+ debugfs_create_file(CYAPA_DEBUGFS_RAW_DATA, S_IRUSR, cyapa->dentry_dev,
+ cyapa, &cyapa_read_raw_data_fops);
+ return 0;
+}
/*
* Sysfs Interface.
@@ -3110,6 +3923,93 @@ static void cyapa_start_runtime(struct cyapa *cyapa)
static void cyapa_start_runtime(struct cyapa *cyapa) {}
#endif /* CONFIG_PM_RUNTIME */
+static ssize_t cyapa_show_fm_ver(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ return scnprintf(buf, PAGE_SIZE, "%d.%d\n", cyapa->fw_maj_ver,
+ cyapa->fw_min_ver);
+}
+
+static ssize_t cyapa_show_product_id(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ return scnprintf(buf, PAGE_SIZE, "%s\n", cyapa->product_id);
+}
+
+static ssize_t cyapa_update_fw_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ const char *fw_name;
+ int ret;
+
+ /* Do not allow paths that step out of /lib/firmware */
+ if (strstr(buf, "../") != NULL)
+ return -EINVAL;
+
+ fw_name = !strncmp(buf, "1", count) ||
+ !strncmp(buf, "1\n", count) ? CYAPA_FW_NAME : buf;
+
+ ret = cyapa_firmware(cyapa, fw_name);
+ if (ret)
+ dev_err(dev, "firmware update failed, %d\n", ret);
+ else
+ dev_dbg(dev, "firmware update succeeded\n");
+
+ return ret ? ret : count;
+}
+
+static ssize_t cyapa_calibrate_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ int ret;
+
+ if (!cyapa->cyapa_calibrate_store) {
+ dev_err(dev, "Calibrate operation not permitted.\n");
+ return -EPERM;
+ }
+
+ ret = cyapa->cyapa_calibrate_store(dev, attr, buf, count);
+ return ret < 0 ? ret : count;
+}
+
+static ssize_t cyapa_show_baseline(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+
+ if (!cyapa->cyapa_show_baseline) {
+ dev_err(dev, "Calibrate operation not permitted.\n");
+ return -EPERM;
+ }
+
+ return cyapa->cyapa_show_baseline(dev, attr, buf);
+}
+
+static DEVICE_ATTR(firmware_version, S_IRUGO, cyapa_show_fm_ver, NULL);
+static DEVICE_ATTR(product_id, S_IRUGO, cyapa_show_product_id, NULL);
+static DEVICE_ATTR(update_fw, S_IWUSR, NULL, cyapa_update_fw_store);
+static DEVICE_ATTR(baseline, S_IRUGO, cyapa_show_baseline, NULL);
+static DEVICE_ATTR(calibrate, S_IWUSR, NULL, cyapa_calibrate_store);
+
+static struct attribute *cyapa_sysfs_entries[] = {
+ &dev_attr_firmware_version.attr,
+ &dev_attr_product_id.attr,
+ &dev_attr_update_fw.attr,
+ &dev_attr_baseline.attr,
+ &dev_attr_calibrate.attr,
+ NULL,
+};
+
+static const struct attribute_group cyapa_sysfs_group = {
+ .attrs = cyapa_sysfs_entries,
+};
+
/*
* We rely on EV_SW and SW_LID bits to identify a LID device, and hook
@@ -3311,6 +4211,12 @@ static int cyapa_probe(struct i2c_client *client,
}
cyapa_disable_irq(cyapa);
+ if (sysfs_create_group(&client->dev.kobj, &cyapa_sysfs_group))
+ dev_warn(dev, "error creating sysfs entries.\n");
+
+ if (cyapa_debugfs_init(cyapa))
+ dev_warn(dev, "error creating debugfs entries.\n");
+
#ifdef CONFIG_PM_SLEEP
if (device_can_wakeup(dev) &&
sysfs_merge_group(&client->dev.kobj, &cyapa_power_wakeup_group))
@@ -3333,6 +4239,7 @@ static int cyapa_remove(struct i2c_client *client)
struct cyapa *cyapa = i2c_get_clientdata(client);
pm_runtime_disable(&client->dev);
+ sysfs_remove_group(&client->dev.kobj, &cyapa_sysfs_group);
#ifdef CONFIG_PM_SLEEP
sysfs_unmerge_group(&client->dev.kobj, &cyapa_power_wakeup_group);
@@ -3342,7 +4249,17 @@ static int cyapa_remove(struct i2c_client *client)
sysfs_unmerge_group(&client->dev.kobj, &cyapa_power_runtime_group);
#endif
+ kfree(cyapa->read_fw_image);
+ cyapa->read_fw_image = NULL;
+ cyapa->read_fw_image_size = 0;
+ kfree(cyapa->tp_raw_data);
+ cyapa->tp_raw_data = NULL;
+ cyapa->tp_raw_data_size = 0;
free_irq(cyapa->irq, cyapa);
+
+ debugfs_remove_recursive(cyapa->dentry_dev);
+ mutex_destroy(&cyapa->debugfs_mutex);
+
input_unregister_device(cyapa->input);
lid_event_unregister_handler(cyapa);
if (cyapa->cyapa_set_power_mode)
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.
[-- Attachment #2: winmail.dat --]
[-- Type: application/ms-tnef, Size: 16402 bytes --]
^ permalink raw reply related
* [PATCH 6/6] input: cyapa: add sysfs interfaces supported for gen5 trackpad device
From: Dudley Du @ 2014-04-14 7:54 UTC (permalink / raw)
To: Dmitry Torokhov (dmitry.torokhov@gmail.com)
Cc: Benson Leung, Daniel Kurtz, linux-input@vger.kernel.org,
linux-kernel@vger.kernel.org
[-- Attachment #1: Type: text/plain, Size: 52899 bytes --]
Add sysfs interfaces for gen5 trackpad devices that required in production,
including read and update firmware image, report baselines, sensors calibrate,
read product id, read firmware version.
TEST=test on Chomebooks.
Signed-off-by: Du, Dudley <dudl@cypress.com>
---
diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
index effa9c5..8653ffd 100644
--- a/drivers/input/mouse/Kconfig
+++ b/drivers/input/mouse/Kconfig
@@ -205,7 +205,7 @@ config MOUSE_BCM5974
config MOUSE_CYAPA
tristate "Cypress APA I2C Trackpad support"
- depends on I2C
+ depends on I2C && CRC_ITU_T
help
This driver adds support for Cypress All Points Addressable (APA)
I2C Trackpads, including the ones used in 2012 Samsung Chromebooks.
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 917eabe..4fb3677 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -29,6 +29,8 @@
#include <linux/uaccess.h>
#include <linux/unaligned/access_ok.h>
#include <linux/pm_runtime.h>
+#include <linux/crc-ccitt.h>
+#include <linux/crc-itu-t.h>
/* APA trackpad firmware generation */
@@ -2364,6 +2366,86 @@ static int cyapa_gen5_state_parse(struct cyapa *cyapa, u8 *reg_data, int len)
return -EAGAIN;
}
+static int cyapa_gen5_bl_initiate(struct cyapa *cyapa,
+ const struct firmware *fw)
+{
+ int ret = 0;
+ u16 length = 0;
+ u16 data_len = 0;
+ u16 meta_data_crc = 0;
+ u16 cmd_crc = 0;
+ u8 bl_gen5_activate[18 + CYAPA_TSG_FLASH_MAP_BLOCK_SIZE + 3];
+ int bl_gen5_activate_size = 0;
+ u8 resp_data[11];
+ int resp_len;
+ struct cyapa_tsg_bin_image *image;
+ int records_num;
+ u8 *data;
+
+ /* Try to dump all bufferred report data before send any command. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ bl_gen5_activate_size = sizeof(bl_gen5_activate);
+ memset(bl_gen5_activate, 0, bl_gen5_activate_size);
+
+ /* Output Report Register Address[15:0] = 0004h */
+ bl_gen5_activate[0] = 0x04;
+ bl_gen5_activate[1] = 0x00;
+
+ /* totoal command length[15:0] */
+ length = bl_gen5_activate_size - 2;
+ put_unaligned_le16(length, &bl_gen5_activate[2]);
+ bl_gen5_activate[4] = 0x40; /* Report ID = 40h */
+ bl_gen5_activate[5] = 0x00; /* RSVD = 00h */
+
+ bl_gen5_activate[6] = GEN5_SOP_KEY; /* SOP = 01h */
+ bl_gen5_activate[7] = 0x48; /* Command Code = 48h */
+
+ /* 8 Key bytes and block size */
+ data_len = CYAPA_TSG_BL_KEY_SIZE + CYAPA_TSG_FLASH_MAP_BLOCK_SIZE;
+ /* Data Length[15:0] */
+ put_unaligned_le16(data_len, &bl_gen5_activate[8]);
+ bl_gen5_activate[10] = 0xa5; /* Key Byte 0 */
+ bl_gen5_activate[11] = 0x01;
+ bl_gen5_activate[12] = 0x02; /* . */
+ bl_gen5_activate[13] = 0x03; /* . */
+ bl_gen5_activate[14] = 0xff; /* . */
+ bl_gen5_activate[15] = 0xfe;
+ bl_gen5_activate[16] = 0xfd;
+ bl_gen5_activate[17] = 0x5a; /* Key Byte 7 */
+
+ /* copy 60 bytes Meta Data Row Parameters */
+ image = (struct cyapa_tsg_bin_image *)fw->data;
+ records_num = (fw->size - sizeof(struct cyapa_tsg_bin_image_head)) /
+ sizeof(struct cyapa_tsg_bin_image_data_record);
+ /* APP_INTEGRITY row is always the last row block */
+ data = image->records[records_num - 1].record_data;
+ memcpy(&bl_gen5_activate[18], data, CYAPA_TSG_FLASH_MAP_METADATA_SIZE);
+
+ meta_data_crc = crc_itu_t(0xffff, &bl_gen5_activate[18],
+ CYAPA_TSG_FLASH_MAP_METADATA_SIZE);
+ /* Meta Data CRC[15:0] */
+ put_unaligned_le16(meta_data_crc,
+ &bl_gen5_activate[18 + CYAPA_TSG_FLASH_MAP_METADATA_SIZE]);
+
+ cmd_crc = crc_itu_t(0xffff, &bl_gen5_activate[6], 4 + data_len);
+ put_unaligned_le16(cmd_crc,
+ &bl_gen5_activate[bl_gen5_activate_size - 3]); /* CRC[15:0] */
+ bl_gen5_activate[bl_gen5_activate_size - 1] = GEN5_EOP_KEY;
+
+ resp_len = sizeof(resp_data);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ bl_gen5_activate, sizeof(bl_gen5_activate),
+ resp_data, &resp_len, 12000,
+ cyapa_gen5_sort_tsg_pip_bl_resp_data);
+ if (ret || resp_len != GEN5_BL_INITIATE_RESP_LEN ||
+ resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return (ret < 0) ? ret : -EAGAIN;
+
+ return 0;
+}
+
bool cyapa_gen5_sort_bl_exit_data(struct cyapa *cyapa, u8 *buf, int len)
{
if (buf == NULL || len < GEN5_RESP_LENGTH_SIZE)
@@ -2410,6 +2492,373 @@ static int cyapa_gen5_bl_exit(struct cyapa *cyapa)
return -EAGAIN;
}
+static int cyapa_gen5_bl_enter(struct cyapa *cyapa)
+{
+ int ret;
+ u8 cmd[] = { 0x04, 0x00, 0x05, 0x00, 0x2F, 0x00, 0x01 };
+ u8 resp_data[2];
+ int resp_len;
+
+ if (cyapa->input) {
+ cyapa_disable_irq(cyapa);
+ input_unregister_device(cyapa->input);
+ cyapa->input = NULL;
+ }
+ cyapa_enable_irq(cyapa);
+
+ ret = cyapa_poll_state(cyapa, 500);
+ if (ret < 0)
+ return ret;
+ if (cyapa->gen != CYAPA_GEN5)
+ return -EINVAL;
+
+ /* Already in Gen5 BL. Skipping exit. */
+ if (cyapa->state == CYAPA_STATE_GEN5_BL)
+ return 0;
+
+ if (cyapa->state != CYAPA_STATE_GEN5_APP)
+ return -EAGAIN;
+
+ /* Try to dump all bufferred report data before send any command. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ /*
+ * send bootloader enter command to trackpad device,
+ * after enter bootloader, the response data is two bytes of 0x00 0x00.
+ */
+ resp_len = sizeof(resp_data);
+ memset(resp_data, 0, resp_len);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, sizeof(cmd),
+ resp_data, &resp_len,
+ 5000, cyapa_gen5_sort_application_launch_data);
+ if (ret || resp_data[0] != 0x00 || resp_data[1] != 0x00)
+ return (ret < 0) ? ret : -EAGAIN;
+
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ return 0;
+}
+
+static int cyapa_gen5_check_fw(struct cyapa *cyapa, const struct firmware *fw)
+{
+ int i;
+ struct cyapa_tsg_bin_image *image;
+ int flash_records_count;
+ u16 expected_app_crc;
+ u16 expected_app_integrity_crc;
+ u16 app_crc = 0;
+ u16 app_integrity_crc = 0;
+ u16 row_num;
+ u8 *data;
+ u32 app_start;
+ u16 app_len;
+ u32 img_start;
+ u16 img_len;
+ int record_index;
+ struct device *dev = &cyapa->client->dev;
+
+ image = (struct cyapa_tsg_bin_image *)fw->data;
+ flash_records_count = (fw->size -
+ sizeof(struct cyapa_tsg_bin_image_head)) /
+ sizeof(struct cyapa_tsg_bin_image_data_record);
+
+ /* APP_INTEGRITY row is always the last row block,
+ * and the row id must be 0x01ff */
+ row_num = get_unaligned_be16(
+ &image->records[flash_records_count - 1].row_number);
+ if (&image->records[flash_records_count - 1].flash_array_id != 0x00 &&
+ row_num != 0x01ff) {
+ dev_err(dev, "%s: invaid app_integrity data.\n", __func__);
+ return -EINVAL;
+ }
+ data = image->records[flash_records_count - 1].record_data;
+ app_start = get_unaligned_le32(&data[4]);
+ app_len = get_unaligned_le16(&data[8]);
+ expected_app_crc = get_unaligned_le16(&data[10]);
+ img_start = get_unaligned_le32(&data[16]);
+ img_len = get_unaligned_le16(&data[20]);
+ expected_app_integrity_crc = get_unaligned_le16(&data[60]);
+
+ if ((app_start + app_len + img_start + img_len) %
+ CYAPA_TSG_FW_ROW_SIZE) {
+ dev_err(dev, "%s: invaid image alignment.\n", __func__);
+ return -EINVAL;
+ }
+
+ /* verify app_integrity crc */
+ app_integrity_crc = crc_itu_t(0xffff, data,
+ CYAPA_TSG_APP_INTEGRITY_SIZE);
+ if (app_integrity_crc != expected_app_integrity_crc) {
+ dev_err(dev, "%s: invaid app_integrity crc.\n", __func__);
+ return -EINVAL;
+ }
+
+ /*
+ * verify application image CRC
+ */
+ record_index = app_start / CYAPA_TSG_FW_ROW_SIZE -
+ CYAPA_TSG_IMG_START_ROW_NUM;
+ data = (u8 *)&image->records[record_index].record_data;
+ app_crc = crc_itu_t(0xffff, data, CYAPA_TSG_FW_ROW_SIZE);
+ for (i = 1; i < (app_len / CYAPA_TSG_FW_ROW_SIZE); i++) {
+ data = (u8 *)&image->records[++record_index].record_data;
+ app_crc = crc_itu_t(app_crc, data, CYAPA_TSG_FW_ROW_SIZE);
+ }
+
+ if (app_crc != expected_app_crc) {
+ dev_err(dev, "%s: invaid firmware app crc check.\n", __func__);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cyapa_gen5_write_fw_block(struct cyapa *cyapa,
+ struct cyapa_tsg_bin_image_data_record *flash_record)
+{
+ u8 flash_array_id;
+ u16 flash_row_id;
+ u16 record_len;
+ u8 *record_data;
+ u8 cmd[144]; /* 13 + 128+ 3 */
+ u16 cmd_len;
+ u16 data_len;
+ u16 crc;
+ u8 resp_data[11];
+ int resp_len;
+ int ret;
+
+ flash_array_id = flash_record->flash_array_id;
+ flash_row_id = get_unaligned_be16(&flash_record->row_number);
+ record_len = get_unaligned_be16(&flash_record->record_len);
+ record_data = flash_record->record_data;
+
+ cmd_len = sizeof(cmd) - 2; /* not include 2 bytes regisetr address. */
+ memset(cmd, 0, cmd_len + 2);
+ cmd[0] = 0x04; /* register address */
+ cmd[1] = 0x00;
+
+ put_unaligned_le16(cmd_len, &cmd[2]);
+ cmd[4] = 0x40; /* report id 40h */
+ cmd[5] = 0x00;
+
+ cmd[6] = GEN5_SOP_KEY; /* SOP = 01h */
+ cmd[7] = 0x39; /* command code = 39h */
+ /* 1 (Flash Array ID) + 2 (Flash Row ID) + 128 (flash data) */
+ data_len = 3 + record_len;
+ put_unaligned_le16(data_len, &cmd[8]);
+ cmd[10] = flash_array_id; /* Flash Array ID = 00h */
+ put_unaligned_le16(flash_row_id, &cmd[11]);
+
+ memcpy(&cmd[13], record_data, record_len);
+ crc = crc_itu_t(0xffff, &cmd[6], 4 + data_len);
+ put_unaligned_le16(crc, &cmd[2 + cmd_len - 3]);
+ cmd[2 + cmd_len - 1] = GEN5_EOP_KEY;
+
+ resp_len = sizeof(resp_data);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, sizeof(cmd),
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_bl_resp_data);
+ if (ret || resp_len != GEN5_BL_BLOCK_WRITE_RESP_LEN ||
+ resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return ret < 0 ? ret : -EAGAIN;
+
+ return 0;
+}
+
+static int cyapa_gen5_read_fw_bytes(struct cyapa *cyapa, u16 row_num, u8 *data)
+{
+ int ret;
+ u8 cmd[16];
+ size_t cmd_len;
+ u8 resp_data[CYAPA_TSG_FW_ROW_SIZE / 2 + GEN5_MIN_BL_RESP_LENGTH];
+ int resp_len;
+ u16 offset;
+ u16 cmd_crc;
+ struct cyapa_tsg_bin_image_data_record *fw_img_record;
+
+ fw_img_record = (struct cyapa_tsg_bin_image_data_record *)data;
+
+ cmd[0] = 0x04; /* register address */
+ cmd[1] = 0x00;
+ cmd[2] = 0x0e;
+ cmd[3] = 0x00;
+ cmd[4] = 0x40; /* report id 40h */
+ cmd[5] = 0x00;
+ cmd[6] = GEN5_SOP_KEY;
+ cmd[7] = 0x3d; /* read application image command code */
+ cmd[8] = 0x03;
+ cmd[9] = 0x00;
+ offset = row_num * CYAPA_TSG_FW_ROW_SIZE -
+ CYAPA_TSG_START_OF_APPLICATION;
+ put_unaligned_le16(offset, &cmd[10]);
+ cmd[12] = CYAPA_TSG_IMG_READ_SIZE;
+ cmd_crc = crc_itu_t(0xffff, &cmd[6], 7);
+ put_unaligned_le16(cmd_crc, &cmd[13]); /* CRC[15:0] */
+ cmd[15] = GEN5_EOP_KEY; /* EOP = 17h */
+ cmd_len = 16;
+
+ resp_len = CYAPA_TSG_IMG_READ_SIZE + GEN5_MIN_BL_RESP_LENGTH;
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, cmd_len,
+ resp_data, &resp_len,
+ 50, cyapa_gen5_sort_tsg_pip_bl_resp_data);
+ if (resp_len != (CYAPA_TSG_IMG_READ_SIZE + GEN5_MIN_BL_RESP_LENGTH) ||
+ ret || resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return (ret < 0) ? ret : -EAGAIN;
+
+ /* copy first 64 bytes in the row. */
+ memcpy(&fw_img_record->record_data[0], &resp_data[8],
+ CYAPA_TSG_IMG_READ_SIZE);
+
+ if (row_num == CYAPA_TSG_IMG_APP_INTEGRITY_ROW_NUM) {
+ /* last row's rest 64 bytes are bootloader metadata,
+ * it's not allowed to be read out, will respond with error. */
+ memset(&fw_img_record->record_data[CYAPA_TSG_IMG_READ_SIZE],
+ 0, CYAPA_TSG_IMG_READ_SIZE);
+ goto skip_last_row;
+ }
+
+ /* read next 64 bytes in the row. */
+ offset = offset + CYAPA_TSG_IMG_READ_SIZE;
+ put_unaligned_le16(offset, &cmd[10]);
+ cmd_crc = crc_itu_t(0xffff, &cmd[6], 7);
+ put_unaligned_le16(cmd_crc, &cmd[13]); /* CRC[15:0] */
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, cmd_len,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_bl_resp_data);
+ if (resp_len != (CYAPA_TSG_IMG_READ_SIZE + GEN5_MIN_BL_RESP_LENGTH) ||
+ ret || resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return (ret < 0) ? ret : -EAGAIN;
+
+ /* copy last 64 bytes in the row. */
+ memcpy(&fw_img_record->record_data[CYAPA_TSG_IMG_READ_SIZE],
+ &resp_data[8], CYAPA_TSG_IMG_READ_SIZE);
+
+skip_last_row:
+ fw_img_record->flash_array_id = 0;
+ put_unaligned_be16(row_num, &fw_img_record->row_number);
+ put_unaligned_be16(CYAPA_TSG_FW_ROW_SIZE, &fw_img_record->record_len);
+
+ return 0;
+}
+
+static int cyapa_gen5_read_fw(struct cyapa *cyapa)
+{
+ int ret;
+ int fw_img_head_size;
+ int fw_img_record_size;
+ int row_index;
+ int array_index;
+ u32 img_start;
+ u16 img_len;
+ u16 img_start_row;
+ u16 img_end_row;
+ struct cyapa_tsg_bin_image_data_record app_integrity;
+ u8 *record_data;
+
+ if (cyapa->read_fw_image)
+ return 0;
+
+ ret = cyapa_gen5_bl_enter(cyapa);
+ if (ret)
+ goto err;
+
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ fw_img_head_size = sizeof(struct cyapa_tsg_bin_image_head);
+ fw_img_record_size = sizeof(struct cyapa_tsg_bin_image_data_record);
+
+ /* Read app integrity block data. */
+ row_index = CYAPA_TSG_IMG_APP_INTEGRITY_ROW_NUM;
+ ret = cyapa_gen5_read_fw_bytes(cyapa, row_index, (u8 *)&app_integrity);
+ if (ret)
+ goto err;
+ img_start = get_unaligned_le32(&app_integrity.record_data[16]);
+ img_len = get_unaligned_le16(&app_integrity.record_data[20]);
+ if ((img_start + img_len) % CYAPA_TSG_FW_ROW_SIZE)
+ goto err;
+ img_start_row = img_start / CYAPA_TSG_FW_ROW_SIZE;
+ img_end_row = (img_start + img_len) / CYAPA_TSG_FW_ROW_SIZE - 1;
+
+ /* allocate memory for image. */
+ cyapa->read_fw_image_size = fw_img_head_size +
+ (img_end_row - img_start_row + 2) * fw_img_record_size;
+ cyapa->read_fw_image = kmalloc(cyapa->read_fw_image_size, GFP_KERNEL);
+ if (!cyapa->read_fw_image) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ /* set image head data. */
+ memcpy(cyapa->read_fw_image, &cyapa->fw_img_head, fw_img_head_size);
+
+ /* read image blocks. */
+ for (row_index = img_start_row, array_index = 0;
+ row_index <= img_end_row;
+ row_index++, array_index++) {
+ record_data = &cyapa->read_fw_image[fw_img_head_size +
+ array_index * fw_img_record_size];
+ ret = cyapa_gen5_read_fw_bytes(cyapa, row_index, record_data);
+ if (ret)
+ goto err;
+ }
+
+ /* append last app integrity block data. */
+ record_data = &cyapa->read_fw_image[fw_img_head_size +
+ array_index * fw_img_record_size];
+ memcpy(record_data, &app_integrity, fw_img_record_size);
+
+err:
+ if (ret) {
+ kfree(cyapa->read_fw_image);
+ cyapa->read_fw_image = NULL;
+ cyapa->read_fw_image_size = 0;
+ }
+
+ cyapa_detect_async(cyapa, 0);
+ return ret;
+}
+
+static int cyapa_gen5_do_fw_update(struct cyapa *cyapa,
+ const struct firmware *fw)
+{
+ struct device *dev = &cyapa->client->dev;
+ struct cyapa_tsg_bin_image *image =
+ (struct cyapa_tsg_bin_image *)fw->data;
+ struct cyapa_tsg_bin_image_data_record *flash_record;
+ int flash_records_count;
+ int i;
+ int ret;
+
+ /* Try to dump all bufferred data if exists before send commands. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ flash_records_count =
+ (fw->size - sizeof(struct cyapa_tsg_bin_image_head)) /
+ sizeof(struct cyapa_tsg_bin_image_data_record);
+ /*
+ * the last flash row 0x01ff has been written through bl_initiate
+ * command, so DO NOT write flash 0x01ff to trackpad device.
+ */
+ for (i = 0; i < (flash_records_count - 1); i++) {
+ flash_record = &image->records[i];
+ ret = cyapa_gen5_write_fw_block(cyapa, flash_record);
+ if (ret) {
+ dev_err(dev, "%s: Gen5 FW update aborted, %d\n",
+ __func__, ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
static int cyapa_gen5_sleep_time_check(u16 sleep_time)
{
if (sleep_time > 1000)
@@ -2694,6 +3143,763 @@ static int cyapa_gen5_set_power_mode(struct cyapa *cyapa,
return ret;
}
+static int cyapa_gen5_resume_scanning(struct cyapa *cyapa)
+{
+ u8 cmd[7] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x04 };
+ u8 resp_data[6];
+ int resp_len;
+ int ret;
+
+ /* Try to dump all bufferred data before doing command. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ resp_len = 6;
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, 7,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+ if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+ resp_data[3] != GEN5_RESP_RSVD_KEY ||
+ GET_GEN5_CMD_CODE(resp_data[4]) != 0x04)
+ return -EINVAL;
+
+ /* Try to dump all bufferred data when resuming scanning. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ return 0;
+}
+
+static int cyapa_gen5_suspend_scanning(struct cyapa *cyapa)
+{
+ u8 cmd[7] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x03 };
+ u8 resp_data[6];
+ int resp_len;
+ int ret;
+
+ /* Try to dump all bufferred data before doing command. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ resp_len = 6;
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, 7,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+ if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+ resp_data[3] != GEN5_RESP_RSVD_KEY ||
+ GET_GEN5_CMD_CODE(resp_data[4]) != 0x03)
+ return -EINVAL;
+
+ /* Try to dump all bufferred data when suspending scanning. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ return 0;
+}
+
+static int cyapa_gen5_calibrate_pwcs(struct cyapa *cyapa,
+ u8 calibrate_sensing_mode_type)
+{
+ int ret;
+ u8 cmd[8];
+ u8 resp_data[6];
+ int resp_len;
+
+ /* Try to dump all bufferred data before doing command. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ cmd[2] = 0x06;
+ cmd[3] = 0x00;
+ cmd[4] = GEN5_APP_CMD_REPORT_ID;
+ cmd[5] = 0x00;
+ cmd[6] = GEN5_CMD_CALIBRATE;
+ cmd[7] = calibrate_sensing_mode_type;
+ resp_len = sizeof(resp_data);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, sizeof(cmd),
+ resp_data, &resp_len,
+ 5000, cyapa_gen5_sort_tsg_pip_app_resp_data);
+ if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+ GET_GEN5_CMD_CODE(resp_data[4]) != GEN5_CMD_CALIBRATE ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return ret < 0 ? ret : -EAGAIN;
+
+ return 0;
+}
+
+static ssize_t cyapa_gen5_do_calibrate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ int ret, calibrate_ret;
+
+ /* 1. suspend Scanning*/
+ ret = cyapa_gen5_suspend_scanning(cyapa);
+ if (ret)
+ return ret;
+
+ /* 2. do mutual capacitance fine calibrate. */
+ calibrate_ret = cyapa_gen5_calibrate_pwcs(cyapa,
+ CYAPA_SENSING_MODE_MUTUAL_CAP_FINE);
+ if (calibrate_ret)
+ goto resume_scanning;
+
+ /* 3. do self capacitance calibrate. */
+ calibrate_ret = cyapa_gen5_calibrate_pwcs(cyapa,
+ CYAPA_SENSING_MODE_SELF_CAP);
+ if (calibrate_ret)
+ goto resume_scanning;
+
+resume_scanning:
+ /* 4. resume Scanning*/
+ ret = cyapa_gen5_resume_scanning(cyapa);
+ if (ret || calibrate_ret)
+ return ret ? ret : calibrate_ret;
+
+ return count;
+}
+
+static s32 two_complement_to_s32(s32 value, int num_bits)
+{
+ if (value >> (num_bits - 1))
+ value |= -1 << num_bits;
+ return value;
+}
+
+static s32 cyapa_parse_structure_data(u8 data_format, u8 *buf, int buf_len)
+{
+ int data_size;
+ bool big_endian;
+ bool unsigned_type;
+ s32 value;
+
+ data_size = (data_format & 0x07);
+ big_endian = ((data_format & 0x10) == 0x00);
+ unsigned_type = ((data_format & 0x20) == 0x00);
+
+ if (buf_len < data_size)
+ return 0;
+
+ switch (data_size) {
+ case 1:
+ value = buf[0];
+ break;
+ case 2:
+ if (big_endian)
+ value = get_unaligned_be16(buf);
+ else
+ value = get_unaligned_le16(buf);
+ break;
+ case 4:
+ if (big_endian)
+ value = get_unaligned_be32(buf);
+ else
+ value = get_unaligned_le32(buf);
+ break;
+ default:
+ /* should not happen, just as default case here. */
+ value = 0;
+ break;
+ }
+
+ if (!unsigned_type)
+ value = two_complement_to_s32(value, data_size * 8);
+
+ return value;
+}
+
+
+/*
+ * Read all the global mutual or self idac data or mutual or self local PWC
+ * data based on the @idac_data_type.
+ * If the input value of @data_size is 0, then means read global mutual or
+ * self idac data. For read global mutual idac data, @idac_max, @idac_min and
+ * @idac_ave are in order used to return the max value of global mutual idac
+ * data, the min value of global mutual idac and the average value of the
+ * global mutual idac data. For read global self idac data, @idac_max is used
+ * to return the global self cap idac data in Rx direction, @idac_min is used
+ * to return the global self cap idac data in Tx direction. @idac_ave is not
+ * used.
+ * If the input value of @data_size is not 0, than means read the mutual or
+ * self local PWC data. The @idac_max, @idac_min and @idac_ave are used to
+ * return the max, min and average value of the mutual or self local PWC data.
+ * Note, in order to raed mutual local PWC data, must read invoke this function
+ * to read the mutual global idac data firstly to set the correct Rx number
+ * value, otherwise, the read mutual idac and PWC data may not correct.
+ */
+static int cyapa_gen5_read_idac_data(struct cyapa *cyapa,
+ u8 cmd_code, u8 idac_data_type, int *data_size,
+ int *idac_max, int *idac_min, int *idac_ave)
+{
+ int ret;
+ int i;
+ u8 cmd[12];
+ u8 resp_data[256];
+ int resp_len;
+ int read_len;
+ int value;
+ u16 offset;
+ int read_elements;
+ bool read_global_idac;
+ int sum, count, max_element_cnt;
+ int tmp_max, tmp_min, tmp_ave, tmp_sum, tmp_count, tmp_max_elements;
+ int electrodes_rx;
+
+ if (cmd_code != GEN5_CMD_RETRIEVE_DATA_STRUCTURE ||
+ (idac_data_type != GEN5_RETRIEVE_MUTUAL_PWC_DATA &&
+ idac_data_type != GEN5_RETRIEVE_SELF_CAP_PWC_DATA) ||
+ !data_size || !idac_max || !idac_min || !idac_ave)
+ return -EINVAL;
+
+ *idac_max = INT_MIN;
+ *idac_min = INT_MAX;
+ sum = count = tmp_count = 0;
+ electrodes_rx = 0;
+ tmp_max_elements = 0;
+ if (*data_size == 0) {
+ /* Read global idac values firstly.
+ * Currently, no idac data exceed 4 bytes. */
+ read_global_idac = true;
+ offset = 0;
+ *data_size = 4;
+
+ if (idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA) {
+ if (cyapa->electrodes_rx == 0) {
+ if (cyapa->electrodes_y > cyapa->electrodes_x) {
+ electrodes_rx = cyapa->electrodes_y;
+ tmp_max_elements = cyapa->electrodes_x;
+ } else {
+ electrodes_rx = cyapa->electrodes_x;
+ tmp_max_elements = cyapa->electrodes_y;
+ }
+ } else {
+ electrodes_rx = cyapa->electrodes_rx;
+ tmp_max_elements = 0; /* disable Rx detect. */
+ }
+ max_element_cnt = ((electrodes_rx + 7) / 8) * 8;
+ tmp_max = INT_MIN;
+ tmp_min = INT_MAX;
+ tmp_ave = tmp_sum = tmp_count = 0;
+ } else
+ max_element_cnt = 2;
+ } else {
+ read_global_idac = false;
+ if (*data_size > 4)
+ *data_size = 4;
+ /* calculate the start offset in bytes of local PWC data. */
+ if (idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA) {
+ offset = ((cyapa->electrodes_rx + 7) / 8) * 8
+ * (*data_size);
+ if (cyapa->electrodes_rx == cyapa->electrodes_x)
+ tmp_count = cyapa->electrodes_y;
+ else
+ tmp_count = cyapa->electrodes_x;
+ max_element_cnt = ((cyapa->electrodes_rx + 7) / 8) *
+ 8 * tmp_count;
+ } else if (idac_data_type == GEN5_RETRIEVE_SELF_CAP_PWC_DATA) {
+ offset = 2;
+ max_element_cnt = cyapa->electrodes_x +
+ cyapa->electrodes_y;
+ }
+ }
+
+ do {
+ read_elements = (256 - 10) / (*data_size);
+ read_elements = min(read_elements, max_element_cnt - count);
+ read_len = read_elements * (*data_size);
+
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ cmd[2] = 0x0a;
+ cmd[3] = 0x00;
+ cmd[4] = GEN5_APP_CMD_REPORT_ID;
+ cmd[5] = 0x00;
+ cmd[6] = cmd_code;
+ put_unaligned_le16(offset, &cmd[7]); /* Read Offset[15:0] */
+ put_unaligned_le16(read_len, &cmd[9]); /* Read Length[15:0] */
+ cmd[11] = idac_data_type;
+ resp_len = 10 + read_len;
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, 12,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+ if (ret || resp_len < 10 || resp_data[2] !=
+ GEN5_APP_RESP_REPORT_ID ||
+ GET_GEN5_CMD_CODE(resp_data[4]) != cmd_code ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]) ||
+ resp_data[6] != idac_data_type)
+ return (ret < 0) ? ret : -EAGAIN;
+ read_len = get_unaligned_le16(&resp_data[7]);
+ if (read_len == 0)
+ break;
+
+ *data_size = (resp_data[9] & GEN5_PWC_DATA_ELEMENT_SIZE_MASK);
+ if (read_len < *data_size)
+ return -EINVAL;
+
+ if (read_global_idac &&
+ idac_data_type == GEN5_RETRIEVE_SELF_CAP_PWC_DATA) {
+ /* Rx's self global idac data. */
+ *idac_max = cyapa_parse_structure_data(
+ resp_data[9], &resp_data[10],
+ *data_size);
+ /* Tx's self global idac data. */
+ *idac_min = cyapa_parse_structure_data(
+ resp_data[9],
+ &resp_data[10 + *data_size],
+ *data_size);
+ break;
+ }
+
+ /* read mutual global idac or local mutual/self PWC data. */
+ offset += read_len;
+ for (i = 10; i < (read_len + 10); i += *data_size) {
+ value = cyapa_parse_structure_data(resp_data[9],
+ &resp_data[i], *data_size);
+ *idac_min = min(value, *idac_min);
+ *idac_max = max(value, *idac_max);
+
+ if (idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA &&
+ tmp_count < tmp_max_elements &&
+ read_global_idac) {
+ tmp_min = min(value, tmp_min);
+ tmp_max = max(value, tmp_max);
+ tmp_sum += value;
+ tmp_count++;
+ }
+
+ sum += value;
+ count++;
+
+ if (count >= max_element_cnt)
+ goto out;
+ }
+ } while (true);
+
+out:
+ *idac_ave = count ? (sum / count) : 0;
+
+ if (read_global_idac &&
+ idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA) {
+ if (tmp_count == 0)
+ return 0;
+ /* algorithm to detect electrodes_rx value. */
+ tmp_ave = tmp_sum / tmp_count;
+ tmp_count = tmp_ave * 15 / 100;
+ if (abs(tmp_ave - *idac_ave) > tmp_count ||
+ (abs(tmp_ave - *idac_min) > (tmp_count * 2) &&
+ *idac_min < tmp_min) ||
+ (abs(*idac_max - tmp_ave) > (tmp_count * 2) &&
+ *idac_max > tmp_max)) {
+ /* overcount the mutual global idac values. */
+ cyapa->electrodes_rx = tmp_max_elements;
+ *idac_min = tmp_min;
+ *idac_max = tmp_max;
+ *idac_ave = tmp_ave;
+ } else
+ cyapa->electrodes_rx = electrodes_rx;
+ }
+
+ return 0;
+}
+
+static int cyapa_gen5_read_mutual_idac_data(struct cyapa *cyapa,
+ int *gidac_mutual_max, int *gidac_mutual_min, int *gidac_mutual_ave,
+ int *lidac_mutual_max, int *lidac_mutual_min, int *lidac_mutual_ave)
+{
+ int ret;
+ int data_size;
+
+ *gidac_mutual_max = *gidac_mutual_min = *gidac_mutual_ave = 0;
+ *lidac_mutual_max = *lidac_mutual_min = *lidac_mutual_ave = 0;
+
+ data_size = 0;
+ ret = cyapa_gen5_read_idac_data(cyapa,
+ GEN5_CMD_RETRIEVE_DATA_STRUCTURE,
+ GEN5_RETRIEVE_MUTUAL_PWC_DATA,
+ &data_size,
+ gidac_mutual_max, gidac_mutual_min, gidac_mutual_ave);
+ if (ret)
+ return ret;
+
+ ret = cyapa_gen5_read_idac_data(cyapa,
+ GEN5_CMD_RETRIEVE_DATA_STRUCTURE,
+ GEN5_RETRIEVE_MUTUAL_PWC_DATA,
+ &data_size,
+ lidac_mutual_max, lidac_mutual_min, lidac_mutual_ave);
+ return ret;
+}
+
+static int cyapa_gen5_read_self_idac_data(struct cyapa *cyapa,
+ int *gidac_self_rx, int *gidac_self_tx,
+ int *lidac_self_max, int *lidac_self_min, int *lidac_self_ave)
+{
+ int ret;
+ int data_size;
+
+ *gidac_self_rx = *gidac_self_tx = 0;
+ *lidac_self_max = *lidac_self_min = *lidac_self_ave = 0;
+
+ data_size = 0;
+ ret = cyapa_gen5_read_idac_data(cyapa,
+ GEN5_CMD_RETRIEVE_DATA_STRUCTURE,
+ GEN5_RETRIEVE_SELF_CAP_PWC_DATA,
+ &data_size,
+ lidac_self_max, lidac_self_min, lidac_self_ave);
+ if (ret)
+ return ret;
+ *gidac_self_rx = *lidac_self_max;
+ *gidac_self_tx = *lidac_self_min;
+
+ ret = cyapa_gen5_read_idac_data(cyapa,
+ GEN5_CMD_RETRIEVE_DATA_STRUCTURE,
+ GEN5_RETRIEVE_SELF_CAP_PWC_DATA,
+ &data_size,
+ lidac_self_max, lidac_self_min, lidac_self_ave);
+ return ret;
+}
+
+static ssize_t cyapa_gen5_execute_panel_scan(struct cyapa *cyapa)
+{
+ int ret;
+ u8 cmd[7];
+ u8 resp_data[6];
+ int resp_len;
+
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ cmd[2] = 0x05;
+ cmd[3] = 0x00;
+ cmd[4] = GEN5_APP_CMD_REPORT_ID;
+ cmd[5] = 0x00;
+ cmd[6] = GEN5_CMD_EXECUTE_PANEL_SCAN; /* command code */
+ resp_len = 6;
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, 7,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+ if (ret || resp_len != 6 ||
+ resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+ GET_GEN5_CMD_CODE(resp_data[4]) !=
+ GEN5_CMD_EXECUTE_PANEL_SCAN ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5])) {
+ cyapa_gen5_resume_scanning(cyapa);
+ return (ret < 0) ? ret : -EAGAIN;
+ }
+
+ return 0;
+}
+
+static int cyapa_gen5_read_panel_scan_raw_data(struct cyapa *cyapa,
+ u8 cmd_code, u8 raw_data_type, int raw_data_max_num,
+ int *raw_data_max, int *raw_data_min, int *raw_data_ave,
+ u8 *buffer)
+{
+ int ret;
+ int i;
+ u8 cmd[12];
+ u8 resp_data[256]; /* max bytes can transfer one time. */
+ int resp_len;
+ int read_elements;
+ int read_len;
+ u16 offset;
+ s32 value;
+ int sum, count;
+ int data_size;
+ s32 *intp;
+
+ if (cmd_code != GEN5_CMD_RETRIEVE_PANEL_SCAN ||
+ (raw_data_type > GEN5_PANEL_SCAN_SELF_DIFFCOUNT) ||
+ !raw_data_max || !raw_data_min || !raw_data_ave)
+ return -EINVAL;
+
+ intp = (s32 *)buffer;
+ *raw_data_max = INT_MIN;
+ *raw_data_min = INT_MAX;
+ sum = count = 0;
+ offset = 0;
+ read_elements = (256 - 10) / 4; /* currently, max element size is 4. */
+ read_len = read_elements * 4;
+ do {
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ cmd[2] = 0x0a;
+ cmd[3] = 0x00;
+ cmd[4] = GEN5_APP_CMD_REPORT_ID;
+ cmd[5] = 0x00;
+ cmd[6] = cmd_code; /* command code */
+ put_unaligned_le16(offset, &cmd[7]);
+ put_unaligned_le16(read_elements, &cmd[9]);
+ cmd[11] = raw_data_type;
+ resp_len = 10 + read_len;
+
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, 12,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+ if (ret || resp_len < 10 ||
+ resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+ (resp_data[4] & 0x7f) != cmd_code ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]) ||
+ resp_data[6] != raw_data_type)
+ return (ret < 0) ? ret : -EAGAIN;
+
+ read_elements = get_unaligned_le16(&resp_data[7]);
+ if (read_elements == 0)
+ break;
+
+ data_size = (resp_data[9] & GEN5_PWC_DATA_ELEMENT_SIZE_MASK);
+ offset += read_elements;
+ if (read_elements) {
+ for (i = 10;
+ i < (read_elements * data_size + 10);
+ i += data_size) {
+ value = cyapa_parse_structure_data(resp_data[9],
+ &resp_data[i], data_size);
+ *raw_data_min = min(value, *raw_data_min);
+ *raw_data_max = max(value, *raw_data_max);
+
+ if (intp)
+ put_unaligned_le32(value, &intp[count]);
+
+ sum += value;
+ count++;
+
+ }
+ }
+
+ if (count >= raw_data_max_num)
+ break;
+
+ read_elements = (sizeof(resp_data) - 10) / data_size;
+ read_len = read_elements * data_size;
+ } while (true);
+
+ *raw_data_ave = count ? (sum / count) : 0;
+
+ return 0;
+}
+
+static ssize_t cyapa_gen5_show_baseline(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ int ret, err;
+ int gidac_mutual_max, gidac_mutual_min, gidac_mutual_ave;
+ int lidac_mutual_max, lidac_mutual_min, lidac_mutual_ave;
+ int gidac_self_rx, gidac_self_tx;
+ int lidac_self_max, lidac_self_min, lidac_self_ave;
+ int raw_cap_mutual_max, raw_cap_mutual_min, raw_cap_mutual_ave;
+ int raw_cap_self_max, raw_cap_self_min, raw_cap_self_ave;
+ int mutual_diffdata_max, mutual_diffdata_min, mutual_diffdata_ave;
+ int self_diffdata_max, self_diffdata_min, self_diffdata_ave;
+ int mutual_baseline_max, mutual_baseline_min, mutual_baseline_ave;
+ int self_baseline_max, self_baseline_min, self_baseline_ave;
+
+ /* 1. suspend Scanning*/
+ ret = cyapa_gen5_suspend_scanning(cyapa);
+ if (ret)
+ return ret;
+
+ /* 2. read global and local mutual IDAC data. */
+ gidac_self_rx = gidac_self_tx = 0;
+ err = cyapa_gen5_read_mutual_idac_data(cyapa,
+ &gidac_mutual_max, &gidac_mutual_min,
+ &gidac_mutual_ave, &lidac_mutual_max,
+ &lidac_mutual_min, &lidac_mutual_ave);
+ if (err)
+ goto resume_scanning;
+
+ /* 3. read global and local self IDAC data. */
+ err = cyapa_gen5_read_self_idac_data(cyapa,
+ &gidac_self_rx, &gidac_self_tx,
+ &lidac_self_max, &lidac_self_min,
+ &lidac_self_ave);
+ if (err)
+ goto resume_scanning;
+
+ /* 4. execuate panel scan. It must be executed before read data. */
+ err = cyapa_gen5_execute_panel_scan(cyapa);
+ if (err)
+ goto resume_scanning;
+
+ /* 5. retrive panel scan, mutual cap raw data. */
+ err = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+ GEN5_CMD_RETRIEVE_PANEL_SCAN,
+ GEN5_PANEL_SCAN_MUTUAL_RAW_DATA,
+ cyapa->electrodes_x * cyapa->electrodes_y,
+ &raw_cap_mutual_max, &raw_cap_mutual_min,
+ &raw_cap_mutual_ave,
+ NULL);
+ if (err)
+ goto resume_scanning;
+
+ /* 6. retrive panel scan, self cap raw data. */
+ err = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+ GEN5_CMD_RETRIEVE_PANEL_SCAN,
+ GEN5_PANEL_SCAN_SELF_RAW_DATA,
+ cyapa->electrodes_x + cyapa->electrodes_y,
+ &raw_cap_self_max, &raw_cap_self_min,
+ &raw_cap_self_ave,
+ NULL);
+ if (err)
+ goto resume_scanning;
+
+ /* 7. retrive panel scan, mutual cap diffcount raw data. */
+ err = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+ GEN5_CMD_RETRIEVE_PANEL_SCAN,
+ GEN5_PANEL_SCAN_MUTUAL_DIFFCOUNT,
+ cyapa->electrodes_x * cyapa->electrodes_y,
+ &mutual_diffdata_max, &mutual_diffdata_min,
+ &mutual_diffdata_ave,
+ NULL);
+ if (err)
+ goto resume_scanning;
+
+ /* 8. retrive panel scan, self cap diffcount raw data. */
+ err = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+ GEN5_CMD_RETRIEVE_PANEL_SCAN,
+ GEN5_PANEL_SCAN_SELF_DIFFCOUNT,
+ cyapa->electrodes_x + cyapa->electrodes_y,
+ &self_diffdata_max, &self_diffdata_min,
+ &self_diffdata_ave,
+ NULL);
+ if (err)
+ goto resume_scanning;
+
+ /* 9. retrive panel scan, mutual cap baseline raw data. */
+ err = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+ GEN5_CMD_RETRIEVE_PANEL_SCAN,
+ GEN5_PANEL_SCAN_MUTUAL_BASELINE,
+ cyapa->electrodes_x * cyapa->electrodes_y,
+ &mutual_baseline_max, &mutual_baseline_min,
+ &mutual_baseline_ave,
+ NULL);
+ if (err)
+ goto resume_scanning;
+
+ /* 10. retrive panel scan, self cap baseline raw data. */
+ err = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+ GEN5_CMD_RETRIEVE_PANEL_SCAN,
+ GEN5_PANEL_SCAN_SELF_BASELINE,
+ cyapa->electrodes_x + cyapa->electrodes_y,
+ &self_baseline_max, &self_baseline_min,
+ &self_baseline_ave,
+ NULL);
+ if (err)
+ goto resume_scanning;
+
+resume_scanning:
+ /* 11. resume Scanning*/
+ ret = cyapa_gen5_resume_scanning(cyapa);
+ if (ret || err)
+ return ret ? ret : err;
+
+ /* 12. output data strings */
+ ret = scnprintf(buf, PAGE_SIZE, "%d %d %d %d %d %d %d %d %d %d %d ",
+ gidac_mutual_min, gidac_mutual_max, gidac_mutual_ave,
+ lidac_mutual_min, lidac_mutual_max, lidac_mutual_ave,
+ gidac_self_rx, gidac_self_tx,
+ lidac_self_min, lidac_self_max, lidac_self_ave);
+ err = scnprintf(buf + ret, PAGE_SIZE - ret,
+ "%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
+ raw_cap_mutual_min, raw_cap_mutual_max, raw_cap_mutual_ave,
+ raw_cap_self_min, raw_cap_self_max, raw_cap_self_ave,
+ mutual_diffdata_min, mutual_diffdata_max, mutual_diffdata_ave,
+ self_diffdata_min, self_diffdata_max, self_diffdata_ave,
+ mutual_baseline_min, mutual_baseline_max, mutual_baseline_ave,
+ self_baseline_min, self_baseline_max, self_baseline_ave);
+ return ret + err;
+}
+
+static int cyapa_gen5_read_raw_data(struct cyapa *cyapa)
+{
+ int ret, err;
+ int raw_cap_mutual_max, raw_cap_mutual_min, raw_cap_mutual_ave;
+ int raw_cap_self_max, raw_cap_self_min, raw_cap_self_ave;
+ int offset;
+ int data_size, max, min, ave;
+ ktime_t time_mono;
+
+ offset = 0;
+ if (!cyapa->tp_raw_data)
+ return -ENOMEM;
+
+ /* 1. suspend Scanning.
+ * After suspend scanning, the raw data will not be updated,
+ * so the time of the raw data is before scanning suspended. */
+ time_mono = ktime_get();
+ ret = cyapa_gen5_suspend_scanning(cyapa);
+ if (ret)
+ return ret;
+
+ /* 2. get the correct electrodes_rx number. */
+ if (cyapa->electrodes_rx == 0) {
+ /* Through the read global idac interface to get the Rx number.
+ * this value is useful to the raw data map.*/
+ data_size = 0;
+ err = cyapa_gen5_read_idac_data(cyapa,
+ GEN5_CMD_RETRIEVE_DATA_STRUCTURE,
+ GEN5_RETRIEVE_MUTUAL_PWC_DATA,
+ &data_size, &max, &min, &ave);
+ if (err || cyapa->electrodes_rx == 0)
+ goto resume_scanning;
+ }
+
+ /* 3. execuate panel scan. It must be executed before read data. */
+ err = cyapa_gen5_execute_panel_scan(cyapa);
+ if (err)
+ goto resume_scanning;
+
+ /* 4. retrive panel scan, mutual cap raw data. */
+ offset = GEN5_RAW_DATA_HEAD_SIZE;
+ err = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+ GEN5_CMD_RETRIEVE_PANEL_SCAN,
+ GEN5_PANEL_SCAN_MUTUAL_DIFFCOUNT,
+ cyapa->electrodes_x * cyapa->electrodes_y,
+ &raw_cap_mutual_max, &raw_cap_mutual_min,
+ &raw_cap_mutual_ave,
+ cyapa->tp_raw_data + offset);
+ if (err)
+ goto resume_scanning;
+
+ offset += sizeof(s32) * cyapa->electrodes_x * cyapa->electrodes_y;
+
+ /* 5. retrive panel scan, self cap raw data. */
+ err = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+ GEN5_CMD_RETRIEVE_PANEL_SCAN,
+ GEN5_PANEL_SCAN_SELF_DIFFCOUNT,
+ cyapa->electrodes_x + cyapa->electrodes_y,
+ &raw_cap_self_max, &raw_cap_self_min,
+ &raw_cap_self_ave,
+ cyapa->tp_raw_data + offset);
+ if (err)
+ goto resume_scanning;
+
+ offset += sizeof(s32) * (cyapa->electrodes_x + cyapa->electrodes_y);
+
+resume_scanning:
+ /* 6. resume Scanning*/
+ ret = cyapa_gen5_resume_scanning(cyapa);
+ if (ret || err)
+ return ret ? ret : err;
+
+ *((struct timeval *)&cyapa->tp_raw_data[0]) =
+ ktime_to_timeval(time_mono);
+ cyapa->tp_raw_data[16] = (u8)cyapa->electrodes_x;
+ cyapa->tp_raw_data[17] = (u8)cyapa->electrodes_y;
+ cyapa->tp_raw_data[18] = (u8)cyapa->x_origin;
+ cyapa->tp_raw_data[19] = (u8)cyapa->y_origin;
+ cyapa->tp_raw_data[20] = (u8)sizeof(s32);
+ cyapa->tp_raw_data[21] = (u8)sizeof(s32);
+ cyapa->tp_raw_data[22] = (u8)cyapa->electrodes_rx;
+ cyapa->tp_raw_data[23] = 0; /* reserved. */
+
+ cyapa->tp_raw_data_size = offset;
+ return 0;
+}
+
static bool cyapa_gen5_sort_system_info_data(struct cyapa *cyapa,
u8 *buf, int len)
{
@@ -3050,19 +4256,19 @@ static int cyapa_check_is_operational(struct cyapa *cyapa)
switch (cyapa->gen) {
case CYAPA_GEN5:
- cyapa->cyapa_check_fw = NULL;
- cyapa->cyapa_bl_enter = NULL;
+ cyapa->cyapa_check_fw = cyapa_gen5_check_fw;
+ cyapa->cyapa_bl_enter = cyapa_gen5_bl_enter;
cyapa->cyapa_bl_activate = NULL;
- cyapa->cyapa_bl_initiate = NULL;
- cyapa->cyapa_update_fw = NULL;
+ cyapa->cyapa_bl_initiate = cyapa_gen5_bl_initiate;
+ cyapa->cyapa_update_fw = cyapa_gen5_do_fw_update;
cyapa->cyapa_bl_verify_app_integrity = NULL;
cyapa->cyapa_bl_deactivate = NULL;
- cyapa->cyapa_show_baseline = NULL;
- cyapa->cyapa_calibrate_store = NULL;
+ cyapa->cyapa_show_baseline = cyapa_gen5_show_baseline;
+ cyapa->cyapa_calibrate_store = cyapa_gen5_do_calibrate;
cyapa->cyapa_irq_handler = cyapa_gen5_irq_handler;
cyapa->cyapa_set_power_mode = cyapa_gen5_set_power_mode;
- cyapa->cyapa_read_fw = NULL;
- cyapa->cyapa_read_raw_data = NULL;
+ cyapa->cyapa_read_fw = cyapa_gen5_read_fw;
+ cyapa->cyapa_read_raw_data = cyapa_gen5_read_raw_data;
cyapa_enable_irq_save(cyapa);
ret = cyapa_gen5_do_operational_check(cyapa);
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.
[-- Attachment #2: winmail.dat --]
[-- Type: application/ms-tnef, Size: 19394 bytes --]
^ permalink raw reply related
* [PATCH 4/6] input: cyapa: enable/disable trackpad device based on LID state
From: Dudley Du @ 2014-04-14 7:54 UTC (permalink / raw)
To: Dmitry Torokhov (dmitry.torokhov@gmail.com)
Cc: Benson Leung, Daniel Kurtz, linux-input@vger.kernel.org,
linux-kernel@vger.kernel.org
[-- Attachment #1: Type: text/plain, Size: 6046 bytes --]
Rely on EV_SW and SW_LID bits to identify a LID device, and hook
up our filter to listen for SW_LID events to enable/disable touchpad when
LID is open/closed.
TEST=test on Chomebooks.
Signed-off-by: Du, Dudley <dudl@cypress.com>
---
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 6820b3f..da03427 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -523,6 +523,9 @@ struct cyapa {
int physical_size_x;
int physical_size_y;
+ bool lid_handler_registered;
+ struct input_handler lid_handler;
+
/* used in ttsp and truetouch based trackpad devices. */
u8 x_origin; /* X Axis Origin: 0 = left side; 1 = rigth side. */
u8 y_origin; /* Y Axis Origin: 0 = top; 1 = bottom. */
@@ -3107,6 +3110,125 @@ static void cyapa_start_runtime(struct cyapa *cyapa)
static void cyapa_start_runtime(struct cyapa *cyapa) {}
#endif /* CONFIG_PM_RUNTIME */
+
+/*
+ * We rely on EV_SW and SW_LID bits to identify a LID device, and hook
+ * up our filter to listen for SW_LID events to enable/disable touchpad when
+ * LID is open/closed.
+ */
+static const struct input_device_id lid_device_ids[] = {
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
+ INPUT_DEVICE_ID_MATCH_SWBIT,
+ .evbit = { BIT_MASK(EV_SW) },
+ .swbit = { BIT_MASK(SW_LID) },
+ },
+ { },
+};
+
+static int lid_device_connect(struct input_handler *handler,
+ struct input_dev *dev,
+ const struct input_device_id *id)
+{
+ struct input_handle *lid_handle;
+ int error;
+
+ lid_handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
+ if (!lid_handle)
+ return -ENOMEM;
+
+ lid_handle->dev = dev;
+ lid_handle->handler = handler;
+ lid_handle->name = "lid_event_handler";
+ lid_handle->private = handler->private;
+
+ error = input_register_handle(lid_handle);
+ if (error)
+ goto err_free;
+
+ error = input_open_device(lid_handle);
+ if (error)
+ goto err_unregister;
+
+ return 0;
+err_unregister:
+ input_unregister_handle(lid_handle);
+err_free:
+ kfree(lid_handle);
+ return error;
+}
+
+static void lid_device_disconnect(struct input_handle *handle)
+{
+ input_close_device(handle);
+ input_unregister_handle(handle);
+ kfree(handle);
+}
+
+static bool lid_event_filter(struct input_handle *handle,
+ unsigned int type, unsigned int code, int value)
+{
+ struct cyapa *cyapa = handle->private;
+ struct device *dev = &cyapa->client->dev;
+
+ if (type == EV_SW && code == SW_LID) {
+ if (cyapa->suspended) {
+ /*
+ * If the lid event filter is called while suspended,
+ * there is no guarantee that the underlying i2cs are
+ * resumed at this point, so it is not safe to issue
+ * the command to change power modes.
+ * Instead, rely on cyapa_resume to set us back to
+ * PWR_MODE_FULL_ACTIVE.
+ */
+ return false;
+ }
+ if (value == 0) {
+ if (cyapa->cyapa_set_power_mode)
+ cyapa->cyapa_set_power_mode(cyapa,
+ PWR_MODE_FULL_ACTIVE, 0);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ } else {
+ pm_runtime_disable(dev);
+ if (cyapa->cyapa_set_power_mode)
+ cyapa->cyapa_set_power_mode(cyapa,
+ PWR_MODE_OFF, 0);
+ }
+ }
+
+ return false;
+}
+
+static void lid_event_register_handler(struct cyapa *cyapa)
+{
+ int error;
+ struct input_handler *lid_handler = &cyapa->lid_handler;
+
+ if (cyapa->lid_handler_registered)
+ return;
+
+ lid_handler->filter = lid_event_filter;
+ lid_handler->connect = lid_device_connect;
+ lid_handler->disconnect = lid_device_disconnect;
+ lid_handler->name = "cyapa_lid_event_handler";
+ lid_handler->id_table = lid_device_ids;
+ lid_handler->private = cyapa;
+
+ error = input_register_handler(lid_handler);
+ if (error)
+ return;
+ cyapa->lid_handler_registered = true;
+}
+
+static void lid_event_unregister_handler(struct cyapa *cyapa)
+{
+ if (cyapa->lid_handler_registered) {
+ input_unregister_handler(&cyapa->lid_handler);
+ cyapa->lid_handler_registered = false;
+ }
+}
+
static void cyapa_detect_async(void *data, async_cookie_t cookie)
{
struct cyapa *cyapa = (struct cyapa *)data;
@@ -3126,6 +3248,7 @@ static void cyapa_detect_and_start(void *data, async_cookie_t cookie)
cyapa_detect_async(data, cookie);
cyapa_start_runtime(cyapa);
+ lid_event_register_handler(cyapa);
}
static int cyapa_probe(struct i2c_client *client,
@@ -3221,7 +3344,7 @@ static int cyapa_remove(struct i2c_client *client)
free_irq(cyapa->irq, cyapa);
input_unregister_device(cyapa->input);
-
+ lid_event_unregister_handler(cyapa);
if (cyapa->cyapa_set_power_mode)
cyapa->cyapa_set_power_mode(cyapa, PWR_MODE_OFF, 0);
i2c_set_clientdata(client, NULL);
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.
[-- Attachment #2: winmail.dat --]
[-- Type: application/ms-tnef, Size: 5984 bytes --]
^ permalink raw reply related
* [PATCH 3/6] input: cyapa: add power mode sleep and runtime power mode supported.
From: Dudley Du @ 2014-04-14 7:54 UTC (permalink / raw)
To: Dmitry Torokhov (dmitry.torokhov@gmail.com)
Cc: Benson Leung, Daniel Kurtz, linux-input@vger.kernel.org,
linux-kernel@vger.kernel.org
[-- Attachment #1: Type: text/plain, Size: 11211 bytes --]
In order to save power when the trackpad device is not used, the sleep power
mode and runtime power mode must be supported. And the enter sleep time can
be configured in the sysfs system.
TEST=test on Chomebooks.
Signed-off-by: Du, Dudley <dudl@cypress.com>
---
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 7b269d8..6820b3f 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -27,6 +27,7 @@
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/unaligned/access_ok.h>
+#include <linux/pm_runtime.h>
/* APA trackpad firmware generation */
#define CYAPA_GEN_UNKNOWN 0x00 /* unknown protocol. */
@@ -505,6 +506,10 @@ struct cyapa {
u16 suspend_sleep_time;
u8 real_power_mode;
u16 real_sleep_time;
+#ifdef CONFIG_PM_RUNTIME
+ u8 runtime_suspend_power_mode;
+ u16 runtime_suspend_sleep_time;
+#endif /* CONFIG_PM_RUNTIME */
bool suspended;
/* read from query data region. */
@@ -1873,6 +1878,13 @@ static int cyapa_gen5_bl_exit(struct cyapa *cyapa)
return -EAGAIN;
}
+static int cyapa_gen5_sleep_time_check(u16 sleep_time)
+{
+ if (sleep_time > 1000)
+ sleep_time = 1000;
+ return sleep_time;
+}
+
static int cyapa_gen5_change_power_state(struct cyapa *cyapa, u8 power_state)
{
int ret;
@@ -2571,6 +2583,9 @@ static irqreturn_t cyapa_irq(int irq, void *dev_id)
struct input_dev *input = cyapa->input;
int length;
+ pm_runtime_get_sync(dev);
+ pm_runtime_mark_last_busy(dev);
+
if (device_may_wakeup(dev))
pm_wakeup_event(dev, 0);
@@ -2646,6 +2661,7 @@ static irqreturn_t cyapa_irq(int irq, void *dev_id)
if (cyapa->cyapa_irq_handler)
cyapa->cyapa_irq_handler(cyapa);
+ pm_runtime_put_sync_autosuspend(dev);
out:
return IRQ_HANDLED;
@@ -2945,6 +2961,152 @@ static void cyapa_detect(struct cyapa *cyapa)
cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
}
+
+/*
+ * Sysfs Interface.
+ */
+
+#ifdef CONFIG_PM_SLEEP
+static ssize_t cyapa_show_suspend_scanrate(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ u8 pwr_cmd = cyapa->suspend_power_mode;
+ u16 sleep_time;
+ int len;
+
+ if (pwr_cmd == PWR_MODE_BTN_ONLY)
+ len = scnprintf(buf, PAGE_SIZE, "%s\n", BTN_ONLY_MODE_NAME);
+ else if (pwr_cmd == PWR_MODE_OFF)
+ len = scnprintf(buf, PAGE_SIZE, "%s\n", OFF_MODE_NAME);
+ else {
+ if (cyapa->gen == CYAPA_GEN3)
+ sleep_time = cyapa_pwr_cmd_to_sleep_time(pwr_cmd);
+ else
+ sleep_time = cyapa->suspend_sleep_time;
+ len = scnprintf(buf, PAGE_SIZE, "%u\n", sleep_time);
+ }
+
+ return len;
+}
+
+static ssize_t cyapa_update_suspend_scanrate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ u16 sleep_time;
+
+ if (buf == NULL || count == 0)
+ goto invalidparam;
+
+ if (sysfs_streq(buf, BTN_ONLY_MODE_NAME))
+ cyapa->suspend_power_mode = PWR_MODE_BTN_ONLY;
+ else if (sysfs_streq(buf, OFF_MODE_NAME))
+ cyapa->suspend_power_mode = PWR_MODE_OFF;
+ else if (!kstrtou16(buf, 10, &sleep_time)) {
+ cyapa->suspend_power_mode =
+ cyapa_sleep_time_to_pwr_cmd(sleep_time);
+ if (cyapa->gen > CYAPA_GEN3)
+ cyapa->suspend_sleep_time =
+ cyapa_gen5_sleep_time_check(sleep_time);
+ } else
+ goto invalidparam;
+
+ return count;
+
+invalidparam:
+ dev_err(dev, "invalid suspend scanrate ms parameters\n");
+ return -EINVAL;
+}
+
+static DEVICE_ATTR(suspend_scanrate_ms, S_IRUGO|S_IWUSR,
+ cyapa_show_suspend_scanrate,
+ cyapa_update_suspend_scanrate);
+
+static struct attribute *cyapa_power_wakeup_entries[] = {
+ &dev_attr_suspend_scanrate_ms.attr,
+ NULL,
+};
+
+static const struct attribute_group cyapa_power_wakeup_group = {
+ .name = power_group_name,
+ .attrs = cyapa_power_wakeup_entries,
+};
+#endif /* CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_PM_RUNTIME
+static ssize_t cyapa_show_rt_suspend_scanrate(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ u8 pwr_cmd = cyapa->runtime_suspend_power_mode;
+
+ if (cyapa->gen == CYAPA_GEN3)
+ return scnprintf(buf, PAGE_SIZE, "%u\n",
+ cyapa_pwr_cmd_to_sleep_time(pwr_cmd));
+ else
+ return scnprintf(buf, PAGE_SIZE, "%u\n",
+ cyapa->runtime_suspend_sleep_time);
+}
+
+static ssize_t cyapa_update_rt_suspend_scanrate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ u16 time;
+
+ if (buf == NULL || count == 0 || kstrtou16(buf, 10, &time)) {
+ dev_err(dev, "invalid runtime suspend scanrate ms parameter\n");
+ return -EINVAL;
+ }
+
+ /*
+ * When the suspend scanrate is changed, pm_runtime_get to resume
+ * a potentially suspended device, update to the new pwr_cmd
+ * and then pm_runtime_put to suspend into the new power mode.
+ */
+ pm_runtime_get_sync(dev);
+ cyapa->runtime_suspend_power_mode = cyapa_sleep_time_to_pwr_cmd(time);
+ if (cyapa->gen > CYAPA_GEN3)
+ cyapa->runtime_suspend_sleep_time = time;
+ pm_runtime_put_sync_autosuspend(dev);
+ return count;
+}
+
+static DEVICE_ATTR(runtime_suspend_scanrate_ms, S_IRUGO|S_IWUSR,
+ cyapa_show_rt_suspend_scanrate,
+ cyapa_update_rt_suspend_scanrate);
+
+static struct attribute *cyapa_power_runtime_entries[] = {
+ &dev_attr_runtime_suspend_scanrate_ms.attr,
+ NULL,
+};
+
+static const struct attribute_group cyapa_power_runtime_group = {
+ .name = power_group_name,
+ .attrs = cyapa_power_runtime_entries,
+};
+
+static void cyapa_start_runtime(struct cyapa *cyapa)
+{
+ struct device *dev = &cyapa->client->dev;
+
+ cyapa->runtime_suspend_power_mode = PWR_MODE_IDLE;
+ if (sysfs_merge_group(&dev->kobj, &cyapa_power_runtime_group))
+ dev_warn(dev, "error creating wakeup runtime entries.\n");
+ pm_runtime_set_active(dev);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_autosuspend_delay(dev, AUTOSUSPEND_DELAY);
+ pm_runtime_enable(dev);
+}
+#else
+static void cyapa_start_runtime(struct cyapa *cyapa) {}
+#endif /* CONFIG_PM_RUNTIME */
+
static void cyapa_detect_async(void *data, async_cookie_t cookie)
{
struct cyapa *cyapa = (struct cyapa *)data;
@@ -2957,6 +3119,15 @@ static void cyapa_detect_async(void *data, async_cookie_t cookie)
atomic_dec(&cyapa->in_detecting);
}
+static void cyapa_detect_and_start(void *data, async_cookie_t cookie)
+{
+ struct cyapa *cyapa = data;
+
+ cyapa_detect_async(data, cookie);
+
+ cyapa_start_runtime(cyapa);
+}
+
static int cyapa_probe(struct i2c_client *client,
const struct i2c_device_id *dev_id)
{
@@ -3017,7 +3188,13 @@ static int cyapa_probe(struct i2c_client *client,
}
cyapa_disable_irq(cyapa);
- async_schedule(cyapa_detect_async, cyapa);
+#ifdef CONFIG_PM_SLEEP
+ if (device_can_wakeup(dev) &&
+ sysfs_merge_group(&client->dev.kobj, &cyapa_power_wakeup_group))
+ dev_warn(dev, "error creating wakeup power entries.\n");
+#endif /* CONFIG_PM_SLEEP */
+
+ async_schedule(cyapa_detect_and_start, cyapa);
return 0;
err_unregister_device:
@@ -3032,6 +3209,16 @@ static int cyapa_remove(struct i2c_client *client)
{
struct cyapa *cyapa = i2c_get_clientdata(client);
+ pm_runtime_disable(&client->dev);
+
+#ifdef CONFIG_PM_SLEEP
+ sysfs_unmerge_group(&client->dev.kobj, &cyapa_power_wakeup_group);
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+ sysfs_unmerge_group(&client->dev.kobj, &cyapa_power_runtime_group);
+#endif
+
free_irq(cyapa->irq, cyapa);
input_unregister_device(cyapa->input);
@@ -3090,12 +3277,56 @@ static int cyapa_resume(struct device *dev)
async_schedule(cyapa_detect_async, cyapa);
+ /* runtime set active to reflect active state. */
+ pm_runtime_disable(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
cyapa->suspended = false;
return 0;
}
#endif /* CONFIG_PM_SLEEP */
-static SIMPLE_DEV_PM_OPS(cyapa_pm_ops, cyapa_suspend, cyapa_resume);
+#ifdef CONFIG_PM_RUNTIME
+static int cyapa_runtime_suspend(struct device *dev)
+{
+ int ret;
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+
+ if (cyapa->cyapa_set_power_mode) {
+ /* set trackpad device to idle mode */
+ ret = cyapa->cyapa_set_power_mode(cyapa,
+ cyapa->runtime_suspend_power_mode,
+ cyapa->runtime_suspend_sleep_time);
+ if (ret)
+ dev_err(dev, "runtime suspend failed, %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cyapa_runtime_resume(struct device *dev)
+{
+ int ret;
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+
+ if (cyapa->cyapa_set_power_mode) {
+ /* resume to full active mode */
+ ret = cyapa->cyapa_set_power_mode(cyapa,
+ PWR_MODE_FULL_ACTIVE, 0);
+ if (ret)
+ dev_err(dev, "runtime resume failed, %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+#endif /* CONFIG_PM_RUNTIME */
+
+static const struct dev_pm_ops cyapa_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(cyapa_suspend, cyapa_resume)
+ SET_RUNTIME_PM_OPS(cyapa_runtime_suspend, cyapa_runtime_resume, NULL)
+};
static const struct i2c_device_id cyapa_id_table[] = {
{ "cyapa", 0 },
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.
[-- Attachment #2: winmail.dat --]
[-- Type: application/ms-tnef, Size: 7477 bytes --]
^ permalink raw reply related
* [PATCH 2/6] input: cyapa: add gen5 trackpad device supported in one driver
From: Dudley Du @ 2014-04-14 7:54 UTC (permalink / raw)
To: Dmitry Torokhov (dmitry.torokhov@gmail.com)
Cc: Benson Leung, Daniel Kurtz, linux-input@vger.kernel.org,
linux-kernel@vger.kernel.org
[-- Attachment #1: Type: text/plain, Size: 77012 bytes --]
Add variable, macros, fucntions and interfaces to support gen5 trackpad device,
The gen5 trackpad device is different protocol with gen3 trackpad device.
And in order to keep compatible with old products and easy customer support,
these two type devices must be integrated and supported in one driver.
So this cyapa driver is rearchitecture to support multi-type devices.
TEST=test on Chomebooks.
Signed-off-by: Du, Dudley <dudl@cypress.com>
---
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 4361ee1..7b269d8 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -22,12 +22,16 @@
#include <linux/input/mt.h>
#include <linux/interrupt.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/completion.h>
#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/unaligned/access_ok.h>
/* APA trackpad firmware generation */
#define CYAPA_GEN_UNKNOWN 0x00 /* unknown protocol. */
#define CYAPA_GEN3 0x03 /* support MT-protocol B with tracking ID. */
+#define CYAPA_GEN5 0x05 /* support TrueTouch GEN5 trackpad device. */
#define CYAPA_NAME "Cypress APA Trackpad (cyapa)"
@@ -178,6 +182,144 @@
#define MAX_TMP_BUF_SIZE (CYAPA_REG_MAP_SIZE)
+/* mcros of Gen5 */
+#define RECORD_EVENT_NONE 0
+#define RECORD_EVENT_TOUCHDOWN 1
+#define RECORD_EVENT_DISPLACE 2
+#define RECORD_EVENT_LIFTOFF 3
+
+#define CYAPA_TSG_FLASH_MAP_BLOCK_SIZE 0x80
+#define CYAPA_TSG_IMG_FW_HDR_SIZE 13
+#define CYAPA_TSG_FW_ROW_SIZE (CYAPA_TSG_FLASH_MAP_BLOCK_SIZE)
+#define CYAPA_TSG_IMG_START_ROW_NUM 0x002e
+#define CYAPA_TSG_IMG_END_ROW_NUM 0x01fe
+#define CYAPA_TSG_IMG_APP_INTEGRITY_ROW_NUM 0x01ff
+#define CYAPA_TSG_IMG_MAX_RECORDS (CYAPA_TSG_IMG_END_ROW_NUM - \
+ CYAPA_TSG_IMG_START_ROW_NUM + 1 + 1)
+#define CYAPA_TSG_IMG_READ_SIZE (CYAPA_TSG_FLASH_MAP_BLOCK_SIZE / 2)
+#define CYAPA_TSG_START_OF_APPLICATION 0x1700
+#define CYAPA_TSG_APP_INTEGRITY_SIZE 60
+#define CYAPA_TSG_FLASH_MAP_METADATA_SIZE 60
+#define CYAPA_TSG_BL_KEY_SIZE 8
+
+/* Macro definitions for Gen5 trackpad device. */
+#define GEN5_TOUCH_REPORT_HEAD_SIZE 7
+#define GEN5_TOUCH_REPORT_MAX_SIZE 127
+#define GEN5_BTN_REPORT_HEAD_SIZE 6
+#define GEN5_BTN_REPORT_MAX_SIZE 14
+#define GEN5_WAKEUP_EVENT_SIZE 4
+#define GEN5_RAW_DATA_HEAD_SIZE 24
+
+#define GEN5_BL_CMD_REPORT_ID 0x40
+#define GEN5_BL_RESP_REPORT_ID 0x30
+#define GEN5_APP_CMD_REPORT_ID 0x2f
+#define GEN5_APP_RESP_REPORT_ID 0x1f
+
+#define GEN5_APP_DEEP_SLEEP_REPORT_ID 0xf0
+#define GEN5_DEEP_SLEEP_RESP_LENGTH 5
+
+#define GEN5_PARAMETER_ACT_INTERVL_ID 0x4d
+#define GEN5_PARAMETER_ACT_INTERVL_SIZE 1
+#define GEN5_PARAMETER_ACT_LFT_INTERVL_ID 0x4f
+#define GEN5_PARAMETER_ACT_LFT_INTERVL_SIZE 2
+#define GEN5_PARAMETER_LP_INTRVL_ID 0x4c
+#define GEN5_PARAMETER_LP_INTRVL_SIZE 2
+
+#define GEN5_PARAMETER_DISABLE_PIP_REPORT 0x08
+
+#define GEN5_POWER_STATE_ACTIVE 0x01
+#define GEN5_POWER_STATE_LOOK_FOR_TOUCH 0x02
+#define GEN5_POWER_STATE_READY 0x03
+#define GEN5_POWER_STATE_IDLE 0x04
+#define GEN5_POWER_STATE_BTN_ONLY 0x05
+#define GEN5_POWER_STATE_OFF 0x06
+
+#define GEN5_DEEP_SLEEP_STATE_MASK 0x03
+#define GEN5_DEEP_SLEEP_STATE_ON 0x00
+#define GEN5_DEEP_SLEEP_STATE_OFF 0x01
+
+#define GEN5_DEEP_SLEEP_OPCODE 0x08
+#define GEN5_DEEP_SLEEP_OPCODE_MASK 0x0f
+
+#define GEN5_POWER_READY_MAX_INTRVL_TIME 50 /* unit: ms */
+#define GEN5_POWER_IDLE_MAX_INTRVL_TIME 250 /* unit: ms */
+
+#define GEN5_CMD_REPORT_ID_OFFSET 4
+
+#define GEN5_RESP_REPORT_ID_OFFSET 2
+#define GEN5_RESP_RSVD_OFFSET 3
+#define GEN5_RESP_RSVD_KEY 0x00
+#define GEN5_RESP_BL_SOP_OFFSET 4
+#define GEN5_SOP_KEY 0x01 /* Start of Packet */
+#define GEN5_EOP_KEY 0x17 /* End of Packet */
+#define GEN5_RESP_APP_CMD_OFFSET 4
+#define GET_GEN5_CMD_CODE(reg) ((reg) & 0x7f)
+
+#define GEN5_MIN_BL_CMD_LENGTH 13
+#define GEN5_MIN_BL_RESP_LENGTH 11
+#define GEN5_MIN_APP_CMD_LENGTH 7
+#define GEN5_MIN_APP_RESP_LENGTH 5
+#define GEN5_UNSUPPORTED_CMD_RESP_LENGTH 6
+
+#define GEN5_RESP_LENGTH_SIZE 2
+
+#define GEN5_HID_DESCRIPTOR_SIZE 32
+#define GEN5_BL_HID_REPORT_ID 0xff
+#define GEN5_APP_HID_REPORT_ID 0xf7
+#define GEN5_BL_MAX_OUTPUT_LENGTH 0x0100
+#define GEN5_APP_MAX_OUTPUT_LENGTH 0x00fe
+
+#define GEN5_BL_REPORT_DESCRIPTOR_SIZE 0x1d
+#define GEN5_BL_REPORT_DESCRIPTOR_ID 0xfe
+#define GEN5_APP_REPORT_DESCRIPTOR_SIZE 0xee
+#define GEN5_APP_CONTRACT_REPORT_DESCRIPTOR_SIZE 0xfa
+#define GEN5_APP_REPORT_DESCRIPTOR_ID 0xf6
+
+#define GEN5_TOUCH_REPORT_ID 0x01
+#define GEN5_BTN_REPORT_ID 0x03
+#define GEN5_WAKEUP_EVENT_REPORT_ID 0x04
+#define GEN5_OLD_PUSH_BTN_REPORT_ID 0x05
+#define GEN5_PUSH_BTN_REPORT_ID 0x06
+
+#define GEN5_CMD_COMPLETE_SUCCESS(status) ((status) == 0x00)
+
+#define GEN5_BL_INITIATE_RESP_LEN 11
+#define GEN5_BL_FAIL_EXIT_RESP_LEN 11
+#define GEN5_BL_FAIL_EXIT_STATUS_CODE 0x0c
+#define GEN5_BL_VERIFY_INTEGRITY_RESP_LEN 12
+#define GEN5_BL_INTEGRITY_CHEKC_PASS 0x00
+#define GEN5_BL_BLOCK_WRITE_RESP_LEN 11
+#define GEN5_BL_READ_APP_INFO_RESP_LEN 31
+#define GEN5_CMD_CALIBRATE 0x28
+#define CYAPA_SENSING_MODE_MUTUAL_CAP_FINE 0x00
+#define CYAPA_SENSING_MODE_SELF_CAP 0x02
+
+#define GEN5_CMD_RETRIEVE_DATA_STRUCTURE 0x24
+#define GEN5_RETRIEVE_MUTUAL_PWC_DATA 0x00
+#define GEN5_RETRIEVE_SELF_CAP_PWC_DATA 0x01
+
+#define GEN5_RETRIEVE_DATA_ELEMENT_SIZE_MASK 0x07
+
+#define GEN5_CMD_EXECUTE_PANEL_SCAN 0x2a
+#define GEN5_CMD_RETRIEVE_PANEL_SCAN 0x2b
+#define GEN5_PANEL_SCAN_MUTUAL_RAW_DATA 0x00
+#define GEN5_PANEL_SCAN_MUTUAL_BASELINE 0x01
+#define GEN5_PANEL_SCAN_MUTUAL_DIFFCOUNT 0x02
+#define GEN5_PANEL_SCAN_SELF_RAW_DATA 0x03
+#define GEN5_PANEL_SCAN_SELF_BASELINE 0x04
+#define GEN5_PANEL_SCAN_SELF_DIFFCOUNT 0x05
+
+#define GEN5_PWC_DATA_ELEMENT_SIZE_MASK 0x07
+
+#define GEN5_NUMBER_OF_TOUCH_MASK 0x1f
+#define GEN5_GET_EVENT_ID(reg) (((reg) >> 5) & 0x03)
+#define GEN5_GET_TOUCH_ID(reg) ((reg) & 0x1f)
+
+#define GEN5_PRODUCT_FAMILY_MASK 0xf000
+#define GEN5_PRODUCT_FAMILY_TRACKPAD 0x1000
+
+#define TSG_INVALID_CMD 0xff
+
struct cyapa;
typedef void (*irq_handler_func)(struct cyapa *);
typedef int (*set_power_mode_func)(struct cyapa *, u8, u16);
@@ -200,6 +342,8 @@ enum cyapa_state {
CYAPA_STATE_OP,
CYAPA_STATE_BL_IDLE,
CYAPA_STATE_BL_ACTIVE,
+ CYAPA_STATE_GEN5_BL,
+ CYAPA_STATE_GEN5_APP,
CYAPA_STATE_BL_BUSY,
CYAPA_STATE_NO_DEVICE,
};
@@ -242,6 +386,109 @@ struct cyapa_reg_data {
struct cyapa_touch touches[5];
} __packed;
+struct cyapa_gen5_touch_record {
+ /*
+ * bit 7 - 3: reserved
+ * bit 2 - 0: touch type;
+ * 0 : standard finger;
+ * 1 - 15 : reserved.
+ */
+ u8 touch_type;
+
+ /*
+ * bit 7: indicates touch liftoff status.
+ * 0 : touch is currently on the panel.
+ * 1 : touch record indicates a liftoff.
+ * bit 6 - 5: indicates an event associated with this touch instance
+ * 0 : no event
+ * 1 : touchdown
+ * 2 : significant displacement (> active distance)
+ * 3 : liftoff (record reports last known coordinates)
+ * bit 4 - 0: An arbitrary ID tag associated with a finger
+ * to alow tracking a touch as it moves around the panel.
+ */
+ u8 touch_tip_event_id;
+
+ /* bit 7 - 0 of X-axis corrinate of the touch in pixel. */
+ u8 x_lo;
+
+ /* bit 15 - 8 of X-axis corrinate of the touch in pixel. */
+ u8 x_hi;
+
+ /* bit 7 - 0 of Y-axis corrinate of the touch in pixel. */
+ u8 y_lo;
+
+ /* bit 15 - 8 of Y-axis corrinate of the touch in pixel. */
+ u8 y_hi;
+
+ /* touch intensity in counts, pressure value. */
+ u8 z;
+
+ /*
+ * The length of the major axis of the ellipse of contact between
+ * the finger and the panel (ABS_MT_TOUCH_MAJOR).
+ */
+ u8 major_axis_len;
+
+ /*
+ * The length of the minor axis of the ellipse of contact between
+ * the finger and the panel (ABS_MT_TOUCH_MINOR).
+ */
+ u8 minor_axis_len;
+
+ /*
+ * The length of the major axis of the approaching tool.
+ * (ABS_MT_WIDTH_MAJOR)
+ */
+ u8 major_tool_len;
+
+ /*
+ * The length of the minor axis of the approaching tool.
+ * (ABS_MT_WIDTH_MINOR)
+ */
+ u8 minor_tool_len;
+
+ /*
+ * The angle between the panel vertical axis and
+ * the major axis of the contact ellipse. This value is an 8-bit
+ * signed integer. The range is -127 to +127 (corresponding to
+ * -90 degree and +90 degree respectively).
+ * The positive direction is clockwise from the vertical axis.
+ * If the ellipse of contact degenerates into a circle,
+ * orientation is reported as 0.
+ */
+ u8 orientation;
+} __packed;
+
+
+struct cyapa_gen5_report_data {
+ u8 report_head[GEN5_TOUCH_REPORT_HEAD_SIZE];
+ struct cyapa_gen5_touch_record touch_records[10];
+} __packed;
+
+struct cyapa_tsg_bin_image_head {
+ u8 head_size; /* in bytes, including itself. */
+ u8 ttda_driver_major_version; /* reserved as 0. */
+ u8 ttda_driver_minor_version; /* reserved as 0. */
+ u8 fw_major_version;
+ u8 fw_minor_version;
+ u8 fw_revision_control_number[8];
+} __packed;
+
+struct cyapa_tsg_bin_image_data_record {
+ u8 flash_array_id;
+ __be16 row_number;
+ /* the number of bytes of flash data contained in this record. */
+ __be16 record_len;
+ /* the flash program data. */
+ u8 record_data[CYAPA_TSG_FW_ROW_SIZE];
+} __packed;
+
+struct cyapa_tsg_bin_image {
+ struct cyapa_tsg_bin_image_head image_head;
+ struct cyapa_tsg_bin_image_data_record records[0];
+} __packed;
+
/* The main device structure */
struct cyapa {
enum cyapa_state state;
@@ -256,6 +503,8 @@ struct cyapa {
/* power mode settings */
u8 suspend_power_mode;
u16 suspend_sleep_time;
+ u8 real_power_mode;
+ u16 real_sleep_time;
bool suspended;
/* read from query data region. */
@@ -269,10 +518,33 @@ struct cyapa {
int physical_size_x;
int physical_size_y;
- u8 gen_detecting;
+ /* used in ttsp and truetouch based trackpad devices. */
+ u8 x_origin; /* X Axis Origin: 0 = left side; 1 = rigth side. */
+ u8 y_origin; /* Y Axis Origin: 0 = top; 1 = bottom. */
+ int electrodes_x; /* Number of electrodes on the X Axis*/
+ int electrodes_y; /* Number of electrodes on the Y Axis*/
+ int electrodes_rx; /* Number of Rx electrodes */
+ int max_z;
+ u8 gen_detecting;
+ u8 in_progress_cmd;
+ func_sort resp_sort_func;
+ u8 *resp_data;
+ int *resp_len;
+
+ /* trackpad is ready for next command */
+ struct completion cmd_ready;
+ atomic_t cmd_issued;
atomic_t in_detecting;
+ /* record irq disabled/enable state. */
+ struct mutex irq_state_lock;
+ bool irq_enabled;
+ bool prev_irq_enabled;
+ struct mutex cmd_lock;
+
+ /* temple buffer to read all data out. */
+ u8 tmp_irq_buf[MAX_TMP_BUF_SIZE];
u8 tmp_buf[MAX_TMP_BUF_SIZE];
check_fw_func cyapa_check_fw;
@@ -288,6 +560,8 @@ struct cyapa {
set_power_mode_func cyapa_set_power_mode;
bl_read_fw_func cyapa_read_fw;
read_raw_data_func cyapa_read_raw_data;
+
+ struct cyapa_tsg_bin_image_head fw_img_head;
};
static const u8 bl_deactivate[] = { 0x00, 0xff, 0x3b, 0x00, 0x01, 0x02, 0x03,
@@ -392,6 +666,71 @@ static int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout);
static void cyapa_detect(struct cyapa *cyapa);
static void cyapa_detect_async(void *data, async_cookie_t cookie);
+void cyapa_enable_irq(struct cyapa *cyapa)
+{
+ mutex_lock(&cyapa->irq_state_lock);
+ if (!cyapa->irq_enabled)
+ enable_irq(cyapa->irq);
+ cyapa->irq_enabled = true;
+ cyapa->prev_irq_enabled = true;
+ mutex_unlock(&cyapa->irq_state_lock);
+}
+
+void cyapa_disable_irq(struct cyapa *cyapa)
+{
+ mutex_lock(&cyapa->irq_state_lock);
+ if (cyapa->irq_enabled)
+ disable_irq(cyapa->irq);
+ cyapa->irq_enabled = false;
+ cyapa->prev_irq_enabled = false;
+ mutex_unlock(&cyapa->irq_state_lock);
+}
+
+void cyapa_enable_irq_save(struct cyapa *cyapa)
+{
+ mutex_lock(&cyapa->irq_state_lock);
+ if (!cyapa->irq_enabled) {
+ enable_irq(cyapa->irq);
+ cyapa->irq_enabled = true;
+ }
+ mutex_unlock(&cyapa->irq_state_lock);
+}
+
+void cyapa_disable_irq_save(struct cyapa *cyapa)
+{
+ mutex_lock(&cyapa->irq_state_lock);
+ if (cyapa->irq_enabled) {
+ disable_irq(cyapa->irq);
+ cyapa->irq_enabled = false;
+ }
+ mutex_unlock(&cyapa->irq_state_lock);
+}
+
+void cyapa_irq_restore(struct cyapa *cyapa)
+{
+ mutex_lock(&cyapa->irq_state_lock);
+ if (cyapa->irq_enabled != cyapa->prev_irq_enabled) {
+ if (cyapa->prev_irq_enabled) {
+ enable_irq(cyapa->irq);
+ cyapa->irq_enabled = true;
+ } else {
+ disable_irq(cyapa->irq);
+ cyapa->irq_enabled = false;
+ }
+ }
+ mutex_unlock(&cyapa->irq_state_lock);
+}
+
+bool cyapa_is_irq_enabled(struct cyapa *cyapa)
+{
+ bool enabled;
+
+ mutex_lock(&cyapa->irq_state_lock);
+ enabled = cyapa->irq_enabled;
+ mutex_unlock(&cyapa->irq_state_lock);
+ return enabled;
+}
+
static ssize_t cyapa_i2c_reg_read_block(struct cyapa *cyapa, u8 reg, size_t len,
u8 *values)
{
@@ -603,6 +942,33 @@ static int cyapa_gen3_bl_exit(struct cyapa *cyapa)
return 0;
}
+/*
+ * cyapa_sleep_time_to_pwr_cmd and cyapa_pwr_cmd_to_sleep_time
+ *
+ * These are helper functions that convert to and from integer idle
+ * times and register settings to write to the PowerMode register.
+ * The trackpad supports between 20ms to 1000ms scan intervals.
+ * The time will be increased in increments of 10ms from 20ms to 100ms.
+ * From 100ms to 1000ms, time will be increased in increments of 20ms.
+ *
+ * When Idle_Time < 100, the format to convert Idle_Time to Idle_Command is:
+ * Idle_Command = Idle Time / 10;
+ * When Idle_Time >= 100, the format to convert Idle_Time to Idle_Command is:
+ * Idle_Command = Idle Time / 20 + 5;
+ */
+static u8 cyapa_sleep_time_to_pwr_cmd(u16 sleep_time)
+{
+ if (sleep_time < 20)
+ sleep_time = 20; /* minimal sleep time. */
+ else if (sleep_time > 1000)
+ sleep_time = 1000; /* maximal sleep time. */
+
+ if (sleep_time < 100)
+ return ((sleep_time / 10) << 2) & PWR_MODE_MASK;
+ else
+ return ((sleep_time / 20 + 5) << 2) & PWR_MODE_MASK;
+}
+
static u16 cyapa_pwr_cmd_to_sleep_time(u8 pwr_mode)
{
u8 encoded_time = pwr_mode >> 2;
@@ -734,6 +1100,8 @@ static int cyapa_gen3_get_query_data(struct cyapa *cyapa)
cyapa->physical_size_y =
((query_data[24] & 0x0f) << 8) | query_data[26];
+ cyapa->max_z = 255;
+
return 0;
}
@@ -906,6 +1274,1209 @@ static ssize_t cyapa_i2c_write(struct cyapa *cyapa, u8 reg,
return (ret == (len + 1)) ? 0 : ((ret < 0) ? ret : -EIO);
}
+
+/*******************************************************************
+ * Functions defined for Gen5 trackapd device.
+ *******************************************************************/
+
+/* Return negative errno, or else the number of bytes read. */
+static ssize_t cyapa_i2c_pip_read(struct cyapa *cyapa, u8 *buf, size_t size)
+{
+ int ret;
+
+ if (size == 0)
+ return 0;
+
+ if (!buf || size > CYAPA_REG_MAP_SIZE)
+ return -EINVAL;
+
+ ret = i2c_master_recv(cyapa->client, buf, size);
+
+ if (ret != size)
+ return (ret < 0) ? ret : -EIO;
+
+ return size;
+}
+
+/**
+ * Return a negative errno code else zero on success.
+ */
+static ssize_t cyapa_i2c_pip_write(struct cyapa *cyapa, u8 *buf, size_t size)
+{
+ int ret;
+
+ if (!buf || !size)
+ return -EINVAL;
+
+ ret = i2c_master_send(cyapa->client, buf, size);
+
+ if (ret != size)
+ return (ret < 0) ? ret : -EIO;
+
+ return 0;
+}
+
+static int cyapa_do_i2c_pip_cmd_irq_sync(
+ struct cyapa *cyapa,
+ u8 *cmd, size_t cmd_len,
+ unsigned long timeout)
+{
+ int ret;
+
+ /* wait for interrupt to set ready completion */
+ init_completion(&cyapa->cmd_ready);
+
+ atomic_inc(&cyapa->cmd_issued);
+ ret = cyapa_i2c_pip_write(cyapa, cmd, cmd_len);
+ if (ret) {
+ atomic_dec(&cyapa->cmd_issued);
+ return (ret < 0) ? ret : -EIO;
+ }
+
+ /* wait for interrupt to indicate command is completed. */
+ timeout = wait_for_completion_timeout(&cyapa->cmd_ready,
+ msecs_to_jiffies(timeout));
+ if (timeout == 0) {
+ atomic_dec(&cyapa->cmd_issued);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+/**
+ * this function is aimed to dump all not read data in Gen5 trackpad
+ * before send any command, otherwise, the interrupt line will be blocked.
+ */
+static int cyapa_empty_pip_output_data(struct cyapa *cyapa,
+ u8 *buf, int *len, func_sort func)
+{
+ int ret;
+ int length;
+ int report_count;
+ int empty_count;
+ int buf_len;
+
+ buf_len = 0;
+ if (len) {
+ buf_len = (*len < MAX_TMP_BUF_SIZE) ? *len : MAX_TMP_BUF_SIZE;
+ *len = 0;
+ }
+
+ report_count = 8; /* max 7 pending data before command response data */
+ empty_count = 0;
+ do {
+ /*
+ * Depnding on testing in cyapa driver, there are max 5 "02 00"
+ * packets between two valid bufferred data report in firmware.
+ * So in order to dump all buffered data out and
+ * make interrupt line release for reassert again,
+ * we must set the empty_count check value bigger than 5 to
+ * make it work. Otherwise, in some situation,
+ * the interrupt line may unable to reactive again,
+ * which will cause trackpad device unable to
+ * report data any more.
+ * for example, it may happen in EFT and ESD testing.
+ */
+ if (empty_count > 5)
+ return 0;
+
+ ret = cyapa_i2c_pip_read(cyapa, cyapa->tmp_buf,
+ GEN5_RESP_LENGTH_SIZE);
+ if (ret < 0)
+ return ret;
+
+ length = get_unaligned_le16(cyapa->tmp_buf);
+ if (length == GEN5_RESP_LENGTH_SIZE) {
+ empty_count++;
+ continue;
+ } else if (length > MAX_TMP_BUF_SIZE) {
+ /* should not happen */
+ return -EINVAL;
+ } else if (length == 0) {
+ /* application or bootloader launch data polled out. */
+ length = GEN5_RESP_LENGTH_SIZE;
+ if (buf && buf_len && func &&
+ func(cyapa, cyapa->tmp_buf, length)) {
+ length = min(buf_len, length);
+ memcpy(buf, cyapa->tmp_buf, length);
+ *len = length;
+ /* response found, success. */
+ return 0;
+ } else {
+ continue;
+ }
+ }
+
+ ret = cyapa_i2c_pip_read(cyapa, cyapa->tmp_buf, length);
+ if (ret < 0)
+ return ret;
+
+ report_count--;
+ empty_count = 0;
+ length = get_unaligned_le16(cyapa->tmp_buf);
+ if (length <= GEN5_RESP_LENGTH_SIZE) {
+ empty_count++;
+ } else if (buf && buf_len && func &&
+ func(cyapa, cyapa->tmp_buf, length)) {
+ length = min(buf_len, length);
+ memcpy(buf, cyapa->tmp_buf, length);
+ *len = length;
+ /* response found, success. */
+ return 0;
+ }
+
+ ret = -EINVAL;
+ } while (report_count);
+
+ return ret;
+}
+
+static int cyapa_i2c_pip_cmd_irq_sync(
+ struct cyapa *cyapa,
+ u8 *cmd, int cmd_len,
+ u8 *resp_data, int *resp_len,
+ unsigned long timeout,
+ func_sort func)
+{
+ int ret;
+ int tries;
+ int length;
+
+ if (!cmd || !cmd_len)
+ return -EINVAL;
+
+ mutex_lock(&cyapa->cmd_lock);
+
+ cyapa->resp_sort_func = func;
+ cyapa->resp_data = resp_data;
+ cyapa->resp_len = resp_len;
+
+ if (cmd_len >= GEN5_MIN_APP_CMD_LENGTH &&
+ cmd[4] == GEN5_APP_CMD_REPORT_ID) {
+ /* application command */
+ cyapa->in_progress_cmd = cmd[6] & 0x7f;
+ } else if (cmd_len >= GEN5_MIN_BL_CMD_LENGTH &&
+ cmd[4] == GEN5_BL_CMD_REPORT_ID) {
+ /* bootloader command */
+ cyapa->in_progress_cmd = cmd[7];
+ }
+
+ /* send command data, wait and read output response data's length. */
+ if (cyapa_is_irq_enabled(cyapa)) {
+ ret = cyapa_do_i2c_pip_cmd_irq_sync(cyapa, cmd, cmd_len,
+ timeout);
+ if (ret == -ETIMEDOUT && resp_data &&
+ resp_len && *resp_len != 0 && func) {
+ /*
+ * for some old version with some unknown reasons,
+ * there was no interrupt for the command response data,
+ * so need to poll here to try to get the response data.
+ */
+ ret = cyapa_empty_pip_output_data(cyapa,
+ resp_data, resp_len, func);
+ if (ret || *resp_len == 0)
+ ret = ret ? ret : -ETIMEDOUT;
+ }
+ } else {
+ ret = cyapa_i2c_pip_write(cyapa, cmd, cmd_len);
+ if (ret) {
+ ret = ret < 0 ? ret : -EIO;
+ goto out;
+ }
+
+ tries = timeout / 5;
+ length = *resp_len;
+ if (resp_data && resp_len && length != 0 && func) {
+ do {
+ usleep_range(3000, 5000);
+ *resp_len = length;
+ ret = cyapa_empty_pip_output_data(cyapa,
+ resp_data, resp_len, func);
+ if (ret || *resp_len == 0)
+ continue;
+ else
+ break;
+ } while (--tries > 0);
+ if ((ret || *resp_len == 0) || tries <= 0)
+ ret = ret ? ret : -ETIMEDOUT;
+ }
+ }
+
+out:
+ cyapa->resp_sort_func = NULL;
+ cyapa->resp_data = NULL;
+ cyapa->resp_len = NULL;
+ cyapa->in_progress_cmd = TSG_INVALID_CMD;
+
+ mutex_unlock(&cyapa->cmd_lock);
+ return ret;
+}
+
+bool cyapa_gen5_sort_tsg_pip_bl_resp_data(struct cyapa *cyapa,
+ u8 *data, int len)
+{
+ if (!data || len < GEN5_MIN_BL_RESP_LENGTH)
+ return false;
+
+ /* bootloader input report id 30h */
+ if (data[GEN5_RESP_REPORT_ID_OFFSET] == GEN5_BL_RESP_REPORT_ID &&
+ data[GEN5_RESP_RSVD_OFFSET] == GEN5_RESP_RSVD_KEY &&
+ data[GEN5_RESP_BL_SOP_OFFSET] == GEN5_SOP_KEY)
+ return true;
+
+ return false;
+}
+
+bool cyapa_gen5_sort_tsg_pip_app_resp_data(struct cyapa *cyapa,
+ u8 *data, int len)
+{
+ int resp_len;
+
+ if (!data || len < GEN5_MIN_APP_RESP_LENGTH)
+ return false;
+
+ if (data[GEN5_RESP_REPORT_ID_OFFSET] == GEN5_APP_RESP_REPORT_ID &&
+ data[GEN5_RESP_RSVD_OFFSET] == GEN5_RESP_RSVD_KEY) {
+ resp_len = get_unaligned_le16(&data[0]);
+ if (GET_GEN5_CMD_CODE(data[GEN5_RESP_APP_CMD_OFFSET]) == 0x00 &&
+ resp_len == GEN5_UNSUPPORTED_CMD_RESP_LENGTH &&
+ data[5] == cyapa->in_progress_cmd) {
+ /* unsupported command code */
+ return false;
+ } else if (GET_GEN5_CMD_CODE(data[GEN5_RESP_APP_CMD_OFFSET]) ==
+ cyapa->in_progress_cmd) {
+ /* correct command response received */
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool cyapa_gen5_sort_application_launch_data(struct cyapa *cyapa,
+ u8 *buf, int len)
+{
+ if (buf == NULL || len < GEN5_RESP_LENGTH_SIZE)
+ return false;
+
+ if (buf[0] == 0 && buf[1] == 0)
+ return true;
+
+ return false;
+}
+
+static bool cyapa_gen5_sort_hid_descriptor_data(struct cyapa *cyapa,
+ u8 *buf, int len)
+{
+ int resp_len;
+ int max_output_len;
+
+ /* check hid descriptor. */
+ if (len != GEN5_HID_DESCRIPTOR_SIZE)
+ return false;
+
+ resp_len = get_unaligned_le16(&buf[0]);
+ max_output_len = get_unaligned_le16(&buf[16]);
+ if (resp_len == GEN5_HID_DESCRIPTOR_SIZE) {
+ if (buf[2] == GEN5_BL_HID_REPORT_ID &&
+ max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) {
+ /* BL mode HID Descriptor */
+ return true;
+ } else if (buf[2] == GEN5_APP_HID_REPORT_ID &&
+ max_output_len == GEN5_APP_MAX_OUTPUT_LENGTH) {
+ /* APP mode HID Descriptor */
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool cyapa_gen5_sort_deep_sleep_data(struct cyapa *cyapa,
+ u8 *buf, int len)
+{
+ if (len == GEN5_DEEP_SLEEP_RESP_LENGTH &&
+ buf[2] == GEN5_APP_DEEP_SLEEP_REPORT_ID &&
+ (buf[4] & GEN5_DEEP_SLEEP_OPCODE_MASK) ==
+ GEN5_DEEP_SLEEP_OPCODE)
+ return true;
+ return false;
+}
+
+static int cyapa_gen5_state_parse(struct cyapa *cyapa, u8 *reg_data, int len)
+{
+ int ret;
+ int length;
+ u8 cmd[2];
+ u8 resp_data[32];
+ int max_output_len;
+
+ /* Parse based on Gen5 characteristic regiters and bits */
+ length = get_unaligned_le16(®_data[0]);
+ if (length == 0 || length == GEN5_RESP_LENGTH_SIZE) {
+ /* dump all buffered data firstly, specific for the situation
+ * that when trackpad is just power on the cyapa go here. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ memset(resp_data, 0, sizeof(resp_data));
+ ret = cyapa_i2c_pip_read(cyapa, resp_data, 3);
+ if (ret != 3)
+ return -EAGAIN;
+
+ length = get_unaligned_le16(&resp_data[0]);
+ if (length == GEN5_RESP_LENGTH_SIZE) {
+ /* normal state of Gen5 with no data to respose */
+ cyapa->gen = CYAPA_GEN5;
+
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ /* read HID Description from trackpad device */
+ cmd[0] = 0x01;
+ cmd[1] = 0x00;
+ length = GEN5_HID_DESCRIPTOR_SIZE;
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, 2,
+ resp_data, &length,
+ 300,
+ cyapa_gen5_sort_hid_descriptor_data);
+ if (ret)
+ return -EAGAIN;
+
+ length = get_unaligned_le16(&resp_data[0]);
+ max_output_len = get_unaligned_le16(&resp_data[16]);
+ if ((length == GEN5_HID_DESCRIPTOR_SIZE ||
+ length == GEN5_RESP_LENGTH_SIZE) &&
+ resp_data[2] == GEN5_BL_HID_REPORT_ID &&
+ max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) {
+ /* BL mode HID Description read */
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ return 0;
+ } else if ((length == GEN5_HID_DESCRIPTOR_SIZE ||
+ length == GEN5_RESP_LENGTH_SIZE) &&
+ resp_data[2] == GEN5_APP_HID_REPORT_ID &&
+ max_output_len == GEN5_APP_MAX_OUTPUT_LENGTH) {
+ /* APP mode HID Description read */
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ return 0;
+ } else {
+ /* should not happen!!! */
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+ return -EAGAIN;
+ }
+ }
+ } else if (length == GEN5_HID_DESCRIPTOR_SIZE &&
+ (reg_data[2] == GEN5_BL_HID_REPORT_ID ||
+ reg_data[2] == GEN5_APP_HID_REPORT_ID)) {
+ /* 0x20 0x00 0xF7 is Gen5 Application HID Description Header;
+ * 0x20 0x00 0xFF is Gen5 Booloader HID Description Header.
+ *
+ * must read Report Description content through out,
+ * otherwise Gen5 trackpad cannot reponse next command
+ * or report any touch or button data.
+ */
+ ret = cyapa_i2c_pip_read(cyapa, resp_data,
+ GEN5_HID_DESCRIPTOR_SIZE);
+ if (ret != GEN5_HID_DESCRIPTOR_SIZE)
+ return -EAGAIN;
+ length = get_unaligned_le16(&resp_data[0]);
+ max_output_len = get_unaligned_le16(&resp_data[16]);
+ if (length == GEN5_RESP_LENGTH_SIZE) {
+ if (reg_data[2] == GEN5_BL_HID_REPORT_ID) {
+ /* BL mode HID Description has been previously
+ * read out */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ return 0;
+ } else {
+ /* APP mode HID Description has been previously
+ * read out */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ return 0;
+ }
+ } else if (length == GEN5_HID_DESCRIPTOR_SIZE &&
+ resp_data[2] == GEN5_BL_HID_REPORT_ID &&
+ max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) {
+ /* BL mode HID Description read */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ return 0;
+ } else if (length == GEN5_HID_DESCRIPTOR_SIZE &&
+ resp_data[2] == GEN5_APP_HID_REPORT_ID &&
+ max_output_len == GEN5_APP_MAX_OUTPUT_LENGTH) {
+ /* APP mode HID Description read */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ return 0;
+ } else {
+ /* should not happen!!! */
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+ return -EAGAIN;
+ }
+ } else if ((length == GEN5_APP_REPORT_DESCRIPTOR_SIZE ||
+ length == GEN5_APP_CONTRACT_REPORT_DESCRIPTOR_SIZE) &&
+ reg_data[2] == GEN5_APP_REPORT_DESCRIPTOR_ID) {
+ /* 0xEE 0x00 0xF6 is Gen5 APP Report Description header. */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+
+ /*
+ * must read Report Description content through out,
+ * otherwise Gen5 trackpad cannot reponse next command
+ * or report any touch or button data.
+ */
+
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ return 0;
+ } else if (length == GEN5_BL_REPORT_DESCRIPTOR_SIZE &&
+ reg_data[2] == GEN5_BL_REPORT_DESCRIPTOR_ID) {
+ /* 0x1D 0x00 0xFE is Gen5 BL Report Descriptior header. */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+
+ /*
+ * must read Report Description content through out,
+ * otherwise Gen5 trackpad cannot reponse next command
+ * or report any touch or button data.
+ */
+ ret = cyapa_i2c_pip_read(cyapa, cyapa->tmp_buf,
+ GEN5_BL_REPORT_DESCRIPTOR_SIZE);
+ if (ret != GEN5_BL_REPORT_DESCRIPTOR_SIZE)
+ return -EAGAIN;
+
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ return 0;
+ } else if (reg_data[2] == GEN5_TOUCH_REPORT_ID ||
+ reg_data[2] == GEN5_BTN_REPORT_ID ||
+ reg_data[2] == GEN5_OLD_PUSH_BTN_REPORT_ID ||
+ reg_data[2] == GEN5_PUSH_BTN_REPORT_ID ||
+ reg_data[2] == GEN5_WAKEUP_EVENT_REPORT_ID) {
+ ret = 0;
+ length = get_unaligned_le16(®_data[0]);
+ switch (reg_data[2]) {
+ case GEN5_TOUCH_REPORT_ID:
+ if (length < GEN5_TOUCH_REPORT_HEAD_SIZE ||
+ length > GEN5_TOUCH_REPORT_MAX_SIZE)
+ ret = -EINVAL;
+ break;
+ case GEN5_BTN_REPORT_ID:
+ case GEN5_OLD_PUSH_BTN_REPORT_ID:
+ case GEN5_PUSH_BTN_REPORT_ID:
+ if (length < GEN5_BTN_REPORT_HEAD_SIZE ||
+ length > GEN5_BTN_REPORT_MAX_SIZE)
+ ret = -EINVAL;
+ break;
+ case GEN5_WAKEUP_EVENT_REPORT_ID:
+ if (length != GEN5_WAKEUP_EVENT_SIZE)
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret < 0)
+ return -EAGAIN;
+
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ return 0;
+ } else if (reg_data[2] == GEN5_BL_RESP_REPORT_ID ||
+ reg_data[2] == GEN5_APP_RESP_REPORT_ID) {
+ /*
+ * must read report data through out,
+ * otherwise Gen5 trackpad cannot reponse next command
+ * or report any touch or button data.
+ */
+ length = get_unaligned_le16(reg_data);
+ ret = cyapa_i2c_pip_read(cyapa, cyapa->tmp_buf, length);
+ if (ret != length)
+ return -EAGAIN;
+
+ if (length == GEN5_RESP_LENGTH_SIZE) {
+ /* previous command has read the data through out. */
+ if (reg_data[2] == GEN5_BL_RESP_REPORT_ID) {
+ /* Gen5 BL command response data detected */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ } else {
+ /* Gen5 APP command response data detected */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ }
+ } else if (cyapa->tmp_buf[2] == GEN5_BL_RESP_REPORT_ID &&
+ cyapa->tmp_buf[3] == GEN5_RESP_RSVD_KEY &&
+ cyapa->tmp_buf[4] == GEN5_SOP_KEY &&
+ cyapa->tmp_buf[length - 1] == GEN5_EOP_KEY) {
+ /* Gen5 BL command response data detected */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ } else if (cyapa->tmp_buf[2] == GEN5_APP_RESP_REPORT_ID &&
+ cyapa->tmp_buf[3] == GEN5_RESP_RSVD_KEY) {
+ /* Gen5 APP command response data detected */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ } else {
+ /* should not happen!!! */
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+ }
+
+ if (cyapa->state == CYAPA_STATE_NO_DEVICE)
+ return -EAGAIN;
+ return 0;
+ }
+
+ return -EAGAIN;
+}
+
+bool cyapa_gen5_sort_bl_exit_data(struct cyapa *cyapa, u8 *buf, int len)
+{
+ if (buf == NULL || len < GEN5_RESP_LENGTH_SIZE)
+ return false;
+
+ if (buf[0] == 0 && buf[1] == 0)
+ return true;
+
+ /* exit bootloader failed for some reason. */
+ if (len == GEN5_BL_FAIL_EXIT_RESP_LEN &&
+ buf[2] == GEN5_BL_RESP_REPORT_ID &&
+ buf[3] == GEN5_RESP_RSVD_KEY &&
+ buf[4] == GEN5_SOP_KEY &&
+ buf[10] == GEN5_EOP_KEY)
+ return true;
+
+ return false;
+}
+static int cyapa_gen5_bl_exit(struct cyapa *cyapa)
+{
+ int ret;
+ u8 resp_data[11];
+ int resp_len;
+ u8 bl_gen5_bl_exit[] = { 0x04, 0x00,
+ 0x0B, 0x00, 0x40, 0x00, 0x01, 0x3b, 0x00, 0x00,
+ 0x20, 0xc7, 0x17
+ };
+
+ resp_len = sizeof(resp_data);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ bl_gen5_bl_exit, sizeof(bl_gen5_bl_exit),
+ resp_data, &resp_len,
+ 5000, cyapa_gen5_sort_bl_exit_data);
+ if (ret)
+ return ret;
+
+ if (resp_len == GEN5_BL_FAIL_EXIT_RESP_LEN ||
+ resp_data[2] == GEN5_BL_RESP_REPORT_ID)
+ return -EAGAIN;
+
+ if (resp_data[0] == 0x00 && resp_data[1] == 0x00)
+ return 0;
+
+ return -EAGAIN;
+}
+
+static int cyapa_gen5_change_power_state(struct cyapa *cyapa, u8 power_state)
+{
+ int ret;
+ u8 cmd[8] = { 0x04, 0x00, 0x06, 0x00, 0x2f, 0x00, 0x08, 0x01 };
+ u8 resp_data[6];
+ int resp_len;
+
+ cmd[7] = power_state;
+ resp_len = sizeof(resp_data);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+ if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+ resp_data[3] != GEN5_RESP_RSVD_KEY ||
+ GET_GEN5_CMD_CODE(resp_data[4]) != 0x08 ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return ret < 0 ? ret : -EINVAL;
+
+ return 0;
+}
+
+static int cyapa_gen5_set_interval_time(struct cyapa *cyapa,
+ u8 parameter_id, u16 interval_time)
+{
+ int ret;
+ u8 cmd[13];
+ int cmd_len;
+ u8 resp_data[7];
+ int resp_len;
+ u8 parameter_size;
+
+ switch (parameter_id) {
+ case GEN5_PARAMETER_ACT_INTERVL_ID:
+ parameter_size = GEN5_PARAMETER_ACT_INTERVL_SIZE;
+ break;
+ case GEN5_PARAMETER_ACT_LFT_INTERVL_ID:
+ parameter_size = GEN5_PARAMETER_ACT_LFT_INTERVL_SIZE;
+ break;
+ case GEN5_PARAMETER_LP_INTRVL_ID:
+ parameter_size = GEN5_PARAMETER_LP_INTRVL_SIZE;
+ break;
+ default:
+ return -EINVAL;
+ }
+ cmd_len = 7 + parameter_size; /* not incuding 2 bytes address */
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ put_unaligned_le16(cmd_len, &cmd[2]);
+ cmd[4] = 0x2f;
+ cmd[5] = 0x00;
+ cmd[6] = 0x06; /* set parameter command code */
+ cmd[7] = parameter_id;
+ cmd[8] = parameter_size;
+ put_unaligned_le16(interval_time, &cmd[9]);
+ resp_len = sizeof(resp_data);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, cmd_len + 2,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+ if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+ resp_data[3] != GEN5_RESP_RSVD_KEY ||
+ GET_GEN5_CMD_CODE(resp_data[4]) != 0x06 ||
+ resp_data[5] != parameter_id ||
+ resp_data[6] != parameter_size)
+ return ret < 0 ? ret : -EINVAL;
+
+ return 0;
+}
+
+static int cyapa_gen5_get_interval_time(struct cyapa *cyapa,
+ u8 parameter_id, u16 *interval_time)
+{
+ int ret;
+ u8 cmd[8];
+ u8 resp_data[11];
+ int resp_len;
+ u8 parameter_size;
+ u16 mask, i;
+
+ *interval_time = 0;
+ switch (parameter_id) {
+ case GEN5_PARAMETER_ACT_INTERVL_ID:
+ parameter_size = GEN5_PARAMETER_ACT_INTERVL_SIZE;
+ break;
+ case GEN5_PARAMETER_ACT_LFT_INTERVL_ID:
+ parameter_size = GEN5_PARAMETER_ACT_LFT_INTERVL_SIZE;
+ break;
+ case GEN5_PARAMETER_LP_INTRVL_ID:
+ parameter_size = GEN5_PARAMETER_LP_INTRVL_SIZE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ cmd[2] = 0x06;
+ cmd[3] = 0x00;
+ cmd[4] = 0x2f;
+ cmd[5] = 0x00;
+ cmd[6] = 0x05; /* get parameter command code */
+ cmd[7] = parameter_id;
+ resp_len = sizeof(resp_data);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+ if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+ resp_data[3] != GEN5_RESP_RSVD_KEY ||
+ GET_GEN5_CMD_CODE(resp_data[4]) != 0x05 ||
+ resp_data[5] != parameter_id ||
+ resp_data[6] == 0)
+ return ret < 0 ? ret : -EINVAL;
+
+ mask = 0;
+ for (i = 0; i < parameter_size; i++)
+ mask |= (0xff << (i * 8));
+ *interval_time = get_unaligned_le16(&resp_data[7]) & mask;
+
+ return 0;
+}
+
+static int cyapa_gen5_disable_pip_report(struct cyapa *cyapa)
+{
+ int ret;
+ u8 cmd[10];
+ u8 resp_data[7];
+ int resp_len;
+
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ put_unaligned_le16(8, &cmd[2]);
+ cmd[4] = 0x2f;
+ cmd[5] = 0x00;
+ cmd[6] = 0x06; /* set parameter command code */
+ cmd[7] = GEN5_PARAMETER_DISABLE_PIP_REPORT;
+ cmd[8] = 0x01;
+ cmd[9] = 0x01;
+ resp_len = sizeof(resp_data);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, 10,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+ if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+ resp_data[3] != GEN5_RESP_RSVD_KEY ||
+ GET_GEN5_CMD_CODE(resp_data[4]) != 0x06 ||
+ resp_data[5] != GEN5_PARAMETER_DISABLE_PIP_REPORT ||
+ resp_data[6] != 0x01)
+ return ret < 0 ? ret : -EINVAL;
+
+ return 0;
+}
+
+static int cyapa_gen5_deep_sleep(struct cyapa *cyapa, u8 state)
+{
+ int ret;
+ u8 cmd[4] = { 0x05, 0x00, 0x00, 0x08};
+ u8 resp_data[5];
+ int resp_len;
+
+ cmd[0] = 0x05;
+ cmd[1] = 0x00;
+ cmd[2] = state & GEN5_DEEP_SLEEP_STATE_MASK;
+ cmd[3] = 0x08;
+ resp_len = sizeof(resp_data);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+ resp_data, &resp_len,
+ 100, cyapa_gen5_sort_deep_sleep_data);
+ if (ret || ((resp_data[3] & GEN5_DEEP_SLEEP_STATE_MASK) != state))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int cyapa_gen5_set_power_mode(struct cyapa *cyapa,
+ u8 power_mode, u16 sleep_time)
+{
+ struct device *dev = &cyapa->client->dev;
+ int ret;
+ u8 power_state;
+
+ if (cyapa->state != CYAPA_STATE_GEN5_APP)
+ return 0;
+
+ /* dump all the report data before do power mode commmands. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ switch (power_mode) {
+ case PWR_MODE_OFF:
+ case PWR_MODE_FULL_ACTIVE:
+ case PWR_MODE_BTN_ONLY:
+ if (power_mode == cyapa->real_power_mode)
+ return 0;
+
+ if (power_mode == PWR_MODE_OFF) {
+ ret = cyapa_gen5_deep_sleep(cyapa,
+ GEN5_DEEP_SLEEP_STATE_OFF);
+ if (ret) {
+ dev_err(dev, "enter deep sleep fail, (%d)\n",
+ ret);
+ return ret;
+ }
+
+ cyapa->real_power_mode = PWR_MODE_OFF;
+ return 0;
+ }
+ break;
+ default:
+ if (sleep_time == cyapa->real_sleep_time &&
+ power_mode == cyapa->real_power_mode)
+ return 0;
+ }
+
+ if (cyapa->real_power_mode == PWR_MODE_OFF) {
+ ret = cyapa_gen5_deep_sleep(cyapa, GEN5_DEEP_SLEEP_STATE_ON);
+ if (ret) {
+ dev_err(dev, "deep sleep wake fail, (%d)\n", ret);
+ return ret;
+ }
+ /* set current power state in driver. */
+ cyapa->real_power_mode = cyapa->suspend_power_mode;
+ cyapa->real_sleep_time = cyapa->suspend_sleep_time;
+ }
+
+ if (power_mode == PWR_MODE_FULL_ACTIVE) {
+ ret = cyapa_gen5_change_power_state(cyapa,
+ GEN5_POWER_STATE_ACTIVE);
+ if (ret) {
+ dev_err(dev, "change to active fail, (%d)\n", ret);
+ return ret;
+ }
+
+ cyapa->real_power_mode = PWR_MODE_FULL_ACTIVE;
+ } else if (power_mode == PWR_MODE_BTN_ONLY) {
+ ret = cyapa_gen5_change_power_state(cyapa,
+ GEN5_POWER_STATE_BTN_ONLY);
+ if (ret) {
+ dev_err(dev, "fail change to active, (%d)\n", ret);
+ return ret;
+ }
+
+ cyapa->real_power_mode = PWR_MODE_BTN_ONLY;
+ } else {
+ /* continue to change power mode even failed to set
+ * interval time, it won't affect the power mode change. */
+ cyapa_gen5_set_interval_time(cyapa,
+ GEN5_PARAMETER_LP_INTRVL_ID, sleep_time);
+
+ if (sleep_time <= GEN5_POWER_READY_MAX_INTRVL_TIME)
+ power_state = GEN5_POWER_STATE_READY;
+ else
+ power_state = GEN5_POWER_STATE_IDLE;
+ ret = cyapa_gen5_change_power_state(cyapa, power_state);
+ if (ret) {
+ dev_err(dev, "set power state %d fail, (%d)\n",
+ power_state, ret);
+ cyapa_gen5_set_interval_time(cyapa,
+ GEN5_PARAMETER_LP_INTRVL_ID,
+ cyapa->real_sleep_time);
+ return ret;
+ }
+
+ /* disable pip report for a little time, firmware will
+ * re-enable it automatically. It's used to fix the issue
+ * that trackpad unable to report signal to wake system up
+ * in the special situation that system is in suspending, and
+ * at the same time, user touch trackpad to wake system up.
+ * This function can avoid the data to be buffured when system
+ * is suspending which may cause interrput line unable to be
+ * asserted again. */
+ cyapa_gen5_disable_pip_report(cyapa);
+
+ cyapa->real_power_mode =
+ cyapa_sleep_time_to_pwr_cmd(sleep_time);
+ cyapa->real_sleep_time = sleep_time;
+ }
+
+ return ret;
+}
+
+static bool cyapa_gen5_sort_system_info_data(struct cyapa *cyapa,
+ u8 *buf, int len)
+{
+ /* check the report id and command code */
+ if (buf[2] == GEN5_APP_RESP_REPORT_ID &&
+ GET_GEN5_CMD_CODE(buf[4]) == 0x02)
+ return true;
+
+ return false;
+}
+
+static int cyapa_gen5_bl_query_data(struct cyapa *cyapa)
+{
+ int ret;
+ u8 cmd[16];
+ int cmd_len;
+ u8 resp_data[GEN5_BL_READ_APP_INFO_RESP_LEN];
+ int resp_len;
+
+ /* read application information. */
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ cmd[2] = 0x0b;
+ cmd[3] = 0x00;
+ cmd[4] = 0x40;
+ cmd[5] = 0x00;
+ cmd[6] = GEN5_SOP_KEY;
+ cmd[7] = 0x3c; /* read application information command code */
+ cmd[8] = 0x00;
+ cmd[9] = 0x00;
+ cmd[10] = 0xb0;
+ cmd[11] = 0x42;
+ cmd[12] = GEN5_EOP_KEY;
+ cmd_len = 13;
+ resp_len = GEN5_BL_READ_APP_INFO_RESP_LEN;
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, cmd_len,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_bl_resp_data);
+ if (ret || resp_len != GEN5_BL_READ_APP_INFO_RESP_LEN ||
+ resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return (ret < 0) ? ret : -EIO;
+
+ memcpy(&cyapa->product_id[0], &resp_data[8], 5);
+ cyapa->product_id[5] = '-';
+ memcpy(&cyapa->product_id[6], &resp_data[13], 6);
+ cyapa->product_id[12] = '-';
+ memcpy(&cyapa->product_id[13], &resp_data[19], 2);
+ cyapa->product_id[15] = '\0';
+
+ cyapa->fw_maj_ver = resp_data[22];
+ cyapa->fw_min_ver = resp_data[23];
+
+ return 0;
+}
+
+static int cyapa_gen5_get_query_data(struct cyapa *cyapa)
+{
+ int ret;
+ u8 resp_data[71];
+ int resp_len;
+ u8 get_system_information[] = {
+ 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x02
+ };
+ u16 product_family;
+
+ resp_len = sizeof(resp_data);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ get_system_information, sizeof(get_system_information),
+ resp_data, &resp_len,
+ 2000, cyapa_gen5_sort_system_info_data);
+ if (ret || resp_len < sizeof(resp_data))
+ return ret;
+
+ cyapa->fw_img_head.head_size =
+ sizeof(struct cyapa_tsg_bin_image_head) - 1;
+ memcpy(&cyapa->fw_img_head.ttda_driver_major_version,
+ &resp_data[5], cyapa->fw_img_head.head_size);
+
+ product_family = get_unaligned_le16(&resp_data[7]);
+ if ((product_family & GEN5_PRODUCT_FAMILY_MASK) !=
+ GEN5_PRODUCT_FAMILY_TRACKPAD)
+ return -EINVAL;
+
+ cyapa->fw_maj_ver = resp_data[15];
+ cyapa->fw_min_ver = resp_data[16];
+
+ cyapa->electrodes_x = resp_data[52];
+ cyapa->electrodes_y = resp_data[53];
+
+ cyapa->physical_size_x = get_unaligned_le16(&resp_data[54]) / 100;
+ cyapa->physical_size_y = get_unaligned_le16(&resp_data[56]) / 100;
+
+ cyapa->max_abs_x = get_unaligned_le16(&resp_data[58]);
+ cyapa->max_abs_y = get_unaligned_le16(&resp_data[60]);
+
+ cyapa->max_z = get_unaligned_le16(&resp_data[62]);
+
+ cyapa->x_origin = resp_data[64] & 0x01;
+ cyapa->y_origin = resp_data[65] & 0x01;
+
+ cyapa->btn_capability = (resp_data[70] << 3) & CAPABILITY_BTN_MASK;
+
+ memcpy(&cyapa->product_id[0], &resp_data[33], 5);
+ cyapa->product_id[5] = '-';
+ memcpy(&cyapa->product_id[6], &resp_data[38], 6);
+ cyapa->product_id[12] = '-';
+ memcpy(&cyapa->product_id[13], &resp_data[44], 2);
+ cyapa->product_id[15] = '\0';
+
+ if (!cyapa->electrodes_x || !cyapa->electrodes_y ||
+ !cyapa->physical_size_x || !cyapa->physical_size_y ||
+ !cyapa->max_abs_x || !cyapa->max_abs_y || !cyapa->max_z)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int cyapa_gen5_do_operational_check(struct cyapa *cyapa)
+{
+ struct device *dev = &cyapa->client->dev;
+ int ret;
+
+ if (cyapa->gen != CYAPA_GEN5)
+ return -EINVAL;
+
+ switch (cyapa->state) {
+ case CYAPA_STATE_GEN5_BL:
+ ret = cyapa_gen5_bl_exit(cyapa);
+ if (ret) {
+ /* try to update trackpad product information. */
+ cyapa_gen5_bl_query_data(cyapa);
+ return ret;
+ }
+
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+
+ case CYAPA_STATE_GEN5_APP:
+ /* if trackpad device in deep sleep mode,
+ * the app command will fail.
+ * So always reset trackpad device to full active when
+ * the device state is requeried.
+ */
+ if (cyapa->real_power_mode == PWR_MODE_OFF) {
+ ret = cyapa_gen5_set_power_mode(cyapa,
+ PWR_MODE_FULL_ACTIVE, 0);
+ if (ret)
+ return ret;
+
+ /* set initial power state of trackpad device. */
+ cyapa->real_power_mode = PWR_MODE_FULL_ACTIVE;
+ cyapa_gen5_get_interval_time(cyapa,
+ GEN5_PARAMETER_LP_INTRVL_ID,
+ &cyapa->real_sleep_time);
+ cyapa->suspend_power_mode =
+ cyapa_sleep_time_to_pwr_cmd(
+ cyapa->real_sleep_time);
+ cyapa->suspend_sleep_time = cyapa->real_sleep_time;
+ }
+
+ /* Get trackpad product information. */
+ ret = cyapa_gen5_get_query_data(cyapa);
+ if (ret)
+ return ret;
+ /* only support product ID starting with CYTRA */
+ if (memcmp(cyapa->product_id, unique_str,
+ sizeof(unique_str) - 1) != 0) {
+ dev_err(dev, "%s: unknown product ID (%s)\n",
+ __func__, cyapa->product_id);
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void cyapa_gen5_irq_handler(struct cyapa *cyapa)
+{
+ struct input_dev *input = cyapa->input;
+ struct cyapa_gen5_report_data report_data;
+ int i;
+ int ret;
+ u8 report_id;
+ u8 buttons;
+ unsigned int report_len, touch_num;
+ int x, y;
+
+ if (cyapa->gen != CYAPA_GEN5 ||
+ cyapa->state != CYAPA_STATE_GEN5_APP) {
+ async_schedule(cyapa_detect_async, cyapa);
+ return;
+ }
+
+ ret = cyapa_i2c_pip_read(cyapa, (u8 *)&report_data,
+ GEN5_TOUCH_REPORT_HEAD_SIZE);
+ if (ret != GEN5_TOUCH_REPORT_HEAD_SIZE) {
+ /* failed to read report head data. */
+ async_schedule(cyapa_detect_async, cyapa);
+ return;
+ }
+
+ report_len = get_unaligned_le16(&report_data.report_head[0]);
+ if (report_len <= 2) {
+ /*
+ * trackpad power up event or end of one touch packets report,
+ * no data for report.
+ */
+ if (report_len != 2)
+ async_schedule(cyapa_detect_async, cyapa);
+
+ return;
+ }
+
+ report_id = report_data.report_head[2];
+ if (report_id == GEN5_WAKEUP_EVENT_REPORT_ID &&
+ report_len == GEN5_WAKEUP_EVENT_SIZE) {
+ /* Wake event from deep sleep mode, reset power state. */
+ return;
+ } else if (report_id != GEN5_TOUCH_REPORT_ID &&
+ report_id != GEN5_BTN_REPORT_ID &&
+ report_id != GEN5_OLD_PUSH_BTN_REPORT_ID &&
+ report_id != GEN5_PUSH_BTN_REPORT_ID) {
+ /* Running in BL mode or unknown response data read. */
+
+ async_schedule(cyapa_detect_async, cyapa);
+ return;
+ }
+
+ if (report_len > GEN5_TOUCH_REPORT_HEAD_SIZE) {
+ /* must make sure to read all data through out before return. */
+ ret = cyapa_i2c_pip_read(cyapa, (u8 *)&report_data, report_len);
+ if (ret != report_len) {
+ /* failed to read report head data. */
+ async_schedule(cyapa_detect_async, cyapa);
+ return;
+ }
+ }
+
+ if (report_id == GEN5_TOUCH_REPORT_ID &&
+ (report_len < GEN5_TOUCH_REPORT_HEAD_SIZE ||
+ report_len > GEN5_TOUCH_REPORT_MAX_SIZE)) {
+ /* Invald report data length for finger packet. */
+ async_schedule(cyapa_detect_async, cyapa);
+ return;
+ }
+
+ if ((report_id == GEN5_BTN_REPORT_ID ||
+ report_id == GEN5_OLD_PUSH_BTN_REPORT_ID ||
+ report_id == GEN5_PUSH_BTN_REPORT_ID) &&
+ (report_len < GEN5_BTN_REPORT_HEAD_SIZE ||
+ report_len > GEN5_BTN_REPORT_MAX_SIZE)) {
+ /* Invald report data length of button packet. */
+ async_schedule(cyapa_detect_async, cyapa);
+ return;
+ }
+
+ if (report_id == GEN5_BTN_REPORT_ID ||
+ report_id == GEN5_OLD_PUSH_BTN_REPORT_ID ||
+ report_id == GEN5_PUSH_BTN_REPORT_ID) {
+ /* button report data */
+ buttons = (report_data.report_head[5] << 3) &
+ CAPABILITY_BTN_MASK;
+ if (cyapa->btn_capability & CAPABILITY_LEFT_BTN_MASK) {
+ input_report_key(input, BTN_LEFT,
+ !!(buttons & CAPABILITY_LEFT_BTN_MASK));
+ }
+ if (cyapa->btn_capability & CAPABILITY_MIDDLE_BTN_MASK) {
+ input_report_key(input, BTN_MIDDLE,
+ !!(buttons & CAPABILITY_MIDDLE_BTN_MASK));
+ }
+ if (cyapa->btn_capability & CAPABILITY_RIGHT_BTN_MASK) {
+ input_report_key(input, BTN_RIGHT,
+ !!(buttons & CAPABILITY_RIGHT_BTN_MASK));
+ }
+
+ input_sync(input);
+ } else {
+ /* touch report data */
+ touch_num = report_data.report_head[5] &
+ GEN5_NUMBER_OF_TOUCH_MASK;
+
+ for (i = 0; i < touch_num; i++) {
+ const struct cyapa_gen5_touch_record *touch =
+ &report_data.touch_records[i];
+ u8 event_id =
+ GEN5_GET_EVENT_ID(touch->touch_tip_event_id);
+ int slot = GEN5_GET_TOUCH_ID(touch->touch_tip_event_id);
+
+ if (event_id == RECORD_EVENT_LIFTOFF)
+ continue;
+
+ input_mt_slot(input, slot);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+ x = (touch->x_hi << 8) | touch->x_lo;
+ if (cyapa->x_origin)
+ x = cyapa->max_abs_x - x;
+ input_report_abs(input, ABS_MT_POSITION_X, x);
+ y = (touch->y_hi << 8) | touch->y_lo;
+ if (cyapa->y_origin)
+ y = cyapa->max_abs_y - y;
+ input_report_abs(input, ABS_MT_POSITION_Y, y);
+ input_report_abs(input, ABS_MT_PRESSURE,
+ touch->z);
+ input_report_abs(input, ABS_MT_TOUCH_MAJOR,
+ touch->major_axis_len);
+ input_report_abs(input, ABS_MT_TOUCH_MINOR,
+ touch->minor_axis_len);
+
+ input_report_abs(input, ABS_MT_WIDTH_MAJOR,
+ touch->major_tool_len);
+ input_report_abs(input, ABS_MT_WIDTH_MINOR,
+ touch->minor_tool_len);
+
+ input_report_abs(input, ABS_MT_ORIENTATION,
+ touch->orientation);
+ }
+
+ input_mt_sync_frame(input);
+
+ input_sync(input);
+ }
+}
+
static void cyapa_default_irq_handler(struct cyapa *cyapa)
{
async_schedule(cyapa_detect_async, cyapa);
@@ -934,6 +2505,26 @@ static int cyapa_check_is_operational(struct cyapa *cyapa)
return ret;
switch (cyapa->gen) {
+ case CYAPA_GEN5:
+ cyapa->cyapa_check_fw = NULL;
+ cyapa->cyapa_bl_enter = NULL;
+ cyapa->cyapa_bl_activate = NULL;
+ cyapa->cyapa_bl_initiate = NULL;
+ cyapa->cyapa_update_fw = NULL;
+ cyapa->cyapa_bl_verify_app_integrity = NULL;
+ cyapa->cyapa_bl_deactivate = NULL;
+ cyapa->cyapa_show_baseline = NULL;
+ cyapa->cyapa_calibrate_store = NULL;
+ cyapa->cyapa_irq_handler = cyapa_gen5_irq_handler;
+ cyapa->cyapa_set_power_mode = cyapa_gen5_set_power_mode;
+ cyapa->cyapa_read_fw = NULL;
+ cyapa->cyapa_read_raw_data = NULL;
+
+ cyapa_enable_irq_save(cyapa);
+ ret = cyapa_gen5_do_operational_check(cyapa);
+ cyapa_irq_restore(cyapa);
+
+ break;
case CYAPA_GEN3:
cyapa->cyapa_check_fw = NULL;
cyapa->cyapa_bl_enter = NULL;
@@ -983,14 +2574,75 @@ static irqreturn_t cyapa_irq(int irq, void *dev_id)
if (device_may_wakeup(dev))
pm_wakeup_event(dev, 0);
+ if (atomic_read(&cyapa->cmd_issued)) {
+ if (cyapa->gen_detecting == CYAPA_GEN5) {
+ /*
+ * read out all none command response data.
+ * these output data may caused by user put finger on
+ * trackpad when host waiting the command response.
+ */
+ cyapa_i2c_pip_read(cyapa, cyapa->tmp_irq_buf, 2);
+ length = get_unaligned_le16(cyapa->tmp_irq_buf);
+ length = (length <= 2) ? 2 : length;
+ if (length > 2)
+ cyapa_i2c_pip_read(cyapa,
+ cyapa->tmp_irq_buf, length);
+ if (!(cyapa->resp_sort_func &&
+ cyapa->resp_sort_func(cyapa,
+ cyapa->tmp_irq_buf, length))) {
+ /*
+ * Cover the Gen5 V1 firmware issue.
+ * The issue is there is no interrut will be
+ * asserted to notityf host to read a command
+ * data out when always has finger touch on
+ * trackpad during the command is issued to
+ * trackad device.
+ * This issue has the scenario is that,
+ * user always has his fingers touched on
+ * trackpad device when booting/rebooting
+ * their chrome book.
+ */
+ length = *cyapa->resp_len;
+ cyapa_empty_pip_output_data(cyapa,
+ cyapa->resp_data,
+ &length,
+ cyapa->resp_sort_func);
+ if (cyapa->resp_len && length != 0) {
+ *cyapa->resp_len = length;
+ complete(&cyapa->cmd_ready);
+ atomic_dec(&cyapa->cmd_issued);
+ }
+ goto out;
+ }
+
+ if (cyapa->resp_data && cyapa->resp_len) {
+ *cyapa->resp_len = (*cyapa->resp_len < length) ?
+ *cyapa->resp_len : length;
+ memcpy(cyapa->resp_data, cyapa->tmp_irq_buf,
+ *cyapa->resp_len);
+ }
+ }
+ complete(&cyapa->cmd_ready);
+ atomic_dec(&cyapa->cmd_issued);
+ goto out;
+ }
+
/*
* Don't read input if input device has not been configured.
* This check solves a race during probe() between irq_request()
* and irq_disable(), since there is no way to request an irq that is
* initially disabled.
*/
- if (!input || atomic_read(&cyapa->in_detecting))
+ if (!input || atomic_read(&cyapa->in_detecting)) {
+ if (cyapa->gen_detecting == CYAPA_GEN5) {
+ cyapa_i2c_pip_read(cyapa, cyapa->tmp_irq_buf, 2);
+ length = get_unaligned_le16(cyapa->tmp_irq_buf);
+ if (length > 2)
+ cyapa_i2c_pip_read(cyapa,
+ cyapa->tmp_irq_buf, length);
+ }
goto out;
+ }
if (cyapa->cyapa_irq_handler)
cyapa->cyapa_irq_handler(cyapa);
@@ -1051,6 +2703,22 @@ static int cyapa_get_state(struct cyapa *cyapa)
goto out_detected;
cyapa->gen_detecting = CYAPA_GEN_UNKNOWN;
}
+ if ((cyapa->gen == CYAPA_GEN_UNKNOWN ||
+ cyapa->gen == CYAPA_GEN5) &&
+ !smbus && even_addr) {
+ cyapa->gen_detecting = CYAPA_GEN5;
+
+ cyapa_enable_irq_save(cyapa);
+ ret = cyapa_gen5_state_parse(cyapa,
+ status, BL_STATUS_SIZE);
+ cyapa_irq_restore(cyapa);
+ if (ret == 0) {
+ cyapa_empty_pip_output_data(cyapa,
+ NULL, NULL, NULL);
+ goto out_detected;
+ }
+ cyapa->gen_detecting = CYAPA_GEN_UNKNOWN;
+ }
/*
* cannot detect communication protocol based on current
@@ -1157,7 +2825,27 @@ static int cyapa_create_input_dev(struct cyapa *cyapa)
0);
input_set_abs_params(input, ABS_MT_POSITION_Y, 0, cyapa->max_abs_y, 0,
0);
- input_set_abs_params(input, ABS_MT_PRESSURE, 0, 255, 0, 0);
+ input_set_abs_params(input, ABS_MT_PRESSURE, 0, cyapa->max_z, 0, 0);
+ if (cyapa->gen > CYAPA_GEN3) {
+ input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+ input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
+ /* orientation is the angle between the vertial axis and
+ * the major axis of the contact ellipse.
+ * The range is -127 to 127.
+ * the positive direction is clockwise form the vertical axis.
+ * If the ellipse of contact degenerates into a circle,
+ * orientation is reported as 0.
+ *
+ * Also, for Gen5 trackpad the accurate of this orientation
+ * value is value + (-30 ~ 30).
+ */
+ input_set_abs_params(input, ABS_MT_ORIENTATION,
+ -127, 127, 0, 0);
+ }
+ if (cyapa->gen >= CYAPA_GEN5) {
+ input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
+ input_set_abs_params(input, ABS_MT_WIDTH_MINOR, 0, 255, 0, 0);
+ }
input_abs_set_res(input, ABS_MT_POSITION_X,
cyapa->max_abs_x / cyapa->physical_size_x);
@@ -1202,6 +2890,14 @@ static void cyapa_detect(struct cyapa *cyapa)
char *envp[] = {"ERROR=1", NULL};
int ret;
+ /*
+ * Try to dump all bufferred data if it's known gen5 trackpad
+ * before detecting. Because the irq routine may disabled
+ * before enter this routine.
+ */
+ if (cyapa->gen == CYAPA_GEN5)
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
ret = cyapa_check_is_operational(cyapa);
if (ret == -ETIMEDOUT)
dev_err(dev, "no device detected, %d\n", ret);
@@ -1219,7 +2915,8 @@ static void cyapa_detect(struct cyapa *cyapa)
dev_err(dev, "create input_dev instance failed, %d\n",
ret);
- enable_irq(cyapa->irq);
+ cyapa_enable_irq(cyapa);
+
/*
* On some systems, a system crash / warm boot does not reset
* the device's current power mode to FULL_ACTIVE.
@@ -1238,6 +2935,14 @@ static void cyapa_detect(struct cyapa *cyapa)
ret);
}
}
+
+ /*
+ * Try to dump all bufferred data if it's known gen5 trackpad before
+ * detecting. Because the irq routine may disabled before
+ * leave this routine.
+ */
+ if (cyapa->gen == CYAPA_GEN5)
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
}
static void cyapa_detect_async(void *data, async_cookie_t cookie)
@@ -1290,7 +2995,16 @@ static int cyapa_probe(struct i2c_client *client,
cyapa->state = CYAPA_STATE_NO_DEVICE;
cyapa->suspend_power_mode = PWR_MODE_SLEEP;
+ init_completion(&cyapa->cmd_ready);
+ atomic_set(&cyapa->cmd_issued, 0);
+ mutex_init(&cyapa->irq_state_lock);
+ mutex_init(&cyapa->cmd_lock);
+ atomic_set(&cyapa->in_detecting, 0);
+ cyapa->resp_sort_func = NULL;
+ cyapa->in_progress_cmd = TSG_INVALID_CMD;
+
cyapa->irq = client->irq;
+ cyapa->irq_enabled = true;
ret = request_threaded_irq(cyapa->irq,
NULL,
cyapa_irq,
@@ -1301,7 +3015,7 @@ static int cyapa_probe(struct i2c_client *client,
dev_err(dev, "IRQ request failed: %d\n, ", ret);
goto err_unregister_device;
}
- disable_irq(cyapa->irq);
+ cyapa_disable_irq(cyapa);
async_schedule(cyapa_detect_async, cyapa);
return 0;
@@ -1336,7 +3050,7 @@ static int cyapa_suspend(struct device *dev)
u8 power_mode;
struct cyapa *cyapa = dev_get_drvdata(dev);
- disable_irq(cyapa->irq);
+ cyapa_disable_irq(cyapa);
cyapa->suspended = true;
/*
@@ -1365,7 +3079,7 @@ static int cyapa_resume(struct device *dev)
if (device_may_wakeup(dev) && cyapa->irq_wake)
disable_irq_wake(cyapa->irq);
- enable_irq(cyapa->irq);
+ cyapa_enable_irq(cyapa);
if (cyapa->cyapa_set_power_mode) {
ret = cyapa->cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE,
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.
[-- Attachment #2: winmail.dat --]
[-- Type: application/ms-tnef, Size: 26895 bytes --]
^ permalink raw reply related
* [PATCH 1/6] input: cyapa: rearchitecture driver to support function pointers
From: Dudley Du @ 2014-04-14 7:53 UTC (permalink / raw)
To: Dmitry Torokhov (dmitry.torokhov@gmail.com)
Cc: Benson Leung, Daniel Kurtz, linux-input@vger.kernel.org,
linux-kernel@vger.kernel.org
[-- Attachment #1: Type: text/plain, Size: 38009 bytes --]
Re-architecture the driver to support function pointers, so it can
support and integrate new devices later in one driver. Including use
async thread in device proble to speed up system boot time.
TEST=test on Chomebooks.
Signed-off-by: Du, Dudley <dudl@cypress.com>
---
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index b409c3d..4361ee1 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -14,15 +14,19 @@
* more details.
*/
+#include <linux/async.h>
#include <linux/delay.h>
+#include <linux/firmware.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/interrupt.h>
#include <linux/module.h>
+#include <linux/completion.h>
#include <linux/slab.h>
/* APA trackpad firmware generation */
+#define CYAPA_GEN_UNKNOWN 0x00 /* unknown protocol. */
#define CYAPA_GEN3 0x03 /* support MT-protocol B with tracking ID. */
#define CYAPA_NAME "Cypress APA Trackpad (cyapa)"
@@ -85,14 +89,19 @@
* bit 7: Busy
* bit 6 - 5: Reserved
* bit 4: Bootloader running
- * bit 3 - 1: Reserved
+ * bit 3 - 2: Reserved
+ * bit 1: Watchdog Reset
* bit 0: Checksum valid
*/
#define REG_BL_STATUS 0x01
+#define BL_STATUS_REV_6_5 0x60
#define BL_STATUS_BUSY 0x80
#define BL_STATUS_RUNNING 0x10
-#define BL_STATUS_DATA_VALID 0x08
+#define BL_STATUS_REV_3_2 0x0c
+#define BL_STATUS_WATCHDOG 0x02
#define BL_STATUS_CSUM_VALID 0x01
+#define BL_STATUS_REV_MASK (BL_STATUS_WATCHDOG | BL_STATUS_REV_3_2 | \
+ BL_STATUS_REV_6_5)
/*
* Bootloader Error Register
@@ -112,10 +121,14 @@
#define BL_ERROR_CMD_CSUM 0x10
#define BL_ERROR_FLASH_PROT 0x08
#define BL_ERROR_FLASH_CSUM 0x04
+#define BL_ERROR_RESERVED 0x03
#define BL_STATUS_SIZE 3 /* length of bootloader status registers */
#define BLK_HEAD_BYTES 32
+/* Macro for register map group offset. */
+#define CYAPA_REG_MAP_SIZE 256
+
#define PRODUCT_ID_SIZE 16
#define QUERY_DATA_SIZE 27
#define REG_PROTOCOL_GEN_QUERY_OFFSET 20
@@ -134,17 +147,27 @@
#define CYAPA_OFFSET_SOFT_RESET REG_OFFSET_COMMAND_BASE
#define REG_OFFSET_POWER_MODE (REG_OFFSET_COMMAND_BASE + 1)
+#define SET_POWER_MODE_DELAY 10000 /* unit: us */
+#define SET_POWER_MODE_TRIES 5
#define PWR_MODE_MASK 0xfc
#define PWR_MODE_FULL_ACTIVE (0x3f << 2)
-#define PWR_MODE_IDLE (0x05 << 2) /* default sleep time is 50 ms. */
+#define PWR_MODE_IDLE (0x03 << 2) /* default rt suspend scanrate: 30ms */
+#define PWR_MODE_SLEEP (0x05 << 2) /* default suspend scanrate: 50ms */
+#define PWR_MODE_BTN_ONLY (0x01 << 2)
#define PWR_MODE_OFF (0x00 << 2)
+#define BTN_ONLY_MODE_NAME "buttononly"
+#define OFF_MODE_NAME "off"
+
#define PWR_STATUS_MASK 0x0c
#define PWR_STATUS_ACTIVE (0x03 << 2)
#define PWR_STATUS_IDLE (0x02 << 2)
+#define PWR_STATUS_BTN_ONLY (0x01 << 2)
#define PWR_STATUS_OFF (0x00 << 2)
+#define AUTOSUSPEND_DELAY 2000 /* unit : ms */
+
/*
* CYAPA trackpad device states.
* Used in register 0x00, bit1-0, DeviceStatus field.
@@ -153,6 +176,26 @@
#define CYAPA_DEV_NORMAL 0x03
#define CYAPA_DEV_BUSY 0x01
+#define MAX_TMP_BUF_SIZE (CYAPA_REG_MAP_SIZE)
+
+struct cyapa;
+typedef void (*irq_handler_func)(struct cyapa *);
+typedef int (*set_power_mode_func)(struct cyapa *, u8, u16);
+typedef int (*bl_enter_func)(struct cyapa *);
+typedef int (*bl_activate_func)(struct cyapa *);
+typedef int (*bl_verify_app_integrity_func)(struct cyapa *);
+typedef int (*bl_deactivate_func)(struct cyapa *);
+typedef int (*bl_read_fw_func)(struct cyapa *);
+typedef int (*check_fw_func)(struct cyapa *, const struct firmware *);
+typedef int (*bl_initiate_func)(struct cyapa *, const struct firmware *);
+typedef int (*update_fw_func)(struct cyapa *, const struct firmware *);
+typedef ssize_t (*show_baseline_func)(
+ struct device *, struct device_attribute *, char *);
+typedef ssize_t (*calibrate_store_func)(
+ struct device *, struct device_attribute *, const char *, size_t);
+typedef bool (*func_sort)(struct cyapa *, u8 *, int);
+typedef int (*read_raw_data_func)(struct cyapa *);
+
enum cyapa_state {
CYAPA_STATE_OP,
CYAPA_STATE_BL_IDLE,
@@ -210,14 +253,41 @@ struct cyapa {
bool irq_wake; /* irq wake is enabled */
bool smbus;
+ /* power mode settings */
+ u8 suspend_power_mode;
+ u16 suspend_sleep_time;
+ bool suspended;
+
/* read from query data region. */
char product_id[16];
+ u8 fw_maj_ver; /* firmware major version. */
+ u8 fw_min_ver; /* firmware minor version. */
u8 btn_capability;
u8 gen;
int max_abs_x;
int max_abs_y;
int physical_size_x;
int physical_size_y;
+
+ u8 gen_detecting;
+
+ atomic_t in_detecting;
+
+ u8 tmp_buf[MAX_TMP_BUF_SIZE];
+
+ check_fw_func cyapa_check_fw;
+ bl_enter_func cyapa_bl_enter;
+ bl_activate_func cyapa_bl_activate;
+ bl_initiate_func cyapa_bl_initiate;
+ update_fw_func cyapa_update_fw;
+ bl_verify_app_integrity_func cyapa_bl_verify_app_integrity;
+ bl_deactivate_func cyapa_bl_deactivate;
+ show_baseline_func cyapa_show_baseline;
+ calibrate_store_func cyapa_calibrate_store;
+ irq_handler_func cyapa_irq_handler;
+ set_power_mode_func cyapa_set_power_mode;
+ bl_read_fw_func cyapa_read_fw;
+ read_raw_data_func cyapa_read_raw_data;
};
static const u8 bl_deactivate[] = { 0x00, 0xff, 0x3b, 0x00, 0x01, 0x02, 0x03,
@@ -316,6 +386,12 @@ static const struct cyapa_cmd_len cyapa_smbus_cmds[] = {
{ CYAPA_SMBUS_BLK_HEAD, 16 },
};
+static const char unique_str[] = "CYTRA";
+
+static int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout);
+static void cyapa_detect(struct cyapa *cyapa);
+static void cyapa_detect_async(void *data, async_cookie_t cookie);
+
static ssize_t cyapa_i2c_reg_read_block(struct cyapa *cyapa, u8 reg, size_t len,
u8 *values)
{
@@ -417,88 +493,57 @@ static ssize_t cyapa_read_block(struct cyapa *cyapa, u8 cmd_idx, u8 *values)
}
/*
- * Query device for its current operating state.
+ * Determine the Gen3 trackpad device's current operating state.
*
*/
-static int cyapa_get_state(struct cyapa *cyapa)
+static int cyapa_gen3_state_parse(struct cyapa *cyapa, u8 *reg_data, int len)
{
- int ret;
- u8 status[BL_STATUS_SIZE];
-
- cyapa->state = CYAPA_STATE_NO_DEVICE;
-
- /*
- * Get trackpad status by reading 3 registers starting from 0.
- * If the device is in the bootloader, this will be BL_HEAD.
- * If the device is in operation mode, this will be the DATA regs.
- *
- */
- ret = cyapa_i2c_reg_read_block(cyapa, BL_HEAD_OFFSET, BL_STATUS_SIZE,
- status);
-
- /*
- * On smbus systems in OP mode, the i2c_reg_read will fail with
- * -ETIMEDOUT. In this case, try again using the smbus equivalent
- * command. This should return a BL_HEAD indicating CYAPA_STATE_OP.
- */
- if (cyapa->smbus && (ret == -ETIMEDOUT || ret == -ENXIO))
- ret = cyapa_read_block(cyapa, CYAPA_CMD_BL_STATUS, status);
-
- if (ret != BL_STATUS_SIZE)
- goto error;
-
- if ((status[REG_OP_STATUS] & OP_STATUS_SRC) == OP_STATUS_SRC) {
- switch (status[REG_OP_STATUS] & OP_STATUS_DEV) {
- case CYAPA_DEV_NORMAL:
- case CYAPA_DEV_BUSY:
+ /* Parse based on Gen3 characteristic regiters and bits */
+ if (reg_data[0] == 0x00 && reg_data[2] == 0x00 &&
+ (reg_data[1] == 0x11 || reg_data[1] == 0x10)) {
+ /* normal state after power on or reset,
+ * reg_data[1] == 0x11, firmware image checksum is valid.
+ * reg_data[1] == 0x10, firmware image checksum is invalid. */
+ cyapa->gen = CYAPA_GEN3;
+ cyapa->state = CYAPA_STATE_BL_IDLE;
+ return 0;
+ } else if (reg_data[0] == 0x00 &&
+ (reg_data[1] & 0x10) == 0x10) {
+ cyapa->gen = CYAPA_GEN3;
+ if (reg_data[1] & BL_STATUS_BUSY) {
+ cyapa->state = CYAPA_STATE_BL_BUSY;
+ } else {
+ if ((reg_data[2] & 0x20) == 0x00)
+ cyapa->state = CYAPA_STATE_BL_IDLE;
+ else
+ cyapa->state = CYAPA_STATE_BL_ACTIVE;
+ }
+ return 0;
+ } else if ((reg_data[0] & 0x80) && (reg_data[1] & 0x08)) {
+ /* normal state when running in operaitonal mode,
+ * may also not in full power state or
+ * busying in command process. */
+ if (((reg_data[1] >> 4) & 0x07) <= 5) {
+ /* only finger number data is valid. */
+ cyapa->gen = CYAPA_GEN3;
cyapa->state = CYAPA_STATE_OP;
- break;
- default:
- ret = -EAGAIN;
- goto error;
+ return 0;
}
- } else {
- if (status[REG_BL_STATUS] & BL_STATUS_BUSY)
- cyapa->state = CYAPA_STATE_BL_BUSY;
- else if (status[REG_BL_ERROR] & BL_ERROR_BOOTLOADING)
- cyapa->state = CYAPA_STATE_BL_ACTIVE;
- else
- cyapa->state = CYAPA_STATE_BL_IDLE;
+ } else if (reg_data[0] == 0x0C && reg_data[1] == 0x08) {
+ /* Op state when first two registers overwritten with 0x00 */
+ cyapa->gen = CYAPA_GEN3;
+ cyapa->state = CYAPA_STATE_OP;
+ return 0;
+ } else if (reg_data[1] & (BL_STATUS_RUNNING | BL_STATUS_BUSY)) {
+ cyapa->gen = CYAPA_GEN3;
+ cyapa->state = CYAPA_STATE_BL_BUSY;
+ return 0;
}
- return 0;
-error:
- return (ret < 0) ? ret : -EAGAIN;
+ return -EAGAIN;
}
-/*
- * Poll device for its status in a loop, waiting up to timeout for a response.
- *
- * When the device switches state, it usually takes ~300 ms.
- * However, when running a new firmware image, the device must calibrate its
- * sensors, which can take as long as 2 seconds.
- *
- * Note: The timeout has granularity of the polling rate, which is 100 ms.
- *
- * Returns:
- * 0 when the device eventually responds with a valid non-busy state.
- * -ETIMEDOUT if device never responds (too many -EAGAIN)
- * < 0 other errors
- */
-static int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout)
-{
- int ret;
- int tries = timeout / 100;
-
- ret = cyapa_get_state(cyapa);
- while ((ret || cyapa->state >= CYAPA_STATE_BL_BUSY) && tries--) {
- msleep(100);
- ret = cyapa_get_state(cyapa);
- }
- return (ret == -EAGAIN || ret == -ETIMEDOUT) ? -ETIMEDOUT : ret;
-}
-
-static int cyapa_bl_deactivate(struct cyapa *cyapa)
+static int cyapa_gen3_bl_deactivate(struct cyapa *cyapa)
{
int ret;
@@ -530,7 +575,7 @@ static int cyapa_bl_deactivate(struct cyapa *cyapa)
* -EAGAIN device is stuck in bootloader, b/c it has invalid firmware
* 0 device is supported and in operational mode
*/
-static int cyapa_bl_exit(struct cyapa *cyapa)
+static int cyapa_gen3_bl_exit(struct cyapa *cyapa)
{
int ret;
@@ -546,9 +591,10 @@ static int cyapa_bl_exit(struct cyapa *cyapa)
/*
* In addition, when a device boots for the first time after being
* updated to new firmware, it must first calibrate its sensors, which
- * can take up to an additional 2 seconds.
+ * can take up to an additional 2 seconds. If the device power is
+ * running low, this may take even longer.
*/
- ret = cyapa_poll_state(cyapa, 2000);
+ ret = cyapa_poll_state(cyapa, 4000);
if (ret < 0)
return ret;
if (cyapa->state != CYAPA_STATE_OP)
@@ -557,33 +603,104 @@ static int cyapa_bl_exit(struct cyapa *cyapa)
return 0;
}
+static u16 cyapa_pwr_cmd_to_sleep_time(u8 pwr_mode)
+{
+ u8 encoded_time = pwr_mode >> 2;
+ return (encoded_time < 10) ? encoded_time * 10
+ : (encoded_time - 5) * 20;
+}
+
+/*
+ * cyapa_get_wait_time_for_pwr_cmd
+ *
+ * Compute the amount of time we need to wait after updating the touchpad
+ * power mode. The touchpad needs to consume the incoming power mode set
+ * command at the current clock rate.
+ */
+
+static u16 cyapa_get_wait_time_for_pwr_cmd(u8 pwr_mode)
+{
+ switch (pwr_mode) {
+ case PWR_MODE_FULL_ACTIVE: return 20;
+ case PWR_MODE_BTN_ONLY: return 20;
+ case PWR_MODE_OFF: return 20;
+ default: return cyapa_pwr_cmd_to_sleep_time(pwr_mode) + 50;
+ }
+}
+
/*
* Set device power mode
*
+ * Write to the field to configure power state. Power states include :
+ * Full : Max scans and report rate.
+ * Idle : Report rate set by user specified time.
+ * ButtonOnly : No scans for fingers. When the button is triggered,
+ * a slave interrupt is asserted to notify host to wake up.
+ * Off : Only awake for i2c commands from host. No function for button
+ * or touch sensors.
+ *
+ * The power_mode command should conform to the following :
+ * Full : 0x3f
+ * Idle : Configurable from 20 to 1000ms. See note below for
+ * cyapa_sleep_time_to_pwr_cmd and cyapa_pwr_cmd_to_sleep_time
+ * ButtonOnly : 0x01
+ * Off : 0x00
+ *
+ * Device power mode can only be set when device is in operational mode.
*/
-static int cyapa_set_power_mode(struct cyapa *cyapa, u8 power_mode)
+static int cyapa_gen3_set_power_mode(struct cyapa *cyapa, u8 power_mode,
+ u16 reverved)
{
- struct device *dev = &cyapa->client->dev;
int ret;
u8 power;
+ int tries = SET_POWER_MODE_TRIES;
+ u16 sleep_time;
+ /* Specific parameter for Gen4 and later trackpad devices.
+ * Avoid compile warning.
+ */
+ reverved = 0;
if (cyapa->state != CYAPA_STATE_OP)
return 0;
- ret = cyapa_read_byte(cyapa, CYAPA_CMD_POWER_MODE);
+ while (true) {
+ ret = cyapa_read_byte(cyapa, CYAPA_CMD_POWER_MODE);
+ if (ret >= 0 || --tries < 1)
+ break;
+ usleep_range(SET_POWER_MODE_DELAY, 2 * SET_POWER_MODE_DELAY);
+ }
if (ret < 0)
return ret;
- power = ret & ~PWR_MODE_MASK;
+ /*
+ * Return early if the power mode to set is the same as the current
+ * one.
+ */
+ if ((ret & PWR_MODE_MASK) == power_mode)
+ return 0;
+
+ sleep_time = cyapa_get_wait_time_for_pwr_cmd(ret & PWR_MODE_MASK);
+ power = ret;
+ power &= ~PWR_MODE_MASK;
power |= power_mode & PWR_MODE_MASK;
- ret = cyapa_write_byte(cyapa, CYAPA_CMD_POWER_MODE, power);
- if (ret < 0)
- dev_err(dev, "failed to set power_mode 0x%02x err = %d\n",
- power_mode, ret);
+ while (true) {
+ ret = cyapa_write_byte(cyapa, CYAPA_CMD_POWER_MODE, power);
+ if (!ret || --tries < 1)
+ break;
+ usleep_range(SET_POWER_MODE_DELAY, 2 * SET_POWER_MODE_DELAY);
+ }
+
+ /*
+ * Wait for the newly set power command to go in at the previous
+ * clock speed (scanrate) used by the touchpad firmware. Not
+ * doing so before issuing the next command may result in errors
+ * depending on the command's content.
+ */
+ msleep(sleep_time);
return ret;
}
-static int cyapa_get_query_data(struct cyapa *cyapa)
+static int cyapa_gen3_get_query_data(struct cyapa *cyapa)
{
u8 query_data[QUERY_DATA_SIZE];
int ret;
@@ -592,10 +709,8 @@ static int cyapa_get_query_data(struct cyapa *cyapa)
return -EBUSY;
ret = cyapa_read_block(cyapa, CYAPA_CMD_GROUP_QUERY, query_data);
- if (ret < 0)
- return ret;
if (ret != QUERY_DATA_SIZE)
- return -EIO;
+ return (ret < 0) ? ret : -EIO;
memcpy(&cyapa->product_id[0], &query_data[0], 5);
cyapa->product_id[5] = '-';
@@ -604,6 +719,9 @@ static int cyapa_get_query_data(struct cyapa *cyapa)
memcpy(&cyapa->product_id[13], &query_data[11], 2);
cyapa->product_id[15] = '\0';
+ cyapa->fw_maj_ver = query_data[15];
+ cyapa->fw_min_ver = query_data[16];
+
cyapa->btn_capability = query_data[19] & CAPABILITY_BTN_MASK;
cyapa->gen = query_data[20] & 0x0f;
@@ -633,30 +751,38 @@ static int cyapa_get_query_data(struct cyapa *cyapa)
* -EINVAL device is in operational mode, but not supported by this driver
* 0 device is supported
*/
-static int cyapa_check_is_operational(struct cyapa *cyapa)
+static int cyapa_gen3_do_operational_check(struct cyapa *cyapa)
{
struct device *dev = &cyapa->client->dev;
- static const char unique_str[] = "CYTRA";
int ret;
- ret = cyapa_poll_state(cyapa, 2000);
- if (ret < 0)
- return ret;
switch (cyapa->state) {
case CYAPA_STATE_BL_ACTIVE:
- ret = cyapa_bl_deactivate(cyapa);
- if (ret)
+ ret = cyapa_gen3_bl_deactivate(cyapa);
+ if (ret) {
+ dev_err(dev, "failed to bl_deactivate. %d\n", ret);
return ret;
+ }
/* Fallthrough state */
case CYAPA_STATE_BL_IDLE:
- ret = cyapa_bl_exit(cyapa);
- if (ret)
+ ret = cyapa_gen3_bl_exit(cyapa);
+ if (ret) {
+ dev_err(dev, "failed to bl_exit. %d\n", ret);
return ret;
+ }
/* Fallthrough state */
case CYAPA_STATE_OP:
- ret = cyapa_get_query_data(cyapa);
+ /*
+ * Reading query data before going back to the full mode
+ * may cause problems, so we set the power mode first here.
+ */
+ ret = cyapa_gen3_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE, 0);
+ if (ret)
+ dev_err(dev, "%s: set full power mode failed, (%d)\n",
+ __func__, ret);
+ ret = cyapa_gen3_get_query_data(cyapa);
if (ret < 0)
return ret;
@@ -682,27 +808,25 @@ static int cyapa_check_is_operational(struct cyapa *cyapa)
return 0;
}
-static irqreturn_t cyapa_irq(int irq, void *dev_id)
+static void cyapa_gen3_irq_handler(struct cyapa *cyapa)
{
- struct cyapa *cyapa = dev_id;
- struct device *dev = &cyapa->client->dev;
struct input_dev *input = cyapa->input;
struct cyapa_reg_data data;
int i;
int ret;
int num_fingers;
- if (device_may_wakeup(dev))
- pm_wakeup_event(dev, 0);
-
ret = cyapa_read_block(cyapa, CYAPA_CMD_GROUP_DATA, (u8 *)&data);
- if (ret != sizeof(data))
- goto out;
+ if (ret != sizeof(data)) {
+ async_schedule(cyapa_detect_async, cyapa);
+ return;
+ }
if ((data.device_status & OP_STATUS_SRC) != OP_STATUS_SRC ||
(data.device_status & OP_STATUS_DEV) != CYAPA_DEV_NORMAL ||
(data.finger_btn & OP_DATA_VALID) != OP_DATA_VALID) {
- goto out;
+ async_schedule(cyapa_detect_async, cyapa);
+ return;
}
num_fingers = (data.finger_btn >> 4) & 0x0f;
@@ -724,22 +848,271 @@ static irqreturn_t cyapa_irq(int irq, void *dev_id)
if (cyapa->btn_capability & CAPABILITY_LEFT_BTN_MASK)
input_report_key(input, BTN_LEFT,
- data.finger_btn & OP_DATA_LEFT_BTN);
-
+ !!(data.finger_btn & OP_DATA_LEFT_BTN));
if (cyapa->btn_capability & CAPABILITY_MIDDLE_BTN_MASK)
input_report_key(input, BTN_MIDDLE,
- data.finger_btn & OP_DATA_MIDDLE_BTN);
-
+ !!(data.finger_btn & OP_DATA_MIDDLE_BTN));
if (cyapa->btn_capability & CAPABILITY_RIGHT_BTN_MASK)
input_report_key(input, BTN_RIGHT,
- data.finger_btn & OP_DATA_RIGHT_BTN);
-
+ !!(data.finger_btn & OP_DATA_RIGHT_BTN));
input_sync(input);
+}
+
+/* Returns the number of read bytes or a negative errno code. */
+static ssize_t cyapa_i2c_read(struct cyapa *cyapa, u8 reg, size_t len,
+ u8 *values)
+{
+ int ret;
+ struct i2c_client *client = cyapa->client;
+ struct i2c_msg msgs[] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .len = 1,
+ .buf = ®,
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = len,
+ .buf = values,
+ },
+ };
+
+ ret = i2c_transfer(client->adapter, msgs, 2);
+
+ return (ret == 2) ? len : ((ret < 0) ? ret : -EIO);
+}
+
+/**
+ * cyapa_i2c_write - execute i2c block data write operation
+ * @cyapa: Handle to this driver
+ * @ret: Offset of the data to written in the register map
+ * @len: the data length of bytes to written.
+ * @values: Data to be written
+ *
+ * This executes returns a negative errno code else zero on success.
+ */
+static ssize_t cyapa_i2c_write(struct cyapa *cyapa, u8 reg,
+ size_t len, const u8 *values)
+{
+ int ret;
+ struct i2c_client *client = cyapa->client;
+
+ cyapa->tmp_buf[0] = reg;
+ memcpy(&cyapa->tmp_buf[1], values, len);
+ ret = i2c_master_send(client, cyapa->tmp_buf, len + 1);
+
+ return (ret == (len + 1)) ? 0 : ((ret < 0) ? ret : -EIO);
+}
+
+static void cyapa_default_irq_handler(struct cyapa *cyapa)
+{
+ async_schedule(cyapa_detect_async, cyapa);
+}
+
+/*
+ * Check if device is operational.
+ *
+ * An operational device is responding, has exited bootloader, and has
+ * firmware supported by this driver.
+ *
+ * Returns:
+ * -EBUSY no device or in bootloader
+ * -EIO failure while reading from device
+ * -EAGAIN device is still in bootloader
+ * if ->state = CYAPA_STATE_BL_IDLE, device has invalid firmware
+ * -EINVAL device is in operational mode, but not supported by this driver
+ * 0 device is supported
+ */
+static int cyapa_check_is_operational(struct cyapa *cyapa)
+{
+ int ret;
+
+ ret = cyapa_poll_state(cyapa, 4000);
+ if (ret)
+ return ret;
+
+ switch (cyapa->gen) {
+ case CYAPA_GEN3:
+ cyapa->cyapa_check_fw = NULL;
+ cyapa->cyapa_bl_enter = NULL;
+ cyapa->cyapa_bl_activate = NULL;
+ cyapa->cyapa_bl_initiate = NULL;
+ cyapa->cyapa_update_fw = NULL;
+ cyapa->cyapa_bl_verify_app_integrity = NULL;
+ cyapa->cyapa_bl_deactivate = cyapa_gen3_bl_deactivate;
+ cyapa->cyapa_show_baseline = NULL;
+ cyapa->cyapa_calibrate_store = NULL;
+ cyapa->cyapa_irq_handler = cyapa_gen3_irq_handler;
+ cyapa->cyapa_set_power_mode = cyapa_gen3_set_power_mode;
+ cyapa->cyapa_read_fw = NULL;
+ cyapa->cyapa_read_raw_data = NULL;
+
+ ret = cyapa_gen3_do_operational_check(cyapa);
+ break;
+ default:
+ cyapa->gen = CYAPA_GEN_UNKNOWN;
+ cyapa->cyapa_check_fw = NULL;
+ cyapa->cyapa_bl_enter = NULL;
+ cyapa->cyapa_bl_activate = NULL;
+ cyapa->cyapa_bl_initiate = NULL;
+ cyapa->cyapa_update_fw = NULL;
+ cyapa->cyapa_bl_verify_app_integrity = NULL;
+ cyapa->cyapa_show_baseline = NULL;
+ cyapa->cyapa_calibrate_store = NULL;
+ cyapa->cyapa_irq_handler = cyapa_default_irq_handler;
+ cyapa->cyapa_set_power_mode = NULL;
+ cyapa->cyapa_read_fw = NULL;
+ cyapa->cyapa_read_raw_data = NULL;
+
+ ret = -EAGAIN;
+ break;
+ }
+
+ return ret;
+}
+
+static irqreturn_t cyapa_irq(int irq, void *dev_id)
+{
+ struct cyapa *cyapa = dev_id;
+ struct device *dev = &cyapa->client->dev;
+ struct input_dev *input = cyapa->input;
+ int length;
+
+ if (device_may_wakeup(dev))
+ pm_wakeup_event(dev, 0);
+
+ /*
+ * Don't read input if input device has not been configured.
+ * This check solves a race during probe() between irq_request()
+ * and irq_disable(), since there is no way to request an irq that is
+ * initially disabled.
+ */
+ if (!input || atomic_read(&cyapa->in_detecting))
+ goto out;
+
+ if (cyapa->cyapa_irq_handler)
+ cyapa->cyapa_irq_handler(cyapa);
out:
return IRQ_HANDLED;
}
+/*
+ * Query device for its current operating state.
+ *
+ */
+static int cyapa_get_state(struct cyapa *cyapa)
+{
+ int ret;
+ u8 status[BL_STATUS_SIZE];
+ u8 cmd[32];
+ /* The i2c address of gen4 and gen5 trackpad device must be even. */
+ bool even_addr = ((cyapa->client->addr & 0x0001) == 0);
+ bool smbus = false;
+ int retires = 2;
+
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+
+ /*
+ * Get trackpad status by reading 3 registers starting from 0.
+ * If the device is in the bootloader, this will be BL_HEAD.
+ * If the device is in operation mode, this will be the DATA regs.
+ *
+ */
+ ret = cyapa_i2c_reg_read_block(cyapa, BL_HEAD_OFFSET, BL_STATUS_SIZE,
+ status);
+
+ /*
+ * On smbus systems in OP mode, the i2c_reg_read will fail with
+ * -ETIMEDOUT. In this case, try again using the smbus equivalent
+ * command. This should return a BL_HEAD indicating CYAPA_STATE_OP.
+ */
+ if (cyapa->smbus && (ret == -ETIMEDOUT || ret == -ENXIO)) {
+ if (!even_addr)
+ ret = cyapa_read_block(cyapa,
+ CYAPA_CMD_BL_STATUS, status);
+ smbus = true;
+ }
+ if (ret != BL_STATUS_SIZE)
+ goto error;
+
+ /*
+ * detect trackpad protocol based on characristic registers and bits.
+ */
+ do {
+ if (cyapa->gen == CYAPA_GEN_UNKNOWN ||
+ cyapa->gen == CYAPA_GEN3) {
+ cyapa->gen_detecting = CYAPA_GEN3;
+ ret = cyapa_gen3_state_parse(cyapa,
+ status, BL_STATUS_SIZE);
+ if (ret == 0)
+ goto out_detected;
+ cyapa->gen_detecting = CYAPA_GEN_UNKNOWN;
+ }
+
+ /*
+ * cannot detect communication protocol based on current
+ * charateristic registers and bits.
+ * So write error command to do further detection.
+ * this method only valid on I2C bus.
+ * for smbus interface, it won't have overwrite issue.
+ */
+ if (!smbus) {
+ cmd[0] = 0x00;
+ cmd[1] = 0x00;
+ ret = cyapa_i2c_write(cyapa, 0x00, 2, cmd);
+ if (ret)
+ goto error;
+
+ msleep(50);
+
+ ret = cyapa_i2c_read(cyapa, BL_HEAD_OFFSET,
+ BL_STATUS_SIZE, status);
+ if (ret != BL_STATUS_SIZE)
+ goto error;
+ }
+ } while (--retires > 0 && !smbus);
+
+ goto error;
+
+out_detected:
+ return 0;
+
+error:
+ return (ret < 0) ? ret : -EAGAIN;
+}
+
+/*
+ * Poll device for its status in a loop, waiting up to timeout for a response.
+ *
+ * When the device switches state, it usually takes ~300 ms.
+ * However, when running a new firmware image, the device must calibrate its
+ * sensors, which can take as long as 2 seconds.
+ *
+ * Note: The timeout has granularity of the polling rate, which is 100 ms.
+ *
+ * Returns:
+ * 0 when the device eventually responds with a valid non-busy state.
+ * -ETIMEDOUT if device never responds (too many -EAGAIN)
+ * < 0 other errors
+ */
+static int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout)
+{
+ int ret;
+ int tries = timeout / 100;
+
+ ret = cyapa_get_state(cyapa);
+
+ while ((ret || cyapa->state >= CYAPA_STATE_BL_BUSY) && tries--) {
+ msleep(100);
+ ret = cyapa_get_state(cyapa);
+ }
+
+ return (ret == -EAGAIN || ret == -ETIMEDOUT) ? -ETIMEDOUT : ret;
+}
+
static u8 cyapa_check_adapter_functionality(struct i2c_client *client)
{
u8 ret = CYAPA_ADAPTER_FUNC_NONE;
@@ -823,6 +1196,62 @@ err_free_device:
return ret;
}
+static void cyapa_detect(struct cyapa *cyapa)
+{
+ struct device *dev = &cyapa->client->dev;
+ char *envp[] = {"ERROR=1", NULL};
+ int ret;
+
+ ret = cyapa_check_is_operational(cyapa);
+ if (ret == -ETIMEDOUT)
+ dev_err(dev, "no device detected, %d\n", ret);
+ else if (ret)
+ dev_err(dev, "device detected, but not operational, %d\n", ret);
+
+ if (ret) {
+ kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
+ return;
+ }
+
+ if (!cyapa->input) {
+ ret = cyapa_create_input_dev(cyapa);
+ if (ret)
+ dev_err(dev, "create input_dev instance failed, %d\n",
+ ret);
+
+ enable_irq(cyapa->irq);
+ /*
+ * On some systems, a system crash / warm boot does not reset
+ * the device's current power mode to FULL_ACTIVE.
+ * If such an event happens during suspend, after the device
+ * has been put in a low power mode, the device will still be
+ * in low power mode on a subsequent boot, since there was
+ * never a matching resume().
+ * Handle this by always forcing full power here, when a
+ * device is first detected to be in operational mode.
+ */
+ if (cyapa->cyapa_set_power_mode) {
+ ret = cyapa->cyapa_set_power_mode(cyapa,
+ PWR_MODE_FULL_ACTIVE, 0);
+ if (ret)
+ dev_warn(dev, "set active power failed, %d\n",
+ ret);
+ }
+ }
+}
+
+static void cyapa_detect_async(void *data, async_cookie_t cookie)
+{
+ struct cyapa *cyapa = (struct cyapa *)data;
+
+ if (atomic_read(&cyapa->in_detecting))
+ return;
+
+ atomic_inc(&cyapa->in_detecting);
+ cyapa_detect(cyapa);
+ atomic_dec(&cyapa->in_detecting);
+}
+
static int cyapa_probe(struct i2c_client *client,
const struct i2c_device_id *dev_id)
{
@@ -830,6 +1259,7 @@ static int cyapa_probe(struct i2c_client *client,
u8 adapter_func;
struct cyapa *cyapa;
struct device *dev = &client->dev;
+ union i2c_smbus_data dummy;
adapter_func = cyapa_check_adapter_functionality(client);
if (adapter_func == CYAPA_ADAPTER_FUNC_NONE) {
@@ -837,13 +1267,18 @@ static int cyapa_probe(struct i2c_client *client,
return -EIO;
}
+ /* Make sure there is something at this address */
+ if (dev->of_node && i2c_smbus_xfer(client->adapter, client->addr, 0,
+ I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0)
+ return -ENODEV;
+
cyapa = kzalloc(sizeof(struct cyapa), GFP_KERNEL);
if (!cyapa) {
dev_err(dev, "allocate memory for cyapa failed\n");
return -ENOMEM;
}
- cyapa->gen = CYAPA_GEN3;
+ cyapa->gen = CYAPA_GEN_UNKNOWN;
cyapa->client = client;
i2c_set_clientdata(client, cyapa);
sprintf(cyapa->phys, "i2c-%d-%04x/input0", client->adapter->nr,
@@ -853,23 +1288,7 @@ static int cyapa_probe(struct i2c_client *client,
if (adapter_func == CYAPA_ADAPTER_FUNC_SMBUS)
cyapa->smbus = true;
cyapa->state = CYAPA_STATE_NO_DEVICE;
- ret = cyapa_check_is_operational(cyapa);
- if (ret) {
- dev_err(dev, "device not operational, %d\n", ret);
- goto err_mem_free;
- }
-
- ret = cyapa_create_input_dev(cyapa);
- if (ret) {
- dev_err(dev, "create input_dev instance failed, %d\n", ret);
- goto err_mem_free;
- }
-
- ret = cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE);
- if (ret) {
- dev_err(dev, "set active power failed, %d\n", ret);
- goto err_unregister_device;
- }
+ cyapa->suspend_power_mode = PWR_MODE_SLEEP;
cyapa->irq = client->irq;
ret = request_threaded_irq(cyapa->irq,
@@ -882,12 +1301,14 @@ static int cyapa_probe(struct i2c_client *client,
dev_err(dev, "IRQ request failed: %d\n, ", ret);
goto err_unregister_device;
}
+ disable_irq(cyapa->irq);
+ async_schedule(cyapa_detect_async, cyapa);
return 0;
err_unregister_device:
input_unregister_device(cyapa->input);
-err_mem_free:
+ i2c_set_clientdata(client, NULL);
kfree(cyapa);
return ret;
@@ -899,7 +1320,10 @@ static int cyapa_remove(struct i2c_client *client)
free_irq(cyapa->irq, cyapa);
input_unregister_device(cyapa->input);
- cyapa_set_power_mode(cyapa, PWR_MODE_OFF);
+
+ if (cyapa->cyapa_set_power_mode)
+ cyapa->cyapa_set_power_mode(cyapa, PWR_MODE_OFF, 0);
+ i2c_set_clientdata(client, NULL);
kfree(cyapa);
return 0;
@@ -913,16 +1337,21 @@ static int cyapa_suspend(struct device *dev)
struct cyapa *cyapa = dev_get_drvdata(dev);
disable_irq(cyapa->irq);
+ cyapa->suspended = true;
/*
* Set trackpad device to idle mode if wakeup is allowed,
* otherwise turn off.
*/
- power_mode = device_may_wakeup(dev) ? PWR_MODE_IDLE
+ power_mode = device_may_wakeup(dev) ? cyapa->suspend_power_mode
: PWR_MODE_OFF;
- ret = cyapa_set_power_mode(cyapa, power_mode);
- if (ret < 0)
- dev_err(dev, "set power mode failed, %d\n", ret);
+ if (cyapa->cyapa_set_power_mode) {
+ ret = cyapa->cyapa_set_power_mode(cyapa, power_mode,
+ cyapa->suspend_sleep_time);
+ if (ret < 0)
+ dev_err(dev, "suspend set power mode failed, %d\n",
+ ret);
+ }
if (device_may_wakeup(dev))
cyapa->irq_wake = (enable_irq_wake(cyapa->irq) == 0);
@@ -936,12 +1365,18 @@ static int cyapa_resume(struct device *dev)
if (device_may_wakeup(dev) && cyapa->irq_wake)
disable_irq_wake(cyapa->irq);
+ enable_irq(cyapa->irq);
- ret = cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE);
- if (ret)
- dev_warn(dev, "resume active power failed, %d\n", ret);
+ if (cyapa->cyapa_set_power_mode) {
+ ret = cyapa->cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE,
+ cyapa->suspend_sleep_time);
+ if (ret)
+ dev_warn(dev, "resume active power failed, %d\n", ret);
+ }
- enable_irq(cyapa->irq);
+ async_schedule(cyapa_detect_async, cyapa);
+
+ cyapa->suspended = false;
return 0;
}
#endif /* CONFIG_PM_SLEEP */
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.
[-- Attachment #2: winmail.dat --]
[-- Type: application/ms-tnef, Size: 17557 bytes --]
^ permalink raw reply related
* [PATCH 0/6] input: cyapa: integrated with gen5 trackpad supported in one driver.
From: Dudley Du @ 2014-04-14 7:53 UTC (permalink / raw)
To: Dmitry Torokhov (dmitry.torokhov@gmail.com)
Cc: Benson Leung, Daniel Kurtz, linux-input@vger.kernel.org,
linux-kernel@vger.kernel.org
[-- Attachment #1: Type: text/plain, Size: 1167 bytes --]
This patch set is made based on kernel 3.14.0. It's aimed to
re-architecture the cyapa driver to support the old gen3 trackpad
device and new gen5 trackpad device in one cyapa driver for
easily products support based on customers' requirements,
and add sysfs functions and interfaces supported that required
by users and customers.
Beside this patch, it has 6 patches listed as below.
For these patches each one is patched based on previous one.
patch 1/6: change the architecture of cyapa driver to support function
pointers and applying the device proble function in async thread
to speed up system boot time.
patch 2/6: add gen5 trackpad devices supported in cyapa driver.
patch 3/6: add full power mode and runtime power mode supported.
patch 4/6: enable/disable trackpad device based on LID state.
patch 5/6: add sysfs interfaces supported for gen3 trackpad device.
patch 6/6: add sysfs interfaces supported for gen5 trackpad device.
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.
[-- Attachment #2: winmail.dat --]
[-- Type: application/ms-tnef, Size: 4397 bytes --]
^ permalink raw reply
* Re: [PATCH] input: add support for ALPS v7 protocol device
From: Elaine Chen @ 2014-04-14 3:05 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: david turvene, Kevin Cernekee, linux-input, Justin Clift,
Tommy Will, Qiting Chen
In-Reply-To: <1395219353-27683-1-git-send-email-qiting.chen@cn.alps.com>
Hi, Dmitry,
Would you please send your suggestions for this patch? Thank you!
2014-03-19 16:55 GMT+08:00 Qiting Chen <elaineee66@gmail.com>:
> Here is a patch of supporting ALPS v7 protocol device.
> ALPS v7 protocol device is a clickpad that is currently used on
> Lenovo S430/S435/S530, Lenovo Z410/Z510, HP Odie, HP Revolve 810 G1,
> as well as other machines with ALPS Touchpad of following infomation:
> Device ID = 0x73, 0x03, 0x0a
> Firmware ID = 0x88, 0xb*, 0x**
>suggestion
> A v7 protocol support patch is first relesed 2 months ago:
> http://www.spinics.net/lists/linux-input/msg29084.html
> After that some feedbacks were received from end user. Now this patch fixed the bugs
> reported by them:
> 1) Fix cursor jump when doing a right click drag
> 2) Fix cursor jitter when button clicking
>
> Signed-off-by: Qiting Chen <qiting.chen@cn.alps.com>
> ---
> drivers/input/mouse/alps.c | 560 ++++++++++++++++++++++++++++++++++++++++++---
> drivers/input/mouse/alps.h | 132 +++++++++--
> 2 files changed, 641 insertions(+), 51 deletions(-)
>
> diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c
> index fb15c64..383281f 100644
> --- a/drivers/input/mouse/alps.c
> +++ b/drivers/input/mouse/alps.c
> @@ -32,6 +32,13 @@
> #define ALPS_REG_BASE_RUSHMORE 0xc2c0
> #define ALPS_REG_BASE_PINNACLE 0x0000
>
> +#define LEFT_BUTTON_BIT 0x01
> +#define RIGHT_BUTTON_BIT 0x02
> +
> +#define V7_LARGE_MOVEMENT 130
> +#define V7_DEAD_ZONE_OFFSET_X 72
> +#define V7_DEAD_ZONE_OFFSET_Y 72
> +
> static const struct alps_nibble_commands alps_v3_nibble_commands[] = {
> { PSMOUSE_CMD_SETPOLL, 0x00 }, /* 0 */
> { PSMOUSE_CMD_RESET_DIS, 0x00 }, /* 1 */
> @@ -99,6 +106,7 @@ static const struct alps_nibble_commands alps_v6_nibble_commands[] = {
> #define ALPS_FOUR_BUTTONS 0x40 /* 4 direction button present */
> #define ALPS_PS2_INTERLEAVED 0x80 /* 3-byte PS/2 packet interleaved with
> 6-byte ALPS packet */
> +#define ALPS_BTNLESS 0x100 /* ALPS ClickPad flag */
>
> static const struct alps_model_info alps_model_data[] = {
> { { 0x32, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* Toshiba Salellite Pro M10 */
> @@ -140,6 +148,20 @@ static void alps_set_abs_params_mt(struct alps_data *priv,
> * isn't valid per PS/2 spec.
> */
>
> +static unsigned int alps_pt_distance(struct alps_abs_data *pt0,
> + struct alps_abs_data *pt1)
> +{
> + int vect_x, vect_y;
> +
> + if (!pt0 || !pt1)
> + return 0;
> +
> + vect_x = pt0->x - pt1->x;
> + vect_y = pt0->y - pt1->y;
> +
> + return int_sqrt(vect_x * vect_x + vect_y * vect_y);
> +}
> +
> /* Packet formats are described in Documentation/input/alps.txt */
>
> static bool alps_is_valid_first_byte(struct alps_data *priv,
> @@ -320,8 +342,8 @@ static void alps_process_bitmap_dolphin(struct alps_data *priv,
> end_bit = y_msb - 1;
> box_middle_y = (priv->y_max * (start_bit + end_bit)) /
> (2 * (priv->y_bits - 1));
> - *x1 = fields->x;
> - *y1 = fields->y;
> + *x1 = fields->pt.x;
> + *y1 = fields->pt.y;
> *x2 = 2 * box_middle_x - *x1;
> *y2 = 2 * box_middle_y - *y1;
> }
> @@ -461,6 +483,38 @@ static void alps_report_semi_mt_data(struct input_dev *dev, int num_fingers,
> alps_set_slot(dev, 1, num_fingers == 2, x2, y2);
> }
>
> +static void alps_report_coord_and_btn(struct psmouse *psmouse,
> + struct alps_fields *f)
> +{
> + struct input_dev *dev;
> +
> + if (!psmouse || !f)
> + return;
> +
> + dev = psmouse->dev;
> +
> + if (f->fingers) {
> + input_report_key(dev, BTN_TOUCH, 1);
> + alps_report_semi_mt_data(dev, f->fingers,
> + f->pt_img[0].x, f->pt_img[0].y,
> + f->pt_img[1].x, f->pt_img[1].y);
> + input_mt_report_finger_count(dev, f->fingers);
> +
> + input_report_abs(dev, ABS_X, f->pt_img[0].x);
> + input_report_abs(dev, ABS_Y, f->pt_img[0].y);
> + input_report_abs(dev, ABS_PRESSURE, f->pt_img[0].z);
> + } else {
> + input_report_key(dev, BTN_TOUCH, 0);
> + input_mt_report_finger_count(dev, 0);
> + input_report_abs(dev, ABS_PRESSURE, 0);
> + }
> +
> + input_report_key(dev, BTN_LEFT, f->btn.left);
> + input_report_key(dev, BTN_RIGHT, f->btn.right);
> +
> + input_sync(dev);
> +}
> +
> static void alps_process_trackstick_packet_v3(struct psmouse *psmouse)
> {
> struct alps_data *priv = psmouse->private;
> @@ -523,13 +577,13 @@ static void alps_process_trackstick_packet_v3(struct psmouse *psmouse)
>
> static void alps_decode_buttons_v3(struct alps_fields *f, unsigned char *p)
> {
> - f->left = !!(p[3] & 0x01);
> - f->right = !!(p[3] & 0x02);
> - f->middle = !!(p[3] & 0x04);
> + f->btn.left = !!(p[3] & 0x01);
> + f->btn.right = !!(p[3] & 0x02);
> + f->btn.middle = !!(p[3] & 0x04);
>
> - f->ts_left = !!(p[3] & 0x10);
> - f->ts_right = !!(p[3] & 0x20);
> - f->ts_middle = !!(p[3] & 0x40);
> + f->btn.ts_left = !!(p[3] & 0x10);
> + f->btn.ts_right = !!(p[3] & 0x20);
> + f->btn.ts_middle = !!(p[3] & 0x40);
> }
>
> static void alps_decode_pinnacle(struct alps_fields *f, unsigned char *p,
> @@ -546,10 +600,10 @@ static void alps_decode_pinnacle(struct alps_fields *f, unsigned char *p,
> ((p[2] & 0x7f) << 1) |
> (p[4] & 0x01);
>
> - f->x = ((p[1] & 0x7f) << 4) | ((p[4] & 0x30) >> 2) |
> + f->pt.x = ((p[1] & 0x7f) << 4) | ((p[4] & 0x30) >> 2) |
> ((p[0] & 0x30) >> 4);
> - f->y = ((p[2] & 0x7f) << 4) | (p[4] & 0x0f);
> - f->z = p[5] & 0x7f;
> + f->pt.y = ((p[2] & 0x7f) << 4) | (p[4] & 0x0f);
> + f->pt.z = p[5] & 0x7f;
>
> alps_decode_buttons_v3(f, p);
> }
> @@ -573,9 +627,9 @@ static void alps_decode_dolphin(struct alps_fields *f, unsigned char *p,
> f->is_mp = !!(p[0] & 0x20);
>
> if (!f->is_mp) {
> - f->x = ((p[1] & 0x7f) | ((p[4] & 0x0f) << 7));
> - f->y = ((p[2] & 0x7f) | ((p[4] & 0xf0) << 3));
> - f->z = (p[0] & 4) ? 0 : p[5] & 0x7f;
> + f->pt.x = ((p[1] & 0x7f) | ((p[4] & 0x0f) << 7));
> + f->pt.y = ((p[2] & 0x7f) | ((p[4] & 0xf0) << 3));
> + f->pt.z = (p[0] & 4) ? 0 : p[5] & 0x7f;
> alps_decode_buttons_v3(f, p);
> } else {
> f->fingers = ((p[0] & 0x6) >> 1 |
> @@ -687,7 +741,7 @@ static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
> * with x, y, and z all zero, so these seem to be flukes.
> * Ignore them.
> */
> - if (f.x && f.y && !f.z)
> + if (f.pt.x && f.pt.y && !f.pt.z)
> return;
>
> /*
> @@ -695,12 +749,12 @@ static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
> * to rely on ST data.
> */
> if (!fingers) {
> - x1 = f.x;
> - y1 = f.y;
> - fingers = f.z > 0 ? 1 : 0;
> + x1 = f.pt.x;
> + y1 = f.pt.y;
> + fingers = f.pt.z > 0 ? 1 : 0;
> }
>
> - if (f.z >= 64)
> + if (f.pt.z >= 64)
> input_report_key(dev, BTN_TOUCH, 1);
> else
> input_report_key(dev, BTN_TOUCH, 0);
> @@ -709,22 +763,22 @@ static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
>
> input_mt_report_finger_count(dev, fingers);
>
> - input_report_key(dev, BTN_LEFT, f.left);
> - input_report_key(dev, BTN_RIGHT, f.right);
> - input_report_key(dev, BTN_MIDDLE, f.middle);
> + input_report_key(dev, BTN_LEFT, f.btn.left);
> + input_report_key(dev, BTN_RIGHT, f.btn.right);
> + input_report_key(dev, BTN_MIDDLE, f.btn.middle);
>
> - if (f.z > 0) {
> - input_report_abs(dev, ABS_X, f.x);
> - input_report_abs(dev, ABS_Y, f.y);
> + if (f.pt.z > 0) {
> + input_report_abs(dev, ABS_X, f.pt.x);
> + input_report_abs(dev, ABS_Y, f.pt.y);
> }
> - input_report_abs(dev, ABS_PRESSURE, f.z);
> + input_report_abs(dev, ABS_PRESSURE, f.pt.z);
>
> input_sync(dev);
>
> if (!(priv->quirks & ALPS_QUIRK_TRACKSTICK_BUTTONS)) {
> - input_report_key(dev2, BTN_LEFT, f.ts_left);
> - input_report_key(dev2, BTN_RIGHT, f.ts_right);
> - input_report_key(dev2, BTN_MIDDLE, f.ts_middle);
> + input_report_key(dev2, BTN_LEFT, f.btn.ts_left);
> + input_report_key(dev2, BTN_RIGHT, f.btn.ts_right);
> + input_report_key(dev2, BTN_MIDDLE, f.btn.ts_middle);
> input_sync(dev2);
> }
> }
> @@ -916,6 +970,364 @@ static void alps_process_packet_v4(struct psmouse *psmouse)
> input_sync(dev);
> }
>
> +static bool alps_is_valid_package_v7(struct psmouse *psmouse)
> +{
> + if ((psmouse->pktcnt == 3) && ((psmouse->packet[2] & 0x40) != 0x40))
> + return false;
> + if ((psmouse->pktcnt == 4) && ((psmouse->packet[3] & 0x48) != 0x48))
> + return false;
> + if ((psmouse->pktcnt == 6) && ((psmouse->packet[5] & 0x40) != 0x0))
> + return false;
> + return true;
> +}
> +
> +static int alps_drop_unsupported_packet_v7(struct psmouse *psmouse)
> +{
> + struct alps_data *priv = psmouse->private;
> + int drop = 1;
> +
> + if (priv->r.v7.pkt_id == V7_PACKET_ID_NEW ||
> + priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
> + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI ||
> + priv->r.v7.pkt_id == V7_PACKET_ID_IDLE)
> + drop = 0;
> +
> + return drop;
> +}
> +
> +static unsigned char alps_get_packet_id_v7(char *byte)
> +{
> + unsigned char packet_id;
> +
> + if (byte[4] & 0x40)
> + packet_id = V7_PACKET_ID_TWO;
> + else if (byte[4] & 0x01)
> + packet_id = V7_PACKET_ID_MULTI;
> + else if ((byte[0] & 0x10) && !(byte[4] & 0x43))
> + packet_id = V7_PACKET_ID_NEW;
> + else
> + packet_id = V7_PACKET_ID_IDLE;
> +
> + return packet_id;
> +}
> +
> +static void alps_get_finger_coordinate_v7(struct alps_abs_data *pt,
> + unsigned char *pkt,
> + unsigned char pkt_id)
> +{
> + if ((pkt_id == V7_PACKET_ID_TWO) ||
> + (pkt_id == V7_PACKET_ID_MULTI) ||
> + (pkt_id == V7_PACKET_ID_NEW)) {
> + pt[0].x = ((pkt[2] & 0x80) << 4);
> + pt[0].x |= ((pkt[2] & 0x3F) << 5);
> + pt[0].x |= ((pkt[3] & 0x30) >> 1);
> + pt[0].x |= (pkt[3] & 0x07);
> + pt[0].y = (pkt[1] << 3) | (pkt[0] & 0x07);
> +
> + pt[1].x = ((pkt[3] & 0x80) << 4);
> + pt[1].x |= ((pkt[4] & 0x80) << 3);
> + pt[1].x |= ((pkt[4] & 0x3F) << 4);
> + pt[1].y = ((pkt[5] & 0x80) << 3);
> + pt[1].y |= ((pkt[5] & 0x3F) << 4);
> +
> + if (pkt_id == V7_PACKET_ID_TWO) {
> + pt[1].x &= ~0x000F;
> + pt[1].y |= 0x000F;
> + } else if (pkt_id == V7_PACKET_ID_MULTI) {
> + pt[1].x &= ~0x003F;
> + pt[1].y &= ~0x0020;
> + pt[1].y |= ((pkt[4] & 0x02) << 4);
> + pt[1].y |= 0x001F;
> + } else if (pkt_id == V7_PACKET_ID_NEW) {
> + pt[1].x &= ~0x003F;
> + pt[1].x |= (pkt[0] & 0x20);
> + pt[1].y |= 0x000F;
> + }
> +
> + pt[0].y = 0x7FF - pt[0].y;
> + pt[1].y = 0x7FF - pt[1].y;
> +
> + pt[0].z = (pt[0].x && pt[0].y) ? 62 : 0;
> + pt[1].z = (pt[1].x && pt[1].y) ? 62 : 0;
> + }
> +}
> +
> +static void alps_decode_packet_v7(struct alps_fields *f,
> + unsigned char *p,
> + struct psmouse *psmouse)
> +{
> + struct alps_data *priv = psmouse->private;
> + static struct v7_raw prev_r;
> +
> + priv->r.v7.pkt_id = alps_get_packet_id_v7(p);
> +
> + alps_get_finger_coordinate_v7(f->pt_img, p, priv->r.v7.pkt_id);
> +
> + priv->r.v7.rest_left = 0;
> + priv->r.v7.rest_right = 0;
> + priv->r.v7.additional_fingers = 0;
> + priv->phy_btn = 0;
> +
> + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
> + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI) {
> + priv->r.v7.rest_left = (p[0] & 0x10) >> 4;
> + priv->r.v7.rest_right = (p[0] & 0x20) >> 5;
> + }
> +
> + if (priv->r.v7.pkt_id == V7_PACKET_ID_MULTI)
> + priv->r.v7.additional_fingers = p[5] & 0x03;
> +
> + priv->phy_btn = (p[0] & 0x80) >> 7;
> +
> + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO) {
> + if (f->pt_img[0].z != 0 && f->pt_img[1].z != 0)
> + priv->r.v7.raw_fn = 2;
> + else
> + priv->r.v7.raw_fn = 1;
> + } else if (priv->r.v7.pkt_id == V7_PACKET_ID_MULTI)
> + priv->r.v7.raw_fn = 3 + priv->r.v7.additional_fingers;
> + else if (priv->r.v7.pkt_id == V7_PACKET_ID_IDLE)
> + priv->r.v7.raw_fn = 0;
> + else if (priv->r.v7.pkt_id == V7_PACKET_ID_NEW)
> + priv->r.v7.raw_fn = prev_r.raw_fn;
> +
> + /* It is a trick to bypass firmware bug of older version
> + that 'New' Packet is missed when finger number changed.
> + We fake a 'New' Packet in such cases.*/
> + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
> + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI ||
> + priv->r.v7.pkt_id == V7_PACKET_ID_IDLE) {
> + if (priv->r.v7.raw_fn != prev_r.raw_fn)
> + priv->r.v7.pkt_id = V7_PACKET_ID_NEW;
> + }
> +
> + memcpy(&prev_r, &priv->r.v7, sizeof(struct v7_raw));
> +}
> +
> +static void alps_set_each_pt_attr_v7(struct psmouse *psmouse,
> + struct alps_abs_data *pt,
> + struct alps_bl_pt_attr *pt_attr)
> +{
> + struct alps_data *priv = psmouse->private;
> + unsigned int dist;
> +
> + if (!pt_attr->is_init_pt_got && pt->z != 0) {
> + pt_attr->is_init_pt_got = 1;
> + pt_attr->is_counted = 0;
> + memcpy(&pt_attr->init_pt, pt, sizeof(pt_attr->init_pt));
> + }
> +
> + if (pt->z != 0) {
> + if (pt->y < priv->resting_zone_y_min) {
> + /* A finger is recognized as a non-resting finger
> + if it's position is outside the resting finger zone.*/
> + pt_attr->zone = ZONE_NORMAL;
> + pt_attr->is_counted = 1;
> + } else {
> + /* A finger is recognized as a resting finger if it's
> + position is inside the resting finger zone and there's
> + no large movement from it's touch down position.*/
> + pt_attr->zone = ZONE_RESTING;
> +
> + if (pt->x > priv->x_max / 2)
> + pt_attr->zone |= ZONE_RIGHT_BTN;
> + else
> + pt_attr->zone |= ZONE_LEFT_BTN;
> +
> + /* A resting finger will turn to be a non-resting
> + finger if it has made large movement from it's touch
> + down position. A non-resting finger will never turn
> + to a resting finger before it leaves the touchpad
> + surface.*/
> + if (pt_attr->is_init_pt_got) {
> + dist = alps_pt_distance(pt, &pt_attr->init_pt);
> +
> + if (dist > V7_LARGE_MOVEMENT)
> + pt_attr->is_counted = 1;
> + }
> + }
> + }
> +}
> +
> +static void alps_set_pt_attr_v7(struct psmouse *psmouse,
> + struct alps_fields *f)
> +{
> + struct alps_data *priv = psmouse->private;
> + int i;
> +
> + switch (priv->r.v7.pkt_id) {
> + case V7_PACKET_ID_TWO:
> + case V7_PACKET_ID_MULTI:
> + for (i = 0; i < V7_IMG_PT_NUM; i++) {
> + alps_set_each_pt_attr_v7(psmouse,
> + &f->pt_img[i],
> + &priv->pt_attr[i]);
> + }
> + break;
> + default:
> + /*All finger attributes are cleared when packet ID is
> + 'IDLE', 'New'or other unknown IDs. An 'IDLE' packet
> + indicates that there's no finger and no button activity.
> + A 'NEW' packet indicates the finger position in packet
> + is not continues from previous packet. Such as the
> + condition there's finger placed or lifted. In these cases,
> + finger attributes will be reset.*/
> + memset(priv->pt_attr, 0, sizeof(priv->pt_attr[0]) * 2);
> + break;
> + }
> +}
> +
> +static void alps_cal_output_finger_num_v7(struct psmouse *psmouse,
> + struct alps_fields *f)
> +{
> + struct alps_data *priv = psmouse->private;
> + unsigned int fn = 0;
> + int i;
> +
> + switch (priv->r.v7.pkt_id) {
> + case V7_PACKET_ID_IDLE:
> + case V7_PACKET_ID_NEW:
> + /*No finger is reported when packet ID is 'IDLE' or 'New'.
> + An 'IDLE' packet indicates that there's no finger on touchpad.
> + A 'NEW' packet indicates there's finger placed or lifted.
> + Finger position of 'New' packet is not continues from the
> + previous packet.*/
> + fn = 0;
> + break;
> + case V7_PACKET_ID_TWO:
> + if (f->pt_img[0].z == 0) {
> + /*The first finger slot is zero when a non-resting
> + finger lifted and remaining only one resting finger
> + on touchpad. Hardware report the remaining resting
> + finger in second slot. This resting is ignored*/
> + fn = 0;
> + } else if (f->pt_img[1].z == 0) {
> + /* The second finger slot is zero if there's
> + only one finger*/
> + fn = 1;
> + } else {
> + /*All non-resting fingers will be counted to report*/
> + fn = 0;
> + for (i = 0; i < V7_IMG_PT_NUM; i++) {
> + if (priv->pt_attr[i].is_counted)
> + fn++;
> + }
> +
> + /*In the case that both fingers are
> + resting fingers, report the first one*/
> + if (!priv->pt_attr[0].is_counted &&
> + !priv->pt_attr[1].is_counted) {
> + fn = 1;
> + }
> + }
> + break;
> + case V7_PACKET_ID_MULTI:
> + /*A packet ID 'MULTI' indicats that at least 3 non-resting
> + finger exist.*/
> + fn = 3 + priv->r.v7.additional_fingers;
> + break;
> + }
> +
> + f->fingers = fn;
> +}
> +
> +static void alps_button_dead_zone_filter(struct psmouse *psmouse,
> + struct alps_fields *f,
> + struct alps_fields *prev_f)
> +{
> + struct alps_data *priv = psmouse->private;
> + int dx, dy;
> +
> + if (priv->prev_phy_btn == 0 && priv->phy_btn != 0) {
> + memcpy(&priv->pt_attr[0].init_dead_pt,
> + &f->pt_img[0],
> + sizeof(struct alps_abs_data));
> + }
> +
> + if (priv->pt_attr[0].init_dead_pt.x != 0 &&
> + priv->pt_attr[0].init_dead_pt.x != 0) {
> + dx = f->pt_img[0].x - priv->pt_attr[0].init_dead_pt.x;
> + dy = f->pt_img[0].y - priv->pt_attr[0].init_dead_pt.y;
> + if ((abs(dx) > V7_DEAD_ZONE_OFFSET_X) ||
> + (abs(dy) > V7_DEAD_ZONE_OFFSET_Y)) {
> + memset(&priv->pt_attr[0].init_dead_pt, 0,
> + sizeof(struct alps_abs_data));
> + priv->btn_delay_cnt = 0;
> + } else {
> + memcpy(&f->pt_img[0],
> + &prev_f->pt_img[0],
> + sizeof(struct alps_abs_data));
> + if (priv->prev_phy_btn == 0 && priv->phy_btn != 0)
> + priv->btn_delay_cnt = 2;
> + }
> + }
> +
> + if (priv->btn_delay_cnt > 0) {
> + f->btn.left = 0;
> + f->btn.right = 0;
> + priv->btn_delay_cnt--;
> + }
> +}
> +
> +static void alps_assign_buttons_v7(struct psmouse *psmouse,
> + struct alps_fields *f,
> + struct alps_fields *prev_f)
> +{
> + struct alps_data *priv = psmouse->private;
> +
> + if (priv->phy_btn) {
> + if (!priv->prev_phy_btn) {
> + /* Report a right click as long as there's finger on
> + right button zone. Othrewise, report a left click.*/
> + if (priv->r.v7.rest_right ||
> + priv->pt_attr[0].zone & ZONE_RIGHT_BTN ||
> + priv->pt_attr[1].zone & ZONE_RIGHT_BTN) {
> + f->btn.right = 1;
> + priv->pressed_btn_bits |= RIGHT_BUTTON_BIT;
> + } else {
> + f->btn.left = 1;
> + priv->pressed_btn_bits |= LEFT_BUTTON_BIT;
> + }
> + } else {
> + if (priv->pressed_btn_bits & RIGHT_BUTTON_BIT)
> + f->btn.right = 1;
> + if (priv->pressed_btn_bits & LEFT_BUTTON_BIT)
> + f->btn.left = 1;
> + }
> + } else {
> + priv->pressed_btn_bits = 0;
> + f->btn.right = 0;
> + f->btn.left = 0;
> + }
> +
> + alps_button_dead_zone_filter(psmouse, f, prev_f);
> +
> + priv->prev_phy_btn = priv->phy_btn;
> +}
> +
> +static void alps_process_packet_v7(struct psmouse *psmouse)
> +{
> + struct alps_data *priv = psmouse->private;
> + struct alps_fields f = {0};
> + static struct alps_fields prev_f;
> + unsigned char *packet = psmouse->packet;
> +
> + priv->decode_fields(&f, packet, psmouse);
> +
> + if (alps_drop_unsupported_packet_v7(psmouse))
> + return;
> +
> + alps_set_pt_attr_v7(psmouse, &f);
> +
> + alps_cal_output_finger_num_v7(psmouse, &f);
> +
> + alps_assign_buttons_v7(psmouse, &f, &prev_f);
> +
> + alps_report_coord_and_btn(psmouse, &f);
> +
> + memcpy(&prev_f, &f, sizeof(struct alps_fields));
> +}
> +
> static void alps_report_bare_ps2_packet(struct psmouse *psmouse,
> unsigned char packet[],
> bool report_buttons)
> @@ -1080,6 +1492,14 @@ static psmouse_ret_t alps_process_byte(struct psmouse *psmouse)
> return PSMOUSE_BAD_DATA;
> }
>
> + if ((priv->proto_version == ALPS_PROTO_V7 &&
> + !alps_is_valid_package_v7(psmouse))) {
> + psmouse_dbg(psmouse, "refusing packet[%i] = %x\n",
> + psmouse->pktcnt - 1,
> + psmouse->packet[psmouse->pktcnt - 1]);
> + return PSMOUSE_BAD_DATA;
> + }
> +
> if (psmouse->pktcnt == psmouse->pktsize) {
> priv->process_packet(psmouse);
> return PSMOUSE_FULL_PACKET;
> @@ -1192,6 +1612,22 @@ static int alps_rpt_cmd(struct psmouse *psmouse, int init_command,
> return 0;
> }
>
> +static int alps_check_valid_firmware_id(unsigned char id[])
> +{
> + int valid = 1;
> +
> + if (id[0] == 0x73)
> + valid = 1;
> + else if (id[0] == 0x88) {
> + if ((id[1] == 0x07) ||
> + (id[1] == 0x08) ||
> + ((id[1] & 0xf0) == 0xB0))
> + valid = 1;
> + }
> +
> + return valid;
> +}
> +
> static int alps_enter_command_mode(struct psmouse *psmouse)
> {
> unsigned char param[4];
> @@ -1201,8 +1637,7 @@ static int alps_enter_command_mode(struct psmouse *psmouse)
> return -1;
> }
>
> - if ((param[0] != 0x88 || (param[1] != 0x07 && param[1] != 0x08)) &&
> - param[0] != 0x73) {
> + if (!alps_check_valid_firmware_id(param)) {
> psmouse_dbg(psmouse,
> "unknown response while entering command mode\n");
> return -1;
> @@ -1704,6 +2139,36 @@ error:
> return ret;
> }
>
> +static int alps_hw_init_v7(struct psmouse *psmouse)
> +{
> + struct ps2dev *ps2dev = &psmouse->ps2dev;
> + int reg_val, ret = -1;
> +
> + if (alps_enter_command_mode(psmouse))
> + goto error;
> +
> + reg_val = alps_command_mode_read_reg(psmouse, 0xc2d9);
> + if (reg_val == -1)
> + goto error;
> +
> + if (alps_command_mode_write_reg(psmouse, 0xc2c9, 0x64))
> + goto error;
> +
> + reg_val = alps_command_mode_read_reg(psmouse, 0xc2c4);
> + if (reg_val == -1)
> + goto error;
> +
> + if (__alps_command_mode_write_reg(psmouse, reg_val | 0x02))
> + goto error;
> +
> + alps_exit_command_mode(psmouse);
> + return ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE);
> +
> +error:
> + alps_exit_command_mode(psmouse);
> + return ret;
> +}
> +
> /* Must be in command mode when calling this function */
> static int alps_absolute_mode_v4(struct psmouse *psmouse)
> {
> @@ -1875,6 +2340,7 @@ static void alps_set_defaults(struct alps_data *priv)
> priv->set_abs_params = alps_set_abs_params_st;
> priv->x_max = 1023;
> priv->y_max = 767;
> + priv->slot_number = 1;
> break;
> case ALPS_PROTO_V3:
> priv->hw_init = alps_hw_init_v3;
> @@ -1883,6 +2349,7 @@ static void alps_set_defaults(struct alps_data *priv)
> priv->decode_fields = alps_decode_pinnacle;
> priv->nibble_commands = alps_v3_nibble_commands;
> priv->addr_command = PSMOUSE_CMD_RESET_WRAP;
> + priv->slot_number = 2;
> break;
> case ALPS_PROTO_V4:
> priv->hw_init = alps_hw_init_v4;
> @@ -1890,6 +2357,7 @@ static void alps_set_defaults(struct alps_data *priv)
> priv->set_abs_params = alps_set_abs_params_mt;
> priv->nibble_commands = alps_v4_nibble_commands;
> priv->addr_command = PSMOUSE_CMD_DISABLE;
> + priv->slot_number = 2;
> break;
> case ALPS_PROTO_V5:
> priv->hw_init = alps_hw_init_dolphin_v1;
> @@ -1905,6 +2373,7 @@ static void alps_set_defaults(struct alps_data *priv)
> priv->y_max = 660;
> priv->x_bits = 23;
> priv->y_bits = 12;
> + priv->slot_number = 2;
> break;
> case ALPS_PROTO_V6:
> priv->hw_init = alps_hw_init_v6;
> @@ -1913,6 +2382,28 @@ static void alps_set_defaults(struct alps_data *priv)
> priv->nibble_commands = alps_v6_nibble_commands;
> priv->x_max = 2047;
> priv->y_max = 1535;
> + priv->slot_number = 2;
> + break;
> + case ALPS_PROTO_V7:
> + priv->hw_init = alps_hw_init_v7;
> + priv->process_packet = alps_process_packet_v7;
> + priv->decode_fields = alps_decode_packet_v7;
> + priv->set_abs_params = alps_set_abs_params_mt;
> + priv->nibble_commands = alps_v3_nibble_commands;
> + priv->addr_command = PSMOUSE_CMD_RESET_WRAP;
> + priv->x_max = 0xfff;
> + priv->y_max = 0x7ff;
> + priv->resting_zone_y_min = 0x654;
> + priv->byte0 = 0x48;
> + priv->mask0 = 0x48;
> + priv->flags = 0;
> + priv->slot_number = 2;
> +
> + priv->phy_btn = 0;
> + priv->prev_phy_btn = 0;
> + priv->btn_delay_cnt = 0;
> + priv->pressed_btn_bits = 0;
> + memset(priv->pt_attr, 0, sizeof(priv->pt_attr[0]) * 2);
> break;
> }
> }
> @@ -1982,6 +2473,11 @@ static int alps_identify(struct psmouse *psmouse, struct alps_data *priv)
> return -EIO;
> else
> return 0;
> + } else if (ec[0] == 0x88 && (ec[1] & 0xf0) == 0xB0) {
> + priv->proto_version = ALPS_PROTO_V7;
> + alps_set_defaults(priv);
> +
> + return 0;
> } else if (ec[0] == 0x88 && ec[1] == 0x08) {
> priv->proto_version = ALPS_PROTO_V3;
> alps_set_defaults(priv);
> @@ -2045,7 +2541,7 @@ static void alps_set_abs_params_mt(struct alps_data *priv,
> struct input_dev *dev1)
> {
> set_bit(INPUT_PROP_SEMI_MT, dev1->propbit);
> - input_mt_init_slots(dev1, 2, 0);
> + input_mt_init_slots(dev1, priv->slot_number, 0);
> input_set_abs_params(dev1, ABS_MT_POSITION_X, 0, priv->x_max, 0, 0);
> input_set_abs_params(dev1, ABS_MT_POSITION_Y, 0, priv->y_max, 0, 0);
>
> diff --git a/drivers/input/mouse/alps.h b/drivers/input/mouse/alps.h
> index 03f88b6..dedbd27 100644
> --- a/drivers/input/mouse/alps.h
> +++ b/drivers/input/mouse/alps.h
> @@ -18,11 +18,36 @@
> #define ALPS_PROTO_V4 4
> #define ALPS_PROTO_V5 5
> #define ALPS_PROTO_V6 6
> +#define ALPS_PROTO_V7 7
> +
> +#define MAX_IMG_PT_NUM 5
> +#define V7_IMG_PT_NUM 2
> +
> +#define ZONE_NORMAL 0x01
> +#define ZONE_RESTING 0x02
> +#define ZONE_LEFT_BTN 0x04
> +#define ZONE_RIGHT_BTN 0x08
>
> #define DOLPHIN_COUNT_PER_ELECTRODE 64
> #define DOLPHIN_PROFILE_XOFFSET 8 /* x-electrode offset */
> #define DOLPHIN_PROFILE_YOFFSET 1 /* y-electrode offset */
>
> +/*
> + * enum V7_PACKET_ID - defines the packet type for V7
> + * V7_PACKET_ID_IDLE: There's no finger and no button activity.
> + * V7_PACKET_ID_TWO: There's one or two non-resting fingers on touchpad
> + * or there's button activities.
> + * V7_PACKET_ID_MULTI: There are at least three non-resting fingers.
> + * V7_PACKET_ID_NEW: The finger position in slot is not continues from
> + * previous packet.
> +*/
> +enum V7_PACKET_ID {
> + V7_PACKET_ID_IDLE,
> + V7_PACKET_ID_TWO,
> + V7_PACKET_ID_MULTI,
> + V7_PACKET_ID_NEW,
> +};
> +
> /**
> * struct alps_model_info - touchpad ID table
> * @signature: E7 response string to match.
> @@ -66,15 +91,7 @@ struct alps_nibble_commands {
> };
>
> /**
> - * struct alps_fields - decoded version of the report packet
> - * @x_map: Bitmap of active X positions for MT.
> - * @y_map: Bitmap of active Y positions for MT.
> - * @fingers: Number of fingers for MT.
> - * @x: X position for ST.
> - * @y: Y position for ST.
> - * @z: Z position for ST.
> - * @first_mp: Packet is the first of a multi-packet report.
> - * @is_mp: Packet is part of a multi-packet report.
> + * struct alps_btn - decoded version of the button status
> * @left: Left touchpad button is active.
> * @right: Right touchpad button is active.
> * @middle: Middle touchpad button is active.
> @@ -82,16 +99,7 @@ struct alps_nibble_commands {
> * @ts_right: Right trackstick button is active.
> * @ts_middle: Middle trackstick button is active.
> */
> -struct alps_fields {
> - unsigned int x_map;
> - unsigned int y_map;
> - unsigned int fingers;
> - unsigned int x;
> - unsigned int y;
> - unsigned int z;
> - unsigned int first_mp:1;
> - unsigned int is_mp:1;
> -
> +struct alps_btn {
> unsigned int left:1;
> unsigned int right:1;
> unsigned int middle:1;
> @@ -102,6 +110,73 @@ struct alps_fields {
> };
>
> /**
> + * struct alps_btn - decoded version of the X Y Z postion for ST.
> + * @x: X position for ST.
> + * @y: Y position for ST.
> + * @z: Z position for ST.
> + */
> +struct alps_abs_data {
> + unsigned int x;
> + unsigned int y;
> + unsigned int z;
> +};
> +
> +/**
> + * struct alps_fields - decoded version of the report packet
> + * @fingers: Number of fingers for MT.
> + * @pt: X Y Z postion for ST.
> + * @pt: X Y Z postion for image MT.
> + * @x_map: Bitmap of active X positions for MT.
> + * @y_map: Bitmap of active Y positions for MT.
> + * @first_mp: Packet is the first of a multi-packet report.
> + * @is_mp: Packet is part of a multi-packet report.
> + * @btn: Button activity status
> + */
> +struct alps_fields {
> + unsigned int fingers;
> + struct alps_abs_data pt;
> + struct alps_abs_data pt_img[MAX_IMG_PT_NUM];
> + unsigned int x_map;
> + unsigned int y_map;
> + unsigned int first_mp:1;
> + unsigned int is_mp:1;
> + struct alps_btn btn;
> +};
> +
> +/**
> + * struct v7_raw - data decoded from raw packet for V7.
> + * @pkt_id: An id that specifies the type of packet.
> + * @additional_fingers: Number of additional finger that is neighter included
> + * in pt slot nor reflected in rest_left and rest_right flag of data packet.
> + * @rest_left: There are fingers on left resting zone.
> + * @rest_right: There are fingers on right resting zone.
> + * @raw_fn: The number of finger on touchpad.
> + */
> +struct v7_raw {
> + unsigned char pkt_id;
> + unsigned int additional_fingers;
> + unsigned char rest_left;
> + unsigned char rest_right;
> + unsigned char raw_fn;
> +};
> +
> +/**
> + * struct alps_bl_pt_attr - generic attributes of touch points for buttonless device
> + * @zone: The part of touchpad that the touch point locates
> + * @is_counted: The touch point is not a resting finger.
> + * @is_init_pt_got: The touch down point is got.
> + * @init_pt: The X Y Z position of the touch down point.
> + * @init_dead_pt: The touch down point of a finger used by dead zone process.
> + */
> +struct alps_bl_pt_attr {
> + unsigned char zone;
> + unsigned char is_counted;
> + unsigned char is_init_pt_got;
> + struct alps_abs_data init_pt;
> + struct alps_abs_data init_dead_pt;
> +};
> +
> +/**
> * struct alps_data - private data structure for the ALPS driver
> * @dev2: "Relative" device used to report trackstick or mouse activity.
> * @phys: Physical path for the relative device.
> @@ -116,8 +191,10 @@ struct alps_fields {
> * @flags: Additional device capabilities (passthrough port, trackstick, etc.).
> * @x_max: Largest possible X position value.
> * @y_max: Largest possible Y position value.
> + * @resting_zone_y_min: Smallest Y postion value of the bottom resting zone.
> * @x_bits: Number of X bits in the MT bitmap.
> * @y_bits: Number of Y bits in the MT bitmap.
> + * @img_fingers: Number of image fingers.
> * @hw_init: Protocol-specific hardware init function.
> * @process_packet: Protocol-specific function to process a report packet.
> * @decode_fields: Protocol-specific function to read packet bitfields.
> @@ -132,6 +209,11 @@ struct alps_fields {
> * @fingers: Number of fingers from last MT report.
> * @quirks: Bitmap of ALPS_QUIRK_*.
> * @timer: Timer for flushing out the final report packet in the stream.
> + * @v7: Data decoded from raw packet for V7
> + * @phy_btn: Physical button is active.
> + * @prev_phy_btn: Physical button of previous packet is active.
> + * @pressed_btn_bits: Pressed positon of button zone
> + * @pt_attr: Generic attributes of touch points for buttonless device.
> */
> struct alps_data {
> struct input_dev *dev2;
> @@ -145,8 +227,10 @@ struct alps_data {
> unsigned char flags;
> int x_max;
> int y_max;
> + int resting_zone_y_min;
> int x_bits;
> int y_bits;
> + unsigned char slot_number;
>
> int (*hw_init)(struct psmouse *psmouse);
> void (*process_packet)(struct psmouse *psmouse);
> @@ -161,6 +245,16 @@ struct alps_data {
> int fingers;
> u8 quirks;
> struct timer_list timer;
> +
> + /* these are used for buttonless touchpad*/
> + union {
> + struct v7_raw v7;
> + } r;
> + unsigned char phy_btn;
> + unsigned char prev_phy_btn;
> + unsigned char btn_delay_cnt;
> + unsigned char pressed_btn_bits;
> + struct alps_bl_pt_attr pt_attr[MAX_IMG_PT_NUM];
> };
>
> #define ALPS_QUIRK_TRACKSTICK_BUTTONS 1 /* trakcstick buttons in trackstick packet */
> --
> 1.8.3.2
>
^ permalink raw reply
* Re: [PATCH resend 2/2] input/serio/8042: Add firmware_id support
From: Dmitry Torokhov @ 2014-04-13 8:23 UTC (permalink / raw)
To: Hans de Goede
Cc: Matthew Garrett, Benjamin Tissoires, Peter Hutterer, linux-input
In-Reply-To: <53465E19.1060705@redhat.com>
On Thu, Apr 10, 2014 at 11:02:17AM +0200, Hans de Goede wrote:
> Hi,
>
> On 04/09/2014 10:09 PM, Dmitry Torokhov wrote:
> > On Wed, Apr 09, 2014 at 07:29:26PM +0100, Matthew Garrett wrote:
> >> On Wed, Apr 09, 2014 at 11:24:34AM -0700, Dmitry Torokhov wrote:
> >>
> >>> Do we need all IDs? I'd expect we only interested in HID, not CIDs?
> >>
> >> I think HID handles the cases we've seen so far, but we could imagine a
> >> system vendor providing their own HID, a trackpad vendor's CID and then
> >> the generic mouse CID. It seems better to err on the side of including
> >> them.
> >
> > OK, fair enough. Another question - do we want to prefix IDs with "PNP:"
> > prefix so that if we add device tree in the future we'll know what kind
> > of IDs we are dealing with?
>
> I'm a bit divided on this, adding a "PNP: " prefix will make it a bit harder
> to parse, OTOH once we will have other users like devicetree knowing where
> the info comes from will be very useful. To me in the end the latter argument
> wins. Let me know if you agree and I'll do a v3 adding the PNP: prefix.
Yes please.
--
Dmitry
^ permalink raw reply
* Re: [PATCH 2/4] serio: Add pnp_id to struct serio
From: Dmitry Torokhov @ 2014-04-13 8:22 UTC (permalink / raw)
To: Hans de Goede; +Cc: Benjamin Tissoires, Peter Hutterer, linux-input
In-Reply-To: <53465049.1070305@redhat.com>
On Thu, Apr 10, 2014 at 10:03:21AM +0200, Hans de Goede wrote:
> Hi,
>
> On 04/09/2014 08:06 PM, Dmitry Torokhov wrote:
> > Hi Hans,
> >
> > On Wed, Apr 09, 2014 at 04:02:59PM +0200, Hans de Goede wrote:
> >> Serio device drivers need access to the pnp_id of the serio port, windows
> >> drivers bind by the pnp-id and some drivers need to know the exact pnp-id
> >> used so they know exactly with which hardware model / revision they are
> >> dealing with.
> >>
> >> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
> >> ---
> >> include/linux/serio.h | 2 ++
> >> 1 file changed, 2 insertions(+)
> >>
> >> diff --git a/include/linux/serio.h b/include/linux/serio.h
> >> index 9f779c7..6532440 100644
> >> --- a/include/linux/serio.h
> >> +++ b/include/linux/serio.h
> >> @@ -16,6 +16,7 @@
> >> #include <linux/mutex.h>
> >> #include <linux/device.h>
> >> #include <linux/mod_devicetable.h>
> >> +#include <linux/pnp.h>
> >> #include <uapi/linux/serio.h>
> >>
> >> struct serio {
> >> @@ -28,6 +29,7 @@ struct serio {
> >> bool manual_bind;
> >>
> >> struct serio_device_id id;
> >> + struct pnp_id *pnp_id;
> >
> > Why do we need this if we are already adding generic 'firmware_id'
> > attribute?
>
> In patch 4/4 we do pnp-id matching in synaptics.c to set the new
> INPUT_PROP_TOPBUTTONPAD property on relevant touchpads. Since the kernel
> has a special pnp_id type + pnp_id matching code, the correct thing to
> do is to use that for in kernel pnp-id matching, which means that we
> need to propagate the pnp-id to the serio drivers.
I disagree that it is good enough reason for putting platform-specific
data in a generic structure. If you really want to use generic PNP
matching code you can just convert firmware_id back to pnp_id in
Synaptics driver.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH v4 4/9] input: misc: Add driver for AXP20x Power Enable Key
From: Dmitry Torokhov @ 2014-04-13 8:17 UTC (permalink / raw)
To: Carlo Caione
Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw,
maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8,
hdegoede-H+wXaHxf7aLQT0dZR+AlfA, emilio-0Z03zUJReD5OxF6Tv1QG9Q,
wens-jdAy2FN1RRM, sameo-VuQAYsv1563Yd54FQh9/CA,
linux-input-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA,
lgirdwood-Re5JQEeQqe8AvxtiuMwx3w, broonie-DgEjT+Ai2ygdnm+yROfE0A,
lee.jones-QSEj5FYQhm4dnm+yROfE0A,
boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8
In-Reply-To: <1397209093-10077-5-git-send-email-carlo-KA+7E9HrN00dnm+yROfE0A@public.gmane.org>
On Fri, Apr 11, 2014 at 11:38:08AM +0200, Carlo Caione wrote:
> This patch add support for the Power Enable Key found on MFD AXP202 and
> AXP209. Besides the basic support for the button, the driver adds two
> entries in sysfs to configure the time delay for power on/off.
>
> Signed-off-by: Carlo Caione <carlo-KA+7E9HrN00dnm+yROfE0A@public.gmane.org>
> ---
> drivers/input/misc/Kconfig | 11 ++
> drivers/input/misc/Makefile | 1 +
> drivers/input/misc/axp20x-pek.c | 261 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 273 insertions(+)
> create mode 100644 drivers/input/misc/axp20x-pek.c
>
> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
> index 7904ab0..87244fb 100644
> --- a/drivers/input/misc/Kconfig
> +++ b/drivers/input/misc/Kconfig
> @@ -393,6 +393,17 @@ config INPUT_RETU_PWRBUTTON
> To compile this driver as a module, choose M here. The module will
> be called retu-pwrbutton.
>
> +config INPUT_AXP20X_PEK
> + tristate "X-Powers AXP20X power button driver"
> + depends on MFD_AXP20X
> + help
> + Say Y here if you want to enable power key reporting via the
> + AXP20X PMIC.
> +
> + To compile this driver as a module, choose M here. The module will
> + be called axp20x-pek.
> +
> +
> config INPUT_TWL4030_PWRBUTTON
> tristate "TWL4030 Power button Driver"
> depends on TWL4030_CORE
> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
> index cda71fc..624abf5 100644
> --- a/drivers/input/misc/Makefile
> +++ b/drivers/input/misc/Makefile
> @@ -50,6 +50,7 @@ obj-$(CONFIG_INPUT_POWERMATE) += powermate.o
> obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o
> obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o
> obj-$(CONFIG_INPUT_RETU_PWRBUTTON) += retu-pwrbutton.o
> +obj-$(CONFIG_INPUT_AXP20X_PEK) += axp20x-pek.o
> obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER) += rotary_encoder.o
> obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o
> obj-$(CONFIG_INPUT_SIRFSOC_ONKEY) += sirfsoc-onkey.o
> diff --git a/drivers/input/misc/axp20x-pek.c b/drivers/input/misc/axp20x-pek.c
> new file mode 100644
> index 0000000..a9112bd
> --- /dev/null
> +++ b/drivers/input/misc/axp20x-pek.c
> @@ -0,0 +1,261 @@
> +/*
> + * axp20x power button driver.
> + *
> + * Copyright (C) 2013 Carlo Caione <carlo-KA+7E9HrN00dnm+yROfE0A@public.gmane.org>
> + *
> + * This file is subject to the terms and conditions of the GNU General
> + * Public License. See the file "COPYING" in the main directory of this
> + * archive for more details.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/errno.h>
> +#include <linux/irq.h>
> +#include <linux/init.h>
> +#include <linux/input.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/axp20x.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +
> +#define AXP20X_PEK_STARTUP_MASK (0xc0)
> +#define AXP20X_PEK_SHUTDOWN_MASK (0x03)
> +
> +struct axp20x_pek {
> + struct axp20x_dev *axp20x;
> + struct input_dev *input;
> + int irq_dbr;
> + int irq_dbf;
> +};
> +
> +struct axp20x_time {
> + unsigned int time;
> + unsigned int idx;
> +};
> +
> +static const struct axp20x_time startup_time[] = {
> + { .time = 128, .idx = 0 },
> + { .time = 1000, .idx = 2 },
> + { .time = 3000, .idx = 1 },
> + { .time = 2000, .idx = 3 },
> +};
> +
> +static const struct axp20x_time shutdown_time[] = {
> + { .time = 4000, .idx = 0 },
> + { .time = 6000, .idx = 1 },
> + { .time = 8000, .idx = 2 },
> + { .time = 10000, .idx = 3 },
> +};
> +
> +struct axp20x_pek_ext_attr {
> + const struct axp20x_time *p_time;
> + unsigned int mask;
> +};
> +
> +static struct axp20x_pek_ext_attr axp20x_pek_startup_ext_attr = {
> + .p_time = startup_time,
> + .mask = AXP20X_PEK_STARTUP_MASK,
> +};
> +
> +static struct axp20x_pek_ext_attr axp20x_pek_shutdown_ext_attr = {
> + .p_time = shutdown_time,
> + .mask = AXP20X_PEK_SHUTDOWN_MASK,
> +};
> +
> +static struct axp20x_pek_ext_attr *get_axp_ext_attr(struct device_attribute *attr)
> +{
> + return container_of(attr, struct dev_ext_attribute, attr)->var;
> +}
> +
> +static ssize_t axp20x_show_ext_attr(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
> + struct axp20x_pek_ext_attr *axp20x_ea = get_axp_ext_attr(attr);
> + unsigned int val;
> + int ret, i;
> +
> + ret = regmap_read(axp20x_pek->axp20x->regmap, AXP20X_PEK_KEY, &val);
> + if (ret != 0)
> + return ret;
> +
> + val &= axp20x_ea->mask;
> + val >>= ffs(axp20x_ea->mask) - 1;
> +
> + for (i = 0; i < 4; i++)
> + if (val == axp20x_ea->p_time[i].idx)
> + val = axp20x_ea->p_time[i].time;
> +
> + return sprintf(buf, "%ums\n", val);
Please do not print ums and instead document the units in sysfs ABI
docs.
> +}
> +
> +static ssize_t axp20x_store_ext_attr(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
> + struct axp20x_pek_ext_attr *axp20x_ea = get_axp_ext_attr(attr);
> + char val_str[20];
> + size_t len;
> + int ret, i;
> + unsigned int val, idx = 0;
> + unsigned int best_err = UINT_MAX;
> +
> + val_str[sizeof(val_str) - 1] = '\0';
> + strncpy(val_str, buf, sizeof(val_str) - 1);
> + len = strlen(val_str);
> +
> + if (len && val_str[len - 1] == '\n')
> + val_str[len - 1] = '\0';
> +
> + ret = kstrtouint(val_str, 10, &val);
> + if (ret)
> + return ret;
> +
> + for (i = 3; i >= 0; i--) {
> + unsigned int err;
> +
> + err = abs(axp20x_ea->p_time[i].time - val);
> + if (err < best_err) {
> + best_err = err;
> + idx = axp20x_ea->p_time[i].idx;
> + }
> +
> + if (!err)
> + break;
> + }
> +
> + idx <<= ffs(axp20x_ea->mask) - 1;
> + ret = regmap_update_bits(axp20x_pek->axp20x->regmap,
> + AXP20X_PEK_KEY,
> + axp20x_ea->mask, idx);
> + if (ret != 0)
> + return -EINVAL;
> + return count;
> +}
> +
> +static struct dev_ext_attribute axp20x_dev_attr_startup = {
> + .attr = __ATTR(startup, 0644, axp20x_show_ext_attr, axp20x_store_ext_attr),
> + .var = &axp20x_pek_startup_ext_attr
> +};
> +
> +static struct dev_ext_attribute axp20x_dev_attr_shutdown = {
> + .attr = __ATTR(shutdown, 0644, axp20x_show_ext_attr, axp20x_store_ext_attr),
> + .var = &axp20x_pek_shutdown_ext_attr
> +};
> +
> +static irqreturn_t axp20x_pek_irq(int irq, void *pwr)
> +{
> + struct input_dev *idev = pwr;
> + struct axp20x_pek *axp20x_pek = input_get_drvdata(idev);
> +
> + if (irq == axp20x_pek->irq_dbr)
> + input_report_key(idev, KEY_POWER, true);
> + else if (irq == axp20x_pek->irq_dbf)
> + input_report_key(idev, KEY_POWER, false);
> +
> + input_sync(idev);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int axp20x_pek_probe(struct platform_device *pdev)
> +{
> + struct axp20x_pek *axp20x_pek;
> + struct axp20x_dev *axp20x;
> + struct input_dev *idev;
> + int error;
> +
> + axp20x_pek = devm_kzalloc(&pdev->dev, sizeof(struct axp20x_pek),
> + GFP_KERNEL);
> + if (!axp20x_pek)
> + return -ENOMEM;
> +
> + axp20x_pek->axp20x = dev_get_drvdata(pdev->dev.parent);
> + axp20x = axp20x_pek->axp20x;
> +
> + axp20x_pek->irq_dbr = platform_get_irq_byname(pdev, "PEK_DBR");
> + if (axp20x_pek->irq_dbr < 0) {
> + dev_err(&pdev->dev, "No IRQ for PEK_DBR, error=%d\n",
> + axp20x_pek->irq_dbr);
> + return axp20x_pek->irq_dbr;
> + }
> + axp20x_pek->irq_dbr = regmap_irq_get_virq(axp20x->regmap_irqc,
> + axp20x_pek->irq_dbr);
> +
> + axp20x_pek->irq_dbf = platform_get_irq_byname(pdev, "PEK_DBF");
> + if (axp20x_pek->irq_dbf < 0) {
> + dev_err(&pdev->dev, "No IRQ for PEK_DBF, error=%d\n",
> + axp20x_pek->irq_dbf);
> + return axp20x_pek->irq_dbf;
> + }
> + axp20x_pek->irq_dbf = regmap_irq_get_virq(axp20x->regmap_irqc,
> + axp20x_pek->irq_dbf);
> +
> + axp20x_pek->input = devm_input_allocate_device(&pdev->dev);
> + if (!axp20x_pek->input)
> + return -ENOMEM;
> +
> + idev = axp20x_pek->input;
> +
> + idev->name = "axp20x-pek";
> + idev->phys = "m1kbd/input2";
> + idev->dev.parent = &pdev->dev;
> +
> + input_set_capability(idev, EV_KEY, KEY_POWER);
> +
> + input_set_drvdata(idev, axp20x_pek);
> +
> + error = devm_request_any_context_irq(&pdev->dev, axp20x_pek->irq_dbr,
> + axp20x_pek_irq, 0,
> + "axp20x-pek-dbr", idev);
> + if (error < 0) {
> + dev_err(axp20x->dev, "Failed to request dbr IRQ#%d: %d\n",
> + axp20x_pek->irq_dbr, error);
> +
> + return error;
> + }
> +
> + error = devm_request_any_context_irq(&pdev->dev, axp20x_pek->irq_dbf,
> + axp20x_pek_irq, 0,
> + "axp20x-pek-dbf", idev);
> + if (error < 0) {
> + dev_err(axp20x->dev, "Failed to request dbf IRQ#%d: %d\n",
> + axp20x_pek->irq_dbf, error);
> + return error;
> + }
> +
> +
> + if (device_create_file(&pdev->dev, &axp20x_dev_attr_startup.attr) ||
> + device_create_file(&pdev->dev, &axp20x_dev_attr_shutdown.attr))
> + return -ENODEV;
I think you still want to use attribute group here. You also need to
clean up the attributes when unbinding device. Also, why returning
-ENODEV instead of the proper error code?
> +
> + error = input_register_device(idev);
> + if (error) {
> + dev_err(axp20x->dev, "Can't register input device: %d\n", error);
> + return error;
> + }
> +
> + platform_set_drvdata(pdev, axp20x_pek);
> +
> + return 0;
> +}
> +
> +static struct platform_driver axp20x_pek_driver = {
> + .probe = axp20x_pek_probe,
> + .driver = {
> + .name = "axp20x-pek",
> + .owner = THIS_MODULE,
> + },
> +};
> +module_platform_driver(axp20x_pek_driver);
> +
> +MODULE_DESCRIPTION("axp20x Power Button");
> +MODULE_AUTHOR("Carlo Caione <carlo-KA+7E9HrN00dnm+yROfE0A@public.gmane.org>");
> +MODULE_LICENSE("GPL");
> --
> 1.8.3.2
>
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH v4 1/7] drivers: input: keyboard: st-keyscan: add keyscan driver
From: Dmitry Torokhov @ 2014-04-13 5:10 UTC (permalink / raw)
To: Gabriel FERNANDEZ
Cc: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
Rob Landley, Russell King, Grant Likely, devicetree, linux-doc,
linux-kernel, linux-arm-kernel, linux-input, kernel, Lee Jones,
Gabriel Fernandez, Giuseppe Condorelli
In-Reply-To: <1397228856-9218-2-git-send-email-gabriel.fernandez@linaro.org>
Hi Gabriel,
On Fri, Apr 11, 2014 at 05:07:30PM +0200, Gabriel FERNANDEZ wrote:
> This patch adds ST Keyscan driver to use the keypad hw a subset
> of ST boards provide. Specific board setup will be put in the
> given dt.
>
> Signed-off-by: Gabriel Fernandez <gabriel.fernandez@linaro.org>
> Signed-off-by: Giuseppe Condorelli <giuseppe.condorelli@st.com>
> ---
> .../devicetree/bindings/input/st-keyscan.txt | 60 +++++
> drivers/input/keyboard/Kconfig | 12 +
> drivers/input/keyboard/Makefile | 1 +
> drivers/input/keyboard/st-keyscan.c | 260 +++++++++++++++++++++
> 4 files changed, 333 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/input/st-keyscan.txt
> create mode 100644 drivers/input/keyboard/st-keyscan.c
>
> diff --git a/Documentation/devicetree/bindings/input/st-keyscan.txt b/Documentation/devicetree/bindings/input/st-keyscan.txt
> new file mode 100644
> index 0000000..51eb428
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/st-keyscan.txt
> @@ -0,0 +1,60 @@
> +* ST Keyscan controller Device Tree bindings
> +
> +The ST keyscan controller Device Tree binding is based on the
> +matrix-keymap.
> +
> +Required properties:
> +- compatible: "st,sti-keyscan"
> +
> +- reg: Register base address and size of st-keyscan controller.
> +
> +- interrupts: Interrupt number for the st-keyscan controller.
> +
> +- clocks: Must contain one entry, for the module clock.
> + See ../clocks/clock-bindings.txt for details.
> +
> +- pinctrl: Should specify pin control groups used for this controller.
> + See ../pinctrl/pinctrl-bindings.txt for details.
> +
> +- linux,keymap: The keymap for keys as described in the binding document
> + devicetree/bindings/input/matrix-keymap.txt.
> +
> +- keypad,num-rows: Number of row lines connected to the keypad controller.
> +
> +- keypad,num-columns: Number of column lines connected to the keypad
> + controller.
> +
> +Optional property:
> +- st,debounce_us: Debouncing interval time in microseconds
> +
> +Example:
> +
> +keyscan: keyscan@fe4b0000 {
> + compatible = "st,sti-keyscan";
> + reg = <0xfe4b0000 0x2000>;
> + interrupts = <GIC_SPI 212 IRQ_TYPE_NONE>;
> + clocks = <&CLK_SYSIN>;
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_keyscan>;
> +
> + keypad,num-rows = <4>;
> + keypad,num-columns = <4>;
> + st,debounce_us = <5000>;
> +
> + linux,keymap = < MATRIX_KEY(0x00, 0x00, KEY_F13)
> + MATRIX_KEY(0x00, 0x01, KEY_F9)
> + MATRIX_KEY(0x00, 0x02, KEY_F5)
> + MATRIX_KEY(0x00, 0x03, KEY_F1)
> + MATRIX_KEY(0x01, 0x00, KEY_F14)
> + MATRIX_KEY(0x01, 0x01, KEY_F10)
> + MATRIX_KEY(0x01, 0x02, KEY_F6)
> + MATRIX_KEY(0x01, 0x03, KEY_F2)
> + MATRIX_KEY(0x02, 0x00, KEY_F15)
> + MATRIX_KEY(0x02, 0x01, KEY_F11)
> + MATRIX_KEY(0x02, 0x02, KEY_F7)
> + MATRIX_KEY(0x02, 0x03, KEY_F3)
> + MATRIX_KEY(0x03, 0x00, KEY_F16)
> + MATRIX_KEY(0x03, 0x01, KEY_F12)
> + MATRIX_KEY(0x03, 0x02, KEY_F8)
> + MATRIX_KEY(0x03, 0x03, KEY_F4) >;
> + };
> diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
> index 76842d7..40a377c 100644
> --- a/drivers/input/keyboard/Kconfig
> +++ b/drivers/input/keyboard/Kconfig
> @@ -524,6 +524,18 @@ config KEYBOARD_STOWAWAY
> To compile this driver as a module, choose M here: the
> module will be called stowaway.
>
> +config KEYBOARD_ST_KEYSCAN
> + tristate "STMicroelectronics keyscan support"
> + depends on ARCH_STI
> + select INPUT_EVDEV
Why are you selecting evdev? Its presence is not essential for the
driver.
> + select INPUT_MATRIXKMAP
> + help
> + Say Y here if you want to use a keypad attached to the keyscan block
> + on some STMicroelectronics SoC devices.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called st-keyscan.
> +
> config KEYBOARD_SUNKBD
> tristate "Sun Type 4 and Type 5 keyboard"
> select SERIO
> diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
> index 11cff7b..7504ae1 100644
> --- a/drivers/input/keyboard/Makefile
> +++ b/drivers/input/keyboard/Makefile
> @@ -51,6 +51,7 @@ obj-$(CONFIG_KEYBOARD_SH_KEYSC) += sh_keysc.o
> obj-$(CONFIG_KEYBOARD_SPEAR) += spear-keyboard.o
> obj-$(CONFIG_KEYBOARD_STMPE) += stmpe-keypad.o
> obj-$(CONFIG_KEYBOARD_STOWAWAY) += stowaway.o
> +obj-$(CONFIG_KEYBOARD_ST_KEYSCAN) += st-keyscan.o
> obj-$(CONFIG_KEYBOARD_SUNKBD) += sunkbd.o
> obj-$(CONFIG_KEYBOARD_TC3589X) += tc3589x-keypad.o
> obj-$(CONFIG_KEYBOARD_TEGRA) += tegra-kbc.o
> diff --git a/drivers/input/keyboard/st-keyscan.c b/drivers/input/keyboard/st-keyscan.c
> new file mode 100644
> index 0000000..7ed3b3e
> --- /dev/null
> +++ b/drivers/input/keyboard/st-keyscan.c
> @@ -0,0 +1,260 @@
> +/*
> + * STMicroelectronics Key Scanning driver
> + *
> + * Copyright (c) 2014 STMicroelectonics Ltd.
> + * Author: Stuart Menefy <stuart.menefy@st.com>
> + *
> + * Based on sh_keysc.c, copyright 2008 Magnus Damm
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +#include <linux/input/matrix_keypad.h>
> +
> +#define ST_KEYSCAN_MAXKEYS 16
> +
> +#define KEYSCAN_CONFIG_OFF 0x0
> +#define KEYSCAN_CONFIG_ENABLE 0x1
> +#define KEYSCAN_DEBOUNCE_TIME_OFF 0x4
> +#define KEYSCAN_MATRIX_STATE_OFF 0x8
> +#define KEYSCAN_MATRIX_DIM_OFF 0xc
> +#define KEYSCAN_MATRIX_DIM_X_SHIFT 0x0
> +#define KEYSCAN_MATRIX_DIM_Y_SHIFT 0x2
> +
> +struct st_keyscan {
> + void __iomem *base;
> + int irq;
> + struct clk *clk;
> + struct input_dev *input_dev;
> + unsigned int last_state;
> + unsigned int n_rows;
> + unsigned int n_cols;
> + unsigned int debounce_us;
> +};
> +
> +static irqreturn_t keyscan_isr(int irq, void *dev_id)
> +{
> + struct platform_device *pdev = dev_id;
> + struct st_keyscan *keypad = platform_get_drvdata(pdev);
This is not safe, you are assigning platform data at the very end of
probe; input device may get opened before probe is completed and IRS
may come early causing OOPS.
> + unsigned short *keycode = keypad->input_dev->keycode;
> + int state;
> + int change;
> +
> + state = readl(keypad->base + KEYSCAN_MATRIX_STATE_OFF) & 0xffff;
> + change = keypad->last_state ^ state;
> +
> + while (change) {
> + int scancode = __ffs(change);
> + int down = state & BIT(scancode);
> +
> + input_report_key(keypad->input_dev, keycode[scancode], down);
> +
> + change ^= BIT(scancode);
> + };
> + input_sync(keypad->input_dev);
> +
> + keypad->last_state = state;
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int keyscan_start(struct st_keyscan *keypad)
> +{
> + clk_enable(keypad->clk);
Should handle clk_enable() failures.
> +
> + writel(keypad->debounce_us * (clk_get_rate(keypad->clk) / 1000000),
> + keypad->base + KEYSCAN_DEBOUNCE_TIME_OFF);
> +
> + writel(((keypad->n_cols - 1) << KEYSCAN_MATRIX_DIM_X_SHIFT) |
> + ((keypad->n_rows - 1) << KEYSCAN_MATRIX_DIM_Y_SHIFT),
> + keypad->base + KEYSCAN_MATRIX_DIM_OFF);
> +
> + writel(KEYSCAN_CONFIG_ENABLE, keypad->base + KEYSCAN_CONFIG_OFF);
> +
> + return 0;
> +}
> +
> +static void keyscan_stop(struct st_keyscan *keypad)
> +{
> + writel(0, keypad->base + KEYSCAN_CONFIG_OFF);
> +
> + clk_disable(keypad->clk);
> +}
> +
> +static int keyscan_open(struct input_dev *dev)
> +{
> + struct st_keyscan *keypad = input_get_drvdata(dev);
> +
> + return keyscan_start(keypad);
> +}
> +
> +static void keyscan_close(struct input_dev *dev)
> +{
> + struct st_keyscan *keypad = input_get_drvdata(dev);
> +
> + keyscan_stop(keypad);
> +}
> +
> +static int keypad_matrix_key_parse_dt(struct st_keyscan *keypad_data)
> +{
> + struct device *dev = keypad_data->input_dev->dev.parent;
> + struct device_node *np = dev->of_node;
> + int error;
> +
> + error = matrix_keypad_parse_of_params(dev, &keypad_data->n_rows,
> + &keypad_data->n_cols);
> + if (error) {
> + dev_err(dev, "failed to parse keypad params\n");
> + return error;
> + }
> +
> + of_property_read_u32(np, "st,debounce-us", &keypad_data->debounce_us);
> +
> + dev_info(dev, "n_rows=%d n_col=%d debounce=%d\n",
> + keypad_data->n_rows,
> + keypad_data->n_cols,
> + keypad_data->debounce_us);
> +
> + return 0;
> +}
> +
> +static int __init keyscan_probe(struct platform_device *pdev)
keyscan_probe() should not be marked __init as it may be needed again if
one were to unbind and rebind the driver to the device through sysfs.
> +{
> + struct st_keyscan *keypad_data;
> + struct input_dev *input_dev;
> + struct resource *res;
> + int error;
> +
> + if (!pdev->dev.of_node) {
> + dev_err(&pdev->dev, "no keymap defined\n");
> + return -EINVAL;
> + }
> +
> + keypad_data = devm_kzalloc(&pdev->dev,
> + sizeof(*keypad_data), GFP_KERNEL);
> + if (!keypad_data)
> + return -ENOMEM;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + keypad_data->base = devm_ioremap_resource(&pdev->dev, res);
> + if (IS_ERR(keypad_data->base))
> + return PTR_ERR(keypad_data->base);
> +
> + keypad_data->irq = platform_get_irq(pdev, 0);
> + if (keypad_data->irq < 0) {
> + dev_err(&pdev->dev, "no IRQ specified\n");
> + return -EINVAL;
> + }
> +
> + error = devm_request_irq(&pdev->dev, keypad_data->irq, keyscan_isr, 0,
> + pdev->name, pdev);
> + if (error) {
> + dev_err(&pdev->dev, "failed to request IRQ\n");
> + return error;
> + }
> +
> + input_dev = devm_input_allocate_device(&pdev->dev);
> +
> + if (!input_dev) {
> + dev_err(&pdev->dev, "failed to allocate the input device\n");
> + return -ENOMEM;
> + }
> +
> + keypad_data->clk = devm_clk_get(&pdev->dev, NULL);
> + if (IS_ERR(keypad_data->clk)) {
> + dev_err(&pdev->dev, "cannot get clock");
> + return PTR_ERR(keypad_data->clk);
> + }
> +
> + keypad_data->input_dev = input_dev;
> +
> + input_dev->name = pdev->name;
> + input_dev->phys = "keyscan-keys/input0";
> + input_dev->dev.parent = &pdev->dev;
> + input_dev->open = keyscan_open;
> + input_dev->close = keyscan_close;
> +
> + input_dev->id.bustype = BUS_HOST;
> +
> + error = keypad_matrix_key_parse_dt(keypad_data);
> + if (error)
> + return error;
> +
> + error = matrix_keypad_build_keymap(NULL, NULL,
> + keypad_data->n_rows, keypad_data->n_cols,
> + NULL, input_dev);
> + if (error) {
> + dev_err(&pdev->dev, "failed to build keymap\n");
> + return error;
> + }
> +
> + input_set_drvdata(input_dev, keypad_data);
> +
> + error = input_register_device(input_dev);
> + if (error) {
> + dev_err(&pdev->dev, "failed to register input device\n");
> + return error;
> + }
> +
> + platform_set_drvdata(pdev, keypad_data);
> +
> + device_set_wakeup_capable(&pdev->dev, 1);
> +
> + return 0;
> +}
> +
> +static int keyscan_suspend(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct st_keyscan *keypad = platform_get_drvdata(pdev);
> +
> + if (device_may_wakeup(dev))
> + enable_irq_wake(keypad->irq);
> + else
> + keyscan_stop(keypad);
> +
> + return 0;
> +}
> +
> +static int keyscan_resume(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct st_keyscan *keypad = platform_get_drvdata(pdev);
> +
> + if (device_may_wakeup(dev))
> + disable_irq_wake(keypad->irq);
> + else
> + keyscan_start(keypad);
If devoice has not been opened you should not try to stop it otherwise
clk counter will be imbalanced.
> +
> + return 0;
> +}
> +
> +static SIMPLE_DEV_PM_OPS(keyscan_dev_pm_ops, keyscan_suspend, keyscan_resume);
> +
> +static const struct of_device_id keyscan_of_match[] = {
> + { .compatible = "st,sti-keyscan" },
> + { },
> +};
> +MODULE_DEVICE_TABLE(of, keyscan_of_match);
> +
> +struct platform_driver keyscan_device_driver = {
> + .probe = keyscan_probe,
> + .driver = {
> + .name = "st-keyscan",
> + .pm = &keyscan_dev_pm_ops,
> + .of_match_table = of_match_ptr(keyscan_of_match),
> + }
> +};
> +
> +module_platform_driver(keyscan_device_driver);
> +
> +MODULE_AUTHOR("Stuart Menefy <stuart.menefy@st.com>");
> +MODULE_DESCRIPTION("STMicroelectronics keyscan device driver");
> +MODULE_LICENSE("GPL");
> --
> 1.9.1
>
Does the version of the patch below still work for you?
Thanks.
--
Dmitry
Input: add st-keyscan driver
From: Gabriel FERNANDEZ <gabriel.fernandez@st.com>
This patch adds ST Keyscan driver to use the keypad hw a subset of ST
boards provide. Specific board setup will be put in the given dt.
Signed-off-by: Gabriel Fernandez <gabriel.fernandez@linaro.org>
Signed-off-by: Giuseppe Condorelli <giuseppe.condorelli@st.com>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
.../devicetree/bindings/input/st-keyscan.txt | 60 ++++
drivers/input/keyboard/Kconfig | 11 +
drivers/input/keyboard/Makefile | 1
drivers/input/keyboard/st-keyscan.c | 274 ++++++++++++++++++++
4 files changed, 346 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/st-keyscan.txt
create mode 100644 drivers/input/keyboard/st-keyscan.c
diff --git a/Documentation/devicetree/bindings/input/st-keyscan.txt b/Documentation/devicetree/bindings/input/st-keyscan.txt
new file mode 100644
index 0000000..51eb428
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/st-keyscan.txt
@@ -0,0 +1,60 @@
+* ST Keyscan controller Device Tree bindings
+
+The ST keyscan controller Device Tree binding is based on the
+matrix-keymap.
+
+Required properties:
+- compatible: "st,sti-keyscan"
+
+- reg: Register base address and size of st-keyscan controller.
+
+- interrupts: Interrupt number for the st-keyscan controller.
+
+- clocks: Must contain one entry, for the module clock.
+ See ../clocks/clock-bindings.txt for details.
+
+- pinctrl: Should specify pin control groups used for this controller.
+ See ../pinctrl/pinctrl-bindings.txt for details.
+
+- linux,keymap: The keymap for keys as described in the binding document
+ devicetree/bindings/input/matrix-keymap.txt.
+
+- keypad,num-rows: Number of row lines connected to the keypad controller.
+
+- keypad,num-columns: Number of column lines connected to the keypad
+ controller.
+
+Optional property:
+- st,debounce_us: Debouncing interval time in microseconds
+
+Example:
+
+keyscan: keyscan@fe4b0000 {
+ compatible = "st,sti-keyscan";
+ reg = <0xfe4b0000 0x2000>;
+ interrupts = <GIC_SPI 212 IRQ_TYPE_NONE>;
+ clocks = <&CLK_SYSIN>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_keyscan>;
+
+ keypad,num-rows = <4>;
+ keypad,num-columns = <4>;
+ st,debounce_us = <5000>;
+
+ linux,keymap = < MATRIX_KEY(0x00, 0x00, KEY_F13)
+ MATRIX_KEY(0x00, 0x01, KEY_F9)
+ MATRIX_KEY(0x00, 0x02, KEY_F5)
+ MATRIX_KEY(0x00, 0x03, KEY_F1)
+ MATRIX_KEY(0x01, 0x00, KEY_F14)
+ MATRIX_KEY(0x01, 0x01, KEY_F10)
+ MATRIX_KEY(0x01, 0x02, KEY_F6)
+ MATRIX_KEY(0x01, 0x03, KEY_F2)
+ MATRIX_KEY(0x02, 0x00, KEY_F15)
+ MATRIX_KEY(0x02, 0x01, KEY_F11)
+ MATRIX_KEY(0x02, 0x02, KEY_F7)
+ MATRIX_KEY(0x02, 0x03, KEY_F3)
+ MATRIX_KEY(0x03, 0x00, KEY_F16)
+ MATRIX_KEY(0x03, 0x01, KEY_F12)
+ MATRIX_KEY(0x03, 0x02, KEY_F8)
+ MATRIX_KEY(0x03, 0x03, KEY_F4) >;
+ };
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 76842d7..948a303 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -524,6 +524,17 @@ config KEYBOARD_STOWAWAY
To compile this driver as a module, choose M here: the
module will be called stowaway.
+config KEYBOARD_ST_KEYSCAN
+ tristate "STMicroelectronics keyscan support"
+ depends on ARCH_STI || COMPILE_TEST
+ select INPUT_MATRIXKMAP
+ help
+ Say Y here if you want to use a keypad attached to the keyscan block
+ on some STMicroelectronics SoC devices.
+
+ To compile this driver as a module, choose M here: the
+ module will be called st-keyscan.
+
config KEYBOARD_SUNKBD
tristate "Sun Type 4 and Type 5 keyboard"
select SERIO
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 11cff7b..7504ae1 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -51,6 +51,7 @@ obj-$(CONFIG_KEYBOARD_SH_KEYSC) += sh_keysc.o
obj-$(CONFIG_KEYBOARD_SPEAR) += spear-keyboard.o
obj-$(CONFIG_KEYBOARD_STMPE) += stmpe-keypad.o
obj-$(CONFIG_KEYBOARD_STOWAWAY) += stowaway.o
+obj-$(CONFIG_KEYBOARD_ST_KEYSCAN) += st-keyscan.o
obj-$(CONFIG_KEYBOARD_SUNKBD) += sunkbd.o
obj-$(CONFIG_KEYBOARD_TC3589X) += tc3589x-keypad.o
obj-$(CONFIG_KEYBOARD_TEGRA) += tegra-kbc.o
diff --git a/drivers/input/keyboard/st-keyscan.c b/drivers/input/keyboard/st-keyscan.c
new file mode 100644
index 0000000..758b487
--- /dev/null
+++ b/drivers/input/keyboard/st-keyscan.c
@@ -0,0 +1,274 @@
+/*
+ * STMicroelectronics Key Scanning driver
+ *
+ * Copyright (c) 2014 STMicroelectonics Ltd.
+ * Author: Stuart Menefy <stuart.menefy@st.com>
+ *
+ * Based on sh_keysc.c, copyright 2008 Magnus Damm
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/input/matrix_keypad.h>
+
+#define ST_KEYSCAN_MAXKEYS 16
+
+#define KEYSCAN_CONFIG_OFF 0x0
+#define KEYSCAN_CONFIG_ENABLE 0x1
+#define KEYSCAN_DEBOUNCE_TIME_OFF 0x4
+#define KEYSCAN_MATRIX_STATE_OFF 0x8
+#define KEYSCAN_MATRIX_DIM_OFF 0xc
+#define KEYSCAN_MATRIX_DIM_X_SHIFT 0x0
+#define KEYSCAN_MATRIX_DIM_Y_SHIFT 0x2
+
+struct st_keyscan {
+ void __iomem *base;
+ int irq;
+ struct clk *clk;
+ struct input_dev *input_dev;
+ unsigned long last_state;
+ unsigned int n_rows;
+ unsigned int n_cols;
+ unsigned int debounce_us;
+};
+
+static irqreturn_t keyscan_isr(int irq, void *dev_id)
+{
+ struct st_keyscan *keypad = dev_id;
+ unsigned short *keycode = keypad->input_dev->keycode;
+ unsigned long state, change;
+ int bit_nr;
+
+ state = readl(keypad->base + KEYSCAN_MATRIX_STATE_OFF) & 0xffff;
+ change = keypad->last_state ^ state;
+ keypad->last_state = state;
+
+ for_each_set_bit(bit_nr, &change, BITS_PER_LONG)
+ input_report_key(keypad->input_dev,
+ keycode[bit_nr], state & BIT(bit_nr));
+
+ input_sync(keypad->input_dev);
+
+ return IRQ_HANDLED;
+}
+
+static int keyscan_start(struct st_keyscan *keypad)
+{
+ int error;
+
+ error = clk_enable(keypad->clk);
+ if (error)
+ return error;
+
+ writel(keypad->debounce_us * (clk_get_rate(keypad->clk) / 1000000),
+ keypad->base + KEYSCAN_DEBOUNCE_TIME_OFF);
+
+ writel(((keypad->n_cols - 1) << KEYSCAN_MATRIX_DIM_X_SHIFT) |
+ ((keypad->n_rows - 1) << KEYSCAN_MATRIX_DIM_Y_SHIFT),
+ keypad->base + KEYSCAN_MATRIX_DIM_OFF);
+
+ writel(KEYSCAN_CONFIG_ENABLE, keypad->base + KEYSCAN_CONFIG_OFF);
+
+ return 0;
+}
+
+static void keyscan_stop(struct st_keyscan *keypad)
+{
+ writel(0, keypad->base + KEYSCAN_CONFIG_OFF);
+
+ clk_disable(keypad->clk);
+}
+
+static int keyscan_open(struct input_dev *dev)
+{
+ struct st_keyscan *keypad = input_get_drvdata(dev);
+
+ return keyscan_start(keypad);
+}
+
+static void keyscan_close(struct input_dev *dev)
+{
+ struct st_keyscan *keypad = input_get_drvdata(dev);
+
+ keyscan_stop(keypad);
+}
+
+static int keypad_matrix_key_parse_dt(struct st_keyscan *keypad_data)
+{
+ struct device *dev = keypad_data->input_dev->dev.parent;
+ struct device_node *np = dev->of_node;
+ int error;
+
+ error = matrix_keypad_parse_of_params(dev, &keypad_data->n_rows,
+ &keypad_data->n_cols);
+ if (error) {
+ dev_err(dev, "failed to parse keypad params\n");
+ return error;
+ }
+
+ of_property_read_u32(np, "st,debounce-us", &keypad_data->debounce_us);
+
+ dev_dbg(dev, "n_rows=%d n_col=%d debounce=%d\n",
+ keypad_data->n_rows, keypad_data->n_cols,
+ keypad_data->debounce_us);
+
+ return 0;
+}
+
+static int keyscan_probe(struct platform_device *pdev)
+{
+ struct st_keyscan *keypad_data;
+ struct input_dev *input_dev;
+ struct resource *res;
+ int error;
+
+ if (!pdev->dev.of_node) {
+ dev_err(&pdev->dev, "no DT data present\n");
+ return -EINVAL;
+ }
+
+ keypad_data = devm_kzalloc(&pdev->dev, sizeof(*keypad_data),
+ GFP_KERNEL);
+ if (!keypad_data)
+ return -ENOMEM;
+
+ input_dev = devm_input_allocate_device(&pdev->dev);
+ if (!input_dev) {
+ dev_err(&pdev->dev, "failed to allocate the input device\n");
+ return -ENOMEM;
+ }
+
+ input_dev->name = pdev->name;
+ input_dev->phys = "keyscan-keys/input0";
+ input_dev->dev.parent = &pdev->dev;
+ input_dev->open = keyscan_open;
+ input_dev->close = keyscan_close;
+
+ input_dev->id.bustype = BUS_HOST;
+
+ error = keypad_matrix_key_parse_dt(keypad_data);
+ if (error)
+ return error;
+
+ error = matrix_keypad_build_keymap(NULL, NULL,
+ keypad_data->n_rows,
+ keypad_data->n_cols,
+ NULL, input_dev);
+ if (error) {
+ dev_err(&pdev->dev, "failed to build keymap\n");
+ return error;
+ }
+
+ input_set_drvdata(input_dev, keypad_data);
+
+ keypad_data->input_dev = input_dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ keypad_data->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(keypad_data->base))
+ return PTR_ERR(keypad_data->base);
+
+ keypad_data->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(keypad_data->clk)) {
+ dev_err(&pdev->dev, "cannot get clock\n");
+ return PTR_ERR(keypad_data->clk);
+ }
+
+ error = clk_enable(keypad_data->clk);
+ if (error) {
+ dev_err(&pdev->dev, "failed to enable clock\n");
+ return error;
+ }
+
+ keyscan_stop(keypad_data);
+
+ keypad_data->irq = platform_get_irq(pdev, 0);
+ if (keypad_data->irq < 0) {
+ dev_err(&pdev->dev, "no IRQ specified\n");
+ return -EINVAL;
+ }
+
+ error = devm_request_irq(&pdev->dev, keypad_data->irq, keyscan_isr, 0,
+ pdev->name, keypad_data);
+ if (error) {
+ dev_err(&pdev->dev, "failed to request IRQ\n");
+ return error;
+ }
+
+ error = input_register_device(input_dev);
+ if (error) {
+ dev_err(&pdev->dev, "failed to register input device\n");
+ return error;
+ }
+
+ platform_set_drvdata(pdev, keypad_data);
+
+ device_set_wakeup_capable(&pdev->dev, 1);
+
+ return 0;
+}
+
+static int keyscan_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct st_keyscan *keypad = platform_get_drvdata(pdev);
+ struct input_dev *input = keypad->input_dev;
+
+ mutex_lock(&input->mutex);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(keypad->irq);
+ else if (input->users)
+ keyscan_stop(keypad);
+
+ mutex_unlock(&input->mutex);
+ return 0;
+}
+
+static int keyscan_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct st_keyscan *keypad = platform_get_drvdata(pdev);
+ struct input_dev *input = keypad->input_dev;
+ int retval = 0;
+
+ mutex_lock(&input->mutex);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(keypad->irq);
+ else if (input->users)
+ retval = keyscan_start(keypad);
+
+ mutex_unlock(&input->mutex);
+ return retval;
+}
+
+static SIMPLE_DEV_PM_OPS(keyscan_dev_pm_ops, keyscan_suspend, keyscan_resume);
+
+static const struct of_device_id keyscan_of_match[] = {
+ { .compatible = "st,sti-keyscan" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, keyscan_of_match);
+
+static struct platform_driver keyscan_device_driver = {
+ .probe = keyscan_probe,
+ .driver = {
+ .name = "st-keyscan",
+ .pm = &keyscan_dev_pm_ops,
+ .of_match_table = of_match_ptr(keyscan_of_match),
+ }
+};
+
+module_platform_driver(keyscan_device_driver);
+
+MODULE_AUTHOR("Stuart Menefy <stuart.menefy@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics keyscan device driver");
+MODULE_LICENSE("GPL");
^ permalink raw reply related
* Re: Re: [PATCH] gpio/at91: free GPIO after configuring as input
From: Alexandre Belloni @ 2014-04-12 22:48 UTC (permalink / raw)
To: Javier Martinez Canillas
Cc: Alexander Stein, Linus Walleij, Dmitry Torokhov,
Jean-Christophe PLAGNIOL-VILLARD,
linux-arm-kernel@lists.infradead.org, linux-input
In-Reply-To: <CABxcv=mdy_PmzgRN7SmBppS=8rDcP+jX0q1EwduwR0QOW=JWgg@mail.gmail.com>
Hi,
On 11/04/2014 at 19:28:08 +0200, Javier Martinez Canillas wrote :
> On Fri, Apr 11, 2014 at 5:45 PM, Alexander Stein <alexanders83@web.de> wrote:
> > I'm CC'ing some input guys if this can't be solved in the pinctrl/irq side.
> >
> > On Friday 11 April 2014, 23:30:33 wrote Jean-Christophe PLAGNIOL-VILLARD:
> >>
> >> On Apr 11, 2014, at 10:24 PM, Alexander Stein <alexanders83@web.de> wrote:
> >>
> >> >
> >> > If the GPIO stays requested a device driver can't request it again.
> >> > e.g. Without this patch the ads7846 driver returns the following error:
> >> > ads7846 spi32766.3: failed to request/setup pendown GPIO15: -16
> >> > ads7846: probe of spi32766.3 failed with error -16
> >> >
> >> > /sys/kernel/debug/gpio shows this:
> >> > GPIOs 0-31, platform/fffff200.gpio, fffff200.gpio:
> >> > [/ahb/apb/pinctrl@fffff200/gpio@fffff200] GPIOfffff200.gpio15: [gpio] set
> >> >
> >> > Signed-off-by: Alexander Stein <alexanders83@web.de>
> >> > ---
> >> > I'm aware that it makes sense this GPIO is/stays requested, but either the
> >> > pinctl or device driver have to be adjusted as both can't request this GPIO.
> >> > I think the latter shouldn't change.
> >> >
> >> > drivers/pinctrl/pinctrl-at91.c | 2 ++
> >> > 1 file changed, 2 insertions(+)
> >> >
> >> > diff --git a/drivers/pinctrl/pinctrl-at91.c b/drivers/pinctrl/pinctrl-at91.c
> >> > index d990e33..63176f2 100644
> >> > --- a/drivers/pinctrl/pinctrl-at91.c
> >> > +++ b/drivers/pinctrl/pinctrl-at91.c
> >> > @@ -1493,6 +1493,8 @@ static int at91_gpio_irq_domain_xlate(struct irq_domain *d,
> >> > if (ret)
> >> > return ret;
> >> >
> >> > + gpio_free(pin);
> >> > +
> >>
> >> NACK it the whole key point the gpio use as a IRQ so the irq generic code request it
> >> > return 0;
> >> > }
> >> >
> >>
>
> The problem is that the GPIO and IRQ subsystems are somehow related
> but completely independent. A GPIO pin used as an IRQ line is
> completely orthogonal to requesting a GPIO pin.
>
> Is true that here is a common pattern in the kernel that is:
>
> gpio_request(gpio,...);
> gpio_direction_input()
> request[_threaded]_irq(gpio_to_irq(gpio), ...);
>
> But one should not assume that requesting a GPIO to be used as an IRQ
> line implies requesting the GPIO first. It is completely legal to only
> request the IRQ without requesting the GPIO before.
>
> Of course at a hardware level the pin has to be configured as input if
> needed by the chip controller and also a following call to
> gpio_request() as output should not be supported.
>
> After a long discussion the agreement is that the driver should be
> able to setup as a hw level if another driver wants to use a GPIO pin
> as an IRQ line but not requesting the GPIO. So, I think that is wrong
> to call gpio_request() and gpio_direction_input() on a irq_domain_ops
> .xlate() function handler since by doing that a subsequent call to
> gpio_request() is failing like you are reporting.
>
> The GPIO subsystem has a new gpio_lock_as_irq() helper function to
> mark a GPIO pin as already used as a IRQ line and only allowing to
> request a GPIO as input if is already marked as an IRQ.
>
> Please take a look to commit 2f56e0a ("gpio/omap: use gpiolib API to
> mark a GPIO used as an IRQ") for an example on how to use this helper
> function.
>
> Also, there is a new GPIOLIB_IRQCHIP infrastructure in gpiolib that
> are a set of helper functions to associate an IRQ chip to a GPIO
> controller. This should cover the most common use case and most
> probably this driver and can be converted to use it.
>
> Please refer to commits:
>
> 1425052 ("gpio: add IRQ chip helpers in gpiolib")
> e0bc34a ("pinctrl: nomadik: convert driver to use gpiolib irqchip")
>
> for an example on how to use it.
>
You probably want to have a look at:
https://lkml.org/lkml/2014/3/3/110
--
Alexandre Belloni, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com
^ permalink raw reply
* Re: [PATCH] Input: ads7846: Fix device usage within attribute show
From: Dmitry Torokhov @ 2014-04-12 20:43 UTC (permalink / raw)
To: Guenter Roeck; +Cc: Alexander Stein, linux-input
In-Reply-To: <5348644C.7070109@roeck-us.net>
On Fri, Apr 11, 2014 at 02:53:16PM -0700, Guenter Roeck wrote:
> On 04/11/2014 01:03 PM, Alexander Stein wrote:
> >With commit e585c40ba (Input: ads7846 - convert to
> >hwmon_device_register_with_groups()) the device passed to the attribute's
> >show function isn't the spi device as before.
> >So fixup the passed device to ads7846_read12_ser.
> >
> >Signed-off-by: Alexander Stein <alexanders83@web.de>
>
> And I thought I got that right. Oh well.
>
> Acked-by: Guenter Roeck <linux@roeck-us.net>
Applied, thank you.
--
Dmitry
^ permalink raw reply
* Modifier keys on the Zowie Celeritas keyboard don't work
From: Vadim Trochinsky @ 2014-04-11 23:11 UTC (permalink / raw)
To: linux-input
Hello!
I've got this keyboard and it's got the extremely irritating problem of
that the Alt/Shift/Ctrl keys don't work at all. Caps lock does work
though.
xev reports the following:
The 'a' key gives this. Holding the key produces a continuous stream of
these two:
KeyPress event, serial 43, synthetic NO, window 0x7200001,
root 0x29e, subw 0x0, time 31503085, (-189,538), root:(859,561),
state 0x0, keycode 38 (keysym 0x61, a), same_screen YES,
XLookupString gives 1 bytes: (61) "a"
XmbLookupString gives 1 bytes: (61) "a"
XFilterEvent returns: False
KeyRelease event, serial 43, synthetic NO, window 0x7200001,
root 0x29e, subw 0x0, time 31503190, (-189,538), root:(859,561),
state 0x0, keycode 38 (keysym 0x61, a), same_screen YES,
XLookupString gives 1 bytes: (61) "a"
XFilterEvent returns: False
The Shift key gives this. Notice how unlike in the previous one, the
time is exactly the same -- the release is registered immediately,
though it did take me a bit of time to release the key. Also holding the
key pressed only ever outputs the Press/Release pair once.
KeyPress event, serial 43, synthetic NO, window 0x7200001,
root 0x29e, subw 0x0, time 31524269, (-603,723), root:(445,746),
state 0x0, keycode 50 (keysym 0xffe1, Shift_L), same_screen YES,
XLookupString gives 0 bytes:
XmbLookupString gives 0 bytes:
XFilterEvent returns: False
KeyRelease event, serial 43, synthetic NO, window 0x7200001,
root 0x29e, subw 0x0, time 31524269, (-603,723), root:(445,746),
state 0x1, keycode 50 (keysym 0xffe1, Shift_L), same_screen YES,
XLookupString gives 0 bytes:
XFilterEvent returns: False
Pressing Shift+A results in this, which shows that both keys are sensed,
but Shift is being interpreted as being released immediately (and
twice), hence why it never overlaps the 'a' keypress.
KeyPress event, serial 49, synthetic NO, window 0x7200001,
root 0x29e, subw 0x0, time 31724281, (68,-49), root:(1909,91),
state 0x0, keycode 50 (keysym 0xffe1, Shift_L), same_screen YES,
XLookupString gives 0 bytes:
XmbLookupString gives 0 bytes:
XFilterEvent returns: False
KeyRelease event, serial 49, synthetic NO, window 0x7200001,
root 0x29e, subw 0x0, time 31724281, (68,-49), root:(1909,91),
state 0x1, keycode 50 (keysym 0xffe1, Shift_L), same_screen YES,
XLookupString gives 0 bytes:
XFilterEvent returns: False
KeyPress event, serial 49, synthetic NO, window 0x7200001,
root 0x29e, subw 0x0, time 31724442, (68,-49), root:(1909,91),
state 0x0, keycode 50 (keysym 0xffe1, Shift_L), same_screen YES,
XLookupString gives 0 bytes:
XmbLookupString gives 0 bytes:
XFilterEvent returns: False
KeyRelease event, serial 49, synthetic NO, window 0x7200001,
root 0x29e, subw 0x0, time 31724442, (68,-49), root:(1909,91),
state 0x1, keycode 50 (keysym 0xffe1, Shift_L), same_screen YES,
XLookupString gives 0 bytes:
XFilterEvent returns: False
KeyPress event, serial 49, synthetic NO, window 0x7200001,
root 0x29e, subw 0x0, time 31724442, (68,-49), root:(1909,91),
state 0x0, keycode 38 (keysym 0x61, a), same_screen YES,
XLookupString gives 1 bytes: (61) "a"
XmbLookupString gives 1 bytes: (61) "a"
XFilterEvent returns: False
KeyPress event, serial 49, synthetic NO, window 0x7200001,
root 0x29e, subw 0x0, time 31724570, (68,-49), root:(1909,91),
state 0x0, keycode 50 (keysym 0xffe1, Shift_L), same_screen YES,
XLookupString gives 0 bytes:
XmbLookupString gives 0 bytes:
XFilterEvent returns: False
KeyRelease event, serial 49, synthetic NO, window 0x7200001,
root 0x29e, subw 0x0, time 31724570, (68,-49), root:(1909,91),
state 0x1, keycode 50 (keysym 0xffe1, Shift_L), same_screen YES,
XLookupString gives 0 bytes:
XFilterEvent returns: False
KeyRelease event, serial 49, synthetic NO, window 0x7200001,
root 0x29e, subw 0x0, time 31724570, (68,-49), root:(1909,91),
state 0x0, keycode 38 (keysym 0x61, a), same_screen YES,
XLookupString gives 1 bytes: (61) "a"
XFilterEvent returns: False
In comparison, here's another keyboard, which works fine:
KeyPress event, serial 48, synthetic NO, window 0x7200001,
root 0x29e, subw 0x0, time 31588524, (-536,223), root:(1305,363),
state 0x0, keycode 50 (keysym 0xffe1, Shift_L), same_screen YES,
XLookupString gives 0 bytes:
XmbLookupString gives 0 bytes:
XFilterEvent returns: False
KeyPress event, serial 48, synthetic NO, window 0x7200001,
root 0x29e, subw 0x0, time 31593276, (-536,223), root:(1305,363),
state 0x1, keycode 38 (keysym 0x41, A), same_screen YES,
XLookupString gives 1 bytes: (41) "A"
XmbLookupString gives 1 bytes: (41) "A"
XFilterEvent returns: False
KeyRelease event, serial 48, synthetic NO, window 0x7200001,
root 0x29e, subw 0x0, time 31593380, (-536,223), root:(1305,363),
state 0x1, keycode 38 (keysym 0x41, A), same_screen YES,
XLookupString gives 1 bytes: (41) "A"
XFilterEvent returns: False
KeyRelease event, serial 48, synthetic NO, window 0x7200001,
root 0x29e, subw 0x0, time 31593435, (-536,223), root:(1305,363),
state 0x1, keycode 50 (keysym 0xffe1, Shift_L), same_screen YES,
XLookupString gives 0 bytes:
XFilterEvent returns: False
I found these bug reports, which contain information on the problem.
What's happening to me is exactly the same:
https://bugzilla.kernel.org/show_bug.cgi?id=51371
https://bugzilla.kernel.org/show_bug.cgi?id=46491
As of 3.13.9, it still doesn't work. I tried usbmon to see what's
different about this keyboard vs another perfectly good working one that
I have, and the packet dumps have come out exactly identical, except for
the minor and expected differences in timestamps. None of this "shift is
immediately released" behavior detailed above appears in the USB dumps,
the keyboard seems to work exactly as expected.
I have to say that I'm quite baffled by this revelation.
The keyboard also works perfectly well in GRUB and Windows, and previous
reports of this problem mentioned it works with the usbkbd driver, but
that doesn't seem to exist anymore.
Maybe is there something that I'm not capturing? (I used "tshark -i
usbmon0") Or perhaps some kind of inappropriate quirks mode being turned
on? I tried looking at the kernel source, but I'm completely
inexperienced in that area (though I can code), and failed to find
anything.
I'd like to have this fixed. Please do tell what information might be
useful.
Thanks!
^ permalink raw reply
* Re: [PATCH] Input: ads7846: Fix device usage within attribute show
From: Guenter Roeck @ 2014-04-11 21:53 UTC (permalink / raw)
To: Alexander Stein, Dmitry Torokhov; +Cc: linux-input
In-Reply-To: <1397246613-26538-1-git-send-email-alexanders83@web.de>
On 04/11/2014 01:03 PM, Alexander Stein wrote:
> With commit e585c40ba (Input: ads7846 - convert to
> hwmon_device_register_with_groups()) the device passed to the attribute's
> show function isn't the spi device as before.
> So fixup the passed device to ads7846_read12_ser.
>
> Signed-off-by: Alexander Stein <alexanders83@web.de>
And I thought I got that right. Oh well.
Acked-by: Guenter Roeck <linux@roeck-us.net>
> ---
> drivers/input/touchscreen/ads7846.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/input/touchscreen/ads7846.c b/drivers/input/touchscreen/ads7846.c
> index 180ff7a..3824369 100644
> --- a/drivers/input/touchscreen/ads7846.c
> +++ b/drivers/input/touchscreen/ads7846.c
> @@ -425,7 +425,7 @@ static int ads7845_read12_ser(struct device *dev, unsigned command)
> name ## _show(struct device *dev, struct device_attribute *attr, char *buf) \
> { \
> struct ads7846 *ts = dev_get_drvdata(dev); \
> - ssize_t v = ads7846_read12_ser(dev, \
> + ssize_t v = ads7846_read12_ser(&ts->spi->dev, \
> READ_12BIT_SER(var)); \
> if (v < 0) \
> return v; \
>
^ permalink raw reply
* [PATCH] Input: ads7846: Fix device usage within attribute show
From: Alexander Stein @ 2014-04-11 20:03 UTC (permalink / raw)
To: Dmitry Torokhov, Guenter Roeck; +Cc: Alexander Stein, linux-input
With commit e585c40ba (Input: ads7846 - convert to
hwmon_device_register_with_groups()) the device passed to the attribute's
show function isn't the spi device as before.
So fixup the passed device to ads7846_read12_ser.
Signed-off-by: Alexander Stein <alexanders83@web.de>
---
drivers/input/touchscreen/ads7846.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/input/touchscreen/ads7846.c b/drivers/input/touchscreen/ads7846.c
index 180ff7a..3824369 100644
--- a/drivers/input/touchscreen/ads7846.c
+++ b/drivers/input/touchscreen/ads7846.c
@@ -425,7 +425,7 @@ static int ads7845_read12_ser(struct device *dev, unsigned command)
name ## _show(struct device *dev, struct device_attribute *attr, char *buf) \
{ \
struct ads7846 *ts = dev_get_drvdata(dev); \
- ssize_t v = ads7846_read12_ser(dev, \
+ ssize_t v = ads7846_read12_ser(&ts->spi->dev, \
READ_12BIT_SER(var)); \
if (v < 0) \
return v; \
--
1.9.2
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox