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