blob: b38e559e45a64cb6c56ca2bc4c11e14f46ab7e2c [file] [log] [blame]
Tero Kristo82ceb0d2021-06-11 11:45:14 +03001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Texas Instruments K3 clock driver
4 *
Nishanth Menoneaa39c62023-11-01 15:56:03 -05005 * Copyright (C) 2020-2021 Texas Instruments Incorporated - https://www.ti.com/
Tero Kristo82ceb0d2021-06-11 11:45:14 +03006 * Tero Kristo <t-kristo@ti.com>
7 */
8
Tero Kristo82ceb0d2021-06-11 11:45:14 +03009#include <dm.h>
10#include <errno.h>
11#include <soc.h>
12#include <clk-uclass.h>
Udit Kumarc648daa2023-09-21 22:30:38 +053013#include <k3-avs.h>
Tero Kristo82ceb0d2021-06-11 11:45:14 +030014#include "k3-clk.h"
15
16#define PLL_MIN_FREQ 800000000
17#define PLL_MAX_FREQ 3200000000UL
18#define PLL_MAX_DIV 127
19
20/**
21 * struct clk_map - mapping from dev/clk id tuples towards physical clocks
22 * @dev_id: device ID for the clock
23 * @clk_id: clock ID for the clock
24 * @clk: pointer to the registered clock entry for the mapping
25 */
26struct clk_map {
27 u16 dev_id;
28 u32 clk_id;
29 struct clk *clk;
30};
31
32/**
33 * struct ti_clk_data - clock controller information structure
34 * @map: mapping from dev/clk id tuples to physical clock entries
35 * @size: number of entries in the map
36 */
37struct ti_clk_data {
38 struct clk_map *map;
39 int size;
40};
41
42static ulong osc_freq;
43
44static void clk_add_map(struct ti_clk_data *data, struct clk *clk,
45 u32 dev_id, u32 clk_id)
46{
47 struct clk_map *map;
48
49 debug("%s: added clk=%p, data=%p, dev=%d, clk=%d\n", __func__,
50 clk, data, dev_id, clk_id);
51 if (!clk)
52 return;
53
54 map = data->map + data->size++;
55
56 map->dev_id = dev_id;
57 map->clk_id = clk_id;
58 map->clk = clk;
59}
60
61static const struct soc_attr ti_k3_soc_clk_data[] = {
Jayesh Choudhary21b21772024-06-12 14:41:13 +053062#if IS_ENABLED(CONFIG_SOC_K3_AM625)
63 {
64 .family = "AM62X",
65 .data = &am62x_clk_platdata,
66 },
67#endif
68#if IS_ENABLED(CONFIG_SOC_K3_AM62A7)
69 {
70 .family = "AM62AX",
71 .data = &am62ax_clk_platdata,
72 },
73#endif
74#if IS_ENABLED(CONFIG_SOC_K3_AM62P5)
75 {
76 .family = "AM62PX",
77 .data = &am62px_clk_platdata,
78 },
79#endif
Tero Kristo82ceb0d2021-06-11 11:45:14 +030080#if IS_ENABLED(CONFIG_SOC_K3_J721E)
81 {
82 .family = "J721E",
83 .data = &j721e_clk_platdata,
84 },
Andrew Davisbb6cc992025-03-19 13:54:58 -050085#endif
86#if IS_ENABLED(CONFIG_SOC_K3_J7200)
Tero Kristo82ceb0d2021-06-11 11:45:14 +030087 {
88 .family = "J7200",
89 .data = &j7200_clk_platdata,
90 },
Jayesh Choudhary21b21772024-06-12 14:41:13 +053091#endif
92#if IS_ENABLED(CONFIG_SOC_K3_J721S2)
David Huange04854b2022-01-25 20:56:33 +053093 {
94 .family = "J721S2",
95 .data = &j721s2_clk_platdata,
96 },
Tero Kristo82ceb0d2021-06-11 11:45:14 +030097#endif
Jayesh Choudhary9332a6372024-06-12 14:41:16 +053098#if IS_ENABLED(CONFIG_SOC_K3_J722S)
99 {
100 .family = "J722S",
101 .data = &j722s_clk_platdata,
102 },
103#endif
Jayesh Choudhary21b21772024-06-12 14:41:13 +0530104#if IS_ENABLED(CONFIG_SOC_K3_J784S4)
Apurva Nandanb93ab922024-02-24 01:51:44 +0530105 {
106 .family = "J784S4",
107 .data = &j784s4_clk_platdata,
108 },
Manorit Chawdhryb5a384e2025-03-17 10:24:24 +0530109 {
110 .family = "J742S2",
111 .data = &j784s4_clk_platdata,
112 },
Apurva Nandanb93ab922024-02-24 01:51:44 +0530113#endif
Tero Kristo82ceb0d2021-06-11 11:45:14 +0300114 { /* sentinel */ }
115};
116
117static int ti_clk_probe(struct udevice *dev)
118{
119 struct ti_clk_data *data = dev_get_priv(dev);
120 struct clk *clk;
121 const char *name;
122 const struct clk_data *ti_clk_data;
123 int i, j;
124 const struct soc_attr *soc_match_data;
125 const struct ti_k3_clk_platdata *pdata;
126
127 debug("%s(dev=%p)\n", __func__, dev);
128
129 soc_match_data = soc_device_match(ti_k3_soc_clk_data);
130 if (!soc_match_data)
131 return -ENODEV;
132
133 pdata = (const struct ti_k3_clk_platdata *)soc_match_data->data;
134
135 data->map = kcalloc(pdata->soc_dev_clk_data_cnt, sizeof(*data->map),
136 GFP_KERNEL);
137 data->size = 0;
138
139 for (i = 0; i < pdata->clk_list_cnt; i++) {
140 ti_clk_data = &pdata->clk_list[i];
141
142 switch (ti_clk_data->type) {
143 case CLK_TYPE_FIXED_RATE:
144 name = ti_clk_data->clk.fixed_rate.name;
145 clk = clk_register_fixed_rate(NULL,
146 name,
147 ti_clk_data->clk.fixed_rate.rate);
148 break;
149 case CLK_TYPE_DIV:
150 name = ti_clk_data->clk.div.name;
151 clk = clk_register_divider(NULL, name,
152 ti_clk_data->clk.div.parent,
153 ti_clk_data->clk.div.flags,
154 map_physmem(ti_clk_data->clk.div.reg, 0, MAP_NOCACHE),
155 ti_clk_data->clk.div.shift,
156 ti_clk_data->clk.div.width,
Suman Annadb4c2dc2021-09-07 17:16:58 -0500157 ti_clk_data->clk.div.div_flags);
Tero Kristo82ceb0d2021-06-11 11:45:14 +0300158 break;
159 case CLK_TYPE_MUX:
160 name = ti_clk_data->clk.mux.name;
161 clk = clk_register_mux(NULL, name,
162 ti_clk_data->clk.mux.parents,
163 ti_clk_data->clk.mux.num_parents,
164 ti_clk_data->clk.mux.flags,
165 map_physmem(ti_clk_data->clk.mux.reg, 0, MAP_NOCACHE),
166 ti_clk_data->clk.mux.shift,
167 ti_clk_data->clk.mux.width,
168 0);
169 break;
170 case CLK_TYPE_PLL:
171 name = ti_clk_data->clk.pll.name;
172 clk = clk_register_ti_pll(name,
173 ti_clk_data->clk.pll.parent,
174 map_physmem(ti_clk_data->clk.pll.reg, 0, MAP_NOCACHE));
175
176 if (!osc_freq)
177 osc_freq = clk_get_rate(clk_get_parent(clk));
178 break;
179 default:
180 name = NULL;
181 clk = NULL;
182 printf("WARNING: %s has encountered unknown clk type %d\n",
183 __func__, ti_clk_data->type);
184 }
185
186 if (clk && ti_clk_data->default_freq)
187 clk_set_rate(clk, ti_clk_data->default_freq);
188
189 if (clk && name) {
190 for (j = 0; j < pdata->soc_dev_clk_data_cnt; j++) {
191 if (!strcmp(name, pdata->soc_dev_clk_data[j].clk_name)) {
192 clk_add_map(data, clk, pdata->soc_dev_clk_data[j].dev_id,
193 pdata->soc_dev_clk_data[j].clk_id);
194 }
195 }
196 }
197 }
198
199 return 0;
200}
201
202static int _clk_cmp(u32 dev_id, u32 clk_id, const struct clk_map *map)
203{
204 if (map->dev_id == dev_id && map->clk_id == clk_id)
205 return 0;
206 if (map->dev_id > dev_id ||
207 (map->dev_id == dev_id && map->clk_id > clk_id))
208 return -1;
209 return 1;
210}
211
212static int bsearch(u32 dev_id, u32 clk_id, struct clk_map *map, int num)
213{
214 int result;
215 int idx;
216
217 for (idx = 0; idx < num; idx++) {
218 result = _clk_cmp(dev_id, clk_id, &map[idx]);
219
220 if (result == 0)
221 return idx;
222 }
223
224 return -ENOENT;
225}
226
227static int ti_clk_of_xlate(struct clk *clk,
228 struct ofnode_phandle_args *args)
229{
230 struct ti_clk_data *data = dev_get_priv(clk->dev);
231 int idx;
232
233 debug("%s(clk=%p, args_count=%d [0]=%d [1]=%d)\n", __func__, clk,
234 args->args_count, args->args[0], args->args[1]);
235
236 if (args->args_count != 2) {
237 debug("Invalid args_count: %d\n", args->args_count);
238 return -EINVAL;
239 }
240
241 if (!data->size)
242 return -EPROBE_DEFER;
243
244 idx = bsearch(args->args[0], args->args[1], data->map, data->size);
245 if (idx < 0)
246 return idx;
247
248 clk->id = idx;
249
250 return 0;
251}
252
253static ulong ti_clk_get_rate(struct clk *clk)
254{
255 struct ti_clk_data *data = dev_get_priv(clk->dev);
256 struct clk *clkp = data->map[clk->id].clk;
257
258 return clk_get_rate(clkp);
259}
260
261static ulong ti_clk_set_rate(struct clk *clk, ulong rate)
262{
263 struct ti_clk_data *data = dev_get_priv(clk->dev);
264 struct clk *clkp = data->map[clk->id].clk;
265 int div = 1;
266 ulong child_rate;
267 const struct clk_ops *ops;
268 ulong new_rate, rem;
269 ulong diff, new_diff;
Udit Kumarc648daa2023-09-21 22:30:38 +0530270 int freq_scale_up = rate >= ti_clk_get_rate(clk) ? 1 : 0;
Tero Kristo82ceb0d2021-06-11 11:45:14 +0300271
Udit Kumarc648daa2023-09-21 22:30:38 +0530272 if (IS_ENABLED(CONFIG_K3_AVS0) && freq_scale_up)
273 k3_avs_notify_freq(data->map[clk->id].dev_id,
274 data->map[clk->id].clk_id, rate);
Tero Kristo82ceb0d2021-06-11 11:45:14 +0300275 /*
276 * We must propagate rate change to parent if current clock type
277 * does not allow setting it.
278 */
279 while (clkp) {
280 ops = clkp->dev->driver->ops;
281 if (ops->set_rate)
282 break;
283
284 /*
285 * Store child rate so we can calculate the clock rate
286 * that must be passed to parent
287 */
288 child_rate = clk_get_rate(clkp);
289 clkp = clk_get_parent(clkp);
290 if (clkp) {
291 debug("%s: propagating rate change to parent %s, rate=%u.\n",
292 __func__, clkp->dev->name, (u32)rate / div);
293 div *= clk_get_rate(clkp) / child_rate;
294 }
295 }
296
297 if (!clkp)
298 return -ENOSYS;
299
300 child_rate = clk_get_rate(clkp);
301
302 new_rate = clk_set_rate(clkp, rate / div);
303
304 diff = abs(new_rate - rate / div);
305
306 debug("%s: clk=%s, div=%d, rate=%u, new_rate=%u, diff=%u\n", __func__,
307 clkp->dev->name, div, (u32)rate, (u32)new_rate, (u32)diff);
308
309 /*
310 * If the new rate differs by 50% of the target,
311 * modify parent. This handles typical cases where we have a hsdiv
312 * following directly a PLL
313 */
314
315 if (diff > rate / div / 2) {
316 ulong pll_tgt;
317 int pll_div = 0;
318
319 clk = clkp;
320
321 debug("%s: propagating rate change to parent, rate=%u.\n",
322 __func__, (u32)rate / div);
323
324 clkp = clk_get_parent(clkp);
325
326 if (rate > osc_freq) {
327 if (rate > PLL_MAX_FREQ / 2 && rate < PLL_MAX_FREQ) {
328 pll_tgt = rate;
329 pll_div = 1;
330 } else {
331 for (pll_div = 2; pll_div < PLL_MAX_DIV; pll_div++) {
332 pll_tgt = rate / div * pll_div;
333 if (pll_tgt >= PLL_MIN_FREQ && pll_tgt <= PLL_MAX_FREQ)
334 break;
335 }
336 }
337 } else {
338 pll_tgt = osc_freq;
339 pll_div = rate / div / osc_freq;
340 }
341
342 debug("%s: pll_tgt=%u, rate=%u, div=%u\n", __func__,
343 (u32)pll_tgt, (u32)rate, pll_div);
344
345 clk_set_rate(clkp, pll_tgt);
346
347 return clk_set_rate(clk, rate / div) * div;
348 }
349
350 /*
351 * If the new rate differs by at least 5% of the target,
352 * we must check for rounding error in a divider, so try
353 * set rate with rate + (parent_freq % rate).
354 */
355
356 if (diff > rate / div / 20) {
357 u64 parent_freq = clk_get_parent_rate(clkp);
358
359 rem = parent_freq % rate;
360 new_rate = clk_set_rate(clkp, (rate / div) + rem);
361 new_diff = abs(new_rate - rate / div);
362
363 if (new_diff > diff) {
364 new_rate = clk_set_rate(clkp, rate / div);
365 } else {
366 debug("%s: Using better rate %lu that gives diff %lu\n",
367 __func__, new_rate, new_diff);
368 }
369 }
370
Udit Kumarc648daa2023-09-21 22:30:38 +0530371 if (IS_ENABLED(CONFIG_K3_AVS0) && !freq_scale_up)
372 k3_avs_notify_freq(data->map[clk->id].dev_id,
373 data->map[clk->id].clk_id, rate);
374
Tero Kristo82ceb0d2021-06-11 11:45:14 +0300375 return new_rate;
376}
377
378static int ti_clk_set_parent(struct clk *clk, struct clk *parent)
379{
380 struct ti_clk_data *data = dev_get_priv(clk->dev);
381 struct clk *clkp = data->map[clk->id].clk;
382 struct clk *parentp = data->map[parent->id].clk;
383
384 return clk_set_parent(clkp, parentp);
385}
386
387static int ti_clk_enable(struct clk *clk)
388{
389 struct ti_clk_data *data = dev_get_priv(clk->dev);
390 struct clk *clkp = data->map[clk->id].clk;
391
392 return clk_enable(clkp);
393}
394
395static int ti_clk_disable(struct clk *clk)
396{
397 struct ti_clk_data *data = dev_get_priv(clk->dev);
398 struct clk *clkp = data->map[clk->id].clk;
399
400 return clk_disable(clkp);
401}
402
403static const struct udevice_id ti_clk_of_match[] = {
404 { .compatible = "ti,k2g-sci-clk" },
405 { /* sentinel */ },
406};
407
408static const struct clk_ops ti_clk_ops = {
409 .of_xlate = ti_clk_of_xlate,
410 .set_rate = ti_clk_set_rate,
411 .get_rate = ti_clk_get_rate,
412 .enable = ti_clk_enable,
413 .disable = ti_clk_disable,
414 .set_parent = ti_clk_set_parent,
415};
416
417U_BOOT_DRIVER(ti_clk) = {
418 .name = "ti-clk",
419 .id = UCLASS_CLK,
420 .of_match = ti_clk_of_match,
421 .probe = ti_clk_probe,
422 .priv_auto = sizeof(struct ti_clk_data),
423 .ops = &ti_clk_ops,
424};