Commit | Line | Data |
---|---|---|
59b644b0 EBS |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * Copyright (c) 2020 Collabora Ltd. | |
4 | */ | |
5 | #include <linux/clk.h> | |
123e8b4f | 6 | #include <linux/clk-provider.h> |
59b644b0 EBS |
7 | #include <linux/init.h> |
8 | #include <linux/io.h> | |
9 | #include <linux/iopoll.h> | |
10 | #include <linux/mfd/syscon.h> | |
f6e35a67 | 11 | #include <linux/of.h> |
59b644b0 | 12 | #include <linux/of_clk.h> |
59b644b0 EBS |
13 | #include <linux/platform_device.h> |
14 | #include <linux/pm_domain.h> | |
15 | #include <linux/regmap.h> | |
1b18c055 | 16 | #include <linux/regulator/consumer.h> |
59b644b0 EBS |
17 | #include <linux/soc/mediatek/infracfg.h> |
18 | ||
2b5764fb | 19 | #include "mt6795-pm-domains.h" |
207f13b4 | 20 | #include "mt8167-pm-domains.h" |
59b644b0 | 21 | #include "mt8173-pm-domains.h" |
eb9fa767 | 22 | #include "mt8183-pm-domains.h" |
88590cbc | 23 | #include "mt8186-pm-domains.h" |
e610e814 | 24 | #include "mt8188-pm-domains.h" |
a49d5e7a | 25 | #include "mt8192-pm-domains.h" |
342479c8 | 26 | #include "mt8195-pm-domains.h" |
59b644b0 EBS |
27 | |
28 | #define MTK_POLL_DELAY_US 10 | |
29 | #define MTK_POLL_TIMEOUT USEC_PER_SEC | |
30 | ||
31 | #define PWR_RST_B_BIT BIT(0) | |
32 | #define PWR_ISO_BIT BIT(1) | |
33 | #define PWR_ON_BIT BIT(2) | |
34 | #define PWR_ON_2ND_BIT BIT(3) | |
35 | #define PWR_CLK_DIS_BIT BIT(4) | |
58a17e31 MB |
36 | #define PWR_SRAM_CLKISO_BIT BIT(5) |
37 | #define PWR_SRAM_ISOINT_B_BIT BIT(6) | |
59b644b0 EBS |
38 | |
39 | struct scpsys_domain { | |
40 | struct generic_pm_domain genpd; | |
41 | const struct scpsys_domain_data *data; | |
42 | struct scpsys *scpsys; | |
43 | int num_clks; | |
44 | struct clk_bulk_data *clks; | |
123e8b4f MB |
45 | int num_subsys_clks; |
46 | struct clk_bulk_data *subsys_clks; | |
59b644b0 | 47 | struct regmap *infracfg; |
f414854c | 48 | struct regmap *smi; |
1b18c055 | 49 | struct regulator *supply; |
59b644b0 EBS |
50 | }; |
51 | ||
52 | struct scpsys { | |
53 | struct device *dev; | |
54 | struct regmap *base; | |
55 | const struct scpsys_soc_data *soc_data; | |
56 | struct genpd_onecell_data pd_data; | |
57 | struct generic_pm_domain *domains[]; | |
58 | }; | |
59 | ||
60 | #define to_scpsys_domain(gpd) container_of(gpd, struct scpsys_domain, genpd) | |
61 | ||
62 | static bool scpsys_domain_is_on(struct scpsys_domain *pd) | |
63 | { | |
64 | struct scpsys *scpsys = pd->scpsys; | |
65 | u32 status, status2; | |
66 | ||
db2ca860 | 67 | regmap_read(scpsys->base, pd->data->pwr_sta_offs, &status); |
59b644b0 EBS |
68 | status &= pd->data->sta_mask; |
69 | ||
db2ca860 | 70 | regmap_read(scpsys->base, pd->data->pwr_sta2nd_offs, &status2); |
59b644b0 EBS |
71 | status2 &= pd->data->sta_mask; |
72 | ||
73 | /* A domain is on when both status bits are set. */ | |
74 | return status && status2; | |
75 | } | |
76 | ||
77 | static int scpsys_sram_enable(struct scpsys_domain *pd) | |
78 | { | |
79 | u32 pdn_ack = pd->data->sram_pdn_ack_bits; | |
80 | struct scpsys *scpsys = pd->scpsys; | |
81 | unsigned int tmp; | |
58a17e31 | 82 | int ret; |
59b644b0 EBS |
83 | |
84 | regmap_clear_bits(scpsys->base, pd->data->ctl_offs, pd->data->sram_pdn_bits); | |
85 | ||
86 | /* Either wait until SRAM_PDN_ACK all 1 or 0 */ | |
58a17e31 MB |
87 | ret = regmap_read_poll_timeout(scpsys->base, pd->data->ctl_offs, tmp, |
88 | (tmp & pdn_ack) == 0, MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT); | |
89 | if (ret < 0) | |
90 | return ret; | |
91 | ||
92 | if (MTK_SCPD_CAPS(pd, MTK_SCPD_SRAM_ISO)) { | |
93 | regmap_set_bits(scpsys->base, pd->data->ctl_offs, PWR_SRAM_ISOINT_B_BIT); | |
94 | udelay(1); | |
95 | regmap_clear_bits(scpsys->base, pd->data->ctl_offs, PWR_SRAM_CLKISO_BIT); | |
96 | } | |
97 | ||
98 | return 0; | |
59b644b0 EBS |
99 | } |
100 | ||
101 | static int scpsys_sram_disable(struct scpsys_domain *pd) | |
102 | { | |
103 | u32 pdn_ack = pd->data->sram_pdn_ack_bits; | |
104 | struct scpsys *scpsys = pd->scpsys; | |
105 | unsigned int tmp; | |
106 | ||
58a17e31 MB |
107 | if (MTK_SCPD_CAPS(pd, MTK_SCPD_SRAM_ISO)) { |
108 | regmap_set_bits(scpsys->base, pd->data->ctl_offs, PWR_SRAM_CLKISO_BIT); | |
109 | udelay(1); | |
110 | regmap_clear_bits(scpsys->base, pd->data->ctl_offs, PWR_SRAM_ISOINT_B_BIT); | |
111 | } | |
112 | ||
59b644b0 EBS |
113 | regmap_set_bits(scpsys->base, pd->data->ctl_offs, pd->data->sram_pdn_bits); |
114 | ||
115 | /* Either wait until SRAM_PDN_ACK all 1 or 0 */ | |
116 | return regmap_read_poll_timeout(scpsys->base, pd->data->ctl_offs, tmp, | |
117 | (tmp & pdn_ack) == pdn_ack, MTK_POLL_DELAY_US, | |
118 | MTK_POLL_TIMEOUT); | |
119 | } | |
120 | ||
928296ea | 121 | static int _scpsys_bus_protect_enable(const struct scpsys_bus_prot_data *bpd, struct regmap *regmap) |
59b644b0 | 122 | { |
916d6d71 | 123 | int i, ret; |
59b644b0 | 124 | |
916d6d71 | 125 | for (i = 0; i < SPM_MAX_BUS_PROT_DATA; i++) { |
928296ea MB |
126 | u32 val, mask = bpd[i].bus_prot_mask; |
127 | ||
128 | if (!mask) | |
916d6d71 | 129 | break; |
59b644b0 | 130 | |
928296ea MB |
131 | if (bpd[i].bus_prot_reg_update) |
132 | regmap_set_bits(regmap, bpd[i].bus_prot_set, mask); | |
133 | else | |
f414854c | 134 | regmap_write(regmap, bpd[i].bus_prot_set, mask); |
928296ea | 135 | |
f414854c | 136 | ret = regmap_read_poll_timeout(regmap, bpd[i].bus_prot_sta, |
928296ea MB |
137 | val, (val & mask) == mask, |
138 | MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT); | |
916d6d71 MB |
139 | if (ret) |
140 | return ret; | |
141 | } | |
142 | ||
143 | return 0; | |
59b644b0 EBS |
144 | } |
145 | ||
928296ea MB |
146 | static int scpsys_bus_protect_enable(struct scpsys_domain *pd) |
147 | { | |
148 | int ret; | |
149 | ||
150 | ret = _scpsys_bus_protect_enable(pd->data->bp_infracfg, pd->infracfg); | |
f414854c MB |
151 | if (ret) |
152 | return ret; | |
928296ea | 153 | |
f414854c | 154 | return _scpsys_bus_protect_enable(pd->data->bp_smi, pd->smi); |
928296ea MB |
155 | } |
156 | ||
157 | static int _scpsys_bus_protect_disable(const struct scpsys_bus_prot_data *bpd, | |
158 | struct regmap *regmap) | |
59b644b0 | 159 | { |
916d6d71 MB |
160 | int i, ret; |
161 | ||
928296ea MB |
162 | for (i = SPM_MAX_BUS_PROT_DATA - 1; i >= 0; i--) { |
163 | u32 val, mask = bpd[i].bus_prot_mask; | |
164 | ||
165 | if (!mask) | |
916d6d71 | 166 | continue; |
59b644b0 | 167 | |
928296ea MB |
168 | if (bpd[i].bus_prot_reg_update) |
169 | regmap_clear_bits(regmap, bpd[i].bus_prot_clr, mask); | |
170 | else | |
f414854c | 171 | regmap_write(regmap, bpd[i].bus_prot_clr, mask); |
928296ea | 172 | |
1d4597fa MB |
173 | if (bpd[i].ignore_clr_ack) |
174 | continue; | |
175 | ||
f414854c | 176 | ret = regmap_read_poll_timeout(regmap, bpd[i].bus_prot_sta, |
928296ea MB |
177 | val, !(val & mask), |
178 | MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT); | |
916d6d71 MB |
179 | if (ret) |
180 | return ret; | |
181 | } | |
59b644b0 | 182 | |
916d6d71 | 183 | return 0; |
59b644b0 EBS |
184 | } |
185 | ||
928296ea MB |
186 | static int scpsys_bus_protect_disable(struct scpsys_domain *pd) |
187 | { | |
188 | int ret; | |
189 | ||
f414854c MB |
190 | ret = _scpsys_bus_protect_disable(pd->data->bp_smi, pd->smi); |
191 | if (ret) | |
192 | return ret; | |
928296ea | 193 | |
f414854c | 194 | return _scpsys_bus_protect_disable(pd->data->bp_infracfg, pd->infracfg); |
928296ea MB |
195 | } |
196 | ||
1b18c055 HYW |
197 | static int scpsys_regulator_enable(struct regulator *supply) |
198 | { | |
199 | return supply ? regulator_enable(supply) : 0; | |
200 | } | |
201 | ||
202 | static int scpsys_regulator_disable(struct regulator *supply) | |
203 | { | |
204 | return supply ? regulator_disable(supply) : 0; | |
205 | } | |
206 | ||
59b644b0 EBS |
207 | static int scpsys_power_on(struct generic_pm_domain *genpd) |
208 | { | |
209 | struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain, genpd); | |
210 | struct scpsys *scpsys = pd->scpsys; | |
211 | bool tmp; | |
212 | int ret; | |
213 | ||
1b18c055 | 214 | ret = scpsys_regulator_enable(pd->supply); |
59b644b0 EBS |
215 | if (ret) |
216 | return ret; | |
217 | ||
f0fce06e | 218 | ret = clk_bulk_prepare_enable(pd->num_clks, pd->clks); |
1b18c055 HYW |
219 | if (ret) |
220 | goto err_reg; | |
221 | ||
7d1ae592 AKC |
222 | if (pd->data->ext_buck_iso_offs && MTK_SCPD_CAPS(pd, MTK_SCPD_EXT_BUCK_ISO)) |
223 | regmap_clear_bits(scpsys->base, pd->data->ext_buck_iso_offs, | |
224 | pd->data->ext_buck_iso_mask); | |
225 | ||
59b644b0 EBS |
226 | /* subsys power on */ |
227 | regmap_set_bits(scpsys->base, pd->data->ctl_offs, PWR_ON_BIT); | |
228 | regmap_set_bits(scpsys->base, pd->data->ctl_offs, PWR_ON_2ND_BIT); | |
229 | ||
230 | /* wait until PWR_ACK = 1 */ | |
231 | ret = readx_poll_timeout(scpsys_domain_is_on, pd, tmp, tmp, MTK_POLL_DELAY_US, | |
232 | MTK_POLL_TIMEOUT); | |
233 | if (ret < 0) | |
234 | goto err_pwr_ack; | |
235 | ||
236 | regmap_clear_bits(scpsys->base, pd->data->ctl_offs, PWR_CLK_DIS_BIT); | |
237 | regmap_clear_bits(scpsys->base, pd->data->ctl_offs, PWR_ISO_BIT); | |
238 | regmap_set_bits(scpsys->base, pd->data->ctl_offs, PWR_RST_B_BIT); | |
239 | ||
f0fce06e | 240 | ret = clk_bulk_prepare_enable(pd->num_subsys_clks, pd->subsys_clks); |
123e8b4f MB |
241 | if (ret) |
242 | goto err_pwr_ack; | |
243 | ||
59b644b0 EBS |
244 | ret = scpsys_sram_enable(pd); |
245 | if (ret < 0) | |
123e8b4f | 246 | goto err_disable_subsys_clks; |
59b644b0 EBS |
247 | |
248 | ret = scpsys_bus_protect_disable(pd); | |
249 | if (ret < 0) | |
250 | goto err_disable_sram; | |
251 | ||
252 | return 0; | |
253 | ||
254 | err_disable_sram: | |
255 | scpsys_sram_disable(pd); | |
123e8b4f | 256 | err_disable_subsys_clks: |
f0fce06e | 257 | clk_bulk_disable_unprepare(pd->num_subsys_clks, pd->subsys_clks); |
59b644b0 | 258 | err_pwr_ack: |
f0fce06e | 259 | clk_bulk_disable_unprepare(pd->num_clks, pd->clks); |
1b18c055 HYW |
260 | err_reg: |
261 | scpsys_regulator_disable(pd->supply); | |
59b644b0 EBS |
262 | return ret; |
263 | } | |
264 | ||
265 | static int scpsys_power_off(struct generic_pm_domain *genpd) | |
266 | { | |
267 | struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain, genpd); | |
268 | struct scpsys *scpsys = pd->scpsys; | |
269 | bool tmp; | |
270 | int ret; | |
271 | ||
272 | ret = scpsys_bus_protect_enable(pd); | |
273 | if (ret < 0) | |
274 | return ret; | |
275 | ||
276 | ret = scpsys_sram_disable(pd); | |
277 | if (ret < 0) | |
278 | return ret; | |
279 | ||
7d1ae592 AKC |
280 | if (pd->data->ext_buck_iso_offs && MTK_SCPD_CAPS(pd, MTK_SCPD_EXT_BUCK_ISO)) |
281 | regmap_set_bits(scpsys->base, pd->data->ext_buck_iso_offs, | |
282 | pd->data->ext_buck_iso_mask); | |
283 | ||
f0fce06e | 284 | clk_bulk_disable_unprepare(pd->num_subsys_clks, pd->subsys_clks); |
123e8b4f | 285 | |
59b644b0 | 286 | /* subsys power off */ |
59b644b0 EBS |
287 | regmap_set_bits(scpsys->base, pd->data->ctl_offs, PWR_ISO_BIT); |
288 | regmap_set_bits(scpsys->base, pd->data->ctl_offs, PWR_CLK_DIS_BIT); | |
dba8eb83 | 289 | regmap_clear_bits(scpsys->base, pd->data->ctl_offs, PWR_RST_B_BIT); |
59b644b0 EBS |
290 | regmap_clear_bits(scpsys->base, pd->data->ctl_offs, PWR_ON_2ND_BIT); |
291 | regmap_clear_bits(scpsys->base, pd->data->ctl_offs, PWR_ON_BIT); | |
292 | ||
293 | /* wait until PWR_ACK = 0 */ | |
294 | ret = readx_poll_timeout(scpsys_domain_is_on, pd, tmp, !tmp, MTK_POLL_DELAY_US, | |
295 | MTK_POLL_TIMEOUT); | |
296 | if (ret < 0) | |
297 | return ret; | |
298 | ||
f0fce06e | 299 | clk_bulk_disable_unprepare(pd->num_clks, pd->clks); |
59b644b0 | 300 | |
1b18c055 HYW |
301 | scpsys_regulator_disable(pd->supply); |
302 | ||
59b644b0 EBS |
303 | return 0; |
304 | } | |
305 | ||
306 | static struct | |
307 | generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_node *node) | |
308 | { | |
309 | const struct scpsys_domain_data *domain_data; | |
310 | struct scpsys_domain *pd; | |
1b18c055 | 311 | struct device_node *root_node = scpsys->dev->of_node; |
eed6ff1b | 312 | struct device_node *smi_node; |
123e8b4f MB |
313 | struct property *prop; |
314 | const char *clk_name; | |
315 | int i, ret, num_clks; | |
316 | struct clk *clk; | |
317 | int clk_ind = 0; | |
59b644b0 EBS |
318 | u32 id; |
319 | ||
320 | ret = of_property_read_u32(node, "reg", &id); | |
321 | if (ret) { | |
322 | dev_err(scpsys->dev, "%pOF: failed to retrieve domain id from reg: %d\n", | |
323 | node, ret); | |
324 | return ERR_PTR(-EINVAL); | |
325 | } | |
326 | ||
327 | if (id >= scpsys->soc_data->num_domains) { | |
328 | dev_err(scpsys->dev, "%pOF: invalid domain id %d\n", node, id); | |
329 | return ERR_PTR(-EINVAL); | |
330 | } | |
331 | ||
332 | domain_data = &scpsys->soc_data->domains_data[id]; | |
333 | if (domain_data->sta_mask == 0) { | |
334 | dev_err(scpsys->dev, "%pOF: undefined domain id %d\n", node, id); | |
335 | return ERR_PTR(-EINVAL); | |
336 | } | |
337 | ||
338 | pd = devm_kzalloc(scpsys->dev, sizeof(*pd), GFP_KERNEL); | |
339 | if (!pd) | |
340 | return ERR_PTR(-ENOMEM); | |
341 | ||
342 | pd->data = domain_data; | |
343 | pd->scpsys = scpsys; | |
344 | ||
1b18c055 HYW |
345 | if (MTK_SCPD_CAPS(pd, MTK_SCPD_DOMAIN_SUPPLY)) { |
346 | /* | |
347 | * Find regulator in current power domain node. | |
348 | * devm_regulator_get() finds regulator in a node and its child | |
349 | * node, so set of_node to current power domain node then change | |
350 | * back to original node after regulator is found for current | |
351 | * power domain node. | |
352 | */ | |
353 | scpsys->dev->of_node = node; | |
354 | pd->supply = devm_regulator_get(scpsys->dev, "domain"); | |
355 | scpsys->dev->of_node = root_node; | |
356 | if (IS_ERR(pd->supply)) { | |
357 | dev_err_probe(scpsys->dev, PTR_ERR(pd->supply), | |
358 | "%pOF: failed to get power supply.\n", | |
359 | node); | |
360 | return ERR_CAST(pd->supply); | |
361 | } | |
362 | } | |
363 | ||
59b644b0 EBS |
364 | pd->infracfg = syscon_regmap_lookup_by_phandle_optional(node, "mediatek,infracfg"); |
365 | if (IS_ERR(pd->infracfg)) | |
366 | return ERR_CAST(pd->infracfg); | |
367 | ||
eed6ff1b HYW |
368 | smi_node = of_parse_phandle(node, "mediatek,smi", 0); |
369 | if (smi_node) { | |
370 | pd->smi = device_node_to_regmap(smi_node); | |
371 | of_node_put(smi_node); | |
372 | if (IS_ERR(pd->smi)) | |
373 | return ERR_CAST(pd->smi); | |
374 | } | |
f414854c | 375 | |
123e8b4f MB |
376 | num_clks = of_clk_get_parent_count(node); |
377 | if (num_clks > 0) { | |
378 | /* Calculate number of subsys_clks */ | |
379 | of_property_for_each_string(node, "clock-names", prop, clk_name) { | |
380 | char *subsys; | |
381 | ||
382 | subsys = strchr(clk_name, '-'); | |
383 | if (subsys) | |
384 | pd->num_subsys_clks++; | |
385 | else | |
386 | pd->num_clks++; | |
387 | } | |
388 | ||
59b644b0 EBS |
389 | pd->clks = devm_kcalloc(scpsys->dev, pd->num_clks, sizeof(*pd->clks), GFP_KERNEL); |
390 | if (!pd->clks) | |
391 | return ERR_PTR(-ENOMEM); | |
123e8b4f MB |
392 | |
393 | pd->subsys_clks = devm_kcalloc(scpsys->dev, pd->num_subsys_clks, | |
394 | sizeof(*pd->subsys_clks), GFP_KERNEL); | |
395 | if (!pd->subsys_clks) | |
396 | return ERR_PTR(-ENOMEM); | |
397 | ||
59b644b0 EBS |
398 | } |
399 | ||
400 | for (i = 0; i < pd->num_clks; i++) { | |
123e8b4f MB |
401 | clk = of_clk_get(node, i); |
402 | if (IS_ERR(clk)) { | |
403 | ret = PTR_ERR(clk); | |
404 | dev_err_probe(scpsys->dev, ret, | |
385ba16d | 405 | "%pOF: failed to get clk at index %d\n", node, i); |
123e8b4f | 406 | goto err_put_clocks; |
59b644b0 | 407 | } |
123e8b4f MB |
408 | |
409 | pd->clks[clk_ind++].clk = clk; | |
410 | } | |
411 | ||
412 | for (i = 0; i < pd->num_subsys_clks; i++) { | |
413 | clk = of_clk_get(node, i + clk_ind); | |
414 | if (IS_ERR(clk)) { | |
415 | ret = PTR_ERR(clk); | |
416 | dev_err_probe(scpsys->dev, ret, | |
385ba16d CJ |
417 | "%pOF: failed to get clk at index %d\n", node, |
418 | i + clk_ind); | |
123e8b4f MB |
419 | goto err_put_subsys_clocks; |
420 | } | |
421 | ||
422 | pd->subsys_clks[i].clk = clk; | |
59b644b0 EBS |
423 | } |
424 | ||
59b644b0 EBS |
425 | /* |
426 | * Initially turn on all domains to make the domains usable | |
427 | * with !CONFIG_PM and to get the hardware in sync with the | |
428 | * software. The unused domains will be switched off during | |
429 | * late_init time. | |
430 | */ | |
c1f3163d WL |
431 | if (MTK_SCPD_CAPS(pd, MTK_SCPD_KEEP_DEFAULT_OFF)) { |
432 | if (scpsys_domain_is_on(pd)) | |
433 | dev_warn(scpsys->dev, | |
434 | "%pOF: A default off power domain has been ON\n", node); | |
435 | } else { | |
436 | ret = scpsys_power_on(&pd->genpd); | |
437 | if (ret < 0) { | |
438 | dev_err(scpsys->dev, "%pOF: failed to power on domain: %d\n", node, ret); | |
f0fce06e | 439 | goto err_put_subsys_clocks; |
c1f3163d | 440 | } |
72be1e7a CY |
441 | |
442 | if (MTK_SCPD_CAPS(pd, MTK_SCPD_ALWAYS_ON)) | |
443 | pd->genpd.flags |= GENPD_FLAG_ALWAYS_ON; | |
59b644b0 EBS |
444 | } |
445 | ||
446 | if (scpsys->domains[id]) { | |
447 | ret = -EINVAL; | |
448 | dev_err(scpsys->dev, | |
449 | "power domain with id %d already exists, check your device-tree\n", id); | |
f0fce06e | 450 | goto err_put_subsys_clocks; |
59b644b0 EBS |
451 | } |
452 | ||
022b02b4 EBS |
453 | if (!pd->data->name) |
454 | pd->genpd.name = node->name; | |
455 | else | |
456 | pd->genpd.name = pd->data->name; | |
457 | ||
59b644b0 EBS |
458 | pd->genpd.power_off = scpsys_power_off; |
459 | pd->genpd.power_on = scpsys_power_on; | |
460 | ||
ac0ca395 CJC |
461 | if (MTK_SCPD_CAPS(pd, MTK_SCPD_ACTIVE_WAKEUP)) |
462 | pd->genpd.flags |= GENPD_FLAG_ACTIVE_WAKEUP; | |
463 | ||
c1f3163d WL |
464 | if (MTK_SCPD_CAPS(pd, MTK_SCPD_KEEP_DEFAULT_OFF)) |
465 | pm_genpd_init(&pd->genpd, NULL, true); | |
466 | else | |
467 | pm_genpd_init(&pd->genpd, NULL, false); | |
468 | ||
59b644b0 EBS |
469 | scpsys->domains[id] = &pd->genpd; |
470 | ||
471 | return scpsys->pd_data.domains[id]; | |
472 | ||
123e8b4f MB |
473 | err_put_subsys_clocks: |
474 | clk_bulk_put(pd->num_subsys_clks, pd->subsys_clks); | |
59b644b0 EBS |
475 | err_put_clocks: |
476 | clk_bulk_put(pd->num_clks, pd->clks); | |
477 | return ERR_PTR(ret); | |
478 | } | |
479 | ||
480 | static int scpsys_add_subdomain(struct scpsys *scpsys, struct device_node *parent) | |
481 | { | |
482 | struct generic_pm_domain *child_pd, *parent_pd; | |
483 | struct device_node *child; | |
484 | int ret; | |
485 | ||
486 | for_each_child_of_node(parent, child) { | |
487 | u32 id; | |
488 | ||
489 | ret = of_property_read_u32(parent, "reg", &id); | |
490 | if (ret) { | |
491 | dev_err(scpsys->dev, "%pOF: failed to get parent domain id\n", child); | |
492 | goto err_put_node; | |
493 | } | |
494 | ||
495 | if (!scpsys->pd_data.domains[id]) { | |
496 | ret = -EINVAL; | |
497 | dev_err(scpsys->dev, "power domain with id %d does not exist\n", id); | |
498 | goto err_put_node; | |
499 | } | |
500 | ||
501 | parent_pd = scpsys->pd_data.domains[id]; | |
502 | ||
503 | child_pd = scpsys_add_one_domain(scpsys, child); | |
504 | if (IS_ERR(child_pd)) { | |
9950588a EBS |
505 | ret = PTR_ERR(child_pd); |
506 | dev_err_probe(scpsys->dev, ret, "%pOF: failed to get child domain id\n", | |
507 | child); | |
59b644b0 EBS |
508 | goto err_put_node; |
509 | } | |
510 | ||
511 | ret = pm_genpd_add_subdomain(parent_pd, child_pd); | |
512 | if (ret) { | |
513 | dev_err(scpsys->dev, "failed to add %s subdomain to parent %s\n", | |
514 | child_pd->name, parent_pd->name); | |
515 | goto err_put_node; | |
516 | } else { | |
517 | dev_dbg(scpsys->dev, "%s add subdomain: %s\n", parent_pd->name, | |
518 | child_pd->name); | |
519 | } | |
520 | ||
521 | /* recursive call to add all subdomains */ | |
522 | ret = scpsys_add_subdomain(scpsys, child); | |
523 | if (ret) | |
524 | goto err_put_node; | |
525 | } | |
526 | ||
527 | return 0; | |
528 | ||
529 | err_put_node: | |
530 | of_node_put(child); | |
531 | return ret; | |
532 | } | |
533 | ||
534 | static void scpsys_remove_one_domain(struct scpsys_domain *pd) | |
535 | { | |
536 | int ret; | |
537 | ||
c1f3163d WL |
538 | if (scpsys_domain_is_on(pd)) |
539 | scpsys_power_off(&pd->genpd); | |
540 | ||
59b644b0 EBS |
541 | /* |
542 | * We're in the error cleanup already, so we only complain, | |
543 | * but won't emit another error on top of the original one. | |
544 | */ | |
545 | ret = pm_genpd_remove(&pd->genpd); | |
546 | if (ret < 0) | |
547 | dev_err(pd->scpsys->dev, | |
548 | "failed to remove domain '%s' : %d - state may be inconsistent\n", | |
549 | pd->genpd.name, ret); | |
550 | ||
59b644b0 | 551 | clk_bulk_put(pd->num_clks, pd->clks); |
123e8b4f | 552 | clk_bulk_put(pd->num_subsys_clks, pd->subsys_clks); |
59b644b0 EBS |
553 | } |
554 | ||
555 | static void scpsys_domain_cleanup(struct scpsys *scpsys) | |
556 | { | |
557 | struct generic_pm_domain *genpd; | |
558 | struct scpsys_domain *pd; | |
559 | int i; | |
560 | ||
561 | for (i = scpsys->pd_data.num_domains - 1; i >= 0; i--) { | |
562 | genpd = scpsys->pd_data.domains[i]; | |
563 | if (genpd) { | |
564 | pd = to_scpsys_domain(genpd); | |
565 | scpsys_remove_one_domain(pd); | |
566 | } | |
567 | } | |
568 | } | |
569 | ||
570 | static const struct of_device_id scpsys_of_match[] = { | |
2b5764fb ADR |
571 | { |
572 | .compatible = "mediatek,mt6795-power-controller", | |
573 | .data = &mt6795_scpsys_data, | |
574 | }, | |
207f13b4 FP |
575 | { |
576 | .compatible = "mediatek,mt8167-power-controller", | |
577 | .data = &mt8167_scpsys_data, | |
578 | }, | |
59b644b0 EBS |
579 | { |
580 | .compatible = "mediatek,mt8173-power-controller", | |
581 | .data = &mt8173_scpsys_data, | |
582 | }, | |
eb9fa767 MB |
583 | { |
584 | .compatible = "mediatek,mt8183-power-controller", | |
585 | .data = &mt8183_scpsys_data, | |
586 | }, | |
88590cbc CJC |
587 | { |
588 | .compatible = "mediatek,mt8186-power-controller", | |
589 | .data = &mt8186_scpsys_data, | |
590 | }, | |
e610e814 GC |
591 | { |
592 | .compatible = "mediatek,mt8188-power-controller", | |
593 | .data = &mt8188_scpsys_data, | |
594 | }, | |
a49d5e7a WL |
595 | { |
596 | .compatible = "mediatek,mt8192-power-controller", | |
597 | .data = &mt8192_scpsys_data, | |
598 | }, | |
342479c8 CJC |
599 | { |
600 | .compatible = "mediatek,mt8195-power-controller", | |
601 | .data = &mt8195_scpsys_data, | |
602 | }, | |
59b644b0 EBS |
603 | { } |
604 | }; | |
605 | ||
606 | static int scpsys_probe(struct platform_device *pdev) | |
607 | { | |
608 | struct device *dev = &pdev->dev; | |
609 | struct device_node *np = dev->of_node; | |
610 | const struct scpsys_soc_data *soc; | |
611 | struct device_node *node; | |
612 | struct device *parent; | |
613 | struct scpsys *scpsys; | |
614 | int ret; | |
615 | ||
616 | soc = of_device_get_match_data(&pdev->dev); | |
617 | if (!soc) { | |
618 | dev_err(&pdev->dev, "no power controller data\n"); | |
619 | return -EINVAL; | |
620 | } | |
621 | ||
622 | scpsys = devm_kzalloc(dev, struct_size(scpsys, domains, soc->num_domains), GFP_KERNEL); | |
623 | if (!scpsys) | |
624 | return -ENOMEM; | |
625 | ||
626 | scpsys->dev = dev; | |
627 | scpsys->soc_data = soc; | |
628 | ||
629 | scpsys->pd_data.domains = scpsys->domains; | |
630 | scpsys->pd_data.num_domains = soc->num_domains; | |
631 | ||
632 | parent = dev->parent; | |
633 | if (!parent) { | |
634 | dev_err(dev, "no parent for syscon devices\n"); | |
635 | return -ENODEV; | |
636 | } | |
637 | ||
638 | scpsys->base = syscon_node_to_regmap(parent->of_node); | |
639 | if (IS_ERR(scpsys->base)) { | |
640 | dev_err(dev, "no regmap available\n"); | |
641 | return PTR_ERR(scpsys->base); | |
642 | } | |
643 | ||
644 | ret = -ENODEV; | |
645 | for_each_available_child_of_node(np, node) { | |
646 | struct generic_pm_domain *domain; | |
647 | ||
648 | domain = scpsys_add_one_domain(scpsys, node); | |
649 | if (IS_ERR(domain)) { | |
650 | ret = PTR_ERR(domain); | |
651 | of_node_put(node); | |
652 | goto err_cleanup_domains; | |
653 | } | |
654 | ||
655 | ret = scpsys_add_subdomain(scpsys, node); | |
656 | if (ret) { | |
657 | of_node_put(node); | |
658 | goto err_cleanup_domains; | |
659 | } | |
660 | } | |
661 | ||
662 | if (ret) { | |
663 | dev_dbg(dev, "no power domains present\n"); | |
664 | return ret; | |
665 | } | |
666 | ||
667 | ret = of_genpd_add_provider_onecell(np, &scpsys->pd_data); | |
668 | if (ret) { | |
669 | dev_err(dev, "failed to add provider: %d\n", ret); | |
670 | goto err_cleanup_domains; | |
671 | } | |
672 | ||
673 | return 0; | |
674 | ||
675 | err_cleanup_domains: | |
676 | scpsys_domain_cleanup(scpsys); | |
677 | return ret; | |
678 | } | |
679 | ||
680 | static struct platform_driver scpsys_pm_domain_driver = { | |
681 | .probe = scpsys_probe, | |
682 | .driver = { | |
683 | .name = "mtk-power-controller", | |
684 | .suppress_bind_attrs = true, | |
685 | .of_match_table = scpsys_of_match, | |
686 | }, | |
687 | }; | |
688 | builtin_platform_driver(scpsys_pm_domain_driver); |