1 /*
2 * Copyright (c) 2020-2022 IoT.bzh
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/drivers/clock_control.h>
8 #include <zephyr/drivers/clock_control/renesas_cpg_mssr.h>
9 #include <zephyr/dt-bindings/clock/renesas_cpg_mssr.h>
10 #include <zephyr/irq.h>
11 #include <zephyr/kernel.h>
12 #include "clock_control_renesas_cpg_mssr.h"
13 #include <stdlib.h>
14
15 #define LOG_LEVEL CONFIG_CLOCK_CONTROL_LOG_LEVEL
16 #include <zephyr/logging/log.h>
17 LOG_MODULE_REGISTER(clock_control_rcar);
18
rcar_cpg_reset(uint32_t base_address,uint32_t reg,uint32_t bit)19 static void rcar_cpg_reset(uint32_t base_address, uint32_t reg, uint32_t bit)
20 {
21 rcar_cpg_write(base_address, srcr[reg], BIT(bit));
22 rcar_cpg_write(base_address, SRSTCLR(reg), BIT(bit));
23 }
24
rcar_cpg_write(uint32_t base_address,uint32_t reg,uint32_t val)25 void rcar_cpg_write(uint32_t base_address, uint32_t reg, uint32_t val)
26 {
27 sys_write32(~val, base_address + CPGWPR);
28 sys_write32(val, base_address + reg);
29 /* Wait for at least one cycle of the RCLK clock (@ ca. 32 kHz) */
30 k_sleep(K_USEC(35));
31 }
32
rcar_cpg_mstp_clock_endisable(uint32_t base_address,uint32_t module,bool enable)33 int rcar_cpg_mstp_clock_endisable(uint32_t base_address, uint32_t module, bool enable)
34 {
35 uint32_t reg = module / 100;
36 uint32_t bit = module % 100;
37 uint32_t bitmask = BIT(bit);
38 uint32_t reg_val;
39
40 __ASSERT((bit < 32) && reg < ARRAY_SIZE(mstpcr), "Invalid module number for cpg clock: %d",
41 module);
42
43 reg_val = sys_read32(base_address + mstpcr[reg]);
44 if (enable) {
45 reg_val &= ~bitmask;
46 } else {
47 reg_val |= bitmask;
48 }
49
50 sys_write32(reg_val, base_address + mstpcr[reg]);
51 if (!enable) {
52 rcar_cpg_reset(base_address, reg, bit);
53 }
54
55 return 0;
56 }
57
cmp_cpg_clk_info_table_items(const void * key,const void * element)58 static int cmp_cpg_clk_info_table_items(const void *key, const void *element)
59 {
60 const struct cpg_clk_info_table *e = element;
61 uint32_t module = (uintptr_t)key;
62
63 if (e->module == module) {
64 return 0;
65 } else if (e->module < module) {
66 return 1;
67 } else {
68 return -1;
69 }
70 }
71
72 struct cpg_clk_info_table *
rcar_cpg_find_clk_info_by_module_id(const struct device * dev,uint32_t domain,uint32_t id)73 rcar_cpg_find_clk_info_by_module_id(const struct device *dev, uint32_t domain, uint32_t id)
74 {
75 struct rcar_cpg_mssr_data *data = dev->data;
76 struct cpg_clk_info_table *item;
77 struct cpg_clk_info_table *table = data->clk_info_table[domain];
78 uint32_t table_size = data->clk_info_table_size[domain];
79 uintptr_t uintptr_id = id;
80
81 item = bsearch((void *)uintptr_id, table, table_size, sizeof(*item),
82 cmp_cpg_clk_info_table_items);
83 if (!item) {
84 LOG_ERR("%s: can't find clk info (domain %u module %u)", dev->name, domain, id);
85 }
86
87 return item;
88 }
89
rcar_cpg_get_divider(const struct device * dev,struct cpg_clk_info_table * clk_info)90 static uint32_t rcar_cpg_get_divider(const struct device *dev, struct cpg_clk_info_table *clk_info)
91 {
92 mem_addr_t reg_addr;
93 mm_reg_t reg_val;
94 uint32_t divider = RCAR_CPG_NONE;
95 struct rcar_cpg_mssr_data *data = dev->data;
96
97 if (clk_info->domain == CPG_MOD) {
98 return 1;
99 }
100
101 reg_addr = clk_info->offset;
102 if (reg_addr == RCAR_CPG_NONE) {
103 /* if we don't have valid offset, in is equal to out */
104 return 1;
105 }
106
107 reg_addr += DEVICE_MMIO_GET(dev);
108 reg_val = sys_read32(reg_addr);
109
110 if (data->get_div_helper) {
111 divider = data->get_div_helper(reg_val, clk_info->module);
112 }
113
114 if (!divider) {
115 return RCAR_CPG_NONE;
116 }
117
118 return divider;
119 }
120
rcar_cpg_update_out_freq(const struct device * dev,struct cpg_clk_info_table * clk_info)121 static int rcar_cpg_update_out_freq(const struct device *dev, struct cpg_clk_info_table *clk_info)
122 {
123 uint32_t divider = rcar_cpg_get_divider(dev, clk_info);
124
125 if (divider == RCAR_CPG_NONE) {
126 return -EINVAL;
127 }
128
129 clk_info->out_freq = clk_info->in_freq / divider;
130 return 0;
131 }
132
rcar_cpg_get_in_update_out_freq(const struct device * dev,struct cpg_clk_info_table * clk_info)133 static int64_t rcar_cpg_get_in_update_out_freq(const struct device *dev,
134 struct cpg_clk_info_table *clk_info)
135 {
136 int64_t freq = -ENOTSUP;
137 struct cpg_clk_info_table *parent_clk;
138
139 if (!clk_info) {
140 return freq;
141 }
142
143 if (clk_info->in_freq != RCAR_CPG_NONE) {
144 if (clk_info->out_freq == RCAR_CPG_NONE) {
145 if (rcar_cpg_update_out_freq(dev, clk_info) < 0) {
146 return freq;
147 }
148 }
149 return clk_info->in_freq;
150 }
151
152 parent_clk = clk_info->parent;
153
154 freq = rcar_cpg_get_in_update_out_freq(dev, parent_clk);
155 if (freq < 0) {
156 return freq;
157 }
158
159 clk_info->in_freq = parent_clk->out_freq;
160
161 freq = rcar_cpg_update_out_freq(dev, clk_info);
162 if (freq < 0) {
163 return freq;
164 }
165
166 return clk_info->in_freq;
167 }
168
rcar_cpg_get_out_freq(const struct device * dev,struct cpg_clk_info_table * clk_info)169 static int64_t rcar_cpg_get_out_freq(const struct device *dev, struct cpg_clk_info_table *clk_info)
170 {
171 int64_t freq;
172
173 if (clk_info->out_freq != RCAR_CPG_NONE) {
174 return clk_info->out_freq;
175 }
176
177 freq = rcar_cpg_get_in_update_out_freq(dev, clk_info);
178 if (freq < 0) {
179 return freq;
180 }
181
182 return clk_info->out_freq;
183 }
184
rcar_cpg_change_children_in_out_freq(const struct device * dev,struct cpg_clk_info_table * parent)185 static void rcar_cpg_change_children_in_out_freq(const struct device *dev,
186 struct cpg_clk_info_table *parent)
187 {
188 struct cpg_clk_info_table *children_list = parent->children_list;
189
190 while (children_list) {
191 children_list->in_freq = parent->out_freq;
192
193 if (rcar_cpg_update_out_freq(dev, children_list) < 0) {
194 /*
195 * Why it can happen:
196 * - divider is zero (with current implementation of board specific
197 * divider helper function it is impossible);
198 * - we don't have board specific implementation of get divider helper
199 * function;
200 * - we don't have this module in a table (for some of call chains of
201 * this function it is impossible);
202 * - impossible value is set in clock register divider bits.
203 */
204 LOG_ERR("%s: error during getting divider from clock register, domain %u "
205 "module %u! Please, revise logic related to obtaining divider or "
206 "check presentence of clock inside appropriate clk_info_table",
207 dev->name, children_list->domain, children_list->module);
208 k_panic();
209 return;
210 }
211
212 /* child can have childrens */
213 rcar_cpg_change_children_in_out_freq(dev, children_list);
214 children_list = children_list->next_sibling;
215 }
216 }
217
rcar_cpg_get_rate(const struct device * dev,clock_control_subsys_t sys,uint32_t * rate)218 int rcar_cpg_get_rate(const struct device *dev, clock_control_subsys_t sys, uint32_t *rate)
219 {
220 int64_t ret;
221 struct rcar_cpg_mssr_data *data;
222 struct rcar_cpg_clk *clk = (struct rcar_cpg_clk *)sys;
223 k_spinlock_key_t key;
224
225 struct cpg_clk_info_table *clk_info;
226
227 if (!dev || !sys || !rate) {
228 LOG_ERR("%s: received null ptr input arg(s) dev %p sys %p rate %p",
229 __func__, dev, sys, rate);
230 return -EINVAL;
231 }
232
233 clk_info = rcar_cpg_find_clk_info_by_module_id(dev, clk->domain, clk->module);
234 if (clk_info == NULL) {
235 return -EINVAL;
236 }
237
238 data = dev->data;
239
240 key = k_spin_lock(&data->lock);
241 ret = rcar_cpg_get_out_freq(dev, clk_info);
242 k_spin_unlock(&data->lock, key);
243
244 if (ret < 0) {
245 LOG_ERR("%s: clk (domain %u module %u) error (%lld) during getting out frequency",
246 dev->name, clk->domain, clk->module, ret);
247 return -EINVAL;
248 } else if (ret > UINT_MAX) {
249 LOG_ERR("%s: clk (domain %u module %u) frequency bigger then max uint value",
250 dev->name, clk->domain, clk->module);
251 return -EINVAL;
252 }
253
254 *rate = ret;
255 return 0;
256 }
257
rcar_cpg_set_rate(const struct device * dev,clock_control_subsys_t sys,clock_control_subsys_rate_t rate)258 int rcar_cpg_set_rate(const struct device *dev, clock_control_subsys_t sys,
259 clock_control_subsys_rate_t rate)
260 {
261 int ret = -ENOTSUP;
262 k_spinlock_key_t key;
263 struct cpg_clk_info_table *clk_info;
264 struct rcar_cpg_clk *clk = (struct rcar_cpg_clk *)sys;
265 struct rcar_cpg_mssr_data *data;
266 int64_t in_freq;
267 uint32_t divider;
268 uint32_t div_mask;
269 uint32_t module;
270 uintptr_t u_rate = (uintptr_t)rate;
271
272 if (!dev || !sys || !rate) {
273 LOG_ERR("%s: received null ptr input arg(s) dev %p sys %p rate %p",
274 __func__, dev, sys, rate);
275 return -EINVAL;
276 }
277
278 clk_info = rcar_cpg_find_clk_info_by_module_id(dev, clk->domain, clk->module);
279 if (clk_info == NULL) {
280 return -EINVAL;
281 }
282
283 if (clk_info->domain == CPG_MOD) {
284 if (!clk_info->parent) {
285 LOG_ERR("%s: parent isn't present for module clock, module id %u",
286 dev->name, clk_info->module);
287 k_panic();
288 }
289 clk_info = clk_info->parent;
290 }
291
292 module = clk_info->module;
293 data = dev->data;
294
295 key = k_spin_lock(&data->lock);
296 in_freq = rcar_cpg_get_in_update_out_freq(dev, clk_info);
297 if (in_freq < 0) {
298 ret = in_freq;
299 goto unlock;
300 }
301
302 divider = in_freq / u_rate;
303 if (divider * u_rate != in_freq) {
304 ret = -EINVAL;
305 goto unlock;
306 }
307
308 if (!data->set_rate_helper) {
309 ret = -ENOTSUP;
310 goto unlock;
311 }
312
313 ret = data->set_rate_helper(module, ÷r, &div_mask);
314 if (!ret) {
315 int64_t out_rate;
316 uint32_t reg = sys_read32(clk_info->offset + DEVICE_MMIO_GET(dev));
317
318 reg &= ~div_mask;
319 rcar_cpg_write(DEVICE_MMIO_GET(dev), clk_info->offset, reg | divider);
320
321 clk_info->out_freq = RCAR_CPG_NONE;
322
323 out_rate = rcar_cpg_get_out_freq(dev, clk_info);
324 if (out_rate < 0 || out_rate != u_rate) {
325 ret = -EINVAL;
326 LOG_ERR("%s: clock (domain %u module %u) register cfg freq (%lld) "
327 "isn't equal to requested %lu",
328 dev->name, clk->domain, clk->module, out_rate, u_rate);
329 goto unlock;
330 }
331
332 rcar_cpg_change_children_in_out_freq(dev, clk_info);
333 }
334
335 unlock:
336 k_spin_unlock(&data->lock, key);
337 return ret;
338 }
339
rcar_cpg_build_clock_relationship(const struct device * dev)340 void rcar_cpg_build_clock_relationship(const struct device *dev)
341 {
342 uint32_t domain;
343 k_spinlock_key_t key;
344 struct rcar_cpg_mssr_data *data = dev->data;
345
346 if (!data) {
347 return;
348 }
349
350 key = k_spin_lock(&data->lock);
351 for (domain = 0; domain < CPG_NUM_DOMAINS; domain++) {
352 uint32_t idx;
353 uint32_t prev_mod_id = 0;
354 struct cpg_clk_info_table *item = data->clk_info_table[domain];
355
356 for (idx = 0; idx < data->clk_info_table_size[domain]; idx++, item++) {
357 struct cpg_clk_info_table *parent;
358
359 /* check if an array is sorted by module id or not */
360 if (prev_mod_id >= item->module) {
361 LOG_ERR("%s: clocks have to be sorted inside clock table in "
362 "ascending order by module id field, domain %u "
363 "module id %u",
364 dev->name, item->domain, item->module);
365 k_panic();
366 }
367
368 prev_mod_id = item->module;
369
370 if (item->parent_id == RCAR_CPG_NONE) {
371 continue;
372 }
373
374 parent = rcar_cpg_find_clk_info_by_module_id(dev, CPG_CORE,
375 item->parent_id);
376 if (!parent) {
377 LOG_ERR("%s: can't find parent for clock with valid parent id, "
378 "domain %u module id %u",
379 dev->name, item->domain, item->module);
380 k_panic();
381 }
382
383 if (item->parent != NULL) {
384 LOG_ERR("%s: trying to set another parent for a clock, domain %u "
385 "module id %u, parent for the clock has been already set",
386 dev->name, item->domain, item->module);
387 k_panic();
388 }
389
390 item->parent = parent;
391
392 /* insert in the head of the children list of the parent */
393 item->next_sibling = parent->children_list;
394 parent->children_list = item;
395 }
396 }
397 k_spin_unlock(&data->lock, key);
398 }
399
rcar_cpg_update_all_in_out_freq(const struct device * dev)400 void rcar_cpg_update_all_in_out_freq(const struct device *dev)
401 {
402 uint32_t domain;
403 k_spinlock_key_t key;
404 struct rcar_cpg_mssr_data *data = dev->data;
405
406 if (!data) {
407 return;
408 }
409
410 key = k_spin_lock(&data->lock);
411 for (domain = 0; domain < CPG_NUM_DOMAINS; domain++) {
412 uint32_t idx;
413 struct cpg_clk_info_table *item = data->clk_info_table[domain];
414
415 for (idx = 0; idx < data->clk_info_table_size[domain]; idx++, item++) {
416 if (rcar_cpg_get_in_update_out_freq(dev, item) < 0) {
417 LOG_ERR("%s: can't update in/out freq for clock during init, "
418 "domain %u module %u! Please, review correctness of data "
419 "inside clk_info_table",
420 dev->name, item->domain, item->module);
421 k_panic();
422 }
423 }
424 }
425 k_spin_unlock(&data->lock, key);
426 }
427