Re: [PATCH v3] HID: i2c-hid: Enable touchpad wakeup from Suspend-to-Idle

From: Kai-Heng Feng
Date: Thu Jul 09 2020 - 01:46:24 EST


Hi,

> On Jul 3, 2020, at 20:27, Hans de Goede <hdegoede@xxxxxxxxxx> wrote:
>
> Hi,
>
> On 7/1/20 8:46 AM, Kai-Heng Feng wrote:
>>> On Jun 19, 2020, at 17:56, Hans de Goede <hdegoede@xxxxxxxxxx> wrote:
>>>
>>> Hi,
>>>
>>> On 6/19/20 6:16 AM, Kai-Heng Feng wrote:
>>>> Hi,
>>>>> On Jun 18, 2020, at 23:28, Hans de Goede <hdegoede@xxxxxxxxxx> wrote:
>>>>>
>>>>> Hi,
>>>>>
>>>>> On 6/18/20 4:55 PM, Kai-Heng Feng wrote:
>>>>>> Many laptops can be woken up from Suspend-to-Idle by touchpad. This is
>>>>>> also the default behavior on other OSes.
>>>>>> So let's enable the wakeup support if the system defaults to
>>>>>> Suspend-to-Idle.
>>>>>
>>>>> I have been debugging a spurious wakeup issue on an Asus T101HA,
>>>>> where the system would not stay suspended when the lid was closed.
>>>>>
>>>>> The issue turns out to be that the touchpad is generating touch
>>>>> events when the lid/display gets close to the touchpad. In this case
>>>>> wakeup is already enabled by default because it is an USB device.
>>>> Sounds like a mechanical/hardware issue to me.
>>>
>>> Nope, the laptop is pretty much in new state.
>> Ack.
>>>
>>>> I've seen some old laptops have the same issue.
>>>> Swollen battery can push up the touchpad, makes it contact to touchscreen, and wakes up the system.
>>>
>>> This is a 2-in-1 with a detachable keyboard, which is why the
>>> kbd/touchpad is a USB device rather then i2c-hid. Even if the
>>> battery were swollen this would push up the back cover of the
>>> tablet.
>> What's the behavior on Windows?
>> I wonder if Windows has a different wakeup policy?
>> Like disable remote wakeup for USB touchpad and touchscreen?
>
> I'm afraid I no longer have Windows on the device, so I cannot
> test this. I guess that Windows disables the builtin touchpad and
> touchscreenn when the lid is closed. That is sensible to do from
> a power-management pov even when not suspending, but just closing
> the lid and using an external monitor + kbd.

Ok. This seems worth further investigation.

>
>>>>> So I do not believe that this is a good idea, most current devices
>>>>> with a HID multi-touch touchpad use i2c-hid and also use S2idle,
>>>>> so this will basically enable wakeup by touchpad on almost all
>>>>> current devices.
>>>> However, it's really handy to wake up the system from touchpad.
>>>
>>> I agree this is somewhat handy, but the keyboard (space-bar) is
>>> usually sitting right next to it. So we can live without it,
>>> we really need to fix the spurious wakeup issue first, once
>>> that is fixed I'm fine with enabling wakeup by touchpad.
>> That's true. Spacebar is a good enough alternative.
>>>
>>>>> There will likely be other devices with the same issue as the T101HA,
>>>>> but currently we are not seeing this issue because by default i2c-hid
>>>>> devices do not have wakeup enabled. This change will thus likely cause
>>>>> new spurious wakeup issues on more devices. So this seems like a
>>>>> bad idea.
>>>> But only under lid is closed?
>>>
>>> Right.
>>>
>>>> I wonder if it's okay to handle the case in s2idle_loop() or in userspace?
>>>> Lid close -> Wakeup event from touchpad -> Found the lid is closed
>>>> -> Turn off touchpad wakeup -> continue suspend.
>>>
>>> I've discussed doing something about the spurious wakeup issue in
>>> the kernel with the the kernel input maintainer (Dmitry) here:
>>>
>>> https://lore.kernel.org/linux-acpi/964ca07a-3da5-101f-7edf-64bdeec98a4b@xxxxxxxxxx/
>>>
>>> He was quite clear that this must be fixed in userspace. We did
>>> come up with a plan for fixing this in userspace:
>>>
>>> 1) Have udev-rules setting using hwdb for quirks which tags
>>> some input devices as "input-disable-wake-on-closed-lid".
>>> A simple udev rule could tag all i2c-hid touchpads with this and
>>> for the detachable USB keyboards with builtin touchpad some
>>> 2-in-1s have we can then use quirks in hwdb to set the tag
>>> on those.
>> Maybe we can avoid using quirks in hwdb, external USB devices should have "Removable" bit set (i.e. it's external).
>
> At the USB level there only is a somewhat reliable "removal" bit
> for storage devices, defined at the storage protocol level.
>
> For other devices there is:
>
> cat /sys/bus/usb/devices/1-8/removable
>
> This comes from the ACPI tables parsed by:
> drivers/usb/core/usb-acpi.c
>
> Which set the port's connect_type variable.

Actually most laptops I've used correctly marked internally connected USB 2 device with Removable bit.
And that's the reason why USB2 LPM, which only enables when Removable == 1, caused many issues for us.

>
> I've tested this on a bunch of 2-in-1s with detachable USB
> keyboards and it is almost completely random and not at
> all related to the reality (both for the kbd-dock connector
> and for other USB ports) this info is so unreliable it is
> pretty much useless.

Ok, that means USB2 LPM will be inadvertently enabled for those devices...
Doesn't sound right.

>
> And the kbd-dock is special anyways, the dock is removable,
> but when docked / the 2-in-1 is in laptop mode, the touchpad
> should be treated as an internal touchpad, as it will be
> covered by the display when the lid is closed.
>
> Note I'm mostly referring to 2-in-1's which turn into normal
> clamshell laptops when docked into their keyboard dock,
> not those with the flimsy type-covers.
>
> Good example's of the type I mean are the Asus T100 series
> and the Lenovo Miix 300 / 310 / 320.

Ok, good to know the difference.

>
>>> 2) Teach systemd-logind which does the suspend-on-lid on modern
>>> GNOME3 based systems to disable wakeup on the parent of
>>> input-devices which have this tag set before suspending.
>>>
>>> As mentioned the kernel can then also use this to save
>>> some power by disable scanning for fingers on suspend.
>>>
>>> If you have time to work on these 2 items, that would be
>>> great. Once this is in place I'm fine with the suggested
>>> kernel change.
>> Ok, let me investigate a bit more.
>>>
>>> ###
>>>
>>> Semi-off-topic:
>>>
>>> The thread I linked to above is about adding a new inhibit
>>> feature to the input system, which is intended to allow
>>> telling the input system system to stop listening for
>>> events even if userspace has the device open (or it has
>>> in kernel listeners) once this has landed, we can use
>>> the same udev-tag to also teach systemd-logind to inhibit
>>> e.g. the touchpad when the lid is closed but the system is
>>> not suspending (e.g. external monitor connected).
>>> Combined with some extra hid-multitouch changes this will
>>> again allow us to tell the touchpad to stop scanning
>>> for fingers saving some power.
>>>
>>> The inhibit feature could likewise also be enabled on
>>> internal touchpads and keyboards when a 360 degree (yoga)
>>> style 2-in-1 is in tablet mode to avoid accidental key-pressed /
>>> touchpad touches.
>> I thought disabling keyboard/touchpad in tablet mode is done by system firmware.
>> Is it different now?
>
> It is in some cases but not always.
>
>> How does the kernel know it's in tablet mode?
>
> There are various interfaces for this. I've recently submitted
> a series of patches upstream adding an input dev with SW_TABLET
> functionality to most 2-in-1s:
>
> -I've done a bunch of fixes making the intel-vbtn code export
> SW_TABLET on more devices
>
> -I've added support for the INT33D3 ACPI device to
> the soc_button_array driver, this device offers direct access
> to a GPIO for SW_TABLET mode (when it has a non empty resource list)
>
> -I've added SW_TABLET support for HP devices using HP's custom WMI
> method for this to the hp_wmi driver.

Thanks for implementing this.

Seems like more and more devices will use software-based implementation for disabling input device.

>
>
>>> Note the inhibit when in tablet mode thing would require
>>> a new: "input-inhibit-on-tablet-mode" tag. At first I
>>> was thinking to just have an "input-device-is-internal"
>>> tag, but that would e.g. also apply to the touchscreen,
>>> on which we do want to disable wakeup when the lid is
>>> closed, but not when in tablet mode.
>>>
>>> Hmm, I guess to prepare for the inhibit stuff we should
>>> probably call the other tag "input-inhibit-on-closed-lid"
>>> rather then "input-disable-wake-on-closed-lid", and then
>>> systemd-logind can defer that wit should also disable wake
>>> (or initially only disable wake) from that. Otherwise we
>>> get 4 possible tags and I don't see a usecase where we
>>> want to inhibit but not also disable wakeup.
>> I'll focus on the clamshell case for now, I don't have 2-in-1 in hand right now.
>
> Ok, for the clamshell case it should be easy to have a few'
> simple universal (based on the bus/connection type of the touchpad)
> udev rules to tag them as "input-disable-wake-on-closed-lid".
>
> Because of the Bay/Cherry Trail hw-enablement I've been doing as
> a personal side project I have quite a few 2-in-1s, so I can
> build on top of that adding a hwdb template + some initial
> entries for tagging the touchpads in detachable keyboards the
> same way.

Ack.

>
>
>>>
>>>>> Also your commit message mentions touchpads, but the change
>>>>> will also enable wakeup on most touchscreens out there, meaning
>>>>> that just picking up a device in tablet mode and accidentally
>>>>> touching the screen will wake it up.
>>>> I tried touch and i2c-hid touchscreen and it doesn't wake up the system.
>>>
>>> I guess the :
>>>
>>> i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
>>>
>>> Call from i2c_hid_suspend() causes that, interesting that that
>>> works for touchscreens but not for touchpads.
>>>
>>> I'm pretty sure that if you comment out that line, your
>>> patch will cause wake-ups on touchscreens too, which
>>> IMHO means that your patch should maybe move the above
>>> call into the else of the:
>>>
>>> if (device_may_wakeup(&client->dev)) {
>>>
>>> block, if we enable wakeup then it should work. This should
>>> probably be combined with being smarter about which devices
>>> to enable wakeup on by default...
>> Comment out that line doesn't make touchscreen have the ability to wake up the system.
>> My guess is that there's no ACPI GPIO connects to the touchscreen.
>
> But there is, i2c-hid devices always have an interrupt line,
> possibly the touchpad gets powered of on transition to D3 though.
>
> Yes that is probably it, many touchscreens have a _PS3 method
> like this:
>
> Method (_PS3, 0, Serialized) // _PS3: Power State 3
> {
> If ((^^^I2C7.AVBL == One))
> {
> DATA = 0x1C
> ^^^I2C7.DL13 = BUFF /* \_SB_.PCI0.I2C6.TCS0.BUFF */
> }
> }
>
> Which tells the PMIC to turn of the LDO supplying the touchscreen,
> so that means no wake from suspend by the touchscreen.
>
> I'm pretty sure you will find something similar in the ACPI
> node describing the touchscreen on the device you are testing
> with.
>
> If the ACPI tables do not explicitly turn off the device on D3,
> then it would be good to disable wake, and it cannot hurt on
> devices which do completely power down the touchscreen. So
> extending the "input-disable-wake-on-closed-lid" stuff to the
> touchscreen is probably a good idea. But for starters lets just
> focus on touchpads.

Ok, thanks for the explanation!

>
>>>> However we should still handle the two different cases, probably differentiate touchpad and touchscreen in hid-multitouch.
>>>
>>> Ack, see above.
>>>
>>>>> Also hid multi-touch devices have 3 modes, see the diagrams
>>>>> in Microsoft hw design guides for win8/10 touch devices:
>>>>> 1. Reporting events with low latency (high power mode)
>>>>> 2. Reporting events with high latency (lower power mode)
>>>>> 3. Not reporting events (lowest power mode)
>>>>>
>>>>> I actually still need to write some patches for hid-multitouch.c
>>>>> to set the mode to 2 or 3 on suspend depending on the device_may_wakeup
>>>>> setting of the parent. Once that patch is written, it should
>>>>> put most i2c-hid mt devices in mode 3, hopefully also helping
>>>>> with Linux' relative high power consumption when a device is
>>>>> suspended. With your change instead my to-be-written patch
>>>>> would put the device in mode 2, which would still be an
>>>>> improvement but less so.
>>>> IIRC, touchpad and touchscreen connect to different parents on all laptops I worked on.
>>>> So I think it's possible to enable mode 2 for touchpad, and mode 3 for touchscreen.
>>>
>>> Ack.
>>>
>>>> Touchpad wake is really handy, let's figure out how to enable it while covering all potential regression risks.
>>>
>>> See above I believe we should first get the userspace bits to disable it
>>> when the lid is closed in place. And even then we may need to have
>>> a Kconfig option to disable it for people running an older userspace,
>>> but I guess once the userspace bits are there, we can proceed without
>>> the Kconfig option and then add that later if necessary (if people are
>>> seeing regressions).
>> For now, as a comprise, can we still enable the wake up capability but disable it by default?
>> i.e. "device_init_wakeup(dev, false)" so user can still choose to enable touchpad wakeup at their own discretion.
>
> Yes that sounds reasonable.

Thanks, will send a new revision.

Kai-Heng

>
> Regards,
>
> Hans
>
>
>
>> Kai-Heng
>>>
>>> Regards,
>>>
>>> Hans
>>>
>>>
>>>>>> Signed-off-by: Kai-Heng Feng <kai.heng.feng@xxxxxxxxxxxxx>
>>>>>> ---
>>>>>> v3:
>>>>>> - Use device_init_wakeup().
>>>>>> - Wording change.
>>>>>> v2:
>>>>>> - Fix compile error when ACPI is not enabled.
>>>>>> drivers/hid/i2c-hid/i2c-hid-core.c | 10 ++++++++++
>>>>>> 1 file changed, 10 insertions(+)
>>>>>> diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c
>>>>>> index 294c84e136d7..dae1d072daf6 100644
>>>>>> --- a/drivers/hid/i2c-hid/i2c-hid-core.c
>>>>>> +++ b/drivers/hid/i2c-hid/i2c-hid-core.c
>>>>>> @@ -931,6 +931,12 @@ static void i2c_hid_acpi_fix_up_power(struct device *dev)
>>>>>> acpi_device_fix_up_power(adev);
>>>>>> }
>>>>>> +static void i2c_hid_acpi_enable_wakeup(struct device *dev)
>>>>>> +{
>>>>>> + if (acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0)
>>>>>> + device_init_wakeup(dev, true);
>>>>>> +}
>>>>>> +
>>>>>> static const struct acpi_device_id i2c_hid_acpi_match[] = {
>>>>>> {"ACPI0C50", 0 },
>>>>>> {"PNP0C50", 0 },
>>>>>> @@ -945,6 +951,8 @@ static inline int i2c_hid_acpi_pdata(struct i2c_client *client,
>>>>>> }
>>>>>> static inline void i2c_hid_acpi_fix_up_power(struct device *dev) {}
>>>>>> +
>>>>>> +static inline void i2c_hid_acpi_enable_wakeup(struct device *dev) {}
>>>>>> #endif
>>>>>> #ifdef CONFIG_OF
>>>>>> @@ -1072,6 +1080,8 @@ static int i2c_hid_probe(struct i2c_client *client,
>>>>>> i2c_hid_acpi_fix_up_power(&client->dev);
>>>>>> + i2c_hid_acpi_enable_wakeup(&client->dev);
>>>>>> +
>>>>>> device_enable_async_suspend(&client->dev);
>>>>>> /* Make sure there is something at this address */