From: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
To: Frank Li <Frank.li@nxp.com>
Cc: tomm.merciai@gmail.com, linux-renesas-soc@vger.kernel.org,
biju.das.jz@bp.renesas.com, wsa+renesas@sang-engineering.com,
Alexandre Belloni <alexandre.belloni@bootlin.com>,
Philipp Zabel <p.zabel@pengutronix.de>,
linux-i3c@lists.infradead.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH] i3c: renesas: Add suspend/resume support
Date: Tue, 30 Dec 2025 09:19:55 +0100 [thread overview]
Message-ID: <aVOLK9AU4Zma69VF@tom-desktop> (raw)
In-Reply-To: <aVKjbYTLcptpERZW@lizhi-Precision-Tower-5810>
Hi Frank,
Thanks for your review!
On Mon, Dec 29, 2025 at 10:51:09AM -0500, Frank Li wrote:
> On Tue, Dec 23, 2025 at 05:54:08PM +0100, Tommaso Merciai wrote:
> > The Renesas I3C controller does not retain its register state across system
> > suspend, requiring the driver to explicitly save and restore hardware
> > configuration.
> >
> > Add suspend and resume NOIRQ callbacks to handle system sleep transitions.
> >
> > During suspend, save the Device Address Table (DAT) entries, assert reset
> > lines, and disable all related clocks to allow the controller to enter a
> > low-power state.
> >
> > On resume, re-enable clocks and reset lines in the proper order. Restore
> > the REFCKCTL register, master dynamic address, and all DAT entries, then
> > reinitialize the controller.
> >
> > Factor out the common hardware initialization sequence into a dedicated
> > helper to avoid code duplication between bus initialization and resume.
> >
> > Store the target clock rate, reset and clock handles, the REFCLK divider
> > value, and the master dynamic address to restore timing and addressing
> > configuration after resume.
> >
> > Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> > ---
> > drivers/i3c/master/renesas-i3c.c | 246 ++++++++++++++++++++++---------
> > 1 file changed, 179 insertions(+), 67 deletions(-)
> >
> > diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c
> > index 426a418f29b6..b980e2a84fcd 100644
> > --- a/drivers/i3c/master/renesas-i3c.c
> > +++ b/drivers/i3c/master/renesas-i3c.c
> > @@ -254,12 +254,20 @@ struct renesas_i3c {
> > enum i3c_internal_state internal_state;
> > u16 maxdevs;
> > u32 free_pos;
> > + u32 dyn_addr;
> > u32 i2c_STDBR;
> > u32 i3c_STDBR;
> > + unsigned long rate;
> > u8 addrs[RENESAS_I3C_MAX_DEVS];
> > struct renesas_i3c_xferqueue xferqueue;
> > void __iomem *regs;
> > + u32 *DATBASn;
> > struct clk *tclk;
> > + struct clk *pclk;
> > + struct clk *pclkrw;
> > + struct reset_control *presetn;
> > + struct reset_control *tresetn;
> > + u8 refclk_div;
> > };
> >
> > struct renesas_i3c_i2c_dev_data {
> > @@ -477,28 +485,79 @@ static int renesas_i3c_reset(struct renesas_i3c *i3c)
> > 0, 1000, false, i3c->regs, RSTCTL);
> > }
> >
> > +static void renesas_i3c_hw_init(struct renesas_i3c *i3c)
> > +{
>
> Can you split it to new patch to add helper renesas_i3c_hw_init() only.
Will do this in v2.
>
> > + u32 val;
> > +
> > + /* Disable Slave Mode */
> > + renesas_writel(i3c->regs, SVCTL, 0);
> > +
> > + /* Initialize Queue/Buffer threshold */
> > + renesas_writel(i3c->regs, NQTHCTL, NQTHCTL_IBIDSSZ(6) |
> > + NQTHCTL_CMDQTH(1));
> > +
> > + /* The only supported configuration is two entries*/
> > + renesas_writel(i3c->regs, NTBTHCTL0, 0);
> > + /* Interrupt when there is one entry in the queue */
> > + renesas_writel(i3c->regs, NRQTHCTL, 0);
> > +
> > + /* Enable all Bus/Transfer Status Flags */
> > + renesas_writel(i3c->regs, BSTE, BSTE_ALL_FLAG);
> > + renesas_writel(i3c->regs, NTSTE, NTSTE_ALL_FLAG);
> > +
> > + /* Interrupt enable settings */
> > + renesas_writel(i3c->regs, BIE, BIE_NACKDIE | BIE_TENDIE);
> > + renesas_writel(i3c->regs, NTIE, 0);
> > +
> > + /* Clear Status register */
> > + renesas_writel(i3c->regs, NTST, 0);
> > + renesas_writel(i3c->regs, INST, 0);
> > + renesas_writel(i3c->regs, BST, 0);
> > +
> > + /* Hot-Join Acknowlege setting. */
> > + renesas_set_bit(i3c->regs, BCTL, BCTL_HJACKCTL);
> > +
> > + renesas_writel(i3c->regs, IBINCTL, IBINCTL_NRHJCTL | IBINCTL_NRMRCTL |
> > + IBINCTL_NRSIRCTL);
> > +
> > + renesas_writel(i3c->regs, SCSTLCTL, 0);
> > + renesas_set_bit(i3c->regs, SCSTRCTL, SCSTRCTL_ACKTWE);
> > +
> > + /* Bus condition timing */
> > + val = DIV_ROUND_UP(I3C_BUS_TBUF_MIXED_FM_MIN_NS,
> > + NSEC_PER_SEC / i3c->rate);
> > + renesas_writel(i3c->regs, BFRECDT, BFRECDT_FRECYC(val));
> > +
> > + val = DIV_ROUND_UP(I3C_BUS_TAVAL_MIN_NS,
> > + NSEC_PER_SEC / i3c->rate);
> > + renesas_writel(i3c->regs, BAVLCDT, BAVLCDT_AVLCYC(val));
> > +
> > + val = DIV_ROUND_UP(I3C_BUS_TIDLE_MIN_NS,
> > + NSEC_PER_SEC / i3c->rate);
> > + renesas_writel(i3c->regs, BIDLCDT, BIDLCDT_IDLCYC(val));
> > +}
> > +
> > static int renesas_i3c_bus_init(struct i3c_master_controller *m)
> > {
> > struct renesas_i3c *i3c = to_renesas_i3c(m);
> > struct i3c_bus *bus = i3c_master_get_bus(m);
> > struct i3c_device_info info = {};
> > struct i2c_timings t;
> > - unsigned long rate;
> > - u32 double_SBR, val;
> > + u32 double_SBR;
> > int cks, pp_high_ticks, pp_low_ticks, i3c_total_ticks;
> > int od_high_ticks, od_low_ticks, i2c_total_ticks;
> > int ret;
> >
> > - rate = clk_get_rate(i3c->tclk);
> > - if (!rate)
> > + i3c->rate = clk_get_rate(i3c->tclk);
> > + if (!i3c->rate)
> > return -EINVAL;
> >
> > ret = renesas_i3c_reset(i3c);
> > if (ret)
> > return ret;
> >
> > - i2c_total_ticks = DIV_ROUND_UP(rate, bus->scl_rate.i2c);
> > - i3c_total_ticks = DIV_ROUND_UP(rate, bus->scl_rate.i3c);
> > + i2c_total_ticks = DIV_ROUND_UP(i3c->rate, bus->scl_rate.i2c);
> > + i3c_total_ticks = DIV_ROUND_UP(i3c->rate, bus->scl_rate.i3c);
> >
> > i2c_parse_fw_timings(&m->dev, &t, true);
> >
> > @@ -511,7 +570,7 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m)
> > pp_high_ticks = ((i3c_total_ticks * 5) / 10);
> > else
> > pp_high_ticks = DIV_ROUND_UP(I3C_BUS_THIGH_MIXED_MAX_NS,
> > - NSEC_PER_SEC / rate);
> > + NSEC_PER_SEC / i3c->rate);
> > pp_low_ticks = i3c_total_ticks - pp_high_ticks;
> >
> > if ((od_low_ticks / 2) <= 0xFF && pp_low_ticks < 0x3F)
> > @@ -519,7 +578,7 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m)
> >
> > i2c_total_ticks /= 2;
> > i3c_total_ticks /= 2;
> > - rate /= 2;
> > + i3c->rate /= 2;
> > }
> >
> > /* SCL clock period calculation in Open-drain mode */
> > @@ -540,8 +599,8 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m)
> > STDBR_SBRLP(pp_low_ticks) |
> > STDBR_SBRHP(pp_high_ticks);
> >
> > - od_low_ticks -= t.scl_fall_ns / (NSEC_PER_SEC / rate) + 1;
> > - od_high_ticks -= t.scl_rise_ns / (NSEC_PER_SEC / rate) + 1;
> > + od_low_ticks -= t.scl_fall_ns / (NSEC_PER_SEC / i3c->rate) + 1;
> > + od_high_ticks -= t.scl_rise_ns / (NSEC_PER_SEC / i3c->rate) + 1;
> > i3c->i2c_STDBR = (double_SBR ? STDBR_DSBRPO : 0) |
> > STDBR_SBRLO(double_SBR, od_low_ticks) |
> > STDBR_SBRHO(double_SBR, od_high_ticks) |
> > @@ -556,55 +615,16 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m)
> > EXTBR_EBRHP(pp_high_ticks));
> >
> > renesas_writel(i3c->regs, REFCKCTL, REFCKCTL_IREFCKS(cks));
> > + i3c->refclk_div = cks;
> >
> > - /* Disable Slave Mode */
> > - renesas_writel(i3c->regs, SVCTL, 0);
> > -
> > - /* Initialize Queue/Buffer threshold */
> > - renesas_writel(i3c->regs, NQTHCTL, NQTHCTL_IBIDSSZ(6) |
> > - NQTHCTL_CMDQTH(1));
> > -
> > - /* The only supported configuration is two entries*/
> > - renesas_writel(i3c->regs, NTBTHCTL0, 0);
> > - /* Interrupt when there is one entry in the queue */
> > - renesas_writel(i3c->regs, NRQTHCTL, 0);
> > -
> > - /* Enable all Bus/Transfer Status Flags */
> > - renesas_writel(i3c->regs, BSTE, BSTE_ALL_FLAG);
> > - renesas_writel(i3c->regs, NTSTE, NTSTE_ALL_FLAG);
> > -
> > - /* Interrupt enable settings */
> > - renesas_writel(i3c->regs, BIE, BIE_NACKDIE | BIE_TENDIE);
> > - renesas_writel(i3c->regs, NTIE, 0);
> > -
> > - /* Clear Status register */
> > - renesas_writel(i3c->regs, NTST, 0);
> > - renesas_writel(i3c->regs, INST, 0);
> > - renesas_writel(i3c->regs, BST, 0);
> > -
> > - /* Hot-Join Acknowlege setting. */
> > - renesas_set_bit(i3c->regs, BCTL, BCTL_HJACKCTL);
> > -
> > - renesas_writel(i3c->regs, IBINCTL, IBINCTL_NRHJCTL | IBINCTL_NRMRCTL |
> > - IBINCTL_NRSIRCTL);
> > -
> > - renesas_writel(i3c->regs, SCSTLCTL, 0);
> > - renesas_set_bit(i3c->regs, SCSTRCTL, SCSTRCTL_ACKTWE);
> > -
> > - /* Bus condition timing */
> > - val = DIV_ROUND_UP(I3C_BUS_TBUF_MIXED_FM_MIN_NS, NSEC_PER_SEC / rate);
> > - renesas_writel(i3c->regs, BFRECDT, BFRECDT_FRECYC(val));
> > -
> > - val = DIV_ROUND_UP(I3C_BUS_TAVAL_MIN_NS, NSEC_PER_SEC / rate);
> > - renesas_writel(i3c->regs, BAVLCDT, BAVLCDT_AVLCYC(val));
> > -
> > - val = DIV_ROUND_UP(I3C_BUS_TIDLE_MIN_NS, NSEC_PER_SEC / rate);
> > - renesas_writel(i3c->regs, BIDLCDT, BIDLCDT_IDLCYC(val));
> > + /* I3C hw init*/
> > + renesas_i3c_hw_init(i3c);
> >
> > ret = i3c_master_get_free_addr(m, 0);
> > if (ret < 0)
> > return ret;
> >
> > + i3c->dyn_addr = ret;
> > renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYAD(ret) | MSDVAD_MDYADV);
> >
> > memset(&info, 0, sizeof(info));
> > @@ -1301,8 +1321,6 @@ static const struct renesas_i3c_irq_desc renesas_i3c_irqs[] = {
> > static int renesas_i3c_probe(struct platform_device *pdev)
> > {
> > struct renesas_i3c *i3c;
> > - struct reset_control *reset;
> > - struct clk *clk;
> > const struct renesas_i3c_config *config = of_device_get_match_data(&pdev->dev);
> > int ret, i;
> >
> > @@ -1317,28 +1335,28 @@ static int renesas_i3c_probe(struct platform_device *pdev)
> > if (IS_ERR(i3c->regs))
> > return PTR_ERR(i3c->regs);
> >
> > - clk = devm_clk_get_enabled(&pdev->dev, "pclk");
> > - if (IS_ERR(clk))
> > - return PTR_ERR(clk);
> > + i3c->pclk = devm_clk_get_enabled(&pdev->dev, "pclk");
> > + if (IS_ERR(i3c->pclk))
> > + return PTR_ERR(i3c->pclk);
> >
> > if (config->has_pclkrw) {
> > - clk = devm_clk_get_enabled(&pdev->dev, "pclkrw");
> > - if (IS_ERR(clk))
> > - return PTR_ERR(clk);
> > + i3c->pclkrw = devm_clk_get_enabled(&pdev->dev, "pclkrw");
> > + if (IS_ERR(i3c->pclkrw))
> > + return PTR_ERR(i3c->pclkrw);
> > }
> >
> > i3c->tclk = devm_clk_get_enabled(&pdev->dev, "tclk");
> > if (IS_ERR(i3c->tclk))
> > return PTR_ERR(i3c->tclk);
> >
> > - reset = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "tresetn");
> > - if (IS_ERR(reset))
> > - return dev_err_probe(&pdev->dev, PTR_ERR(reset),
> > + i3c->tresetn = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "tresetn");
> > + if (IS_ERR(i3c->tresetn))
> > + return dev_err_probe(&pdev->dev, PTR_ERR(i3c->tresetn),
> > "Error: missing tresetn ctrl\n");
> >
> > - reset = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "presetn");
> > - if (IS_ERR(reset))
> > - return dev_err_probe(&pdev->dev, PTR_ERR(reset),
> > + i3c->presetn = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "presetn");
> > + if (IS_ERR(i3c->presetn))
> > + return dev_err_probe(&pdev->dev, PTR_ERR(i3c->presetn),
> > "Error: missing presetn ctrl\n");
>
> And another patch to save clk, rate and reset to into struct renesas_i3c.
I will do this in v2.
>
> >
> > spin_lock_init(&i3c->xferqueue.lock);
> > @@ -1364,6 +1382,13 @@ 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);
> > }
> >
> > @@ -1374,6 +1399,92 @@ static void renesas_i3c_remove(struct platform_device *pdev)
> > i3c_master_unregister(&i3c->base);
> > }
> >
> > +static int renesas_i3c_suspend_noirq(struct device *dev)
> > +{
> > + struct renesas_i3c *i3c = dev_get_drvdata(dev);
> > + int i, 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)
> > + return ret;
> > +
> > + ret = reset_control_assert(i3c->tresetn);
> > + if (ret) {
> > + reset_control_deassert(i3c->presetn);
> > + return ret;
> > + }
> > +
> > + clk_disable_unprepare(i3c->pclk);
> > + clk_disable_unprepare(i3c->tclk);
> > + clk_disable_unprepare(i3c->pclkrw);
> > +
> > + return 0;
> > +}
> > +
> > +static int renesas_i3c_resume_noirq(struct device *dev)
> > +{
> > + struct renesas_i3c *i3c = dev_get_drvdata(dev);
> > + int i, ret;
> > +
> > + ret = reset_control_deassert(i3c->presetn);
> > + if (ret)
> > + return ret;
> > +
> > + ret = reset_control_deassert(i3c->tresetn);
> > + if (ret)
> > + goto err_presetn;
> > +
> > + ret = clk_prepare_enable(i3c->pclkrw);
> > + if (ret)
> > + goto err_tresetn;
> > +
> > + ret = clk_prepare_enable(i3c->pclk);
> > + if (ret)
> > + goto err_pclkrw;
> > +
> > + ret = clk_prepare_enable(i3c->tclk);
> > + if (ret)
> > + goto err_pclk;
>
> Can you use clk bulk API? Just for reference, clk prepare have mutex lock,
> which may cause dead lock, such as if use gpio expendor to control external
> clock gate.
>
> https://lists.openwall.net/linux-kernel/2025/07/01/139
> https://lore.kernel.org/imx/87pldsd1tq.fsf@bootlin.com/#t
Thanks for sharing.
My plan is to add then
struct clk_bulk_data clks[3];
u8 num_clks;
and
static const char * const renesas_i3c_clks[] = {
"pclk", "tclk", "pclkrw"
};
Then into the renesas_i3c_probe() we can use:
i3c->num_clks = config->has_pclkrw ? 3 : 2;
for (i = 0; i < i3c->num_clks; i++)
i3c->clks[i].id = renesas_i3c_clks[i];
ret = devm_clk_bulk_get(&pdev->dev, i3c->num_clks, i3c->clks);
if (ret)
return ret;
ret = clk_bulk_prepare_enable(i3c->num_clks, i3c->clks);
if (ret)
return ret;
And update then renesas_i3c_resume_noirq()/renesas_i3c_suspend_noirq()
with:
clk_bulk_prepare_enable(i3c->num_clks, i3c->clks);
clk_bulk_disable_unprepare(i3c->num_clks, i3c->clks)
Thanks & Regards,
Tommaso
>
> Frank
>
> > +
> > + /* Re-store I3C registers value. */
> > + renesas_writel(i3c->regs, REFCKCTL,
> > + REFCKCTL_IREFCKS(i3c->refclk_div));
> > + 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);
> > +
> > + i2c_mark_adapter_resumed(&i3c->base.i2c);
> > +
> > + return 0;
> > +
> > +err_pclk:
> > + clk_disable_unprepare(i3c->pclk);
> > +err_pclkrw:
> > + clk_disable_unprepare(i3c->pclkrw);
> > +err_tresetn:
> > + reset_control_assert(i3c->tresetn);
> > +err_presetn:
> > + reset_control_assert(i3c->presetn);
> > + return ret;
> > +}
> > +
> > +static const struct dev_pm_ops renesas_i3c_pm_ops = {
> > + NOIRQ_SYSTEM_SLEEP_PM_OPS(renesas_i3c_suspend_noirq,
> > + renesas_i3c_resume_noirq)
> > +};
> > +
> > static const struct renesas_i3c_config empty_i3c_config = {
> > };
> >
> > @@ -1394,6 +1505,7 @@ static struct platform_driver renesas_i3c = {
> > .driver = {
> > .name = "renesas-i3c",
> > .of_match_table = renesas_i3c_of_ids,
> > + .pm = pm_sleep_ptr(&renesas_i3c_pm_ops),
> > },
> > };
> > module_platform_driver(renesas_i3c);
> > --
> > 2.43.0
> >
--
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c
WARNING: multiple messages have this Message-ID (diff)
From: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
To: Frank Li <Frank.li@nxp.com>
Cc: tomm.merciai@gmail.com, linux-renesas-soc@vger.kernel.org,
biju.das.jz@bp.renesas.com, wsa+renesas@sang-engineering.com,
Alexandre Belloni <alexandre.belloni@bootlin.com>,
Philipp Zabel <p.zabel@pengutronix.de>,
linux-i3c@lists.infradead.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH] i3c: renesas: Add suspend/resume support
Date: Tue, 30 Dec 2025 09:19:55 +0100 [thread overview]
Message-ID: <aVOLK9AU4Zma69VF@tom-desktop> (raw)
In-Reply-To: <aVKjbYTLcptpERZW@lizhi-Precision-Tower-5810>
Hi Frank,
Thanks for your review!
On Mon, Dec 29, 2025 at 10:51:09AM -0500, Frank Li wrote:
> On Tue, Dec 23, 2025 at 05:54:08PM +0100, Tommaso Merciai wrote:
> > The Renesas I3C controller does not retain its register state across system
> > suspend, requiring the driver to explicitly save and restore hardware
> > configuration.
> >
> > Add suspend and resume NOIRQ callbacks to handle system sleep transitions.
> >
> > During suspend, save the Device Address Table (DAT) entries, assert reset
> > lines, and disable all related clocks to allow the controller to enter a
> > low-power state.
> >
> > On resume, re-enable clocks and reset lines in the proper order. Restore
> > the REFCKCTL register, master dynamic address, and all DAT entries, then
> > reinitialize the controller.
> >
> > Factor out the common hardware initialization sequence into a dedicated
> > helper to avoid code duplication between bus initialization and resume.
> >
> > Store the target clock rate, reset and clock handles, the REFCLK divider
> > value, and the master dynamic address to restore timing and addressing
> > configuration after resume.
> >
> > Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> > ---
> > drivers/i3c/master/renesas-i3c.c | 246 ++++++++++++++++++++++---------
> > 1 file changed, 179 insertions(+), 67 deletions(-)
> >
> > diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c
> > index 426a418f29b6..b980e2a84fcd 100644
> > --- a/drivers/i3c/master/renesas-i3c.c
> > +++ b/drivers/i3c/master/renesas-i3c.c
> > @@ -254,12 +254,20 @@ struct renesas_i3c {
> > enum i3c_internal_state internal_state;
> > u16 maxdevs;
> > u32 free_pos;
> > + u32 dyn_addr;
> > u32 i2c_STDBR;
> > u32 i3c_STDBR;
> > + unsigned long rate;
> > u8 addrs[RENESAS_I3C_MAX_DEVS];
> > struct renesas_i3c_xferqueue xferqueue;
> > void __iomem *regs;
> > + u32 *DATBASn;
> > struct clk *tclk;
> > + struct clk *pclk;
> > + struct clk *pclkrw;
> > + struct reset_control *presetn;
> > + struct reset_control *tresetn;
> > + u8 refclk_div;
> > };
> >
> > struct renesas_i3c_i2c_dev_data {
> > @@ -477,28 +485,79 @@ static int renesas_i3c_reset(struct renesas_i3c *i3c)
> > 0, 1000, false, i3c->regs, RSTCTL);
> > }
> >
> > +static void renesas_i3c_hw_init(struct renesas_i3c *i3c)
> > +{
>
> Can you split it to new patch to add helper renesas_i3c_hw_init() only.
Will do this in v2.
>
> > + u32 val;
> > +
> > + /* Disable Slave Mode */
> > + renesas_writel(i3c->regs, SVCTL, 0);
> > +
> > + /* Initialize Queue/Buffer threshold */
> > + renesas_writel(i3c->regs, NQTHCTL, NQTHCTL_IBIDSSZ(6) |
> > + NQTHCTL_CMDQTH(1));
> > +
> > + /* The only supported configuration is two entries*/
> > + renesas_writel(i3c->regs, NTBTHCTL0, 0);
> > + /* Interrupt when there is one entry in the queue */
> > + renesas_writel(i3c->regs, NRQTHCTL, 0);
> > +
> > + /* Enable all Bus/Transfer Status Flags */
> > + renesas_writel(i3c->regs, BSTE, BSTE_ALL_FLAG);
> > + renesas_writel(i3c->regs, NTSTE, NTSTE_ALL_FLAG);
> > +
> > + /* Interrupt enable settings */
> > + renesas_writel(i3c->regs, BIE, BIE_NACKDIE | BIE_TENDIE);
> > + renesas_writel(i3c->regs, NTIE, 0);
> > +
> > + /* Clear Status register */
> > + renesas_writel(i3c->regs, NTST, 0);
> > + renesas_writel(i3c->regs, INST, 0);
> > + renesas_writel(i3c->regs, BST, 0);
> > +
> > + /* Hot-Join Acknowlege setting. */
> > + renesas_set_bit(i3c->regs, BCTL, BCTL_HJACKCTL);
> > +
> > + renesas_writel(i3c->regs, IBINCTL, IBINCTL_NRHJCTL | IBINCTL_NRMRCTL |
> > + IBINCTL_NRSIRCTL);
> > +
> > + renesas_writel(i3c->regs, SCSTLCTL, 0);
> > + renesas_set_bit(i3c->regs, SCSTRCTL, SCSTRCTL_ACKTWE);
> > +
> > + /* Bus condition timing */
> > + val = DIV_ROUND_UP(I3C_BUS_TBUF_MIXED_FM_MIN_NS,
> > + NSEC_PER_SEC / i3c->rate);
> > + renesas_writel(i3c->regs, BFRECDT, BFRECDT_FRECYC(val));
> > +
> > + val = DIV_ROUND_UP(I3C_BUS_TAVAL_MIN_NS,
> > + NSEC_PER_SEC / i3c->rate);
> > + renesas_writel(i3c->regs, BAVLCDT, BAVLCDT_AVLCYC(val));
> > +
> > + val = DIV_ROUND_UP(I3C_BUS_TIDLE_MIN_NS,
> > + NSEC_PER_SEC / i3c->rate);
> > + renesas_writel(i3c->regs, BIDLCDT, BIDLCDT_IDLCYC(val));
> > +}
> > +
> > static int renesas_i3c_bus_init(struct i3c_master_controller *m)
> > {
> > struct renesas_i3c *i3c = to_renesas_i3c(m);
> > struct i3c_bus *bus = i3c_master_get_bus(m);
> > struct i3c_device_info info = {};
> > struct i2c_timings t;
> > - unsigned long rate;
> > - u32 double_SBR, val;
> > + u32 double_SBR;
> > int cks, pp_high_ticks, pp_low_ticks, i3c_total_ticks;
> > int od_high_ticks, od_low_ticks, i2c_total_ticks;
> > int ret;
> >
> > - rate = clk_get_rate(i3c->tclk);
> > - if (!rate)
> > + i3c->rate = clk_get_rate(i3c->tclk);
> > + if (!i3c->rate)
> > return -EINVAL;
> >
> > ret = renesas_i3c_reset(i3c);
> > if (ret)
> > return ret;
> >
> > - i2c_total_ticks = DIV_ROUND_UP(rate, bus->scl_rate.i2c);
> > - i3c_total_ticks = DIV_ROUND_UP(rate, bus->scl_rate.i3c);
> > + i2c_total_ticks = DIV_ROUND_UP(i3c->rate, bus->scl_rate.i2c);
> > + i3c_total_ticks = DIV_ROUND_UP(i3c->rate, bus->scl_rate.i3c);
> >
> > i2c_parse_fw_timings(&m->dev, &t, true);
> >
> > @@ -511,7 +570,7 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m)
> > pp_high_ticks = ((i3c_total_ticks * 5) / 10);
> > else
> > pp_high_ticks = DIV_ROUND_UP(I3C_BUS_THIGH_MIXED_MAX_NS,
> > - NSEC_PER_SEC / rate);
> > + NSEC_PER_SEC / i3c->rate);
> > pp_low_ticks = i3c_total_ticks - pp_high_ticks;
> >
> > if ((od_low_ticks / 2) <= 0xFF && pp_low_ticks < 0x3F)
> > @@ -519,7 +578,7 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m)
> >
> > i2c_total_ticks /= 2;
> > i3c_total_ticks /= 2;
> > - rate /= 2;
> > + i3c->rate /= 2;
> > }
> >
> > /* SCL clock period calculation in Open-drain mode */
> > @@ -540,8 +599,8 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m)
> > STDBR_SBRLP(pp_low_ticks) |
> > STDBR_SBRHP(pp_high_ticks);
> >
> > - od_low_ticks -= t.scl_fall_ns / (NSEC_PER_SEC / rate) + 1;
> > - od_high_ticks -= t.scl_rise_ns / (NSEC_PER_SEC / rate) + 1;
> > + od_low_ticks -= t.scl_fall_ns / (NSEC_PER_SEC / i3c->rate) + 1;
> > + od_high_ticks -= t.scl_rise_ns / (NSEC_PER_SEC / i3c->rate) + 1;
> > i3c->i2c_STDBR = (double_SBR ? STDBR_DSBRPO : 0) |
> > STDBR_SBRLO(double_SBR, od_low_ticks) |
> > STDBR_SBRHO(double_SBR, od_high_ticks) |
> > @@ -556,55 +615,16 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m)
> > EXTBR_EBRHP(pp_high_ticks));
> >
> > renesas_writel(i3c->regs, REFCKCTL, REFCKCTL_IREFCKS(cks));
> > + i3c->refclk_div = cks;
> >
> > - /* Disable Slave Mode */
> > - renesas_writel(i3c->regs, SVCTL, 0);
> > -
> > - /* Initialize Queue/Buffer threshold */
> > - renesas_writel(i3c->regs, NQTHCTL, NQTHCTL_IBIDSSZ(6) |
> > - NQTHCTL_CMDQTH(1));
> > -
> > - /* The only supported configuration is two entries*/
> > - renesas_writel(i3c->regs, NTBTHCTL0, 0);
> > - /* Interrupt when there is one entry in the queue */
> > - renesas_writel(i3c->regs, NRQTHCTL, 0);
> > -
> > - /* Enable all Bus/Transfer Status Flags */
> > - renesas_writel(i3c->regs, BSTE, BSTE_ALL_FLAG);
> > - renesas_writel(i3c->regs, NTSTE, NTSTE_ALL_FLAG);
> > -
> > - /* Interrupt enable settings */
> > - renesas_writel(i3c->regs, BIE, BIE_NACKDIE | BIE_TENDIE);
> > - renesas_writel(i3c->regs, NTIE, 0);
> > -
> > - /* Clear Status register */
> > - renesas_writel(i3c->regs, NTST, 0);
> > - renesas_writel(i3c->regs, INST, 0);
> > - renesas_writel(i3c->regs, BST, 0);
> > -
> > - /* Hot-Join Acknowlege setting. */
> > - renesas_set_bit(i3c->regs, BCTL, BCTL_HJACKCTL);
> > -
> > - renesas_writel(i3c->regs, IBINCTL, IBINCTL_NRHJCTL | IBINCTL_NRMRCTL |
> > - IBINCTL_NRSIRCTL);
> > -
> > - renesas_writel(i3c->regs, SCSTLCTL, 0);
> > - renesas_set_bit(i3c->regs, SCSTRCTL, SCSTRCTL_ACKTWE);
> > -
> > - /* Bus condition timing */
> > - val = DIV_ROUND_UP(I3C_BUS_TBUF_MIXED_FM_MIN_NS, NSEC_PER_SEC / rate);
> > - renesas_writel(i3c->regs, BFRECDT, BFRECDT_FRECYC(val));
> > -
> > - val = DIV_ROUND_UP(I3C_BUS_TAVAL_MIN_NS, NSEC_PER_SEC / rate);
> > - renesas_writel(i3c->regs, BAVLCDT, BAVLCDT_AVLCYC(val));
> > -
> > - val = DIV_ROUND_UP(I3C_BUS_TIDLE_MIN_NS, NSEC_PER_SEC / rate);
> > - renesas_writel(i3c->regs, BIDLCDT, BIDLCDT_IDLCYC(val));
> > + /* I3C hw init*/
> > + renesas_i3c_hw_init(i3c);
> >
> > ret = i3c_master_get_free_addr(m, 0);
> > if (ret < 0)
> > return ret;
> >
> > + i3c->dyn_addr = ret;
> > renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYAD(ret) | MSDVAD_MDYADV);
> >
> > memset(&info, 0, sizeof(info));
> > @@ -1301,8 +1321,6 @@ static const struct renesas_i3c_irq_desc renesas_i3c_irqs[] = {
> > static int renesas_i3c_probe(struct platform_device *pdev)
> > {
> > struct renesas_i3c *i3c;
> > - struct reset_control *reset;
> > - struct clk *clk;
> > const struct renesas_i3c_config *config = of_device_get_match_data(&pdev->dev);
> > int ret, i;
> >
> > @@ -1317,28 +1335,28 @@ static int renesas_i3c_probe(struct platform_device *pdev)
> > if (IS_ERR(i3c->regs))
> > return PTR_ERR(i3c->regs);
> >
> > - clk = devm_clk_get_enabled(&pdev->dev, "pclk");
> > - if (IS_ERR(clk))
> > - return PTR_ERR(clk);
> > + i3c->pclk = devm_clk_get_enabled(&pdev->dev, "pclk");
> > + if (IS_ERR(i3c->pclk))
> > + return PTR_ERR(i3c->pclk);
> >
> > if (config->has_pclkrw) {
> > - clk = devm_clk_get_enabled(&pdev->dev, "pclkrw");
> > - if (IS_ERR(clk))
> > - return PTR_ERR(clk);
> > + i3c->pclkrw = devm_clk_get_enabled(&pdev->dev, "pclkrw");
> > + if (IS_ERR(i3c->pclkrw))
> > + return PTR_ERR(i3c->pclkrw);
> > }
> >
> > i3c->tclk = devm_clk_get_enabled(&pdev->dev, "tclk");
> > if (IS_ERR(i3c->tclk))
> > return PTR_ERR(i3c->tclk);
> >
> > - reset = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "tresetn");
> > - if (IS_ERR(reset))
> > - return dev_err_probe(&pdev->dev, PTR_ERR(reset),
> > + i3c->tresetn = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "tresetn");
> > + if (IS_ERR(i3c->tresetn))
> > + return dev_err_probe(&pdev->dev, PTR_ERR(i3c->tresetn),
> > "Error: missing tresetn ctrl\n");
> >
> > - reset = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "presetn");
> > - if (IS_ERR(reset))
> > - return dev_err_probe(&pdev->dev, PTR_ERR(reset),
> > + i3c->presetn = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "presetn");
> > + if (IS_ERR(i3c->presetn))
> > + return dev_err_probe(&pdev->dev, PTR_ERR(i3c->presetn),
> > "Error: missing presetn ctrl\n");
>
> And another patch to save clk, rate and reset to into struct renesas_i3c.
I will do this in v2.
>
> >
> > spin_lock_init(&i3c->xferqueue.lock);
> > @@ -1364,6 +1382,13 @@ 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);
> > }
> >
> > @@ -1374,6 +1399,92 @@ static void renesas_i3c_remove(struct platform_device *pdev)
> > i3c_master_unregister(&i3c->base);
> > }
> >
> > +static int renesas_i3c_suspend_noirq(struct device *dev)
> > +{
> > + struct renesas_i3c *i3c = dev_get_drvdata(dev);
> > + int i, 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)
> > + return ret;
> > +
> > + ret = reset_control_assert(i3c->tresetn);
> > + if (ret) {
> > + reset_control_deassert(i3c->presetn);
> > + return ret;
> > + }
> > +
> > + clk_disable_unprepare(i3c->pclk);
> > + clk_disable_unprepare(i3c->tclk);
> > + clk_disable_unprepare(i3c->pclkrw);
> > +
> > + return 0;
> > +}
> > +
> > +static int renesas_i3c_resume_noirq(struct device *dev)
> > +{
> > + struct renesas_i3c *i3c = dev_get_drvdata(dev);
> > + int i, ret;
> > +
> > + ret = reset_control_deassert(i3c->presetn);
> > + if (ret)
> > + return ret;
> > +
> > + ret = reset_control_deassert(i3c->tresetn);
> > + if (ret)
> > + goto err_presetn;
> > +
> > + ret = clk_prepare_enable(i3c->pclkrw);
> > + if (ret)
> > + goto err_tresetn;
> > +
> > + ret = clk_prepare_enable(i3c->pclk);
> > + if (ret)
> > + goto err_pclkrw;
> > +
> > + ret = clk_prepare_enable(i3c->tclk);
> > + if (ret)
> > + goto err_pclk;
>
> Can you use clk bulk API? Just for reference, clk prepare have mutex lock,
> which may cause dead lock, such as if use gpio expendor to control external
> clock gate.
>
> https://lists.openwall.net/linux-kernel/2025/07/01/139
> https://lore.kernel.org/imx/87pldsd1tq.fsf@bootlin.com/#t
Thanks for sharing.
My plan is to add then
struct clk_bulk_data clks[3];
u8 num_clks;
and
static const char * const renesas_i3c_clks[] = {
"pclk", "tclk", "pclkrw"
};
Then into the renesas_i3c_probe() we can use:
i3c->num_clks = config->has_pclkrw ? 3 : 2;
for (i = 0; i < i3c->num_clks; i++)
i3c->clks[i].id = renesas_i3c_clks[i];
ret = devm_clk_bulk_get(&pdev->dev, i3c->num_clks, i3c->clks);
if (ret)
return ret;
ret = clk_bulk_prepare_enable(i3c->num_clks, i3c->clks);
if (ret)
return ret;
And update then renesas_i3c_resume_noirq()/renesas_i3c_suspend_noirq()
with:
clk_bulk_prepare_enable(i3c->num_clks, i3c->clks);
clk_bulk_disable_unprepare(i3c->num_clks, i3c->clks)
Thanks & Regards,
Tommaso
>
> Frank
>
> > +
> > + /* Re-store I3C registers value. */
> > + renesas_writel(i3c->regs, REFCKCTL,
> > + REFCKCTL_IREFCKS(i3c->refclk_div));
> > + 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);
> > +
> > + i2c_mark_adapter_resumed(&i3c->base.i2c);
> > +
> > + return 0;
> > +
> > +err_pclk:
> > + clk_disable_unprepare(i3c->pclk);
> > +err_pclkrw:
> > + clk_disable_unprepare(i3c->pclkrw);
> > +err_tresetn:
> > + reset_control_assert(i3c->tresetn);
> > +err_presetn:
> > + reset_control_assert(i3c->presetn);
> > + return ret;
> > +}
> > +
> > +static const struct dev_pm_ops renesas_i3c_pm_ops = {
> > + NOIRQ_SYSTEM_SLEEP_PM_OPS(renesas_i3c_suspend_noirq,
> > + renesas_i3c_resume_noirq)
> > +};
> > +
> > static const struct renesas_i3c_config empty_i3c_config = {
> > };
> >
> > @@ -1394,6 +1505,7 @@ static struct platform_driver renesas_i3c = {
> > .driver = {
> > .name = "renesas-i3c",
> > .of_match_table = renesas_i3c_of_ids,
> > + .pm = pm_sleep_ptr(&renesas_i3c_pm_ops),
> > },
> > };
> > module_platform_driver(renesas_i3c);
> > --
> > 2.43.0
> >
next prev parent reply other threads:[~2025-12-30 8:20 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-12-23 16:54 [PATCH] i3c: renesas: Add suspend/resume support Tommaso Merciai
2025-12-23 16:54 ` Tommaso Merciai
2025-12-29 15:51 ` Frank Li
2025-12-29 15:51 ` Frank Li
2025-12-30 8:19 ` Tommaso Merciai [this message]
2025-12-30 8:19 ` Tommaso Merciai
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=aVOLK9AU4Zma69VF@tom-desktop \
--to=tommaso.merciai.xr@bp.renesas.com \
--cc=Frank.li@nxp.com \
--cc=alexandre.belloni@bootlin.com \
--cc=biju.das.jz@bp.renesas.com \
--cc=linux-i3c@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-renesas-soc@vger.kernel.org \
--cc=p.zabel@pengutronix.de \
--cc=tomm.merciai@gmail.com \
--cc=wsa+renesas@sang-engineering.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.