* [RFC v3] clk: imx8mm: Add dram freq switch support
@ 2019-07-18 19:06 Leonard Crestez
0 siblings, 0 replies; only message in thread
From: Leonard Crestez @ 2019-07-18 19:06 UTC (permalink / raw)
To: Stephen Boyd, Michael Turquette
Cc: Dong Aisheng, Abel Vesa, Anson Huang, linux-imx,
Alexandre Bailon, kernel, Fabio Estevam, Shawn Guo, linux-clk,
linux-arm-kernel, Jacky Bai
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
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2019-07-18 19:06 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-07-18 19:06 [RFC v3] clk: imx8mm: Add dram freq switch support Leonard Crestez
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).