summaryrefslogtreecommitdiffstats
path: root/sys/dev/usb/controller
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/usb/controller')
-rw-r--r--sys/dev/usb/controller/at91dci.c2467
-rw-r--r--sys/dev/usb/controller/at91dci.h245
-rw-r--r--sys/dev/usb/controller/at91dci_atmelarm.c347
-rw-r--r--sys/dev/usb/controller/atmegadci.c2327
-rw-r--r--sys/dev/usb/controller/atmegadci.h273
-rw-r--r--sys/dev/usb/controller/atmegadci_atmelarm.c27
-rw-r--r--sys/dev/usb/controller/ehci.c3965
-rw-r--r--sys/dev/usb/controller/ehci.h532
-rw-r--r--sys/dev/usb/controller/ehci_ixp4xx.c348
-rw-r--r--sys/dev/usb/controller/ehci_mbus.c364
-rw-r--r--sys/dev/usb/controller/ehci_pci.c486
-rw-r--r--sys/dev/usb/controller/musb_otg.c2875
-rw-r--r--sys/dev/usb/controller/musb_otg.h407
-rw-r--r--sys/dev/usb/controller/musb_otg_atmelarm.c239
-rw-r--r--sys/dev/usb/controller/ohci.c2862
-rw-r--r--sys/dev/usb/controller/ohci.h366
-rw-r--r--sys/dev/usb/controller/ohci_atmelarm.c223
-rw-r--r--sys/dev/usb/controller/ohci_pci.c387
-rw-r--r--sys/dev/usb/controller/uhci.c3381
-rw-r--r--sys/dev/usb/controller/uhci.h321
-rw-r--r--sys/dev/usb/controller/uhci_pci.c443
-rw-r--r--sys/dev/usb/controller/usb_controller.c620
-rw-r--r--sys/dev/usb/controller/uss820dci.c2489
-rw-r--r--sys/dev/usb/controller/uss820dci.h377
-rw-r--r--sys/dev/usb/controller/uss820dci_atmelarm.c238
25 files changed, 26609 insertions, 0 deletions
diff --git a/sys/dev/usb/controller/at91dci.c b/sys/dev/usb/controller/at91dci.c
new file mode 100644
index 0000000..a7283b4
--- /dev/null
+++ b/sys/dev/usb/controller/at91dci.c
@@ -0,0 +1,2467 @@
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*-
+ * Copyright (c) 2007-2008 Hans Petter Selasky. 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.
+ */
+
+/*
+ * This file contains the driver for the AT91 series USB Device
+ * Controller
+ */
+
+/*
+ * Thanks to "David Brownell" for helping out regarding the hardware
+ * endpoint profiles.
+ */
+
+/*
+ * NOTE: The "fifo_bank" is not reset in hardware when the endpoint is
+ * reset !
+ *
+ * NOTE: When the chip detects BUS-reset it will also reset the
+ * endpoints, Function-address and more.
+ */
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_error.h>
+#include <dev/usb/usb_defs.h>
+
+#define USB_DEBUG_VAR at91dcidebug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/at91dci.h>
+
+#define AT9100_DCI_BUS2SC(bus) \
+ ((struct at91dci_softc *)(((uint8_t *)(bus)) - \
+ USB_P2U(&(((struct at91dci_softc *)0)->sc_bus))))
+
+#define AT9100_DCI_PC2SC(pc) \
+ AT9100_DCI_BUS2SC((pc)->tag_parent->info->bus)
+
+#if USB_DEBUG
+static int at91dcidebug = 0;
+
+SYSCTL_NODE(_hw_usb2, OID_AUTO, at91dci, CTLFLAG_RW, 0, "USB at91dci");
+SYSCTL_INT(_hw_usb2_at91dci, OID_AUTO, debug, CTLFLAG_RW,
+ &at91dcidebug, 0, "at91dci debug level");
+#endif
+
+#define AT9100_DCI_INTR_ENDPT 1
+
+/* prototypes */
+
+struct usb2_bus_methods at91dci_bus_methods;
+struct usb2_pipe_methods at91dci_device_bulk_methods;
+struct usb2_pipe_methods at91dci_device_ctrl_methods;
+struct usb2_pipe_methods at91dci_device_intr_methods;
+struct usb2_pipe_methods at91dci_device_isoc_fs_methods;
+struct usb2_pipe_methods at91dci_root_ctrl_methods;
+struct usb2_pipe_methods at91dci_root_intr_methods;
+
+static at91dci_cmd_t at91dci_setup_rx;
+static at91dci_cmd_t at91dci_data_rx;
+static at91dci_cmd_t at91dci_data_tx;
+static at91dci_cmd_t at91dci_data_tx_sync;
+static void at91dci_device_done(struct usb2_xfer *, usb2_error_t);
+static void at91dci_do_poll(struct usb2_bus *);
+static void at91dci_root_ctrl_poll(struct at91dci_softc *);
+static void at91dci_standard_done(struct usb2_xfer *);
+
+static usb2_sw_transfer_func_t at91dci_root_intr_done;
+static usb2_sw_transfer_func_t at91dci_root_ctrl_done;
+
+/*
+ * NOTE: Some of the bits in the CSR register have inverse meaning so
+ * we need a helper macro when acknowledging events:
+ */
+#define AT91_CSR_ACK(csr, what) do { \
+ (csr) &= ~((AT91_UDP_CSR_FORCESTALL| \
+ AT91_UDP_CSR_TXPKTRDY| \
+ AT91_UDP_CSR_RXBYTECNT) ^ (what));\
+ (csr) |= ((AT91_UDP_CSR_RX_DATA_BK0| \
+ AT91_UDP_CSR_RX_DATA_BK1| \
+ AT91_UDP_CSR_TXCOMP| \
+ AT91_UDP_CSR_RXSETUP| \
+ AT91_UDP_CSR_STALLSENT) ^ (what)); \
+} while (0)
+
+/*
+ * Here is a list of what the chip supports.
+ * Probably it supports more than listed here!
+ */
+static const struct usb2_hw_ep_profile
+ at91dci_ep_profile[AT91_UDP_EP_MAX] = {
+
+ [0] = {
+ .max_in_frame_size = 8,
+ .max_out_frame_size = 8,
+ .is_simplex = 1,
+ .support_control = 1,
+ },
+ [1] = {
+ .max_in_frame_size = 64,
+ .max_out_frame_size = 64,
+ .is_simplex = 1,
+ .support_multi_buffer = 1,
+ .support_bulk = 1,
+ .support_interrupt = 1,
+ .support_isochronous = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+ [2] = {
+ .max_in_frame_size = 64,
+ .max_out_frame_size = 64,
+ .is_simplex = 1,
+ .support_multi_buffer = 1,
+ .support_bulk = 1,
+ .support_interrupt = 1,
+ .support_isochronous = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+ [3] = {
+ /* can also do BULK */
+ .max_in_frame_size = 8,
+ .max_out_frame_size = 8,
+ .is_simplex = 1,
+ .support_interrupt = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+ [4] = {
+ .max_in_frame_size = 256,
+ .max_out_frame_size = 256,
+ .is_simplex = 1,
+ .support_multi_buffer = 1,
+ .support_bulk = 1,
+ .support_interrupt = 1,
+ .support_isochronous = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+ [5] = {
+ .max_in_frame_size = 256,
+ .max_out_frame_size = 256,
+ .is_simplex = 1,
+ .support_multi_buffer = 1,
+ .support_bulk = 1,
+ .support_interrupt = 1,
+ .support_isochronous = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+};
+
+static void
+at91dci_get_hw_ep_profile(struct usb2_device *udev,
+ const struct usb2_hw_ep_profile **ppf, uint8_t ep_addr)
+{
+ if (ep_addr < AT91_UDP_EP_MAX) {
+ *ppf = (at91dci_ep_profile + ep_addr);
+ } else {
+ *ppf = NULL;
+ }
+}
+
+static void
+at91dci_clocks_on(struct at91dci_softc *sc)
+{
+ if (sc->sc_flags.clocks_off &&
+ sc->sc_flags.port_powered) {
+
+ DPRINTFN(5, "\n");
+
+ if (sc->sc_clocks_on) {
+ (sc->sc_clocks_on) (sc->sc_clocks_arg);
+ }
+ sc->sc_flags.clocks_off = 0;
+
+ /* enable Transceiver */
+ AT91_UDP_WRITE_4(sc, AT91_UDP_TXVC, 0);
+ }
+}
+
+static void
+at91dci_clocks_off(struct at91dci_softc *sc)
+{
+ if (!sc->sc_flags.clocks_off) {
+
+ DPRINTFN(5, "\n");
+
+ /* disable Transceiver */
+ AT91_UDP_WRITE_4(sc, AT91_UDP_TXVC, AT91_UDP_TXVC_DIS);
+
+ if (sc->sc_clocks_off) {
+ (sc->sc_clocks_off) (sc->sc_clocks_arg);
+ }
+ sc->sc_flags.clocks_off = 1;
+ }
+}
+
+static void
+at91dci_pull_up(struct at91dci_softc *sc)
+{
+ /* pullup D+, if possible */
+
+ if (!sc->sc_flags.d_pulled_up &&
+ sc->sc_flags.port_powered) {
+ sc->sc_flags.d_pulled_up = 1;
+ (sc->sc_pull_up) (sc->sc_pull_arg);
+ }
+}
+
+static void
+at91dci_pull_down(struct at91dci_softc *sc)
+{
+ /* pulldown D+, if possible */
+
+ if (sc->sc_flags.d_pulled_up) {
+ sc->sc_flags.d_pulled_up = 0;
+ (sc->sc_pull_down) (sc->sc_pull_arg);
+ }
+}
+
+static void
+at91dci_wakeup_peer(struct usb2_xfer *xfer)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+ uint8_t use_polling;
+
+ if (!(sc->sc_flags.status_suspend)) {
+ return;
+ }
+ use_polling = mtx_owned(xfer->xroot->xfer_mtx) ? 1 : 0;
+
+ AT91_UDP_WRITE_4(sc, AT91_UDP_GSTATE, AT91_UDP_GSTATE_ESR);
+
+ /* wait 8 milliseconds */
+ if (use_polling) {
+ /* polling */
+ DELAY(8000);
+ } else {
+ /* Wait for reset to complete. */
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125);
+ }
+
+ AT91_UDP_WRITE_4(sc, AT91_UDP_GSTATE, 0);
+}
+
+static void
+at91dci_set_address(struct at91dci_softc *sc, uint8_t addr)
+{
+ DPRINTFN(5, "addr=%d\n", addr);
+
+ AT91_UDP_WRITE_4(sc, AT91_UDP_FADDR, addr |
+ AT91_UDP_FADDR_EN);
+}
+
+static uint8_t
+at91dci_setup_rx(struct at91dci_td *td)
+{
+ struct at91dci_softc *sc;
+ struct usb2_device_request req;
+ uint32_t csr;
+ uint32_t temp;
+ uint16_t count;
+
+ /* read out FIFO status */
+ csr = bus_space_read_4(td->io_tag, td->io_hdl,
+ td->status_reg);
+
+ DPRINTFN(5, "csr=0x%08x rem=%u\n", csr, td->remainder);
+
+ temp = csr;
+ temp &= (AT91_UDP_CSR_RX_DATA_BK0 |
+ AT91_UDP_CSR_RX_DATA_BK1 |
+ AT91_UDP_CSR_STALLSENT |
+ AT91_UDP_CSR_RXSETUP |
+ AT91_UDP_CSR_TXCOMP);
+
+ if (!(csr & AT91_UDP_CSR_RXSETUP)) {
+ /* abort any ongoing transfer */
+ if (!td->did_stall) {
+ DPRINTFN(5, "stalling\n");
+ temp |= AT91_UDP_CSR_FORCESTALL;
+ td->did_stall = 1;
+ }
+ goto not_complete;
+ }
+ /* get the packet byte count */
+ count = (csr & AT91_UDP_CSR_RXBYTECNT) >> 16;
+
+ /* verify data length */
+ if (count != td->remainder) {
+ DPRINTFN(0, "Invalid SETUP packet "
+ "length, %d bytes\n", count);
+ goto not_complete;
+ }
+ if (count != sizeof(req)) {
+ DPRINTFN(0, "Unsupported SETUP packet "
+ "length, %d bytes\n", count);
+ goto not_complete;
+ }
+ /* receive data */
+ bus_space_read_multi_1(td->io_tag, td->io_hdl,
+ td->fifo_reg, (void *)&req, sizeof(req));
+
+ /* copy data into real buffer */
+ usb2_copy_in(td->pc, 0, &req, sizeof(req));
+
+ td->offset = sizeof(req);
+ td->remainder = 0;
+
+ /* get pointer to softc */
+ sc = AT9100_DCI_PC2SC(td->pc);
+
+ /* sneak peek the set address */
+ if ((req.bmRequestType == UT_WRITE_DEVICE) &&
+ (req.bRequest == UR_SET_ADDRESS)) {
+ sc->sc_dv_addr = req.wValue[0] & 0x7F;
+ } else {
+ sc->sc_dv_addr = 0xFF;
+ }
+
+ /* sneak peek the endpoint direction */
+ if (req.bmRequestType & UE_DIR_IN) {
+ csr |= AT91_UDP_CSR_DIR;
+ } else {
+ csr &= ~AT91_UDP_CSR_DIR;
+ }
+
+ /* write the direction of the control transfer */
+ AT91_CSR_ACK(csr, temp);
+ bus_space_write_4(td->io_tag, td->io_hdl,
+ td->status_reg, csr);
+ return (0); /* complete */
+
+not_complete:
+ /* clear interrupts, if any */
+ if (temp) {
+ DPRINTFN(5, "clearing 0x%08x\n", temp);
+ AT91_CSR_ACK(csr, temp);
+ bus_space_write_4(td->io_tag, td->io_hdl,
+ td->status_reg, csr);
+ }
+ return (1); /* not complete */
+
+}
+
+static uint8_t
+at91dci_data_rx(struct at91dci_td *td)
+{
+ struct usb2_page_search buf_res;
+ uint32_t csr;
+ uint32_t temp;
+ uint16_t count;
+ uint8_t to;
+ uint8_t got_short;
+
+ to = 2; /* don't loop forever! */
+ got_short = 0;
+
+ /* check if any of the FIFO banks have data */
+repeat:
+ /* read out FIFO status */
+ csr = bus_space_read_4(td->io_tag, td->io_hdl,
+ td->status_reg);
+
+ DPRINTFN(5, "csr=0x%08x rem=%u\n", csr, td->remainder);
+
+ if (csr & AT91_UDP_CSR_RXSETUP) {
+ if (td->remainder == 0) {
+ /*
+ * We are actually complete and have
+ * received the next SETUP
+ */
+ DPRINTFN(5, "faking complete\n");
+ return (0); /* complete */
+ }
+ /*
+ * USB Host Aborted the transfer.
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ /* Make sure that "STALLSENT" gets cleared */
+ temp = csr;
+ temp &= AT91_UDP_CSR_STALLSENT;
+
+ /* check status */
+ if (!(csr & (AT91_UDP_CSR_RX_DATA_BK0 |
+ AT91_UDP_CSR_RX_DATA_BK1))) {
+ if (temp) {
+ /* write command */
+ AT91_CSR_ACK(csr, temp);
+ bus_space_write_4(td->io_tag, td->io_hdl,
+ td->status_reg, csr);
+ }
+ return (1); /* not complete */
+ }
+ /* get the packet byte count */
+ count = (csr & AT91_UDP_CSR_RXBYTECNT) >> 16;
+
+ /* verify the packet byte count */
+ if (count != td->max_packet_size) {
+ if (count < td->max_packet_size) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ got_short = 1;
+ } else {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ }
+ /* verify the packet byte count */
+ if (count > td->remainder) {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ while (count > 0) {
+ usb2_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* receive data */
+ bus_space_read_multi_1(td->io_tag, td->io_hdl,
+ td->fifo_reg, buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* clear status bits */
+ if (td->support_multi_buffer) {
+ if (td->fifo_bank) {
+ td->fifo_bank = 0;
+ temp |= AT91_UDP_CSR_RX_DATA_BK1;
+ } else {
+ td->fifo_bank = 1;
+ temp |= AT91_UDP_CSR_RX_DATA_BK0;
+ }
+ } else {
+ temp |= (AT91_UDP_CSR_RX_DATA_BK0 |
+ AT91_UDP_CSR_RX_DATA_BK1);
+ }
+
+ /* write command */
+ AT91_CSR_ACK(csr, temp);
+ bus_space_write_4(td->io_tag, td->io_hdl,
+ td->status_reg, csr);
+
+ /*
+ * NOTE: We may have to delay a little bit before
+ * proceeding after clearing the DATA_BK bits.
+ */
+
+ /* check if we are complete */
+ if ((td->remainder == 0) || got_short) {
+ if (td->short_pkt) {
+ /* we are complete */
+ return (0);
+ }
+ /* else need to receive a zero length packet */
+ }
+ if (--to) {
+ goto repeat;
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+at91dci_data_tx(struct at91dci_td *td)
+{
+ struct usb2_page_search buf_res;
+ uint32_t csr;
+ uint32_t temp;
+ uint16_t count;
+ uint8_t to;
+
+ to = 2; /* don't loop forever! */
+
+repeat:
+
+ /* read out FIFO status */
+ csr = bus_space_read_4(td->io_tag, td->io_hdl,
+ td->status_reg);
+
+ DPRINTFN(5, "csr=0x%08x rem=%u\n", csr, td->remainder);
+
+ if (csr & AT91_UDP_CSR_RXSETUP) {
+ /*
+ * The current transfer was aborted
+ * by the USB Host
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ /* Make sure that "STALLSENT" gets cleared */
+ temp = csr;
+ temp &= AT91_UDP_CSR_STALLSENT;
+
+ if (csr & AT91_UDP_CSR_TXPKTRDY) {
+ if (temp) {
+ /* write command */
+ AT91_CSR_ACK(csr, temp);
+ bus_space_write_4(td->io_tag, td->io_hdl,
+ td->status_reg, csr);
+ }
+ return (1); /* not complete */
+ } else {
+ /* clear TXCOMP and set TXPKTRDY */
+ temp |= (AT91_UDP_CSR_TXCOMP |
+ AT91_UDP_CSR_TXPKTRDY);
+ }
+
+ count = td->max_packet_size;
+ if (td->remainder < count) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ count = td->remainder;
+ }
+ while (count > 0) {
+
+ usb2_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* transmit data */
+ bus_space_write_multi_1(td->io_tag, td->io_hdl,
+ td->fifo_reg, buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* write command */
+ AT91_CSR_ACK(csr, temp);
+ bus_space_write_4(td->io_tag, td->io_hdl,
+ td->status_reg, csr);
+
+ /* check remainder */
+ if (td->remainder == 0) {
+ if (td->short_pkt) {
+ return (0); /* complete */
+ }
+ /* else we need to transmit a short packet */
+ }
+ if (--to) {
+ goto repeat;
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+at91dci_data_tx_sync(struct at91dci_td *td)
+{
+ struct at91dci_softc *sc;
+ uint32_t csr;
+ uint32_t temp;
+
+#if 0
+repeat:
+#endif
+
+ /* read out FIFO status */
+ csr = bus_space_read_4(td->io_tag, td->io_hdl,
+ td->status_reg);
+
+ DPRINTFN(5, "csr=0x%08x\n", csr);
+
+ if (csr & AT91_UDP_CSR_RXSETUP) {
+ DPRINTFN(5, "faking complete\n");
+ /* Race condition */
+ return (0); /* complete */
+ }
+ temp = csr;
+ temp &= (AT91_UDP_CSR_STALLSENT |
+ AT91_UDP_CSR_TXCOMP);
+
+ /* check status */
+ if (csr & AT91_UDP_CSR_TXPKTRDY) {
+ goto not_complete;
+ }
+ if (!(csr & AT91_UDP_CSR_TXCOMP)) {
+ goto not_complete;
+ }
+ sc = AT9100_DCI_PC2SC(td->pc);
+ if (sc->sc_dv_addr != 0xFF) {
+ /*
+ * The AT91 has a special requirement with regard to
+ * setting the address and that is to write the new
+ * address before clearing TXCOMP:
+ */
+ at91dci_set_address(sc, sc->sc_dv_addr);
+ }
+ /* write command */
+ AT91_CSR_ACK(csr, temp);
+ bus_space_write_4(td->io_tag, td->io_hdl,
+ td->status_reg, csr);
+
+ return (0); /* complete */
+
+not_complete:
+ if (temp) {
+ /* write command */
+ AT91_CSR_ACK(csr, temp);
+ bus_space_write_4(td->io_tag, td->io_hdl,
+ td->status_reg, csr);
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+at91dci_xfer_do_fifo(struct usb2_xfer *xfer)
+{
+ struct at91dci_softc *sc;
+ struct at91dci_td *td;
+ uint8_t temp;
+
+ DPRINTFN(9, "\n");
+
+ td = xfer->td_transfer_cache;
+ while (1) {
+ if ((td->func) (td)) {
+ /* operation in progress */
+ break;
+ }
+ if (((void *)td) == xfer->td_transfer_last) {
+ goto done;
+ }
+ if (td->error) {
+ goto done;
+ } else if (td->remainder > 0) {
+ /*
+ * We had a short transfer. If there is no alternate
+ * next, stop processing !
+ */
+ if (!td->alt_next) {
+ goto done;
+ }
+ }
+ /*
+ * Fetch the next transfer descriptor and transfer
+ * some flags to the next transfer descriptor
+ */
+ temp = 0;
+ if (td->fifo_bank)
+ temp |= 1;
+ td = td->obj_next;
+ xfer->td_transfer_cache = td;
+ if (temp & 1)
+ td->fifo_bank = 1;
+ }
+ return (1); /* not complete */
+
+done:
+ sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+ temp = (xfer->endpoint & UE_ADDR);
+
+ /* update FIFO bank flag and multi buffer */
+ if (td->fifo_bank) {
+ sc->sc_ep_flags[temp].fifo_bank = 1;
+ } else {
+ sc->sc_ep_flags[temp].fifo_bank = 0;
+ }
+
+ /* compute all actual lengths */
+
+ at91dci_standard_done(xfer);
+
+ return (0); /* complete */
+}
+
+static void
+at91dci_interrupt_poll(struct at91dci_softc *sc)
+{
+ struct usb2_xfer *xfer;
+
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ if (!at91dci_xfer_do_fifo(xfer)) {
+ /* queue has been modified */
+ goto repeat;
+ }
+ }
+}
+
+void
+at91dci_vbus_interrupt(struct at91dci_softc *sc, uint8_t is_on)
+{
+ DPRINTFN(5, "vbus = %u\n", is_on);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ if (is_on) {
+ if (!sc->sc_flags.status_vbus) {
+ sc->sc_flags.status_vbus = 1;
+
+ /* complete root HUB interrupt endpoint */
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &at91dci_root_intr_done);
+ }
+ } else {
+ if (sc->sc_flags.status_vbus) {
+ sc->sc_flags.status_vbus = 0;
+ sc->sc_flags.status_bus_reset = 0;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ /* complete root HUB interrupt endpoint */
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &at91dci_root_intr_done);
+ }
+ }
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+void
+at91dci_interrupt(struct at91dci_softc *sc)
+{
+ uint32_t status;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ status = AT91_UDP_READ_4(sc, AT91_UDP_ISR);
+ status &= AT91_UDP_INT_DEFAULT;
+
+ if (!status) {
+ USB_BUS_UNLOCK(&sc->sc_bus);
+ return;
+ }
+ /* acknowledge interrupts */
+
+ AT91_UDP_WRITE_4(sc, AT91_UDP_ICR, status);
+
+ /* check for any bus state change interrupts */
+
+ if (status & AT91_UDP_INT_BUS) {
+
+ DPRINTFN(5, "real bus interrupt 0x%08x\n", status);
+
+ if (status & AT91_UDP_INT_END_BR) {
+
+ /* set correct state */
+ sc->sc_flags.status_bus_reset = 1;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ /* disable resume interrupt */
+ AT91_UDP_WRITE_4(sc, AT91_UDP_IDR,
+ AT91_UDP_INT_RXRSM);
+ /* enable suspend interrupt */
+ AT91_UDP_WRITE_4(sc, AT91_UDP_IER,
+ AT91_UDP_INT_RXSUSP);
+ }
+ /*
+ * If RXRSM and RXSUSP is set at the same time we interpret
+ * that like RESUME. Resume is set when there is at least 3
+ * milliseconds of inactivity on the USB BUS.
+ */
+ if (status & AT91_UDP_INT_RXRSM) {
+ if (sc->sc_flags.status_suspend) {
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 1;
+
+ /* disable resume interrupt */
+ AT91_UDP_WRITE_4(sc, AT91_UDP_IDR,
+ AT91_UDP_INT_RXRSM);
+ /* enable suspend interrupt */
+ AT91_UDP_WRITE_4(sc, AT91_UDP_IER,
+ AT91_UDP_INT_RXSUSP);
+ }
+ } else if (status & AT91_UDP_INT_RXSUSP) {
+ if (!sc->sc_flags.status_suspend) {
+ sc->sc_flags.status_suspend = 1;
+ sc->sc_flags.change_suspend = 1;
+
+ /* disable suspend interrupt */
+ AT91_UDP_WRITE_4(sc, AT91_UDP_IDR,
+ AT91_UDP_INT_RXSUSP);
+
+ /* enable resume interrupt */
+ AT91_UDP_WRITE_4(sc, AT91_UDP_IER,
+ AT91_UDP_INT_RXRSM);
+ }
+ }
+ /* complete root HUB interrupt endpoint */
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &at91dci_root_intr_done);
+ }
+ /* check for any endpoint interrupts */
+
+ if (status & AT91_UDP_INT_EPS) {
+
+ DPRINTFN(5, "real endpoint interrupt 0x%08x\n", status);
+
+ at91dci_interrupt_poll(sc);
+ }
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+at91dci_setup_standard_chain_sub(struct at91dci_std_temp *temp)
+{
+ struct at91dci_td *td;
+
+ /* get current Transfer Descriptor */
+ td = temp->td_next;
+ temp->td = td;
+
+ /* prepare for next TD */
+ temp->td_next = td->obj_next;
+
+ /* fill out the Transfer Descriptor */
+ td->func = temp->func;
+ td->pc = temp->pc;
+ td->offset = temp->offset;
+ td->remainder = temp->len;
+ td->fifo_bank = 0;
+ td->error = 0;
+ td->did_stall = 0;
+ td->short_pkt = temp->short_pkt;
+ td->alt_next = temp->setup_alt_next;
+}
+
+static void
+at91dci_setup_standard_chain(struct usb2_xfer *xfer)
+{
+ struct at91dci_std_temp temp;
+ struct at91dci_softc *sc;
+ struct at91dci_td *td;
+ uint32_t x;
+ uint8_t ep_no;
+ uint8_t need_sync;
+
+ DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpoint),
+ xfer->sumlen, usb2_get_speed(xfer->xroot->udev));
+
+ temp.max_frame_size = xfer->max_frame_size;
+
+ td = xfer->td_start[0];
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ /* setup temp */
+
+ temp.td = NULL;
+ temp.td_next = xfer->td_start[0];
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+ temp.offset = 0;
+
+ sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+ ep_no = (xfer->endpoint & UE_ADDR);
+
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+
+ temp.func = &at91dci_setup_rx;
+ temp.len = xfer->frlengths[0];
+ temp.pc = xfer->frbuffers + 0;
+ temp.short_pkt = temp.len ? 1 : 0;
+
+ at91dci_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ if (x != xfer->nframes) {
+ if (xfer->endpoint & UE_DIR_IN) {
+ temp.func = &at91dci_data_tx;
+ need_sync = 1;
+ } else {
+ temp.func = &at91dci_data_rx;
+ need_sync = 0;
+ }
+
+ /* setup "pc" pointer */
+ temp.pc = xfer->frbuffers + x;
+ } else {
+ need_sync = 0;
+ }
+ while (x != xfer->nframes) {
+
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+
+ x++;
+
+ if (x == xfer->nframes) {
+ temp.setup_alt_next = 0;
+ }
+ if (temp.len == 0) {
+
+ /* make sure that we send an USB packet */
+
+ temp.short_pkt = 0;
+
+ } else {
+
+ /* regular data transfer */
+
+ temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+ }
+
+ at91dci_setup_standard_chain_sub(&temp);
+
+ if (xfer->flags_int.isochronous_xfr) {
+ temp.offset += temp.len;
+ } else {
+ /* get next Page Cache pointer */
+ temp.pc = xfer->frbuffers + x;
+ }
+ }
+
+ /* always setup a valid "pc" pointer for status and sync */
+ temp.pc = xfer->frbuffers + 0;
+
+ /* check if we need to sync */
+ if (need_sync && xfer->flags_int.control_xfr) {
+
+ /* we need a SYNC point after TX */
+ temp.func = &at91dci_data_tx_sync;
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ at91dci_setup_standard_chain_sub(&temp);
+ }
+ /* check if we should append a status stage */
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ /*
+ * Send a DATA1 message and invert the current
+ * endpoint direction.
+ */
+ if (xfer->endpoint & UE_DIR_IN) {
+ temp.func = &at91dci_data_rx;
+ need_sync = 0;
+ } else {
+ temp.func = &at91dci_data_tx;
+ need_sync = 1;
+ }
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ at91dci_setup_standard_chain_sub(&temp);
+ if (need_sync) {
+ /* we need a SYNC point after TX */
+ temp.func = &at91dci_data_tx_sync;
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ at91dci_setup_standard_chain_sub(&temp);
+ }
+ }
+ /* must have at least one frame! */
+ td = temp.td;
+ xfer->td_transfer_last = td;
+
+ /* setup the correct fifo bank */
+ if (sc->sc_ep_flags[ep_no].fifo_bank) {
+ td = xfer->td_transfer_first;
+ td->fifo_bank = 1;
+ }
+}
+
+static void
+at91dci_timeout(void *arg)
+{
+ struct usb2_xfer *xfer = arg;
+
+ DPRINTF("xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ at91dci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+at91dci_start_standard_chain(struct usb2_xfer *xfer)
+{
+ DPRINTFN(9, "\n");
+
+ /* poll one time */
+ if (at91dci_xfer_do_fifo(xfer)) {
+
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+ uint8_t ep_no = xfer->endpoint & UE_ADDR;
+
+ /*
+ * Only enable the endpoint interrupt when we are actually
+ * waiting for data, hence we are dealing with level
+ * triggered interrupts !
+ */
+ AT91_UDP_WRITE_4(sc, AT91_UDP_IER, AT91_UDP_INT_EP(ep_no));
+
+ DPRINTFN(15, "enable interrupts on endpoint %d\n", ep_no);
+
+ /* put transfer on interrupt queue */
+ usb2_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usb2_transfer_timeout_ms(xfer,
+ &at91dci_timeout, xfer->timeout);
+ }
+ }
+}
+
+static void
+at91dci_root_intr_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+
+ DPRINTFN(9, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_PRE_DATA) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ at91dci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* setup buffer */
+ std->ptr = sc->sc_hub_idata;
+ std->len = sizeof(sc->sc_hub_idata);
+
+ /* set port bit */
+ sc->sc_hub_idata[0] = 0x02; /* we only have one port */
+
+done:
+ return;
+}
+
+static usb2_error_t
+at91dci_standard_done_sub(struct usb2_xfer *xfer)
+{
+ struct at91dci_td *td;
+ uint32_t len;
+ uint8_t error;
+
+ DPRINTFN(9, "\n");
+
+ td = xfer->td_transfer_cache;
+
+ do {
+ len = td->remainder;
+
+ if (xfer->aframes != xfer->nframes) {
+ /*
+ * Verify the length and subtract
+ * the remainder from "frlengths[]":
+ */
+ if (len > xfer->frlengths[xfer->aframes]) {
+ td->error = 1;
+ } else {
+ xfer->frlengths[xfer->aframes] -= len;
+ }
+ }
+ /* Check for transfer error */
+ if (td->error) {
+ /* the transfer is finished */
+ error = 1;
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (len > 0) {
+ if (xfer->flags_int.short_frames_ok) {
+ /* follow alt next */
+ if (td->alt_next) {
+ td = td->obj_next;
+ } else {
+ td = NULL;
+ }
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ error = 0;
+ break;
+ }
+ td = td->obj_next;
+
+ /* this USB frame is complete */
+ error = 0;
+ break;
+
+ } while (0);
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ return (error ?
+ USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+at91dci_standard_done(struct usb2_xfer *xfer)
+{
+ usb2_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->pipe);
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+
+ if (xfer->flags_int.control_hdr) {
+
+ err = at91dci_standard_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+
+ err = at91dci_standard_done_sub(xfer);
+ xfer->aframes++;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ err = at91dci_standard_done_sub(xfer);
+ }
+done:
+ at91dci_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * at91dci_device_done
+ *
+ * NOTE: this function can be called more than one time on the
+ * same USB transfer!
+ *------------------------------------------------------------------------*/
+static void
+at91dci_device_done(struct usb2_xfer *xfer, usb2_error_t error)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+ uint8_t ep_no;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ DPRINTFN(2, "xfer=%p, pipe=%p, error=%d\n",
+ xfer, xfer->pipe, error);
+
+ if (xfer->flags_int.usb2_mode == USB_MODE_DEVICE) {
+ ep_no = (xfer->endpoint & UE_ADDR);
+
+ /* disable endpoint interrupt */
+ AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, AT91_UDP_INT_EP(ep_no));
+
+ DPRINTFN(15, "disable interrupts on endpoint %d\n", ep_no);
+ }
+ /* dequeue transfer and start next transfer */
+ usb2_transfer_done(xfer, error);
+}
+
+static void
+at91dci_set_stall(struct usb2_device *udev, struct usb2_xfer *xfer,
+ struct usb2_pipe *pipe)
+{
+ struct at91dci_softc *sc;
+ uint32_t csr_val;
+ uint8_t csr_reg;
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ DPRINTFN(5, "pipe=%p\n", pipe);
+
+ if (xfer) {
+ /* cancel any ongoing transfers */
+ at91dci_device_done(xfer, USB_ERR_STALLED);
+ }
+ /* set FORCESTALL */
+ sc = AT9100_DCI_BUS2SC(udev->bus);
+ csr_reg = (pipe->edesc->bEndpointAddress & UE_ADDR);
+ csr_reg = AT91_UDP_CSR(csr_reg);
+ csr_val = AT91_UDP_READ_4(sc, csr_reg);
+ AT91_CSR_ACK(csr_val, AT91_UDP_CSR_FORCESTALL);
+ AT91_UDP_WRITE_4(sc, csr_reg, csr_val);
+}
+
+static void
+at91dci_clear_stall_sub(struct at91dci_softc *sc, uint8_t ep_no,
+ uint8_t ep_type, uint8_t ep_dir)
+{
+ const struct usb2_hw_ep_profile *pf;
+ uint32_t csr_val;
+ uint32_t temp;
+ uint8_t csr_reg;
+ uint8_t to;
+
+ if (ep_type == UE_CONTROL) {
+ /* clearing stall is not needed */
+ return;
+ }
+ /* compute CSR register offset */
+ csr_reg = AT91_UDP_CSR(ep_no);
+
+ /* compute default CSR value */
+ csr_val = 0;
+ AT91_CSR_ACK(csr_val, 0);
+
+ /* disable endpoint */
+ AT91_UDP_WRITE_4(sc, csr_reg, csr_val);
+
+ /* get endpoint profile */
+ at91dci_get_hw_ep_profile(NULL, &pf, ep_no);
+
+ /* reset FIFO */
+ AT91_UDP_WRITE_4(sc, AT91_UDP_RST, AT91_UDP_RST_EP(ep_no));
+ AT91_UDP_WRITE_4(sc, AT91_UDP_RST, 0);
+
+ /*
+ * NOTE: One would assume that a FIFO reset would release the
+ * FIFO banks aswell, but it doesn't! We have to do this
+ * manually!
+ */
+
+ /* release FIFO banks, if any */
+ for (to = 0; to != 2; to++) {
+
+ /* get csr value */
+ csr_val = AT91_UDP_READ_4(sc, csr_reg);
+
+ if (csr_val & (AT91_UDP_CSR_RX_DATA_BK0 |
+ AT91_UDP_CSR_RX_DATA_BK1)) {
+ /* clear status bits */
+ if (pf->support_multi_buffer) {
+ if (sc->sc_ep_flags[ep_no].fifo_bank) {
+ sc->sc_ep_flags[ep_no].fifo_bank = 0;
+ temp = AT91_UDP_CSR_RX_DATA_BK1;
+ } else {
+ sc->sc_ep_flags[ep_no].fifo_bank = 1;
+ temp = AT91_UDP_CSR_RX_DATA_BK0;
+ }
+ } else {
+ temp = (AT91_UDP_CSR_RX_DATA_BK0 |
+ AT91_UDP_CSR_RX_DATA_BK1);
+ }
+ } else {
+ temp = 0;
+ }
+
+ /* clear FORCESTALL */
+ temp |= AT91_UDP_CSR_STALLSENT;
+
+ AT91_CSR_ACK(csr_val, temp);
+ AT91_UDP_WRITE_4(sc, csr_reg, csr_val);
+ }
+
+ /* compute default CSR value */
+ csr_val = 0;
+ AT91_CSR_ACK(csr_val, 0);
+
+ /* enable endpoint */
+ csr_val &= ~AT91_UDP_CSR_ET_MASK;
+ csr_val |= AT91_UDP_CSR_EPEDS;
+
+ if (ep_type == UE_CONTROL) {
+ csr_val |= AT91_UDP_CSR_ET_CTRL;
+ } else {
+ if (ep_type == UE_BULK) {
+ csr_val |= AT91_UDP_CSR_ET_BULK;
+ } else if (ep_type == UE_INTERRUPT) {
+ csr_val |= AT91_UDP_CSR_ET_INT;
+ } else {
+ csr_val |= AT91_UDP_CSR_ET_ISO;
+ }
+ if (ep_dir & UE_DIR_IN) {
+ csr_val |= AT91_UDP_CSR_ET_DIR_IN;
+ }
+ }
+
+ /* enable endpoint */
+ AT91_UDP_WRITE_4(sc, AT91_UDP_CSR(ep_no), csr_val);
+}
+
+static void
+at91dci_clear_stall(struct usb2_device *udev, struct usb2_pipe *pipe)
+{
+ struct at91dci_softc *sc;
+ struct usb2_endpoint_descriptor *ed;
+
+ DPRINTFN(5, "pipe=%p\n", pipe);
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ /* check mode */
+ if (udev->flags.usb2_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ /* get softc */
+ sc = AT9100_DCI_BUS2SC(udev->bus);
+
+ /* get endpoint descriptor */
+ ed = pipe->edesc;
+
+ /* reset endpoint */
+ at91dci_clear_stall_sub(sc,
+ (ed->bEndpointAddress & UE_ADDR),
+ (ed->bmAttributes & UE_XFERTYPE),
+ (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)));
+}
+
+usb2_error_t
+at91dci_init(struct at91dci_softc *sc)
+{
+ uint32_t csr_val;
+ uint8_t n;
+
+ DPRINTF("start\n");
+
+ /* set up the bus structure */
+ sc->sc_bus.usbrev = USB_REV_1_1;
+ sc->sc_bus.methods = &at91dci_bus_methods;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* turn on clocks */
+
+ if (sc->sc_clocks_on) {
+ (sc->sc_clocks_on) (sc->sc_clocks_arg);
+ }
+ /* wait a little for things to stabilise */
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+
+ /* disable and clear all interrupts */
+
+ AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, 0xFFFFFFFF);
+ AT91_UDP_WRITE_4(sc, AT91_UDP_ICR, 0xFFFFFFFF);
+
+ /* compute default CSR value */
+
+ csr_val = 0;
+ AT91_CSR_ACK(csr_val, 0);
+
+ /* disable all endpoints */
+
+ for (n = 0; n != AT91_UDP_EP_MAX; n++) {
+
+ /* disable endpoint */
+ AT91_UDP_WRITE_4(sc, AT91_UDP_CSR(n), csr_val);
+ }
+
+ /* enable the control endpoint */
+
+ AT91_CSR_ACK(csr_val, AT91_UDP_CSR_ET_CTRL |
+ AT91_UDP_CSR_EPEDS);
+
+ /* write to FIFO control register */
+
+ AT91_UDP_WRITE_4(sc, AT91_UDP_CSR(0), csr_val);
+
+ /* enable the interrupts we want */
+
+ AT91_UDP_WRITE_4(sc, AT91_UDP_IER, AT91_UDP_INT_BUS);
+
+ /* turn off clocks */
+
+ at91dci_clocks_off(sc);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* catch any lost interrupts */
+
+ at91dci_do_poll(&sc->sc_bus);
+
+ return (0); /* success */
+}
+
+void
+at91dci_uninit(struct at91dci_softc *sc)
+{
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* disable and clear all interrupts */
+ AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, 0xFFFFFFFF);
+ AT91_UDP_WRITE_4(sc, AT91_UDP_ICR, 0xFFFFFFFF);
+
+ sc->sc_flags.port_powered = 0;
+ sc->sc_flags.status_vbus = 0;
+ sc->sc_flags.status_bus_reset = 0;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ at91dci_pull_down(sc);
+ at91dci_clocks_off(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+void
+at91dci_suspend(struct at91dci_softc *sc)
+{
+ return;
+}
+
+void
+at91dci_resume(struct at91dci_softc *sc)
+{
+ return;
+}
+
+static void
+at91dci_do_poll(struct usb2_bus *bus)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ at91dci_interrupt_poll(sc);
+ at91dci_root_ctrl_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*------------------------------------------------------------------------*
+ * at91dci bulk support
+ *------------------------------------------------------------------------*/
+static void
+at91dci_device_bulk_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_device_bulk_close(struct usb2_xfer *xfer)
+{
+ at91dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+at91dci_device_bulk_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_device_bulk_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ at91dci_setup_standard_chain(xfer);
+ at91dci_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods at91dci_device_bulk_methods =
+{
+ .open = at91dci_device_bulk_open,
+ .close = at91dci_device_bulk_close,
+ .enter = at91dci_device_bulk_enter,
+ .start = at91dci_device_bulk_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci control support
+ *------------------------------------------------------------------------*/
+static void
+at91dci_device_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_device_ctrl_close(struct usb2_xfer *xfer)
+{
+ at91dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+at91dci_device_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_device_ctrl_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ at91dci_setup_standard_chain(xfer);
+ at91dci_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods at91dci_device_ctrl_methods =
+{
+ .open = at91dci_device_ctrl_open,
+ .close = at91dci_device_ctrl_close,
+ .enter = at91dci_device_ctrl_enter,
+ .start = at91dci_device_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+at91dci_device_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_device_intr_close(struct usb2_xfer *xfer)
+{
+ at91dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+at91dci_device_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_device_intr_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ at91dci_setup_standard_chain(xfer);
+ at91dci_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods at91dci_device_intr_methods =
+{
+ .open = at91dci_device_intr_open,
+ .close = at91dci_device_intr_close,
+ .enter = at91dci_device_intr_enter,
+ .start = at91dci_device_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci full speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+at91dci_device_isoc_fs_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_device_isoc_fs_close(struct usb2_xfer *xfer)
+{
+ at91dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+at91dci_device_isoc_fs_enter(struct usb2_xfer *xfer)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+ uint32_t temp;
+ uint32_t nframes;
+
+ DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
+ xfer, xfer->pipe->isoc_next, xfer->nframes);
+
+ /* get the current frame index */
+
+ nframes = AT91_UDP_READ_4(sc, AT91_UDP_FRM);
+
+ /*
+ * check if the frame index is within the window where the frames
+ * will be inserted
+ */
+ temp = (nframes - xfer->pipe->isoc_next) & AT91_UDP_FRM_MASK;
+
+ if ((xfer->pipe->is_synced == 0) ||
+ (temp < xfer->nframes)) {
+ /*
+ * If there is data underflow or the pipe queue is
+ * empty we schedule the transfer a few frames ahead
+ * of the current frame position. Else two isochronous
+ * transfers might overlap.
+ */
+ xfer->pipe->isoc_next = (nframes + 3) & AT91_UDP_FRM_MASK;
+ xfer->pipe->is_synced = 1;
+ DPRINTFN(3, "start next=%d\n", xfer->pipe->isoc_next);
+ }
+ /*
+ * compute how many milliseconds the insertion is ahead of the
+ * current frame position:
+ */
+ temp = (xfer->pipe->isoc_next - nframes) & AT91_UDP_FRM_MASK;
+
+ /*
+ * pre-compute when the isochronous transfer will be finished:
+ */
+ xfer->isoc_time_complete =
+ usb2_isoc_time_expand(&sc->sc_bus, nframes) + temp +
+ xfer->nframes;
+
+ /* compute frame number for next insertion */
+ xfer->pipe->isoc_next += xfer->nframes;
+
+ /* setup TDs */
+ at91dci_setup_standard_chain(xfer);
+}
+
+static void
+at91dci_device_isoc_fs_start(struct usb2_xfer *xfer)
+{
+ /* start TD chain */
+ at91dci_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods at91dci_device_isoc_fs_methods =
+{
+ .open = at91dci_device_isoc_fs_open,
+ .close = at91dci_device_isoc_fs_close,
+ .enter = at91dci_device_isoc_fs_enter,
+ .start = at91dci_device_isoc_fs_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci root control support
+ *------------------------------------------------------------------------*
+ * simulate a hardware HUB by handling
+ * all the necessary requests
+ *------------------------------------------------------------------------*/
+
+static void
+at91dci_root_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_root_ctrl_close(struct usb2_xfer *xfer)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_ctrl.xfer == xfer) {
+ sc->sc_root_ctrl.xfer = NULL;
+ }
+ at91dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+/*
+ * USB descriptors for the virtual Root HUB:
+ */
+
+static const struct usb2_device_descriptor at91dci_devd = {
+ .bLength = sizeof(struct usb2_device_descriptor),
+ .bDescriptorType = UDESC_DEVICE,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_HSHUBSTT,
+ .bMaxPacketSize = 64,
+ .bcdDevice = {0x00, 0x01},
+ .iManufacturer = 1,
+ .iProduct = 2,
+ .bNumConfigurations = 1,
+};
+
+static const struct usb2_device_qualifier at91dci_odevd = {
+ .bLength = sizeof(struct usb2_device_qualifier),
+ .bDescriptorType = UDESC_DEVICE_QUALIFIER,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_FSHUB,
+ .bMaxPacketSize0 = 0,
+ .bNumConfigurations = 0,
+};
+
+static const struct at91dci_config_desc at91dci_confd = {
+ .confd = {
+ .bLength = sizeof(struct usb2_config_descriptor),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(at91dci_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0,
+ },
+ .ifcd = {
+ .bLength = sizeof(struct usb2_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = UIPROTO_HSHUBSTT,
+ },
+
+ .endpd = {
+ .bLength = sizeof(struct usb2_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = (UE_DIR_IN | AT9100_DCI_INTR_ENDPT),
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 8,
+ .bInterval = 255,
+ },
+};
+
+static const struct usb2_hub_descriptor_min at91dci_hubd = {
+ .bDescLength = sizeof(at91dci_hubd),
+ .bDescriptorType = UDESC_HUB,
+ .bNbrPorts = 1,
+ .wHubCharacteristics[0] =
+ (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) & 0xFF,
+ .wHubCharacteristics[1] =
+ (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) >> 8,
+ .bPwrOn2PwrGood = 50,
+ .bHubContrCurrent = 0,
+ .DeviceRemovable = {0}, /* port is removable */
+};
+
+#define STRING_LANG \
+ 0x09, 0x04, /* American English */
+
+#define STRING_VENDOR \
+ 'A', 0, 'T', 0, 'M', 0, 'E', 0, 'L', 0
+
+#define STRING_PRODUCT \
+ 'D', 0, 'C', 0, 'I', 0, ' ', 0, 'R', 0, \
+ 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, \
+ 'U', 0, 'B', 0,
+
+USB_MAKE_STRING_DESC(STRING_LANG, at91dci_langtab);
+USB_MAKE_STRING_DESC(STRING_VENDOR, at91dci_vendor);
+USB_MAKE_STRING_DESC(STRING_PRODUCT, at91dci_product);
+
+static void
+at91dci_root_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_root_ctrl_start(struct usb2_xfer *xfer)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_ctrl.xfer = xfer;
+
+ usb2_bus_roothub_exec(xfer->xroot->bus);
+}
+
+static void
+at91dci_root_ctrl_task(struct usb2_bus *bus)
+{
+ at91dci_root_ctrl_poll(AT9100_DCI_BUS2SC(bus));
+}
+
+static void
+at91dci_root_ctrl_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+ uint16_t value;
+ uint16_t index;
+ uint8_t use_polling;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_SETUP) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ at91dci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* buffer reset */
+ std->ptr = USB_ADD_BYTES(&sc->sc_hub_temp, 0);
+ std->len = 0;
+
+ value = UGETW(std->req.wValue);
+ index = UGETW(std->req.wIndex);
+
+ use_polling = mtx_owned(xfer->xroot->xfer_mtx) ? 1 : 0;
+
+ /* demultiplex the control request */
+
+ switch (std->req.bmRequestType) {
+ case UT_READ_DEVICE:
+ switch (std->req.bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_descriptor;
+ case UR_GET_CONFIG:
+ goto tr_handle_get_config;
+ case UR_GET_STATUS:
+ goto tr_handle_get_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_DEVICE:
+ switch (std->req.bRequest) {
+ case UR_SET_ADDRESS:
+ goto tr_handle_set_address;
+ case UR_SET_CONFIG:
+ goto tr_handle_set_config;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_DESCRIPTOR:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_ENDPOINT:
+ switch (std->req.bRequest) {
+ case UR_CLEAR_FEATURE:
+ switch (UGETW(std->req.wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_clear_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_clear_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SET_FEATURE:
+ switch (UGETW(std->req.wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_set_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_set_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SYNCH_FRAME:
+ goto tr_valid; /* nop */
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_ENDPOINT:
+ switch (std->req.bRequest) {
+ case UR_GET_STATUS:
+ goto tr_handle_get_ep_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_INTERFACE:
+ switch (std->req.bRequest) {
+ case UR_SET_INTERFACE:
+ goto tr_handle_set_interface;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_INTERFACE:
+ switch (std->req.bRequest) {
+ case UR_GET_INTERFACE:
+ goto tr_handle_get_interface;
+ case UR_GET_STATUS:
+ goto tr_handle_get_iface_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_INTERFACE:
+ case UT_WRITE_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_READ_CLASS_INTERFACE:
+ case UT_READ_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_WRITE_CLASS_DEVICE:
+ switch (std->req.bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_valid;
+ case UR_SET_DESCRIPTOR:
+ case UR_SET_FEATURE:
+ break;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_OTHER:
+ switch (std->req.bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_handle_clear_port_feature;
+ case UR_SET_FEATURE:
+ goto tr_handle_set_port_feature;
+ case UR_CLEAR_TT_BUFFER:
+ case UR_RESET_TT:
+ case UR_STOP_TT:
+ goto tr_valid;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_OTHER:
+ switch (std->req.bRequest) {
+ case UR_GET_TT_STATE:
+ goto tr_handle_get_tt_state;
+ case UR_GET_STATUS:
+ goto tr_handle_get_port_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_DEVICE:
+ switch (std->req.bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_class_descriptor;
+ case UR_GET_STATUS:
+ goto tr_handle_get_class_status;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_valid;
+
+tr_handle_get_descriptor:
+ switch (value >> 8) {
+ case UDESC_DEVICE:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ std->len = sizeof(at91dci_devd);
+ std->ptr = USB_ADD_BYTES(&at91dci_devd, 0);
+ goto tr_valid;
+ case UDESC_CONFIG:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ std->len = sizeof(at91dci_confd);
+ std->ptr = USB_ADD_BYTES(&at91dci_confd, 0);
+ goto tr_valid;
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ std->len = sizeof(at91dci_langtab);
+ std->ptr = USB_ADD_BYTES(&at91dci_langtab, 0);
+ goto tr_valid;
+
+ case 1: /* Vendor */
+ std->len = sizeof(at91dci_vendor);
+ std->ptr = USB_ADD_BYTES(&at91dci_vendor, 0);
+ goto tr_valid;
+
+ case 2: /* Product */
+ std->len = sizeof(at91dci_product);
+ std->ptr = USB_ADD_BYTES(&at91dci_product, 0);
+ goto tr_valid;
+ default:
+ break;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_stalled;
+
+tr_handle_get_config:
+ std->len = 1;
+ sc->sc_hub_temp.wValue[0] = sc->sc_conf;
+ goto tr_valid;
+
+tr_handle_get_status:
+ std->len = 2;
+ USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED);
+ goto tr_valid;
+
+tr_handle_set_address:
+ if (value & 0xFF00) {
+ goto tr_stalled;
+ }
+ sc->sc_rt_addr = value;
+ goto tr_valid;
+
+tr_handle_set_config:
+ if (value >= 2) {
+ goto tr_stalled;
+ }
+ sc->sc_conf = value;
+ goto tr_valid;
+
+tr_handle_get_interface:
+ std->len = 1;
+ sc->sc_hub_temp.wValue[0] = 0;
+ goto tr_valid;
+
+tr_handle_get_tt_state:
+tr_handle_get_class_status:
+tr_handle_get_iface_status:
+tr_handle_get_ep_status:
+ std->len = 2;
+ USETW(sc->sc_hub_temp.wValue, 0);
+ goto tr_valid;
+
+tr_handle_set_halt:
+tr_handle_set_interface:
+tr_handle_set_wakeup:
+tr_handle_clear_wakeup:
+tr_handle_clear_halt:
+ goto tr_valid;
+
+tr_handle_clear_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index);
+
+ switch (value) {
+ case UHF_PORT_SUSPEND:
+ at91dci_wakeup_peer(xfer);
+ break;
+
+ case UHF_PORT_ENABLE:
+ sc->sc_flags.port_enabled = 0;
+ break;
+
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ case UHF_C_PORT_ENABLE:
+ case UHF_C_PORT_OVER_CURRENT:
+ case UHF_C_PORT_RESET:
+ /* nops */
+ break;
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 0;
+ at91dci_pull_down(sc);
+ at91dci_clocks_off(sc);
+ break;
+ case UHF_C_PORT_CONNECTION:
+ sc->sc_flags.change_connect = 0;
+ break;
+ case UHF_C_PORT_SUSPEND:
+ sc->sc_flags.change_suspend = 0;
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_set_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(9, "UR_SET_PORT_FEATURE\n");
+
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ sc->sc_flags.port_enabled = 1;
+ break;
+ case UHF_PORT_SUSPEND:
+ case UHF_PORT_RESET:
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ /* nops */
+ break;
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 1;
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_get_port_status:
+
+ DPRINTFN(9, "UR_GET_PORT_STATUS\n");
+
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ if (sc->sc_flags.status_vbus) {
+ at91dci_clocks_on(sc);
+ at91dci_pull_up(sc);
+ } else {
+ at91dci_pull_down(sc);
+ at91dci_clocks_off(sc);
+ }
+
+ /* Select FULL-speed and Device Side Mode */
+
+ value = UPS_PORT_MODE_DEVICE;
+
+ if (sc->sc_flags.port_powered) {
+ value |= UPS_PORT_POWER;
+ }
+ if (sc->sc_flags.port_enabled) {
+ value |= UPS_PORT_ENABLED;
+ }
+ if (sc->sc_flags.status_vbus &&
+ sc->sc_flags.status_bus_reset) {
+ value |= UPS_CURRENT_CONNECT_STATUS;
+ }
+ if (sc->sc_flags.status_suspend) {
+ value |= UPS_SUSPEND;
+ }
+ USETW(sc->sc_hub_temp.ps.wPortStatus, value);
+
+ value = 0;
+
+ if (sc->sc_flags.change_connect) {
+ value |= UPS_C_CONNECT_STATUS;
+
+ if (sc->sc_flags.status_vbus &&
+ sc->sc_flags.status_bus_reset) {
+ /* reset endpoint flags */
+ bzero(sc->sc_ep_flags, sizeof(sc->sc_ep_flags));
+ }
+ }
+ if (sc->sc_flags.change_suspend) {
+ value |= UPS_C_SUSPEND;
+ }
+ USETW(sc->sc_hub_temp.ps.wPortChange, value);
+ std->len = sizeof(sc->sc_hub_temp.ps);
+ goto tr_valid;
+
+tr_handle_get_class_descriptor:
+ if (value & 0xFF) {
+ goto tr_stalled;
+ }
+ std->ptr = USB_ADD_BYTES(&at91dci_hubd, 0);
+ std->len = sizeof(at91dci_hubd);
+ goto tr_valid;
+
+tr_stalled:
+ std->err = USB_ERR_STALLED;
+tr_valid:
+done:
+ return;
+}
+
+static void
+at91dci_root_ctrl_poll(struct at91dci_softc *sc)
+{
+ usb2_sw_transfer(&sc->sc_root_ctrl,
+ &at91dci_root_ctrl_done);
+}
+
+struct usb2_pipe_methods at91dci_root_ctrl_methods =
+{
+ .open = at91dci_root_ctrl_open,
+ .close = at91dci_root_ctrl_close,
+ .enter = at91dci_root_ctrl_enter,
+ .start = at91dci_root_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 0,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci root interrupt support
+ *------------------------------------------------------------------------*/
+static void
+at91dci_root_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_root_intr_close(struct usb2_xfer *xfer)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_intr.xfer == xfer) {
+ sc->sc_root_intr.xfer = NULL;
+ }
+ at91dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+at91dci_root_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_root_intr_start(struct usb2_xfer *xfer)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_intr.xfer = xfer;
+}
+
+struct usb2_pipe_methods at91dci_root_intr_methods =
+{
+ .open = at91dci_root_intr_open,
+ .close = at91dci_root_intr_close,
+ .enter = at91dci_root_intr_enter,
+ .start = at91dci_root_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+static void
+at91dci_xfer_setup(struct usb2_setup_params *parm)
+{
+ const struct usb2_hw_ep_profile *pf;
+ struct at91dci_softc *sc;
+ struct usb2_xfer *xfer;
+ void *last_obj;
+ uint32_t ntd;
+ uint32_t n;
+ uint8_t ep_no;
+
+ sc = AT9100_DCI_BUS2SC(parm->udev->bus);
+ xfer = parm->curr_xfer;
+
+ /*
+ * NOTE: This driver does not use any of the parameters that
+ * are computed from the following values. Just set some
+ * reasonable dummies:
+ */
+ parm->hc_max_packet_size = 0x500;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = 0x500;
+
+ usb2_transfer_setup_sub(parm);
+
+ /*
+ * compute maximum number of TDs
+ */
+ if (parm->methods == &at91dci_device_ctrl_methods) {
+
+ ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC 1 */
+ + 1 /* SYNC 2 */ ;
+
+ } else if (parm->methods == &at91dci_device_bulk_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &at91dci_device_intr_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &at91dci_device_isoc_fs_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else {
+
+ ntd = 0;
+ }
+
+ /*
+ * check if "usb2_transfer_setup_sub" set an error
+ */
+ if (parm->err) {
+ return;
+ }
+ /*
+ * allocate transfer descriptors
+ */
+ last_obj = NULL;
+
+ /*
+ * get profile stuff
+ */
+ if (ntd) {
+
+ ep_no = xfer->endpoint & UE_ADDR;
+ at91dci_get_hw_ep_profile(parm->udev, &pf, ep_no);
+
+ if (pf == NULL) {
+ /* should not happen */
+ parm->err = USB_ERR_INVAL;
+ return;
+ }
+ } else {
+ ep_no = 0;
+ pf = NULL;
+ }
+
+ /* align data */
+ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+ for (n = 0; n != ntd; n++) {
+
+ struct at91dci_td *td;
+
+ if (parm->buf) {
+
+ td = USB_ADD_BYTES(parm->buf, parm->size[0]);
+
+ /* init TD */
+ td->io_tag = sc->sc_io_tag;
+ td->io_hdl = sc->sc_io_hdl;
+ td->max_packet_size = xfer->max_packet_size;
+ td->status_reg = AT91_UDP_CSR(ep_no);
+ td->fifo_reg = AT91_UDP_FDR(ep_no);
+ if (pf->support_multi_buffer) {
+ td->support_multi_buffer = 1;
+ }
+ td->obj_next = last_obj;
+
+ last_obj = td;
+ }
+ parm->size[0] += sizeof(*td);
+ }
+
+ xfer->td_start[0] = last_obj;
+}
+
+static void
+at91dci_xfer_unsetup(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+at91dci_pipe_init(struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc,
+ struct usb2_pipe *pipe)
+{
+ struct at91dci_softc *sc = AT9100_DCI_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
+ pipe, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb2_mode,
+ sc->sc_rt_addr);
+
+ if (udev->device_index == sc->sc_rt_addr) {
+
+ if (udev->flags.usb2_mode != USB_MODE_HOST) {
+ /* not supported */
+ return;
+ }
+ switch (edesc->bEndpointAddress) {
+ case USB_CONTROL_ENDPOINT:
+ pipe->methods = &at91dci_root_ctrl_methods;
+ break;
+ case UE_DIR_IN | AT9100_DCI_INTR_ENDPT:
+ pipe->methods = &at91dci_root_intr_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ } else {
+
+ if (udev->flags.usb2_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ if (udev->speed != USB_SPEED_FULL) {
+ /* not supported */
+ return;
+ }
+ switch (edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_CONTROL:
+ pipe->methods = &at91dci_device_ctrl_methods;
+ break;
+ case UE_INTERRUPT:
+ pipe->methods = &at91dci_device_intr_methods;
+ break;
+ case UE_ISOCHRONOUS:
+ pipe->methods = &at91dci_device_isoc_fs_methods;
+ break;
+ case UE_BULK:
+ pipe->methods = &at91dci_device_bulk_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+}
+
+struct usb2_bus_methods at91dci_bus_methods =
+{
+ .pipe_init = &at91dci_pipe_init,
+ .xfer_setup = &at91dci_xfer_setup,
+ .xfer_unsetup = &at91dci_xfer_unsetup,
+ .do_poll = &at91dci_do_poll,
+ .get_hw_ep_profile = &at91dci_get_hw_ep_profile,
+ .set_stall = &at91dci_set_stall,
+ .clear_stall = &at91dci_clear_stall,
+ .roothub_exec = &at91dci_root_ctrl_task,
+};
diff --git a/sys/dev/usb/controller/at91dci.h b/sys/dev/usb/controller/at91dci.h
new file mode 100644
index 0000000..d386307
--- /dev/null
+++ b/sys/dev/usb/controller/at91dci.h
@@ -0,0 +1,245 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 2006 ATMEL
+ * Copyright (c) 2007 Hans Petter Selasky <hselasky@freebsd.org>
+ * 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.
+ */
+
+/*
+ * USB Device Port (UDP) register definition, based on
+ * "AT91RM9200.h" provided by ATMEL.
+ */
+
+#ifndef _AT9100_DCI_H_
+#define _AT9100_DCI_H_
+
+#define AT91_MAX_DEVICES (USB_MIN_DEVICES + 1)
+
+#define AT91_UDP_FRM 0x00 /* Frame number register */
+#define AT91_UDP_FRM_MASK (0x7FF << 0) /* Frame Number as Defined in
+ * the Packet Field Formats */
+#define AT91_UDP_FRM_ERR (0x1 << 16) /* Frame Error */
+#define AT91_UDP_FRM_OK (0x1 << 17) /* Frame OK */
+
+#define AT91_UDP_GSTATE 0x04 /* Global state register */
+#define AT91_UDP_GSTATE_ADDR (0x1 << 0) /* Addressed state */
+#define AT91_UDP_GSTATE_CONFG (0x1 << 1) /* Configured */
+#define AT91_UDP_GSTATE_ESR (0x1 << 2) /* Enable Send Resume */
+#define AT91_UDP_GSTATE_RSM (0x1 << 3) /* A Resume Has Been Sent to
+ * the Host */
+#define AT91_UDP_GSTATE_RMW (0x1 << 4) /* Remote Wake Up Enable */
+
+#define AT91_UDP_FADDR 0x08 /* Function Address Register */
+#define AT91_UDP_FADDR_MASK (0x7F << 0)/* Function Address Mask */
+#define AT91_UDP_FADDR_EN (0x1 << 8)/* Function Enable */
+
+#define AT91_UDP_RES0 0x0C /* Reserved 0 */
+
+#define AT91_UDP_IER 0x10 /* Interrupt Enable Register */
+#define AT91_UDP_IDR 0x14 /* Interrupt Disable Register */
+#define AT91_UDP_IMR 0x18 /* Interrupt Mask Register */
+#define AT91_UDP_ISR 0x1C /* Interrupt Status Register */
+#define AT91_UDP_ICR 0x20 /* Interrupt Clear Register */
+#define AT91_UDP_INT_EP(n) (0x1 <<(n))/* Endpoint "n" Interrupt */
+#define AT91_UDP_INT_RXSUSP (0x1 << 8)/* USB Suspend Interrupt */
+#define AT91_UDP_INT_RXRSM (0x1 << 9)/* USB Resume Interrupt */
+#define AT91_UDP_INT_EXTRSM (0x1 << 10)/* USB External Resume Interrupt */
+#define AT91_UDP_INT_SOFINT (0x1 << 11)/* USB Start Of frame Interrupt */
+#define AT91_UDP_INT_END_BR (0x1 << 12)/* USB End Of Bus Reset Interrupt */
+#define AT91_UDP_INT_WAKEUP (0x1 << 13)/* USB Resume Interrupt */
+
+#define AT91_UDP_INT_BUS \
+ (AT91_UDP_INT_RXSUSP|AT91_UDP_INT_RXRSM| \
+ AT91_UDP_INT_END_BR)
+
+#define AT91_UDP_INT_EPS \
+ (AT91_UDP_INT_EP(0)|AT91_UDP_INT_EP(1)| \
+ AT91_UDP_INT_EP(2)|AT91_UDP_INT_EP(3)| \
+ AT91_UDP_INT_EP(4)|AT91_UDP_INT_EP(5))
+
+#define AT91_UDP_INT_DEFAULT \
+ (AT91_UDP_INT_EPS|AT91_UDP_INT_BUS)
+
+#define AT91_UDP_RES1 0x24 /* Reserved 1 */
+#define AT91_UDP_RST 0x28 /* Reset Endpoint Register */
+#define AT91_UDP_RST_EP(n) (0x1 << (n))/* Reset Endpoint "n" */
+
+#define AT91_UDP_RES2 0x2C /* Reserved 2 */
+
+#define AT91_UDP_CSR(n) (0x30 + (4*(n)))/* Endpoint Control and Status
+ * Register */
+#define AT91_UDP_CSR_TXCOMP (0x1 << 0) /* Generates an IN packet with data
+ * previously written in the DPR */
+#define AT91_UDP_CSR_RX_DATA_BK0 (0x1 << 1) /* Receive Data Bank 0 */
+#define AT91_UDP_CSR_RXSETUP (0x1 << 2) /* Sends STALL to the Host
+ * (Control endpoints) */
+#define AT91_UDP_CSR_ISOERROR (0x1 << 3) /* Isochronous error
+ * (Isochronous endpoints) */
+#define AT91_UDP_CSR_STALLSENT (0x1 << 3) /* Stall sent (Control, bulk,
+ * interrupt endpoints) */
+#define AT91_UDP_CSR_TXPKTRDY (0x1 << 4) /* Transmit Packet Ready */
+#define AT91_UDP_CSR_FORCESTALL (0x1 << 5) /* Force Stall (used by
+ * Control, Bulk and
+ * Isochronous endpoints). */
+#define AT91_UDP_CSR_RX_DATA_BK1 (0x1 << 6) /* Receive Data Bank 1 (only
+ * used by endpoints with
+ * ping-pong attributes). */
+#define AT91_UDP_CSR_DIR (0x1 << 7) /* Transfer Direction */
+#define AT91_UDP_CSR_ET_MASK (0x7 << 8) /* Endpoint transfer type mask */
+#define AT91_UDP_CSR_ET_CTRL (0x0 << 8) /* Control IN+OUT */
+#define AT91_UDP_CSR_ET_ISO (0x1 << 8) /* Isochronous */
+#define AT91_UDP_CSR_ET_BULK (0x2 << 8) /* Bulk */
+#define AT91_UDP_CSR_ET_INT (0x3 << 8) /* Interrupt */
+#define AT91_UDP_CSR_ET_DIR_OUT (0x0 << 8) /* OUT tokens */
+#define AT91_UDP_CSR_ET_DIR_IN (0x4 << 8) /* IN tokens */
+#define AT91_UDP_CSR_DTGLE (0x1 << 11) /* Data Toggle */
+#define AT91_UDP_CSR_EPEDS (0x1 << 15) /* Endpoint Enable Disable */
+#define AT91_UDP_CSR_RXBYTECNT (0x7FF << 16) /* Number Of Bytes Available
+ * in the FIFO */
+
+#define AT91_UDP_FDR(n) (0x50 + (4*(n)))/* Endpoint FIFO Data Register */
+#define AT91_UDP_RES3 0x70 /* Reserved 3 */
+#define AT91_UDP_TXVC 0x74 /* Transceiver Control Register */
+#define AT91_UDP_TXVC_DIS (0x1 << 8)
+
+#define AT91_UDP_EP_MAX 6 /* maximum number of endpoints
+ * supported */
+
+#define AT91_UDP_READ_4(sc, reg) \
+ bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg)
+
+#define AT91_UDP_WRITE_4(sc, reg, data) \
+ bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data)
+
+struct at91dci_td;
+
+typedef uint8_t (at91dci_cmd_t)(struct at91dci_td *td);
+
+struct at91dci_td {
+ bus_space_tag_t io_tag;
+ bus_space_handle_t io_hdl;
+ struct at91dci_td *obj_next;
+ at91dci_cmd_t *func;
+ struct usb2_page_cache *pc;
+ uint32_t offset;
+ uint32_t remainder;
+ uint16_t max_packet_size;
+ uint8_t status_reg;
+ uint8_t fifo_reg;
+ uint8_t fifo_bank:1;
+ uint8_t error:1;
+ uint8_t alt_next:1;
+ uint8_t short_pkt:1;
+ uint8_t support_multi_buffer:1;
+ uint8_t did_stall:1;
+};
+
+struct at91dci_std_temp {
+ at91dci_cmd_t *func;
+ struct usb2_page_cache *pc;
+ struct at91dci_td *td;
+ struct at91dci_td *td_next;
+ uint32_t len;
+ uint32_t offset;
+ uint16_t max_frame_size;
+ uint8_t short_pkt;
+ /*
+ * short_pkt = 0: transfer should be short terminated
+ * short_pkt = 1: transfer should not be short terminated
+ */
+ uint8_t setup_alt_next;
+};
+
+struct at91dci_config_desc {
+ struct usb2_config_descriptor confd;
+ struct usb2_interface_descriptor ifcd;
+ struct usb2_endpoint_descriptor endpd;
+} __packed;
+
+union at91dci_hub_temp {
+ uWord wValue;
+ struct usb2_port_status ps;
+};
+
+struct at91dci_ep_flags {
+ uint8_t fifo_bank:1; /* hardware specific */
+};
+
+struct at91dci_flags {
+ uint8_t change_connect:1;
+ uint8_t change_suspend:1;
+ uint8_t status_suspend:1; /* set if suspended */
+ uint8_t status_vbus:1; /* set if present */
+ uint8_t status_bus_reset:1; /* set if reset complete */
+ uint8_t remote_wakeup:1;
+ uint8_t self_powered:1;
+ uint8_t clocks_off:1;
+ uint8_t port_powered:1;
+ uint8_t port_enabled:1;
+ uint8_t d_pulled_up:1;
+};
+
+struct at91dci_softc {
+ struct usb2_bus sc_bus;
+ union at91dci_hub_temp sc_hub_temp;
+ LIST_HEAD(, usb2_xfer) sc_interrupt_list_head;
+ struct usb2_sw_transfer sc_root_ctrl;
+ struct usb2_sw_transfer sc_root_intr;
+
+ struct usb2_device *sc_devices[AT91_MAX_DEVICES];
+ struct resource *sc_io_res;
+ struct resource *sc_irq_res;
+ void *sc_intr_hdl;
+ bus_size_t sc_io_size;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+
+ void (*sc_clocks_on) (void *arg);
+ void (*sc_clocks_off) (void *arg);
+ void *sc_clocks_arg;
+
+ void (*sc_pull_up) (void *arg);
+ void (*sc_pull_down) (void *arg);
+ void *sc_pull_arg;
+
+ uint8_t sc_rt_addr; /* root HUB address */
+ uint8_t sc_dv_addr; /* device address */
+ uint8_t sc_conf; /* root HUB config */
+
+ uint8_t sc_hub_idata[1];
+
+ struct at91dci_flags sc_flags;
+ struct at91dci_ep_flags sc_ep_flags[AT91_UDP_EP_MAX];
+};
+
+/* prototypes */
+
+usb2_error_t at91dci_init(struct at91dci_softc *sc);
+void at91dci_uninit(struct at91dci_softc *sc);
+void at91dci_suspend(struct at91dci_softc *sc);
+void at91dci_resume(struct at91dci_softc *sc);
+void at91dci_interrupt(struct at91dci_softc *sc);
+void at91dci_vbus_interrupt(struct at91dci_softc *sc, uint8_t is_on);
+
+#endif /* _AT9100_DCI_H_ */
diff --git a/sys/dev/usb/controller/at91dci_atmelarm.c b/sys/dev/usb/controller/at91dci_atmelarm.c
new file mode 100644
index 0000000..71d2937
--- /dev/null
+++ b/sys/dev/usb/controller/at91dci_atmelarm.c
@@ -0,0 +1,347 @@
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*-
+ * Copyright (c) 2007-2008 Hans Petter Selasky. 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 <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_defs.h>
+#include <dev/usb/usb.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/at91dci.h>
+
+#include <sys/rman.h>
+
+#include <arm/at91/at91_pmcvar.h>
+#include <arm/at91/at91rm92reg.h>
+#include <arm/at91/at91_pio_rm9200.h>
+#include <arm/at91/at91_piovar.h>
+
+#define MEM_RID 0
+
+/* Pin Definitions - do they belong here or somewhere else ? */
+
+#define VBUS_MASK AT91C_PIO_PB24
+#define VBUS_BASE AT91RM92_PIOB_BASE
+
+#define PULLUP_MASK AT91C_PIO_PB22
+#define PULLUP_BASE AT91RM92_PIOB_BASE
+
+static device_probe_t at91_udp_probe;
+static device_attach_t at91_udp_attach;
+static device_detach_t at91_udp_detach;
+static device_shutdown_t at91_udp_shutdown;
+
+struct at91_udp_softc {
+ struct at91dci_softc sc_dci; /* must be first */
+ struct at91_pmc_clock *sc_iclk;
+ struct at91_pmc_clock *sc_fclk;
+ struct resource *sc_vbus_irq_res;
+ void *sc_vbus_intr_hdl;
+};
+
+static void
+at91_vbus_poll(struct at91_udp_softc *sc)
+{
+ uint32_t temp;
+ uint8_t vbus_val;
+
+ /* XXX temporary clear interrupts here */
+
+ temp = at91_pio_gpio_clear_interrupt(VBUS_BASE);
+
+ /* just forward it */
+
+ vbus_val = at91_pio_gpio_get(VBUS_BASE, VBUS_MASK);
+ at91dci_vbus_interrupt(&sc->sc_dci, vbus_val);
+}
+
+static void
+at91_udp_clocks_on(void *arg)
+{
+ struct at91_udp_softc *sc = arg;
+
+ at91_pmc_clock_enable(sc->sc_iclk);
+ at91_pmc_clock_enable(sc->sc_fclk);
+}
+
+static void
+at91_udp_clocks_off(void *arg)
+{
+ struct at91_udp_softc *sc = arg;
+
+ at91_pmc_clock_disable(sc->sc_fclk);
+ at91_pmc_clock_disable(sc->sc_iclk);
+}
+
+static void
+at91_udp_pull_up(void *arg)
+{
+ at91_pio_gpio_set(PULLUP_BASE, PULLUP_MASK);
+}
+
+static void
+at91_udp_pull_down(void *arg)
+{
+ at91_pio_gpio_clear(PULLUP_BASE, PULLUP_MASK);
+}
+
+static int
+at91_udp_probe(device_t dev)
+{
+ device_set_desc(dev, "AT91 integrated AT91_UDP controller");
+ return (0);
+}
+
+static int
+at91_udp_attach(device_t dev)
+{
+ struct at91_udp_softc *sc = device_get_softc(dev);
+ int err;
+ int rid;
+
+ /* setup AT9100 USB device controller interface softc */
+
+ sc->sc_dci.sc_clocks_on = &at91_udp_clocks_on;
+ sc->sc_dci.sc_clocks_off = &at91_udp_clocks_off;
+ sc->sc_dci.sc_clocks_arg = sc;
+ sc->sc_dci.sc_pull_up = &at91_udp_pull_up;
+ sc->sc_dci.sc_pull_down = &at91_udp_pull_down;
+ sc->sc_dci.sc_pull_arg = sc;
+
+ /* initialise some bus fields */
+ sc->sc_dci.sc_bus.parent = dev;
+ sc->sc_dci.sc_bus.devices = sc->sc_dci.sc_devices;
+ sc->sc_dci.sc_bus.devices_max = AT91_MAX_DEVICES;
+
+ /* get all DMA memory */
+ if (usb2_bus_mem_alloc_all(&sc->sc_dci.sc_bus,
+ USB_GET_DMA_TAG(dev), NULL)) {
+ return (ENOMEM);
+ }
+ /*
+ * configure VBUS input pin, enable deglitch and enable
+ * interrupt :
+ */
+ at91_pio_use_gpio(VBUS_BASE, VBUS_MASK);
+ at91_pio_gpio_input(VBUS_BASE, VBUS_MASK);
+ at91_pio_gpio_set_deglitch(VBUS_BASE, VBUS_MASK, 1);
+ at91_pio_gpio_set_interrupt(VBUS_BASE, VBUS_MASK, 1);
+
+ /*
+ * configure PULLUP output pin :
+ */
+ at91_pio_use_gpio(PULLUP_BASE, PULLUP_MASK);
+ at91_pio_gpio_output(PULLUP_BASE, PULLUP_MASK, 0);
+
+ at91_udp_pull_down(sc);
+
+ /* wait 10ms for pulldown to stabilise */
+ usb2_pause_mtx(NULL, hz / 100);
+
+ sc->sc_iclk = at91_pmc_clock_ref("udc_clk");
+ sc->sc_fclk = at91_pmc_clock_ref("udpck");
+
+ rid = MEM_RID;
+ sc->sc_dci.sc_io_res =
+ bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
+
+ if (!(sc->sc_dci.sc_io_res)) {
+ err = ENOMEM;
+ goto error;
+ }
+ sc->sc_dci.sc_io_tag = rman_get_bustag(sc->sc_dci.sc_io_res);
+ sc->sc_dci.sc_io_hdl = rman_get_bushandle(sc->sc_dci.sc_io_res);
+ sc->sc_dci.sc_io_size = rman_get_size(sc->sc_dci.sc_io_res);
+
+ rid = 0;
+ sc->sc_dci.sc_irq_res =
+ bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE);
+ if (!(sc->sc_dci.sc_irq_res)) {
+ goto error;
+ }
+ rid = 1;
+ sc->sc_vbus_irq_res =
+ bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE);
+ if (!(sc->sc_vbus_irq_res)) {
+ goto error;
+ }
+ sc->sc_dci.sc_bus.bdev = device_add_child(dev, "usbus", -1);
+ if (!(sc->sc_dci.sc_bus.bdev)) {
+ goto error;
+ }
+ device_set_ivars(sc->sc_dci.sc_bus.bdev, &sc->sc_dci.sc_bus);
+
+#if (__FreeBSD_version >= 700031)
+ err = bus_setup_intr(dev, sc->sc_dci.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (void *)at91dci_interrupt, sc, &sc->sc_dci.sc_intr_hdl);
+#else
+ err = bus_setup_intr(dev, sc->sc_dci.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ (void *)at91dci_interrupt, sc, &sc->sc_dci.sc_intr_hdl);
+#endif
+ if (err) {
+ sc->sc_dci.sc_intr_hdl = NULL;
+ goto error;
+ }
+#if (__FreeBSD_version >= 700031)
+ err = bus_setup_intr(dev, sc->sc_vbus_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (void *)at91_vbus_poll, sc, &sc->sc_vbus_intr_hdl);
+#else
+ err = bus_setup_intr(dev, sc->sc_vbus_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ (void *)at91_vbus_poll, sc, &sc->sc_vbus_intr_hdl);
+#endif
+ if (err) {
+ sc->sc_vbus_intr_hdl = NULL;
+ goto error;
+ }
+ err = at91dci_init(&sc->sc_dci);
+ if (!err) {
+ err = device_probe_and_attach(sc->sc_dci.sc_bus.bdev);
+ }
+ if (err) {
+ goto error;
+ } else {
+ /* poll VBUS one time */
+ at91_vbus_poll(sc);
+ }
+ return (0);
+
+error:
+ at91_udp_detach(dev);
+ return (ENXIO);
+}
+
+static int
+at91_udp_detach(device_t dev)
+{
+ struct at91_udp_softc *sc = device_get_softc(dev);
+ device_t bdev;
+ int err;
+
+ if (sc->sc_dci.sc_bus.bdev) {
+ bdev = sc->sc_dci.sc_bus.bdev;
+ device_detach(bdev);
+ device_delete_child(dev, bdev);
+ }
+ /* during module unload there are lots of children leftover */
+ device_delete_all_children(dev);
+
+ /* disable Transceiver */
+ AT91_UDP_WRITE_4(&sc->sc_dci, AT91_UDP_TXVC, AT91_UDP_TXVC_DIS);
+
+ /* disable and clear all interrupts */
+ AT91_UDP_WRITE_4(&sc->sc_dci, AT91_UDP_IDR, 0xFFFFFFFF);
+ AT91_UDP_WRITE_4(&sc->sc_dci, AT91_UDP_ICR, 0xFFFFFFFF);
+
+ /* disable VBUS interrupt */
+ at91_pio_gpio_set_interrupt(VBUS_BASE, VBUS_MASK, 0);
+
+ if (sc->sc_vbus_irq_res && sc->sc_vbus_intr_hdl) {
+ err = bus_teardown_intr(dev, sc->sc_vbus_irq_res,
+ sc->sc_vbus_intr_hdl);
+ sc->sc_vbus_intr_hdl = NULL;
+ }
+ if (sc->sc_vbus_irq_res) {
+ bus_release_resource(dev, SYS_RES_IRQ, 1,
+ sc->sc_vbus_irq_res);
+ sc->sc_vbus_irq_res = NULL;
+ }
+ if (sc->sc_dci.sc_irq_res && sc->sc_dci.sc_intr_hdl) {
+ /*
+ * only call at91_udp_uninit() after at91_udp_init()
+ */
+ at91dci_uninit(&sc->sc_dci);
+
+ err = bus_teardown_intr(dev, sc->sc_dci.sc_irq_res,
+ sc->sc_dci.sc_intr_hdl);
+ sc->sc_dci.sc_intr_hdl = NULL;
+ }
+ if (sc->sc_dci.sc_irq_res) {
+ bus_release_resource(dev, SYS_RES_IRQ, 0,
+ sc->sc_dci.sc_irq_res);
+ sc->sc_dci.sc_irq_res = NULL;
+ }
+ if (sc->sc_dci.sc_io_res) {
+ bus_release_resource(dev, SYS_RES_MEMORY, MEM_RID,
+ sc->sc_dci.sc_io_res);
+ sc->sc_dci.sc_io_res = NULL;
+ }
+ usb2_bus_mem_free_all(&sc->sc_dci.sc_bus, NULL);
+
+ /* disable clocks */
+ at91_pmc_clock_disable(sc->sc_iclk);
+ at91_pmc_clock_disable(sc->sc_fclk);
+ at91_pmc_clock_deref(sc->sc_fclk);
+ at91_pmc_clock_deref(sc->sc_iclk);
+
+ return (0);
+}
+
+static int
+at91_udp_shutdown(device_t dev)
+{
+ struct at91_udp_softc *sc = device_get_softc(dev);
+ int err;
+
+ err = bus_generic_shutdown(dev);
+ if (err)
+ return (err);
+
+ at91dci_uninit(&sc->sc_dci);
+
+ return (0);
+}
+
+static device_method_t at91_udp_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, at91_udp_probe),
+ DEVMETHOD(device_attach, at91_udp_attach),
+ DEVMETHOD(device_detach, at91_udp_detach),
+ DEVMETHOD(device_shutdown, at91_udp_shutdown),
+
+ /* Bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+
+ {0, 0}
+};
+
+static driver_t at91_udp_driver = {
+ "at91_udp",
+ at91_udp_methods,
+ sizeof(struct at91_udp_softc),
+};
+
+static devclass_t at91_udp_devclass;
+
+DRIVER_MODULE(at91_udp, atmelarm, at91_udp_driver, at91_udp_devclass, 0, 0);
diff --git a/sys/dev/usb/controller/atmegadci.c b/sys/dev/usb/controller/atmegadci.c
new file mode 100644
index 0000000..8a68822
--- /dev/null
+++ b/sys/dev/usb/controller/atmegadci.c
@@ -0,0 +1,2327 @@
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*-
+ * Copyright (c) 2009 Hans Petter Selasky. 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.
+ */
+
+/*
+ * This file contains the driver for the ATMEGA series USB Device
+ * Controller
+ */
+
+/*
+ * NOTE: When the chip detects BUS-reset it will also reset the
+ * endpoints, Function-address and more.
+ */
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_error.h>
+#include <dev/usb/usb_defs.h>
+
+#define USB_DEBUG_VAR atmegadci_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/atmegadci.h>
+
+#define ATMEGA_BUS2SC(bus) \
+ ((struct atmegadci_softc *)(((uint8_t *)(bus)) - \
+ USB_P2U(&(((struct atmegadci_softc *)0)->sc_bus))))
+
+#define ATMEGA_PC2SC(pc) \
+ ATMEGA_BUS2SC((pc)->tag_parent->info->bus)
+
+#if USB_DEBUG
+static int atmegadci_debug = 0;
+
+SYSCTL_NODE(_hw_usb2, OID_AUTO, atmegadci, CTLFLAG_RW, 0, "USB ATMEGA DCI");
+SYSCTL_INT(_hw_usb2_atmegadci, OID_AUTO, debug, CTLFLAG_RW,
+ &atmegadci_debug, 0, "ATMEGA DCI debug level");
+#endif
+
+#define ATMEGA_INTR_ENDPT 1
+
+/* prototypes */
+
+struct usb2_bus_methods atmegadci_bus_methods;
+struct usb2_pipe_methods atmegadci_device_bulk_methods;
+struct usb2_pipe_methods atmegadci_device_ctrl_methods;
+struct usb2_pipe_methods atmegadci_device_intr_methods;
+struct usb2_pipe_methods atmegadci_device_isoc_fs_methods;
+struct usb2_pipe_methods atmegadci_root_ctrl_methods;
+struct usb2_pipe_methods atmegadci_root_intr_methods;
+
+static atmegadci_cmd_t atmegadci_setup_rx;
+static atmegadci_cmd_t atmegadci_data_rx;
+static atmegadci_cmd_t atmegadci_data_tx;
+static atmegadci_cmd_t atmegadci_data_tx_sync;
+static void atmegadci_device_done(struct usb2_xfer *, usb2_error_t);
+static void atmegadci_do_poll(struct usb2_bus *);
+static void atmegadci_root_ctrl_poll(struct atmegadci_softc *);
+static void atmegadci_standard_done(struct usb2_xfer *);
+
+static usb2_sw_transfer_func_t atmegadci_root_intr_done;
+static usb2_sw_transfer_func_t atmegadci_root_ctrl_done;
+
+/*
+ * Here is a list of what the chip supports:
+ */
+static const struct usb2_hw_ep_profile
+ atmegadci_ep_profile[2] = {
+
+ [0] = {
+ .max_in_frame_size = 64,
+ .max_out_frame_size = 64,
+ .is_simplex = 1,
+ .support_control = 1,
+ },
+ [1] = {
+ .max_in_frame_size = 64,
+ .max_out_frame_size = 64,
+ .is_simplex = 1,
+ .support_multi_buffer = 1,
+ .support_bulk = 1,
+ .support_interrupt = 1,
+ .support_isochronous = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+};
+
+static void
+atmegadci_get_hw_ep_profile(struct usb2_device *udev,
+ const struct usb2_hw_ep_profile **ppf, uint8_t ep_addr)
+{
+ if (ep_addr == 0)
+ *ppf = atmegadci_ep_profile;
+ else if (ep_addr < ATMEGA_EP_MAX)
+ *ppf = atmegadci_ep_profile + 1;
+ else
+ *ppf = NULL;
+}
+
+static void
+atmegadci_clocks_on(struct atmegadci_softc *sc)
+{
+ if (sc->sc_flags.clocks_off &&
+ sc->sc_flags.port_powered) {
+
+ DPRINTFN(5, "\n");
+
+ /* turn on clocks */
+ (sc->sc_clocks_on) (&sc->sc_bus);
+
+ ATMEGA_WRITE_1(sc, ATMEGA_USBCON,
+ ATMEGA_USBCON_USBE |
+ ATMEGA_USBCON_OTGPADE |
+ ATMEGA_USBCON_VBUSTE);
+
+ sc->sc_flags.clocks_off = 0;
+
+ /* enable transceiver ? */
+ }
+}
+
+static void
+atmegadci_clocks_off(struct atmegadci_softc *sc)
+{
+ if (!sc->sc_flags.clocks_off) {
+
+ DPRINTFN(5, "\n");
+
+ /* disable Transceiver ? */
+
+ ATMEGA_WRITE_1(sc, ATMEGA_USBCON,
+ ATMEGA_USBCON_USBE |
+ ATMEGA_USBCON_OTGPADE |
+ ATMEGA_USBCON_FRZCLK |
+ ATMEGA_USBCON_VBUSTE);
+
+ /* turn clocks off */
+ (sc->sc_clocks_off) (&sc->sc_bus);
+
+ sc->sc_flags.clocks_off = 1;
+ }
+}
+
+static void
+atmegadci_pull_up(struct atmegadci_softc *sc)
+{
+ /* pullup D+, if possible */
+
+ if (!sc->sc_flags.d_pulled_up &&
+ sc->sc_flags.port_powered) {
+ sc->sc_flags.d_pulled_up = 1;
+ ATMEGA_WRITE_1(sc, ATMEGA_UDCON, 0);
+ }
+}
+
+static void
+atmegadci_pull_down(struct atmegadci_softc *sc)
+{
+ /* pulldown D+, if possible */
+
+ if (sc->sc_flags.d_pulled_up) {
+ sc->sc_flags.d_pulled_up = 0;
+ ATMEGA_WRITE_1(sc, ATMEGA_UDCON, ATMEGA_UDCON_DETACH);
+ }
+}
+
+static void
+atmegadci_wakeup_peer(struct usb2_xfer *xfer)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+ uint8_t use_polling;
+ uint8_t temp;
+
+ if (!sc->sc_flags.status_suspend) {
+ return;
+ }
+ use_polling = mtx_owned(xfer->xroot->xfer_mtx) ? 1 : 0;
+
+ temp = ATMEGA_READ_1(sc, ATMEGA_UDCON);
+ ATMEGA_WRITE_1(sc, ATMEGA_UDCON, temp | ATMEGA_UDCON_RMWKUP);
+
+ /* wait 8 milliseconds */
+ if (use_polling) {
+ /* polling */
+ DELAY(8000);
+ } else {
+ /* Wait for reset to complete. */
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125);
+ }
+
+ /* hardware should have cleared RMWKUP bit */
+}
+
+static void
+atmegadci_set_address(struct atmegadci_softc *sc, uint8_t addr)
+{
+ DPRINTFN(5, "addr=%d\n", addr);
+
+ ATMEGA_WRITE_1(sc, ATMEGA_UDADDR, addr);
+
+ addr |= ATMEGA_UDADDR_ADDEN;
+
+ ATMEGA_WRITE_1(sc, ATMEGA_UDADDR, addr);
+}
+
+static uint8_t
+atmegadci_setup_rx(struct atmegadci_td *td)
+{
+ struct atmegadci_softc *sc;
+ struct usb2_device_request req;
+ uint16_t count;
+ uint8_t temp;
+
+ /* get pointer to softc */
+ sc = ATMEGA_PC2SC(td->pc);
+
+ /* select endpoint number */
+ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no);
+
+ /* check endpoint status */
+ temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX);
+
+ DPRINTFN(5, "UEINTX=0x%02x\n", temp);
+
+ if (!(temp & ATMEGA_UEINTX_RXSTPI)) {
+ /* abort any ongoing transfer */
+ if (!td->did_stall) {
+ DPRINTFN(5, "stalling\n");
+ ATMEGA_WRITE_1(sc, ATMEGA_UECONX,
+ ATMEGA_UECONX_EPEN |
+ ATMEGA_UECONX_STALLRQ);
+ td->did_stall = 1;
+ }
+ goto not_complete;
+ }
+ /* get the packet byte count */
+ count =
+ (ATMEGA_READ_1(sc, ATMEGA_UEBCHX) << 8) |
+ (ATMEGA_READ_1(sc, ATMEGA_UEBCLX));
+
+ /* mask away undefined bits */
+ count &= 0x7FF;
+
+ /* verify data length */
+ if (count != td->remainder) {
+ DPRINTFN(0, "Invalid SETUP packet "
+ "length, %d bytes\n", count);
+ goto not_complete;
+ }
+ if (count != sizeof(req)) {
+ DPRINTFN(0, "Unsupported SETUP packet "
+ "length, %d bytes\n", count);
+ goto not_complete;
+ }
+ /* receive data */
+ ATMEGA_READ_MULTI_1(sc, ATMEGA_UEDATX,
+ (void *)&req, sizeof(req));
+
+ /* copy data into real buffer */
+ usb2_copy_in(td->pc, 0, &req, sizeof(req));
+
+ td->offset = sizeof(req);
+ td->remainder = 0;
+
+ /* sneak peek the set address */
+ if ((req.bmRequestType == UT_WRITE_DEVICE) &&
+ (req.bRequest == UR_SET_ADDRESS)) {
+ sc->sc_dv_addr = req.wValue[0] & 0x7F;
+ } else {
+ sc->sc_dv_addr = 0xFF;
+ }
+
+ /* clear SETUP packet interrupt */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, ~ATMEGA_UEINTX_RXSTPI);
+ return (0); /* complete */
+
+not_complete:
+ /* we only want to know if there is a SETUP packet */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, ATMEGA_UEIENX_RXSTPE);
+ return (1); /* not complete */
+}
+
+static uint8_t
+atmegadci_data_rx(struct atmegadci_td *td)
+{
+ struct atmegadci_softc *sc;
+ struct usb2_page_search buf_res;
+ uint16_t count;
+ uint8_t temp;
+ uint8_t to;
+ uint8_t got_short;
+
+ to = 3; /* don't loop forever! */
+ got_short = 0;
+
+ /* get pointer to softc */
+ sc = ATMEGA_PC2SC(td->pc);
+
+ /* select endpoint number */
+ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no);
+
+repeat:
+ /* check if any of the FIFO banks have data */
+ /* check endpoint status */
+ temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX);
+
+ DPRINTFN(5, "temp=0x%02x rem=%u\n", temp, td->remainder);
+
+ if (temp & ATMEGA_UEINTX_RXSTPI) {
+ if (td->remainder == 0) {
+ /*
+ * We are actually complete and have
+ * received the next SETUP
+ */
+ DPRINTFN(5, "faking complete\n");
+ return (0); /* complete */
+ }
+ /*
+ * USB Host Aborted the transfer.
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ /* check status */
+ if (!(temp & (ATMEGA_UEINTX_FIFOCON |
+ ATMEGA_UEINTX_RXOUTI))) {
+ /* no data */
+ goto not_complete;
+ }
+ /* get the packet byte count */
+ count =
+ (ATMEGA_READ_1(sc, ATMEGA_UEBCHX) << 8) |
+ (ATMEGA_READ_1(sc, ATMEGA_UEBCLX));
+
+ /* mask away undefined bits */
+ count &= 0x7FF;
+
+ /* verify the packet byte count */
+ if (count != td->max_packet_size) {
+ if (count < td->max_packet_size) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ got_short = 1;
+ } else {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ }
+ /* verify the packet byte count */
+ if (count > td->remainder) {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ while (count > 0) {
+ usb2_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* receive data */
+ ATMEGA_READ_MULTI_1(sc, ATMEGA_UEDATX,
+ buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* clear OUT packet interrupt */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, ATMEGA_UEINTX_RXOUTI ^ 0xFF);
+
+ /* release FIFO bank */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, ATMEGA_UEINTX_FIFOCON ^ 0xFF);
+
+ /* check if we are complete */
+ if ((td->remainder == 0) || got_short) {
+ if (td->short_pkt) {
+ /* we are complete */
+ return (0);
+ }
+ /* else need to receive a zero length packet */
+ }
+ if (--to) {
+ goto repeat;
+ }
+not_complete:
+ /* we only want to know if there is a SETUP packet or OUT packet */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX,
+ ATMEGA_UEIENX_RXSTPE | ATMEGA_UEIENX_RXOUTE);
+ return (1); /* not complete */
+}
+
+static uint8_t
+atmegadci_data_tx(struct atmegadci_td *td)
+{
+ struct atmegadci_softc *sc;
+ struct usb2_page_search buf_res;
+ uint16_t count;
+ uint8_t to;
+ uint8_t temp;
+
+ to = 3; /* don't loop forever! */
+
+ /* get pointer to softc */
+ sc = ATMEGA_PC2SC(td->pc);
+
+ /* select endpoint number */
+ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no);
+
+repeat:
+
+ /* check endpoint status */
+ temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX);
+
+ DPRINTFN(5, "temp=0x%02x rem=%u\n", temp, td->remainder);
+
+ if (temp & ATMEGA_UEINTX_RXSTPI) {
+ /*
+ * The current transfer was aborted
+ * by the USB Host
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ if (!(temp & (ATMEGA_UEINTX_FIFOCON |
+ ATMEGA_UEINTX_TXINI))) {
+ /* cannot write any data */
+ goto not_complete;
+ }
+ count = td->max_packet_size;
+ if (td->remainder < count) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ count = td->remainder;
+ }
+ while (count > 0) {
+
+ usb2_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* transmit data */
+ ATMEGA_WRITE_MULTI_1(sc, ATMEGA_UEDATX,
+ buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* clear IN packet interrupt */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, 0xFF ^ ATMEGA_UEINTX_TXINI);
+
+ /* allocate FIFO bank */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, 0xFF ^ ATMEGA_UEINTX_FIFOCON);
+
+ /* check remainder */
+ if (td->remainder == 0) {
+ if (td->short_pkt) {
+ return (0); /* complete */
+ }
+ /* else we need to transmit a short packet */
+ }
+ if (--to) {
+ goto repeat;
+ }
+not_complete:
+ /* we only want to know if there is a SETUP packet or free IN packet */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX,
+ ATMEGA_UEIENX_RXSTPE | ATMEGA_UEIENX_TXINE);
+ return (1); /* not complete */
+}
+
+static uint8_t
+atmegadci_data_tx_sync(struct atmegadci_td *td)
+{
+ struct atmegadci_softc *sc;
+ uint8_t temp;
+
+ /* get pointer to softc */
+ sc = ATMEGA_PC2SC(td->pc);
+
+ /* select endpoint number */
+ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no);
+
+ /* check endpoint status */
+ temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX);
+
+ DPRINTFN(5, "temp=0x%02x\n", temp);
+
+ if (temp & ATMEGA_UEINTX_RXSTPI) {
+ DPRINTFN(5, "faking complete\n");
+ /* Race condition */
+ return (0); /* complete */
+ }
+ /*
+ * The control endpoint has only got one bank, so if that bank
+ * is free the packet has been transferred!
+ */
+ if (!(temp & (ATMEGA_UEINTX_FIFOCON |
+ ATMEGA_UEINTX_TXINI))) {
+ /* cannot write any data */
+ goto not_complete;
+ }
+ if (sc->sc_dv_addr != 0xFF) {
+ /* set new address */
+ atmegadci_set_address(sc, sc->sc_dv_addr);
+ }
+ return (0); /* complete */
+
+not_complete:
+ /* we only want to know if there is a SETUP packet or free IN packet */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX,
+ ATMEGA_UEIENX_RXSTPE | ATMEGA_UEIENX_TXINE);
+ return (1); /* not complete */
+}
+
+static uint8_t
+atmegadci_xfer_do_fifo(struct usb2_xfer *xfer)
+{
+ struct atmegadci_td *td;
+
+ DPRINTFN(9, "\n");
+
+ td = xfer->td_transfer_cache;
+ while (1) {
+ if ((td->func) (td)) {
+ /* operation in progress */
+ break;
+ }
+ if (((void *)td) == xfer->td_transfer_last) {
+ goto done;
+ }
+ if (td->error) {
+ goto done;
+ } else if (td->remainder > 0) {
+ /*
+ * We had a short transfer. If there is no alternate
+ * next, stop processing !
+ */
+ if (!td->alt_next) {
+ goto done;
+ }
+ }
+ /*
+ * Fetch the next transfer descriptor and transfer
+ * some flags to the next transfer descriptor
+ */
+ td = td->obj_next;
+ xfer->td_transfer_cache = td;
+ }
+ return (1); /* not complete */
+
+done:
+ /* compute all actual lengths */
+
+ atmegadci_standard_done(xfer);
+ return (0); /* complete */
+}
+
+static void
+atmegadci_interrupt_poll(struct atmegadci_softc *sc)
+{
+ struct usb2_xfer *xfer;
+
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ if (!atmegadci_xfer_do_fifo(xfer)) {
+ /* queue has been modified */
+ goto repeat;
+ }
+ }
+}
+
+static void
+atmegadci_vbus_interrupt(struct atmegadci_softc *sc, uint8_t is_on)
+{
+ DPRINTFN(5, "vbus = %u\n", is_on);
+
+ if (is_on) {
+ if (!sc->sc_flags.status_vbus) {
+ sc->sc_flags.status_vbus = 1;
+
+ /* complete root HUB interrupt endpoint */
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &atmegadci_root_intr_done);
+ }
+ } else {
+ if (sc->sc_flags.status_vbus) {
+ sc->sc_flags.status_vbus = 0;
+ sc->sc_flags.status_bus_reset = 0;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ /* complete root HUB interrupt endpoint */
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &atmegadci_root_intr_done);
+ }
+ }
+}
+
+void
+atmegadci_interrupt(struct atmegadci_softc *sc)
+{
+ uint8_t status;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* read interrupt status */
+ status = ATMEGA_READ_1(sc, ATMEGA_UDINT);
+
+ /* clear all set interrupts */
+ ATMEGA_WRITE_1(sc, ATMEGA_UDINT, ~status);
+
+ /* check for any bus state change interrupts */
+ if (status & ATMEGA_UDINT_EORSTI) {
+
+ DPRINTFN(5, "end of reset\n");
+
+ /* set correct state */
+ sc->sc_flags.status_bus_reset = 1;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ /* disable resume interrupt */
+ ATMEGA_WRITE_1(sc, ATMEGA_UDIEN,
+ ATMEGA_UDINT_SUSPE |
+ ATMEGA_UDINT_EORSTE);
+
+ /* complete root HUB interrupt endpoint */
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &atmegadci_root_intr_done);
+ }
+ /*
+ * If resume and suspend is set at the same time we interpret
+ * that like RESUME. Resume is set when there is at least 3
+ * milliseconds of inactivity on the USB BUS.
+ */
+ if (status & ATMEGA_UDINT_EORSMI) {
+
+ DPRINTFN(5, "resume interrupt\n");
+
+ if (sc->sc_flags.status_suspend) {
+ /* update status bits */
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 1;
+
+ /* disable resume interrupt */
+ ATMEGA_WRITE_1(sc, ATMEGA_UDIEN,
+ ATMEGA_UDINT_SUSPE |
+ ATMEGA_UDINT_EORSTE);
+
+ /* complete root HUB interrupt endpoint */
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &atmegadci_root_intr_done);
+ }
+ } else if (status & ATMEGA_UDINT_SUSPI) {
+
+ DPRINTFN(5, "suspend interrupt\n");
+
+ if (!sc->sc_flags.status_suspend) {
+ /* update status bits */
+ sc->sc_flags.status_suspend = 1;
+ sc->sc_flags.change_suspend = 1;
+
+ /* disable suspend interrupt */
+ ATMEGA_WRITE_1(sc, ATMEGA_UDIEN,
+ ATMEGA_UDINT_EORSMI |
+ ATMEGA_UDINT_EORSTE);
+
+ /* complete root HUB interrupt endpoint */
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &atmegadci_root_intr_done);
+ }
+ }
+ /* check VBUS */
+ status = ATMEGA_READ_1(sc, ATMEGA_USBINT);
+
+ /* clear all set interrupts */
+ ATMEGA_WRITE_1(sc, ATMEGA_USBINT, ~status);
+
+ if (status & ATMEGA_USBINT_VBUSTI) {
+ uint8_t temp;
+
+ temp = ATMEGA_READ_1(sc, ATMEGA_USBSTA);
+ atmegadci_vbus_interrupt(sc, temp & ATMEGA_USBSTA_VBUS);
+ }
+ /* check for any endpoint interrupts */
+ status = ATMEGA_READ_1(sc, ATMEGA_UEINT);
+
+ /* clear all set interrupts */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEINT, ~status);
+
+ if (status) {
+
+ DPRINTFN(5, "real endpoint interrupt 0x%02x\n", status);
+
+ atmegadci_interrupt_poll(sc);
+ }
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+atmegadci_setup_standard_chain_sub(struct atmegadci_std_temp *temp)
+{
+ struct atmegadci_td *td;
+
+ /* get current Transfer Descriptor */
+ td = temp->td_next;
+ temp->td = td;
+
+ /* prepare for next TD */
+ temp->td_next = td->obj_next;
+
+ /* fill out the Transfer Descriptor */
+ td->func = temp->func;
+ td->pc = temp->pc;
+ td->offset = temp->offset;
+ td->remainder = temp->len;
+ td->error = 0;
+ td->did_stall = 0;
+ td->short_pkt = temp->short_pkt;
+ td->alt_next = temp->setup_alt_next;
+}
+
+static void
+atmegadci_setup_standard_chain(struct usb2_xfer *xfer)
+{
+ struct atmegadci_std_temp temp;
+ struct atmegadci_softc *sc;
+ struct atmegadci_td *td;
+ uint32_t x;
+ uint8_t ep_no;
+ uint8_t need_sync;
+
+ DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpoint),
+ xfer->sumlen, usb2_get_speed(xfer->xroot->udev));
+
+ temp.max_frame_size = xfer->max_frame_size;
+
+ td = xfer->td_start[0];
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ /* setup temp */
+
+ temp.td = NULL;
+ temp.td_next = xfer->td_start[0];
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+ temp.offset = 0;
+
+ sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+ ep_no = (xfer->endpoint & UE_ADDR);
+
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+
+ temp.func = &atmegadci_setup_rx;
+ temp.len = xfer->frlengths[0];
+ temp.pc = xfer->frbuffers + 0;
+ temp.short_pkt = temp.len ? 1 : 0;
+
+ atmegadci_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ if (x != xfer->nframes) {
+ if (xfer->endpoint & UE_DIR_IN) {
+ temp.func = &atmegadci_data_tx;
+ need_sync = 1;
+ } else {
+ temp.func = &atmegadci_data_rx;
+ need_sync = 0;
+ }
+
+ /* setup "pc" pointer */
+ temp.pc = xfer->frbuffers + x;
+ } else {
+ need_sync = 0;
+ }
+ while (x != xfer->nframes) {
+
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+
+ x++;
+
+ if (x == xfer->nframes) {
+ temp.setup_alt_next = 0;
+ }
+ if (temp.len == 0) {
+
+ /* make sure that we send an USB packet */
+
+ temp.short_pkt = 0;
+
+ } else {
+
+ /* regular data transfer */
+
+ temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+ }
+
+ atmegadci_setup_standard_chain_sub(&temp);
+
+ if (xfer->flags_int.isochronous_xfr) {
+ temp.offset += temp.len;
+ } else {
+ /* get next Page Cache pointer */
+ temp.pc = xfer->frbuffers + x;
+ }
+ }
+
+ /* always setup a valid "pc" pointer for status and sync */
+ temp.pc = xfer->frbuffers + 0;
+
+ /* check if we need to sync */
+ if (need_sync && xfer->flags_int.control_xfr) {
+
+ /* we need a SYNC point after TX */
+ temp.func = &atmegadci_data_tx_sync;
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ atmegadci_setup_standard_chain_sub(&temp);
+ }
+ /* check if we should append a status stage */
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ /*
+ * Send a DATA1 message and invert the current
+ * endpoint direction.
+ */
+ if (xfer->endpoint & UE_DIR_IN) {
+ temp.func = &atmegadci_data_rx;
+ need_sync = 0;
+ } else {
+ temp.func = &atmegadci_data_tx;
+ need_sync = 1;
+ }
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ atmegadci_setup_standard_chain_sub(&temp);
+ if (need_sync) {
+ /* we need a SYNC point after TX */
+ temp.func = &atmegadci_data_tx_sync;
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ atmegadci_setup_standard_chain_sub(&temp);
+ }
+ }
+ /* must have at least one frame! */
+ td = temp.td;
+ xfer->td_transfer_last = td;
+}
+
+static void
+atmegadci_timeout(void *arg)
+{
+ struct usb2_xfer *xfer = arg;
+
+ DPRINTF("xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ atmegadci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+atmegadci_start_standard_chain(struct usb2_xfer *xfer)
+{
+ DPRINTFN(9, "\n");
+
+ /* poll one time - will turn on interrupts */
+ if (atmegadci_xfer_do_fifo(xfer)) {
+
+ /* put transfer on interrupt queue */
+ usb2_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usb2_transfer_timeout_ms(xfer,
+ &atmegadci_timeout, xfer->timeout);
+ }
+ }
+}
+
+static void
+atmegadci_root_intr_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+
+ DPRINTFN(9, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_PRE_DATA) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ atmegadci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* setup buffer */
+ std->ptr = sc->sc_hub_idata;
+ std->len = sizeof(sc->sc_hub_idata);
+
+ /* set port bit */
+ sc->sc_hub_idata[0] = 0x02; /* we only have one port */
+
+done:
+ return;
+}
+
+static usb2_error_t
+atmegadci_standard_done_sub(struct usb2_xfer *xfer)
+{
+ struct atmegadci_td *td;
+ uint32_t len;
+ uint8_t error;
+
+ DPRINTFN(9, "\n");
+
+ td = xfer->td_transfer_cache;
+
+ do {
+ len = td->remainder;
+
+ if (xfer->aframes != xfer->nframes) {
+ /*
+ * Verify the length and subtract
+ * the remainder from "frlengths[]":
+ */
+ if (len > xfer->frlengths[xfer->aframes]) {
+ td->error = 1;
+ } else {
+ xfer->frlengths[xfer->aframes] -= len;
+ }
+ }
+ /* Check for transfer error */
+ if (td->error) {
+ /* the transfer is finished */
+ error = 1;
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (len > 0) {
+ if (xfer->flags_int.short_frames_ok) {
+ /* follow alt next */
+ if (td->alt_next) {
+ td = td->obj_next;
+ } else {
+ td = NULL;
+ }
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ error = 0;
+ break;
+ }
+ td = td->obj_next;
+
+ /* this USB frame is complete */
+ error = 0;
+ break;
+
+ } while (0);
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ return (error ?
+ USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+atmegadci_standard_done(struct usb2_xfer *xfer)
+{
+ usb2_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->pipe);
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+
+ if (xfer->flags_int.control_hdr) {
+
+ err = atmegadci_standard_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+
+ err = atmegadci_standard_done_sub(xfer);
+ xfer->aframes++;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ err = atmegadci_standard_done_sub(xfer);
+ }
+done:
+ atmegadci_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * atmegadci_device_done
+ *
+ * NOTE: this function can be called more than one time on the
+ * same USB transfer!
+ *------------------------------------------------------------------------*/
+static void
+atmegadci_device_done(struct usb2_xfer *xfer, usb2_error_t error)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+ uint8_t ep_no;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ DPRINTFN(2, "xfer=%p, pipe=%p, error=%d\n",
+ xfer, xfer->pipe, error);
+
+ if (xfer->flags_int.usb2_mode == USB_MODE_DEVICE) {
+ ep_no = (xfer->endpoint & UE_ADDR);
+
+ /* select endpoint number */
+ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, ep_no);
+
+ /* disable endpoint interrupt */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, 0);
+
+ DPRINTFN(15, "disabled interrupts!\n");
+ }
+ /* dequeue transfer and start next transfer */
+ usb2_transfer_done(xfer, error);
+}
+
+static void
+atmegadci_set_stall(struct usb2_device *udev, struct usb2_xfer *xfer,
+ struct usb2_pipe *pipe)
+{
+ struct atmegadci_softc *sc;
+ uint8_t ep_no;
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ DPRINTFN(5, "pipe=%p\n", pipe);
+
+ if (xfer) {
+ /* cancel any ongoing transfers */
+ atmegadci_device_done(xfer, USB_ERR_STALLED);
+ }
+ sc = ATMEGA_BUS2SC(udev->bus);
+ /* get endpoint number */
+ ep_no = (pipe->edesc->bEndpointAddress & UE_ADDR);
+ /* select endpoint number */
+ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, ep_no);
+ /* set stall */
+ ATMEGA_WRITE_1(sc, ATMEGA_UECONX,
+ ATMEGA_UECONX_EPEN |
+ ATMEGA_UECONX_STALLRQ);
+}
+
+static void
+atmegadci_clear_stall_sub(struct atmegadci_softc *sc, uint8_t ep_no,
+ uint8_t ep_type, uint8_t ep_dir)
+{
+ uint8_t temp;
+
+ if (ep_type == UE_CONTROL) {
+ /* clearing stall is not needed */
+ return;
+ }
+ /* select endpoint number */
+ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, ep_no);
+
+ /* set endpoint reset */
+ ATMEGA_WRITE_1(sc, ATMEGA_UERST, ATMEGA_UERST_MASK(ep_no));
+
+ /* clear endpoint reset */
+ ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0);
+
+ /* set stall */
+ ATMEGA_WRITE_1(sc, ATMEGA_UECONX,
+ ATMEGA_UECONX_EPEN |
+ ATMEGA_UECONX_STALLRQ);
+
+ /* reset data toggle */
+ ATMEGA_WRITE_1(sc, ATMEGA_UECONX,
+ ATMEGA_UECONX_EPEN |
+ ATMEGA_UECONX_RSTDT);
+
+ /* clear stall */
+ ATMEGA_WRITE_1(sc, ATMEGA_UECONX,
+ ATMEGA_UECONX_EPEN |
+ ATMEGA_UECONX_STALLRQC);
+
+ if (ep_type == UE_CONTROL) {
+ /* one bank, 64-bytes wMaxPacket */
+ ATMEGA_WRITE_1(sc, ATMEGA_UECFG0X,
+ ATMEGA_UECFG0X_EPTYPE0);
+ ATMEGA_WRITE_1(sc, ATMEGA_UECFG1X,
+ ATMEGA_UECFG1X_ALLOC |
+ ATMEGA_UECFG1X_EPBK0 |
+ ATMEGA_UECFG1X_EPSIZE(7));
+ } else {
+ temp = 0;
+ if (ep_type == UE_BULK) {
+ temp |= ATMEGA_UECFG0X_EPTYPE2;
+ } else if (ep_type == UE_INTERRUPT) {
+ temp |= ATMEGA_UECFG0X_EPTYPE3;
+ } else {
+ temp |= ATMEGA_UECFG0X_EPTYPE1;
+ }
+ if (ep_dir & UE_DIR_IN) {
+ temp |= ATMEGA_UECFG0X_EPDIR;
+ }
+ /* two banks, 64-bytes wMaxPacket */
+ ATMEGA_WRITE_1(sc, ATMEGA_UECFG0X, temp);
+ ATMEGA_WRITE_1(sc, ATMEGA_UECFG1X,
+ ATMEGA_UECFG1X_ALLOC |
+ ATMEGA_UECFG1X_EPBK1 |
+ ATMEGA_UECFG1X_EPSIZE(7));
+
+ temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X);
+ if (!(temp & ATMEGA_UESTA0X_CFGOK)) {
+ DPRINTFN(0, "Chip rejected configuration\n");
+ }
+ }
+}
+
+static void
+atmegadci_clear_stall(struct usb2_device *udev, struct usb2_pipe *pipe)
+{
+ struct atmegadci_softc *sc;
+ struct usb2_endpoint_descriptor *ed;
+
+ DPRINTFN(5, "pipe=%p\n", pipe);
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ /* check mode */
+ if (udev->flags.usb2_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ /* get softc */
+ sc = ATMEGA_BUS2SC(udev->bus);
+
+ /* get endpoint descriptor */
+ ed = pipe->edesc;
+
+ /* reset endpoint */
+ atmegadci_clear_stall_sub(sc,
+ (ed->bEndpointAddress & UE_ADDR),
+ (ed->bmAttributes & UE_XFERTYPE),
+ (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)));
+}
+
+usb2_error_t
+atmegadci_init(struct atmegadci_softc *sc)
+{
+ uint8_t n;
+
+ DPRINTF("start\n");
+
+ /* set up the bus structure */
+ sc->sc_bus.usbrev = USB_REV_1_1;
+ sc->sc_bus.methods = &atmegadci_bus_methods;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* enable USB PAD regulator */
+ ATMEGA_WRITE_1(sc, ATMEGA_UHWCON,
+ ATMEGA_UHWCON_UVREGE);
+
+ /* turn on clocks */
+ (sc->sc_clocks_on) (&sc->sc_bus);
+
+ /* wait a little for things to stabilise */
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+
+ /* enable interrupts */
+ ATMEGA_WRITE_1(sc, ATMEGA_UDIEN,
+ ATMEGA_UDINT_SUSPE |
+ ATMEGA_UDINT_EORSTE);
+
+ /* reset all endpoints */
+ ATMEGA_WRITE_1(sc, ATMEGA_UERST,
+ (1 << ATMEGA_EP_MAX) - 1);
+
+ /* disable reset */
+ ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0);
+
+ /* disable all endpoints */
+ for (n = 1; n != ATMEGA_EP_MAX; n++) {
+
+ /* select endpoint */
+ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, n);
+
+ /* disable endpoint interrupt */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, 0);
+
+ /* disable endpoint */
+ ATMEGA_WRITE_1(sc, ATMEGA_UECONX, 0);
+ }
+
+ /* turn off clocks */
+
+ atmegadci_clocks_off(sc);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* catch any lost interrupts */
+
+ atmegadci_do_poll(&sc->sc_bus);
+
+ return (0); /* success */
+}
+
+void
+atmegadci_uninit(struct atmegadci_softc *sc)
+{
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* turn on clocks */
+ (sc->sc_clocks_on) (&sc->sc_bus);
+
+ /* disable interrupts */
+ ATMEGA_WRITE_1(sc, ATMEGA_UDIEN, 0);
+
+ /* reset all endpoints */
+ ATMEGA_WRITE_1(sc, ATMEGA_UERST,
+ (1 << ATMEGA_EP_MAX) - 1);
+
+ /* disable reset */
+ ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0);
+
+ sc->sc_flags.port_powered = 0;
+ sc->sc_flags.status_vbus = 0;
+ sc->sc_flags.status_bus_reset = 0;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ atmegadci_pull_down(sc);
+ atmegadci_clocks_off(sc);
+
+ /* disable USB PAD regulator */
+ ATMEGA_WRITE_1(sc, ATMEGA_UHWCON, 0);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+void
+atmegadci_suspend(struct atmegadci_softc *sc)
+{
+ return;
+}
+
+void
+atmegadci_resume(struct atmegadci_softc *sc)
+{
+ return;
+}
+
+static void
+atmegadci_do_poll(struct usb2_bus *bus)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ atmegadci_interrupt_poll(sc);
+ atmegadci_root_ctrl_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*------------------------------------------------------------------------*
+ * at91dci bulk support
+ *------------------------------------------------------------------------*/
+static void
+atmegadci_device_bulk_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_device_bulk_close(struct usb2_xfer *xfer)
+{
+ atmegadci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+atmegadci_device_bulk_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_device_bulk_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ atmegadci_setup_standard_chain(xfer);
+ atmegadci_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods atmegadci_device_bulk_methods =
+{
+ .open = atmegadci_device_bulk_open,
+ .close = atmegadci_device_bulk_close,
+ .enter = atmegadci_device_bulk_enter,
+ .start = atmegadci_device_bulk_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci control support
+ *------------------------------------------------------------------------*/
+static void
+atmegadci_device_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_device_ctrl_close(struct usb2_xfer *xfer)
+{
+ atmegadci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+atmegadci_device_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_device_ctrl_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ atmegadci_setup_standard_chain(xfer);
+ atmegadci_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods atmegadci_device_ctrl_methods =
+{
+ .open = atmegadci_device_ctrl_open,
+ .close = atmegadci_device_ctrl_close,
+ .enter = atmegadci_device_ctrl_enter,
+ .start = atmegadci_device_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+atmegadci_device_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_device_intr_close(struct usb2_xfer *xfer)
+{
+ atmegadci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+atmegadci_device_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_device_intr_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ atmegadci_setup_standard_chain(xfer);
+ atmegadci_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods atmegadci_device_intr_methods =
+{
+ .open = atmegadci_device_intr_open,
+ .close = atmegadci_device_intr_close,
+ .enter = atmegadci_device_intr_enter,
+ .start = atmegadci_device_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci full speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+atmegadci_device_isoc_fs_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_device_isoc_fs_close(struct usb2_xfer *xfer)
+{
+ atmegadci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+atmegadci_device_isoc_fs_enter(struct usb2_xfer *xfer)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+ uint32_t temp;
+ uint32_t nframes;
+
+ DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
+ xfer, xfer->pipe->isoc_next, xfer->nframes);
+
+ /* get the current frame index */
+
+ nframes =
+ (ATMEGA_READ_1(sc, ATMEGA_UDFNUMH) << 8) |
+ (ATMEGA_READ_1(sc, ATMEGA_UDFNUML));
+
+ nframes &= ATMEGA_FRAME_MASK;
+
+ /*
+ * check if the frame index is within the window where the frames
+ * will be inserted
+ */
+ temp = (nframes - xfer->pipe->isoc_next) & ATMEGA_FRAME_MASK;
+
+ if ((xfer->pipe->is_synced == 0) ||
+ (temp < xfer->nframes)) {
+ /*
+ * If there is data underflow or the pipe queue is
+ * empty we schedule the transfer a few frames ahead
+ * of the current frame position. Else two isochronous
+ * transfers might overlap.
+ */
+ xfer->pipe->isoc_next = (nframes + 3) & ATMEGA_FRAME_MASK;
+ xfer->pipe->is_synced = 1;
+ DPRINTFN(3, "start next=%d\n", xfer->pipe->isoc_next);
+ }
+ /*
+ * compute how many milliseconds the insertion is ahead of the
+ * current frame position:
+ */
+ temp = (xfer->pipe->isoc_next - nframes) & ATMEGA_FRAME_MASK;
+
+ /*
+ * pre-compute when the isochronous transfer will be finished:
+ */
+ xfer->isoc_time_complete =
+ usb2_isoc_time_expand(&sc->sc_bus, nframes) + temp +
+ xfer->nframes;
+
+ /* compute frame number for next insertion */
+ xfer->pipe->isoc_next += xfer->nframes;
+
+ /* setup TDs */
+ atmegadci_setup_standard_chain(xfer);
+}
+
+static void
+atmegadci_device_isoc_fs_start(struct usb2_xfer *xfer)
+{
+ /* start TD chain */
+ atmegadci_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods atmegadci_device_isoc_fs_methods =
+{
+ .open = atmegadci_device_isoc_fs_open,
+ .close = atmegadci_device_isoc_fs_close,
+ .enter = atmegadci_device_isoc_fs_enter,
+ .start = atmegadci_device_isoc_fs_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci root control support
+ *------------------------------------------------------------------------*
+ * simulate a hardware HUB by handling
+ * all the necessary requests
+ *------------------------------------------------------------------------*/
+
+static void
+atmegadci_root_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_root_ctrl_close(struct usb2_xfer *xfer)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_ctrl.xfer == xfer) {
+ sc->sc_root_ctrl.xfer = NULL;
+ }
+ atmegadci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+/*
+ * USB descriptors for the virtual Root HUB:
+ */
+
+static const struct usb2_device_descriptor atmegadci_devd = {
+ .bLength = sizeof(struct usb2_device_descriptor),
+ .bDescriptorType = UDESC_DEVICE,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_HSHUBSTT,
+ .bMaxPacketSize = 64,
+ .bcdDevice = {0x00, 0x01},
+ .iManufacturer = 1,
+ .iProduct = 2,
+ .bNumConfigurations = 1,
+};
+
+static const struct usb2_device_qualifier atmegadci_odevd = {
+ .bLength = sizeof(struct usb2_device_qualifier),
+ .bDescriptorType = UDESC_DEVICE_QUALIFIER,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_FSHUB,
+ .bMaxPacketSize0 = 0,
+ .bNumConfigurations = 0,
+};
+
+static const struct atmegadci_config_desc atmegadci_confd = {
+ .confd = {
+ .bLength = sizeof(struct usb2_config_descriptor),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(atmegadci_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0,
+ },
+ .ifcd = {
+ .bLength = sizeof(struct usb2_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = UIPROTO_HSHUBSTT,
+ },
+
+ .endpd = {
+ .bLength = sizeof(struct usb2_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = (UE_DIR_IN | ATMEGA_INTR_ENDPT),
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 8,
+ .bInterval = 255,
+ },
+};
+
+static const struct usb2_hub_descriptor_min atmegadci_hubd = {
+ .bDescLength = sizeof(atmegadci_hubd),
+ .bDescriptorType = UDESC_HUB,
+ .bNbrPorts = 1,
+ .wHubCharacteristics[0] =
+ (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) & 0xFF,
+ .wHubCharacteristics[1] =
+ (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) >> 8,
+ .bPwrOn2PwrGood = 50,
+ .bHubContrCurrent = 0,
+ .DeviceRemovable = {0}, /* port is removable */
+};
+
+#define STRING_LANG \
+ 0x09, 0x04, /* American English */
+
+#define STRING_VENDOR \
+ 'A', 0, 'T', 0, 'M', 0, 'E', 0, 'G', 0, 'A', 0
+
+#define STRING_PRODUCT \
+ 'D', 0, 'C', 0, 'I', 0, ' ', 0, 'R', 0, \
+ 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, \
+ 'U', 0, 'B', 0,
+
+USB_MAKE_STRING_DESC(STRING_LANG, atmegadci_langtab);
+USB_MAKE_STRING_DESC(STRING_VENDOR, atmegadci_vendor);
+USB_MAKE_STRING_DESC(STRING_PRODUCT, atmegadci_product);
+
+static void
+atmegadci_root_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_root_ctrl_start(struct usb2_xfer *xfer)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_ctrl.xfer = xfer;
+
+ usb2_bus_roothub_exec(xfer->xroot->bus);
+}
+
+static void
+atmegadci_root_ctrl_task(struct usb2_bus *bus)
+{
+ atmegadci_root_ctrl_poll(ATMEGA_BUS2SC(bus));
+}
+
+static void
+atmegadci_root_ctrl_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+ uint16_t value;
+ uint16_t index;
+ uint8_t use_polling;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_SETUP) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ atmegadci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* buffer reset */
+ std->ptr = USB_ADD_BYTES(&sc->sc_hub_temp, 0);
+ std->len = 0;
+
+ value = UGETW(std->req.wValue);
+ index = UGETW(std->req.wIndex);
+
+ use_polling = mtx_owned(xfer->xroot->xfer_mtx) ? 1 : 0;
+
+ /* demultiplex the control request */
+
+ switch (std->req.bmRequestType) {
+ case UT_READ_DEVICE:
+ switch (std->req.bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_descriptor;
+ case UR_GET_CONFIG:
+ goto tr_handle_get_config;
+ case UR_GET_STATUS:
+ goto tr_handle_get_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_DEVICE:
+ switch (std->req.bRequest) {
+ case UR_SET_ADDRESS:
+ goto tr_handle_set_address;
+ case UR_SET_CONFIG:
+ goto tr_handle_set_config;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_DESCRIPTOR:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_ENDPOINT:
+ switch (std->req.bRequest) {
+ case UR_CLEAR_FEATURE:
+ switch (UGETW(std->req.wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_clear_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_clear_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SET_FEATURE:
+ switch (UGETW(std->req.wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_set_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_set_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SYNCH_FRAME:
+ goto tr_valid; /* nop */
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_ENDPOINT:
+ switch (std->req.bRequest) {
+ case UR_GET_STATUS:
+ goto tr_handle_get_ep_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_INTERFACE:
+ switch (std->req.bRequest) {
+ case UR_SET_INTERFACE:
+ goto tr_handle_set_interface;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_INTERFACE:
+ switch (std->req.bRequest) {
+ case UR_GET_INTERFACE:
+ goto tr_handle_get_interface;
+ case UR_GET_STATUS:
+ goto tr_handle_get_iface_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_INTERFACE:
+ case UT_WRITE_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_READ_CLASS_INTERFACE:
+ case UT_READ_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_WRITE_CLASS_DEVICE:
+ switch (std->req.bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_valid;
+ case UR_SET_DESCRIPTOR:
+ case UR_SET_FEATURE:
+ break;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_OTHER:
+ switch (std->req.bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_handle_clear_port_feature;
+ case UR_SET_FEATURE:
+ goto tr_handle_set_port_feature;
+ case UR_CLEAR_TT_BUFFER:
+ case UR_RESET_TT:
+ case UR_STOP_TT:
+ goto tr_valid;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_OTHER:
+ switch (std->req.bRequest) {
+ case UR_GET_TT_STATE:
+ goto tr_handle_get_tt_state;
+ case UR_GET_STATUS:
+ goto tr_handle_get_port_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_DEVICE:
+ switch (std->req.bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_class_descriptor;
+ case UR_GET_STATUS:
+ goto tr_handle_get_class_status;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_valid;
+
+tr_handle_get_descriptor:
+ switch (value >> 8) {
+ case UDESC_DEVICE:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ std->len = sizeof(atmegadci_devd);
+ std->ptr = USB_ADD_BYTES(&atmegadci_devd, 0);
+ goto tr_valid;
+ case UDESC_CONFIG:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ std->len = sizeof(atmegadci_confd);
+ std->ptr = USB_ADD_BYTES(&atmegadci_confd, 0);
+ goto tr_valid;
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ std->len = sizeof(atmegadci_langtab);
+ std->ptr = USB_ADD_BYTES(&atmegadci_langtab, 0);
+ goto tr_valid;
+
+ case 1: /* Vendor */
+ std->len = sizeof(atmegadci_vendor);
+ std->ptr = USB_ADD_BYTES(&atmegadci_vendor, 0);
+ goto tr_valid;
+
+ case 2: /* Product */
+ std->len = sizeof(atmegadci_product);
+ std->ptr = USB_ADD_BYTES(&atmegadci_product, 0);
+ goto tr_valid;
+ default:
+ break;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_stalled;
+
+tr_handle_get_config:
+ std->len = 1;
+ sc->sc_hub_temp.wValue[0] = sc->sc_conf;
+ goto tr_valid;
+
+tr_handle_get_status:
+ std->len = 2;
+ USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED);
+ goto tr_valid;
+
+tr_handle_set_address:
+ if (value & 0xFF00) {
+ goto tr_stalled;
+ }
+ sc->sc_rt_addr = value;
+ goto tr_valid;
+
+tr_handle_set_config:
+ if (value >= 2) {
+ goto tr_stalled;
+ }
+ sc->sc_conf = value;
+ goto tr_valid;
+
+tr_handle_get_interface:
+ std->len = 1;
+ sc->sc_hub_temp.wValue[0] = 0;
+ goto tr_valid;
+
+tr_handle_get_tt_state:
+tr_handle_get_class_status:
+tr_handle_get_iface_status:
+tr_handle_get_ep_status:
+ std->len = 2;
+ USETW(sc->sc_hub_temp.wValue, 0);
+ goto tr_valid;
+
+tr_handle_set_halt:
+tr_handle_set_interface:
+tr_handle_set_wakeup:
+tr_handle_clear_wakeup:
+tr_handle_clear_halt:
+ goto tr_valid;
+
+tr_handle_clear_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index);
+
+ switch (value) {
+ case UHF_PORT_SUSPEND:
+ atmegadci_wakeup_peer(xfer);
+ break;
+
+ case UHF_PORT_ENABLE:
+ sc->sc_flags.port_enabled = 0;
+ break;
+
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ case UHF_C_PORT_ENABLE:
+ case UHF_C_PORT_OVER_CURRENT:
+ case UHF_C_PORT_RESET:
+ /* nops */
+ break;
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 0;
+ atmegadci_pull_down(sc);
+ atmegadci_clocks_off(sc);
+ break;
+ case UHF_C_PORT_CONNECTION:
+ sc->sc_flags.change_connect = 0;
+ break;
+ case UHF_C_PORT_SUSPEND:
+ sc->sc_flags.change_suspend = 0;
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_set_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(9, "UR_SET_PORT_FEATURE\n");
+
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ sc->sc_flags.port_enabled = 1;
+ break;
+ case UHF_PORT_SUSPEND:
+ case UHF_PORT_RESET:
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ /* nops */
+ break;
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 1;
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_get_port_status:
+
+ DPRINTFN(9, "UR_GET_PORT_STATUS\n");
+
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ if (sc->sc_flags.status_vbus) {
+ atmegadci_clocks_on(sc);
+ atmegadci_pull_up(sc);
+ } else {
+ atmegadci_pull_down(sc);
+ atmegadci_clocks_off(sc);
+ }
+
+ /* Select FULL-speed and Device Side Mode */
+
+ value = UPS_PORT_MODE_DEVICE;
+
+ if (sc->sc_flags.port_powered) {
+ value |= UPS_PORT_POWER;
+ }
+ if (sc->sc_flags.port_enabled) {
+ value |= UPS_PORT_ENABLED;
+ }
+ if (sc->sc_flags.status_vbus &&
+ sc->sc_flags.status_bus_reset) {
+ value |= UPS_CURRENT_CONNECT_STATUS;
+ }
+ if (sc->sc_flags.status_suspend) {
+ value |= UPS_SUSPEND;
+ }
+ USETW(sc->sc_hub_temp.ps.wPortStatus, value);
+
+ value = 0;
+
+ if (sc->sc_flags.change_connect) {
+ value |= UPS_C_CONNECT_STATUS;
+ }
+ if (sc->sc_flags.change_suspend) {
+ value |= UPS_C_SUSPEND;
+ }
+ USETW(sc->sc_hub_temp.ps.wPortChange, value);
+ std->len = sizeof(sc->sc_hub_temp.ps);
+ goto tr_valid;
+
+tr_handle_get_class_descriptor:
+ if (value & 0xFF) {
+ goto tr_stalled;
+ }
+ std->ptr = USB_ADD_BYTES(&atmegadci_hubd, 0);
+ std->len = sizeof(atmegadci_hubd);
+ goto tr_valid;
+
+tr_stalled:
+ std->err = USB_ERR_STALLED;
+tr_valid:
+done:
+ return;
+}
+
+static void
+atmegadci_root_ctrl_poll(struct atmegadci_softc *sc)
+{
+ usb2_sw_transfer(&sc->sc_root_ctrl,
+ &atmegadci_root_ctrl_done);
+}
+
+struct usb2_pipe_methods atmegadci_root_ctrl_methods =
+{
+ .open = atmegadci_root_ctrl_open,
+ .close = atmegadci_root_ctrl_close,
+ .enter = atmegadci_root_ctrl_enter,
+ .start = atmegadci_root_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 0,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci root interrupt support
+ *------------------------------------------------------------------------*/
+static void
+atmegadci_root_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_root_intr_close(struct usb2_xfer *xfer)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_intr.xfer == xfer) {
+ sc->sc_root_intr.xfer = NULL;
+ }
+ atmegadci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+atmegadci_root_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_root_intr_start(struct usb2_xfer *xfer)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_intr.xfer = xfer;
+}
+
+struct usb2_pipe_methods atmegadci_root_intr_methods =
+{
+ .open = atmegadci_root_intr_open,
+ .close = atmegadci_root_intr_close,
+ .enter = atmegadci_root_intr_enter,
+ .start = atmegadci_root_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+static void
+atmegadci_xfer_setup(struct usb2_setup_params *parm)
+{
+ const struct usb2_hw_ep_profile *pf;
+ struct atmegadci_softc *sc;
+ struct usb2_xfer *xfer;
+ void *last_obj;
+ uint32_t ntd;
+ uint32_t n;
+ uint8_t ep_no;
+
+ sc = ATMEGA_BUS2SC(parm->udev->bus);
+ xfer = parm->curr_xfer;
+
+ /*
+ * NOTE: This driver does not use any of the parameters that
+ * are computed from the following values. Just set some
+ * reasonable dummies:
+ */
+ parm->hc_max_packet_size = 0x500;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = 0x500;
+
+ usb2_transfer_setup_sub(parm);
+
+ /*
+ * compute maximum number of TDs
+ */
+ if (parm->methods == &atmegadci_device_ctrl_methods) {
+
+ ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC 1 */
+ + 1 /* SYNC 2 */ ;
+
+ } else if (parm->methods == &atmegadci_device_bulk_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &atmegadci_device_intr_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &atmegadci_device_isoc_fs_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else {
+
+ ntd = 0;
+ }
+
+ /*
+ * check if "usb2_transfer_setup_sub" set an error
+ */
+ if (parm->err) {
+ return;
+ }
+ /*
+ * allocate transfer descriptors
+ */
+ last_obj = NULL;
+
+ /*
+ * get profile stuff
+ */
+ if (ntd) {
+
+ ep_no = xfer->endpoint & UE_ADDR;
+ atmegadci_get_hw_ep_profile(parm->udev, &pf, ep_no);
+
+ if (pf == NULL) {
+ /* should not happen */
+ parm->err = USB_ERR_INVAL;
+ return;
+ }
+ } else {
+ ep_no = 0;
+ pf = NULL;
+ }
+
+ /* align data */
+ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+ for (n = 0; n != ntd; n++) {
+
+ struct atmegadci_td *td;
+
+ if (parm->buf) {
+
+ td = USB_ADD_BYTES(parm->buf, parm->size[0]);
+
+ /* init TD */
+ td->max_packet_size = xfer->max_packet_size;
+ td->ep_no = ep_no;
+ if (pf->support_multi_buffer) {
+ td->support_multi_buffer = 1;
+ }
+ td->obj_next = last_obj;
+
+ last_obj = td;
+ }
+ parm->size[0] += sizeof(*td);
+ }
+
+ xfer->td_start[0] = last_obj;
+}
+
+static void
+atmegadci_xfer_unsetup(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_pipe_init(struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc,
+ struct usb2_pipe *pipe)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
+ pipe, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb2_mode,
+ sc->sc_rt_addr);
+
+ if (udev->device_index == sc->sc_rt_addr) {
+
+ if (udev->flags.usb2_mode != USB_MODE_HOST) {
+ /* not supported */
+ return;
+ }
+ switch (edesc->bEndpointAddress) {
+ case USB_CONTROL_ENDPOINT:
+ pipe->methods = &atmegadci_root_ctrl_methods;
+ break;
+ case UE_DIR_IN | ATMEGA_INTR_ENDPT:
+ pipe->methods = &atmegadci_root_intr_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ } else {
+
+ if (udev->flags.usb2_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ if (udev->speed != USB_SPEED_FULL) {
+ /* not supported */
+ return;
+ }
+ switch (edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_CONTROL:
+ pipe->methods = &atmegadci_device_ctrl_methods;
+ break;
+ case UE_INTERRUPT:
+ pipe->methods = &atmegadci_device_intr_methods;
+ break;
+ case UE_ISOCHRONOUS:
+ pipe->methods = &atmegadci_device_isoc_fs_methods;
+ break;
+ case UE_BULK:
+ pipe->methods = &atmegadci_device_bulk_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+}
+
+struct usb2_bus_methods atmegadci_bus_methods =
+{
+ .pipe_init = &atmegadci_pipe_init,
+ .xfer_setup = &atmegadci_xfer_setup,
+ .xfer_unsetup = &atmegadci_xfer_unsetup,
+ .do_poll = &atmegadci_do_poll,
+ .get_hw_ep_profile = &atmegadci_get_hw_ep_profile,
+ .set_stall = &atmegadci_set_stall,
+ .clear_stall = &atmegadci_clear_stall,
+ .roothub_exec = &atmegadci_root_ctrl_task,
+};
diff --git a/sys/dev/usb/controller/atmegadci.h b/sys/dev/usb/controller/atmegadci.h
new file mode 100644
index 0000000..90b3334
--- /dev/null
+++ b/sys/dev/usb/controller/atmegadci.h
@@ -0,0 +1,273 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 2009 Hans Petter Selasky. 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.
+ */
+
+/*
+ * USB Device Port register definitions, copied from ATMEGA
+ * documentation provided by ATMEL.
+ */
+
+#ifndef _ATMEGADCI_H_
+#define _ATMEGADCI_H_
+
+#define ATMEGA_MAX_DEVICES (USB_MIN_DEVICES + 1)
+
+#ifndef ATMEGA_HAVE_BUS_SPACE
+#define ATMEGA_HAVE_BUS_SPACE 1
+#endif
+
+#define ATMEGA_UEINT 0xF4
+#define ATMEGA_UEINT_MASK(n) (1 << (n)) /* endpoint interrupt mask */
+
+#define ATMEGA_UEBCHX 0xF3 /* FIFO byte count high */
+#define ATMEGA_UEBCLX 0xF2 /* FIFO byte count low */
+#define ATMEGA_UEDATX 0xF1 /* FIFO data */
+
+#define ATMEGA_UEIENX 0xF0 /* interrupt enable register */
+#define ATMEGA_UEIENX_TXINE (1 << 0)
+#define ATMEGA_UEIENX_STALLEDE (1 << 1)
+#define ATMEGA_UEIENX_RXOUTE (1 << 2)
+#define ATMEGA_UEIENX_RXSTPE (1 << 3) /* received SETUP packet */
+#define ATMEGA_UEIENX_NAKOUTE (1 << 4)
+#define ATMEGA_UEIENX_NAKINE (1 << 6)
+#define ATMEGA_UEIENX_FLERRE (1 << 7)
+
+#define ATMEGA_UESTA1X 0xEF
+#define ATMEGA_UESTA1X_CURRBK (3 << 0) /* current bank */
+#define ATMEGA_UESTA1X_CTRLDIR (1 << 2) /* control endpoint direction */
+
+#define ATMEGA_UESTA0X 0xEE
+#define ATMEGA_UESTA0X_NBUSYBK (3 << 0)
+#define ATMEGA_UESTA0X_DTSEQ (3 << 2)
+#define ATMEGA_UESTA0X_UNDERFI (1 << 5) /* underflow */
+#define ATMEGA_UESTA0X_OVERFI (1 << 6) /* overflow */
+#define ATMEGA_UESTA0X_CFGOK (1 << 7)
+
+#define ATMEGA_UECFG1X 0xED /* endpoint config register */
+#define ATMEGA_UECFG1X_ALLOC (1 << 1)
+#define ATMEGA_UECFG1X_EPBK0 (0 << 2)
+#define ATMEGA_UECFG1X_EPBK1 (1 << 2)
+#define ATMEGA_UECFG1X_EPBK2 (2 << 2)
+#define ATMEGA_UECFG1X_EPBK3 (3 << 2)
+#define ATMEGA_UECFG1X_EPSIZE(n) ((n) << 4)
+
+#define ATMEGA_UECFG0X 0xEC
+#define ATMEGA_UECFG0X_EPDIR (1 << 0) /* endpoint direction */
+#define ATMEGA_UECFG0X_EPTYPE0 (0 << 6)
+#define ATMEGA_UECFG0X_EPTYPE1 (1 << 6)
+#define ATMEGA_UECFG0X_EPTYPE2 (2 << 6)
+#define ATMEGA_UECFG0X_EPTYPE3 (3 << 6)
+
+#define ATMEGA_UECONX 0xEB
+#define ATMEGA_UECONX_EPEN (1 << 0)
+#define ATMEGA_UECONX_RSTDT (1 << 3)
+#define ATMEGA_UECONX_STALLRQC (1 << 4) /* stall request clear */
+#define ATMEGA_UECONX_STALLRQ (1 << 5) /* stall request set */
+
+#define ATMEGA_UERST 0xEA /* endpoint reset register */
+#define ATMEGA_UERST_MASK(n) (1 << (n))
+
+#define ATMEGA_UENUM 0xE9 /* endpoint number */
+
+#define ATMEGA_UEINTX 0xE8 /* interrupt register */
+#define ATMEGA_UEINTX_TXINI (1 << 0)
+#define ATMEGA_UEINTX_STALLEDI (1 << 1)
+#define ATMEGA_UEINTX_RXOUTI (1 << 2)
+#define ATMEGA_UEINTX_RXSTPI (1 << 3) /* received setup packet */
+#define ATMEGA_UEINTX_NAKOUTI (1 << 4)
+#define ATMEGA_UEINTX_RWAL (1 << 5)
+#define ATMEGA_UEINTX_NAKINI (1 << 6)
+#define ATMEGA_UEINTX_FIFOCON (1 << 7)
+
+#define ATMEGA_UDMFN 0xE6
+#define ATMEGA_UDMFN_FNCERR (1 << 4)
+
+#define ATMEGA_UDFNUMH 0xE5 /* frame number high */
+#define ATMEGA_UDFNUMH_MASK 7
+
+#define ATMEGA_UDFNUML 0xE4 /* frame number low */
+#define ATMEGA_UDFNUML_MASK 0xFF
+
+#define ATMEGA_FRAME_MASK 0x7FF
+
+#define ATMEGA_UDADDR 0xE3 /* USB address */
+#define ATMEGA_UDADDR_MASK 0x7F
+#define ATMEGA_UDADDR_ADDEN (1 << 7)
+
+#define ATMEGA_UDIEN 0xE2 /* USB device interrupt enable */
+#define ATMEGA_UDINT_SUSPE (1 << 0)
+#define ATMEGA_UDINT_MSOFE (1 << 1)
+#define ATMEGA_UDINT_SOFE (1 << 2)
+#define ATMEGA_UDINT_EORSTE (1 << 3)
+#define ATMEGA_UDINT_WAKEUPE (1 << 4)
+#define ATMEGA_UDINT_EORSME (1 << 5)
+#define ATMEGA_UDINT_UPRSME (1 << 6)
+
+#define ATMEGA_UDINT 0xE1 /* USB device interrupt status */
+#define ATMEGA_UDINT_SUSPI (1 << 0)
+#define ATMEGA_UDINT_MSOFI (1 << 1)
+#define ATMEGA_UDINT_SOFI (1 << 2)
+#define ATMEGA_UDINT_EORSTI (1 << 3)
+#define ATMEGA_UDINT_WAKEUPI (1 << 4)
+#define ATMEGA_UDINT_EORSMI (1 << 5)
+#define ATMEGA_UDINT_UPRSMI (1 << 6)
+
+#define ATMEGA_UDCON 0xE0 /* USB device connection register */
+#define ATMEGA_UDCON_DETACH (1 << 0)
+#define ATMEGA_UDCON_RMWKUP (1 << 1)
+#define ATMEGA_UDCON_LSM (1 << 2)
+#define ATMEGA_UDCON_RSTCPU (1 << 3)
+
+#define ATMEGA_USBINT 0xDA
+#define ATMEGA_USBINT_VBUSTI (1 << 0) /* USB VBUS interrupt */
+
+#define ATMEGA_USBSTA 0xD9
+#define ATMEGA_USBSTA_VBUS (1 << 0)
+#define ATMEGA_USBSTA_ID (1 << 1)
+
+#define ATMEGA_USBCON 0xD8
+#define ATMEGA_USBCON_VBUSTE (1 << 0)
+#define ATMEGA_USBCON_OTGPADE (1 << 4)
+#define ATMEGA_USBCON_FRZCLK (1 << 5)
+#define ATMEGA_USBCON_USBE (1 << 7)
+
+#define ATMEGA_UHWCON 0xD7
+#define ATMEGA_UHWCON_UVREGE (1 << 0)
+
+#define ATMEGA_READ_1(sc, reg) \
+ bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg)
+
+#define ATMEGA_WRITE_1(sc, reg, data) \
+ bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data)
+
+#define ATMEGA_WRITE_MULTI_1(sc, reg, ptr, len) \
+ bus_space_write_multi_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, ptr, len)
+
+#define ATMEGA_READ_MULTI_1(sc, reg, ptr, len) \
+ bus_space_read_multi_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, ptr, len)
+
+/*
+ * Maximum number of endpoints supported:
+ */
+#define ATMEGA_EP_MAX 7
+
+struct atmegadci_td;
+
+typedef uint8_t (atmegadci_cmd_t)(struct atmegadci_td *td);
+typedef void (atmegadci_clocks_t)(struct usb2_bus *);
+
+struct atmegadci_td {
+ struct atmegadci_td *obj_next;
+ atmegadci_cmd_t *func;
+ struct usb2_page_cache *pc;
+ uint32_t offset;
+ uint32_t remainder;
+ uint16_t max_packet_size;
+ uint8_t error:1;
+ uint8_t alt_next:1;
+ uint8_t short_pkt:1;
+ uint8_t support_multi_buffer:1;
+ uint8_t did_stall:1;
+ uint8_t ep_no:3;
+};
+
+struct atmegadci_std_temp {
+ atmegadci_cmd_t *func;
+ struct usb2_page_cache *pc;
+ struct atmegadci_td *td;
+ struct atmegadci_td *td_next;
+ uint32_t len;
+ uint32_t offset;
+ uint16_t max_frame_size;
+ uint8_t short_pkt;
+ /*
+ * short_pkt = 0: transfer should be short terminated
+ * short_pkt = 1: transfer should not be short terminated
+ */
+ uint8_t setup_alt_next;
+};
+
+struct atmegadci_config_desc {
+ struct usb2_config_descriptor confd;
+ struct usb2_interface_descriptor ifcd;
+ struct usb2_endpoint_descriptor endpd;
+} __packed;
+
+union atmegadci_hub_temp {
+ uWord wValue;
+ struct usb2_port_status ps;
+};
+
+struct atmegadci_flags {
+ uint8_t change_connect:1;
+ uint8_t change_suspend:1;
+ uint8_t status_suspend:1; /* set if suspended */
+ uint8_t status_vbus:1; /* set if present */
+ uint8_t status_bus_reset:1; /* set if reset complete */
+ uint8_t remote_wakeup:1;
+ uint8_t self_powered:1;
+ uint8_t clocks_off:1;
+ uint8_t port_powered:1;
+ uint8_t port_enabled:1;
+ uint8_t d_pulled_up:1;
+};
+
+struct atmegadci_softc {
+ struct usb2_bus sc_bus;
+ union atmegadci_hub_temp sc_hub_temp;
+ LIST_HEAD(, usb2_xfer) sc_interrupt_list_head;
+ struct usb2_sw_transfer sc_root_ctrl;
+ struct usb2_sw_transfer sc_root_intr;
+
+ /* must be set by by the bus interface layer */
+ atmegadci_clocks_t *sc_clocks_on;
+ atmegadci_clocks_t *sc_clocks_off;
+
+ struct usb2_device *sc_devices[ATMEGA_MAX_DEVICES];
+ struct resource *sc_irq_res;
+ void *sc_intr_hdl;
+#if (ATMEGA_HAVE_BUS_SPACE != 0)
+ struct resource *sc_io_res;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+#endif
+ uint8_t sc_rt_addr; /* root hub address */
+ uint8_t sc_dv_addr; /* device address */
+ uint8_t sc_conf; /* root hub config */
+
+ uint8_t sc_hub_idata[1];
+
+ struct atmegadci_flags sc_flags;
+};
+
+/* prototypes */
+
+usb2_error_t atmegadci_init(struct atmegadci_softc *sc);
+void atmegadci_uninit(struct atmegadci_softc *sc);
+void atmegadci_suspend(struct atmegadci_softc *sc);
+void atmegadci_resume(struct atmegadci_softc *sc);
+void atmegadci_interrupt(struct atmegadci_softc *sc);
+
+#endif /* _ATMEGADCI_H_ */
diff --git a/sys/dev/usb/controller/atmegadci_atmelarm.c b/sys/dev/usb/controller/atmegadci_atmelarm.c
new file mode 100644
index 0000000..e63f5cc
--- /dev/null
+++ b/sys/dev/usb/controller/atmegadci_atmelarm.c
@@ -0,0 +1,27 @@
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*-
+ * Copyright (c) 2009 Hans Petter Selasky. 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.
+ */
diff --git a/sys/dev/usb/controller/ehci.c b/sys/dev/usb/controller/ehci.c
new file mode 100644
index 0000000..5802268
--- /dev/null
+++ b/sys/dev/usb/controller/ehci.c
@@ -0,0 +1,3965 @@
+/*-
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 2004 The NetBSD Foundation, Inc. All rights reserved.
+ * Copyright (c) 2004 Lennart Augustsson. All rights reserved.
+ * Copyright (c) 2004 Charles M. Hannum. 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.
+ */
+
+/*
+ * USB Enhanced Host Controller Driver, a.k.a. USB 2.0 controller.
+ *
+ * The EHCI 0.96 spec can be found at
+ * http://developer.intel.com/technology/usb/download/ehci-r096.pdf
+ * The EHCI 1.0 spec can be found at
+ * http://developer.intel.com/technology/usb/download/ehci-r10.pdf
+ * and the USB 2.0 spec at
+ * http://www.usb.org/developers/docs/usb_20.zip
+ *
+ */
+
+/*
+ * TODO:
+ * 1) command failures are not recovered correctly
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_error.h>
+#include <dev/usb/usb_defs.h>
+
+#define USB_DEBUG_VAR ehcidebug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/ehci.h>
+
+#define EHCI_BUS2SC(bus) ((ehci_softc_t *)(((uint8_t *)(bus)) - \
+ USB_P2U(&(((ehci_softc_t *)0)->sc_bus))))
+
+#if USB_DEBUG
+static int ehcidebug = 0;
+static int ehcinohighspeed = 0;
+
+SYSCTL_NODE(_hw_usb2, OID_AUTO, ehci, CTLFLAG_RW, 0, "USB ehci");
+SYSCTL_INT(_hw_usb2_ehci, OID_AUTO, debug, CTLFLAG_RW,
+ &ehcidebug, 0, "Debug level");
+SYSCTL_INT(_hw_usb2_ehci, OID_AUTO, no_hs, CTLFLAG_RW,
+ &ehcinohighspeed, 0, "Disable High Speed USB");
+
+static void ehci_dump_regs(ehci_softc_t *sc);
+static void ehci_dump_sqh(ehci_softc_t *sc, ehci_qh_t *sqh);
+
+#endif
+
+#define EHCI_INTR_ENDPT 1
+
+extern struct usb2_bus_methods ehci_bus_methods;
+extern struct usb2_pipe_methods ehci_device_bulk_methods;
+extern struct usb2_pipe_methods ehci_device_ctrl_methods;
+extern struct usb2_pipe_methods ehci_device_intr_methods;
+extern struct usb2_pipe_methods ehci_device_isoc_fs_methods;
+extern struct usb2_pipe_methods ehci_device_isoc_hs_methods;
+extern struct usb2_pipe_methods ehci_root_ctrl_methods;
+extern struct usb2_pipe_methods ehci_root_intr_methods;
+
+static void ehci_do_poll(struct usb2_bus *bus);
+static void ehci_root_ctrl_poll(ehci_softc_t *sc);
+static void ehci_device_done(struct usb2_xfer *xfer, usb2_error_t error);
+static uint8_t ehci_check_transfer(struct usb2_xfer *xfer);
+static void ehci_timeout(void *arg);
+
+static usb2_sw_transfer_func_t ehci_root_intr_done;
+static usb2_sw_transfer_func_t ehci_root_ctrl_done;
+
+struct ehci_std_temp {
+ ehci_softc_t *sc;
+ struct usb2_page_cache *pc;
+ ehci_qtd_t *td;
+ ehci_qtd_t *td_next;
+ uint32_t average;
+ uint32_t qtd_status;
+ uint32_t len;
+ uint16_t max_frame_size;
+ uint8_t shortpkt;
+ uint8_t auto_data_toggle;
+ uint8_t setup_alt_next;
+ uint8_t short_frames_ok;
+};
+
+/*
+ * Byte-order conversion functions.
+ */
+static uint32_t
+htoehci32(ehci_softc_t *sc, const uint32_t v)
+{
+ return ((sc->sc_flags & EHCI_SCFLG_BIGEDESC) ?
+ htobe32(v) : htole32(v));
+}
+
+static uint32_t
+ehci32toh(ehci_softc_t *sc, const uint32_t v)
+{
+ return ((sc->sc_flags & EHCI_SCFLG_BIGEDESC) ?
+ be32toh(v) : le32toh(v));
+}
+
+void
+ehci_iterate_hw_softc(struct usb2_bus *bus, usb2_bus_mem_sub_cb_t *cb)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(bus);
+ uint32_t i;
+
+ cb(bus, &sc->sc_hw.pframes_pc, &sc->sc_hw.pframes_pg,
+ sizeof(uint32_t) * EHCI_FRAMELIST_COUNT, EHCI_FRAMELIST_ALIGN);
+
+ cb(bus, &sc->sc_hw.async_start_pc, &sc->sc_hw.async_start_pg,
+ sizeof(ehci_qh_t), EHCI_QH_ALIGN);
+
+ for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) {
+ cb(bus, sc->sc_hw.intr_start_pc + i,
+ sc->sc_hw.intr_start_pg + i,
+ sizeof(ehci_qh_t), EHCI_QH_ALIGN);
+ }
+
+ for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) {
+ cb(bus, sc->sc_hw.isoc_hs_start_pc + i,
+ sc->sc_hw.isoc_hs_start_pg + i,
+ sizeof(ehci_itd_t), EHCI_ITD_ALIGN);
+ }
+
+ for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) {
+ cb(bus, sc->sc_hw.isoc_fs_start_pc + i,
+ sc->sc_hw.isoc_fs_start_pg + i,
+ sizeof(ehci_sitd_t), EHCI_SITD_ALIGN);
+ }
+}
+
+static usb2_error_t
+ehci_hc_reset(ehci_softc_t *sc)
+{
+ uint32_t hcr;
+ uint32_t n;
+
+ EOWRITE4(sc, EHCI_USBCMD, 0); /* Halt controller */
+
+ for (n = 0; n != 100; n++) {
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+ hcr = EOREAD4(sc, EHCI_USBSTS);
+ if (hcr & EHCI_STS_HCH) {
+ hcr = 0;
+ break;
+ }
+ }
+
+ /*
+ * Fall through and try reset anyway even though
+ * Table 2-9 in the EHCI spec says this will result
+ * in undefined behavior.
+ */
+
+ EOWRITE4(sc, EHCI_USBCMD, EHCI_CMD_HCRESET);
+ for (n = 0; n != 100; n++) {
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+ hcr = EOREAD4(sc, EHCI_USBCMD);
+ if (!(hcr & EHCI_CMD_HCRESET)) {
+ if (sc->sc_flags & EHCI_SCFLG_SETMODE)
+ EOWRITE4(sc, 0x68, 0x3);
+ hcr = 0;
+ break;
+ }
+ }
+
+ if (hcr) {
+ return (USB_ERR_IOERROR);
+ }
+ return (0);
+}
+
+usb2_error_t
+ehci_init(ehci_softc_t *sc)
+{
+ struct usb2_page_search buf_res;
+ uint32_t version;
+ uint32_t sparams;
+ uint32_t cparams;
+ uint32_t hcr;
+ uint16_t i;
+ uint16_t x;
+ uint16_t y;
+ uint16_t bit;
+ usb2_error_t err = 0;
+
+ DPRINTF("start\n");
+
+ usb2_callout_init_mtx(&sc->sc_tmo_pcd, &sc->sc_bus.bus_mtx, 0);
+
+#if USB_DEBUG
+ if (ehcidebug > 2) {
+ ehci_dump_regs(sc);
+ }
+#endif
+
+ sc->sc_offs = EREAD1(sc, EHCI_CAPLENGTH);
+
+ version = EREAD2(sc, EHCI_HCIVERSION);
+ device_printf(sc->sc_bus.bdev, "EHCI version %x.%x\n",
+ version >> 8, version & 0xff);
+
+ sparams = EREAD4(sc, EHCI_HCSPARAMS);
+ DPRINTF("sparams=0x%x\n", sparams);
+
+ sc->sc_noport = EHCI_HCS_N_PORTS(sparams);
+ cparams = EREAD4(sc, EHCI_HCCPARAMS);
+ DPRINTF("cparams=0x%x\n", cparams);
+
+ if (EHCI_HCC_64BIT(cparams)) {
+ DPRINTF("HCC uses 64-bit structures\n");
+
+ /* MUST clear segment register if 64 bit capable */
+ EWRITE4(sc, EHCI_CTRLDSSEGMENT, 0);
+ }
+ sc->sc_bus.usbrev = USB_REV_2_0;
+
+ /* Reset the controller */
+ DPRINTF("%s: resetting\n", device_get_nameunit(sc->sc_bus.bdev));
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ err = ehci_hc_reset(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+ if (err) {
+ device_printf(sc->sc_bus.bdev, "reset timeout\n");
+ return (err);
+ }
+ /*
+ * use current frame-list-size selection 0: 1024*4 bytes 1: 512*4
+ * bytes 2: 256*4 bytes 3: unknown
+ */
+ if (EHCI_CMD_FLS(EOREAD4(sc, EHCI_USBCMD)) == 3) {
+ device_printf(sc->sc_bus.bdev, "invalid frame-list-size\n");
+ return (USB_ERR_IOERROR);
+ }
+ /* set up the bus struct */
+ sc->sc_bus.methods = &ehci_bus_methods;
+
+ sc->sc_eintrs = EHCI_NORMAL_INTRS;
+
+ for (i = 0; i < EHCI_VIRTUAL_FRAMELIST_COUNT; i++) {
+ ehci_qh_t *qh;
+
+ usb2_get_page(sc->sc_hw.intr_start_pc + i, 0, &buf_res);
+
+ qh = buf_res.buffer;
+
+ /* initialize page cache pointer */
+
+ qh->page_cache = sc->sc_hw.intr_start_pc + i;
+
+ /* store a pointer to queue head */
+
+ sc->sc_intr_p_last[i] = qh;
+
+ qh->qh_self =
+ htoehci32(sc, buf_res.physaddr) |
+ htoehci32(sc, EHCI_LINK_QH);
+
+ qh->qh_endp =
+ htoehci32(sc, EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH));
+ qh->qh_endphub =
+ htoehci32(sc, EHCI_QH_SET_MULT(1));
+ qh->qh_curqtd = 0;
+
+ qh->qh_qtd.qtd_next =
+ htoehci32(sc, EHCI_LINK_TERMINATE);
+ qh->qh_qtd.qtd_altnext =
+ htoehci32(sc, EHCI_LINK_TERMINATE);
+ qh->qh_qtd.qtd_status =
+ htoehci32(sc, EHCI_QTD_HALTED);
+ }
+
+ /*
+ * the QHs are arranged to give poll intervals that are
+ * powers of 2 times 1ms
+ */
+ bit = EHCI_VIRTUAL_FRAMELIST_COUNT / 2;
+ while (bit) {
+ x = bit;
+ while (x & bit) {
+ ehci_qh_t *qh_x;
+ ehci_qh_t *qh_y;
+
+ y = (x ^ bit) | (bit / 2);
+
+ qh_x = sc->sc_intr_p_last[x];
+ qh_y = sc->sc_intr_p_last[y];
+
+ /*
+ * the next QH has half the poll interval
+ */
+ qh_x->qh_link = qh_y->qh_self;
+
+ x++;
+ }
+ bit >>= 1;
+ }
+
+ if (1) {
+ ehci_qh_t *qh;
+
+ qh = sc->sc_intr_p_last[0];
+
+ /* the last (1ms) QH terminates */
+ qh->qh_link = htoehci32(sc, EHCI_LINK_TERMINATE);
+ }
+ for (i = 0; i < EHCI_VIRTUAL_FRAMELIST_COUNT; i++) {
+ ehci_sitd_t *sitd;
+ ehci_itd_t *itd;
+
+ usb2_get_page(sc->sc_hw.isoc_fs_start_pc + i, 0, &buf_res);
+
+ sitd = buf_res.buffer;
+
+ /* initialize page cache pointer */
+
+ sitd->page_cache = sc->sc_hw.isoc_fs_start_pc + i;
+
+ /* store a pointer to the transfer descriptor */
+
+ sc->sc_isoc_fs_p_last[i] = sitd;
+
+ /* initialize full speed isochronous */
+
+ sitd->sitd_self =
+ htoehci32(sc, buf_res.physaddr) |
+ htoehci32(sc, EHCI_LINK_SITD);
+
+ sitd->sitd_back =
+ htoehci32(sc, EHCI_LINK_TERMINATE);
+
+ sitd->sitd_next =
+ sc->sc_intr_p_last[i | (EHCI_VIRTUAL_FRAMELIST_COUNT / 2)]->qh_self;
+
+
+ usb2_get_page(sc->sc_hw.isoc_hs_start_pc + i, 0, &buf_res);
+
+ itd = buf_res.buffer;
+
+ /* initialize page cache pointer */
+
+ itd->page_cache = sc->sc_hw.isoc_hs_start_pc + i;
+
+ /* store a pointer to the transfer descriptor */
+
+ sc->sc_isoc_hs_p_last[i] = itd;
+
+ /* initialize high speed isochronous */
+
+ itd->itd_self =
+ htoehci32(sc, buf_res.physaddr) |
+ htoehci32(sc, EHCI_LINK_ITD);
+
+ itd->itd_next =
+ sitd->sitd_self;
+ }
+
+ usb2_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res);
+
+ if (1) {
+ uint32_t *pframes;
+
+ pframes = buf_res.buffer;
+
+ /*
+ * execution order:
+ * pframes -> high speed isochronous ->
+ * full speed isochronous -> interrupt QH's
+ */
+ for (i = 0; i < EHCI_FRAMELIST_COUNT; i++) {
+ pframes[i] = sc->sc_isoc_hs_p_last
+ [i & (EHCI_VIRTUAL_FRAMELIST_COUNT - 1)]->itd_self;
+ }
+ }
+ /* setup sync list pointer */
+ EOWRITE4(sc, EHCI_PERIODICLISTBASE, buf_res.physaddr);
+
+ usb2_get_page(&sc->sc_hw.async_start_pc, 0, &buf_res);
+
+ if (1) {
+
+ ehci_qh_t *qh;
+
+ qh = buf_res.buffer;
+
+ /* initialize page cache pointer */
+
+ qh->page_cache = &sc->sc_hw.async_start_pc;
+
+ /* store a pointer to the queue head */
+
+ sc->sc_async_p_last = qh;
+
+ /* init dummy QH that starts the async list */
+
+ qh->qh_self =
+ htoehci32(sc, buf_res.physaddr) |
+ htoehci32(sc, EHCI_LINK_QH);
+
+ /* fill the QH */
+ qh->qh_endp =
+ htoehci32(sc, EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH) | EHCI_QH_HRECL);
+ qh->qh_endphub = htoehci32(sc, EHCI_QH_SET_MULT(1));
+ qh->qh_link = qh->qh_self;
+ qh->qh_curqtd = 0;
+
+ /* fill the overlay qTD */
+ qh->qh_qtd.qtd_next = htoehci32(sc, EHCI_LINK_TERMINATE);
+ qh->qh_qtd.qtd_altnext = htoehci32(sc, EHCI_LINK_TERMINATE);
+ qh->qh_qtd.qtd_status = htoehci32(sc, EHCI_QTD_HALTED);
+ }
+ /* flush all cache into memory */
+
+ usb2_bus_mem_flush_all(&sc->sc_bus, &ehci_iterate_hw_softc);
+
+#if USB_DEBUG
+ if (ehcidebug) {
+ ehci_dump_sqh(sc, sc->sc_async_p_last);
+ }
+#endif
+
+ /* setup async list pointer */
+ EOWRITE4(sc, EHCI_ASYNCLISTADDR, buf_res.physaddr | EHCI_LINK_QH);
+
+
+ /* enable interrupts */
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+
+ /* turn on controller */
+ EOWRITE4(sc, EHCI_USBCMD,
+ EHCI_CMD_ITC_1 | /* 1 microframes interrupt delay */
+ (EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_FLS_M) |
+ EHCI_CMD_ASE |
+ EHCI_CMD_PSE |
+ EHCI_CMD_RS);
+
+ /* Take over port ownership */
+ EOWRITE4(sc, EHCI_CONFIGFLAG, EHCI_CONF_CF);
+
+ for (i = 0; i < 100; i++) {
+ usb2_pause_mtx(NULL, hz / 1000);
+ hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH;
+ if (!hcr) {
+ break;
+ }
+ }
+ if (hcr) {
+ device_printf(sc->sc_bus.bdev, "run timeout\n");
+ return (USB_ERR_IOERROR);
+ }
+
+ if (!err) {
+ /* catch any lost interrupts */
+ ehci_do_poll(&sc->sc_bus);
+ }
+ return (err);
+}
+
+/*
+ * shut down the controller when the system is going down
+ */
+void
+ehci_detach(ehci_softc_t *sc)
+{
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ usb2_callout_stop(&sc->sc_tmo_pcd);
+
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+
+ if (ehci_hc_reset(sc)) {
+ DPRINTF("reset failed!\n");
+ }
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* XXX let stray task complete */
+ usb2_pause_mtx(NULL, hz / 20);
+
+ usb2_callout_drain(&sc->sc_tmo_pcd);
+}
+
+void
+ehci_suspend(ehci_softc_t *sc)
+{
+ uint32_t cmd;
+ uint32_t hcr;
+ uint8_t i;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ for (i = 1; i <= sc->sc_noport; i++) {
+ cmd = EOREAD4(sc, EHCI_PORTSC(i));
+ if (((cmd & EHCI_PS_PO) == 0) &&
+ ((cmd & EHCI_PS_PE) == EHCI_PS_PE)) {
+ EOWRITE4(sc, EHCI_PORTSC(i),
+ cmd | EHCI_PS_SUSP);
+ }
+ }
+
+ sc->sc_cmd = EOREAD4(sc, EHCI_USBCMD);
+
+ cmd = sc->sc_cmd & ~(EHCI_CMD_ASE | EHCI_CMD_PSE);
+ EOWRITE4(sc, EHCI_USBCMD, cmd);
+
+ for (i = 0; i < 100; i++) {
+ hcr = EOREAD4(sc, EHCI_USBSTS) &
+ (EHCI_STS_ASS | EHCI_STS_PSS);
+
+ if (hcr == 0) {
+ break;
+ }
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+ }
+
+ if (hcr != 0) {
+ device_printf(sc->sc_bus.bdev, "reset timeout\n");
+ }
+ cmd &= ~EHCI_CMD_RS;
+ EOWRITE4(sc, EHCI_USBCMD, cmd);
+
+ for (i = 0; i < 100; i++) {
+ hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH;
+ if (hcr == EHCI_STS_HCH) {
+ break;
+ }
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+ }
+
+ if (hcr != EHCI_STS_HCH) {
+ device_printf(sc->sc_bus.bdev,
+ "config timeout\n");
+ }
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+void
+ehci_resume(ehci_softc_t *sc)
+{
+ struct usb2_page_search buf_res;
+ uint32_t cmd;
+ uint32_t hcr;
+ uint8_t i;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* restore things in case the bios doesn't */
+ EOWRITE4(sc, EHCI_CTRLDSSEGMENT, 0);
+
+ usb2_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res);
+ EOWRITE4(sc, EHCI_PERIODICLISTBASE, buf_res.physaddr);
+
+ usb2_get_page(&sc->sc_hw.async_start_pc, 0, &buf_res);
+ EOWRITE4(sc, EHCI_ASYNCLISTADDR, buf_res.physaddr | EHCI_LINK_QH);
+
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+
+ hcr = 0;
+ for (i = 1; i <= sc->sc_noport; i++) {
+ cmd = EOREAD4(sc, EHCI_PORTSC(i));
+ if (((cmd & EHCI_PS_PO) == 0) &&
+ ((cmd & EHCI_PS_SUSP) == EHCI_PS_SUSP)) {
+ EOWRITE4(sc, EHCI_PORTSC(i),
+ cmd | EHCI_PS_FPR);
+ hcr = 1;
+ }
+ }
+
+ if (hcr) {
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(USB_RESUME_WAIT));
+
+ for (i = 1; i <= sc->sc_noport; i++) {
+ cmd = EOREAD4(sc, EHCI_PORTSC(i));
+ if (((cmd & EHCI_PS_PO) == 0) &&
+ ((cmd & EHCI_PS_SUSP) == EHCI_PS_SUSP)) {
+ EOWRITE4(sc, EHCI_PORTSC(i),
+ cmd & ~EHCI_PS_FPR);
+ }
+ }
+ }
+ EOWRITE4(sc, EHCI_USBCMD, sc->sc_cmd);
+
+ for (i = 0; i < 100; i++) {
+ hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH;
+ if (hcr != EHCI_STS_HCH) {
+ break;
+ }
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+ }
+ if (hcr == EHCI_STS_HCH) {
+ device_printf(sc->sc_bus.bdev, "config timeout\n");
+ }
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ usb2_pause_mtx(NULL,
+ USB_MS_TO_TICKS(USB_RESUME_WAIT));
+
+ /* catch any lost interrupts */
+ ehci_do_poll(&sc->sc_bus);
+}
+
+void
+ehci_shutdown(ehci_softc_t *sc)
+{
+ DPRINTF("stopping the HC\n");
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ if (ehci_hc_reset(sc)) {
+ DPRINTF("reset failed!\n");
+ }
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+#if USB_DEBUG
+static void
+ehci_dump_regs(ehci_softc_t *sc)
+{
+ uint32_t i;
+
+ i = EOREAD4(sc, EHCI_USBCMD);
+ printf("cmd=0x%08x\n", i);
+
+ if (i & EHCI_CMD_ITC_1)
+ printf(" EHCI_CMD_ITC_1\n");
+ if (i & EHCI_CMD_ITC_2)
+ printf(" EHCI_CMD_ITC_2\n");
+ if (i & EHCI_CMD_ITC_4)
+ printf(" EHCI_CMD_ITC_4\n");
+ if (i & EHCI_CMD_ITC_8)
+ printf(" EHCI_CMD_ITC_8\n");
+ if (i & EHCI_CMD_ITC_16)
+ printf(" EHCI_CMD_ITC_16\n");
+ if (i & EHCI_CMD_ITC_32)
+ printf(" EHCI_CMD_ITC_32\n");
+ if (i & EHCI_CMD_ITC_64)
+ printf(" EHCI_CMD_ITC_64\n");
+ if (i & EHCI_CMD_ASPME)
+ printf(" EHCI_CMD_ASPME\n");
+ if (i & EHCI_CMD_ASPMC)
+ printf(" EHCI_CMD_ASPMC\n");
+ if (i & EHCI_CMD_LHCR)
+ printf(" EHCI_CMD_LHCR\n");
+ if (i & EHCI_CMD_IAAD)
+ printf(" EHCI_CMD_IAAD\n");
+ if (i & EHCI_CMD_ASE)
+ printf(" EHCI_CMD_ASE\n");
+ if (i & EHCI_CMD_PSE)
+ printf(" EHCI_CMD_PSE\n");
+ if (i & EHCI_CMD_FLS_M)
+ printf(" EHCI_CMD_FLS_M\n");
+ if (i & EHCI_CMD_HCRESET)
+ printf(" EHCI_CMD_HCRESET\n");
+ if (i & EHCI_CMD_RS)
+ printf(" EHCI_CMD_RS\n");
+
+ i = EOREAD4(sc, EHCI_USBSTS);
+
+ printf("sts=0x%08x\n", i);
+
+ if (i & EHCI_STS_ASS)
+ printf(" EHCI_STS_ASS\n");
+ if (i & EHCI_STS_PSS)
+ printf(" EHCI_STS_PSS\n");
+ if (i & EHCI_STS_REC)
+ printf(" EHCI_STS_REC\n");
+ if (i & EHCI_STS_HCH)
+ printf(" EHCI_STS_HCH\n");
+ if (i & EHCI_STS_IAA)
+ printf(" EHCI_STS_IAA\n");
+ if (i & EHCI_STS_HSE)
+ printf(" EHCI_STS_HSE\n");
+ if (i & EHCI_STS_FLR)
+ printf(" EHCI_STS_FLR\n");
+ if (i & EHCI_STS_PCD)
+ printf(" EHCI_STS_PCD\n");
+ if (i & EHCI_STS_ERRINT)
+ printf(" EHCI_STS_ERRINT\n");
+ if (i & EHCI_STS_INT)
+ printf(" EHCI_STS_INT\n");
+
+ printf("ien=0x%08x\n",
+ EOREAD4(sc, EHCI_USBINTR));
+ printf("frindex=0x%08x ctrdsegm=0x%08x periodic=0x%08x async=0x%08x\n",
+ EOREAD4(sc, EHCI_FRINDEX),
+ EOREAD4(sc, EHCI_CTRLDSSEGMENT),
+ EOREAD4(sc, EHCI_PERIODICLISTBASE),
+ EOREAD4(sc, EHCI_ASYNCLISTADDR));
+ for (i = 1; i <= sc->sc_noport; i++) {
+ printf("port %d status=0x%08x\n", i,
+ EOREAD4(sc, EHCI_PORTSC(i)));
+ }
+}
+
+static void
+ehci_dump_link(ehci_softc_t *sc, uint32_t link, int type)
+{
+ link = ehci32toh(sc, link);
+ printf("0x%08x", link);
+ if (link & EHCI_LINK_TERMINATE)
+ printf("<T>");
+ else {
+ printf("<");
+ if (type) {
+ switch (EHCI_LINK_TYPE(link)) {
+ case EHCI_LINK_ITD:
+ printf("ITD");
+ break;
+ case EHCI_LINK_QH:
+ printf("QH");
+ break;
+ case EHCI_LINK_SITD:
+ printf("SITD");
+ break;
+ case EHCI_LINK_FSTN:
+ printf("FSTN");
+ break;
+ }
+ }
+ printf(">");
+ }
+}
+
+static void
+ehci_dump_qtd(ehci_softc_t *sc, ehci_qtd_t *qtd)
+{
+ uint32_t s;
+
+ printf(" next=");
+ ehci_dump_link(sc, qtd->qtd_next, 0);
+ printf(" altnext=");
+ ehci_dump_link(sc, qtd->qtd_altnext, 0);
+ printf("\n");
+ s = ehci32toh(sc, qtd->qtd_status);
+ printf(" status=0x%08x: toggle=%d bytes=0x%x ioc=%d c_page=0x%x\n",
+ s, EHCI_QTD_GET_TOGGLE(s), EHCI_QTD_GET_BYTES(s),
+ EHCI_QTD_GET_IOC(s), EHCI_QTD_GET_C_PAGE(s));
+ printf(" cerr=%d pid=%d stat=%s%s%s%s%s%s%s%s\n",
+ EHCI_QTD_GET_CERR(s), EHCI_QTD_GET_PID(s),
+ (s & EHCI_QTD_ACTIVE) ? "ACTIVE" : "NOT_ACTIVE",
+ (s & EHCI_QTD_HALTED) ? "-HALTED" : "",
+ (s & EHCI_QTD_BUFERR) ? "-BUFERR" : "",
+ (s & EHCI_QTD_BABBLE) ? "-BABBLE" : "",
+ (s & EHCI_QTD_XACTERR) ? "-XACTERR" : "",
+ (s & EHCI_QTD_MISSEDMICRO) ? "-MISSED" : "",
+ (s & EHCI_QTD_SPLITXSTATE) ? "-SPLIT" : "",
+ (s & EHCI_QTD_PINGSTATE) ? "-PING" : "");
+
+ for (s = 0; s < 5; s++) {
+ printf(" buffer[%d]=0x%08x\n", s,
+ ehci32toh(sc, qtd->qtd_buffer[s]));
+ }
+ for (s = 0; s < 5; s++) {
+ printf(" buffer_hi[%d]=0x%08x\n", s,
+ ehci32toh(sc, qtd->qtd_buffer_hi[s]));
+ }
+}
+
+static uint8_t
+ehci_dump_sqtd(ehci_softc_t *sc, ehci_qtd_t *sqtd)
+{
+ uint8_t temp;
+
+ usb2_pc_cpu_invalidate(sqtd->page_cache);
+ printf("QTD(%p) at 0x%08x:\n", sqtd, ehci32toh(sc, sqtd->qtd_self));
+ ehci_dump_qtd(sc, sqtd);
+ temp = (sqtd->qtd_next & htoehci32(sc, EHCI_LINK_TERMINATE)) ? 1 : 0;
+ return (temp);
+}
+
+static void
+ehci_dump_sqtds(ehci_softc_t *sc, ehci_qtd_t *sqtd)
+{
+ uint16_t i;
+ uint8_t stop;
+
+ stop = 0;
+ for (i = 0; sqtd && (i < 20) && !stop; sqtd = sqtd->obj_next, i++) {
+ stop = ehci_dump_sqtd(sc, sqtd);
+ }
+ if (sqtd) {
+ printf("dump aborted, too many TDs\n");
+ }
+}
+
+static void
+ehci_dump_sqh(ehci_softc_t *sc, ehci_qh_t *qh)
+{
+ uint32_t endp;
+ uint32_t endphub;
+
+ usb2_pc_cpu_invalidate(qh->page_cache);
+ printf("QH(%p) at 0x%08x:\n", qh, ehci32toh(sc, qh->qh_self) & ~0x1F);
+ printf(" link=");
+ ehci_dump_link(sc, qh->qh_link, 1);
+ printf("\n");
+ endp = ehci32toh(sc, qh->qh_endp);
+ printf(" endp=0x%08x\n", endp);
+ printf(" addr=0x%02x inact=%d endpt=%d eps=%d dtc=%d hrecl=%d\n",
+ EHCI_QH_GET_ADDR(endp), EHCI_QH_GET_INACT(endp),
+ EHCI_QH_GET_ENDPT(endp), EHCI_QH_GET_EPS(endp),
+ EHCI_QH_GET_DTC(endp), EHCI_QH_GET_HRECL(endp));
+ printf(" mpl=0x%x ctl=%d nrl=%d\n",
+ EHCI_QH_GET_MPL(endp), EHCI_QH_GET_CTL(endp),
+ EHCI_QH_GET_NRL(endp));
+ endphub = ehci32toh(sc, qh->qh_endphub);
+ printf(" endphub=0x%08x\n", endphub);
+ printf(" smask=0x%02x cmask=0x%02x huba=0x%02x port=%d mult=%d\n",
+ EHCI_QH_GET_SMASK(endphub), EHCI_QH_GET_CMASK(endphub),
+ EHCI_QH_GET_HUBA(endphub), EHCI_QH_GET_PORT(endphub),
+ EHCI_QH_GET_MULT(endphub));
+ printf(" curqtd=");
+ ehci_dump_link(sc, qh->qh_curqtd, 0);
+ printf("\n");
+ printf("Overlay qTD:\n");
+ ehci_dump_qtd(sc, (void *)&qh->qh_qtd);
+}
+
+static void
+ehci_dump_sitd(ehci_softc_t *sc, ehci_sitd_t *sitd)
+{
+ usb2_pc_cpu_invalidate(sitd->page_cache);
+ printf("SITD(%p) at 0x%08x\n", sitd, ehci32toh(sc, sitd->sitd_self) & ~0x1F);
+ printf(" next=0x%08x\n", ehci32toh(sc, sitd->sitd_next));
+ printf(" portaddr=0x%08x dir=%s addr=%d endpt=0x%x port=0x%x huba=0x%x\n",
+ ehci32toh(sc, sitd->sitd_portaddr),
+ (sitd->sitd_portaddr & htoehci32(sc, EHCI_SITD_SET_DIR_IN))
+ ? "in" : "out",
+ EHCI_SITD_GET_ADDR(ehci32toh(sc, sitd->sitd_portaddr)),
+ EHCI_SITD_GET_ENDPT(ehci32toh(sc, sitd->sitd_portaddr)),
+ EHCI_SITD_GET_PORT(ehci32toh(sc, sitd->sitd_portaddr)),
+ EHCI_SITD_GET_HUBA(ehci32toh(sc, sitd->sitd_portaddr)));
+ printf(" mask=0x%08x\n", ehci32toh(sc, sitd->sitd_mask));
+ printf(" status=0x%08x <%s> len=0x%x\n", ehci32toh(sc, sitd->sitd_status),
+ (sitd->sitd_status & htoehci32(sc, EHCI_SITD_ACTIVE)) ? "ACTIVE" : "",
+ EHCI_SITD_GET_LEN(ehci32toh(sc, sitd->sitd_status)));
+ printf(" back=0x%08x, bp=0x%08x,0x%08x,0x%08x,0x%08x\n",
+ ehci32toh(sc, sitd->sitd_back),
+ ehci32toh(sc, sitd->sitd_bp[0]),
+ ehci32toh(sc, sitd->sitd_bp[1]),
+ ehci32toh(sc, sitd->sitd_bp_hi[0]),
+ ehci32toh(sc, sitd->sitd_bp_hi[1]));
+}
+
+static void
+ehci_dump_itd(ehci_softc_t *sc, ehci_itd_t *itd)
+{
+ usb2_pc_cpu_invalidate(itd->page_cache);
+ printf("ITD(%p) at 0x%08x\n", itd, ehci32toh(sc, itd->itd_self) & ~0x1F);
+ printf(" next=0x%08x\n", ehci32toh(sc, itd->itd_next));
+ printf(" status[0]=0x%08x; <%s>\n", ehci32toh(sc, itd->itd_status[0]),
+ (itd->itd_status[0] & htoehci32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[1]=0x%08x; <%s>\n", ehci32toh(sc, itd->itd_status[1]),
+ (itd->itd_status[1] & htoehci32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[2]=0x%08x; <%s>\n", ehci32toh(sc, itd->itd_status[2]),
+ (itd->itd_status[2] & htoehci32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[3]=0x%08x; <%s>\n", ehci32toh(sc, itd->itd_status[3]),
+ (itd->itd_status[3] & htoehci32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[4]=0x%08x; <%s>\n", ehci32toh(sc, itd->itd_status[4]),
+ (itd->itd_status[4] & htoehci32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[5]=0x%08x; <%s>\n", ehci32toh(sc, itd->itd_status[5]),
+ (itd->itd_status[5] & htoehci32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[6]=0x%08x; <%s>\n", ehci32toh(sc, itd->itd_status[6]),
+ (itd->itd_status[6] & htoehci32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[7]=0x%08x; <%s>\n", ehci32toh(sc, itd->itd_status[7]),
+ (itd->itd_status[7] & htoehci32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" bp[0]=0x%08x\n", ehci32toh(sc, itd->itd_bp[0]));
+ printf(" addr=0x%02x; endpt=0x%01x\n",
+ EHCI_ITD_GET_ADDR(ehci32toh(sc, itd->itd_bp[0])),
+ EHCI_ITD_GET_ENDPT(ehci32toh(sc, itd->itd_bp[0])));
+ printf(" bp[1]=0x%08x\n", ehci32toh(sc, itd->itd_bp[1]));
+ printf(" dir=%s; mpl=0x%02x\n",
+ (ehci32toh(sc, itd->itd_bp[1]) & EHCI_ITD_SET_DIR_IN) ? "in" : "out",
+ EHCI_ITD_GET_MPL(ehci32toh(sc, itd->itd_bp[1])));
+ printf(" bp[2..6]=0x%08x,0x%08x,0x%08x,0x%08x,0x%08x\n",
+ ehci32toh(sc, itd->itd_bp[2]),
+ ehci32toh(sc, itd->itd_bp[3]),
+ ehci32toh(sc, itd->itd_bp[4]),
+ ehci32toh(sc, itd->itd_bp[5]),
+ ehci32toh(sc, itd->itd_bp[6]));
+ printf(" bp_hi=0x%08x,0x%08x,0x%08x,0x%08x,\n"
+ " 0x%08x,0x%08x,0x%08x\n",
+ ehci32toh(sc, itd->itd_bp_hi[0]),
+ ehci32toh(sc, itd->itd_bp_hi[1]),
+ ehci32toh(sc, itd->itd_bp_hi[2]),
+ ehci32toh(sc, itd->itd_bp_hi[3]),
+ ehci32toh(sc, itd->itd_bp_hi[4]),
+ ehci32toh(sc, itd->itd_bp_hi[5]),
+ ehci32toh(sc, itd->itd_bp_hi[6]));
+}
+
+static void
+ehci_dump_isoc(ehci_softc_t *sc)
+{
+ ehci_itd_t *itd;
+ ehci_sitd_t *sitd;
+ uint16_t max = 1000;
+ uint16_t pos;
+
+ pos = (EOREAD4(sc, EHCI_FRINDEX) / 8) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+
+ printf("%s: isochronous dump from frame 0x%03x:\n",
+ __FUNCTION__, pos);
+
+ itd = sc->sc_isoc_hs_p_last[pos];
+ sitd = sc->sc_isoc_fs_p_last[pos];
+
+ while (itd && max && max--) {
+ ehci_dump_itd(sc, itd);
+ itd = itd->prev;
+ }
+
+ while (sitd && max && max--) {
+ ehci_dump_sitd(sc, sitd);
+ sitd = sitd->prev;
+ }
+}
+
+#endif
+
+static void
+ehci_transfer_intr_enqueue(struct usb2_xfer *xfer)
+{
+ /* check for early completion */
+ if (ehci_check_transfer(xfer)) {
+ return;
+ }
+ /* put transfer on interrupt queue */
+ usb2_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usb2_transfer_timeout_ms(xfer, &ehci_timeout, xfer->timeout);
+ }
+}
+
+#define EHCI_APPEND_FS_TD(std,last) (last) = _ehci_append_fs_td(std,last)
+static ehci_sitd_t *
+_ehci_append_fs_td(ehci_sitd_t *std, ehci_sitd_t *last)
+{
+ DPRINTFN(11, "%p to %p\n", std, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ std->next = last->next;
+ std->sitd_next = last->sitd_next;
+
+ std->prev = last;
+
+ usb2_pc_cpu_flush(std->page_cache);
+
+ /*
+ * the last->next->prev is never followed: std->next->prev = std;
+ */
+ last->next = std;
+ last->sitd_next = std->sitd_self;
+
+ usb2_pc_cpu_flush(last->page_cache);
+
+ return (std);
+}
+
+#define EHCI_APPEND_HS_TD(std,last) (last) = _ehci_append_hs_td(std,last)
+static ehci_itd_t *
+_ehci_append_hs_td(ehci_itd_t *std, ehci_itd_t *last)
+{
+ DPRINTFN(11, "%p to %p\n", std, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ std->next = last->next;
+ std->itd_next = last->itd_next;
+
+ std->prev = last;
+
+ usb2_pc_cpu_flush(std->page_cache);
+
+ /*
+ * the last->next->prev is never followed: std->next->prev = std;
+ */
+ last->next = std;
+ last->itd_next = std->itd_self;
+
+ usb2_pc_cpu_flush(last->page_cache);
+
+ return (std);
+}
+
+#define EHCI_APPEND_QH(sqh,last) (last) = _ehci_append_qh(sqh,last)
+static ehci_qh_t *
+_ehci_append_qh(ehci_qh_t *sqh, ehci_qh_t *last)
+{
+ DPRINTFN(11, "%p to %p\n", sqh, last);
+
+ if (sqh->prev != NULL) {
+ /* should not happen */
+ DPRINTFN(0, "QH already linked!\n");
+ return (last);
+ }
+ /* (sc->sc_bus.mtx) must be locked */
+
+ sqh->next = last->next;
+ sqh->qh_link = last->qh_link;
+
+ sqh->prev = last;
+
+ usb2_pc_cpu_flush(sqh->page_cache);
+
+ /*
+ * the last->next->prev is never followed: sqh->next->prev = sqh;
+ */
+
+ last->next = sqh;
+ last->qh_link = sqh->qh_self;
+
+ usb2_pc_cpu_flush(last->page_cache);
+
+ return (sqh);
+}
+
+#define EHCI_REMOVE_FS_TD(std,last) (last) = _ehci_remove_fs_td(std,last)
+static ehci_sitd_t *
+_ehci_remove_fs_td(ehci_sitd_t *std, ehci_sitd_t *last)
+{
+ DPRINTFN(11, "%p from %p\n", std, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ std->prev->next = std->next;
+ std->prev->sitd_next = std->sitd_next;
+
+ usb2_pc_cpu_flush(std->prev->page_cache);
+
+ if (std->next) {
+ std->next->prev = std->prev;
+ usb2_pc_cpu_flush(std->next->page_cache);
+ }
+ return ((last == std) ? std->prev : last);
+}
+
+#define EHCI_REMOVE_HS_TD(std,last) (last) = _ehci_remove_hs_td(std,last)
+static ehci_itd_t *
+_ehci_remove_hs_td(ehci_itd_t *std, ehci_itd_t *last)
+{
+ DPRINTFN(11, "%p from %p\n", std, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ std->prev->next = std->next;
+ std->prev->itd_next = std->itd_next;
+
+ usb2_pc_cpu_flush(std->prev->page_cache);
+
+ if (std->next) {
+ std->next->prev = std->prev;
+ usb2_pc_cpu_flush(std->next->page_cache);
+ }
+ return ((last == std) ? std->prev : last);
+}
+
+#define EHCI_REMOVE_QH(sqh,last) (last) = _ehci_remove_qh(sqh,last)
+static ehci_qh_t *
+_ehci_remove_qh(ehci_qh_t *sqh, ehci_qh_t *last)
+{
+ DPRINTFN(11, "%p from %p\n", sqh, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ /* only remove if not removed from a queue */
+ if (sqh->prev) {
+
+ sqh->prev->next = sqh->next;
+ sqh->prev->qh_link = sqh->qh_link;
+
+ usb2_pc_cpu_flush(sqh->prev->page_cache);
+
+ if (sqh->next) {
+ sqh->next->prev = sqh->prev;
+ usb2_pc_cpu_flush(sqh->next->page_cache);
+ }
+ last = ((last == sqh) ? sqh->prev : last);
+
+ sqh->prev = 0;
+
+ usb2_pc_cpu_flush(sqh->page_cache);
+ }
+ return (last);
+}
+
+static usb2_error_t
+ehci_non_isoc_done_sub(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ ehci_qtd_t *td;
+ ehci_qtd_t *td_alt_next;
+ uint32_t status;
+ uint16_t len;
+
+ td = xfer->td_transfer_cache;
+ td_alt_next = td->alt_next;
+
+ if (xfer->aframes != xfer->nframes) {
+ xfer->frlengths[xfer->aframes] = 0;
+ }
+ while (1) {
+
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status = ehci32toh(sc, td->qtd_status);
+
+ len = EHCI_QTD_GET_BYTES(status);
+
+ /*
+ * Verify the status length and
+ * add the length to "frlengths[]":
+ */
+ if (len > td->len) {
+ /* should not happen */
+ DPRINTF("Invalid status length, "
+ "0x%04x/0x%04x bytes\n", len, td->len);
+ status |= EHCI_QTD_HALTED;
+ } else if (xfer->aframes != xfer->nframes) {
+ xfer->frlengths[xfer->aframes] += td->len - len;
+ }
+ /* Check for last transfer */
+ if (((void *)td) == xfer->td_transfer_last) {
+ if (len == 0) {
+ /*
+ * Halt is ok if descriptor is last,
+ * and complete:
+ */
+ status &= ~EHCI_QTD_HALTED;
+ }
+ td = NULL;
+ break;
+ }
+ /* Check for transfer error */
+ if (status & EHCI_QTD_HALTED) {
+ /* the transfer is finished */
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (len > 0) {
+ if (xfer->flags_int.short_frames_ok) {
+ /* follow alt next */
+ td = td->alt_next;
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ break;
+ }
+ td = td->obj_next;
+
+ if (td->alt_next != td_alt_next) {
+ /* this USB frame is complete */
+ break;
+ }
+ }
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ /* update data toggle */
+
+ xfer->pipe->toggle_next =
+ (status & EHCI_QTD_TOGGLE_MASK) ? 1 : 0;
+
+#if USB_DEBUG
+ if (status & EHCI_QTD_STATERRS) {
+ DPRINTFN(11, "error, addr=%d, endpt=0x%02x, frame=0x%02x"
+ "status=%s%s%s%s%s%s%s%s\n",
+ xfer->address, xfer->endpoint, xfer->aframes,
+ (status & EHCI_QTD_ACTIVE) ? "[ACTIVE]" : "[NOT_ACTIVE]",
+ (status & EHCI_QTD_HALTED) ? "[HALTED]" : "",
+ (status & EHCI_QTD_BUFERR) ? "[BUFERR]" : "",
+ (status & EHCI_QTD_BABBLE) ? "[BABBLE]" : "",
+ (status & EHCI_QTD_XACTERR) ? "[XACTERR]" : "",
+ (status & EHCI_QTD_MISSEDMICRO) ? "[MISSED]" : "",
+ (status & EHCI_QTD_SPLITXSTATE) ? "[SPLIT]" : "",
+ (status & EHCI_QTD_PINGSTATE) ? "[PING]" : "");
+ }
+#endif
+
+ return ((status & EHCI_QTD_HALTED) ?
+ USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+ehci_non_isoc_done(struct usb2_xfer *xfer)
+{
+ usb2_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->pipe);
+
+#if USB_DEBUG
+ if (ehcidebug > 10) {
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ ehci_dump_sqtds(sc, xfer->td_transfer_first);
+ }
+#endif
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+
+ if (xfer->flags_int.control_hdr) {
+
+ err = ehci_non_isoc_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+
+ err = ehci_non_isoc_done_sub(xfer);
+ xfer->aframes++;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ err = ehci_non_isoc_done_sub(xfer);
+ }
+done:
+ ehci_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * ehci_check_transfer
+ *
+ * Return values:
+ * 0: USB transfer is not finished
+ * Else: USB transfer is finished
+ *------------------------------------------------------------------------*/
+static uint8_t
+ehci_check_transfer(struct usb2_xfer *xfer)
+{
+ struct usb2_pipe_methods *methods = xfer->pipe->methods;
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ uint32_t status;
+
+ DPRINTFN(13, "xfer=%p checking transfer\n", xfer);
+
+ if (methods == &ehci_device_isoc_fs_methods) {
+ ehci_sitd_t *td;
+
+ /* isochronous full speed transfer */
+
+ td = xfer->td_transfer_last;
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status = ehci32toh(sc, td->sitd_status);
+
+ /* also check if first is complete */
+
+ td = xfer->td_transfer_first;
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status |= ehci32toh(sc, td->sitd_status);
+
+ if (!(status & EHCI_SITD_ACTIVE)) {
+ ehci_device_done(xfer, USB_ERR_NORMAL_COMPLETION);
+ goto transferred;
+ }
+ } else if (methods == &ehci_device_isoc_hs_methods) {
+ ehci_itd_t *td;
+
+ /* isochronous high speed transfer */
+
+ td = xfer->td_transfer_last;
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status =
+ td->itd_status[0] | td->itd_status[1] |
+ td->itd_status[2] | td->itd_status[3] |
+ td->itd_status[4] | td->itd_status[5] |
+ td->itd_status[6] | td->itd_status[7];
+
+ /* also check first transfer */
+ td = xfer->td_transfer_first;
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status |=
+ td->itd_status[0] | td->itd_status[1] |
+ td->itd_status[2] | td->itd_status[3] |
+ td->itd_status[4] | td->itd_status[5] |
+ td->itd_status[6] | td->itd_status[7];
+
+ /* if no transactions are active we continue */
+ if (!(status & htoehci32(sc, EHCI_ITD_ACTIVE))) {
+ ehci_device_done(xfer, USB_ERR_NORMAL_COMPLETION);
+ goto transferred;
+ }
+ } else {
+ ehci_qtd_t *td;
+
+ /* non-isochronous transfer */
+
+ /*
+ * check whether there is an error somewhere in the middle,
+ * or whether there was a short packet (SPD and not ACTIVE)
+ */
+ td = xfer->td_transfer_cache;
+
+ while (1) {
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status = ehci32toh(sc, td->qtd_status);
+
+ /*
+ * if there is an active TD the transfer isn't done
+ */
+ if (status & EHCI_QTD_ACTIVE) {
+ /* update cache */
+ xfer->td_transfer_cache = td;
+ goto done;
+ }
+ /*
+ * last transfer descriptor makes the transfer done
+ */
+ if (((void *)td) == xfer->td_transfer_last) {
+ break;
+ }
+ /*
+ * any kind of error makes the transfer done
+ */
+ if (status & EHCI_QTD_HALTED) {
+ break;
+ }
+ /*
+ * if there is no alternate next transfer, a short
+ * packet also makes the transfer done
+ */
+ if (EHCI_QTD_GET_BYTES(status)) {
+ if (xfer->flags_int.short_frames_ok) {
+ /* follow alt next */
+ if (td->alt_next) {
+ td = td->alt_next;
+ continue;
+ }
+ }
+ /* transfer is done */
+ break;
+ }
+ td = td->obj_next;
+ }
+ ehci_non_isoc_done(xfer);
+ goto transferred;
+ }
+
+done:
+ DPRINTFN(13, "xfer=%p is still active\n", xfer);
+ return (0);
+
+transferred:
+ return (1);
+}
+
+static void
+ehci_pcd_enable(ehci_softc_t *sc)
+{
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ sc->sc_eintrs |= EHCI_STS_PCD;
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+
+ /* acknowledge any PCD interrupt */
+ EOWRITE4(sc, EHCI_USBSTS, EHCI_STS_PCD);
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &ehci_root_intr_done);
+}
+
+static void
+ehci_interrupt_poll(ehci_softc_t *sc)
+{
+ struct usb2_xfer *xfer;
+
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ /*
+ * check if transfer is transferred
+ */
+ if (ehci_check_transfer(xfer)) {
+ /* queue has been modified */
+ goto repeat;
+ }
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * ehci_interrupt - EHCI interrupt handler
+ *
+ * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler,
+ * hence the interrupt handler will be setup before "sc->sc_bus.bdev"
+ * is present !
+ *------------------------------------------------------------------------*/
+void
+ehci_interrupt(ehci_softc_t *sc)
+{
+ uint32_t status;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ DPRINTFN(16, "real interrupt\n");
+
+#if USB_DEBUG
+ if (ehcidebug > 15) {
+ ehci_dump_regs(sc);
+ }
+#endif
+
+ status = EHCI_STS_INTRS(EOREAD4(sc, EHCI_USBSTS));
+ if (status == 0) {
+ /* the interrupt was not for us */
+ goto done;
+ }
+ if (!(status & sc->sc_eintrs)) {
+ goto done;
+ }
+ EOWRITE4(sc, EHCI_USBSTS, status); /* acknowledge */
+
+ status &= sc->sc_eintrs;
+
+ if (status & EHCI_STS_HSE) {
+ printf("%s: unrecoverable error, "
+ "controller halted\n", __FUNCTION__);
+#if USB_DEBUG
+ ehci_dump_regs(sc);
+ ehci_dump_isoc(sc);
+#endif
+ }
+ if (status & EHCI_STS_PCD) {
+ /*
+ * Disable PCD interrupt for now, because it will be
+ * on until the port has been reset.
+ */
+ sc->sc_eintrs &= ~EHCI_STS_PCD;
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &ehci_root_intr_done);
+
+ /* do not allow RHSC interrupts > 1 per second */
+ usb2_callout_reset(&sc->sc_tmo_pcd, hz,
+ (void *)&ehci_pcd_enable, sc);
+ }
+ status &= ~(EHCI_STS_INT | EHCI_STS_ERRINT | EHCI_STS_PCD | EHCI_STS_IAA);
+
+ if (status != 0) {
+ /* block unprocessed interrupts */
+ sc->sc_eintrs &= ~status;
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+ printf("%s: blocking interrupts 0x%x\n", __FUNCTION__, status);
+ }
+ /* poll all the USB transfers */
+ ehci_interrupt_poll(sc);
+
+done:
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*
+ * called when a request does not complete
+ */
+static void
+ehci_timeout(void *arg)
+{
+ struct usb2_xfer *xfer = arg;
+
+ DPRINTF("xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ ehci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+ehci_do_poll(struct usb2_bus *bus)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ ehci_interrupt_poll(sc);
+ ehci_root_ctrl_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+ehci_setup_standard_chain_sub(struct ehci_std_temp *temp)
+{
+ struct usb2_page_search buf_res;
+ ehci_qtd_t *td;
+ ehci_qtd_t *td_next;
+ ehci_qtd_t *td_alt_next;
+ uint32_t qtd_altnext;
+ uint32_t buf_offset;
+ uint32_t average;
+ uint32_t len_old;
+ uint8_t shortpkt_old;
+ uint8_t precompute;
+
+ qtd_altnext = htoehci32(temp->sc, EHCI_LINK_TERMINATE);
+ td_alt_next = NULL;
+ buf_offset = 0;
+ shortpkt_old = temp->shortpkt;
+ len_old = temp->len;
+ precompute = 1;
+
+restart:
+
+ td = temp->td;
+ td_next = temp->td_next;
+
+ while (1) {
+
+ if (temp->len == 0) {
+
+ if (temp->shortpkt) {
+ break;
+ }
+ /* send a Zero Length Packet, ZLP, last */
+
+ temp->shortpkt = 1;
+ average = 0;
+
+ } else {
+
+ average = temp->average;
+
+ if (temp->len < average) {
+ if (temp->len % temp->max_frame_size) {
+ temp->shortpkt = 1;
+ }
+ average = temp->len;
+ }
+ }
+
+ if (td_next == NULL) {
+ panic("%s: out of EHCI transfer descriptors!", __FUNCTION__);
+ }
+ /* get next TD */
+
+ td = td_next;
+ td_next = td->obj_next;
+
+ /* check if we are pre-computing */
+
+ if (precompute) {
+
+ /* update remaining length */
+
+ temp->len -= average;
+
+ continue;
+ }
+ /* fill out current TD */
+
+ td->qtd_status =
+ temp->qtd_status |
+ htoehci32(temp->sc, EHCI_QTD_SET_BYTES(average));
+
+ if (average == 0) {
+
+ if (temp->auto_data_toggle == 0) {
+
+ /* update data toggle, ZLP case */
+
+ temp->qtd_status ^=
+ htoehci32(temp->sc, EHCI_QTD_TOGGLE_MASK);
+ }
+ td->len = 0;
+
+ td->qtd_buffer[0] = 0;
+ td->qtd_buffer_hi[0] = 0;
+
+ td->qtd_buffer[1] = 0;
+ td->qtd_buffer_hi[1] = 0;
+
+ } else {
+
+ uint8_t x;
+
+ if (temp->auto_data_toggle == 0) {
+
+ /* update data toggle */
+
+ if (((average + temp->max_frame_size - 1) /
+ temp->max_frame_size) & 1) {
+ temp->qtd_status ^=
+ htoehci32(temp->sc, EHCI_QTD_TOGGLE_MASK);
+ }
+ }
+ td->len = average;
+
+ /* update remaining length */
+
+ temp->len -= average;
+
+ /* fill out buffer pointers */
+
+ usb2_get_page(temp->pc, buf_offset, &buf_res);
+ td->qtd_buffer[0] =
+ htoehci32(temp->sc, buf_res.physaddr);
+ td->qtd_buffer_hi[0] = 0;
+
+ x = 1;
+
+ while (average > EHCI_PAGE_SIZE) {
+ average -= EHCI_PAGE_SIZE;
+ buf_offset += EHCI_PAGE_SIZE;
+ usb2_get_page(temp->pc, buf_offset, &buf_res);
+ td->qtd_buffer[x] =
+ htoehci32(temp->sc,
+ buf_res.physaddr & (~0xFFF));
+ td->qtd_buffer_hi[x] = 0;
+ x++;
+ }
+
+ /*
+ * NOTE: The "average" variable is never zero after
+ * exiting the loop above !
+ *
+ * NOTE: We have to subtract one from the offset to
+ * ensure that we are computing the physical address
+ * of a valid page !
+ */
+ buf_offset += average;
+ usb2_get_page(temp->pc, buf_offset - 1, &buf_res);
+ td->qtd_buffer[x] =
+ htoehci32(temp->sc,
+ buf_res.physaddr & (~0xFFF));
+ td->qtd_buffer_hi[x] = 0;
+ }
+
+ if (td_next) {
+ /* link the current TD with the next one */
+ td->qtd_next = td_next->qtd_self;
+ }
+ td->qtd_altnext = qtd_altnext;
+ td->alt_next = td_alt_next;
+
+ usb2_pc_cpu_flush(td->page_cache);
+ }
+
+ if (precompute) {
+ precompute = 0;
+
+ /* setup alt next pointer, if any */
+ if (temp->short_frames_ok) {
+ if (temp->setup_alt_next) {
+ td_alt_next = td_next;
+ qtd_altnext = td_next->qtd_self;
+ }
+ } else {
+ /* we use this field internally */
+ td_alt_next = td_next;
+ }
+
+ /* restore */
+ temp->shortpkt = shortpkt_old;
+ temp->len = len_old;
+ goto restart;
+ }
+ temp->td = td;
+ temp->td_next = td_next;
+}
+
+static void
+ehci_setup_standard_chain(struct usb2_xfer *xfer, ehci_qh_t **qh_last)
+{
+ struct ehci_std_temp temp;
+ struct usb2_pipe_methods *methods;
+ ehci_qh_t *qh;
+ ehci_qtd_t *td;
+ uint32_t qh_endp;
+ uint32_t qh_endphub;
+ uint32_t x;
+
+ DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpoint),
+ xfer->sumlen, usb2_get_speed(xfer->xroot->udev));
+
+ temp.average = xfer->max_usb2_frame_size;
+ temp.max_frame_size = xfer->max_frame_size;
+ temp.sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ temp.td = NULL;
+ temp.td_next = td;
+ temp.qtd_status = 0;
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+ temp.short_frames_ok = xfer->flags_int.short_frames_ok;
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->pipe->toggle_next) {
+ /* DATA1 is next */
+ temp.qtd_status |=
+ htoehci32(temp.sc, EHCI_QTD_SET_TOGGLE(1));
+ }
+ temp.auto_data_toggle = 0;
+ } else {
+ temp.auto_data_toggle = 1;
+ }
+
+ if (usb2_get_speed(xfer->xroot->udev) != USB_SPEED_HIGH) {
+ /* max 3 retries */
+ temp.qtd_status |=
+ htoehci32(temp.sc, EHCI_QTD_SET_CERR(3));
+ }
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+
+ temp.qtd_status &=
+ htoehci32(temp.sc, EHCI_QTD_SET_CERR(3));
+ temp.qtd_status |= htole32
+ (EHCI_QTD_ACTIVE |
+ EHCI_QTD_SET_PID(EHCI_QTD_PID_SETUP) |
+ EHCI_QTD_SET_TOGGLE(0));
+
+ temp.len = xfer->frlengths[0];
+ temp.pc = xfer->frbuffers + 0;
+ temp.shortpkt = temp.len ? 1 : 0;
+
+ ehci_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ while (x != xfer->nframes) {
+
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+ temp.pc = xfer->frbuffers + x;
+
+ x++;
+
+ if (x == xfer->nframes) {
+ temp.setup_alt_next = 0;
+ }
+ /* keep previous data toggle and error count */
+
+ temp.qtd_status &=
+ htoehci32(temp.sc, EHCI_QTD_SET_CERR(3) |
+ EHCI_QTD_SET_TOGGLE(1));
+
+ if (temp.len == 0) {
+
+ /* make sure that we send an USB packet */
+
+ temp.shortpkt = 0;
+
+ } else {
+
+ /* regular data transfer */
+
+ temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+ }
+
+ /* set endpoint direction */
+
+ temp.qtd_status |=
+ (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) ?
+ htoehci32(temp.sc, EHCI_QTD_ACTIVE |
+ EHCI_QTD_SET_PID(EHCI_QTD_PID_IN)) :
+ htoehci32(temp.sc, EHCI_QTD_ACTIVE |
+ EHCI_QTD_SET_PID(EHCI_QTD_PID_OUT));
+
+ ehci_setup_standard_chain_sub(&temp);
+ }
+
+ /* check if we should append a status stage */
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ /*
+ * Send a DATA1 message and invert the current endpoint
+ * direction.
+ */
+
+ temp.qtd_status &= htoehci32(temp.sc, EHCI_QTD_SET_CERR(3) |
+ EHCI_QTD_SET_TOGGLE(1));
+ temp.qtd_status |=
+ (UE_GET_DIR(xfer->endpoint) == UE_DIR_OUT) ?
+ htoehci32(temp.sc, EHCI_QTD_ACTIVE |
+ EHCI_QTD_SET_PID(EHCI_QTD_PID_IN) |
+ EHCI_QTD_SET_TOGGLE(1)) :
+ htoehci32(temp.sc, EHCI_QTD_ACTIVE |
+ EHCI_QTD_SET_PID(EHCI_QTD_PID_OUT) |
+ EHCI_QTD_SET_TOGGLE(1));
+
+ temp.len = 0;
+ temp.pc = NULL;
+ temp.shortpkt = 0;
+
+ ehci_setup_standard_chain_sub(&temp);
+ }
+ td = temp.td;
+
+ /* the last TD terminates the transfer: */
+ td->qtd_next = htoehci32(temp.sc, EHCI_LINK_TERMINATE);
+ td->qtd_altnext = htoehci32(temp.sc, EHCI_LINK_TERMINATE);
+ td->qtd_status |= htoehci32(temp.sc, EHCI_QTD_IOC);
+
+ usb2_pc_cpu_flush(td->page_cache);
+
+ /* must have at least one frame! */
+
+ xfer->td_transfer_last = td;
+
+#if USB_DEBUG
+ if (ehcidebug > 8) {
+ DPRINTF("nexttog=%d; data before transfer:\n",
+ xfer->pipe->toggle_next);
+ ehci_dump_sqtds(temp.sc,
+ xfer->td_transfer_first);
+ }
+#endif
+
+ methods = xfer->pipe->methods;
+
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ /* the "qh_link" field is filled when the QH is added */
+
+ qh_endp =
+ (EHCI_QH_SET_ADDR(xfer->address) |
+ EHCI_QH_SET_ENDPT(UE_GET_ADDR(xfer->endpoint)) |
+ EHCI_QH_SET_MPL(xfer->max_packet_size));
+
+ if (usb2_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) {
+ qh_endp |= (EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH) |
+ EHCI_QH_DTC);
+ if (methods != &ehci_device_intr_methods)
+ qh_endp |= EHCI_QH_SET_NRL(8);
+ } else {
+
+ if (usb2_get_speed(xfer->xroot->udev) == USB_SPEED_FULL) {
+ qh_endp |= (EHCI_QH_SET_EPS(EHCI_QH_SPEED_FULL) |
+ EHCI_QH_DTC);
+ } else {
+ qh_endp |= (EHCI_QH_SET_EPS(EHCI_QH_SPEED_LOW) |
+ EHCI_QH_DTC);
+ }
+
+ if (methods == &ehci_device_ctrl_methods) {
+ qh_endp |= EHCI_QH_CTL;
+ }
+ if (methods != &ehci_device_intr_methods) {
+ /* Only try one time per microframe! */
+ qh_endp |= EHCI_QH_SET_NRL(1);
+ }
+ }
+
+ qh->qh_endp = htoehci32(temp.sc, qh_endp);
+
+ qh_endphub =
+ (EHCI_QH_SET_MULT(xfer->max_packet_count & 3) |
+ EHCI_QH_SET_CMASK(xfer->usb2_cmask) |
+ EHCI_QH_SET_SMASK(xfer->usb2_smask) |
+ EHCI_QH_SET_HUBA(xfer->xroot->udev->hs_hub_addr) |
+ EHCI_QH_SET_PORT(xfer->xroot->udev->hs_port_no));
+
+ qh->qh_endphub = htoehci32(temp.sc, qh_endphub);
+ qh->qh_curqtd = htoehci32(temp.sc, 0);
+
+ /* fill the overlay qTD */
+ qh->qh_qtd.qtd_status = htoehci32(temp.sc, 0);
+
+ if (temp.auto_data_toggle) {
+
+ /* let the hardware compute the data toggle */
+
+ qh->qh_endp &= htoehci32(temp.sc, ~EHCI_QH_DTC);
+
+ if (xfer->pipe->toggle_next) {
+ /* DATA1 is next */
+ qh->qh_qtd.qtd_status |=
+ htoehci32(temp.sc, EHCI_QTD_SET_TOGGLE(1));
+ }
+ }
+ td = xfer->td_transfer_first;
+
+ qh->qh_qtd.qtd_next = td->qtd_self;
+ qh->qh_qtd.qtd_altnext =
+ htoehci32(temp.sc, EHCI_LINK_TERMINATE);
+
+ usb2_pc_cpu_flush(qh->page_cache);
+
+ if (xfer->xroot->udev->pwr_save.suspended == 0) {
+ EHCI_APPEND_QH(qh, *qh_last);
+ }
+}
+
+static void
+ehci_root_intr_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ uint16_t i;
+ uint16_t m;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_PRE_DATA) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ ehci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* setup buffer */
+ std->ptr = sc->sc_hub_idata;
+ std->len = sizeof(sc->sc_hub_idata);
+
+ /* clear any old interrupt data */
+ bzero(sc->sc_hub_idata, sizeof(sc->sc_hub_idata));
+
+ /* set bits */
+ m = (sc->sc_noport + 1);
+ if (m > (8 * sizeof(sc->sc_hub_idata))) {
+ m = (8 * sizeof(sc->sc_hub_idata));
+ }
+ for (i = 1; i < m; i++) {
+ /* pick out CHANGE bits from the status register */
+ if (EOREAD4(sc, EHCI_PORTSC(i)) & EHCI_PS_CLEAR) {
+ sc->sc_hub_idata[i / 8] |= 1 << (i % 8);
+ DPRINTF("port %d changed\n", i);
+ }
+ }
+done:
+ return;
+}
+
+static void
+ehci_isoc_fs_done(ehci_softc_t *sc, struct usb2_xfer *xfer)
+{
+ uint32_t nframes = xfer->nframes;
+ uint32_t status;
+ uint32_t *plen = xfer->frlengths;
+ uint16_t len = 0;
+ ehci_sitd_t *td = xfer->td_transfer_first;
+ ehci_sitd_t **pp_last = &sc->sc_isoc_fs_p_last[xfer->qh_pos];
+
+ DPRINTFN(13, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->pipe);
+
+ while (nframes--) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ if (pp_last >= &sc->sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) {
+ pp_last = &sc->sc_isoc_fs_p_last[0];
+ }
+#if USB_DEBUG
+ if (ehcidebug > 15) {
+ DPRINTF("isoc FS-TD\n");
+ ehci_dump_sitd(sc, td);
+ }
+#endif
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status = ehci32toh(sc, td->sitd_status);
+
+ len = EHCI_SITD_GET_LEN(status);
+
+ if (*plen >= len) {
+ len = *plen - len;
+ } else {
+ len = 0;
+ }
+
+ *plen = len;
+
+ /* remove FS-TD from schedule */
+ EHCI_REMOVE_FS_TD(td, *pp_last);
+
+ pp_last++;
+ plen++;
+ td = td->obj_next;
+ }
+
+ xfer->aframes = xfer->nframes;
+}
+
+static void
+ehci_isoc_hs_done(ehci_softc_t *sc, struct usb2_xfer *xfer)
+{
+ uint32_t nframes = xfer->nframes;
+ uint32_t status;
+ uint32_t *plen = xfer->frlengths;
+ uint16_t len = 0;
+ uint8_t td_no = 0;
+ ehci_itd_t *td = xfer->td_transfer_first;
+ ehci_itd_t **pp_last = &sc->sc_isoc_hs_p_last[xfer->qh_pos];
+
+ DPRINTFN(13, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->pipe);
+
+ while (nframes--) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ if (pp_last >= &sc->sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) {
+ pp_last = &sc->sc_isoc_hs_p_last[0];
+ }
+#if USB_DEBUG
+ if (ehcidebug > 15) {
+ DPRINTF("isoc HS-TD\n");
+ ehci_dump_itd(sc, td);
+ }
+#endif
+
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status = ehci32toh(sc, td->itd_status[td_no]);
+
+ len = EHCI_ITD_GET_LEN(status);
+
+ if (*plen >= len) {
+ /*
+ * The length is valid. NOTE: The complete
+ * length is written back into the status
+ * field, and not the remainder like with
+ * other transfer descriptor types.
+ */
+ } else {
+ /* Invalid length - truncate */
+ len = 0;
+ }
+
+ *plen = len;
+
+ plen++;
+ td_no++;
+
+ if ((td_no == 8) || (nframes == 0)) {
+ /* remove HS-TD from schedule */
+ EHCI_REMOVE_HS_TD(td, *pp_last);
+ pp_last++;
+
+ td_no = 0;
+ td = td->obj_next;
+ }
+ }
+ xfer->aframes = xfer->nframes;
+}
+
+/* NOTE: "done" can be run two times in a row,
+ * from close and from interrupt
+ */
+static void
+ehci_device_done(struct usb2_xfer *xfer, usb2_error_t error)
+{
+ struct usb2_pipe_methods *methods = xfer->pipe->methods;
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ DPRINTFN(2, "xfer=%p, pipe=%p, error=%d\n",
+ xfer, xfer->pipe, error);
+
+ if ((methods == &ehci_device_bulk_methods) ||
+ (methods == &ehci_device_ctrl_methods)) {
+#if USB_DEBUG
+ if (ehcidebug > 8) {
+ DPRINTF("nexttog=%d; data after transfer:\n",
+ xfer->pipe->toggle_next);
+ ehci_dump_sqtds(sc,
+ xfer->td_transfer_first);
+ }
+#endif
+
+ EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_async_p_last);
+ }
+ if (methods == &ehci_device_intr_methods) {
+ EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ /*
+ * Only finish isochronous transfers once which will update
+ * "xfer->frlengths".
+ */
+ if (xfer->td_transfer_first &&
+ xfer->td_transfer_last) {
+ if (methods == &ehci_device_isoc_fs_methods) {
+ ehci_isoc_fs_done(sc, xfer);
+ }
+ if (methods == &ehci_device_isoc_hs_methods) {
+ ehci_isoc_hs_done(sc, xfer);
+ }
+ xfer->td_transfer_first = NULL;
+ xfer->td_transfer_last = NULL;
+ }
+ /* dequeue transfer and start next transfer */
+ usb2_transfer_done(xfer, error);
+}
+
+/*------------------------------------------------------------------------*
+ * ehci bulk support
+ *------------------------------------------------------------------------*/
+static void
+ehci_device_bulk_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_device_bulk_close(struct usb2_xfer *xfer)
+{
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ehci_device_bulk_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_device_bulk_start(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ehci_setup_standard_chain(xfer, &sc->sc_async_p_last);
+
+ /* put transfer on interrupt queue */
+ ehci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods ehci_device_bulk_methods =
+{
+ .open = ehci_device_bulk_open,
+ .close = ehci_device_bulk_close,
+ .enter = ehci_device_bulk_enter,
+ .start = ehci_device_bulk_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * ehci control support
+ *------------------------------------------------------------------------*/
+static void
+ehci_device_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_device_ctrl_close(struct usb2_xfer *xfer)
+{
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ehci_device_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_device_ctrl_start(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ehci_setup_standard_chain(xfer, &sc->sc_async_p_last);
+
+ /* put transfer on interrupt queue */
+ ehci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods ehci_device_ctrl_methods =
+{
+ .open = ehci_device_ctrl_open,
+ .close = ehci_device_ctrl_close,
+ .enter = ehci_device_ctrl_enter,
+ .start = ehci_device_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * ehci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+ehci_device_intr_open(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ uint16_t best;
+ uint16_t bit;
+ uint16_t x;
+ uint8_t slot;
+
+ /* Allocate a microframe slot first: */
+
+ slot = usb2_intr_schedule_adjust
+ (xfer->xroot->udev, xfer->max_frame_size, USB_HS_MICRO_FRAMES_MAX);
+
+ if (usb2_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) {
+ xfer->usb2_uframe = slot;
+ xfer->usb2_smask = (1 << slot) & 0xFF;
+ xfer->usb2_cmask = 0;
+ } else {
+ xfer->usb2_uframe = slot;
+ xfer->usb2_smask = (1 << slot) & 0x3F;
+ xfer->usb2_cmask = (-(4 << slot)) & 0xFE;
+ }
+
+ /*
+ * Find the best QH position corresponding to the given interval:
+ */
+
+ best = 0;
+ bit = EHCI_VIRTUAL_FRAMELIST_COUNT / 2;
+ while (bit) {
+ if (xfer->interval >= bit) {
+ x = bit;
+ best = bit;
+ while (x & bit) {
+ if (sc->sc_intr_stat[x] <
+ sc->sc_intr_stat[best]) {
+ best = x;
+ }
+ x++;
+ }
+ break;
+ }
+ bit >>= 1;
+ }
+
+ sc->sc_intr_stat[best]++;
+ xfer->qh_pos = best;
+
+ DPRINTFN(3, "best=%d interval=%d\n",
+ best, xfer->interval);
+}
+
+static void
+ehci_device_intr_close(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ uint8_t slot;
+
+ slot = usb2_intr_schedule_adjust
+ (xfer->xroot->udev, -(xfer->max_frame_size), xfer->usb2_uframe);
+
+ sc->sc_intr_stat[xfer->qh_pos]--;
+
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ehci_device_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_device_intr_start(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ehci_setup_standard_chain(xfer, &sc->sc_intr_p_last[xfer->qh_pos]);
+
+ /* put transfer on interrupt queue */
+ ehci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods ehci_device_intr_methods =
+{
+ .open = ehci_device_intr_open,
+ .close = ehci_device_intr_close,
+ .enter = ehci_device_intr_enter,
+ .start = ehci_device_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * ehci full speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+ehci_device_isoc_fs_open(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ ehci_sitd_t *td;
+ uint32_t sitd_portaddr;
+ uint8_t ds;
+
+ sitd_portaddr =
+ EHCI_SITD_SET_ADDR(xfer->address) |
+ EHCI_SITD_SET_ENDPT(UE_GET_ADDR(xfer->endpoint)) |
+ EHCI_SITD_SET_HUBA(xfer->xroot->udev->hs_hub_addr) |
+ EHCI_SITD_SET_PORT(xfer->xroot->udev->hs_port_no);
+
+ if (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) {
+ sitd_portaddr |= EHCI_SITD_SET_DIR_IN;
+ }
+ sitd_portaddr = htoehci32(sc, sitd_portaddr);
+
+ /* initialize all TD's */
+
+ for (ds = 0; ds != 2; ds++) {
+
+ for (td = xfer->td_start[ds]; td; td = td->obj_next) {
+
+ td->sitd_portaddr = sitd_portaddr;
+
+ /*
+ * TODO: make some kind of automatic
+ * SMASK/CMASK selection based on micro-frame
+ * usage
+ *
+ * micro-frame usage (8 microframes per 1ms)
+ */
+ td->sitd_back = htoehci32(sc, EHCI_LINK_TERMINATE);
+
+ usb2_pc_cpu_flush(td->page_cache);
+ }
+ }
+}
+
+static void
+ehci_device_isoc_fs_close(struct usb2_xfer *xfer)
+{
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ehci_device_isoc_fs_enter(struct usb2_xfer *xfer)
+{
+ struct usb2_page_search buf_res;
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ struct usb2_fs_isoc_schedule *fss_start;
+ struct usb2_fs_isoc_schedule *fss_end;
+ struct usb2_fs_isoc_schedule *fss;
+ ehci_sitd_t *td;
+ ehci_sitd_t *td_last = NULL;
+ ehci_sitd_t **pp_last;
+ uint32_t *plen;
+ uint32_t buf_offset;
+ uint32_t nframes;
+ uint32_t temp;
+ uint32_t sitd_mask;
+ uint16_t tlen;
+ uint8_t sa;
+ uint8_t sb;
+ uint8_t error;
+
+#if USB_DEBUG
+ uint8_t once = 1;
+
+#endif
+
+ DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
+ xfer, xfer->pipe->isoc_next, xfer->nframes);
+
+ /* get the current frame index */
+
+ nframes = EOREAD4(sc, EHCI_FRINDEX) / 8;
+
+ /*
+ * check if the frame index is within the window where the frames
+ * will be inserted
+ */
+ buf_offset = (nframes - xfer->pipe->isoc_next) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+
+ if ((xfer->pipe->is_synced == 0) ||
+ (buf_offset < xfer->nframes)) {
+ /*
+ * If there is data underflow or the pipe queue is empty we
+ * schedule the transfer a few frames ahead of the current
+ * frame position. Else two isochronous transfers might
+ * overlap.
+ */
+ xfer->pipe->isoc_next = (nframes + 3) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+ xfer->pipe->is_synced = 1;
+ DPRINTFN(3, "start next=%d\n", xfer->pipe->isoc_next);
+ }
+ /*
+ * compute how many milliseconds the insertion is ahead of the
+ * current frame position:
+ */
+ buf_offset = (xfer->pipe->isoc_next - nframes) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+
+ /*
+ * pre-compute when the isochronous transfer will be finished:
+ */
+ xfer->isoc_time_complete =
+ usb2_fs_isoc_schedule_isoc_time_expand
+ (xfer->xroot->udev, &fss_start, &fss_end, nframes) + buf_offset +
+ xfer->nframes;
+
+ /* get the real number of frames */
+
+ nframes = xfer->nframes;
+
+ buf_offset = 0;
+
+ plen = xfer->frlengths;
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+ xfer->td_transfer_first = td;
+
+ pp_last = &sc->sc_isoc_fs_p_last[xfer->pipe->isoc_next];
+
+ /* store starting position */
+
+ xfer->qh_pos = xfer->pipe->isoc_next;
+
+ fss = fss_start + (xfer->qh_pos % USB_ISOC_TIME_MAX);
+
+ while (nframes--) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ if (pp_last >= &sc->sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) {
+ pp_last = &sc->sc_isoc_fs_p_last[0];
+ }
+ if (fss >= fss_end) {
+ fss = fss_start;
+ }
+ /* reuse sitd_portaddr and sitd_back from last transfer */
+
+ if (*plen > xfer->max_frame_size) {
+#if USB_DEBUG
+ if (once) {
+ once = 0;
+ printf("%s: frame length(%d) exceeds %d "
+ "bytes (frame truncated)\n",
+ __FUNCTION__, *plen,
+ xfer->max_frame_size);
+ }
+#endif
+ *plen = xfer->max_frame_size;
+ }
+ /*
+ * We currently don't care if the ISOCHRONOUS schedule is
+ * full!
+ */
+ error = usb2_fs_isoc_schedule_alloc(fss, &sa, *plen);
+ if (error) {
+ /*
+ * The FULL speed schedule is FULL! Set length
+ * to zero.
+ */
+ *plen = 0;
+ }
+ if (*plen) {
+ /*
+ * only call "usb2_get_page()" when we have a
+ * non-zero length
+ */
+ usb2_get_page(xfer->frbuffers, buf_offset, &buf_res);
+ td->sitd_bp[0] = htoehci32(sc, buf_res.physaddr);
+ buf_offset += *plen;
+ /*
+ * NOTE: We need to subtract one from the offset so
+ * that we are on a valid page!
+ */
+ usb2_get_page(xfer->frbuffers, buf_offset - 1,
+ &buf_res);
+ temp = buf_res.physaddr & ~0xFFF;
+ } else {
+ td->sitd_bp[0] = 0;
+ temp = 0;
+ }
+
+ if (UE_GET_DIR(xfer->endpoint) == UE_DIR_OUT) {
+ tlen = *plen;
+ if (tlen <= 188) {
+ temp |= 1; /* T-count = 1, TP = ALL */
+ tlen = 1;
+ } else {
+ tlen += 187;
+ tlen /= 188;
+ temp |= tlen; /* T-count = [1..6] */
+ temp |= 8; /* TP = Begin */
+ }
+
+ tlen += sa;
+
+ if (tlen >= 8) {
+ sb = 0;
+ } else {
+ sb = (1 << tlen);
+ }
+
+ sa = (1 << sa);
+ sa = (sb - sa) & 0x3F;
+ sb = 0;
+ } else {
+ sb = (-(4 << sa)) & 0xFE;
+ sa = (1 << sa) & 0x3F;
+ }
+
+ sitd_mask = (EHCI_SITD_SET_SMASK(sa) |
+ EHCI_SITD_SET_CMASK(sb));
+
+ td->sitd_bp[1] = htoehci32(sc, temp);
+
+ td->sitd_mask = htoehci32(sc, sitd_mask);
+
+ if (nframes == 0) {
+ td->sitd_status = htole32
+ (EHCI_SITD_IOC |
+ EHCI_SITD_ACTIVE |
+ EHCI_SITD_SET_LEN(*plen));
+ } else {
+ td->sitd_status = htole32
+ (EHCI_SITD_ACTIVE |
+ EHCI_SITD_SET_LEN(*plen));
+ }
+ usb2_pc_cpu_flush(td->page_cache);
+
+#if USB_DEBUG
+ if (ehcidebug > 15) {
+ DPRINTF("FS-TD %d\n", nframes);
+ ehci_dump_sitd(sc, td);
+ }
+#endif
+ /* insert TD into schedule */
+ EHCI_APPEND_FS_TD(td, *pp_last);
+ pp_last++;
+
+ plen++;
+ fss++;
+ td_last = td;
+ td = td->obj_next;
+ }
+
+ xfer->td_transfer_last = td_last;
+
+ /* update isoc_next */
+ xfer->pipe->isoc_next = (pp_last - &sc->sc_isoc_fs_p_last[0]) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+}
+
+static void
+ehci_device_isoc_fs_start(struct usb2_xfer *xfer)
+{
+ /* put transfer on interrupt queue */
+ ehci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods ehci_device_isoc_fs_methods =
+{
+ .open = ehci_device_isoc_fs_open,
+ .close = ehci_device_isoc_fs_close,
+ .enter = ehci_device_isoc_fs_enter,
+ .start = ehci_device_isoc_fs_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * ehci high speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+ehci_device_isoc_hs_open(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ ehci_itd_t *td;
+ uint32_t temp;
+ uint8_t ds;
+
+ /* initialize all TD's */
+
+ for (ds = 0; ds != 2; ds++) {
+
+ for (td = xfer->td_start[ds]; td; td = td->obj_next) {
+
+ /* set TD inactive */
+ td->itd_status[0] = 0;
+ td->itd_status[1] = 0;
+ td->itd_status[2] = 0;
+ td->itd_status[3] = 0;
+ td->itd_status[4] = 0;
+ td->itd_status[5] = 0;
+ td->itd_status[6] = 0;
+ td->itd_status[7] = 0;
+
+ /* set endpoint and address */
+ td->itd_bp[0] = htole32
+ (EHCI_ITD_SET_ADDR(xfer->address) |
+ EHCI_ITD_SET_ENDPT(UE_GET_ADDR(xfer->endpoint)));
+
+ temp =
+ EHCI_ITD_SET_MPL(xfer->max_packet_size & 0x7FF);
+
+ /* set direction */
+ if (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) {
+ temp |= EHCI_ITD_SET_DIR_IN;
+ }
+ /* set maximum packet size */
+ td->itd_bp[1] = htoehci32(sc, temp);
+
+ /* set transfer multiplier */
+ td->itd_bp[2] = htoehci32(sc, xfer->max_packet_count & 3);
+
+ usb2_pc_cpu_flush(td->page_cache);
+ }
+ }
+}
+
+static void
+ehci_device_isoc_hs_close(struct usb2_xfer *xfer)
+{
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ehci_device_isoc_hs_enter(struct usb2_xfer *xfer)
+{
+ struct usb2_page_search buf_res;
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ ehci_itd_t *td;
+ ehci_itd_t *td_last = NULL;
+ ehci_itd_t **pp_last;
+ bus_size_t page_addr;
+ uint32_t *plen;
+ uint32_t status;
+ uint32_t buf_offset;
+ uint32_t nframes;
+ uint32_t itd_offset[8 + 1];
+ uint8_t x;
+ uint8_t td_no;
+ uint8_t page_no;
+
+#if USB_DEBUG
+ uint8_t once = 1;
+
+#endif
+
+ DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
+ xfer, xfer->pipe->isoc_next, xfer->nframes);
+
+ /* get the current frame index */
+
+ nframes = EOREAD4(sc, EHCI_FRINDEX) / 8;
+
+ /*
+ * check if the frame index is within the window where the frames
+ * will be inserted
+ */
+ buf_offset = (nframes - xfer->pipe->isoc_next) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+
+ if ((xfer->pipe->is_synced == 0) ||
+ (buf_offset < ((xfer->nframes + 7) / 8))) {
+ /*
+ * If there is data underflow or the pipe queue is empty we
+ * schedule the transfer a few frames ahead of the current
+ * frame position. Else two isochronous transfers might
+ * overlap.
+ */
+ xfer->pipe->isoc_next = (nframes + 3) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+ xfer->pipe->is_synced = 1;
+ DPRINTFN(3, "start next=%d\n", xfer->pipe->isoc_next);
+ }
+ /*
+ * compute how many milliseconds the insertion is ahead of the
+ * current frame position:
+ */
+ buf_offset = (xfer->pipe->isoc_next - nframes) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+
+ /*
+ * pre-compute when the isochronous transfer will be finished:
+ */
+ xfer->isoc_time_complete =
+ usb2_isoc_time_expand(&sc->sc_bus, nframes) + buf_offset +
+ ((xfer->nframes + 7) / 8);
+
+ /* get the real number of frames */
+
+ nframes = xfer->nframes;
+
+ buf_offset = 0;
+ td_no = 0;
+
+ plen = xfer->frlengths;
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+ xfer->td_transfer_first = td;
+
+ pp_last = &sc->sc_isoc_hs_p_last[xfer->pipe->isoc_next];
+
+ /* store starting position */
+
+ xfer->qh_pos = xfer->pipe->isoc_next;
+
+ while (nframes--) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ if (pp_last >= &sc->sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) {
+ pp_last = &sc->sc_isoc_hs_p_last[0];
+ }
+ /* range check */
+ if (*plen > xfer->max_frame_size) {
+#if USB_DEBUG
+ if (once) {
+ once = 0;
+ printf("%s: frame length(%d) exceeds %d bytes "
+ "(frame truncated)\n",
+ __FUNCTION__, *plen, xfer->max_frame_size);
+ }
+#endif
+ *plen = xfer->max_frame_size;
+ }
+ status = (EHCI_ITD_SET_LEN(*plen) |
+ EHCI_ITD_ACTIVE |
+ EHCI_ITD_SET_PG(0));
+ td->itd_status[td_no] = htoehci32(sc, status);
+ itd_offset[td_no] = buf_offset;
+ buf_offset += *plen;
+ plen++;
+ td_no++;
+
+ if ((td_no == 8) || (nframes == 0)) {
+
+ /* the rest of the transfers are not active, if any */
+ for (x = td_no; x != 8; x++) {
+ td->itd_status[x] = 0; /* not active */
+ }
+
+ /* check if there is any data to be transferred */
+ if (itd_offset[0] != buf_offset) {
+ page_no = 0;
+ itd_offset[td_no] = buf_offset;
+
+ /* get first page offset */
+ usb2_get_page(xfer->frbuffers, itd_offset[0], &buf_res);
+ /* get page address */
+ page_addr = buf_res.physaddr & ~0xFFF;
+ /* update page address */
+ td->itd_bp[0] &= htoehci32(sc, 0xFFF);
+ td->itd_bp[0] |= htoehci32(sc, page_addr);
+
+ for (x = 0; x != td_no; x++) {
+ /* set page number and page offset */
+ status = (EHCI_ITD_SET_PG(page_no) |
+ (buf_res.physaddr & 0xFFF));
+ td->itd_status[x] |= htoehci32(sc, status);
+
+ /* get next page offset */
+ if (itd_offset[x + 1] == buf_offset) {
+ /*
+ * We subtract one so that
+ * we don't go off the last
+ * page!
+ */
+ usb2_get_page(xfer->frbuffers, buf_offset - 1, &buf_res);
+ } else {
+ usb2_get_page(xfer->frbuffers, itd_offset[x + 1], &buf_res);
+ }
+
+ /* check if we need a new page */
+ if ((buf_res.physaddr ^ page_addr) & ~0xFFF) {
+ /* new page needed */
+ page_addr = buf_res.physaddr & ~0xFFF;
+ if (page_no == 6) {
+ panic("%s: too many pages\n", __FUNCTION__);
+ }
+ page_no++;
+ /* update page address */
+ td->itd_bp[page_no] &= htoehci32(sc, 0xFFF);
+ td->itd_bp[page_no] |= htoehci32(sc, page_addr);
+ }
+ }
+ }
+ /* set IOC bit if we are complete */
+ if (nframes == 0) {
+ td->itd_status[7] |= htoehci32(sc, EHCI_ITD_IOC);
+ }
+ usb2_pc_cpu_flush(td->page_cache);
+#if USB_DEBUG
+ if (ehcidebug > 15) {
+ DPRINTF("HS-TD %d\n", nframes);
+ ehci_dump_itd(sc, td);
+ }
+#endif
+ /* insert TD into schedule */
+ EHCI_APPEND_HS_TD(td, *pp_last);
+ pp_last++;
+
+ td_no = 0;
+ td_last = td;
+ td = td->obj_next;
+ }
+ }
+
+ xfer->td_transfer_last = td_last;
+
+ /* update isoc_next */
+ xfer->pipe->isoc_next = (pp_last - &sc->sc_isoc_hs_p_last[0]) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+}
+
+static void
+ehci_device_isoc_hs_start(struct usb2_xfer *xfer)
+{
+ /* put transfer on interrupt queue */
+ ehci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods ehci_device_isoc_hs_methods =
+{
+ .open = ehci_device_isoc_hs_open,
+ .close = ehci_device_isoc_hs_close,
+ .enter = ehci_device_isoc_hs_enter,
+ .start = ehci_device_isoc_hs_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * ehci root control support
+ *------------------------------------------------------------------------*
+ * simulate a hardware hub by handling
+ * all the necessary requests
+ *------------------------------------------------------------------------*/
+
+static void
+ehci_root_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_root_ctrl_close(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_ctrl.xfer == xfer) {
+ sc->sc_root_ctrl.xfer = NULL;
+ }
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+/* data structures and routines
+ * to emulate the root hub:
+ */
+
+static const
+struct usb2_device_descriptor ehci_devd =
+{
+ sizeof(struct usb2_device_descriptor),
+ UDESC_DEVICE, /* type */
+ {0x00, 0x02}, /* USB version */
+ UDCLASS_HUB, /* class */
+ UDSUBCLASS_HUB, /* subclass */
+ UDPROTO_HSHUBSTT, /* protocol */
+ 64, /* max packet */
+ {0}, {0}, {0x00, 0x01}, /* device id */
+ 1, 2, 0, /* string indicies */
+ 1 /* # of configurations */
+};
+
+static const
+struct usb2_device_qualifier ehci_odevd =
+{
+ sizeof(struct usb2_device_qualifier),
+ UDESC_DEVICE_QUALIFIER, /* type */
+ {0x00, 0x02}, /* USB version */
+ UDCLASS_HUB, /* class */
+ UDSUBCLASS_HUB, /* subclass */
+ UDPROTO_FSHUB, /* protocol */
+ 0, /* max packet */
+ 0, /* # of configurations */
+ 0
+};
+
+static const struct ehci_config_desc ehci_confd = {
+ .confd = {
+ .bLength = sizeof(struct usb2_config_descriptor),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(ehci_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0 /* max power */
+ },
+
+ .ifcd = {
+ .bLength = sizeof(struct usb2_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = UIPROTO_HSHUBSTT,
+ 0
+ },
+
+ .endpd = {
+ .bLength = sizeof(struct usb2_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = UE_DIR_IN | EHCI_INTR_ENDPT,
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 8, /* max packet (63 ports) */
+ .bInterval = 255,
+ },
+};
+
+static const
+struct usb2_hub_descriptor ehci_hubd =
+{
+ 0, /* dynamic length */
+ UDESC_HUB,
+ 0,
+ {0, 0},
+ 0,
+ 0,
+ {0},
+};
+
+static void
+ehci_disown(ehci_softc_t *sc, uint16_t index, uint8_t lowspeed)
+{
+ uint32_t port;
+ uint32_t v;
+
+ DPRINTF("index=%d lowspeed=%d\n", index, lowspeed);
+
+ port = EHCI_PORTSC(index);
+ v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR;
+ EOWRITE4(sc, port, v | EHCI_PS_PO);
+}
+
+static void
+ehci_root_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_root_ctrl_start(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ DPRINTF("\n");
+
+ sc->sc_root_ctrl.xfer = xfer;
+
+ usb2_bus_roothub_exec(xfer->xroot->bus);
+}
+
+static void
+ehci_root_ctrl_task(struct usb2_bus *bus)
+{
+ ehci_root_ctrl_poll(EHCI_BUS2SC(bus));
+}
+
+static void
+ehci_root_ctrl_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ char *ptr;
+ uint32_t port;
+ uint32_t v;
+ uint16_t i;
+ uint16_t value;
+ uint16_t index;
+ uint8_t l;
+ uint8_t use_polling;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_SETUP) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ ehci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* buffer reset */
+ std->ptr = sc->sc_hub_desc.temp;
+ std->len = 0;
+
+ value = UGETW(std->req.wValue);
+ index = UGETW(std->req.wIndex);
+
+ use_polling = mtx_owned(xfer->xroot->xfer_mtx) ? 1 : 0;
+
+ DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x "
+ "wValue=0x%04x wIndex=0x%04x\n",
+ std->req.bmRequestType, std->req.bRequest,
+ UGETW(std->req.wLength), value, index);
+
+#define C(x,y) ((x) | ((y) << 8))
+ switch (C(std->req.bRequest, std->req.bmRequestType)) {
+ case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE):
+ case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE):
+ case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT):
+ /*
+ * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops
+ * for the integrated root hub.
+ */
+ break;
+ case C(UR_GET_CONFIG, UT_READ_DEVICE):
+ std->len = 1;
+ sc->sc_hub_desc.temp[0] = sc->sc_conf;
+ break;
+ case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE):
+ switch (value >> 8) {
+ case UDESC_DEVICE:
+ if ((value & 0xff) != 0) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ std->len = sizeof(ehci_devd);
+ sc->sc_hub_desc.devd = ehci_devd;
+ break;
+ /*
+ * We can't really operate at another speed,
+ * but the specification says we need this
+ * descriptor:
+ */
+ case UDESC_DEVICE_QUALIFIER:
+ if ((value & 0xff) != 0) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ std->len = sizeof(ehci_odevd);
+ sc->sc_hub_desc.odevd = ehci_odevd;
+ break;
+
+ case UDESC_CONFIG:
+ if ((value & 0xff) != 0) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ std->len = sizeof(ehci_confd);
+ std->ptr = USB_ADD_BYTES(&ehci_confd, 0);
+ break;
+
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ ptr = "\001";
+ break;
+
+ case 1: /* Vendor */
+ ptr = sc->sc_vendor;
+ break;
+
+ case 2: /* Product */
+ ptr = "EHCI root HUB";
+ break;
+
+ default:
+ ptr = "";
+ break;
+ }
+
+ std->len = usb2_make_str_desc
+ (sc->sc_hub_desc.temp,
+ sizeof(sc->sc_hub_desc.temp),
+ ptr);
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ case C(UR_GET_INTERFACE, UT_READ_INTERFACE):
+ std->len = 1;
+ sc->sc_hub_desc.temp[0] = 0;
+ break;
+ case C(UR_GET_STATUS, UT_READ_DEVICE):
+ std->len = 2;
+ USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED);
+ break;
+ case C(UR_GET_STATUS, UT_READ_INTERFACE):
+ case C(UR_GET_STATUS, UT_READ_ENDPOINT):
+ std->len = 2;
+ USETW(sc->sc_hub_desc.stat.wStatus, 0);
+ break;
+ case C(UR_SET_ADDRESS, UT_WRITE_DEVICE):
+ if (value >= USB_MAX_DEVICES) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ sc->sc_addr = value;
+ break;
+ case C(UR_SET_CONFIG, UT_WRITE_DEVICE):
+ if ((value != 0) && (value != 1)) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ sc->sc_conf = value;
+ break;
+ case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE):
+ break;
+ case C(UR_SET_FEATURE, UT_WRITE_DEVICE):
+ case C(UR_SET_FEATURE, UT_WRITE_INTERFACE):
+ case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT):
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE):
+ break;
+ case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT):
+ break;
+ /* Hub requests */
+ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE):
+ break;
+ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER):
+ DPRINTFN(9, "UR_CLEAR_PORT_FEATURE\n");
+
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ port = EHCI_PORTSC(index);
+ v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR;
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ EOWRITE4(sc, port, v & ~EHCI_PS_PE);
+ break;
+ case UHF_PORT_SUSPEND:
+ if ((v & EHCI_PS_SUSP) && (!(v & EHCI_PS_FPR))) {
+
+ /*
+ * waking up a High Speed device is rather
+ * complicated if
+ */
+ EOWRITE4(sc, port, v | EHCI_PS_FPR);
+ }
+ /* wait 20ms for resume sequence to complete */
+ if (use_polling) {
+ /* polling */
+ DELAY(20000);
+ } else {
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50);
+ }
+
+ EOWRITE4(sc, port, v & ~(EHCI_PS_SUSP |
+ EHCI_PS_FPR | (3 << 10) /* High Speed */ ));
+
+ /* settle time */
+ if (use_polling) {
+ /* polling */
+ DELAY(4000);
+ } else {
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 250);
+ }
+ break;
+ case UHF_PORT_POWER:
+ EOWRITE4(sc, port, v & ~EHCI_PS_PP);
+ break;
+ case UHF_PORT_TEST:
+ DPRINTFN(3, "clear port test "
+ "%d\n", index);
+ break;
+ case UHF_PORT_INDICATOR:
+ DPRINTFN(3, "clear port ind "
+ "%d\n", index);
+ EOWRITE4(sc, port, v & ~EHCI_PS_PIC);
+ break;
+ case UHF_C_PORT_CONNECTION:
+ EOWRITE4(sc, port, v | EHCI_PS_CSC);
+ break;
+ case UHF_C_PORT_ENABLE:
+ EOWRITE4(sc, port, v | EHCI_PS_PEC);
+ break;
+ case UHF_C_PORT_SUSPEND:
+ EOWRITE4(sc, port, v | EHCI_PS_SUSP);
+ break;
+ case UHF_C_PORT_OVER_CURRENT:
+ EOWRITE4(sc, port, v | EHCI_PS_OCC);
+ break;
+ case UHF_C_PORT_RESET:
+ sc->sc_isreset = 0;
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE):
+ if ((value & 0xff) != 0) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ v = EOREAD4(sc, EHCI_HCSPARAMS);
+
+ sc->sc_hub_desc.hubd = ehci_hubd;
+ sc->sc_hub_desc.hubd.bNbrPorts = sc->sc_noport;
+ USETW(sc->sc_hub_desc.hubd.wHubCharacteristics,
+ (EHCI_HCS_PPC(v) ? UHD_PWR_INDIVIDUAL : UHD_PWR_NO_SWITCH) |
+ (EHCI_HCS_P_INDICATOR(EREAD4(sc, EHCI_HCSPARAMS)) ?
+ UHD_PORT_IND : 0));
+ /* XXX can't find out? */
+ sc->sc_hub_desc.hubd.bPwrOn2PwrGood = 200;
+ for (l = 0; l < sc->sc_noport; l++) {
+ /* XXX can't find out? */
+ sc->sc_hub_desc.hubd.DeviceRemovable[l / 8] &= ~(1 << (l % 8));
+ }
+ sc->sc_hub_desc.hubd.bDescLength =
+ 8 + ((sc->sc_noport + 7) / 8);
+ std->len = sc->sc_hub_desc.hubd.bDescLength;
+ break;
+ case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE):
+ std->len = 16;
+ bzero(sc->sc_hub_desc.temp, 16);
+ break;
+ case C(UR_GET_STATUS, UT_READ_CLASS_OTHER):
+ DPRINTFN(9, "get port status i=%d\n",
+ index);
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ v = EOREAD4(sc, EHCI_PORTSC(index));
+ DPRINTFN(9, "port status=0x%04x\n", v);
+ if (sc->sc_flags & EHCI_SCFLG_FORCESPEED) {
+ if ((v & 0xc000000) == 0x8000000)
+ i = UPS_HIGH_SPEED;
+ else if ((v & 0xc000000) == 0x4000000)
+ i = UPS_LOW_SPEED;
+ else
+ i = 0;
+ } else {
+ i = UPS_HIGH_SPEED;
+ }
+ if (v & EHCI_PS_CS)
+ i |= UPS_CURRENT_CONNECT_STATUS;
+ if (v & EHCI_PS_PE)
+ i |= UPS_PORT_ENABLED;
+ if ((v & EHCI_PS_SUSP) && !(v & EHCI_PS_FPR))
+ i |= UPS_SUSPEND;
+ if (v & EHCI_PS_OCA)
+ i |= UPS_OVERCURRENT_INDICATOR;
+ if (v & EHCI_PS_PR)
+ i |= UPS_RESET;
+ if (v & EHCI_PS_PP)
+ i |= UPS_PORT_POWER;
+ USETW(sc->sc_hub_desc.ps.wPortStatus, i);
+ i = 0;
+ if (v & EHCI_PS_CSC)
+ i |= UPS_C_CONNECT_STATUS;
+ if (v & EHCI_PS_PEC)
+ i |= UPS_C_PORT_ENABLED;
+ if (v & EHCI_PS_OCC)
+ i |= UPS_C_OVERCURRENT_INDICATOR;
+ if (v & EHCI_PS_FPR)
+ i |= UPS_C_SUSPEND;
+ if (sc->sc_isreset)
+ i |= UPS_C_PORT_RESET;
+ USETW(sc->sc_hub_desc.ps.wPortChange, i);
+ std->len = sizeof(sc->sc_hub_desc.ps);
+ break;
+ case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE):
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE):
+ break;
+ case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER):
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ port = EHCI_PORTSC(index);
+ v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR;
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ EOWRITE4(sc, port, v | EHCI_PS_PE);
+ break;
+ case UHF_PORT_SUSPEND:
+ EOWRITE4(sc, port, v | EHCI_PS_SUSP);
+ break;
+ case UHF_PORT_RESET:
+ DPRINTFN(6, "reset port %d\n", index);
+#if USB_DEBUG
+ if (ehcinohighspeed) {
+ /*
+ * Connect USB device to companion
+ * controller.
+ */
+ ehci_disown(sc, index, 1);
+ break;
+ }
+#endif
+ if (EHCI_PS_IS_LOWSPEED(v)) {
+ /* Low speed device, give up ownership. */
+ ehci_disown(sc, index, 1);
+ break;
+ }
+ /* Start reset sequence. */
+ v &= ~(EHCI_PS_PE | EHCI_PS_PR);
+ EOWRITE4(sc, port, v | EHCI_PS_PR);
+
+ if (use_polling) {
+ /* polling */
+ DELAY(USB_PORT_ROOT_RESET_DELAY * 1000);
+ } else {
+ /* Wait for reset to complete. */
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(USB_PORT_ROOT_RESET_DELAY));
+ }
+
+ /* Terminate reset sequence. */
+ if (!(sc->sc_flags & EHCI_SCFLG_NORESTERM))
+ EOWRITE4(sc, port, v);
+
+ if (use_polling) {
+ /* polling */
+ DELAY(EHCI_PORT_RESET_COMPLETE * 1000);
+ } else {
+ /* Wait for HC to complete reset. */
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(EHCI_PORT_RESET_COMPLETE));
+ }
+
+ v = EOREAD4(sc, port);
+ DPRINTF("ehci after reset, status=0x%08x\n", v);
+ if (v & EHCI_PS_PR) {
+ device_printf(sc->sc_bus.bdev,
+ "port reset timeout\n");
+ std->err = USB_ERR_TIMEOUT;
+ goto done;
+ }
+ if (!(v & EHCI_PS_PE)) {
+ /*
+ * Not a high speed device, give up
+ * ownership.
+ */
+ ehci_disown(sc, index, 0);
+ break;
+ }
+ sc->sc_isreset = 1;
+ DPRINTF("ehci port %d reset, status = 0x%08x\n",
+ index, v);
+ break;
+
+ case UHF_PORT_POWER:
+ DPRINTFN(3, "set port power %d\n", index);
+ EOWRITE4(sc, port, v | EHCI_PS_PP);
+ break;
+
+ case UHF_PORT_TEST:
+ DPRINTFN(3, "set port test %d\n", index);
+ break;
+
+ case UHF_PORT_INDICATOR:
+ DPRINTFN(3, "set port ind %d\n", index);
+ EOWRITE4(sc, port, v | EHCI_PS_PIC);
+ break;
+
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ case C(UR_CLEAR_TT_BUFFER, UT_WRITE_CLASS_OTHER):
+ case C(UR_RESET_TT, UT_WRITE_CLASS_OTHER):
+ case C(UR_GET_TT_STATE, UT_READ_CLASS_OTHER):
+ case C(UR_STOP_TT, UT_WRITE_CLASS_OTHER):
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+done:
+ return;
+}
+
+static void
+ehci_root_ctrl_poll(ehci_softc_t *sc)
+{
+ usb2_sw_transfer(&sc->sc_root_ctrl,
+ &ehci_root_ctrl_done);
+}
+
+struct usb2_pipe_methods ehci_root_ctrl_methods =
+{
+ .open = ehci_root_ctrl_open,
+ .close = ehci_root_ctrl_close,
+ .enter = ehci_root_ctrl_enter,
+ .start = ehci_root_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 0,
+};
+
+/*------------------------------------------------------------------------*
+ * ehci root interrupt support
+ *------------------------------------------------------------------------*/
+static void
+ehci_root_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_root_intr_close(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_intr.xfer == xfer) {
+ sc->sc_root_intr.xfer = NULL;
+ }
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ehci_root_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_root_intr_start(struct usb2_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_intr.xfer = xfer;
+}
+
+struct usb2_pipe_methods ehci_root_intr_methods =
+{
+ .open = ehci_root_intr_open,
+ .close = ehci_root_intr_close,
+ .enter = ehci_root_intr_enter,
+ .start = ehci_root_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+static void
+ehci_xfer_setup(struct usb2_setup_params *parm)
+{
+ struct usb2_page_search page_info;
+ struct usb2_page_cache *pc;
+ ehci_softc_t *sc;
+ struct usb2_xfer *xfer;
+ void *last_obj;
+ uint32_t nqtd;
+ uint32_t nqh;
+ uint32_t nsitd;
+ uint32_t nitd;
+ uint32_t n;
+
+ sc = EHCI_BUS2SC(parm->udev->bus);
+ xfer = parm->curr_xfer;
+
+ nqtd = 0;
+ nqh = 0;
+ nsitd = 0;
+ nitd = 0;
+
+ /*
+ * compute maximum number of some structures
+ */
+ if (parm->methods == &ehci_device_ctrl_methods) {
+
+ /*
+ * The proof for the "nqtd" formula is illustrated like
+ * this:
+ *
+ * +------------------------------------+
+ * | |
+ * | |remainder -> |
+ * | +-----+---+ |
+ * | | xxx | x | frm 0 |
+ * | +-----+---++ |
+ * | | xxx | xx | frm 1 |
+ * | +-----+----+ |
+ * | ... |
+ * +------------------------------------+
+ *
+ * "xxx" means a completely full USB transfer descriptor
+ *
+ * "x" and "xx" means a short USB packet
+ *
+ * For the remainder of an USB transfer modulo
+ * "max_data_length" we need two USB transfer descriptors.
+ * One to transfer the remaining data and one to finalise
+ * with a zero length packet in case the "force_short_xfer"
+ * flag is set. We only need two USB transfer descriptors in
+ * the case where the transfer length of the first one is a
+ * factor of "max_frame_size". The rest of the needed USB
+ * transfer descriptors is given by the buffer size divided
+ * by the maximum data payload.
+ */
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX;
+ xfer->flags_int.bdma_enable = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ nqh = 1;
+ nqtd = ((2 * xfer->nframes) + 1 /* STATUS */
+ + (xfer->max_data_length / xfer->max_usb2_frame_size));
+
+ } else if (parm->methods == &ehci_device_bulk_methods) {
+
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX;
+ xfer->flags_int.bdma_enable = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ nqh = 1;
+ nqtd = ((2 * xfer->nframes)
+ + (xfer->max_data_length / xfer->max_usb2_frame_size));
+
+ } else if (parm->methods == &ehci_device_intr_methods) {
+
+ if (parm->speed == USB_SPEED_HIGH) {
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_packet_count = 3;
+ } else if (parm->speed == USB_SPEED_FULL) {
+ parm->hc_max_packet_size = USB_FS_BYTES_PER_HS_UFRAME;
+ parm->hc_max_packet_count = 1;
+ } else {
+ parm->hc_max_packet_size = USB_FS_BYTES_PER_HS_UFRAME / 8;
+ parm->hc_max_packet_count = 1;
+ }
+
+ parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX;
+ xfer->flags_int.bdma_enable = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ nqh = 1;
+ nqtd = ((2 * xfer->nframes)
+ + (xfer->max_data_length / xfer->max_usb2_frame_size));
+
+ } else if (parm->methods == &ehci_device_isoc_fs_methods) {
+
+ parm->hc_max_packet_size = 0x3FF;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = 0x3FF;
+ xfer->flags_int.bdma_enable = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ nsitd = xfer->nframes;
+
+ } else if (parm->methods == &ehci_device_isoc_hs_methods) {
+
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_packet_count = 3;
+ parm->hc_max_frame_size = 0xC00;
+ xfer->flags_int.bdma_enable = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ nitd = (xfer->nframes + 7) / 8;
+
+ } else {
+
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = 0x400;
+
+ usb2_transfer_setup_sub(parm);
+ }
+
+alloc_dma_set:
+
+ if (parm->err) {
+ return;
+ }
+ /*
+ * Allocate queue heads and transfer descriptors
+ */
+ last_obj = NULL;
+
+ if (usb2_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ehci_itd_t),
+ EHCI_ITD_ALIGN, nitd)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nitd; n++) {
+ ehci_itd_t *td;
+
+ usb2_get_page(pc + n, 0, &page_info);
+
+ td = page_info.buffer;
+
+ /* init TD */
+ td->itd_self = htoehci32(sc, page_info.physaddr | EHCI_LINK_ITD);
+ td->obj_next = last_obj;
+ td->page_cache = pc + n;
+
+ last_obj = td;
+
+ usb2_pc_cpu_flush(pc + n);
+ }
+ }
+ if (usb2_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ehci_sitd_t),
+ EHCI_SITD_ALIGN, nsitd)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nsitd; n++) {
+ ehci_sitd_t *td;
+
+ usb2_get_page(pc + n, 0, &page_info);
+
+ td = page_info.buffer;
+
+ /* init TD */
+ td->sitd_self = htoehci32(sc, page_info.physaddr | EHCI_LINK_SITD);
+ td->obj_next = last_obj;
+ td->page_cache = pc + n;
+
+ last_obj = td;
+
+ usb2_pc_cpu_flush(pc + n);
+ }
+ }
+ if (usb2_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ehci_qtd_t),
+ EHCI_QTD_ALIGN, nqtd)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nqtd; n++) {
+ ehci_qtd_t *qtd;
+
+ usb2_get_page(pc + n, 0, &page_info);
+
+ qtd = page_info.buffer;
+
+ /* init TD */
+ qtd->qtd_self = htoehci32(sc, page_info.physaddr);
+ qtd->obj_next = last_obj;
+ qtd->page_cache = pc + n;
+
+ last_obj = qtd;
+
+ usb2_pc_cpu_flush(pc + n);
+ }
+ }
+ xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj;
+
+ last_obj = NULL;
+
+ if (usb2_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ehci_qh_t),
+ EHCI_QH_ALIGN, nqh)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nqh; n++) {
+ ehci_qh_t *qh;
+
+ usb2_get_page(pc + n, 0, &page_info);
+
+ qh = page_info.buffer;
+
+ /* init QH */
+ qh->qh_self = htoehci32(sc, page_info.physaddr | EHCI_LINK_QH);
+ qh->obj_next = last_obj;
+ qh->page_cache = pc + n;
+
+ last_obj = qh;
+
+ usb2_pc_cpu_flush(pc + n);
+ }
+ }
+ xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj;
+
+ if (!xfer->flags_int.curr_dma_set) {
+ xfer->flags_int.curr_dma_set = 1;
+ goto alloc_dma_set;
+ }
+}
+
+static void
+ehci_xfer_unsetup(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_pipe_init(struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc,
+ struct usb2_pipe *pipe)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
+ pipe, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb2_mode,
+ sc->sc_addr);
+
+ if (udev->flags.usb2_mode != USB_MODE_HOST) {
+ /* not supported */
+ return;
+ }
+ if (udev->device_index == sc->sc_addr) {
+ switch (edesc->bEndpointAddress) {
+ case USB_CONTROL_ENDPOINT:
+ pipe->methods = &ehci_root_ctrl_methods;
+ break;
+ case UE_DIR_IN | EHCI_INTR_ENDPT:
+ pipe->methods = &ehci_root_intr_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ } else {
+ if ((udev->speed != USB_SPEED_HIGH) &&
+ ((udev->hs_hub_addr == 0) ||
+ (udev->hs_port_no == 0) ||
+ (udev->bus->devices[udev->hs_hub_addr] == NULL) ||
+ (udev->bus->devices[udev->hs_hub_addr]->hub == NULL))) {
+ /* We need a transaction translator */
+ goto done;
+ }
+ switch (edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_CONTROL:
+ pipe->methods = &ehci_device_ctrl_methods;
+ break;
+ case UE_INTERRUPT:
+ pipe->methods = &ehci_device_intr_methods;
+ break;
+ case UE_ISOCHRONOUS:
+ if (udev->speed == USB_SPEED_HIGH) {
+ pipe->methods = &ehci_device_isoc_hs_methods;
+ } else if (udev->speed == USB_SPEED_FULL) {
+ pipe->methods = &ehci_device_isoc_fs_methods;
+ }
+ break;
+ case UE_BULK:
+ if (udev->speed != USB_SPEED_LOW) {
+ pipe->methods = &ehci_device_bulk_methods;
+ }
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+done:
+ return;
+}
+
+static void
+ehci_get_dma_delay(struct usb2_bus *bus, uint32_t *pus)
+{
+ /*
+ * Wait until the hardware has finished any possible use of
+ * the transfer descriptor(s) and QH
+ */
+ *pus = (188); /* microseconds */
+}
+
+static void
+ehci_device_resume(struct usb2_device *udev)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(udev->bus);
+ struct usb2_xfer *xfer;
+ struct usb2_pipe_methods *methods;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(udev->bus);
+
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+
+ if (xfer->xroot->udev == udev) {
+
+ methods = xfer->pipe->methods;
+
+ if ((methods == &ehci_device_bulk_methods) ||
+ (methods == &ehci_device_ctrl_methods)) {
+ EHCI_APPEND_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_async_p_last);
+ }
+ if (methods == &ehci_device_intr_methods) {
+ EHCI_APPEND_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ }
+ }
+
+ USB_BUS_UNLOCK(udev->bus);
+
+ return;
+}
+
+static void
+ehci_device_suspend(struct usb2_device *udev)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(udev->bus);
+ struct usb2_xfer *xfer;
+ struct usb2_pipe_methods *methods;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(udev->bus);
+
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+
+ if (xfer->xroot->udev == udev) {
+
+ methods = xfer->pipe->methods;
+
+ if ((methods == &ehci_device_bulk_methods) ||
+ (methods == &ehci_device_ctrl_methods)) {
+ EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_async_p_last);
+ }
+ if (methods == &ehci_device_intr_methods) {
+ EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ }
+ }
+
+ USB_BUS_UNLOCK(udev->bus);
+
+ return;
+}
+
+static void
+ehci_set_hw_power(struct usb2_bus *bus)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(bus);
+ uint32_t temp;
+ uint32_t flags;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(bus);
+
+ flags = bus->hw_power_state;
+
+ temp = EOREAD4(sc, EHCI_USBCMD);
+
+ temp &= ~(EHCI_CMD_ASE | EHCI_CMD_PSE);
+
+ if (flags & (USB_HW_POWER_CONTROL |
+ USB_HW_POWER_BULK)) {
+ DPRINTF("Async is active\n");
+ temp |= EHCI_CMD_ASE;
+ }
+ if (flags & (USB_HW_POWER_INTERRUPT |
+ USB_HW_POWER_ISOC)) {
+ DPRINTF("Periodic is active\n");
+ temp |= EHCI_CMD_PSE;
+ }
+ EOWRITE4(sc, EHCI_USBCMD, temp);
+
+ USB_BUS_UNLOCK(bus);
+
+ return;
+}
+
+struct usb2_bus_methods ehci_bus_methods =
+{
+ .pipe_init = ehci_pipe_init,
+ .xfer_setup = ehci_xfer_setup,
+ .xfer_unsetup = ehci_xfer_unsetup,
+ .do_poll = ehci_do_poll,
+ .get_dma_delay = ehci_get_dma_delay,
+ .device_resume = ehci_device_resume,
+ .device_suspend = ehci_device_suspend,
+ .set_hw_power = ehci_set_hw_power,
+ .roothub_exec = ehci_root_ctrl_task,
+};
diff --git a/sys/dev/usb/controller/ehci.h b/sys/dev/usb/controller/ehci.h
new file mode 100644
index 0000000..9d7baa1
--- /dev/null
+++ b/sys/dev/usb/controller/ehci.h
@@ -0,0 +1,532 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 2001 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Lennart Augustsson (lennart@augustsson.net).
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the NetBSD
+ * Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#ifndef _EHCI_H_
+#define _EHCI_H_
+
+#define EHCI_MAX_DEVICES USB_MAX_DEVICES
+
+/* PCI config registers */
+#define PCI_CBMEM 0x10 /* configuration base MEM */
+#define PCI_INTERFACE_EHCI 0x20
+#define PCI_USBREV 0x60 /* RO USB protocol revision */
+#define PCI_USB_REV_MASK 0xff
+#define PCI_USB_REV_PRE_1_0 0x00
+#define PCI_USB_REV_1_0 0x10
+#define PCI_USB_REV_1_1 0x11
+#define PCI_USB_REV_2_0 0x20
+#define PCI_EHCI_FLADJ 0x61 /* RW Frame len adj, SOF=59488+6*fladj */
+#define PCI_EHCI_PORTWAKECAP 0x62 /* RW Port wake caps (opt) */
+
+/* EHCI Extended Capabilities */
+#define EHCI_EC_LEGSUP 0x01
+#define EHCI_EECP_NEXT(x) (((x) >> 8) & 0xff)
+#define EHCI_EECP_ID(x) ((x) & 0xff)
+
+/* Legacy support extended capability */
+#define EHCI_LEGSUP_BIOS_SEM 0x02
+#define EHCI_LEGSUP_OS_SEM 0x03
+#define EHCI_LEGSUP_USBLEGCTLSTS 0x04
+
+/* EHCI capability registers */
+#define EHCI_CAPLENGTH 0x00 /* RO Capability register length field */
+/* reserved 0x01 */
+#define EHCI_HCIVERSION 0x02 /* RO Interface version number */
+#define EHCI_HCSPARAMS 0x04 /* RO Structural parameters */
+#define EHCI_HCS_DEBUGPORT(x) (((x) >> 20) & 0xf)
+#define EHCI_HCS_P_INDICATOR(x) ((x) & 0x10000)
+#define EHCI_HCS_N_CC(x) (((x) >> 12) & 0xf) /* # of companion ctlrs */
+#define EHCI_HCS_N_PCC(x) (((x) >> 8) & 0xf) /* # of ports per comp. */
+#define EHCI_HCS_PPC(x) ((x) & 0x10) /* port power control */
+#define EHCI_HCS_N_PORTS(x) ((x) & 0xf) /* # of ports */
+#define EHCI_HCCPARAMS 0x08 /* RO Capability parameters */
+#define EHCI_HCC_EECP(x) (((x) >> 8) & 0xff) /* extended ports caps */
+#define EHCI_HCC_IST(x) (((x) >> 4) & 0xf) /* isoc sched threshold */
+#define EHCI_HCC_ASPC(x) ((x) & 0x4) /* async sched park cap */
+#define EHCI_HCC_PFLF(x) ((x) & 0x2) /* prog frame list flag */
+#define EHCI_HCC_64BIT(x) ((x) & 0x1) /* 64 bit address cap */
+#define EHCI_HCSP_PORTROUTE 0x0c /* RO Companion port route description */
+
+/* EHCI operational registers. Offset given by EHCI_CAPLENGTH register */
+#define EHCI_USBCMD 0x00 /* RO, RW, WO Command register */
+#define EHCI_CMD_ITC_M 0x00ff0000 /* RW interrupt threshold ctrl */
+#define EHCI_CMD_ITC_1 0x00010000
+#define EHCI_CMD_ITC_2 0x00020000
+#define EHCI_CMD_ITC_4 0x00040000
+#define EHCI_CMD_ITC_8 0x00080000
+#define EHCI_CMD_ITC_16 0x00100000
+#define EHCI_CMD_ITC_32 0x00200000
+#define EHCI_CMD_ITC_64 0x00400000
+#define EHCI_CMD_ASPME 0x00000800 /* RW/RO async park enable */
+#define EHCI_CMD_ASPMC 0x00000300 /* RW/RO async park count */
+#define EHCI_CMD_LHCR 0x00000080 /* RW light host ctrl reset */
+#define EHCI_CMD_IAAD 0x00000040 /* RW intr on async adv door
+ * bell */
+#define EHCI_CMD_ASE 0x00000020 /* RW async sched enable */
+#define EHCI_CMD_PSE 0x00000010 /* RW periodic sched enable */
+#define EHCI_CMD_FLS_M 0x0000000c /* RW/RO frame list size */
+#define EHCI_CMD_FLS(x) (((x) >> 2) & 3) /* RW/RO frame list size */
+#define EHCI_CMD_HCRESET 0x00000002 /* RW reset */
+#define EHCI_CMD_RS 0x00000001 /* RW run/stop */
+#define EHCI_USBSTS 0x04 /* RO, RW, RWC Status register */
+#define EHCI_STS_ASS 0x00008000 /* RO async sched status */
+#define EHCI_STS_PSS 0x00004000 /* RO periodic sched status */
+#define EHCI_STS_REC 0x00002000 /* RO reclamation */
+#define EHCI_STS_HCH 0x00001000 /* RO host controller halted */
+#define EHCI_STS_IAA 0x00000020 /* RWC interrupt on async adv */
+#define EHCI_STS_HSE 0x00000010 /* RWC host system error */
+#define EHCI_STS_FLR 0x00000008 /* RWC frame list rollover */
+#define EHCI_STS_PCD 0x00000004 /* RWC port change detect */
+#define EHCI_STS_ERRINT 0x00000002 /* RWC error interrupt */
+#define EHCI_STS_INT 0x00000001 /* RWC interrupt */
+#define EHCI_STS_INTRS(x) ((x) & 0x3f)
+
+/*
+ * NOTE: the doorbell interrupt is enabled, but the doorbell is never
+ * used! SiS chipsets require this.
+ */
+#define EHCI_NORMAL_INTRS (EHCI_STS_IAA | EHCI_STS_HSE | \
+ EHCI_STS_PCD | EHCI_STS_ERRINT | EHCI_STS_INT)
+
+#define EHCI_USBINTR 0x08 /* RW Interrupt register */
+#define EHCI_INTR_IAAE 0x00000020 /* interrupt on async advance
+ * ena */
+#define EHCI_INTR_HSEE 0x00000010 /* host system error ena */
+#define EHCI_INTR_FLRE 0x00000008 /* frame list rollover ena */
+#define EHCI_INTR_PCIE 0x00000004 /* port change ena */
+#define EHCI_INTR_UEIE 0x00000002 /* USB error intr ena */
+#define EHCI_INTR_UIE 0x00000001 /* USB intr ena */
+
+#define EHCI_FRINDEX 0x0c /* RW Frame Index register */
+
+#define EHCI_CTRLDSSEGMENT 0x10 /* RW Control Data Structure Segment */
+
+#define EHCI_PERIODICLISTBASE 0x14 /* RW Periodic List Base */
+#define EHCI_ASYNCLISTADDR 0x18 /* RW Async List Base */
+
+#define EHCI_CONFIGFLAG 0x40 /* RW Configure Flag register */
+#define EHCI_CONF_CF 0x00000001 /* RW configure flag */
+
+#define EHCI_PORTSC(n) (0x40+(4*(n))) /* RO, RW, RWC Port Status reg */
+#define EHCI_PS_WKOC_E 0x00400000 /* RW wake on over current ena */
+#define EHCI_PS_WKDSCNNT_E 0x00200000 /* RW wake on disconnect ena */
+#define EHCI_PS_WKCNNT_E 0x00100000 /* RW wake on connect ena */
+#define EHCI_PS_PTC 0x000f0000 /* RW port test control */
+#define EHCI_PS_PIC 0x0000c000 /* RW port indicator control */
+#define EHCI_PS_PO 0x00002000 /* RW port owner */
+#define EHCI_PS_PP 0x00001000 /* RW,RO port power */
+#define EHCI_PS_LS 0x00000c00 /* RO line status */
+#define EHCI_PS_IS_LOWSPEED(x) (((x) & EHCI_PS_LS) == 0x00000400)
+#define EHCI_PS_PR 0x00000100 /* RW port reset */
+#define EHCI_PS_SUSP 0x00000080 /* RW suspend */
+#define EHCI_PS_FPR 0x00000040 /* RW force port resume */
+#define EHCI_PS_OCC 0x00000020 /* RWC over current change */
+#define EHCI_PS_OCA 0x00000010 /* RO over current active */
+#define EHCI_PS_PEC 0x00000008 /* RWC port enable change */
+#define EHCI_PS_PE 0x00000004 /* RW port enable */
+#define EHCI_PS_CSC 0x00000002 /* RWC connect status change */
+#define EHCI_PS_CS 0x00000001 /* RO connect status */
+#define EHCI_PS_CLEAR (EHCI_PS_OCC | EHCI_PS_PEC | EHCI_PS_CSC)
+
+#define EHCI_USBMODE 0x68 /* RW USB Device mode register */
+#define EHCI_UM_CM 0x00000003 /* R/WO Controller Mode */
+#define EHCI_UM_CM_IDLE 0x0 /* Idle */
+#define EHCI_UM_CM_HOST 0x3 /* Host Controller */
+#define EHCI_UM_ES 0x00000004 /* R/WO Endian Select */
+#define EHCI_UM_ES_LE 0x0 /* Little-endian byte alignment */
+#define EHCI_UM_ES_BE 0x4 /* Big-endian byte alignment */
+#define EHCI_UM_SDIS 0x00000010 /* R/WO Stream Disable Mode */
+
+#define EHCI_PORT_RESET_COMPLETE 2 /* ms */
+
+/*
+ * Alignment NOTE: structures must be aligned so that the hardware can index
+ * without performing addition.
+ */
+#define EHCI_FRAMELIST_ALIGN 0x1000 /* bytes */
+#define EHCI_FRAMELIST_COUNT 1024 /* units */
+#define EHCI_VIRTUAL_FRAMELIST_COUNT 128 /* units */
+
+#if ((8*EHCI_VIRTUAL_FRAMELIST_COUNT) < USB_MAX_HS_ISOC_FRAMES_PER_XFER)
+#error "maximum number of high-speed isochronous frames is higher than supported!"
+#endif
+
+#if (EHCI_VIRTUAL_FRAMELIST_COUNT < USB_MAX_FS_ISOC_FRAMES_PER_XFER)
+#error "maximum number of full-speed isochronous frames is higher than supported!"
+#endif
+
+/* Link types */
+#define EHCI_LINK_TERMINATE 0x00000001
+#define EHCI_LINK_TYPE(x) ((x) & 0x00000006)
+#define EHCI_LINK_ITD 0x0
+#define EHCI_LINK_QH 0x2
+#define EHCI_LINK_SITD 0x4
+#define EHCI_LINK_FSTN 0x6
+#define EHCI_LINK_ADDR(x) ((x) &~ 0x1f)
+
+/* Structures alignment (bytes) */
+#define EHCI_ITD_ALIGN 128
+#define EHCI_SITD_ALIGN 64
+#define EHCI_QTD_ALIGN 64
+#define EHCI_QH_ALIGN 128
+#define EHCI_FSTN_ALIGN 32
+/* Data buffers are divided into one or more pages */
+#define EHCI_PAGE_SIZE 0x1000
+#if ((USB_PAGE_SIZE < EHCI_PAGE_SIZE) || (EHCI_PAGE_SIZE == 0) || \
+ (USB_PAGE_SIZE < EHCI_ITD_ALIGN) || (EHCI_ITD_ALIGN == 0) || \
+ (USB_PAGE_SIZE < EHCI_SITD_ALIGN) || (EHCI_SITD_ALIGN == 0) || \
+ (USB_PAGE_SIZE < EHCI_QTD_ALIGN) || (EHCI_QTD_ALIGN == 0) || \
+ (USB_PAGE_SIZE < EHCI_QH_ALIGN) || (EHCI_QH_ALIGN == 0) || \
+ (USB_PAGE_SIZE < EHCI_FSTN_ALIGN) || (EHCI_FSTN_ALIGN == 0))
+#error "Invalid USB page size!"
+#endif
+
+
+/*
+ * Isochronous Transfer Descriptor. This descriptor is used for high speed
+ * transfers only.
+ */
+struct ehci_itd {
+ volatile uint32_t itd_next;
+ volatile uint32_t itd_status[8];
+#define EHCI_ITD_SET_LEN(x) ((x) << 16)
+#define EHCI_ITD_GET_LEN(x) (((x) >> 16) & 0xFFF)
+#define EHCI_ITD_IOC (1 << 15)
+#define EHCI_ITD_SET_PG(x) ((x) << 12)
+#define EHCI_ITD_GET_PG(x) (((x) >> 12) & 0x7)
+#define EHCI_ITD_SET_OFFS(x) (x)
+#define EHCI_ITD_GET_OFFS(x) (((x) >> 0) & 0xFFF)
+#define EHCI_ITD_ACTIVE (1 << 31)
+#define EHCI_ITD_DATABUFERR (1 << 30)
+#define EHCI_ITD_BABBLE (1 << 29)
+#define EHCI_ITD_XACTERR (1 << 28)
+ volatile uint32_t itd_bp[7];
+ /* itd_bp[0] */
+#define EHCI_ITD_SET_ADDR(x) (x)
+#define EHCI_ITD_GET_ADDR(x) (((x) >> 0) & 0x7F)
+#define EHCI_ITD_SET_ENDPT(x) ((x) << 8)
+#define EHCI_ITD_GET_ENDPT(x) (((x) >> 8) & 0xF)
+ /* itd_bp[1] */
+#define EHCI_ITD_SET_DIR_IN (1 << 11)
+#define EHCI_ITD_SET_DIR_OUT (0 << 11)
+#define EHCI_ITD_SET_MPL(x) (x)
+#define EHCI_ITD_GET_MPL(x) (((x) >> 0) & 0x7FF)
+ volatile uint32_t itd_bp_hi[7];
+/*
+ * Extra information needed:
+ */
+ uint32_t itd_self;
+ struct ehci_itd *next;
+ struct ehci_itd *prev;
+ struct ehci_itd *obj_next;
+ struct usb2_page_cache *page_cache;
+} __aligned(EHCI_ITD_ALIGN);
+
+typedef struct ehci_itd ehci_itd_t;
+
+/*
+ * Split Transaction Isochronous Transfer Descriptor. This descriptor is used
+ * for full speed transfers only.
+ */
+struct ehci_sitd {
+ volatile uint32_t sitd_next;
+ volatile uint32_t sitd_portaddr;
+#define EHCI_SITD_SET_DIR_OUT (0 << 31)
+#define EHCI_SITD_SET_DIR_IN (1 << 31)
+#define EHCI_SITD_SET_ADDR(x) (x)
+#define EHCI_SITD_GET_ADDR(x) ((x) & 0x7F)
+#define EHCI_SITD_SET_ENDPT(x) ((x) << 8)
+#define EHCI_SITD_GET_ENDPT(x) (((x) >> 8) & 0xF)
+#define EHCI_SITD_GET_DIR(x) ((x) >> 31)
+#define EHCI_SITD_SET_PORT(x) ((x) << 24)
+#define EHCI_SITD_GET_PORT(x) (((x) >> 24) & 0x7F)
+#define EHCI_SITD_SET_HUBA(x) ((x) << 16)
+#define EHCI_SITD_GET_HUBA(x) (((x) >> 16) & 0x7F)
+ volatile uint32_t sitd_mask;
+#define EHCI_SITD_SET_SMASK(x) (x)
+#define EHCI_SITD_SET_CMASK(x) ((x) << 8)
+ volatile uint32_t sitd_status;
+#define EHCI_SITD_COMPLETE_SPLIT (1<<1)
+#define EHCI_SITD_START_SPLIT (0<<1)
+#define EHCI_SITD_MISSED_MICRO_FRAME (1<<2)
+#define EHCI_SITD_XACTERR (1<<3)
+#define EHCI_SITD_BABBLE (1<<4)
+#define EHCI_SITD_DATABUFERR (1<<5)
+#define EHCI_SITD_ERROR (1<<6)
+#define EHCI_SITD_ACTIVE (1<<7)
+#define EHCI_SITD_IOC (1<<31)
+#define EHCI_SITD_SET_LEN(len) ((len)<<16)
+#define EHCI_SITD_GET_LEN(x) (((x)>>16) & 0x3FF)
+ volatile uint32_t sitd_bp[2];
+ volatile uint32_t sitd_back;
+ volatile uint32_t sitd_bp_hi[2];
+/*
+ * Extra information needed:
+ */
+ uint32_t sitd_self;
+ struct ehci_sitd *next;
+ struct ehci_sitd *prev;
+ struct ehci_sitd *obj_next;
+ struct usb2_page_cache *page_cache;
+} __aligned(EHCI_SITD_ALIGN);
+
+typedef struct ehci_sitd ehci_sitd_t;
+
+/* Queue Element Transfer Descriptor */
+struct ehci_qtd {
+ volatile uint32_t qtd_next;
+ volatile uint32_t qtd_altnext;
+ volatile uint32_t qtd_status;
+#define EHCI_QTD_GET_STATUS(x) (((x) >> 0) & 0xff)
+#define EHCI_QTD_SET_STATUS(x) ((x) << 0)
+#define EHCI_QTD_ACTIVE 0x80
+#define EHCI_QTD_HALTED 0x40
+#define EHCI_QTD_BUFERR 0x20
+#define EHCI_QTD_BABBLE 0x10
+#define EHCI_QTD_XACTERR 0x08
+#define EHCI_QTD_MISSEDMICRO 0x04
+#define EHCI_QTD_SPLITXSTATE 0x02
+#define EHCI_QTD_PINGSTATE 0x01
+#define EHCI_QTD_STATERRS 0x74
+#define EHCI_QTD_GET_PID(x) (((x) >> 8) & 0x3)
+#define EHCI_QTD_SET_PID(x) ((x) << 8)
+#define EHCI_QTD_PID_OUT 0x0
+#define EHCI_QTD_PID_IN 0x1
+#define EHCI_QTD_PID_SETUP 0x2
+#define EHCI_QTD_GET_CERR(x) (((x) >> 10) & 0x3)
+#define EHCI_QTD_SET_CERR(x) ((x) << 10)
+#define EHCI_QTD_GET_C_PAGE(x) (((x) >> 12) & 0x7)
+#define EHCI_QTD_SET_C_PAGE(x) ((x) << 12)
+#define EHCI_QTD_GET_IOC(x) (((x) >> 15) & 0x1)
+#define EHCI_QTD_IOC 0x00008000
+#define EHCI_QTD_GET_BYTES(x) (((x) >> 16) & 0x7fff)
+#define EHCI_QTD_SET_BYTES(x) ((x) << 16)
+#define EHCI_QTD_GET_TOGGLE(x) (((x) >> 31) & 0x1)
+#define EHCI_QTD_SET_TOGGLE(x) ((x) << 31)
+#define EHCI_QTD_TOGGLE_MASK 0x80000000
+#define EHCI_QTD_NBUFFERS 5
+#define EHCI_QTD_PAYLOAD_MAX ((EHCI_QTD_NBUFFERS-1)*EHCI_PAGE_SIZE)
+ volatile uint32_t qtd_buffer[EHCI_QTD_NBUFFERS];
+ volatile uint32_t qtd_buffer_hi[EHCI_QTD_NBUFFERS];
+/*
+ * Extra information needed:
+ */
+ struct ehci_qtd *alt_next;
+ struct ehci_qtd *obj_next;
+ struct usb2_page_cache *page_cache;
+ uint32_t qtd_self;
+ uint16_t len;
+} __aligned(EHCI_QTD_ALIGN);
+
+typedef struct ehci_qtd ehci_qtd_t;
+
+/* Queue Head Sub Structure */
+struct ehci_qh_sub {
+ volatile uint32_t qtd_next;
+ volatile uint32_t qtd_altnext;
+ volatile uint32_t qtd_status;
+ volatile uint32_t qtd_buffer[EHCI_QTD_NBUFFERS];
+ volatile uint32_t qtd_buffer_hi[EHCI_QTD_NBUFFERS];
+} __aligned(4);
+
+/* Queue Head */
+struct ehci_qh {
+ volatile uint32_t qh_link;
+ volatile uint32_t qh_endp;
+#define EHCI_QH_GET_ADDR(x) (((x) >> 0) & 0x7f) /* endpoint addr */
+#define EHCI_QH_SET_ADDR(x) (x)
+#define EHCI_QH_ADDRMASK 0x0000007f
+#define EHCI_QH_GET_INACT(x) (((x) >> 7) & 0x01) /* inactivate on next */
+#define EHCI_QH_INACT 0x00000080
+#define EHCI_QH_GET_ENDPT(x) (((x) >> 8) & 0x0f) /* endpoint no */
+#define EHCI_QH_SET_ENDPT(x) ((x) << 8)
+#define EHCI_QH_GET_EPS(x) (((x) >> 12) & 0x03) /* endpoint speed */
+#define EHCI_QH_SET_EPS(x) ((x) << 12)
+#define EHCI_QH_SPEED_FULL 0x0
+#define EHCI_QH_SPEED_LOW 0x1
+#define EHCI_QH_SPEED_HIGH 0x2
+#define EHCI_QH_GET_DTC(x) (((x) >> 14) & 0x01) /* data toggle control */
+#define EHCI_QH_DTC 0x00004000
+#define EHCI_QH_GET_HRECL(x) (((x) >> 15) & 0x01) /* head of reclamation */
+#define EHCI_QH_HRECL 0x00008000
+#define EHCI_QH_GET_MPL(x) (((x) >> 16) & 0x7ff) /* max packet len */
+#define EHCI_QH_SET_MPL(x) ((x) << 16)
+#define EHCI_QH_MPLMASK 0x07ff0000
+#define EHCI_QH_GET_CTL(x) (((x) >> 27) & 0x01) /* control endpoint */
+#define EHCI_QH_CTL 0x08000000
+#define EHCI_QH_GET_NRL(x) (((x) >> 28) & 0x0f) /* NAK reload */
+#define EHCI_QH_SET_NRL(x) ((x) << 28)
+ volatile uint32_t qh_endphub;
+#define EHCI_QH_GET_SMASK(x) (((x) >> 0) & 0xff) /* intr sched mask */
+#define EHCI_QH_SET_SMASK(x) ((x) << 0)
+#define EHCI_QH_GET_CMASK(x) (((x) >> 8) & 0xff) /* split completion mask */
+#define EHCI_QH_SET_CMASK(x) ((x) << 8)
+#define EHCI_QH_GET_HUBA(x) (((x) >> 16) & 0x7f) /* hub address */
+#define EHCI_QH_SET_HUBA(x) ((x) << 16)
+#define EHCI_QH_GET_PORT(x) (((x) >> 23) & 0x7f) /* hub port */
+#define EHCI_QH_SET_PORT(x) ((x) << 23)
+#define EHCI_QH_GET_MULT(x) (((x) >> 30) & 0x03) /* pipe multiplier */
+#define EHCI_QH_SET_MULT(x) ((x) << 30)
+ volatile uint32_t qh_curqtd;
+ struct ehci_qh_sub qh_qtd;
+/*
+ * Extra information needed:
+ */
+ struct ehci_qh *next;
+ struct ehci_qh *prev;
+ struct ehci_qh *obj_next;
+ struct usb2_page_cache *page_cache;
+ uint32_t qh_self;
+} __aligned(EHCI_QH_ALIGN);
+
+typedef struct ehci_qh ehci_qh_t;
+
+/* Periodic Frame Span Traversal Node */
+struct ehci_fstn {
+ volatile uint32_t fstn_link;
+ volatile uint32_t fstn_back;
+} __aligned(EHCI_FSTN_ALIGN);
+
+typedef struct ehci_fstn ehci_fstn_t;
+
+struct ehci_hw_softc {
+ struct usb2_page_cache pframes_pc;
+ struct usb2_page_cache async_start_pc;
+ struct usb2_page_cache intr_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct usb2_page_cache isoc_hs_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct usb2_page_cache isoc_fs_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT];
+
+ struct usb2_page pframes_pg;
+ struct usb2_page async_start_pg;
+ struct usb2_page intr_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct usb2_page isoc_hs_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct usb2_page isoc_fs_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT];
+};
+
+struct ehci_config_desc {
+ struct usb2_config_descriptor confd;
+ struct usb2_interface_descriptor ifcd;
+ struct usb2_endpoint_descriptor endpd;
+} __packed;
+
+union ehci_hub_desc {
+ struct usb2_status stat;
+ struct usb2_port_status ps;
+ struct usb2_device_descriptor devd;
+ struct usb2_device_qualifier odevd;
+ struct usb2_hub_descriptor hubd;
+ uint8_t temp[128];
+};
+
+typedef struct ehci_softc {
+ struct ehci_hw_softc sc_hw;
+ struct usb2_bus sc_bus; /* base device */
+ struct usb2_callout sc_tmo_pcd;
+ union ehci_hub_desc sc_hub_desc;
+ struct usb2_sw_transfer sc_root_ctrl;
+ struct usb2_sw_transfer sc_root_intr;
+
+ struct usb2_device *sc_devices[EHCI_MAX_DEVICES];
+ struct resource *sc_io_res;
+ struct resource *sc_irq_res;
+ struct ehci_qh *sc_async_p_last;
+ struct ehci_qh *sc_intr_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct ehci_sitd *sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct ehci_itd *sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ void *sc_intr_hdl;
+ bus_size_t sc_io_size;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+
+ uint32_t sc_eintrs;
+ uint32_t sc_cmd; /* shadow of cmd register during
+ * suspend */
+
+ uint16_t sc_intr_stat[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ uint16_t sc_id_vendor; /* vendor ID for root hub */
+ uint16_t sc_flags; /* chip specific flags */
+#define EHCI_SCFLG_SETMODE 0x0001 /* set bridge mode again after init */
+#define EHCI_SCFLG_FORCESPEED 0x0002 /* force speed */
+#define EHCI_SCFLG_NORESTERM 0x0004 /* don't terminate reset sequence */
+#define EHCI_SCFLG_BIGEDESC 0x0008 /* big-endian byte order descriptors */
+#define EHCI_SCFLG_BIGEMMIO 0x0010 /* big-endian byte order MMIO */
+#define EHCI_SCFLG_TT 0x0020 /* transaction translator present */
+
+ uint8_t sc_offs; /* offset to operational registers */
+ uint8_t sc_doorbell_disable; /* set on doorbell failure */
+ uint8_t sc_noport;
+ uint8_t sc_addr; /* device address */
+ uint8_t sc_conf; /* device configuration */
+ uint8_t sc_isreset;
+ uint8_t sc_hub_idata[8];
+
+ char sc_vendor[16]; /* vendor string for root hub */
+
+} ehci_softc_t;
+
+#define EREAD1(sc, a) bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (a))
+#define EREAD2(sc, a) bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (a))
+#define EREAD4(sc, a) bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (a))
+#define EWRITE1(sc, a, x) \
+ bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x))
+#define EWRITE2(sc, a, x) \
+ bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x))
+#define EWRITE4(sc, a, x) \
+ bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x))
+#define EOREAD1(sc, a) \
+ bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a))
+#define EOREAD2(sc, a) \
+ bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a))
+#define EOREAD4(sc, a) \
+ bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a))
+#define EOWRITE1(sc, a, x) \
+ bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x))
+#define EOWRITE2(sc, a, x) \
+ bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x))
+#define EOWRITE4(sc, a, x) \
+ bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x))
+
+usb2_bus_mem_cb_t ehci_iterate_hw_softc;
+
+usb2_error_t ehci_init(ehci_softc_t *sc);
+void ehci_detach(struct ehci_softc *sc);
+void ehci_suspend(struct ehci_softc *sc);
+void ehci_resume(struct ehci_softc *sc);
+void ehci_shutdown(ehci_softc_t *sc);
+void ehci_interrupt(ehci_softc_t *sc);
+
+#endif /* _EHCI_H_ */
diff --git a/sys/dev/usb/controller/ehci_ixp4xx.c b/sys/dev/usb/controller/ehci_ixp4xx.c
new file mode 100644
index 0000000..b369d47
--- /dev/null
+++ b/sys/dev/usb/controller/ehci_ixp4xx.c
@@ -0,0 +1,348 @@
+/*-
+ * Copyright (c) 2008 Sam Leffler. 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.
+ */
+
+/*
+ * IXP435 attachment driver for the USB Enhanced Host Controller.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_bus.h"
+
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_defs.h>
+#include <dev/usb/usb.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/ehci.h>
+
+#include <arm/xscale/ixp425/ixp425reg.h>
+#include <arm/xscale/ixp425/ixp425var.h>
+
+#define EHCI_VENDORID_IXP4XX 0x42fa05
+#define EHCI_HC_DEVSTR "IXP4XX Integrated USB 2.0 controller"
+
+struct ixp_ehci_softc {
+ ehci_softc_t base; /* storage for EHCI code */
+ bus_space_tag_t iot;
+ bus_space_handle_t ioh;
+ struct bus_space tag; /* tag for private bus space ops */
+};
+
+static device_attach_t ehci_ixp_attach;
+static device_detach_t ehci_ixp_detach;
+static device_shutdown_t ehci_ixp_shutdown;
+static device_suspend_t ehci_ixp_suspend;
+static device_resume_t ehci_ixp_resume;
+
+static uint8_t ehci_bs_r_1(void *, bus_space_handle_t, bus_size_t);
+static void ehci_bs_w_1(void *, bus_space_handle_t, bus_size_t, u_int8_t);
+static uint16_t ehci_bs_r_2(void *, bus_space_handle_t, bus_size_t);
+static void ehci_bs_w_2(void *, bus_space_handle_t, bus_size_t, uint16_t);
+static uint32_t ehci_bs_r_4(void *, bus_space_handle_t, bus_size_t);
+static void ehci_bs_w_4(void *, bus_space_handle_t, bus_size_t, uint32_t);
+
+static int
+ehci_ixp_suspend(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+
+ err = bus_generic_suspend(self);
+ if (err)
+ return (err);
+ ehci_suspend(sc);
+ return (0);
+}
+
+static int
+ehci_ixp_resume(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+
+ ehci_resume(sc);
+
+ bus_generic_resume(self);
+
+ return (0);
+}
+
+static int
+ehci_ixp_shutdown(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+
+ err = bus_generic_shutdown(self);
+ if (err)
+ return (err);
+ ehci_shutdown(sc);
+
+ return (0);
+}
+
+static int
+ehci_ixp_probe(device_t self)
+{
+
+ device_set_desc(self, EHCI_HC_DEVSTR);
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+ehci_ixp_attach(device_t self)
+{
+ struct ixp_ehci_softc *isc = device_get_softc(self);
+ ehci_softc_t *sc = &isc->base;
+ int err;
+ int rid;
+
+ /* initialise some bus fields */
+ sc->sc_bus.parent = self;
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = EHCI_MAX_DEVICES;
+
+ /* get all DMA memory */
+ if (usb2_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;
+
+ /* NB: hints fix the memory location and irq */
+
+ 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;
+ }
+
+ /*
+ * Craft special resource for bus space ops that handle
+ * byte-alignment of non-word addresses. Also, since
+ * we're already intercepting bus space ops we handle
+ * the register window offset that could otherwise be
+ * done with bus_space_subregion.
+ */
+ isc->iot = rman_get_bustag(sc->sc_io_res);
+ isc->tag.bs_cookie = isc->iot;
+ /* read single */
+ isc->tag.bs_r_1 = ehci_bs_r_1,
+ isc->tag.bs_r_2 = ehci_bs_r_2,
+ isc->tag.bs_r_4 = ehci_bs_r_4,
+ /* write (single) */
+ isc->tag.bs_w_1 = ehci_bs_w_1,
+ isc->tag.bs_w_2 = ehci_bs_w_2,
+ isc->tag.bs_w_4 = ehci_bs_w_4,
+
+ sc->sc_io_tag = &isc->tag;
+ sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res);
+ sc->sc_io_size = IXP435_USB1_SIZE - 0x100;
+
+ rid = 0;
+ sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid,
+ 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, "Intel");
+
+
+ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (void *)(void *)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;
+ }
+
+ /*
+ * Arrange to force Host mode, select big-endian byte alignment,
+ * and arrange to not terminate reset operations (the adapter
+ * will ignore it if we do but might as well save a reg write).
+ * Also, the controller has an embedded Transaction Translator
+ * which means port speed must be read from the Port Status
+ * register following a port enable.
+ */
+ sc->sc_flags |= EHCI_SCFLG_TT
+ | EHCI_SCFLG_SETMODE
+ | EHCI_SCFLG_BIGEDESC
+ | EHCI_SCFLG_BIGEMMIO
+ | EHCI_SCFLG_NORESTERM
+ ;
+
+ 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:
+ ehci_ixp_detach(self);
+ return (ENXIO);
+}
+
+static int
+ehci_ixp_detach(device_t self)
+{
+ struct ixp_ehci_softc *isc = device_get_softc(self);
+ ehci_softc_t *sc = &isc->base;
+ device_t bdev;
+ int err;
+
+ 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_all_children(self);
+
+ /*
+ * disable interrupts that might have been switched on in ehci_init
+ */
+ if (sc->sc_io_res) {
+ EWRITE4(sc, EHCI_USBINTR, 0);
+ }
+
+ 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;
+ }
+ usb2_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc);
+
+ return (0);
+}
+
+/*
+ * Bus space accessors for PIO operations.
+ */
+
+static uint8_t
+ehci_bs_r_1(void *t, bus_space_handle_t h, bus_size_t o)
+{
+ return bus_space_read_1((bus_space_tag_t) t, h,
+ 0x100 + (o &~ 3) + (3 - (o & 3)));
+}
+
+static void
+ehci_bs_w_1(void *t, bus_space_handle_t h, bus_size_t o, u_int8_t v)
+{
+ panic("%s", __func__);
+}
+
+static uint16_t
+ehci_bs_r_2(void *t, bus_space_handle_t h, bus_size_t o)
+{
+ return bus_space_read_2((bus_space_tag_t) t, h,
+ 0x100 + (o &~ 3) + (2 - (o & 3)));
+}
+
+static void
+ehci_bs_w_2(void *t, bus_space_handle_t h, bus_size_t o, uint16_t v)
+{
+ panic("%s", __func__);
+}
+
+static uint32_t
+ehci_bs_r_4(void *t, bus_space_handle_t h, bus_size_t o)
+{
+ return bus_space_read_4((bus_space_tag_t) t, h, 0x100 + o);
+}
+
+static void
+ehci_bs_w_4(void *t, bus_space_handle_t h, bus_size_t o, uint32_t v)
+{
+ bus_space_write_4((bus_space_tag_t) t, h, 0x100 + o, v);
+}
+
+static device_method_t ehci_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, ehci_ixp_probe),
+ DEVMETHOD(device_attach, ehci_ixp_attach),
+ DEVMETHOD(device_detach, ehci_ixp_detach),
+ DEVMETHOD(device_suspend, ehci_ixp_suspend),
+ DEVMETHOD(device_resume, ehci_ixp_resume),
+ DEVMETHOD(device_shutdown, ehci_ixp_shutdown),
+
+ /* Bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+
+ {0, 0}
+};
+
+static driver_t ehci_driver = {
+ "ehci",
+ ehci_methods,
+ sizeof(struct ixp_ehci_softc),
+};
+
+static devclass_t ehci_devclass;
+
+DRIVER_MODULE(ehci, ixp, ehci_driver, ehci_devclass, 0, 0);
+MODULE_DEPEND(ehci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/ehci_mbus.c b/sys/dev/usb/controller/ehci_mbus.c
new file mode 100644
index 0000000..66cf8cc
--- /dev/null
+++ b/sys/dev/usb/controller/ehci_mbus.c
@@ -0,0 +1,364 @@
+/*-
+ * Copyright (C) 2008 MARVELL INTERNATIONAL LTD.
+ * All rights reserved.
+ *
+ * Developed by Semihalf.
+ *
+ * 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.
+ * 3. Neither the name of MARVELL nor the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY 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 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.
+ */
+
+/*
+ * MBus attachment driver for the USB Enhanced Host Controller.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_bus.h"
+
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_defs.h>
+#include <dev/usb/usb.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/ehci.h>
+
+#include <arm/mv/mvreg.h>
+#include <arm/mv/mvvar.h>
+
+#define EHCI_VENDORID_MRVL 0x1286
+#define EHCI_HC_DEVSTR "Marvell Integrated USB 2.0 controller"
+
+static device_attach_t ehci_mbus_attach;
+static device_detach_t ehci_mbus_detach;
+static device_shutdown_t ehci_mbus_shutdown;
+static device_suspend_t ehci_mbus_suspend;
+static device_resume_t ehci_mbus_resume;
+
+static int err_intr(void *arg);
+
+static struct resource *irq_err;
+static void *ih_err;
+
+#define USB_BRIDGE_INTR_CAUSE 0x210
+#define USB_BRIDGE_INTR_MASK 0x214
+
+#define MV_USB_ADDR_DECODE_ERR (1 << 0)
+#define MV_USB_HOST_UNDERFLOW (1 << 1)
+#define MV_USB_HOST_OVERFLOW (1 << 2)
+#define MV_USB_DEVICE_UNDERFLOW (1 << 3)
+
+static int
+ehci_mbus_suspend(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+
+ err = bus_generic_suspend(self);
+ if (err)
+ return (err);
+ ehci_suspend(sc);
+ return (0);
+}
+
+static int
+ehci_mbus_resume(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+
+ ehci_resume(sc);
+
+ bus_generic_resume(self);
+
+ return (0);
+}
+
+static int
+ehci_mbus_shutdown(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+
+ err = bus_generic_shutdown(self);
+ if (err)
+ return (err);
+ ehci_shutdown(sc);
+
+ return (0);
+}
+
+static int
+ehci_mbus_probe(device_t self)
+{
+
+ device_set_desc(self, EHCI_HC_DEVSTR);
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+ehci_mbus_attach(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ bus_space_handle_t bsh;
+ int err;
+ int rid;
+
+ /* initialise some bus fields */
+ sc->sc_bus.parent = self;
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = EHCI_MAX_DEVICES;
+
+ /* get all DMA memory */
+ if (usb2_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);
+ bsh = rman_get_bushandle(sc->sc_io_res);
+ sc->sc_io_size = MV_USB_SIZE - MV_USB_HOST_OFST;
+
+ /*
+ * Marvell EHCI host controller registers start at certain offset within
+ * the whole USB registers range, so create a subregion for the host
+ * mode configuration purposes.
+ */
+ if (bus_space_subregion(sc->sc_io_tag, bsh, MV_USB_HOST_OFST,
+ sc->sc_io_size, &sc->sc_io_hdl) != 0)
+ panic("%s: unable to subregion USB host registers",
+ device_get_name(self));
+
+ rid = 0;
+ irq_err = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid,
+ RF_SHAREABLE | RF_ACTIVE);
+ if (irq_err == NULL) {
+ device_printf(self, "Could not allocate error irq\n");
+ ehci_mbus_detach(self);
+ return (ENXIO);
+ }
+
+ /*
+ * Notice: Marvell EHCI controller has TWO interrupt lines, so make sure to
+ * use the correct rid for the main one (controller interrupt) --
+ * refer to obio_devices[] for the right resource number to use here.
+ */
+ rid = 1;
+ 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, "Marvell");
+
+ err = bus_setup_intr(self, irq_err, INTR_FAST | INTR_TYPE_BIO,
+ err_intr, NULL, sc, &ih_err);
+ if (err) {
+ device_printf(self, "Could not setup error irq, %d\n", err);
+ ih_err = NULL;
+ goto error;
+ }
+
+ EWRITE4(sc, USB_BRIDGE_INTR_MASK, MV_USB_ADDR_DECODE_ERR |
+ MV_USB_HOST_UNDERFLOW | MV_USB_HOST_OVERFLOW |
+ MV_USB_DEVICE_UNDERFLOW);
+
+ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (void *)(void *)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;
+ }
+
+ /*
+ * Workaround for Marvell integrated EHCI controller: reset of
+ * the EHCI core clears the USBMODE register, which sets the core in
+ * an undefined state (neither host nor agent), so it needs to be set
+ * again for proper operation.
+ *
+ * Refer to errata document MV-S500832-00D.pdf (p. 5.24 GL USB-2) for
+ * details.
+ */
+ sc->sc_flags |= EHCI_SCFLG_SETMODE;
+ if (bootverbose)
+ device_printf(self, "5.24 GL USB-2 workaround enabled\n");
+
+ /* XXX all MV chips need it? */
+ sc->sc_flags |= EHCI_SCFLG_FORCESPEED | EHCI_SCFLG_NORESTERM;
+
+ 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:
+ ehci_mbus_detach(self);
+ return (ENXIO);
+}
+
+static int
+ehci_mbus_detach(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ device_t bdev;
+ int err;
+
+ 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_all_children(self);
+
+ /*
+ * disable interrupts that might have been switched on in ehci_init
+ */
+ if (sc->sc_io_res) {
+ EWRITE4(sc, EHCI_USBINTR, 0);
+ EWRITE4(sc, USB_BRIDGE_INTR_MASK, 0);
+ }
+ 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 (irq_err && ih_err) {
+ err = bus_teardown_intr(self, irq_err, ih_err);
+
+ if (err)
+ device_printf(self, "Could not tear down irq, %d\n",
+ err);
+ ih_err = NULL;
+ }
+ if (irq_err) {
+ bus_release_resource(self, SYS_RES_IRQ, 0, irq_err);
+ irq_err = NULL;
+ }
+ if (sc->sc_irq_res) {
+ bus_release_resource(self, SYS_RES_IRQ, 1, 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;
+ }
+ usb2_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc);
+
+ return (0);
+}
+
+static int
+err_intr(void *arg)
+{
+ ehci_softc_t *sc = arg;
+ unsigned int cause;
+
+ cause = EREAD4(sc, USB_BRIDGE_INTR_CAUSE);
+ if (cause) {
+ printf("IRQ ERR: cause: 0x%08x\n", cause);
+ if (cause & MV_USB_ADDR_DECODE_ERR)
+ printf("IRQ ERR: Address decoding error\n");
+ if (cause & MV_USB_HOST_UNDERFLOW)
+ printf("IRQ ERR: USB Host Underflow\n");
+ if (cause & MV_USB_HOST_OVERFLOW)
+ printf("IRQ ERR: USB Host Overflow\n");
+ if (cause & MV_USB_DEVICE_UNDERFLOW)
+ printf("IRQ ERR: USB Device Underflow\n");
+ if (cause & ~(MV_USB_ADDR_DECODE_ERR | MV_USB_HOST_UNDERFLOW |
+ MV_USB_HOST_OVERFLOW | MV_USB_DEVICE_UNDERFLOW))
+ printf("IRQ ERR: Unknown error\n");
+
+ EWRITE4(sc, USB_BRIDGE_INTR_CAUSE, 0);
+ }
+ return (FILTER_HANDLED);
+}
+
+static device_method_t ehci_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, ehci_mbus_probe),
+ DEVMETHOD(device_attach, ehci_mbus_attach),
+ DEVMETHOD(device_detach, ehci_mbus_detach),
+ DEVMETHOD(device_suspend, ehci_mbus_suspend),
+ DEVMETHOD(device_resume, ehci_mbus_resume),
+ DEVMETHOD(device_shutdown, ehci_mbus_shutdown),
+
+ /* Bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+
+ {0, 0}
+};
+
+static driver_t ehci_driver = {
+ "ehci",
+ ehci_methods,
+ sizeof(ehci_softc_t),
+};
+
+static devclass_t ehci_devclass;
+
+DRIVER_MODULE(ehci, mbus, ehci_driver, ehci_devclass, 0, 0);
+MODULE_DEPEND(ehci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/ehci_pci.c b/sys/dev/usb/controller/ehci_pci.c
new file mode 100644
index 0000000..856ea68
--- /dev/null
+++ b/sys/dev/usb/controller/ehci_pci.c
@@ -0,0 +1,486 @@
+/*-
+ * Copyright (c) 1998 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Lennart Augustsson (augustss@carlstedt.se) at
+ * Carlstedt Research & Technology.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the NetBSD
+ * Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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$");
+
+/*
+ * USB Enhanced Host Controller Driver, a.k.a. USB 2.0 controller.
+ *
+ * The EHCI 1.0 spec can be found at
+ * http://developer.intel.com/technology/usb/download/ehci-r10.pdf
+ * and the USB 2.0 spec at
+ * http://www.usb.org/developers/docs/usb_20.zip
+ */
+
+/* The low level controller code for EHCI has been split into
+ * PCI probes and EHCI specific code. This was done to facilitate the
+ * sharing of code between *BSD's
+ */
+
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_defs.h>
+#include <dev/usb/usb.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/usb_pci.h>
+#include <dev/usb/controller/ehci.h>
+
+#define PCI_EHCI_VENDORID_ACERLABS 0x10b9
+#define PCI_EHCI_VENDORID_AMD 0x1022
+#define PCI_EHCI_VENDORID_APPLE 0x106b
+#define PCI_EHCI_VENDORID_ATI 0x1002
+#define PCI_EHCI_VENDORID_CMDTECH 0x1095
+#define PCI_EHCI_VENDORID_INTEL 0x8086
+#define PCI_EHCI_VENDORID_NEC 0x1033
+#define PCI_EHCI_VENDORID_OPTI 0x1045
+#define PCI_EHCI_VENDORID_PHILIPS 0x1131
+#define PCI_EHCI_VENDORID_SIS 0x1039
+#define PCI_EHCI_VENDORID_NVIDIA 0x12D2
+#define PCI_EHCI_VENDORID_NVIDIA2 0x10DE
+#define PCI_EHCI_VENDORID_VIA 0x1106
+
+#define PCI_EHCI_BASE_REG 0x10
+
+static void ehci_pci_takecontroller(device_t self);
+
+static device_probe_t ehci_pci_probe;
+static device_attach_t ehci_pci_attach;
+static device_detach_t ehci_pci_detach;
+static device_suspend_t ehci_pci_suspend;
+static device_resume_t ehci_pci_resume;
+static device_shutdown_t ehci_pci_shutdown;
+
+static int
+ehci_pci_suspend(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+
+ err = bus_generic_suspend(self);
+ if (err)
+ return (err);
+ ehci_suspend(sc);
+ return (0);
+}
+
+static int
+ehci_pci_resume(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+
+ ehci_pci_takecontroller(self);
+ ehci_resume(sc);
+
+ bus_generic_resume(self);
+
+ return (0);
+}
+
+static int
+ehci_pci_shutdown(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+
+ err = bus_generic_shutdown(self);
+ if (err)
+ return (err);
+ ehci_shutdown(sc);
+
+ return (0);
+}
+
+static const char *
+ehci_pci_match(device_t self)
+{
+ uint32_t device_id = pci_get_devid(self);
+
+ switch (device_id) {
+ case 0x268c8086:
+ return ("Intel 63XXESB USB 2.0 controller");
+
+ case 0x523910b9:
+ return "ALi M5239 USB 2.0 controller";
+
+ case 0x10227463:
+ return "AMD 8111 USB 2.0 controller";
+
+ case 0x20951022:
+ return ("AMD CS5536 (Geode) USB 2.0 controller");
+
+ case 0x43451002:
+ return "ATI SB200 USB 2.0 controller";
+ case 0x43731002:
+ return "ATI SB400 USB 2.0 controller";
+
+ case 0x25ad8086:
+ return "Intel 6300ESB USB 2.0 controller";
+ case 0x24cd8086:
+ return "Intel 82801DB/L/M (ICH4) USB 2.0 controller";
+ case 0x24dd8086:
+ return "Intel 82801EB/R (ICH5) USB 2.0 controller";
+ case 0x265c8086:
+ return "Intel 82801FB (ICH6) USB 2.0 controller";
+ case 0x27cc8086:
+ return "Intel 82801GB/R (ICH7) USB 2.0 controller";
+
+ case 0x28368086:
+ return "Intel 82801H (ICH8) USB 2.0 controller USB2-A";
+ case 0x283a8086:
+ return "Intel 82801H (ICH8) USB 2.0 controller USB2-B";
+ case 0x293a8086:
+ return "Intel 82801I (ICH9) USB 2.0 controller";
+ case 0x293c8086:
+ return "Intel 82801I (ICH9) USB 2.0 controller";
+
+ case 0x00e01033:
+ return ("NEC uPD 720100 USB 2.0 controller");
+
+ case 0x006810de:
+ return "NVIDIA nForce2 USB 2.0 controller";
+ case 0x008810de:
+ return "NVIDIA nForce2 Ultra 400 USB 2.0 controller";
+ case 0x00d810de:
+ return "NVIDIA nForce3 USB 2.0 controller";
+ case 0x00e810de:
+ return "NVIDIA nForce3 250 USB 2.0 controller";
+ case 0x005b10de:
+ return "NVIDIA nForce4 USB 2.0 controller";
+
+ case 0x15621131:
+ return "Philips ISP156x USB 2.0 controller";
+
+ case 0x31041106:
+ return ("VIA VT6202 USB 2.0 controller");
+
+ default:
+ break;
+ }
+
+ if ((pci_get_class(self) == PCIC_SERIALBUS)
+ && (pci_get_subclass(self) == PCIS_SERIALBUS_USB)
+ && (pci_get_progif(self) == PCI_INTERFACE_EHCI)) {
+ return ("EHCI (generic) USB 2.0 controller");
+ }
+ return (NULL); /* dunno */
+}
+
+static int
+ehci_pci_probe(device_t self)
+{
+ const char *desc = ehci_pci_match(self);
+
+ if (desc) {
+ device_set_desc(self, desc);
+ return (0);
+ } else {
+ return (ENXIO);
+ }
+}
+
+static int
+ehci_pci_attach(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+ int rid;
+
+ /* initialise some bus fields */
+ sc->sc_bus.parent = self;
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = EHCI_MAX_DEVICES;
+
+ /* get all DMA memory */
+ if (usb2_bus_mem_alloc_all(&sc->sc_bus,
+ USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) {
+ return (ENOMEM);
+ }
+
+ pci_enable_busmaster(self);
+
+ switch (pci_read_config(self, PCI_USBREV, 1) & PCI_USB_REV_MASK) {
+ case PCI_USB_REV_PRE_1_0:
+ case PCI_USB_REV_1_0:
+ case PCI_USB_REV_1_1:
+ /*
+ * NOTE: some EHCI USB controllers have the wrong USB
+ * revision number. It appears those controllers are
+ * fully compliant so we just ignore this value in
+ * some common cases.
+ */
+ device_printf(self, "pre-2.0 USB revision (ignored)\n");
+ /* fallthrough */
+ case PCI_USB_REV_2_0:
+ sc->sc_bus.usbrev = USB_REV_2_0;
+ break;
+ default:
+ /* Quirk for Parallels Desktop 4.0 */
+ device_printf(self, "USB revision is unknown. Assuming v2.0.\n");
+ sc->sc_bus.usbrev = USB_REV_2_0;
+ break;
+ }
+
+ rid = PCI_CBMEM;
+ 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);
+ sc->sc_io_size = rman_get_size(sc->sc_io_res);
+
+ 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);
+
+ /*
+ * ehci_pci_match will never return NULL if ehci_pci_probe
+ * succeeded
+ */
+ device_set_desc(sc->sc_bus.bdev, ehci_pci_match(self));
+ switch (pci_get_vendor(self)) {
+ case PCI_EHCI_VENDORID_ACERLABS:
+ sprintf(sc->sc_vendor, "AcerLabs");
+ break;
+ case PCI_EHCI_VENDORID_AMD:
+ sprintf(sc->sc_vendor, "AMD");
+ break;
+ case PCI_EHCI_VENDORID_APPLE:
+ sprintf(sc->sc_vendor, "Apple");
+ break;
+ case PCI_EHCI_VENDORID_ATI:
+ sprintf(sc->sc_vendor, "ATI");
+ break;
+ case PCI_EHCI_VENDORID_CMDTECH:
+ sprintf(sc->sc_vendor, "CMDTECH");
+ break;
+ case PCI_EHCI_VENDORID_INTEL:
+ sprintf(sc->sc_vendor, "Intel");
+ break;
+ case PCI_EHCI_VENDORID_NEC:
+ sprintf(sc->sc_vendor, "NEC");
+ break;
+ case PCI_EHCI_VENDORID_OPTI:
+ sprintf(sc->sc_vendor, "OPTi");
+ break;
+ case PCI_EHCI_VENDORID_PHILIPS:
+ sprintf(sc->sc_vendor, "Philips");
+ break;
+ case PCI_EHCI_VENDORID_SIS:
+ sprintf(sc->sc_vendor, "SiS");
+ break;
+ case PCI_EHCI_VENDORID_NVIDIA:
+ case PCI_EHCI_VENDORID_NVIDIA2:
+ sprintf(sc->sc_vendor, "nVidia");
+ break;
+ case PCI_EHCI_VENDORID_VIA:
+ sprintf(sc->sc_vendor, "VIA");
+ break;
+ default:
+ if (bootverbose)
+ device_printf(self, "(New EHCI DeviceId=0x%08x)\n",
+ pci_get_devid(self));
+ sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self));
+ }
+
+#if (__FreeBSD_version >= 700031)
+ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (void *)(void *)ehci_interrupt, sc, &sc->sc_intr_hdl);
+#else
+ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ (void *)(void *)ehci_interrupt, sc, &sc->sc_intr_hdl);
+#endif
+ if (err) {
+ device_printf(self, "Could not setup irq, %d\n", err);
+ sc->sc_intr_hdl = NULL;
+ goto error;
+ }
+ ehci_pci_takecontroller(self);
+ 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:
+ ehci_pci_detach(self);
+ return (ENXIO);
+}
+
+static int
+ehci_pci_detach(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ device_t bdev;
+
+ 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_all_children(self);
+
+ pci_disable_busmaster(self);
+
+ /*
+ * disable interrupts that might have been switched on in ehci_init
+ */
+ if (sc->sc_io_res) {
+ EWRITE4(sc, EHCI_USBINTR, 0);
+ }
+ if (sc->sc_irq_res && sc->sc_intr_hdl) {
+ /*
+ * only call ehci_detach() after ehci_init()
+ */
+ ehci_detach(sc);
+
+ int 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, PCI_CBMEM,
+ sc->sc_io_res);
+ sc->sc_io_res = NULL;
+ }
+ usb2_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc);
+
+ return (0);
+}
+
+static void
+ehci_pci_takecontroller(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ uint32_t cparams;
+ uint32_t eec;
+ uint16_t to;
+ uint8_t eecp;
+ uint8_t bios_sem;
+
+ cparams = EREAD4(sc, EHCI_HCCPARAMS);
+
+ /* Synchronise with the BIOS if it owns the controller. */
+ for (eecp = EHCI_HCC_EECP(cparams); eecp != 0;
+ eecp = EHCI_EECP_NEXT(eec)) {
+ eec = pci_read_config(self, eecp, 4);
+ if (EHCI_EECP_ID(eec) != EHCI_EC_LEGSUP) {
+ continue;
+ }
+ bios_sem = pci_read_config(self, eecp +
+ EHCI_LEGSUP_BIOS_SEM, 1);
+ if (bios_sem == 0) {
+ continue;
+ }
+ device_printf(sc->sc_bus.bdev, "waiting for BIOS "
+ "to give up control\n");
+ pci_write_config(self, eecp +
+ EHCI_LEGSUP_OS_SEM, 1, 1);
+ to = 500;
+ while (1) {
+ bios_sem = pci_read_config(self, eecp +
+ EHCI_LEGSUP_BIOS_SEM, 1);
+ if (bios_sem == 0)
+ break;
+
+ if (--to == 0) {
+ device_printf(sc->sc_bus.bdev,
+ "timed out waiting for BIOS\n");
+ break;
+ }
+ usb2_pause_mtx(NULL, hz / 100); /* wait 10ms */
+ }
+ }
+}
+
+static driver_t ehci_driver =
+{
+ .name = "ehci",
+ .methods = (device_method_t[]){
+ /* device interface */
+ DEVMETHOD(device_probe, ehci_pci_probe),
+ DEVMETHOD(device_attach, ehci_pci_attach),
+ DEVMETHOD(device_detach, ehci_pci_detach),
+ DEVMETHOD(device_suspend, ehci_pci_suspend),
+ DEVMETHOD(device_resume, ehci_pci_resume),
+ DEVMETHOD(device_shutdown, ehci_pci_shutdown),
+ /* bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+
+ {0, 0}
+ },
+ .size = sizeof(struct ehci_softc),
+};
+
+static devclass_t ehci_devclass;
+
+DRIVER_MODULE(ehci, pci, ehci_driver, ehci_devclass, 0, 0);
+DRIVER_MODULE(ehci, cardbus, ehci_driver, ehci_devclass, 0, 0);
+MODULE_DEPEND(ehci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/musb_otg.c b/sys/dev/usb/controller/musb_otg.c
new file mode 100644
index 0000000..2194756
--- /dev/null
+++ b/sys/dev/usb/controller/musb_otg.c
@@ -0,0 +1,2875 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 2008 Hans Petter Selasky. 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.
+ */
+
+/*
+ * Thanks to Mentor Graphics for providing a reference driver for this
+ * USB chip at their homepage.
+ */
+
+/*
+ * This file contains the driver for the Mentor Graphics Inventra USB
+ * 2.0 High Speed Dual-Role controller.
+ *
+ * NOTE: The current implementation only supports Device Side Mode!
+ */
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_error.h>
+#include <dev/usb/usb_defs.h>
+
+#define USB_DEBUG_VAR musbotgdebug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/musb_otg.h>
+
+#define MUSBOTG_INTR_ENDPT 1
+
+#define MUSBOTG_BUS2SC(bus) \
+ ((struct musbotg_softc *)(((uint8_t *)(bus)) - \
+ USB_P2U(&(((struct musbotg_softc *)0)->sc_bus))))
+
+#define MUSBOTG_PC2SC(pc) \
+ MUSBOTG_BUS2SC((pc)->tag_parent->info->bus)
+
+#if USB_DEBUG
+static int musbotgdebug = 0;
+
+SYSCTL_NODE(_hw_usb2, OID_AUTO, musbotg, CTLFLAG_RW, 0, "USB musbotg");
+SYSCTL_INT(_hw_usb2_musbotg, OID_AUTO, debug, CTLFLAG_RW,
+ &musbotgdebug, 0, "Debug level");
+#endif
+
+/* prototypes */
+
+struct usb2_bus_methods musbotg_bus_methods;
+struct usb2_pipe_methods musbotg_device_bulk_methods;
+struct usb2_pipe_methods musbotg_device_ctrl_methods;
+struct usb2_pipe_methods musbotg_device_intr_methods;
+struct usb2_pipe_methods musbotg_device_isoc_methods;
+struct usb2_pipe_methods musbotg_root_ctrl_methods;
+struct usb2_pipe_methods musbotg_root_intr_methods;
+
+static musbotg_cmd_t musbotg_setup_rx;
+static musbotg_cmd_t musbotg_setup_data_rx;
+static musbotg_cmd_t musbotg_setup_data_tx;
+static musbotg_cmd_t musbotg_setup_status;
+static musbotg_cmd_t musbotg_data_rx;
+static musbotg_cmd_t musbotg_data_tx;
+static void musbotg_device_done(struct usb2_xfer *, usb2_error_t);
+static void musbotg_do_poll(struct usb2_bus *);
+static void musbotg_root_ctrl_poll(struct musbotg_softc *);
+static void musbotg_standard_done(struct usb2_xfer *);
+static void musbotg_interrupt_poll(struct musbotg_softc *);
+
+static usb2_sw_transfer_func_t musbotg_root_intr_done;
+static usb2_sw_transfer_func_t musbotg_root_ctrl_done;
+
+/*
+ * Here is a configuration that the chip supports.
+ */
+static const struct usb2_hw_ep_profile musbotg_ep_profile[1] = {
+
+ [0] = {
+ .max_in_frame_size = 64,/* fixed */
+ .max_out_frame_size = 64, /* fixed */
+ .is_simplex = 1,
+ .support_control = 1,
+ }
+};
+
+static void
+musbotg_get_hw_ep_profile(struct usb2_device *udev,
+ const struct usb2_hw_ep_profile **ppf, uint8_t ep_addr)
+{
+ struct musbotg_softc *sc;
+
+ sc = MUSBOTG_BUS2SC(udev->bus);
+
+ if (ep_addr == 0) {
+ /* control endpoint */
+ *ppf = musbotg_ep_profile;
+ } else if (ep_addr <= sc->sc_ep_max) {
+ /* other endpoints */
+ *ppf = sc->sc_hw_ep_profile + ep_addr;
+ } else {
+ *ppf = NULL;
+ }
+}
+
+static void
+musbotg_clocks_on(struct musbotg_softc *sc)
+{
+ if (sc->sc_flags.clocks_off &&
+ sc->sc_flags.port_powered) {
+
+ DPRINTFN(4, "\n");
+
+ if (sc->sc_clocks_on) {
+ (sc->sc_clocks_on) (sc->sc_clocks_arg);
+ }
+ sc->sc_flags.clocks_off = 0;
+
+ /* XXX enable Transceiver */
+ }
+}
+
+static void
+musbotg_clocks_off(struct musbotg_softc *sc)
+{
+ if (!sc->sc_flags.clocks_off) {
+
+ DPRINTFN(4, "\n");
+
+ /* XXX disable Transceiver */
+
+ if (sc->sc_clocks_off) {
+ (sc->sc_clocks_off) (sc->sc_clocks_arg);
+ }
+ sc->sc_flags.clocks_off = 1;
+ }
+}
+
+static void
+musbotg_pull_common(struct musbotg_softc *sc, uint8_t on)
+{
+ uint8_t temp;
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ if (on)
+ temp |= MUSB2_MASK_SOFTC;
+ else
+ temp &= ~MUSB2_MASK_SOFTC;
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp);
+}
+
+static void
+musbotg_pull_up(struct musbotg_softc *sc)
+{
+ /* pullup D+, if possible */
+
+ if (!sc->sc_flags.d_pulled_up &&
+ sc->sc_flags.port_powered) {
+ sc->sc_flags.d_pulled_up = 1;
+ musbotg_pull_common(sc, 1);
+ }
+}
+
+static void
+musbotg_pull_down(struct musbotg_softc *sc)
+{
+ /* pulldown D+, if possible */
+
+ if (sc->sc_flags.d_pulled_up) {
+ sc->sc_flags.d_pulled_up = 0;
+ musbotg_pull_common(sc, 0);
+ }
+}
+
+static void
+musbotg_wakeup_peer(struct usb2_xfer *xfer)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+ uint8_t temp;
+ uint8_t use_polling;
+
+ if (!(sc->sc_flags.status_suspend)) {
+ return;
+ }
+ use_polling = mtx_owned(xfer->xroot->xfer_mtx) ? 1 : 0;
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ temp |= MUSB2_MASK_RESUME;
+ MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp);
+
+ /* wait 8 milliseconds */
+ if (use_polling) {
+ /* polling */
+ DELAY(8000);
+ } else {
+ /* Wait for reset to complete. */
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125);
+ }
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ temp &= ~MUSB2_MASK_RESUME;
+ MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp);
+}
+
+static void
+musbotg_set_address(struct musbotg_softc *sc, uint8_t addr)
+{
+ DPRINTFN(4, "addr=%d\n", addr);
+ addr &= 0x7F;
+ MUSB2_WRITE_1(sc, MUSB2_REG_FADDR, addr);
+}
+
+static uint8_t
+musbotg_setup_rx(struct musbotg_td *td)
+{
+ struct musbotg_softc *sc;
+ struct usb2_device_request req;
+ uint16_t count;
+ uint8_t csr;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ /* select endpoint 0 */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ /*
+ * NOTE: If DATAEND is set we should not call the
+ * callback, hence the status stage is not complete.
+ */
+ if (csr & MUSB2_MASK_CSR0L_DATAEND) {
+ /* wait for interrupt */
+ goto not_complete;
+ }
+ if (csr & MUSB2_MASK_CSR0L_SENTSTALL) {
+ /* clear SENTSTALL */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ /* get latest status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ /* update EP0 state */
+ sc->sc_ep0_busy = 0;
+ }
+ if (csr & MUSB2_MASK_CSR0L_SETUPEND) {
+ /* clear SETUPEND */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_SETUPEND_CLR);
+ /* get latest status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ /* update EP0 state */
+ sc->sc_ep0_busy = 0;
+ }
+ if (sc->sc_ep0_busy) {
+ /* abort any ongoing transfer */
+ if (!td->did_stall) {
+ DPRINTFN(4, "stalling\n");
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_SENDSTALL);
+ td->did_stall = 1;
+ }
+ goto not_complete;
+ }
+ if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY)) {
+ goto not_complete;
+ }
+ /* get the packet byte count */
+ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT);
+
+ /* verify data length */
+ if (count != td->remainder) {
+ DPRINTFN(0, "Invalid SETUP packet "
+ "length, %d bytes\n", count);
+ goto not_complete;
+ }
+ if (count != sizeof(req)) {
+ DPRINTFN(0, "Unsupported SETUP packet "
+ "length, %d bytes\n", count);
+ goto not_complete;
+ }
+ /* receive data */
+ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), (void *)&req, sizeof(req));
+
+ /* copy data into real buffer */
+ usb2_copy_in(td->pc, 0, &req, sizeof(req));
+
+ td->offset = sizeof(req);
+ td->remainder = 0;
+
+ /* set pending command */
+ sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_RXPKTRDY_CLR;
+
+ /* we need set stall or dataend after this */
+ sc->sc_ep0_busy = 1;
+
+ /* sneak peek the set address */
+ if ((req.bmRequestType == UT_WRITE_DEVICE) &&
+ (req.bRequest == UR_SET_ADDRESS)) {
+ sc->sc_dv_addr = req.wValue[0] & 0x7F;
+ } else {
+ sc->sc_dv_addr = 0xFF;
+ }
+ return (0); /* complete */
+
+not_complete:
+ return (1); /* not complete */
+}
+
+/* Control endpoint only data handling functions (RX/TX/SYNC) */
+
+static uint8_t
+musbotg_setup_data_rx(struct musbotg_td *td)
+{
+ struct usb2_page_search buf_res;
+ struct musbotg_softc *sc;
+ uint16_t count;
+ uint8_t csr;
+ uint8_t got_short;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ /* select endpoint 0 */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ /* check if a command is pending */
+ if (sc->sc_ep0_cmd) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd);
+ sc->sc_ep0_cmd = 0;
+ }
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ got_short = 0;
+
+ if (csr & (MUSB2_MASK_CSR0L_SETUPEND |
+ MUSB2_MASK_CSR0L_SENTSTALL)) {
+ if (td->remainder == 0) {
+ /*
+ * We are actually complete and have
+ * received the next SETUP
+ */
+ DPRINTFN(4, "faking complete\n");
+ return (0); /* complete */
+ }
+ /*
+ * USB Host Aborted the transfer.
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY)) {
+ return (1); /* not complete */
+ }
+ /* get the packet byte count */
+ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT);
+
+ /* verify the packet byte count */
+ if (count != td->max_frame_size) {
+ if (count < td->max_frame_size) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ got_short = 1;
+ } else {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ }
+ /* verify the packet byte count */
+ if (count > td->remainder) {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ while (count > 0) {
+ uint32_t temp;
+
+ usb2_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* check for unaligned memory address */
+ if (USB_P2U(buf_res.buffer) & 3) {
+
+ temp = count & ~3;
+
+ if (temp) {
+ /* receive data 4 bytes at a time */
+ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf,
+ temp / 4);
+ }
+ temp = count & 3;
+ if (temp) {
+ /* receive data 1 byte at a time */
+ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0),
+ (void *)(&sc->sc_bounce_buf[count / 4]), temp);
+ }
+ usb2_copy_in(td->pc, td->offset,
+ sc->sc_bounce_buf, count);
+
+ /* update offset and remainder */
+ td->offset += count;
+ td->remainder -= count;
+ break;
+ }
+ /* check if we can optimise */
+ if (buf_res.length >= 4) {
+
+ /* receive data 4 bytes at a time */
+ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), buf_res.buffer,
+ buf_res.length / 4);
+
+ temp = buf_res.length & ~3;
+
+ /* update counters */
+ count -= temp;
+ td->offset += temp;
+ td->remainder -= temp;
+ continue;
+ }
+ /* receive data */
+ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* check if we are complete */
+ if ((td->remainder == 0) || got_short) {
+ if (td->short_pkt) {
+ /* we are complete */
+ sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_RXPKTRDY_CLR;
+ return (0);
+ }
+ /* else need to receive a zero length packet */
+ }
+ /* write command - need more data */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_RXPKTRDY_CLR);
+ return (1); /* not complete */
+}
+
+static uint8_t
+musbotg_setup_data_tx(struct musbotg_td *td)
+{
+ struct usb2_page_search buf_res;
+ struct musbotg_softc *sc;
+ uint16_t count;
+ uint8_t csr;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ /* select endpoint 0 */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ /* check if a command is pending */
+ if (sc->sc_ep0_cmd) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd);
+ sc->sc_ep0_cmd = 0;
+ }
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ if (csr & (MUSB2_MASK_CSR0L_SETUPEND |
+ MUSB2_MASK_CSR0L_SENTSTALL)) {
+ /*
+ * The current transfer was aborted
+ * by the USB Host
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ if (csr & MUSB2_MASK_CSR0L_TXPKTRDY) {
+ return (1); /* not complete */
+ }
+ count = td->max_frame_size;
+ if (td->remainder < count) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ count = td->remainder;
+ }
+ while (count > 0) {
+ uint32_t temp;
+
+ usb2_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* check for unaligned memory address */
+ if (USB_P2U(buf_res.buffer) & 3) {
+
+ usb2_copy_out(td->pc, td->offset,
+ sc->sc_bounce_buf, count);
+
+ temp = count & ~3;
+
+ if (temp) {
+ /* transmit data 4 bytes at a time */
+ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf,
+ temp / 4);
+ }
+ temp = count & 3;
+ if (temp) {
+ /* receive data 1 byte at a time */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0),
+ ((void *)&sc->sc_bounce_buf[count / 4]), temp);
+ }
+ /* update offset and remainder */
+ td->offset += count;
+ td->remainder -= count;
+ break;
+ }
+ /* check if we can optimise */
+ if (buf_res.length >= 4) {
+
+ /* transmit data 4 bytes at a time */
+ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), buf_res.buffer,
+ buf_res.length / 4);
+
+ temp = buf_res.length & ~3;
+
+ /* update counters */
+ count -= temp;
+ td->offset += temp;
+ td->remainder -= temp;
+ continue;
+ }
+ /* transmit data */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* check remainder */
+ if (td->remainder == 0) {
+ if (td->short_pkt) {
+ sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_TXPKTRDY;
+ return (0); /* complete */
+ }
+ /* else we need to transmit a short packet */
+ }
+ /* write command */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_TXPKTRDY);
+
+ return (1); /* not complete */
+}
+
+static uint8_t
+musbotg_setup_status(struct musbotg_td *td)
+{
+ struct musbotg_softc *sc;
+ uint8_t csr;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ /* select endpoint 0 */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ if (sc->sc_ep0_busy) {
+ sc->sc_ep0_busy = 0;
+ sc->sc_ep0_cmd |= MUSB2_MASK_CSR0L_DATAEND;
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd);
+ sc->sc_ep0_cmd = 0;
+ }
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ if (csr & MUSB2_MASK_CSR0L_DATAEND) {
+ /* wait for interrupt */
+ return (1); /* not complete */
+ }
+ if (sc->sc_dv_addr != 0xFF) {
+ /* write function address */
+ musbotg_set_address(sc, sc->sc_dv_addr);
+ }
+ return (0); /* complete */
+}
+
+static uint8_t
+musbotg_data_rx(struct musbotg_td *td)
+{
+ struct usb2_page_search buf_res;
+ struct musbotg_softc *sc;
+ uint16_t count;
+ uint8_t csr;
+ uint8_t to;
+ uint8_t got_short;
+
+ to = 8; /* don't loop forever! */
+ got_short = 0;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->ep_no);
+
+repeat:
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ /* clear overrun */
+ if (csr & MUSB2_MASK_CSRL_RXOVERRUN) {
+ /* make sure we don't clear "RXPKTRDY" */
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL,
+ MUSB2_MASK_CSRL_RXPKTRDY);
+ }
+ /* check status */
+ if (!(csr & MUSB2_MASK_CSRL_RXPKTRDY)) {
+ return (1); /* not complete */
+ }
+ /* get the packet byte count */
+ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT);
+
+ DPRINTFN(4, "count=0x%04x\n", count);
+
+ /*
+ * Check for short or invalid packet:
+ */
+ if (count != td->max_frame_size) {
+ if (count < td->max_frame_size) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ got_short = 1;
+ } else {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ }
+ /* verify the packet byte count */
+ if (count > td->remainder) {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ while (count > 0) {
+ uint32_t temp;
+
+ usb2_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* check for unaligned memory address */
+ if (USB_P2U(buf_res.buffer) & 3) {
+
+ temp = count & ~3;
+
+ if (temp) {
+ /* receive data 4 bytes at a time */
+ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->ep_no), sc->sc_bounce_buf,
+ temp / 4);
+ }
+ temp = count & 3;
+ if (temp) {
+ /* receive data 1 byte at a time */
+ bus_space_read_multi_1(sc->sc_io_tag,
+ sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->ep_no),
+ ((void *)&sc->sc_bounce_buf[count / 4]), temp);
+ }
+ usb2_copy_in(td->pc, td->offset,
+ sc->sc_bounce_buf, count);
+
+ /* update offset and remainder */
+ td->offset += count;
+ td->remainder -= count;
+ break;
+ }
+ /* check if we can optimise */
+ if (buf_res.length >= 4) {
+
+ /* receive data 4 bytes at a time */
+ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->ep_no), buf_res.buffer,
+ buf_res.length / 4);
+
+ temp = buf_res.length & ~3;
+
+ /* update counters */
+ count -= temp;
+ td->offset += temp;
+ td->remainder -= temp;
+ continue;
+ }
+ /* receive data */
+ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->ep_no), buf_res.buffer,
+ buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* clear status bits */
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0);
+
+ /* check if we are complete */
+ if ((td->remainder == 0) || got_short) {
+ if (td->short_pkt) {
+ /* we are complete */
+ return (0);
+ }
+ /* else need to receive a zero length packet */
+ }
+ if (--to) {
+ goto repeat;
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+musbotg_data_tx(struct musbotg_td *td)
+{
+ struct usb2_page_search buf_res;
+ struct musbotg_softc *sc;
+ uint16_t count;
+ uint8_t csr;
+ uint8_t to;
+
+ to = 8; /* don't loop forever! */
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->ep_no);
+
+repeat:
+
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ if (csr & (MUSB2_MASK_CSRL_TXINCOMP |
+ MUSB2_MASK_CSRL_TXUNDERRUN)) {
+ /* clear status bits */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ }
+ if (csr & MUSB2_MASK_CSRL_TXPKTRDY) {
+ return (1); /* not complete */
+ }
+ /* check for short packet */
+ count = td->max_frame_size;
+ if (td->remainder < count) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ count = td->remainder;
+ }
+ while (count > 0) {
+ uint32_t temp;
+
+ usb2_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* check for unaligned memory address */
+ if (USB_P2U(buf_res.buffer) & 3) {
+
+ usb2_copy_out(td->pc, td->offset,
+ sc->sc_bounce_buf, count);
+
+ temp = count & ~3;
+
+ if (temp) {
+ /* transmit data 4 bytes at a time */
+ bus_space_write_multi_4(sc->sc_io_tag,
+ sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->ep_no),
+ sc->sc_bounce_buf, temp / 4);
+ }
+ temp = count & 3;
+ if (temp) {
+ /* receive data 1 byte at a time */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->ep_no),
+ ((void *)&sc->sc_bounce_buf[count / 4]), temp);
+ }
+ /* update offset and remainder */
+ td->offset += count;
+ td->remainder -= count;
+ break;
+ }
+ /* check if we can optimise */
+ if (buf_res.length >= 4) {
+
+ /* transmit data 4 bytes at a time */
+ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->ep_no), buf_res.buffer,
+ buf_res.length / 4);
+
+ temp = buf_res.length & ~3;
+
+ /* update counters */
+ count -= temp;
+ td->offset += temp;
+ td->remainder -= temp;
+ continue;
+ }
+ /* transmit data */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->ep_no), buf_res.buffer,
+ buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* write command */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSRL_TXPKTRDY);
+
+ /* check remainder */
+ if (td->remainder == 0) {
+ if (td->short_pkt) {
+ return (0); /* complete */
+ }
+ /* else we need to transmit a short packet */
+ }
+ if (--to) {
+ goto repeat;
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+musbotg_xfer_do_fifo(struct usb2_xfer *xfer)
+{
+ struct musbotg_softc *sc;
+ struct musbotg_td *td;
+
+ DPRINTFN(8, "\n");
+
+ td = xfer->td_transfer_cache;
+ while (1) {
+ if ((td->func) (td)) {
+ /* operation in progress */
+ break;
+ }
+ if (((void *)td) == xfer->td_transfer_last) {
+ goto done;
+ }
+ if (td->error) {
+ goto done;
+ } else if (td->remainder > 0) {
+ /*
+ * We had a short transfer. If there is no alternate
+ * next, stop processing !
+ */
+ if (!td->alt_next) {
+ goto done;
+ }
+ }
+ /*
+ * Fetch the next transfer descriptor and transfer
+ * some flags to the next transfer descriptor
+ */
+ td = td->obj_next;
+ xfer->td_transfer_cache = td;
+ }
+ return (1); /* not complete */
+
+done:
+ sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+
+ /* compute all actual lengths */
+
+ musbotg_standard_done(xfer);
+
+ return (0); /* complete */
+}
+
+static void
+musbotg_interrupt_poll(struct musbotg_softc *sc)
+{
+ struct usb2_xfer *xfer;
+
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ if (!musbotg_xfer_do_fifo(xfer)) {
+ /* queue has been modified */
+ goto repeat;
+ }
+ }
+}
+
+void
+musbotg_vbus_interrupt(struct musbotg_softc *sc, uint8_t is_on)
+{
+ DPRINTFN(4, "vbus = %u\n", is_on);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ if (is_on) {
+ if (!sc->sc_flags.status_vbus) {
+ sc->sc_flags.status_vbus = 1;
+
+ /* complete root HUB interrupt endpoint */
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &musbotg_root_intr_done);
+ }
+ } else {
+ if (sc->sc_flags.status_vbus) {
+ sc->sc_flags.status_vbus = 0;
+ sc->sc_flags.status_bus_reset = 0;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ /* complete root HUB interrupt endpoint */
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &musbotg_root_intr_done);
+ }
+ }
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+void
+musbotg_interrupt(struct musbotg_softc *sc)
+{
+ uint16_t rx_status;
+ uint16_t tx_status;
+ uint8_t usb_status;
+ uint8_t temp;
+ uint8_t to = 2;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+repeat:
+
+ /* read all interrupt registers */
+ usb_status = MUSB2_READ_1(sc, MUSB2_REG_INTUSB);
+
+ /* read all FIFO interrupts */
+ rx_status = MUSB2_READ_2(sc, MUSB2_REG_INTRX);
+ tx_status = MUSB2_READ_2(sc, MUSB2_REG_INTTX);
+
+ /* check for any bus state change interrupts */
+
+ if (usb_status & (MUSB2_MASK_IRESET |
+ MUSB2_MASK_IRESUME | MUSB2_MASK_ISUSP)) {
+
+ DPRINTFN(4, "real bus interrupt 0x%08x\n", usb_status);
+
+ if (usb_status & MUSB2_MASK_IRESET) {
+
+ /* set correct state */
+ sc->sc_flags.status_bus_reset = 1;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ /* determine line speed */
+ temp = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ if (temp & MUSB2_MASK_HSMODE)
+ sc->sc_flags.status_high_speed = 1;
+ else
+ sc->sc_flags.status_high_speed = 0;
+
+ /*
+ * After reset all interrupts are on and we need to
+ * turn them off!
+ */
+ temp = MUSB2_MASK_IRESET;
+ /* disable resume interrupt */
+ temp &= ~MUSB2_MASK_IRESUME;
+ /* enable suspend interrupt */
+ temp |= MUSB2_MASK_ISUSP;
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp);
+ /* disable TX and RX interrupts */
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0);
+ }
+ /*
+ * If RXRSM and RXSUSP is set at the same time we interpret
+ * that like RESUME. Resume is set when there is at least 3
+ * milliseconds of inactivity on the USB BUS.
+ */
+ if (usb_status & MUSB2_MASK_IRESUME) {
+ if (sc->sc_flags.status_suspend) {
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 1;
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_INTUSBE);
+ /* disable resume interrupt */
+ temp &= ~MUSB2_MASK_IRESUME;
+ /* enable suspend interrupt */
+ temp |= MUSB2_MASK_ISUSP;
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp);
+ }
+ } else if (usb_status & MUSB2_MASK_ISUSP) {
+ if (!sc->sc_flags.status_suspend) {
+ sc->sc_flags.status_suspend = 1;
+ sc->sc_flags.change_suspend = 1;
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_INTUSBE);
+ /* disable suspend interrupt */
+ temp &= ~MUSB2_MASK_ISUSP;
+ /* enable resume interrupt */
+ temp |= MUSB2_MASK_IRESUME;
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp);
+ }
+ }
+ /* complete root HUB interrupt endpoint */
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &musbotg_root_intr_done);
+ }
+ /* check for any endpoint interrupts */
+
+ if (rx_status || tx_status) {
+ DPRINTFN(4, "real endpoint interrupt "
+ "rx=0x%04x, tx=0x%04x\n", rx_status, tx_status);
+ }
+ /* poll one time regardless of FIFO status */
+
+ musbotg_interrupt_poll(sc);
+
+ if (--to)
+ goto repeat;
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+musbotg_setup_standard_chain_sub(struct musbotg_std_temp *temp)
+{
+ struct musbotg_td *td;
+
+ /* get current Transfer Descriptor */
+ td = temp->td_next;
+ temp->td = td;
+
+ /* prepare for next TD */
+ temp->td_next = td->obj_next;
+
+ /* fill out the Transfer Descriptor */
+ td->func = temp->func;
+ td->pc = temp->pc;
+ td->offset = temp->offset;
+ td->remainder = temp->len;
+ td->error = 0;
+ td->did_stall = 0;
+ td->short_pkt = temp->short_pkt;
+ td->alt_next = temp->setup_alt_next;
+}
+
+static void
+musbotg_setup_standard_chain(struct usb2_xfer *xfer)
+{
+ struct musbotg_std_temp temp;
+ struct musbotg_softc *sc;
+ struct musbotg_td *td;
+ uint32_t x;
+ uint8_t ep_no;
+
+ DPRINTFN(8, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpoint),
+ xfer->sumlen, usb2_get_speed(xfer->xroot->udev));
+
+ temp.max_frame_size = xfer->max_frame_size;
+
+ td = xfer->td_start[0];
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ /* setup temp */
+
+ temp.td = NULL;
+ temp.td_next = xfer->td_start[0];
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+ temp.offset = 0;
+
+ sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+ ep_no = (xfer->endpoint & UE_ADDR);
+
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+
+ temp.func = &musbotg_setup_rx;
+ temp.len = xfer->frlengths[0];
+ temp.pc = xfer->frbuffers + 0;
+ temp.short_pkt = temp.len ? 1 : 0;
+
+ musbotg_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ if (x != xfer->nframes) {
+ if (xfer->endpoint & UE_DIR_IN) {
+ if (xfer->flags_int.control_xfr)
+ temp.func = &musbotg_setup_data_tx;
+ else
+ temp.func = &musbotg_data_tx;
+ } else {
+ if (xfer->flags_int.control_xfr)
+ temp.func = &musbotg_setup_data_rx;
+ else
+ temp.func = &musbotg_data_rx;
+ }
+
+ /* setup "pc" pointer */
+ temp.pc = xfer->frbuffers + x;
+ }
+ while (x != xfer->nframes) {
+
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+
+ x++;
+
+ if (x == xfer->nframes) {
+ temp.setup_alt_next = 0;
+ }
+ if (temp.len == 0) {
+
+ /* make sure that we send an USB packet */
+
+ temp.short_pkt = 0;
+
+ } else {
+
+ /* regular data transfer */
+
+ temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+ }
+
+ musbotg_setup_standard_chain_sub(&temp);
+
+ if (xfer->flags_int.isochronous_xfr) {
+ temp.offset += temp.len;
+ } else {
+ /* get next Page Cache pointer */
+ temp.pc = xfer->frbuffers + x;
+ }
+ }
+
+ /* always setup a valid "pc" pointer for status and sync */
+ temp.pc = xfer->frbuffers + 0;
+
+ /* check if we should append a status stage */
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ /*
+ * Send a DATA1 message and invert the current
+ * endpoint direction.
+ */
+ temp.func = &musbotg_setup_status;
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ musbotg_setup_standard_chain_sub(&temp);
+ }
+ /* must have at least one frame! */
+ td = temp.td;
+ xfer->td_transfer_last = td;
+}
+
+static void
+musbotg_timeout(void *arg)
+{
+ struct usb2_xfer *xfer = arg;
+
+ DPRINTFN(1, "xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ musbotg_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+musbotg_ep_int_set(struct usb2_xfer *xfer, uint8_t on)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+ uint16_t temp;
+ uint8_t ep_no = xfer->endpoint & UE_ADDR;
+
+ /*
+ * Only enable the endpoint interrupt when we are
+ * actually waiting for data, hence we are dealing
+ * with level triggered interrupts !
+ */
+ if (ep_no == 0) {
+ temp = MUSB2_READ_2(sc, MUSB2_REG_INTTXE);
+ if (on)
+ temp |= MUSB2_MASK_EPINT(0);
+ else
+ temp &= ~MUSB2_MASK_EPINT(0);
+
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, temp);
+ } else {
+ if (USB_GET_DATA_ISREAD(xfer)) {
+ temp = MUSB2_READ_2(sc, MUSB2_REG_INTRXE);
+ if (on)
+ temp |= MUSB2_MASK_EPINT(ep_no);
+ else
+ temp &= ~MUSB2_MASK_EPINT(ep_no);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, temp);
+
+ } else {
+ temp = MUSB2_READ_2(sc, MUSB2_REG_INTTXE);
+ if (on)
+ temp |= MUSB2_MASK_EPINT(ep_no);
+ else
+ temp &= ~MUSB2_MASK_EPINT(ep_no);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, temp);
+ }
+ }
+}
+
+static void
+musbotg_start_standard_chain(struct usb2_xfer *xfer)
+{
+ DPRINTFN(8, "\n");
+
+ /* poll one time */
+ if (musbotg_xfer_do_fifo(xfer)) {
+
+ musbotg_ep_int_set(xfer, 1);
+
+ DPRINTFN(14, "enabled interrupts on endpoint\n");
+
+ /* put transfer on interrupt queue */
+ usb2_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usb2_transfer_timeout_ms(xfer,
+ &musbotg_timeout, xfer->timeout);
+ }
+ }
+}
+
+static void
+musbotg_root_intr_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+
+ DPRINTFN(8, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_PRE_DATA) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ musbotg_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* setup buffer */
+ std->ptr = sc->sc_hub_idata;
+ std->len = sizeof(sc->sc_hub_idata);
+
+ /* set port bit */
+ sc->sc_hub_idata[0] = 0x02; /* we only have one port */
+
+done:
+ return;
+}
+
+static usb2_error_t
+musbotg_standard_done_sub(struct usb2_xfer *xfer)
+{
+ struct musbotg_td *td;
+ uint32_t len;
+ uint8_t error;
+
+ DPRINTFN(8, "\n");
+
+ td = xfer->td_transfer_cache;
+
+ do {
+ len = td->remainder;
+
+ if (xfer->aframes != xfer->nframes) {
+ /*
+ * Verify the length and subtract
+ * the remainder from "frlengths[]":
+ */
+ if (len > xfer->frlengths[xfer->aframes]) {
+ td->error = 1;
+ } else {
+ xfer->frlengths[xfer->aframes] -= len;
+ }
+ }
+ /* Check for transfer error */
+ if (td->error) {
+ /* the transfer is finished */
+ error = 1;
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (len > 0) {
+ if (xfer->flags_int.short_frames_ok) {
+ /* follow alt next */
+ if (td->alt_next) {
+ td = td->obj_next;
+ } else {
+ td = NULL;
+ }
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ error = 0;
+ break;
+ }
+ td = td->obj_next;
+
+ /* this USB frame is complete */
+ error = 0;
+ break;
+
+ } while (0);
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ return (error ?
+ USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+musbotg_standard_done(struct usb2_xfer *xfer)
+{
+ usb2_error_t err = 0;
+
+ DPRINTFN(12, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->pipe);
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+
+ if (xfer->flags_int.control_hdr) {
+
+ err = musbotg_standard_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+
+ err = musbotg_standard_done_sub(xfer);
+ xfer->aframes++;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ err = musbotg_standard_done_sub(xfer);
+ }
+done:
+ musbotg_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * musbotg_device_done
+ *
+ * NOTE: this function can be called more than one time on the
+ * same USB transfer!
+ *------------------------------------------------------------------------*/
+static void
+musbotg_device_done(struct usb2_xfer *xfer, usb2_error_t error)
+{
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ DPRINTFN(2, "xfer=%p, pipe=%p, error=%d\n",
+ xfer, xfer->pipe, error);
+
+ if (xfer->flags_int.usb2_mode == USB_MODE_DEVICE) {
+
+ musbotg_ep_int_set(xfer, 0);
+
+ DPRINTFN(14, "disabled interrupts on endpoint\n");
+ }
+ /* dequeue transfer and start next transfer */
+ usb2_transfer_done(xfer, error);
+}
+
+static void
+musbotg_set_stall(struct usb2_device *udev, struct usb2_xfer *xfer,
+ struct usb2_pipe *pipe)
+{
+ struct musbotg_softc *sc;
+ uint8_t ep_no;
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ DPRINTFN(4, "pipe=%p\n", pipe);
+
+ if (xfer) {
+ /* cancel any ongoing transfers */
+ musbotg_device_done(xfer, USB_ERR_STALLED);
+ }
+ /* set FORCESTALL */
+ sc = MUSBOTG_BUS2SC(udev->bus);
+
+ ep_no = (pipe->edesc->bEndpointAddress & UE_ADDR);
+
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, ep_no);
+
+ if (pipe->edesc->bEndpointAddress & UE_DIR_IN) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSRL_TXSENDSTALL);
+ } else {
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL,
+ MUSB2_MASK_CSRL_RXSENDSTALL);
+ }
+}
+
+static void
+musbotg_clear_stall_sub(struct musbotg_softc *sc, uint16_t wMaxPacket,
+ uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir)
+{
+ uint16_t mps;
+ uint16_t temp;
+ uint8_t csr;
+
+ if (ep_type == UE_CONTROL) {
+ /* clearing stall is not needed */
+ return;
+ }
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, ep_no);
+
+ /* compute max frame size */
+ mps = wMaxPacket & 0x7FF;
+ switch ((wMaxPacket >> 11) & 3) {
+ case 1:
+ mps *= 2;
+ break;
+ case 2:
+ mps *= 3;
+ break;
+ default:
+ break;
+ }
+
+ if (ep_dir == UE_DIR_IN) {
+
+ temp = 0;
+
+ /* Configure endpoint */
+ switch (ep_type) {
+ case UE_INTERRUPT:
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH,
+ MUSB2_MASK_CSRH_TXMODE | temp);
+ break;
+ case UE_ISOCHRONOUS:
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH,
+ MUSB2_MASK_CSRH_TXMODE |
+ MUSB2_MASK_CSRH_TXISO | temp);
+ break;
+ case UE_BULK:
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH,
+ MUSB2_MASK_CSRH_TXMODE | temp);
+ break;
+ default:
+ break;
+ }
+
+ /* Need to flush twice in case of double bufring */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSRL_TXFFLUSH);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSRL_TXFFLUSH);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ }
+ }
+ /* reset data toggle */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSRL_TXDT_CLR);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ /* set double/single buffering */
+ temp = MUSB2_READ_2(sc, MUSB2_REG_TXDBDIS);
+ if (mps <= (sc->sc_hw_ep_profile[ep_no].
+ max_in_frame_size / 2)) {
+ /* double buffer */
+ temp &= ~(1 << ep_no);
+ } else {
+ /* single buffer */
+ temp |= (1 << ep_no);
+ }
+ MUSB2_WRITE_2(sc, MUSB2_REG_TXDBDIS, temp);
+
+ /* clear sent stall */
+ if (csr & MUSB2_MASK_CSRL_TXSENTSTALL) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ }
+ } else {
+
+ temp = 0;
+
+ /* Configure endpoint */
+ switch (ep_type) {
+ case UE_INTERRUPT:
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH,
+ MUSB2_MASK_CSRH_RXNYET | temp);
+ break;
+ case UE_ISOCHRONOUS:
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH,
+ MUSB2_MASK_CSRH_RXNYET |
+ MUSB2_MASK_CSRH_RXISO | temp);
+ break;
+ case UE_BULK:
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, temp);
+ break;
+ default:
+ break;
+ }
+
+ /* Need to flush twice in case of double bufring */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL);
+ if (csr & MUSB2_MASK_CSRL_RXPKTRDY) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL,
+ MUSB2_MASK_CSRL_RXFFLUSH);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL);
+ if (csr & MUSB2_MASK_CSRL_RXPKTRDY) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL,
+ MUSB2_MASK_CSRL_RXFFLUSH);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL);
+ }
+ }
+ /* reset data toggle */
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL,
+ MUSB2_MASK_CSRL_RXDT_CLR);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL);
+
+ /* set double/single buffering */
+ temp = MUSB2_READ_2(sc, MUSB2_REG_RXDBDIS);
+ if (mps <= (sc->sc_hw_ep_profile[ep_no].
+ max_out_frame_size / 2)) {
+ /* double buffer */
+ temp &= ~(1 << ep_no);
+ } else {
+ /* single buffer */
+ temp |= (1 << ep_no);
+ }
+ MUSB2_WRITE_2(sc, MUSB2_REG_RXDBDIS, temp);
+
+ /* clear sent stall */
+ if (csr & MUSB2_MASK_CSRL_RXSENTSTALL) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0);
+ }
+ }
+}
+
+static void
+musbotg_clear_stall(struct usb2_device *udev, struct usb2_pipe *pipe)
+{
+ struct musbotg_softc *sc;
+ struct usb2_endpoint_descriptor *ed;
+
+ DPRINTFN(4, "pipe=%p\n", pipe);
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ /* check mode */
+ if (udev->flags.usb2_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ /* get softc */
+ sc = MUSBOTG_BUS2SC(udev->bus);
+
+ /* get endpoint descriptor */
+ ed = pipe->edesc;
+
+ /* reset endpoint */
+ musbotg_clear_stall_sub(sc,
+ UGETW(ed->wMaxPacketSize),
+ (ed->bEndpointAddress & UE_ADDR),
+ (ed->bmAttributes & UE_XFERTYPE),
+ (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)));
+}
+
+usb2_error_t
+musbotg_init(struct musbotg_softc *sc)
+{
+ struct usb2_hw_ep_profile *pf;
+ uint8_t nrx;
+ uint8_t ntx;
+ uint8_t temp;
+ uint8_t fsize;
+ uint8_t frx;
+ uint8_t ftx;
+
+ DPRINTFN(1, "start\n");
+
+ /* set up the bus structure */
+ sc->sc_bus.usbrev = USB_REV_2_0;
+ sc->sc_bus.methods = &musbotg_bus_methods;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* turn on clocks */
+
+ if (sc->sc_clocks_on) {
+ (sc->sc_clocks_on) (sc->sc_clocks_arg);
+ }
+ /* wait a little for things to stabilise */
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+
+ /* disable all interrupts */
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, 0);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0);
+
+ /* disable pullup */
+
+ musbotg_pull_common(sc, 0);
+
+ /* wait a little bit (10ms) */
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100);
+
+ /* disable double packet buffering */
+ MUSB2_WRITE_2(sc, MUSB2_REG_RXDBDIS, 0xFFFF);
+ MUSB2_WRITE_2(sc, MUSB2_REG_TXDBDIS, 0xFFFF);
+
+ /* enable HighSpeed and ISO Update flags */
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_POWER,
+ MUSB2_MASK_HSENAB | MUSB2_MASK_ISOUPD);
+
+ /* clear Session bit, if set */
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_DEVCTL);
+ temp &= ~MUSB2_MASK_SESS;
+ MUSB2_WRITE_1(sc, MUSB2_REG_DEVCTL, temp);
+
+ DPRINTF("DEVCTL=0x%02x\n", temp);
+
+ /* disable testmode */
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_TESTMODE, 0);
+
+ /* set default value */
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_MISC, 0);
+
+ /* select endpoint index 0 */
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ /* read out number of endpoints */
+
+ nrx =
+ (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) / 16);
+
+ ntx =
+ (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) % 16);
+
+ /* 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");
+ }
+ /* read out configuration data */
+
+ sc->sc_conf_data = MUSB2_READ_1(sc, MUSB2_REG_CONFDATA);
+
+ DPRINTFN(2, "Config Data: 0x%02x\n",
+ sc->sc_conf_data);
+
+ DPRINTFN(2, "HW version: 0x%04x\n",
+ MUSB2_READ_1(sc, MUSB2_REG_HWVERS));
+
+ /* initialise endpoint profiles */
+
+ for (temp = 1; temp <= sc->sc_ep_max; temp++) {
+ pf = sc->sc_hw_ep_profile + temp;
+
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, temp);
+
+ fsize = MUSB2_READ_1(sc, MUSB2_REG_FSIZE);
+ frx = (fsize & MUSB2_MASK_RX_FSIZE) / 16;;
+ ftx = (fsize & MUSB2_MASK_TX_FSIZE);
+
+ DPRINTF("Endpoint %u FIFO size: IN=%u, OUT=%u\n",
+ temp, pf->max_in_frame_size,
+ pf->max_out_frame_size);
+
+ if (frx && ftx && (temp <= nrx) && (temp <= ntx)) {
+ pf->max_in_frame_size = 1 << ftx;
+ pf->max_out_frame_size = 1 << frx;
+ pf->is_simplex = 0; /* duplex */
+ pf->support_multi_buffer = 1;
+ pf->support_bulk = 1;
+ pf->support_interrupt = 1;
+ pf->support_isochronous = 1;
+ pf->support_in = 1;
+ pf->support_out = 1;
+ } else if (frx && (temp <= nrx)) {
+ pf->max_out_frame_size = 1 << frx;
+ pf->is_simplex = 1; /* simplex */
+ pf->support_multi_buffer = 1;
+ pf->support_bulk = 1;
+ pf->support_interrupt = 1;
+ pf->support_isochronous = 1;
+ pf->support_out = 1;
+ } else if (ftx && (temp <= ntx)) {
+ pf->max_in_frame_size = 1 << ftx;
+ pf->is_simplex = 1; /* simplex */
+ pf->support_multi_buffer = 1;
+ pf->support_bulk = 1;
+ pf->support_interrupt = 1;
+ pf->support_isochronous = 1;
+ pf->support_in = 1;
+ }
+ }
+
+ /* turn on default interrupts */
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE,
+ MUSB2_MASK_IRESET);
+
+ musbotg_clocks_off(sc);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* catch any lost interrupts */
+
+ musbotg_do_poll(&sc->sc_bus);
+
+ return (0); /* success */
+}
+
+void
+musbotg_uninit(struct musbotg_softc *sc)
+{
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* disable all interrupts */
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, 0);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0);
+
+ sc->sc_flags.port_powered = 0;
+ sc->sc_flags.status_vbus = 0;
+ sc->sc_flags.status_bus_reset = 0;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ musbotg_pull_down(sc);
+ musbotg_clocks_off(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+void
+musbotg_suspend(struct musbotg_softc *sc)
+{
+ return;
+}
+
+void
+musbotg_resume(struct musbotg_softc *sc)
+{
+ return;
+}
+
+static void
+musbotg_do_poll(struct usb2_bus *bus)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ musbotg_interrupt_poll(sc);
+ musbotg_root_ctrl_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*------------------------------------------------------------------------*
+ * musbotg bulk support
+ *------------------------------------------------------------------------*/
+static void
+musbotg_device_bulk_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_bulk_close(struct usb2_xfer *xfer)
+{
+ musbotg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+musbotg_device_bulk_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_bulk_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ musbotg_setup_standard_chain(xfer);
+ musbotg_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods musbotg_device_bulk_methods =
+{
+ .open = musbotg_device_bulk_open,
+ .close = musbotg_device_bulk_close,
+ .enter = musbotg_device_bulk_enter,
+ .start = musbotg_device_bulk_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * musbotg control support
+ *------------------------------------------------------------------------*/
+static void
+musbotg_device_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_ctrl_close(struct usb2_xfer *xfer)
+{
+ musbotg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+musbotg_device_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_ctrl_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ musbotg_setup_standard_chain(xfer);
+ musbotg_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods musbotg_device_ctrl_methods =
+{
+ .open = musbotg_device_ctrl_open,
+ .close = musbotg_device_ctrl_close,
+ .enter = musbotg_device_ctrl_enter,
+ .start = musbotg_device_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * musbotg interrupt support
+ *------------------------------------------------------------------------*/
+static void
+musbotg_device_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_intr_close(struct usb2_xfer *xfer)
+{
+ musbotg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+musbotg_device_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_intr_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ musbotg_setup_standard_chain(xfer);
+ musbotg_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods musbotg_device_intr_methods =
+{
+ .open = musbotg_device_intr_open,
+ .close = musbotg_device_intr_close,
+ .enter = musbotg_device_intr_enter,
+ .start = musbotg_device_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * musbotg full speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+musbotg_device_isoc_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_isoc_close(struct usb2_xfer *xfer)
+{
+ musbotg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+musbotg_device_isoc_enter(struct usb2_xfer *xfer)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+ uint32_t temp;
+ uint32_t nframes;
+ uint32_t fs_frames;
+
+ DPRINTFN(5, "xfer=%p next=%d nframes=%d\n",
+ xfer, xfer->pipe->isoc_next, xfer->nframes);
+
+ /* get the current frame index */
+
+ nframes = MUSB2_READ_2(sc, MUSB2_REG_FRAME);
+
+ /*
+ * check if the frame index is within the window where the frames
+ * will be inserted
+ */
+ temp = (nframes - xfer->pipe->isoc_next) & MUSB2_MASK_FRAME;
+
+ if (usb2_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) {
+ fs_frames = (xfer->nframes + 7) / 8;
+ } else {
+ fs_frames = xfer->nframes;
+ }
+
+ if ((xfer->pipe->is_synced == 0) ||
+ (temp < fs_frames)) {
+ /*
+ * If there is data underflow or the pipe queue is
+ * empty we schedule the transfer a few frames ahead
+ * of the current frame position. Else two isochronous
+ * transfers might overlap.
+ */
+ xfer->pipe->isoc_next = (nframes + 3) & MUSB2_MASK_FRAME;
+ xfer->pipe->is_synced = 1;
+ DPRINTFN(2, "start next=%d\n", xfer->pipe->isoc_next);
+ }
+ /*
+ * compute how many milliseconds the insertion is ahead of the
+ * current frame position:
+ */
+ temp = (xfer->pipe->isoc_next - nframes) & MUSB2_MASK_FRAME;
+
+ /*
+ * pre-compute when the isochronous transfer will be finished:
+ */
+ xfer->isoc_time_complete =
+ usb2_isoc_time_expand(&sc->sc_bus, nframes) + temp +
+ fs_frames;
+
+ /* compute frame number for next insertion */
+ xfer->pipe->isoc_next += fs_frames;
+
+ /* setup TDs */
+ musbotg_setup_standard_chain(xfer);
+}
+
+static void
+musbotg_device_isoc_start(struct usb2_xfer *xfer)
+{
+ /* start TD chain */
+ musbotg_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods musbotg_device_isoc_methods =
+{
+ .open = musbotg_device_isoc_open,
+ .close = musbotg_device_isoc_close,
+ .enter = musbotg_device_isoc_enter,
+ .start = musbotg_device_isoc_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * musbotg root control support
+ *------------------------------------------------------------------------*
+ * simulate a hardware HUB by handling
+ * all the necessary requests
+ *------------------------------------------------------------------------*/
+static void
+musbotg_root_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_root_ctrl_close(struct usb2_xfer *xfer)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_ctrl.xfer == xfer) {
+ sc->sc_root_ctrl.xfer = NULL;
+ }
+ musbotg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+/*
+ * USB descriptors for the virtual Root HUB:
+ */
+
+static const struct usb2_device_descriptor musbotg_devd = {
+ .bLength = sizeof(struct usb2_device_descriptor),
+ .bDescriptorType = UDESC_DEVICE,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_HSHUBSTT,
+ .bMaxPacketSize = 64,
+ .bcdDevice = {0x00, 0x01},
+ .iManufacturer = 1,
+ .iProduct = 2,
+ .bNumConfigurations = 1,
+};
+
+static const struct usb2_device_qualifier musbotg_odevd = {
+ .bLength = sizeof(struct usb2_device_qualifier),
+ .bDescriptorType = UDESC_DEVICE_QUALIFIER,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_FSHUB,
+ .bMaxPacketSize0 = 0,
+ .bNumConfigurations = 0,
+};
+
+static const struct musbotg_config_desc musbotg_confd = {
+ .confd = {
+ .bLength = sizeof(struct usb2_config_descriptor),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(musbotg_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0,
+ },
+ .ifcd = {
+ .bLength = sizeof(struct usb2_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = UIPROTO_HSHUBSTT,
+ },
+
+ .endpd = {
+ .bLength = sizeof(struct usb2_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = (UE_DIR_IN | MUSBOTG_INTR_ENDPT),
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 8,
+ .bInterval = 255,
+ },
+};
+
+static const struct usb2_hub_descriptor_min musbotg_hubd = {
+ .bDescLength = sizeof(musbotg_hubd),
+ .bDescriptorType = UDESC_HUB,
+ .bNbrPorts = 1,
+ .wHubCharacteristics[0] =
+ (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) & 0xFF,
+ .wHubCharacteristics[1] =
+ (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) >> 16,
+ .bPwrOn2PwrGood = 50,
+ .bHubContrCurrent = 0,
+ .DeviceRemovable = {0}, /* port is removable */
+};
+
+#define STRING_LANG \
+ 0x09, 0x04, /* American English */
+
+#define STRING_VENDOR \
+ 'M', 0, 'e', 0, 'n', 0, 't', 0, 'o', 0, 'r', 0, ' ', 0, \
+ 'G', 0, 'r', 0, 'a', 0, 'p', 0, 'h', 0, 'i', 0, 'c', 0, 's', 0
+
+#define STRING_PRODUCT \
+ 'O', 0, 'T', 0, 'G', 0, ' ', 0, 'R', 0, \
+ 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, \
+ 'U', 0, 'B', 0,
+
+USB_MAKE_STRING_DESC(STRING_LANG, musbotg_langtab);
+USB_MAKE_STRING_DESC(STRING_VENDOR, musbotg_vendor);
+USB_MAKE_STRING_DESC(STRING_PRODUCT, musbotg_product);
+
+static void
+musbotg_root_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_root_ctrl_start(struct usb2_xfer *xfer)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_ctrl.xfer = xfer;
+
+ usb2_bus_roothub_exec(xfer->xroot->bus);
+}
+
+static void
+musbotg_root_ctrl_task(struct usb2_bus *bus)
+{
+ musbotg_root_ctrl_poll(MUSBOTG_BUS2SC(bus));
+}
+
+static void
+musbotg_root_ctrl_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+ uint16_t value;
+ uint16_t index;
+ uint8_t use_polling;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_SETUP) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ musbotg_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* buffer reset */
+ std->ptr = USB_ADD_BYTES(&sc->sc_hub_temp, 0);
+ std->len = 0;
+
+ value = UGETW(std->req.wValue);
+ index = UGETW(std->req.wIndex);
+
+ use_polling = mtx_owned(xfer->xroot->xfer_mtx) ? 1 : 0;
+
+ /* demultiplex the control request */
+
+ switch (std->req.bmRequestType) {
+ case UT_READ_DEVICE:
+ switch (std->req.bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_descriptor;
+ case UR_GET_CONFIG:
+ goto tr_handle_get_config;
+ case UR_GET_STATUS:
+ goto tr_handle_get_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_DEVICE:
+ switch (std->req.bRequest) {
+ case UR_SET_ADDRESS:
+ goto tr_handle_set_address;
+ case UR_SET_CONFIG:
+ goto tr_handle_set_config;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_DESCRIPTOR:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_ENDPOINT:
+ switch (std->req.bRequest) {
+ case UR_CLEAR_FEATURE:
+ switch (UGETW(std->req.wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_clear_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_clear_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SET_FEATURE:
+ switch (UGETW(std->req.wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_set_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_set_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SYNCH_FRAME:
+ goto tr_valid; /* nop */
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_ENDPOINT:
+ switch (std->req.bRequest) {
+ case UR_GET_STATUS:
+ goto tr_handle_get_ep_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_INTERFACE:
+ switch (std->req.bRequest) {
+ case UR_SET_INTERFACE:
+ goto tr_handle_set_interface;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_INTERFACE:
+ switch (std->req.bRequest) {
+ case UR_GET_INTERFACE:
+ goto tr_handle_get_interface;
+ case UR_GET_STATUS:
+ goto tr_handle_get_iface_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_INTERFACE:
+ case UT_WRITE_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_READ_CLASS_INTERFACE:
+ case UT_READ_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_WRITE_CLASS_DEVICE:
+ switch (std->req.bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_valid;
+ case UR_SET_DESCRIPTOR:
+ case UR_SET_FEATURE:
+ break;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_OTHER:
+ switch (std->req.bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_handle_clear_port_feature;
+ case UR_SET_FEATURE:
+ goto tr_handle_set_port_feature;
+ case UR_CLEAR_TT_BUFFER:
+ case UR_RESET_TT:
+ case UR_STOP_TT:
+ goto tr_valid;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_OTHER:
+ switch (std->req.bRequest) {
+ case UR_GET_TT_STATE:
+ goto tr_handle_get_tt_state;
+ case UR_GET_STATUS:
+ goto tr_handle_get_port_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_DEVICE:
+ switch (std->req.bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_class_descriptor;
+ case UR_GET_STATUS:
+ goto tr_handle_get_class_status;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_valid;
+
+tr_handle_get_descriptor:
+ switch (value >> 8) {
+ case UDESC_DEVICE:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ std->len = sizeof(musbotg_devd);
+ std->ptr = USB_ADD_BYTES(&musbotg_devd, 0);
+ goto tr_valid;
+ case UDESC_CONFIG:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ std->len = sizeof(musbotg_confd);
+ std->ptr = USB_ADD_BYTES(&musbotg_confd, 0);
+ goto tr_valid;
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ std->len = sizeof(musbotg_langtab);
+ std->ptr = USB_ADD_BYTES(&musbotg_langtab, 0);
+ goto tr_valid;
+
+ case 1: /* Vendor */
+ std->len = sizeof(musbotg_vendor);
+ std->ptr = USB_ADD_BYTES(&musbotg_vendor, 0);
+ goto tr_valid;
+
+ case 2: /* Product */
+ std->len = sizeof(musbotg_product);
+ std->ptr = USB_ADD_BYTES(&musbotg_product, 0);
+ goto tr_valid;
+ default:
+ break;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_stalled;
+
+tr_handle_get_config:
+ std->len = 1;
+ sc->sc_hub_temp.wValue[0] = sc->sc_conf;
+ goto tr_valid;
+
+tr_handle_get_status:
+ std->len = 2;
+ USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED);
+ goto tr_valid;
+
+tr_handle_set_address:
+ if (value & 0xFF00) {
+ goto tr_stalled;
+ }
+ sc->sc_rt_addr = value;
+ goto tr_valid;
+
+tr_handle_set_config:
+ if (value >= 2) {
+ goto tr_stalled;
+ }
+ sc->sc_conf = value;
+ goto tr_valid;
+
+tr_handle_get_interface:
+ std->len = 1;
+ sc->sc_hub_temp.wValue[0] = 0;
+ goto tr_valid;
+
+tr_handle_get_tt_state:
+tr_handle_get_class_status:
+tr_handle_get_iface_status:
+tr_handle_get_ep_status:
+ std->len = 2;
+ USETW(sc->sc_hub_temp.wValue, 0);
+ goto tr_valid;
+
+tr_handle_set_halt:
+tr_handle_set_interface:
+tr_handle_set_wakeup:
+tr_handle_clear_wakeup:
+tr_handle_clear_halt:
+ goto tr_valid;
+
+tr_handle_clear_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(8, "UR_CLEAR_PORT_FEATURE on port %d\n", index);
+
+ switch (value) {
+ case UHF_PORT_SUSPEND:
+ musbotg_wakeup_peer(xfer);
+ break;
+
+ case UHF_PORT_ENABLE:
+ sc->sc_flags.port_enabled = 0;
+ break;
+
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ case UHF_C_PORT_ENABLE:
+ case UHF_C_PORT_OVER_CURRENT:
+ case UHF_C_PORT_RESET:
+ /* nops */
+ break;
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 0;
+ musbotg_pull_down(sc);
+ musbotg_clocks_off(sc);
+ break;
+ case UHF_C_PORT_CONNECTION:
+ sc->sc_flags.change_connect = 0;
+ break;
+ case UHF_C_PORT_SUSPEND:
+ sc->sc_flags.change_suspend = 0;
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_set_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(8, "UR_SET_PORT_FEATURE\n");
+
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ sc->sc_flags.port_enabled = 1;
+ break;
+ case UHF_PORT_SUSPEND:
+ case UHF_PORT_RESET:
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ /* nops */
+ break;
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 1;
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_get_port_status:
+
+ DPRINTFN(8, "UR_GET_PORT_STATUS\n");
+
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ if (sc->sc_flags.status_vbus) {
+ musbotg_clocks_on(sc);
+ musbotg_pull_up(sc);
+ } else {
+ musbotg_pull_down(sc);
+ musbotg_clocks_off(sc);
+ }
+
+ /* Select Device Side Mode */
+ value = UPS_PORT_MODE_DEVICE;
+
+ if (sc->sc_flags.status_high_speed) {
+ value |= UPS_HIGH_SPEED;
+ }
+ if (sc->sc_flags.port_powered) {
+ value |= UPS_PORT_POWER;
+ }
+ if (sc->sc_flags.port_enabled) {
+ value |= UPS_PORT_ENABLED;
+ }
+ if (sc->sc_flags.status_vbus &&
+ sc->sc_flags.status_bus_reset) {
+ value |= UPS_CURRENT_CONNECT_STATUS;
+ }
+ if (sc->sc_flags.status_suspend) {
+ value |= UPS_SUSPEND;
+ }
+ USETW(sc->sc_hub_temp.ps.wPortStatus, value);
+
+ value = 0;
+
+ if (sc->sc_flags.change_connect) {
+ value |= UPS_C_CONNECT_STATUS;
+
+ if (sc->sc_flags.status_vbus &&
+ sc->sc_flags.status_bus_reset) {
+ /* reset EP0 state */
+ sc->sc_ep0_busy = 0;
+ sc->sc_ep0_cmd = 0;
+ }
+ }
+ if (sc->sc_flags.change_suspend) {
+ value |= UPS_C_SUSPEND;
+ }
+ USETW(sc->sc_hub_temp.ps.wPortChange, value);
+ std->len = sizeof(sc->sc_hub_temp.ps);
+ goto tr_valid;
+
+tr_handle_get_class_descriptor:
+ if (value & 0xFF) {
+ goto tr_stalled;
+ }
+ std->ptr = USB_ADD_BYTES(&musbotg_hubd, 0);
+ std->len = sizeof(musbotg_hubd);
+ goto tr_valid;
+
+tr_stalled:
+ std->err = USB_ERR_STALLED;
+tr_valid:
+done:
+ return;
+}
+
+static void
+musbotg_root_ctrl_poll(struct musbotg_softc *sc)
+{
+ usb2_sw_transfer(&sc->sc_root_ctrl,
+ &musbotg_root_ctrl_done);
+}
+
+struct usb2_pipe_methods musbotg_root_ctrl_methods =
+{
+ .open = musbotg_root_ctrl_open,
+ .close = musbotg_root_ctrl_close,
+ .enter = musbotg_root_ctrl_enter,
+ .start = musbotg_root_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 0,
+};
+
+/*------------------------------------------------------------------------*
+ * musbotg root interrupt support
+ *------------------------------------------------------------------------*/
+static void
+musbotg_root_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_root_intr_close(struct usb2_xfer *xfer)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_intr.xfer == xfer) {
+ sc->sc_root_intr.xfer = NULL;
+ }
+ musbotg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+musbotg_root_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_root_intr_start(struct usb2_xfer *xfer)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_intr.xfer = xfer;
+}
+
+struct usb2_pipe_methods musbotg_root_intr_methods =
+{
+ .open = musbotg_root_intr_open,
+ .close = musbotg_root_intr_close,
+ .enter = musbotg_root_intr_enter,
+ .start = musbotg_root_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+static void
+musbotg_xfer_setup(struct usb2_setup_params *parm)
+{
+ const struct usb2_hw_ep_profile *pf;
+ struct musbotg_softc *sc;
+ struct usb2_xfer *xfer;
+ void *last_obj;
+ uint32_t ntd;
+ uint32_t n;
+ uint8_t ep_no;
+
+ sc = MUSBOTG_BUS2SC(parm->udev->bus);
+ xfer = parm->curr_xfer;
+
+ /*
+ * NOTE: This driver does not use any of the parameters that
+ * are computed from the following values. Just set some
+ * reasonable dummies:
+ */
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_frame_size = 0x400;
+
+ if ((parm->methods == &musbotg_device_isoc_methods) ||
+ (parm->methods == &musbotg_device_intr_methods))
+ parm->hc_max_packet_count = 3;
+ else
+ parm->hc_max_packet_count = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ /*
+ * compute maximum number of TDs
+ */
+ if (parm->methods == &musbotg_device_ctrl_methods) {
+
+ ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &musbotg_device_bulk_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &musbotg_device_intr_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &musbotg_device_isoc_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else {
+
+ ntd = 0;
+ }
+
+ /*
+ * check if "usb2_transfer_setup_sub" set an error
+ */
+ if (parm->err) {
+ return;
+ }
+ /*
+ * allocate transfer descriptors
+ */
+ last_obj = NULL;
+
+ /*
+ * get profile stuff
+ */
+ if (ntd) {
+
+ ep_no = xfer->endpoint & UE_ADDR;
+ musbotg_get_hw_ep_profile(parm->udev, &pf, ep_no);
+
+ if (pf == NULL) {
+ /* should not happen */
+ parm->err = USB_ERR_INVAL;
+ return;
+ }
+ } else {
+ ep_no = 0;
+ pf = NULL;
+ }
+
+ /* align data */
+ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+ for (n = 0; n != ntd; n++) {
+
+ struct musbotg_td *td;
+
+ if (parm->buf) {
+
+ td = USB_ADD_BYTES(parm->buf, parm->size[0]);
+
+ /* init TD */
+ td->max_frame_size = xfer->max_frame_size;
+ td->ep_no = ep_no;
+ td->obj_next = last_obj;
+
+ last_obj = td;
+ }
+ parm->size[0] += sizeof(*td);
+ }
+
+ xfer->td_start[0] = last_obj;
+}
+
+static void
+musbotg_xfer_unsetup(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_pipe_init(struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc,
+ struct usb2_pipe *pipe)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
+ pipe, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb2_mode,
+ sc->sc_rt_addr);
+
+ if (udev->device_index == sc->sc_rt_addr) {
+
+ if (udev->flags.usb2_mode != USB_MODE_HOST) {
+ /* not supported */
+ return;
+ }
+ switch (edesc->bEndpointAddress) {
+ case USB_CONTROL_ENDPOINT:
+ pipe->methods = &musbotg_root_ctrl_methods;
+ break;
+ case UE_DIR_IN | MUSBOTG_INTR_ENDPT:
+ pipe->methods = &musbotg_root_intr_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ } else {
+
+ if (udev->flags.usb2_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ if ((udev->speed != USB_SPEED_FULL) &&
+ (udev->speed != USB_SPEED_HIGH)) {
+ /* not supported */
+ return;
+ }
+ switch (edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_CONTROL:
+ pipe->methods = &musbotg_device_ctrl_methods;
+ break;
+ case UE_INTERRUPT:
+ pipe->methods = &musbotg_device_intr_methods;
+ break;
+ case UE_ISOCHRONOUS:
+ pipe->methods = &musbotg_device_isoc_methods;
+ break;
+ case UE_BULK:
+ pipe->methods = &musbotg_device_bulk_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+}
+
+struct usb2_bus_methods musbotg_bus_methods =
+{
+ .pipe_init = &musbotg_pipe_init,
+ .xfer_setup = &musbotg_xfer_setup,
+ .xfer_unsetup = &musbotg_xfer_unsetup,
+ .do_poll = &musbotg_do_poll,
+ .get_hw_ep_profile = &musbotg_get_hw_ep_profile,
+ .set_stall = &musbotg_set_stall,
+ .clear_stall = &musbotg_clear_stall,
+ .roothub_exec = &musbotg_root_ctrl_task,
+};
diff --git a/sys/dev/usb/controller/musb_otg.h b/sys/dev/usb/controller/musb_otg.h
new file mode 100644
index 0000000..0d880e1
--- /dev/null
+++ b/sys/dev/usb/controller/musb_otg.h
@@ -0,0 +1,407 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 2008 Hans Petter Selasky. 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.
+ */
+
+/*
+ * This header file defines the registers of the Mentor Graphics
+ * USB OnTheGo Inventra chip.
+ */
+
+#ifndef _MUSB2_OTG_H_
+#define _MUSB2_OTG_H_
+
+#define MUSB2_MAX_DEVICES (USB_MIN_DEVICES + 1)
+
+/* Common registers */
+
+#define MUSB2_REG_FADDR 0x0000 /* function address register */
+#define MUSB2_MASK_FADDR 0x7F
+
+#define MUSB2_REG_POWER 0x0001 /* power register */
+#define MUSB2_MASK_SUSPM_ENA 0x01
+#define MUSB2_MASK_SUSPMODE 0x02
+#define MUSB2_MASK_RESUME 0x04
+#define MUSB2_MASK_RESET 0x08
+#define MUSB2_MASK_HSMODE 0x10
+#define MUSB2_MASK_HSENAB 0x20
+#define MUSB2_MASK_SOFTC 0x40
+#define MUSB2_MASK_ISOUPD 0x80
+
+/* Endpoint interrupt handling */
+
+#define MUSB2_REG_INTTX 0x0002 /* transmit interrupt register */
+#define MUSB2_REG_INTRX 0x0004 /* receive interrupt register */
+#define MUSB2_REG_INTTXE 0x0006 /* transmit interrupt enable register */
+#define MUSB2_REG_INTRXE 0x0008 /* receive interrupt enable register */
+#define MUSB2_MASK_EPINT(epn) (1 << (epn)) /* epn = [0..15] */
+
+/* Common interrupt handling */
+
+#define MUSB2_REG_INTUSB 0x000A /* USB interrupt register */
+#define MUSB2_MASK_ISUSP 0x01
+#define MUSB2_MASK_IRESUME 0x02
+#define MUSB2_MASK_IRESET 0x04
+#define MUSB2_MASK_IBABBLE 0x04
+#define MUSB2_MASK_ISOF 0x08
+#define MUSB2_MASK_ICONN 0x10
+#define MUSB2_MASK_IDISC 0x20
+#define MUSB2_MASK_ISESSRQ 0x40
+#define MUSB2_MASK_IVBUSERR 0x80
+
+#define MUSB2_REG_INTUSBE 0x000B /* USB interrupt enable register */
+#define MUSB2_REG_FRAME 0x000C /* USB frame register */
+#define MUSB2_MASK_FRAME 0x3FF /* 0..1023 */
+
+#define MUSB2_REG_EPINDEX 0x000E /* endpoint index register */
+#define MUSB2_MASK_EPINDEX 0x0F
+
+#define MUSB2_REG_TESTMODE 0x000F /* test mode register */
+#define MUSB2_MASK_TSE0_NAK 0x01
+#define MUSB2_MASK_TJ 0x02
+#define MUSB2_MASK_TK 0x04
+#define MUSB2_MASK_TPACKET 0x08
+#define MUSB2_MASK_TFORCE_HS 0x10
+#define MUSB2_MASK_TFORCE_LS 0x20
+#define MUSB2_MASK_TFIFO_ACC 0x40
+#define MUSB2_MASK_TFORCE_HC 0x80
+
+#define MUSB2_REG_INDEXED_CSR 0x0010 /* EP control status register offset */
+
+#define MUSB2_REG_TXMAXP (0x0000 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_REG_RXMAXP (0x0004 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_PKTSIZE 0x03FF /* in bytes, should be even */
+#define MUSB2_MASK_PKTMULT 0xFC00 /* HS packet multiplier: 0..2 */
+
+#define MUSB2_REG_TXCSRL (0x0002 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_CSRL_TXPKTRDY 0x01
+#define MUSB2_MASK_CSRL_TXFIFONEMPTY 0x02
+#define MUSB2_MASK_CSRL_TXUNDERRUN 0x04 /* Device Mode */
+#define MUSB2_MASK_CSRL_TXERROR 0x04 /* Host Mode */
+#define MUSB2_MASK_CSRL_TXFFLUSH 0x08
+#define MUSB2_MASK_CSRL_TXSENDSTALL 0x10/* Device Mode */
+#define MUSB2_MASK_CSRL_TXSETUPPKT 0x10 /* Host Mode */
+#define MUSB2_MASK_CSRL_TXSENTSTALL 0x20/* Device Mode */
+#define MUSB2_MASK_CSRL_TXSTALLED 0x20 /* Host Mode */
+#define MUSB2_MASK_CSRL_TXDT_CLR 0x40
+#define MUSB2_MASK_CSRL_TXINCOMP 0x80
+
+/* Device Side Mode */
+#define MUSB2_MASK_CSR0L_RXPKTRDY 0x01
+#define MUSB2_MASK_CSR0L_TXPKTRDY 0x02
+#define MUSB2_MASK_CSR0L_SENTSTALL 0x04
+#define MUSB2_MASK_CSR0L_DATAEND 0x08
+#define MUSB2_MASK_CSR0L_SETUPEND 0x10
+#define MUSB2_MASK_CSR0L_SENDSTALL 0x20
+#define MUSB2_MASK_CSR0L_RXPKTRDY_CLR 0x40
+#define MUSB2_MASK_CSR0L_SETUPEND_CLR 0x80
+
+/* Host Side Mode */
+#define MUSB2_MASK_CSR0L_RXSTALL 0x04
+#define MUSB2_MASK_CSR0L_SETUPPKT 0x08
+#define MUSB2_MASK_CSR0L_ERROR 0x10
+#define MUSB2_MASK_CSR0L_REQPKT 0x20
+#define MUSB2_MASK_CSR0L_STATUSPKT 0x40
+#define MUSB2_MASK_CSR0L_NAKTIMO 0x80
+
+#define MUSB2_REG_TXCSRH (0x0003 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_CSRH_TXDT_VAL 0x01 /* Host Mode */
+#define MUSB2_MASK_CSRH_TXDT_WR 0x02 /* Host Mode */
+#define MUSB2_MASK_CSRH_TXDMAREQMODE 0x04
+#define MUSB2_MASK_CSRH_TXDT_SWITCH 0x08
+#define MUSB2_MASK_CSRH_TXDMAREQENA 0x10
+#define MUSB2_MASK_CSRH_RXMODE 0x00
+#define MUSB2_MASK_CSRH_TXMODE 0x20
+#define MUSB2_MASK_CSRH_TXISO 0x40 /* Device Mode */
+#define MUSB2_MASK_CSRH_TXAUTOSET 0x80
+
+#define MUSB2_MASK_CSR0H_FFLUSH 0x01 /* Device Side flush FIFO */
+#define MUSB2_MASK_CSR0H_DT 0x02 /* Host Side data toggle */
+#define MUSB2_MASK_CSR0H_DT_SET 0x04 /* Host Side */
+#define MUSB2_MASK_CSR0H_PING_DIS 0x08 /* Host Side */
+
+#define MUSB2_REG_RXCSRL (0x0006 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_CSRL_RXPKTRDY 0x01
+#define MUSB2_MASK_CSRL_RXFIFOFULL 0x02
+#define MUSB2_MASK_CSRL_RXOVERRUN 0x04
+#define MUSB2_MASK_CSRL_RXDATAERR 0x08
+#define MUSB2_MASK_CSRL_RXFFLUSH 0x10
+#define MUSB2_MASK_CSRL_RXSENDSTALL 0x20/* Device Mode */
+#define MUSB2_MASK_CSRL_RXREQPKT 0x20 /* Host Mode */
+#define MUSB2_MASK_CSRL_RXSENTSTALL 0x40/* Device Mode */
+#define MUSB2_MASK_CSRL_RXSTALL 0x40 /* Host Mode */
+#define MUSB2_MASK_CSRL_RXDT_CLR 0x80
+
+#define MUSB2_REG_RXCSRH (0x0007 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_CSRH_RXINCOMP 0x01
+#define MUSB2_MASK_CSRH_RXDT_VAL 0x02 /* Host Mode */
+#define MUSB2_MASK_CSRH_RXDT_SET 0x04 /* Host Mode */
+#define MUSB2_MASK_CSRH_RXDMAREQMODE 0x08
+#define MUSB2_MASK_CSRH_RXNYET 0x10
+#define MUSB2_MASK_CSRH_RXDMAREQENA 0x20
+#define MUSB2_MASK_CSRH_RXISO 0x40 /* Device Mode */
+#define MUSB2_MASK_CSRH_RXAUTOREQ 0x40 /* Host Mode */
+#define MUSB2_MASK_CSRH_RXAUTOCLEAR 0x80
+
+#define MUSB2_REG_RXCOUNT (0x0008 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_RXCOUNT 0xFFFF
+
+#define MUSB2_REG_TXTI (0x000A + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_REG_RXTI (0x000C + MUSB2_REG_INDEXED_CSR)
+
+/* Host Mode */
+#define MUSB2_MASK_TI_SPEED 0xC0
+#define MUSB2_MASK_TI_SPEED_LO 0xC0
+#define MUSB2_MASK_TI_SPEED_FS 0x80
+#define MUSB2_MASK_TI_SPEED_HS 0x40
+#define MUSB2_MASK_TI_PROTO_CTRL 0x00
+#define MUSB2_MASK_TI_PROTO_ISOC 0x10
+#define MUSB2_MASK_TI_PROTO_BULK 0x20
+#define MUSB2_MASK_TI_PROTO_INTR 0x30
+#define MUSB2_MASK_TI_EP_NUM 0x0F
+
+#define MUSB2_REG_TXNAKLIMIT (0x000B /* EPN=0 */ + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_REG_RXNAKLIMIT (0x000D /* EPN=0 */ + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_NAKLIMIT 0xFF
+
+#define MUSB2_REG_FSIZE (0x000F + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_RX_FSIZE 0xF0 /* 3..13, 2**n bytes */
+#define MUSB2_MASK_TX_FSIZE 0x0F /* 3..13, 2**n bytes */
+
+#define MUSB2_REG_EPFIFO(n) (0x0020 + (4*(n)))
+
+#define MUSB2_REG_CONFDATA 0x000F /* EPN=0 */
+#define MUSB2_MASK_CD_UTMI_DW 0x01
+#define MUSB2_MASK_CD_SOFTCONE 0x02
+#define MUSB2_MASK_CD_DYNFIFOSZ 0x04
+#define MUSB2_MASK_CD_HBTXE 0x08
+#define MUSB2_MASK_CD_HBRXE 0x10
+#define MUSB2_MASK_CD_BIGEND 0x20
+#define MUSB2_MASK_CD_MPTXE 0x40
+#define MUSB2_MASK_CD_MPRXE 0x80
+
+/* Various registers */
+
+#define MUSB2_REG_DEVCTL 0x0060
+#define MUSB2_MASK_SESS 0x01
+#define MUSB2_MASK_HOSTREQ 0x02
+#define MUSB2_MASK_HOSTMD 0x04
+#define MUSB2_MASK_VBUS0 0x08
+#define MUSB2_MASK_VBUS1 0x10
+#define MUSB2_MASK_LSDEV 0x20
+#define MUSB2_MASK_FSDEV 0x40
+#define MUSB2_MASK_BDEV 0x80
+
+#define MUSB2_REG_MISC 0x0061
+#define MUSB2_MASK_RXEDMA 0x01
+#define MUSB2_MASK_TXEDMA 0x02
+
+#define MUSB2_REG_TXFIFOSZ 0x0062
+#define MUSB2_REG_RXFIFOSZ 0x0063
+#define MUSB2_MASK_FIFODB 0x10 /* set if double buffering, r/w */
+#define MUSB2_MASK_FIFOSZ 0x0F
+#define MUSB2_VAL_FIFOSZ_8 0
+#define MUSB2_VAL_FIFOSZ_16 1
+#define MUSB2_VAL_FIFOSZ_32 2
+#define MUSB2_VAL_FIFOSZ_64 3
+#define MUSB2_VAL_FIFOSZ_128 4
+#define MUSB2_VAL_FIFOSZ_256 5
+#define MUSB2_VAL_FIFOSZ_512 6
+#define MUSB2_VAL_FIFOSZ_1024 7
+#define MUSB2_VAL_FIFOSZ_2048 8
+#define MUSB2_VAL_FIFOSZ_4096 9
+
+#define MUSB2_REG_TXFIFOADD 0x0064
+#define MUSB2_REG_RXFIFOADD 0x0066
+#define MUSB2_MASK_FIFOADD 0xFFF /* unit is 8-bytes */
+
+#define MUSB2_REG_VSTATUS 0x0068
+#define MUSB2_REG_VCONTROL 0x0068
+#define MUSB2_REG_HWVERS 0x006C
+#define MUSB2_REG_ULPI_BASE 0x0070
+
+#define MUSB2_REG_EPINFO 0x0078
+#define MUSB2_MASK_NRXEP 0xF0
+#define MUSB2_MASK_NTXEP 0x0F
+
+#define MUSB2_REG_RAMINFO 0x0079
+#define MUSB2_REG_LINKINFO 0x007A
+
+#define MUSB2_REG_VPLEN 0x007B
+#define MUSB2_MASK_VPLEN 0xFF
+
+#define MUSB2_REG_HS_EOF1 0x007C
+#define MUSB2_REG_FS_EOF1 0x007D
+#define MUSB2_REG_LS_EOF1 0x007E
+#define MUSB2_REG_SOFT_RST 0x007F
+#define MUSB2_MASK_SRST 0x01
+#define MUSB2_MASK_SRSTX 0x02
+
+#define MUSB2_REG_RQPKTCOUNT(n) (0x0300 + (4*(n))
+#define MUSB2_REG_RXDBDIS 0x0340
+#define MUSB2_REG_TXDBDIS 0x0342
+#define MUSB2_MASK_DB(n) (1 << (n)) /* disable double buffer, n = [0..15] */
+
+#define MUSB2_REG_CHIRPTO 0x0344
+#define MUSB2_REG_HSRESUM 0x0346
+
+/* Host Mode only registers */
+
+#define MUSB2_REG_TXFADDR(n) (0x0080 + (8*(n)))
+#define MUSB2_REG_TXHADDR(n) (0x0082 + (8*(n)))
+#define MUSB2_REG_TXHUBPORT(n) (0x0083 + (8*(n)))
+#define MUSB2_REG_RXFADDR(n) (0x0084 + (8*(n)))
+#define MUSB2_REG_RXHADDR(n) (0x0086 + (8*(n)))
+#define MUSB2_REG_RXHPORT(n) (0x0087 + (8*(n)))
+
+#define MUSB2_EP_MAX 16 /* maximum number of endpoints */
+
+#define MUSB2_READ_2(sc, reg) \
+ bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, reg)
+
+#define MUSB2_WRITE_2(sc, reg, data) \
+ bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data)
+
+#define MUSB2_READ_1(sc, reg) \
+ bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg)
+
+#define MUSB2_WRITE_1(sc, reg, data) \
+ bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data)
+
+struct musbotg_td;
+struct musbotg_softc;
+
+typedef uint8_t (musbotg_cmd_t)(struct musbotg_td *td);
+
+struct musbotg_dma {
+ struct musbotg_softc *sc;
+ uint32_t dma_chan;
+ uint8_t busy:1;
+ uint8_t complete:1;
+ uint8_t error:1;
+};
+
+struct musbotg_td {
+ struct musbotg_td *obj_next;
+ musbotg_cmd_t *func;
+ struct usb2_page_cache *pc;
+ uint32_t offset;
+ uint32_t remainder;
+ uint16_t max_frame_size; /* packet_size * mult */
+ uint8_t ep_no;
+ uint8_t error:1;
+ uint8_t alt_next:1;
+ uint8_t short_pkt:1;
+ uint8_t support_multi_buffer:1;
+ uint8_t did_stall:1;
+ uint8_t dma_enabled:1;
+};
+
+struct musbotg_std_temp {
+ musbotg_cmd_t *func;
+ struct usb2_page_cache *pc;
+ struct musbotg_td *td;
+ struct musbotg_td *td_next;
+ uint32_t len;
+ uint32_t offset;
+ uint16_t max_frame_size;
+ uint8_t short_pkt;
+ /*
+ * short_pkt = 0: transfer should be short terminated
+ * short_pkt = 1: transfer should not be short terminated
+ */
+ uint8_t setup_alt_next;
+};
+
+struct musbotg_config_desc {
+ struct usb2_config_descriptor confd;
+ struct usb2_interface_descriptor ifcd;
+ struct usb2_endpoint_descriptor endpd;
+} __packed;
+
+union musbotg_hub_temp {
+ uWord wValue;
+ struct usb2_port_status ps;
+};
+
+struct musbotg_flags {
+ uint8_t change_connect:1;
+ uint8_t change_suspend:1;
+ uint8_t status_suspend:1; /* set if suspended */
+ uint8_t status_vbus:1; /* set if present */
+ uint8_t status_bus_reset:1; /* set if reset complete */
+ uint8_t status_high_speed:1; /* set if High Speed is selected */
+ uint8_t remote_wakeup:1;
+ uint8_t self_powered:1;
+ uint8_t clocks_off:1;
+ uint8_t port_powered:1;
+ uint8_t port_enabled:1;
+ uint8_t d_pulled_up:1;
+};
+
+struct musbotg_softc {
+ struct usb2_bus sc_bus;
+ union musbotg_hub_temp sc_hub_temp;
+ struct usb2_sw_transfer sc_root_ctrl;
+ struct usb2_sw_transfer sc_root_intr;
+ struct usb2_hw_ep_profile sc_hw_ep_profile[16];
+
+ struct usb2_device *sc_devices[MUSB2_MAX_DEVICES];
+ struct resource *sc_io_res;
+ struct resource *sc_irq_res;
+ void *sc_intr_hdl;
+ bus_size_t sc_io_size;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+
+ void (*sc_clocks_on) (void *arg);
+ void (*sc_clocks_off) (void *arg);
+ void *sc_clocks_arg;
+
+ uint32_t sc_bounce_buf[(1024 * 3) / 4]; /* bounce buffer */
+
+ uint8_t sc_ep_max; /* maximum number of RX and TX
+ * endpoints supported */
+ uint8_t sc_rt_addr; /* root HUB address */
+ uint8_t sc_dv_addr; /* device address */
+ uint8_t sc_conf; /* root HUB config */
+ uint8_t sc_ep0_busy; /* set if ep0 is busy */
+ uint8_t sc_ep0_cmd; /* pending commands */
+ uint8_t sc_conf_data; /* copy of hardware register */
+
+ uint8_t sc_hub_idata[1];
+
+ struct musbotg_flags sc_flags;
+};
+
+/* prototypes */
+
+usb2_error_t musbotg_init(struct musbotg_softc *sc);
+void musbotg_uninit(struct musbotg_softc *sc);
+void musbotg_suspend(struct musbotg_softc *sc);
+void musbotg_resume(struct musbotg_softc *sc);
+void musbotg_interrupt(struct musbotg_softc *sc);
+void musbotg_vbus_interrupt(struct musbotg_softc *sc, uint8_t is_on);
+
+#endif /* _MUSB2_OTG_H_ */
diff --git a/sys/dev/usb/controller/musb_otg_atmelarm.c b/sys/dev/usb/controller/musb_otg_atmelarm.c
new file mode 100644
index 0000000..7652424
--- /dev/null
+++ b/sys/dev/usb/controller/musb_otg_atmelarm.c
@@ -0,0 +1,239 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 2008 Hans Petter Selasky. 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 <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_defs.h>
+#include <dev/usb/usb.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/mus2_otg.h>
+
+#include <sys/rman.h>
+
+static device_probe_t musbotg_probe;
+static device_attach_t musbotg_attach;
+static device_detach_t musbotg_detach;
+static device_shutdown_t musbotg_shutdown;
+
+struct musbotg_super_softc {
+ struct musbotg_softc sc_otg; /* must be first */
+};
+
+static void
+musbotg_vbus_poll(struct musbotg_super_softc *sc)
+{
+ uint8_t vbus_val = 1; /* fake VBUS on - TODO */
+
+ /* just forward it */
+ musbotg_vbus_interrupt(&sc->sc_otg, vbus_val);
+}
+
+static void
+musbotg_clocks_on(void *arg)
+{
+#if 0
+ struct musbotg_super_softc *sc = arg;
+
+#endif
+}
+
+static void
+musbotg_clocks_off(void *arg)
+{
+#if 0
+ struct musbotg_super_softc *sc = arg;
+
+#endif
+}
+
+static int
+musbotg_probe(device_t dev)
+{
+ device_set_desc(dev, "MUSB OTG integrated USB controller");
+ return (0);
+}
+
+static int
+musbotg_attach(device_t dev)
+{
+ struct musbotg_super_softc *sc = device_get_softc(dev);
+ int err;
+ int rid;
+
+ /* setup MUSB OTG USB controller interface softc */
+ sc->sc_otg.sc_clocks_on = &musbotg_clocks_on;
+ sc->sc_otg.sc_clocks_off = &musbotg_clocks_off;
+ sc->sc_otg.sc_clocks_arg = sc;
+
+ /* initialise some bus fields */
+ sc->sc_otg.sc_bus.parent = dev;
+ sc->sc_otg.sc_bus.devices = sc->sc_otg.sc_devices;
+ sc->sc_otg.sc_bus.devices_max = MUSB2_MAX_DEVICES;
+
+ /* get all DMA memory */
+ if (usb2_bus_mem_alloc_all(&sc->sc_otg.sc_bus,
+ USB_GET_DMA_TAG(dev), NULL)) {
+ return (ENOMEM);
+ }
+ rid = 0;
+ sc->sc_otg.sc_io_res =
+ bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
+
+ if (!(sc->sc_otg.sc_io_res)) {
+ err = ENOMEM;
+ goto error;
+ }
+ sc->sc_otg.sc_io_tag = rman_get_bustag(sc->sc_otg.sc_io_res);
+ sc->sc_otg.sc_io_hdl = rman_get_bushandle(sc->sc_otg.sc_io_res);
+ sc->sc_otg.sc_io_size = rman_get_size(sc->sc_otg.sc_io_res);
+
+ rid = 0;
+ sc->sc_otg.sc_irq_res =
+ bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE);
+ if (!(sc->sc_otg.sc_irq_res)) {
+ goto error;
+ }
+ sc->sc_otg.sc_bus.bdev = device_add_child(dev, "usbus", -1);
+ if (!(sc->sc_otg.sc_bus.bdev)) {
+ goto error;
+ }
+ device_set_ivars(sc->sc_otg.sc_bus.bdev, &sc->sc_otg.sc_bus);
+
+#if (__FreeBSD_version >= 700031)
+ err = bus_setup_intr(dev, sc->sc_otg.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (void *)musbotg_interrupt, sc, &sc->sc_otg.sc_intr_hdl);
+#else
+ err = bus_setup_intr(dev, sc->sc_otg.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ (void *)musbotg_interrupt, sc, &sc->sc_otg.sc_intr_hdl);
+#endif
+ if (err) {
+ sc->sc_otg.sc_intr_hdl = NULL;
+ goto error;
+ }
+ err = musbotg_init(&sc->sc_otg);
+ if (!err) {
+ err = device_probe_and_attach(sc->sc_otg.sc_bus.bdev);
+ }
+ if (err) {
+ goto error;
+ } else {
+ /* poll VBUS one time */
+ musbotg_vbus_poll(sc);
+ }
+ return (0);
+
+error:
+ musbotg_detach(dev);
+ return (ENXIO);
+}
+
+static int
+musbotg_detach(device_t dev)
+{
+ struct musbotg_super_softc *sc = device_get_softc(dev);
+ device_t bdev;
+ int err;
+
+ if (sc->sc_otg.sc_bus.bdev) {
+ bdev = sc->sc_otg.sc_bus.bdev;
+ device_detach(bdev);
+ device_delete_child(dev, bdev);
+ }
+ /* during module unload there are lots of children leftover */
+ device_delete_all_children(dev);
+
+ if (sc->sc_otg.sc_irq_res && sc->sc_otg.sc_intr_hdl) {
+ /*
+ * only call musbotg_uninit() after musbotg_init()
+ */
+ musbotg_uninit(&sc->sc_otg);
+
+ err = bus_teardown_intr(dev, sc->sc_otg.sc_irq_res,
+ sc->sc_otg.sc_intr_hdl);
+ sc->sc_otg.sc_intr_hdl = NULL;
+ }
+ /* free IRQ channel, if any */
+ if (sc->sc_otg.sc_irq_res) {
+ bus_release_resource(dev, SYS_RES_IRQ, 0,
+ sc->sc_otg.sc_irq_res);
+ sc->sc_otg.sc_irq_res = NULL;
+ }
+ /* free memory resource, if any */
+ if (sc->sc_otg.sc_io_res) {
+ bus_release_resource(dev, SYS_RES_MEMORY, 0,
+ sc->sc_otg.sc_io_res);
+ sc->sc_otg.sc_io_res = NULL;
+ }
+ usb2_bus_mem_free_all(&sc->sc_otg.sc_bus, NULL);
+
+ return (0);
+}
+
+static int
+musbotg_shutdown(device_t dev)
+{
+ struct musbotg_super_softc *sc = device_get_softc(dev);
+ int err;
+
+ err = bus_generic_shutdown(dev);
+ if (err)
+ return (err);
+
+ musbotg_uninit(&sc->sc_otg);
+
+ return (0);
+}
+
+static device_method_t musbotg_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, musbotg_probe),
+ DEVMETHOD(device_attach, musbotg_attach),
+ DEVMETHOD(device_detach, musbotg_detach),
+ DEVMETHOD(device_shutdown, musbotg_shutdown),
+
+ /* Bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+
+ {0, 0}
+};
+
+static driver_t musbotg_driver = {
+ "musbotg",
+ musbotg_methods,
+ sizeof(struct musbotg_super_softc),
+};
+
+static devclass_t musbotg_devclass;
+
+DRIVER_MODULE(musbotg, atmelarm, musbotg_driver, musbotg_devclass, 0, 0);
+MODULE_DEPEND(musbotg, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/ohci.c b/sys/dev/usb/controller/ohci.c
new file mode 100644
index 0000000..7751bd5
--- /dev/null
+++ b/sys/dev/usb/controller/ohci.c
@@ -0,0 +1,2862 @@
+/*-
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
+ * Copyright (c) 1998 Lennart Augustsson. 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$");
+
+/*
+ * USB Open Host Controller driver.
+ *
+ * OHCI spec: http://www.compaq.com/productinfo/development/openhci.html
+ * USB spec: http://www.usb.org/developers/docs/usbspec.zip
+ */
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_error.h>
+#include <dev/usb/usb_defs.h>
+
+#define USB_DEBUG_VAR ohcidebug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/ohci.h>
+
+#define OHCI_BUS2SC(bus) ((ohci_softc_t *)(((uint8_t *)(bus)) - \
+ USB_P2U(&(((ohci_softc_t *)0)->sc_bus))))
+
+#if USB_DEBUG
+static int ohcidebug = 0;
+
+SYSCTL_NODE(_hw_usb2, OID_AUTO, ohci, CTLFLAG_RW, 0, "USB ohci");
+SYSCTL_INT(_hw_usb2_ohci, OID_AUTO, debug, CTLFLAG_RW,
+ &ohcidebug, 0, "ohci debug level");
+static void ohci_dumpregs(ohci_softc_t *);
+static void ohci_dump_tds(ohci_td_t *);
+static uint8_t ohci_dump_td(ohci_td_t *);
+static void ohci_dump_ed(ohci_ed_t *);
+static uint8_t ohci_dump_itd(ohci_itd_t *);
+static void ohci_dump_itds(ohci_itd_t *);
+
+#endif
+
+#define OBARR(sc) bus_space_barrier((sc)->sc_io_tag, (sc)->sc_io_hdl, 0, (sc)->sc_io_size, \
+ BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE)
+#define OWRITE1(sc, r, x) \
+ do { OBARR(sc); bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0)
+#define OWRITE2(sc, r, x) \
+ do { OBARR(sc); bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0)
+#define OWRITE4(sc, r, x) \
+ do { OBARR(sc); bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0)
+#define OREAD1(sc, r) (OBARR(sc), bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r)))
+#define OREAD2(sc, r) (OBARR(sc), bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r)))
+#define OREAD4(sc, r) (OBARR(sc), bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r)))
+
+#define OHCI_INTR_ENDPT 1
+
+extern struct usb2_bus_methods ohci_bus_methods;
+extern struct usb2_pipe_methods ohci_device_bulk_methods;
+extern struct usb2_pipe_methods ohci_device_ctrl_methods;
+extern struct usb2_pipe_methods ohci_device_intr_methods;
+extern struct usb2_pipe_methods ohci_device_isoc_methods;
+extern struct usb2_pipe_methods ohci_root_ctrl_methods;
+extern struct usb2_pipe_methods ohci_root_intr_methods;
+
+static void ohci_root_ctrl_poll(struct ohci_softc *sc);
+static void ohci_do_poll(struct usb2_bus *bus);
+static void ohci_device_done(struct usb2_xfer *xfer, usb2_error_t error);
+
+static usb2_sw_transfer_func_t ohci_root_intr_done;
+static usb2_sw_transfer_func_t ohci_root_ctrl_done;
+static void ohci_timeout(void *arg);
+static uint8_t ohci_check_transfer(struct usb2_xfer *xfer);
+
+struct ohci_std_temp {
+ struct usb2_page_cache *pc;
+ ohci_td_t *td;
+ ohci_td_t *td_next;
+ uint32_t average;
+ uint32_t td_flags;
+ uint32_t len;
+ uint16_t max_frame_size;
+ uint8_t shortpkt;
+ uint8_t setup_alt_next;
+ uint8_t short_frames_ok;
+};
+
+static struct ohci_hcca *
+ohci_get_hcca(ohci_softc_t *sc)
+{
+ usb2_pc_cpu_invalidate(&sc->sc_hw.hcca_pc);
+ return (sc->sc_hcca_p);
+}
+
+void
+ohci_iterate_hw_softc(struct usb2_bus *bus, usb2_bus_mem_sub_cb_t *cb)
+{
+ struct ohci_softc *sc = OHCI_BUS2SC(bus);
+ uint32_t i;
+
+ cb(bus, &sc->sc_hw.hcca_pc, &sc->sc_hw.hcca_pg,
+ sizeof(ohci_hcca_t), OHCI_HCCA_ALIGN);
+
+ cb(bus, &sc->sc_hw.ctrl_start_pc, &sc->sc_hw.ctrl_start_pg,
+ sizeof(ohci_ed_t), OHCI_ED_ALIGN);
+
+ cb(bus, &sc->sc_hw.bulk_start_pc, &sc->sc_hw.bulk_start_pg,
+ sizeof(ohci_ed_t), OHCI_ED_ALIGN);
+
+ cb(bus, &sc->sc_hw.isoc_start_pc, &sc->sc_hw.isoc_start_pg,
+ sizeof(ohci_ed_t), OHCI_ED_ALIGN);
+
+ for (i = 0; i != OHCI_NO_EDS; i++) {
+ cb(bus, sc->sc_hw.intr_start_pc + i, sc->sc_hw.intr_start_pg + i,
+ sizeof(ohci_ed_t), OHCI_ED_ALIGN);
+ }
+}
+
+static usb2_error_t
+ohci_controller_init(ohci_softc_t *sc)
+{
+ struct usb2_page_search buf_res;
+ uint32_t i;
+ uint32_t ctl;
+ uint32_t ival;
+ uint32_t hcr;
+ uint32_t fm;
+ uint32_t per;
+ uint32_t desca;
+
+ /* Determine in what context we are running. */
+ ctl = OREAD4(sc, OHCI_CONTROL);
+ if (ctl & OHCI_IR) {
+ /* SMM active, request change */
+ DPRINTF("SMM active, request owner change\n");
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_OCR);
+ for (i = 0; (i < 100) && (ctl & OHCI_IR); i++) {
+ usb2_pause_mtx(NULL, hz / 1000);
+ ctl = OREAD4(sc, OHCI_CONTROL);
+ }
+ if (ctl & OHCI_IR) {
+ device_printf(sc->sc_bus.bdev,
+ "SMM does not respond, resetting\n");
+ OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET);
+ goto reset;
+ }
+ } else {
+ DPRINTF("cold started\n");
+reset:
+ /* controller was cold started */
+ usb2_pause_mtx(NULL,
+ USB_MS_TO_TICKS(USB_BUS_RESET_DELAY));
+ }
+
+ /*
+ * This reset should not be necessary according to the OHCI spec, but
+ * without it some controllers do not start.
+ */
+ DPRINTF("%s: resetting\n", device_get_nameunit(sc->sc_bus.bdev));
+ OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET);
+
+ usb2_pause_mtx(NULL,
+ USB_MS_TO_TICKS(USB_BUS_RESET_DELAY));
+
+ /* we now own the host controller and the bus has been reset */
+ ival = OHCI_GET_IVAL(OREAD4(sc, OHCI_FM_INTERVAL));
+
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_HCR); /* Reset HC */
+ /* nominal time for a reset is 10 us */
+ for (i = 0; i < 10; i++) {
+ DELAY(10);
+ hcr = OREAD4(sc, OHCI_COMMAND_STATUS) & OHCI_HCR;
+ if (!hcr) {
+ break;
+ }
+ }
+ if (hcr) {
+ device_printf(sc->sc_bus.bdev, "reset timeout\n");
+ return (USB_ERR_IOERROR);
+ }
+#if USB_DEBUG
+ if (ohcidebug > 15) {
+ ohci_dumpregs(sc);
+ }
+#endif
+
+ /* The controller is now in SUSPEND state, we have 2ms to finish. */
+
+ /* set up HC registers */
+ usb2_get_page(&sc->sc_hw.hcca_pc, 0, &buf_res);
+ OWRITE4(sc, OHCI_HCCA, buf_res.physaddr);
+
+ usb2_get_page(&sc->sc_hw.ctrl_start_pc, 0, &buf_res);
+ OWRITE4(sc, OHCI_CONTROL_HEAD_ED, buf_res.physaddr);
+
+ usb2_get_page(&sc->sc_hw.bulk_start_pc, 0, &buf_res);
+ OWRITE4(sc, OHCI_BULK_HEAD_ED, buf_res.physaddr);
+
+ /* disable all interrupts and then switch on all desired interrupts */
+ OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS);
+ OWRITE4(sc, OHCI_INTERRUPT_ENABLE, sc->sc_eintrs | OHCI_MIE);
+ /* switch on desired functional features */
+ ctl = OREAD4(sc, OHCI_CONTROL);
+ ctl &= ~(OHCI_CBSR_MASK | OHCI_LES | OHCI_HCFS_MASK | OHCI_IR);
+ ctl |= OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE |
+ OHCI_RATIO_1_4 | OHCI_HCFS_OPERATIONAL;
+ /* And finally start it! */
+ OWRITE4(sc, OHCI_CONTROL, ctl);
+
+ /*
+ * The controller is now OPERATIONAL. Set a some final
+ * registers that should be set earlier, but that the
+ * controller ignores when in the SUSPEND state.
+ */
+ fm = (OREAD4(sc, OHCI_FM_INTERVAL) & OHCI_FIT) ^ OHCI_FIT;
+ fm |= OHCI_FSMPS(ival) | ival;
+ OWRITE4(sc, OHCI_FM_INTERVAL, fm);
+ per = OHCI_PERIODIC(ival); /* 90% periodic */
+ OWRITE4(sc, OHCI_PERIODIC_START, per);
+
+ /* Fiddle the No OverCurrent Protection bit to avoid chip bug. */
+ desca = OREAD4(sc, OHCI_RH_DESCRIPTOR_A);
+ OWRITE4(sc, OHCI_RH_DESCRIPTOR_A, desca | OHCI_NOCP);
+ OWRITE4(sc, OHCI_RH_STATUS, OHCI_LPSC); /* Enable port power */
+ usb2_pause_mtx(NULL,
+ USB_MS_TO_TICKS(OHCI_ENABLE_POWER_DELAY));
+ OWRITE4(sc, OHCI_RH_DESCRIPTOR_A, desca);
+
+ /*
+ * The AMD756 requires a delay before re-reading the register,
+ * otherwise it will occasionally report 0 ports.
+ */
+ sc->sc_noport = 0;
+ for (i = 0; (i < 10) && (sc->sc_noport == 0); i++) {
+ usb2_pause_mtx(NULL,
+ USB_MS_TO_TICKS(OHCI_READ_DESC_DELAY));
+ sc->sc_noport = OHCI_GET_NDP(OREAD4(sc, OHCI_RH_DESCRIPTOR_A));
+ }
+
+#if USB_DEBUG
+ if (ohcidebug > 5) {
+ ohci_dumpregs(sc);
+ }
+#endif
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static struct ohci_ed *
+ohci_init_ed(struct usb2_page_cache *pc)
+{
+ struct usb2_page_search buf_res;
+ struct ohci_ed *ed;
+
+ usb2_get_page(pc, 0, &buf_res);
+
+ ed = buf_res.buffer;
+
+ ed->ed_self = htole32(buf_res.physaddr);
+ ed->ed_flags = htole32(OHCI_ED_SKIP);
+ ed->page_cache = pc;
+
+ return (ed);
+}
+
+usb2_error_t
+ohci_init(ohci_softc_t *sc)
+{
+ struct usb2_page_search buf_res;
+ uint16_t i;
+ uint16_t bit;
+ uint16_t x;
+ uint16_t y;
+
+ DPRINTF("start\n");
+
+ sc->sc_eintrs = OHCI_NORMAL_INTRS;
+
+ /*
+ * Setup all ED's
+ */
+
+ sc->sc_ctrl_p_last =
+ ohci_init_ed(&sc->sc_hw.ctrl_start_pc);
+
+ sc->sc_bulk_p_last =
+ ohci_init_ed(&sc->sc_hw.bulk_start_pc);
+
+ sc->sc_isoc_p_last =
+ ohci_init_ed(&sc->sc_hw.isoc_start_pc);
+
+ for (i = 0; i != OHCI_NO_EDS; i++) {
+ sc->sc_intr_p_last[i] =
+ ohci_init_ed(sc->sc_hw.intr_start_pc + i);
+ }
+
+ /*
+ * the QHs are arranged to give poll intervals that are
+ * powers of 2 times 1ms
+ */
+ bit = OHCI_NO_EDS / 2;
+ while (bit) {
+ x = bit;
+ while (x & bit) {
+ ohci_ed_t *ed_x;
+ ohci_ed_t *ed_y;
+
+ y = (x ^ bit) | (bit / 2);
+
+ /*
+ * the next QH has half the poll interval
+ */
+ ed_x = sc->sc_intr_p_last[x];
+ ed_y = sc->sc_intr_p_last[y];
+
+ ed_x->next = NULL;
+ ed_x->ed_next = ed_y->ed_self;
+
+ x++;
+ }
+ bit >>= 1;
+ }
+
+ if (1) {
+
+ ohci_ed_t *ed_int;
+ ohci_ed_t *ed_isc;
+
+ ed_int = sc->sc_intr_p_last[0];
+ ed_isc = sc->sc_isoc_p_last;
+
+ /* the last (1ms) QH */
+ ed_int->next = ed_isc;
+ ed_int->ed_next = ed_isc->ed_self;
+ }
+ usb2_get_page(&sc->sc_hw.hcca_pc, 0, &buf_res);
+
+ sc->sc_hcca_p = buf_res.buffer;
+
+ /*
+ * Fill HCCA interrupt table. The bit reversal is to get
+ * the tree set up properly to spread the interrupts.
+ */
+ for (i = 0; i != OHCI_NO_INTRS; i++) {
+ sc->sc_hcca_p->hcca_interrupt_table[i] =
+ sc->sc_intr_p_last[i | (OHCI_NO_EDS / 2)]->ed_self;
+ }
+ /* flush all cache into memory */
+
+ usb2_bus_mem_flush_all(&sc->sc_bus, &ohci_iterate_hw_softc);
+
+ /* set up the bus struct */
+ sc->sc_bus.methods = &ohci_bus_methods;
+
+ usb2_callout_init_mtx(&sc->sc_tmo_rhsc, &sc->sc_bus.bus_mtx, 0);
+
+#if USB_DEBUG
+ if (ohcidebug > 15) {
+ for (i = 0; i != OHCI_NO_EDS; i++) {
+ printf("ed#%d ", i);
+ ohci_dump_ed(sc->sc_intr_p_last[i]);
+ }
+ printf("iso ");
+ ohci_dump_ed(sc->sc_isoc_p_last);
+ }
+#endif
+
+ sc->sc_bus.usbrev = USB_REV_1_0;
+
+ if (ohci_controller_init(sc)) {
+ return (USB_ERR_INVAL);
+ } else {
+ /* catch any lost interrupts */
+ ohci_do_poll(&sc->sc_bus);
+ return (USB_ERR_NORMAL_COMPLETION);
+ }
+}
+
+/*
+ * shut down the controller when the system is going down
+ */
+void
+ohci_detach(struct ohci_softc *sc)
+{
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ usb2_callout_stop(&sc->sc_tmo_rhsc);
+
+ OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS);
+ OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* XXX let stray task complete */
+ usb2_pause_mtx(NULL, hz / 20);
+
+ usb2_callout_drain(&sc->sc_tmo_rhsc);
+}
+
+/* NOTE: suspend/resume is called from
+ * interrupt context and cannot sleep!
+ */
+void
+ohci_suspend(ohci_softc_t *sc)
+{
+ uint32_t ctl;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+#if USB_DEBUG
+ DPRINTF("\n");
+ if (ohcidebug > 2) {
+ ohci_dumpregs(sc);
+ }
+#endif
+
+ ctl = OREAD4(sc, OHCI_CONTROL) & ~OHCI_HCFS_MASK;
+ if (sc->sc_control == 0) {
+ /*
+ * Preserve register values, in case that APM BIOS
+ * does not recover them.
+ */
+ sc->sc_control = ctl;
+ sc->sc_intre = OREAD4(sc, OHCI_INTERRUPT_ENABLE);
+ }
+ ctl |= OHCI_HCFS_SUSPEND;
+ OWRITE4(sc, OHCI_CONTROL, ctl);
+
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(USB_RESUME_WAIT));
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+void
+ohci_resume(ohci_softc_t *sc)
+{
+ uint32_t ctl;
+
+#if USB_DEBUG
+ DPRINTF("\n");
+ if (ohcidebug > 2) {
+ ohci_dumpregs(sc);
+ }
+#endif
+ /* some broken BIOSes never initialize the Controller chip */
+ ohci_controller_init(sc);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ if (sc->sc_intre) {
+ OWRITE4(sc, OHCI_INTERRUPT_ENABLE,
+ sc->sc_intre & (OHCI_ALL_INTRS | OHCI_MIE));
+ }
+ if (sc->sc_control)
+ ctl = sc->sc_control;
+ else
+ ctl = OREAD4(sc, OHCI_CONTROL);
+ ctl |= OHCI_HCFS_RESUME;
+ OWRITE4(sc, OHCI_CONTROL, ctl);
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(USB_RESUME_DELAY));
+ ctl = (ctl & ~OHCI_HCFS_MASK) | OHCI_HCFS_OPERATIONAL;
+ OWRITE4(sc, OHCI_CONTROL, ctl);
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(USB_RESUME_RECOVERY));
+ sc->sc_control = sc->sc_intre = 0;
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* catch any lost interrupts */
+ ohci_do_poll(&sc->sc_bus);
+}
+
+#if USB_DEBUG
+static void
+ohci_dumpregs(ohci_softc_t *sc)
+{
+ struct ohci_hcca *hcca;
+
+ DPRINTF("ohci_dumpregs: rev=0x%08x control=0x%08x command=0x%08x\n",
+ OREAD4(sc, OHCI_REVISION),
+ OREAD4(sc, OHCI_CONTROL),
+ OREAD4(sc, OHCI_COMMAND_STATUS));
+ DPRINTF(" intrstat=0x%08x intre=0x%08x intrd=0x%08x\n",
+ OREAD4(sc, OHCI_INTERRUPT_STATUS),
+ OREAD4(sc, OHCI_INTERRUPT_ENABLE),
+ OREAD4(sc, OHCI_INTERRUPT_DISABLE));
+ DPRINTF(" hcca=0x%08x percur=0x%08x ctrlhd=0x%08x\n",
+ OREAD4(sc, OHCI_HCCA),
+ OREAD4(sc, OHCI_PERIOD_CURRENT_ED),
+ OREAD4(sc, OHCI_CONTROL_HEAD_ED));
+ DPRINTF(" ctrlcur=0x%08x bulkhd=0x%08x bulkcur=0x%08x\n",
+ OREAD4(sc, OHCI_CONTROL_CURRENT_ED),
+ OREAD4(sc, OHCI_BULK_HEAD_ED),
+ OREAD4(sc, OHCI_BULK_CURRENT_ED));
+ DPRINTF(" done=0x%08x fmival=0x%08x fmrem=0x%08x\n",
+ OREAD4(sc, OHCI_DONE_HEAD),
+ OREAD4(sc, OHCI_FM_INTERVAL),
+ OREAD4(sc, OHCI_FM_REMAINING));
+ DPRINTF(" fmnum=0x%08x perst=0x%08x lsthrs=0x%08x\n",
+ OREAD4(sc, OHCI_FM_NUMBER),
+ OREAD4(sc, OHCI_PERIODIC_START),
+ OREAD4(sc, OHCI_LS_THRESHOLD));
+ DPRINTF(" desca=0x%08x descb=0x%08x stat=0x%08x\n",
+ OREAD4(sc, OHCI_RH_DESCRIPTOR_A),
+ OREAD4(sc, OHCI_RH_DESCRIPTOR_B),
+ OREAD4(sc, OHCI_RH_STATUS));
+ DPRINTF(" port1=0x%08x port2=0x%08x\n",
+ OREAD4(sc, OHCI_RH_PORT_STATUS(1)),
+ OREAD4(sc, OHCI_RH_PORT_STATUS(2)));
+
+ hcca = ohci_get_hcca(sc);
+
+ DPRINTF(" HCCA: frame_number=0x%04x done_head=0x%08x\n",
+ le32toh(hcca->hcca_frame_number),
+ le32toh(hcca->hcca_done_head));
+}
+static void
+ohci_dump_tds(ohci_td_t *std)
+{
+ for (; std; std = std->obj_next) {
+ if (ohci_dump_td(std)) {
+ break;
+ }
+ }
+}
+
+static uint8_t
+ohci_dump_td(ohci_td_t *std)
+{
+ uint32_t td_flags;
+ uint8_t temp;
+
+ usb2_pc_cpu_invalidate(std->page_cache);
+
+ td_flags = le32toh(std->td_flags);
+ temp = (std->td_next == 0);
+
+ printf("TD(%p) at 0x%08x: %s%s%s%s%s delay=%d ec=%d "
+ "cc=%d\ncbp=0x%08x next=0x%08x be=0x%08x\n",
+ std, le32toh(std->td_self),
+ (td_flags & OHCI_TD_R) ? "-R" : "",
+ (td_flags & OHCI_TD_OUT) ? "-OUT" : "",
+ (td_flags & OHCI_TD_IN) ? "-IN" : "",
+ ((td_flags & OHCI_TD_TOGGLE_MASK) == OHCI_TD_TOGGLE_1) ? "-TOG1" : "",
+ ((td_flags & OHCI_TD_TOGGLE_MASK) == OHCI_TD_TOGGLE_0) ? "-TOG0" : "",
+ OHCI_TD_GET_DI(td_flags),
+ OHCI_TD_GET_EC(td_flags),
+ OHCI_TD_GET_CC(td_flags),
+ le32toh(std->td_cbp),
+ le32toh(std->td_next),
+ le32toh(std->td_be));
+
+ return (temp);
+}
+
+static uint8_t
+ohci_dump_itd(ohci_itd_t *sitd)
+{
+ uint32_t itd_flags;
+ uint16_t i;
+ uint8_t temp;
+
+ usb2_pc_cpu_invalidate(sitd->page_cache);
+
+ itd_flags = le32toh(sitd->itd_flags);
+ temp = (sitd->itd_next == 0);
+
+ printf("ITD(%p) at 0x%08x: sf=%d di=%d fc=%d cc=%d\n"
+ "bp0=0x%08x next=0x%08x be=0x%08x\n",
+ sitd, le32toh(sitd->itd_self),
+ OHCI_ITD_GET_SF(itd_flags),
+ OHCI_ITD_GET_DI(itd_flags),
+ OHCI_ITD_GET_FC(itd_flags),
+ OHCI_ITD_GET_CC(itd_flags),
+ le32toh(sitd->itd_bp0),
+ le32toh(sitd->itd_next),
+ le32toh(sitd->itd_be));
+ for (i = 0; i < OHCI_ITD_NOFFSET; i++) {
+ printf("offs[%d]=0x%04x ", i,
+ (uint32_t)le16toh(sitd->itd_offset[i]));
+ }
+ printf("\n");
+
+ return (temp);
+}
+
+static void
+ohci_dump_itds(ohci_itd_t *sitd)
+{
+ for (; sitd; sitd = sitd->obj_next) {
+ if (ohci_dump_itd(sitd)) {
+ break;
+ }
+ }
+}
+
+static void
+ohci_dump_ed(ohci_ed_t *sed)
+{
+ uint32_t ed_flags;
+ uint32_t ed_headp;
+
+ usb2_pc_cpu_invalidate(sed->page_cache);
+
+ ed_flags = le32toh(sed->ed_flags);
+ ed_headp = le32toh(sed->ed_headp);
+
+ printf("ED(%p) at 0x%08x: addr=%d endpt=%d maxp=%d flags=%s%s%s%s%s\n"
+ "tailp=0x%08x headflags=%s%s headp=0x%08x nexted=0x%08x\n",
+ sed, le32toh(sed->ed_self),
+ OHCI_ED_GET_FA(ed_flags),
+ OHCI_ED_GET_EN(ed_flags),
+ OHCI_ED_GET_MAXP(ed_flags),
+ (ed_flags & OHCI_ED_DIR_OUT) ? "-OUT" : "",
+ (ed_flags & OHCI_ED_DIR_IN) ? "-IN" : "",
+ (ed_flags & OHCI_ED_SPEED) ? "-LOWSPEED" : "",
+ (ed_flags & OHCI_ED_SKIP) ? "-SKIP" : "",
+ (ed_flags & OHCI_ED_FORMAT_ISO) ? "-ISO" : "",
+ le32toh(sed->ed_tailp),
+ (ed_headp & OHCI_HALTED) ? "-HALTED" : "",
+ (ed_headp & OHCI_TOGGLECARRY) ? "-CARRY" : "",
+ le32toh(sed->ed_headp),
+ le32toh(sed->ed_next));
+}
+
+#endif
+
+static void
+ohci_transfer_intr_enqueue(struct usb2_xfer *xfer)
+{
+ /* check for early completion */
+ if (ohci_check_transfer(xfer)) {
+ return;
+ }
+ /* put transfer on interrupt queue */
+ usb2_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usb2_transfer_timeout_ms(xfer, &ohci_timeout, xfer->timeout);
+ }
+}
+
+#define OHCI_APPEND_QH(sed,last) (last) = _ohci_append_qh(sed,last)
+static ohci_ed_t *
+_ohci_append_qh(ohci_ed_t *sed, ohci_ed_t *last)
+{
+ DPRINTFN(11, "%p to %p\n", sed, last);
+
+ if (sed->prev != NULL) {
+ /* should not happen */
+ DPRINTFN(0, "ED already linked!\n");
+ return (last);
+ }
+ /* (sc->sc_bus.bus_mtx) must be locked */
+
+ sed->next = last->next;
+ sed->ed_next = last->ed_next;
+ sed->ed_tailp = 0;
+
+ sed->prev = last;
+
+ usb2_pc_cpu_flush(sed->page_cache);
+
+ /*
+ * the last->next->prev is never followed: sed->next->prev = sed;
+ */
+
+ last->next = sed;
+ last->ed_next = sed->ed_self;
+
+ usb2_pc_cpu_flush(last->page_cache);
+
+ return (sed);
+}
+
+#define OHCI_REMOVE_QH(sed,last) (last) = _ohci_remove_qh(sed,last)
+static ohci_ed_t *
+_ohci_remove_qh(ohci_ed_t *sed, ohci_ed_t *last)
+{
+ DPRINTFN(11, "%p from %p\n", sed, last);
+
+ /* (sc->sc_bus.bus_mtx) must be locked */
+
+ /* only remove if not removed from a queue */
+ if (sed->prev) {
+
+ sed->prev->next = sed->next;
+ sed->prev->ed_next = sed->ed_next;
+
+ usb2_pc_cpu_flush(sed->prev->page_cache);
+
+ if (sed->next) {
+ sed->next->prev = sed->prev;
+ usb2_pc_cpu_flush(sed->next->page_cache);
+ }
+ last = ((last == sed) ? sed->prev : last);
+
+ sed->prev = 0;
+
+ usb2_pc_cpu_flush(sed->page_cache);
+ }
+ return (last);
+}
+
+static void
+ohci_isoc_done(struct usb2_xfer *xfer)
+{
+ uint8_t nframes;
+ uint32_t *plen = xfer->frlengths;
+ volatile uint16_t *olen;
+ uint16_t len = 0;
+ ohci_itd_t *td = xfer->td_transfer_first;
+
+ while (1) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+#if USB_DEBUG
+ if (ohcidebug > 5) {
+ DPRINTF("isoc TD\n");
+ ohci_dump_itd(td);
+ }
+#endif
+ usb2_pc_cpu_invalidate(td->page_cache);
+
+ nframes = td->frames;
+ olen = &td->itd_offset[0];
+
+ if (nframes > 8) {
+ nframes = 8;
+ }
+ while (nframes--) {
+ len = le16toh(*olen);
+
+ if ((len >> 12) == OHCI_CC_NOT_ACCESSED) {
+ len = 0;
+ } else {
+ len &= ((1 << 12) - 1);
+ }
+
+ if (len > *plen) {
+ len = 0;/* invalid length */
+ }
+ *plen = len;
+ plen++;
+ olen++;
+ }
+
+ if (((void *)td) == xfer->td_transfer_last) {
+ break;
+ }
+ td = td->obj_next;
+ }
+
+ xfer->aframes = xfer->nframes;
+ ohci_device_done(xfer, USB_ERR_NORMAL_COMPLETION);
+}
+
+#if USB_DEBUG
+static const char *const
+ ohci_cc_strs[] =
+{
+ "NO_ERROR",
+ "CRC",
+ "BIT_STUFFING",
+ "DATA_TOGGLE_MISMATCH",
+
+ "STALL",
+ "DEVICE_NOT_RESPONDING",
+ "PID_CHECK_FAILURE",
+ "UNEXPECTED_PID",
+
+ "DATA_OVERRUN",
+ "DATA_UNDERRUN",
+ "BUFFER_OVERRUN",
+ "BUFFER_UNDERRUN",
+
+ "reserved",
+ "reserved",
+ "NOT_ACCESSED",
+ "NOT_ACCESSED"
+};
+
+#endif
+
+static usb2_error_t
+ohci_non_isoc_done_sub(struct usb2_xfer *xfer)
+{
+ ohci_td_t *td;
+ ohci_td_t *td_alt_next;
+ uint32_t temp;
+ uint32_t phy_start;
+ uint32_t phy_end;
+ uint32_t td_flags;
+ uint16_t cc;
+
+ td = xfer->td_transfer_cache;
+ td_alt_next = td->alt_next;
+ td_flags = 0;
+
+ if (xfer->aframes != xfer->nframes) {
+ xfer->frlengths[xfer->aframes] = 0;
+ }
+ while (1) {
+
+ usb2_pc_cpu_invalidate(td->page_cache);
+ phy_start = le32toh(td->td_cbp);
+ td_flags = le32toh(td->td_flags);
+ cc = OHCI_TD_GET_CC(td_flags);
+
+ if (phy_start) {
+ /*
+ * short transfer - compute the number of remaining
+ * bytes in the hardware buffer:
+ */
+ phy_end = le32toh(td->td_be);
+ temp = (OHCI_PAGE(phy_start ^ phy_end) ?
+ (OHCI_PAGE_SIZE + 1) : 0x0001);
+ temp += OHCI_PAGE_OFFSET(phy_end);
+ temp -= OHCI_PAGE_OFFSET(phy_start);
+
+ if (temp > td->len) {
+ /* guard against corruption */
+ cc = OHCI_CC_STALL;
+ } else if (xfer->aframes != xfer->nframes) {
+ /*
+ * Sum up total transfer length
+ * in "frlengths[]":
+ */
+ xfer->frlengths[xfer->aframes] += td->len - temp;
+ }
+ } else {
+ if (xfer->aframes != xfer->nframes) {
+ /* transfer was complete */
+ xfer->frlengths[xfer->aframes] += td->len;
+ }
+ }
+ /* Check for last transfer */
+ if (((void *)td) == xfer->td_transfer_last) {
+ td = NULL;
+ break;
+ }
+ /* Check transfer status */
+ if (cc) {
+ /* the transfer is finished */
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (phy_start) {
+ if (xfer->flags_int.short_frames_ok) {
+ /* follow alt next */
+ td = td->alt_next;
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ break;
+ }
+ td = td->obj_next;
+
+ if (td->alt_next != td_alt_next) {
+ /* this USB frame is complete */
+ break;
+ }
+ }
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ DPRINTFN(16, "error cc=%d (%s)\n",
+ cc, ohci_cc_strs[cc]);
+
+ return ((cc == 0) ? USB_ERR_NORMAL_COMPLETION :
+ (cc == OHCI_CC_STALL) ? USB_ERR_STALLED : USB_ERR_IOERROR);
+}
+
+static void
+ohci_non_isoc_done(struct usb2_xfer *xfer)
+{
+ usb2_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->pipe);
+
+#if USB_DEBUG
+ if (ohcidebug > 10) {
+ ohci_dump_tds(xfer->td_transfer_first);
+ }
+#endif
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+
+ if (xfer->flags_int.control_hdr) {
+
+ err = ohci_non_isoc_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+
+ err = ohci_non_isoc_done_sub(xfer);
+ xfer->aframes++;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ err = ohci_non_isoc_done_sub(xfer);
+ }
+done:
+ ohci_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * ohci_check_transfer_sub
+ *------------------------------------------------------------------------*/
+static void
+ohci_check_transfer_sub(struct usb2_xfer *xfer)
+{
+ ohci_td_t *td;
+ ohci_ed_t *ed;
+ uint32_t phy_start;
+ uint32_t td_flags;
+ uint32_t td_next;
+ uint16_t cc;
+
+ td = xfer->td_transfer_cache;
+
+ while (1) {
+
+ usb2_pc_cpu_invalidate(td->page_cache);
+ phy_start = le32toh(td->td_cbp);
+ td_flags = le32toh(td->td_flags);
+ td_next = le32toh(td->td_next);
+
+ /* Check for last transfer */
+ if (((void *)td) == xfer->td_transfer_last) {
+ /* the transfer is finished */
+ td = NULL;
+ break;
+ }
+ /* Check transfer status */
+ cc = OHCI_TD_GET_CC(td_flags);
+ if (cc) {
+ /* the transfer is finished */
+ td = NULL;
+ break;
+ }
+ /*
+ * Check if we reached the last packet
+ * or if there is a short packet:
+ */
+
+ if (((td_next & (~0xF)) == OHCI_TD_NEXT_END) || phy_start) {
+ /* follow alt next */
+ td = td->alt_next;
+ break;
+ }
+ td = td->obj_next;
+ }
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ if (td) {
+
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ ed->ed_headp = td->td_self;
+ usb2_pc_cpu_flush(ed->page_cache);
+
+ DPRINTFN(13, "xfer=%p following alt next\n", xfer);
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * ohci_check_transfer
+ *
+ * Return values:
+ * 0: USB transfer is not finished
+ * Else: USB transfer is finished
+ *------------------------------------------------------------------------*/
+static uint8_t
+ohci_check_transfer(struct usb2_xfer *xfer)
+{
+ ohci_ed_t *ed;
+ uint32_t ed_flags;
+ uint32_t ed_headp;
+ uint32_t ed_tailp;
+
+ DPRINTFN(13, "xfer=%p checking transfer\n", xfer);
+
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ usb2_pc_cpu_invalidate(ed->page_cache);
+ ed_flags = le32toh(ed->ed_flags);
+ ed_headp = le32toh(ed->ed_headp);
+ ed_tailp = le32toh(ed->ed_tailp);
+
+ if ((ed_flags & OHCI_ED_SKIP) ||
+ (ed_headp & OHCI_HALTED) ||
+ (((ed_headp ^ ed_tailp) & (~0xF)) == 0)) {
+ if (xfer->pipe->methods == &ohci_device_isoc_methods) {
+ /* isochronous transfer */
+ ohci_isoc_done(xfer);
+ } else {
+ if (xfer->flags_int.short_frames_ok) {
+ ohci_check_transfer_sub(xfer);
+ if (xfer->td_transfer_cache) {
+ /* not finished yet */
+ return (0);
+ }
+ }
+ /* store data-toggle */
+ if (ed_headp & OHCI_TOGGLECARRY) {
+ xfer->pipe->toggle_next = 1;
+ } else {
+ xfer->pipe->toggle_next = 0;
+ }
+
+ /* non-isochronous transfer */
+ ohci_non_isoc_done(xfer);
+ }
+ return (1);
+ }
+ DPRINTFN(13, "xfer=%p is still active\n", xfer);
+ return (0);
+}
+
+static void
+ohci_rhsc_enable(ohci_softc_t *sc)
+{
+ DPRINTFN(5, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ sc->sc_eintrs |= OHCI_RHSC;
+ OWRITE4(sc, OHCI_INTERRUPT_ENABLE, OHCI_RHSC);
+
+ /* acknowledge any RHSC interrupt */
+ OWRITE4(sc, OHCI_INTERRUPT_STATUS, OHCI_RHSC);
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &ohci_root_intr_done);
+}
+
+static void
+ohci_interrupt_poll(ohci_softc_t *sc)
+{
+ struct usb2_xfer *xfer;
+
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ /*
+ * check if transfer is transferred
+ */
+ if (ohci_check_transfer(xfer)) {
+ /* queue has been modified */
+ goto repeat;
+ }
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * ohci_interrupt - OHCI interrupt handler
+ *
+ * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler,
+ * hence the interrupt handler will be setup before "sc->sc_bus.bdev"
+ * is present !
+ *------------------------------------------------------------------------*/
+void
+ohci_interrupt(ohci_softc_t *sc)
+{
+ struct ohci_hcca *hcca;
+ uint32_t status;
+ uint32_t done;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ hcca = ohci_get_hcca(sc);
+
+ DPRINTFN(16, "real interrupt\n");
+
+#if USB_DEBUG
+ if (ohcidebug > 15) {
+ ohci_dumpregs(sc);
+ }
+#endif
+
+ done = le32toh(hcca->hcca_done_head);
+
+ /*
+ * The LSb of done is used to inform the HC Driver that an interrupt
+ * condition exists for both the Done list and for another event
+ * recorded in HcInterruptStatus. On an interrupt from the HC, the
+ * HC Driver checks the HccaDoneHead Value. If this value is 0, then
+ * the interrupt was caused by other than the HccaDoneHead update
+ * and the HcInterruptStatus register needs to be accessed to
+ * determine that exact interrupt cause. If HccaDoneHead is nonzero,
+ * then a Done list update interrupt is indicated and if the LSb of
+ * done is nonzero, then an additional interrupt event is indicated
+ * and HcInterruptStatus should be checked to determine its cause.
+ */
+ if (done != 0) {
+ status = 0;
+
+ if (done & ~OHCI_DONE_INTRS) {
+ status |= OHCI_WDH;
+ }
+ if (done & OHCI_DONE_INTRS) {
+ status |= OREAD4(sc, OHCI_INTERRUPT_STATUS);
+ }
+ hcca->hcca_done_head = 0;
+
+ usb2_pc_cpu_flush(&sc->sc_hw.hcca_pc);
+ } else {
+ status = OREAD4(sc, OHCI_INTERRUPT_STATUS) & ~OHCI_WDH;
+ }
+
+ status &= ~OHCI_MIE;
+ if (status == 0) {
+ /*
+ * nothing to be done (PCI shared
+ * interrupt)
+ */
+ goto done;
+ }
+ OWRITE4(sc, OHCI_INTERRUPT_STATUS, status); /* Acknowledge */
+
+ status &= sc->sc_eintrs;
+ if (status == 0) {
+ goto done;
+ }
+ if (status & (OHCI_SO | OHCI_RD | OHCI_UE | OHCI_RHSC)) {
+#if 0
+ if (status & OHCI_SO) {
+ /* XXX do what */
+ }
+#endif
+ if (status & OHCI_RD) {
+ printf("%s: resume detect\n", __FUNCTION__);
+ /* XXX process resume detect */
+ }
+ if (status & OHCI_UE) {
+ printf("%s: unrecoverable error, "
+ "controller halted\n", __FUNCTION__);
+ OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET);
+ /* XXX what else */
+ }
+ if (status & OHCI_RHSC) {
+ /*
+ * Disable RHSC interrupt for now, because it will be
+ * on until the port has been reset.
+ */
+ sc->sc_eintrs &= ~OHCI_RHSC;
+ OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_RHSC);
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &ohci_root_intr_done);
+
+ /* do not allow RHSC interrupts > 1 per second */
+ usb2_callout_reset(&sc->sc_tmo_rhsc, hz,
+ (void *)&ohci_rhsc_enable, sc);
+ }
+ }
+ status &= ~(OHCI_RHSC | OHCI_WDH | OHCI_SO);
+ if (status != 0) {
+ /* Block unprocessed interrupts. XXX */
+ OWRITE4(sc, OHCI_INTERRUPT_DISABLE, status);
+ sc->sc_eintrs &= ~status;
+ printf("%s: blocking intrs 0x%x\n",
+ __FUNCTION__, status);
+ }
+ /* poll all the USB transfers */
+ ohci_interrupt_poll(sc);
+
+done:
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*
+ * called when a request does not complete
+ */
+static void
+ohci_timeout(void *arg)
+{
+ struct usb2_xfer *xfer = arg;
+
+ DPRINTF("xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ ohci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+ohci_do_poll(struct usb2_bus *bus)
+{
+ struct ohci_softc *sc = OHCI_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ ohci_interrupt_poll(sc);
+ ohci_root_ctrl_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+ohci_setup_standard_chain_sub(struct ohci_std_temp *temp)
+{
+ struct usb2_page_search buf_res;
+ ohci_td_t *td;
+ ohci_td_t *td_next;
+ ohci_td_t *td_alt_next;
+ uint32_t buf_offset;
+ uint32_t average;
+ uint32_t len_old;
+ uint8_t shortpkt_old;
+ uint8_t precompute;
+
+ td_alt_next = NULL;
+ buf_offset = 0;
+ shortpkt_old = temp->shortpkt;
+ len_old = temp->len;
+ precompute = 1;
+
+ /* software is used to detect short incoming transfers */
+
+ if ((temp->td_flags & htole32(OHCI_TD_DP_MASK)) == htole32(OHCI_TD_IN)) {
+ temp->td_flags |= htole32(OHCI_TD_R);
+ } else {
+ temp->td_flags &= ~htole32(OHCI_TD_R);
+ }
+
+restart:
+
+ td = temp->td;
+ td_next = temp->td_next;
+
+ while (1) {
+
+ if (temp->len == 0) {
+
+ if (temp->shortpkt) {
+ break;
+ }
+ /* send a Zero Length Packet, ZLP, last */
+
+ temp->shortpkt = 1;
+ average = 0;
+
+ } else {
+
+ average = temp->average;
+
+ if (temp->len < average) {
+ if (temp->len % temp->max_frame_size) {
+ temp->shortpkt = 1;
+ }
+ average = temp->len;
+ }
+ }
+
+ if (td_next == NULL) {
+ panic("%s: out of OHCI transfer descriptors!", __FUNCTION__);
+ }
+ /* get next TD */
+
+ td = td_next;
+ td_next = td->obj_next;
+
+ /* check if we are pre-computing */
+
+ if (precompute) {
+
+ /* update remaining length */
+
+ temp->len -= average;
+
+ continue;
+ }
+ /* fill out current TD */
+ td->td_flags = temp->td_flags;
+
+ /* the next TD uses TOGGLE_CARRY */
+ temp->td_flags &= ~htole32(OHCI_TD_TOGGLE_MASK);
+
+ if (average == 0) {
+
+ td->td_cbp = 0;
+ td->td_be = ~0;
+ td->len = 0;
+
+ } else {
+
+ usb2_get_page(temp->pc, buf_offset, &buf_res);
+ td->td_cbp = htole32(buf_res.physaddr);
+ buf_offset += (average - 1);
+
+ usb2_get_page(temp->pc, buf_offset, &buf_res);
+ td->td_be = htole32(buf_res.physaddr);
+ buf_offset++;
+
+ td->len = average;
+
+ /* update remaining length */
+
+ temp->len -= average;
+ }
+
+ if ((td_next == td_alt_next) && temp->setup_alt_next) {
+ /* we need to receive these frames one by one ! */
+ td->td_flags &= htole32(~OHCI_TD_INTR_MASK);
+ td->td_flags |= htole32(OHCI_TD_SET_DI(1));
+ td->td_next = htole32(OHCI_TD_NEXT_END);
+ } else {
+ if (td_next) {
+ /* link the current TD with the next one */
+ td->td_next = td_next->td_self;
+ }
+ }
+
+ td->alt_next = td_alt_next;
+
+ usb2_pc_cpu_flush(td->page_cache);
+ }
+
+ if (precompute) {
+ precompute = 0;
+
+ /* setup alt next pointer, if any */
+ if (temp->short_frames_ok) {
+ if (temp->setup_alt_next) {
+ td_alt_next = td_next;
+ }
+ } else {
+ /* we use this field internally */
+ td_alt_next = td_next;
+ }
+
+ /* restore */
+ temp->shortpkt = shortpkt_old;
+ temp->len = len_old;
+ goto restart;
+ }
+ temp->td = td;
+ temp->td_next = td_next;
+}
+
+static void
+ohci_setup_standard_chain(struct usb2_xfer *xfer, ohci_ed_t **ed_last)
+{
+ struct ohci_std_temp temp;
+ struct usb2_pipe_methods *methods;
+ ohci_ed_t *ed;
+ ohci_td_t *td;
+ uint32_t ed_flags;
+ uint32_t x;
+
+ DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpoint),
+ xfer->sumlen, usb2_get_speed(xfer->xroot->udev));
+
+ temp.average = xfer->max_usb2_frame_size;
+ temp.max_frame_size = xfer->max_frame_size;
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ temp.td = NULL;
+ temp.td_next = td;
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+ temp.short_frames_ok = xfer->flags_int.short_frames_ok;
+
+ methods = xfer->pipe->methods;
+
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+
+ temp.td_flags = htole32(OHCI_TD_SETUP | OHCI_TD_NOCC |
+ OHCI_TD_TOGGLE_0 | OHCI_TD_NOINTR);
+
+ temp.len = xfer->frlengths[0];
+ temp.pc = xfer->frbuffers + 0;
+ temp.shortpkt = temp.len ? 1 : 0;
+
+ ohci_setup_standard_chain_sub(&temp);
+
+ /*
+ * XXX assume that the setup message is
+ * contained within one USB packet:
+ */
+ xfer->pipe->toggle_next = 1;
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+ temp.td_flags = htole32(OHCI_TD_NOCC | OHCI_TD_NOINTR);
+
+ /* set data toggle */
+
+ if (xfer->pipe->toggle_next) {
+ temp.td_flags |= htole32(OHCI_TD_TOGGLE_1);
+ } else {
+ temp.td_flags |= htole32(OHCI_TD_TOGGLE_0);
+ }
+
+ /* set endpoint direction */
+
+ if (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) {
+ temp.td_flags |= htole32(OHCI_TD_IN);
+ } else {
+ temp.td_flags |= htole32(OHCI_TD_OUT);
+ }
+
+ while (x != xfer->nframes) {
+
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+ temp.pc = xfer->frbuffers + x;
+
+ x++;
+
+ if (x == xfer->nframes) {
+ temp.setup_alt_next = 0;
+ }
+ if (temp.len == 0) {
+
+ /* make sure that we send an USB packet */
+
+ temp.shortpkt = 0;
+
+ } else {
+
+ /* regular data transfer */
+
+ temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+ }
+
+ ohci_setup_standard_chain_sub(&temp);
+ }
+
+ /* check if we should append a status stage */
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ /*
+ * Send a DATA1 message and invert the current endpoint
+ * direction.
+ */
+
+ /* set endpoint direction and data toggle */
+
+ if (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) {
+ temp.td_flags = htole32(OHCI_TD_OUT |
+ OHCI_TD_NOCC | OHCI_TD_TOGGLE_1 | OHCI_TD_SET_DI(1));
+ } else {
+ temp.td_flags = htole32(OHCI_TD_IN |
+ OHCI_TD_NOCC | OHCI_TD_TOGGLE_1 | OHCI_TD_SET_DI(1));
+ }
+
+ temp.len = 0;
+ temp.pc = NULL;
+ temp.shortpkt = 0;
+
+ ohci_setup_standard_chain_sub(&temp);
+ }
+ td = temp.td;
+
+ td->td_next = htole32(OHCI_TD_NEXT_END);
+ td->td_flags &= ~htole32(OHCI_TD_INTR_MASK);
+ td->td_flags |= htole32(OHCI_TD_SET_DI(1));
+
+ usb2_pc_cpu_flush(td->page_cache);
+
+ /* must have at least one frame! */
+
+ xfer->td_transfer_last = td;
+
+#if USB_DEBUG
+ if (ohcidebug > 8) {
+ DPRINTF("nexttog=%d; data before transfer:\n",
+ xfer->pipe->toggle_next);
+ ohci_dump_tds(xfer->td_transfer_first);
+ }
+#endif
+
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ ed_flags = (OHCI_ED_SET_FA(xfer->address) |
+ OHCI_ED_SET_EN(UE_GET_ADDR(xfer->endpoint)) |
+ OHCI_ED_SET_MAXP(xfer->max_frame_size));
+
+ ed_flags |= (OHCI_ED_FORMAT_GEN | OHCI_ED_DIR_TD);
+
+ if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
+ ed_flags |= OHCI_ED_SPEED;
+ }
+ ed->ed_flags = htole32(ed_flags);
+
+ td = xfer->td_transfer_first;
+
+ ed->ed_headp = td->td_self;
+
+ if (xfer->xroot->udev->pwr_save.suspended == 0) {
+ /* the append function will flush the endpoint descriptor */
+ OHCI_APPEND_QH(ed, *ed_last);
+
+ if (methods == &ohci_device_bulk_methods) {
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF);
+ }
+ if (methods == &ohci_device_ctrl_methods) {
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF);
+ }
+ } else {
+ usb2_pc_cpu_flush(ed->page_cache);
+ }
+}
+
+static void
+ohci_root_intr_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+ uint32_t hstatus;
+ uint16_t i;
+ uint16_t m;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_PRE_DATA) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ ohci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* setup buffer */
+ std->ptr = sc->sc_hub_idata;
+ std->len = sizeof(sc->sc_hub_idata);
+
+ /* clear any old interrupt data */
+ bzero(sc->sc_hub_idata, sizeof(sc->sc_hub_idata));
+
+ hstatus = OREAD4(sc, OHCI_RH_STATUS);
+ DPRINTF("sc=%p xfer=%p hstatus=0x%08x\n",
+ sc, xfer, hstatus);
+
+ /* set bits */
+ m = (sc->sc_noport + 1);
+ if (m > (8 * sizeof(sc->sc_hub_idata))) {
+ m = (8 * sizeof(sc->sc_hub_idata));
+ }
+ for (i = 1; i < m; i++) {
+ /* pick out CHANGE bits from the status register */
+ if (OREAD4(sc, OHCI_RH_PORT_STATUS(i)) >> 16) {
+ sc->sc_hub_idata[i / 8] |= 1 << (i % 8);
+ DPRINTF("port %d changed\n", i);
+ }
+ }
+done:
+ return;
+}
+
+/* NOTE: "done" can be run two times in a row,
+ * from close and from interrupt
+ */
+static void
+ohci_device_done(struct usb2_xfer *xfer, usb2_error_t error)
+{
+ struct usb2_pipe_methods *methods = xfer->pipe->methods;
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+ ohci_ed_t *ed;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+
+ DPRINTFN(2, "xfer=%p, pipe=%p, error=%d\n",
+ xfer, xfer->pipe, error);
+
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+ if (ed) {
+ usb2_pc_cpu_invalidate(ed->page_cache);
+ }
+ if (methods == &ohci_device_bulk_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_bulk_p_last);
+ }
+ if (methods == &ohci_device_ctrl_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_ctrl_p_last);
+ }
+ if (methods == &ohci_device_intr_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ if (methods == &ohci_device_isoc_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_isoc_p_last);
+ }
+ xfer->td_transfer_first = NULL;
+ xfer->td_transfer_last = NULL;
+
+ /* dequeue transfer and start next transfer */
+ usb2_transfer_done(xfer, error);
+}
+
+/*------------------------------------------------------------------------*
+ * ohci bulk support
+ *------------------------------------------------------------------------*/
+static void
+ohci_device_bulk_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_device_bulk_close(struct usb2_xfer *xfer)
+{
+ ohci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ohci_device_bulk_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_device_bulk_start(struct usb2_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ohci_setup_standard_chain(xfer, &sc->sc_bulk_p_last);
+
+ /* put transfer on interrupt queue */
+ ohci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods ohci_device_bulk_methods =
+{
+ .open = ohci_device_bulk_open,
+ .close = ohci_device_bulk_close,
+ .enter = ohci_device_bulk_enter,
+ .start = ohci_device_bulk_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * ohci control support
+ *------------------------------------------------------------------------*/
+static void
+ohci_device_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_device_ctrl_close(struct usb2_xfer *xfer)
+{
+ ohci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ohci_device_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_device_ctrl_start(struct usb2_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ohci_setup_standard_chain(xfer, &sc->sc_ctrl_p_last);
+
+ /* put transfer on interrupt queue */
+ ohci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods ohci_device_ctrl_methods =
+{
+ .open = ohci_device_ctrl_open,
+ .close = ohci_device_ctrl_close,
+ .enter = ohci_device_ctrl_enter,
+ .start = ohci_device_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * ohci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+ohci_device_intr_open(struct usb2_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+ uint16_t best;
+ uint16_t bit;
+ uint16_t x;
+
+ best = 0;
+ bit = OHCI_NO_EDS / 2;
+ while (bit) {
+ if (xfer->interval >= bit) {
+ x = bit;
+ best = bit;
+ while (x & bit) {
+ if (sc->sc_intr_stat[x] <
+ sc->sc_intr_stat[best]) {
+ best = x;
+ }
+ x++;
+ }
+ break;
+ }
+ bit >>= 1;
+ }
+
+ sc->sc_intr_stat[best]++;
+ xfer->qh_pos = best;
+
+ DPRINTFN(3, "best=%d interval=%d\n",
+ best, xfer->interval);
+}
+
+static void
+ohci_device_intr_close(struct usb2_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_intr_stat[xfer->qh_pos]--;
+
+ ohci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ohci_device_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_device_intr_start(struct usb2_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ohci_setup_standard_chain(xfer, &sc->sc_intr_p_last[xfer->qh_pos]);
+
+ /* put transfer on interrupt queue */
+ ohci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods ohci_device_intr_methods =
+{
+ .open = ohci_device_intr_open,
+ .close = ohci_device_intr_close,
+ .enter = ohci_device_intr_enter,
+ .start = ohci_device_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * ohci isochronous support
+ *------------------------------------------------------------------------*/
+static void
+ohci_device_isoc_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_device_isoc_close(struct usb2_xfer *xfer)
+{
+ /**/
+ ohci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ohci_device_isoc_enter(struct usb2_xfer *xfer)
+{
+ struct usb2_page_search buf_res;
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+ struct ohci_hcca *hcca;
+ uint32_t buf_offset;
+ uint32_t nframes;
+ uint32_t ed_flags;
+ uint32_t *plen;
+ uint16_t itd_offset[OHCI_ITD_NOFFSET];
+ uint16_t length;
+ uint8_t ncur;
+ ohci_itd_t *td;
+ ohci_itd_t *td_last = NULL;
+ ohci_ed_t *ed;
+
+ hcca = ohci_get_hcca(sc);
+
+ nframes = le32toh(hcca->hcca_frame_number);
+
+ DPRINTFN(6, "xfer=%p isoc_next=%u nframes=%u hcca_fn=%u\n",
+ xfer, xfer->pipe->isoc_next, xfer->nframes, nframes);
+
+ if ((xfer->pipe->is_synced == 0) ||
+ (((nframes - xfer->pipe->isoc_next) & 0xFFFF) < xfer->nframes) ||
+ (((xfer->pipe->isoc_next - nframes) & 0xFFFF) >= 128)) {
+ /*
+ * If there is data underflow or the pipe queue is empty we
+ * schedule the transfer a few frames ahead of the current
+ * frame position. Else two isochronous transfers might
+ * overlap.
+ */
+ xfer->pipe->isoc_next = (nframes + 3) & 0xFFFF;
+ xfer->pipe->is_synced = 1;
+ DPRINTFN(3, "start next=%d\n", xfer->pipe->isoc_next);
+ }
+ /*
+ * compute how many milliseconds the insertion is ahead of the
+ * current frame position:
+ */
+ buf_offset = ((xfer->pipe->isoc_next - nframes) & 0xFFFF);
+
+ /*
+ * pre-compute when the isochronous transfer will be finished:
+ */
+ xfer->isoc_time_complete =
+ (usb2_isoc_time_expand(&sc->sc_bus, nframes) + buf_offset +
+ xfer->nframes);
+
+ /* get the real number of frames */
+
+ nframes = xfer->nframes;
+
+ buf_offset = 0;
+
+ plen = xfer->frlengths;
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+
+ xfer->td_transfer_first = td;
+
+ ncur = 0;
+ length = 0;
+
+ while (nframes--) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ itd_offset[ncur] = length;
+ buf_offset += *plen;
+ length += *plen;
+ plen++;
+ ncur++;
+
+ if ( /* check if the ITD is full */
+ (ncur == OHCI_ITD_NOFFSET) ||
+ /* check if we have put more than 4K into the ITD */
+ (length & 0xF000) ||
+ /* check if it is the last frame */
+ (nframes == 0)) {
+
+ /* fill current ITD */
+ td->itd_flags = htole32(
+ OHCI_ITD_NOCC |
+ OHCI_ITD_SET_SF(xfer->pipe->isoc_next) |
+ OHCI_ITD_NOINTR |
+ OHCI_ITD_SET_FC(ncur));
+
+ td->frames = ncur;
+ xfer->pipe->isoc_next += ncur;
+
+ if (length == 0) {
+ /* all zero */
+ td->itd_bp0 = 0;
+ td->itd_be = ~0;
+
+ while (ncur--) {
+ td->itd_offset[ncur] =
+ htole16(OHCI_ITD_MK_OFFS(0));
+ }
+ } else {
+ usb2_get_page(xfer->frbuffers, buf_offset - length, &buf_res);
+ length = OHCI_PAGE_MASK(buf_res.physaddr);
+ buf_res.physaddr =
+ OHCI_PAGE(buf_res.physaddr);
+ td->itd_bp0 = htole32(buf_res.physaddr);
+ usb2_get_page(xfer->frbuffers, buf_offset - 1, &buf_res);
+ td->itd_be = htole32(buf_res.physaddr);
+
+ while (ncur--) {
+ itd_offset[ncur] += length;
+ itd_offset[ncur] =
+ OHCI_ITD_MK_OFFS(itd_offset[ncur]);
+ td->itd_offset[ncur] =
+ htole16(itd_offset[ncur]);
+ }
+ }
+ ncur = 0;
+ length = 0;
+ td_last = td;
+ td = td->obj_next;
+
+ if (td) {
+ /* link the last TD with the next one */
+ td_last->itd_next = td->itd_self;
+ }
+ usb2_pc_cpu_flush(td_last->page_cache);
+ }
+ }
+
+ /* update the last TD */
+ td_last->itd_flags &= ~htole32(OHCI_ITD_NOINTR);
+ td_last->itd_flags |= htole32(OHCI_ITD_SET_DI(0));
+ td_last->itd_next = 0;
+
+ usb2_pc_cpu_flush(td_last->page_cache);
+
+ xfer->td_transfer_last = td_last;
+
+#if USB_DEBUG
+ if (ohcidebug > 8) {
+ DPRINTF("data before transfer:\n");
+ ohci_dump_itds(xfer->td_transfer_first);
+ }
+#endif
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ if (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN)
+ ed_flags = (OHCI_ED_DIR_IN | OHCI_ED_FORMAT_ISO);
+ else
+ ed_flags = (OHCI_ED_DIR_OUT | OHCI_ED_FORMAT_ISO);
+
+ ed_flags |= (OHCI_ED_SET_FA(xfer->address) |
+ OHCI_ED_SET_EN(UE_GET_ADDR(xfer->endpoint)) |
+ OHCI_ED_SET_MAXP(xfer->max_frame_size));
+
+ if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
+ ed_flags |= OHCI_ED_SPEED;
+ }
+ ed->ed_flags = htole32(ed_flags);
+
+ td = xfer->td_transfer_first;
+
+ ed->ed_headp = td->itd_self;
+
+ /* isochronous transfers are not affected by suspend / resume */
+ /* the append function will flush the endpoint descriptor */
+
+ OHCI_APPEND_QH(ed, sc->sc_isoc_p_last);
+}
+
+static void
+ohci_device_isoc_start(struct usb2_xfer *xfer)
+{
+ /* put transfer on interrupt queue */
+ ohci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods ohci_device_isoc_methods =
+{
+ .open = ohci_device_isoc_open,
+ .close = ohci_device_isoc_close,
+ .enter = ohci_device_isoc_enter,
+ .start = ohci_device_isoc_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * ohci root control support
+ *------------------------------------------------------------------------*
+ * simulate a hardware hub by handling
+ * all the necessary requests
+ *------------------------------------------------------------------------*/
+
+static void
+ohci_root_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_root_ctrl_close(struct usb2_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_ctrl.xfer == xfer) {
+ sc->sc_root_ctrl.xfer = NULL;
+ }
+ ohci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+/* data structures and routines
+ * to emulate the root hub:
+ */
+static const
+struct usb2_device_descriptor ohci_devd =
+{
+ sizeof(struct usb2_device_descriptor),
+ UDESC_DEVICE, /* type */
+ {0x00, 0x01}, /* USB version */
+ UDCLASS_HUB, /* class */
+ UDSUBCLASS_HUB, /* subclass */
+ UDPROTO_FSHUB, /* protocol */
+ 64, /* max packet */
+ {0}, {0}, {0x00, 0x01}, /* device id */
+ 1, 2, 0, /* string indicies */
+ 1 /* # of configurations */
+};
+
+static const
+struct ohci_config_desc ohci_confd =
+{
+ .confd = {
+ .bLength = sizeof(struct usb2_config_descriptor),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(ohci_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0, /* max power */
+ },
+
+ .ifcd = {
+ .bLength = sizeof(struct usb2_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = UIPROTO_FSHUB,
+ },
+
+ .endpd = {
+ .bLength = sizeof(struct usb2_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = UE_DIR_IN | OHCI_INTR_ENDPT,
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 32,/* max packet (255 ports) */
+ .bInterval = 255,
+ },
+};
+
+static const
+struct usb2_hub_descriptor ohci_hubd =
+{
+ 0, /* dynamic length */
+ UDESC_HUB,
+ 0,
+ {0, 0},
+ 0,
+ 0,
+ {0},
+};
+
+static void
+ohci_root_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_root_ctrl_start(struct usb2_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_ctrl.xfer = xfer;
+
+ usb2_bus_roothub_exec(xfer->xroot->bus);
+}
+
+static void
+ohci_root_ctrl_task(struct usb2_bus *bus)
+{
+ ohci_root_ctrl_poll(OHCI_BUS2SC(bus));
+}
+
+static void
+ohci_root_ctrl_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+ char *ptr;
+ uint32_t port;
+ uint32_t v;
+ uint16_t value;
+ uint16_t index;
+ uint8_t l;
+ uint8_t use_polling;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_SETUP) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ ohci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* buffer reset */
+ std->ptr = sc->sc_hub_desc.temp;
+ std->len = 0;
+
+ value = UGETW(std->req.wValue);
+ index = UGETW(std->req.wIndex);
+
+ use_polling = mtx_owned(xfer->xroot->xfer_mtx) ? 1 : 0;
+
+ DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x "
+ "wValue=0x%04x wIndex=0x%04x\n",
+ std->req.bmRequestType, std->req.bRequest,
+ UGETW(std->req.wLength), value, index);
+
+#define C(x,y) ((x) | ((y) << 8))
+ switch (C(std->req.bRequest, std->req.bmRequestType)) {
+ case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE):
+ case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE):
+ case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT):
+ /*
+ * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops
+ * for the integrated root hub.
+ */
+ break;
+ case C(UR_GET_CONFIG, UT_READ_DEVICE):
+ std->len = 1;
+ sc->sc_hub_desc.temp[0] = sc->sc_conf;
+ break;
+ case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE):
+ switch (value >> 8) {
+ case UDESC_DEVICE:
+ if ((value & 0xff) != 0) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ std->len = sizeof(ohci_devd);
+ sc->sc_hub_desc.devd = ohci_devd;
+ break;
+
+ case UDESC_CONFIG:
+ if ((value & 0xff) != 0) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ std->len = sizeof(ohci_confd);
+ std->ptr = USB_ADD_BYTES(&ohci_confd, 0);
+ break;
+
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ ptr = "\001";
+ break;
+
+ case 1: /* Vendor */
+ ptr = sc->sc_vendor;
+ break;
+
+ case 2: /* Product */
+ ptr = "OHCI root HUB";
+ break;
+
+ default:
+ ptr = "";
+ break;
+ }
+
+ std->len = usb2_make_str_desc
+ (sc->sc_hub_desc.temp,
+ sizeof(sc->sc_hub_desc.temp),
+ ptr);
+ break;
+
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ case C(UR_GET_INTERFACE, UT_READ_INTERFACE):
+ std->len = 1;
+ sc->sc_hub_desc.temp[0] = 0;
+ break;
+ case C(UR_GET_STATUS, UT_READ_DEVICE):
+ std->len = 2;
+ USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED);
+ break;
+ case C(UR_GET_STATUS, UT_READ_INTERFACE):
+ case C(UR_GET_STATUS, UT_READ_ENDPOINT):
+ std->len = 2;
+ USETW(sc->sc_hub_desc.stat.wStatus, 0);
+ break;
+ case C(UR_SET_ADDRESS, UT_WRITE_DEVICE):
+ if (value >= USB_MAX_DEVICES) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ sc->sc_addr = value;
+ break;
+ case C(UR_SET_CONFIG, UT_WRITE_DEVICE):
+ if ((value != 0) && (value != 1)) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ sc->sc_conf = value;
+ break;
+ case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE):
+ break;
+ case C(UR_SET_FEATURE, UT_WRITE_DEVICE):
+ case C(UR_SET_FEATURE, UT_WRITE_INTERFACE):
+ case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT):
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE):
+ break;
+ case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT):
+ break;
+ /* Hub requests */
+ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE):
+ break;
+ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER):
+ DPRINTFN(9, "UR_CLEAR_PORT_FEATURE "
+ "port=%d feature=%d\n",
+ index, value);
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ port = OHCI_RH_PORT_STATUS(index);
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ OWRITE4(sc, port, UPS_CURRENT_CONNECT_STATUS);
+ break;
+ case UHF_PORT_SUSPEND:
+ OWRITE4(sc, port, UPS_OVERCURRENT_INDICATOR);
+ break;
+ case UHF_PORT_POWER:
+ /* Yes, writing to the LOW_SPEED bit clears power. */
+ OWRITE4(sc, port, UPS_LOW_SPEED);
+ break;
+ case UHF_C_PORT_CONNECTION:
+ OWRITE4(sc, port, UPS_C_CONNECT_STATUS << 16);
+ break;
+ case UHF_C_PORT_ENABLE:
+ OWRITE4(sc, port, UPS_C_PORT_ENABLED << 16);
+ break;
+ case UHF_C_PORT_SUSPEND:
+ OWRITE4(sc, port, UPS_C_SUSPEND << 16);
+ break;
+ case UHF_C_PORT_OVER_CURRENT:
+ OWRITE4(sc, port, UPS_C_OVERCURRENT_INDICATOR << 16);
+ break;
+ case UHF_C_PORT_RESET:
+ OWRITE4(sc, port, UPS_C_PORT_RESET << 16);
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ switch (value) {
+ case UHF_C_PORT_CONNECTION:
+ case UHF_C_PORT_ENABLE:
+ case UHF_C_PORT_SUSPEND:
+ case UHF_C_PORT_OVER_CURRENT:
+ case UHF_C_PORT_RESET:
+ /* enable RHSC interrupt if condition is cleared. */
+ if ((OREAD4(sc, port) >> 16) == 0)
+ ohci_rhsc_enable(sc);
+ break;
+ default:
+ break;
+ }
+ break;
+ case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE):
+ if ((value & 0xff) != 0) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ v = OREAD4(sc, OHCI_RH_DESCRIPTOR_A);
+
+ sc->sc_hub_desc.hubd = ohci_hubd;
+ sc->sc_hub_desc.hubd.bNbrPorts = sc->sc_noport;
+ USETW(sc->sc_hub_desc.hubd.wHubCharacteristics,
+ (v & OHCI_NPS ? UHD_PWR_NO_SWITCH :
+ v & OHCI_PSM ? UHD_PWR_GANGED : UHD_PWR_INDIVIDUAL)
+ /* XXX overcurrent */
+ );
+ sc->sc_hub_desc.hubd.bPwrOn2PwrGood = OHCI_GET_POTPGT(v);
+ v = OREAD4(sc, OHCI_RH_DESCRIPTOR_B);
+
+ for (l = 0; l < sc->sc_noport; l++) {
+ if (v & 1) {
+ sc->sc_hub_desc.hubd.DeviceRemovable[l / 8] |= (1 << (l % 8));
+ }
+ v >>= 1;
+ }
+ sc->sc_hub_desc.hubd.bDescLength =
+ 8 + ((sc->sc_noport + 7) / 8);
+ std->len = sc->sc_hub_desc.hubd.bDescLength;
+ break;
+
+ case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE):
+ std->len = 16;
+ bzero(sc->sc_hub_desc.temp, 16);
+ break;
+ case C(UR_GET_STATUS, UT_READ_CLASS_OTHER):
+ DPRINTFN(9, "get port status i=%d\n",
+ index);
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ v = OREAD4(sc, OHCI_RH_PORT_STATUS(index));
+ DPRINTFN(9, "port status=0x%04x\n", v);
+ USETW(sc->sc_hub_desc.ps.wPortStatus, v);
+ USETW(sc->sc_hub_desc.ps.wPortChange, v >> 16);
+ std->len = sizeof(sc->sc_hub_desc.ps);
+ break;
+ case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE):
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE):
+ break;
+ case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER):
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ port = OHCI_RH_PORT_STATUS(index);
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ OWRITE4(sc, port, UPS_PORT_ENABLED);
+ break;
+ case UHF_PORT_SUSPEND:
+ OWRITE4(sc, port, UPS_SUSPEND);
+ break;
+ case UHF_PORT_RESET:
+ DPRINTFN(6, "reset port %d\n", index);
+ OWRITE4(sc, port, UPS_RESET);
+ for (v = 0;; v++) {
+ if (v < 12) {
+ if (use_polling) {
+ /* polling */
+ DELAY(USB_PORT_ROOT_RESET_DELAY * 1000);
+ } else {
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(USB_PORT_ROOT_RESET_DELAY));
+ }
+
+ if ((OREAD4(sc, port) & UPS_RESET) == 0) {
+ break;
+ }
+ } else {
+ std->err = USB_ERR_TIMEOUT;
+ goto done;
+ }
+ }
+ DPRINTFN(9, "ohci port %d reset, status = 0x%04x\n",
+ index, OREAD4(sc, port));
+ break;
+ case UHF_PORT_POWER:
+ DPRINTFN(3, "set port power %d\n", index);
+ OWRITE4(sc, port, UPS_PORT_POWER);
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+done:
+ return;
+}
+
+static void
+ohci_root_ctrl_poll(struct ohci_softc *sc)
+{
+ usb2_sw_transfer(&sc->sc_root_ctrl,
+ &ohci_root_ctrl_done);
+}
+
+struct usb2_pipe_methods ohci_root_ctrl_methods =
+{
+ .open = ohci_root_ctrl_open,
+ .close = ohci_root_ctrl_close,
+ .enter = ohci_root_ctrl_enter,
+ .start = ohci_root_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 0,
+};
+
+/*------------------------------------------------------------------------*
+ * ohci root interrupt support
+ *------------------------------------------------------------------------*/
+static void
+ohci_root_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_root_intr_close(struct usb2_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_intr.xfer == xfer) {
+ sc->sc_root_intr.xfer = NULL;
+ }
+ ohci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ohci_root_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_root_intr_start(struct usb2_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_intr.xfer = xfer;
+}
+
+struct usb2_pipe_methods ohci_root_intr_methods =
+{
+ .open = ohci_root_intr_open,
+ .close = ohci_root_intr_close,
+ .enter = ohci_root_intr_enter,
+ .start = ohci_root_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+static void
+ohci_xfer_setup(struct usb2_setup_params *parm)
+{
+ struct usb2_page_search page_info;
+ struct usb2_page_cache *pc;
+ ohci_softc_t *sc;
+ struct usb2_xfer *xfer;
+ void *last_obj;
+ uint32_t ntd;
+ uint32_t nitd;
+ uint32_t nqh;
+ uint32_t n;
+
+ sc = OHCI_BUS2SC(parm->udev->bus);
+ xfer = parm->curr_xfer;
+
+ parm->hc_max_packet_size = 0x500;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = OHCI_PAGE_SIZE;
+
+ /*
+ * calculate ntd and nqh
+ */
+ if (parm->methods == &ohci_device_ctrl_methods) {
+ xfer->flags_int.bdma_enable = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ nitd = 0;
+ ntd = ((2 * xfer->nframes) + 1 /* STATUS */
+ + (xfer->max_data_length / xfer->max_usb2_frame_size));
+ nqh = 1;
+
+ } else if (parm->methods == &ohci_device_bulk_methods) {
+ xfer->flags_int.bdma_enable = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ nitd = 0;
+ ntd = ((2 * xfer->nframes)
+ + (xfer->max_data_length / xfer->max_usb2_frame_size));
+ nqh = 1;
+
+ } else if (parm->methods == &ohci_device_intr_methods) {
+ xfer->flags_int.bdma_enable = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ nitd = 0;
+ ntd = ((2 * xfer->nframes)
+ + (xfer->max_data_length / xfer->max_usb2_frame_size));
+ nqh = 1;
+
+ } else if (parm->methods == &ohci_device_isoc_methods) {
+ xfer->flags_int.bdma_enable = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ nitd = ((xfer->max_data_length / OHCI_PAGE_SIZE) +
+ ((xfer->nframes + OHCI_ITD_NOFFSET - 1) / OHCI_ITD_NOFFSET) +
+ 1 /* EXTRA */ );
+ ntd = 0;
+ nqh = 1;
+
+ } else {
+
+ usb2_transfer_setup_sub(parm);
+
+ nitd = 0;
+ ntd = 0;
+ nqh = 0;
+ }
+
+alloc_dma_set:
+
+ if (parm->err) {
+ return;
+ }
+ last_obj = NULL;
+
+ if (usb2_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ohci_td_t),
+ OHCI_TD_ALIGN, ntd)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != ntd; n++) {
+ ohci_td_t *td;
+
+ usb2_get_page(pc + n, 0, &page_info);
+
+ td = page_info.buffer;
+
+ /* init TD */
+ td->td_self = htole32(page_info.physaddr);
+ td->obj_next = last_obj;
+ td->page_cache = pc + n;
+
+ last_obj = td;
+
+ usb2_pc_cpu_flush(pc + n);
+ }
+ }
+ if (usb2_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ohci_itd_t),
+ OHCI_ITD_ALIGN, nitd)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nitd; n++) {
+ ohci_itd_t *itd;
+
+ usb2_get_page(pc + n, 0, &page_info);
+
+ itd = page_info.buffer;
+
+ /* init TD */
+ itd->itd_self = htole32(page_info.physaddr);
+ itd->obj_next = last_obj;
+ itd->page_cache = pc + n;
+
+ last_obj = itd;
+
+ usb2_pc_cpu_flush(pc + n);
+ }
+ }
+ xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj;
+
+ last_obj = NULL;
+
+ if (usb2_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ohci_ed_t),
+ OHCI_ED_ALIGN, nqh)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nqh; n++) {
+ ohci_ed_t *ed;
+
+ usb2_get_page(pc + n, 0, &page_info);
+
+ ed = page_info.buffer;
+
+ /* init QH */
+ ed->ed_self = htole32(page_info.physaddr);
+ ed->obj_next = last_obj;
+ ed->page_cache = pc + n;
+
+ last_obj = ed;
+
+ usb2_pc_cpu_flush(pc + n);
+ }
+ }
+ xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj;
+
+ if (!xfer->flags_int.curr_dma_set) {
+ xfer->flags_int.curr_dma_set = 1;
+ goto alloc_dma_set;
+ }
+}
+
+static void
+ohci_pipe_init(struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc,
+ struct usb2_pipe *pipe)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
+ pipe, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb2_mode,
+ sc->sc_addr);
+
+ if (udev->flags.usb2_mode != USB_MODE_HOST) {
+ /* not supported */
+ return;
+ }
+ if (udev->device_index == sc->sc_addr) {
+ switch (edesc->bEndpointAddress) {
+ case USB_CONTROL_ENDPOINT:
+ pipe->methods = &ohci_root_ctrl_methods;
+ break;
+ case UE_DIR_IN | OHCI_INTR_ENDPT:
+ pipe->methods = &ohci_root_intr_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ } else {
+ switch (edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_CONTROL:
+ pipe->methods = &ohci_device_ctrl_methods;
+ break;
+ case UE_INTERRUPT:
+ pipe->methods = &ohci_device_intr_methods;
+ break;
+ case UE_ISOCHRONOUS:
+ if (udev->speed == USB_SPEED_FULL) {
+ pipe->methods = &ohci_device_isoc_methods;
+ }
+ break;
+ case UE_BULK:
+ if (udev->speed != USB_SPEED_LOW) {
+ pipe->methods = &ohci_device_bulk_methods;
+ }
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+}
+
+static void
+ohci_xfer_unsetup(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_get_dma_delay(struct usb2_bus *bus, uint32_t *pus)
+{
+ /*
+ * Wait until hardware has finished any possible use of the
+ * transfer descriptor(s) and QH
+ */
+ *pus = (1125); /* microseconds */
+}
+
+static void
+ohci_device_resume(struct usb2_device *udev)
+{
+ struct ohci_softc *sc = OHCI_BUS2SC(udev->bus);
+ struct usb2_xfer *xfer;
+ struct usb2_pipe_methods *methods;
+ ohci_ed_t *ed;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(udev->bus);
+
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+
+ if (xfer->xroot->udev == udev) {
+
+ methods = xfer->pipe->methods;
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ if (methods == &ohci_device_bulk_methods) {
+ OHCI_APPEND_QH(ed, sc->sc_bulk_p_last);
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF);
+ }
+ if (methods == &ohci_device_ctrl_methods) {
+ OHCI_APPEND_QH(ed, sc->sc_ctrl_p_last);
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF);
+ }
+ if (methods == &ohci_device_intr_methods) {
+ OHCI_APPEND_QH(ed, sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ }
+ }
+
+ USB_BUS_UNLOCK(udev->bus);
+
+ return;
+}
+
+static void
+ohci_device_suspend(struct usb2_device *udev)
+{
+ struct ohci_softc *sc = OHCI_BUS2SC(udev->bus);
+ struct usb2_xfer *xfer;
+ struct usb2_pipe_methods *methods;
+ ohci_ed_t *ed;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(udev->bus);
+
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+
+ if (xfer->xroot->udev == udev) {
+
+ methods = xfer->pipe->methods;
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ if (methods == &ohci_device_bulk_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_bulk_p_last);
+ }
+ if (methods == &ohci_device_ctrl_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_ctrl_p_last);
+ }
+ if (methods == &ohci_device_intr_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ }
+ }
+
+ USB_BUS_UNLOCK(udev->bus);
+
+ return;
+}
+
+static void
+ohci_set_hw_power(struct usb2_bus *bus)
+{
+ struct ohci_softc *sc = OHCI_BUS2SC(bus);
+ uint32_t temp;
+ uint32_t flags;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(bus);
+
+ flags = bus->hw_power_state;
+
+ temp = OREAD4(sc, OHCI_CONTROL);
+ temp &= ~(OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE);
+
+ if (flags & USB_HW_POWER_CONTROL)
+ temp |= OHCI_CLE;
+
+ if (flags & USB_HW_POWER_BULK)
+ temp |= OHCI_BLE;
+
+ if (flags & USB_HW_POWER_INTERRUPT)
+ temp |= OHCI_PLE;
+
+ if (flags & USB_HW_POWER_ISOC)
+ temp |= OHCI_IE | OHCI_PLE;
+
+ OWRITE4(sc, OHCI_CONTROL, temp);
+
+ USB_BUS_UNLOCK(bus);
+
+ return;
+}
+
+struct usb2_bus_methods ohci_bus_methods =
+{
+ .pipe_init = ohci_pipe_init,
+ .xfer_setup = ohci_xfer_setup,
+ .xfer_unsetup = ohci_xfer_unsetup,
+ .do_poll = ohci_do_poll,
+ .get_dma_delay = ohci_get_dma_delay,
+ .device_resume = ohci_device_resume,
+ .device_suspend = ohci_device_suspend,
+ .set_hw_power = ohci_set_hw_power,
+ .roothub_exec = ohci_root_ctrl_task,
+};
diff --git a/sys/dev/usb/controller/ohci.h b/sys/dev/usb/controller/ohci.h
new file mode 100644
index 0000000..84a6afd
--- /dev/null
+++ b/sys/dev/usb/controller/ohci.h
@@ -0,0 +1,366 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 1998 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Lennart Augustsson (lennart@augustsson.net) at
+ * Carlstedt Research & Technology.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the NetBSD
+ * Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#ifndef _OHCI_H_
+#define _OHCI_H_
+
+#define OHCI_MAX_DEVICES USB_MAX_DEVICES
+
+/* PCI config registers */
+#define PCI_CBMEM 0x10 /* configuration base memory */
+#define PCI_INTERFACE_OHCI 0x10
+
+/* OHCI registers */
+#define OHCI_REVISION 0x00 /* OHCI revision */
+#define OHCI_REV_LO(rev) ((rev) & 0xf)
+#define OHCI_REV_HI(rev) (((rev)>>4) & 0xf)
+#define OHCI_REV_LEGACY(rev) ((rev) & 0x100)
+#define OHCI_CONTROL 0x04
+#define OHCI_CBSR_MASK 0x00000003 /* Control/Bulk Service Ratio */
+#define OHCI_RATIO_1_1 0x00000000
+#define OHCI_RATIO_1_2 0x00000001
+#define OHCI_RATIO_1_3 0x00000002
+#define OHCI_RATIO_1_4 0x00000003
+#define OHCI_PLE 0x00000004 /* Periodic List Enable */
+#define OHCI_IE 0x00000008 /* Isochronous Enable */
+#define OHCI_CLE 0x00000010 /* Control List Enable */
+#define OHCI_BLE 0x00000020 /* Bulk List Enable */
+#define OHCI_HCFS_MASK 0x000000c0 /* HostControllerFunctionalStat
+ * e */
+#define OHCI_HCFS_RESET 0x00000000
+#define OHCI_HCFS_RESUME 0x00000040
+#define OHCI_HCFS_OPERATIONAL 0x00000080
+#define OHCI_HCFS_SUSPEND 0x000000c0
+#define OHCI_IR 0x00000100 /* Interrupt Routing */
+#define OHCI_RWC 0x00000200 /* Remote Wakeup Connected */
+#define OHCI_RWE 0x00000400 /* Remote Wakeup Enabled */
+#define OHCI_COMMAND_STATUS 0x08
+#define OHCI_HCR 0x00000001 /* Host Controller Reset */
+#define OHCI_CLF 0x00000002 /* Control List Filled */
+#define OHCI_BLF 0x00000004 /* Bulk List Filled */
+#define OHCI_OCR 0x00000008 /* Ownership Change Request */
+#define OHCI_SOC_MASK 0x00030000 /* Scheduling Overrun Count */
+#define OHCI_INTERRUPT_STATUS 0x0c
+#define OHCI_SO 0x00000001 /* Scheduling Overrun */
+#define OHCI_WDH 0x00000002 /* Writeback Done Head */
+#define OHCI_SF 0x00000004 /* Start of Frame */
+#define OHCI_RD 0x00000008 /* Resume Detected */
+#define OHCI_UE 0x00000010 /* Unrecoverable Error */
+#define OHCI_FNO 0x00000020 /* Frame Number Overflow */
+#define OHCI_RHSC 0x00000040 /* Root Hub Status Change */
+#define OHCI_OC 0x40000000 /* Ownership Change */
+#define OHCI_MIE 0x80000000 /* Master Interrupt Enable */
+#define OHCI_INTERRUPT_ENABLE 0x10
+#define OHCI_INTERRUPT_DISABLE 0x14
+#define OHCI_HCCA 0x18
+#define OHCI_PERIOD_CURRENT_ED 0x1c
+#define OHCI_CONTROL_HEAD_ED 0x20
+#define OHCI_CONTROL_CURRENT_ED 0x24
+#define OHCI_BULK_HEAD_ED 0x28
+#define OHCI_BULK_CURRENT_ED 0x2c
+#define OHCI_DONE_HEAD 0x30
+#define OHCI_FM_INTERVAL 0x34
+#define OHCI_GET_IVAL(s) ((s) & 0x3fff)
+#define OHCI_GET_FSMPS(s) (((s) >> 16) & 0x7fff)
+#define OHCI_FIT 0x80000000
+#define OHCI_FM_REMAINING 0x38
+#define OHCI_FM_NUMBER 0x3c
+#define OHCI_PERIODIC_START 0x40
+#define OHCI_LS_THRESHOLD 0x44
+#define OHCI_RH_DESCRIPTOR_A 0x48
+#define OHCI_GET_NDP(s) ((s) & 0xff)
+#define OHCI_PSM 0x0100 /* Power Switching Mode */
+#define OHCI_NPS 0x0200 /* No Power Switching */
+#define OHCI_DT 0x0400 /* Device Type */
+#define OHCI_OCPM 0x0800 /* Overcurrent Protection Mode */
+#define OHCI_NOCP 0x1000 /* No Overcurrent Protection */
+#define OHCI_GET_POTPGT(s) ((s) >> 24)
+#define OHCI_RH_DESCRIPTOR_B 0x4c
+#define OHCI_RH_STATUS 0x50
+#define OHCI_LPS 0x00000001 /* Local Power Status */
+#define OHCI_OCI 0x00000002 /* OverCurrent Indicator */
+#define OHCI_DRWE 0x00008000 /* Device Remote Wakeup Enable */
+#define OHCI_LPSC 0x00010000 /* Local Power Status Change */
+#define OHCI_CCIC 0x00020000 /* OverCurrent Indicator
+ * Change */
+#define OHCI_CRWE 0x80000000 /* Clear Remote Wakeup Enable */
+#define OHCI_RH_PORT_STATUS(n) (0x50 + ((n)*4)) /* 1 based indexing */
+
+#define OHCI_LES (OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE)
+#define OHCI_ALL_INTRS (OHCI_SO | OHCI_WDH | OHCI_SF | \
+ OHCI_RD | OHCI_UE | OHCI_FNO | \
+ OHCI_RHSC | OHCI_OC)
+#define OHCI_NORMAL_INTRS (OHCI_WDH | OHCI_RD | OHCI_UE | OHCI_RHSC)
+
+#define OHCI_FSMPS(i) (((i-210)*6/7) << 16)
+#define OHCI_PERIODIC(i) ((i)*9/10)
+
+#define OHCI_NO_INTRS 32
+#define OHCI_HCCA_SIZE 256
+
+/* Structures alignment (bytes) */
+#define OHCI_HCCA_ALIGN 256
+#define OHCI_ED_ALIGN 16
+#define OHCI_TD_ALIGN 16
+#define OHCI_ITD_ALIGN 32
+
+#define OHCI_PAGE_SIZE 0x1000
+#define OHCI_PAGE(x) ((x) &~ 0xfff)
+#define OHCI_PAGE_OFFSET(x) ((x) & 0xfff)
+#define OHCI_PAGE_MASK(x) ((x) & 0xfff)
+
+#if ((USB_PAGE_SIZE < OHCI_ED_ALIGN) || (OHCI_ED_ALIGN == 0) || \
+ (USB_PAGE_SIZE < OHCI_TD_ALIGN) || (OHCI_TD_ALIGN == 0) || \
+ (USB_PAGE_SIZE < OHCI_ITD_ALIGN) || (OHCI_ITD_ALIGN == 0) || \
+ (USB_PAGE_SIZE < OHCI_PAGE_SIZE) || (OHCI_PAGE_SIZE == 0))
+#error "Invalid USB page size!"
+#endif
+
+#define OHCI_VIRTUAL_FRAMELIST_COUNT 128/* dummy */
+
+#if (OHCI_VIRTUAL_FRAMELIST_COUNT < USB_MAX_FS_ISOC_FRAMES_PER_XFER)
+#error "maximum number of full-speed isochronous frames is higher than supported!"
+#endif
+
+struct ohci_hcca {
+ volatile uint32_t hcca_interrupt_table[OHCI_NO_INTRS];
+ volatile uint32_t hcca_frame_number;
+ volatile uint32_t hcca_done_head;
+#define OHCI_DONE_INTRS 1
+} __aligned(OHCI_HCCA_ALIGN);
+
+typedef struct ohci_hcca ohci_hcca_t;
+
+struct ohci_ed {
+ volatile uint32_t ed_flags;
+#define OHCI_ED_GET_FA(s) ((s) & 0x7f)
+#define OHCI_ED_ADDRMASK 0x0000007f
+#define OHCI_ED_SET_FA(s) (s)
+#define OHCI_ED_GET_EN(s) (((s) >> 7) & 0xf)
+#define OHCI_ED_SET_EN(s) ((s) << 7)
+#define OHCI_ED_DIR_MASK 0x00001800
+#define OHCI_ED_DIR_TD 0x00000000
+#define OHCI_ED_DIR_OUT 0x00000800
+#define OHCI_ED_DIR_IN 0x00001000
+#define OHCI_ED_SPEED 0x00002000
+#define OHCI_ED_SKIP 0x00004000
+#define OHCI_ED_FORMAT_GEN 0x00000000
+#define OHCI_ED_FORMAT_ISO 0x00008000
+#define OHCI_ED_GET_MAXP(s) (((s) >> 16) & 0x07ff)
+#define OHCI_ED_SET_MAXP(s) ((s) << 16)
+#define OHCI_ED_MAXPMASK (0x7ff << 16)
+ volatile uint32_t ed_tailp;
+ volatile uint32_t ed_headp;
+#define OHCI_HALTED 0x00000001
+#define OHCI_TOGGLECARRY 0x00000002
+#define OHCI_HEADMASK 0xfffffffc
+ volatile uint32_t ed_next;
+/*
+ * Extra information needed:
+ */
+ struct ohci_ed *next;
+ struct ohci_ed *prev;
+ struct ohci_ed *obj_next;
+ struct usb2_page_cache *page_cache;
+ uint32_t ed_self;
+} __aligned(OHCI_ED_ALIGN);
+
+typedef struct ohci_ed ohci_ed_t;
+
+struct ohci_td {
+ volatile uint32_t td_flags;
+#define OHCI_TD_R 0x00040000 /* Buffer Rounding */
+#define OHCI_TD_DP_MASK 0x00180000 /* Direction / PID */
+#define OHCI_TD_SETUP 0x00000000
+#define OHCI_TD_OUT 0x00080000
+#define OHCI_TD_IN 0x00100000
+#define OHCI_TD_GET_DI(x) (((x) >> 21) & 7) /* Delay Interrupt */
+#define OHCI_TD_SET_DI(x) ((x) << 21)
+#define OHCI_TD_NOINTR 0x00e00000
+#define OHCI_TD_INTR_MASK 0x00e00000
+#define OHCI_TD_TOGGLE_CARRY 0x00000000
+#define OHCI_TD_TOGGLE_0 0x02000000
+#define OHCI_TD_TOGGLE_1 0x03000000
+#define OHCI_TD_TOGGLE_MASK 0x03000000
+#define OHCI_TD_GET_EC(x) (((x) >> 26) & 3) /* Error Count */
+#define OHCI_TD_GET_CC(x) ((x) >> 28) /* Condition Code */
+#define OHCI_TD_SET_CC(x) ((x) << 28)
+#define OHCI_TD_NOCC 0xf0000000
+ volatile uint32_t td_cbp; /* Current Buffer Pointer */
+ volatile uint32_t td_next; /* Next TD */
+#define OHCI_TD_NEXT_END 0
+ volatile uint32_t td_be; /* Buffer End */
+/*
+ * Extra information needed:
+ */
+ struct ohci_td *obj_next;
+ struct ohci_td *alt_next;
+ struct usb2_page_cache *page_cache;
+ uint32_t td_self;
+ uint16_t len;
+} __aligned(OHCI_TD_ALIGN);
+
+typedef struct ohci_td ohci_td_t;
+
+struct ohci_itd {
+ volatile uint32_t itd_flags;
+#define OHCI_ITD_GET_SF(x) ((x) & 0x0000ffff)
+#define OHCI_ITD_SET_SF(x) ((x) & 0xffff)
+#define OHCI_ITD_GET_DI(x) (((x) >> 21) & 7) /* Delay Interrupt */
+#define OHCI_ITD_SET_DI(x) ((x) << 21)
+#define OHCI_ITD_NOINTR 0x00e00000
+#define OHCI_ITD_GET_FC(x) ((((x) >> 24) & 7)+1) /* Frame Count */
+#define OHCI_ITD_SET_FC(x) (((x)-1) << 24)
+#define OHCI_ITD_GET_CC(x) ((x) >> 28) /* Condition Code */
+#define OHCI_ITD_NOCC 0xf0000000
+#define OHCI_ITD_NOFFSET 8
+ volatile uint32_t itd_bp0; /* Buffer Page 0 */
+ volatile uint32_t itd_next; /* Next ITD */
+ volatile uint32_t itd_be; /* Buffer End */
+ volatile uint16_t itd_offset[OHCI_ITD_NOFFSET]; /* Buffer offsets and
+ * Status */
+#define OHCI_ITD_PAGE_SELECT 0x00001000
+#define OHCI_ITD_MK_OFFS(len) (0xe000 | ((len) & 0x1fff))
+#define OHCI_ITD_PSW_LENGTH(x) ((x) & 0xfff) /* Transfer length */
+#define OHCI_ITD_PSW_GET_CC(x) ((x) >> 12) /* Condition Code */
+/*
+ * Extra information needed:
+ */
+ struct ohci_itd *obj_next;
+ struct usb2_page_cache *page_cache;
+ uint32_t itd_self;
+ uint8_t frames;
+} __aligned(OHCI_ITD_ALIGN);
+
+typedef struct ohci_itd ohci_itd_t;
+
+#define OHCI_CC_NO_ERROR 0
+#define OHCI_CC_CRC 1
+#define OHCI_CC_BIT_STUFFING 2
+#define OHCI_CC_DATA_TOGGLE_MISMATCH 3
+#define OHCI_CC_STALL 4
+#define OHCI_CC_DEVICE_NOT_RESPONDING 5
+#define OHCI_CC_PID_CHECK_FAILURE 6
+#define OHCI_CC_UNEXPECTED_PID 7
+#define OHCI_CC_DATA_OVERRUN 8
+#define OHCI_CC_DATA_UNDERRUN 9
+#define OHCI_CC_BUFFER_OVERRUN 12
+#define OHCI_CC_BUFFER_UNDERRUN 13
+#define OHCI_CC_NOT_ACCESSED 15
+
+/* Some delay needed when changing certain registers. */
+#define OHCI_ENABLE_POWER_DELAY 5
+#define OHCI_READ_DESC_DELAY 5
+
+#define OHCI_NO_EDS (2*OHCI_NO_INTRS)
+
+struct ohci_hw_softc {
+ struct usb2_page_cache hcca_pc;
+ struct usb2_page_cache ctrl_start_pc;
+ struct usb2_page_cache bulk_start_pc;
+ struct usb2_page_cache isoc_start_pc;
+ struct usb2_page_cache intr_start_pc[OHCI_NO_EDS];
+
+ struct usb2_page hcca_pg;
+ struct usb2_page ctrl_start_pg;
+ struct usb2_page bulk_start_pg;
+ struct usb2_page isoc_start_pg;
+ struct usb2_page intr_start_pg[OHCI_NO_EDS];
+};
+
+struct ohci_config_desc {
+ struct usb2_config_descriptor confd;
+ struct usb2_interface_descriptor ifcd;
+ struct usb2_endpoint_descriptor endpd;
+} __packed;
+
+union ohci_hub_desc {
+ struct usb2_status stat;
+ struct usb2_port_status ps;
+ struct usb2_device_descriptor devd;
+ struct usb2_hub_descriptor hubd;
+ uint8_t temp[128];
+};
+
+typedef struct ohci_softc {
+ struct ohci_hw_softc sc_hw;
+ struct usb2_bus sc_bus; /* base device */
+ struct usb2_callout sc_tmo_rhsc;
+ union ohci_hub_desc sc_hub_desc;
+ struct usb2_sw_transfer sc_root_ctrl;
+ struct usb2_sw_transfer sc_root_intr;
+
+ struct usb2_device *sc_devices[OHCI_MAX_DEVICES];
+ struct resource *sc_io_res;
+ struct resource *sc_irq_res;
+ struct ohci_hcca *sc_hcca_p;
+ struct ohci_ed *sc_ctrl_p_last;
+ struct ohci_ed *sc_bulk_p_last;
+ struct ohci_ed *sc_isoc_p_last;
+ struct ohci_ed *sc_intr_p_last[OHCI_NO_EDS];
+ void *sc_intr_hdl;
+ device_t sc_dev;
+ bus_size_t sc_io_size;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+
+ uint32_t sc_eintrs; /* enabled interrupts */
+ uint32_t sc_control; /* Preserved during suspend/standby */
+ uint32_t sc_intre;
+
+ uint16_t sc_intr_stat[OHCI_NO_EDS];
+ uint16_t sc_id_vendor;
+
+ uint8_t sc_noport;
+ uint8_t sc_addr; /* device address */
+ uint8_t sc_conf; /* device configuration */
+ uint8_t sc_hub_idata[32];
+
+ char sc_vendor[16];
+
+} ohci_softc_t;
+
+usb2_bus_mem_cb_t ohci_iterate_hw_softc;
+
+usb2_error_t ohci_init(ohci_softc_t *sc);
+void ohci_detach(struct ohci_softc *sc);
+void ohci_suspend(ohci_softc_t *sc);
+void ohci_resume(ohci_softc_t *sc);
+void ohci_interrupt(ohci_softc_t *sc);
+
+#endif /* _OHCI_H_ */
diff --git a/sys/dev/usb/controller/ohci_atmelarm.c b/sys/dev/usb/controller/ohci_atmelarm.c
new file mode 100644
index 0000000..562cf3d
--- /dev/null
+++ b/sys/dev/usb/controller/ohci_atmelarm.c
@@ -0,0 +1,223 @@
+/*-
+ * Copyright (c) 2006 M. Warner Losh. 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_defs.h>
+#include <dev/usb/usb.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/ohci.h>
+
+#include <sys/rman.h>
+
+#include <arm/at91/at91_pmcvar.h>
+
+#define MEM_RID 0
+
+static device_probe_t ohci_atmelarm_probe;
+static device_attach_t ohci_atmelarm_attach;
+static device_detach_t ohci_atmelarm_detach;
+
+struct at91_ohci_softc {
+ struct ohci_softc sc_ohci; /* must be first */
+ struct at91_pmc_clock *iclk;
+ struct at91_pmc_clock *fclk;
+};
+
+static int
+ohci_atmelarm_probe(device_t dev)
+{
+ device_set_desc(dev, "AT91 integrated OHCI controller");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+ohci_atmelarm_attach(device_t dev)
+{
+ struct at91_ohci_softc *sc = device_get_softc(dev);
+ int err;
+ int rid;
+
+ /* initialise some bus fields */
+ sc->sc_ohci.sc_bus.parent = dev;
+ sc->sc_ohci.sc_bus.devices = sc->sc_ohci.sc_devices;
+ sc->sc_ohci.sc_bus.devices_max = OHCI_MAX_DEVICES;
+
+ /* get all DMA memory */
+ if (usb2_bus_mem_alloc_all(&sc->sc_ohci.sc_bus,
+ USB_GET_DMA_TAG(dev), &ohci_iterate_hw_softc)) {
+ return (ENOMEM);
+ }
+ sc->iclk = at91_pmc_clock_ref("ohci_clk");
+ sc->fclk = at91_pmc_clock_ref("uhpck");
+
+ sc->sc_ohci.sc_dev = dev;
+
+ rid = MEM_RID;
+ sc->sc_ohci.sc_io_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
+ &rid, RF_ACTIVE);
+
+ if (!(sc->sc_ohci.sc_io_res)) {
+ err = ENOMEM;
+ goto error;
+ }
+ sc->sc_ohci.sc_io_tag = rman_get_bustag(sc->sc_ohci.sc_io_res);
+ sc->sc_ohci.sc_io_hdl = rman_get_bushandle(sc->sc_ohci.sc_io_res);
+ sc->sc_ohci.sc_io_size = rman_get_size(sc->sc_ohci.sc_io_res);
+
+ rid = 0;
+ sc->sc_ohci.sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
+ RF_ACTIVE);
+ if (!(sc->sc_ohci.sc_irq_res)) {
+ goto error;
+ }
+ sc->sc_ohci.sc_bus.bdev = device_add_child(dev, "usbus", -1);
+ if (!(sc->sc_ohci.sc_bus.bdev)) {
+ goto error;
+ }
+ device_set_ivars(sc->sc_ohci.sc_bus.bdev, &sc->sc_ohci.sc_bus);
+
+ strlcpy(sc->sc_ohci.sc_vendor, "Atmel", sizeof(sc->sc_ohci.sc_vendor));
+
+#if (__FreeBSD_version >= 700031)
+ err = bus_setup_intr(dev, sc->sc_ohci.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (void *)ohci_interrupt, sc, &sc->sc_ohci.sc_intr_hdl);
+#else
+ err = bus_setup_intr(dev, sc->sc_ohci.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ (void *)ohci_interrupt, sc, &sc->sc_ohci.sc_intr_hdl);
+#endif
+ if (err) {
+ sc->sc_ohci.sc_intr_hdl = NULL;
+ goto error;
+ }
+ /*
+ * turn on the clocks from the AT91's point of view. Keep the unit in reset.
+ */
+ at91_pmc_clock_enable(sc->iclk);
+ at91_pmc_clock_enable(sc->fclk);
+ bus_space_write_4(sc->sc_ohci.sc_io_tag, sc->sc_ohci.sc_io_hdl,
+ OHCI_CONTROL, 0);
+
+ err = ohci_init(&sc->sc_ohci);
+ if (!err) {
+ err = device_probe_and_attach(sc->sc_ohci.sc_bus.bdev);
+ }
+ if (err) {
+ goto error;
+ }
+ return (0);
+
+error:
+ ohci_atmelarm_detach(dev);
+ return (ENXIO);
+}
+
+static int
+ohci_atmelarm_detach(device_t dev)
+{
+ struct at91_ohci_softc *sc = device_get_softc(dev);
+ device_t bdev;
+ int err;
+
+ if (sc->sc_ohci.sc_bus.bdev) {
+ bdev = sc->sc_ohci.sc_bus.bdev;
+ device_detach(bdev);
+ device_delete_child(dev, bdev);
+ }
+ /* during module unload there are lots of children leftover */
+ device_delete_all_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->sc_ohci.sc_io_tag, sc->sc_ohci.sc_io_hdl,
+ OHCI_CONTROL, 0);
+
+ at91_pmc_clock_disable(sc->fclk);
+ at91_pmc_clock_disable(sc->iclk);
+ at91_pmc_clock_deref(sc->fclk);
+ at91_pmc_clock_deref(sc->iclk);
+
+ if (sc->sc_ohci.sc_irq_res && sc->sc_ohci.sc_intr_hdl) {
+ /*
+ * only call ohci_detach() after ohci_init()
+ */
+ ohci_detach(&sc->sc_ohci);
+
+ err = bus_teardown_intr(dev, sc->sc_ohci.sc_irq_res, sc->sc_ohci.sc_intr_hdl);
+ sc->sc_ohci.sc_intr_hdl = NULL;
+ }
+ if (sc->sc_ohci.sc_irq_res) {
+ bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_ohci.sc_irq_res);
+ sc->sc_ohci.sc_irq_res = NULL;
+ }
+ if (sc->sc_ohci.sc_io_res) {
+ bus_release_resource(dev, SYS_RES_MEMORY, MEM_RID,
+ sc->sc_ohci.sc_io_res);
+ sc->sc_ohci.sc_io_res = NULL;
+ }
+ usb2_bus_mem_free_all(&sc->sc_ohci.sc_bus, &ohci_iterate_hw_softc);
+
+ return (0);
+}
+
+static device_method_t ohci_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, ohci_atmelarm_probe),
+ DEVMETHOD(device_attach, ohci_atmelarm_attach),
+ DEVMETHOD(device_detach, ohci_atmelarm_detach),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+
+ /* Bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+
+ {0, 0}
+};
+
+static driver_t ohci_driver = {
+ "ohci",
+ ohci_methods,
+ sizeof(struct at91_ohci_softc),
+};
+
+static devclass_t ohci_devclass;
+
+DRIVER_MODULE(ohci, atmelarm, ohci_driver, ohci_devclass, 0, 0);
+MODULE_DEPEND(ohci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/ohci_pci.c b/sys/dev/usb/controller/ohci_pci.c
new file mode 100644
index 0000000..49591af
--- /dev/null
+++ b/sys/dev/usb/controller/ohci_pci.c
@@ -0,0 +1,387 @@
+/*-
+ * Copyright (c) 1998 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Lennart Augustsson (augustss@carlstedt.se) at
+ * Carlstedt Research & Technology.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the NetBSD
+ * Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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$");
+
+/*
+ * USB Open Host Controller driver.
+ *
+ * OHCI spec: http://www.intel.com/design/usb/ohci11d.pdf
+ */
+
+/* The low level controller code for OHCI has been split into
+ * PCI probes and OHCI specific code. This was done to facilitate the
+ * sharing of code between *BSD's
+ */
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_error.h>
+#include <dev/usb/usb_defs.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/usb_pci.h>
+#include <dev/usb/controller/ohci.h>
+
+#define PCI_OHCI_VENDORID_ACERLABS 0x10b9
+#define PCI_OHCI_VENDORID_AMD 0x1022
+#define PCI_OHCI_VENDORID_APPLE 0x106b
+#define PCI_OHCI_VENDORID_ATI 0x1002
+#define PCI_OHCI_VENDORID_CMDTECH 0x1095
+#define PCI_OHCI_VENDORID_NEC 0x1033
+#define PCI_OHCI_VENDORID_NVIDIA 0x12D2
+#define PCI_OHCI_VENDORID_NVIDIA2 0x10DE
+#define PCI_OHCI_VENDORID_OPTI 0x1045
+#define PCI_OHCI_VENDORID_SIS 0x1039
+#define PCI_OHCI_VENDORID_SUN 0x108e
+
+#define PCI_OHCI_BASE_REG 0x10
+
+static device_probe_t ohci_pci_probe;
+static device_attach_t ohci_pci_attach;
+static device_detach_t ohci_pci_detach;
+static device_suspend_t ohci_pci_suspend;
+static device_resume_t ohci_pci_resume;
+
+static int
+ohci_pci_suspend(device_t self)
+{
+ ohci_softc_t *sc = device_get_softc(self);
+ int err;
+
+ err = bus_generic_suspend(self);
+ if (err) {
+ return (err);
+ }
+ ohci_suspend(sc);
+ return (0);
+}
+
+static int
+ohci_pci_resume(device_t self)
+{
+ ohci_softc_t *sc = device_get_softc(self);
+ uint32_t reg, int_line;
+
+ if (pci_get_powerstate(self) != PCI_POWERSTATE_D0) {
+ device_printf(self, "chip is in D%d mode "
+ "-- setting to D0\n", pci_get_powerstate(self));
+ reg = pci_read_config(self, PCI_CBMEM, 4);
+ int_line = pci_read_config(self, PCIR_INTLINE, 4);
+ pci_set_powerstate(self, PCI_POWERSTATE_D0);
+ pci_write_config(self, PCI_CBMEM, reg, 4);
+ pci_write_config(self, PCIR_INTLINE, int_line, 4);
+ }
+ ohci_resume(sc);
+
+ bus_generic_resume(self);
+ return (0);
+}
+
+static const char *
+ohci_pci_match(device_t self)
+{
+ uint32_t device_id = pci_get_devid(self);
+
+ switch (device_id) {
+ case 0x523710b9:
+ return ("AcerLabs M5237 (Aladdin-V) USB controller");
+
+ case 0x740c1022:
+ return ("AMD-756 USB Controller");
+
+ case 0x74141022:
+ return ("AMD-766 USB Controller");
+
+ case 0x43741002:
+ return "ATI SB400 USB Controller";
+ case 0x43751002:
+ return "ATI SB400 USB Controller";
+
+ case 0x06701095:
+ return ("CMD Tech 670 (USB0670) USB controller");
+
+ case 0x06731095:
+ return ("CMD Tech 673 (USB0673) USB controller");
+
+ case 0xc8611045:
+ return ("OPTi 82C861 (FireLink) USB controller");
+
+ case 0x00351033:
+ return ("NEC uPD 9210 USB controller");
+
+ case 0x00d710de:
+ return ("nVidia nForce3 USB Controller");
+
+ case 0x70011039:
+ return ("SiS 5571 USB controller");
+
+ case 0x1103108e:
+ return "Sun PCIO-2 USB controller";
+
+ case 0x0019106b:
+ return ("Apple KeyLargo USB controller");
+
+ default:
+ break;
+ }
+ if ((pci_get_class(self) == PCIC_SERIALBUS) &&
+ (pci_get_subclass(self) == PCIS_SERIALBUS_USB) &&
+ (pci_get_progif(self) == PCI_INTERFACE_OHCI)) {
+ return ("OHCI (generic) USB controller");
+ }
+ return (NULL);
+}
+
+static int
+ohci_pci_probe(device_t self)
+{
+ const char *desc = ohci_pci_match(self);
+
+ if (desc) {
+ device_set_desc(self, desc);
+ return (0);
+ } else {
+ return (ENXIO);
+ }
+}
+
+static int
+ohci_pci_attach(device_t self)
+{
+ ohci_softc_t *sc = device_get_softc(self);
+ int rid;
+ int err;
+
+ /* initialise some bus fields */
+ sc->sc_bus.parent = self;
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = OHCI_MAX_DEVICES;
+
+ /* get all DMA memory */
+ if (usb2_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self),
+ &ohci_iterate_hw_softc)) {
+ return (ENOMEM);
+ }
+ sc->sc_dev = self;
+
+ pci_enable_busmaster(self);
+
+ /*
+ * Some Sun PCIO-2 USB controllers have their intpin register
+ * bogusly set to 0, although it should be 4. Correct that.
+ */
+ if (pci_get_devid(self) == 0x1103108e && pci_get_intpin(self) == 0)
+ pci_set_intpin(self, 4);
+
+ rid = PCI_CBMEM;
+ 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);
+ sc->sc_io_size = rman_get_size(sc->sc_io_res);
+
+ 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);
+
+ /*
+ * ohci_pci_match will never return NULL if ohci_pci_probe
+ * succeeded
+ */
+ device_set_desc(sc->sc_bus.bdev, ohci_pci_match(self));
+ switch (pci_get_vendor(self)) {
+ case PCI_OHCI_VENDORID_ACERLABS:
+ sprintf(sc->sc_vendor, "AcerLabs");
+ break;
+ case PCI_OHCI_VENDORID_AMD:
+ sprintf(sc->sc_vendor, "AMD");
+ break;
+ case PCI_OHCI_VENDORID_APPLE:
+ sprintf(sc->sc_vendor, "Apple");
+ break;
+ case PCI_OHCI_VENDORID_ATI:
+ sprintf(sc->sc_vendor, "ATI");
+ break;
+ case PCI_OHCI_VENDORID_CMDTECH:
+ sprintf(sc->sc_vendor, "CMDTECH");
+ break;
+ case PCI_OHCI_VENDORID_NEC:
+ sprintf(sc->sc_vendor, "NEC");
+ break;
+ case PCI_OHCI_VENDORID_NVIDIA:
+ case PCI_OHCI_VENDORID_NVIDIA2:
+ sprintf(sc->sc_vendor, "nVidia");
+ break;
+ case PCI_OHCI_VENDORID_OPTI:
+ sprintf(sc->sc_vendor, "OPTi");
+ break;
+ case PCI_OHCI_VENDORID_SIS:
+ sprintf(sc->sc_vendor, "SiS");
+ break;
+ case PCI_OHCI_VENDORID_SUN:
+ sprintf(sc->sc_vendor, "SUN");
+ break;
+ default:
+ if (bootverbose) {
+ device_printf(self, "(New OHCI DeviceId=0x%08x)\n",
+ pci_get_devid(self));
+ }
+ sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self));
+ }
+
+ /* sc->sc_bus.usbrev; set by ohci_init() */
+
+#if (__FreeBSD_version >= 700031)
+ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (void *)(void *)ohci_interrupt, sc, &sc->sc_intr_hdl);
+#else
+ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ (void *)(void *)ohci_interrupt, sc, &sc->sc_intr_hdl);
+#endif
+ if (err) {
+ device_printf(self, "Could not setup irq, %d\n", err);
+ sc->sc_intr_hdl = NULL;
+ goto error;
+ }
+ err = ohci_init(sc);
+ if (!err) {
+ err = device_probe_and_attach(sc->sc_bus.bdev);
+ }
+ if (err) {
+ device_printf(self, "USB init failed\n");
+ goto error;
+ }
+ return (0);
+
+error:
+ ohci_pci_detach(self);
+ return (ENXIO);
+}
+
+static int
+ohci_pci_detach(device_t self)
+{
+ ohci_softc_t *sc = device_get_softc(self);
+ device_t bdev;
+
+ 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_all_children(self);
+
+ pci_disable_busmaster(self);
+
+ if (sc->sc_irq_res && sc->sc_intr_hdl) {
+ /*
+ * only call ohci_detach() after ohci_init()
+ */
+ ohci_detach(sc);
+
+ int 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, PCI_CBMEM,
+ sc->sc_io_res);
+ sc->sc_io_res = NULL;
+ }
+ usb2_bus_mem_free_all(&sc->sc_bus, &ohci_iterate_hw_softc);
+
+ return (0);
+}
+
+static driver_t ohci_driver =
+{
+ .name = "ohci",
+ .methods = (device_method_t[]){
+ /* device interface */
+ DEVMETHOD(device_probe, ohci_pci_probe),
+ DEVMETHOD(device_attach, ohci_pci_attach),
+ DEVMETHOD(device_detach, ohci_pci_detach),
+ DEVMETHOD(device_suspend, ohci_pci_suspend),
+ DEVMETHOD(device_resume, ohci_pci_resume),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+
+ /* bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+
+ {0, 0}
+ },
+ .size = sizeof(struct ohci_softc),
+};
+
+static devclass_t ohci_devclass;
+
+DRIVER_MODULE(ohci, pci, ohci_driver, ohci_devclass, 0, 0);
+DRIVER_MODULE(ohci, cardbus, ohci_driver, ohci_devclass, 0, 0);
+MODULE_DEPEND(ohci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/uhci.c b/sys/dev/usb/controller/uhci.c
new file mode 100644
index 0000000..40ae82a
--- /dev/null
+++ b/sys/dev/usb/controller/uhci.c
@@ -0,0 +1,3381 @@
+/*-
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
+ * Copyright (c) 1998 Lennart Augustsson. 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$");
+
+/*
+ * USB Universal Host Controller driver.
+ * Handles e.g. PIIX3 and PIIX4.
+ *
+ * UHCI spec: http://developer.intel.com/design/USB/UHCI11D.htm
+ * USB spec: http://www.usb.org/developers/docs/usbspec.zip
+ * PIIXn spec: ftp://download.intel.com/design/intarch/datashts/29055002.pdf
+ * ftp://download.intel.com/design/intarch/datashts/29056201.pdf
+ */
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_error.h>
+#include <dev/usb/usb_defs.h>
+
+#define USB_DEBUG_VAR uhcidebug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/uhci.h>
+
+#define alt_next next
+#define UHCI_BUS2SC(bus) ((uhci_softc_t *)(((uint8_t *)(bus)) - \
+ USB_P2U(&(((uhci_softc_t *)0)->sc_bus))))
+
+#if USB_DEBUG
+static int uhcidebug = 0;
+static int uhcinoloop = 0;
+
+SYSCTL_NODE(_hw_usb2, OID_AUTO, uhci, CTLFLAG_RW, 0, "USB uhci");
+SYSCTL_INT(_hw_usb2_uhci, OID_AUTO, debug, CTLFLAG_RW,
+ &uhcidebug, 0, "uhci debug level");
+SYSCTL_INT(_hw_usb2_uhci, OID_AUTO, loop, CTLFLAG_RW,
+ &uhcinoloop, 0, "uhci noloop");
+static void uhci_dumpregs(uhci_softc_t *sc);
+static void uhci_dump_tds(uhci_td_t *td);
+
+#endif
+
+#define UBARR(sc) bus_space_barrier((sc)->sc_io_tag, (sc)->sc_io_hdl, 0, (sc)->sc_io_size, \
+ BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE)
+#define UWRITE1(sc, r, x) \
+ do { UBARR(sc); bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \
+ } while (/*CONSTCOND*/0)
+#define UWRITE2(sc, r, x) \
+ do { UBARR(sc); bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \
+ } while (/*CONSTCOND*/0)
+#define UWRITE4(sc, r, x) \
+ do { UBARR(sc); bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \
+ } while (/*CONSTCOND*/0)
+#define UREAD1(sc, r) (UBARR(sc), bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r)))
+#define UREAD2(sc, r) (UBARR(sc), bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r)))
+#define UREAD4(sc, r) (UBARR(sc), bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r)))
+
+#define UHCICMD(sc, cmd) UWRITE2(sc, UHCI_CMD, cmd)
+#define UHCISTS(sc) UREAD2(sc, UHCI_STS)
+
+#define UHCI_RESET_TIMEOUT 100 /* ms, reset timeout */
+
+#define UHCI_INTR_ENDPT 1
+
+struct uhci_mem_layout {
+
+ struct usb2_page_search buf_res;
+ struct usb2_page_search fix_res;
+
+ struct usb2_page_cache *buf_pc;
+ struct usb2_page_cache *fix_pc;
+
+ uint32_t buf_offset;
+
+ uint16_t max_frame_size;
+};
+
+struct uhci_std_temp {
+
+ struct uhci_mem_layout ml;
+ uhci_td_t *td;
+ uhci_td_t *td_next;
+ uint32_t average;
+ uint32_t td_status;
+ uint32_t td_token;
+ uint32_t len;
+ uint16_t max_frame_size;
+ uint8_t shortpkt;
+ uint8_t setup_alt_next;
+ uint8_t short_frames_ok;
+};
+
+extern struct usb2_bus_methods uhci_bus_methods;
+extern struct usb2_pipe_methods uhci_device_bulk_methods;
+extern struct usb2_pipe_methods uhci_device_ctrl_methods;
+extern struct usb2_pipe_methods uhci_device_intr_methods;
+extern struct usb2_pipe_methods uhci_device_isoc_methods;
+extern struct usb2_pipe_methods uhci_root_ctrl_methods;
+extern struct usb2_pipe_methods uhci_root_intr_methods;
+
+static void uhci_root_ctrl_poll(struct uhci_softc *);
+static void uhci_do_poll(struct usb2_bus *);
+static void uhci_device_done(struct usb2_xfer *, usb2_error_t);
+static void uhci_transfer_intr_enqueue(struct usb2_xfer *);
+static void uhci_root_intr_check(void *);
+static void uhci_timeout(void *);
+static uint8_t uhci_check_transfer(struct usb2_xfer *);
+
+void
+uhci_iterate_hw_softc(struct usb2_bus *bus, usb2_bus_mem_sub_cb_t *cb)
+{
+ struct uhci_softc *sc = UHCI_BUS2SC(bus);
+ uint32_t i;
+
+ cb(bus, &sc->sc_hw.pframes_pc, &sc->sc_hw.pframes_pg,
+ sizeof(uint32_t) * UHCI_FRAMELIST_COUNT, UHCI_FRAMELIST_ALIGN);
+
+ cb(bus, &sc->sc_hw.ls_ctl_start_pc, &sc->sc_hw.ls_ctl_start_pg,
+ sizeof(uhci_qh_t), UHCI_QH_ALIGN);
+
+ cb(bus, &sc->sc_hw.fs_ctl_start_pc, &sc->sc_hw.fs_ctl_start_pg,
+ sizeof(uhci_qh_t), UHCI_QH_ALIGN);
+
+ cb(bus, &sc->sc_hw.bulk_start_pc, &sc->sc_hw.bulk_start_pg,
+ sizeof(uhci_qh_t), UHCI_QH_ALIGN);
+
+ cb(bus, &sc->sc_hw.last_qh_pc, &sc->sc_hw.last_qh_pg,
+ sizeof(uhci_qh_t), UHCI_QH_ALIGN);
+
+ cb(bus, &sc->sc_hw.last_td_pc, &sc->sc_hw.last_td_pg,
+ sizeof(uhci_td_t), UHCI_TD_ALIGN);
+
+ for (i = 0; i != UHCI_VFRAMELIST_COUNT; i++) {
+ cb(bus, sc->sc_hw.isoc_start_pc + i,
+ sc->sc_hw.isoc_start_pg + i,
+ sizeof(uhci_td_t), UHCI_TD_ALIGN);
+ }
+
+ for (i = 0; i != UHCI_IFRAMELIST_COUNT; i++) {
+ cb(bus, sc->sc_hw.intr_start_pc + i,
+ sc->sc_hw.intr_start_pg + i,
+ sizeof(uhci_qh_t), UHCI_QH_ALIGN);
+ }
+}
+
+static void
+uhci_mem_layout_init(struct uhci_mem_layout *ml, struct usb2_xfer *xfer)
+{
+ ml->buf_pc = xfer->frbuffers + 0;
+ ml->fix_pc = xfer->buf_fixup;
+
+ ml->buf_offset = 0;
+
+ ml->max_frame_size = xfer->max_frame_size;
+}
+
+static void
+uhci_mem_layout_fixup(struct uhci_mem_layout *ml, struct uhci_td *td)
+{
+ usb2_get_page(ml->buf_pc, ml->buf_offset, &ml->buf_res);
+
+ if (ml->buf_res.length < td->len) {
+
+ /* need to do a fixup */
+
+ usb2_get_page(ml->fix_pc, 0, &ml->fix_res);
+
+ td->td_buffer = htole32(ml->fix_res.physaddr);
+
+ /*
+ * The UHCI driver cannot handle
+ * page crossings, so a fixup is
+ * needed:
+ *
+ * +----+----+ - - -
+ * | YYY|Y |
+ * +----+----+ - - -
+ * \ \
+ * \ \
+ * +----+
+ * |YYYY| (fixup)
+ * +----+
+ */
+
+ if ((td->td_token & htole32(UHCI_TD_PID)) ==
+ htole32(UHCI_TD_PID_IN)) {
+ td->fix_pc = ml->fix_pc;
+ usb2_pc_cpu_invalidate(ml->fix_pc);
+
+ } else {
+ td->fix_pc = NULL;
+
+ /* copy data to fixup location */
+
+ usb2_copy_out(ml->buf_pc, ml->buf_offset,
+ ml->fix_res.buffer, td->len);
+
+ usb2_pc_cpu_flush(ml->fix_pc);
+ }
+
+ /* prepare next fixup */
+
+ ml->fix_pc++;
+
+ } else {
+
+ td->td_buffer = htole32(ml->buf_res.physaddr);
+ td->fix_pc = NULL;
+ }
+
+ /* prepare next data location */
+
+ ml->buf_offset += td->len;
+}
+
+void
+uhci_reset(uhci_softc_t *sc)
+{
+ struct usb2_page_search buf_res;
+ uint16_t n;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ DPRINTF("resetting the HC\n");
+
+ /* disable interrupts */
+
+ UWRITE2(sc, UHCI_INTR, 0);
+
+ /* global reset */
+
+ UHCICMD(sc, UHCI_CMD_GRESET);
+
+ /* wait */
+
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(USB_BUS_RESET_DELAY));
+
+ /* terminate all transfers */
+
+ UHCICMD(sc, UHCI_CMD_HCRESET);
+
+ /* the reset bit goes low when the controller is done */
+
+ n = UHCI_RESET_TIMEOUT;
+ while (n--) {
+ /* wait one millisecond */
+
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+
+ if (!(UREAD2(sc, UHCI_CMD) & UHCI_CMD_HCRESET)) {
+ goto done_1;
+ }
+ }
+
+ device_printf(sc->sc_bus.bdev,
+ "controller did not reset\n");
+
+done_1:
+
+ n = 10;
+ while (n--) {
+ /* wait one millisecond */
+
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+
+ /* check if HC is stopped */
+ if (UREAD2(sc, UHCI_STS) & UHCI_STS_HCH) {
+ goto done_2;
+ }
+ }
+
+ device_printf(sc->sc_bus.bdev,
+ "controller did not stop\n");
+
+done_2:
+
+ /* reload the configuration */
+ usb2_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res);
+ UWRITE4(sc, UHCI_FLBASEADDR, buf_res.physaddr);
+ UWRITE2(sc, UHCI_FRNUM, sc->sc_saved_frnum);
+ UWRITE1(sc, UHCI_SOF, sc->sc_saved_sof);
+}
+
+static void
+uhci_start(uhci_softc_t *sc)
+{
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ DPRINTFN(2, "enabling\n");
+
+ /* enable interrupts */
+
+ UWRITE2(sc, UHCI_INTR,
+ (UHCI_INTR_TOCRCIE |
+ UHCI_INTR_RIE |
+ UHCI_INTR_IOCE |
+ UHCI_INTR_SPIE));
+
+ /*
+ * assume 64 byte packets at frame end and start HC controller
+ */
+
+ UHCICMD(sc, (UHCI_CMD_MAXP | UHCI_CMD_RS));
+
+ uint8_t n = 10;
+
+ while (n--) {
+ /* wait one millisecond */
+
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+
+ /* check that controller has started */
+
+ if (!(UREAD2(sc, UHCI_STS) & UHCI_STS_HCH)) {
+ goto done;
+ }
+ }
+
+ device_printf(sc->sc_bus.bdev,
+ "cannot start HC controller\n");
+
+done:
+ return;
+}
+
+static struct uhci_qh *
+uhci_init_qh(struct usb2_page_cache *pc)
+{
+ struct usb2_page_search buf_res;
+ struct uhci_qh *qh;
+
+ usb2_get_page(pc, 0, &buf_res);
+
+ qh = buf_res.buffer;
+
+ qh->qh_self =
+ htole32(buf_res.physaddr) |
+ htole32(UHCI_PTR_QH);
+
+ qh->page_cache = pc;
+
+ return (qh);
+}
+
+static struct uhci_td *
+uhci_init_td(struct usb2_page_cache *pc)
+{
+ struct usb2_page_search buf_res;
+ struct uhci_td *td;
+
+ usb2_get_page(pc, 0, &buf_res);
+
+ td = buf_res.buffer;
+
+ td->td_self =
+ htole32(buf_res.physaddr) |
+ htole32(UHCI_PTR_TD);
+
+ td->page_cache = pc;
+
+ return (td);
+}
+
+usb2_error_t
+uhci_init(uhci_softc_t *sc)
+{
+ uint16_t bit;
+ uint16_t x;
+ uint16_t y;
+
+ DPRINTF("start\n");
+
+#if USB_DEBUG
+ if (uhcidebug > 2) {
+ uhci_dumpregs(sc);
+ }
+#endif
+
+ sc->sc_saved_sof = 0x40; /* default value */
+ sc->sc_saved_frnum = 0; /* default frame number */
+
+ /*
+ * Setup QH's
+ */
+ sc->sc_ls_ctl_p_last =
+ uhci_init_qh(&sc->sc_hw.ls_ctl_start_pc);
+
+ sc->sc_fs_ctl_p_last =
+ uhci_init_qh(&sc->sc_hw.fs_ctl_start_pc);
+
+ sc->sc_bulk_p_last =
+ uhci_init_qh(&sc->sc_hw.bulk_start_pc);
+#if 0
+ sc->sc_reclaim_qh_p =
+ sc->sc_fs_ctl_p_last;
+#else
+ /* setup reclaim looping point */
+ sc->sc_reclaim_qh_p =
+ sc->sc_bulk_p_last;
+#endif
+
+ sc->sc_last_qh_p =
+ uhci_init_qh(&sc->sc_hw.last_qh_pc);
+
+ sc->sc_last_td_p =
+ uhci_init_td(&sc->sc_hw.last_td_pc);
+
+ for (x = 0; x != UHCI_VFRAMELIST_COUNT; x++) {
+ sc->sc_isoc_p_last[x] =
+ uhci_init_td(sc->sc_hw.isoc_start_pc + x);
+ }
+
+ for (x = 0; x != UHCI_IFRAMELIST_COUNT; x++) {
+ sc->sc_intr_p_last[x] =
+ uhci_init_qh(sc->sc_hw.intr_start_pc + x);
+ }
+
+ /*
+ * the QHs are arranged to give poll intervals that are
+ * powers of 2 times 1ms
+ */
+ bit = UHCI_IFRAMELIST_COUNT / 2;
+ while (bit) {
+ x = bit;
+ while (x & bit) {
+ uhci_qh_t *qh_x;
+ uhci_qh_t *qh_y;
+
+ y = (x ^ bit) | (bit / 2);
+
+ /*
+ * the next QH has half the poll interval
+ */
+ qh_x = sc->sc_intr_p_last[x];
+ qh_y = sc->sc_intr_p_last[y];
+
+ qh_x->h_next = NULL;
+ qh_x->qh_h_next = qh_y->qh_self;
+ qh_x->e_next = NULL;
+ qh_x->qh_e_next = htole32(UHCI_PTR_T);
+ x++;
+ }
+ bit >>= 1;
+ }
+
+ if (1) {
+ uhci_qh_t *qh_ls;
+ uhci_qh_t *qh_intr;
+
+ qh_ls = sc->sc_ls_ctl_p_last;
+ qh_intr = sc->sc_intr_p_last[0];
+
+ /* start QH for interrupt traffic */
+ qh_intr->h_next = qh_ls;
+ qh_intr->qh_h_next = qh_ls->qh_self;
+ qh_intr->e_next = 0;
+ qh_intr->qh_e_next = htole32(UHCI_PTR_T);
+ }
+ for (x = 0; x != UHCI_VFRAMELIST_COUNT; x++) {
+
+ uhci_td_t *td_x;
+ uhci_qh_t *qh_intr;
+
+ td_x = sc->sc_isoc_p_last[x];
+ qh_intr = sc->sc_intr_p_last[x | (UHCI_IFRAMELIST_COUNT / 2)];
+
+ /* start TD for isochronous traffic */
+ td_x->next = NULL;
+ td_x->td_next = qh_intr->qh_self;
+ td_x->td_status = htole32(UHCI_TD_IOS);
+ td_x->td_token = htole32(0);
+ td_x->td_buffer = htole32(0);
+ }
+
+ if (1) {
+ uhci_qh_t *qh_ls;
+ uhci_qh_t *qh_fs;
+
+ qh_ls = sc->sc_ls_ctl_p_last;
+ qh_fs = sc->sc_fs_ctl_p_last;
+
+ /* start QH where low speed control traffic will be queued */
+ qh_ls->h_next = qh_fs;
+ qh_ls->qh_h_next = qh_fs->qh_self;
+ qh_ls->e_next = 0;
+ qh_ls->qh_e_next = htole32(UHCI_PTR_T);
+ }
+ if (1) {
+ uhci_qh_t *qh_ctl;
+ uhci_qh_t *qh_blk;
+ uhci_qh_t *qh_lst;
+ uhci_td_t *td_lst;
+
+ qh_ctl = sc->sc_fs_ctl_p_last;
+ qh_blk = sc->sc_bulk_p_last;
+
+ /* start QH where full speed control traffic will be queued */
+ qh_ctl->h_next = qh_blk;
+ qh_ctl->qh_h_next = qh_blk->qh_self;
+ qh_ctl->e_next = 0;
+ qh_ctl->qh_e_next = htole32(UHCI_PTR_T);
+
+ qh_lst = sc->sc_last_qh_p;
+
+ /* start QH where bulk traffic will be queued */
+ qh_blk->h_next = qh_lst;
+ qh_blk->qh_h_next = qh_lst->qh_self;
+ qh_blk->e_next = 0;
+ qh_blk->qh_e_next = htole32(UHCI_PTR_T);
+
+ td_lst = sc->sc_last_td_p;
+
+ /* end QH which is used for looping the QHs */
+ qh_lst->h_next = 0;
+ qh_lst->qh_h_next = htole32(UHCI_PTR_T); /* end of QH chain */
+ qh_lst->e_next = td_lst;
+ qh_lst->qh_e_next = td_lst->td_self;
+
+ /*
+ * end TD which hangs from the last QH, to avoid a bug in the PIIX
+ * that makes it run berserk otherwise
+ */
+ td_lst->next = 0;
+ td_lst->td_next = htole32(UHCI_PTR_T);
+ td_lst->td_status = htole32(0); /* inactive */
+ td_lst->td_token = htole32(0);
+ td_lst->td_buffer = htole32(0);
+ }
+ if (1) {
+ struct usb2_page_search buf_res;
+ uint32_t *pframes;
+
+ usb2_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res);
+
+ pframes = buf_res.buffer;
+
+
+ /*
+ * Setup UHCI framelist
+ *
+ * Execution order:
+ *
+ * pframes -> full speed isochronous -> interrupt QH's -> low
+ * speed control -> full speed control -> bulk transfers
+ *
+ */
+
+ for (x = 0; x != UHCI_FRAMELIST_COUNT; x++) {
+ pframes[x] =
+ sc->sc_isoc_p_last[x % UHCI_VFRAMELIST_COUNT]->td_self;
+ }
+ }
+ /* flush all cache into memory */
+
+ usb2_bus_mem_flush_all(&sc->sc_bus, &uhci_iterate_hw_softc);
+
+ /* set up the bus struct */
+ sc->sc_bus.methods = &uhci_bus_methods;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ /* reset the controller */
+ uhci_reset(sc);
+
+ /* start the controller */
+ uhci_start(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* catch lost interrupts */
+ uhci_do_poll(&sc->sc_bus);
+
+ return (0);
+}
+
+/* NOTE: suspend/resume is called from
+ * interrupt context and cannot sleep!
+ */
+
+void
+uhci_suspend(uhci_softc_t *sc)
+{
+ USB_BUS_LOCK(&sc->sc_bus);
+
+#if USB_DEBUG
+ if (uhcidebug > 2) {
+ uhci_dumpregs(sc);
+ }
+#endif
+ /* save some state if BIOS doesn't */
+
+ sc->sc_saved_frnum = UREAD2(sc, UHCI_FRNUM);
+ sc->sc_saved_sof = UREAD1(sc, UHCI_SOF);
+
+ /* stop the controller */
+
+ uhci_reset(sc);
+
+ /* enter global suspend */
+
+ UHCICMD(sc, UHCI_CMD_EGSM);
+
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(USB_RESUME_WAIT));
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+void
+uhci_resume(uhci_softc_t *sc)
+{
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* reset the controller */
+
+ uhci_reset(sc);
+
+ /* force global resume */
+
+ UHCICMD(sc, UHCI_CMD_FGR);
+
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(USB_RESUME_DELAY));
+
+ /* and start traffic again */
+
+ uhci_start(sc);
+
+#if USB_DEBUG
+ if (uhcidebug > 2) {
+ uhci_dumpregs(sc);
+ }
+#endif
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* catch lost interrupts */
+ uhci_do_poll(&sc->sc_bus);
+}
+
+#if USB_DEBUG
+static void
+uhci_dumpregs(uhci_softc_t *sc)
+{
+ DPRINTFN(0, "%s regs: cmd=%04x, sts=%04x, intr=%04x, frnum=%04x, "
+ "flbase=%08x, sof=%04x, portsc1=%04x, portsc2=%04x\n",
+ device_get_nameunit(sc->sc_bus.bdev),
+ UREAD2(sc, UHCI_CMD),
+ UREAD2(sc, UHCI_STS),
+ UREAD2(sc, UHCI_INTR),
+ UREAD2(sc, UHCI_FRNUM),
+ UREAD4(sc, UHCI_FLBASEADDR),
+ UREAD1(sc, UHCI_SOF),
+ UREAD2(sc, UHCI_PORTSC1),
+ UREAD2(sc, UHCI_PORTSC2));
+}
+
+static uint8_t
+uhci_dump_td(uhci_td_t *p)
+{
+ uint32_t td_next;
+ uint32_t td_status;
+ uint32_t td_token;
+ uint8_t temp;
+
+ usb2_pc_cpu_invalidate(p->page_cache);
+
+ td_next = le32toh(p->td_next);
+ td_status = le32toh(p->td_status);
+ td_token = le32toh(p->td_token);
+
+ /*
+ * Check whether the link pointer in this TD marks the link pointer
+ * as end of queue:
+ */
+ temp = ((td_next & UHCI_PTR_T) || (td_next == 0));
+
+ printf("TD(%p) at 0x%08x = link=0x%08x status=0x%08x "
+ "token=0x%08x buffer=0x%08x\n",
+ p,
+ le32toh(p->td_self),
+ td_next,
+ td_status,
+ td_token,
+ le32toh(p->td_buffer));
+
+ printf("TD(%p) td_next=%s%s%s td_status=%s%s%s%s%s%s%s%s%s%s%s, errcnt=%d, actlen=%d pid=%02x,"
+ "addr=%d,endpt=%d,D=%d,maxlen=%d\n",
+ p,
+ (td_next & 1) ? "-T" : "",
+ (td_next & 2) ? "-Q" : "",
+ (td_next & 4) ? "-VF" : "",
+ (td_status & UHCI_TD_BITSTUFF) ? "-BITSTUFF" : "",
+ (td_status & UHCI_TD_CRCTO) ? "-CRCTO" : "",
+ (td_status & UHCI_TD_NAK) ? "-NAK" : "",
+ (td_status & UHCI_TD_BABBLE) ? "-BABBLE" : "",
+ (td_status & UHCI_TD_DBUFFER) ? "-DBUFFER" : "",
+ (td_status & UHCI_TD_STALLED) ? "-STALLED" : "",
+ (td_status & UHCI_TD_ACTIVE) ? "-ACTIVE" : "",
+ (td_status & UHCI_TD_IOC) ? "-IOC" : "",
+ (td_status & UHCI_TD_IOS) ? "-IOS" : "",
+ (td_status & UHCI_TD_LS) ? "-LS" : "",
+ (td_status & UHCI_TD_SPD) ? "-SPD" : "",
+ UHCI_TD_GET_ERRCNT(td_status),
+ UHCI_TD_GET_ACTLEN(td_status),
+ UHCI_TD_GET_PID(td_token),
+ UHCI_TD_GET_DEVADDR(td_token),
+ UHCI_TD_GET_ENDPT(td_token),
+ UHCI_TD_GET_DT(td_token),
+ UHCI_TD_GET_MAXLEN(td_token));
+
+ return (temp);
+}
+
+static uint8_t
+uhci_dump_qh(uhci_qh_t *sqh)
+{
+ uint8_t temp;
+ uint32_t qh_h_next;
+ uint32_t qh_e_next;
+
+ usb2_pc_cpu_invalidate(sqh->page_cache);
+
+ qh_h_next = le32toh(sqh->qh_h_next);
+ qh_e_next = le32toh(sqh->qh_e_next);
+
+ DPRINTFN(0, "QH(%p) at 0x%08x: h_next=0x%08x e_next=0x%08x\n", sqh,
+ le32toh(sqh->qh_self), qh_h_next, qh_e_next);
+
+ temp = ((((sqh->h_next != NULL) && !(qh_h_next & UHCI_PTR_T)) ? 1 : 0) |
+ (((sqh->e_next != NULL) && !(qh_e_next & UHCI_PTR_T)) ? 2 : 0));
+
+ return (temp);
+}
+
+static void
+uhci_dump_all(uhci_softc_t *sc)
+{
+ uhci_dumpregs(sc);
+ uhci_dump_qh(sc->sc_ls_ctl_p_last);
+ uhci_dump_qh(sc->sc_fs_ctl_p_last);
+ uhci_dump_qh(sc->sc_bulk_p_last);
+ uhci_dump_qh(sc->sc_last_qh_p);
+}
+
+static void
+uhci_dump_qhs(uhci_qh_t *sqh)
+{
+ uint8_t temp;
+
+ temp = uhci_dump_qh(sqh);
+
+ /*
+ * uhci_dump_qhs displays all the QHs and TDs from the given QH
+ * onwards Traverses sideways first, then down.
+ *
+ * QH1 QH2 No QH TD2.1 TD2.2 TD1.1 etc.
+ *
+ * TD2.x being the TDs queued at QH2 and QH1 being referenced from QH1.
+ */
+
+ if (temp & 1)
+ uhci_dump_qhs(sqh->h_next);
+ else
+ DPRINTF("No QH\n");
+
+ if (temp & 2)
+ uhci_dump_tds(sqh->e_next);
+ else
+ DPRINTF("No TD\n");
+}
+
+static void
+uhci_dump_tds(uhci_td_t *td)
+{
+ for (;
+ td != NULL;
+ td = td->obj_next) {
+ if (uhci_dump_td(td)) {
+ break;
+ }
+ }
+}
+
+#endif
+
+/*
+ * Let the last QH loop back to the full speed control transfer QH.
+ * This is what intel calls "bandwidth reclamation" and improves
+ * USB performance a lot for some devices.
+ * If we are already looping, just count it.
+ */
+static void
+uhci_add_loop(uhci_softc_t *sc)
+{
+ struct uhci_qh *qh_lst;
+ struct uhci_qh *qh_rec;
+
+#if USB_DEBUG
+ if (uhcinoloop) {
+ return;
+ }
+#endif
+ if (++(sc->sc_loops) == 1) {
+ DPRINTFN(6, "add\n");
+
+ qh_lst = sc->sc_last_qh_p;
+ qh_rec = sc->sc_reclaim_qh_p;
+
+ /* NOTE: we don't loop back the soft pointer */
+
+ qh_lst->qh_h_next = qh_rec->qh_self;
+ usb2_pc_cpu_flush(qh_lst->page_cache);
+ }
+}
+
+static void
+uhci_rem_loop(uhci_softc_t *sc)
+{
+ struct uhci_qh *qh_lst;
+
+#if USB_DEBUG
+ if (uhcinoloop) {
+ return;
+ }
+#endif
+ if (--(sc->sc_loops) == 0) {
+ DPRINTFN(6, "remove\n");
+
+ qh_lst = sc->sc_last_qh_p;
+ qh_lst->qh_h_next = htole32(UHCI_PTR_T);
+ usb2_pc_cpu_flush(qh_lst->page_cache);
+ }
+}
+
+static void
+uhci_transfer_intr_enqueue(struct usb2_xfer *xfer)
+{
+ /* check for early completion */
+ if (uhci_check_transfer(xfer)) {
+ return;
+ }
+ /* put transfer on interrupt queue */
+ usb2_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usb2_transfer_timeout_ms(xfer, &uhci_timeout, xfer->timeout);
+ }
+}
+
+#define UHCI_APPEND_TD(std,last) (last) = _uhci_append_td(std,last)
+static uhci_td_t *
+_uhci_append_td(uhci_td_t *std, uhci_td_t *last)
+{
+ DPRINTFN(11, "%p to %p\n", std, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ std->next = last->next;
+ std->td_next = last->td_next;
+
+ std->prev = last;
+
+ usb2_pc_cpu_flush(std->page_cache);
+
+ /*
+ * the last->next->prev is never followed: std->next->prev = std;
+ */
+ last->next = std;
+ last->td_next = std->td_self;
+
+ usb2_pc_cpu_flush(last->page_cache);
+
+ return (std);
+}
+
+#define UHCI_APPEND_QH(sqh,last) (last) = _uhci_append_qh(sqh,last)
+static uhci_qh_t *
+_uhci_append_qh(uhci_qh_t *sqh, uhci_qh_t *last)
+{
+ DPRINTFN(11, "%p to %p\n", sqh, last);
+
+ if (sqh->h_prev != NULL) {
+ /* should not happen */
+ DPRINTFN(0, "QH already linked!\n");
+ return (last);
+ }
+ /* (sc->sc_bus.mtx) must be locked */
+
+ sqh->h_next = last->h_next;
+ sqh->qh_h_next = last->qh_h_next;
+
+ sqh->h_prev = last;
+
+ usb2_pc_cpu_flush(sqh->page_cache);
+
+ /*
+ * The "last->h_next->h_prev" is never followed:
+ *
+ * "sqh->h_next->h_prev" = sqh;
+ */
+
+ last->h_next = sqh;
+ last->qh_h_next = sqh->qh_self;
+
+ usb2_pc_cpu_flush(last->page_cache);
+
+ return (sqh);
+}
+
+/**/
+
+#define UHCI_REMOVE_TD(std,last) (last) = _uhci_remove_td(std,last)
+static uhci_td_t *
+_uhci_remove_td(uhci_td_t *std, uhci_td_t *last)
+{
+ DPRINTFN(11, "%p from %p\n", std, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ std->prev->next = std->next;
+ std->prev->td_next = std->td_next;
+
+ usb2_pc_cpu_flush(std->prev->page_cache);
+
+ if (std->next) {
+ std->next->prev = std->prev;
+ usb2_pc_cpu_flush(std->next->page_cache);
+ }
+ return ((last == std) ? std->prev : last);
+}
+
+#define UHCI_REMOVE_QH(sqh,last) (last) = _uhci_remove_qh(sqh,last)
+static uhci_qh_t *
+_uhci_remove_qh(uhci_qh_t *sqh, uhci_qh_t *last)
+{
+ DPRINTFN(11, "%p from %p\n", sqh, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ /* only remove if not removed from a queue */
+ if (sqh->h_prev) {
+
+ sqh->h_prev->h_next = sqh->h_next;
+ sqh->h_prev->qh_h_next = sqh->qh_h_next;
+
+ usb2_pc_cpu_flush(sqh->h_prev->page_cache);
+
+ if (sqh->h_next) {
+ sqh->h_next->h_prev = sqh->h_prev;
+ usb2_pc_cpu_flush(sqh->h_next->page_cache);
+ }
+ last = ((last == sqh) ? sqh->h_prev : last);
+
+ sqh->h_prev = 0;
+
+ usb2_pc_cpu_flush(sqh->page_cache);
+ }
+ return (last);
+}
+
+static void
+uhci_isoc_done(uhci_softc_t *sc, struct usb2_xfer *xfer)
+{
+ struct usb2_page_search res;
+ uint32_t nframes = xfer->nframes;
+ uint32_t status;
+ uint32_t offset = 0;
+ uint32_t *plen = xfer->frlengths;
+ uint16_t len = 0;
+ uhci_td_t *td = xfer->td_transfer_first;
+ uhci_td_t **pp_last = &sc->sc_isoc_p_last[xfer->qh_pos];
+
+ DPRINTFN(13, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->pipe);
+
+ /* sync any DMA memory before doing fixups */
+
+ usb2_bdma_post_sync(xfer);
+
+ while (nframes--) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ if (pp_last >= &sc->sc_isoc_p_last[UHCI_VFRAMELIST_COUNT]) {
+ pp_last = &sc->sc_isoc_p_last[0];
+ }
+#if USB_DEBUG
+ if (uhcidebug > 5) {
+ DPRINTF("isoc TD\n");
+ uhci_dump_td(td);
+ }
+#endif
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status = le32toh(td->td_status);
+
+ len = UHCI_TD_GET_ACTLEN(status);
+
+ if (len > *plen) {
+ len = *plen;
+ }
+ if (td->fix_pc) {
+
+ usb2_get_page(td->fix_pc, 0, &res);
+
+ /* copy data from fixup location to real location */
+
+ usb2_pc_cpu_invalidate(td->fix_pc);
+
+ usb2_copy_in(xfer->frbuffers, offset,
+ res.buffer, len);
+ }
+ offset += *plen;
+
+ *plen = len;
+
+ /* remove TD from schedule */
+ UHCI_REMOVE_TD(td, *pp_last);
+
+ pp_last++;
+ plen++;
+ td = td->obj_next;
+ }
+
+ xfer->aframes = xfer->nframes;
+}
+
+static usb2_error_t
+uhci_non_isoc_done_sub(struct usb2_xfer *xfer)
+{
+ struct usb2_page_search res;
+ uhci_td_t *td;
+ uhci_td_t *td_alt_next;
+ uint32_t status;
+ uint32_t token;
+ uint16_t len;
+
+ td = xfer->td_transfer_cache;
+ td_alt_next = td->alt_next;
+
+ if (xfer->aframes != xfer->nframes) {
+ xfer->frlengths[xfer->aframes] = 0;
+ }
+ while (1) {
+
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status = le32toh(td->td_status);
+ token = le32toh(td->td_token);
+
+ /*
+ * Verify the status and add
+ * up the actual length:
+ */
+
+ len = UHCI_TD_GET_ACTLEN(status);
+ if (len > td->len) {
+ /* should not happen */
+ DPRINTF("Invalid status length, "
+ "0x%04x/0x%04x bytes\n", len, td->len);
+ status |= UHCI_TD_STALLED;
+
+ } else if ((xfer->aframes != xfer->nframes) && (len > 0)) {
+
+ if (td->fix_pc) {
+
+ usb2_get_page(td->fix_pc, 0, &res);
+
+ /*
+ * copy data from fixup location to real
+ * location
+ */
+
+ usb2_pc_cpu_invalidate(td->fix_pc);
+
+ usb2_copy_in(xfer->frbuffers + xfer->aframes,
+ xfer->frlengths[xfer->aframes], res.buffer, len);
+ }
+ /* update actual length */
+
+ xfer->frlengths[xfer->aframes] += len;
+ }
+ /* Check for last transfer */
+ if (((void *)td) == xfer->td_transfer_last) {
+ td = NULL;
+ break;
+ }
+ if (status & UHCI_TD_STALLED) {
+ /* the transfer is finished */
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (len != td->len) {
+ if (xfer->flags_int.short_frames_ok) {
+ /* follow alt next */
+ td = td->alt_next;
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ break;
+ }
+ td = td->obj_next;
+
+ if (td->alt_next != td_alt_next) {
+ /* this USB frame is complete */
+ break;
+ }
+ }
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ /* update data toggle */
+
+ xfer->pipe->toggle_next = (token & UHCI_TD_SET_DT(1)) ? 0 : 1;
+
+#if USB_DEBUG
+ if (status & UHCI_TD_ERROR) {
+ DPRINTFN(11, "error, addr=%d, endpt=0x%02x, frame=0x%02x "
+ "status=%s%s%s%s%s%s%s%s%s%s%s\n",
+ xfer->address, xfer->endpoint, xfer->aframes,
+ (status & UHCI_TD_BITSTUFF) ? "[BITSTUFF]" : "",
+ (status & UHCI_TD_CRCTO) ? "[CRCTO]" : "",
+ (status & UHCI_TD_NAK) ? "[NAK]" : "",
+ (status & UHCI_TD_BABBLE) ? "[BABBLE]" : "",
+ (status & UHCI_TD_DBUFFER) ? "[DBUFFER]" : "",
+ (status & UHCI_TD_STALLED) ? "[STALLED]" : "",
+ (status & UHCI_TD_ACTIVE) ? "[ACTIVE]" : "[NOT_ACTIVE]",
+ (status & UHCI_TD_IOC) ? "[IOC]" : "",
+ (status & UHCI_TD_IOS) ? "[IOS]" : "",
+ (status & UHCI_TD_LS) ? "[LS]" : "",
+ (status & UHCI_TD_SPD) ? "[SPD]" : "");
+ }
+#endif
+ return (status & UHCI_TD_STALLED) ?
+ USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION;
+}
+
+static void
+uhci_non_isoc_done(struct usb2_xfer *xfer)
+{
+ usb2_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->pipe);
+
+#if USB_DEBUG
+ if (uhcidebug > 10) {
+ uhci_dump_tds(xfer->td_transfer_first);
+ }
+#endif
+
+ /* sync any DMA memory before doing fixups */
+
+ usb2_bdma_post_sync(xfer);
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+
+ err = uhci_non_isoc_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+
+ err = uhci_non_isoc_done_sub(xfer);
+ xfer->aframes++;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ err = uhci_non_isoc_done_sub(xfer);
+ }
+done:
+ uhci_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * uhci_check_transfer_sub
+ *
+ * The main purpose of this function is to update the data-toggle
+ * in case it is wrong.
+ *------------------------------------------------------------------------*/
+static void
+uhci_check_transfer_sub(struct usb2_xfer *xfer)
+{
+ uhci_qh_t *qh;
+ uhci_td_t *td;
+ uhci_td_t *td_alt_next;
+
+ uint32_t td_token;
+ uint32_t td_self;
+
+ td = xfer->td_transfer_cache;
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ td_token = td->obj_next->td_token;
+ td = td->alt_next;
+ xfer->td_transfer_cache = td;
+ td_self = td->td_self;
+ td_alt_next = td->alt_next;
+
+ if ((td->td_token ^ td_token) & htole32(UHCI_TD_SET_DT(1))) {
+
+ /*
+ * The data toggle is wrong and
+ * we need to switch it !
+ */
+
+ while (1) {
+
+ td->td_token ^= htole32(UHCI_TD_SET_DT(1));
+ usb2_pc_cpu_flush(td->page_cache);
+
+ if (td == xfer->td_transfer_last) {
+ /* last transfer */
+ break;
+ }
+ td = td->obj_next;
+
+ if (td->alt_next != td_alt_next) {
+ /* next frame */
+ break;
+ }
+ }
+ }
+ /* update the QH */
+ qh->qh_e_next = td_self;
+ usb2_pc_cpu_flush(qh->page_cache);
+
+ DPRINTFN(13, "xfer=%p following alt next\n", xfer);
+}
+
+/*------------------------------------------------------------------------*
+ * uhci_check_transfer
+ *
+ * Return values:
+ * 0: USB transfer is not finished
+ * Else: USB transfer is finished
+ *------------------------------------------------------------------------*/
+static uint8_t
+uhci_check_transfer(struct usb2_xfer *xfer)
+{
+ uint32_t status;
+ uint32_t token;
+ uhci_td_t *td;
+
+ DPRINTFN(16, "xfer=%p checking transfer\n", xfer);
+
+ if (xfer->pipe->methods == &uhci_device_isoc_methods) {
+ /* isochronous transfer */
+
+ td = xfer->td_transfer_last;
+
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status = le32toh(td->td_status);
+
+ /* check also if the first is complete */
+
+ td = xfer->td_transfer_first;
+
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status |= le32toh(td->td_status);
+
+ if (!(status & UHCI_TD_ACTIVE)) {
+ uhci_device_done(xfer, USB_ERR_NORMAL_COMPLETION);
+ goto transferred;
+ }
+ } else {
+ /* non-isochronous transfer */
+
+ /*
+ * check whether there is an error somewhere
+ * in the middle, or whether there was a short
+ * packet (SPD and not ACTIVE)
+ */
+ td = xfer->td_transfer_cache;
+
+ while (1) {
+ usb2_pc_cpu_invalidate(td->page_cache);
+ status = le32toh(td->td_status);
+ token = le32toh(td->td_token);
+
+ /*
+ * if there is an active TD the transfer isn't done
+ */
+ if (status & UHCI_TD_ACTIVE) {
+ /* update cache */
+ xfer->td_transfer_cache = td;
+ goto done;
+ }
+ /*
+ * last transfer descriptor makes the transfer done
+ */
+ if (((void *)td) == xfer->td_transfer_last) {
+ break;
+ }
+ /*
+ * any kind of error makes the transfer done
+ */
+ if (status & UHCI_TD_STALLED) {
+ break;
+ }
+ /*
+ * check if we reached the last packet
+ * or if there is a short packet:
+ */
+ if ((td->td_next == htole32(UHCI_PTR_T)) ||
+ (UHCI_TD_GET_ACTLEN(status) < td->len)) {
+
+ if (xfer->flags_int.short_frames_ok) {
+ /* follow alt next */
+ if (td->alt_next) {
+ /* update cache */
+ xfer->td_transfer_cache = td;
+ uhci_check_transfer_sub(xfer);
+ goto done;
+ }
+ }
+ /* transfer is done */
+ break;
+ }
+ td = td->obj_next;
+ }
+ uhci_non_isoc_done(xfer);
+ goto transferred;
+ }
+
+done:
+ DPRINTFN(13, "xfer=%p is still active\n", xfer);
+ return (0);
+
+transferred:
+ return (1);
+}
+
+static void
+uhci_interrupt_poll(uhci_softc_t *sc)
+{
+ struct usb2_xfer *xfer;
+
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ /*
+ * check if transfer is transferred
+ */
+ if (uhci_check_transfer(xfer)) {
+ /* queue has been modified */
+ goto repeat;
+ }
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * uhci_interrupt - UHCI interrupt handler
+ *
+ * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler,
+ * hence the interrupt handler will be setup before "sc->sc_bus.bdev"
+ * is present !
+ *------------------------------------------------------------------------*/
+void
+uhci_interrupt(uhci_softc_t *sc)
+{
+ uint32_t status;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ DPRINTFN(16, "real interrupt\n");
+
+#if USB_DEBUG
+ if (uhcidebug > 15) {
+ uhci_dumpregs(sc);
+ }
+#endif
+ status = UREAD2(sc, UHCI_STS) & UHCI_STS_ALLINTRS;
+ if (status == 0) {
+ /* the interrupt was not for us */
+ goto done;
+ }
+ if (status & (UHCI_STS_RD | UHCI_STS_HSE |
+ UHCI_STS_HCPE | UHCI_STS_HCH)) {
+
+ if (status & UHCI_STS_RD) {
+#if USB_DEBUG
+ printf("%s: resume detect\n",
+ __FUNCTION__);
+#endif
+ }
+ if (status & UHCI_STS_HSE) {
+ printf("%s: host system error\n",
+ __FUNCTION__);
+ }
+ if (status & UHCI_STS_HCPE) {
+ printf("%s: host controller process error\n",
+ __FUNCTION__);
+ }
+ if (status & UHCI_STS_HCH) {
+ /* no acknowledge needed */
+ DPRINTF("%s: host controller halted\n",
+ __FUNCTION__);
+#if USB_DEBUG
+ if (uhcidebug > 0) {
+ uhci_dump_all(sc);
+ }
+#endif
+ }
+ }
+ /* get acknowledge bits */
+ status &= (UHCI_STS_USBINT |
+ UHCI_STS_USBEI |
+ UHCI_STS_RD |
+ UHCI_STS_HSE |
+ UHCI_STS_HCPE);
+
+ if (status == 0) {
+ /* nothing to acknowledge */
+ goto done;
+ }
+ /* acknowledge interrupts */
+ UWRITE2(sc, UHCI_STS, status);
+
+ /* poll all the USB transfers */
+ uhci_interrupt_poll(sc);
+
+done:
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*
+ * called when a request does not complete
+ */
+static void
+uhci_timeout(void *arg)
+{
+ struct usb2_xfer *xfer = arg;
+
+ DPRINTF("xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ uhci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+uhci_do_poll(struct usb2_bus *bus)
+{
+ struct uhci_softc *sc = UHCI_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ uhci_interrupt_poll(sc);
+ uhci_root_ctrl_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+uhci_setup_standard_chain_sub(struct uhci_std_temp *temp)
+{
+ uhci_td_t *td;
+ uhci_td_t *td_next;
+ uhci_td_t *td_alt_next;
+ uint32_t average;
+ uint32_t len_old;
+ uint8_t shortpkt_old;
+ uint8_t precompute;
+
+ td_alt_next = NULL;
+ shortpkt_old = temp->shortpkt;
+ len_old = temp->len;
+ precompute = 1;
+
+ /* software is used to detect short incoming transfers */
+
+ if ((temp->td_token & htole32(UHCI_TD_PID)) == htole32(UHCI_TD_PID_IN)) {
+ temp->td_status |= htole32(UHCI_TD_SPD);
+ } else {
+ temp->td_status &= ~htole32(UHCI_TD_SPD);
+ }
+
+ temp->ml.buf_offset = 0;
+
+restart:
+
+ temp->td_token &= ~htole32(UHCI_TD_SET_MAXLEN(0));
+ temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(temp->average));
+
+ td = temp->td;
+ td_next = temp->td_next;
+
+ while (1) {
+
+ if (temp->len == 0) {
+
+ if (temp->shortpkt) {
+ break;
+ }
+ /* send a Zero Length Packet, ZLP, last */
+
+ temp->shortpkt = 1;
+ temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(0));
+ average = 0;
+
+ } else {
+
+ average = temp->average;
+
+ if (temp->len < average) {
+ temp->shortpkt = 1;
+ temp->td_token &= ~htole32(UHCI_TD_SET_MAXLEN(0));
+ temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(temp->len));
+ average = temp->len;
+ }
+ }
+
+ if (td_next == NULL) {
+ panic("%s: out of UHCI transfer descriptors!", __FUNCTION__);
+ }
+ /* get next TD */
+
+ td = td_next;
+ td_next = td->obj_next;
+
+ /* check if we are pre-computing */
+
+ if (precompute) {
+
+ /* update remaining length */
+
+ temp->len -= average;
+
+ continue;
+ }
+ /* fill out current TD */
+
+ td->td_status = temp->td_status;
+ td->td_token = temp->td_token;
+
+ /* update data toggle */
+
+ temp->td_token ^= htole32(UHCI_TD_SET_DT(1));
+
+ if (average == 0) {
+
+ td->len = 0;
+ td->td_buffer = 0;
+ td->fix_pc = NULL;
+
+ } else {
+
+ /* update remaining length */
+
+ temp->len -= average;
+
+ td->len = average;
+
+ /* fill out buffer pointer and do fixup, if any */
+
+ uhci_mem_layout_fixup(&temp->ml, td);
+ }
+
+ td->alt_next = td_alt_next;
+
+ if ((td_next == td_alt_next) && temp->setup_alt_next) {
+ /* we need to receive these frames one by one ! */
+ td->td_status |= htole32(UHCI_TD_IOC);
+ td->td_next = htole32(UHCI_PTR_T);
+ } else {
+ if (td_next) {
+ /* link the current TD with the next one */
+ td->td_next = td_next->td_self;
+ }
+ }
+
+ usb2_pc_cpu_flush(td->page_cache);
+ }
+
+ if (precompute) {
+ precompute = 0;
+
+ /* setup alt next pointer, if any */
+ if (temp->short_frames_ok) {
+ if (temp->setup_alt_next) {
+ td_alt_next = td_next;
+ }
+ } else {
+ /* we use this field internally */
+ td_alt_next = td_next;
+ }
+
+ /* restore */
+ temp->shortpkt = shortpkt_old;
+ temp->len = len_old;
+ goto restart;
+ }
+ temp->td = td;
+ temp->td_next = td_next;
+}
+
+static uhci_td_t *
+uhci_setup_standard_chain(struct usb2_xfer *xfer)
+{
+ struct uhci_std_temp temp;
+ uhci_td_t *td;
+ uint32_t x;
+
+ DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpoint),
+ xfer->sumlen, usb2_get_speed(xfer->xroot->udev));
+
+ temp.average = xfer->max_frame_size;
+ temp.max_frame_size = xfer->max_frame_size;
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ temp.td = NULL;
+ temp.td_next = td;
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+ temp.short_frames_ok = xfer->flags_int.short_frames_ok;
+
+ uhci_mem_layout_init(&temp.ml, xfer);
+
+ temp.td_status =
+ htole32(UHCI_TD_ZERO_ACTLEN(UHCI_TD_SET_ERRCNT(3) |
+ UHCI_TD_ACTIVE));
+
+ if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
+ temp.td_status |= htole32(UHCI_TD_LS);
+ }
+ temp.td_token =
+ htole32(UHCI_TD_SET_ENDPT(xfer->endpoint) |
+ UHCI_TD_SET_DEVADDR(xfer->address));
+
+ if (xfer->pipe->toggle_next) {
+ /* DATA1 is next */
+ temp.td_token |= htole32(UHCI_TD_SET_DT(1));
+ }
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+
+ if (xfer->flags_int.control_hdr) {
+
+ temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) |
+ UHCI_TD_SET_ENDPT(0xF));
+ temp.td_token |= htole32(UHCI_TD_PID_SETUP |
+ UHCI_TD_SET_DT(0));
+
+ temp.len = xfer->frlengths[0];
+ temp.ml.buf_pc = xfer->frbuffers + 0;
+ temp.shortpkt = temp.len ? 1 : 0;
+
+ uhci_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ while (x != xfer->nframes) {
+
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+ temp.ml.buf_pc = xfer->frbuffers + x;
+
+ x++;
+
+ if (x == xfer->nframes) {
+ temp.setup_alt_next = 0;
+ }
+ /*
+ * Keep previous data toggle,
+ * device address and endpoint number:
+ */
+
+ temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) |
+ UHCI_TD_SET_ENDPT(0xF) |
+ UHCI_TD_SET_DT(1));
+
+ if (temp.len == 0) {
+
+ /* make sure that we send an USB packet */
+
+ temp.shortpkt = 0;
+
+ } else {
+
+ /* regular data transfer */
+
+ temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+ }
+
+ /* set endpoint direction */
+
+ temp.td_token |=
+ (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) ?
+ htole32(UHCI_TD_PID_IN) :
+ htole32(UHCI_TD_PID_OUT);
+
+ uhci_setup_standard_chain_sub(&temp);
+ }
+
+ /* check if we should append a status stage */
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ /*
+ * send a DATA1 message and reverse the current endpoint
+ * direction
+ */
+
+ temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) |
+ UHCI_TD_SET_ENDPT(0xF) |
+ UHCI_TD_SET_DT(1));
+ temp.td_token |=
+ (UE_GET_DIR(xfer->endpoint) == UE_DIR_OUT) ?
+ htole32(UHCI_TD_PID_IN | UHCI_TD_SET_DT(1)) :
+ htole32(UHCI_TD_PID_OUT | UHCI_TD_SET_DT(1));
+
+ temp.len = 0;
+ temp.ml.buf_pc = NULL;
+ temp.shortpkt = 0;
+
+ uhci_setup_standard_chain_sub(&temp);
+ }
+ td = temp.td;
+
+ td->td_next = htole32(UHCI_PTR_T);
+
+ /* set interrupt bit */
+
+ td->td_status |= htole32(UHCI_TD_IOC);
+
+ usb2_pc_cpu_flush(td->page_cache);
+
+ /* must have at least one frame! */
+
+ xfer->td_transfer_last = td;
+
+#if USB_DEBUG
+ if (uhcidebug > 8) {
+ DPRINTF("nexttog=%d; data before transfer:\n",
+ xfer->pipe->toggle_next);
+ uhci_dump_tds(xfer->td_transfer_first);
+ }
+#endif
+ return (xfer->td_transfer_first);
+}
+
+/* NOTE: "done" can be run two times in a row,
+ * from close and from interrupt
+ */
+
+static void
+uhci_device_done(struct usb2_xfer *xfer, usb2_error_t error)
+{
+ struct usb2_pipe_methods *methods = xfer->pipe->methods;
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+ uhci_qh_t *qh;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ DPRINTFN(2, "xfer=%p, pipe=%p, error=%d\n",
+ xfer, xfer->pipe, error);
+
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+ if (qh) {
+ usb2_pc_cpu_invalidate(qh->page_cache);
+ }
+ if (xfer->flags_int.bandwidth_reclaimed) {
+ xfer->flags_int.bandwidth_reclaimed = 0;
+ uhci_rem_loop(sc);
+ }
+ if (methods == &uhci_device_bulk_methods) {
+ UHCI_REMOVE_QH(qh, sc->sc_bulk_p_last);
+ }
+ if (methods == &uhci_device_ctrl_methods) {
+ if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
+ UHCI_REMOVE_QH(qh, sc->sc_ls_ctl_p_last);
+ } else {
+ UHCI_REMOVE_QH(qh, sc->sc_fs_ctl_p_last);
+ }
+ }
+ if (methods == &uhci_device_intr_methods) {
+ UHCI_REMOVE_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ /*
+ * Only finish isochronous transfers once
+ * which will update "xfer->frlengths".
+ */
+ if (xfer->td_transfer_first &&
+ xfer->td_transfer_last) {
+ if (methods == &uhci_device_isoc_methods) {
+ uhci_isoc_done(sc, xfer);
+ }
+ xfer->td_transfer_first = NULL;
+ xfer->td_transfer_last = NULL;
+ }
+ /* dequeue transfer and start next transfer */
+ usb2_transfer_done(xfer, error);
+}
+
+/*------------------------------------------------------------------------*
+ * uhci bulk support
+ *------------------------------------------------------------------------*/
+static void
+uhci_device_bulk_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uhci_device_bulk_close(struct usb2_xfer *xfer)
+{
+ uhci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uhci_device_bulk_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uhci_device_bulk_start(struct usb2_xfer *xfer)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+ uhci_td_t *td;
+ uhci_qh_t *qh;
+
+ /* setup TD's */
+ td = uhci_setup_standard_chain(xfer);
+
+ /* setup QH */
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ qh->e_next = td;
+ qh->qh_e_next = td->td_self;
+
+ if (xfer->xroot->udev->pwr_save.suspended == 0) {
+ UHCI_APPEND_QH(qh, sc->sc_bulk_p_last);
+ uhci_add_loop(sc);
+ xfer->flags_int.bandwidth_reclaimed = 1;
+ } else {
+ usb2_pc_cpu_flush(qh->page_cache);
+ }
+
+ /* put transfer on interrupt queue */
+ uhci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods uhci_device_bulk_methods =
+{
+ .open = uhci_device_bulk_open,
+ .close = uhci_device_bulk_close,
+ .enter = uhci_device_bulk_enter,
+ .start = uhci_device_bulk_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * uhci control support
+ *------------------------------------------------------------------------*/
+static void
+uhci_device_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uhci_device_ctrl_close(struct usb2_xfer *xfer)
+{
+ uhci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uhci_device_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uhci_device_ctrl_start(struct usb2_xfer *xfer)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+ uhci_qh_t *qh;
+ uhci_td_t *td;
+
+ /* setup TD's */
+ td = uhci_setup_standard_chain(xfer);
+
+ /* setup QH */
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ qh->e_next = td;
+ qh->qh_e_next = td->td_self;
+
+ /*
+ * NOTE: some devices choke on bandwidth- reclamation for control
+ * transfers
+ */
+ if (xfer->xroot->udev->pwr_save.suspended == 0) {
+ if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
+ UHCI_APPEND_QH(qh, sc->sc_ls_ctl_p_last);
+ } else {
+ UHCI_APPEND_QH(qh, sc->sc_fs_ctl_p_last);
+ }
+ } else {
+ usb2_pc_cpu_flush(qh->page_cache);
+ }
+ /* put transfer on interrupt queue */
+ uhci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods uhci_device_ctrl_methods =
+{
+ .open = uhci_device_ctrl_open,
+ .close = uhci_device_ctrl_close,
+ .enter = uhci_device_ctrl_enter,
+ .start = uhci_device_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * uhci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+uhci_device_intr_open(struct usb2_xfer *xfer)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+ uint16_t best;
+ uint16_t bit;
+ uint16_t x;
+
+ best = 0;
+ bit = UHCI_IFRAMELIST_COUNT / 2;
+ while (bit) {
+ if (xfer->interval >= bit) {
+ x = bit;
+ best = bit;
+ while (x & bit) {
+ if (sc->sc_intr_stat[x] <
+ sc->sc_intr_stat[best]) {
+ best = x;
+ }
+ x++;
+ }
+ break;
+ }
+ bit >>= 1;
+ }
+
+ sc->sc_intr_stat[best]++;
+ xfer->qh_pos = best;
+
+ DPRINTFN(3, "best=%d interval=%d\n",
+ best, xfer->interval);
+}
+
+static void
+uhci_device_intr_close(struct usb2_xfer *xfer)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_intr_stat[xfer->qh_pos]--;
+
+ uhci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uhci_device_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uhci_device_intr_start(struct usb2_xfer *xfer)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+ uhci_qh_t *qh;
+ uhci_td_t *td;
+
+ /* setup TD's */
+ td = uhci_setup_standard_chain(xfer);
+
+ /* setup QH */
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ qh->e_next = td;
+ qh->qh_e_next = td->td_self;
+
+ if (xfer->xroot->udev->pwr_save.suspended == 0) {
+
+ /* enter QHs into the controller data structures */
+ UHCI_APPEND_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]);
+
+ } else {
+ usb2_pc_cpu_flush(qh->page_cache);
+ }
+
+ /* put transfer on interrupt queue */
+ uhci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods uhci_device_intr_methods =
+{
+ .open = uhci_device_intr_open,
+ .close = uhci_device_intr_close,
+ .enter = uhci_device_intr_enter,
+ .start = uhci_device_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * uhci isochronous support
+ *------------------------------------------------------------------------*/
+static void
+uhci_device_isoc_open(struct usb2_xfer *xfer)
+{
+ uhci_td_t *td;
+ uint32_t td_token;
+ uint8_t ds;
+
+ td_token =
+ (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) ?
+ UHCI_TD_IN(0, xfer->endpoint, xfer->address, 0) :
+ UHCI_TD_OUT(0, xfer->endpoint, xfer->address, 0);
+
+ td_token = htole32(td_token);
+
+ /* initialize all TD's */
+
+ for (ds = 0; ds != 2; ds++) {
+
+ for (td = xfer->td_start[ds]; td; td = td->obj_next) {
+
+ /* mark TD as inactive */
+ td->td_status = htole32(UHCI_TD_IOS);
+ td->td_token = td_token;
+
+ usb2_pc_cpu_flush(td->page_cache);
+ }
+ }
+}
+
+static void
+uhci_device_isoc_close(struct usb2_xfer *xfer)
+{
+ uhci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uhci_device_isoc_enter(struct usb2_xfer *xfer)
+{
+ struct uhci_mem_layout ml;
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+ uint32_t nframes;
+ uint32_t temp;
+ uint32_t *plen;
+
+#if USB_DEBUG
+ uint8_t once = 1;
+
+#endif
+ uhci_td_t *td;
+ uhci_td_t *td_last = NULL;
+ uhci_td_t **pp_last;
+
+ DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
+ xfer, xfer->pipe->isoc_next, xfer->nframes);
+
+ nframes = UREAD2(sc, UHCI_FRNUM);
+
+ temp = (nframes - xfer->pipe->isoc_next) &
+ (UHCI_VFRAMELIST_COUNT - 1);
+
+ if ((xfer->pipe->is_synced == 0) ||
+ (temp < xfer->nframes)) {
+ /*
+ * If there is data underflow or the pipe queue is empty we
+ * schedule the transfer a few frames ahead of the current
+ * frame position. Else two isochronous transfers might
+ * overlap.
+ */
+ xfer->pipe->isoc_next = (nframes + 3) & (UHCI_VFRAMELIST_COUNT - 1);
+ xfer->pipe->is_synced = 1;
+ DPRINTFN(3, "start next=%d\n", xfer->pipe->isoc_next);
+ }
+ /*
+ * compute how many milliseconds the insertion is ahead of the
+ * current frame position:
+ */
+ temp = (xfer->pipe->isoc_next - nframes) &
+ (UHCI_VFRAMELIST_COUNT - 1);
+
+ /*
+ * pre-compute when the isochronous transfer will be finished:
+ */
+ xfer->isoc_time_complete =
+ usb2_isoc_time_expand(&sc->sc_bus, nframes) + temp +
+ xfer->nframes;
+
+ /* get the real number of frames */
+
+ nframes = xfer->nframes;
+
+ uhci_mem_layout_init(&ml, xfer);
+
+ plen = xfer->frlengths;
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+ xfer->td_transfer_first = td;
+
+ pp_last = &sc->sc_isoc_p_last[xfer->pipe->isoc_next];
+
+ /* store starting position */
+
+ xfer->qh_pos = xfer->pipe->isoc_next;
+
+ while (nframes--) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ if (pp_last >= &sc->sc_isoc_p_last[UHCI_VFRAMELIST_COUNT]) {
+ pp_last = &sc->sc_isoc_p_last[0];
+ }
+ if (*plen > xfer->max_frame_size) {
+#if USB_DEBUG
+ if (once) {
+ once = 0;
+ printf("%s: frame length(%d) exceeds %d "
+ "bytes (frame truncated)\n",
+ __FUNCTION__, *plen,
+ xfer->max_frame_size);
+ }
+#endif
+ *plen = xfer->max_frame_size;
+ }
+ /* reuse td_token from last transfer */
+
+ td->td_token &= htole32(~UHCI_TD_MAXLEN_MASK);
+ td->td_token |= htole32(UHCI_TD_SET_MAXLEN(*plen));
+
+ td->len = *plen;
+
+ if (td->len == 0) {
+ /*
+ * Do not call "uhci_mem_layout_fixup()" when the
+ * length is zero!
+ */
+ td->td_buffer = 0;
+ td->fix_pc = NULL;
+
+ } else {
+
+ /* fill out buffer pointer and do fixup, if any */
+
+ uhci_mem_layout_fixup(&ml, td);
+
+ }
+
+ /* update status */
+ if (nframes == 0) {
+ td->td_status = htole32
+ (UHCI_TD_ZERO_ACTLEN
+ (UHCI_TD_SET_ERRCNT(0) |
+ UHCI_TD_ACTIVE |
+ UHCI_TD_IOS |
+ UHCI_TD_IOC));
+ } else {
+ td->td_status = htole32
+ (UHCI_TD_ZERO_ACTLEN
+ (UHCI_TD_SET_ERRCNT(0) |
+ UHCI_TD_ACTIVE |
+ UHCI_TD_IOS));
+ }
+
+ usb2_pc_cpu_flush(td->page_cache);
+
+#if USB_DEBUG
+ if (uhcidebug > 5) {
+ DPRINTF("TD %d\n", nframes);
+ uhci_dump_td(td);
+ }
+#endif
+ /* insert TD into schedule */
+ UHCI_APPEND_TD(td, *pp_last);
+ pp_last++;
+
+ plen++;
+ td_last = td;
+ td = td->obj_next;
+ }
+
+ xfer->td_transfer_last = td_last;
+
+ /* update isoc_next */
+ xfer->pipe->isoc_next = (pp_last - &sc->sc_isoc_p_last[0]) &
+ (UHCI_VFRAMELIST_COUNT - 1);
+}
+
+static void
+uhci_device_isoc_start(struct usb2_xfer *xfer)
+{
+ /* put transfer on interrupt queue */
+ uhci_transfer_intr_enqueue(xfer);
+}
+
+struct usb2_pipe_methods uhci_device_isoc_methods =
+{
+ .open = uhci_device_isoc_open,
+ .close = uhci_device_isoc_close,
+ .enter = uhci_device_isoc_enter,
+ .start = uhci_device_isoc_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * uhci root control support
+ *------------------------------------------------------------------------*
+ * simulate a hardware hub by handling
+ * all the necessary requests
+ *------------------------------------------------------------------------*/
+
+static void
+uhci_root_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uhci_root_ctrl_close(struct usb2_xfer *xfer)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_ctrl.xfer == xfer) {
+ sc->sc_root_ctrl.xfer = NULL;
+ }
+ uhci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+/* data structures and routines
+ * to emulate the root hub:
+ */
+
+static const
+struct usb2_device_descriptor uhci_devd =
+{
+ sizeof(struct usb2_device_descriptor),
+ UDESC_DEVICE, /* type */
+ {0x00, 0x01}, /* USB version */
+ UDCLASS_HUB, /* class */
+ UDSUBCLASS_HUB, /* subclass */
+ UDPROTO_FSHUB, /* protocol */
+ 64, /* max packet */
+ {0}, {0}, {0x00, 0x01}, /* device id */
+ 1, 2, 0, /* string indicies */
+ 1 /* # of configurations */
+};
+
+static const struct uhci_config_desc uhci_confd = {
+ .confd = {
+ .bLength = sizeof(struct usb2_config_descriptor),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(uhci_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0 /* max power */
+ },
+
+ .ifcd = {
+ .bLength = sizeof(struct usb2_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = UIPROTO_FSHUB,
+ },
+
+ .endpd = {
+ .bLength = sizeof(struct usb2_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = UE_DIR_IN | UHCI_INTR_ENDPT,
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 8, /* max packet (63 ports) */
+ .bInterval = 255,
+ },
+};
+
+static const
+struct usb2_hub_descriptor_min uhci_hubd_piix =
+{
+ sizeof(uhci_hubd_piix),
+ UDESC_HUB,
+ 2,
+ {UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL, 0},
+ 50, /* power on to power good */
+ 0,
+ {0x00}, /* both ports are removable */
+};
+
+/*
+ * The USB hub protocol requires that SET_FEATURE(PORT_RESET) also
+ * enables the port, and also states that SET_FEATURE(PORT_ENABLE)
+ * should not be used by the USB subsystem. As we cannot issue a
+ * SET_FEATURE(PORT_ENABLE) externally, we must ensure that the port
+ * will be enabled as part of the reset.
+ *
+ * On the VT83C572, the port cannot be successfully enabled until the
+ * outstanding "port enable change" and "connection status change"
+ * events have been reset.
+ */
+static usb2_error_t
+uhci_portreset(uhci_softc_t *sc, uint16_t index, uint8_t use_polling)
+{
+ uint16_t port;
+ uint16_t x;
+ uint8_t lim;
+
+ if (index == 1)
+ port = UHCI_PORTSC1;
+ else if (index == 2)
+ port = UHCI_PORTSC2;
+ else
+ return (USB_ERR_IOERROR);
+
+ /*
+ * Before we do anything, turn on SOF messages on the USB
+ * BUS. Some USB devices do not cope without them!
+ */
+ if (!(UREAD2(sc, UHCI_CMD) & UHCI_CMD_RS)) {
+
+ DPRINTF("Activating SOFs!\n");
+
+ UHCICMD(sc, (UHCI_CMD_MAXP | UHCI_CMD_RS));
+
+ /* wait a little bit */
+ if (use_polling) {
+ DELAY(10000);
+ } else {
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100);
+ }
+ }
+
+ x = URWMASK(UREAD2(sc, port));
+ UWRITE2(sc, port, x | UHCI_PORTSC_PR);
+
+ if (use_polling) {
+ /* polling */
+ DELAY(USB_PORT_ROOT_RESET_DELAY * 1000);
+ } else {
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(USB_PORT_ROOT_RESET_DELAY));
+ }
+
+ DPRINTFN(4, "uhci port %d reset, status0 = 0x%04x\n",
+ index, UREAD2(sc, port));
+
+ x = URWMASK(UREAD2(sc, port));
+ UWRITE2(sc, port, x & ~UHCI_PORTSC_PR);
+
+
+ mtx_unlock(&sc->sc_bus.bus_mtx);
+
+ /*
+ * This delay needs to be exactly 100us, else some USB devices
+ * fail to attach!
+ */
+ DELAY(100);
+
+ mtx_lock(&sc->sc_bus.bus_mtx);
+
+ DPRINTFN(4, "uhci port %d reset, status1 = 0x%04x\n",
+ index, UREAD2(sc, port));
+
+ x = URWMASK(UREAD2(sc, port));
+ UWRITE2(sc, port, x | UHCI_PORTSC_PE);
+
+ for (lim = 0; lim < 12; lim++) {
+
+ if (use_polling) {
+ /* polling */
+ DELAY(USB_PORT_RESET_DELAY * 1000);
+ } else {
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(USB_PORT_RESET_DELAY));
+ }
+
+ x = UREAD2(sc, port);
+
+ DPRINTFN(4, "uhci port %d iteration %u, status = 0x%04x\n",
+ index, lim, x);
+
+ if (!(x & UHCI_PORTSC_CCS)) {
+ /*
+ * No device is connected (or was disconnected
+ * during reset). Consider the port reset.
+ * The delay must be long enough to ensure on
+ * the initial iteration that the device
+ * connection will have been registered. 50ms
+ * appears to be sufficient, but 20ms is not.
+ */
+ DPRINTFN(4, "uhci port %d loop %u, device detached\n",
+ index, lim);
+ goto done;
+ }
+ if (x & (UHCI_PORTSC_POEDC | UHCI_PORTSC_CSC)) {
+ /*
+ * Port enabled changed and/or connection
+ * status changed were set. Reset either or
+ * both raised flags (by writing a 1 to that
+ * bit), and wait again for state to settle.
+ */
+ UWRITE2(sc, port, URWMASK(x) |
+ (x & (UHCI_PORTSC_POEDC | UHCI_PORTSC_CSC)));
+ continue;
+ }
+ if (x & UHCI_PORTSC_PE) {
+ /* port is enabled */
+ goto done;
+ }
+ UWRITE2(sc, port, URWMASK(x) | UHCI_PORTSC_PE);
+ }
+
+ DPRINTFN(2, "uhci port %d reset timed out\n", index);
+ return (USB_ERR_TIMEOUT);
+
+done:
+ DPRINTFN(4, "uhci port %d reset, status2 = 0x%04x\n",
+ index, UREAD2(sc, port));
+
+ sc->sc_isreset = 1;
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+uhci_root_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uhci_root_ctrl_start(struct usb2_xfer *xfer)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+
+ DPRINTF("\n");
+
+ sc->sc_root_ctrl.xfer = xfer;
+
+ usb2_bus_roothub_exec(xfer->xroot->bus);
+}
+
+static void
+uhci_root_ctrl_task(struct usb2_bus *bus)
+{
+ uhci_root_ctrl_poll(UHCI_BUS2SC(bus));
+}
+
+static void
+uhci_root_ctrl_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+ char *ptr;
+ uint16_t x;
+ uint16_t port;
+ uint16_t value;
+ uint16_t index;
+ uint16_t status;
+ uint16_t change;
+ uint8_t use_polling;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_SETUP) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ uhci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* buffer reset */
+ std->ptr = sc->sc_hub_desc.temp;
+ std->len = 0;
+
+ value = UGETW(std->req.wValue);
+ index = UGETW(std->req.wIndex);
+
+ use_polling = mtx_owned(xfer->xroot->xfer_mtx) ? 1 : 0;
+
+ DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x "
+ "wValue=0x%04x wIndex=0x%04x\n",
+ std->req.bmRequestType, std->req.bRequest,
+ UGETW(std->req.wLength), value, index);
+
+#define C(x,y) ((x) | ((y) << 8))
+ switch (C(std->req.bRequest, std->req.bmRequestType)) {
+ case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE):
+ case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE):
+ case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT):
+ /*
+ * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops
+ * for the integrated root hub.
+ */
+ break;
+ case C(UR_GET_CONFIG, UT_READ_DEVICE):
+ std->len = 1;
+ sc->sc_hub_desc.temp[0] = sc->sc_conf;
+ break;
+ case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE):
+ switch (value >> 8) {
+ case UDESC_DEVICE:
+ if ((value & 0xff) != 0) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ std->len = sizeof(uhci_devd);
+ sc->sc_hub_desc.devd = uhci_devd;
+ break;
+
+ case UDESC_CONFIG:
+ if ((value & 0xff) != 0) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ std->len = sizeof(uhci_confd);
+ std->ptr = USB_ADD_BYTES(&uhci_confd, 0);
+ break;
+
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ ptr = "\001";
+ break;
+
+ case 1: /* Vendor */
+ ptr = sc->sc_vendor;
+ break;
+
+ case 2: /* Product */
+ ptr = "UHCI root HUB";
+ break;
+
+ default:
+ ptr = "";
+ break;
+ }
+
+ std->len = usb2_make_str_desc
+ (sc->sc_hub_desc.temp,
+ sizeof(sc->sc_hub_desc.temp),
+ ptr);
+ break;
+
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ case C(UR_GET_INTERFACE, UT_READ_INTERFACE):
+ std->len = 1;
+ sc->sc_hub_desc.temp[0] = 0;
+ break;
+ case C(UR_GET_STATUS, UT_READ_DEVICE):
+ std->len = 2;
+ USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED);
+ break;
+ case C(UR_GET_STATUS, UT_READ_INTERFACE):
+ case C(UR_GET_STATUS, UT_READ_ENDPOINT):
+ std->len = 2;
+ USETW(sc->sc_hub_desc.stat.wStatus, 0);
+ break;
+ case C(UR_SET_ADDRESS, UT_WRITE_DEVICE):
+ if (value >= USB_MAX_DEVICES) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ sc->sc_addr = value;
+ break;
+ case C(UR_SET_CONFIG, UT_WRITE_DEVICE):
+ if ((value != 0) && (value != 1)) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ sc->sc_conf = value;
+ break;
+ case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE):
+ break;
+ case C(UR_SET_FEATURE, UT_WRITE_DEVICE):
+ case C(UR_SET_FEATURE, UT_WRITE_INTERFACE):
+ case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT):
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE):
+ break;
+ case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT):
+ break;
+ /* Hub requests */
+ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE):
+ break;
+ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER):
+ DPRINTFN(4, "UR_CLEAR_PORT_FEATURE "
+ "port=%d feature=%d\n",
+ index, value);
+ if (index == 1)
+ port = UHCI_PORTSC1;
+ else if (index == 2)
+ port = UHCI_PORTSC2;
+ else {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ x = URWMASK(UREAD2(sc, port));
+ UWRITE2(sc, port, x & ~UHCI_PORTSC_PE);
+ break;
+ case UHF_PORT_SUSPEND:
+ x = URWMASK(UREAD2(sc, port));
+ UWRITE2(sc, port, x & ~(UHCI_PORTSC_SUSP));
+ break;
+ case UHF_PORT_RESET:
+ x = URWMASK(UREAD2(sc, port));
+ UWRITE2(sc, port, x & ~UHCI_PORTSC_PR);
+ break;
+ case UHF_C_PORT_CONNECTION:
+ x = URWMASK(UREAD2(sc, port));
+ UWRITE2(sc, port, x | UHCI_PORTSC_CSC);
+ break;
+ case UHF_C_PORT_ENABLE:
+ x = URWMASK(UREAD2(sc, port));
+ UWRITE2(sc, port, x | UHCI_PORTSC_POEDC);
+ break;
+ case UHF_C_PORT_OVER_CURRENT:
+ x = URWMASK(UREAD2(sc, port));
+ UWRITE2(sc, port, x | UHCI_PORTSC_OCIC);
+ break;
+ case UHF_C_PORT_RESET:
+ sc->sc_isreset = 0;
+ std->err = USB_ERR_NORMAL_COMPLETION;
+ goto done;
+ case UHF_C_PORT_SUSPEND:
+ sc->sc_isresumed &= ~(1 << index);
+ break;
+ case UHF_PORT_CONNECTION:
+ case UHF_PORT_OVER_CURRENT:
+ case UHF_PORT_POWER:
+ case UHF_PORT_LOW_SPEED:
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ case C(UR_GET_BUS_STATE, UT_READ_CLASS_OTHER):
+ if (index == 1)
+ port = UHCI_PORTSC1;
+ else if (index == 2)
+ port = UHCI_PORTSC2;
+ else {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ std->len = 1;
+ sc->sc_hub_desc.temp[0] =
+ ((UREAD2(sc, port) & UHCI_PORTSC_LS) >>
+ UHCI_PORTSC_LS_SHIFT);
+ break;
+ case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE):
+ if ((value & 0xff) != 0) {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ std->len = sizeof(uhci_hubd_piix);
+ std->ptr = USB_ADD_BYTES(&uhci_hubd_piix, 0);
+ break;
+ case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE):
+ std->len = 16;
+ bzero(sc->sc_hub_desc.temp, 16);
+ break;
+ case C(UR_GET_STATUS, UT_READ_CLASS_OTHER):
+ if (index == 1)
+ port = UHCI_PORTSC1;
+ else if (index == 2)
+ port = UHCI_PORTSC2;
+ else {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ x = UREAD2(sc, port);
+ status = change = 0;
+ if (x & UHCI_PORTSC_CCS)
+ status |= UPS_CURRENT_CONNECT_STATUS;
+ if (x & UHCI_PORTSC_CSC)
+ change |= UPS_C_CONNECT_STATUS;
+ if (x & UHCI_PORTSC_PE)
+ status |= UPS_PORT_ENABLED;
+ if (x & UHCI_PORTSC_POEDC)
+ change |= UPS_C_PORT_ENABLED;
+ if (x & UHCI_PORTSC_OCI)
+ status |= UPS_OVERCURRENT_INDICATOR;
+ if (x & UHCI_PORTSC_OCIC)
+ change |= UPS_C_OVERCURRENT_INDICATOR;
+ if (x & UHCI_PORTSC_LSDA)
+ status |= UPS_LOW_SPEED;
+ if ((x & UHCI_PORTSC_PE) && (x & UHCI_PORTSC_RD)) {
+ /* need to do a write back */
+ UWRITE2(sc, port, URWMASK(x));
+
+ /* wait 20ms for resume sequence to complete */
+ if (use_polling) {
+ /* polling */
+ DELAY(20000);
+ } else {
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50);
+ }
+
+ /* clear suspend and resume detect */
+ UWRITE2(sc, port, URWMASK(x) & ~(UHCI_PORTSC_RD |
+ UHCI_PORTSC_SUSP));
+
+ /* wait a little bit */
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 500);
+
+ sc->sc_isresumed |= (1 << index);
+
+ } else if (x & UHCI_PORTSC_SUSP) {
+ status |= UPS_SUSPEND;
+ }
+ status |= UPS_PORT_POWER;
+ if (sc->sc_isresumed & (1 << index))
+ change |= UPS_C_SUSPEND;
+ if (sc->sc_isreset)
+ change |= UPS_C_PORT_RESET;
+ USETW(sc->sc_hub_desc.ps.wPortStatus, status);
+ USETW(sc->sc_hub_desc.ps.wPortChange, change);
+ std->len = sizeof(sc->sc_hub_desc.ps);
+ break;
+ case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE):
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE):
+ break;
+ case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER):
+ if (index == 1)
+ port = UHCI_PORTSC1;
+ else if (index == 2)
+ port = UHCI_PORTSC2;
+ else {
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ x = URWMASK(UREAD2(sc, port));
+ UWRITE2(sc, port, x | UHCI_PORTSC_PE);
+ break;
+ case UHF_PORT_SUSPEND:
+ x = URWMASK(UREAD2(sc, port));
+ UWRITE2(sc, port, x | UHCI_PORTSC_SUSP);
+ break;
+ case UHF_PORT_RESET:
+ std->err = uhci_portreset(sc, index, use_polling);
+ goto done;
+ case UHF_PORT_POWER:
+ /* pretend we turned on power */
+ std->err = USB_ERR_NORMAL_COMPLETION;
+ goto done;
+ case UHF_C_PORT_CONNECTION:
+ case UHF_C_PORT_ENABLE:
+ case UHF_C_PORT_OVER_CURRENT:
+ case UHF_PORT_CONNECTION:
+ case UHF_PORT_OVER_CURRENT:
+ case UHF_PORT_LOW_SPEED:
+ case UHF_C_PORT_SUSPEND:
+ case UHF_C_PORT_RESET:
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+done:
+ return;
+}
+
+static void
+uhci_root_ctrl_poll(struct uhci_softc *sc)
+{
+ usb2_sw_transfer(&sc->sc_root_ctrl,
+ &uhci_root_ctrl_done);
+}
+
+struct usb2_pipe_methods uhci_root_ctrl_methods =
+{
+ .open = uhci_root_ctrl_open,
+ .close = uhci_root_ctrl_close,
+ .enter = uhci_root_ctrl_enter,
+ .start = uhci_root_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 0,
+};
+
+/*------------------------------------------------------------------------*
+ * uhci root interrupt support
+ *------------------------------------------------------------------------*/
+static void
+uhci_root_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uhci_root_intr_close(struct usb2_xfer *xfer)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_intr.xfer == xfer) {
+ sc->sc_root_intr.xfer = NULL;
+ }
+ uhci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uhci_root_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uhci_root_intr_start(struct usb2_xfer *xfer)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_intr.xfer = xfer;
+
+ usb2_transfer_timeout_ms(xfer,
+ &uhci_root_intr_check, xfer->interval);
+}
+
+static void
+uhci_root_intr_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_PRE_DATA) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer is transferred */
+ uhci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* setup buffer */
+ std->ptr = sc->sc_hub_idata;
+ std->len = sizeof(sc->sc_hub_idata);
+done:
+ return;
+}
+
+/*
+ * this routine is executed periodically and simulates interrupts
+ * from the root controller interrupt pipe for port status change
+ */
+static void
+uhci_root_intr_check(void *arg)
+{
+ struct usb2_xfer *xfer = arg;
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+
+ DPRINTFN(21, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ sc->sc_hub_idata[0] = 0;
+
+ if (UREAD2(sc, UHCI_PORTSC1) & (UHCI_PORTSC_CSC |
+ UHCI_PORTSC_OCIC | UHCI_PORTSC_RD)) {
+ sc->sc_hub_idata[0] |= 1 << 1;
+ }
+ if (UREAD2(sc, UHCI_PORTSC2) & (UHCI_PORTSC_CSC |
+ UHCI_PORTSC_OCIC | UHCI_PORTSC_RD)) {
+ sc->sc_hub_idata[0] |= 1 << 2;
+ }
+ if (sc->sc_hub_idata[0] == 0) {
+ /*
+ * no change or controller not running, try again in a while
+ */
+ uhci_root_intr_start(xfer);
+ } else {
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &uhci_root_intr_done);
+ }
+}
+
+struct usb2_pipe_methods uhci_root_intr_methods =
+{
+ .open = uhci_root_intr_open,
+ .close = uhci_root_intr_close,
+ .enter = uhci_root_intr_enter,
+ .start = uhci_root_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+static void
+uhci_xfer_setup(struct usb2_setup_params *parm)
+{
+ struct usb2_page_search page_info;
+ struct usb2_page_cache *pc;
+ uhci_softc_t *sc;
+ struct usb2_xfer *xfer;
+ void *last_obj;
+ uint32_t ntd;
+ uint32_t nqh;
+ uint32_t nfixup;
+ uint32_t n;
+ uint16_t align;
+
+ sc = UHCI_BUS2SC(parm->udev->bus);
+ xfer = parm->curr_xfer;
+
+ parm->hc_max_packet_size = 0x500;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = 0x500;
+
+ /*
+ * compute ntd and nqh
+ */
+ if (parm->methods == &uhci_device_ctrl_methods) {
+ xfer->flags_int.bdma_enable = 1;
+ xfer->flags_int.bdma_no_post_sync = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ /* see EHCI HC driver for proof of "ntd" formula */
+
+ nqh = 1;
+ ntd = ((2 * xfer->nframes) + 1 /* STATUS */
+ + (xfer->max_data_length / xfer->max_frame_size));
+
+ } else if (parm->methods == &uhci_device_bulk_methods) {
+ xfer->flags_int.bdma_enable = 1;
+ xfer->flags_int.bdma_no_post_sync = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ nqh = 1;
+ ntd = ((2 * xfer->nframes)
+ + (xfer->max_data_length / xfer->max_frame_size));
+
+ } else if (parm->methods == &uhci_device_intr_methods) {
+ xfer->flags_int.bdma_enable = 1;
+ xfer->flags_int.bdma_no_post_sync = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ nqh = 1;
+ ntd = ((2 * xfer->nframes)
+ + (xfer->max_data_length / xfer->max_frame_size));
+
+ } else if (parm->methods == &uhci_device_isoc_methods) {
+ xfer->flags_int.bdma_enable = 1;
+ xfer->flags_int.bdma_no_post_sync = 1;
+
+ usb2_transfer_setup_sub(parm);
+
+ nqh = 0;
+ ntd = xfer->nframes;
+
+ } else {
+
+ usb2_transfer_setup_sub(parm);
+
+ nqh = 0;
+ ntd = 0;
+ }
+
+ if (parm->err) {
+ return;
+ }
+ /*
+ * NOTE: the UHCI controller requires that
+ * every packet must be contiguous on
+ * the same USB memory page !
+ */
+ nfixup = (parm->bufsize / USB_PAGE_SIZE) + 1;
+
+ /*
+ * Compute a suitable power of two alignment
+ * for our "max_frame_size" fixup buffer(s):
+ */
+ align = xfer->max_frame_size;
+ n = 0;
+ while (align) {
+ align >>= 1;
+ n++;
+ }
+
+ /* check for power of two */
+ if (!(xfer->max_frame_size &
+ (xfer->max_frame_size - 1))) {
+ n--;
+ }
+ /*
+ * We don't allow alignments of
+ * less than 8 bytes:
+ *
+ * NOTE: Allocating using an aligment
+ * of 1 byte has special meaning!
+ */
+ if (n < 3) {
+ n = 3;
+ }
+ align = (1 << n);
+
+ if (usb2_transfer_setup_sub_malloc(
+ parm, &pc, xfer->max_frame_size,
+ align, nfixup)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ xfer->buf_fixup = pc;
+
+alloc_dma_set:
+
+ if (parm->err) {
+ return;
+ }
+ last_obj = NULL;
+
+ if (usb2_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(uhci_td_t),
+ UHCI_TD_ALIGN, ntd)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != ntd; n++) {
+ uhci_td_t *td;
+
+ usb2_get_page(pc + n, 0, &page_info);
+
+ td = page_info.buffer;
+
+ /* init TD */
+ if ((parm->methods == &uhci_device_bulk_methods) ||
+ (parm->methods == &uhci_device_ctrl_methods) ||
+ (parm->methods == &uhci_device_intr_methods)) {
+ /* set depth first bit */
+ td->td_self = htole32(page_info.physaddr |
+ UHCI_PTR_TD | UHCI_PTR_VF);
+ } else {
+ td->td_self = htole32(page_info.physaddr |
+ UHCI_PTR_TD);
+ }
+
+ td->obj_next = last_obj;
+ td->page_cache = pc + n;
+
+ last_obj = td;
+
+ usb2_pc_cpu_flush(pc + n);
+ }
+ }
+ xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj;
+
+ last_obj = NULL;
+
+ if (usb2_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(uhci_qh_t),
+ UHCI_QH_ALIGN, nqh)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nqh; n++) {
+ uhci_qh_t *qh;
+
+ usb2_get_page(pc + n, 0, &page_info);
+
+ qh = page_info.buffer;
+
+ /* init QH */
+ qh->qh_self = htole32(page_info.physaddr | UHCI_PTR_QH);
+ qh->obj_next = last_obj;
+ qh->page_cache = pc + n;
+
+ last_obj = qh;
+
+ usb2_pc_cpu_flush(pc + n);
+ }
+ }
+ xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj;
+
+ if (!xfer->flags_int.curr_dma_set) {
+ xfer->flags_int.curr_dma_set = 1;
+ goto alloc_dma_set;
+ }
+}
+
+static void
+uhci_pipe_init(struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc,
+ struct usb2_pipe *pipe)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
+ pipe, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb2_mode,
+ sc->sc_addr);
+
+ if (udev->flags.usb2_mode != USB_MODE_HOST) {
+ /* not supported */
+ return;
+ }
+ if (udev->device_index == sc->sc_addr) {
+ switch (edesc->bEndpointAddress) {
+ case USB_CONTROL_ENDPOINT:
+ pipe->methods = &uhci_root_ctrl_methods;
+ break;
+ case UE_DIR_IN | UHCI_INTR_ENDPT:
+ pipe->methods = &uhci_root_intr_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ } else {
+ switch (edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_CONTROL:
+ pipe->methods = &uhci_device_ctrl_methods;
+ break;
+ case UE_INTERRUPT:
+ pipe->methods = &uhci_device_intr_methods;
+ break;
+ case UE_ISOCHRONOUS:
+ if (udev->speed == USB_SPEED_FULL) {
+ pipe->methods = &uhci_device_isoc_methods;
+ }
+ break;
+ case UE_BULK:
+ if (udev->speed != USB_SPEED_LOW) {
+ pipe->methods = &uhci_device_bulk_methods;
+ }
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+}
+
+static void
+uhci_xfer_unsetup(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uhci_get_dma_delay(struct usb2_bus *bus, uint32_t *pus)
+{
+ /*
+ * Wait until hardware has finished any possible use of the
+ * transfer descriptor(s) and QH
+ */
+ *pus = (1125); /* microseconds */
+}
+
+static void
+uhci_device_resume(struct usb2_device *udev)
+{
+ struct uhci_softc *sc = UHCI_BUS2SC(udev->bus);
+ struct usb2_xfer *xfer;
+ struct usb2_pipe_methods *methods;
+ uhci_qh_t *qh;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(udev->bus);
+
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+
+ if (xfer->xroot->udev == udev) {
+
+ methods = xfer->pipe->methods;
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ if (methods == &uhci_device_bulk_methods) {
+ UHCI_APPEND_QH(qh, sc->sc_bulk_p_last);
+ uhci_add_loop(sc);
+ xfer->flags_int.bandwidth_reclaimed = 1;
+ }
+ if (methods == &uhci_device_ctrl_methods) {
+ if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
+ UHCI_APPEND_QH(qh, sc->sc_ls_ctl_p_last);
+ } else {
+ UHCI_APPEND_QH(qh, sc->sc_fs_ctl_p_last);
+ }
+ }
+ if (methods == &uhci_device_intr_methods) {
+ UHCI_APPEND_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ }
+ }
+
+ USB_BUS_UNLOCK(udev->bus);
+
+ return;
+}
+
+static void
+uhci_device_suspend(struct usb2_device *udev)
+{
+ struct uhci_softc *sc = UHCI_BUS2SC(udev->bus);
+ struct usb2_xfer *xfer;
+ struct usb2_pipe_methods *methods;
+ uhci_qh_t *qh;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(udev->bus);
+
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+
+ if (xfer->xroot->udev == udev) {
+
+ methods = xfer->pipe->methods;
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ if (xfer->flags_int.bandwidth_reclaimed) {
+ xfer->flags_int.bandwidth_reclaimed = 0;
+ uhci_rem_loop(sc);
+ }
+ if (methods == &uhci_device_bulk_methods) {
+ UHCI_REMOVE_QH(qh, sc->sc_bulk_p_last);
+ }
+ if (methods == &uhci_device_ctrl_methods) {
+ if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
+ UHCI_REMOVE_QH(qh, sc->sc_ls_ctl_p_last);
+ } else {
+ UHCI_REMOVE_QH(qh, sc->sc_fs_ctl_p_last);
+ }
+ }
+ if (methods == &uhci_device_intr_methods) {
+ UHCI_REMOVE_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ }
+ }
+
+ USB_BUS_UNLOCK(udev->bus);
+
+ return;
+}
+
+static void
+uhci_set_hw_power(struct usb2_bus *bus)
+{
+ struct uhci_softc *sc = UHCI_BUS2SC(bus);
+ uint32_t flags;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(bus);
+
+ flags = bus->hw_power_state;
+
+ /*
+ * WARNING: Some FULL speed USB devices require periodic SOF
+ * messages! If any USB devices are connected through the
+ * UHCI, power save will be disabled!
+ */
+ if (flags & (USB_HW_POWER_CONTROL |
+ USB_HW_POWER_NON_ROOT_HUB |
+ USB_HW_POWER_BULK |
+ USB_HW_POWER_INTERRUPT |
+ USB_HW_POWER_ISOC)) {
+ DPRINTF("Some USB transfer is "
+ "active on %u.\n",
+ device_get_unit(sc->sc_bus.bdev));
+ UHCICMD(sc, (UHCI_CMD_MAXP | UHCI_CMD_RS));
+ } else {
+ DPRINTF("Power save on %u.\n",
+ device_get_unit(sc->sc_bus.bdev));
+ UHCICMD(sc, UHCI_CMD_MAXP);
+ }
+
+ USB_BUS_UNLOCK(bus);
+
+ return;
+}
+
+
+struct usb2_bus_methods uhci_bus_methods =
+{
+ .pipe_init = uhci_pipe_init,
+ .xfer_setup = uhci_xfer_setup,
+ .xfer_unsetup = uhci_xfer_unsetup,
+ .do_poll = uhci_do_poll,
+ .get_dma_delay = uhci_get_dma_delay,
+ .device_resume = uhci_device_resume,
+ .device_suspend = uhci_device_suspend,
+ .set_hw_power = uhci_set_hw_power,
+ .roothub_exec = uhci_root_ctrl_task,
+};
diff --git a/sys/dev/usb/controller/uhci.h b/sys/dev/usb/controller/uhci.h
new file mode 100644
index 0000000..9365a4c
--- /dev/null
+++ b/sys/dev/usb/controller/uhci.h
@@ -0,0 +1,321 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 1998 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Lennart Augustsson (lennart@augustsson.net) at
+ * Carlstedt Research & Technology.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the NetBSD
+ * Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#ifndef _UHCI_H_
+#define _UHCI_H_
+
+#define UHCI_MAX_DEVICES USB_MAX_DEVICES
+
+/* PCI config registers */
+#define PCI_USBREV 0x60 /* USB protocol revision */
+#define PCI_USB_REV_MASK 0xff
+#define PCI_USB_REV_PRE_1_0 0x00
+#define PCI_USB_REV_1_0 0x10
+#define PCI_USB_REV_1_1 0x11
+#define PCI_LEGSUP 0xc0 /* Legacy Support register */
+#define PCI_LEGSUP_USBPIRQDEN 0x2000 /* USB PIRQ D Enable */
+#define PCI_CBIO 0x20 /* configuration base IO */
+#define PCI_INTERFACE_UHCI 0x00
+
+/* UHCI registers */
+#define UHCI_CMD 0x00
+#define UHCI_CMD_RS 0x0001
+#define UHCI_CMD_HCRESET 0x0002
+#define UHCI_CMD_GRESET 0x0004
+#define UHCI_CMD_EGSM 0x0008
+#define UHCI_CMD_FGR 0x0010
+#define UHCI_CMD_SWDBG 0x0020
+#define UHCI_CMD_CF 0x0040
+#define UHCI_CMD_MAXP 0x0080
+#define UHCI_STS 0x02
+#define UHCI_STS_USBINT 0x0001
+#define UHCI_STS_USBEI 0x0002
+#define UHCI_STS_RD 0x0004
+#define UHCI_STS_HSE 0x0008
+#define UHCI_STS_HCPE 0x0010
+#define UHCI_STS_HCH 0x0020
+#define UHCI_STS_ALLINTRS 0x003f
+#define UHCI_INTR 0x04
+#define UHCI_INTR_TOCRCIE 0x0001
+#define UHCI_INTR_RIE 0x0002
+#define UHCI_INTR_IOCE 0x0004
+#define UHCI_INTR_SPIE 0x0008
+#define UHCI_FRNUM 0x06
+#define UHCI_FRNUM_MASK 0x03ff
+#define UHCI_FLBASEADDR 0x08
+#define UHCI_SOF 0x0c
+#define UHCI_SOF_MASK 0x7f
+#define UHCI_PORTSC1 0x010
+#define UHCI_PORTSC2 0x012
+#define UHCI_PORTSC_CCS 0x0001
+#define UHCI_PORTSC_CSC 0x0002
+#define UHCI_PORTSC_PE 0x0004
+#define UHCI_PORTSC_POEDC 0x0008
+#define UHCI_PORTSC_LS 0x0030
+#define UHCI_PORTSC_LS_SHIFT 4
+#define UHCI_PORTSC_RD 0x0040
+#define UHCI_PORTSC_LSDA 0x0100
+#define UHCI_PORTSC_PR 0x0200
+#define UHCI_PORTSC_OCI 0x0400
+#define UHCI_PORTSC_OCIC 0x0800
+#define UHCI_PORTSC_SUSP 0x1000
+
+#define URWMASK(x) ((x) & (UHCI_PORTSC_SUSP | \
+ UHCI_PORTSC_PR | UHCI_PORTSC_RD | \
+ UHCI_PORTSC_PE))
+
+#define UHCI_FRAMELIST_COUNT 1024 /* units */
+#define UHCI_FRAMELIST_ALIGN 4096 /* bytes */
+
+/* Structures alignment (bytes) */
+#define UHCI_TD_ALIGN 16
+#define UHCI_QH_ALIGN 16
+
+#if ((USB_PAGE_SIZE < UHCI_TD_ALIGN) || (UHCI_TD_ALIGN == 0) || \
+ (USB_PAGE_SIZE < UHCI_QH_ALIGN) || (UHCI_QH_ALIGN == 0))
+#error "Invalid USB page size!"
+#endif
+
+typedef uint32_t uhci_physaddr_t;
+
+#define UHCI_PTR_T 0x00000001
+#define UHCI_PTR_TD 0x00000000
+#define UHCI_PTR_QH 0x00000002
+#define UHCI_PTR_VF 0x00000004
+
+#define UHCI_QH_REMOVE_DELAY 5 /* us - QH remove delay */
+
+/*
+ * The Queue Heads (QH) and Transfer Descriptors (TD) are accessed by
+ * both the CPU and the USB-controller which run concurrently. Great
+ * care must be taken. When the data-structures are linked into the
+ * USB controller's frame list, the USB-controller "owns" the
+ * td_status and qh_elink fields, which will not be written by the
+ * CPU.
+ *
+ */
+
+struct uhci_td {
+/*
+ * Data used by the UHCI controller.
+ * volatile is used in order to mantain struct members ordering.
+ */
+ volatile uint32_t td_next;
+ volatile uint32_t td_status;
+#define UHCI_TD_GET_ACTLEN(s) (((s) + 1) & 0x3ff)
+#define UHCI_TD_ZERO_ACTLEN(t) ((t) | 0x3ff)
+#define UHCI_TD_BITSTUFF 0x00020000
+#define UHCI_TD_CRCTO 0x00040000
+#define UHCI_TD_NAK 0x00080000
+#define UHCI_TD_BABBLE 0x00100000
+#define UHCI_TD_DBUFFER 0x00200000
+#define UHCI_TD_STALLED 0x00400000
+#define UHCI_TD_ACTIVE 0x00800000
+#define UHCI_TD_IOC 0x01000000
+#define UHCI_TD_IOS 0x02000000
+#define UHCI_TD_LS 0x04000000
+#define UHCI_TD_GET_ERRCNT(s) (((s) >> 27) & 3)
+#define UHCI_TD_SET_ERRCNT(n) ((n) << 27)
+#define UHCI_TD_SPD 0x20000000
+ volatile uint32_t td_token;
+#define UHCI_TD_PID 0x000000ff
+#define UHCI_TD_PID_IN 0x00000069
+#define UHCI_TD_PID_OUT 0x000000e1
+#define UHCI_TD_PID_SETUP 0x0000002d
+#define UHCI_TD_GET_PID(s) ((s) & 0xff)
+#define UHCI_TD_SET_DEVADDR(a) ((a) << 8)
+#define UHCI_TD_GET_DEVADDR(s) (((s) >> 8) & 0x7f)
+#define UHCI_TD_SET_ENDPT(e) (((e) & 0xf) << 15)
+#define UHCI_TD_GET_ENDPT(s) (((s) >> 15) & 0xf)
+#define UHCI_TD_SET_DT(t) ((t) << 19)
+#define UHCI_TD_GET_DT(s) (((s) >> 19) & 1)
+#define UHCI_TD_SET_MAXLEN(l) (((l)-1) << 21)
+#define UHCI_TD_GET_MAXLEN(s) ((((s) >> 21) + 1) & 0x7ff)
+#define UHCI_TD_MAXLEN_MASK 0xffe00000
+ volatile uint32_t td_buffer;
+/*
+ * Extra information needed:
+ */
+ struct uhci_td *next;
+ struct uhci_td *prev;
+ struct uhci_td *obj_next;
+ struct usb2_page_cache *page_cache;
+ struct usb2_page_cache *fix_pc;
+ uint32_t td_self;
+ uint16_t len;
+} __aligned(UHCI_TD_ALIGN);
+
+typedef struct uhci_td uhci_td_t;
+
+#define UHCI_TD_ERROR (UHCI_TD_BITSTUFF | UHCI_TD_CRCTO | \
+ UHCI_TD_BABBLE | UHCI_TD_DBUFFER | UHCI_TD_STALLED)
+
+#define UHCI_TD_SETUP(len, endp, dev) (UHCI_TD_SET_MAXLEN(len) | \
+ UHCI_TD_SET_ENDPT(endp) | \
+ UHCI_TD_SET_DEVADDR(dev) | \
+ UHCI_TD_PID_SETUP)
+
+#define UHCI_TD_OUT(len, endp, dev, dt) (UHCI_TD_SET_MAXLEN(len) | \
+ UHCI_TD_SET_ENDPT(endp) | \
+ UHCI_TD_SET_DEVADDR(dev) | \
+ UHCI_TD_PID_OUT | UHCI_TD_SET_DT(dt))
+
+#define UHCI_TD_IN(len, endp, dev, dt) (UHCI_TD_SET_MAXLEN(len) | \
+ UHCI_TD_SET_ENDPT(endp) | \
+ UHCI_TD_SET_DEVADDR(dev) | \
+ UHCI_TD_PID_IN | UHCI_TD_SET_DT(dt))
+
+struct uhci_qh {
+/*
+ * Data used by the UHCI controller.
+ */
+ volatile uint32_t qh_h_next;
+ volatile uint32_t qh_e_next;
+/*
+ * Extra information needed:
+ */
+ struct uhci_qh *h_next;
+ struct uhci_qh *h_prev;
+ struct uhci_qh *obj_next;
+ struct uhci_td *e_next;
+ struct usb2_page_cache *page_cache;
+ uint32_t qh_self;
+ uint16_t intr_pos;
+} __aligned(UHCI_QH_ALIGN);
+
+typedef struct uhci_qh uhci_qh_t;
+
+/* Maximum number of isochronous TD's and QH's interrupt */
+#define UHCI_VFRAMELIST_COUNT 128
+#define UHCI_IFRAMELIST_COUNT (2 * UHCI_VFRAMELIST_COUNT)
+
+#if (((UHCI_VFRAMELIST_COUNT & (UHCI_VFRAMELIST_COUNT-1)) != 0) || \
+ (UHCI_VFRAMELIST_COUNT > UHCI_FRAMELIST_COUNT))
+#error "UHCI_VFRAMELIST_COUNT is not power of two"
+#error "or UHCI_VFRAMELIST_COUNT > UHCI_FRAMELIST_COUNT"
+#endif
+
+#if (UHCI_VFRAMELIST_COUNT < USB_MAX_FS_ISOC_FRAMES_PER_XFER)
+#error "maximum number of full-speed isochronous frames is higher than supported!"
+#endif
+
+struct uhci_config_desc {
+ struct usb2_config_descriptor confd;
+ struct usb2_interface_descriptor ifcd;
+ struct usb2_endpoint_descriptor endpd;
+} __packed;
+
+union uhci_hub_desc {
+ struct usb2_status stat;
+ struct usb2_port_status ps;
+ struct usb2_device_descriptor devd;
+ uint8_t temp[128];
+};
+
+struct uhci_hw_softc {
+ struct usb2_page_cache pframes_pc;
+ struct usb2_page_cache isoc_start_pc[UHCI_VFRAMELIST_COUNT];
+ struct usb2_page_cache intr_start_pc[UHCI_IFRAMELIST_COUNT];
+ struct usb2_page_cache ls_ctl_start_pc;
+ struct usb2_page_cache fs_ctl_start_pc;
+ struct usb2_page_cache bulk_start_pc;
+ struct usb2_page_cache last_qh_pc;
+ struct usb2_page_cache last_td_pc;
+
+ struct usb2_page pframes_pg;
+ struct usb2_page isoc_start_pg[UHCI_VFRAMELIST_COUNT];
+ struct usb2_page intr_start_pg[UHCI_IFRAMELIST_COUNT];
+ struct usb2_page ls_ctl_start_pg;
+ struct usb2_page fs_ctl_start_pg;
+ struct usb2_page bulk_start_pg;
+ struct usb2_page last_qh_pg;
+ struct usb2_page last_td_pg;
+};
+
+typedef struct uhci_softc {
+ struct uhci_hw_softc sc_hw;
+ struct usb2_bus sc_bus; /* base device */
+ union uhci_hub_desc sc_hub_desc;
+ struct usb2_sw_transfer sc_root_ctrl;
+ struct usb2_sw_transfer sc_root_intr;
+
+ struct usb2_device *sc_devices[UHCI_MAX_DEVICES];
+ struct uhci_td *sc_isoc_p_last[UHCI_VFRAMELIST_COUNT]; /* pointer to last TD
+ * for isochronous */
+ struct uhci_qh *sc_intr_p_last[UHCI_IFRAMELIST_COUNT]; /* pointer to last QH
+ * for interrupt */
+ struct uhci_qh *sc_ls_ctl_p_last; /* pointer to last QH for low
+ * speed control */
+ struct uhci_qh *sc_fs_ctl_p_last; /* pointer to last QH for full
+ * speed control */
+ struct uhci_qh *sc_bulk_p_last; /* pointer to last QH for bulk */
+ struct uhci_qh *sc_reclaim_qh_p;
+ struct uhci_qh *sc_last_qh_p;
+ struct uhci_td *sc_last_td_p;
+ struct resource *sc_io_res;
+ struct resource *sc_irq_res;
+ void *sc_intr_hdl;
+ device_t sc_dev;
+ bus_size_t sc_io_size;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+
+ uint32_t sc_loops; /* number of QHs that wants looping */
+
+ uint16_t sc_intr_stat[UHCI_IFRAMELIST_COUNT];
+ uint16_t sc_saved_frnum;
+
+ uint8_t sc_addr; /* device address */
+ uint8_t sc_conf; /* device configuration */
+ uint8_t sc_isreset; /* bits set if a root hub is reset */
+ uint8_t sc_isresumed; /* bits set if a port was resumed */
+ uint8_t sc_saved_sof;
+ uint8_t sc_hub_idata[1];
+
+ char sc_vendor[16]; /* vendor string for root hub */
+} uhci_softc_t;
+
+usb2_bus_mem_cb_t uhci_iterate_hw_softc;
+
+usb2_error_t uhci_init(uhci_softc_t *sc);
+void uhci_suspend(uhci_softc_t *sc);
+void uhci_resume(uhci_softc_t *sc);
+void uhci_reset(uhci_softc_t *sc);
+void uhci_interrupt(uhci_softc_t *sc);
+
+#endif /* _UHCI_H_ */
diff --git a/sys/dev/usb/controller/uhci_pci.c b/sys/dev/usb/controller/uhci_pci.c
new file mode 100644
index 0000000..f7f6f9c
--- /dev/null
+++ b/sys/dev/usb/controller/uhci_pci.c
@@ -0,0 +1,443 @@
+/*-
+ * Copyright (c) 1998 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Lennart Augustsson (augustss@carlstedt.se) at
+ * Carlstedt Research & Technology.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the NetBSD
+ * Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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$");
+
+/* Universal Host Controller Interface
+ *
+ * UHCI spec: http://www.intel.com/
+ */
+
+/* The low level controller code for UHCI has been split into
+ * PCI probes and UHCI specific code. This was done to facilitate the
+ * sharing of code between *BSD's
+ */
+
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_defs.h>
+#include <dev/usb/usb.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_util.h>
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/usb_pci.h>
+#include <dev/usb/controller/uhci.h>
+
+#define PCI_UHCI_VENDORID_INTEL 0x8086
+#define PCI_UHCI_VENDORID_VIA 0x1106
+
+/* PIIX4E has no separate stepping */
+
+#define PCI_UHCI_BASE_REG 0x20
+
+static device_probe_t uhci_pci_probe;
+static device_attach_t uhci_pci_attach;
+static device_detach_t uhci_pci_detach;
+static device_suspend_t uhci_pci_suspend;
+static device_resume_t uhci_pci_resume;
+
+static int
+uhci_pci_suspend(device_t self)
+{
+ uhci_softc_t *sc = device_get_softc(self);
+ int err;
+
+ err = bus_generic_suspend(self);
+ if (err) {
+ return (err);
+ }
+ uhci_suspend(sc);
+ return (0);
+}
+
+static int
+uhci_pci_resume(device_t self)
+{
+ uhci_softc_t *sc = device_get_softc(self);
+
+ pci_write_config(self, PCI_LEGSUP, PCI_LEGSUP_USBPIRQDEN, 2);
+
+ uhci_resume(sc);
+
+ bus_generic_resume(self);
+ return (0);
+}
+
+static const char *
+uhci_pci_match(device_t self)
+{
+ uint32_t device_id = pci_get_devid(self);
+
+ switch (device_id) {
+ case 0x26888086:
+ return ("Intel 631XESB/632XESB/3100 USB controller USB-1");
+
+ case 0x26898086:
+ return ("Intel 631XESB/632XESB/3100 USB controller USB-2");
+
+ case 0x268a8086:
+ return ("Intel 631XESB/632XESB/3100 USB controller USB-3");
+
+ case 0x268b8086:
+ return ("Intel 631XESB/632XESB/3100 USB controller USB-4");
+
+ case 0x70208086:
+ return ("Intel 82371SB (PIIX3) USB controller");
+
+ case 0x71128086:
+ return ("Intel 82371AB/EB (PIIX4) USB controller");
+
+ case 0x24128086:
+ return ("Intel 82801AA (ICH) USB controller");
+
+ case 0x24228086:
+ return ("Intel 82801AB (ICH0) USB controller");
+
+ case 0x24428086:
+ return ("Intel 82801BA/BAM (ICH2) USB controller USB-A");
+
+ case 0x24448086:
+ return ("Intel 82801BA/BAM (ICH2) USB controller USB-B");
+
+ case 0x24828086:
+ return ("Intel 82801CA/CAM (ICH3) USB controller USB-A");
+
+ case 0x24848086:
+ return ("Intel 82801CA/CAM (ICH3) USB controller USB-B");
+
+ case 0x24878086:
+ return ("Intel 82801CA/CAM (ICH3) USB controller USB-C");
+
+ case 0x24c28086:
+ return ("Intel 82801DB (ICH4) USB controller USB-A");
+
+ case 0x24c48086:
+ return ("Intel 82801DB (ICH4) USB controller USB-B");
+
+ case 0x24c78086:
+ return ("Intel 82801DB (ICH4) USB controller USB-C");
+
+ case 0x24d28086:
+ return ("Intel 82801EB (ICH5) USB controller USB-A");
+
+ case 0x24d48086:
+ return ("Intel 82801EB (ICH5) USB controller USB-B");
+
+ case 0x24d78086:
+ return ("Intel 82801EB (ICH5) USB controller USB-C");
+
+ case 0x24de8086:
+ return ("Intel 82801EB (ICH5) USB controller USB-D");
+
+ case 0x26588086:
+ return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-A");
+
+ case 0x26598086:
+ return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-B");
+
+ case 0x265a8086:
+ return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-C");
+
+ case 0x265b8086:
+ return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-D");
+
+ case 0x28308086:
+ return ("Intel 82801H (ICH8) USB controller USB-A");
+ case 0x28318086:
+ return ("Intel 82801H (ICH8) USB controller USB-B");
+ case 0x28328086:
+ return ("Intel 82801H (ICH8) USB controller USB-C");
+ case 0x28348086:
+ return ("Intel 82801H (ICH8) USB controller USB-D");
+ case 0x28358086:
+ return ("Intel 82801H (ICH8) USB controller USB-E");
+ case 0x29348086:
+ return ("Intel 82801I (ICH9) USB controller");
+ case 0x29358086:
+ return ("Intel 82801I (ICH9) USB controller");
+ case 0x29368086:
+ return ("Intel 82801I (ICH9) USB controller");
+ case 0x29378086:
+ return ("Intel 82801I (ICH9) USB controller");
+ case 0x29388086:
+ return ("Intel 82801I (ICH9) USB controller");
+ case 0x29398086:
+ return ("Intel 82801I (ICH9) USB controller");
+
+ case 0x719a8086:
+ return ("Intel 82443MX USB controller");
+
+ case 0x76028086:
+ return ("Intel 82372FB/82468GX USB controller");
+
+ case 0x30381106:
+ return ("VIA 83C572 USB controller");
+
+ default:
+ break;
+ }
+
+ if ((pci_get_class(self) == PCIC_SERIALBUS) &&
+ (pci_get_subclass(self) == PCIS_SERIALBUS_USB) &&
+ (pci_get_progif(self) == PCI_INTERFACE_UHCI)) {
+ return ("UHCI (generic) USB controller");
+ }
+ return (NULL);
+}
+
+static int
+uhci_pci_probe(device_t self)
+{
+ const char *desc = uhci_pci_match(self);
+
+ if (desc) {
+ device_set_desc(self, desc);
+ return (0);
+ } else {
+ return (ENXIO);
+ }
+}
+
+static int
+uhci_pci_attach(device_t self)
+{
+ uhci_softc_t *sc = device_get_softc(self);
+ int rid;
+ int err;
+
+ /* initialise some bus fields */
+ sc->sc_bus.parent = self;
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = UHCI_MAX_DEVICES;
+
+ /* get all DMA memory */
+ if (usb2_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self),
+ &uhci_iterate_hw_softc)) {
+ return ENOMEM;
+ }
+ sc->sc_dev = self;
+
+ pci_enable_busmaster(self);
+
+ rid = PCI_UHCI_BASE_REG;
+ sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_IOPORT, &rid,
+ RF_ACTIVE);
+ if (!sc->sc_io_res) {
+ device_printf(self, "Could not map ports\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);
+ sc->sc_io_size = rman_get_size(sc->sc_io_res);
+
+ /* disable interrupts */
+ bus_space_write_2(sc->sc_io_tag, sc->sc_io_hdl, UHCI_INTR, 0);
+
+ 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);
+
+ /*
+ * uhci_pci_match must never return NULL if uhci_pci_probe
+ * succeeded
+ */
+ device_set_desc(sc->sc_bus.bdev, uhci_pci_match(self));
+ switch (pci_get_vendor(self)) {
+ case PCI_UHCI_VENDORID_INTEL:
+ sprintf(sc->sc_vendor, "Intel");
+ break;
+ case PCI_UHCI_VENDORID_VIA:
+ sprintf(sc->sc_vendor, "VIA");
+ break;
+ default:
+ if (bootverbose) {
+ device_printf(self, "(New UHCI DeviceId=0x%08x)\n",
+ pci_get_devid(self));
+ }
+ sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self));
+ }
+
+ switch (pci_read_config(self, PCI_USBREV, 1) & PCI_USB_REV_MASK) {
+ case PCI_USB_REV_PRE_1_0:
+ sc->sc_bus.usbrev = USB_REV_PRE_1_0;
+ break;
+ case PCI_USB_REV_1_0:
+ sc->sc_bus.usbrev = USB_REV_1_0;
+ break;
+ default:
+ /* Quirk for Parallels Desktop 4.0 */
+ device_printf(self, "USB revision is unknown. Assuming v1.1.\n");
+ sc->sc_bus.usbrev = USB_REV_1_1;
+ break;
+ }
+
+#if (__FreeBSD_version >= 700031)
+ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (void *)(void *)uhci_interrupt, sc, &sc->sc_intr_hdl);
+#else
+ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ (void *)(void *)uhci_interrupt, sc, &sc->sc_intr_hdl);
+#endif
+
+ if (err) {
+ device_printf(self, "Could not setup irq, %d\n", err);
+ sc->sc_intr_hdl = NULL;
+ goto error;
+ }
+ /*
+ * Set the PIRQD enable bit and switch off all the others. We don't
+ * want legacy support to interfere with us XXX Does this also mean
+ * that the BIOS won't touch the keyboard anymore if it is connected
+ * to the ports of the root hub?
+ */
+#if USB_DEBUG
+ if (pci_read_config(self, PCI_LEGSUP, 2) != PCI_LEGSUP_USBPIRQDEN) {
+ device_printf(self, "LegSup = 0x%04x\n",
+ pci_read_config(self, PCI_LEGSUP, 2));
+ }
+#endif
+ pci_write_config(self, PCI_LEGSUP, PCI_LEGSUP_USBPIRQDEN, 2);
+
+ err = uhci_init(sc);
+ if (!err) {
+ err = device_probe_and_attach(sc->sc_bus.bdev);
+ }
+ if (err) {
+ device_printf(self, "USB init failed\n");
+ goto error;
+ }
+ return (0);
+
+error:
+ uhci_pci_detach(self);
+ return (ENXIO);
+}
+
+int
+uhci_pci_detach(device_t self)
+{
+ uhci_softc_t *sc = device_get_softc(self);
+ device_t bdev;
+
+ 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_all_children(self);
+
+ /*
+ * disable interrupts that might have been switched on in
+ * uhci_init.
+ */
+ if (sc->sc_io_res) {
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* stop the controller */
+ uhci_reset(sc);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+ }
+ pci_disable_busmaster(self);
+
+ if (sc->sc_irq_res && sc->sc_intr_hdl) {
+ int 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_IOPORT, PCI_UHCI_BASE_REG,
+ sc->sc_io_res);
+ sc->sc_io_res = NULL;
+ }
+ usb2_bus_mem_free_all(&sc->sc_bus, &uhci_iterate_hw_softc);
+
+ return (0);
+}
+
+static driver_t uhci_driver =
+{
+ .name = "uhci",
+ .methods = (device_method_t[]){
+ /* device interface */
+ DEVMETHOD(device_probe, uhci_pci_probe),
+ DEVMETHOD(device_attach, uhci_pci_attach),
+ DEVMETHOD(device_detach, uhci_pci_detach),
+
+ DEVMETHOD(device_suspend, uhci_pci_suspend),
+ DEVMETHOD(device_resume, uhci_pci_resume),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+
+ /* Bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+ {0, 0}
+ },
+ .size = sizeof(struct uhci_softc),
+};
+
+static devclass_t uhci_devclass;
+
+DRIVER_MODULE(uhci, pci, uhci_driver, uhci_devclass, 0, 0);
+DRIVER_MODULE(uhci, cardbus, uhci_driver, uhci_devclass, 0, 0);
+MODULE_DEPEND(uhci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/usb_controller.c b/sys/dev/usb/controller/usb_controller.c
new file mode 100644
index 0000000..b91be6c
--- /dev/null
+++ b/sys/dev/usb/controller/usb_controller.c
@@ -0,0 +1,620 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 2008 Hans Petter Selasky. 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 <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_defs.h>
+#include <dev/usb/usb_error.h>
+#include <dev/usb/usb.h>
+
+#define USB_DEBUG_VAR usb2_ctrl_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_dynamic.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+
+/* function prototypes */
+
+static device_probe_t usb2_probe;
+static device_attach_t usb2_attach;
+static device_detach_t usb2_detach;
+
+static void usb2_attach_sub(device_t, struct usb2_bus *);
+static void usb2_post_init(void *);
+static void usb2_bus_mem_flush_all_cb(struct usb2_bus *,
+ struct usb2_page_cache *, struct usb2_page *, uint32_t,
+ uint32_t);
+static void usb2_bus_mem_alloc_all_cb(struct usb2_bus *,
+ struct usb2_page_cache *, struct usb2_page *, uint32_t,
+ uint32_t);
+static void usb2_bus_mem_free_all_cb(struct usb2_bus *,
+ struct usb2_page_cache *, struct usb2_page *, uint32_t,
+ uint32_t);
+static void usb2_bus_roothub(struct usb2_proc_msg *pm);
+
+/* static variables */
+
+#if USB_DEBUG
+static int usb2_ctrl_debug = 0;
+
+SYSCTL_NODE(_hw_usb2, OID_AUTO, ctrl, CTLFLAG_RW, 0, "USB controller");
+SYSCTL_INT(_hw_usb2_ctrl, OID_AUTO, debug, CTLFLAG_RW, &usb2_ctrl_debug, 0,
+ "Debug level");
+#endif
+
+static uint8_t usb2_post_init_called = 0;
+
+static devclass_t usb2_devclass;
+
+static device_method_t usb2_methods[] = {
+ DEVMETHOD(device_probe, usb2_probe),
+ DEVMETHOD(device_attach, usb2_attach),
+ DEVMETHOD(device_detach, usb2_detach),
+ DEVMETHOD(device_suspend, bus_generic_suspend),
+ DEVMETHOD(device_resume, bus_generic_resume),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+ {0, 0}
+};
+
+static driver_t usb2_driver = {
+ .name = "usbus",
+ .methods = usb2_methods,
+ .size = 0,
+};
+
+DRIVER_MODULE(usbus, ohci, usb2_driver, usb2_devclass, 0, 0);
+DRIVER_MODULE(usbus, uhci, usb2_driver, usb2_devclass, 0, 0);
+DRIVER_MODULE(usbus, ehci, usb2_driver, usb2_devclass, 0, 0);
+DRIVER_MODULE(usbus, at91_udp, usb2_driver, usb2_devclass, 0, 0);
+DRIVER_MODULE(usbus, uss820, usb2_driver, usb2_devclass, 0, 0);
+
+/*------------------------------------------------------------------------*
+ * usb2_probe
+ *
+ * This function is called from "{ehci,ohci,uhci}_pci_attach()".
+ *------------------------------------------------------------------------*/
+static int
+usb2_probe(device_t dev)
+{
+ DPRINTF("\n");
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb2_attach
+ *------------------------------------------------------------------------*/
+static int
+usb2_attach(device_t dev)
+{
+ struct usb2_bus *bus = device_get_ivars(dev);
+
+ DPRINTF("\n");
+
+ if (bus == NULL) {
+ DPRINTFN(0, "USB device has no ivars\n");
+ return (ENXIO);
+ }
+
+ /* delay vfs_mountroot until the bus is explored */
+ bus->bus_roothold = root_mount_hold(device_get_nameunit(dev));
+
+ if (usb2_post_init_called) {
+ mtx_lock(&Giant);
+ usb2_attach_sub(dev, bus);
+ mtx_unlock(&Giant);
+ usb2_needs_explore(bus, 1);
+ }
+ return (0); /* return success */
+}
+
+/*------------------------------------------------------------------------*
+ * usb2_detach
+ *------------------------------------------------------------------------*/
+static int
+usb2_detach(device_t dev)
+{
+ struct usb2_bus *bus = device_get_softc(dev);
+
+ DPRINTF("\n");
+
+ if (bus == NULL) {
+ /* was never setup properly */
+ return (0);
+ }
+ /* Stop power watchdog */
+ usb2_callout_drain(&bus->power_wdog);
+
+ /* Let the USB explore process detach all devices. */
+ if (bus->bus_roothold != NULL) {
+ root_mount_rel(bus->bus_roothold);
+ bus->bus_roothold = NULL;
+ }
+
+ USB_BUS_LOCK(bus);
+ if (usb2_proc_msignal(&bus->explore_proc,
+ &bus->detach_msg[0], &bus->detach_msg[1])) {
+ /* ignore */
+ }
+ /* Wait for detach to complete */
+
+ usb2_proc_mwait(&bus->explore_proc,
+ &bus->detach_msg[0], &bus->detach_msg[1]);
+
+ USB_BUS_UNLOCK(bus);
+
+ /* Get rid of USB callback processes */
+
+ usb2_proc_free(&bus->giant_callback_proc);
+ usb2_proc_free(&bus->non_giant_callback_proc);
+
+ /* Get rid of USB roothub process */
+
+ usb2_proc_free(&bus->roothub_proc);
+
+ /* Get rid of USB explore process */
+
+ usb2_proc_free(&bus->explore_proc);
+
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb2_bus_explore
+ *
+ * This function is used to explore the device tree from the root.
+ *------------------------------------------------------------------------*/
+static void
+usb2_bus_explore(struct usb2_proc_msg *pm)
+{
+ struct usb2_bus *bus;
+ struct usb2_device *udev;
+
+ bus = ((struct usb2_bus_msg *)pm)->bus;
+ udev = bus->devices[USB_ROOT_HUB_ADDR];
+
+ if (udev && udev->hub) {
+
+ if (bus->do_probe) {
+ bus->do_probe = 0;
+ bus->driver_added_refcount++;
+ }
+ if (bus->driver_added_refcount == 0) {
+ /* avoid zero, hence that is memory default */
+ bus->driver_added_refcount = 1;
+ }
+ USB_BUS_UNLOCK(bus);
+
+ mtx_lock(&Giant);
+
+ /*
+ * First update the USB power state!
+ */
+ usb2_bus_powerd(bus);
+
+ /*
+ * Explore the Root USB HUB. This call can sleep,
+ * exiting Giant, which is actually Giant.
+ */
+ (udev->hub->explore) (udev);
+
+ mtx_unlock(&Giant);
+
+ USB_BUS_LOCK(bus);
+ }
+ if (bus->bus_roothold != NULL) {
+ root_mount_rel(bus->bus_roothold);
+ bus->bus_roothold = NULL;
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb2_bus_detach
+ *
+ * This function is used to detach the device tree from the root.
+ *------------------------------------------------------------------------*/
+static void
+usb2_bus_detach(struct usb2_proc_msg *pm)
+{
+ struct usb2_bus *bus;
+ struct usb2_device *udev;
+ device_t dev;
+
+ bus = ((struct usb2_bus_msg *)pm)->bus;
+ udev = bus->devices[USB_ROOT_HUB_ADDR];
+ dev = bus->bdev;
+ /* clear the softc */
+ device_set_softc(dev, NULL);
+ USB_BUS_UNLOCK(bus);
+
+ mtx_lock(&Giant);
+
+ /* detach children first */
+ bus_generic_detach(dev);
+
+ /*
+ * Free USB Root device, but not any sub-devices, hence they
+ * are freed by the caller of this function:
+ */
+ usb2_detach_device(udev, USB_IFACE_INDEX_ANY, 0);
+ usb2_free_device(udev);
+
+ mtx_unlock(&Giant);
+ USB_BUS_LOCK(bus);
+ /* clear bdev variable last */
+ bus->bdev = NULL;
+}
+
+static void
+usb2_power_wdog(void *arg)
+{
+ struct usb2_bus *bus = arg;
+
+ USB_BUS_LOCK_ASSERT(bus, MA_OWNED);
+
+ usb2_callout_reset(&bus->power_wdog,
+ 4 * hz, usb2_power_wdog, arg);
+
+ USB_BUS_UNLOCK(bus);
+
+ usb2_bus_power_update(bus);
+
+ return;
+}
+
+/*------------------------------------------------------------------------*
+ * usb2_bus_attach
+ *
+ * This function attaches USB in context of the explore thread.
+ *------------------------------------------------------------------------*/
+static void
+usb2_bus_attach(struct usb2_proc_msg *pm)
+{
+ struct usb2_bus *bus;
+ struct usb2_device *child;
+ device_t dev;
+ usb2_error_t err;
+ uint8_t speed;
+
+ bus = ((struct usb2_bus_msg *)pm)->bus;
+ dev = bus->bdev;
+
+ DPRINTF("\n");
+
+ switch (bus->usbrev) {
+ case USB_REV_1_0:
+ speed = USB_SPEED_FULL;
+ device_printf(bus->bdev, "12Mbps Full Speed USB v1.0\n");
+ break;
+
+ case USB_REV_1_1:
+ speed = USB_SPEED_FULL;
+ device_printf(bus->bdev, "12Mbps Full Speed USB v1.1\n");
+ break;
+
+ case USB_REV_2_0:
+ speed = USB_SPEED_HIGH;
+ device_printf(bus->bdev, "480Mbps High Speed USB v2.0\n");
+ break;
+
+ case USB_REV_2_5:
+ speed = USB_SPEED_VARIABLE;
+ device_printf(bus->bdev, "480Mbps Wireless USB v2.5\n");
+ break;
+
+ default:
+ device_printf(bus->bdev, "Unsupported USB revision!\n");
+ return;
+ }
+
+ USB_BUS_UNLOCK(bus);
+ mtx_lock(&Giant); /* XXX not required by USB */
+
+ /* Allocate the Root USB device */
+
+ child = usb2_alloc_device(bus->bdev, bus, NULL, 0, 0, 1,
+ speed, USB_MODE_HOST);
+ if (child) {
+ err = usb2_probe_and_attach(child,
+ USB_IFACE_INDEX_ANY);
+ if (!err) {
+ if (!bus->devices[USB_ROOT_HUB_ADDR]->hub) {
+ err = USB_ERR_NO_ROOT_HUB;
+ }
+ }
+ } else {
+ err = USB_ERR_NOMEM;
+ }
+
+ mtx_unlock(&Giant);
+ USB_BUS_LOCK(bus);
+
+ if (err) {
+ device_printf(bus->bdev, "Root HUB problem, error=%s\n",
+ usb2_errstr(err));
+ }
+
+ /* set softc - we are ready */
+ device_set_softc(dev, bus);
+
+ /* start watchdog - this function will unlock the BUS lock ! */
+ usb2_power_wdog(bus);
+
+ /* need to return locked */
+ USB_BUS_LOCK(bus);
+}
+
+/*------------------------------------------------------------------------*
+ * usb2_attach_sub
+ *
+ * This function creates a thread which runs the USB attach code. It
+ * is factored out, hence it can be called at two different places in
+ * time. During bootup this function is called from
+ * "usb2_post_init". During hot-plug it is called directly from the
+ * "usb2_attach()" method.
+ *------------------------------------------------------------------------*/
+static void
+usb2_attach_sub(device_t dev, struct usb2_bus *bus)
+{
+ const char *pname = device_get_nameunit(dev);
+
+ /* Initialise USB process messages */
+ bus->explore_msg[0].hdr.pm_callback = &usb2_bus_explore;
+ bus->explore_msg[0].bus = bus;
+ bus->explore_msg[1].hdr.pm_callback = &usb2_bus_explore;
+ bus->explore_msg[1].bus = bus;
+
+ bus->detach_msg[0].hdr.pm_callback = &usb2_bus_detach;
+ bus->detach_msg[0].bus = bus;
+ bus->detach_msg[1].hdr.pm_callback = &usb2_bus_detach;
+ bus->detach_msg[1].bus = bus;
+
+ bus->attach_msg[0].hdr.pm_callback = &usb2_bus_attach;
+ bus->attach_msg[0].bus = bus;
+ bus->attach_msg[1].hdr.pm_callback = &usb2_bus_attach;
+ bus->attach_msg[1].bus = bus;
+
+ bus->roothub_msg[0].hdr.pm_callback = &usb2_bus_roothub;
+ bus->roothub_msg[0].bus = bus;
+ bus->roothub_msg[1].hdr.pm_callback = &usb2_bus_roothub;
+ bus->roothub_msg[1].bus = bus;
+
+ /* Create USB explore, roothub and callback processes */
+
+ if (usb2_proc_create(&bus->giant_callback_proc,
+ &bus->bus_mtx, pname, USB_PRI_MED)) {
+ printf("WARNING: Creation of USB Giant "
+ "callback process failed.\n");
+ } else if (usb2_proc_create(&bus->non_giant_callback_proc,
+ &bus->bus_mtx, pname, USB_PRI_HIGH)) {
+ printf("WARNING: Creation of USB non-Giant "
+ "callback process failed.\n");
+ } else if (usb2_proc_create(&bus->roothub_proc,
+ &bus->bus_mtx, pname, USB_PRI_HIGH)) {
+ printf("WARNING: Creation of USB roothub "
+ "process failed.\n");
+ } else if (usb2_proc_create(&bus->explore_proc,
+ &bus->bus_mtx, pname, USB_PRI_MED)) {
+ printf("WARNING: Creation of USB explore "
+ "process failed.\n");
+ } else {
+ /* Get final attach going */
+ USB_BUS_LOCK(bus);
+ if (usb2_proc_msignal(&bus->explore_proc,
+ &bus->attach_msg[0], &bus->attach_msg[1])) {
+ /* ignore */
+ }
+ USB_BUS_UNLOCK(bus);
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb2_post_init
+ *
+ * This function is called to attach all USB busses that were found
+ * during bootup.
+ *------------------------------------------------------------------------*/
+static void
+usb2_post_init(void *arg)
+{
+ struct usb2_bus *bus;
+ devclass_t dc;
+ device_t dev;
+ int max;
+ int n;
+
+ mtx_lock(&Giant);
+
+ usb2_devclass_ptr = devclass_find("usbus");
+
+ dc = usb2_devclass_ptr;
+ if (dc) {
+ max = devclass_get_maxunit(dc) + 1;
+ for (n = 0; n != max; n++) {
+ dev = devclass_get_device(dc, n);
+ if (dev && device_is_attached(dev)) {
+ bus = device_get_ivars(dev);
+ if (bus) {
+ mtx_lock(&Giant);
+ usb2_attach_sub(dev, bus);
+ mtx_unlock(&Giant);
+ }
+ }
+ }
+ } else {
+ DPRINTFN(0, "no devclass\n");
+ }
+ usb2_post_init_called = 1;
+
+ /* explore all USB busses in parallell */
+
+ usb2_needs_explore_all();
+
+ mtx_unlock(&Giant);
+}
+
+SYSINIT(usb2_post_init, SI_SUB_KICK_SCHEDULER, SI_ORDER_ANY, usb2_post_init, NULL);
+SYSUNINIT(usb2_bus_unload, SI_SUB_KLD, SI_ORDER_ANY, usb2_bus_unload, NULL);
+
+/*------------------------------------------------------------------------*
+ * usb2_bus_mem_flush_all_cb
+ *------------------------------------------------------------------------*/
+static void
+usb2_bus_mem_flush_all_cb(struct usb2_bus *bus, struct usb2_page_cache *pc,
+ struct usb2_page *pg, uint32_t size, uint32_t align)
+{
+ usb2_pc_cpu_flush(pc);
+}
+
+/*------------------------------------------------------------------------*
+ * usb2_bus_mem_flush_all - factored out code
+ *------------------------------------------------------------------------*/
+void
+usb2_bus_mem_flush_all(struct usb2_bus *bus, usb2_bus_mem_cb_t *cb)
+{
+ if (cb) {
+ cb(bus, &usb2_bus_mem_flush_all_cb);
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb2_bus_mem_alloc_all_cb
+ *------------------------------------------------------------------------*/
+static void
+usb2_bus_mem_alloc_all_cb(struct usb2_bus *bus, struct usb2_page_cache *pc,
+ struct usb2_page *pg, uint32_t size, uint32_t align)
+{
+ /* need to initialize the page cache */
+ pc->tag_parent = bus->dma_parent_tag;
+
+ if (usb2_pc_alloc_mem(pc, pg, size, align)) {
+ bus->alloc_failed = 1;
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb2_bus_mem_alloc_all - factored out code
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+uint8_t
+usb2_bus_mem_alloc_all(struct usb2_bus *bus, bus_dma_tag_t dmat,
+ usb2_bus_mem_cb_t *cb)
+{
+ bus->alloc_failed = 0;
+
+ mtx_init(&bus->bus_mtx, device_get_nameunit(bus->parent),
+ NULL, MTX_DEF | MTX_RECURSE);
+
+ usb2_callout_init_mtx(&bus->power_wdog,
+ &bus->bus_mtx, CALLOUT_RETURNUNLOCKED);
+
+ TAILQ_INIT(&bus->intr_q.head);
+
+ usb2_dma_tag_setup(bus->dma_parent_tag, bus->dma_tags,
+ dmat, &bus->bus_mtx, NULL, NULL, 32, USB_BUS_DMA_TAG_MAX);
+
+ if ((bus->devices_max > USB_MAX_DEVICES) ||
+ (bus->devices_max < USB_MIN_DEVICES) ||
+ (bus->devices == NULL)) {
+ DPRINTFN(0, "Devices field has not been "
+ "initialised properly!\n");
+ bus->alloc_failed = 1; /* failure */
+ }
+ if (cb) {
+ cb(bus, &usb2_bus_mem_alloc_all_cb);
+ }
+ if (bus->alloc_failed) {
+ usb2_bus_mem_free_all(bus, cb);
+ }
+ return (bus->alloc_failed);
+}
+
+/*------------------------------------------------------------------------*
+ * usb2_bus_mem_free_all_cb
+ *------------------------------------------------------------------------*/
+static void
+usb2_bus_mem_free_all_cb(struct usb2_bus *bus, struct usb2_page_cache *pc,
+ struct usb2_page *pg, uint32_t size, uint32_t align)
+{
+ usb2_pc_free_mem(pc);
+}
+
+/*------------------------------------------------------------------------*
+ * usb2_bus_mem_free_all - factored out code
+ *------------------------------------------------------------------------*/
+void
+usb2_bus_mem_free_all(struct usb2_bus *bus, usb2_bus_mem_cb_t *cb)
+{
+ if (cb) {
+ cb(bus, &usb2_bus_mem_free_all_cb);
+ }
+ usb2_dma_tag_unsetup(bus->dma_parent_tag);
+
+ mtx_destroy(&bus->bus_mtx);
+}
+
+/*------------------------------------------------------------------------*
+ * usb2_bus_roothub
+ *
+ * This function is used to execute roothub control requests on the
+ * roothub and is called from the roothub process.
+ *------------------------------------------------------------------------*/
+static void
+usb2_bus_roothub(struct usb2_proc_msg *pm)
+{
+ struct usb2_bus *bus;
+
+ bus = ((struct usb2_bus_msg *)pm)->bus;
+
+ USB_BUS_LOCK_ASSERT(bus, MA_OWNED);
+
+ (bus->methods->roothub_exec) (bus);
+}
+
+/*------------------------------------------------------------------------*
+ * usb2_bus_roothub_exec
+ *
+ * This function is used to schedule the "roothub_done" bus callback
+ * method. The bus lock must be locked when calling this function.
+ *------------------------------------------------------------------------*/
+void
+usb2_bus_roothub_exec(struct usb2_bus *bus)
+{
+ USB_BUS_LOCK_ASSERT(bus, MA_OWNED);
+
+ if (usb2_proc_msignal(&bus->roothub_proc,
+ &bus->roothub_msg[0], &bus->roothub_msg[1])) {
+ /* ignore */
+ }
+}
diff --git a/sys/dev/usb/controller/uss820dci.c b/sys/dev/usb/controller/uss820dci.c
new file mode 100644
index 0000000..2adc4e3
--- /dev/null
+++ b/sys/dev/usb/controller/uss820dci.c
@@ -0,0 +1,2489 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 2008 Hans Petter Selasky <hselasky@freebsd.org>
+ * 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.
+ */
+
+/*
+ * This file contains the driver for the USS820 series USB Device
+ * Controller
+ *
+ * NOTE: The datasheet does not document everything!
+ */
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_revision.h>
+#include <dev/usb/usb_error.h>
+#include <dev/usb/usb_defs.h>
+
+#define USB_DEBUG_VAR uss820dcidebug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/uss820dci.h>
+
+#define USS820_DCI_BUS2SC(bus) \
+ ((struct uss820dci_softc *)(((uint8_t *)(bus)) - \
+ USB_P2U(&(((struct uss820dci_softc *)0)->sc_bus))))
+
+#define USS820_DCI_PC2SC(pc) \
+ USS820_DCI_BUS2SC((pc)->tag_parent->info->bus)
+
+#if USB_DEBUG
+static int uss820dcidebug = 0;
+
+SYSCTL_NODE(_hw_usb2, OID_AUTO, uss820dci, CTLFLAG_RW, 0, "USB uss820dci");
+SYSCTL_INT(_hw_usb2_uss820dci, OID_AUTO, debug, CTLFLAG_RW,
+ &uss820dcidebug, 0, "uss820dci debug level");
+#endif
+
+#define USS820_DCI_INTR_ENDPT 1
+
+/* prototypes */
+
+struct usb2_bus_methods uss820dci_bus_methods;
+struct usb2_pipe_methods uss820dci_device_bulk_methods;
+struct usb2_pipe_methods uss820dci_device_ctrl_methods;
+struct usb2_pipe_methods uss820dci_device_intr_methods;
+struct usb2_pipe_methods uss820dci_device_isoc_fs_methods;
+struct usb2_pipe_methods uss820dci_root_ctrl_methods;
+struct usb2_pipe_methods uss820dci_root_intr_methods;
+
+static uss820dci_cmd_t uss820dci_setup_rx;
+static uss820dci_cmd_t uss820dci_data_rx;
+static uss820dci_cmd_t uss820dci_data_tx;
+static uss820dci_cmd_t uss820dci_data_tx_sync;
+static void uss820dci_device_done(struct usb2_xfer *, usb2_error_t);
+static void uss820dci_do_poll(struct usb2_bus *);
+static void uss820dci_root_ctrl_poll(struct uss820dci_softc *);
+static void uss820dci_standard_done(struct usb2_xfer *);
+static void uss820dci_intr_set(struct usb2_xfer *, uint8_t);
+static void uss820dci_update_shared_1(struct uss820dci_softc *, uint8_t,
+ uint8_t, uint8_t);
+
+static usb2_sw_transfer_func_t uss820dci_root_intr_done;
+static usb2_sw_transfer_func_t uss820dci_root_ctrl_done;
+
+/*
+ * Here is a list of what the USS820D chip can support. The main
+ * limitation is that the sum of the buffer sizes must be less than
+ * 1120 bytes.
+ */
+static const struct usb2_hw_ep_profile
+ uss820dci_ep_profile[] = {
+
+ [0] = {
+ .max_in_frame_size = 32,
+ .max_out_frame_size = 32,
+ .is_simplex = 0,
+ .support_control = 1,
+ },
+ [1] = {
+ .max_in_frame_size = 64,
+ .max_out_frame_size = 64,
+ .is_simplex = 0,
+ .support_multi_buffer = 1,
+ .support_bulk = 1,
+ .support_interrupt = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+ [2] = {
+ .max_in_frame_size = 8,
+ .max_out_frame_size = 8,
+ .is_simplex = 0,
+ .support_multi_buffer = 1,
+ .support_bulk = 1,
+ .support_interrupt = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+ [3] = {
+ .max_in_frame_size = 256,
+ .max_out_frame_size = 256,
+ .is_simplex = 0,
+ .support_multi_buffer = 1,
+ .support_isochronous = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+};
+
+static void
+uss820dci_update_shared_1(struct uss820dci_softc *sc, uint8_t reg,
+ uint8_t keep_mask, uint8_t set_mask)
+{
+ uint8_t temp;
+
+ USS820_WRITE_1(sc, USS820_PEND, 1);
+ temp = USS820_READ_1(sc, reg);
+ temp &= (keep_mask);
+ temp |= (set_mask);
+ USS820_WRITE_1(sc, reg, temp);
+ USS820_WRITE_1(sc, USS820_PEND, 0);
+}
+
+static void
+uss820dci_get_hw_ep_profile(struct usb2_device *udev,
+ const struct usb2_hw_ep_profile **ppf, uint8_t ep_addr)
+{
+ if (ep_addr == 0) {
+ *ppf = uss820dci_ep_profile + 0;
+ } else if (ep_addr < 5) {
+ *ppf = uss820dci_ep_profile + 1;
+ } else if (ep_addr < 7) {
+ *ppf = uss820dci_ep_profile + 2;
+ } else if (ep_addr == 7) {
+ *ppf = uss820dci_ep_profile + 3;
+ } else {
+ *ppf = NULL;
+ }
+}
+
+static void
+uss820dci_pull_up(struct uss820dci_softc *sc)
+{
+ uint8_t temp;
+
+ /* pullup D+, if possible */
+
+ if (!sc->sc_flags.d_pulled_up &&
+ sc->sc_flags.port_powered) {
+ sc->sc_flags.d_pulled_up = 1;
+
+ DPRINTF("\n");
+
+ temp = USS820_READ_1(sc, USS820_MCSR);
+ temp |= USS820_MCSR_DPEN;
+ USS820_WRITE_1(sc, USS820_MCSR, temp);
+ }
+}
+
+static void
+uss820dci_pull_down(struct uss820dci_softc *sc)
+{
+ uint8_t temp;
+
+ /* pulldown D+, if possible */
+
+ if (sc->sc_flags.d_pulled_up) {
+ sc->sc_flags.d_pulled_up = 0;
+
+ DPRINTF("\n");
+
+ temp = USS820_READ_1(sc, USS820_MCSR);
+ temp &= ~USS820_MCSR_DPEN;
+ USS820_WRITE_1(sc, USS820_MCSR, temp);
+ }
+}
+
+static void
+uss820dci_wakeup_peer(struct uss820dci_softc *sc)
+{
+ if (!(sc->sc_flags.status_suspend)) {
+ return;
+ }
+ DPRINTFN(0, "not supported\n");
+}
+
+static void
+uss820dci_set_address(struct uss820dci_softc *sc, uint8_t addr)
+{
+ DPRINTFN(5, "addr=%d\n", addr);
+
+ USS820_WRITE_1(sc, USS820_FADDR, addr);
+}
+
+static uint8_t
+uss820dci_setup_rx(struct uss820dci_td *td)
+{
+ struct uss820dci_softc *sc;
+ struct usb2_device_request req;
+ uint16_t count;
+ uint8_t rx_stat;
+ uint8_t temp;
+
+ /* select the correct endpoint */
+ bus_space_write_1(td->io_tag, td->io_hdl,
+ td->ep_reg, td->ep_index);
+
+ /* read out FIFO status */
+ rx_stat = bus_space_read_1(td->io_tag, td->io_hdl,
+ td->rx_stat_reg);
+
+ /* get pointer to softc */
+ sc = USS820_DCI_PC2SC(td->pc);
+
+ DPRINTFN(5, "rx_stat=0x%02x rem=%u\n", rx_stat, td->remainder);
+
+ if (!(rx_stat & USS820_RXSTAT_RXSETUP)) {
+ /* abort any ongoing transfer */
+ if (!td->did_stall) {
+ DPRINTFN(5, "stalling\n");
+
+ /* set stall */
+
+ uss820dci_update_shared_1(sc, USS820_EPCON, 0xFF,
+ (USS820_EPCON_TXSTL | USS820_EPCON_RXSTL));
+
+ td->did_stall = 1;
+ }
+ goto not_complete;
+ }
+ /* clear stall and all I/O */
+ uss820dci_update_shared_1(sc, USS820_EPCON,
+ 0xFF ^ (USS820_EPCON_TXSTL |
+ USS820_EPCON_RXSTL |
+ USS820_EPCON_RXIE |
+ USS820_EPCON_TXOE), 0);
+
+ /* clear end overwrite flag */
+ uss820dci_update_shared_1(sc, USS820_RXSTAT,
+ 0xFF ^ USS820_RXSTAT_EDOVW, 0);
+
+ /* get the packet byte count */
+ count = bus_space_read_1(td->io_tag, td->io_hdl,
+ td->rx_count_low_reg);
+ count |= (bus_space_read_1(td->io_tag, td->io_hdl,
+ td->rx_count_high_reg) << 8);
+ count &= 0x3FF;
+
+ /* verify data length */
+ if (count != td->remainder) {
+ DPRINTFN(0, "Invalid SETUP packet "
+ "length, %d bytes\n", count);
+ goto not_complete;
+ }
+ if (count != sizeof(req)) {
+ DPRINTFN(0, "Unsupported SETUP packet "
+ "length, %d bytes\n", count);
+ goto not_complete;
+ }
+ /* receive data */
+ bus_space_read_multi_1(td->io_tag, td->io_hdl,
+ td->rx_fifo_reg, (void *)&req, sizeof(req));
+
+ /* read out FIFO status */
+ rx_stat = bus_space_read_1(td->io_tag, td->io_hdl,
+ td->rx_stat_reg);
+
+ if (rx_stat & (USS820_RXSTAT_EDOVW |
+ USS820_RXSTAT_STOVW)) {
+ DPRINTF("new SETUP packet received\n");
+ return (1); /* not complete */
+ }
+ /* clear receive setup bit */
+ uss820dci_update_shared_1(sc, USS820_RXSTAT,
+ 0xFF ^ (USS820_RXSTAT_RXSETUP |
+ USS820_RXSTAT_EDOVW |
+ USS820_RXSTAT_STOVW), 0);
+
+ /* set RXFFRC bit */
+ temp = bus_space_read_1(td->io_tag, td->io_hdl,
+ td->rx_cntl_reg);
+ temp |= USS820_RXCON_RXFFRC;
+ bus_space_write_1(td->io_tag, td->io_hdl,
+ td->rx_cntl_reg, temp);
+
+ /* copy data into real buffer */
+ usb2_copy_in(td->pc, 0, &req, sizeof(req));
+
+ td->offset = sizeof(req);
+ td->remainder = 0;
+
+ /* sneak peek the set address */
+ if ((req.bmRequestType == UT_WRITE_DEVICE) &&
+ (req.bRequest == UR_SET_ADDRESS)) {
+ sc->sc_dv_addr = req.wValue[0] & 0x7F;
+ } else {
+ sc->sc_dv_addr = 0xFF;
+ }
+ return (0); /* complete */
+
+not_complete:
+ /* clear end overwrite flag, if any */
+ if (rx_stat & USS820_RXSTAT_RXSETUP) {
+ uss820dci_update_shared_1(sc, USS820_RXSTAT,
+ 0xFF ^ (USS820_RXSTAT_EDOVW |
+ USS820_RXSTAT_STOVW |
+ USS820_RXSTAT_RXSETUP), 0);
+ }
+ return (1); /* not complete */
+
+}
+
+static uint8_t
+uss820dci_data_rx(struct uss820dci_td *td)
+{
+ struct usb2_page_search buf_res;
+ uint16_t count;
+ uint8_t rx_flag;
+ uint8_t rx_stat;
+ uint8_t rx_cntl;
+ uint8_t to;
+ uint8_t got_short;
+
+ to = 2; /* don't loop forever! */
+ got_short = 0;
+
+ /* select the correct endpoint */
+ bus_space_write_1(td->io_tag, td->io_hdl, td->ep_reg, td->ep_index);
+
+ /* check if any of the FIFO banks have data */
+repeat:
+ /* read out FIFO flag */
+ rx_flag = bus_space_read_1(td->io_tag, td->io_hdl,
+ td->rx_flag_reg);
+ /* read out FIFO status */
+ rx_stat = bus_space_read_1(td->io_tag, td->io_hdl,
+ td->rx_stat_reg);
+
+ DPRINTFN(5, "rx_stat=0x%02x rx_flag=0x%02x rem=%u\n",
+ rx_stat, rx_flag, td->remainder);
+
+ if (rx_stat & (USS820_RXSTAT_RXSETUP |
+ USS820_RXSTAT_RXSOVW |
+ USS820_RXSTAT_EDOVW)) {
+ if (td->remainder == 0) {
+ /*
+ * We are actually complete and have
+ * received the next SETUP
+ */
+ DPRINTFN(5, "faking complete\n");
+ return (0); /* complete */
+ }
+ /*
+ * USB Host Aborted the transfer.
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ /* check for errors */
+ if (rx_flag & (USS820_RXFLG_RXOVF |
+ USS820_RXFLG_RXURF)) {
+ DPRINTFN(5, "overflow or underflow\n");
+ /* should not happen */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ /* check status */
+ if (!(rx_flag & (USS820_RXFLG_RXFIF0 |
+ USS820_RXFLG_RXFIF1))) {
+
+ /* read out EPCON register */
+ /* enable RX input */
+ if (!td->did_stall) {
+ uss820dci_update_shared_1(USS820_DCI_PC2SC(td->pc),
+ USS820_EPCON, 0xFF, USS820_EPCON_RXIE);
+ td->did_stall = 1;
+ }
+ return (1); /* not complete */
+ }
+ /* get the packet byte count */
+ count = bus_space_read_1(td->io_tag, td->io_hdl,
+ td->rx_count_low_reg);
+
+ count |= (bus_space_read_1(td->io_tag, td->io_hdl,
+ td->rx_count_high_reg) << 8);
+ count &= 0x3FF;
+
+ DPRINTFN(5, "count=0x%04x\n", count);
+
+ /* verify the packet byte count */
+ if (count != td->max_packet_size) {
+ if (count < td->max_packet_size) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ got_short = 1;
+ } else {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ }
+ /* verify the packet byte count */
+ if (count > td->remainder) {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ while (count > 0) {
+ usb2_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* receive data */
+ bus_space_read_multi_1(td->io_tag, td->io_hdl,
+ td->rx_fifo_reg, buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* set RXFFRC bit */
+ rx_cntl = bus_space_read_1(td->io_tag, td->io_hdl,
+ td->rx_cntl_reg);
+ rx_cntl |= USS820_RXCON_RXFFRC;
+ bus_space_write_1(td->io_tag, td->io_hdl,
+ td->rx_cntl_reg, rx_cntl);
+
+ /* check if we are complete */
+ if ((td->remainder == 0) || got_short) {
+ if (td->short_pkt) {
+ /* we are complete */
+ return (0);
+ }
+ /* else need to receive a zero length packet */
+ }
+ if (--to) {
+ goto repeat;
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+uss820dci_data_tx(struct uss820dci_td *td)
+{
+ struct usb2_page_search buf_res;
+ uint16_t count;
+ uint16_t count_copy;
+ uint8_t rx_stat;
+ uint8_t tx_flag;
+ uint8_t to;
+
+ /* select the correct endpoint */
+ bus_space_write_1(td->io_tag, td->io_hdl,
+ td->ep_reg, td->ep_index);
+
+ to = 2; /* don't loop forever! */
+
+repeat:
+ /* read out TX FIFO flags */
+ tx_flag = bus_space_read_1(td->io_tag, td->io_hdl,
+ td->tx_flag_reg);
+
+ /* read out RX FIFO status last */
+ rx_stat = bus_space_read_1(td->io_tag, td->io_hdl,
+ td->rx_stat_reg);
+
+ DPRINTFN(5, "rx_stat=0x%02x tx_flag=0x%02x rem=%u\n",
+ rx_stat, tx_flag, td->remainder);
+
+ if (rx_stat & (USS820_RXSTAT_RXSETUP |
+ USS820_RXSTAT_RXSOVW |
+ USS820_RXSTAT_EDOVW)) {
+ /*
+ * The current transfer was aborted
+ * by the USB Host
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ if (tx_flag & (USS820_TXFLG_TXOVF |
+ USS820_TXFLG_TXURF)) {
+ td->error = 1;
+ return (0); /* complete */
+ }
+ if (tx_flag & USS820_TXFLG_TXFIF0) {
+ if (tx_flag & USS820_TXFLG_TXFIF1) {
+ return (1); /* not complete */
+ }
+ }
+ if ((!td->support_multi_buffer) &&
+ (tx_flag & (USS820_TXFLG_TXFIF0 |
+ USS820_TXFLG_TXFIF1))) {
+ return (1); /* not complete */
+ }
+ count = td->max_packet_size;
+ if (td->remainder < count) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ count = td->remainder;
+ }
+ count_copy = count;
+ while (count > 0) {
+
+ usb2_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* transmit data */
+ bus_space_write_multi_1(td->io_tag, td->io_hdl,
+ td->tx_fifo_reg, buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* post-write high packet byte count first */
+ bus_space_write_1(td->io_tag, td->io_hdl,
+ td->tx_count_high_reg, count_copy >> 8);
+
+ /* post-write low packet byte count last */
+ bus_space_write_1(td->io_tag, td->io_hdl,
+ td->tx_count_low_reg, count_copy);
+
+ /*
+ * Enable TX output, which must happen after that we have written
+ * data into the FIFO. This is undocumented.
+ */
+ if (!td->did_stall) {
+ uss820dci_update_shared_1(USS820_DCI_PC2SC(td->pc),
+ USS820_EPCON, 0xFF, USS820_EPCON_TXOE);
+ td->did_stall = 1;
+ }
+ /* check remainder */
+ if (td->remainder == 0) {
+ if (td->short_pkt) {
+ return (0); /* complete */
+ }
+ /* else we need to transmit a short packet */
+ }
+ if (--to) {
+ goto repeat;
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+uss820dci_data_tx_sync(struct uss820dci_td *td)
+{
+ struct uss820dci_softc *sc;
+ uint8_t rx_stat;
+ uint8_t tx_flag;
+
+ /* select the correct endpoint */
+ bus_space_write_1(td->io_tag, td->io_hdl,
+ td->ep_reg, td->ep_index);
+
+ /* read out TX FIFO flag */
+ tx_flag = bus_space_read_1(td->io_tag, td->io_hdl,
+ td->tx_flag_reg);
+
+ /* read out RX FIFO status last */
+ rx_stat = bus_space_read_1(td->io_tag, td->io_hdl,
+ td->rx_stat_reg);
+
+ DPRINTFN(5, "rx_stat=0x%02x rem=%u\n", rx_stat, td->remainder);
+
+ if (rx_stat & (USS820_RXSTAT_RXSETUP |
+ USS820_RXSTAT_RXSOVW |
+ USS820_RXSTAT_EDOVW)) {
+ DPRINTFN(5, "faking complete\n");
+ /* Race condition */
+ return (0); /* complete */
+ }
+ DPRINTFN(5, "tx_flag=0x%02x rem=%u\n",
+ tx_flag, td->remainder);
+
+ if (tx_flag & (USS820_TXFLG_TXOVF |
+ USS820_TXFLG_TXURF)) {
+ td->error = 1;
+ return (0); /* complete */
+ }
+ if (tx_flag & (USS820_TXFLG_TXFIF0 |
+ USS820_TXFLG_TXFIF1)) {
+ return (1); /* not complete */
+ }
+ sc = USS820_DCI_PC2SC(td->pc);
+ if (sc->sc_dv_addr != 0xFF) {
+ /* write function address */
+ uss820dci_set_address(sc, sc->sc_dv_addr);
+ }
+ return (0); /* complete */
+}
+
+static uint8_t
+uss820dci_xfer_do_fifo(struct usb2_xfer *xfer)
+{
+ struct uss820dci_td *td;
+
+ DPRINTFN(9, "\n");
+
+ td = xfer->td_transfer_cache;
+ while (1) {
+ if ((td->func) (td)) {
+ /* operation in progress */
+ break;
+ }
+ if (((void *)td) == xfer->td_transfer_last) {
+ goto done;
+ }
+ if (td->error) {
+ goto done;
+ } else if (td->remainder > 0) {
+ /*
+ * We had a short transfer. If there is no alternate
+ * next, stop processing !
+ */
+ if (!td->alt_next) {
+ goto done;
+ }
+ }
+ /*
+ * Fetch the next transfer descriptor.
+ */
+ td = td->obj_next;
+ xfer->td_transfer_cache = td;
+ }
+ return (1); /* not complete */
+
+done:
+ /* compute all actual lengths */
+
+ uss820dci_standard_done(xfer);
+
+ return (0); /* complete */
+}
+
+static void
+uss820dci_interrupt_poll(struct uss820dci_softc *sc)
+{
+ struct usb2_xfer *xfer;
+
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ if (!uss820dci_xfer_do_fifo(xfer)) {
+ /* queue has been modified */
+ goto repeat;
+ }
+ }
+}
+
+static void
+uss820dci_wait_suspend(struct uss820dci_softc *sc, uint8_t on)
+{
+ uint8_t scr;
+ uint8_t scratch;
+
+ scr = USS820_READ_1(sc, USS820_SCR);
+ scratch = USS820_READ_1(sc, USS820_SCRATCH);
+
+ if (on) {
+ scr |= USS820_SCR_IE_SUSP;
+ scratch &= ~USS820_SCRATCH_IE_RESUME;
+ } else {
+ scr &= ~USS820_SCR_IE_SUSP;
+ scratch |= USS820_SCRATCH_IE_RESUME;
+ }
+
+ USS820_WRITE_1(sc, USS820_SCR, scr);
+ USS820_WRITE_1(sc, USS820_SCRATCH, scratch);
+}
+
+void
+uss820dci_interrupt(struct uss820dci_softc *sc)
+{
+ uint8_t ssr;
+ uint8_t event;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ ssr = USS820_READ_1(sc, USS820_SSR);
+
+ ssr &= (USS820_SSR_SUSPEND |
+ USS820_SSR_RESUME |
+ USS820_SSR_RESET);
+
+ /* acknowledge all interrupts */
+
+ uss820dci_update_shared_1(sc, USS820_SSR, 0, 0);
+
+ /* check for any bus state change interrupts */
+
+ if (ssr) {
+
+ event = 0;
+
+ if (ssr & USS820_SSR_RESET) {
+ sc->sc_flags.status_bus_reset = 1;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ /* disable resume interrupt */
+ uss820dci_wait_suspend(sc, 1);
+
+ event = 1;
+ }
+ /*
+ * If "RESUME" and "SUSPEND" is set at the same time
+ * we interpret that like "RESUME". Resume is set when
+ * there is at least 3 milliseconds of inactivity on
+ * the USB BUS.
+ */
+ if (ssr & USS820_SSR_RESUME) {
+ if (sc->sc_flags.status_suspend) {
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 1;
+ /* disable resume interrupt */
+ uss820dci_wait_suspend(sc, 1);
+ event = 1;
+ }
+ } else if (ssr & USS820_SSR_SUSPEND) {
+ if (!sc->sc_flags.status_suspend) {
+ sc->sc_flags.status_suspend = 1;
+ sc->sc_flags.change_suspend = 1;
+ /* enable resume interrupt */
+ uss820dci_wait_suspend(sc, 0);
+ event = 1;
+ }
+ }
+ if (event) {
+
+ DPRINTF("real bus interrupt 0x%02x\n", ssr);
+
+ /* complete root HUB interrupt endpoint */
+
+ usb2_sw_transfer(&sc->sc_root_intr,
+ &uss820dci_root_intr_done);
+ }
+ }
+ /* acknowledge all SBI interrupts */
+ uss820dci_update_shared_1(sc, USS820_SBI, 0, 0);
+
+ /* acknowledge all SBI1 interrupts */
+ uss820dci_update_shared_1(sc, USS820_SBI1, 0, 0);
+
+ /* poll all active transfers */
+ uss820dci_interrupt_poll(sc);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+uss820dci_setup_standard_chain_sub(struct uss820_std_temp *temp)
+{
+ struct uss820dci_td *td;
+
+ /* get current Transfer Descriptor */
+ td = temp->td_next;
+ temp->td = td;
+
+ /* prepare for next TD */
+ temp->td_next = td->obj_next;
+
+ /* fill out the Transfer Descriptor */
+ td->func = temp->func;
+ td->pc = temp->pc;
+ td->offset = temp->offset;
+ td->remainder = temp->len;
+ td->error = 0;
+ td->did_stall = 0;
+ td->short_pkt = temp->short_pkt;
+ td->alt_next = temp->setup_alt_next;
+}
+
+static void
+uss820dci_setup_standard_chain(struct usb2_xfer *xfer)
+{
+ struct uss820_std_temp temp;
+ struct uss820dci_softc *sc;
+ struct uss820dci_td *td;
+ uint32_t x;
+ uint8_t ep_no;
+
+ DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpoint),
+ xfer->sumlen, usb2_get_speed(xfer->xroot->udev));
+
+ temp.max_frame_size = xfer->max_frame_size;
+
+ td = xfer->td_start[0];
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ /* setup temp */
+
+ temp.td = NULL;
+ temp.td_next = xfer->td_start[0];
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+ temp.offset = 0;
+
+ sc = USS820_DCI_BUS2SC(xfer->xroot->bus);
+ ep_no = (xfer->endpoint & UE_ADDR);
+
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+
+ temp.func = &uss820dci_setup_rx;
+ temp.len = xfer->frlengths[0];
+ temp.pc = xfer->frbuffers + 0;
+ temp.short_pkt = temp.len ? 1 : 0;
+
+ uss820dci_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ if (x != xfer->nframes) {
+ if (xfer->endpoint & UE_DIR_IN) {
+ temp.func = &uss820dci_data_tx;
+ } else {
+ temp.func = &uss820dci_data_rx;
+ }
+
+ /* setup "pc" pointer */
+ temp.pc = xfer->frbuffers + x;
+ }
+ while (x != xfer->nframes) {
+
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+
+ x++;
+
+ if (x == xfer->nframes) {
+ temp.setup_alt_next = 0;
+ }
+ if (temp.len == 0) {
+
+ /* make sure that we send an USB packet */
+
+ temp.short_pkt = 0;
+
+ } else {
+
+ /* regular data transfer */
+
+ temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+ }
+
+ uss820dci_setup_standard_chain_sub(&temp);
+
+ if (xfer->flags_int.isochronous_xfr) {
+ temp.offset += temp.len;
+ } else {
+ /* get next Page Cache pointer */
+ temp.pc = xfer->frbuffers + x;
+ }
+ }
+
+ /* always setup a valid "pc" pointer for status and sync */
+ temp.pc = xfer->frbuffers + 0;
+
+ /* check if we should append a status stage */
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+ uint8_t need_sync;
+
+ /*
+ * Send a DATA1 message and invert the current
+ * endpoint direction.
+ */
+ if (xfer->endpoint & UE_DIR_IN) {
+ temp.func = &uss820dci_data_rx;
+ need_sync = 0;
+ } else {
+ temp.func = &uss820dci_data_tx;
+ need_sync = 1;
+ }
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ uss820dci_setup_standard_chain_sub(&temp);
+ if (need_sync) {
+ /* we need a SYNC point after TX */
+ temp.func = &uss820dci_data_tx_sync;
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ uss820dci_setup_standard_chain_sub(&temp);
+ }
+ }
+ /* must have at least one frame! */
+ td = temp.td;
+ xfer->td_transfer_last = td;
+}
+
+static void
+uss820dci_timeout(void *arg)
+{
+ struct usb2_xfer *xfer = arg;
+
+ DPRINTF("xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ uss820dci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+uss820dci_intr_set(struct usb2_xfer *xfer, uint8_t set)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus);
+ uint8_t ep_no = (xfer->endpoint & UE_ADDR);
+ uint8_t ep_reg;
+ uint8_t temp;
+
+ DPRINTFN(15, "endpoint 0x%02x\n", xfer->endpoint);
+
+ if (ep_no > 3) {
+ ep_reg = USS820_SBIE1;
+ } else {
+ ep_reg = USS820_SBIE;
+ }
+
+ ep_no &= 3;
+ ep_no = 1 << (2 * ep_no);
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ ep_no <<= 1; /* RX interrupt only */
+ } else {
+ ep_no |= (ep_no << 1); /* RX and TX interrupt */
+ }
+ } else {
+ if (!(xfer->endpoint & UE_DIR_IN)) {
+ ep_no <<= 1;
+ }
+ }
+ temp = USS820_READ_1(sc, ep_reg);
+ if (set) {
+ temp |= ep_no;
+ } else {
+ temp &= ~ep_no;
+ }
+ USS820_WRITE_1(sc, ep_reg, temp);
+}
+
+static void
+uss820dci_start_standard_chain(struct usb2_xfer *xfer)
+{
+ DPRINTFN(9, "\n");
+
+ /* poll one time */
+ if (uss820dci_xfer_do_fifo(xfer)) {
+
+ /*
+ * Only enable the endpoint interrupt when we are
+ * actually waiting for data, hence we are dealing
+ * with level triggered interrupts !
+ */
+ uss820dci_intr_set(xfer, 1);
+
+ /* put transfer on interrupt queue */
+ usb2_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usb2_transfer_timeout_ms(xfer,
+ &uss820dci_timeout, xfer->timeout);
+ }
+ }
+}
+
+static void
+uss820dci_root_intr_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus);
+
+ DPRINTFN(9, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_PRE_DATA) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ uss820dci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* setup buffer */
+ std->ptr = sc->sc_hub_idata;
+ std->len = sizeof(sc->sc_hub_idata);
+
+ /* set port bit */
+ sc->sc_hub_idata[0] = 0x02; /* we only have one port */
+
+done:
+ return;
+}
+
+static usb2_error_t
+uss820dci_standard_done_sub(struct usb2_xfer *xfer)
+{
+ struct uss820dci_td *td;
+ uint32_t len;
+ uint8_t error;
+
+ DPRINTFN(9, "\n");
+
+ td = xfer->td_transfer_cache;
+
+ do {
+ len = td->remainder;
+
+ if (xfer->aframes != xfer->nframes) {
+ /*
+ * Verify the length and subtract
+ * the remainder from "frlengths[]":
+ */
+ if (len > xfer->frlengths[xfer->aframes]) {
+ td->error = 1;
+ } else {
+ xfer->frlengths[xfer->aframes] -= len;
+ }
+ }
+ /* Check for transfer error */
+ if (td->error) {
+ /* the transfer is finished */
+ error = 1;
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (len > 0) {
+ if (xfer->flags_int.short_frames_ok) {
+ /* follow alt next */
+ if (td->alt_next) {
+ td = td->obj_next;
+ } else {
+ td = NULL;
+ }
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ error = 0;
+ break;
+ }
+ td = td->obj_next;
+
+ /* this USB frame is complete */
+ error = 0;
+ break;
+
+ } while (0);
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ return (error ?
+ USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+uss820dci_standard_done(struct usb2_xfer *xfer)
+{
+ usb2_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->pipe);
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+
+ if (xfer->flags_int.control_hdr) {
+
+ err = uss820dci_standard_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+
+ err = uss820dci_standard_done_sub(xfer);
+ xfer->aframes++;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+
+ err = uss820dci_standard_done_sub(xfer);
+ }
+done:
+ uss820dci_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * uss820dci_device_done
+ *
+ * NOTE: this function can be called more than one time on the
+ * same USB transfer!
+ *------------------------------------------------------------------------*/
+static void
+uss820dci_device_done(struct usb2_xfer *xfer, usb2_error_t error)
+{
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ DPRINTFN(2, "xfer=%p, pipe=%p, error=%d\n",
+ xfer, xfer->pipe, error);
+
+ if (xfer->flags_int.usb2_mode == USB_MODE_DEVICE) {
+ uss820dci_intr_set(xfer, 0);
+ }
+ /* dequeue transfer and start next transfer */
+ usb2_transfer_done(xfer, error);
+}
+
+static void
+uss820dci_set_stall(struct usb2_device *udev, struct usb2_xfer *xfer,
+ struct usb2_pipe *pipe)
+{
+ struct uss820dci_softc *sc;
+ uint8_t ep_no;
+ uint8_t ep_type;
+ uint8_t ep_dir;
+ uint8_t temp;
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ DPRINTFN(5, "pipe=%p\n", pipe);
+
+ if (xfer) {
+ /* cancel any ongoing transfers */
+ uss820dci_device_done(xfer, USB_ERR_STALLED);
+ }
+ /* set FORCESTALL */
+ sc = USS820_DCI_BUS2SC(udev->bus);
+ ep_no = (pipe->edesc->bEndpointAddress & UE_ADDR);
+ ep_dir = (pipe->edesc->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT));
+ ep_type = (pipe->edesc->bmAttributes & UE_XFERTYPE);
+
+ if (ep_type == UE_CONTROL) {
+ /* should not happen */
+ return;
+ }
+ USS820_WRITE_1(sc, USS820_EPINDEX, ep_no);
+
+ if (ep_dir == UE_DIR_IN) {
+ temp = USS820_EPCON_TXSTL;
+ } else {
+ temp = USS820_EPCON_RXSTL;
+ }
+ uss820dci_update_shared_1(sc, USS820_EPCON, 0xFF, temp);
+}
+
+static void
+uss820dci_clear_stall_sub(struct uss820dci_softc *sc,
+ uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir)
+{
+ uint8_t temp;
+
+ if (ep_type == UE_CONTROL) {
+ /* clearing stall is not needed */
+ return;
+ }
+ /* select endpoint index */
+ USS820_WRITE_1(sc, USS820_EPINDEX, ep_no);
+
+ /* clear stall and disable I/O transfers */
+ if (ep_dir == UE_DIR_IN) {
+ temp = 0xFF ^ (USS820_EPCON_TXOE |
+ USS820_EPCON_TXSTL);
+ } else {
+ temp = 0xFF ^ (USS820_EPCON_RXIE |
+ USS820_EPCON_RXSTL);
+ }
+ uss820dci_update_shared_1(sc, USS820_EPCON, temp, 0);
+
+ if (ep_dir == UE_DIR_IN) {
+ /* reset data toggle */
+ USS820_WRITE_1(sc, USS820_TXSTAT,
+ USS820_TXSTAT_TXSOVW);
+
+ /* reset FIFO */
+ temp = USS820_READ_1(sc, USS820_TXCON);
+ temp |= USS820_TXCON_TXCLR;
+ USS820_WRITE_1(sc, USS820_TXCON, temp);
+ temp &= ~USS820_TXCON_TXCLR;
+ USS820_WRITE_1(sc, USS820_TXCON, temp);
+ } else {
+
+ /* reset data toggle */
+ uss820dci_update_shared_1(sc, USS820_RXSTAT,
+ 0, USS820_RXSTAT_RXSOVW);
+
+ /* reset FIFO */
+ temp = USS820_READ_1(sc, USS820_RXCON);
+ temp |= USS820_RXCON_RXCLR;
+ temp &= ~USS820_RXCON_RXFFRC;
+ USS820_WRITE_1(sc, USS820_RXCON, temp);
+ temp &= ~USS820_RXCON_RXCLR;
+ USS820_WRITE_1(sc, USS820_RXCON, temp);
+ }
+}
+
+static void
+uss820dci_clear_stall(struct usb2_device *udev, struct usb2_pipe *pipe)
+{
+ struct uss820dci_softc *sc;
+ struct usb2_endpoint_descriptor *ed;
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ DPRINTFN(5, "pipe=%p\n", pipe);
+
+ /* check mode */
+ if (udev->flags.usb2_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ /* get softc */
+ sc = USS820_DCI_BUS2SC(udev->bus);
+
+ /* get endpoint descriptor */
+ ed = pipe->edesc;
+
+ /* reset endpoint */
+ uss820dci_clear_stall_sub(sc,
+ (ed->bEndpointAddress & UE_ADDR),
+ (ed->bmAttributes & UE_XFERTYPE),
+ (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)));
+}
+
+usb2_error_t
+uss820dci_init(struct uss820dci_softc *sc)
+{
+ const struct usb2_hw_ep_profile *pf;
+ uint8_t n;
+ uint8_t temp;
+
+ DPRINTF("start\n");
+
+ /* set up the bus structure */
+ sc->sc_bus.usbrev = USB_REV_1_1;
+ sc->sc_bus.methods = &uss820dci_bus_methods;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* we always have VBUS */
+ sc->sc_flags.status_vbus = 1;
+
+ /* reset the chip */
+ USS820_WRITE_1(sc, USS820_SCR, USS820_SCR_SRESET);
+ DELAY(100);
+ USS820_WRITE_1(sc, USS820_SCR, 0);
+
+ /* wait for reset to complete */
+ for (n = 0;; n++) {
+
+ temp = USS820_READ_1(sc, USS820_MCSR);
+
+ if (temp & USS820_MCSR_INIT) {
+ break;
+ }
+ if (n == 100) {
+ USB_BUS_UNLOCK(&sc->sc_bus);
+ return (USB_ERR_INVAL);
+ }
+ /* wait a little for things to stabilise */
+ DELAY(100);
+ }
+
+ /* do a pulldown */
+ uss820dci_pull_down(sc);
+
+ /* wait 10ms for pulldown to stabilise */
+ usb2_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100);
+
+ /* check hardware revision */
+ temp = USS820_READ_1(sc, USS820_REV);
+
+ if (temp < 0x13) {
+ USB_BUS_UNLOCK(&sc->sc_bus);
+ return (USB_ERR_INVAL);
+ }
+ /* enable interrupts */
+ USS820_WRITE_1(sc, USS820_SCR,
+ USS820_SCR_T_IRQ |
+ USS820_SCR_IE_RESET |
+ /* USS820_SCR_RWUPE | */
+ USS820_SCR_IE_SUSP |
+ USS820_SCR_IRQPOL);
+
+ /* enable interrupts */
+ USS820_WRITE_1(sc, USS820_SCRATCH,
+ USS820_SCRATCH_IE_RESUME);
+
+ /* enable features */
+ USS820_WRITE_1(sc, USS820_MCSR,
+ USS820_MCSR_BDFEAT |
+ USS820_MCSR_FEAT);
+
+ sc->sc_flags.mcsr_feat = 1;
+
+ /* disable interrupts */
+ USS820_WRITE_1(sc, USS820_SBIE, 0);
+
+ /* disable interrupts */
+ USS820_WRITE_1(sc, USS820_SBIE1, 0);
+
+ /* disable all endpoints */
+ for (n = 0; n != USS820_EP_MAX; n++) {
+
+ /* select endpoint */
+ USS820_WRITE_1(sc, USS820_EPINDEX, n);
+
+ /* disable endpoint */
+ uss820dci_update_shared_1(sc, USS820_EPCON, 0, 0);
+ }
+
+ /*
+ * Initialise default values for some registers that cannot be
+ * changed during operation!
+ */
+ for (n = 0; n != USS820_EP_MAX; n++) {
+
+ uss820dci_get_hw_ep_profile(NULL, &pf, n);
+
+ /* the maximum frame sizes should be the same */
+ if (pf->max_in_frame_size != pf->max_out_frame_size) {
+ DPRINTF("Max frame size mismatch %u != %u\n",
+ pf->max_in_frame_size, pf->max_out_frame_size);
+ }
+ if (pf->support_isochronous) {
+ if (pf->max_in_frame_size <= 64) {
+ temp = (USS820_TXCON_FFSZ_16_64 |
+ USS820_TXCON_TXISO |
+ USS820_TXCON_ATM);
+ } else if (pf->max_in_frame_size <= 256) {
+ temp = (USS820_TXCON_FFSZ_64_256 |
+ USS820_TXCON_TXISO |
+ USS820_TXCON_ATM);
+ } else if (pf->max_in_frame_size <= 512) {
+ temp = (USS820_TXCON_FFSZ_8_512 |
+ USS820_TXCON_TXISO |
+ USS820_TXCON_ATM);
+ } else { /* 1024 bytes */
+ temp = (USS820_TXCON_FFSZ_32_1024 |
+ USS820_TXCON_TXISO |
+ USS820_TXCON_ATM);
+ }
+ } else {
+ if ((pf->max_in_frame_size <= 8) &&
+ (sc->sc_flags.mcsr_feat)) {
+ temp = (USS820_TXCON_FFSZ_8_512 |
+ USS820_TXCON_ATM);
+ } else if (pf->max_in_frame_size <= 16) {
+ temp = (USS820_TXCON_FFSZ_16_64 |
+ USS820_TXCON_ATM);
+ } else if ((pf->max_in_frame_size <= 32) &&
+ (sc->sc_flags.mcsr_feat)) {
+ temp = (USS820_TXCON_FFSZ_32_1024 |
+ USS820_TXCON_ATM);
+ } else { /* 64 bytes */
+ temp = (USS820_TXCON_FFSZ_64_256 |
+ USS820_TXCON_ATM);
+ }
+ }
+
+ /* need to configure the chip early */
+
+ USS820_WRITE_1(sc, USS820_EPINDEX, n);
+ USS820_WRITE_1(sc, USS820_TXCON, temp);
+ USS820_WRITE_1(sc, USS820_RXCON, temp);
+
+ if (pf->support_control) {
+ temp = USS820_EPCON_CTLEP |
+ USS820_EPCON_RXSPM |
+ USS820_EPCON_RXIE |
+ USS820_EPCON_RXEPEN |
+ USS820_EPCON_TXOE |
+ USS820_EPCON_TXEPEN;
+ } else {
+ temp = USS820_EPCON_RXEPEN | USS820_EPCON_TXEPEN;
+ }
+
+ uss820dci_update_shared_1(sc, USS820_EPCON, 0xFF, temp);
+ }
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* catch any lost interrupts */
+
+ uss820dci_do_poll(&sc->sc_bus);
+
+ return (0); /* success */
+}
+
+void
+uss820dci_uninit(struct uss820dci_softc *sc)
+{
+ uint8_t temp;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* disable all interrupts */
+ temp = USS820_READ_1(sc, USS820_SCR);
+ temp &= ~USS820_SCR_T_IRQ;
+ USS820_WRITE_1(sc, USS820_SCR, temp);
+
+ sc->sc_flags.port_powered = 0;
+ sc->sc_flags.status_vbus = 0;
+ sc->sc_flags.status_bus_reset = 0;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ uss820dci_pull_down(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+void
+uss820dci_suspend(struct uss820dci_softc *sc)
+{
+ return;
+}
+
+void
+uss820dci_resume(struct uss820dci_softc *sc)
+{
+ return;
+}
+
+static void
+uss820dci_do_poll(struct usb2_bus *bus)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ uss820dci_interrupt_poll(sc);
+ uss820dci_root_ctrl_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*------------------------------------------------------------------------*
+ * at91dci bulk support
+ *------------------------------------------------------------------------*/
+static void
+uss820dci_device_bulk_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_device_bulk_close(struct usb2_xfer *xfer)
+{
+ uss820dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uss820dci_device_bulk_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_device_bulk_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ uss820dci_setup_standard_chain(xfer);
+ uss820dci_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods uss820dci_device_bulk_methods =
+{
+ .open = uss820dci_device_bulk_open,
+ .close = uss820dci_device_bulk_close,
+ .enter = uss820dci_device_bulk_enter,
+ .start = uss820dci_device_bulk_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci control support
+ *------------------------------------------------------------------------*/
+static void
+uss820dci_device_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_device_ctrl_close(struct usb2_xfer *xfer)
+{
+ uss820dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uss820dci_device_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_device_ctrl_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ uss820dci_setup_standard_chain(xfer);
+ uss820dci_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods uss820dci_device_ctrl_methods =
+{
+ .open = uss820dci_device_ctrl_open,
+ .close = uss820dci_device_ctrl_close,
+ .enter = uss820dci_device_ctrl_enter,
+ .start = uss820dci_device_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+uss820dci_device_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_device_intr_close(struct usb2_xfer *xfer)
+{
+ uss820dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uss820dci_device_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_device_intr_start(struct usb2_xfer *xfer)
+{
+ /* setup TDs */
+ uss820dci_setup_standard_chain(xfer);
+ uss820dci_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods uss820dci_device_intr_methods =
+{
+ .open = uss820dci_device_intr_open,
+ .close = uss820dci_device_intr_close,
+ .enter = uss820dci_device_intr_enter,
+ .start = uss820dci_device_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci full speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+uss820dci_device_isoc_fs_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_device_isoc_fs_close(struct usb2_xfer *xfer)
+{
+ uss820dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uss820dci_device_isoc_fs_enter(struct usb2_xfer *xfer)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus);
+ uint32_t temp;
+ uint32_t nframes;
+
+ DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
+ xfer, xfer->pipe->isoc_next, xfer->nframes);
+
+ /* get the current frame index - we don't need the high bits */
+
+ nframes = USS820_READ_1(sc, USS820_SOFL);
+
+ /*
+ * check if the frame index is within the window where the
+ * frames will be inserted
+ */
+ temp = (nframes - xfer->pipe->isoc_next) & USS820_SOFL_MASK;
+
+ if ((xfer->pipe->is_synced == 0) ||
+ (temp < xfer->nframes)) {
+ /*
+ * If there is data underflow or the pipe queue is
+ * empty we schedule the transfer a few frames ahead
+ * of the current frame position. Else two isochronous
+ * transfers might overlap.
+ */
+ xfer->pipe->isoc_next = (nframes + 3) & USS820_SOFL_MASK;
+ xfer->pipe->is_synced = 1;
+ DPRINTFN(3, "start next=%d\n", xfer->pipe->isoc_next);
+ }
+ /*
+ * compute how many milliseconds the insertion is ahead of the
+ * current frame position:
+ */
+ temp = (xfer->pipe->isoc_next - nframes) & USS820_SOFL_MASK;
+
+ /*
+ * pre-compute when the isochronous transfer will be finished:
+ */
+ xfer->isoc_time_complete =
+ usb2_isoc_time_expand(&sc->sc_bus, nframes) + temp +
+ xfer->nframes;
+
+ /* compute frame number for next insertion */
+ xfer->pipe->isoc_next += xfer->nframes;
+
+ /* setup TDs */
+ uss820dci_setup_standard_chain(xfer);
+}
+
+static void
+uss820dci_device_isoc_fs_start(struct usb2_xfer *xfer)
+{
+ /* start TD chain */
+ uss820dci_start_standard_chain(xfer);
+}
+
+struct usb2_pipe_methods uss820dci_device_isoc_fs_methods =
+{
+ .open = uss820dci_device_isoc_fs_open,
+ .close = uss820dci_device_isoc_fs_close,
+ .enter = uss820dci_device_isoc_fs_enter,
+ .start = uss820dci_device_isoc_fs_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci root control support
+ *------------------------------------------------------------------------*
+ * simulate a hardware HUB by handling
+ * all the necessary requests
+ *------------------------------------------------------------------------*/
+
+static void
+uss820dci_root_ctrl_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_root_ctrl_close(struct usb2_xfer *xfer)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_ctrl.xfer == xfer) {
+ sc->sc_root_ctrl.xfer = NULL;
+ }
+ uss820dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+/*
+ * USB descriptors for the virtual Root HUB:
+ */
+
+static const struct usb2_device_descriptor uss820dci_devd = {
+ .bLength = sizeof(struct usb2_device_descriptor),
+ .bDescriptorType = UDESC_DEVICE,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_HSHUBSTT,
+ .bMaxPacketSize = 64,
+ .bcdDevice = {0x00, 0x01},
+ .iManufacturer = 1,
+ .iProduct = 2,
+ .bNumConfigurations = 1,
+};
+
+static const struct usb2_device_qualifier uss820dci_odevd = {
+ .bLength = sizeof(struct usb2_device_qualifier),
+ .bDescriptorType = UDESC_DEVICE_QUALIFIER,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_FSHUB,
+ .bMaxPacketSize0 = 0,
+ .bNumConfigurations = 0,
+};
+
+static const struct uss820dci_config_desc uss820dci_confd = {
+ .confd = {
+ .bLength = sizeof(struct usb2_config_descriptor),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(uss820dci_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0,
+ },
+ .ifcd = {
+ .bLength = sizeof(struct usb2_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = UIPROTO_HSHUBSTT,
+ },
+
+ .endpd = {
+ .bLength = sizeof(struct usb2_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = (UE_DIR_IN | USS820_DCI_INTR_ENDPT),
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 8,
+ .bInterval = 255,
+ },
+};
+
+static const struct usb2_hub_descriptor_min uss820dci_hubd = {
+ .bDescLength = sizeof(uss820dci_hubd),
+ .bDescriptorType = UDESC_HUB,
+ .bNbrPorts = 1,
+ .wHubCharacteristics[0] =
+ (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) & 0xFF,
+ .wHubCharacteristics[1] =
+ (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) >> 8,
+ .bPwrOn2PwrGood = 50,
+ .bHubContrCurrent = 0,
+ .DeviceRemovable = {0}, /* port is removable */
+};
+
+#define STRING_LANG \
+ 0x09, 0x04, /* American English */
+
+#define STRING_VENDOR \
+ 'A', 0, 'G', 0, 'E', 0, 'R', 0, 'E', 0
+
+#define STRING_PRODUCT \
+ 'D', 0, 'C', 0, 'I', 0, ' ', 0, 'R', 0, \
+ 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, \
+ 'U', 0, 'B', 0,
+
+USB_MAKE_STRING_DESC(STRING_LANG, uss820dci_langtab);
+USB_MAKE_STRING_DESC(STRING_VENDOR, uss820dci_vendor);
+USB_MAKE_STRING_DESC(STRING_PRODUCT, uss820dci_product);
+
+static void
+uss820dci_root_ctrl_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_root_ctrl_start(struct usb2_xfer *xfer)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_ctrl.xfer = xfer;
+
+ usb2_bus_roothub_exec(xfer->xroot->bus);
+}
+
+static void
+uss820dci_root_ctrl_task(struct usb2_bus *bus)
+{
+ uss820dci_root_ctrl_poll(USS820_DCI_BUS2SC(bus));
+}
+
+static void
+uss820dci_root_ctrl_done(struct usb2_xfer *xfer,
+ struct usb2_sw_transfer *std)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus);
+ uint16_t value;
+ uint16_t index;
+ uint8_t use_polling;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (std->state != USB_SW_TR_SETUP) {
+ if (std->state == USB_SW_TR_PRE_CALLBACK) {
+ /* transfer transferred */
+ uss820dci_device_done(xfer, std->err);
+ }
+ goto done;
+ }
+ /* buffer reset */
+ std->ptr = USB_ADD_BYTES(&sc->sc_hub_temp, 0);
+ std->len = 0;
+
+ value = UGETW(std->req.wValue);
+ index = UGETW(std->req.wIndex);
+
+ use_polling = mtx_owned(xfer->xroot->xfer_mtx) ? 1 : 0;
+
+ /* demultiplex the control request */
+
+ switch (std->req.bmRequestType) {
+ case UT_READ_DEVICE:
+ switch (std->req.bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_descriptor;
+ case UR_GET_CONFIG:
+ goto tr_handle_get_config;
+ case UR_GET_STATUS:
+ goto tr_handle_get_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_DEVICE:
+ switch (std->req.bRequest) {
+ case UR_SET_ADDRESS:
+ goto tr_handle_set_address;
+ case UR_SET_CONFIG:
+ goto tr_handle_set_config;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_DESCRIPTOR:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_ENDPOINT:
+ switch (std->req.bRequest) {
+ case UR_CLEAR_FEATURE:
+ switch (UGETW(std->req.wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_clear_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_clear_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SET_FEATURE:
+ switch (UGETW(std->req.wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_set_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_set_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SYNCH_FRAME:
+ goto tr_valid; /* nop */
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_ENDPOINT:
+ switch (std->req.bRequest) {
+ case UR_GET_STATUS:
+ goto tr_handle_get_ep_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_INTERFACE:
+ switch (std->req.bRequest) {
+ case UR_SET_INTERFACE:
+ goto tr_handle_set_interface;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_INTERFACE:
+ switch (std->req.bRequest) {
+ case UR_GET_INTERFACE:
+ goto tr_handle_get_interface;
+ case UR_GET_STATUS:
+ goto tr_handle_get_iface_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_INTERFACE:
+ case UT_WRITE_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_READ_CLASS_INTERFACE:
+ case UT_READ_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_WRITE_CLASS_DEVICE:
+ switch (std->req.bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_valid;
+ case UR_SET_DESCRIPTOR:
+ case UR_SET_FEATURE:
+ break;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_OTHER:
+ switch (std->req.bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_handle_clear_port_feature;
+ case UR_SET_FEATURE:
+ goto tr_handle_set_port_feature;
+ case UR_CLEAR_TT_BUFFER:
+ case UR_RESET_TT:
+ case UR_STOP_TT:
+ goto tr_valid;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_OTHER:
+ switch (std->req.bRequest) {
+ case UR_GET_TT_STATE:
+ goto tr_handle_get_tt_state;
+ case UR_GET_STATUS:
+ goto tr_handle_get_port_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_DEVICE:
+ switch (std->req.bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_class_descriptor;
+ case UR_GET_STATUS:
+ goto tr_handle_get_class_status;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_valid;
+
+tr_handle_get_descriptor:
+ switch (value >> 8) {
+ case UDESC_DEVICE:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ std->len = sizeof(uss820dci_devd);
+ std->ptr = USB_ADD_BYTES(&uss820dci_devd, 0);
+ goto tr_valid;
+ case UDESC_CONFIG:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ std->len = sizeof(uss820dci_confd);
+ std->ptr = USB_ADD_BYTES(&uss820dci_confd, 0);
+ goto tr_valid;
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ std->len = sizeof(uss820dci_langtab);
+ std->ptr = USB_ADD_BYTES(&uss820dci_langtab, 0);
+ goto tr_valid;
+
+ case 1: /* Vendor */
+ std->len = sizeof(uss820dci_vendor);
+ std->ptr = USB_ADD_BYTES(&uss820dci_vendor, 0);
+ goto tr_valid;
+
+ case 2: /* Product */
+ std->len = sizeof(uss820dci_product);
+ std->ptr = USB_ADD_BYTES(&uss820dci_product, 0);
+ goto tr_valid;
+ default:
+ break;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_stalled;
+
+tr_handle_get_config:
+ std->len = 1;
+ sc->sc_hub_temp.wValue[0] = sc->sc_conf;
+ goto tr_valid;
+
+tr_handle_get_status:
+ std->len = 2;
+ USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED);
+ goto tr_valid;
+
+tr_handle_set_address:
+ if (value & 0xFF00) {
+ goto tr_stalled;
+ }
+ sc->sc_rt_addr = value;
+ goto tr_valid;
+
+tr_handle_set_config:
+ if (value >= 2) {
+ goto tr_stalled;
+ }
+ sc->sc_conf = value;
+ goto tr_valid;
+
+tr_handle_get_interface:
+ std->len = 1;
+ sc->sc_hub_temp.wValue[0] = 0;
+ goto tr_valid;
+
+tr_handle_get_tt_state:
+tr_handle_get_class_status:
+tr_handle_get_iface_status:
+tr_handle_get_ep_status:
+ std->len = 2;
+ USETW(sc->sc_hub_temp.wValue, 0);
+ goto tr_valid;
+
+tr_handle_set_halt:
+tr_handle_set_interface:
+tr_handle_set_wakeup:
+tr_handle_clear_wakeup:
+tr_handle_clear_halt:
+ goto tr_valid;
+
+tr_handle_clear_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index);
+
+ switch (value) {
+ case UHF_PORT_SUSPEND:
+ uss820dci_wakeup_peer(sc);
+ break;
+
+ case UHF_PORT_ENABLE:
+ sc->sc_flags.port_enabled = 0;
+ break;
+
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ case UHF_C_PORT_ENABLE:
+ case UHF_C_PORT_OVER_CURRENT:
+ case UHF_C_PORT_RESET:
+ /* nops */
+ break;
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 0;
+ uss820dci_pull_down(sc);
+ break;
+ case UHF_C_PORT_CONNECTION:
+ sc->sc_flags.change_connect = 0;
+ break;
+ case UHF_C_PORT_SUSPEND:
+ sc->sc_flags.change_suspend = 0;
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_set_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(9, "UR_SET_PORT_FEATURE\n");
+
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ sc->sc_flags.port_enabled = 1;
+ break;
+ case UHF_PORT_SUSPEND:
+ case UHF_PORT_RESET:
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ /* nops */
+ break;
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 1;
+ break;
+ default:
+ std->err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_get_port_status:
+
+ DPRINTFN(9, "UR_GET_PORT_STATUS\n");
+
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ if (sc->sc_flags.status_vbus) {
+ uss820dci_pull_up(sc);
+ } else {
+ uss820dci_pull_down(sc);
+ }
+
+ /* Select FULL-speed and Device Side Mode */
+
+ value = UPS_PORT_MODE_DEVICE;
+
+ if (sc->sc_flags.port_powered) {
+ value |= UPS_PORT_POWER;
+ }
+ if (sc->sc_flags.port_enabled) {
+ value |= UPS_PORT_ENABLED;
+ }
+ if (sc->sc_flags.status_vbus &&
+ sc->sc_flags.status_bus_reset) {
+ value |= UPS_CURRENT_CONNECT_STATUS;
+ }
+ if (sc->sc_flags.status_suspend) {
+ value |= UPS_SUSPEND;
+ }
+ USETW(sc->sc_hub_temp.ps.wPortStatus, value);
+
+ value = 0;
+
+ if (sc->sc_flags.change_connect) {
+ value |= UPS_C_CONNECT_STATUS;
+ }
+ if (sc->sc_flags.change_suspend) {
+ value |= UPS_C_SUSPEND;
+ }
+ USETW(sc->sc_hub_temp.ps.wPortChange, value);
+ std->len = sizeof(sc->sc_hub_temp.ps);
+ goto tr_valid;
+
+tr_handle_get_class_descriptor:
+ if (value & 0xFF) {
+ goto tr_stalled;
+ }
+ std->ptr = USB_ADD_BYTES(&uss820dci_hubd, 0);
+ std->len = sizeof(uss820dci_hubd);
+ goto tr_valid;
+
+tr_stalled:
+ std->err = USB_ERR_STALLED;
+tr_valid:
+done:
+ return;
+}
+
+static void
+uss820dci_root_ctrl_poll(struct uss820dci_softc *sc)
+{
+ usb2_sw_transfer(&sc->sc_root_ctrl,
+ &uss820dci_root_ctrl_done);
+}
+
+struct usb2_pipe_methods uss820dci_root_ctrl_methods =
+{
+ .open = uss820dci_root_ctrl_open,
+ .close = uss820dci_root_ctrl_close,
+ .enter = uss820dci_root_ctrl_enter,
+ .start = uss820dci_root_ctrl_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 0,
+};
+
+/*------------------------------------------------------------------------*
+ * at91dci root interrupt support
+ *------------------------------------------------------------------------*/
+static void
+uss820dci_root_intr_open(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_root_intr_close(struct usb2_xfer *xfer)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus);
+
+ if (sc->sc_root_intr.xfer == xfer) {
+ sc->sc_root_intr.xfer = NULL;
+ }
+ uss820dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uss820dci_root_intr_enter(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_root_intr_start(struct usb2_xfer *xfer)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_root_intr.xfer = xfer;
+}
+
+struct usb2_pipe_methods uss820dci_root_intr_methods =
+{
+ .open = uss820dci_root_intr_open,
+ .close = uss820dci_root_intr_close,
+ .enter = uss820dci_root_intr_enter,
+ .start = uss820dci_root_intr_start,
+ .enter_is_cancelable = 1,
+ .start_is_cancelable = 1,
+};
+
+static void
+uss820dci_xfer_setup(struct usb2_setup_params *parm)
+{
+ const struct usb2_hw_ep_profile *pf;
+ struct uss820dci_softc *sc;
+ struct usb2_xfer *xfer;
+ void *last_obj;
+ uint32_t ntd;
+ uint32_t n;
+ uint8_t ep_no;
+
+ sc = USS820_DCI_BUS2SC(parm->udev->bus);
+ xfer = parm->curr_xfer;
+
+ /*
+ * NOTE: This driver does not use any of the parameters that
+ * are computed from the following values. Just set some
+ * reasonable dummies:
+ */
+ parm->hc_max_packet_size = 0x500;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = 0x500;
+
+ usb2_transfer_setup_sub(parm);
+
+ /*
+ * compute maximum number of TDs
+ */
+ if (parm->methods == &uss820dci_device_ctrl_methods) {
+
+ ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &uss820dci_device_bulk_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &uss820dci_device_intr_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &uss820dci_device_isoc_fs_methods) {
+
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else {
+
+ ntd = 0;
+ }
+
+ /*
+ * check if "usb2_transfer_setup_sub" set an error
+ */
+ if (parm->err) {
+ return;
+ }
+ /*
+ * allocate transfer descriptors
+ */
+ last_obj = NULL;
+
+ /*
+ * get profile stuff
+ */
+ if (ntd) {
+
+ ep_no = xfer->endpoint & UE_ADDR;
+ uss820dci_get_hw_ep_profile(parm->udev, &pf, ep_no);
+
+ if (pf == NULL) {
+ /* should not happen */
+ parm->err = USB_ERR_INVAL;
+ return;
+ }
+ } else {
+ ep_no = 0;
+ pf = NULL;
+ }
+
+ /* align data */
+ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+ for (n = 0; n != ntd; n++) {
+
+ struct uss820dci_td *td;
+
+ if (parm->buf) {
+
+ td = USB_ADD_BYTES(parm->buf, parm->size[0]);
+
+ /* init TD */
+ td->io_tag = sc->sc_io_tag;
+ td->io_hdl = sc->sc_io_hdl;
+ td->max_packet_size = xfer->max_packet_size;
+ td->rx_stat_reg = USS820_GET_REG(sc, USS820_RXSTAT);
+ td->tx_stat_reg = USS820_GET_REG(sc, USS820_TXSTAT);
+ td->rx_flag_reg = USS820_GET_REG(sc, USS820_RXFLG);
+ td->tx_flag_reg = USS820_GET_REG(sc, USS820_TXFLG);
+ td->rx_fifo_reg = USS820_GET_REG(sc, USS820_RXDAT);
+ td->tx_fifo_reg = USS820_GET_REG(sc, USS820_TXDAT);
+ td->rx_count_low_reg = USS820_GET_REG(sc, USS820_RXCNTL);
+ td->rx_count_high_reg = USS820_GET_REG(sc, USS820_RXCNTH);
+ td->tx_count_low_reg = USS820_GET_REG(sc, USS820_TXCNTL);
+ td->tx_count_high_reg = USS820_GET_REG(sc, USS820_TXCNTH);
+ td->rx_cntl_reg = USS820_GET_REG(sc, USS820_RXCON);
+ td->tx_cntl_reg = USS820_GET_REG(sc, USS820_TXCON);
+ td->pend_reg = USS820_GET_REG(sc, USS820_PEND);
+ td->ep_reg = USS820_GET_REG(sc, USS820_EPINDEX);
+ td->ep_index = ep_no;
+ if (pf->support_multi_buffer &&
+ (parm->methods != &uss820dci_device_ctrl_methods)) {
+ td->support_multi_buffer = 1;
+ }
+ td->obj_next = last_obj;
+
+ last_obj = td;
+ }
+ parm->size[0] += sizeof(*td);
+ }
+
+ xfer->td_start[0] = last_obj;
+}
+
+static void
+uss820dci_xfer_unsetup(struct usb2_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_pipe_init(struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc,
+ struct usb2_pipe *pipe)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
+ pipe, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb2_mode,
+ sc->sc_rt_addr);
+
+ if (udev->device_index == sc->sc_rt_addr) {
+
+ if (udev->flags.usb2_mode != USB_MODE_HOST) {
+ /* not supported */
+ return;
+ }
+ switch (edesc->bEndpointAddress) {
+ case USB_CONTROL_ENDPOINT:
+ pipe->methods = &uss820dci_root_ctrl_methods;
+ break;
+ case UE_DIR_IN | USS820_DCI_INTR_ENDPT:
+ pipe->methods = &uss820dci_root_intr_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ } else {
+
+ if (udev->flags.usb2_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ if (udev->speed != USB_SPEED_FULL) {
+ /* not supported */
+ return;
+ }
+ switch (edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_CONTROL:
+ pipe->methods = &uss820dci_device_ctrl_methods;
+ break;
+ case UE_INTERRUPT:
+ pipe->methods = &uss820dci_device_intr_methods;
+ break;
+ case UE_ISOCHRONOUS:
+ pipe->methods = &uss820dci_device_isoc_fs_methods;
+ break;
+ case UE_BULK:
+ pipe->methods = &uss820dci_device_bulk_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+}
+
+struct usb2_bus_methods uss820dci_bus_methods =
+{
+ .pipe_init = &uss820dci_pipe_init,
+ .xfer_setup = &uss820dci_xfer_setup,
+ .xfer_unsetup = &uss820dci_xfer_unsetup,
+ .do_poll = &uss820dci_do_poll,
+ .get_hw_ep_profile = &uss820dci_get_hw_ep_profile,
+ .set_stall = &uss820dci_set_stall,
+ .clear_stall = &uss820dci_clear_stall,
+ .roothub_exec = &uss820dci_root_ctrl_task,
+};
diff --git a/sys/dev/usb/controller/uss820dci.h b/sys/dev/usb/controller/uss820dci.h
new file mode 100644
index 0000000..f99e2d5
--- /dev/null
+++ b/sys/dev/usb/controller/uss820dci.h
@@ -0,0 +1,377 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 2007 Hans Petter Selasky <hselasky@freebsd.org>
+ * 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.
+ */
+
+#ifndef _USS820_DCI_H_
+#define _USS820_DCI_H_
+
+#define USS820_MAX_DEVICES (USB_MIN_DEVICES + 1)
+
+#define USS820_EP_MAX 8 /* maximum number of endpoints */
+
+#define USS820_TXDAT 0x00 /* Transmit FIFO data */
+
+#define USS820_TXCNTL 0x01 /* Transmit FIFO byte count low */
+#define USS820_TXCNTL_MASK 0xFF
+
+#define USS820_TXCNTH 0x02 /* Transmit FIFO byte count high */
+#define USS820_TXCNTH_MASK 0x03
+#define USS820_TXCNTH_UNUSED 0xFC
+
+#define USS820_TXCON 0x03 /* USB transmit FIFO control */
+#define USS820_TXCON_REVRP 0x01
+#define USS820_TXCON_ADVRM 0x02
+#define USS820_TXCON_ATM 0x04 /* Automatic Transmit Management */
+#define USS820_TXCON_TXISO 0x08 /* Transmit Isochronous Data */
+#define USS820_TXCON_UNUSED 0x10
+#define USS820_TXCON_FFSZ_16_64 0x00
+#define USS820_TXCON_FFSZ_64_256 0x20
+#define USS820_TXCON_FFSZ_8_512 0x40
+#define USS820_TXCON_FFSZ_32_1024 0x60
+#define USS820_TXCON_FFSZ_MASK 0x60
+#define USS820_TXCON_TXCLR 0x80 /* Transmit FIFO clear */
+
+#define USS820_TXFLG 0x04 /* Transmit FIFO flag (Read Only) */
+#define USS820_TXFLG_TXOVF 0x01 /* TX overrun */
+#define USS820_TXFLG_TXURF 0x02 /* TX underrun */
+#define USS820_TXFLG_TXFULL 0x04 /* TX full */
+#define USS820_TXFLG_TXEMP 0x08 /* TX empty */
+#define USS820_TXFLG_UNUSED 0x30
+#define USS820_TXFLG_TXFIF0 0x40
+#define USS820_TXFLG_TXFIF1 0x80
+
+#define USS820_RXDAT 0x05 /* Receive FIFO data */
+
+#define USS820_RXCNTL 0x06 /* Receive FIFO byte count low */
+#define USS820_RXCNTL_MASK 0xFF
+
+#define USS820_RXCNTH 0x07 /* Receive FIFO byte count high */
+#define USS820_RXCNTH_MASK 0x03
+#define USS820_RXCNTH_UNUSED 0xFC
+
+#define USS820_RXCON 0x08 /* Receive FIFO control */
+#define USS820_RXCON_REVWP 0x01
+#define USS820_RXCON_ADVWM 0x02
+#define USS820_RXCON_ARM 0x04 /* Auto Receive Management */
+#define USS820_RXCON_RXISO 0x08 /* Receive Isochronous Data */
+#define USS820_RXCON_RXFFRC 0x10 /* FIFO Read Complete */
+#define USS820_RXCON_FFSZ_16_64 0x00
+#define USS820_RXCON_FFSZ_64_256 0x20
+#define USS820_RXCON_FFSZ_8_512 0x40
+#define USS820_RXCON_FFSZ_32_1024 0x60
+#define USS820_RXCON_RXCLR 0x80 /* Receive FIFO clear */
+
+#define USS820_RXFLG 0x09 /* Receive FIFO flag (Read Only) */
+#define USS820_RXFLG_RXOVF 0x01 /* RX overflow */
+#define USS820_RXFLG_RXURF 0x02 /* RX underflow */
+#define USS820_RXFLG_RXFULL 0x04 /* RX full */
+#define USS820_RXFLG_RXEMP 0x08 /* RX empty */
+#define USS820_RXFLG_RXFLUSH 0x10 /* RX flush */
+#define USS820_RXFLG_UNUSED 0x20
+#define USS820_RXFLG_RXFIF0 0x40
+#define USS820_RXFLG_RXFIF1 0x80
+
+#define USS820_EPINDEX 0x0a /* Endpoint index selection */
+#define USS820_EPINDEX_MASK 0x07
+#define USS820_EPINDEX_UNUSED 0xF8
+
+#define USS820_EPCON 0x0b /* Endpoint control */
+#define USS820_EPCON_TXEPEN 0x01 /* Transmit Endpoint Enable */
+#define USS820_EPCON_TXOE 0x02 /* Transmit Output Enable */
+#define USS820_EPCON_RXEPEN 0x04 /* Receive Endpoint Enable */
+#define USS820_EPCON_RXIE 0x08 /* Receive Input Enable */
+#define USS820_EPCON_RXSPM 0x10 /* Receive Single-Packet Mode */
+#define USS820_EPCON_CTLEP 0x20 /* Control Endpoint */
+#define USS820_EPCON_TXSTL 0x40 /* Stall Transmit Endpoint */
+#define USS820_EPCON_RXSTL 0x80 /* Stall Receive Endpoint */
+
+#define USS820_TXSTAT 0x0c /* Transmit status */
+#define USS820_TXSTAT_TXACK 0x01 /* Transmit Acknowledge */
+#define USS820_TXSTAT_TXERR 0x02 /* Transmit Error */
+#define USS820_TXSTAT_TXVOID 0x04 /* Transmit Void */
+#define USS820_TXSTAT_TXSOVW 0x08 /* Transmit Data Sequence Overwrite
+ * Bit */
+#define USS820_TXSTAT_TXFLUSH 0x10 /* Transmit FIFO Packet Flushed */
+#define USS820_TXSTAT_TXNAKE 0x20 /* Transmit NAK Mode Enable */
+#define USS820_TXSTAT_TXDSAM 0x40 /* Transmit Data-Set-Available Mode */
+#define USS820_TXSTAT_TXSEQ 0x80 /* Transmitter Current Sequence Bit */
+
+#define USS820_RXSTAT 0x0d /* Receive status */
+#define USS820_RXSTAT_RXACK 0x01 /* Receive Acknowledge */
+#define USS820_RXSTAT_RXERR 0x02 /* Receive Error */
+#define USS820_RXSTAT_RXVOID 0x04 /* Receive Void */
+#define USS820_RXSTAT_RXSOVW 0x08 /* Receive Data Sequence Overwrite Bit */
+#define USS820_RXSTAT_EDOVW 0x10 /* End Overwrite Flag */
+#define USS820_RXSTAT_STOVW 0x20 /* Start Overwrite Flag */
+#define USS820_RXSTAT_RXSETUP 0x40 /* Received SETUP token */
+#define USS820_RXSTAT_RXSEQ 0x80 /* Receiver Endpoint Sequence Bit */
+
+#define USS820_SOFL 0x0e /* Start Of Frame counter low */
+#define USS820_SOFL_MASK 0xFF
+
+#define USS820_SOFH 0x0f /* Start Of Frame counter high */
+#define USS820_SOFH_MASK 0x07
+#define USS820_SOFH_SOFDIS 0x08 /* SOF Pin Output Disable */
+#define USS820_SOFH_FTLOCK 0x10 /* Frame Timer Lock */
+#define USS820_SOFH_SOFIE 0x20 /* SOF Interrupt Enable */
+#define USS820_SOFH_ASOF 0x40 /* Any Start of Frame */
+#define USS820_SOFH_SOFACK 0x80 /* SOF Token Received Without Error */
+
+#define USS820_FADDR 0x10 /* Function Address */
+#define USS820_FADDR_MASK 0x7F
+#define USS820_FADDR_UNUSED 0x80
+
+#define USS820_SCR 0x11 /* System Control */
+#define USS820_SCR_UNUSED 0x01
+#define USS820_SCR_T_IRQ 0x02 /* Global Interrupt Enable */
+#define USS820_SCR_IRQLVL 0x04 /* Interrupt Mode */
+#define USS820_SCR_SRESET 0x08 /* Software reset */
+#define USS820_SCR_IE_RESET 0x10 /* Enable Reset Interrupt */
+#define USS820_SCR_IE_SUSP 0x20 /* Enable Suspend Interrupt */
+#define USS820_SCR_RWUPE 0x40 /* Enable Remote Wake-Up Feature */
+#define USS820_SCR_IRQPOL 0x80 /* IRQ polarity */
+
+#define USS820_SSR 0x12 /* System Status */
+#define USS820_SSR_RESET 0x01 /* Reset Condition Detected on USB
+ * cable */
+#define USS820_SSR_SUSPEND 0x02 /* Suspend Detected */
+#define USS820_SSR_RESUME 0x04 /* Resume Detected */
+#define USS820_SSR_SUSPDIS 0x08 /* Suspend Disable */
+#define USS820_SSR_SUSPPO 0x10 /* Suspend Power Off */
+#define USS820_SSR_UNUSED 0xE0
+
+#define USS820_UNK0 0x13 /* Unknown */
+#define USS820_UNK0_UNUSED 0xFF
+
+#define USS820_SBI 0x14 /* Serial bus interrupt low */
+#define USS820_SBI_FTXD0 0x01 /* Function Transmit Done, EP 0 */
+#define USS820_SBI_FRXD0 0x02 /* Function Receive Done, EP 0 */
+#define USS820_SBI_FTXD1 0x04
+#define USS820_SBI_FRXD1 0x08
+#define USS820_SBI_FTXD2 0x10
+#define USS820_SBI_FRXD2 0x20
+#define USS820_SBI_FTXD3 0x40
+#define USS820_SBI_FRXD3 0x80
+
+#define USS820_SBI1 0x15 /* Serial bus interrupt high */
+#define USS820_SBI1_FTXD4 0x01
+#define USS820_SBI1_FRXD4 0x02
+#define USS820_SBI1_FTXD5 0x04
+#define USS820_SBI1_FRXD5 0x08
+#define USS820_SBI1_FTXD6 0x10
+#define USS820_SBI1_FRXD6 0x20
+#define USS820_SBI1_FTXD7 0x40
+#define USS820_SBI1_FRXD7 0x80
+
+#define USS820_SBIE 0x16 /* Serial bus interrupt enable low */
+#define USS820_SBIE_FTXIE0 0x01
+#define USS820_SBIE_FRXIE0 0x02
+#define USS820_SBIE_FTXIE1 0x04
+#define USS820_SBIE_FRXIE1 0x08
+#define USS820_SBIE_FTXIE2 0x10
+#define USS820_SBIE_FRXIE2 0x20
+#define USS820_SBIE_FTXIE3 0x40
+#define USS820_SBIE_FRXIE3 0x80
+
+#define USS820_SBIE1 0x17 /* Serial bus interrupt enable high */
+#define USS820_SBIE1_FTXIE4 0x01
+#define USS820_SBIE1_FRXIE4 0x02
+#define USS820_SBIE1_FTXIE5 0x04
+#define USS820_SBIE1_FRXIE5 0x08
+#define USS820_SBIE1_FTXIE6 0x10
+#define USS820_SBIE1_FRXIE6 0x20
+#define USS820_SBIE1_FTXIE7 0x40
+#define USS820_SBIE1_FRXIE7 0x80
+
+#define USS820_REV 0x18 /* Hardware revision */
+#define USS820_REV_MIN 0x0F
+#define USS820_REV_MAJ 0xF0
+
+#define USS820_LOCK 0x19 /* Suspend power-off locking */
+#define USS820_LOCK_UNLOCKED 0x01
+#define USS820_LOCK_UNUSED 0xFE
+
+#define USS820_PEND 0x1a /* Pend hardware status update */
+#define USS820_PEND_PEND 0x01
+#define USS820_PEND_UNUSED 0xFE
+
+#define USS820_SCRATCH 0x1b /* Scratch firmware information */
+#define USS820_SCRATCH_MASK 0x7F
+#define USS820_SCRATCH_IE_RESUME 0x80 /* Enable Resume Interrupt */
+
+#define USS820_MCSR 0x1c /* Miscellaneous control and status */
+#define USS820_MCSR_DPEN 0x01 /* DPLS Pull-Up Enable */
+#define USS820_MCSR_SUSPLOE 0x02 /* Suspend Lock Out Enable */
+#define USS820_MCSR_BDFEAT 0x04 /* Board Feature Enable */
+#define USS820_MCSR_FEAT 0x08 /* Feature Enable */
+#define USS820_MCSR_PKGID 0x10 /* Package Identification */
+#define USS820_MCSR_SUSPS 0x20 /* Suspend Status */
+#define USS820_MCSR_INIT 0x40 /* Device Initialized */
+#define USS820_MCSR_RWUPR 0x80 /* Remote Wakeup-Up Remember */
+
+#define USS820_DSAV 0x1d /* Data set available low (Read Only) */
+#define USS820_DSAV_TXAV0 0x01
+#define USS820_DSAV_RXAV0 0x02
+#define USS820_DSAV_TXAV1 0x04
+#define USS820_DSAV_RXAV1 0x08
+#define USS820_DSAV_TXAV2 0x10
+#define USS820_DSAV_RXAV2 0x20
+#define USS820_DSAV_TXAV3 0x40
+#define USS820_DSAV_RXAV3 0x80
+
+#define USS820_DSAV1 0x1e /* Data set available high */
+#define USS820_DSAV1_TXAV4 0x01
+#define USS820_DSAV1_RXAV4 0x02
+#define USS820_DSAV1_TXAV5 0x04
+#define USS820_DSAV1_RXAV5 0x08
+#define USS820_DSAV1_TXAV6 0x10
+#define USS820_DSAV1_RXAV6 0x20
+#define USS820_DSAV1_TXAV7 0x40
+#define USS820_DSAV1_RXAV7 0x80
+
+#define USS820_UNK1 0x1f /* Unknown */
+#define USS820_UNK1_UNKNOWN 0xFF
+
+#define USS820_GET_REG(sc,reg) \
+ ((reg) << (sc)->sc_reg_shift)
+
+#define USS820_READ_1(sc, reg) \
+ bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, \
+ USS820_GET_REG(sc,reg))
+
+#define USS820_WRITE_1(sc, reg, data) \
+ bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, \
+ USS820_GET_REG(sc,reg), data)
+
+struct uss820dci_td;
+
+typedef uint8_t (uss820dci_cmd_t)(struct uss820dci_td *td);
+
+struct uss820dci_td {
+ bus_space_tag_t io_tag;
+ bus_space_handle_t io_hdl;
+ struct uss820dci_td *obj_next;
+ uss820dci_cmd_t *func;
+ struct usb2_page_cache *pc;
+ uint32_t offset;
+ uint32_t remainder;
+ uint16_t max_packet_size;
+ uint8_t rx_stat_reg;
+ uint8_t tx_stat_reg;
+ uint8_t rx_flag_reg;
+ uint8_t tx_flag_reg;
+ uint8_t rx_fifo_reg;
+ uint8_t tx_fifo_reg;
+ uint8_t rx_count_low_reg;
+ uint8_t rx_count_high_reg;
+ uint8_t tx_count_low_reg;
+ uint8_t tx_count_high_reg;
+ uint8_t rx_cntl_reg;
+ uint8_t tx_cntl_reg;
+ uint8_t ep_reg;
+ uint8_t pend_reg;
+ uint8_t ep_index;
+ uint8_t error:1;
+ uint8_t alt_next:1;
+ uint8_t short_pkt:1;
+ uint8_t support_multi_buffer:1;
+ uint8_t did_stall:1;
+};
+
+struct uss820_std_temp {
+ uss820dci_cmd_t *func;
+ struct usb2_page_cache *pc;
+ struct uss820dci_td *td;
+ struct uss820dci_td *td_next;
+ uint32_t len;
+ uint32_t offset;
+ uint16_t max_frame_size;
+ uint8_t short_pkt;
+ /*
+ * short_pkt = 0: transfer should be short terminated
+ * short_pkt = 1: transfer should not be short terminated
+ */
+ uint8_t setup_alt_next;
+};
+
+struct uss820dci_config_desc {
+ struct usb2_config_descriptor confd;
+ struct usb2_interface_descriptor ifcd;
+ struct usb2_endpoint_descriptor endpd;
+} __packed;
+
+union uss820_hub_temp {
+ uWord wValue;
+ struct usb2_port_status ps;
+};
+
+struct uss820_flags {
+ uint8_t change_connect:1;
+ uint8_t change_suspend:1;
+ uint8_t status_suspend:1; /* set if suspended */
+ uint8_t status_vbus:1; /* set if present */
+ uint8_t status_bus_reset:1; /* set if reset complete */
+ uint8_t clocks_off:1;
+ uint8_t port_powered:1;
+ uint8_t port_enabled:1;
+ uint8_t d_pulled_up:1;
+ uint8_t mcsr_feat:1;
+};
+
+struct uss820dci_softc {
+ struct usb2_bus sc_bus;
+ union uss820_hub_temp sc_hub_temp;
+ LIST_HEAD(, usb2_xfer) sc_interrupt_list_head;
+ struct usb2_sw_transfer sc_root_ctrl;
+ struct usb2_sw_transfer sc_root_intr;
+
+ struct usb2_device *sc_devices[USS820_MAX_DEVICES];
+ struct resource *sc_io_res;
+ struct resource *sc_irq_res;
+ void *sc_intr_hdl;
+ bus_size_t sc_io_size;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+
+ uint8_t sc_rt_addr; /* root HUB address */
+ uint8_t sc_dv_addr; /* device address */
+ uint8_t sc_conf; /* root HUB config */
+ uint8_t sc_reg_shift;
+
+ uint8_t sc_hub_idata[1];
+
+ struct uss820_flags sc_flags;
+};
+
+/* prototypes */
+
+usb2_error_t uss820dci_init(struct uss820dci_softc *sc);
+void uss820dci_uninit(struct uss820dci_softc *sc);
+void uss820dci_suspend(struct uss820dci_softc *sc);
+void uss820dci_resume(struct uss820dci_softc *sc);
+void uss820dci_interrupt(struct uss820dci_softc *sc);
+
+#endif /* _USS820_DCI_H_ */
diff --git a/sys/dev/usb/controller/uss820dci_atmelarm.c b/sys/dev/usb/controller/uss820dci_atmelarm.c
new file mode 100644
index 0000000..ddbffd7
--- /dev/null
+++ b/sys/dev/usb/controller/uss820dci_atmelarm.c
@@ -0,0 +1,238 @@
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*-
+ * Copyright (c) 2008 Hans Petter Selasky <hselasky@freebsd.org>
+ * 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 <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_defs.h>
+#include <dev/usb/usb.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_sw_transfer.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/uss820dci.h>
+
+#include <sys/rman.h>
+
+static device_probe_t uss820_atmelarm_probe;
+static device_attach_t uss820_atmelarm_attach;
+static device_detach_t uss820_atmelarm_detach;
+static device_suspend_t uss820_atmelarm_suspend;
+static device_resume_t uss820_atmelarm_resume;
+static device_shutdown_t uss820_atmelarm_shutdown;
+
+static device_method_t uss820dci_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, uss820_atmelarm_probe),
+ DEVMETHOD(device_attach, uss820_atmelarm_attach),
+ DEVMETHOD(device_detach, uss820_atmelarm_detach),
+ DEVMETHOD(device_suspend, uss820_atmelarm_suspend),
+ DEVMETHOD(device_resume, uss820_atmelarm_resume),
+ DEVMETHOD(device_shutdown, uss820_atmelarm_shutdown),
+
+ /* Bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+
+ {0, 0}
+};
+
+static driver_t uss820dci_driver = {
+ .name = "uss820",
+ .methods = uss820dci_methods,
+ .size = sizeof(struct uss820dci_softc),
+};
+
+static devclass_t uss820dci_devclass;
+
+DRIVER_MODULE(uss820, atmelarm, uss820dci_driver, uss820dci_devclass, 0, 0);
+MODULE_DEPEND(uss820, usb, 1, 1, 1);
+
+static const char *const uss820_desc = "USS820 USB Device Controller";
+
+static int
+uss820_atmelarm_suspend(device_t dev)
+{
+ struct uss820dci_softc *sc = device_get_softc(dev);
+ int err;
+
+ err = bus_generic_suspend(dev);
+ if (err == 0) {
+ uss820dci_suspend(sc);
+ }
+ return (err);
+}
+
+static int
+uss820_atmelarm_resume(device_t dev)
+{
+ struct uss820dci_softc *sc = device_get_softc(dev);
+ int err;
+
+ uss820dci_resume(sc);
+
+ err = bus_generic_resume(dev);
+
+ return (err);
+}
+
+static int
+uss820_atmelarm_shutdown(device_t dev)
+{
+ struct uss820dci_softc *sc = device_get_softc(dev);
+ int err;
+
+ err = bus_generic_shutdown(dev);
+ if (err)
+ return (err);
+
+ uss820dci_uninit(sc);
+
+ return (0);
+}
+
+static int
+uss820_atmelarm_probe(device_t dev)
+{
+ device_set_desc(dev, uss820_desc);
+ return (0); /* success */
+}
+
+static int
+uss820_atmelarm_attach(device_t dev)
+{
+ struct uss820dci_softc *sc = device_get_softc(dev);
+ int err;
+ int rid;
+
+ /* initialise some bus fields */
+ sc->sc_bus.parent = dev;
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = USS820_MAX_DEVICES;
+
+ /* get all DMA memory */
+ if (usb2_bus_mem_alloc_all(&sc->sc_bus,
+ USB_GET_DMA_TAG(dev), NULL)) {
+ return (ENOMEM);
+ }
+ rid = 0;
+ sc->sc_io_res =
+ bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE);
+
+ if (!sc->sc_io_res) {
+ goto error;
+ }
+ sc->sc_io_tag = rman_get_bustag(sc->sc_io_res);
+ sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res);
+ sc->sc_io_size = rman_get_size(sc->sc_io_res);
+
+ /* multiply all addresses by 4 */
+ sc->sc_reg_shift = 2;
+
+ rid = 0;
+ sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
+ RF_SHAREABLE | RF_ACTIVE);
+ if (sc->sc_irq_res == NULL) {
+ goto error;
+ }
+ sc->sc_bus.bdev = device_add_child(dev, "usbus", -1);
+ if (!(sc->sc_bus.bdev)) {
+ goto error;
+ }
+ device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus);
+
+#if (__FreeBSD_version >= 700031)
+ err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (void *)uss820dci_interrupt, sc, &sc->sc_intr_hdl);
+#else
+ err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ (void *)uss820dci_interrupt, sc, &sc->sc_intr_hdl);
+#endif
+ if (err) {
+ sc->sc_intr_hdl = NULL;
+ goto error;
+ }
+ err = uss820dci_init(sc);
+ if (err) {
+ device_printf(dev, "Init failed\n");
+ goto error;
+ }
+ err = device_probe_and_attach(sc->sc_bus.bdev);
+ if (err) {
+ device_printf(dev, "USB probe and attach failed\n");
+ goto error;
+ }
+ return (0);
+
+error:
+ uss820_atmelarm_detach(dev);
+ return (ENXIO);
+}
+
+static int
+uss820_atmelarm_detach(device_t dev)
+{
+ struct uss820dci_softc *sc = device_get_softc(dev);
+ device_t bdev;
+ int err;
+
+ if (sc->sc_bus.bdev) {
+ bdev = sc->sc_bus.bdev;
+ device_detach(bdev);
+ device_delete_child(dev, bdev);
+ }
+ /* during module unload there are lots of children leftover */
+ device_delete_all_children(dev);
+
+ if (sc->sc_irq_res && sc->sc_intr_hdl) {
+ /*
+ * only call at91_udp_uninit() after at91_udp_init()
+ */
+ uss820dci_uninit(sc);
+
+ err = bus_teardown_intr(dev, sc->sc_irq_res,
+ sc->sc_intr_hdl);
+ sc->sc_intr_hdl = NULL;
+ }
+ if (sc->sc_irq_res) {
+ bus_release_resource(dev, SYS_RES_IRQ, 0,
+ sc->sc_irq_res);
+ sc->sc_irq_res = NULL;
+ }
+ if (sc->sc_io_res) {
+ bus_release_resource(dev, SYS_RES_IOPORT, 0,
+ sc->sc_io_res);
+ sc->sc_io_res = NULL;
+ }
+ usb2_bus_mem_free_all(&sc->sc_bus, NULL);
+
+ return (0);
+}
OpenPOWER on IntegriCloud