Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

pmdomain: marvell: Add PXA1908 power domains

Marvell's PXA1908 SoC has a few power domains for its VPU, GPU, image
processor and DSI PHY. Add a driver to control these.

Signed-off-by: Duje Mihanović <duje@dujemihanovic.xyz>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>

authored by

Duje Mihanović and committed by
Ulf Hansson
6f51a045 614106a7

+298
+1
MAINTAINERS
··· 2871 2871 S: Maintained 2872 2872 F: arch/arm64/boot/dts/marvell/mmp/ 2873 2873 F: drivers/clk/mmp/clk-pxa1908*.c 2874 + F: drivers/pmdomain/marvell/ 2874 2875 F: include/dt-bindings/clock/marvell,pxa1908.h 2875 2876 F: include/dt-bindings/power/marvell,pxa1908-power.h 2876 2877
+1
drivers/pmdomain/Kconfig
··· 7 7 source "drivers/pmdomain/arm/Kconfig" 8 8 source "drivers/pmdomain/bcm/Kconfig" 9 9 source "drivers/pmdomain/imx/Kconfig" 10 + source "drivers/pmdomain/marvell/Kconfig" 10 11 source "drivers/pmdomain/mediatek/Kconfig" 11 12 source "drivers/pmdomain/qcom/Kconfig" 12 13 source "drivers/pmdomain/renesas/Kconfig"
+1
drivers/pmdomain/Makefile
··· 5 5 obj-y += arm/ 6 6 obj-y += bcm/ 7 7 obj-y += imx/ 8 + obj-y += marvell/ 8 9 obj-y += mediatek/ 9 10 obj-y += qcom/ 10 11 obj-y += renesas/
+18
drivers/pmdomain/marvell/Kconfig
··· 1 + # SPDX-License-Identifier: GPL-2.0-only 2 + 3 + menu "Marvell PM Domains" 4 + depends on ARCH_MMP || COMPILE_TEST 5 + 6 + config PXA1908_PM_DOMAINS 7 + tristate "Marvell PXA1908 power domains" 8 + depends on OF 9 + depends on PM 10 + default y if ARCH_MMP && ARM64 11 + select AUXILIARY_BUS 12 + select MFD_SYSCON 13 + select PM_GENERIC_DOMAINS 14 + select PM_GENERIC_DOMAINS_OF 15 + help 16 + Say Y here to enable support for Marvell PXA1908's power domanis. 17 + 18 + endmenu
+3
drivers/pmdomain/marvell/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0-only 2 + 3 + obj-$(CONFIG_PXA1908_PM_DOMAINS) += pxa1908-power-controller.o
+274
drivers/pmdomain/marvell/pxa1908-power-controller.c
··· 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");