1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * dwc3-haps.c - Synopsys HAPS PCI Specific glue layer |
4 | * |
5 | * Copyright (C) 2018 Synopsys, Inc. |
6 | * |
7 | * Authors: Thinh Nguyen <thinhn@synopsys.com>, |
8 | * John Youn <johnyoun@synopsys.com> |
9 | */ |
10 | |
11 | #include <linux/kernel.h> |
12 | #include <linux/module.h> |
13 | #include <linux/slab.h> |
14 | #include <linux/pci.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/property.h> |
17 | |
18 | /** |
19 | * struct dwc3_haps - Driver private structure |
20 | * @dwc3: child dwc3 platform_device |
21 | * @pci: our link to PCI bus |
22 | */ |
23 | struct dwc3_haps { |
24 | struct platform_device *dwc3; |
25 | struct pci_dev *pci; |
26 | }; |
27 | |
28 | static const struct property_entry initial_properties[] = { |
29 | PROPERTY_ENTRY_BOOL("snps,usb3_lpm_capable" ), |
30 | PROPERTY_ENTRY_BOOL("snps,has-lpm-erratum" ), |
31 | PROPERTY_ENTRY_BOOL("snps,dis_enblslpm_quirk" ), |
32 | PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent" ), |
33 | { }, |
34 | }; |
35 | |
36 | static const struct software_node dwc3_haps_swnode = { |
37 | .properties = initial_properties, |
38 | }; |
39 | |
40 | static int dwc3_haps_probe(struct pci_dev *pci, |
41 | const struct pci_device_id *id) |
42 | { |
43 | struct dwc3_haps *dwc; |
44 | struct device *dev = &pci->dev; |
45 | struct resource res[2]; |
46 | int ret; |
47 | |
48 | ret = pcim_enable_device(pdev: pci); |
49 | if (ret) { |
50 | dev_err(dev, "failed to enable pci device\n" ); |
51 | return -ENODEV; |
52 | } |
53 | |
54 | pci_set_master(dev: pci); |
55 | |
56 | dwc = devm_kzalloc(dev, size: sizeof(*dwc), GFP_KERNEL); |
57 | if (!dwc) |
58 | return -ENOMEM; |
59 | |
60 | dwc->dwc3 = platform_device_alloc(name: "dwc3" , PLATFORM_DEVID_AUTO); |
61 | if (!dwc->dwc3) |
62 | return -ENOMEM; |
63 | |
64 | memset(res, 0x00, sizeof(struct resource) * ARRAY_SIZE(res)); |
65 | |
66 | res[0].start = pci_resource_start(pci, 0); |
67 | res[0].end = pci_resource_end(pci, 0); |
68 | res[0].name = "dwc_usb3" ; |
69 | res[0].flags = IORESOURCE_MEM; |
70 | |
71 | res[1].start = pci->irq; |
72 | res[1].name = "dwc_usb3" ; |
73 | res[1].flags = IORESOURCE_IRQ; |
74 | |
75 | ret = platform_device_add_resources(pdev: dwc->dwc3, res, ARRAY_SIZE(res)); |
76 | if (ret) { |
77 | dev_err(dev, "couldn't add resources to dwc3 device\n" ); |
78 | goto err; |
79 | } |
80 | |
81 | dwc->pci = pci; |
82 | dwc->dwc3->dev.parent = dev; |
83 | |
84 | ret = device_add_software_node(dev: &dwc->dwc3->dev, node: &dwc3_haps_swnode); |
85 | if (ret) |
86 | goto err; |
87 | |
88 | ret = platform_device_add(pdev: dwc->dwc3); |
89 | if (ret) { |
90 | dev_err(dev, "failed to register dwc3 device\n" ); |
91 | goto err; |
92 | } |
93 | |
94 | pci_set_drvdata(pdev: pci, data: dwc); |
95 | |
96 | return 0; |
97 | err: |
98 | device_remove_software_node(dev: &dwc->dwc3->dev); |
99 | platform_device_put(pdev: dwc->dwc3); |
100 | return ret; |
101 | } |
102 | |
103 | static void dwc3_haps_remove(struct pci_dev *pci) |
104 | { |
105 | struct dwc3_haps *dwc = pci_get_drvdata(pdev: pci); |
106 | |
107 | device_remove_software_node(dev: &dwc->dwc3->dev); |
108 | platform_device_unregister(dwc->dwc3); |
109 | } |
110 | |
111 | static const struct pci_device_id dwc3_haps_id_table[] = { |
112 | { |
113 | PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS, |
114 | PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3), |
115 | /* |
116 | * i.MX6QP and i.MX7D platform use a PCIe controller with the |
117 | * same VID and PID as this USB controller. The system may |
118 | * incorrectly match this driver to that PCIe controller. To |
119 | * workaround this, specifically use class type USB to prevent |
120 | * incorrect driver matching. |
121 | */ |
122 | .class = (PCI_CLASS_SERIAL_USB << 8), |
123 | .class_mask = 0xffff00, |
124 | }, |
125 | { |
126 | PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS, |
127 | PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3_AXI), |
128 | }, |
129 | { |
130 | PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS, |
131 | PCI_DEVICE_ID_SYNOPSYS_HAPSUSB31), |
132 | }, |
133 | { } /* Terminating Entry */ |
134 | }; |
135 | MODULE_DEVICE_TABLE(pci, dwc3_haps_id_table); |
136 | |
137 | static struct pci_driver dwc3_haps_driver = { |
138 | .name = "dwc3-haps" , |
139 | .id_table = dwc3_haps_id_table, |
140 | .probe = dwc3_haps_probe, |
141 | .remove = dwc3_haps_remove, |
142 | }; |
143 | |
144 | MODULE_AUTHOR("Thinh Nguyen <thinhn@synopsys.com>" ); |
145 | MODULE_LICENSE("GPL v2" ); |
146 | MODULE_DESCRIPTION("Synopsys HAPS PCI Glue Layer" ); |
147 | |
148 | module_pci_driver(dwc3_haps_driver); |
149 | |