diff options
author | Geert Uytterhoeven <geert+renesas@glider.be> | 2017-06-21 22:51:21 +0200 |
---|---|---|
committer | Geert Uytterhoeven <geert+renesas@glider.be> | 2017-10-20 11:16:05 +0200 |
commit | 9f55b17ff6387ab9c4caa9280e2e194bb03ad532 (patch) | |
tree | d8eba133be4bcd0b27dedd585106780691d21929 | |
parent | 9f8c71e5134982cdf8ee35acb204715a2a47ba2e (diff) |
clk: renesas: rcar-gen3: Restore SDHI clocks during resume
On R-Car Gen3 systems, PSCI system suspend powers down the SoC, losing
clock configuration. Register a notifier to save/restore SDHI clock
registers during system suspend/resume.
This is implemented using the cpg_simple_notifier abstraction, which can
be reused for others clocks that just need to save/restore a single
register.
Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
Tested-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
-rw-r--r-- | drivers/clk/renesas/rcar-gen3-cpg.c | 63 |
1 files changed, 50 insertions, 13 deletions
diff --git a/drivers/clk/renesas/rcar-gen3-cpg.c b/drivers/clk/renesas/rcar-gen3-cpg.c index 139985257003..267b5629e3bd 100644 --- a/drivers/clk/renesas/rcar-gen3-cpg.c +++ b/drivers/clk/renesas/rcar-gen3-cpg.c @@ -19,6 +19,7 @@ #include <linux/err.h> #include <linux/init.h> #include <linux/io.h> +#include <linux/pm.h> #include <linux/slab.h> #include <linux/sys_soc.h> @@ -29,6 +30,36 @@ #define CPG_PLL2CR 0x002c #define CPG_PLL4CR 0x01f4 +struct cpg_simple_notifier { + struct notifier_block nb; + void __iomem *reg; + u32 saved; +}; + +static int cpg_simple_notifier_call(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct cpg_simple_notifier *csn = + container_of(nb, struct cpg_simple_notifier, nb); + + switch (action) { + case PM_EVENT_SUSPEND: + csn->saved = readl(csn->reg); + return NOTIFY_OK; + + case PM_EVENT_RESUME: + writel(csn->saved, csn->reg); + return NOTIFY_OK; + } + return NOTIFY_DONE; +} + +static void cpg_simple_notifier_register(struct raw_notifier_head *notifiers, + struct cpg_simple_notifier *csn) +{ + csn->nb.notifier_call = cpg_simple_notifier_call; + raw_notifier_chain_register(notifiers, &csn->nb); +} /* * SDn Clock @@ -55,8 +86,8 @@ struct sd_div_table { struct sd_clock { struct clk_hw hw; - void __iomem *reg; const struct sd_div_table *div_table; + struct cpg_simple_notifier csn; unsigned int div_num; unsigned int div_min; unsigned int div_max; @@ -97,12 +128,12 @@ static const struct sd_div_table cpg_sd_div_table[] = { static int cpg_sd_clock_enable(struct clk_hw *hw) { struct sd_clock *clock = to_sd_clock(hw); - u32 val = readl(clock->reg); + u32 val = readl(clock->csn.reg); val &= ~(CPG_SD_STP_MASK); val |= clock->div_table[clock->cur_div_idx].val & CPG_SD_STP_MASK; - writel(val, clock->reg); + writel(val, clock->csn.reg); return 0; } @@ -111,14 +142,14 @@ static void cpg_sd_clock_disable(struct clk_hw *hw) { struct sd_clock *clock = to_sd_clock(hw); - writel(readl(clock->reg) | CPG_SD_STP_MASK, clock->reg); + writel(readl(clock->csn.reg) | CPG_SD_STP_MASK, clock->csn.reg); } static int cpg_sd_clock_is_enabled(struct clk_hw *hw) { struct sd_clock *clock = to_sd_clock(hw); - return !(readl(clock->reg) & CPG_SD_STP_MASK); + return !(readl(clock->csn.reg) & CPG_SD_STP_MASK); } static unsigned long cpg_sd_clock_recalc_rate(struct clk_hw *hw, @@ -170,10 +201,10 @@ static int cpg_sd_clock_set_rate(struct clk_hw *hw, unsigned long rate, clock->cur_div_idx = i; - val = readl(clock->reg); + val = readl(clock->csn.reg); val &= ~(CPG_SD_STP_MASK | CPG_SD_FC_MASK); val |= clock->div_table[i].val & (CPG_SD_STP_MASK | CPG_SD_FC_MASK); - writel(val, clock->reg); + writel(val, clock->csn.reg); return 0; } @@ -188,8 +219,8 @@ static const struct clk_ops cpg_sd_clock_ops = { }; static struct clk * __init cpg_sd_clk_register(const struct cpg_core_clk *core, - void __iomem *base, - const char *parent_name) + void __iomem *base, const char *parent_name, + struct raw_notifier_head *notifiers) { struct clk_init_data init; struct sd_clock *clock; @@ -207,12 +238,12 @@ static struct clk * __init cpg_sd_clk_register(const struct cpg_core_clk *core, init.parent_names = &parent_name; init.num_parents = 1; - clock->reg = base + core->offset; + clock->csn.reg = base + core->offset; clock->hw.init = &init; clock->div_table = cpg_sd_div_table; clock->div_num = ARRAY_SIZE(cpg_sd_div_table); - sd_fc = readl(clock->reg) & CPG_SD_FC_MASK; + sd_fc = readl(clock->csn.reg) & CPG_SD_FC_MASK; for (i = 0; i < clock->div_num; i++) if (sd_fc == (clock->div_table[i].val & CPG_SD_FC_MASK)) break; @@ -233,8 +264,13 @@ static struct clk * __init cpg_sd_clk_register(const struct cpg_core_clk *core, clk = clk_register(NULL, &clock->hw); if (IS_ERR(clk)) - kfree(clock); + goto free_clock; + + cpg_simple_notifier_register(notifiers, &clock->csn); + return clk; +free_clock: + kfree(clock); return clk; } @@ -332,7 +368,8 @@ struct clk * __init rcar_gen3_cpg_clk_register(struct device *dev, break; case CLK_TYPE_GEN3_SD: - return cpg_sd_clk_register(core, base, __clk_get_name(parent)); + return cpg_sd_clk_register(core, base, __clk_get_name(parent), + notifiers); case CLK_TYPE_GEN3_R: if (cpg_quirks & RCKCR_CKSEL) { |