]>
Commit | Line | Data |
---|---|---|
3d829045 PL |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Cadence PCI Glue driver. | |
4 | * | |
5 | * Copyright (C) 2019 Cadence. | |
6 | * | |
7 | * Author: Pawel Laszczak <pawell@cadence.com> | |
8 | * | |
9 | */ | |
10 | ||
11 | #include <linux/platform_device.h> | |
12 | #include <linux/dma-mapping.h> | |
13 | #include <linux/kernel.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/slab.h> | |
16 | #include <linux/pci.h> | |
17 | ||
18 | #include "core.h" | |
19 | #include "gadget-export.h" | |
20 | ||
21 | #define PCI_BAR_HOST 0 | |
22 | #define PCI_BAR_OTG 0 | |
23 | #define PCI_BAR_DEV 2 | |
24 | ||
25 | #define PCI_DEV_FN_HOST_DEVICE 0 | |
26 | #define PCI_DEV_FN_OTG 1 | |
27 | ||
28 | #define PCI_DRIVER_NAME "cdns-pci-usbssp" | |
29 | #define PLAT_DRIVER_NAME "cdns-usbssp" | |
30 | ||
31 | #define CDNS_VENDOR_ID 0x17cd | |
32 | #define CDNS_DEVICE_ID 0x0100 | |
33 | #define CDNS_DRD_IF (PCI_CLASS_SERIAL_USB << 8 | 0x80) | |
34 | ||
35 | static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev) | |
36 | { | |
37 | struct pci_dev *func; | |
38 | ||
39 | /* | |
40 | * Gets the second function. | |
41 | * It's little tricky, but this platform has two function. | |
42 | * The fist keeps resources for Host/Device while the second | |
43 | * keeps resources for DRD/OTG. | |
44 | */ | |
45 | func = pci_get_device(pdev->vendor, pdev->device, NULL); | |
46 | if (!func) | |
47 | return NULL; | |
48 | ||
49 | if (func->devfn == pdev->devfn) { | |
50 | func = pci_get_device(pdev->vendor, pdev->device, func); | |
51 | if (!func) | |
52 | return NULL; | |
53 | } | |
54 | ||
55 | return func; | |
56 | } | |
57 | ||
58 | static int cdnsp_pci_probe(struct pci_dev *pdev, | |
59 | const struct pci_device_id *id) | |
60 | { | |
61 | struct device *dev = &pdev->dev; | |
62 | struct pci_dev *func; | |
63 | struct resource *res; | |
64 | struct cdns *cdnsp; | |
65 | int ret; | |
66 | ||
67 | /* | |
68 | * For GADGET/HOST PCI (devfn) function number is 0, | |
69 | * for OTG PCI (devfn) function number is 1. | |
70 | */ | |
71 | if (!id || (pdev->devfn != PCI_DEV_FN_HOST_DEVICE && | |
72 | pdev->devfn != PCI_DEV_FN_OTG)) | |
73 | return -EINVAL; | |
74 | ||
75 | func = cdnsp_get_second_fun(pdev); | |
76 | if (!func) | |
77 | return -EINVAL; | |
78 | ||
79 | if (func->class == PCI_CLASS_SERIAL_USB_XHCI || | |
80 | pdev->class == PCI_CLASS_SERIAL_USB_XHCI) { | |
81 | ret = -EINVAL; | |
82 | goto put_pci; | |
83 | } | |
84 | ||
85 | ret = pcim_enable_device(pdev); | |
86 | if (ret) { | |
87 | dev_err(&pdev->dev, "Enabling PCI device has failed %d\n", ret); | |
88 | goto put_pci; | |
89 | } | |
90 | ||
91 | pci_set_master(pdev); | |
92 | if (pci_is_enabled(func)) { | |
93 | cdnsp = pci_get_drvdata(func); | |
94 | } else { | |
95 | cdnsp = kzalloc(sizeof(*cdnsp), GFP_KERNEL); | |
96 | if (!cdnsp) { | |
97 | ret = -ENOMEM; | |
98 | goto disable_pci; | |
99 | } | |
100 | } | |
101 | ||
102 | /* For GADGET device function number is 0. */ | |
103 | if (pdev->devfn == 0) { | |
104 | resource_size_t rsrc_start, rsrc_len; | |
105 | ||
106 | /* Function 0: host(BAR_0) + device(BAR_1).*/ | |
107 | dev_dbg(dev, "Initialize resources\n"); | |
108 | rsrc_start = pci_resource_start(pdev, PCI_BAR_DEV); | |
109 | rsrc_len = pci_resource_len(pdev, PCI_BAR_DEV); | |
110 | res = devm_request_mem_region(dev, rsrc_start, rsrc_len, "dev"); | |
111 | if (!res) { | |
112 | dev_dbg(dev, "controller already in use\n"); | |
113 | ret = -EBUSY; | |
114 | goto free_cdnsp; | |
115 | } | |
116 | ||
117 | cdnsp->dev_regs = devm_ioremap(dev, rsrc_start, rsrc_len); | |
118 | if (!cdnsp->dev_regs) { | |
119 | dev_dbg(dev, "error mapping memory\n"); | |
120 | ret = -EFAULT; | |
121 | goto free_cdnsp; | |
122 | } | |
123 | ||
124 | cdnsp->dev_irq = pdev->irq; | |
125 | dev_dbg(dev, "USBSS-DEV physical base addr: %pa\n", | |
126 | &rsrc_start); | |
127 | ||
128 | res = &cdnsp->xhci_res[0]; | |
129 | res->start = pci_resource_start(pdev, PCI_BAR_HOST); | |
130 | res->end = pci_resource_end(pdev, PCI_BAR_HOST); | |
131 | res->name = "xhci"; | |
132 | res->flags = IORESOURCE_MEM; | |
133 | dev_dbg(dev, "USBSS-XHCI physical base addr: %pa\n", | |
134 | &res->start); | |
135 | ||
136 | /* Interrupt for XHCI, */ | |
137 | res = &cdnsp->xhci_res[1]; | |
138 | res->start = pdev->irq; | |
139 | res->name = "host"; | |
140 | res->flags = IORESOURCE_IRQ; | |
141 | } else { | |
142 | res = &cdnsp->otg_res; | |
143 | res->start = pci_resource_start(pdev, PCI_BAR_OTG); | |
144 | res->end = pci_resource_end(pdev, PCI_BAR_OTG); | |
145 | res->name = "otg"; | |
146 | res->flags = IORESOURCE_MEM; | |
147 | dev_dbg(dev, "CDNSP-DRD physical base addr: %pa\n", | |
148 | &res->start); | |
149 | ||
150 | /* Interrupt for OTG/DRD. */ | |
151 | cdnsp->otg_irq = pdev->irq; | |
152 | } | |
153 | ||
154 | if (pci_is_enabled(func)) { | |
155 | cdnsp->dev = dev; | |
156 | cdnsp->gadget_init = cdnsp_gadget_init; | |
157 | ||
158 | ret = cdns_init(cdnsp); | |
159 | if (ret) | |
160 | goto free_cdnsp; | |
161 | } | |
162 | ||
163 | pci_set_drvdata(pdev, cdnsp); | |
164 | ||
165 | device_wakeup_enable(&pdev->dev); | |
166 | if (pci_dev_run_wake(pdev)) | |
167 | pm_runtime_put_noidle(&pdev->dev); | |
168 | ||
169 | return 0; | |
170 | ||
171 | free_cdnsp: | |
172 | if (!pci_is_enabled(func)) | |
173 | kfree(cdnsp); | |
174 | ||
175 | disable_pci: | |
176 | pci_disable_device(pdev); | |
177 | ||
178 | put_pci: | |
179 | pci_dev_put(func); | |
180 | ||
181 | return ret; | |
182 | } | |
183 | ||
184 | static void cdnsp_pci_remove(struct pci_dev *pdev) | |
185 | { | |
186 | struct cdns *cdnsp; | |
187 | struct pci_dev *func; | |
188 | ||
189 | func = cdnsp_get_second_fun(pdev); | |
190 | cdnsp = (struct cdns *)pci_get_drvdata(pdev); | |
191 | ||
192 | if (pci_dev_run_wake(pdev)) | |
193 | pm_runtime_get_noresume(&pdev->dev); | |
194 | ||
195 | if (!pci_is_enabled(func)) { | |
196 | kfree(cdnsp); | |
197 | goto pci_put; | |
198 | } | |
199 | ||
200 | cdns_remove(cdnsp); | |
201 | ||
202 | pci_put: | |
203 | pci_dev_put(func); | |
204 | } | |
205 | ||
206 | static int __maybe_unused cdnsp_pci_suspend(struct device *dev) | |
207 | { | |
208 | struct cdns *cdns = dev_get_drvdata(dev); | |
209 | ||
210 | return cdns_suspend(cdns); | |
211 | } | |
212 | ||
213 | static int __maybe_unused cdnsp_pci_resume(struct device *dev) | |
214 | { | |
215 | struct cdns *cdns = dev_get_drvdata(dev); | |
216 | unsigned long flags; | |
217 | int ret; | |
218 | ||
219 | spin_lock_irqsave(&cdns->lock, flags); | |
220 | ret = cdns_resume(cdns, 1); | |
221 | spin_unlock_irqrestore(&cdns->lock, flags); | |
222 | ||
223 | return ret; | |
224 | } | |
225 | ||
226 | static const struct dev_pm_ops cdnsp_pci_pm_ops = { | |
227 | SET_SYSTEM_SLEEP_PM_OPS(cdnsp_pci_suspend, cdnsp_pci_resume) | |
228 | }; | |
229 | ||
230 | static const struct pci_device_id cdnsp_pci_ids[] = { | |
231 | { PCI_VENDOR_ID_CDNS, CDNS_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, | |
232 | PCI_CLASS_SERIAL_USB_DEVICE, PCI_ANY_ID }, | |
233 | { PCI_VENDOR_ID_CDNS, CDNS_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, | |
234 | CDNS_DRD_IF, PCI_ANY_ID }, | |
235 | { 0, } | |
236 | }; | |
237 | ||
238 | static struct pci_driver cdnsp_pci_driver = { | |
239 | .name = "cdnsp-pci", | |
240 | .id_table = &cdnsp_pci_ids[0], | |
241 | .probe = cdnsp_pci_probe, | |
242 | .remove = cdnsp_pci_remove, | |
243 | .driver = { | |
244 | .pm = &cdnsp_pci_pm_ops, | |
245 | } | |
246 | }; | |
247 | ||
248 | module_pci_driver(cdnsp_pci_driver); | |
249 | MODULE_DEVICE_TABLE(pci, cdnsp_pci_ids); | |
250 | ||
251 | MODULE_ALIAS("pci:cdnsp"); | |
252 | MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>"); | |
253 | MODULE_LICENSE("GPL v2"); | |
254 | MODULE_DESCRIPTION("Cadence CDNSP PCI driver"); |