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