[Zrouter-src-freebsd] ZRouter.org: push to FreeBSD HEAD tree

zrouter-src-freebsd at zrouter.org zrouter-src-freebsd at zrouter.org
Tue Oct 16 10:36:42 UTC 2012


details:   http://zrouter.org/hg/FreeBSD/head//rev/cdfc72b65a60
changeset: 537:cdfc72b65a60
user:      Aleksandr Rybalko <ray at ddteam.net>
date:      Tue Sep 25 11:48:54 2012 +0300
description:
Update to FreeBSD-HEAD r240914.

diffstat:

 head/sys/dev/usb/controller/dwc_otg.c |  1919 +++++++++++++++++++++++++++++---
 1 files changed, 1696 insertions(+), 223 deletions(-)

diffs (2596 lines):

diff -r 5fa13a913a6d -r cdfc72b65a60 head/sys/dev/usb/controller/dwc_otg.c
--- a/head/sys/dev/usb/controller/dwc_otg.c	Sun Sep 16 23:02:39 2012 +0300
+++ b/head/sys/dev/usb/controller/dwc_otg.c	Tue Sep 25 11:48:54 2012 +0300
@@ -1,5 +1,6 @@
 /*-
  * Copyright (c) 2012 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 2010-2011 Aleksandr Rybalko. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -25,8 +26,7 @@
 
 /*
  * 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.
+ * Controller.
  */
 
 /*
@@ -42,7 +42,7 @@
  */
 
 #include <sys/cdefs.h>
-__FBSDID("$FreeBSD: head/sys/dev/usb/controller/dwc_otg.c 239909 2012-08-30 16:19:05Z hselasky $");
+__FBSDID("$FreeBSD: head/sys/dev/usb/controller/dwc_otg.c 240890 2012-09-24 16:34:13Z hselasky $");
 
 #include <sys/stdint.h>
 #include <sys/stddef.h>
@@ -91,19 +91,28 @@
    DWC_OTG_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus)
 
 #define	DWC_OTG_MSK_GINT_ENABLED	\
-   (GINTSTS_ENUMDONE |	\
-   GINTSTS_USBRST |		\
-   GINTSTS_USBSUSP |	\
-   GINTSTS_IEPINT |		\
-   GINTSTS_RXFLVL |		\
-   GINTSTS_SESSREQINT)
-
-#define DWC_OTG_USE_HSIC 0
+   (GINTSTS_ENUMDONE |			\
+   GINTSTS_USBRST |			\
+   GINTSTS_USBSUSP |			\
+   GINTSTS_IEPINT |			\
+   GINTSTS_RXFLVL |			\
+   GINTSTS_SESSREQINT |			\
+   GINTMSK_OTGINTMSK |			\
+   GINTMSK_HCHINTMSK |			\
+   GINTSTS_PRTINT)
+
+static int dwc_otg_use_hsic;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, dwc_otg, CTLFLAG_RW, 0, "USB DWC OTG");
+
+SYSCTL_INT(_hw_usb_dwc_otg, OID_AUTO, use_hsic, CTLFLAG_RD,
+    &dwc_otg_use_hsic, 0, "DWC OTG uses HSIC interface");
+
+TUNABLE_INT("hw.usb.dwc_otg.use_hsic", &dwc_otg_use_hsic);
 
 #ifdef USB_DEBUG
-static int dwc_otg_debug = 0;
-
-static SYSCTL_NODE(_hw_usb, OID_AUTO, dwc_otg, CTLFLAG_RW, 0, "USB DWC OTG");
+static int dwc_otg_debug;
+
 SYSCTL_INT(_hw_usb_dwc_otg, OID_AUTO, debug, CTLFLAG_RW,
     &dwc_otg_debug, 0, "DWC OTG debug level");
 #endif
@@ -114,16 +123,22 @@
 
 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;
+struct usb_pipe_methods dwc_otg_device_isoc_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 dwc_otg_cmd_t dwc_otg_host_setup_tx;
+static dwc_otg_cmd_t dwc_otg_host_data_tx;
+static dwc_otg_cmd_t dwc_otg_host_data_rx;
+
 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);
+static void dwc_otg_interrupt_poll(struct dwc_otg_softc *sc);
 
 /*
  * Here is a configuration that the chip supports.
@@ -153,7 +168,7 @@
 }
 
 static int
-dwc_otg_init_fifo(struct dwc_otg_softc *sc)
+dwc_otg_init_fifo(struct dwc_otg_softc *sc, uint8_t mode)
 {
 	struct dwc_otg_profile *pf;
 	uint32_t fifo_size;
@@ -186,14 +201,52 @@
 		return (EINVAL);
 	}
 
-	DWC_OTG_WRITE_4(sc, DOTG_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++) {
+	if (mode == DWC_MODE_HOST) {
+
+		/* reset active endpoints */
+		sc->sc_active_rx_ep = 0;
+
+		fifo_size /= 2;
+
+		DWC_OTG_WRITE_4(sc, DOTG_GNPTXFSIZ,
+		    ((fifo_size / 4) << 16) |
+		    (tx_start / 4));
+
+		tx_start += fifo_size;
+
+		DWC_OTG_WRITE_4(sc, DOTG_HPTXFSIZ,
+		    ((fifo_size / 4) << 16) |
+		    (tx_start / 4));
+
+		for (x = 0; x != sc->sc_host_ch_max; x++) {
+			/* enable interrupts */
+			DWC_OTG_WRITE_4(sc, DOTG_HCINTMSK(x),
+			    HCINT_STALL | HCINT_BBLERR |
+			    HCINT_XACTERR | HCINT_XFERCOMPL |
+			    HCINT_NAK | HCINT_ACK | HCINT_NYET |
+			    HCINT_CHHLTD | HCINT_FRMOVRUN |
+			    HCINT_DATATGLERR);
+		}
+
+		/* enable host channel interrupts */
+		DWC_OTG_WRITE_4(sc, DOTG_HAINTMSK,
+		    (1U << sc->sc_host_ch_max) - 1U);
+	}
+
+	if (mode == DWC_MODE_DEVICE) {
+
+	    DWC_OTG_WRITE_4(sc, DOTG_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];
+
+	    /* reset active endpoints */
+	    sc->sc_active_rx_ep = 1;
+
+	    for (x = 1; x != sc->sc_dev_ep_max; x++) {
 
 		pf = sc->sc_hw_ep_profile + x;
 
@@ -240,17 +293,22 @@
 		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, DOTG_GRSTCTL,
 	    GRSTCTL_RXFFLSH);
 
-	/* reset all TX FIFOs */
-	DWC_OTG_WRITE_4(sc, DOTG_GRSTCTL,
-	   GRSTCTL_TXFIFO(0x10) |
-	   GRSTCTL_TXFFLSH);
-
+	if (mode != DWC_MODE_OTG) {
+		/* reset all TX FIFOs */
+		DWC_OTG_WRITE_4(sc, DOTG_GRSTCTL,
+		    GRSTCTL_TXFIFO(0x10) |
+		    GRSTCTL_TXFFLSH);
+	} else {
+		/* reset active endpoints */
+		sc->sc_active_rx_ep = 0;
+	}
 	return (0);
 }
 
@@ -322,13 +380,15 @@
 		sc->sc_flags.status_suspend = 0;
 		sc->sc_flags.change_suspend = 1;
 
-		/*
-		 * Disable resume interrupt and enable suspend
-		 * interrupt:
-		 */
-		sc->sc_irq_mask &= ~GINTSTS_WKUPINT;
-		sc->sc_irq_mask |= GINTSTS_USBSUSP;
-		DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
+		if (sc->sc_flags.status_device_mode) {
+			/*
+			 * Disable resume interrupt and enable suspend
+			 * interrupt:
+			 */
+			sc->sc_irq_mask &= ~GINTSTS_WKUPINT;
+			sc->sc_irq_mask |= GINTSTS_USBSUSP;
+			DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
+		}
 
 		/* complete root HUB interrupt endpoint */
 		dwc_otg_root_intr(sc);
@@ -336,25 +396,70 @@
 }
 
 static void
+dwc_otg_suspend_irq(struct dwc_otg_softc *sc)
+{
+	if (!sc->sc_flags.status_suspend) {
+		/* update status bits */
+		sc->sc_flags.status_suspend = 1;
+		sc->sc_flags.change_suspend = 1;
+
+		if (sc->sc_flags.status_device_mode) {
+			/*
+			 * Disable suspend interrupt and enable resume
+			 * interrupt:
+			 */
+			sc->sc_irq_mask &= ~GINTSTS_USBSUSP;
+			sc->sc_irq_mask |= GINTSTS_WKUPINT;
+			DWC_OTG_WRITE_4(sc, DOTG_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, DOTG_DCTL);
-	temp |= DCTL_RMTWKUPSIG;
-	DWC_OTG_WRITE_4(sc, DOTG_DCTL, temp);
-
-	/* Wait 8ms for remote wakeup to complete. */
-	usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125);
-
-	temp &= ~DCTL_RMTWKUPSIG;
-	DWC_OTG_WRITE_4(sc, DOTG_DCTL, temp);
+	if (sc->sc_flags.status_device_mode) {
+		uint32_t temp;
+
+		/* enable remote wakeup signalling */
+		temp = DWC_OTG_READ_4(sc, DOTG_DCTL);
+		temp |= DCTL_RMTWKUPSIG;
+		DWC_OTG_WRITE_4(sc, DOTG_DCTL, temp);
+
+		/* Wait 8ms for remote wakeup to complete. */
+		usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125);
+
+		temp &= ~DCTL_RMTWKUPSIG;
+		DWC_OTG_WRITE_4(sc, DOTG_DCTL, temp);
+	} else {
+		/* enable USB port */
+		DWC_OTG_WRITE_4(sc, DOTG_PCGCCTL, 0);
+
+		/* wait 10ms */
+		usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100);
+
+		/* resume port */
+		sc->sc_hprt_val |= HPRT_PRTRES;
+		DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val);
+
+		/* Wait 100ms for resume signalling to complete. */
+		usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 10);
+
+		/* clear suspend and resume */
+		sc->sc_hprt_val &= ~(HPRT_PRTSUSP | HPRT_PRTRES);
+		DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val);
+
+		/* Wait 4ms */
+		usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 250);
+	}
 
 	/* need to fake resume IRQ */
 	dwc_otg_resume_irq(sc);
@@ -386,6 +491,362 @@
 	sc->sc_last_rx_status = 0;
 }
 
+static void
+dwc_otg_clear_hcint(struct dwc_otg_softc *sc, uint8_t x)
+{
+	uint32_t hcint;
+
+	hcint = DWC_OTG_READ_4(sc, DOTG_HCINT(x));
+	DWC_OTG_WRITE_4(sc, DOTG_HCINT(x), hcint);
+
+	/* clear buffered interrupts */
+	sc->sc_chan_state[x].hcint = 0;
+}
+
+static uint8_t
+dwc_otg_host_channel_wait(struct dwc_otg_td *td)
+{
+	struct dwc_otg_softc *sc;
+	uint8_t x;
+
+	x = td->channel;
+
+	DPRINTF("CH=%d\n", x);
+
+	/* get pointer to softc */
+	sc = DWC_OTG_PC2SC(td->pc);
+
+	if (sc->sc_chan_state[x].hcint & HCINT_HALTED_ONLY) {
+		dwc_otg_clear_hcint(sc, x);
+		return (1);
+	}
+
+	if (x == 0)
+		return (0);	/* wait */
+
+	/* find new disabled channel */
+	for (x = 1; x != sc->sc_host_ch_max; x++) {
+
+		uint32_t hcchar;
+
+		if (sc->sc_chan_state[x].allocated)
+			continue;
+
+		/* check if channel is enabled */
+		hcchar = DWC_OTG_READ_4(sc, DOTG_HCCHAR(x));
+		if (hcchar & (HCCHAR_CHENA | HCCHAR_CHDIS)) {
+			DPRINTF("CH=%d is BUSY\n", x);
+			continue;
+		}
+
+		sc->sc_chan_state[td->channel].allocated = 0;
+		sc->sc_chan_state[x].allocated = 1;
+
+		if (sc->sc_chan_state[td->channel].suspended) {
+			sc->sc_chan_state[td->channel].suspended = 0;
+			sc->sc_chan_state[x].suspended = 1;
+		}
+
+		/* clear interrupts */
+		dwc_otg_clear_hcint(sc, x);
+
+		DPRINTF("CH=%d HCCHAR=0x%08x(0x%08x) "
+		    "HCSPLT=0x%08x\n", x, td->hcchar,
+		    hcchar, td->hcsplt);
+
+		/* ack any pending messages */
+		if (sc->sc_last_rx_status != 0 &&
+		    GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status) == td->channel) {
+			/* get rid of message */
+			dwc_otg_common_rx_ack(sc);
+		}
+
+		/* move active channel */
+		sc->sc_active_rx_ep &= ~(1 << td->channel);
+		sc->sc_active_rx_ep |= (1 << x);
+
+		/* set channel */
+		td->channel = x;
+
+		return (1);	/* new channel allocated */
+	}
+	return (0);	/* wait */
+}
+
+static uint8_t
+dwc_otg_host_channel_alloc(struct dwc_otg_td *td)
+{
+	struct dwc_otg_softc *sc;
+	uint8_t x;
+	uint8_t max_channel;
+
+	if (td->channel < DWC_OTG_MAX_CHANNELS)
+		return (0);		/* already allocated */
+
+	/* get pointer to softc */
+	sc = DWC_OTG_PC2SC(td->pc);
+
+	if ((td->hcchar & HCCHAR_EPNUM_MASK) == 0) {
+		max_channel = 1;
+		x = 0;
+	} else {
+		max_channel = sc->sc_host_ch_max;
+		x = 1;
+	}
+
+	for (; x != max_channel; x++) {
+
+		uint32_t hcchar;
+
+		if (sc->sc_chan_state[x].allocated)
+			continue;
+	  
+		/* check if channel is enabled */
+		hcchar = DWC_OTG_READ_4(sc, DOTG_HCCHAR(x));
+		if (hcchar & (HCCHAR_CHENA | HCCHAR_CHDIS)) {
+			DPRINTF("CH=%d is BUSY\n", x);
+			continue;
+		}
+
+		sc->sc_chan_state[x].allocated = 1;
+
+		/* clear interrupts */
+		dwc_otg_clear_hcint(sc, x);
+
+		DPRINTF("CH=%d HCCHAR=0x%08x(0x%08x) "
+		    "HCSPLT=0x%08x\n", x, td->hcchar,
+		    hcchar, td->hcsplt);
+
+		/* set active channel */
+		sc->sc_active_rx_ep |= (1 << x);
+
+		/* set channel */
+		td->channel = x;
+
+		return (0);	/* allocated */
+	}
+	return (1);	/* busy */
+}
+
+static void
+dwc_otg_host_channel_disable(struct dwc_otg_softc *sc, uint8_t x)
+{
+	uint32_t hcchar;
+	hcchar = DWC_OTG_READ_4(sc, DOTG_HCCHAR(x));
+	if (hcchar & (HCCHAR_CHENA | HCCHAR_CHDIS))
+		DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(x), HCCHAR_CHENA | HCCHAR_CHDIS);
+}
+
+static void
+dwc_otg_host_channel_free(struct dwc_otg_td *td)
+{
+	struct dwc_otg_softc *sc;
+	uint8_t x;
+
+	if (td->channel >= DWC_OTG_MAX_CHANNELS)
+		return;		/* already freed */
+
+	/* free channel */
+	x = td->channel;
+	td->channel = DWC_OTG_MAX_CHANNELS;
+
+	DPRINTF("CH=%d\n", x);
+
+	/* get pointer to softc */
+	sc = DWC_OTG_PC2SC(td->pc);
+
+	dwc_otg_host_channel_disable(sc, x);
+
+	sc->sc_chan_state[x].allocated = 0;
+	sc->sc_chan_state[x].suspended = 0;
+
+	/* ack any pending messages */
+	if (sc->sc_last_rx_status != 0 &&
+	    GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status) == x) {
+		dwc_otg_common_rx_ack(sc);
+	}
+
+	/* clear active channel */
+	sc->sc_active_rx_ep &= ~(1 << x);
+}
+
+static uint8_t
+dwc_otg_host_setup_tx(struct dwc_otg_td *td)
+{
+	struct usb_device_request req __aligned(4);
+	struct dwc_otg_softc *sc;
+	uint32_t hcint;
+	uint32_t hcchar;
+
+	if (dwc_otg_host_channel_alloc(td))
+		return (1);		/* busy */
+
+	/* get pointer to softc */
+	sc = DWC_OTG_PC2SC(td->pc);
+
+	hcint = sc->sc_chan_state[td->channel].hcint;
+
+	DPRINTF("CH=%d ST=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n",
+	    td->channel, td->state, hcint,
+	    DWC_OTG_READ_4(sc, DOTG_HCCHAR(td->channel)),
+	    DWC_OTG_READ_4(sc, DOTG_HCTSIZ(td->channel)));
+
+	if (hcint & HCINT_STALL) {
+		DPRINTF("CH=%d STALL\n", td->channel);
+		td->error_stall = 1;
+		td->error_any = 1;
+		return (0);		/* complete */
+	}
+
+	if (hcint & HCINT_ERRORS) {
+		DPRINTF("CH=%d ERROR\n", td->channel);
+		td->errcnt++;
+		if (td->hcsplt != 0 || td->errcnt >= 3) {
+			td->error_any = 1;
+			return (0);		/* complete */
+		}
+	}
+
+	/* treat NYET like NAK, if SPLIT transactions are used */
+	if (hcint & HCINT_NYET) {
+		if (td->hcsplt != 0) {
+			DPRINTF("CH=%d NYET+SPLIT\n", td->channel);
+			hcint &= ~HCINT_NYET;
+			hcint |= HCINT_NAK;
+		}
+	}
+
+	/* channel must be disabled before we can complete the transfer */
+
+	if (hcint & (HCINT_ERRORS | HCINT_RETRY |
+	    HCINT_ACK | HCINT_NYET)) {
+		uint32_t hcchar;
+
+		dwc_otg_host_channel_disable(sc, td->channel);
+
+		hcchar = DWC_OTG_READ_4(sc, DOTG_HCCHAR(td->channel));
+
+		if (!(hcchar & HCCHAR_CHENA)) {
+			hcint |= HCINT_HALTED_ONLY;
+			sc->sc_chan_state[td->channel].hcint = hcint;
+		}
+		if (!(hcint & HCINT_ERRORS))
+			td->errcnt = 0;
+	}
+
+	switch (td->state) {
+	case DWC_CHAN_ST_START:
+		goto send_pkt;
+
+	case DWC_CHAN_ST_WAIT_ANE:
+		if (hcint & (HCINT_RETRY | HCINT_ERRORS)) {
+			if (!dwc_otg_host_channel_wait(td))
+				break;
+			td->did_nak = 1;
+			goto send_pkt;
+		}
+		if (hcint & (HCINT_ACK | HCINT_NYET)) {
+			if (!dwc_otg_host_channel_wait(td))
+				break;
+			td->offset += td->tx_bytes;
+			td->remainder -= td->tx_bytes;
+			td->toggle = 1;
+			return (0);	/* complete */
+		}
+		break;
+	case DWC_CHAN_ST_WAIT_S_ANE:
+		if (hcint & (HCINT_RETRY | HCINT_ERRORS)) {
+			if (!dwc_otg_host_channel_wait(td))
+				break;
+			td->did_nak = 1;
+			goto send_pkt;
+		}
+		if (hcint & (HCINT_ACK | HCINT_NYET)) {
+			if (!dwc_otg_host_channel_wait(td))
+				break;
+			goto send_cpkt;
+		}
+		break;
+	case DWC_CHAN_ST_WAIT_C_ANE:
+		if (hcint & (HCINT_RETRY | HCINT_ERRORS)) {
+			if (!dwc_otg_host_channel_wait(td))
+				break;
+			td->did_nak = 1;
+			goto send_cpkt;
+		}
+		if (hcint & (HCINT_ACK | HCINT_NYET)) {
+			if (!dwc_otg_host_channel_wait(td))
+				break;
+			td->offset += td->tx_bytes;
+			td->remainder -= td->tx_bytes;
+			td->toggle = 1;
+			return (0);	/* complete */
+		}
+		break;
+	default:
+		break;
+	}
+	return (1);		/* busy */
+
+send_pkt:
+	if (sizeof(req) != td->remainder) {
+		td->error_any = 1;
+		return (0);		/* complete */
+	}
+
+	if (td->hcsplt != 0) {
+		td->hcsplt &= ~HCSPLT_COMPSPLT;
+		td->state = DWC_CHAN_ST_WAIT_S_ANE;
+	} else {
+		td->state = DWC_CHAN_ST_WAIT_ANE;
+	}
+
+	usbd_copy_out(td->pc, 0, &req, sizeof(req));
+
+	DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel),
+	    (sizeof(req) << HCTSIZ_XFERSIZE_SHIFT) |
+	    (1 << HCTSIZ_PKTCNT_SHIFT) |
+	    (HCTSIZ_PID_SETUP << HCTSIZ_PID_SHIFT));
+
+	DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(td->channel), td->hcsplt);
+
+	hcchar = td->hcchar;
+	hcchar &= ~HCCHAR_EPDIR_IN;
+
+	/* must enable channel before writing data to FIFO */
+	DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel), hcchar);
+#define _DMA_	0
+	if (_DMA_) {
+	} else {
+		/* transfer data into FIFO */
+		bus_space_write_region_4(sc->sc_io_tag, sc->sc_io_hdl,
+		    DOTG_DFIFO(td->channel), (uint32_t *)&req,
+		    sizeof(req) / 4);
+	}
+
+	/* store number of bytes transmitted */
+	td->tx_bytes = sizeof(req);
+
+	return (1);	/* busy */
+
+send_cpkt:
+	td->hcsplt |= HCSPLT_COMPSPLT;
+	td->state = DWC_CHAN_ST_WAIT_C_ANE;
+
+	DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel),
+	    (HCTSIZ_PID_SETUP << HCTSIZ_PID_SHIFT));
+
+	DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(td->channel), td->hcsplt);
+
+	hcchar = td->hcchar;
+	hcchar &= ~HCCHAR_EPDIR_IN;
+
+	/* must enable channel before writing data to FIFO */
+	DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel), hcchar);
+
+	return (1);	/* busy */
+}
+
 static uint8_t
 dwc_otg_setup_rx(struct dwc_otg_td *td)
 {
@@ -518,6 +979,293 @@
 }
 
 static uint8_t
+dwc_otg_host_rate_check(struct dwc_otg_td *td,
+    uint8_t do_inc)
+{
+	struct dwc_otg_softc *sc;
+	uint8_t ep_type;
+
+	/* get pointer to softc */
+	sc = DWC_OTG_PC2SC(td->pc);
+
+	ep_type = ((td->hcchar &
+	    HCCHAR_EPTYPE_MASK) >> HCCHAR_EPTYPE_SHIFT);
+
+	if (sc->sc_chan_state[td->channel].suspended)
+		goto busy;
+
+	if (ep_type == UE_ISOCHRONOUS) {
+		if (td->tmr_val & 1)
+			td->hcchar |= HCCHAR_ODDFRM;
+		else
+			td->hcchar &= ~HCCHAR_ODDFRM;
+		if (do_inc)
+			td->tmr_val += td->tmr_res;
+	} else if (ep_type == UE_INTERRUPT) {
+		if ((sc->sc_tmr_val & 0xFF) != td->tmr_val)
+			goto busy;
+		if (do_inc)
+			td->tmr_val += td->tmr_res;
+	} else if (td->did_nak != 0) {
+		goto busy;
+	}
+
+	if (ep_type == UE_ISOCHRONOUS) {
+		td->toggle = 0;
+	} else if (td->set_toggle) {
+		td->set_toggle = 0;
+		td->toggle = 1;
+	}
+	return (0);
+busy:
+	return (1);
+}
+
+static uint8_t
+dwc_otg_host_data_rx(struct dwc_otg_td *td)
+{
+	struct dwc_otg_softc *sc;
+	uint32_t hcint;
+	uint32_t hcchar;
+	uint32_t count;
+
+	if (dwc_otg_host_channel_alloc(td))
+		return (1);		/* busy */
+
+	/* get pointer to softc */
+	sc = DWC_OTG_PC2SC(td->pc);
+
+	hcint = sc->sc_chan_state[td->channel].hcint;
+
+	DPRINTF("CH=%d ST=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n",
+	    td->channel, td->state, hcint,
+	    DWC_OTG_READ_4(sc, DOTG_HCCHAR(td->channel)),
+	    DWC_OTG_READ_4(sc, DOTG_HCTSIZ(td->channel)));
+
+	/* check interrupt bits */
+
+	if (hcint & HCINT_STALL) {
+		DPRINTF("CH=%d STALL\n", td->channel);
+		td->error_stall = 1;
+		td->error_any = 1;
+		return (0);		/* complete */
+	}
+
+	if (hcint & HCINT_ERRORS) {
+		DPRINTF("CH=%d ERROR\n", td->channel);
+		td->errcnt++;
+		if (td->hcsplt != 0 || td->errcnt >= 3) {
+			td->error_any = 1;
+			return (0);		/* complete */
+		}
+	}
+
+	/* treat NYET like NAK, if SPLIT transactions are used */
+	if (hcint & HCINT_NYET) {
+		if (td->hcsplt != 0) {
+			DPRINTF("CH=%d NYET+SPLIT\n", td->channel);
+			hcint &= ~HCINT_NYET;
+			hcint |= HCINT_NAK;
+		}
+	}
+
+	/* channel must be disabled before we can complete the transfer */
+
+	if (hcint & (HCINT_ERRORS | HCINT_RETRY |
+	    HCINT_ACK | HCINT_NYET)) {
+
+		dwc_otg_host_channel_disable(sc, td->channel);
+
+		hcchar = DWC_OTG_READ_4(sc, DOTG_HCCHAR(td->channel));
+
+		if (!(hcchar & HCCHAR_CHENA)) {
+			hcint |= HCINT_HALTED_ONLY;
+			sc->sc_chan_state[td->channel].hcint = hcint;
+		}
+		if (!(hcint & HCINT_ERRORS))
+			td->errcnt = 0;
+	}
+
+	/* check endpoint status */
+	if (sc->sc_last_rx_status == 0)
+		goto check_state;
+
+	if (GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status) != td->channel)
+		goto check_state;
+
+	switch (sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) {
+	case GRXSTSRH_IN_DATA:
+
+		DPRINTF("DATA\n");
+
+		td->toggle ^= 1;
+
+		/* get the packet byte count */
+		count = GRXSTSRD_BCNT_GET(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;
+				td->got_short = 1;
+			} else {
+				/* invalid USB packet */
+				td->error_any = 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_any = 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;
+		hcint |= HCINT_SOFTWARE_ONLY | HCINT_ACK;
+		sc->sc_chan_state[td->channel].hcint = hcint;
+		break;
+
+	default:
+		DPRINTF("OTHER\n");
+		break;
+	}
+	/* release FIFO */
+	dwc_otg_common_rx_ack(sc);
+
+check_state:
+	switch (td->state) {
+	case DWC_CHAN_ST_START:
+		if (td->hcsplt != 0)
+			goto receive_spkt;
+		else
+			goto receive_pkt;
+
+	case DWC_CHAN_ST_WAIT_ANE:
+		if (hcint & (HCINT_RETRY | HCINT_ERRORS)) {
+			if (!dwc_otg_host_channel_wait(td))
+				break;
+
+			if (td->hcsplt != 0)
+				goto receive_spkt;
+			else
+				goto receive_pkt;
+		}
+		if (!(hcint & HCINT_SOFTWARE_ONLY))
+			break;
+		if (hcint & (HCINT_ACK | HCINT_NYET)) {
+			if (!dwc_otg_host_channel_wait(td))
+				break;
+
+			/* check if we are complete */
+			if ((td->remainder == 0) || (td->got_short != 0)) {
+				if (td->short_pkt)
+					return (0);	/* complete */
+
+				/*
+				 * Else need to receive a zero length
+				 * packet.
+				 */
+			}
+			if (td->hcsplt != 0)
+				goto receive_spkt;
+			else
+				goto receive_pkt;
+		}
+		break;
+
+	case DWC_CHAN_ST_WAIT_S_ANE:
+		if (hcint & (HCINT_RETRY | HCINT_ERRORS)) {
+			if (!dwc_otg_host_channel_wait(td))
+				break;
+			goto receive_spkt;
+		}
+		if (hcint & (HCINT_ACK | HCINT_NYET)) {
+			if (!dwc_otg_host_channel_wait(td))
+				break;
+			goto receive_pkt;
+		}
+		break;
+
+	case DWC_CHAN_ST_RX_PKT:
+		goto receive_pkt;
+
+	case DWC_CHAN_ST_RX_SPKT:
+		goto receive_spkt;
+
+	default:
+		break;
+	}
+	goto busy;
+
+receive_pkt:
+	if (dwc_otg_host_rate_check(td, 1)) {
+		td->state = DWC_CHAN_ST_RX_PKT;
+		dwc_otg_host_channel_free(td);
+		goto busy;
+	}
+
+	if (td->hcsplt != 0)
+		td->hcsplt |= HCSPLT_COMPSPLT;
+	td->state = DWC_CHAN_ST_WAIT_ANE;
+
+	/* receive one packet */
+	DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel),
+	    (td->max_packet_size << HCTSIZ_XFERSIZE_SHIFT) |
+	    (1 << HCTSIZ_PKTCNT_SHIFT) |
+	    (td->toggle ? (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT) :
+	    (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT)));
+
+	DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(td->channel), td->hcsplt);
+
+	hcchar = td->hcchar;
+	hcchar |= HCCHAR_EPDIR_IN;
+
+	/* must enable channel before data can be received */
+	DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel), hcchar);
+
+	goto busy;
+
+receive_spkt:
+	if (dwc_otg_host_rate_check(td, 0)) {
+		td->state = DWC_CHAN_ST_RX_SPKT;
+		dwc_otg_host_channel_free(td);
+		goto busy;
+	}
+
+	td->hcsplt &= ~HCSPLT_COMPSPLT;
+	td->state = DWC_CHAN_ST_WAIT_S_ANE;
+
+	/* receive one packet */
+	DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel),
+	    (td->toggle ? (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT) :
+	    (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT)));
+
+	DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(td->channel), td->hcsplt);
+
+	hcchar = td->hcchar;
+	hcchar |= HCCHAR_EPDIR_IN;
+
+	/* must enable channel before data can be received */
+	DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel), hcchar);
+
+busy:
+	return (1);	/* busy */
+}
+
+static uint8_t
 dwc_otg_data_rx(struct dwc_otg_td *td)
 {
 	struct dwc_otg_softc *sc;
@@ -551,7 +1299,7 @@
 		/*
 		 * USB Host Aborted the transfer.
 		 */
-		td->error = 1;
+		td->error_any = 1;
 		return (0);		/* complete */
 	}
 
@@ -573,7 +1321,7 @@
 			got_short = 1;
 		} else {
 			/* invalid USB packet */
-			td->error = 1;
+			td->error_any = 1;
 
 			/* release FIFO */
 			dwc_otg_common_rx_ack(sc);
@@ -583,7 +1331,7 @@
 	/* verify the packet byte count */
 	if (count > td->remainder) {
 		/* invalid USB packet */
-		td->error = 1;
+		td->error_any = 1;
 
 		/* release FIFO */
 		dwc_otg_common_rx_ack(sc);
@@ -610,8 +1358,7 @@
 
 	temp = sc->sc_out_ctl[td->ep_no];
 
-	temp |= DOEPCTL_EPENA |
-	    DOEPCTL_CNAK;
+	temp |= DOEPCTL_EPENA | DOEPCTL_CNAK;
 
 	DWC_OTG_WRITE_4(sc, DOTG_DOEPCTL(td->ep_no), temp);
 
@@ -632,6 +1379,235 @@
 }
 
 static uint8_t
+dwc_otg_host_data_tx(struct dwc_otg_td *td)
+{
+	struct dwc_otg_softc *sc;
+	uint32_t count;
+	uint32_t hcint;
+	uint32_t hcchar;
+	uint8_t ep_type;
+
+	if (dwc_otg_host_channel_alloc(td))
+		return (1);		/* busy */
+
+	/* get pointer to softc */
+	sc = DWC_OTG_PC2SC(td->pc);
+
+	ep_type = ((td->hcchar &
+	    HCCHAR_EPTYPE_MASK) >> HCCHAR_EPTYPE_SHIFT);
+
+	hcint = sc->sc_chan_state[td->channel].hcint;
+
+	DPRINTF("CH=%d ST=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n",
+	    td->channel, td->state, hcint,
+	    DWC_OTG_READ_4(sc, DOTG_HCCHAR(td->channel)),
+	    DWC_OTG_READ_4(sc, DOTG_HCTSIZ(td->channel)));
+
+	if (hcint & HCINT_STALL) {
+		DPRINTF("CH=%d STALL\n", td->channel);
+		td->error_stall = 1;
+		td->error_any = 1;
+		return (0);		/* complete */
+	}
+
+	if (hcint & HCINT_ERRORS) {
+		DPRINTF("CH=%d ERROR\n", td->channel);
+		td->errcnt++;
+		if (td->hcsplt != 0 || td->errcnt >= 3) {
+			td->error_any = 1;
+			return (0);		/* complete */
+		}
+	}
+
+	/* treat NYET like NAK, if SPLIT transactions are used */
+	if (hcint & HCINT_NYET) {
+		if (td->hcsplt != 0) {
+			DPRINTF("CH=%d NYET+SPLIT\n", td->channel);
+			hcint &= ~HCINT_NYET;
+			hcint |= HCINT_NAK;
+		}
+	}
+
+	/* channel must be disabled before we can complete the transfer */
+
+	if (hcint & (HCINT_ERRORS | HCINT_RETRY |
+	    HCINT_ACK | HCINT_NYET)) {
+
+		dwc_otg_host_channel_disable(sc, td->channel);
+
+		hcchar = DWC_OTG_READ_4(sc, DOTG_HCCHAR(td->channel));
+
+		if (!(hcchar & HCCHAR_CHENA)) {
+			hcint |= HCINT_HALTED_ONLY;
+			sc->sc_chan_state[td->channel].hcint = hcint;
+		}
+		if (!(hcint & HCINT_ERRORS))
+			td->errcnt = 0;
+	}
+
+	switch (td->state) {
+	case DWC_CHAN_ST_START:
+		goto send_pkt;
+
+	case DWC_CHAN_ST_WAIT_ANE:
+		if (hcint & (HCINT_RETRY | HCINT_ERRORS)) {
+			if (!dwc_otg_host_channel_wait(td))
+				break;
+			td->did_nak = 1;
+			goto send_pkt;
+		}
+		if (hcint & (HCINT_ACK | HCINT_NYET)) {
+			if (!dwc_otg_host_channel_wait(td))
+				break;
+
+			td->offset += td->tx_bytes;
+			td->remainder -= td->tx_bytes;
+			td->toggle ^= 1;
+
+			/* check remainder */
+			if (td->remainder == 0) {
+				if (td->short_pkt)
+					return (0);	/* complete */
+
+				/*
+				 * Else we need to transmit a short
+				 * packet:
+				 */
+			}
+			goto send_pkt;
+		}
+		break;
+	case DWC_CHAN_ST_WAIT_S_ANE:
+		if (hcint & (HCINT_RETRY | HCINT_ERRORS)) {
+			if (!dwc_otg_host_channel_wait(td))
+				break;
+			td->did_nak = 1;
+			goto send_pkt;
+		}
+		if (hcint & (HCINT_ACK | HCINT_NYET)) {
+			if (!dwc_otg_host_channel_wait(td))
+				break;
+			goto send_cpkt;
+		}
+		break;
+	case DWC_CHAN_ST_WAIT_C_ANE:
+		if (hcint & (HCINT_RETRY | HCINT_ERRORS)) {
+			if (!dwc_otg_host_channel_wait(td))
+				break;
+			td->did_nak = 1;
+			goto send_cpkt;
+		}
+		if (hcint & (HCINT_ACK | HCINT_NYET)) {
+			if (!dwc_otg_host_channel_wait(td))
+				break;
+			td->offset += td->tx_bytes;
+			td->remainder -= td->tx_bytes;
+			td->toggle ^= 1;
+
+			/* check remainder */
+			if (td->remainder == 0) {
+				if (td->short_pkt)
+					return (0);	/* complete */
+
+				/* else we need to transmit a short packet */
+			}
+			goto send_pkt;
+		}
+		break;
+
+	case DWC_CHAN_ST_TX_PKT:
+		goto send_pkt;
+
+	case DWC_CHAN_ST_TX_CPKT:
+		goto send_cpkt;
+
+	default:
+		break;
+	}
+	goto busy;
+
+send_pkt:
+	if (dwc_otg_host_rate_check(td, 1)) {
+		td->state = DWC_CHAN_ST_TX_PKT;
+		dwc_otg_host_channel_free(td);
+		goto busy;
+	}
+
+	if (td->hcsplt != 0) {
+		td->hcsplt &= ~HCSPLT_COMPSPLT;
+		td->state = DWC_CHAN_ST_WAIT_S_ANE;
+	} else {
+		td->state = DWC_CHAN_ST_WAIT_ANE;
+	}
+
+	/* send one packet at a time */
+	count = td->max_packet_size;
+	if (td->remainder < count) {
+		/* we have a short packet */
+		td->short_pkt = 1;
+		count = td->remainder;
+	}
+
+	/* TODO: HCTSIZ_DOPNG */
+
+	DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel),
+	    (count << HCTSIZ_XFERSIZE_SHIFT) |
+	    (1 << HCTSIZ_PKTCNT_SHIFT) |
+	    (td->toggle ? (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT) :
+	    (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT)));
+
+	DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(td->channel), td->hcsplt);
+
+	hcchar = td->hcchar;
+	hcchar &= ~HCCHAR_EPDIR_IN;
+
+	/* must enable before writing data to FIFO */
+	DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel), hcchar);
+
+	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);
+
+		if (_DMA_) {
+		} else {
+			/* transfer data into FIFO */
+			bus_space_write_region_4(sc->sc_io_tag, sc->sc_io_hdl,
+			    DOTG_DFIFO(td->channel),
+			    sc->sc_tx_bounce_buffer, (count + 3) / 4);
+		}
+	}
+
+	/* store number of bytes transmitted */
+	td->tx_bytes = count;
+
+	goto busy;
+
+send_cpkt:
+	td->hcsplt |= HCSPLT_COMPSPLT;
+	td->state = DWC_CHAN_ST_WAIT_C_ANE;
+
+	DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel),
+	    (td->toggle ? (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT) :
+	    (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT)));
+
+	DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(td->channel), td->hcsplt);
+
+	hcchar = td->hcchar;
+	hcchar &= ~HCCHAR_EPDIR_IN;
+
+	/* must enable channel before writing data to FIFO */
+	DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel), hcchar);
+
+busy:
+	return (1);	/* busy */
+}
+
+static uint8_t
 dwc_otg_data_tx(struct dwc_otg_td *td)
 {
 	struct dwc_otg_softc *sc;
@@ -667,7 +1643,7 @@
 			 * The current transfer was cancelled
 			 * by the USB Host:
 			 */
-			td->error = 1;
+			td->error_any = 1;
 			return (0);		/* complete */
 		}
 	}
@@ -710,10 +1686,14 @@
 			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,
-			    DOTG_DFIFO(td->ep_no),
-			    sc->sc_tx_bounce_buffer, (count + 3) / 4);
+			if (_DMA_) {
+			} else {
+				/* transfer data into FIFO */
+				bus_space_write_region_4(sc->sc_io_tag,
+				    sc->sc_io_hdl,
+				    DOTG_DFIFO(td->ep_no),
+				    sc->sc_tx_bounce_buffer, (count + 3) / 4);
+			}
 
 			td->tx_bytes -= count;
 			td->remainder -= count;
@@ -872,10 +1852,24 @@
 dwc_otg_xfer_do_fifo(struct usb_xfer *xfer)
 {
 	struct dwc_otg_td *td;
+	uint8_t toggle;
+	uint8_t channel;
+	uint8_t tmr_val;
+	uint8_t tmr_res;
 
 	DPRINTFN(9, "\n");
 
 	td = xfer->td_transfer_cache;
+
+	/*
+	 * If we are suspended in host mode and no channel is
+	 * allocated, simply return:
+	 */
+	if (xfer->xroot->udev->flags.self_suspended != 0 &&
+	    xfer->xroot->udev->flags.usb_mode == USB_MODE_HOST &&
+	    td->channel >= DWC_OTG_MAX_CHANNELS) {
+		return (1);	/* not complete */
+	}
 	while (1) {
 		if ((td->func) (td)) {
 			/* operation in progress */
@@ -884,7 +1878,7 @@
 		if (((void *)td) == xfer->td_transfer_last) {
 			goto done;
 		}
-		if (td->error) {
+		if (td->error_any) {
 			goto done;
 		} else if (td->remainder > 0) {
 			/*
@@ -899,8 +1893,16 @@
 		 * Fetch the next transfer descriptor and transfer
 		 * some flags to the next transfer descriptor
 		 */
+		tmr_res = td->tmr_res;
+		tmr_val = td->tmr_val;
+		toggle = td->toggle;
+		channel = td->channel;
 		td = td->obj_next;
 		xfer->td_transfer_cache = td;
+		td->toggle = toggle;	/* transfer toggle */
+		td->channel = channel;	/* transfer channel */
+		td->tmr_res = tmr_res;
+		td->tmr_val = tmr_val;
 	}
 	return (1);			/* not complete */
 
@@ -912,13 +1914,82 @@
 }
 
 static void
+dwc_otg_timer(void *_sc)
+{
+	struct dwc_otg_softc *sc = _sc;
+	struct usb_xfer *xfer;
+	struct dwc_otg_td *td;
+
+	USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+	DPRINTF("\n");
+
+	/* increment timer value */
+	sc->sc_tmr_val++;
+
+	TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+		td = xfer->td_transfer_cache;
+		if (td != NULL)
+			td->did_nak = 0;
+	}
+
+	/* poll jobs */
+	dwc_otg_interrupt_poll(sc);
+
+	if (sc->sc_timer_active) {
+		/* restart timer */
+		usb_callout_reset(&sc->sc_timer,
+		    hz / (1000 / DWC_OTG_HOST_TIMER_RATE),
+		    &dwc_otg_timer, sc);
+	}
+}
+
+static void
+dwc_otg_timer_start(struct dwc_otg_softc *sc)
+{
+	if (sc->sc_timer_active != 0)
+		return;
+
+	sc->sc_timer_active = 1;
+
+	/* restart timer */
+	usb_callout_reset(&sc->sc_timer,
+	    hz / (1000 / DWC_OTG_HOST_TIMER_RATE),
+	    &dwc_otg_timer, sc);
+}
+
+static void
+dwc_otg_timer_stop(struct dwc_otg_softc *sc)
+{
+	if (sc->sc_timer_active == 0)
+		return;
+
+	sc->sc_timer_active = 0;
+
+	/* stop timer */
+	usb_callout_stop(&sc->sc_timer);
+}
+
+static void
 dwc_otg_interrupt_poll(struct dwc_otg_softc *sc)
 {
 	struct usb_xfer *xfer;
 	uint32_t temp;
 	uint8_t got_rx_status;
+	uint8_t x;
 
 repeat:
+	/* get all channel interrupts */
+	for (x = 0; x != sc->sc_host_ch_max; x++) {
+		temp = DWC_OTG_READ_4(sc, DOTG_HCINT(x));
+		if (temp != 0) {
+			DWC_OTG_WRITE_4(sc, DOTG_HCINT(x), temp);
+			temp &= ~(HCINT_SOFTWARE_ONLY |
+			    HCINT_HALTED_ONLY);
+			sc->sc_chan_state[x].hcint |= temp;
+		}
+	}
+
 	if (sc->sc_last_rx_status == 0) {
 
 		temp = DWC_OTG_READ_4(sc, DOTG_GINTSTS);
@@ -932,19 +2003,6 @@
 
 			uint8_t ep_no;
 
-			temp = GRXSTSRD_BCNT_GET(
-			    sc->sc_last_rx_status);
-			ep_no = GRXSTSRD_CHNUM_GET(
-			    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,
-				    DOTG_DFIFO(ep_no),
-				    sc->sc_rx_bounce_buffer, (temp + 3) / 4);
-			}
-
 			temp = sc->sc_last_rx_status &
 			    GRXSTSRD_PKTSTS_MASK;
 
@@ -955,8 +2013,26 @@
 				goto repeat;
 			}
 
+			temp = GRXSTSRD_BCNT_GET(
+			    sc->sc_last_rx_status);
+			ep_no = GRXSTSRD_CHNUM_GET(
+			    sc->sc_last_rx_status);
+
+			/* receive data, if any */
+			if (temp != 0) {
+				DPRINTF("Reading %d bytes from ep %d\n", temp, ep_no);
+				if (_DMA_) {
+				} else {
+					bus_space_read_region_4(sc->sc_io_tag,
+					    sc->sc_io_hdl,
+					    DOTG_DFIFO(ep_no),
+					    sc->sc_rx_bounce_buffer,
+					    (temp + 3) / 4);
+				}
+			}
+
 			/* check if we should dump the data */
-			if (!(sc->sc_active_out_ep & (1U << ep_no))) {
+			if (!(sc->sc_active_rx_ep & (1U << ep_no))) {
 				dwc_otg_common_rx_ack(sc);
 				goto repeat;
 			}
@@ -978,7 +2054,7 @@
 		    sc->sc_last_rx_status);
 
 		/* check if we should dump the data */
-		if (!(sc->sc_active_out_ep & (1U << ep_no))) {
+		if (!(sc->sc_active_rx_ep & (1U << ep_no))) {
 			dwc_otg_common_rx_ack(sc);
 			goto repeat;
 		}
@@ -994,6 +2070,7 @@
 	}
 
 	if (got_rx_status) {
+		/* check if data was consumed */
 		if (sc->sc_last_rx_status == 0)
 			goto repeat;
 
@@ -1042,11 +2119,14 @@
 	status = DWC_OTG_READ_4(sc, DOTG_GINTSTS);
 	DWC_OTG_WRITE_4(sc, DOTG_GINTSTS, status);
 
-	DPRINTFN(14, "GINTSTS=0x%08x\n", status);
+	DPRINTFN(14, "GINTSTS=0x%08x HAINT=0x%08x HFNUM=0x%08x\n",
+	    status, DWC_OTG_READ_4(sc, DOTG_HAINT),
+	    DWC_OTG_READ_4(sc, DOTG_HFNUM));
 
 	if (status & GINTSTS_USBRST) {
 
 		/* set correct state */
+		sc->sc_flags.status_device_mode = 1;
 		sc->sc_flags.status_bus_reset = 0;
 		sc->sc_flags.status_suspend = 0;
 		sc->sc_flags.change_suspend = 0;
@@ -1064,24 +2144,23 @@
 		DPRINTFN(5, "end of reset\n");
 
 		/* set correct state */
+		sc->sc_flags.status_device_mode = 1;
 		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;
+		sc->sc_flags.status_low_speed = 0;
+		sc->sc_flags.port_enabled = 1;
 
 		/* reset FIFOs */
-		dwc_otg_init_fifo(sc);
+		dwc_otg_init_fifo(sc, DWC_MODE_DEVICE);
 
 		/* reset function address */
 		dwc_otg_set_address(sc, 0);
 
-		/* reset active endpoints */
-		sc->sc_active_out_ep = 1;
-
 		/* figure out enumeration speed */
 		temp = DWC_OTG_READ_4(sc, DOTG_DSTS);
-		if (DSTS_ENUMSPD_GET(temp) ==
-		    DSTS_ENUMSPD_HI)
+		if (DSTS_ENUMSPD_GET(temp) == DSTS_ENUMSPD_HI)
 			sc->sc_flags.status_high_speed = 1;
 		else
 			sc->sc_flags.status_high_speed = 0;
@@ -1095,6 +2174,72 @@
 		/* complete root HUB interrupt endpoint */
 		dwc_otg_root_intr(sc);
 	}
+
+	if (status & GINTSTS_PRTINT) {
+		uint32_t hprt;
+
+		hprt = DWC_OTG_READ_4(sc, DOTG_HPRT);
+
+		/* clear change bits */
+		DWC_OTG_WRITE_4(sc, DOTG_HPRT, (hprt & (
+		    HPRT_PRTPWR | HPRT_PRTENCHNG |
+		    HPRT_PRTCONNDET | HPRT_PRTOVRCURRCHNG)) |
+		    sc->sc_hprt_val);
+
+		DPRINTFN(12, "GINTSTS=0x%08x, HPRT=0x%08x\n", status, hprt);
+
+		sc->sc_flags.status_device_mode = 0;
+
+		if (hprt & HPRT_PRTCONNSTS)
+			sc->sc_flags.status_bus_reset = 1;
+		else
+			sc->sc_flags.status_bus_reset = 0;
+
+		if (hprt & HPRT_PRTENCHNG)
+			sc->sc_flags.change_enabled = 1;
+
+		if (hprt & HPRT_PRTENA)
+			sc->sc_flags.port_enabled = 1;
+		else
+			sc->sc_flags.port_enabled = 0;
+
+		if (hprt & HPRT_PRTOVRCURRCHNG)
+			sc->sc_flags.change_over_current = 1;
+
+		if (hprt & HPRT_PRTOVRCURRACT)
+			sc->sc_flags.port_over_current = 1;
+		else
+			sc->sc_flags.port_over_current = 0;
+
+		if (hprt & HPRT_PRTPWR)
+			sc->sc_flags.port_powered = 1;
+		else
+			sc->sc_flags.port_powered = 0;
+
+		if (((hprt & HPRT_PRTSPD_MASK)
+		    >> HPRT_PRTSPD_SHIFT) == HPRT_PRTSPD_LOW)
+			sc->sc_flags.status_low_speed = 1;
+		else
+			sc->sc_flags.status_low_speed = 0;
+
+		if (((hprt & HPRT_PRTSPD_MASK)
+		    >> HPRT_PRTSPD_SHIFT) == HPRT_PRTSPD_HIGH)
+			sc->sc_flags.status_high_speed = 1;
+		else
+			sc->sc_flags.status_high_speed = 0;
+
+		if (hprt & HPRT_PRTCONNDET)
+			sc->sc_flags.change_connect = 1;
+
+		if (hprt & HPRT_PRTSUSP)
+			dwc_otg_suspend_irq(sc);
+		else
+			dwc_otg_resume_irq(sc);
+
+		/* complete root HUB interrupt endpoint */
+		dwc_otg_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
@@ -1110,26 +2255,12 @@
 
 		DPRINTFN(5, "suspend interrupt\n");
 
-		if (!sc->sc_flags.status_suspend) {
-			/* update status bits */
-			sc->sc_flags.status_suspend = 1;
-			sc->sc_flags.change_suspend = 1;
-
-			/*
-			 * Disable suspend interrupt and enable resume
-			 * interrupt:
-			 */
-			sc->sc_irq_mask &= ~GINTSTS_USBSUSP;
-			sc->sc_irq_mask |= GINTSTS_WKUPINT;
-			DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
-
-			/* complete root HUB interrupt endpoint */
-			dwc_otg_root_intr(sc);
-		}
+		dwc_otg_suspend_irq(sc);
 	}
 	/* check VBUS */
 	if (status & (GINTSTS_USBSUSP |
 	    GINTSTS_USBRST |
+	    GINTMSK_OTGINTMSK |
 	    GINTSTS_SESSREQINT)) {
 		uint32_t temp;
 
@@ -1138,7 +2269,7 @@
 		DPRINTFN(5, "GOTGCTL=0x%08x\n", temp);
 
 		dwc_otg_vbus_interrupt(sc,
-		    (temp & GOTGCTL_BSESVLD) ? 1 : 0);
+		    (temp & (GOTGCTL_ASESVLD | GOTGCTL_BSESVLD)) ? 1 : 0);
 	}
 
 	/* clear all IN endpoint interrupts */
@@ -1155,12 +2286,8 @@
 		}
 	}
 
-#if 0
-	/* check if we should poll the FIFOs */
-	if (status & (GINTSTS_RXFLVL | GINTSTS_IEPINT))
-#endif
-		/* poll FIFO(s) */
-		dwc_otg_interrupt_poll(sc);
+	/* poll FIFO(s) */
+	dwc_otg_interrupt_poll(sc);
 
 	USB_BUS_UNLOCK(&sc->sc_bus);
 }
@@ -1183,11 +2310,17 @@
 	td->offset = temp->offset;
 	td->remainder = temp->len;
 	td->tx_bytes = 0;
-	td->error = 0;
-	td->npkt = 1;
+	td->error_any = 0;
+	td->npkt = 0;
 	td->did_stall = temp->did_stall;
 	td->short_pkt = temp->short_pkt;
 	td->alt_next = temp->setup_alt_next;
+	td->set_toggle = 0;
+	td->got_short = 0;
+	td->did_nak = 0;
+	td->channel = DWC_OTG_MAX_CHANNELS;
+	td->state = 0;
+	td->errcnt = 0;
 }
 
 static void
@@ -1197,6 +2330,7 @@
 	struct dwc_otg_td *td;
 	uint32_t x;
 	uint8_t need_sync;
+	uint8_t is_host;
 
 	DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
 	    xfer->address, UE_GET_ADDR(xfer->endpointno),
@@ -1217,12 +2351,18 @@
 	temp.setup_alt_next = xfer->flags_int.short_frames_ok;
 	temp.did_stall = !xfer->flags_int.control_stall;
 
+	is_host = (xfer->xroot->udev->flags.usb_mode == USB_MODE_HOST);
+
 	/* check if we should prepend a setup message */
 
 	if (xfer->flags_int.control_xfr) {
 		if (xfer->flags_int.control_hdr) {
 
-			temp.func = &dwc_otg_setup_rx;
+			if (is_host)
+				temp.func = &dwc_otg_host_setup_tx;
+			else
+				temp.func = &dwc_otg_setup_rx;
+
 			temp.len = xfer->frlengths[0];
 			temp.pc = xfer->frbuffers + 0;
 			temp.short_pkt = temp.len ? 1 : 0;
@@ -1243,11 +2383,21 @@
 
 	if (x != xfer->nframes) {
 		if (xfer->endpointno & UE_DIR_IN) {
-			temp.func = &dwc_otg_data_tx;
-			need_sync = 1;
+			if (is_host) {
+				temp.func = &dwc_otg_host_data_rx;
+				need_sync = 0;
+			} else {
+				temp.func = &dwc_otg_data_tx;
+				need_sync = 1;
+			}
 		} else {
-			temp.func = &dwc_otg_data_rx;
-			need_sync = 0;
+			if (is_host) {
+				temp.func = &dwc_otg_host_data_tx;
+				need_sync = 0;
+			} else {
+				temp.func = &dwc_otg_data_rx;
+				need_sync = 0;
+			}
 		}
 
 		/* setup "pc" pointer */
@@ -1318,14 +2468,29 @@
 			 * endpoint direction.
 			 */
 			if (xfer->endpointno & UE_DIR_IN) {
-				temp.func = &dwc_otg_data_rx;
-				need_sync = 0;
+				if (is_host) {
+					temp.func = &dwc_otg_host_data_tx;
+					need_sync = 0;
+				} else {
+					temp.func = &dwc_otg_data_rx;
+					need_sync = 0;
+				}
 			} else {
-				temp.func = &dwc_otg_data_tx;
-				need_sync = 1;
+				if (is_host) {
+					temp.func = &dwc_otg_host_data_rx;
+					need_sync = 0;
+				} else {
+					temp.func = &dwc_otg_data_tx;
+					need_sync = 1;
+				}
 			}
 
 			dwc_otg_setup_standard_chain_sub(&temp);
+
+			/* data toggle should be DATA1 */
+			td = temp.td;
+			td->set_toggle = 1;
+
 			if (need_sync) {
 				/* we need a SYNC point after TX */
 				temp.func = &dwc_otg_data_tx_sync;
@@ -1350,6 +2515,101 @@
 	/* must have at least one frame! */
 	td = temp.td;
 	xfer->td_transfer_last = td;
+
+	if (is_host) {
+
+		struct dwc_otg_softc *sc;
+		uint32_t hcchar;
+		uint32_t hcsplt;
+		uint8_t xfer_type;
+
+		sc = DWC_OTG_BUS2SC(xfer->xroot->bus);
+		xfer_type = xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE;
+
+		/* get first again */
+		td = xfer->td_transfer_first;
+		td->toggle = (xfer->endpoint->toggle_next ? 1 : 0);
+
+		hcchar =
+			(xfer->address << HCCHAR_DEVADDR_SHIFT) |
+			(xfer_type << HCCHAR_EPTYPE_SHIFT) |
+			((xfer->endpointno & UE_ADDR) << HCCHAR_EPNUM_SHIFT) |
+			(xfer->max_packet_size << HCCHAR_MPS_SHIFT) |
+			HCCHAR_CHENA;
+
+		if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_LOW)
+			hcchar |= HCCHAR_LSPDDEV;
+		if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN)
+			hcchar |= HCCHAR_EPDIR_IN;
+
+		switch (xfer->xroot->udev->speed) {
+		case USB_SPEED_FULL:
+		case USB_SPEED_LOW:
+			/* check if root HUB port is running High Speed */
+			if (sc->sc_flags.status_high_speed != 0) {
+				hcsplt = HCSPLT_SPLTENA |
+				    (xfer->xroot->udev->hs_port_no <<
+				    HCSPLT_PRTADDR_SHIFT) |
+				    (xfer->xroot->udev->hs_hub_addr <<
+				    HCSPLT_HUBADDR_SHIFT);
+				if (xfer_type == UE_ISOCHRONOUS)  /* XXX */
+					hcsplt |= (3 << HCSPLT_XACTPOS_SHIFT);
+			} else {
+				hcsplt = 0;
+			}
+			if (xfer_type == UE_INTERRUPT) {
+				uint32_t ival;
+				ival = xfer->interval / DWC_OTG_HOST_TIMER_RATE;
+				if (ival == 0)
+					ival = 1;
+				else if (ival > 255)
+					ival = 255;
+				td->tmr_val = sc->sc_tmr_val + ival;
+				td->tmr_res = ival;
+			}
+			break;
+		case USB_SPEED_HIGH:
+			hcsplt = 0;
+			if (xfer_type == UE_ISOCHRONOUS ||
+			    xfer_type == UE_INTERRUPT) {
+				hcchar |= ((xfer->max_packet_count & 3)
+				    << HCCHAR_MC_SHIFT);
+			}
+			if (xfer_type == UE_INTERRUPT) {
+				uint32_t ival;
+				ival = xfer->interval / DWC_OTG_HOST_TIMER_RATE;
+				if (ival == 0)
+					ival = 1;
+				else if (ival > 255)
+					ival = 255;
+				td->tmr_val = sc->sc_tmr_val + ival;
+				td->tmr_res = ival;
+			}
+			break;
+		default:
+			hcsplt = 0;
+			break;
+		}
+
+		if (xfer_type == UE_ISOCHRONOUS) {
+			td->tmr_val = xfer->endpoint->isoc_next & 0xFF;
+			td->tmr_res = 1 << usbd_xfer_get_fps_shift(xfer);
+		} else if (xfer_type != UE_INTERRUPT) {
+			td->tmr_val = 0;
+			td->tmr_res = 0;
+		}
+
+		/* store configuration in all TD's */
+		while (1) {
+			td->hcchar = hcchar;
+			td->hcsplt = hcsplt;
+
+			if (((void *)td) == xfer->td_transfer_last)
+				break;
+
+			td = td->obj_next;
+		}
+	}
 }
 
 static void
@@ -1403,7 +2663,7 @@
 {
 	struct dwc_otg_td *td;
 	uint32_t len;
-	uint8_t error;
+	usb_error_t error;
 
 	DPRINTFN(9, "\n");
 
@@ -1412,21 +2672,25 @@
 	do {
 		len = td->remainder;
 
+		/* store last data toggle */
+		xfer->endpoint->toggle_next = td->toggle;
+
 		if (xfer->aframes != xfer->nframes) {
 			/*
 			 * Verify the length and subtract
 			 * the remainder from "frlengths[]":
 			 */
 			if (len > xfer->frlengths[xfer->aframes]) {
-				td->error = 1;
+				td->error_any = 1;
 			} else {
 				xfer->frlengths[xfer->aframes] -= len;
 			}
 		}
 		/* Check for transfer error */
-		if (td->error) {
+		if (td->error_any) {
 			/* the transfer is finished */
-			error = 1;
+			error = (td->error_stall ?
+			    USB_ERR_STALLED : USB_ERR_IOERROR);
 			td = NULL;
 			break;
 		}
@@ -1458,8 +2722,7 @@
 
 	xfer->td_transfer_cache = td;
 
-	return (error ?
-	    USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION);
+	return (error);
 }
 
 static void
@@ -1519,6 +2782,13 @@
 
 	if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) {
 		DPRINTFN(15, "disabled interrupts!\n");
+	} else {
+		struct dwc_otg_td *td;
+
+		td = xfer->td_transfer_first;
+
+		if (td != NULL)
+			dwc_otg_host_channel_free(td);
 	}
 	/* dequeue transfer and start next transfer */
 	usbd_transfer_done(xfer, error);
@@ -1541,6 +2811,12 @@
 
 	USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
 
+	/* check mode */
+	if (udev->flags.usb_mode != USB_MODE_DEVICE) {
+		/* not supported */
+		return;
+	}
+
 	sc = DWC_OTG_BUS2SC(udev->bus);
 
 	/* get endpoint address */
@@ -1563,7 +2839,7 @@
 	/* clear active OUT ep */
 	if (!(ep_no & UE_DIR_IN)) {
 
-		sc->sc_active_out_ep &= ~(1U << (ep_no & UE_ADDR));
+		sc->sc_active_rx_ep &= ~(1U << (ep_no & UE_ADDR));
 
 		if (sc->sc_last_rx_status != 0 &&
 		    (ep_no & UE_ADDR) == GRXSTSRD_CHNUM_GET(
@@ -1592,7 +2868,7 @@
 		reg = DOTG_DIEPCTL(ep_no);
 	} else {
 		reg = DOTG_DOEPCTL(ep_no);
-		sc->sc_active_out_ep |= (1U << ep_no);
+		sc->sc_active_rx_ep |= (1U << ep_no);
 	}
 
 	/* round up and mask away the multiplier count */
@@ -1673,6 +2949,12 @@
 	struct dwc_otg_softc *sc;
 	uint8_t x;
 
+	/* check mode */
+	if (udev->flags.usb_mode != USB_MODE_DEVICE) {
+		/* not supported */
+		return;
+	}
+
 	/* get softc */
 	sc = DWC_OTG_BUS2SC(udev->bus);
 
@@ -1709,8 +2991,8 @@
 	sc->sc_bus.usbrev = USB_REV_2_0;
 	sc->sc_bus.methods = &dwc_otg_bus_methods;
 
-	/* reset active endpoints */
-	sc->sc_active_out_ep = 1;
+	usb_callout_init_mtx(&sc->sc_timer,
+	    &sc->sc_bus.bus_mtx, 0);
 
 	USB_BUS_LOCK(&sc->sc_bus);
 
@@ -1733,11 +3015,23 @@
 	/* wait a little bit for block to reset */
 	usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 128);
 
+	switch (sc->sc_mode) {
+	case DWC_MODE_DEVICE:
+		temp = GUSBCFG_FORCEDEVMODE;
+		break;
+	case DWC_MODE_HOST:
+		temp = GUSBCFG_FORCEHOSTMODE;
+		break;
+	default:
+		temp = 0;
+		break;
+	}
+
 	/* select HSIC or non-HSIC mode */
-	if (DWC_OTG_USE_HSIC) {
+	if (dwc_otg_use_hsic) {
 		DWC_OTG_WRITE_4(sc, DOTG_GUSBCFG,
 		    GUSBCFG_PHYIF |
-		    GUSBCFG_TRD_TIM_SET(5));
+		    GUSBCFG_TRD_TIM_SET(5) | temp);
 		DWC_OTG_WRITE_4(sc, DOTG_GOTGCTL,
 		    0x000000EC);
 
@@ -1749,7 +3043,7 @@
 	} else {
 		DWC_OTG_WRITE_4(sc, DOTG_GUSBCFG,
 		    GUSBCFG_ULPI_UTMI_SEL |
-		    GUSBCFG_TRD_TIM_SET(5));
+		    GUSBCFG_TRD_TIM_SET(5) | temp);
 		DWC_OTG_WRITE_4(sc, DOTG_GOTGCTL, 0);
 
 		temp = DWC_OTG_READ_4(sc, DOTG_GLPMCFG);
@@ -1762,9 +3056,18 @@
 	    DCTL_CGOUTNAK |
 	    DCTL_CGNPINNAK);
 
+	/* disable USB port */
+	DWC_OTG_WRITE_4(sc, DOTG_PCGCCTL, 0xFFFFFFFF);
+
+	/* wait 10ms */
+	usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100);
+
 	/* enable USB port */
 	DWC_OTG_WRITE_4(sc, DOTG_PCGCCTL, 0);
 
+	/* wait 10ms */
+	usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100);
+
 	/* pull up D+ */
 	dwc_otg_pull_up(sc);
 
@@ -1776,42 +3079,62 @@
 
 	sc->sc_dev_ep_max = GHWCFG2_NUMDEVEPS_GET(temp);
 
+	if (sc->sc_dev_ep_max > DWC_OTG_MAX_ENDPOINTS)
+		sc->sc_dev_ep_max = DWC_OTG_MAX_ENDPOINTS;
+
+	sc->sc_host_ch_max = GHWCFG2_NUMHSTCHNL_GET(temp);
+
+	if (sc->sc_host_ch_max > DWC_OTG_MAX_CHANNELS)
+		sc->sc_host_ch_max = DWC_OTG_MAX_CHANNELS;
+
 	temp = DWC_OTG_READ_4(sc, DOTG_GHWCFG4);
 
 	sc->sc_dev_in_ep_max = GHWCFG4_NUM_IN_EP_GET(temp);
 
-	DPRINTF("Total FIFO size = %d bytes, Device EPs = %d/%d\n",
-	    sc->sc_fifo_size, sc->sc_dev_ep_max, sc->sc_dev_in_ep_max);
+	DPRINTF("Total FIFO size = %d bytes, Device EPs = %d/%d Host CHs = %d\n",
+	    sc->sc_fifo_size, sc->sc_dev_ep_max, sc->sc_dev_in_ep_max,
+	    sc->sc_host_ch_max);
 
 	/* setup FIFO */
-	if (dwc_otg_init_fifo(sc))
+	if (dwc_otg_init_fifo(sc, DWC_MODE_OTG))
 		return (EINVAL);
 
 	/* enable interrupts */
 	sc->sc_irq_mask = DWC_OTG_MSK_GINT_ENABLED;
 	DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
 
-	/* enable all endpoint interrupts */
-	temp = DWC_OTG_READ_4(sc, DOTG_GHWCFG2);
-	if (temp & GHWCFG2_MPI) {
-		uint8_t x;
-
-		DPRINTF("Multi Process Interrupts\n");
-
-		for (x = 0; x != sc->sc_dev_in_ep_max; x++) {
-			DWC_OTG_WRITE_4(sc, DOTG_DIEPEACHINTMSK(x),
+	if (sc->sc_mode == DWC_MODE_OTG || sc->sc_mode == DWC_MODE_DEVICE) {
+
+		/* enable all endpoint interrupts */
+		temp = DWC_OTG_READ_4(sc, DOTG_GHWCFG2);
+		if (temp & GHWCFG2_MPI) {
+			uint8_t x;
+
+			DPRINTF("Multi Process Interrupts\n");
+
+			for (x = 0; x != sc->sc_dev_in_ep_max; x++) {
+				DWC_OTG_WRITE_4(sc, DOTG_DIEPEACHINTMSK(x),
+				    DIEPMSK_XFERCOMPLMSK);
+				DWC_OTG_WRITE_4(sc, DOTG_DOEPEACHINTMSK(x), 0);
+			}
+			DWC_OTG_WRITE_4(sc, DOTG_DEACHINTMSK, 0xFFFF);
+		} else {
+			DWC_OTG_WRITE_4(sc, DOTG_DIEPMSK,
 			    DIEPMSK_XFERCOMPLMSK);
-			DWC_OTG_WRITE_4(sc, DOTG_DOEPEACHINTMSK(x), 0);
+			DWC_OTG_WRITE_4(sc, DOTG_DOEPMSK, 0);
+			DWC_OTG_WRITE_4(sc, DOTG_DAINTMSK, 0xFFFF);
 		}
-		DWC_OTG_WRITE_4(sc, DOTG_DEACHINTMSK, 0xFFFF);
-	} else {
-		DWC_OTG_WRITE_4(sc, DOTG_DIEPMSK,
-		    DIEPMSK_XFERCOMPLMSK);
-		DWC_OTG_WRITE_4(sc, DOTG_DOEPMSK, 0);
-		DWC_OTG_WRITE_4(sc, DOTG_DAINTMSK, 0xFFFF);
 	}
 
-	/* enable global IRQ */
+	if (sc->sc_mode == DWC_MODE_OTG || sc->sc_mode == DWC_MODE_HOST) {
+		/* setup clocks */
+		temp = DWC_OTG_READ_4(sc, DOTG_HCFG);
+		temp &= ~(HCFG_FSLSSUPP | HCFG_FSLSPCLKSEL_MASK);
+		temp |= (1 << HCFG_FSLSPCLKSEL_SHIFT);
+		DWC_OTG_WRITE_4(sc, DOTG_HCFG, temp);
+	}
+
+	/* only enable global IRQ */
 	DWC_OTG_WRITE_4(sc, DOTG_GAHBCFG,
 	    GAHBCFG_GLBLINTRMSK);
 
@@ -1825,7 +3148,7 @@
 	DPRINTFN(5, "GOTCTL=0x%08x\n", temp);
 
 	dwc_otg_vbus_interrupt(sc,
-	    (temp & GOTGCTL_BSESVLD) ? 1 : 0);
+	    (temp & (GOTGCTL_ASESVLD | GOTGCTL_BSESVLD)) ? 1 : 0);
 
 	USB_BUS_UNLOCK(&sc->sc_bus);
 
@@ -1841,6 +3164,9 @@
 {
 	USB_BUS_LOCK(&sc->sc_bus);
 
+	/* stop host timer */
+	dwc_otg_timer_stop(sc);
+
 	/* set disconnect */
 	DWC_OTG_WRITE_4(sc, DOTG_DCTL,
 	    DCTL_SFTDISCON);
@@ -1848,6 +3174,7 @@
 	/* turn off global IRQ */
 	DWC_OTG_WRITE_4(sc, DOTG_GAHBCFG, 0);
 
+	sc->sc_flags.port_enabled = 0;
 	sc->sc_flags.port_powered = 0;
 	sc->sc_flags.status_vbus = 0;
 	sc->sc_flags.status_bus_reset = 0;
@@ -1859,6 +3186,8 @@
 	dwc_otg_clocks_off(sc);
 
 	USB_BUS_UNLOCK(&sc->sc_bus);
+
+	usb_callout_drain(&sc->sc_timer);
 }
 
 static void
@@ -1884,14 +3213,13 @@
 }
 
 /*------------------------------------------------------------------------*
- * at91dci bulk support
- * at91dci control support
- * at91dci interrupt support
+ * DWC OTG bulk support
+ * DWC OTG control support
+ * DWC OTG interrupt support
  *------------------------------------------------------------------------*/
 static void
 dwc_otg_device_non_isoc_open(struct usb_xfer *xfer)
 {
-	return;
 }
 
 static void
@@ -1903,7 +3231,6 @@
 static void
 dwc_otg_device_non_isoc_enter(struct usb_xfer *xfer)
 {
-	return;
 }
 
 static void
@@ -1923,35 +3250,41 @@
 };
 
 /*------------------------------------------------------------------------*
- * at91dci full speed isochronous support
+ * DWC OTG full speed isochronous support
  *------------------------------------------------------------------------*/
 static void
-dwc_otg_device_isoc_fs_open(struct usb_xfer *xfer)
+dwc_otg_device_isoc_open(struct usb_xfer *xfer)
 {
-	return;
 }
 
 static void
-dwc_otg_device_isoc_fs_close(struct usb_xfer *xfer)
+dwc_otg_device_isoc_close(struct usb_xfer *xfer)
 {
 	dwc_otg_device_done(xfer, USB_ERR_CANCELLED);
 }
 
 static void
-dwc_otg_device_isoc_fs_enter(struct usb_xfer *xfer)
+dwc_otg_device_isoc_enter(struct usb_xfer *xfer)
 {
 	struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(xfer->xroot->bus);
 	uint32_t temp;
 	uint32_t nframes;
+	uint8_t shift = usbd_xfer_get_fps_shift(xfer);
 
 	DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
 	    xfer, xfer->endpoint->isoc_next, xfer->nframes);
 
-	temp = DWC_OTG_READ_4(sc, DOTG_DSTS);
-
-	/* get the current frame index */
-
-	nframes = DSTS_SOFFN_GET(temp);
+	if (xfer->xroot->udev->flags.usb_mode == USB_MODE_HOST) {
+		temp = DWC_OTG_READ_4(sc, DOTG_HFNUM);
+
+		/* get the current frame index */
+		nframes = (temp & HFNUM_FRNUM_MASK);
+	} else {
+		temp = DWC_OTG_READ_4(sc, DOTG_DSTS);
+
+		/* get the current frame index */
+		nframes = DSTS_SOFFN_GET(temp);
+	}
 
 	if (sc->sc_flags.status_high_speed)
 		nframes /= 8;
@@ -1965,7 +3298,7 @@
 	temp = (nframes - xfer->endpoint->isoc_next) & DWC_OTG_FRAME_MASK;
 
 	if ((xfer->endpoint->is_synced == 0) ||
-	    (temp < xfer->nframes)) {
+	    (temp < (((xfer->nframes << shift) + 7) / 8))) {
 		/*
 		 * If there is data underflow or the pipe queue is
 		 * empty we schedule the transfer a few frames ahead
@@ -1987,32 +3320,32 @@
 	 */
 	xfer->isoc_time_complete =
 	    usb_isoc_time_expand(&sc->sc_bus, nframes) + temp +
-	    xfer->nframes;
-
-	/* compute frame number for next insertion */
-	xfer->endpoint->isoc_next += xfer->nframes;
+	    (((xfer->nframes << shift) + 7) / 8);
 
 	/* setup TDs */
 	dwc_otg_setup_standard_chain(xfer);
+
+	/* compute frame number for next insertion */
+	xfer->endpoint->isoc_next += (xfer->nframes << shift);
 }
 
 static void
-dwc_otg_device_isoc_fs_start(struct usb_xfer *xfer)
+dwc_otg_device_isoc_start(struct usb_xfer *xfer)
 {
 	/* start TD chain */
 	dwc_otg_start_standard_chain(xfer);
 }
 
-struct usb_pipe_methods dwc_otg_device_isoc_fs_methods =
+struct usb_pipe_methods dwc_otg_device_isoc_methods =
 {
-	.open = dwc_otg_device_isoc_fs_open,
-	.close = dwc_otg_device_isoc_fs_close,
-	.enter = dwc_otg_device_isoc_fs_enter,
-	.start = dwc_otg_device_isoc_fs_start,
+	.open = dwc_otg_device_isoc_open,
+	.close = dwc_otg_device_isoc_close,
+	.enter = dwc_otg_device_isoc_enter,
+	.start = dwc_otg_device_isoc_start,
 };
 
 /*------------------------------------------------------------------------*
- * at91dci root control support
+ * DWC OTG root control support
  *------------------------------------------------------------------------*
  * Simulate a hardware HUB by handling all the necessary requests.
  *------------------------------------------------------------------------*/
@@ -2079,7 +3412,7 @@
   'D', 0, 'W', 0, 'C', 0, 'O', 0, 'T', 0, 'G', 0
 
 #define	STRING_PRODUCT \
-  'D', 0, 'C', 0, 'I', 0, ' ', 0, 'R', 0, \
+  'O', 0, 'T', 0, 'G', 0, ' ', 0, 'R', 0, \
   'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, \
   'U', 0, 'B', 0,
 
@@ -2352,9 +3685,9 @@
 	goto tr_valid;
 
 tr_handle_clear_port_feature:
-	if (index != 1) {
+	if (index != 1)
 		goto tr_stalled;
-	}
+
 	DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index);
 
 	switch (value) {
@@ -2363,33 +3696,49 @@
 		break;
 
 	case UHF_PORT_ENABLE:
+		if (sc->sc_flags.status_device_mode == 0) {
+			DWC_OTG_WRITE_4(sc, DOTG_HPRT,
+			    sc->sc_hprt_val | HPRT_PRTENA);
+		}
 		sc->sc_flags.port_enabled = 0;
 		break;
 
+	case UHF_C_PORT_RESET:
+		sc->sc_flags.change_reset = 0;
+		break;
+
+	case UHF_C_PORT_ENABLE:
+		sc->sc_flags.change_enabled = 0;
+		break;
+
+	case UHF_C_PORT_OVER_CURRENT:
+		sc->sc_flags.change_over_current = 0;
+		break;
+
 	case UHF_PORT_TEST:
 	case UHF_PORT_INDICATOR:
-	case UHF_C_PORT_ENABLE:
-	case UHF_C_PORT_OVER_CURRENT:
-	case UHF_C_PORT_RESET:
 		/* nops */
 		break;
+
 	case UHF_PORT_POWER:
 		sc->sc_flags.port_powered = 0;
+		if (sc->sc_mode == DWC_MODE_HOST || sc->sc_mode == DWC_MODE_OTG) {
+			sc->sc_hprt_val = 0;
+			DWC_OTG_WRITE_4(sc, DOTG_HPRT, HPRT_PRTENA);
+		}
 		dwc_otg_pull_down(sc);
 		dwc_otg_clocks_off(sc);
 		break;
+
 	case UHF_C_PORT_CONNECTION:
 		/* clear connect change flag */
 		sc->sc_flags.change_connect = 0;
-
-		if (!sc->sc_flags.status_bus_reset) {
-			/* we are not connected */
-			break;
-		}
 		break;
+
 	case UHF_C_PORT_SUSPEND:
 		sc->sc_flags.change_suspend = 0;
 		break;
+
 	default:
 		err = USB_ERR_IOERROR;
 		goto done;
@@ -2404,15 +3753,53 @@
 
 	switch (value) {
 	case UHF_PORT_ENABLE:
-		sc->sc_flags.port_enabled = 1;
 		break;
+
 	case UHF_PORT_SUSPEND:
+		if (sc->sc_flags.status_device_mode == 0) {
+			/* set suspend BIT */
+			sc->sc_hprt_val |= HPRT_PRTSUSP;
+			DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val);
+
+			/* generate HUB suspend event */
+			dwc_otg_suspend_irq(sc);
+		}
+		break;
+
 	case UHF_PORT_RESET:
+		if (sc->sc_flags.status_device_mode == 0) {
+
+			DPRINTF("PORT RESET\n");
+
+			/* enable PORT reset */
+			DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val | HPRT_PRTRST);
+
+			/* Wait 62.5ms for reset to complete */
+			usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 16);
+
+			DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val);
+
+			/* Wait 62.5ms for reset to complete */
+			usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 16);
+
+			/* reset FIFOs */
+			dwc_otg_init_fifo(sc, DWC_MODE_HOST);
+
+			sc->sc_flags.change_reset = 1;
+		} else {
+			err = USB_ERR_IOERROR;
+		}
+		break;
+
 	case UHF_PORT_TEST:
 	case UHF_PORT_INDICATOR:
 		/* nops */
 		break;
 	case UHF_PORT_POWER:
+		if (sc->sc_mode == DWC_MODE_HOST || sc->sc_mode == DWC_MODE_OTG) {
+			sc->sc_hprt_val |= HPRT_PRTPWR;
+			DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val);
+		}
 		sc->sc_flags.port_powered = 1;
 		break;
 	default:
@@ -2425,45 +3812,58 @@
 
 	DPRINTFN(9, "UR_GET_PORT_STATUS\n");
 
-	if (index != 1) {
+	if (index != 1)
 		goto tr_stalled;
+
+	if (sc->sc_flags.status_vbus)
+		dwc_otg_clocks_on(sc);
+	else
+		dwc_otg_clocks_off(sc);
+
+	/* Select Device Side Mode */
+
+	if (sc->sc_flags.status_device_mode) {
+		value = UPS_PORT_MODE_DEVICE;
+		dwc_otg_timer_stop(sc);
+	} else {
+		value = 0;
+		dwc_otg_timer_start(sc);
 	}
-	if (sc->sc_flags.status_vbus) {
-		dwc_otg_clocks_on(sc);
-	} else {
-		dwc_otg_clocks_off(sc);
-	}
-
-	/* Select Device Side Mode */
-
-	value = UPS_PORT_MODE_DEVICE;
-
-	if (sc->sc_flags.status_high_speed) {
+
+	if (sc->sc_flags.status_high_speed)
 		value |= UPS_HIGH_SPEED;
-	}
-	if (sc->sc_flags.port_powered) {
+	else if (sc->sc_flags.status_low_speed)
+		value |= UPS_LOW_SPEED;
+
+	if (sc->sc_flags.port_powered)
 		value |= UPS_PORT_POWER;
-	}
-	if (sc->sc_flags.port_enabled) {
+
+	if (sc->sc_flags.port_enabled)
 		value |= UPS_PORT_ENABLED;
-	}
+
+	if (sc->sc_flags.port_over_current)
+		value |= UPS_OVERCURRENT_INDICATOR;
+
 	if (sc->sc_flags.status_vbus &&
-	    sc->sc_flags.status_bus_reset) {
+	    sc->sc_flags.status_bus_reset)
 		value |= UPS_CURRENT_CONNECT_STATUS;
-	}
-	if (sc->sc_flags.status_suspend) {
+
+	if (sc->sc_flags.status_suspend)
 		value |= UPS_SUSPEND;
-	}
+
 	USETW(sc->sc_hub_temp.ps.wPortStatus, value);
 
 	value = 0;
 
-	if (sc->sc_flags.change_connect) {
+	if (sc->sc_flags.change_connect)
 		value |= UPS_C_CONNECT_STATUS;
-	}
-	if (sc->sc_flags.change_suspend) {
+	if (sc->sc_flags.change_suspend)
 		value |= UPS_C_SUSPEND;
-	}
+	if (sc->sc_flags.change_reset)
+		value |= UPS_C_PORT_RESET;
+	if (sc->sc_flags.change_over_current)
+		value |= UPS_C_OVERCURRENT_INDICATOR;
+
 	USETW(sc->sc_hub_temp.ps.wPortChange, value);
 	len = sizeof(sc->sc_hub_temp.ps);
 	goto tr_valid;
@@ -2514,7 +3914,7 @@
 	if ((xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) == UE_CONTROL) {
 
 		ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC 1 */
-		    + 1 /* SYNC 2 */ ;
+		    + 1 /* SYNC 2 */ + 1 /* SYNC 3 */;
 	} else {
 
 		ntd = xfer->nframes + 1 /* SYNC */ ;
@@ -2586,17 +3986,23 @@
 
 	if (udev->device_index != sc->sc_rt_addr) {
 
-		if (udev->flags.usb_mode != USB_MODE_DEVICE) {
-			/* not supported */
-			return;
+		if (udev->flags.usb_mode == USB_MODE_DEVICE) {
+			if (udev->speed != USB_SPEED_FULL &&
+			    udev->speed != USB_SPEED_HIGH) {
+				/* not supported */
+				return;
+			}
+		} else {
+			if (udev->speed != USB_SPEED_LOW &&
+			    udev->speed != USB_SPEED_FULL &&
+			    udev->speed != USB_SPEED_HIGH) {
+				/* not supported */
+				return;
+			}
 		}
-		if (udev->speed != USB_SPEED_FULL &&
-		    udev->speed != USB_SPEED_HIGH) {
-			/* not supported */
-			return;
-		}
+
 		if ((edesc->bmAttributes & UE_XFERTYPE) == UE_ISOCHRONOUS)
-			ep->methods = &dwc_otg_device_isoc_fs_methods;
+			ep->methods = &dwc_otg_device_isoc_methods;
 		else
 			ep->methods = &dwc_otg_device_non_isoc_methods;
 	}
@@ -2622,6 +4028,70 @@
 	}
 }
 
+static void
+dwc_otg_get_dma_delay(struct usb_device *udev, uint32_t *pus)
+{
+	/* DMA delay - wait until any use of memory is finished */
+	*pus = (2125);			/* microseconds */
+}
+
+static void
+dwc_otg_device_resume(struct usb_device *udev)
+{
+	struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(udev->bus);
+	struct usb_xfer *xfer;
+	struct dwc_otg_td *td;
+
+	DPRINTF("\n");
+
+	/* Enable relevant Host channels before resuming */
+
+	USB_BUS_LOCK(udev->bus);
+
+	TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+
+		if (xfer->xroot->udev == udev) {
+
+			td = xfer->td_transfer_cache;
+			if (td != NULL &&
+			    td->channel < DWC_OTG_MAX_CHANNELS)
+				sc->sc_chan_state[td->channel].suspended = 0;
+		}
+	}
+
+	USB_BUS_UNLOCK(udev->bus);
+
+	/* poll all transfers again to restart resumed ones */
+	dwc_otg_do_poll(udev->bus);
+}
+
+static void
+dwc_otg_device_suspend(struct usb_device *udev)
+{
+	struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(udev->bus);
+	struct usb_xfer *xfer;
+	struct dwc_otg_td *td;
+
+	DPRINTF("\n");
+
+	/* Disable relevant Host channels before going to suspend */
+
+	USB_BUS_LOCK(udev->bus);
+
+	TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+
+		if (xfer->xroot->udev == udev) {
+
+			td = xfer->td_transfer_cache;
+			if (td != NULL &&
+			    td->channel < DWC_OTG_MAX_CHANNELS)
+				sc->sc_chan_state[td->channel].suspended = 1;
+		}
+	}
+
+	USB_BUS_UNLOCK(udev->bus);
+}
+
 struct usb_bus_methods dwc_otg_bus_methods =
 {
 	.endpoint_init = &dwc_otg_ep_init,
@@ -2635,4 +4105,7 @@
 	.xfer_poll = &dwc_otg_do_poll,
 	.device_state_change = &dwc_otg_device_state_change,
 	.set_hw_power_sleep = &dwc_otg_set_hw_power_sleep,
+	.get_dma_delay = &dwc_otg_get_dma_delay,
+	.device_resume = &dwc_otg_device_resume,
+	.device_suspend = &dwc_otg_device_suspend,
 };


More information about the Zrouter-src-freebsd mailing list