[PATCH 3/4] clk: samsung: add cpu clock support for Exynos7

Abhilash Kesavan a.kesavan at samsung.com
Wed Nov 26 03:17:50 PST 2014


The divider and mux register offsets and bits are different on
Exynos7 from the older SoCs. Add new pre/post rate change callbacks
for Exynos7 to handle these differences. To do this:
	- Add a new exynos_cpuclk_soc_data structure that will hold
	the SoC-specific pre/post rate change call-backs
	- Modify exynos_register_cpu_clock() prototype to include a
	node pointer

Signed-off-by: Abhilash Kesavan <a.kesavan at samsung.com>
---
 drivers/clk/samsung/clk-cpu.c        |  130 +++++++++++++++++++++++++++++++++-
 drivers/clk/samsung/clk-cpu.h        |   33 ++++++++-
 drivers/clk/samsung/clk-exynos4.c    |    2 +-
 drivers/clk/samsung/clk-exynos5250.c |    2 +-
 drivers/clk/samsung/clk-exynos5420.c |    4 +-
 5 files changed, 163 insertions(+), 8 deletions(-)

diff --git a/drivers/clk/samsung/clk-cpu.c b/drivers/clk/samsung/clk-cpu.c
index 009a21b..6c00802 100644
--- a/drivers/clk/samsung/clk-cpu.c
+++ b/drivers/clk/samsung/clk-cpu.c
@@ -51,6 +51,13 @@
 #define DIV_MASK_ALL		0xffffffff
 #define MUX_MASK		7
 
+#define EXYNOS7_SRC_CPU		0x208
+#define EXYNOS7_STAT_CPU	0x408
+#define EXYNOS7_DIV_CPU0	0x600
+#define EXYNOS7_DIV_CPU1	0x604
+#define EXYNOS7_DIV_STAT_CPU0	0x700
+#define EXYNOS7_DIV_STAT_CPU1	0x704
+
 /*
  * Helper function to wait until divider(s) have stabilized after the divider
  * value has changed.
@@ -128,6 +135,88 @@ static void exynos_set_safe_div(void __iomem *base, unsigned long div,
 	wait_until_divider_stable(base + E4210_DIV_STAT_CPU0, mask);
 }
 
+static void exynos7_set_safe_div(void __iomem *base, unsigned long div,
+					unsigned long mask)
+{
+	unsigned long div0;
+
+	div0 = readl(base + EXYNOS7_DIV_CPU0);
+	div0 = (div0 & ~mask) | (div & mask);
+	writel(div0, base + EXYNOS7_DIV_CPU0);
+	wait_until_divider_stable(base + EXYNOS7_DIV_STAT_CPU0, mask);
+}
+
+/* Exynos7 handler for pre-rate change notification from parent clock */
+static int exynos7_cpuclk_pre_rate_change(struct clk_notifier_data *ndata,
+			struct exynos_cpuclk *cpuclk, void __iomem *base)
+{
+	const struct exynos_cpuclk_cfg_data *cfg_data = cpuclk->cfg;
+	unsigned long alt_prate = clk_get_rate(cpuclk->alt_parent);
+	unsigned long alt_div = 0, alt_div_mask = DIV_MASK;
+	unsigned long div0, div1 = 0, mux_reg;
+
+	/* find out the divider values to use for clock data */
+	while ((cfg_data->prate * 1000) != ndata->new_rate) {
+		if (cfg_data->prate == 0)
+			return -EINVAL;
+		cfg_data++;
+	}
+
+	spin_lock(cpuclk->lock);
+
+	/*
+	 * If the new and old parent clock speed is less than the clock speed
+	 * of the alternate parent, then it should be ensured that at no point
+	 * the armclk speed is more than the old_prate until the dividers are
+	 * set.
+	 */
+	div0 = cfg_data->div0;
+	if (alt_prate > ndata->old_rate) {
+		alt_div =  DIV_ROUND_UP(alt_prate, ndata->old_rate) - 1;
+		WARN_ON(alt_div >= MAX_DIV);
+
+		exynos7_set_safe_div(base, alt_div, alt_div_mask);
+		div0 |= alt_div;
+	}
+
+	/* select mout_bus0_pll_atlas as the alternate parent */
+	mux_reg = readl(base + EXYNOS7_SRC_CPU);
+	writel(mux_reg | (1 << 0), base + EXYNOS7_SRC_CPU);
+	wait_until_mux_stable(base + EXYNOS7_STAT_CPU, 0, 1);
+
+	/* alternate parent is active now. set the dividers */
+	writel(div0, base + EXYNOS7_DIV_CPU0);
+	wait_until_divider_stable(base + EXYNOS7_DIV_STAT_CPU0, DIV_MASK_ALL);
+
+	if (test_bit(CLK_CPU_HAS_DIV1, &cpuclk->flags)) {
+		writel(div1, base + EXYNOS7_DIV_CPU1);
+		wait_until_divider_stable(base + EXYNOS7_DIV_STAT_CPU1,
+				DIV_MASK_ALL);
+	}
+
+	spin_unlock(cpuclk->lock);
+	return 0;
+}
+
+/* Exynos7 handler for post-rate change notification from parent clock */
+static int exynos7_cpuclk_post_rate_change(struct clk_notifier_data *ndata,
+			struct exynos_cpuclk *cpuclk, void __iomem *base)
+{
+	unsigned long div = 0, div_mask = DIV_MASK;
+	unsigned long mux_reg;
+
+	spin_lock(cpuclk->lock);
+
+	/* select mout_atlas_pll as the alternate parent */
+	mux_reg = readl(base + EXYNOS7_SRC_CPU);
+	writel(mux_reg & ~(1 << 0), base + EXYNOS7_SRC_CPU);
+	wait_until_mux_stable(base + EXYNOS7_STAT_CPU, 0, 0);
+
+	exynos7_set_safe_div(base, div, div_mask);
+	spin_unlock(cpuclk->lock);
+	return 0;
+}
+
 /* handler for pre-rate change notification from parent clock */
 static int exynos_cpuclk_pre_rate_change(struct clk_notifier_data *ndata,
 			struct exynos_cpuclk *cpuclk, void __iomem *base)
@@ -248,25 +337,58 @@ static int exynos_cpuclk_notifier_cb(struct notifier_block *nb,
 	base = cpuclk->ctrl_base;
 
 	if (event == PRE_RATE_CHANGE)
-		err = exynos_cpuclk_pre_rate_change(ndata, cpuclk, base);
+		err = cpuclk->pre_rate_cb(ndata, cpuclk, base);
 	else if (event == POST_RATE_CHANGE)
-		err = exynos_cpuclk_post_rate_change(ndata, cpuclk, base);
+		err = cpuclk->post_rate_cb(ndata, cpuclk, base);
 
 	return notifier_from_errno(err);
 }
 
+static const struct exynos_cpuclk_soc_data e4210_clk_soc_data __initconst = {
+	.pre_rate_cb = exynos_cpuclk_pre_rate_change,
+	.post_rate_cb = exynos_cpuclk_post_rate_change,
+};
+
+static const struct exynos_cpuclk_soc_data e7_clk_soc_data __initconst = {
+	.pre_rate_cb = exynos7_cpuclk_pre_rate_change,
+	.post_rate_cb = exynos7_cpuclk_post_rate_change,
+};
+
+static const struct of_device_id exynos_cpuclk_ids[] __initconst = {
+	{ .compatible = "samsung,exynos4210-clock",
+			.data = &e4210_clk_soc_data, },
+	{ .compatible = "samsung,exynos5250-clock",
+			.data = &e4210_clk_soc_data, },
+	{ .compatible = "samsung,exynos5420-clock",
+			.data = &e4210_clk_soc_data, },
+	{ .compatible = "samsung,exynos7-clock-atlas",
+			.data = &e7_clk_soc_data, },
+	{ },
+};
+
 /* helper function to register a CPU clock */
 int __init exynos_register_cpu_clock(struct samsung_clk_provider *ctx,
 		unsigned int lookup_id, const char *name, const char *parent,
 		const char *alt_parent, unsigned long offset,
 		const struct exynos_cpuclk_cfg_data *cfg,
-		unsigned long num_cfgs, unsigned long flags)
+		unsigned long num_cfgs, unsigned long flags,
+		struct device_node *np)
 {
+	const struct of_device_id *match;
+	const struct exynos_cpuclk_soc_data *data = NULL;
 	struct exynos_cpuclk *cpuclk;
 	struct clk_init_data init;
 	struct clk *clk;
 	int ret = 0;
 
+	if (!np)
+		return -EINVAL;
+
+	match = of_match_node(exynos_cpuclk_ids, np);
+	if (!match)
+		return -EINVAL;
+	data = match->data;
+
 	cpuclk = kzalloc(sizeof(*cpuclk), GFP_KERNEL);
 	if (!cpuclk)
 		return -ENOMEM;
@@ -281,6 +403,8 @@ int __init exynos_register_cpu_clock(struct samsung_clk_provider *ctx,
 	cpuclk->ctrl_base = ctx->reg_base + offset;
 	cpuclk->lock = &ctx->lock;
 	cpuclk->flags = flags;
+	cpuclk->pre_rate_cb = data->pre_rate_cb;
+	cpuclk->post_rate_cb = data->post_rate_cb;
 	cpuclk->clk_nb.notifier_call = exynos_cpuclk_notifier_cb;
 
 	cpuclk->alt_parent = __clk_lookup(alt_parent);
diff --git a/drivers/clk/samsung/clk-cpu.h b/drivers/clk/samsung/clk-cpu.h
index 42e1905..24e844e 100644
--- a/drivers/clk/samsung/clk-cpu.h
+++ b/drivers/clk/samsung/clk-cpu.h
@@ -60,6 +60,10 @@ struct exynos_cpuclk_cfg_data {
  * @num_cfgs: number of array elements in @cfg array.
  * @clk_nb: clock notifier registered for changes in clock speed of the
  *	primary parent clock.
+ * @pre_rate_cb: callback function to handle PRE_RATE_CHANGE notification
+ *	of the primary parent clock.
+ * @post_rate_cb: callback function to handle POST_RATE_CHANGE notification
+ *	of the primary parent clock.
  * @flags: configuration flags for the CPU clock.
  *
  * This structure holds information required for programming the CPU clock for
@@ -73,6 +77,12 @@ struct exynos_cpuclk {
 	const struct exynos_cpuclk_cfg_data	*cfg;
 	const unsigned long			num_cfgs;
 	struct notifier_block			clk_nb;
+	int			(*pre_rate_cb)(struct clk_notifier_data *,
+					struct exynos_cpuclk *,
+					void __iomem *base);
+	int			(*post_rate_cb)(struct clk_notifier_data *,
+					struct exynos_cpuclk *,
+					void __iomem *base);
 	unsigned long				flags;
 
 /* The CPU clock registers has DIV1 configuration register */
@@ -81,11 +91,32 @@ struct exynos_cpuclk {
 #define CLK_CPU_NEEDS_DEBUG_ALT_DIV	(1 << 1)
 };
 
+/**
+ * struct exynos_cpuclk_soc_data: soc specific data for cpu clocks.
+ * @pre_rate_cb: callback function to handle PRE_RATE_CHANGE notification
+ *	of the primary parent clock.
+ * @post_rate_cb: callback function to handle POST_RATE_CHANGE notification
+ *	of the primary parent clock.
+ *
+ * This structure provides SoC specific data for CPU clocks. Based on
+ * the compatible value of the clock controller node, the value of the
+ * fields in this structure can be populated.
+ */
+struct exynos_cpuclk_soc_data {
+	int			(*pre_rate_cb)(struct clk_notifier_data *,
+					struct exynos_cpuclk *,
+					void __iomem *base);
+	int			(*post_rate_cb)(struct clk_notifier_data *,
+					struct exynos_cpuclk *,
+					void __iomem *base);
+};
+
 extern int __init exynos_register_cpu_clock(struct samsung_clk_provider *ctx,
 			unsigned int lookup_id, const char *name,
 			const char *parent, const char *alt_parent,
 			unsigned long offset,
 			const struct exynos_cpuclk_cfg_data *cfg,
-			unsigned long num_cfgs, unsigned long flags);
+			unsigned long num_cfgs, unsigned long flags,
+			struct device_node *np);
 
 #endif /* __SAMSUNG_CLK_CPU_H */
diff --git a/drivers/clk/samsung/clk-exynos4.c b/drivers/clk/samsung/clk-exynos4.c
index 3731fc7..a057a24 100644
--- a/drivers/clk/samsung/clk-exynos4.c
+++ b/drivers/clk/samsung/clk-exynos4.c
@@ -1473,7 +1473,7 @@ static void __init exynos4_clk_init(struct device_node *np,
 		exynos_register_cpu_clock(ctx, CLK_ARM_CLK, "armclk",
 			mout_core_p4210[0], mout_core_p4210[1], 0x14200,
 			e4210_armclk_d, ARRAY_SIZE(e4210_armclk_d),
-			CLK_CPU_NEEDS_DEBUG_ALT_DIV | CLK_CPU_HAS_DIV1);
+			CLK_CPU_NEEDS_DEBUG_ALT_DIV | CLK_CPU_HAS_DIV1, np);
 	} else {
 		samsung_clk_register_mux(ctx, exynos4x12_mux_clks,
 			ARRAY_SIZE(exynos4x12_mux_clks));
diff --git a/drivers/clk/samsung/clk-exynos5250.c b/drivers/clk/samsung/clk-exynos5250.c
index 1d958f1..56b4147b 100644
--- a/drivers/clk/samsung/clk-exynos5250.c
+++ b/drivers/clk/samsung/clk-exynos5250.c
@@ -824,7 +824,7 @@ static void __init exynos5250_clk_init(struct device_node *np)
 	exynos_register_cpu_clock(ctx, CLK_ARM_CLK, "armclk",
 			mout_cpu_p[0], mout_cpu_p[1], 0x200,
 			exynos5250_armclk_d, ARRAY_SIZE(exynos5250_armclk_d),
-			CLK_CPU_HAS_DIV1);
+			CLK_CPU_HAS_DIV1, np);
 
 	/*
 	 * Enable arm clock down (in idle) and set arm divider
diff --git a/drivers/clk/samsung/clk-exynos5420.c b/drivers/clk/samsung/clk-exynos5420.c
index fcf365d..a8c668d 100644
--- a/drivers/clk/samsung/clk-exynos5420.c
+++ b/drivers/clk/samsung/clk-exynos5420.c
@@ -1358,10 +1358,10 @@ static void __init exynos5x_clk_init(struct device_node *np,
 
 	exynos_register_cpu_clock(ctx, CLK_ARM_CLK, "armclk",
 		mout_cpu_p[0], mout_cpu_p[1], 0x200,
-		exynos5420_eglclk_d, ARRAY_SIZE(exynos5420_eglclk_d), 0);
+		exynos5420_eglclk_d, ARRAY_SIZE(exynos5420_eglclk_d), 0, np);
 	exynos_register_cpu_clock(ctx, CLK_KFC_CLK, "kfcclk",
 		mout_kfc_p[0], mout_kfc_p[1], 0x28200,
-		exynos5420_kfcclk_d, ARRAY_SIZE(exynos5420_kfcclk_d), 0);
+		exynos5420_kfcclk_d, ARRAY_SIZE(exynos5420_kfcclk_d), 0, np);
 
 	exynos5420_clk_sleep_init();
 
-- 
1.7.9.5




More information about the linux-arm-kernel mailing list