469 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright 2024 NXP4 */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 24enum {25 CLK_GATE,26 CLK_DIVIDER,27 CLK_MUX,28};29 30struct 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 40struct 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 53struct 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 60static 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 93static 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 100static 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 153static 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 159static 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 216static 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 222static 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 245static 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 251static 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"},255 .num_parents = 2,256 .reg = 4,257 .bit_idx = 5,258 .bit_width = 1,259 .type = CLK_MUX,260 .flags = CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT,261 },262 [IMX95_CLK_NETCMIX_ENETC1_RMII] = {263 .name = "enetc1_rmii_sel",264 .parent_names = (const char *[]){"ext_enetref", "enetref"},265 .num_parents = 2,266 .reg = 4,267 .bit_idx = 10,268 .bit_width = 1,269 .type = CLK_MUX,270 .flags = CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT,271 },272};273 274static 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,277 .clk_reg_offset = 0,278};279 280static int imx95_bc_probe(struct platform_device *pdev)281{282 struct device *dev = &pdev->dev;283 const struct imx95_blk_ctl_dev_data *bc_data;284 struct imx95_blk_ctl *bc;285 struct clk_hw_onecell_data *clk_hw_data;286 struct clk_hw **hws;287 void __iomem *base;288 int i, ret;289 290 bc = devm_kzalloc(dev, sizeof(*bc), GFP_KERNEL);291 if (!bc)292 return -ENOMEM;293 bc->dev = dev;294 dev_set_drvdata(&pdev->dev, bc);295 296 spin_lock_init(&bc->lock);297 298 base = devm_platform_ioremap_resource(pdev, 0);299 if (IS_ERR(base))300 return PTR_ERR(base);301 302 bc->base = base;303 bc->clk_apb = devm_clk_get(dev, NULL);304 if (IS_ERR(bc->clk_apb))305 return dev_err_probe(dev, PTR_ERR(bc->clk_apb), "failed to get APB clock\n");306 307 ret = clk_prepare_enable(bc->clk_apb);308 if (ret) {309 dev_err(dev, "failed to enable apb clock: %d\n", ret);310 return ret;311 }312 313 bc_data = of_device_get_match_data(dev);314 if (!bc_data)315 return devm_of_platform_populate(dev);316 317 clk_hw_data = devm_kzalloc(dev, struct_size(clk_hw_data, hws, bc_data->num_clks),318 GFP_KERNEL);319 if (!clk_hw_data)320 return -ENOMEM;321 322 if (bc_data->rpm_enabled)323 pm_runtime_enable(&pdev->dev);324 325 clk_hw_data->num = bc_data->num_clks;326 hws = clk_hw_data->hws;327 328 for (i = 0; i < bc_data->num_clks; i++) {329 const struct imx95_blk_ctl_clk_dev_data *data = &bc_data->clk_dev_data[i];330 void __iomem *reg = base + data->reg;331 332 if (data->type == CLK_MUX) {333 hws[i] = clk_hw_register_mux(dev, data->name, data->parent_names,334 data->num_parents, data->flags, reg,335 data->bit_idx, data->bit_width,336 data->flags2, &bc->lock);337 } else if (data->type == CLK_DIVIDER) {338 hws[i] = clk_hw_register_divider(dev, data->name, data->parent_names[0],339 data->flags, reg, data->bit_idx,340 data->bit_width, data->flags2, &bc->lock);341 } else {342 hws[i] = clk_hw_register_gate(dev, data->name, data->parent_names[0],343 data->flags, reg, data->bit_idx,344 data->flags2, &bc->lock);345 }346 if (IS_ERR(hws[i])) {347 ret = PTR_ERR(hws[i]);348 dev_err(dev, "failed to register: %s:%d\n", data->name, ret);349 goto cleanup;350 }351 }352 353 ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, clk_hw_data);354 if (ret)355 goto cleanup;356 357 ret = devm_of_platform_populate(dev);358 if (ret) {359 of_clk_del_provider(dev->of_node);360 goto cleanup;361 }362 363 if (pm_runtime_enabled(bc->dev))364 clk_disable_unprepare(bc->clk_apb);365 366 return 0;367 368cleanup:369 for (i = 0; i < bc_data->num_clks; i++) {370 if (IS_ERR_OR_NULL(hws[i]))371 continue;372 clk_hw_unregister(hws[i]);373 }374 375 if (bc_data->rpm_enabled)376 pm_runtime_disable(&pdev->dev);377 378 return ret;379}380 381#ifdef CONFIG_PM382static int imx95_bc_runtime_suspend(struct device *dev)383{384 struct imx95_blk_ctl *bc = dev_get_drvdata(dev);385 386 clk_disable_unprepare(bc->clk_apb);387 return 0;388}389 390static int imx95_bc_runtime_resume(struct device *dev)391{392 struct imx95_blk_ctl *bc = dev_get_drvdata(dev);393 394 return clk_prepare_enable(bc->clk_apb);395}396#endif397 398#ifdef CONFIG_PM_SLEEP399static int imx95_bc_suspend(struct device *dev)400{401 struct imx95_blk_ctl *bc = dev_get_drvdata(dev);402 const struct imx95_blk_ctl_dev_data *bc_data;403 int ret;404 405 bc_data = of_device_get_match_data(dev);406 if (!bc_data)407 return 0;408 409 if (bc_data->rpm_enabled) {410 ret = pm_runtime_get_sync(bc->dev);411 if (ret < 0) {412 pm_runtime_put_noidle(bc->dev);413 return ret;414 }415 }416 417 bc->clk_reg_restore = readl(bc->base + bc_data->clk_reg_offset);418 419 return 0;420}421 422static int imx95_bc_resume(struct device *dev)423{424 struct imx95_blk_ctl *bc = dev_get_drvdata(dev);425 const struct imx95_blk_ctl_dev_data *bc_data;426 427 bc_data = of_device_get_match_data(dev);428 if (!bc_data)429 return 0;430 431 writel(bc->clk_reg_restore, bc->base + bc_data->clk_reg_offset);432 433 if (bc_data->rpm_enabled)434 pm_runtime_put(bc->dev);435 436 return 0;437}438#endif439 440static const struct dev_pm_ops imx95_bc_pm_ops = {441 SET_RUNTIME_PM_OPS(imx95_bc_runtime_suspend, imx95_bc_runtime_resume, NULL)442 SET_SYSTEM_SLEEP_PM_OPS(imx95_bc_suspend, imx95_bc_resume)443};444 445static const struct of_device_id imx95_bc_of_match[] = {446 { .compatible = "nxp,imx95-camera-csr", .data = &camblk_dev_data },447 { .compatible = "nxp,imx95-display-master-csr", },448 { .compatible = "nxp,imx95-lvds-csr", .data = &lvds_csr_dev_data },449 { .compatible = "nxp,imx95-display-csr", .data = &dispmix_csr_dev_data },450 { .compatible = "nxp,imx95-vpu-csr", .data = &vpublk_dev_data },451 { .compatible = "nxp,imx95-netcmix-blk-ctrl", .data = &netcmix_dev_data},452 { /* Sentinel */ },453};454MODULE_DEVICE_TABLE(of, imx95_bc_of_match);455 456static struct platform_driver imx95_bc_driver = {457 .probe = imx95_bc_probe,458 .driver = {459 .name = "imx95-blk-ctl",460 .of_match_table = imx95_bc_of_match,461 .pm = &imx95_bc_pm_ops,462 },463};464module_platform_driver(imx95_bc_driver);465 466MODULE_DESCRIPTION("NXP i.MX95 blk ctl driver");467MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>");468MODULE_LICENSE("GPL");469