summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrian Hunter <adrian.hunter@intel.com>2026-03-06 10:24:51 +0300
committerAlexandre Belloni <alexandre.belloni@bootlin.com>2026-03-12 00:10:02 +0300
commit9a258d1336f7ff3add8b92d566d3a421f03bf4d2 (patch)
treeecc7d9b4724254e2f9ebf8bef0451b46290ec5d7
parentc6396b835a5e599c4df656112140f065bb544a24 (diff)
downloadlinux-9a258d1336f7ff3add8b92d566d3a421f03bf4d2.tar.xz
i3c: mipi-i3c-hci: Fallback to software reset when bus disable fails
Disruption of the MIPI I3C HCI controller's internal state can cause i3c_hci_bus_disable() to fail when attempting to shut down the bus. In the code paths where bus disable is invoked - bus clean-up and runtime suspend - the controller does not need to remain operational afterward, so a full controller reset is a safe recovery mechanism. Add a fallback to issue a software reset when disabling the bus fails. This ensures the bus is reliably halted even if the controller's state machine is stuck or unresponsive. The fallback is used both during bus clean-up and in the runtime suspend path. In the latter case, ensure interrupts are quiesced after reset. Fixes: 9ad9a52cce282 ("i3c/master: introduce the mipi-i3c-hci driver") Cc: stable@vger.kernel.org Signed-off-by: Adrian Hunter <adrian.hunter@intel.com> Reviewed-by: Frank Li <Frank.Li@nxp.com> Link: https://patch.msgid.link/20260306072451.11131-15-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
-rw-r--r--drivers/i3c/master/mipi-i3c-hci/core.c65
1 files changed, 35 insertions, 30 deletions
diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c
index d5e91af7d569..284f3ed7af8c 100644
--- a/drivers/i3c/master/mipi-i3c-hci/core.c
+++ b/drivers/i3c/master/mipi-i3c-hci/core.c
@@ -181,6 +181,34 @@ static int i3c_hci_bus_disable(struct i3c_hci *hci)
return ret;
}
+static int i3c_hci_software_reset(struct i3c_hci *hci)
+{
+ u32 regval;
+ int ret;
+
+ /*
+ * SOFT_RST must be clear before we write to it.
+ * Then we must wait until it clears again.
+ */
+ ret = readx_poll_timeout(reg_read, RESET_CONTROL, regval,
+ !(regval & SOFT_RST), 0, 10 * USEC_PER_MSEC);
+ if (ret) {
+ dev_err(&hci->master.dev, "%s: Software reset stuck\n", __func__);
+ return ret;
+ }
+
+ reg_write(RESET_CONTROL, SOFT_RST);
+
+ ret = readx_poll_timeout(reg_read, RESET_CONTROL, regval,
+ !(regval & SOFT_RST), 0, 10 * USEC_PER_MSEC);
+ if (ret) {
+ dev_err(&hci->master.dev, "%s: Software reset failed\n", __func__);
+ return ret;
+ }
+
+ return 0;
+}
+
void i3c_hci_sync_irq_inactive(struct i3c_hci *hci)
{
struct platform_device *pdev = to_platform_device(hci->master.dev.parent);
@@ -196,7 +224,8 @@ static void i3c_hci_bus_cleanup(struct i3c_master_controller *m)
{
struct i3c_hci *hci = to_i3c_hci(m);
- i3c_hci_bus_disable(hci);
+ if (i3c_hci_bus_disable(hci))
+ i3c_hci_software_reset(hci);
hci->io->cleanup(hci);
}
@@ -626,34 +655,6 @@ static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id)
return result;
}
-static int i3c_hci_software_reset(struct i3c_hci *hci)
-{
- u32 regval;
- int ret;
-
- /*
- * SOFT_RST must be clear before we write to it.
- * Then we must wait until it clears again.
- */
- ret = readx_poll_timeout(reg_read, RESET_CONTROL, regval,
- !(regval & SOFT_RST), 0, 10 * USEC_PER_MSEC);
- if (ret) {
- dev_err(&hci->master.dev, "%s: Software reset stuck\n", __func__);
- return ret;
- }
-
- reg_write(RESET_CONTROL, SOFT_RST);
-
- ret = readx_poll_timeout(reg_read, RESET_CONTROL, regval,
- !(regval & SOFT_RST), 0, 10 * USEC_PER_MSEC);
- if (ret) {
- dev_err(&hci->master.dev, "%s: Software reset failed\n", __func__);
- return ret;
- }
-
- return 0;
-}
-
static inline bool is_version_1_1_or_newer(struct i3c_hci *hci)
{
return hci->version_major > 1 || (hci->version_major == 1 && hci->version_minor > 0);
@@ -764,8 +765,12 @@ static int i3c_hci_runtime_suspend(struct device *dev)
int ret;
ret = i3c_hci_bus_disable(hci);
- if (ret)
+ if (ret) {
+ /* Fall back to software reset to disable the bus */
+ ret = i3c_hci_software_reset(hci);
+ i3c_hci_sync_irq_inactive(hci);
return ret;
+ }
hci->io->suspend(hci);