[PATCH 1/2] i2c: qcom-cci: Reset the controller when it resumes

From: Birk Skyum

Date: Tue Oct 20 2026 - 15:41:00 EST


A CCI master can stop starting transfers after a target on its bus was
powered down and up again while the controller kept its clocks running.
The next transfer's queue then stays on its first command, neither
CCI_IRQ_STATUS_0 nor the queue's report status changes, and
cci_run_queue() times out after 100 ms ("master 1 queue 0 timeout").
Only the controller reset in that error path, or the controller's power
domain collapsing while it is runtime suspended, brings it back.

On the Lenovo Yoga Slim 7x (X1E80100) the OV02C10 front camera is
powered down whenever it stops streaming, and the unpowered sensor
holds SCL and SDA low: both read low at the TLMM pins with the internal
pull-ups enabled. The queue registers at the timeout show the stuck
first command (CUR_CMD 0x08010339, a write of 03 01 08) with the bus
timing registers intact and no error or halt interrupt before it.

cci_resume_runtime() only programs the interrupt mask and the bus timing
again, so a controller that was runtime suspended without its power
domain collapsing keeps the stuck state. With the camera's TITAN_TOP
GDSC held on and the controller suspended between camera starts, 3 of
40 starts failed this way; with a reset on resume, 0 of 40.

Reset the controller on every runtime resume, as the timeout path does.

Signed-off-by: Birk Skyum <birk.skyum@xxxxx>
---
drivers/i2c/busses/i2c-qcom-cci.c | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/drivers/i2c/busses/i2c-qcom-cci.c b/drivers/i2c/busses/i2c-qcom-cci.c
index 76a5ded..51ce174 100644
--- a/drivers/i2c/busses/i2c-qcom-cci.c
+++ b/drivers/i2c/busses/i2c-qcom-cci.c
@@ -651,7 +651,20 @@ static int __maybe_unused cci_resume_runtime(struct device *dev)
if (ret)
return ret;

- return cci_init(cci);
+ /*
+ * A master can stop starting transfers after a target was powered
+ * down and up again while the controller ran, and keeps that state
+ * over a runtime suspend unless the power domain collapsed. Reset
+ * the controller, with the interrupt mask set first so that the
+ * reset-done interrupt also arrives after a power collapse.
+ */
+ ret = cci_init(cci);
+ if (!ret)
+ ret = cci_reset(cci);
+ if (ret)
+ cci_disable_clocks(cci);
+
+ return ret;
}

static const struct dev_pm_ops qcom_cci_pm = {
--
2.53.0