// SPDX-License-Identifier: GPL-2.0-only /* * Copyright 2025 Duje Mihanović */ #include #include #include #include #include #include #include #include #include #include /* VPU, GPU, ISP */ #define APMU_PWR_CTRL_REG 0xd8 #define APMU_PWR_BLK_TMR_REG 0xdc #define APMU_PWR_STATUS_REG 0xf0 /* DSI */ #define APMU_DEBUG 0x88 #define DSI_PHY_DVM_MASK BIT(31) #define POWER_ON_LATENCY_US 300 #define POWER_OFF_LATENCY_US 20 #define POWER_POLL_TIMEOUT_US (25 * USEC_PER_MSEC) #define POWER_POLL_SLEEP_US 6 #define NR_DOMAINS 5 #define to_pxa1908_pd(_genpd) container_of(_genpd, struct pxa1908_pd, genpd) struct pxa1908_pd_ctrl { struct generic_pm_domain *domains[NR_DOMAINS]; struct genpd_onecell_data onecell_data; struct regmap *base; struct device *dev; }; struct pxa1908_pd_data { u32 reg_clk_res_ctrl; u32 pwr_state; u32 hw_mode; bool keep_on; int id; }; struct pxa1908_pd { const struct pxa1908_pd_data data; struct pxa1908_pd_ctrl *ctrl; struct generic_pm_domain genpd; bool initialized; }; static inline bool pxa1908_pd_is_on(struct pxa1908_pd *pd) { struct pxa1908_pd_ctrl *ctrl = pd->ctrl; return pd->data.id != PXA1908_POWER_DOMAIN_DSI ? regmap_test_bits(ctrl->base, APMU_PWR_STATUS_REG, pd->data.pwr_state) : regmap_test_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK); } static int pxa1908_pd_power_on(struct generic_pm_domain *genpd) { struct pxa1908_pd *pd = to_pxa1908_pd(genpd); const struct pxa1908_pd_data *data = &pd->data; struct pxa1908_pd_ctrl *ctrl = pd->ctrl; unsigned int status; int ret = 0; regmap_set_bits(ctrl->base, data->reg_clk_res_ctrl, data->hw_mode); if (data->id != PXA1908_POWER_DOMAIN_ISP) regmap_write(ctrl->base, APMU_PWR_BLK_TMR_REG, 0x20001fff); regmap_set_bits(ctrl->base, APMU_PWR_CTRL_REG, data->pwr_state); ret = regmap_read_poll_timeout(ctrl->base, APMU_PWR_STATUS_REG, status, status & data->pwr_state, POWER_POLL_SLEEP_US, POWER_ON_LATENCY_US + POWER_POLL_TIMEOUT_US); if (ret == -ETIMEDOUT) dev_err(ctrl->dev, "timed out powering on domain '%s'\n", pd->genpd.name); return ret; } static int pxa1908_pd_power_off(struct generic_pm_domain *genpd) { struct pxa1908_pd *pd = to_pxa1908_pd(genpd); const struct pxa1908_pd_data *data = &pd->data; struct pxa1908_pd_ctrl *ctrl = pd->ctrl; unsigned int status; int ret; regmap_clear_bits(ctrl->base, APMU_PWR_CTRL_REG, data->pwr_state); ret = regmap_read_poll_timeout(ctrl->base, APMU_PWR_STATUS_REG, status, !(status & data->pwr_state), POWER_POLL_SLEEP_US, POWER_OFF_LATENCY_US + POWER_POLL_TIMEOUT_US); if (ret == -ETIMEDOUT) { dev_err(ctrl->dev, "timed out powering off domain '%s'\n", pd->genpd.name); return ret; } return regmap_clear_bits(ctrl->base, data->reg_clk_res_ctrl, data->hw_mode); } static inline int pxa1908_dsi_power_on(struct generic_pm_domain *genpd) { struct pxa1908_pd *pd = to_pxa1908_pd(genpd); struct pxa1908_pd_ctrl *ctrl = pd->ctrl; return regmap_set_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK); } static inline int pxa1908_dsi_power_off(struct generic_pm_domain *genpd) { struct pxa1908_pd *pd = to_pxa1908_pd(genpd); struct pxa1908_pd_ctrl *ctrl = pd->ctrl; return regmap_clear_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK); } #define DOMAIN(_id, _name, ctrl, mode, state) \ [_id] = { \ .data = { \ .reg_clk_res_ctrl = ctrl, \ .hw_mode = BIT(mode), \ .pwr_state = BIT(state), \ .id = _id, \ }, \ .genpd = { \ .name = _name, \ .power_on = pxa1908_pd_power_on, \ .power_off = pxa1908_pd_power_off, \ }, \ } static struct pxa1908_pd domains[NR_DOMAINS] = { DOMAIN(PXA1908_POWER_DOMAIN_VPU, "vpu", 0xa4, 19, 2), DOMAIN(PXA1908_POWER_DOMAIN_GPU, "gpu", 0xcc, 11, 0), DOMAIN(PXA1908_POWER_DOMAIN_GPU2D, "gpu2d", 0xf4, 11, 6), DOMAIN(PXA1908_POWER_DOMAIN_ISP, "isp", 0x38, 15, 4), [PXA1908_POWER_DOMAIN_DSI] = { .genpd = { .name = "dsi", .power_on = pxa1908_dsi_power_on, .power_off = pxa1908_dsi_power_off, /* * TODO: There is no DSI driver written yet and until then we probably * don't want to power off the DSI PHY ever. */ .flags = GENPD_FLAG_ALWAYS_ON, }, .data = { /* See above. */ .keep_on = true, }, }, }; static void pxa1908_pd_remove(struct auxiliary_device *auxdev) { struct pxa1908_pd *pd; int ret; for (int i = NR_DOMAINS - 1; i >= 0; i--) { pd = &domains[i]; if (!pd->initialized) continue; if (pxa1908_pd_is_on(pd) && !pd->data.keep_on) pxa1908_pd_power_off(&pd->genpd); ret = pm_genpd_remove(&pd->genpd); if (ret) dev_err(&auxdev->dev, "failed to remove domain '%s': %d\n", pd->genpd.name, ret); } } static int pxa1908_pd_init(struct pxa1908_pd_ctrl *ctrl, int id, struct device *dev) { struct pxa1908_pd *pd = &domains[id]; int ret; ctrl->domains[id] = &pd->genpd; pd->ctrl = ctrl; /* Make sure the state of the hardware is synced with the domain table above. */ if (pd->data.keep_on) { ret = pd->genpd.power_on(&pd->genpd); if (ret) return dev_err_probe(dev, ret, "failed to power on domain '%s'\n", pd->genpd.name); } else { if (pxa1908_pd_is_on(pd)) { dev_warn(dev, "domain '%s' is on despite being default off; powering off\n", pd->genpd.name); ret = pd->genpd.power_off(&pd->genpd); if (ret) return dev_err_probe(dev, ret, "failed to power off domain '%s'\n", pd->genpd.name); } } ret = pm_genpd_init(&pd->genpd, NULL, !pd->data.keep_on); if (ret) return dev_err_probe(dev, ret, "domain '%s' failed to initialize\n", pd->genpd.name); pd->initialized = true; return 0; } static int pxa1908_pd_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *aux_id) { struct pxa1908_pd_ctrl *ctrl; struct device *dev = &auxdev->dev; int ret; ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); if (!ctrl) return -ENOMEM; auxiliary_set_drvdata(auxdev, ctrl); ctrl->base = syscon_node_to_regmap(dev->parent->of_node); if (IS_ERR(ctrl->base)) return dev_err_probe(dev, PTR_ERR(ctrl->base), "no regmap available\n"); ctrl->dev = dev; ctrl->onecell_data.domains = ctrl->domains; ctrl->onecell_data.num_domains = NR_DOMAINS; for (int i = 0; i < NR_DOMAINS; i++) { ret = pxa1908_pd_init(ctrl, i, dev); if (ret) goto err; } return of_genpd_add_provider_onecell(dev->parent->of_node, &ctrl->onecell_data); err: pxa1908_pd_remove(auxdev); return ret; } static const struct auxiliary_device_id pxa1908_pd_id[] = { { .name = "clk_pxa1908_apmu.power" }, { } }; MODULE_DEVICE_TABLE(auxiliary, pxa1908_pd_id); static struct auxiliary_driver pxa1908_pd_driver = { .probe = pxa1908_pd_probe, .remove = pxa1908_pd_remove, .id_table = pxa1908_pd_id, }; module_auxiliary_driver(pxa1908_pd_driver); MODULE_AUTHOR("Duje Mihanović "); MODULE_DESCRIPTION("Marvell PXA1908 power domain driver"); MODULE_LICENSE("GPL");