Skip to content

i2c-designware: lcnt/2 SDA hold default is a no-op on rpi-7.x (fs_lcnt still 0), regressing SSD1306 OLED on Pi 5 #7740

Open

Description

@jflecool2

Disclosure: this report was written by an AI assistant (GitHub Copilot CLI,
Claude Sonnet 5.5) working with a human engineer who owns the hardware and
reviewed the measurements. The kernel experiments were run on the engineer's
Pi 5.

Summary

The Pi-specific default "SDA hold = SCL low count / 2" (#6091, b74c3e1 on
rpi-6.18.y) no longer takes effect on rpi-7.0.y .. rpi-7.3.y. In 7.x the line sits in
i2c_dw_probe() before i2c_dw_probe_master() has computed fs_lcnt, so it
evaluates to 0. The driver then keeps the hardware reset value (1 cycle, ~5 ns)
and the Pi 5 returns to the behaviour #6091 and #6056 fixed.

Impact

Adafruit PiOLED (128x32 SSD1306, 0x3c) on a Pi 5, no DT i2c-sda-hold-time-ns:

  • stock 6.18.60+rpt-rpi-2712: works
  • custom 7.3.1 (rpi-7.3.y): SDA stuck at low, OSError: [Errno 121] Remote I/O error
  • same Pi, same userspace, same DT, same cmdline

Same symptom as #6056 (AQM0802 LCD: lost arbitration / SDA stuck at low).
Another Pi 5 on 7.3.0-rc3 has the same IC_SDA_HOLD and its OLED happens to work,
so it is marginal and device dependent, not universal.

Root cause

rpi-6.18.y / 6.19.y: the default is set inside i2c_dw_set_timings_master()
right after dev->fs_lcnt = lcnt;.

rpi-7.0.y .. 7.3.y (first bad: bb68f1f on rpi-7.0.y; later rebases
d2587df, 6b7c1f3, 6019937): the line is in i2c_dw_probe():

if (!dev->sda_hold_time)
    dev->sda_hold_time = dev->fs_lcnt / 2;   /* fs_lcnt == 0 here */
ret = i2c_dw_set_sda_hold(dev);
...
ret = i2c_dw_probe_master(dev);              /* computes fs_lcnt */

This follows mainline 38fa29b ("Combine the init functions"), which moved
i2c_dw_set_sda_hold() ahead of the timing calculation. Mainline has no such
default, so nothing upstream notices.

Evidence (printk on 7.3.1 + instrumented module)

probe: fs_lcnt=0, sda_hold_time=0 before default
default: sda_hold_time = fs_lcnt/2 = 0
set_sda_hold: sda_hold_time was 0 -> kept HW reset value 0x1
after set_timings_master: fs_lcnt=1186

Register (/dev/mem read of RP1 i2c1 at 0x1f00074000, clk_sys 200 MHz, 100 kHz):

kernel IC_SDA_HOLD OLED
6.18.60+rpt-rpi-2712 stock 0x10251 works
7.3.1 unpatched 0x10001 fails
7.3.1 with patch below 0x10251 works

0x251 = 593 = 1186 / 2 cycles, about 3 us.

What was checked

Proposed fix

Compute the default after the timings are known, then run i2c_dw_set_sda_hold()
(which also adds the RX hold bit). Keeps all existing logic; only reorders:

--- a/drivers/i2c/busses/i2c-designware-common.c
+++ b/drivers/i2c/busses/i2c-designware-common.c
@@ -949,18 +949,18 @@ int i2c_dw_probe(struct dw_i2c_dev *dev)
 	if (ret)
 		return ret;
 
-	if (!dev->sda_hold_time)
-		dev->sda_hold_time = dev->fs_lcnt / 2;
-
-	ret = i2c_dw_set_sda_hold(dev);
+	ret = i2c_dw_set_fifo_size(dev);
 	if (ret)
 		return ret;
 
-	ret = i2c_dw_set_fifo_size(dev);
+	ret = i2c_dw_probe_master(dev);
 	if (ret)
 		return ret;
 
-	ret = i2c_dw_probe_master(dev);
+	if (!dev->sda_hold_time)
+		dev->sda_hold_time = dev->fs_lcnt / 2;
+
+	ret = i2c_dw_set_sda_hold(dev);
 	if (ret)
 		return ret;

Tested on Pi 5 with 7.3.1: IC_SDA_HOLD = 0x10251, OLED connects and refreshes,
no restarts. Not tested on other boards or with slave mode.

Caveats: "first bad" comes from reading the branch history (rebased, non-linear),
not a build bisect.

PR with the same change: see the linked pull request from jflecool2:i2c-designware-sda-hold-fix.

Activity

  1. jflecool2 commented on Nov 6, 2026

    @jflecool2
    Author

    Hey, this was indeed generated by AI. I challenged it as much as I could, and forced it to test its solution and researched. I think other may benefits from their i2c stuff working, but if you dont want this patch because you dont trust this AI, then its ok too! No hard feeling :)

  2. pelwell commented on Nov 6, 2026

    @pelwell
    Contributor

    This should be fixed now #7741 has been merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions