1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Allwinner PCK-600 power domain support 4 * 5 * Copyright (c) 2025 Chen-Yu Tsai <wens@csie.org> 6 * 7 * The hardware is likely based on the Arm PCK-600 IP, since some of 8 * the registers match Arm's documents, with additional delay controls 9 * that are in registers listed as reserved. 10 * 11 * Documents include: 12 * - "Arm CoreLink PCK-600 Power Control Kit" TRM 13 * - "Arm Power Policy Unit" architecture specification (DEN0051E) 14 */ 15 16 #include <linux/bitfield.h> 17 #include <linux/clk.h> 18 #include <linux/container_of.h> 19 #include <linux/device.h> 20 #include <linux/dev_printk.h> 21 #include <linux/err.h> 22 #include <linux/io.h> 23 #include <linux/iopoll.h> 24 #include <linux/module.h> 25 #include <linux/of.h> 26 #include <linux/platform_device.h> 27 #include <linux/pm_domain.h> 28 #include <linux/reset.h> 29 #include <linux/slab.h> 30 #include <linux/string_choices.h> 31 32 #define PPU_PWPR 0x0 33 #define PPU_PWSR 0x8 34 #define PPU_DCDR0 0x170 35 #define PPU_DCDR1 0x174 36 37 /* shared definition for PPU_PWPR and PPU_PWSR */ 38 #define PPU_PWR_STATUS GENMASK(3, 0) 39 #define PPU_POWER_MODE_ON 0x8 40 #define PPU_POWER_MODE_OFF 0x0 41 42 #define PPU_REG_SIZE 0x1000 43 44 struct sunxi_pck600_desc { 45 const char * const *pd_names; 46 unsigned int num_domains; 47 u32 logic_power_switch0_delay_offset; 48 u32 logic_power_switch1_delay_offset; 49 u32 off2on_delay_offset; 50 u32 device_ctrl0_delay; 51 u32 device_ctrl1_delay; 52 u32 logic_power_switch0_delay; 53 u32 logic_power_switch1_delay; 54 u32 off2on_delay; 55 }; 56 57 struct sunxi_pck600_pd { 58 struct generic_pm_domain genpd; 59 struct sunxi_pck600 *pck; 60 void __iomem *base; 61 }; 62 63 struct sunxi_pck600 { 64 struct device *dev; 65 struct genpd_onecell_data genpd_data; 66 struct sunxi_pck600_pd pds[]; 67 }; 68 69 #define to_sunxi_pd(gpd) container_of(gpd, struct sunxi_pck600_pd, genpd) 70 71 static int sunxi_pck600_pd_set_power(struct sunxi_pck600_pd *pd, bool on) 72 { 73 struct sunxi_pck600 *pck = pd->pck; 74 struct generic_pm_domain *genpd = &pd->genpd; 75 int ret; 76 u32 val, reg; 77 78 val = on ? PPU_POWER_MODE_ON : PPU_POWER_MODE_OFF; 79 80 reg = readl(pd->base + PPU_PWPR); 81 FIELD_MODIFY(PPU_PWR_STATUS, ®, val); 82 writel(reg, pd->base + PPU_PWPR); 83 84 /* push write out to hardware */ 85 reg = readl(pd->base + PPU_PWPR); 86 87 ret = readl_poll_timeout_atomic(pd->base + PPU_PWSR, reg, 88 FIELD_GET(PPU_PWR_STATUS, reg) == val, 89 0, 10000); 90 if (ret) 91 dev_err(pck->dev, "failed to turn domain \"%s\" %s: %d\n", 92 genpd->name, str_on_off(on), ret); 93 94 return ret; 95 } 96 97 static int sunxi_pck600_power_on(struct generic_pm_domain *domain) 98 { 99 struct sunxi_pck600_pd *pd = to_sunxi_pd(domain); 100 101 return sunxi_pck600_pd_set_power(pd, true); 102 } 103 104 static int sunxi_pck600_power_off(struct generic_pm_domain *domain) 105 { 106 struct sunxi_pck600_pd *pd = to_sunxi_pd(domain); 107 108 return sunxi_pck600_pd_set_power(pd, false); 109 } 110 111 static void sunxi_pck600_pd_setup(struct sunxi_pck600_pd *pd, 112 const struct sunxi_pck600_desc *desc) 113 { 114 writel(desc->device_ctrl0_delay, pd->base + PPU_DCDR0); 115 writel(desc->device_ctrl1_delay, pd->base + PPU_DCDR1); 116 writel(desc->logic_power_switch0_delay, 117 pd->base + desc->logic_power_switch0_delay_offset); 118 writel(desc->logic_power_switch1_delay, 119 pd->base + desc->logic_power_switch1_delay_offset); 120 writel(desc->off2on_delay, pd->base + desc->off2on_delay_offset); 121 } 122 123 static int sunxi_pck600_probe(struct platform_device *pdev) 124 { 125 struct device *dev = &pdev->dev; 126 const struct sunxi_pck600_desc *desc; 127 struct genpd_onecell_data *genpds; 128 struct sunxi_pck600 *pck; 129 struct reset_control *rst; 130 struct clk *clk; 131 void __iomem *base; 132 int i, ret; 133 134 desc = of_device_get_match_data(dev); 135 136 pck = devm_kzalloc(dev, struct_size(pck, pds, desc->num_domains), GFP_KERNEL); 137 if (!pck) 138 return -ENOMEM; 139 140 pck->dev = &pdev->dev; 141 platform_set_drvdata(pdev, pck); 142 143 genpds = &pck->genpd_data; 144 genpds->num_domains = desc->num_domains; 145 genpds->domains = devm_kcalloc(dev, desc->num_domains, 146 sizeof(*genpds->domains), GFP_KERNEL); 147 if (!genpds->domains) 148 return -ENOMEM; 149 150 base = devm_platform_ioremap_resource(pdev, 0); 151 if (IS_ERR(base)) 152 return PTR_ERR(base); 153 154 rst = devm_reset_control_get_exclusive_released(dev, NULL); 155 if (IS_ERR(rst)) 156 return dev_err_probe(dev, PTR_ERR(rst), "failed to get reset control\n"); 157 158 clk = devm_clk_get_enabled(dev, NULL); 159 if (IS_ERR(clk)) 160 return dev_err_probe(dev, PTR_ERR(clk), "failed to get clock\n"); 161 162 for (i = 0; i < desc->num_domains; i++) { 163 struct sunxi_pck600_pd *pd = &pck->pds[i]; 164 165 pd->genpd.name = desc->pd_names[i]; 166 pd->genpd.power_off = sunxi_pck600_power_off; 167 pd->genpd.power_on = sunxi_pck600_power_on; 168 pd->base = base + PPU_REG_SIZE * i; 169 170 sunxi_pck600_pd_setup(pd, desc); 171 ret = pm_genpd_init(&pd->genpd, NULL, false); 172 if (ret) { 173 dev_err_probe(dev, ret, "failed to initialize power domain\n"); 174 goto err_remove_pds; 175 } 176 177 genpds->domains[i] = &pd->genpd; 178 } 179 180 ret = of_genpd_add_provider_onecell(dev_of_node(dev), genpds); 181 if (ret) { 182 dev_err_probe(dev, ret, "failed to add PD provider\n"); 183 goto err_remove_pds; 184 } 185 186 return 0; 187 188 err_remove_pds: 189 for (i--; i >= 0; i--) 190 pm_genpd_remove(genpds->domains[i]); 191 192 return ret; 193 } 194 195 static const char * const sun55i_a523_pck600_pd_names[] = { 196 "VE", "GPU", "VI", "VO0", "VO1", "DE", "NAND", "PCIE" 197 }; 198 199 static const struct sunxi_pck600_desc sun55i_a523_pck600_desc = { 200 .pd_names = sun55i_a523_pck600_pd_names, 201 .num_domains = ARRAY_SIZE(sun55i_a523_pck600_pd_names), 202 .logic_power_switch0_delay_offset = 0xc00, 203 .logic_power_switch1_delay_offset = 0xc04, 204 .off2on_delay_offset = 0xc10, 205 .device_ctrl0_delay = 0xffffff, 206 .device_ctrl1_delay = 0xffff, 207 .logic_power_switch0_delay = 0x8080808, 208 .logic_power_switch1_delay = 0x808, 209 .off2on_delay = 0x8 210 }; 211 212 static const struct of_device_id sunxi_pck600_of_match[] = { 213 { 214 .compatible = "allwinner,sun55i-a523-pck-600", 215 .data = &sun55i_a523_pck600_desc, 216 }, 217 {} 218 }; 219 MODULE_DEVICE_TABLE(of, sunxi_pck600_of_match); 220 221 static struct platform_driver sunxi_pck600_driver = { 222 .probe = sunxi_pck600_probe, 223 .driver = { 224 .name = "sunxi-pck-600", 225 .of_match_table = sunxi_pck600_of_match, 226 /* Power domains cannot be removed if in use. */ 227 .suppress_bind_attrs = true, 228 }, 229 }; 230 module_platform_driver(sunxi_pck600_driver); 231 232 MODULE_DESCRIPTION("Allwinner PCK-600 power domain driver"); 233 MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>"); 234 MODULE_LICENSE("GPL"); 235