From c5a141a78655017f94642e5781d79cc5978e46ab Mon Sep 17 00:00:00 2001 From: Sebastian Huber Date: Thu, 12 Nov 2015 13:01:25 +0100 Subject: dwc_otg: Import from FreeBSD trunk 2015-11-10 --- freebsd/sys/dev/usb/controller/dwc_otg.c | 4969 +++++++++++++++++++++++++++ freebsd/sys/dev/usb/controller/dwc_otg.h | 218 ++ freebsd/sys/dev/usb/controller/dwc_otgreg.h | 802 +++++ 3 files changed, 5989 insertions(+) create mode 100644 freebsd/sys/dev/usb/controller/dwc_otg.c create mode 100644 freebsd/sys/dev/usb/controller/dwc_otg.h create mode 100644 freebsd/sys/dev/usb/controller/dwc_otgreg.h diff --git a/freebsd/sys/dev/usb/controller/dwc_otg.c b/freebsd/sys/dev/usb/controller/dwc_otg.c new file mode 100644 index 00000000..2110b94d --- /dev/null +++ b/freebsd/sys/dev/usb/controller/dwc_otg.c @@ -0,0 +1,4969 @@ +#include + +/* $FreeBSD$ */ +/*- + * Copyright (c) 2015 Daisuke Aoyama. All rights reserved. + * Copyright (c) 2012-2015 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 + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file contains the driver for the DesignWare series USB 2.0 OTG + * Controller. + */ + +/* + * LIMITATION: Drivers must be bound to all OUT endpoints in the + * active configuration for this driver to work properly. Blocking any + * OUT endpoint will block all OUT endpoints including the control + * endpoint. Usually this is not a problem. + */ + +/* + * NOTE: Writing to non-existing registers appears to cause an + * internal reset. + */ + +#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 dwc_otg_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#endif /* USB_GLOBAL_INCLUDE_FILE */ + +#include +#include + +#define DWC_OTG_BUS2SC(bus) \ + ((struct dwc_otg_softc *)(((uint8_t *)(bus)) - \ + ((uint8_t *)&(((struct dwc_otg_softc *)0)->sc_bus)))) + +#define DWC_OTG_PC2UDEV(pc) \ + (USB_DMATAG_TO_XROOT((pc)->tag_parent)->udev) + +#define DWC_OTG_MSK_GINT_ENABLED \ + (GINTMSK_ENUMDONEMSK | \ + GINTMSK_USBRSTMSK | \ + GINTMSK_USBSUSPMSK | \ + GINTMSK_IEPINTMSK | \ + GINTMSK_SESSREQINTMSK | \ + GINTMSK_RXFLVLMSK | \ + GINTMSK_HCHINTMSK | \ + GINTMSK_OTGINTMSK | \ + GINTMSK_PRTINTMSK) + +#define DWC_OTG_MSK_GINT_THREAD_IRQ \ + (GINTSTS_USBRST | GINTSTS_ENUMDONE | GINTSTS_PRTINT | \ + GINTSTS_WKUPINT | GINTSTS_USBSUSP | GINTMSK_OTGINTMSK | \ + GINTSTS_SESSREQINT) + +#define DWC_OTG_PHY_ULPI 0 +#define DWC_OTG_PHY_HSIC 1 +#define DWC_OTG_PHY_INTERNAL 2 + +#ifndef DWC_OTG_PHY_DEFAULT +#define DWC_OTG_PHY_DEFAULT DWC_OTG_PHY_ULPI +#endif + +static int dwc_otg_phy_type = DWC_OTG_PHY_DEFAULT; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, dwc_otg, CTLFLAG_RW, 0, "USB DWC OTG"); +SYSCTL_INT(_hw_usb_dwc_otg, OID_AUTO, phy_type, CTLFLAG_RDTUN, + &dwc_otg_phy_type, 0, "DWC OTG PHY TYPE - 0/1/2 - ULPI/HSIC/INTERNAL"); + +#ifdef USB_DEBUG +static int dwc_otg_debug; + +SYSCTL_INT(_hw_usb_dwc_otg, OID_AUTO, debug, CTLFLAG_RWTUN, + &dwc_otg_debug, 0, "DWC OTG debug level"); +#endif + +#define DWC_OTG_INTR_ENDPT 1 + +/* prototypes */ + +static const struct usb_bus_methods dwc_otg_bus_methods; +static const struct usb_pipe_methods dwc_otg_device_non_isoc_methods; +static const 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 *); +static void dwc_otg_interrupt_poll_locked(struct dwc_otg_softc *); + +/* + * Here is a configuration that the chip supports. + */ +static const struct usb_hw_ep_profile dwc_otg_ep_profile[1] = { + + [0] = { + .max_in_frame_size = 64,/* fixed */ + .max_out_frame_size = 64, /* fixed */ + .is_simplex = 1, + .support_control = 1, + } +}; + +static void +dwc_otg_get_hw_ep_profile(struct usb_device *udev, + const struct usb_hw_ep_profile **ppf, uint8_t ep_addr) +{ + struct dwc_otg_softc *sc; + + sc = DWC_OTG_BUS2SC(udev->bus); + + if (ep_addr < sc->sc_dev_ep_max) + *ppf = &sc->sc_hw_ep_profile[ep_addr].usb; + else + *ppf = NULL; +} + +static void +dwc_otg_write_fifo(struct dwc_otg_softc *sc, struct usb_page_cache *pc, + uint32_t offset, uint32_t fifo, uint32_t count) +{ + uint32_t temp; + + /* round down length to nearest 4-bytes */ + temp = count & ~3; + + /* check if we can write the data directly */ + if (temp != 0 && usb_pc_buffer_is_aligned(pc, offset, temp, 3)) { + struct usb_page_search buf_res; + + /* pre-subtract length */ + count -= temp; + + /* iterate buffer list */ + do { + /* get current buffer pointer */ + usbd_get_page(pc, offset, &buf_res); + + if (buf_res.length > temp) + buf_res.length = temp; + + /* transfer data into FIFO */ + bus_space_write_region_4(sc->sc_io_tag, sc->sc_io_hdl, + fifo, buf_res.buffer, buf_res.length / 4); + + offset += buf_res.length; + fifo += buf_res.length; + temp -= buf_res.length; + } while (temp != 0); + } + + /* check for remainder */ + if (count != 0) { + /* clear topmost word before copy */ + sc->sc_bounce_buffer[(count - 1) / 4] = 0; + + /* copy out data */ + usbd_copy_out(pc, offset, + sc->sc_bounce_buffer, count); + + /* transfer data into FIFO */ + bus_space_write_region_4(sc->sc_io_tag, + sc->sc_io_hdl, fifo, sc->sc_bounce_buffer, + (count + 3) / 4); + } +} + +static void +dwc_otg_read_fifo(struct dwc_otg_softc *sc, struct usb_page_cache *pc, + uint32_t offset, uint32_t count) +{ + uint32_t temp; + + /* round down length to nearest 4-bytes */ + temp = count & ~3; + + /* check if we can read the data directly */ + if (temp != 0 && usb_pc_buffer_is_aligned(pc, offset, temp, 3)) { + struct usb_page_search buf_res; + + /* pre-subtract length */ + count -= temp; + + /* iterate buffer list */ + do { + /* get current buffer pointer */ + usbd_get_page(pc, offset, &buf_res); + + if (buf_res.length > temp) + buf_res.length = temp; + + /* transfer data from FIFO */ + bus_space_read_region_4(sc->sc_io_tag, sc->sc_io_hdl, + sc->sc_current_rx_fifo, buf_res.buffer, buf_res.length / 4); + + offset += buf_res.length; + sc->sc_current_rx_fifo += buf_res.length; + sc->sc_current_rx_bytes -= buf_res.length; + temp -= buf_res.length; + } while (temp != 0); + } + + /* check for remainder */ + if (count != 0) { + /* read data into bounce buffer */ + bus_space_read_region_4(sc->sc_io_tag, sc->sc_io_hdl, + sc->sc_current_rx_fifo, + sc->sc_bounce_buffer, (count + 3) / 4); + + /* store data into proper buffer */ + usbd_copy_in(pc, offset, sc->sc_bounce_buffer, count); + + /* round length up to nearest 4 bytes */ + count = (count + 3) & ~3; + + /* update counters */ + sc->sc_current_rx_bytes -= count; + sc->sc_current_rx_fifo += count; + } +} + +static void +dwc_otg_tx_fifo_reset(struct dwc_otg_softc *sc, uint32_t value) +{ + uint32_t temp; + + /* reset FIFO */ + DWC_OTG_WRITE_4(sc, DOTG_GRSTCTL, value); + + /* wait for reset to complete */ + for (temp = 0; temp != 16; temp++) { + value = DWC_OTG_READ_4(sc, DOTG_GRSTCTL); + if (!(value & (GRSTCTL_TXFFLSH | GRSTCTL_RXFFLSH))) + break; + } +} + +static int +dwc_otg_init_fifo(struct dwc_otg_softc *sc, uint8_t mode) +{ + struct dwc_otg_profile *pf; + uint32_t fifo_size; + uint32_t fifo_regs; + uint32_t tx_start; + uint8_t x; + + fifo_size = sc->sc_fifo_size; + + /* + * NOTE: Reserved fixed size area at end of RAM, which must + * not be allocated to the FIFOs: + */ + fifo_regs = 4 * 16; + + if (fifo_size < fifo_regs) { + DPRINTF("Too little FIFO\n"); + return (EINVAL); + } + + /* subtract FIFO regs from total once */ + fifo_size -= fifo_regs; + + /* split equally for IN and OUT */ + fifo_size /= 2; + + /* Align to 4 bytes boundary (refer to PGM) */ + fifo_size &= ~3; + + /* set global receive FIFO size */ + DWC_OTG_WRITE_4(sc, DOTG_GRXFSIZ, fifo_size / 4); + + tx_start = fifo_size; + + if (fifo_size < 64) { + DPRINTFN(-1, "Not enough data space for EP0 FIFO.\n"); + return (EINVAL); + } + + if (mode == DWC_MODE_HOST) { + + /* reset active endpoints */ + sc->sc_active_rx_ep = 0; + + /* split equally for periodic and non-periodic */ + fifo_size /= 2; + + DPRINTF("PTX/NPTX FIFO=%u\n", fifo_size); + + /* align to 4 bytes boundary */ + fifo_size &= ~3; + + DWC_OTG_WRITE_4(sc, DOTG_GNPTXFSIZ, + ((fifo_size / 4) << 16) | + (tx_start / 4)); + + tx_start += fifo_size; + + for (x = 0; x != sc->sc_host_ch_max; x++) { + /* enable all host interrupts */ + DWC_OTG_WRITE_4(sc, DOTG_HCINTMSK(x), + HCINT_DEFAULT_MASK); + } + + DWC_OTG_WRITE_4(sc, DOTG_HPTXFSIZ, + ((fifo_size / 4) << 16) | + (tx_start / 4)); + + /* reset host channel state */ + memset(sc->sc_chan_state, 0, sizeof(sc->sc_chan_state)); + + /* enable all 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; + + pf->usb.max_out_frame_size = 1024 * 3; + pf->usb.is_simplex = 0; /* assume duplex */ + pf->usb.support_bulk = 1; + pf->usb.support_interrupt = 1; + pf->usb.support_isochronous = 1; + pf->usb.support_out = 1; + + if (x < sc->sc_dev_in_ep_max) { + uint32_t limit; + + limit = (x == 1) ? MIN(DWC_OTG_TX_MAX_FIFO_SIZE, + DWC_OTG_MAX_TXN) : MIN(DWC_OTG_MAX_TXN / 2, + DWC_OTG_TX_MAX_FIFO_SIZE); + + /* see if there is enough FIFO space */ + if (limit <= fifo_size) { + pf->max_buffer = limit; + pf->usb.support_in = 1; + } else { + limit = MIN(DWC_OTG_TX_MAX_FIFO_SIZE, 0x40); + if (limit <= fifo_size) { + pf->usb.support_in = 1; + } else { + pf->usb.is_simplex = 1; + limit = 0; + } + } + /* set FIFO size */ + DWC_OTG_WRITE_4(sc, DOTG_DIEPTXF(x), + ((limit / 4) << 16) | (tx_start / 4)); + tx_start += limit; + fifo_size -= limit; + pf->usb.max_in_frame_size = limit; + } else { + pf->usb.is_simplex = 1; + } + + DPRINTF("FIFO%d = IN:%d / OUT:%d\n", x, + pf->usb.max_in_frame_size, + pf->usb.max_out_frame_size); + } + } + + /* reset RX FIFO */ + dwc_otg_tx_fifo_reset(sc, GRSTCTL_RXFFLSH); + + if (mode != DWC_MODE_OTG) { + /* reset all TX FIFOs */ + dwc_otg_tx_fifo_reset(sc, + GRSTCTL_TXFIFO(0x10) | + GRSTCTL_TXFFLSH); + } else { + /* reset active endpoints */ + sc->sc_active_rx_ep = 0; + + /* reset host channel state */ + memset(sc->sc_chan_state, 0, sizeof(sc->sc_chan_state)); + } + return (0); +} + +static void +dwc_otg_update_host_frame_interval(struct dwc_otg_softc *sc) +{ + + /* + * Disabled until further. Assuming that the register is already + * programmed correctly by the boot loader. + */ +#if 0 + uint32_t temp; + + /* setup HOST frame interval register, based on existing value */ + temp = DWC_OTG_READ_4(sc, DOTG_HFIR) & HFIR_FRINT_MASK; + if (temp >= 10000) + temp /= 1000; + else + temp /= 125; + + /* figure out nearest X-tal value */ + if (temp >= 54) + temp = 60; /* MHz */ + else if (temp >= 39) + temp = 48; /* MHz */ + else + temp = 30; /* MHz */ + + if (sc->sc_flags.status_high_speed) + temp *= 125; + else + temp *= 1000; + + DPRINTF("HFIR=0x%08x\n", temp); + + DWC_OTG_WRITE_4(sc, DOTG_HFIR, temp); +#endif +} + +static void +dwc_otg_clocks_on(struct dwc_otg_softc *sc) +{ + if (sc->sc_flags.clocks_off && + sc->sc_flags.port_powered) { + + DPRINTFN(5, "\n"); + + /* TODO - platform specific */ + + sc->sc_flags.clocks_off = 0; + } +} + +static void +dwc_otg_clocks_off(struct dwc_otg_softc *sc) +{ + if (!sc->sc_flags.clocks_off) { + + DPRINTFN(5, "\n"); + + /* TODO - platform specific */ + + sc->sc_flags.clocks_off = 1; + } +} + +static void +dwc_otg_pull_up(struct dwc_otg_softc *sc) +{ + uint32_t temp; + + /* pullup D+, if possible */ + + if (!sc->sc_flags.d_pulled_up && + sc->sc_flags.port_powered) { + sc->sc_flags.d_pulled_up = 1; + + temp = DWC_OTG_READ_4(sc, DOTG_DCTL); + temp &= ~DCTL_SFTDISCON; + DWC_OTG_WRITE_4(sc, DOTG_DCTL, temp); + } +} + +static void +dwc_otg_pull_down(struct dwc_otg_softc *sc) +{ + uint32_t temp; + + /* pulldown D+, if possible */ + + if (sc->sc_flags.d_pulled_up) { + sc->sc_flags.d_pulled_up = 0; + + temp = DWC_OTG_READ_4(sc, DOTG_DCTL); + temp |= DCTL_SFTDISCON; + DWC_OTG_WRITE_4(sc, DOTG_DCTL, temp); + } +} + +static void +dwc_otg_enable_sof_irq(struct dwc_otg_softc *sc) +{ + /* In device mode we don't use the SOF interrupt */ + if (sc->sc_flags.status_device_mode != 0) + return; + /* Ensure the SOF interrupt is not disabled */ + sc->sc_needsof = 1; + /* Check if the SOF interrupt is already enabled */ + if ((sc->sc_irq_mask & GINTMSK_SOFMSK) != 0) + return; + sc->sc_irq_mask |= GINTMSK_SOFMSK; + DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); +} + +static void +dwc_otg_resume_irq(struct dwc_otg_softc *sc) +{ + if (sc->sc_flags.status_suspend) { + /* update status bits */ + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 1; + + if (sc->sc_flags.status_device_mode) { + /* + * Disable resume interrupt and enable suspend + * interrupt: + */ + sc->sc_irq_mask &= ~GINTMSK_WKUPINTMSK; + sc->sc_irq_mask |= GINTMSK_USBSUSPMSK; + 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_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 &= ~GINTMSK_USBSUSPMSK; + sc->sc_irq_mask |= GINTMSK_WKUPINTMSK; + 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"); + + 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); +} + +static void +dwc_otg_set_address(struct dwc_otg_softc *sc, uint8_t addr) +{ + uint32_t temp; + + DPRINTFN(5, "addr=%d\n", addr); + + temp = DWC_OTG_READ_4(sc, DOTG_DCFG); + temp &= ~DCFG_DEVADDR_SET(0x7F); + temp |= DCFG_DEVADDR_SET(addr); + DWC_OTG_WRITE_4(sc, DOTG_DCFG, temp); +} + +static void +dwc_otg_common_rx_ack(struct dwc_otg_softc *sc) +{ + DPRINTFN(5, "RX status clear\n"); + + /* enable RX FIFO level interrupt */ + sc->sc_irq_mask |= GINTMSK_RXFLVLMSK; + DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); + + if (sc->sc_current_rx_bytes != 0) { + /* need to dump remaining data */ + bus_space_read_region_4(sc->sc_io_tag, sc->sc_io_hdl, + sc->sc_current_rx_fifo, sc->sc_bounce_buffer, + sc->sc_current_rx_bytes / 4); + /* clear number of active bytes to receive */ + sc->sc_current_rx_bytes = 0; + } + /* clear cached status */ + sc->sc_last_rx_status = 0; +} + +static void +dwc_otg_clear_hcint(struct dwc_otg_softc *sc, uint8_t x) +{ + uint32_t hcint; + + /* clear all pending interrupts */ + 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_check_tx_fifo_empty(struct dwc_otg_softc *sc, struct dwc_otg_td *td) +{ + uint32_t temp; + + temp = DWC_OTG_READ_4(sc, DOTG_GINTSTS); + + if (td->ep_type == UE_ISOCHRONOUS) { + /* + * NOTE: USB INTERRUPT transactions are executed like + * USB CONTROL transactions! See the setup standard + * chain function for more information. + */ + if (!(temp & GINTSTS_PTXFEMP)) { + DPRINTF("Periodic TX FIFO is not empty\n"); + if (!(sc->sc_irq_mask & GINTMSK_PTXFEMPMSK)) { + sc->sc_irq_mask |= GINTMSK_PTXFEMPMSK; + DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); + } + return (1); /* busy */ + } + } else { + if (!(temp & GINTSTS_NPTXFEMP)) { + DPRINTF("Non-periodic TX FIFO is not empty\n"); + if (!(sc->sc_irq_mask & GINTMSK_NPTXFEMPMSK)) { + sc->sc_irq_mask |= GINTMSK_NPTXFEMPMSK; + DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); + } + return (1); /* busy */ + } + } + return (0); /* ready for transmit */ +} + +static uint8_t +dwc_otg_host_channel_alloc(struct dwc_otg_softc *sc, + struct dwc_otg_td *td, uint8_t is_out) +{ + uint8_t x; + uint8_t y; + uint8_t z; + + if (td->channel[0] < DWC_OTG_MAX_CHANNELS) + return (0); /* already allocated */ + + /* check if device is suspended */ + if (DWC_OTG_PC2UDEV(td->pc)->flags.self_suspended != 0) + return (1); /* busy - cannot transfer data */ + + /* compute needed TX FIFO size */ + if (is_out != 0) { + if (dwc_otg_host_check_tx_fifo_empty(sc, td) != 0) + return (1); /* busy - cannot transfer data */ + } + z = td->max_packet_count; + for (x = y = 0; x != sc->sc_host_ch_max; x++) { + /* check if channel is allocated */ + if (sc->sc_chan_state[x].allocated != 0) + continue; + /* check if channel is still enabled */ + if (sc->sc_chan_state[x].wait_halted != 0) + continue; + /* store channel number */ + td->channel[y++] = x; + /* check if we got all channels */ + if (y == z) + break; + } + if (y != z) { + /* reset channel variable */ + td->channel[0] = DWC_OTG_MAX_CHANNELS; + td->channel[1] = DWC_OTG_MAX_CHANNELS; + td->channel[2] = DWC_OTG_MAX_CHANNELS; + /* wait a bit */ + dwc_otg_enable_sof_irq(sc); + return (1); /* busy - not enough channels */ + } + + for (y = 0; y != z; y++) { + x = td->channel[y]; + + /* set allocated */ + sc->sc_chan_state[x].allocated = 1; + + /* set wait halted */ + sc->sc_chan_state[x].wait_halted = 1; + + /* clear interrupts */ + dwc_otg_clear_hcint(sc, x); + + DPRINTF("CH=%d HCCHAR=0x%08x " + "HCSPLT=0x%08x\n", x, td->hcchar, td->hcsplt); + + /* set active channel */ + sc->sc_active_rx_ep |= (1 << x); + } + return (0); /* allocated */ +} + +static void +dwc_otg_host_channel_free_sub(struct dwc_otg_softc *sc, struct dwc_otg_td *td, uint8_t index) +{ + uint32_t hcchar; + uint8_t x; + + if (td->channel[index] >= DWC_OTG_MAX_CHANNELS) + return; /* already freed */ + + /* free channel */ + x = td->channel[index]; + td->channel[index] = DWC_OTG_MAX_CHANNELS; + + DPRINTF("CH=%d\n", x); + + /* + * We need to let programmed host channels run till complete + * else the host channel will stop functioning. + */ + sc->sc_chan_state[x].allocated = 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); + + /* check if already halted */ + if (sc->sc_chan_state[x].wait_halted == 0) + return; + + /* disable host channel */ + hcchar = DWC_OTG_READ_4(sc, DOTG_HCCHAR(x)); + if (hcchar & HCCHAR_CHENA) { + DPRINTF("Halting channel %d\n", x); + DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(x), + hcchar | HCCHAR_CHDIS); + /* don't write HCCHAR until the channel is halted */ + } else { + sc->sc_chan_state[x].wait_halted = 0; + } +} + +static void +dwc_otg_host_channel_free(struct dwc_otg_softc *sc, struct dwc_otg_td *td) +{ + uint8_t x; + for (x = 0; x != td->max_packet_count; x++) + dwc_otg_host_channel_free_sub(sc, td, x); +} + +static void +dwc_otg_host_dump_rx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) +{ + uint8_t x; + /* dump any pending messages */ + if (sc->sc_last_rx_status == 0) + return; + for (x = 0; x != td->max_packet_count; x++) { + if (td->channel[x] >= DWC_OTG_MAX_CHANNELS || + td->channel[x] != GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status)) + continue; + dwc_otg_common_rx_ack(sc); + break; + } +} + +static uint8_t +dwc_otg_host_setup_tx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) +{ + struct usb_device_request req __aligned(4); + uint32_t hcint; + uint32_t hcchar; + uint8_t delta; + + dwc_otg_host_dump_rx(sc, td); + + if (td->channel[0] < DWC_OTG_MAX_CHANNELS) { + hcint = sc->sc_chan_state[td->channel[0]].hcint; + + DPRINTF("CH=%d ST=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n", + td->channel[0], td->state, hcint, + DWC_OTG_READ_4(sc, DOTG_HCCHAR(td->channel[0])), + DWC_OTG_READ_4(sc, DOTG_HCTSIZ(td->channel[0]))); + } else { + hcint = 0; + goto check_state; + } + + if (hcint & (HCINT_RETRY | + HCINT_ACK | HCINT_NYET)) { + /* give success bits priority over failure bits */ + } else if (hcint & HCINT_STALL) { + DPRINTF("CH=%d STALL\n", td->channel[0]); + td->error_stall = 1; + td->error_any = 1; + goto complete; + } else if (hcint & HCINT_ERRORS) { + DPRINTF("CH=%d ERROR\n", td->channel[0]); + td->errcnt++; + if (td->hcsplt != 0 || td->errcnt >= 3) { + td->error_any = 1; + goto complete; + } + } + + if (hcint & (HCINT_ERRORS | HCINT_RETRY | + HCINT_ACK | HCINT_NYET)) { + if (!(hcint & HCINT_ERRORS)) + td->errcnt = 0; + } + +check_state: + switch (td->state) { + case DWC_CHAN_ST_START: + goto send_pkt; + + case DWC_CHAN_ST_WAIT_ANE: + if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { + td->did_nak = 1; + td->tt_scheduled = 0; + goto send_pkt; + } else if (hcint & (HCINT_ACK | HCINT_NYET)) { + td->offset += td->tx_bytes; + td->remainder -= td->tx_bytes; + td->toggle = 1; + td->tt_scheduled = 0; + goto complete; + } + break; + + case DWC_CHAN_ST_WAIT_S_ANE: + if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { + td->did_nak = 1; + td->tt_scheduled = 0; + goto send_pkt; + } else if (hcint & (HCINT_ACK | HCINT_NYET)) { + goto send_cpkt; + } + break; + + case DWC_CHAN_ST_WAIT_C_ANE: + if (hcint & HCINT_NYET) { + goto send_cpkt; + } else if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { + td->did_nak = 1; + td->tt_scheduled = 0; + goto send_pkt; + } else if (hcint & HCINT_ACK) { + td->offset += td->tx_bytes; + td->remainder -= td->tx_bytes; + td->toggle = 1; + goto complete; + } + break; + + case DWC_CHAN_ST_WAIT_C_PKT: + goto send_cpkt; + + default: + break; + } + goto busy; + +send_pkt: + /* free existing channel, if any */ + dwc_otg_host_channel_free(sc, td); + + if (sizeof(req) != td->remainder) { + td->error_any = 1; + goto complete; + } + + if (td->hcsplt != 0) { + delta = td->tt_start_slot - sc->sc_last_frame_num - 1; + if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) { + td->state = DWC_CHAN_ST_START; + goto busy; + } + delta = sc->sc_last_frame_num - td->tt_start_slot; + if (delta > 5) { + /* missed it */ + td->tt_scheduled = 0; + td->state = DWC_CHAN_ST_START; + goto busy; + } + } + + /* allocate a new channel */ + if (dwc_otg_host_channel_alloc(sc, td, 1)) { + td->state = DWC_CHAN_ST_START; + 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; + } + + /* copy out control request */ + usbd_copy_out(td->pc, 0, &req, sizeof(req)); + + DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel[0]), + (sizeof(req) << HCTSIZ_XFERSIZE_SHIFT) | + (1 << HCTSIZ_PKTCNT_SHIFT) | + (HCTSIZ_PID_SETUP << HCTSIZ_PID_SHIFT)); + + DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(td->channel[0]), td->hcsplt); + + hcchar = td->hcchar; + hcchar &= ~(HCCHAR_EPDIR_IN | HCCHAR_EPTYPE_MASK); + hcchar |= UE_CONTROL << HCCHAR_EPTYPE_SHIFT; + + /* must enable channel before writing data to FIFO */ + DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel[0]), hcchar); + + /* transfer data into FIFO */ + bus_space_write_region_4(sc->sc_io_tag, sc->sc_io_hdl, + DOTG_DFIFO(td->channel[0]), (uint32_t *)&req, sizeof(req) / 4); + + /* wait until next slot before trying complete split */ + td->tt_complete_slot = sc->sc_last_frame_num + 1; + + /* store number of bytes transmitted */ + td->tx_bytes = sizeof(req); + goto busy; + +send_cpkt: + /* free existing channel, if any */ + dwc_otg_host_channel_free(sc, td); + + delta = td->tt_complete_slot - sc->sc_last_frame_num - 1; + if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) { + td->state = DWC_CHAN_ST_WAIT_C_PKT; + goto busy; + } + delta = sc->sc_last_frame_num - td->tt_start_slot; + if (delta > DWC_OTG_TT_SLOT_MAX) { + /* we missed the service interval */ + if (td->ep_type != UE_ISOCHRONOUS) + td->error_any = 1; + goto complete; + } + /* allocate a new channel */ + if (dwc_otg_host_channel_alloc(sc, td, 0)) { + td->state = DWC_CHAN_ST_WAIT_C_PKT; + goto busy; + } + + /* wait until next slot before trying complete split */ + td->tt_complete_slot = sc->sc_last_frame_num + 1; + + td->hcsplt |= HCSPLT_COMPSPLT; + td->state = DWC_CHAN_ST_WAIT_C_ANE; + + DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel[0]), + (HCTSIZ_PID_SETUP << HCTSIZ_PID_SHIFT)); + + DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(td->channel[0]), td->hcsplt); + + hcchar = td->hcchar; + hcchar &= ~(HCCHAR_EPDIR_IN | HCCHAR_EPTYPE_MASK); + hcchar |= UE_CONTROL << HCCHAR_EPTYPE_SHIFT; + + /* must enable channel before writing data to FIFO */ + DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel[0]), hcchar); + +busy: + return (1); /* busy */ + +complete: + dwc_otg_host_channel_free(sc, td); + return (0); /* complete */ +} + +static uint8_t +dwc_otg_setup_rx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) +{ + struct usb_device_request req __aligned(4); + uint32_t temp; + uint16_t count; + + /* check endpoint status */ + + if (sc->sc_last_rx_status == 0) + goto not_complete; + + if (GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status) != 0) + goto not_complete; + + if ((sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) != + GRXSTSRD_STP_DATA) { + if ((sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) != + GRXSTSRD_STP_COMPLETE || td->remainder != 0) { + /* release FIFO */ + dwc_otg_common_rx_ack(sc); + goto not_complete; + } + /* release FIFO */ + dwc_otg_common_rx_ack(sc); + return (0); /* complete */ + } + + if ((sc->sc_last_rx_status & GRXSTSRD_DPID_MASK) != + GRXSTSRD_DPID_DATA0) { + /* release FIFO */ + dwc_otg_common_rx_ack(sc); + goto not_complete; + } + + DPRINTFN(5, "GRXSTSR=0x%08x\n", sc->sc_last_rx_status); + + /* clear did stall */ + td->did_stall = 0; + + /* get the packet byte count */ + count = GRXSTSRD_BCNT_GET(sc->sc_last_rx_status); + + if (count != sizeof(req)) { + DPRINTFN(0, "Unsupported SETUP packet " + "length, %d bytes\n", count); + /* release FIFO */ + dwc_otg_common_rx_ack(sc); + goto not_complete; + } + + /* read FIFO */ + dwc_otg_read_fifo(sc, td->pc, 0, sizeof(req)); + + /* copy out control request */ + usbd_copy_out(td->pc, 0, &req, sizeof(req)); + + td->offset = sizeof(req); + td->remainder = 0; + + /* sneak peek the set address */ + if ((req.bmRequestType == UT_WRITE_DEVICE) && + (req.bRequest == UR_SET_ADDRESS)) { + /* must write address before ZLP */ + dwc_otg_set_address(sc, req.wValue[0] & 0x7F); + } + + /* don't send any data by default */ + DWC_OTG_WRITE_4(sc, DOTG_DIEPTSIZ(0), DIEPCTL_EPDIS); + DWC_OTG_WRITE_4(sc, DOTG_DOEPTSIZ(0), DOEPCTL_EPDIS); + + /* reset IN endpoint buffer */ + dwc_otg_tx_fifo_reset(sc, + GRSTCTL_TXFIFO(0) | + GRSTCTL_TXFFLSH); + + /* acknowledge RX status */ + dwc_otg_common_rx_ack(sc); + td->did_stall = 1; + +not_complete: + /* abort any ongoing transfer, before enabling again */ + if (!td->did_stall) { + td->did_stall = 1; + + DPRINTFN(5, "stalling IN and OUT direction\n"); + + temp = sc->sc_out_ctl[0]; + + /* set stall after enabling endpoint */ + DWC_OTG_WRITE_4(sc, DOTG_DOEPCTL(0), + temp | DOEPCTL_STALL); + + temp = sc->sc_in_ctl[0]; + + /* set stall assuming endpoint is enabled */ + DWC_OTG_WRITE_4(sc, DOTG_DIEPCTL(0), + temp | DIEPCTL_STALL); + } + return (1); /* not complete */ +} + +static uint8_t +dwc_otg_host_rate_check_interrupt(struct dwc_otg_softc *sc, struct dwc_otg_td *td) +{ + uint8_t delta; + + delta = sc->sc_tmr_val - td->tmr_val; + if (delta >= 128) + return (1); /* busy */ + + td->tmr_val = sc->sc_tmr_val + td->tmr_res; + + /* set toggle, if any */ + if (td->set_toggle) { + td->set_toggle = 0; + td->toggle = 1; + } + return (0); +} + +static uint8_t +dwc_otg_host_rate_check(struct dwc_otg_softc *sc, struct dwc_otg_td *td) +{ + uint8_t frame_num = (uint8_t)sc->sc_last_frame_num; + + if (td->ep_type == UE_ISOCHRONOUS) { + /* non TT isochronous traffic */ + if (frame_num & (td->tmr_res - 1)) + goto busy; + if ((frame_num ^ td->tmr_val) & td->tmr_res) + goto busy; + td->tmr_val = td->tmr_res + sc->sc_last_frame_num; + td->toggle = 0; + return (0); + } else if (td->ep_type == UE_INTERRUPT) { + if (!td->tt_scheduled) + goto busy; + td->tt_scheduled = 0; + return (0); + } else if (td->did_nak != 0) { + /* check if we should pause sending queries for 125us */ + if (td->tmr_res == frame_num) { + /* wait a bit */ + dwc_otg_enable_sof_irq(sc); + goto busy; + } + } else if (td->set_toggle) { + td->set_toggle = 0; + td->toggle = 1; + } + /* query for data one more time */ + td->tmr_res = frame_num; + td->did_nak = 0; + return (0); +busy: + return (1); +} + +static uint8_t +dwc_otg_host_data_rx_sub(struct dwc_otg_softc *sc, struct dwc_otg_td *td, + uint8_t channel) +{ + uint32_t count; + + /* check endpoint status */ + if (sc->sc_last_rx_status == 0) + goto busy; + + if (channel >= DWC_OTG_MAX_CHANNELS) + goto busy; + + if (GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status) != channel) + goto busy; + + switch (sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) { + case GRXSTSRH_IN_DATA: + + DPRINTF("DATA ST=%d STATUS=0x%08x\n", + (int)td->state, (int)sc->sc_last_rx_status); + + if (sc->sc_chan_state[channel].hcint & HCINT_SOFTWARE_ONLY) { + /* + * When using SPLIT transactions on interrupt + * endpoints, sometimes data occurs twice. + */ + DPRINTF("Data already received\n"); + break; + } + + /* get the packet byte count */ + count = GRXSTSRD_BCNT_GET(sc->sc_last_rx_status); + + /* check for ISOCHRONOUS endpoint */ + if (td->ep_type == UE_ISOCHRONOUS) { + if ((sc->sc_last_rx_status & GRXSTSRD_DPID_MASK) != + GRXSTSRD_DPID_DATA0) { + /* more data to be received */ + td->tt_xactpos = HCSPLT_XACTPOS_MIDDLE; + } else { + /* all data received */ + td->tt_xactpos = HCSPLT_XACTPOS_BEGIN; + /* verify the packet byte count */ + if (count != td->remainder) { + /* we have a short packet */ + td->short_pkt = 1; + td->got_short = 1; + } + } + } else { + /* 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); + goto complete; + } + } + td->toggle ^= 1; + td->tt_scheduled = 0; + } + + /* verify the packet byte count */ + if (count > td->remainder) { + /* invalid USB packet */ + td->error_any = 1; + + /* release FIFO */ + dwc_otg_common_rx_ack(sc); + goto complete; + } + + /* read data from FIFO */ + dwc_otg_read_fifo(sc, td->pc, td->offset, count); + + td->remainder -= count; + td->offset += count; + sc->sc_chan_state[channel].hcint |= HCINT_SOFTWARE_ONLY; + break; + default: + break; + } + /* release FIFO */ + dwc_otg_common_rx_ack(sc); +busy: + return (0); +complete: + return (1); +} + +static uint8_t +dwc_otg_host_data_rx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) +{ + uint32_t hcint = 0; + uint32_t hcchar; + uint8_t delta; + uint8_t channel; + uint8_t x; + + for (x = 0; x != td->max_packet_count; x++) { + channel = td->channel[x]; + if (channel >= DWC_OTG_MAX_CHANNELS) + continue; + hcint |= sc->sc_chan_state[channel].hcint; + + DPRINTF("CH=%d ST=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n", + channel, td->state, hcint, + DWC_OTG_READ_4(sc, DOTG_HCCHAR(channel)), + DWC_OTG_READ_4(sc, DOTG_HCTSIZ(channel))); + + /* check interrupt bits */ + if (hcint & (HCINT_RETRY | + HCINT_ACK | HCINT_NYET)) { + /* give success bits priority over failure bits */ + } else if (hcint & HCINT_STALL) { + DPRINTF("CH=%d STALL\n", channel); + td->error_stall = 1; + td->error_any = 1; + goto complete; + } else if (hcint & HCINT_ERRORS) { + DPRINTF("CH=%d ERROR\n", channel); + td->errcnt++; + if (td->hcsplt != 0 || td->errcnt >= 3) { + if (td->ep_type != UE_ISOCHRONOUS) { + td->error_any = 1; + goto complete; + } + } + } + + /* check channels for data, if any */ + if (dwc_otg_host_data_rx_sub(sc, td, channel)) + goto complete; + + /* refresh interrupt status */ + hcint |= sc->sc_chan_state[channel].hcint; + + if (hcint & (HCINT_ERRORS | HCINT_RETRY | + HCINT_ACK | HCINT_NYET)) { + if (!(hcint & HCINT_ERRORS)) + td->errcnt = 0; + } + } + + 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 (td->ep_type == UE_INTERRUPT) { + /* + * The USB specification does not + * mandate a particular data toggle + * value for USB INTERRUPT + * transfers. Switch the data toggle + * value to receive the packet + * correctly: + */ + if (hcint & HCINT_DATATGLERR) { + DPRINTF("Retrying packet due to " + "data toggle error\n"); + td->toggle ^= 1; + goto receive_pkt; + } + } else if (td->ep_type == UE_ISOCHRONOUS) { + goto complete; + } + td->did_nak = 1; + td->tt_scheduled = 0; + if (td->hcsplt != 0) + goto receive_spkt; + else + goto receive_pkt; + } else if (hcint & HCINT_NYET) { + if (td->hcsplt != 0) { + /* try again */ + goto receive_pkt; + } else { + /* not a valid token for IN endpoints */ + td->error_any = 1; + goto complete; + } + } else if (hcint & HCINT_ACK) { + /* wait for data - ACK arrived first */ + if (!(hcint & HCINT_SOFTWARE_ONLY)) + goto busy; + + if (td->ep_type == UE_ISOCHRONOUS) { + /* check if we are complete */ + if (td->tt_xactpos == HCSPLT_XACTPOS_BEGIN) { + goto complete; + } else { + /* get more packets */ + goto busy; + } + } else { + /* check if we are complete */ + if ((td->remainder == 0) || (td->got_short != 0)) { + if (td->short_pkt) + goto complete; + + /* + * Else need to receive a zero length + * packet. + */ + } + td->tt_scheduled = 0; + td->did_nak = 0; + if (td->hcsplt != 0) + goto receive_spkt; + else + goto receive_pkt; + } + } + break; + + case DWC_CHAN_ST_WAIT_S_ANE: + /* + * NOTE: The DWC OTG hardware provides a fake ACK in + * case of interrupt and isochronous transfers: + */ + if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { + td->did_nak = 1; + td->tt_scheduled = 0; + goto receive_spkt; + } else if (hcint & HCINT_NYET) { + td->tt_scheduled = 0; + goto receive_spkt; + } else if (hcint & HCINT_ACK) { + td->did_nak = 0; + goto receive_pkt; + } + break; + + case DWC_CHAN_ST_WAIT_C_PKT: + goto receive_pkt; + + default: + break; + } + goto busy; + +receive_pkt: + /* free existing channel, if any */ + dwc_otg_host_channel_free(sc, td); + + if (td->hcsplt != 0) { + delta = td->tt_complete_slot - sc->sc_last_frame_num - 1; + if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) { + td->state = DWC_CHAN_ST_WAIT_C_PKT; + goto busy; + } + delta = sc->sc_last_frame_num - td->tt_start_slot; + if (delta > DWC_OTG_TT_SLOT_MAX) { + if (td->ep_type != UE_ISOCHRONOUS) { + /* we missed the service interval */ + td->error_any = 1; + } + goto complete; + } + /* complete split */ + td->hcsplt |= HCSPLT_COMPSPLT; + } else if (dwc_otg_host_rate_check(sc, td)) { + td->state = DWC_CHAN_ST_WAIT_C_PKT; + goto busy; + } + + /* allocate a new channel */ + if (dwc_otg_host_channel_alloc(sc, td, 0)) { + td->state = DWC_CHAN_ST_WAIT_C_PKT; + goto busy; + } + + /* set toggle, if any */ + if (td->set_toggle) { + td->set_toggle = 0; + td->toggle = 1; + } + + td->state = DWC_CHAN_ST_WAIT_ANE; + + for (x = 0; x != td->max_packet_count; x++) { + channel = td->channel[x]; + + /* receive one packet */ + DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(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(channel), td->hcsplt); + + hcchar = td->hcchar; + hcchar |= HCCHAR_EPDIR_IN; + + /* receive complete split ASAP */ + if ((sc->sc_last_frame_num & 1) != 0 && + td->ep_type == UE_ISOCHRONOUS) + hcchar |= HCCHAR_ODDFRM; + else + hcchar &= ~HCCHAR_ODDFRM; + + /* must enable channel before data can be received */ + DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(channel), hcchar); + } + /* wait until next slot before trying complete split */ + td->tt_complete_slot = sc->sc_last_frame_num + 1; + + goto busy; + +receive_spkt: + /* free existing channel(s), if any */ + dwc_otg_host_channel_free(sc, td); + + delta = td->tt_start_slot - sc->sc_last_frame_num - 1; + if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) { + td->state = DWC_CHAN_ST_START; + goto busy; + } + delta = sc->sc_last_frame_num - td->tt_start_slot; + if (delta > 5) { + /* missed it */ + td->tt_scheduled = 0; + td->state = DWC_CHAN_ST_START; + goto busy; + } + + /* allocate a new channel */ + if (dwc_otg_host_channel_alloc(sc, td, 0)) { + td->state = DWC_CHAN_ST_START; + goto busy; + } + + channel = td->channel[0]; + + td->hcsplt &= ~HCSPLT_COMPSPLT; + td->state = DWC_CHAN_ST_WAIT_S_ANE; + + /* receive one packet */ + DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), + (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT)); + + DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(channel), td->hcsplt); + + /* send after next SOF event */ + if ((sc->sc_last_frame_num & 1) == 0 && + td->ep_type == UE_ISOCHRONOUS) + td->hcchar |= HCCHAR_ODDFRM; + else + td->hcchar &= ~HCCHAR_ODDFRM; + + hcchar = td->hcchar; + hcchar |= HCCHAR_EPDIR_IN; + + /* wait until next slot before trying complete split */ + td->tt_complete_slot = sc->sc_last_frame_num + 1; + + /* must enable channel before data can be received */ + DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(channel), hcchar); +busy: + return (1); /* busy */ + +complete: + dwc_otg_host_channel_free(sc, td); + return (0); /* complete */ +} + +static uint8_t +dwc_otg_data_rx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) +{ + uint32_t temp; + uint16_t count; + uint8_t got_short; + + got_short = 0; + + /* check endpoint status */ + if (sc->sc_last_rx_status == 0) + goto not_complete; + + if (GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status) != td->ep_no) + goto not_complete; + + /* check for SETUP packet */ + if ((sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) == + GRXSTSRD_STP_DATA || + (sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) == + GRXSTSRD_STP_COMPLETE) { + if (td->remainder == 0) { + /* + * We are actually complete and have + * received the next SETUP + */ + DPRINTFN(5, "faking complete\n"); + return (0); /* complete */ + } + /* + * USB Host Aborted the transfer. + */ + td->error_any = 1; + return (0); /* complete */ + } + + if ((sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) != + GRXSTSRD_OUT_DATA) { + /* 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 */ + } + + /* read data from FIFO */ + dwc_otg_read_fifo(sc, td->pc, td->offset, count); + + td->remainder -= count; + td->offset += count; + + /* release FIFO */ + dwc_otg_common_rx_ack(sc); + + temp = sc->sc_out_ctl[td->ep_no]; + + /* check for isochronous mode */ + if ((temp & DIEPCTL_EPTYPE_MASK) == + (DIEPCTL_EPTYPE_ISOC << DIEPCTL_EPTYPE_SHIFT)) { + /* toggle odd or even frame bit */ + if (temp & DIEPCTL_SETD1PID) { + temp &= ~DIEPCTL_SETD1PID; + temp |= DIEPCTL_SETD0PID; + } else { + temp &= ~DIEPCTL_SETD0PID; + temp |= DIEPCTL_SETD1PID; + } + sc->sc_out_ctl[td->ep_no] = temp; + } + + /* 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: + + /* enable SETUP and transfer complete interrupt */ + if (td->ep_no == 0) { + DWC_OTG_WRITE_4(sc, DOTG_DOEPTSIZ(0), + DXEPTSIZ_SET_MULTI(3) | + DXEPTSIZ_SET_NPKT(1) | + DXEPTSIZ_SET_NBYTES(td->max_packet_size)); + } else { + /* allow reception of multiple packets */ + DWC_OTG_WRITE_4(sc, DOTG_DOEPTSIZ(td->ep_no), + DXEPTSIZ_SET_MULTI(1) | + DXEPTSIZ_SET_NPKT(4) | + DXEPTSIZ_SET_NBYTES(4 * + ((td->max_packet_size + 3) & ~3))); + } + temp = sc->sc_out_ctl[td->ep_no]; + DWC_OTG_WRITE_4(sc, DOTG_DOEPCTL(td->ep_no), temp | + DOEPCTL_EPENA | DOEPCTL_CNAK); + + return (1); /* not complete */ +} + +static uint8_t +dwc_otg_host_data_tx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) +{ + uint32_t count; + uint32_t hcint; + uint32_t hcchar; + uint8_t delta; + uint8_t channel; + uint8_t x; + + dwc_otg_host_dump_rx(sc, td); + + /* check that last channel is complete */ + channel = td->channel[td->npkt]; + + if (channel < DWC_OTG_MAX_CHANNELS) { + hcint = sc->sc_chan_state[channel].hcint; + + DPRINTF("CH=%d ST=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n", + channel, td->state, hcint, + DWC_OTG_READ_4(sc, DOTG_HCCHAR(channel)), + DWC_OTG_READ_4(sc, DOTG_HCTSIZ(channel))); + + if (hcint & (HCINT_RETRY | + HCINT_ACK | HCINT_NYET)) { + /* give success bits priority over failure bits */ + } else if (hcint & HCINT_STALL) { + DPRINTF("CH=%d STALL\n", channel); + td->error_stall = 1; + td->error_any = 1; + goto complete; + } else if (hcint & HCINT_ERRORS) { + DPRINTF("CH=%d ERROR\n", channel); + td->errcnt++; + if (td->hcsplt != 0 || td->errcnt >= 3) { + td->error_any = 1; + goto complete; + } + } + + if (hcint & (HCINT_ERRORS | HCINT_RETRY | + HCINT_ACK | HCINT_NYET)) { + + if (!(hcint & HCINT_ERRORS)) + td->errcnt = 0; + } + } else { + hcint = 0; + } + + switch (td->state) { + case DWC_CHAN_ST_START: + goto send_pkt; + + case DWC_CHAN_ST_WAIT_ANE: + if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { + td->did_nak = 1; + td->tt_scheduled = 0; + goto send_pkt; + } else if (hcint & (HCINT_ACK | HCINT_NYET)) { + td->offset += td->tx_bytes; + td->remainder -= td->tx_bytes; + td->toggle ^= 1; + /* check if next response will be a NAK */ + if (hcint & HCINT_NYET) + td->did_nak = 1; + else + td->did_nak = 0; + td->tt_scheduled = 0; + + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) + goto 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)) { + td->did_nak = 1; + td->tt_scheduled = 0; + goto send_pkt; + } else if (hcint & (HCINT_ACK | HCINT_NYET)) { + td->did_nak = 0; + goto send_cpkt; + } + break; + + case DWC_CHAN_ST_WAIT_C_ANE: + if (hcint & HCINT_NYET) { + goto send_cpkt; + } else if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { + td->did_nak = 1; + td->tt_scheduled = 0; + goto send_pkt; + } else if (hcint & HCINT_ACK) { + td->offset += td->tx_bytes; + td->remainder -= td->tx_bytes; + td->toggle ^= 1; + td->did_nak = 0; + td->tt_scheduled = 0; + + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) + goto complete; + + /* else we need to transmit a short packet */ + } + goto send_pkt; + } + break; + + case DWC_CHAN_ST_WAIT_C_PKT: + goto send_cpkt; + + case DWC_CHAN_ST_TX_WAIT_ISOC: + /* Check if ISOCHRONOUS OUT traffic is complete */ + if ((hcint & HCINT_HCH_DONE_MASK) == 0) + break; + + td->offset += td->tx_bytes; + td->remainder -= td->tx_bytes; + goto complete; + default: + break; + } + goto busy; + +send_pkt: + /* free existing channel(s), if any */ + dwc_otg_host_channel_free(sc, td); + + if (td->hcsplt != 0) { + delta = td->tt_start_slot - sc->sc_last_frame_num - 1; + if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) { + td->state = DWC_CHAN_ST_START; + goto busy; + } + delta = sc->sc_last_frame_num - td->tt_start_slot; + if (delta > 5) { + /* missed it */ + td->tt_scheduled = 0; + td->state = DWC_CHAN_ST_START; + goto busy; + } + } else if (dwc_otg_host_rate_check(sc, td)) { + td->state = DWC_CHAN_ST_START; + goto busy; + } + + /* allocate a new channel */ + if (dwc_otg_host_channel_alloc(sc, td, 1)) { + td->state = DWC_CHAN_ST_START; + goto busy; + } + + /* set toggle, if any */ + if (td->set_toggle) { + td->set_toggle = 0; + td->toggle = 1; + } + + if (td->ep_type == UE_ISOCHRONOUS) { + /* ISOCHRONOUS OUT transfers don't have any ACKs */ + td->state = DWC_CHAN_ST_TX_WAIT_ISOC; + td->hcsplt &= ~HCSPLT_COMPSPLT; + if (td->hcsplt != 0) { + /* get maximum transfer length */ + count = td->remainder; + if (count > HCSPLT_XACTLEN_BURST) { + DPRINTF("TT overflow\n"); + td->error_any = 1; + goto complete; + } + /* Update transaction position */ + td->hcsplt &= ~HCSPLT_XACTPOS_MASK; + td->hcsplt |= (HCSPLT_XACTPOS_ALL << HCSPLT_XACTPOS_SHIFT); + } + } else if (td->hcsplt != 0) { + td->hcsplt &= ~HCSPLT_COMPSPLT; + /* Wait for ACK/NAK/ERR from TT */ + td->state = DWC_CHAN_ST_WAIT_S_ANE; + } else { + /* Wait for ACK/NAK/STALL from device */ + td->state = DWC_CHAN_ST_WAIT_ANE; + } + + td->tx_bytes = 0; + + for (x = 0; x != td->max_packet_count; x++) { + uint32_t rem_bytes; + + channel = td->channel[x]; + + /* send one packet at a time */ + count = td->max_packet_size; + rem_bytes = td->remainder - td->tx_bytes; + if (rem_bytes < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = rem_bytes; + } + if (count == rem_bytes) { + /* last packet */ + switch (x) { + case 0: + DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), + (count << HCTSIZ_XFERSIZE_SHIFT) | + (1 << HCTSIZ_PKTCNT_SHIFT) | + (td->toggle ? (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT) : + (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT))); + break; + case 1: + DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), + (count << HCTSIZ_XFERSIZE_SHIFT) | + (1 << HCTSIZ_PKTCNT_SHIFT) | + (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT)); + break; + default: + DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), + (count << HCTSIZ_XFERSIZE_SHIFT) | + (1 << HCTSIZ_PKTCNT_SHIFT) | + (HCTSIZ_PID_DATA2 << HCTSIZ_PID_SHIFT)); + break; + } + } else if (td->ep_type == UE_ISOCHRONOUS && + td->max_packet_count > 1) { + /* ISOCHRONOUS multi packet */ + DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), + (count << HCTSIZ_XFERSIZE_SHIFT) | + (1 << HCTSIZ_PKTCNT_SHIFT) | + (HCTSIZ_PID_MDATA << HCTSIZ_PID_SHIFT)); + } else { + /* TODO: HCTSIZ_DOPNG */ + /* standard BULK/INTERRUPT/CONTROL packet */ + DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(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(channel), td->hcsplt); + + hcchar = td->hcchar; + hcchar &= ~HCCHAR_EPDIR_IN; + + /* send after next SOF event */ + if ((sc->sc_last_frame_num & 1) == 0 && + td->ep_type == UE_ISOCHRONOUS) + hcchar |= HCCHAR_ODDFRM; + else + hcchar &= ~HCCHAR_ODDFRM; + + /* must enable before writing data to FIFO */ + DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(channel), hcchar); + + if (count != 0) { + /* write data into FIFO */ + dwc_otg_write_fifo(sc, td->pc, td->offset + + td->tx_bytes, DOTG_DFIFO(channel), count); + } + + /* store number of bytes transmitted */ + td->tx_bytes += count; + + /* store last packet index */ + td->npkt = x; + + /* check for last packet */ + if (count == rem_bytes) + break; + } + goto busy; + +send_cpkt: + /* free existing channel, if any */ + dwc_otg_host_channel_free(sc, td); + + delta = td->tt_complete_slot - sc->sc_last_frame_num - 1; + if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) { + td->state = DWC_CHAN_ST_WAIT_C_PKT; + goto busy; + } + delta = sc->sc_last_frame_num - td->tt_start_slot; + if (delta > DWC_OTG_TT_SLOT_MAX) { + /* we missed the service interval */ + if (td->ep_type != UE_ISOCHRONOUS) + td->error_any = 1; + goto complete; + } + + /* allocate a new channel */ + if (dwc_otg_host_channel_alloc(sc, td, 0)) { + td->state = DWC_CHAN_ST_WAIT_C_PKT; + goto busy; + } + + channel = td->channel[0]; + + td->hcsplt |= HCSPLT_COMPSPLT; + td->state = DWC_CHAN_ST_WAIT_C_ANE; + + DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), + (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT)); + + DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(channel), td->hcsplt); + + hcchar = td->hcchar; + hcchar &= ~HCCHAR_EPDIR_IN; + + /* receive complete split ASAP */ + if ((sc->sc_last_frame_num & 1) != 0 && + td->ep_type == UE_ISOCHRONOUS) + hcchar |= HCCHAR_ODDFRM; + else + hcchar &= ~HCCHAR_ODDFRM; + + /* must enable channel before data can be received */ + DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(channel), hcchar); + + /* wait until next slot before trying complete split */ + td->tt_complete_slot = sc->sc_last_frame_num + 1; +busy: + return (1); /* busy */ + +complete: + dwc_otg_host_channel_free(sc, td); + return (0); /* complete */ +} + +static uint8_t +dwc_otg_data_tx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) +{ + uint32_t max_buffer; + uint32_t count; + uint32_t fifo_left; + uint32_t mpkt; + uint32_t temp; + uint8_t to; + + to = 3; /* don't loop forever! */ + + max_buffer = sc->sc_hw_ep_profile[td->ep_no].max_buffer; + +repeat: + /* check for for endpoint 0 data */ + + temp = sc->sc_last_rx_status; + + if ((td->ep_no == 0) && (temp != 0) && + (GRXSTSRD_CHNUM_GET(temp) == 0)) { + + if ((temp & GRXSTSRD_PKTSTS_MASK) != + GRXSTSRD_STP_DATA && + (temp & GRXSTSRD_PKTSTS_MASK) != + GRXSTSRD_STP_COMPLETE) { + + /* dump data - wrong direction */ + dwc_otg_common_rx_ack(sc); + } else { + /* + * The current transfer was cancelled + * by the USB Host: + */ + td->error_any = 1; + return (0); /* complete */ + } + } + + /* fill in more TX data, if possible */ + if (td->tx_bytes != 0) { + + uint16_t cpkt; + + /* check if packets have been transferred */ + temp = DWC_OTG_READ_4(sc, DOTG_DIEPTSIZ(td->ep_no)); + + /* get current packet number */ + cpkt = DXEPTSIZ_GET_NPKT(temp); + + if (cpkt >= td->npkt) { + fifo_left = 0; + } else { + if (max_buffer != 0) { + fifo_left = (td->npkt - cpkt) * + td->max_packet_size; + + if (fifo_left > max_buffer) + fifo_left = max_buffer; + } else { + fifo_left = td->max_packet_size; + } + } + + count = td->tx_bytes; + if (count > fifo_left) + count = fifo_left; + + if (count != 0) { + /* write data into FIFO */ + dwc_otg_write_fifo(sc, td->pc, td->offset, + DOTG_DFIFO(td->ep_no), count); + + td->tx_bytes -= count; + td->remainder -= count; + td->offset += count; + td->npkt = cpkt; + } + if (td->tx_bytes != 0) + goto not_complete; + + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) + return (0); /* complete */ + + /* else we need to transmit a short packet */ + } + } + + if (!to--) + goto not_complete; + + /* check if not all packets have been transferred */ + temp = DWC_OTG_READ_4(sc, DOTG_DIEPTSIZ(td->ep_no)); + + if (DXEPTSIZ_GET_NPKT(temp) != 0) { + + DPRINTFN(5, "busy ep=%d npkt=%d DIEPTSIZ=0x%08x " + "DIEPCTL=0x%08x\n", td->ep_no, + DXEPTSIZ_GET_NPKT(temp), + temp, DWC_OTG_READ_4(sc, DOTG_DIEPCTL(td->ep_no))); + + goto not_complete; + } + + DPRINTFN(5, "rem=%u ep=%d\n", td->remainder, td->ep_no); + + /* try to optimise by sending more data */ + if ((max_buffer != 0) && ((td->max_packet_size & 3) == 0)) { + + /* send multiple packets at the same time */ + mpkt = max_buffer / td->max_packet_size; + + if (mpkt > 0x3FE) + mpkt = 0x3FE; + + count = td->remainder; + if (count > 0x7FFFFF) + count = 0x7FFFFF - (0x7FFFFF % td->max_packet_size); + + td->npkt = count / td->max_packet_size; + + /* + * NOTE: We could use 0x3FE instead of "mpkt" in the + * check below to get more throughput, but then we + * have a dependency towards non-generic chip features + * to disable the TX-FIFO-EMPTY interrupts on a per + * endpoint basis. Increase the maximum buffer size of + * the IN endpoint to increase the performance. + */ + if (td->npkt > mpkt) { + td->npkt = mpkt; + count = td->max_packet_size * mpkt; + } else if ((count == 0) || (count % td->max_packet_size)) { + /* we are transmitting a short packet */ + td->npkt++; + td->short_pkt = 1; + } + } else { + /* send one packet at a time */ + mpkt = 1; + count = td->max_packet_size; + if (td->remainder < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = td->remainder; + } + td->npkt = 1; + } + DWC_OTG_WRITE_4(sc, DOTG_DIEPTSIZ(td->ep_no), + DXEPTSIZ_SET_MULTI(1) | + DXEPTSIZ_SET_NPKT(td->npkt) | + DXEPTSIZ_SET_NBYTES(count)); + + /* make room for buffering */ + td->npkt += mpkt; + + temp = sc->sc_in_ctl[td->ep_no]; + + /* check for isochronous mode */ + if ((temp & DIEPCTL_EPTYPE_MASK) == + (DIEPCTL_EPTYPE_ISOC << DIEPCTL_EPTYPE_SHIFT)) { + /* toggle odd or even frame bit */ + if (temp & DIEPCTL_SETD1PID) { + temp &= ~DIEPCTL_SETD1PID; + temp |= DIEPCTL_SETD0PID; + } else { + temp &= ~DIEPCTL_SETD0PID; + temp |= DIEPCTL_SETD1PID; + } + sc->sc_in_ctl[td->ep_no] = temp; + } + + /* must enable before writing data to FIFO */ + DWC_OTG_WRITE_4(sc, DOTG_DIEPCTL(td->ep_no), temp | + DIEPCTL_EPENA | DIEPCTL_CNAK); + + td->tx_bytes = count; + + /* check remainder */ + if (td->tx_bytes == 0 && + td->remainder == 0) { + if (td->short_pkt) + return (0); /* complete */ + + /* else we need to transmit a short packet */ + } + goto repeat; + +not_complete: + return (1); /* not complete */ +} + +static uint8_t +dwc_otg_data_tx_sync(struct dwc_otg_softc *sc, struct dwc_otg_td *td) +{ + uint32_t temp; + + /* + * If all packets are transferred we are complete: + */ + temp = DWC_OTG_READ_4(sc, DOTG_DIEPTSIZ(td->ep_no)); + + /* check that all packets have been transferred */ + if (DXEPTSIZ_GET_NPKT(temp) != 0) { + DPRINTFN(5, "busy ep=%d\n", td->ep_no); + goto not_complete; + } + return (0); + +not_complete: + + /* we only want to know if there is a SETUP packet or free IN packet */ + + temp = sc->sc_last_rx_status; + + if ((td->ep_no == 0) && (temp != 0) && + (GRXSTSRD_CHNUM_GET(temp) == 0)) { + + if ((temp & GRXSTSRD_PKTSTS_MASK) == + GRXSTSRD_STP_DATA || + (temp & GRXSTSRD_PKTSTS_MASK) == + GRXSTSRD_STP_COMPLETE) { + DPRINTFN(5, "faking complete\n"); + /* + * Race condition: We are complete! + */ + return (0); + } else { + /* dump data - wrong direction */ + dwc_otg_common_rx_ack(sc); + } + } + return (1); /* not complete */ +} + +static void +dwc_otg_xfer_do_fifo(struct dwc_otg_softc *sc, struct usb_xfer *xfer) +{ + struct dwc_otg_td *td; + uint8_t toggle; + uint8_t tmr_val; + uint8_t tmr_res; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + if (td == NULL) + return; + + while (1) { + if ((td->func) (sc, td)) { + /* operation in progress */ + break; + } + if (((void *)td) == xfer->td_transfer_last) { + goto done; + } + if (td->error_any) { + goto done; + } else if (td->remainder > 0) { + /* + * We had a short transfer. If there is no alternate + * next, stop processing ! + */ + if (!td->alt_next) + goto done; + } + + /* + * Fetch the next transfer descriptor and transfer + * some flags to the next transfer descriptor + */ + tmr_res = td->tmr_res; + tmr_val = td->tmr_val; + toggle = td->toggle; + td = td->obj_next; + xfer->td_transfer_cache = td; + td->toggle = toggle; /* transfer toggle */ + td->tmr_res = tmr_res; + td->tmr_val = tmr_val; + } + return; + +done: + xfer->td_transfer_cache = NULL; + sc->sc_xfer_complete = 1; +} + +static uint8_t +dwc_otg_xfer_do_complete_locked(struct dwc_otg_softc *sc, struct usb_xfer *xfer) +{ + struct dwc_otg_td *td; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + if (td == NULL) { + /* compute all actual lengths */ + dwc_otg_standard_done(xfer); + return (1); + } + return (0); +} + +static void +dwc_otg_timer(void *_sc) +{ + struct dwc_otg_softc *sc = _sc; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + DPRINTF("\n"); + + USB_BUS_SPIN_LOCK(&sc->sc_bus); + + /* increment timer value */ + sc->sc_tmr_val++; + + /* enable SOF interrupt, which will poll jobs */ + dwc_otg_enable_sof_irq(sc); + + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); + + 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 uint16_t +dwc_otg_compute_isoc_rx_tt_slot(struct dwc_otg_tt_info *pinfo) +{ + if (pinfo->slot_index < DWC_OTG_TT_SLOT_MAX) + pinfo->slot_index++; + return (pinfo->slot_index); +} + +static uint8_t +dwc_otg_update_host_transfer_schedule_locked(struct dwc_otg_softc *sc) +{ + TAILQ_HEAD(, usb_xfer) head; + struct usb_xfer *xfer; + struct usb_xfer *xfer_next; + struct dwc_otg_td *td; + uint16_t temp; + uint16_t slot; + + temp = DWC_OTG_READ_4(sc, DOTG_HFNUM) & DWC_OTG_FRAME_MASK; + + if (sc->sc_last_frame_num == temp) + return (0); + + sc->sc_last_frame_num = temp; + + TAILQ_INIT(&head); + + if ((temp & 7) == 0) { + + /* reset the schedule */ + memset(sc->sc_tt_info, 0, sizeof(sc->sc_tt_info)); + + TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) { + td = xfer->td_transfer_cache; + if (td == NULL || td->ep_type != UE_ISOCHRONOUS) + continue; + + /* check for IN direction */ + if ((td->hcchar & HCCHAR_EPDIR_IN) != 0) + continue; + + sc->sc_needsof = 1; + + if (td->hcsplt == 0 || td->tt_scheduled != 0) + continue; + + /* compute slot */ + slot = dwc_otg_compute_isoc_rx_tt_slot( + sc->sc_tt_info + td->tt_index); + if (slot > 3) { + /* + * Not enough time to get complete + * split executed. + */ + continue; + } + /* Delayed start */ + td->tt_start_slot = temp + slot; + td->tt_scheduled = 1; + TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); + TAILQ_INSERT_TAIL(&head, xfer, wait_entry); + } + + TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) { + td = xfer->td_transfer_cache; + if (td == NULL || td->ep_type != UE_ISOCHRONOUS) + continue; + + /* check for OUT direction */ + if ((td->hcchar & HCCHAR_EPDIR_IN) == 0) + continue; + + sc->sc_needsof = 1; + + if (td->hcsplt == 0 || td->tt_scheduled != 0) + continue; + + /* Start ASAP */ + td->tt_start_slot = temp; + td->tt_scheduled = 1; + TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); + TAILQ_INSERT_TAIL(&head, xfer, wait_entry); + } + + TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) { + td = xfer->td_transfer_cache; + if (td == NULL || td->ep_type != UE_INTERRUPT) + continue; + + if (td->tt_scheduled != 0) { + sc->sc_needsof = 1; + continue; + } + + if (dwc_otg_host_rate_check_interrupt(sc, td)) + continue; + + if (td->hcsplt == 0) { + sc->sc_needsof = 1; + td->tt_scheduled = 1; + continue; + } + + /* start ASAP */ + td->tt_start_slot = temp; + sc->sc_needsof = 1; + td->tt_scheduled = 1; + TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); + TAILQ_INSERT_TAIL(&head, xfer, wait_entry); + } + + TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) { + td = xfer->td_transfer_cache; + if (td == NULL || + td->ep_type != UE_CONTROL) { + continue; + } + + sc->sc_needsof = 1; + + if (td->hcsplt == 0 || td->tt_scheduled != 0) + continue; + + /* start ASAP */ + td->tt_start_slot = temp; + td->tt_scheduled = 1; + TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); + TAILQ_INSERT_TAIL(&head, xfer, wait_entry); + } + } + if ((temp & 7) < 6) { + TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) { + td = xfer->td_transfer_cache; + if (td == NULL || + td->ep_type != UE_BULK) { + continue; + } + + sc->sc_needsof = 1; + + if (td->hcsplt == 0 || td->tt_scheduled != 0) + continue; + + /* start ASAP */ + td->tt_start_slot = temp; + td->tt_scheduled = 1; + TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); + TAILQ_INSERT_TAIL(&head, xfer, wait_entry); + } + } + + /* Put TT transfers in execution order at the end */ + TAILQ_CONCAT(&sc->sc_bus.intr_q.head, &head, wait_entry); + + /* move all TT transfers in front, keeping the current order */ + TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) { + td = xfer->td_transfer_cache; + if (td == NULL || td->hcsplt == 0) + continue; + TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); + TAILQ_INSERT_TAIL(&head, xfer, wait_entry); + } + TAILQ_CONCAT(&head, &sc->sc_bus.intr_q.head, wait_entry); + TAILQ_CONCAT(&sc->sc_bus.intr_q.head, &head, wait_entry); + + /* put non-TT non-ISOCHRONOUS transfers last */ + TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) { + td = xfer->td_transfer_cache; + if (td == NULL || td->hcsplt != 0 || td->ep_type == UE_ISOCHRONOUS) + continue; + TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); + TAILQ_INSERT_TAIL(&head, xfer, wait_entry); + } + TAILQ_CONCAT(&sc->sc_bus.intr_q.head, &head, wait_entry); + + if ((temp & 7) == 0) { + + DPRINTFN(12, "SOF interrupt #%d, needsof=%d\n", + (int)temp, (int)sc->sc_needsof); + + /* update SOF IRQ mask */ + if (sc->sc_irq_mask & GINTMSK_SOFMSK) { + if (sc->sc_needsof == 0) { + sc->sc_irq_mask &= ~GINTMSK_SOFMSK; + DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); + } + } else { + if (sc->sc_needsof != 0) { + sc->sc_irq_mask |= GINTMSK_SOFMSK; + DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); + } + } + + /* clear need SOF flag */ + sc->sc_needsof = 0; + } + return (1); +} + +static void +dwc_otg_interrupt_poll_locked(struct dwc_otg_softc *sc) +{ + struct usb_xfer *xfer; + uint32_t count; + uint32_t temp; + uint32_t haint; + uint8_t got_rx_status; + uint8_t x; + + if (sc->sc_flags.status_device_mode == 0) { + /* + * Update host transfer schedule, so that new + * transfers can be issued: + */ + dwc_otg_update_host_transfer_schedule_locked(sc); + } + count = 0; +repeat: + if (++count == 16) { + /* give other interrupts a chance */ + DPRINTF("Yield\n"); + return; + } + + /* get all host channel interrupts */ + haint = DWC_OTG_READ_4(sc, DOTG_HAINT); + while (1) { + x = ffs(haint) - 1; + if (x >= sc->sc_host_ch_max) + break; + temp = DWC_OTG_READ_4(sc, DOTG_HCINT(x)); + DWC_OTG_WRITE_4(sc, DOTG_HCINT(x), temp); + temp &= ~HCINT_SOFTWARE_ONLY; + sc->sc_chan_state[x].hcint |= temp; + haint &= ~(1U << x); + } + + if (sc->sc_last_rx_status == 0) { + + temp = DWC_OTG_READ_4(sc, DOTG_GINTSTS); + if (temp & GINTSTS_RXFLVL) { + /* pop current status */ + sc->sc_last_rx_status = + DWC_OTG_READ_4(sc, DOTG_GRXSTSPD); + } + + if (sc->sc_last_rx_status != 0) { + + uint8_t ep_no; + + temp = sc->sc_last_rx_status & + GRXSTSRD_PKTSTS_MASK; + + /* non-data messages we simply skip */ + if (temp != GRXSTSRD_STP_DATA && + temp != GRXSTSRD_STP_COMPLETE && + temp != GRXSTSRD_OUT_DATA) { + /* check for halted channel */ + if (temp == GRXSTSRH_HALTED) { + ep_no = GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status); + sc->sc_chan_state[ep_no].wait_halted = 0; + DPRINTFN(5, "channel halt complete ch=%u\n", ep_no); + } + /* store bytes and FIFO offset */ + sc->sc_current_rx_bytes = 0; + sc->sc_current_rx_fifo = 0; + + /* acknowledge status */ + dwc_otg_common_rx_ack(sc); + goto repeat; + } + + temp = GRXSTSRD_BCNT_GET( + sc->sc_last_rx_status); + ep_no = GRXSTSRD_CHNUM_GET( + sc->sc_last_rx_status); + + /* store bytes and FIFO offset */ + sc->sc_current_rx_bytes = (temp + 3) & ~3; + sc->sc_current_rx_fifo = DOTG_DFIFO(ep_no); + + DPRINTF("Reading %d bytes from ep %d\n", temp, ep_no); + + /* check if we should dump the data */ + if (!(sc->sc_active_rx_ep & (1U << ep_no))) { + dwc_otg_common_rx_ack(sc); + goto repeat; + } + + got_rx_status = 1; + + DPRINTFN(5, "RX status = 0x%08x: ch=%d pid=%d bytes=%d sts=%d\n", + sc->sc_last_rx_status, ep_no, + (sc->sc_last_rx_status >> 15) & 3, + GRXSTSRD_BCNT_GET(sc->sc_last_rx_status), + (sc->sc_last_rx_status >> 17) & 15); + } else { + got_rx_status = 0; + } + } else { + uint8_t ep_no; + + ep_no = GRXSTSRD_CHNUM_GET( + sc->sc_last_rx_status); + + /* check if we should dump the data */ + if (!(sc->sc_active_rx_ep & (1U << ep_no))) { + dwc_otg_common_rx_ack(sc); + goto repeat; + } + + got_rx_status = 1; + } + + /* execute FIFOs */ + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) + dwc_otg_xfer_do_fifo(sc, xfer); + + if (got_rx_status) { + /* check if data was consumed */ + if (sc->sc_last_rx_status == 0) + goto repeat; + + /* disable RX FIFO level interrupt */ + sc->sc_irq_mask &= ~GINTMSK_RXFLVLMSK; + DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); + } +} + +static void +dwc_otg_interrupt_complete_locked(struct dwc_otg_softc *sc) +{ + struct usb_xfer *xfer; +repeat: + /* scan for completion events */ + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + if (dwc_otg_xfer_do_complete_locked(sc, xfer)) + goto repeat; + } +} + +static void +dwc_otg_vbus_interrupt(struct dwc_otg_softc *sc, uint8_t is_on) +{ + DPRINTFN(5, "vbus = %u\n", is_on); + + /* + * If the USB host mode is forced, then assume VBUS is always + * present else rely on the input to this function: + */ + if ((is_on != 0) || (sc->sc_mode == DWC_MODE_HOST)) { + + if (!sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 1; + + /* complete root HUB interrupt endpoint */ + + dwc_otg_root_intr(sc); + } + } else { + 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 */ + + dwc_otg_root_intr(sc); + } + } +} + +int +dwc_otg_filter_interrupt(void *arg) +{ + struct dwc_otg_softc *sc = arg; + int retval = FILTER_HANDLED; + uint32_t status; + + USB_BUS_SPIN_LOCK(&sc->sc_bus); + + /* read and clear interrupt status */ + status = DWC_OTG_READ_4(sc, DOTG_GINTSTS); + + /* clear interrupts we are handling here */ + DWC_OTG_WRITE_4(sc, DOTG_GINTSTS, status & ~DWC_OTG_MSK_GINT_THREAD_IRQ); + + /* check for USB state change interrupts */ + if ((status & DWC_OTG_MSK_GINT_THREAD_IRQ) != 0) + retval = FILTER_SCHEDULE_THREAD; + + /* clear FIFO empty interrupts */ + if (status & sc->sc_irq_mask & + (GINTSTS_PTXFEMP | GINTSTS_NPTXFEMP)) { + sc->sc_irq_mask &= ~(GINTSTS_PTXFEMP | GINTSTS_NPTXFEMP); + DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); + } + /* clear all IN endpoint interrupts */ + if (status & GINTSTS_IEPINT) { + uint32_t temp; + uint8_t x; + + for (x = 0; x != sc->sc_dev_in_ep_max; x++) { + temp = DWC_OTG_READ_4(sc, DOTG_DIEPINT(x)); + if (temp & DIEPMSK_XFERCOMPLMSK) { + DWC_OTG_WRITE_4(sc, DOTG_DIEPINT(x), + DIEPMSK_XFERCOMPLMSK); + } + } + } + + /* poll FIFOs, if any */ + dwc_otg_interrupt_poll_locked(sc); + + if (sc->sc_xfer_complete != 0) + retval = FILTER_SCHEDULE_THREAD; + + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); + + return (retval); +} + +void +dwc_otg_interrupt(void *arg) +{ + struct dwc_otg_softc *sc = arg; + uint32_t status; + + USB_BUS_LOCK(&sc->sc_bus); + USB_BUS_SPIN_LOCK(&sc->sc_bus); + + /* read and clear interrupt status */ + status = DWC_OTG_READ_4(sc, DOTG_GINTSTS); + + /* clear interrupts we are handling here */ + DWC_OTG_WRITE_4(sc, DOTG_GINTSTS, status & DWC_OTG_MSK_GINT_THREAD_IRQ); + + 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; + sc->sc_flags.change_connect = 1; + + /* Disable SOF interrupt */ + sc->sc_irq_mask &= ~GINTMSK_SOFMSK; + DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); + + /* complete root HUB interrupt endpoint */ + dwc_otg_root_intr(sc); + } + + /* check for any bus state change interrupts */ + if (status & GINTSTS_ENUMDONE) { + + uint32_t temp; + + 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 */ + (void) dwc_otg_init_fifo(sc, DWC_MODE_DEVICE); + + /* reset function address */ + dwc_otg_set_address(sc, 0); + + /* figure out enumeration speed */ + temp = DWC_OTG_READ_4(sc, DOTG_DSTS); + if (DSTS_ENUMSPD_GET(temp) == DSTS_ENUMSPD_HI) + sc->sc_flags.status_high_speed = 1; + else + sc->sc_flags.status_high_speed = 0; + + /* + * Disable resume and SOF interrupt, and enable + * suspend and RX frame interrupt: + */ + sc->sc_irq_mask &= ~(GINTMSK_WKUPINTMSK | GINTMSK_SOFMSK); + sc->sc_irq_mask |= GINTMSK_USBSUSPMSK; + DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); + + /* 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); + + /* update host frame interval */ + dwc_otg_update_host_frame_interval(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 & GINTSTS_WKUPINT) { + + DPRINTFN(5, "resume interrupt\n"); + + dwc_otg_resume_irq(sc); + + } else if (status & GINTSTS_USBSUSP) { + + DPRINTFN(5, "suspend interrupt\n"); + + dwc_otg_suspend_irq(sc); + } + /* check VBUS */ + if (status & (GINTSTS_USBSUSP | + GINTSTS_USBRST | + GINTMSK_OTGINTMSK | + GINTSTS_SESSREQINT)) { + uint32_t temp; + + temp = DWC_OTG_READ_4(sc, DOTG_GOTGCTL); + + DPRINTFN(5, "GOTGCTL=0x%08x\n", temp); + + dwc_otg_vbus_interrupt(sc, + (temp & (GOTGCTL_ASESVLD | GOTGCTL_BSESVLD)) ? 1 : 0); + } + + if (sc->sc_xfer_complete != 0) { + sc->sc_xfer_complete = 0; + + /* complete FIFOs, if any */ + dwc_otg_interrupt_complete_locked(sc); + } + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +dwc_otg_setup_standard_chain_sub(struct dwc_otg_std_temp *temp) +{ + struct dwc_otg_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->tx_bytes = 0; + td->error_any = 0; + td->error_stall = 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[0] = DWC_OTG_MAX_CHANNELS; + td->channel[1] = DWC_OTG_MAX_CHANNELS; + td->channel[2] = DWC_OTG_MAX_CHANNELS; + td->state = 0; + td->errcnt = 0; + td->tt_scheduled = 0; + td->tt_xactpos = HCSPLT_XACTPOS_BEGIN; +} + +static void +dwc_otg_setup_standard_chain(struct usb_xfer *xfer) +{ + struct dwc_otg_std_temp temp; + 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), + 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 || + xfer->flags_int.isochronous_xfr; + 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) { + + 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; + + /* 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; + } + + dwc_otg_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + if (x != xfer->nframes) { + if (xfer->endpointno & UE_DIR_IN) { + if (is_host) { + temp.func = &dwc_otg_host_data_rx; + need_sync = 0; + } else { + temp.func = &dwc_otg_data_tx; + need_sync = 1; + } + } else { + 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 */ + temp.pc = xfer->frbuffers + x; + } else { + need_sync = 0; + } + 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); + } + + dwc_otg_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; + } + } + + if (xfer->flags_int.control_xfr) { + + /* 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 need to sync */ + if (need_sync) { + /* we need a SYNC point after TX */ + temp.func = &dwc_otg_data_tx_sync; + dwc_otg_setup_standard_chain_sub(&temp); + } + + /* 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) { + if (is_host) { + temp.func = &dwc_otg_host_data_tx; + need_sync = 0; + } else { + temp.func = &dwc_otg_data_rx; + need_sync = 0; + } + } else { + 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; + dwc_otg_setup_standard_chain_sub(&temp); + } + } + } else { + /* check if we need to sync */ + 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 = &dwc_otg_data_tx_sync; + dwc_otg_setup_standard_chain_sub(&temp); + } + } + + /* 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; + + sc = DWC_OTG_BUS2SC(xfer->xroot->bus); + + /* get first again */ + td = xfer->td_transfer_first; + td->toggle = (xfer->endpoint->toggle_next ? 1 : 0); + + hcchar = + (xfer->address << HCCHAR_DEVADDR_SHIFT) | + ((xfer->endpointno & UE_ADDR) << HCCHAR_EPNUM_SHIFT) | + (xfer->max_packet_size << HCCHAR_MPS_SHIFT) | + HCCHAR_CHENA; + + /* + * We are not always able to meet the timing + * requirements of the USB interrupt endpoint's + * complete split token, when doing transfers going + * via a transaction translator. Use the CONTROL + * transfer type instead of the INTERRUPT transfer + * type in general, as a means to workaround + * that. This trick should work for both FULL and LOW + * speed USB traffic going through a TT. For non-TT + * traffic it works aswell. The reason for using + * CONTROL type instead of BULK is that some TTs might + * reject LOW speed BULK traffic. + */ + if (td->ep_type == UE_INTERRUPT) + hcchar |= (UE_CONTROL << HCCHAR_EPTYPE_SHIFT); + else + hcchar |= (td->ep_type << HCCHAR_EPTYPE_SHIFT); + + 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 (xfer->xroot->udev->parent_hs_hub != NULL) { + hcsplt = HCSPLT_SPLTENA | + (xfer->xroot->udev->hs_port_no << + HCSPLT_PRTADDR_SHIFT) | + (xfer->xroot->udev->hs_hub_addr << + HCSPLT_HUBADDR_SHIFT); + } else { + hcsplt = 0; + } + if (td->ep_type == UE_INTERRUPT) { + uint32_t ival; + ival = xfer->interval / DWC_OTG_HOST_TIMER_RATE; + if (ival == 0) + ival = 1; + else if (ival > 127) + ival = 127; + td->tmr_val = sc->sc_tmr_val + ival; + td->tmr_res = ival; + } else if (td->ep_type == UE_ISOCHRONOUS) { + td->tmr_res = 1; + td->tmr_val = sc->sc_last_frame_num; + if (td->hcchar & HCCHAR_EPDIR_IN) + td->tmr_val++; + } else { + td->tmr_val = 0; + td->tmr_res = (uint8_t)sc->sc_last_frame_num; + } + break; + case USB_SPEED_HIGH: + hcsplt = 0; + if (td->ep_type == UE_INTERRUPT) { + uint32_t ival; + hcchar |= ((xfer->max_packet_count & 3) + << HCCHAR_MC_SHIFT); + ival = xfer->interval / DWC_OTG_HOST_TIMER_RATE; + if (ival == 0) + ival = 1; + else if (ival > 127) + ival = 127; + td->tmr_val = sc->sc_tmr_val + ival; + td->tmr_res = ival; + } else if (td->ep_type == UE_ISOCHRONOUS) { + hcchar |= ((xfer->max_packet_count & 3) + << HCCHAR_MC_SHIFT); + td->tmr_res = 1 << usbd_xfer_get_fps_shift(xfer); + td->tmr_val = sc->sc_last_frame_num; + if (td->hcchar & HCCHAR_EPDIR_IN) + td->tmr_val += td->tmr_res; + + } else { + td->tmr_val = 0; + td->tmr_res = (uint8_t)sc->sc_last_frame_num; + } + break; + default: + hcsplt = 0; + td->tmr_val = 0; + td->tmr_res = 0; + break; + } + + /* 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 +dwc_otg_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 */ + dwc_otg_device_done(xfer, USB_ERR_TIMEOUT); +} + +static void +dwc_otg_start_standard_chain(struct usb_xfer *xfer) +{ + struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(xfer->xroot->bus); + + DPRINTFN(9, "\n"); + + /* + * Poll one time in device mode, which will turn on the + * endpoint interrupts. Else wait for SOF interrupt in host + * mode. + */ + USB_BUS_SPIN_LOCK(&sc->sc_bus); + + if (sc->sc_flags.status_device_mode != 0) { + dwc_otg_xfer_do_fifo(sc, xfer); + if (dwc_otg_xfer_do_complete_locked(sc, xfer)) + goto done; + } else { + struct dwc_otg_td *td = xfer->td_transfer_cache; + if (td->ep_type == UE_ISOCHRONOUS && + (td->hcchar & HCCHAR_EPDIR_IN) == 0) { + /* + * Need to start ISOCHRONOUS OUT transfer ASAP + * because execution is delayed by one 125us + * microframe: + */ + dwc_otg_xfer_do_fifo(sc, xfer); + if (dwc_otg_xfer_do_complete_locked(sc, xfer)) + goto done; + } + } + + /* 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, + &dwc_otg_timeout, xfer->timeout); + } + + if (sc->sc_flags.status_device_mode != 0) + goto done; + + /* enable SOF interrupt, if any */ + dwc_otg_enable_sof_irq(sc); +done: + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); +} + +static void +dwc_otg_root_intr(struct dwc_otg_softc *sc) +{ + DPRINTFN(9, "\n"); + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* set port bit */ + sc->sc_hub_idata[0] = 0x02; /* we only have one port */ + + uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, + sizeof(sc->sc_hub_idata)); +} + +static usb_error_t +dwc_otg_standard_done_sub(struct usb_xfer *xfer) +{ + struct dwc_otg_td *td; + uint32_t len; + usb_error_t error; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + + 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_any = 1; + } else { + xfer->frlengths[xfer->aframes] -= len; + } + } + /* Check for transfer error */ + if (td->error_any) { + /* the transfer is finished */ + error = (td->error_stall ? + USB_ERR_STALLED : USB_ERR_IOERROR); + td = NULL; + break; + } + /* Check for short transfer */ + if (len > 0) { + if (xfer->flags_int.short_frames_ok || + xfer->flags_int.isochronous_xfr) { + /* 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); +} + +static void +dwc_otg_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 = dwc_otg_standard_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = dwc_otg_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 = dwc_otg_standard_done_sub(xfer); + } +done: + dwc_otg_device_done(xfer, err); +} + +/*------------------------------------------------------------------------* + * dwc_otg_device_done + * + * NOTE: this function can be called more than one time on the + * same USB transfer! + *------------------------------------------------------------------------*/ +static void +dwc_otg_device_done(struct usb_xfer *xfer, usb_error_t error) +{ + struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(xfer->xroot->bus); + + DPRINTFN(9, "xfer=%p, endpoint=%p, error=%d\n", + xfer, xfer->endpoint, error); + + USB_BUS_SPIN_LOCK(&sc->sc_bus); + + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { + /* Interrupts are cleared by the interrupt handler */ + } else { + struct dwc_otg_td *td; + + td = xfer->td_transfer_cache; + if (td != NULL) + dwc_otg_host_channel_free(sc, td); + } + /* dequeue transfer and start next transfer */ + usbd_transfer_done(xfer, error); + + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); +} + +static void +dwc_otg_xfer_stall(struct usb_xfer *xfer) +{ + dwc_otg_device_done(xfer, USB_ERR_STALLED); +} + +static void +dwc_otg_set_stall(struct usb_device *udev, + struct usb_endpoint *ep, uint8_t *did_stall) +{ + struct dwc_otg_softc *sc; + uint32_t temp; + uint32_t reg; + uint8_t ep_no; + + 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); + + USB_BUS_SPIN_LOCK(&sc->sc_bus); + + /* get endpoint address */ + ep_no = ep->edesc->bEndpointAddress; + + DPRINTFN(5, "endpoint=0x%x\n", ep_no); + + if (ep_no & UE_DIR_IN) { + reg = DOTG_DIEPCTL(ep_no & UE_ADDR); + temp = sc->sc_in_ctl[ep_no & UE_ADDR]; + } else { + reg = DOTG_DOEPCTL(ep_no & UE_ADDR); + temp = sc->sc_out_ctl[ep_no & UE_ADDR]; + } + + /* disable and stall endpoint */ + DWC_OTG_WRITE_4(sc, reg, temp | DOEPCTL_EPDIS); + DWC_OTG_WRITE_4(sc, reg, temp | DOEPCTL_STALL); + + /* clear active OUT ep */ + if (!(ep_no & UE_DIR_IN)) { + + sc->sc_active_rx_ep &= ~(1U << (ep_no & UE_ADDR)); + + if (sc->sc_last_rx_status != 0 && + (ep_no & UE_ADDR) == GRXSTSRD_CHNUM_GET( + sc->sc_last_rx_status)) { + /* dump data */ + dwc_otg_common_rx_ack(sc); + /* poll interrupt */ + dwc_otg_interrupt_poll_locked(sc); + dwc_otg_interrupt_complete_locked(sc); + } + } + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); +} + +static void +dwc_otg_clear_stall_sub_locked(struct dwc_otg_softc *sc, uint32_t mps, + uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir) +{ + uint32_t reg; + uint32_t temp; + + if (ep_type == UE_CONTROL) { + /* clearing stall is not needed */ + return; + } + + if (ep_dir) { + reg = DOTG_DIEPCTL(ep_no); + } else { + reg = DOTG_DOEPCTL(ep_no); + sc->sc_active_rx_ep |= (1U << ep_no); + } + + /* round up and mask away the multiplier count */ + mps = (mps + 3) & 0x7FC; + + if (ep_type == UE_BULK) { + temp = DIEPCTL_EPTYPE_SET( + DIEPCTL_EPTYPE_BULK) | + DIEPCTL_USBACTEP; + } else if (ep_type == UE_INTERRUPT) { + temp = DIEPCTL_EPTYPE_SET( + DIEPCTL_EPTYPE_INTERRUPT) | + DIEPCTL_USBACTEP; + } else { + temp = DIEPCTL_EPTYPE_SET( + DIEPCTL_EPTYPE_ISOC) | + DIEPCTL_USBACTEP; + } + + temp |= DIEPCTL_MPS_SET(mps); + temp |= DIEPCTL_TXFNUM_SET(ep_no); + + if (ep_dir) + sc->sc_in_ctl[ep_no] = temp; + else + sc->sc_out_ctl[ep_no] = temp; + + DWC_OTG_WRITE_4(sc, reg, temp | DOEPCTL_EPDIS); + DWC_OTG_WRITE_4(sc, reg, temp | DOEPCTL_SETD0PID); + DWC_OTG_WRITE_4(sc, reg, temp | DIEPCTL_SNAK); + + /* we only reset the transmit FIFO */ + if (ep_dir) { + dwc_otg_tx_fifo_reset(sc, + GRSTCTL_TXFIFO(ep_no) | + GRSTCTL_TXFFLSH); + + DWC_OTG_WRITE_4(sc, + DOTG_DIEPTSIZ(ep_no), 0); + } + + /* poll interrupt */ + dwc_otg_interrupt_poll_locked(sc); + dwc_otg_interrupt_complete_locked(sc); +} + +static void +dwc_otg_clear_stall(struct usb_device *udev, struct usb_endpoint *ep) +{ + struct dwc_otg_softc *sc; + struct usb_endpoint_descriptor *ed; + + DPRINTFN(5, "endpoint=%p\n", ep); + + USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); + + /* check mode */ + if (udev->flags.usb_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + /* get softc */ + sc = DWC_OTG_BUS2SC(udev->bus); + + USB_BUS_SPIN_LOCK(&sc->sc_bus); + + /* get endpoint descriptor */ + ed = ep->edesc; + + /* reset endpoint */ + dwc_otg_clear_stall_sub_locked(sc, + UGETW(ed->wMaxPacketSize), + (ed->bEndpointAddress & UE_ADDR), + (ed->bmAttributes & UE_XFERTYPE), + (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); + + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); +} + +static void +dwc_otg_device_state_change(struct usb_device *udev) +{ + 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); + + /* deactivate all other endpoint but the control endpoint */ + if (udev->state == USB_STATE_CONFIGURED || + udev->state == USB_STATE_ADDRESSED) { + + USB_BUS_LOCK(&sc->sc_bus); + + for (x = 1; x != sc->sc_dev_ep_max; x++) { + + if (x < sc->sc_dev_in_ep_max) { + DWC_OTG_WRITE_4(sc, DOTG_DIEPCTL(x), + DIEPCTL_EPDIS); + DWC_OTG_WRITE_4(sc, DOTG_DIEPCTL(x), 0); + } + + DWC_OTG_WRITE_4(sc, DOTG_DOEPCTL(x), + DOEPCTL_EPDIS); + DWC_OTG_WRITE_4(sc, DOTG_DOEPCTL(x), 0); + } + USB_BUS_UNLOCK(&sc->sc_bus); + } +} + +int +dwc_otg_init(struct dwc_otg_softc *sc) +{ + uint32_t temp; + + DPRINTF("start\n"); + + /* set up the bus structure */ + sc->sc_bus.usbrev = USB_REV_2_0; + sc->sc_bus.methods = &dwc_otg_bus_methods; + + usb_callout_init_mtx(&sc->sc_timer, + &sc->sc_bus.bus_mtx, 0); + + USB_BUS_LOCK(&sc->sc_bus); + + /* turn on clocks */ + dwc_otg_clocks_on(sc); + + temp = DWC_OTG_READ_4(sc, DOTG_GSNPSID); + DPRINTF("Version = 0x%08x\n", temp); + + /* disconnect */ + DWC_OTG_WRITE_4(sc, DOTG_DCTL, + DCTL_SFTDISCON); + + /* wait for host to detect disconnect */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 32); + + DWC_OTG_WRITE_4(sc, DOTG_GRSTCTL, + GRSTCTL_CSFTRST); + + /* 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, ULPI or internal PHY mode */ + switch (dwc_otg_phy_type) { + case DWC_OTG_PHY_HSIC: + DWC_OTG_WRITE_4(sc, DOTG_GUSBCFG, + GUSBCFG_PHYIF | + GUSBCFG_TRD_TIM_SET(5) | temp); + DWC_OTG_WRITE_4(sc, DOTG_GOTGCTL, + 0x000000EC); + + temp = DWC_OTG_READ_4(sc, DOTG_GLPMCFG); + DWC_OTG_WRITE_4(sc, DOTG_GLPMCFG, + temp & ~GLPMCFG_HSIC_CONN); + DWC_OTG_WRITE_4(sc, DOTG_GLPMCFG, + temp | GLPMCFG_HSIC_CONN); + break; + case DWC_OTG_PHY_ULPI: + DWC_OTG_WRITE_4(sc, DOTG_GUSBCFG, + GUSBCFG_ULPI_UTMI_SEL | + GUSBCFG_TRD_TIM_SET(5) | temp); + DWC_OTG_WRITE_4(sc, DOTG_GOTGCTL, 0); + + temp = DWC_OTG_READ_4(sc, DOTG_GLPMCFG); + DWC_OTG_WRITE_4(sc, DOTG_GLPMCFG, + temp & ~GLPMCFG_HSIC_CONN); + break; + case DWC_OTG_PHY_INTERNAL: + DWC_OTG_WRITE_4(sc, DOTG_GUSBCFG, + GUSBCFG_PHYSEL | + GUSBCFG_TRD_TIM_SET(5) | temp); + DWC_OTG_WRITE_4(sc, DOTG_GOTGCTL, 0); + + temp = DWC_OTG_READ_4(sc, DOTG_GLPMCFG); + DWC_OTG_WRITE_4(sc, DOTG_GLPMCFG, + temp & ~GLPMCFG_HSIC_CONN); + + temp = DWC_OTG_READ_4(sc, DOTG_GGPIO); + temp &= ~(DOTG_GGPIO_NOVBUSSENS | DOTG_GGPIO_I2CPADEN); + temp |= (DOTG_GGPIO_VBUSASEN | DOTG_GGPIO_VBUSBSEN | + DOTG_GGPIO_PWRDWN); + DWC_OTG_WRITE_4(sc, DOTG_GGPIO, temp); + break; + default: + break; + } + + /* clear global nak */ + DWC_OTG_WRITE_4(sc, DOTG_DCTL, + 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); + + temp = DWC_OTG_READ_4(sc, DOTG_GHWCFG3); + + sc->sc_fifo_size = 4 * GHWCFG3_DFIFODEPTH_GET(temp); + + temp = DWC_OTG_READ_4(sc, DOTG_GHWCFG2); + + 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 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, sc->sc_mode)) { + USB_BUS_UNLOCK(&sc->sc_bus); + return (EINVAL); + } + + /* enable interrupts */ + sc->sc_irq_mask = DWC_OTG_MSK_GINT_ENABLED; + DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); + + 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("Disable Multi Process Interrupts\n"); + + for (x = 0; x != sc->sc_dev_in_ep_max; x++) { + DWC_OTG_WRITE_4(sc, DOTG_DIEPEACHINTMSK(x), 0); + DWC_OTG_WRITE_4(sc, DOTG_DOEPEACHINTMSK(x), 0); + } + DWC_OTG_WRITE_4(sc, DOTG_DEACHINTMSK, 0); + } + 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); + } + + 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); + + /* turn off clocks */ + dwc_otg_clocks_off(sc); + + /* read initial VBUS state */ + + temp = DWC_OTG_READ_4(sc, DOTG_GOTGCTL); + + DPRINTFN(5, "GOTCTL=0x%08x\n", temp); + + dwc_otg_vbus_interrupt(sc, + (temp & (GOTGCTL_ASESVLD | GOTGCTL_BSESVLD)) ? 1 : 0); + + USB_BUS_UNLOCK(&sc->sc_bus); + + /* catch any lost interrupts */ + + dwc_otg_do_poll(&sc->sc_bus); + + return (0); /* success */ +} + +void +dwc_otg_uninit(struct dwc_otg_softc *sc) +{ + 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); + + /* 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; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + dwc_otg_pull_down(sc); + dwc_otg_clocks_off(sc); + + USB_BUS_UNLOCK(&sc->sc_bus); + + usb_callout_drain(&sc->sc_timer); +} + +static void +dwc_otg_suspend(struct dwc_otg_softc *sc) +{ + return; +} + +static void +dwc_otg_resume(struct dwc_otg_softc *sc) +{ + return; +} + +static void +dwc_otg_do_poll(struct usb_bus *bus) +{ + struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(bus); + + USB_BUS_LOCK(&sc->sc_bus); + USB_BUS_SPIN_LOCK(&sc->sc_bus); + dwc_otg_interrupt_poll_locked(sc); + dwc_otg_interrupt_complete_locked(sc); + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +/*------------------------------------------------------------------------* + * DWC OTG bulk support + * DWC OTG control support + * DWC OTG interrupt support + *------------------------------------------------------------------------*/ +static void +dwc_otg_device_non_isoc_open(struct usb_xfer *xfer) +{ +} + +static void +dwc_otg_device_non_isoc_close(struct usb_xfer *xfer) +{ + dwc_otg_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +dwc_otg_device_non_isoc_enter(struct usb_xfer *xfer) +{ +} + +static void +dwc_otg_device_non_isoc_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + dwc_otg_setup_standard_chain(xfer); + dwc_otg_start_standard_chain(xfer); +} + +static const struct usb_pipe_methods dwc_otg_device_non_isoc_methods = +{ + .open = dwc_otg_device_non_isoc_open, + .close = dwc_otg_device_non_isoc_close, + .enter = dwc_otg_device_non_isoc_enter, + .start = dwc_otg_device_non_isoc_start, +}; + +/*------------------------------------------------------------------------* + * DWC OTG full speed isochronous support + *------------------------------------------------------------------------*/ +static void +dwc_otg_device_isoc_open(struct usb_xfer *xfer) +{ +} + +static void +dwc_otg_device_isoc_close(struct usb_xfer *xfer) +{ + dwc_otg_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +dwc_otg_device_isoc_enter(struct usb_xfer *xfer) +{ +} + +static void +dwc_otg_device_isoc_start(struct usb_xfer *xfer) +{ + struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(xfer->xroot->bus); + uint32_t temp; + uint32_t msframes; + uint32_t framenum; + 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); + + if (xfer->xroot->udev->flags.usb_mode == USB_MODE_HOST) { + temp = DWC_OTG_READ_4(sc, DOTG_HFNUM); + + /* get the current frame index */ + framenum = (temp & HFNUM_FRNUM_MASK); + } else { + temp = DWC_OTG_READ_4(sc, DOTG_DSTS); + + /* get the current frame index */ + framenum = DSTS_SOFFN_GET(temp); + } + + if (xfer->xroot->udev->parent_hs_hub != NULL) + framenum /= 8; + + framenum &= DWC_OTG_FRAME_MASK; + + /* + * Compute number of milliseconds worth of data traffic for + * this USB transfer: + */ + if (xfer->xroot->udev->speed == USB_SPEED_HIGH) + msframes = ((xfer->nframes << shift) + 7) / 8; + else + msframes = xfer->nframes; + + /* + * check if the frame index is within the window where the frames + * will be inserted + */ + temp = (framenum - xfer->endpoint->isoc_next) & DWC_OTG_FRAME_MASK; + + if ((xfer->endpoint->is_synced == 0) || (temp < msframes)) { + /* + * If there is data underflow or the pipe queue is + * empty we schedule the transfer a few frames ahead + * of the current frame position. Else two isochronous + * transfers might overlap. + */ + xfer->endpoint->isoc_next = (framenum + 3) & DWC_OTG_FRAME_MASK; + xfer->endpoint->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + temp = (xfer->endpoint->isoc_next - framenum) & DWC_OTG_FRAME_MASK; + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb_isoc_time_expand(&sc->sc_bus, framenum) + temp + msframes; + + /* setup TDs */ + dwc_otg_setup_standard_chain(xfer); + + /* compute frame number for next insertion */ + xfer->endpoint->isoc_next += msframes; + + /* start TD chain */ + dwc_otg_start_standard_chain(xfer); +} + +static const struct usb_pipe_methods dwc_otg_device_isoc_methods = +{ + .open = dwc_otg_device_isoc_open, + .close = dwc_otg_device_isoc_close, + .enter = dwc_otg_device_isoc_enter, + .start = dwc_otg_device_isoc_start, +}; + +/*------------------------------------------------------------------------* + * DWC OTG root control support + *------------------------------------------------------------------------* + * Simulate a hardware HUB by handling all the necessary requests. + *------------------------------------------------------------------------*/ + +static const struct usb_device_descriptor dwc_otg_devd = { + .bLength = sizeof(struct usb_device_descriptor), + .bDescriptorType = UDESC_DEVICE, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_HSHUBSTT, + .bMaxPacketSize = 64, + .bcdDevice = {0x00, 0x01}, + .iManufacturer = 1, + .iProduct = 2, + .bNumConfigurations = 1, +}; + +static const struct dwc_otg_config_desc dwc_otg_confd = { + .confd = { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(dwc_otg_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0, + }, + .ifcd = { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = 0, + }, + .endpd = { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = (UE_DIR_IN | DWC_OTG_INTR_ENDPT), + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 8, + .bInterval = 255, + }, +}; + +#define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) } + +static const struct usb_hub_descriptor_min dwc_otg_hubd = { + .bDescLength = sizeof(dwc_otg_hubd), + .bDescriptorType = UDESC_HUB, + .bNbrPorts = 1, + HSETW(.wHubCharacteristics, (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL)), + .bPwrOn2PwrGood = 50, + .bHubContrCurrent = 0, + .DeviceRemovable = {0}, /* port is removable */ +}; + +#define STRING_VENDOR \ + "D\0W\0C\0O\0T\0G" + +#define STRING_PRODUCT \ + "O\0T\0G\0 \0R\0o\0o\0t\0 \0H\0U\0B" + +USB_MAKE_STRING_DESC(STRING_VENDOR, dwc_otg_vendor); +USB_MAKE_STRING_DESC(STRING_PRODUCT, dwc_otg_product); + +static usb_error_t +dwc_otg_roothub_exec(struct usb_device *udev, + struct usb_device_request *req, const void **pptr, uint16_t *plength) +{ + struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(udev->bus); + const void *ptr; + uint16_t len; + uint16_t value; + uint16_t index; + usb_error_t err; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* buffer reset */ + ptr = (const void *)&sc->sc_hub_temp; + len = 0; + err = 0; + + value = UGETW(req->wValue); + index = UGETW(req->wIndex); + + /* demultiplex the control request */ + + switch (req->bmRequestType) { + case UT_READ_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_descriptor; + case UR_GET_CONFIG: + goto tr_handle_get_config; + case UR_GET_STATUS: + goto tr_handle_get_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_DEVICE: + switch (req->bRequest) { + case UR_SET_ADDRESS: + goto tr_handle_set_address; + case UR_SET_CONFIG: + goto tr_handle_set_config; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_DESCRIPTOR: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_WRITE_ENDPOINT: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + switch (UGETW(req->wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_clear_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_clear_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (UGETW(req->wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_set_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_set_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SYNCH_FRAME: + goto tr_valid; /* nop */ + default: + goto tr_stalled; + } + break; + + case UT_READ_ENDPOINT: + switch (req->bRequest) { + case UR_GET_STATUS: + goto tr_handle_get_ep_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_INTERFACE: + switch (req->bRequest) { + case UR_SET_INTERFACE: + goto tr_handle_set_interface; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_READ_INTERFACE: + switch (req->bRequest) { + case UR_GET_INTERFACE: + goto tr_handle_get_interface; + case UR_GET_STATUS: + goto tr_handle_get_iface_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_INTERFACE: + case UT_WRITE_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_READ_CLASS_INTERFACE: + case UT_READ_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_WRITE_CLASS_DEVICE: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + goto tr_valid; + case UR_SET_DESCRIPTOR: + case UR_SET_FEATURE: + break; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_OTHER: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + goto tr_handle_clear_port_feature; + case UR_SET_FEATURE: + goto tr_handle_set_port_feature; + case UR_CLEAR_TT_BUFFER: + case UR_RESET_TT: + case UR_STOP_TT: + goto tr_valid; + + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_OTHER: + switch (req->bRequest) { + case UR_GET_TT_STATE: + goto tr_handle_get_tt_state; + case UR_GET_STATUS: + goto tr_handle_get_port_status; + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_class_descriptor; + case UR_GET_STATUS: + goto tr_handle_get_class_status; + + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + goto tr_valid; + +tr_handle_get_descriptor: + switch (value >> 8) { + case UDESC_DEVICE: + if (value & 0xff) { + goto tr_stalled; + } + len = sizeof(dwc_otg_devd); + ptr = (const void *)&dwc_otg_devd; + goto tr_valid; + case UDESC_CONFIG: + if (value & 0xff) { + goto tr_stalled; + } + len = sizeof(dwc_otg_confd); + ptr = (const void *)&dwc_otg_confd; + goto tr_valid; + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + len = sizeof(usb_string_lang_en); + ptr = (const void *)&usb_string_lang_en; + goto tr_valid; + + case 1: /* Vendor */ + len = sizeof(dwc_otg_vendor); + ptr = (const void *)&dwc_otg_vendor; + goto tr_valid; + + case 2: /* Product */ + len = sizeof(dwc_otg_product); + ptr = (const void *)&dwc_otg_product; + goto tr_valid; + default: + break; + } + break; + default: + goto tr_stalled; + } + goto tr_stalled; + +tr_handle_get_config: + len = 1; + sc->sc_hub_temp.wValue[0] = sc->sc_conf; + goto tr_valid; + +tr_handle_get_status: + len = 2; + USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED); + goto tr_valid; + +tr_handle_set_address: + if (value & 0xFF00) { + goto tr_stalled; + } + sc->sc_rt_addr = value; + goto tr_valid; + +tr_handle_set_config: + if (value >= 2) { + goto tr_stalled; + } + sc->sc_conf = value; + goto tr_valid; + +tr_handle_get_interface: + len = 1; + sc->sc_hub_temp.wValue[0] = 0; + goto tr_valid; + +tr_handle_get_tt_state: +tr_handle_get_class_status: +tr_handle_get_iface_status: +tr_handle_get_ep_status: + len = 2; + USETW(sc->sc_hub_temp.wValue, 0); + goto tr_valid; + +tr_handle_set_halt: +tr_handle_set_interface: +tr_handle_set_wakeup: +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); + + switch (value) { + case UHF_PORT_SUSPEND: + dwc_otg_wakeup_peer(sc); + 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: + /* 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; + break; + + case UHF_C_PORT_SUSPEND: + sc->sc_flags.change_suspend = 0; + break; + + default: + err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_set_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(9, "UR_SET_PORT_FEATURE\n"); + + switch (value) { + case UHF_PORT_ENABLE: + 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 */ + (void) 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: + sc->sc_flags.port_powered = 1; + 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); + } + if (sc->sc_mode == DWC_MODE_DEVICE || sc->sc_mode == DWC_MODE_OTG) { + /* pull up D+, if any */ + dwc_otg_pull_up(sc); + } + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_get_port_status: + + DPRINTFN(9, "UR_GET_PORT_STATUS\n"); + + 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_high_speed) + value |= UPS_HIGH_SPEED; + 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) + 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) + value |= UPS_CURRENT_CONNECT_STATUS; + + 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) + value |= UPS_C_CONNECT_STATUS; + 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; + +tr_handle_get_class_descriptor: + if (value & 0xFF) { + goto tr_stalled; + } + ptr = (const void *)&dwc_otg_hubd; + len = sizeof(dwc_otg_hubd); + goto tr_valid; + +tr_stalled: + err = USB_ERR_STALLED; +tr_valid: +done: + *plength = len; + *pptr = ptr; + return (err); +} + +static void +dwc_otg_xfer_setup(struct usb_setup_params *parm) +{ + struct usb_xfer *xfer; + void *last_obj; + uint32_t ntd; + uint32_t n; + uint8_t ep_no; + uint8_t ep_type; + + xfer = parm->curr_xfer; + + /* + * NOTE: This driver does not use any of the parameters that + * are computed from the following values. Just set some + * reasonable dummies: + */ + parm->hc_max_packet_size = 0x500; + parm->hc_max_packet_count = 3; + parm->hc_max_frame_size = 3 * 0x500; + + usbd_transfer_setup_sub(parm); + + /* + * compute maximum number of TDs + */ + ep_type = (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE); + + if (ep_type == UE_CONTROL) { + + ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC 1 */ + + 1 /* SYNC 2 */ + 1 /* SYNC 3 */; + } else { + + ntd = xfer->nframes + 1 /* SYNC */ ; + } + + /* + * check if "usbd_transfer_setup_sub" set an error + */ + if (parm->err) + return; + + /* + * allocate transfer descriptors + */ + last_obj = NULL; + + ep_no = xfer->endpointno & UE_ADDR; + + /* + * Check for a valid endpoint profile in USB device mode: + */ + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { + const struct usb_hw_ep_profile *pf; + + dwc_otg_get_hw_ep_profile(parm->udev, &pf, ep_no); + + if (pf == NULL) { + /* should not happen */ + parm->err = USB_ERR_INVAL; + return; + } + } + + /* align data */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + + for (n = 0; n != ntd; n++) { + + struct dwc_otg_td *td; + + if (parm->buf) { + + td = USB_ADD_BYTES(parm->buf, parm->size[0]); + + /* compute shared bandwidth resource index for TT */ + if (parm->udev->parent_hs_hub != NULL && parm->udev->speed != USB_SPEED_HIGH) { + if (parm->udev->parent_hs_hub->ddesc.bDeviceProtocol == UDPROTO_HSHUBMTT) + td->tt_index = parm->udev->device_index; + else + td->tt_index = parm->udev->parent_hs_hub->device_index; + } else { + td->tt_index = parm->udev->device_index; + } + + /* init TD */ + td->max_packet_size = xfer->max_packet_size; + td->max_packet_count = xfer->max_packet_count; + /* range check */ + if (td->max_packet_count == 0 || td->max_packet_count > 3) + td->max_packet_count = 1; + td->ep_no = ep_no; + td->ep_type = ep_type; + td->obj_next = last_obj; + + last_obj = td; + } + parm->size[0] += sizeof(*td); + } + + xfer->td_start[0] = last_obj; +} + +static void +dwc_otg_xfer_unsetup(struct usb_xfer *xfer) +{ + return; +} + +static void +dwc_otg_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, + struct usb_endpoint *ep) +{ + struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(udev->bus); + + DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d,%d)\n", + ep, udev->address, + edesc->bEndpointAddress, udev->flags.usb_mode, + sc->sc_rt_addr, udev->device_index); + + if (udev->device_index != sc->sc_rt_addr) { + + 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_HIGH && + (edesc->wMaxPacketSize[1] & 0x18) != 0 && + (edesc->bmAttributes & UE_XFERTYPE) != UE_ISOCHRONOUS) { + /* not supported */ + DPRINTFN(-1, "Non-isochronous high bandwidth " + "endpoint not supported\n"); + return; + } + } + if ((edesc->bmAttributes & UE_XFERTYPE) == UE_ISOCHRONOUS) + ep->methods = &dwc_otg_device_isoc_methods; + else + ep->methods = &dwc_otg_device_non_isoc_methods; + } +} + +static void +dwc_otg_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) +{ + struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(bus); + + switch (state) { + case USB_HW_POWER_SUSPEND: + dwc_otg_suspend(sc); + break; + case USB_HW_POWER_SHUTDOWN: + dwc_otg_uninit(sc); + break; + case USB_HW_POWER_RESUME: + dwc_otg_resume(sc); + break; + default: + break; + } +} + +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) +{ + DPRINTF("\n"); + + /* poll all transfers again to restart resumed ones */ + dwc_otg_do_poll(udev->bus); +} + +static void +dwc_otg_device_suspend(struct usb_device *udev) +{ + DPRINTF("\n"); +} + +static const struct usb_bus_methods dwc_otg_bus_methods = +{ + .endpoint_init = &dwc_otg_ep_init, + .xfer_setup = &dwc_otg_xfer_setup, + .xfer_unsetup = &dwc_otg_xfer_unsetup, + .get_hw_ep_profile = &dwc_otg_get_hw_ep_profile, + .xfer_stall = &dwc_otg_xfer_stall, + .set_stall = &dwc_otg_set_stall, + .clear_stall = &dwc_otg_clear_stall, + .roothub_exec = &dwc_otg_roothub_exec, + .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, +}; diff --git a/freebsd/sys/dev/usb/controller/dwc_otg.h b/freebsd/sys/dev/usb/controller/dwc_otg.h new file mode 100644 index 00000000..038b072a --- /dev/null +++ b/freebsd/sys/dev/usb/controller/dwc_otg.h @@ -0,0 +1,218 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2012 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _DWC_OTG_H_ +#define _DWC_OTG_H_ + +#define DWC_OTG_MAX_DEVICES MIN(USB_MAX_DEVICES, 32) +#define DWC_OTG_FRAME_MASK 0x7FF +#define DWC_OTG_MAX_TXP 4 +#define DWC_OTG_MAX_TXN (0x200 * DWC_OTG_MAX_TXP) +#define DWC_OTG_MAX_CHANNELS 16 +#define DWC_OTG_MAX_ENDPOINTS 16 +#define DWC_OTG_HOST_TIMER_RATE 10 /* ms */ +#define DWC_OTG_TT_SLOT_MAX 8 +#define DWC_OTG_SLOT_IDLE_MAX 3 +#define DWC_OTG_SLOT_IDLE_MIN 2 +#ifndef DWC_OTG_TX_MAX_FIFO_SIZE +#define DWC_OTG_TX_MAX_FIFO_SIZE DWC_OTG_MAX_TXN +#endif + +#define DWC_OTG_READ_4(sc, reg) \ + bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg) + +#define DWC_OTG_WRITE_4(sc, reg, data) \ + bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data) + +struct dwc_otg_td; +struct dwc_otg_softc; + +typedef uint8_t (dwc_otg_cmd_t)(struct dwc_otg_softc *sc, struct dwc_otg_td *td); + +struct dwc_otg_td { + struct dwc_otg_td *obj_next; + dwc_otg_cmd_t *func; + struct usb_page_cache *pc; + uint32_t tx_bytes; + uint32_t offset; + uint32_t remainder; + uint32_t hcchar; /* HOST CFG */ + uint32_t hcsplt; /* HOST CFG */ + uint16_t max_packet_size; /* packet_size */ + uint16_t npkt; + uint8_t max_packet_count; /* packet_count */ + uint8_t errcnt; + uint8_t tmr_res; + uint8_t tmr_val; + uint8_t ep_no; + uint8_t ep_type; + uint8_t channel[3]; + uint8_t tt_index; /* TT data */ + uint8_t tt_start_slot; /* TT data */ + uint8_t tt_complete_slot; /* TT data */ + uint8_t tt_xactpos; /* TT data */ + uint8_t state; +#define DWC_CHAN_ST_START 0 +#define DWC_CHAN_ST_WAIT_ANE 1 +#define DWC_CHAN_ST_WAIT_S_ANE 2 +#define DWC_CHAN_ST_WAIT_C_ANE 3 +#define DWC_CHAN_ST_WAIT_C_PKT 4 +#define DWC_CHAN_ST_TX_WAIT_ISOC 5 + uint8_t error_any:1; + uint8_t error_stall:1; + uint8_t alt_next:1; + uint8_t short_pkt:1; + uint8_t did_stall:1; + uint8_t toggle:1; + uint8_t set_toggle:1; + uint8_t got_short:1; + uint8_t tt_scheduled:1; + uint8_t did_nak:1; +}; + +struct dwc_otg_tt_info { + uint8_t slot_index; +}; + +struct dwc_otg_std_temp { + dwc_otg_cmd_t *func; + struct usb_page_cache *pc; + struct dwc_otg_td *td; + struct dwc_otg_td *td_next; + uint32_t len; + uint32_t offset; + uint16_t max_frame_size; + uint8_t short_pkt; + + /* + * short_pkt = 0: transfer should be short terminated + * short_pkt = 1: transfer should not be short terminated + */ + uint8_t setup_alt_next; + uint8_t did_stall; + uint8_t bulk_or_control; +}; + +struct dwc_otg_config_desc { + struct usb_config_descriptor confd; + struct usb_interface_descriptor ifcd; + struct usb_endpoint_descriptor endpd; +} __packed; + +union dwc_otg_hub_temp { + uWord wValue; + struct usb_port_status ps; +}; + +struct dwc_otg_flags { + uint8_t change_connect:1; + uint8_t change_suspend:1; + uint8_t change_reset:1; + uint8_t change_enabled:1; + uint8_t change_over_current:1; + uint8_t status_suspend:1; /* set if suspended */ + uint8_t status_vbus:1; /* set if present */ + uint8_t status_bus_reset:1; /* set if reset complete */ + uint8_t status_high_speed:1; /* set if High Speed is selected */ + uint8_t status_low_speed:1; /* set if Low Speed is selected */ + uint8_t status_device_mode:1; /* set if device mode */ + uint8_t self_powered:1; + uint8_t clocks_off:1; + uint8_t port_powered:1; + uint8_t port_enabled:1; + uint8_t port_over_current:1; + uint8_t d_pulled_up:1; +}; + +struct dwc_otg_profile { + struct usb_hw_ep_profile usb; + uint16_t max_buffer; +}; + +struct dwc_otg_chan_state { + uint16_t allocated; + uint16_t wait_halted; + uint32_t hcint; +}; + +struct dwc_otg_softc { + struct usb_bus sc_bus; + union dwc_otg_hub_temp sc_hub_temp; + struct dwc_otg_profile sc_hw_ep_profile[DWC_OTG_MAX_ENDPOINTS]; + struct dwc_otg_tt_info sc_tt_info[DWC_OTG_MAX_DEVICES]; + struct usb_callout sc_timer; + + struct usb_device *sc_devices[DWC_OTG_MAX_DEVICES]; + struct resource *sc_io_res; + struct resource *sc_irq_res; + void *sc_intr_hdl; + bus_size_t sc_io_size; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + + uint32_t sc_bounce_buffer[MAX(512 * DWC_OTG_MAX_TXP, 1024) / 4]; + + uint32_t sc_fifo_size; + uint32_t sc_irq_mask; + uint32_t sc_last_rx_status; + uint32_t sc_out_ctl[DWC_OTG_MAX_ENDPOINTS]; + uint32_t sc_in_ctl[DWC_OTG_MAX_ENDPOINTS]; + struct dwc_otg_chan_state sc_chan_state[DWC_OTG_MAX_CHANNELS]; + uint32_t sc_tmr_val; + uint32_t sc_hprt_val; + uint32_t sc_xfer_complete; + + uint16_t sc_current_rx_bytes; + uint16_t sc_current_rx_fifo; + + uint16_t sc_active_rx_ep; + uint16_t sc_last_frame_num; + + uint8_t sc_timer_active; + uint8_t sc_dev_ep_max; + uint8_t sc_dev_in_ep_max; + uint8_t sc_host_ch_max; + uint8_t sc_needsof; + uint8_t sc_rt_addr; /* root HUB address */ + uint8_t sc_conf; /* root HUB config */ + uint8_t sc_mode; /* mode of operation */ +#define DWC_MODE_OTG 0 /* both modes */ +#define DWC_MODE_DEVICE 1 /* device only */ +#define DWC_MODE_HOST 2 /* host only */ + + uint8_t sc_hub_idata[1]; + + struct dwc_otg_flags sc_flags; +}; + +/* prototypes */ + +driver_filter_t dwc_otg_filter_interrupt; +driver_intr_t dwc_otg_interrupt; +int dwc_otg_init(struct dwc_otg_softc *); +void dwc_otg_uninit(struct dwc_otg_softc *); + +#endif /* _DWC_OTG_H_ */ diff --git a/freebsd/sys/dev/usb/controller/dwc_otgreg.h b/freebsd/sys/dev/usb/controller/dwc_otgreg.h new file mode 100644 index 00000000..8b9538ae --- /dev/null +++ b/freebsd/sys/dev/usb/controller/dwc_otgreg.h @@ -0,0 +1,802 @@ +/* $FreeBSD$ */ + +/*- + * 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 + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _DWC_OTGREG_H_ +#define _DWC_OTGREG_H_ + +#define DOTG_GOTGCTL 0x0000 +#define DOTG_GOTGINT 0x0004 +#define DOTG_GAHBCFG 0x0008 +#define DOTG_GUSBCFG 0x000C +#define DOTG_GRSTCTL 0x0010 +#define DOTG_GINTSTS 0x0014 +#define DOTG_GINTMSK 0x0018 +#define DOTG_GRXSTSRD 0x001C +#define DOTG_GRXSTSRH 0x001C +#define DOTG_GRXSTSPD 0x0020 +#define DOTG_GRXSTSPH 0x0020 +#define DOTG_GRXFSIZ 0x0024 +#define DOTG_GNPTXFSIZ 0x0028 +#define DOTG_GNPTXSTS 0x002C +#define DOTG_GI2CCTL 0x0030 +#define DOTG_GPVNDCTL 0x0034 +#define DOTG_GGPIO 0x0038 +#define DOTG_GUID 0x003C +#define DOTG_GSNPSID 0x0040 +#define DOTG_GSNPSID_REV_2_80a 0x4f54280a /* RPi model B/RPi2 */ +#define DOTG_GSNPSID_REV_3_10a 0x4f54310a /* ODROID-C1 */ +#define DOTG_GHWCFG1 0x0044 +#define DOTG_GHWCFG2 0x0048 +#define DOTG_GHWCFG3 0x004C +#define DOTG_GHWCFG4 0x0050 +#define DOTG_GLPMCFG 0x0054 +#define DOTG_GPWRDN 0x0058 +#define DOTG_GDFIFOCFG 0x005C +#define DOTG_GADPCTL 0x0060 + +#define DOTG_HPTXFSIZ 0x0100 +/* start from 0x104, but fifo0 not exists */ +#define DOTG_DPTXFSIZ(fifo) (0x0100 + (4*(fifo))) +#define DOTG_DIEPTXF(fifo) (0x0100 + (4*(fifo))) + +#define DOTG_HCFG 0x0400 +#define DOTG_HFIR 0x0404 +#define DOTG_HFNUM 0x0408 +#define DOTG_HPTXSTS 0x0410 +#define DOTG_HAINT 0x0414 +#define DOTG_HAINTMSK 0x0418 +#define DOTG_HPRT 0x0440 + +#define DOTG_HCCHAR(ch) (0x0500 + (32*(ch))) +#define DOTG_HCSPLT(ch) (0x0504 + (32*(ch))) +#define DOTG_HCINT(ch) (0x0508 + (32*(ch))) +#define DOTG_HCINTMSK(ch) (0x050C + (32*(ch))) +#define DOTG_HCTSIZ(ch) (0x0510 + (32*(ch))) +#define DOTG_HCDMA(ch) (0x0514 + (32*(ch))) +#define DOTG_HCDMAI(ch) (0x0514 + (32*(ch))) +#define DOTG_HCDMAO(ch) (0x0514 + (32*(ch))) +#define DOTG_HCDMAB(ch) (0x051C + (32*(ch))) + +/* Device Mode */ +#define DOTG_DCFG 0x0800 +#define DOTG_DCTL 0x0804 +#define DOTG_DSTS 0x0808 +#define DOTG_DIEPMSK 0x0810 +#define DOTG_DOEPMSK 0x0814 +#define DOTG_DAINT 0x0818 +#define DOTG_DAINTMSK 0x081C +#define DOTG_DTKNQR1 0x0820 +#define DOTG_DTKNQR2 0x0824 +#define DOTG_DVBUSDIS 0x0828 +#define DOTG_DVBUSPULSE 0x082C +#define DOTG_DTHRCTL 0x0830 +#define DOTG_DTKNQR4 0x0834 +#define DOTG_DIEPEMPMSK 0x0834 +#define DOTG_DEACHINT 0x0838 +#define DOTG_DEACHINTMSK 0x083C +#define DOTG_DIEPEACHINTMSK(ch) (0x0840 + (4*(ch))) +#define DOTG_DOEPEACHINTMSK(ch) (0x0880 + (4*(ch))) + +#define DOTG_DIEPCTL(ep) (0x0900 + (32*(ep))) +#define DOTG_DIEPINT(ep) (0x0908 + (32*(ep))) +#define DOTG_DIEPTSIZ(ep) (0x0910 + (32*(ep))) +#define DOTG_DIEPDMA(ep) (0x0914 + (32*(ep))) +#define DOTG_DTXFSTS(ep) (0x0918 + (32*(ep))) +#define DOTG_DIEPDMAB(ep) (0x091c + (32*(ep))) + +#define DOTG_DOEPCTL(ep) (0x0B00 + (32*(ep))) +#define DOTG_DOEPFN(ep) (0x0B04 + (32*(ep))) +#define DOTG_DOEPINT(ep) (0x0B08 + (32*(ep))) +#define DOTG_DOEPTSIZ(ep) (0x0B10 + (32*(ep))) +#define DOTG_DOEPDMA(ep) (0x0B14 + (32*(ep))) +#define DOTG_DOEPDMAB(ep) (0x0B1c + (32*(ep))) +/* End Device Mode */ + +/* Host Mode +#define DOTG_CTL_STATUS 0x0800 +#define DOTG_DMA0_INB_CHN0 0x0818 +#define DOTG_DMA0_INB_CHN1 0x0820 +#define DOTG_DMA0_INB_CHN2 0x0828 +#define DOTG_DVBUSDIS 0x0828 +#define DOTG_DVBUSPULSE 0x082c +#define DOTG_DMA0_INB_CHN3 0x0830 +#define DOTG_DMA0_INB_CHN4 0x0838 +#define DOTG_DMA0_INB_CHN5 0x0840 +#define DOTG_DMA0_INB_CHN6 0x0848 +#define DOTG_DMA0_INB_CHN7 0x0850 +#define DOTG_DMA0_OUTB_CHN0 0x0858 +#define DOTG_DMA0_OUTB_CHN1 0x0860 +#define DOTG_DMA0_OUTB_CHN2 0x0868 +#define DOTG_DMA0_OUTB_CHN3 0x0870 +#define DOTG_DMA0_OUTB_CHN4 0x0878 +#define DOTG_DMA0_OUTB_CHN5 0x0880 +#define DOTG_DMA0_OUTB_CHN6 0x0888 +#define DOTG_DMA0_OUTB_CHN7 0x0890 + End Host Mode */ + +/* Power and clock gating CSR */ + +#define DOTG_PCGCCTL 0x0E00 + +/* FIFO access registers (PIO-mode) */ + +#define DOTG_DFIFO(n) (0x1000 + (0x1000 * (n))) + +#define GOTGCTL_CHIRP_ON (1<<27) +#define GOTGCTL_BSESVLD (1<<19) +#define GOTGCTL_ASESVLD (1<<18) +#define GOTGCTL_DBNCTIME (1<<17) +#define GOTGCTL_CONIDSTS (1<<16) +#define GOTGCTL_DEVHNPEN (1<<11) +#define GOTGCTL_HSTSETHNPEN (1<<10) +#define GOTGCTL_HNPREQ (1<<9) +#define GOTGCTL_HSTNEGSCS (1<<8) +#define GOTGCTL_SESREQ (1<<1) +#define GOTGCTL_SESREQSCS (1<<0) + +#define GOTGCTL_DBNCEDONE (1<<19) +#define GOTGCTL_ADEVTOUTCHG (1<<18) +#define GOTGCTL_HSTNEGDET (1<<17) +#define GOTGCTL_HSTNEGSUCSTSCHG (1<<9) +#define GOTGCTL_SESREQSUCSTSCHG (1<<8) +#define GOTGCTL_SESENDDET (1<<2) + +#define GAHBCFG_PTXFEMPLVL (1<<8) +#define GAHBCFG_NPTXFEMPLVL (1<<7) +#define GAHBCFG_DMAEN (1<<5) +#define GAHBCFG_HBSTLEN_MASK 0x0000001e +#define GAHBCFG_HBSTLEN_SHIFT 1 +#define GAHBCFG_GLBLINTRMSK (1<<0) + +#define GUSBCFG_CORRUPTTXPACKET (1<<31) +#define GUSBCFG_FORCEDEVMODE (1<<30) +#define GUSBCFG_FORCEHOSTMODE (1<<29) +#define GUSBCFG_NO_PULLUP (1<<27) +#define GUSBCFG_IC_USB_CAP (1<<26) +#define GUSBCFG_TERMSELDLPULSE (1<<22) +#define GUSBCFG_ULPIEXTVBUSINDICATOR (1<<21) +#define GUSBCFG_ULPIEXTVBUSDRV (1<<20) +#define GUSBCFG_ULPICLKSUSM (1<<19) +#define GUSBCFG_ULPIAUTORES (1<<18) +#define GUSBCFG_ULPIFSLS (1<<17) +#define GUSBCFG_OTGI2CSEL (1<<16) +#define GUSBCFG_PHYLPWRCLKSEL (1<<15) +#define GUSBCFG_USBTRDTIM_MASK 0x00003c00 +#define GUSBCFG_USBTRDTIM_SHIFT 10 +#define GUSBCFG_TRD_TIM_SET(x) (((x) & 15) << 10) +#define GUSBCFG_HNPCAP (1<<9) +#define GUSBCFG_SRPCAP (1<<8) +#define GUSBCFG_DDRSEL (1<<7) +#define GUSBCFG_PHYSEL (1<<6) +#define GUSBCFG_FSINTF (1<<5) +#define GUSBCFG_ULPI_UTMI_SEL (1<<4) +#define GUSBCFG_PHYIF (1<<3) +#define GUSBCFG_TOUTCAL_MASK 0x00000007 +#define GUSBCFG_TOUTCAL_SHIFT 0 + +/* STM32F4 */ +#define DOTG_GGPIO_NOVBUSSENS (1 << 21) +#define DOTG_GGPIO_SOFOUTEN (1 << 20) +#define DOTG_GGPIO_VBUSBSEN (1 << 19) +#define DOTG_GGPIO_VBUSASEN (1 << 18) +#define DOTG_GGPIO_I2CPADEN (1 << 17) +#define DOTG_GGPIO_PWRDWN (1 << 16) + +#define GRSTCTL_AHBIDLE (1<<31) +#define GRSTCTL_DMAREQ (1<<30) +#define GRSTCTL_TXFNUM_MASK 0x000007c0 +#define GRSTCTL_TXFNUM_SHIFT 6 +#define GRSTCTL_TXFIFO(n) (((n) & 31) << 6) +#define GRSTCTL_TXFFLSH (1<<5) +#define GRSTCTL_RXFFLSH (1<<4) +#define GRSTCTL_INTKNQFLSH (1<<3) +#define GRSTCTL_FRMCNTRRST (1<<2) +#define GRSTCTL_HSFTRST (1<<1) +#define GRSTCTL_CSFTRST (1<<0) + +#define GINTSTS_WKUPINT (1<<31) +#define GINTSTS_SESSREQINT (1<<30) +#define GINTSTS_DISCONNINT (1<<29) +#define GINTSTS_CONIDSTSCHNG (1<<28) +#define GINTSTS_LPM (1<<27) +#define GINTSTS_PTXFEMP (1<<26) +#define GINTSTS_HCHINT (1<<25) +#define GINTSTS_PRTINT (1<<24) +#define GINTSTS_RESETDET (1<<23) +#define GINTSTS_FETSUSP (1<<22) +#define GINTSTS_INCOMPLP (1<<21) +#define GINTSTS_INCOMPISOIN (1<<20) +#define GINTSTS_OEPINT (1<<19) +#define GINTSTS_IEPINT (1<<18) +#define GINTSTS_EPMIS (1<<17) +#define GINTSTS_RESTORE_DONE (1<<16) +#define GINTSTS_EOPF (1<<15) +#define GINTSTS_ISOOUTDROP (1<<14) +#define GINTSTS_ENUMDONE (1<<13) +#define GINTSTS_USBRST (1<<12) +#define GINTSTS_USBSUSP (1<<11) +#define GINTSTS_ERLYSUSP (1<<10) +#define GINTSTS_I2CINT (1<<9) +#define GINTSTS_ULPICKINT (1<<8) +#define GINTSTS_GOUTNAKEFF (1<<7) +#define GINTSTS_GINNAKEFF (1<<6) +#define GINTSTS_NPTXFEMP (1<<5) +#define GINTSTS_RXFLVL (1<<4) +#define GINTSTS_SOF (1<<3) +#define GINTSTS_OTGINT (1<<2) +#define GINTSTS_MODEMIS (1<<1) +#define GINTSTS_CURMOD (1<<0) + +#define GINTMSK_WKUPINTMSK (1<<31) +#define GINTMSK_SESSREQINTMSK (1<<30) +#define GINTMSK_DISCONNINTMSK (1<<29) +#define GINTMSK_CONIDSTSCHNGMSK (1<<28) +#define GINTMSK_PTXFEMPMSK (1<<26) +#define GINTMSK_HCHINTMSK (1<<25) +#define GINTMSK_PRTINTMSK (1<<24) +#define GINTMSK_FETSUSPMSK (1<<22) +#define GINTMSK_INCOMPLPMSK (1<<21) +#define GINTMSK_INCOMPISOINMSK (1<<20) +#define GINTMSK_OEPINTMSK (1<<19) +#define GINTMSK_IEPINTMSK (1<<18) +#define GINTMSK_EPMISMSK (1<<17) +#define GINTMSK_EOPFMSK (1<<15) +#define GINTMSK_ISOOUTDROPMSK (1<<14) +#define GINTMSK_ENUMDONEMSK (1<<13) +#define GINTMSK_USBRSTMSK (1<<12) +#define GINTMSK_USBSUSPMSK (1<<11) +#define GINTMSK_ERLYSUSPMSK (1<<10) +#define GINTMSK_I2CINTMSK (1<<9) +#define GINTMSK_ULPICKINTMSK (1<<8) +#define GINTMSK_GOUTNAKEFFMSK (1<<7) +#define GINTMSK_GINNAKEFFMSK (1<<6) +#define GINTMSK_NPTXFEMPMSK (1<<5) +#define GINTMSK_RXFLVLMSK (1<<4) +#define GINTMSK_SOFMSK (1<<3) +#define GINTMSK_OTGINTMSK (1<<2) +#define GINTMSK_MODEMISMSK (1<<1) +#define GINTMSK_CURMODMSK (1<<0) + +#define GRXSTSRH_PKTSTS_MASK 0x001e0000 +#define GRXSTSRH_PKTSTS_SHIFT 17 +#define GRXSTSRH_DPID_MASK 0x00018000 +#define GRXSTSRH_DPID_SHIFT 15 +#define GRXSTSRH_BCNT_MASK 0x00007ff0 +#define GRXSTSRH_BCNT_SHIFT 4 +#define GRXSTSRH_CHNUM_MASK 0x0000000f +#define GRXSTSRH_CHNUM_SHIFT 0 + +#define GRXSTSRD_FN_MASK 0x01e00000 +#define GRXSTSRD_FN_GET(x) (((x) >> 21) & 15) +#define GRXSTSRD_FN_SHIFT 21 +#define GRXSTSRD_PKTSTS_MASK 0x001e0000 +#define GRXSTSRD_PKTSTS_SHIFT 17 +#define GRXSTSRH_IN_DATA (2<<17) +#define GRXSTSRH_IN_COMPLETE (3<<17) +#define GRXSTSRH_DT_ERROR (5<<17) +#define GRXSTSRH_HALTED (7<<17) +#define GRXSTSRD_GLOB_OUT_NAK (1<<17) +#define GRXSTSRD_OUT_DATA (2<<17) +#define GRXSTSRD_OUT_COMPLETE (3<<17) +#define GRXSTSRD_STP_COMPLETE (4<<17) +#define GRXSTSRD_STP_DATA (6<<17) +#define GRXSTSRD_DPID_MASK 0x00018000 +#define GRXSTSRD_DPID_SHIFT 15 +#define GRXSTSRD_DPID_DATA0 (0<<15) +#define GRXSTSRD_DPID_DATA1 (2<<15) +#define GRXSTSRD_DPID_DATA2 (1<<15) +#define GRXSTSRD_DPID_MDATA (3<<15) +#define GRXSTSRD_BCNT_MASK 0x00007ff0 +#define GRXSTSRD_BCNT_GET(x) (((x) >> 4) & 0x7FF) +#define GRXSTSRD_BCNT_SHIFT 4 +#define GRXSTSRD_CHNUM_MASK 0x0000000f +#define GRXSTSRD_CHNUM_GET(x) ((x) & 15) +#define GRXSTSRD_CHNUM_SHIFT 0 + +#define GRXFSIZ_RXFDEP_MASK 0x0000ffff +#define GRXFSIZ_RXFDEP_SHIFT 0 + +#define GNPTXFSIZ_NPTXFDEP_MASK 0xffff0000 +#define GNPTXFSIZ_NPTXFDEP_SHIFT 0 +#define GNPTXFSIZ_NPTXFSTADDR_MASK 0x0000ffff +#define GNPTXFSIZ_NPTXFSTADDR_SHIFT 16 + +#define GNPTXSTS_NPTXQTOP_SHIFT 24 +#define GNPTXSTS_NPTXQTOP_MASK 0x7f000000 +#define GNPTXSTS_NPTXQSPCAVAIL_SHIFT 16 +#define GNPTXSTS_NPTXQSPCAVAIL_MASK 0x00ff0000 +#define GNPTXSTS_NPTXFSPCAVAIL_SHIFT 0 +#define GNPTXSTS_NPTXFSPCAVAIL_MASK 0x0000ffff + +#define GI2CCTL_BSYDNE_SC (1<<31) +#define GI2CCTL_RW (1<<30) +#define GI2CCTL_I2CDATSE0 (1<<28) +#define GI2CCTL_I2CDEVADR_SHIFT 26 +#define GI2CCTL_I2CDEVADR_MASK 0x0c000000 +#define GI2CCTL_I2CSUSPCTL (1<<25) +#define GI2CCTL_ACK (1<<24) +#define GI2CCTL_I2CEN (1<<23) +#define GI2CCTL_ADDR_SHIFT 16 +#define GI2CCTL_ADDR_MASK 0x007f0000 +#define GI2CCTL_REGADDR_SHIFT 8 +#define GI2CCTL_REGADDR_MASK 0x0000ff00 +#define GI2CCTL_RWDATA_SHIFT 0 +#define GI2CCTL_RWDATA_MASK 0x000000ff + +#define GPVNDCTL_DISULPIDRVR (1<<31) +#define GPVNDCTL_VSTSDONE (1<<27) +#define GPVNDCTL_VSTSBSY (1<<26) +#define GPVNDCTL_NEWREGREQ (1<<25) +#define GPVNDCTL_REGWR (1<<22) +#define GPVNDCTL_REGADDR_SHIFT 16 +#define GPVNDCTL_REGADDR_MASK 0x003f0000 +#define GPVNDCTL_VCTRL_SHIFT 8 +#define GPVNDCTL_VCTRL_MASK 0x0000ff00 +#define GPVNDCTL_REGDATA_SHIFT 0 +#define GPVNDCTL_REGDATA_MASK 0x000000ff + +#define GGPIO_GPO_SHIFT 16 +#define GGPIO_GPO_MASK 0xffff0000 +#define GGPIO_GPI_SHIFT 0 +#define GGPIO_GPI_MASK 0x0000ffff + +#define GHWCFG1_GET_DIR(x, n) (((x) >> (2 * (n))) & 3) +#define GHWCFG1_BIDIR 0 +#define GHWCFG1_IN 1 +#define GHWCFG1_OUT 2 + +#define GHWCFG2_TKNQDEPTH_SHIFT 26 +#define GHWCFG2_TKNQDEPTH_MASK 0x7c000000 +#define GHWCFG2_PTXQDEPTH_SHIFT 24 +#define GHWCFG2_PTXQDEPTH_MASK 0x03000000 +#define GHWCFG2_NPTXQDEPTH_SHIFT 22 +#define GHWCFG2_NPTXQDEPTH_MASK 0x00c00000 +#define GHWCFG2_MPI (1<<20) +#define GHWCFG2_DYNFIFOSIZING (1<<19) +#define GHWCFG2_PERIOSUPPORT (1<<18) +#define GHWCFG2_NUMHSTCHNL_SHIFT 14 +#define GHWCFG2_NUMHSTCHNL_MASK 0x0003c000 +#define GHWCFG2_NUMHSTCHNL_GET(x) ((((x) >> 14) & 15) + 1) +#define GHWCFG2_NUMDEVEPS_SHIFT 10 +#define GHWCFG2_NUMDEVEPS_MASK 0x00003c00 +#define GHWCFG2_NUMDEVEPS_GET(x) ((((x) >> 10) & 15) + 1) +#define GHWCFG2_FSPHYTYPE_SHIFT 8 +#define GHWCFG2_FSPHYTYPE_MASK 0x00000300 +#define GHWCFG2_HSPHYTYPE_SHIFT 6 +#define GHWCFG2_HSPHYTYPE_MASK 0x000000c0 +#define GHWCFG2_SINGPNT (1<<5) +#define GHWCFG2_OTGARCH_SHIFT 3 +#define GHWCFG2_OTGARCH_MASK 0x00000018 +#define GHWCFG2_OTGMODE_SHIFT 0 +#define GHWCFG2_OTGMODE_MASK 0x00000007 + +#define GHWCFG3_DFIFODEPTH_SHIFT 16 +#define GHWCFG3_DFIFODEPTH_MASK 0xffff0000 +#define GHWCFG3_DFIFODEPTH_GET(x) ((x) >> 16) +#define GHWCFG3_RSTTYPE (1<<11) +#define GHWCFG3_OPTFEATURE (1<<10) +#define GHWCFG3_VNDCTLSUPT (1<<9) +#define GHWCFG3_I2CINTSEL (1<<8) +#define GHWCFG3_OTGEN (1<<7) +#define GHWCFG3_PKTSIZEWIDTH_SHIFT 4 +#define GHWCFG3_PKTSIZEWIDTH_MASK 0x00000070 +#define GHWCFG3_PKTSIZE_GET(x) (0x10<<(((x) >> 4) & 7)) +#define GHWCFG3_XFERSIZEWIDTH_SHIFT 0 +#define GHWCFG3_XFERSIZEWIDTH_MASK 0x0000000f +#define GHWCFG3_XFRRSIZE_GET(x) (0x400<<(((x) >> 0) & 15)) + +#define GHWCFG4_NUM_IN_EP_GET(x) ((((x) >> 26) & 15) + 1) +#define GHWCFG4_SESSENDFLTR (1<<24) +#define GHWCFG4_BVALIDFLTR (1<<23) +#define GHWCFG4_AVALIDFLTR (1<<22) +#define GHWCFG4_VBUSVALIDFLTR (1<<21) +#define GHWCFG4_IDDGFLTR (1<<20) +#define GHWCFG4_NUMCTLEPS_SHIFT 16 +#define GHWCFG4_NUMCTLEPS_MASK 0x000f0000 +#define GHWCFG4_NUMCTLEPS_GET(x) (((x) >> 16) & 15) +#define GHWCFG4_PHYDATAWIDTH_SHIFT 14 +#define GHWCFG4_PHYDATAWIDTH_MASK 0x0000c000 +#define GHWCFG4_AHBFREQ (1<<5) +#define GHWCFG4_ENABLEPWROPT (1<<4) +#define GHWCFG4_NUMDEVPERIOEPS_SHIFT 0 +#define GHWCFG4_NUMDEVPERIOEPS_MASK 0x0000000f +#define GHWCFG4_NUMDEVPERIOEPS_GET(x) (((x) >> 0) & 15) + +#define GLPMCFG_HSIC_CONN (1<<30) + +#define GPWRDN_BVALID (1<<22) +#define GPWRDN_IDDIG (1<<21) +#define GPWRDN_CONNDET_INT (1<<14) +#define GPWRDN_CONNDET (1<<13) +#define GPWRDN_DISCONN_INT (1<<12) +#define GPWRDN_DISCONN (1<<11) +#define GPWRDN_RESETDET_INT (1<<10) +#define GPWRDN_RESETDET (1<<9) +#define GPWRDN_LINESTATE_INT (1<<8) +#define GPWRDN_LINESTATE (1<<7) +#define GPWRDN_DISABLE_VBUS (1<<6) +#define GPWRDN_POWER_DOWN (1<<5) +#define GPWRDN_POWER_DOWN_RST (1<<4) +#define GPWRDN_POWER_DOWN_CLAMP (1<<3) +#define GPWRDN_RESTORE (1<<2) +#define GPWRDN_PMU_ACTIVE (1<<1) +#define GPWRDN_PMU_IRQ_SEL (1<<0) + +#define HPTXFSIZ_PTXFSIZE_SHIFT 16 +#define HPTXFSIZ_PTXFSIZE_MASK 0xffff0000 +#define HPTXFSIZ_PTXFSTADDR_SHIFT 0 +#define HPTXFSIZ_PTXFSTADDR_MASK 0x0000ffff + +#define DPTXFSIZN_DPTXFSIZE_SHIFT 16 +#define DPTXFSIZN_DPTXFSIZE_MASK 0xffff0000 +#define DPTXFSIZN_PTXFSTADDR_SHIFT 0 +#define DPTXFSIZN_PTXFSTADDR_MASK 0x0000ffff + +#define DIEPTXFN_INEPNTXFDEP_SHIFT 16 +#define DIEPTXFN_INEPNTXFDEP_MASK 0xffff0000 +#define DIEPTXFN_INEPNTXFSTADDR_SHIFT 0 +#define DIEPTXFN_INEPNTXFSTADDR_MASK 0x0000ffff + +#define HCFG_MODECHANGERDY (1<<31) +#define HCFG_PERSCHEDENABLE (1<<26) +#define HCFG_FLENTRIES_SHIFT 24 +#define HCFG_FLENTRIES_MASK 0x03000000 +#define HCFG_FLENTRIES_8 (0) +#define HCFG_FLENTRIES_16 (1) +#define HCFG_FLENTRIES_32 (2) +#define HCFG_FLENTRIES_64 (3) +#define HCFG_MULTISEGDMA (1<<23) +#define HCFG_32KHZSUSPEND (1<<7) +#define HCFG_FSLSSUPP (1<<2) +#define HCFG_FSLSPCLKSEL_SHIFT 0 +#define HCFG_FSLSPCLKSEL_MASK 0x00000003 + +#define HFIR_RELOADCTRL (1<<16) +#define HFIR_FRINT_SHIFT 0 +#define HFIR_FRINT_MASK 0x0000ffff + +#define HFNUM_FRREM_SHIFT 16 +#define HFNUM_FRREM_MASK 0xffff0000 +#define HFNUM_FRNUM_SHIFT 0 +#define HFNUM_FRNUM_MASK 0x0000ffff + +#define HPTXSTS_ODD (1<<31) +#define HPTXSTS_CHAN_SHIFT 27 +#define HPTXSTS_CHAN_MASK 0x78000000 +#define HPTXSTS_TOKEN_SHIFT 25 +#define HPTXSTS_TOKEN_MASK 0x06000000 +#define HPTXSTS_TOKEN_ZL 0 +#define HPTXSTS_TOKEN_PING 1 +#define HPTXSTS_TOKEN_DISABLE 2 +#define HPTXSTS_TERMINATE (1<<24) +#define HPTXSTS_PTXQSPCAVAIL_SHIFT 16 +#define HPTXSTS_PTXQSPCAVAIL_MASK 0x00ff0000 +#define HPTXSTS_PTXFSPCAVAIL_SHIFT 0 +#define HPTXSTS_PTXFSPCAVAIL_MASK 0x0000ffff + +#define HAINT_HAINT_SHIFT 0 +#define HAINT_HAINT_MASK 0x0000ffff +#define HAINTMSK_HAINTMSK_SHIFT 0 +#define HAINTMSK_HAINTMSK_MASK 0x0000ffff + +#define HPRT_PRTSPD_SHIFT 17 +#define HPRT_PRTSPD_MASK 0x00060000 +#define HPRT_PRTSPD_HIGH 0 +#define HPRT_PRTSPD_FULL 1 +#define HPRT_PRTSPD_LOW 2 +#define HPRT_PRTSPD_MASK 0x00060000 +#define HPRT_PRTTSTCTL_SHIFT 13 +#define HPRT_PRTTSTCTL_MASK 0x0001e000 +#define HPRT_PRTPWR (1<<12) +#define HPRT_PRTLNSTS_SHIFT 10 +#define HPRT_PRTLNSTS_MASK 0x00000c00 +#define HPRT_PRTRST (1<<8) +#define HPRT_PRTSUSP (1<<7) +#define HPRT_PRTRES (1<<6) +#define HPRT_PRTOVRCURRCHNG (1<<5) +#define HPRT_PRTOVRCURRACT (1<<4) +#define HPRT_PRTENCHNG (1<<3) +#define HPRT_PRTENA (1<<2) +#define HPRT_PRTCONNDET (1<<1) +#define HPRT_PRTCONNSTS (1<<0) + +#define HCCHAR_CHENA (1<<31) +#define HCCHAR_CHDIS (1<<30) +#define HCCHAR_ODDFRM (1<<29) +#define HCCHAR_DEVADDR_SHIFT 22 +#define HCCHAR_DEVADDR_MASK 0x1fc00000 +#define HCCHAR_MC_SHIFT 20 +#define HCCHAR_MC_MASK 0x00300000 +#define HCCHAR_EPTYPE_SHIFT 18 +#define HCCHAR_EPTYPE_MASK 0x000c0000 +#define HCCHAR_LSPDDEV (1<<17) +#define HCCHAR_EPDIR (1<<15) +#define HCCHAR_EPDIR_IN (1<<15) +#define HCCHAR_EPDIR_OUT 0 +#define HCCHAR_EPNUM_SHIFT 11 +#define HCCHAR_EPNUM_MASK 0x00007800 +#define HCCHAR_MPS_SHIFT 0 +#define HCCHAR_MPS_MASK 0x000007ff + +#define HCSPLT_SPLTENA (1<<31) +#define HCSPLT_COMPSPLT (1<<16) +#define HCSPLT_XACTPOS_SHIFT 14 +#define HCSPLT_XACTPOS_MASK 0x0000c000 +#define HCSPLT_XACTPOS_MIDDLE 0 +#define HCSPLT_XACTPOS_LAST 1 +#define HCSPLT_XACTPOS_BEGIN 2 +#define HCSPLT_XACTPOS_ALL 3 +#define HCSPLT_XACTLEN_BURST 1023 /* bytes */ +#define HCSPLT_HUBADDR_SHIFT 7 +#define HCSPLT_HUBADDR_MASK 0x00003f80 +#define HCSPLT_PRTADDR_SHIFT 0 +#define HCSPLT_PRTADDR_MASK 0x0000007f + +#define HCINT_ERRORS \ + (HCINT_BBLERR | HCINT_XACTERR) +#define HCINT_RETRY \ + (HCINT_DATATGLERR | HCINT_FRMOVRUN | HCINT_NAK) +#define HCINT_DEFAULT_MASK \ + (HCINT_STALL | HCINT_BBLERR | \ + HCINT_XACTERR | HCINT_NAK | HCINT_ACK | HCINT_NYET | \ + HCINT_CHHLTD | HCINT_FRMOVRUN | \ + HCINT_DATATGLERR) +#define HCINT_HCH_DONE_MASK \ + (HCINT_ACK | HCINT_RETRY | HCINT_NYET | \ + HCINT_ERRORS | HCINT_STALL | HCINT_SOFTWARE_ONLY) + +#define HCINT_SOFTWARE_ONLY (1<<20) /* BSD only */ +#define HCINT_DATATGLERR (1<<10) +#define HCINT_FRMOVRUN (1<<9) +#define HCINT_BBLERR (1<<8) +#define HCINT_XACTERR (1<<7) +#define HCINT_NYET (1<<6) +#define HCINT_ACK (1<<5) +#define HCINT_NAK (1<<4) +#define HCINT_STALL (1<<3) +#define HCINT_AHBERR (1<<2) +#define HCINT_CHHLTD (1<<1) +#define HCINT_XFERCOMPL (1<<0) + +#define HCINTMSK_DATATGLERRMSK (1<<10) +#define HCINTMSK_FRMOVRUNMSK (1<<9) +#define HCINTMSK_BBLERRMSK (1<<8) +#define HCINTMSK_XACTERRMSK (1<<7) +#define HCINTMSK_NYETMSK (1<<6) +#define HCINTMSK_ACKMSK (1<<5) +#define HCINTMSK_NAKMSK (1<<4) +#define HCINTMSK_STALLMSK (1<<3) +#define HCINTMSK_AHBERRMSK (1<<2) +#define HCINTMSK_CHHLTDMSK (1<<1) +#define HCINTMSK_XFERCOMPLMSK (1<<0) + +#define HCTSIZ_DOPNG (1<<31) +#define HCTSIZ_PID_SHIFT 29 +#define HCTSIZ_PID_MASK 0x60000000 +#define HCTSIZ_PID_DATA0 0 +#define HCTSIZ_PID_DATA2 1 +#define HCTSIZ_PID_DATA1 2 +#define HCTSIZ_PID_MDATA 3 +#define HCTSIZ_PID_SETUP 3 +#define HCTSIZ_PKTCNT_SHIFT 19 +#define HCTSIZ_PKTCNT_MASK 0x1ff80000 +#define HCTSIZ_XFERSIZE_SHIFT 0 +#define HCTSIZ_XFERSIZE_MASK 0x0007ffff + +#define DCFG_EPMISCNT_SHIFT 18 +#define DCFG_EPMISCNT_MASK 0x007c0000 +#define DCFG_PERFRINT_SHIFT 11 +#define DCFG_PERFRINT_MASK 0x00001800 +#define DCFG_DEVADDR_SHIFT 4 +#define DCFG_DEVADDR_MASK 0x000007f0 +#define DCFG_DEVADDR_SET(x) (((x) & 0x7F) << 4) +#define DCFG_NZSTSOUTHSHK (1<<2) +#define DCFG_DEVSPD_SHIFT 0 +#define DCFG_DEVSPD_MASK 0x00000003 +#define DCFG_DEVSPD_SET(x) ((x) & 0x3) +#define DCFG_DEVSPD_HI 0 +#define DCFG_DEVSPD_FULL20 1 +#define DCFG_DEVSPD_FULL10 3 + +#define DCTL_PWRONPRGDONE (1<<11) +#define DCTL_CGOUTNAK (1<<10) +#define DCTL_SGOUTNAK (1<<9) +#define DCTL_CGNPINNAK (1<<8) +#define DCTL_SGNPINNAK (1<<7) +#define DCTL_TSTCTL_SHIFT 4 +#define DCTL_TSTCTL_MASK 0x00000070 +#define DCTL_GOUTNAKSTS (1<<3) +#define DCTL_GNPINNAKSTS (1<<2) +#define DCTL_SFTDISCON (1<<1) +#define DCTL_RMTWKUPSIG (1<<0) + +#define DSTS_SOFFN_SHIFT 8 +#define DSTS_SOFFN_MASK 0x003fff00 +#define DSTS_SOFFN_GET(x) (((x) >> 8) & 0x3FFF) +#define DSTS_ERRTICERR (1<<3) +#define DSTS_ENUMSPD_SHIFT 1 +#define DSTS_ENUMSPD_MASK 0x00000006 +#define DSTS_ENUMSPD_GET(x) (((x) >> 1) & 3) +#define DSTS_ENUMSPD_HI 0 +#define DSTS_ENUMSPD_FULL20 1 +#define DSTS_ENUMSPD_LOW10 2 +#define DSTS_ENUMSPD_FULL10 3 +#define DSTS_SUSPSTS (1<<0) + +#define DIEPMSK_TXFIFOUNDRNMSK (1<<8) +#define DIEPMSK_INEPNAKEFFMSK (1<<6) +#define DIEPMSK_INTKNEPMISMSK (1<<5) +#define DIEPMSK_INTKNTXFEMPMSK (1<<4) +#define DIEPMSK_FIFOEMPTY (1<<4) +#define DIEPMSK_TIMEOUTMSK (1<<3) +#define DIEPMSK_AHBERRMSK (1<<2) +#define DIEPMSK_EPDISBLDMSK (1<<1) +#define DIEPMSK_XFERCOMPLMSK (1<<0) + +#define DOEPMSK_OUTPKTERRMSK (1<<8) +#define DOEPMSK_BACK2BACKSETUP (1<<6) +#define DOEPMSK_OUTTKNEPDISMSK (1<<4) +#define DOEPMSK_FIFOEMPTY (1<<4) +#define DOEPMSK_SETUPMSK (1<<3) +#define DOEPMSK_AHBERRMSK (1<<2) +#define DOEPMSK_EPDISBLDMSK (1<<1) +#define DOEPMSK_XFERCOMPLMSK (1<<0) + +#define DIEPINT_TXFIFOUNDRN (1<<8) +#define DIEPINT_INEPNAKEFF (1<<6) +#define DIEPINT_INTKNEPMIS (1<<5) +#define DIEPINT_INTKNTXFEMP (1<<4) +#define DIEPINT_TIMEOUT (1<<3) +#define DIEPINT_AHBERR (1<<2) +#define DIEPINT_EPDISBLD (1<<1) +#define DIEPINT_XFERCOMPL (1<<0) + +#define DOEPINT_OUTPKTERR (1<<8) +#define DOEPINT_BACK2BACKSETUP (1<<6) +#define DOEPINT_OUTTKNEPDIS (1<<4) +#define DOEPINT_SETUP (1<<3) +#define DOEPINT_AHBERR (1<<2) +#define DOEPINT_EPDISBLD (1<<1) +#define DOEPINT_XFERCOMPL (1<<0) + +#define DAINT_INEPINT_MASK 0xffff0000 +#define DAINT_INEPINT_SHIFT 0 +#define DAINT_OUTEPINT_MASK 0x0000ffff +#define DAINT_OUTEPINT_SHIFT 16 + +#define DAINTMSK_INEPINT_MASK 0xffff0000 +#define DAINTMSK_INEPINT_SHIFT 0 +#define DAINTMSK_OUTEPINT_MASK 0x0000ffff +#define DAINTMSK_OUTEPINT_SHIFT 16 + +#define DTKNQR1_EPTKN_SHIFT 8 +#define DTKNQR1_EPTKN_MASK 0xffffff00 +#define DTKNQR1_WRAPBIT (1<<7) +#define DTKNQR1_INTKNWPTR_SHIFT 0 +#define DTKNQR1_INTKNWPTR_MASK 0x0000001f + +#define DVBUSDIS_DVBUSDIS_SHIFT 0 +#define DVBUSDIS_DVBUSDIS_MASK 0x0000ffff + +#define DVBUSPULSE_DVBUSPULSE_SHIFT 0 +#define DVBUSPULSE_DVBUSPULSE_MASK 0x00000fff + +#define DTHRCTL_ARBPRKEN (1<<27) +#define DTHRCTL_RXTHRLEN_SHIFT 17 +#define DTHRCTL_RXTHRLEN_MASK 0x03fe0000 +#define DTHRCTL_RXTHREN (1<<16) +#define DTHRCTL_TXTHRLEN_SHIFT 2 +#define DTHRCTL_TXTHRLEN_MASK 0x000007fc +#define DTHRCTL_ISOTHREN (1<<1) +#define DTHRCTL_NONISOTHREN (1<<0) + +#define DIEPEMPMSK_INEPTXFEMPMSK_SHIFT 0 +#define DIEPEMPMSK_INEPTXFEMPMSK_MASK 0x0000ffff + +#define DIEPCTL_EPENA (1<<31) +#define DIEPCTL_EPDIS (1<<30) +#define DIEPCTL_SETD1PID (1<<29) +#define DIEPCTL_SETD0PID (1<<28) +#define DIEPCTL_SNAK (1<<27) +#define DIEPCTL_CNAK (1<<26) +#define DIEPCTL_TXFNUM_SHIFT 22 +#define DIEPCTL_TXFNUM_MASK 0x03c00000 +#define DIEPCTL_TXFNUM_SET(n) (((n) & 15) << 22) +#define DIEPCTL_STALL (1<<21) +#define DIEPCTL_EPTYPE_SHIFT 18 +#define DIEPCTL_EPTYPE_MASK 0x000c0000 +#define DIEPCTL_EPTYPE_SET(n) (((n) & 3) << 18) +#define DIEPCTL_EPTYPE_CONTROL 0 +#define DIEPCTL_EPTYPE_ISOC 1 +#define DIEPCTL_EPTYPE_BULK 2 +#define DIEPCTL_EPTYPE_INTERRUPT 3 +#define DIEPCTL_NAKSTS (1<<17) +#define DIEPCTL_USBACTEP (1<<15) +#define DIEPCTL_NEXTEP_SHIFT 11 +#define DIEPCTL_NEXTEP_MASK 0x00007800 +#define DIEPCTL_MPS_SHIFT 0 +#define DIEPCTL_MPS_MASK 0x000007ff +#define DIEPCTL_MPS_SET(n) ((n) & 0x7FF) +#define DIEPCTL_MPS_64 (0<<0) +#define DIEPCTL_MPS_32 (1<<0) +#define DIEPCTL_MPS_16 (2<<0) +#define DIEPCTL_MPS_8 (3<<0) + +#define DOEPCTL_EPENA (1<<31) +#define DOEPCTL_EPDIS (1<<30) +#define DOEPCTL_SETD1PID (1<<29) +#define DOEPCTL_SETD0PID (1<<28) +#define DOEPCTL_SNAK (1<<27) +#define DOEPCTL_CNAK (1<<26) +#define DOEPCTL_FNUM_SET(n) (((n) & 15) << 22) +#define DOEPCTL_STALL (1<<21) +#define DOEPCTL_EPTYPE_SHIFT 18 +#define DOEPCTL_EPTYPE_MASK 0x000c0000 +#define DOEPCTL_EPTYPE_SET(n) (((n) & 3) << 18) +#define DOEPCTL_NAKSTS (1<<17) +#define DOEPCTL_USBACTEP (1<<15) +#define DOEPCTL_MPS_SHIFT 0 +#define DOEPCTL_MPS_MASK 0x000007ff +#define DOEPCTL_MPS_SET(n) ((n) & 0x7FF) +#define DOEPCTL_MPS_64 (0<<0) +#define DOEPCTL_MPS_32 (1<<0) +#define DOEPCTL_MPS_16 (2<<0) +#define DOEPCTL_MPS_8 (3<<0) + +/* common bits */ +#define DXEPINT_TXFEMP (1<<7) +#define DXEPINT_SETUP (1<<3) +#define DXEPINT_XFER_COMPL (1<<0) + +#define DIEPTSIZ_XFERSIZE_MASK 0x0007ffff +#define DIEPTSIZ_XFERSIZE_SHIFT 0 +#define DIEPTSIZ_PKTCNT_MASK 0x1ff80000 +#define DIEPTSIZ_PKTCNT_SHIFT 19 +#define DIEPTSIZ_MC_MASK 0x60000000 +#define DIEPTSIZ_MC_SHIFT 29 + +#define DOEPTSIZ_XFERSIZE_MASK 0x0007ffff +#define DOEPTSIZ_XFERSIZE_SHIFT 0 +#define DOEPTSIZ_PKTCNT_MASK 0x1ff80000 +#define DOEPTSIZ_PKTCNT_SHIFT 19 +#define DOEPTSIZ_MC_MASK 0x60000000 +#define DOEPTSIZ_MC_SHIFT 29 + +/* common bits */ +#define DXEPTSIZ_SET_MULTI(n) (((n) & 3) << 29) +#define DXEPTSIZ_SET_NPKT(n) (((n) & 0x3FF) << 19) +#define DXEPTSIZ_GET_NPKT(n) (((n) >> 19) & 0x3FF) +#define DXEPTSIZ_SET_NBYTES(n) (((n) & 0x7FFFFF) << 0) +#define DXEPTSIZ_GET_NBYTES(n) (((n) >> 0) & 0x7FFFFF) + +/* generic endpoint mask */ + +#define ENDPOINT_MASK(x,in) \ + ((in) ? (1U << ((x) & 15U)) : \ + (0x10000U << ((x) & 15U))) + +#endif /* _DWC_OTGREG_H_ */ -- cgit v1.2.3