diff options
-rw-r--r-- | Documentation/devicetree/bindings/soc/mediatek/scpsys.txt | 41 | ||||
-rw-r--r-- | arch/arm/mach-mediatek/Kconfig | 1 | ||||
-rw-r--r-- | drivers/soc/mediatek/Kconfig | 19 | ||||
-rw-r--r-- | drivers/soc/mediatek/Makefile | 2 | ||||
-rw-r--r-- | drivers/soc/mediatek/mtk-infracfg.c | 91 | ||||
-rw-r--r-- | drivers/soc/mediatek/mtk-pmic-wrap.c | 1 | ||||
-rw-r--r-- | drivers/soc/mediatek/mtk-scpsys.c | 487 | ||||
-rw-r--r-- | include/dt-bindings/power/mt8173-power.h | 15 | ||||
-rw-r--r-- | include/linux/soc/mediatek/infracfg.h | 26 |
9 files changed, 682 insertions, 1 deletions
diff --git a/Documentation/devicetree/bindings/soc/mediatek/scpsys.txt b/Documentation/devicetree/bindings/soc/mediatek/scpsys.txt new file mode 100644 index 000000000000..c0511142b39c --- /dev/null +++ b/Documentation/devicetree/bindings/soc/mediatek/scpsys.txt | |||
@@ -0,0 +1,41 @@ | |||
1 | MediaTek SCPSYS | ||
2 | =============== | ||
3 | |||
4 | The System Control Processor System (SCPSYS) has several power management | ||
5 | related tasks in the system. The tasks include thermal measurement, dynamic | ||
6 | voltage frequency scaling (DVFS), interrupt filter and lowlevel sleep control. | ||
7 | The System Power Manager (SPM) inside the SCPSYS is for the MTCMOS power | ||
8 | domain control. | ||
9 | |||
10 | The driver implements the Generic PM domain bindings described in | ||
11 | power/power_domain.txt. It provides the power domains defined in | ||
12 | include/dt-bindings/power/mt8173-power.h. | ||
13 | |||
14 | Required properties: | ||
15 | - compatible: Must be "mediatek,mt8173-scpsys" | ||
16 | - #power-domain-cells: Must be 1 | ||
17 | - reg: Address range of the SCPSYS unit | ||
18 | - infracfg: must contain a phandle to the infracfg controller | ||
19 | - clock, clock-names: clocks according to the common clock binding. | ||
20 | The clocks needed "mm" and "mfg". These are the | ||
21 | clocks which hardware needs to be enabled before | ||
22 | enabling certain power domains. | ||
23 | |||
24 | Example: | ||
25 | |||
26 | scpsys: scpsys@10006000 { | ||
27 | #power-domain-cells = <1>; | ||
28 | compatible = "mediatek,mt8173-scpsys"; | ||
29 | reg = <0 0x10006000 0 0x1000>; | ||
30 | infracfg = <&infracfg>; | ||
31 | clocks = <&clk26m>, | ||
32 | <&topckgen CLK_TOP_MM_SEL>; | ||
33 | clock-names = "mfg", "mm"; | ||
34 | }; | ||
35 | |||
36 | Example consumer: | ||
37 | |||
38 | afe: mt8173-afe-pcm@11220000 { | ||
39 | compatible = "mediatek,mt8173-afe-pcm"; | ||
40 | power-domains = <&scpsys MT8173_POWER_DOMAIN_AUDIO>; | ||
41 | }; | ||
diff --git a/arch/arm/mach-mediatek/Kconfig b/arch/arm/mach-mediatek/Kconfig index 9f59e58da3a4..aeece17e5cea 100644 --- a/arch/arm/mach-mediatek/Kconfig +++ b/arch/arm/mach-mediatek/Kconfig | |||
@@ -3,6 +3,7 @@ menuconfig ARCH_MEDIATEK | |||
3 | select ARM_GIC | 3 | select ARM_GIC |
4 | select PINCTRL | 4 | select PINCTRL |
5 | select MTK_TIMER | 5 | select MTK_TIMER |
6 | select MFD_SYSCON | ||
6 | help | 7 | help |
7 | Support for Mediatek MT65xx & MT81xx SoCs | 8 | Support for Mediatek MT65xx & MT81xx SoCs |
8 | 9 | ||
diff --git a/drivers/soc/mediatek/Kconfig b/drivers/soc/mediatek/Kconfig index 3c1850332a90..9d5068248aa0 100644 --- a/drivers/soc/mediatek/Kconfig +++ b/drivers/soc/mediatek/Kconfig | |||
@@ -1,6 +1,15 @@ | |||
1 | # | 1 | # |
2 | # MediaTek SoC drivers | 2 | # MediaTek SoC drivers |
3 | # | 3 | # |
4 | config MTK_INFRACFG | ||
5 | bool "MediaTek INFRACFG Support" | ||
6 | depends on ARCH_MEDIATEK || COMPILE_TEST | ||
7 | select REGMAP | ||
8 | help | ||
9 | Say yes here to add support for the MediaTek INFRACFG controller. The | ||
10 | INFRACFG controller contains various infrastructure registers not | ||
11 | directly associated to any device. | ||
12 | |||
4 | config MTK_PMIC_WRAP | 13 | config MTK_PMIC_WRAP |
5 | tristate "MediaTek PMIC Wrapper Support" | 14 | tristate "MediaTek PMIC Wrapper Support" |
6 | depends on ARCH_MEDIATEK | 15 | depends on ARCH_MEDIATEK |
@@ -10,3 +19,13 @@ config MTK_PMIC_WRAP | |||
10 | Say yes here to add support for MediaTek PMIC Wrapper found | 19 | Say yes here to add support for MediaTek PMIC Wrapper found |
11 | on different MediaTek SoCs. The PMIC wrapper is a proprietary | 20 | on different MediaTek SoCs. The PMIC wrapper is a proprietary |
12 | hardware to connect the PMIC. | 21 | hardware to connect the PMIC. |
22 | |||
23 | config 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 ecaf4defd7f6..12998b08819e 100644 --- a/drivers/soc/mediatek/Makefile +++ b/drivers/soc/mediatek/Makefile | |||
@@ -1 +1,3 @@ | |||
1 | obj-$(CONFIG_MTK_INFRACFG) += mtk-infracfg.o | ||
1 | obj-$(CONFIG_MTK_PMIC_WRAP) += mtk-pmic-wrap.o | 2 | obj-$(CONFIG_MTK_PMIC_WRAP) += mtk-pmic-wrap.o |
3 | obj-$(CONFIG_MTK_SCPSYS) += mtk-scpsys.o | ||
diff --git a/drivers/soc/mediatek/mtk-infracfg.c b/drivers/soc/mediatek/mtk-infracfg.c new file mode 100644 index 000000000000..dba3055a9493 --- /dev/null +++ b/drivers/soc/mediatek/mtk-infracfg.c | |||
@@ -0,0 +1,91 @@ | |||
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 | |||
14 | #include <linux/export.h> | ||
15 | #include <linux/jiffies.h> | ||
16 | #include <linux/regmap.h> | ||
17 | #include <linux/soc/mediatek/infracfg.h> | ||
18 | #include <asm/processor.h> | ||
19 | |||
20 | #define INFRA_TOPAXI_PROTECTEN 0x0220 | ||
21 | #define INFRA_TOPAXI_PROTECTSTA1 0x0228 | ||
22 | |||
23 | /** | ||
24 | * mtk_infracfg_set_bus_protection - enable bus protection | ||
25 | * @regmap: The infracfg regmap | ||
26 | * @mask: The mask containing the protection bits to be enabled. | ||
27 | * | ||
28 | * This function enables the bus protection bits for disabled power | ||
29 | * domains so that the system does not hang when some unit accesses the | ||
30 | * bus while in power down. | ||
31 | */ | ||
32 | int mtk_infracfg_set_bus_protection(struct regmap *infracfg, u32 mask) | ||
33 | { | ||
34 | unsigned long expired; | ||
35 | u32 val; | ||
36 | int ret; | ||
37 | |||
38 | regmap_update_bits(infracfg, INFRA_TOPAXI_PROTECTEN, mask, mask); | ||
39 | |||
40 | expired = jiffies + HZ; | ||
41 | |||
42 | while (1) { | ||
43 | ret = regmap_read(infracfg, INFRA_TOPAXI_PROTECTSTA1, &val); | ||
44 | if (ret) | ||
45 | return ret; | ||
46 | |||
47 | if ((val & mask) == mask) | ||
48 | break; | ||
49 | |||
50 | cpu_relax(); | ||
51 | if (time_after(jiffies, expired)) | ||
52 | return -EIO; | ||
53 | } | ||
54 | |||
55 | return 0; | ||
56 | } | ||
57 | |||
58 | /** | ||
59 | * mtk_infracfg_clear_bus_protection - disable bus protection | ||
60 | * @regmap: The infracfg regmap | ||
61 | * @mask: The mask containing the protection bits to be disabled. | ||
62 | * | ||
63 | * This function disables the bus protection bits previously enabled with | ||
64 | * mtk_infracfg_set_bus_protection. | ||
65 | */ | ||
66 | int mtk_infracfg_clear_bus_protection(struct regmap *infracfg, u32 mask) | ||
67 | { | ||
68 | unsigned long expired; | ||
69 | int ret; | ||
70 | |||
71 | regmap_update_bits(infracfg, INFRA_TOPAXI_PROTECTEN, mask, 0); | ||
72 | |||
73 | expired = jiffies + HZ; | ||
74 | |||
75 | while (1) { | ||
76 | u32 val; | ||
77 | |||
78 | ret = regmap_read(infracfg, INFRA_TOPAXI_PROTECTSTA1, &val); | ||
79 | if (ret) | ||
80 | return ret; | ||
81 | |||
82 | if (!(val & mask)) | ||
83 | break; | ||
84 | |||
85 | cpu_relax(); | ||
86 | if (time_after(jiffies, expired)) | ||
87 | return -EIO; | ||
88 | } | ||
89 | |||
90 | return 0; | ||
91 | } | ||
diff --git a/drivers/soc/mediatek/mtk-pmic-wrap.c b/drivers/soc/mediatek/mtk-pmic-wrap.c index f432291feee9..8bc7b41b09fd 100644 --- a/drivers/soc/mediatek/mtk-pmic-wrap.c +++ b/drivers/soc/mediatek/mtk-pmic-wrap.c | |||
@@ -926,7 +926,6 @@ err_out1: | |||
926 | static struct platform_driver pwrap_drv = { | 926 | static struct platform_driver pwrap_drv = { |
927 | .driver = { | 927 | .driver = { |
928 | .name = "mt-pmic-pwrap", | 928 | .name = "mt-pmic-pwrap", |
929 | .owner = THIS_MODULE, | ||
930 | .of_match_table = of_match_ptr(of_pwrap_match_tbl), | 929 | .of_match_table = of_match_ptr(of_pwrap_match_tbl), |
931 | }, | 930 | }, |
932 | .probe = pwrap_probe, | 931 | .probe = pwrap_probe, |
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); | ||
diff --git a/include/dt-bindings/power/mt8173-power.h b/include/dt-bindings/power/mt8173-power.h new file mode 100644 index 000000000000..b34cee95aa89 --- /dev/null +++ b/include/dt-bindings/power/mt8173-power.h | |||
@@ -0,0 +1,15 @@ | |||
1 | #ifndef _DT_BINDINGS_POWER_MT8183_POWER_H | ||
2 | #define _DT_BINDINGS_POWER_MT8183_POWER_H | ||
3 | |||
4 | #define MT8173_POWER_DOMAIN_VDEC 0 | ||
5 | #define MT8173_POWER_DOMAIN_VENC 1 | ||
6 | #define MT8173_POWER_DOMAIN_ISP 2 | ||
7 | #define MT8173_POWER_DOMAIN_MM 3 | ||
8 | #define MT8173_POWER_DOMAIN_VENC_LT 4 | ||
9 | #define MT8173_POWER_DOMAIN_AUDIO 5 | ||
10 | #define MT8173_POWER_DOMAIN_USB 6 | ||
11 | #define MT8173_POWER_DOMAIN_MFG_ASYNC 7 | ||
12 | #define MT8173_POWER_DOMAIN_MFG_2D 8 | ||
13 | #define MT8173_POWER_DOMAIN_MFG 9 | ||
14 | |||
15 | #endif /* _DT_BINDINGS_POWER_MT8183_POWER_H */ | ||
diff --git a/include/linux/soc/mediatek/infracfg.h b/include/linux/soc/mediatek/infracfg.h new file mode 100644 index 000000000000..a5714e93fb34 --- /dev/null +++ b/include/linux/soc/mediatek/infracfg.h | |||
@@ -0,0 +1,26 @@ | |||
1 | #ifndef __SOC_MEDIATEK_INFRACFG_H | ||
2 | #define __SOC_MEDIATEK_INFRACFG_H | ||
3 | |||
4 | #define MT8173_TOP_AXI_PROT_EN_MCI_M2 BIT(0) | ||
5 | #define MT8173_TOP_AXI_PROT_EN_MM_M0 BIT(1) | ||
6 | #define MT8173_TOP_AXI_PROT_EN_MM_M1 BIT(2) | ||
7 | #define MT8173_TOP_AXI_PROT_EN_MMAPB_S BIT(6) | ||
8 | #define MT8173_TOP_AXI_PROT_EN_L2C_M2 BIT(9) | ||
9 | #define MT8173_TOP_AXI_PROT_EN_L2SS_SMI BIT(11) | ||
10 | #define MT8173_TOP_AXI_PROT_EN_L2SS_ADD BIT(12) | ||
11 | #define MT8173_TOP_AXI_PROT_EN_CCI_M2 BIT(13) | ||
12 | #define MT8173_TOP_AXI_PROT_EN_MFG_S BIT(14) | ||
13 | #define MT8173_TOP_AXI_PROT_EN_PERI_M0 BIT(15) | ||
14 | #define MT8173_TOP_AXI_PROT_EN_PERI_M1 BIT(16) | ||
15 | #define MT8173_TOP_AXI_PROT_EN_DEBUGSYS BIT(17) | ||
16 | #define MT8173_TOP_AXI_PROT_EN_CQ_DMA BIT(18) | ||
17 | #define MT8173_TOP_AXI_PROT_EN_GCPU BIT(19) | ||
18 | #define MT8173_TOP_AXI_PROT_EN_IOMMU BIT(20) | ||
19 | #define MT8173_TOP_AXI_PROT_EN_MFG_M0 BIT(21) | ||
20 | #define MT8173_TOP_AXI_PROT_EN_MFG_M1 BIT(22) | ||
21 | #define MT8173_TOP_AXI_PROT_EN_MFG_SNOOP_OUT BIT(23) | ||
22 | |||
23 | int mtk_infracfg_set_bus_protection(struct regmap *infracfg, u32 mask); | ||
24 | int mtk_infracfg_clear_bus_protection(struct regmap *infracfg, u32 mask); | ||
25 | |||
26 | #endif /* __SOC_MEDIATEK_INFRACFG_H */ | ||