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