On Thursday 04 May 2017 01:47 PM, Tero Kristo wrote: > On 04/05/17 10:51, Peter De Schrijver wrote: >> On Tue, Apr 18, 2017 at 10:42:49AM +0530, Keerthy wrote: >>> From: Russ Dill <Russ.Dill@xxxxxx> >>> >>> 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. > > Do you have your own implementation in place? Is it possible to modify > the base code here to add your requirements (drivers/clk/clk.c)? > > I guess we could also just add clk_for_each() type of call that would > allow us to iterate over clock hierarchy. Any patch links to your implementation could be very helpful. > > -Tero > >> >> >>> Signed-off-by: Russ Dill <Russ.Dill@xxxxxx> >>> Signed-off-by: Keerthy <j-keerthy@xxxxxx> >>> --- >>> 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 majordomo@xxxxxxxxxxxxxxx >>> More majordomo info at http://vger.kernel.org/majordomo-info.html > -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html