#include __FBSDID("$FreeBSD$"); /*- * Copyright (c) 2010,2011 Aleksandr Rybalko. All rights reserved. * Copyright (c) 2010 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. */ /* * This file contains the driver for DWC like USB OTG Controller. */ /* TODO: * FIX BULK OUT, work fine with 1k blocks, but after xfer (sometime with 2k/4k, everytime 8k) don`t answer to USBC packets * Implement SPLIT (Required if used High speed HUB and Low or Full speed device on it) * Make ZERO_COPY_SOCKET like (speedup) * Implement and Test support for ISOC endpoints * Implement Device mode */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* We need to know if it CPU_CNMIPS */ #include "opt_global.h" #include #include #undef USB_DEBUG #define USB_DEBUG_VAR dotgdebug #include #include #include #include #include #include #include #include #include #include #include #include #include #define DOTG_BUS2SC(bus) \ ((struct dotg_softc *)(((uint8_t *)(bus)) - \ ((uint8_t *)&(((struct dotg_softc *)0)->sc_bus)))) #ifdef USB_DEBUG static int dotgdebug = 0; SYSCTL_NODE(_hw_usb, OID_AUTO, dotg, CTLFLAG_RW, 0, "DOTG"); SYSCTL_INT(_hw_usb_dotg, OID_AUTO, debug, CTLFLAG_RW, &dotgdebug, 0, "DOTG debug level"); TUNABLE_INT("hw.usb.dotg.debug", &dotgdebug); #endif struct dotg_std_temp { dotg_cmd_t *func; struct dotg_td *td; struct dotg_td *td_next; struct usb_page_cache *pc; uint32_t offset; uint32_t len; uint8_t short_pkt; uint8_t setup_alt_next; uint8_t channel; }; extern struct usb_bus_methods dotg_bus_methods; extern struct usb_pipe_methods dotg_device_bulk_methods; extern struct usb_pipe_methods dotg_device_ctrl_methods; extern struct usb_pipe_methods dotg_device_intr_methods; extern struct usb_pipe_methods dotg_device_isoc_methods; static void dotg_standard_done(struct usb_xfer *); static void dotg_device_done(struct usb_xfer *, usb_error_t); static void dotg_timeout(void *); static void dotg_do_poll(struct usb_bus *); static void dotg_complete_cb(dotg_complete_t, int, struct dotg_td *); #ifdef CPU_CNMIPS #define WRITEDMA(sc, channel, val) \ { \ bus_space_write_8(sc->sc_bst, 0x00016F0000000000ull, 818+(channel)*8, htole64(val)); \ bus_space_write_8(sc->sc_bst, 0x00016F0000000000ull, 858+(channel)*8, htole64(val)); \ printf("Set dma to %p for channel %d\n", (val), (channel)); \ } #define READDMA(sc, channel) \ htole64(bus_space_read_8(sc->sc_bst, 0x00016F0000000000ull, 818+(channel)*8)) #else /* defined(__mips_n64) */ #define WRITEDMA(sc, channel, val) \ { bus_space_write_4(sc->sc_bst, sc->sc_bsh, DOTG_HCDMA(channel), htole32(val)); } #define READDMA(sc, offset) \ le32toh(bus_space_read_4(sc->sc_bst, sc->sc_bsh, DOTG_HCDMA(channel))) #endif /* defined(__mips_n64) */ #define WRITE4(sc, offset, val) \ bus_space_write_4(sc->sc_bst, sc->sc_bsh, (offset), (val)) #define READ4(sc, offset) \ (bus_space_read_4(sc->sc_bst, sc->sc_bsh, (offset))) static inline int dotg_allocate_channel(struct dotg_softc *sc) { uint32_t old, new; int channel; do { cpu_dcache_inv_range((vm_offset_t)sc, sizeof(sc)); old = sc->idle_hardware_channels; // printf("%s: old=%08x\n", __func__, old); new = 0; for (channel = 0; channel < sc->channels; channel++) if (old & (1 << channel)) { new = old ^ (1 << channel); break; } if (channel == sc->channels) { device_printf(sc->sc_dev, "All channels busy\n"); return (-1); } } while (!atomic_cmpset_32(&sc->idle_hardware_channels, old, new)); // printf("%s: updated=%08x\n", __func__, sc->idle_hardware_channels); cpu_dcache_wb_range((vm_offset_t)sc, sizeof(sc)); return (channel); } static inline void dotg_free_channel(struct dotg_softc *sc, int channel) { uint32_t old, new; do { cpu_dcache_inv_range((vm_offset_t)sc, sizeof(sc)); old = sc->idle_hardware_channels; // printf("%s: old=%08x\n", __func__, old); if (old & (1 << channel)) /* Already idle */ return; new = sc->idle_hardware_channels | (1 << channel); } while (!atomic_cmpset_32(&sc->idle_hardware_channels, old, new)); // printf("%s: updated=%08x\n", __func__, sc->idle_hardware_channels); cpu_dcache_wb_range((vm_offset_t)sc, sizeof(sc)); } static inline int needs_split(struct dotg_softc * sc, struct dotg_td *td) { return ((td->qh->dev_speed != USB_SPEED_HIGH) && (GETFLD(sc->dotg_hprt, HPRT_PRTSPD) == HPRT_PRTSPD_HIGH)); } static inline int get_data_pid(struct dotg_td *td) { return ((td->qh->ep_toggle_next)?HCTSIZ_PID_DATA1:HCTSIZ_PID_DATA0); } #if 0 static int dotg_core_reset(struct dotg_softc * sc) { DPRINTF("\n"); /* Wait for AHB IDLE */ if (WAIT_FOR_FIELD32(sc, DOTG_GRSTCTL, GRSTCTL_AHBIDLE, 1, 100000)) { DPRINTF("AHB not Idle after 100ms\n"); return (1); } /* Core Soft Reset */ SETFIELD32(sc, DOTG_GRSTCTL, GRSTCTL_CSFTRST, 1); if (WAIT_FOR_FIELD32(sc, DOTG_GRSTCTL, GRSTCTL_CSFTRST, 0, 100000)) { DPRINTF("Soft Reset Failed\n"); return (1); } DELAY(100); return (0); } #endif int dotg_enable(struct dotg_softc * sc) { uint32_t ghwcfg3, tmp; sc->dotg_hprt = READ4(sc, DOTG_HPRT); if (sc->dotg_hprt & HPRT_PRTENA) return (0); /* Some devices don`t shown without delay here */ DELAY(100000); sc->dotg_hprt = READ4(sc, DOTG_HPRT); if (!(sc->dotg_hprt & HPRT_PRTCONNSTS)) { DPRINTF("Nothing plugged into the port\n"); return (1); } SETFIELD32(sc, DOTG_HPRT, HPRT_PRTRST, 1); DELAY(50000); SETFIELD32(sc, DOTG_HPRT, HPRT_PRTRST, 0); if (WAIT_FOR_FIELD32(sc, DOTG_HPRT, HPRT_PRTENA, 1, 100000)) { DPRINTF("Timeout waiting for the port to finish reset\n"); return (1); } sc->dotg_hprt = READ4(sc, DOTG_HPRT); DPRINTF("port is in %s speed mode\n", (GETFLD(sc->dotg_hprt, HPRT_PRTSPD) == HPRT_PRTSPD_HIGH) ? "high" : (GETFLD(sc->dotg_hprt, HPRT_PRTSPD) == HPRT_PRTSPD_FULL) ? "full" : "low"); /* NOT WORKING tmp = READ4(sc, DOTG_HCFG); tmp &= ~(HCFG_FSLSSUPP | HCFG_FSLSPCLKSEL_MASK); if (GETFLD(sc->dotg_hprt, HPRT_PRTSPD) == HPRT_PRTSPD_HIGH) { tmp &= ~HCFG_FSLSSUPP; tmp |= SETFLD(1, HCFG_FSLSPCLKSEL); } else { tmp |= HCFG_FSLSSUPP; tmp |= SETFLD(0, HCFG_FSLSPCLKSEL); } WRITE4(sc, DOTG_HCFG, tmp); */ /* NOT WORKING tmp = READ4(sc, DOTG_GUSBCFG); tmp &= ~GUSBCFG_TOUTCAL_MASK; if (GETFLD(sc->dotg_hprt, HPRT_PRTSPD) != HPRT_PRTSPD_HIGH) { tmp |= SETFLD(0x7, GUSBCFG_TOUTCAL); } WRITE4(sc, DOTG_GUSBCFG, tmp); */ ghwcfg3 = READ4(sc, DOTG_GHWCFG3); SETFIELD32_(sc, DOTG_GRXFSIZ, GRXFSIZ_RXFDEP, GETFLD(ghwcfg3, GHWCFG3_DFIFODEPTH) / 4); tmp = (GETFLD(ghwcfg3, GHWCFG3_DFIFODEPTH) / 2) << GNPTXFSIZ_NPTXFDEP_SHIFT; tmp |= (GETFLD(ghwcfg3, GHWCFG3_DFIFODEPTH) / 4) << GNPTXFSIZ_NPTXFSTADDR_SHIFT; WRITE4(sc, DOTG_GNPTXFSIZ, tmp); tmp = (GETFLD(ghwcfg3, GHWCFG3_DFIFODEPTH) / 4) << HPTXFSIZ_PTXFSIZE_SHIFT; tmp |= 3 * (GETFLD(ghwcfg3, GHWCFG3_DFIFODEPTH) / 4) << HPTXFSIZ_PTXFSTADDR_SHIFT; WRITE4(sc, DOTG_HPTXFSIZ, tmp); SETFIELD32_(sc, DOTG_GRSTCTL, GRSTCTL_TXFNUM, 0x10); SETFIELD32(sc, DOTG_GRSTCTL, GRSTCTL_TXFFLSH, 1); WAIT_FOR_FIELD32(sc, DOTG_GRSTCTL, GRSTCTL_TXFFLSH, 0, 100); SETFIELD32(sc, DOTG_GRSTCTL, GRSTCTL_RXFFLSH, 1); WAIT_FOR_FIELD32(sc, DOTG_GRSTCTL, GRSTCTL_RXFFLSH, 0, 100); return (0); } #ifdef USB_DEBUG static void dump_hcchar(uint32_t reg) { printf("\tHCCHAR 0x%08x %s%s%s DEVADDR=%x MC_EC=%x EPTYPE=%x %s %s EPNUM=%x MPS=%x\n", reg, ((reg & HCCHAR_CHENA)?"ENA ":"DIS "), ((reg & HCCHAR_CHDIS)?"disable in progress ":""), ((reg & HCCHAR_ODDFRM)?"send on ODD frame":"send on EVN frame"), GETFLD(reg, HCCHAR_DEVADDR), GETFLD(reg, HCCHAR_MC_EC), GETFLD(reg, HCCHAR_EPTYPE), ((reg & HCCHAR_LSPDDEV)?"LSPDDEV ":"F/HSPDDEV "), ((reg & HCCHAR_EPDIR)?"DIR_IN":"DIR_OUT"), GETFLD(reg, HCCHAR_EPNUM), GETFLD(reg, HCCHAR_MPS)); } static void dump_hcsplt(uint32_t reg) { printf("\tHCSPLT 0x%08x %s%s XACTPOS=%x HUBADDR=%x PRTADDR=%x\n", reg, ((reg & HCSPLT_SPLTENA)?"SPLTENA ":""), ((reg & HCSPLT_COMPSPLT)?"COMPSPLT ":""), GETFLD(reg, HCSPLT_XACTPOS), GETFLD(reg, HCSPLT_HUBADDR), GETFLD(reg, HCSPLT_PRTADDR)); } static void dump_hcint(uint32_t reg) { printf("\tHCINT 0x%08x %s %s %s %s %s %s %s %s %s %s %s\n", reg, ((reg & HCINT_DATATGLERR )?"DATATGLERR":""), ((reg & HCINT_FRMOVRUN )?"FRMOVRUN":""), ((reg & HCINT_BBLERR )?"BBLERR":""), ((reg & HCINT_XACTERR )?"XACTERR":""), ((reg & HCINT_NYET )?"NYET":""), ((reg & HCINT_ACK )?"ACK":""), ((reg & HCINT_NAK )?"NAK":""), ((reg & HCINT_STALL )?"STALL":""), ((reg & HCINT_AHBERR )?"AHBERR":""), ((reg & HCINT_CHHLTD )?"CHHLTD":""), ((reg & HCINT_XFERCOMPL )?"XFERCOMPL":"")); } static void dump_hcintmsk(uint32_t reg) { printf("\tHCINTMSK 0x%08x %s %s %s %s %s %s %s %s %s %s %s\n", reg, ((reg & HCINTMSK_DATATGLERRMSK)?"DATATGLERRMSK":""), ((reg & HCINTMSK_FRMOVRUNMSK)?"FRMOVRUNMSK":""), ((reg & HCINTMSK_BBLERRMSK )?"BBLERRMSK":""), ((reg & HCINTMSK_XACTERRMSK )?"XACTERRMSK":""), ((reg & HCINTMSK_NYETMSK )?"NYETMSK":""), ((reg & HCINTMSK_ACKMSK )?"ACKMSK":""), ((reg & HCINTMSK_NAKMSK )?"NAKMSK":""), ((reg & HCINTMSK_STALLMSK )?"STALLMSK":""), ((reg & HCINTMSK_AHBERRMSK )?"AHBERRMSK":""), ((reg & HCINTMSK_CHHLTDMSK )?"CHHLTDMSK":""), ((reg & HCINTMSK_XFERCOMPLMSK)?"XFERCOMPLMSK":"")); } static void dump_hctsiz(uint32_t reg) { printf("\tHCTSIZ 0x%08x %s ", reg, ((reg & HCTSIZ_DOPNG)?"DOPNG":"")); switch (GETFLD(reg, HCTSIZ_PID)) { case HCTSIZ_PID_DATA0: printf("DATA0"); break; case HCTSIZ_PID_DATA2: printf("DATA2"); break; case HCTSIZ_PID_DATA1: printf("DATA1"); break; case HCTSIZ_PID_MDATA: printf("MDATA/SETUP"); break; } printf(" PKTCNT=%x XFERSIZE=%x\n", GETFLD(reg, HCTSIZ_PKTCNT), GETFLD(reg, HCTSIZ_XFERSIZE)); } static void dump_channel_regs(struct dotg_softc * sc, int channel) { printf("\tchannel=%d\n", channel); dump_hcchar (READ4(sc, DOTG_HCCHAR (channel))); dump_hcsplt (READ4(sc, DOTG_HCSPLT (channel))); dump_hctsiz (READ4(sc, DOTG_HCTSIZ (channel))); printf("\tHCDMA=0x%p\n", (int *)READDMA(sc, channel)); return; dump_hcint (READ4(sc, DOTG_HCINT (channel))); dump_hcintmsk (READ4(sc, DOTG_HCINTMSK (channel))); } #endif static int poll_channel(struct dotg_softc * sc, int channel) { uint32_t hcint; uint32_t hctsiz; uint32_t hcchar; int bytes_this_transfer; int packets_processed; struct dotg_td *td; hcint = READ4(sc, DOTG_HCINT(channel)); WRITE4(sc, DOTG_HCINT(channel), hcint); if (sc->idle_hardware_channels & (1 << channel)) { printf("Channel %d not allocated\n", channel); return (0); } #ifdef USB_DEBUG if (hcint & HCINT_AHBERR) { DPRINTF("\nAHB ERROR: \n"); dump_channel_regs(sc, channel); } #endif hcchar = READ4(sc, DOTG_HCCHAR(channel)); hctsiz = READ4(sc, DOTG_HCTSIZ(channel)); if (sc->td_for_channel[channel]) { td = sc->td_for_channel[channel]; /* Free channel */ DPRINTF("idle=%02x\n", sc->idle_hardware_channels); sc->td_for_channel[channel] = 0; dotg_free_channel(sc, channel); DPRINTF("Release hardware channel %d. idle=%02x\n", channel, sc->idle_hardware_channels); } else { /* No transfer for channel */ printf("No transfer for channel %d\n", channel); #ifdef USB_DEBUG if (dotgdebug) { dump_channel_regs(sc, channel); dump_hcint(hcint); } #endif return (0); } DPRINTF("channel %d halted td=%p qh=%p\n", channel, td, td->qh); DPRINTF("channel=%0d xfersize=0x%08x\n", channel, td->qh->this_xfersize); #ifdef USB_DEBUG if (dotgdebug) { dump_channel_regs(sc, channel); dump_hcint(hcint); } #endif /* Find trasfered sizes */ packets_processed = GETFLD(hctsiz, HCTSIZ_PKTCNT); if ((hcchar & HCCHAR_EPDIR) == HCCHAR_EPDIR_IN) { if (td->qh->this_xfersize) { bytes_this_transfer = (td->qh->this_xfersize - GETFLD(hctsiz, HCTSIZ_XFERSIZE)); } else bytes_this_transfer = td->remainder - ((GETFLD(hctsiz, HCTSIZ_XFERSIZE) > 0)? GETFLD(hctsiz, HCTSIZ_XFERSIZE):0); } else { bytes_this_transfer = packets_processed * GETFLD(hcchar, HCCHAR_MPS); if (bytes_this_transfer > td->remainder) bytes_this_transfer = td->remainder; } if (td->qh->dev_speed == USB_SPEED_HIGH) { if ((td->qh->flags & __DOTG_FLAGS_NEED_PING) & (hcint & HCINT_ACK) & !(hcint & HCINT_NAK)) td->qh->flags &= ~__DOTG_FLAGS_NEED_PING; } if (hcint & HCINT_STALL) { td->qh->ep_toggle_next = 0; dotg_complete_cb(DOTG_COMPLETE_STALL, 0, td); } else if (hcint & HCINT_XACTERR) { if ((hcint & HCINT_NAK) || (hcint & HCINT_ACK)) td->qh->retries = 0; td->qh->retries++; if (td->qh->retries > MAX_RETRIES) { dotg_complete_cb(DOTG_COMPLETE_XACTERR, 0, td); } else { td->qh->split_sc_frame = -1; td->qh->next_tx_cycle = cpu_ticks() + /* +ep_interval in ms */ td->qh->ep_interval*(cpu_tickrate()/1000); } } else if (hcint & HCINT_BBLERR) { dotg_complete_cb(DOTG_COMPLETE_BABBLEERR, 0, td); } else if (hcint & HCINT_FRMOVRUN) { td->qh->split_sc_frame = -1; WRITE4(sc, DOTG_HCCHAR(channel), hcchar | HCCHAR_CHDIS); dotg_complete_cb(DOTG_COMPLETE_FRAMEERR, 0, td); } else if ((hcint & HCINT_ACK) || (hcint & HCINT_NYET)) { td->qh->retries = 0; if (td->qh->dev_speed == USB_SPEED_HIGH) { td->qh->flags &= ~__DOTG_FLAGS_NEED_PING; td->qh->flags |= __DOTG_FLAGS_DONE_PING; } switch (td->qh->ep_type) { case UE_CONTROL: if ((!td->qh->ep_toggle_next) && (GETFLD(hctsiz, HCTSIZ_PID) == 0)) td->qh->ep_toggle_next = 1; else if ((td->qh->ep_toggle_next) && (GETFLD(hctsiz, HCTSIZ_PID) != 0)) td->qh->ep_toggle_next = 0; dotg_complete_cb(DOTG_COMPLETE_SUCCESS, bytes_this_transfer, td); break; case UE_BULK: case UE_INTERRUPT: if ((!td->qh->ep_toggle_next) && (GETFLD(hctsiz, HCTSIZ_PID) != 0)) td->qh->ep_toggle_next = 1; else if ((td->qh->ep_toggle_next) && (GETFLD(hctsiz, HCTSIZ_PID) == 0)) td->qh->ep_toggle_next = 0; td->qh->next_tx_cycle += td->qh->ep_interval; dotg_complete_cb(DOTG_COMPLETE_SUCCESS, bytes_this_transfer, td); break; case UE_ISOCHRONOUS: td->qh->next_tx_cycle += td->qh->ep_interval; dotg_complete_cb(DOTG_COMPLETE_SUCCESS, bytes_this_transfer, td); break; } } else if (hcint & HCINT_NAK) { uint64_t ipd_clk_count; if ((td->qh->ep_type == UE_INTERRUPT) && (td->qh->retries > 7)) { dotg_complete_cb(DOTG_COMPLETE_SUCCESS, 0, td); return (0); } DPRINTF("NAK, td->qh->retries=%d\n",td->qh->retries); td->qh->fixup_state = FIXUP_NAK; td->qh->retries ++; ipd_clk_count = cpu_ticks(); td->qh->next_tx_cycle = ipd_clk_count + td->qh->ep_interval; } else { dotg_complete_cb(DOTG_COMPLETE_ERROR, 0, td); } return (0); } static uint8_t dotg_host_alloc_endpoint(struct dotg_td *td) { struct dotg_softc *sc; if (td->qh->fixup_state == FIXUP_PEND) return (1); /* busy */ if (td->qh->fixup_state > FIXUP_NONE) return (0); /* success */ /* get softc */ sc = td->qh->sc; td->qh->fixup_len = 0; td->qh->fixup_off = 0; td->qh->fixup_actlen = 0; td->qh->retries = 0; td->qh->this_xfersize = 0; td->qh->fixup_state = FIXUP_ALOC; return (0); /* success */ } static void dotg_host_free_endpoint(struct dotg_td *td) { struct dotg_softc *sc = td->qh->sc; uint32_t hcchar; if (td->qh->fixup_state == FIXUP_NONE) return; if (td->channel >= 0) { /* cancel, if any */ hcchar = READ4(sc, DOTG_HCCHAR(td->channel)); if (hcchar & HCCHAR_CHENA) WRITE4(sc, DOTG_HCCHAR(td->channel), hcchar | HCCHAR_CHDIS); td->channel = -1; } td->qh->fixup_state = FIXUP_NONE; } static void dotg_complete_cb(dotg_complete_t status, int bytes, struct dotg_td *td) { td->qh->fixup_state = FIXUP_CMPL; td->qh->fixup_actlen = bytes; switch (status) { case DOTG_COMPLETE_SUCCESS: case DOTG_COMPLETE_SHORT: td->error_any = 0; td->error_stall = 0; break; case DOTG_COMPLETE_STALL: td->error_stall = 1; td->error_any = 1; break; default: td->error_any = 1; break; } } static int configure_channel(struct dotg_td *td, int xfersize, int pid, int dirin) { /* get softc */ struct dotg_softc *sc = td->qh->sc; uint32_t hcsplt = 0, hctsiz = 0, hcchar = 0, hcint; int channel = -1; DPRINTF("idle=%02x\n", sc->idle_hardware_channels); channel = dotg_allocate_channel(sc); if (channel < 0) return (EIO); sc->td_for_channel[channel] = td; td->channel = channel; DPRINTF("Allocate hardware channel %d. idle=%02x\n", channel, sc->idle_hardware_channels); if (channel < 0 || channel > sc->channels) { device_printf(sc->sc_dev, "Wrong allocated channel=%d\n", channel); return (EIO); } /* Force clear channel */ SETFIELD32(sc, DOTG_HCCHAR(td->channel), HCCHAR_CHENA, 0); hcint = READ4(sc, DOTG_HCINT(td->channel)); WRITE4(sc, DOTG_HCINT(td->channel), hcint); td->qh->this_xfersize = xfersize; hctsiz |= SETFLD(xfersize, HCTSIZ_XFERSIZE); hctsiz |= SETFLD(((xfersize > td->qh->max_packet_size)? ((xfersize + td->qh->max_packet_size - 1) / td->qh->max_packet_size):1), HCTSIZ_PKTCNT); hctsiz |= SETFLD(pid, HCTSIZ_PID); DPRINTF("Use %s for ep %02x\n", (pid == HCTSIZ_PID_DATA0)?"DATA0": (pid == HCTSIZ_PID_DATA1)?"DATA1": (pid == HCTSIZ_PID_SETUP)?"SETUP": "DATA2", td->qh->ep_num ); if (td->qh->ep_mult < 1) hcchar |= SETFLD(1, HCCHAR_EC); else if (td->qh->ep_mult > 3) hcchar |= SETFLD(3, HCCHAR_EC); else hcchar |= SETFLD(td->qh->ep_mult, HCCHAR_EC); hcchar |= SETFLD(td->qh->dev_addr, HCCHAR_DEVADDR); hcchar |= SETFLD(td->qh->ep_type, HCCHAR_EPTYPE); hcchar |= (td->qh->dev_speed == USB_SPEED_LOW)?HCCHAR_LSPDDEV:0; hcchar |= dirin?HCCHAR_EPDIR_IN:HCCHAR_EPDIR_OUT; hcchar |= SETFLD(td->qh->ep_num & 0x0f, HCCHAR_EPNUM); hcchar |= SETFLD(td->qh->max_packet_size, HCCHAR_MPS); if (needs_split(sc, td)) { hcsplt |= HCSPLT_SPLTENA;// | HCSPLT_COMPSPLT; hcsplt |= SETFLD(td->qh->hs_hub_port, HCSPLT_PRTADDR); hcsplt |= SETFLD(td->qh->hs_hub_addr, HCSPLT_HUBADDR); hcsplt |= SETFLD(3, HCSPLT_XACTPOS); } WRITEDMA(sc, td->channel, (unsigned int *)(td->qh->fixup_phys + td->qh->fixup_off)); WRITE4(sc, DOTG_HAINTMSK, (READ4(sc, DOTG_HAINTMSK) | (1<channel))); WRITE4(sc, DOTG_HCINTMSK(td->channel), HCINTMSK_ANY); WRITE4(sc, DOTG_HCSPLT(td->channel), hcsplt); WRITE4(sc, DOTG_HCTSIZ(td->channel), hctsiz); WRITE4(sc, DOTG_HCCHAR(td->channel), hcchar); #ifdef USB_DEBUG if (dotgdebug) dump_channel_regs(sc, td->channel); #endif return (0); } static uint8_t dotg_host_control_header_tx(struct dotg_td *td) { struct dotg_softc *sc; if (dotg_host_alloc_endpoint(td)) return (1); /* busy */ DPRINTF("remainder=%d\n",td->remainder); /* check error */ if (td->error_any) return (0); /* done */ if (td->qh->fixup_state == FIXUP_CMPL) { /* clear complete flag */ td->qh->fixup_state = FIXUP_ALOC; /* flush data */ usb_pc_cpu_invalidate(td->qh->fixup_pc); return (0); /* done */ } /* verify length */ if (td->remainder != 8) { td->error_any = 1; return (0); /* done */ } usbd_copy_out(td->pc, td->offset, td->qh->fixup_buf, 8); /* update offset and remainder */ td->offset += 8; td->remainder -= 8; /* setup data length and offset */ td->qh->fixup_len = UGETW(td->qh->fixup_buf + 6); td->qh->fixup_off = 0; if (td->qh->fixup_len > (DOTG_MAX_FIXUP - 8)) { td->error_any = 1; return (0); /* done */ } /* get softc */ sc = td->qh->sc; int ret = configure_channel(td, 8, HCTSIZ_PID_SETUP, HCCHAR_EPDIR_OUT); if (ret != 0) return (1); uint32_t hcchar = READ4(sc, DOTG_HCCHAR(td->channel)); hcchar |= HCCHAR_CHENA; td->qh->fixup_state = FIXUP_PEND; usb_pc_cpu_flush(td->qh->fixup_pc); WRITE4(sc, DOTG_HCCHAR(td->channel), hcchar); return (1); /* busy */ } static uint8_t dotg_host_control_data_tx(struct dotg_td *td) { struct dotg_softc *sc = td->qh->sc; uint32_t rem; /* allocate endpoint and check pending */ if (dotg_host_alloc_endpoint(td)) return (1); /* busy */ /* check error */ if (td->error_any) return (0); /* done */ if (td->qh->fixup_state == FIXUP_CMPL) { rem = td->qh->fixup_len - td->qh->fixup_off; if (td->remainder > rem) { td->error_any = 1; DPRINTFN(1, "Excess setup transmit data\n"); return (0); /* done */ } return (0); } usbd_copy_out(td->pc, td->offset, td->qh->fixup_buf+8, td->remainder); /*---------------------------------------------------------------*/ DPRINTF("%d: Ctrl data tx\n", __LINE__); td->qh->fixup_off = 8; int ret = configure_channel(td, td->remainder, td->qh->ep_toggle_next?HCTSIZ_PID_DATA0:HCTSIZ_PID_DATA1, HCCHAR_EPDIR_OUT); if (ret != 0) return (1); uint32_t hcchar = READ4(sc, DOTG_HCCHAR(td->channel)); hcchar |= HCCHAR_CHENA; /*---------------------------------------------------------------*/ td->qh->fixup_state = FIXUP_PEND; td->offset += td->remainder; td->remainder = 0; usb_pc_cpu_flush(td->qh->fixup_pc); /*---------------------------------------------------------------*/ WRITE4(sc, DOTG_HCCHAR(td->channel), hcchar); /*---------------------------------------------------------------*/ return (1); /* done */ } static uint8_t dotg_host_control_data_rx(struct dotg_td *td) { struct dotg_softc *sc = td->qh->sc; uint32_t rem; /* allocate endpoint and check pending */ if (dotg_host_alloc_endpoint(td)) return (1); /* busy */ /* check error */ if (td->error_any) return (0); /* done */ if (td->qh->fixup_state == FIXUP_CMPL) { /* clear complete flag */ td->qh->fixup_state = FIXUP_ALOC; /* flush data */ usb_pc_cpu_invalidate(td->qh->fixup_pc); DPRINTF("fixup_actlen=%d, fixup_off=%d, td->offset=%d, td->remainder=%d\n", td->qh->fixup_actlen, td->qh->fixup_off, td->offset, td->remainder); /* copy data from buffer */ rem = td->qh->fixup_actlen; if (rem > td->remainder) rem = td->remainder; usbd_copy_in(td->pc, td->offset, td->qh->fixup_buf+8, rem); DPRINTF("Data RX\n"); #ifdef USB_DEBUG if (dotgdebug == 0x0fffffff) hexdump((uint8_t *)(td->qh->fixup_buf+8), rem, 0, 0); #endif td->offset += rem; td->qh->fixup_off += rem; td->remainder -= rem; return (0); /* done */ } td->qh->fixup_off = 8; int ret = configure_channel(td, td->remainder, td->qh->ep_toggle_next?HCTSIZ_PID_DATA0:HCTSIZ_PID_DATA1, HCCHAR_EPDIR_IN); if (ret != 0) return (1); uint32_t hcchar = READ4(sc, DOTG_HCCHAR(td->channel)); hcchar |= HCCHAR_CHENA; td->qh->fixup_state = FIXUP_PEND; usb_pc_cpu_flush(td->qh->fixup_pc); WRITE4(sc, DOTG_HCCHAR(td->channel), hcchar); return (1); } static uint8_t dotg_host_control_status_tx(struct dotg_td *td) { /* allocate endpoint and check pending */ if (dotg_host_alloc_endpoint(td)) return (1); /* busy */ /* check error */ if (td->error_any) return (0); /* done */ if (td->qh->fixup_state == FIXUP_CMPL) { /* clear complete flag */ td->qh->fixup_state = FIXUP_ALOC; DPRINTF("%d: control status TX:\n", __LINE__); #ifdef USB_DEBUG if (dotgdebug == 0x0fffffff) hexdump((uint8_t *)td->qh->fixup_buf, 8, 0, 0); #endif /* done */ return (0); } /* do control IN request */ if (!(td->qh->fixup_buf[0] & UE_DIR_IN)) { struct dotg_softc *sc; /* get softc */ sc = td->qh->sc; /* start USB transfer */ td->qh->fixup_off = 0; int ret = configure_channel(td, 0, td->qh->ep_toggle_next?HCTSIZ_PID_DATA0:HCTSIZ_PID_DATA1, HCCHAR_EPDIR_IN); if (ret != 0) return (1); uint32_t hcchar = READ4(sc, DOTG_HCCHAR(td->channel)); hcchar |= HCCHAR_CHENA; td->qh->fixup_state = FIXUP_PEND; usb_pc_cpu_flush(td->qh->fixup_pc); WRITE4(sc, DOTG_HCCHAR(td->channel), hcchar); return (1); /* busy */ } return (0); /* done */ } static uint8_t dotg_non_control_data_tx(struct dotg_td *td) { struct dotg_softc *sc; uint32_t rem = 0; if ( td->qh->fixup_state == FIXUP_NAK) { DPRINTF("%d: NAKed, retry\n", __LINE__); rem = td->qh->this_xfersize; goto dotg_non_control_data_tx_retry; } /* allocate endpoint and check pending */ if (dotg_host_alloc_endpoint(td)) return (1); /* busy */ /* check error */ if (td->error_any) return (0); /* done */ DPRINTF("td->qh->fixup_state = %d, td->short_pkt=%d, td->qh->dev_speed=%d == %d\n",td->qh->fixup_state, td->short_pkt, td->qh->dev_speed, USB_SPEED_HIGH); if (td->qh->fixup_state == FIXUP_CMPL) { if ((td->qh->ep_type & UE_XFERTYPE) == UE_ISOCHRONOUS) { td->qh->fixup_state = FIXUP_ALOC; DPRINTF("fixup_complete UE_ISOCHRONOUS\n"); return (0); /* done */ } rem = td->qh->this_xfersize; td->offset += rem; td->qh->fixup_off += rem; td->remainder -= rem; } /* check complete */ if (td->remainder == 0) { if (td->short_pkt) { DPRINTF("remainder == 0 && short_pkt\n"); return (0); /* complete */ } /* else need to send a zero length packet */ rem = 0; td->short_pkt = 1; return (0); /* complete */ } else { /* * XXX TODO: fix handling transfers 1024 < x < DOTG_MAX_FIXUP * "Small" devices says ACK & NYET in middle of 2048 BULK OUT */ #define DOTG_MAX_FIXUP_TX 1024 /* get maximum length */ rem = DOTG_MAX_FIXUP_TX % td->qh->max_frame_size; rem = DOTG_MAX_FIXUP_TX - rem; if (rem == 0) { /* should not happen */ DPRINTFN(1, "Fixup buffer is too small\n"); td->error_any = 1; return (0); /* done */ } /* get minimum length */ if (rem > td->remainder) { rem = td->remainder; if ((rem == 0) || (rem % td->qh->max_frame_size)) td->short_pkt = 1; } /* copy data into fixup buffer */ usbd_copy_out(td->pc, td->offset, td->qh->fixup_buf, rem); td->qh->fixup_off = 0; /* flush data */ usb_pc_cpu_flush(td->qh->fixup_pc); /* pre-increment TX buffer offset */ } dotg_non_control_data_tx_retry: /* get softc */ sc = td->qh->sc; if (td->qh->dev_speed == USB_SPEED_HIGH) { /* If USB2.0 BULK OUT */ DPRINTF("%d: td->qh->dev_speed == USB_SPEED_HIGH\n", __LINE__); td->qh->flags |= __DOTG_FLAGS_NEED_PING; td->qh->flags &= ~__DOTG_FLAGS_DONE_PING; } else { td->qh->flags &= ~(__DOTG_FLAGS_DONE_PING| __DOTG_FLAGS_NEED_PING); } /*---------------------------------------------------------------*/ DPRINTF("+++++++++++++++++ rem=%d\n", rem); DPRINTF("\n"); int ret = configure_channel(td, rem, td->qh->ep_toggle_next?HCTSIZ_PID_DATA1:HCTSIZ_PID_DATA0, HCCHAR_EPDIR_OUT); if (ret != 0) return (1); uint32_t hcchar = READ4(sc, DOTG_HCCHAR(td->channel)); uint32_t hctsiz = READ4(sc, DOTG_HCTSIZ(td->channel)); if (td->qh->dev_speed == USB_SPEED_HIGH) { if (td->qh->flags &__DOTG_FLAGS_NEED_PING) { DPRINTF("Add DOPNG flag\n"); hctsiz |= HCTSIZ_DOPNG; } } WRITE4(sc, DOTG_HCTSIZ(td->channel), hctsiz); hcchar &= ~HCCHAR_MC_EC_MASK; hcchar |= (!(GETFLD(READ4(sc, DOTG_HFNUM), HFNUM_FRNUM) & 1))?HCCHAR_ODDFRM:0; WRITE4(sc, DOTG_HCCHAR(td->channel), hcchar); hcchar |= HCCHAR_CHENA; td->qh->fixup_state = FIXUP_PEND; usb_pc_cpu_flush(td->qh->fixup_pc); WRITE4(sc, DOTG_HCCHAR(td->channel), hcchar); return (1); /* busy */ } static uint8_t dotg_non_control_data_rx(struct dotg_td *td) { struct dotg_softc *sc; uint32_t rem; uint8_t got_short; /* allocate endpoint and check pending */ if (dotg_host_alloc_endpoint(td)) return (1); /* busy */ if ( (((td->qh->ep_type & UE_XFERTYPE) == UE_ISOCHRONOUS) || ((td->qh->ep_type & UE_XFERTYPE) == UE_INTERRUPT)) && (td->qh->next_tx_cycle >= cpu_ticks())) { return (1); /* busy */ } /* check error */ if (td->error_any) return (0); /* done */ got_short = 0; if (td->qh->fixup_state == FIXUP_CMPL) { DPRINTF("%d: no errors, fixup_state == FIXUP_CMPL\n", __LINE__); /* invalidate data */ usb_pc_cpu_invalidate(td->qh->fixup_pc); /* verify transfer length */ if (td->qh->this_xfersize != td->qh->fixup_actlen) { if (td->qh->this_xfersize > td->qh->fixup_actlen) { /* 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 */ } } /* copy data into fixup buffer */ usbd_copy_in(td->pc, td->offset, td->qh->fixup_buf, td->qh->fixup_actlen); /* post-increment RX buffer offset */ td->offset += td->qh->fixup_actlen; td->qh->fixup_off += td->qh->fixup_actlen; td->remainder -= td->qh->fixup_actlen; td->qh->fixup_state = FIXUP_ALOC; if ((td->qh->ep_type & UE_XFERTYPE) == UE_ISOCHRONOUS) return (0); /* done */ } /* 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 */ rem = 0; td->short_pkt = 1; } else { /* get maximum length */ rem = DOTG_MAX_FIXUP % td->qh->max_frame_size; rem = DOTG_MAX_FIXUP - rem; DPRINTFN(2, "DOTG_MAX_FIXUP=%d, td->qh->max_frame_size=%d\n", DOTG_MAX_FIXUP, td->qh->max_frame_size); DPRINTFN(2, "rem=%d, td->qh->fixup_actlen=%d\n", rem, td->qh->fixup_actlen); if (rem == 0) { /* should not happen */ DPRINTFN(1, "Fixup buffer is too small\n"); td->error_any = 1; return (0); /* done */ } /* get minimum length */ if (rem > td->remainder) rem = td->remainder; } /* invalidate data */ usb_pc_cpu_invalidate(td->qh->fixup_pc); /* get softc */ sc = td->qh->sc; DPRINTF("%d: \t\tep_toggle_next=%d\n", __LINE__, td->qh->ep_toggle_next); td->qh->fixup_off = 0; int ret = configure_channel(td, rem, td->qh->ep_toggle_next?HCTSIZ_PID_DATA1:HCTSIZ_PID_DATA0, HCCHAR_EPDIR_IN); if (ret != 0) return (1); uint32_t hcchar = READ4(sc, DOTG_HCCHAR(td->channel)); // if ( td->qh->ep_type == UE_INTERRUPT ) hcchar |= (!(GETFLD(READ4(sc, DOTG_HFNUM), HFNUM_FRNUM) & 1))?HCCHAR_ODDFRM:0; hcchar |= HCCHAR_CHENA; td->qh->fixup_state = FIXUP_PEND; usb_pc_cpu_flush(td->qh->fixup_pc); WRITE4(sc, DOTG_HCCHAR(td->channel), hcchar); return (1); /* busy */ } static uint8_t dotg_xfer_do_fifo(struct usb_xfer *xfer) { struct dotg_td *td; td = xfer->td_transfer_cache; while (1) { if ((td->func) (td)) { /* operation in progress */ break; } if (((void *)td) == xfer->td_transfer_last) { DPRINTF("%d: td_transfer_last\n", __LINE__); goto done; } if (td->error_any) { DPRINTF("%d: error_any\n", __LINE__); goto done; } else if (td->remainder > 0) { /* * We had a short transfer. If there is no * alternate next, stop processing ! */ DPRINTF("%d: td->remainder[%d] > 0\n", __LINE__, td->remainder); if (td->alt_next == 0) { DPRINTF("%d: alt_next is 0\n", __LINE__); goto done; } } /* * Fetch the next transfer descriptor and transfer * some flags to the next transfer descriptor */ td = td->obj_next; xfer->td_transfer_cache = td; } return (1); /* not complete */ done: /* compute all actual lengths */ dotg_standard_done(xfer); return (0); /* complete */ } static usb_error_t dotg_standard_done_sub(struct usb_xfer *xfer) { struct dotg_td *td; uint32_t len; usb_error_t error; DPRINTFN(8, "dotg_standard_done_sub\n"); td = xfer->td_transfer_cache; do { len = td->remainder; if (xfer->aframes != xfer->nframes) { /* * Verify the length and subtract * the remainder from "frlengths[]": */ if (len > xfer->frlengths[xfer->aframes]) { td->error_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) { /* 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 dotg_standard_done(struct usb_xfer *xfer) { struct dotg_qh *qh; usb_error_t error = 0; DPRINTFN(12, "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) error = dotg_standard_done_sub(xfer); xfer->aframes = 1; if (xfer->td_transfer_cache == NULL) goto done; } while (xfer->aframes != xfer->nframes) { error = dotg_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) error = dotg_standard_done_sub(xfer); done: /* update data toggle */ qh = xfer->qh_start[0]; xfer->endpoint->toggle_next = (qh->ep_toggle_next)? 1 : 0; dotg_device_done(xfer, error); } static void dotg_interrupt_poll(struct dotg_softc *sc) { struct usb_xfer *xfer; uint32_t gintsts; cpu_dcache_inv_range((vm_offset_t)sc, sizeof(sc)); gintsts = READ4(sc, DOTG_GINTSTS); WRITE4(sc, DOTG_GINTSTS, gintsts); if ((gintsts & GINTSTS_PRTINT) || (gintsts & GINTSTS_DISCONNINT)) { uint32_t hprt = READ4(sc, DOTG_HPRT); WRITE4(sc, DOTG_HPRT, hprt & ~HPRT_PRTENA); DPRINTFN(12, "gintsts=0x%08x, hprt=0x%08x\n", gintsts, hprt); if (hprt & HPRT_PRTENCHNG) sc->sc_ischange = 1; if (hprt & HPRT_PRTCONNSTS) { if (!(hprt & HPRT_PRTENA)) { dotg_enable(sc); } } /* Call root hub port status handler */ uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, sizeof(sc->sc_hub_idata)); } /* if (gintsts & GINTSTS_HCHINT) */ { uint32_t haint = READ4(sc, DOTG_HAINT); WRITE4(sc, DOTG_HAINT, haint); haint &= ((1<channels) - 1); while (haint) { int channel; for (channel = 0; channel < sc->channels; channel ++) if (haint & (1<sc_bus.intr_q.head, wait_entry) { if (!dotg_xfer_do_fifo(xfer)) { /* queue has been modified */ goto repeat; } } } static void dotg_start_standard_chain(struct usb_xfer *xfer) { DPRINTFN(8, "dotg_start_standard_chain\n"); /* poll one time */ if (dotg_xfer_do_fifo(xfer)) { DPRINTFN(8, "Not transfered, will be queued, ++++++++++++++++++++++++++++++++++ xfer=%p\n", xfer); /* put transfer on interrupt queue */ mtx_lock_spin(&((struct dotg_qh *)xfer->qh_start[0])->sc->q_mtx); usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); mtx_unlock_spin(&((struct dotg_qh *)xfer->qh_start[0])->sc->q_mtx); DPRINTFN(8, "queued ++++++++++++++++++++++++++++++++++ xfer=%p\n", xfer); /* start timeout, if any */ if (xfer->timeout != 0) { usbd_transfer_timeout_ms(xfer, &dotg_timeout, xfer->timeout); } } } void dotg_iterate_hw_softc(struct usb_bus *bus, usb_bus_mem_sub_cb_t *cb) { } usb_error_t dotg_init(struct dotg_softc *sc) { int channel; uint32_t tmp; #ifdef USB_DEBUG dotgdebug = 0x0fffffff; #endif #define CP printf("%s:%d\n", __func__, __LINE__) /* flush all cache into memory */ usb_bus_mem_flush_all(&sc->sc_bus, &dotg_iterate_hw_softc); CP; /* set up the bus struct */ sc->sc_bus.methods = &dotg_bus_methods; /* set USB revision */ sc->sc_bus.usbrev = USB_REV_2_0; CP; USB_BUS_LOCK(&sc->sc_bus); mtx_init(&sc->q_mtx, "otgusb_xferq_spin", NULL, MTX_SPIN); CP; WRITE4(sc, DOTG_PCGCCTL, 0xffffffff); DELAY(10000); WRITE4(sc, DOTG_PCGCCTL, 0); DELAY(10000); sc->index = 0; CP; // sc->channels = GETFLD(READ4(sc, DOTG_GHWCFG2), GHWCFG2_NUMHSTCHNL)+1; sc->channels = 8; /* TODO(device): GHWCFG2_NUMDEVEPS for device mode */ sc->idle_hardware_channels = (1<channels) - 1; DPRINTF("%d: idle_hardware_channels= 0x%08x\n", __LINE__, sc->idle_hardware_channels); CP; WRITE4(sc, DOTG_GAHBCFG, GAHBCFG_DMAEN | GAHBCFG_NPTXFEMPLVL | GAHBCFG_PTXFEMPLVL | GAHBCFG_GLBLINTRMSK | SETFLD(0, GAHBCFG_HBSTLEN)); CP; tmp = READ4(sc, DOTG_GUSBCFG); tmp &= ~(GUSBCFG_CORRUPTTXPACKET | GUSBCFG_USBTRDTIM_MASK | GUSBCFG_TOUTCAL_MASK | GUSBCFG_DDRSEL | GUSBCFG_PHYLPWRCLKSEL); tmp |= SETFLD(0xf, GUSBCFG_USBTRDTIM); WRITE4(sc, DOTG_GUSBCFG, tmp); CP; for (channel = 0; channel < sc->channels; channel++) if (sc->idle_hardware_channels & (1 << channel)) { #ifdef USB_DEBUG if (dotgdebug) printf("HCINTMSK(%d) = 0x%08x\n", channel, HCINTMSK_CHHLTDMSK | HCINTMSK_AHBERRMSK); #endif WRITE4(sc, DOTG_HCINTMSK(channel), HCINTMSK_CHHLTDMSK | HCINTMSK_AHBERRMSK); } CP; WRITE4(sc, DOTG_HAINTMSK, sc->idle_hardware_channels); sc->sc_mode_device = 0; DPRINTF("%s: USB%d is in host mode\n", __func__, 0); CP; SETFIELD32(sc, DOTG_GINTMSK, GINTMSK_PRTINTMSK, 1); SETFIELD32(sc, DOTG_GINTMSK, GINTMSK_DISCONNINTMSK, 1); CP; tmp = READ4(sc, DOTG_HCFG); tmp &= ~(HCFG_FSLSSUPP | HCFG_FSLSPCLKSEL_MASK); tmp |= SETFLD(1, HCFG_FSLSPCLKSEL); WRITE4(sc, DOTG_HCFG, tmp); CP; if (READ4(sc, DOTG_HPRT) & HPRT_PRTCONNSTS) SETFIELD32(sc, DOTG_HPRT, HPRT_PRTPWR, 1); CP; USB_BUS_UNLOCK(&sc->sc_bus); CP; /* catch lost interrupts */ dotg_do_poll(&sc->sc_bus); CP; /* Enable interrupts */ tmp = READ4(sc, DOTG_GINTMSK); // tmp |= (GINTMSK_SOFMSK); tmp &= ~(GINTMSK_SOFMSK); tmp |= (GINTMSK_OTGINTMSK | GINTMSK_MODEMISMSK | GINTMSK_HCHINTMSK); WRITE4(sc, DOTG_GINTMSK, tmp); CP; return (0); } usb_error_t dotg_uninit(struct dotg_softc *sc) { return (0); } void dotg_suspend(struct dotg_softc *sc) { } void dotg_resume(struct dotg_softc *sc) { } /*------------------------------------------------------------------------* * dotg_interrupt - DOTG interrupt handler *------------------------------------------------------------------------*/ int dotg_interrupt(void *arg) { struct dotg_softc *sc = arg; /* Get current interrupts mask */ uint32_t tmp = READ4(sc, DOTG_GINTMSK); /* Disable interrupts */ WRITE4(sc, DOTG_GINTMSK, 0); USB_BUS_LOCK(&sc->sc_bus); /* poll all the USB transfers */ dotg_interrupt_poll(sc); USB_BUS_UNLOCK(&sc->sc_bus); /* Restore interrupts */ WRITE4(sc, DOTG_GINTMSK, tmp); return (FILTER_HANDLED); } /*------------------------------------------------------------------------* * dump_xfer_info - DEBUG print info about xfer, ep and channel regs * *------------------------------------------------------------------------*/ #ifdef USB_DEBUG static void dump_xfer_info(struct usb_xfer *xfer) { int channel; DPRINTF("Timeout xfer=%p\n", xfer); usb_dump_xfer(xfer); usb_dump_endpoint(xfer->endpoint); channel = ((struct dotg_td *)xfer->td_transfer_cache)->channel; if (channel >= 0) { DPRINTF("channel=%d\n", channel); dump_channel_regs(((struct dotg_qh *)xfer->qh_start[0])->sc, channel); } else { DPRINTF("No channel asigned\n"); } } #endif /*------------------------------------------------------------------------* * dotg_timeout - DOTG transfer timeout handler *------------------------------------------------------------------------*/ static void dotg_timeout(void *arg) { struct usb_xfer *xfer = arg; DPRINTF("xfer=%p\n", xfer); USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); #ifdef USB_DEBUG dump_xfer_info(xfer); #endif /* transfer is transferred */ dotg_device_done(xfer, USB_ERR_TIMEOUT); } /*------------------------------------------------------------------------* * dotg_do_poll - DOTG poll transfers *------------------------------------------------------------------------*/ static void dotg_do_poll(struct usb_bus *bus) { struct dotg_softc *sc = DOTG_BUS2SC(bus); USB_BUS_LOCK(&sc->sc_bus); dotg_interrupt_poll(sc); USB_BUS_UNLOCK(&sc->sc_bus); } static void dotg_setup_standard_chain_sub(struct dotg_std_temp *temp) { struct dotg_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->short_pkt = temp->short_pkt; td->alt_next = temp->setup_alt_next; if (td->qh->dev_speed == USB_SPEED_HIGH) { td->qh->flags |= __DOTG_FLAGS_NEED_PING; } } static void dotg_setup_standard_chain(struct usb_xfer *xfer) { struct dotg_std_temp temp; struct dotg_td *td; uint32_t x; 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)); /* setup starting point */ td = xfer->td_start[0]; xfer->td_transfer_first = td; xfer->td_transfer_cache = td; temp.td = NULL; temp.td_next = td; temp.setup_alt_next = xfer->flags_int.short_frames_ok; temp.offset = 0; temp.channel = td->channel; /* check if we should prepend a setup message */ DPRINTF("xfer=%p, control_xfr=%d, control_hdr=%d, control_act=%d, nframes=%d\n", xfer, xfer->flags_int.control_xfr, xfer->flags_int.control_hdr, xfer->flags_int.control_act, xfer->nframes ); if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { DPRINTF("Add &dotg_host_control_header_tx to xfer=%p\n", xfer); temp.func = &dotg_host_control_header_tx; 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; } dotg_setup_standard_chain_sub(&temp); } x = 1; } else { x = 0; } if (x != xfer->nframes) { if (xfer->endpointno & UE_DIR_IN) { if (xfer->flags_int.control_xfr) { DPRINTF("Add &dotg_host_control_data_rx to xfer=%p\n", xfer); temp.func = &dotg_host_control_data_rx; } else { DPRINTF("Add &dotg_non_control_data_rx to xfer=%p\n", xfer); temp.func = &dotg_non_control_data_rx; } } else { if (xfer->flags_int.control_xfr) { DPRINTF("Add &dotg_host_control_data_tx to xfer=%p\n", xfer); temp.func = &dotg_host_control_data_tx; } else { DPRINTF("Add &dotg_non_control_data_tx to xfer=%p\n", xfer); temp.func = &dotg_non_control_data_tx; } } /* setup "pc" pointer */ temp.pc = xfer->frbuffers + x; } while (x != xfer->nframes) { /* DATA0 or DATA1 message */ temp.len = xfer->frlengths[x]; x++; if (x == xfer->nframes) { if (xfer->flags_int.control_xfr) { /* no STATUS stage yet, DATA is last */ 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; } DPRINTF("Add TD with len=%d to xfer=%p\n", temp.len, xfer); dotg_setup_standard_chain_sub(&temp); if (xfer->flags_int.isochronous_xfr) { /* get next data offset */ temp.offset += temp.len; } else { /* get next Page Cache pointer */ temp.pc = xfer->frbuffers + x; } } /* check if we should append a status stage */ if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) { DPRINTF("Add &dotg_host_control_status_tx to xfer=%p\n", xfer); temp.func = &dotg_host_control_status_tx; temp.len = 0; temp.pc = NULL; temp.short_pkt = 0; temp.setup_alt_next = 0; dotg_setup_standard_chain_sub(&temp); } /* must have at least one frame! */ td = temp.td; xfer->td_transfer_last = td; /* properly setup QH */ td->qh->fixup_state = FIXUP_NONE; td->qh->ep_toggle_next = xfer->endpoint->toggle_next ? 1 : 0; } /*------------------------------------------------------------------------* * dotg_device_done - DOTG transfers done code * * NOTE: This function can be called more than one time in a row. *------------------------------------------------------------------------*/ static void dotg_device_done(struct usb_xfer *xfer, usb_error_t error) { USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d ----------------------------------!!!!\n", xfer, xfer->endpoint, error); /* * 1) Free any endpoints. * 2) Control transfers can be split and we should not re-open * the data pipe between transactions unless there is an error. */ if ((xfer->flags_int.control_act == 0) || (error != 0)) { struct dotg_td *td; td = xfer->td_start[0]; dotg_host_free_endpoint(td); } /* dequeue transfer and start next transfer */ mtx_lock_spin(&((struct dotg_qh *)xfer->qh_start[0])->sc->q_mtx); usbd_transfer_done(xfer, error); mtx_unlock_spin(&((struct dotg_qh *)xfer->qh_start[0])->sc->q_mtx); DPRINTFN(2, "xfer=%p removed from queue (error=%d) ----------------------------------!!!!\n", xfer, error); } /*------------------------------------------------------------------------* * dotg bulk support *------------------------------------------------------------------------*/ static void dotg_device_bulk_open(struct usb_xfer *xfer) { return; } static void dotg_device_bulk_close(struct usb_xfer *xfer) { dotg_device_done(xfer, USB_ERR_CANCELLED); } static void dotg_device_bulk_enter(struct usb_xfer *xfer) { return; } static void dotg_device_bulk_start(struct usb_xfer *xfer) { /* setup TDs */ dotg_setup_standard_chain(xfer); dotg_start_standard_chain(xfer); } struct usb_pipe_methods dotg_device_bulk_methods = { .open = dotg_device_bulk_open, .close = dotg_device_bulk_close, .enter = dotg_device_bulk_enter, .start = dotg_device_bulk_start, }; /*------------------------------------------------------------------------* * dotg control support *------------------------------------------------------------------------*/ static void dotg_device_ctrl_open(struct usb_xfer *xfer) { return; } static void dotg_device_ctrl_close(struct usb_xfer *xfer) { dotg_device_done(xfer, USB_ERR_CANCELLED); } static void dotg_device_ctrl_enter(struct usb_xfer *xfer) { return; } static void dotg_device_ctrl_start(struct usb_xfer *xfer) { /* setup TDs */ dotg_setup_standard_chain(xfer); dotg_start_standard_chain(xfer); } struct usb_pipe_methods dotg_device_ctrl_methods = { .open = dotg_device_ctrl_open, .close = dotg_device_ctrl_close, .enter = dotg_device_ctrl_enter, .start = dotg_device_ctrl_start, }; /*------------------------------------------------------------------------* * dotg interrupt support *------------------------------------------------------------------------*/ static void dotg_device_intr_open(struct usb_xfer *xfer) { usb_hs_bandwidth_alloc(xfer); return; } static void dotg_device_intr_close(struct usb_xfer *xfer) { dotg_device_done(xfer, USB_ERR_CANCELLED); /* bandwidth must be freed after device done */ usb_hs_bandwidth_free(xfer); } static void dotg_device_intr_enter(struct usb_xfer *xfer) { return; } static void dotg_device_intr_start(struct usb_xfer *xfer) { /* setup TDs */ dotg_setup_standard_chain(xfer); dotg_start_standard_chain(xfer); } struct usb_pipe_methods dotg_device_intr_methods = { .open = dotg_device_intr_open, .close = dotg_device_intr_close, .enter = dotg_device_intr_enter, .start = dotg_device_intr_start, }; /*------------------------------------------------------------------------* * dotg isochronous support *------------------------------------------------------------------------*/ static void dotg_device_isoc_open(struct usb_xfer *xfer) { return; } static void dotg_device_isoc_close(struct usb_xfer *xfer) { dotg_device_done(xfer, USB_ERR_CANCELLED); } static void dotg_device_isoc_enter(struct usb_xfer *xfer) { struct dotg_softc *sc = DOTG_BUS2SC(xfer->xroot->bus); uint32_t temp; uint32_t frame_count; uint32_t fs_frames; DPRINTFN(5, "xfer=%p next=%d nframes=%d\n", xfer, xfer->endpoint->isoc_next, xfer->nframes); /* get the current frame index */ frame_count = /*(sc->init_flags & DEVICE_MODE)? GETFLD(READ4(sc, DOTG_DSTS), DSTS_SOFFN) : */ GETFLD(READ4(sc, DOTG_HFNUM), HFNUM_FRNUM); /* * check if the frame index is within the window where the frames * will be inserted */ temp = (frame_count - xfer->endpoint->isoc_next) & 0x7FF; if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) { fs_frames = (xfer->nframes + 7) / 8; } else { fs_frames = xfer->nframes; } if ((xfer->endpoint->is_synced == 0) || (temp < fs_frames)) { /* * 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 = (frame_count + 3) & 0x7FF; xfer->endpoint->is_synced = 1; DPRINTFN(2, "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 - frame_count) & 0x7FF; /* * pre-compute when the isochronous transfer will be finished: */ xfer->isoc_time_complete = usb_isoc_time_expand(&sc->sc_bus, frame_count) + temp + fs_frames; /* compute frame number for next insertion */ xfer->endpoint->isoc_next += fs_frames; } static void dotg_device_isoc_start(struct usb_xfer *xfer) { /* setup TDs */ dotg_setup_standard_chain(xfer); dotg_start_standard_chain(xfer); } struct usb_pipe_methods dotg_device_isoc_methods = { .open = dotg_device_isoc_open, .close = dotg_device_isoc_close, .enter = dotg_device_isoc_enter, .start = dotg_device_isoc_start, }; /*------------------------------------------------------------------------* * DOTG root HUB support *------------------------------------------------------------------------* * Simulate a hardware HUB by handling all the necessary requests. *------------------------------------------------------------------------*/ static const struct usb_device_descriptor dotg_devd = { .bLength = sizeof(dotg_devd), .bDescriptorType = UDESC_DEVICE, .bcdUSB = {0x00, 0x02}, .bDeviceClass = UDCLASS_HUB, .bDeviceSubClass = UDSUBCLASS_HUB, .bDeviceProtocol = UDPROTO_FSHUB, .bMaxPacketSize = 64, .idVendor = {0}, .idProduct = {0}, .bcdDevice = {0x00, 0x01}, .iManufacturer = 1, .iProduct = 2, .iSerialNumber = 0, .bNumConfigurations = 1, }; static const struct usb_device_qualifier dotg_odevd = { .bLength = sizeof(dotg_odevd), .bDescriptorType = UDESC_DEVICE_QUALIFIER, .bcdUSB = {0x00, 0x02}, .bDeviceClass = UDCLASS_HUB, .bDeviceSubClass = UDSUBCLASS_HUB, .bDeviceProtocol = UDPROTO_FSHUB, .bMaxPacketSize0 = 0, .bNumConfigurations = 0, .bReserved = 0, }; static const struct dotg_config_desc dotg_confd = { .confd = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = UDESC_CONFIG, .wTotalLength[0] = sizeof(dotg_confd), .bNumInterface = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = UC_SELF_POWERED, .bMaxPower = 0 /* max power */ }, .ifcd = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = UDESC_INTERFACE, .bNumEndpoints = 1, .bInterfaceClass = UICLASS_HUB, .bInterfaceSubClass = UISUBCLASS_HUB, .bInterfaceProtocol = UIPROTO_FSHUB, }, .endpd = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = UDESC_ENDPOINT, .bEndpointAddress = UE_DIR_IN | DOTG_INTR_ENDPT, .bmAttributes = UE_INTERRUPT, .wMaxPacketSize[0] = 8, /* max packet (63 ports) */ .bInterval = 255, }, }; static const struct usb_hub_descriptor_min dotg_hubd = { .bDescLength = sizeof(dotg_hubd), .bDescriptorType = UDESC_HUB, .bNbrPorts = 1, .wHubCharacteristics = {UHD_OC_INDIVIDUAL, 0}, .bPwrOn2PwrGood = 50, .bHubContrCurrent = 0, .DeviceRemovable = {0x00}, /* all ports are removable */ }; static usb_error_t dotg_roothub_exec(struct usb_device *udev, struct usb_device_request *req, const void **pptr, uint16_t *plength) { struct dotg_softc *sc = DOTG_BUS2SC(udev->bus); const void *ptr; const char *str_ptr; uint16_t value; uint16_t index; uint16_t status; uint16_t change; uint16_t len; usb_error_t err; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* XXX disable power save mode, hence it is not supported */ udev->power_mode = USB_POWER_MODE_ON; /* buffer reset */ ptr = (const void *)&sc->sc_hub_desc.temp; len = 0; err = 0; value = UGETW(req->wValue); index = UGETW(req->wIndex); DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x " "wValue=0x%04x wIndex=0x%04x\n", req->bmRequestType, req->bRequest, UGETW(req->wLength), value, index); #define C(x,y) ((x) | ((y) << 8)) switch (C(req->bRequest, req->bmRequestType)) { case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): break; case C(UR_GET_CONFIG, UT_READ_DEVICE): len = 1; sc->sc_hub_desc.temp[0] = sc->sc_conf; break; case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): switch (value >> 8) { case UDESC_DEVICE: if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } len = sizeof(dotg_devd); ptr = (const void *)&dotg_devd; break; case UDESC_DEVICE_QUALIFIER: if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } len = sizeof(dotg_odevd); ptr = (const void *)&dotg_odevd; break; case UDESC_CONFIG: if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } len = sizeof(dotg_confd); ptr = (const void *)&dotg_confd; break; case UDESC_STRING: switch (value & 0xff) { case 0: /* Language table */ str_ptr = "\001"; break; case 1: /* Vendor */ str_ptr = "DWC"; break; case 2: /* Product */ str_ptr = "OTG Root HUB"; break; default: str_ptr = ""; break; } len = usb_make_str_desc(sc->sc_hub_desc.temp, sizeof(sc->sc_hub_desc.temp), str_ptr); break; default: err = USB_ERR_IOERROR; goto done; } break; case C(UR_GET_INTERFACE, UT_READ_INTERFACE): len = 1; sc->sc_hub_desc.temp[0] = 0; break; case C(UR_GET_STATUS, UT_READ_DEVICE): len = 2; USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED); break; case C(UR_GET_STATUS, UT_READ_INTERFACE): case C(UR_GET_STATUS, UT_READ_ENDPOINT): len = 2; USETW(sc->sc_hub_desc.stat.wStatus, 0); break; case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): if (value >= DOTG_MAX_DEVICES) { err = USB_ERR_IOERROR; goto done; } sc->sc_addr = value; break; case C(UR_SET_CONFIG, UT_WRITE_DEVICE): if ((value != 0) && (value != 1)) { err = USB_ERR_IOERROR; goto done; } sc->sc_conf = value; break; case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): break; case C(UR_SET_FEATURE, UT_WRITE_DEVICE): case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): err = USB_ERR_IOERROR; goto done; case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): break; case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): break; /* Hub requests */ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): break; case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): if (index != 1) { err = USB_ERR_IOERROR; goto done; } switch (value) { case UHF_PORT_ENABLE: break; case UHF_PORT_SUSPEND: break; case UHF_PORT_RESET: break; case UHF_C_PORT_CONNECTION: break; case UHF_C_PORT_ENABLE: break; case UHF_C_PORT_OVER_CURRENT: break; case UHF_C_PORT_RESET: sc->sc_isreset = 0; goto done; case UHF_C_PORT_SUSPEND: break; case UHF_PORT_POWER: SETFIELD32(sc, DOTG_HPRT, HPRT_PRTPWR, 0); break; case UHF_PORT_CONNECTION: case UHF_PORT_OVER_CURRENT: case UHF_PORT_LOW_SPEED: default: err = USB_ERR_IOERROR; goto done; } break; case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } sc->sc_hubd = dotg_hubd; sc->sc_hubd.bNbrPorts = 1; len = sizeof(sc->sc_hubd); ptr = (const void *)&sc->sc_hubd; break; case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): len = 16; memset(sc->sc_hub_desc.temp, 0, 16); break; case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): if ((index < 1) || (index > 1) ) { err = USB_ERR_IOERROR; goto done; } status = change = 0; uint32_t hprt = READ4(sc, DOTG_HPRT); status |= (hprt & HPRT_PRTCONNSTS)? UPS_CURRENT_CONNECT_STATUS:0; status |= (hprt & HPRT_PRTENA)? UPS_PORT_ENABLED:0; status |= (hprt & HPRT_PRTOVRCURRACT)? UPS_OVERCURRENT_INDICATOR:0; status |= (hprt & HPRT_PRTPWR)? UPS_PORT_POWER:0; status |= (GETFLD(hprt, HPRT_PRTSPD) == HPRT_PRTSPD_LOW)? UPS_LOW_SPEED: (GETFLD(hprt, HPRT_PRTSPD) == HPRT_PRTSPD_FULL)? 0: UPS_HIGH_SPEED; change |= (hprt & HPRT_PRTENCHNG)?UPS_C_CONNECT_STATUS:0; change |= (sc->sc_ischange)?UPS_C_CONNECT_STATUS:0; sc->sc_ischange = 0; if (sc->sc_isreset) change |= UPS_C_PORT_RESET; USETW(sc->sc_hub_desc.ps.wPortStatus, status); USETW(sc->sc_hub_desc.ps.wPortChange, change); len = sizeof(sc->sc_hub_desc.ps); break; case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): err = USB_ERR_IOERROR; goto done; case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): break; case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): if ((index < 1) || (index > 1)) { err = USB_ERR_IOERROR; goto done; } switch (value) { case UHF_PORT_RESET: DPRINTF("Reset port\n"); /* passtrow */ case UHF_PORT_ENABLE: if (dotg_enable(sc)) { err = USB_ERR_IOERROR; goto done; } sc->sc_isreset = 1; goto done; case UHF_PORT_POWER: /* pretend we turned on power */ SETFIELD32(sc, DOTG_HPRT, HPRT_PRTPWR, 1); /* 20ms */ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50); goto done; case UHF_PORT_SUSPEND: goto done; case UHF_C_PORT_CONNECTION: case UHF_C_PORT_ENABLE: case UHF_C_PORT_OVER_CURRENT: case UHF_PORT_CONNECTION: case UHF_PORT_OVER_CURRENT: case UHF_PORT_LOW_SPEED: case UHF_C_PORT_SUSPEND: case UHF_C_PORT_RESET: default: err = USB_ERR_IOERROR; goto done; } break; default: err = USB_ERR_IOERROR; goto done; } done: *plength = len; *pptr = ptr; return (err); } static void dotg_xfer_setup(struct usb_setup_params *parm) { struct usb_page_search page_info; struct usb_page_cache *pc; struct dotg_softc *sc; struct dotg_qh *qh; struct usb_xfer *xfer; void *last_obj; uint32_t n; uint32_t ntd; sc = DOTG_BUS2SC(parm->udev->bus); xfer = parm->curr_xfer; qh = NULL; /* * 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 = 0x400; parm->hc_max_packet_count = 8; parm->hc_max_frame_size = 0x2000; usbd_transfer_setup_sub(parm); if (parm->err) return; /* Allocate a queue head */ if (usbd_transfer_setup_sub_malloc( parm, &pc, sizeof(struct dotg_qh), USB_HOST_ALIGN, 1)) { parm->err = USB_ERR_NOMEM; return; } if (parm->buf) { usbd_get_page(pc, 0, &page_info); qh = page_info.buffer; /* fill out QH */ qh->sc = DOTG_BUS2SC(xfer->xroot->bus); qh->max_frame_size = xfer->max_frame_size; qh->max_packet_size = xfer->max_packet_size; qh->ep_num = xfer->endpointno; qh->ep_type = xfer->endpoint->edesc->bmAttributes; qh->dev_addr = xfer->address; qh->dev_speed = usbd_get_speed(xfer->xroot->udev); qh->port_index = xfer->xroot->udev->port_index; qh->ep_mult = xfer->max_packet_count & 3; qh->hs_hub_addr = xfer->xroot->udev->hs_hub_addr; qh->hs_hub_port = xfer->xroot->udev->hs_port_no; switch (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) { case UE_INTERRUPT: if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) qh->ep_interval = xfer->interval * 8; else qh->ep_interval = xfer->interval * 1; break; case UE_ISOCHRONOUS: qh->ep_interval = 1 << xfer->fps_shift; break; default: qh->ep_interval = 0; break; } } xfer->qh_start[0] = qh; /* Allocate a fixup buffer */ if (usbd_transfer_setup_sub_malloc( parm, &pc, DOTG_MAX_FIXUP, DOTG_MAX_FIXUP, 1)) { parm->err = USB_ERR_NOMEM; return; } if (parm->buf) { usbd_get_page(pc, 0, &page_info); qh->fixup_phys = page_info.physaddr; qh->fixup_pc = pc; qh->fixup_buf = page_info.buffer; } /* Allocate transfer descriptors */ last_obj = NULL; ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC */ ; if (usbd_transfer_setup_sub_malloc( parm, &pc, sizeof(struct dotg_td), USB_HOST_ALIGN, ntd)) { parm->err = USB_ERR_NOMEM; return; } if (parm->buf) { for (n = 0; n != ntd; n++) { struct dotg_td *td; usbd_get_page(pc + n, 0, &page_info); td = page_info.buffer; td->qh = qh; td->obj_next = last_obj; last_obj = td; } } xfer->td_start[0] = last_obj; } static void dotg_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, struct usb_endpoint *ep) { struct dotg_softc *sc = DOTG_BUS2SC(udev->bus); DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n", ep, udev->address, edesc->bEndpointAddress, udev->flags.usb_mode, sc->sc_addr); if (udev->flags.usb_mode != USB_MODE_HOST) { /* not supported */ return; } if (udev->device_index != sc->sc_addr) { switch (edesc->bmAttributes & UE_XFERTYPE) { case UE_CONTROL: ep->methods = &dotg_device_ctrl_methods; break; case UE_INTERRUPT: ep->methods = &dotg_device_intr_methods; break; case UE_ISOCHRONOUS: if (udev->speed != USB_SPEED_LOW) ep->methods = &dotg_device_isoc_methods; break; case UE_BULK: ep->methods = &dotg_device_bulk_methods; break; default: /* do nothing */ break; } } } static void dotg_xfer_unsetup(struct usb_xfer *xfer) { return; } static void dotg_get_dma_delay(struct usb_device *udev, uint32_t *pus) { /* DMA delay - wait until any use of memory is finished */ *pus = (2125); /* microseconds */ // *pus = (300); /* microseconds */ } static void dotg_device_resume(struct usb_device *udev) { DPRINTF("%s: Nothing to do.\n", __func__); } static void dotg_device_suspend(struct usb_device *udev) { DPRINTF("%s: Nothing to do.\n", __func__); } static void dotg_set_hw_power(struct usb_bus *bus) { } struct usb_bus_methods dotg_bus_methods = { .endpoint_init = dotg_ep_init, .xfer_setup = dotg_xfer_setup, .xfer_unsetup = dotg_xfer_unsetup, .get_dma_delay = dotg_get_dma_delay, .device_resume = dotg_device_resume, .device_suspend = dotg_device_suspend, .set_hw_power = dotg_set_hw_power, .roothub_exec = dotg_roothub_exec, .xfer_poll = dotg_do_poll, };