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 *
to_th1520_power_domain(struct generic_pm_domain * genpd)45 to_th1520_power_domain(struct generic_pm_domain *genpd)
46 {
47 	return container_of(genpd, struct th1520_power_domain, genpd);
48 }
49 
th1520_pd_power_on(struct generic_pm_domain * domain)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 
th1520_pd_power_off(struct generic_pm_domain * domain)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 
th1520_pd_xlate(const struct of_phandle_args * spec,void * data)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 *
th1520_add_pm_domain(struct device * dev,const struct th1520_power_info * pi)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 
th1520_pd_init_all_off(struct generic_pm_domain ** domains,struct device * dev)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 
th1520_pd_probe(struct platform_device * pdev)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