mvebu: sort armada 37xx upstream patches chronologically
[openwrt/openwrt.git] / target / linux / mvebu / patches-4.14 / 505-clk-mvebu-armada-37xx-periph-add-DVFS-support-for-cp.patch
1 From 2089dc33ea0e3917465929d4020fbff3d6dbf7f4 Mon Sep 17 00:00:00 2001
2 From: Gregory CLEMENT <gregory.clement@free-electrons.com>
3 Date: Thu, 30 Nov 2017 14:40:29 +0100
4 Subject: clk: mvebu: armada-37xx-periph: add DVFS support for cpu clocks
5
6 When DVFS is enabled the CPU clock setting is done using an other set of
7 registers.
8
9 These Power Management registers are exposed through a syscon as they
10 will also be used by other drivers such as the cpufreq.
11
12 This patch add the possibility to modify the CPU frequency using the
13 associate load level matching the target frequency. Then all the
14 frequency switch is handle by the hardware.
15
16 Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
17 [sboyd@codeaurora.org: Grow a local variable for regmap pointer
18 to keep lines shorter]
19 Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
20 ---
21 drivers/clk/mvebu/armada-37xx-periph.c | 221 ++++++++++++++++++++++++++++++++-
22 1 file changed, 217 insertions(+), 4 deletions(-)
23
24 --- a/drivers/clk/mvebu/armada-37xx-periph.c
25 +++ b/drivers/clk/mvebu/armada-37xx-periph.c
26 @@ -21,9 +21,11 @@
27 */
28
29 #include <linux/clk-provider.h>
30 +#include <linux/mfd/syscon.h>
31 #include <linux/of.h>
32 #include <linux/of_device.h>
33 #include <linux/platform_device.h>
34 +#include <linux/regmap.h>
35 #include <linux/slab.h>
36
37 #define TBG_SEL 0x0
38 @@ -33,6 +35,26 @@
39 #define CLK_SEL 0x10
40 #define CLK_DIS 0x14
41
42 +#define LOAD_LEVEL_NR 4
43 +
44 +#define ARMADA_37XX_NB_L0L1 0x18
45 +#define ARMADA_37XX_NB_L2L3 0x1C
46 +#define ARMADA_37XX_NB_TBG_DIV_OFF 13
47 +#define ARMADA_37XX_NB_TBG_DIV_MASK 0x7
48 +#define ARMADA_37XX_NB_CLK_SEL_OFF 11
49 +#define ARMADA_37XX_NB_CLK_SEL_MASK 0x1
50 +#define ARMADA_37XX_NB_TBG_SEL_OFF 9
51 +#define ARMADA_37XX_NB_TBG_SEL_MASK 0x3
52 +#define ARMADA_37XX_NB_CONFIG_SHIFT 16
53 +#define ARMADA_37XX_NB_DYN_MOD 0x24
54 +#define ARMADA_37XX_NB_DFS_EN 31
55 +#define ARMADA_37XX_NB_CPU_LOAD 0x30
56 +#define ARMADA_37XX_NB_CPU_LOAD_MASK 0x3
57 +#define ARMADA_37XX_DVFS_LOAD_0 0
58 +#define ARMADA_37XX_DVFS_LOAD_1 1
59 +#define ARMADA_37XX_DVFS_LOAD_2 2
60 +#define ARMADA_37XX_DVFS_LOAD_3 3
61 +
62 struct clk_periph_driver_data {
63 struct clk_hw_onecell_data *hw_data;
64 spinlock_t lock;
65 @@ -53,6 +75,7 @@ struct clk_pm_cpu {
66 u32 mask_mux;
67 void __iomem *reg_div;
68 u8 shift_div;
69 + struct regmap *nb_pm_base;
70 };
71
72 #define to_clk_double_div(_hw) container_of(_hw, struct clk_double_div, hw)
73 @@ -316,14 +339,94 @@ static const struct clk_ops clk_double_d
74 .recalc_rate = clk_double_div_recalc_rate,
75 };
76
77 +static void armada_3700_pm_dvfs_update_regs(unsigned int load_level,
78 + unsigned int *reg,
79 + unsigned int *offset)
80 +{
81 + if (load_level <= ARMADA_37XX_DVFS_LOAD_1)
82 + *reg = ARMADA_37XX_NB_L0L1;
83 + else
84 + *reg = ARMADA_37XX_NB_L2L3;
85 +
86 + if (load_level == ARMADA_37XX_DVFS_LOAD_0 ||
87 + load_level == ARMADA_37XX_DVFS_LOAD_2)
88 + *offset += ARMADA_37XX_NB_CONFIG_SHIFT;
89 +}
90 +
91 +static bool armada_3700_pm_dvfs_is_enabled(struct regmap *base)
92 +{
93 + unsigned int val, reg = ARMADA_37XX_NB_DYN_MOD;
94 +
95 + if (IS_ERR(base))
96 + return false;
97 +
98 + regmap_read(base, reg, &val);
99 +
100 + return !!(val & BIT(ARMADA_37XX_NB_DFS_EN));
101 +}
102 +
103 +static unsigned int armada_3700_pm_dvfs_get_cpu_div(struct regmap *base)
104 +{
105 + unsigned int reg = ARMADA_37XX_NB_CPU_LOAD;
106 + unsigned int offset = ARMADA_37XX_NB_TBG_DIV_OFF;
107 + unsigned int load_level, div;
108 +
109 + /*
110 + * This function is always called after the function
111 + * armada_3700_pm_dvfs_is_enabled, so no need to check again
112 + * if the base is valid.
113 + */
114 + regmap_read(base, reg, &load_level);
115 +
116 + /*
117 + * The register and the offset inside this register accessed to
118 + * read the current divider depend on the load level
119 + */
120 + load_level &= ARMADA_37XX_NB_CPU_LOAD_MASK;
121 + armada_3700_pm_dvfs_update_regs(load_level, &reg, &offset);
122 +
123 + regmap_read(base, reg, &div);
124 +
125 + return (div >> offset) & ARMADA_37XX_NB_TBG_DIV_MASK;
126 +}
127 +
128 +static unsigned int armada_3700_pm_dvfs_get_cpu_parent(struct regmap *base)
129 +{
130 + unsigned int reg = ARMADA_37XX_NB_CPU_LOAD;
131 + unsigned int offset = ARMADA_37XX_NB_TBG_SEL_OFF;
132 + unsigned int load_level, sel;
133 +
134 + /*
135 + * This function is always called after the function
136 + * armada_3700_pm_dvfs_is_enabled, so no need to check again
137 + * if the base is valid
138 + */
139 + regmap_read(base, reg, &load_level);
140 +
141 + /*
142 + * The register and the offset inside this register accessed to
143 + * read the current divider depend on the load level
144 + */
145 + load_level &= ARMADA_37XX_NB_CPU_LOAD_MASK;
146 + armada_3700_pm_dvfs_update_regs(load_level, &reg, &offset);
147 +
148 + regmap_read(base, reg, &sel);
149 +
150 + return (sel >> offset) & ARMADA_37XX_NB_TBG_SEL_MASK;
151 +}
152 +
153 static u8 clk_pm_cpu_get_parent(struct clk_hw *hw)
154 {
155 struct clk_pm_cpu *pm_cpu = to_clk_pm_cpu(hw);
156 int num_parents = clk_hw_get_num_parents(hw);
157 u32 val;
158
159 - val = readl(pm_cpu->reg_mux) >> pm_cpu->shift_mux;
160 - val &= pm_cpu->mask_mux;
161 + if (armada_3700_pm_dvfs_is_enabled(pm_cpu->nb_pm_base)) {
162 + val = armada_3700_pm_dvfs_get_cpu_parent(pm_cpu->nb_pm_base);
163 + } else {
164 + val = readl(pm_cpu->reg_mux) >> pm_cpu->shift_mux;
165 + val &= pm_cpu->mask_mux;
166 + }
167
168 if (val >= num_parents)
169 return -EINVAL;
170 @@ -331,19 +434,124 @@ static u8 clk_pm_cpu_get_parent(struct c
171 return val;
172 }
173
174 +static int clk_pm_cpu_set_parent(struct clk_hw *hw, u8 index)
175 +{
176 + struct clk_pm_cpu *pm_cpu = to_clk_pm_cpu(hw);
177 + struct regmap *base = pm_cpu->nb_pm_base;
178 + int load_level;
179 +
180 + /*
181 + * We set the clock parent only if the DVFS is available but
182 + * not enabled.
183 + */
184 + if (IS_ERR(base) || armada_3700_pm_dvfs_is_enabled(base))
185 + return -EINVAL;
186 +
187 + /* Set the parent clock for all the load level */
188 + for (load_level = 0; load_level < LOAD_LEVEL_NR; load_level++) {
189 + unsigned int reg, mask, val,
190 + offset = ARMADA_37XX_NB_TBG_SEL_OFF;
191 +
192 + armada_3700_pm_dvfs_update_regs(load_level, &reg, &offset);
193 +
194 + val = index << offset;
195 + mask = ARMADA_37XX_NB_TBG_SEL_MASK << offset;
196 + regmap_update_bits(base, reg, mask, val);
197 + }
198 + return 0;
199 +}
200 +
201 static unsigned long clk_pm_cpu_recalc_rate(struct clk_hw *hw,
202 unsigned long parent_rate)
203 {
204 struct clk_pm_cpu *pm_cpu = to_clk_pm_cpu(hw);
205 unsigned int div;
206
207 - div = get_div(pm_cpu->reg_div, pm_cpu->shift_div);
208 -
209 + if (armada_3700_pm_dvfs_is_enabled(pm_cpu->nb_pm_base))
210 + div = armada_3700_pm_dvfs_get_cpu_div(pm_cpu->nb_pm_base);
211 + else
212 + div = get_div(pm_cpu->reg_div, pm_cpu->shift_div);
213 return DIV_ROUND_UP_ULL((u64)parent_rate, div);
214 }
215
216 +static long clk_pm_cpu_round_rate(struct clk_hw *hw, unsigned long rate,
217 + unsigned long *parent_rate)
218 +{
219 + struct clk_pm_cpu *pm_cpu = to_clk_pm_cpu(hw);
220 + struct regmap *base = pm_cpu->nb_pm_base;
221 + unsigned int div = *parent_rate / rate;
222 + unsigned int load_level;
223 + /* only available when DVFS is enabled */
224 + if (!armada_3700_pm_dvfs_is_enabled(base))
225 + return -EINVAL;
226 +
227 + for (load_level = 0; load_level < LOAD_LEVEL_NR; load_level++) {
228 + unsigned int reg, val, offset = ARMADA_37XX_NB_TBG_DIV_OFF;
229 +
230 + armada_3700_pm_dvfs_update_regs(load_level, &reg, &offset);
231 +
232 + regmap_read(base, reg, &val);
233 +
234 + val >>= offset;
235 + val &= ARMADA_37XX_NB_TBG_DIV_MASK;
236 + if (val == div)
237 + /*
238 + * We found a load level matching the target
239 + * divider, switch to this load level and
240 + * return.
241 + */
242 + return *parent_rate / div;
243 + }
244 +
245 + /* We didn't find any valid divider */
246 + return -EINVAL;
247 +}
248 +
249 +static int clk_pm_cpu_set_rate(struct clk_hw *hw, unsigned long rate,
250 + unsigned long parent_rate)
251 +{
252 + struct clk_pm_cpu *pm_cpu = to_clk_pm_cpu(hw);
253 + struct regmap *base = pm_cpu->nb_pm_base;
254 + unsigned int div = parent_rate / rate;
255 + unsigned int load_level;
256 +
257 + /* only available when DVFS is enabled */
258 + if (!armada_3700_pm_dvfs_is_enabled(base))
259 + return -EINVAL;
260 +
261 + for (load_level = 0; load_level < LOAD_LEVEL_NR; load_level++) {
262 + unsigned int reg, mask, val,
263 + offset = ARMADA_37XX_NB_TBG_DIV_OFF;
264 +
265 + armada_3700_pm_dvfs_update_regs(load_level, &reg, &offset);
266 +
267 + regmap_read(base, reg, &val);
268 + val >>= offset;
269 + val &= ARMADA_37XX_NB_TBG_DIV_MASK;
270 +
271 + if (val == div) {
272 + /*
273 + * We found a load level matching the target
274 + * divider, switch to this load level and
275 + * return.
276 + */
277 + reg = ARMADA_37XX_NB_CPU_LOAD;
278 + mask = ARMADA_37XX_NB_CPU_LOAD_MASK;
279 + regmap_update_bits(base, reg, mask, load_level);
280 +
281 + return rate;
282 + }
283 + }
284 +
285 + /* We didn't find any valid divider */
286 + return -EINVAL;
287 +}
288 +
289 static const struct clk_ops clk_pm_cpu_ops = {
290 .get_parent = clk_pm_cpu_get_parent,
291 + .set_parent = clk_pm_cpu_set_parent,
292 + .round_rate = clk_pm_cpu_round_rate,
293 + .set_rate = clk_pm_cpu_set_rate,
294 .recalc_rate = clk_pm_cpu_recalc_rate,
295 };
296
297 @@ -409,6 +617,7 @@ static int armada_3700_add_composite_clk
298 if (data->muxrate_hw) {
299 struct clk_pm_cpu *pmcpu_clk;
300 struct clk_hw *muxrate_hw = data->muxrate_hw;
301 + struct regmap *map;
302
303 pmcpu_clk = to_clk_pm_cpu(muxrate_hw);
304 pmcpu_clk->reg_mux = reg + (u64)pmcpu_clk->reg_mux;
305 @@ -418,6 +627,10 @@ static int armada_3700_add_composite_clk
306 rate_hw = muxrate_hw;
307 mux_ops = muxrate_hw->init->ops;
308 rate_ops = muxrate_hw->init->ops;
309 +
310 + map = syscon_regmap_lookup_by_compatible(
311 + "marvell,armada-3700-nb-pm");
312 + pmcpu_clk->nb_pm_base = map;
313 }
314
315 *hw = clk_hw_register_composite(dev, data->name, data->parent_names,