1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Xilinx VCU Init |
4 | * |
5 | * Copyright (C) 2016 - 2017 Xilinx, Inc. |
6 | * |
7 | * Contacts Dhaval Shah <dshah@xilinx.com> |
8 | */ |
9 | #include <linux/bitfield.h> |
10 | #include <linux/clk.h> |
11 | #include <linux/clk-provider.h> |
12 | #include <linux/device.h> |
13 | #include <linux/errno.h> |
14 | #include <linux/io.h> |
15 | #include <linux/mfd/syscon.h> |
16 | #include <linux/mfd/syscon/xlnx-vcu.h> |
17 | #include <linux/module.h> |
18 | #include <linux/mod_devicetable.h> |
19 | #include <linux/platform_device.h> |
20 | #include <linux/regmap.h> |
21 | |
22 | #include <dt-bindings/clock/xlnx-vcu.h> |
23 | |
24 | #define VCU_PLL_CTRL 0x24 |
25 | #define VCU_PLL_CTRL_RESET BIT(0) |
26 | #define VCU_PLL_CTRL_POR_IN BIT(1) |
27 | #define VCU_PLL_CTRL_PWR_POR BIT(2) |
28 | #define VCU_PLL_CTRL_BYPASS BIT(3) |
29 | #define VCU_PLL_CTRL_FBDIV GENMASK(14, 8) |
30 | #define VCU_PLL_CTRL_CLKOUTDIV GENMASK(18, 16) |
31 | |
32 | #define VCU_PLL_CFG 0x28 |
33 | #define VCU_PLL_CFG_RES GENMASK(3, 0) |
34 | #define VCU_PLL_CFG_CP GENMASK(8, 5) |
35 | #define VCU_PLL_CFG_LFHF GENMASK(12, 10) |
36 | #define VCU_PLL_CFG_LOCK_CNT GENMASK(22, 13) |
37 | #define VCU_PLL_CFG_LOCK_DLY GENMASK(31, 25) |
38 | #define VCU_ENC_CORE_CTRL 0x30 |
39 | #define VCU_ENC_MCU_CTRL 0x34 |
40 | #define VCU_DEC_CORE_CTRL 0x38 |
41 | #define VCU_DEC_MCU_CTRL 0x3c |
42 | #define VCU_PLL_STATUS 0x60 |
43 | #define VCU_PLL_STATUS_LOCK_STATUS BIT(0) |
44 | |
45 | #define MHZ 1000000 |
46 | #define FVCO_MIN (1500U * MHZ) |
47 | #define FVCO_MAX (3000U * MHZ) |
48 | |
49 | /** |
50 | * struct xvcu_device - Xilinx VCU init device structure |
51 | * @dev: Platform device |
52 | * @pll_ref: pll ref clock source |
53 | * @aclk: axi clock source |
54 | * @logicore_reg_ba: logicore reg base address |
55 | * @vcu_slcr_ba: vcu_slcr Register base address |
56 | * @pll: handle for the VCU PLL |
57 | * @pll_post: handle for the VCU PLL post divider |
58 | * @clk_data: clocks provided by the vcu clock provider |
59 | */ |
60 | struct xvcu_device { |
61 | struct device *dev; |
62 | struct clk *pll_ref; |
63 | struct clk *aclk; |
64 | struct regmap *logicore_reg_ba; |
65 | void __iomem *vcu_slcr_ba; |
66 | struct clk_hw *pll; |
67 | struct clk_hw *pll_post; |
68 | struct clk_hw_onecell_data *clk_data; |
69 | }; |
70 | |
71 | static struct regmap_config vcu_settings_regmap_config = { |
72 | .name = "regmap" , |
73 | .reg_bits = 32, |
74 | .val_bits = 32, |
75 | .reg_stride = 4, |
76 | .max_register = 0xfff, |
77 | .cache_type = REGCACHE_NONE, |
78 | }; |
79 | |
80 | /** |
81 | * struct xvcu_pll_cfg - Helper data |
82 | * @fbdiv: The integer portion of the feedback divider to the PLL |
83 | * @cp: PLL charge pump control |
84 | * @res: PLL loop filter resistor control |
85 | * @lfhf: PLL loop filter high frequency capacitor control |
86 | * @lock_dly: Lock circuit configuration settings for lock windowsize |
87 | * @lock_cnt: Lock circuit counter setting |
88 | */ |
89 | struct xvcu_pll_cfg { |
90 | u32 fbdiv; |
91 | u32 cp; |
92 | u32 res; |
93 | u32 lfhf; |
94 | u32 lock_dly; |
95 | u32 lock_cnt; |
96 | }; |
97 | |
98 | static const struct xvcu_pll_cfg xvcu_pll_cfg[] = { |
99 | { 25, 3, 10, 3, 63, 1000 }, |
100 | { 26, 3, 10, 3, 63, 1000 }, |
101 | { 27, 4, 6, 3, 63, 1000 }, |
102 | { 28, 4, 6, 3, 63, 1000 }, |
103 | { 29, 4, 6, 3, 63, 1000 }, |
104 | { 30, 4, 6, 3, 63, 1000 }, |
105 | { 31, 6, 1, 3, 63, 1000 }, |
106 | { 32, 6, 1, 3, 63, 1000 }, |
107 | { 33, 4, 10, 3, 63, 1000 }, |
108 | { 34, 5, 6, 3, 63, 1000 }, |
109 | { 35, 5, 6, 3, 63, 1000 }, |
110 | { 36, 5, 6, 3, 63, 1000 }, |
111 | { 37, 5, 6, 3, 63, 1000 }, |
112 | { 38, 5, 6, 3, 63, 975 }, |
113 | { 39, 3, 12, 3, 63, 950 }, |
114 | { 40, 3, 12, 3, 63, 925 }, |
115 | { 41, 3, 12, 3, 63, 900 }, |
116 | { 42, 3, 12, 3, 63, 875 }, |
117 | { 43, 3, 12, 3, 63, 850 }, |
118 | { 44, 3, 12, 3, 63, 850 }, |
119 | { 45, 3, 12, 3, 63, 825 }, |
120 | { 46, 3, 12, 3, 63, 800 }, |
121 | { 47, 3, 12, 3, 63, 775 }, |
122 | { 48, 3, 12, 3, 63, 775 }, |
123 | { 49, 3, 12, 3, 63, 750 }, |
124 | { 50, 3, 12, 3, 63, 750 }, |
125 | { 51, 3, 2, 3, 63, 725 }, |
126 | { 52, 3, 2, 3, 63, 700 }, |
127 | { 53, 3, 2, 3, 63, 700 }, |
128 | { 54, 3, 2, 3, 63, 675 }, |
129 | { 55, 3, 2, 3, 63, 675 }, |
130 | { 56, 3, 2, 3, 63, 650 }, |
131 | { 57, 3, 2, 3, 63, 650 }, |
132 | { 58, 3, 2, 3, 63, 625 }, |
133 | { 59, 3, 2, 3, 63, 625 }, |
134 | { 60, 3, 2, 3, 63, 625 }, |
135 | { 61, 3, 2, 3, 63, 600 }, |
136 | { 62, 3, 2, 3, 63, 600 }, |
137 | { 63, 3, 2, 3, 63, 600 }, |
138 | { 64, 3, 2, 3, 63, 600 }, |
139 | { 65, 3, 2, 3, 63, 600 }, |
140 | { 66, 3, 2, 3, 63, 600 }, |
141 | { 67, 3, 2, 3, 63, 600 }, |
142 | { 68, 3, 2, 3, 63, 600 }, |
143 | { 69, 3, 2, 3, 63, 600 }, |
144 | { 70, 3, 2, 3, 63, 600 }, |
145 | { 71, 3, 2, 3, 63, 600 }, |
146 | { 72, 3, 2, 3, 63, 600 }, |
147 | { 73, 3, 2, 3, 63, 600 }, |
148 | { 74, 3, 2, 3, 63, 600 }, |
149 | { 75, 3, 2, 3, 63, 600 }, |
150 | { 76, 3, 2, 3, 63, 600 }, |
151 | { 77, 3, 2, 3, 63, 600 }, |
152 | { 78, 3, 2, 3, 63, 600 }, |
153 | { 79, 3, 2, 3, 63, 600 }, |
154 | { 80, 3, 2, 3, 63, 600 }, |
155 | { 81, 3, 2, 3, 63, 600 }, |
156 | { 82, 3, 2, 3, 63, 600 }, |
157 | { 83, 4, 2, 3, 63, 600 }, |
158 | { 84, 4, 2, 3, 63, 600 }, |
159 | { 85, 4, 2, 3, 63, 600 }, |
160 | { 86, 4, 2, 3, 63, 600 }, |
161 | { 87, 4, 2, 3, 63, 600 }, |
162 | { 88, 4, 2, 3, 63, 600 }, |
163 | { 89, 4, 2, 3, 63, 600 }, |
164 | { 90, 4, 2, 3, 63, 600 }, |
165 | { 91, 4, 2, 3, 63, 600 }, |
166 | { 92, 4, 2, 3, 63, 600 }, |
167 | { 93, 4, 2, 3, 63, 600 }, |
168 | { 94, 4, 2, 3, 63, 600 }, |
169 | { 95, 4, 2, 3, 63, 600 }, |
170 | { 96, 4, 2, 3, 63, 600 }, |
171 | { 97, 4, 2, 3, 63, 600 }, |
172 | { 98, 4, 2, 3, 63, 600 }, |
173 | { 99, 4, 2, 3, 63, 600 }, |
174 | { 100, 4, 2, 3, 63, 600 }, |
175 | { 101, 4, 2, 3, 63, 600 }, |
176 | { 102, 4, 2, 3, 63, 600 }, |
177 | { 103, 5, 2, 3, 63, 600 }, |
178 | { 104, 5, 2, 3, 63, 600 }, |
179 | { 105, 5, 2, 3, 63, 600 }, |
180 | { 106, 5, 2, 3, 63, 600 }, |
181 | { 107, 3, 4, 3, 63, 600 }, |
182 | { 108, 3, 4, 3, 63, 600 }, |
183 | { 109, 3, 4, 3, 63, 600 }, |
184 | { 110, 3, 4, 3, 63, 600 }, |
185 | { 111, 3, 4, 3, 63, 600 }, |
186 | { 112, 3, 4, 3, 63, 600 }, |
187 | { 113, 3, 4, 3, 63, 600 }, |
188 | { 114, 3, 4, 3, 63, 600 }, |
189 | { 115, 3, 4, 3, 63, 600 }, |
190 | { 116, 3, 4, 3, 63, 600 }, |
191 | { 117, 3, 4, 3, 63, 600 }, |
192 | { 118, 3, 4, 3, 63, 600 }, |
193 | { 119, 3, 4, 3, 63, 600 }, |
194 | { 120, 3, 4, 3, 63, 600 }, |
195 | { 121, 3, 4, 3, 63, 600 }, |
196 | { 122, 3, 4, 3, 63, 600 }, |
197 | { 123, 3, 4, 3, 63, 600 }, |
198 | { 124, 3, 4, 3, 63, 600 }, |
199 | { 125, 3, 4, 3, 63, 600 }, |
200 | }; |
201 | |
202 | /** |
203 | * xvcu_read - Read from the VCU register space |
204 | * @iomem: vcu reg space base address |
205 | * @offset: vcu reg offset from base |
206 | * |
207 | * Return: Returns 32bit value from VCU register specified |
208 | * |
209 | */ |
210 | static inline u32 xvcu_read(void __iomem *iomem, u32 offset) |
211 | { |
212 | return ioread32(iomem + offset); |
213 | } |
214 | |
215 | /** |
216 | * xvcu_write - Write to the VCU register space |
217 | * @iomem: vcu reg space base address |
218 | * @offset: vcu reg offset from base |
219 | * @value: Value to write |
220 | */ |
221 | static inline void xvcu_write(void __iomem *iomem, u32 offset, u32 value) |
222 | { |
223 | iowrite32(value, iomem + offset); |
224 | } |
225 | |
226 | #define to_vcu_pll(_hw) container_of(_hw, struct vcu_pll, hw) |
227 | |
228 | struct vcu_pll { |
229 | struct clk_hw hw; |
230 | void __iomem *reg_base; |
231 | unsigned long fvco_min; |
232 | unsigned long fvco_max; |
233 | }; |
234 | |
235 | static int xvcu_pll_wait_for_lock(struct vcu_pll *pll) |
236 | { |
237 | void __iomem *base = pll->reg_base; |
238 | unsigned long timeout; |
239 | u32 lock_status; |
240 | |
241 | timeout = jiffies + msecs_to_jiffies(m: 2000); |
242 | do { |
243 | lock_status = xvcu_read(iomem: base, VCU_PLL_STATUS); |
244 | if (lock_status & VCU_PLL_STATUS_LOCK_STATUS) |
245 | return 0; |
246 | } while (!time_after(jiffies, timeout)); |
247 | |
248 | return -ETIMEDOUT; |
249 | } |
250 | |
251 | static struct clk_hw *xvcu_register_pll_post(struct device *dev, |
252 | const char *name, |
253 | const struct clk_hw *parent_hw, |
254 | void __iomem *reg_base) |
255 | { |
256 | u32 div; |
257 | u32 vcu_pll_ctrl; |
258 | |
259 | /* |
260 | * The output divider of the PLL must be set to 1/2 to meet the |
261 | * timing in the design. |
262 | */ |
263 | vcu_pll_ctrl = xvcu_read(iomem: reg_base, VCU_PLL_CTRL); |
264 | div = FIELD_GET(VCU_PLL_CTRL_CLKOUTDIV, vcu_pll_ctrl); |
265 | if (div != 1) |
266 | return ERR_PTR(error: -EINVAL); |
267 | |
268 | return clk_hw_register_fixed_factor(dev, name: "vcu_pll_post" , |
269 | parent_name: clk_hw_get_name(hw: parent_hw), |
270 | CLK_SET_RATE_PARENT, mult: 1, div: 2); |
271 | } |
272 | |
273 | static const struct xvcu_pll_cfg *xvcu_find_cfg(int div) |
274 | { |
275 | const struct xvcu_pll_cfg *cfg = NULL; |
276 | unsigned int i; |
277 | |
278 | for (i = 0; i < ARRAY_SIZE(xvcu_pll_cfg) - 1; i++) |
279 | if (xvcu_pll_cfg[i].fbdiv == div) |
280 | cfg = &xvcu_pll_cfg[i]; |
281 | |
282 | return cfg; |
283 | } |
284 | |
285 | static int xvcu_pll_set_div(struct vcu_pll *pll, int div) |
286 | { |
287 | void __iomem *base = pll->reg_base; |
288 | const struct xvcu_pll_cfg *cfg = NULL; |
289 | u32 vcu_pll_ctrl; |
290 | u32 cfg_val; |
291 | |
292 | cfg = xvcu_find_cfg(div); |
293 | if (!cfg) |
294 | return -EINVAL; |
295 | |
296 | vcu_pll_ctrl = xvcu_read(iomem: base, VCU_PLL_CTRL); |
297 | vcu_pll_ctrl &= ~VCU_PLL_CTRL_FBDIV; |
298 | vcu_pll_ctrl |= FIELD_PREP(VCU_PLL_CTRL_FBDIV, cfg->fbdiv); |
299 | xvcu_write(iomem: base, VCU_PLL_CTRL, value: vcu_pll_ctrl); |
300 | |
301 | cfg_val = FIELD_PREP(VCU_PLL_CFG_RES, cfg->res) | |
302 | FIELD_PREP(VCU_PLL_CFG_CP, cfg->cp) | |
303 | FIELD_PREP(VCU_PLL_CFG_LFHF, cfg->lfhf) | |
304 | FIELD_PREP(VCU_PLL_CFG_LOCK_CNT, cfg->lock_cnt) | |
305 | FIELD_PREP(VCU_PLL_CFG_LOCK_DLY, cfg->lock_dly); |
306 | xvcu_write(iomem: base, VCU_PLL_CFG, value: cfg_val); |
307 | |
308 | return 0; |
309 | } |
310 | |
311 | static long xvcu_pll_round_rate(struct clk_hw *hw, |
312 | unsigned long rate, unsigned long *parent_rate) |
313 | { |
314 | struct vcu_pll *pll = to_vcu_pll(hw); |
315 | unsigned int feedback_div; |
316 | |
317 | rate = clamp_t(unsigned long, rate, pll->fvco_min, pll->fvco_max); |
318 | |
319 | feedback_div = DIV_ROUND_CLOSEST_ULL(rate, *parent_rate); |
320 | feedback_div = clamp_t(unsigned int, feedback_div, 25, 125); |
321 | |
322 | return *parent_rate * feedback_div; |
323 | } |
324 | |
325 | static unsigned long xvcu_pll_recalc_rate(struct clk_hw *hw, |
326 | unsigned long parent_rate) |
327 | { |
328 | struct vcu_pll *pll = to_vcu_pll(hw); |
329 | void __iomem *base = pll->reg_base; |
330 | unsigned int div; |
331 | u32 vcu_pll_ctrl; |
332 | |
333 | vcu_pll_ctrl = xvcu_read(iomem: base, VCU_PLL_CTRL); |
334 | div = FIELD_GET(VCU_PLL_CTRL_FBDIV, vcu_pll_ctrl); |
335 | |
336 | return div * parent_rate; |
337 | } |
338 | |
339 | static int xvcu_pll_set_rate(struct clk_hw *hw, |
340 | unsigned long rate, unsigned long parent_rate) |
341 | { |
342 | struct vcu_pll *pll = to_vcu_pll(hw); |
343 | |
344 | return xvcu_pll_set_div(pll, div: rate / parent_rate); |
345 | } |
346 | |
347 | static int xvcu_pll_enable(struct clk_hw *hw) |
348 | { |
349 | struct vcu_pll *pll = to_vcu_pll(hw); |
350 | void __iomem *base = pll->reg_base; |
351 | u32 vcu_pll_ctrl; |
352 | int ret; |
353 | |
354 | vcu_pll_ctrl = xvcu_read(iomem: base, VCU_PLL_CTRL); |
355 | vcu_pll_ctrl |= VCU_PLL_CTRL_BYPASS; |
356 | xvcu_write(iomem: base, VCU_PLL_CTRL, value: vcu_pll_ctrl); |
357 | |
358 | vcu_pll_ctrl = xvcu_read(iomem: base, VCU_PLL_CTRL); |
359 | vcu_pll_ctrl &= ~VCU_PLL_CTRL_POR_IN; |
360 | vcu_pll_ctrl &= ~VCU_PLL_CTRL_PWR_POR; |
361 | vcu_pll_ctrl &= ~VCU_PLL_CTRL_RESET; |
362 | xvcu_write(iomem: base, VCU_PLL_CTRL, value: vcu_pll_ctrl); |
363 | |
364 | ret = xvcu_pll_wait_for_lock(pll); |
365 | if (ret) { |
366 | pr_err("VCU PLL is not locked\n" ); |
367 | goto err; |
368 | } |
369 | |
370 | vcu_pll_ctrl = xvcu_read(iomem: base, VCU_PLL_CTRL); |
371 | vcu_pll_ctrl &= ~VCU_PLL_CTRL_BYPASS; |
372 | xvcu_write(iomem: base, VCU_PLL_CTRL, value: vcu_pll_ctrl); |
373 | |
374 | err: |
375 | return ret; |
376 | } |
377 | |
378 | static void xvcu_pll_disable(struct clk_hw *hw) |
379 | { |
380 | struct vcu_pll *pll = to_vcu_pll(hw); |
381 | void __iomem *base = pll->reg_base; |
382 | u32 vcu_pll_ctrl; |
383 | |
384 | vcu_pll_ctrl = xvcu_read(iomem: base, VCU_PLL_CTRL); |
385 | vcu_pll_ctrl |= VCU_PLL_CTRL_POR_IN; |
386 | vcu_pll_ctrl |= VCU_PLL_CTRL_PWR_POR; |
387 | vcu_pll_ctrl |= VCU_PLL_CTRL_RESET; |
388 | xvcu_write(iomem: base, VCU_PLL_CTRL, value: vcu_pll_ctrl); |
389 | } |
390 | |
391 | static const struct clk_ops vcu_pll_ops = { |
392 | .enable = xvcu_pll_enable, |
393 | .disable = xvcu_pll_disable, |
394 | .round_rate = xvcu_pll_round_rate, |
395 | .recalc_rate = xvcu_pll_recalc_rate, |
396 | .set_rate = xvcu_pll_set_rate, |
397 | }; |
398 | |
399 | static struct clk_hw *xvcu_register_pll(struct device *dev, |
400 | void __iomem *reg_base, |
401 | const char *name, const char *parent, |
402 | unsigned long flags) |
403 | { |
404 | struct vcu_pll *pll; |
405 | struct clk_hw *hw; |
406 | struct clk_init_data init; |
407 | int ret; |
408 | |
409 | init.name = name; |
410 | init.parent_names = &parent; |
411 | init.ops = &vcu_pll_ops; |
412 | init.num_parents = 1; |
413 | init.flags = flags; |
414 | |
415 | pll = devm_kmalloc(dev, size: sizeof(*pll), GFP_KERNEL); |
416 | if (!pll) |
417 | return ERR_PTR(error: -ENOMEM); |
418 | |
419 | pll->hw.init = &init; |
420 | pll->reg_base = reg_base; |
421 | pll->fvco_min = FVCO_MIN; |
422 | pll->fvco_max = FVCO_MAX; |
423 | |
424 | hw = &pll->hw; |
425 | ret = devm_clk_hw_register(dev, hw); |
426 | if (ret) |
427 | return ERR_PTR(error: ret); |
428 | |
429 | clk_hw_set_rate_range(hw, min_rate: pll->fvco_min, max_rate: pll->fvco_max); |
430 | |
431 | return hw; |
432 | } |
433 | |
434 | static struct clk_hw *xvcu_clk_hw_register_leaf(struct device *dev, |
435 | const char *name, |
436 | const struct clk_parent_data *parent_data, |
437 | u8 num_parents, |
438 | void __iomem *reg) |
439 | { |
440 | u8 mux_flags = CLK_MUX_ROUND_CLOSEST; |
441 | u8 divider_flags = CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO | |
442 | CLK_DIVIDER_ROUND_CLOSEST; |
443 | struct clk_hw *mux = NULL; |
444 | struct clk_hw *divider = NULL; |
445 | struct clk_hw *gate = NULL; |
446 | char *name_mux; |
447 | char *name_div; |
448 | int err; |
449 | /* Protect register shared by clocks */ |
450 | spinlock_t *lock; |
451 | |
452 | lock = devm_kzalloc(dev, size: sizeof(*lock), GFP_KERNEL); |
453 | if (!lock) |
454 | return ERR_PTR(error: -ENOMEM); |
455 | spin_lock_init(lock); |
456 | |
457 | name_mux = devm_kasprintf(dev, GFP_KERNEL, fmt: "%s%s" , name, "_mux" ); |
458 | if (!name_mux) |
459 | return ERR_PTR(error: -ENOMEM); |
460 | mux = clk_hw_register_mux_parent_data(dev, name_mux, |
461 | parent_data, num_parents, |
462 | CLK_SET_RATE_PARENT, |
463 | reg, 0, 1, mux_flags, lock); |
464 | if (IS_ERR(ptr: mux)) |
465 | return mux; |
466 | |
467 | name_div = devm_kasprintf(dev, GFP_KERNEL, fmt: "%s%s" , name, "_div" ); |
468 | if (!name_div) { |
469 | err = -ENOMEM; |
470 | goto unregister_mux; |
471 | } |
472 | divider = clk_hw_register_divider_parent_hw(dev, name_div, mux, |
473 | CLK_SET_RATE_PARENT, |
474 | reg, 4, 6, divider_flags, |
475 | lock); |
476 | if (IS_ERR(ptr: divider)) { |
477 | err = PTR_ERR(ptr: divider); |
478 | goto unregister_mux; |
479 | } |
480 | |
481 | gate = clk_hw_register_gate_parent_hw(dev, name, divider, |
482 | CLK_SET_RATE_PARENT, reg, 12, 0, |
483 | lock); |
484 | if (IS_ERR(ptr: gate)) { |
485 | err = PTR_ERR(ptr: gate); |
486 | goto unregister_divider; |
487 | } |
488 | |
489 | return gate; |
490 | |
491 | unregister_divider: |
492 | clk_hw_unregister_divider(hw: divider); |
493 | unregister_mux: |
494 | clk_hw_unregister_mux(hw: mux); |
495 | |
496 | return ERR_PTR(error: err); |
497 | } |
498 | |
499 | static void xvcu_clk_hw_unregister_leaf(struct clk_hw *hw) |
500 | { |
501 | struct clk_hw *gate = hw; |
502 | struct clk_hw *divider; |
503 | struct clk_hw *mux; |
504 | |
505 | if (!gate) |
506 | return; |
507 | |
508 | divider = clk_hw_get_parent(hw: gate); |
509 | clk_hw_unregister_gate(hw: gate); |
510 | if (!divider) |
511 | return; |
512 | |
513 | mux = clk_hw_get_parent(hw: divider); |
514 | clk_hw_unregister_mux(hw: mux); |
515 | if (!divider) |
516 | return; |
517 | |
518 | clk_hw_unregister_divider(hw: divider); |
519 | } |
520 | |
521 | static int xvcu_register_clock_provider(struct xvcu_device *xvcu) |
522 | { |
523 | struct device *dev = xvcu->dev; |
524 | struct clk_parent_data parent_data[2] = { 0 }; |
525 | struct clk_hw_onecell_data *data; |
526 | struct clk_hw **hws; |
527 | struct clk_hw *hw; |
528 | void __iomem *reg_base = xvcu->vcu_slcr_ba; |
529 | |
530 | data = devm_kzalloc(dev, struct_size(data, hws, CLK_XVCU_NUM_CLOCKS), GFP_KERNEL); |
531 | if (!data) |
532 | return -ENOMEM; |
533 | data->num = CLK_XVCU_NUM_CLOCKS; |
534 | hws = data->hws; |
535 | |
536 | xvcu->clk_data = data; |
537 | |
538 | hw = xvcu_register_pll(dev, reg_base, |
539 | name: "vcu_pll" , parent: __clk_get_name(clk: xvcu->pll_ref), |
540 | CLK_SET_RATE_NO_REPARENT | CLK_OPS_PARENT_ENABLE); |
541 | if (IS_ERR(ptr: hw)) |
542 | return PTR_ERR(ptr: hw); |
543 | xvcu->pll = hw; |
544 | |
545 | hw = xvcu_register_pll_post(dev, name: "vcu_pll_post" , parent_hw: xvcu->pll, reg_base); |
546 | if (IS_ERR(ptr: hw)) |
547 | return PTR_ERR(ptr: hw); |
548 | xvcu->pll_post = hw; |
549 | |
550 | parent_data[0].fw_name = "pll_ref" ; |
551 | parent_data[1].hw = xvcu->pll_post; |
552 | |
553 | hws[CLK_XVCU_ENC_CORE] = |
554 | xvcu_clk_hw_register_leaf(dev, name: "venc_core_clk" , |
555 | parent_data, |
556 | ARRAY_SIZE(parent_data), |
557 | reg: reg_base + VCU_ENC_CORE_CTRL); |
558 | hws[CLK_XVCU_ENC_MCU] = |
559 | xvcu_clk_hw_register_leaf(dev, name: "venc_mcu_clk" , |
560 | parent_data, |
561 | ARRAY_SIZE(parent_data), |
562 | reg: reg_base + VCU_ENC_MCU_CTRL); |
563 | hws[CLK_XVCU_DEC_CORE] = |
564 | xvcu_clk_hw_register_leaf(dev, name: "vdec_core_clk" , |
565 | parent_data, |
566 | ARRAY_SIZE(parent_data), |
567 | reg: reg_base + VCU_DEC_CORE_CTRL); |
568 | hws[CLK_XVCU_DEC_MCU] = |
569 | xvcu_clk_hw_register_leaf(dev, name: "vdec_mcu_clk" , |
570 | parent_data, |
571 | ARRAY_SIZE(parent_data), |
572 | reg: reg_base + VCU_DEC_MCU_CTRL); |
573 | |
574 | return devm_of_clk_add_hw_provider(dev, get: of_clk_hw_onecell_get, data); |
575 | } |
576 | |
577 | static void xvcu_unregister_clock_provider(struct xvcu_device *xvcu) |
578 | { |
579 | struct clk_hw_onecell_data *data = xvcu->clk_data; |
580 | struct clk_hw **hws = data->hws; |
581 | |
582 | if (!IS_ERR_OR_NULL(ptr: hws[CLK_XVCU_DEC_MCU])) |
583 | xvcu_clk_hw_unregister_leaf(hw: hws[CLK_XVCU_DEC_MCU]); |
584 | if (!IS_ERR_OR_NULL(ptr: hws[CLK_XVCU_DEC_CORE])) |
585 | xvcu_clk_hw_unregister_leaf(hw: hws[CLK_XVCU_DEC_CORE]); |
586 | if (!IS_ERR_OR_NULL(ptr: hws[CLK_XVCU_ENC_MCU])) |
587 | xvcu_clk_hw_unregister_leaf(hw: hws[CLK_XVCU_ENC_MCU]); |
588 | if (!IS_ERR_OR_NULL(ptr: hws[CLK_XVCU_ENC_CORE])) |
589 | xvcu_clk_hw_unregister_leaf(hw: hws[CLK_XVCU_ENC_CORE]); |
590 | |
591 | clk_hw_unregister_fixed_factor(hw: xvcu->pll_post); |
592 | } |
593 | |
594 | /** |
595 | * xvcu_probe - Probe existence of the logicoreIP |
596 | * and initialize PLL |
597 | * |
598 | * @pdev: Pointer to the platform_device structure |
599 | * |
600 | * Return: Returns 0 on success |
601 | * Negative error code otherwise |
602 | */ |
603 | static int xvcu_probe(struct platform_device *pdev) |
604 | { |
605 | struct resource *res; |
606 | struct xvcu_device *xvcu; |
607 | void __iomem *regs; |
608 | int ret; |
609 | |
610 | xvcu = devm_kzalloc(dev: &pdev->dev, size: sizeof(*xvcu), GFP_KERNEL); |
611 | if (!xvcu) |
612 | return -ENOMEM; |
613 | |
614 | xvcu->dev = &pdev->dev; |
615 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vcu_slcr" ); |
616 | if (!res) { |
617 | dev_err(&pdev->dev, "get vcu_slcr memory resource failed.\n" ); |
618 | return -ENODEV; |
619 | } |
620 | |
621 | xvcu->vcu_slcr_ba = devm_ioremap(dev: &pdev->dev, offset: res->start, |
622 | size: resource_size(res)); |
623 | if (!xvcu->vcu_slcr_ba) { |
624 | dev_err(&pdev->dev, "vcu_slcr register mapping failed.\n" ); |
625 | return -ENOMEM; |
626 | } |
627 | |
628 | xvcu->logicore_reg_ba = |
629 | syscon_regmap_lookup_by_compatible(s: "xlnx,vcu-settings" ); |
630 | if (IS_ERR(ptr: xvcu->logicore_reg_ba)) { |
631 | dev_info(&pdev->dev, |
632 | "could not find xlnx,vcu-settings: trying direct register access\n" ); |
633 | |
634 | res = platform_get_resource_byname(pdev, |
635 | IORESOURCE_MEM, "logicore" ); |
636 | if (!res) { |
637 | dev_err(&pdev->dev, "get logicore memory resource failed.\n" ); |
638 | return -ENODEV; |
639 | } |
640 | |
641 | regs = devm_ioremap(dev: &pdev->dev, offset: res->start, size: resource_size(res)); |
642 | if (!regs) { |
643 | dev_err(&pdev->dev, "logicore register mapping failed.\n" ); |
644 | return -ENOMEM; |
645 | } |
646 | |
647 | xvcu->logicore_reg_ba = |
648 | devm_regmap_init_mmio(&pdev->dev, regs, |
649 | &vcu_settings_regmap_config); |
650 | if (IS_ERR(ptr: xvcu->logicore_reg_ba)) { |
651 | dev_err(&pdev->dev, "failed to init regmap\n" ); |
652 | return PTR_ERR(ptr: xvcu->logicore_reg_ba); |
653 | } |
654 | } |
655 | |
656 | xvcu->aclk = devm_clk_get(dev: &pdev->dev, id: "aclk" ); |
657 | if (IS_ERR(ptr: xvcu->aclk)) { |
658 | dev_err(&pdev->dev, "Could not get aclk clock\n" ); |
659 | return PTR_ERR(ptr: xvcu->aclk); |
660 | } |
661 | |
662 | xvcu->pll_ref = devm_clk_get(dev: &pdev->dev, id: "pll_ref" ); |
663 | if (IS_ERR(ptr: xvcu->pll_ref)) { |
664 | dev_err(&pdev->dev, "Could not get pll_ref clock\n" ); |
665 | return PTR_ERR(ptr: xvcu->pll_ref); |
666 | } |
667 | |
668 | ret = clk_prepare_enable(clk: xvcu->aclk); |
669 | if (ret) { |
670 | dev_err(&pdev->dev, "aclk clock enable failed\n" ); |
671 | return ret; |
672 | } |
673 | |
674 | /* |
675 | * Do the Gasket isolation and put the VCU out of reset |
676 | * Bit 0 : Gasket isolation |
677 | * Bit 1 : put VCU out of reset |
678 | */ |
679 | regmap_write(map: xvcu->logicore_reg_ba, VCU_GASKET_INIT, VCU_GASKET_VALUE); |
680 | |
681 | ret = xvcu_register_clock_provider(xvcu); |
682 | if (ret) { |
683 | dev_err(&pdev->dev, "failed to register clock provider\n" ); |
684 | goto error_clk_provider; |
685 | } |
686 | |
687 | dev_set_drvdata(dev: &pdev->dev, data: xvcu); |
688 | |
689 | return 0; |
690 | |
691 | error_clk_provider: |
692 | xvcu_unregister_clock_provider(xvcu); |
693 | clk_disable_unprepare(clk: xvcu->aclk); |
694 | return ret; |
695 | } |
696 | |
697 | /** |
698 | * xvcu_remove - Insert gasket isolation |
699 | * and disable the clock |
700 | * @pdev: Pointer to the platform_device structure |
701 | * |
702 | * Return: Returns 0 on success |
703 | * Negative error code otherwise |
704 | */ |
705 | static void xvcu_remove(struct platform_device *pdev) |
706 | { |
707 | struct xvcu_device *xvcu; |
708 | |
709 | xvcu = platform_get_drvdata(pdev); |
710 | |
711 | xvcu_unregister_clock_provider(xvcu); |
712 | |
713 | /* Add the Gasket isolation and put the VCU in reset. */ |
714 | regmap_write(map: xvcu->logicore_reg_ba, VCU_GASKET_INIT, val: 0); |
715 | |
716 | clk_disable_unprepare(clk: xvcu->aclk); |
717 | } |
718 | |
719 | static const struct of_device_id xvcu_of_id_table[] = { |
720 | { .compatible = "xlnx,vcu" }, |
721 | { .compatible = "xlnx,vcu-logicoreip-1.0" }, |
722 | { } |
723 | }; |
724 | MODULE_DEVICE_TABLE(of, xvcu_of_id_table); |
725 | |
726 | static struct platform_driver xvcu_driver = { |
727 | .driver = { |
728 | .name = "xilinx-vcu" , |
729 | .of_match_table = xvcu_of_id_table, |
730 | }, |
731 | .probe = xvcu_probe, |
732 | .remove_new = xvcu_remove, |
733 | }; |
734 | |
735 | module_platform_driver(xvcu_driver); |
736 | |
737 | MODULE_AUTHOR("Dhaval Shah <dshah@xilinx.com>" ); |
738 | MODULE_DESCRIPTION("Xilinx VCU init Driver" ); |
739 | MODULE_LICENSE("GPL v2" ); |
740 | |