Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 9 Sep 2012 14:41:34 +0000 (UTC)
From:      Hans Petter Selasky <hselasky@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r240279 - head/sys/dev/usb/controller
Message-ID:  <201209091441.q89EfYV8092581@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: hselasky
Date: Sun Sep  9 14:41:34 2012
New Revision: 240279
URL: http://svn.freebsd.org/changeset/base/240279

Log:
  Add support for host mode to the DWC OTG controller driver.
  The DWC OTG host mode support should still be considered
  experimental. Isochronous support for DWC OTG is not
  fully implemented. Some code added derives from
  Aleksandr Rybalko's dotg.c driver.

Modified:
  head/sys/dev/usb/controller/dwc_otg.c
  head/sys/dev/usb/controller/dwc_otg.h
  head/sys/dev/usb/controller/dwc_otgreg.h

Modified: head/sys/dev/usb/controller/dwc_otg.c
==============================================================================
--- head/sys/dev/usb/controller/dwc_otg.c	Sun Sep  9 13:18:13 2012	(r240278)
+++ head/sys/dev/usb/controller/dwc_otg.c	Sun Sep  9 14:41:34 2012	(r240279)
@@ -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.
  */
 
 /*
@@ -91,12 +91,15 @@ __FBSDID("$FreeBSD$");
    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)
+   (GINTSTS_ENUMDONE |			\
+   GINTSTS_USBRST |			\
+   GINTSTS_USBSUSP |			\
+   GINTSTS_IEPINT |			\
+   GINTSTS_RXFLVL |			\
+   GINTSTS_SESSREQINT |			\
+   GINTMSK_OTGINTMSK |			\
+   GINTMSK_HCHINTMSK |			\
+   GINTSTS_PRTINT)
 
 #define DWC_OTG_USE_HSIC 0
 
@@ -114,12 +117,18 @@ SYSCTL_INT(_hw_usb_dwc_otg, OID_AUTO, de
 
 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 dwc_otg_cmd_t dwc_otg_host_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 *);
@@ -153,7 +162,7 @@ dwc_otg_get_hw_ep_profile(struct usb_dev
 }
 
 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;
@@ -193,7 +202,21 @@ dwc_otg_init_fifo(struct dwc_otg_softc *
 	/* 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;
+
+		DWC_OTG_WRITE_4(sc, DOTG_HPTXFSIZ,
+		    ((fifo_size / 4) << 16) |
+		    (tx_start / 4));
+	}
+	if (mode == DWC_MODE_DEVICE) {
+
+	    /* 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 +263,22 @@ dwc_otg_init_fifo(struct dwc_otg_softc *
 		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 +350,15 @@ dwc_otg_resume_irq(struct dwc_otg_softc 
 		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 +366,70 @@ dwc_otg_resume_irq(struct dwc_otg_softc 
 }
 
 static void
-dwc_otg_wakeup_peer(struct dwc_otg_softc *sc)
+dwc_otg_suspend_irq(struct dwc_otg_softc *sc)
 {
-	uint32_t temp;
+	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)
+{
 	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);
+	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);
 
-	/* Wait 8ms for remote wakeup to complete. */
-	usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125);
+		/* resume port */
+		sc->sc_hprt_val |= HPRT_PRTRES;
+		DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val);
 
-	temp &= ~DCTL_RMTWKUPSIG;
-	DWC_OTG_WRITE_4(sc, DOTG_DCTL, temp);
+		/* 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);
@@ -387,6 +462,128 @@ dwc_otg_common_rx_ack(struct dwc_otg_sof
 }
 
 static uint8_t
+dwc_otg_host_channel_alloc(struct dwc_otg_td *td)
+{
+	struct dwc_otg_softc *sc;
+	uint32_t temp;
+	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++) {
+		if (sc->sc_hcchar[x] == 0) {
+
+			/* check if channel is enabled */
+			temp = DWC_OTG_READ_4(sc, DOTG_HCCHAR(x));
+			if (temp & HCCHAR_CHENA)
+				continue;
+
+			sc->sc_hcchar[x] = td->hcchar;
+
+			DPRINTF("HCCHAR=0x%08x HCSPLT=0x%08x\n",
+			    td->hcchar, td->hcsplt);
+
+			temp = DWC_OTG_READ_4(sc, DOTG_HCINT(x));
+			DWC_OTG_WRITE_4(sc, DOTG_HCINT(x), temp);
+			DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(x), td->hcsplt);
+			DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(x), 0);
+			DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(x), 0);
+
+			/* set channel */
+			td->channel = x;
+
+			/* set active EP */
+			sc->sc_active_rx_ep |= (1 << x);
+
+			return (0);	/* allocated */
+		}
+	}
+	return (1);	/* busy */
+}
+
+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 temp;
+	uint32_t max_buffer;
+	uint8_t max_frames;
+
+	if (dwc_otg_host_channel_alloc(td))
+		return (1);		/* busy */
+
+	/* get pointer to softc */
+	sc = DWC_OTG_PC2SC(td->pc);
+
+	temp = DWC_OTG_READ_4(sc, DOTG_HPTXSTS);
+
+	DPRINTF("HPTXSTS=0x%08x\n", temp);
+
+	max_buffer = 4 * (temp & HPTXSTS_PTXFSPCAVAIL_MASK);
+	max_frames = (temp & HPTXSTS_PTXQSPCAVAIL_MASK) >> HPTXSTS_PTXQSPCAVAIL_SHIFT;
+
+	max_buffer = max_buffer - (max_buffer % td->max_packet_size);
+	if (max_buffer == 0 || max_frames == 0)
+		return (1);	/* busy */
+
+	if (sizeof(req) != td->remainder) {
+		td->error_any = 1;
+		return (0);		/* complete */
+	}
+
+	usbd_copy_out(td->pc, 0, &req, sizeof(req));
+
+	td->offset = sizeof(req);
+	td->remainder = 0;
+
+	DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel),
+	    (sizeof(req) << HCTSIZ_XFERSIZE_SHIFT) |
+	    (1 << HCTSIZ_PKTCNT_SHIFT) |
+	    (HCTSIZ_PID_SETUP << HCTSIZ_PID_SHIFT));
+
+	temp = sc->sc_hcchar[td->channel];
+	temp &= ~HCCHAR_EPDIR_IN;
+	temp |= HCCHAR_CHENA;
+
+	/* must enable channel before writing data to FIFO */
+	DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel), temp);
+
+	/* enable interrupts */
+	DWC_OTG_WRITE_4(sc, DOTG_HCINTMSK(td->channel),
+	    HCINT_STALL | HCINT_DATATGLERR | HCINT_BBLERR |
+	    HCINT_AHBERR | HCINT_CHHLTD | HCINT_XFERCOMPL);
+
+	sc->sc_haint_mask |= (1 << td->channel);
+	DWC_OTG_WRITE_4(sc, DOTG_HAINTMSK, sc->sc_haint_mask);
+
+	/* 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);
+
+	td->toggle = 1;
+
+	/* need to sync before complete */
+	td->func = &dwc_otg_host_data_tx_sync;
+
+	/* check status */
+	return (dwc_otg_host_data_tx_sync(td));
+}
+
+static uint8_t
 dwc_otg_setup_rx(struct dwc_otg_td *td)
 {
 	struct dwc_otg_softc *sc;
@@ -518,6 +715,178 @@ not_complete:
 }
 
 static uint8_t
+dwc_otg_host_data_rx(struct dwc_otg_td *td)
+{
+	struct dwc_otg_softc *sc;
+	uint32_t temp;
+	uint16_t count;
+	uint8_t got_short;
+	uint8_t is_isoc;
+
+	if (dwc_otg_host_channel_alloc(td))
+		return (1);		/* busy */
+
+	got_short = 0;
+
+	/* get pointer to softc */
+	sc = DWC_OTG_PC2SC(td->pc);
+
+	temp = DWC_OTG_READ_4(sc, DOTG_HCINT(td->channel));
+	DWC_OTG_WRITE_4(sc, DOTG_HCINT(td->channel), temp);
+
+	DPRINTF("CH=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n",
+	    td->channel,
+	    temp, DWC_OTG_READ_4(sc, DOTG_HCCHAR(td->channel)),
+	    DWC_OTG_READ_4(sc, DOTG_HCTSIZ(td->channel)));
+
+	if (temp & HCINT_STALL) {
+		td->error_stall = 1;
+		td->error_any = 1;
+		return (0);		/* complete */
+	}
+
+	if (temp & (HCINT_DATATGLERR | HCINT_BBLERR |
+	    HCINT_AHBERR | HCINT_CHHLTD)) {
+		td->error_any = 1;
+		return (0);		/* complete */
+	}
+
+	/* check endpoint status */
+	if (sc->sc_last_rx_status == 0)
+		goto not_complete;
+
+	if (GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status) != td->channel)
+		goto not_complete;
+
+	switch (sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) {
+	case GRXSTSRH_IN_DATA:
+
+		DPRINTF("DATA\n");
+
+		td->npkt = 0;
+
+		if ((sc->sc_last_rx_status &
+		    GRXSTSRD_DPID_MASK) == GRXSTSRD_DPID_DATA0) {
+			if (td->toggle == 1) {
+				/* release FIFO - wrong toggle */
+				DPRINTF("Wrong DT\n");
+				dwc_otg_common_rx_ack(sc);
+				goto not_complete;
+			}
+			td->toggle = 1;
+		} else {
+			if (td->toggle == 0) {
+				/* release FIFO - wrong toggle */
+				DPRINTF("Wrong DT\n");
+				dwc_otg_common_rx_ack(sc);
+				goto not_complete;
+			}
+			td->toggle = 0;
+		}
+		break;
+
+	default:
+		DPRINTF("OTHER\n");
+
+		/* release FIFO */
+		dwc_otg_common_rx_ack(sc);
+		goto not_complete;
+	}
+
+	/* 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;
+			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;
+
+	/* 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:
+
+	if (td->npkt != 0)
+		return (1);	/* busy */
+
+	temp = sc->sc_hcchar[td->channel];
+
+	is_isoc = (((temp & HCCHAR_EPTYPE_MASK) >>
+	    HCCHAR_EPTYPE_SHIFT) == UE_ISOCHRONOUS);
+
+	if (is_isoc != 0) {
+		if ((sc->sc_sof_val & 0xFF) != td->sof_val)
+			return (1);	/* busy */
+		if (td->sof_val & 1)
+			temp |= HCCHAR_ODDFRM;
+		td->sof_val += td->sof_res;
+		/* DATA 0 */
+		td->toggle = 0;
+	} else if (td->set_toggle) {
+		td->set_toggle = 0;
+		td->toggle = 1;
+	}
+
+	/* receive one packet at a time */
+	td->npkt = 1;
+
+	DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel),
+	    (td->max_packet_size << HCTSIZ_XFERSIZE_SHIFT) |
+	    (td->npkt << HCTSIZ_PKTCNT_SHIFT) |
+	    (td->toggle ? (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT) :
+	    (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT)));
+
+	/* enable interrupts */
+	DWC_OTG_WRITE_4(sc, DOTG_HCINTMSK(td->channel),
+	    HCINT_STALL | HCINT_DATATGLERR | HCINT_BBLERR |
+	    HCINT_AHBERR | HCINT_CHHLTD | HCINT_XFERCOMPL);
+
+	sc->sc_haint_mask |= (1 << td->channel);
+	DWC_OTG_WRITE_4(sc, DOTG_HAINTMSK, sc->sc_haint_mask);
+
+	temp |= HCCHAR_CHENA | HCCHAR_EPDIR_IN;
+
+	/* must enable channel before data can be received */
+	DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel), temp);
+
+	return (1);			/* not complete */
+}
+
+static uint8_t
 dwc_otg_data_rx(struct dwc_otg_td *td)
 {
 	struct dwc_otg_softc *sc;
@@ -551,7 +920,7 @@ dwc_otg_data_rx(struct dwc_otg_td *td)
 		/*
 		 * USB Host Aborted the transfer.
 		 */
-		td->error = 1;
+		td->error_any = 1;
 		return (0);		/* complete */
 	}
 
@@ -573,7 +942,7 @@ dwc_otg_data_rx(struct dwc_otg_td *td)
 			got_short = 1;
 		} else {
 			/* invalid USB packet */
-			td->error = 1;
+			td->error_any = 1;
 
 			/* release FIFO */
 			dwc_otg_common_rx_ack(sc);
@@ -583,7 +952,7 @@ dwc_otg_data_rx(struct dwc_otg_td *td)
 	/* 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 +979,7 @@ not_complete:
 
 	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 +1000,163 @@ not_complete:
 }
 
 static uint8_t
+dwc_otg_host_data_tx(struct dwc_otg_td *td)
+{
+	struct dwc_otg_softc *sc;
+	uint32_t max_buffer;
+	uint32_t count;
+	uint32_t mpkt;
+	uint32_t temp;
+	uint8_t is_isoc;
+	uint8_t max_frames;
+
+	if (dwc_otg_host_channel_alloc(td))
+		return (1);		/* busy */
+
+	/* get pointer to softc */
+	sc = DWC_OTG_PC2SC(td->pc);
+
+	temp = DWC_OTG_READ_4(sc, DOTG_HCINT(td->channel));
+	DWC_OTG_WRITE_4(sc, DOTG_HCINT(td->channel), temp);
+
+	DPRINTF("CH=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n",
+	    td->channel,
+	    temp, DWC_OTG_READ_4(sc, DOTG_HCCHAR(td->channel)),
+	    DWC_OTG_READ_4(sc, DOTG_HCTSIZ(td->channel)));
+
+	if (temp & HCINT_STALL) {
+		td->error_stall = 1;
+		td->error_any = 1;
+		return (0);		/* complete */
+	}
+
+	if (temp & (HCINT_DATATGLERR | HCINT_BBLERR |
+	    HCINT_AHBERR | HCINT_CHHLTD)) {
+		td->error_any = 1;
+		return (0);		/* complete */
+	}
+
+	temp = sc->sc_hcchar[td->channel];
+
+	is_isoc = (((temp & HCCHAR_EPTYPE_MASK) >>
+	    HCCHAR_EPTYPE_SHIFT) == UE_ISOCHRONOUS);
+
+	if (is_isoc != 0) {
+		if ((sc->sc_sof_val & 0xFF) != td->sof_val)
+			return (1);	/* busy */
+		if (td->sof_val & 1)
+			sc->sc_hcchar[td->channel] |= HCCHAR_ODDFRM;
+		else
+			sc->sc_hcchar[td->channel] &= ~HCCHAR_ODDFRM;
+		td->sof_val += td->sof_res;
+		td->toggle = 0;
+
+	} else if (td->set_toggle) {
+		td->set_toggle = 0;
+		td->toggle = 1;
+	}
+
+	/* check if all packets have been transferred */
+	temp = DWC_OTG_READ_4(sc, DOTG_HCTSIZ(td->channel));
+	if (temp & HCTSIZ_PKTCNT_MASK) {
+		DPRINTFN(5, "busy ch=%d npkt=%d HCTSIZ=0x%08x\n",
+		    td->channel, (temp & HCTSIZ_PKTCNT_MASK) >>
+		    HCTSIZ_PKTCNT_SHIFT, temp);
+		return (1);	/* busy */
+	}
+
+	temp = DWC_OTG_READ_4(sc, DOTG_HPTXSTS);
+
+	max_buffer = 4 * (temp & HPTXSTS_PTXFSPCAVAIL_MASK);
+	max_frames = (temp & HPTXSTS_PTXQSPCAVAIL_MASK) >> HPTXSTS_PTXQSPCAVAIL_SHIFT;
+
+	max_buffer = max_buffer - (max_buffer % td->max_packet_size);
+	if (max_buffer == 0 || max_frames < 2)
+		return (1);	/* busy */
+
+	/* try to optimise by sending more data */
+	if (td->channel != 0 &&
+	    (td->max_packet_size & 3) == 0 && is_isoc == 0) {
+
+		count = td->remainder;
+		if (count > max_buffer)
+			count = max_buffer;
+
+		mpkt = count / td->max_packet_size;
+
+		if (mpkt > max_frames) {
+			mpkt = max_frames;
+			count = td->max_packet_size * mpkt;
+		} else if ((count == 0) || (count % td->max_packet_size)) {
+			/* we are transmitting a short packet */
+			mpkt++;
+			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;
+		}
+	}
+
+	DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel),
+	    (count << HCTSIZ_XFERSIZE_SHIFT) |
+	    (mpkt << HCTSIZ_PKTCNT_SHIFT) |
+	    (td->toggle ? (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT) :
+	    (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT)));
+
+	td->toggle ^= (mpkt & 1);
+
+	/* TODO: HCTSIZ_DOPNG */
+
+	temp = sc->sc_hcchar[td->channel];
+	temp &= ~(HCCHAR_EPDIR_IN);
+	temp |= HCCHAR_CHENA;
+
+	/* must enable before writing data to FIFO */
+	DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel), temp);
+
+	/* enable interrupts */
+	DWC_OTG_WRITE_4(sc, DOTG_HCINTMSK(td->channel),
+	    HCINT_STALL | HCINT_DATATGLERR | HCINT_BBLERR |
+	    HCINT_AHBERR | HCINT_CHHLTD | HCINT_XFERCOMPL);
+
+	sc->sc_haint_mask |= (1 << td->channel);
+	DWC_OTG_WRITE_4(sc, DOTG_HAINTMSK, sc->sc_haint_mask);
+
+	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,
+		    DOTG_DFIFO(td->channel),
+		    sc->sc_tx_bounce_buffer, (count + 3) / 4);
+
+		td->remainder -= count;
+		td->offset += count;
+	}
+
+	/* 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
 dwc_otg_data_tx(struct dwc_otg_td *td)
 {
 	struct dwc_otg_softc *sc;
@@ -667,7 +1192,7 @@ repeat:
 			 * The current transfer was cancelled
 			 * by the USB Host:
 			 */
-			td->error = 1;
+			td->error_any = 1;
 			return (0);		/* complete */
 		}
 	}
@@ -819,8 +1344,46 @@ repeat:
 	}
 	goto repeat;
 
-not_complete:
-	return (1);			/* not complete */
+not_complete:
+	return (1);			/* not complete */
+}
+
+static uint8_t
+dwc_otg_host_data_tx_sync(struct dwc_otg_td *td)
+{
+	struct dwc_otg_softc *sc;
+	uint32_t temp;
+
+	if (dwc_otg_host_channel_alloc(td))
+		return (1);		/* busy */
+
+	/* get pointer to softc */
+	sc = DWC_OTG_PC2SC(td->pc);
+
+	temp = DWC_OTG_READ_4(sc, DOTG_HCINT(td->channel));
+	DWC_OTG_WRITE_4(sc, DOTG_HCINT(td->channel), temp);
+
+	DPRINTF("CH=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n",
+	    td->channel,
+	    temp, DWC_OTG_READ_4(sc, DOTG_HCCHAR(td->channel)),
+	    DWC_OTG_READ_4(sc, DOTG_HCTSIZ(td->channel)));
+
+	if (temp & HCINT_STALL) {
+		td->error_stall = 1;
+		td->error_any = 1;
+		return (0);		/* complete */
+	}
+
+	if (temp & (HCINT_DATATGLERR | HCINT_BBLERR |
+	    HCINT_AHBERR | HCINT_CHHLTD)) {
+		td->error_any = 1;
+		return (0);		/* complete */
+	}
+
+	if (temp & HCINT_XFERCOMPL)
+		return (0);		/* complete */
+
+	return (1);	/* busy */
 }
 
 static uint8_t
@@ -872,6 +1435,10 @@ static uint8_t
 dwc_otg_xfer_do_fifo(struct usb_xfer *xfer)
 {
 	struct dwc_otg_td *td;
+	uint8_t toggle;
+	uint8_t channel;
+	uint8_t sof_val;
+	uint8_t sof_res;
 
 	DPRINTFN(9, "\n");
 
@@ -884,7 +1451,7 @@ dwc_otg_xfer_do_fifo(struct usb_xfer *xf
 		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 +1466,16 @@ dwc_otg_xfer_do_fifo(struct usb_xfer *xf
 		 * Fetch the next transfer descriptor and transfer
 		 * some flags to the next transfer descriptor
 		 */
+		sof_res = td->sof_res;
+		sof_val = td->sof_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->sof_res = sof_res;
+		td->sof_val = sof_val;
 	}
 	return (1);			/* not complete */
 
@@ -956,7 +1531,7 @@ repeat:
 			}
 
 			/* 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 +1553,7 @@ repeat:
 		    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,9 +1569,16 @@ repeat:
 	}
 
 	if (got_rx_status) {
+		/* check if data was consumed */
 		if (sc->sc_last_rx_status == 0)
 			goto repeat;
 
+		/* if no host listener - dump data */
+		if (sc->sc_flags.status_device_mode == 0) {
+			dwc_otg_common_rx_ack(sc);
+			goto repeat;
+		}
+
 		/* disable RX FIFO level interrupt */
 		sc->sc_irq_mask &= ~GINTSTS_RXFLVL;
 		DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
@@ -1044,9 +1626,17 @@ dwc_otg_interrupt(struct dwc_otg_softc *
 
 	DPRINTFN(14, "GINTSTS=0x%08x\n", status);
 
+	if (status & GINTSTS_HCHINT) {
+		uint32_t temp = DWC_OTG_READ_4(sc, DOTG_HAINT);
+		DWC_OTG_WRITE_4(sc, DOTG_HAINT, temp);
+		DPRINTFN(14, "HAINT=0x%08x HFNUM=0x%08x\n",
+		    temp, 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 +1654,23 @@ dwc_otg_interrupt(struct dwc_otg_softc *
 		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 +1684,72 @@ dwc_otg_interrupt(struct dwc_otg_softc *
 		/* 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 +1765,12 @@ dwc_otg_interrupt(struct dwc_otg_softc *
 
 		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;

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



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201209091441.q89EfYV8092581>