svn commit: r230424 - head/sys/dev/usb/controller

Hans Petter Selasky hselasky at FreeBSD.org
Sat Jan 21 13:31:38 UTC 2012


Author: hselasky
Date: Sat Jan 21 13:31:38 2012
New Revision: 230424
URL: http://svn.freebsd.org/changeset/base/230424

Log:
  Add support for the DesignWare USB 2.0 OTG controller chipset.
  Currently the code is not built by any modules. That will
  be fixed later. The Atmel ARM bus interface file part of this
  commit is just for sake of example. All registers and bits are
  declared like macros and not C-structures like in official
  Synopsis header files. This driver mostly origins from the
  musb_otg.c driver in FreeBSD except that the chip specific
  programming has been replaced by the one for DWC 2.0 USB OTG.
  Some parts related to system suspend and resume have been left
  like empty functions for the future. USB suspend and resume is
  fully supported.

Added:
  head/sys/dev/usb/controller/dwc_otg.c   (contents, props changed)
  head/sys/dev/usb/controller/dwc_otg.h   (contents, props changed)
  head/sys/dev/usb/controller/dwc_otg_atmelarm.c   (contents, props changed)

Added: head/sys/dev/usb/controller/dwc_otg.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/dev/usb/controller/dwc_otg.c	Sat Jan 21 13:31:38 2012	(r230424)
@@ -0,0 +1,2612 @@
+/*-
+ * Copyright (c) 2012 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 DesignWare series USB 2.0 OTG
+ * Controller. This driver currently only supports the device mode of
+ * the USB hardware.
+ */
+
+/*
+ * LIMITATION: Drivers must be bound to all OUT endpoints in the
+ * active configuration for this driver to work properly. Blocking any
+ * OUT endpoint will block all OUT endpoints including the control
+ * endpoint. Usually this is not a problem.
+ */
+
+/*
+ * NOTE: Writing to non-existing registers appears to cause an
+ * internal reset.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#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 dwc_otg_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>
+
+#include <dev/usb/controller/dwc_otg.h>
+
+#define	DWC_OTG_BUS2SC(bus) \
+   ((struct dwc_otg_softc *)(((uint8_t *)(bus)) - \
+    ((uint8_t *)&(((struct dwc_otg_softc *)0)->sc_bus))))
+
+#define	DWC_OTG_PC2SC(pc) \
+   DWC_OTG_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus)
+
+#define	DWC_OTG_MSK_GINT_ENABLED	\
+   (DWC_OTG_MSK_GINT_ENUM_DONE |	\
+   DWC_OTG_MSK_GINT_USB_SUSPEND |	\
+   DWC_OTG_MSK_GINT_INEP |		\
+   DWC_OTG_MSK_GINT_RXFLVL |		\
+   DWC_OTG_MSK_GINT_SESSREQINT)
+
+#define DWC_OTG_USE_HSIC 0
+
+#ifdef USB_DEBUG
+static int dwc_otg_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, dwc_otg, CTLFLAG_RW, 0, "USB DWC OTG");
+SYSCTL_INT(_hw_usb_dwc_otg, OID_AUTO, debug, CTLFLAG_RW,
+    &dwc_otg_debug, 0, "DWC OTG debug level");
+#endif
+
+#define	DWC_OTG_INTR_ENDPT 1
+
+/* prototypes */
+
+struct usb_bus_methods dwc_otg_bus_methods;
+struct usb_pipe_methods dwc_otg_device_non_isoc_methods;
+struct usb_pipe_methods dwc_otg_device_isoc_fs_methods;
+
+static dwc_otg_cmd_t dwc_otg_setup_rx;
+static dwc_otg_cmd_t dwc_otg_data_rx;
+static dwc_otg_cmd_t dwc_otg_data_tx;
+static dwc_otg_cmd_t dwc_otg_data_tx_sync;
+static void dwc_otg_device_done(struct usb_xfer *, usb_error_t);
+static void dwc_otg_do_poll(struct usb_bus *);
+static void dwc_otg_standard_done(struct usb_xfer *);
+static void dwc_otg_root_intr(struct dwc_otg_softc *sc);
+
+/*
+ * Here is a configuration that the chip supports.
+ */
+static const struct usb_hw_ep_profile dwc_otg_ep_profile[1] = {
+
+	[0] = {
+		.max_in_frame_size = 64,/* fixed */
+		.max_out_frame_size = 64,	/* fixed */
+		.is_simplex = 1,
+		.support_control = 1,
+	}
+};
+
+static void
+dwc_otg_get_hw_ep_profile(struct usb_device *udev,
+    const struct usb_hw_ep_profile **ppf, uint8_t ep_addr)
+{
+	struct dwc_otg_softc *sc;
+
+	sc = DWC_OTG_BUS2SC(udev->bus);
+
+	if (ep_addr < sc->sc_dev_ep_max)
+		*ppf = &sc->sc_hw_ep_profile[ep_addr].usb;
+	else
+		*ppf = NULL;
+}
+
+static int
+dwc_otg_init_fifo(struct dwc_otg_softc *sc)
+{
+	struct dwc_otg_profile *pf;
+	uint32_t fifo_size;
+	uint32_t fifo_regs;
+	uint32_t tx_start;
+	uint8_t x;
+
+	fifo_size = sc->sc_fifo_size;
+
+	fifo_regs = 4 * (sc->sc_dev_ep_max + sc->sc_dev_in_ep_max);
+
+	if (fifo_size >= fifo_regs)
+		fifo_size -= fifo_regs;
+	else
+		fifo_size = 0;
+
+	/* split equally for IN and OUT */
+	fifo_size /= 2;
+
+	DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GRXFSIZ, fifo_size / 4);
+
+	/* align to 4-bytes */
+	fifo_size &= ~3;
+
+	tx_start = fifo_size;
+
+	if (fifo_size < 0x40) {
+		DPRINTFN(-1, "Not enough data space for EP0 FIFO.\n");
+		USB_BUS_UNLOCK(&sc->sc_bus);
+		return (EINVAL);
+	}
+
+	DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GNPTXFSIZ, (0x10 << 16) | (tx_start / 4));
+	fifo_size -= 0x40;
+	tx_start += 0x40;
+
+	/* setup control endpoint profile */
+	sc->sc_hw_ep_profile[0].usb = dwc_otg_ep_profile[0];
+
+	for (x = 1; x != sc->sc_dev_ep_max; x++) {
+
+		pf = sc->sc_hw_ep_profile + x;
+
+		pf->usb.max_out_frame_size = 1024 * 3;
+		pf->usb.is_simplex = 0;	/* assume duplex */
+		pf->usb.support_bulk = 1;
+		pf->usb.support_interrupt = 1;
+		pf->usb.support_isochronous = 1;
+		pf->usb.support_out = 1;
+
+		if (x < sc->sc_dev_in_ep_max) {
+			uint32_t limit;
+
+			limit = (x == 1) ? DWC_OTG_MAX_TXN :
+			    (DWC_OTG_MAX_TXN / 2);
+
+			if (fifo_size >= limit) {
+				DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPTXF(x),
+				    ((limit / 4) << 16) |
+				    (tx_start / 4));
+				tx_start += limit;
+				fifo_size -= limit;
+				pf->usb.max_in_frame_size = 0x200;
+				pf->usb.support_in = 1;
+				pf->max_buffer = limit;
+
+			} else if (fifo_size >= 0x80) {
+				DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPTXF(x),
+				    ((0x80 / 4) << 16) | (tx_start / 4));
+				tx_start += 0x80;
+				fifo_size -= 0x80;
+				pf->usb.max_in_frame_size = 0x40;
+				pf->usb.support_in = 1;
+
+			} else {
+				pf->usb.is_simplex = 1;
+				DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPTXF(x),
+				    (0x0 << 16) | (tx_start / 4));
+			}
+		} else {
+			pf->usb.is_simplex = 1;
+		}
+
+		DPRINTF("FIFO%d = IN:%d / OUT:%d\n", x,
+		    pf->usb.max_in_frame_size,
+		    pf->usb.max_out_frame_size);
+	}
+
+	/* reset RX FIFO */
+	DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GRSTCTL,
+	    DWC_OTG_MSK_GRSTCTL_RXFFLUSH);
+
+	/* reset all TX FIFOs */
+	DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GRSTCTL,
+	   DWC_OTG_MSK_GRSTCTL_TXFIFO(0x10) |
+	   DWC_OTG_MSK_GRSTCTL_TXFFLUSH);
+
+	return (0);
+}
+
+static void
+dwc_otg_clocks_on(struct dwc_otg_softc *sc)
+{
+	if (sc->sc_flags.clocks_off &&
+	    sc->sc_flags.port_powered) {
+
+		DPRINTFN(5, "\n");
+
+		/* TODO - platform specific */
+
+		sc->sc_flags.clocks_off = 0;
+	}
+}
+
+static void
+dwc_otg_clocks_off(struct dwc_otg_softc *sc)
+{
+	if (!sc->sc_flags.clocks_off) {
+
+		DPRINTFN(5, "\n");
+
+		/* TODO - platform specific */
+
+		sc->sc_flags.clocks_off = 1;
+	}
+}
+
+static void
+dwc_otg_pull_up(struct dwc_otg_softc *sc)
+{
+	uint32_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;
+
+		temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_DCTL);
+		temp &= ~DWC_OTG_MSK_DCTL_SOFT_DISC;
+		DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DCTL, temp);
+	}
+}
+
+static void
+dwc_otg_pull_down(struct dwc_otg_softc *sc)
+{
+	uint32_t temp;
+
+	/* pulldown D+, if possible */
+
+	if (sc->sc_flags.d_pulled_up) {
+		sc->sc_flags.d_pulled_up = 0;
+
+		temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_DCTL);
+		temp |= DWC_OTG_MSK_DCTL_SOFT_DISC;
+		DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DCTL, temp);
+	}
+}
+
+static void
+dwc_otg_resume_irq(struct dwc_otg_softc *sc)
+{
+	if (sc->sc_flags.status_suspend) {
+		/* update status bits */
+		sc->sc_flags.status_suspend = 0;
+		sc->sc_flags.change_suspend = 1;
+
+		/*
+		 * Disable resume interrupt and enable suspend
+		 * interrupt:
+		 */
+		sc->sc_irq_mask &= ~DWC_OTG_MSK_GINT_WKUPINT;
+		sc->sc_irq_mask |= DWC_OTG_MSK_GINT_USB_SUSPEND;
+		DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GINTMSK, sc->sc_irq_mask);
+
+		/* complete root HUB interrupt endpoint */
+		dwc_otg_root_intr(sc);
+	}
+}
+
+static void
+dwc_otg_wakeup_peer(struct dwc_otg_softc *sc)
+{
+	uint32_t temp;
+
+	if (!sc->sc_flags.status_suspend)
+		return;
+
+	DPRINTFN(5, "Remote wakeup\n");
+
+	/* enable remote wakeup signalling */
+	temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_DCTL);
+	temp |= DWC_OTG_MSK_DCTL_REMOTE_WAKEUP;
+	DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DCTL, temp);
+
+	/* Wait 8ms for remote wakeup to complete. */
+	usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125);
+
+	temp &= ~DWC_OTG_MSK_DCTL_REMOTE_WAKEUP;
+	DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DCTL, temp);
+
+	/* need to fake resume IRQ */
+	dwc_otg_resume_irq(sc);
+}
+
+static void
+dwc_otg_set_address(struct dwc_otg_softc *sc, uint8_t addr)
+{
+	uint32_t temp;
+
+	DPRINTFN(5, "addr=%d\n", addr);
+
+	temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_DCFG);
+	temp &= ~DWC_OTG_MSK_DCFG_SET_DEV_ADDR(0x7F);
+	temp |= DWC_OTG_MSK_DCFG_SET_DEV_ADDR(addr);
+	DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DCFG, temp);
+}
+
+static void
+dwc_otg_common_rx_ack(struct dwc_otg_softc *sc)
+{
+	DPRINTFN(5, "RX status clear\n");
+
+	/* enable RX FIFO level interrupt */
+	sc->sc_irq_mask |= DWC_OTG_MSK_GINT_RXFLVL;
+	DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GINTMSK, sc->sc_irq_mask);
+
+	/* clear cached status */
+	sc->sc_last_rx_status = 0;
+}
+
+static uint8_t
+dwc_otg_setup_rx(struct dwc_otg_td *td)
+{
+	struct dwc_otg_softc *sc;
+	struct usb_device_request req __aligned(4);
+	uint32_t temp;
+	uint16_t count;
+
+	/* get pointer to softc */
+	sc = DWC_OTG_PC2SC(td->pc);
+
+	/* check endpoint status */
+
+	if (sc->sc_last_rx_status == 0)
+		goto not_complete;
+
+	if (DWC_OTG_MSK_GRXSTS_GET_CHANNEL(sc->sc_last_rx_status) != 0)
+		goto not_complete;
+
+	if ((sc->sc_last_rx_status & DWC_OTG_MSK_GRXSTS_PID) !=
+	    DWC_OTG_MSK_GRXSTS_PID_DATA0) {
+		/* release FIFO */
+		dwc_otg_common_rx_ack(sc);
+		goto not_complete;
+	}
+
+	if ((sc->sc_last_rx_status & DWC_OTG_MSK_GRXSTS_PACKET_STS) !=
+	    DWC_OTG_MSK_GRXSTS_DEV_STP_DATA) {
+		/* release FIFO */
+		dwc_otg_common_rx_ack(sc);
+		goto not_complete;
+	}
+
+	DPRINTFN(5, "GRXSTSR=0x%08x\n", sc->sc_last_rx_status);
+
+	/* clear did stall */
+	td->did_stall = 0;
+
+	/* get the packet byte count */
+	count = DWC_OTG_MSK_GRXSTS_GET_BYTE_CNT(sc->sc_last_rx_status);
+
+	/* verify data length */
+	if (count != td->remainder) {
+		DPRINTFN(0, "Invalid SETUP packet "
+		    "length, %d bytes\n", count);
+		/* release FIFO */
+		dwc_otg_common_rx_ack(sc);
+		goto not_complete;
+	}
+	if (count != sizeof(req)) {
+		DPRINTFN(0, "Unsupported SETUP packet "
+		    "length, %d bytes\n", count);
+		/* release FIFO */
+		dwc_otg_common_rx_ack(sc);
+		goto not_complete;
+	}
+
+	/* copy in control request */
+	memcpy(&req, sc->sc_rx_bounce_buffer, 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)) {
+		/* must write address before ZLP */
+		dwc_otg_set_address(sc, req.wValue[0] & 0x7F);
+	}
+
+	/* don't send any data by default */
+	DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPTSIZ(0),
+	    DWC_OTG_MSK_DXEPTSIZ_SET_NPKT(0) | 
+	    DWC_OTG_MSK_DXEPTSIZ_SET_NBYTES(0));
+
+	temp = sc->sc_in_ctl[0];
+
+	/* enable IN endpoint */
+	DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPCTL(0),
+	    temp | DWC_OTG_MSK_DIEPCTL_ENABLE);
+	DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPCTL(0),
+	    temp | DWC_OTG_MSK_DIEPCTL_SET_NAK);
+
+	/* reset IN endpoint buffer */
+	DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GRSTCTL,
+	    DWC_OTG_MSK_GRSTCTL_TXFIFO(0) |
+	    DWC_OTG_MSK_GRSTCTL_TXFFLUSH);
+
+	/* acknowledge RX status */
+	dwc_otg_common_rx_ack(sc);
+	return (0);			/* complete */
+
+not_complete:
+	/* abort any ongoing transfer, before enabling again */
+
+	temp = sc->sc_out_ctl[0];
+
+	temp |= DWC_OTG_MSK_DOEPCTL_ENABLE |
+	    DWC_OTG_MSK_DOEPCTL_SET_NAK;
+
+	/* enable OUT endpoint */
+	DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DOEPCTL(0), temp);
+
+	if (!td->did_stall) {
+		td->did_stall = 1;
+
+		DPRINTFN(5, "stalling IN and OUT direction\n");
+
+		/* set stall after enabling endpoint */
+		DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DOEPCTL(0),
+		    temp | DWC_OTG_MSK_DOEPCTL_STALL);
+
+		temp = sc->sc_in_ctl[0];
+
+		/* set stall assuming endpoint is enabled */
+		DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPCTL(0),
+		    temp | DWC_OTG_MSK_DIEPCTL_STALL);
+	}
+
+	/* setup number of buffers to receive */
+	DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DOEPTSIZ(0),
+	    DWC_OTG_MSK_DXEPTSIZ_SET_MULTI(3) |
+	    DWC_OTG_MSK_DXEPTSIZ_SET_NPKT(1) | 
+	    DWC_OTG_MSK_DXEPTSIZ_SET_NBYTES(sizeof(req)));
+
+	return (1);			/* not complete */
+}
+
+static uint8_t
+dwc_otg_data_rx(struct dwc_otg_td *td)
+{
+	struct dwc_otg_softc *sc;
+	uint32_t temp;
+	uint16_t count;
+	uint8_t got_short;
+
+	got_short = 0;
+
+	/* get pointer to softc */
+	sc = DWC_OTG_PC2SC(td->pc);
+
+	/* check endpoint status */
+	if (sc->sc_last_rx_status == 0)
+		goto not_complete;
+
+	if (DWC_OTG_MSK_GRXSTS_GET_CHANNEL(sc->sc_last_rx_status) != td->ep_no)
+		goto not_complete;
+
+	/* check for SETUP packet */
+	if ((sc->sc_last_rx_status & DWC_OTG_MSK_GRXSTS_PACKET_STS) ==
+	    DWC_OTG_MSK_GRXSTS_DEV_STP_DATA) {
+		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 */
+	}
+
+	if ((sc->sc_last_rx_status & DWC_OTG_MSK_GRXSTS_PACKET_STS) !=
+	    DWC_OTG_MSK_GRXSTS_DEV_OUT_DATA) {
+		/* release FIFO */
+		dwc_otg_common_rx_ack(sc);
+		goto not_complete;
+	}
+
+	/* get the packet byte count */
+	count = DWC_OTG_MSK_GRXSTS_GET_BYTE_CNT(sc->sc_last_rx_status);
+
+	/* 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;
+
+			/* release FIFO */
+			dwc_otg_common_rx_ack(sc);
+			return (0);	/* we are complete */
+		}
+	}
+	/* verify the packet byte count */
+	if (count > td->remainder) {
+		/* invalid USB packet */
+		td->error = 1;
+
+		/* release FIFO */
+		dwc_otg_common_rx_ack(sc);
+		return (0);		/* we are complete */
+	}
+
+	usbd_copy_in(td->pc, td->offset, sc->sc_rx_bounce_buffer, count);
+	td->remainder -= count;
+	td->offset += count;
+
+	/* release FIFO */
+	dwc_otg_common_rx_ack(sc);
+
+	/* 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 */
+	}
+
+not_complete:
+
+	temp = sc->sc_out_ctl[td->ep_no];
+
+	temp |= DWC_OTG_MSK_DOEPCTL_ENABLE |
+	    DWC_OTG_MSK_DOEPCTL_CLR_NAK;
+
+	DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DOEPCTL(td->ep_no), temp);
+
+	/* enable SETUP and transfer complete interrupt */
+	if (td->ep_no == 0) {
+		DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DOEPTSIZ(0),
+		    DWC_OTG_MSK_DXEPTSIZ_SET_NPKT(1) | 
+		    DWC_OTG_MSK_DXEPTSIZ_SET_NBYTES(td->max_packet_size));
+	} else {
+		/* allow reception of multiple packets */
+		DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DOEPTSIZ(td->ep_no),
+		    DWC_OTG_MSK_DXEPTSIZ_SET_MULTI(1) |
+		    DWC_OTG_MSK_DXEPTSIZ_SET_NPKT(4) | 
+		    DWC_OTG_MSK_DXEPTSIZ_SET_NBYTES(4 *
+		    ((td->max_packet_size + 3) & ~3)));
+	}
+	return (1);			/* not complete */
+}
+
+static uint8_t
+dwc_otg_data_tx(struct dwc_otg_td *td)
+{
+	struct dwc_otg_softc *sc;
+	uint32_t max_buffer;
+	uint32_t count;
+	uint32_t fifo_left;
+	uint32_t mpkt;
+	uint32_t temp;
+	uint8_t to;
+
+	to = 3;				/* don't loop forever! */
+
+	/* get pointer to softc */
+	sc = DWC_OTG_PC2SC(td->pc);
+
+	max_buffer = sc->sc_hw_ep_profile[td->ep_no].max_buffer;
+
+repeat:
+	/* check for for endpoint 0 data */
+
+	temp = sc->sc_last_rx_status;
+
+	if ((td->ep_no == 0) && (temp != 0) &&
+	    (DWC_OTG_MSK_GRXSTS_GET_CHANNEL(temp) == 0)) {
+
+		if ((temp & DWC_OTG_MSK_GRXSTS_PACKET_STS) !=
+		    DWC_OTG_MSK_GRXSTS_DEV_STP_DATA) {
+
+			/* dump data - wrong direction */
+			dwc_otg_common_rx_ack(sc);
+		} else {
+			/*
+			 * The current transfer was cancelled
+			 * by the USB Host:
+			 */
+			td->error = 1;
+			return (0);		/* complete */
+		}
+	}
+
+	/* fill in more TX data, if possible */
+	if (td->tx_bytes != 0) {
+
+		uint16_t cpkt;
+
+		/* check if packets have been transferred */
+		temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_DIEPTSIZ(td->ep_no));
+
+		/* get current packet number */
+		cpkt = DWC_OTG_MSK_DXEPTSIZ_GET_NPKT(temp);
+
+		if (cpkt >= td->npkt) {
+			fifo_left = 0;
+		} else {
+			if (max_buffer != 0) {
+				fifo_left = (td->npkt - cpkt) *
+				    td->max_packet_size;
+
+				if (fifo_left > max_buffer)
+					fifo_left = max_buffer;
+			} else {
+				fifo_left = td->max_packet_size;
+			}
+		}
+
+		count = td->tx_bytes;
+		if (count > fifo_left)
+			count = fifo_left;
+
+		if (count != 0) {
+
+			/* clear topmost word before copy */
+			sc->sc_tx_bounce_buffer[(count - 1) / 4] = 0;
+
+			/* copy out data */
+			usbd_copy_out(td->pc, td->offset,
+			    sc->sc_tx_bounce_buffer, count);
+
+			/* transfer data into FIFO */
+			bus_space_write_region_4(sc->sc_io_tag, sc->sc_io_hdl,
+			    DWC_OTG_REG_DFIFO(td->ep_no),
+			    sc->sc_tx_bounce_buffer, (count + 3) / 4);
+
+			td->tx_bytes -= count;
+			td->remainder -= count;
+			td->offset += count;
+			td->npkt = cpkt;
+		}
+		if (td->tx_bytes != 0)
+			goto not_complete;
+
+		/* check remainder */
+		if (td->remainder == 0) {
+			if (td->short_pkt)
+				return (0);	/* complete */
+
+			/* else we need to transmit a short packet */
+		}
+	}
+
+	/* check if no packets have been transferred */
+	temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_DIEPTSIZ(td->ep_no));
+
+	if (DWC_OTG_MSK_DXEPTSIZ_GET_NPKT(temp) != 0) {
+
+		DPRINTFN(5, "busy ep=%d npkt=%d DIEPTSIZ=0x%08x "
+		    "DIEPCTL=0x%08x\n", td->ep_no,
+		    DWC_OTG_MSK_DXEPTSIZ_GET_NPKT(temp),
+		    temp, DWC_OTG_READ_4(sc, DWC_OTG_REG_DIEPCTL(td->ep_no)));
+
+		goto not_complete;
+	}
+
+	DPRINTFN(5, "rem=%u ep=%d\n", td->remainder, td->ep_no);
+
+	/* try to optimise by sending more data */
+	if ((max_buffer != 0) && ((td->max_packet_size & 3) == 0)) {
+
+		/* send multiple packets at the same time */
+		mpkt = max_buffer / td->max_packet_size;
+
+		if (mpkt > 0x3FE)
+			mpkt = 0x3FE;
+
+		count = td->remainder;
+		if (count > 0x7FFFFF)
+			count = 0x7FFFFF - (0x7FFFFF % td->max_packet_size);
+
+		td->npkt = count / td->max_packet_size;
+
+		/*
+		 * NOTE: We could use 0x3FE instead of "mpkt" in the
+		 * check below to get more throughput, but then we
+		 * have a dependency towards non-generic chip features
+		 * to disable the TX-FIFO-EMPTY interrupts on a per
+		 * endpoint basis. Increase the maximum buffer size of
+		 * the IN endpoint to increase the performance.
+		 */
+		if (td->npkt > mpkt) {
+			td->npkt = mpkt;
+			count = td->max_packet_size * mpkt;
+		} else if ((count == 0) || (count % td->max_packet_size)) {
+			/* we are transmitting a short packet */
+			td->npkt++;
+			td->short_pkt = 1;
+		}
+	} else {
+		/* send one packet at a time */
+		mpkt = 1;
+		count = td->max_packet_size;
+		if (td->remainder < count) {
+			/* we have a short packet */
+			td->short_pkt = 1;
+			count = td->remainder;
+		}
+		td->npkt = 1;
+	}
+	DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPTSIZ(td->ep_no),
+	    DWC_OTG_MSK_DXEPTSIZ_SET_MULTI(1) |
+	    DWC_OTG_MSK_DXEPTSIZ_SET_NPKT(td->npkt) | 
+	    DWC_OTG_MSK_DXEPTSIZ_SET_NBYTES(count));
+
+	/* make room for buffering */
+	td->npkt += mpkt;
+
+	temp = sc->sc_in_ctl[td->ep_no];
+
+	/* must enable before writing data to FIFO */
+	DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPCTL(td->ep_no), temp |
+	    DWC_OTG_MSK_DIEPCTL_ENABLE |
+	    DWC_OTG_MSK_DIEPCTL_CLR_NAK);
+
+	td->tx_bytes = count;
+
+	/* check remainder */
+	if (td->tx_bytes == 0 &&
+	    td->remainder == 0) {
+		if (td->short_pkt)
+			return (0);	/* complete */
+
+		/* else we need to transmit a short packet */
+	}
+
+	if (--to)
+		goto repeat;
+
+not_complete:
+	return (1);			/* not complete */
+}
+
+static uint8_t
+dwc_otg_data_tx_sync(struct dwc_otg_td *td)
+{
+	struct dwc_otg_softc *sc;
+	uint32_t temp;
+
+	/* get pointer to softc */
+	sc = DWC_OTG_PC2SC(td->pc);
+
+	/*
+	 * If all packets are transferred we are complete:
+	 */
+	temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_DIEPTSIZ(td->ep_no));
+
+	/* check that all packets have been transferred */
+	if (DWC_OTG_MSK_DXEPTSIZ_GET_NPKT(temp) != 0) {
+		DPRINTFN(5, "busy ep=%d\n", td->ep_no);
+		goto not_complete;
+	}
+	return (0);
+
+not_complete:
+
+	/* we only want to know if there is a SETUP packet or free IN packet */
+
+	temp = sc->sc_last_rx_status;
+
+	if ((td->ep_no == 0) && (temp != 0) &&
+	    (DWC_OTG_MSK_GRXSTS_GET_CHANNEL(temp) == 0)) {
+
+		if ((temp & DWC_OTG_MSK_GRXSTS_PACKET_STS) ==
+		    DWC_OTG_MSK_GRXSTS_DEV_STP_DATA) {
+			DPRINTFN(5, "faking complete\n");
+			/*
+			 * Race condition: We are complete!
+			 */
+			return (0);
+		} else {
+			/* dump data - wrong direction */
+			dwc_otg_common_rx_ack(sc);
+		}
+	}
+	return (1);			/* not complete */
+}
+
+static uint8_t
+dwc_otg_xfer_do_fifo(struct usb_xfer *xfer)
+{
+	struct dwc_otg_td *td;
+
+	DPRINTFN(9, "\n");
+
+	td = xfer->td_transfer_cache;
+	while (1) {
+		if ((td->func) (td)) {
+			/* operation in progress */
+			break;
+		}
+		if (((void *)td) == xfer->td_transfer_last) {
+			goto done;
+		}
+		if (td->error) {
+			goto done;
+		} else if (td->remainder > 0) {
+			/*
+			 * We had a short transfer. If there is no alternate
+			 * next, stop processing !
+			 */
+			if (!td->alt_next)
+				goto done;
+		}
+
+		/*
+		 * Fetch the next transfer descriptor and transfer
+		 * some flags to the next transfer descriptor
+		 */
+		td = td->obj_next;
+		xfer->td_transfer_cache = td;
+	}
+	return (1);			/* not complete */
+
+done:
+	/* compute all actual lengths */
+
+	dwc_otg_standard_done(xfer);
+	return (0);			/* complete */
+}
+
+static void
+dwc_otg_interrupt_poll(struct dwc_otg_softc *sc)
+{
+	struct usb_xfer *xfer;
+	uint32_t temp;
+	uint8_t got_rx_status;
+
+repeat:
+	if (sc->sc_last_rx_status == 0) {
+
+		temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_GINTSTS);
+		if (temp & DWC_OTG_MSK_GINT_RXFLVL) {
+			/* pop current status */
+			sc->sc_last_rx_status =
+			    DWC_OTG_READ_4(sc, DWC_OTG_REG_GRXSTSP);
+		}
+
+		if (sc->sc_last_rx_status != 0) {
+
+			uint32_t temp;
+			uint8_t ep_no;
+
+			temp = DWC_OTG_MSK_GRXSTS_GET_BYTE_CNT(
+			    sc->sc_last_rx_status);
+			ep_no = DWC_OTG_MSK_GRXSTS_GET_CHANNEL(
+			    sc->sc_last_rx_status);
+
+			/* receive data, if any */
+			if (temp != 0) {
+				DPRINTF("Reading %d bytes from ep %d\n", temp, ep_no);
+				bus_space_read_region_4(sc->sc_io_tag, sc->sc_io_hdl,
+				    DWC_OTG_REG_DFIFO(ep_no),
+				    sc->sc_rx_bounce_buffer, (temp + 3) / 4);
+			}
+
+			temp = sc->sc_last_rx_status &
+			    DWC_OTG_MSK_GRXSTS_PACKET_STS;
+
+			/* non-data messages we simply skip */
+			if (temp != DWC_OTG_MSK_GRXSTS_DEV_STP_DATA &&
+			    temp != DWC_OTG_MSK_GRXSTS_DEV_OUT_DATA) {
+				dwc_otg_common_rx_ack(sc);
+				goto repeat;
+			}
+
+			/* check if we should dump the data */
+			if (!(sc->sc_active_out_ep & (1U << ep_no))) {
+				dwc_otg_common_rx_ack(sc);
+				goto repeat;
+			}
+
+			got_rx_status = 1;
+
+			DPRINTFN(5, "RX status = 0x%08x: ch=%d pid=%d bytes=%d sts=%d\n",
+			    sc->sc_last_rx_status, ep_no,
+			    (sc->sc_last_rx_status >> 15) & 3,
+			    DWC_OTG_MSK_GRXSTS_GET_BYTE_CNT(sc->sc_last_rx_status),
+			    (sc->sc_last_rx_status >> 17) & 15);
+		} else {
+			got_rx_status = 0;
+		}
+	} else {
+		uint8_t ep_no;
+
+		ep_no = DWC_OTG_MSK_GRXSTS_GET_CHANNEL(
+		    sc->sc_last_rx_status);
+
+		/* check if we should dump the data */
+		if (!(sc->sc_active_out_ep & (1U << ep_no))) {
+			dwc_otg_common_rx_ack(sc);
+			goto repeat;
+		}
+
+		got_rx_status = 1;
+	}
+
+	TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+		if (!dwc_otg_xfer_do_fifo(xfer)) {
+			/* queue has been modified */
+			goto repeat;
+		}
+	}
+
+	if (got_rx_status) {
+		if (sc->sc_last_rx_status == 0)
+			goto repeat;
+

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***


More information about the svn-src-all mailing list