Re: [PATCH v2 08/10] clk: renesas: rzg2l-cpg: Add suspend/resume support for power domains
From: Ulf Hansson
Date: Wed Apr 17 2024 - 05:41:21 EST
On Wed, 17 Apr 2024 at 10:05, claudiu beznea <claudiu.beznea@xxxxxxxxx> wrote:
>
> Hi, Ulf,
>
> On 16.04.2024 15:07, Ulf Hansson wrote:
> > On Thu, 7 Mar 2024 at 15:10, Claudiu <claudiu.beznea@xxxxxxxxx> wrote:
> >>
> >> From: Claudiu Beznea <claudiu.beznea.uj@xxxxxxxxxxxxxx>
> >>
> >> RZ/G3S supports deep sleep states that it can reach with the help of the
> >> TF-A.
> >>
> >> RZ/G3S has a few power domains (e.g. GIC) that need to be always-on while
> >> Linux is running. These domains are initialized (and powered on) when
> >> clock driver is probed.
> >>
> >> As the TF-A takes control at the very last(suspend)/first(resume)
> >> phase of configuring the deep sleep state, it can do it's own settings on
> >> power domains.
> >
> > For my understanding, can you please elaborate on this part a bit.
> > What does the "last suspend/resume phase" mean, more exactly, here?
>
> The RZ/G3S SoC support a power saving mode where most of the SoC parts are
> turned off and the system RAM is switched to retention mode. This is done
> with the help of TF-A. The handshake b/w Linux and TF-A is done though the
> drivers/firmware/psci/psci.c driver.
>
> After Linux finishes the execution of suspend code the control is taken by
> TF-A. TF-A does the final settings on the system (e.g. switching the RAM to
> retention mode) and power off most of the SoC parts.
>
> By the last phase of the suspend I'm referring to the TF-A doing the final
> adjustments for the system to switch to this power saving mode.
>
> When resuming, as the TF-A is the 1st one being executed on the system
> (this is what I called above the 1st phase of the resume), TF-A moves the
> DDR out of retention mode, reconfigure basic IPs (like in boot case as most
> of the SoC parts were powered off) and then give the control to Linux which
> will execute the resume code.
Alright, thanks for clarifying! This makes sense to me now!
>
>
> >
> >>
> >> Thus, to restore the proper Linux state, add rzg2l_cpg_resume() which
> >> powers on the always-on domains and rzg2l_cpg_complete() which activates
> >> the power down mode for the IPs selected through CPG_PWRDN_IP{1, 2}.
> >>
> >> Along with it, added the suspend_check member to the RZ/G2L power domain
> >> data structure whose purpose is to checks if a domain can be powered off
> >> while the system is going to suspend. This is necessary for the serial
> >> console domain which needs to be powered on if no_console_suspend is
> >> available in bootargs.
> >>
> >> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@xxxxxxxxxxxxxx>
> >> ---
> >>
> >> Changes in v2:
> >> - none; this patch is new
> >>
> >> drivers/clk/renesas/rzg2l-cpg.c | 66 ++++++++++++++++++++++++++++++---
> >> drivers/clk/renesas/rzg2l-cpg.h | 1 +
> >> 2 files changed, 62 insertions(+), 5 deletions(-)
> >>
> >> diff --git a/drivers/clk/renesas/rzg2l-cpg.c b/drivers/clk/renesas/rzg2l-cpg.c
> >> index b36700f4a9f5..b18af227177e 100644
> >> --- a/drivers/clk/renesas/rzg2l-cpg.c
> >> +++ b/drivers/clk/renesas/rzg2l-cpg.c
> >> @@ -15,6 +15,7 @@
> >> #include <linux/clk.h>
> >> #include <linux/clk-provider.h>
> >> #include <linux/clk/renesas.h>
> >> +#include <linux/console.h>
> >> #include <linux/delay.h>
> >> #include <linux/device.h>
> >> #include <linux/init.h>
> >> @@ -139,6 +140,7 @@ struct rzg2l_pll5_mux_dsi_div_param {
> >> * @num_resets: Number of Module Resets in info->resets[]
> >> * @last_dt_core_clk: ID of the last Core Clock exported to DT
> >> * @info: Pointer to platform data
> >> + * @domains: generic PM domains
> >> * @mux_dsi_div_params: pll5 mux and dsi div parameters
> >> */
> >> struct rzg2l_cpg_priv {
> >> @@ -155,6 +157,8 @@ struct rzg2l_cpg_priv {
> >>
> >> const struct rzg2l_cpg_info *info;
> >>
> >> + struct generic_pm_domain **domains;
> >> +
> >> struct rzg2l_pll5_mux_dsi_div_param mux_dsi_div_params;
> >> };
> >>
> >> @@ -1570,12 +1574,14 @@ struct rzg2l_cpg_pm_domains {
> >> * struct rzg2l_cpg_pd - RZ/G2L power domain data structure
> >> * @genpd: generic PM domain
> >> * @priv: pointer to CPG private data structure
> >> + * @suspend_check: check if domain could be powered off in suspend
> >> * @conf: CPG PM domain configuration info
> >> * @id: RZ/G2L power domain ID
> >> */
> >> struct rzg2l_cpg_pd {
> >> struct generic_pm_domain genpd;
> >> struct rzg2l_cpg_priv *priv;
> >> + int (*suspend_check)(void);
> >> struct rzg2l_cpg_pm_domain_conf conf;
> >> u16 id;
> >> };
> >> @@ -1676,6 +1682,13 @@ static int rzg2l_cpg_power_off(struct generic_pm_domain *domain)
> >> struct rzg2l_cpg_reg_conf pwrdn = pd->conf.pwrdn;
> >> struct rzg2l_cpg_priv *priv = pd->priv;
> >>
> >> + if (pd->suspend_check) {
> >> + int ret = pd->suspend_check();
> >> +
> >> + if (ret)
> >> + return ret;
> >> + }
> >> +
> >
> > This should not be needed at all, I think.
> >
> > Instead, genpd should be able to take the correct decision during
> > system-wide suspend and simply avoid calling the ->power_off()
> > callback, when that is needed.
> >
> > If I understand correctly, GENPD_FLAG_ACTIVE_WAKEUP is set for the
> > genpd in question. The only remaining thing would then be to let the
> > console driver, during system suspend, check whether
> > "console_suspend_enabled" is set and then call device_set_awake_path()
> > for its device. In this way, genpd should then keep the corresponding
> > PM domain powered-on.
>
> You're right! I've checked it and all good w/o ->suspend_check() if
> device_set_wakeup_path() is called for the console driver.
>
> I'll send an update for it.
Great! Please keep me posted on the entire series for next version. I
will try to continue to help review this.
>
> >
> >> /* Set MSTOP. */
> >> if (mstop.mask)
> >> writel(mstop.mask | (mstop.mask << 16), priv->base + mstop.off);
> >> @@ -1687,8 +1700,14 @@ static int rzg2l_cpg_power_off(struct generic_pm_domain *domain)
> >> return 0;
> >> }
> >>
> >> -static int __init rzg2l_cpg_pd_setup(struct rzg2l_cpg_pd *pd, bool always_on)
> >> +static int rzg2l_pd_suspend_check_console(void)
> >> {
> >> + return console_suspend_enabled ? 0 : -EBUSY;
> >> +}
> >> +
> >> +static int __init rzg2l_cpg_pd_setup(struct rzg2l_cpg_pd *pd, u32 flags)
> >> +{
> >> + bool always_on = !!(flags & RZG2L_PD_F_ALWAYS_ON);
> >> struct dev_power_governor *governor;
> >>
> >> pd->genpd.flags |= GENPD_FLAG_PM_CLK | GENPD_FLAG_ACTIVE_WAKEUP;
> >> @@ -1700,6 +1719,8 @@ static int __init rzg2l_cpg_pd_setup(struct rzg2l_cpg_pd *pd, bool always_on)
> >> } else {
> >> pd->genpd.power_on = rzg2l_cpg_power_on;
> >> pd->genpd.power_off = rzg2l_cpg_power_off;
> >> + if (flags & RZG2L_PD_F_CONSOLE)
> >> + pd->suspend_check = rzg2l_pd_suspend_check_console;
> >> governor = &simple_qos_governor;
> >> }
> >>
> >> @@ -1719,7 +1740,7 @@ static int __init rzg2l_cpg_add_clk_domain(struct rzg2l_cpg_priv *priv)
> >>
> >> pd->genpd.name = np->name;
> >> pd->priv = priv;
> >> - ret = rzg2l_cpg_pd_setup(pd, true);
> >> + ret = rzg2l_cpg_pd_setup(pd, RZG2L_PD_F_ALWAYS_ON);
> >> if (ret)
> >> return ret;
> >>
> >> @@ -1778,13 +1799,13 @@ static int __init rzg2l_cpg_add_pm_domains(struct rzg2l_cpg_priv *priv)
> >> domains->onecell_data.domains = domains->domains;
> >> domains->onecell_data.num_domains = info->num_pm_domains;
> >> domains->onecell_data.xlate = rzg2l_cpg_pm_domain_xlate;
> >> + priv->domains = domains->domains;
> >>
> >> ret = devm_add_action_or_reset(dev, rzg2l_cpg_genpd_remove, &domains->onecell_data);
> >> if (ret)
> >> return ret;
> >>
> >> for (unsigned int i = 0; i < info->num_pm_domains; i++) {
> >> - bool always_on = !!(info->pm_domains[i].flags & RZG2L_PD_F_ALWAYS_ON);
> >> struct rzg2l_cpg_pd *pd;
> >>
> >> pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
> >> @@ -1796,11 +1817,11 @@ static int __init rzg2l_cpg_add_pm_domains(struct rzg2l_cpg_priv *priv)
> >> pd->id = info->pm_domains[i].id;
> >> pd->priv = priv;
> >>
> >> - ret = rzg2l_cpg_pd_setup(pd, always_on);
> >> + ret = rzg2l_cpg_pd_setup(pd, info->pm_domains[i].flags);
> >> if (ret)
> >> return ret;
> >>
> >> - if (always_on) {
> >> + if (info->pm_domains[i].flags & RZG2L_PD_F_ALWAYS_ON) {
> >> ret = rzg2l_cpg_power_on(&pd->genpd);
> >> if (ret)
> >> return ret;
> >> @@ -1890,9 +1911,43 @@ static int __init rzg2l_cpg_probe(struct platform_device *pdev)
> >> if (error)
> >> return error;
> >>
> >> + dev_set_drvdata(dev, priv);
> >> +
> >> return 0;
> >> }
> >>
> >> +static int rzg2l_cpg_resume(struct device *dev)
> >> +{
> >> + struct rzg2l_cpg_priv *priv = dev_get_drvdata(dev);
> >> + const struct rzg2l_cpg_info *info = priv->info;
> >> +
> >> + /* Power on always ON domains. */
> >> + for (unsigned int i = 0; i < info->num_pm_domains; i++) {
> >> + if (info->pm_domains[i].flags & RZG2L_PD_F_ALWAYS_ON) {
> >> + int ret = rzg2l_cpg_power_on(priv->domains[i]);
> >> +
> >> + if (ret)
> >> + return ret;
> >> + }
> >> + }
> >
> > I don't quite understand why this is needed? Is always-on PM domains
> > being powered-off during system wide suspend, so you need to power
> > them on again?
>
> Yes, as power to most of the system parts is cut off during sytem suspend
> (and DDR is kept in retention mode) and the resume is almost like a cold
> boot where the TF-A does basic re-initialization and then pass execution to
> Linux resume code.
Hmm. If these are really always-on PM domains, why isn't the FW
powering them on again then before returning to Linux after a system
resume?
In a way it sounds to me that they aren't really always-on PM domains,
as Linux seems to be capable of turning them on/off too, right?
That said, perhaps using GENPD_FLAG_RPM_ALWAYS_ON instead of
GENPD_FLAG_ALWAYS_ON for some PM domains can be another way forward?
In this way, the ->power_on|off() callbacks can be used to turn on/off
the PM domains, but only during system suspend/resume. Would that
work?
[...]
Kind regards
Uffe