1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2017 Icenowy Zheng <icenowy@aosc.io> |
4 | */ |
5 | |
6 | #include <linux/clk.h> |
7 | #include <linux/clk-provider.h> |
8 | #include <linux/module.h> |
9 | #include <linux/of.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/reset.h> |
12 | |
13 | #include "ccu_common.h" |
14 | #include "ccu_div.h" |
15 | #include "ccu_gate.h" |
16 | #include "ccu_reset.h" |
17 | |
18 | #include "ccu-sun8i-de2.h" |
19 | |
20 | static SUNXI_CCU_GATE(bus_mixer0_clk, "bus-mixer0" , "bus-de" , |
21 | 0x04, BIT(0), 0); |
22 | static SUNXI_CCU_GATE(bus_mixer1_clk, "bus-mixer1" , "bus-de" , |
23 | 0x04, BIT(1), 0); |
24 | static SUNXI_CCU_GATE(bus_wb_clk, "bus-wb" , "bus-de" , |
25 | 0x04, BIT(2), 0); |
26 | static SUNXI_CCU_GATE(bus_rot_clk, "bus-rot" , "bus-de" , |
27 | 0x04, BIT(3), 0); |
28 | |
29 | static SUNXI_CCU_GATE(mixer0_clk, "mixer0" , "mixer0-div" , |
30 | 0x00, BIT(0), CLK_SET_RATE_PARENT); |
31 | static SUNXI_CCU_GATE(mixer1_clk, "mixer1" , "mixer1-div" , |
32 | 0x00, BIT(1), CLK_SET_RATE_PARENT); |
33 | static SUNXI_CCU_GATE(wb_clk, "wb" , "wb-div" , |
34 | 0x00, BIT(2), CLK_SET_RATE_PARENT); |
35 | static SUNXI_CCU_GATE(rot_clk, "rot" , "rot-div" , |
36 | 0x00, BIT(3), CLK_SET_RATE_PARENT); |
37 | |
38 | static SUNXI_CCU_M(mixer0_div_clk, "mixer0-div" , "de" , 0x0c, 0, 4, |
39 | CLK_SET_RATE_PARENT); |
40 | static SUNXI_CCU_M(mixer1_div_clk, "mixer1-div" , "de" , 0x0c, 4, 4, |
41 | CLK_SET_RATE_PARENT); |
42 | static SUNXI_CCU_M(wb_div_clk, "wb-div" , "de" , 0x0c, 8, 4, |
43 | CLK_SET_RATE_PARENT); |
44 | static SUNXI_CCU_M(rot_div_clk, "rot-div" , "de" , 0x0c, 0x0c, 4, |
45 | CLK_SET_RATE_PARENT); |
46 | |
47 | static SUNXI_CCU_M(mixer0_div_a83_clk, "mixer0-div" , "pll-de" , 0x0c, 0, 4, |
48 | CLK_SET_RATE_PARENT); |
49 | static SUNXI_CCU_M(mixer1_div_a83_clk, "mixer1-div" , "pll-de" , 0x0c, 4, 4, |
50 | CLK_SET_RATE_PARENT); |
51 | static SUNXI_CCU_M(wb_div_a83_clk, "wb-div" , "pll-de" , 0x0c, 8, 4, |
52 | CLK_SET_RATE_PARENT); |
53 | static SUNXI_CCU_M(rot_div_a83_clk, "rot-div" , "pll-de" , 0x0c, 0x0c, 4, |
54 | CLK_SET_RATE_PARENT); |
55 | |
56 | static struct ccu_common *sun8i_de2_ccu_clks[] = { |
57 | &mixer0_clk.common, |
58 | &mixer1_clk.common, |
59 | &wb_clk.common, |
60 | &rot_clk.common, |
61 | |
62 | &bus_mixer0_clk.common, |
63 | &bus_mixer1_clk.common, |
64 | &bus_wb_clk.common, |
65 | &bus_rot_clk.common, |
66 | |
67 | &mixer0_div_clk.common, |
68 | &mixer1_div_clk.common, |
69 | &wb_div_clk.common, |
70 | &rot_div_clk.common, |
71 | |
72 | &mixer0_div_a83_clk.common, |
73 | &mixer1_div_a83_clk.common, |
74 | &wb_div_a83_clk.common, |
75 | &rot_div_a83_clk.common, |
76 | }; |
77 | |
78 | static struct clk_hw_onecell_data sun8i_a83t_de2_hw_clks = { |
79 | .hws = { |
80 | [CLK_MIXER0] = &mixer0_clk.common.hw, |
81 | [CLK_MIXER1] = &mixer1_clk.common.hw, |
82 | [CLK_WB] = &wb_clk.common.hw, |
83 | [CLK_ROT] = &rot_clk.common.hw, |
84 | |
85 | [CLK_BUS_MIXER0] = &bus_mixer0_clk.common.hw, |
86 | [CLK_BUS_MIXER1] = &bus_mixer1_clk.common.hw, |
87 | [CLK_BUS_WB] = &bus_wb_clk.common.hw, |
88 | [CLK_BUS_ROT] = &bus_rot_clk.common.hw, |
89 | |
90 | [CLK_MIXER0_DIV] = &mixer0_div_a83_clk.common.hw, |
91 | [CLK_MIXER1_DIV] = &mixer1_div_a83_clk.common.hw, |
92 | [CLK_WB_DIV] = &wb_div_a83_clk.common.hw, |
93 | [CLK_ROT_DIV] = &rot_div_a83_clk.common.hw, |
94 | }, |
95 | .num = CLK_NUMBER_WITH_ROT, |
96 | }; |
97 | |
98 | static struct clk_hw_onecell_data sun8i_h3_de2_hw_clks = { |
99 | .hws = { |
100 | [CLK_MIXER0] = &mixer0_clk.common.hw, |
101 | [CLK_MIXER1] = &mixer1_clk.common.hw, |
102 | [CLK_WB] = &wb_clk.common.hw, |
103 | |
104 | [CLK_BUS_MIXER0] = &bus_mixer0_clk.common.hw, |
105 | [CLK_BUS_MIXER1] = &bus_mixer1_clk.common.hw, |
106 | [CLK_BUS_WB] = &bus_wb_clk.common.hw, |
107 | |
108 | [CLK_MIXER0_DIV] = &mixer0_div_clk.common.hw, |
109 | [CLK_MIXER1_DIV] = &mixer1_div_clk.common.hw, |
110 | [CLK_WB_DIV] = &wb_div_clk.common.hw, |
111 | }, |
112 | .num = CLK_NUMBER_WITHOUT_ROT, |
113 | }; |
114 | |
115 | static struct clk_hw_onecell_data sun8i_v3s_de2_hw_clks = { |
116 | .hws = { |
117 | [CLK_MIXER0] = &mixer0_clk.common.hw, |
118 | [CLK_WB] = &wb_clk.common.hw, |
119 | |
120 | [CLK_BUS_MIXER0] = &bus_mixer0_clk.common.hw, |
121 | [CLK_BUS_WB] = &bus_wb_clk.common.hw, |
122 | |
123 | [CLK_MIXER0_DIV] = &mixer0_div_clk.common.hw, |
124 | [CLK_WB_DIV] = &wb_div_clk.common.hw, |
125 | }, |
126 | .num = CLK_NUMBER_WITHOUT_ROT, |
127 | }; |
128 | |
129 | static struct clk_hw_onecell_data sun50i_a64_de2_hw_clks = { |
130 | .hws = { |
131 | [CLK_MIXER0] = &mixer0_clk.common.hw, |
132 | [CLK_MIXER1] = &mixer1_clk.common.hw, |
133 | [CLK_WB] = &wb_clk.common.hw, |
134 | [CLK_ROT] = &rot_clk.common.hw, |
135 | |
136 | [CLK_BUS_MIXER0] = &bus_mixer0_clk.common.hw, |
137 | [CLK_BUS_MIXER1] = &bus_mixer1_clk.common.hw, |
138 | [CLK_BUS_WB] = &bus_wb_clk.common.hw, |
139 | [CLK_BUS_ROT] = &bus_rot_clk.common.hw, |
140 | |
141 | [CLK_MIXER0_DIV] = &mixer0_div_clk.common.hw, |
142 | [CLK_MIXER1_DIV] = &mixer1_div_clk.common.hw, |
143 | [CLK_WB_DIV] = &wb_div_clk.common.hw, |
144 | [CLK_ROT_DIV] = &rot_div_clk.common.hw, |
145 | }, |
146 | .num = CLK_NUMBER_WITH_ROT, |
147 | }; |
148 | |
149 | static struct ccu_reset_map sun8i_a83t_de2_resets[] = { |
150 | [RST_MIXER0] = { 0x08, BIT(0) }, |
151 | /* |
152 | * Mixer1 reset line is shared with wb, so only RST_WB is |
153 | * exported here. |
154 | */ |
155 | [RST_WB] = { 0x08, BIT(2) }, |
156 | [RST_ROT] = { 0x08, BIT(3) }, |
157 | }; |
158 | |
159 | static struct ccu_reset_map sun8i_h3_de2_resets[] = { |
160 | [RST_MIXER0] = { 0x08, BIT(0) }, |
161 | /* |
162 | * Mixer1 reset line is shared with wb, so only RST_WB is |
163 | * exported here. |
164 | * V3s doesn't have mixer1, so it also shares this struct. |
165 | */ |
166 | [RST_WB] = { 0x08, BIT(2) }, |
167 | }; |
168 | |
169 | static struct ccu_reset_map sun50i_a64_de2_resets[] = { |
170 | [RST_MIXER0] = { 0x08, BIT(0) }, |
171 | [RST_MIXER1] = { 0x08, BIT(1) }, |
172 | [RST_WB] = { 0x08, BIT(2) }, |
173 | [RST_ROT] = { 0x08, BIT(3) }, |
174 | }; |
175 | |
176 | static struct ccu_reset_map sun50i_h5_de2_resets[] = { |
177 | [RST_MIXER0] = { 0x08, BIT(0) }, |
178 | [RST_MIXER1] = { 0x08, BIT(1) }, |
179 | [RST_WB] = { 0x08, BIT(2) }, |
180 | }; |
181 | |
182 | static const struct sunxi_ccu_desc sun8i_a83t_de2_clk_desc = { |
183 | .ccu_clks = sun8i_de2_ccu_clks, |
184 | .num_ccu_clks = ARRAY_SIZE(sun8i_de2_ccu_clks), |
185 | |
186 | .hw_clks = &sun8i_a83t_de2_hw_clks, |
187 | |
188 | .resets = sun8i_a83t_de2_resets, |
189 | .num_resets = ARRAY_SIZE(sun8i_a83t_de2_resets), |
190 | }; |
191 | |
192 | static const struct sunxi_ccu_desc sun8i_h3_de2_clk_desc = { |
193 | .ccu_clks = sun8i_de2_ccu_clks, |
194 | .num_ccu_clks = ARRAY_SIZE(sun8i_de2_ccu_clks), |
195 | |
196 | .hw_clks = &sun8i_h3_de2_hw_clks, |
197 | |
198 | .resets = sun8i_h3_de2_resets, |
199 | .num_resets = ARRAY_SIZE(sun8i_h3_de2_resets), |
200 | }; |
201 | |
202 | static const struct sunxi_ccu_desc sun8i_r40_de2_clk_desc = { |
203 | .ccu_clks = sun8i_de2_ccu_clks, |
204 | .num_ccu_clks = ARRAY_SIZE(sun8i_de2_ccu_clks), |
205 | |
206 | .hw_clks = &sun50i_a64_de2_hw_clks, |
207 | |
208 | .resets = sun8i_a83t_de2_resets, |
209 | .num_resets = ARRAY_SIZE(sun8i_a83t_de2_resets), |
210 | }; |
211 | |
212 | static const struct sunxi_ccu_desc sun8i_v3s_de2_clk_desc = { |
213 | .ccu_clks = sun8i_de2_ccu_clks, |
214 | .num_ccu_clks = ARRAY_SIZE(sun8i_de2_ccu_clks), |
215 | |
216 | .hw_clks = &sun8i_v3s_de2_hw_clks, |
217 | |
218 | .resets = sun8i_a83t_de2_resets, |
219 | .num_resets = ARRAY_SIZE(sun8i_a83t_de2_resets), |
220 | }; |
221 | |
222 | static const struct sunxi_ccu_desc sun50i_a64_de2_clk_desc = { |
223 | .ccu_clks = sun8i_de2_ccu_clks, |
224 | .num_ccu_clks = ARRAY_SIZE(sun8i_de2_ccu_clks), |
225 | |
226 | .hw_clks = &sun50i_a64_de2_hw_clks, |
227 | |
228 | .resets = sun50i_a64_de2_resets, |
229 | .num_resets = ARRAY_SIZE(sun50i_a64_de2_resets), |
230 | }; |
231 | |
232 | static const struct sunxi_ccu_desc sun50i_h5_de2_clk_desc = { |
233 | .ccu_clks = sun8i_de2_ccu_clks, |
234 | .num_ccu_clks = ARRAY_SIZE(sun8i_de2_ccu_clks), |
235 | |
236 | .hw_clks = &sun8i_h3_de2_hw_clks, |
237 | |
238 | .resets = sun50i_h5_de2_resets, |
239 | .num_resets = ARRAY_SIZE(sun50i_h5_de2_resets), |
240 | }; |
241 | |
242 | static int sunxi_de2_clk_probe(struct platform_device *pdev) |
243 | { |
244 | struct clk *bus_clk, *mod_clk; |
245 | struct reset_control *rstc; |
246 | void __iomem *reg; |
247 | const struct sunxi_ccu_desc *ccu_desc; |
248 | int ret; |
249 | |
250 | ccu_desc = of_device_get_match_data(dev: &pdev->dev); |
251 | if (!ccu_desc) |
252 | return -EINVAL; |
253 | |
254 | reg = devm_platform_ioremap_resource(pdev, index: 0); |
255 | if (IS_ERR(ptr: reg)) |
256 | return PTR_ERR(ptr: reg); |
257 | |
258 | bus_clk = devm_clk_get(dev: &pdev->dev, id: "bus" ); |
259 | if (IS_ERR(ptr: bus_clk)) |
260 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: bus_clk), |
261 | fmt: "Couldn't get bus clk\n" ); |
262 | |
263 | mod_clk = devm_clk_get(dev: &pdev->dev, id: "mod" ); |
264 | if (IS_ERR(ptr: mod_clk)) |
265 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: mod_clk), |
266 | fmt: "Couldn't get mod clk\n" ); |
267 | |
268 | rstc = devm_reset_control_get_exclusive(dev: &pdev->dev, NULL); |
269 | if (IS_ERR(ptr: rstc)) |
270 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: rstc), |
271 | fmt: "Couldn't get reset control\n" ); |
272 | |
273 | /* The clocks need to be enabled for us to access the registers */ |
274 | ret = clk_prepare_enable(clk: bus_clk); |
275 | if (ret) { |
276 | dev_err(&pdev->dev, "Couldn't enable bus clk: %d\n" , ret); |
277 | return ret; |
278 | } |
279 | |
280 | ret = clk_prepare_enable(clk: mod_clk); |
281 | if (ret) { |
282 | dev_err(&pdev->dev, "Couldn't enable mod clk: %d\n" , ret); |
283 | goto err_disable_bus_clk; |
284 | } |
285 | |
286 | /* The reset control needs to be asserted for the controls to work */ |
287 | ret = reset_control_deassert(rstc); |
288 | if (ret) { |
289 | dev_err(&pdev->dev, |
290 | "Couldn't deassert reset control: %d\n" , ret); |
291 | goto err_disable_mod_clk; |
292 | } |
293 | |
294 | ret = devm_sunxi_ccu_probe(dev: &pdev->dev, reg, desc: ccu_desc); |
295 | if (ret) |
296 | goto err_assert_reset; |
297 | |
298 | return 0; |
299 | |
300 | err_assert_reset: |
301 | reset_control_assert(rstc); |
302 | err_disable_mod_clk: |
303 | clk_disable_unprepare(clk: mod_clk); |
304 | err_disable_bus_clk: |
305 | clk_disable_unprepare(clk: bus_clk); |
306 | return ret; |
307 | } |
308 | |
309 | static const struct of_device_id sunxi_de2_clk_ids[] = { |
310 | { |
311 | .compatible = "allwinner,sun8i-a83t-de2-clk" , |
312 | .data = &sun8i_a83t_de2_clk_desc, |
313 | }, |
314 | { |
315 | .compatible = "allwinner,sun8i-h3-de2-clk" , |
316 | .data = &sun8i_h3_de2_clk_desc, |
317 | }, |
318 | { |
319 | .compatible = "allwinner,sun8i-r40-de2-clk" , |
320 | .data = &sun8i_r40_de2_clk_desc, |
321 | }, |
322 | { |
323 | .compatible = "allwinner,sun8i-v3s-de2-clk" , |
324 | .data = &sun8i_v3s_de2_clk_desc, |
325 | }, |
326 | { |
327 | .compatible = "allwinner,sun50i-a64-de2-clk" , |
328 | .data = &sun50i_a64_de2_clk_desc, |
329 | }, |
330 | { |
331 | .compatible = "allwinner,sun50i-h5-de2-clk" , |
332 | .data = &sun50i_h5_de2_clk_desc, |
333 | }, |
334 | { |
335 | .compatible = "allwinner,sun50i-h6-de3-clk" , |
336 | .data = &sun50i_h5_de2_clk_desc, |
337 | }, |
338 | { } |
339 | }; |
340 | |
341 | static struct platform_driver sunxi_de2_clk_driver = { |
342 | .probe = sunxi_de2_clk_probe, |
343 | .driver = { |
344 | .name = "sunxi-de2-clks" , |
345 | .of_match_table = sunxi_de2_clk_ids, |
346 | }, |
347 | }; |
348 | module_platform_driver(sunxi_de2_clk_driver); |
349 | |
350 | MODULE_IMPORT_NS(SUNXI_CCU); |
351 | MODULE_LICENSE("GPL" ); |
352 | |