[PATCH] drm/amd/display: fall back to software I2C on hardware engine failure
From: NepNep7601
Date: Wed Aug 26 2026 - 13:14:19 EST
dce_i2c_submit_command() returns the hardware I2C engine's result
directly, so a failed transfer is reported to the caller even though a
bit-banging software engine is available. That engine is only reachable
when the hardware engine cannot be acquired, never when its transfer
fails.
On DCE6 the hardware engine acknowledges a slave address but fails to
complete a 128 byte EDID block read. On an Oland-based GPU (Radeon
R7 430, rebranded R7 240, 1002:6611) driving a monitor through a
passive DP to HDMI to DVI chain, this leaves
dm_helpers_read_local_edid() with EDID_NO_RESPONSE:
[ 6.684221] [drm:dm_helpers_read_local_edid [amdgpu]] *ERROR* EDID err: 2, on connector: DP-1
[ 6.684726] amdgpu 0000:01:00.0: [drm] *ERROR* No EDID read.
The connector is then left with no modes and falls back to 640x480
instead of the display's native 1600x900. The DDC bus itself is fine:
i2cdetect sees the EDID EEPROM acknowledge at 0x50, while a real block
read with "i2ctransfer -y 1 w1@0x50 0x00 r128" fails with EIO. Short
transfers work, long ones do not.
dce_i2c_submit_command_hw() clears i2c_hw_buffer_in_use, releases the
engine and closes the DDC line on every exit path, so the software
engine can safely re-acquire it. Retry there rather than failing
outright. Behaviour is unchanged whenever the hardware engine
succeeds, and the fallback only runs where the transfer had already
failed.
Assisted-by: Claude:claude-opus-5
Signed-off-by: NepNep7601 <neptune@xxxxxxxxxxxxxxxxxxxx>
---
Tested on 7.1.8 on an Oland (Radeon R7 430, rebranded R7 240, 1002:6611)
with a passive DP to HDMI to DVI chain: with this patch the EDID reads
256 bytes and the display comes up at its native 1600x900 instead of
640x480. Without it, dm_helpers_read_local_edid() returns
EDID_NO_RESPONSE.
On amd-staging-drm-next this is compile-tested only.
drivers/gpu/drm/amd/display/dc/dce/dce_i2c.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/dc/dce/dce_i2c.c b/drivers/gpu/drm/amd/display/dc/dce/dce_i2c.c
index f5261e8d7678..238c17e6f51d 100644
--- a/drivers/gpu/drm/amd/display/dc/dce/dce_i2c.c
+++ b/drivers/gpu/drm/amd/display/dc/dce/dce_i2c.c
@@ -72,9 +72,16 @@ bool dce_i2c_submit_command(
dce_i2c_hw = acquire_i2c_hw_engine(pool, ddc);
- if (dce_i2c_hw)
- return dce_i2c_submit_command_hw(pool, ddc, cmd, dce_i2c_hw);
+ if (dce_i2c_hw && dce_i2c_submit_command_hw(pool, ddc, cmd, dce_i2c_hw))
+ return true;
+ /*
+ * The hardware I2C engine on DCE6 can fail to complete longer
+ * transfers, such as a 128 byte EDID block read, even when the slave
+ * acknowledges its address. dce_i2c_submit_command_hw() releases the
+ * engine and closes the DDC line on every exit path, so retry the
+ * transfer on the bit-banging software engine instead of giving up.
+ */
dce_i2c_sw.ctx = ddc->ctx;
if (dce_i2c_engine_acquire_sw(&dce_i2c_sw, ddc)) {
return dce_i2c_submit_command_sw(pool, ddc, cmd, &dce_i2c_sw);
--
2.47.3