]>
Commit | Line | Data |
---|---|---|
5fd54ace | 1 | // SPDX-License-Identifier: GPL-2.0 |
0cbd4b34 CY |
2 | /* |
3 | * MediaTek xHCI Host Controller Driver | |
4 | * | |
5 | * Copyright (c) 2015 MediaTek Inc. | |
6 | * Author: | |
7 | * Chunfeng Yun <chunfeng.yun@mediatek.com> | |
0cbd4b34 CY |
8 | */ |
9 | ||
0cbd4b34 CY |
10 | #include <linux/dma-mapping.h> |
11 | #include <linux/iopoll.h> | |
12 | #include <linux/kernel.h> | |
13 | #include <linux/mfd/syscon.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/of.h> | |
0cbd4b34 CY |
16 | #include <linux/platform_device.h> |
17 | #include <linux/pm_runtime.h> | |
04284eb7 | 18 | #include <linux/pm_wakeirq.h> |
0cbd4b34 CY |
19 | #include <linux/regmap.h> |
20 | #include <linux/regulator/consumer.h> | |
21 | ||
22 | #include "xhci.h" | |
23 | #include "xhci-mtk.h" | |
24 | ||
25 | /* ip_pw_ctrl0 register */ | |
26 | #define CTRL0_IP_SW_RST BIT(0) | |
27 | ||
28 | /* ip_pw_ctrl1 register */ | |
29 | #define CTRL1_IP_HOST_PDN BIT(0) | |
30 | ||
31 | /* ip_pw_ctrl2 register */ | |
32 | #define CTRL2_IP_DEV_PDN BIT(0) | |
33 | ||
34 | /* ip_pw_sts1 register */ | |
35 | #define STS1_IP_SLEEP_STS BIT(30) | |
ce370bfd | 36 | #define STS1_U3_MAC_RST BIT(16) |
0cbd4b34 CY |
37 | #define STS1_XHCI_RST BIT(11) |
38 | #define STS1_SYS125_RST BIT(10) | |
39 | #define STS1_REF_RST BIT(8) | |
40 | #define STS1_SYSPLL_STABLE BIT(0) | |
41 | ||
42 | /* ip_xhci_cap register */ | |
43 | #define CAP_U3_PORT_NUM(p) ((p) & 0xff) | |
44 | #define CAP_U2_PORT_NUM(p) (((p) >> 8) & 0xff) | |
45 | ||
46 | /* u3_ctrl_p register */ | |
47 | #define CTRL_U3_PORT_HOST_SEL BIT(2) | |
48 | #define CTRL_U3_PORT_PDN BIT(1) | |
49 | #define CTRL_U3_PORT_DIS BIT(0) | |
50 | ||
51 | /* u2_ctrl_p register */ | |
52 | #define CTRL_U2_PORT_HOST_SEL BIT(2) | |
53 | #define CTRL_U2_PORT_PDN BIT(1) | |
54 | #define CTRL_U2_PORT_DIS BIT(0) | |
55 | ||
56 | /* u2_phy_pll register */ | |
57 | #define CTRL_U2_FORCE_PLL_STB BIT(28) | |
58 | ||
a2ecc4df | 59 | /* usb remote wakeup registers in syscon */ |
c03b4ccb | 60 | |
a2ecc4df CY |
61 | /* mt8173 etc */ |
62 | #define PERI_WK_CTRL1 0x4 | |
63 | #define WC1_IS_C(x) (((x) & 0xf) << 26) /* cycle debounce */ | |
64 | #define WC1_IS_EN BIT(25) | |
65 | #define WC1_IS_P BIT(6) /* polarity for ip sleep */ | |
66 | ||
c03b4ccb CY |
67 | /* mt8183 */ |
68 | #define PERI_WK_CTRL0 0x0 | |
69 | #define WC0_IS_C(x) ((u32)(((x) & 0xf) << 28)) /* cycle debounce */ | |
70 | #define WC0_IS_P BIT(12) /* polarity */ | |
71 | #define WC0_IS_EN BIT(6) | |
72 | ||
331c5058 CY |
73 | /* mt8192 */ |
74 | #define WC0_SSUSB0_CDEN BIT(6) | |
75 | #define WC0_IS_SPM_EN BIT(1) | |
76 | ||
a2ecc4df CY |
77 | /* mt2712 etc */ |
78 | #define PERI_SSUSB_SPM_CTRL 0x0 | |
79 | #define SSC_IP_SLEEP_EN BIT(4) | |
80 | #define SSC_SPM_INT_EN BIT(1) | |
81 | ||
82 | enum ssusb_uwk_vers { | |
83 | SSUSB_UWK_V1 = 1, | |
84 | SSUSB_UWK_V2, | |
c03b4ccb | 85 | SSUSB_UWK_V1_1 = 101, /* specific revision 1.01 */ |
331c5058 | 86 | SSUSB_UWK_V1_2, /* specific revision 1.2 */ |
0cbd4b34 CY |
87 | }; |
88 | ||
89 | static int xhci_mtk_host_enable(struct xhci_hcd_mtk *mtk) | |
90 | { | |
91 | struct mu3c_ippc_regs __iomem *ippc = mtk->ippc_regs; | |
92 | u32 value, check_val; | |
6e18cfca | 93 | int u3_ports_disabled = 0; |
0cbd4b34 CY |
94 | int ret; |
95 | int i; | |
96 | ||
065d48cf CY |
97 | if (!mtk->has_ippc) |
98 | return 0; | |
99 | ||
0cbd4b34 CY |
100 | /* power on host ip */ |
101 | value = readl(&ippc->ip_pw_ctr1); | |
102 | value &= ~CTRL1_IP_HOST_PDN; | |
103 | writel(value, &ippc->ip_pw_ctr1); | |
104 | ||
55ba6e9e | 105 | /* power on and enable u3 ports except skipped ones */ |
0cbd4b34 | 106 | for (i = 0; i < mtk->num_u3_ports; i++) { |
55ba6e9e | 107 | if ((0x1 << i) & mtk->u3p_dis_msk) { |
6e18cfca | 108 | u3_ports_disabled++; |
55ba6e9e CY |
109 | continue; |
110 | } | |
111 | ||
0cbd4b34 CY |
112 | value = readl(&ippc->u3_ctrl_p[i]); |
113 | value &= ~(CTRL_U3_PORT_PDN | CTRL_U3_PORT_DIS); | |
114 | value |= CTRL_U3_PORT_HOST_SEL; | |
115 | writel(value, &ippc->u3_ctrl_p[i]); | |
116 | } | |
117 | ||
118 | /* power on and enable all u2 ports */ | |
119 | for (i = 0; i < mtk->num_u2_ports; i++) { | |
120 | value = readl(&ippc->u2_ctrl_p[i]); | |
121 | value &= ~(CTRL_U2_PORT_PDN | CTRL_U2_PORT_DIS); | |
122 | value |= CTRL_U2_PORT_HOST_SEL; | |
123 | writel(value, &ippc->u2_ctrl_p[i]); | |
124 | } | |
125 | ||
126 | /* | |
127 | * wait for clocks to be stable, and clock domains reset to | |
128 | * be inactive after power on and enable ports | |
129 | */ | |
130 | check_val = STS1_SYSPLL_STABLE | STS1_REF_RST | | |
131 | STS1_SYS125_RST | STS1_XHCI_RST; | |
132 | ||
6e18cfca | 133 | if (mtk->num_u3_ports > u3_ports_disabled) |
ce370bfd CY |
134 | check_val |= STS1_U3_MAC_RST; |
135 | ||
0cbd4b34 CY |
136 | ret = readl_poll_timeout(&ippc->ip_pw_sts1, value, |
137 | (check_val == (value & check_val)), 100, 20000); | |
138 | if (ret) { | |
139 | dev_err(mtk->dev, "clocks are not stable (0x%x)\n", value); | |
140 | return ret; | |
141 | } | |
142 | ||
143 | return 0; | |
144 | } | |
145 | ||
146 | static int xhci_mtk_host_disable(struct xhci_hcd_mtk *mtk) | |
147 | { | |
148 | struct mu3c_ippc_regs __iomem *ippc = mtk->ippc_regs; | |
149 | u32 value; | |
150 | int ret; | |
151 | int i; | |
152 | ||
065d48cf CY |
153 | if (!mtk->has_ippc) |
154 | return 0; | |
155 | ||
55ba6e9e | 156 | /* power down u3 ports except skipped ones */ |
0cbd4b34 | 157 | for (i = 0; i < mtk->num_u3_ports; i++) { |
55ba6e9e CY |
158 | if ((0x1 << i) & mtk->u3p_dis_msk) |
159 | continue; | |
160 | ||
0cbd4b34 CY |
161 | value = readl(&ippc->u3_ctrl_p[i]); |
162 | value |= CTRL_U3_PORT_PDN; | |
163 | writel(value, &ippc->u3_ctrl_p[i]); | |
164 | } | |
165 | ||
166 | /* power down all u2 ports */ | |
167 | for (i = 0; i < mtk->num_u2_ports; i++) { | |
168 | value = readl(&ippc->u2_ctrl_p[i]); | |
169 | value |= CTRL_U2_PORT_PDN; | |
170 | writel(value, &ippc->u2_ctrl_p[i]); | |
171 | } | |
172 | ||
173 | /* power down host ip */ | |
174 | value = readl(&ippc->ip_pw_ctr1); | |
175 | value |= CTRL1_IP_HOST_PDN; | |
176 | writel(value, &ippc->ip_pw_ctr1); | |
177 | ||
178 | /* wait for host ip to sleep */ | |
179 | ret = readl_poll_timeout(&ippc->ip_pw_sts1, value, | |
180 | (value & STS1_IP_SLEEP_STS), 100, 100000); | |
181 | if (ret) { | |
182 | dev_err(mtk->dev, "ip sleep failed!!!\n"); | |
183 | return ret; | |
184 | } | |
185 | return 0; | |
186 | } | |
187 | ||
188 | static int xhci_mtk_ssusb_config(struct xhci_hcd_mtk *mtk) | |
189 | { | |
190 | struct mu3c_ippc_regs __iomem *ippc = mtk->ippc_regs; | |
191 | u32 value; | |
192 | ||
065d48cf CY |
193 | if (!mtk->has_ippc) |
194 | return 0; | |
195 | ||
0cbd4b34 CY |
196 | /* reset whole ip */ |
197 | value = readl(&ippc->ip_pw_ctr0); | |
198 | value |= CTRL0_IP_SW_RST; | |
199 | writel(value, &ippc->ip_pw_ctr0); | |
200 | udelay(1); | |
201 | value = readl(&ippc->ip_pw_ctr0); | |
202 | value &= ~CTRL0_IP_SW_RST; | |
203 | writel(value, &ippc->ip_pw_ctr0); | |
204 | ||
205 | /* | |
206 | * device ip is default power-on in fact | |
207 | * power down device ip, otherwise ip-sleep will fail | |
208 | */ | |
209 | value = readl(&ippc->ip_pw_ctr2); | |
210 | value |= CTRL2_IP_DEV_PDN; | |
211 | writel(value, &ippc->ip_pw_ctr2); | |
212 | ||
213 | value = readl(&ippc->ip_xhci_cap); | |
214 | mtk->num_u3_ports = CAP_U3_PORT_NUM(value); | |
215 | mtk->num_u2_ports = CAP_U2_PORT_NUM(value); | |
216 | dev_dbg(mtk->dev, "%s u2p:%d, u3p:%d\n", __func__, | |
217 | mtk->num_u2_ports, mtk->num_u3_ports); | |
218 | ||
219 | return xhci_mtk_host_enable(mtk); | |
220 | } | |
221 | ||
0cbd4b34 | 222 | /* only clocks can be turn off for ip-sleep wakeup mode */ |
a2ecc4df | 223 | static void usb_wakeup_ip_sleep_set(struct xhci_hcd_mtk *mtk, bool enable) |
0cbd4b34 | 224 | { |
a2ecc4df CY |
225 | u32 reg, msk, val; |
226 | ||
227 | switch (mtk->uwk_vers) { | |
228 | case SSUSB_UWK_V1: | |
229 | reg = mtk->uwk_reg_base + PERI_WK_CTRL1; | |
230 | msk = WC1_IS_EN | WC1_IS_C(0xf) | WC1_IS_P; | |
231 | val = enable ? (WC1_IS_EN | WC1_IS_C(0x8)) : 0; | |
232 | break; | |
c03b4ccb CY |
233 | case SSUSB_UWK_V1_1: |
234 | reg = mtk->uwk_reg_base + PERI_WK_CTRL0; | |
235 | msk = WC0_IS_EN | WC0_IS_C(0xf) | WC0_IS_P; | |
236 | val = enable ? (WC0_IS_EN | WC0_IS_C(0x8)) : 0; | |
237 | break; | |
331c5058 CY |
238 | case SSUSB_UWK_V1_2: |
239 | reg = mtk->uwk_reg_base + PERI_WK_CTRL0; | |
240 | msk = WC0_SSUSB0_CDEN | WC0_IS_SPM_EN; | |
241 | val = enable ? msk : 0; | |
242 | break; | |
a2ecc4df CY |
243 | case SSUSB_UWK_V2: |
244 | reg = mtk->uwk_reg_base + PERI_SSUSB_SPM_CTRL; | |
245 | msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN; | |
246 | val = enable ? msk : 0; | |
247 | break; | |
248 | default: | |
249 | return; | |
dae06208 | 250 | } |
a2ecc4df | 251 | regmap_update_bits(mtk->uwk, reg, msk, val); |
0cbd4b34 CY |
252 | } |
253 | ||
a2ecc4df CY |
254 | static int usb_wakeup_of_property_parse(struct xhci_hcd_mtk *mtk, |
255 | struct device_node *dn) | |
0cbd4b34 | 256 | { |
a2ecc4df CY |
257 | struct of_phandle_args args; |
258 | int ret; | |
0cbd4b34 | 259 | |
a2ecc4df CY |
260 | /* Wakeup function is optional */ |
261 | mtk->uwk_en = of_property_read_bool(dn, "wakeup-source"); | |
262 | if (!mtk->uwk_en) | |
263 | return 0; | |
0cbd4b34 | 264 | |
a2ecc4df CY |
265 | ret = of_parse_phandle_with_fixed_args(dn, |
266 | "mediatek,syscon-wakeup", 2, 0, &args); | |
267 | if (ret) | |
268 | return ret; | |
0cbd4b34 | 269 | |
a2ecc4df CY |
270 | mtk->uwk_reg_base = args.args[0]; |
271 | mtk->uwk_vers = args.args[1]; | |
272 | mtk->uwk = syscon_node_to_regmap(args.np); | |
273 | of_node_put(args.np); | |
274 | dev_info(mtk->dev, "uwk - reg:0x%x, version:%d\n", | |
275 | mtk->uwk_reg_base, mtk->uwk_vers); | |
0cbd4b34 | 276 | |
a2ecc4df | 277 | return PTR_ERR_OR_ZERO(mtk->uwk); |
0cbd4b34 CY |
278 | } |
279 | ||
a2ecc4df | 280 | static void usb_wakeup_set(struct xhci_hcd_mtk *mtk, bool enable) |
0cbd4b34 | 281 | { |
a2ecc4df CY |
282 | if (mtk->uwk_en) |
283 | usb_wakeup_ip_sleep_set(mtk, enable); | |
0cbd4b34 CY |
284 | } |
285 | ||
7fed6368 CY |
286 | static int xhci_mtk_clks_get(struct xhci_hcd_mtk *mtk) |
287 | { | |
288 | struct clk_bulk_data *clks = mtk->clks; | |
289 | ||
290 | clks[0].id = "sys_ck"; | |
291 | clks[1].id = "xhci_ck"; | |
292 | clks[2].id = "ref_ck"; | |
293 | clks[3].id = "mcu_ck"; | |
294 | clks[4].id = "dma_ck"; | |
295 | ||
296 | return devm_clk_bulk_get_optional(mtk->dev, BULK_CLKS_NUM, clks); | |
297 | } | |
298 | ||
0cbd4b34 CY |
299 | static int xhci_mtk_ldos_enable(struct xhci_hcd_mtk *mtk) |
300 | { | |
301 | int ret; | |
302 | ||
303 | ret = regulator_enable(mtk->vbus); | |
304 | if (ret) { | |
305 | dev_err(mtk->dev, "failed to enable vbus\n"); | |
306 | return ret; | |
307 | } | |
308 | ||
309 | ret = regulator_enable(mtk->vusb33); | |
310 | if (ret) { | |
311 | dev_err(mtk->dev, "failed to enable vusb33\n"); | |
312 | regulator_disable(mtk->vbus); | |
313 | return ret; | |
314 | } | |
315 | return 0; | |
316 | } | |
317 | ||
318 | static void xhci_mtk_ldos_disable(struct xhci_hcd_mtk *mtk) | |
319 | { | |
320 | regulator_disable(mtk->vbus); | |
321 | regulator_disable(mtk->vusb33); | |
322 | } | |
323 | ||
324 | static void xhci_mtk_quirks(struct device *dev, struct xhci_hcd *xhci) | |
325 | { | |
326 | struct usb_hcd *hcd = xhci_to_hcd(xhci); | |
327 | struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd); | |
328 | ||
329 | /* | |
330 | * As of now platform drivers don't provide MSI support so we ensure | |
331 | * here that the generic code does not try to make a pci_dev from our | |
332 | * dev struct in order to setup MSI | |
333 | */ | |
334 | xhci->quirks |= XHCI_PLAT; | |
335 | xhci->quirks |= XHCI_MTK_HOST; | |
336 | /* | |
337 | * MTK host controller gives a spurious successful event after a | |
338 | * short transfer. Ignore it. | |
339 | */ | |
340 | xhci->quirks |= XHCI_SPURIOUS_SUCCESS; | |
341 | if (mtk->lpm_support) | |
342 | xhci->quirks |= XHCI_LPM_SUPPORT; | |
bee1f89a CY |
343 | if (mtk->u2_lpm_disable) |
344 | xhci->quirks |= XHCI_HW_LPM_DISABLE; | |
1f743c87 CY |
345 | |
346 | /* | |
347 | * MTK xHCI 0.96: PSA is 1 by default even if doesn't support stream, | |
348 | * and it's 3 when support it. | |
349 | */ | |
350 | if (xhci->hci_version < 0x100 && HCC_MAX_PSA(xhci->hcc_params) == 4) | |
351 | xhci->quirks |= XHCI_BROKEN_STREAMS; | |
0cbd4b34 CY |
352 | } |
353 | ||
354 | /* called during probe() after chip reset completes */ | |
355 | static int xhci_mtk_setup(struct usb_hcd *hcd) | |
356 | { | |
357 | struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd); | |
358 | int ret; | |
359 | ||
360 | if (usb_hcd_is_primary_hcd(hcd)) { | |
361 | ret = xhci_mtk_ssusb_config(mtk); | |
362 | if (ret) | |
363 | return ret; | |
065d48cf CY |
364 | } |
365 | ||
366 | ret = xhci_gen_setup(hcd, xhci_mtk_quirks); | |
367 | if (ret) | |
368 | return ret; | |
369 | ||
370 | if (usb_hcd_is_primary_hcd(hcd)) { | |
0cbd4b34 CY |
371 | ret = xhci_mtk_sch_init(mtk); |
372 | if (ret) | |
373 | return ret; | |
374 | } | |
375 | ||
065d48cf | 376 | return ret; |
0cbd4b34 CY |
377 | } |
378 | ||
dc9d3b2c CY |
379 | static const struct xhci_driver_overrides xhci_mtk_overrides __initconst = { |
380 | .reset = xhci_mtk_setup, | |
14295a15 CY |
381 | .add_endpoint = xhci_mtk_add_ep, |
382 | .drop_endpoint = xhci_mtk_drop_ep, | |
dc9d3b2c CY |
383 | .check_bandwidth = xhci_mtk_check_bandwidth, |
384 | .reset_bandwidth = xhci_mtk_reset_bandwidth, | |
385 | }; | |
386 | ||
387 | static struct hc_driver __read_mostly xhci_mtk_hc_driver; | |
388 | ||
0cbd4b34 CY |
389 | static int xhci_mtk_probe(struct platform_device *pdev) |
390 | { | |
391 | struct device *dev = &pdev->dev; | |
392 | struct device_node *node = dev->of_node; | |
393 | struct xhci_hcd_mtk *mtk; | |
394 | const struct hc_driver *driver; | |
395 | struct xhci_hcd *xhci; | |
396 | struct resource *res; | |
397 | struct usb_hcd *hcd; | |
0cbd4b34 | 398 | int ret = -ENODEV; |
04284eb7 | 399 | int wakeup_irq; |
0cbd4b34 CY |
400 | int irq; |
401 | ||
402 | if (usb_disabled()) | |
403 | return -ENODEV; | |
404 | ||
405 | driver = &xhci_mtk_hc_driver; | |
406 | mtk = devm_kzalloc(dev, sizeof(*mtk), GFP_KERNEL); | |
407 | if (!mtk) | |
408 | return -ENOMEM; | |
409 | ||
410 | mtk->dev = dev; | |
411 | mtk->vbus = devm_regulator_get(dev, "vbus"); | |
412 | if (IS_ERR(mtk->vbus)) { | |
413 | dev_err(dev, "fail to get vbus\n"); | |
414 | return PTR_ERR(mtk->vbus); | |
415 | } | |
416 | ||
417 | mtk->vusb33 = devm_regulator_get(dev, "vusb33"); | |
418 | if (IS_ERR(mtk->vusb33)) { | |
419 | dev_err(dev, "fail to get vusb33\n"); | |
420 | return PTR_ERR(mtk->vusb33); | |
421 | } | |
422 | ||
b6bb72cf CY |
423 | ret = xhci_mtk_clks_get(mtk); |
424 | if (ret) | |
425 | return ret; | |
9c4afd42 | 426 | |
04284eb7 CY |
427 | irq = platform_get_irq_byname_optional(pdev, "host"); |
428 | if (irq < 0) { | |
429 | if (irq == -EPROBE_DEFER) | |
430 | return irq; | |
431 | ||
432 | /* for backward compatibility */ | |
433 | irq = platform_get_irq(pdev, 0); | |
434 | if (irq < 0) | |
435 | return irq; | |
436 | } | |
437 | ||
438 | wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup"); | |
439 | if (wakeup_irq == -EPROBE_DEFER) | |
440 | return wakeup_irq; | |
441 | ||
0cbd4b34 | 442 | mtk->lpm_support = of_property_read_bool(node, "usb3-lpm-capable"); |
bee1f89a | 443 | mtk->u2_lpm_disable = of_property_read_bool(node, "usb2-lpm-disable"); |
55ba6e9e CY |
444 | /* optional property, ignore the error if it does not exist */ |
445 | of_property_read_u32(node, "mediatek,u3p-dis-msk", | |
446 | &mtk->u3p_dis_msk); | |
0cbd4b34 CY |
447 | |
448 | ret = usb_wakeup_of_property_parse(mtk, node); | |
a2ecc4df CY |
449 | if (ret) { |
450 | dev_err(dev, "failed to parse uwk property\n"); | |
0cbd4b34 | 451 | return ret; |
a2ecc4df | 452 | } |
0cbd4b34 | 453 | |
04284eb7 CY |
454 | pm_runtime_set_active(dev); |
455 | pm_runtime_use_autosuspend(dev); | |
456 | pm_runtime_set_autosuspend_delay(dev, 4000); | |
0cbd4b34 CY |
457 | pm_runtime_enable(dev); |
458 | pm_runtime_get_sync(dev); | |
0cbd4b34 CY |
459 | |
460 | ret = xhci_mtk_ldos_enable(mtk); | |
461 | if (ret) | |
462 | goto disable_pm; | |
463 | ||
7fed6368 | 464 | ret = clk_bulk_prepare_enable(BULK_CLKS_NUM, mtk->clks); |
0cbd4b34 CY |
465 | if (ret) |
466 | goto disable_ldos; | |
467 | ||
0cbd4b34 CY |
468 | hcd = usb_create_hcd(driver, dev, dev_name(dev)); |
469 | if (!hcd) { | |
470 | ret = -ENOMEM; | |
471 | goto disable_clk; | |
472 | } | |
473 | ||
474 | /* | |
475 | * USB 2.0 roothub is stored in the platform_device. | |
476 | * Swap it with mtk HCD. | |
477 | */ | |
478 | mtk->hcd = platform_get_drvdata(pdev); | |
479 | platform_set_drvdata(pdev, mtk); | |
480 | ||
065d48cf | 481 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mac"); |
0cbd4b34 CY |
482 | hcd->regs = devm_ioremap_resource(dev, res); |
483 | if (IS_ERR(hcd->regs)) { | |
484 | ret = PTR_ERR(hcd->regs); | |
485 | goto put_usb2_hcd; | |
486 | } | |
487 | hcd->rsrc_start = res->start; | |
488 | hcd->rsrc_len = resource_size(res); | |
489 | ||
065d48cf CY |
490 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ippc"); |
491 | if (res) { /* ippc register is optional */ | |
492 | mtk->ippc_regs = devm_ioremap_resource(dev, res); | |
493 | if (IS_ERR(mtk->ippc_regs)) { | |
494 | ret = PTR_ERR(mtk->ippc_regs); | |
495 | goto put_usb2_hcd; | |
496 | } | |
497 | mtk->has_ippc = true; | |
498 | } else { | |
499 | mtk->has_ippc = false; | |
0cbd4b34 CY |
500 | } |
501 | ||
0cbd4b34 CY |
502 | device_init_wakeup(dev, true); |
503 | ||
504 | xhci = hcd_to_xhci(hcd); | |
505 | xhci->main_hcd = hcd; | |
ab725cbe AW |
506 | |
507 | /* | |
508 | * imod_interval is the interrupt moderation value in nanoseconds. | |
509 | * The increment interval is 8 times as much as that defined in | |
510 | * the xHCI spec on MTK's controller. | |
511 | */ | |
512 | xhci->imod_interval = 5000; | |
513 | device_property_read_u32(dev, "imod-interval-ns", &xhci->imod_interval); | |
514 | ||
0cbd4b34 CY |
515 | xhci->shared_hcd = usb_create_shared_hcd(driver, dev, |
516 | dev_name(dev), hcd); | |
517 | if (!xhci->shared_hcd) { | |
518 | ret = -ENOMEM; | |
6ae9f506 | 519 | goto disable_device_wakeup; |
0cbd4b34 CY |
520 | } |
521 | ||
0cbd4b34 CY |
522 | ret = usb_add_hcd(hcd, irq, IRQF_SHARED); |
523 | if (ret) | |
524 | goto put_usb3_hcd; | |
525 | ||
1f743c87 CY |
526 | if (HCC_MAX_PSA(xhci->hcc_params) >= 4 && |
527 | !(xhci->quirks & XHCI_BROKEN_STREAMS)) | |
94a631d9 CY |
528 | xhci->shared_hcd->can_do_streams = 1; |
529 | ||
0cbd4b34 CY |
530 | ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); |
531 | if (ret) | |
532 | goto dealloc_usb2_hcd; | |
533 | ||
04284eb7 CY |
534 | if (wakeup_irq > 0) { |
535 | ret = dev_pm_set_dedicated_wake_irq(dev, wakeup_irq); | |
536 | if (ret) { | |
537 | dev_err(dev, "set wakeup irq %d failed\n", wakeup_irq); | |
538 | goto dealloc_usb3_hcd; | |
539 | } | |
540 | dev_info(dev, "wakeup irq %d\n", wakeup_irq); | |
541 | } | |
542 | ||
543 | device_enable_async_suspend(dev); | |
544 | pm_runtime_mark_last_busy(dev); | |
545 | pm_runtime_put_autosuspend(dev); | |
546 | pm_runtime_forbid(dev); | |
547 | ||
0cbd4b34 CY |
548 | return 0; |
549 | ||
04284eb7 CY |
550 | dealloc_usb3_hcd: |
551 | usb_remove_hcd(xhci->shared_hcd); | |
552 | xhci->shared_hcd = NULL; | |
553 | ||
0cbd4b34 CY |
554 | dealloc_usb2_hcd: |
555 | usb_remove_hcd(hcd); | |
556 | ||
557 | put_usb3_hcd: | |
558 | xhci_mtk_sch_exit(mtk); | |
559 | usb_put_hcd(xhci->shared_hcd); | |
560 | ||
6ae9f506 | 561 | disable_device_wakeup: |
0cbd4b34 CY |
562 | device_init_wakeup(dev, false); |
563 | ||
0cbd4b34 CY |
564 | put_usb2_hcd: |
565 | usb_put_hcd(hcd); | |
566 | ||
567 | disable_clk: | |
7fed6368 | 568 | clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks); |
0cbd4b34 CY |
569 | |
570 | disable_ldos: | |
571 | xhci_mtk_ldos_disable(mtk); | |
572 | ||
573 | disable_pm: | |
04284eb7 | 574 | pm_runtime_put_sync_autosuspend(dev); |
0cbd4b34 CY |
575 | pm_runtime_disable(dev); |
576 | return ret; | |
577 | } | |
578 | ||
04284eb7 | 579 | static int xhci_mtk_remove(struct platform_device *pdev) |
0cbd4b34 | 580 | { |
04284eb7 | 581 | struct xhci_hcd_mtk *mtk = platform_get_drvdata(pdev); |
0cbd4b34 CY |
582 | struct usb_hcd *hcd = mtk->hcd; |
583 | struct xhci_hcd *xhci = hcd_to_xhci(hcd); | |
f0680904 | 584 | struct usb_hcd *shared_hcd = xhci->shared_hcd; |
04284eb7 | 585 | struct device *dev = &pdev->dev; |
0cbd4b34 | 586 | |
04284eb7 CY |
587 | pm_runtime_get_sync(dev); |
588 | xhci->xhc_state |= XHCI_STATE_REMOVING; | |
589 | dev_pm_clear_wake_irq(dev); | |
590 | device_init_wakeup(dev, false); | |
a24d5072 | 591 | |
f0680904 MN |
592 | usb_remove_hcd(shared_hcd); |
593 | xhci->shared_hcd = NULL; | |
0cbd4b34 | 594 | usb_remove_hcd(hcd); |
f0680904 | 595 | usb_put_hcd(shared_hcd); |
0cbd4b34 CY |
596 | usb_put_hcd(hcd); |
597 | xhci_mtk_sch_exit(mtk); | |
7fed6368 | 598 | clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks); |
0cbd4b34 | 599 | xhci_mtk_ldos_disable(mtk); |
0cbd4b34 | 600 | |
04284eb7 CY |
601 | pm_runtime_disable(dev); |
602 | pm_runtime_put_noidle(dev); | |
603 | pm_runtime_set_suspended(dev); | |
604 | ||
0cbd4b34 CY |
605 | return 0; |
606 | } | |
607 | ||
8dac5300 | 608 | static int __maybe_unused xhci_mtk_suspend(struct device *dev) |
0cbd4b34 CY |
609 | { |
610 | struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev); | |
882fa27f CY |
611 | struct usb_hcd *hcd = mtk->hcd; |
612 | struct xhci_hcd *xhci = hcd_to_xhci(hcd); | |
82dad9fb | 613 | int ret; |
882fa27f CY |
614 | |
615 | xhci_dbg(xhci, "%s: stop port polling\n", __func__); | |
616 | clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); | |
617 | del_timer_sync(&hcd->rh_timer); | |
618 | clear_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags); | |
619 | del_timer_sync(&xhci->shared_hcd->rh_timer); | |
0cbd4b34 | 620 | |
82dad9fb CY |
621 | ret = xhci_mtk_host_disable(mtk); |
622 | if (ret) | |
623 | goto restart_poll_rh; | |
624 | ||
7fed6368 | 625 | clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks); |
a2ecc4df | 626 | usb_wakeup_set(mtk, true); |
0cbd4b34 | 627 | return 0; |
82dad9fb CY |
628 | |
629 | restart_poll_rh: | |
630 | xhci_dbg(xhci, "%s: restart port polling\n", __func__); | |
631 | set_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags); | |
632 | usb_hcd_poll_rh_status(xhci->shared_hcd); | |
633 | set_bit(HCD_FLAG_POLL_RH, &hcd->flags); | |
634 | usb_hcd_poll_rh_status(hcd); | |
635 | return ret; | |
0cbd4b34 CY |
636 | } |
637 | ||
8dac5300 | 638 | static int __maybe_unused xhci_mtk_resume(struct device *dev) |
0cbd4b34 CY |
639 | { |
640 | struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev); | |
882fa27f CY |
641 | struct usb_hcd *hcd = mtk->hcd; |
642 | struct xhci_hcd *xhci = hcd_to_xhci(hcd); | |
82dad9fb | 643 | int ret; |
0cbd4b34 | 644 | |
a2ecc4df | 645 | usb_wakeup_set(mtk, false); |
7fed6368 | 646 | ret = clk_bulk_prepare_enable(BULK_CLKS_NUM, mtk->clks); |
82dad9fb CY |
647 | if (ret) |
648 | goto enable_wakeup; | |
649 | ||
650 | ret = xhci_mtk_host_enable(mtk); | |
651 | if (ret) | |
652 | goto disable_clks; | |
882fa27f CY |
653 | |
654 | xhci_dbg(xhci, "%s: restart port polling\n", __func__); | |
882fa27f CY |
655 | set_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags); |
656 | usb_hcd_poll_rh_status(xhci->shared_hcd); | |
555df582 CY |
657 | set_bit(HCD_FLAG_POLL_RH, &hcd->flags); |
658 | usb_hcd_poll_rh_status(hcd); | |
0cbd4b34 | 659 | return 0; |
82dad9fb CY |
660 | |
661 | disable_clks: | |
7fed6368 | 662 | clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks); |
82dad9fb CY |
663 | enable_wakeup: |
664 | usb_wakeup_set(mtk, true); | |
665 | return ret; | |
0cbd4b34 CY |
666 | } |
667 | ||
04284eb7 CY |
668 | static int __maybe_unused xhci_mtk_runtime_suspend(struct device *dev) |
669 | { | |
670 | struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev); | |
671 | struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd); | |
672 | int ret = 0; | |
673 | ||
674 | if (xhci->xhc_state) | |
675 | return -ESHUTDOWN; | |
676 | ||
5951b7c2 CY |
677 | if (device_may_wakeup(dev)) |
678 | ret = xhci_mtk_suspend(dev); | |
04284eb7 CY |
679 | |
680 | /* -EBUSY: let PM automatically reschedule another autosuspend */ | |
681 | return ret ? -EBUSY : 0; | |
682 | } | |
683 | ||
684 | static int __maybe_unused xhci_mtk_runtime_resume(struct device *dev) | |
685 | { | |
686 | struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev); | |
687 | struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd); | |
688 | int ret = 0; | |
689 | ||
690 | if (xhci->xhc_state) | |
691 | return -ESHUTDOWN; | |
692 | ||
693 | if (device_may_wakeup(dev)) | |
694 | ret = xhci_mtk_resume(dev); | |
695 | ||
696 | return ret; | |
697 | } | |
698 | ||
0cbd4b34 CY |
699 | static const struct dev_pm_ops xhci_mtk_pm_ops = { |
700 | SET_SYSTEM_SLEEP_PM_OPS(xhci_mtk_suspend, xhci_mtk_resume) | |
04284eb7 CY |
701 | SET_RUNTIME_PM_OPS(xhci_mtk_runtime_suspend, |
702 | xhci_mtk_runtime_resume, NULL) | |
0cbd4b34 | 703 | }; |
04284eb7 CY |
704 | |
705 | #define DEV_PM_OPS (IS_ENABLED(CONFIG_PM) ? &xhci_mtk_pm_ops : NULL) | |
0cbd4b34 | 706 | |
0cbd4b34 CY |
707 | static const struct of_device_id mtk_xhci_of_match[] = { |
708 | { .compatible = "mediatek,mt8173-xhci"}, | |
5675b4d4 | 709 | { .compatible = "mediatek,mtk-xhci"}, |
0cbd4b34 CY |
710 | { }, |
711 | }; | |
712 | MODULE_DEVICE_TABLE(of, mtk_xhci_of_match); | |
0cbd4b34 CY |
713 | |
714 | static struct platform_driver mtk_xhci_driver = { | |
715 | .probe = xhci_mtk_probe, | |
716 | .remove = xhci_mtk_remove, | |
717 | .driver = { | |
718 | .name = "xhci-mtk", | |
719 | .pm = DEV_PM_OPS, | |
6144ef35 | 720 | .of_match_table = mtk_xhci_of_match, |
0cbd4b34 CY |
721 | }, |
722 | }; | |
0cbd4b34 CY |
723 | |
724 | static int __init xhci_mtk_init(void) | |
725 | { | |
726 | xhci_init_driver(&xhci_mtk_hc_driver, &xhci_mtk_overrides); | |
727 | return platform_driver_register(&mtk_xhci_driver); | |
728 | } | |
729 | module_init(xhci_mtk_init); | |
730 | ||
731 | static void __exit xhci_mtk_exit(void) | |
732 | { | |
733 | platform_driver_unregister(&mtk_xhci_driver); | |
734 | } | |
735 | module_exit(xhci_mtk_exit); | |
736 | ||
737 | MODULE_AUTHOR("Chunfeng Yun <chunfeng.yun@mediatek.com>"); | |
738 | MODULE_DESCRIPTION("MediaTek xHCI Host Controller Driver"); | |
739 | MODULE_LICENSE("GPL v2"); |