diff options
Diffstat (limited to 'drivers/soc/mediatek/mtk-scpsys.c')
-rw-r--r-- | drivers/soc/mediatek/mtk-scpsys.c | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/drivers/soc/mediatek/mtk-scpsys.c b/drivers/soc/mediatek/mtk-scpsys.c new file mode 100644 index 000000000000..43a79ed761c4 --- /dev/null +++ b/drivers/soc/mediatek/mtk-scpsys.c | |||
@@ -0,0 +1,487 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2015 Pengutronix, Sascha Hauer <kernel@pengutronix.de> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License version 2 as | ||
6 | * published by the Free Software Foundation. | ||
7 | * | ||
8 | * This program is distributed in the hope that it will be useful, | ||
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
11 | * GNU General Public License for more details. | ||
12 | */ | ||
13 | #include <linux/clk.h> | ||
14 | #include <linux/delay.h> | ||
15 | #include <linux/io.h> | ||
16 | #include <linux/kernel.h> | ||
17 | #include <linux/mfd/syscon.h> | ||
18 | #include <linux/of_device.h> | ||
19 | #include <linux/platform_device.h> | ||
20 | #include <linux/pm_domain.h> | ||
21 | #include <linux/regmap.h> | ||
22 | #include <linux/soc/mediatek/infracfg.h> | ||
23 | #include <dt-bindings/power/mt8173-power.h> | ||
24 | |||
25 | #define SPM_VDE_PWR_CON 0x0210 | ||
26 | #define SPM_MFG_PWR_CON 0x0214 | ||
27 | #define SPM_VEN_PWR_CON 0x0230 | ||
28 | #define SPM_ISP_PWR_CON 0x0238 | ||
29 | #define SPM_DIS_PWR_CON 0x023c | ||
30 | #define SPM_VEN2_PWR_CON 0x0298 | ||
31 | #define SPM_AUDIO_PWR_CON 0x029c | ||
32 | #define SPM_MFG_2D_PWR_CON 0x02c0 | ||
33 | #define SPM_MFG_ASYNC_PWR_CON 0x02c4 | ||
34 | #define SPM_USB_PWR_CON 0x02cc | ||
35 | #define SPM_PWR_STATUS 0x060c | ||
36 | #define SPM_PWR_STATUS_2ND 0x0610 | ||
37 | |||
38 | #define PWR_RST_B_BIT BIT(0) | ||
39 | #define PWR_ISO_BIT BIT(1) | ||
40 | #define PWR_ON_BIT BIT(2) | ||
41 | #define PWR_ON_2ND_BIT BIT(3) | ||
42 | #define PWR_CLK_DIS_BIT BIT(4) | ||
43 | |||
44 | #define PWR_STATUS_DISP BIT(3) | ||
45 | #define PWR_STATUS_MFG BIT(4) | ||
46 | #define PWR_STATUS_ISP BIT(5) | ||
47 | #define PWR_STATUS_VDEC BIT(7) | ||
48 | #define PWR_STATUS_VENC_LT BIT(20) | ||
49 | #define PWR_STATUS_VENC BIT(21) | ||
50 | #define PWR_STATUS_MFG_2D BIT(22) | ||
51 | #define PWR_STATUS_MFG_ASYNC BIT(23) | ||
52 | #define PWR_STATUS_AUDIO BIT(24) | ||
53 | #define PWR_STATUS_USB BIT(25) | ||
54 | |||
55 | enum clk_id { | ||
56 | MT8173_CLK_MM, | ||
57 | MT8173_CLK_MFG, | ||
58 | MT8173_CLK_NONE, | ||
59 | MT8173_CLK_MAX = MT8173_CLK_NONE, | ||
60 | }; | ||
61 | |||
62 | struct scp_domain_data { | ||
63 | const char *name; | ||
64 | u32 sta_mask; | ||
65 | int ctl_offs; | ||
66 | u32 sram_pdn_bits; | ||
67 | u32 sram_pdn_ack_bits; | ||
68 | u32 bus_prot_mask; | ||
69 | enum clk_id clk_id; | ||
70 | }; | ||
71 | |||
72 | static const struct scp_domain_data scp_domain_data[] __initconst = { | ||
73 | [MT8173_POWER_DOMAIN_VDEC] = { | ||
74 | .name = "vdec", | ||
75 | .sta_mask = PWR_STATUS_VDEC, | ||
76 | .ctl_offs = SPM_VDE_PWR_CON, | ||
77 | .sram_pdn_bits = GENMASK(11, 8), | ||
78 | .sram_pdn_ack_bits = GENMASK(12, 12), | ||
79 | .clk_id = MT8173_CLK_MM, | ||
80 | }, | ||
81 | [MT8173_POWER_DOMAIN_VENC] = { | ||
82 | .name = "venc", | ||
83 | .sta_mask = PWR_STATUS_VENC, | ||
84 | .ctl_offs = SPM_VEN_PWR_CON, | ||
85 | .sram_pdn_bits = GENMASK(11, 8), | ||
86 | .sram_pdn_ack_bits = GENMASK(15, 12), | ||
87 | .clk_id = MT8173_CLK_MM, | ||
88 | }, | ||
89 | [MT8173_POWER_DOMAIN_ISP] = { | ||
90 | .name = "isp", | ||
91 | .sta_mask = PWR_STATUS_ISP, | ||
92 | .ctl_offs = SPM_ISP_PWR_CON, | ||
93 | .sram_pdn_bits = GENMASK(11, 8), | ||
94 | .sram_pdn_ack_bits = GENMASK(13, 12), | ||
95 | .clk_id = MT8173_CLK_MM, | ||
96 | }, | ||
97 | [MT8173_POWER_DOMAIN_MM] = { | ||
98 | .name = "mm", | ||
99 | .sta_mask = PWR_STATUS_DISP, | ||
100 | .ctl_offs = SPM_DIS_PWR_CON, | ||
101 | .sram_pdn_bits = GENMASK(11, 8), | ||
102 | .sram_pdn_ack_bits = GENMASK(12, 12), | ||
103 | .clk_id = MT8173_CLK_MM, | ||
104 | .bus_prot_mask = MT8173_TOP_AXI_PROT_EN_MM_M0 | | ||
105 | MT8173_TOP_AXI_PROT_EN_MM_M1, | ||
106 | }, | ||
107 | [MT8173_POWER_DOMAIN_VENC_LT] = { | ||
108 | .name = "venc_lt", | ||
109 | .sta_mask = PWR_STATUS_VENC_LT, | ||
110 | .ctl_offs = SPM_VEN2_PWR_CON, | ||
111 | .sram_pdn_bits = GENMASK(11, 8), | ||
112 | .sram_pdn_ack_bits = GENMASK(15, 12), | ||
113 | .clk_id = MT8173_CLK_MM, | ||
114 | }, | ||
115 | [MT8173_POWER_DOMAIN_AUDIO] = { | ||
116 | .name = "audio", | ||
117 | .sta_mask = PWR_STATUS_AUDIO, | ||
118 | .ctl_offs = SPM_AUDIO_PWR_CON, | ||
119 | .sram_pdn_bits = GENMASK(11, 8), | ||
120 | .sram_pdn_ack_bits = GENMASK(15, 12), | ||
121 | .clk_id = MT8173_CLK_NONE, | ||
122 | }, | ||
123 | [MT8173_POWER_DOMAIN_USB] = { | ||
124 | .name = "usb", | ||
125 | .sta_mask = PWR_STATUS_USB, | ||
126 | .ctl_offs = SPM_USB_PWR_CON, | ||
127 | .sram_pdn_bits = GENMASK(11, 8), | ||
128 | .sram_pdn_ack_bits = GENMASK(15, 12), | ||
129 | .clk_id = MT8173_CLK_NONE, | ||
130 | }, | ||
131 | [MT8173_POWER_DOMAIN_MFG_ASYNC] = { | ||
132 | .name = "mfg_async", | ||
133 | .sta_mask = PWR_STATUS_MFG_ASYNC, | ||
134 | .ctl_offs = SPM_MFG_ASYNC_PWR_CON, | ||
135 | .sram_pdn_bits = GENMASK(11, 8), | ||
136 | .sram_pdn_ack_bits = 0, | ||
137 | .clk_id = MT8173_CLK_MFG, | ||
138 | }, | ||
139 | [MT8173_POWER_DOMAIN_MFG_2D] = { | ||
140 | .name = "mfg_2d", | ||
141 | .sta_mask = PWR_STATUS_MFG_2D, | ||
142 | .ctl_offs = SPM_MFG_2D_PWR_CON, | ||
143 | .sram_pdn_bits = GENMASK(11, 8), | ||
144 | .sram_pdn_ack_bits = GENMASK(13, 12), | ||
145 | .clk_id = MT8173_CLK_NONE, | ||
146 | }, | ||
147 | [MT8173_POWER_DOMAIN_MFG] = { | ||
148 | .name = "mfg", | ||
149 | .sta_mask = PWR_STATUS_MFG, | ||
150 | .ctl_offs = SPM_MFG_PWR_CON, | ||
151 | .sram_pdn_bits = GENMASK(13, 8), | ||
152 | .sram_pdn_ack_bits = GENMASK(21, 16), | ||
153 | .clk_id = MT8173_CLK_NONE, | ||
154 | .bus_prot_mask = MT8173_TOP_AXI_PROT_EN_MFG_S | | ||
155 | MT8173_TOP_AXI_PROT_EN_MFG_M0 | | ||
156 | MT8173_TOP_AXI_PROT_EN_MFG_M1 | | ||
157 | MT8173_TOP_AXI_PROT_EN_MFG_SNOOP_OUT, | ||
158 | }, | ||
159 | }; | ||
160 | |||
161 | #define NUM_DOMAINS ARRAY_SIZE(scp_domain_data) | ||
162 | |||
163 | struct scp; | ||
164 | |||
165 | struct scp_domain { | ||
166 | struct generic_pm_domain genpd; | ||
167 | struct scp *scp; | ||
168 | struct clk *clk; | ||
169 | u32 sta_mask; | ||
170 | void __iomem *ctl_addr; | ||
171 | u32 sram_pdn_bits; | ||
172 | u32 sram_pdn_ack_bits; | ||
173 | u32 bus_prot_mask; | ||
174 | }; | ||
175 | |||
176 | struct scp { | ||
177 | struct scp_domain domains[NUM_DOMAINS]; | ||
178 | struct genpd_onecell_data pd_data; | ||
179 | struct device *dev; | ||
180 | void __iomem *base; | ||
181 | struct regmap *infracfg; | ||
182 | }; | ||
183 | |||
184 | static int scpsys_domain_is_on(struct scp_domain *scpd) | ||
185 | { | ||
186 | struct scp *scp = scpd->scp; | ||
187 | |||
188 | u32 status = readl(scp->base + SPM_PWR_STATUS) & scpd->sta_mask; | ||
189 | u32 status2 = readl(scp->base + SPM_PWR_STATUS_2ND) & scpd->sta_mask; | ||
190 | |||
191 | /* | ||
192 | * A domain is on when both status bits are set. If only one is set | ||
193 | * return an error. This happens while powering up a domain | ||
194 | */ | ||
195 | |||
196 | if (status && status2) | ||
197 | return true; | ||
198 | if (!status && !status2) | ||
199 | return false; | ||
200 | |||
201 | return -EINVAL; | ||
202 | } | ||
203 | |||
204 | static int scpsys_power_on(struct generic_pm_domain *genpd) | ||
205 | { | ||
206 | struct scp_domain *scpd = container_of(genpd, struct scp_domain, genpd); | ||
207 | struct scp *scp = scpd->scp; | ||
208 | unsigned long timeout; | ||
209 | bool expired; | ||
210 | void __iomem *ctl_addr = scpd->ctl_addr; | ||
211 | u32 sram_pdn_ack = scpd->sram_pdn_ack_bits; | ||
212 | u32 val; | ||
213 | int ret; | ||
214 | |||
215 | if (scpd->clk) { | ||
216 | ret = clk_prepare_enable(scpd->clk); | ||
217 | if (ret) | ||
218 | goto err_clk; | ||
219 | } | ||
220 | |||
221 | val = readl(ctl_addr); | ||
222 | val |= PWR_ON_BIT; | ||
223 | writel(val, ctl_addr); | ||
224 | val |= PWR_ON_2ND_BIT; | ||
225 | writel(val, ctl_addr); | ||
226 | |||
227 | /* wait until PWR_ACK = 1 */ | ||
228 | timeout = jiffies + HZ; | ||
229 | expired = false; | ||
230 | while (1) { | ||
231 | ret = scpsys_domain_is_on(scpd); | ||
232 | if (ret > 0) | ||
233 | break; | ||
234 | |||
235 | if (expired) { | ||
236 | ret = -ETIMEDOUT; | ||
237 | goto err_pwr_ack; | ||
238 | } | ||
239 | |||
240 | cpu_relax(); | ||
241 | |||
242 | if (time_after(jiffies, timeout)) | ||
243 | expired = true; | ||
244 | } | ||
245 | |||
246 | val &= ~PWR_CLK_DIS_BIT; | ||
247 | writel(val, ctl_addr); | ||
248 | |||
249 | val &= ~PWR_ISO_BIT; | ||
250 | writel(val, ctl_addr); | ||
251 | |||
252 | val |= PWR_RST_B_BIT; | ||
253 | writel(val, ctl_addr); | ||
254 | |||
255 | val &= ~scpd->sram_pdn_bits; | ||
256 | writel(val, ctl_addr); | ||
257 | |||
258 | /* wait until SRAM_PDN_ACK all 0 */ | ||
259 | timeout = jiffies + HZ; | ||
260 | expired = false; | ||
261 | while (sram_pdn_ack && (readl(ctl_addr) & sram_pdn_ack)) { | ||
262 | |||
263 | if (expired) { | ||
264 | ret = -ETIMEDOUT; | ||
265 | goto err_pwr_ack; | ||
266 | } | ||
267 | |||
268 | cpu_relax(); | ||
269 | |||
270 | if (time_after(jiffies, timeout)) | ||
271 | expired = true; | ||
272 | } | ||
273 | |||
274 | if (scpd->bus_prot_mask) { | ||
275 | ret = mtk_infracfg_clear_bus_protection(scp->infracfg, | ||
276 | scpd->bus_prot_mask); | ||
277 | if (ret) | ||
278 | goto err_pwr_ack; | ||
279 | } | ||
280 | |||
281 | return 0; | ||
282 | |||
283 | err_pwr_ack: | ||
284 | clk_disable_unprepare(scpd->clk); | ||
285 | err_clk: | ||
286 | dev_err(scp->dev, "Failed to power on domain %s\n", genpd->name); | ||
287 | |||
288 | return ret; | ||
289 | } | ||
290 | |||
291 | static int scpsys_power_off(struct generic_pm_domain *genpd) | ||
292 | { | ||
293 | struct scp_domain *scpd = container_of(genpd, struct scp_domain, genpd); | ||
294 | struct scp *scp = scpd->scp; | ||
295 | unsigned long timeout; | ||
296 | bool expired; | ||
297 | void __iomem *ctl_addr = scpd->ctl_addr; | ||
298 | u32 pdn_ack = scpd->sram_pdn_ack_bits; | ||
299 | u32 val; | ||
300 | int ret; | ||
301 | |||
302 | if (scpd->bus_prot_mask) { | ||
303 | ret = mtk_infracfg_set_bus_protection(scp->infracfg, | ||
304 | scpd->bus_prot_mask); | ||
305 | if (ret) | ||
306 | goto out; | ||
307 | } | ||
308 | |||
309 | val = readl(ctl_addr); | ||
310 | val |= scpd->sram_pdn_bits; | ||
311 | writel(val, ctl_addr); | ||
312 | |||
313 | /* wait until SRAM_PDN_ACK all 1 */ | ||
314 | timeout = jiffies + HZ; | ||
315 | expired = false; | ||
316 | while (pdn_ack && (readl(ctl_addr) & pdn_ack) != pdn_ack) { | ||
317 | if (expired) { | ||
318 | ret = -ETIMEDOUT; | ||
319 | goto out; | ||
320 | } | ||
321 | |||
322 | cpu_relax(); | ||
323 | |||
324 | if (time_after(jiffies, timeout)) | ||
325 | expired = true; | ||
326 | } | ||
327 | |||
328 | val |= PWR_ISO_BIT; | ||
329 | writel(val, ctl_addr); | ||
330 | |||
331 | val &= ~PWR_RST_B_BIT; | ||
332 | writel(val, ctl_addr); | ||
333 | |||
334 | val |= PWR_CLK_DIS_BIT; | ||
335 | writel(val, ctl_addr); | ||
336 | |||
337 | val &= ~PWR_ON_BIT; | ||
338 | writel(val, ctl_addr); | ||
339 | |||
340 | val &= ~PWR_ON_2ND_BIT; | ||
341 | writel(val, ctl_addr); | ||
342 | |||
343 | /* wait until PWR_ACK = 0 */ | ||
344 | timeout = jiffies + HZ; | ||
345 | expired = false; | ||
346 | while (1) { | ||
347 | ret = scpsys_domain_is_on(scpd); | ||
348 | if (ret == 0) | ||
349 | break; | ||
350 | |||
351 | if (expired) { | ||
352 | ret = -ETIMEDOUT; | ||
353 | goto out; | ||
354 | } | ||
355 | |||
356 | cpu_relax(); | ||
357 | |||
358 | if (time_after(jiffies, timeout)) | ||
359 | expired = true; | ||
360 | } | ||
361 | |||
362 | if (scpd->clk) | ||
363 | clk_disable_unprepare(scpd->clk); | ||
364 | |||
365 | return 0; | ||
366 | |||
367 | out: | ||
368 | dev_err(scp->dev, "Failed to power off domain %s\n", genpd->name); | ||
369 | |||
370 | return ret; | ||
371 | } | ||
372 | |||
373 | static int __init scpsys_probe(struct platform_device *pdev) | ||
374 | { | ||
375 | struct genpd_onecell_data *pd_data; | ||
376 | struct resource *res; | ||
377 | int i, ret; | ||
378 | struct scp *scp; | ||
379 | struct clk *clk[MT8173_CLK_MAX]; | ||
380 | |||
381 | scp = devm_kzalloc(&pdev->dev, sizeof(*scp), GFP_KERNEL); | ||
382 | if (!scp) | ||
383 | return -ENOMEM; | ||
384 | |||
385 | scp->dev = &pdev->dev; | ||
386 | |||
387 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
388 | scp->base = devm_ioremap_resource(&pdev->dev, res); | ||
389 | if (IS_ERR(scp->base)) | ||
390 | return PTR_ERR(scp->base); | ||
391 | |||
392 | pd_data = &scp->pd_data; | ||
393 | |||
394 | pd_data->domains = devm_kzalloc(&pdev->dev, | ||
395 | sizeof(*pd_data->domains) * NUM_DOMAINS, GFP_KERNEL); | ||
396 | if (!pd_data->domains) | ||
397 | return -ENOMEM; | ||
398 | |||
399 | clk[MT8173_CLK_MM] = devm_clk_get(&pdev->dev, "mm"); | ||
400 | if (IS_ERR(clk[MT8173_CLK_MM])) | ||
401 | return PTR_ERR(clk[MT8173_CLK_MM]); | ||
402 | |||
403 | clk[MT8173_CLK_MFG] = devm_clk_get(&pdev->dev, "mfg"); | ||
404 | if (IS_ERR(clk[MT8173_CLK_MFG])) | ||
405 | return PTR_ERR(clk[MT8173_CLK_MFG]); | ||
406 | |||
407 | scp->infracfg = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, | ||
408 | "infracfg"); | ||
409 | if (IS_ERR(scp->infracfg)) { | ||
410 | dev_err(&pdev->dev, "Cannot find infracfg controller: %ld\n", | ||
411 | PTR_ERR(scp->infracfg)); | ||
412 | return PTR_ERR(scp->infracfg); | ||
413 | } | ||
414 | |||
415 | pd_data->num_domains = NUM_DOMAINS; | ||
416 | |||
417 | for (i = 0; i < NUM_DOMAINS; i++) { | ||
418 | struct scp_domain *scpd = &scp->domains[i]; | ||
419 | struct generic_pm_domain *genpd = &scpd->genpd; | ||
420 | const struct scp_domain_data *data = &scp_domain_data[i]; | ||
421 | |||
422 | pd_data->domains[i] = genpd; | ||
423 | scpd->scp = scp; | ||
424 | |||
425 | scpd->sta_mask = data->sta_mask; | ||
426 | scpd->ctl_addr = scp->base + data->ctl_offs; | ||
427 | scpd->sram_pdn_bits = data->sram_pdn_bits; | ||
428 | scpd->sram_pdn_ack_bits = data->sram_pdn_ack_bits; | ||
429 | scpd->bus_prot_mask = data->bus_prot_mask; | ||
430 | if (data->clk_id != MT8173_CLK_NONE) | ||
431 | scpd->clk = clk[data->clk_id]; | ||
432 | |||
433 | genpd->name = data->name; | ||
434 | genpd->power_off = scpsys_power_off; | ||
435 | genpd->power_on = scpsys_power_on; | ||
436 | |||
437 | /* | ||
438 | * Initially turn on all domains to make the domains usable | ||
439 | * with !CONFIG_PM and to get the hardware in sync with the | ||
440 | * software. The unused domains will be switched off during | ||
441 | * late_init time. | ||
442 | */ | ||
443 | genpd->power_on(genpd); | ||
444 | |||
445 | pm_genpd_init(genpd, NULL, false); | ||
446 | } | ||
447 | |||
448 | /* | ||
449 | * We are not allowed to fail here since there is no way to unregister | ||
450 | * a power domain. Once registered above we have to keep the domains | ||
451 | * valid. | ||
452 | */ | ||
453 | |||
454 | ret = pm_genpd_add_subdomain(pd_data->domains[MT8173_POWER_DOMAIN_MFG_ASYNC], | ||
455 | pd_data->domains[MT8173_POWER_DOMAIN_MFG_2D]); | ||
456 | if (ret && IS_ENABLED(CONFIG_PM)) | ||
457 | dev_err(&pdev->dev, "Failed to add subdomain: %d\n", ret); | ||
458 | |||
459 | ret = pm_genpd_add_subdomain(pd_data->domains[MT8173_POWER_DOMAIN_MFG_2D], | ||
460 | pd_data->domains[MT8173_POWER_DOMAIN_MFG]); | ||
461 | if (ret && IS_ENABLED(CONFIG_PM)) | ||
462 | dev_err(&pdev->dev, "Failed to add subdomain: %d\n", ret); | ||
463 | |||
464 | ret = of_genpd_add_provider_onecell(pdev->dev.of_node, pd_data); | ||
465 | if (ret) | ||
466 | dev_err(&pdev->dev, "Failed to add OF provider: %d\n", ret); | ||
467 | |||
468 | return 0; | ||
469 | } | ||
470 | |||
471 | static const struct of_device_id of_scpsys_match_tbl[] = { | ||
472 | { | ||
473 | .compatible = "mediatek,mt8173-scpsys", | ||
474 | }, { | ||
475 | /* sentinel */ | ||
476 | } | ||
477 | }; | ||
478 | |||
479 | static struct platform_driver scpsys_drv = { | ||
480 | .driver = { | ||
481 | .name = "mtk-scpsys", | ||
482 | .owner = THIS_MODULE, | ||
483 | .of_match_table = of_match_ptr(of_scpsys_match_tbl), | ||
484 | }, | ||
485 | }; | ||
486 | |||
487 | module_platform_driver_probe(scpsys_drv, scpsys_probe); | ||