Index: head/sys/arm/allwinner/a10_ehci.c =================================================================== --- head/sys/arm/allwinner/a10_ehci.c (revision 300727) +++ head/sys/arm/allwinner/a10_ehci.c (revision 300728) @@ -1,355 +1,369 @@ /*- * Copyright (c) 2012 Ganbold Tsagaankhuu * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Allwinner A10 attachment driver for the USB Enhanced Host Controller. */ #include __FBSDID("$FreeBSD$"); #include "opt_bus.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #define EHCI_HC_DEVSTR "Allwinner Integrated USB 2.0 controller" #define SW_USB_PMU_IRQ_ENABLE 0x800 #define SW_SDRAM_REG_HPCR_USB1 (0x250 + ((1 << 2) * 4)) #define SW_SDRAM_REG_HPCR_USB2 (0x250 + ((1 << 2) * 5)) #define SW_SDRAM_BP_HPCR_ACCESS (1 << 0) #define SW_ULPI_BYPASS (1 << 0) #define SW_AHB_INCRX_ALIGN (1 << 8) #define SW_AHB_INCR4 (1 << 9) #define SW_AHB_INCR8 (1 << 10) #define USB_CONF(d) \ (void *)ofw_bus_search_compatible((d), compat_data)->ocd_data #define A10_READ_4(sc, reg) \ bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg) #define A10_WRITE_4(sc, reg, data) \ bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data) static device_attach_t a10_ehci_attach; static device_detach_t a10_ehci_detach; bs_r_1_proto(reversed); bs_w_1_proto(reversed); struct aw_ehci_softc { ehci_softc_t sc; clk_t clk; hwreset_t rst; + phy_t phy; }; struct aw_ehci_conf { bool sdram_init; }; static const struct aw_ehci_conf a10_ehci_conf = { .sdram_init = true, }; static const struct aw_ehci_conf a31_ehci_conf = { .sdram_init = false, }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-ehci", (uintptr_t)&a10_ehci_conf }, { "allwinner,sun6i-a31-ehci", (uintptr_t)&a31_ehci_conf }, { "allwinner,sun7i-a20-ehci", (uintptr_t)&a10_ehci_conf }, { "allwinner,sun8i-a83t-ehci", (uintptr_t)&a31_ehci_conf }, { "allwinner,sun8i-h3-ehci", (uintptr_t)&a31_ehci_conf }, { NULL, (uintptr_t)NULL } }; static int a10_ehci_probe(device_t self) { if (!ofw_bus_status_okay(self)) return (ENXIO); if (ofw_bus_search_compatible(self, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(self, EHCI_HC_DEVSTR); return (BUS_PROBE_DEFAULT); } static int a10_ehci_attach(device_t self) { struct aw_ehci_softc *aw_sc = device_get_softc(self); ehci_softc_t *sc = &aw_sc->sc; const struct aw_ehci_conf *conf; bus_space_handle_t bsh; int err; int rid; uint32_t reg_value = 0; conf = USB_CONF(self); /* initialise some bus fields */ sc->sc_bus.parent = self; sc->sc_bus.devices = sc->sc_devices; sc->sc_bus.devices_max = EHCI_MAX_DEVICES; sc->sc_bus.dma_bits = 32; /* get all DMA memory */ if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) { return (ENOMEM); } sc->sc_bus.usbrev = USB_REV_2_0; rid = 0; sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!sc->sc_io_res) { device_printf(self, "Could not map memory\n"); goto error; } sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); bsh = rman_get_bushandle(sc->sc_io_res); sc->sc_io_size = rman_get_size(sc->sc_io_res); if (bus_space_subregion(sc->sc_io_tag, bsh, 0x00, sc->sc_io_size, &sc->sc_io_hdl) != 0) panic("%s: unable to subregion USB host registers", device_get_name(self)); rid = 0; sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->sc_irq_res == NULL) { device_printf(self, "Could not allocate irq\n"); goto error; } sc->sc_bus.bdev = device_add_child(self, "usbus", -1); if (!sc->sc_bus.bdev) { device_printf(self, "Could not add USB device\n"); goto error; } device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); device_set_desc(sc->sc_bus.bdev, EHCI_HC_DEVSTR); sprintf(sc->sc_vendor, "Allwinner"); err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl); if (err) { device_printf(self, "Could not setup irq, %d\n", err); sc->sc_intr_hdl = NULL; goto error; } sc->sc_flags |= EHCI_SCFLG_DONTRESET; /* De-assert reset */ if (hwreset_get_by_ofw_idx(self, 0, &aw_sc->rst) == 0) { err = hwreset_deassert(aw_sc->rst); if (err != 0) { device_printf(self, "Could not de-assert reset\n"); goto error; } } /* Enable clock for USB */ err = clk_get_by_ofw_index(self, 0, &aw_sc->clk); if (err != 0) { device_printf(self, "Could not get clock\n"); goto error; } err = clk_enable(aw_sc->clk); if (err != 0) { device_printf(self, "Could not enable clock\n"); + goto error; + } + + /* Enable USB PHY */ + err = phy_get_by_ofw_name(self, "usb", &aw_sc->phy); + if (err != 0) { + device_printf(self, "Could not get phy\n"); + goto error; + } + err = phy_enable(self, aw_sc->phy); + if (err != 0) { + device_printf(self, "Could not enable phy\n"); goto error; } /* Enable passby */ reg_value = A10_READ_4(sc, SW_USB_PMU_IRQ_ENABLE); reg_value |= SW_AHB_INCR8; /* AHB INCR8 enable */ reg_value |= SW_AHB_INCR4; /* AHB burst type INCR4 enable */ reg_value |= SW_AHB_INCRX_ALIGN; /* AHB INCRX align enable */ reg_value |= SW_ULPI_BYPASS; /* ULPI bypass enable */ A10_WRITE_4(sc, SW_USB_PMU_IRQ_ENABLE, reg_value); /* Configure port */ if (conf->sdram_init) { reg_value = A10_READ_4(sc, SW_SDRAM_REG_HPCR_USB2); reg_value |= SW_SDRAM_BP_HPCR_ACCESS; A10_WRITE_4(sc, SW_SDRAM_REG_HPCR_USB2, reg_value); } err = ehci_init(sc); if (!err) { err = device_probe_and_attach(sc->sc_bus.bdev); } if (err) { device_printf(self, "USB init failed err=%d\n", err); goto error; } return (0); error: if (aw_sc->clk) clk_release(aw_sc->clk); a10_ehci_detach(self); return (ENXIO); } static int a10_ehci_detach(device_t self) { struct aw_ehci_softc *aw_sc = device_get_softc(self); ehci_softc_t *sc = &aw_sc->sc; const struct aw_ehci_conf *conf; device_t bdev; int err; uint32_t reg_value = 0; conf = USB_CONF(self); if (sc->sc_bus.bdev) { bdev = sc->sc_bus.bdev; device_detach(bdev); device_delete_child(self, bdev); } /* during module unload there are lots of children leftover */ device_delete_children(self); if (sc->sc_irq_res && sc->sc_intr_hdl) { /* * only call ehci_detach() after ehci_init() */ ehci_detach(sc); err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); if (err) /* XXX or should we panic? */ device_printf(self, "Could not tear down irq, %d\n", err); sc->sc_intr_hdl = NULL; } if (sc->sc_irq_res) { bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res); sc->sc_irq_res = NULL; } if (sc->sc_io_res) { bus_release_resource(self, SYS_RES_MEMORY, 0, sc->sc_io_res); sc->sc_io_res = NULL; } usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc); /* Disable configure port */ if (conf->sdram_init) { reg_value = A10_READ_4(sc, SW_SDRAM_REG_HPCR_USB2); reg_value &= ~SW_SDRAM_BP_HPCR_ACCESS; A10_WRITE_4(sc, SW_SDRAM_REG_HPCR_USB2, reg_value); } /* Disable passby */ reg_value = A10_READ_4(sc, SW_USB_PMU_IRQ_ENABLE); reg_value &= ~SW_AHB_INCR8; /* AHB INCR8 disable */ reg_value &= ~SW_AHB_INCR4; /* AHB burst type INCR4 disable */ reg_value &= ~SW_AHB_INCRX_ALIGN; /* AHB INCRX align disable */ reg_value &= ~SW_ULPI_BYPASS; /* ULPI bypass disable */ A10_WRITE_4(sc, SW_USB_PMU_IRQ_ENABLE, reg_value); /* Disable clock for USB */ clk_disable(aw_sc->clk); clk_release(aw_sc->clk); /* Assert reset */ if (aw_sc->rst != NULL) { hwreset_assert(aw_sc->rst); hwreset_release(aw_sc->rst); } return (0); } static device_method_t ehci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, a10_ehci_probe), DEVMETHOD(device_attach, a10_ehci_attach), DEVMETHOD(device_detach, a10_ehci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; static driver_t ehci_driver = { .name = "ehci", .methods = ehci_methods, .size = sizeof(ehci_softc_t), }; static devclass_t ehci_devclass; DRIVER_MODULE(ehci, simplebus, ehci_driver, ehci_devclass, 0, 0); MODULE_DEPEND(ehci, usb, 1, 1, 1); Index: head/sys/arm/allwinner/aw_usbphy.c =================================================================== --- head/sys/arm/allwinner/aw_usbphy.c (revision 300727) +++ head/sys/arm/allwinner/aw_usbphy.c (revision 300728) @@ -1,151 +1,239 @@ /*- * Copyright (c) 2016 Jared McNeill * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * Allwinner USB PHY */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include +#include #include #include #include +#include -#define USBPHY_NUMOFF 3 +#include "phy_if.h" +#define USBPHY_NPHYS 4 + static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-usb-phy", 1 }, { "allwinner,sun5i-a13-usb-phy", 1 }, { "allwinner,sun6i-a31-usb-phy", 1 }, { "allwinner,sun7i-a20-usb-phy", 1 }, { "allwinner,sun8i-a83t-usb-phy", 1 }, { "allwinner,sun8i-h3-usb-phy", 1 }, { NULL, 0 } }; +struct awusbphy_softc { + regulator_t reg[USBPHY_NPHYS]; + gpio_pin_t id_det_pin; + int id_det_valid; + gpio_pin_t vbus_det_pin; + int vbus_det_valid; +}; + static int awusbphy_init(device_t dev) { + struct awusbphy_softc *sc; + phandle_t node; char pname[20]; int error, off; regulator_t reg; hwreset_t rst; clk_t clk; + sc = device_get_softc(dev); + node = ofw_bus_get_node(dev); + /* Enable clocks */ for (off = 0; clk_get_by_ofw_index(dev, off, &clk) == 0; off++) { error = clk_enable(clk); if (error != 0) { device_printf(dev, "couldn't enable clock %s\n", clk_get_name(clk)); return (error); } } /* De-assert resets */ for (off = 0; hwreset_get_by_ofw_idx(dev, off, &rst) == 0; off++) { error = hwreset_deassert(rst); if (error != 0) { device_printf(dev, "couldn't de-assert reset %d\n", off); return (error); } } - /* Enable regulator(s) */ - for (off = 0; off < USBPHY_NUMOFF; off++) { + /* Get regulators */ + for (off = 0; off < USBPHY_NPHYS; off++) { snprintf(pname, sizeof(pname), "usb%d_vbus-supply", off); - if (regulator_get_by_ofw_property(dev, pname, ®) != 0) - continue; - error = regulator_enable(reg); - if (error != 0) { - device_printf(dev, "couldn't enable regulator %s\n", - pname); + if (regulator_get_by_ofw_property(dev, pname, ®) == 0) + sc->reg[off] = reg; + } + + /* Get GPIOs */ + error = gpio_pin_get_by_ofw_property(dev, node, "usb0_id_det-gpios", + &sc->id_det_pin); + if (error == 0) + sc->id_det_valid = 1; + error = gpio_pin_get_by_ofw_property(dev, node, "usb0_vbus_det-gpios", + &sc->vbus_det_pin); + if (error == 0) + sc->vbus_det_valid = 1; + + return (0); +} + +static int +awusbphy_vbus_detect(device_t dev, int *val) +{ + struct awusbphy_softc *sc; + bool active; + int error; + + sc = device_get_softc(dev); + + if (sc->vbus_det_valid) { + error = gpio_pin_is_active(sc->vbus_det_pin, &active); + if (error != 0) return (error); - } + *val = active; + return (0); } + *val = 1; return (0); } static int +awusbphy_phy_enable(device_t dev, int phy, bool enable) +{ + struct awusbphy_softc *sc; + regulator_t reg; + int error, vbus_det; + + if (phy < 0 || phy >= USBPHY_NPHYS) + return (ERANGE); + + sc = device_get_softc(dev); + + /* Regulators are optional. If not found, return success. */ + reg = sc->reg[phy]; + if (reg == NULL) + return (0); + + if (enable) { + /* If an external vbus is detected, do not enable phy 0 */ + if (phy == 0) { + error = awusbphy_vbus_detect(dev, &vbus_det); + if (error == 0 && vbus_det == 1) + return (0); + } else + error = 0; + if (error == 0) + error = regulator_enable(reg); + } else + error = regulator_disable(reg); + if (error != 0) { + device_printf(dev, "couldn't %s regulator for phy %d\n", + enable ? "enable" : "disable", phy); + return (error); + } + + return (0); +} + +static int awusbphy_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner USB PHY"); return (BUS_PROBE_DEFAULT); } static int awusbphy_attach(device_t dev) { int error; error = awusbphy_init(dev); - if (error) + if (error) { device_printf(dev, "failed to initialize USB PHY, error %d\n", error); + return (error); + } + phy_register_provider(dev); + return (error); } static device_method_t awusbphy_methods[] = { /* Device interface */ DEVMETHOD(device_probe, awusbphy_probe), DEVMETHOD(device_attach, awusbphy_attach), + /* PHY interface */ + DEVMETHOD(phy_enable, awusbphy_phy_enable), + DEVMETHOD_END }; static driver_t awusbphy_driver = { "awusbphy", awusbphy_methods, - 0, + sizeof(struct awusbphy_softc) }; static devclass_t awusbphy_devclass; -DRIVER_MODULE(awusbphy, simplebus, awusbphy_driver, awusbphy_devclass, 0, 0); +EARLY_DRIVER_MODULE(awusbphy, simplebus, awusbphy_driver, awusbphy_devclass, + 0, 0, BUS_PASS_RESOURCE + BUS_PASS_ORDER_MIDDLE); MODULE_VERSION(awusbphy, 1); Index: head/sys/dev/usb/controller/generic_ohci.c =================================================================== --- head/sys/dev/usb/controller/generic_ohci.c (revision 300727) +++ head/sys/dev/usb/controller/generic_ohci.c (revision 300728) @@ -1,308 +1,327 @@ /*- * Copyright (c) 2006 M. Warner Losh. All rights reserved. * Copyright (c) 2016 Emmanuel Vadot * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Generic OHCI driver based on AT91 OHCI */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef EXT_RESOURCES #include #include +#include #endif #include "generic_usb_if.h" #ifdef EXT_RESOURCES struct clk_list { TAILQ_ENTRY(clk_list) next; clk_t clk; }; #endif struct generic_ohci_softc { ohci_softc_t ohci_sc; #ifdef EXT_RESOURCES hwreset_t rst; + phy_t phy; TAILQ_HEAD(, clk_list) clk_list; #endif }; static int generic_ohci_detach(device_t); static int generic_ohci_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "generic-ohci")) return (ENXIO); device_set_desc(dev, "Generic OHCI Controller"); return (BUS_PROBE_DEFAULT); } static int generic_ohci_attach(device_t dev) { struct generic_ohci_softc *sc = device_get_softc(dev); int err, rid; #ifdef EXT_RESOURCES int off; struct clk_list *clkp; clk_t clk; #endif sc->ohci_sc.sc_bus.parent = dev; sc->ohci_sc.sc_bus.devices = sc->ohci_sc.sc_devices; sc->ohci_sc.sc_bus.devices_max = OHCI_MAX_DEVICES; sc->ohci_sc.sc_bus.dma_bits = 32; /* get all DMA memory */ if (usb_bus_mem_alloc_all(&sc->ohci_sc.sc_bus, USB_GET_DMA_TAG(dev), &ohci_iterate_hw_softc)) { return (ENOMEM); } rid = 0; sc->ohci_sc.sc_io_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->ohci_sc.sc_io_res == 0) { err = ENOMEM; goto error; } sc->ohci_sc.sc_io_tag = rman_get_bustag(sc->ohci_sc.sc_io_res); sc->ohci_sc.sc_io_hdl = rman_get_bushandle(sc->ohci_sc.sc_io_res); sc->ohci_sc.sc_io_size = rman_get_size(sc->ohci_sc.sc_io_res); rid = 0; sc->ohci_sc.sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->ohci_sc.sc_irq_res == 0) { err = ENXIO; goto error; } sc->ohci_sc.sc_bus.bdev = device_add_child(dev, "usbus", -1); if (sc->ohci_sc.sc_bus.bdev == 0) { err = ENXIO; goto error; } device_set_ivars(sc->ohci_sc.sc_bus.bdev, &sc->ohci_sc.sc_bus); strlcpy(sc->ohci_sc.sc_vendor, "Generic", sizeof(sc->ohci_sc.sc_vendor)); err = bus_setup_intr(dev, sc->ohci_sc.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)ohci_interrupt, sc, &sc->ohci_sc.sc_intr_hdl); if (err) { sc->ohci_sc.sc_intr_hdl = NULL; goto error; } #ifdef EXT_RESOURCES TAILQ_INIT(&sc->clk_list); /* Enable clock */ for (off = 0; clk_get_by_ofw_index(dev, off, &clk) == 0; off++) { err = clk_enable(clk); if (err != 0) { device_printf(dev, "Could not enable clock %s\n", clk_get_name(clk)); goto error; } clkp = malloc(sizeof(*clkp), M_DEVBUF, M_WAITOK | M_ZERO); clkp->clk = clk; TAILQ_INSERT_TAIL(&sc->clk_list, clkp, next); } /* De-assert reset */ if (hwreset_get_by_ofw_idx(dev, 0, &sc->rst) == 0) { err = hwreset_deassert(sc->rst); if (err != 0) { device_printf(dev, "Could not de-assert reset %d\n", off); goto error; } } + + /* Enable phy */ + if (phy_get_by_ofw_name(dev, "usb", &sc->phy) == 0) { + err = phy_enable(dev, sc->phy); + if (err != 0) { + device_printf(dev, "Could not enable phy\n"); + goto error; + } + } #endif if (GENERIC_USB_INIT(dev) != 0) { err = ENXIO; goto error; } err = ohci_init(&sc->ohci_sc); if (err == 0) err = device_probe_and_attach(sc->ohci_sc.sc_bus.bdev); if (err) goto error; return (0); error: generic_ohci_detach(dev); return (err); } static int generic_ohci_detach(device_t dev) { struct generic_ohci_softc *sc = device_get_softc(dev); device_t bdev; int err; #ifdef EXT_RESOURCES struct clk_list *clk, *clk_tmp; #endif if (sc->ohci_sc.sc_bus.bdev) { bdev = sc->ohci_sc.sc_bus.bdev; device_detach(bdev); device_delete_child(dev, bdev); } /* during module unload there are lots of children leftover */ device_delete_children(dev); /* * Put the controller into reset, then disable clocks and do * the MI tear down. We have to disable the clocks/hardware * after we do the rest of the teardown. We also disable the * clocks in the opposite order we acquire them, but that * doesn't seem to be absolutely necessary. We free up the * clocks after we disable them, so the system could, in * theory, reuse them. */ bus_space_write_4(sc->ohci_sc.sc_io_tag, sc->ohci_sc.sc_io_hdl, OHCI_CONTROL, 0); if (sc->ohci_sc.sc_irq_res && sc->ohci_sc.sc_intr_hdl) { /* * only call ohci_detach() after ohci_init() */ ohci_detach(&sc->ohci_sc); err = bus_teardown_intr(dev, sc->ohci_sc.sc_irq_res, sc->ohci_sc.sc_intr_hdl); sc->ohci_sc.sc_intr_hdl = NULL; } if (sc->ohci_sc.sc_irq_res) { bus_release_resource(dev, SYS_RES_IRQ, 0, sc->ohci_sc.sc_irq_res); sc->ohci_sc.sc_irq_res = NULL; } if (sc->ohci_sc.sc_io_res) { bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->ohci_sc.sc_io_res); sc->ohci_sc.sc_io_res = NULL; } usb_bus_mem_free_all(&sc->ohci_sc.sc_bus, &ohci_iterate_hw_softc); #ifdef EXT_RESOURCES + /* Disable phy */ + if (sc->phy) { + err = phy_disable(dev, sc->phy); + if (err != 0) + device_printf(dev, "Could not disable phy\n"); + phy_release(sc->phy); + } + /* Disable clock */ TAILQ_FOREACH_SAFE(clk, &sc->clk_list, next, clk_tmp) { err = clk_disable(clk->clk); if (err != 0) device_printf(dev, "Could not disable clock %s\n", clk_get_name(clk->clk)); err = clk_release(clk->clk); if (err != 0) device_printf(dev, "Could not release clock %s\n", clk_get_name(clk->clk)); TAILQ_REMOVE(&sc->clk_list, clk, next); free(clk, M_DEVBUF); } /* De-assert reset */ if (sc->rst) { err = hwreset_assert(sc->rst); if (err != 0) device_printf(dev, "Could not assert reset\n"); hwreset_release(sc->rst); } #endif if (GENERIC_USB_DEINIT(dev) != 0) return (ENXIO); return (0); } static device_method_t generic_ohci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, generic_ohci_probe), DEVMETHOD(device_attach, generic_ohci_attach), DEVMETHOD(device_detach, generic_ohci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; driver_t generic_ohci_driver = { .name = "ohci", .methods = generic_ohci_methods, .size = sizeof(struct generic_ohci_softc), }; static devclass_t generic_ohci_devclass; DRIVER_MODULE(ohci, simplebus, generic_ohci_driver, generic_ohci_devclass, 0, 0); MODULE_DEPEND(ohci, usb, 1, 1, 1);