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(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(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(xhci_regs + offset);
50			reg &= ~PORT_POWER;
51			writel(reg, xhci_regs + offset);
52		}
53
54		iounmap(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(&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(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(dwc3_pdev, "host");
81	if (irq > 0) {
82		dwc3_host_fill_xhci_irq_res(dwc, irq, "host");
83		goto out;
84	}
85
86	if (irq == -EPROBE_DEFER)
87		goto out;
88
89	irq = platform_get_irq_byname_optional(dwc3_pdev, "dwc_usb3");
90	if (irq > 0) {
91		dwc3_host_fill_xhci_irq_res(dwc, irq, "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("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(xhci, 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(&xhci->dev, 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(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(dwc->sysdev);
179
180		/* Pass on wakeup setting to the new xhci platform device */
181		device_init_wakeup(&xhci->dev, true);
182	}
183
184	return 0;
185err:
186	platform_device_put(xhci);
187	return ret;
188}
189
190void dwc3_host_exit(struct dwc3 *dwc)
191{
192	if (dwc->sys_wakeup)
193		device_init_wakeup(&dwc->xhci->dev, false);
194
195	platform_device_unregister(dwc->xhci);
196	dwc->xhci = NULL;
197}
198