[RFCv2 1/8] clk: imx8mm: Add dram freq switch support
From: Leonard Crestez <hidden>
Date: 2019-06-28 07:40:05
Also in:
linux-clk, linux-pm
Subsystem:
common clk framework, nxp i.mx clock drivers, open firmware and flattened device tree bindings, the rest · Maintainers:
Michael Turquette, Stephen Boyd, Abel Vesa, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Linus Torvalds
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. Linux-side implementation is similar in principle to imx_clk_cpu or a composite clock. Only some preparation is done inside the kernel, the actual freq switch is performed from TF-A code which runs from an SRAM area. This is an early proof-of-concept which only support low/high mode on imx8mm. Signed-off-by: Leonard Crestez <redacted> --- Changes since v1: * Implement determin_rate so that consumers can set_min_rate * Initial freq table Link to v1: Among the possible cleanups: * Handle errors in low/high busfreq code and back off * Move irq to secure world * Try to use fewer clk parameters * Use a table of frequencies * More chips and frequencies --- drivers/clk/imx/Makefile | 1 + drivers/clk/imx/clk-imx8m-dram.c | 357 +++++++++++++++++++++++ drivers/clk/imx/clk-imx8mm.c | 12 + drivers/clk/imx/clk.h | 13 + include/dt-bindings/clock/imx8mm-clock.h | 4 +- 5 files changed, 386 insertions(+), 1 deletion(-) create mode 100644 drivers/clk/imx/clk-imx8m-dram.c
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..1d75be4a2f3a
--- /dev/null
+++ b/drivers/clk/imx/clk-imx8m-dram.c@@ -0,0 +1,357 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 NXP + */ + +#define DEBUG +#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 <linux/of_irq.h> +#include "clk.h" + +#define FSL_SIP_DDR_DVFS 0xc2000004 + +/* Freq of setpoints is soc-dependent */ +#define FSL_SIP_DDR_FREQ_SET_HIGH 0x0 +#define FSL_SIP_DDR_FREQ_WAIT_DONE 0xf + +struct imx8m_dram_rate { + unsigned long rate; + unsigned int smcarg; +}; + +/* + * This clk roughly wraps the following clk structure: + * + * +----------+ |\ +------+ + * | 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. + * + * The actual switch is done inside ATF, what this wrapper does is: + * - Enable the new parents + * - Call into ATF + * - Set the new rates + * - Set the new parents + * - Drop the reference count added to new parents at step 1 + * + * In practice only 2 rates are supported: low and high. + */ + +struct dram_clk { + struct clk_hw hw; + struct clk *dram_core; + struct clk *dram_apb; + struct clk *dram_pll; + struct clk *dram_alt; + struct clk *dram_alt_root; + struct clk *sys1_pll_40m; + struct clk *sys1_pll_100m; + struct clk *sys1_pll_800m; + int irqs[CONFIG_NR_CPUS]; + + unsigned int rate_count; + const struct imx8m_dram_rate *rate_table; +}; + +static inline struct dram_clk *to_dram_clk(struct clk_hw *hw) +{ + return container_of(hw, struct dram_clk, hw); +} + +static irqreturn_t wait_in_wfe_irq(int irq, void *dev_id) +{ + struct arm_smccc_res res; + + /* call smc trap to ATF */ + arm_smccc_smc(FSL_SIP_DDR_DVFS, FSL_SIP_DDR_FREQ_WAIT_DONE, 0, + 0, 0, 0, 0, 0, &res); + + return IRQ_HANDLED; +} + +static void 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(FSL_SIP_DDR_DVFS, target_freq, online_cpus, + 0, 0, 0, 0, 0, &res); + + local_irq_enable(); +} + +static int dram_clk_ensure_irq_affinity(struct dram_clk* priv) +{ + int err, cpu; + + for_each_online_cpu(cpu) { + err = irq_set_affinity(priv->irqs[cpu], cpumask_of(cpu)); + if (err) { + pr_err("imx8m_dram_clk set irqs[%d] affinity failed: %d\n", + cpu, err); + return err; + } + } + + return 0; +} + +/* Round UP */ +static const struct imx8m_dram_rate *dram_clk_find_rate( + struct dram_clk *priv, + unsigned long rate) +{ + int i; + + for (i = priv->rate_count - 1; i >= 0; --i) + if (priv->rate_table[i].rate >= rate) + return &priv->rate_table[i]; + + return &priv->rate_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->rate_count - 1; i >= 0; --i) { + tab_rate = priv->rate_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_low(struct dram_clk *priv) +{ + clk_prepare_enable(priv->sys1_pll_40m); + clk_prepare_enable(priv->dram_alt_root); + clk_prepare_enable(priv->sys1_pll_100m); + + /* switch the DDR frequency */ + update_bus_freq(0x2); + + /* correct the clock tree info */ + clk_set_parent(priv->dram_alt, priv->sys1_pll_100m); + clk_set_parent(priv->dram_core, priv->dram_alt_root); + clk_set_parent(priv->dram_apb, priv->sys1_pll_40m); + clk_set_rate(priv->dram_apb, 20000000); + clk_disable_unprepare(priv->sys1_pll_100m); + clk_disable_unprepare(priv->sys1_pll_40m); + clk_disable_unprepare(priv->dram_alt_root); + return 0; +} + +static int dram_clk_set_high(struct dram_clk *priv) +{ + clk_prepare_enable(priv->sys1_pll_800m); + clk_prepare_enable(priv->dram_pll); + + /* switch the DDR frequency */ + update_bus_freq(0x0); + + /* correct the clock tree info */ + clk_set_parent(priv->dram_apb, priv->sys1_pll_800m); + clk_set_rate(priv->dram_apb, 160000000); + clk_set_parent(priv->dram_core, priv->dram_pll); + clk_disable_unprepare(priv->sys1_pll_800m); + clk_disable_unprepare(priv->dram_pll); + + return 0; +} + +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); + const struct imx8m_dram_rate *opp = dram_clk_find_rate(priv, rate); + int ret; + + ret = dram_clk_ensure_irq_affinity(priv); + if (ret) + return ret; + + if (opp->smcarg == 2) { + ret = dram_clk_set_low(priv); + } else if (opp->smcarg == 0) { + ret = dram_clk_set_high(priv); + } else { + ret = -EINVAL; + } + + if (ret == 0) + pr_debug("%s freq set to %lu\n", clk_hw_get_name(hw), opp->rate); + else + pr_debug("%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_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, +}; + +static int dram_clk_init_irqs(struct dram_clk* priv, struct device_node *np) +{ + int err, irq, cpu; + + for_each_possible_cpu(cpu) { + irq = of_irq_get(np, cpu); + if (irq < 0) { + pr_err("imx8m_dram_clk fail of_irq_get %d\n", irq); + return irq; + } + + err = request_irq(irq, wait_in_wfe_irq, + IRQF_PERCPU, "ddrc", NULL); + if (err) { + pr_err("imx8m_dram_clk request irq %d failed: %d\n", + irq, err); + return err; + } + priv->irqs[cpu] = irq; + } + + return 0; +} + +static void dram_clk_free_irqs(struct dram_clk* priv) +{ + int cpu; + + for_each_possible_cpu(cpu) { + free_irq(priv->irqs[cpu], NULL); + priv->irqs[cpu] = 0; + } +} + +static const struct imx8m_dram_rate imx8mq_dram_rate_table[] = { + { + .rate = 800000000, + .smcarg = 0x0, + }, + { + .rate = 25000000, + .smcarg = 0x2, + }, +}; + +static const struct imx8m_dram_rate imx8mm_dram_rate_table[] = { + { + .rate = 750000000, + .smcarg = 0x0, + }, + { + .rate = 25000000, + .smcarg = 0x2, + }, +}; + +struct clk* imx8m_dram_clk( + struct device_node* np, + const char *name, const char* parent_name, + struct clk* dram_core, + struct clk* dram_apb, + struct clk* dram_pll, + struct clk* dram_alt, + struct clk* dram_alt_root, + struct clk* sys1_pll_40m, + struct clk* sys1_pll_100m, + struct clk* sys1_pll_800m) +{ + struct dram_clk *priv; + struct clk *clk; + struct clk_init_data init; + int err; + + 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->sys1_pll_40m = sys1_pll_40m; + priv->sys1_pll_100m = sys1_pll_100m; + priv->sys1_pll_800m = sys1_pll_800m; + priv->rate_count = ARRAY_SIZE(imx8mm_dram_rate_table); + priv->rate_table = imx8mm_dram_rate_table; + + init.name = name; + init.ops = &dram_clk_ops; + init.flags = CLK_IS_CRITICAL; + init.parent_names = &parent_name; + init.num_parents = 1; + + err = dram_clk_init_irqs(priv, np); + if (err) + goto err_free_priv; + + priv->hw.init = &init; + clk = clk_register(NULL, &priv->hw); + if (IS_ERR(clk)) { + err = PTR_ERR(clk); + goto err_free_irqs; + } + return clk; + +err_free_irqs: + dram_clk_free_irqs(priv); +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..8e19a6fdc415 100644
--- a/drivers/clk/imx/clk-imx8mm.c
+++ b/drivers/clk/imx/clk-imx8mm.c@@ -660,10 +660,22 @@ 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( + ccm_node, + "dram", "dram_core_clk", + clks[IMX8MM_CLK_DRAM_CORE], + clks[IMX8MM_CLK_DRAM_APB], + clks[IMX8MM_DRAM_PLL_OUT], + clks[IMX8MM_CLK_DRAM_ALT], + clks[IMX8MM_CLK_DRAM_ALT_ROOT], + clks[IMX8MM_SYS_PLL1_40M], + clks[IMX8MM_SYS_PLL1_100M], + clks[IMX8MM_SYS_PLL1_800M]); + 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..858547fbad58 100644
--- a/drivers/clk/imx/clk.h
+++ b/drivers/clk/imx/clk.h@@ -468,6 +468,19 @@ 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( + struct device_node *np, + const char *name, const char* parent_name, + struct clk* dram_core, + struct clk* dram_apb, + struct clk* dram_pll, + struct clk* dram_alt, + struct clk* dram_alt_root, + struct clk* sys1_pll_40m, + struct clk* sys1_pll_100m, + struct clk* sys1_pll_800m); + #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
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel