diff options
author | alfred <alfred@FreeBSD.org> | 2008-11-04 02:31:03 +0000 |
---|---|---|
committer | alfred <alfred@FreeBSD.org> | 2008-11-04 02:31:03 +0000 |
commit | effcf5d59c063e4380b3ca7098356d4bfa8b6633 (patch) | |
tree | f7c8289a089d1c56007a55463a568b1ccd445e5c /sys/dev/usb2/controller | |
parent | c472e6126ea8321c622e67625e038abef748b7d3 (diff) | |
download | FreeBSD-src-effcf5d59c063e4380b3ca7098356d4bfa8b6633.zip FreeBSD-src-effcf5d59c063e4380b3ca7098356d4bfa8b6633.tar.gz |
Bring in USB4BSD, Hans Petter Selasky rework of the USB stack
that includes significant features and SMP safety.
This commit includes a more or less complete rewrite of the *BSD USB
stack, including Host Controller and Device Controller drivers and
updating all existing USB drivers to use the new USB API:
1) A brief feature list:
- A new and mutex enabled USB API.
- Many USB drivers are now running Giant free.
- Linux USB kernel compatibility layer.
- New UGEN backend and libusb library, finally solves the "driver
unloading" problem. The new BSD licensed libusb20 library is fully
compatible with libusb-0.1.12 from sourceforge.
- New "usbconfig" utility, for easy configuration of USB.
- Full support for Split transactions, which means you can use your
full speed USB audio device on a high speed USB HUB.
- Full support for HS ISOC transactions, which makes writing drivers
for various HS webcams possible, for example.
- Full support for USB on embedded platforms, mostly cache flushing
and buffer invalidating stuff.
- Safer parsing of USB descriptors.
- Autodetect of annoying USB install disks.
- Support for USB device side mode, also called USB gadget mode,
using the same API like the USB host side. In other words the new
USB stack is symmetric with regard to host and device side.
- Support for USB transfers like I/O vectors, means more throughput
and less interrupts.
- ... see the FreeBSD quarterly status reports under "USB project"
2) To enable the driver in the default kernel build:
2.a) Remove all existing USB device options from your kernel config
file.
2.b) Add the following USB device options to your kernel configuration
file:
# USB core support
device usb2_core
# USB controller support
device usb2_controller
device usb2_controller_ehci
device usb2_controller_ohci
device usb2_controller_uhci
# USB mass storage support
device usb2_storage
device usb2_storage_mass
# USB ethernet support, requires miibus
device usb2_ethernet
device usb2_ethernet_aue
device usb2_ethernet_axe
device usb2_ethernet_cdce
device usb2_ethernet_cue
device usb2_ethernet_kue
device usb2_ethernet_rue
device usb2_ethernet_dav
# USB wireless LAN support
device usb2_wlan
device usb2_wlan_rum
device usb2_wlan_ral
device usb2_wlan_zyd
# USB serial device support
device usb2_serial
device usb2_serial_ark
device usb2_serial_bsa
device usb2_serial_bser
device usb2_serial_chcom
device usb2_serial_cycom
device usb2_serial_foma
device usb2_serial_ftdi
device usb2_serial_gensa
device usb2_serial_ipaq
device usb2_serial_lpt
device usb2_serial_mct
device usb2_serial_modem
device usb2_serial_moscom
device usb2_serial_plcom
device usb2_serial_visor
device usb2_serial_vscom
# USB bluetooth support
device usb2_bluetooth
device usb2_bluetooth_ng
# USB input device support
device usb2_input
device usb2_input_hid
device usb2_input_kbd
device usb2_input_ms
# USB sound and MIDI device support
device usb2_sound
2) To enable the driver at runtime:
2.a) Unload all existing USB modules. If USB is compiled into the
kernel then you might have to build a new kernel.
2.b) Load the "usb2_xxx.ko" modules under /boot/kernel having the same
base name like the kernel device option.
Submitted by: Hans Petter Selasky hselasky at c2i dot net
Reviewed by: imp, alfred
Diffstat (limited to 'sys/dev/usb2/controller')
24 files changed, 23674 insertions, 0 deletions
diff --git a/sys/dev/usb2/controller/at91dci.c b/sys/dev/usb2/controller/at91dci.c new file mode 100644 index 0000000..d5c7508 --- /dev/null +++ b/sys/dev/usb2/controller/at91dci.c @@ -0,0 +1,2547 @@ +#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/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_defs.h> + +#define USB_DEBUG_VAR at91dcidebug +#define usb2_config_td_cc at91dci_config_copy +#define usb2_config_td_softc at91dci_softc + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_config_td.h> +#include <dev/usb2/core/usb2_sw_transfer.h> +#include <dev/usb2/core/usb2_transfer.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_hub.h> +#include <dev/usb2/core/usb2_util.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> +#include <dev/usb2/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 *xfer, usb2_error_t error); +static void at91dci_do_poll(struct usb2_bus *bus); +static void at91dci_root_ctrl_poll(struct at91dci_softc *sc); +static void at91dci_standard_done(struct usb2_xfer *xfer); + +static usb2_sw_transfer_func_t at91dci_root_intr_done; +static usb2_sw_transfer_func_t at91dci_root_ctrl_done; +static usb2_config_td_command_t at91dci_root_ctrl_task; + +/* + * 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; + } + return; +} + +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); + } + return; +} + +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; + } + return; +} + +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); + } + return; +} + +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); + } + return; +} + +static void +at91dci_wakeup_peer(struct at91dci_softc *sc) +{ + uint32_t temp; + + if (!(sc->sc_flags.status_suspend)) { + return; + } + temp = AT91_UDP_READ_4(sc, AT91_UDP_GSTATE); + + if (!(temp & AT91_UDP_GSTATE_ESR)) { + return; + } + AT91_UDP_WRITE_4(sc, AT91_UDP_GSTATE, temp); + + return; +} + +static void +at91dci_rem_wakeup_set(struct usb2_device *udev, uint8_t is_on) +{ + struct at91dci_softc *sc; + uint32_t temp; + + DPRINTFN(5, "is_on=%u\n", is_on); + + mtx_assert(&udev->bus->mtx, MA_OWNED); + + sc = AT9100_DCI_BUS2SC(udev->bus); + + temp = AT91_UDP_READ_4(sc, AT91_UDP_GSTATE); + + if (is_on) { + temp |= AT91_UDP_GSTATE_ESR; + } else { + temp &= ~AT91_UDP_GSTATE_ESR; + } + + AT91_UDP_WRITE_4(sc, AT91_UDP_GSTATE, temp); + + return; +} + +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); + + return; +} + +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 = xfer->usb2_sc; + 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; + } + } + return; +} + +static void +at91dci_vbus_interrupt(struct usb2_bus *bus, uint8_t is_on) +{ + struct at91dci_softc *sc = AT9100_DCI_BUS2SC(bus); + + DPRINTFN(5, "vbus = %u\n", is_on); + + mtx_lock(&sc->sc_bus.mtx); + 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); + } + } + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +void +at91dci_interrupt(struct at91dci_softc *sc) +{ + uint32_t status; + + mtx_lock(&sc->sc_bus.mtx); + + status = AT91_UDP_READ_4(sc, AT91_UDP_ISR); + status &= AT91_UDP_INT_DEFAULT; + + if (!status) { + mtx_unlock(&sc->sc_bus.mtx); + 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); + } + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +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; + return; +} + +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->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 = xfer->usb2_sc; + 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; + } + return; +} + +static void +at91dci_timeout(void *arg) +{ + struct usb2_xfer *xfer = arg; + struct at91dci_softc *sc = xfer->usb2_sc; + + DPRINTF("xfer=%p\n", xfer); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + /* transfer is transferred */ + at91dci_device_done(xfer, USB_ERR_TIMEOUT); + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +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 = xfer->usb2_sc; + 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->udev->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usb2_transfer_timeout_ms(xfer, + &at91dci_timeout, xfer->timeout); + } + } + return; +} + +static void +at91dci_root_intr_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + struct at91dci_softc *sc = xfer->usb2_sc; + + DPRINTFN(9, "\n"); + + mtx_assert(&sc->sc_bus.mtx, 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); + return; +} + +/*------------------------------------------------------------------------* + * 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 = xfer->usb2_sc; + uint8_t ep_no; + + mtx_assert(&sc->sc_bus.mtx, 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); + return; +} + +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; + + mtx_assert(&udev->bus->mtx, 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); + return; +} + +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); + + return; +} + +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); + + mtx_assert(&udev->bus->mtx, 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))); + return; +} + +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; + + mtx_lock(&sc->sc_bus.mtx); + + /* 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.mtx, 1); + + /* 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); + + mtx_unlock(&sc->sc_bus.mtx); + + /* catch any lost interrupts */ + + at91dci_do_poll(&sc->sc_bus); + + return (0); /* success */ +} + +void +at91dci_uninit(struct at91dci_softc *sc) +{ + mtx_lock(&sc->sc_bus.mtx); + + /* 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); + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +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); + + mtx_lock(&sc->sc_bus.mtx); + at91dci_interrupt_poll(sc); + at91dci_root_ctrl_poll(sc); + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +/*------------------------------------------------------------------------* + * 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); + return; +} + +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); + return; +} + +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); + return; +} + +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); + return; +} + +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); + return; +} + +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); + return; +} + +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); + return; +} + +static void +at91dci_device_isoc_fs_enter(struct usb2_xfer *xfer) +{ + struct at91dci_softc *sc = xfer->usb2_sc; + 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); + return; +} + +static void +at91dci_device_isoc_fs_start(struct usb2_xfer *xfer) +{ + /* start TD chain */ + at91dci_start_standard_chain(xfer); + return; +} + +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 = xfer->usb2_sc; + + if (sc->sc_root_ctrl.xfer == xfer) { + sc->sc_root_ctrl.xfer = NULL; + } + at91dci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +/* + * 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) >> 16, + .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 = xfer->usb2_sc; + + sc->sc_root_ctrl.xfer = xfer; + + usb2_config_td_queue_command( + &sc->sc_config_td, NULL, &at91dci_root_ctrl_task, 0, 0); + + return; +} + +static void +at91dci_root_ctrl_task(struct at91dci_softc *sc, + struct at91dci_config_copy *cc, uint16_t refcount) +{ + at91dci_root_ctrl_poll(sc); + return; +} + +static void +at91dci_root_ctrl_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + struct at91dci_softc *sc = xfer->usb2_sc; + uint16_t value; + uint16_t index; + uint8_t use_polling; + + mtx_assert(&sc->sc_bus.mtx, 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->priv_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(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; + 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); + return; +} + +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 = xfer->usb2_sc; + + if (sc->sc_root_intr.xfer == xfer) { + sc->sc_root_intr.xfer = NULL; + } + at91dci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +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 = xfer->usb2_sc; + + sc->sc_root_intr.xfer = xfer; + return; +} + +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; + + /* + * setup xfer + */ + xfer->usb2_sc = sc; + + /* + * 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; + return; +} + +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; + } + } + return; +} + +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, + .vbus_interrupt = &at91dci_vbus_interrupt, + .rem_wakeup_set = &at91dci_rem_wakeup_set, +}; diff --git a/sys/dev/usb2/controller/at91dci.h b/sys/dev/usb2/controller/at91dci.h new file mode 100644 index 0000000..bade80a --- /dev/null +++ b/sys/dev/usb2/controller/at91dci.h @@ -0,0 +1,242 @@ +/* $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_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_config_td sc_config_td; + + 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); + +#endif /* _AT9100_DCI_H_ */ diff --git a/sys/dev/usb2/controller/at91dci_atmelarm.c b/sys/dev/usb2/controller/at91dci_atmelarm.c new file mode 100644 index 0000000..0a6f961 --- /dev/null +++ b/sys/dev/usb2/controller/at91dci_atmelarm.c @@ -0,0 +1,361 @@ +#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/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_standard.h> + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_config_td.h> +#include <dev/usb2/core/usb2_sw_transfer.h> +#include <dev/usb2/core/usb2_util.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> +#include <dev/usb2/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_interrupt(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); + (sc->sc_dci.sc_bus.methods->vbus_interrupt) + (&sc->sc_dci.sc_bus, vbus_val); + return; +} + +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); + return; +} + +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); + return; +} + +static void +at91_udp_pull_up(void *arg) +{ + at91_pio_gpio_set(PULLUP_BASE, PULLUP_MASK); + return; +} + +static void +at91_udp_pull_down(void *arg) +{ + at91_pio_gpio_clear(PULLUP_BASE, PULLUP_MASK); + return; +} + +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; + + if (sc == NULL) { + return (ENXIO); + } + /* 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; + + /* 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, 10); + + 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); + + err = usb2_config_td_setup(&sc->sc_dci.sc_config_td, sc, + &sc->sc_dci.sc_bus.mtx, NULL, 0, 4); + if (err) { + device_printf(dev, "could not setup config thread!\n"); + goto error; + } +#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_interrupt, 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_interrupt, 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_interrupt(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_config_td_unsetup(&sc->sc_dci.sc_config_td); + + 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/usb2/controller/ehci2.c b/sys/dev/usb2/controller/ehci2.c new file mode 100644 index 0000000..218a362 --- /dev/null +++ b/sys/dev/usb2/controller/ehci2.c @@ -0,0 +1,3854 @@ +/*- + * 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/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_defs.h> + +#define USB_DEBUG_VAR ehcidebug +#define usb2_config_td_cc ehci_config_copy +#define usb2_config_td_softc ehci_softc + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_config_td.h> +#include <dev/usb2/core/usb2_sw_transfer.h> +#include <dev/usb2/core/usb2_transfer.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_hub.h> +#include <dev/usb2/core/usb2_util.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> +#include <dev/usb2/controller/ehci2.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_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 usb2_config_td_command_t ehci_root_ctrl_task; +static void ehci_do_poll(struct usb2_bus *bus); +static void ehci_root_ctrl_poll(struct ehci_softc *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 { + 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; +}; + +void +ehci_iterate_hw_softc(struct usb2_bus *bus, usb2_bus_mem_sub_cb_t *cb) +{ + struct ehci_softc *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); + } + return; +} + +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.mtx, 1); + 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.mtx, 1); + hcr = EOREAD4(sc, EHCI_USBCMD); + if (!(hcr & EHCI_CMD_HCRESET)) { + 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; + + mtx_lock(&sc->sc_bus.mtx); + + DPRINTF("start\n"); + + usb2_callout_init_mtx(&sc->sc_tmo_pcd, &sc->sc_bus.mtx, + CALLOUT_RETURNUNLOCKED); + +#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)); + + err = ehci_hc_reset(sc); + if (err) { + device_printf(sc->sc_bus.bdev, "reset timeout\n"); + goto done; + } + /* + * 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"); + err = USB_ERR_IOERROR; + goto done; + } + /* 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 = + htole32(buf_res.physaddr) | + htole32(EHCI_LINK_QH); + + qh->qh_endp = + htole32(EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH)); + qh->qh_endphub = + htole32(EHCI_QH_SET_MULT(1)); + qh->qh_curqtd = 0; + + qh->qh_qtd.qtd_next = + htole32(EHCI_LINK_TERMINATE); + qh->qh_qtd.qtd_altnext = + htole32(EHCI_LINK_TERMINATE); + qh->qh_qtd.qtd_status = + htole32(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 = htole32(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 = + htole32(buf_res.physaddr) | + htole32(EHCI_LINK_SITD); + + sitd->sitd_back = + htole32(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 = + htole32(buf_res.physaddr) | + htole32(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 = + htole32(buf_res.physaddr) | + htole32(EHCI_LINK_QH); + + /* fill the QH */ + qh->qh_endp = + htole32(EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH) | EHCI_QH_HRECL); + qh->qh_endphub = htole32(EHCI_QH_SET_MULT(1)); + qh->qh_link = qh->qh_self; + qh->qh_curqtd = 0; + + /* fill the overlay qTD */ + qh->qh_qtd.qtd_next = htole32(EHCI_LINK_TERMINATE); + qh->qh_qtd.qtd_altnext = htole32(EHCI_LINK_TERMINATE); + qh->qh_qtd.qtd_status = htole32(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_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(&sc->sc_bus.mtx, 1); + hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; + if (!hcr) { + break; + } + } + if (hcr) { + device_printf(sc->sc_bus.bdev, "run timeout\n"); + err = USB_ERR_IOERROR; + goto done; + } +done: + mtx_unlock(&sc->sc_bus.mtx); + + 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(struct ehci_softc *sc) +{ + mtx_lock(&sc->sc_bus.mtx); + + usb2_callout_stop(&sc->sc_tmo_pcd); + + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + + if (ehci_hc_reset(sc)) { + DPRINTF("reset failed!\n"); + } + /* XXX let stray task complete */ + usb2_pause_mtx(&sc->sc_bus.mtx, 50); + + mtx_unlock(&sc->sc_bus.mtx); + + usb2_callout_drain(&sc->sc_tmo_pcd); + + return; +} + +void +ehci_suspend(struct ehci_softc *sc) +{ + uint32_t cmd; + uint32_t hcr; + uint8_t i; + + mtx_lock(&sc->sc_bus.mtx); + + 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.mtx, 1); + } + + 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.mtx, 1); + } + + if (hcr != EHCI_STS_HCH) { + device_printf(sc->sc_bus.bdev, + "config timeout\n"); + } + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +void +ehci_resume(struct ehci_softc *sc) +{ + struct usb2_page_search buf_res; + uint32_t cmd; + uint32_t hcr; + uint8_t i; + + mtx_lock(&sc->sc_bus.mtx); + + /* 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.mtx, + 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.mtx, 1); + } + if (hcr == EHCI_STS_HCH) { + device_printf(sc->sc_bus.bdev, "config timeout\n"); + } + usb2_pause_mtx(&sc->sc_bus.mtx, + USB_RESUME_WAIT); + + mtx_unlock(&sc->sc_bus.mtx); + + /* catch any lost interrupts */ + ehci_do_poll(&sc->sc_bus); + + return; +} + +void +ehci_shutdown(ehci_softc_t *sc) +{ + DPRINTF("stopping the HC\n"); + + mtx_lock(&sc->sc_bus.mtx); + + if (ehci_hc_reset(sc)) { + DPRINTF("reset failed!\n"); + } + mtx_unlock(&sc->sc_bus.mtx); +} + +#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))); + } + return; +} + +static void +ehci_dump_link(uint32_t link, int type) +{ + link = le32toh(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(">"); + } + return; +} + +static void +ehci_dump_qtd(ehci_qtd_t *qtd) +{ + uint32_t s; + + printf(" next="); + ehci_dump_link(qtd->qtd_next, 0); + printf(" altnext="); + ehci_dump_link(qtd->qtd_altnext, 0); + printf("\n"); + s = le32toh(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, + le32toh(qtd->qtd_buffer[s])); + } + for (s = 0; s < 5; s++) { + printf(" buffer_hi[%d]=0x%08x\n", s, + le32toh(qtd->qtd_buffer_hi[s])); + } + return; +} + +static uint8_t +ehci_dump_sqtd(ehci_qtd_t *sqtd) +{ + uint8_t temp; + + usb2_pc_cpu_invalidate(sqtd->page_cache); + printf("QTD(%p) at 0x%08x:\n", sqtd, le32toh(sqtd->qtd_self)); + ehci_dump_qtd(sqtd); + temp = (sqtd->qtd_next & htole32(EHCI_LINK_TERMINATE)) ? 1 : 0; + return (temp); +} + +static void +ehci_dump_sqtds(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(sqtd); + } + if (sqtd) { + printf("dump aborted, too many TDs\n"); + } + return; +} + +static void +ehci_dump_sqh(ehci_qh_t *qh) +{ + uint32_t endp, endphub; + + usb2_pc_cpu_invalidate(qh->page_cache); + printf("QH(%p) at 0x%08x:\n", qh, le32toh(qh->qh_self) & ~0x1F); + printf(" link="); + ehci_dump_link(qh->qh_link, 1); + printf("\n"); + endp = le32toh(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 = le32toh(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(qh->qh_curqtd, 0); + printf("\n"); + printf("Overlay qTD:\n"); + ehci_dump_qtd((void *)&qh->qh_qtd); + return; +} + +static void +ehci_dump_sitd(ehci_sitd_t *sitd) +{ + usb2_pc_cpu_invalidate(sitd->page_cache); + printf("SITD(%p) at 0x%08x\n", sitd, le32toh(sitd->sitd_self) & ~0x1F); + printf(" next=0x%08x\n", le32toh(sitd->sitd_next)); + printf(" portaddr=0x%08x dir=%s addr=%d endpt=0x%x port=0x%x huba=0x%x\n", + le32toh(sitd->sitd_portaddr), + (sitd->sitd_portaddr & htole32(EHCI_SITD_SET_DIR_IN)) + ? "in" : "out", + EHCI_SITD_GET_ADDR(le32toh(sitd->sitd_portaddr)), + EHCI_SITD_GET_ENDPT(le32toh(sitd->sitd_portaddr)), + EHCI_SITD_GET_PORT(le32toh(sitd->sitd_portaddr)), + EHCI_SITD_GET_HUBA(le32toh(sitd->sitd_portaddr))); + printf(" mask=0x%08x\n", le32toh(sitd->sitd_mask)); + printf(" status=0x%08x <%s> len=0x%x\n", le32toh(sitd->sitd_status), + (sitd->sitd_status & htole32(EHCI_SITD_ACTIVE)) ? "ACTIVE" : "", + EHCI_SITD_GET_LEN(le32toh(sitd->sitd_status))); + printf(" back=0x%08x, bp=0x%08x,0x%08x,0x%08x,0x%08x\n", + le32toh(sitd->sitd_back), + le32toh(sitd->sitd_bp[0]), + le32toh(sitd->sitd_bp[1]), + le32toh(sitd->sitd_bp_hi[0]), + le32toh(sitd->sitd_bp_hi[1])); + return; +} + +static void +ehci_dump_itd(ehci_itd_t *itd) +{ + usb2_pc_cpu_invalidate(itd->page_cache); + printf("ITD(%p) at 0x%08x\n", itd, le32toh(itd->itd_self) & ~0x1F); + printf(" next=0x%08x\n", le32toh(itd->itd_next)); + printf(" status[0]=0x%08x; <%s>\n", le32toh(itd->itd_status[0]), + (itd->itd_status[0] & htole32(EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[1]=0x%08x; <%s>\n", le32toh(itd->itd_status[1]), + (itd->itd_status[1] & htole32(EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[2]=0x%08x; <%s>\n", le32toh(itd->itd_status[2]), + (itd->itd_status[2] & htole32(EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[3]=0x%08x; <%s>\n", le32toh(itd->itd_status[3]), + (itd->itd_status[3] & htole32(EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[4]=0x%08x; <%s>\n", le32toh(itd->itd_status[4]), + (itd->itd_status[4] & htole32(EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[5]=0x%08x; <%s>\n", le32toh(itd->itd_status[5]), + (itd->itd_status[5] & htole32(EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[6]=0x%08x; <%s>\n", le32toh(itd->itd_status[6]), + (itd->itd_status[6] & htole32(EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[7]=0x%08x; <%s>\n", le32toh(itd->itd_status[7]), + (itd->itd_status[7] & htole32(EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" bp[0]=0x%08x\n", le32toh(itd->itd_bp[0])); + printf(" addr=0x%02x; endpt=0x%01x\n", + EHCI_ITD_GET_ADDR(le32toh(itd->itd_bp[0])), + EHCI_ITD_GET_ENDPT(le32toh(itd->itd_bp[0]))); + printf(" bp[1]=0x%08x\n", le32toh(itd->itd_bp[1])); + printf(" dir=%s; mpl=0x%02x\n", + (le32toh(itd->itd_bp[1]) & EHCI_ITD_SET_DIR_IN) ? "in" : "out", + EHCI_ITD_GET_MPL(le32toh(itd->itd_bp[1]))); + printf(" bp[2..6]=0x%08x,0x%08x,0x%08x,0x%08x,0x%08x\n", + le32toh(itd->itd_bp[2]), + le32toh(itd->itd_bp[3]), + le32toh(itd->itd_bp[4]), + le32toh(itd->itd_bp[5]), + le32toh(itd->itd_bp[6])); + printf(" bp_hi=0x%08x,0x%08x,0x%08x,0x%08x,\n" + " 0x%08x,0x%08x,0x%08x\n", + le32toh(itd->itd_bp_hi[0]), + le32toh(itd->itd_bp_hi[1]), + le32toh(itd->itd_bp_hi[2]), + le32toh(itd->itd_bp_hi[3]), + le32toh(itd->itd_bp_hi[4]), + le32toh(itd->itd_bp_hi[5]), + le32toh(itd->itd_bp_hi[6])); + return; +} + +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(itd); + itd = itd->prev; + } + + while (sitd && max && max--) { + ehci_dump_sitd(sitd); + sitd = sitd->prev; + } + return; +} + +#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->udev->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usb2_transfer_timeout_ms(xfer, &ehci_timeout, xfer->timeout); + } + return; +} + +#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); + + /* (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); + +#if USB_DEBUG + if (ehcidebug > 5) { + printf("%s:\n", __FUNCTION__); + ehci_dump_sqh(sqh); + } +#endif + 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); + } + /* + * set the Terminate-bit in the e_next of the QH, in case + * the transferred packet was short so that the QH still + * points at the last used TD + */ + + sqh->qh_qtd.qtd_next = htole32(EHCI_LINK_TERMINATE); + + 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_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; + + while (1) { + + usb2_pc_cpu_invalidate(td->page_cache); + status = le32toh(td->qtd_status); + + len = EHCI_QTD_GET_BYTES(status); + + /* + * Verify the status length and subtract + * the remainder from "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] -= 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_dump_sqtds(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); + return; +} + +/*------------------------------------------------------------------------* + * 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; + + 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 = le32toh(td->sitd_status); + + /* also check if first is complete */ + + td = xfer->td_transfer_first; + usb2_pc_cpu_invalidate(td->page_cache); + status |= le32toh(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 & htole32(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 = le32toh(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) +{ + mtx_assert(&sc->sc_bus.mtx, 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); + + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +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; + } + } + return; +} + +/*------------------------------------------------------------------------* + * 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; + + mtx_lock(&sc->sc_bus.mtx); + + 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: + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +/* + * called when a request does not complete + */ +static void +ehci_timeout(void *arg) +{ + struct usb2_xfer *xfer = arg; + ehci_softc_t *sc = xfer->usb2_sc; + + DPRINTF("xfer=%p\n", xfer); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + /* transfer is transferred */ + ehci_device_done(xfer, USB_ERR_TIMEOUT); + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +static void +ehci_do_poll(struct usb2_bus *bus) +{ + struct ehci_softc *sc = EHCI_BUS2SC(bus); + + mtx_lock(&sc->sc_bus.mtx); + ehci_interrupt_poll(sc); + ehci_root_ctrl_poll(sc); + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +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 = htole32(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 | htole32(EHCI_QTD_SET_BYTES(average)); + + if (average == 0) { + + if (temp->auto_data_toggle == 0) { + + /* update data toggle, ZLP case */ + + temp->qtd_status ^= htole32(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 ^= htole32(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] = htole32(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] = htole32(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] = htole32(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; + + return; +} + +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->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.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 |= htole32(EHCI_QTD_SET_TOGGLE(1)); + } + temp.auto_data_toggle = 0; + } else { + temp.auto_data_toggle = 1; + } + + if (usb2_get_speed(xfer->udev) != USB_SPEED_HIGH) { + /* max 3 retries */ + temp.qtd_status |= htole32(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 &= htole32(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 &= htole32(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) ? + htole32(EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(EHCI_QTD_PID_IN)) : + htole32(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 &= htole32(EHCI_QTD_SET_CERR(3) | + EHCI_QTD_SET_TOGGLE(1)); + temp.qtd_status |= + (UE_GET_DIR(xfer->endpoint) == UE_DIR_OUT) ? + htole32(EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(EHCI_QTD_PID_IN) | + EHCI_QTD_SET_TOGGLE(1)) : + htole32(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 = htole32(EHCI_LINK_TERMINATE); + td->qtd_altnext = htole32(EHCI_LINK_TERMINATE); + td->qtd_status |= htole32(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(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->udev) == USB_SPEED_HIGH) { + qh_endp |= (EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH) | + EHCI_QH_DTC | EHCI_QH_SET_NRL(8)); + } else { + + if (usb2_get_speed(xfer->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 = htole32(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->udev->hs_hub_addr) | + EHCI_QH_SET_PORT(xfer->udev->hs_port_no)); + + qh->qh_endphub = htole32(qh_endphub); + qh->qh_curqtd = htole32(0); + + /* fill the overlay qTD */ + qh->qh_qtd.qtd_status = htole32(0); + + if (temp.auto_data_toggle) { + + /* let the hardware compute the data toggle */ + + qh->qh_endp &= ~htole32(EHCI_QH_DTC); + + if (xfer->pipe->toggle_next) { + /* DATA1 is next */ + qh->qh_qtd.qtd_status |= htole32(EHCI_QTD_SET_TOGGLE(1)); + } + } + td = xfer->td_transfer_first; + + qh->qh_qtd.qtd_next = td->qtd_self; + qh->qh_qtd.qtd_altnext = htole32(EHCI_LINK_TERMINATE); + + usb2_pc_cpu_flush(qh->page_cache); + + EHCI_APPEND_QH(qh, *qh_last); + return; +} + +static void +ehci_root_intr_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + struct ehci_softc *sc = xfer->usb2_sc; + uint16_t i; + uint16_t m; + + mtx_assert(&sc->sc_bus.mtx, 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(td); + } +#endif + usb2_pc_cpu_invalidate(td->page_cache); + status = le32toh(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; + + return; +} + +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(td); + } +#endif + + usb2_pc_cpu_invalidate(td->page_cache); + status = le32toh(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; + + return; +} + +/* 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 = xfer->usb2_sc; + + mtx_assert(&sc->sc_bus.mtx, 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(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); + return; +} + +/*------------------------------------------------------------------------* + * 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); + return; +} + +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 = xfer->usb2_sc; + + /* 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); + return; +} + +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); + return; +} + +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 = xfer->usb2_sc; + + /* 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); + return; +} + +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 = xfer->usb2_sc; + uint16_t best; + uint16_t bit; + uint16_t x; + uint8_t slot; + + /* Allocate a microframe slot first: */ + + slot = usb2_intr_schedule_adjust + (xfer->udev, xfer->max_frame_size, USB_HS_MICRO_FRAMES_MAX); + + if (usb2_get_speed(xfer->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); + return; +} + +static void +ehci_device_intr_close(struct usb2_xfer *xfer) +{ + ehci_softc_t *sc = xfer->usb2_sc; + uint8_t slot; + + slot = usb2_intr_schedule_adjust + (xfer->udev, -(xfer->max_frame_size), xfer->usb2_uframe); + + sc->sc_intr_stat[xfer->qh_pos]--; + + ehci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +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 = xfer->usb2_sc; + + /* 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); + return; +} + +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_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->udev->hs_hub_addr) | + EHCI_SITD_SET_PORT(xfer->udev->hs_port_no); + + if (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) { + sitd_portaddr |= EHCI_SITD_SET_DIR_IN; + } + sitd_portaddr = htole32(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 = htole32(EHCI_LINK_TERMINATE); + + usb2_pc_cpu_flush(td->page_cache); + } + } + return; +} + +static void +ehci_device_isoc_fs_close(struct usb2_xfer *xfer) +{ + ehci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +ehci_device_isoc_fs_enter(struct usb2_xfer *xfer) +{ + struct usb2_page_search buf_res; + ehci_softc_t *sc = xfer->usb2_sc; + 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->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] = htole32(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] = htole32(temp); + + td->sitd_mask = htole32(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(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); + + return; +} + +static void +ehci_device_isoc_fs_start(struct usb2_xfer *xfer) +{ + /* put transfer on interrupt queue */ + ehci_transfer_intr_enqueue(xfer); + return; +} + +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_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] = htole32(temp); + + /* set transfer multiplier */ + td->itd_bp[2] = htole32(xfer->max_packet_count & 3); + + usb2_pc_cpu_flush(td->page_cache); + } + } + return; +} + +static void +ehci_device_isoc_hs_close(struct usb2_xfer *xfer) +{ + ehci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +ehci_device_isoc_hs_enter(struct usb2_xfer *xfer) +{ + struct usb2_page_search buf_res; + ehci_softc_t *sc = xfer->usb2_sc; + 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] = htole32(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] &= htole32(0xFFF); + td->itd_bp[0] |= htole32(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] |= htole32(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] &= htole32(0xFFF); + td->itd_bp[page_no] |= htole32(page_addr); + } + } + } + /* set IOC bit if we are complete */ + if (nframes == 0) { + td->itd_status[7] |= htole32(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(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); + + return; +} + +static void +ehci_device_isoc_hs_start(struct usb2_xfer *xfer) +{ + /* put transfer on interrupt queue */ + ehci_transfer_intr_enqueue(xfer); + return; +} + +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 = xfer->usb2_sc; + + if (sc->sc_root_ctrl.xfer == xfer) { + sc->sc_root_ctrl.xfer = NULL; + } + ehci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +/* 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 = xfer->usb2_sc; + + DPRINTF("\n"); + + sc->sc_root_ctrl.xfer = xfer; + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &ehci_root_ctrl_task, 0, 0); + + return; +} + +static void +ehci_root_ctrl_task(struct ehci_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + ehci_root_ctrl_poll(sc); + return; +} + +static void +ehci_root_ctrl_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + struct ehci_softc *sc = xfer->usb2_sc; + char *ptr; + uint32_t port; + uint32_t v; + uint16_t i; + uint16_t value; + uint16_t index; + uint8_t l; + uint8_t use_polling; + + mtx_assert(&sc->sc_bus.mtx, 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->priv_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: + EOWRITE4(sc, port, v & ~EHCI_PS_SUSP); + 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)); + sc->sc_hub_desc.hubd.bPwrOn2PwrGood = 200; /* XXX can't find out? */ + 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); + 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) + 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 (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.mtx, + USB_PORT_ROOT_RESET_DELAY); + } + + /* Terminate reset sequence. */ + 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.mtx, + 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(struct ehci_softc *sc) +{ + usb2_sw_transfer(&sc->sc_root_ctrl, + &ehci_root_ctrl_done); + return; +} + +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 = xfer->usb2_sc; + + if (sc->sc_root_intr.xfer == xfer) { + sc->sc_root_intr.xfer = NULL; + } + ehci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +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 = xfer->usb2_sc; + + sc->sc_root_intr.xfer = xfer; + return; +} + +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; + + /* + * setup xfer + */ + xfer->usb2_sc = sc; + + /* + * 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 = htole32(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 = htole32(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 = htole32(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 = htole32(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; + } + return; +} + +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 */ + 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, +}; diff --git a/sys/dev/usb2/controller/ehci2.h b/sys/dev/usb2/controller/ehci2.h new file mode 100644 index 0000000..f002d55 --- /dev/null +++ b/sys/dev/usb2/controller/ehci2.h @@ -0,0 +1,515 @@ +/* $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_ + +/* 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_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_config_td sc_config_td; + 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 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; + 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; + 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 */ + + 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/usb2/controller/ehci2_pci.c b/sys/dev/usb2/controller/ehci2_pci.c new file mode 100644 index 0000000..1fb7dc2 --- /dev/null +++ b/sys/dev/usb2/controller/ehci2_pci.c @@ -0,0 +1,498 @@ +/*- + * 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/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_standard.h> + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_config_td.h> +#include <dev/usb2/core/usb2_sw_transfer.h> +#include <dev/usb2/core/usb2_util.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> +#include <dev/usb2/controller/usb2_pci.h> +#include <dev/usb2/controller/ehci2.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; + + if (sc == NULL) { + device_printf(self, "Could not allocate sc\n"); + return (ENXIO); + } + /* 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_dev = self; + + 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: + sc->sc_bus.usbrev = USB_REV_UNKNOWN; + 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)); + } + + err = usb2_config_td_setup(&sc->sc_config_td, sc, &sc->sc_bus.mtx, + NULL, 0, 4); + if (err) { + device_printf(self, "could not setup config thread!\n"); + goto error; + } +#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; + + usb2_config_td_drain(&sc->sc_config_td); + + 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_config_td_unsetup(&sc->sc_config_td); + + 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, 10); /* wait 10ms */ + } + } + return; +} + +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, usb2_controller, 1, 1, 1); +MODULE_DEPEND(ehci, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/controller/musb2_otg.c b/sys/dev/usb2/controller/musb2_otg.c new file mode 100644 index 0000000..b036d5c --- /dev/null +++ b/sys/dev/usb2/controller/musb2_otg.c @@ -0,0 +1,2945 @@ +/* $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/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_defs.h> + +#define USB_DEBUG_VAR musbotgdebug +#define usb2_config_td_cc musbotg_config_copy +#define usb2_config_td_softc musbotg_softc + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_config_td.h> +#include <dev/usb2/core/usb2_sw_transfer.h> +#include <dev/usb2/core/usb2_transfer.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_hub.h> +#include <dev/usb2/core/usb2_util.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> +#include <dev/usb2/controller/musb2_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 *xfer, usb2_error_t error); +static void musbotg_do_poll(struct usb2_bus *bus); +static void musbotg_root_ctrl_poll(struct musbotg_softc *sc); +static void musbotg_standard_done(struct usb2_xfer *xfer); +static void musbotg_interrupt_poll(struct musbotg_softc *sc); + +static usb2_sw_transfer_func_t musbotg_root_intr_done; +static usb2_sw_transfer_func_t musbotg_root_ctrl_done; +static usb2_config_td_command_t musbotg_root_ctrl_task; + +/* + * 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; + } + return; +} + +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 */ + } + return; +} + +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; + } + return; +} + +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); + return; +} + +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); + } + return; +} + +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); + } + return; +} + +static void +musbotg_wakeup_peer(struct usb2_xfer *xfer) +{ + struct musbotg_softc *sc = xfer->usb2_sc; + uint8_t temp; + uint8_t use_polling; + + if (!(sc->sc_flags.status_suspend)) { + return; + } + use_polling = mtx_owned(xfer->priv_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.mtx, 8); + } + + temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); + temp &= ~MUSB2_MASK_RESUME; + MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); + return; +} + +static void +musbotg_rem_wakeup_set(struct usb2_device *udev, uint8_t is_on) +{ + DPRINTFN(4, "is_on=%u\n", is_on); + return; +} + +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); + return; +} + +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 = xfer->usb2_sc; + + /* 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; + } + } + + return; +} + +static void +musbotg_vbus_interrupt(struct usb2_bus *bus, uint8_t is_on) +{ + struct musbotg_softc *sc = MUSBOTG_BUS2SC(bus); + + DPRINTFN(4, "vbus = %u\n", is_on); + + mtx_lock(&sc->sc_bus.mtx); + 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); + } + } + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +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; + + mtx_lock(&sc->sc_bus.mtx); + +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; + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +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; + return; +} + +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->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 = xfer->usb2_sc; + 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; + return; +} + +static void +musbotg_timeout(void *arg) +{ + struct usb2_xfer *xfer = arg; + struct musbotg_softc *sc = xfer->usb2_sc; + + DPRINTFN(1, "xfer=%p\n", xfer); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + /* transfer is transferred */ + musbotg_device_done(xfer, USB_ERR_TIMEOUT); + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +static void +musbotg_ep_int_set(struct usb2_xfer *xfer, uint8_t on) +{ + struct musbotg_softc *sc = xfer->usb2_sc; + 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); + } + } + return; +} + +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->udev->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usb2_transfer_timeout_ms(xfer, + &musbotg_timeout, xfer->timeout); + } + } + return; +} + +static void +musbotg_root_intr_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + struct musbotg_softc *sc = xfer->usb2_sc; + + DPRINTFN(8, "\n"); + + mtx_assert(&sc->sc_bus.mtx, 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); + return; +} + +/*------------------------------------------------------------------------* + * 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) +{ + mtx_assert(xfer->usb2_mtx, 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); + return; +} + +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; + + mtx_assert(&udev->bus->mtx, 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); + } + return; +} + +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); + } + } + return; +} + +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); + + mtx_assert(&udev->bus->mtx, 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))); + return; +} + +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; + + mtx_lock(&sc->sc_bus.mtx); + + /* 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.mtx, 1); + + /* 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.mtx, 10); + + /* 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); + + mtx_unlock(&sc->sc_bus.mtx); + + /* catch any lost interrupts */ + + musbotg_do_poll(&sc->sc_bus); + + return (0); /* success */ +} + +void +musbotg_uninit(struct musbotg_softc *sc) +{ + mtx_lock(&sc->sc_bus.mtx); + + /* 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); + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +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); + + mtx_lock(&sc->sc_bus.mtx); + musbotg_interrupt_poll(sc); + musbotg_root_ctrl_poll(sc); + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +/*------------------------------------------------------------------------* + * 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); + return; +} + +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); + return; +} + +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); + return; +} + +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); + return; +} + +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); + return; +} + +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); + return; +} + +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); + return; +} + +static void +musbotg_device_isoc_enter(struct usb2_xfer *xfer) +{ + struct musbotg_softc *sc = xfer->usb2_sc; + 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->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); + return; +} + +static void +musbotg_device_isoc_start(struct usb2_xfer *xfer) +{ + /* start TD chain */ + musbotg_start_standard_chain(xfer); + return; +} + +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 = xfer->usb2_sc; + + if (sc->sc_root_ctrl.xfer == xfer) { + sc->sc_root_ctrl.xfer = NULL; + } + musbotg_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +/* + * 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 = xfer->usb2_sc; + + sc->sc_root_ctrl.xfer = xfer; + + usb2_config_td_queue_command( + &sc->sc_config_td, NULL, &musbotg_root_ctrl_task, 0, 0); + + return; +} + +static void +musbotg_root_ctrl_task(struct musbotg_softc *sc, + struct musbotg_config_copy *cc, uint16_t refcount) +{ + musbotg_root_ctrl_poll(sc); + return; +} + +static void +musbotg_root_ctrl_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + struct musbotg_softc *sc = xfer->usb2_sc; + uint16_t value; + uint16_t index; + uint8_t use_polling; + + mtx_assert(&sc->sc_bus.mtx, 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->priv_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); + return; +} + +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 = xfer->usb2_sc; + + if (sc->sc_root_intr.xfer == xfer) { + sc->sc_root_intr.xfer = NULL; + } + musbotg_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +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 = xfer->usb2_sc; + + sc->sc_root_intr.xfer = xfer; + return; +} + +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; + + /* + * setup xfer + */ + xfer->usb2_sc = sc; + + /* + * 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; + return; +} + +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; + } + } + return; +} + +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, + .vbus_interrupt = &musbotg_vbus_interrupt, + .rem_wakeup_set = &musbotg_rem_wakeup_set, +}; diff --git a/sys/dev/usb2/controller/musb2_otg.h b/sys/dev/usb2/controller/musb2_otg.h new file mode 100644 index 0000000..68193e2 --- /dev/null +++ b/sys/dev/usb2/controller/musb2_otg.h @@ -0,0 +1,403 @@ +/* $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_ + +/* 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_config_td sc_config_td; + struct usb2_hw_ep_profile sc_hw_ep_profile[16]; + 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); + +#endif /* _MUSB2_OTG_H_ */ diff --git a/sys/dev/usb2/controller/musb2_otg_atmelarm.c b/sys/dev/usb2/controller/musb2_otg_atmelarm.c new file mode 100644 index 0000000..58ee134 --- /dev/null +++ b/sys/dev/usb2/controller/musb2_otg_atmelarm.c @@ -0,0 +1,256 @@ +/* $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/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_standard.h> + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_config_td.h> +#include <dev/usb2/core/usb2_sw_transfer.h> +#include <dev/usb2/core/usb2_util.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> +#include <dev/usb2/controller/musb2_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_interrupt(struct musbotg_super_softc *sc) +{ + uint8_t vbus_val = 1; /* fake VBUS on - TODO */ + + /* just forward it */ + + (sc->sc_otg.sc_bus.methods->vbus_interrupt) + (&sc->sc_otg.sc_bus, vbus_val); + return; +} + +static void +musbotg_clocks_on(void *arg) +{ +#if 0 + struct musbotg_super_softc *sc = arg; + +#endif + + return; +} + +static void +musbotg_clocks_off(void *arg) +{ +#if 0 + struct musbotg_super_softc *sc = arg; + +#endif + + return; +} + +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; + + if (sc == NULL) { + return (ENXIO); + } + /* 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; + + /* 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); + + err = usb2_config_td_setup(&sc->sc_otg.sc_config_td, sc, + &sc->sc_otg.sc_bus.mtx, NULL, 0, 4); + if (err) { + device_printf(dev, "could not setup config thread!\n"); + goto error; + } +#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_interrupt(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_config_td_unsetup(&sc->sc_otg.sc_config_td); + + 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, usb2_controller, 1, 1, 1); +MODULE_DEPEND(musbotg, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/controller/ohci2.c b/sys/dev/usb2/controller/ohci2.c new file mode 100644 index 0000000..54133ba --- /dev/null +++ b/sys/dev/usb2/controller/ohci2.c @@ -0,0 +1,2802 @@ +/*- + * 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/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_defs.h> + +#define USB_DEBUG_VAR ohcidebug +#define usb2_config_td_cc ohci_config_copy +#define usb2_config_td_softc ohci_softc + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_config_td.h> +#include <dev/usb2/core/usb2_sw_transfer.h> +#include <dev/usb2/core/usb2_transfer.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_hub.h> +#include <dev/usb2/core/usb2_util.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> +#include <dev/usb2/controller/ohci2.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 usb2_config_td_command_t ohci_root_ctrl_task; +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); + } + return; +} + +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(&sc->sc_bus.mtx, 1); + 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(&sc->sc_bus.mtx, + 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(&sc->sc_bus.mtx, + 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(&sc->sc_bus.mtx, + 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(&sc->sc_bus.mtx, + 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; + + mtx_lock(&sc->sc_bus.mtx); + + 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.mtx, + CALLOUT_RETURNUNLOCKED); + +#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)) { + mtx_unlock(&sc->sc_bus.mtx); + return (USB_ERR_INVAL); + } else { + mtx_unlock(&sc->sc_bus.mtx); + /* 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) +{ + mtx_lock(&sc->sc_bus.mtx); + + usb2_callout_stop(&sc->sc_tmo_rhsc); + + OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS); + OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); + + /* XXX let stray task complete */ + usb2_pause_mtx(&sc->sc_bus.mtx, 50); + + mtx_unlock(&sc->sc_bus.mtx); + + usb2_callout_drain(&sc->sc_tmo_rhsc); + + return; +} + +/* NOTE: suspend/resume is called from + * interrupt context and cannot sleep! + */ +void +ohci_suspend(ohci_softc_t *sc) +{ + uint32_t ctl; + + mtx_lock(&sc->sc_bus.mtx); + +#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.mtx, + USB_RESUME_WAIT); + + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +void +ohci_resume(ohci_softc_t *sc) +{ + uint32_t ctl; + + mtx_lock(&sc->sc_bus.mtx); + +#if USB_DEBUG + DPRINTF("\n"); + if (ohcidebug > 2) { + ohci_dumpregs(sc); + } +#endif + /* some broken BIOSes never initialize the Controller chip */ + ohci_controller_init(sc); + + 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.mtx, USB_RESUME_DELAY); + ctl = (ctl & ~OHCI_HCFS_MASK) | OHCI_HCFS_OPERATIONAL; + OWRITE4(sc, OHCI_CONTROL, ctl); + usb2_pause_mtx(&sc->sc_bus.mtx, USB_RESUME_RECOVERY); + sc->sc_control = sc->sc_intre = 0; + + mtx_unlock(&sc->sc_bus.mtx); + + /* catch any lost interrupts */ + ohci_do_poll(&sc->sc_bus); + + return; +} + +#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)); + return; +} +static void +ohci_dump_tds(ohci_td_t *std) +{ + for (; std; std = std->obj_next) { + if (ohci_dump_td(std)) { + break; + } + } + return; +} + +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; + } + } + return; +} + +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)); + return; +} + +#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->udev->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usb2_transfer_timeout_ms(xfer, &ohci_timeout, xfer->timeout); + } + return; +} + +#define OHCI_APPEND_QH(sed,td_self,last) (last) = _ohci_append_qh(sed,td_self,last) +static ohci_ed_t * +_ohci_append_qh(ohci_ed_t *sed, uint32_t td_self, ohci_ed_t *last) +{ + DPRINTFN(11, "%p to %p\n", sed, last); + + /* (sc->sc_bus.mtx) must be locked */ + + sed->next = last->next; + sed->ed_next = last->ed_next; + sed->ed_tailp = 0; + sed->ed_headp = td_self; + + 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.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); + } + /* + * terminate transfer in case the transferred packet was + * short so that the ED still points at the last used TD + */ + sed->ed_flags |= htole32(OHCI_ED_SKIP); + sed->ed_headp = sed->ed_tailp; + + 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); + return; +} + +#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; + + 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) { + /* + * subtract remaining length from + * "frlengths[]" + */ + xfer->frlengths[xfer->aframes] -= temp; + } + } + /* 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); + return; +} + +/*------------------------------------------------------------------------* + * 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); + } + return; +} + +/*------------------------------------------------------------------------* + * 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"); + + mtx_assert(&sc->sc_bus.mtx, 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); + + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +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; + } + } + return; +} + +/*------------------------------------------------------------------------* + * 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; + + mtx_lock(&sc->sc_bus.mtx); + + 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: + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +/* + * called when a request does not complete + */ +static void +ohci_timeout(void *arg) +{ + struct usb2_xfer *xfer = arg; + ohci_softc_t *sc = xfer->usb2_sc; + + DPRINTF("xfer=%p\n", xfer); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + /* transfer is transferred */ + ohci_device_done(xfer, USB_ERR_TIMEOUT); + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +static void +ohci_do_poll(struct usb2_bus *bus) +{ + struct ohci_softc *sc = OHCI_BUS2SC(bus); + + mtx_lock(&sc->sc_bus.mtx); + ohci_interrupt_poll(sc); + ohci_root_ctrl_poll(sc); + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +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; + + return; +} + +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->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->udev->speed == USB_SPEED_LOW) { + ed_flags |= OHCI_ED_SPEED; + } + ed->ed_flags = htole32(ed_flags); + + usb2_pc_cpu_flush(ed->page_cache); + + td = xfer->td_transfer_first; + + OHCI_APPEND_QH(ed, td->td_self, *ed_last); + + if (methods == &ohci_device_bulk_methods) { + ohci_softc_t *sc = xfer->usb2_sc; + + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF); + } + if (methods == &ohci_device_ctrl_methods) { + ohci_softc_t *sc = xfer->usb2_sc; + + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF); + } + return; +} + +static void +ohci_root_intr_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + ohci_softc_t *sc = xfer->usb2_sc; + uint32_t hstatus; + uint16_t i; + uint16_t m; + + mtx_assert(&sc->sc_bus.mtx, 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 = xfer->usb2_sc; + ohci_ed_t *ed; + + mtx_assert(&sc->sc_bus.mtx, 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); + return; +} + +/*------------------------------------------------------------------------* + * 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); + return; +} + +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 = xfer->usb2_sc; + + /* 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); + return; +} + +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); + return; +} + +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 = xfer->usb2_sc; + + /* 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); + return; +} + +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 = xfer->usb2_sc; + 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); + return; +} + +static void +ohci_device_intr_close(struct usb2_xfer *xfer) +{ + ohci_softc_t *sc = xfer->usb2_sc; + + sc->sc_intr_stat[xfer->qh_pos]--; + + ohci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +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 = xfer->usb2_sc; + + /* 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); + return; +} + +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); + return; +} + +static void +ohci_device_isoc_enter(struct usb2_xfer *xfer) +{ + struct usb2_page_search buf_res; + ohci_softc_t *sc = xfer->usb2_sc; + 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->udev->speed == USB_SPEED_LOW) { + ed_flags |= OHCI_ED_SPEED; + } + ed->ed_flags = htole32(ed_flags); + + usb2_pc_cpu_flush(ed->page_cache); + + td = xfer->td_transfer_first; + + OHCI_APPEND_QH(ed, td->itd_self, sc->sc_isoc_p_last); + return; +} + +static void +ohci_device_isoc_start(struct usb2_xfer *xfer) +{ + /* put transfer on interrupt queue */ + ohci_transfer_intr_enqueue(xfer); + return; +} + +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 = xfer->usb2_sc; + + if (sc->sc_root_ctrl.xfer == xfer) { + sc->sc_root_ctrl.xfer = NULL; + } + ohci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +/* 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 = xfer->usb2_sc; + + sc->sc_root_ctrl.xfer = xfer; + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &ohci_root_ctrl_task, 0, 0); + + return; +} + +static void +ohci_root_ctrl_task(struct ohci_softc *sc, + struct ohci_config_copy *cc, uint16_t refcount) +{ + ohci_root_ctrl_poll(sc); + return; +} + +static void +ohci_root_ctrl_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + ohci_softc_t *sc = xfer->usb2_sc; + char *ptr; + uint32_t port; + uint32_t v; + uint16_t value; + uint16_t index; + uint8_t l; + uint8_t use_polling; + + mtx_assert(&sc->sc_bus.mtx, 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->priv_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); + mtx_lock(&sc->sc_bus.mtx); + } + 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.mtx, + 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); + return; +} + +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 = xfer->usb2_sc; + + if (sc->sc_root_intr.xfer == xfer) { + sc->sc_root_intr.xfer = NULL; + } + ohci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +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 = xfer->usb2_sc; + + sc->sc_root_intr.xfer = xfer; + return; +} + +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; + + /* + * setup xfer + */ + xfer->usb2_sc = sc; + + 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; + } + return; +} + +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; + } + } + return; +} + +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 */ + 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, +}; diff --git a/sys/dev/usb2/controller/ohci2.h b/sys/dev/usb2/controller/ohci2.h new file mode 100644 index 0000000..fefde6c --- /dev/null +++ b/sys/dev/usb2/controller/ohci2.h @@ -0,0 +1,364 @@ +/* $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_ + +/* 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_config_td sc_config_td; + 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 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/usb2/controller/ohci2_atmelarm.c b/sys/dev/usb2/controller/ohci2_atmelarm.c new file mode 100644 index 0000000..83325be --- /dev/null +++ b/sys/dev/usb2/controller/ohci2_atmelarm.c @@ -0,0 +1,232 @@ +/*- + * 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/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_standard.h> + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_config_td.h> +#include <dev/usb2/core/usb2_sw_transfer.h> +#include <dev/usb2/core/usb2_util.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> +#include <dev/usb2/controller/ohci2.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; + + if (sc == NULL) { + return (ENXIO); + } + /* 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)); + + err = usb2_config_td_setup(&sc->sc_ohci.sc_config_td, sc, + &sc->sc_ohci.sc_bus.mtx, NULL, 0, 4); + if (err) { + device_printf(dev, "could not setup config thread!\n"); + goto error; + } +#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_config_td_unsetup(&sc->sc_ohci.sc_config_td); + + 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, usb2_controller, 1, 1, 1); +MODULE_DEPEND(ohci, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/controller/ohci2_pci.c b/sys/dev/usb2/controller/ohci2_pci.c new file mode 100644 index 0000000..60ae0b6 --- /dev/null +++ b/sys/dev/usb2/controller/ohci2_pci.c @@ -0,0 +1,392 @@ +/*- + * 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/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_defs.h> + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_config_td.h> +#include <dev/usb2/core/usb2_sw_transfer.h> +#include <dev/usb2/core/usb2_util.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> +#include <dev/usb2/controller/usb2_pci.h> +#include <dev/usb2/controller/ohci2.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; + + if (sc == NULL) { + device_printf(self, "Could not allocate sc\n"); + return (ENXIO); + } + /* 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); + + 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)); + } + + err = usb2_config_td_setup(&sc->sc_config_td, sc, &sc->sc_bus.mtx, + NULL, 0, 4); + if (err) { + device_printf(self, "could not setup config thread!\n"); + goto error; + } + /* 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; + + usb2_config_td_drain(&sc->sc_config_td); + + 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_config_td_unsetup(&sc->sc_config_td); + + 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, usb2_controller, 1, 1, 1); +MODULE_DEPEND(ohci, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/controller/uhci2.c b/sys/dev/usb2/controller/uhci2.c new file mode 100644 index 0000000..9a938d9 --- /dev/null +++ b/sys/dev/usb2/controller/uhci2.c @@ -0,0 +1,3256 @@ +/*- + * 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/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_defs.h> + +#define USB_DEBUG_VAR uhcidebug +#define usb2_config_td_cc uhci_config_copy +#define usb2_config_td_softc uhci_softc + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_config_td.h> +#include <dev/usb2/core/usb2_sw_transfer.h> +#include <dev/usb2/core/usb2_transfer.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_hub.h> +#include <dev/usb2/core/usb2_util.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> +#include <dev/usb2/controller/uhci2.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 usb2_config_td_command_t uhci_root_ctrl_task; +static void uhci_root_ctrl_poll(struct uhci_softc *sc); +static void uhci_do_poll(struct usb2_bus *bus); +static void uhci_device_done(struct usb2_xfer *xfer, usb2_error_t error); +static void uhci_transfer_intr_enqueue(struct usb2_xfer *xfer); +static void uhci_root_intr_check(void *arg); +static void uhci_timeout(void *arg); +static uint8_t uhci_check_transfer(struct usb2_xfer *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); + } + return; +} + +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; + + return; +} + +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; + + return; +} + +void +uhci_reset(uhci_softc_t *sc) +{ + struct usb2_page_search buf_res; + uint16_t n; + + mtx_assert(&sc->sc_bus.mtx, 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.mtx, + 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.mtx, 1); + + 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.mtx, 1); + + /* 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); + return; +} + +static void +uhci_start(uhci_softc_t *sc) +{ + mtx_assert(&sc->sc_bus.mtx, 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.mtx, 1); + + /* 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; + + mtx_lock(&sc->sc_bus.mtx); + + 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; + + /* reset the controller */ + uhci_reset(sc); + + /* start the controller */ + uhci_start(sc); + + mtx_unlock(&sc->sc_bus.mtx); + + /* 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) +{ + mtx_lock(&sc->sc_bus.mtx); + +#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.mtx, USB_RESUME_WAIT); + + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +void +uhci_resume(uhci_softc_t *sc) +{ + mtx_lock(&sc->sc_bus.mtx); + + /* reset the controller */ + + uhci_reset(sc); + + /* force global resume */ + + UHCICMD(sc, UHCI_CMD_FGR); + + usb2_pause_mtx(&sc->sc_bus.mtx, + USB_RESUME_DELAY); + + /* and start traffic again */ + + uhci_start(sc); + +#if USB_DEBUG + if (uhcidebug > 2) { + uhci_dumpregs(sc); + } +#endif + + mtx_unlock(&sc->sc_bus.mtx); + + /* catch lost interrupts */ + uhci_do_poll(&sc->sc_bus); + + return; +} + +#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)); + return; +} + +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); + return; +} + +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"); + + return; +} + +static void +uhci_dump_tds(uhci_td_t *td) +{ + for (; + td != NULL; + td = td->obj_next) { + if (uhci_dump_td(td)) { + break; + } + } + return; +} + +#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); + } + return; +} + +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); + } + return; +} + +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->udev->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usb2_transfer_timeout_ms(xfer, &uhci_timeout, xfer->timeout); + } + return; +} + +#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,td,last) (last) = _uhci_append_qh(sqh,td,last) +static uhci_qh_t * +_uhci_append_qh(uhci_qh_t *sqh, uhci_td_t *td, uhci_qh_t *last) +{ + DPRINTFN(11, "%p to %p\n", sqh, last); + + /* (sc->sc_bus.mtx) must be locked */ + + sqh->e_next = td; + sqh->qh_e_next = td->td_self; + + 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); + } + /* + * set the Terminate-bit in the e_next of the QH, in case + * the transferred packet was short so that the QH still + * points at the last used TD + */ + sqh->qh_e_next = htole32(UHCI_PTR_T); + + 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; + + return; +} + +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); + return; +} + +/*------------------------------------------------------------------------* + * 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); + return; +} + +/*------------------------------------------------------------------------* + * 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; + } + } + return; +} + +/*------------------------------------------------------------------------* + * 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; + + mtx_lock(&sc->sc_bus.mtx); + + 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 */ + printf("%s: host controller halted\n", + __FUNCTION__); +#if USB_DEBUG + 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: + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +/* + * called when a request does not complete + */ +static void +uhci_timeout(void *arg) +{ + struct usb2_xfer *xfer = arg; + uhci_softc_t *sc = xfer->usb2_sc; + + DPRINTF("xfer=%p\n", xfer); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + /* transfer is transferred */ + uhci_device_done(xfer, USB_ERR_TIMEOUT); + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +static void +uhci_do_poll(struct usb2_bus *bus) +{ + struct uhci_softc *sc = UHCI_BUS2SC(bus); + + mtx_lock(&sc->sc_bus.mtx); + uhci_interrupt_poll(sc); + uhci_root_ctrl_poll(sc); + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +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; + + return; +} + +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->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->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 = xfer->usb2_sc; + uhci_qh_t *qh; + + mtx_assert(&sc->sc_bus.mtx, 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); + + qh->e_next = 0; + qh->qh_e_next = htole32(UHCI_PTR_T); + + usb2_pc_cpu_flush(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->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); + return; +} + +/*------------------------------------------------------------------------* + * 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); + return; +} + +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 = xfer->usb2_sc; + 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]; + + UHCI_APPEND_QH(qh, td, sc->sc_bulk_p_last); + uhci_add_loop(sc); + xfer->flags_int.bandwidth_reclaimed = 1; + + /* put transfer on interrupt queue */ + uhci_transfer_intr_enqueue(xfer); + return; +} + +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); + return; +} + +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 = xfer->usb2_sc; + 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]; + + /* + * NOTE: some devices choke on bandwidth- reclamation for control + * transfers + */ + if (xfer->udev->speed == USB_SPEED_LOW) { + UHCI_APPEND_QH(qh, td, sc->sc_ls_ctl_p_last); + } else { + UHCI_APPEND_QH(qh, td, sc->sc_fs_ctl_p_last); + } + /* put transfer on interrupt queue */ + uhci_transfer_intr_enqueue(xfer); + return; +} + +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 = xfer->usb2_sc; + 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); + return; +} + +static void +uhci_device_intr_close(struct usb2_xfer *xfer) +{ + uhci_softc_t *sc = xfer->usb2_sc; + + sc->sc_intr_stat[xfer->qh_pos]--; + + uhci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +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 = xfer->usb2_sc; + 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]; + + /* enter QHs into the controller data structures */ + UHCI_APPEND_QH(qh, td, sc->sc_intr_p_last[xfer->qh_pos]); + + /* put transfer on interrupt queue */ + uhci_transfer_intr_enqueue(xfer); + return; +} + +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); + } + } + return; +} + +static void +uhci_device_isoc_close(struct usb2_xfer *xfer) +{ + uhci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +uhci_device_isoc_enter(struct usb2_xfer *xfer) +{ + struct uhci_mem_layout ml; + uhci_softc_t *sc = xfer->usb2_sc; + 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); + + return; +} + +static void +uhci_device_isoc_start(struct usb2_xfer *xfer) +{ + /* put transfer on interrupt queue */ + uhci_transfer_intr_enqueue(xfer); + return; +} + +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 = xfer->usb2_sc; + + if (sc->sc_root_ctrl.xfer == xfer) { + sc->sc_root_ctrl.xfer = NULL; + } + uhci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +/* 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); + + 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.mtx, + 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); + + if (use_polling) { + /* polling */ + DELAY(1000); + } else { + usb2_pause_mtx(&sc->sc_bus.mtx, 1); + } + + 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.mtx, + 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 = xfer->usb2_sc; + + DPRINTF("\n"); + + sc->sc_root_ctrl.xfer = xfer; + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &uhci_root_ctrl_task, 0, 0); + + return; +} + +static void +uhci_root_ctrl_task(struct uhci_softc *sc, + struct uhci_config_copy *cc, uint16_t refcount) +{ + uhci_root_ctrl_poll(sc); + return; +} + +static void +uhci_root_ctrl_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + uhci_softc_t *sc = xfer->usb2_sc; + char *ptr; + uint16_t x; + uint16_t port; + uint16_t value; + uint16_t index; + uint16_t status; + uint16_t change; + uint8_t use_polling; + + mtx_assert(&sc->sc_bus.mtx, 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->priv_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_PORT_CONNECTION: + case UHF_PORT_OVER_CURRENT: + case UHF_PORT_POWER: + case UHF_PORT_LOW_SPEED: + case UHF_C_PORT_SUSPEND: + 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_SUSP) + status |= UPS_SUSPEND; + if (x & UHCI_PORTSC_LSDA) + status |= UPS_LOW_SPEED; + status |= UPS_PORT_POWER; + 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); + return; +} + +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 = xfer->usb2_sc; + + if (sc->sc_root_intr.xfer == xfer) { + sc->sc_root_intr.xfer = NULL; + } + uhci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +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 = xfer->usb2_sc; + + sc->sc_root_intr.xfer = xfer; + + usb2_transfer_timeout_ms(xfer, + &uhci_root_intr_check, xfer->interval); + return; +} + +static void +uhci_root_intr_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + uhci_softc_t *sc = xfer->usb2_sc; + + mtx_assert(&sc->sc_bus.mtx, 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 = xfer->usb2_sc; + + DPRINTFN(21, "\n"); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + sc->sc_hub_idata[0] = 0; + + if (UREAD2(sc, UHCI_PORTSC1) & (UHCI_PORTSC_CSC | UHCI_PORTSC_OCIC)) { + sc->sc_hub_idata[0] |= 1 << 1; + } + if (UREAD2(sc, UHCI_PORTSC2) & (UHCI_PORTSC_CSC | UHCI_PORTSC_OCIC)) { + sc->sc_hub_idata[0] |= 1 << 2; + } + if ((sc->sc_hub_idata[0] == 0) || !(UREAD2(sc, UHCI_CMD) & UHCI_CMD_RS)) { + /* + * 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); + } + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +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; + + /* + * setup xfer + */ + xfer->usb2_sc = sc; + + 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; + } + return; +} + +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; + } + } + return; +} + +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 */ + 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, +}; diff --git a/sys/dev/usb2/controller/uhci2.h b/sys/dev/usb2/controller/uhci2.h new file mode 100644 index 0000000..9be89ed --- /dev/null +++ b/sys/dev/usb2/controller/uhci2.h @@ -0,0 +1,318 @@ +/* $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_ + +/* 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 */ + struct usb2_config_td sc_config_td; + union uhci_hub_desc sc_hub_desc; + struct usb2_sw_transfer sc_root_ctrl; + struct usb2_sw_transfer sc_root_intr; + + 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; + 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/usb2/controller/uhci2_pci.c b/sys/dev/usb2/controller/uhci2_pci.c new file mode 100644 index 0000000..947f9af --- /dev/null +++ b/sys/dev/usb2/controller/uhci2_pci.c @@ -0,0 +1,453 @@ +/*- + * 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/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_standard.h> + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_config_td.h> +#include <dev/usb2/core/usb2_sw_transfer.h> +#include <dev/usb2/core/usb2_util.h> +#include <dev/usb2/core/usb2_debug.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> +#include <dev/usb2/controller/usb2_pci.h> +#include <dev/usb2/controller/uhci2.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; + + if (sc == NULL) { + device_printf(self, "Could not allocate sc\n"); + return (ENXIO); + } + /* 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: + sc->sc_bus.usbrev = USB_REV_UNKNOWN; + break; + } + + err = usb2_config_td_setup(&sc->sc_config_td, sc, &sc->sc_bus.mtx, + NULL, 0, 4); + if (err) { + device_printf(self, "could not setup config thread!\n"); + goto error; + } +#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; + + usb2_config_td_drain(&sc->sc_config_td); + + 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) { + mtx_lock(&sc->sc_bus.mtx); + + /* stop the controller */ + uhci_reset(sc); + + mtx_unlock(&sc->sc_bus.mtx); + } + 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_config_td_unsetup(&sc->sc_config_td); + + 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, usb2_controller, 1, 1, 1); +MODULE_DEPEND(uhci, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/controller/usb2_bus.h b/sys/dev/usb2/controller/usb2_bus.h new file mode 100644 index 0000000..cfe98f7 --- /dev/null +++ b/sys/dev/usb2/controller/usb2_bus.h @@ -0,0 +1,88 @@ +/* $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. + */ + +#ifndef _USB2_BUS_H_ +#define _USB2_BUS_H_ + +/* + * The following structure defines the USB explore message sent to the + * USB explore process. + */ + +struct usb2_bus_msg { + struct usb2_proc_msg hdr; + struct usb2_bus *bus; +}; + +/* + * The following structure defines the USB statistics structure. + */ +struct usb2_bus_stat { + uint32_t uds_requests[4]; +}; + +/* + * The following structure defines an USB BUS. There is one USB BUS + * for every Host or Device controller. + */ +struct usb2_bus { + struct usb2_bus_stat stats_err; + struct usb2_bus_stat stats_ok; + struct usb2_process explore_proc; + struct usb2_bus_msg explore_msg[2]; + struct usb2_bus_msg detach_msg[2]; + struct mtx mtx; /* This mutex protects the USB + * hardware */ + struct usb2_perm perm; + struct usb2_xfer_queue intr_q; + + device_t bdev; /* filled by HC driver */ + + struct usb2_dma_parent_tag dma_parent_tag[1]; + struct usb2_dma_tag dma_tags[USB_BUS_DMA_TAG_MAX]; + + struct usb2_bus_methods *methods; /* filled by HC driver */ + struct usb2_device *devices[USB_MAX_DEVICES]; + + uint32_t uframe_usage[USB_HS_MICRO_FRAMES_MAX]; + uint32_t transfer_count[4]; + uint16_t isoc_time_last; /* in milliseconds */ + + uint8_t alloc_failed; /* Set if memory allocation failed. */ + uint8_t driver_added_refcount; /* Current driver generation count */ + uint8_t usbrev; /* USB revision. See "USB_REV_XXX". */ + + uint8_t devices_max; /* maximum number of USB devices */ + uint8_t do_probe; /* set if USB BUS should be re-probed */ + + union { + struct usb2_hw_ep_scratch hw_ep_scratch[1]; + struct usb2_temp_setup temp_setup[1]; + uint8_t data[128]; + } scratch[1]; +}; + +#endif /* _USB2_BUS_H_ */ diff --git a/sys/dev/usb2/controller/usb2_controller.c b/sys/dev/usb2/controller/usb2_controller.c new file mode 100644 index 0000000..fdfa3a6 --- /dev/null +++ b/sys/dev/usb2/controller/usb2_controller.c @@ -0,0 +1,477 @@ +/* $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/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_standard.h> + +#define USB_DEBUG_VAR usb2_ctrl_debug + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_dynamic.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_hub.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_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 dev, struct usb2_bus *bus); +static void usb2_post_init(void *arg); +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); +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); +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); + +/* 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); + +MODULE_DEPEND(usb2_controller, usb2_core, 1, 1, 1); +MODULE_VERSION(usb2_controller, 1); + +/*------------------------------------------------------------------------* + * 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); + } + 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); + } + /* Let the USB explore process detach all devices. */ + + mtx_lock(&bus->mtx); + 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]); + + mtx_unlock(&bus->mtx); + + /* Get rid of USB explore process */ + + usb2_proc_unsetup(&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; + } + mtx_unlock(&bus->mtx); + + mtx_lock(&Giant); + + /* + * Explore the Root USB HUB. This call can sleep, + * exiting Giant, which is actually Giant. + */ + (udev->hub->explore) (udev); + + mtx_unlock(&Giant); + + mtx_lock(&bus->mtx); + } + return; +} + +/*------------------------------------------------------------------------* + * 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); + mtx_unlock(&bus->mtx); + + 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); + mtx_lock(&bus->mtx); + /* clear bdev variable last */ + bus->bdev = NULL; + return; +} + +/*------------------------------------------------------------------------* + * usb2_attach_sub + * + * This function is the real USB bus 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) +{ + struct usb2_device *child; + usb2_error_t err; + uint8_t speed; + + DPRINTF("\n"); + + mtx_assert(&Giant, MA_OWNED); + + 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; + } + + /* 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; + } + + if (err) { + device_printf(bus->bdev, "Root HUB problem, error=%s\n", + usb2_errstr(err)); + } + /* 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; + + /* Create a new USB process */ + if (usb2_proc_setup(&bus->explore_proc, + &bus->mtx, USB_PRI_MED)) { + printf("WARNING: Creation of USB explore process failed.\n"); + } + /* set softc - we are ready */ + device_set_softc(dev, bus); + return; +} + +/*------------------------------------------------------------------------* + * 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); + + return; +} + +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); + return; +} + +/*------------------------------------------------------------------------* + * 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); + } + return; +} + +/*------------------------------------------------------------------------* + * 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; + } + return; +} + +/*------------------------------------------------------------------------* + * 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; + + bus->devices_max = USB_MAX_DEVICES; + + mtx_init(&bus->mtx, "USB lock", + NULL, MTX_DEF | MTX_RECURSE); + + TAILQ_INIT(&bus->intr_q.head); + + usb2_dma_tag_setup(bus->dma_parent_tag, bus->dma_tags, + dmat, &bus->mtx, NULL, NULL, 32, USB_BUS_DMA_TAG_MAX); + + 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); + return; +} + +/*------------------------------------------------------------------------* + * 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->mtx); + + return; +} diff --git a/sys/dev/usb2/controller/usb2_controller.h b/sys/dev/usb2/controller/usb2_controller.h new file mode 100644 index 0000000..496e8c2 --- /dev/null +++ b/sys/dev/usb2/controller/usb2_controller.h @@ -0,0 +1,172 @@ +/* $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. + */ + +#ifndef _USB2_CONTROLLER_H_ +#define _USB2_CONTROLLER_H_ + +/* defines */ + +#define USB_BUS_DMA_TAG_MAX 8 + +/* structure prototypes */ + +struct usb2_bus; +struct usb2_page; +struct usb2_pipe; +struct usb2_page_cache; +struct usb2_setup_params; +struct usb2_hw_ep_profile; +struct usb2_fs_isoc_schedule; +struct usb2_config_descriptor; +struct usb2_endpoint_descriptor; + +/* typedefs */ + +typedef void (usb2_bus_mem_sub_cb_t)(struct usb2_bus *bus, struct usb2_page_cache *pc, struct usb2_page *pg, uint32_t size, uint32_t align); +typedef void (usb2_bus_mem_cb_t)(struct usb2_bus *bus, usb2_bus_mem_sub_cb_t *scb); + +/* + * The following structure is used to define all the USB BUS + * callbacks. + */ +struct usb2_bus_methods { + + /* USB Device and Host mode - Mandatory */ + + void (*pipe_init) (struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc, struct usb2_pipe *pipe); + void (*do_poll) (struct usb2_bus *); + void (*xfer_setup) (struct usb2_setup_params *parm); + void (*xfer_unsetup) (struct usb2_xfer *xfer); + void (*get_dma_delay) (struct usb2_bus *, uint32_t *pdelay); + + /* USB Device mode only - Mandatory */ + + void (*get_hw_ep_profile) (struct usb2_device *udev, const struct usb2_hw_ep_profile **ppf, uint8_t ep_addr); + void (*set_stall) (struct usb2_device *udev, struct usb2_xfer *xfer, struct usb2_pipe *pipe); + void (*clear_stall) (struct usb2_device *udev, struct usb2_pipe *pipe); + void (*rem_wakeup_set) (struct usb2_device *udev, uint8_t is_on); + + /* USB Device mode only - Optional */ + + void (*vbus_interrupt) (struct usb2_bus *, uint8_t is_on); +}; + +/* + * The following structure is used to define all the USB pipe + * callbacks. + */ +struct usb2_pipe_methods { + + /* Mandatory USB Device and Host mode callbacks: */ + + void (*open) (struct usb2_xfer *xfer); + void (*close) (struct usb2_xfer *xfer); + + void (*enter) (struct usb2_xfer *xfer); + void (*start) (struct usb2_xfer *xfer); + + /* Optional */ + + uint8_t (*isdone) (struct usb2_xfer *xfer); + void *info; + + /* Flags */ + + uint8_t enter_is_cancelable:1; + uint8_t start_is_cancelable:1; +}; + +/* + * The following structure keeps information about what a hardware USB + * endpoint supports. + */ +struct usb2_hw_ep_profile { + uint16_t max_in_frame_size; /* IN-token direction */ + uint16_t max_out_frame_size; /* OUT-token direction */ + uint8_t is_simplex:1; + uint8_t support_multi_buffer:1; + uint8_t support_bulk:1; + uint8_t support_control:1; + uint8_t support_interrupt:1; + uint8_t support_isochronous:1; + uint8_t support_in:1; /* IN-token is supported */ + uint8_t support_out:1; /* OUT-token is supported */ +}; + +/* + * The following structure is used when trying to allocate hardware + * endpoints for an USB configuration in USB device side mode. + */ +struct usb2_hw_ep_scratch_sub { + const struct usb2_hw_ep_profile *pf; + uint16_t max_frame_size; + uint8_t hw_endpoint_out; + uint8_t hw_endpoint_in; + uint8_t needs_ep_type; + uint8_t needs_in:1; + uint8_t needs_out:1; +}; + +/* + * The following structure is used when trying to allocate hardware + * endpoints for an USB configuration in USB device side mode. + */ +struct usb2_hw_ep_scratch { + struct usb2_hw_ep_scratch_sub ep[USB_EP_MAX]; + struct usb2_hw_ep_scratch_sub *ep_max; + struct usb2_config_descriptor *cd; + struct usb2_device *udev; + struct usb2_bus_methods *methods; + uint8_t bmOutAlloc[(USB_EP_MAX + 15) / 16]; + uint8_t bmInAlloc[(USB_EP_MAX + 15) / 16]; +}; + +/* + * The following structure is used when generating USB descriptors + * from USB templates. + */ +struct usb2_temp_setup { + void *buf; + uint32_t size; + uint8_t usb2_speed; + uint8_t self_powered; + uint8_t bNumEndpoints; + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bConfigurationValue; + usb2_error_t err; +}; + +/* prototypes */ + +void usb2_bus_mem_flush_all(struct usb2_bus *bus, usb2_bus_mem_cb_t *cb); +uint8_t usb2_bus_mem_alloc_all(struct usb2_bus *bus, bus_dma_tag_t dmat, usb2_bus_mem_cb_t *cb); +void usb2_bus_mem_free_all(struct usb2_bus *bus, usb2_bus_mem_cb_t *cb); +uint16_t usb2_isoc_time_expand(struct usb2_bus *bus, uint16_t isoc_time_curr); +uint16_t usb2_fs_isoc_schedule_isoc_time_expand(struct usb2_device *udev, struct usb2_fs_isoc_schedule **pp_start, struct usb2_fs_isoc_schedule **pp_end, uint16_t isoc_time); +uint8_t usb2_fs_isoc_schedule_alloc(struct usb2_fs_isoc_schedule *fss, uint8_t *pstart, uint16_t len); + +#endif /* _USB2_CONTROLLER_H_ */ diff --git a/sys/dev/usb2/controller/usb2_pci.h b/sys/dev/usb2/controller/usb2_pci.h new file mode 100644 index 0000000..9297c29 --- /dev/null +++ b/sys/dev/usb2/controller/usb2_pci.h @@ -0,0 +1,39 @@ +/* $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. + */ + +#ifndef _USB2_PCI_H_ +#define _USB2_PCI_H_ + +/* + * We don't want the following files included everywhere, that's why + * they are in a separate file. + */ +#include <dev/pci/pcireg.h> +#include <dev/pci/pcivar.h> + +#include <sys/rman.h> + +#endif /* _USB2_PCI_H_ */ diff --git a/sys/dev/usb2/controller/uss820dci.c b/sys/dev/usb2/controller/uss820dci.c new file mode 100644 index 0000000..cc9f219 --- /dev/null +++ b/sys/dev/usb2/controller/uss820dci.c @@ -0,0 +1,2572 @@ +/* $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/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_revision.h> +#include <dev/usb2/include/usb2_error.h> +#include <dev/usb2/include/usb2_defs.h> + +#define USB_DEBUG_VAR uss820dcidebug +#define usb2_config_td_cc uss820dci_config_copy +#define usb2_config_td_softc uss820dci_softc + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_debug.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_config_td.h> +#include <dev/usb2/core/usb2_sw_transfer.h> +#include <dev/usb2/core/usb2_transfer.h> +#include <dev/usb2/core/usb2_device.h> +#include <dev/usb2/core/usb2_hub.h> +#include <dev/usb2/core/usb2_util.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> +#include <dev/usb2/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 *xfer, usb2_error_t error); +static void uss820dci_do_poll(struct usb2_bus *bus); +static void uss820dci_root_ctrl_poll(struct uss820dci_softc *sc); +static void uss820dci_standard_done(struct usb2_xfer *xfer); +static void uss820dci_intr_set(struct usb2_xfer *xfer, uint8_t set); +static void uss820dci_update_shared_1(struct uss820dci_softc *sc, uint8_t reg, uint8_t keep_mask, uint8_t set_mask); + +static usb2_sw_transfer_func_t uss820dci_root_intr_done; +static usb2_sw_transfer_func_t uss820dci_root_ctrl_done; +static usb2_config_td_command_t uss820dci_root_ctrl_task; + +/* + * 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); + return; +} + +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; + } + return; +} + +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); + } + return; +} + +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); + } + return; +} + +static void +uss820dci_wakeup_peer(struct uss820dci_softc *sc) +{ + if (!(sc->sc_flags.status_suspend)) { + return; + } + DPRINTFN(0, "not supported\n"); + + return; +} + +static void +uss820dci_rem_wakeup_set(struct usb2_device *udev, uint8_t is_on) +{ + struct uss820dci_softc *sc; + uint8_t temp; + + DPRINTFN(5, "is_on=%u\n", is_on); + + mtx_assert(&udev->bus->mtx, MA_OWNED); + + sc = USS820_DCI_BUS2SC(udev->bus); + + temp = USS820_READ_1(sc, USS820_SCR); + + if (is_on) { + temp |= USS820_SCR_RWUPE; + } else { + temp &= ~USS820_SCR_RWUPE; + } + + USS820_WRITE_1(sc, USS820_SCR, temp); + + return; +} + +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); + + return; +} + +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; + } + } + return; +} + +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); + return; +} + +void +uss820dci_interrupt(struct uss820dci_softc *sc) +{ + uint8_t ssr; + uint8_t event; + + mtx_lock(&sc->sc_bus.mtx); + + 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); + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +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; + return; +} + +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->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 = xfer->usb2_sc; + 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; + + return; +} + +static void +uss820dci_timeout(void *arg) +{ + struct usb2_xfer *xfer = arg; + struct uss820dci_softc *sc = xfer->usb2_sc; + + DPRINTF("xfer=%p\n", xfer); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + /* transfer is transferred */ + uss820dci_device_done(xfer, USB_ERR_TIMEOUT); + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +static void +uss820dci_intr_set(struct usb2_xfer *xfer, uint8_t set) +{ + struct uss820dci_softc *sc = xfer->usb2_sc; + 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); + return; +} + +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->udev->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usb2_transfer_timeout_ms(xfer, + &uss820dci_timeout, xfer->timeout); + } + } + return; +} + +static void +uss820dci_root_intr_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + struct uss820dci_softc *sc = xfer->usb2_sc; + + DPRINTFN(9, "\n"); + + mtx_assert(&sc->sc_bus.mtx, 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); + return; +} + +/*------------------------------------------------------------------------* + * 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) +{ + mtx_assert(xfer->usb2_mtx, 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); + return; +} + +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; + + mtx_assert(&udev->bus->mtx, 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); + return; +} + +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); + } + return; +} + +static void +uss820dci_clear_stall(struct usb2_device *udev, struct usb2_pipe *pipe) +{ + struct uss820dci_softc *sc; + struct usb2_endpoint_descriptor *ed; + + mtx_assert(&udev->bus->mtx, 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))); + + return; +} + +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; + + mtx_lock(&sc->sc_bus.mtx); + + /* 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) { + mtx_unlock(&sc->sc_bus.mtx); + 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.mtx, 10); + + /* check hardware revision */ + temp = USS820_READ_1(sc, USS820_REV); + + if (temp < 0x13) { + mtx_unlock(&sc->sc_bus.mtx); + return (USB_ERR_INVAL); + } + /* enable interrupts */ + USS820_WRITE_1(sc, USS820_SCR, + USS820_SCR_T_IRQ | + USS820_SCR_IE_RESET | + 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); + } + + mtx_unlock(&sc->sc_bus.mtx); + + /* catch any lost interrupts */ + + uss820dci_do_poll(&sc->sc_bus); + + return (0); /* success */ +} + +void +uss820dci_uninit(struct uss820dci_softc *sc) +{ + uint8_t temp; + + mtx_lock(&sc->sc_bus.mtx); + + /* 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); + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +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); + + mtx_lock(&sc->sc_bus.mtx); + uss820dci_interrupt_poll(sc); + uss820dci_root_ctrl_poll(sc); + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +/*------------------------------------------------------------------------* + * 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); + return; +} + +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); + return; +} + +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); + return; +} + +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); + return; +} + +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); + return; +} + +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); + return; +} + +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); + return; +} + +static void +uss820dci_device_isoc_fs_enter(struct usb2_xfer *xfer) +{ + struct uss820dci_softc *sc = xfer->usb2_sc; + 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); + return; +} + +static void +uss820dci_device_isoc_fs_start(struct usb2_xfer *xfer) +{ + /* start TD chain */ + uss820dci_start_standard_chain(xfer); + return; +} + +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 = xfer->usb2_sc; + + if (sc->sc_root_ctrl.xfer == xfer) { + sc->sc_root_ctrl.xfer = NULL; + } + uss820dci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +/* + * 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) >> 16, + .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 = xfer->usb2_sc; + + sc->sc_root_ctrl.xfer = xfer; + + usb2_config_td_queue_command( + &sc->sc_config_td, NULL, &uss820dci_root_ctrl_task, 0, 0); + + return; +} + +static void +uss820dci_root_ctrl_task(struct uss820dci_softc *sc, + struct uss820dci_config_copy *cc, uint16_t refcount) +{ + uss820dci_root_ctrl_poll(sc); + return; +} + +static void +uss820dci_root_ctrl_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + struct uss820dci_softc *sc = xfer->usb2_sc; + uint16_t value; + uint16_t index; + uint8_t use_polling; + + mtx_assert(&sc->sc_bus.mtx, 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->priv_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); + return; +} + +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 = xfer->usb2_sc; + + if (sc->sc_root_intr.xfer == xfer) { + sc->sc_root_intr.xfer = NULL; + } + uss820dci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +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 = xfer->usb2_sc; + + sc->sc_root_intr.xfer = xfer; + return; +} + +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; + + /* + * setup xfer + */ + xfer->usb2_sc = sc; + + /* + * 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; + return; +} + +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; + } + } + return; +} + +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, + .rem_wakeup_set = &uss820dci_rem_wakeup_set, +}; diff --git a/sys/dev/usb2/controller/uss820dci.h b/sys/dev/usb2/controller/uss820dci.h new file mode 100644 index 0000000..aa8b535 --- /dev/null +++ b/sys/dev/usb2/controller/uss820dci.h @@ -0,0 +1,375 @@ +/* $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_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_config_td sc_config_td; + + 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/usb2/controller/uss820dci_atmelarm.c b/sys/dev/usb2/controller/uss820dci_atmelarm.c new file mode 100644 index 0000000..be71fb3 --- /dev/null +++ b/sys/dev/usb2/controller/uss820dci_atmelarm.c @@ -0,0 +1,247 @@ +#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/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_standard.h> + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_config_td.h> +#include <dev/usb2/core/usb2_sw_transfer.h> +#include <dev/usb2/core/usb2_util.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> +#include <dev/usb2/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, usb2_controller, 1, 1, 1); +MODULE_DEPEND(uss820, usb2_core, 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; + + if (sc == NULL) { + return (ENXIO); + } + /* 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); + + err = usb2_config_td_setup(&sc->sc_config_td, sc, + &sc->sc_bus.mtx, NULL, 0, 4); + if (err) { + device_printf(dev, "could not setup config thread!\n"); + goto error; + } +#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_config_td_unsetup(&sc->sc_config_td); + + usb2_bus_mem_free_all(&sc->sc_bus, NULL); + + return (0); +} diff --git a/sys/dev/usb2/controller/uss820dci_pccard.c b/sys/dev/usb2/controller/uss820dci_pccard.c new file mode 100644 index 0000000..f57935d --- /dev/null +++ b/sys/dev/usb2/controller/uss820dci_pccard.c @@ -0,0 +1,266 @@ +#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/usb2/include/usb2_mfunc.h> +#include <dev/usb2/include/usb2_defs.h> +#include <dev/usb2/include/usb2_standard.h> + +#include <dev/usb2/core/usb2_core.h> +#include <dev/usb2/core/usb2_busdma.h> +#include <dev/usb2/core/usb2_process.h> +#include <dev/usb2/core/usb2_config_td.h> +#include <dev/usb2/core/usb2_sw_transfer.h> +#include <dev/usb2/core/usb2_util.h> + +#include <dev/usb2/controller/usb2_controller.h> +#include <dev/usb2/controller/usb2_bus.h> +#include <dev/usb2/controller/uss820dci.h> + +#include <dev/pccard/pccardreg.h> +#include <dev/pccard/pccardvar.h> + +#include <sys/rman.h> + +static device_probe_t uss820_pccard_probe; +static device_attach_t uss820_pccard_attach; +static device_detach_t uss820_pccard_detach; +static device_suspend_t uss820_pccard_suspend; +static device_resume_t uss820_pccard_resume; +static device_shutdown_t uss820_pccard_shutdown; + +static uint8_t uss820_pccard_lookup(device_t dev); + +static device_method_t uss820dci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uss820_pccard_probe), + DEVMETHOD(device_attach, uss820_pccard_attach), + DEVMETHOD(device_detach, uss820_pccard_detach), + DEVMETHOD(device_suspend, uss820_pccard_suspend), + DEVMETHOD(device_resume, uss820_pccard_resume), + DEVMETHOD(device_shutdown, uss820_pccard_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, pccard, uss820dci_driver, uss820dci_devclass, 0, 0); +MODULE_DEPEND(uss820, usb2_core, 1, 1, 1); + +static const char *const uss820_desc = "USS820 USB Device Controller"; + +static int +uss820_pccard_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_pccard_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_pccard_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 uint8_t +uss820_pccard_lookup(device_t dev) +{ + uint32_t prod; + uint32_t vend; + + pccard_get_vendor(dev, &vend); + pccard_get_product(dev, &prod); + + /* ID's will be added later */ + return (0); +} + +static int +uss820_pccard_probe(device_t dev) +{ + if (uss820_pccard_lookup(dev)) { + device_set_desc(dev, uss820_desc); + return (0); + } + return (ENXIO); +} +static int +uss820_pccard_attach(device_t dev) +{ + struct uss820dci_softc *sc = device_get_softc(dev); + int err; + int rid; + + if (sc == NULL) { + return (ENXIO); + } + /* 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); + + err = usb2_config_td_setup(&sc->sc_config_td, sc, + &sc->sc_bus.mtx, NULL, 0, 4); + if (err) { + device_printf(dev, "could not setup config thread!\n"); + goto error; + } +#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_pccard_detach(dev); + return (ENXIO); +} + +static int +uss820_pccard_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_config_td_unsetup(&sc->sc_config_td); + + usb2_bus_mem_free_all(&sc->sc_bus, NULL); + + return (0); +} |