1*6f51a045SDuje Mihanović // SPDX-License-Identifier: GPL-2.0-only 2*6f51a045SDuje Mihanović /* 3*6f51a045SDuje Mihanović * Copyright 2025 Duje Mihanović <duje@dujemihanovic.xyz> 4*6f51a045SDuje Mihanović */ 5*6f51a045SDuje Mihanović 6*6f51a045SDuje Mihanović #include <linux/auxiliary_bus.h> 7*6f51a045SDuje Mihanović #include <linux/container_of.h> 8*6f51a045SDuje Mihanović #include <linux/dev_printk.h> 9*6f51a045SDuje Mihanović #include <linux/device.h> 10*6f51a045SDuje Mihanović #include <linux/mfd/syscon.h> 11*6f51a045SDuje Mihanović #include <linux/mod_devicetable.h> 12*6f51a045SDuje Mihanović #include <linux/module.h> 13*6f51a045SDuje Mihanović #include <linux/pm_domain.h> 14*6f51a045SDuje Mihanović #include <linux/regmap.h> 15*6f51a045SDuje Mihanović 16*6f51a045SDuje Mihanović #include <dt-bindings/power/marvell,pxa1908-power.h> 17*6f51a045SDuje Mihanović 18*6f51a045SDuje Mihanović /* VPU, GPU, ISP */ 19*6f51a045SDuje Mihanović #define APMU_PWR_CTRL_REG 0xd8 20*6f51a045SDuje Mihanović #define APMU_PWR_BLK_TMR_REG 0xdc 21*6f51a045SDuje Mihanović #define APMU_PWR_STATUS_REG 0xf0 22*6f51a045SDuje Mihanović 23*6f51a045SDuje Mihanović /* DSI */ 24*6f51a045SDuje Mihanović #define APMU_DEBUG 0x88 25*6f51a045SDuje Mihanović #define DSI_PHY_DVM_MASK BIT(31) 26*6f51a045SDuje Mihanović 27*6f51a045SDuje Mihanović #define POWER_ON_LATENCY_US 300 28*6f51a045SDuje Mihanović #define POWER_OFF_LATENCY_US 20 29*6f51a045SDuje Mihanović #define POWER_POLL_TIMEOUT_US (25 * USEC_PER_MSEC) 30*6f51a045SDuje Mihanović #define POWER_POLL_SLEEP_US 6 31*6f51a045SDuje Mihanović 32*6f51a045SDuje Mihanović #define NR_DOMAINS 5 33*6f51a045SDuje Mihanović 34*6f51a045SDuje Mihanović #define to_pxa1908_pd(_genpd) container_of(_genpd, struct pxa1908_pd, genpd) 35*6f51a045SDuje Mihanović 36*6f51a045SDuje Mihanović struct pxa1908_pd_ctrl { 37*6f51a045SDuje Mihanović struct generic_pm_domain *domains[NR_DOMAINS]; 38*6f51a045SDuje Mihanović struct genpd_onecell_data onecell_data; 39*6f51a045SDuje Mihanović struct regmap *base; 40*6f51a045SDuje Mihanović struct device *dev; 41*6f51a045SDuje Mihanović }; 42*6f51a045SDuje Mihanović 43*6f51a045SDuje Mihanović struct pxa1908_pd_data { 44*6f51a045SDuje Mihanović u32 reg_clk_res_ctrl; 45*6f51a045SDuje Mihanović u32 pwr_state; 46*6f51a045SDuje Mihanović u32 hw_mode; 47*6f51a045SDuje Mihanović bool keep_on; 48*6f51a045SDuje Mihanović int id; 49*6f51a045SDuje Mihanović }; 50*6f51a045SDuje Mihanović 51*6f51a045SDuje Mihanović struct pxa1908_pd { 52*6f51a045SDuje Mihanović const struct pxa1908_pd_data data; 53*6f51a045SDuje Mihanović struct pxa1908_pd_ctrl *ctrl; 54*6f51a045SDuje Mihanović struct generic_pm_domain genpd; 55*6f51a045SDuje Mihanović bool initialized; 56*6f51a045SDuje Mihanović }; 57*6f51a045SDuje Mihanović 58*6f51a045SDuje Mihanović static inline bool pxa1908_pd_is_on(struct pxa1908_pd *pd) 59*6f51a045SDuje Mihanović { 60*6f51a045SDuje Mihanović struct pxa1908_pd_ctrl *ctrl = pd->ctrl; 61*6f51a045SDuje Mihanović 62*6f51a045SDuje Mihanović return pd->data.id != PXA1908_POWER_DOMAIN_DSI 63*6f51a045SDuje Mihanović ? regmap_test_bits(ctrl->base, APMU_PWR_STATUS_REG, pd->data.pwr_state) 64*6f51a045SDuje Mihanović : regmap_test_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK); 65*6f51a045SDuje Mihanović } 66*6f51a045SDuje Mihanović 67*6f51a045SDuje Mihanović static int pxa1908_pd_power_on(struct generic_pm_domain *genpd) 68*6f51a045SDuje Mihanović { 69*6f51a045SDuje Mihanović struct pxa1908_pd *pd = to_pxa1908_pd(genpd); 70*6f51a045SDuje Mihanović const struct pxa1908_pd_data *data = &pd->data; 71*6f51a045SDuje Mihanović struct pxa1908_pd_ctrl *ctrl = pd->ctrl; 72*6f51a045SDuje Mihanović unsigned int status; 73*6f51a045SDuje Mihanović int ret = 0; 74*6f51a045SDuje Mihanović 75*6f51a045SDuje Mihanović regmap_set_bits(ctrl->base, data->reg_clk_res_ctrl, data->hw_mode); 76*6f51a045SDuje Mihanović if (data->id != PXA1908_POWER_DOMAIN_ISP) 77*6f51a045SDuje Mihanović regmap_write(ctrl->base, APMU_PWR_BLK_TMR_REG, 0x20001fff); 78*6f51a045SDuje Mihanović regmap_set_bits(ctrl->base, APMU_PWR_CTRL_REG, data->pwr_state); 79*6f51a045SDuje Mihanović 80*6f51a045SDuje Mihanović ret = regmap_read_poll_timeout(ctrl->base, APMU_PWR_STATUS_REG, status, 81*6f51a045SDuje Mihanović status & data->pwr_state, POWER_POLL_SLEEP_US, 82*6f51a045SDuje Mihanović POWER_ON_LATENCY_US + POWER_POLL_TIMEOUT_US); 83*6f51a045SDuje Mihanović if (ret == -ETIMEDOUT) 84*6f51a045SDuje Mihanović dev_err(ctrl->dev, "timed out powering on domain '%s'\n", pd->genpd.name); 85*6f51a045SDuje Mihanović 86*6f51a045SDuje Mihanović return ret; 87*6f51a045SDuje Mihanović } 88*6f51a045SDuje Mihanović 89*6f51a045SDuje Mihanović static int pxa1908_pd_power_off(struct generic_pm_domain *genpd) 90*6f51a045SDuje Mihanović { 91*6f51a045SDuje Mihanović struct pxa1908_pd *pd = to_pxa1908_pd(genpd); 92*6f51a045SDuje Mihanović const struct pxa1908_pd_data *data = &pd->data; 93*6f51a045SDuje Mihanović struct pxa1908_pd_ctrl *ctrl = pd->ctrl; 94*6f51a045SDuje Mihanović unsigned int status; 95*6f51a045SDuje Mihanović int ret; 96*6f51a045SDuje Mihanović 97*6f51a045SDuje Mihanović regmap_clear_bits(ctrl->base, APMU_PWR_CTRL_REG, data->pwr_state); 98*6f51a045SDuje Mihanović 99*6f51a045SDuje Mihanović ret = regmap_read_poll_timeout(ctrl->base, APMU_PWR_STATUS_REG, status, 100*6f51a045SDuje Mihanović !(status & data->pwr_state), POWER_POLL_SLEEP_US, 101*6f51a045SDuje Mihanović POWER_OFF_LATENCY_US + POWER_POLL_TIMEOUT_US); 102*6f51a045SDuje Mihanović if (ret == -ETIMEDOUT) { 103*6f51a045SDuje Mihanović dev_err(ctrl->dev, "timed out powering off domain '%s'\n", pd->genpd.name); 104*6f51a045SDuje Mihanović return ret; 105*6f51a045SDuje Mihanović } 106*6f51a045SDuje Mihanović 107*6f51a045SDuje Mihanović return regmap_clear_bits(ctrl->base, data->reg_clk_res_ctrl, data->hw_mode); 108*6f51a045SDuje Mihanović } 109*6f51a045SDuje Mihanović 110*6f51a045SDuje Mihanović static inline int pxa1908_dsi_power_on(struct generic_pm_domain *genpd) 111*6f51a045SDuje Mihanović { 112*6f51a045SDuje Mihanović struct pxa1908_pd *pd = to_pxa1908_pd(genpd); 113*6f51a045SDuje Mihanović struct pxa1908_pd_ctrl *ctrl = pd->ctrl; 114*6f51a045SDuje Mihanović 115*6f51a045SDuje Mihanović return regmap_set_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK); 116*6f51a045SDuje Mihanović } 117*6f51a045SDuje Mihanović 118*6f51a045SDuje Mihanović static inline int pxa1908_dsi_power_off(struct generic_pm_domain *genpd) 119*6f51a045SDuje Mihanović { 120*6f51a045SDuje Mihanović struct pxa1908_pd *pd = to_pxa1908_pd(genpd); 121*6f51a045SDuje Mihanović struct pxa1908_pd_ctrl *ctrl = pd->ctrl; 122*6f51a045SDuje Mihanović 123*6f51a045SDuje Mihanović return regmap_clear_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK); 124*6f51a045SDuje Mihanović } 125*6f51a045SDuje Mihanović 126*6f51a045SDuje Mihanović #define DOMAIN(_id, _name, ctrl, mode, state) \ 127*6f51a045SDuje Mihanović [_id] = { \ 128*6f51a045SDuje Mihanović .data = { \ 129*6f51a045SDuje Mihanović .reg_clk_res_ctrl = ctrl, \ 130*6f51a045SDuje Mihanović .hw_mode = BIT(mode), \ 131*6f51a045SDuje Mihanović .pwr_state = BIT(state), \ 132*6f51a045SDuje Mihanović .id = _id, \ 133*6f51a045SDuje Mihanović }, \ 134*6f51a045SDuje Mihanović .genpd = { \ 135*6f51a045SDuje Mihanović .name = _name, \ 136*6f51a045SDuje Mihanović .power_on = pxa1908_pd_power_on, \ 137*6f51a045SDuje Mihanović .power_off = pxa1908_pd_power_off, \ 138*6f51a045SDuje Mihanović }, \ 139*6f51a045SDuje Mihanović } 140*6f51a045SDuje Mihanović 141*6f51a045SDuje Mihanović static struct pxa1908_pd domains[NR_DOMAINS] = { 142*6f51a045SDuje Mihanović DOMAIN(PXA1908_POWER_DOMAIN_VPU, "vpu", 0xa4, 19, 2), 143*6f51a045SDuje Mihanović DOMAIN(PXA1908_POWER_DOMAIN_GPU, "gpu", 0xcc, 11, 0), 144*6f51a045SDuje Mihanović DOMAIN(PXA1908_POWER_DOMAIN_GPU2D, "gpu2d", 0xf4, 11, 6), 145*6f51a045SDuje Mihanović DOMAIN(PXA1908_POWER_DOMAIN_ISP, "isp", 0x38, 15, 4), 146*6f51a045SDuje Mihanović [PXA1908_POWER_DOMAIN_DSI] = { 147*6f51a045SDuje Mihanović .genpd = { 148*6f51a045SDuje Mihanović .name = "dsi", 149*6f51a045SDuje Mihanović .power_on = pxa1908_dsi_power_on, 150*6f51a045SDuje Mihanović .power_off = pxa1908_dsi_power_off, 151*6f51a045SDuje Mihanović /* 152*6f51a045SDuje Mihanović * TODO: There is no DSI driver written yet and until then we probably 153*6f51a045SDuje Mihanović * don't want to power off the DSI PHY ever. 154*6f51a045SDuje Mihanović */ 155*6f51a045SDuje Mihanović .flags = GENPD_FLAG_ALWAYS_ON, 156*6f51a045SDuje Mihanović }, 157*6f51a045SDuje Mihanović .data = { 158*6f51a045SDuje Mihanović /* See above. */ 159*6f51a045SDuje Mihanović .keep_on = true, 160*6f51a045SDuje Mihanović }, 161*6f51a045SDuje Mihanović }, 162*6f51a045SDuje Mihanović }; 163*6f51a045SDuje Mihanović 164*6f51a045SDuje Mihanović static void pxa1908_pd_remove(struct auxiliary_device *auxdev) 165*6f51a045SDuje Mihanović { 166*6f51a045SDuje Mihanović struct pxa1908_pd *pd; 167*6f51a045SDuje Mihanović int ret; 168*6f51a045SDuje Mihanović 169*6f51a045SDuje Mihanović for (int i = NR_DOMAINS - 1; i >= 0; i--) { 170*6f51a045SDuje Mihanović pd = &domains[i]; 171*6f51a045SDuje Mihanović 172*6f51a045SDuje Mihanović if (!pd->initialized) 173*6f51a045SDuje Mihanović continue; 174*6f51a045SDuje Mihanović 175*6f51a045SDuje Mihanović if (pxa1908_pd_is_on(pd) && !pd->data.keep_on) 176*6f51a045SDuje Mihanović pxa1908_pd_power_off(&pd->genpd); 177*6f51a045SDuje Mihanović 178*6f51a045SDuje Mihanović ret = pm_genpd_remove(&pd->genpd); 179*6f51a045SDuje Mihanović if (ret) 180*6f51a045SDuje Mihanović dev_err(&auxdev->dev, "failed to remove domain '%s': %d\n", 181*6f51a045SDuje Mihanović pd->genpd.name, ret); 182*6f51a045SDuje Mihanović } 183*6f51a045SDuje Mihanović } 184*6f51a045SDuje Mihanović 185*6f51a045SDuje Mihanović static int 186*6f51a045SDuje Mihanović pxa1908_pd_init(struct pxa1908_pd_ctrl *ctrl, int id, struct device *dev) 187*6f51a045SDuje Mihanović { 188*6f51a045SDuje Mihanović struct pxa1908_pd *pd = &domains[id]; 189*6f51a045SDuje Mihanović int ret; 190*6f51a045SDuje Mihanović 191*6f51a045SDuje Mihanović ctrl->domains[id] = &pd->genpd; 192*6f51a045SDuje Mihanović 193*6f51a045SDuje Mihanović pd->ctrl = ctrl; 194*6f51a045SDuje Mihanović 195*6f51a045SDuje Mihanović /* Make sure the state of the hardware is synced with the domain table above. */ 196*6f51a045SDuje Mihanović if (pd->data.keep_on) { 197*6f51a045SDuje Mihanović ret = pd->genpd.power_on(&pd->genpd); 198*6f51a045SDuje Mihanović if (ret) 199*6f51a045SDuje Mihanović return dev_err_probe(dev, ret, "failed to power on domain '%s'\n", 200*6f51a045SDuje Mihanović pd->genpd.name); 201*6f51a045SDuje Mihanović } else { 202*6f51a045SDuje Mihanović if (pxa1908_pd_is_on(pd)) { 203*6f51a045SDuje Mihanović dev_warn(dev, 204*6f51a045SDuje Mihanović "domain '%s' is on despite being default off; powering off\n", 205*6f51a045SDuje Mihanović pd->genpd.name); 206*6f51a045SDuje Mihanović 207*6f51a045SDuje Mihanović ret = pd->genpd.power_off(&pd->genpd); 208*6f51a045SDuje Mihanović if (ret) 209*6f51a045SDuje Mihanović return dev_err_probe(dev, ret, 210*6f51a045SDuje Mihanović "failed to power off domain '%s'\n", 211*6f51a045SDuje Mihanović pd->genpd.name); 212*6f51a045SDuje Mihanović } 213*6f51a045SDuje Mihanović } 214*6f51a045SDuje Mihanović 215*6f51a045SDuje Mihanović ret = pm_genpd_init(&pd->genpd, NULL, !pd->data.keep_on); 216*6f51a045SDuje Mihanović if (ret) 217*6f51a045SDuje Mihanović return dev_err_probe(dev, ret, "domain '%s' failed to initialize\n", 218*6f51a045SDuje Mihanović pd->genpd.name); 219*6f51a045SDuje Mihanović 220*6f51a045SDuje Mihanović pd->initialized = true; 221*6f51a045SDuje Mihanović 222*6f51a045SDuje Mihanović return 0; 223*6f51a045SDuje Mihanović } 224*6f51a045SDuje Mihanović 225*6f51a045SDuje Mihanović static int 226*6f51a045SDuje Mihanović pxa1908_pd_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *aux_id) 227*6f51a045SDuje Mihanović { 228*6f51a045SDuje Mihanović struct pxa1908_pd_ctrl *ctrl; 229*6f51a045SDuje Mihanović struct device *dev = &auxdev->dev; 230*6f51a045SDuje Mihanović int ret; 231*6f51a045SDuje Mihanović 232*6f51a045SDuje Mihanović ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); 233*6f51a045SDuje Mihanović if (!ctrl) 234*6f51a045SDuje Mihanović return -ENOMEM; 235*6f51a045SDuje Mihanović 236*6f51a045SDuje Mihanović auxiliary_set_drvdata(auxdev, ctrl); 237*6f51a045SDuje Mihanović 238*6f51a045SDuje Mihanović ctrl->base = syscon_node_to_regmap(dev->parent->of_node); 239*6f51a045SDuje Mihanović if (IS_ERR(ctrl->base)) 240*6f51a045SDuje Mihanović return dev_err_probe(dev, PTR_ERR(ctrl->base), "no regmap available\n"); 241*6f51a045SDuje Mihanović 242*6f51a045SDuje Mihanović ctrl->dev = dev; 243*6f51a045SDuje Mihanović ctrl->onecell_data.domains = ctrl->domains; 244*6f51a045SDuje Mihanović ctrl->onecell_data.num_domains = NR_DOMAINS; 245*6f51a045SDuje Mihanović 246*6f51a045SDuje Mihanović for (int i = 0; i < NR_DOMAINS; i++) { 247*6f51a045SDuje Mihanović ret = pxa1908_pd_init(ctrl, i, dev); 248*6f51a045SDuje Mihanović if (ret) 249*6f51a045SDuje Mihanović goto err; 250*6f51a045SDuje Mihanović } 251*6f51a045SDuje Mihanović 252*6f51a045SDuje Mihanović return of_genpd_add_provider_onecell(dev->parent->of_node, &ctrl->onecell_data); 253*6f51a045SDuje Mihanović 254*6f51a045SDuje Mihanović err: 255*6f51a045SDuje Mihanović pxa1908_pd_remove(auxdev); 256*6f51a045SDuje Mihanović return ret; 257*6f51a045SDuje Mihanović } 258*6f51a045SDuje Mihanović 259*6f51a045SDuje Mihanović static const struct auxiliary_device_id pxa1908_pd_id[] = { 260*6f51a045SDuje Mihanović { .name = "clk_pxa1908_apmu.power" }, 261*6f51a045SDuje Mihanović { } 262*6f51a045SDuje Mihanović }; 263*6f51a045SDuje Mihanović MODULE_DEVICE_TABLE(auxiliary, pxa1908_pd_id); 264*6f51a045SDuje Mihanović 265*6f51a045SDuje Mihanović static struct auxiliary_driver pxa1908_pd_driver = { 266*6f51a045SDuje Mihanović .probe = pxa1908_pd_probe, 267*6f51a045SDuje Mihanović .remove = pxa1908_pd_remove, 268*6f51a045SDuje Mihanović .id_table = pxa1908_pd_id, 269*6f51a045SDuje Mihanović }; 270*6f51a045SDuje Mihanović module_auxiliary_driver(pxa1908_pd_driver); 271*6f51a045SDuje Mihanović 272*6f51a045SDuje Mihanović MODULE_AUTHOR("Duje Mihanović <duje@dujemihanovic.xyz>"); 273*6f51a045SDuje Mihanović MODULE_DESCRIPTION("Marvell PXA1908 power domain driver"); 274*6f51a045SDuje Mihanović MODULE_LICENSE("GPL"); 275