1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Sonics Silicon Backplane |
4 | * Broadcom USB-core driver (SSB bus glue) |
5 | * |
6 | * Copyright 2011-2012 Hauke Mehrtens <hauke@hauke-m.de> |
7 | * |
8 | * Based on ssb-ohci driver |
9 | * Copyright 2007 Michael Buesch <m@bues.ch> |
10 | * |
11 | * Derived from the OHCI-PCI driver |
12 | * Copyright 1999 Roman Weissgaerber |
13 | * Copyright 2000-2002 David Brownell |
14 | * Copyright 1999 Linus Torvalds |
15 | * Copyright 1999 Gregory P. Smith |
16 | * |
17 | * Derived from the USBcore related parts of Broadcom-SB |
18 | * Copyright 2005-2011 Broadcom Corporation |
19 | */ |
20 | #include <linux/ssb/ssb.h> |
21 | #include <linux/delay.h> |
22 | #include <linux/platform_device.h> |
23 | #include <linux/module.h> |
24 | #include <linux/slab.h> |
25 | #include <linux/usb/ehci_pdriver.h> |
26 | #include <linux/usb/ohci_pdriver.h> |
27 | |
28 | MODULE_AUTHOR("Hauke Mehrtens" ); |
29 | MODULE_DESCRIPTION("Common USB driver for SSB Bus" ); |
30 | MODULE_LICENSE("GPL" ); |
31 | |
32 | #define SSB_HCD_TMSLOW_HOSTMODE (1 << 29) |
33 | |
34 | struct ssb_hcd_device { |
35 | struct platform_device *ehci_dev; |
36 | struct platform_device *ohci_dev; |
37 | |
38 | u32 enable_flags; |
39 | }; |
40 | |
41 | static void ssb_hcd_5354wa(struct ssb_device *dev) |
42 | { |
43 | #ifdef CONFIG_SSB_DRIVER_MIPS |
44 | /* Work around for 5354 failures */ |
45 | if (dev->id.revision == 2 && dev->bus->chip_id == 0x5354) { |
46 | /* Change syn01 reg */ |
47 | ssb_write32(dev, 0x894, 0x00fe00fe); |
48 | |
49 | /* Change syn03 reg */ |
50 | ssb_write32(dev, 0x89c, ssb_read32(dev, 0x89c) | 0x1); |
51 | } |
52 | #endif |
53 | } |
54 | |
55 | static void ssb_hcd_usb20wa(struct ssb_device *dev) |
56 | { |
57 | if (dev->id.coreid == SSB_DEV_USB20_HOST) { |
58 | /* |
59 | * USB 2.0 special considerations: |
60 | * |
61 | * In addition to the standard SSB reset sequence, the Host |
62 | * Control Register must be programmed to bring the USB core |
63 | * and various phy components out of reset. |
64 | */ |
65 | ssb_write32(dev, offset: 0x200, value: 0x7ff); |
66 | |
67 | /* Change Flush control reg */ |
68 | ssb_write32(dev, offset: 0x400, value: ssb_read32(dev, offset: 0x400) & ~8); |
69 | ssb_read32(dev, offset: 0x400); |
70 | |
71 | /* Change Shim control reg */ |
72 | ssb_write32(dev, offset: 0x304, value: ssb_read32(dev, offset: 0x304) & ~0x100); |
73 | ssb_read32(dev, offset: 0x304); |
74 | |
75 | udelay(1); |
76 | |
77 | ssb_hcd_5354wa(dev); |
78 | } |
79 | } |
80 | |
81 | /* based on arch/mips/brcm-boards/bcm947xx/pcibios.c */ |
82 | static u32 ssb_hcd_init_chip(struct ssb_device *dev) |
83 | { |
84 | u32 flags = 0; |
85 | |
86 | if (dev->id.coreid == SSB_DEV_USB11_HOSTDEV) |
87 | /* Put the device into host-mode. */ |
88 | flags |= SSB_HCD_TMSLOW_HOSTMODE; |
89 | |
90 | ssb_device_enable(dev, core_specific_flags: flags); |
91 | |
92 | ssb_hcd_usb20wa(dev); |
93 | |
94 | return flags; |
95 | } |
96 | |
97 | static const struct usb_ehci_pdata ehci_pdata = { |
98 | }; |
99 | |
100 | static const struct usb_ohci_pdata ohci_pdata = { |
101 | }; |
102 | |
103 | static struct platform_device *ssb_hcd_create_pdev(struct ssb_device *dev, bool ohci, u32 addr, u32 len) |
104 | { |
105 | struct platform_device *hci_dev; |
106 | struct resource hci_res[2]; |
107 | int ret; |
108 | |
109 | memset(hci_res, 0, sizeof(hci_res)); |
110 | |
111 | hci_res[0].start = addr; |
112 | hci_res[0].end = hci_res[0].start + len - 1; |
113 | hci_res[0].flags = IORESOURCE_MEM; |
114 | |
115 | hci_res[1].start = dev->irq; |
116 | hci_res[1].flags = IORESOURCE_IRQ; |
117 | |
118 | hci_dev = platform_device_alloc(name: ohci ? "ohci-platform" : |
119 | "ehci-platform" , id: 0); |
120 | if (!hci_dev) |
121 | return ERR_PTR(error: -ENOMEM); |
122 | |
123 | hci_dev->dev.parent = dev->dev; |
124 | hci_dev->dev.dma_mask = &hci_dev->dev.coherent_dma_mask; |
125 | |
126 | ret = platform_device_add_resources(pdev: hci_dev, res: hci_res, |
127 | ARRAY_SIZE(hci_res)); |
128 | if (ret) |
129 | goto err_alloc; |
130 | if (ohci) |
131 | ret = platform_device_add_data(pdev: hci_dev, data: &ohci_pdata, |
132 | size: sizeof(ohci_pdata)); |
133 | else |
134 | ret = platform_device_add_data(pdev: hci_dev, data: &ehci_pdata, |
135 | size: sizeof(ehci_pdata)); |
136 | if (ret) |
137 | goto err_alloc; |
138 | ret = platform_device_add(pdev: hci_dev); |
139 | if (ret) |
140 | goto err_alloc; |
141 | |
142 | return hci_dev; |
143 | |
144 | err_alloc: |
145 | platform_device_put(pdev: hci_dev); |
146 | return ERR_PTR(error: ret); |
147 | } |
148 | |
149 | static int ssb_hcd_probe(struct ssb_device *dev, |
150 | const struct ssb_device_id *id) |
151 | { |
152 | int err, tmp; |
153 | int start, len; |
154 | u16 chipid_top; |
155 | u16 coreid = dev->id.coreid; |
156 | struct ssb_hcd_device *usb_dev; |
157 | |
158 | /* USBcores are only connected on embedded devices. */ |
159 | chipid_top = (dev->bus->chip_id & 0xFF00); |
160 | if (chipid_top != 0x4700 && chipid_top != 0x5300) |
161 | return -ENODEV; |
162 | |
163 | /* TODO: Probably need checks here; is the core connected? */ |
164 | |
165 | if (dma_set_mask_and_coherent(dev: dev->dma_dev, DMA_BIT_MASK(32))) |
166 | return -EOPNOTSUPP; |
167 | |
168 | usb_dev = devm_kzalloc(dev: dev->dev, size: sizeof(struct ssb_hcd_device), |
169 | GFP_KERNEL); |
170 | if (!usb_dev) |
171 | return -ENOMEM; |
172 | |
173 | /* We currently always attach SSB_DEV_USB11_HOSTDEV |
174 | * as HOST OHCI. If we want to attach it as Client device, |
175 | * we must branch here and call into the (yet to |
176 | * be written) Client mode driver. Same for remove(). */ |
177 | usb_dev->enable_flags = ssb_hcd_init_chip(dev); |
178 | |
179 | tmp = ssb_read32(dev, SSB_ADMATCH0); |
180 | |
181 | start = ssb_admatch_base(adm: tmp); |
182 | len = (coreid == SSB_DEV_USB20_HOST) ? 0x800 : ssb_admatch_size(adm: tmp); |
183 | usb_dev->ohci_dev = ssb_hcd_create_pdev(dev, ohci: true, addr: start, len); |
184 | if (IS_ERR(ptr: usb_dev->ohci_dev)) |
185 | return PTR_ERR(ptr: usb_dev->ohci_dev); |
186 | |
187 | if (coreid == SSB_DEV_USB20_HOST) { |
188 | start = ssb_admatch_base(adm: tmp) + 0x800; /* ehci core offset */ |
189 | usb_dev->ehci_dev = ssb_hcd_create_pdev(dev, ohci: false, addr: start, len); |
190 | if (IS_ERR(ptr: usb_dev->ehci_dev)) { |
191 | err = PTR_ERR(ptr: usb_dev->ehci_dev); |
192 | goto err_unregister_ohci_dev; |
193 | } |
194 | } |
195 | |
196 | ssb_set_drvdata(dev, data: usb_dev); |
197 | return 0; |
198 | |
199 | err_unregister_ohci_dev: |
200 | platform_device_unregister(usb_dev->ohci_dev); |
201 | return err; |
202 | } |
203 | |
204 | static void ssb_hcd_remove(struct ssb_device *dev) |
205 | { |
206 | struct ssb_hcd_device *usb_dev = ssb_get_drvdata(dev); |
207 | struct platform_device *ohci_dev = usb_dev->ohci_dev; |
208 | struct platform_device *ehci_dev = usb_dev->ehci_dev; |
209 | |
210 | if (ohci_dev) |
211 | platform_device_unregister(ohci_dev); |
212 | if (ehci_dev) |
213 | platform_device_unregister(ehci_dev); |
214 | |
215 | ssb_device_disable(dev, core_specific_flags: 0); |
216 | } |
217 | |
218 | static void ssb_hcd_shutdown(struct ssb_device *dev) |
219 | { |
220 | ssb_device_disable(dev, core_specific_flags: 0); |
221 | } |
222 | |
223 | #ifdef CONFIG_PM |
224 | |
225 | static int ssb_hcd_suspend(struct ssb_device *dev, pm_message_t state) |
226 | { |
227 | ssb_device_disable(dev, core_specific_flags: 0); |
228 | |
229 | return 0; |
230 | } |
231 | |
232 | static int ssb_hcd_resume(struct ssb_device *dev) |
233 | { |
234 | struct ssb_hcd_device *usb_dev = ssb_get_drvdata(dev); |
235 | |
236 | ssb_device_enable(dev, core_specific_flags: usb_dev->enable_flags); |
237 | |
238 | return 0; |
239 | } |
240 | |
241 | #else /* !CONFIG_PM */ |
242 | #define ssb_hcd_suspend NULL |
243 | #define ssb_hcd_resume NULL |
244 | #endif /* CONFIG_PM */ |
245 | |
246 | static const struct ssb_device_id ssb_hcd_table[] = { |
247 | SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_USB11_HOSTDEV, SSB_ANY_REV), |
248 | SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_USB11_HOST, SSB_ANY_REV), |
249 | SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_USB20_HOST, SSB_ANY_REV), |
250 | {}, |
251 | }; |
252 | MODULE_DEVICE_TABLE(ssb, ssb_hcd_table); |
253 | |
254 | static struct ssb_driver ssb_hcd_driver = { |
255 | .name = KBUILD_MODNAME, |
256 | .id_table = ssb_hcd_table, |
257 | .probe = ssb_hcd_probe, |
258 | .remove = ssb_hcd_remove, |
259 | .shutdown = ssb_hcd_shutdown, |
260 | .suspend = ssb_hcd_suspend, |
261 | .resume = ssb_hcd_resume, |
262 | }; |
263 | |
264 | static int __init ssb_hcd_init(void) |
265 | { |
266 | return ssb_driver_register(&ssb_hcd_driver); |
267 | } |
268 | module_init(ssb_hcd_init); |
269 | |
270 | static void __exit ssb_hcd_exit(void) |
271 | { |
272 | ssb_driver_unregister(drv: &ssb_hcd_driver); |
273 | } |
274 | module_exit(ssb_hcd_exit); |
275 | |