1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright 2024-2025 NXP
4 */
5
6 #include <dt-bindings/clock/nxp,imx94-clock.h>
7 #include <dt-bindings/clock/nxp,imx95-clock.h>
8 #include <linux/clk.h>
9 #include <linux/clk-provider.h>
10 #include <linux/pm_runtime.h>
11 #include <linux/debugfs.h>
12 #include <linux/device.h>
13 #include <linux/err.h>
14 #include <linux/io.h>
15 #include <linux/module.h>
16 #include <linux/of_address.h>
17 #include <linux/of_device.h>
18 #include <linux/of_platform.h>
19 #include <linux/platform_device.h>
20 #include <linux/regmap.h>
21 #include <linux/slab.h>
22 #include <linux/spinlock.h>
23 #include <linux/types.h>
24
25 enum {
26 CLK_GATE,
27 CLK_DIVIDER,
28 CLK_MUX,
29 };
30
31 struct imx95_blk_ctl {
32 struct device *dev;
33 spinlock_t lock;
34 struct clk *clk_apb;
35
36 void __iomem *base;
37 /* clock gate register */
38 u32 clk_reg_restore;
39 const struct imx95_blk_ctl_dev_data *pdata;
40 };
41
42 struct imx95_blk_ctl_clk_dev_data {
43 const char *name;
44 const char * const *parent_names;
45 u32 num_parents;
46 u32 reg;
47 u32 bit_idx;
48 u32 bit_width;
49 u32 clk_type;
50 u32 flags;
51 u32 flags2;
52 u32 type;
53 };
54
55 struct imx95_blk_ctl_dev_data {
56 const struct imx95_blk_ctl_clk_dev_data *clk_dev_data;
57 u32 num_clks;
58 bool rpm_enabled;
59 u32 clk_reg_offset;
60 };
61
62 static const struct imx95_blk_ctl_clk_dev_data vpublk_clk_dev_data[] = {
63 [IMX95_CLK_VPUBLK_WAVE] = {
64 .name = "vpublk_wave_vpu",
65 .parent_names = (const char *[]){ "vpu", },
66 .num_parents = 1,
67 .reg = 8,
68 .bit_idx = 0,
69 .type = CLK_GATE,
70 .flags = CLK_SET_RATE_PARENT,
71 .flags2 = CLK_GATE_SET_TO_DISABLE,
72 },
73 [IMX95_CLK_VPUBLK_JPEG_ENC] = {
74 .name = "vpublk_jpeg_enc",
75 .parent_names = (const char *[]){ "vpujpeg", },
76 .num_parents = 1,
77 .reg = 8,
78 .bit_idx = 1,
79 .type = CLK_GATE,
80 .flags = CLK_SET_RATE_PARENT,
81 .flags2 = CLK_GATE_SET_TO_DISABLE,
82 },
83 [IMX95_CLK_VPUBLK_JPEG_DEC] = {
84 .name = "vpublk_jpeg_dec",
85 .parent_names = (const char *[]){ "vpujpeg", },
86 .num_parents = 1,
87 .reg = 8,
88 .bit_idx = 2,
89 .type = CLK_GATE,
90 .flags = CLK_SET_RATE_PARENT,
91 .flags2 = CLK_GATE_SET_TO_DISABLE,
92 }
93 };
94
95 static const struct imx95_blk_ctl_dev_data vpublk_dev_data = {
96 .num_clks = ARRAY_SIZE(vpublk_clk_dev_data),
97 .clk_dev_data = vpublk_clk_dev_data,
98 .rpm_enabled = true,
99 .clk_reg_offset = 8,
100 };
101
102 static const struct imx95_blk_ctl_clk_dev_data camblk_clk_dev_data[] = {
103 [IMX95_CLK_CAMBLK_CSI2_FOR0] = {
104 .name = "camblk_csi2_for0",
105 .parent_names = (const char *[]){ "camisi", },
106 .num_parents = 1,
107 .reg = 0,
108 .bit_idx = 0,
109 .type = CLK_GATE,
110 .flags = CLK_SET_RATE_PARENT,
111 .flags2 = CLK_GATE_SET_TO_DISABLE,
112 },
113 [IMX95_CLK_CAMBLK_CSI2_FOR1] = {
114 .name = "camblk_csi2_for1",
115 .parent_names = (const char *[]){ "camisi", },
116 .num_parents = 1,
117 .reg = 0,
118 .bit_idx = 1,
119 .type = CLK_GATE,
120 .flags = CLK_SET_RATE_PARENT,
121 .flags2 = CLK_GATE_SET_TO_DISABLE,
122 },
123 [IMX95_CLK_CAMBLK_ISP_AXI] = {
124 .name = "camblk_isp_axi",
125 .parent_names = (const char *[]){ "camaxi", },
126 .num_parents = 1,
127 .reg = 0,
128 .bit_idx = 4,
129 .type = CLK_GATE,
130 .flags = CLK_SET_RATE_PARENT,
131 .flags2 = CLK_GATE_SET_TO_DISABLE,
132 },
133 [IMX95_CLK_CAMBLK_ISP_PIXEL] = {
134 .name = "camblk_isp_pixel",
135 .parent_names = (const char *[]){ "camisi", },
136 .num_parents = 1,
137 .reg = 0,
138 .bit_idx = 5,
139 .type = CLK_GATE,
140 .flags = CLK_SET_RATE_PARENT,
141 .flags2 = CLK_GATE_SET_TO_DISABLE,
142 },
143 [IMX95_CLK_CAMBLK_ISP] = {
144 .name = "camblk_isp",
145 .parent_names = (const char *[]){ "camisi", },
146 .num_parents = 1,
147 .reg = 0,
148 .bit_idx = 6,
149 .type = CLK_GATE,
150 .flags = CLK_SET_RATE_PARENT,
151 .flags2 = CLK_GATE_SET_TO_DISABLE,
152 }
153 };
154
155 static const struct imx95_blk_ctl_dev_data camblk_dev_data = {
156 .num_clks = ARRAY_SIZE(camblk_clk_dev_data),
157 .clk_dev_data = camblk_clk_dev_data,
158 .clk_reg_offset = 0,
159 };
160
161 static const struct imx95_blk_ctl_clk_dev_data imx95_lvds_clk_dev_data[] = {
162 [IMX95_CLK_DISPMIX_LVDS_PHY_DIV] = {
163 .name = "ldb_phy_div",
164 .parent_names = (const char *[]){ "ldbpll", },
165 .num_parents = 1,
166 .reg = 0,
167 .bit_idx = 0,
168 .bit_width = 1,
169 .type = CLK_DIVIDER,
170 .flags2 = CLK_DIVIDER_POWER_OF_TWO,
171 },
172 [IMX95_CLK_DISPMIX_LVDS_CH0_GATE] = {
173 .name = "lvds_ch0_gate",
174 .parent_names = (const char *[]){ "ldb_phy_div", },
175 .num_parents = 1,
176 .reg = 0,
177 .bit_idx = 1,
178 .bit_width = 1,
179 .type = CLK_GATE,
180 .flags = CLK_SET_RATE_PARENT,
181 .flags2 = CLK_GATE_SET_TO_DISABLE,
182 },
183 [IMX95_CLK_DISPMIX_LVDS_CH1_GATE] = {
184 .name = "lvds_ch1_gate",
185 .parent_names = (const char *[]){ "ldb_phy_div", },
186 .num_parents = 1,
187 .reg = 0,
188 .bit_idx = 2,
189 .bit_width = 1,
190 .type = CLK_GATE,
191 .flags = CLK_SET_RATE_PARENT,
192 .flags2 = CLK_GATE_SET_TO_DISABLE,
193 },
194 [IMX95_CLK_DISPMIX_PIX_DI0_GATE] = {
195 .name = "lvds_di0_gate",
196 .parent_names = (const char *[]){ "ldb_pll_div7", },
197 .num_parents = 1,
198 .reg = 0,
199 .bit_idx = 3,
200 .bit_width = 1,
201 .type = CLK_GATE,
202 .flags = CLK_SET_RATE_PARENT,
203 .flags2 = CLK_GATE_SET_TO_DISABLE,
204 },
205 [IMX95_CLK_DISPMIX_PIX_DI1_GATE] = {
206 .name = "lvds_di1_gate",
207 .parent_names = (const char *[]){ "ldb_pll_div7", },
208 .num_parents = 1,
209 .reg = 0,
210 .bit_idx = 4,
211 .bit_width = 1,
212 .type = CLK_GATE,
213 .flags = CLK_SET_RATE_PARENT,
214 .flags2 = CLK_GATE_SET_TO_DISABLE,
215 },
216 };
217
218 static const struct imx95_blk_ctl_dev_data imx95_lvds_csr_dev_data = {
219 .num_clks = ARRAY_SIZE(imx95_lvds_clk_dev_data),
220 .clk_dev_data = imx95_lvds_clk_dev_data,
221 .clk_reg_offset = 0,
222 };
223
224 static const char * const imx95_disp_engine_parents[] = {
225 "videopll1", "dsi_pll", "ldb_pll_div7"
226 };
227
228 static const struct imx95_blk_ctl_clk_dev_data imx95_dispmix_csr_clk_dev_data[] = {
229 [IMX95_CLK_DISPMIX_ENG0_SEL] = {
230 .name = "disp_engine0_sel",
231 .parent_names = imx95_disp_engine_parents,
232 .num_parents = ARRAY_SIZE(imx95_disp_engine_parents),
233 .reg = 0,
234 .bit_idx = 0,
235 .bit_width = 2,
236 .type = CLK_MUX,
237 .flags = CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT,
238 },
239 [IMX95_CLK_DISPMIX_ENG1_SEL] = {
240 .name = "disp_engine1_sel",
241 .parent_names = imx95_disp_engine_parents,
242 .num_parents = ARRAY_SIZE(imx95_disp_engine_parents),
243 .reg = 0,
244 .bit_idx = 2,
245 .bit_width = 2,
246 .type = CLK_MUX,
247 .flags = CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT,
248 }
249 };
250
251 static const struct imx95_blk_ctl_dev_data imx95_dispmix_csr_dev_data = {
252 .num_clks = ARRAY_SIZE(imx95_dispmix_csr_clk_dev_data),
253 .clk_dev_data = imx95_dispmix_csr_clk_dev_data,
254 .clk_reg_offset = 0,
255 };
256
257 static const struct imx95_blk_ctl_clk_dev_data netxmix_clk_dev_data[] = {
258 [IMX95_CLK_NETCMIX_ENETC0_RMII] = {
259 .name = "enetc0_rmii_sel",
260 .parent_names = (const char *[]){"ext_enetref", "enetref"},
261 .num_parents = 2,
262 .reg = 4,
263 .bit_idx = 5,
264 .bit_width = 1,
265 .type = CLK_MUX,
266 .flags = CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT,
267 },
268 [IMX95_CLK_NETCMIX_ENETC1_RMII] = {
269 .name = "enetc1_rmii_sel",
270 .parent_names = (const char *[]){"ext_enetref", "enetref"},
271 .num_parents = 2,
272 .reg = 4,
273 .bit_idx = 10,
274 .bit_width = 1,
275 .type = CLK_MUX,
276 .flags = CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT,
277 },
278 };
279
280 static const struct imx95_blk_ctl_dev_data netcmix_dev_data = {
281 .num_clks = ARRAY_SIZE(netxmix_clk_dev_data),
282 .clk_dev_data = netxmix_clk_dev_data,
283 .clk_reg_offset = 0,
284 };
285
286 static const struct imx95_blk_ctl_clk_dev_data hsio_blk_ctl_clk_dev_data[] = {
287 [0] = {
288 .name = "hsio_blk_ctl_clk",
289 .parent_names = (const char *[]){ "hsio_pll", },
290 .num_parents = 1,
291 .reg = 0,
292 .bit_idx = 6,
293 .bit_width = 1,
294 .type = CLK_GATE,
295 .flags = CLK_SET_RATE_PARENT,
296 }
297 };
298
299 static const struct imx95_blk_ctl_dev_data hsio_blk_ctl_dev_data = {
300 .num_clks = 1,
301 .clk_dev_data = hsio_blk_ctl_clk_dev_data,
302 .clk_reg_offset = 0,
303 };
304
305 static const struct imx95_blk_ctl_clk_dev_data imx94_lvds_clk_dev_data[] = {
306 [IMX94_CLK_DISPMIX_LVDS_CLK_GATE] = {
307 .name = "lvds_clk_gate",
308 .parent_names = (const char *[]){ "ldbpll", },
309 .num_parents = 1,
310 .reg = 0,
311 .bit_idx = 1,
312 .bit_width = 1,
313 .type = CLK_GATE,
314 .flags = CLK_SET_RATE_PARENT,
315 .flags2 = CLK_GATE_SET_TO_DISABLE,
316 },
317 };
318
319 static const struct imx95_blk_ctl_dev_data imx94_lvds_csr_dev_data = {
320 .num_clks = ARRAY_SIZE(imx94_lvds_clk_dev_data),
321 .clk_dev_data = imx94_lvds_clk_dev_data,
322 .clk_reg_offset = 0,
323 .rpm_enabled = true,
324 };
325
326 static const char * const imx94_disp_engine_parents[] = {
327 "disppix", "ldb_pll_div7"
328 };
329
330 static const struct imx95_blk_ctl_clk_dev_data imx94_dispmix_csr_clk_dev_data[] = {
331 [IMX94_CLK_DISPMIX_CLK_SEL] = {
332 .name = "disp_clk_sel",
333 .parent_names = imx94_disp_engine_parents,
334 .num_parents = ARRAY_SIZE(imx94_disp_engine_parents),
335 .reg = 0,
336 .bit_idx = 1,
337 .bit_width = 1,
338 .type = CLK_MUX,
339 .flags = CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT,
340 },
341 };
342
343 static const struct imx95_blk_ctl_dev_data imx94_dispmix_csr_dev_data = {
344 .num_clks = ARRAY_SIZE(imx94_dispmix_csr_clk_dev_data),
345 .clk_dev_data = imx94_dispmix_csr_clk_dev_data,
346 .clk_reg_offset = 0,
347 .rpm_enabled = true,
348 };
349
imx95_bc_probe(struct platform_device * pdev)350 static int imx95_bc_probe(struct platform_device *pdev)
351 {
352 struct device *dev = &pdev->dev;
353 struct imx95_blk_ctl *bc;
354 struct clk_hw_onecell_data *clk_hw_data;
355 struct clk_hw **hws;
356 void __iomem *base;
357 int i, ret;
358
359 bc = devm_kzalloc(dev, sizeof(*bc), GFP_KERNEL);
360 if (!bc)
361 return -ENOMEM;
362 bc->dev = dev;
363 dev_set_drvdata(&pdev->dev, bc);
364
365 spin_lock_init(&bc->lock);
366
367 base = devm_platform_ioremap_resource(pdev, 0);
368 if (IS_ERR(base))
369 return PTR_ERR(base);
370
371 bc->base = base;
372 bc->clk_apb = devm_clk_get(dev, NULL);
373 if (IS_ERR(bc->clk_apb))
374 return dev_err_probe(dev, PTR_ERR(bc->clk_apb), "failed to get APB clock\n");
375
376 ret = clk_prepare_enable(bc->clk_apb);
377 if (ret) {
378 dev_err(dev, "failed to enable apb clock: %d\n", ret);
379 return ret;
380 }
381
382 bc->pdata = of_device_get_match_data(dev);
383 if (!bc->pdata)
384 return devm_of_platform_populate(dev);
385
386 clk_hw_data = devm_kzalloc(dev, struct_size(clk_hw_data, hws, bc->pdata->num_clks),
387 GFP_KERNEL);
388 if (!clk_hw_data)
389 return -ENOMEM;
390
391 if (bc->pdata->rpm_enabled) {
392 devm_pm_runtime_enable(&pdev->dev);
393 pm_runtime_resume_and_get(&pdev->dev);
394 }
395
396 clk_hw_data->num = bc->pdata->num_clks;
397 hws = clk_hw_data->hws;
398
399 for (i = 0; i < bc->pdata->num_clks; i++) {
400 const struct imx95_blk_ctl_clk_dev_data *data = &bc->pdata->clk_dev_data[i];
401 void __iomem *reg = base + data->reg;
402
403 if (data->type == CLK_MUX) {
404 hws[i] = clk_hw_register_mux(dev, data->name, data->parent_names,
405 data->num_parents, data->flags, reg,
406 data->bit_idx, data->bit_width,
407 data->flags2, &bc->lock);
408 } else if (data->type == CLK_DIVIDER) {
409 hws[i] = clk_hw_register_divider(dev, data->name, data->parent_names[0],
410 data->flags, reg, data->bit_idx,
411 data->bit_width, data->flags2, &bc->lock);
412 } else {
413 hws[i] = clk_hw_register_gate(dev, data->name, data->parent_names[0],
414 data->flags, reg, data->bit_idx,
415 data->flags2, &bc->lock);
416 }
417 if (IS_ERR(hws[i])) {
418 ret = PTR_ERR(hws[i]);
419 dev_err(dev, "failed to register: %s:%d\n", data->name, ret);
420 goto cleanup;
421 }
422 }
423
424 ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, clk_hw_data);
425 if (ret)
426 goto cleanup;
427
428 ret = devm_of_platform_populate(dev);
429 if (ret) {
430 of_clk_del_provider(dev->of_node);
431 goto cleanup;
432 }
433
434 if (pm_runtime_enabled(bc->dev)) {
435 pm_runtime_put_sync(&pdev->dev);
436 clk_disable_unprepare(bc->clk_apb);
437 }
438
439 return 0;
440
441 cleanup:
442 for (i = 0; i < bc->pdata->num_clks; i++) {
443 if (IS_ERR_OR_NULL(hws[i]))
444 continue;
445 clk_hw_unregister(hws[i]);
446 }
447
448 return ret;
449 }
450
451 #ifdef CONFIG_PM
imx95_bc_runtime_suspend(struct device * dev)452 static int imx95_bc_runtime_suspend(struct device *dev)
453 {
454 struct imx95_blk_ctl *bc = dev_get_drvdata(dev);
455
456 bc->clk_reg_restore = readl(bc->base + bc->pdata->clk_reg_offset);
457 clk_disable_unprepare(bc->clk_apb);
458
459 return 0;
460 }
461
imx95_bc_runtime_resume(struct device * dev)462 static int imx95_bc_runtime_resume(struct device *dev)
463 {
464 struct imx95_blk_ctl *bc = dev_get_drvdata(dev);
465 int ret;
466
467 ret = clk_prepare_enable(bc->clk_apb);
468 if (ret)
469 return ret;
470
471 writel(bc->clk_reg_restore, bc->base + bc->pdata->clk_reg_offset);
472
473 return 0;
474 }
475 #endif
476
477 #ifdef CONFIG_PM_SLEEP
imx95_bc_suspend(struct device * dev)478 static int imx95_bc_suspend(struct device *dev)
479 {
480 struct imx95_blk_ctl *bc = dev_get_drvdata(dev);
481
482 if (pm_runtime_suspended(dev))
483 return 0;
484
485 bc->clk_reg_restore = readl(bc->base + bc->pdata->clk_reg_offset);
486 clk_disable_unprepare(bc->clk_apb);
487
488 return 0;
489 }
490
imx95_bc_resume(struct device * dev)491 static int imx95_bc_resume(struct device *dev)
492 {
493 struct imx95_blk_ctl *bc = dev_get_drvdata(dev);
494 int ret;
495
496 if (pm_runtime_suspended(dev))
497 return 0;
498
499 ret = clk_prepare_enable(bc->clk_apb);
500 if (ret)
501 return ret;
502
503 writel(bc->clk_reg_restore, bc->base + bc->pdata->clk_reg_offset);
504
505 return 0;
506 }
507 #endif
508
509 static const struct dev_pm_ops imx95_bc_pm_ops = {
510 SET_RUNTIME_PM_OPS(imx95_bc_runtime_suspend, imx95_bc_runtime_resume, NULL)
511 SET_SYSTEM_SLEEP_PM_OPS(imx95_bc_suspend, imx95_bc_resume)
512 };
513
514 static const struct of_device_id imx95_bc_of_match[] = {
515 { .compatible = "nxp,imx94-display-csr", .data = &imx94_dispmix_csr_dev_data },
516 { .compatible = "nxp,imx94-lvds-csr", .data = &imx94_lvds_csr_dev_data },
517 { .compatible = "nxp,imx95-camera-csr", .data = &camblk_dev_data },
518 { .compatible = "nxp,imx95-display-master-csr", },
519 { .compatible = "nxp,imx95-display-csr", .data = &imx95_dispmix_csr_dev_data },
520 { .compatible = "nxp,imx95-lvds-csr", .data = &imx95_lvds_csr_dev_data },
521 { .compatible = "nxp,imx95-hsio-blk-ctl", .data = &hsio_blk_ctl_dev_data },
522 { .compatible = "nxp,imx95-vpu-csr", .data = &vpublk_dev_data },
523 { .compatible = "nxp,imx95-netcmix-blk-ctrl", .data = &netcmix_dev_data},
524 { /* Sentinel */ },
525 };
526 MODULE_DEVICE_TABLE(of, imx95_bc_of_match);
527
528 static struct platform_driver imx95_bc_driver = {
529 .probe = imx95_bc_probe,
530 .driver = {
531 .name = "imx95-blk-ctl",
532 .of_match_table = imx95_bc_of_match,
533 .pm = &imx95_bc_pm_ops,
534 },
535 };
536 module_platform_driver(imx95_bc_driver);
537
538 MODULE_DESCRIPTION("NXP i.MX95 blk ctl driver");
539 MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>");
540 MODULE_LICENSE("GPL");
541