diff options
Diffstat (limited to 'sys/arm/samsung/exynos/exynos5_xhci.c')
-rw-r--r-- | sys/arm/samsung/exynos/exynos5_xhci.c | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/sys/arm/samsung/exynos/exynos5_xhci.c b/sys/arm/samsung/exynos/exynos5_xhci.c new file mode 100644 index 0000000..393f442 --- /dev/null +++ b/sys/arm/samsung/exynos/exynos5_xhci.c @@ -0,0 +1,341 @@ +/*- + * Copyright (c) 2014 Ruslan Bukin <br@bsdpad.com> + * 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_bus.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/condvar.h> +#include <sys/rman.h> + +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usb_busdma.h> +#include <dev/usb/usb_process.h> +#include <dev/usb/usb_controller.h> +#include <dev/usb/usb_bus.h> +#include <dev/usb/controller/xhci.h> +#include <dev/usb/controller/xhcireg.h> + +#include <dev/fdt/fdt_common.h> + +#include <machine/bus.h> +#include <machine/resource.h> + +#include <arm/samsung/exynos/exynos5_common.h> + +#include "opt_platform.h" + +#define GSNPSID 0x20 +#define GSNPSID_MASK 0xffff0000 +#define REVISION_MASK 0xffff +#define GCTL 0x10 +#define GCTL_PWRDNSCALE(n) ((n) << 19) +#define GCTL_U2RSTECN (1 << 16) +#define GCTL_CLK_BUS (0) +#define GCTL_CLK_PIPE (1) +#define GCTL_CLK_PIPEHALF (2) +#define GCTL_CLK_M (3) +#define GCTL_CLK_S (6) +#define GCTL_PRTCAP(n) (((n) & (3 << 12)) >> 12) +#define GCTL_PRTCAPDIR(n) ((n) << 12) +#define GCTL_PRTCAP_HOST 1 +#define GCTL_PRTCAP_DEVICE 2 +#define GCTL_PRTCAP_OTG 3 +#define GCTL_CORESOFTRESET (1 << 11) +#define GCTL_SCALEDOWN_MASK 3 +#define GCTL_SCALEDOWN_SHIFT 4 +#define GCTL_DISSCRAMBLE (1 << 3) +#define GCTL_DSBLCLKGTNG (1 << 0) +#define GHWPARAMS1 0x3c +#define GHWPARAMS1_EN_PWROPT(n) (((n) & (3 << 24)) >> 24) +#define GHWPARAMS1_EN_PWROPT_NO 0 +#define GHWPARAMS1_EN_PWROPT_CLK 1 +#define GUSB2PHYCFG(n) (0x100 + (n * 0x04)) +#define GUSB2PHYCFG_PHYSOFTRST (1 << 31) +#define GUSB2PHYCFG_SUSPHY (1 << 6) +#define GUSB3PIPECTL(n) (0x1c0 + (n * 0x04)) +#define GUSB3PIPECTL_PHYSOFTRST (1 << 31) +#define GUSB3PIPECTL_SUSPHY (1 << 17) + +/* Forward declarations */ +static int exynos_xhci_attach(device_t dev); +static int exynos_xhci_detach(device_t dev); +static int exynos_xhci_probe(device_t dev); + +struct exynos_xhci_softc { + device_t dev; + struct xhci_softc base; + struct resource *res[3]; + bus_space_tag_t bst; + bus_space_handle_t bsh; +}; + +static struct resource_spec exynos_xhci_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { SYS_RES_MEMORY, 1, RF_ACTIVE }, + { SYS_RES_IRQ, 0, RF_ACTIVE }, + { -1, 0 } +}; + +static device_method_t xhci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, exynos_xhci_probe), + DEVMETHOD(device_attach, exynos_xhci_attach), + DEVMETHOD(device_detach, exynos_xhci_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + DEVMETHOD_END +}; + +/* kobj_class definition */ +static driver_t xhci_driver = { + "xhci", + xhci_methods, + sizeof(struct xhci_softc) +}; + +static devclass_t xhci_devclass; + +DRIVER_MODULE(xhci, simplebus, xhci_driver, xhci_devclass, 0, 0); +MODULE_DEPEND(xhci, usb, 1, 1, 1); + +/* + * Public methods + */ +static int +exynos_xhci_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (ofw_bus_is_compatible(dev, "samsung,exynos5250-dwusb3") == 0) + return (ENXIO); + + device_set_desc(dev, "Exynos USB 3.0 controller"); + return (BUS_PROBE_DEFAULT); +} + +static int +dwc3_init(struct exynos_xhci_softc *esc) +{ + struct xhci_softc *sc; + int hwparams1; + int rev; + int reg; + + sc = &esc->base; + + rev = READ4(esc, GSNPSID); + if ((rev & GSNPSID_MASK) != 0x55330000) { + printf("It is not DWC3 controller\n"); + return (-1); + } + + /* Reset controller */ + WRITE4(esc, GCTL, GCTL_CORESOFTRESET); + WRITE4(esc, GUSB3PIPECTL(0), GUSB3PIPECTL_PHYSOFTRST); + WRITE4(esc, GUSB2PHYCFG(0), GUSB2PHYCFG_PHYSOFTRST); + + DELAY(100000); + + reg = READ4(esc, GUSB3PIPECTL(0)); + reg &= ~(GUSB3PIPECTL_PHYSOFTRST); + WRITE4(esc, GUSB3PIPECTL(0), reg); + + reg = READ4(esc, GUSB2PHYCFG(0)); + reg &= ~(GUSB2PHYCFG_PHYSOFTRST); + WRITE4(esc, GUSB2PHYCFG(0), reg); + + reg = READ4(esc, GCTL); + reg &= ~GCTL_CORESOFTRESET; + WRITE4(esc, GCTL, reg); + + hwparams1 = READ4(esc, GHWPARAMS1); + + reg = READ4(esc, GCTL); + reg &= ~(GCTL_SCALEDOWN_MASK << GCTL_SCALEDOWN_SHIFT); + reg &= ~(GCTL_DISSCRAMBLE); + + if (GHWPARAMS1_EN_PWROPT(hwparams1) == \ + GHWPARAMS1_EN_PWROPT_CLK) + reg &= ~(GCTL_DSBLCLKGTNG); + + if ((rev & REVISION_MASK) < 0x190a) + reg |= (GCTL_U2RSTECN); + WRITE4(esc, GCTL, reg); + + /* Set host mode */ + reg = READ4(esc, GCTL); + reg &= ~(GCTL_PRTCAPDIR(GCTL_PRTCAP_OTG)); + reg |= GCTL_PRTCAPDIR(GCTL_PRTCAP_HOST); + WRITE4(esc, GCTL, reg); + + return (0); +} + +static int +exynos_xhci_attach(device_t dev) +{ + struct exynos_xhci_softc *esc; + struct xhci_softc *sc; + bus_space_handle_t bsh; + int err; + + esc = device_get_softc(dev); + esc->dev = dev; + + sc = &esc->base; + + if (xhci_init(sc, dev)) { + device_printf(dev, "Could not initialize softc\n"); + return (ENXIO); + } + + if (bus_alloc_resources(dev, exynos_xhci_spec, esc->res)) { + device_printf(dev, "could not allocate resources\n"); + return (ENXIO); + } + + usb_callout_init_mtx(&sc->sc_callout, &sc->sc_bus.bus_mtx, 0); + + /* XHCI registers */ + sc->sc_io_tag = rman_get_bustag(esc->res[0]); + bsh = rman_get_bushandle(esc->res[0]); + sc->sc_io_size = rman_get_size(esc->res[0]); + + /* DWC3 ctrl registers */ + esc->bst = rman_get_bustag(esc->res[1]); + esc->bsh = rman_get_bushandle(esc->res[1]); + + /* + * Set handle to USB related registers subregion used by + * generic XHCI driver. + */ + err = bus_space_subregion(sc->sc_io_tag, bsh, 0x0, + sc->sc_io_size, &sc->sc_io_hdl); + if (err != 0) + return (ENXIO); + + /* Setup interrupt handler */ + err = bus_setup_intr(dev, esc->res[2], INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)xhci_interrupt, sc, + &sc->sc_intr_hdl); + if (err) { + device_printf(dev, "Could not setup irq, %d\n", err); + return (1); + } + + /* Add USB device */ + sc->sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (!sc->sc_bus.bdev) { + device_printf(dev, "Could not add USB device\n"); + err = bus_teardown_intr(dev, esc->res[2], + sc->sc_intr_hdl); + if (err) + device_printf(dev, "Could not tear down irq," + " %d\n", err); + return (1); + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + strlcpy(sc->sc_vendor, "Samsung", sizeof(sc->sc_vendor)); + + dwc3_init(esc); + + err = xhci_halt_controller(sc); + if (err == 0) { + device_printf(dev, "Starting controller\n"); + err = xhci_start_controller(sc); + } + + if (err == 0) { + device_printf(dev, "Controller started\n"); + err = device_probe_and_attach(sc->sc_bus.bdev); + } + + if (err == 0) { + device_printf(dev, "Attached success\n"); + } else { + device_printf(dev, "USB 3.0 init failed err=%d\n", err); + + device_delete_child(dev, sc->sc_bus.bdev); + sc->sc_bus.bdev = NULL; + + err = bus_teardown_intr(dev, esc->res[2], + sc->sc_intr_hdl); + if (err) + device_printf(dev, "Could not tear down irq," + " %d\n", err); + return (1); + } + return (0); +} + +static int +exynos_xhci_detach(device_t dev) +{ + struct exynos_xhci_softc *esc; + struct xhci_softc *sc; + int err; + + esc = device_get_softc(dev); + sc = &esc->base; + + if (esc->res[2] && sc->sc_intr_hdl) { + err = bus_teardown_intr(dev, esc->res[2], + sc->sc_intr_hdl); + if (err) { + device_printf(dev, "Could not tear down irq," + " %d\n", err); + return (err); + } + sc->sc_intr_hdl = NULL; + } + + if (sc->sc_bus.bdev) { + device_delete_child(dev, sc->sc_bus.bdev); + sc->sc_bus.bdev = NULL; + } + + /* During module unload there are lots of children leftover */ + device_delete_children(dev); + + bus_release_resources(dev, exynos_xhci_spec, esc->res); + + return (0); +} |