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 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 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 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 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 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