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, &divider, &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