blob: b1a35968e15c72d533096db6b3c8c9e4a8b7d249 [file] [log] [blame]
Marek Behún82a248d2018-04-24 17:21:25 +02001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Marvell Armada 37xx SoC Peripheral clocks
4 *
5 * Marek Behun <marek.behun@nic.cz>
6 *
7 * Based on Linux driver by:
8 * Gregory CLEMENT <gregory.clement@free-electrons.com>
9 */
10
11#include <common.h>
12#include <malloc.h>
13#include <clk-uclass.h>
14#include <clk.h>
15#include <dm.h>
16#include <asm/io.h>
17#include <asm/arch/cpu.h>
18
19#define TBG_SEL 0x0
20#define DIV_SEL0 0x4
21#define DIV_SEL1 0x8
22#define DIV_SEL2 0xC
23#define CLK_SEL 0x10
24#define CLK_DIS 0x14
25
26enum a37xx_periph_parent {
27 TBG_A_P = 0,
28 TBG_B_P = 1,
29 TBG_A_S = 2,
30 TBG_B_S = 3,
31 MAX_TBG_PARENTS = 4,
32 XTAL = 4,
33 MAX_PARENTS = 5,
34};
35
36static const struct {
37 const char *name;
38 enum a37xx_periph_parent parent;
39} a37xx_periph_parent_names[] = {
40 { "TBG-A-P", TBG_A_P },
41 { "TBG-B-P", TBG_B_P },
42 { "TBG-A-S", TBG_A_S },
43 { "TBG-B-S", TBG_B_S },
44 { "xtal", XTAL },
45};
46
47struct clk_periph;
48
49struct a37xx_periphclk {
50 void __iomem *reg;
51
52 ulong parents[MAX_PARENTS];
53
54 const struct clk_periph *clks;
55 bool clk_has_periph_parent[16];
56 int clk_parent[16];
57
58 int count;
59};
60
61struct clk_div_table {
62 u32 div;
63 u32 val;
64};
65
66struct clk_periph {
67 const char *name;
68
69 const char *parent_name;
70
71 u32 disable_bit;
72 int mux_shift;
73
74 const struct clk_div_table *div_table[2];
75 s32 div_reg_off[2];
76 u32 div_mask[2];
77 int div_shift[2];
78
79 unsigned can_gate : 1;
80 unsigned can_mux : 1;
81 unsigned dividers : 2;
82};
83
84static const struct clk_div_table div_table1[] = {
85 { 1, 1 },
86 { 2, 2 },
87 { 0, 0 },
88};
89
90static const struct clk_div_table div_table2[] = {
91 { 2, 1 },
92 { 4, 2 },
93 { 0, 0 },
94};
95
96static const struct clk_div_table div_table6[] = {
97 { 1, 1 },
98 { 2, 2 },
99 { 3, 3 },
100 { 4, 4 },
101 { 5, 5 },
102 { 6, 6 },
103 { 0, 0 },
104};
105
106#define CLK_FULL_DD(_n, _d, _mux, _r0, _r1, _s0, _s1) \
107 { \
108 .name = #_n, \
109 .disable_bit = BIT(_d), \
110 .mux_shift = _mux, \
111 .div_table[0] = div_table6, \
112 .div_table[1] = div_table6, \
113 .div_reg_off[0] = _r0, \
114 .div_reg_off[1] = _r1, \
115 .div_shift[0] = _s0, \
116 .div_shift[1] = _s1, \
117 .div_mask[0] = 7, \
118 .div_mask[1] = 7, \
119 .can_gate = 1, \
120 .can_mux = 1, \
121 .dividers = 2, \
122 }
123
124#define CLK_FULL(_n, _d, _mux, _r, _s, _m, _t) \
125 { \
126 .name = #_n, \
127 .disable_bit = BIT(_d), \
128 .mux_shift = _mux, \
129 .div_table[0] = _t, \
130 .div_reg_off[0] = _r, \
131 .div_shift[0] = _s, \
132 .div_mask[0] = _m, \
133 .can_gate = 1, \
134 .can_mux = 1, \
135 .dividers = 1, \
136 }
137
138#define CLK_GATE_DIV(_n, _d, _r, _s, _m, _t, _p) \
139 { \
140 .name = #_n, \
141 .parent_name = _p, \
142 .disable_bit = BIT(_d), \
143 .div_table[0] = _t, \
144 .div_reg_off[0] = _r, \
145 .div_shift[0] = _s, \
146 .div_mask[0] = _m, \
147 .can_gate = 1, \
148 .dividers = 1, \
149 }
150
151#define CLK_GATE(_n, _d, _p) \
152 { \
153 .name = #_n, \
154 .parent_name = _p, \
155 .disable_bit = BIT(_d), \
156 .can_gate = 1, \
157 }
158
159#define CLK_MUX_DIV(_n, _mux, _r, _s, _m, _t) \
160 { \
161 .name = #_n, \
162 .mux_shift = _mux, \
163 .div_table[0] = _t, \
164 .div_reg_off[0] = _r, \
165 .div_shift[0] = _s, \
166 .div_mask[0] = _m, \
167 .can_mux = 1, \
168 .dividers = 1, \
169 }
170
171#define CLK_MUX_DD(_n, _mux, _r0, _r1, _s0, _s1) \
172 { \
173 .name = #_n, \
174 .mux_shift = _mux, \
175 .div_table[0] = div_table6, \
176 .div_table[1] = div_table6, \
177 .div_reg_off[0] = _r0, \
178 .div_reg_off[1] = _r1, \
179 .div_shift[0] = _s0, \
180 .div_shift[1] = _s1, \
181 .div_mask[0] = 7, \
182 .div_mask[1] = 7, \
183 .can_mux = 1, \
184 .dividers = 2, \
185 }
186
187/* NB periph clocks */
188static const struct clk_periph clks_nb[] = {
189 CLK_FULL_DD(mmc, 2, 0, DIV_SEL2, DIV_SEL2, 16, 13),
190 CLK_FULL_DD(sata_host, 3, 2, DIV_SEL2, DIV_SEL2, 10, 7),
191 CLK_FULL_DD(sec_at, 6, 4, DIV_SEL1, DIV_SEL1, 3, 0),
192 CLK_FULL_DD(sec_dap, 7, 6, DIV_SEL1, DIV_SEL1, 9, 6),
193 CLK_FULL_DD(tscem, 8, 8, DIV_SEL1, DIV_SEL1, 15, 12),
194 CLK_FULL(tscem_tmx, 10, 10, DIV_SEL1, 18, 7, div_table6),
195 CLK_GATE(avs, 11, "xtal"),
196 CLK_FULL_DD(sqf, 12, 12, DIV_SEL1, DIV_SEL1, 27, 24),
197 CLK_FULL_DD(pwm, 13, 14, DIV_SEL0, DIV_SEL0, 3, 0),
198 CLK_GATE(i2c_2, 16, "xtal"),
199 CLK_GATE(i2c_1, 17, "xtal"),
200 CLK_GATE_DIV(ddr_phy, 19, DIV_SEL0, 18, 1, div_table2, "TBG-A-S"),
201 CLK_FULL_DD(ddr_fclk, 21, 16, DIV_SEL0, DIV_SEL0, 15, 12),
202 CLK_FULL(trace, 22, 18, DIV_SEL0, 20, 7, div_table6),
203 CLK_FULL(counter, 23, 20, DIV_SEL0, 23, 7, div_table6),
204 CLK_FULL_DD(eip97, 24, 24, DIV_SEL2, DIV_SEL2, 22, 19),
205 CLK_MUX_DIV(cpu, 22, DIV_SEL0, 28, 7, div_table6),
206 { },
207};
208
209/* SB periph clocks */
210static const struct clk_periph clks_sb[] = {
211 CLK_MUX_DD(gbe_50, 6, DIV_SEL2, DIV_SEL2, 6, 9),
212 CLK_MUX_DD(gbe_core, 8, DIV_SEL1, DIV_SEL1, 18, 21),
213 CLK_MUX_DD(gbe_125, 10, DIV_SEL1, DIV_SEL1, 6, 9),
214 CLK_GATE(gbe1_50, 0, "gbe_50"),
215 CLK_GATE(gbe0_50, 1, "gbe_50"),
216 CLK_GATE(gbe1_125, 2, "gbe_125"),
217 CLK_GATE(gbe0_125, 3, "gbe_125"),
218 CLK_GATE_DIV(gbe1_core, 4, DIV_SEL1, 13, 1, div_table1, "gbe_core"),
219 CLK_GATE_DIV(gbe0_core, 5, DIV_SEL1, 14, 1, div_table1, "gbe_core"),
220 CLK_GATE_DIV(gbe_bm, 12, DIV_SEL1, 0, 1, div_table1, "gbe_core"),
221 CLK_FULL_DD(sdio, 11, 14, DIV_SEL0, DIV_SEL0, 3, 6),
222 CLK_FULL_DD(usb32_usb2_sys, 16, 16, DIV_SEL0, DIV_SEL0, 9, 12),
223 CLK_FULL_DD(usb32_ss_sys, 17, 18, DIV_SEL0, DIV_SEL0, 15, 18),
224 { },
225};
226
Marek Behúnde75fb02018-08-17 12:58:52 +0200227static int get_mux(struct a37xx_periphclk *priv, int shift)
Marek Behún82a248d2018-04-24 17:21:25 +0200228{
229 return (readl(priv->reg + TBG_SEL) >> shift) & 3;
230}
231
Marek Behúnde75fb02018-08-17 12:58:52 +0200232static void set_mux(struct a37xx_periphclk *priv, int shift, int val)
233{
234 u32 reg;
235
236 reg = readl(priv->reg + TBG_SEL);
237 reg &= ~(3 << shift);
238 reg |= (val & 3) << shift;
239 writel(reg, priv->reg + TBG_SEL);
240}
241
Marek Behún82a248d2018-04-24 17:21:25 +0200242static ulong periph_clk_get_rate(struct a37xx_periphclk *priv, int id);
243
244static ulong get_parent_rate(struct a37xx_periphclk *priv, int id)
245{
246 const struct clk_periph *clk = &priv->clks[id];
247 ulong res;
248
249 if (clk->can_mux) {
250 /* parent is one of TBG clocks */
251 int tbg = get_mux(priv, clk->mux_shift);
252
253 res = priv->parents[tbg];
254 } else if (priv->clk_has_periph_parent[id]) {
255 /* parent is one of other periph clocks */
256
257 if (priv->clk_parent[id] >= priv->count)
258 return -EINVAL;
259
260 res = periph_clk_get_rate(priv, priv->clk_parent[id]);
261 } else {
262 /* otherwise parent is one of TBGs or XTAL */
263
264 if (priv->clk_parent[id] >= MAX_PARENTS)
265 return -EINVAL;
266
267 res = priv->parents[priv->clk_parent[id]];
268 }
269
270 return res;
271}
272
273static ulong get_div(struct a37xx_periphclk *priv,
274 const struct clk_periph *clk, int idx)
275{
276 const struct clk_div_table *i;
277 u32 reg;
278
279 reg = readl(priv->reg + clk->div_reg_off[idx]);
280 reg = (reg >> clk->div_shift[idx]) & clk->div_mask[idx];
281
282 /* find divisor for register value val */
283 for (i = clk->div_table[idx]; i && i->div != 0; ++i)
284 if (i->val == reg)
285 return i->div;
286
287 return 0;
288}
289
Marek Behúnde75fb02018-08-17 12:58:52 +0200290static void set_div_val(struct a37xx_periphclk *priv,
291 const struct clk_periph *clk, int idx, int val)
292{
293 u32 reg;
294
295 reg = readl(priv->reg + clk->div_reg_off[idx]);
296 reg &= ~(clk->div_mask[idx] << clk->div_shift[idx]);
297 reg |= (val & clk->div_mask[idx]) << clk->div_shift[idx];
298 writel(reg, priv->reg + clk->div_reg_off[idx]);
299}
300
Marek Behún82a248d2018-04-24 17:21:25 +0200301static ulong periph_clk_get_rate(struct a37xx_periphclk *priv, int id)
302{
303 const struct clk_periph *clk = &priv->clks[id];
304 ulong rate, div;
305 int i;
306
307 rate = get_parent_rate(priv, id);
308 if (rate == -EINVAL)
309 return -EINVAL;
310
311 /* divide the parent rate by dividers */
312 div = 1;
313 for (i = 0; i < clk->dividers; ++i)
314 div *= get_div(priv, clk, i);
315
316 if (!div)
317 return 0;
318
319 return DIV_ROUND_UP(rate, div);
320}
321
322static ulong armada_37xx_periph_clk_get_rate(struct clk *clk)
323{
324 struct a37xx_periphclk *priv = dev_get_priv(clk->dev);
325
326 if (clk->id >= priv->count)
327 return -EINVAL;
328
329 return periph_clk_get_rate(priv, clk->id);
330}
331
332static int periph_clk_enable(struct clk *clk, int enable)
333{
334 struct a37xx_periphclk *priv = dev_get_priv(clk->dev);
335 const struct clk_periph *periph_clk = &priv->clks[clk->id];
336
337 if (clk->id >= priv->count)
338 return -EINVAL;
339
340 if (!periph_clk->can_gate)
341 return -ENOTSUPP;
342
343 if (enable)
344 clrbits_le32(priv->reg + CLK_DIS, periph_clk->disable_bit);
345 else
346 setbits_le32(priv->reg + CLK_DIS, periph_clk->disable_bit);
347
348 return 0;
349}
350
351static int armada_37xx_periph_clk_enable(struct clk *clk)
352{
353 return periph_clk_enable(clk, 1);
354}
355
356static int armada_37xx_periph_clk_disable(struct clk *clk)
357{
358 return periph_clk_enable(clk, 0);
359}
360
Marek Behúnde75fb02018-08-17 12:58:52 +0200361#define diff(a, b) abs((long)(a) - (long)(b))
362
363static ulong find_best_div(const struct clk_div_table *t0,
364 const struct clk_div_table *t1, ulong parent_rate,
365 ulong req_rate, int *v0, int *v1)
366{
367 const struct clk_div_table *i, *j;
368 ulong rate, best_rate = 0;
369
370 for (i = t0; i && i->div; ++i) {
371 for (j = t1; j && j->div; ++j) {
372 rate = DIV_ROUND_UP(parent_rate, i->div * j->div);
373
374 if (!best_rate ||
375 diff(rate, req_rate) < diff(best_rate, req_rate)) {
376 best_rate = rate;
377 *v0 = i->val;
378 *v1 = j->val;
379 }
380 }
381 }
382
383 return best_rate;
384}
385
386static ulong armada_37xx_periph_clk_set_rate(struct clk *clk, ulong req_rate)
387{
388 struct a37xx_periphclk *priv = dev_get_priv(clk->dev);
389 const struct clk_periph *periph_clk = &priv->clks[clk->id];
390 ulong rate, old_rate, parent_rate;
391 int div_val0 = 0, div_val1 = 0;
392 const struct clk_div_table *t1;
393 static const struct clk_div_table empty_table[2] = {
394 { 1, 0 },
395 { 0, 0 }
396 };
397
398 if (clk->id > priv->count)
399 return -EINVAL;
400
401 old_rate = periph_clk_get_rate(priv, clk->id);
402 if (old_rate == -EINVAL)
403 return -EINVAL;
404
405 if (old_rate == req_rate)
406 return old_rate;
407
408 if (!periph_clk->can_gate || !periph_clk->dividers)
409 return -ENOTSUPP;
410
411 parent_rate = get_parent_rate(priv, clk->id);
412 if (parent_rate == -EINVAL)
413 return -EINVAL;
414
415 t1 = empty_table;
416 if (periph_clk->dividers > 1)
417 t1 = periph_clk->div_table[1];
418
419 rate = find_best_div(periph_clk->div_table[0], t1, parent_rate,
420 req_rate, &div_val0, &div_val1);
421
422 periph_clk_enable(clk, 0);
423
424 set_div_val(priv, periph_clk, 0, div_val0);
425 if (periph_clk->dividers > 1)
426 set_div_val(priv, periph_clk, 1, div_val1);
427
428 periph_clk_enable(clk, 1);
429
430 return rate;
431}
432
433static int armada_37xx_periph_clk_set_parent(struct clk *clk,
434 struct clk *parent)
435{
436 struct a37xx_periphclk *priv = dev_get_priv(clk->dev);
437 const struct clk_periph *periph_clk = &priv->clks[clk->id];
438 struct clk check_parent;
439 int ret;
440
441 /* We also check if parent is our TBG clock */
442 if (clk->id > priv->count || parent->id >= MAX_TBG_PARENTS)
443 return -EINVAL;
444
445 if (!periph_clk->can_mux || !periph_clk->can_gate)
446 return -ENOTSUPP;
447
448 ret = clk_get_by_index(clk->dev, 0, &check_parent);
449 if (ret < 0)
450 return ret;
451
452 if (parent->dev != check_parent.dev)
453 ret = -EINVAL;
454
455 clk_free(&check_parent);
456 if (ret < 0)
457 return ret;
458
459 periph_clk_enable(clk, 0);
460 set_mux(priv, periph_clk->mux_shift, parent->id);
461 periph_clk_enable(clk, 1);
462
463 return 0;
464}
465
Marek Behúndd776902018-04-24 17:21:27 +0200466#if defined(CONFIG_CMD_CLK) && defined(CONFIG_CLK_ARMADA_3720)
467static int armada_37xx_periph_clk_dump(struct udevice *dev)
Marek Behún82a248d2018-04-24 17:21:25 +0200468{
469 struct a37xx_periphclk *priv = dev_get_priv(dev);
470 const struct clk_periph *clks;
471 int i;
472
473 if (!priv)
474 return -ENODEV;
475
476 clks = priv->clks;
477
478 for (i = 0; i < priv->count; ++i)
479 printf(" %s at %lu Hz\n", clks[i].name,
480 periph_clk_get_rate(priv, i));
481 printf("\n");
482
483 return 0;
484}
485
Marek Behúndd776902018-04-24 17:21:27 +0200486static int clk_dump(const char *name, int (*func)(struct udevice *))
487{
488 struct udevice *dev;
489
490 if (uclass_get_device_by_name(UCLASS_CLK, name, &dev)) {
491 printf("Cannot find device %s\n", name);
492 return -ENODEV;
493 }
494
495 return func(dev);
496}
497
498int armada_37xx_tbg_clk_dump(struct udevice *);
499
500int soc_clk_dump(void)
501{
502 printf(" xtal at %u000000 Hz\n\n", get_ref_clk());
503
504 if (clk_dump("tbg@13200", armada_37xx_tbg_clk_dump))
505 return 1;
506
507 if (clk_dump("nb-periph-clk@13000",
508 armada_37xx_periph_clk_dump))
509 return 1;
510
511 if (clk_dump("sb-periph-clk@18000",
512 armada_37xx_periph_clk_dump))
513 return 1;
514
515 return 0;
516}
517#endif
518
Marek Behún82a248d2018-04-24 17:21:25 +0200519static int armada_37xx_periph_clk_probe(struct udevice *dev)
520{
521 struct a37xx_periphclk *priv = dev_get_priv(dev);
522 const struct clk_periph *clks;
523 int ret, i;
524
525 clks = (const struct clk_periph *)dev_get_driver_data(dev);
526 if (!clks)
527 return -ENODEV;
528
529 priv->reg = dev_read_addr_ptr(dev);
530 if (!priv->reg) {
531 dev_err(dev, "no io address\n");
532 return -ENODEV;
533 }
534
535 /* count clk_periph nodes */
536 priv->count = 0;
537 while (clks[priv->count].name)
538 priv->count++;
539
540 priv->clks = clks;
541
542 /* assign parent IDs to nodes which have non-NULL parent_name */
543 for (i = 0; i < priv->count; ++i) {
544 int j;
545
546 if (!clks[i].parent_name)
547 continue;
548
549 /* first try if parent_name is one of TBGs or XTAL */
550 for (j = 0; j < MAX_PARENTS; ++j)
551 if (!strcmp(clks[i].parent_name,
552 a37xx_periph_parent_names[j].name))
553 break;
554
555 if (j < MAX_PARENTS) {
556 priv->clk_has_periph_parent[i] = false;
557 priv->clk_parent[i] =
558 a37xx_periph_parent_names[j].parent;
559 continue;
560 }
561
562 /* else parent_name should be one of other periph clocks */
563 for (j = 0; j < priv->count; ++j) {
564 if (!strcmp(clks[i].parent_name, clks[j].name))
565 break;
566 }
567
568 if (j < priv->count) {
569 priv->clk_has_periph_parent[i] = true;
570 priv->clk_parent[i] = j;
571 continue;
572 }
573
574 dev_err(dev, "undefined parent %s\n", clks[i].parent_name);
575 return -EINVAL;
576 }
577
578 for (i = 0; i < MAX_PARENTS; ++i) {
579 struct clk clk;
580
581 if (i == XTAL) {
582 priv->parents[i] = get_ref_clk() * 1000000;
583 continue;
584 }
585
586 ret = clk_get_by_index(dev, i, &clk);
587 if (ret) {
588 dev_err(dev, "one of parent clocks (%i) missing: %i\n",
589 i, ret);
590 return -ENODEV;
591 }
592
593 priv->parents[i] = clk_get_rate(&clk);
594 clk_free(&clk);
595 }
596
597 return 0;
598}
599
600static const struct clk_ops armada_37xx_periph_clk_ops = {
601 .get_rate = armada_37xx_periph_clk_get_rate,
Marek Behúnde75fb02018-08-17 12:58:52 +0200602 .set_rate = armada_37xx_periph_clk_set_rate,
603 .set_parent = armada_37xx_periph_clk_set_parent,
Marek Behún82a248d2018-04-24 17:21:25 +0200604 .enable = armada_37xx_periph_clk_enable,
605 .disable = armada_37xx_periph_clk_disable,
606};
607
608static const struct udevice_id armada_37xx_periph_clk_ids[] = {
609 {
610 .compatible = "marvell,armada-3700-periph-clock-nb",
611 .data = (ulong)clks_nb,
612 },
613 {
614 .compatible = "marvell,armada-3700-periph-clock-sb",
615 .data = (ulong)clks_sb,
616 },
617 {}
618};
619
620U_BOOT_DRIVER(armada_37xx_periph_clk) = {
621 .name = "armada_37xx_periph_clk",
622 .id = UCLASS_CLK,
623 .of_match = armada_37xx_periph_clk_ids,
624 .ops = &armada_37xx_periph_clk_ops,
625 .priv_auto_alloc_size = sizeof(struct a37xx_periphclk),
626 .probe = armada_37xx_periph_clk_probe,
627};