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