git: 8c86b9812403 - main - iichid(4): Improve idle sampling hysteresis

From: Alexander Motin <mav_at_FreeBSD.org>
Date: Sat, 23 Dec 2023 04:11:05 UTC
The branch main has been updated by mav:

URL: https://cgit.FreeBSD.org/src/commit/?id=8c86b981240324c1daaa387d4d3f8e3e53db3d2e

commit 8c86b981240324c1daaa387d4d3f8e3e53db3d2e
Author:     Alexander Motin <mav@FreeBSD.org>
AuthorDate: 2023-12-23 03:50:52 +0000
Commit:     Alexander Motin <mav@FreeBSD.org>
CommitDate: 2023-12-23 03:50:52 +0000

    iichid(4): Improve idle sampling hysteresis
    
    In sampling mode some devices return same data indefinitely even if
    there is nothing to report.  Previous idle hysteresis implementation
    activated only when device returned no data, so some devices ended up
    polled at fast rate all the time.  This new implementation compares
    each new report with the previous, and, if they are identical, after
    reaching threshold also drop sampling rate to slow.
    
    On my Dell XPS 13 9310 with iichid(4) touchscreen and touchpad this
    reduces idle power consumption by ~0.5W by reducing number of context
    switches in the driver from ~4000 to ~700 per second when not touched.
    
    MFC after:      1 month
---
 sys/dev/iicbus/iichid.c | 39 +++++++++++++++++++++++++++++----------
 1 file changed, 29 insertions(+), 10 deletions(-)

diff --git a/sys/dev/iicbus/iichid.c b/sys/dev/iicbus/iichid.c
index 7ab5b61d9be8..1035088c11b5 100644
--- a/sys/dev/iicbus/iichid.c
+++ b/sys/dev/iicbus/iichid.c
@@ -108,7 +108,7 @@ enum {
  */
 #define	IICHID_SAMPLING_RATE_FAST	60
 #define	IICHID_SAMPLING_RATE_SLOW	10
-#define	IICHID_SAMPLING_HYSTERESIS	1
+#define	IICHID_SAMPLING_HYSTERESIS	12	/* ~ 2x fast / slow */
 
 /* 5.1.1 - HID Descriptor Format */
 struct i2c_hid_desc {
@@ -177,9 +177,12 @@ struct iichid_softc {
 	int			sampling_rate_fast;
 	int			sampling_hysteresis;
 	int			missing_samples;	/* iicbus lock */
-	struct timeout_task	periodic_task;		/* iicbus lock */
+	int			dup_samples;		/* iicbus lock */
+	iichid_size_t		dup_size;		/* iicbus lock */
 	bool			callout_setup;		/* iicbus lock */
+	uint8_t			*dup_buf;
 	struct taskqueue	*taskqueue;
+	struct timeout_task	periodic_task;		/* iicbus lock */
 	struct task		event_task;
 #endif
 
@@ -523,7 +526,7 @@ iichid_event_task(void *context, int pending)
 	device_t parent;
 	iichid_size_t actual;
 	bool bus_requested;
-	int error;
+	int error, rate;
 
 	sc = context;
 	parent = device_get_parent(sc->dev);
@@ -541,18 +544,30 @@ iichid_event_task(void *context, int pending)
 		if (actual > 0) {
 			sc->intr_handler(sc->intr_ctx, sc->intr_buf, actual);
 			sc->missing_samples = 0;
-		} else
-			++sc->missing_samples;
+			if (sc->dup_size != actual ||
+			    memcmp(sc->dup_buf, sc->intr_buf, actual) != 0) {
+				sc->dup_size = actual;
+				memcpy(sc->dup_buf, sc->intr_buf, actual);
+				sc->dup_samples = 0;
+			} else
+				++sc->dup_samples;
+		} else {
+			if (++sc->missing_samples == 1)
+				sc->intr_handler(sc->intr_ctx, sc->intr_buf, 0);
+			sc->dup_samples = 0;
+		}
 	} else
 		DPRINTF(sc, "read error occurred: %d\n", error);
 
 rearm:
 	if (sc->callout_setup && sc->sampling_rate_slow > 0) {
-		if (sc->missing_samples == sc->sampling_hysteresis)
-			sc->intr_handler(sc->intr_ctx, sc->intr_buf, 0);
-		taskqueue_enqueue_timeout(sc->taskqueue, &sc->periodic_task,
-		    hz / MAX(sc->missing_samples >= sc->sampling_hysteresis ?
-		      sc->sampling_rate_slow : sc->sampling_rate_fast, 1));
+		if (sc->missing_samples >= sc->sampling_hysteresis ||
+		    sc->dup_samples >= sc->sampling_hysteresis)
+			rate = sc->sampling_rate_slow;
+		else
+			rate = sc->sampling_rate_fast;
+		taskqueue_enqueue_timeout_sbt(sc->taskqueue, &sc->periodic_task,
+		    SBT_1S / MAX(rate, 1), 0, C_PREL(1));
 	}
 out:
 	if (bus_requested)
@@ -725,6 +740,8 @@ iichid_reset_callout(struct iichid_softc *sc)
 
 	/* Start with slow sampling. */
 	sc->missing_samples = sc->sampling_hysteresis;
+	sc->dup_samples = 0;
+	sc->dup_size = 0;
 	taskqueue_enqueue(sc->taskqueue, &sc->event_task);
 
 	return (0);
@@ -812,6 +829,7 @@ iichid_intr_setup(device_t dev, device_t child __unused, hid_intr_t intr,
 	sc->intr_buf = malloc(rdesc->rdsize, M_DEVBUF, M_WAITOK | M_ZERO);
 	sc->intr_bufsize = rdesc->rdsize;
 #ifdef IICHID_SAMPLING
+	sc->dup_buf = malloc(rdesc->rdsize, M_DEVBUF, M_WAITOK | M_ZERO);
 	taskqueue_start_threads(&sc->taskqueue, 1, PI_TTY,
 	    "%s taskq", device_get_nameunit(sc->dev));
 #endif
@@ -825,6 +843,7 @@ iichid_intr_unsetup(device_t dev, device_t child __unused)
 	sc = device_get_softc(dev);
 #ifdef IICHID_SAMPLING
 	taskqueue_drain_all(sc->taskqueue);
+	free(sc->dup_buf, M_DEVBUF);
 #endif
 	free(sc->intr_buf, M_DEVBUF);
 }