[PATCH v2 17/17] i3c: renesas: Add runtime PM support

From: Claudiu Beznea

Date: Tue Jun 02 2026 - 09:33:02 EST


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

On the SoCs where the Renesas I3C driver is enabled (RZ/G3S and RZ/G3E),
the clocks of the IP are managed through a clock PM domain. To keep the
I3C code simpler, the explicit clock handling was dropped along with the
addition of runtime PM support, in favor of the runtime PM APIs. Only the
code for getting tclk was preserved, as it is necessary to compute the
I3C clock rate.

All the APIs provided to the I3C subsystem through struct
i3c_master_controller_ops are guarded with runtime PM APIs to
enable/disable the controller at runtime.

As the Renesas I3C driver implements an asynchronous transmit model by
preparing a transfer and waiting for its completion through the ISR,
renesas_i3c_abort_xfer() was added to disable interrupts and clear any
pending IRQ status bits when there is no completion in the defined
timeout. Along with this, renesas_i3c_wait_xfer() return type was changed
to unsigned long.

Add runtime PM support for the Renesas I3C driver.

Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@xxxxxxxxxxxxxx>
---

Changes in v2:
- dropped the runtime suspend/resume functions as for now, there will be
no pinctrl sleep state described in DT
- do not synchronize the IRQs in renesas_i3c_abort_xfer() as some handlers
may re-enable interrupts; instead just disable the interrupts and clean
any status bits that the IRQ handlers are touching; with this the
struct renesas_i3c::{irqs, num_irqs} and the associated code was removed
- dropped the renesas_i3c_dont_use_autosuspend() along with the
devm_add_action_or_reset() call to set it since the same operation is
done by the devres cleanup helper of devm_pm_runtime_enable()
- adjusted the renesas_i3c_suspend() with RPM calls to save the DATBAS
registers
- use pm_runtime_resume_and_get() in renesas_i3c_resume() to avoid
mixing gotos with cleanup helpers
- adjusted the patch description to reflect these changes

drivers/i3c/master/renesas-i3c.c | 158 +++++++++++++++++++++++++------
1 file changed, 130 insertions(+), 28 deletions(-)

diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c
index 2a9bb035b8a2..6684a0e75bb9 100644
--- a/drivers/i3c/master/renesas-i3c.c
+++ b/drivers/i3c/master/renesas-i3c.c
@@ -22,6 +22,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include "../internals.h"
@@ -199,8 +200,6 @@
#define RENESAS_I3C_MAX_DEVS 8
#define I2C_INIT_MSG -1

-#define RENESAS_I3C_TCLK_IDX 1
-
enum i3c_internal_state {
I3C_INTERNAL_STATE_DISABLED,
I3C_INTERNAL_STATE_CONTROLLER_IDLE,
@@ -259,9 +258,10 @@ struct renesas_i3c_addr {

struct renesas_i3c {
void __iomem *regs;
- struct clk_bulk_data *clks;
+ struct clk *tclk;
struct reset_control *presetn;
struct reset_control *tresetn;
+ struct device *dev;
u32 *DATBASn;
struct renesas_i3c_xferqueue xferqueue;
struct i3c_master_controller base;
@@ -275,7 +275,6 @@ struct renesas_i3c {
u32 i3c_STDBR;
u32 extbr;
u16 maxdevs;
- u8 num_clks;
u8 refclk_div;
};

@@ -440,7 +439,24 @@ static void renesas_i3c_enqueue_xfer(struct renesas_i3c *i3c, struct renesas_i3c
}
}

-static void renesas_i3c_wait_xfer(struct renesas_i3c *i3c, struct renesas_i3c_xfer *xfer)
+static void renesas_i3c_abort_xfer(struct renesas_i3c *i3c)
+{
+ guard(spinlock_irqsave)(&i3c->xferqueue.lock);
+
+ /* Disable all the interrupts */
+ renesas_writel(i3c->regs, BIE, 0);
+ renesas_writel(i3c->regs, NTIE, 0);
+
+ /* Clear normal transfer status flags. */
+ renesas_clear_bit(i3c->regs, NTST, NTST_TDBEF0 | NTST_RDBFF0 | NTST_RSPQFF |
+ NTST_TEF | NTST_TABTF);
+ /* Clear bus status flags. */
+ renesas_clear_bit(i3c->regs, BST, BST_NACKDF | BST_TENDF | BST_SPCNDDF);
+ /* Clear error flags. */
+ renesas_clear_bit(i3c->regs, BCTL, BCTL_ABT);
+}
+
+static unsigned long renesas_i3c_wait_xfer(struct renesas_i3c *i3c, struct renesas_i3c_xfer *xfer)
{
unsigned long time_left;

@@ -449,6 +465,8 @@ static void renesas_i3c_wait_xfer(struct renesas_i3c *i3c, struct renesas_i3c_xf
time_left = wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000));
if (!time_left)
renesas_i3c_dequeue_xfer(i3c, xfer);
+
+ return time_left;
}

static void renesas_i3c_set_prts(struct renesas_i3c *i3c, u32 val)
@@ -482,6 +500,12 @@ static void renesas_i3c_bus_enable(struct i3c_master_controller *m, bool i3c_mod
static int renesas_i3c_reset(struct renesas_i3c *i3c)
{
u32 val;
+ int ret;
+
+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;

renesas_writel(i3c->regs, BCTL, 0);
renesas_set_bit(i3c->regs, RSTCTL, RSTCTL_RI3CRST);
@@ -553,7 +577,7 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m)
int od_high_ticks, od_low_ticks, i2c_total_ticks;
int ret;

- i3c->rate = clk_get_rate(i3c->clks[RENESAS_I3C_TCLK_IDX].clk);
+ i3c->rate = clk_get_rate(i3c->tclk);
if (!i3c->rate)
return -EINVAL;

@@ -624,6 +648,11 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m)
if (ret)
return ret;

+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR);
renesas_writel(i3c->regs, EXTBR, i3c->extbr);
renesas_writel(i3c->regs, REFCKCTL, REFCKCTL_IREFCKS(cks));
@@ -646,6 +675,7 @@ static int renesas_i3c_daa(struct i3c_master_controller *m)
{
struct renesas_i3c *i3c = to_renesas_i3c(m);
struct renesas_i3c_cmd *cmd;
+ unsigned long time_left;
u8 last_addr = 0, pos;
int last_i2c_pos = -1;
u32 olddevs, newdevs;
@@ -659,6 +689,11 @@ static int renesas_i3c_daa(struct i3c_master_controller *m)
cmd = xfer->cmds;
cmd->rx_count = 0;

+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
/* Enable I3C bus. */
renesas_i3c_bus_enable(m, true);

@@ -724,7 +759,9 @@ static int renesas_i3c_daa(struct i3c_master_controller *m)
NCMDQP_CMD(I3C_CCC_ENTDAA) | NCMDQP_DEV_INDEX(ret) |
NCMDQP_DEV_COUNT(i3c->maxdevs - ret) | NCMDQP_TOC;

- renesas_i3c_wait_xfer(i3c, xfer);
+ time_left = renesas_i3c_wait_xfer(i3c, xfer);
+ if (!time_left)
+ renesas_i3c_abort_xfer(i3c);

/* Skip attaching if there are failures on the xfer. */
if (xfer->ret) {
@@ -791,6 +828,7 @@ static int renesas_i3c_send_ccc_cmd(struct i3c_master_controller *m,
{
struct renesas_i3c *i3c = to_renesas_i3c(m);
struct renesas_i3c_cmd *cmd;
+ unsigned long time_left;
int ret, pos = 0;

if (ccc->id & I3C_CCC_DIRECT) {
@@ -808,6 +846,11 @@ static int renesas_i3c_send_ccc_cmd(struct i3c_master_controller *m,
cmd->rnw = ccc->rnw;
cmd->cmd0 = 0;

+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
renesas_i3c_bus_enable(m, true);

/* Calculate the command descriptor. */
@@ -842,7 +885,9 @@ static int renesas_i3c_send_ccc_cmd(struct i3c_master_controller *m,
}
}

- renesas_i3c_wait_xfer(i3c, xfer);
+ time_left = renesas_i3c_wait_xfer(i3c, xfer);
+ if (!time_left)
+ renesas_i3c_abort_xfer(i3c);

ret = xfer->ret;
if (ret)
@@ -857,7 +902,9 @@ static int renesas_i3c_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *i3c_
struct i3c_master_controller *m = i3c_dev_get_master(dev);
struct renesas_i3c *i3c = to_renesas_i3c(m);
struct renesas_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
- int i;
+ unsigned long time_left;
+ bool abort_xfer = false;
+ int i, ret;

struct renesas_i3c_xfer *xfer __free(kfree) = renesas_i3c_alloc_xfer(i3c, 1);
if (!xfer)
@@ -865,6 +912,11 @@ static int renesas_i3c_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *i3c_

init_completion(&xfer->comp);

+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
/* Enable I3C bus. */
renesas_i3c_bus_enable(m, true);

@@ -896,9 +948,14 @@ static int renesas_i3c_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *i3c_
renesas_set_bit(i3c->regs, NTIE, NTIE_TDBEIE0);
}

- renesas_i3c_wait_xfer(i3c, xfer);
+ time_left = renesas_i3c_wait_xfer(i3c, xfer);
+ if (!time_left)
+ abort_xfer = true;
}

+ if (abort_xfer)
+ renesas_i3c_abort_xfer(i3c);
+
return 0;
}

@@ -907,12 +964,17 @@ static int renesas_i3c_attach_i3c_dev(struct i3c_dev_desc *dev)
struct i3c_master_controller *m = i3c_dev_get_master(dev);
struct renesas_i3c *i3c = to_renesas_i3c(m);
struct renesas_i3c_i2c_dev_data *data;
- int pos;
+ int pos, ret;

pos = renesas_i3c_get_free_pos(i3c);
if (pos < 0)
return pos;

+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
data = kzalloc_obj(*data);
if (!data)
return -ENOMEM;
@@ -934,7 +996,12 @@ static int renesas_i3c_reattach_i3c_dev(struct i3c_dev_desc *dev,
struct i3c_master_controller *m = i3c_dev_get_master(dev);
struct renesas_i3c *i3c = to_renesas_i3c(m);
struct renesas_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
- int pos;
+ int pos, ret;
+
+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;

pos = renesas_i3c_get_free_pos(i3c);

@@ -962,8 +1029,12 @@ static void renesas_i3c_detach_i3c_dev(struct i3c_dev_desc *dev)
struct renesas_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
struct i3c_master_controller *m = i3c_dev_get_master(dev);
struct renesas_i3c *i3c = to_renesas_i3c(m);
+ int ret;

- renesas_writel(i3c->regs, DATBAS(data->index), 0);
+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (!ret)
+ renesas_writel(i3c->regs, DATBAS(data->index), 0);

i3c_dev_set_master_data(dev, NULL);
i3c->addrs[data->index].addr = 0;
@@ -979,7 +1050,9 @@ static int renesas_i3c_i2c_xfers(struct i2c_dev_desc *dev,
struct renesas_i3c *i3c = to_renesas_i3c(m);
struct renesas_i3c_cmd *cmd;
u8 start_bit = CNDCTL_STCND;
- int i;
+ unsigned long time_left;
+ bool abort_xfer = false;
+ int i, ret;

if (!i2c_nxfers)
return 0;
@@ -992,6 +1065,11 @@ static int renesas_i3c_i2c_xfers(struct i2c_dev_desc *dev,
xfer->is_i2c_xfer = true;
cmd = xfer->cmds;

+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
renesas_i3c_bus_enable(m, false);

if (!(renesas_readl(i3c->regs, BCST) & BCST_BFREF)) {
@@ -1018,7 +1096,9 @@ static int renesas_i3c_i2c_xfers(struct i2c_dev_desc *dev,

renesas_set_bit(i3c->regs, NTSTE, NTSTE_TDBEE0);

- wait_for_completion_timeout(&xfer->comp, m->i2c.timeout);
+ time_left = wait_for_completion_timeout(&xfer->comp, m->i2c.timeout);
+ if (!time_left)
+ abort_xfer = true;

if (cmd->err)
break;
@@ -1027,6 +1107,10 @@ static int renesas_i3c_i2c_xfers(struct i2c_dev_desc *dev,
}

renesas_i3c_dequeue_xfer(i3c, xfer);
+
+ if (abort_xfer)
+ renesas_i3c_abort_xfer(i3c);
+
return cmd->err;
}

@@ -1425,12 +1509,16 @@ static int renesas_i3c_probe(struct platform_device *pdev)
if (IS_ERR(i3c->regs))
return PTR_ERR(i3c->regs);

- ret = devm_clk_bulk_get_all_enabled(&pdev->dev, &i3c->clks);
- if (ret <= RENESAS_I3C_TCLK_IDX)
- return dev_err_probe(&pdev->dev, ret < 0 ? ret : -EINVAL,
- "Failed to get clocks (need > %d, got %d)\n",
- RENESAS_I3C_TCLK_IDX, ret);
- i3c->num_clks = ret;
+ i3c->tclk = devm_clk_get(&pdev->dev, "tclk");
+ if (IS_ERR(i3c->tclk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(i3c->tclk), "Failed to get tclk");
+
+ i3c->dev = &pdev->dev;
+ pm_runtime_set_autosuspend_delay(&pdev->dev, 300);
+ pm_runtime_use_autosuspend(&pdev->dev);
+ ret = devm_pm_runtime_enable(&pdev->dev);
+ if (ret)
+ return ret;

i3c->tresetn = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "tresetn");
if (IS_ERR(i3c->tresetn))
@@ -1499,16 +1587,22 @@ static int renesas_i3c_suspend(struct device *dev)

i2c_mark_adapter_suspended(&i3c->base.i2c);

+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ goto err_mark_resumed;
+
/* Store Device Address Table values. */
for (i = 0; i < i3c->maxdevs; i++)
i3c->DATBASn[i] = renesas_readl(i3c->regs, DATBAS(i));

+ ret = pm_runtime_put_sync(dev);
+ if (ret < 0)
+ goto err_mark_resumed;
+
ret = reset_control_bulk_assert(ARRAY_SIZE(resets), resets);
if (ret)
goto err_mark_resumed;

- clk_bulk_disable(i3c->num_clks, i3c->clks);
-
return 0;

err_mark_resumed:
@@ -1530,13 +1624,13 @@ static int renesas_i3c_resume(struct device *dev)
if (ret)
return ret;

- ret = clk_bulk_enable(i3c->num_clks, i3c->clks);
+ ret = renesas_i3c_reset(i3c);
if (ret)
goto err_resets_asserted;

- ret = renesas_i3c_reset(i3c);
+ ret = pm_runtime_resume_and_get(dev);
if (ret)
- goto err_clks_disable;
+ goto err_resets_asserted;

/* Re-store I3C registers value. */
renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR);
@@ -1559,15 +1653,23 @@ static int renesas_i3c_resume(struct device *dev)

i3c->resuming = false;

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

-err_clks_disable:
- clk_bulk_disable(i3c->num_clks, i3c->clks);
err_resets_asserted:
+ /*
+ * If this happens, there is no way to recover from this state without
+ * reloading the driver. We want to avoid keeping the reset line
+ * deasserted unnecessarily. The runtime paths will still work correctly
+ * even if the IP registers are accessed while reset is asserted (e.g.
+ * if a runtime path is triggered after a failed resume). Checked on
+ * RZ/G3S.
+ */
reset_control_bulk_assert(ARRAY_SIZE(resets), resets);
return ret;
}
--
2.43.0