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