1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC |
4 | * |
5 | * Authors: |
6 | * Serge Semin <Sergey.Semin@baikalelectronics.ru> |
7 | * Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru> |
8 | * |
9 | * Baikal-T1 CCU Dividers clock driver |
10 | */ |
11 | |
12 | #define pr_fmt(fmt) "bt1-ccu-div: " fmt |
13 | |
14 | #include <linux/kernel.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/printk.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/clk-provider.h> |
19 | #include <linux/reset-controller.h> |
20 | #include <linux/mfd/syscon.h> |
21 | #include <linux/of.h> |
22 | #include <linux/of_address.h> |
23 | #include <linux/ioport.h> |
24 | #include <linux/regmap.h> |
25 | |
26 | #include <dt-bindings/clock/bt1-ccu.h> |
27 | |
28 | #include "ccu-div.h" |
29 | #include "ccu-rst.h" |
30 | |
31 | #define CCU_AXI_MAIN_BASE 0x030 |
32 | #define CCU_AXI_DDR_BASE 0x034 |
33 | #define CCU_AXI_SATA_BASE 0x038 |
34 | #define CCU_AXI_GMAC0_BASE 0x03C |
35 | #define CCU_AXI_GMAC1_BASE 0x040 |
36 | #define CCU_AXI_XGMAC_BASE 0x044 |
37 | #define CCU_AXI_PCIE_M_BASE 0x048 |
38 | #define CCU_AXI_PCIE_S_BASE 0x04C |
39 | #define CCU_AXI_USB_BASE 0x050 |
40 | #define CCU_AXI_HWA_BASE 0x054 |
41 | #define CCU_AXI_SRAM_BASE 0x058 |
42 | |
43 | #define CCU_SYS_SATA_REF_BASE 0x060 |
44 | #define CCU_SYS_APB_BASE 0x064 |
45 | #define CCU_SYS_GMAC0_BASE 0x068 |
46 | #define CCU_SYS_GMAC1_BASE 0x06C |
47 | #define CCU_SYS_XGMAC_BASE 0x070 |
48 | #define CCU_SYS_USB_BASE 0x074 |
49 | #define CCU_SYS_PVT_BASE 0x078 |
50 | #define CCU_SYS_HWA_BASE 0x07C |
51 | #define CCU_SYS_UART_BASE 0x084 |
52 | #define CCU_SYS_TIMER0_BASE 0x088 |
53 | #define CCU_SYS_TIMER1_BASE 0x08C |
54 | #define CCU_SYS_TIMER2_BASE 0x090 |
55 | #define CCU_SYS_WDT_BASE 0x150 |
56 | |
57 | #define CCU_DIV_VAR_INFO(_id, _name, _pname, _base, _width, _flags, _features) \ |
58 | { \ |
59 | .id = _id, \ |
60 | .name = _name, \ |
61 | .parent_name = _pname, \ |
62 | .base = _base, \ |
63 | .type = CCU_DIV_VAR, \ |
64 | .width = _width, \ |
65 | .flags = _flags, \ |
66 | .features = _features \ |
67 | } |
68 | |
69 | #define CCU_DIV_GATE_INFO(_id, _name, _pname, _base, _divider) \ |
70 | { \ |
71 | .id = _id, \ |
72 | .name = _name, \ |
73 | .parent_name = _pname, \ |
74 | .base = _base, \ |
75 | .type = CCU_DIV_GATE, \ |
76 | .divider = _divider \ |
77 | } |
78 | |
79 | #define CCU_DIV_BUF_INFO(_id, _name, _pname, _base, _flags) \ |
80 | { \ |
81 | .id = _id, \ |
82 | .name = _name, \ |
83 | .parent_name = _pname, \ |
84 | .base = _base, \ |
85 | .type = CCU_DIV_BUF, \ |
86 | .flags = _flags \ |
87 | } |
88 | |
89 | #define CCU_DIV_FIXED_INFO(_id, _name, _pname, _divider) \ |
90 | { \ |
91 | .id = _id, \ |
92 | .name = _name, \ |
93 | .parent_name = _pname, \ |
94 | .type = CCU_DIV_FIXED, \ |
95 | .divider = _divider \ |
96 | } |
97 | |
98 | struct ccu_div_info { |
99 | unsigned int id; |
100 | const char *name; |
101 | const char *parent_name; |
102 | unsigned int base; |
103 | enum ccu_div_type type; |
104 | union { |
105 | unsigned int width; |
106 | unsigned int divider; |
107 | }; |
108 | unsigned long flags; |
109 | unsigned long features; |
110 | }; |
111 | |
112 | struct ccu_div_data { |
113 | struct device_node *np; |
114 | struct regmap *sys_regs; |
115 | |
116 | unsigned int divs_num; |
117 | const struct ccu_div_info *divs_info; |
118 | struct ccu_div **divs; |
119 | |
120 | struct ccu_rst *rsts; |
121 | }; |
122 | |
123 | /* |
124 | * AXI Main Interconnect (axi_main_clk) and DDR AXI-bus (axi_ddr_clk) clocks |
125 | * must be left enabled in any case, since former one is responsible for |
126 | * clocking a bus between CPU cores and the rest of the SoC components, while |
127 | * the later is clocking the AXI-bus between DDR controller and the Main |
128 | * Interconnect. So should any of these clocks get to be disabled, the system |
129 | * will literally stop working. That's why we marked them as critical. |
130 | */ |
131 | static const struct ccu_div_info axi_info[] = { |
132 | CCU_DIV_VAR_INFO(CCU_AXI_MAIN_CLK, "axi_main_clk" , "pcie_clk" , |
133 | CCU_AXI_MAIN_BASE, 4, |
134 | CLK_IS_CRITICAL, CCU_DIV_RESET_DOMAIN), |
135 | CCU_DIV_VAR_INFO(CCU_AXI_DDR_CLK, "axi_ddr_clk" , "sata_clk" , |
136 | CCU_AXI_DDR_BASE, 4, |
137 | CLK_IS_CRITICAL | CLK_SET_RATE_GATE, |
138 | CCU_DIV_RESET_DOMAIN), |
139 | CCU_DIV_VAR_INFO(CCU_AXI_SATA_CLK, "axi_sata_clk" , "sata_clk" , |
140 | CCU_AXI_SATA_BASE, 4, |
141 | CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), |
142 | CCU_DIV_VAR_INFO(CCU_AXI_GMAC0_CLK, "axi_gmac0_clk" , "eth_clk" , |
143 | CCU_AXI_GMAC0_BASE, 4, |
144 | CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), |
145 | CCU_DIV_VAR_INFO(CCU_AXI_GMAC1_CLK, "axi_gmac1_clk" , "eth_clk" , |
146 | CCU_AXI_GMAC1_BASE, 4, |
147 | CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), |
148 | CCU_DIV_VAR_INFO(CCU_AXI_XGMAC_CLK, "axi_xgmac_clk" , "eth_clk" , |
149 | CCU_AXI_XGMAC_BASE, 4, |
150 | CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), |
151 | CCU_DIV_VAR_INFO(CCU_AXI_PCIE_M_CLK, "axi_pcie_m_clk" , "pcie_clk" , |
152 | CCU_AXI_PCIE_M_BASE, 4, |
153 | CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), |
154 | CCU_DIV_VAR_INFO(CCU_AXI_PCIE_S_CLK, "axi_pcie_s_clk" , "pcie_clk" , |
155 | CCU_AXI_PCIE_S_BASE, 4, |
156 | CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), |
157 | CCU_DIV_VAR_INFO(CCU_AXI_USB_CLK, "axi_usb_clk" , "sata_clk" , |
158 | CCU_AXI_USB_BASE, 4, |
159 | CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), |
160 | CCU_DIV_VAR_INFO(CCU_AXI_HWA_CLK, "axi_hwa_clk" , "sata_clk" , |
161 | CCU_AXI_HWA_BASE, 4, |
162 | CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), |
163 | CCU_DIV_VAR_INFO(CCU_AXI_SRAM_CLK, "axi_sram_clk" , "eth_clk" , |
164 | CCU_AXI_SRAM_BASE, 4, |
165 | CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN) |
166 | }; |
167 | |
168 | /* |
169 | * APB-bus clock is marked as critical since it's a main communication bus |
170 | * for the SoC devices registers IO-operations. |
171 | */ |
172 | static const struct ccu_div_info sys_info[] = { |
173 | CCU_DIV_VAR_INFO(CCU_SYS_SATA_CLK, "sys_sata_clk" , |
174 | "sata_clk" , CCU_SYS_SATA_REF_BASE, 4, |
175 | CLK_SET_RATE_GATE, |
176 | CCU_DIV_SKIP_ONE | CCU_DIV_LOCK_SHIFTED | |
177 | CCU_DIV_RESET_DOMAIN), |
178 | CCU_DIV_BUF_INFO(CCU_SYS_SATA_REF_CLK, "sys_sata_ref_clk" , |
179 | "sys_sata_clk" , CCU_SYS_SATA_REF_BASE, |
180 | CLK_SET_RATE_PARENT), |
181 | CCU_DIV_VAR_INFO(CCU_SYS_APB_CLK, "sys_apb_clk" , |
182 | "pcie_clk" , CCU_SYS_APB_BASE, 5, |
183 | CLK_IS_CRITICAL, CCU_DIV_BASIC | CCU_DIV_RESET_DOMAIN), |
184 | CCU_DIV_GATE_INFO(CCU_SYS_GMAC0_TX_CLK, "sys_gmac0_tx_clk" , |
185 | "eth_clk" , CCU_SYS_GMAC0_BASE, 5), |
186 | CCU_DIV_FIXED_INFO(CCU_SYS_GMAC0_PTP_CLK, "sys_gmac0_ptp_clk" , |
187 | "eth_clk" , 10), |
188 | CCU_DIV_GATE_INFO(CCU_SYS_GMAC1_TX_CLK, "sys_gmac1_tx_clk" , |
189 | "eth_clk" , CCU_SYS_GMAC1_BASE, 5), |
190 | CCU_DIV_FIXED_INFO(CCU_SYS_GMAC1_PTP_CLK, "sys_gmac1_ptp_clk" , |
191 | "eth_clk" , 10), |
192 | CCU_DIV_GATE_INFO(CCU_SYS_XGMAC_CLK, "sys_xgmac_clk" , |
193 | "eth_clk" , CCU_SYS_XGMAC_BASE, 1), |
194 | CCU_DIV_FIXED_INFO(CCU_SYS_XGMAC_REF_CLK, "sys_xgmac_ref_clk" , |
195 | "sys_xgmac_clk" , 8), |
196 | CCU_DIV_FIXED_INFO(CCU_SYS_XGMAC_PTP_CLK, "sys_xgmac_ptp_clk" , |
197 | "sys_xgmac_clk" , 8), |
198 | CCU_DIV_GATE_INFO(CCU_SYS_USB_CLK, "sys_usb_clk" , |
199 | "eth_clk" , CCU_SYS_USB_BASE, 10), |
200 | CCU_DIV_VAR_INFO(CCU_SYS_PVT_CLK, "sys_pvt_clk" , |
201 | "ref_clk" , CCU_SYS_PVT_BASE, 5, |
202 | CLK_SET_RATE_GATE, 0), |
203 | CCU_DIV_VAR_INFO(CCU_SYS_HWA_CLK, "sys_hwa_clk" , |
204 | "sata_clk" , CCU_SYS_HWA_BASE, 4, |
205 | CLK_SET_RATE_GATE, 0), |
206 | CCU_DIV_VAR_INFO(CCU_SYS_UART_CLK, "sys_uart_clk" , |
207 | "eth_clk" , CCU_SYS_UART_BASE, 17, |
208 | CLK_SET_RATE_GATE, 0), |
209 | CCU_DIV_FIXED_INFO(CCU_SYS_I2C1_CLK, "sys_i2c1_clk" , |
210 | "eth_clk" , 10), |
211 | CCU_DIV_FIXED_INFO(CCU_SYS_I2C2_CLK, "sys_i2c2_clk" , |
212 | "eth_clk" , 10), |
213 | CCU_DIV_FIXED_INFO(CCU_SYS_GPIO_CLK, "sys_gpio_clk" , |
214 | "ref_clk" , 25), |
215 | CCU_DIV_VAR_INFO(CCU_SYS_TIMER0_CLK, "sys_timer0_clk" , |
216 | "ref_clk" , CCU_SYS_TIMER0_BASE, 17, |
217 | CLK_SET_RATE_GATE, CCU_DIV_BASIC), |
218 | CCU_DIV_VAR_INFO(CCU_SYS_TIMER1_CLK, "sys_timer1_clk" , |
219 | "ref_clk" , CCU_SYS_TIMER1_BASE, 17, |
220 | CLK_SET_RATE_GATE, CCU_DIV_BASIC), |
221 | CCU_DIV_VAR_INFO(CCU_SYS_TIMER2_CLK, "sys_timer2_clk" , |
222 | "ref_clk" , CCU_SYS_TIMER2_BASE, 17, |
223 | CLK_SET_RATE_GATE, CCU_DIV_BASIC), |
224 | CCU_DIV_VAR_INFO(CCU_SYS_WDT_CLK, "sys_wdt_clk" , |
225 | "eth_clk" , CCU_SYS_WDT_BASE, 17, |
226 | CLK_SET_RATE_GATE, CCU_DIV_SKIP_ONE_TO_THREE) |
227 | }; |
228 | |
229 | static struct ccu_div_data *axi_data; |
230 | static struct ccu_div_data *sys_data; |
231 | |
232 | static void ccu_div_set_data(struct ccu_div_data *data) |
233 | { |
234 | struct device_node *np = data->np; |
235 | |
236 | if (of_device_is_compatible(device: np, "baikal,bt1-ccu-axi" )) |
237 | axi_data = data; |
238 | else if (of_device_is_compatible(device: np, "baikal,bt1-ccu-sys" )) |
239 | sys_data = data; |
240 | else |
241 | pr_err("Invalid DT node '%s' specified\n" , of_node_full_name(np)); |
242 | } |
243 | |
244 | static struct ccu_div_data *ccu_div_get_data(struct device_node *np) |
245 | { |
246 | if (of_device_is_compatible(device: np, "baikal,bt1-ccu-axi" )) |
247 | return axi_data; |
248 | else if (of_device_is_compatible(device: np, "baikal,bt1-ccu-sys" )) |
249 | return sys_data; |
250 | |
251 | pr_err("Invalid DT node '%s' specified\n" , of_node_full_name(np)); |
252 | |
253 | return NULL; |
254 | } |
255 | |
256 | static struct ccu_div *ccu_div_find_desc(struct ccu_div_data *data, |
257 | unsigned int clk_id) |
258 | { |
259 | int idx; |
260 | |
261 | for (idx = 0; idx < data->divs_num; ++idx) { |
262 | if (data->divs_info[idx].id == clk_id) |
263 | return data->divs[idx]; |
264 | } |
265 | |
266 | return ERR_PTR(error: -EINVAL); |
267 | } |
268 | |
269 | static struct ccu_div_data *ccu_div_create_data(struct device_node *np) |
270 | { |
271 | struct ccu_div_data *data; |
272 | int ret; |
273 | |
274 | data = kzalloc(size: sizeof(*data), GFP_KERNEL); |
275 | if (!data) |
276 | return ERR_PTR(error: -ENOMEM); |
277 | |
278 | data->np = np; |
279 | if (of_device_is_compatible(device: np, "baikal,bt1-ccu-axi" )) { |
280 | data->divs_num = ARRAY_SIZE(axi_info); |
281 | data->divs_info = axi_info; |
282 | } else if (of_device_is_compatible(device: np, "baikal,bt1-ccu-sys" )) { |
283 | data->divs_num = ARRAY_SIZE(sys_info); |
284 | data->divs_info = sys_info; |
285 | } else { |
286 | pr_err("Incompatible DT node '%s' specified\n" , |
287 | of_node_full_name(np)); |
288 | ret = -EINVAL; |
289 | goto err_kfree_data; |
290 | } |
291 | |
292 | data->divs = kcalloc(n: data->divs_num, size: sizeof(*data->divs), GFP_KERNEL); |
293 | if (!data->divs) { |
294 | ret = -ENOMEM; |
295 | goto err_kfree_data; |
296 | } |
297 | |
298 | return data; |
299 | |
300 | err_kfree_data: |
301 | kfree(objp: data); |
302 | |
303 | return ERR_PTR(error: ret); |
304 | } |
305 | |
306 | static void ccu_div_free_data(struct ccu_div_data *data) |
307 | { |
308 | kfree(objp: data->divs); |
309 | |
310 | kfree(objp: data); |
311 | } |
312 | |
313 | static int ccu_div_find_sys_regs(struct ccu_div_data *data) |
314 | { |
315 | data->sys_regs = syscon_node_to_regmap(np: data->np->parent); |
316 | if (IS_ERR(ptr: data->sys_regs)) { |
317 | pr_err("Failed to find syscon regs for '%s'\n" , |
318 | of_node_full_name(data->np)); |
319 | return PTR_ERR(ptr: data->sys_regs); |
320 | } |
321 | |
322 | return 0; |
323 | } |
324 | |
325 | static struct clk_hw *ccu_div_of_clk_hw_get(struct of_phandle_args *clkspec, |
326 | void *priv) |
327 | { |
328 | struct ccu_div_data *data = priv; |
329 | struct ccu_div *div; |
330 | unsigned int clk_id; |
331 | |
332 | clk_id = clkspec->args[0]; |
333 | div = ccu_div_find_desc(data, clk_id); |
334 | if (IS_ERR(ptr: div)) { |
335 | if (div != ERR_PTR(error: -EPROBE_DEFER)) |
336 | pr_info("Invalid clock ID %d specified\n" , clk_id); |
337 | |
338 | return ERR_CAST(ptr: div); |
339 | } |
340 | |
341 | return ccu_div_get_clk_hw(div); |
342 | } |
343 | |
344 | static int ccu_div_clk_register(struct ccu_div_data *data, bool defer) |
345 | { |
346 | int idx, ret; |
347 | |
348 | for (idx = 0; idx < data->divs_num; ++idx) { |
349 | const struct ccu_div_info *info = &data->divs_info[idx]; |
350 | struct ccu_div_init_data init = {0}; |
351 | |
352 | if (!!(info->features & CCU_DIV_BASIC) ^ defer) { |
353 | if (!data->divs[idx]) |
354 | data->divs[idx] = ERR_PTR(error: -EPROBE_DEFER); |
355 | |
356 | continue; |
357 | } |
358 | |
359 | init.id = info->id; |
360 | init.name = info->name; |
361 | init.parent_name = info->parent_name; |
362 | init.np = data->np; |
363 | init.type = info->type; |
364 | init.flags = info->flags; |
365 | init.features = info->features; |
366 | |
367 | if (init.type == CCU_DIV_VAR) { |
368 | init.base = info->base; |
369 | init.sys_regs = data->sys_regs; |
370 | init.width = info->width; |
371 | } else if (init.type == CCU_DIV_GATE) { |
372 | init.base = info->base; |
373 | init.sys_regs = data->sys_regs; |
374 | init.divider = info->divider; |
375 | } else if (init.type == CCU_DIV_BUF) { |
376 | init.base = info->base; |
377 | init.sys_regs = data->sys_regs; |
378 | } else { |
379 | init.divider = info->divider; |
380 | } |
381 | |
382 | data->divs[idx] = ccu_div_hw_register(init: &init); |
383 | if (IS_ERR(ptr: data->divs[idx])) { |
384 | ret = PTR_ERR(ptr: data->divs[idx]); |
385 | pr_err("Couldn't register divider '%s' hw\n" , |
386 | init.name); |
387 | goto err_hw_unregister; |
388 | } |
389 | } |
390 | |
391 | return 0; |
392 | |
393 | err_hw_unregister: |
394 | for (--idx; idx >= 0; --idx) { |
395 | if (!!(data->divs_info[idx].features & CCU_DIV_BASIC) ^ defer) |
396 | continue; |
397 | |
398 | ccu_div_hw_unregister(div: data->divs[idx]); |
399 | } |
400 | |
401 | return ret; |
402 | } |
403 | |
404 | static void ccu_div_clk_unregister(struct ccu_div_data *data, bool defer) |
405 | { |
406 | int idx; |
407 | |
408 | /* Uninstall only the clocks registered on the specfied stage */ |
409 | for (idx = 0; idx < data->divs_num; ++idx) { |
410 | if (!!(data->divs_info[idx].features & CCU_DIV_BASIC) ^ defer) |
411 | continue; |
412 | |
413 | ccu_div_hw_unregister(div: data->divs[idx]); |
414 | } |
415 | } |
416 | |
417 | static int ccu_div_of_register(struct ccu_div_data *data) |
418 | { |
419 | int ret; |
420 | |
421 | ret = of_clk_add_hw_provider(np: data->np, get: ccu_div_of_clk_hw_get, data); |
422 | if (ret) { |
423 | pr_err("Couldn't register dividers '%s' clock provider\n" , |
424 | of_node_full_name(data->np)); |
425 | } |
426 | |
427 | return ret; |
428 | } |
429 | |
430 | static int ccu_div_rst_register(struct ccu_div_data *data) |
431 | { |
432 | struct ccu_rst_init_data init = {0}; |
433 | |
434 | init.sys_regs = data->sys_regs; |
435 | init.np = data->np; |
436 | |
437 | data->rsts = ccu_rst_hw_register(init: &init); |
438 | if (IS_ERR(ptr: data->rsts)) { |
439 | pr_err("Couldn't register divider '%s' reset controller\n" , |
440 | of_node_full_name(data->np)); |
441 | return PTR_ERR(ptr: data->rsts); |
442 | } |
443 | |
444 | return 0; |
445 | } |
446 | |
447 | static int ccu_div_probe(struct platform_device *pdev) |
448 | { |
449 | struct ccu_div_data *data; |
450 | int ret; |
451 | |
452 | data = ccu_div_get_data(np: dev_of_node(dev: &pdev->dev)); |
453 | if (!data) |
454 | return -EINVAL; |
455 | |
456 | ret = ccu_div_clk_register(data, defer: false); |
457 | if (ret) |
458 | return ret; |
459 | |
460 | ret = ccu_div_rst_register(data); |
461 | if (ret) |
462 | goto err_clk_unregister; |
463 | |
464 | return 0; |
465 | |
466 | err_clk_unregister: |
467 | ccu_div_clk_unregister(data, defer: false); |
468 | |
469 | return ret; |
470 | } |
471 | |
472 | static const struct of_device_id ccu_div_of_match[] = { |
473 | { .compatible = "baikal,bt1-ccu-axi" }, |
474 | { .compatible = "baikal,bt1-ccu-sys" }, |
475 | { } |
476 | }; |
477 | |
478 | static struct platform_driver ccu_div_driver = { |
479 | .probe = ccu_div_probe, |
480 | .driver = { |
481 | .name = "clk-ccu-div" , |
482 | .of_match_table = ccu_div_of_match, |
483 | .suppress_bind_attrs = true, |
484 | }, |
485 | }; |
486 | builtin_platform_driver(ccu_div_driver); |
487 | |
488 | static __init void ccu_div_init(struct device_node *np) |
489 | { |
490 | struct ccu_div_data *data; |
491 | int ret; |
492 | |
493 | data = ccu_div_create_data(np); |
494 | if (IS_ERR(ptr: data)) |
495 | return; |
496 | |
497 | ret = ccu_div_find_sys_regs(data); |
498 | if (ret) |
499 | goto err_free_data; |
500 | |
501 | ret = ccu_div_clk_register(data, defer: true); |
502 | if (ret) |
503 | goto err_free_data; |
504 | |
505 | ret = ccu_div_of_register(data); |
506 | if (ret) |
507 | goto err_clk_unregister; |
508 | |
509 | ccu_div_set_data(data); |
510 | |
511 | return; |
512 | |
513 | err_clk_unregister: |
514 | ccu_div_clk_unregister(data, defer: true); |
515 | |
516 | err_free_data: |
517 | ccu_div_free_data(data); |
518 | } |
519 | CLK_OF_DECLARE_DRIVER(ccu_axi, "baikal,bt1-ccu-axi" , ccu_div_init); |
520 | CLK_OF_DECLARE_DRIVER(ccu_sys, "baikal,bt1-ccu-sys" , ccu_div_init); |
521 | |