]>
Commit | Line | Data |
---|---|---|
9e5935c0 WY |
1 | /* |
2 | * Copyright (C) 2016 Atmel Corporation | |
3 | * Wenyou.Yang <wenyou.yang@atmel.com> | |
4 | * | |
5 | * SPDX-License-Identifier: GPL-2.0+ | |
6 | */ | |
7 | ||
8 | #include <common.h> | |
9 | #include <clk-uclass.h> | |
9d922450 | 10 | #include <dm.h> |
9e5935c0 WY |
11 | #include <linux/io.h> |
12 | #include <mach/at91_pmc.h> | |
13 | #include "pmc.h" | |
14 | ||
15 | DECLARE_GLOBAL_DATA_PTR; | |
16 | ||
17 | #define GENERATED_SOURCE_MAX 6 | |
18 | #define GENERATED_MAX_DIV 255 | |
19 | ||
6cadaa04 WY |
20 | /** |
21 | * generated_clk_bind() - for the generated clock driver | |
22 | * Recursively bind its children as clk devices. | |
23 | * | |
24 | * @return: 0 on success, or negative error code on failure | |
25 | */ | |
26 | static int generated_clk_bind(struct udevice *dev) | |
27 | { | |
28 | return at91_clk_sub_device_bind(dev, "generic-clk"); | |
29 | } | |
30 | ||
31 | static const struct udevice_id generated_clk_match[] = { | |
32 | { .compatible = "atmel,sama5d2-clk-generated" }, | |
33 | {} | |
34 | }; | |
35 | ||
36 | U_BOOT_DRIVER(generated_clk) = { | |
37 | .name = "generated-clk", | |
38 | .id = UCLASS_MISC, | |
39 | .of_match = generated_clk_match, | |
40 | .bind = generated_clk_bind, | |
41 | }; | |
42 | ||
43 | /*-------------------------------------------------------------*/ | |
44 | ||
45 | struct generic_clk_priv { | |
9e5935c0 WY |
46 | u32 num_parents; |
47 | }; | |
48 | ||
6cadaa04 | 49 | static ulong generic_clk_get_rate(struct clk *clk) |
9e5935c0 WY |
50 | { |
51 | struct pmc_platdata *plat = dev_get_platdata(clk->dev); | |
52 | struct at91_pmc *pmc = plat->reg_base; | |
53 | struct clk parent; | |
6cadaa04 | 54 | ulong clk_rate; |
9e5935c0 | 55 | u32 tmp, gckdiv; |
63a80b8d | 56 | u8 clock_source, parent_index; |
9e5935c0 WY |
57 | int ret; |
58 | ||
59 | writel(clk->id & AT91_PMC_PCR_PID_MASK, &pmc->pcr); | |
60 | tmp = readl(&pmc->pcr); | |
63a80b8d | 61 | clock_source = (tmp >> AT91_PMC_PCR_GCKCSS_OFFSET) & |
9e5935c0 WY |
62 | AT91_PMC_PCR_GCKCSS_MASK; |
63 | gckdiv = (tmp >> AT91_PMC_PCR_GCKDIV_OFFSET) & AT91_PMC_PCR_GCKDIV_MASK; | |
64 | ||
63a80b8d WY |
65 | parent_index = clock_source - 1; |
66 | ret = clk_get_by_index(dev_get_parent(clk->dev), parent_index, &parent); | |
9e5935c0 WY |
67 | if (ret) |
68 | return 0; | |
69 | ||
6cadaa04 WY |
70 | clk_rate = clk_get_rate(&parent) / (gckdiv + 1); |
71 | ||
72 | clk_free(&parent); | |
73 | ||
74 | return clk_rate; | |
9e5935c0 WY |
75 | } |
76 | ||
6cadaa04 | 77 | static ulong generic_clk_set_rate(struct clk *clk, ulong rate) |
9e5935c0 WY |
78 | { |
79 | struct pmc_platdata *plat = dev_get_platdata(clk->dev); | |
80 | struct at91_pmc *pmc = plat->reg_base; | |
6cadaa04 | 81 | struct generic_clk_priv *priv = dev_get_priv(clk->dev); |
9e5935c0 WY |
82 | struct clk parent, best_parent; |
83 | ulong tmp_rate, best_rate = rate, parent_rate; | |
84 | int tmp_diff, best_diff = -1; | |
85 | u32 div, best_div = 0; | |
63a80b8d | 86 | u8 best_parent_index, best_clock_source = 0; |
9e5935c0 WY |
87 | u8 i; |
88 | u32 tmp; | |
89 | int ret; | |
90 | ||
91 | for (i = 0; i < priv->num_parents; i++) { | |
6cadaa04 | 92 | ret = clk_get_by_index(dev_get_parent(clk->dev), i, &parent); |
9e5935c0 WY |
93 | if (ret) |
94 | return ret; | |
95 | ||
96 | parent_rate = clk_get_rate(&parent); | |
97 | if (IS_ERR_VALUE(parent_rate)) | |
98 | return parent_rate; | |
99 | ||
100 | for (div = 1; div < GENERATED_MAX_DIV + 2; div++) { | |
101 | tmp_rate = DIV_ROUND_CLOSEST(parent_rate, div); | |
eb1ed2b1 | 102 | tmp_diff = abs(rate - tmp_rate); |
9e5935c0 WY |
103 | |
104 | if (best_diff < 0 || best_diff > tmp_diff) { | |
105 | best_rate = tmp_rate; | |
106 | best_diff = tmp_diff; | |
107 | ||
108 | best_div = div - 1; | |
109 | best_parent = parent; | |
63a80b8d WY |
110 | best_parent_index = i; |
111 | best_clock_source = best_parent_index + 1; | |
9e5935c0 WY |
112 | } |
113 | ||
114 | if (!best_diff || tmp_rate < rate) | |
115 | break; | |
116 | } | |
117 | ||
118 | if (!best_diff) | |
119 | break; | |
120 | } | |
121 | ||
122 | debug("GCK: best parent: %s, best_rate = %ld, best_div = %d\n", | |
123 | best_parent.dev->name, best_rate, best_div); | |
124 | ||
125 | ret = clk_enable(&best_parent); | |
126 | if (ret) | |
127 | return ret; | |
128 | ||
129 | writel(clk->id & AT91_PMC_PCR_PID_MASK, &pmc->pcr); | |
130 | tmp = readl(&pmc->pcr); | |
131 | tmp &= ~(AT91_PMC_PCR_GCKDIV | AT91_PMC_PCR_GCKCSS); | |
63a80b8d | 132 | tmp |= AT91_PMC_PCR_GCKCSS_(best_clock_source) | |
9e5935c0 WY |
133 | AT91_PMC_PCR_CMD_WRITE | |
134 | AT91_PMC_PCR_GCKDIV_(best_div) | | |
135 | AT91_PMC_PCR_GCKEN; | |
136 | writel(tmp, &pmc->pcr); | |
137 | ||
138 | while (!(readl(&pmc->sr) & AT91_PMC_GCKRDY)) | |
139 | ; | |
140 | ||
141 | return 0; | |
142 | } | |
143 | ||
6cadaa04 WY |
144 | static struct clk_ops generic_clk_ops = { |
145 | .of_xlate = at91_clk_of_xlate, | |
146 | .get_rate = generic_clk_get_rate, | |
147 | .set_rate = generic_clk_set_rate, | |
9e5935c0 WY |
148 | }; |
149 | ||
6cadaa04 | 150 | static int generic_clk_ofdata_to_platdata(struct udevice *dev) |
9e5935c0 | 151 | { |
6cadaa04 | 152 | struct generic_clk_priv *priv = dev_get_priv(dev); |
9e5935c0 WY |
153 | u32 cells[GENERATED_SOURCE_MAX]; |
154 | u32 num_parents; | |
155 | ||
6cadaa04 | 156 | num_parents = fdtdec_get_int_array_count(gd->fdt_blob, |
e160f7d4 SG |
157 | dev_of_offset(dev_get_parent(dev)), "clocks", cells, |
158 | GENERATED_SOURCE_MAX); | |
9e5935c0 WY |
159 | |
160 | if (!num_parents) | |
161 | return -1; | |
162 | ||
163 | priv->num_parents = num_parents; | |
164 | ||
165 | return 0; | |
166 | } | |
167 | ||
6cadaa04 WY |
168 | U_BOOT_DRIVER(generic_clk) = { |
169 | .name = "generic-clk", | |
9e5935c0 | 170 | .id = UCLASS_CLK, |
6cadaa04 WY |
171 | .probe = at91_clk_probe, |
172 | .ofdata_to_platdata = generic_clk_ofdata_to_platdata, | |
173 | .priv_auto_alloc_size = sizeof(struct generic_clk_priv), | |
9e5935c0 | 174 | .platdata_auto_alloc_size = sizeof(struct pmc_platdata), |
6cadaa04 | 175 | .ops = &generic_clk_ops, |
9e5935c0 | 176 | }; |