aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/soc/mediatek
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2015-06-24 02:17:04 -0400
committerMatthias Brugger <matthias.bgg@gmail.com>2015-07-06 12:36:32 -0400
commitc84e358718a66f76ac0de1681d15d8d0c68fcdab (patch)
tree6135dd6b9e8d60033a5bf088017472f0033ec85b /drivers/soc/mediatek
parent859e42800bcfc4db9cefaa2c24d6e3a203fe961d (diff)
soc: Mediatek: Add SCPSYS power domain driver
This adds a power domain driver for the Mediatek SCPSYS unit. The System Control Processor System (SCPSYS) has several power management related tasks in the system. The tasks include thermal measurement, dynamic voltage frequency scaling (DVFS), interrupt filter and lowlevel sleep control. The System Power Manager (SPM) inside the SCPSYS is for the MTCMOS power domain control. For now this driver only adds power domain support, the more advanced features are not yet supported. The driver implements the generic PM domain device tree bindings, the first user will most likely be the Mediatek AFE audio driver. Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de> Reviewed-by: Daniel Kurtz <djkurtz@chromium.org> Signed-off-by: Matthias Brugger <matthias.bgg@gmail.com>
Diffstat (limited to 'drivers/soc/mediatek')
-rw-r--r--drivers/soc/mediatek/Kconfig10
-rw-r--r--drivers/soc/mediatek/Makefile1
-rw-r--r--drivers/soc/mediatek/mtk-scpsys.c487
3 files changed, 498 insertions, 0 deletions
diff --git a/drivers/soc/mediatek/Kconfig b/drivers/soc/mediatek/Kconfig
index e609a6f5e2eb..9d5068248aa0 100644
--- a/drivers/soc/mediatek/Kconfig
+++ b/drivers/soc/mediatek/Kconfig
@@ -19,3 +19,13 @@ config MTK_PMIC_WRAP
19 Say yes here to add support for MediaTek PMIC Wrapper found 19 Say yes here to add support for MediaTek PMIC Wrapper found
20 on different MediaTek SoCs. The PMIC wrapper is a proprietary 20 on different MediaTek SoCs. The PMIC wrapper is a proprietary
21 hardware to connect the PMIC. 21 hardware to connect the PMIC.
22
23config MTK_SCPSYS
24 bool "MediaTek SCPSYS Support"
25 depends on ARCH_MEDIATEK || COMPILE_TEST
26 select REGMAP
27 select MTK_INFRACFG
28 select PM_GENERIC_DOMAINS if PM
29 help
30 Say yes here to add support for the MediaTek SCPSYS power domain
31 driver.
diff --git a/drivers/soc/mediatek/Makefile b/drivers/soc/mediatek/Makefile
index 3fa940fb4eab..12998b08819e 100644
--- a/drivers/soc/mediatek/Makefile
+++ b/drivers/soc/mediatek/Makefile
@@ -1,2 +1,3 @@
1obj-$(CONFIG_MTK_INFRACFG) += mtk-infracfg.o 1obj-$(CONFIG_MTK_INFRACFG) += mtk-infracfg.o
2obj-$(CONFIG_MTK_PMIC_WRAP) += mtk-pmic-wrap.o 2obj-$(CONFIG_MTK_PMIC_WRAP) += mtk-pmic-wrap.o
3obj-$(CONFIG_MTK_SCPSYS) += mtk-scpsys.o
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
55enum clk_id {
56 MT8173_CLK_MM,
57 MT8173_CLK_MFG,
58 MT8173_CLK_NONE,
59 MT8173_CLK_MAX = MT8173_CLK_NONE,
60};
61
62struct 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
72static 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
163struct scp;
164
165struct 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
176struct 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
184static 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
204static 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
283err_pwr_ack:
284 clk_disable_unprepare(scpd->clk);
285err_clk:
286 dev_err(scp->dev, "Failed to power on domain %s\n", genpd->name);
287
288 return ret;
289}
290
291static 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
367out:
368 dev_err(scp->dev, "Failed to power off domain %s\n", genpd->name);
369
370 return ret;
371}
372
373static 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
471static const struct of_device_id of_scpsys_match_tbl[] = {
472 {
473 .compatible = "mediatek,mt8173-scpsys",
474 }, {
475 /* sentinel */
476 }
477};
478
479static 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
487module_platform_driver_probe(scpsys_drv, scpsys_probe);