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 } 283