diff options
author | thompsa <thompsa@FreeBSD.org> | 2009-02-23 18:31:00 +0000 |
---|---|---|
committer | thompsa <thompsa@FreeBSD.org> | 2009-02-23 18:31:00 +0000 |
commit | 111a707d99983acd73c4212036000aa3e88425b6 (patch) | |
tree | 691e9b9009214e6138d3913e4c022c897e386667 /sys/dev/usb/controller | |
parent | 124e83aa64b26f6b00736a45c3e25dfda8c7060e (diff) | |
download | FreeBSD-src-111a707d99983acd73c4212036000aa3e88425b6.zip FreeBSD-src-111a707d99983acd73c4212036000aa3e88425b6.tar.gz |
Move the new USB stack into its new home.
Diffstat (limited to 'sys/dev/usb/controller')
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); +} |