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/firmware/thead/thead,th1520-aon.h> 9 #include <linux/slab.h> 10 #include <linux/platform_device.h> 11 #include <linux/pm_domain.h> 12 13 #include <dt-bindings/power/thead,th1520-power.h> 14 15 struct th1520_power_domain { 16 struct th1520_aon_chan *aon_chan; 17 struct generic_pm_domain genpd; 18 u32 rsrc; 19 }; 20 21 struct th1520_power_info { 22 const char *name; 23 u32 rsrc; 24 bool disabled; 25 }; 26 27 /* 28 * The AUDIO power domain is marked as disabled to prevent the driver from 29 * managing its power state. Direct AON firmware calls to control this power 30 * island trigger a firmware bug causing system instability. Until this 31 * firmware issue is resolved, the AUDIO power domain must remain disabled 32 * to avoid crashes. 33 */ 34 static const struct th1520_power_info th1520_pd_ranges[] = { 35 [TH1520_AUDIO_PD] = {"audio", TH1520_AON_AUDIO_PD, true }, 36 [TH1520_VDEC_PD] = { "vdec", TH1520_AON_VDEC_PD, false }, 37 [TH1520_NPU_PD] = { "npu", TH1520_AON_NPU_PD, false }, 38 [TH1520_VENC_PD] = { "venc", TH1520_AON_VENC_PD, false }, 39 [TH1520_GPU_PD] = { "gpu", TH1520_AON_GPU_PD, false }, 40 [TH1520_DSP0_PD] = { "dsp0", TH1520_AON_DSP0_PD, false }, 41 [TH1520_DSP1_PD] = { "dsp1", TH1520_AON_DSP1_PD, false } 42 }; 43 44 static inline struct th1520_power_domain * 45 to_th1520_power_domain(struct generic_pm_domain *genpd) 46 { 47 return container_of(genpd, struct th1520_power_domain, genpd); 48 } 49 50 static int th1520_pd_power_on(struct generic_pm_domain *domain) 51 { 52 struct th1520_power_domain *pd = to_th1520_power_domain(domain); 53 54 return th1520_aon_power_update(pd->aon_chan, pd->rsrc, true); 55 } 56 57 static int th1520_pd_power_off(struct generic_pm_domain *domain) 58 { 59 struct th1520_power_domain *pd = to_th1520_power_domain(domain); 60 61 return th1520_aon_power_update(pd->aon_chan, pd->rsrc, false); 62 } 63 64 static struct generic_pm_domain *th1520_pd_xlate(const struct of_phandle_args *spec, 65 void *data) 66 { 67 struct generic_pm_domain *domain = ERR_PTR(-ENOENT); 68 struct genpd_onecell_data *pd_data = data; 69 unsigned int i; 70 71 for (i = 0; i < ARRAY_SIZE(th1520_pd_ranges); i++) { 72 struct th1520_power_domain *pd; 73 74 if (th1520_pd_ranges[i].disabled) 75 continue; 76 77 pd = to_th1520_power_domain(pd_data->domains[i]); 78 if (pd->rsrc == spec->args[0]) { 79 domain = &pd->genpd; 80 break; 81 } 82 } 83 84 return domain; 85 } 86 87 static struct th1520_power_domain * 88 th1520_add_pm_domain(struct device *dev, const struct th1520_power_info *pi) 89 { 90 struct th1520_power_domain *pd; 91 int ret; 92 93 pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL); 94 if (!pd) 95 return ERR_PTR(-ENOMEM); 96 97 pd->rsrc = pi->rsrc; 98 pd->genpd.power_on = th1520_pd_power_on; 99 pd->genpd.power_off = th1520_pd_power_off; 100 pd->genpd.name = pi->name; 101 102 ret = pm_genpd_init(&pd->genpd, NULL, true); 103 if (ret) 104 return ERR_PTR(ret); 105 106 return pd; 107 } 108 109 static void th1520_pd_init_all_off(struct generic_pm_domain **domains, 110 struct device *dev) 111 { 112 int ret; 113 int i; 114 115 for (i = 0; i < ARRAY_SIZE(th1520_pd_ranges); i++) { 116 struct th1520_power_domain *pd; 117 118 if (th1520_pd_ranges[i].disabled) 119 continue; 120 121 pd = to_th1520_power_domain(domains[i]); 122 123 ret = th1520_aon_power_update(pd->aon_chan, pd->rsrc, false); 124 if (ret) 125 dev_err(dev, 126 "Failed to initially power down power domain %s\n", 127 pd->genpd.name); 128 } 129 } 130 131 static int th1520_pd_probe(struct platform_device *pdev) 132 { 133 struct generic_pm_domain **domains; 134 struct genpd_onecell_data *pd_data; 135 struct th1520_aon_chan *aon_chan; 136 struct device *dev = &pdev->dev; 137 int i, ret; 138 139 aon_chan = th1520_aon_init(dev); 140 if (IS_ERR(aon_chan)) 141 return dev_err_probe(dev, PTR_ERR(aon_chan), 142 "Failed to get AON channel\n"); 143 144 domains = devm_kcalloc(dev, ARRAY_SIZE(th1520_pd_ranges), 145 sizeof(*domains), GFP_KERNEL); 146 if (!domains) { 147 ret = -ENOMEM; 148 goto err_clean_aon; 149 } 150 151 pd_data = devm_kzalloc(dev, sizeof(*pd_data), GFP_KERNEL); 152 if (!pd_data) { 153 ret = -ENOMEM; 154 goto err_clean_aon; 155 } 156 157 for (i = 0; i < ARRAY_SIZE(th1520_pd_ranges); i++) { 158 struct th1520_power_domain *pd; 159 160 if (th1520_pd_ranges[i].disabled) 161 continue; 162 163 pd = th1520_add_pm_domain(dev, &th1520_pd_ranges[i]); 164 if (IS_ERR(pd)) { 165 ret = PTR_ERR(pd); 166 goto err_clean_genpd; 167 } 168 169 pd->aon_chan = aon_chan; 170 domains[i] = &pd->genpd; 171 dev_dbg(dev, "added power domain %s\n", pd->genpd.name); 172 } 173 174 pd_data->domains = domains; 175 pd_data->num_domains = ARRAY_SIZE(th1520_pd_ranges); 176 pd_data->xlate = th1520_pd_xlate; 177 178 /* 179 * Initialize all power domains to off to ensure they start in a 180 * low-power state. This allows device drivers to manage power 181 * domains by turning them on or off as needed. 182 */ 183 th1520_pd_init_all_off(domains, dev); 184 185 ret = of_genpd_add_provider_onecell(dev->of_node, pd_data); 186 if (ret) 187 goto err_clean_genpd; 188 189 return 0; 190 191 err_clean_genpd: 192 for (i--; i >= 0; i--) 193 pm_genpd_remove(domains[i]); 194 err_clean_aon: 195 th1520_aon_deinit(aon_chan); 196 197 return ret; 198 } 199 200 static const struct of_device_id th1520_pd_match[] = { 201 { .compatible = "thead,th1520-aon" }, 202 { /* Sentinel */ } 203 }; 204 MODULE_DEVICE_TABLE(of, th1520_pd_match); 205 206 static struct platform_driver th1520_pd_driver = { 207 .driver = { 208 .name = "th1520-pd", 209 .of_match_table = th1520_pd_match, 210 .suppress_bind_attrs = true, 211 }, 212 .probe = th1520_pd_probe, 213 }; 214 module_platform_driver(th1520_pd_driver); 215 216 MODULE_AUTHOR("Michal Wilczynski <m.wilczynski@samsung.com>"); 217 MODULE_DESCRIPTION("T-HEAD TH1520 SoC power domain controller"); 218 MODULE_LICENSE("GPL"); 219