diff options
| author | Simon J. Gerraty <sjg@FreeBSD.org> | 2014-08-19 06:50:54 +0000 |
|---|---|---|
| committer | Simon J. Gerraty <sjg@FreeBSD.org> | 2014-08-19 06:50:54 +0000 |
| commit | ee7b0571c2c18bdec848ed2044223cc88db29bd8 (patch) | |
| tree | b04f4bd7cd887f50e7d98af35f46b9834ff86c80 /sys/dev/usb/controller | |
| parent | ffda191e301f128a62c152fde92b692548367fca (diff) | |
| parent | 15fc2873832ea5b9b639e701bbbf2e73af8b6a88 (diff) | |
Notes
Diffstat (limited to 'sys/dev/usb/controller')
24 files changed, 5587 insertions, 1201 deletions
diff --git a/sys/dev/usb/controller/at91dci.c b/sys/dev/usb/controller/at91dci.c index 0d5f176a3589f..abcad6233d218 100644 --- a/sys/dev/usb/controller/at91dci.c +++ b/sys/dev/usb/controller/at91dci.c @@ -898,7 +898,8 @@ at91dci_setup_standard_chain(struct usb_xfer *xfer) temp.td = NULL; temp.td_next = xfer->td_start[0]; temp.offset = 0; - temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.setup_alt_next = xfer->flags_int.short_frames_ok || + xfer->flags_int.isochronous_xfr; temp.did_stall = !xfer->flags_int.control_stall; sc = AT9100_DCI_BUS2SC(xfer->xroot->bus); @@ -1124,7 +1125,8 @@ at91dci_standard_done_sub(struct usb_xfer *xfer) } /* Check for short transfer */ if (len > 0) { - if (xfer->flags_int.short_frames_ok) { + if (xfer->flags_int.short_frames_ok || + xfer->flags_int.isochronous_xfr) { /* follow alt next */ if (td->alt_next) { td = td->obj_next; diff --git a/sys/dev/usb/controller/atmegadci.c b/sys/dev/usb/controller/atmegadci.c index 856d0bd916cfd..8e08ff1bdca69 100644 --- a/sys/dev/usb/controller/atmegadci.c +++ b/sys/dev/usb/controller/atmegadci.c @@ -804,7 +804,8 @@ atmegadci_setup_standard_chain(struct usb_xfer *xfer) temp.td = NULL; temp.td_next = xfer->td_start[0]; temp.offset = 0; - temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.setup_alt_next = xfer->flags_int.short_frames_ok || + xfer->flags_int.isochronous_xfr; temp.did_stall = !xfer->flags_int.control_stall; sc = ATMEGA_BUS2SC(xfer->xroot->bus); @@ -1010,7 +1011,8 @@ atmegadci_standard_done_sub(struct usb_xfer *xfer) } /* Check for short transfer */ if (len > 0) { - if (xfer->flags_int.short_frames_ok) { + if (xfer->flags_int.short_frames_ok || + xfer->flags_int.isochronous_xfr) { /* follow alt next */ if (td->alt_next) { td = td->obj_next; @@ -1380,9 +1382,9 @@ atmegadci_do_poll(struct usb_bus *bus) } /*------------------------------------------------------------------------* - * at91dci bulk support - * at91dci control support - * at91dci interrupt support + * atmegadci bulk support + * atmegadci control support + * atmegadci interrupt support *------------------------------------------------------------------------*/ static void atmegadci_device_non_isoc_open(struct usb_xfer *xfer) @@ -1419,7 +1421,7 @@ static const struct usb_pipe_methods atmegadci_device_non_isoc_methods = }; /*------------------------------------------------------------------------* - * at91dci full speed isochronous support + * atmegadci full speed isochronous support *------------------------------------------------------------------------*/ static void atmegadci_device_isoc_fs_open(struct usb_xfer *xfer) @@ -1505,7 +1507,7 @@ static const struct usb_pipe_methods atmegadci_device_isoc_fs_methods = }; /*------------------------------------------------------------------------* - * at91dci root control support + * atmegadci root control support *------------------------------------------------------------------------* * Simulate a hardware HUB by handling all the necessary requests. *------------------------------------------------------------------------*/ diff --git a/sys/dev/usb/controller/avr32dci.c b/sys/dev/usb/controller/avr32dci.c index c7c25046a4450..f9b4c641c08af 100644 --- a/sys/dev/usb/controller/avr32dci.c +++ b/sys/dev/usb/controller/avr32dci.c @@ -771,7 +771,8 @@ avr32dci_setup_standard_chain(struct usb_xfer *xfer) temp.td = NULL; temp.td_next = xfer->td_start[0]; temp.offset = 0; - temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.setup_alt_next = xfer->flags_int.short_frames_ok || + xfer->flags_int.isochronous_xfr; temp.did_stall = !xfer->flags_int.control_stall; sc = AVR32_BUS2SC(xfer->xroot->bus); @@ -979,7 +980,8 @@ avr32dci_standard_done_sub(struct usb_xfer *xfer) } /* Check for short transfer */ if (len > 0) { - if (xfer->flags_int.short_frames_ok) { + if (xfer->flags_int.short_frames_ok || + xfer->flags_int.isochronous_xfr) { /* follow alt next */ if (td->alt_next) { td = td->obj_next; @@ -1310,9 +1312,9 @@ avr32dci_do_poll(struct usb_bus *bus) } /*------------------------------------------------------------------------* - * at91dci bulk support - * at91dci control support - * at91dci interrupt support + * avr32dci bulk support + * avr32dci control support + * avr32dci interrupt support *------------------------------------------------------------------------*/ static void avr32dci_device_non_isoc_open(struct usb_xfer *xfer) @@ -1349,7 +1351,7 @@ static const struct usb_pipe_methods avr32dci_device_non_isoc_methods = }; /*------------------------------------------------------------------------* - * at91dci full speed isochronous support + * avr32dci full speed isochronous support *------------------------------------------------------------------------*/ static void avr32dci_device_isoc_fs_open(struct usb_xfer *xfer) @@ -1434,7 +1436,7 @@ static const struct usb_pipe_methods avr32dci_device_isoc_fs_methods = }; /*------------------------------------------------------------------------* - * at91dci root control support + * avr32dci root control support *------------------------------------------------------------------------* * Simulate a hardware HUB by handling all the necessary requests. *------------------------------------------------------------------------*/ diff --git a/sys/dev/usb/controller/dwc_otg.c b/sys/dev/usb/controller/dwc_otg.c index d8bf7ea39be70..b90fbf56022d5 100644 --- a/sys/dev/usb/controller/dwc_otg.c +++ b/sys/dev/usb/controller/dwc_otg.c @@ -89,8 +89,8 @@ ((struct dwc_otg_softc *)(((uint8_t *)(bus)) - \ ((uint8_t *)&(((struct dwc_otg_softc *)0)->sc_bus)))) -#define DWC_OTG_PC2SC(pc) \ - DWC_OTG_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus) +#define DWC_OTG_PC2UDEV(pc) \ + (USB_DMATAG_TO_XROOT((pc)->tag_parent)->udev) #define DWC_OTG_MSK_GINT_ENABLED \ (GINTMSK_ENUMDONEMSK | \ @@ -98,21 +98,27 @@ 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) + static int dwc_otg_use_hsic; static SYSCTL_NODE(_hw_usb, OID_AUTO, dwc_otg, CTLFLAG_RW, 0, "USB DWC OTG"); -SYSCTL_INT(_hw_usb_dwc_otg, OID_AUTO, use_hsic, CTLFLAG_RD | CTLFLAG_TUN, +SYSCTL_INT(_hw_usb_dwc_otg, OID_AUTO, use_hsic, CTLFLAG_RDTUN, &dwc_otg_use_hsic, 0, "DWC OTG uses HSIC interface"); -TUNABLE_INT("hw.usb.dwc_otg.use_hsic", &dwc_otg_use_hsic); #ifdef USB_DEBUG static int dwc_otg_debug; -SYSCTL_INT(_hw_usb_dwc_otg, OID_AUTO, debug, CTLFLAG_RW, +SYSCTL_INT(_hw_usb_dwc_otg, OID_AUTO, debug, CTLFLAG_RWTUN, &dwc_otg_debug, 0, "DWC OTG debug level"); #endif @@ -136,8 +142,9 @@ static dwc_otg_cmd_t dwc_otg_host_data_rx; static void dwc_otg_device_done(struct usb_xfer *, usb_error_t); static void dwc_otg_do_poll(struct usb_bus *); static void dwc_otg_standard_done(struct usb_xfer *); -static void dwc_otg_root_intr(struct dwc_otg_softc *sc); -static void dwc_otg_interrupt_poll(struct dwc_otg_softc *sc); +static void dwc_otg_root_intr(struct dwc_otg_softc *); +static void dwc_otg_interrupt_poll_locked(struct dwc_otg_softc *); +static void dwc_otg_host_channel_disable(struct dwc_otg_softc *, uint8_t); /* * Here is a configuration that the chip supports. @@ -177,42 +184,54 @@ dwc_otg_init_fifo(struct dwc_otg_softc *sc, uint8_t mode) fifo_size = sc->sc_fifo_size; - fifo_regs = 4 * (sc->sc_dev_ep_max + sc->sc_dev_in_ep_max); + /* + * 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) - fifo_size -= fifo_regs; - else - fifo_size = 0; + 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; - DWC_OTG_WRITE_4(sc, DOTG_GRXFSIZ, fifo_size / 4); - - /* align to 4-bytes */ + /* align to 4 bytes boundary */ 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 < 0x40) { + if (fifo_size < 64) { DPRINTFN(-1, "Not enough data space for EP0 FIFO.\n"); - USB_BUS_UNLOCK(&sc->sc_bus); return (EINVAL); } + /* disable any leftover host channels */ + for (x = 0; x != sc->sc_host_ch_max; x++) { + if (sc->sc_chan_state[x].wait_sof == 0) + continue; + dwc_otg_host_channel_disable(sc, x); + } + if (mode == DWC_MODE_HOST) { /* reset active endpoints */ sc->sc_active_rx_ep = 0; - /* reset TX size */ - sc->sc_tx_cur_size = 0; - - /* reset TT info */ - memset(sc->sc_tt_info, 0, sizeof(sc->sc_tt_info)); - + /* split equally for periodic and non-periodic */ fifo_size /= 2; + /* align to 4 bytes boundary */ + fifo_size &= ~3; + DWC_OTG_WRITE_4(sc, DOTG_GNPTXFSIZ, ((fifo_size / 4) << 16) | (tx_start / 4)); @@ -221,18 +240,27 @@ dwc_otg_init_fifo(struct dwc_otg_softc *sc, uint8_t mode) for (x = 0; x != sc->sc_host_ch_max; x++) { /* disable all host interrupts */ - DWC_OTG_WRITE_4(sc, DOTG_HCINTMSK(x), 0); + 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)); - /* store maximum TX FIFO size */ + /* reset host channel state */ + memset(sc->sc_chan_state, 0, sizeof(sc->sc_chan_state)); + + /* reset FIFO TX levels */ + sc->sc_tx_cur_p_level = 0; + sc->sc_tx_cur_np_level = 0; + + /* store maximum periodic and non-periodic FIFO TX size */ sc->sc_tx_max_size = fifo_size; - /* disable all host channel interrupts */ - DWC_OTG_WRITE_4(sc, DOTG_HAINTMSK, 0); + /* enable all host channel interrupts */ + DWC_OTG_WRITE_4(sc, DOTG_HAINTMSK, + (1U << sc->sc_host_ch_max) - 1U); } if (mode == DWC_MODE_DEVICE) { @@ -311,11 +339,15 @@ dwc_otg_init_fifo(struct dwc_otg_softc *sc, uint8_t mode) /* reset active endpoints */ sc->sc_active_rx_ep = 0; - /* reset TX size */ - sc->sc_tx_cur_size = 0; + /* reset periodic and non-periodic FIFO TX size */ + sc->sc_tx_max_size = fifo_size; - /* reset TT info */ - memset(sc->sc_tt_info, 0, sizeof(sc->sc_tt_info)); + /* reset host channel state */ + memset(sc->sc_chan_state, 0, sizeof(sc->sc_chan_state)); + + /* reset FIFO TX levels */ + sc->sc_tx_cur_p_level = 0; + sc->sc_tx_cur_np_level = 0; } return (0); } @@ -323,6 +355,12 @@ dwc_otg_init_fifo(struct dwc_otg_softc *sc, uint8_t mode) 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 */ @@ -348,6 +386,7 @@ dwc_otg_update_host_frame_interval(struct dwc_otg_softc *sc) DPRINTF("HFIR=0x%08x\n", temp); DWC_OTG_WRITE_4(sc, DOTG_HFIR, temp); +#endif } static void @@ -532,11 +571,9 @@ dwc_otg_common_rx_ack(struct dwc_otg_softc *sc) { DPRINTFN(5, "RX status clear\n"); - if (sc->sc_flags.status_device_mode != 0) { - /* enable RX FIFO level interrupt */ - sc->sc_irq_mask |= GINTMSK_RXFLVLMSK; - DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); - } + /* enable RX FIFO level interrupt */ + sc->sc_irq_mask |= GINTMSK_RXFLVLMSK; + DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); /* clear cached status */ sc->sc_last_rx_status = 0; @@ -555,125 +592,61 @@ dwc_otg_clear_hcint(struct dwc_otg_softc *sc, uint8_t x) sc->sc_chan_state[x].hcint = 0; } -/* - * This function waits until a DWC OTG host channel is ready to be - * used again: - */ static uint8_t -dwc_otg_host_channel_wait(struct dwc_otg_td *td) +dwc_otg_host_channel_alloc(struct dwc_otg_softc *sc, struct dwc_otg_td *td, uint8_t is_out) { - struct dwc_otg_softc *sc; + uint32_t tx_p_size; + uint32_t tx_np_size; uint8_t x; - x = td->channel; - - DPRINTF("CH=%d\n", x); - - /* get pointer to softc */ - sc = DWC_OTG_PC2SC(td->pc); - - if (sc->sc_chan_state[x].wait_sof == 0) { - dwc_otg_clear_hcint(sc, x); - return (1); /* done */ - } - - if (x == 0) - return (0); /* wait */ - - /* find new disabled channel */ - for (x = 1; x != sc->sc_host_ch_max; x++) { - - if (sc->sc_chan_state[x].allocated) - continue; - if (sc->sc_chan_state[x].wait_sof != 0) - continue; - - sc->sc_chan_state[td->channel].allocated = 0; - sc->sc_chan_state[x].allocated = 1; - - sc->sc_chan_state[x].tx_size = - sc->sc_chan_state[td->channel].tx_size; - - if (sc->sc_chan_state[td->channel].suspended) { - sc->sc_chan_state[td->channel].suspended = 0; - sc->sc_chan_state[x].suspended = 1; - } - - /* clear interrupts */ - dwc_otg_clear_hcint(sc, x); - - DPRINTF("CH=%d HCCHAR=0x%08x " - "HCSPLT=0x%08x\n", x, td->hcchar, td->hcsplt); - - /* ack any pending messages */ - if (sc->sc_last_rx_status != 0 && - GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status) == td->channel) { - /* get rid of message */ - dwc_otg_common_rx_ack(sc); - } - - /* move active channel */ - sc->sc_active_rx_ep &= ~(1 << td->channel); - sc->sc_active_rx_ep |= (1 << x); - - /* set channel */ - td->channel = x; - - return (1); /* new channel allocated */ - } - return (0); /* wait */ -} - -static uint8_t -dwc_otg_host_channel_alloc(struct dwc_otg_td *td) -{ - struct dwc_otg_softc *sc; - uint32_t tx_size; - uint8_t x; - uint8_t max_channel; - if (td->channel < DWC_OTG_MAX_CHANNELS) return (0); /* already allocated */ - /* get pointer to softc */ - sc = DWC_OTG_PC2SC(td->pc); + /* check if device is suspended */ + if (DWC_OTG_PC2UDEV(td->pc)->flags.self_suspended != 0) + return (1); /* busy - cannot transfer data */ - if ((td->hcchar & HCCHAR_EPNUM_MASK) == 0) { - max_channel = 1; - x = 0; - tx_size = td->max_packet_size; - if ((sc->sc_tx_cur_size + tx_size) > sc->sc_tx_max_size) { - DPRINTF("Too little FIFO space\n"); - return (1); /* too little FIFO */ - } - } else { - max_channel = sc->sc_host_ch_max; - x = 1; - if ((td->hcchar & HCCHAR_EPDIR) == HCCHAR_EPDIR_OUT) { - tx_size = td->max_packet_size; - if (td->hcsplt != 0 && tx_size > HCSPLT_XACTLEN_MAX) - tx_size = HCSPLT_XACTLEN_MAX; - if ((sc->sc_tx_cur_size + tx_size) > sc->sc_tx_max_size) { + /* compute needed TX FIFO size */ + if (is_out != 0) { + if (td->ep_type == UE_ISOCHRONOUS) { + tx_p_size = td->max_packet_size; + tx_np_size = 0; + if (td->hcsplt != 0 && tx_p_size > HCSPLT_XACTLEN_BURST) + tx_p_size = HCSPLT_XACTLEN_BURST; + if ((sc->sc_tx_cur_p_level + tx_p_size) > sc->sc_tx_max_size) { DPRINTF("Too little FIFO space\n"); return (1); /* too little FIFO */ } } else { - tx_size = 0; + tx_p_size = 0; + tx_np_size = td->max_packet_size; + if (td->hcsplt != 0 && tx_np_size > HCSPLT_XACTLEN_BURST) + tx_np_size = HCSPLT_XACTLEN_BURST; + if ((sc->sc_tx_cur_np_level + tx_np_size) > sc->sc_tx_max_size) { + DPRINTF("Too little FIFO space\n"); + return (1); /* too little FIFO */ + } } + } else { + /* not a TX transaction */ + tx_p_size = 0; + tx_np_size = 0; } - for (; x != max_channel; x++) { - - if (sc->sc_chan_state[x].allocated) + for (x = 0; x != sc->sc_host_ch_max; x++) { + if (sc->sc_chan_state[x].allocated != 0) continue; + /* check if channel is still enabled */ if (sc->sc_chan_state[x].wait_sof != 0) continue; sc->sc_chan_state[x].allocated = 1; - sc->sc_chan_state[x].tx_size = tx_size; + sc->sc_chan_state[x].tx_p_size = tx_p_size; + sc->sc_chan_state[x].tx_np_size = tx_np_size; - /* keep track of used FIFO */ - sc->sc_tx_cur_size += tx_size; + /* keep track of used TX FIFO, if any */ + sc->sc_tx_cur_p_level += tx_p_size; + sc->sc_tx_cur_np_level += tx_np_size; /* clear interrupts */ dwc_otg_clear_hcint(sc, x); @@ -689,29 +662,14 @@ dwc_otg_host_channel_alloc(struct dwc_otg_td *td) return (0); /* allocated */ } + /* wait a bit */ + dwc_otg_enable_sof_irq(sc); return (1); /* busy */ } static void -dwc_otg_host_channel_disable(struct dwc_otg_softc *sc, uint8_t x) -{ - uint32_t hcchar; - if (sc->sc_chan_state[x].wait_sof != 0) - return; - hcchar = DWC_OTG_READ_4(sc, DOTG_HCCHAR(x)); - if (hcchar & (HCCHAR_CHENA | HCCHAR_CHDIS)) { - /* disable channel */ - DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(x), - HCCHAR_CHENA | HCCHAR_CHDIS); - /* don't re-use channel until next SOF is transmitted */ - sc->sc_chan_state[x].wait_sof = 2; - } -} - -static void -dwc_otg_host_channel_free(struct dwc_otg_td *td) +dwc_otg_host_channel_free(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { - struct dwc_otg_softc *sc; uint8_t x; if (td->channel >= DWC_OTG_MAX_CHANNELS) @@ -723,16 +681,21 @@ dwc_otg_host_channel_free(struct dwc_otg_td *td) DPRINTF("CH=%d\n", x); - /* get pointer to softc */ - sc = DWC_OTG_PC2SC(td->pc); - - dwc_otg_host_channel_disable(sc, x); + /* + * We need to let programmed host channels run till complete + * else the host channel will stop functioning. Assume that + * after a fixed given amount of time the host channel is no + * longer doing any USB traffic: + */ + if (td->ep_type == UE_ISOCHRONOUS) { + /* double buffered */ + sc->sc_chan_state[x].wait_sof = DWC_OTG_SLOT_IDLE_MAX; + } else { + /* single buffered */ + sc->sc_chan_state[x].wait_sof = DWC_OTG_SLOT_IDLE_MIN; + } sc->sc_chan_state[x].allocated = 0; - sc->sc_chan_state[x].suspended = 0; - - /* keep track of used FIFO */ - sc->sc_tx_cur_size -= sc->sc_chan_state[x].tx_size; /* ack any pending messages */ if (sc->sc_last_rx_status != 0 && @@ -744,26 +707,39 @@ dwc_otg_host_channel_free(struct dwc_otg_td *td) sc->sc_active_rx_ep &= ~(1 << x); } +static void +dwc_otg_host_dump_rx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) +{ + /* dump any pending messages */ + if (sc->sc_last_rx_status != 0) { + if (td->channel < DWC_OTG_MAX_CHANNELS && + td->channel == GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status)) { + dwc_otg_common_rx_ack(sc); + } + } +} + static uint8_t -dwc_otg_host_setup_tx(struct dwc_otg_td *td) +dwc_otg_host_setup_tx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { struct usb_device_request req __aligned(4); - struct dwc_otg_softc *sc; uint32_t hcint; uint32_t hcchar; + uint8_t delta; - if (dwc_otg_host_channel_alloc(td)) - goto busy; - - /* get pointer to softc */ - sc = DWC_OTG_PC2SC(td->pc); + dwc_otg_host_dump_rx(sc, td); - hcint = sc->sc_chan_state[td->channel].hcint; + if (td->channel < DWC_OTG_MAX_CHANNELS) { + hcint = sc->sc_chan_state[td->channel].hcint; - DPRINTF("CH=%d ST=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n", - td->channel, td->state, hcint, - DWC_OTG_READ_4(sc, DOTG_HCCHAR(td->channel)), - DWC_OTG_READ_4(sc, DOTG_HCTSIZ(td->channel))); + DPRINTF("CH=%d ST=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n", + td->channel, td->state, hcint, + DWC_OTG_READ_4(sc, DOTG_HCCHAR(td->channel)), + DWC_OTG_READ_4(sc, DOTG_HCTSIZ(td->channel))); + } else { + hcint = 0; + goto check_state; + } if (hcint & (HCINT_RETRY | HCINT_ACK | HCINT_NYET)) { @@ -782,34 +758,23 @@ dwc_otg_host_setup_tx(struct dwc_otg_td *td) } } - /* channel must be disabled before we can complete the transfer */ - if (hcint & (HCINT_ERRORS | HCINT_RETRY | HCINT_ACK | HCINT_NYET)) { - - dwc_otg_host_channel_disable(sc, td->channel); - if (!(hcint & HCINT_ERRORS)) td->errcnt = 0; } +check_state: switch (td->state) { case DWC_CHAN_ST_START: - if (!dwc_otg_host_channel_wait(td)) - break; goto send_pkt; case DWC_CHAN_ST_WAIT_ANE: if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { - if (!dwc_otg_host_channel_wait(td)) - break; - td->did_nak = 1; + td->did_nak++; td->tt_scheduled = 0; goto send_pkt; - } - if (hcint & (HCINT_ACK | HCINT_NYET)) { - if (!dwc_otg_host_channel_wait(td)) - break; + } else if (hcint & (HCINT_ACK | HCINT_NYET)) { td->offset += td->tx_bytes; td->remainder -= td->tx_bytes; td->toggle = 1; @@ -820,35 +785,22 @@ dwc_otg_host_setup_tx(struct dwc_otg_td *td) case DWC_CHAN_ST_WAIT_S_ANE: if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { - if (!dwc_otg_host_channel_wait(td)) - break; - td->did_nak = 1; + td->did_nak++; td->tt_scheduled = 0; goto send_pkt; - } - if (hcint & (HCINT_ACK | HCINT_NYET)) { - if (!dwc_otg_host_channel_wait(td)) - break; + } else if (hcint & (HCINT_ACK | HCINT_NYET)) { goto send_cpkt; } break; case DWC_CHAN_ST_WAIT_C_ANE: if (hcint & HCINT_NYET) { - if (!dwc_otg_host_channel_wait(td)) - break; goto send_cpkt; - } - if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { - if (!dwc_otg_host_channel_wait(td)) - break; - td->did_nak = 1; + } else if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { + td->did_nak++; td->tt_scheduled = 0; goto send_pkt; - } - if (hcint & HCINT_ACK) { - if (!dwc_otg_host_channel_wait(td)) - break; + } else if (hcint & HCINT_ACK) { td->offset += td->tx_bytes; td->remainder -= td->tx_bytes; td->toggle = 1; @@ -857,8 +809,6 @@ dwc_otg_host_setup_tx(struct dwc_otg_td *td) break; case DWC_CHAN_ST_WAIT_C_PKT: - if (!dwc_otg_host_channel_wait(td)) - break; goto send_cpkt; default: @@ -867,20 +817,36 @@ dwc_otg_host_setup_tx(struct dwc_otg_td *td) 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) { - /* Wait for our turn, if TT transfer */ - if (td->tt_scheduled == 0 || - (sc->sc_last_frame_num & 7) < td->tt_start_slot) { - /* set return state */ + 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 tt_wait; + 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 { @@ -897,7 +863,8 @@ send_pkt: DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(td->channel), td->hcsplt); hcchar = td->hcchar; - hcchar &= ~HCCHAR_EPDIR_IN; + 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), hcchar); @@ -906,21 +873,37 @@ send_pkt: bus_space_write_region_4(sc->sc_io_tag, sc->sc_io_hdl, DOTG_DFIFO(td->channel), (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: - /* Wait for our turn, if TT transfer */ - if (td->tt_scheduled == 0 || - (sc->sc_last_frame_num & 7) < td->tt_complete_slot) { - /* set return state */ + /* 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 tt_wait; + 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 again */ - td->tt_complete_slot++; + + /* 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; @@ -931,32 +914,27 @@ send_cpkt: DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(td->channel), td->hcsplt); hcchar = td->hcchar; - hcchar &= ~HCCHAR_EPDIR_IN; + 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), hcchar); - goto busy; -tt_wait: - /* free allocated channel */ - dwc_otg_host_channel_free(td); 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_td *td) +dwc_otg_setup_rx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { - struct dwc_otg_softc *sc; struct usb_device_request req __aligned(4); uint32_t temp; uint16_t count; - /* get pointer to softc */ - sc = DWC_OTG_PC2SC(td->pc); - /* check endpoint status */ if (sc->sc_last_rx_status == 0) @@ -1097,23 +1075,9 @@ dwc_otg_host_rate_check_interrupt(struct dwc_otg_softc *sc, struct dwc_otg_td *t } static uint8_t -dwc_otg_host_rate_check(struct dwc_otg_td *td) +dwc_otg_host_rate_check(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { - struct dwc_otg_softc *sc; - uint8_t ep_type; - - /* get pointer to softc */ - sc = DWC_OTG_PC2SC(td->pc); - - if (td->channel < DWC_OTG_MAX_CHANNELS && - sc->sc_chan_state[td->channel].suspended) - goto busy; - - ep_type = ((td->hcchar & - HCCHAR_EPTYPE_MASK) >> HCCHAR_EPTYPE_SHIFT); - - if (ep_type == UE_ISOCHRONOUS) { - + if (td->ep_type == UE_ISOCHRONOUS) { /* non TT isochronous traffic */ if ((td->tmr_val != 0) || (sc->sc_last_frame_num & (td->tmr_res - 1))) { @@ -1122,11 +1086,11 @@ dwc_otg_host_rate_check(struct dwc_otg_td *td) td->tmr_val = 1; /* executed */ td->toggle = 0; - } else if (ep_type == UE_INTERRUPT) { + } else if (td->ep_type == UE_INTERRUPT) { if (!td->tt_scheduled) goto busy; td->tt_scheduled = 0; - } else if (td->did_nak != 0) { + } else if (td->did_nak >= DWC_OTG_NAK_MAX) { goto busy; } else if (td->set_toggle) { td->set_toggle = 0; @@ -1138,68 +1102,21 @@ busy: } static uint8_t -dwc_otg_host_data_rx(struct dwc_otg_td *td) +dwc_otg_host_data_rx_sub(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { - struct dwc_otg_softc *sc; - uint32_t hcint; - uint32_t hcchar; uint32_t count; - uint8_t ep_type; - - if (dwc_otg_host_channel_alloc(td)) - goto busy; - - /* get pointer to softc */ - sc = DWC_OTG_PC2SC(td->pc); - - ep_type = ((td->hcchar & - HCCHAR_EPTYPE_MASK) >> HCCHAR_EPTYPE_SHIFT); - - hcint = sc->sc_chan_state[td->channel].hcint; - - DPRINTF("CH=%d ST=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n", - td->channel, td->state, hcint, - DWC_OTG_READ_4(sc, DOTG_HCCHAR(td->channel)), - DWC_OTG_READ_4(sc, DOTG_HCTSIZ(td->channel))); - - /* 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", td->channel); - td->error_stall = 1; - td->error_any = 1; - goto complete; - } else if (hcint & HCINT_ERRORS) { - DPRINTF("CH=%d ERROR\n", td->channel); - td->errcnt++; - if (td->hcsplt != 0 || td->errcnt >= 3) { - if (ep_type != UE_ISOCHRONOUS) { - td->error_any = 1; - goto complete; - } - } - } - - /* channel must be disabled before we can complete the transfer */ - - if (hcint & (HCINT_ERRORS | HCINT_RETRY | - HCINT_ACK | HCINT_NYET)) { - - dwc_otg_host_channel_disable(sc, td->channel); - - if (!(hcint & HCINT_ERRORS)) - td->errcnt = 0; - } + uint8_t channel; /* check endpoint status */ if (sc->sc_last_rx_status == 0) - goto check_state; + goto busy; - if (GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status) != td->channel) - goto check_state; + channel = td->channel; + 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: @@ -1207,7 +1124,7 @@ dwc_otg_host_data_rx(struct dwc_otg_td *td) DPRINTF("DATA ST=%d STATUS=0x%08x\n", (int)td->state, (int)sc->sc_last_rx_status); - if (hcint & HCINT_SOFTWARE_ONLY) { + if (sc->sc_chan_state[channel].hcint & HCINT_SOFTWARE_ONLY) { /* * When using SPLIT transactions on interrupt * endpoints, sometimes data occurs twice. @@ -1220,7 +1137,7 @@ dwc_otg_host_data_rx(struct dwc_otg_td *td) count = GRXSTSRD_BCNT_GET(sc->sc_last_rx_status); /* check for isochronous transfer or high-speed bandwidth endpoint */ - if (ep_type == UE_ISOCHRONOUS || td->max_packet_count > 1) { + if (td->ep_type == UE_ISOCHRONOUS || td->max_packet_count > 1) { if ((sc->sc_last_rx_status & GRXSTSRD_DPID_MASK) != GRXSTSRD_DPID_DATA0) { td->tt_xactpos = HCSPLT_XACTPOS_MIDDLE; } else { @@ -1269,21 +1186,75 @@ dwc_otg_host_data_rx(struct dwc_otg_td *td) td->remainder -= count; td->offset += count; - hcint |= HCINT_SOFTWARE_ONLY | HCINT_ACK; - sc->sc_chan_state[td->channel].hcint = hcint; + 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; + uint32_t hcchar; + uint8_t delta; + uint8_t channel; + + channel = td->channel; + + 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))); + + /* 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)) + 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; + } + } else { + hcint = 0; + } -check_state: switch (td->state) { case DWC_CHAN_ST_START: - if (!dwc_otg_host_channel_wait(td)) - break; if (td->hcsplt != 0) goto receive_spkt; else @@ -1291,38 +1262,49 @@ check_state: case DWC_CHAN_ST_WAIT_ANE: if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { - if (!dwc_otg_host_channel_wait(td)) - break; - - td->did_nak = 1; + 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; + } + } + td->did_nak++; td->tt_scheduled = 0; if (td->hcsplt != 0) goto receive_spkt; else goto receive_pkt; - } - if (!(hcint & HCINT_SOFTWARE_ONLY)) { - if (hcint & HCINT_NYET) { - if (ep_type == UE_ISOCHRONOUS) { - /* we missed the service interval */ - goto complete; - } - if (!dwc_otg_host_channel_wait(td)) - break; + } 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; } - break; - } - if (hcint & (HCINT_ACK | HCINT_NYET)) { - if (!dwc_otg_host_channel_wait(td)) - break; + } else if (hcint & HCINT_ACK) { + /* wait for data - ACK arrived first */ + if (!(hcint & HCINT_SOFTWARE_ONLY)) + goto busy; - if (ep_type == UE_ISOCHRONOUS) { + if (td->ep_type == UE_ISOCHRONOUS) { /* check if we are complete */ if ((td->remainder == 0) || - (td->tt_xactpos == HCSPLT_XACTPOS_BEGIN)) + (td->tt_xactpos == HCSPLT_XACTPOS_BEGIN)) { goto complete; - + } + /* get another packet */ goto receive_pkt; } else { /* check if we are complete */ @@ -1336,6 +1318,7 @@ check_state: */ } td->tt_scheduled = 0; + td->did_nak = 0; if (td->hcsplt != 0) goto receive_spkt; else @@ -1350,23 +1333,19 @@ check_state: * case of interrupt and isochronous transfers: */ if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { - if (!dwc_otg_host_channel_wait(td)) - break; - - td->did_nak = 1; + td->did_nak++; td->tt_scheduled = 0; goto receive_spkt; - } - if (hcint & (HCINT_ACK | HCINT_NYET)) { - if (!dwc_otg_host_channel_wait(td)) - break; + } 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: - if (!dwc_otg_host_channel_wait(td)) - break; goto receive_pkt; default: @@ -1375,119 +1354,138 @@ check_state: goto busy; receive_pkt: + /* free existing channel, if any */ + dwc_otg_host_channel_free(sc, td); + if (td->hcsplt != 0) { - /* Wait for our turn, if TT transfer */ - if (td->tt_scheduled == 0 || - (sc->sc_last_frame_num & 7) < td->tt_complete_slot) { - /* set return state */ + 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 tt_wait; + goto busy; } - /* wait until next slot before trying again */ - td->tt_complete_slot++; - - /* set toggle, if any */ - if (td->set_toggle) { - td->set_toggle = 0; - td->toggle = 1; + 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; - count = HCSPLT_XACTLEN_MAX; } else if (td->tt_xactpos == HCSPLT_XACTPOS_BEGIN && - dwc_otg_host_rate_check(td)) { - td->state = DWC_CHAN_ST_START; - dwc_otg_host_channel_free(td); + 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; - } else { - count = td->max_packet_size; } + + channel = td->channel; + + /* set toggle, if any */ + if (td->set_toggle) { + td->set_toggle = 0; + td->toggle = 1; + } + td->state = DWC_CHAN_ST_WAIT_ANE; /* receive one packet */ - DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel), - (count << HCTSIZ_XFERSIZE_SHIFT) | + 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(td->channel), td->hcsplt); - - /* send ASAP */ - if ((ep_type == UE_ISOCHRONOUS) && !(sc->sc_last_frame_num & 1)) - td->hcchar |= HCCHAR_ODDFRM; - else - td->hcchar &= ~HCCHAR_ODDFRM; + 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) + hcchar |= HCCHAR_ODDFRM; + else + hcchar &= ~HCCHAR_ODDFRM; + /* must enable channel before data can be received */ - DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel), hcchar); + 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: - /* Wait for our turn, if TT transfer */ - if (td->tt_scheduled == 0) { - if (ep_type == UE_INTERRUPT) { - td->state = DWC_CHAN_ST_START; - dwc_otg_host_channel_free(td); - goto busy; - } - /* set return state */ + /* 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 tt_wait; + goto busy; } - if ((sc->sc_last_frame_num & 7) < td->tt_start_slot) { - /* set return state */ + 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 tt_wait; + goto busy; } - /* send ASAP */ - if ((ep_type == UE_ISOCHRONOUS) && !(sc->sc_last_frame_num & 1)) - td->hcchar |= HCCHAR_ODDFRM; - else - td->hcchar &= ~HCCHAR_ODDFRM; + /* allocate a new channel */ + if (dwc_otg_host_channel_alloc(sc, td, 0)) { + td->state = DWC_CHAN_ST_START; + goto busy; + } + + channel = td->channel; td->hcsplt &= ~HCSPLT_COMPSPLT; td->state = DWC_CHAN_ST_WAIT_S_ANE; /* receive one packet */ - DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel), - (td->toggle ? (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT) : - (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT))); + DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), + (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT)); - DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(td->channel), td->hcsplt); + DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(channel), td->hcsplt); + + /* send after next SOF event */ + if ((sc->sc_last_frame_num & 1) == 0) + td->hcchar |= HCCHAR_ODDFRM; + else + td->hcchar &= ~HCCHAR_ODDFRM; hcchar = td->hcchar; hcchar |= HCCHAR_EPDIR_IN; - /* must enable channel before data can be received */ - DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel), hcchar); - goto busy; + /* wait until next slot before trying complete split */ + td->tt_complete_slot = sc->sc_last_frame_num + 1; -tt_wait: - /* free allocated channel */ - dwc_otg_host_channel_free(td); + /* 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_td *td) +dwc_otg_data_rx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { - struct dwc_otg_softc *sc; uint32_t temp; uint16_t count; uint8_t got_short; got_short = 0; - /* get pointer to softc */ - sc = DWC_OTG_PC2SC(td->pc); - /* check endpoint status */ if (sc->sc_last_rx_status == 0) goto not_complete; @@ -1589,79 +1587,67 @@ not_complete: } static uint8_t -dwc_otg_host_data_tx(struct dwc_otg_td *td) +dwc_otg_host_data_tx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { - struct dwc_otg_softc *sc; uint32_t count; uint32_t hcint; uint32_t hcchar; - uint8_t ep_type; - - if (dwc_otg_host_channel_alloc(td)) - goto busy; + uint8_t delta; + uint8_t channel; - /* get pointer to softc */ - sc = DWC_OTG_PC2SC(td->pc); + dwc_otg_host_dump_rx(sc, td); - ep_type = ((td->hcchar & - HCCHAR_EPTYPE_MASK) >> HCCHAR_EPTYPE_SHIFT); + channel = td->channel; - hcint = sc->sc_chan_state[td->channel].hcint; + 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", - td->channel, td->state, hcint, - DWC_OTG_READ_4(sc, DOTG_HCCHAR(td->channel)), - DWC_OTG_READ_4(sc, DOTG_HCTSIZ(td->channel))); + 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", td->channel); - td->error_stall = 1; - td->error_any = 1; - goto complete; - } else if (hcint & HCINT_ERRORS) { - DPRINTF("CH=%d ERROR\n", td->channel); - td->errcnt++; - if (td->hcsplt != 0 || td->errcnt >= 3) { + 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; + } } - } - - /* channel must be disabled before we can complete the transfer */ - - if (hcint & (HCINT_ERRORS | HCINT_RETRY | - HCINT_ACK | HCINT_NYET)) { - dwc_otg_host_channel_disable(sc, td->channel); + if (hcint & (HCINT_ERRORS | HCINT_RETRY | + HCINT_ACK | HCINT_NYET)) { - if (!(hcint & HCINT_ERRORS)) - td->errcnt = 0; + if (!(hcint & HCINT_ERRORS)) + td->errcnt = 0; + } + } else { + hcint = 0; } switch (td->state) { case DWC_CHAN_ST_START: - if (!dwc_otg_host_channel_wait(td)) - break; goto send_pkt; case DWC_CHAN_ST_WAIT_ANE: if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { - if (!dwc_otg_host_channel_wait(td)) - break; - td->did_nak = 1; + td->did_nak++; td->tt_scheduled = 0; goto send_pkt; - } - if (hcint & (HCINT_ACK | HCINT_NYET)) { - if (!dwc_otg_host_channel_wait(td)) - break; - + } else if (hcint & (HCINT_ACK | HCINT_NYET)) { td->offset += td->tx_bytes; td->remainder -= td->tx_bytes; td->toggle ^= 1; + td->did_nak = 0; td->tt_scheduled = 0; /* check remainder */ @@ -1680,38 +1666,27 @@ dwc_otg_host_data_tx(struct dwc_otg_td *td) case DWC_CHAN_ST_WAIT_S_ANE: if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { - if (!dwc_otg_host_channel_wait(td)) - break; - td->did_nak = 1; + td->did_nak++; td->tt_scheduled = 0; goto send_pkt; - } - if (hcint & (HCINT_ACK | HCINT_NYET)) { - if (!dwc_otg_host_channel_wait(td)) - break; + } 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) { - if (!dwc_otg_host_channel_wait(td)) - break; goto send_cpkt; - } - if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { - if (!dwc_otg_host_channel_wait(td)) - break; - td->did_nak = 1; + } else if (hcint & (HCINT_RETRY | HCINT_ERRORS)) { + td->did_nak++; td->tt_scheduled = 0; goto send_pkt; - } - if (hcint & HCINT_ACK) { - if (!dwc_otg_host_channel_wait(td)) - break; + } 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 */ @@ -1726,46 +1701,35 @@ dwc_otg_host_data_tx(struct dwc_otg_td *td) break; case DWC_CHAN_ST_WAIT_C_PKT: - if (!dwc_otg_host_channel_wait(td)) - break; goto send_cpkt; case DWC_CHAN_ST_TX_WAIT_ISOC: /* Check if isochronous OUT traffic is complete */ - if ((hcint & HCINT_ACK) == 0) + if ((hcint & HCINT_HCH_DONE_MASK) == 0) break; td->offset += td->tx_bytes; td->remainder -= td->tx_bytes; - /* Update split token according to specification */ - if (td->hcsplt != 0) { - if (td->tt_xactpos == HCSPLT_XACTPOS_BEGIN) - td->tt_xactpos = HCSPLT_XACTPOS_MIDDLE; - } else if (td->max_packet_count > 1) { - td->tt_xactpos++; - } + if (td->hcsplt != 0 || td->remainder == 0) + goto complete; - dwc_otg_host_channel_disable(sc, td->channel); + /* check for next packet */ + if (td->max_packet_count > 1) + td->tt_xactpos++; - if (td->remainder == 0) - goto complete; + /* free existing channel, if any */ + dwc_otg_host_channel_free(sc, td); td->state = DWC_CHAN_ST_TX_PKT_ISOC; /* FALLTHROUGH */ case DWC_CHAN_ST_TX_PKT_ISOC: - if (!dwc_otg_host_channel_wait(td)) + if (dwc_otg_host_channel_alloc(sc, td, 1)) break; - - if (td->hcsplt != 0) { - if ((sc->sc_last_frame_num & 7) < td->tt_start_slot) - goto tt_wait; - /* packets must be 125us apart */ - td->tt_start_slot++; - } + channel = td->channel; goto send_isoc_pkt; default: break; @@ -1773,39 +1737,42 @@ dwc_otg_host_data_tx(struct dwc_otg_td *td) goto busy; send_pkt: + /* free existing channel(s), if any */ + dwc_otg_host_channel_free(sc, td); + if (td->hcsplt != 0) { - /* Wait for our turn, if TT transfer */ - if (td->tt_scheduled == 0) { - if (ep_type == UE_INTERRUPT) { - td->state = DWC_CHAN_ST_START; - dwc_otg_host_channel_free(td); - goto busy; - } - /* set return state */ + 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 tt_wait; + goto busy; } - if ((sc->sc_last_frame_num & 7) < td->tt_start_slot) { - /* set return state */ + 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 tt_wait; + goto busy; } + } else if (dwc_otg_host_rate_check(sc, td)) { + td->state = DWC_CHAN_ST_START; + goto busy; + } - /* packets must be 125us apart */ - td->tt_start_slot++; - - /* set toggle, if any */ - if (td->set_toggle) { - td->set_toggle = 0; - td->toggle = 1; - } - } else if (dwc_otg_host_rate_check(td)) { + /* allocate a new channel */ + if (dwc_otg_host_channel_alloc(sc, td, 1)) { td->state = DWC_CHAN_ST_START; - dwc_otg_host_channel_free(td); goto busy; } - if (ep_type == UE_ISOCHRONOUS) { + channel = td->channel; + + /* set toggle, if any */ + if (td->set_toggle) { + td->set_toggle = 0; + td->toggle = 1; + } + + if (td->ep_type == UE_ISOCHRONOUS) { send_isoc_pkt: /* Isochronous OUT transfers don't have any ACKs */ td->state = DWC_CHAN_ST_TX_WAIT_ISOC; @@ -1813,23 +1780,14 @@ send_isoc_pkt: if (td->hcsplt != 0) { /* get maximum transfer length */ count = td->remainder; - - /* Update split token according to specification */ - if (td->tt_xactpos == HCSPLT_XACTPOS_BEGIN) { - if (count <= HCSPLT_XACTLEN_MAX) - td->tt_xactpos = HCSPLT_XACTPOS_ALL; - else - count = HCSPLT_XACTLEN_MAX; - } else if (td->tt_xactpos == HCSPLT_XACTPOS_MIDDLE) { - if (count <= HCSPLT_XACTLEN_MAX) - td->tt_xactpos = HCSPLT_XACTPOS_LAST; - else - count = HCSPLT_XACTLEN_MAX; + 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 |= ((uint32_t)td->tt_xactpos << HCSPLT_XACTPOS_SHIFT); + td->hcsplt |= (HCSPLT_XACTPOS_ALL << HCSPLT_XACTPOS_SHIFT); } else { /* send one packet at a time */ count = td->max_packet_size; @@ -1883,24 +1841,24 @@ send_isoc_pkt: } if (td->tt_xactpos == td->npkt) { if (td->npkt == 1) { - DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel), + DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), (count << HCTSIZ_XFERSIZE_SHIFT) | (1 << HCTSIZ_PKTCNT_SHIFT) | (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT)); } else if (td->npkt == 2) { - DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel), + DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), (count << HCTSIZ_XFERSIZE_SHIFT) | (1 << HCTSIZ_PKTCNT_SHIFT) | (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT)); } else { - DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel), + DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), (count << HCTSIZ_XFERSIZE_SHIFT) | (1 << HCTSIZ_PKTCNT_SHIFT) | (HCTSIZ_PID_DATA2 << HCTSIZ_PID_SHIFT)); } td->npkt = 0; } else { - DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel), + DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), (count << HCTSIZ_XFERSIZE_SHIFT) | (1 << HCTSIZ_PKTCNT_SHIFT) | (HCTSIZ_PID_MDATA << HCTSIZ_PID_SHIFT)); @@ -1908,26 +1866,26 @@ send_isoc_pkt: } else { /* TODO: HCTSIZ_DOPNG */ - DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel), + 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(td->channel), td->hcsplt); - - /* send ASAP */ - if ((ep_type == UE_ISOCHRONOUS) && !(sc->sc_last_frame_num & 1)) - td->hcchar |= HCCHAR_ODDFRM; - else - td->hcchar &= ~HCCHAR_ODDFRM; + 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) + hcchar |= HCCHAR_ODDFRM; + else + hcchar &= ~HCCHAR_ODDFRM; + /* must enable before writing data to FIFO */ - DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel), hcchar); + DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(channel), hcchar); if (count != 0) { @@ -1940,7 +1898,7 @@ send_isoc_pkt: /* transfer data into FIFO */ bus_space_write_region_4(sc->sc_io_tag, sc->sc_io_hdl, - DOTG_DFIFO(td->channel), + DOTG_DFIFO(channel), sc->sc_tx_bounce_buffer, (count + 3) / 4); } @@ -1949,45 +1907,63 @@ send_isoc_pkt: goto busy; send_cpkt: - /* Wait for our turn, if TT transfer */ - if (td->tt_scheduled == 0 || - (sc->sc_last_frame_num & 7) < td->tt_complete_slot) { - /* set return state */ + /* 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 tt_wait; + goto busy; } - /* wait until next slot before trying again */ - td->tt_complete_slot++; + + channel = td->channel; td->hcsplt |= HCSPLT_COMPSPLT; td->state = DWC_CHAN_ST_WAIT_C_ANE; - DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel), - (td->toggle ? (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT) : - (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT))); + DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel), + (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT)); - DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(td->channel), td->hcsplt); + DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(channel), td->hcsplt); hcchar = td->hcchar; hcchar &= ~HCCHAR_EPDIR_IN; - /* must enable channel before writing data to FIFO */ - DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel), hcchar); - goto busy; + /* receive complete split ASAP */ + if ((sc->sc_last_frame_num & 1) != 0) + hcchar |= HCCHAR_ODDFRM; + else + hcchar &= ~HCCHAR_ODDFRM; + + /* must enable channel before data can be received */ + DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(channel), hcchar); -tt_wait: - /* free allocated channel */ - dwc_otg_host_channel_free(td); + /* 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_td *td) +dwc_otg_data_tx(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { - struct dwc_otg_softc *sc; uint32_t max_buffer; uint32_t count; uint32_t fifo_left; @@ -1997,9 +1973,6 @@ dwc_otg_data_tx(struct dwc_otg_td *td) to = 3; /* don't loop forever! */ - /* get pointer to softc */ - sc = DWC_OTG_PC2SC(td->pc); - max_buffer = sc->sc_hw_ep_profile[td->ep_no].max_buffer; repeat: @@ -2177,14 +2150,10 @@ not_complete: } static uint8_t -dwc_otg_data_tx_sync(struct dwc_otg_td *td) +dwc_otg_data_tx_sync(struct dwc_otg_softc *sc, struct dwc_otg_td *td) { - struct dwc_otg_softc *sc; uint32_t temp; - /* get pointer to softc */ - sc = DWC_OTG_PC2SC(td->pc); - /* * If all packets are transferred we are complete: */ @@ -2221,30 +2190,22 @@ not_complete: return (1); /* not complete */ } -static uint8_t -dwc_otg_xfer_do_fifo(struct usb_xfer *xfer) +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 channel; uint8_t tmr_val; uint8_t tmr_res; DPRINTFN(9, "\n"); td = xfer->td_transfer_cache; + if (td == NULL) + return; - /* - * If we are suspended in host mode and no channel is - * allocated, simply return: - */ - if (xfer->xroot->udev->flags.self_suspended != 0 && - xfer->xroot->udev->flags.usb_mode == USB_MODE_HOST && - td->channel >= DWC_OTG_MAX_CHANNELS) { - return (1); /* not complete */ - } while (1) { - if ((td->func) (td)) { + if ((td->func) (sc, td)) { /* operation in progress */ break; } @@ -2269,21 +2230,33 @@ dwc_otg_xfer_do_fifo(struct usb_xfer *xfer) tmr_res = td->tmr_res; tmr_val = td->tmr_val; toggle = td->toggle; - channel = td->channel; td = td->obj_next; xfer->td_transfer_cache = td; td->toggle = toggle; /* transfer toggle */ - td->channel = channel; /* transfer channel */ td->tmr_res = tmr_res; td->tmr_val = tmr_val; } - return (1); /* not complete */ + return; done: - /* compute all actual lengths */ + xfer->td_transfer_cache = NULL; + sc->sc_xfer_complete = 1; +} - dwc_otg_standard_done(xfer); - return (0); /* complete */ +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 @@ -2297,18 +2270,24 @@ dwc_otg_timer(void *_sc) DPRINTF("\n"); + USB_BUS_SPIN_LOCK(&sc->sc_bus); + /* increment timer value */ sc->sc_tmr_val++; TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { td = xfer->td_transfer_cache; - if (td != NULL) + if (td != NULL) { + /* reset NAK counter */ td->did_nak = 0; + } } /* 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, @@ -2344,239 +2323,249 @@ dwc_otg_timer_stop(struct dwc_otg_softc *sc) } static void -dwc_otg_update_host_transfer_schedule(struct dwc_otg_softc *sc) +dwc_otg_host_channel_disable(struct dwc_otg_softc *sc, uint8_t x) +{ + uint32_t hcchar; + + hcchar = DWC_OTG_READ_4(sc, DOTG_HCCHAR(x)); + + /* disable host channel, if any */ + if (hcchar & (HCCHAR_CHENA | HCCHAR_CHDIS)) { + /* disable channel */ + DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(x), + HCCHAR_CHENA | HCCHAR_CHDIS); + /* wait for chip to get its brains in order */ + sc->sc_chan_state[x].wait_sof = 2; + } + + /* release TX FIFO usage, if any */ + sc->sc_tx_cur_p_level -= sc->sc_chan_state[x].tx_p_size; + sc->sc_tx_cur_np_level -= sc->sc_chan_state[x].tx_np_size; + + /* don't release TX FIFO usage twice */ + sc->sc_chan_state[x].tx_p_size = 0; + sc->sc_chan_state[x].tx_np_size = 0; +} + +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; - uint8_t needsof; uint16_t temp; + uint16_t slot; + uint8_t x; - /* FS/LS TT frames are one behind, so add one here */ - temp = (DWC_OTG_READ_4(sc, DOTG_HFNUM) + 1) & HFNUM_FRNUM_MASK; + temp = DWC_OTG_READ_4(sc, DOTG_HFNUM) & DWC_OTG_FRAME_MASK; if (sc->sc_last_frame_num == temp) - return; + return (0); sc->sc_last_frame_num = temp; - needsof = 0; - TAILQ_INIT(&head); - if (sc->sc_irq_mask & GINTMSK_SOFMSK) { - uint8_t x; + for (x = 0; x != sc->sc_host_ch_max; x++) { + if (sc->sc_chan_state[x].wait_sof == 0) + continue; - for (x = 0; x != sc->sc_host_ch_max; x++) { - if (sc->sc_chan_state[x].wait_sof != 0) { - if (--(sc->sc_chan_state[x].wait_sof) != 0) - needsof = 1; - } - } + sc->sc_needsof = 1; + if (--(sc->sc_chan_state[x].wait_sof) == 0) + dwc_otg_host_channel_disable(sc, x); } if ((temp & 7) == 0) { - /* reset TT info */ + /* reset the schedule */ memset(sc->sc_tt_info, 0, sizeof(sc->sc_tt_info)); - /* - * Plan ahead FULL speed transfers going through the - * transaction translator, according to the USB - * specified priority: - */ TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) { - struct dwc_otg_tt_info *pinfo; - td = xfer->td_transfer_cache; - if (td == NULL || td->did_nak != 0 || - (td->hcchar & HCCHAR_EPTYPE_MASK) != - (UE_CONTROL << HCCHAR_EPTYPE_SHIFT)) + if (td == NULL || td->ep_type != UE_ISOCHRONOUS) continue; - needsof = 1; - - if (td->hcsplt == 0) + /* check for IN direction */ + if ((td->hcchar & HCCHAR_EPDIR_IN) != 0) continue; - /* Reset state if stuck waiting for complete split */ - if (td->state == DWC_CHAN_ST_WAIT_C_PKT) - td->state = DWC_CHAN_ST_START; + /* execute more frames */ + td->tmr_val = 0; - pinfo = sc->sc_tt_info + td->tt_index; + sc->sc_needsof = 1; - td->tt_start_slot = pinfo->slot_index; - pinfo->bytes_used += td->max_packet_size; - while (pinfo->bytes_used >= HCSPLT_XACTLEN_MAX) { - pinfo->bytes_used -= HCSPLT_XACTLEN_MAX; - pinfo->slot_index ++; - } + if (td->hcsplt == 0 || td->tt_scheduled != 0) + continue; - td->tt_complete_slot = pinfo->slot_index + 2; - if (td->tt_complete_slot < 8) { - td->tt_scheduled = 1; - TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); - TAILQ_INSERT_TAIL(&head, xfer, wait_entry); - } else { - td->tt_scheduled = 0; + /* 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) { - struct dwc_otg_tt_info *pinfo; - td = xfer->td_transfer_cache; - if (td == NULL || - (td->hcchar & HCCHAR_EPTYPE_MASK) != - (UE_ISOCHRONOUS << HCCHAR_EPTYPE_SHIFT)) + if (td == NULL || td->ep_type != UE_ISOCHRONOUS) + continue; + + /* check for OUT direction */ + if ((td->hcchar & HCCHAR_EPDIR_IN) == 0) continue; /* execute more frames */ td->tmr_val = 0; - needsof = 1; + sc->sc_needsof = 1; - if (td->hcsplt == 0) + if (td->hcsplt == 0 || td->tt_scheduled != 0) continue; - /* Reset state if stuck waiting for complete split */ - if (td->state == DWC_CHAN_ST_WAIT_C_PKT) - td->state = DWC_CHAN_ST_START; - - pinfo = sc->sc_tt_info + td->tt_index; - - td->tt_start_slot = pinfo->slot_index; - pinfo->bytes_used += td->remainder; - while (pinfo->bytes_used >= HCSPLT_XACTLEN_MAX) { - pinfo->bytes_used -= HCSPLT_XACTLEN_MAX; - pinfo->slot_index ++; - } - - td->tt_complete_slot = pinfo->slot_index + 2; - if (td->tt_complete_slot < 8) { - td->tt_scheduled = 1; - TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); - TAILQ_INSERT_TAIL(&head, xfer, wait_entry); - } else { - td->tt_scheduled = 0; - } + /* 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) { - struct dwc_otg_tt_info *pinfo; - td = xfer->td_transfer_cache; - if (td == NULL || - (td->hcchar & HCCHAR_EPTYPE_MASK) != - (UE_INTERRUPT << HCCHAR_EPTYPE_SHIFT)) { + 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; - needsof = 1; - if (td->hcsplt == 0) { + sc->sc_needsof = 1; td->tt_scheduled = 1; continue; } - /* Reset state if stuck waiting for complete split */ - if (td->state == DWC_CHAN_ST_WAIT_C_PKT) - td->state = DWC_CHAN_ST_START; - - pinfo = sc->sc_tt_info + td->tt_index; + /* 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); + } - td->tt_start_slot = pinfo->slot_index; - pinfo->bytes_used += td->remainder; - while (pinfo->bytes_used >= HCSPLT_XACTLEN_MAX) { - pinfo->bytes_used -= HCSPLT_XACTLEN_MAX; - pinfo->slot_index ++; + 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 || + td->did_nak >= DWC_OTG_NAK_MAX) { + continue; } - td->tt_complete_slot = pinfo->slot_index + 2; - if (td->tt_complete_slot < 8) { - td->tt_scheduled = 1; - TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); - TAILQ_INSERT_TAIL(&head, xfer, wait_entry); - } else { - td->tt_scheduled = 0; - } + 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) { - struct dwc_otg_tt_info *pinfo; - td = xfer->td_transfer_cache; - if (td == NULL || td->did_nak != 0 || - (td->hcchar & HCCHAR_EPTYPE_MASK) != - (UE_BULK << HCCHAR_EPTYPE_SHIFT)) { + if (td == NULL || + td->ep_type != UE_BULK || + td->did_nak >= DWC_OTG_NAK_MAX) { continue; } - needsof = 1; + sc->sc_needsof = 1; - if (td->hcsplt == 0) + if (td->hcsplt == 0 || td->tt_scheduled != 0) continue; - if ((temp & 7) == 0) { - /* Reset state if stuck waiting for complete split */ - if (td->state == DWC_CHAN_ST_WAIT_C_PKT) - td->state = DWC_CHAN_ST_START; - } else if (td->tt_scheduled != 0) - continue; /* already scheduled */ - - pinfo = sc->sc_tt_info + td->tt_index; - - td->tt_start_slot = pinfo->slot_index; - pinfo->bytes_used += td->remainder; - while (pinfo->bytes_used >= HCSPLT_XACTLEN_MAX) { - pinfo->bytes_used -= HCSPLT_XACTLEN_MAX; - pinfo->slot_index ++; - } - - td->tt_complete_slot = pinfo->slot_index + 2; - if (td->tt_complete_slot < 8) { - td->tt_scheduled = 1; - TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry); - TAILQ_INSERT_TAIL(&head, xfer, wait_entry); - } else { - td->tt_scheduled = 0; - } + /* 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); } } - /* TT transfers need to be executed in a specific order */ + /* 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 TT transfers first in the queue */ + /* put non-TT BULK 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_BULK) + 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)needsof); + (int)temp, (int)sc->sc_needsof); /* update SOF IRQ mask */ if (sc->sc_irq_mask & GINTMSK_SOFMSK) { - if (needsof == 0) { + if (sc->sc_needsof == 0) { sc->sc_irq_mask &= ~GINTMSK_SOFMSK; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); } } else { - if (needsof != 0) { + 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(struct dwc_otg_softc *sc) +dwc_otg_interrupt_poll_locked(struct dwc_otg_softc *sc) { struct usb_xfer *xfer; uint32_t temp; @@ -2661,26 +2650,9 @@ repeat: got_rx_status = 1; } - /* scan for completion events first */ - TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { - if (!dwc_otg_xfer_do_fifo(xfer)) { - /* queue has been modified */ - goto repeat; - } - } - - 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(sc); - - /* start re-scheduled transfers */ - TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { - if (!dwc_otg_xfer_do_fifo(xfer)) { - /* queue has been modified */ - goto repeat; - } - } - } + /* 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 */ @@ -2691,6 +2663,24 @@ repeat: sc->sc_irq_mask &= ~GINTMSK_RXFLVLMSK; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); } + + if (sc->sc_flags.status_device_mode == 0 && sc->sc_xfer_complete == 0) { + /* update host transfer schedule, so that new transfers can be issued */ + if (dwc_otg_update_host_transfer_schedule_locked(sc)) + goto repeat; + } +} + +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 @@ -2726,16 +2716,64 @@ dwc_otg_vbus_interrupt(struct dwc_otg_softc *sc, uint8_t is_on) } } +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 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(struct dwc_otg_softc *sc) +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); - DWC_OTG_WRITE_4(sc, DOTG_GINTSTS, status); + + /* 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), @@ -2752,8 +2790,6 @@ dwc_otg_interrupt(struct dwc_otg_softc *sc) /* Disable SOF interrupt */ sc->sc_irq_mask &= ~GINTMSK_SOFMSK; - /* Enable RX frame interrupt */ - sc->sc_irq_mask |= GINTMSK_RXFLVLMSK; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); /* complete root HUB interrupt endpoint */ @@ -2777,7 +2813,7 @@ dwc_otg_interrupt(struct dwc_otg_softc *sc) sc->sc_flags.port_enabled = 1; /* reset FIFOs */ - dwc_otg_init_fifo(sc, DWC_MODE_DEVICE); + (void) dwc_otg_init_fifo(sc, DWC_MODE_DEVICE); /* reset function address */ dwc_otg_set_address(sc, 0); @@ -2794,7 +2830,7 @@ dwc_otg_interrupt(struct dwc_otg_softc *sc) * suspend and RX frame interrupt: */ sc->sc_irq_mask &= ~(GINTMSK_WKUPINTMSK | GINTMSK_SOFMSK); - sc->sc_irq_mask |= (GINTMSK_USBSUSPMSK | GINTMSK_RXFLVLMSK); + sc->sc_irq_mask |= GINTMSK_USBSUSPMSK; DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); /* complete root HUB interrupt endpoint */ @@ -2865,10 +2901,6 @@ dwc_otg_interrupt(struct dwc_otg_softc *sc) /* complete root HUB interrupt endpoint */ dwc_otg_root_intr(sc); - /* disable RX FIFO level interrupt */ - sc->sc_irq_mask &= ~GINTMSK_RXFLVLMSK; - DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask); - /* update host frame interval */ dwc_otg_update_host_frame_interval(sc); } @@ -2905,23 +2937,19 @@ dwc_otg_interrupt(struct dwc_otg_softc *sc) (temp & (GOTGCTL_ASESVLD | GOTGCTL_BSESVLD)) ? 1 : 0); } - /* clear all IN endpoint interrupts */ - if (status & GINTSTS_IEPINT) { - uint32_t temp; - uint8_t x; + if (sc->sc_xfer_complete != 0) { + sc->sc_xfer_complete = 0; - 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); - } + /* complete FIFOs, if any */ + dwc_otg_interrupt_complete_locked(sc); + + if (sc->sc_flags.status_device_mode == 0) { + /* update host transfer schedule, so that new transfers can be issued */ + if (dwc_otg_update_host_transfer_schedule_locked(sc)) + dwc_otg_interrupt_poll_locked(sc); } } - - /* poll FIFO(s) */ - dwc_otg_interrupt_poll(sc); - + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); USB_BUS_UNLOCK(&sc->sc_bus); } @@ -2956,7 +2984,6 @@ dwc_otg_setup_standard_chain_sub(struct dwc_otg_std_temp *temp) td->state = 0; td->errcnt = 0; td->tt_scheduled = 0; - td->tt_index = temp->tt_index; td->tt_xactpos = HCSPLT_XACTPOS_BEGIN; } @@ -2965,7 +2992,6 @@ dwc_otg_setup_standard_chain(struct usb_xfer *xfer) { struct dwc_otg_std_temp temp; struct dwc_otg_td *td; - struct usb_device *udev; uint32_t x; uint8_t need_sync; uint8_t is_host; @@ -2976,16 +3002,6 @@ dwc_otg_setup_standard_chain(struct usb_xfer *xfer) temp.max_frame_size = xfer->max_frame_size; - udev = xfer->xroot->udev; - if (udev->parent_hs_hub != NULL && udev->speed != USB_SPEED_HIGH) { - if (udev->parent_hs_hub->ddesc.bDeviceProtocol == UDPROTO_HSHUBMTT) - temp.tt_index = udev->device_index; - else - temp.tt_index = udev->parent_hs_hub->device_index; - } else { - temp.tt_index = udev->device_index; - } - td = xfer->td_start[0]; xfer->td_transfer_first = td; xfer->td_transfer_cache = td; @@ -3170,10 +3186,8 @@ dwc_otg_setup_standard_chain(struct usb_xfer *xfer) struct dwc_otg_softc *sc; uint32_t hcchar; uint32_t hcsplt; - uint8_t xfer_type; sc = DWC_OTG_BUS2SC(xfer->xroot->bus); - xfer_type = xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE; /* get first again */ td = xfer->td_transfer_first; @@ -3181,11 +3195,28 @@ dwc_otg_setup_standard_chain(struct usb_xfer *xfer) hcchar = (xfer->address << HCCHAR_DEVADDR_SHIFT) | - (xfer_type << HCCHAR_EPTYPE_SHIFT) | ((xfer->endpointno & UE_ADDR) << HCCHAR_EPNUM_SHIFT) | (xfer->max_packet_size << HCCHAR_MPS_SHIFT) | HCCHAR_CHENA; + /* + * 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) @@ -3204,7 +3235,7 @@ dwc_otg_setup_standard_chain(struct usb_xfer *xfer) } else { hcsplt = 0; } - if (xfer_type == UE_INTERRUPT) { + if (td->ep_type == UE_INTERRUPT) { uint32_t ival; ival = xfer->interval / DWC_OTG_HOST_TIMER_RATE; if (ival == 0) @@ -3213,7 +3244,7 @@ dwc_otg_setup_standard_chain(struct usb_xfer *xfer) ival = 127; td->tmr_val = sc->sc_tmr_val + ival; td->tmr_res = ival; - } else if (xfer_type == UE_ISOCHRONOUS) { + } else if (td->ep_type == UE_ISOCHRONOUS) { td->tmr_val = 0; td->tmr_res = 1; } else { @@ -3223,13 +3254,12 @@ dwc_otg_setup_standard_chain(struct usb_xfer *xfer) break; case USB_SPEED_HIGH: hcsplt = 0; - if (xfer_type == UE_ISOCHRONOUS || - xfer_type == UE_INTERRUPT) { + if (td->ep_type == UE_INTERRUPT) { + uint32_t ival; +#if 0 hcchar |= ((xfer->max_packet_count & 3) << HCCHAR_MC_SHIFT); - } - if (xfer_type == UE_INTERRUPT) { - uint32_t ival; +#endif ival = xfer->interval / DWC_OTG_HOST_TIMER_RATE; if (ival == 0) ival = 1; @@ -3237,7 +3267,9 @@ dwc_otg_setup_standard_chain(struct usb_xfer *xfer) ival = 127; td->tmr_val = sc->sc_tmr_val + ival; td->tmr_res = ival; - } else if (xfer_type == UE_ISOCHRONOUS) { + } else if (td->ep_type == UE_ISOCHRONOUS) { + hcchar |= ((xfer->max_packet_count & 3) + << HCCHAR_MC_SHIFT); td->tmr_val = 0; td->tmr_res = 1 << usbd_xfer_get_fps_shift(xfer); } else { @@ -3282,24 +3314,58 @@ static void dwc_otg_start_standard_chain(struct usb_xfer *xfer) { struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(xfer->xroot->bus); + struct usb_xfer_root *xroot; + struct dwc_otg_td *td; DPRINTFN(9, "\n"); - /* poll one time - will turn on interrupts */ - if (dwc_otg_xfer_do_fifo(xfer)) { + /* + * 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); - /* put transfer on interrupt queue */ - usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); + 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; + } - /* start timeout, if any */ - if (xfer->timeout != 0) { - usbd_transfer_timeout_ms(xfer, - &dwc_otg_timeout, xfer->timeout); - } + /* put transfer on interrupt queue */ + usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); - /* enable SOF interrupt, if any */ - dwc_otg_enable_sof_irq(sc); + /* 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); + + td = xfer->td_transfer_cache; + if (td->ep_type != UE_BULK) + goto done; + + xroot = xfer->xroot; + + /* + * Optimise the ping-pong effect by waking up other BULK + * transfers belonging to the same device group: + */ + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + td = xfer->td_transfer_cache; + if (td == NULL || td->ep_type != UE_BULK || xfer->xroot != xroot) + continue; + /* reset NAK counter */ + td->did_nak = 0; + } +done: + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); } static void @@ -3436,21 +3502,26 @@ done: 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) { - DPRINTFN(15, "disabled interrupts!\n"); + /* Interrupts are cleared by the interrupt handler */ } else { struct dwc_otg_td *td; - td = xfer->td_transfer_first; - - if (td != NULL) - dwc_otg_host_channel_free(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 @@ -3478,6 +3549,8 @@ dwc_otg_set_stall(struct usb_device *udev, sc = DWC_OTG_BUS2SC(udev->bus); + USB_BUS_SPIN_LOCK(&sc->sc_bus); + /* get endpoint address */ ep_no = ep->edesc->bEndpointAddress; @@ -3506,13 +3579,15 @@ dwc_otg_set_stall(struct usb_device *udev, /* dump data */ dwc_otg_common_rx_ack(sc); /* poll interrupt */ - dwc_otg_interrupt_poll(sc); + 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(struct dwc_otg_softc *sc, uint32_t mps, +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; @@ -3570,7 +3645,8 @@ dwc_otg_clear_stall_sub(struct dwc_otg_softc *sc, uint32_t mps, } /* poll interrupt */ - dwc_otg_interrupt_poll(sc); + dwc_otg_interrupt_poll_locked(sc); + dwc_otg_interrupt_complete_locked(sc); } static void @@ -3591,15 +3667,19 @@ dwc_otg_clear_stall(struct usb_device *udev, struct usb_endpoint *ep) /* 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(sc, + 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 @@ -3755,8 +3835,10 @@ dwc_otg_init(struct dwc_otg_softc *sc) sc->sc_host_ch_max); /* setup FIFO */ - if (dwc_otg_init_fifo(sc, sc->sc_mode)) + 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; @@ -3867,7 +3949,15 @@ dwc_otg_do_poll(struct usb_bus *bus) struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(bus); USB_BUS_LOCK(&sc->sc_bus); - dwc_otg_interrupt_poll(sc); + USB_BUS_SPIN_LOCK(&sc->sc_bus); + dwc_otg_interrupt_poll_locked(sc); + dwc_otg_interrupt_complete_locked(sc); + if (sc->sc_flags.status_device_mode == 0) { + /* update host transfer schedule, so that new transfers can be issued */ + if (dwc_otg_update_host_transfer_schedule_locked(sc)) + dwc_otg_interrupt_poll_locked(sc); + } + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); USB_BUS_UNLOCK(&sc->sc_bus); } @@ -4445,7 +4535,7 @@ tr_handle_set_port_feature: usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 16); /* reset FIFOs */ - dwc_otg_init_fifo(sc, DWC_MODE_HOST); + (void) dwc_otg_init_fifo(sc, DWC_MODE_HOST); sc->sc_flags.change_reset = 1; } else { @@ -4555,6 +4645,7 @@ dwc_otg_xfer_setup(struct usb_setup_params *parm) uint32_t ntd; uint32_t n; uint8_t ep_no; + uint8_t ep_type; xfer = parm->curr_xfer; @@ -4572,7 +4663,9 @@ dwc_otg_xfer_setup(struct usb_setup_params *parm) /* * compute maximum number of TDs */ - if ((xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) == UE_CONTROL) { + 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 */; @@ -4620,10 +4713,21 @@ dwc_otg_xfer_setup(struct usb_setup_params *parm) 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; td->ep_no = ep_no; + td->ep_type = ep_type; td->obj_next = last_obj; last_obj = td; @@ -4659,6 +4763,14 @@ dwc_otg_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, /* not supported */ return; } + } else { + if (udev->speed == USB_SPEED_HIGH) { + if ((UGETW(edesc->wMaxPacketSize) >> 11) & 3) { + /* high bandwidth endpoint - not tested */ + DPRINTF("High Bandwidth Endpoint - not tested\n"); + return; + } + } } if ((edesc->bmAttributes & UE_XFERTYPE) == UE_ISOCHRONOUS) ep->methods = &dwc_otg_device_isoc_methods; @@ -4697,29 +4809,8 @@ dwc_otg_get_dma_delay(struct usb_device *udev, uint32_t *pus) static void dwc_otg_device_resume(struct usb_device *udev) { - struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(udev->bus); - struct usb_xfer *xfer; - struct dwc_otg_td *td; - DPRINTF("\n"); - /* Enable relevant Host channels before resuming */ - - USB_BUS_LOCK(udev->bus); - - TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { - - if (xfer->xroot->udev == udev) { - - td = xfer->td_transfer_cache; - if (td != NULL && - td->channel < DWC_OTG_MAX_CHANNELS) - sc->sc_chan_state[td->channel].suspended = 0; - } - } - - USB_BUS_UNLOCK(udev->bus); - /* poll all transfers again to restart resumed ones */ dwc_otg_do_poll(udev->bus); } @@ -4727,28 +4818,7 @@ dwc_otg_device_resume(struct usb_device *udev) static void dwc_otg_device_suspend(struct usb_device *udev) { - struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(udev->bus); - struct usb_xfer *xfer; - struct dwc_otg_td *td; - DPRINTF("\n"); - - /* Disable relevant Host channels before going to suspend */ - - USB_BUS_LOCK(udev->bus); - - TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { - - if (xfer->xroot->udev == udev) { - - td = xfer->td_transfer_cache; - if (td != NULL && - td->channel < DWC_OTG_MAX_CHANNELS) - sc->sc_chan_state[td->channel].suspended = 1; - } - } - - USB_BUS_UNLOCK(udev->bus); } static const struct usb_bus_methods dwc_otg_bus_methods = diff --git a/sys/dev/usb/controller/dwc_otg.h b/sys/dev/usb/controller/dwc_otg.h index 2ae2eaeb60141..1fa1bbf670511 100644 --- a/sys/dev/usb/controller/dwc_otg.h +++ b/sys/dev/usb/controller/dwc_otg.h @@ -34,6 +34,10 @@ #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 +#define DWC_OTG_NAK_MAX 8 /* 1 ms */ #define DWC_OTG_READ_4(sc, reg) \ bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg) @@ -44,7 +48,7 @@ struct dwc_otg_td; struct dwc_otg_softc; -typedef uint8_t (dwc_otg_cmd_t)(struct dwc_otg_td *td); +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; @@ -61,7 +65,9 @@ struct dwc_otg_td { uint8_t errcnt; uint8_t tmr_res; uint8_t tmr_val; + uint8_t did_nak; /* NAK counter */ uint8_t ep_no; + uint8_t ep_type; uint8_t channel; uint8_t tt_index; /* TT data */ uint8_t tt_start_slot; /* TT data */ @@ -75,7 +81,6 @@ struct dwc_otg_td { #define DWC_CHAN_ST_WAIT_C_PKT 4 #define DWC_CHAN_ST_TX_PKT_ISOC 5 #define DWC_CHAN_ST_TX_WAIT_ISOC 6 - uint8_t error:1; uint8_t error_any:1; uint8_t error_stall:1; uint8_t alt_next:1; @@ -84,10 +89,13 @@ struct dwc_otg_td { uint8_t toggle:1; uint8_t set_toggle:1; uint8_t got_short:1; - uint8_t did_nak:1; uint8_t tt_scheduled: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; @@ -105,7 +113,6 @@ struct dwc_otg_std_temp { uint8_t setup_alt_next; uint8_t did_stall; uint8_t bulk_or_control; - uint8_t tt_index; }; struct dwc_otg_config_desc { @@ -145,23 +152,18 @@ struct dwc_otg_profile { }; struct dwc_otg_chan_state { + uint16_t allocated; + uint16_t wait_sof; uint32_t hcint; - uint32_t tx_size; - uint8_t wait_sof; - uint8_t allocated; - uint8_t suspended; -}; - -struct dwc_otg_tt_info { - uint16_t bytes_used; - uint8_t slot_index; - uint8_t dummy; + uint16_t tx_p_size; /* periodic */ + uint16_t tx_np_size; /* non-periodic */ }; 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]; @@ -177,7 +179,8 @@ struct dwc_otg_softc { uint32_t sc_fifo_size; uint32_t sc_tx_max_size; - uint32_t sc_tx_cur_size; + uint32_t sc_tx_cur_p_level; /* periodic */ + uint32_t sc_tx_cur_np_level; /* non-periodic */ uint32_t sc_irq_mask; uint32_t sc_last_rx_status; uint32_t sc_out_ctl[DWC_OTG_MAX_ENDPOINTS]; @@ -185,8 +188,8 @@ struct dwc_otg_softc { 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; - struct dwc_otg_tt_info sc_tt_info[DWC_OTG_MAX_DEVICES]; uint16_t sc_active_rx_ep; uint16_t sc_last_frame_num; @@ -194,6 +197,7 @@ struct dwc_otg_softc { 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 */ @@ -208,7 +212,8 @@ struct dwc_otg_softc { /* prototypes */ -void dwc_otg_interrupt(struct dwc_otg_softc *); +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 *); diff --git a/sys/dev/usb/controller/dwc_otg_atmelarm.c b/sys/dev/usb/controller/dwc_otg_atmelarm.c deleted file mode 100644 index 9dc072d82a248..0000000000000 --- a/sys/dev/usb/controller/dwc_otg_atmelarm.c +++ /dev/null @@ -1,200 +0,0 @@ -/*- - * 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. - */ - -#include <sys/cdefs.h> -__FBSDID("$FreeBSD$"); - -#include <sys/stdint.h> -#include <sys/stddef.h> -#include <sys/param.h> -#include <sys/queue.h> -#include <sys/types.h> -#include <sys/systm.h> -#include <sys/kernel.h> -#include <sys/bus.h> -#include <sys/module.h> -#include <sys/lock.h> -#include <sys/mutex.h> -#include <sys/condvar.h> -#include <sys/sysctl.h> -#include <sys/sx.h> -#include <sys/unistd.h> -#include <sys/callout.h> -#include <sys/malloc.h> -#include <sys/priv.h> -#include <sys/rman.h> - -#include <dev/usb/usb.h> -#include <dev/usb/usbdi.h> - -#include <dev/usb/usb_core.h> -#include <dev/usb/usb_busdma.h> -#include <dev/usb/usb_process.h> -#include <dev/usb/usb_util.h> - -#include <dev/usb/usb_controller.h> -#include <dev/usb/usb_bus.h> - -#include <dev/usb/controller/dwc_otg.h> - -static device_probe_t dwc_otg_probe; -static device_attach_t dwc_otg_attach; -static device_detach_t dwc_otg_detach; - -struct dwc_otg_super_softc { - struct dwc_otg_softc sc_otg; /* must be first */ -}; - -static int -dwc_otg_probe(device_t dev) -{ - device_set_desc(dev, "DWC OTG 2.0 integrated USB controller"); - return (0); -} - -static int -dwc_otg_attach(device_t dev) -{ - struct dwc_otg_super_softc *sc = device_get_softc(dev); - int err; - int rid; - - /* initialise some bus fields */ - sc->sc_otg.sc_bus.parent = dev; - sc->sc_otg.sc_bus.devices = sc->sc_otg.sc_devices; - sc->sc_otg.sc_bus.devices_max = DWC_OTG_MAX_DEVICES; - - /* get all DMA memory */ - if (usb_bus_mem_alloc_all(&sc->sc_otg.sc_bus, - USB_GET_DMA_TAG(dev), NULL)) { - return (ENOMEM); - } - rid = 0; - sc->sc_otg.sc_io_res = - bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); - - if (!(sc->sc_otg.sc_io_res)) { - err = ENOMEM; - goto error; - } - sc->sc_otg.sc_io_tag = rman_get_bustag(sc->sc_otg.sc_io_res); - sc->sc_otg.sc_io_hdl = rman_get_bushandle(sc->sc_otg.sc_io_res); - sc->sc_otg.sc_io_size = rman_get_size(sc->sc_otg.sc_io_res); - - rid = 0; - sc->sc_otg.sc_irq_res = - bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); - if (sc->sc_otg.sc_irq_res == NULL) - goto error; - - sc->sc_otg.sc_bus.bdev = device_add_child(dev, "usbus", -1); - if (sc->sc_otg.sc_bus.bdev == NULL) - goto error; - - device_set_ivars(sc->sc_otg.sc_bus.bdev, &sc->sc_otg.sc_bus); - - err = bus_setup_intr(dev, sc->sc_otg.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, - NULL, (driver_intr_t *)dwc_otg_interrupt, sc, &sc->sc_otg.sc_intr_hdl); - if (err) { - sc->sc_otg.sc_intr_hdl = NULL; - goto error; - } - err = dwc_otg_init(&sc->sc_otg); - if (err == 0) { - err = device_probe_and_attach(sc->sc_otg.sc_bus.bdev); - } - if (err) - goto error; - return (0); - -error: - dwc_otg_detach(dev); - return (ENXIO); -} - -static int -dwc_otg_detach(device_t dev) -{ - struct dwc_otg_super_softc *sc = device_get_softc(dev); - device_t bdev; - int err; - - if (sc->sc_otg.sc_bus.bdev) { - bdev = sc->sc_otg.sc_bus.bdev; - device_detach(bdev); - device_delete_child(dev, bdev); - } - /* during module unload there are lots of children leftover */ - device_delete_children(dev); - - if (sc->sc_otg.sc_irq_res && sc->sc_otg.sc_intr_hdl) { - /* - * only call dwc_otg_uninit() after dwc_otg_init() - */ - dwc_otg_uninit(&sc->sc_otg); - - err = bus_teardown_intr(dev, sc->sc_otg.sc_irq_res, - sc->sc_otg.sc_intr_hdl); - sc->sc_otg.sc_intr_hdl = NULL; - } - /* free IRQ channel, if any */ - if (sc->sc_otg.sc_irq_res) { - bus_release_resource(dev, SYS_RES_IRQ, 0, - sc->sc_otg.sc_irq_res); - sc->sc_otg.sc_irq_res = NULL; - } - /* free memory resource, if any */ - if (sc->sc_otg.sc_io_res) { - bus_release_resource(dev, SYS_RES_MEMORY, 0, - sc->sc_otg.sc_io_res); - sc->sc_otg.sc_io_res = NULL; - } - usb_bus_mem_free_all(&sc->sc_otg.sc_bus, NULL); - - return (0); -} - -static device_method_t dwc_otg_methods[] = { - /* Device interface */ - DEVMETHOD(device_probe, dwc_otg_probe), - DEVMETHOD(device_attach, dwc_otg_attach), - DEVMETHOD(device_detach, dwc_otg_detach), - DEVMETHOD(device_suspend, bus_generic_suspend), - DEVMETHOD(device_resume, bus_generic_resume), - DEVMETHOD(device_shutdown, bus_generic_shutdown), - - DEVMETHOD_END -}; - -static driver_t dwc_otg_driver = { - .name = "dwc_otg", - .methods = dwc_otg_methods, - .size = sizeof(struct dwc_otg_super_softc), -}; - -static devclass_t dwc_otg_devclass; - -DRIVER_MODULE(dwcotg, atmelarm, dwc_otg_driver, dwc_otg_devclass, 0, 0); -MODULE_DEPEND(dwcotg, usb, 1, 1, 1); diff --git a/sys/dev/usb/controller/dwc_otg_fdt.c b/sys/dev/usb/controller/dwc_otg_fdt.c index 78bb801edf5ba..938ef35546aaa 100644 --- a/sys/dev/usb/controller/dwc_otg_fdt.c +++ b/sys/dev/usb/controller/dwc_otg_fdt.c @@ -146,8 +146,8 @@ dwc_otg_attach(device_t dev) device_set_ivars(sc->sc_otg.sc_bus.bdev, &sc->sc_otg.sc_bus); - err = bus_setup_intr(dev, sc->sc_otg.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, - NULL, (driver_intr_t *)dwc_otg_interrupt, sc, &sc->sc_otg.sc_intr_hdl); + err = bus_setup_intr(dev, sc->sc_otg.sc_irq_res, INTR_TYPE_TTY | INTR_MPSAFE, + &dwc_otg_filter_interrupt, &dwc_otg_interrupt, sc, &sc->sc_otg.sc_intr_hdl); if (err) { sc->sc_otg.sc_intr_hdl = NULL; goto error; diff --git a/sys/dev/usb/controller/dwc_otgreg.h b/sys/dev/usb/controller/dwc_otgreg.h index cd2f45de32a60..344d36a28534f 100644 --- a/sys/dev/usb/controller/dwc_otgreg.h +++ b/sys/dev/usb/controller/dwc_otgreg.h @@ -540,7 +540,7 @@ #define HCSPLT_XACTPOS_LAST 1 #define HCSPLT_XACTPOS_BEGIN 2 #define HCSPLT_XACTPOS_ALL 3 -#define HCSPLT_XACTLEN_MAX 188 /* bytes */ +#define HCSPLT_XACTLEN_BURST 1023 /* bytes */ #define HCSPLT_HUBADDR_SHIFT 7 #define HCSPLT_HUBADDR_MASK 0x00003f80 #define HCSPLT_PRTADDR_SHIFT 0 @@ -555,6 +555,9 @@ 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) diff --git a/sys/dev/usb/controller/ehci.c b/sys/dev/usb/controller/ehci.c index ad3a3fc55d2b1..7d0d5ab6d2099 100644 --- a/sys/dev/usb/controller/ehci.c +++ b/sys/dev/usb/controller/ehci.c @@ -98,19 +98,14 @@ static int ehciiaadbug = 0; static int ehcilostintrbug = 0; static SYSCTL_NODE(_hw_usb, OID_AUTO, ehci, CTLFLAG_RW, 0, "USB ehci"); -SYSCTL_INT(_hw_usb_ehci, OID_AUTO, debug, CTLFLAG_RW | CTLFLAG_TUN, +SYSCTL_INT(_hw_usb_ehci, OID_AUTO, debug, CTLFLAG_RWTUN, &ehcidebug, 0, "Debug level"); -TUNABLE_INT("hw.usb.ehci.debug", &ehcidebug); -SYSCTL_INT(_hw_usb_ehci, OID_AUTO, no_hs, CTLFLAG_RW | CTLFLAG_TUN, +SYSCTL_INT(_hw_usb_ehci, OID_AUTO, no_hs, CTLFLAG_RWTUN, &ehcinohighspeed, 0, "Disable High Speed USB"); -TUNABLE_INT("hw.usb.ehci.no_hs", &ehcinohighspeed); -SYSCTL_INT(_hw_usb_ehci, OID_AUTO, iaadbug, CTLFLAG_RW | CTLFLAG_TUN, +SYSCTL_INT(_hw_usb_ehci, OID_AUTO, iaadbug, CTLFLAG_RWTUN, &ehciiaadbug, 0, "Enable doorbell bug workaround"); -TUNABLE_INT("hw.usb.ehci.iaadbug", &ehciiaadbug); -SYSCTL_INT(_hw_usb_ehci, OID_AUTO, lostintrbug, CTLFLAG_RW | CTLFLAG_TUN, +SYSCTL_INT(_hw_usb_ehci, OID_AUTO, lostintrbug, CTLFLAG_RWTUN, &ehcilostintrbug, 0, "Enable lost interrupt bug workaround"); -TUNABLE_INT("hw.usb.ehci.lostintrbug", &ehcilostintrbug); - static void ehci_dump_regs(ehci_softc_t *sc); static void ehci_dump_sqh(ehci_softc_t *sc, ehci_qh_t *sqh); diff --git a/sys/dev/usb/controller/musb_otg.c b/sys/dev/usb/controller/musb_otg.c index 39ff96844be7d..89de3d582b6c6 100644 --- a/sys/dev/usb/controller/musb_otg.c +++ b/sys/dev/usb/controller/musb_otg.c @@ -131,7 +131,7 @@ static void musbotg_do_poll(struct usb_bus *); static void musbotg_standard_done(struct usb_xfer *); static void musbotg_interrupt_poll(struct musbotg_softc *); static void musbotg_root_intr(struct musbotg_softc *); -static int musbotg_channel_alloc(struct musbotg_softc *, struct musbotg_td *td); +static int musbotg_channel_alloc(struct musbotg_softc *, struct musbotg_td *td, uint8_t); static void musbotg_channel_free(struct musbotg_softc *, struct musbotg_td *td); static void musbotg_ep_int_set(struct musbotg_softc *sc, int channel, int on); @@ -149,7 +149,7 @@ static const struct usb_hw_ep_profile musbotg_ep_profile[1] = { }; static int -musbotg_channel_alloc(struct musbotg_softc *sc, struct musbotg_td *td) +musbotg_channel_alloc(struct musbotg_softc *sc, struct musbotg_td *td, uint8_t is_tx) { int ch; int ep; @@ -173,12 +173,23 @@ musbotg_channel_alloc(struct musbotg_softc *sc, struct musbotg_td *td) return (0); } - for (ch = 1; ch < MUSB2_EP_MAX; ch++) { - if (!(sc->sc_channel_mask & (1 << ch))) { - sc->sc_channel_mask |= (1 << ch); - musbotg_ep_int_set(sc, ch, 1); - return (ch); + for (ch = sc->sc_ep_max; ch != 0; ch--) { + if (sc->sc_channel_mask & (1 << ch)) + continue; + + /* check FIFO size requirement */ + if (is_tx) { + if (td->max_frame_size > + sc->sc_hw_ep_profile[ch].max_in_frame_size) + continue; + } else { + if (td->max_frame_size > + sc->sc_hw_ep_profile[ch].max_out_frame_size) + continue; } + sc->sc_channel_mask |= (1 << ch); + musbotg_ep_int_set(sc, ch, 1); + return (ch); } DPRINTFN(-1, "No available channels. Mask: %04x\n", sc->sc_channel_mask); @@ -377,7 +388,7 @@ musbotg_dev_ctrl_setup_rx(struct musbotg_td *td) sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) - td->channel = musbotg_channel_alloc(sc, td); + td->channel = musbotg_channel_alloc(sc, td, 0); /* EP0 is busy, wait */ if (td->channel == -1) @@ -498,7 +509,7 @@ musbotg_host_ctrl_setup_tx(struct musbotg_td *td) sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) - td->channel = musbotg_channel_alloc(sc, td); + td->channel = musbotg_channel_alloc(sc, td, 1); /* EP0 is busy, wait */ if (td->channel == -1) @@ -870,7 +881,7 @@ musbotg_host_ctrl_data_rx(struct musbotg_td *td) sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) - td->channel = musbotg_channel_alloc(sc, td); + td->channel = musbotg_channel_alloc(sc, td, 0); /* EP0 is busy, wait */ if (td->channel == -1) @@ -1049,7 +1060,7 @@ musbotg_host_ctrl_data_tx(struct musbotg_td *td) sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) - td->channel = musbotg_channel_alloc(sc, td); + td->channel = musbotg_channel_alloc(sc, td, 1); /* No free EPs */ if (td->channel == -1) @@ -1259,7 +1270,7 @@ musbotg_host_ctrl_status_rx(struct musbotg_td *td) sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) - td->channel = musbotg_channel_alloc(sc, td); + td->channel = musbotg_channel_alloc(sc, td, 0); /* EP0 is busy, wait */ if (td->channel == -1) @@ -1346,7 +1357,7 @@ musbotg_host_ctrl_status_tx(struct musbotg_td *td) sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) - td->channel = musbotg_channel_alloc(sc, td); + td->channel = musbotg_channel_alloc(sc, td, 1); /* EP0 is busy, wait */ if (td->channel == -1) @@ -1419,7 +1430,7 @@ musbotg_dev_data_rx(struct musbotg_td *td) sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) - td->channel = musbotg_channel_alloc(sc, td); + td->channel = musbotg_channel_alloc(sc, td, 0); /* EP0 is busy, wait */ if (td->channel == -1) @@ -1567,7 +1578,7 @@ musbotg_dev_data_tx(struct musbotg_td *td) sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) - td->channel = musbotg_channel_alloc(sc, td); + td->channel = musbotg_channel_alloc(sc, td, 1); /* EP0 is busy, wait */ if (td->channel == -1) @@ -1695,7 +1706,7 @@ musbotg_host_data_rx(struct musbotg_td *td) sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) - td->channel = musbotg_channel_alloc(sc, td); + td->channel = musbotg_channel_alloc(sc, td, 0); /* No free EPs */ if (td->channel == -1) @@ -1917,7 +1928,7 @@ musbotg_host_data_tx(struct musbotg_td *td) sc = MUSBOTG_PC2SC(td->pc); if (td->channel == -1) - td->channel = musbotg_channel_alloc(sc, td); + td->channel = musbotg_channel_alloc(sc, td, 1); /* No free EPs */ if (td->channel == -1) @@ -2403,7 +2414,8 @@ musbotg_setup_standard_chain(struct usb_xfer *xfer) temp.td = NULL; temp.td_next = xfer->td_start[0]; temp.offset = 0; - temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.setup_alt_next = xfer->flags_int.short_frames_ok || + xfer->flags_int.isochronous_xfr; temp.did_stall = !xfer->flags_int.control_stall; temp.channel = -1; temp.dev_addr = dev_addr; @@ -2714,7 +2726,8 @@ musbotg_standard_done_sub(struct usb_xfer *xfer) } /* Check for short transfer */ if (len > 0) { - if (xfer->flags_int.short_frames_ok) { + if (xfer->flags_int.short_frames_ok || + xfer->flags_int.isochronous_xfr) { /* follow alt next */ if (td->alt_next) { td = td->obj_next; @@ -3224,6 +3237,7 @@ musbotg_init(struct musbotg_softc *sc) pf->support_out = 1; } else if (frx && (temp <= nrx)) { pf->max_out_frame_size = 1 << frx; + pf->max_in_frame_size = 0; pf->is_simplex = 1; /* simplex */ pf->support_multi_buffer = 1; pf->support_bulk = 1; @@ -3232,6 +3246,7 @@ musbotg_init(struct musbotg_softc *sc) pf->support_out = 1; } else if (ftx && (temp <= ntx)) { pf->max_in_frame_size = 1 << ftx; + pf->max_out_frame_size = 0; pf->is_simplex = 1; /* simplex */ pf->support_multi_buffer = 1; pf->support_bulk = 1; @@ -3285,18 +3300,6 @@ musbotg_uninit(struct musbotg_softc *sc) } static void -musbotg_suspend(struct musbotg_softc *sc) -{ - /* TODO */ -} - -static void -musbotg_resume(struct musbotg_softc *sc) -{ - /* TODO */ -} - -static void musbotg_do_poll(struct usb_bus *bus) { struct musbotg_softc *sc = MUSBOTG_BUS2SC(bus); @@ -4212,13 +4215,13 @@ musbotg_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) switch (state) { case USB_HW_POWER_SUSPEND: - musbotg_suspend(sc); + musbotg_uninit(sc); break; case USB_HW_POWER_SHUTDOWN: musbotg_uninit(sc); break; case USB_HW_POWER_RESUME: - musbotg_resume(sc); + musbotg_init(sc); break; default: break; diff --git a/sys/dev/usb/controller/musb_otg.h b/sys/dev/usb/controller/musb_otg.h index f8f3470dd9dab..7fe19a7ce6541 100644 --- a/sys/dev/usb/controller/musb_otg.h +++ b/sys/dev/usb/controller/musb_otg.h @@ -388,7 +388,7 @@ struct musbotg_flags { struct musbotg_softc { struct usb_bus sc_bus; union musbotg_hub_temp sc_hub_temp; - struct usb_hw_ep_profile sc_hw_ep_profile[16]; + struct usb_hw_ep_profile sc_hw_ep_profile[MUSB2_EP_MAX]; struct usb_device *sc_devices[MUSB2_MAX_DEVICES]; struct resource *sc_io_res; diff --git a/sys/dev/usb/controller/ohci.c b/sys/dev/usb/controller/ohci.c index 86fca1eeaa3b6..206c75314d0c5 100644 --- a/sys/dev/usb/controller/ohci.c +++ b/sys/dev/usb/controller/ohci.c @@ -84,9 +84,8 @@ static int ohcidebug = 0; static SYSCTL_NODE(_hw_usb, OID_AUTO, ohci, CTLFLAG_RW, 0, "USB ohci"); -SYSCTL_INT(_hw_usb_ohci, OID_AUTO, debug, CTLFLAG_RW | CTLFLAG_TUN, +SYSCTL_INT(_hw_usb_ohci, OID_AUTO, debug, CTLFLAG_RWTUN, &ohcidebug, 0, "ohci debug level"); -TUNABLE_INT("hw.usb.ohci.debug", &ohcidebug); static void ohci_dumpregs(ohci_softc_t *); static void ohci_dump_tds(ohci_td_t *); diff --git a/sys/dev/usb/controller/saf1761_otg.c b/sys/dev/usb/controller/saf1761_otg.c new file mode 100644 index 0000000000000..0f5b4741d7531 --- /dev/null +++ b/sys/dev/usb/controller/saf1761_otg.c @@ -0,0 +1,3633 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2014 Hans Petter Selasky <hselasky@FreeBSD.org> + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * 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 SAF1761 series USB OTG + * controller. + * + * Datasheet is available from: + * http://www.nxp.com/products/automotive/multimedia/usb/SAF1761BE.html + */ + +#ifdef USB_GLOBAL_INCLUDE_FILE +#include USB_GLOBAL_INCLUDE_FILE +#else +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> + +#define USB_DEBUG_VAR saf1761_otg_debug + +#include <dev/usb/usb_core.h> +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_busdma.h> +#include <dev/usb/usb_process.h> +#include <dev/usb/usb_transfer.h> +#include <dev/usb/usb_device.h> +#include <dev/usb/usb_hub.h> +#include <dev/usb/usb_util.h> + +#include <dev/usb/usb_controller.h> +#include <dev/usb/usb_bus.h> +#endif /* USB_GLOBAL_INCLUDE_FILE */ + +#include <dev/usb/controller/saf1761_otg.h> +#include <dev/usb/controller/saf1761_otg_reg.h> + +#define SAF1761_OTG_BUS2SC(bus) \ + ((struct saf1761_otg_softc *)(((uint8_t *)(bus)) - \ + ((uint8_t *)&(((struct saf1761_otg_softc *)0)->sc_bus)))) + +#define SAF1761_OTG_PC2UDEV(pc) \ + (USB_DMATAG_TO_XROOT((pc)->tag_parent)->udev) + +#define SAF1761_DCINTERRUPT_THREAD_IRQ \ + (SOTG_DCINTERRUPT_IEVBUS | SOTG_DCINTERRUPT_IEBRST | \ + SOTG_DCINTERRUPT_IERESM | SOTG_DCINTERRUPT_IESUSP) + +#ifdef USB_DEBUG +static int saf1761_otg_debug = 0; +static int saf1761_otg_forcefs = 0; + +static +SYSCTL_NODE(_hw_usb, OID_AUTO, saf1761_otg, CTLFLAG_RW, 0, + "USB SAF1761 DCI"); + +SYSCTL_INT(_hw_usb_saf1761_otg, OID_AUTO, debug, CTLFLAG_RW, + &saf1761_otg_debug, 0, "SAF1761 DCI debug level"); +SYSCTL_INT(_hw_usb_saf1761_otg, OID_AUTO, forcefs, CTLFLAG_RW, + &saf1761_otg_forcefs, 0, "SAF1761 DCI force FULL speed"); +#endif + +#define SAF1761_OTG_INTR_ENDPT 1 + +/* prototypes */ + +static const struct usb_bus_methods saf1761_otg_bus_methods; +static const struct usb_pipe_methods saf1761_otg_non_isoc_methods; +static const struct usb_pipe_methods saf1761_otg_device_isoc_methods; +static const struct usb_pipe_methods saf1761_otg_host_isoc_methods; + +static saf1761_otg_cmd_t saf1761_host_setup_tx; +static saf1761_otg_cmd_t saf1761_host_bulk_data_rx; +static saf1761_otg_cmd_t saf1761_host_bulk_data_tx; +static saf1761_otg_cmd_t saf1761_host_intr_data_rx; +static saf1761_otg_cmd_t saf1761_host_intr_data_tx; +static saf1761_otg_cmd_t saf1761_host_isoc_data_rx; +static saf1761_otg_cmd_t saf1761_host_isoc_data_tx; +static saf1761_otg_cmd_t saf1761_device_setup_rx; +static saf1761_otg_cmd_t saf1761_device_data_rx; +static saf1761_otg_cmd_t saf1761_device_data_tx; +static saf1761_otg_cmd_t saf1761_device_data_tx_sync; +static void saf1761_otg_device_done(struct usb_xfer *, usb_error_t); +static void saf1761_otg_do_poll(struct usb_bus *); +static void saf1761_otg_standard_done(struct usb_xfer *); +static void saf1761_otg_intr_set(struct usb_xfer *, uint8_t); +static void saf1761_otg_root_intr(struct saf1761_otg_softc *); + +/* + * Here is a list of what the SAF1761 chip can support. The main + * limitation is that the sum of the buffer sizes must be less than + * 8192 bytes. + */ +static const struct usb_hw_ep_profile saf1761_otg_ep_profile[] = { + + [0] = { + .max_in_frame_size = 64, + .max_out_frame_size = 64, + .is_simplex = 0, + .support_control = 1, + }, + [1] = { + .max_in_frame_size = SOTG_HS_MAX_PACKET_SIZE, + .max_out_frame_size = SOTG_HS_MAX_PACKET_SIZE, + .is_simplex = 0, + .support_interrupt = 1, + .support_bulk = 1, + .support_isochronous = 1, + .support_in = 1, + .support_out = 1, + }, +}; + +static void +saf1761_otg_get_hw_ep_profile(struct usb_device *udev, + const struct usb_hw_ep_profile **ppf, uint8_t ep_addr) +{ + if (ep_addr == 0) { + *ppf = saf1761_otg_ep_profile + 0; + } else if (ep_addr < 8) { + *ppf = saf1761_otg_ep_profile + 1; + } else { + *ppf = NULL; + } +} + +static void +saf1761_otg_pull_up(struct saf1761_otg_softc *sc) +{ + /* activate pullup on D+, if possible */ + + if (!sc->sc_flags.d_pulled_up && sc->sc_flags.port_powered) { + DPRINTF("\n"); + + sc->sc_flags.d_pulled_up = 1; + } +} + +static void +saf1761_otg_pull_down(struct saf1761_otg_softc *sc) +{ + /* release pullup on D+, if possible */ + + if (sc->sc_flags.d_pulled_up) { + DPRINTF("\n"); + + sc->sc_flags.d_pulled_up = 0; + } +} + +static void +saf1761_otg_wakeup_peer(struct saf1761_otg_softc *sc) +{ + uint16_t temp; + + if (!(sc->sc_flags.status_suspend)) + return; + + DPRINTFN(5, "\n"); + + temp = SAF1761_READ_LE_4(sc, SOTG_MODE); + SAF1761_WRITE_LE_4(sc, SOTG_MODE, temp | SOTG_MODE_SNDRSU); + SAF1761_WRITE_LE_4(sc, SOTG_MODE, temp & ~SOTG_MODE_SNDRSU); + + /* Wait 8ms for remote wakeup to complete. */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125); +} + +static uint8_t +saf1761_host_channel_alloc(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) +{ + uint32_t x; + + if (td->channel < SOTG_HOST_CHANNEL_MAX) + return (0); + + /* check if device is suspended */ + if (SAF1761_OTG_PC2UDEV(td->pc)->flags.self_suspended != 0) + return (1); /* busy - cannot transfer data */ + + switch (td->ep_type) { + case UE_INTERRUPT: + for (x = 0; x != 32; x++) { + if (sc->sc_host_intr_map & (1 << x)) + continue; + sc->sc_host_intr_map |= (1 << x); + td->channel = 32 + x; + return (0); + } + break; + case UE_ISOCHRONOUS: + for (x = 0; x != 32; x++) { + if (sc->sc_host_isoc_map & (1 << x)) + continue; + sc->sc_host_isoc_map |= (1 << x); + td->channel = x; + return (0); + } + break; + default: + for (x = 0; x != 32; x++) { + if (sc->sc_host_async_map & (1 << x)) + continue; + sc->sc_host_async_map |= (1 << x); + td->channel = 64 + x; + return (0); + } + break; + } + return (1); +} + +static void +saf1761_host_channel_free(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) +{ + uint32_t x; + + if (td->channel >= SOTG_HOST_CHANNEL_MAX) + return; + + switch (td->ep_type) { + case UE_INTERRUPT: + x = td->channel - 32; + td->channel = SOTG_HOST_CHANNEL_MAX; + sc->sc_host_intr_map &= ~(1 << x); + sc->sc_host_intr_suspend_map &= ~(1 << x); + SAF1761_WRITE_LE_4(sc, SOTG_INT_PTD_SKIP_PTD, + (~sc->sc_host_intr_map) | sc->sc_host_intr_suspend_map); + break; + case UE_ISOCHRONOUS: + x = td->channel; + td->channel = SOTG_HOST_CHANNEL_MAX; + sc->sc_host_isoc_map &= ~(1 << x); + sc->sc_host_isoc_suspend_map &= ~(1 << x); + SAF1761_WRITE_LE_4(sc, SOTG_ISO_PTD_SKIP_PTD, + (~sc->sc_host_isoc_map) | sc->sc_host_isoc_suspend_map); + break; + default: + x = td->channel - 64; + td->channel = SOTG_HOST_CHANNEL_MAX; + sc->sc_host_async_map &= ~(1 << x); + sc->sc_host_async_suspend_map &= ~(1 << x); + SAF1761_WRITE_LE_4(sc, SOTG_ATL_PTD_SKIP_PTD, + (~sc->sc_host_async_map) | sc->sc_host_async_suspend_map); + break; + } +} + +static uint32_t +saf1761_peek_host_status_le_4(struct saf1761_otg_softc *sc, uint32_t offset) +{ + uint32_t x = 0; + while (1) { + uint32_t retval; + + SAF1761_WRITE_LE_4(sc, SOTG_MEMORY_REG, offset); + SAF1761_90NS_DELAY(sc); /* read prefetch time is 90ns */ + retval = SAF1761_READ_LE_4(sc, offset); + if (retval != 0) + return (retval); + if (++x == 8) { + DPRINTF("STAUS is zero at offset 0x%x\n", offset); + break; + } + } + return (0); +} + +static void +saf1761_read_host_memory(struct saf1761_otg_softc *sc, + struct saf1761_otg_td *td, uint32_t len) +{ + struct usb_page_search buf_res; + uint32_t offset; + uint32_t count; + + if (len == 0) + return; + + offset = SOTG_DATA_ADDR(td->channel); + SAF1761_WRITE_LE_4(sc, SOTG_MEMORY_REG, offset); + SAF1761_90NS_DELAY(sc); /* read prefetch time is 90ns */ + + /* optimised read first */ + while (len > 0) { + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > len) + buf_res.length = len; + + /* check buffer alignment */ + if (((uintptr_t)buf_res.buffer) & 3) + break; + + count = buf_res.length & ~3; + if (count == 0) + break; + + bus_space_read_region_4((sc)->sc_io_tag, (sc)->sc_io_hdl, + offset, buf_res.buffer, count / 4); + + len -= count; + offset += count; + + /* update remainder and offset */ + td->remainder -= count; + td->offset += count; + } + + if (len > 0) { + /* use bounce buffer */ + bus_space_read_region_4((sc)->sc_io_tag, (sc)->sc_io_hdl, + offset, sc->sc_bounce_buffer, (len + 3) / 4); + usbd_copy_in(td->pc, td->offset, + sc->sc_bounce_buffer, len); + + /* update remainder and offset */ + td->remainder -= len; + td->offset += len; + } +} + +static void +saf1761_write_host_memory(struct saf1761_otg_softc *sc, + struct saf1761_otg_td *td, uint32_t len) +{ + struct usb_page_search buf_res; + uint32_t offset; + uint32_t count; + + if (len == 0) + return; + + offset = SOTG_DATA_ADDR(td->channel); + + /* optimised write first */ + while (len > 0) { + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > len) + buf_res.length = len; + + /* check buffer alignment */ + if (((uintptr_t)buf_res.buffer) & 3) + break; + + count = buf_res.length & ~3; + if (count == 0) + break; + + bus_space_write_region_4((sc)->sc_io_tag, (sc)->sc_io_hdl, + offset, buf_res.buffer, count / 4); + + len -= count; + offset += count; + + /* update remainder and offset */ + td->remainder -= count; + td->offset += count; + } + if (len > 0) { + /* use bounce buffer */ + usbd_copy_out(td->pc, td->offset, sc->sc_bounce_buffer, len); + bus_space_write_region_4((sc)->sc_io_tag, (sc)->sc_io_hdl, + offset, sc->sc_bounce_buffer, (len + 3) / 4); + + /* update remainder and offset */ + td->remainder -= len; + td->offset += len; + } +} + +static uint8_t +saf1761_host_setup_tx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) +{ + uint32_t pdt_addr; + uint32_t status; + uint32_t count; + uint32_t temp; + + if (td->channel < SOTG_HOST_CHANNEL_MAX) { + pdt_addr = SOTG_PTD(td->channel); + + status = saf1761_peek_host_status_le_4(sc, pdt_addr + SOTG_PTD_DW3); + + DPRINTFN(5, "STATUS=0x%08x\n", status); + + if (status & SOTG_PTD_DW3_ACTIVE) { + goto busy; + } else if (status & SOTG_PTD_DW3_HALTED) { + td->error_any = 1; + } + goto complete; + } + if (saf1761_host_channel_alloc(sc, td)) + goto busy; + + count = 8; + + if (count != td->remainder) { + td->error_any = 1; + goto complete; + } + + saf1761_write_host_memory(sc, td, count); + + pdt_addr = SOTG_PTD(td->channel); + + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW7, 0); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW6, 0); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW5, 0); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW4, 0); + + temp = SOTG_PTD_DW3_ACTIVE | (td->toggle << 25) | SOTG_PTD_DW3_CERR_3; + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW3, temp); + + temp = SOTG_HC_MEMORY_ADDR(SOTG_DATA_ADDR(td->channel)) << 8; + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW2, temp); + + temp = td->dw1_value | (2 << 10) /* SETUP PID */ | (td->ep_index >> 1); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW1, temp); + + temp = (td->ep_index << 31) | (1 << 29) /* pkt-multiplier */ | + (td->max_packet_size << 18) /* wMaxPacketSize */ | + (count << 3) /* transfer count */ | + SOTG_PTD_DW0_VALID; + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW0, temp); + + /* activate PTD */ + SAF1761_WRITE_LE_4(sc, SOTG_ATL_PTD_SKIP_PTD, + (~sc->sc_host_async_map) | sc->sc_host_async_suspend_map); + + td->toggle = 1; +busy: + return (1); /* busy */ +complete: + saf1761_host_channel_free(sc, td); + return (0); /* complete */ +} + +static uint8_t +saf1761_host_bulk_data_rx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) +{ + uint32_t pdt_addr; + uint32_t temp; + + if (td->channel < SOTG_HOST_CHANNEL_MAX) { + uint32_t status; + uint32_t count; + uint8_t got_short; + + pdt_addr = SOTG_PTD(td->channel); + + status = saf1761_peek_host_status_le_4(sc, pdt_addr + SOTG_PTD_DW3); + + DPRINTFN(5, "STATUS=0x%08x\n", status); + + if (status & SOTG_PTD_DW3_ACTIVE) { + goto busy; + } else if (status & SOTG_PTD_DW3_HALTED) { + if (!(status & SOTG_PTD_DW3_ERRORS)) + td->error_stall = 1; + td->error_any = 1; + goto complete; + } + count = (status & SOTG_PTD_DW3_XFER_COUNT); + got_short = 0; + + /* verify the packet byte count */ + if (count != td->max_packet_size) { + if (count < td->max_packet_size) { + /* we have a short packet */ + td->short_pkt = 1; + got_short = 1; + } else { + /* invalid USB packet */ + td->error_any = 1; + goto complete; + } + } + td->toggle ^= 1; + + /* verify the packet byte count */ + if (count > td->remainder) { + /* invalid USB packet */ + td->error_any = 1; + goto complete; + } + + saf1761_read_host_memory(sc, td, count); + + /* check if we are complete */ + if ((td->remainder == 0) || got_short) { + if (td->short_pkt) + goto complete; + /* else need to receive a zero length packet */ + } + saf1761_host_channel_free(sc, td); + } + if (saf1761_host_channel_alloc(sc, td)) + goto busy; + + /* set toggle, if any */ + if (td->set_toggle) { + td->set_toggle = 0; + td->toggle = 1; + } + + /* receive one more packet */ + + pdt_addr = SOTG_PTD(td->channel); + + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW7, 0); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW6, 0); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW5, 0); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW4, 0); + + temp = SOTG_PTD_DW3_ACTIVE | (td->toggle << 25) | + SOTG_PTD_DW3_CERR_2; + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW3, temp); + + temp = (SOTG_HC_MEMORY_ADDR(SOTG_DATA_ADDR(td->channel)) << 8); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW2, temp); + + temp = td->dw1_value | (1 << 10) /* IN-PID */ | (td->ep_index >> 1); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW1, temp); + + temp = (td->ep_index << 31) | (1 << 29) /* pkt-multiplier */ | + (td->max_packet_size << 18) /* wMaxPacketSize */ | + (td->max_packet_size << 3) /* transfer count */ | + SOTG_PTD_DW0_VALID; + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW0, temp); + + /* activate PTD */ + SAF1761_WRITE_LE_4(sc, SOTG_ATL_PTD_SKIP_PTD, + (~sc->sc_host_async_map) | sc->sc_host_async_suspend_map); +busy: + return (1); /* busy */ +complete: + saf1761_host_channel_free(sc, td); + return (0); /* complete */ +} + +static uint8_t +saf1761_host_bulk_data_tx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) +{ + uint32_t pdt_addr; + uint32_t temp; + uint32_t count; + + if (td->channel < SOTG_HOST_CHANNEL_MAX) { + uint32_t status; + + pdt_addr = SOTG_PTD(td->channel); + + status = saf1761_peek_host_status_le_4(sc, pdt_addr + SOTG_PTD_DW3); + + DPRINTFN(5, "STATUS=0x%08x\n", status); + + if (status & SOTG_PTD_DW3_ACTIVE) { + goto busy; + } else if (status & SOTG_PTD_DW3_HALTED) { + if (!(status & SOTG_PTD_DW3_ERRORS)) + td->error_stall = 1; + td->error_any = 1; + goto complete; + } + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) + goto complete; + /* else we need to transmit a short packet */ + } + saf1761_host_channel_free(sc, td); + } + if (saf1761_host_channel_alloc(sc, td)) + goto busy; + + count = td->max_packet_size; + if (td->remainder < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = td->remainder; + } + + saf1761_write_host_memory(sc, td, count); + + /* set toggle, if any */ + if (td->set_toggle) { + td->set_toggle = 0; + td->toggle = 1; + } + + /* send one more packet */ + + pdt_addr = SOTG_PTD(td->channel); + + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW7, 0); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW6, 0); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW5, 0); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW4, 0); + + temp = SOTG_PTD_DW3_ACTIVE | (td->toggle << 25) | + SOTG_PTD_DW3_CERR_2; + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW3, temp); + + temp = (SOTG_HC_MEMORY_ADDR(SOTG_DATA_ADDR(td->channel)) << 8); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW2, temp); + + temp = td->dw1_value | (0 << 10) /* OUT-PID */ | (td->ep_index >> 1); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW1, temp); + + temp = (td->ep_index << 31) | (1 << 29) /* pkt-multiplier */ | + (td->max_packet_size << 18) /* wMaxPacketSize */ | + (count << 3) /* transfer count */ | + SOTG_PTD_DW0_VALID; + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW0, temp); + + /* activate PTD */ + SAF1761_WRITE_LE_4(sc, SOTG_ATL_PTD_SKIP_PTD, + (~sc->sc_host_async_map) | sc->sc_host_async_suspend_map); + + td->toggle ^= 1; +busy: + return (1); /* busy */ +complete: + saf1761_host_channel_free(sc, td); + return (0); /* complete */ +} + +static uint8_t +saf1761_host_intr_data_rx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) +{ + uint32_t pdt_addr; + uint32_t temp; + + if (td->channel < SOTG_HOST_CHANNEL_MAX) { + uint32_t status; + uint32_t count; + uint8_t got_short; + + pdt_addr = SOTG_PTD(td->channel); + + status = saf1761_peek_host_status_le_4(sc, pdt_addr + SOTG_PTD_DW3); + + DPRINTFN(5, "STATUS=0x%08x\n", status); + + if (status & SOTG_PTD_DW3_ACTIVE) { + goto busy; + } else if (status & SOTG_PTD_DW3_HALTED) { + if (!(status & SOTG_PTD_DW3_ERRORS)) + td->error_stall = 1; + td->error_any = 1; + goto complete; + } + count = (status & SOTG_PTD_DW3_XFER_COUNT); + got_short = 0; + + /* verify the packet byte count */ + if (count != td->max_packet_size) { + if (count < td->max_packet_size) { + /* we have a short packet */ + td->short_pkt = 1; + got_short = 1; + } else { + /* invalid USB packet */ + td->error_any = 1; + goto complete; + } + } + td->toggle ^= 1; + + /* verify the packet byte count */ + if (count > td->remainder) { + /* invalid USB packet */ + td->error_any = 1; + goto complete; + } + + saf1761_read_host_memory(sc, td, count); + + /* check if we are complete */ + if ((td->remainder == 0) || got_short) { + if (td->short_pkt) + goto complete; + /* else need to receive a zero length packet */ + } + saf1761_host_channel_free(sc, td); + } + if (saf1761_host_channel_alloc(sc, td)) + goto busy; + + /* set toggle, if any */ + if (td->set_toggle) { + td->set_toggle = 0; + td->toggle = 1; + } + + /* receive one more packet */ + + pdt_addr = SOTG_PTD(td->channel); + + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW7, 0); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW6, 0); + + temp = (0xFC << td->uframe) & 0xFF; /* complete split */ + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW5, temp); + + temp = (1U << td->uframe); /* start split */ + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW4, temp); + + temp = SOTG_PTD_DW3_ACTIVE | (td->toggle << 25) | SOTG_PTD_DW3_CERR_3; + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW3, temp); + + temp = (SOTG_HC_MEMORY_ADDR(SOTG_DATA_ADDR(td->channel)) << 8) | + (td->interval & 0xF8); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW2, temp); + + temp = td->dw1_value | (1 << 10) /* IN-PID */ | (td->ep_index >> 1); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW1, temp); + + temp = (td->ep_index << 31) | (1 << 29) /* pkt-multiplier */ | + (td->max_packet_size << 18) /* wMaxPacketSize */ | + (td->max_packet_size << 3) /* transfer count */ | + SOTG_PTD_DW0_VALID; + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW0, temp); + + /* activate PTD */ + SAF1761_WRITE_LE_4(sc, SOTG_INT_PTD_SKIP_PTD, + (~sc->sc_host_intr_map) | sc->sc_host_intr_suspend_map); +busy: + return (1); /* busy */ +complete: + saf1761_host_channel_free(sc, td); + return (0); /* complete */ +} + +static uint8_t +saf1761_host_intr_data_tx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) +{ + uint32_t pdt_addr; + uint32_t temp; + uint32_t count; + + if (td->channel < SOTG_HOST_CHANNEL_MAX) { + uint32_t status; + + pdt_addr = SOTG_PTD(td->channel); + + status = saf1761_peek_host_status_le_4(sc, pdt_addr + SOTG_PTD_DW3); + + DPRINTFN(5, "STATUS=0x%08x\n", status); + + if (status & SOTG_PTD_DW3_ACTIVE) { + goto busy; + } else if (status & SOTG_PTD_DW3_HALTED) { + if (!(status & SOTG_PTD_DW3_ERRORS)) + td->error_stall = 1; + td->error_any = 1; + goto complete; + } + + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) + goto complete; + /* else we need to transmit a short packet */ + } + saf1761_host_channel_free(sc, td); + } + if (saf1761_host_channel_alloc(sc, td)) + goto busy; + + count = td->max_packet_size; + if (td->remainder < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = td->remainder; + } + + saf1761_write_host_memory(sc, td, count); + + /* set toggle, if any */ + if (td->set_toggle) { + td->set_toggle = 0; + td->toggle = 1; + } + + /* send one more packet */ + + pdt_addr = SOTG_PTD(td->channel); + + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW7, 0); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW6, 0); + + temp = (0xFC << td->uframe) & 0xFF; /* complete split */ + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW5, temp); + + temp = (1U << td->uframe); /* start split */ + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW4, temp); + + temp = SOTG_PTD_DW3_ACTIVE | (td->toggle << 25) | SOTG_PTD_DW3_CERR_3; + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW3, temp); + + temp = (SOTG_HC_MEMORY_ADDR(SOTG_DATA_ADDR(td->channel)) << 8) | + (td->interval & 0xF8); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW2, temp); + + temp = td->dw1_value | (0 << 10) /* OUT-PID */ | (td->ep_index >> 1); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW1, temp); + + temp = (td->ep_index << 31) | (1 << 29) /* pkt-multiplier */ | + (td->max_packet_size << 18) /* wMaxPacketSize */ | + (count << 3) /* transfer count */ | + SOTG_PTD_DW0_VALID; + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW0, temp); + + /* activate PTD */ + SAF1761_WRITE_LE_4(sc, SOTG_INT_PTD_SKIP_PTD, + (~sc->sc_host_intr_map) | sc->sc_host_intr_suspend_map); + + td->toggle ^= 1; +busy: + return (1); /* busy */ +complete: + saf1761_host_channel_free(sc, td); + return (0); /* complete */ +} + +static uint8_t +saf1761_host_isoc_data_rx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) +{ + uint32_t pdt_addr; + uint32_t temp; + + if (td->channel < SOTG_HOST_CHANNEL_MAX) { + uint32_t status; + uint32_t count; + + pdt_addr = SOTG_PTD(td->channel); + + status = saf1761_peek_host_status_le_4(sc, pdt_addr + SOTG_PTD_DW3); + + DPRINTFN(5, "STATUS=0x%08x\n", status); + + if (status & SOTG_PTD_DW3_ACTIVE) { + goto busy; + } else if (status & SOTG_PTD_DW3_HALTED) { + goto complete; + } + count = (status & SOTG_PTD_DW3_XFER_COUNT); + + /* verify the packet byte count */ + if (count != td->max_packet_size) { + if (count < td->max_packet_size) { + /* we have a short packet */ + td->short_pkt = 1; + } else { + /* invalid USB packet */ + td->error_any = 1; + goto complete; + } + } + + /* verify the packet byte count */ + if (count > td->remainder) { + /* invalid USB packet */ + td->error_any = 1; + goto complete; + } + + saf1761_read_host_memory(sc, td, count); + goto complete; + } + + if (saf1761_host_channel_alloc(sc, td)) + goto busy; + + /* receive one more packet */ + + pdt_addr = SOTG_PTD(td->channel); + + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW7, 0); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW6, 0); + + temp = (0xFC << td->uframe) & 0xFF; /* complete split */ + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW5, temp); + + temp = (1U << td->uframe); /* start split */ + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW4, temp); + + temp = SOTG_PTD_DW3_ACTIVE | SOTG_PTD_DW3_CERR_3; + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW3, temp); + + temp = (SOTG_HC_MEMORY_ADDR(SOTG_DATA_ADDR(td->channel)) << 8); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW2, temp); + + temp = td->dw1_value | (1 << 10) /* IN-PID */ | (td->ep_index >> 1); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW1, temp); + + temp = (td->ep_index << 31) | (1 << 29) /* pkt-multiplier */ | + (td->max_packet_size << 18) /* wMaxPacketSize */ | + (td->max_packet_size << 3) /* transfer count */ | + SOTG_PTD_DW0_VALID; + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW0, temp); + + /* activate PTD */ + SAF1761_WRITE_LE_4(sc, SOTG_ISO_PTD_SKIP_PTD, + (~sc->sc_host_isoc_map) | sc->sc_host_isoc_suspend_map); +busy: + return (1); /* busy */ +complete: + saf1761_host_channel_free(sc, td); + return (0); /* complete */ +} + +static uint8_t +saf1761_host_isoc_data_tx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) +{ + uint32_t pdt_addr; + uint32_t temp; + uint32_t count; + + if (td->channel < SOTG_HOST_CHANNEL_MAX) { + uint32_t status; + + pdt_addr = SOTG_PTD(td->channel); + + status = saf1761_peek_host_status_le_4(sc, pdt_addr + SOTG_PTD_DW3); + + DPRINTFN(5, "STATUS=0x%08x\n", status); + + if (status & SOTG_PTD_DW3_ACTIVE) { + goto busy; + } else if (status & SOTG_PTD_DW3_HALTED) { + goto complete; + } + + goto complete; + } + if (saf1761_host_channel_alloc(sc, td)) + goto busy; + + count = td->max_packet_size; + if (td->remainder < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = td->remainder; + } + + saf1761_write_host_memory(sc, td, count); + + /* send one more packet */ + + pdt_addr = SOTG_PTD(td->channel); + + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW7, 0); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW6, 0); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW5, 0); + + temp = (1U << td->uframe); /* start split */ + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW4, temp); + + temp = SOTG_PTD_DW3_ACTIVE | SOTG_PTD_DW3_CERR_3; + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW3, temp); + + temp = (SOTG_HC_MEMORY_ADDR(SOTG_DATA_ADDR(td->channel)) << 8); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW2, temp); + + temp = td->dw1_value | (0 << 10) /* OUT-PID */ | (td->ep_index >> 1); + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW1, temp); + + temp = (td->ep_index << 31) | (1 << 29) /* pkt-multiplier */ | + (count << 18) /* wMaxPacketSize */ | + (count << 3) /* transfer count */ | + SOTG_PTD_DW0_VALID; + SAF1761_WRITE_LE_4(sc, pdt_addr + SOTG_PTD_DW0, temp); + + /* activate PTD */ + SAF1761_WRITE_LE_4(sc, SOTG_ISO_PTD_SKIP_PTD, + (~sc->sc_host_isoc_map) | sc->sc_host_isoc_suspend_map); +busy: + return (1); /* busy */ +complete: + saf1761_host_channel_free(sc, td); + return (0); /* complete */ +} + +static void +saf1761_otg_set_address(struct saf1761_otg_softc *sc, uint8_t addr) +{ + DPRINTFN(5, "addr=%d\n", addr); + + SAF1761_WRITE_LE_4(sc, SOTG_ADDRESS, addr | SOTG_ADDRESS_ENABLE); +} + + +static void +saf1761_read_device_fifo(struct saf1761_otg_softc *sc, + struct saf1761_otg_td *td, uint32_t len) +{ + struct usb_page_search buf_res; + uint32_t count; + + /* optimised read first */ + while (len > 0) { + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > len) + buf_res.length = len; + + /* check buffer alignment */ + if (((uintptr_t)buf_res.buffer) & 3) + break; + + count = buf_res.length & ~3; + if (count == 0) + break; + + bus_space_read_multi_4((sc)->sc_io_tag, (sc)->sc_io_hdl, + SOTG_DATA_PORT, buf_res.buffer, count / 4); + + len -= count; + + /* update remainder and offset */ + td->remainder -= count; + td->offset += count; + } + + if (len > 0) { + /* use bounce buffer */ + bus_space_read_multi_4((sc)->sc_io_tag, (sc)->sc_io_hdl, + SOTG_DATA_PORT, sc->sc_bounce_buffer, (len + 3) / 4); + usbd_copy_in(td->pc, td->offset, + sc->sc_bounce_buffer, len); + + /* update remainder and offset */ + td->remainder -= len; + td->offset += len; + } +} + +static void +saf1761_write_device_fifo(struct saf1761_otg_softc *sc, + struct saf1761_otg_td *td, uint32_t len) +{ + struct usb_page_search buf_res; + uint32_t count; + + /* optimised write first */ + while (len > 0) { + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > len) + buf_res.length = len; + + /* check buffer alignment */ + if (((uintptr_t)buf_res.buffer) & 3) + break; + + count = buf_res.length & ~3; + if (count == 0) + break; + + bus_space_write_multi_4((sc)->sc_io_tag, (sc)->sc_io_hdl, + SOTG_DATA_PORT, buf_res.buffer, count / 4); + + len -= count; + + /* update remainder and offset */ + td->remainder -= count; + td->offset += count; + } + if (len > 0) { + /* use bounce buffer */ + usbd_copy_out(td->pc, td->offset, sc->sc_bounce_buffer, len); + bus_space_write_multi_4((sc)->sc_io_tag, (sc)->sc_io_hdl, + SOTG_DATA_PORT, sc->sc_bounce_buffer, (len + 3) / 4); + + /* update remainder and offset */ + td->remainder -= len; + td->offset += len; + } +} + +static uint8_t +saf1761_device_setup_rx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) +{ + struct usb_device_request req; + uint32_t count; + + /* select the correct endpoint */ + SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, SOTG_EP_INDEX_EP0SETUP); + + count = SAF1761_READ_LE_4(sc, SOTG_BUF_LENGTH); + + /* check buffer status */ + if ((count & SOTG_BUF_LENGTH_FILLED_MASK) == 0) + goto busy; + + /* get buffer length */ + count &= SOTG_BUF_LENGTH_BUFLEN_MASK; + + DPRINTFN(5, "count=%u rem=%u\n", count, td->remainder); + + /* clear did stall */ + td->did_stall = 0; + + /* clear stall */ + SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, 0); + + /* verify data length */ + if (count != td->remainder) { + DPRINTFN(0, "Invalid SETUP packet " + "length, %d bytes\n", count); + goto busy; + } + if (count != sizeof(req)) { + DPRINTFN(0, "Unsupported SETUP packet " + "length, %d bytes\n", count); + goto busy; + } + /* receive data */ + saf1761_read_device_fifo(sc, td, sizeof(req)); + + /* extract SETUP packet again */ + usbd_copy_out(td->pc, 0, &req, sizeof(req)); + + /* sneak peek the set address request */ + if ((req.bmRequestType == UT_WRITE_DEVICE) && + (req.bRequest == UR_SET_ADDRESS)) { + sc->sc_dv_addr = req.wValue[0] & 0x7F; + DPRINTF("Set address %d\n", sc->sc_dv_addr); + } else { + sc->sc_dv_addr = 0xFF; + } + return (0); /* complete */ + +busy: + /* abort any ongoing transfer */ + if (!td->did_stall) { + DPRINTFN(5, "stalling\n"); + + /* set stall */ + SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_STALL); + + td->did_stall = 1; + } + return (1); /* not complete */ +} + +static uint8_t +saf1761_device_data_rx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) +{ + uint32_t count; + uint8_t got_short = 0; + + if (td->ep_index == 0) { + /* select the correct endpoint */ + SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, SOTG_EP_INDEX_EP0SETUP); + + count = SAF1761_READ_LE_4(sc, SOTG_BUF_LENGTH); + + /* check buffer status */ + if ((count & SOTG_BUF_LENGTH_FILLED_MASK) != 0) { + + if (td->remainder == 0) { + /* + * We are actually complete and have + * received the next SETUP: + */ + DPRINTFN(5, "faking complete\n"); + return (0); /* complete */ + } + DPRINTFN(5, "SETUP packet while receiving data\n"); + /* + * USB Host Aborted the transfer. + */ + td->error_any = 1; + return (0); /* complete */ + } + } + /* select the correct endpoint */ + SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, + (td->ep_index << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) | + SOTG_EP_INDEX_DIR_OUT); + + /* enable data stage */ + if (td->set_toggle) { + td->set_toggle = 0; + SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_DSEN); + } + + count = SAF1761_READ_LE_4(sc, SOTG_BUF_LENGTH); + + /* check buffer status */ + if ((count & SOTG_BUF_LENGTH_FILLED_MASK) == 0) + return (1); /* not complete */ + + /* get buffer length */ + count &= SOTG_BUF_LENGTH_BUFLEN_MASK; + + DPRINTFN(5, "rem=%u count=0x%04x\n", td->remainder, count); + + /* verify the packet byte count */ + if (count != td->max_packet_size) { + if (count < td->max_packet_size) { + /* we have a short packet */ + td->short_pkt = 1; + got_short = 1; + } else { + /* invalid USB packet */ + td->error_any = 1; + return (0); /* we are complete */ + } + } + /* verify the packet byte count */ + if (count > td->remainder) { + /* invalid USB packet */ + td->error_any = 1; + return (0); /* we are complete */ + } + /* receive data */ + saf1761_read_device_fifo(sc, td, count); + + /* check if we are complete */ + if ((td->remainder == 0) || got_short) { + if (td->short_pkt) { + /* we are complete */ + return (0); + } + /* else need to receive a zero length packet */ + } + return (1); /* not complete */ +} + +static uint8_t +saf1761_device_data_tx(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) +{ + uint32_t count; + + if (td->ep_index == 0) { + /* select the correct endpoint */ + SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, SOTG_EP_INDEX_EP0SETUP); + + count = SAF1761_READ_LE_4(sc, SOTG_BUF_LENGTH); + + /* check buffer status */ + if ((count & SOTG_BUF_LENGTH_FILLED_MASK) != 0) { + DPRINTFN(5, "SETUP abort\n"); + /* + * USB Host Aborted the transfer. + */ + td->error_any = 1; + return (0); /* complete */ + } + } + /* select the correct endpoint */ + SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, + (td->ep_index << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) | + SOTG_EP_INDEX_DIR_IN); + + count = SAF1761_READ_LE_4(sc, SOTG_BUF_LENGTH); + + /* check buffer status */ + if ((count & SOTG_BUF_LENGTH_FILLED_MASK) != 0) + return (1); /* not complete */ + + /* enable data stage */ + if (td->set_toggle) { + td->set_toggle = 0; + SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_DSEN); + } + + DPRINTFN(5, "rem=%u\n", td->remainder); + + count = td->max_packet_size; + if (td->remainder < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = td->remainder; + } + /* transmit data */ + saf1761_write_device_fifo(sc, td, count); + + if (td->ep_index == 0) { + if (count < SOTG_FS_MAX_PACKET_SIZE) { + /* set end of packet */ + SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_VENDP); + } + } else { + if (count < SOTG_HS_MAX_PACKET_SIZE) { + /* set end of packet */ + SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_VENDP); + } + } + + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) { + return (0); /* complete */ + } + /* else we need to transmit a short packet */ + } + return (1); /* not complete */ +} + +static uint8_t +saf1761_device_data_tx_sync(struct saf1761_otg_softc *sc, struct saf1761_otg_td *td) +{ + uint32_t count; + + if (td->ep_index == 0) { + /* select the correct endpoint */ + SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, SOTG_EP_INDEX_EP0SETUP); + + count = SAF1761_READ_LE_4(sc, SOTG_BUF_LENGTH); + + /* check buffer status */ + if ((count & SOTG_BUF_LENGTH_FILLED_MASK) != 0) { + DPRINTFN(5, "Faking complete\n"); + return (0); /* complete */ + } + } + /* select the correct endpoint */ + SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, + (td->ep_index << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) | + SOTG_EP_INDEX_DIR_IN); + + count = SAF1761_READ_LE_4(sc, SOTG_BUF_LENGTH); + + /* check buffer status */ + if ((count & SOTG_BUF_LENGTH_FILLED_MASK) != 0) + return (1); /* busy */ + + if (sc->sc_dv_addr != 0xFF) { + /* write function address */ + saf1761_otg_set_address(sc, sc->sc_dv_addr); + } + return (0); /* complete */ +} + +static void +saf1761_otg_xfer_do_fifo(struct saf1761_otg_softc *sc, struct usb_xfer *xfer) +{ + struct saf1761_otg_td *td; + uint8_t toggle; + + 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. + */ + toggle = td->toggle; + td = td->obj_next; + td->toggle = toggle; + xfer->td_transfer_cache = td; + } + return; + +done: + /* compute all actual lengths */ + xfer->td_transfer_cache = NULL; + sc->sc_xfer_complete = 1; +} + +static uint8_t +saf1761_otg_xfer_do_complete(struct saf1761_otg_softc *sc, struct usb_xfer *xfer) +{ + struct saf1761_otg_td *td; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + if (td == NULL) { + /* compute all actual lengths */ + saf1761_otg_standard_done(xfer); + return (1); + } + return (0); +} + +static void +saf1761_otg_interrupt_poll_locked(struct saf1761_otg_softc *sc) +{ + struct usb_xfer *xfer; + + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) + saf1761_otg_xfer_do_fifo(sc, xfer); +} + +static void +saf1761_otg_wait_suspend(struct saf1761_otg_softc *sc, uint8_t on) +{ + if (on) { + sc->sc_intr_enable |= SOTG_DCINTERRUPT_IESUSP; + sc->sc_intr_enable &= ~SOTG_DCINTERRUPT_IERESM; + } else { + sc->sc_intr_enable &= ~SOTG_DCINTERRUPT_IESUSP; + sc->sc_intr_enable |= SOTG_DCINTERRUPT_IERESM; + } + SAF1761_WRITE_LE_4(sc, SOTG_DCINTERRUPT_EN, sc->sc_intr_enable); +} + +static void +saf1761_otg_update_vbus(struct saf1761_otg_softc *sc) +{ + uint16_t status; + + /* read fresh status */ + status = SAF1761_READ_LE_4(sc, SOTG_STATUS); + + DPRINTFN(4, "STATUS=0x%04x\n", status); + + if ((status & SOTG_STATUS_VBUS_VLD) && + (status & SOTG_STATUS_ID)) { + /* VBUS present and device mode */ + if (!sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 1; + + /* complete root HUB interrupt endpoint */ + saf1761_otg_root_intr(sc); + } + } else { + /* VBUS not-present or host mode */ + if (sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* complete root HUB interrupt endpoint */ + saf1761_otg_root_intr(sc); + } + } +} + +static void +saf1761_otg_interrupt_complete_locked(struct saf1761_otg_softc *sc) +{ + struct usb_xfer *xfer; +repeat: + /* scan for completion events */ + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + if (saf1761_otg_xfer_do_complete(sc, xfer)) + goto repeat; + } +} + +int +saf1761_otg_filter_interrupt(void *arg) +{ + struct saf1761_otg_softc *sc = arg; + int retval = FILTER_HANDLED; + uint32_t hcstat; + uint32_t status; + + USB_BUS_SPIN_LOCK(&sc->sc_bus); + + hcstat = SAF1761_READ_LE_4(sc, SOTG_HCINTERRUPT); + /* acknowledge all host controller interrupts */ + SAF1761_WRITE_LE_4(sc, SOTG_HCINTERRUPT, hcstat); + + status = SAF1761_READ_LE_4(sc, SOTG_DCINTERRUPT); + /* acknowledge all device controller interrupts */ + SAF1761_WRITE_LE_4(sc, SOTG_DCINTERRUPT, + status & ~SAF1761_DCINTERRUPT_THREAD_IRQ); + + (void) SAF1761_READ_LE_4(sc, SOTG_ATL_PTD_DONE_PTD); + (void) SAF1761_READ_LE_4(sc, SOTG_INT_PTD_DONE_PTD); + (void) SAF1761_READ_LE_4(sc, SOTG_ISO_PTD_DONE_PTD); + + if (status & SAF1761_DCINTERRUPT_THREAD_IRQ) + retval = FILTER_SCHEDULE_THREAD; + + /* poll FIFOs, if any */ + saf1761_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 +saf1761_otg_interrupt(void *arg) +{ + struct saf1761_otg_softc *sc = arg; + uint32_t status; + + USB_BUS_LOCK(&sc->sc_bus); + USB_BUS_SPIN_LOCK(&sc->sc_bus); + + status = SAF1761_READ_LE_4(sc, SOTG_DCINTERRUPT) & + SAF1761_DCINTERRUPT_THREAD_IRQ; + + /* acknowledge all device controller interrupts */ + SAF1761_WRITE_LE_4(sc, SOTG_DCINTERRUPT, status); + + DPRINTF("DCINTERRUPT=0x%08x SOF=0x%08x " + "FRINDEX=0x%08x\n", status, + SAF1761_READ_LE_4(sc, SOTG_FRAME_NUM), + SAF1761_READ_LE_4(sc, SOTG_FRINDEX)); + + /* update VBUS and ID bits, if any */ + if (status & SOTG_DCINTERRUPT_IEVBUS) + saf1761_otg_update_vbus(sc); + + if (status & SOTG_DCINTERRUPT_IEBRST) { + /* unlock device */ + SAF1761_WRITE_LE_4(sc, SOTG_UNLOCK_DEVICE, + SOTG_UNLOCK_DEVICE_CODE); + + /* Enable device address */ + SAF1761_WRITE_LE_4(sc, SOTG_ADDRESS, + SOTG_ADDRESS_ENABLE); + + sc->sc_flags.status_bus_reset = 1; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* disable resume interrupt */ + saf1761_otg_wait_suspend(sc, 1); + /* complete root HUB interrupt endpoint */ + saf1761_otg_root_intr(sc); + } + /* + * If "RESUME" and "SUSPEND" is set at the same time we + * interpret that like "RESUME". Resume is set when there is + * at least 3 milliseconds of inactivity on the USB BUS: + */ + if (status & SOTG_DCINTERRUPT_IERESM) { + /* unlock device */ + SAF1761_WRITE_LE_4(sc, SOTG_UNLOCK_DEVICE, + SOTG_UNLOCK_DEVICE_CODE); + + if (sc->sc_flags.status_suspend) { + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 1; + /* disable resume interrupt */ + saf1761_otg_wait_suspend(sc, 1); + /* complete root HUB interrupt endpoint */ + saf1761_otg_root_intr(sc); + } + } else if (status & SOTG_DCINTERRUPT_IESUSP) { + if (!sc->sc_flags.status_suspend) { + sc->sc_flags.status_suspend = 1; + sc->sc_flags.change_suspend = 1; + /* enable resume interrupt */ + saf1761_otg_wait_suspend(sc, 0); + /* complete root HUB interrupt endpoint */ + saf1761_otg_root_intr(sc); + } + } + + if (sc->sc_xfer_complete != 0) { + sc->sc_xfer_complete = 0; + + /* complete FIFOs, if any */ + saf1761_otg_interrupt_complete_locked(sc); + } + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +saf1761_otg_setup_standard_chain_sub(struct saf1761_otg_std_temp *temp) +{ + struct saf1761_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->error_any = 0; + td->error_stall = 0; + td->set_toggle = 0; + td->did_stall = temp->did_stall; + td->short_pkt = temp->short_pkt; + td->alt_next = temp->setup_alt_next; + td->channel = SOTG_HOST_CHANNEL_MAX; +} + +static void +saf1761_otg_setup_standard_chain(struct usb_xfer *xfer) +{ + struct saf1761_otg_std_temp temp; + struct saf1761_otg_softc *sc; + struct saf1761_otg_td *td; + uint32_t x; + uint8_t ep_no; + uint8_t ep_type; + uint8_t need_sync; + uint8_t is_host; + + DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpointno), + 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); + + sc = SAF1761_OTG_BUS2SC(xfer->xroot->bus); + ep_no = (xfer->endpointno & UE_ADDR); + ep_type = (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE); + + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + if (is_host) + temp.func = &saf1761_host_setup_tx; + else + temp.func = &saf1761_device_setup_rx; + + temp.len = xfer->frlengths[0]; + temp.pc = xfer->frbuffers + 0; + temp.short_pkt = temp.len ? 1 : 0; + /* check for last frame */ + if (xfer->nframes == 1) { + /* no STATUS stage yet, SETUP is last */ + if (xfer->flags_int.control_act) + temp.setup_alt_next = 0; + } + saf1761_otg_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + if (x != xfer->nframes) { + if (xfer->endpointno & UE_DIR_IN) { + if (is_host) { + if (ep_type == UE_INTERRUPT) + temp.func = &saf1761_host_intr_data_rx; + else if (ep_type == UE_ISOCHRONOUS) + temp.func = &saf1761_host_isoc_data_rx; + else + temp.func = &saf1761_host_bulk_data_rx; + need_sync = 0; + } else { + temp.func = &saf1761_device_data_tx; + need_sync = 1; + } + } else { + if (is_host) { + if (ep_type == UE_INTERRUPT) + temp.func = &saf1761_host_intr_data_tx; + else if (ep_type == UE_ISOCHRONOUS) + temp.func = &saf1761_host_isoc_data_tx; + else + temp.func = &saf1761_host_bulk_data_tx; + need_sync = 0; + } else { + temp.func = &saf1761_device_data_rx; + need_sync = 0; + } + } + + /* setup "pc" pointer */ + temp.pc = xfer->frbuffers + x; + } else { + need_sync = 0; + } + + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + + x++; + + if (x == xfer->nframes) { + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_act) { + temp.setup_alt_next = 0; + } + } else { + temp.setup_alt_next = 0; + } + } + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.short_pkt = 0; + + } else { + + /* regular data transfer */ + + temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1; + } + + saf1761_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; + } + } + + /* check for control transfer */ + 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 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 = &saf1761_host_bulk_data_tx; + need_sync = 0; + } else { + temp.func = &saf1761_device_data_rx; + need_sync = 0; + } + } else { + if (is_host) { + temp.func = &saf1761_host_bulk_data_rx; + need_sync = 0; + } else { + temp.func = &saf1761_device_data_tx; + need_sync = 1; + } + } + temp.len = 0; + temp.short_pkt = 0; + + saf1761_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 = &saf1761_device_data_tx_sync; + saf1761_otg_setup_standard_chain_sub(&temp); + } + } + } else { + if (need_sync) { + temp.pc = xfer->frbuffers + 0; + temp.len = 0; + temp.short_pkt = 0; + temp.setup_alt_next = 0; + + /* we need a SYNC point after TX */ + temp.func = &saf1761_device_data_tx_sync; + saf1761_otg_setup_standard_chain_sub(&temp); + } + } + + /* must have at least one frame! */ + td = temp.td; + xfer->td_transfer_last = td; + + if (is_host) { + /* get first again */ + td = xfer->td_transfer_first; + td->toggle = (xfer->endpoint->toggle_next ? 1 : 0); + } +} + +static void +saf1761_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 */ + saf1761_otg_device_done(xfer, USB_ERR_TIMEOUT); +} + +static void +saf1761_otg_intr_set(struct usb_xfer *xfer, uint8_t set) +{ + struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(xfer->xroot->bus); + uint8_t ep_no = (xfer->endpointno & UE_ADDR); + uint32_t mask; + + DPRINTFN(15, "endpoint=%d set=%d\n", xfer->endpointno, set); + + if (ep_no == 0) { + mask = SOTG_DCINTERRUPT_IEPRX(0) | + SOTG_DCINTERRUPT_IEPTX(0) | + SOTG_DCINTERRUPT_IEP0SETUP; + } else if (xfer->endpointno & UE_DIR_IN) { + mask = SOTG_DCINTERRUPT_IEPTX(ep_no); + } else { + mask = SOTG_DCINTERRUPT_IEPRX(ep_no); + } + + if (set) + sc->sc_intr_enable |= mask; + else + sc->sc_intr_enable &= ~mask; + + SAF1761_WRITE_LE_4(sc, SOTG_DCINTERRUPT_EN, sc->sc_intr_enable); +} + +static void +saf1761_otg_start_standard_chain(struct usb_xfer *xfer) +{ + struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(xfer->xroot->bus); + + DPRINTFN(9, "\n"); + + USB_BUS_SPIN_LOCK(&sc->sc_bus); + + /* poll one time */ + saf1761_otg_xfer_do_fifo(sc, xfer); + + if (xfer->td_transfer_cache != NULL) { + /* + * Only enable the endpoint interrupt when we are + * actually waiting for data, hence we are dealing + * with level triggered interrupts ! + */ + saf1761_otg_intr_set(xfer, 1); + + /* put transfer on interrupt queue */ + usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usbd_transfer_timeout_ms(xfer, + &saf1761_otg_timeout, xfer->timeout); + } + } else { + /* catch completion, if any */ + saf1761_otg_interrupt_complete_locked(sc); + } + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); +} + +static void +saf1761_otg_root_intr(struct saf1761_otg_softc *sc) +{ + DPRINTFN(9, "\n"); + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* set port bit - we only have one port */ + sc->sc_hub_idata[0] = 0x02; + + uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, + sizeof(sc->sc_hub_idata)); +} + +static usb_error_t +saf1761_otg_standard_done_sub(struct usb_xfer *xfer) +{ + struct saf1761_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 +saf1761_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 = saf1761_otg_standard_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = saf1761_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 = saf1761_otg_standard_done_sub(xfer); + } +done: + saf1761_otg_device_done(xfer, err); +} + +/*------------------------------------------------------------------------* + * saf1761_otg_device_done + * + * NOTE: this function can be called more than one time on the + * same USB transfer! + *------------------------------------------------------------------------*/ +static void +saf1761_otg_device_done(struct usb_xfer *xfer, usb_error_t error) +{ + struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(xfer->xroot->bus); + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", + xfer, xfer->endpoint, error); + + USB_BUS_SPIN_LOCK(&sc->sc_bus); + + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { + saf1761_otg_intr_set(xfer, 0); + } else { + struct saf1761_otg_td *td; + + td = xfer->td_transfer_cache; + + if (td != NULL) + saf1761_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 +saf1761_otg_xfer_stall(struct usb_xfer *xfer) +{ + saf1761_otg_device_done(xfer, USB_ERR_STALLED); +} + +static void +saf1761_otg_set_stall(struct usb_device *udev, + struct usb_endpoint *ep, uint8_t *did_stall) +{ + struct saf1761_otg_softc *sc; + uint8_t ep_no; + uint8_t ep_type; + uint8_t ep_dir; + + USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); + + /* check mode */ + if (udev->flags.usb_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + + DPRINTFN(5, "endpoint=%p\n", ep); + + /* set STALL bit */ + sc = SAF1761_OTG_BUS2SC(udev->bus); + + ep_no = (ep->edesc->bEndpointAddress & UE_ADDR); + ep_dir = (ep->edesc->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)); + ep_type = (ep->edesc->bmAttributes & UE_XFERTYPE); + + if (ep_type == UE_CONTROL) { + /* should not happen */ + return; + } + USB_BUS_SPIN_LOCK(&sc->sc_bus); + + /* select the correct endpoint */ + SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, + (ep_no << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) | + ((ep_dir == UE_DIR_IN) ? SOTG_EP_INDEX_DIR_IN : + SOTG_EP_INDEX_DIR_OUT)); + + /* set stall */ + SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_STALL); + + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); +} + +static void +saf1761_otg_clear_stall_sub_locked(struct saf1761_otg_softc *sc, + uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir) +{ + if (ep_type == UE_CONTROL) { + /* clearing stall is not needed */ + return; + } + /* select the correct endpoint */ + SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, + (ep_no << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) | + ((ep_dir == UE_DIR_IN) ? SOTG_EP_INDEX_DIR_IN : + SOTG_EP_INDEX_DIR_OUT)); + + /* disable endpoint */ + SAF1761_WRITE_LE_4(sc, SOTG_EP_TYPE, 0); + /* enable endpoint again - will clear data toggle */ + SAF1761_WRITE_LE_4(sc, SOTG_EP_TYPE, ep_type | SOTG_EP_TYPE_ENABLE); + + /* clear buffer */ + SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, SOTG_CTRL_FUNC_CLBUF); + /* clear stall */ + SAF1761_WRITE_LE_4(sc, SOTG_CTRL_FUNC, 0); +} + +static void +saf1761_otg_clear_stall(struct usb_device *udev, struct usb_endpoint *ep) +{ + struct saf1761_otg_softc *sc; + struct usb_endpoint_descriptor *ed; + + USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); + + DPRINTFN(5, "endpoint=%p\n", ep); + + /* check mode */ + if (udev->flags.usb_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + /* get softc */ + sc = SAF1761_OTG_BUS2SC(udev->bus); + + USB_BUS_SPIN_LOCK(&sc->sc_bus); + + /* get endpoint descriptor */ + ed = ep->edesc; + + /* reset endpoint */ + saf1761_otg_clear_stall_sub_locked(sc, + (ed->bEndpointAddress & UE_ADDR), + (ed->bmAttributes & UE_XFERTYPE), + (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); + + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); +} + +usb_error_t +saf1761_otg_init(struct saf1761_otg_softc *sc) +{ + const struct usb_hw_ep_profile *pf; + uint32_t x; + + DPRINTF("\n"); + + /* set up the bus structure */ + sc->sc_bus.usbrev = USB_REV_2_0; + sc->sc_bus.methods = &saf1761_otg_bus_methods; + + USB_BUS_LOCK(&sc->sc_bus); + + /* Reset Host controller, including HW mode */ + SAF1761_WRITE_LE_4(sc, SOTG_SW_RESET, SOTG_SW_RESET_ALL); + + DELAY(1000); + + /* Reset Host controller, including HW mode */ + SAF1761_WRITE_LE_4(sc, SOTG_SW_RESET, SOTG_SW_RESET_HC); + + /* wait a bit */ + DELAY(1000); + + SAF1761_WRITE_LE_4(sc, SOTG_SW_RESET, 0); + + /* wait a bit */ + DELAY(1000); + + /* Enable interrupts */ + sc->sc_hw_mode |= SOTG_HW_MODE_CTRL_GLOBAL_INTR_EN | + SOTG_HW_MODE_CTRL_COMN_INT; + + /* unlock device */ + SAF1761_WRITE_LE_4(sc, SOTG_UNLOCK_DEVICE, SOTG_UNLOCK_DEVICE_CODE); + + /* + * Set correct hardware mode, must be written twice if bus + * width is changed: + */ + SAF1761_WRITE_LE_4(sc, SOTG_HW_MODE_CTRL, sc->sc_hw_mode); + SAF1761_WRITE_LE_4(sc, SOTG_HW_MODE_CTRL, sc->sc_hw_mode); + + SAF1761_WRITE_LE_4(sc, SOTG_DCSCRATCH, 0xdeadbeef); + SAF1761_WRITE_LE_4(sc, SOTG_HCSCRATCH, 0xdeadbeef); + + DPRINTF("DCID=0x%08x VEND_PROD=0x%08x HWMODE=0x%08x SCRATCH=0x%08x,0x%08x\n", + SAF1761_READ_LE_4(sc, SOTG_DCCHIP_ID), + SAF1761_READ_LE_4(sc, SOTG_VEND_PROD_ID), + SAF1761_READ_LE_4(sc, SOTG_HW_MODE_CTRL), + SAF1761_READ_LE_4(sc, SOTG_DCSCRATCH), + SAF1761_READ_LE_4(sc, SOTG_HCSCRATCH)); + + /* reset device controller */ + SAF1761_WRITE_LE_4(sc, SOTG_MODE, SOTG_MODE_SFRESET); + SAF1761_WRITE_LE_4(sc, SOTG_MODE, 0); + + /* wait a bit */ + DELAY(1000); + + /* reset host controller */ + SAF1761_WRITE_LE_4(sc, SOTG_USBCMD, SOTG_USBCMD_HCRESET); + + /* wait for reset to clear */ + for (x = 0; x != 10; x++) { + if ((SAF1761_READ_LE_4(sc, SOTG_USBCMD) & SOTG_USBCMD_HCRESET) == 0) + break; + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 10); + } + + SAF1761_WRITE_LE_4(sc, SOTG_HW_MODE_CTRL, sc->sc_hw_mode | + SOTG_HW_MODE_CTRL_ALL_ATX_RESET); + + /* wait a bit */ + DELAY(1000); + + SAF1761_WRITE_LE_4(sc, SOTG_HW_MODE_CTRL, sc->sc_hw_mode); + + /* wait a bit */ + DELAY(1000); + + /* do a pulldown */ + saf1761_otg_pull_down(sc); + + /* wait 10ms for pulldown to stabilise */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100); + + for (x = 1;; x++) { + + saf1761_otg_get_hw_ep_profile(NULL, &pf, x); + if (pf == NULL) + break; + + /* select the correct endpoint */ + SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, + (x << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) | + SOTG_EP_INDEX_DIR_IN); + + /* select the maximum packet size */ + SAF1761_WRITE_LE_4(sc, SOTG_EP_MAXPACKET, pf->max_in_frame_size); + + /* select the correct endpoint */ + SAF1761_WRITE_LE_4(sc, SOTG_EP_INDEX, + (x << SOTG_EP_INDEX_ENDP_INDEX_SHIFT) | + SOTG_EP_INDEX_DIR_OUT); + + /* select the maximum packet size */ + SAF1761_WRITE_LE_4(sc, SOTG_EP_MAXPACKET, pf->max_out_frame_size); + } + + /* enable interrupts */ + SAF1761_WRITE_LE_4(sc, SOTG_MODE, SOTG_MODE_GLINTENA | + SOTG_MODE_CLKAON | SOTG_MODE_WKUPCS); + + sc->sc_interrupt_cfg |= + SOTG_INTERRUPT_CFG_CDBGMOD | + SOTG_INTERRUPT_CFG_DDBGMODIN | + SOTG_INTERRUPT_CFG_DDBGMODOUT; + + /* set default values */ + SAF1761_WRITE_LE_4(sc, SOTG_INTERRUPT_CFG, sc->sc_interrupt_cfg); + + /* enable VBUS and ID interrupt */ + SAF1761_WRITE_LE_4(sc, SOTG_IRQ_ENABLE_SET_CLR, + SOTG_IRQ_ENABLE_CLR(0xFFFF)); + SAF1761_WRITE_LE_4(sc, SOTG_IRQ_ENABLE_SET_CLR, + SOTG_IRQ_ENABLE_SET(SOTG_IRQ_ID | SOTG_IRQ_VBUS_VLD)); + + /* enable interrupts */ + sc->sc_intr_enable = SOTG_DCINTERRUPT_IEVBUS | + SOTG_DCINTERRUPT_IEBRST | SOTG_DCINTERRUPT_IESUSP; + SAF1761_WRITE_LE_4(sc, SOTG_DCINTERRUPT_EN, sc->sc_intr_enable); + + /* + * Connect ATX port 1 to device controller, select external + * charge pump and driver VBUS to +5V: + */ + SAF1761_WRITE_LE_4(sc, SOTG_CTRL_SET_CLR, + SOTG_CTRL_CLR(0xFFFF)); + SAF1761_WRITE_LE_4(sc, SOTG_CTRL_SET_CLR, + SOTG_CTRL_SET(SOTG_CTRL_SW_SEL_HC_DC | + SOTG_CTRL_BDIS_ACON_EN | SOTG_CTRL_SEL_CP_EXT | + SOTG_CTRL_VBUS_DRV)); + + /* disable device address */ + SAF1761_WRITE_LE_4(sc, SOTG_ADDRESS, 0); + + /* enable host controller clock and preserve reserved bits */ + x = SAF1761_READ_LE_4(sc, SOTG_POWER_DOWN); + SAF1761_WRITE_LE_4(sc, SOTG_POWER_DOWN, x | SOTG_POWER_DOWN_HC_CLK_EN); + + /* wait 10ms for clock */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100); + + /* enable configuration flag */ + SAF1761_WRITE_LE_4(sc, SOTG_CONFIGFLAG, SOTG_CONFIGFLAG_ENABLE); + + /* clear RAM block */ + for (x = 0x400; x != 0x10000; x += 4) + SAF1761_WRITE_LE_4(sc, x, 0); + + /* start the HC */ + SAF1761_WRITE_LE_4(sc, SOTG_USBCMD, SOTG_USBCMD_RS); + + DPRINTF("USBCMD=0x%08x\n", SAF1761_READ_LE_4(sc, SOTG_USBCMD)); + + /* make HC scan all PTDs */ + SAF1761_WRITE_LE_4(sc, SOTG_ATL_PTD_LAST_PTD, (1 << 31)); + SAF1761_WRITE_LE_4(sc, SOTG_INT_PTD_LAST_PTD, (1 << 31)); + SAF1761_WRITE_LE_4(sc, SOTG_ISO_PTD_LAST_PTD, (1 << 31)); + + /* skip all PTDs by default */ + SAF1761_WRITE_LE_4(sc, SOTG_ATL_PTD_SKIP_PTD, -1U); + SAF1761_WRITE_LE_4(sc, SOTG_INT_PTD_SKIP_PTD, -1U); + SAF1761_WRITE_LE_4(sc, SOTG_ISO_PTD_SKIP_PTD, -1U); + + /* activate all PTD types */ + SAF1761_WRITE_LE_4(sc, SOTG_HCBUFFERSTATUS, + SOTG_HCBUFFERSTATUS_ISO_BUF_FILL | + SOTG_HCBUFFERSTATUS_INT_BUF_FILL | + SOTG_HCBUFFERSTATUS_ATL_BUF_FILL); + + /* we don't use the AND mask */ + SAF1761_WRITE_LE_4(sc, SOTG_ISO_IRQ_MASK_AND, 0); + SAF1761_WRITE_LE_4(sc, SOTG_INT_IRQ_MASK_AND, 0); + SAF1761_WRITE_LE_4(sc, SOTG_ATL_IRQ_MASK_AND, 0); + + /* enable all PTD OR interrupts by default */ + SAF1761_WRITE_LE_4(sc, SOTG_ISO_IRQ_MASK_OR, -1U); + SAF1761_WRITE_LE_4(sc, SOTG_INT_IRQ_MASK_OR, -1U); + SAF1761_WRITE_LE_4(sc, SOTG_ATL_IRQ_MASK_OR, -1U); + + /* enable HC interrupts */ + SAF1761_WRITE_LE_4(sc, SOTG_HCINTERRUPT_ENABLE, + SOTG_HCINTERRUPT_OTG_IRQ | + SOTG_HCINTERRUPT_ISO_IRQ | + SOTG_HCINTERRUPT_ALT_IRQ | + SOTG_HCINTERRUPT_INT_IRQ); + + /* poll initial VBUS status */ + saf1761_otg_update_vbus(sc); + + USB_BUS_UNLOCK(&sc->sc_bus); + + /* catch any lost interrupts */ + + saf1761_otg_do_poll(&sc->sc_bus); + + return (0); /* success */ +} + +void +saf1761_otg_uninit(struct saf1761_otg_softc *sc) +{ + USB_BUS_LOCK(&sc->sc_bus); + + /* disable all interrupts */ + SAF1761_WRITE_LE_4(sc, SOTG_MODE, 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; + + saf1761_otg_pull_down(sc); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +saf1761_otg_suspend(struct saf1761_otg_softc *sc) +{ + /* TODO */ +} + +static void +saf1761_otg_resume(struct saf1761_otg_softc *sc) +{ + /* TODO */ +} + +static void +saf1761_otg_do_poll(struct usb_bus *bus) +{ + struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(bus); + + USB_BUS_LOCK(&sc->sc_bus); + USB_BUS_SPIN_LOCK(&sc->sc_bus); + saf1761_otg_interrupt_poll_locked(sc); + saf1761_otg_interrupt_complete_locked(sc); + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +/*------------------------------------------------------------------------* + * saf1761_otg control support + * saf1761_otg interrupt support + * saf1761_otg bulk support + *------------------------------------------------------------------------*/ +static void +saf1761_otg_device_non_isoc_open(struct usb_xfer *xfer) +{ + return; +} + +static void +saf1761_otg_device_non_isoc_close(struct usb_xfer *xfer) +{ + saf1761_otg_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +saf1761_otg_device_non_isoc_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +saf1761_otg_device_non_isoc_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + saf1761_otg_setup_standard_chain(xfer); + saf1761_otg_start_standard_chain(xfer); +} + +static const struct usb_pipe_methods saf1761_otg_non_isoc_methods = +{ + .open = saf1761_otg_device_non_isoc_open, + .close = saf1761_otg_device_non_isoc_close, + .enter = saf1761_otg_device_non_isoc_enter, + .start = saf1761_otg_device_non_isoc_start, +}; + +/*------------------------------------------------------------------------* + * saf1761_otg device side isochronous support + *------------------------------------------------------------------------*/ +static void +saf1761_otg_device_isoc_open(struct usb_xfer *xfer) +{ + return; +} + +static void +saf1761_otg_device_isoc_close(struct usb_xfer *xfer) +{ + saf1761_otg_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +saf1761_otg_device_isoc_enter(struct usb_xfer *xfer) +{ + struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(xfer->xroot->bus); + uint32_t temp; + uint32_t nframes; + + DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->endpoint->isoc_next, xfer->nframes); + + /* get the current frame index - we don't need the high bits */ + + nframes = SAF1761_READ_LE_4(sc, SOTG_FRAME_NUM); + + /* + * check if the frame index is within the window where the + * frames will be inserted + */ + temp = (nframes - xfer->endpoint->isoc_next) & SOTG_FRAME_NUM_SOFR_MASK; + + if ((xfer->endpoint->is_synced == 0) || + (temp < xfer->nframes)) { + /* + * 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 = (nframes + 3) & SOTG_FRAME_NUM_SOFR_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 - nframes) & SOTG_FRAME_NUM_SOFR_MASK; + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb_isoc_time_expand(&sc->sc_bus, nframes) + temp + + xfer->nframes; + + /* compute frame number for next insertion */ + xfer->endpoint->isoc_next += xfer->nframes; + + /* setup TDs */ + saf1761_otg_setup_standard_chain(xfer); +} + +static void +saf1761_otg_device_isoc_start(struct usb_xfer *xfer) +{ + /* start TD chain */ + saf1761_otg_start_standard_chain(xfer); +} + +static const struct usb_pipe_methods saf1761_otg_device_isoc_methods = +{ + .open = saf1761_otg_device_isoc_open, + .close = saf1761_otg_device_isoc_close, + .enter = saf1761_otg_device_isoc_enter, + .start = saf1761_otg_device_isoc_start, +}; + +/*------------------------------------------------------------------------* + * saf1761_otg host side isochronous support + *------------------------------------------------------------------------*/ +static void +saf1761_otg_host_isoc_open(struct usb_xfer *xfer) +{ + return; +} + +static void +saf1761_otg_host_isoc_close(struct usb_xfer *xfer) +{ + saf1761_otg_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +saf1761_otg_host_isoc_enter(struct usb_xfer *xfer) +{ + struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(xfer->xroot->bus); + uint32_t temp; + uint32_t nframes; + + DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->endpoint->isoc_next, xfer->nframes); + + /* get the current frame index - we don't need the high bits */ + + nframes = (SAF1761_READ_LE_4(sc, SOTG_FRINDEX) & SOTG_FRINDEX_MASK) >> 3; + + /* + * check if the frame index is within the window where the + * frames will be inserted + */ + temp = (nframes - xfer->endpoint->isoc_next) & (SOTG_FRINDEX_MASK >> 3); + + if ((xfer->endpoint->is_synced == 0) || + (temp < xfer->nframes)) { + /* + * 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 = (nframes + 3) & (SOTG_FRINDEX_MASK >> 3); + 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 - nframes) & (SOTG_FRINDEX_MASK >> 3); + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb_isoc_time_expand(&sc->sc_bus, nframes) + temp + + xfer->nframes; + + /* compute frame number for next insertion */ + xfer->endpoint->isoc_next += xfer->nframes; + + /* setup TDs */ + saf1761_otg_setup_standard_chain(xfer); +} + +static void +saf1761_otg_host_isoc_start(struct usb_xfer *xfer) +{ + /* start TD chain */ + saf1761_otg_start_standard_chain(xfer); +} + +static const struct usb_pipe_methods saf1761_otg_host_isoc_methods = +{ + .open = saf1761_otg_host_isoc_open, + .close = saf1761_otg_host_isoc_close, + .enter = saf1761_otg_host_isoc_enter, + .start = saf1761_otg_host_isoc_start, +}; + +/*------------------------------------------------------------------------* + * saf1761_otg root control support + *------------------------------------------------------------------------* + * Simulate a hardware HUB by handling all the necessary requests. + *------------------------------------------------------------------------*/ + +#define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) } + +static const struct usb_device_descriptor saf1761_otg_devd = { + .bLength = sizeof(struct usb_device_descriptor), + .bDescriptorType = UDESC_DEVICE, + HSETW(.idVendor, 0x04cc), + HSETW(.idProduct, 0x1761), + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_FSHUB, + .bMaxPacketSize = 64, + .bcdDevice = {0x00, 0x01}, + .iManufacturer = 1, + .iProduct = 2, + .bNumConfigurations = 1, +}; + +static const struct usb_device_qualifier saf1761_otg_odevd = { + .bLength = sizeof(struct usb_device_qualifier), + .bDescriptorType = UDESC_DEVICE_QUALIFIER, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_FSHUB, + .bMaxPacketSize0 = 0, + .bNumConfigurations = 0, +}; + +static const struct saf1761_otg_config_desc saf1761_otg_confd = { + .confd = { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(saf1761_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 | SAF1761_OTG_INTR_ENDPT), + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 8, + .bInterval = 255, + }, +}; + +static const struct usb_hub_descriptor_min saf1761_otg_hubd = { + .bDescLength = sizeof(saf1761_otg_hubd), + .bDescriptorType = UDESC_HUB, + .bNbrPorts = SOTG_NUM_PORTS, + HSETW(.wHubCharacteristics, (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL)), + .bPwrOn2PwrGood = 50, + .bHubContrCurrent = 0, + .DeviceRemovable = {0}, /* port is removable */ +}; + +#define STRING_VENDOR \ + "N\0X\0P" + +#define STRING_PRODUCT \ + "D\0C\0I\0 \0R\0o\0o\0t\0 \0H\0U\0B" + +USB_MAKE_STRING_DESC(STRING_VENDOR, saf1761_otg_vendor); +USB_MAKE_STRING_DESC(STRING_PRODUCT, saf1761_otg_product); + +static usb_error_t +saf1761_otg_roothub_exec(struct usb_device *udev, + struct usb_device_request *req, const void **pptr, uint16_t *plength) +{ + struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(udev->bus); + const void *ptr; + uint16_t len; + uint16_t value; + uint16_t index; + uint32_t temp; + uint32_t i; + usb_error_t err; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* 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: + if (index == SOTG_HOST_PORT_NUM) + goto tr_handle_clear_port_feature_host; + else if (index == SOTG_DEVICE_PORT_NUM) + goto tr_handle_clear_port_feature_device; + else + goto tr_stalled; + case UR_SET_FEATURE: + if (index == SOTG_HOST_PORT_NUM) + goto tr_handle_set_port_feature_host; + else if (index == SOTG_DEVICE_PORT_NUM) + goto tr_handle_set_port_feature_device; + else + goto tr_stalled; + case UR_CLEAR_TT_BUFFER: + case UR_RESET_TT: + case UR_STOP_TT: + 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: + if (index == SOTG_HOST_PORT_NUM) + goto tr_handle_get_port_status_host; + else if (index == SOTG_DEVICE_PORT_NUM) + goto tr_handle_get_port_status_device; + else + goto tr_stalled; + default: + goto tr_stalled; + } + 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(saf1761_otg_devd); + ptr = (const void *)&saf1761_otg_devd; + goto tr_valid; + case UDESC_DEVICE_QUALIFIER: + if (value & 0xff) + goto tr_stalled; + len = sizeof(saf1761_otg_odevd); + ptr = (const void *)&saf1761_otg_odevd; + goto tr_valid; + case UDESC_CONFIG: + if (value & 0xff) + goto tr_stalled; + len = sizeof(saf1761_otg_confd); + ptr = (const void *)&saf1761_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(saf1761_otg_vendor); + ptr = (const void *)&saf1761_otg_vendor; + goto tr_valid; + + case 2: /* Product */ + len = sizeof(saf1761_otg_product); + ptr = (const void *)&saf1761_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_device: + DPRINTFN(9, "UR_CLEAR_FEATURE on port %d\n", index); + + switch (value) { + case UHF_PORT_SUSPEND: + saf1761_otg_wakeup_peer(sc); + break; + + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 0; + break; + + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_OVER_CURRENT: + case UHF_C_PORT_RESET: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 0; + saf1761_otg_pull_down(sc); + break; + case UHF_C_PORT_CONNECTION: + 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 tr_valid; + } + goto tr_valid; + +tr_handle_clear_port_feature_host: + DPRINTFN(9, "UR_CLEAR_FEATURE on port %d\n", index); + + temp = SAF1761_READ_LE_4(sc, SOTG_PORTSC1); + + switch (value) { + case UHF_PORT_ENABLE: + SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp & ~SOTG_PORTSC1_PED); + break; + case UHF_PORT_SUSPEND: + if ((temp & SOTG_PORTSC1_SUSP) && (!(temp & SOTG_PORTSC1_FPR))) + SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp | SOTG_PORTSC1_FPR); + + /* wait 20ms for resume sequence to complete */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50); + + SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp & ~(SOTG_PORTSC1_SUSP | + SOTG_PORTSC1_FPR | SOTG_PORTSC1_LS /* High Speed */ )); + + /* 4ms settle time */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 250); + break; + case UHF_PORT_INDICATOR: + SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp & ~SOTG_PORTSC1_PIC); + break; + case UHF_PORT_TEST: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_OVER_CURRENT: + case UHF_C_PORT_RESET: + case UHF_C_PORT_SUSPEND: + /* NOPs */ + break; + case UHF_PORT_POWER: + SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp & ~SOTG_PORTSC1_PP); + break; + case UHF_C_PORT_CONNECTION: + SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp & ~SOTG_PORTSC1_ECSC); + break; + default: + err = USB_ERR_IOERROR; + goto tr_valid; + } + goto tr_valid; + +tr_handle_set_port_feature_device: + DPRINTFN(9, "UR_SET_FEATURE on port %d\n", index); + + switch (value) { + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 1; + break; + case UHF_PORT_SUSPEND: + case UHF_PORT_RESET: + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 1; + break; + default: + err = USB_ERR_IOERROR; + goto tr_valid; + } + goto tr_valid; + +tr_handle_set_port_feature_host: + DPRINTFN(9, "UR_SET_FEATURE on port %d\n", index); + + temp = SAF1761_READ_LE_4(sc, SOTG_PORTSC1); + + switch (value) { + case UHF_PORT_ENABLE: + SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp | SOTG_PORTSC1_PED); + break; + case UHF_PORT_SUSPEND: + SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp | SOTG_PORTSC1_SUSP); + break; + case UHF_PORT_RESET: + DPRINTFN(6, "reset port %d\n", index); + + /* Start reset sequence. */ + temp &= ~(SOTG_PORTSC1_PED | SOTG_PORTSC1_PR); + + SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp | SOTG_PORTSC1_PR); + + /* Wait for reset to complete. */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, + USB_MS_TO_TICKS(usb_port_root_reset_delay)); + + SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp); + + /* Wait for HC to complete reset. */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, USB_MS_TO_TICKS(2)); + + temp = SAF1761_READ_LE_4(sc, SOTG_PORTSC1); + + DPRINTF("After reset, status=0x%08x\n", temp); + if (temp & SOTG_PORTSC1_PR) { + device_printf(sc->sc_bus.bdev, "port reset timeout\n"); + err = USB_ERR_TIMEOUT; + goto tr_valid; + } + if (!(temp & SOTG_PORTSC1_PED)) { + /* Not a high speed device, give up ownership.*/ + SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp | SOTG_PORTSC1_PO); + break; + } + sc->sc_isreset = 1; + DPRINTF("port %d reset, status = 0x%08x\n", index, temp); + break; + case UHF_PORT_POWER: + DPRINTFN(3, "set port power %d\n", index); + SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp | SOTG_PORTSC1_PP); + break; + + case UHF_PORT_TEST: + DPRINTFN(3, "set port test %d\n", index); + break; + + case UHF_PORT_INDICATOR: + DPRINTFN(3, "set port ind %d\n", index); + SAF1761_WRITE_LE_4(sc, SOTG_PORTSC1, temp | SOTG_PORTSC1_PIC); + break; + default: + err = USB_ERR_IOERROR; + goto tr_valid; + } + goto tr_valid; + +tr_handle_get_port_status_device: + + DPRINTFN(9, "UR_GET_PORT_STATUS on port %d\n", index); + + if (sc->sc_flags.status_vbus) { + saf1761_otg_pull_up(sc); + } else { + saf1761_otg_pull_down(sc); + } + + /* Select FULL-speed and Device Side Mode */ + + value = UPS_PORT_MODE_DEVICE; + + if (sc->sc_flags.port_powered) + value |= UPS_PORT_POWER; + + if (sc->sc_flags.port_enabled) + value |= UPS_PORT_ENABLED; + + 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; + + USETW(sc->sc_hub_temp.ps.wPortChange, value); + len = sizeof(sc->sc_hub_temp.ps); + goto tr_valid; + +tr_handle_get_port_status_host: + + temp = SAF1761_READ_LE_4(sc, SOTG_PORTSC1); + + DPRINTFN(9, "UR_GET_PORT_STATUS on port %d = 0x%08x\n", index, temp); + + i = UPS_HIGH_SPEED; + + if (temp & SOTG_PORTSC1_ECCS) + i |= UPS_CURRENT_CONNECT_STATUS; + if (temp & SOTG_PORTSC1_PED) + i |= UPS_PORT_ENABLED; + if ((temp & SOTG_PORTSC1_SUSP) && !(temp & SOTG_PORTSC1_FPR)) + i |= UPS_SUSPEND; + if (temp & SOTG_PORTSC1_PR) + i |= UPS_RESET; + if (temp & SOTG_PORTSC1_PP) + i |= UPS_PORT_POWER; + + USETW(sc->sc_hub_temp.ps.wPortStatus, i); + i = 0; + + if (temp & SOTG_PORTSC1_ECSC) + i |= UPS_C_CONNECT_STATUS; + if (temp & SOTG_PORTSC1_FPR) + i |= UPS_C_SUSPEND; + if (sc->sc_isreset) + i |= UPS_C_PORT_RESET; + USETW(sc->sc_hub_temp.ps.wPortChange, i); + len = sizeof(sc->sc_hub_temp.ps); + goto tr_valid; + +tr_handle_get_class_descriptor: + if (value & 0xFF) + goto tr_stalled; + ptr = (const void *)&saf1761_otg_hubd; + len = sizeof(saf1761_otg_hubd); + goto tr_valid; + +tr_stalled: + err = USB_ERR_STALLED; +tr_valid: + *plength = len; + *pptr = ptr; + return (err); +} + +static void +saf1761_otg_xfer_setup(struct usb_setup_params *parm) +{ + struct saf1761_otg_softc *sc; + struct usb_xfer *xfer; + void *last_obj; + uint32_t dw1; + uint32_t ntd; + uint32_t n; + uint8_t ep_no; + uint8_t ep_type; + + sc = SAF1761_OTG_BUS2SC(parm->udev->bus); + 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 = 1; + parm->hc_max_frame_size = 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 */ ; + + } 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 profile stuff + */ + if (parm->udev->flags.usb_mode == USB_MODE_DEVICE) { + const struct usb_hw_ep_profile *pf; + + saf1761_otg_get_hw_ep_profile(parm->udev, &pf, ep_no); + + if (pf == NULL) { + /* should not happen */ + parm->err = USB_ERR_INVAL; + return; + } + } + + dw1 = (xfer->address << 3) | (ep_type << 12); + + switch (parm->udev->speed) { + case USB_SPEED_FULL: + case USB_SPEED_LOW: + /* check if root HUB port is running High Speed */ + if (parm->udev->parent_hs_hub != NULL) { + dw1 |= SOTG_PTD_DW1_ENABLE_SPLIT; + dw1 |= (parm->udev->hs_port_no << 18); + dw1 |= (parm->udev->hs_hub_addr << 25); + if (parm->udev->speed == USB_SPEED_LOW) + dw1 |= (1 << 17); + } + break; + default: + break; + } + + /* align data */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + + for (n = 0; n != ntd; n++) { + + struct saf1761_otg_td *td; + + if (parm->buf) { + + td = USB_ADD_BYTES(parm->buf, parm->size[0]); + + /* init TD */ + td->max_packet_size = xfer->max_packet_size; + td->ep_index = ep_no; + td->ep_type = ep_type; + td->dw1_value = dw1; + if (ep_type == UE_ISOCHRONOUS) { + if (parm->udev->speed == USB_SPEED_HIGH) { + uint8_t uframe_index = (ntd - 1 - n); + uframe_index <<= usbd_xfer_get_fps_shift(xfer); + td->uframe = (uframe_index & 7); + } else { + td->uframe = 0; + } + } else { + td->uframe = 0; + } + if (ep_type == UE_INTERRUPT) { + if (xfer->interval > 32) + td->interval = (32 / 2) << 3; + else + td->interval = (xfer->interval / 2) << 3; + } else { + td->interval = 0; + } + td->obj_next = last_obj; + + last_obj = td; + } + parm->size[0] += sizeof(*td); + } + + xfer->td_start[0] = last_obj; +} + +static void +saf1761_otg_xfer_unsetup(struct usb_xfer *xfer) +{ +} + +static void +saf1761_otg_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, + struct usb_endpoint *ep) +{ + uint16_t mps; + + DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d\n", + ep, udev->address, + edesc->bEndpointAddress, udev->flags.usb_mode); + + if (udev->parent_hub == NULL) { + /* root HUB has special endpoint handling */ + return; + } + + /* Verify wMaxPacketSize */ + mps = UGETW(edesc->wMaxPacketSize); + if (udev->speed == USB_SPEED_HIGH) { + if ((mps >> 11) & 3) { + DPRINTF("A packet multiplier different from " + "1 is not supported\n"); + return; + } + } + if (mps > SOTG_HS_MAX_PACKET_SIZE) { + DPRINTF("Packet size %d bigger than %d\n", + (int)mps, SOTG_HS_MAX_PACKET_SIZE); + return; + } + if (udev->flags.usb_mode == USB_MODE_DEVICE) { + if (udev->speed != USB_SPEED_FULL && + udev->speed != USB_SPEED_HIGH) { + /* not supported */ + return; + } + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_ISOCHRONOUS: + ep->methods = &saf1761_otg_device_isoc_methods; + break; + default: + ep->methods = &saf1761_otg_non_isoc_methods; + break; + } + } else { + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_ISOCHRONOUS: + ep->methods = &saf1761_otg_host_isoc_methods; + break; + default: + ep->methods = &saf1761_otg_non_isoc_methods; + break; + } + } +} + +static void +saf1761_otg_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) +{ + struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(bus); + + switch (state) { + case USB_HW_POWER_SUSPEND: + saf1761_otg_suspend(sc); + break; + case USB_HW_POWER_SHUTDOWN: + saf1761_otg_uninit(sc); + break; + case USB_HW_POWER_RESUME: + saf1761_otg_resume(sc); + break; + default: + break; + } +} + +static void +saf1761_otg_device_resume(struct usb_device *udev) +{ + struct saf1761_otg_softc *sc; + struct saf1761_otg_td *td; + struct usb_xfer *xfer; + uint8_t x; + + DPRINTF("\n"); + + if (udev->flags.usb_mode != USB_MODE_HOST) + return; + + sc = SAF1761_OTG_BUS2SC(udev->bus); + + USB_BUS_LOCK(&sc->sc_bus); + USB_BUS_SPIN_LOCK(&sc->sc_bus); + + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + + if (xfer->xroot->udev != udev) + continue; + + td = xfer->td_transfer_cache; + if (td == NULL || td->channel >= SOTG_HOST_CHANNEL_MAX) + continue; + + switch (td->ep_type) { + case UE_INTERRUPT: + x = td->channel - 32; + sc->sc_host_intr_suspend_map &= ~(1 << x); + SAF1761_WRITE_LE_4(sc, SOTG_INT_PTD_SKIP_PTD, + (~sc->sc_host_intr_map) | sc->sc_host_intr_suspend_map); + break; + case UE_ISOCHRONOUS: + x = td->channel; + sc->sc_host_isoc_suspend_map &= ~(1 << x); + SAF1761_WRITE_LE_4(sc, SOTG_ISO_PTD_SKIP_PTD, + (~sc->sc_host_isoc_map) | sc->sc_host_isoc_suspend_map); + break; + default: + x = td->channel - 64; + sc->sc_host_async_suspend_map &= ~(1 << x); + SAF1761_WRITE_LE_4(sc, SOTG_ATL_PTD_SKIP_PTD, + (~sc->sc_host_async_map) | sc->sc_host_async_suspend_map); + break; + } + } + + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); + USB_BUS_UNLOCK(&sc->sc_bus); + + /* poll all transfers again to restart resumed ones */ + saf1761_otg_do_poll(&sc->sc_bus); +} + +static void +saf1761_otg_device_suspend(struct usb_device *udev) +{ + struct saf1761_otg_softc *sc; + struct saf1761_otg_td *td; + struct usb_xfer *xfer; + uint8_t x; + + DPRINTF("\n"); + + if (udev->flags.usb_mode != USB_MODE_HOST) + return; + + sc = SAF1761_OTG_BUS2SC(udev->bus); + + USB_BUS_LOCK(&sc->sc_bus); + USB_BUS_SPIN_LOCK(&sc->sc_bus); + + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + + if (xfer->xroot->udev != udev) + continue; + + td = xfer->td_transfer_cache; + if (td == NULL || td->channel >= SOTG_HOST_CHANNEL_MAX) + continue; + + switch (td->ep_type) { + case UE_INTERRUPT: + x = td->channel - 32; + sc->sc_host_intr_suspend_map |= (1 << x); + SAF1761_WRITE_LE_4(sc, SOTG_INT_PTD_SKIP_PTD, + (~sc->sc_host_intr_map) | sc->sc_host_intr_suspend_map); + break; + case UE_ISOCHRONOUS: + x = td->channel; + sc->sc_host_isoc_suspend_map |= (1 << x); + SAF1761_WRITE_LE_4(sc, SOTG_ISO_PTD_SKIP_PTD, + (~sc->sc_host_isoc_map) | sc->sc_host_isoc_suspend_map); + break; + default: + x = td->channel - 64; + sc->sc_host_async_suspend_map |= (1 << x); + SAF1761_WRITE_LE_4(sc, SOTG_ATL_PTD_SKIP_PTD, + (~sc->sc_host_async_map) | sc->sc_host_async_suspend_map); + break; + } + } + + USB_BUS_SPIN_UNLOCK(&sc->sc_bus); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static const struct usb_bus_methods saf1761_otg_bus_methods = +{ + .endpoint_init = &saf1761_otg_ep_init, + .xfer_setup = &saf1761_otg_xfer_setup, + .xfer_unsetup = &saf1761_otg_xfer_unsetup, + .get_hw_ep_profile = &saf1761_otg_get_hw_ep_profile, + .xfer_stall = &saf1761_otg_xfer_stall, + .set_stall = &saf1761_otg_set_stall, + .clear_stall = &saf1761_otg_clear_stall, + .roothub_exec = &saf1761_otg_roothub_exec, + .xfer_poll = &saf1761_otg_do_poll, + .set_hw_power_sleep = saf1761_otg_set_hw_power_sleep, + .device_resume = &saf1761_otg_device_resume, + .device_suspend = &saf1761_otg_device_suspend, +}; diff --git a/sys/dev/usb/controller/saf1761_otg.h b/sys/dev/usb/controller/saf1761_otg.h new file mode 100644 index 0000000000000..6d68e3e1bb233 --- /dev/null +++ b/sys/dev/usb/controller/saf1761_otg.h @@ -0,0 +1,172 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2014 Hans Petter Selasky <hselasky@FreeBSD.org> + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * 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 _SAF1761_OTG_H_ +#define _SAF1761_OTG_H_ + +#define SOTG_MAX_DEVICES MIN(USB_MAX_DEVICES, 32) +#define SOTG_FS_MAX_PACKET_SIZE 64 +#define SOTG_HS_MAX_PACKET_SIZE 512 +#define SOTG_NUM_PORTS 2 /* one Device and one Host port */ +#define SOTG_HOST_PORT_NUM 1 +#define SOTG_DEVICE_PORT_NUM 2 +#define SOTG_HOST_CHANNEL_MAX (3 * 32) + +/* Macros used for reading and writing little endian registers */ + +#define SAF1761_READ_LE_4(sc, reg) ({ uint32_t _temp; \ + _temp = bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (reg)); \ + le32toh(_temp); }) + +#define SAF1761_WRITE_LE_4(sc, reg, data) do { \ + uint32_t _temp = (data); \ + bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (reg), htole32(_temp)); \ +} while (0) + +/* 90ns delay macro */ + +#define SAF1761_90NS_DELAY(sc) do { \ + (void) SAF1761_READ_LE_4(sc, SOTG_VEND_PROD_ID); \ + (void) SAF1761_READ_LE_4(sc, SOTG_VEND_PROD_ID); \ + (void) SAF1761_READ_LE_4(sc, SOTG_VEND_PROD_ID); \ + (void) SAF1761_READ_LE_4(sc, SOTG_VEND_PROD_ID); \ +} while (0) + +struct saf1761_otg_softc; +struct saf1761_otg_td; + +typedef uint8_t (saf1761_otg_cmd_t)(struct saf1761_otg_softc *, struct saf1761_otg_td *td); + +struct saf1761_otg_td { + struct saf1761_otg_td *obj_next; + saf1761_otg_cmd_t *func; + struct usb_page_cache *pc; + uint32_t offset; + uint32_t remainder; + uint32_t dw1_value; + uint16_t max_packet_size; + uint8_t ep_index; + uint8_t ep_type; + uint8_t channel; + uint8_t uframe; + uint8_t interval; + 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; +}; + +struct saf1761_otg_std_temp { + saf1761_otg_cmd_t *func; + struct usb_page_cache *pc; + struct saf1761_otg_td *td; + struct saf1761_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; +}; + +struct saf1761_otg_config_desc { + struct usb_config_descriptor confd; + struct usb_interface_descriptor ifcd; + struct usb_endpoint_descriptor endpd; +} __packed; + +union saf1761_otg_hub_temp { + uWord wValue; + struct usb_port_status ps; +}; + +struct saf1761_otg_flags { + uint8_t change_connect:1; + uint8_t change_suspend: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 clocks_off:1; + uint8_t port_powered:1; + uint8_t port_enabled:1; + uint8_t d_pulled_up:1; +}; + +struct saf1761_otg_softc { + struct usb_bus sc_bus; + union saf1761_otg_hub_temp sc_hub_temp; + + struct usb_device *sc_devices[SOTG_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_host_async_map; + uint32_t sc_host_async_suspend_map; + uint32_t sc_host_intr_map; + uint32_t sc_host_intr_suspend_map; + uint32_t sc_host_isoc_map; + uint32_t sc_host_isoc_suspend_map; + uint32_t sc_intr_enable; /* enabled interrupts */ + uint32_t sc_hw_mode; /* hardware mode */ + uint32_t sc_interrupt_cfg; /* interrupt configuration */ + uint32_t sc_xfer_complete; + + uint32_t sc_bounce_buffer[1024 / 4]; + + uint8_t sc_rt_addr; /* root HUB address */ + uint8_t sc_dv_addr; /* device address */ + uint8_t sc_conf; /* root HUB config */ + uint8_t sc_isreset; /* host mode */ + + uint8_t sc_hub_idata[1]; + + struct saf1761_otg_flags sc_flags; +}; + +/* prototypes */ + +usb_error_t saf1761_otg_init(struct saf1761_otg_softc *sc); +void saf1761_otg_uninit(struct saf1761_otg_softc *sc); +driver_filter_t saf1761_otg_filter_interrupt; +driver_intr_t saf1761_otg_interrupt; + +#endif /* _SAF1761_OTG_H_ */ diff --git a/sys/dev/usb/controller/saf1761_otg_boot.c b/sys/dev/usb/controller/saf1761_otg_boot.c new file mode 100644 index 0000000000000..ead8de30e5543 --- /dev/null +++ b/sys/dev/usb/controller/saf1761_otg_boot.c @@ -0,0 +1,146 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2014 Hans Petter Selasky <hselasky@FreeBSD.org> + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * 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. + */ + +#include USB_GLOBAL_INCLUDE_FILE + +#include <dev/usb/controller/saf1761_otg.h> +#include <dev/usb/controller/saf1761_otg_reg.h> + +static device_probe_t saf1761_otg_fdt_probe; +static device_attach_t saf1761_otg_fdt_attach; +static device_detach_t saf1761_otg_fdt_detach; + +static device_method_t saf1761_otg_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, saf1761_otg_fdt_probe), + DEVMETHOD(device_attach, saf1761_otg_fdt_attach), + DEVMETHOD(device_detach, saf1761_otg_fdt_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + DEVMETHOD_END +}; + +static driver_t saf1761_otg_driver = { + .name = "saf1761otg", + .methods = saf1761_otg_methods, + .size = sizeof(struct saf1761_otg_softc), +}; + +static devclass_t saf1761_otg_devclass; + +DRIVER_MODULE(saf1761otg, pci, saf1761_otg_driver, saf1761_otg_devclass, 0, 0); +MODULE_DEPEND(saf1761otg, usb, 1, 1, 1); + +static int +saf1761_otg_fdt_probe(device_t dev) +{ + if (device_get_unit(dev) != 0) + return (ENXIO); + + device_set_desc(dev, "ISP1761/SAF1761 DCI USB 2.0 Device Controller"); + + return (0); +} + +static int +saf1761_otg_fdt_attach(device_t dev) +{ + struct saf1761_otg_softc *sc = device_get_softc(dev); + int err; + + /* 32-bit data bus */ + sc->sc_hw_mode |= SOTG_HW_MODE_CTRL_DATA_BUS_WIDTH; + + /* initialise some bus fields */ + sc->sc_bus.parent = dev; + sc->sc_bus.devices = sc->sc_devices; + sc->sc_bus.devices_max = SOTG_MAX_DEVICES; + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(dev), NULL)) + return (ENOMEM); + + sc->sc_io_res = (void *)1; + sc->sc_io_tag = (void *)1; + sc->sc_io_hdl = (void *)USB_PCI_MEMORY_ADDRESS; + sc->sc_io_size = USB_PCI_MEMORY_SIZE; + + sc->sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (sc->sc_bus.bdev == NULL) + goto error; + + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + device_set_interrupt(dev, &saf1761_otg_filter_interrupt, &saf1761_otg_interrupt, sc); + + err = saf1761_otg_init(sc); + if (err) { + device_printf(dev, "Init failed\n"); + goto error; + } + err = device_probe_and_attach(sc->sc_bus.bdev); + if (err) { + device_printf(dev, "USB probe and attach failed\n"); + goto error; + } + return (0); + +error: + saf1761_otg_fdt_detach(dev); + return (ENXIO); +} + +static int +saf1761_otg_fdt_detach(device_t dev) +{ + struct saf1761_otg_softc *sc = device_get_softc(dev); + device_t bdev; + + if (sc->sc_bus.bdev) { + bdev = sc->sc_bus.bdev; + device_detach(bdev); + device_delete_child(dev, bdev); + } + + /* during module unload there are lots of children leftover */ + device_delete_children(dev); + + if (sc->sc_irq_res) { + /* + * Only call uninit() after init() + */ + saf1761_otg_uninit(sc); + } + usb_bus_mem_free_all(&sc->sc_bus, NULL); + + return (0); +} diff --git a/sys/dev/usb/controller/saf1761_otg_fdt.c b/sys/dev/usb/controller/saf1761_otg_fdt.c new file mode 100644 index 0000000000000..1b18aea33c4db --- /dev/null +++ b/sys/dev/usb/controller/saf1761_otg_fdt.c @@ -0,0 +1,274 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2014 Hans Petter Selasky <hselasky@FreeBSD.org> + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * 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. + */ + +#ifdef USB_GLOBAL_INCLUDE_FILE +#include USB_GLOBAL_INCLUDE_FILE +#else +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> +#include <sys/rman.h> + +#include <dev/fdt/fdt_common.h> + +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> + +#include <dev/usb/usb_core.h> +#include <dev/usb/usb_busdma.h> +#include <dev/usb/usb_process.h> +#include <dev/usb/usb_transfer.h> +#include <dev/usb/usb_device.h> +#include <dev/usb/usb_hub.h> +#include <dev/usb/usb_util.h> + +#include <dev/usb/usb_controller.h> +#include <dev/usb/usb_bus.h> +#endif /* USB_GLOBAL_INCLUDE_FILE */ + +#include <dev/usb/controller/saf1761_otg.h> +#include <dev/usb/controller/saf1761_otg_reg.h> + +static device_probe_t saf1761_otg_fdt_probe; +static device_attach_t saf1761_otg_fdt_attach; +static device_detach_t saf1761_otg_fdt_detach; + +static device_method_t saf1761_otg_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, saf1761_otg_fdt_probe), + DEVMETHOD(device_attach, saf1761_otg_fdt_attach), + DEVMETHOD(device_detach, saf1761_otg_fdt_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + DEVMETHOD_END +}; + +static driver_t saf1761_otg_driver = { + .name = "saf1761otg", + .methods = saf1761_otg_methods, + .size = sizeof(struct saf1761_otg_softc), +}; + +static devclass_t saf1761_otg_devclass; + +DRIVER_MODULE(saf1761otg, simplebus, saf1761_otg_driver, saf1761_otg_devclass, 0, 0); +MODULE_DEPEND(saf1761otg, usb, 1, 1, 1); + +static int +saf1761_otg_fdt_probe(device_t dev) +{ + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (!ofw_bus_is_compatible(dev, "nxp,usb-isp1761")) + return (ENXIO); + + device_set_desc(dev, "ISP1761/SAF1761 DCI USB 2.0 Device Controller"); + + return (0); +} + +static int +saf1761_otg_fdt_attach(device_t dev) +{ + struct saf1761_otg_softc *sc = device_get_softc(dev); + char param[24]; + int err; + int rid; + + /* get configuration from FDT */ + + /* get bus-width, if any */ + if (OF_getprop(ofw_bus_get_node(dev), "bus-width", + ¶m, sizeof(param)) > 0) { + param[sizeof(param) - 1] = 0; + if (strcmp(param, "32") == 0) + sc->sc_hw_mode |= SOTG_HW_MODE_CTRL_DATA_BUS_WIDTH; + } else { + /* assume 32-bit data bus */ + sc->sc_hw_mode |= SOTG_HW_MODE_CTRL_DATA_BUS_WIDTH; + } + + /* get analog over-current setting */ + if (OF_getprop(ofw_bus_get_node(dev), "analog-oc", + ¶m, sizeof(param)) > 0) { + sc->sc_hw_mode |= SOTG_HW_MODE_CTRL_ANA_DIGI_OC; + } + + /* get DACK polarity */ + if (OF_getprop(ofw_bus_get_node(dev), "dack-polarity", + ¶m, sizeof(param)) > 0) { + sc->sc_hw_mode |= SOTG_HW_MODE_CTRL_DACK_POL; + } + + /* get DREQ polarity */ + if (OF_getprop(ofw_bus_get_node(dev), "dreq-polarity", + ¶m, sizeof(param)) > 0) { + sc->sc_hw_mode |= SOTG_HW_MODE_CTRL_DREQ_POL; + } + + /* get IRQ polarity */ + if (OF_getprop(ofw_bus_get_node(dev), "int-polarity", + ¶m, sizeof(param)) > 0) { + sc->sc_interrupt_cfg |= SOTG_INTERRUPT_CFG_INTPOL; + sc->sc_hw_mode |= SOTG_HW_MODE_CTRL_INTR_POL; + } + + /* get IRQ level triggering */ + if (OF_getprop(ofw_bus_get_node(dev), "int-level", + ¶m, sizeof(param)) > 0) { + sc->sc_interrupt_cfg |= SOTG_INTERRUPT_CFG_INTLVL; + sc->sc_hw_mode |= SOTG_HW_MODE_CTRL_INTR_LEVEL; + } + + /* initialise some bus fields */ + sc->sc_bus.parent = dev; + sc->sc_bus.devices = sc->sc_devices; + sc->sc_bus.devices_max = SOTG_MAX_DEVICES; + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_bus, + USB_GET_DMA_TAG(dev), NULL)) { + return (ENOMEM); + } + rid = 0; + sc->sc_io_res = + bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); + + if (sc->sc_io_res == NULL) + goto error; + + sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); + sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); + sc->sc_io_size = rman_get_size(sc->sc_io_res); + + /* try to allocate the HC interrupt first */ + rid = 1; + sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->sc_irq_res == NULL) { + /* try to allocate a common IRQ second */ + rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->sc_irq_res == NULL) + goto error; + } + + sc->sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (sc->sc_bus.bdev == NULL) + goto error; + + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + + err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_TTY | INTR_MPSAFE, + &saf1761_otg_filter_interrupt, &saf1761_otg_interrupt, sc, &sc->sc_intr_hdl); + if (err) { + sc->sc_intr_hdl = NULL; + goto error; + } + err = saf1761_otg_init(sc); + if (err) { + device_printf(dev, "Init failed\n"); + goto error; + } + err = device_probe_and_attach(sc->sc_bus.bdev); + if (err) { + device_printf(dev, "USB probe and attach failed\n"); + goto error; + } + return (0); + +error: + saf1761_otg_fdt_detach(dev); + return (ENXIO); +} + +static int +saf1761_otg_fdt_detach(device_t dev) +{ + struct saf1761_otg_softc *sc = device_get_softc(dev); + device_t bdev; + int err; + + if (sc->sc_bus.bdev) { + bdev = sc->sc_bus.bdev; + device_detach(bdev); + device_delete_child(dev, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_children(dev); + + if (sc->sc_irq_res && sc->sc_intr_hdl) { + /* + * Only call uninit() after init() + */ + saf1761_otg_uninit(sc); + + err = bus_teardown_intr(dev, sc->sc_irq_res, + sc->sc_intr_hdl); + sc->sc_intr_hdl = NULL; + } + if (sc->sc_irq_res) { + bus_release_resource(dev, SYS_RES_IRQ, 0, + sc->sc_irq_res); + sc->sc_irq_res = NULL; + } + if (sc->sc_io_res) { + bus_release_resource(dev, SYS_RES_MEMORY, 0, + sc->sc_io_res); + sc->sc_io_res = NULL; + } + usb_bus_mem_free_all(&sc->sc_bus, NULL); + + return (0); +} diff --git a/sys/dev/usb/controller/saf1761_otg_reg.h b/sys/dev/usb/controller/saf1761_otg_reg.h new file mode 100644 index 0000000000000..d73718f20ddcf --- /dev/null +++ b/sys/dev/usb/controller/saf1761_otg_reg.h @@ -0,0 +1,273 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2014 Hans Petter Selasky <hselasky@FreeBSD.org> + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * 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 _SAF1761_OTG_REG_H_ +#define _SAF1761_OTG_REG_H_ + +/* Global registers */ + +#define SOTG_VEND_PROD_ID 0x370 +#define SOTG_VEND_ID(x) ((x) & 0xFFFF) +#define SOTG_PROD_ID(x) (((x) >> 16) & 0xFFFF) +#define SOTG_CTRL_SET_CLR 0x374 +#define SOTG_CTRL_SET(x) ((x) & 0xFFFF) +#define SOTG_CTRL_CLR(x) (((x) << 16) & 0xFFFF0000) +#define SOTG_CTRL_OTG_DISABLE (1 << 10) +#define SOTG_CTRL_OTG_SE0_EN (1 << 9) +#define SOTG_CTRL_BDIS_ACON_EN (1 << 8) +#define SOTG_CTRL_SW_SEL_HC_DC (1 << 7) +#define SOTG_CTRL_VBUS_CHRG (1 << 6) +#define SOTG_CTRL_VBUS_DISCHRG (1 << 5) +#define SOTG_CTRL_VBUS_DRV (1 << 4) +#define SOTG_CTRL_SEL_CP_EXT (1 << 3) +#define SOTG_CTRL_DM_PULL_DOWN (1 << 2) +#define SOTG_CTRL_DP_PULL_DOWN (1 << 1) +#define SOTG_CTRL_DP_PULL_UP (1 << 0) +#define SOTG_STATUS 0x378 +#define SOTG_STATUS_B_SE0_SRP (1 << 8) +#define SOTG_STATUS_B_SESS_END (1 << 7) +#define SOTG_STATUS_RMT_CONN (1 << 4) +#define SOTG_STATUS_ID (1 << 3) +#define SOTG_STATUS_DP_SRP (1 << 2) +#define SOTG_STATUS_A_B_SESS_VLD (1 << 1) +#define SOTG_STATUS_VBUS_VLD (1 << 0) +#define SOTG_IRQ_LATCH_SET_CLR 0x37C +#define SOTG_IRQ_LATCH_SET(x) ((x) & 0xFFFF) +#define SOTG_IRQ_LATCH_CLR(x) (((x) << 16) & 0xFFFF0000) +#define SOTG_IRQ_ENABLE_SET_CLR 0x380 +#define SOTG_IRQ_ENABLE_SET(x) ((x) & 0xFFFF) +#define SOTG_IRQ_ENABLE_CLR(x) (((x) << 16) & 0xFFFF0000) +#define SOTG_IRQ_RISE_SET_CLR 0x384 +#define SOTG_IRQ_RISE_SET(x) ((x) & 0xFFFF) +#define SOTG_IRQ_RISE_CLR(x) (((x) << 16) & 0xFFFF0000) +#define SOTG_IRQ_OTG_TMR_TIMEOUT (1 << 9) +#define SOTG_IRQ_B_SE0_SRP (1 << 8) +#define SOTG_IRQ_B_SESS_END (1 << 7) +#define SOTG_IRQ_BDIS_ACON (1 << 6) +#define SOTG_IRQ_OTG_RESUME (1 << 5) +#define SOTG_IRQ_RMT_CONN (1 << 4) +#define SOTG_IRQ_ID (1 << 3) +#define SOTG_IRQ_DP_SRP (1 << 2) +#define SOTG_IRQ_A_B_SESS_VLD (1 << 1) +#define SOTG_IRQ_VBUS_VLD (1 << 0) +#define SOTG_TIMER_LOW_SET_CLR 0x388 +#define SOTG_TIMER_LOW_SET(x) ((x) & 0xFFFF) +#define SOTG_TIMER_LOW_CLR(x) (((x) << 16) & 0xFFFF0000) +#define SOTG_TIMER_HIGH_SET_CLR 0x38C +#define SOTG_TIMER_HIGH_SET(x) ((x) & 0xFFFF) +#define SOTG_TIMER_HIGH_CLR(x) (((x) << 16) & 0xFFFF0000) +#define SOTG_TIMER_HIGH_START (1U << 15) +#define SOTG_MEMORY_REG 0x33c + +/* Peripheral controller specific registers */ + +#define SOTG_ADDRESS 0x200 +#define SOTG_ADDRESS_ENABLE (1 << 7) +#define SOTG_MODE 0x20C +#define SOTG_MODE_DMACLK_ON (1 << 9) +#define SOTG_MODE_VBUSSTAT (1 << 8) +#define SOTG_MODE_CLKAON (1 << 7) +#define SOTG_MODE_SNDRSU (1 << 6) +#define SOTG_MODE_GOSUSP (1 << 5) +#define SOTG_MODE_SFRESET (1 << 4) +#define SOTG_MODE_GLINTENA (1 << 3) +#define SOTG_MODE_WKUPCS (1 << 2) +#define SOTG_INTERRUPT_CFG 0x210 +#define SOTG_INTERRUPT_CFG_DEBUG_SET (1 << 16) +#define SOTG_INTERRUPT_CFG_CDBGMOD (1 << 6) /* ACK only */ +#define SOTG_INTERRUPT_CFG_DDBGMODIN (1 << 4) /* ACK only */ +#define SOTG_INTERRUPT_CFG_DDBGMODOUT (1 << 2) /* ACK and NYET only */ +#define SOTG_INTERRUPT_CFG_INTLVL (1 << 1) +#define SOTG_INTERRUPT_CFG_INTPOL (1 << 0) +#define SOTG_DCINTERRUPT_EN 0x214 +#define SOTG_HW_MODE_CTRL 0x300 +#define SOTG_HW_MODE_CTRL_ALL_ATX_RESET (1 << 31) +#define SOTG_HW_MODE_CTRL_ANA_DIGI_OC (1 << 15) +#define SOTG_HW_MODE_CTRL_DEV_DMA (1 << 11) +#define SOTG_HW_MODE_CTRL_COMN_INT (1 << 10) +#define SOTG_HW_MODE_CTRL_COMN_DMA (1 << 9) +#define SOTG_HW_MODE_CTRL_DATA_BUS_WIDTH (1 << 8) +#define SOTG_HW_MODE_CTRL_DACK_POL (1 << 6) +#define SOTG_HW_MODE_CTRL_DREQ_POL (1 << 5) +#define SOTG_HW_MODE_CTRL_INTR_POL (1 << 2) +#define SOTG_HW_MODE_CTRL_INTR_LEVEL (1 << 1) +#define SOTG_HW_MODE_CTRL_GLOBAL_INTR_EN (1 << 0) +#define SOTG_OTG_CTRL 0x374 +#define SOTG_EP_INDEX 0x22c +#define SOTG_EP_INDEX_EP0SETUP (1 << 5) +#define SOTG_EP_INDEX_ENDP_INDEX_MASK (15 << 1) +#define SOTG_EP_INDEX_ENDP_INDEX_SHIFT 1 +#define SOTG_EP_INDEX_DIR_IN (1 << 0) +#define SOTG_EP_INDEX_DIR_OUT 0 +#define SOTG_CTRL_FUNC 0x228 +#define SOTG_CTRL_FUNC_CLBUF (1 << 4) +#define SOTG_CTRL_FUNC_VENDP (1 << 3) +#define SOTG_CTRL_FUNC_DSEN (1 << 2) +#define SOTG_CTRL_FUNC_STATUS (1 << 1) +#define SOTG_CTRL_FUNC_STALL (1 << 0) +#define SOTG_DATA_PORT 0x220 +#define SOTG_BUF_LENGTH 0x21C +#define SOTG_BUF_LENGTH_BUFLEN_MASK 0xFFFF +#define SOTG_BUF_LENGTH_FILLED_MASK (3 << 16) +#define SOTG_EP_MAXPACKET 0x204 +#define SOTG_EP_TYPE 0x208 +#define SOTG_EP_TYPE_NOEMPPKT (1 << 4) +#define SOTG_EP_TYPE_ENABLE (1 << 3) +#define SOTG_EP_TYPE_DBLBUF (1 << 2) +#define SOTG_EP_TYPE_EP_TYPE (3 << 0) +#define SOTG_DMA_CMD 0x230 +#define SOTG_DMA_XFER_COUNT 0x234 +#define SOTG_DCDMA_CFG 0x238 +#define SOTG_DMA_HW 0x23C +#define SOTG_DMA_IRQ_REASON 0x250 +#define SOTG_DMA_IRQ_ENABLE 0x254 +#define SOTG_DMA_EP 0x258 +#define SOTG_BURST_COUNTER 0x264 +#define SOTG_DCINTERRUPT 0x218 +#define SOTG_DCINTERRUPT_IEPRX(n) (1 << (10 + (2*(n)))) +#define SOTG_DCINTERRUPT_IEPTX(n) (1 << (11 + (2*(n)))) +#define SOTG_DCINTERRUPT_IEP0SETUP (1 << 8) +#define SOTG_DCINTERRUPT_IEVBUS (1 << 7) +#define SOTG_DCINTERRUPT_IEDMA (1 << 6) +#define SOTG_DCINTERRUPT_IEHS_STA (1 << 5) +#define SOTG_DCINTERRUPT_IERESM (1 << 4) +#define SOTG_DCINTERRUPT_IESUSP (1 << 3) +#define SOTG_DCINTERRUPT_IEPSOF (1 << 2) +#define SOTG_DCINTERRUPT_IESOF (1 << 1) +#define SOTG_DCINTERRUPT_IEBRST (1 << 0) +#define SOTG_DCCHIP_ID 0x270 +#define SOTG_FRAME_NUM 0x274 +#define SOTG_FRAME_NUM_MICROSOFR_MASK 0x3800 +#define SOTG_FRAME_NUM_MICROSOFR_SHIFT 11 +#define SOTG_FRAME_NUM_SOFR_MASK 0x7FF +#define SOTG_DCSCRATCH 0x278 +#define SOTG_UNLOCK_DEVICE 0x27C +#define SOTG_UNLOCK_DEVICE_CODE 0xAA37 +#define SOTG_IRQ_PULSE_WIDTH 0x280 +#define SOTG_TEST_MODE 0x284 +#define SOTG_TEST_MODE_FORCEHS (1 << 7) +#define SOTG_TEST_MODE_FORCEFS (1 << 4) +#define SOTG_TEST_MODE_PRBS (1 << 3) +#define SOTG_TEST_MODE_KSTATE (1 << 2) +#define SOTG_TEST_MODE_JSTATE (1 << 1) +#define SOTG_TEST_MODE_SE0_NAK (1 << 0) + +/* Host controller specific registers */ + +#define SOTG_FRINDEX 0x002c +#define SOTG_FRINDEX_MASK 0x3fff +#define SOTG_CONFIGFLAG 0x0060 +#define SOTG_CONFIGFLAG_ENABLE (1 << 0) +#define SOTG_PORTSC1 0x0064 +#define SOTG_PORTSC1_PIC (3 << 14) +#define SOTG_PORTSC1_PO (1 << 13) +#define SOTG_PORTSC1_PP (1 << 12) +#define SOTG_PORTSC1_LS (3 << 10) +#define SOTG_PORTSC1_PR (1 << 8) +#define SOTG_PORTSC1_SUSP (1 << 7) +#define SOTG_PORTSC1_FPR (1 << 6) +#define SOTG_PORTSC1_PED (1 << 2) +#define SOTG_PORTSC1_ECSC (1 << 1) +#define SOTG_PORTSC1_ECCS (1 << 0) +#define SOTG_PTD_DW0 0 +#define SOTG_PTD_DW0_VALID 1U +#define SOTG_PTD_DW1 4 +#define SOTG_PTD_DW1_ENABLE_SPLIT (1 << 14) +#define SOTG_PTD_DW2 8 +#define SOTG_PTD_DW2_RL (0xf << 25) +#define SOTG_PTD_DW3 12 +#define SOTG_PTD_DW3_NRL (0xf << 19) +#define SOTG_PTD_DW3_ACTIVE (1U << 31) +#define SOTG_PTD_DW3_HALTED (1U << 30) +#define SOTG_PTD_DW3_ERRORS (3U << 28) +#define SOTG_PTD_DW3_CERR_3 (3U << 23) +#define SOTG_PTD_DW3_CERR_2 (2U << 23) /* infinite NAKs */ +#define SOTG_PTD_DW3_CERR_1 (1U << 23) +#define SOTG_PTD_DW3_XFER_COUNT 0x7FFF +#define SOTG_PTD_DW4 16 +#define SOTG_PTD_DW5 20 +#define SOTG_PTD_DW6 24 +#define SOTG_PTD_DW7 28 +#define SOTG_DATA_ADDR(x) (0x1000 + (512 * (x))) +#define SOTG_ASYNC_PTD(x) (0xC00 + ((x) * 32)) +#define SOTG_INTR_PTD(x) (0x800 + ((x) * 32)) +#define SOTG_ISOC_PTD(x) (0x400 + ((x) * 32)) +#define SOTG_PTD(x) (0x400 + ((x) * 32)) +#define SOTG_HC_MEMORY_ADDR(x) (((x) - 0x400) >> 3) +#define SOTG_SW_RESET 0x30C +#define SOTG_SW_RESET_HC (1 << 1) +#define SOTG_SW_RESET_ALL (1 << 0) +#define SOTG_POWER_DOWN 0x354 +#define SOTG_POWER_DOWN_PORT3_PD (1 << 12) +#define SOTG_POWER_DOWN_PORT2_PD (1 << 11) +#define SOTG_POWER_DOWN_VBATDET_PWR (1 << 10) +#define SOTG_POWER_DOWN_BIAS_EN (1 << 5) +#define SOTG_POWER_DOWN_VREG_ON (1 << 4) +#define SOTG_POWER_DOWN_OC3_PWR (1 << 3) +#define SOTG_POWER_DOWN_OC2_PWR (1 << 2) +#define SOTG_POWER_DOWN_OC1_PWR (1 << 1) +#define SOTG_POWER_DOWN_HC_CLK_EN (1 << 0) +#define SOTG_USBCMD 0x20 +#define SOTG_USBCMD_LHCR (1 << 7) +#define SOTG_USBCMD_HCRESET (1 << 1) +#define SOTG_USBCMD_RS (1 << 0) +#define SOTG_HCSCRATCH 0x308 +#define SOTG_HCINTERRUPT 0x310 +#define SOTG_HCINTERRUPT_OTG_IRQ (1 << 10) +#define SOTG_HCINTERRUPT_ISO_IRQ (1 << 9) +#define SOTG_HCINTERRUPT_ALT_IRQ (1 << 8) +#define SOTG_HCINTERRUPT_INT_IRQ (1 << 7) +#define SOTG_HCINTERRUPT_CLKREADY (1 << 6) +#define SOTG_HCINTERRUPT_HCSUSP (1 << 5) +#define SOTG_HCINTERRUPT_DMAEOTINT (1 << 3) +#define SOTG_HCINTERRUPT_SOFITLINT (1 << 1) +#define SOTG_HCINTERRUPT_ENABLE 0x314 +#define SOTG_ATL_PTD_DONE_PTD 0x150 +#define SOTG_ATL_PTD_SKIP_PTD 0x154 +#define SOTG_ATL_PTD_LAST_PTD 0x158 +#define SOTG_INT_PTD_DONE_PTD 0x140 +#define SOTG_INT_PTD_SKIP_PTD 0x144 +#define SOTG_INT_PTD_LAST_PTD 0x148 +#define SOTG_ISO_PTD_DONE_PTD 0x130 +#define SOTG_ISO_PTD_SKIP_PTD 0x134 +#define SOTG_ISO_PTD_LAST_PTD 0x138 +#define SOTG_HCBUFFERSTATUS 0x334 +#define SOTG_HCBUFFERSTATUS_ISO_BUF_FILL (1 << 2) +#define SOTG_HCBUFFERSTATUS_INT_BUF_FILL (1 << 1) +#define SOTG_HCBUFFERSTATUS_ATL_BUF_FILL (1 << 0) +#define SOTG_ISO_IRQ_MASK_OR 0x318 +#define SOTG_INT_IRQ_MASK_OR 0x31C +#define SOTG_ATL_IRQ_MASK_OR 0x320 +#define SOTG_ISO_IRQ_MASK_AND 0x324 +#define SOTG_INT_IRQ_MASK_AND 0x328 +#define SOTG_ATL_IRQ_MASK_AND 0x32C + +#endif /* _SAF1761_OTG_REG_H_ */ diff --git a/sys/dev/usb/controller/uhci.c b/sys/dev/usb/controller/uhci.c index 134a4472363ac..00776154447c0 100644 --- a/sys/dev/usb/controller/uhci.c +++ b/sys/dev/usb/controller/uhci.c @@ -89,12 +89,10 @@ static int uhcidebug = 0; static int uhcinoloop = 0; static SYSCTL_NODE(_hw_usb, OID_AUTO, uhci, CTLFLAG_RW, 0, "USB uhci"); -SYSCTL_INT(_hw_usb_uhci, OID_AUTO, debug, CTLFLAG_RW | CTLFLAG_TUN, +SYSCTL_INT(_hw_usb_uhci, OID_AUTO, debug, CTLFLAG_RWTUN, &uhcidebug, 0, "uhci debug level"); -TUNABLE_INT("hw.usb.uhci.debug", &uhcidebug); -SYSCTL_INT(_hw_usb_uhci, OID_AUTO, loop, CTLFLAG_RW | CTLFLAG_TUN, +SYSCTL_INT(_hw_usb_uhci, OID_AUTO, loop, CTLFLAG_RWTUN, &uhcinoloop, 0, "uhci noloop"); -TUNABLE_INT("hw.usb.uhci.loop", &uhcinoloop); static void uhci_dumpregs(uhci_softc_t *sc); static void uhci_dump_tds(uhci_td_t *td); diff --git a/sys/dev/usb/controller/usb_controller.c b/sys/dev/usb/controller/usb_controller.c index bbb9e85df954e..dd75ba7a5aa24 100644 --- a/sys/dev/usb/controller/usb_controller.c +++ b/sys/dev/usb/controller/usb_controller.c @@ -90,19 +90,16 @@ SYSCTL_INT(_hw_usb_ctrl, OID_AUTO, debug, CTLFLAG_RW, &usb_ctrl_debug, 0, #if USB_HAVE_ROOT_MOUNT_HOLD static int usb_no_boot_wait = 0; -TUNABLE_INT("hw.usb.no_boot_wait", &usb_no_boot_wait); -SYSCTL_INT(_hw_usb, OID_AUTO, no_boot_wait, CTLFLAG_RD|CTLFLAG_TUN, &usb_no_boot_wait, 0, +SYSCTL_INT(_hw_usb, OID_AUTO, no_boot_wait, CTLFLAG_RDTUN, &usb_no_boot_wait, 0, "No USB device enumerate waiting at boot."); #endif static int usb_no_suspend_wait = 0; -TUNABLE_INT("hw.usb.no_suspend_wait", &usb_no_suspend_wait); -SYSCTL_INT(_hw_usb, OID_AUTO, no_suspend_wait, CTLFLAG_RW|CTLFLAG_TUN, +SYSCTL_INT(_hw_usb, OID_AUTO, no_suspend_wait, CTLFLAG_RWTUN, &usb_no_suspend_wait, 0, "No USB device waiting at system suspend."); static int usb_no_shutdown_wait = 0; -TUNABLE_INT("hw.usb.no_shutdown_wait", &usb_no_shutdown_wait); -SYSCTL_INT(_hw_usb, OID_AUTO, no_shutdown_wait, CTLFLAG_RW|CTLFLAG_TUN, +SYSCTL_INT(_hw_usb, OID_AUTO, no_shutdown_wait, CTLFLAG_RWTUN, &usb_no_shutdown_wait, 0, "No USB device waiting at system shutdown."); static devclass_t usb_devclass; @@ -138,6 +135,7 @@ DRIVER_MODULE(usbus, octusb, usb_driver, usb_devclass, 0, 0); /* Dual Mode Drivers */ DRIVER_MODULE(usbus, dwcotg, usb_driver, usb_devclass, 0, 0); +DRIVER_MODULE(usbus, saf1761otg, usb_driver, usb_devclass, 0, 0); /*------------------------------------------------------------------------* * usb_probe @@ -332,7 +330,7 @@ usb_shutdown(device_t dev) return (0); } - device_printf(bus->bdev, "Controller shutdown\n"); + DPRINTF("%s: Controller shutdown\n", device_get_nameunit(bus->bdev)); USB_BUS_LOCK(bus); usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus), @@ -344,7 +342,8 @@ usb_shutdown(device_t dev) } USB_BUS_UNLOCK(bus); - device_printf(bus->bdev, "Controller shutdown complete\n"); + DPRINTF("%s: Controller shutdown complete\n", + device_get_nameunit(bus->bdev)); return (0); } @@ -366,7 +365,13 @@ usb_bus_explore(struct usb_proc_msg *pm) if (bus->no_explore != 0) return; - if (udev && udev->hub) { + if (udev != NULL) { + USB_BUS_UNLOCK(bus); + uhub_explore_handle_re_enumerate(udev); + USB_BUS_LOCK(bus); + } + + if (udev != NULL && udev->hub != NULL) { if (bus->do_probe) { bus->do_probe = 0; @@ -898,7 +903,10 @@ usb_bus_mem_alloc_all(struct usb_bus *bus, bus_dma_tag_t dmat, bus->alloc_failed = 0; mtx_init(&bus->bus_mtx, device_get_nameunit(bus->parent), - NULL, MTX_DEF | MTX_RECURSE); + "usb_def_mtx", MTX_DEF | MTX_RECURSE); + + mtx_init(&bus->bus_spin_lock, device_get_nameunit(bus->parent), + "usb_spin_mtx", MTX_SPIN | MTX_RECURSE); usb_callout_init_mtx(&bus->power_wdog, &bus->bus_mtx, 0); @@ -953,6 +961,7 @@ usb_bus_mem_free_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb) #endif mtx_destroy(&bus->bus_mtx); + mtx_destroy(&bus->bus_spin_lock); } /* convenience wrappers */ diff --git a/sys/dev/usb/controller/uss820dci.c b/sys/dev/usb/controller/uss820dci.c index ba777115cc36a..9e10000030387 100644 --- a/sys/dev/usb/controller/uss820dci.c +++ b/sys/dev/usb/controller/uss820dci.c @@ -875,7 +875,8 @@ uss820dci_setup_standard_chain(struct usb_xfer *xfer) temp.td = NULL; temp.td_next = xfer->td_start[0]; temp.offset = 0; - temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.setup_alt_next = xfer->flags_int.short_frames_ok || + xfer->flags_int.isochronous_xfr; temp.did_stall = !xfer->flags_int.control_stall; sc = USS820_DCI_BUS2SC(xfer->xroot->bus); @@ -1120,7 +1121,8 @@ uss820dci_standard_done_sub(struct usb_xfer *xfer) } /* Check for short transfer */ if (len > 0) { - if (xfer->flags_int.short_frames_ok) { + if (xfer->flags_int.short_frames_ok || + xfer->flags_int.isochronous_xfr) { /* follow alt next */ if (td->alt_next) { td = td->obj_next; @@ -1543,7 +1545,7 @@ uss820dci_do_poll(struct usb_bus *bus) } /*------------------------------------------------------------------------* - * at91dci bulk support + * uss820dci bulk support *------------------------------------------------------------------------*/ static void uss820dci_device_bulk_open(struct usb_xfer *xfer) @@ -1580,7 +1582,7 @@ static const struct usb_pipe_methods uss820dci_device_bulk_methods = }; /*------------------------------------------------------------------------* - * at91dci control support + * uss820dci control support *------------------------------------------------------------------------*/ static void uss820dci_device_ctrl_open(struct usb_xfer *xfer) @@ -1617,7 +1619,7 @@ static const struct usb_pipe_methods uss820dci_device_ctrl_methods = }; /*------------------------------------------------------------------------* - * at91dci interrupt support + * uss820dci interrupt support *------------------------------------------------------------------------*/ static void uss820dci_device_intr_open(struct usb_xfer *xfer) @@ -1654,7 +1656,7 @@ static const struct usb_pipe_methods uss820dci_device_intr_methods = }; /*------------------------------------------------------------------------* - * at91dci full speed isochronous support + * uss820dci full speed isochronous support *------------------------------------------------------------------------*/ static void uss820dci_device_isoc_fs_open(struct usb_xfer *xfer) @@ -1736,7 +1738,7 @@ static const struct usb_pipe_methods uss820dci_device_isoc_fs_methods = }; /*------------------------------------------------------------------------* - * at91dci root control support + * uss820dci root control support *------------------------------------------------------------------------* * Simulate a hardware HUB by handling all the necessary requests. *------------------------------------------------------------------------*/ diff --git a/sys/dev/usb/controller/xhci.c b/sys/dev/usb/controller/xhci.c index 5092cc962727e..5e9029c5abe9e 100644 --- a/sys/dev/usb/controller/xhci.c +++ b/sys/dev/usb/controller/xhci.c @@ -90,24 +90,20 @@ static SYSCTL_NODE(_hw_usb, OID_AUTO, xhci, CTLFLAG_RW, 0, "USB XHCI"); static int xhcistreams; -SYSCTL_INT(_hw_usb_xhci, OID_AUTO, streams, CTLFLAG_RW | CTLFLAG_TUN, +SYSCTL_INT(_hw_usb_xhci, OID_AUTO, streams, CTLFLAG_RWTUN, &xhcistreams, 0, "Set to enable streams mode support"); -TUNABLE_INT("hw.usb.xhci.streams", &xhcistreams); #ifdef USB_DEBUG static int xhcidebug; static int xhciroute; static int xhcipolling; -SYSCTL_INT(_hw_usb_xhci, OID_AUTO, debug, CTLFLAG_RW | CTLFLAG_TUN, +SYSCTL_INT(_hw_usb_xhci, OID_AUTO, debug, CTLFLAG_RWTUN, &xhcidebug, 0, "Debug level"); -TUNABLE_INT("hw.usb.xhci.debug", &xhcidebug); -SYSCTL_INT(_hw_usb_xhci, OID_AUTO, xhci_port_route, CTLFLAG_RW | CTLFLAG_TUN, +SYSCTL_INT(_hw_usb_xhci, OID_AUTO, xhci_port_route, CTLFLAG_RWTUN, &xhciroute, 0, "Routing bitmap for switching EHCI ports to XHCI controller"); -TUNABLE_INT("hw.usb.xhci.xhci_port_route", &xhciroute); -SYSCTL_INT(_hw_usb_xhci, OID_AUTO, use_polling, CTLFLAG_RW | CTLFLAG_TUN, +SYSCTL_INT(_hw_usb_xhci, OID_AUTO, use_polling, CTLFLAG_RWTUN, &xhcipolling, 0, "Set to enable software interrupt polling for XHCI controller"); -TUNABLE_INT("hw.usb.xhci.use_polling", &xhcipolling); #else #define xhciroute 0 #endif @@ -1849,31 +1845,25 @@ restart: XHCI_TRB_3_ISO_SIA_BIT; } if (temp->direction == UE_DIR_IN) - dword |= XHCI_TRB_3_DIR_IN | XHCI_TRB_3_ISP_BIT; + dword |= XHCI_TRB_3_ISP_BIT; break; case XHCI_TRB_TYPE_DATA_STAGE: dword = XHCI_TRB_3_CHAIN_BIT | XHCI_TRB_3_CYCLE_BIT | - XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_DATA_STAGE) | - XHCI_TRB_3_TBC_SET(temp->tbc) | - XHCI_TRB_3_TLBPC_SET(temp->tlbpc); + XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_DATA_STAGE); if (temp->direction == UE_DIR_IN) dword |= XHCI_TRB_3_DIR_IN | XHCI_TRB_3_ISP_BIT; break; case XHCI_TRB_TYPE_STATUS_STAGE: dword = XHCI_TRB_3_CHAIN_BIT | XHCI_TRB_3_CYCLE_BIT | - XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_STATUS_STAGE) | - XHCI_TRB_3_TBC_SET(temp->tbc) | - XHCI_TRB_3_TLBPC_SET(temp->tlbpc); + XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_STATUS_STAGE); if (temp->direction == UE_DIR_IN) dword |= XHCI_TRB_3_DIR_IN; break; default: /* XHCI_TRB_TYPE_NORMAL */ dword = XHCI_TRB_3_CHAIN_BIT | XHCI_TRB_3_CYCLE_BIT | - XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_NORMAL) | - XHCI_TRB_3_TBC_SET(temp->tbc) | - XHCI_TRB_3_TLBPC_SET(temp->tlbpc); + XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_NORMAL); if (temp->direction == UE_DIR_IN) - dword |= XHCI_TRB_3_DIR_IN | XHCI_TRB_3_ISP_BIT; + dword |= XHCI_TRB_3_ISP_BIT; break; } td->td_trb[x].dwTrb3 = htole32(dword); @@ -2688,24 +2678,23 @@ xhci_alloc_device_ext(struct usb_device *udev) goto error; } - pc = &sc->sc_hw.devs[index].endpoint_pc; - pg = &sc->sc_hw.devs[index].endpoint_pg; + /* initialise all endpoint LINK TRBs */ - /* need to initialize the page cache */ - pc->tag_parent = sc->sc_bus.dma_parent_tag; + for (i = 0; i != XHCI_MAX_ENDPOINTS; i++) { - if (usb_pc_alloc_mem(pc, pg, - sizeof(struct xhci_dev_endpoint_trbs), XHCI_PAGE_SIZE)) { - goto error; - } + pc = &sc->sc_hw.devs[index].endpoint_pc[i]; + pg = &sc->sc_hw.devs[index].endpoint_pg[i]; - /* initialise all endpoint LINK TRBs */ + /* need to initialize the page cache */ + pc->tag_parent = sc->sc_bus.dma_parent_tag; - for (i = 0; i != XHCI_MAX_ENDPOINTS; i++) { + if (usb_pc_alloc_mem(pc, pg, + sizeof(struct xhci_dev_endpoint_trbs), XHCI_TRB_ALIGN)) { + goto error; + } /* lookup endpoint TRB ring */ - usbd_get_page(pc, (uintptr_t)& - ((struct xhci_dev_endpoint_trbs *)0)->trb[i][0], &buf_ep); + usbd_get_page(pc, 0, &buf_ep); /* get TRB pointer */ trb = buf_ep.buffer; @@ -2719,9 +2708,9 @@ xhci_alloc_device_ext(struct usb_device *udev) trb->dwTrb2 = htole32(XHCI_TRB_2_IRQ_SET(0)); trb->dwTrb3 = htole32(XHCI_TRB_3_CYCLE_BIT | XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK)); - } - usb_pc_cpu_flush(pc); + usb_pc_cpu_flush(pc); + } xhci_set_slot_pointer(sc, index, buf_dev.physaddr); @@ -2738,13 +2727,15 @@ xhci_free_device_ext(struct usb_device *udev) { struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); uint8_t index; + uint8_t i; index = udev->controller_slot_id; xhci_set_slot_pointer(sc, index, 0); usb_pc_free_mem(&sc->sc_hw.devs[index].device_pc); usb_pc_free_mem(&sc->sc_hw.devs[index].input_pc); - usb_pc_free_mem(&sc->sc_hw.devs[index].endpoint_pc); + for (i = 0; i != XHCI_MAX_ENDPOINTS; i++) + usb_pc_free_mem(&sc->sc_hw.devs[index].endpoint_pc[i]); } static struct xhci_endpoint_ext * @@ -2765,10 +2756,9 @@ xhci_get_endpoint_ext(struct usb_device *udev, struct usb_endpoint_descriptor *e index = udev->controller_slot_id; - pc = &sc->sc_hw.devs[index].endpoint_pc; + pc = &sc->sc_hw.devs[index].endpoint_pc[epno]; - usbd_get_page(pc, (uintptr_t)&((struct xhci_dev_endpoint_trbs *)0)-> - trb[epno][0], &buf_ep); + usbd_get_page(pc, 0, &buf_ep); pepext = &sc->sc_hw.devs[index].endp[epno]; pepext->page_cache = pc; diff --git a/sys/dev/usb/controller/xhci.h b/sys/dev/usb/controller/xhci.h index b000e4f11dd6c..408b429dff0ab 100644 --- a/sys/dev/usb/controller/xhci.h +++ b/sys/dev/usb/controller/xhci.h @@ -316,8 +316,8 @@ struct xhci_trb { } __aligned(4); struct xhci_dev_endpoint_trbs { - struct xhci_trb trb[XHCI_MAX_ENDPOINTS] - [(XHCI_MAX_STREAMS * XHCI_MAX_TRANSFERS) + XHCI_MAX_STREAMS]; + struct xhci_trb trb[(XHCI_MAX_STREAMS * + XHCI_MAX_TRANSFERS) + XHCI_MAX_STREAMS]; }; #define XHCI_TD_PAGE_NBUF 17 /* units, room enough for 64Kbytes */ @@ -385,11 +385,11 @@ enum { struct xhci_hw_dev { struct usb_page_cache device_pc; struct usb_page_cache input_pc; - struct usb_page_cache endpoint_pc; + struct usb_page_cache endpoint_pc[XHCI_MAX_ENDPOINTS]; struct usb_page device_pg; struct usb_page input_pg; - struct usb_page endpoint_pg; + struct usb_page endpoint_pg[XHCI_MAX_ENDPOINTS]; struct xhci_endpoint_ext endp[XHCI_MAX_ENDPOINTS]; diff --git a/sys/dev/usb/controller/xhci_pci.c b/sys/dev/usb/controller/xhci_pci.c index dc5a6e9dd445a..f0b789a3d66b2 100644 --- a/sys/dev/usb/controller/xhci_pci.c +++ b/sys/dev/usb/controller/xhci_pci.c @@ -150,6 +150,8 @@ static int xhci_pci_port_route(device_t self, uint32_t set, uint32_t clear) { uint32_t temp; + uint32_t usb3_mask; + uint32_t usb2_mask; temp = pci_read_config(self, PCI_XHCI_INTEL_USB3_PSSEN, 4) | pci_read_config(self, PCI_XHCI_INTEL_XUSB2PR, 4); @@ -157,8 +159,12 @@ xhci_pci_port_route(device_t self, uint32_t set, uint32_t clear) temp |= set; temp &= ~clear; - pci_write_config(self, PCI_XHCI_INTEL_USB3_PSSEN, temp, 4); - pci_write_config(self, PCI_XHCI_INTEL_XUSB2PR, temp, 4); + /* Don't set bits which the hardware doesn't support */ + usb3_mask = pci_read_config(self, PCI_XHCI_INTEL_USB3PRM, 4); + usb2_mask = pci_read_config(self, PCI_XHCI_INTEL_USB2PRM, 4); + + pci_write_config(self, PCI_XHCI_INTEL_USB3_PSSEN, temp & usb3_mask, 4); + pci_write_config(self, PCI_XHCI_INTEL_XUSB2PR, temp & usb2_mask, 4); device_printf(self, "Port routing mask set to 0x%08x\n", temp); diff --git a/sys/dev/usb/controller/xhcireg.h b/sys/dev/usb/controller/xhcireg.h index bd1d635cff778..a0b7397110811 100644 --- a/sys/dev/usb/controller/xhcireg.h +++ b/sys/dev/usb/controller/xhcireg.h @@ -35,7 +35,9 @@ #define PCI_XHCI_FLADJ 0x61 /* RW frame length adjust */ #define PCI_XHCI_INTEL_XUSB2PR 0xD0 /* Intel USB2 Port Routing */ +#define PCI_XHCI_INTEL_USB2PRM 0xD4 /* Intel USB2 Port Routing Mask */ #define PCI_XHCI_INTEL_USB3_PSSEN 0xD8 /* Intel USB3 Port SuperSpeed Enable */ +#define PCI_XHCI_INTEL_USB3PRM 0xDC /* Intel USB3 Port Routing Mask */ /* XHCI capability registers */ #define XHCI_CAPLENGTH 0x00 /* RO capability */ |
