diff options
Diffstat (limited to 'sys/mips/cavium/usb')
-rw-r--r-- | sys/mips/cavium/usb/octusb.c | 1922 | ||||
-rw-r--r-- | sys/mips/cavium/usb/octusb.h | 147 | ||||
-rw-r--r-- | sys/mips/cavium/usb/octusb_octeon.c | 223 |
3 files changed, 2292 insertions, 0 deletions
diff --git a/sys/mips/cavium/usb/octusb.c b/sys/mips/cavium/usb/octusb.c new file mode 100644 index 0000000..d090f12 --- /dev/null +++ b/sys/mips/cavium/usb/octusb.c @@ -0,0 +1,1922 @@ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2010 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 Octeon Executive Library USB + * Controller driver API. + */ + +/* TODO: The root HUB port callback is not yet implemented. */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/linker_set.h> +#include <sys/module.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> + +#define USB_DEBUG_VAR octusbdebug + +#include <dev/usb/usb_core.h> +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_busdma.h> +#include <dev/usb/usb_process.h> +#include <dev/usb/usb_transfer.h> +#include <dev/usb/usb_device.h> +#include <dev/usb/usb_hub.h> +#include <dev/usb/usb_util.h> + +#include <dev/usb/usb_controller.h> +#include <dev/usb/usb_bus.h> + +#include <contrib/octeon-sdk/cvmx.h> +#include <contrib/octeon-sdk/cvmx-usb.h> + +#include <mips/cavium/usb/octusb.h> + +#define OCTUSB_BUS2SC(bus) \ + ((struct octusb_softc *)(((uint8_t *)(bus)) - \ + ((uint8_t *)&(((struct octusb_softc *)0)->sc_bus)))) + +#ifdef USB_DEBUG +static int octusbdebug = 0; + +SYSCTL_NODE(_hw_usb, OID_AUTO, octusb, CTLFLAG_RW, 0, "OCTUSB"); +SYSCTL_INT(_hw_usb_octusb, OID_AUTO, debug, CTLFLAG_RW, + &octusbdebug, 0, "OCTUSB debug level"); + +TUNABLE_INT("hw.usb.octusb.debug", &octusbdebug); + +#endif + +struct octusb_std_temp { + octusb_cmd_t *func; + struct octusb_td *td; + struct octusb_td *td_next; + struct usb_page_cache *pc; + uint32_t offset; + uint32_t len; + uint8_t short_pkt; + uint8_t setup_alt_next; +}; + +extern struct usb_bus_methods octusb_bus_methods; +extern struct usb_pipe_methods octusb_device_bulk_methods; +extern struct usb_pipe_methods octusb_device_ctrl_methods; +extern struct usb_pipe_methods octusb_device_intr_methods; +extern struct usb_pipe_methods octusb_device_isoc_methods; + +static void octusb_standard_done(struct usb_xfer *); +static void octusb_device_done(struct usb_xfer *, usb_error_t); +static void octusb_timeout(void *); +static void octusb_do_poll(struct usb_bus *); + +static cvmx_usb_speed_t +octusb_convert_speed(enum usb_dev_speed speed) +{ + ; /* indent fix */ + switch (speed) { + case USB_SPEED_HIGH: + return (CVMX_USB_SPEED_HIGH); + case USB_SPEED_FULL: + return (CVMX_USB_SPEED_FULL); + default: + return (CVMX_USB_SPEED_LOW); + } +} + +static cvmx_usb_transfer_t +octusb_convert_ep_type(uint8_t ep_type) +{ + ; /* indent fix */ + switch (ep_type & UE_XFERTYPE) { + case UE_CONTROL: + return (CVMX_USB_TRANSFER_CONTROL); + case UE_INTERRUPT: + return (CVMX_USB_TRANSFER_INTERRUPT); + case UE_ISOCHRONOUS: + return (CVMX_USB_TRANSFER_ISOCHRONOUS); + case UE_BULK: + return (CVMX_USB_TRANSFER_BULK); + default: + return (0); /* should not happen */ + } +} + +static uint8_t +octusb_host_alloc_endpoint(struct octusb_td *td) +{ + struct octusb_softc *sc; + int ep_handle; + + if (td->qh->fixup_pending) + return (1); /* busy */ + + if (td->qh->ep_allocated) + return (0); /* success */ + + /* get softc */ + sc = td->qh->sc; + + ep_handle = cvmx_usb_open_pipe( + &sc->sc_port[td->qh->port_index].state, + 0, + td->qh->dev_addr, + td->qh->ep_num, + octusb_convert_speed(td->qh->dev_speed), + td->qh->max_packet_size, + octusb_convert_ep_type(td->qh->ep_type), + (td->qh->ep_num & UE_DIR_IN) ? CVMX_USB_DIRECTION_IN : + CVMX_USB_DIRECTION_OUT, + td->qh->ep_interval, + td->qh->ep_mult, + td->qh->hs_hub_addr, + td->qh->hs_hub_port); + + if (ep_handle < 0) + return (1); /* busy */ + + cvmx_usb_set_toggle( + &sc->sc_port[td->qh->port_index].state, + ep_handle, td->qh->ep_toggle_next); + + td->qh->fixup_handle = -1; + td->qh->fixup_complete = 0; + td->qh->fixup_len = 0; + td->qh->fixup_off = 0; + td->qh->fixup_pending = 0; + td->qh->fixup_actlen = 0; + + td->qh->ep_handle = ep_handle; + td->qh->ep_allocated = 1; + + return (0); /* success */ +} + +static void +octusb_host_free_endpoint(struct octusb_td *td) +{ + struct octusb_softc *sc; + + if (td->qh->ep_allocated == 0) + return; + + /* get softc */ + sc = td->qh->sc; + + if (td->qh->fixup_handle >= 0) { + /* cancel, if any */ + cvmx_usb_cancel(&sc->sc_port[td->qh->port_index].state, + td->qh->ep_handle, td->qh->fixup_handle); + } + cvmx_usb_close_pipe(&sc->sc_port[td->qh->port_index].state, td->qh->ep_handle); + + td->qh->ep_allocated = 0; +} + +static void +octusb_complete_cb(cvmx_usb_state_t *state, + cvmx_usb_callback_t reason, + cvmx_usb_complete_t status, + int pipe_handle, int submit_handle, + int bytes_transferred, void *user_data) +{ + struct octusb_td *td; + + if (reason != CVMX_USB_CALLBACK_TRANSFER_COMPLETE) + return; + + td = user_data; + + td->qh->fixup_complete = 1; + td->qh->fixup_pending = 0; + td->qh->fixup_actlen = bytes_transferred; + td->qh->fixup_handle = -1; + + switch (status) { + case CVMX_USB_COMPLETE_SUCCESS: + case CVMX_USB_COMPLETE_SHORT: + td->error_any = 0; + td->error_stall = 0; + break; + case CVMX_USB_COMPLETE_STALL: + td->error_stall = 1; + td->error_any = 1; + break; + default: + td->error_any = 1; + break; + } +} + +static uint8_t +octusb_host_control_header_tx(struct octusb_td *td) +{ + int status; + + /* allocate endpoint and check pending */ + if (octusb_host_alloc_endpoint(td)) + return (1); /* busy */ + + /* check error */ + if (td->error_any) + return (0); /* done */ + + if (td->qh->fixup_complete != 0) { + /* clear complete flag */ + td->qh->fixup_complete = 0; + + /* flush data */ + usb_pc_cpu_invalidate(td->qh->fixup_pc); + return (0); /* done */ + } + /* verify length */ + if (td->remainder != 8) { + td->error_any = 1; + return (0); /* done */ + } + usbd_copy_out(td->pc, td->offset, td->qh->fixup_buf, 8); + + /* update offset and remainder */ + td->offset += 8; + td->remainder -= 8; + + /* setup data length and offset */ + td->qh->fixup_len = UGETW(td->qh->fixup_buf + 6); + td->qh->fixup_off = 0; + + if (td->qh->fixup_len > (OCTUSB_MAX_FIXUP - 8)) { + td->error_any = 1; + return (0); /* done */ + } + /* do control IN request */ + if (td->qh->fixup_buf[0] & UE_DIR_IN) { + + struct octusb_softc *sc; + + /* get softc */ + sc = td->qh->sc; + + /* flush data */ + usb_pc_cpu_flush(td->qh->fixup_pc); + + status = cvmx_usb_submit_control( + &sc->sc_port[td->qh->port_index].state, + td->qh->ep_handle, td->qh->fixup_phys, + td->qh->fixup_phys + 8, td->qh->fixup_len, + &octusb_complete_cb, td); + /* check status */ + if (status < 0) { + td->error_any = 1; + return (0); /* done */ + } + td->qh->fixup_handle = status; + td->qh->fixup_pending = 1; + td->qh->fixup_complete = 0; + + return (1); /* busy */ + } + return (0); /* done */ +} + +static uint8_t +octusb_host_control_data_tx(struct octusb_td *td) +{ + uint32_t rem; + + /* allocate endpoint and check pending */ + if (octusb_host_alloc_endpoint(td)) + return (1); /* busy */ + + /* check error */ + if (td->error_any) + return (0); /* done */ + + rem = td->qh->fixup_len - td->qh->fixup_off; + + if (td->remainder > rem) { + td->error_any = 1; + DPRINTFN(1, "Excess setup transmit data\n"); + return (0); /* done */ + } + usbd_copy_out(td->pc, td->offset, td->qh->fixup_buf + + td->qh->fixup_off + 8, td->remainder); + + td->offset += td->remainder; + td->qh->fixup_off += td->remainder; + td->remainder = 0; + + return (0); /* done */ +} + +static uint8_t +octusb_host_control_data_rx(struct octusb_td *td) +{ + uint32_t rem; + + /* allocate endpoint and check pending */ + if (octusb_host_alloc_endpoint(td)) + return (1); /* busy */ + + /* check error */ + if (td->error_any) + return (0); /* done */ + + /* copy data from buffer */ + rem = td->qh->fixup_actlen - td->qh->fixup_off; + + if (rem > td->remainder) + rem = td->remainder; + + usbd_copy_in(td->pc, td->offset, td->qh->fixup_buf + + td->qh->fixup_off + 8, rem); + + td->offset += rem; + td->remainder -= rem; + td->qh->fixup_off += rem; + + return (0); /* done */ +} + +static uint8_t +octusb_host_control_status_tx(struct octusb_td *td) +{ + int status; + + /* allocate endpoint and check pending */ + if (octusb_host_alloc_endpoint(td)) + return (1); /* busy */ + + /* check error */ + if (td->error_any) + return (0); /* done */ + + if (td->qh->fixup_complete != 0) { + /* clear complete flag */ + td->qh->fixup_complete = 0; + /* done */ + return (0); + } + /* do control IN request */ + if (!(td->qh->fixup_buf[0] & UE_DIR_IN)) { + + struct octusb_softc *sc; + + /* get softc */ + sc = td->qh->sc; + + /* flush data */ + usb_pc_cpu_flush(td->qh->fixup_pc); + + /* start USB transfer */ + status = cvmx_usb_submit_control( + &sc->sc_port[td->qh->port_index].state, + td->qh->ep_handle, td->qh->fixup_phys, + td->qh->fixup_phys + 8, td->qh->fixup_len, + &octusb_complete_cb, td); + + /* check status */ + if (status < 0) { + td->error_any = 1; + return (0); /* done */ + } + td->qh->fixup_handle = status; + td->qh->fixup_pending = 1; + td->qh->fixup_complete = 0; + + return (1); /* busy */ + } + return (0); /* done */ +} + +static uint8_t +octusb_non_control_data_tx(struct octusb_td *td) +{ + struct octusb_softc *sc; + uint32_t rem; + int status; + + /* allocate endpoint and check pending */ + if (octusb_host_alloc_endpoint(td)) + return (1); /* busy */ + + /* check error */ + if (td->error_any) + return (0); /* done */ + + if ((td->qh->fixup_complete != 0) && + ((td->qh->ep_type & UE_XFERTYPE) == UE_ISOCHRONOUS)) { + td->qh->fixup_complete = 0; + return (0); /* done */ + } + /* check complete */ + if (td->remainder == 0) { + if (td->short_pkt) + return (0); /* complete */ + /* else need to send a zero length packet */ + rem = 0; + td->short_pkt = 1; + } else { + /* get maximum length */ + rem = OCTUSB_MAX_FIXUP % td->qh->max_frame_size; + rem = OCTUSB_MAX_FIXUP - rem; + + if (rem == 0) { + /* should not happen */ + DPRINTFN(1, "Fixup buffer is too small\n"); + td->error_any = 1; + return (0); /* done */ + } + /* get minimum length */ + if (rem > td->remainder) { + rem = td->remainder; + if ((rem == 0) || (rem % td->qh->max_frame_size)) + td->short_pkt = 1; + } + /* copy data into fixup buffer */ + usbd_copy_out(td->pc, td->offset, td->qh->fixup_buf, rem); + + /* flush data */ + usb_pc_cpu_flush(td->qh->fixup_pc); + + /* pre-increment TX buffer offset */ + td->offset += rem; + td->remainder -= rem; + } + + /* get softc */ + sc = td->qh->sc; + + switch (td->qh->ep_type & UE_XFERTYPE) { + case UE_ISOCHRONOUS: + td->qh->iso_pkt.offset = 0; + td->qh->iso_pkt.length = rem; + td->qh->iso_pkt.status = 0; + /* start USB transfer */ + status = cvmx_usb_submit_isochronous(&sc->sc_port[td->qh->port_index].state, + td->qh->ep_handle, 1, CVMX_USB_ISOCHRONOUS_FLAGS_ALLOW_SHORT | + CVMX_USB_ISOCHRONOUS_FLAGS_ASAP, 1, &td->qh->iso_pkt, + td->qh->fixup_phys, rem, &octusb_complete_cb, td); + break; + case UE_BULK: + /* start USB transfer */ + status = cvmx_usb_submit_bulk(&sc->sc_port[td->qh->port_index].state, + td->qh->ep_handle, td->qh->fixup_phys, rem, &octusb_complete_cb, td); + break; + case UE_INTERRUPT: + /* start USB transfer (interrupt or interrupt) */ + status = cvmx_usb_submit_interrupt(&sc->sc_port[td->qh->port_index].state, + td->qh->ep_handle, td->qh->fixup_phys, rem, &octusb_complete_cb, td); + break; + default: + status = -1; + break; + } + + /* check status */ + if (status < 0) { + td->error_any = 1; + return (0); /* done */ + } + td->qh->fixup_handle = status; + td->qh->fixup_len = rem; + td->qh->fixup_pending = 1; + td->qh->fixup_complete = 0; + + return (1); /* busy */ +} + +static uint8_t +octusb_non_control_data_rx(struct octusb_td *td) +{ + struct octusb_softc *sc; + uint32_t rem; + int status; + uint8_t got_short; + + /* allocate endpoint and check pending */ + if (octusb_host_alloc_endpoint(td)) + return (1); /* busy */ + + /* check error */ + if (td->error_any) + return (0); /* done */ + + got_short = 0; + + if (td->qh->fixup_complete != 0) { + + /* invalidate data */ + usb_pc_cpu_invalidate(td->qh->fixup_pc); + + rem = td->qh->fixup_actlen; + + /* verify transfer length */ + if (rem != td->qh->fixup_len) { + if (rem < td->qh->fixup_len) { + /* we have a short packet */ + td->short_pkt = 1; + got_short = 1; + } else { + /* invalid USB packet */ + td->error_any = 1; + return (0); /* we are complete */ + } + } + /* copy data into fixup buffer */ + usbd_copy_in(td->pc, td->offset, td->qh->fixup_buf, rem); + + /* post-increment RX buffer offset */ + td->offset += rem; + td->remainder -= rem; + + td->qh->fixup_complete = 0; + + if ((td->qh->ep_type & UE_XFERTYPE) == UE_ISOCHRONOUS) + return (0); /* done */ + } + /* 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 */ + rem = 0; + td->short_pkt = 1; + } else { + /* get maximum length */ + rem = OCTUSB_MAX_FIXUP % td->qh->max_frame_size; + rem = OCTUSB_MAX_FIXUP - rem; + + if (rem == 0) { + /* should not happen */ + DPRINTFN(1, "Fixup buffer is too small\n"); + td->error_any = 1; + return (0); /* done */ + } + /* get minimum length */ + if (rem > td->remainder) + rem = td->remainder; + } + + /* invalidate data */ + usb_pc_cpu_invalidate(td->qh->fixup_pc); + + /* get softc */ + sc = td->qh->sc; + + switch (td->qh->ep_type & UE_XFERTYPE) { + case UE_ISOCHRONOUS: + td->qh->iso_pkt.offset = 0; + td->qh->iso_pkt.length = rem; + td->qh->iso_pkt.status = 0; + /* start USB transfer */ + status = cvmx_usb_submit_isochronous(&sc->sc_port[td->qh->port_index].state, + td->qh->ep_handle, 1, CVMX_USB_ISOCHRONOUS_FLAGS_ALLOW_SHORT | + CVMX_USB_ISOCHRONOUS_FLAGS_ASAP, 1, &td->qh->iso_pkt, + td->qh->fixup_phys, rem, &octusb_complete_cb, td); + break; + case UE_BULK: + /* start USB transfer */ + status = cvmx_usb_submit_bulk(&sc->sc_port[td->qh->port_index].state, + td->qh->ep_handle, td->qh->fixup_phys, rem, &octusb_complete_cb, td); + break; + case UE_INTERRUPT: + /* start USB transfer */ + status = cvmx_usb_submit_interrupt(&sc->sc_port[td->qh->port_index].state, + td->qh->ep_handle, td->qh->fixup_phys, rem, &octusb_complete_cb, td); + break; + default: + status = -1; + break; + } + + /* check status */ + if (status < 0) { + td->error_any = 1; + return (0); /* done */ + } + td->qh->fixup_handle = status; + td->qh->fixup_len = rem; + td->qh->fixup_pending = 1; + td->qh->fixup_complete = 0; + + return (1); /* busy */ +} + +static uint8_t +octusb_xfer_do_fifo(struct usb_xfer *xfer) +{ + struct octusb_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_any) { + goto done; + } else if (td->remainder > 0) { + /* + * We had a short transfer. If there is no + * alternate next, stop processing ! + */ + if (td->alt_next == 0) + goto done; + } + /* + * Fetch the next transfer descriptor and transfer + * some flags to the next transfer descriptor + */ + td = td->obj_next; + xfer->td_transfer_cache = td; + } + return (1); /* not complete */ + +done: + /* compute all actual lengths */ + + octusb_standard_done(xfer); + + return (0); /* complete */ +} + +static usb_error_t +octusb_standard_done_sub(struct usb_xfer *xfer) +{ + struct octusb_td *td; + uint32_t len; + usb_error_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_any = 1; + } else { + xfer->frlengths[xfer->aframes] -= len; + } + } + /* Check for transfer error */ + if (td->error_any) { + /* the transfer is finished */ + error = td->error_stall ? USB_ERR_STALLED : USB_ERR_IOERROR; + 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); +} + +static void +octusb_standard_done(struct usb_xfer *xfer) +{ + struct octusb_softc *sc; + struct octusb_qh *qh; + usb_error_t error = 0; + + DPRINTFN(12, "xfer=%p endpoint=%p transfer done\n", + xfer, xfer->endpoint); + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) + error = octusb_standard_done_sub(xfer); + + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) + goto done; + } + while (xfer->aframes != xfer->nframes) { + + error = octusb_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) + error = octusb_standard_done_sub(xfer); + +done: + /* update data toggle */ + + qh = xfer->qh_start[0]; + sc = qh->sc; + + xfer->endpoint->toggle_next = + cvmx_usb_get_toggle( + &sc->sc_port[qh->port_index].state, + qh->ep_handle) ? 1 : 0; + + octusb_device_done(xfer, error); +} + +static void +octusb_interrupt_poll(struct octusb_softc *sc) +{ + struct usb_xfer *xfer; + uint8_t x; + + /* poll all ports */ + for (x = 0; x != sc->sc_noport; x++) + cvmx_usb_poll(&sc->sc_port[x].state); + +repeat: + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + if (!octusb_xfer_do_fifo(xfer)) { + /* queue has been modified */ + goto repeat; + } + } +} + +static void +octusb_start_standard_chain(struct usb_xfer *xfer) +{ + DPRINTFN(8, "\n"); + + /* poll one time */ + if (octusb_xfer_do_fifo(xfer)) { + + /* put transfer on interrupt queue */ + usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usbd_transfer_timeout_ms(xfer, + &octusb_timeout, xfer->timeout); + } + } +} + +void +octusb_iterate_hw_softc(struct usb_bus *bus, usb_bus_mem_sub_cb_t *cb) +{ + +} + +usb_error_t +octusb_init(struct octusb_softc *sc) +{ + cvmx_usb_initialize_flags_t flags; + int status; + uint8_t x; + + /* flush all cache into memory */ + + usb_bus_mem_flush_all(&sc->sc_bus, &octusb_iterate_hw_softc); + + /* set up the bus struct */ + sc->sc_bus.methods = &octusb_bus_methods; + + /* get number of ports */ + sc->sc_noport = cvmx_usb_get_num_ports(); + + /* check number of ports */ + if (sc->sc_noport > OCTUSB_MAX_PORTS) + sc->sc_noport = OCTUSB_MAX_PORTS; + + /* set USB revision */ + sc->sc_bus.usbrev = USB_REV_2_0; + + /* flags for port initialization */ + flags = CVMX_USB_INITIALIZE_FLAGS_CLOCK_AUTO; +#ifdef USB_DEBUG + if (octusbdebug > 100) + flags |= CVMX_USB_INITIALIZE_FLAGS_DEBUG_ALL; +#endif + + USB_BUS_LOCK(&sc->sc_bus); + + /* setup all ports */ + for (x = 0; x != sc->sc_noport; x++) { + status = cvmx_usb_initialize(&sc->sc_port[x].state, x, flags); + if (status < 0) + sc->sc_port[x].disabled = 1; + } + + USB_BUS_UNLOCK(&sc->sc_bus); + + /* catch lost interrupts */ + octusb_do_poll(&sc->sc_bus); + + return (0); +} + +usb_error_t +octusb_uninit(struct octusb_softc *sc) +{ + uint8_t x; + + USB_BUS_LOCK(&sc->sc_bus); + + for (x = 0; x != sc->sc_noport; x++) { + if (sc->sc_port[x].disabled == 0) + cvmx_usb_shutdown(&sc->sc_port[x].state); + } + USB_BUS_UNLOCK(&sc->sc_bus); + + return (0); + +} + +void +octusb_suspend(struct octusb_softc *sc) +{ + +} + +void +octusb_resume(struct octusb_softc *sc) +{ + +} + +/*------------------------------------------------------------------------* + * octusb_interrupt - OCTUSB interrupt handler + *------------------------------------------------------------------------*/ +void +octusb_interrupt(struct octusb_softc *sc) +{ + USB_BUS_LOCK(&sc->sc_bus); + + DPRINTFN(16, "real interrupt\n"); + + /* poll all the USB transfers */ + octusb_interrupt_poll(sc); + + USB_BUS_UNLOCK(&sc->sc_bus); +} + +/*------------------------------------------------------------------------* + * octusb_timeout - OCTUSB transfer timeout handler + *------------------------------------------------------------------------*/ +static void +octusb_timeout(void *arg) +{ + struct usb_xfer *xfer = arg; + + DPRINTF("xfer=%p\n", xfer); + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + /* transfer is transferred */ + octusb_device_done(xfer, USB_ERR_TIMEOUT); +} + +/*------------------------------------------------------------------------* + * octusb_do_poll - OCTUSB poll transfers + *------------------------------------------------------------------------*/ +static void +octusb_do_poll(struct usb_bus *bus) +{ + struct octusb_softc *sc = OCTUSB_BUS2SC(bus); + + USB_BUS_LOCK(&sc->sc_bus); + octusb_interrupt_poll(sc); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +octusb_setup_standard_chain_sub(struct octusb_std_temp *temp) +{ + struct octusb_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_any = 0; + td->error_stall = 0; + td->short_pkt = temp->short_pkt; + td->alt_next = temp->setup_alt_next; +} + +static void +octusb_setup_standard_chain(struct usb_xfer *xfer) +{ + struct octusb_std_temp temp; + struct octusb_td *td; + uint32_t x; + + DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpointno), + xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); + + /* setup starting point */ + td = xfer->td_start[0]; + 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.offset = 0; + + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + temp.func = &octusb_host_control_header_tx; + temp.len = xfer->frlengths[0]; + temp.pc = xfer->frbuffers + 0; + temp.short_pkt = temp.len ? 1 : 0; + + /* check for last frame */ + if (xfer->nframes == 1) { + /* + * no STATUS stage yet, SETUP is + * last + */ + if (xfer->flags_int.control_act) + temp.setup_alt_next = 0; + } + octusb_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + if (x != xfer->nframes) { + if (xfer->endpointno & UE_DIR_IN) { + if (xfer->flags_int.control_xfr) + temp.func = &octusb_host_control_data_rx; + else + temp.func = &octusb_non_control_data_rx; + } else { + if (xfer->flags_int.control_xfr) + temp.func = &octusb_host_control_data_tx; + else + temp.func = &octusb_non_control_data_tx; + } + + /* setup "pc" pointer */ + temp.pc = xfer->frbuffers + x; + } + while (x != xfer->nframes) { + + /* DATA0 or DATA1 message */ + + temp.len = xfer->frlengths[x]; + + x++; + + if (x == xfer->nframes) { + if (xfer->flags_int.control_xfr) { + /* no STATUS stage yet, DATA is last */ + if (xfer->flags_int.control_act) + temp.setup_alt_next = 0; + } else { + 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; + } + + octusb_setup_standard_chain_sub(&temp); + + if (xfer->flags_int.isochronous_xfr) { + /* get next data offset */ + temp.offset += temp.len; + } else { + /* get next Page Cache pointer */ + temp.pc = xfer->frbuffers + x; + } + } + + /* check if we should append a status stage */ + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + temp.func = &octusb_host_control_status_tx; + temp.len = 0; + temp.pc = NULL; + temp.short_pkt = 0; + temp.setup_alt_next = 0; + + octusb_setup_standard_chain_sub(&temp); + } + /* must have at least one frame! */ + td = temp.td; + xfer->td_transfer_last = td; + + /* properly setup QH */ + + td->qh->ep_allocated = 0; + td->qh->ep_toggle_next = xfer->endpoint->toggle_next ? 1 : 0; +} + +/*------------------------------------------------------------------------* + * octusb_device_done - OCTUSB transfers done code + * + * NOTE: This function can be called more than one time in a row. + *------------------------------------------------------------------------*/ +static void +octusb_device_done(struct usb_xfer *xfer, usb_error_t error) +{ + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", + xfer, xfer->endpoint, error); + + /* + * 1) Free any endpoints. + * 2) Control transfers can be split and we should not re-open + * the data pipe between transactions unless there is an error. + */ + if ((xfer->flags_int.control_act == 0) || (error != 0)) { + struct octusb_td *td; + + td = xfer->td_start[0]; + + octusb_host_free_endpoint(td); + } + /* dequeue transfer and start next transfer */ + usbd_transfer_done(xfer, error); +} + +/*------------------------------------------------------------------------* + * octusb bulk support + *------------------------------------------------------------------------*/ +static void +octusb_device_bulk_open(struct usb_xfer *xfer) +{ + return; +} + +static void +octusb_device_bulk_close(struct usb_xfer *xfer) +{ + octusb_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +octusb_device_bulk_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +octusb_device_bulk_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + octusb_setup_standard_chain(xfer); + octusb_start_standard_chain(xfer); +} + +struct usb_pipe_methods octusb_device_bulk_methods = +{ + .open = octusb_device_bulk_open, + .close = octusb_device_bulk_close, + .enter = octusb_device_bulk_enter, + .start = octusb_device_bulk_start, +}; + +/*------------------------------------------------------------------------* + * octusb control support + *------------------------------------------------------------------------*/ +static void +octusb_device_ctrl_open(struct usb_xfer *xfer) +{ + return; +} + +static void +octusb_device_ctrl_close(struct usb_xfer *xfer) +{ + octusb_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +octusb_device_ctrl_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +octusb_device_ctrl_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + octusb_setup_standard_chain(xfer); + octusb_start_standard_chain(xfer); +} + +struct usb_pipe_methods octusb_device_ctrl_methods = +{ + .open = octusb_device_ctrl_open, + .close = octusb_device_ctrl_close, + .enter = octusb_device_ctrl_enter, + .start = octusb_device_ctrl_start, +}; + +/*------------------------------------------------------------------------* + * octusb interrupt support + *------------------------------------------------------------------------*/ +static void +octusb_device_intr_open(struct usb_xfer *xfer) +{ + return; +} + +static void +octusb_device_intr_close(struct usb_xfer *xfer) +{ + octusb_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +octusb_device_intr_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +octusb_device_intr_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + octusb_setup_standard_chain(xfer); + octusb_start_standard_chain(xfer); +} + +struct usb_pipe_methods octusb_device_intr_methods = +{ + .open = octusb_device_intr_open, + .close = octusb_device_intr_close, + .enter = octusb_device_intr_enter, + .start = octusb_device_intr_start, +}; + +/*------------------------------------------------------------------------* + * octusb isochronous support + *------------------------------------------------------------------------*/ +static void +octusb_device_isoc_open(struct usb_xfer *xfer) +{ + return; +} + +static void +octusb_device_isoc_close(struct usb_xfer *xfer) +{ + octusb_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +octusb_device_isoc_enter(struct usb_xfer *xfer) +{ + struct octusb_softc *sc = OCTUSB_BUS2SC(xfer->xroot->bus); + uint32_t temp; + uint32_t frame_count; + uint32_t fs_frames; + + DPRINTFN(5, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->endpoint->isoc_next, xfer->nframes); + + /* get the current frame index */ + + frame_count = cvmx_usb_get_frame_number( + &sc->sc_port[xfer->xroot->udev->port_index].state); + + /* + * check if the frame index is within the window where the frames + * will be inserted + */ + temp = (frame_count - xfer->endpoint->isoc_next) & 0x7FF; + + if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) { + fs_frames = (xfer->nframes + 7) / 8; + } else { + fs_frames = xfer->nframes; + } + + if ((xfer->endpoint->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->endpoint->isoc_next = (frame_count + 3) & 0x7FF; + xfer->endpoint->is_synced = 1; + DPRINTFN(2, "start next=%d\n", xfer->endpoint->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + temp = (xfer->endpoint->isoc_next - frame_count) & 0x7FF; + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb_isoc_time_expand(&sc->sc_bus, frame_count) + temp + + fs_frames; + + /* compute frame number for next insertion */ + xfer->endpoint->isoc_next += fs_frames; +} + +static void +octusb_device_isoc_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + octusb_setup_standard_chain(xfer); + octusb_start_standard_chain(xfer); +} + +struct usb_pipe_methods octusb_device_isoc_methods = +{ + .open = octusb_device_isoc_open, + .close = octusb_device_isoc_close, + .enter = octusb_device_isoc_enter, + .start = octusb_device_isoc_start, +}; + +/*------------------------------------------------------------------------* + * OCTUSB root HUB support + *------------------------------------------------------------------------* + * Simulate a hardware HUB by handling all the necessary requests. + *------------------------------------------------------------------------*/ +static const +struct usb_device_descriptor octusb_devd = { + .bLength = sizeof(octusb_devd), + .bDescriptorType = UDESC_DEVICE, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_FSHUB, + .bMaxPacketSize = 64, + .idVendor = {0}, + .idProduct = {0}, + .bcdDevice = {0x00, 0x01}, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 0, + .bNumConfigurations = 1, +}; + +static const +struct usb_device_qualifier octusb_odevd = { + .bLength = sizeof(octusb_odevd), + .bDescriptorType = UDESC_DEVICE_QUALIFIER, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_FSHUB, + .bMaxPacketSize0 = 0, + .bNumConfigurations = 0, + .bReserved = 0, +}; + +static const +struct octusb_config_desc octusb_confd = { + .confd = { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(octusb_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0 /* max power */ + }, + .ifcd = { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = UIPROTO_FSHUB, + }, + .endpd = { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = UE_DIR_IN | OCTUSB_INTR_ENDPT, + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 8, /* max packet (63 ports) */ + .bInterval = 255, + }, +}; + +static const +struct usb_hub_descriptor_min octusb_hubd = +{ + .bDescLength = sizeof(octusb_hubd), + .bDescriptorType = UDESC_HUB, + .bNbrPorts = 2, + .wHubCharacteristics = {UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL, 0}, + .bPwrOn2PwrGood = 50, + .bHubContrCurrent = 0, + .DeviceRemovable = {0x00}, /* all ports are removable */ +}; + +static usb_error_t +octusb_roothub_exec(struct usb_device *udev, + struct usb_device_request *req, const void **pptr, uint16_t *plength) +{ + struct octusb_softc *sc = OCTUSB_BUS2SC(udev->bus); + const void *ptr; + const char *str_ptr; + uint16_t value; + uint16_t index; + uint16_t status; + uint16_t change; + uint16_t len; + usb_error_t err; + cvmx_usb_port_status_t usb_port_status; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* XXX disable power save mode, hence it is not supported */ + udev->power_mode = USB_POWER_MODE_ON; + + /* buffer reset */ + ptr = (const void *)&sc->sc_hub_desc.temp; + len = 0; + err = 0; + + value = UGETW(req->wValue); + index = UGETW(req->wIndex); + + DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x " + "wValue=0x%04x wIndex=0x%04x\n", + req->bmRequestType, req->bRequest, + UGETW(req->wLength), value, index); + +#define C(x,y) ((x) | ((y) << 8)) + switch (C(req->bRequest, 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): + break; + case C(UR_GET_CONFIG, UT_READ_DEVICE): + 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) { + err = USB_ERR_IOERROR; + goto done; + } + len = sizeof(octusb_devd); + + ptr = (const void *)&octusb_devd; + break; + + case UDESC_DEVICE_QUALIFIER: + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + len = sizeof(octusb_odevd); + ptr = (const void *)&octusb_odevd; + break; + + case UDESC_CONFIG: + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + len = sizeof(octusb_confd); + ptr = (const void *)&octusb_confd; + break; + + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + str_ptr = "\001"; + break; + + case 1: /* Vendor */ + str_ptr = "Cavium Networks"; + break; + + case 2: /* Product */ + str_ptr = "OCTUSB Root HUB"; + break; + + default: + str_ptr = ""; + break; + } + + len = usb_make_str_desc(sc->sc_hub_desc.temp, + sizeof(sc->sc_hub_desc.temp), str_ptr); + break; + + default: + err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_GET_INTERFACE, UT_READ_INTERFACE): + len = 1; + sc->sc_hub_desc.temp[0] = 0; + break; + case C(UR_GET_STATUS, UT_READ_DEVICE): + 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): + len = 2; + USETW(sc->sc_hub_desc.stat.wStatus, 0); + break; + case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): + if (value >= OCTUSB_MAX_DEVICES) { + err = USB_ERR_IOERROR; + goto done; + } + sc->sc_addr = value; + break; + case C(UR_SET_CONFIG, UT_WRITE_DEVICE): + if ((value != 0) && (value != 1)) { + 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): + 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) || + (index > sc->sc_noport) || + sc->sc_port[index - 1].disabled) { + err = USB_ERR_IOERROR; + goto done; + } + index--; + + switch (value) { + case UHF_PORT_ENABLE: + cvmx_usb_disable(&sc->sc_port[index].state); + break; + case UHF_PORT_SUSPEND: + case UHF_PORT_RESET: + break; + case UHF_C_PORT_CONNECTION: + cvmx_usb_set_status(&sc->sc_port[index].state, + cvmx_usb_get_status(&sc->sc_port[index].state)); + break; + case UHF_C_PORT_ENABLE: + cvmx_usb_set_status(&sc->sc_port[index].state, + cvmx_usb_get_status(&sc->sc_port[index].state)); + break; + case UHF_C_PORT_OVER_CURRENT: + cvmx_usb_set_status(&sc->sc_port[index].state, + cvmx_usb_get_status(&sc->sc_port[index].state)); + break; + case UHF_C_PORT_RESET: + sc->sc_isreset = 0; + goto done; + case UHF_C_PORT_SUSPEND: + break; + case UHF_PORT_CONNECTION: + case UHF_PORT_OVER_CURRENT: + case UHF_PORT_POWER: + case UHF_PORT_LOW_SPEED: + default: + err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + sc->sc_hubd = octusb_hubd; + sc->sc_hubd.bNbrPorts = sc->sc_noport; + len = sizeof(sc->sc_hubd); + ptr = (const void *)&sc->sc_hubd; + break; + case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): + len = 16; + memset(sc->sc_hub_desc.temp, 0, 16); + break; + case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): + if ((index < 1) || + (index > sc->sc_noport) || + sc->sc_port[index - 1].disabled) { + err = USB_ERR_IOERROR; + goto done; + } + index--; + + usb_port_status = cvmx_usb_get_status(&sc->sc_port[index].state); + + status = change = 0; + if (usb_port_status.connected) + status |= UPS_CURRENT_CONNECT_STATUS; + if (usb_port_status.port_enabled) + status |= UPS_PORT_ENABLED; + if (usb_port_status.port_over_current) + status |= UPS_OVERCURRENT_INDICATOR; + if (usb_port_status.port_powered) + status |= UPS_PORT_POWER; + + switch (usb_port_status.port_speed) { + case CVMX_USB_SPEED_HIGH: + status |= UPS_HIGH_SPEED; + break; + case CVMX_USB_SPEED_FULL: + break; + default: + status |= UPS_LOW_SPEED; + break; + } + + if (usb_port_status.connect_change) + change |= UPS_C_CONNECT_STATUS; + 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); + + len = sizeof(sc->sc_hub_desc.ps); + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): + 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) || + sc->sc_port[index - 1].disabled) { + err = USB_ERR_IOERROR; + goto done; + } + index--; + + switch (value) { + case UHF_PORT_ENABLE: + break; + case UHF_PORT_RESET: + cvmx_usb_disable(&sc->sc_port[index].state); + if (cvmx_usb_enable(&sc->sc_port[index].state)) { + err = USB_ERR_IOERROR; + goto done; + } + sc->sc_isreset = 1; + goto done; + case UHF_PORT_POWER: + /* pretend we turned on power */ + goto done; + case UHF_PORT_SUSPEND: + 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: + err = USB_ERR_IOERROR; + goto done; + } + break; + default: + err = USB_ERR_IOERROR; + goto done; + } +done: + *plength = len; + *pptr = ptr; + return (err); +} + +static void +octusb_xfer_setup(struct usb_setup_params *parm) +{ + struct usb_page_search page_info; + struct usb_page_cache *pc; + struct octusb_softc *sc; + struct octusb_qh *qh; + struct usb_xfer *xfer; + void *last_obj; + uint32_t n; + uint32_t ntd; + + sc = OCTUSB_BUS2SC(parm->udev->bus); + xfer = parm->curr_xfer; + qh = NULL; + + /* + * 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_packet_count = 3; + parm->hc_max_frame_size = 0xC00; + + usbd_transfer_setup_sub(parm); + + if (parm->err) + return; + + /* Allocate a queue head */ + + if (usbd_transfer_setup_sub_malloc( + parm, &pc, sizeof(struct octusb_qh), + USB_HOST_ALIGN, 1)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + usbd_get_page(pc, 0, &page_info); + + qh = page_info.buffer; + + /* fill out QH */ + + qh->sc = OCTUSB_BUS2SC(xfer->xroot->bus); + qh->max_frame_size = xfer->max_frame_size; + qh->max_packet_size = xfer->max_packet_size; + qh->ep_num = xfer->endpointno; + qh->ep_type = xfer->endpoint->edesc->bmAttributes; + qh->dev_addr = xfer->address; + qh->dev_speed = usbd_get_speed(xfer->xroot->udev); + qh->port_index = xfer->xroot->udev->port_index; + + switch (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) + qh->ep_interval = xfer->interval * 8; + else + qh->ep_interval = xfer->interval * 1; + break; + case UE_ISOCHRONOUS: + qh->ep_interval = 1 << xfer->fps_shift; + break; + default: + qh->ep_interval = 0; + break; + } + + qh->ep_mult = xfer->max_packet_count & 3; + qh->hs_hub_addr = xfer->xroot->udev->hs_hub_addr; + qh->hs_hub_port = xfer->xroot->udev->hs_port_no; + } + xfer->qh_start[0] = qh; + + /* Allocate a fixup buffer */ + + if (usbd_transfer_setup_sub_malloc( + parm, &pc, OCTUSB_MAX_FIXUP, + OCTUSB_MAX_FIXUP, 1)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + usbd_get_page(pc, 0, &page_info); + + qh->fixup_phys = page_info.physaddr; + qh->fixup_pc = pc; + qh->fixup_buf = page_info.buffer; + } + /* Allocate transfer descriptors */ + + last_obj = NULL; + + ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC */ ; + + if (usbd_transfer_setup_sub_malloc( + parm, &pc, sizeof(struct octusb_td), + USB_HOST_ALIGN, ntd)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != ntd; n++) { + struct octusb_td *td; + + usbd_get_page(pc + n, 0, &page_info); + + td = page_info.buffer; + + td->qh = qh; + td->obj_next = last_obj; + + last_obj = td; + } + } + xfer->td_start[0] = last_obj; +} + +static void +octusb_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, + struct usb_endpoint *ep) +{ + struct octusb_softc *sc = OCTUSB_BUS2SC(udev->bus); + + DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n", + ep, udev->address, edesc->bEndpointAddress, + udev->flags.usb_mode, sc->sc_addr); + + if (udev->flags.usb_mode != USB_MODE_HOST) { + /* not supported */ + return; + } + if (udev->device_index != sc->sc_addr) { + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + ep->methods = &octusb_device_ctrl_methods; + break; + case UE_INTERRUPT: + ep->methods = &octusb_device_intr_methods; + break; + case UE_ISOCHRONOUS: + if (udev->speed != USB_SPEED_LOW) + ep->methods = &octusb_device_isoc_methods; + break; + case UE_BULK: + ep->methods = &octusb_device_bulk_methods; + break; + default: + /* do nothing */ + break; + } + } +} + +static void +octusb_xfer_unsetup(struct usb_xfer *xfer) +{ + DPRINTF("Nothing to do.\n"); +} + +static void +octusb_get_dma_delay(struct usb_bus *bus, uint32_t *pus) +{ + /* DMA delay - wait until any use of memory is finished */ + *pus = (2125); /* microseconds */ +} + +static void +octusb_device_resume(struct usb_device *udev) +{ + DPRINTF("Nothing to do.\n"); +} + +static void +octusb_device_suspend(struct usb_device *udev) +{ + DPRINTF("Nothing to do.\n"); +} + +static void +octusb_set_hw_power(struct usb_bus *bus) +{ + DPRINTF("Nothing to do.\n"); +} + +struct usb_bus_methods octusb_bus_methods = { + .endpoint_init = octusb_ep_init, + .xfer_setup = octusb_xfer_setup, + .xfer_unsetup = octusb_xfer_unsetup, + .get_dma_delay = octusb_get_dma_delay, + .device_resume = octusb_device_resume, + .device_suspend = octusb_device_suspend, + .set_hw_power = octusb_set_hw_power, + .roothub_exec = octusb_roothub_exec, + .xfer_poll = octusb_do_poll, +}; diff --git a/sys/mips/cavium/usb/octusb.h b/sys/mips/cavium/usb/octusb.h new file mode 100644 index 0000000..31f4fc0 --- /dev/null +++ b/sys/mips/cavium/usb/octusb.h @@ -0,0 +1,147 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2010 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 _OCTUSB_H_ +#define _OCTUSB_H_ + +#define OCTUSB_MAX_DEVICES MIN(USB_MAX_DEVICES, 64) +/* + * The second port is on a different IRQ and so we disable it for now. + */ +#if 1 +#define OCTUSB_MAX_PORTS 1 /* hardcoded */ +#else +#define OCTUSB_MAX_PORTS 2 /* hardcoded */ +#endif +#define OCTUSB_MAX_FIXUP 4096 /* bytes */ +#define OCTUSB_INTR_ENDPT 0x01 + +struct octusb_qh; +struct octusb_td; +struct octusb_softc; + +typedef uint8_t (octusb_cmd_t)(struct octusb_td *td); + +struct octusb_td { + struct octusb_qh *qh; + struct octusb_td *obj_next; + struct usb_page_cache *pc; + octusb_cmd_t *func; + + uint32_t remainder; + uint32_t offset; + + uint8_t error_any:1; + uint8_t error_stall:1; + uint8_t short_pkt:1; + uint8_t alt_next:1; + uint8_t reserved:4; +}; + +struct octusb_qh { + + uint64_t fixup_phys; + + struct octusb_softc *sc; + struct usb_page_cache *fixup_pc; + uint8_t *fixup_buf; + + cvmx_usb_iso_packet_t iso_pkt; + + uint32_t fixup_off; + + uint16_t max_frame_size; + uint16_t max_packet_size; + uint16_t fixup_actlen; + uint16_t fixup_len; + uint16_t ep_interval; + + uint8_t dev_addr; + uint8_t dev_speed; + uint8_t ep_allocated; + uint8_t ep_mult; + uint8_t ep_num; + uint8_t ep_type; + uint8_t ep_toggle_next; + uint8_t port_index; + uint8_t fixup_complete; + uint8_t fixup_pending; + uint8_t hs_hub_addr; + uint8_t hs_hub_port; + + int fixup_handle; + int ep_handle; +}; + +struct octusb_config_desc { + struct usb_config_descriptor confd; + struct usb_interface_descriptor ifcd; + struct usb_endpoint_descriptor endpd; +} __packed; + +union octusb_hub_desc { + struct usb_status stat; + struct usb_port_status ps; + uint8_t temp[128]; +}; + +struct octusb_port { + cvmx_usb_state_t state; + uint8_t disabled; +}; + +struct octusb_softc { + + struct usb_bus sc_bus; /* base device */ + union octusb_hub_desc sc_hub_desc; + + struct usb_device *sc_devices[OCTUSB_MAX_DEVICES]; + + struct resource *sc_irq_res; + void *sc_intr_hdl; + + struct octusb_port sc_port[OCTUSB_MAX_PORTS]; + device_t sc_dev; + + struct usb_hub_descriptor_min sc_hubd; + + uint8_t sc_noport; /* number of ports */ + uint8_t sc_addr; /* device address */ + uint8_t sc_conf; /* device configuration */ + uint8_t sc_isreset; /* set if current port is reset */ + + uint8_t sc_hub_idata[1]; +}; + +usb_bus_mem_cb_t octusb_iterate_hw_softc; +usb_error_t octusb_init(struct octusb_softc *); +usb_error_t octusb_uninit(struct octusb_softc *); +void octusb_suspend(struct octusb_softc *); +void octusb_resume(struct octusb_softc *); +void octusb_interrupt(struct octusb_softc *); + +#endif /* _OCTUSB_H_ */ diff --git a/sys/mips/cavium/usb/octusb_octeon.c b/sys/mips/cavium/usb/octusb_octeon.c new file mode 100644 index 0000000..edd8290 --- /dev/null +++ b/sys/mips/cavium/usb/octusb_octeon.c @@ -0,0 +1,223 @@ +#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 <sys/stdint.h> +#include <sys/stddef.h> +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/linker_set.h> +#include <sys/module.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> +#include <sys/rman.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> + +#include <dev/usb/usb_core.h> +#include <dev/usb/usb_busdma.h> +#include <dev/usb/usb_process.h> +#include <dev/usb/usb_util.h> + +#include <dev/usb/usb_controller.h> +#include <dev/usb/usb_bus.h> + +#include <contrib/octeon-sdk/cvmx.h> +#include <contrib/octeon-sdk/cvmx-interrupt.h> +#include <contrib/octeon-sdk/cvmx-usb.h> + +#include <mips/cavium/usb/octusb.h> + +#define MEM_RID 0 + +static device_identify_t octusb_octeon_identify; +static device_probe_t octusb_octeon_probe; +static device_attach_t octusb_octeon_attach; +static device_detach_t octusb_octeon_detach; +static device_shutdown_t octusb_octeon_shutdown; + +struct octusb_octeon_softc { + struct octusb_softc sc_dci; /* must be first */ +}; + +static void +octusb_octeon_identify(driver_t *drv, device_t parent) +{ + if (octeon_has_feature(OCTEON_FEATURE_USB)) + BUS_ADD_CHILD(parent, 0, "octusb", 0); +} + +static int +octusb_octeon_probe(device_t dev) +{ + device_set_desc(dev, "Cavium Octeon USB controller"); + return (0); +} + +static int +octusb_octeon_attach(device_t dev) +{ + struct octusb_octeon_softc *sc = device_get_softc(dev); + int err; + int rid; + + /* setup controller interface softc */ + + /* initialise some bus fields */ + sc->sc_dci.sc_bus.parent = dev; + sc->sc_dci.sc_bus.devices = sc->sc_dci.sc_devices; + sc->sc_dci.sc_bus.devices_max = OCTUSB_MAX_DEVICES; + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_dci.sc_bus, + USB_GET_DMA_TAG(dev), NULL)) { + return (ENOMEM); + } + rid = 0; + sc->sc_dci.sc_irq_res = + bus_alloc_resource(dev, SYS_RES_IRQ, &rid, + CVMX_IRQ_USB, CVMX_IRQ_USB, 1, RF_ACTIVE); + if (!(sc->sc_dci.sc_irq_res)) { + goto error; + } + + sc->sc_dci.sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (!(sc->sc_dci.sc_bus.bdev)) { + goto error; + } + device_set_ivars(sc->sc_dci.sc_bus.bdev, &sc->sc_dci.sc_bus); + +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(dev, sc->sc_dci.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)octusb_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, + (driver_intr_t *)octusb_interrupt, sc, &sc->sc_dci.sc_intr_hdl); +#endif + if (err) { + sc->sc_dci.sc_intr_hdl = NULL; + goto error; + } + err = octusb_init(&sc->sc_dci); + if (!err) { + err = device_probe_and_attach(sc->sc_dci.sc_bus.bdev); + } + if (err) { + goto error; + } + return (0); + +error: + octusb_octeon_detach(dev); + return (ENXIO); +} + +static int +octusb_octeon_detach(device_t dev) +{ + struct octusb_octeon_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); + + if (sc->sc_dci.sc_irq_res && sc->sc_dci.sc_intr_hdl) { + /* + * only call octusb_octeon_uninit() after octusb_octeon_init() + */ + octusb_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; + } + usb_bus_mem_free_all(&sc->sc_dci.sc_bus, NULL); + + return (0); +} + +static int +octusb_octeon_shutdown(device_t dev) +{ + struct octusb_octeon_softc *sc = device_get_softc(dev); + int err; + + err = bus_generic_shutdown(dev); + if (err) + return (err); + + octusb_uninit(&sc->sc_dci); + + return (0); +} + +static device_method_t octusb_octeon_methods[] = { + /* Device interface */ + DEVMETHOD(device_identify, octusb_octeon_identify), + DEVMETHOD(device_probe, octusb_octeon_probe), + DEVMETHOD(device_attach, octusb_octeon_attach), + DEVMETHOD(device_detach, octusb_octeon_detach), + DEVMETHOD(device_shutdown, octusb_octeon_shutdown), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + + {0, 0} +}; + +static driver_t octusb_octeon_driver = { + "octusb", + octusb_octeon_methods, + sizeof(struct octusb_octeon_softc), +}; + +static devclass_t octusb_octeon_devclass; + +DRIVER_MODULE(octusb, ciu, octusb_octeon_driver, octusb_octeon_devclass, 0, 0); |