From owner-svn-src-all@FreeBSD.ORG Wed May 14 17:04:03 2014 Return-Path: Delivered-To: svn-src-all@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits)) (No client certificate requested) by hub.freebsd.org (Postfix) with ESMTPS id F2C2B99; Wed, 14 May 2014 17:04:02 +0000 (UTC) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:1900:2254:2068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id D0F1423F8; Wed, 14 May 2014 17:04:02 +0000 (UTC) Received: from svn.freebsd.org ([127.0.1.70]) by svn.freebsd.org (8.14.8/8.14.8) with ESMTP id s4EH42B0038255; Wed, 14 May 2014 17:04:02 GMT (envelope-from hselasky@svn.freebsd.org) Received: (from hselasky@localhost) by svn.freebsd.org (8.14.8/8.14.8/Submit) id s4EH420q038253; Wed, 14 May 2014 17:04:02 GMT (envelope-from hselasky@svn.freebsd.org) Message-Id: <201405141704.s4EH420q038253@svn.freebsd.org> From: Hans Petter Selasky Date: Wed, 14 May 2014 17:04:02 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r266051 - head/sys/dev/usb/controller X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-all@freebsd.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: "SVN commit messages for the entire src tree \(except for " user" and " projects" \)" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 14 May 2014 17:04:03 -0000 Author: hselasky Date: Wed May 14 17:04:02 2014 New Revision: 266051 URL: http://svnweb.freebsd.org/changeset/base/266051 Log: Implement USB device side driver code for SAF1761 and compatible chips, based on datasheet and existing USS820 DCI driver. This code is not yet tested. Sponsored by: DARPA, AFRL Modified: head/sys/dev/usb/controller/saf1761_dci.c head/sys/dev/usb/controller/saf1761_dci.h head/sys/dev/usb/controller/saf1761_dci_fdt.c head/sys/dev/usb/controller/saf1761_dci_reg.h Modified: head/sys/dev/usb/controller/saf1761_dci.c ============================================================================== --- head/sys/dev/usb/controller/saf1761_dci.c Wed May 14 17:01:35 2014 (r266050) +++ head/sys/dev/usb/controller/saf1761_dci.c Wed May 14 17:04:02 2014 (r266051) @@ -1,6 +1,6 @@ /* $FreeBSD$ */ /*- - * Copyright (c) 2014 Hans Petter Selasky + * Copyright (c) 2014 Hans Petter Selasky * All rights reserved. * * This software was developed by SRI International and the University of @@ -29,5 +29,1958 @@ * SUCH DAMAGE. */ +/* + * This file contains the driver for the SAF1761 series USB OTG + * controller. + * + * Datasheet is available from: + * http://www.nxp.com/products/automotive/multimedia/usb/SAF1761BE.html + */ + +#ifdef USB_GLOBAL_INCLUDE_FILE +#include USB_GLOBAL_INCLUDE_FILE +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define USB_DEBUG_VAR saf1761_dci_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#endif /* USB_GLOBAL_INCLUDE_FILE */ + #include #include + +#define SAF1761_DCI_BUS2SC(bus) \ + ((struct saf1761_dci_softc *)(((uint8_t *)(bus)) - \ + ((uint8_t *)&(((struct saf1761_dci_softc *)0)->sc_bus)))) + +#ifdef USB_DEBUG +static int saf1761_dci_debug = 0; +static int saf1761_dci_forcefs = 0; + +static +SYSCTL_NODE(_hw_usb, OID_AUTO, saf1761_dci, CTLFLAG_RW, 0, + "USB SAF1761 DCI"); + +SYSCTL_INT(_hw_usb_saf1761_dci, OID_AUTO, debug, CTLFLAG_RW, + &saf1761_dci_debug, 0, "SAF1761 DCI debug level"); +SYSCTL_INT(_hw_usb_saf1761_dci, OID_AUTO, forcefs, CTLFLAG_RW, + &saf1761_dci_forcefs, 0, "SAF1761 DCI force FULL speed"); +#endif + +#define SAF1761_DCI_INTR_ENDPT 1 + +/* prototypes */ + +static const struct usb_bus_methods saf1761_dci_bus_methods; +static const struct usb_pipe_methods saf1761_dci_device_non_isoc_methods; +static const struct usb_pipe_methods saf1761_dci_device_isoc_methods; + +static saf1761_dci_cmd_t saf1761_dci_setup_rx; +static saf1761_dci_cmd_t saf1761_dci_data_rx; +static saf1761_dci_cmd_t saf1761_dci_data_tx; +static saf1761_dci_cmd_t saf1761_dci_data_tx_sync; +static void saf1761_dci_device_done(struct usb_xfer *, usb_error_t); +static void saf1761_dci_do_poll(struct usb_bus *); +static void saf1761_dci_standard_done(struct usb_xfer *); +static void saf1761_dci_intr_set(struct usb_xfer *, uint8_t); +static void saf1761_dci_root_intr(struct saf1761_dci_softc *); + +/* + * Here is a list of what the SAF1761 chip can support. The main + * limitation is that the sum of the buffer sizes must be less than + * 8192 bytes. + */ +static const struct usb_hw_ep_profile saf1761_dci_ep_profile[] = { + + [0] = { + .max_in_frame_size = 64, + .max_out_frame_size = 64, + .is_simplex = 0, + .support_control = 1, + }, + [1] = { + .max_in_frame_size = SOTG_HS_MAX_PACKET_SIZE, + .max_out_frame_size = SOTG_HS_MAX_PACKET_SIZE, + .is_simplex = 0, + .support_interrupt = 1, + .support_bulk = 1, + .support_isochronous = 1, + .support_in = 1, + .support_out = 1, + }, +}; + +static void +saf1761_dci_get_hw_ep_profile(struct usb_device *udev, + const struct usb_hw_ep_profile **ppf, uint8_t ep_addr) +{ + if (ep_addr == 0) { + *ppf = saf1761_dci_ep_profile + 0; + } else if (ep_addr < 8) { + *ppf = saf1761_dci_ep_profile + 1; + } else { + *ppf = NULL; + } +} + +static void +saf1761_dci_pull_up(struct saf1761_dci_softc *sc) +{ + /* activate pullup on D+, if possible */ + + if (!sc->sc_flags.d_pulled_up && sc->sc_flags.port_powered) { + DPRINTF("\n"); + + sc->sc_flags.d_pulled_up = 1; + + SAF1761_WRITE_2(sc, SOTG_CTRL_SET, SOTG_CTRL_DP_PULL_UP); + } +} + +static void +saf1761_dci_pull_down(struct saf1761_dci_softc *sc) +{ + /* release pullup on D+, if possible */ + + if (sc->sc_flags.d_pulled_up) { + DPRINTF("\n"); + + sc->sc_flags.d_pulled_up = 0; + + SAF1761_WRITE_2(sc, SOTG_CTRL_CLR, SOTG_CTRL_DP_PULL_UP); + } +} + +static void +saf1761_dci_wakeup_peer(struct saf1761_dci_softc *sc) +{ + uint16_t temp; + + if (!(sc->sc_flags.status_suspend)) + return; + + DPRINTFN(5, "\n"); + + temp = SAF1761_READ_2(sc, SOTG_MODE); + SAF1761_WRITE_2(sc, SOTG_MODE, temp | SOTG_MODE_SNDRSU); + SAF1761_WRITE_2(sc, SOTG_MODE, temp & ~SOTG_MODE_SNDRSU); + + /* Wait 8ms for remote wakeup to complete. */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125); + +} + +static void +saf1761_dci_set_address(struct saf1761_dci_softc *sc, uint8_t addr) +{ + DPRINTFN(5, "addr=%d\n", addr); + + SAF1761_WRITE_1(sc, SOTG_ADDRESS, addr | SOTG_ADDRESS_ENABLE); +} + +static void +saf1761_read_fifo(struct saf1761_dci_softc *sc, void *buf, uint32_t len) +{ + bus_space_read_multi_1((sc)->sc_io_tag, (sc)->sc_io_hdl, SOTG_DATA_PORT, buf, len); +} + +static void +saf1761_write_fifo(struct saf1761_dci_softc *sc, void *buf, uint32_t len) +{ + bus_space_write_multi_1((sc)->sc_io_tag, (sc)->sc_io_hdl, SOTG_DATA_PORT, buf, len); +} + +static uint8_t +saf1761_dci_setup_rx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td) +{ + struct usb_device_request req; + uint16_t count; + + /* select the correct endpoint */ + SAF1761_WRITE_1(sc, SOTG_EP_INDEX, SOTG_EP_INDEX_EP0SETUP); + + /* check buffer status */ + if ((SAF1761_READ_1(sc, SOTG_DCBUFFERSTATUS) & + SOTG_DCBUFFERSTATUS_FILLED_MASK) == 0) + goto busy; + + /* read buffer length */ + count = SAF1761_READ_2(sc, SOTG_BUF_LENGTH); + + DPRINTFN(5, "count=%u rem=%u\n", count, td->remainder); + + /* clear did stall */ + td->did_stall = 0; + + /* clear stall */ + SAF1761_WRITE_1(sc, SOTG_CTRL_FUNC, 0); + + /* verify data length */ + if (count != td->remainder) { + DPRINTFN(0, "Invalid SETUP packet " + "length, %d bytes\n", count); + goto busy; + } + if (count != sizeof(req)) { + DPRINTFN(0, "Unsupported SETUP packet " + "length, %d bytes\n", count); + goto busy; + } + /* receive data */ + saf1761_read_fifo(sc, &req, sizeof(req)); + + /* copy data into real buffer */ + usbd_copy_in(td->pc, 0, &req, sizeof(req)); + + td->offset = sizeof(req); + td->remainder = 0; + + /* sneak peek the set address */ + if ((req.bmRequestType == UT_WRITE_DEVICE) && + (req.bRequest == UR_SET_ADDRESS)) { + sc->sc_dv_addr = req.wValue[0] & 0x7F; + } else { + sc->sc_dv_addr = 0xFF; + } + return (0); /* complete */ + +busy: + /* abort any ongoing transfer */ + if (!td->did_stall) { + DPRINTFN(5, "stalling\n"); + + /* set stall */ + SAF1761_WRITE_1(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_STALL); + + td->did_stall = 1; + } + return (1); /* not complete */ +} + +static uint8_t +saf1761_dci_data_rx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td) +{ + struct usb_page_search buf_res; + uint16_t count; + uint8_t got_short = 0; + + if (td->ep_index == 0) { + /* select the correct endpoint */ + SAF1761_WRITE_1(sc, SOTG_EP_INDEX, SOTG_EP_INDEX_EP0SETUP); + + /* check buffer status */ + if ((SAF1761_READ_1(sc, SOTG_DCBUFFERSTATUS) & + SOTG_DCBUFFERSTATUS_FILLED_MASK) != 0) { + + if (td->remainder == 0) { + /* + * We are actually complete and have + * received the next SETUP: + */ + DPRINTFN(5, "faking complete\n"); + return (0); /* complete */ + } + DPRINTFN(5, "SETUP packet while receiving data\n"); + /* + * USB Host Aborted the transfer. + */ + td->error = 1; + return (0); /* complete */ + } + } + /* select the correct endpoint */ + SAF1761_WRITE_1(sc, SOTG_EP_INDEX, + (td->ep_index << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) | + SOTG_EP_INDEX_DIR_OUT); + + /* check buffer status */ + if ((SAF1761_READ_1(sc, SOTG_DCBUFFERSTATUS) & + SOTG_DCBUFFERSTATUS_FILLED_MASK) == 0) { + return (1); /* not complete */ + } + /* read buffer length */ + count = SAF1761_READ_2(sc, SOTG_BUF_LENGTH); + + DPRINTFN(5, "rem=%u count=0x%04x\n", td->remainder, count); + + /* verify the packet byte count */ + if (count != td->max_packet_size) { + if (count < td->max_packet_size) { + /* we have a short packet */ + td->short_pkt = 1; + got_short = 1; + } else { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + } + /* verify the packet byte count */ + if (count > td->remainder) { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + while (count > 0) { + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) + buf_res.length = count; + + /* receive data */ + saf1761_read_fifo(sc, buf_res.buffer, buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + /* check if we are complete */ + if ((td->remainder == 0) || got_short) { + if (td->short_pkt) { + /* we are complete */ + return (0); + } + /* else need to receive a zero length packet */ + } + return (1); /* not complete */ +} + +static uint8_t +saf1761_dci_data_tx(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td) +{ + struct usb_page_search buf_res; + uint16_t count; + uint16_t count_old; + + if (td->ep_index == 0) { + /* select the correct endpoint */ + SAF1761_WRITE_1(sc, SOTG_EP_INDEX, SOTG_EP_INDEX_EP0SETUP); + + /* check buffer status */ + if ((SAF1761_READ_1(sc, SOTG_DCBUFFERSTATUS) & + SOTG_DCBUFFERSTATUS_FILLED_MASK) != 0) { + DPRINTFN(5, "SETUP abort\n"); + /* + * USB Host Aborted the transfer. + */ + td->error = 1; + return (0); /* complete */ + } + } + /* select the correct endpoint */ + SAF1761_WRITE_1(sc, SOTG_EP_INDEX, + (td->ep_index << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) | + SOTG_EP_INDEX_DIR_IN); + + /* check buffer status */ + if ((SAF1761_READ_1(sc, SOTG_DCBUFFERSTATUS) & + SOTG_DCBUFFERSTATUS_FILLED_MASK) != 0) { + return (1); /* not complete */ + } + DPRINTFN(5, "rem=%u\n", td->remainder); + + count = td->max_packet_size; + if (td->remainder < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = td->remainder; + } + count_old = count; + + while (count > 0) { + + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) + buf_res.length = count; + + /* transmit data */ + saf1761_write_fifo(sc, buf_res.buffer, buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + if (td->ep_index == 0) { + if (count_old < SOTG_FS_MAX_PACKET_SIZE) { + /* set end of packet */ + SAF1761_WRITE_1(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_VENDP); + } + } else { + if (count_old < SOTG_HS_MAX_PACKET_SIZE) { + /* set end of packet */ + SAF1761_WRITE_1(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_VENDP); + } + } + + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) { + return (0); /* complete */ + } + /* else we need to transmit a short packet */ + } + return (1); /* not complete */ +} + +static uint8_t +saf1761_dci_data_tx_sync(struct saf1761_dci_softc *sc, struct saf1761_dci_td *td) +{ + if (td->ep_index == 0) { + /* select the correct endpoint */ + SAF1761_WRITE_1(sc, SOTG_EP_INDEX, SOTG_EP_INDEX_EP0SETUP); + + /* check buffer status */ + if ((SAF1761_READ_1(sc, SOTG_DCBUFFERSTATUS) & + SOTG_DCBUFFERSTATUS_FILLED_MASK) != 0) { + DPRINTFN(5, "Faking complete\n"); + return (0); /* complete */ + } + } + /* select the correct endpoint */ + SAF1761_WRITE_1(sc, SOTG_EP_INDEX, + (td->ep_index << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) | + SOTG_EP_INDEX_DIR_IN); + + /* check buffer status */ + if ((SAF1761_READ_1(sc, SOTG_DCBUFFERSTATUS) & + SOTG_DCBUFFERSTATUS_FILLED_MASK) != 0) + return (1); /* busy */ + + if (sc->sc_dv_addr != 0xFF) { + /* write function address */ + saf1761_dci_set_address(sc, sc->sc_dv_addr); + } + return (0); /* complete */ +} + +static uint8_t +saf1761_dci_xfer_do_fifo(struct saf1761_dci_softc *sc, struct usb_xfer *xfer) +{ + struct saf1761_dci_td *td; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + while (1) { + if ((td->func) (sc, td)) { + /* operation in progress */ + break; + } + if (((void *)td) == xfer->td_transfer_last) { + goto done; + } + if (td->error) { + goto done; + } else if (td->remainder > 0) { + /* + * We had a short transfer. If there is no alternate + * next, stop processing ! + */ + if (!td->alt_next) { + goto done; + } + } + /* + * Fetch the next transfer descriptor. + */ + td = td->obj_next; + xfer->td_transfer_cache = td; + } + return (1); /* not complete */ + +done: + /* compute all actual lengths */ + + saf1761_dci_standard_done(xfer); + + return (0); /* complete */ +} + +static void +saf1761_dci_interrupt_poll(struct saf1761_dci_softc *sc) +{ + struct usb_xfer *xfer; + +repeat: + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + if (!saf1761_dci_xfer_do_fifo(sc, xfer)) { + /* queue has been modified */ + goto repeat; + } + } +} + +static void +saf1761_dci_wait_suspend(struct saf1761_dci_softc *sc, uint8_t on) +{ + if (on) { + sc->sc_intr_enable |= SOTG_DCINTERRUPT_IESUSP; + sc->sc_intr_enable &= ~SOTG_DCINTERRUPT_IERESM; + } else { + sc->sc_intr_enable &= ~SOTG_DCINTERRUPT_IESUSP; + sc->sc_intr_enable |= SOTG_DCINTERRUPT_IERESM; + } + SAF1761_WRITE_4(sc, SOTG_DCINTERRUPT_EN, sc->sc_intr_enable); +} + +static void +saf1761_dci_update_vbus(struct saf1761_dci_softc *sc) +{ + if (SAF1761_READ_4(sc, SOTG_MODE) & SOTG_MODE_VBUSSTAT) { + DPRINTFN(4, "VBUS ON\n"); + + /* VBUS present */ + if (!sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 1; + + /* complete root HUB interrupt endpoint */ + saf1761_dci_root_intr(sc); + } + } else { + DPRINTFN(4, "VBUS OFF\n"); + + /* VBUS not-present */ + if (sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* complete root HUB interrupt endpoint */ + saf1761_dci_root_intr(sc); + } + } +} + +void +saf1761_dci_interrupt(struct saf1761_dci_softc *sc) +{ + uint32_t status; + + USB_BUS_LOCK(&sc->sc_bus); + + status = SAF1761_READ_4(sc, SOTG_DCINTERRUPT); + + /* acknowledge all interrupts */ + SAF1761_WRITE_4(sc, SOTG_DCINTERRUPT, status); + + if (status & SOTG_DCINTERRUPT_IEVBUS) { + /* update VBUS bit */ + saf1761_dci_update_vbus(sc); + } + if (status & SOTG_DCINTERRUPT_IEBRST) { + sc->sc_flags.status_bus_reset = 1; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* disable resume interrupt */ + saf1761_dci_wait_suspend(sc, 1); + /* complete root HUB interrupt endpoint */ + saf1761_dci_root_intr(sc); + } + /* + * If "RESUME" and "SUSPEND" is set at the same time we + * interpret that like "RESUME". Resume is set when there is + * at least 3 milliseconds of inactivity on the USB BUS: + */ + if (status & SOTG_DCINTERRUPT_IERESM) { + if (sc->sc_flags.status_suspend) { + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 1; + /* disable resume interrupt */ + saf1761_dci_wait_suspend(sc, 1); + /* complete root HUB interrupt endpoint */ + saf1761_dci_root_intr(sc); + } + } else if (status & SOTG_DCINTERRUPT_IESUSP) { + if (!sc->sc_flags.status_suspend) { + sc->sc_flags.status_suspend = 1; + sc->sc_flags.change_suspend = 1; + /* enable resume interrupt */ + saf1761_dci_wait_suspend(sc, 0); + /* complete root HUB interrupt endpoint */ + saf1761_dci_root_intr(sc); + } + } + /* poll all active transfers */ + saf1761_dci_interrupt_poll(sc); + + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +saf1761_dci_setup_standard_chain_sub(struct saf1761_dci_std_temp *temp) +{ + struct saf1761_dci_td *td; + + /* get current Transfer Descriptor */ + td = temp->td_next; + temp->td = td; + + /* prepare for next TD */ + temp->td_next = td->obj_next; + + /* fill out the Transfer Descriptor */ + td->func = temp->func; + td->pc = temp->pc; + td->offset = temp->offset; + td->remainder = temp->len; + td->error = 0; + td->did_stall = temp->did_stall; + td->short_pkt = temp->short_pkt; + td->alt_next = temp->setup_alt_next; +} + +static void +saf1761_dci_setup_standard_chain(struct usb_xfer *xfer) +{ + struct saf1761_dci_std_temp temp; + struct saf1761_dci_softc *sc; + struct saf1761_dci_td *td; + uint32_t x; + uint8_t ep_no; + + DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpointno), + xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); + + temp.max_frame_size = xfer->max_frame_size; + + td = xfer->td_start[0]; + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; + + /* setup temp */ + + temp.pc = NULL; + temp.td = NULL; + temp.td_next = xfer->td_start[0]; + temp.offset = 0; + temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.did_stall = !xfer->flags_int.control_stall; + + sc = SAF1761_DCI_BUS2SC(xfer->xroot->bus); + ep_no = (xfer->endpointno & UE_ADDR); + + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + temp.func = &saf1761_dci_setup_rx; + temp.len = xfer->frlengths[0]; + temp.pc = xfer->frbuffers + 0; + temp.short_pkt = temp.len ? 1 : 0; + /* check for last frame */ + if (xfer->nframes == 1) { + /* no STATUS stage yet, SETUP is last */ + if (xfer->flags_int.control_act) + temp.setup_alt_next = 0; + } + saf1761_dci_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + if (x != xfer->nframes) { + if (xfer->endpointno & UE_DIR_IN) { + temp.func = &saf1761_dci_data_tx; + } else { + temp.func = &saf1761_dci_data_rx; + } + + /* setup "pc" pointer */ + temp.pc = xfer->frbuffers + x; + } + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + + x++; + + if (x == xfer->nframes) { + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_act) { + temp.setup_alt_next = 0; + } + } else { + temp.setup_alt_next = 0; + } + } + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.short_pkt = 0; + + } else { + + /* regular data transfer */ + + temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1; + } + + saf1761_dci_setup_standard_chain_sub(&temp); + + if (xfer->flags_int.isochronous_xfr) { + temp.offset += temp.len; + } else { + /* get next Page Cache pointer */ + temp.pc = xfer->frbuffers + x; + } + } + + /* check for control transfer */ + if (xfer->flags_int.control_xfr) { + uint8_t need_sync; + + /* always setup a valid "pc" pointer for status and sync */ + temp.pc = xfer->frbuffers + 0; + temp.len = 0; + temp.short_pkt = 0; + temp.setup_alt_next = 0; + + /* check if we should append a status stage */ + if (!xfer->flags_int.control_act) { + + /* + * Send a DATA1 message and invert the current + * endpoint direction. + */ + if (xfer->endpointno & UE_DIR_IN) { + temp.func = &saf1761_dci_data_rx; + need_sync = 0; + } else { + temp.func = &saf1761_dci_data_tx; + need_sync = 1; + } + temp.len = 0; + temp.short_pkt = 0; + + saf1761_dci_setup_standard_chain_sub(&temp); + if (need_sync) { + /* we need a SYNC point after TX */ + temp.func = &saf1761_dci_data_tx_sync; + saf1761_dci_setup_standard_chain_sub(&temp); + } + } + } + /* must have at least one frame! */ + td = temp.td; + xfer->td_transfer_last = td; +} + +static void +saf1761_dci_timeout(void *arg) +{ + struct usb_xfer *xfer = arg; + + DPRINTF("xfer=%p\n", xfer); + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + /* transfer is transferred */ + saf1761_dci_device_done(xfer, USB_ERR_TIMEOUT); +} + +static void +saf1761_dci_intr_set(struct usb_xfer *xfer, uint8_t set) +{ + struct saf1761_dci_softc *sc = SAF1761_DCI_BUS2SC(xfer->xroot->bus); + uint8_t ep_no = (xfer->endpointno & UE_ADDR); + uint32_t mask; + + DPRINTFN(15, "endpoint 0x%02x\n", xfer->endpointno); + + if (ep_no == 0) { + mask = SOTG_DCINTERRUPT_IEPRX(0) | + SOTG_DCINTERRUPT_IEPTX(0) | + SOTG_DCINTERRUPT_IEP0SETUP; + } else if (xfer->endpointno & UE_DIR_IN) { + mask = SOTG_DCINTERRUPT_IEPTX(ep_no); + } else { + mask = SOTG_DCINTERRUPT_IEPRX(ep_no); + } + + if (set) + sc->sc_intr_enable |= mask; + else + sc->sc_intr_enable &= ~mask; + + SAF1761_WRITE_4(sc, SOTG_DCINTERRUPT_EN, sc->sc_intr_enable); +} + +static void +saf1761_dci_start_standard_chain(struct usb_xfer *xfer) +{ + struct saf1761_dci_softc *sc = SAF1761_DCI_BUS2SC(xfer->xroot->bus); + + DPRINTFN(9, "\n"); + + /* poll one time */ + if (saf1761_dci_xfer_do_fifo(sc, xfer)) { + + /* + * Only enable the endpoint interrupt when we are + * actually waiting for data, hence we are dealing + * with level triggered interrupts ! + */ + saf1761_dci_intr_set(xfer, 1); + + /* put transfer on interrupt queue */ + usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usbd_transfer_timeout_ms(xfer, + &saf1761_dci_timeout, xfer->timeout); + } + } +} + +static void +saf1761_dci_root_intr(struct saf1761_dci_softc *sc) +{ + DPRINTFN(9, "\n"); + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* set port bit - we only have one port */ + sc->sc_hub_idata[0] = 0x02; + + uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, + sizeof(sc->sc_hub_idata)); +} + +static usb_error_t +saf1761_dci_standard_done_sub(struct usb_xfer *xfer) +{ + struct saf1761_dci_td *td; + uint32_t len; + uint8_t error; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + + do { + len = td->remainder; + + if (xfer->aframes != xfer->nframes) { + /* + * Verify the length and subtract + * the remainder from "frlengths[]": + */ + if (len > xfer->frlengths[xfer->aframes]) { + td->error = 1; + } else { + xfer->frlengths[xfer->aframes] -= len; + } + } + /* Check for transfer error */ + if (td->error) { + /* the transfer is finished */ + error = 1; + td = NULL; + break; + } + /* Check for short transfer */ + if (len > 0) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + if (td->alt_next) { + td = td->obj_next; + } else { + td = NULL; + } + } else { + /* the transfer is finished */ + td = NULL; + } + error = 0; + break; + } + td = td->obj_next; + + /* this USB frame is complete */ + error = 0; + break; + + } while (0); + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + return (error ? + USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); +} + +static void +saf1761_dci_standard_done(struct usb_xfer *xfer) +{ + usb_error_t err = 0; + + DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", + xfer, xfer->endpoint); + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + err = saf1761_dci_standard_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = saf1761_dci_standard_done_sub(xfer); + xfer->aframes++; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + err = saf1761_dci_standard_done_sub(xfer); + } +done: + saf1761_dci_device_done(xfer, err); +} + +/*------------------------------------------------------------------------* + * saf1761_dci_device_done + * + * NOTE: this function can be called more than one time on the + * same USB transfer! + *------------------------------------------------------------------------*/ +static void +saf1761_dci_device_done(struct usb_xfer *xfer, usb_error_t error) +{ + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", + xfer, xfer->endpoint, error); *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***