Page MenuHomeFreeBSD

ig4: unconditionally un-idle the controller core on resume
ClosedPublic

Authored by dteske on Fri, Jun 19, 5:36 PM.
Referenced Files
Unknown Object (File)
Sat, Jun 20, 7:57 PM
Unknown Object (File)
Sat, Jun 20, 6:58 PM
Unknown Object (File)
Sat, Jun 20, 3:24 AM
Subscribers

Details

Summary

On controllers with the LPSS "additional registers" (Skylake and later,
IG4_HAS_ADDREGS), ig4iic_suspend() places the controller in the device
idle state (IG4_DEVICE_IDLE) and asserts core reset. While idle the
DesignWare core is power-gated: its register bank reads back as zero and
writes are dropped until the core is taken out of the idle state again.

ig4iic_set_config(), called from ig4iic_resume(), only performs that
un-idle handshake when it observes IG4_RESTORE_REQUIRED set in
DEVIDLE_CTRL. Some platforms (e.g. Intel Alder Lake-P) do not raise
that status across suspend-to-idle (S0ix). The core is then left gated:
set_config()'s register writes have no effect, it nevertheless returns
success, and every subsequent transfer fails with IIC_ETIMEOUT, leaving
child I2C-HID devices (touchpad, touchscreen) dead after resume.

Perform the un-idle handshake unconditionally in ig4iic_resume() for
IG4_HAS_ADDREGS controllers, symmetric with ig4iic_suspend(), so the
core is reliably restored before reconfiguration regardless of the
RESTORE_REQUIRED status.

While integrating these I2C-HID touch devices, also raise hid(4)'s
MAXLOCCNT from 2048 to 4096: the report-descriptor parser truncates any
variable main item whose usage count exceeds MAXLOCCNT, silently
dropping the trailing report fields. Some contemporary touch devices
declare variable items with more than 2048 entries, so part of their
input reports was being discarded.

MFC after: 2 weeks

Test Plan

Hardware: Framework 12, Intel Raptor Lake-P; ig4(4) I2C controller with
a PIXA3854 touchpad and ILIT2901 touchscreen attached via iichid(4).

Before: after an s2idle/suspend_to_idle suspend/resume cycle, every
transfer on the ig4 controller fails with IIC_ETIMEOUT and the touchpad
and touchscreen are dead until reboot.

After: 3 consecutive suspend/resume cycles, both devices keep working
every time. A/B tested to confirm ig4(4) change alone is sufficient.

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Skipped
Unit
Tests Skipped
Build Status
Buildable 74070
Build 70953: arc lint + arc unit

Event Timeline

sys/dev/ichiic/ig4_iic.c
1186

i get why you'd want to wait a while for things to settle, but is there a more reliable way? eg is there some bit that will return a specific value once core power/reset has stabilised?

sys/dev/ichiic/ig4_iic.c
1186

Good call — there is a deterministic signal, and I switched to it.

I instrumented the resume path on Alder/Raptor Lake-P (Framework Laptop 12):

  • At the original pause() location (after the DEVIDLE un-idle, before the core reset is deasserted) the entire DesignWare register bank still reads back 0x0: the core reset asserted in ig4iic_suspend() is still asserted there, so there is nothing to poll yet. I polled COMP_TYPE for 10 ms and never saw it change.
  • The core only becomes readable after the RESETS_SKL deassert in ig4iic_set_config(). Immediately after that deassert, COMP_TYPE reads its fixed signature 0x44570140 (and COMP_VER/COMP_PARAM1 are valid) with zero wait, on every cold boot and every resume.

So I dropped the blind pause() entirely and instead poll COMP_TYPE right
after the core reset is deasserted, breaking out as soon as it returns
the signature (bounded to ~1 ms for slower silicon). Measured settle on
this hardware is 0 us. This also covers the existing RESTORE_REQUIRED
path, since both reach that reset.

I left the pre-existing pause() in the RESTORE_REQUIRED branch (between
the un-idle and the reset) untouched to keep the change scoped — happy to
convert that one too if you'd prefer.

Updated the diff.

Address Adrian's comment with using a blind pause. This was seen elsewhere
in the code and used when Adrian correctly points out that it could be done
better. In other words, just because it was done elsewhere does not mean it
should continue being done that way. This update does it a better way.

sys/dev/ichiic/ig4_iic.c
925

So, see here how it's reprogramming some stuff based on hardware type / config and if it was reset above?
And looking at it now, the reset path looks suspiciously like what you're doing.

I wonder if it would be cleaner to modify ig4iic_set_config() to take a third parameter, call it "force restore",
which is false except in the restore path you're modifying where you'd call it set to true.

Then the if statement becomes something like:

if (IG4_HAS_ADDREGS(sc->version) && ((force_restore == true) || (v & IG4_RESTORE_REQUIRED))) {

.. which would force do the reset/restore here, set reset = true (which happens to be the case in your current diff).

Then your resume handler would be:

bool force_restore = false;
...
if (IG4_HAS_ADDREGS(sc->version) 
  force_restore = true;
...
	if (ig4iic_set_config(sc, IG4_HAS_ADDREGS(sc->version), force_restore))
``

That way we have a way to force a reset /and/ force a full restore whilst keeping the actual reset/restore handshake in one place.

What do you think?
951

This is good; I'd add a final check after the loop to see if it's fully woken up and if not print a nice big warning.
(Later on after this lands we can refactor the check out, return an error if it doesn't wake up, etc..)

1185

You may want to put that pause() back here just to match the other reset logic being done in set_config().

sys/dev/ichiic/ig4_iic.c
951

Thanks! Will do

1185

👍

sys/dev/ichiic/ig4_iic.c
925

I am fine with this approach. I discussed with Deb that I only intend to MFC this to stable/15 and also likely releng/15.1 for a p-release to propagate it to other Framework users (I usually don't like to change API, but it's file-local and static, so MFC friction should be low -- especially bounded to just the stable/releng 15 series)

Address review: consolidate the un-idle/restore handshake into ig4iic_set_config() behind a force_restore argument (per adrian) so ig4iic_resume() no longer duplicates it; warn if the core never leaves reset after the COMP_TYPE poll; restore the matching pause() in the handshake.

This revision is now accepted and ready to land.Sat, Jun 20, 9:58 PM

Address review: consolidate the un-idle/restore handshake into ig4iic_set_config() behind a force_restore argument (per adrian) so ig4iic_resume() no longer duplicates it; warn if the core never leaves reset after the COMP_TYPE poll; restore the matching pause() in the handshake.

Oh, late to the party, but was going to suggest the same thing as Adrian (to factor out the register writes), so great.