root/sys/arch/arm/xscale/pxa2x0_ohci.c
/*      $NetBSD: pxa2x0_ohci.c,v 1.14 2025/03/31 14:46:42 riastradh Exp $       */
/*      $OpenBSD: pxa2x0_ohci.c,v 1.19 2005/04/08 02:32:54 dlg Exp $ */

/*
 * Copyright (c) 2005 David Gwynne <dlg@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kernel.h>

#include <machine/intr.h>
#include <sys/bus.h>

#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdivar.h>
#include <dev/usb/usb_mem.h>

#include <dev/usb/ohcireg.h>
#include <dev/usb/ohcivar.h>

#include <arm/xscale/pxa2x0cpu.h>
#include <arm/xscale/pxa2x0reg.h>
#include <arm/xscale/pxa2x0var.h>
#include <arm/xscale/pxa2x0_gpio.h>

struct pxaohci_softc {
        ohci_softc_t    sc;

        void            *sc_ih;
};

#if 0
static void     pxaohci_power(int, void *);
#endif
static void     pxaohci_enable(struct pxaohci_softc *);
static void     pxaohci_disable(struct pxaohci_softc *);

#define HREAD4(sc,r)    bus_space_read_4((sc)->sc.iot, (sc)->sc.ioh, (r))
#define HWRITE4(sc,r,v) bus_space_write_4((sc)->sc.iot, (sc)->sc.ioh, (r), (v))

static int
pxaohci_match(device_t parent, struct cfdata *cf, void *aux)
{
        struct pxaip_attach_args *pxa = aux;

        if (CPU_IS_PXA270 && strcmp(pxa->pxa_name, cf->cf_name) == 0) {
                pxa->pxa_size = PXA2X0_USBHC_SIZE;
                return 1;
        }
        return 0;
}

static void
pxaohci_attach(device_t parent, device_t self, void *aux)
{
        struct pxaohci_softc *sc = device_private(self);
        struct pxaip_attach_args *pxa = aux;

#ifdef USB_DEBUG
        {
                //extern int ohcidebug;
                //ohcidebug = 16;
        }
#endif

        sc->sc.iot = pxa->pxa_iot;
        sc->sc.sc_bus.ub_dmatag = pxa->pxa_dmat;
        sc->sc.sc_size = 0;
        sc->sc_ih = NULL;
        sc->sc.sc_dev = self;
        sc->sc.sc_bus.ub_hcpriv = sc;

        aprint_normal("\n");
        aprint_naive("\n");

        /* Map I/O space */
        if (bus_space_map(sc->sc.iot, pxa->pxa_addr, pxa->pxa_size, 0,
            &sc->sc.ioh)) {
                aprint_error_dev(sc->sc.sc_dev, "couldn't map memory space\n");
                return;
        }
        sc->sc.sc_size = pxa->pxa_size;

        /* XXX copied from ohci_pci.c. needed? */
        bus_space_barrier(sc->sc.iot, sc->sc.ioh, 0, sc->sc.sc_size,
            BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE);

        /* start the usb clock */
        pxa2x0_clkman_config(CKEN_USBHC, 1);
        pxaohci_enable(sc);

        /* Disable interrupts, so we don't get any spurious ones. */
        bus_space_write_4(sc->sc.iot, sc->sc.ioh, OHCI_INTERRUPT_DISABLE,
            OHCI_MIE);

        sc->sc_ih = pxa2x0_intr_establish(PXA2X0_INT_USBH1, IPL_USB,
            ohci_intr, &sc->sc);
        if (sc->sc_ih == NULL) {
                aprint_error_dev(sc->sc.sc_dev,
                    "unable to establish interrupt\n");
                goto free_map;
        }

        int err = ohci_init(&sc->sc);
        if (err) {
                aprint_error_dev(sc->sc.sc_dev, "init failed, error=%d\n", err);
                goto free_intr;
        }

#if 0
        pmf_device_register1(self, ohci_suspend, ohci_resume, ohci_shutdown);
#endif

        sc->sc.sc_child = config_found(self, &sc->sc.sc_bus, usbctlprint,
            CFARGS_NONE);

        return;

free_intr:
        pxa2x0_intr_disestablish(sc->sc_ih);
        sc->sc_ih = NULL;
free_map:
        pxaohci_disable(sc);
        pxa2x0_clkman_config(CKEN_USBHC, 0);
        bus_space_unmap(sc->sc.iot, sc->sc.ioh, sc->sc.sc_size);
        sc->sc.sc_size = 0;
}

static int
pxaohci_detach(device_t self, int flags)
{
        struct pxaohci_softc *sc = device_private(self);
        int error;

        /*
         * Detach the USB child first.  Disconnects all USB devices and
         * prevents connecting new ones.
         */
        error = config_detach_children(self, flags);
        if (error)
                return error;

        /*
         * Shut down the controller and block interrupts at the device
         * level.  Once we have shut down the controller, the shutdown
         * handler no longer needed -- deregister it from PMF.
         * (Harmless to call ohci_shutdown more than once, so no
         * synchronization needed.)
         */
        ohci_shutdown(self, 0);
#if 0
        pmf_device_deregister(self);
#endif

        /*
         * Interrupts are blocked at the device level by ohci_shutdown.
         * Disestablish the interrupt handler.  This waits for it to
         * complete on all CPUs.
         */
        if (sc->sc_ih) {
                pxa2x0_intr_disestablish(sc->sc_ih);
                sc->sc_ih = NULL;
        }

        /*
         * Free the bus-independent ohci(4) state now that the
         * interrupt handler has ceased to run on all CPUs.
         */
        ohci_detach(&sc->sc);

        /*
         * Issue a Full Host Reset to disable the host controller
         * interface.
         *
         * XXX Is this necessary or is it redundant with ohci_shutdown?
         * Should it be done in ohci_shutdown as well?
         */
        pxaohci_disable(sc);

        /* stop clock */
        pxa2x0_clkman_config(CKEN_USBHC, 0);

        /*
         * Unmap the registers now that we're all done with them.
         */
        if (sc->sc.sc_size) {
                bus_space_unmap(sc->sc.iot, sc->sc.ioh, sc->sc.sc_size);
                sc->sc.sc_size = 0;
        }

        return 0;
}

#if 0
static void
pxaohci_power(int why, void *arg)
{
        struct pxaohci_softc *sc = (struct pxaohci_softc *)arg;
        int s;

        s = splhardusb();
        sc->sc.sc_bus.ub_usepolling++;
        switch (why) {
        case PWR_STANDBY:
        case PWR_SUSPEND:
#if 0
                ohci_power(why, &sc->sc);
#endif
                pxa2x0_clkman_config(CKEN_USBHC, 0);
                break;

        case PWR_RESUME:
                pxa2x0_clkman_config(CKEN_USBHC, 1);
                pxaohci_enable(sc);
#if 0
                ohci_power(why, &sc->sc);
#endif
                break;
        }
        sc->sc.sc_bus.ub_usepolling--;
        splx(s);
}
#endif

static void
pxaohci_enable(struct pxaohci_softc *sc)
{
        uint32_t hr;

        /* Full host reset */
        hr = HREAD4(sc, USBHC_HR);
        HWRITE4(sc, USBHC_HR, (hr & USBHC_HR_MASK) | USBHC_HR_FHR);

        DELAY(USBHC_RST_WAIT);

        hr = HREAD4(sc, USBHC_HR);
        HWRITE4(sc, USBHC_HR, (hr & USBHC_HR_MASK) & ~(USBHC_HR_FHR));

        /* Force system bus interface reset */
        hr = HREAD4(sc, USBHC_HR);
        HWRITE4(sc, USBHC_HR, (hr & USBHC_HR_MASK) | USBHC_HR_FSBIR);

        while (HREAD4(sc, USBHC_HR) & USBHC_HR_FSBIR)
                DELAY(3);

        /* Enable the ports (physically only one, only enable that one?) */
        hr = HREAD4(sc, USBHC_HR);
        HWRITE4(sc, USBHC_HR, (hr & USBHC_HR_MASK) & ~(USBHC_HR_SSE));
        hr = HREAD4(sc, USBHC_HR);
        HWRITE4(sc, USBHC_HR, (hr & USBHC_HR_MASK) &
                        ~(USBHC_HR_SSEP1 | USBHC_HR_SSEP2 | USBHC_HR_SSEP3));
        HWRITE4(sc, USBHC_HIE, USBHC_HIE_RWIE | USBHC_HIE_UPRIE);

        hr = HREAD4(sc, USBHC_UHCRHDA);
}

static void
pxaohci_disable(struct pxaohci_softc *sc)
{
        uint32_t hr;

        /* Full host reset */
        hr = HREAD4(sc, USBHC_HR);
        HWRITE4(sc, USBHC_HR, (hr & USBHC_HR_MASK) | USBHC_HR_FHR);

        DELAY(USBHC_RST_WAIT);

        hr = HREAD4(sc, USBHC_HR);
        HWRITE4(sc, USBHC_HR, (hr & USBHC_HR_MASK) & ~(USBHC_HR_FHR));
}


CFATTACH_DECL2_NEW(pxaohci, sizeof(struct pxaohci_softc),
    pxaohci_match, pxaohci_attach, pxaohci_detach, ohci_activate, NULL,
    ohci_childdet);