xref: /linux/drivers/pmdomain/thead/th1520-pm-domains.c (revision 68a052239fc4b351e961f698b824f7654a346091)
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