From: Leonard Crestez <leonard.crestez@nxp.com> To: Stephen Boyd <sboyd@kernel.org>, Michael Turquette <mturquette@baylibre.com> Cc: Jacky Bai <ping.bai@nxp.com>, Alexandre Bailon <abailon@baylibre.com>, Anson Huang <Anson.Huang@nxp.com>, Abel Vesa <abel.vesa@nxp.com>, Shawn Guo <shawnguo@kernel.org>, Dong Aisheng <aisheng.dong@nxp.com>, Fabio Estevam <fabio.estevam@nxp.com>, kernel@pengutronix.de, linux-imx@nxp.com, linux-arm-kernel@lists.infradead.org, linux-clk@vger.kernel.org Subject: [RFC v3] clk: imx8mm: Add dram freq switch support Date: Thu, 18 Jul 2019 22:06:00 +0300 [thread overview] Message-ID: <d2ff5121bced3e5632ff246a51e1f56ee3fe03f9.1563476560.git.leonard.crestez@nxp.com> (raw) Add a compound clock encapsulating dram frequency switch support for imx8m chips. This allows higher-level DVFS code to manipulate dram frequency using standard clock framework APIs. Only some preparation is done inside the kernel, the actual freq switch is performed from TF-A code which runs from an SRAM area. After the freq is changed the rates and parents are refreshed on linux side. A "clk_hw_reinit_parent" function is added to deal with external reparenting. It's similar to CLK_GET_RATE_NOCACHE but for muxes (and needs to be called explicitly). Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com> --- Objections were raised to earlier hacky versions that this doesn't really belong inside clk, however if we need to "refresh" clk tree after a complex frequency switch then it makes sense to do it inside a clk provider rather than some other random driver. There are other clk implementations which internally wrap multiple clocks or deal with DDR or are implemented via SMC: imx_clk_cpu, tegra/clk-emc and rockchip/clk-ddr. Out-of-tree ATF patches are required, this branch can be used for testing: https://github.com/cdleonard/arm-trusted-firmware/commits/imx_2.0.y_caf_busfreq Firmware API could be adjusted to make this more palatable for inclusion; for example maybe info about new parents could be provided so that CLK can enable them in advance? In pratice they're always on. Also a linux branch with extra patches for testing: https://github.com/cdleonard/linux/commits/next_imx8mm_busfreq Changes since v2: * Remove IRQ handling (thanks Jacky for ATF patch) * Fetch supported rates from firmware instead of hardcoding imx8mm-evk. Should now work for all imx8m chips/board/ddr types * Add clk_hw_reinit_parent instead of explicit set_parent * Use fewer consumer APIs in provider * Explicitly mark dram_alt/apb with GET_RATE_NOCACHE Link to v2: https://patchwork.kernel.org/patch/11021565/ diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index b1c79a58d734..be9663b1e254 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -2388,10 +2388,35 @@ void clk_hw_reparent(struct clk_hw *hw, struct clk_hw *new_parent) return; clk_core_reparent(hw->core, !new_parent ? NULL : new_parent->core); } +/** + * clk_hw_reinit_parent - update clock tree after reparent outside framework + * @clk: clock source + * @parent: parent clock source + * + * This function should be used after a clock is reparented externally (for + * example with a firmware call or some ASM sequence). + * + * It will call clk_ops.get_parent again and reassign parents. + */ +void clk_hw_reinit_parent(struct clk_hw *hw) +{ + struct clk_core *new_parent, *old_parent; + + lockdep_assert_held(&prepare_lock); + if (!hw) + return; + + new_parent = __clk_init_parent(hw->core); + old_parent = __clk_set_parent_before(hw->core, new_parent); + clk_core_reparent(hw->core, new_parent); + __clk_set_parent_after(hw->core, new_parent, old_parent); +} +EXPORT_SYMBOL_GPL(clk_hw_reinit_parent); + /** * clk_has_parent - check if a clock is a possible parent for another * @clk: clock source * @parent: parent clock source * diff --git a/drivers/clk/imx/Makefile b/drivers/clk/imx/Makefile index 05641c64b317..0fc7195d6d3a 100644 --- a/drivers/clk/imx/Makefile +++ b/drivers/clk/imx/Makefile @@ -10,10 +10,11 @@ obj-$(CONFIG_MXC_CLK) += \ clk-fixup-div.o \ clk-fixup-mux.o \ clk-frac-pll.o \ clk-gate-exclusive.o \ clk-gate2.o \ + clk-imx8m-dram.o \ clk-pfd.o \ clk-pfdv2.o \ clk-pllv1.o \ clk-pllv2.o \ clk-pllv3.o \ diff --git a/drivers/clk/imx/clk-imx8m-dram.c b/drivers/clk/imx/clk-imx8m-dram.c new file mode 100644 index 000000000000..d6971fe72cbe --- /dev/null +++ b/drivers/clk/imx/clk-imx8m-dram.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 NXP + */ + +#include <linux/arm-smccc.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include "clk.h" + +#define IMX_SIP_DDR_DVFS 0xc2000004 + +/* Values starting from 0 switch to specific frequency */ +#define IMX_SIP_DDR_FREQ_SET_HIGH 0x00 + +/* Deprecated after moving IRQ handling to ATF */ +#define IMX_SIP_DDR_DVFS_WAIT_CHANGE 0x0F + +/* Query available frequencies. */ +#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT 0x10 +#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO 0x11 + +/* Hardware limitation */ +#define IMX8M_DRAM_MAX_OPP 4 + +struct imx8m_dram_opp { + unsigned long rate; + unsigned int smcarg; +}; + +/* + * This clk wraps the following structure (abridged): + * + * +----------+ |\ +------+ + * | dram_pll |-------|M| dram_core | | + * +----------+ |U|---------->| D | + * /--|X| | D | + * dram_alt_root | |/ | R | + * | | C | + * +---------+ | | + * |FIX DIV/4| | | + * +---------+ | | + * composite: | | | + * +----------+ | | | + * | dram_alt |----/ | | + * +----------+ | | + * | dram_apb |-------------------->| | + * +----------+ +------+ + * + * The DDR data rate is 4x dram_core + * + * The APB interface is only used for control registers and can otherwise + * be shut off. + * + * The dram_pll is used for higher rates and dram_alt is used for lower rates. + */ +struct dram_clk { + struct clk_hw hw; + struct clk_hw *dram_core; + struct clk_hw *dram_apb; + struct clk_hw *dram_pll; + struct clk_hw *dram_alt; + struct clk_hw *dram_alt_root; + + unsigned int opp_count; + struct imx8m_dram_opp table[IMX8M_DRAM_MAX_OPP]; +}; + +static inline struct dram_clk *to_dram_clk(struct clk_hw *hw) +{ + return container_of(hw, struct dram_clk, hw); +} + +static int update_bus_freq(int target_freq) +{ + struct arm_smccc_res res; + u32 online_cpus = 0; + int cpu = 0; + + local_irq_disable(); + + for_each_online_cpu(cpu) + online_cpus |= (1 << (cpu * 8)); + + /* change the ddr freqency */ + arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus, + 0, 0, 0, 0, 0, &res); + + local_irq_enable(); + + return 0; +} + +/* Round UP */ +static struct imx8m_dram_opp *dram_clk_find_rate( + struct dram_clk *priv, + unsigned long rate) +{ + int i; + + for (i = priv->opp_count - 1; i >= 0; --i) + if (priv->table[i].rate >= rate) + return &priv->table[i]; + + return &priv->table[0]; +} + +/* Round UP taking min and max into account */ +static int dram_clk_determine_rate( + struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct dram_clk *priv = to_dram_clk(hw); + unsigned long tab_rate; + int i; + + for (i = priv->opp_count - 1; i >= 0; --i) { + tab_rate = priv->table[i].rate; + if (tab_rate >= req->rate && + tab_rate >= req->min_rate && + tab_rate <= req->max_rate) + { + req->rate = tab_rate; + return 0; + } + } + + return -EINVAL; +} + +static int dram_clk_set_rate( + struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct dram_clk *priv = to_dram_clk(hw); + struct imx8m_dram_opp *opp = dram_clk_find_rate(priv, rate); + int ret; + + /* + * The actual switch is done inside ATF, here just reload parents. + * all we do here is reload parents + */ + clk_prepare_enable(priv->dram_alt_root->clk); + clk_prepare_enable(priv->dram_pll->clk); + ret = update_bus_freq(opp->smcarg); + clk_hw_reinit_parent(priv->dram_alt); + clk_hw_reinit_parent(priv->dram_apb); + clk_hw_reinit_parent(priv->dram_core); + clk_disable_unprepare(priv->dram_alt_root->clk); + clk_disable_unprepare(priv->dram_pll->clk); + + if (ret == 0) + pr_debug("%s freq set to %lu\n", clk_hw_get_name(hw), opp->rate); + else + pr_err("%s freq set fail: %d\n", clk_hw_get_name(hw), ret); + + return ret; +} + +static unsigned long dram_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct dram_clk *priv = to_dram_clk(hw); + + return clk_hw_get_rate(priv->dram_core); +} + +static const struct clk_ops dram_clk_ops = { + .determine_rate = dram_clk_determine_rate, + .recalc_rate = dram_clk_recalc_rate, + .set_rate = dram_clk_set_rate, +}; + +struct clk* imx8m_dram_clk( + const char *name, const char* parent_name, + struct clk_hw* dram_core, + struct clk_hw* dram_apb, + struct clk_hw* dram_pll, + struct clk_hw* dram_alt, + struct clk_hw* dram_alt_root) +{ + struct arm_smccc_res res; + struct dram_clk *priv; + struct clk *clk; + struct clk_init_data init; + int opp_count, index; + int err; + + /* + * Count available frequencies + * An error here means DDR DVFS not supported by firmware + */ + arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT, + 0, 0, 0, 0, 0, 0, &res); + opp_count = res.a0; + if (opp_count <= 0 || opp_count > IMX8M_DRAM_MAX_OPP) + return ERR_PTR(-ENOSYS); + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return ERR_PTR(-ENOMEM); + + priv->dram_apb = dram_apb; + priv->dram_core = dram_core; + priv->dram_pll = dram_pll; + priv->dram_alt = dram_alt; + priv->dram_alt_root = dram_alt_root; + + priv->opp_count = opp_count; + for (index = 0; index < opp_count; ++index) { + arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO, + index, 0, 0, 0, 0, 0, &res); + /* Results should be strictly positive */ + if ((long)res.a0 <= 0) { + err = -ENOSYS; + goto err_free_priv; + } + priv->table[index].smcarg = index; + priv->table[index].rate = res.a0 * 250000; + } + + init.name = name; + init.ops = &dram_clk_ops; + init.flags = CLK_IS_CRITICAL; + init.parent_names = &parent_name; + init.num_parents = 1; + + priv->hw.init = &init; + clk = clk_register(NULL, &priv->hw); + if (IS_ERR(clk)) { + err = PTR_ERR(clk); + goto err_free_priv; + } + return clk; + +err_free_priv: + kfree(priv); + return ERR_PTR(err); +} diff --git a/drivers/clk/imx/clk-imx8mm.c b/drivers/clk/imx/clk-imx8mm.c index 6b8e75df994d..e37442a12fed 100644 --- a/drivers/clk/imx/clk-imx8mm.c +++ b/drivers/clk/imx/clk-imx8mm.c @@ -523,12 +523,14 @@ static int __init imx8mm_clocks_init(struct device_node *ccm_node) /* IPG */ clks[IMX8MM_CLK_IPG_ROOT] = imx_clk_divider2("ipg_root", "ahb", base + 0x9080, 0, 1); clks[IMX8MM_CLK_IPG_AUDIO_ROOT] = imx_clk_divider2("ipg_audio_root", "audio_ahb", base + 0x9180, 0, 1); /* IP */ - clks[IMX8MM_CLK_DRAM_ALT] = imx8m_clk_composite("dram_alt", imx8mm_dram_alt_sels, base + 0xa000); - clks[IMX8MM_CLK_DRAM_APB] = imx8m_clk_composite_critical("dram_apb", imx8mm_dram_apb_sels, base + 0xa080); + clks[IMX8MM_CLK_DRAM_ALT] = __imx8m_clk_composite("dram_alt", imx8mm_dram_alt_sels, base + 0xa000, + CLK_GET_RATE_NOCACHE); + clks[IMX8MM_CLK_DRAM_APB] = __imx8m_clk_composite("dram_apb", imx8mm_dram_apb_sels, base + 0xa080, + CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL); clks[IMX8MM_CLK_VPU_G1] = imx8m_clk_composite("vpu_g1", imx8mm_vpu_g1_sels, base + 0xa100); clks[IMX8MM_CLK_VPU_G2] = imx8m_clk_composite("vpu_g2", imx8mm_vpu_g2_sels, base + 0xa180); clks[IMX8MM_CLK_DISP_DTRC] = imx8m_clk_composite("disp_dtrc", imx8mm_disp_dtrc_sels, base + 0xa200); clks[IMX8MM_CLK_DISP_DC8000] = imx8m_clk_composite("disp_dc8000", imx8mm_disp_dc8000_sels, base + 0xa280); clks[IMX8MM_CLK_PCIE1_CTRL] = imx8m_clk_composite("pcie1_ctrl", imx8mm_pcie1_ctrl_sels, base + 0xa300); @@ -660,10 +662,18 @@ static int __init imx8mm_clocks_init(struct device_node *ccm_node) clks[IMX8MM_CLK_GPT_3M] = imx_clk_fixed_factor("gpt_3m", "osc_24m", 1, 8); clks[IMX8MM_CLK_DRAM_ALT_ROOT] = imx_clk_fixed_factor("dram_alt_root", "dram_alt", 1, 4); clks[IMX8MM_CLK_DRAM_CORE] = imx_clk_mux2_flags("dram_core_clk", base + 0x9800, 24, 1, imx8mm_dram_core_sels, ARRAY_SIZE(imx8mm_dram_core_sels), CLK_IS_CRITICAL); + clks[IMX8MM_CLK_DRAM] = imx8m_dram_clk( + "dram", "dram_core_clk", + __clk_get_hw(clks[IMX8MM_CLK_DRAM_CORE]), + __clk_get_hw(clks[IMX8MM_CLK_DRAM_APB]), + __clk_get_hw(clks[IMX8MM_DRAM_PLL_OUT]), + __clk_get_hw(clks[IMX8MM_CLK_DRAM_ALT]), + __clk_get_hw(clks[IMX8MM_CLK_DRAM_ALT_ROOT])); + clks[IMX8MM_CLK_ARM] = imx_clk_cpu("arm", "arm_a53_div", clks[IMX8MM_CLK_A53_DIV], clks[IMX8MM_CLK_A53_SRC], clks[IMX8MM_ARM_PLL_OUT], clks[IMX8MM_CLK_24M]); diff --git a/drivers/clk/imx/clk.h b/drivers/clk/imx/clk.h index d94d9cb079d3..f0f42b3a5d8d 100644 --- a/drivers/clk/imx/clk.h +++ b/drivers/clk/imx/clk.h @@ -468,6 +468,15 @@ struct clk *imx8m_clk_composite_flags(const char *name, struct clk_hw *imx_clk_divider_gate(const char *name, const char *parent_name, unsigned long flags, void __iomem *reg, u8 shift, u8 width, u8 clk_divider_flags, const struct clk_div_table *table, spinlock_t *lock); + +struct clk* imx8m_dram_clk( + const char *name, const char* parent_name, + struct clk_hw* dram_core, + struct clk_hw* dram_apb, + struct clk_hw* dram_pll, + struct clk_hw* dram_alt, + struct clk_hw* dram_alt_root); + #endif diff --git a/include/dt-bindings/clock/imx8mm-clock.h b/include/dt-bindings/clock/imx8mm-clock.h index 07e6c686f3ef..dde146b923a8 100644 --- a/include/dt-bindings/clock/imx8mm-clock.h +++ b/include/dt-bindings/clock/imx8mm-clock.h @@ -246,8 +246,10 @@ #define IMX8MM_CLK_GPIO5_ROOT 227 #define IMX8MM_CLK_SNVS_ROOT 228 #define IMX8MM_CLK_GIC 229 -#define IMX8MM_CLK_END 230 +#define IMX8MM_CLK_DRAM 230 + +#define IMX8MM_CLK_END 231 #endif diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 2ae7604783dd..f85f1fb8621b 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -836,10 +836,11 @@ int __clk_mux_determine_rate_closest(struct clk_hw *hw, struct clk_rate_request *req); int clk_mux_determine_rate_flags(struct clk_hw *hw, struct clk_rate_request *req, unsigned long flags); void clk_hw_reparent(struct clk_hw *hw, struct clk_hw *new_parent); +void clk_hw_reinit_parent(struct clk_hw *hw); void clk_hw_set_rate_range(struct clk_hw *hw, unsigned long min_rate, unsigned long max_rate); static inline void __clk_hw_set_clk(struct clk_hw *dst, struct clk_hw *src) { -- 2.17.1
WARNING: multiple messages have this Message-ID (diff)
From: Leonard Crestez <leonard.crestez@nxp.com> To: Stephen Boyd <sboyd@kernel.org>, Michael Turquette <mturquette@baylibre.com> Cc: Dong Aisheng <aisheng.dong@nxp.com>, Abel Vesa <abel.vesa@nxp.com>, Anson Huang <Anson.Huang@nxp.com>, linux-imx@nxp.com, Alexandre Bailon <abailon@baylibre.com>, kernel@pengutronix.de, Fabio Estevam <fabio.estevam@nxp.com>, Shawn Guo <shawnguo@kernel.org>, linux-clk@vger.kernel.org, linux-arm-kernel@lists.infradead.org, Jacky Bai <ping.bai@nxp.com> Subject: [RFC v3] clk: imx8mm: Add dram freq switch support Date: Thu, 18 Jul 2019 22:06:00 +0300 [thread overview] Message-ID: <d2ff5121bced3e5632ff246a51e1f56ee3fe03f9.1563476560.git.leonard.crestez@nxp.com> (raw) Add a compound clock encapsulating dram frequency switch support for imx8m chips. This allows higher-level DVFS code to manipulate dram frequency using standard clock framework APIs. Only some preparation is done inside the kernel, the actual freq switch is performed from TF-A code which runs from an SRAM area. After the freq is changed the rates and parents are refreshed on linux side. A "clk_hw_reinit_parent" function is added to deal with external reparenting. It's similar to CLK_GET_RATE_NOCACHE but for muxes (and needs to be called explicitly). Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com> --- Objections were raised to earlier hacky versions that this doesn't really belong inside clk, however if we need to "refresh" clk tree after a complex frequency switch then it makes sense to do it inside a clk provider rather than some other random driver. There are other clk implementations which internally wrap multiple clocks or deal with DDR or are implemented via SMC: imx_clk_cpu, tegra/clk-emc and rockchip/clk-ddr. Out-of-tree ATF patches are required, this branch can be used for testing: https://github.com/cdleonard/arm-trusted-firmware/commits/imx_2.0.y_caf_busfreq Firmware API could be adjusted to make this more palatable for inclusion; for example maybe info about new parents could be provided so that CLK can enable them in advance? In pratice they're always on. Also a linux branch with extra patches for testing: https://github.com/cdleonard/linux/commits/next_imx8mm_busfreq Changes since v2: * Remove IRQ handling (thanks Jacky for ATF patch) * Fetch supported rates from firmware instead of hardcoding imx8mm-evk. Should now work for all imx8m chips/board/ddr types * Add clk_hw_reinit_parent instead of explicit set_parent * Use fewer consumer APIs in provider * Explicitly mark dram_alt/apb with GET_RATE_NOCACHE Link to v2: https://patchwork.kernel.org/patch/11021565/ diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index b1c79a58d734..be9663b1e254 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -2388,10 +2388,35 @@ void clk_hw_reparent(struct clk_hw *hw, struct clk_hw *new_parent) return; clk_core_reparent(hw->core, !new_parent ? NULL : new_parent->core); } +/** + * clk_hw_reinit_parent - update clock tree after reparent outside framework + * @clk: clock source + * @parent: parent clock source + * + * This function should be used after a clock is reparented externally (for + * example with a firmware call or some ASM sequence). + * + * It will call clk_ops.get_parent again and reassign parents. + */ +void clk_hw_reinit_parent(struct clk_hw *hw) +{ + struct clk_core *new_parent, *old_parent; + + lockdep_assert_held(&prepare_lock); + if (!hw) + return; + + new_parent = __clk_init_parent(hw->core); + old_parent = __clk_set_parent_before(hw->core, new_parent); + clk_core_reparent(hw->core, new_parent); + __clk_set_parent_after(hw->core, new_parent, old_parent); +} +EXPORT_SYMBOL_GPL(clk_hw_reinit_parent); + /** * clk_has_parent - check if a clock is a possible parent for another * @clk: clock source * @parent: parent clock source * diff --git a/drivers/clk/imx/Makefile b/drivers/clk/imx/Makefile index 05641c64b317..0fc7195d6d3a 100644 --- a/drivers/clk/imx/Makefile +++ b/drivers/clk/imx/Makefile @@ -10,10 +10,11 @@ obj-$(CONFIG_MXC_CLK) += \ clk-fixup-div.o \ clk-fixup-mux.o \ clk-frac-pll.o \ clk-gate-exclusive.o \ clk-gate2.o \ + clk-imx8m-dram.o \ clk-pfd.o \ clk-pfdv2.o \ clk-pllv1.o \ clk-pllv2.o \ clk-pllv3.o \ diff --git a/drivers/clk/imx/clk-imx8m-dram.c b/drivers/clk/imx/clk-imx8m-dram.c new file mode 100644 index 000000000000..d6971fe72cbe --- /dev/null +++ b/drivers/clk/imx/clk-imx8m-dram.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 NXP + */ + +#include <linux/arm-smccc.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include "clk.h" + +#define IMX_SIP_DDR_DVFS 0xc2000004 + +/* Values starting from 0 switch to specific frequency */ +#define IMX_SIP_DDR_FREQ_SET_HIGH 0x00 + +/* Deprecated after moving IRQ handling to ATF */ +#define IMX_SIP_DDR_DVFS_WAIT_CHANGE 0x0F + +/* Query available frequencies. */ +#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT 0x10 +#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO 0x11 + +/* Hardware limitation */ +#define IMX8M_DRAM_MAX_OPP 4 + +struct imx8m_dram_opp { + unsigned long rate; + unsigned int smcarg; +}; + +/* + * This clk wraps the following structure (abridged): + * + * +----------+ |\ +------+ + * | dram_pll |-------|M| dram_core | | + * +----------+ |U|---------->| D | + * /--|X| | D | + * dram_alt_root | |/ | R | + * | | C | + * +---------+ | | + * |FIX DIV/4| | | + * +---------+ | | + * composite: | | | + * +----------+ | | | + * | dram_alt |----/ | | + * +----------+ | | + * | dram_apb |-------------------->| | + * +----------+ +------+ + * + * The DDR data rate is 4x dram_core + * + * The APB interface is only used for control registers and can otherwise + * be shut off. + * + * The dram_pll is used for higher rates and dram_alt is used for lower rates. + */ +struct dram_clk { + struct clk_hw hw; + struct clk_hw *dram_core; + struct clk_hw *dram_apb; + struct clk_hw *dram_pll; + struct clk_hw *dram_alt; + struct clk_hw *dram_alt_root; + + unsigned int opp_count; + struct imx8m_dram_opp table[IMX8M_DRAM_MAX_OPP]; +}; + +static inline struct dram_clk *to_dram_clk(struct clk_hw *hw) +{ + return container_of(hw, struct dram_clk, hw); +} + +static int update_bus_freq(int target_freq) +{ + struct arm_smccc_res res; + u32 online_cpus = 0; + int cpu = 0; + + local_irq_disable(); + + for_each_online_cpu(cpu) + online_cpus |= (1 << (cpu * 8)); + + /* change the ddr freqency */ + arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus, + 0, 0, 0, 0, 0, &res); + + local_irq_enable(); + + return 0; +} + +/* Round UP */ +static struct imx8m_dram_opp *dram_clk_find_rate( + struct dram_clk *priv, + unsigned long rate) +{ + int i; + + for (i = priv->opp_count - 1; i >= 0; --i) + if (priv->table[i].rate >= rate) + return &priv->table[i]; + + return &priv->table[0]; +} + +/* Round UP taking min and max into account */ +static int dram_clk_determine_rate( + struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct dram_clk *priv = to_dram_clk(hw); + unsigned long tab_rate; + int i; + + for (i = priv->opp_count - 1; i >= 0; --i) { + tab_rate = priv->table[i].rate; + if (tab_rate >= req->rate && + tab_rate >= req->min_rate && + tab_rate <= req->max_rate) + { + req->rate = tab_rate; + return 0; + } + } + + return -EINVAL; +} + +static int dram_clk_set_rate( + struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct dram_clk *priv = to_dram_clk(hw); + struct imx8m_dram_opp *opp = dram_clk_find_rate(priv, rate); + int ret; + + /* + * The actual switch is done inside ATF, here just reload parents. + * all we do here is reload parents + */ + clk_prepare_enable(priv->dram_alt_root->clk); + clk_prepare_enable(priv->dram_pll->clk); + ret = update_bus_freq(opp->smcarg); + clk_hw_reinit_parent(priv->dram_alt); + clk_hw_reinit_parent(priv->dram_apb); + clk_hw_reinit_parent(priv->dram_core); + clk_disable_unprepare(priv->dram_alt_root->clk); + clk_disable_unprepare(priv->dram_pll->clk); + + if (ret == 0) + pr_debug("%s freq set to %lu\n", clk_hw_get_name(hw), opp->rate); + else + pr_err("%s freq set fail: %d\n", clk_hw_get_name(hw), ret); + + return ret; +} + +static unsigned long dram_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct dram_clk *priv = to_dram_clk(hw); + + return clk_hw_get_rate(priv->dram_core); +} + +static const struct clk_ops dram_clk_ops = { + .determine_rate = dram_clk_determine_rate, + .recalc_rate = dram_clk_recalc_rate, + .set_rate = dram_clk_set_rate, +}; + +struct clk* imx8m_dram_clk( + const char *name, const char* parent_name, + struct clk_hw* dram_core, + struct clk_hw* dram_apb, + struct clk_hw* dram_pll, + struct clk_hw* dram_alt, + struct clk_hw* dram_alt_root) +{ + struct arm_smccc_res res; + struct dram_clk *priv; + struct clk *clk; + struct clk_init_data init; + int opp_count, index; + int err; + + /* + * Count available frequencies + * An error here means DDR DVFS not supported by firmware + */ + arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT, + 0, 0, 0, 0, 0, 0, &res); + opp_count = res.a0; + if (opp_count <= 0 || opp_count > IMX8M_DRAM_MAX_OPP) + return ERR_PTR(-ENOSYS); + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return ERR_PTR(-ENOMEM); + + priv->dram_apb = dram_apb; + priv->dram_core = dram_core; + priv->dram_pll = dram_pll; + priv->dram_alt = dram_alt; + priv->dram_alt_root = dram_alt_root; + + priv->opp_count = opp_count; + for (index = 0; index < opp_count; ++index) { + arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO, + index, 0, 0, 0, 0, 0, &res); + /* Results should be strictly positive */ + if ((long)res.a0 <= 0) { + err = -ENOSYS; + goto err_free_priv; + } + priv->table[index].smcarg = index; + priv->table[index].rate = res.a0 * 250000; + } + + init.name = name; + init.ops = &dram_clk_ops; + init.flags = CLK_IS_CRITICAL; + init.parent_names = &parent_name; + init.num_parents = 1; + + priv->hw.init = &init; + clk = clk_register(NULL, &priv->hw); + if (IS_ERR(clk)) { + err = PTR_ERR(clk); + goto err_free_priv; + } + return clk; + +err_free_priv: + kfree(priv); + return ERR_PTR(err); +} diff --git a/drivers/clk/imx/clk-imx8mm.c b/drivers/clk/imx/clk-imx8mm.c index 6b8e75df994d..e37442a12fed 100644 --- a/drivers/clk/imx/clk-imx8mm.c +++ b/drivers/clk/imx/clk-imx8mm.c @@ -523,12 +523,14 @@ static int __init imx8mm_clocks_init(struct device_node *ccm_node) /* IPG */ clks[IMX8MM_CLK_IPG_ROOT] = imx_clk_divider2("ipg_root", "ahb", base + 0x9080, 0, 1); clks[IMX8MM_CLK_IPG_AUDIO_ROOT] = imx_clk_divider2("ipg_audio_root", "audio_ahb", base + 0x9180, 0, 1); /* IP */ - clks[IMX8MM_CLK_DRAM_ALT] = imx8m_clk_composite("dram_alt", imx8mm_dram_alt_sels, base + 0xa000); - clks[IMX8MM_CLK_DRAM_APB] = imx8m_clk_composite_critical("dram_apb", imx8mm_dram_apb_sels, base + 0xa080); + clks[IMX8MM_CLK_DRAM_ALT] = __imx8m_clk_composite("dram_alt", imx8mm_dram_alt_sels, base + 0xa000, + CLK_GET_RATE_NOCACHE); + clks[IMX8MM_CLK_DRAM_APB] = __imx8m_clk_composite("dram_apb", imx8mm_dram_apb_sels, base + 0xa080, + CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL); clks[IMX8MM_CLK_VPU_G1] = imx8m_clk_composite("vpu_g1", imx8mm_vpu_g1_sels, base + 0xa100); clks[IMX8MM_CLK_VPU_G2] = imx8m_clk_composite("vpu_g2", imx8mm_vpu_g2_sels, base + 0xa180); clks[IMX8MM_CLK_DISP_DTRC] = imx8m_clk_composite("disp_dtrc", imx8mm_disp_dtrc_sels, base + 0xa200); clks[IMX8MM_CLK_DISP_DC8000] = imx8m_clk_composite("disp_dc8000", imx8mm_disp_dc8000_sels, base + 0xa280); clks[IMX8MM_CLK_PCIE1_CTRL] = imx8m_clk_composite("pcie1_ctrl", imx8mm_pcie1_ctrl_sels, base + 0xa300); @@ -660,10 +662,18 @@ static int __init imx8mm_clocks_init(struct device_node *ccm_node) clks[IMX8MM_CLK_GPT_3M] = imx_clk_fixed_factor("gpt_3m", "osc_24m", 1, 8); clks[IMX8MM_CLK_DRAM_ALT_ROOT] = imx_clk_fixed_factor("dram_alt_root", "dram_alt", 1, 4); clks[IMX8MM_CLK_DRAM_CORE] = imx_clk_mux2_flags("dram_core_clk", base + 0x9800, 24, 1, imx8mm_dram_core_sels, ARRAY_SIZE(imx8mm_dram_core_sels), CLK_IS_CRITICAL); + clks[IMX8MM_CLK_DRAM] = imx8m_dram_clk( + "dram", "dram_core_clk", + __clk_get_hw(clks[IMX8MM_CLK_DRAM_CORE]), + __clk_get_hw(clks[IMX8MM_CLK_DRAM_APB]), + __clk_get_hw(clks[IMX8MM_DRAM_PLL_OUT]), + __clk_get_hw(clks[IMX8MM_CLK_DRAM_ALT]), + __clk_get_hw(clks[IMX8MM_CLK_DRAM_ALT_ROOT])); + clks[IMX8MM_CLK_ARM] = imx_clk_cpu("arm", "arm_a53_div", clks[IMX8MM_CLK_A53_DIV], clks[IMX8MM_CLK_A53_SRC], clks[IMX8MM_ARM_PLL_OUT], clks[IMX8MM_CLK_24M]); diff --git a/drivers/clk/imx/clk.h b/drivers/clk/imx/clk.h index d94d9cb079d3..f0f42b3a5d8d 100644 --- a/drivers/clk/imx/clk.h +++ b/drivers/clk/imx/clk.h @@ -468,6 +468,15 @@ struct clk *imx8m_clk_composite_flags(const char *name, struct clk_hw *imx_clk_divider_gate(const char *name, const char *parent_name, unsigned long flags, void __iomem *reg, u8 shift, u8 width, u8 clk_divider_flags, const struct clk_div_table *table, spinlock_t *lock); + +struct clk* imx8m_dram_clk( + const char *name, const char* parent_name, + struct clk_hw* dram_core, + struct clk_hw* dram_apb, + struct clk_hw* dram_pll, + struct clk_hw* dram_alt, + struct clk_hw* dram_alt_root); + #endif diff --git a/include/dt-bindings/clock/imx8mm-clock.h b/include/dt-bindings/clock/imx8mm-clock.h index 07e6c686f3ef..dde146b923a8 100644 --- a/include/dt-bindings/clock/imx8mm-clock.h +++ b/include/dt-bindings/clock/imx8mm-clock.h @@ -246,8 +246,10 @@ #define IMX8MM_CLK_GPIO5_ROOT 227 #define IMX8MM_CLK_SNVS_ROOT 228 #define IMX8MM_CLK_GIC 229 -#define IMX8MM_CLK_END 230 +#define IMX8MM_CLK_DRAM 230 + +#define IMX8MM_CLK_END 231 #endif diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 2ae7604783dd..f85f1fb8621b 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -836,10 +836,11 @@ int __clk_mux_determine_rate_closest(struct clk_hw *hw, struct clk_rate_request *req); int clk_mux_determine_rate_flags(struct clk_hw *hw, struct clk_rate_request *req, unsigned long flags); void clk_hw_reparent(struct clk_hw *hw, struct clk_hw *new_parent); +void clk_hw_reinit_parent(struct clk_hw *hw); void clk_hw_set_rate_range(struct clk_hw *hw, unsigned long min_rate, unsigned long max_rate); static inline void __clk_hw_set_clk(struct clk_hw *dst, struct clk_hw *src) { -- 2.17.1 _______________________________________________ linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
next reply other threads:[~2019-07-18 19:06 UTC|newest] Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top 2019-07-18 19:06 Leonard Crestez [this message] 2019-07-18 19:06 ` [RFC v3] clk: imx8mm: Add dram freq switch support Leonard Crestez
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=d2ff5121bced3e5632ff246a51e1f56ee3fe03f9.1563476560.git.leonard.crestez@nxp.com \ --to=leonard.crestez@nxp.com \ --cc=Anson.Huang@nxp.com \ --cc=abailon@baylibre.com \ --cc=abel.vesa@nxp.com \ --cc=aisheng.dong@nxp.com \ --cc=fabio.estevam@nxp.com \ --cc=kernel@pengutronix.de \ --cc=linux-arm-kernel@lists.infradead.org \ --cc=linux-clk@vger.kernel.org \ --cc=linux-imx@nxp.com \ --cc=mturquette@baylibre.com \ --cc=ping.bai@nxp.com \ --cc=sboyd@kernel.org \ --cc=shawnguo@kernel.org \ /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: linkBe 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.