1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (C) 2021 Alibaba Group Holding Limited. 4 * Copyright (c) 2024 Samsung Electronics Co., Ltd. 5 * Author: Michal Wilczynski <m.wilczynski@samsung.com> 6 */ 7 8 #include <linux/auxiliary_bus.h> 9 #include <linux/firmware/thead/thead,th1520-aon.h> 10 #include <linux/slab.h> 11 #include <linux/platform_device.h> 12 #include <linux/pm_domain.h> 13 14 #include <dt-bindings/power/thead,th1520-power.h> 15 16 struct th1520_power_domain { 17 struct th1520_aon_chan *aon_chan; 18 struct generic_pm_domain genpd; 19 u32 rsrc; 20 }; 21 22 struct th1520_power_info { 23 const char *name; 24 u32 rsrc; 25 bool disabled; 26 }; 27 28 /* 29 * The AUDIO power domain is marked as disabled to prevent the driver from 30 * managing its power state. Direct AON firmware calls to control this power 31 * island trigger a firmware bug causing system instability. Until this 32 * firmware issue is resolved, the AUDIO power domain must remain disabled 33 * to avoid crashes. 34 */ 35 static const struct th1520_power_info th1520_pd_ranges[] = { 36 [TH1520_AUDIO_PD] = {"audio", TH1520_AON_AUDIO_PD, true }, 37 [TH1520_VDEC_PD] = { "vdec", TH1520_AON_VDEC_PD, false }, 38 [TH1520_NPU_PD] = { "npu", TH1520_AON_NPU_PD, false }, 39 [TH1520_VENC_PD] = { "venc", TH1520_AON_VENC_PD, false }, 40 [TH1520_GPU_PD] = { "gpu", TH1520_AON_GPU_PD, false }, 41 [TH1520_DSP0_PD] = { "dsp0", TH1520_AON_DSP0_PD, false }, 42 [TH1520_DSP1_PD] = { "dsp1", TH1520_AON_DSP1_PD, false } 43 }; 44 45 static inline struct th1520_power_domain * 46 to_th1520_power_domain(struct generic_pm_domain *genpd) 47 { 48 return container_of(genpd, struct th1520_power_domain, genpd); 49 } 50 51 static int th1520_pd_power_on(struct generic_pm_domain *domain) 52 { 53 struct th1520_power_domain *pd = to_th1520_power_domain(domain); 54 55 return th1520_aon_power_update(pd->aon_chan, pd->rsrc, true); 56 } 57 58 static int th1520_pd_power_off(struct generic_pm_domain *domain) 59 { 60 struct th1520_power_domain *pd = to_th1520_power_domain(domain); 61 62 return th1520_aon_power_update(pd->aon_chan, pd->rsrc, false); 63 } 64 65 static struct generic_pm_domain *th1520_pd_xlate(const struct of_phandle_args *spec, 66 void *data) 67 { 68 struct generic_pm_domain *domain = ERR_PTR(-ENOENT); 69 struct genpd_onecell_data *pd_data = data; 70 unsigned int i; 71 72 for (i = 0; i < ARRAY_SIZE(th1520_pd_ranges); i++) { 73 struct th1520_power_domain *pd; 74 75 if (th1520_pd_ranges[i].disabled) 76 continue; 77 78 pd = to_th1520_power_domain(pd_data->domains[i]); 79 if (pd->rsrc == spec->args[0]) { 80 domain = &pd->genpd; 81 break; 82 } 83 } 84 85 return domain; 86 } 87 88 static struct th1520_power_domain * 89 th1520_add_pm_domain(struct device *dev, const struct th1520_power_info *pi) 90 { 91 struct th1520_power_domain *pd; 92 int ret; 93 94 pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL); 95 if (!pd) 96 return ERR_PTR(-ENOMEM); 97 98 pd->rsrc = pi->rsrc; 99 pd->genpd.power_on = th1520_pd_power_on; 100 pd->genpd.power_off = th1520_pd_power_off; 101 pd->genpd.name = pi->name; 102 103 ret = pm_genpd_init(&pd->genpd, NULL, true); 104 if (ret) 105 return ERR_PTR(ret); 106 107 return pd; 108 } 109 110 static void th1520_pd_init_all_off(struct generic_pm_domain **domains, 111 struct device *dev) 112 { 113 int ret; 114 int i; 115 116 for (i = 0; i < ARRAY_SIZE(th1520_pd_ranges); i++) { 117 struct th1520_power_domain *pd; 118 119 if (th1520_pd_ranges[i].disabled) 120 continue; 121 122 pd = to_th1520_power_domain(domains[i]); 123 124 ret = th1520_aon_power_update(pd->aon_chan, pd->rsrc, false); 125 if (ret) 126 dev_err(dev, 127 "Failed to initially power down power domain %s\n", 128 pd->genpd.name); 129 } 130 } 131 132 static void th1520_pd_pwrseq_unregister_adev(void *adev) 133 { 134 auxiliary_device_delete(adev); 135 auxiliary_device_uninit(adev); 136 } 137 138 static int th1520_pd_pwrseq_gpu_init(struct device *dev) 139 { 140 struct auxiliary_device *adev; 141 int ret; 142 143 /* 144 * Correctly check only for the property's existence in the DT node. 145 * We don't need to get/claim the reset here; that is the job of 146 * the auxiliary driver that we are about to spawn. 147 */ 148 if (device_property_match_string(dev, "reset-names", "gpu-clkgen") < 0) 149 /* 150 * This is not an error. It simply means the optional sequencer 151 * is not described in the device tree. 152 */ 153 return 0; 154 155 adev = devm_kzalloc(dev, sizeof(*adev), GFP_KERNEL); 156 if (!adev) 157 return -ENOMEM; 158 159 adev->name = "pwrseq-gpu"; 160 adev->dev.parent = dev; 161 162 ret = auxiliary_device_init(adev); 163 if (ret) 164 return ret; 165 166 ret = auxiliary_device_add(adev); 167 if (ret) { 168 auxiliary_device_uninit(adev); 169 return ret; 170 } 171 172 return devm_add_action_or_reset(dev, th1520_pd_pwrseq_unregister_adev, 173 adev); 174 } 175 176 static int th1520_pd_reboot_init(struct device *dev, 177 struct th1520_aon_chan *aon_chan) 178 { 179 struct auxiliary_device *adev; 180 181 adev = devm_auxiliary_device_create(dev, "reboot", aon_chan); 182 if (!adev) 183 return -ENODEV; 184 185 return 0; 186 } 187 188 static int th1520_pd_probe(struct platform_device *pdev) 189 { 190 struct generic_pm_domain **domains; 191 struct genpd_onecell_data *pd_data; 192 struct th1520_aon_chan *aon_chan; 193 struct device *dev = &pdev->dev; 194 int i, ret; 195 196 aon_chan = th1520_aon_init(dev); 197 if (IS_ERR(aon_chan)) 198 return dev_err_probe(dev, PTR_ERR(aon_chan), 199 "Failed to get AON channel\n"); 200 201 domains = devm_kcalloc(dev, ARRAY_SIZE(th1520_pd_ranges), 202 sizeof(*domains), GFP_KERNEL); 203 if (!domains) { 204 ret = -ENOMEM; 205 goto err_clean_aon; 206 } 207 208 pd_data = devm_kzalloc(dev, sizeof(*pd_data), GFP_KERNEL); 209 if (!pd_data) { 210 ret = -ENOMEM; 211 goto err_clean_aon; 212 } 213 214 for (i = 0; i < ARRAY_SIZE(th1520_pd_ranges); i++) { 215 struct th1520_power_domain *pd; 216 217 if (th1520_pd_ranges[i].disabled) 218 continue; 219 220 pd = th1520_add_pm_domain(dev, &th1520_pd_ranges[i]); 221 if (IS_ERR(pd)) { 222 ret = PTR_ERR(pd); 223 goto err_clean_genpd; 224 } 225 226 pd->aon_chan = aon_chan; 227 domains[i] = &pd->genpd; 228 dev_dbg(dev, "added power domain %s\n", pd->genpd.name); 229 } 230 231 pd_data->domains = domains; 232 pd_data->num_domains = ARRAY_SIZE(th1520_pd_ranges); 233 pd_data->xlate = th1520_pd_xlate; 234 235 /* 236 * Initialize all power domains to off to ensure they start in a 237 * low-power state. This allows device drivers to manage power 238 * domains by turning them on or off as needed. 239 */ 240 th1520_pd_init_all_off(domains, dev); 241 242 ret = of_genpd_add_provider_onecell(dev->of_node, pd_data); 243 if (ret) 244 goto err_clean_genpd; 245 246 ret = th1520_pd_pwrseq_gpu_init(dev); 247 if (ret) 248 goto err_clean_provider; 249 250 ret = th1520_pd_reboot_init(dev, aon_chan); 251 if (ret) 252 goto err_clean_provider; 253 254 return 0; 255 256 err_clean_provider: 257 of_genpd_del_provider(dev->of_node); 258 err_clean_genpd: 259 for (i--; i >= 0; i--) 260 pm_genpd_remove(domains[i]); 261 err_clean_aon: 262 th1520_aon_deinit(aon_chan); 263 264 return ret; 265 } 266 267 static const struct of_device_id th1520_pd_match[] = { 268 { .compatible = "thead,th1520-aon" }, 269 { /* Sentinel */ } 270 }; 271 MODULE_DEVICE_TABLE(of, th1520_pd_match); 272 273 static struct platform_driver th1520_pd_driver = { 274 .driver = { 275 .name = "th1520-pd", 276 .of_match_table = th1520_pd_match, 277 .suppress_bind_attrs = true, 278 }, 279 .probe = th1520_pd_probe, 280 }; 281 module_platform_driver(th1520_pd_driver); 282 283 MODULE_AUTHOR("Michal Wilczynski <m.wilczynski@samsung.com>"); 284 MODULE_DESCRIPTION("T-HEAD TH1520 SoC power domain controller"); 285 MODULE_LICENSE("GPL"); 286