svn commit: r266051 - head/sys/dev/usb/controller
Hans Petter Selasky
hselasky at FreeBSD.org
Wed May 14 17:04:03 UTC 2014
Author: hselasky
Date: Wed May 14 17:04:02 2014
New Revision: 266051
URL: http://svnweb.freebsd.org/changeset/base/266051
Log:
Implement USB device side driver code for SAF1761 and compatible
chips, based on datasheet and existing USS820 DCI driver. This code is
not yet tested.
Sponsored by: DARPA, AFRL
Modified:
head/sys/dev/usb/controller/saf1761_dci.c
head/sys/dev/usb/controller/saf1761_dci.h
head/sys/dev/usb/controller/saf1761_dci_fdt.c
head/sys/dev/usb/controller/saf1761_dci_reg.h
Modified: head/sys/dev/usb/controller/saf1761_dci.c
==============================================================================
--- head/sys/dev/usb/controller/saf1761_dci.c Wed May 14 17:01:35 2014 (r266050)
+++ head/sys/dev/usb/controller/saf1761_dci.c Wed May 14 17:04:02 2014 (r266051)
@@ -1,6 +1,6 @@
/* $FreeBSD$ */
/*-
- * Copyright (c) 2014 Hans Petter Selasky
+ * Copyright (c) 2014 Hans Petter Selasky <hselasky at FreeBSD.org>
* All rights reserved.
*
* This software was developed by SRI International and the University of
@@ -29,5 +29,1958 @@
* SUCH DAMAGE.
*/
+/*
+ * This file contains the driver for the SAF1761 series USB OTG
+ * controller.
+ *
+ * Datasheet is available from:
+ * http://www.nxp.com/products/automotive/multimedia/usb/SAF1761BE.html
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#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/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 saf1761_dci_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_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>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
#include <dev/usb/controller/saf1761_dci.h>
#include <dev/usb/controller/saf1761_dci_reg.h>
+
+#define SAF1761_DCI_BUS2SC(bus) \
+ ((struct saf1761_dci_softc *)(((uint8_t *)(bus)) - \
+ ((uint8_t *)&(((struct saf1761_dci_softc *)0)->sc_bus))))
+
+#ifdef USB_DEBUG
+static int saf1761_dci_debug = 0;
+static int saf1761_dci_forcefs = 0;
+
+static
+SYSCTL_NODE(_hw_usb, OID_AUTO, saf1761_dci, CTLFLAG_RW, 0,
+ "USB SAF1761 DCI");
+
+SYSCTL_INT(_hw_usb_saf1761_dci, OID_AUTO, debug, CTLFLAG_RW,
+ &saf1761_dci_debug, 0, "SAF1761 DCI debug level");
+SYSCTL_INT(_hw_usb_saf1761_dci, OID_AUTO, forcefs, CTLFLAG_RW,
+ &saf1761_dci_forcefs, 0, "SAF1761 DCI force FULL speed");
+#endif
+
+#define SAF1761_DCI_INTR_ENDPT 1
+
+/* prototypes */
+
+static const struct usb_bus_methods saf1761_dci_bus_methods;
+static const struct usb_pipe_methods saf1761_dci_device_non_isoc_methods;
+static const struct usb_pipe_methods saf1761_dci_device_isoc_methods;
+
+static saf1761_dci_cmd_t saf1761_dci_setup_rx;
+static saf1761_dci_cmd_t saf1761_dci_data_rx;
+static saf1761_dci_cmd_t saf1761_dci_data_tx;
+static saf1761_dci_cmd_t saf1761_dci_data_tx_sync;
+static void saf1761_dci_device_done(struct usb_xfer *, usb_error_t);
+static void saf1761_dci_do_poll(struct usb_bus *);
+static void saf1761_dci_standard_done(struct usb_xfer *);
+static void saf1761_dci_intr_set(struct usb_xfer *, uint8_t);
+static void saf1761_dci_root_intr(struct saf1761_dci_softc *);
+
+/*
+ * Here is a list of what the SAF1761 chip can support. The main
+ * limitation is that the sum of the buffer sizes must be less than
+ * 8192 bytes.
+ */
+static const struct usb_hw_ep_profile saf1761_dci_ep_profile[] = {
+
+ [0] = {
+ .max_in_frame_size = 64,
+ .max_out_frame_size = 64,
+ .is_simplex = 0,
+ .support_control = 1,
+ },
+ [1] = {
+ .max_in_frame_size = SOTG_HS_MAX_PACKET_SIZE,
+ .max_out_frame_size = SOTG_HS_MAX_PACKET_SIZE,
+ .is_simplex = 0,
+ .support_interrupt = 1,
+ .support_bulk = 1,
+ .support_isochronous = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+};
+
+static void
+saf1761_dci_get_hw_ep_profile(struct usb_device *udev,
+ const struct usb_hw_ep_profile **ppf, uint8_t ep_addr)
+{
+ if (ep_addr == 0) {
+ *ppf = saf1761_dci_ep_profile + 0;
+ } else if (ep_addr < 8) {
+ *ppf = saf1761_dci_ep_profile + 1;
+ } else {
+ *ppf = NULL;
+ }
+}
+
+static void
+saf1761_dci_pull_up(struct saf1761_dci_softc *sc)
+{
+ /* activate pullup on D+, if possible */
+
+ if (!sc->sc_flags.d_pulled_up && sc->sc_flags.port_powered) {
+ DPRINTF("\n");
+
+ sc->sc_flags.d_pulled_up = 1;
+
+ SAF1761_WRITE_2(sc, SOTG_CTRL_SET, SOTG_CTRL_DP_PULL_UP);
+ }
+}
+
+static void
+saf1761_dci_pull_down(struct saf1761_dci_softc *sc)
+{
+ /* release pullup on D+, if possible */
+
+ if (sc->sc_flags.d_pulled_up) {
+ DPRINTF("\n");
+
+ sc->sc_flags.d_pulled_up = 0;
+
+ SAF1761_WRITE_2(sc, SOTG_CTRL_CLR, SOTG_CTRL_DP_PULL_UP);
+ }
+}
+
+static void
+saf1761_dci_wakeup_peer(struct saf1761_dci_softc *sc)
+{
+ uint16_t temp;
+
+ if (!(sc->sc_flags.status_suspend))
+ return;
+
+ DPRINTFN(5, "\n");
+
+ temp = SAF1761_READ_2(sc, SOTG_MODE);
+ SAF1761_WRITE_2(sc, SOTG_MODE, temp | SOTG_MODE_SNDRSU);
+ SAF1761_WRITE_2(sc, SOTG_MODE, temp & ~SOTG_MODE_SNDRSU);
+
+ /* Wait 8ms for remote wakeup to complete. */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125);
+
+}
+
+static void
+saf1761_dci_set_address(struct saf1761_dci_softc *sc, uint8_t addr)
+{
+ DPRINTFN(5, "addr=%d\n", addr);
+
+ SAF1761_WRITE_1(sc, SOTG_ADDRESS, addr | SOTG_ADDRESS_ENABLE);
+}
+
+static void
+saf1761_read_fifo(struct saf1761_dci_softc *sc, void *buf, uint32_t len)
+{
+ bus_space_read_multi_1((sc)->sc_io_tag, (sc)->sc_io_hdl, SOTG_DATA_PORT, buf, len);
+}
+
+static void
+saf1761_write_fifo(struct saf1761_dci_softc *sc, void *buf, uint32_t len)
+{
+ bus_space_write_multi_1((sc)->sc_io_tag, (sc)->sc_io_hdl, SOTG_DATA_PORT, buf, len);
+}
+
+static uint8_t
+saf1761_dci_setup_rx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
+{
+ struct usb_device_request req;
+ uint16_t count;
+
+ /* select the correct endpoint */
+ SAF1761_WRITE_1(sc, SOTG_EP_INDEX, SOTG_EP_INDEX_EP0SETUP);
+
+ /* check buffer status */
+ if ((SAF1761_READ_1(sc, SOTG_DCBUFFERSTATUS) &
+ SOTG_DCBUFFERSTATUS_FILLED_MASK) == 0)
+ goto busy;
+
+ /* read buffer length */
+ count = SAF1761_READ_2(sc, SOTG_BUF_LENGTH);
+
+ DPRINTFN(5, "count=%u rem=%u\n", count, td->remainder);
+
+ /* clear did stall */
+ td->did_stall = 0;
+
+ /* clear stall */
+ SAF1761_WRITE_1(sc, SOTG_CTRL_FUNC, 0);
+
+ /* verify data length */
+ if (count != td->remainder) {
+ DPRINTFN(0, "Invalid SETUP packet "
+ "length, %d bytes\n", count);
+ goto busy;
+ }
+ if (count != sizeof(req)) {
+ DPRINTFN(0, "Unsupported SETUP packet "
+ "length, %d bytes\n", count);
+ goto busy;
+ }
+ /* receive data */
+ saf1761_read_fifo(sc, &req, sizeof(req));
+
+ /* copy data into real buffer */
+ usbd_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 */
+
+busy:
+ /* abort any ongoing transfer */
+ if (!td->did_stall) {
+ DPRINTFN(5, "stalling\n");
+
+ /* set stall */
+ SAF1761_WRITE_1(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_STALL);
+
+ td->did_stall = 1;
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+saf1761_dci_data_rx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
+{
+ struct usb_page_search buf_res;
+ uint16_t count;
+ uint8_t got_short = 0;
+
+ if (td->ep_index == 0) {
+ /* select the correct endpoint */
+ SAF1761_WRITE_1(sc, SOTG_EP_INDEX, SOTG_EP_INDEX_EP0SETUP);
+
+ /* check buffer status */
+ if ((SAF1761_READ_1(sc, SOTG_DCBUFFERSTATUS) &
+ SOTG_DCBUFFERSTATUS_FILLED_MASK) != 0) {
+
+ if (td->remainder == 0) {
+ /*
+ * We are actually complete and have
+ * received the next SETUP:
+ */
+ DPRINTFN(5, "faking complete\n");
+ return (0); /* complete */
+ }
+ DPRINTFN(5, "SETUP packet while receiving data\n");
+ /*
+ * USB Host Aborted the transfer.
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ }
+ /* select the correct endpoint */
+ SAF1761_WRITE_1(sc, SOTG_EP_INDEX,
+ (td->ep_index << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) |
+ SOTG_EP_INDEX_DIR_OUT);
+
+ /* check buffer status */
+ if ((SAF1761_READ_1(sc, SOTG_DCBUFFERSTATUS) &
+ SOTG_DCBUFFERSTATUS_FILLED_MASK) == 0) {
+ return (1); /* not complete */
+ }
+ /* read buffer length */
+ count = SAF1761_READ_2(sc, SOTG_BUF_LENGTH);
+
+ DPRINTFN(5, "rem=%u count=0x%04x\n", td->remainder, 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) {
+ usbd_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count)
+ buf_res.length = count;
+
+ /* receive data */
+ saf1761_read_fifo(sc, 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 */
+ return (0);
+ }
+ /* else need to receive a zero length packet */
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+saf1761_dci_data_tx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
+{
+ struct usb_page_search buf_res;
+ uint16_t count;
+ uint16_t count_old;
+
+ if (td->ep_index == 0) {
+ /* select the correct endpoint */
+ SAF1761_WRITE_1(sc, SOTG_EP_INDEX, SOTG_EP_INDEX_EP0SETUP);
+
+ /* check buffer status */
+ if ((SAF1761_READ_1(sc, SOTG_DCBUFFERSTATUS) &
+ SOTG_DCBUFFERSTATUS_FILLED_MASK) != 0) {
+ DPRINTFN(5, "SETUP abort\n");
+ /*
+ * USB Host Aborted the transfer.
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ }
+ /* select the correct endpoint */
+ SAF1761_WRITE_1(sc, SOTG_EP_INDEX,
+ (td->ep_index << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) |
+ SOTG_EP_INDEX_DIR_IN);
+
+ /* check buffer status */
+ if ((SAF1761_READ_1(sc, SOTG_DCBUFFERSTATUS) &
+ SOTG_DCBUFFERSTATUS_FILLED_MASK) != 0) {
+ return (1); /* not complete */
+ }
+ DPRINTFN(5, "rem=%u\n", td->remainder);
+
+ count = td->max_packet_size;
+ if (td->remainder < count) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ count = td->remainder;
+ }
+ count_old = count;
+
+ while (count > 0) {
+
+ usbd_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count)
+ buf_res.length = count;
+
+ /* transmit data */
+ saf1761_write_fifo(sc, buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ if (td->ep_index == 0) {
+ if (count_old < SOTG_FS_MAX_PACKET_SIZE) {
+ /* set end of packet */
+ SAF1761_WRITE_1(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_VENDP);
+ }
+ } else {
+ if (count_old < SOTG_HS_MAX_PACKET_SIZE) {
+ /* set end of packet */
+ SAF1761_WRITE_1(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_VENDP);
+ }
+ }
+
+ /* check remainder */
+ if (td->remainder == 0) {
+ if (td->short_pkt) {
+ return (0); /* complete */
+ }
+ /* else we need to transmit a short packet */
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+saf1761_dci_data_tx_sync(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
+{
+ if (td->ep_index == 0) {
+ /* select the correct endpoint */
+ SAF1761_WRITE_1(sc, SOTG_EP_INDEX, SOTG_EP_INDEX_EP0SETUP);
+
+ /* check buffer status */
+ if ((SAF1761_READ_1(sc, SOTG_DCBUFFERSTATUS) &
+ SOTG_DCBUFFERSTATUS_FILLED_MASK) != 0) {
+ DPRINTFN(5, "Faking complete\n");
+ return (0); /* complete */
+ }
+ }
+ /* select the correct endpoint */
+ SAF1761_WRITE_1(sc, SOTG_EP_INDEX,
+ (td->ep_index << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) |
+ SOTG_EP_INDEX_DIR_IN);
+
+ /* check buffer status */
+ if ((SAF1761_READ_1(sc, SOTG_DCBUFFERSTATUS) &
+ SOTG_DCBUFFERSTATUS_FILLED_MASK) != 0)
+ return (1); /* busy */
+
+ if (sc->sc_dv_addr != 0xFF) {
+ /* write function address */
+ saf1761_dci_set_address(sc, sc->sc_dv_addr);
+ }
+ return (0); /* complete */
+}
+
+static uint8_t
+saf1761_dci_xfer_do_fifo(struct saf1761_dci_softc *sc, struct usb_xfer *xfer)
+{
+ struct saf1761_dci_td *td;
+
+ DPRINTFN(9, "\n");
+
+ td = xfer->td_transfer_cache;
+ while (1) {
+ if ((td->func) (sc, 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 */
+
+ saf1761_dci_standard_done(xfer);
+
+ return (0); /* complete */
+}
+
+static void
+saf1761_dci_interrupt_poll(struct saf1761_dci_softc *sc)
+{
+ struct usb_xfer *xfer;
+
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ if (!saf1761_dci_xfer_do_fifo(sc, xfer)) {
+ /* queue has been modified */
+ goto repeat;
+ }
+ }
+}
+
+static void
+saf1761_dci_wait_suspend(struct saf1761_dci_softc *sc, uint8_t on)
+{
+ if (on) {
+ sc->sc_intr_enable |= SOTG_DCINTERRUPT_IESUSP;
+ sc->sc_intr_enable &= ~SOTG_DCINTERRUPT_IERESM;
+ } else {
+ sc->sc_intr_enable &= ~SOTG_DCINTERRUPT_IESUSP;
+ sc->sc_intr_enable |= SOTG_DCINTERRUPT_IERESM;
+ }
+ SAF1761_WRITE_4(sc, SOTG_DCINTERRUPT_EN, sc->sc_intr_enable);
+}
+
+static void
+saf1761_dci_update_vbus(struct saf1761_dci_softc *sc)
+{
+ if (SAF1761_READ_4(sc, SOTG_MODE) & SOTG_MODE_VBUSSTAT) {
+ DPRINTFN(4, "VBUS ON\n");
+
+ /* VBUS present */
+ if (!sc->sc_flags.status_vbus) {
+ sc->sc_flags.status_vbus = 1;
+
+ /* complete root HUB interrupt endpoint */
+ saf1761_dci_root_intr(sc);
+ }
+ } else {
+ DPRINTFN(4, "VBUS OFF\n");
+
+ /* VBUS not-present */
+ 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 */
+ saf1761_dci_root_intr(sc);
+ }
+ }
+}
+
+void
+saf1761_dci_interrupt(struct saf1761_dci_softc *sc)
+{
+ uint32_t status;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ status = SAF1761_READ_4(sc, SOTG_DCINTERRUPT);
+
+ /* acknowledge all interrupts */
+ SAF1761_WRITE_4(sc, SOTG_DCINTERRUPT, status);
+
+ if (status & SOTG_DCINTERRUPT_IEVBUS) {
+ /* update VBUS bit */
+ saf1761_dci_update_vbus(sc);
+ }
+ if (status & SOTG_DCINTERRUPT_IEBRST) {
+ 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 */
+ saf1761_dci_wait_suspend(sc, 1);
+ /* complete root HUB interrupt endpoint */
+ saf1761_dci_root_intr(sc);
+ }
+ /*
+ * If "RESUME" and "SUSPEND" is set at the same time we
+ * interpret that like "RESUME". Resume is set when there is
+ * at least 3 milliseconds of inactivity on the USB BUS:
+ */
+ if (status & SOTG_DCINTERRUPT_IERESM) {
+ if (sc->sc_flags.status_suspend) {
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 1;
+ /* disable resume interrupt */
+ saf1761_dci_wait_suspend(sc, 1);
+ /* complete root HUB interrupt endpoint */
+ saf1761_dci_root_intr(sc);
+ }
+ } else if (status & SOTG_DCINTERRUPT_IESUSP) {
+ if (!sc->sc_flags.status_suspend) {
+ sc->sc_flags.status_suspend = 1;
+ sc->sc_flags.change_suspend = 1;
+ /* enable resume interrupt */
+ saf1761_dci_wait_suspend(sc, 0);
+ /* complete root HUB interrupt endpoint */
+ saf1761_dci_root_intr(sc);
+ }
+ }
+ /* poll all active transfers */
+ saf1761_dci_interrupt_poll(sc);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+saf1761_dci_setup_standard_chain_sub(struct saf1761_dci_std_temp *temp)
+{
+ struct saf1761_dci_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 = temp->did_stall;
+ td->short_pkt = temp->short_pkt;
+ td->alt_next = temp->setup_alt_next;
+}
+
+static void
+saf1761_dci_setup_standard_chain(struct usb_xfer *xfer)
+{
+ struct saf1761_dci_std_temp temp;
+ struct saf1761_dci_softc *sc;
+ struct saf1761_dci_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->endpointno),
+ xfer->sumlen, usbd_get_speed(xfer->xroot->udev));
+
+ temp.max_frame_size = xfer->max_frame_size;
+
+ td = xfer->td_start[0];
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ /* setup temp */
+
+ temp.pc = NULL;
+ temp.td = NULL;
+ temp.td_next = xfer->td_start[0];
+ temp.offset = 0;
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+ temp.did_stall = !xfer->flags_int.control_stall;
+
+ sc = SAF1761_DCI_BUS2SC(xfer->xroot->bus);
+ ep_no = (xfer->endpointno & UE_ADDR);
+
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+
+ temp.func = &saf1761_dci_setup_rx;
+ 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;
+ }
+ saf1761_dci_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ if (x != xfer->nframes) {
+ if (xfer->endpointno & UE_DIR_IN) {
+ temp.func = &saf1761_dci_data_tx;
+ } else {
+ temp.func = &saf1761_dci_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) {
+ if (xfer->flags_int.control_xfr) {
+ 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;
+ }
+
+ saf1761_dci_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;
+ }
+ }
+
+ /* check for control transfer */
+ if (xfer->flags_int.control_xfr) {
+ uint8_t need_sync;
+
+ /* always setup a valid "pc" pointer for status and sync */
+ temp.pc = xfer->frbuffers + 0;
+ temp.len = 0;
+ temp.short_pkt = 0;
+ temp.setup_alt_next = 0;
+
+ /* check if we should append a status stage */
+ if (!xfer->flags_int.control_act) {
+
+ /*
+ * Send a DATA1 message and invert the current
+ * endpoint direction.
+ */
+ if (xfer->endpointno & UE_DIR_IN) {
+ temp.func = &saf1761_dci_data_rx;
+ need_sync = 0;
+ } else {
+ temp.func = &saf1761_dci_data_tx;
+ need_sync = 1;
+ }
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ saf1761_dci_setup_standard_chain_sub(&temp);
+ if (need_sync) {
+ /* we need a SYNC point after TX */
+ temp.func = &saf1761_dci_data_tx_sync;
+ saf1761_dci_setup_standard_chain_sub(&temp);
+ }
+ }
+ }
+ /* must have at least one frame! */
+ td = temp.td;
+ xfer->td_transfer_last = td;
+}
+
+static void
+saf1761_dci_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 */
+ saf1761_dci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+saf1761_dci_intr_set(struct usb_xfer *xfer, uint8_t set)
+{
+ struct saf1761_dci_softc *sc = SAF1761_DCI_BUS2SC(xfer->xroot->bus);
+ uint8_t ep_no = (xfer->endpointno & UE_ADDR);
+ uint32_t mask;
+
+ DPRINTFN(15, "endpoint 0x%02x\n", xfer->endpointno);
+
+ if (ep_no == 0) {
+ mask = SOTG_DCINTERRUPT_IEPRX(0) |
+ SOTG_DCINTERRUPT_IEPTX(0) |
+ SOTG_DCINTERRUPT_IEP0SETUP;
+ } else if (xfer->endpointno & UE_DIR_IN) {
+ mask = SOTG_DCINTERRUPT_IEPTX(ep_no);
+ } else {
+ mask = SOTG_DCINTERRUPT_IEPRX(ep_no);
+ }
+
+ if (set)
+ sc->sc_intr_enable |= mask;
+ else
+ sc->sc_intr_enable &= ~mask;
+
+ SAF1761_WRITE_4(sc, SOTG_DCINTERRUPT_EN, sc->sc_intr_enable);
+}
+
+static void
+saf1761_dci_start_standard_chain(struct usb_xfer *xfer)
+{
+ struct saf1761_dci_softc *sc = SAF1761_DCI_BUS2SC(xfer->xroot->bus);
+
+ DPRINTFN(9, "\n");
+
+ /* poll one time */
+ if (saf1761_dci_xfer_do_fifo(sc, xfer)) {
+
+ /*
+ * Only enable the endpoint interrupt when we are
+ * actually waiting for data, hence we are dealing
+ * with level triggered interrupts !
+ */
+ saf1761_dci_intr_set(xfer, 1);
+
+ /* 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,
+ &saf1761_dci_timeout, xfer->timeout);
+ }
+ }
+}
+
+static void
+saf1761_dci_root_intr(struct saf1761_dci_softc *sc)
+{
+ DPRINTFN(9, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* set port bit - we only have one port */
+ sc->sc_hub_idata[0] = 0x02;
+
+ uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata,
+ sizeof(sc->sc_hub_idata));
+}
+
+static usb_error_t
+saf1761_dci_standard_done_sub(struct usb_xfer *xfer)
+{
+ struct saf1761_dci_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
+saf1761_dci_standard_done(struct usb_xfer *xfer)
+{
+ usb_error_t err = 0;
+
+ DPRINTFN(13, "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) {
+
+ err = saf1761_dci_standard_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+
+ err = saf1761_dci_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 = saf1761_dci_standard_done_sub(xfer);
+ }
+done:
+ saf1761_dci_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * saf1761_dci_device_done
+ *
+ * NOTE: this function can be called more than one time on the
+ * same USB transfer!
+ *------------------------------------------------------------------------*/
+static void
+saf1761_dci_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);
*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
More information about the svn-src-head
mailing list