blob: 5d93e6a7e52952d6e285cf3e6a1c65a7999e4141 [file] [log] [blame]
Claudiu Bezneab4c4e182020-09-07 17:46:43 +03001// 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 <claudiu.beznea@microchip.com>
8 *
9 * Based on drivers/clk/at91/clk-master.c from Linux.
10 */
11
12#include <asm/processor.h>
13#include <clk-uclass.h>
14#include <common.h>
15#include <dm.h>
16#include <linux/clk-provider.h>
17#include <linux/clk/at91_pmc.h>
18
19#include "pmc.h"
20
21#define UBOOT_DM_CLK_AT91_MASTER "at91-master-clk"
Claudiu Bezneadd4d19d2020-09-07 17:46:44 +030022#define UBOOT_DM_CLK_AT91_SAMA7G5_MASTER "at91-sama7g5-master-clk"
Claudiu Bezneab4c4e182020-09-07 17:46:43 +030023
24#define MASTER_PRES_MASK 0x7
25#define MASTER_PRES_MAX MASTER_PRES_MASK
26#define MASTER_DIV_SHIFT 8
Eugen Hristevdff39042020-07-01 10:42:58 +030027#define MASTER_DIV_MASK 0x7
Claudiu Bezneab4c4e182020-09-07 17:46:43 +030028
Claudiu Bezneadd4d19d2020-09-07 17:46:44 +030029#define PMC_MCR 0x30
30#define PMC_MCR_ID_MSK GENMASK(3, 0)
31#define PMC_MCR_CMD BIT(7)
32#define PMC_MCR_DIV GENMASK(10, 8)
33#define PMC_MCR_CSS GENMASK(20, 16)
34#define PMC_MCR_CSS_SHIFT (16)
35#define PMC_MCR_EN BIT(28)
36
37#define PMC_MCR_ID(x) ((x) & PMC_MCR_ID_MSK)
38
39#define MASTER_MAX_ID 4
40
Claudiu Bezneab4c4e182020-09-07 17:46:43 +030041struct clk_master {
42 void __iomem *base;
43 const struct clk_master_layout *layout;
44 const struct clk_master_characteristics *characteristics;
45 const u32 *mux_table;
46 const u32 *clk_mux_table;
47 u32 num_parents;
48 struct clk clk;
49 u8 id;
50};
51
52#define to_clk_master(_clk) container_of(_clk, struct clk_master, clk)
53
54static inline bool clk_master_ready(struct clk_master *master)
55{
Claudiu Bezneadd4d19d2020-09-07 17:46:44 +030056 unsigned int bit = master->id ? AT91_PMC_MCKXRDY : AT91_PMC_MCKRDY;
Claudiu Bezneab4c4e182020-09-07 17:46:43 +030057 unsigned int status;
58
59 pmc_read(master->base, AT91_PMC_SR, &status);
60
Claudiu Bezneadd4d19d2020-09-07 17:46:44 +030061 return !!(status & bit);
Claudiu Bezneab4c4e182020-09-07 17:46:43 +030062}
63
64static int clk_master_enable(struct clk *clk)
65{
66 struct clk_master *master = to_clk_master(clk);
67
68 while (!clk_master_ready(master)) {
69 debug("waiting for mck %d\n", master->id);
70 cpu_relax();
71 }
72
73 return 0;
74}
75
76static ulong clk_master_get_rate(struct clk *clk)
77{
78 struct clk_master *master = to_clk_master(clk);
79 const struct clk_master_layout *layout = master->layout;
80 const struct clk_master_characteristics *characteristics =
81 master->characteristics;
82 ulong rate = clk_get_parent_rate(clk);
83 unsigned int mckr;
84 u8 pres, div;
85
86 if (!rate)
87 return 0;
88
89 pmc_read(master->base, master->layout->offset, &mckr);
90 mckr &= layout->mask;
91
92 pres = (mckr >> layout->pres_shift) & MASTER_PRES_MASK;
93 div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
94
95 if (characteristics->have_div3_pres && pres == MASTER_PRES_MAX)
96 rate /= 3;
97 else
98 rate >>= pres;
99
100 rate /= characteristics->divisors[div];
101
102 if (rate < characteristics->output.min)
103 pr_warn("master clk is underclocked");
104 else if (rate > characteristics->output.max)
105 pr_warn("master clk is overclocked");
106
107 return rate;
108}
109
110static const struct clk_ops master_ops = {
111 .enable = clk_master_enable,
112 .get_rate = clk_master_get_rate,
113};
114
115struct clk *at91_clk_register_master(void __iomem *base,
116 const char *name, const char * const *parent_names,
117 int num_parents, const struct clk_master_layout *layout,
118 const struct clk_master_characteristics *characteristics,
119 const u32 *mux_table)
120{
121 struct clk_master *master;
122 struct clk *clk;
123 unsigned int val;
124 int ret;
125
126 if (!base || !name || !num_parents || !parent_names ||
127 !layout || !characteristics || !mux_table)
128 return ERR_PTR(-EINVAL);
129
130 master = kzalloc(sizeof(*master), GFP_KERNEL);
131 if (!master)
132 return ERR_PTR(-ENOMEM);
133
134 master->layout = layout;
135 master->characteristics = characteristics;
136 master->base = base;
137 master->num_parents = num_parents;
138 master->mux_table = mux_table;
139
140 pmc_read(master->base, master->layout->offset, &val);
141 clk = &master->clk;
142 clk->flags = CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL;
143 ret = clk_register(clk, UBOOT_DM_CLK_AT91_MASTER, name,
144 parent_names[val & AT91_PMC_CSS]);
145 if (ret) {
146 kfree(master);
147 clk = ERR_PTR(ret);
148 }
149
150 return clk;
151}
152
153U_BOOT_DRIVER(at91_master_clk) = {
154 .name = UBOOT_DM_CLK_AT91_MASTER,
155 .id = UCLASS_CLK,
156 .ops = &master_ops,
157 .flags = DM_FLAG_PRE_RELOC,
158};
159
Claudiu Bezneadd4d19d2020-09-07 17:46:44 +0300160static int clk_sama7g5_master_set_parent(struct clk *clk, struct clk *parent)
161{
162 struct clk_master *master = to_clk_master(clk);
163 int index;
164
165 index = at91_clk_mux_val_to_index(master->clk_mux_table,
166 master->num_parents, parent->id);
167 if (index < 0)
168 return index;
169
170 index = at91_clk_mux_index_to_val(master->mux_table,
171 master->num_parents, index);
172 if (index < 0)
173 return index;
174
175 pmc_write(master->base, PMC_MCR, PMC_MCR_ID(master->id));
176 pmc_update_bits(master->base, PMC_MCR,
177 PMC_MCR_CSS | PMC_MCR_CMD | PMC_MCR_ID_MSK,
178 (index << PMC_MCR_CSS_SHIFT) | PMC_MCR_CMD |
179 PMC_MCR_ID(master->id));
180 return 0;
181}
182
183static int clk_sama7g5_master_enable(struct clk *clk)
184{
185 struct clk_master *master = to_clk_master(clk);
186
187 pmc_write(master->base, PMC_MCR, PMC_MCR_ID(master->id));
188 pmc_update_bits(master->base, PMC_MCR,
189 PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID_MSK,
190 PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID(master->id));
191
192 return 0;
193}
194
195static int clk_sama7g5_master_disable(struct clk *clk)
196{
197 struct clk_master *master = to_clk_master(clk);
198
199 pmc_write(master->base, PMC_MCR, master->id);
200 pmc_update_bits(master->base, PMC_MCR,
201 PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID_MSK,
202 PMC_MCR_CMD | PMC_MCR_ID(master->id));
203
204 return 0;
205}
206
207static ulong clk_sama7g5_master_set_rate(struct clk *clk, ulong rate)
208{
209 struct clk_master *master = to_clk_master(clk);
210 ulong parent_rate = clk_get_parent_rate(clk);
211 ulong div, rrate;
212
213 if (!parent_rate)
214 return 0;
215
216 div = DIV_ROUND_CLOSEST(parent_rate, rate);
217 if ((div > (1 << (MASTER_PRES_MAX - 1))) || (div & (div - 1))) {
218 return 0;
219 } else if (div == 3) {
220 rrate = DIV_ROUND_CLOSEST(parent_rate, MASTER_PRES_MAX);
221 div = MASTER_PRES_MAX;
222 } else {
223 rrate = DIV_ROUND_CLOSEST(parent_rate, div);
224 div = ffs(div) - 1;
225 }
226
227 pmc_write(master->base, PMC_MCR, master->id);
228 pmc_update_bits(master->base, PMC_MCR,
229 PMC_MCR_DIV | PMC_MCR_CMD | PMC_MCR_ID_MSK,
230 (div << MASTER_DIV_SHIFT) | PMC_MCR_CMD |
231 PMC_MCR_ID(master->id));
232
233 return rrate;
234}
235
236static ulong clk_sama7g5_master_get_rate(struct clk *clk)
237{
238 struct clk_master *master = to_clk_master(clk);
239 ulong parent_rate = clk_get_parent_rate(clk);
240 unsigned int val;
241 ulong div;
242
243 if (!parent_rate)
244 return 0;
245
246 pmc_write(master->base, PMC_MCR, master->id);
247 pmc_read(master->base, PMC_MCR, &val);
248
249 div = (val >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
250
251 if (div == MASTER_PRES_MAX)
252 div = 3;
253 else
254 div = 1 << div;
255
256 return DIV_ROUND_CLOSEST(parent_rate, div);
257}
258
259static const struct clk_ops sama7g5_master_ops = {
260 .enable = clk_sama7g5_master_enable,
261 .disable = clk_sama7g5_master_disable,
262 .set_rate = clk_sama7g5_master_set_rate,
263 .get_rate = clk_sama7g5_master_get_rate,
264 .set_parent = clk_sama7g5_master_set_parent,
265};
266
267struct clk *at91_clk_sama7g5_register_master(void __iomem *base,
268 const char *name, const char * const *parent_names,
269 int num_parents, const u32 *mux_table, const u32 *clk_mux_table,
270 bool critical, u8 id)
271{
272 struct clk_master *master;
273 struct clk *clk;
274 u32 val, index;
275 int ret;
276
277 if (!base || !name || !num_parents || !parent_names ||
278 !mux_table || !clk_mux_table || id > MASTER_MAX_ID)
279 return ERR_PTR(-EINVAL);
280
281 master = kzalloc(sizeof(*master), GFP_KERNEL);
282 if (!master)
283 return ERR_PTR(-ENOMEM);
284
285 master->base = base;
286 master->id = id;
287 master->mux_table = mux_table;
288 master->clk_mux_table = clk_mux_table;
289 master->num_parents = num_parents;
290
291 pmc_write(master->base, PMC_MCR, master->id);
292 pmc_read(master->base, PMC_MCR, &val);
293
294 index = at91_clk_mux_val_to_index(master->mux_table,
295 master->num_parents,
296 (val & PMC_MCR_CSS) >> PMC_MCR_CSS_SHIFT);
297 if (index < 0) {
298 kfree(master);
299 return ERR_PTR(index);
300 }
301
302 clk = &master->clk;
303 clk->flags = CLK_GET_RATE_NOCACHE | (critical ? CLK_IS_CRITICAL : 0);
304
305 ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAMA7G5_MASTER, name,
306 parent_names[index]);
307 if (ret) {
308 kfree(master);
309 clk = ERR_PTR(ret);
310 }
311
312 return clk;
313}
314
315U_BOOT_DRIVER(at91_sama7g5_master_clk) = {
316 .name = UBOOT_DM_CLK_AT91_SAMA7G5_MASTER,
317 .id = UCLASS_CLK,
318 .ops = &sama7g5_master_ops,
319 .flags = DM_FLAG_PRE_RELOC,
320};
321
Claudiu Bezneab4c4e182020-09-07 17:46:43 +0300322const struct clk_master_layout at91rm9200_master_layout = {
323 .mask = 0x31F,
324 .pres_shift = 2,
325 .offset = AT91_PMC_MCKR,
326};
327
328const struct clk_master_layout at91sam9x5_master_layout = {
329 .mask = 0x373,
330 .pres_shift = 4,
331 .offset = AT91_PMC_MCKR,
332};