1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Cadence USBSS and USBSSP DRD Driver - host side |
4 | * |
5 | * Copyright (C) 2018-2019 Cadence Design Systems. |
6 | * Copyright (C) 2017-2018 NXP |
7 | * |
8 | * Authors: Peter Chen <peter.chen@nxp.com> |
9 | * Pawel Laszczak <pawell@cadence.com> |
10 | */ |
11 | |
12 | #include <linux/platform_device.h> |
13 | #include <linux/slab.h> |
14 | #include "core.h" |
15 | #include "drd.h" |
16 | #include "host-export.h" |
17 | #include <linux/usb/hcd.h> |
18 | #include "../host/xhci.h" |
19 | #include "../host/xhci-plat.h" |
20 | |
21 | /* |
22 | * The XECP_PORT_CAP_REG and XECP_AUX_CTRL_REG1 exist only |
23 | * in Cadence USB3 dual-role controller, so it can't be used |
24 | * with Cadence CDNSP dual-role controller. |
25 | */ |
26 | #define XECP_PORT_CAP_REG 0x8000 |
27 | #define XECP_AUX_CTRL_REG1 0x8120 |
28 | |
29 | #define CFG_RXDET_P3_EN BIT(15) |
30 | #define LPM_2_STB_SWITCH_EN BIT(25) |
31 | |
32 | static void xhci_cdns3_plat_start(struct usb_hcd *hcd) |
33 | { |
34 | struct xhci_hcd *xhci = hcd_to_xhci(hcd); |
35 | u32 value; |
36 | |
37 | /* set usbcmd.EU3S */ |
38 | value = readl(addr: &xhci->op_regs->command); |
39 | value |= CMD_PM_INDEX; |
40 | writel(val: value, addr: &xhci->op_regs->command); |
41 | |
42 | if (hcd->regs) { |
43 | value = readl(addr: hcd->regs + XECP_AUX_CTRL_REG1); |
44 | value |= CFG_RXDET_P3_EN; |
45 | writel(val: value, addr: hcd->regs + XECP_AUX_CTRL_REG1); |
46 | |
47 | value = readl(addr: hcd->regs + XECP_PORT_CAP_REG); |
48 | value |= LPM_2_STB_SWITCH_EN; |
49 | writel(val: value, addr: hcd->regs + XECP_PORT_CAP_REG); |
50 | } |
51 | } |
52 | |
53 | static int xhci_cdns3_resume_quirk(struct usb_hcd *hcd) |
54 | { |
55 | xhci_cdns3_plat_start(hcd); |
56 | return 0; |
57 | } |
58 | |
59 | static const struct xhci_plat_priv xhci_plat_cdns3_xhci = { |
60 | .quirks = XHCI_SKIP_PHY_INIT | XHCI_AVOID_BEI, |
61 | .plat_start = xhci_cdns3_plat_start, |
62 | .resume_quirk = xhci_cdns3_resume_quirk, |
63 | }; |
64 | |
65 | static const struct xhci_plat_priv xhci_plat_cdnsp_xhci; |
66 | |
67 | static int __cdns_host_init(struct cdns *cdns) |
68 | { |
69 | struct platform_device *xhci; |
70 | int ret; |
71 | struct usb_hcd *hcd; |
72 | |
73 | cdns_drd_host_on(cdns); |
74 | |
75 | xhci = platform_device_alloc(name: "xhci-hcd" , PLATFORM_DEVID_AUTO); |
76 | if (!xhci) { |
77 | dev_err(cdns->dev, "couldn't allocate xHCI device\n" ); |
78 | return -ENOMEM; |
79 | } |
80 | |
81 | xhci->dev.parent = cdns->dev; |
82 | cdns->host_dev = xhci; |
83 | |
84 | ret = platform_device_add_resources(pdev: xhci, res: cdns->xhci_res, |
85 | CDNS_XHCI_RESOURCES_NUM); |
86 | if (ret) { |
87 | dev_err(cdns->dev, "couldn't add resources to xHCI device\n" ); |
88 | goto err1; |
89 | } |
90 | |
91 | if (cdns->version < CDNSP_CONTROLLER_V2) |
92 | cdns->xhci_plat_data = kmemdup(p: &xhci_plat_cdns3_xhci, |
93 | size: sizeof(struct xhci_plat_priv), GFP_KERNEL); |
94 | else |
95 | cdns->xhci_plat_data = kmemdup(p: &xhci_plat_cdnsp_xhci, |
96 | size: sizeof(struct xhci_plat_priv), GFP_KERNEL); |
97 | |
98 | if (!cdns->xhci_plat_data) { |
99 | ret = -ENOMEM; |
100 | goto err1; |
101 | } |
102 | |
103 | if (cdns->pdata && (cdns->pdata->quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW)) |
104 | cdns->xhci_plat_data->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW; |
105 | |
106 | ret = platform_device_add_data(pdev: xhci, data: cdns->xhci_plat_data, |
107 | size: sizeof(struct xhci_plat_priv)); |
108 | if (ret) |
109 | goto free_memory; |
110 | |
111 | ret = platform_device_add(pdev: xhci); |
112 | if (ret) { |
113 | dev_err(cdns->dev, "failed to register xHCI device\n" ); |
114 | goto free_memory; |
115 | } |
116 | |
117 | /* Glue needs to access xHCI region register for Power management */ |
118 | hcd = platform_get_drvdata(pdev: xhci); |
119 | if (hcd) |
120 | cdns->xhci_regs = hcd->regs; |
121 | |
122 | return 0; |
123 | |
124 | free_memory: |
125 | kfree(objp: cdns->xhci_plat_data); |
126 | err1: |
127 | platform_device_put(pdev: xhci); |
128 | return ret; |
129 | } |
130 | |
131 | static void cdns_host_exit(struct cdns *cdns) |
132 | { |
133 | kfree(objp: cdns->xhci_plat_data); |
134 | platform_device_unregister(cdns->host_dev); |
135 | cdns->host_dev = NULL; |
136 | cdns_drd_host_off(cdns); |
137 | } |
138 | |
139 | int cdns_host_init(struct cdns *cdns) |
140 | { |
141 | struct cdns_role_driver *rdrv; |
142 | |
143 | rdrv = devm_kzalloc(dev: cdns->dev, size: sizeof(*rdrv), GFP_KERNEL); |
144 | if (!rdrv) |
145 | return -ENOMEM; |
146 | |
147 | rdrv->start = __cdns_host_init; |
148 | rdrv->stop = cdns_host_exit; |
149 | rdrv->state = CDNS_ROLE_STATE_INACTIVE; |
150 | rdrv->name = "host" ; |
151 | |
152 | cdns->roles[USB_ROLE_HOST] = rdrv; |
153 | |
154 | return 0; |
155 | } |
156 | |