Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

clk: imx: add i.MX95 BLK CTL clk driver

i.MX95 has BLK CTL modules in various MIXes, the BLK CTL modules
support clock features such as mux/gate/div. This patch
is to add the clock feature of BLK CTL modules

Signed-off-by: Peng Fan <peng.fan@nxp.com>
Reviewed-by: Abel Vesa <abel.vesa@linaro.org>
Link: https://lore.kernel.org/r/20240401-imx95-blk-ctl-v6-4-84d4eca1e759@nxp.com
Signed-off-by: Abel Vesa <abel.vesa@linaro.org>

authored by

Peng Fan and committed by
Abel Vesa
5224b189 c6e87b06

+446
+7
drivers/clk/imx/Kconfig
··· 114 114 help 115 115 Build the driver for i.MX93 CCM Clock Driver 116 116 117 + config CLK_IMX95_BLK_CTL 118 + tristate "IMX95 Clock Driver for BLK CTL" 119 + depends on ARCH_MXC || COMPILE_TEST 120 + select MXC_CLK 121 + help 122 + Build the clock driver for i.MX95 BLK CTL 123 + 117 124 config CLK_IMXRT1050 118 125 tristate "IMXRT1050 CCM Clock Driver" 119 126 depends on SOC_IMXRT || COMPILE_TEST
+1
drivers/clk/imx/Makefile
··· 31 31 obj-$(CONFIG_CLK_IMX8MQ) += clk-imx8mq.o 32 32 33 33 obj-$(CONFIG_CLK_IMX93) += clk-imx93.o 34 + obj-$(CONFIG_CLK_IMX95_BLK_CTL) += clk-imx95-blk-ctl.o 34 35 35 36 obj-$(CONFIG_MXC_CLK_SCU) += clk-imx-scu.o clk-imx-lpcg-scu.o clk-imx-acm.o 36 37 clk-imx-scu-$(CONFIG_CLK_IMX8QXP) += clk-scu.o clk-imx8qxp.o \
+438
drivers/clk/imx/clk-imx95-blk-ctl.c
··· 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 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", }, 226 + .num_parents = 4, 227 + .reg = 0, 228 + .bit_idx = 0, 229 + .bit_width = 2, 230 + .type = CLK_MUX, 231 + .flags = CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT, 232 + }, 233 + [IMX95_CLK_DISPMIX_ENG1_SEL] = { 234 + .name = "disp_engine1_sel", 235 + .parent_names = (const char *[]){"videopll1", "dsi_pll", "ldb_pll_div7", }, 236 + .num_parents = 4, 237 + .reg = 0, 238 + .bit_idx = 2, 239 + .bit_width = 2, 240 + .type = CLK_MUX, 241 + .flags = CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT, 242 + } 243 + }; 244 + 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, 248 + .clk_reg_offset = 0, 249 + }; 250 + 251 + static int imx95_bc_probe(struct platform_device *pdev) 252 + { 253 + struct device *dev = &pdev->dev; 254 + const struct imx95_blk_ctl_dev_data *bc_data; 255 + struct imx95_blk_ctl *bc; 256 + struct clk_hw_onecell_data *clk_hw_data; 257 + struct clk_hw **hws; 258 + void __iomem *base; 259 + int i, ret; 260 + 261 + bc = devm_kzalloc(dev, sizeof(*bc), GFP_KERNEL); 262 + if (!bc) 263 + return -ENOMEM; 264 + bc->dev = dev; 265 + dev_set_drvdata(&pdev->dev, bc); 266 + 267 + spin_lock_init(&bc->lock); 268 + 269 + base = devm_platform_ioremap_resource(pdev, 0); 270 + if (IS_ERR(base)) 271 + return PTR_ERR(base); 272 + 273 + bc->base = base; 274 + bc->clk_apb = devm_clk_get(dev, NULL); 275 + if (IS_ERR(bc->clk_apb)) 276 + return dev_err_probe(dev, PTR_ERR(bc->clk_apb), "failed to get APB clock\n"); 277 + 278 + ret = clk_prepare_enable(bc->clk_apb); 279 + if (ret) { 280 + dev_err(dev, "failed to enable apb clock: %d\n", ret); 281 + return ret; 282 + } 283 + 284 + bc_data = of_device_get_match_data(dev); 285 + if (!bc_data) 286 + return devm_of_platform_populate(dev); 287 + 288 + clk_hw_data = devm_kzalloc(dev, struct_size(clk_hw_data, hws, bc_data->num_clks), 289 + GFP_KERNEL); 290 + if (!clk_hw_data) 291 + return -ENOMEM; 292 + 293 + if (bc_data->rpm_enabled) 294 + pm_runtime_enable(&pdev->dev); 295 + 296 + clk_hw_data->num = bc_data->num_clks; 297 + hws = clk_hw_data->hws; 298 + 299 + for (i = 0; i < bc_data->num_clks; i++) { 300 + const struct imx95_blk_ctl_clk_dev_data *data = &bc_data->clk_dev_data[i]; 301 + void __iomem *reg = base + data->reg; 302 + 303 + if (data->type == CLK_MUX) { 304 + hws[i] = clk_hw_register_mux(dev, data->name, data->parent_names, 305 + data->num_parents, data->flags, reg, 306 + data->bit_idx, data->bit_width, 307 + data->flags2, &bc->lock); 308 + } else if (data->type == CLK_DIVIDER) { 309 + hws[i] = clk_hw_register_divider(dev, data->name, data->parent_names[0], 310 + data->flags, reg, data->bit_idx, 311 + data->bit_width, data->flags2, &bc->lock); 312 + } else { 313 + hws[i] = clk_hw_register_gate(dev, data->name, data->parent_names[0], 314 + data->flags, reg, data->bit_idx, 315 + data->flags2, &bc->lock); 316 + } 317 + if (IS_ERR(hws[i])) { 318 + ret = PTR_ERR(hws[i]); 319 + dev_err(dev, "failed to register: %s:%d\n", data->name, ret); 320 + goto cleanup; 321 + } 322 + } 323 + 324 + ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, clk_hw_data); 325 + if (ret) 326 + goto cleanup; 327 + 328 + ret = devm_of_platform_populate(dev); 329 + if (ret) { 330 + of_clk_del_provider(dev->of_node); 331 + goto cleanup; 332 + } 333 + 334 + if (pm_runtime_enabled(bc->dev)) 335 + clk_disable_unprepare(bc->clk_apb); 336 + 337 + return 0; 338 + 339 + cleanup: 340 + for (i = 0; i < bc_data->num_clks; i++) { 341 + if (IS_ERR_OR_NULL(hws[i])) 342 + continue; 343 + clk_hw_unregister(hws[i]); 344 + } 345 + 346 + if (bc_data->rpm_enabled) 347 + pm_runtime_disable(&pdev->dev); 348 + 349 + return ret; 350 + } 351 + 352 + #ifdef CONFIG_PM 353 + static int imx95_bc_runtime_suspend(struct device *dev) 354 + { 355 + struct imx95_blk_ctl *bc = dev_get_drvdata(dev); 356 + 357 + clk_disable_unprepare(bc->clk_apb); 358 + return 0; 359 + } 360 + 361 + static int imx95_bc_runtime_resume(struct device *dev) 362 + { 363 + struct imx95_blk_ctl *bc = dev_get_drvdata(dev); 364 + 365 + return clk_prepare_enable(bc->clk_apb); 366 + } 367 + #endif 368 + 369 + #ifdef CONFIG_PM_SLEEP 370 + static int imx95_bc_suspend(struct device *dev) 371 + { 372 + struct imx95_blk_ctl *bc = dev_get_drvdata(dev); 373 + const struct imx95_blk_ctl_dev_data *bc_data; 374 + int ret; 375 + 376 + bc_data = of_device_get_match_data(dev); 377 + if (!bc_data) 378 + return 0; 379 + 380 + if (bc_data->rpm_enabled) { 381 + ret = pm_runtime_get_sync(bc->dev); 382 + if (ret < 0) { 383 + pm_runtime_put_noidle(bc->dev); 384 + return ret; 385 + } 386 + } 387 + 388 + bc->clk_reg_restore = readl(bc->base + bc_data->clk_reg_offset); 389 + 390 + return 0; 391 + } 392 + 393 + static int imx95_bc_resume(struct device *dev) 394 + { 395 + struct imx95_blk_ctl *bc = dev_get_drvdata(dev); 396 + const struct imx95_blk_ctl_dev_data *bc_data; 397 + 398 + bc_data = of_device_get_match_data(dev); 399 + if (!bc_data) 400 + return 0; 401 + 402 + writel(bc->clk_reg_restore, bc->base + bc_data->clk_reg_offset); 403 + 404 + if (bc_data->rpm_enabled) 405 + pm_runtime_put(bc->dev); 406 + 407 + return 0; 408 + } 409 + #endif 410 + 411 + static const struct dev_pm_ops imx95_bc_pm_ops = { 412 + SET_RUNTIME_PM_OPS(imx95_bc_runtime_suspend, imx95_bc_runtime_resume, NULL) 413 + SET_SYSTEM_SLEEP_PM_OPS(imx95_bc_suspend, imx95_bc_resume) 414 + }; 415 + 416 + static const struct of_device_id imx95_bc_of_match[] = { 417 + { .compatible = "nxp,imx95-camera-csr", .data = &camblk_dev_data }, 418 + { .compatible = "nxp,imx95-display-master-csr", }, 419 + { .compatible = "nxp,imx95-lvds-csr", .data = &lvds_csr_dev_data }, 420 + { .compatible = "nxp,imx95-display-csr", .data = &dispmix_csr_dev_data }, 421 + { .compatible = "nxp,imx95-vpu-csr", .data = &vpublk_dev_data }, 422 + { /* Sentinel */ }, 423 + }; 424 + MODULE_DEVICE_TABLE(of, imx95_bc_of_match); 425 + 426 + static struct platform_driver imx95_bc_driver = { 427 + .probe = imx95_bc_probe, 428 + .driver = { 429 + .name = "imx95-blk-ctl", 430 + .of_match_table = imx95_bc_of_match, 431 + .pm = &imx95_bc_pm_ops, 432 + }, 433 + }; 434 + module_platform_driver(imx95_bc_driver); 435 + 436 + MODULE_DESCRIPTION("NXP i.MX95 blk ctl driver"); 437 + MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>"); 438 + MODULE_LICENSE("GPL");