]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - sys/dev/usb/controller/ehci_imx.c
Copy head (r256279) to stable/10 as part of the 10.0-RELEASE cycle.
[FreeBSD/stable/10.git] / sys / dev / usb / controller / ehci_imx.c
1 /*-
2  * Copyright (c) 2010-2012 Semihalf
3  * Copyright (c) 2012 The FreeBSD Foundation
4  * All rights reserved.
5  *
6  * Portions of this software were developed by Oleksandr Rybalko
7  * under sponsorship from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30
31 #include <sys/cdefs.h>
32 __FBSDID("$FreeBSD$");
33
34 #include "opt_bus.h"
35
36 #include <sys/param.h>
37 #include <sys/systm.h>
38 #include <sys/kernel.h>
39 #include <sys/module.h>
40 #include <sys/bus.h>
41 #include <sys/condvar.h>
42 #include <sys/rman.h>
43
44 #include <dev/ofw/ofw_bus.h>
45 #include <dev/ofw/ofw_bus_subr.h>
46
47 #include <dev/usb/usb.h>
48 #include <dev/usb/usbdi.h>
49 #include <dev/usb/usb_busdma.h>
50 #include <dev/usb/usb_process.h>
51 #include <dev/usb/usb_controller.h>
52 #include <dev/usb/usb_bus.h>
53 #include <dev/usb/controller/ehci.h>
54 #include <dev/usb/controller/ehcireg.h>
55
56 #include <machine/bus.h>
57 #include <machine/resource.h>
58
59 #include "opt_platform.h"
60
61 #define FSL_EHCI_COUNT          4
62 #define FSL_EHCI_REG_OFF        0x100
63 #define FSL_EHCI_REG_SIZE       0x100
64 #define FSL_EHCI_REG_STEP       0x200
65
66 struct imx_ehci_softc {
67         ehci_softc_t            ehci[FSL_EHCI_COUNT];
68         /* MEM + 4 interrupts */
69         struct resource         *sc_res[1 + FSL_EHCI_COUNT];
70 };
71
72 /* i.MX515 have 4 EHCI inside USB core */
73 /* TODO: we can get number of EHCIs by IRQ allocation */
74 static struct resource_spec imx_ehci_spec[] = {
75         { SYS_RES_MEMORY,       0,      RF_ACTIVE },
76         { SYS_RES_IRQ,          0,      RF_ACTIVE },
77         { SYS_RES_IRQ,          1,      RF_ACTIVE },
78         { SYS_RES_IRQ,          2,      RF_ACTIVE },
79         /* RF_OPTIONAL will allow to use driver for systems with 3 EHCIs */
80         { SYS_RES_IRQ,          3,      RF_ACTIVE | RF_OPTIONAL },
81         { -1, 0 }
82 };
83
84 /* Forward declarations */
85 static int      fsl_ehci_attach(device_t self);
86 static int      fsl_ehci_detach(device_t self);
87 static int      fsl_ehci_probe(device_t self);
88
89 static device_method_t ehci_methods[] = {
90         /* Device interface */
91         DEVMETHOD(device_probe, fsl_ehci_probe),
92         DEVMETHOD(device_attach, fsl_ehci_attach),
93         DEVMETHOD(device_detach, fsl_ehci_detach),
94         DEVMETHOD(device_suspend, bus_generic_suspend),
95         DEVMETHOD(device_resume, bus_generic_resume),
96         DEVMETHOD(device_shutdown, bus_generic_shutdown),
97
98         /* Bus interface */
99         DEVMETHOD(bus_print_child, bus_generic_print_child),
100
101         { 0, 0 }
102 };
103
104 /* kobj_class definition */
105 static driver_t ehci_driver = {
106         "ehci",
107         ehci_methods,
108         sizeof(struct imx_ehci_softc)
109 };
110
111 static devclass_t ehci_devclass;
112
113 DRIVER_MODULE(ehci, simplebus, ehci_driver, ehci_devclass, 0, 0);
114 MODULE_DEPEND(ehci, usb, 1, 1, 1);
115
116 /*
117  * Public methods
118  */
119 static int
120 fsl_ehci_probe(device_t dev)
121 {
122
123         if (ofw_bus_is_compatible(dev, "fsl,usb-4core") == 0)
124                 return (ENXIO);
125
126         device_set_desc(dev, "Freescale integrated USB controller");
127
128         return (BUS_PROBE_DEFAULT);
129 }
130
131 static int
132 fsl_ehci_attach(device_t self)
133 {
134         struct imx_ehci_softc *sc;
135         bus_space_tag_t iot;
136         ehci_softc_t *esc;
137         int err, i, rid;
138
139         sc = device_get_softc(self);
140         rid = 0;
141
142         /* Allocate io resource for EHCI */
143         if (bus_alloc_resources(self, imx_ehci_spec, sc->sc_res)) {
144                 device_printf(self, "could not allocate resources\n");
145                 return (ENXIO);
146         }
147         iot = rman_get_bustag(sc->sc_res[0]);
148
149         /* TODO: Power/clock enable */
150         /* TODO: basic init */
151
152         for (i = 0; i < FSL_EHCI_COUNT; i ++) {
153                 /* No interrupt - no driver */
154                 if (sc->sc_res[1 + i] == NULL)
155                         continue;
156
157                 esc = &sc->ehci[i];
158                 esc->sc_io_tag = iot;
159                 esc->sc_bus.parent = self;
160                 esc->sc_bus.devices = esc->sc_devices;
161                 esc->sc_bus.devices_max = EHCI_MAX_DEVICES;
162
163                 if (usb_bus_mem_alloc_all(&esc->sc_bus, USB_GET_DMA_TAG(self),
164                     &ehci_iterate_hw_softc))
165                         continue;
166
167                 /*
168                  * Set handle to USB related registers subregion used by
169                  * generic EHCI driver.
170                  */
171                 err = bus_space_subregion(iot,
172                     rman_get_bushandle(sc->sc_res[0]),
173                     FSL_EHCI_REG_OFF + (i * FSL_EHCI_REG_STEP),
174                     FSL_EHCI_REG_SIZE, &esc->sc_io_hdl);
175                 if (err != 0)
176                         continue;
177
178                 /* Setup interrupt handler */
179                 err = bus_setup_intr(self, sc->sc_res[1 + i], INTR_TYPE_BIO,
180                     NULL, (driver_intr_t *)ehci_interrupt, esc,
181                     &esc->sc_intr_hdl);
182                 if (err) {
183                         device_printf(self, "Could not setup irq, "
184                             "for EHCI%d %d\n", i, err);
185                         continue;
186                 }
187
188                 /* Add USB device */
189                 esc->sc_bus.bdev = device_add_child(self, "usbus", -1);
190                 if (!esc->sc_bus.bdev) {
191                         device_printf(self, "Could not add USB device\n");
192                         err = bus_teardown_intr(self, esc->sc_irq_res,
193                             esc->sc_intr_hdl);
194                         if (err)
195                                 device_printf(self, "Could not tear down irq,"
196                                     " %d\n", err);
197                         continue;
198                 }
199                 device_set_ivars(esc->sc_bus.bdev, &esc->sc_bus);
200
201                 esc->sc_id_vendor = 0x1234;
202                 strlcpy(esc->sc_vendor, "Freescale", sizeof(esc->sc_vendor));
203
204                 /* Set flags */
205                 esc->sc_flags |= EHCI_SCFLG_DONTRESET | EHCI_SCFLG_NORESTERM;
206
207                 err = ehci_init(esc);
208                 if (!err) {
209                         esc->sc_flags |= EHCI_SCFLG_DONEINIT;
210                         err = device_probe_and_attach(esc->sc_bus.bdev);
211                 } else {
212                         device_printf(self, "USB init failed err=%d\n", err);
213
214                         device_delete_child(self, esc->sc_bus.bdev);
215                         esc->sc_bus.bdev = NULL;
216
217                         err = bus_teardown_intr(self, esc->sc_irq_res,
218                             esc->sc_intr_hdl);
219                         if (err)
220                                 device_printf(self, "Could not tear down irq,"
221                                     " %d\n", err);
222
223                         continue;
224                 }
225         }
226         return (0);
227 }
228
229 static int
230 fsl_ehci_detach(device_t self)
231 {
232         struct imx_ehci_softc *sc;
233         ehci_softc_t *esc;
234         int err, i;
235
236         sc = device_get_softc(self);
237
238         for (i = 0; i < FSL_EHCI_COUNT; i ++) {
239                 esc = &sc->ehci[i];
240                 if (esc->sc_flags & EHCI_SCFLG_DONEINIT)
241                         continue;
242                 /*
243                  * only call ehci_detach() after ehci_init()
244                  */
245                 if (esc->sc_flags & EHCI_SCFLG_DONEINIT) {
246                         ehci_detach(esc);
247                         esc->sc_flags &= ~EHCI_SCFLG_DONEINIT;
248                 }
249
250                 /*
251                  * Disable interrupts that might have been switched on in
252                  * ehci_init.
253                  */
254                 if (esc->sc_io_tag && esc->sc_io_hdl)
255                         bus_space_write_4(esc->sc_io_tag, esc->sc_io_hdl,
256                             EHCI_USBINTR, 0);
257
258                 if (esc->sc_irq_res && esc->sc_intr_hdl) {
259                         err = bus_teardown_intr(self, esc->sc_irq_res,
260                             esc->sc_intr_hdl);
261                         if (err) {
262                                 device_printf(self, "Could not tear down irq,"
263                                     " %d\n", err);
264                                 return (err);
265                         }
266                         esc->sc_intr_hdl = NULL;
267                 }
268
269                 if (esc->sc_bus.bdev) {
270                         device_delete_child(self, esc->sc_bus.bdev);
271                         esc->sc_bus.bdev = NULL;
272                 }
273         }
274
275         /* During module unload there are lots of children leftover */
276         device_delete_children(self);
277
278         if (sc->sc_res[0])
279                 bus_release_resources(self, imx_ehci_spec, sc->sc_res);
280
281         return (0);
282 }