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 #include <linux/usb.h> 14 #include <linux/usb/hcd.h> 15 16 #include "../host/xhci-port.h" 17 #include "../host/xhci-ext-caps.h" 18 #include "../host/xhci-caps.h" 19 #include "../host/xhci-plat.h" 20 #include "core.h" 21 22 #define XHCI_HCSPARAMS1 0x4 23 #define XHCI_PORTSC_BASE 0x400 24 25 /** 26 * dwc3_power_off_all_roothub_ports - Power off all Root hub ports 27 * @dwc: Pointer to our controller context structure 28 */ 29 static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc) 30 { 31 void __iomem *xhci_regs; 32 u32 op_regs_base; 33 int port_num; 34 u32 offset; 35 u32 reg; 36 int i; 37 38 /* xhci regs are not mapped yet, do it temporarily here */ 39 if (dwc->xhci_resources[0].start) { 40 if (dwc->xhci_resources[0].flags & IORESOURCE_MEM_NONPOSTED) 41 xhci_regs = ioremap_np(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END); 42 else 43 xhci_regs = ioremap(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END); 44 if (!xhci_regs) { 45 dev_err(dwc->dev, "Failed to ioremap xhci_regs\n"); 46 return; 47 } 48 49 op_regs_base = HC_LENGTH(readl(xhci_regs)); 50 reg = readl(xhci_regs + XHCI_HCSPARAMS1); 51 port_num = HCS_MAX_PORTS(reg); 52 53 for (i = 1; i <= port_num; i++) { 54 offset = op_regs_base + XHCI_PORTSC_BASE + 0x10 * (i - 1); 55 reg = readl(xhci_regs + offset); 56 reg &= ~PORT_POWER; 57 writel(reg, xhci_regs + offset); 58 } 59 60 iounmap(xhci_regs); 61 } else { 62 dev_err(dwc->dev, "xhci base reg invalid\n"); 63 } 64 } 65 66 static void dwc3_xhci_plat_start(struct usb_hcd *hcd) 67 { 68 struct platform_device *pdev; 69 struct dwc3 *dwc; 70 71 if (!usb_hcd_is_primary_hcd(hcd)) 72 return; 73 74 pdev = to_platform_device(hcd->self.controller); 75 dwc = dev_get_drvdata(pdev->dev.parent); 76 77 dwc3_enable_susphy(dwc, true); 78 } 79 80 static const struct xhci_plat_priv dwc3_xhci_plat_quirk = { 81 .plat_start = dwc3_xhci_plat_start, 82 }; 83 84 static void dwc3_host_fill_xhci_irq_res(struct dwc3 *dwc, 85 int irq, char *name) 86 { 87 struct platform_device *pdev = to_platform_device(dwc->dev); 88 struct device_node *np = dev_of_node(&pdev->dev); 89 90 dwc->xhci_resources[1].start = irq; 91 dwc->xhci_resources[1].end = irq; 92 dwc->xhci_resources[1].flags = IORESOURCE_IRQ | irq_get_trigger_type(irq); 93 if (!name && np) 94 dwc->xhci_resources[1].name = of_node_full_name(pdev->dev.of_node); 95 else 96 dwc->xhci_resources[1].name = name; 97 } 98 99 static int dwc3_host_get_irq(struct dwc3 *dwc) 100 { 101 struct platform_device *dwc3_pdev = to_platform_device(dwc->dev); 102 int irq; 103 104 irq = platform_get_irq_byname_optional(dwc3_pdev, "host"); 105 if (irq > 0) { 106 dwc3_host_fill_xhci_irq_res(dwc, irq, "host"); 107 goto out; 108 } 109 110 if (irq == -EPROBE_DEFER) 111 goto out; 112 113 irq = platform_get_irq_byname_optional(dwc3_pdev, "dwc_usb3"); 114 if (irq > 0) { 115 dwc3_host_fill_xhci_irq_res(dwc, irq, "dwc_usb3"); 116 goto out; 117 } 118 119 if (irq == -EPROBE_DEFER) 120 goto out; 121 122 irq = platform_get_irq(dwc3_pdev, 0); 123 if (irq > 0) 124 dwc3_host_fill_xhci_irq_res(dwc, irq, NULL); 125 126 out: 127 return irq; 128 } 129 130 int dwc3_host_init(struct dwc3 *dwc) 131 { 132 struct property_entry props[6]; 133 struct platform_device *xhci; 134 int ret, irq; 135 int prop_idx = 0; 136 137 /* 138 * Some platforms need to power off all Root hub ports immediately after DWC3 set to host 139 * mode to avoid VBUS glitch happen when xhci get reset later. 140 */ 141 dwc3_power_off_all_roothub_ports(dwc); 142 143 irq = dwc3_host_get_irq(dwc); 144 if (irq < 0) 145 return irq; 146 147 xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO); 148 if (!xhci) { 149 dev_err(dwc->dev, "couldn't allocate xHCI device\n"); 150 return -ENOMEM; 151 } 152 153 xhci->dev.parent = dwc->dev; 154 155 dwc->xhci = xhci; 156 157 ret = platform_device_add_resources(xhci, dwc->xhci_resources, 158 DWC3_XHCI_RESOURCES_NUM); 159 if (ret) { 160 dev_err(dwc->dev, "couldn't add resources to xHCI device\n"); 161 goto err; 162 } 163 164 memset(props, 0, sizeof(struct property_entry) * ARRAY_SIZE(props)); 165 166 props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-sg-trb-cache-size-quirk"); 167 168 props[prop_idx++] = PROPERTY_ENTRY_BOOL("write-64-hi-lo-quirk"); 169 170 if (dwc->usb3_lpm_capable) 171 props[prop_idx++] = PROPERTY_ENTRY_BOOL("usb3-lpm-capable"); 172 173 if (dwc->usb2_lpm_disable) 174 props[prop_idx++] = PROPERTY_ENTRY_BOOL("usb2-lpm-disable"); 175 176 /** 177 * WORKAROUND: dwc3 revisions <=3.00a have a limitation 178 * where Port Disable command doesn't work. 179 * 180 * The suggested workaround is that we avoid Port Disable 181 * completely. 182 * 183 * This following flag tells XHCI to do just that. 184 */ 185 if (DWC3_VER_IS_WITHIN(DWC3, ANY, 300A)) 186 props[prop_idx++] = PROPERTY_ENTRY_BOOL("quirk-broken-port-ped"); 187 188 props[prop_idx++] = PROPERTY_ENTRY_U16("num-hc-interrupters", 189 dwc->num_hc_interrupters); 190 191 if (prop_idx) { 192 ret = device_create_managed_software_node(&xhci->dev, props, NULL); 193 if (ret) { 194 dev_err(dwc->dev, "failed to add properties to xHCI\n"); 195 goto err; 196 } 197 } 198 199 ret = platform_device_add_data(xhci, &dwc3_xhci_plat_quirk, 200 sizeof(struct xhci_plat_priv)); 201 if (ret) 202 goto err; 203 204 ret = platform_device_add(xhci); 205 if (ret) { 206 dev_err(dwc->dev, "failed to register xHCI device\n"); 207 goto err; 208 } 209 210 if (dwc->sys_wakeup) { 211 /* Restore wakeup setting if switched from device */ 212 device_wakeup_enable(dwc->sysdev); 213 214 /* Pass on wakeup setting to the new xhci platform device */ 215 device_init_wakeup(&xhci->dev, true); 216 } 217 218 return 0; 219 err: 220 platform_device_put(xhci); 221 return ret; 222 } 223 EXPORT_SYMBOL_GPL(dwc3_host_init); 224 225 void dwc3_host_exit(struct dwc3 *dwc) 226 { 227 if (dwc->sys_wakeup) 228 device_init_wakeup(&dwc->xhci->dev, false); 229 230 dwc3_enable_susphy(dwc, false); 231 platform_device_unregister(dwc->xhci); 232 dwc->xhci = NULL; 233 } 234 EXPORT_SYMBOL_GPL(dwc3_host_exit); 235