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