1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (C) Arm Ltd. 2024 4 * 5 * Allwinner H6/H616 PRCM power domain driver. 6 * This covers a few registers inside the PRCM (Power Reset Clock Management) 7 * block that control some power rails, most prominently for the Mali GPU. 8 */ 9 10 #include <linux/bitfield.h> 11 #include <linux/clk.h> 12 #include <linux/io.h> 13 #include <linux/iopoll.h> 14 #include <linux/module.h> 15 #include <linux/of.h> 16 #include <linux/platform_device.h> 17 #include <linux/pm_domain.h> 18 #include <linux/reset.h> 19 20 /* 21 * The PRCM block covers multiple devices, starting with some clocks, 22 * then followed by the power rails. 23 * The clocks are covered by a different driver, so this driver's MMIO range 24 * starts later in the PRCM MMIO frame, not at the beginning of it. 25 * To keep the register offsets consistent with other PRCM documentation, 26 * express the registers relative to the beginning of the whole PRCM, and 27 * subtract the PPU offset this driver is bound to. 28 */ 29 #define PD_H6_PPU_OFFSET 0x250 30 #define PD_H6_VDD_SYS_REG 0x250 31 #define PD_H616_ANA_VDD_GATE BIT(4) 32 #define PD_H6_CPUS_VDD_GATE BIT(3) 33 #define PD_H6_AVCC_VDD_GATE BIT(2) 34 #define PD_H6_GPU_REG 0x254 35 #define PD_H6_GPU_GATE BIT(0) 36 37 struct sun50i_h6_ppu_pd { 38 struct generic_pm_domain genpd; 39 void __iomem *reg; 40 u32 gate_mask; 41 bool negated; 42 }; 43 44 #define FLAG_PPU_ALWAYS_ON BIT(0) 45 #define FLAG_PPU_NEGATED BIT(1) 46 47 struct sun50i_h6_ppu_desc { 48 const char *name; 49 u32 offset; 50 u32 mask; 51 unsigned int flags; 52 }; 53 54 static const struct sun50i_h6_ppu_desc sun50i_h6_ppus[] = { 55 { "AVCC", PD_H6_VDD_SYS_REG, PD_H6_AVCC_VDD_GATE }, 56 { "CPUS", PD_H6_VDD_SYS_REG, PD_H6_CPUS_VDD_GATE }, 57 { "GPU", PD_H6_GPU_REG, PD_H6_GPU_GATE }, 58 }; 59 static const struct sun50i_h6_ppu_desc sun50i_h616_ppus[] = { 60 { "PLL", PD_H6_VDD_SYS_REG, PD_H6_AVCC_VDD_GATE, 61 FLAG_PPU_ALWAYS_ON | FLAG_PPU_NEGATED }, 62 { "ANA", PD_H6_VDD_SYS_REG, PD_H616_ANA_VDD_GATE, FLAG_PPU_ALWAYS_ON }, 63 { "GPU", PD_H6_GPU_REG, PD_H6_GPU_GATE, FLAG_PPU_NEGATED }, 64 }; 65 66 struct sun50i_h6_ppu_data { 67 const struct sun50i_h6_ppu_desc *descs; 68 int nr_domains; 69 }; 70 71 static const struct sun50i_h6_ppu_data sun50i_h6_ppu_data = { 72 .descs = sun50i_h6_ppus, 73 .nr_domains = ARRAY_SIZE(sun50i_h6_ppus), 74 }; 75 76 static const struct sun50i_h6_ppu_data sun50i_h616_ppu_data = { 77 .descs = sun50i_h616_ppus, 78 .nr_domains = ARRAY_SIZE(sun50i_h616_ppus), 79 }; 80 81 #define to_sun50i_h6_ppu_pd(_genpd) \ 82 container_of(_genpd, struct sun50i_h6_ppu_pd, genpd) 83 84 static bool sun50i_h6_ppu_power_status(const struct sun50i_h6_ppu_pd *pd) 85 { 86 bool bit = readl(pd->reg) & pd->gate_mask; 87 88 return bit ^ pd->negated; 89 } 90 91 static int sun50i_h6_ppu_pd_set_power(const struct sun50i_h6_ppu_pd *pd, 92 bool set_bit) 93 { 94 u32 reg = readl(pd->reg); 95 96 if (set_bit) 97 writel(reg | pd->gate_mask, pd->reg); 98 else 99 writel(reg & ~pd->gate_mask, pd->reg); 100 101 return 0; 102 } 103 104 static int sun50i_h6_ppu_pd_power_on(struct generic_pm_domain *genpd) 105 { 106 const struct sun50i_h6_ppu_pd *pd = to_sun50i_h6_ppu_pd(genpd); 107 108 return sun50i_h6_ppu_pd_set_power(pd, !pd->negated); 109 } 110 111 static int sun50i_h6_ppu_pd_power_off(struct generic_pm_domain *genpd) 112 { 113 const struct sun50i_h6_ppu_pd *pd = to_sun50i_h6_ppu_pd(genpd); 114 115 return sun50i_h6_ppu_pd_set_power(pd, pd->negated); 116 } 117 118 static int sun50i_h6_ppu_probe(struct platform_device *pdev) 119 { 120 struct device *dev = &pdev->dev; 121 struct genpd_onecell_data *ppu; 122 struct sun50i_h6_ppu_pd *pds; 123 const struct sun50i_h6_ppu_data *data; 124 void __iomem *base; 125 int ret, i; 126 127 data = of_device_get_match_data(dev); 128 if (!data) 129 return -EINVAL; 130 131 pds = devm_kcalloc(dev, data->nr_domains, sizeof(*pds), GFP_KERNEL); 132 if (!pds) 133 return -ENOMEM; 134 135 ppu = devm_kzalloc(dev, sizeof(*ppu), GFP_KERNEL); 136 if (!ppu) 137 return -ENOMEM; 138 139 ppu->num_domains = data->nr_domains; 140 ppu->domains = devm_kcalloc(dev, data->nr_domains, 141 sizeof(*ppu->domains), GFP_KERNEL); 142 if (!ppu->domains) 143 return -ENOMEM; 144 145 platform_set_drvdata(pdev, ppu); 146 147 base = devm_platform_ioremap_resource(pdev, 0); 148 if (IS_ERR(base)) 149 return PTR_ERR(base); 150 151 for (i = 0; i < data->nr_domains; i++) { 152 struct sun50i_h6_ppu_pd *pd = &pds[i]; 153 const struct sun50i_h6_ppu_desc *desc = &data->descs[i]; 154 155 pd->genpd.name = desc->name; 156 pd->genpd.power_off = sun50i_h6_ppu_pd_power_off; 157 pd->genpd.power_on = sun50i_h6_ppu_pd_power_on; 158 if (desc->flags & FLAG_PPU_ALWAYS_ON) 159 pd->genpd.flags = GENPD_FLAG_ALWAYS_ON; 160 pd->negated = !!(desc->flags & FLAG_PPU_NEGATED); 161 pd->reg = base + desc->offset - PD_H6_PPU_OFFSET; 162 pd->gate_mask = desc->mask; 163 164 ret = pm_genpd_init(&pd->genpd, NULL, 165 !sun50i_h6_ppu_power_status(pd)); 166 if (ret) { 167 dev_warn(dev, "Failed to add %s power domain: %d\n", 168 desc->name, ret); 169 goto out_remove_pds; 170 } 171 ppu->domains[i] = &pd->genpd; 172 } 173 174 ret = of_genpd_add_provider_onecell(dev->of_node, ppu); 175 if (!ret) 176 return 0; 177 178 dev_warn(dev, "Failed to add provider: %d\n", ret); 179 out_remove_pds: 180 for (i--; i >= 0; i--) 181 pm_genpd_remove(&pds[i].genpd); 182 183 return ret; 184 } 185 186 static const struct of_device_id sun50i_h6_ppu_of_match[] = { 187 { .compatible = "allwinner,sun50i-h6-prcm-ppu", 188 .data = &sun50i_h6_ppu_data }, 189 { .compatible = "allwinner,sun50i-h616-prcm-ppu", 190 .data = &sun50i_h616_ppu_data }, 191 { } 192 }; 193 MODULE_DEVICE_TABLE(of, sun50i_h6_ppu_of_match); 194 195 static struct platform_driver sun50i_h6_ppu_driver = { 196 .probe = sun50i_h6_ppu_probe, 197 .driver = { 198 .name = "sun50i-h6-prcm-ppu", 199 .of_match_table = sun50i_h6_ppu_of_match, 200 /* Power domains cannot be removed while they are in use. */ 201 .suppress_bind_attrs = true, 202 }, 203 }; 204 module_platform_driver(sun50i_h6_ppu_driver); 205 206 MODULE_AUTHOR("Andre Przywara <andre.przywara@arm.com>"); 207 MODULE_DESCRIPTION("Allwinner H6 PRCM power domain driver"); 208 MODULE_LICENSE("GPL"); 209