1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * mtu3_dr.c - dual role switch and host glue layer |
4 | * |
5 | * Copyright (C) 2016 MediaTek Inc. |
6 | * |
7 | * Author: Chunfeng Yun <chunfeng.yun@mediatek.com> |
8 | */ |
9 | |
10 | #include <linux/clk.h> |
11 | #include <linux/irq.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/mfd/syscon.h> |
14 | #include <linux/of.h> |
15 | #include <linux/of_platform.h> |
16 | #include <linux/regmap.h> |
17 | |
18 | #include "mtu3.h" |
19 | #include "mtu3_dr.h" |
20 | |
21 | /* mt8173 etc */ |
22 | #define PERI_WK_CTRL1 0x4 |
23 | #define WC1_IS_C(x) (((x) & 0xf) << 26) /* cycle debounce */ |
24 | #define WC1_IS_EN BIT(25) |
25 | #define WC1_IS_P BIT(6) /* polarity for ip sleep */ |
26 | |
27 | /* mt8183 */ |
28 | #define PERI_WK_CTRL0 0x0 |
29 | #define WC0_IS_C(x) ((u32)(((x) & 0xf) << 28)) /* cycle debounce */ |
30 | #define WC0_IS_P BIT(12) /* polarity */ |
31 | #define WC0_IS_EN BIT(6) |
32 | |
33 | /* mt8192 */ |
34 | #define WC0_SSUSB0_CDEN BIT(6) |
35 | #define WC0_IS_SPM_EN BIT(1) |
36 | |
37 | /* mt2712 etc */ |
38 | #define PERI_SSUSB_SPM_CTRL 0x0 |
39 | #define SSC_IP_SLEEP_EN BIT(4) |
40 | #define SSC_SPM_INT_EN BIT(1) |
41 | |
42 | enum ssusb_uwk_vers { |
43 | SSUSB_UWK_V1 = 1, |
44 | SSUSB_UWK_V2, |
45 | SSUSB_UWK_V1_1 = 101, /* specific revision 1.01 */ |
46 | SSUSB_UWK_V1_2, /* specific revision 1.02 */ |
47 | }; |
48 | |
49 | /* |
50 | * ip-sleep wakeup mode: |
51 | * all clocks can be turn off, but power domain should be kept on |
52 | */ |
53 | static void ssusb_wakeup_ip_sleep_set(struct ssusb_mtk *ssusb, bool enable) |
54 | { |
55 | u32 reg, msk, val; |
56 | |
57 | switch (ssusb->uwk_vers) { |
58 | case SSUSB_UWK_V1: |
59 | reg = ssusb->uwk_reg_base + PERI_WK_CTRL1; |
60 | msk = WC1_IS_EN | WC1_IS_C(0xf) | WC1_IS_P; |
61 | val = enable ? (WC1_IS_EN | WC1_IS_C(0x8)) : 0; |
62 | break; |
63 | case SSUSB_UWK_V1_1: |
64 | reg = ssusb->uwk_reg_base + PERI_WK_CTRL0; |
65 | msk = WC0_IS_EN | WC0_IS_C(0xf) | WC0_IS_P; |
66 | val = enable ? (WC0_IS_EN | WC0_IS_C(0x1)) : 0; |
67 | break; |
68 | case SSUSB_UWK_V1_2: |
69 | reg = ssusb->uwk_reg_base + PERI_WK_CTRL0; |
70 | msk = WC0_SSUSB0_CDEN | WC0_IS_SPM_EN; |
71 | val = enable ? msk : 0; |
72 | break; |
73 | case SSUSB_UWK_V2: |
74 | reg = ssusb->uwk_reg_base + PERI_SSUSB_SPM_CTRL; |
75 | msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN; |
76 | val = enable ? msk : 0; |
77 | break; |
78 | default: |
79 | return; |
80 | } |
81 | regmap_update_bits(map: ssusb->uwk, reg, mask: msk, val); |
82 | } |
83 | |
84 | int ssusb_wakeup_of_property_parse(struct ssusb_mtk *ssusb, |
85 | struct device_node *dn) |
86 | { |
87 | struct of_phandle_args args; |
88 | int ret; |
89 | |
90 | /* wakeup function is optional */ |
91 | ssusb->uwk_en = of_property_read_bool(np: dn, propname: "wakeup-source" ); |
92 | if (!ssusb->uwk_en) |
93 | return 0; |
94 | |
95 | ret = of_parse_phandle_with_fixed_args(np: dn, |
96 | list_name: "mediatek,syscon-wakeup" , cell_count: 2, index: 0, out_args: &args); |
97 | if (ret) |
98 | return ret; |
99 | |
100 | ssusb->uwk_reg_base = args.args[0]; |
101 | ssusb->uwk_vers = args.args[1]; |
102 | ssusb->uwk = syscon_node_to_regmap(np: args.np); |
103 | of_node_put(node: args.np); |
104 | dev_info(ssusb->dev, "uwk - reg:0x%x, version:%d\n" , |
105 | ssusb->uwk_reg_base, ssusb->uwk_vers); |
106 | |
107 | return PTR_ERR_OR_ZERO(ptr: ssusb->uwk); |
108 | } |
109 | |
110 | void ssusb_wakeup_set(struct ssusb_mtk *ssusb, bool enable) |
111 | { |
112 | if (ssusb->uwk_en) |
113 | ssusb_wakeup_ip_sleep_set(ssusb, enable); |
114 | } |
115 | |
116 | static void host_ports_num_get(struct ssusb_mtk *ssusb) |
117 | { |
118 | u32 xhci_cap; |
119 | |
120 | xhci_cap = mtu3_readl(base: ssusb->ippc_base, U3D_SSUSB_IP_XHCI_CAP); |
121 | ssusb->u2_ports = SSUSB_IP_XHCI_U2_PORT_NUM(xhci_cap); |
122 | ssusb->u3_ports = SSUSB_IP_XHCI_U3_PORT_NUM(xhci_cap); |
123 | |
124 | dev_dbg(ssusb->dev, "host - u2_ports:%d, u3_ports:%d\n" , |
125 | ssusb->u2_ports, ssusb->u3_ports); |
126 | } |
127 | |
128 | /* only configure ports will be used later */ |
129 | static int ssusb_host_enable(struct ssusb_mtk *ssusb) |
130 | { |
131 | void __iomem *ibase = ssusb->ippc_base; |
132 | int num_u3p = ssusb->u3_ports; |
133 | int num_u2p = ssusb->u2_ports; |
134 | int u3_ports_disabled; |
135 | u32 check_clk; |
136 | u32 value; |
137 | int i; |
138 | |
139 | /* power on host ip */ |
140 | mtu3_clrbits(base: ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN); |
141 | |
142 | /* power on and enable u3 ports except skipped ones */ |
143 | u3_ports_disabled = 0; |
144 | for (i = 0; i < num_u3p; i++) { |
145 | if ((0x1 << i) & ssusb->u3p_dis_msk) { |
146 | u3_ports_disabled++; |
147 | continue; |
148 | } |
149 | |
150 | value = mtu3_readl(base: ibase, SSUSB_U3_CTRL(i)); |
151 | value &= ~(SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS); |
152 | value |= SSUSB_U3_PORT_HOST_SEL; |
153 | mtu3_writel(base: ibase, SSUSB_U3_CTRL(i), data: value); |
154 | } |
155 | |
156 | /* power on and enable all u2 ports */ |
157 | for (i = 0; i < num_u2p; i++) { |
158 | if ((0x1 << i) & ssusb->u2p_dis_msk) |
159 | continue; |
160 | |
161 | value = mtu3_readl(base: ibase, SSUSB_U2_CTRL(i)); |
162 | value &= ~(SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS); |
163 | value |= SSUSB_U2_PORT_HOST_SEL; |
164 | mtu3_writel(base: ibase, SSUSB_U2_CTRL(i), data: value); |
165 | } |
166 | |
167 | check_clk = SSUSB_XHCI_RST_B_STS; |
168 | if (num_u3p > u3_ports_disabled) |
169 | check_clk = SSUSB_U3_MAC_RST_B_STS; |
170 | |
171 | return ssusb_check_clocks(ssusb, ex_clks: check_clk); |
172 | } |
173 | |
174 | static int ssusb_host_disable(struct ssusb_mtk *ssusb) |
175 | { |
176 | void __iomem *ibase = ssusb->ippc_base; |
177 | int num_u3p = ssusb->u3_ports; |
178 | int num_u2p = ssusb->u2_ports; |
179 | u32 value; |
180 | int i; |
181 | |
182 | /* power down and disable u3 ports except skipped ones */ |
183 | for (i = 0; i < num_u3p; i++) { |
184 | if ((0x1 << i) & ssusb->u3p_dis_msk) |
185 | continue; |
186 | |
187 | value = mtu3_readl(base: ibase, SSUSB_U3_CTRL(i)); |
188 | value |= SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS; |
189 | mtu3_writel(base: ibase, SSUSB_U3_CTRL(i), data: value); |
190 | } |
191 | |
192 | /* power down and disable u2 ports except skipped ones */ |
193 | for (i = 0; i < num_u2p; i++) { |
194 | if ((0x1 << i) & ssusb->u2p_dis_msk) |
195 | continue; |
196 | |
197 | value = mtu3_readl(base: ibase, SSUSB_U2_CTRL(i)); |
198 | value |= SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS; |
199 | mtu3_writel(base: ibase, SSUSB_U2_CTRL(i), data: value); |
200 | } |
201 | |
202 | /* power down host ip */ |
203 | mtu3_setbits(base: ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN); |
204 | |
205 | return 0; |
206 | } |
207 | |
208 | int ssusb_host_resume(struct ssusb_mtk *ssusb, bool p0_skipped) |
209 | { |
210 | void __iomem *ibase = ssusb->ippc_base; |
211 | int u3p_skip_msk = ssusb->u3p_dis_msk; |
212 | int u2p_skip_msk = ssusb->u2p_dis_msk; |
213 | int num_u3p = ssusb->u3_ports; |
214 | int num_u2p = ssusb->u2_ports; |
215 | u32 value; |
216 | int i; |
217 | |
218 | if (p0_skipped) { |
219 | u2p_skip_msk |= 0x1; |
220 | if (ssusb->otg_switch.is_u3_drd) |
221 | u3p_skip_msk |= 0x1; |
222 | } |
223 | |
224 | /* power on host ip */ |
225 | mtu3_clrbits(base: ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN); |
226 | |
227 | /* power on u3 ports except skipped ones */ |
228 | for (i = 0; i < num_u3p; i++) { |
229 | if ((0x1 << i) & u3p_skip_msk) |
230 | continue; |
231 | |
232 | value = mtu3_readl(base: ibase, SSUSB_U3_CTRL(i)); |
233 | value &= ~SSUSB_U3_PORT_PDN; |
234 | mtu3_writel(base: ibase, SSUSB_U3_CTRL(i), data: value); |
235 | } |
236 | |
237 | /* power on all u2 ports except skipped ones */ |
238 | for (i = 0; i < num_u2p; i++) { |
239 | if ((0x1 << i) & u2p_skip_msk) |
240 | continue; |
241 | |
242 | value = mtu3_readl(base: ibase, SSUSB_U2_CTRL(i)); |
243 | value &= ~SSUSB_U2_PORT_PDN; |
244 | mtu3_writel(base: ibase, SSUSB_U2_CTRL(i), data: value); |
245 | } |
246 | |
247 | return 0; |
248 | } |
249 | |
250 | /* here not skip port0 due to PDN can be set repeatedly */ |
251 | int ssusb_host_suspend(struct ssusb_mtk *ssusb) |
252 | { |
253 | void __iomem *ibase = ssusb->ippc_base; |
254 | int num_u3p = ssusb->u3_ports; |
255 | int num_u2p = ssusb->u2_ports; |
256 | u32 value; |
257 | int i; |
258 | |
259 | /* power down u3 ports except skipped ones */ |
260 | for (i = 0; i < num_u3p; i++) { |
261 | if ((0x1 << i) & ssusb->u3p_dis_msk) |
262 | continue; |
263 | |
264 | value = mtu3_readl(base: ibase, SSUSB_U3_CTRL(i)); |
265 | value |= SSUSB_U3_PORT_PDN; |
266 | mtu3_writel(base: ibase, SSUSB_U3_CTRL(i), data: value); |
267 | } |
268 | |
269 | /* power down u2 ports except skipped ones */ |
270 | for (i = 0; i < num_u2p; i++) { |
271 | if ((0x1 << i) & ssusb->u2p_dis_msk) |
272 | continue; |
273 | |
274 | value = mtu3_readl(base: ibase, SSUSB_U2_CTRL(i)); |
275 | value |= SSUSB_U2_PORT_PDN; |
276 | mtu3_writel(base: ibase, SSUSB_U2_CTRL(i), data: value); |
277 | } |
278 | |
279 | /* power down host ip */ |
280 | mtu3_setbits(base: ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN); |
281 | |
282 | return 0; |
283 | } |
284 | |
285 | static void ssusb_host_setup(struct ssusb_mtk *ssusb) |
286 | { |
287 | host_ports_num_get(ssusb); |
288 | |
289 | /* |
290 | * power on host and power on/enable all ports |
291 | * if support OTG, gadget driver will switch port0 to device mode |
292 | */ |
293 | ssusb_host_enable(ssusb); |
294 | ssusb_set_force_mode(ssusb, mode: MTU3_DR_FORCE_HOST); |
295 | |
296 | /* if port0 supports dual-role, works as host mode by default */ |
297 | ssusb_set_vbus(otg_sx: &ssusb->otg_switch, is_on: 1); |
298 | } |
299 | |
300 | static void ssusb_host_cleanup(struct ssusb_mtk *ssusb) |
301 | { |
302 | if (ssusb->is_host) |
303 | ssusb_set_vbus(otg_sx: &ssusb->otg_switch, is_on: 0); |
304 | |
305 | ssusb_host_disable(ssusb); |
306 | } |
307 | |
308 | /* |
309 | * If host supports multiple ports, the VBUSes(5V) of ports except port0 |
310 | * which supports OTG are better to be enabled by default in DTS. |
311 | * Because the host driver will keep link with devices attached when system |
312 | * enters suspend mode, so no need to control VBUSes after initialization. |
313 | */ |
314 | int ssusb_host_init(struct ssusb_mtk *ssusb, struct device_node *parent_dn) |
315 | { |
316 | struct device *parent_dev = ssusb->dev; |
317 | int ret; |
318 | |
319 | ssusb_host_setup(ssusb); |
320 | |
321 | ret = of_platform_populate(root: parent_dn, NULL, NULL, parent: parent_dev); |
322 | if (ret) { |
323 | dev_dbg(parent_dev, "failed to create child devices at %pOF\n" , |
324 | parent_dn); |
325 | return ret; |
326 | } |
327 | |
328 | dev_info(parent_dev, "xHCI platform device register success...\n" ); |
329 | |
330 | return 0; |
331 | } |
332 | |
333 | void ssusb_host_exit(struct ssusb_mtk *ssusb) |
334 | { |
335 | of_platform_depopulate(parent: ssusb->dev); |
336 | ssusb_host_cleanup(ssusb); |
337 | } |
338 | |