Ten days of measuring what a Synaptics touchpad does after the laptop wakes up. Three symptoms, two of which turned out to be one fault, one fix that was quietly causing the next problem, and two kernel patches at the end of it.
Half of all resumes came back with 12.5 ms reports and merged 24 ms frames instead of a flat 15 ms.
Quick taps vanished; two-finger scrolls occasionally became a zoom.
After a sleep, the cursor sat still on touch-down and then leapt. Appeared on the 8th, once symptom 1 was "fixed".
Where it landed
Symptoms 1 and 3 are one fault. On resume, the PS/2 driver deactivates the pad, and on this pad that command is a firmware reset. The SMBus driver then reconfigures the pad within a few milliseconds, while it is still coming up. Configured that early, it misbehaves until its next reset, as one symptom or the other. A 300 ms wait before reconfiguring cures both.
Symptom 2 is the firmware's power-saving doze. With the default doze interval the pad never reports most short touches and garbles the first frame of two-finger touches. Lenovo's Windows driver sets a longer interval for this pad and eleven others; the same value on Linux gives 20/20 taps and 0 pinches in 145 scrolls.
This machine's touchpad had been stuck in PS/2 fallback since the laptop was new, because AMD's SMBus controller does not advertise Host Notify and the RMI4 driver will not attach without it. An earlier write-up covers the fix: an out-of-tree i2c-piix4 that drives the chipset's ASF block for Host Notify, the same single-driver shape Intel uses. With that installed the pad runs natively over SMBus at a clean 15 ms per report, and three residual problems came into focus.
What follows is the order the facts were found in, because the order is the point. Each fix was measured, each one cleared its symptom, and two of them were wrong.
Every conclusion here rests on one number, so it is worth being precise about it. A capture is thirty seconds of raw evtest output while six slow single-finger drags are made, lifting fully between them. For each drag the analyser takes the distance the reported position moves between the first and second frame. On a healthy pad that is zero or near it: the finger has just landed and is barely moving. On a pad in the wake-jump state it is 60 to 140 units, 5 to 12 mm, in a single 15 ms frame, a speed no slow drag ever reaches.
Two things made the rig trustworthy. The interrupt counters along the pipeline, ASF hard interrupt, Host Notify into the RMI driver, F12 finger frames, and evtest reports, were snapshotted around every capture and always agreed, which ruled out host-side loss early. And a capture that needs hands is only accepted once it contains real drags; the script re-cues and retries, and a multi-step sequence aborts rather than advancing. That rule was added the hard way, after a night where a four-step bisect ran to completion on four empty captures.
This was the clean thread. With the counters agreeing, the missing taps were missing at the source: the pad simply reported six of twenty quick taps, one frame each. Two-finger landings were reported with the objects in the wrong order, so libinput saw finger separation collapse from 165 to 78 units in one frame and called it a pinch, scale 2.45.
The pad's F01 doze registers explained it. Doze is the firmware's low-power scan mode between touches: interval 3, wakeup threshold 45, holdoff 0 on this pad, so it drops into doze the instant a finger lifts and samples too slowly to catch a short touch. Lenovo's Windows driver for this family sets RMIDozeInterval=6 for twelve pad IDs, LEN2073 among them, and does not disable doze. Measured with each configuration:
| doze configuration | quick taps seen of 20 | pinches / scroll episodes |
|---|---|---|
| firmware default (interval 3) | 6, one frame each | 1 / 10 |
| doze disabled (NoSleep) | 20 | 0 / 232 |
| Windows value (interval 6), doze kept | 20, each 60–190 ms | 0 / 145 |
The Windows value keeps the power saving and fixes both. It goes upstream as a small table in the PS/2 Synaptics driver, passing the interval through platform data for the same twelve IDs. Two red herrings on the way, both cheap to rule out and worth naming: libinput's thumb pressure threshold (fingertips here report 50 to 70, well under it) and ASF bus collisions between the pad and the TrackPoint pass-through (the counters matched).
The SMBus transport's resume path does three things in order: reset its own register-mapping cache, run the RMI configuration pass, then wake the sensor. The configuration pass rewrites every function's control registers: the F01 device control byte, the three doze bytes, and the interrupt mask. Reading that order, the story wrote itself: configuring a sensor that is still asleep half-applies, wake it afterwards and you get the degraded clock. Moving the pass after the wake gave 0 degraded resumes in 8, where the stock order gave 8 in 8. A patch was drafted.
It was tested with a knob that ran the pass both before and after the wake, because that was the smallest change. That knob stayed loaded. Two days later the cursor started sticking after every sleep.
The jump looked like a firmware sleep state. The register dump was byte-identical to a healthy pad; the report clock was perfect; and it seemed to need hours of suspend, since 90-second probe cycles had never shown it. Every one of those impressions was wrong, and the sequence of experiments that dismantled them is the useful part of this document.
| wait between the pad's reset and the config pass | first-frame jump, median | drags from rest | clock |
|---|---|---|---|
| 0 ms | 91 | 0 / 7 | 15 ms |
| 30 ms | 97 | 0 / 6 | 15 ms |
| 100 ms | 91 | 0 / 7 | 15 ms |
| 300 ms | 0 | 7 / 7 | 15 ms |
| 2000 ms | 0 | 7 / 7 | 15 ms |
| 300 ms, across a real 4-minute suspend, stock order | 0 | 7 / 7 | flat 15 ms |
| 0 ms across a real suspend, stock order (the original bug) | 0 | 8 / 8 | 12.5 ms, merged frames |
The fault, stated once
On resume the PS/2 driver's deactivate resets the pad. Configuring it within roughly 100 ms of that reset leaves it broken until the next reset, as either the 80 Hz clock or the stale landing frame. Waiting 300 ms first gives a pad that is right in both respects. The reorder fix worked on the clock only because its second pass came late enough; the Get ID fix worked in place only because the script slept first.
libinput debug-events can never show it.rtcwake so no stopwatch is involved.| patch | change | status |
|---|---|---|
| 0001 Input: synaptics-rmi4 | 300 ms settle in rmi_smb_resume() before reconfiguring, with the measurement table in the message | measured across S3, checkpatch clean |
| 0002 Input: synaptics | Windows doze interval (6) via platform data for the twelve LEN pad IDs | measured, checkpatch clean |
| retired: reorder the resume pass | cured the clock, caused the wake jump | withdrawn |
| retired: Get ID on resume | a reset presented as a cure; worked only with a sleep it did not have | withdrawn |
Still to do before sending: a few more natural resumes including an overnight one under the settle delay; narrowing the threshold between 100 and 300 ms if a reviewer asks; real names in the sign-offs. The upstream RFC for the ASF transport itself, which all of this depends on, is a separate piece of work. One unexplained observation remains and is moot for the fix: in place, a second config pass issued a second or more after a clean first one also produced the jump, as if a doze rewrite on an awake sensor is itself a small reset.