On Tue, Apr 18, 2017 at 10:42:49AM +0530, Keerthy wrote: > From: Russ Dill <russ.d...@ti.com> > > The clock/dpll registers are in the WKUP power domain. Under both RTC-only > suspend and hibernation, these registers are lost. Hence save/restore > them accordingly. >
This won't work for Tegra, because we need a 2 pass restore. First restore rate/parents with all clocks enabled and then disable the ones which were disabled before entering suspend. Both passes usually go from root to leave. However a few clocks have to be treated separately, eg the ones related to SDRAM. Peter. > Signed-off-by: Russ Dill <russ.d...@ti.com> > Signed-off-by: Keerthy <j-keer...@ti.com> > --- > drivers/clk/clk.c | 70 ++++++++++++++++++++++++ > drivers/clk/ti/divider.c | 36 +++++++++++++ > drivers/clk/ti/dpll.c | 6 +++ > drivers/clk/ti/dpll3xxx.c | 124 > +++++++++++++++++++++++++++++++++++++++++++ > drivers/clk/ti/gate.c | 3 ++ > drivers/clk/ti/mux.c | 29 ++++++++++ > include/linux/clk-provider.h | 11 ++++ > include/linux/clk.h | 25 +++++++++ > include/linux/clk/ti.h | 6 +++ > 9 files changed, 310 insertions(+) > > diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c > index cddddbe..1ca87f4 100644 > --- a/drivers/clk/clk.c > +++ b/drivers/clk/clk.c > @@ -687,6 +687,76 @@ static int clk_core_enable_lock(struct clk_core *core) > return ret; > } > > +void clk_dflt_restore_context(struct clk_hw *hw) > +{ > + if (hw->clk->core->enable_count) > + hw->clk->core->ops->enable(hw); > + else > + hw->clk->core->ops->disable(hw); > +} > +EXPORT_SYMBOL_GPL(clk_dflt_restore_context); > + > +static int clk_save_context(struct clk_core *clk) > +{ > + struct clk_core *child; > + int ret = 0; > + > + hlist_for_each_entry(child, &clk->children, child_node) { > + ret = clk_save_context(child); > + if (ret < 0) > + return ret; > + } > + > + if (clk->ops && clk->ops->save_context) > + ret = clk->ops->save_context(clk->hw); > + > + return ret; > +} > + > +static void clk_restore_context(struct clk_core *clk) > +{ > + struct clk_core *child; > + > + if (clk->ops && clk->ops->restore_context) > + clk->ops->restore_context(clk->hw); > + > + hlist_for_each_entry(child, &clk->children, child_node) > + clk_restore_context(child); > +} > + > +int clks_save_context(void) > +{ > + struct clk_core *clk; > + int ret; > + > + hlist_for_each_entry(clk, &clk_root_list, child_node) { > + ret = clk_save_context(clk); > + if (ret < 0) > + return ret; > + } > + > + hlist_for_each_entry(clk, &clk_orphan_list, child_node) { > + ret = clk_save_context(clk); > + if (ret < 0) > + return ret; > + } > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(clks_save_context); > + > +void clks_restore_context(void) > +{ > + struct clk_core *clk; > + > + hlist_for_each_entry(clk, &clk_root_list, child_node) > + clk_restore_context(clk); > + > + hlist_for_each_entry(clk, &clk_orphan_list, child_node) > + clk_restore_context(clk); > +} > +EXPORT_SYMBOL_GPL(clks_restore_context); > + > /** > * clk_enable - ungate a clock > * @clk: the clk being ungated > diff --git a/drivers/clk/ti/divider.c b/drivers/clk/ti/divider.c > index d6dcb28..350a58a 100644 > --- a/drivers/clk/ti/divider.c > +++ b/drivers/clk/ti/divider.c > @@ -266,10 +266,46 @@ static int ti_clk_divider_set_rate(struct clk_hw *hw, > unsigned long rate, > return 0; > } > > +/** > + * clk_divider_save_context - Save the divider value > + * @hw: pointer struct clk_hw > + * > + * Save the divider value > + */ > +static int clk_divider_save_context(struct clk_hw *hw) > +{ > + struct clk_divider *divider = to_clk_divider(hw); > + u32 val; > + > + val = ti_clk_ll_ops->clk_readl(divider->reg) >> divider->shift; > + divider->context = val & div_mask(divider); > + > + return 0; > +} > + > +/** > + * clk_divider_restore_context - restore the saved the divider value > + * @hw: pointer struct clk_hw > + * > + * Restore the saved the divider value > + */ > +static void clk_divider_restore_context(struct clk_hw *hw) > +{ > + struct clk_divider *divider = to_clk_divider(hw); > + u32 val; > + > + val = ti_clk_ll_ops->clk_readl(divider->reg); > + val &= ~(div_mask(divider) << divider->shift); > + val |= divider->context << divider->shift; > + ti_clk_ll_ops->clk_writel(val, divider->reg); > +} > + > const struct clk_ops ti_clk_divider_ops = { > .recalc_rate = ti_clk_divider_recalc_rate, > .round_rate = ti_clk_divider_round_rate, > .set_rate = ti_clk_divider_set_rate, > + .save_context = clk_divider_save_context, > + .restore_context = clk_divider_restore_context, > }; > > static struct clk *_register_divider(struct device *dev, const char *name, > diff --git a/drivers/clk/ti/dpll.c b/drivers/clk/ti/dpll.c > index 96d8488..791dd31 100644 > --- a/drivers/clk/ti/dpll.c > +++ b/drivers/clk/ti/dpll.c > @@ -39,6 +39,8 @@ > .set_rate_and_parent = &omap3_noncore_dpll_set_rate_and_parent, > .determine_rate = &omap4_dpll_regm4xen_determine_rate, > .get_parent = &omap2_init_dpll_parent, > + .save_context = &omap3_core_dpll_save_context, > + .restore_context = &omap3_core_dpll_restore_context, > }; > #else > static const struct clk_ops dpll_m4xen_ck_ops = {}; > @@ -62,6 +64,8 @@ > .set_rate_and_parent = &omap3_noncore_dpll_set_rate_and_parent, > .determine_rate = &omap3_noncore_dpll_determine_rate, > .get_parent = &omap2_init_dpll_parent, > + .save_context = &omap3_noncore_dpll_save_context, > + .restore_context = &omap3_noncore_dpll_restore_context, > }; > > static const struct clk_ops dpll_no_gate_ck_ops = { > @@ -72,6 +76,8 @@ > .set_parent = &omap3_noncore_dpll_set_parent, > .set_rate_and_parent = &omap3_noncore_dpll_set_rate_and_parent, > .determine_rate = &omap3_noncore_dpll_determine_rate, > + .save_context = &omap3_noncore_dpll_save_context, > + .restore_context = &omap3_noncore_dpll_restore_context > }; > #else > static const struct clk_ops dpll_core_ck_ops = {}; > diff --git a/drivers/clk/ti/dpll3xxx.c b/drivers/clk/ti/dpll3xxx.c > index 4534de2..44b6b64 100644 > --- a/drivers/clk/ti/dpll3xxx.c > +++ b/drivers/clk/ti/dpll3xxx.c > @@ -782,6 +782,130 @@ unsigned long omap3_clkoutx2_recalc(struct clk_hw *hw, > return rate; > } > > +/** > + * omap3_core_dpll_save_context - Save the m and n values of the divider > + * @hw: pointer struct clk_hw > + * > + * Before the dpll registers are lost save the last rounded rate m and n > + * and the enable mask. > + */ > +int omap3_core_dpll_save_context(struct clk_hw *hw) > +{ > + struct clk_hw_omap *clk = to_clk_hw_omap(hw); > + struct dpll_data *dd; > + u32 v; > + > + dd = clk->dpll_data; > + > + v = ti_clk_ll_ops->clk_readl(&dd->control_reg); > + clk->context = (v & dd->enable_mask) >> __ffs(dd->enable_mask); > + > + if (clk->context == DPLL_LOCKED) { > + v = ti_clk_ll_ops->clk_readl(&dd->mult_div1_reg); > + dd->last_rounded_m = (v & dd->mult_mask) >> > + __ffs(dd->mult_mask); > + dd->last_rounded_n = ((v & dd->div1_mask) >> > + __ffs(dd->div1_mask)) + 1; > + } > + > + return 0; > +} > + > +/** > + * omap3_core_dpll_restore_context - restore the m and n values of the > divider > + * @hw: pointer struct clk_hw > + * > + * Restore the last rounded rate m and n > + * and the enable mask. > + */ > +void omap3_core_dpll_restore_context(struct clk_hw *hw) > +{ > + struct clk_hw_omap *clk = to_clk_hw_omap(hw); > + const struct dpll_data *dd; > + u32 v; > + > + dd = clk->dpll_data; > + > + if (clk->context == DPLL_LOCKED) { > + _omap3_dpll_write_clken(clk, 0x4); > + _omap3_wait_dpll_status(clk, 0); > + > + v = ti_clk_ll_ops->clk_readl(&dd->mult_div1_reg); > + v &= ~(dd->mult_mask | dd->div1_mask); > + v |= dd->last_rounded_m << __ffs(dd->mult_mask); > + v |= (dd->last_rounded_n - 1) << __ffs(dd->div1_mask); > + ti_clk_ll_ops->clk_writel(v, &dd->mult_div1_reg); > + > + _omap3_dpll_write_clken(clk, DPLL_LOCKED); > + _omap3_wait_dpll_status(clk, 1); > + } else { > + _omap3_dpll_write_clken(clk, clk->context); > + } > +} > + > +/** > + * omap3_non_core_dpll_save_context - Save the m and n values of the divider > + * @hw: pointer struct clk_hw > + * > + * Before the dpll registers are lost save the last rounded rate m and n > + * and the enable mask. > + */ > +int omap3_noncore_dpll_save_context(struct clk_hw *hw) > +{ > + struct clk_hw_omap *clk = to_clk_hw_omap(hw); > + struct dpll_data *dd; > + u32 v; > + > + dd = clk->dpll_data; > + > + v = ti_clk_ll_ops->clk_readl(&dd->control_reg); > + clk->context = (v & dd->enable_mask) >> __ffs(dd->enable_mask); > + > + if (clk->context == DPLL_LOCKED) { > + v = ti_clk_ll_ops->clk_readl(&dd->mult_div1_reg); > + dd->last_rounded_m = (v & dd->mult_mask) >> > + __ffs(dd->mult_mask); > + dd->last_rounded_n = ((v & dd->div1_mask) >> > + __ffs(dd->div1_mask)) + 1; > + } > + > + return 0; > +} > + > +/** > + * omap3_core_dpll_restore_context - restore the m and n values of the > divider > + * @hw: pointer struct clk_hw > + * > + * Restore the last rounded rate m and n > + * and the enable mask. > + */ > +void omap3_noncore_dpll_restore_context(struct clk_hw *hw) > +{ > + struct clk_hw_omap *clk = to_clk_hw_omap(hw); > + const struct dpll_data *dd; > + u32 ctrl, mult_div1; > + > + dd = clk->dpll_data; > + > + ctrl = ti_clk_ll_ops->clk_readl(&dd->control_reg); > + mult_div1 = ti_clk_ll_ops->clk_readl(&dd->mult_div1_reg); > + > + if (clk->context == ((ctrl & dd->enable_mask) >> > + __ffs(dd->enable_mask)) && > + dd->last_rounded_m == ((mult_div1 & dd->mult_mask) >> > + __ffs(dd->mult_mask)) && > + dd->last_rounded_n == ((mult_div1 & dd->div1_mask) >> > + __ffs(dd->div1_mask)) + 1) { > + /* nothing to be done */ > + return; > + } > + > + if (clk->context == DPLL_LOCKED) > + omap3_noncore_dpll_program(clk, 0); > + else > + _omap3_dpll_write_clken(clk, clk->context); > +} > + > /* OMAP3/4 non-CORE DPLL clkops */ > const struct clk_hw_omap_ops clkhwops_omap3_dpll = { > .allow_idle = omap3_dpll_allow_idle, > diff --git a/drivers/clk/ti/gate.c b/drivers/clk/ti/gate.c > index 7151ec3..098db66 100644 > --- a/drivers/clk/ti/gate.c > +++ b/drivers/clk/ti/gate.c > @@ -33,6 +33,7 @@ > .init = &omap2_init_clk_clkdm, > .enable = &omap2_clkops_enable_clkdm, > .disable = &omap2_clkops_disable_clkdm, > + .restore_context = clk_dflt_restore_context, > }; > > const struct clk_ops omap_gate_clk_ops = { > @@ -40,6 +41,7 @@ > .enable = &omap2_dflt_clk_enable, > .disable = &omap2_dflt_clk_disable, > .is_enabled = &omap2_dflt_clk_is_enabled, > + .restore_context = clk_dflt_restore_context, > }; > > static const struct clk_ops omap_gate_clk_hsdiv_restore_ops = { > @@ -47,6 +49,7 @@ > .enable = &omap36xx_gate_clk_enable_with_hsdiv_restore, > .disable = &omap2_dflt_clk_disable, > .is_enabled = &omap2_dflt_clk_is_enabled, > + .restore_context = clk_dflt_restore_context, > }; > > /** > diff --git a/drivers/clk/ti/mux.c b/drivers/clk/ti/mux.c > index 18c267b..e73764a 100644 > --- a/drivers/clk/ti/mux.c > +++ b/drivers/clk/ti/mux.c > @@ -90,10 +90,39 @@ static int ti_clk_mux_set_parent(struct clk_hw *hw, u8 > index) > return 0; > } > > +/** > + * clk_mux_save_context - Save the parent selcted in the mux > + * @hw: pointer struct clk_hw > + * > + * Save the parent mux value. > + */ > +static int clk_mux_save_context(struct clk_hw *hw) > +{ > + struct clk_mux *mux = to_clk_mux(hw); > + > + mux->saved_parent = ti_clk_mux_get_parent(hw); > + return 0; > +} > + > +/** > + * clk_mux_restore_context - Restore the parent in the mux > + * @hw: pointer struct clk_hw > + * > + * Restore the saved parent mux value. > + */ > +static void clk_mux_restore_context(struct clk_hw *hw) > +{ > + struct clk_mux *mux = to_clk_mux(hw); > + > + ti_clk_mux_set_parent(hw, mux->saved_parent); > +} > + > const struct clk_ops ti_clk_mux_ops = { > .get_parent = ti_clk_mux_get_parent, > .set_parent = ti_clk_mux_set_parent, > .determine_rate = __clk_mux_determine_rate, > + .save_context = clk_mux_save_context, > + .restore_context = clk_mux_restore_context, > }; > > static struct clk *_register_mux(struct device *dev, const char *name, > diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h > index a428aec..2a8f636 100644 > --- a/include/linux/clk-provider.h > +++ b/include/linux/clk-provider.h > @@ -103,6 +103,11 @@ struct clk_rate_request { > * Called with enable_lock held. This function must not > * sleep. > * > + * @save_context: Save the context of the clock in prepration for poweroff. > + * > + * @restore_context: Restore the context of the clock after a restoration > + * of power. > + * > * @recalc_rate Recalculate the rate of this clock, by querying > hardware. The > * parent rate is an input parameter. It is up to the caller to > * ensure that the prepare_mutex is held across this call. > @@ -198,6 +203,8 @@ struct clk_ops { > void (*disable)(struct clk_hw *hw); > int (*is_enabled)(struct clk_hw *hw); > void (*disable_unused)(struct clk_hw *hw); > + int (*save_context)(struct clk_hw *hw); > + void (*restore_context)(struct clk_hw *hw); > unsigned long (*recalc_rate)(struct clk_hw *hw, > unsigned long parent_rate); > long (*round_rate)(struct clk_hw *hw, unsigned long rate, > @@ -394,6 +401,7 @@ struct clk_divider { > u8 flags; > const struct clk_div_table *table; > spinlock_t *lock; > + u32 context; > }; > > #define to_clk_divider(_hw) container_of(_hw, struct clk_divider, hw) > @@ -471,6 +479,7 @@ struct clk_mux { > u8 shift; > u8 flags; > spinlock_t *lock; > + u8 saved_parent; > }; > > #define to_clk_mux(_hw) container_of(_hw, struct clk_mux, hw) > @@ -912,5 +921,7 @@ struct dentry *clk_debugfs_add_file(struct clk_hw *hw, > char *name, umode_t mode, > void *data, const struct file_operations *fops); > #endif > > +void clk_dflt_restore_context(struct clk_hw *hw); > + > #endif /* CONFIG_COMMON_CLK */ > #endif /* CLK_PROVIDER_H */ > diff --git a/include/linux/clk.h b/include/linux/clk.h > index 024cd07..d071a65 100644 > --- a/include/linux/clk.h > +++ b/include/linux/clk.h > @@ -438,6 +438,23 @@ struct clk *devm_get_clk_from_child(struct device *dev, > */ > struct clk *clk_get_sys(const char *dev_id, const char *con_id); > > +/** > + * clks_save_context - save clock context for poweroff > + * > + * Saves the context of the clock register for powerstates in which the > + * contents of the registers will be lost. Occurs deep within the suspend > + * code so locking is not necessary. > + */ > +int clks_save_context(void); > + > +/** > + * clks_restore_context - restore clock context after poweroff > + * > + * This occurs with all clocks enabled. Occurs deep within the resume code > + * so locking is not necessary. > + */ > +void clks_restore_context(void); > + > #else /* !CONFIG_HAVE_CLK */ > > static inline struct clk *clk_get(struct device *dev, const char *id) > @@ -501,6 +518,14 @@ static inline struct clk *clk_get_sys(const char > *dev_id, const char *con_id) > { > return NULL; > } > + > +static inline int clks_save_context(void) > +{ > + return 0; > +} > + > +static inline void clks_restore_context(void) {} > + > #endif > > /* clk_prepare_enable helps cases using clk_enable in non-atomic context. */ > diff --git a/include/linux/clk/ti.h b/include/linux/clk/ti.h > index d18da83..f604936 100644 > --- a/include/linux/clk/ti.h > +++ b/include/linux/clk/ti.h > @@ -159,6 +159,7 @@ struct clk_hw_omap { > const char *clkdm_name; > struct clockdomain *clkdm; > const struct clk_hw_omap_ops *ops; > + u32 context; > }; > > /* > @@ -290,6 +291,11 @@ struct ti_clk_features { > > void ti_clk_setup_features(struct ti_clk_features *features); > const struct ti_clk_features *ti_clk_get_features(void); > +int omap3_noncore_dpll_save_context(struct clk_hw *hw); > +void omap3_noncore_dpll_restore_context(struct clk_hw *hw); > + > +int omap3_core_dpll_save_context(struct clk_hw *hw); > +void omap3_core_dpll_restore_context(struct clk_hw *hw); > > extern const struct clk_hw_omap_ops clkhwops_omap2xxx_dpll; > > -- > 1.9.1 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-clk" in > the body of a message to majord...@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html