2065e788afe3a9c4a36e78e7fafdbd5bc916ca5a
[oweals/openwrt.git] / target / linux / mvebu / patches-4.14 / 502-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,