[PATCH v5 06/17] i3c: renesas: Perform Dynamic Address Assignment on resume

From: Claudiu Beznea

Date: Mon Jul 13 2026 - 09:09:02 EST


From: Claudiu Beznea <claudiu.beznea.uj@xxxxxxxxxxxxxx>

The Renesas RZ/G3S SoC supports a power saving mode where power to most
SoC components, including I3C, is turned off.

On systems where the I3C devices also loses power during suspend (e.g. NXP
P3T1085UK-ARD connected to the PMOD1_6A connector of the RZ SMARC Carrier
2 + Renesas RZ/G3S SMARC SOM), the devices becomes unreachable after
resume.

Running DAA in the controller resume path restores communication. However,
DAA relies on interrupts for TX/RX, which are not available in the noirq
suspend/resume phase (unless they are wakeup interrupts). For this, the
suspend/resume callbacks were moved out of the noirq phase. Currently,
there is no identified use case on either the Renesas RZ/G3S or Renesas
RZ/G3E SoCs that requires the controller suspend/resume hooks to be part of
the noirq suspend/resume phase.

Since renesas_i3c_reset() is not called anymore in atomic context
update it to use read_poll_timeout().

Along with this, struct renesas_i3c::DATBASn and its usage were removed,
as they are no longer needed.

Fixes: e7218986319b ("i3c: renesas: Add suspend/resume support")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@xxxxxxxxxxxxxx>
---

Changes in v5:
- restore it to the level of v1; the other scenarios updated by
sashiko were already present w/ and w/o this patch and could be
addressed incrementally
- updated the patch description

Changes in v4:
- used directly i3c_dev instead of i3c_dev->dev->desc
- fixed the swap in renesas_i3c_group_devs_in_slots() for i3c->addr[]

Changes in v3:
- added renesas_i3c_group_devs_in_slots(); along with it, the
struct renesas_i3c_addr was updated with i3c_dev and i3c_dev
and the attach/detach/re-attach APIs were adjusted accordingly
- dropped DATBASn member of struct renesas_i3c
- used i3c_master_reattach_i3c_dev_locked() to re-attach devices
on a fully occupied bus
- in resume, moved i2c_mark_adapter_resumed() after i3c_master_do_daa_ext()
since it can update the internal driver data structure i2c specific

Changes in v2:
- adjusted the code to still work in case the full bus was occupied before
a suspend/resume cycle; for that:
-- introduced struct renesas_i3c_addr
-- preserved i3c->DATBASn[] which is saved in suspend and used in resume,
in renesas_i3c_daa()
- updated the patch description to reflect the new updates

drivers/i3c/master/renesas-i3c.c | 38 +++++++++++++-------------------
1 file changed, 15 insertions(+), 23 deletions(-)

diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c
index 6590da962592..acc30ed615ab 100644
--- a/drivers/i3c/master/renesas-i3c.c
+++ b/drivers/i3c/master/renesas-i3c.c
@@ -265,7 +265,6 @@ struct renesas_i3c {
u8 addrs[RENESAS_I3C_MAX_DEVS];
struct renesas_i3c_xferqueue xferqueue;
void __iomem *regs;
- u32 *DATBASn;
struct clk_bulk_data *clks;
struct reset_control *presetn;
struct reset_control *tresetn;
@@ -495,8 +494,8 @@ static int renesas_i3c_reset(struct renesas_i3c *i3c)
renesas_writel(i3c->regs, BCTL, 0);
renesas_set_bit(i3c->regs, RSTCTL, RSTCTL_RI3CRST);

- return read_poll_timeout_atomic(renesas_readl, val, !(val & RSTCTL_RI3CRST),
- 0, 1000, false, i3c->regs, RSTCTL);
+ return read_poll_timeout(renesas_readl, val, !(val & RSTCTL_RI3CRST),
+ 0, 1000, false, i3c->regs, RSTCTL);
}

static void renesas_i3c_hw_init(struct renesas_i3c *i3c)
@@ -1419,12 +1418,6 @@ static int renesas_i3c_probe(struct platform_device *pdev)
i3c->maxdevs = RENESAS_I3C_MAX_DEVS;
i3c->free_pos = GENMASK(i3c->maxdevs - 1, 0);

- /* Allocate dynamic Device Address Table backup. */
- i3c->DATBASn = devm_kzalloc(&pdev->dev, sizeof(u32) * i3c->maxdevs,
- GFP_KERNEL);
- if (!i3c->DATBASn)
- return -ENOMEM;
-
return i3c_master_register(&i3c->base, &pdev->dev, &renesas_i3c_ops, false);
}

@@ -1435,17 +1428,13 @@ static void renesas_i3c_remove(struct platform_device *pdev)
i3c_master_unregister(&i3c->base);
}

-static int renesas_i3c_suspend_noirq(struct device *dev)
+static int renesas_i3c_suspend(struct device *dev)
{
struct renesas_i3c *i3c = dev_get_drvdata(dev);
- int i, ret;
+ int ret;

i2c_mark_adapter_suspended(&i3c->base.i2c);

- /* Store Device Address Table values. */
- for (i = 0; i < i3c->maxdevs; i++)
- i3c->DATBASn[i] = renesas_readl(i3c->regs, DATBAS(i));
-
ret = reset_control_assert(i3c->presetn);
if (ret)
goto err_mark_resumed;
@@ -1466,10 +1455,10 @@ static int renesas_i3c_suspend_noirq(struct device *dev)
return ret;
}

-static int renesas_i3c_resume_noirq(struct device *dev)
+static int renesas_i3c_resume(struct device *dev)
{
struct renesas_i3c *i3c = dev_get_drvdata(dev);
- int i, ret;
+ int ret;

ret = reset_control_deassert(i3c->tresetn);
if (ret)
@@ -1495,15 +1484,19 @@ static int renesas_i3c_resume_noirq(struct device *dev)
renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYADV |
MSDVAD_MDYAD(i3c->dyn_addr));

- /* Restore Device Address Table values. */
- for (i = 0; i < i3c->maxdevs; i++)
- renesas_writel(i3c->regs, DATBAS(i), i3c->DATBASn[i]);
-
/* I3C hw init. */
renesas_i3c_hw_init(i3c);

+ ret = i3c_master_do_daa_ext(&i3c->base, true);
+ if (ret)
+ dev_err(dev, "DAA failed on resume, ret=%d", ret);
+
i2c_mark_adapter_resumed(&i3c->base.i2c);

+ /*
+ * I3C devices may have retained their dynamic address anyway. Do not
+ * fail the resume because of DAA error.
+ */
return 0;

err_clks_disable:
@@ -1516,8 +1509,7 @@ static int renesas_i3c_resume_noirq(struct device *dev)
}

static const struct dev_pm_ops renesas_i3c_pm_ops = {
- NOIRQ_SYSTEM_SLEEP_PM_OPS(renesas_i3c_suspend_noirq,
- renesas_i3c_resume_noirq)
+ SYSTEM_SLEEP_PM_OPS(renesas_i3c_suspend, renesas_i3c_resume)
};

static const struct of_device_id renesas_i3c_of_ids[] = {
--
2.43.0