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