Index: sys/arm/allwinner/aw_usbphy.c =================================================================== --- sys/arm/allwinner/aw_usbphy.c +++ sys/arm/allwinner/aw_usbphy.c @@ -39,7 +39,7 @@ #include #include #include -#include +#include #include #include @@ -48,6 +48,19 @@ #include #include +#define PHY_CSR 0x00 +#define ID_PULLUP_EN (1 << 17) +#define DPDM_PULLUP_EN (1 << 16) +#define FORCE_ID (0x3 << 14) +#define FORCE_ID_SHIFT 14 +#define FORCE_ID_LOW 2 +#define FORCE_VBUS_VALID (0x3 << 12) +#define FORCE_VBUS_VALID_SHIFT 12 +#define FORCE_VBUS_VALID_HIGH 3 +#define VBUS_CHANGE_DET (1 << 6) +#define ID_CHANGE_DET (1 << 5) +#define DPDM_CHANGE_DET (1 << 4) + #define USBPHY_NUMOFF 3 static struct ofw_compat_data compat_data[] = { @@ -58,14 +71,20 @@ { NULL, 0 } }; +static struct resource_spec awusbphy_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { -1, 0 } +}; + static int -awusbphy_init(device_t dev) +awusbphy_init(device_t dev, struct resource *res) { char pname[20]; int error, off; regulator_t reg; hwreset_t rst; clk_t clk; + uint32_t val; /* Enable clocks */ for (off = 0; clk_get_by_ofw_index(dev, off, &clk) == 0; off++) { @@ -100,6 +119,16 @@ } } + /* Enable OTG PHY for host mode */ + val = bus_read_4(res, PHY_CSR); + val &= ~(VBUS_CHANGE_DET | ID_CHANGE_DET | DPDM_CHANGE_DET); + val |= (ID_PULLUP_EN | DPDM_PULLUP_EN); + val &= ~FORCE_ID; + val |= (FORCE_ID_LOW << FORCE_ID_SHIFT); + val &= ~FORCE_VBUS_VALID; + val |= (FORCE_VBUS_VALID_HIGH << FORCE_VBUS_VALID_SHIFT); + bus_write_4(res, PHY_CSR, val); + return (0); } @@ -119,13 +148,22 @@ static int awusbphy_attach(device_t dev) { + struct resource *res; int error; - error = awusbphy_init(dev); + error = bus_alloc_resources(dev, awusbphy_spec, &res); + if (error != 0) { + device_printf(dev, "failed to allocate bus resources\n"); + return (error); + } + + error = awusbphy_init(dev, res); if (error) device_printf(dev, "failed to initialize USB PHY, error %d\n", error); + bus_release_resources(dev, awusbphy_spec, &res); + return (error); } @@ -145,5 +183,6 @@ 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_TIMER + BUS_PASS_ORDER_MIDDLE); MODULE_VERSION(awusbphy, 1); Index: sys/arm/allwinner/files.allwinner =================================================================== --- sys/arm/allwinner/files.allwinner +++ sys/arm/allwinner/files.allwinner @@ -7,6 +7,7 @@ arm/allwinner/a10_dmac.c standard arm/allwinner/a10_ehci.c optional ehci arm/allwinner/aw_usbphy.c optional ehci +dev/usb/controller/musb_otg_allwinner.c optional musb arm/allwinner/a10_gpio.c optional gpio arm/allwinner/a10_mmc.c optional mmc arm/allwinner/a10_sramc.c standard Index: sys/arm/conf/A20 =================================================================== --- sys/arm/conf/A20 +++ sys/arm/conf/A20 @@ -107,6 +107,7 @@ #device uhci #device ohci device ehci +device musb device umass Index: sys/dev/usb/controller/musb_otg.c =================================================================== --- sys/dev/usb/controller/musb_otg.c +++ sys/dev/usb/controller/musb_otg.c @@ -3139,19 +3139,24 @@ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); - /* read out number of endpoints */ + if (sc->sc_ep_max == 0) { + /* read out number of endpoints */ + + nrx = + (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) / 16); - nrx = - (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) / 16); + ntx = + (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) % 16); - ntx = - (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) % 16); + sc->sc_ep_max = (nrx > ntx) ? nrx : ntx; + } else { + nrx = ntx = sc->sc_ep_max; + } /* these numbers exclude the control endpoint */ DPRINTFN(2, "RX/TX endpoints: %u/%u\n", nrx, ntx); - sc->sc_ep_max = (nrx > ntx) ? nrx : ntx; if (sc->sc_ep_max == 0) { DPRINTFN(2, "ERROR: Looks like the clocks are off!\n"); } Index: sys/dev/usb/controller/musb_otg_allwinner.c =================================================================== --- /dev/null +++ sys/dev/usb/controller/musb_otg_allwinner.c @@ -0,0 +1,484 @@ +/*- + * 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 Dual-Role Device (DRD) controller + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#define DRD_EP_MAX 5 + +#define MUSB2_REG_AWIN_VEND0 0x0043 +#define VEND0_PIO_MODE 0 + +static struct ofw_compat_data compat_data[] = { + { "allwinner,sun4i-a10-musb", 1 }, + { "allwinner,sun6i-a31-musb", 1 }, + { NULL, 0 } +}; + +struct awusbdrd_softc { + struct musbotg_softc sc; + struct resource *res[2]; + clk_t clk; + hwreset_t reset; + struct bus_space bs; +}; + +static struct resource_spec awusbdrd_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { SYS_RES_IRQ, 0, RF_ACTIVE }, + { -1, 0 } +}; + +#define REMAPFLAG 0x8000 +#define REGDECL(a, b) [(a)] = ((b) | REMAPFLAG) + +/* Allwinner USB DRD register mappings */ +static const uint16_t awusbdrd_regmap[] = { + REGDECL(MUSB2_REG_EPFIFO(0), 0x0000), + REGDECL(MUSB2_REG_EPFIFO(1), 0x0004), + REGDECL(MUSB2_REG_EPFIFO(2), 0x0008), + REGDECL(MUSB2_REG_EPFIFO(3), 0x000c), + REGDECL(MUSB2_REG_EPFIFO(4), 0x0010), + REGDECL(MUSB2_REG_EPFIFO(5), 0x0014), + REGDECL(MUSB2_REG_POWER, 0x0040), + REGDECL(MUSB2_REG_DEVCTL, 0x0041), + REGDECL(MUSB2_REG_EPINDEX, 0x0042), + REGDECL(MUSB2_REG_INTTX, 0x0044), + REGDECL(MUSB2_REG_INTRX, 0x0046), + REGDECL(MUSB2_REG_INTTXE, 0x0048), + REGDECL(MUSB2_REG_INTRXE, 0x004a), + REGDECL(MUSB2_REG_INTUSB, 0x004c), + REGDECL(MUSB2_REG_INTUSBE, 0x0050), + REGDECL(MUSB2_REG_FRAME, 0x0054), + REGDECL(MUSB2_REG_TESTMODE, 0x007c), + REGDECL(MUSB2_REG_TXMAXP, 0x0080), + REGDECL(MUSB2_REG_TXCSRL, 0x0082), + REGDECL(MUSB2_REG_TXCSRH, 0x0083), + REGDECL(MUSB2_REG_RXMAXP, 0x0084), + REGDECL(MUSB2_REG_RXCSRL, 0x0086), + REGDECL(MUSB2_REG_RXCSRH, 0x0087), + REGDECL(MUSB2_REG_RXCOUNT, 0x0088), + REGDECL(MUSB2_REG_TXTI, 0x008c), + REGDECL(MUSB2_REG_TXNAKLIMIT, 0x008d), + REGDECL(MUSB2_REG_RXNAKLIMIT, 0x008d), + REGDECL(MUSB2_REG_RXTI, 0x008e), + REGDECL(MUSB2_REG_TXFIFOSZ, 0x0090), + REGDECL(MUSB2_REG_TXFIFOADD, 0x0092), + REGDECL(MUSB2_REG_RXFIFOSZ, 0x0094), + REGDECL(MUSB2_REG_RXFIFOADD, 0x0096), + REGDECL(MUSB2_REG_FADDR, 0x0098), + REGDECL(MUSB2_REG_TXFADDR(0), 0x0098), + REGDECL(MUSB2_REG_TXHADDR(0), 0x009a), + REGDECL(MUSB2_REG_TXHUBPORT(0), 0x009b), + REGDECL(MUSB2_REG_RXFADDR(0), 0x009c), + REGDECL(MUSB2_REG_RXHADDR(0), 0x009e), + REGDECL(MUSB2_REG_RXHUBPORT(0), 0x009f), + REGDECL(MUSB2_REG_TXFADDR(1), 0x0098), + REGDECL(MUSB2_REG_TXHADDR(1), 0x009a), + REGDECL(MUSB2_REG_TXHUBPORT(1), 0x009b), + REGDECL(MUSB2_REG_RXFADDR(1), 0x009c), + REGDECL(MUSB2_REG_RXHADDR(1), 0x009e), + REGDECL(MUSB2_REG_RXHUBPORT(1), 0x009f), + REGDECL(MUSB2_REG_TXFADDR(2), 0x0098), + REGDECL(MUSB2_REG_TXHADDR(2), 0x009a), + REGDECL(MUSB2_REG_TXHUBPORT(2), 0x009b), + REGDECL(MUSB2_REG_RXFADDR(2), 0x009c), + REGDECL(MUSB2_REG_RXHADDR(2), 0x009e), + REGDECL(MUSB2_REG_RXHUBPORT(2), 0x009f), + REGDECL(MUSB2_REG_TXFADDR(3), 0x0098), + REGDECL(MUSB2_REG_TXHADDR(3), 0x009a), + REGDECL(MUSB2_REG_TXHUBPORT(3), 0x009b), + REGDECL(MUSB2_REG_RXFADDR(3), 0x009c), + REGDECL(MUSB2_REG_RXHADDR(3), 0x009e), + REGDECL(MUSB2_REG_RXHUBPORT(3), 0x009f), + REGDECL(MUSB2_REG_TXFADDR(4), 0x0098), + REGDECL(MUSB2_REG_TXHADDR(4), 0x009a), + REGDECL(MUSB2_REG_TXHUBPORT(4), 0x009b), + REGDECL(MUSB2_REG_RXFADDR(4), 0x009c), + REGDECL(MUSB2_REG_RXHADDR(4), 0x009e), + REGDECL(MUSB2_REG_RXHUBPORT(4), 0x009f), + REGDECL(MUSB2_REG_TXFADDR(5), 0x0098), + REGDECL(MUSB2_REG_TXHADDR(5), 0x009a), + REGDECL(MUSB2_REG_TXHUBPORT(5), 0x009b), + REGDECL(MUSB2_REG_RXFADDR(5), 0x009c), + REGDECL(MUSB2_REG_RXHADDR(5), 0x009e), + REGDECL(MUSB2_REG_RXHUBPORT(5), 0x009f), + REGDECL(MUSB2_REG_CONFDATA, 0x00c0), +}; + +static bus_size_t +awusbdrd_reg(bus_size_t o) +{ + bus_size_t v; + + if (o >= nitems(awusbdrd_regmap)) + return (o); + + v = awusbdrd_regmap[o]; + + KASSERT((v & REMAPFLAG) != 0, ("%s: reg %#lx not in regmap", + __func__, o)); + + return (v & ~REMAPFLAG); +} + +static int +awusbdrd_filt(bus_size_t o) +{ + switch (o) { + case MUSB2_REG_MISC: + case MUSB2_REG_RXDBDIS: + case MUSB2_REG_TXDBDIS: + return (1); + default: + return (0); + } +} + +static uint8_t +awusbdrd_bs_r_1(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o) +{ + const struct bus_space *bs = t; + + switch (o) { + case MUSB2_REG_HWVERS: + return (0); /* no known equivalent */ + } + + return bus_space_read_1(bs->bs_parent, h, awusbdrd_reg(o)); +} + +static uint16_t +awusbdrd_bs_r_2(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o) +{ + const struct bus_space *bs = t; + + return bus_space_read_2(bs->bs_parent, h, awusbdrd_reg(o)); +} + +static void +awusbdrd_bs_w_1(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o, + uint8_t v) +{ + const struct bus_space *bs = t; + + if (awusbdrd_filt(o) != 0) + return; + + bus_space_write_1(bs->bs_parent, h, awusbdrd_reg(o), v); +} + +static void +awusbdrd_bs_w_2(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o, + uint16_t v) +{ + const struct bus_space *bs = t; + + if (awusbdrd_filt(o) != 0) + return; + + bus_space_write_2(bs->bs_parent, h, awusbdrd_reg(o), v); +} + +static void +awusbdrd_bs_rm_1(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o, + uint8_t *d, bus_size_t c) +{ + const struct bus_space *bs = t; + + bus_space_read_multi_1(bs->bs_parent, h, awusbdrd_reg(o), d, c); +} + +static void +awusbdrd_bs_rm_4(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o, + uint32_t *d, bus_size_t c) +{ + const struct bus_space *bs = t; + + bus_space_read_multi_4(bs->bs_parent, h, awusbdrd_reg(o), d, c); +} + +static void +awusbdrd_bs_wm_1(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o, + const uint8_t *d, bus_size_t c) +{ + const struct bus_space *bs = t; + + if (awusbdrd_filt(o) != 0) + return; + + bus_space_write_multi_1(bs->bs_parent, h, awusbdrd_reg(o), d, c); +} + +static void +awusbdrd_bs_wm_4(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o, + const uint32_t *d, bus_size_t c) +{ + const struct bus_space *bs = t; + + if (awusbdrd_filt(o) != 0) + return; + + bus_space_write_multi_4(bs->bs_parent, h, awusbdrd_reg(o), d, c); +} + +static void +awusbdrd_intr(void *arg) +{ + struct awusbdrd_softc *sc = arg; + uint8_t intusb; + uint16_t inttx, intrx; + + intusb = MUSB2_READ_1(&sc->sc, MUSB2_REG_INTUSB); + inttx = MUSB2_READ_2(&sc->sc, MUSB2_REG_INTTX); + intrx = MUSB2_READ_2(&sc->sc, MUSB2_REG_INTRX); + if (intusb == 0 && inttx == 0 && intrx == 0) + return; + + if (intusb) + MUSB2_WRITE_1(&sc->sc, MUSB2_REG_INTUSB, intusb); + if (inttx) + MUSB2_WRITE_2(&sc->sc, MUSB2_REG_INTTX, inttx); + if (intrx) + MUSB2_WRITE_2(&sc->sc, MUSB2_REG_INTRX, intrx); + + musbotg_interrupt(arg, intrx, inttx, intusb); +} + +static int +awusbdrd_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 DRD"); + return (BUS_PROBE_DEFAULT); +} + +static int +awusbdrd_attach(device_t dev) +{ + struct awusbdrd_softc *sc; + int error; + + sc = device_get_softc(dev); + + error = bus_alloc_resources(dev, awusbdrd_spec, sc->res); + if (error != 0) + return (error); + + /* AHB gate clock is required */ + error = clk_get_by_ofw_index(dev, 0, &sc->clk); + if (error != 0) + goto fail; + + /* AHB reset is only present on some SoCs */ + (void)hwreset_get_by_ofw_idx(dev, 0, &sc->reset); + + /* Enable clocks */ + error = clk_enable(sc->clk); + if (error != 0) { + device_printf(dev, "failed to enable clock: %d\n", error); + goto fail; + } + if (sc->reset != NULL) { + error = hwreset_deassert(sc->reset); + if (error != 0) { + device_printf(dev, "failed to de-assert reset: %d\n", + error); + goto fail; + } + } + + sc->sc.sc_bus.parent = dev; + sc->sc.sc_bus.devices = sc->sc.sc_devices; + sc->sc.sc_bus.devices_max = MUSB2_MAX_DEVICES; + sc->sc.sc_bus.dma_bits = 32; + + error = usb_bus_mem_alloc_all(&sc->sc.sc_bus, USB_GET_DMA_TAG(dev), + NULL); + if (error != 0) { + error = ENOMEM; + goto fail; + } + + sc->bs.bs_parent = rman_get_bustag(sc->res[0]); + sc->bs.bs_r_1 = awusbdrd_bs_r_1; + sc->bs.bs_r_2 = awusbdrd_bs_r_2; + sc->bs.bs_w_1 = awusbdrd_bs_w_1; + sc->bs.bs_w_2 = awusbdrd_bs_w_2; + sc->bs.bs_rm_1 = awusbdrd_bs_rm_1; + sc->bs.bs_rm_4 = awusbdrd_bs_rm_4; + sc->bs.bs_wm_1 = awusbdrd_bs_wm_1; + sc->bs.bs_wm_4 = awusbdrd_bs_wm_4; + + sc->sc.sc_io_tag = &sc->bs; + sc->sc.sc_io_hdl = rman_get_bushandle(sc->res[0]); + sc->sc.sc_io_size = rman_get_size(sc->res[0]); + + sc->sc.sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (sc->sc.sc_bus.bdev == NULL) { + error = ENXIO; + goto fail; + } + device_set_ivars(sc->sc.sc_bus.bdev, &sc->sc.sc_bus); + sc->sc.sc_id = 0; + sc->sc.sc_platform_data = sc; + sc->sc.sc_mode = MUSB2_HOST_MODE; /* XXX HOST vs DEVICE mode */ + sc->sc.sc_ep_max = DRD_EP_MAX; + + error = bus_setup_intr(dev, sc->res[1], INTR_MPSAFE | INTR_TYPE_BIO, + NULL, awusbdrd_intr, sc, &sc->sc.sc_intr_hdl); + if (error != 0) + goto fail; + + /* Enable PIO mode */ + bus_write_1(sc->res[0], MUSB2_REG_AWIN_VEND0, VEND0_PIO_MODE); + + /* Map SRAMD area to USB0 (sun4i/sun7i only) */ + switch (allwinner_soc_family()) { + case ALLWINNERSOC_SUN4I: + case ALLWINNERSOC_SUN7I: + a10_map_to_otg(); + break; + } + + error = musbotg_init(&sc->sc); + if (error != 0) + goto fail; + + error = device_probe_and_attach(sc->sc.sc_bus.bdev); + if (error != 0) + goto fail; + + musbotg_vbus_interrupt(&sc->sc, 1); /* XXX VBUS */ + + return (0); + +fail: + if (sc->reset != NULL) + hwreset_release(sc->reset); + if (sc->clk != NULL) + clk_release(sc->clk); + bus_release_resources(dev, awusbdrd_spec, sc->res); + return (error); +} + +static int +awusbdrd_detach(device_t dev) +{ + struct awusbdrd_softc *sc; + device_t bdev; + int error; + + sc = device_get_softc(dev); + + if (sc->sc.sc_bus.bdev != NULL) { + bdev = sc->sc.sc_bus.bdev; + device_detach(bdev); + device_delete_child(dev, bdev); + } + + musbotg_uninit(&sc->sc); + error = bus_teardown_intr(dev, sc->res[1], sc->sc.sc_intr_hdl); + if (error != 0) + return (error); + + usb_bus_mem_free_all(&sc->sc.sc_bus, NULL); + + if (sc->reset != NULL) + hwreset_release(sc->reset); + if (sc->clk != NULL) + clk_release(sc->clk); + + bus_release_resources(dev, awusbdrd_spec, sc->res); + + device_delete_children(dev); + + return (0); +} + +static device_method_t awusbdrd_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, awusbdrd_probe), + DEVMETHOD(device_attach, awusbdrd_attach), + DEVMETHOD(device_detach, awusbdrd_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + DEVMETHOD_END +}; + +static driver_t awusbdrd_driver = { + .name = "musbotg", + .methods = awusbdrd_methods, + .size = sizeof(struct awusbdrd_softc), +}; + +static devclass_t awusbdrd_devclass; + +DRIVER_MODULE(musbotg, simplebus, awusbdrd_driver, awusbdrd_devclass, 0, 0); +MODULE_DEPEND(musbotg, usb, 1, 1, 1);