452 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright 2022 NXP, Peng Fan <peng.fan@nxp.com>4 */5 6#include <linux/clk.h>7#include <linux/device.h>8#include <linux/module.h>9#include <linux/of.h>10#include <linux/platform_device.h>11#include <linux/pm_domain.h>12#include <linux/pm_runtime.h>13#include <linux/regmap.h>14#include <linux/sizes.h>15 16#include <dt-bindings/power/fsl,imx93-power.h>17 18#define BLK_SFT_RSTN 0x019#define BLK_CLK_EN 0x420#define BLK_MAX_CLKS 421 22#define DOMAIN_MAX_CLKS 423 24#define LCDIF_QOS_REG 0xC25#define LCDIF_DEFAULT_QOS_OFF 1226#define LCDIF_CFG_QOS_OFF 827 28#define PXP_QOS_REG 0x1029#define PXP_R_DEFAULT_QOS_OFF 2830#define PXP_R_CFG_QOS_OFF 2431#define PXP_W_DEFAULT_QOS_OFF 2032#define PXP_W_CFG_QOS_OFF 1633 34#define ISI_CACHE_REG 0x1435 36#define ISI_QOS_REG 0x1C37#define ISI_V_DEFAULT_QOS_OFF 2838#define ISI_V_CFG_QOS_OFF 2439#define ISI_U_DEFAULT_QOS_OFF 2040#define ISI_U_CFG_QOS_OFF 1641#define ISI_Y_R_DEFAULT_QOS_OFF 1242#define ISI_Y_R_CFG_QOS_OFF 843#define ISI_Y_W_DEFAULT_QOS_OFF 444#define ISI_Y_W_CFG_QOS_OFF 045 46#define PRIO_MASK 0xF47 48#define PRIO(X) (X)49 50struct imx93_blk_ctrl_domain;51 52struct imx93_blk_ctrl {53 struct device *dev;54 struct regmap *regmap;55 int num_clks;56 struct clk_bulk_data clks[BLK_MAX_CLKS];57 struct imx93_blk_ctrl_domain *domains;58 struct genpd_onecell_data onecell_data;59};60 61#define DOMAIN_MAX_QOS 462 63struct imx93_blk_ctrl_qos {64 u32 reg;65 u32 cfg_off;66 u32 default_prio;67 u32 cfg_prio;68};69 70struct imx93_blk_ctrl_domain_data {71 const char *name;72 const char * const *clk_names;73 int num_clks;74 u32 rst_mask;75 u32 clk_mask;76 int num_qos;77 struct imx93_blk_ctrl_qos qos[DOMAIN_MAX_QOS];78};79 80struct imx93_blk_ctrl_domain {81 struct generic_pm_domain genpd;82 const struct imx93_blk_ctrl_domain_data *data;83 struct clk_bulk_data clks[DOMAIN_MAX_CLKS];84 struct imx93_blk_ctrl *bc;85};86 87struct imx93_blk_ctrl_data {88 const struct imx93_blk_ctrl_domain_data *domains;89 int num_domains;90 const char * const *clk_names;91 int num_clks;92 const struct regmap_access_table *reg_access_table;93};94 95static inline struct imx93_blk_ctrl_domain *96to_imx93_blk_ctrl_domain(struct generic_pm_domain *genpd)97{98 return container_of(genpd, struct imx93_blk_ctrl_domain, genpd);99}100 101static int imx93_blk_ctrl_set_qos(struct imx93_blk_ctrl_domain *domain)102{103 const struct imx93_blk_ctrl_domain_data *data = domain->data;104 struct imx93_blk_ctrl *bc = domain->bc;105 const struct imx93_blk_ctrl_qos *qos;106 u32 val, mask;107 int i;108 109 for (i = 0; i < data->num_qos; i++) {110 qos = &data->qos[i];111 112 mask = PRIO_MASK << qos->cfg_off;113 mask |= PRIO_MASK << (qos->cfg_off + 4);114 val = qos->cfg_prio << qos->cfg_off;115 val |= qos->default_prio << (qos->cfg_off + 4);116 117 regmap_write_bits(bc->regmap, qos->reg, mask, val);118 119 dev_dbg(bc->dev, "data->qos[i].reg 0x%x 0x%x\n", qos->reg, val);120 }121 122 return 0;123}124 125static int imx93_blk_ctrl_power_on(struct generic_pm_domain *genpd)126{127 struct imx93_blk_ctrl_domain *domain = to_imx93_blk_ctrl_domain(genpd);128 const struct imx93_blk_ctrl_domain_data *data = domain->data;129 struct imx93_blk_ctrl *bc = domain->bc;130 int ret;131 132 ret = clk_bulk_prepare_enable(bc->num_clks, bc->clks);133 if (ret) {134 dev_err(bc->dev, "failed to enable bus clocks\n");135 return ret;136 }137 138 ret = clk_bulk_prepare_enable(data->num_clks, domain->clks);139 if (ret) {140 clk_bulk_disable_unprepare(bc->num_clks, bc->clks);141 dev_err(bc->dev, "failed to enable clocks\n");142 return ret;143 }144 145 ret = pm_runtime_get_sync(bc->dev);146 if (ret < 0) {147 pm_runtime_put_noidle(bc->dev);148 dev_err(bc->dev, "failed to power up domain\n");149 goto disable_clk;150 }151 152 /* ungate clk */153 regmap_clear_bits(bc->regmap, BLK_CLK_EN, data->clk_mask);154 155 /* release reset */156 regmap_set_bits(bc->regmap, BLK_SFT_RSTN, data->rst_mask);157 158 dev_dbg(bc->dev, "pd_on: name: %s\n", genpd->name);159 160 return imx93_blk_ctrl_set_qos(domain);161 162disable_clk:163 clk_bulk_disable_unprepare(data->num_clks, domain->clks);164 165 clk_bulk_disable_unprepare(bc->num_clks, bc->clks);166 167 return ret;168}169 170static int imx93_blk_ctrl_power_off(struct generic_pm_domain *genpd)171{172 struct imx93_blk_ctrl_domain *domain = to_imx93_blk_ctrl_domain(genpd);173 const struct imx93_blk_ctrl_domain_data *data = domain->data;174 struct imx93_blk_ctrl *bc = domain->bc;175 176 dev_dbg(bc->dev, "pd_off: name: %s\n", genpd->name);177 178 regmap_clear_bits(bc->regmap, BLK_SFT_RSTN, data->rst_mask);179 regmap_set_bits(bc->regmap, BLK_CLK_EN, data->clk_mask);180 181 pm_runtime_put(bc->dev);182 183 clk_bulk_disable_unprepare(data->num_clks, domain->clks);184 185 clk_bulk_disable_unprepare(bc->num_clks, bc->clks);186 187 return 0;188}189 190static struct lock_class_key blk_ctrl_genpd_lock_class;191 192static int imx93_blk_ctrl_probe(struct platform_device *pdev)193{194 struct device *dev = &pdev->dev;195 const struct imx93_blk_ctrl_data *bc_data = of_device_get_match_data(dev);196 struct imx93_blk_ctrl *bc;197 void __iomem *base;198 int i, ret;199 200 struct regmap_config regmap_config = {201 .reg_bits = 32,202 .val_bits = 32,203 .reg_stride = 4,204 .rd_table = bc_data->reg_access_table,205 .wr_table = bc_data->reg_access_table,206 .max_register = SZ_4K,207 };208 209 bc = devm_kzalloc(dev, sizeof(*bc), GFP_KERNEL);210 if (!bc)211 return -ENOMEM;212 213 bc->dev = dev;214 215 base = devm_platform_ioremap_resource(pdev, 0);216 if (IS_ERR(base))217 return PTR_ERR(base);218 219 bc->regmap = devm_regmap_init_mmio(dev, base, ®map_config);220 if (IS_ERR(bc->regmap))221 return dev_err_probe(dev, PTR_ERR(bc->regmap),222 "failed to init regmap\n");223 224 bc->domains = devm_kcalloc(dev, bc_data->num_domains,225 sizeof(struct imx93_blk_ctrl_domain),226 GFP_KERNEL);227 if (!bc->domains)228 return -ENOMEM;229 230 bc->onecell_data.num_domains = bc_data->num_domains;231 bc->onecell_data.domains =232 devm_kcalloc(dev, bc_data->num_domains,233 sizeof(struct generic_pm_domain *), GFP_KERNEL);234 if (!bc->onecell_data.domains)235 return -ENOMEM;236 237 for (i = 0; i < bc_data->num_clks; i++)238 bc->clks[i].id = bc_data->clk_names[i];239 bc->num_clks = bc_data->num_clks;240 241 ret = devm_clk_bulk_get(dev, bc->num_clks, bc->clks);242 if (ret) {243 dev_err_probe(dev, ret, "failed to get bus clock\n");244 return ret;245 }246 247 for (i = 0; i < bc_data->num_domains; i++) {248 const struct imx93_blk_ctrl_domain_data *data = &bc_data->domains[i];249 struct imx93_blk_ctrl_domain *domain = &bc->domains[i];250 int j;251 252 domain->data = data;253 254 for (j = 0; j < data->num_clks; j++)255 domain->clks[j].id = data->clk_names[j];256 257 ret = devm_clk_bulk_get(dev, data->num_clks, domain->clks);258 if (ret) {259 dev_err_probe(dev, ret, "failed to get clock\n");260 goto cleanup_pds;261 }262 263 domain->genpd.name = data->name;264 domain->genpd.power_on = imx93_blk_ctrl_power_on;265 domain->genpd.power_off = imx93_blk_ctrl_power_off;266 domain->bc = bc;267 268 ret = pm_genpd_init(&domain->genpd, NULL, true);269 if (ret) {270 dev_err_probe(dev, ret, "failed to init power domain\n");271 goto cleanup_pds;272 }273 274 /*275 * We use runtime PM to trigger power on/off of the upstream GPC276 * domain, as a strict hierarchical parent/child power domain277 * setup doesn't allow us to meet the sequencing requirements.278 * This means we have nested locking of genpd locks, without the279 * nesting being visible at the genpd level, so we need a280 * separate lock class to make lockdep aware of the fact that281 * this are separate domain locks that can be nested without a282 * self-deadlock.283 */284 lockdep_set_class(&domain->genpd.mlock,285 &blk_ctrl_genpd_lock_class);286 287 bc->onecell_data.domains[i] = &domain->genpd;288 }289 290 pm_runtime_enable(dev);291 292 ret = of_genpd_add_provider_onecell(dev->of_node, &bc->onecell_data);293 if (ret) {294 dev_err_probe(dev, ret, "failed to add power domain provider\n");295 goto cleanup_pds;296 }297 298 dev_set_drvdata(dev, bc);299 300 return 0;301 302cleanup_pds:303 for (i--; i >= 0; i--)304 pm_genpd_remove(&bc->domains[i].genpd);305 306 return ret;307}308 309static void imx93_blk_ctrl_remove(struct platform_device *pdev)310{311 struct imx93_blk_ctrl *bc = dev_get_drvdata(&pdev->dev);312 int i;313 314 of_genpd_del_provider(pdev->dev.of_node);315 316 pm_runtime_disable(&pdev->dev);317 318 for (i = 0; i < bc->onecell_data.num_domains; i++) {319 struct imx93_blk_ctrl_domain *domain = &bc->domains[i];320 321 pm_genpd_remove(&domain->genpd);322 }323}324 325static const struct imx93_blk_ctrl_domain_data imx93_media_blk_ctl_domain_data[] = {326 [IMX93_MEDIABLK_PD_MIPI_DSI] = {327 .name = "mediablk-mipi-dsi",328 .clk_names = (const char *[]){ "dsi" },329 .num_clks = 1,330 .rst_mask = BIT(11) | BIT(12),331 .clk_mask = BIT(11) | BIT(12),332 },333 [IMX93_MEDIABLK_PD_MIPI_CSI] = {334 .name = "mediablk-mipi-csi",335 .clk_names = (const char *[]){ "cam", "csi" },336 .num_clks = 2,337 .rst_mask = BIT(9) | BIT(10),338 .clk_mask = BIT(9) | BIT(10),339 },340 [IMX93_MEDIABLK_PD_PXP] = {341 .name = "mediablk-pxp",342 .clk_names = (const char *[]){ "pxp" },343 .num_clks = 1,344 .rst_mask = BIT(7) | BIT(8),345 .clk_mask = BIT(7) | BIT(8),346 .num_qos = 2,347 .qos = {348 {349 .reg = PXP_QOS_REG,350 .cfg_off = PXP_R_CFG_QOS_OFF,351 .default_prio = PRIO(3),352 .cfg_prio = PRIO(6),353 }, {354 .reg = PXP_QOS_REG,355 .cfg_off = PXP_W_CFG_QOS_OFF,356 .default_prio = PRIO(3),357 .cfg_prio = PRIO(6),358 }359 }360 },361 [IMX93_MEDIABLK_PD_LCDIF] = {362 .name = "mediablk-lcdif",363 .clk_names = (const char *[]){ "disp", "lcdif" },364 .num_clks = 2,365 .rst_mask = BIT(4) | BIT(5) | BIT(6),366 .clk_mask = BIT(4) | BIT(5) | BIT(6),367 .num_qos = 1,368 .qos = {369 {370 .reg = LCDIF_QOS_REG,371 .cfg_off = LCDIF_CFG_QOS_OFF,372 .default_prio = PRIO(3),373 .cfg_prio = PRIO(7),374 }375 }376 },377 [IMX93_MEDIABLK_PD_ISI] = {378 .name = "mediablk-isi",379 .clk_names = (const char *[]){ "isi" },380 .num_clks = 1,381 .rst_mask = BIT(2) | BIT(3),382 .clk_mask = BIT(2) | BIT(3),383 .num_qos = 4,384 .qos = {385 {386 .reg = ISI_QOS_REG,387 .cfg_off = ISI_Y_W_CFG_QOS_OFF,388 .default_prio = PRIO(3),389 .cfg_prio = PRIO(7),390 }, {391 .reg = ISI_QOS_REG,392 .cfg_off = ISI_Y_R_CFG_QOS_OFF,393 .default_prio = PRIO(3),394 .cfg_prio = PRIO(7),395 }, {396 .reg = ISI_QOS_REG,397 .cfg_off = ISI_U_CFG_QOS_OFF,398 .default_prio = PRIO(3),399 .cfg_prio = PRIO(7),400 }, {401 .reg = ISI_QOS_REG,402 .cfg_off = ISI_V_CFG_QOS_OFF,403 .default_prio = PRIO(3),404 .cfg_prio = PRIO(7),405 }406 }407 },408};409 410static const struct regmap_range imx93_media_blk_ctl_yes_ranges[] = {411 regmap_reg_range(BLK_SFT_RSTN, BLK_CLK_EN),412 regmap_reg_range(LCDIF_QOS_REG, ISI_CACHE_REG),413 regmap_reg_range(ISI_QOS_REG, ISI_QOS_REG),414};415 416static const struct regmap_access_table imx93_media_blk_ctl_access_table = {417 .yes_ranges = imx93_media_blk_ctl_yes_ranges,418 .n_yes_ranges = ARRAY_SIZE(imx93_media_blk_ctl_yes_ranges),419};420 421static const struct imx93_blk_ctrl_data imx93_media_blk_ctl_dev_data = {422 .domains = imx93_media_blk_ctl_domain_data,423 .num_domains = ARRAY_SIZE(imx93_media_blk_ctl_domain_data),424 .clk_names = (const char *[]){ "axi", "apb", "nic", },425 .num_clks = 3,426 .reg_access_table = &imx93_media_blk_ctl_access_table,427};428 429static const struct of_device_id imx93_blk_ctrl_of_match[] = {430 {431 .compatible = "fsl,imx93-media-blk-ctrl",432 .data = &imx93_media_blk_ctl_dev_data433 }, {434 /* Sentinel */435 }436};437MODULE_DEVICE_TABLE(of, imx93_blk_ctrl_of_match);438 439static struct platform_driver imx93_blk_ctrl_driver = {440 .probe = imx93_blk_ctrl_probe,441 .remove_new = imx93_blk_ctrl_remove,442 .driver = {443 .name = "imx93-blk-ctrl",444 .of_match_table = imx93_blk_ctrl_of_match,445 },446};447module_platform_driver(imx93_blk_ctrl_driver);448 449MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>");450MODULE_DESCRIPTION("i.MX93 BLK CTRL driver");451MODULE_LICENSE("GPL");452