Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 16 May 2014 15:41:55 +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: r266241 - head/sys/dev/usb/controller
Message-ID:  <201405161541.s4GFfteG009737@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: hselasky
Date: Fri May 16 15:41:55 2014
New Revision: 266241
URL: http://svnweb.freebsd.org/changeset/base/266241

Log:
  Implement basic support for the USB host controller found in the
  SAF1761 chip, supporting BULK and CONTROL endpoints. This code is not
  yet tested.
  
  Sponsored by:	DARPA, AFRL

Modified:
  head/sys/dev/usb/controller/saf1761_dci.c
  head/sys/dev/usb/controller/saf1761_dci.h
  head/sys/dev/usb/controller/saf1761_dci_reg.h

Modified: head/sys/dev/usb/controller/saf1761_dci.c
==============================================================================
--- head/sys/dev/usb/controller/saf1761_dci.c	Fri May 16 15:39:11 2014	(r266240)
+++ head/sys/dev/usb/controller/saf1761_dci.c	Fri May 16 15:41:55 2014	(r266241)
@@ -103,13 +103,20 @@ SYSCTL_INT(_hw_usb_saf1761_dci, OID_AUTO
 /* prototypes */
 
 static const struct usb_bus_methods saf1761_dci_bus_methods;
-static const struct usb_pipe_methods saf1761_dci_device_non_isoc_methods;
+static const struct usb_pipe_methods saf1761_otg_non_isoc_methods;
 static const struct usb_pipe_methods saf1761_dci_device_isoc_methods;
 
-static saf1761_dci_cmd_t saf1761_dci_setup_rx;
-static saf1761_dci_cmd_t saf1761_dci_data_rx;
-static saf1761_dci_cmd_t saf1761_dci_data_tx;
-static saf1761_dci_cmd_t saf1761_dci_data_tx_sync;
+static saf1761_dci_cmd_t saf1761_host_setup_tx;
+static saf1761_dci_cmd_t saf1761_host_bulk_data_rx;
+static saf1761_dci_cmd_t saf1761_host_bulk_data_tx;
+static saf1761_dci_cmd_t saf1761_host_intr_data_rx;
+static saf1761_dci_cmd_t saf1761_host_intr_data_tx;
+static saf1761_dci_cmd_t saf1761_host_isoc_data_rx;
+static saf1761_dci_cmd_t saf1761_host_isoc_data_tx;
+static saf1761_dci_cmd_t saf1761_device_setup_rx;
+static saf1761_dci_cmd_t saf1761_device_data_rx;
+static saf1761_dci_cmd_t saf1761_device_data_tx;
+static saf1761_dci_cmd_t saf1761_device_data_tx_sync;
 static void saf1761_dci_device_done(struct usb_xfer *, usb_error_t);
 static void saf1761_dci_do_poll(struct usb_bus *);
 static void saf1761_dci_standard_done(struct usb_xfer *);
@@ -197,6 +204,357 @@ saf1761_dci_wakeup_peer(struct saf1761_d
 
 }
 
+static uint8_t
+saf1761_host_channel_alloc(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
+{
+	uint32_t x;
+
+	if (td->channel < SOTG_HOST_CHANNEL_MAX)
+		return (0);
+
+	switch (td->ep_type) {
+	case UE_INTERRUPT:
+		for (x = 0; x != 32; x++) {
+			if (sc->sc_host_intr_map & (1 << x))
+				continue;
+			sc->sc_host_intr_map |= (1 << x);
+			td->channel = 32 + x;
+			return (0);
+		}
+		break;
+	case UE_ISOCHRONOUS:
+		for (x = 0; x != 32; x++) {
+			if (sc->sc_host_isoc_map & (1 << x))
+				continue;
+			sc->sc_host_isoc_map |= (1 << x);
+			td->channel = 64 + x;
+			return (0);
+		}
+		break;
+	default:
+		for (x = 0; x != 32; x++) {
+			if (sc->sc_host_async_map & (1 << x))
+				continue;
+			sc->sc_host_async_map |= (1 << x);
+			td->channel = x;
+			return (0);
+		}
+		break;
+	}
+	return (1);
+}
+
+static void
+saf1761_host_channel_free(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
+{
+	uint32_t x;
+
+	if (td->channel >= SOTG_HOST_CHANNEL_MAX)
+		return;
+
+	/* disable channel */
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 3), 0);
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 0), 0);
+
+	switch (td->ep_type) {
+	case UE_INTERRUPT:
+		x = td->channel - 32;
+		sc->sc_host_intr_map &= ~(1 << x);
+		td->channel = SOTG_HOST_CHANNEL_MAX;
+		break;
+	case UE_ISOCHRONOUS:
+		x = td->channel - 64;
+		sc->sc_host_isoc_map &= ~(1 << x);
+		td->channel = SOTG_HOST_CHANNEL_MAX;
+		break;
+	default:
+		x = td->channel - 64;
+		sc->sc_host_async_map &= ~(1 << x);
+		td->channel = SOTG_HOST_CHANNEL_MAX;
+		break;
+	}
+}
+
+static void
+saf1761_read_host_fifo_1(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td,
+    void *buf, uint32_t len)
+{
+	bus_space_read_region_1((sc)->sc_io_tag, (sc)->sc_io_hdl,
+	    SOTG_DATA_ADDR(td->channel), buf, len);
+}
+
+static void
+saf1761_write_host_fifo_1(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td,
+    void *buf, uint32_t len)
+{
+	bus_space_write_region_1((sc)->sc_io_tag, (sc)->sc_io_hdl,
+	    SOTG_DATA_ADDR(td->channel), buf, len);
+}
+
+static uint8_t
+saf1761_host_setup_tx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
+{
+	struct usb_device_request req __aligned(4);
+	uint32_t status;
+	uint32_t count;
+
+	if (td->channel < SOTG_HOST_CHANNEL_MAX) {
+		status = SAF1761_READ_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 3));
+		if (status & (1 << 31)) {
+			goto busy;
+		} else if (status & (1 << 30)) {
+			td->error_stall = 1;
+			td->error_any = 1;
+		} else if (status & (3 << 28)) {
+			td->error_any = 1;
+		}
+		count = (status & 0x7FFF);
+
+		saf1761_host_channel_free(sc, td);
+		goto complete;
+	}
+	if (saf1761_host_channel_alloc(sc, td))
+		goto busy;
+
+	if (sizeof(req) != td->remainder) {
+		td->error_any = 1;
+		goto complete;
+	}
+
+	count = sizeof(req);
+
+	usbd_copy_out(td->pc, 0, &req, count);
+
+	saf1761_write_host_fifo_1(sc, td, &req, count);
+
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 7), 0);
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 6), 0);
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 5), 0);
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 4), 0);
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 3),
+	    (1 << 31) | (td->toggle << 25) | (3 << 23));
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 2),
+	    SOTG_HC_MEMORY_ADDR(SOTG_DATA_ADDR(td->channel)) << 8);
+
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 1),
+	    td->dw1_value |
+	    (2 << 10) /* SETUP PID */ |
+	    (td->ep_index >> 1));
+
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 0),
+	    (td->ep_index << 31) |
+	    (1 << 29) /* pkt-multiplier */ |
+	    (td->max_packet_size << 18) /* wMaxPacketSize */ |
+	    (count << 3) /* transfer count */ |
+	    1 /* valid */);
+
+	td->offset += count;
+	td->remainder -= count;
+	td->toggle = 1;
+busy:
+	return (1);	/* busy */
+complete:
+	return (0);	/* complete */
+}
+
+static uint8_t
+saf1761_host_bulk_data_rx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
+{
+	if (td->channel < SOTG_HOST_CHANNEL_MAX) {
+		uint32_t status;
+		uint32_t count;
+		uint8_t got_short;
+
+		status = SAF1761_READ_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 3));
+
+		if (status & (1 << 31)) {
+			goto busy;
+		} else if (status & (1 << 30)) {
+			td->error_stall = 1;
+			td->error_any = 1;
+			goto complete;
+		} else if (status & (3 << 28)) {
+			td->error_any = 1;
+			goto complete;
+		}
+		count = (status & 0x7FFF);
+		got_short = 0;
+
+		/* 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;
+				goto complete;
+			}
+		}
+		td->toggle ^= 1;
+
+		/* verify the packet byte count */
+		if (count > td->remainder) {
+			/* invalid USB packet */
+			td->error_any = 1;
+			goto complete;
+		}
+
+		saf1761_read_host_fifo_1(sc, td,
+		    sc->sc_bounce_buffer, count);
+		usbd_copy_in(td->pc, td->offset,
+		    sc->sc_bounce_buffer, count);
+
+		td->remainder -= count;
+		td->offset += count;
+
+		saf1761_host_channel_free(sc, td);
+
+		/* check if we are complete */
+		if ((td->remainder == 0) || got_short) {
+			if (td->short_pkt)
+				goto complete;
+			/* else need to receive a zero length packet */
+		}
+	}
+	if (saf1761_host_channel_alloc(sc, td))
+		goto busy;
+
+	/* set toggle, if any */
+	if (td->set_toggle) {
+		td->set_toggle = 0;
+		td->toggle = 1;
+	}
+
+	/* receive one more packet */
+
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 7), 0);
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 6), 0);
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 5), 0);
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 4), 0);
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 3),
+	    (1 << 31) | (td->toggle << 25) | (3 << 23));
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 2),
+	    SOTG_HC_MEMORY_ADDR(SOTG_DATA_ADDR(td->channel)) << 8);
+
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 1),
+	    td->dw1_value |
+	    (1 << 10) /* IN-PID */ |
+	    (td->ep_index >> 1));
+
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 0),
+	    (td->ep_index << 31) |
+	    (1 << 29) /* pkt-multiplier */ |
+	    (td->max_packet_size << 18) /* wMaxPacketSize */ |
+	    (td->max_packet_size << 3) /* transfer count */ |
+	    1 /* valid */);
+busy:
+	return (1);	/* busy */
+complete:
+	return (0);	/* complete */
+}
+
+static uint8_t
+saf1761_host_bulk_data_tx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
+{
+	uint32_t count;
+
+	if (td->channel < SOTG_HOST_CHANNEL_MAX) {
+		uint32_t status;
+
+		status = SAF1761_READ_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 3));
+		if (status & (1 << 31)) {
+			goto busy;
+		} else if (status & (1 << 30)) {
+			td->error_stall = 1;
+			td->error_any = 1;
+		} else if (status & (3 << 28)) {
+			td->error_any = 1;
+		}
+
+		saf1761_host_channel_free(sc, td);
+
+		/* check remainder */
+		if (td->remainder == 0) {
+			if (td->short_pkt)
+				goto complete;
+			/* else we need to transmit a short packet */
+		}
+	}
+	if (saf1761_host_channel_alloc(sc, td))
+		goto busy;
+
+	count = td->max_packet_size;
+	if (td->remainder < count) {
+		/* we have a short packet */
+		td->short_pkt = 1;
+		count = td->remainder;
+	}
+
+	usbd_copy_out(td->pc, td->offset, sc->sc_bounce_buffer, count);
+	saf1761_write_host_fifo_1(sc, td, sc->sc_bounce_buffer, count);
+
+	/* set toggle, if any */
+	if (td->set_toggle) {
+		td->set_toggle = 0;
+		td->toggle = 1;
+	}
+
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 7), 0);
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 6), 0);
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 5), 0);
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 4), 0);
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 3),
+	    (1 << 31) | (td->toggle << 25) | (3 << 23));
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 2),
+	    SOTG_HC_MEMORY_ADDR(SOTG_DATA_ADDR(td->channel)) << 8);
+
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 1),
+	    td->dw1_value |
+	    (0 << 10) /* OUT-PID */ |
+	    (td->ep_index >> 1));
+
+	SAF1761_WRITE_4(sc, SOTG_ASYNC_PDT(td->channel) + (4 * 0),
+	    (td->ep_index << 31) |
+	    (1 << 29) /* pkt-multiplier */ |
+	    (td->max_packet_size << 18) /* wMaxPacketSize */ |
+	    (count << 3) /* transfer count */ |
+	    1 /* valid */);
+
+	td->offset += count;
+	td->remainder -= count;
+	td->toggle ^= 1;
+busy:
+	return (1);	/* busy */
+complete:
+	return (0);	/* complete */
+}
+
+static uint8_t
+saf1761_host_intr_data_rx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
+{
+	return (1);	/* busy */
+}
+
+static uint8_t
+saf1761_host_intr_data_tx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
+{
+	return (1);	/* busy */
+}
+
+static uint8_t
+saf1761_host_isoc_data_rx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
+{
+	return (1);	/* busy */
+}
+
+static uint8_t
+saf1761_host_isoc_data_tx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
+{
+	return (1);	/* busy */
+}
+
 static void
 saf1761_dci_set_address(struct saf1761_dci_softc *sc, uint8_t addr)
 {
@@ -206,21 +564,21 @@ saf1761_dci_set_address(struct saf1761_d
 }
 
 static void
-saf1761_read_fifo(struct saf1761_dci_softc *sc, void *buf, uint32_t len)
+saf1761_read_device_fifo_1(struct saf1761_dci_softc *sc, void *buf, uint32_t len)
 {
 	bus_space_read_multi_1((sc)->sc_io_tag, (sc)->sc_io_hdl,
 	    SOTG_DATA_PORT, buf, len);
 }
 
 static void
-saf1761_write_fifo(struct saf1761_dci_softc *sc, void *buf, uint32_t len)
+saf1761_write_device_fifo_1(struct saf1761_dci_softc *sc, void *buf, uint32_t len)
 {
 	bus_space_write_multi_1((sc)->sc_io_tag, (sc)->sc_io_hdl,
 	    SOTG_DATA_PORT, buf, len);
 }
 
 static uint8_t
-saf1761_dci_setup_rx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
+saf1761_device_setup_rx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
 {
 	struct usb_device_request req;
 	uint16_t count;
@@ -256,7 +614,7 @@ saf1761_dci_setup_rx(struct saf1761_dci_
 		goto busy;
 	}
 	/* receive data */
-	saf1761_read_fifo(sc, &req, sizeof(req));
+	saf1761_read_device_fifo_1(sc, &req, sizeof(req));
 
 	/* copy data into real buffer */
 	usbd_copy_in(td->pc, 0, &req, sizeof(req));
@@ -288,7 +646,7 @@ busy:
 }
 
 static uint8_t
-saf1761_dci_data_rx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
+saf1761_device_data_rx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
 {
 	struct usb_page_search buf_res;
 	uint16_t count;
@@ -314,7 +672,7 @@ saf1761_dci_data_rx(struct saf1761_dci_s
 			/*
 			 * USB Host Aborted the transfer.
 			 */
-			td->error = 1;
+			td->error_any = 1;
 			return (0);	/* complete */
 		}
 	}
@@ -323,6 +681,12 @@ saf1761_dci_data_rx(struct saf1761_dci_s
 	    (td->ep_index << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) |
 	    SOTG_EP_INDEX_DIR_OUT);
 
+	/* enable data stage */
+	if (td->set_toggle) {
+		td->set_toggle = 0;
+		SAF1761_WRITE_1(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_DSEN);
+	}
+
 	/* check buffer status */
 	if ((SAF1761_READ_1(sc, SOTG_DCBUFFERSTATUS) &
 	    SOTG_DCBUFFERSTATUS_FILLED_MASK) == 0) {
@@ -341,14 +705,14 @@ saf1761_dci_data_rx(struct saf1761_dci_s
 			got_short = 1;
 		} else {
 			/* invalid USB packet */
-			td->error = 1;
+			td->error_any = 1;
 			return (0);	/* we are complete */
 		}
 	}
 	/* verify the packet byte count */
 	if (count > td->remainder) {
 		/* invalid USB packet */
-		td->error = 1;
+		td->error_any = 1;
 		return (0);		/* we are complete */
 	}
 	while (count > 0) {
@@ -359,7 +723,7 @@ saf1761_dci_data_rx(struct saf1761_dci_s
 			buf_res.length = count;
 
 		/* receive data */
-		saf1761_read_fifo(sc, buf_res.buffer, buf_res.length);
+		saf1761_read_device_fifo_1(sc, buf_res.buffer, buf_res.length);
 
 		/* update counters */
 		count -= buf_res.length;
@@ -378,7 +742,7 @@ saf1761_dci_data_rx(struct saf1761_dci_s
 }
 
 static uint8_t
-saf1761_dci_data_tx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
+saf1761_device_data_tx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
 {
 	struct usb_page_search buf_res;
 	uint16_t count;
@@ -395,7 +759,7 @@ saf1761_dci_data_tx(struct saf1761_dci_s
 			/*
 			 * USB Host Aborted the transfer.
 			 */
-			td->error = 1;
+			td->error_any = 1;
 			return (0);	/* complete */
 		}
 	}
@@ -409,6 +773,13 @@ saf1761_dci_data_tx(struct saf1761_dci_s
 	    SOTG_DCBUFFERSTATUS_FILLED_MASK) != 0) {
 		return (1);		/* not complete */
 	}
+
+	/* enable data stage */
+	if (td->set_toggle) {
+		td->set_toggle = 0;
+		SAF1761_WRITE_1(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_DSEN);
+	}
+
 	DPRINTFN(5, "rem=%u\n", td->remainder);
 
 	count = td->max_packet_size;
@@ -428,7 +799,7 @@ saf1761_dci_data_tx(struct saf1761_dci_s
 			buf_res.length = count;
 
 		/* transmit data */
-		saf1761_write_fifo(sc, buf_res.buffer, buf_res.length);
+		saf1761_write_device_fifo_1(sc, buf_res.buffer, buf_res.length);
 
 		/* update counters */
 		count -= buf_res.length;
@@ -459,7 +830,7 @@ saf1761_dci_data_tx(struct saf1761_dci_s
 }
 
 static uint8_t
-saf1761_dci_data_tx_sync(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
+saf1761_device_data_tx_sync(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td)
 {
 	if (td->ep_index == 0) {
 		/* select the correct endpoint */
@@ -493,6 +864,7 @@ static uint8_t
 saf1761_dci_xfer_do_fifo(struct saf1761_dci_softc *sc, struct usb_xfer *xfer)
 {
 	struct saf1761_dci_td *td;
+	uint8_t toggle;
 
 	DPRINTFN(9, "\n");
 
@@ -505,7 +877,7 @@ saf1761_dci_xfer_do_fifo(struct saf1761_
 		if (((void *)td) == xfer->td_transfer_last) {
 			goto done;
 		}
-		if (td->error) {
+		if (td->error_any) {
 			goto done;
 		} else if (td->remainder > 0) {
 			/*
@@ -519,7 +891,9 @@ saf1761_dci_xfer_do_fifo(struct saf1761_
 		/*
 		 * Fetch the next transfer descriptor.
 		 */
+		toggle = td->toggle;
 		td = td->obj_next;
+		td->toggle = toggle;
 		xfer->td_transfer_cache = td;
 	}
 	return (1);			/* not complete */
@@ -683,10 +1057,13 @@ saf1761_dci_setup_standard_chain_sub(str
 	td->pc = temp->pc;
 	td->offset = temp->offset;
 	td->remainder = temp->len;
-	td->error = 0;
+	td->error_any = 0;
+	td->error_stall = 0;
+	td->set_toggle = 0;
 	td->did_stall = temp->did_stall;
 	td->short_pkt = temp->short_pkt;
 	td->alt_next = temp->setup_alt_next;
+	td->channel = SOTG_HOST_CHANNEL_MAX;
 }
 
 static void
@@ -697,6 +1074,9 @@ saf1761_dci_setup_standard_chain(struct 
 	struct saf1761_dci_td *td;
 	uint32_t x;
 	uint8_t ep_no;
+	uint8_t ep_type;
+	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),
@@ -717,15 +1097,22 @@ saf1761_dci_setup_standard_chain(struct 
 	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);
+
 	sc = SAF1761_DCI_BUS2SC(xfer->xroot->bus);
 	ep_no = (xfer->endpointno & UE_ADDR);
+	ep_type = (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE);
 
 	/* check if we should prepend a setup message */
 
 	if (xfer->flags_int.control_xfr) {
 		if (xfer->flags_int.control_hdr) {
 
-			temp.func = &saf1761_dci_setup_rx;
+			if (is_host)
+				temp.func = &saf1761_host_setup_tx;
+			else
+				temp.func = &saf1761_device_setup_rx;
+
 			temp.len = xfer->frlengths[0];
 			temp.pc = xfer->frbuffers + 0;
 			temp.short_pkt = temp.len ? 1 : 0;
@@ -744,14 +1131,39 @@ saf1761_dci_setup_standard_chain(struct 
 
 	if (x != xfer->nframes) {
 		if (xfer->endpointno & UE_DIR_IN) {
-			temp.func = &saf1761_dci_data_tx;
+			if (is_host) {
+				if (ep_type == UE_INTERRUPT)
+					temp.func = &saf1761_host_intr_data_rx;
+				else if (ep_type == UE_ISOCHRONOUS)
+					temp.func = &saf1761_host_isoc_data_rx;
+				else
+					temp.func = &saf1761_host_bulk_data_rx;
+				need_sync = 0;
+			} else {
+				temp.func = &saf1761_device_data_tx;
+				need_sync = 1;
+			}
 		} else {
-			temp.func = &saf1761_dci_data_rx;
+			if (is_host) {
+				if (ep_type == UE_INTERRUPT)
+					temp.func = &saf1761_host_intr_data_tx;
+				else if (ep_type == UE_ISOCHRONOUS)
+					temp.func = &saf1761_host_isoc_data_tx;
+				else
+					temp.func = &saf1761_host_bulk_data_tx;
+				need_sync = 0;
+			} else {
+				temp.func = &saf1761_device_data_rx;
+				need_sync = 0;
+			}
 		}
 
 		/* setup "pc" pointer */
 		temp.pc = xfer->frbuffers + x;
+	} else {
+		need_sync = 0;
 	}
+
 	while (x != xfer->nframes) {
 
 		/* DATA0 / DATA1 message */
@@ -794,8 +1206,6 @@ saf1761_dci_setup_standard_chain(struct 
 
 	/* check for control transfer */
 	if (xfer->flags_int.control_xfr) {
-		uint8_t need_sync;
-
 		/* always setup a valid "pc" pointer for status and sync */
 		temp.pc = xfer->frbuffers + 0;
 		temp.len = 0;
@@ -810,26 +1220,59 @@ saf1761_dci_setup_standard_chain(struct 
 			 * endpoint direction.
 			 */
 			if (xfer->endpointno & UE_DIR_IN) {
-				temp.func = &saf1761_dci_data_rx;
-				need_sync = 0;
+				if (is_host) {
+					temp.func = &saf1761_host_bulk_data_tx;
+					need_sync = 0;
+				} else {
+					temp.func = &saf1761_device_data_rx;
+					need_sync = 0;
+				}
 			} else {
-				temp.func = &saf1761_dci_data_tx;
-				need_sync = 1;
+				if (is_host) {
+					temp.func = &saf1761_host_bulk_data_rx;
+					need_sync = 0;
+				} else {
+					temp.func = &saf1761_device_data_tx;
+					need_sync = 1;
+				}
 			}
 			temp.len = 0;
 			temp.short_pkt = 0;
 
 			saf1761_dci_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 = &saf1761_dci_data_tx_sync;
+				temp.func = &saf1761_device_data_tx_sync;
 				saf1761_dci_setup_standard_chain_sub(&temp);
 			}
 		}
+	} else {
+		if (need_sync) {
+			temp.pc = xfer->frbuffers + 0;
+			temp.len = 0;
+			temp.short_pkt = 0;
+			temp.setup_alt_next = 0;
+
+			/* we need a SYNC point after TX */
+			temp.func = &saf1761_device_data_tx_sync;
+			saf1761_dci_setup_standard_chain_sub(&temp);
+		}
 	}
+
 	/* must have at least one frame! */
 	td = temp.td;
 	xfer->td_transfer_last = td;
+
+	if (is_host) {
+		/* get first again */
+		td = xfer->td_transfer_first;
+		td->toggle = (xfer->endpoint->toggle_next ? 1 : 0);
+	}
 }
 
 static void
@@ -919,7 +1362,7 @@ saf1761_dci_standard_done_sub(struct usb
 {
 	struct saf1761_dci_td *td;
 	uint32_t len;
-	uint8_t error;
+	usb_error_t error;
 
 	DPRINTFN(9, "\n");
 
@@ -928,21 +1371,25 @@ saf1761_dci_standard_done_sub(struct usb
 	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;
 		}
@@ -974,8 +1421,7 @@ saf1761_dci_standard_done_sub(struct usb
 
 	xfer->td_transfer_cache = td;
 
-	return (error ?
-	    USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION);
+	return (error);
 }
 
 static void
@@ -1030,13 +1476,23 @@ done:
 static void
 saf1761_dci_device_done(struct usb_xfer *xfer, usb_error_t error)
 {
+	struct saf1761_dci_softc *sc = SAF1761_DCI_BUS2SC(xfer->xroot->bus);
+
 	USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
 
 	DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n",
 	    xfer, xfer->endpoint, error);
 
-	if (xfer->flags_int.usb_mode == USB_MODE_DEVICE)
+	if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) {
 		saf1761_dci_intr_set(xfer, 0);
+	} else {
+		struct saf1761_dci_td *td;
+
+		td = xfer->td_transfer_first;
+
+		if (td != NULL)
+			saf1761_host_channel_free(sc, td);
+	}
 
 	/* dequeue transfer and start next transfer */
 	usbd_transfer_done(xfer, error);
@@ -1059,6 +1515,12 @@ saf1761_dci_set_stall(struct usb_device 
 
 	USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
 
+	/* check mode */
+	if (udev->flags.usb_mode != USB_MODE_DEVICE) {
+		/* not supported */
+		return;
+	}
+
 	DPRINTFN(5, "endpoint=%p\n", ep);
 
 	/* set FORCESTALL */
@@ -1138,7 +1600,7 @@ usb_error_t
 saf1761_dci_init(struct saf1761_dci_softc *sc)
 {
 	const struct usb_hw_ep_profile *pf;
-	uint8_t x;
+	uint32_t x;
 
 	DPRINTF("\n");
 
@@ -1161,13 +1623,26 @@ saf1761_dci_init(struct saf1761_dci_soft
 
 	DPRINTF("DCID=0x%08x\n", SAF1761_READ_4(sc, SOTG_DCCHIP_ID));
 
-	/* reset device */
+	/* reset device controller */
 	SAF1761_WRITE_2(sc, SOTG_MODE, SOTG_MODE_SFRESET);
 	SAF1761_WRITE_2(sc, SOTG_MODE, 0);
 
 	/* wait a bit */
 	DELAY(1000);
 
+	/* reset host controller */
+	SAF1761_WRITE_4(sc, SOTG_SW_RESET, SOTG_SW_RESET_HC);
+	SAF1761_WRITE_4(sc, SOTG_USBCMD, SOTG_USBCMD_HCRESET);
+
+	/* wait a bit */
+	DELAY(1000);
+
+	SAF1761_WRITE_4(sc, SOTG_SW_RESET, 0);
+	SAF1761_WRITE_4(sc, SOTG_USBCMD, 0);
+
+	/* wait a bit */
+	DELAY(1000);
+
 	/* do a pulldown */
 	saf1761_dci_pull_down(sc);
 
@@ -1225,6 +1700,22 @@ saf1761_dci_init(struct saf1761_dci_soft
 	/* disable device address */
 	SAF1761_WRITE_1(sc, SOTG_ADDRESS, 0);
 
+	/* enable host controller clock */
+	SAF1761_WRITE_4(sc, SOTG_POWER_DOWN, SOTG_POWER_DOWN_HC_CLK_EN);
+
+	/* wait 10ms for clock */
+	usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100);
+
+	/* enable configuration flag */
+	SAF1761_WRITE_4(sc, SOTG_CONFIGFLAG, SOTG_CONFIGFLAG_ENABLE);
+
+	/* clear RAM block */
+	for (x = 0x400; x != 0x10000; x += 4)
+		SAF1761_WRITE_4(sc, x, 0);
+
+	/* start the HC */
+	SAF1761_WRITE_4(sc, SOTG_USBCMD, SOTG_USBCMD_RS);
+
 	/* poll initial VBUS status */
 	saf1761_dci_update_vbus(sc);
 
@@ -1309,7 +1800,7 @@ saf1761_dci_device_non_isoc_start(struct
 	saf1761_dci_start_standard_chain(xfer);
 }
 
-static const struct usb_pipe_methods saf1761_dci_device_non_isoc_methods =
+static const struct usb_pipe_methods saf1761_otg_non_isoc_methods =
 {
 	.open = saf1761_dci_device_non_isoc_open,
 	.close = saf1761_dci_device_non_isoc_close,
@@ -1465,7 +1956,7 @@ static const struct saf1761_dci_config_d
 static const struct usb_hub_descriptor_min saf1761_dci_hubd = {
 	.bDescLength = sizeof(saf1761_dci_hubd),
 	.bDescriptorType = UDESC_HUB,
-	.bNbrPorts = 1,
+	.bNbrPorts = SOTG_NUM_PORTS,
 	HSETW(.wHubCharacteristics, (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL)),
 	.bPwrOn2PwrGood = 50,
 	.bHubContrCurrent = 0,
@@ -1490,6 +1981,8 @@ saf1761_dci_roothub_exec(struct usb_devi
 	uint16_t len;
 	uint16_t value;
 	uint16_t index;
+	uint32_t temp;
+	uint32_t i;
 	usb_error_t err;
 
 	USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
@@ -1620,9 +2113,19 @@ saf1761_dci_roothub_exec(struct usb_devi
 	case UT_WRITE_CLASS_OTHER:
 		switch (req->bRequest) {
 		case UR_CLEAR_FEATURE:
-			goto tr_handle_clear_port_feature;
+			if (index == SOTG_HOST_PORT_NUM)
+				goto tr_handle_clear_port_feature_host;
+			else if (index == SOTG_DEVICE_PORT_NUM)
+				goto tr_handle_clear_port_feature_device;
+			else
+				goto tr_stalled;
 		case UR_SET_FEATURE:
-			goto tr_handle_set_port_feature;
+			if (index == SOTG_HOST_PORT_NUM)
+				goto tr_handle_set_port_feature_host;
+			else if (index == SOTG_DEVICE_PORT_NUM)
+				goto tr_handle_set_port_feature_device;
+			else
+				goto tr_stalled;
 		case UR_CLEAR_TT_BUFFER:
 		case UR_RESET_TT:
 		case UR_STOP_TT:
@@ -1638,7 +2141,12 @@ saf1761_dci_roothub_exec(struct usb_devi
 		case UR_GET_TT_STATE:
 			goto tr_handle_get_tt_state;
 		case UR_GET_STATUS:
-			goto tr_handle_get_port_status;
+			if (index == SOTG_HOST_PORT_NUM)
+				goto tr_handle_get_port_status_host;
+			else if (index == SOTG_DEVICE_PORT_NUM)
+				goto tr_handle_get_port_status_device;
+			else
+				goto tr_stalled;
 		default:
 			goto tr_stalled;
 		}
@@ -1748,10 +2256,8 @@ tr_handle_clear_wakeup:
 tr_handle_clear_halt:
 	goto tr_valid;
 
-tr_handle_clear_port_feature:
-	if (index != 1)
-		goto tr_stalled;
-	DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index);
+tr_handle_clear_port_feature_device:
+	DPRINTFN(9, "UR_CLEAR_FEATURE on port %d\n", index);
 
 	switch (value) {
 	case UHF_PORT_SUSPEND:
@@ -1785,10 +2291,52 @@ tr_handle_clear_port_feature:
 	}
 	goto tr_valid;
 
-tr_handle_set_port_feature:
-	if (index != 1)
-		goto tr_stalled;
-	DPRINTFN(9, "UR_SET_PORT_FEATURE\n");
+tr_handle_clear_port_feature_host:
+	DPRINTFN(9, "UR_CLEAR_FEATURE on port %d\n", index);
+
+	temp = SAF1761_READ_4(sc, SOTG_PORTSC1);
+
+	switch (value) {
+	case UHF_PORT_ENABLE:
+		SAF1761_WRITE_4(sc, SOTG_PORTSC1, temp & ~SOTG_PORTSC1_PED);
+		break;
+	case UHF_PORT_SUSPEND:
+		if ((temp & SOTG_PORTSC1_SUSP) && (!(temp & SOTG_PORTSC1_FPR)))
+			SAF1761_WRITE_4(sc, SOTG_PORTSC1, temp | SOTG_PORTSC1_FPR);
+
+		/* wait 20ms for resume sequence to complete */
+		usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50);
+
+		SAF1761_WRITE_4(sc, SOTG_PORTSC1, temp & ~(SOTG_PORTSC1_SUSP |
+		    SOTG_PORTSC1_FPR | SOTG_PORTSC1_LS /* High Speed */ ));
+
+		/* 4ms settle time */
+		usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 250);
+		break;
+	case UHF_PORT_INDICATOR:
+		SAF1761_WRITE_4(sc, SOTG_PORTSC1, temp & ~SOTG_PORTSC1_PIC);
+		break;
+	case UHF_PORT_TEST:
+	case UHF_C_PORT_ENABLE:
+	case UHF_C_PORT_OVER_CURRENT:
+	case UHF_C_PORT_RESET:
+	case UHF_C_PORT_SUSPEND:
+		/* NOPs */
+		break;
+	case UHF_PORT_POWER:
+		SAF1761_WRITE_4(sc, SOTG_PORTSC1, temp & ~SOTG_PORTSC1_PP);
+		break;
+	case UHF_C_PORT_CONNECTION:
+		SAF1761_WRITE_4(sc, SOTG_PORTSC1, temp & ~SOTG_PORTSC1_ECSC);
+		break;
+	default:
+		err = USB_ERR_IOERROR;
+		goto tr_valid;
+	}
+	goto tr_valid;
+
+tr_handle_set_port_feature_device:
+	DPRINTFN(9, "UR_SET_FEATURE on port %d\n", index);
 
 	switch (value) {
 	case UHF_PORT_ENABLE:
@@ -1809,12 +2357,73 @@ tr_handle_set_port_feature:
 	}

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



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