1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * host.c - DesignWare USB3 DRD Controller Host Glue |
4 | * |
5 | * Copyright (C) 2011 Texas Instruments Incorporated - https://www.ti.com |
6 | * |
7 | * Authors: Felipe Balbi <balbi@ti.com>, |
8 | */ |
9 | |
10 | #include <linux/irq.h> |
11 | #include <linux/of.h> |
12 | #include <linux/platform_device.h> |
13 | |
14 | #include "../host/xhci-port.h" |
15 | #include "../host/xhci-ext-caps.h" |
16 | #include "../host/xhci-caps.h" |
17 | #include "core.h" |
18 | |
19 | #define XHCI_HCSPARAMS1 0x4 |
20 | #define XHCI_PORTSC_BASE 0x400 |
21 | |
22 | /** |
23 | * dwc3_power_off_all_roothub_ports - Power off all Root hub ports |
24 | * @dwc: Pointer to our controller context structure |
25 | */ |
26 | static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc) |
27 | { |
28 | void __iomem *xhci_regs; |
29 | u32 op_regs_base; |
30 | int port_num; |
31 | u32 offset; |
32 | u32 reg; |
33 | int i; |
34 | |
35 | /* xhci regs is not mapped yet, do it temperary here */ |
36 | if (dwc->xhci_resources[0].start) { |
37 | xhci_regs = ioremap(offset: dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END); |
38 | if (!xhci_regs) { |
39 | dev_err(dwc->dev, "Failed to ioremap xhci_regs\n" ); |
40 | return; |
41 | } |
42 | |
43 | op_regs_base = HC_LENGTH(readl(xhci_regs)); |
44 | reg = readl(addr: xhci_regs + XHCI_HCSPARAMS1); |
45 | port_num = HCS_MAX_PORTS(reg); |
46 | |
47 | for (i = 1; i <= port_num; i++) { |
48 | offset = op_regs_base + XHCI_PORTSC_BASE + 0x10 * (i - 1); |
49 | reg = readl(addr: xhci_regs + offset); |
50 | reg &= ~PORT_POWER; |
51 | writel(val: reg, addr: xhci_regs + offset); |
52 | } |
53 | |
54 | iounmap(addr: xhci_regs); |
55 | } else { |
56 | dev_err(dwc->dev, "xhci base reg invalid\n" ); |
57 | } |
58 | } |
59 | |
60 | static void dwc3_host_fill_xhci_irq_res(struct dwc3 *dwc, |
61 | int irq, char *name) |
62 | { |
63 | struct platform_device *pdev = to_platform_device(dwc->dev); |
64 | struct device_node *np = dev_of_node(dev: &pdev->dev); |
65 | |
66 | dwc->xhci_resources[1].start = irq; |
67 | dwc->xhci_resources[1].end = irq; |
68 | dwc->xhci_resources[1].flags = IORESOURCE_IRQ | irq_get_trigger_type(irq); |
69 | if (!name && np) |
70 | dwc->xhci_resources[1].name = of_node_full_name(np: pdev->dev.of_node); |
71 | else |
72 | dwc->xhci_resources[1].name = name; |
73 | } |
74 | |
75 | static int dwc3_host_get_irq(struct dwc3 *dwc) |
76 | { |
77 | struct platform_device *dwc3_pdev = to_platform_device(dwc->dev); |
78 | int irq; |
79 | |
80 | irq = platform_get_irq_byname_optional(dev: dwc3_pdev, name: "host" ); |
81 | if (irq > 0) { |
82 | dwc3_host_fill_xhci_irq_res(dwc, irq, name: "host" ); |
83 | goto out; |
84 | } |
85 | |
86 | if (irq == -EPROBE_DEFER) |
87 | goto out; |
88 | |
89 | irq = platform_get_irq_byname_optional(dev: dwc3_pdev, name: "dwc_usb3" ); |
90 | if (irq > 0) { |
91 | dwc3_host_fill_xhci_irq_res(dwc, irq, name: "dwc_usb3" ); |
92 | goto out; |
93 | } |
94 | |
95 | if (irq == -EPROBE_DEFER) |
96 | goto out; |
97 | |
98 | irq = platform_get_irq(dwc3_pdev, 0); |
99 | if (irq > 0) |
100 | dwc3_host_fill_xhci_irq_res(dwc, irq, NULL); |
101 | |
102 | out: |
103 | return irq; |
104 | } |
105 | |
106 | int dwc3_host_init(struct dwc3 *dwc) |
107 | { |
108 | struct property_entry props[5]; |
109 | struct platform_device *xhci; |
110 | int ret, irq; |
111 | int prop_idx = 0; |
112 | |
113 | /* |
114 | * Some platforms need to power off all Root hub ports immediately after DWC3 set to host |
115 | * mode to avoid VBUS glitch happen when xhci get reset later. |
116 | */ |
117 | dwc3_power_off_all_roothub_ports(dwc); |
118 | |
119 | irq = dwc3_host_get_irq(dwc); |
120 | if (irq < 0) |
121 | return irq; |
122 | |
123 | xhci = platform_device_alloc(name: "xhci-hcd" , PLATFORM_DEVID_AUTO); |
124 | if (!xhci) { |
125 | dev_err(dwc->dev, "couldn't allocate xHCI device\n" ); |
126 | return -ENOMEM; |
127 | } |
128 | |
129 | xhci->dev.parent = dwc->dev; |
130 | |
131 | dwc->xhci = xhci; |
132 | |
133 | ret = platform_device_add_resources(pdev: xhci, res: dwc->xhci_resources, |
134 | DWC3_XHCI_RESOURCES_NUM); |
135 | if (ret) { |
136 | dev_err(dwc->dev, "couldn't add resources to xHCI device\n" ); |
137 | goto err; |
138 | } |
139 | |
140 | memset(props, 0, sizeof(struct property_entry) * ARRAY_SIZE(props)); |
141 | |
142 | props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-sg-trb-cache-size-quirk" ); |
143 | |
144 | if (dwc->usb3_lpm_capable) |
145 | props[prop_idx++] = PROPERTY_ENTRY_BOOL("usb3-lpm-capable" ); |
146 | |
147 | if (dwc->usb2_lpm_disable) |
148 | props[prop_idx++] = PROPERTY_ENTRY_BOOL("usb2-lpm-disable" ); |
149 | |
150 | /** |
151 | * WORKAROUND: dwc3 revisions <=3.00a have a limitation |
152 | * where Port Disable command doesn't work. |
153 | * |
154 | * The suggested workaround is that we avoid Port Disable |
155 | * completely. |
156 | * |
157 | * This following flag tells XHCI to do just that. |
158 | */ |
159 | if (DWC3_VER_IS_WITHIN(DWC3, ANY, 300A)) |
160 | props[prop_idx++] = PROPERTY_ENTRY_BOOL("quirk-broken-port-ped" ); |
161 | |
162 | if (prop_idx) { |
163 | ret = device_create_managed_software_node(dev: &xhci->dev, properties: props, NULL); |
164 | if (ret) { |
165 | dev_err(dwc->dev, "failed to add properties to xHCI\n" ); |
166 | goto err; |
167 | } |
168 | } |
169 | |
170 | ret = platform_device_add(pdev: xhci); |
171 | if (ret) { |
172 | dev_err(dwc->dev, "failed to register xHCI device\n" ); |
173 | goto err; |
174 | } |
175 | |
176 | if (dwc->sys_wakeup) { |
177 | /* Restore wakeup setting if switched from device */ |
178 | device_wakeup_enable(dev: dwc->sysdev); |
179 | |
180 | /* Pass on wakeup setting to the new xhci platform device */ |
181 | device_init_wakeup(dev: &xhci->dev, enable: true); |
182 | } |
183 | |
184 | return 0; |
185 | err: |
186 | platform_device_put(pdev: xhci); |
187 | return ret; |
188 | } |
189 | |
190 | void dwc3_host_exit(struct dwc3 *dwc) |
191 | { |
192 | if (dwc->sys_wakeup) |
193 | device_init_wakeup(dev: &dwc->xhci->dev, enable: false); |
194 | |
195 | platform_device_unregister(dwc->xhci); |
196 | dwc->xhci = NULL; |
197 | } |
198 | |