1
2
3
4
5
6
7
8
9
10
11
12#include <asm/processor.h>
13#include <clk-uclass.h>
14#include <common.h>
15#include <div64.h>
16#include <dm.h>
17#include <linux/clk-provider.h>
18#include <linux/clk/at91_pmc.h>
19
20#include "pmc.h"
21
22#define UBOOT_DM_CLK_AT91_MASTER_PRES "at91-master-clk-pres"
23#define UBOOT_DM_CLK_AT91_MASTER_DIV "at91-master-clk-div"
24#define UBOOT_DM_CLK_AT91_SAMA7G5_MASTER "at91-sama7g5-master-clk"
25
26#define MASTER_PRES_MASK 0x7
27#define MASTER_PRES_MAX MASTER_PRES_MASK
28#define MASTER_DIV_SHIFT 8
29#define MASTER_DIV_MASK 0x7
30
31#define PMC_MCR 0x30
32#define PMC_MCR_ID_MSK GENMASK(3, 0)
33#define PMC_MCR_CMD BIT(7)
34#define PMC_MCR_DIV GENMASK(10, 8)
35#define PMC_MCR_CSS GENMASK(20, 16)
36#define PMC_MCR_CSS_SHIFT (16)
37#define PMC_MCR_EN BIT(28)
38
39#define PMC_MCR_ID(x) ((x) & PMC_MCR_ID_MSK)
40
41#define MASTER_MAX_ID 4
42
43struct clk_master {
44 void __iomem *base;
45 const struct clk_master_layout *layout;
46 const struct clk_master_characteristics *characteristics;
47 const u32 *mux_table;
48 const u32 *clk_mux_table;
49 u32 num_parents;
50 struct clk clk;
51 u8 id;
52};
53
54#define to_clk_master(_clk) container_of(_clk, struct clk_master, clk)
55
56static inline bool clk_master_ready(struct clk_master *master)
57{
58 unsigned int bit = master->id ? AT91_PMC_MCKXRDY : AT91_PMC_MCKRDY;
59 unsigned int status;
60
61 pmc_read(master->base, AT91_PMC_SR, &status);
62
63 return !!(status & bit);
64}
65
66static int clk_master_enable(struct clk *clk)
67{
68 struct clk_master *master = to_clk_master(clk);
69
70 while (!clk_master_ready(master)) {
71 debug("waiting for mck %d\n", master->id);
72 cpu_relax();
73 }
74
75 return 0;
76}
77
78static ulong clk_master_pres_get_rate(struct clk *clk)
79{
80 struct clk_master *master = to_clk_master(clk);
81 const struct clk_master_layout *layout = master->layout;
82 const struct clk_master_characteristics *characteristics =
83 master->characteristics;
84 ulong rate = clk_get_parent_rate(clk);
85 unsigned int mckr;
86 u8 pres;
87
88 if (!rate)
89 return 0;
90
91 pmc_read(master->base, master->layout->offset, &mckr);
92 mckr &= layout->mask;
93
94 pres = (mckr >> layout->pres_shift) & MASTER_PRES_MASK;
95
96 if (characteristics->have_div3_pres && pres == MASTER_PRES_MAX)
97 pres = 3;
98 else
99 pres = (1 << pres);
100
101 return DIV_ROUND_CLOSEST_ULL(rate, pres);
102}
103
104static const struct clk_ops master_pres_ops = {
105 .enable = clk_master_enable,
106 .get_rate = clk_master_pres_get_rate,
107};
108
109struct clk *at91_clk_register_master_pres(void __iomem *base,
110 const char *name, const char * const *parent_names,
111 int num_parents, const struct clk_master_layout *layout,
112 const struct clk_master_characteristics *characteristics,
113 const u32 *mux_table)
114{
115 struct clk_master *master;
116 struct clk *clk;
117 unsigned int val;
118 int ret;
119
120 if (!base || !name || !num_parents || !parent_names ||
121 !layout || !characteristics || !mux_table)
122 return ERR_PTR(-EINVAL);
123
124 master = kzalloc(sizeof(*master), GFP_KERNEL);
125 if (!master)
126 return ERR_PTR(-ENOMEM);
127
128 master->layout = layout;
129 master->characteristics = characteristics;
130 master->base = base;
131 master->num_parents = num_parents;
132 master->mux_table = mux_table;
133
134 pmc_read(master->base, master->layout->offset, &val);
135 clk = &master->clk;
136 clk->flags = CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL;
137 ret = clk_register(clk, UBOOT_DM_CLK_AT91_MASTER_PRES, name,
138 parent_names[val & AT91_PMC_CSS]);
139 if (ret) {
140 kfree(master);
141 clk = ERR_PTR(ret);
142 }
143
144 return clk;
145}
146
147U_BOOT_DRIVER(at91_master_pres_clk) = {
148 .name = UBOOT_DM_CLK_AT91_MASTER_PRES,
149 .id = UCLASS_CLK,
150 .ops = &master_pres_ops,
151 .flags = DM_FLAG_PRE_RELOC,
152};
153
154static ulong clk_master_div_get_rate(struct clk *clk)
155{
156 struct clk_master *master = to_clk_master(clk);
157 const struct clk_master_layout *layout = master->layout;
158 const struct clk_master_characteristics *characteristics =
159 master->characteristics;
160 ulong rate = clk_get_parent_rate(clk);
161 unsigned int mckr;
162 u8 div;
163
164 if (!rate)
165 return 0;
166
167 pmc_read(master->base, master->layout->offset, &mckr);
168 mckr &= layout->mask;
169 div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
170
171 rate = DIV_ROUND_CLOSEST_ULL(rate, characteristics->divisors[div]);
172 if (rate < characteristics->output.min)
173 pr_warn("master clk is underclocked");
174 else if (rate > characteristics->output.max)
175 pr_warn("master clk is overclocked");
176
177 return rate;
178}
179
180static const struct clk_ops master_div_ops = {
181 .enable = clk_master_enable,
182 .get_rate = clk_master_div_get_rate,
183};
184
185struct clk *at91_clk_register_master_div(void __iomem *base,
186 const char *name, const char *parent_name,
187 const struct clk_master_layout *layout,
188 const struct clk_master_characteristics *characteristics)
189{
190 struct clk_master *master;
191 struct clk *clk;
192 int ret;
193
194 if (!base || !name || !parent_name || !layout || !characteristics)
195 return ERR_PTR(-EINVAL);
196
197 master = kzalloc(sizeof(*master), GFP_KERNEL);
198 if (!master)
199 return ERR_PTR(-ENOMEM);
200
201 master->layout = layout;
202 master->characteristics = characteristics;
203 master->base = base;
204 master->num_parents = 1;
205
206 clk = &master->clk;
207 clk->flags = CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL;
208 ret = clk_register(clk, UBOOT_DM_CLK_AT91_MASTER_DIV, name,
209 parent_name);
210 if (ret) {
211 kfree(master);
212 clk = ERR_PTR(ret);
213 }
214
215 return clk;
216}
217
218U_BOOT_DRIVER(at91_master_div_clk) = {
219 .name = UBOOT_DM_CLK_AT91_MASTER_DIV,
220 .id = UCLASS_CLK,
221 .ops = &master_div_ops,
222 .flags = DM_FLAG_PRE_RELOC,
223};
224
225static int clk_sama7g5_master_set_parent(struct clk *clk, struct clk *parent)
226{
227 struct clk_master *master = to_clk_master(clk);
228 int index;
229
230 index = at91_clk_mux_val_to_index(master->clk_mux_table,
231 master->num_parents, parent->id);
232 if (index < 0)
233 return index;
234
235 index = at91_clk_mux_index_to_val(master->mux_table,
236 master->num_parents, index);
237 if (index < 0)
238 return index;
239
240 pmc_write(master->base, PMC_MCR, PMC_MCR_ID(master->id));
241 pmc_update_bits(master->base, PMC_MCR,
242 PMC_MCR_CSS | PMC_MCR_CMD | PMC_MCR_ID_MSK,
243 (index << PMC_MCR_CSS_SHIFT) | PMC_MCR_CMD |
244 PMC_MCR_ID(master->id));
245 return 0;
246}
247
248static int clk_sama7g5_master_enable(struct clk *clk)
249{
250 struct clk_master *master = to_clk_master(clk);
251
252 pmc_write(master->base, PMC_MCR, PMC_MCR_ID(master->id));
253 pmc_update_bits(master->base, PMC_MCR,
254 PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID_MSK,
255 PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID(master->id));
256
257 return 0;
258}
259
260static int clk_sama7g5_master_disable(struct clk *clk)
261{
262 struct clk_master *master = to_clk_master(clk);
263
264 pmc_write(master->base, PMC_MCR, master->id);
265 pmc_update_bits(master->base, PMC_MCR,
266 PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID_MSK,
267 PMC_MCR_CMD | PMC_MCR_ID(master->id));
268
269 return 0;
270}
271
272static ulong clk_sama7g5_master_set_rate(struct clk *clk, ulong rate)
273{
274 struct clk_master *master = to_clk_master(clk);
275 ulong parent_rate = clk_get_parent_rate(clk);
276 ulong div, rrate;
277
278 if (!parent_rate)
279 return 0;
280
281 div = DIV_ROUND_CLOSEST(parent_rate, rate);
282 if ((div > (1 << (MASTER_PRES_MAX - 1))) || (div & (div - 1))) {
283 return 0;
284 } else if (div == 3) {
285 rrate = DIV_ROUND_CLOSEST(parent_rate, MASTER_PRES_MAX);
286 div = MASTER_PRES_MAX;
287 } else {
288 rrate = DIV_ROUND_CLOSEST(parent_rate, div);
289 div = ffs(div) - 1;
290 }
291
292 pmc_write(master->base, PMC_MCR, master->id);
293 pmc_update_bits(master->base, PMC_MCR,
294 PMC_MCR_DIV | PMC_MCR_CMD | PMC_MCR_ID_MSK,
295 (div << MASTER_DIV_SHIFT) | PMC_MCR_CMD |
296 PMC_MCR_ID(master->id));
297
298 return rrate;
299}
300
301static ulong clk_sama7g5_master_get_rate(struct clk *clk)
302{
303 struct clk_master *master = to_clk_master(clk);
304 ulong parent_rate = clk_get_parent_rate(clk);
305 unsigned int val;
306 ulong div;
307
308 if (!parent_rate)
309 return 0;
310
311 pmc_write(master->base, PMC_MCR, master->id);
312 pmc_read(master->base, PMC_MCR, &val);
313
314 div = (val >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
315
316 if (div == MASTER_PRES_MAX)
317 div = 3;
318 else
319 div = 1 << div;
320
321 return DIV_ROUND_CLOSEST(parent_rate, div);
322}
323
324static const struct clk_ops sama7g5_master_ops = {
325 .enable = clk_sama7g5_master_enable,
326 .disable = clk_sama7g5_master_disable,
327 .set_rate = clk_sama7g5_master_set_rate,
328 .get_rate = clk_sama7g5_master_get_rate,
329 .set_parent = clk_sama7g5_master_set_parent,
330};
331
332struct clk *at91_clk_sama7g5_register_master(void __iomem *base,
333 const char *name, const char * const *parent_names,
334 int num_parents, const u32 *mux_table, const u32 *clk_mux_table,
335 bool critical, u8 id)
336{
337 struct clk_master *master;
338 struct clk *clk;
339 u32 val, index;
340 int ret;
341
342 if (!base || !name || !num_parents || !parent_names ||
343 !mux_table || !clk_mux_table || id > MASTER_MAX_ID)
344 return ERR_PTR(-EINVAL);
345
346 master = kzalloc(sizeof(*master), GFP_KERNEL);
347 if (!master)
348 return ERR_PTR(-ENOMEM);
349
350 master->base = base;
351 master->id = id;
352 master->mux_table = mux_table;
353 master->clk_mux_table = clk_mux_table;
354 master->num_parents = num_parents;
355
356 pmc_write(master->base, PMC_MCR, master->id);
357 pmc_read(master->base, PMC_MCR, &val);
358
359 index = at91_clk_mux_val_to_index(master->mux_table,
360 master->num_parents,
361 (val & PMC_MCR_CSS) >> PMC_MCR_CSS_SHIFT);
362 if (index < 0) {
363 kfree(master);
364 return ERR_PTR(index);
365 }
366
367 clk = &master->clk;
368 clk->flags = CLK_GET_RATE_NOCACHE | (critical ? CLK_IS_CRITICAL : 0);
369
370 ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAMA7G5_MASTER, name,
371 parent_names[index]);
372 if (ret) {
373 kfree(master);
374 clk = ERR_PTR(ret);
375 }
376
377 return clk;
378}
379
380U_BOOT_DRIVER(at91_sama7g5_master_clk) = {
381 .name = UBOOT_DM_CLK_AT91_SAMA7G5_MASTER,
382 .id = UCLASS_CLK,
383 .ops = &sama7g5_master_ops,
384 .flags = DM_FLAG_PRE_RELOC,
385};
386
387const struct clk_master_layout at91rm9200_master_layout = {
388 .mask = 0x31F,
389 .pres_shift = 2,
390 .offset = AT91_PMC_MCKR,
391};
392
393const struct clk_master_layout at91sam9x5_master_layout = {
394 .mask = 0x373,
395 .pres_shift = 4,
396 .offset = AT91_PMC_MCKR,
397};
398