1 // SPDX-License-Identifier: GPL-2.0
6 #include <dt-bindings/clock/nxp,imx95-clock.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>
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>
30 struct imx95_blk_ctl
{
36 /* clock gate register */
40 struct imx95_blk_ctl_clk_dev_data
{
42 const char * const *parent_names
;
53 struct imx95_blk_ctl_dev_data
{
54 const struct imx95_blk_ctl_clk_dev_data
*clk_dev_data
;
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", },
68 .flags
= CLK_SET_RATE_PARENT
,
69 .flags2
= CLK_GATE_SET_TO_DISABLE
,
71 [IMX95_CLK_VPUBLK_JPEG_ENC
] = {
72 .name
= "vpublk_jpeg_enc",
73 .parent_names
= (const char *[]){ "vpujpeg", },
78 .flags
= CLK_SET_RATE_PARENT
,
79 .flags2
= CLK_GATE_SET_TO_DISABLE
,
81 [IMX95_CLK_VPUBLK_JPEG_DEC
] = {
82 .name
= "vpublk_jpeg_dec",
83 .parent_names
= (const char *[]){ "vpujpeg", },
88 .flags
= CLK_SET_RATE_PARENT
,
89 .flags2
= CLK_GATE_SET_TO_DISABLE
,
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
,
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", },
108 .flags
= CLK_SET_RATE_PARENT
,
109 .flags2
= CLK_GATE_SET_TO_DISABLE
,
111 [IMX95_CLK_CAMBLK_CSI2_FOR1
] = {
112 .name
= "camblk_csi2_for1",
113 .parent_names
= (const char *[]){ "camisi", },
118 .flags
= CLK_SET_RATE_PARENT
,
119 .flags2
= CLK_GATE_SET_TO_DISABLE
,
121 [IMX95_CLK_CAMBLK_ISP_AXI
] = {
122 .name
= "camblk_isp_axi",
123 .parent_names
= (const char *[]){ "camaxi", },
128 .flags
= CLK_SET_RATE_PARENT
,
129 .flags2
= CLK_GATE_SET_TO_DISABLE
,
131 [IMX95_CLK_CAMBLK_ISP_PIXEL
] = {
132 .name
= "camblk_isp_pixel",
133 .parent_names
= (const char *[]){ "camisi", },
138 .flags
= CLK_SET_RATE_PARENT
,
139 .flags2
= CLK_GATE_SET_TO_DISABLE
,
141 [IMX95_CLK_CAMBLK_ISP
] = {
142 .name
= "camblk_isp",
143 .parent_names
= (const char *[]){ "camisi", },
148 .flags
= CLK_SET_RATE_PARENT
,
149 .flags2
= CLK_GATE_SET_TO_DISABLE
,
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
,
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", },
168 .flags2
= CLK_DIVIDER_POWER_OF_TWO
,
170 [IMX95_CLK_DISPMIX_LVDS_CH0_GATE
] = {
171 .name
= "lvds_ch0_gate",
172 .parent_names
= (const char *[]){ "ldb_phy_div", },
178 .flags
= CLK_SET_RATE_PARENT
,
179 .flags2
= CLK_GATE_SET_TO_DISABLE
,
181 [IMX95_CLK_DISPMIX_LVDS_CH1_GATE
] = {
182 .name
= "lvds_ch1_gate",
183 .parent_names
= (const char *[]){ "ldb_phy_div", },
189 .flags
= CLK_SET_RATE_PARENT
,
190 .flags2
= CLK_GATE_SET_TO_DISABLE
,
192 [IMX95_CLK_DISPMIX_PIX_DI0_GATE
] = {
193 .name
= "lvds_di0_gate",
194 .parent_names
= (const char *[]){ "ldb_pll_div7", },
200 .flags
= CLK_SET_RATE_PARENT
,
201 .flags2
= CLK_GATE_SET_TO_DISABLE
,
203 [IMX95_CLK_DISPMIX_PIX_DI1_GATE
] = {
204 .name
= "lvds_di1_gate",
205 .parent_names
= (const char *[]){ "ldb_pll_div7", },
211 .flags
= CLK_SET_RATE_PARENT
,
212 .flags2
= CLK_GATE_SET_TO_DISABLE
,
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
,
222 static const struct imx95_blk_ctl_clk_dev_data dispmix_csr_clk_dev_data
[] = {
223 [IMX95_CLK_DISPMIX_ENG0_SEL
] = {
224 .name
= "disp_engine0_sel",
225 .parent_names
= (const char *[]){"videopll1", "dsi_pll", "ldb_pll_div7", },
231 .flags
= CLK_SET_RATE_NO_REPARENT
| CLK_SET_RATE_PARENT
,
233 [IMX95_CLK_DISPMIX_ENG1_SEL
] = {
234 .name
= "disp_engine1_sel",
235 .parent_names
= (const char *[]){"videopll1", "dsi_pll", "ldb_pll_div7", },
241 .flags
= CLK_SET_RATE_NO_REPARENT
| CLK_SET_RATE_PARENT
,
245 static const struct imx95_blk_ctl_dev_data dispmix_csr_dev_data
= {
246 .num_clks
= ARRAY_SIZE(dispmix_csr_clk_dev_data
),
247 .clk_dev_data
= dispmix_csr_clk_dev_data
,
251 static const struct imx95_blk_ctl_clk_dev_data netxmix_clk_dev_data
[] = {
252 [IMX95_CLK_NETCMIX_ENETC0_RMII
] = {
253 .name
= "enetc0_rmii_sel",
254 .parent_names
= (const char *[]){"ext_enetref", "enetref"},
260 .flags
= CLK_SET_RATE_NO_REPARENT
| CLK_SET_RATE_PARENT
,
262 [IMX95_CLK_NETCMIX_ENETC1_RMII
] = {
263 .name
= "enetc1_rmii_sel",
264 .parent_names
= (const char *[]){"ext_enetref", "enetref"},
270 .flags
= CLK_SET_RATE_NO_REPARENT
| CLK_SET_RATE_PARENT
,
274 static const struct imx95_blk_ctl_dev_data netcmix_dev_data
= {
275 .num_clks
= ARRAY_SIZE(netxmix_clk_dev_data
),
276 .clk_dev_data
= netxmix_clk_dev_data
,
280 static const struct imx95_blk_ctl_clk_dev_data hsio_blk_ctl_clk_dev_data
[] = {
282 .name
= "hsio_blk_ctl_clk",
283 .parent_names
= (const char *[]){ "hsio_pll", },
289 .flags
= CLK_SET_RATE_PARENT
,
293 static const struct imx95_blk_ctl_dev_data hsio_blk_ctl_dev_data
= {
295 .clk_dev_data
= hsio_blk_ctl_clk_dev_data
,
299 static int imx95_bc_probe(struct platform_device
*pdev
)
301 struct device
*dev
= &pdev
->dev
;
302 const struct imx95_blk_ctl_dev_data
*bc_data
;
303 struct imx95_blk_ctl
*bc
;
304 struct clk_hw_onecell_data
*clk_hw_data
;
309 bc
= devm_kzalloc(dev
, sizeof(*bc
), GFP_KERNEL
);
313 dev_set_drvdata(&pdev
->dev
, bc
);
315 spin_lock_init(&bc
->lock
);
317 base
= devm_platform_ioremap_resource(pdev
, 0);
319 return PTR_ERR(base
);
322 bc
->clk_apb
= devm_clk_get(dev
, NULL
);
323 if (IS_ERR(bc
->clk_apb
))
324 return dev_err_probe(dev
, PTR_ERR(bc
->clk_apb
), "failed to get APB clock\n");
326 ret
= clk_prepare_enable(bc
->clk_apb
);
328 dev_err(dev
, "failed to enable apb clock: %d\n", ret
);
332 bc_data
= of_device_get_match_data(dev
);
334 return devm_of_platform_populate(dev
);
336 clk_hw_data
= devm_kzalloc(dev
, struct_size(clk_hw_data
, hws
, bc_data
->num_clks
),
341 if (bc_data
->rpm_enabled
)
342 pm_runtime_enable(&pdev
->dev
);
344 clk_hw_data
->num
= bc_data
->num_clks
;
345 hws
= clk_hw_data
->hws
;
347 for (i
= 0; i
< bc_data
->num_clks
; i
++) {
348 const struct imx95_blk_ctl_clk_dev_data
*data
= &bc_data
->clk_dev_data
[i
];
349 void __iomem
*reg
= base
+ data
->reg
;
351 if (data
->type
== CLK_MUX
) {
352 hws
[i
] = clk_hw_register_mux(dev
, data
->name
, data
->parent_names
,
353 data
->num_parents
, data
->flags
, reg
,
354 data
->bit_idx
, data
->bit_width
,
355 data
->flags2
, &bc
->lock
);
356 } else if (data
->type
== CLK_DIVIDER
) {
357 hws
[i
] = clk_hw_register_divider(dev
, data
->name
, data
->parent_names
[0],
358 data
->flags
, reg
, data
->bit_idx
,
359 data
->bit_width
, data
->flags2
, &bc
->lock
);
361 hws
[i
] = clk_hw_register_gate(dev
, data
->name
, data
->parent_names
[0],
362 data
->flags
, reg
, data
->bit_idx
,
363 data
->flags2
, &bc
->lock
);
365 if (IS_ERR(hws
[i
])) {
366 ret
= PTR_ERR(hws
[i
]);
367 dev_err(dev
, "failed to register: %s:%d\n", data
->name
, ret
);
372 ret
= of_clk_add_hw_provider(dev
->of_node
, of_clk_hw_onecell_get
, clk_hw_data
);
376 ret
= devm_of_platform_populate(dev
);
378 of_clk_del_provider(dev
->of_node
);
382 if (pm_runtime_enabled(bc
->dev
))
383 clk_disable_unprepare(bc
->clk_apb
);
388 for (i
= 0; i
< bc_data
->num_clks
; i
++) {
389 if (IS_ERR_OR_NULL(hws
[i
]))
391 clk_hw_unregister(hws
[i
]);
394 if (bc_data
->rpm_enabled
)
395 pm_runtime_disable(&pdev
->dev
);
401 static int imx95_bc_runtime_suspend(struct device
*dev
)
403 struct imx95_blk_ctl
*bc
= dev_get_drvdata(dev
);
405 clk_disable_unprepare(bc
->clk_apb
);
409 static int imx95_bc_runtime_resume(struct device
*dev
)
411 struct imx95_blk_ctl
*bc
= dev_get_drvdata(dev
);
413 return clk_prepare_enable(bc
->clk_apb
);
417 #ifdef CONFIG_PM_SLEEP
418 static int imx95_bc_suspend(struct device
*dev
)
420 struct imx95_blk_ctl
*bc
= dev_get_drvdata(dev
);
421 const struct imx95_blk_ctl_dev_data
*bc_data
;
424 bc_data
= of_device_get_match_data(dev
);
428 if (bc_data
->rpm_enabled
) {
429 ret
= pm_runtime_get_sync(bc
->dev
);
431 pm_runtime_put_noidle(bc
->dev
);
436 bc
->clk_reg_restore
= readl(bc
->base
+ bc_data
->clk_reg_offset
);
441 static int imx95_bc_resume(struct device
*dev
)
443 struct imx95_blk_ctl
*bc
= dev_get_drvdata(dev
);
444 const struct imx95_blk_ctl_dev_data
*bc_data
;
446 bc_data
= of_device_get_match_data(dev
);
450 writel(bc
->clk_reg_restore
, bc
->base
+ bc_data
->clk_reg_offset
);
452 if (bc_data
->rpm_enabled
)
453 pm_runtime_put(bc
->dev
);
459 static const struct dev_pm_ops imx95_bc_pm_ops
= {
460 SET_RUNTIME_PM_OPS(imx95_bc_runtime_suspend
, imx95_bc_runtime_resume
, NULL
)
461 SET_SYSTEM_SLEEP_PM_OPS(imx95_bc_suspend
, imx95_bc_resume
)
464 static const struct of_device_id imx95_bc_of_match
[] = {
465 { .compatible
= "nxp,imx95-camera-csr", .data
= &camblk_dev_data
},
466 { .compatible
= "nxp,imx95-display-master-csr", },
467 { .compatible
= "nxp,imx95-lvds-csr", .data
= &lvds_csr_dev_data
},
468 { .compatible
= "nxp,imx95-display-csr", .data
= &dispmix_csr_dev_data
},
469 { .compatible
= "nxp,imx95-hsio-blk-ctl", .data
= &hsio_blk_ctl_dev_data
},
470 { .compatible
= "nxp,imx95-vpu-csr", .data
= &vpublk_dev_data
},
471 { .compatible
= "nxp,imx95-netcmix-blk-ctrl", .data
= &netcmix_dev_data
},
474 MODULE_DEVICE_TABLE(of
, imx95_bc_of_match
);
476 static struct platform_driver imx95_bc_driver
= {
477 .probe
= imx95_bc_probe
,
479 .name
= "imx95-blk-ctl",
480 .of_match_table
= imx95_bc_of_match
,
481 .pm
= &imx95_bc_pm_ops
,
484 module_platform_driver(imx95_bc_driver
);
486 MODULE_DESCRIPTION("NXP i.MX95 blk ctl driver");
487 MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>");
488 MODULE_LICENSE("GPL");