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 */
26static 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
60static 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
75static 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
102out:
103 return irq;
104}
105
106int 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;
185err:
186 platform_device_put(pdev: xhci);
187 return ret;
188}
189
190void 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

source code of linux/drivers/usb/dwc3/host.c