From: mglocker Date: Sun, 4 Sep 2022 08:42:39 +0000 (+0000) Subject: Improve periodic USB transfers (device intr, isoc) used for input X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=a9beb1ed4245313a4b68e10ec33a67c806523ec4;p=openbsd Improve periodic USB transfers (device intr, isoc) used for input devices, audio, and video. It's still not perfect, and will need further improvements. High level, the diff contains following changes: * Sync up with the Linux code base, which did re-work the periodic scheduling code path. * Run the driver in IPL_VM instead of IPL_USB to prioritize us before lower/equal interrupts (same what NetBSD does). * Add two new flags to our USB stack required by the updated driver code: - 'multi' flag in the usbd_hub structure to keep track whether a hub has one Transaction Translator for all ports (single TT) or one Transaction Translator per port (multi TT). - 'hcpriv' pointer in the usbd_tt structure for the HC driver to allocate memory for the scheduling depending on single or multi TT. "go for it" kettenis@ --- diff --git a/sys/arch/octeon/dev/octdwctwo.c b/sys/arch/octeon/dev/octdwctwo.c index 39a853a594a..252d1387f76 100644 --- a/sys/arch/octeon/dev/octdwctwo.c +++ b/sys/arch/octeon/dev/octdwctwo.c @@ -1,4 +1,4 @@ -/* $OpenBSD: octdwctwo.c,v 1.14 2021/07/24 14:43:53 mglocker Exp $ */ +/* $OpenBSD: octdwctwo.c,v 1.15 2022/09/04 08:42:39 mglocker Exp $ */ /* * Copyright (c) 2015 Masao Uebayashi @@ -76,9 +76,9 @@ struct cfdriver dwctwo_cd = { }; static struct dwc2_core_params octdwctwo_params = { - .otg_cap = 2, - .otg_ver = 0, - .dma_enable = 1, + .otg_caps.hnp_support = 0, + .otg_caps.srp_support = 0, + .host_dma = 1, .dma_desc_enable = 0, .speed = 0, .enable_dynamic_fifo = 1, @@ -101,8 +101,7 @@ static struct dwc2_core_params octdwctwo_params = { .reload_ctl = 0, .ahbcfg = 0x7, .uframe_sched = 1, - .external_id_pin_ctl = -1, - .hibernation = -1, + .external_id_pin_ctl = 0, }; /* @@ -223,7 +222,7 @@ octdwctwo_attach(struct device *parent, struct device *self, void *aux) sc->sc_dwc2.sc_child = config_found(&sc->sc_dwc2.sc_bus.bdev, &sc->sc_dwc2.sc_bus, usbctlprint); - sc->sc_ih = octeon_intr_establish(CIU_INT_USB, IPL_USB | IPL_MPSAFE, + sc->sc_ih = octeon_intr_establish(CIU_INT_USB, IPL_VM | IPL_MPSAFE, dwc2_intr, (void *)&sc->sc_dwc2, sc->sc_dwc2.sc_bus.bdev.dv_xname); KASSERT(sc->sc_ih != NULL); } diff --git a/sys/dev/fdt/bcm2835_dwctwo.c b/sys/dev/fdt/bcm2835_dwctwo.c index 86567b9d7d2..cdfa9e75d4a 100644 --- a/sys/dev/fdt/bcm2835_dwctwo.c +++ b/sys/dev/fdt/bcm2835_dwctwo.c @@ -1,4 +1,4 @@ -/* $OpenBSD: bcm2835_dwctwo.c,v 1.3 2021/07/24 06:04:44 mglocker Exp $ */ +/* $OpenBSD: bcm2835_dwctwo.c,v 1.4 2022/09/04 08:42:39 mglocker Exp $ */ /* * Copyright (c) 2015 Masao Uebayashi * @@ -57,9 +57,9 @@ struct cfdriver dwctwo_cd = { }; static struct dwc2_core_params bcm_dwctwo_params = { - .otg_cap = 0, /* HNP/SRP capable */ - .otg_ver = 0, /* 1.3 */ - .dma_enable = 1, + .otg_caps.hnp_support = 0, /* HNP/SRP capable */ + .otg_caps.srp_support = 0, + .host_dma = 1, .dma_desc_enable = 0, .speed = 0, /* High Speed */ .enable_dynamic_fifo = 1, @@ -82,8 +82,7 @@ static struct dwc2_core_params bcm_dwctwo_params = { .reload_ctl = 0, .ahbcfg = 0x10, .uframe_sched = 1, - .external_id_pin_ctl = -1, - .hibernation = -1, + .external_id_pin_ctl = 0, }; int @@ -117,8 +116,9 @@ bcm_dwctwo_attach(struct device *parent, struct device *self, void *aux) if (idx == -1) idx = 1; - sc->sc_ih = fdt_intr_establish_idx(faa->fa_node, idx, IPL_USB, - dwc2_intr, (void *)&sc->sc_dwc2, sc->sc_dwc2.sc_bus.bdev.dv_xname); + sc->sc_ih = fdt_intr_establish_idx(faa->fa_node, idx, + IPL_VM | IPL_MPSAFE, dwc2_intr, (void *)&sc->sc_dwc2, + sc->sc_dwc2.sc_bus.bdev.dv_xname); if (sc->sc_ih == NULL) panic("%s: intr_establish failed!", __func__); diff --git a/sys/dev/usb/dwc2/dwc2.c b/sys/dev/usb/dwc2/dwc2.c index 15ea1612a9e..7fa6fae9c62 100644 --- a/sys/dev/usb/dwc2/dwc2.c +++ b/sys/dev/usb/dwc2/dwc2.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dwc2.c,v 1.62 2022/07/02 08:50:42 visa Exp $ */ +/* $OpenBSD: dwc2.c,v 1.63 2022/09/04 08:42:39 mglocker Exp $ */ /* $NetBSD: dwc2.c,v 1.32 2014/09/02 23:26:20 macallan Exp $ */ /*- @@ -131,17 +131,7 @@ STATIC void dwc2_rhc(void *); STATIC void dwc2_timeout(void *); STATIC void dwc2_timeout_task(void *); -static inline void -dwc2_allocate_bus_bandwidth(struct dwc2_hsotg *hsotg, u16 bw, - struct usbd_xfer *xfer) -{ -} - -static inline void -dwc2_free_bus_bandwidth(struct dwc2_hsotg *hsotg, u16 bw, - struct usbd_xfer *xfer) -{ -} +int dwc2_check_core_version(struct dwc2_hsotg *); #define DWC2_INTR_ENDPT 1 @@ -440,11 +430,8 @@ STATIC void dwc2_poll(struct usbd_bus *bus) { struct dwc2_softc *sc = DWC2_BUS2SC(bus); - struct dwc2_hsotg *hsotg = sc->sc_hsotg; - mtx_enter(&hsotg->lock); dwc2_interrupt(sc); - mtx_leave(&hsotg->lock); } /* @@ -1162,7 +1149,7 @@ dwc2_device_start(struct usbd_xfer *xfer) uint8_t xfertype = UE_GET_XFERTYPE(ed->bmAttributes); uint8_t epnum = UE_GET_ADDR(ed->bEndpointAddress); uint8_t dir = UE_GET_DIR(ed->bEndpointAddress); - uint16_t mps = UE_GET_SIZE(UGETW(ed->wMaxPacketSize)); + uint32_t mps = UGETW(ed->wMaxPacketSize); uint32_t len; uint32_t flags = 0; @@ -1192,7 +1179,8 @@ dwc2_device_start(struct usbd_xfer *xfer) "mps=%d\n", xfer, req->bmRequestType, req->bRequest, UGETW(req->wValue), UGETW(req->wIndex), UGETW(req->wLength), dev->address, - epnum, dir == UT_READ ? "in" :"out", dev->speed, mps); + epnum, dir == UT_READ ? "in" :"out", dev->speed, + UE_GET_SIZE(mps)); /* Copy request packet to our DMA buffer */ memcpy(KERNADDR(&dpipe->req_dma, 0), req, sizeof(*req)); @@ -1212,7 +1200,7 @@ dwc2_device_start(struct usbd_xfer *xfer) } else if (xfertype == UE_ISOCHRONOUS) { DPRINTFN(3, "xfer=%p nframes=%d flags=%d addr=%d endpt=%d," " mps=%d dir %s\n", xfer, xfer->nframes, xfer->flags, addr, - epnum, mps, dir == UT_READ ? "in" :"out"); + epnum, UE_GET_SIZE(mps), dir == UT_READ ? "in" :"out"); #ifdef DIAGNOSTIC len = 0; @@ -1226,7 +1214,7 @@ dwc2_device_start(struct usbd_xfer *xfer) } else { DPRINTFN(3, "xfer=%p len=%d flags=%d addr=%d endpt=%d," " mps=%d dir %s\n", xfer, xfer->length, xfer->flags, addr, - epnum, mps, dir == UT_READ ? "in" :"out"); + epnum, UE_GET_SIZE(mps), dir == UT_READ ? "in" :"out"); len = xfer->length; } @@ -1243,7 +1231,7 @@ dwc2_device_start(struct usbd_xfer *xfer) dwc2_urb->packet_count = xfer->nframes; dwc2_hcd_urb_set_pipeinfo(hsotg, dwc2_urb, addr, epnum, xfertype, dir, - mps); + UE_GET_SIZE(mps), UE_GET_TRANS(mps) + 1); if (xfertype == UE_CONTROL) { dwc2_urb->setup_usbdma = &dpipe->req_dma; @@ -1251,7 +1239,8 @@ dwc2_device_start(struct usbd_xfer *xfer) dwc2_urb->setup_dma = DMAADDR(&dpipe->req_dma, 0); } else { /* XXXNH - % mps required? */ - if ((xfer->flags & USBD_FORCE_SHORT_XFER) && (len % mps) == 0) + if ((xfer->flags & USBD_FORCE_SHORT_XFER) && (len % + UE_GET_SIZE(mps)) == 0) flags |= URB_SEND_ZERO_PACKET; } flags |= URB_GIVEBACK_ASAP; @@ -1332,7 +1321,7 @@ dwc2_device_start(struct usbd_xfer *xfer) /* Create QH for the endpoint if it doesn't exist */ if (!qh) { - qh = dwc2_hcd_qh_create(hsotg, dwc2_urb, M_NOWAIT); + qh = dwc2_hcd_qh_create(hsotg, dwc2_urb, M_ZERO | M_NOWAIT); if (!qh) { retval = -ENOMEM; goto fail; @@ -1406,7 +1395,7 @@ int dwc2_intr(void *p) return 0; hsotg = sc->sc_hsotg; - mtx_enter(&hsotg->lock); +// mtx_enter(&hsotg->lock); if (sc->sc_bus.dying) goto done; @@ -1415,13 +1404,13 @@ int dwc2_intr(void *p) uint32_t intrs; intrs = dwc2_read_core_intr(hsotg); - DWC2_WRITE_4(hsotg, GINTSTS, intrs); + dwc2_writel(hsotg, intrs, GINTSTS); } else { ret = dwc2_interrupt(sc); } done: - mtx_leave(&hsotg->lock); +// mtx_leave(&hsotg->lock); return ret; } @@ -1470,11 +1459,11 @@ dwc2_init(struct dwc2_softc *sc) sc->sc_rhc_si = softintr_establish(IPL_SOFTUSB, dwc2_rhc, sc); - pool_init(&sc->sc_xferpool, sizeof(struct dwc2_xfer), 0, IPL_USB, 0, + pool_init(&sc->sc_xferpool, sizeof(struct dwc2_xfer), 0, IPL_VM, 0, "dwc2xfer", NULL); - pool_init(&sc->sc_qhpool, sizeof(struct dwc2_qh), 0, IPL_USB, 0, + pool_init(&sc->sc_qhpool, sizeof(struct dwc2_qh), 0, IPL_VM, 0, "dwc2qh", NULL); - pool_init(&sc->sc_qtdpool, sizeof(struct dwc2_qtd), 0, IPL_USB, 0, + pool_init(&sc->sc_qtdpool, sizeof(struct dwc2_qtd), 0, IPL_VM, 0, "dwc2qtd", NULL); sc->sc_hsotg = malloc(sizeof(struct dwc2_hsotg), M_USBHC, @@ -1484,9 +1473,8 @@ dwc2_init(struct dwc2_softc *sc) sc->sc_hcdenabled = true; struct dwc2_hsotg *hsotg = sc->sc_hsotg; - struct dwc2_core_params defparams; int retval; - +#if 0 if (sc->sc_params == NULL) { /* Default all params to autodetect */ dwc2_set_all_params(&defparams, -1); @@ -1498,30 +1486,41 @@ dwc2_init(struct dwc2_softc *sc) */ defparams.dma_desc_enable = 0; } +#endif hsotg->dr_mode = USB_DR_MODE_HOST; + /* + * Before performing any core related operations + * check core version. + */ + retval = dwc2_check_core_version(hsotg); + if (retval) + goto fail2; + /* * Reset before dwc2_get_hwparams() then it could get power-on real * reset value form registers. */ - dwc2_core_reset(hsotg); - usb_delay_ms(&sc->sc_bus, 500); + retval = dwc2_core_reset(hsotg, false); + if (retval) + goto fail2; /* Detect config values from hardware */ retval = dwc2_get_hwparams(hsotg); - if (retval) { + if (retval) goto fail2; - } - - hsotg->core_params = malloc(sizeof(*hsotg->core_params), M_USBHC, - M_ZERO | M_WAITOK); - dwc2_set_all_params(hsotg->core_params, -1); - /* Validate parameter values */ - dwc2_set_parameters(hsotg, sc->sc_params); + /* + * For OTG cores, set the force mode bits to reflect the value + * of dr_mode. Force mode bits should not be touched at any + * other time after this. + */ + dwc2_force_dr_mode(hsotg); -#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ - IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) + retval = dwc2_init_params(hsotg); + if (retval) + goto fail2; +#if 0 if (hsotg->dr_mode != USB_DR_MODE_HOST) { retval = dwc2_gadget_init(hsotg); if (retval) @@ -1529,8 +1528,6 @@ dwc2_init(struct dwc2_softc *sc) hsotg->gadget_enabled = 1; } #endif -#if IS_ENABLED(CONFIG_USB_DWC2_HOST) || \ - IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) if (hsotg->dr_mode != USB_DR_MODE_PERIPHERAL) { retval = dwc2_hcd_init(hsotg); if (retval) { @@ -1540,14 +1537,8 @@ dwc2_init(struct dwc2_softc *sc) } hsotg->hcd_enabled = 1; } -#endif -#ifdef DWC2_DEBUG - uint32_t snpsid = hsotg->hw_params.snpsid; - dev_dbg(hsotg->dev, "Core Release: %x.%x%x%x (snpsid=%x)\n", - snpsid >> 12 & 0xf, snpsid >> 8 & 0xf, - snpsid >> 4 & 0xf, snpsid & 0xf, snpsid); -#endif + hsotg->hibernated = 0; return 0; @@ -1559,27 +1550,6 @@ fail2: return err; } -#if 0 -/* - * curmode is a mode indication bit 0 = device, 1 = host - */ -STATIC const char * const intnames[32] = { - "curmode", "modemis", "otgint", "sof", - "rxflvl", "nptxfemp", "ginnakeff", "goutnakeff", - "ulpickint", "i2cint", "erlysusp", "usbsusp", - "usbrst", "enumdone", "isooutdrop", "eopf", - "restore_done", "epmis", "iepint", "oepint", - "incompisoin", "incomplp", "fetsusp", "resetdet", - "prtint", "hchint", "ptxfemp", "lpm", - "conidstschng", "disconnint", "sessreqint", "wkupint" -}; - - -/***********************************************************************/ - -#endif - - void dw_timeout(void *arg) { @@ -1589,173 +1559,29 @@ dw_timeout(void *arg) task_add(dw->dw_wq, &dw->work); } -void dwc2_host_hub_info(struct dwc2_hsotg *hsotg, void *context, int *hub_addr, - int *hub_port) -{ - struct usbd_xfer *xfer = context; - struct dwc2_pipe *dpipe = DWC2_XFER2DPIPE(xfer); - struct usbd_device *dev = dpipe->pipe.device; +/*** platform.c ***************************************************************/ - *hub_addr = dev->myhsport->parent->address; - *hub_port = dev->myhsport->portno; -} - -int dwc2_host_get_speed(struct dwc2_hsotg *hsotg, void *context) -{ - struct usbd_xfer *xfer = context; - struct dwc2_pipe *dpipe = DWC2_XFER2DPIPE(xfer); - struct usbd_device *dev = dpipe->pipe.device; - - return dev->speed; -} - -/* - * Sets the final status of an URB and returns it to the upper layer. Any - * required cleanup of the URB is performed. - * - * Must be called with interrupt disabled and spinlock held - */ -void dwc2_host_complete(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, - int status) +int dwc2_check_core_version(struct dwc2_hsotg *hsotg) { - struct usbd_xfer *xfer; - struct dwc2_xfer *dxfer; - struct dwc2_softc *sc; - usb_endpoint_descriptor_t *ed; - uint8_t xfertype; + struct dwc2_hw_params *hw = &hsotg->hw_params; - if (!qtd) { - dev_dbg(hsotg->dev, "## %s: qtd is NULL ##\n", __func__); - return; - } - - if (!qtd->urb) { - dev_dbg(hsotg->dev, "## %s: qtd->urb is NULL ##\n", __func__); - return; - } - - xfer = qtd->urb->priv; - if (!xfer) { - dev_dbg(hsotg->dev, "## %s: urb->priv is NULL ##\n", __func__); - return; - } - - dxfer = DWC2_XFER2DXFER(xfer); - sc = DWC2_XFER2SC(xfer); - ed = xfer->pipe->endpoint->edesc; - xfertype = UE_GET_XFERTYPE(ed->bmAttributes); - - struct dwc2_hcd_urb *urb = qtd->urb; - xfer->actlen = dwc2_hcd_urb_get_actual_length(urb); - - DPRINTFN(3, "xfer=%p actlen=%d\n", xfer, xfer->actlen); - - if (xfertype == UE_ISOCHRONOUS) { - xfer->actlen = 0; - for (size_t i = 0; i < xfer->nframes; ++i) { - xfer->frlengths[i] = - dwc2_hcd_urb_get_iso_desc_actual_length( - urb, i); - DPRINTFN(1, "xfer=%p frame=%zu length=%d\n", xfer, i, - xfer->frlengths[i]); - xfer->actlen += xfer->frlengths[i]; - } - DPRINTFN(1, "xfer=%p actlen=%d (isoc)\n", xfer, xfer->actlen); - } - - if (xfertype == UE_ISOCHRONOUS && dbg_perio()) { - for (size_t i = 0; i < xfer->nframes; i++) - dev_vdbg(hsotg->dev, " ISO Desc %zu status %d\n", - i, urb->iso_descs[i].status); - } - - if (!status) { - if (!(xfer->flags & USBD_SHORT_XFER_OK) && - xfer->actlen < xfer->length) - status = -EIO; - } - - switch (status) { - case 0: - dxfer->intr_status = USBD_NORMAL_COMPLETION; - break; - case -EPIPE: - dxfer->intr_status = USBD_STALLED; - break; - case -EPROTO: - dxfer->intr_status = USBD_INVAL; - break; - case -EIO: - dxfer->intr_status = USBD_IOERROR; - break; - case -EOVERFLOW: - dxfer->intr_status = USBD_IOERROR; - break; - default: - dxfer->intr_status = USBD_IOERROR; - printf("%s: unknown error status %d\n", __func__, status); - } - - if (dxfer->intr_status == USBD_NORMAL_COMPLETION) { - /* - * control transfers with no data phase don't touch dmabuf, but - * everything else does. - */ - if (!(xfertype == UE_CONTROL && - UGETW(xfer->request.wLength) == 0) && - xfer->actlen > 0 /* XXX PR/53503 */ - ) { - int rd = usbd_xfer_isread(xfer); - - usb_syncmem(&xfer->dmabuf, 0, xfer->actlen, - rd ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); - } - } - - if (xfertype == UE_ISOCHRONOUS || - xfertype == UE_INTERRUPT) { - struct dwc2_pipe *dpipe = DWC2_XFER2DPIPE(xfer); - - dwc2_free_bus_bandwidth(hsotg, - dwc2_hcd_get_ep_bandwidth(hsotg, dpipe), - xfer); - } - - qtd->urb = NULL; - timeout_del(&xfer->timeout_handle); - usb_rem_task(xfer->device, &xfer->abort_task); - MUTEX_ASSERT_LOCKED(&hsotg->lock); - - TAILQ_INSERT_TAIL(&sc->sc_complete, dxfer, xnext); - - mtx_leave(&hsotg->lock); - usb_schedsoftintr(&sc->sc_bus); - mtx_enter(&hsotg->lock); -} - - -int -_dwc2_hcd_start(struct dwc2_hsotg *hsotg) -{ - dev_dbg(hsotg->dev, "DWC OTG HCD START\n"); - - mtx_enter(&hsotg->lock); - - hsotg->lx_state = DWC2_L0; + /* + * Attempt to ensure this device is really a DWC_otg Controller. + * Read and verify the GSNPSID register contents. The value should be + * 0x45f4xxxx, 0x5531xxxx or 0x5532xxxx + */ - if (dwc2_is_device_mode(hsotg)) { - mtx_leave(&hsotg->lock); - return 0; /* why 0 ?? */ + hw->snpsid = dwc2_readl(hsotg, GSNPSID); + if ((hw->snpsid & GSNPSID_ID_MASK) != DWC2_OTG_ID && + (hw->snpsid & GSNPSID_ID_MASK) != DWC2_FS_IOT_ID && + (hw->snpsid & GSNPSID_ID_MASK) != DWC2_HS_IOT_ID) { + dev_err(hsotg->dev, "Bad value for GSNPSID: 0x%08x\n", + hw->snpsid); + return -ENODEV; } - dwc2_hcd_reinit(hsotg); - - mtx_leave(&hsotg->lock); + dev_dbg(hsotg->dev, "Core Release: %1x.%1x%1x%1x (snpsid=%x)\n", + hw->snpsid >> 12 & 0xf, hw->snpsid >> 8 & 0xf, + hw->snpsid >> 4 & 0xf, hw->snpsid & 0xf, hw->snpsid); return 0; } - -int dwc2_host_is_b_hnp_enabled(struct dwc2_hsotg *hsotg) -{ - - return false; -} diff --git a/sys/dev/usb/dwc2/dwc2.h b/sys/dev/usb/dwc2/dwc2.h index 2f1447091fc..61b84c62560 100644 --- a/sys/dev/usb/dwc2/dwc2.h +++ b/sys/dev/usb/dwc2/dwc2.h @@ -1,4 +1,4 @@ -/* $OpenBSD: dwc2.h,v 1.15 2021/07/22 18:32:33 mglocker Exp $ */ +/* $OpenBSD: dwc2.h,v 1.16 2022/09/04 08:42:39 mglocker Exp $ */ /* $NetBSD: dwc2.h,v 1.4 2014/12/23 16:20:06 macallan Exp $ */ /*- @@ -42,8 +42,8 @@ #define STATIC +// #define DWC2_DEBUG // #define VERBOSE_DEBUG -// #define DWC2_DUMP_FRREM // #define CONFIG_USB_DWC2_TRACK_MISSED_SOFS #define CONFIG_USB_DWC2_HOST 1 @@ -62,15 +62,8 @@ typedef int irqreturn_t; #define dma_addr_t bus_addr_t -#define DWC2_READ_4(hsotg, reg) \ - bus_space_read_4((hsotg)->hsotg_sc->sc_iot, (hsotg)->hsotg_sc->sc_ioh, (reg)) -#define DWC2_WRITE_4(hsotg, reg, data) \ - bus_space_write_4((hsotg)->hsotg_sc->sc_iot, (hsotg)->hsotg_sc->sc_ioh, (reg), (data)); - #ifdef DWC2_DEBUG extern int dwc2debug; -#define WARN_ON(x) KASSERT(!(x)) - #define dev_info(d,fmt,...) do { \ printf("%s: " fmt, device_xname(d), \ ## __VA_ARGS__); \ @@ -96,7 +89,6 @@ extern int dwc2debug; } \ } while (0) #else -#define WARN_ON(x) #define dev_info(...) do { } while (0) #define dev_warn(...) do { } while (0) #define dev_err(...) do { } while (0) @@ -114,16 +106,16 @@ enum usb_otg_state { OTG_STATE_B_PERIPHERAL, }; -#define usleep_range(l, u) do { DELAY(u); } while (0) - #define spinlock_t struct mutex -#define spin_lock_init(lock) mtx_init(lock, IPL_USB) +#define spin_lock_init(lock) mtx_init(lock, IPL_VM) #define spin_lock(l) do { mtx_enter(l); } while (0) #define spin_unlock(l) do { mtx_leave(l); } while (0) #define spin_lock_irqsave(l, f) \ do { mtx_enter(l); (void)(f); } while (0) +#define spin_trylock_irqsave(l, f) mtx_enter_try(l) + #define spin_unlock_irqrestore(l, f) \ do { mtx_leave(l); (void)(f); } while (0) @@ -241,23 +233,29 @@ usb_disabled(void) static inline void udelay(unsigned long usecs) { - DELAY(usecs); } static inline void -ndelay(unsigned long nsecs) +mdelay(unsigned long msecs) { + int loops = msecs; + while (loops--) + DELAY(1000); +} - DELAY(nsecs / 1000); +static inline void +usleep_range(unsigned long min, unsigned long max) +{ + DELAY((min + max) / 2); } +#define dwc2_msleep(x) mdelay(x) + #define EREMOTEIO EIO #define ECOMM EIO #define ENOTSUPP ENOTSUP -#define NS_TO_US(ns) ((ns + 500L) / 1000L) - void dw_timeout(void *); struct delayed_work { diff --git a/sys/dev/usb/dwc2/dwc2_core.c b/sys/dev/usb/dwc2/dwc2_core.c index e7c5a1433dd..f2eb065930c 100644 --- a/sys/dev/usb/dwc2/dwc2_core.c +++ b/sys/dev/usb/dwc2/dwc2_core.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dwc2_core.c,v 1.11 2021/07/27 13:36:59 mglocker Exp $ */ +/* $OpenBSD: dwc2_core.c,v 1.12 2022/09/04 08:42:39 mglocker Exp $ */ /* $NetBSD: dwc2_core.c,v 1.6 2014/04/03 06:34:58 skrll Exp $ */ /* @@ -42,7 +42,6 @@ * DWC_otg hardware. These services are used by both the Host Controller * Driver and the Peripheral Controller Driver. */ - #include #include #include @@ -65,477 +64,459 @@ #include #include -#if IS_ENABLED(CONFIG_USB_DWC2_HOST) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) /** - * dwc2_backup_host_registers() - Backup controller host registers. + * dwc2_backup_global_registers() - Backup global controller registers. * When suspending usb bus, registers needs to be backuped * if controller power is disabled once suspended. * * @hsotg: Programming view of the DWC_otg controller */ -STATIC int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg) +STATIC int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg) { - struct dwc2_hregs_backup *hr; - int i; + struct dwc2_gregs_backup *gr; dev_dbg(hsotg->dev, "%s\n", __func__); - /* Backup Host regs */ - hr = &hsotg->hr_backup; - hr->hcfg = DWC2_READ_4(hsotg, HCFG); - hr->haintmsk = DWC2_READ_4(hsotg, HAINTMSK); - for (i = 0; i < hsotg->core_params->host_channels; ++i) - hr->hcintmsk[i] = DWC2_READ_4(hsotg, HCINTMSK(i)); + /* Backup global regs */ + gr = &hsotg->gr_backup; - hr->hprt0 = DWC2_READ_4(hsotg, HPRT0); - hr->hfir = DWC2_READ_4(hsotg, HFIR); - hr->valid = true; + gr->gotgctl = dwc2_readl(hsotg, GOTGCTL); + gr->gintmsk = dwc2_readl(hsotg, GINTMSK); + gr->gahbcfg = dwc2_readl(hsotg, GAHBCFG); + gr->gusbcfg = dwc2_readl(hsotg, GUSBCFG); + gr->grxfsiz = dwc2_readl(hsotg, GRXFSIZ); + gr->gnptxfsiz = dwc2_readl(hsotg, GNPTXFSIZ); + gr->gdfifocfg = dwc2_readl(hsotg, GDFIFOCFG); + gr->pcgcctl1 = dwc2_readl(hsotg, PCGCCTL1); + gr->glpmcfg = dwc2_readl(hsotg, GLPMCFG); + gr->gi2cctl = dwc2_readl(hsotg, GI2CCTL); + gr->pcgcctl = dwc2_readl(hsotg, PCGCTL); + gr->valid = true; return 0; } /** - * dwc2_restore_host_registers() - Restore controller host registers. + * dwc2_restore_global_registers() - Restore controller global registers. * When resuming usb bus, device registers needs to be restored * if controller power were disabled. * * @hsotg: Programming view of the DWC_otg controller */ -STATIC int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg) +STATIC int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg) { - struct dwc2_hregs_backup *hr; - int i; + struct dwc2_gregs_backup *gr; dev_dbg(hsotg->dev, "%s\n", __func__); - /* Restore host regs */ - hr = &hsotg->hr_backup; - if (!hr->valid) { - dev_err(hsotg->dev, "%s: no host registers to restore\n", - __func__); + /* Restore global regs */ + gr = &hsotg->gr_backup; + if (!gr->valid) { + dev_err(hsotg->dev, "%s: no global registers to restore\n", + __func__); return -EINVAL; } - hr->valid = false; - - DWC2_WRITE_4(hsotg, HCFG, hr->hcfg); - DWC2_WRITE_4(hsotg, HAINTMSK, hr->haintmsk); - - for (i = 0; i < hsotg->core_params->host_channels; ++i) - DWC2_WRITE_4(hsotg, HCINTMSK(i), hr->hcintmsk[i]); + gr->valid = false; - DWC2_WRITE_4(hsotg, HPRT0, hr->hprt0); - DWC2_WRITE_4(hsotg, HFIR, hr->hfir); - hsotg->frame_number = 0; + dwc2_writel(hsotg, 0xffffffff, GINTSTS); + dwc2_writel(hsotg, gr->gotgctl, GOTGCTL); + dwc2_writel(hsotg, gr->gintmsk, GINTMSK); + dwc2_writel(hsotg, gr->gusbcfg, GUSBCFG); + dwc2_writel(hsotg, gr->gahbcfg, GAHBCFG); + dwc2_writel(hsotg, gr->grxfsiz, GRXFSIZ); + dwc2_writel(hsotg, gr->gnptxfsiz, GNPTXFSIZ); + dwc2_writel(hsotg, gr->gdfifocfg, GDFIFOCFG); + dwc2_writel(hsotg, gr->pcgcctl1, PCGCCTL1); + dwc2_writel(hsotg, gr->glpmcfg, GLPMCFG); + dwc2_writel(hsotg, gr->pcgcctl, PCGCTL); + dwc2_writel(hsotg, gr->gi2cctl, GI2CCTL); return 0; } -#else -static inline int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg) -{ return 0; } - -static inline int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg) -{ return 0; } -#endif -#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ - IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) /** - * dwc2_backup_device_registers() - Backup controller device registers. - * When suspending usb bus, registers needs to be backuped - * if controller power is disabled once suspended. + * dwc2_exit_partial_power_down() - Exit controller from Partial Power Down. * * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether resume is initiated by Reset. + * @restore: Controller registers need to be restored */ -STATIC int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg) +int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, int rem_wakeup, + bool restore) { - struct dwc2_dregs_backup *dr; - int i; - - dev_dbg(hsotg->dev, "%s\n", __func__); - - /* Backup dev regs */ - dr = &hsotg->dr_backup; - - dr->dcfg = DWC2_READ_4(hsotg, DCFG); - dr->dctl = DWC2_READ_4(hsotg, DCTL); - dr->daintmsk = DWC2_READ_4(hsotg, DAINTMSK); - dr->diepmsk = DWC2_READ_4(hsotg, DIEPMSK); - dr->doepmsk = DWC2_READ_4(hsotg, DOEPMSK); - - for (i = 0; i < hsotg->num_of_eps; i++) { - /* Backup IN EPs */ - dr->diepctl[i] = DWC2_READ_4(hsotg, DIEPCTL(i)); - - /* Ensure DATA PID is correctly configured */ - if (dr->diepctl[i] & DXEPCTL_DPID) - dr->diepctl[i] |= DXEPCTL_SETD1PID; - else - dr->diepctl[i] |= DXEPCTL_SETD0PID; - - dr->dieptsiz[i] = DWC2_READ_4(hsotg, DIEPTSIZ(i)); - dr->diepdma[i] = DWC2_READ_4(hsotg, DIEPDMA(i)); + struct dwc2_gregs_backup *gr; - /* Backup OUT EPs */ - dr->doepctl[i] = DWC2_READ_4(hsotg, DOEPCTL(i)); + gr = &hsotg->gr_backup; - /* Ensure DATA PID is correctly configured */ - if (dr->doepctl[i] & DXEPCTL_DPID) - dr->doepctl[i] |= DXEPCTL_SETD1PID; - else - dr->doepctl[i] |= DXEPCTL_SETD0PID; + /* + * Restore host or device regisers with the same mode core enterted + * to partial power down by checking "GOTGCTL_CURMODE_HOST" backup + * value of the "gotgctl" register. + */ + if (gr->gotgctl & GOTGCTL_CURMODE_HOST) + return dwc2_host_exit_partial_power_down(hsotg, rem_wakeup, + restore); + else + return dwc2_gadget_exit_partial_power_down(hsotg, restore); +} - dr->doeptsiz[i] = DWC2_READ_4(hsotg, DOEPTSIZ(i)); - dr->doepdma[i] = DWC2_READ_4(hsotg, DOEPDMA(i)); - } - dr->valid = true; - return 0; +/** + * dwc2_enter_partial_power_down() - Put controller in Partial Power Down. + * + * @hsotg: Programming view of the DWC_otg controller + */ +int dwc2_enter_partial_power_down(struct dwc2_hsotg *hsotg) +{ + if (dwc2_is_host_mode(hsotg)) + return dwc2_host_enter_partial_power_down(hsotg); + else + return dwc2_gadget_enter_partial_power_down(hsotg); } /** - * dwc2_restore_device_registers() - Restore controller device registers. - * When resuming usb bus, device registers needs to be restored - * if controller power were disabled. + * dwc2_restore_essential_regs() - Restore essiential regs of core. * * @hsotg: Programming view of the DWC_otg controller + * @rmode: Restore mode, enabled in case of remote-wakeup. + * @is_host: Host or device mode. */ -STATIC int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg) +static void dwc2_restore_essential_regs(struct dwc2_hsotg *hsotg, int rmode, + int is_host) { + u32 pcgcctl; + struct dwc2_gregs_backup *gr; struct dwc2_dregs_backup *dr; - u32 dctl; - int i; - - dev_dbg(hsotg->dev, "%s\n", __func__); + struct dwc2_hregs_backup *hr; - /* Restore dev regs */ + gr = &hsotg->gr_backup; dr = &hsotg->dr_backup; - if (!dr->valid) { - dev_err(hsotg->dev, "%s: no device registers to restore\n", - __func__); - return -EINVAL; - } - dr->valid = false; - - DWC2_WRITE_4(hsotg, DCFG, dr->dcfg); - DWC2_WRITE_4(hsotg, DCTL, dr->dctl); - DWC2_WRITE_4(hsotg, DAINTMSK, dr->daintmsk); - DWC2_WRITE_4(hsotg, DIEPMSK, dr->diepmsk); - DWC2_WRITE_4(hsotg, DOEPMSK, dr->doepmsk); + hr = &hsotg->hr_backup; - for (i = 0; i < hsotg->num_of_eps; i++) { - /* Restore IN EPs */ - DWC2_WRITE_4(hsotg, DIEPCTL(i), dr->diepctl[i]); - DWC2_WRITE_4(hsotg, DIEPTSIZ(i), dr->dieptsiz[i]); - DWC2_WRITE_4(hsotg, DIEPDMA(i), dr->diepdma[i]); + dev_dbg(hsotg->dev, "%s: restoring essential regs\n", __func__); - /* Restore OUT EPs */ - DWC2_WRITE_4(hsotg, DOEPCTL(i), dr->doepctl[i]); - DWC2_WRITE_4(hsotg, DOEPTSIZ(i), dr->doeptsiz[i]); - DWC2_WRITE_4(hsotg, DOEPDMA(i), dr->doepdma[i]); + /* Load restore values for [31:14] bits */ + pcgcctl = (gr->pcgcctl & 0xffffc000); + /* If High Speed */ + if (is_host) { + if (!(pcgcctl & PCGCTL_P2HD_PRT_SPD_MASK)) + pcgcctl |= (1 << 17); + } else { + if (!(pcgcctl & PCGCTL_P2HD_DEV_ENUM_SPD_MASK)) + pcgcctl |= (1 << 17); } + dwc2_writel(hsotg, pcgcctl, PCGCTL); - /* Set the Power-On Programming done bit */ - dctl = DWC2_READ_4(hsotg, DCTL); - dctl |= DCTL_PWRONPRGDONE; - DWC2_WRITE_4(hsotg, DCTL, dctl); + /* Umnask global Interrupt in GAHBCFG and restore it */ + dwc2_writel(hsotg, gr->gahbcfg | GAHBCFG_GLBL_INTR_EN, GAHBCFG); - return 0; -} -#else -static inline int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg) -{ return 0; } + /* Clear all pending interupts */ + dwc2_writel(hsotg, 0xffffffff, GINTSTS); -static inline int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg) -{ return 0; } -#endif + /* Unmask restore done interrupt */ + dwc2_writel(hsotg, GINTSTS_RESTOREDONE, GINTMSK); -/** - * dwc2_backup_global_registers() - Backup global controller registers. - * When suspending usb bus, registers needs to be backuped - * if controller power is disabled once suspended. - * - * @hsotg: Programming view of the DWC_otg controller - */ -STATIC int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg) -{ - struct dwc2_gregs_backup *gr; - int i; + /* Restore GUSBCFG and HCFG/DCFG */ + dwc2_writel(hsotg, gr->gusbcfg, GUSBCFG); - /* Backup global regs */ - gr = &hsotg->gr_backup; + if (is_host) { + dwc2_writel(hsotg, hr->hcfg, HCFG); + if (rmode) + pcgcctl |= PCGCTL_RESTOREMODE; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(10); - gr->gotgctl = DWC2_READ_4(hsotg, GOTGCTL); - gr->gintmsk = DWC2_READ_4(hsotg, GINTMSK); - gr->gahbcfg = DWC2_READ_4(hsotg, GAHBCFG); - gr->gusbcfg = DWC2_READ_4(hsotg, GUSBCFG); - gr->grxfsiz = DWC2_READ_4(hsotg, GRXFSIZ); - gr->gnptxfsiz = DWC2_READ_4(hsotg, GNPTXFSIZ); - gr->hptxfsiz = DWC2_READ_4(hsotg, HPTXFSIZ); - gr->gdfifocfg = DWC2_READ_4(hsotg, GDFIFOCFG); - for (i = 0; i < MAX_EPS_CHANNELS; i++) - gr->dtxfsiz[i] = DWC2_READ_4(hsotg, DPTXFSIZN(i)); + pcgcctl |= PCGCTL_ESS_REG_RESTORED; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(10); + } else { + dwc2_writel(hsotg, dr->dcfg, DCFG); + if (!rmode) + pcgcctl |= PCGCTL_RESTOREMODE | PCGCTL_RSTPDWNMODULE; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(10); - gr->valid = true; - return 0; + pcgcctl |= PCGCTL_ESS_REG_RESTORED; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(10); + } } /** - * dwc2_restore_global_registers() - Restore controller global registers. - * When resuming usb bus, device registers needs to be restored - * if controller power were disabled. + * dwc2_hib_restore_common() - Common part of restore routine. * * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: Remote-wakeup, enabled in case of remote-wakeup. + * @is_host: Host or device mode. */ -STATIC int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg) -{ - struct dwc2_gregs_backup *gr; - int i; +void dwc2_hib_restore_common(struct dwc2_hsotg *hsotg, int rem_wakeup, + int is_host) +{ + u32 gpwrdn; + + /* Switch-on voltage to the core */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_PWRDNSWTCH; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Reset core */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_PWRDNRSTN; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Enable restore from PMU */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_RESTORE; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Disable Power Down Clamp */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_PWRDNCLMP; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(50); + + if (!is_host && rem_wakeup) + udelay(70); + + /* Deassert reset core */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_PWRDNRSTN; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Disable PMU interrupt */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_PMUINTSEL; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Set Restore Essential Regs bit in PCGCCTL register */ + dwc2_restore_essential_regs(hsotg, rem_wakeup, is_host); - dev_dbg(hsotg->dev, "%s\n", __func__); + /* + * Wait For Restore_done Interrupt. This mechanism of polling the + * interrupt is introduced to avoid any possible race conditions + */ + if (dwc2_hsotg_wait_bit_set(hsotg, GINTSTS, GINTSTS_RESTOREDONE, + 20000)) { + dev_dbg(hsotg->dev, + "%s: Restore Done wasn't generated here\n", + __func__); + } else { + dev_dbg(hsotg->dev, "restore done generated here\n"); - /* Restore global regs */ - gr = &hsotg->gr_backup; - if (!gr->valid) { - dev_err(hsotg->dev, "%s: no global registers to restore\n", - __func__); - return -EINVAL; + /* + * To avoid restore done interrupt storm after restore is + * generated clear GINTSTS_RESTOREDONE bit. + */ + dwc2_writel(hsotg, GINTSTS_RESTOREDONE, GINTSTS); } - gr->valid = false; - - DWC2_WRITE_4(hsotg, GINTSTS, 0xffffffff); - DWC2_WRITE_4(hsotg, GOTGCTL, gr->gotgctl); - DWC2_WRITE_4(hsotg, GINTMSK, gr->gintmsk); - DWC2_WRITE_4(hsotg, GUSBCFG, gr->gusbcfg); - DWC2_WRITE_4(hsotg, GAHBCFG, gr->gahbcfg); - DWC2_WRITE_4(hsotg, GRXFSIZ, gr->grxfsiz); - DWC2_WRITE_4(hsotg, GNPTXFSIZ, gr->gnptxfsiz); - DWC2_WRITE_4(hsotg, HPTXFSIZ, gr->hptxfsiz); - DWC2_WRITE_4(hsotg, GDFIFOCFG, gr->gdfifocfg); - for (i = 0; i < MAX_EPS_CHANNELS; i++) - DWC2_WRITE_4(hsotg, DPTXFSIZN(i), gr->dtxfsiz[i]); - - return 0; } /** - * dwc2_exit_hibernation() - Exit controller from Partial Power Down. - * - * @hsotg: Programming view of the DWC_otg controller - * @restore: Controller registers need to be restored + * dwc2_wait_for_mode() - Waits for the controller mode. + * @hsotg: Programming view of the DWC_otg controller. + * @host_mode: If true, waits for host mode, otherwise device mode. */ -int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, bool restore) +static void dwc2_wait_for_mode(struct dwc2_hsotg *hsotg, + bool host_mode) { - u32 pcgcctl; - int ret = 0; - - if (!hsotg->core_params->hibernation) - return -ENOTSUP; - - pcgcctl = DWC2_READ_4(hsotg, PCGCTL); - pcgcctl &= ~PCGCTL_STOPPCLK; - DWC2_WRITE_4(hsotg, PCGCTL, pcgcctl); - - pcgcctl = DWC2_READ_4(hsotg, PCGCTL); - pcgcctl &= ~PCGCTL_PWRCLMP; - DWC2_WRITE_4(hsotg, PCGCTL, pcgcctl); - - pcgcctl = DWC2_READ_4(hsotg, PCGCTL); - pcgcctl &= ~PCGCTL_RSTPDWNMODULE; - DWC2_WRITE_4(hsotg, PCGCTL, pcgcctl); - - udelay(100); - if (restore) { - ret = dwc2_restore_global_registers(hsotg); - if (ret) { - dev_err(hsotg->dev, "%s: failed to restore registers\n", - __func__); - return ret; + struct timeval start; + struct timeval end; + unsigned int timeout = 110; + + dev_vdbg(hsotg->dev, "Waiting for %s mode\n", + host_mode ? "host" : "device"); + + getmicrotime(&start); + + while (1) { + unsigned int ms; + + if (dwc2_is_host_mode(hsotg) == host_mode) { + dev_vdbg(hsotg->dev, "%s mode set\n", + host_mode ? "Host" : "Device"); + break; } - if (dwc2_is_host_mode(hsotg)) { - ret = dwc2_restore_host_registers(hsotg); - if (ret) { - dev_err(hsotg->dev, "%s: failed to restore host registers\n", - __func__); - return ret; - } - } else { - ret = dwc2_restore_device_registers(hsotg); - if (ret) { - dev_err(hsotg->dev, "%s: failed to restore device registers\n", - __func__); - return ret; - } + + getmicrotime(&end); + ms = (end.tv_usec - start.tv_usec) / 1000; + + if (ms >= timeout) { + dev_warn(hsotg->dev, "%s: Couldn't set %s mode\n", + __func__, host_mode ? "host" : "device"); + break; } - } - return ret; + usleep_range(1000, 2000); + } } /** - * dwc2_enter_hibernation() - Put controller in Partial Power Down. + * dwc2_iddig_filter_enabled() - Returns true if the IDDIG debounce + * filter is enabled. * - * @hsotg: Programming view of the DWC_otg controller + * @hsotg: Programming view of DWC_otg controller */ -int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg) +static bool dwc2_iddig_filter_enabled(struct dwc2_hsotg *hsotg) { - u32 pcgcctl; - int ret = 0; + u32 gsnpsid; + u32 ghwcfg4; - if (!hsotg->core_params->hibernation) - return -ENOTSUP; - - /* Backup all registers */ - ret = dwc2_backup_global_registers(hsotg); - if (ret) { - dev_err(hsotg->dev, "%s: failed to backup global registers\n", - __func__); - return ret; - } + if (!dwc2_hw_is_otg(hsotg)) + return false; - if (dwc2_is_host_mode(hsotg)) { - ret = dwc2_backup_host_registers(hsotg); - if (ret) { - dev_err(hsotg->dev, "%s: failed to backup host registers\n", - __func__); - return ret; - } - } else { - ret = dwc2_backup_device_registers(hsotg); - if (ret) { - dev_err(hsotg->dev, "%s: failed to backup device registers\n", - __func__); - return ret; - } - } + /* Check if core configuration includes the IDDIG filter. */ + ghwcfg4 = dwc2_readl(hsotg, GHWCFG4); + if (!(ghwcfg4 & GHWCFG4_IDDIG_FILT_EN)) + return false; /* - * Clear any pending interrupts since dwc2 will not be able to - * clear them after entering hibernation. + * Check if the IDDIG debounce filter is bypassed. Available + * in core version >= 3.10a. */ - DWC2_WRITE_4(hsotg, GINTSTS, 0xffffffff); - - /* Put the controller in low power state */ - pcgcctl = DWC2_READ_4(hsotg, PCGCTL); - - pcgcctl |= PCGCTL_PWRCLMP; - DWC2_WRITE_4(hsotg, PCGCTL, pcgcctl); - ndelay(20); - - pcgcctl |= PCGCTL_RSTPDWNMODULE; - DWC2_WRITE_4(hsotg, PCGCTL, pcgcctl); - ndelay(20); + gsnpsid = dwc2_readl(hsotg, GSNPSID); + if (gsnpsid >= DWC2_CORE_REV_3_10a) { + u32 gotgctl = dwc2_readl(hsotg, GOTGCTL); - pcgcctl |= PCGCTL_STOPPCLK; - DWC2_WRITE_4(hsotg, PCGCTL, pcgcctl); + if (gotgctl & GOTGCTL_DBNCE_FLTR_BYPASS) + return false; + } - return ret; + return true; } -/** - * dwc2_enable_common_interrupts() - Initializes the commmon interrupts, - * used in both device and host modes +/* + * dwc2_enter_hibernation() - Common function to enter hibernation. * * @hsotg: Programming view of the DWC_otg controller + * @is_host: True if core is in host mode. + * + * Return: 0 if successful, negative error code otherwise */ -STATIC void dwc2_enable_common_interrupts(struct dwc2_hsotg *hsotg) +int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg, int is_host) { - u32 intmsk; - - /* Clear any pending OTG Interrupts */ - DWC2_WRITE_4(hsotg, GOTGINT, 0xffffffff); - - /* Clear any pending interrupts */ - DWC2_WRITE_4(hsotg, GINTSTS, 0xffffffff); - - /* Enable the interrupts in the GINTMSK */ - intmsk = GINTSTS_MODEMIS | GINTSTS_OTGINT; - - if (hsotg->core_params->dma_enable <= 0) - intmsk |= GINTSTS_RXFLVL; - if (hsotg->core_params->external_id_pin_ctl <= 0) - intmsk |= GINTSTS_CONIDSTSCHNG; - - intmsk |= GINTSTS_WKUPINT | GINTSTS_USBSUSP | - GINTSTS_SESSREQINT; - - DWC2_WRITE_4(hsotg, GINTMSK, intmsk); + if (is_host) + return dwc2_host_enter_hibernation(hsotg); + else + return dwc2_gadget_enter_hibernation(hsotg); } /* - * Initializes the FSLSPClkSel field of the HCFG register depending on the - * PHY type + * dwc2_exit_hibernation() - Common function to exit from hibernation. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: Remote-wakeup, enabled in case of remote-wakeup. + * @reset: Enabled in case of restore with reset. + * @is_host: True if core is in host mode. + * + * Return: 0 if successful, negative error code otherwise */ -STATIC void dwc2_init_fs_ls_pclk_sel(struct dwc2_hsotg *hsotg) +int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup, + int reset, int is_host) { - u32 hcfg, val; - - if ((hsotg->hw_params.hs_phy_type == GHWCFG2_HS_PHY_TYPE_ULPI && - hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED && - hsotg->core_params->ulpi_fs_ls > 0) || - hsotg->core_params->phy_type == DWC2_PHY_TYPE_PARAM_FS) { - /* Full speed PHY */ - val = HCFG_FSLSPCLKSEL_48_MHZ; - } else { - /* High speed PHY running at full speed or high speed */ - val = HCFG_FSLSPCLKSEL_30_60_MHZ; - } - - dev_dbg(hsotg->dev, "Initializing HCFG.FSLSPClkSel to %08x\n", val); - hcfg = DWC2_READ_4(hsotg, HCFG); - hcfg &= ~HCFG_FSLSPCLKSEL_MASK; - hcfg |= val << HCFG_FSLSPCLKSEL_SHIFT; - DWC2_WRITE_4(hsotg, HCFG, hcfg); + if (is_host) + return dwc2_host_exit_hibernation(hsotg, rem_wakeup, reset); + else + return dwc2_gadget_exit_hibernation(hsotg, rem_wakeup, reset); } /* * Do core a soft reset of the core. Be careful with this because it * resets all the internal state machines of the core. */ -int dwc2_core_reset(struct dwc2_hsotg *hsotg) +int dwc2_core_reset(struct dwc2_hsotg *hsotg, bool skip_wait) { u32 greset; - int count = 0; + bool wait_for_host_mode = false; dev_vdbg(hsotg->dev, "%s()\n", __func__); + /* + * If the current mode is host, either due to the force mode + * bit being set (which persists after core reset) or the + * connector id pin, a core soft reset will temporarily reset + * the mode to device. A delay from the IDDIG debounce filter + * will occur before going back to host mode. + * + * Determine whether we will go back into host mode after a + * reset and account for this delay after the reset. + */ + if (dwc2_iddig_filter_enabled(hsotg)) { + u32 gotgctl = dwc2_readl(hsotg, GOTGCTL); + u32 gusbcfg = dwc2_readl(hsotg, GUSBCFG); + + if (!(gotgctl & GOTGCTL_CONID_B) || + (gusbcfg & GUSBCFG_FORCEHOSTMODE)) { + wait_for_host_mode = true; + } + } + /* Core Soft Reset */ - greset = DWC2_READ_4(hsotg, GRSTCTL); + greset = dwc2_readl(hsotg, GRSTCTL); greset |= GRSTCTL_CSFTRST; - DWC2_WRITE_4(hsotg, GRSTCTL, greset); - do { - udelay(1); - greset = DWC2_READ_4(hsotg, GRSTCTL); - if (++count > 50) { - dev_warn(hsotg->dev, - "%s() HANG! Soft Reset GRSTCTL=%0x\n", - __func__, greset); + dwc2_writel(hsotg, greset, GRSTCTL); + + if ((hsotg->hw_params.snpsid & DWC2_CORE_REV_MASK) < + (DWC2_CORE_REV_4_20a & DWC2_CORE_REV_MASK)) { + if (dwc2_hsotg_wait_bit_clear(hsotg, GRSTCTL, + GRSTCTL_CSFTRST, 10000)) { + dev_warn(hsotg->dev, "%s: HANG! Soft Reset timeout GRSTCTL_CSFTRST\n", + __func__); return -EBUSY; } - } while (greset & GRSTCTL_CSFTRST); - - /* Wait for AHB master IDLE state */ - count = 0; - do { - udelay(1); - greset = DWC2_READ_4(hsotg, GRSTCTL); - if (++count > 50) { - dev_warn(hsotg->dev, - "%s() HANG! AHB Idle GRSTCTL=%0x\n", - __func__, greset); + } else { + if (dwc2_hsotg_wait_bit_set(hsotg, GRSTCTL, + GRSTCTL_CSFTRST_DONE, 10000)) { + dev_warn(hsotg->dev, "%s: HANG! Soft Reset timeout GRSTCTL_CSFTRST_DONE\n", + __func__); return -EBUSY; } - } while (!(greset & GRSTCTL_AHBIDLE)); + greset = dwc2_readl(hsotg, GRSTCTL); + greset &= ~GRSTCTL_CSFTRST; + greset |= GRSTCTL_CSFTRST_DONE; + dwc2_writel(hsotg, greset, GRSTCTL); + } + + /* + * Switching from device mode to host mode by disconnecting + * device cable core enters and exits form hibernation. + * However, the fifo map remains not cleared. It results + * to a WARNING (WARNING: CPU: 5 PID: 0 at drivers/usb/dwc2/ + * gadget.c:307 dwc2_hsotg_init_fifo+0x12/0x152 [dwc2]) + * if in host mode we disconnect the micro a to b host + * cable. Because core reset occurs. + * To avoid the WARNING, fifo_map should be cleared + * in dwc2_core_reset() function by taking into account configs. + * fifo_map must be cleared only if driver is configured in + * "CONFIG_USB_DWC2_PERIPHERAL" or "CONFIG_USB_DWC2_DUAL_ROLE" + * mode. + */ + dwc2_clear_fifo_map(hsotg); + + /* Wait for AHB master IDLE state */ + if (dwc2_hsotg_wait_bit_set(hsotg, GRSTCTL, GRSTCTL_AHBIDLE, 10000)) { + dev_warn(hsotg->dev, "%s: HANG! AHB Idle timeout GRSTCTL GRSTCTL_AHBIDLE\n", + __func__); + return -EBUSY; + } + + if (wait_for_host_mode && !skip_wait) + dwc2_wait_for_mode(hsotg, true); return 0; } -/* - * Force the mode of the controller. +/** + * dwc2_force_mode() - Force the mode of the controller. * * Forcing the mode is needed for two cases: * * 1) If the dr_mode is set to either HOST or PERIPHERAL we force the * controller to stay in a particular mode regardless of ID pin - * changes. We do this usually after a core reset. + * changes. We do this once during probe. * * 2) During probe we want to read reset values of the hw * configuration registers that are only available in either host or @@ -548,13 +529,15 @@ int dwc2_core_reset(struct dwc2_hsotg *hsotg) * Checks are done in this function to determine whether doing a force * would be valid or not. * - * If a force is done, it requires a 25ms delay to take effect. + * If a force is done, it requires a IDDIG debounce filter delay if + * the filter is configured and enabled. We poll the current mode of + * the controller to account for this delay. * - * Returns true if the mode was forced. + * @hsotg: Programming view of DWC_otg controller + * @host: Host mode flag */ -STATIC bool dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host) +void dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host) { - struct dwc2_softc *sc = hsotg->hsotg_sc; u32 gusbcfg; u32 set; u32 clear; @@ -565,53 +548,58 @@ STATIC bool dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host) * Force mode has no effect if the hardware is not OTG. */ if (!dwc2_hw_is_otg(hsotg)) - return false; + return; /* * If dr_mode is either peripheral or host only, there is no * need to ever force the mode to the opposite mode. */ - if (host && hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) { - WARN_ON(1); - return false; - } + if (WARN_ON(host && hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)) + return; - if (!host && hsotg->dr_mode == USB_DR_MODE_HOST) { - WARN_ON(1); - return false; - } + if (WARN_ON(!host && hsotg->dr_mode == USB_DR_MODE_HOST)) + return; - gusbcfg = DWC2_READ_4(hsotg, GUSBCFG); + gusbcfg = dwc2_readl(hsotg, GUSBCFG); set = host ? GUSBCFG_FORCEHOSTMODE : GUSBCFG_FORCEDEVMODE; clear = host ? GUSBCFG_FORCEDEVMODE : GUSBCFG_FORCEHOSTMODE; gusbcfg &= ~clear; gusbcfg |= set; - DWC2_WRITE_4(hsotg, GUSBCFG, gusbcfg); + dwc2_writel(hsotg, gusbcfg, GUSBCFG); - usb_delay_ms(&sc->sc_bus, 25); - return true; + dwc2_wait_for_mode(hsotg, host); + return; } -/* - * Clears the force mode bits. +/** + * dwc2_clear_force_mode() - Clears the force mode bits. + * + * After clearing the bits, wait up to 100 ms to account for any + * potential IDDIG filter delay. We can't know if we expect this delay + * or not because the value of the connector ID status is affected by + * the force mode. We only need to call this once during probe if + * dr_mode == OTG. + * + * @hsotg: Programming view of DWC_otg controller */ STATIC void dwc2_clear_force_mode(struct dwc2_hsotg *hsotg) { - struct dwc2_softc *sc = hsotg->hsotg_sc; u32 gusbcfg; - gusbcfg = DWC2_READ_4(hsotg, GUSBCFG); + if (!dwc2_hw_is_otg(hsotg)) + return; + + dev_dbg(hsotg->dev, "Clearing force mode bits\n"); + + gusbcfg = dwc2_readl(hsotg, GUSBCFG); gusbcfg &= ~GUSBCFG_FORCEHOSTMODE; gusbcfg &= ~GUSBCFG_FORCEDEVMODE; - DWC2_WRITE_4(hsotg, GUSBCFG, gusbcfg); + dwc2_writel(hsotg, gusbcfg, GUSBCFG); - /* - * NOTE: This long sleep is _very_ important, otherwise the core will - * not stay in host mode after a connector ID change! - */ - usb_delay_ms(&sc->sc_bus, 25); + if (dwc2_iddig_filter_enabled(hsotg)) + dwc2_msleep(100); } /* @@ -621,7 +609,13 @@ void dwc2_force_dr_mode(struct dwc2_hsotg *hsotg) { switch (hsotg->dr_mode) { case USB_DR_MODE_HOST: - dwc2_force_mode(hsotg, true); + /* + * NOTE: This is required for some rockchip soc based + * platforms on their host-only dwc2. + */ + if (!dwc2_hw_is_otg(hsotg)) + dwc2_msleep(50); + break; case USB_DR_MODE_PERIPHERAL: dwc2_force_mode(hsotg, false); @@ -637,1686 +631,17 @@ void dwc2_force_dr_mode(struct dwc2_hsotg *hsotg) } /* - * Do core a soft reset of the core. Be careful with this because it - * resets all the internal state machines of the core. - * - * Additionally this will apply force mode as per the hsotg->dr_mode - * parameter. + * dwc2_enable_acg - enable active clock gating feature */ -int dwc2_core_reset_and_force_dr_mode(struct dwc2_hsotg *hsotg) +void dwc2_enable_acg(struct dwc2_hsotg *hsotg) { - int retval; - - retval = dwc2_core_reset(hsotg); - if (retval) - return retval; + if (hsotg->params.acg_enable) { + u32 pcgcctl1 = dwc2_readl(hsotg, PCGCCTL1); - dwc2_force_dr_mode(hsotg); - return 0; -} - -STATIC int dwc2_fs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) -{ - u32 usbcfg, i2cctl; - int retval = 0; - - /* - * core_init() is now called on every switch so only call the - * following for the first time through - */ - if (select_phy) { - dev_dbg(hsotg->dev, "FS PHY selected\n"); - - usbcfg = DWC2_READ_4(hsotg, GUSBCFG); - if (!(usbcfg & GUSBCFG_PHYSEL)) { - usbcfg |= GUSBCFG_PHYSEL; - DWC2_WRITE_4(hsotg, GUSBCFG, usbcfg); - - /* Reset after a PHY select */ - retval = dwc2_core_reset_and_force_dr_mode(hsotg); - - if (retval) { - dev_err(hsotg->dev, - "%s: Reset failed, aborting", __func__); - return retval; - } - } - } - - /* - * Program DCFG.DevSpd or HCFG.FSLSPclkSel to 48Mhz in FS. Also - * do this on HNP Dev/Host mode switches (done in dev_init and - * host_init). - */ - if (dwc2_is_host_mode(hsotg)) - dwc2_init_fs_ls_pclk_sel(hsotg); - - if (hsotg->core_params->i2c_enable > 0) { - dev_dbg(hsotg->dev, "FS PHY enabling I2C\n"); - - /* Program GUSBCFG.OtgUtmiFsSel to I2C */ - usbcfg = DWC2_READ_4(hsotg, GUSBCFG); - usbcfg |= GUSBCFG_OTG_UTMI_FS_SEL; - DWC2_WRITE_4(hsotg, GUSBCFG, usbcfg); - - /* Program GI2CCTL.I2CEn */ - i2cctl = DWC2_READ_4(hsotg, GI2CCTL); - i2cctl &= ~GI2CCTL_I2CDEVADDR_MASK; - i2cctl |= 1 << GI2CCTL_I2CDEVADDR_SHIFT; - i2cctl &= ~GI2CCTL_I2CEN; - DWC2_WRITE_4(hsotg, GI2CCTL, i2cctl); - i2cctl |= GI2CCTL_I2CEN; - DWC2_WRITE_4(hsotg, GI2CCTL, i2cctl); - } - - return retval; -} - -STATIC int dwc2_hs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) -{ - u32 usbcfg, usbcfg_old; - int retval = 0; - - if (!select_phy) - return 0; - - usbcfg = usbcfg_old = DWC2_READ_4(hsotg, GUSBCFG); - - /* - * HS PHY parameters. These parameters are preserved during soft reset - * so only program the first time. Do a soft reset immediately after - * setting phyif. - */ - switch (hsotg->core_params->phy_type) { - case DWC2_PHY_TYPE_PARAM_ULPI: - /* ULPI interface */ - dev_dbg(hsotg->dev, "HS ULPI PHY selected\n"); - usbcfg |= GUSBCFG_ULPI_UTMI_SEL; - usbcfg &= ~(GUSBCFG_PHYIF16 | GUSBCFG_DDRSEL); - if (hsotg->core_params->phy_ulpi_ddr > 0) - usbcfg |= GUSBCFG_DDRSEL; - break; - case DWC2_PHY_TYPE_PARAM_UTMI: - /* UTMI+ interface */ - dev_dbg(hsotg->dev, "HS UTMI+ PHY selected\n"); - usbcfg &= ~(GUSBCFG_ULPI_UTMI_SEL | GUSBCFG_PHYIF16); - if (hsotg->core_params->phy_utmi_width == 16) - usbcfg |= GUSBCFG_PHYIF16; - break; - default: - dev_err(hsotg->dev, "FS PHY selected at HS!\n"); - break; - } - - if (usbcfg != usbcfg_old) { - DWC2_WRITE_4(hsotg, GUSBCFG, usbcfg); - - /* Reset after setting the PHY parameters */ - retval = dwc2_core_reset_and_force_dr_mode(hsotg); - if (retval) { - dev_err(hsotg->dev, - "%s: Reset failed, aborting", __func__); - return retval; - } - } - - return retval; -} - -STATIC int dwc2_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) -{ - u32 usbcfg; - int retval = 0; - - if (hsotg->core_params->speed == DWC2_SPEED_PARAM_FULL && - hsotg->core_params->phy_type == DWC2_PHY_TYPE_PARAM_FS) { - /* If FS mode with FS PHY */ - retval = dwc2_fs_phy_init(hsotg, select_phy); - if (retval) - return retval; - } else { - /* High speed PHY */ - retval = dwc2_hs_phy_init(hsotg, select_phy); - if (retval) - return retval; - } - - if (hsotg->hw_params.hs_phy_type == GHWCFG2_HS_PHY_TYPE_ULPI && - hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED && - hsotg->core_params->ulpi_fs_ls > 0) { - dev_dbg(hsotg->dev, "Setting ULPI FSLS\n"); - usbcfg = DWC2_READ_4(hsotg, GUSBCFG); - usbcfg |= GUSBCFG_ULPI_FS_LS; - usbcfg |= GUSBCFG_ULPI_CLK_SUSP_M; - DWC2_WRITE_4(hsotg, GUSBCFG, usbcfg); - } else { - usbcfg = DWC2_READ_4(hsotg, GUSBCFG); - usbcfg &= ~GUSBCFG_ULPI_FS_LS; - usbcfg &= ~GUSBCFG_ULPI_CLK_SUSP_M; - DWC2_WRITE_4(hsotg, GUSBCFG, usbcfg); - } - - return retval; -} - -STATIC int dwc2_gahbcfg_init(struct dwc2_hsotg *hsotg) -{ - struct dwc2_softc *sc = hsotg->hsotg_sc; - u32 ahbcfg = DWC2_READ_4(hsotg, GAHBCFG); - - switch (hsotg->hw_params.arch) { - case GHWCFG2_EXT_DMA_ARCH: - dev_dbg(hsotg->dev, "External DMA Mode\n"); - if (!sc->sc_set_dma_addr) { - dev_err(hsotg->dev, "External DMA Mode not supported\n"); - return -EINVAL; - } - if (hsotg->core_params->ahbcfg != -1) { - ahbcfg &= GAHBCFG_CTRL_MASK; - ahbcfg |= hsotg->core_params->ahbcfg & - ~GAHBCFG_CTRL_MASK; - } - break; - - case GHWCFG2_INT_DMA_ARCH: - dev_dbg(hsotg->dev, "Internal DMA Mode\n"); - if (hsotg->core_params->ahbcfg != -1) { - ahbcfg &= GAHBCFG_CTRL_MASK; - ahbcfg |= hsotg->core_params->ahbcfg & - ~GAHBCFG_CTRL_MASK; - } - break; - - case GHWCFG2_SLAVE_ONLY_ARCH: - default: - dev_dbg(hsotg->dev, "Slave Only Mode\n"); - break; - } - - dev_dbg(hsotg->dev, "dma_enable:%d dma_desc_enable:%d\n", - hsotg->core_params->dma_enable, - hsotg->core_params->dma_desc_enable); - - if (hsotg->core_params->dma_enable > 0) { - if (hsotg->core_params->dma_desc_enable > 0) - dev_dbg(hsotg->dev, "Using Descriptor DMA mode\n"); - else - dev_dbg(hsotg->dev, "Using Buffer DMA mode\n"); - } else { - dev_dbg(hsotg->dev, "Using Slave mode\n"); - hsotg->core_params->dma_desc_enable = 0; - } - - if (hsotg->core_params->dma_enable > 0) - ahbcfg |= GAHBCFG_DMA_EN; - - DWC2_WRITE_4(hsotg, GAHBCFG, ahbcfg); - - return 0; -} - -STATIC void dwc2_gusbcfg_init(struct dwc2_hsotg *hsotg) -{ - u32 usbcfg; - - usbcfg = DWC2_READ_4(hsotg, GUSBCFG); - usbcfg &= ~(GUSBCFG_HNPCAP | GUSBCFG_SRPCAP); - - switch (hsotg->hw_params.op_mode) { - case GHWCFG2_OP_MODE_HNP_SRP_CAPABLE: - if (hsotg->core_params->otg_cap == - DWC2_CAP_PARAM_HNP_SRP_CAPABLE) - usbcfg |= GUSBCFG_HNPCAP; - if (hsotg->core_params->otg_cap != - DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE) - usbcfg |= GUSBCFG_SRPCAP; - break; - - case GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE: - case GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE: - case GHWCFG2_OP_MODE_SRP_CAPABLE_HOST: - if (hsotg->core_params->otg_cap != - DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE) - usbcfg |= GUSBCFG_SRPCAP; - break; - - case GHWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE: - case GHWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE: - case GHWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST: - default: - break; - } - - DWC2_WRITE_4(hsotg, GUSBCFG, usbcfg); -} - -/** - * dwc2_core_init() - Initializes the DWC_otg controller registers and - * prepares the core for device mode or host mode operation - * - * @hsotg: Programming view of the DWC_otg controller - * @initial_setup: If true then this is the first init for this instance. - */ -int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup) -{ - u32 usbcfg, otgctl; - int retval; - - dev_dbg(hsotg->dev, "%s(%p)\n", __func__, hsotg); - - usbcfg = DWC2_READ_4(hsotg, GUSBCFG); - - /* Set ULPI External VBUS bit if needed */ - usbcfg &= ~GUSBCFG_ULPI_EXT_VBUS_DRV; - if (hsotg->core_params->phy_ulpi_ext_vbus == - DWC2_PHY_ULPI_EXTERNAL_VBUS) - usbcfg |= GUSBCFG_ULPI_EXT_VBUS_DRV; - - /* Set external TS Dline pulsing bit if needed */ - usbcfg &= ~GUSBCFG_TERMSELDLPULSE; - if (hsotg->core_params->ts_dline > 0) - usbcfg |= GUSBCFG_TERMSELDLPULSE; - - DWC2_WRITE_4(hsotg, GUSBCFG, usbcfg); - - /* - * Reset the Controller - * - * We only need to reset the controller if this is a re-init. - * For the first init we know for sure that earlier code reset us (it - * needed to in order to properly detect various parameters). - */ - if (!initial_setup) { - retval = dwc2_core_reset_and_force_dr_mode(hsotg); - if (retval) { - dev_err(hsotg->dev, "%s(): Reset failed, aborting\n", - __func__); - return retval; - } - } - - /* - * This needs to happen in FS mode before any other programming occurs - */ - retval = dwc2_phy_init(hsotg, initial_setup); - if (retval) - return retval; - - /* Program the GAHBCFG Register */ - retval = dwc2_gahbcfg_init(hsotg); - if (retval) - return retval; - - /* Program the GUSBCFG register */ - dwc2_gusbcfg_init(hsotg); - - /* Program the GOTGCTL register */ - otgctl = DWC2_READ_4(hsotg, GOTGCTL); - otgctl &= ~GOTGCTL_OTGVER; - if (hsotg->core_params->otg_ver > 0) - otgctl |= GOTGCTL_OTGVER; - DWC2_WRITE_4(hsotg, GOTGCTL, otgctl); - dev_dbg(hsotg->dev, "OTG VER PARAM: %d\n", hsotg->core_params->otg_ver); - - /* Clear the SRP success bit for FS-I2c */ - hsotg->srp_success = 0; - - /* Enable common interrupts */ - dwc2_enable_common_interrupts(hsotg); - - /* - * Do device or host initialization based on mode during PCD and - * HCD initialization - */ - if (dwc2_is_host_mode(hsotg)) { - dev_dbg(hsotg->dev, "Host Mode\n"); - hsotg->op_state = OTG_STATE_A_HOST; - } else { - dev_dbg(hsotg->dev, "Device Mode\n"); - hsotg->op_state = OTG_STATE_B_PERIPHERAL; - } - - return 0; -} - -/** - * dwc2_enable_host_interrupts() - Enables the Host mode interrupts - * - * @hsotg: Programming view of DWC_otg controller - */ -void dwc2_enable_host_interrupts(struct dwc2_hsotg *hsotg) -{ - u32 intmsk; - - dev_dbg(hsotg->dev, "%s()\n", __func__); - - /* Disable all interrupts */ - DWC2_WRITE_4(hsotg, GINTMSK, 0); - DWC2_WRITE_4(hsotg, HAINTMSK, 0); - - /* Enable the common interrupts */ - dwc2_enable_common_interrupts(hsotg); - - /* Enable host mode interrupts without disturbing common interrupts */ - intmsk = DWC2_READ_4(hsotg, GINTMSK); - intmsk |= GINTSTS_DISCONNINT | GINTSTS_PRTINT | GINTSTS_HCHINT; - DWC2_WRITE_4(hsotg, GINTMSK, intmsk); -} - -/** - * dwc2_disable_host_interrupts() - Disables the Host Mode interrupts - * - * @hsotg: Programming view of DWC_otg controller - */ -void dwc2_disable_host_interrupts(struct dwc2_hsotg *hsotg) -{ - u32 intmsk = DWC2_READ_4(hsotg, GINTMSK); - - /* Disable host mode interrupts without disturbing common interrupts */ - intmsk &= ~(GINTSTS_SOF | GINTSTS_PRTINT | GINTSTS_HCHINT | - GINTSTS_PTXFEMP | GINTSTS_NPTXFEMP | GINTSTS_DISCONNINT); - DWC2_WRITE_4(hsotg, GINTMSK, intmsk); -} - -/* - * dwc2_calculate_dynamic_fifo() - Calculates the default fifo size - * For system that have a total fifo depth that is smaller than the default - * RX + TX fifo size. - * - * @hsotg: Programming view of DWC_otg controller - */ -STATIC void dwc2_calculate_dynamic_fifo(struct dwc2_hsotg *hsotg) -{ - struct dwc2_core_params *params = hsotg->core_params; - struct dwc2_hw_params *hw = &hsotg->hw_params; - u32 rxfsiz, nptxfsiz, ptxfsiz, total_fifo_size; - - total_fifo_size = hw->total_fifo_size; - rxfsiz = params->host_rx_fifo_size; - nptxfsiz = params->host_nperio_tx_fifo_size; - ptxfsiz = params->host_perio_tx_fifo_size; - - /* - * Will use Method 2 defined in the DWC2 spec: minimum FIFO depth - * allocation with support for high bandwidth endpoints. Synopsys - * defines MPS(Max Packet size) for a periodic EP=1024, and for - * non-periodic as 512. - */ - if (total_fifo_size < (rxfsiz + nptxfsiz + ptxfsiz)) { - /* - * For Buffer DMA mode/Scatter Gather DMA mode - * 2 * ((Largest Packet size / 4) + 1 + 1) + n - * with n = number of host channel. - * 2 * ((1024/4) + 2) = 516 - */ - rxfsiz = 516 + hw->host_channels; - - /* - * min non-periodic tx fifo depth - * 2 * (largest non-periodic USB packet used / 4) - * 2 * (512/4) = 256 - */ - nptxfsiz = 256; - - /* - * min periodic tx fifo depth - * (largest packet size*MC)/4 - * (1024 * 3)/4 = 768 - */ - ptxfsiz = 768; - - params->host_rx_fifo_size = rxfsiz; - params->host_nperio_tx_fifo_size = nptxfsiz; - params->host_perio_tx_fifo_size = ptxfsiz; - } - - /* - * If the summation of RX, NPTX and PTX fifo sizes is still - * bigger than the total_fifo_size, then we have a problem. - * - * We won't be able to allocate as many endpoints. Right now, - * we're just printing an error message, but ideally this FIFO - * allocation algorithm would be improved in the future. - * - * FIXME improve this FIFO allocation algorithm. - */ - if (total_fifo_size < (rxfsiz + nptxfsiz + ptxfsiz)) - dev_err(hsotg->dev, "invalid fifo sizes\n"); -} - -STATIC void dwc2_config_fifos(struct dwc2_hsotg *hsotg) -{ - struct dwc2_core_params *params = hsotg->core_params; - u32 nptxfsiz, hptxfsiz, dfifocfg, grxfsiz; - - if (!params->enable_dynamic_fifo) - return; - - dwc2_calculate_dynamic_fifo(hsotg); - - /* Rx FIFO */ - grxfsiz = DWC2_READ_4(hsotg, GRXFSIZ); - dev_dbg(hsotg->dev, "initial grxfsiz=%08x\n", grxfsiz); - grxfsiz &= ~GRXFSIZ_DEPTH_MASK; - grxfsiz |= params->host_rx_fifo_size << - GRXFSIZ_DEPTH_SHIFT & GRXFSIZ_DEPTH_MASK; - DWC2_WRITE_4(hsotg, GRXFSIZ, grxfsiz); - dev_dbg(hsotg->dev, "new grxfsiz=%08x\n", - DWC2_READ_4(hsotg, GRXFSIZ)); - - /* Non-periodic Tx FIFO */ - dev_dbg(hsotg->dev, "initial gnptxfsiz=%08x\n", - DWC2_READ_4(hsotg, GNPTXFSIZ)); - nptxfsiz = params->host_nperio_tx_fifo_size << - FIFOSIZE_DEPTH_SHIFT & FIFOSIZE_DEPTH_MASK; - nptxfsiz |= params->host_rx_fifo_size << - FIFOSIZE_STARTADDR_SHIFT & FIFOSIZE_STARTADDR_MASK; - DWC2_WRITE_4(hsotg, GNPTXFSIZ, nptxfsiz); - dev_dbg(hsotg->dev, "new gnptxfsiz=%08x\n", - DWC2_READ_4(hsotg, GNPTXFSIZ)); - - /* Periodic Tx FIFO */ - dev_dbg(hsotg->dev, "initial hptxfsiz=%08x\n", - DWC2_READ_4(hsotg, HPTXFSIZ)); - hptxfsiz = params->host_perio_tx_fifo_size << - FIFOSIZE_DEPTH_SHIFT & FIFOSIZE_DEPTH_MASK; - hptxfsiz |= (params->host_rx_fifo_size + - params->host_nperio_tx_fifo_size) << - FIFOSIZE_STARTADDR_SHIFT & FIFOSIZE_STARTADDR_MASK; - DWC2_WRITE_4(hsotg, HPTXFSIZ, hptxfsiz); - dev_dbg(hsotg->dev, "new hptxfsiz=%08x\n", - DWC2_READ_4(hsotg, HPTXFSIZ)); - - if (hsotg->core_params->en_multiple_tx_fifo > 0 && - hsotg->hw_params.snpsid <= DWC2_CORE_REV_2_94a) { - /* - * Global DFIFOCFG calculation for Host mode - - * include RxFIFO, NPTXFIFO and HPTXFIFO - */ - dfifocfg = DWC2_READ_4(hsotg, GDFIFOCFG); - dfifocfg &= ~GDFIFOCFG_EPINFOBASE_MASK; - dfifocfg |= (params->host_rx_fifo_size + - params->host_nperio_tx_fifo_size + - params->host_perio_tx_fifo_size) << - GDFIFOCFG_EPINFOBASE_SHIFT & - GDFIFOCFG_EPINFOBASE_MASK; - DWC2_WRITE_4(hsotg, GDFIFOCFG, dfifocfg); - } -} - -/** - * dwc2_core_host_init() - Initializes the DWC_otg controller registers for - * Host mode - * - * @hsotg: Programming view of DWC_otg controller - * - * This function flushes the Tx and Rx FIFOs and flushes any entries in the - * request queues. Host channels are reset to ensure that they are ready for - * performing transfers. - */ -void dwc2_core_host_init(struct dwc2_hsotg *hsotg) -{ - u32 hcfg, hfir, otgctl; - - dev_dbg(hsotg->dev, "%s(%p)\n", __func__, hsotg); - - /* Restart the Phy Clock */ - DWC2_WRITE_4(hsotg, PCGCTL, 0); - - /* Initialize Host Configuration Register */ - dwc2_init_fs_ls_pclk_sel(hsotg); - if (hsotg->core_params->speed == DWC2_SPEED_PARAM_FULL) { - hcfg = DWC2_READ_4(hsotg, HCFG); - hcfg |= HCFG_FSLSSUPP; - DWC2_WRITE_4(hsotg, HCFG, hcfg); - } - - /* - * This bit allows dynamic reloading of the HFIR register during - * runtime. This bit needs to be programmed during initial configuration - * and its value must not be changed during runtime. - */ - if (hsotg->core_params->reload_ctl > 0) { - hfir = DWC2_READ_4(hsotg, HFIR); - hfir |= HFIR_RLDCTRL; - DWC2_WRITE_4(hsotg, HFIR, hfir); - } - - if (hsotg->core_params->dma_desc_enable > 0) { - u32 op_mode = hsotg->hw_params.op_mode; - if (hsotg->hw_params.snpsid < DWC2_CORE_REV_2_90a || - !hsotg->hw_params.dma_desc_enable || - op_mode == GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE || - op_mode == GHWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE || - op_mode == GHWCFG2_OP_MODE_UNDEFINED) { - dev_err(hsotg->dev, - "Hardware does not support descriptor DMA mode -\n"); - dev_err(hsotg->dev, - "falling back to buffer DMA mode.\n"); - hsotg->core_params->dma_desc_enable = 0; - } else { - hcfg = DWC2_READ_4(hsotg, HCFG); - hcfg |= HCFG_DESCDMA; - DWC2_WRITE_4(hsotg, HCFG, hcfg); - } - } - - /* Configure data FIFO sizes */ - dwc2_config_fifos(hsotg); - - /* TODO - check this */ - /* Clear Host Set HNP Enable in the OTG Control Register */ - otgctl = DWC2_READ_4(hsotg, GOTGCTL); - otgctl &= ~GOTGCTL_HSTSETHNPEN; - DWC2_WRITE_4(hsotg, GOTGCTL, otgctl); - - /* Make sure the FIFOs are flushed */ - dwc2_flush_tx_fifo(hsotg, 0x10 /* all TX FIFOs */); - dwc2_flush_rx_fifo(hsotg); - - /* Clear Host Set HNP Enable in the OTG Control Register */ - otgctl = DWC2_READ_4(hsotg, GOTGCTL); - otgctl &= ~GOTGCTL_HSTSETHNPEN; - DWC2_WRITE_4(hsotg, GOTGCTL, otgctl); - - if (hsotg->core_params->dma_desc_enable <= 0) { - int num_channels, i; - u32 hcchar; - - /* Flush out any leftover queued requests */ - num_channels = hsotg->core_params->host_channels; - for (i = 0; i < num_channels; i++) { - hcchar = DWC2_READ_4(hsotg, HCCHAR(i)); - hcchar &= ~HCCHAR_CHENA; - hcchar |= HCCHAR_CHDIS; - hcchar &= ~HCCHAR_EPDIR; - DWC2_WRITE_4(hsotg, HCCHAR(i), hcchar); - } - - /* Halt all channels to put them into a known state */ - for (i = 0; i < num_channels; i++) { - int count = 0; - - hcchar = DWC2_READ_4(hsotg, HCCHAR(i)); - hcchar |= HCCHAR_CHENA | HCCHAR_CHDIS; - hcchar &= ~HCCHAR_EPDIR; - DWC2_WRITE_4(hsotg, HCCHAR(i), hcchar); - dev_dbg(hsotg->dev, "%s: Halt channel %d\n", - __func__, i); - do { - hcchar = DWC2_READ_4(hsotg, HCCHAR(i)); - if (++count > 1000) { - dev_err(hsotg->dev, - "Unable to clear enable on channel %d\n", - i); - break; - } - udelay(1); - } while (hcchar & HCCHAR_CHENA); - } - } - - /* Turn on the vbus power */ - dev_dbg(hsotg->dev, "Init: Port Power? op_state=%d\n", hsotg->op_state); - if (hsotg->op_state == OTG_STATE_A_HOST) { - u32 hprt0 = dwc2_read_hprt0(hsotg); - - dev_dbg(hsotg->dev, "Init: Power Port (%d)\n", - !!(hprt0 & HPRT0_PWR)); - if (!(hprt0 & HPRT0_PWR)) { - hprt0 |= HPRT0_PWR; - DWC2_WRITE_4(hsotg, HPRT0, hprt0); - } - } - - dwc2_enable_host_interrupts(hsotg); -} - -STATIC void dwc2_hc_enable_slave_ints(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan) -{ - u32 hcintmsk = HCINTMSK_CHHLTD; - - switch (chan->ep_type) { - case USB_ENDPOINT_XFER_CONTROL: - case USB_ENDPOINT_XFER_BULK: - dev_vdbg(hsotg->dev, "control/bulk\n"); - hcintmsk |= HCINTMSK_XFERCOMPL; - hcintmsk |= HCINTMSK_STALL; - hcintmsk |= HCINTMSK_XACTERR; - hcintmsk |= HCINTMSK_DATATGLERR; - if (chan->ep_is_in) { - hcintmsk |= HCINTMSK_BBLERR; - } else { - hcintmsk |= HCINTMSK_NAK; - hcintmsk |= HCINTMSK_NYET; - if (chan->do_ping) - hcintmsk |= HCINTMSK_ACK; - } - - if (chan->do_split) { - hcintmsk |= HCINTMSK_NAK; - if (chan->complete_split) - hcintmsk |= HCINTMSK_NYET; - else - hcintmsk |= HCINTMSK_ACK; - } - - if (chan->error_state) - hcintmsk |= HCINTMSK_ACK; - break; - - case USB_ENDPOINT_XFER_INT: - if (dbg_perio()) - dev_vdbg(hsotg->dev, "intr\n"); - hcintmsk |= HCINTMSK_XFERCOMPL; - hcintmsk |= HCINTMSK_NAK; - hcintmsk |= HCINTMSK_STALL; - hcintmsk |= HCINTMSK_XACTERR; - hcintmsk |= HCINTMSK_DATATGLERR; - hcintmsk |= HCINTMSK_FRMOVRUN; - - if (chan->ep_is_in) - hcintmsk |= HCINTMSK_BBLERR; - if (chan->error_state) - hcintmsk |= HCINTMSK_ACK; - if (chan->do_split) { - if (chan->complete_split) - hcintmsk |= HCINTMSK_NYET; - else - hcintmsk |= HCINTMSK_ACK; - } - break; - - case USB_ENDPOINT_XFER_ISOC: - if (dbg_perio()) - dev_vdbg(hsotg->dev, "isoc\n"); - hcintmsk |= HCINTMSK_XFERCOMPL; - hcintmsk |= HCINTMSK_FRMOVRUN; - hcintmsk |= HCINTMSK_ACK; - - if (chan->ep_is_in) { - hcintmsk |= HCINTMSK_XACTERR; - hcintmsk |= HCINTMSK_BBLERR; - } - break; - default: - dev_err(hsotg->dev, "## Unknown EP type ##\n"); - break; - } - - DWC2_WRITE_4(hsotg, HCINTMSK(chan->hc_num), hcintmsk); - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "set HCINTMSK to %08x\n", hcintmsk); -} - -STATIC void dwc2_hc_enable_dma_ints(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan) -{ - u32 hcintmsk = HCINTMSK_CHHLTD; - - /* - * For Descriptor DMA mode core halts the channel on AHB error. - * Interrupt is not required. - */ - if (hsotg->core_params->dma_desc_enable <= 0) { - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "desc DMA disabled\n"); - hcintmsk |= HCINTMSK_AHBERR; - } else { - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "desc DMA enabled\n"); - if (chan->ep_type == USB_ENDPOINT_XFER_ISOC) - hcintmsk |= HCINTMSK_XFERCOMPL; - } - - if (chan->error_state && !chan->do_split && - chan->ep_type != USB_ENDPOINT_XFER_ISOC) { - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "setting ACK\n"); - hcintmsk |= HCINTMSK_ACK; - if (chan->ep_is_in) { - hcintmsk |= HCINTMSK_DATATGLERR; - if (chan->ep_type != USB_ENDPOINT_XFER_INT) - hcintmsk |= HCINTMSK_NAK; - } - } - - DWC2_WRITE_4(hsotg, HCINTMSK(chan->hc_num), hcintmsk); - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "set HCINTMSK to %08x\n", hcintmsk); -} - -STATIC void dwc2_hc_enable_ints(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan) -{ - u32 intmsk; - - if (hsotg->core_params->dma_enable > 0) { - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "DMA enabled\n"); - dwc2_hc_enable_dma_ints(hsotg, chan); - } else { - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "DMA disabled\n"); - dwc2_hc_enable_slave_ints(hsotg, chan); - } - - /* Enable the top level host channel interrupt */ - intmsk = DWC2_READ_4(hsotg, HAINTMSK); - intmsk |= 1 << chan->hc_num; - DWC2_WRITE_4(hsotg, HAINTMSK, intmsk); - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "set HAINTMSK to %08x\n", intmsk); - - /* Make sure host channel interrupts are enabled */ - intmsk = DWC2_READ_4(hsotg, GINTMSK); - intmsk |= GINTSTS_HCHINT; - DWC2_WRITE_4(hsotg, GINTMSK, intmsk); - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "set GINTMSK to %08x\n", intmsk); -} - -/** - * dwc2_hc_init() - Prepares a host channel for transferring packets to/from - * a specific endpoint - * - * @hsotg: Programming view of DWC_otg controller - * @chan: Information needed to initialize the host channel - * - * The HCCHARn register is set up with the characteristics specified in chan. - * Host channel interrupts that may need to be serviced while this transfer is - * in progress are enabled. - */ -void dwc2_hc_init(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan) -{ - u8 hc_num = chan->hc_num; - u32 hcintmsk; - u32 hcchar; - u32 hcsplt = 0; - - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "%s()\n", __func__); - - /* Clear old interrupt conditions for this host channel */ - hcintmsk = 0xffffffff; - hcintmsk &= ~HCINTMSK_RESERVED14_31; - DWC2_WRITE_4(hsotg, HCINT(hc_num), hcintmsk); - - /* Enable channel interrupts required for this transfer */ - dwc2_hc_enable_ints(hsotg, chan); - - /* - * Program the HCCHARn register with the endpoint characteristics for - * the current transfer - */ - hcchar = chan->dev_addr << HCCHAR_DEVADDR_SHIFT & HCCHAR_DEVADDR_MASK; - hcchar |= chan->ep_num << HCCHAR_EPNUM_SHIFT & HCCHAR_EPNUM_MASK; - if (chan->ep_is_in) - hcchar |= HCCHAR_EPDIR; - if (chan->speed == USB_SPEED_LOW) - hcchar |= HCCHAR_LSPDDEV; - hcchar |= chan->ep_type << HCCHAR_EPTYPE_SHIFT & HCCHAR_EPTYPE_MASK; - hcchar |= chan->max_packet << HCCHAR_MPS_SHIFT & HCCHAR_MPS_MASK; - DWC2_WRITE_4(hsotg, HCCHAR(hc_num), hcchar); - if (dbg_hc(chan)) { - dev_vdbg(hsotg->dev, "set HCCHAR(%d) to %08x\n", - hc_num, hcchar); - - dev_vdbg(hsotg->dev, "%s: Channel %d\n", - __func__, hc_num); - dev_vdbg(hsotg->dev, " Dev Addr: %d\n", - chan->dev_addr); - dev_vdbg(hsotg->dev, " Ep Num: %d\n", - chan->ep_num); - dev_vdbg(hsotg->dev, " Is In: %d\n", - chan->ep_is_in); - dev_vdbg(hsotg->dev, " Is Low Speed: %d\n", - chan->speed == USB_SPEED_LOW); - dev_vdbg(hsotg->dev, " Ep Type: %d\n", - chan->ep_type); - dev_vdbg(hsotg->dev, " Max Pkt: %d\n", - chan->max_packet); - } - - /* Program the HCSPLT register for SPLITs */ - if (chan->do_split) { - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, - "Programming HC %d with split --> %s\n", - hc_num, - chan->complete_split ? "CSPLIT" : "SSPLIT"); - if (chan->complete_split) - hcsplt |= HCSPLT_COMPSPLT; - hcsplt |= chan->xact_pos << HCSPLT_XACTPOS_SHIFT & - HCSPLT_XACTPOS_MASK; - hcsplt |= chan->hub_addr << HCSPLT_HUBADDR_SHIFT & - HCSPLT_HUBADDR_MASK; - hcsplt |= chan->hub_port << HCSPLT_PRTADDR_SHIFT & - HCSPLT_PRTADDR_MASK; - if (dbg_hc(chan)) { - dev_vdbg(hsotg->dev, " comp split %d\n", - chan->complete_split); - dev_vdbg(hsotg->dev, " xact pos %d\n", - chan->xact_pos); - dev_vdbg(hsotg->dev, " hub addr %d\n", - chan->hub_addr); - dev_vdbg(hsotg->dev, " hub port %d\n", - chan->hub_port); - dev_vdbg(hsotg->dev, " is_in %d\n", - chan->ep_is_in); - dev_vdbg(hsotg->dev, " Max Pkt %d\n", - chan->max_packet); - dev_vdbg(hsotg->dev, " xferlen %d\n", - chan->xfer_len); - } - } - - DWC2_WRITE_4(hsotg, HCSPLT(hc_num), hcsplt); -} - -/** - * dwc2_hc_halt() - Attempts to halt a host channel - * - * @hsotg: Controller register interface - * @chan: Host channel to halt - * @halt_status: Reason for halting the channel - * - * This function should only be called in Slave mode or to abort a transfer in - * either Slave mode or DMA mode. Under normal circumstances in DMA mode, the - * controller halts the channel when the transfer is complete or a condition - * occurs that requires application intervention. - * - * In slave mode, checks for a free request queue entry, then sets the Channel - * Enable and Channel Disable bits of the Host Channel Characteristics - * register of the specified channel to intiate the halt. If there is no free - * request queue entry, sets only the Channel Disable bit of the HCCHARn - * register to flush requests for this channel. In the latter case, sets a - * flag to indicate that the host channel needs to be halted when a request - * queue slot is open. - * - * In DMA mode, always sets the Channel Enable and Channel Disable bits of the - * HCCHARn register. The controller ensures there is space in the request - * queue before submitting the halt request. - * - * Some time may elapse before the core flushes any posted requests for this - * host channel and halts. The Channel Halted interrupt handler completes the - * deactivation of the host channel. - */ -void dwc2_hc_halt(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan, - enum dwc2_halt_status halt_status) -{ - u32 nptxsts, hptxsts, hcchar; - - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "%s()\n", __func__); - if (halt_status == DWC2_HC_XFER_NO_HALT_STATUS) - dev_err(hsotg->dev, "!!! halt_status = %d !!!\n", halt_status); - - if (halt_status == DWC2_HC_XFER_URB_DEQUEUE || - halt_status == DWC2_HC_XFER_AHB_ERR) { - /* - * Disable all channel interrupts except Ch Halted. The QTD - * and QH state associated with this transfer has been cleared - * (in the case of URB_DEQUEUE), so the channel needs to be - * shut down carefully to prevent crashes. - */ - u32 hcintmsk = HCINTMSK_CHHLTD; - - dev_vdbg(hsotg->dev, "dequeue/error\n"); - DWC2_WRITE_4(hsotg, HCINTMSK(chan->hc_num), hcintmsk); - - /* - * Make sure no other interrupts besides halt are currently - * pending. Handling another interrupt could cause a crash due - * to the QTD and QH state. - */ - DWC2_WRITE_4(hsotg, HCINT(chan->hc_num), ~hcintmsk); - - /* - * Make sure the halt status is set to URB_DEQUEUE or AHB_ERR - * even if the channel was already halted for some other - * reason - */ - chan->halt_status = halt_status; - - hcchar = DWC2_READ_4(hsotg, HCCHAR(chan->hc_num)); - if (!(hcchar & HCCHAR_CHENA)) { - /* - * The channel is either already halted or it hasn't - * started yet. In DMA mode, the transfer may halt if - * it finishes normally or a condition occurs that - * requires driver intervention. Don't want to halt - * the channel again. In either Slave or DMA mode, - * it's possible that the transfer has been assigned - * to a channel, but not started yet when an URB is - * dequeued. Don't want to halt a channel that hasn't - * started yet. - */ - return; - } - } - if (chan->halt_pending) { - /* - * A halt has already been issued for this channel. This might - * happen when a transfer is aborted by a higher level in - * the stack. - */ - dev_vdbg(hsotg->dev, - "*** %s: Channel %d, chan->halt_pending already set ***\n", - __func__, chan->hc_num); - return; - } - - hcchar = DWC2_READ_4(hsotg, HCCHAR(chan->hc_num)); - - /* No need to set the bit in DDMA for disabling the channel */ - /* TODO check it everywhere channel is disabled */ - if (hsotg->core_params->dma_desc_enable <= 0) { - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "desc DMA disabled\n"); - hcchar |= HCCHAR_CHENA; - } else { - if (dbg_hc(chan)) - dev_dbg(hsotg->dev, "desc DMA enabled\n"); - } - hcchar |= HCCHAR_CHDIS; - - if (hsotg->core_params->dma_enable <= 0) { - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "DMA not enabled\n"); - hcchar |= HCCHAR_CHENA; - - /* Check for space in the request queue to issue the halt */ - if (chan->ep_type == USB_ENDPOINT_XFER_CONTROL || - chan->ep_type == USB_ENDPOINT_XFER_BULK) { - dev_vdbg(hsotg->dev, "control/bulk\n"); - nptxsts = DWC2_READ_4(hsotg, GNPTXSTS); - if ((nptxsts & TXSTS_QSPCAVAIL_MASK) == 0) { - dev_vdbg(hsotg->dev, "Disabling channel\n"); - hcchar &= ~HCCHAR_CHENA; - } - } else { - if (dbg_perio()) - dev_vdbg(hsotg->dev, "isoc/intr\n"); - hptxsts = DWC2_READ_4(hsotg, HPTXSTS); - if ((hptxsts & TXSTS_QSPCAVAIL_MASK) == 0 || - hsotg->queuing_high_bandwidth) { - if (dbg_perio()) - dev_vdbg(hsotg->dev, "Disabling channel\n"); - hcchar &= ~HCCHAR_CHENA; - } - } - } else { - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "DMA enabled\n"); - } - - DWC2_WRITE_4(hsotg, HCCHAR(chan->hc_num), hcchar); - chan->halt_status = halt_status; - - if (hcchar & HCCHAR_CHENA) { - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "Channel enabled\n"); - chan->halt_pending = 1; - chan->halt_on_queue = 0; - } else { - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "Channel disabled\n"); - chan->halt_on_queue = 1; - } - - if (dbg_hc(chan)) { - dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, - chan->hc_num); - dev_vdbg(hsotg->dev, " hcchar: 0x%08x\n", - hcchar); - dev_vdbg(hsotg->dev, " halt_pending: %d\n", - chan->halt_pending); - dev_vdbg(hsotg->dev, " halt_on_queue: %d\n", - chan->halt_on_queue); - dev_vdbg(hsotg->dev, " halt_status: %d\n", - chan->halt_status); - } -} - -/** - * dwc2_hc_cleanup() - Clears the transfer state for a host channel - * - * @hsotg: Programming view of DWC_otg controller - * @chan: Identifies the host channel to clean up - * - * This function is normally called after a transfer is done and the host - * channel is being released - */ -void dwc2_hc_cleanup(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan) -{ - u32 hcintmsk; - - chan->xfer_started = 0; - - list_del_init(&chan->split_order_list_entry); - - /* - * Clear channel interrupt enables and any unhandled channel interrupt - * conditions - */ - DWC2_WRITE_4(hsotg, HCINTMSK(chan->hc_num), 0); - hcintmsk = 0xffffffff; - hcintmsk &= ~HCINTMSK_RESERVED14_31; - DWC2_WRITE_4(hsotg, HCINT(chan->hc_num), hcintmsk); -} - -/** - * dwc2_hc_set_even_odd_frame() - Sets the channel property that indicates in - * which frame a periodic transfer should occur - * - * @hsotg: Programming view of DWC_otg controller - * @chan: Identifies the host channel to set up and its properties - * @hcchar: Current value of the HCCHAR register for the specified host channel - * - * This function has no effect on non-periodic transfers - */ -STATIC void dwc2_hc_set_even_odd_frame(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan, u32 *hcchar) -{ - if (chan->ep_type == USB_ENDPOINT_XFER_INT || - chan->ep_type == USB_ENDPOINT_XFER_ISOC) { - /* 1 if _next_ frame is odd, 0 if it's even */ - if (!(dwc2_hcd_get_frame_number(hsotg) & 0x1)) - *hcchar |= HCCHAR_ODDFRM; - } -} - -STATIC void dwc2_set_pid_isoc(struct dwc2_host_chan *chan) -{ - /* Set up the initial PID for the transfer */ - if (chan->speed == USB_SPEED_HIGH) { - if (chan->ep_is_in) { - if (chan->multi_count == 1) - chan->data_pid_start = DWC2_HC_PID_DATA0; - else if (chan->multi_count == 2) - chan->data_pid_start = DWC2_HC_PID_DATA1; - else - chan->data_pid_start = DWC2_HC_PID_DATA2; - } else { - if (chan->multi_count == 1) - chan->data_pid_start = DWC2_HC_PID_DATA0; - else - chan->data_pid_start = DWC2_HC_PID_MDATA; - } - } else { - chan->data_pid_start = DWC2_HC_PID_DATA0; - } -} - -/** - * dwc2_hc_write_packet() - Writes a packet into the Tx FIFO associated with - * the Host Channel - * - * @hsotg: Programming view of DWC_otg controller - * @chan: Information needed to initialize the host channel - * - * This function should only be called in Slave mode. For a channel associated - * with a non-periodic EP, the non-periodic Tx FIFO is written. For a channel - * associated with a periodic EP, the periodic Tx FIFO is written. - * - * Upon return the xfer_buf and xfer_count fields in chan are incremented by - * the number of bytes written to the Tx FIFO. - */ -STATIC void dwc2_hc_write_packet(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan) -{ - u32 i; - u32 remaining_count; - u32 byte_count; - u32 dword_count; - u32 *data_buf = (u32 *)chan->xfer_buf; - u32 data_fifo; - - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "%s()\n", __func__); - - data_fifo = HCFIFO(chan->hc_num); - - remaining_count = chan->xfer_len - chan->xfer_count; - if (remaining_count > chan->max_packet) - byte_count = chan->max_packet; - else - byte_count = remaining_count; - - dword_count = (byte_count + 3) / 4; - - if (((unsigned long)data_buf & 0x3) == 0) { - /* xfer_buf is DWORD aligned */ - for (i = 0; i < dword_count; i++, data_buf++) - DWC2_WRITE_4(hsotg, data_fifo, *data_buf); - } else { - /* xfer_buf is not DWORD aligned */ - for (i = 0; i < dword_count; i++, data_buf++) { - u32 data = data_buf[0] | data_buf[1] << 8 | - data_buf[2] << 16 | data_buf[3] << 24; - DWC2_WRITE_4(hsotg, data_fifo, data); - } - } - - chan->xfer_count += byte_count; - chan->xfer_buf += byte_count; -} - -/** - * dwc2_hc_start_transfer() - Does the setup for a data transfer for a host - * channel and starts the transfer - * - * @hsotg: Programming view of DWC_otg controller - * @chan: Information needed to initialize the host channel. The xfer_len value - * may be reduced to accommodate the max widths of the XferSize and - * PktCnt fields in the HCTSIZn register. The multi_count value may be - * changed to reflect the final xfer_len value. - * - * This function may be called in either Slave mode or DMA mode. In Slave mode, - * the caller must ensure that there is sufficient space in the request queue - * and Tx Data FIFO. - * - * For an OUT transfer in Slave mode, it loads a data packet into the - * appropriate FIFO. If necessary, additional data packets are loaded in the - * Host ISR. - * - * For an IN transfer in Slave mode, a data packet is requested. The data - * packets are unloaded from the Rx FIFO in the Host ISR. If necessary, - * additional data packets are requested in the Host ISR. - * - * For a PING transfer in Slave mode, the Do Ping bit is set in the HCTSIZ - * register along with a packet count of 1 and the channel is enabled. This - * causes a single PING transaction to occur. Other fields in HCTSIZ are - * simply set to 0 since no data transfer occurs in this case. - * - * For a PING transfer in DMA mode, the HCTSIZ register is initialized with - * all the information required to perform the subsequent data transfer. In - * addition, the Do Ping bit is set in the HCTSIZ register. In this case, the - * controller performs the entire PING protocol, then starts the data - * transfer. - */ -void dwc2_hc_start_transfer(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan) -{ - u32 max_hc_xfer_size = hsotg->core_params->max_transfer_size; - u16 max_hc_pkt_count = hsotg->core_params->max_packet_count; - u32 hcchar; - u32 hctsiz = 0; - u16 num_packets; - u32 ec_mc; - - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "%s()\n", __func__); - - if (chan->do_ping) { - if (hsotg->core_params->dma_enable <= 0) { - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "ping, no DMA\n"); - dwc2_hc_do_ping(hsotg, chan); - chan->xfer_started = 1; - return; - } else { - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "ping, DMA\n"); - hctsiz |= TSIZ_DOPNG; - } - } - - if (chan->do_split) { - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "split\n"); - num_packets = 1; - - if (chan->complete_split && !chan->ep_is_in) - /* - * For CSPLIT OUT Transfer, set the size to 0 so the - * core doesn't expect any data written to the FIFO - */ - chan->xfer_len = 0; - else if (chan->ep_is_in || chan->xfer_len > chan->max_packet) - chan->xfer_len = chan->max_packet; - else if (!chan->ep_is_in && chan->xfer_len > 188) - chan->xfer_len = 188; - - hctsiz |= chan->xfer_len << TSIZ_XFERSIZE_SHIFT & - TSIZ_XFERSIZE_MASK; - - /* For split set ec_mc for immediate retries */ - if (chan->ep_type == USB_ENDPOINT_XFER_INT || - chan->ep_type == USB_ENDPOINT_XFER_ISOC) - ec_mc = 3; - else - ec_mc = 1; - } else { - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "no split\n"); - /* - * Ensure that the transfer length and packet count will fit - * in the widths allocated for them in the HCTSIZn register - */ - if (chan->ep_type == USB_ENDPOINT_XFER_INT || - chan->ep_type == USB_ENDPOINT_XFER_ISOC) { - /* - * Make sure the transfer size is no larger than one - * (micro)frame's worth of data. (A check was done - * when the periodic transfer was accepted to ensure - * that a (micro)frame's worth of data can be - * programmed into a channel.) - */ - u32 max_periodic_len = - chan->multi_count * chan->max_packet; - - if (chan->xfer_len > max_periodic_len) - chan->xfer_len = max_periodic_len; - } else if (chan->xfer_len > max_hc_xfer_size) { - /* - * Make sure that xfer_len is a multiple of max packet - * size - */ - chan->xfer_len = - max_hc_xfer_size - chan->max_packet + 1; - } - - if (chan->xfer_len > 0) { - num_packets = (chan->xfer_len + chan->max_packet - 1) / - chan->max_packet; - if (num_packets > max_hc_pkt_count) { - num_packets = max_hc_pkt_count; - chan->xfer_len = num_packets * chan->max_packet; - } - } else { - /* Need 1 packet for transfer length of 0 */ - num_packets = 1; - } - - if (chan->ep_is_in) - /* - * Always program an integral # of max packets for IN - * transfers - */ - chan->xfer_len = num_packets * chan->max_packet; - - if (chan->ep_type == USB_ENDPOINT_XFER_INT || - chan->ep_type == USB_ENDPOINT_XFER_ISOC) - /* - * Make sure that the multi_count field matches the - * actual transfer length - */ - chan->multi_count = num_packets; - - if (chan->ep_type == USB_ENDPOINT_XFER_ISOC) - dwc2_set_pid_isoc(chan); - - hctsiz |= chan->xfer_len << TSIZ_XFERSIZE_SHIFT & - TSIZ_XFERSIZE_MASK; - - /* The ec_mc gets the multi_count for non-split */ - ec_mc = chan->multi_count; - } - - chan->start_pkt_count = num_packets; - hctsiz |= num_packets << TSIZ_PKTCNT_SHIFT & TSIZ_PKTCNT_MASK; - hctsiz |= chan->data_pid_start << TSIZ_SC_MC_PID_SHIFT & - TSIZ_SC_MC_PID_MASK; - DWC2_WRITE_4(hsotg, HCTSIZ(chan->hc_num), hctsiz); - if (dbg_hc(chan)) { - dev_vdbg(hsotg->dev, "Wrote %08x to HCTSIZ(%d)\n", - hctsiz, chan->hc_num); - - dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, - chan->hc_num); - dev_vdbg(hsotg->dev, " Xfer Size: %d\n", - (hctsiz & TSIZ_XFERSIZE_MASK) >> - TSIZ_XFERSIZE_SHIFT); - dev_vdbg(hsotg->dev, " Num Pkts: %d\n", - (hctsiz & TSIZ_PKTCNT_MASK) >> - TSIZ_PKTCNT_SHIFT); - dev_vdbg(hsotg->dev, " Start PID: %d\n", - (hctsiz & TSIZ_SC_MC_PID_MASK) >> - TSIZ_SC_MC_PID_SHIFT); - } - - if (hsotg->core_params->dma_enable > 0) { - dma_addr_t dma_addr; - - if (chan->align_buf) { - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "align_buf\n"); - dma_addr = chan->align_buf; - } else { - dma_addr = chan->xfer_dma; - } - if (hsotg->hsotg_sc->sc_set_dma_addr == NULL) { - DWC2_WRITE_4(hsotg, HCDMA(chan->hc_num), - (u32)dma_addr); - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, - "Wrote %08lx to HCDMA(%d)\n", - (unsigned long)dma_addr, - chan->hc_num); - } else { - (void)(*hsotg->hsotg_sc->sc_set_dma_addr)( - hsotg->dev, dma_addr, chan->hc_num); - } - } - - /* Start the split */ - if (chan->do_split) { - u32 hcsplt = DWC2_READ_4(hsotg, HCSPLT(chan->hc_num)); - - hcsplt |= HCSPLT_SPLTENA; - DWC2_WRITE_4(hsotg, HCSPLT(chan->hc_num), hcsplt); - } - - hcchar = DWC2_READ_4(hsotg, HCCHAR(chan->hc_num)); - hcchar &= ~HCCHAR_MULTICNT_MASK; - hcchar |= (ec_mc << HCCHAR_MULTICNT_SHIFT) & HCCHAR_MULTICNT_MASK; - dwc2_hc_set_even_odd_frame(hsotg, chan, &hcchar); - - if (hcchar & HCCHAR_CHDIS) - dev_warn(hsotg->dev, - "%s: chdis set, channel %d, hcchar 0x%08x\n", - __func__, chan->hc_num, hcchar); - - /* Set host channel enable after all other setup is complete */ - hcchar |= HCCHAR_CHENA; - hcchar &= ~HCCHAR_CHDIS; - - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, " Multi Cnt: %d\n", - (hcchar & HCCHAR_MULTICNT_MASK) >> - HCCHAR_MULTICNT_SHIFT); - - DWC2_WRITE_4(hsotg, HCCHAR(chan->hc_num), hcchar); - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "Wrote %08x to HCCHAR(%d)\n", hcchar, - chan->hc_num); - - chan->xfer_started = 1; - chan->requests++; - - if (hsotg->core_params->dma_enable <= 0 && - !chan->ep_is_in && chan->xfer_len > 0) - /* Load OUT packet into the appropriate Tx FIFO */ - dwc2_hc_write_packet(hsotg, chan); -} - -/** - * dwc2_hc_start_transfer_ddma() - Does the setup for a data transfer for a - * host channel and starts the transfer in Descriptor DMA mode - * - * @hsotg: Programming view of DWC_otg controller - * @chan: Information needed to initialize the host channel - * - * Initializes HCTSIZ register. For a PING transfer the Do Ping bit is set. - * Sets PID and NTD values. For periodic transfers initializes SCHED_INFO field - * with micro-frame bitmap. - * - * Initializes HCDMA register with descriptor list address and CTD value then - * starts the transfer via enabling the channel. - */ -void dwc2_hc_start_transfer_ddma(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan) -{ - u32 hcchar; - u32 hctsiz = 0; - - if (chan->do_ping) - hctsiz |= TSIZ_DOPNG; - - if (chan->ep_type == USB_ENDPOINT_XFER_ISOC) - dwc2_set_pid_isoc(chan); - - /* Packet Count and Xfer Size are not used in Descriptor DMA mode */ - hctsiz |= chan->data_pid_start << TSIZ_SC_MC_PID_SHIFT & - TSIZ_SC_MC_PID_MASK; - - /* 0 - 1 descriptor, 1 - 2 descriptors, etc */ - hctsiz |= (chan->ntd - 1) << TSIZ_NTD_SHIFT & TSIZ_NTD_MASK; - - /* Non-zero only for high-speed interrupt endpoints */ - hctsiz |= chan->schinfo << TSIZ_SCHINFO_SHIFT & TSIZ_SCHINFO_MASK; - - if (dbg_hc(chan)) { - dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, - chan->hc_num); - dev_vdbg(hsotg->dev, " Start PID: %d\n", - chan->data_pid_start); - dev_vdbg(hsotg->dev, " NTD: %d\n", chan->ntd - 1); - } - - DWC2_WRITE_4(hsotg, HCTSIZ(chan->hc_num), hctsiz); - - usb_syncmem(&chan->desc_list_usbdma, 0, chan->desc_list_sz, - BUS_DMASYNC_PREWRITE); - - if (hsotg->hsotg_sc->sc_set_dma_addr == NULL) { - DWC2_WRITE_4(hsotg, HCDMA(chan->hc_num), chan->desc_list_addr); - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "Wrote %pad to HCDMA(%d)\n", - &chan->desc_list_addr, chan->hc_num); - } else { - (void)(*hsotg->hsotg_sc->sc_set_dma_addr)( - hsotg->dev, chan->desc_list_addr, chan->hc_num); - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "Wrote %pad to ext dma(%d)\n", - &chan->desc_list_addr, chan->hc_num); - } - - hcchar = DWC2_READ_4(hsotg, HCCHAR(chan->hc_num)); - hcchar &= ~HCCHAR_MULTICNT_MASK; - hcchar |= chan->multi_count << HCCHAR_MULTICNT_SHIFT & - HCCHAR_MULTICNT_MASK; - - if (hcchar & HCCHAR_CHDIS) - dev_warn(hsotg->dev, - "%s: chdis set, channel %d, hcchar 0x%08x\n", - __func__, chan->hc_num, hcchar); - - /* Set host channel enable after all other setup is complete */ - hcchar |= HCCHAR_CHENA; - hcchar &= ~HCCHAR_CHDIS; - - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, " Multi Cnt: %d\n", - (hcchar & HCCHAR_MULTICNT_MASK) >> - HCCHAR_MULTICNT_SHIFT); - - DWC2_WRITE_4(hsotg, HCCHAR(chan->hc_num), hcchar); - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "Wrote %08x to HCCHAR(%d)\n", hcchar, - chan->hc_num); - - chan->xfer_started = 1; - chan->requests++; -} - -/** - * dwc2_hc_continue_transfer() - Continues a data transfer that was started by - * a previous call to dwc2_hc_start_transfer() - * - * @hsotg: Programming view of DWC_otg controller - * @chan: Information needed to initialize the host channel - * - * The caller must ensure there is sufficient space in the request queue and Tx - * Data FIFO. This function should only be called in Slave mode. In DMA mode, - * the controller acts autonomously to complete transfers programmed to a host - * channel. - * - * For an OUT transfer, a new data packet is loaded into the appropriate FIFO - * if there is any data remaining to be queued. For an IN transfer, another - * data packet is always requested. For the SETUP phase of a control transfer, - * this function does nothing. - * - * Return: 1 if a new request is queued, 0 if no more requests are required - * for this transfer - */ -int dwc2_hc_continue_transfer(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan) -{ - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, - chan->hc_num); - - if (chan->do_split) - /* SPLITs always queue just once per channel */ - return 0; - - if (chan->data_pid_start == DWC2_HC_PID_SETUP) - /* SETUPs are queued only once since they can't be NAK'd */ - return 0; - - if (chan->ep_is_in) { - /* - * Always queue another request for other IN transfers. If - * back-to-back INs are issued and NAKs are received for both, - * the driver may still be processing the first NAK when the - * second NAK is received. When the interrupt handler clears - * the NAK interrupt for the first NAK, the second NAK will - * not be seen. So we can't depend on the NAK interrupt - * handler to requeue a NAK'd request. Instead, IN requests - * are issued each time this function is called. When the - * transfer completes, the extra requests for the channel will - * be flushed. - */ - u32 hcchar = DWC2_READ_4(hsotg, HCCHAR(chan->hc_num)); - - dwc2_hc_set_even_odd_frame(hsotg, chan, &hcchar); - hcchar |= HCCHAR_CHENA; - hcchar &= ~HCCHAR_CHDIS; - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, " IN xfer: hcchar = 0x%08x\n", - hcchar); - DWC2_WRITE_4(hsotg, HCCHAR(chan->hc_num), hcchar); - chan->requests++; - return 1; - } - - /* OUT transfers */ - - if (chan->xfer_count < chan->xfer_len) { - if (chan->ep_type == USB_ENDPOINT_XFER_INT || - chan->ep_type == USB_ENDPOINT_XFER_ISOC) { - u32 hcchar = DWC2_READ_4(hsotg, HCCHAR(chan->hc_num)); - - dwc2_hc_set_even_odd_frame(hsotg, chan, - &hcchar); - } - - /* Load OUT packet into the appropriate Tx FIFO */ - dwc2_hc_write_packet(hsotg, chan); - chan->requests++; - return 1; - } - - return 0; -} - -/** - * dwc2_hc_do_ping() - Starts a PING transfer - * - * @hsotg: Programming view of DWC_otg controller - * @chan: Information needed to initialize the host channel - * - * This function should only be called in Slave mode. The Do Ping bit is set in - * the HCTSIZ register, then the channel is enabled. - */ -void dwc2_hc_do_ping(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan) -{ - u32 hcchar; - u32 hctsiz; - - if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, - chan->hc_num); - - - hctsiz = TSIZ_DOPNG; - hctsiz |= 1 << TSIZ_PKTCNT_SHIFT; - DWC2_WRITE_4(hsotg, HCTSIZ(chan->hc_num), hctsiz); - - hcchar = DWC2_READ_4(hsotg, HCCHAR(chan->hc_num)); - hcchar |= HCCHAR_CHENA; - hcchar &= ~HCCHAR_CHDIS; - DWC2_WRITE_4(hsotg, HCCHAR(chan->hc_num), hcchar); -} - -/** - * dwc2_calc_frame_interval() - Calculates the correct frame Interval value for - * the HFIR register according to PHY type and speed - * - * @hsotg: Programming view of DWC_otg controller - * - * NOTE: The caller can modify the value of the HFIR register only after the - * Port Enable bit of the Host Port Control and Status register (HPRT.EnaPort) - * has been set - */ -u32 dwc2_calc_frame_interval(struct dwc2_hsotg *hsotg) -{ - u32 usbcfg; - u32 hprt0; - int clock = 60; /* default value */ - - usbcfg = DWC2_READ_4(hsotg, GUSBCFG); - hprt0 = DWC2_READ_4(hsotg, HPRT0); - - if (!(usbcfg & GUSBCFG_PHYSEL) && (usbcfg & GUSBCFG_ULPI_UTMI_SEL) && - !(usbcfg & GUSBCFG_PHYIF16)) - clock = 60; - if ((usbcfg & GUSBCFG_PHYSEL) && hsotg->hw_params.fs_phy_type == - GHWCFG2_FS_PHY_TYPE_SHARED_ULPI) - clock = 48; - if (!(usbcfg & GUSBCFG_PHY_LP_CLK_SEL) && !(usbcfg & GUSBCFG_PHYSEL) && - !(usbcfg & GUSBCFG_ULPI_UTMI_SEL) && (usbcfg & GUSBCFG_PHYIF16)) - clock = 30; - if (!(usbcfg & GUSBCFG_PHY_LP_CLK_SEL) && !(usbcfg & GUSBCFG_PHYSEL) && - !(usbcfg & GUSBCFG_ULPI_UTMI_SEL) && !(usbcfg & GUSBCFG_PHYIF16)) - clock = 60; - if ((usbcfg & GUSBCFG_PHY_LP_CLK_SEL) && !(usbcfg & GUSBCFG_PHYSEL) && - !(usbcfg & GUSBCFG_ULPI_UTMI_SEL) && (usbcfg & GUSBCFG_PHYIF16)) - clock = 48; - if ((usbcfg & GUSBCFG_PHYSEL) && !(usbcfg & GUSBCFG_PHYIF16) && - hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_SHARED_UTMI) - clock = 48; - if ((usbcfg & GUSBCFG_PHYSEL) && - hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED) - clock = 48; - - if ((hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT == HPRT0_SPD_HIGH_SPEED) - /* High speed case */ - return 125 * clock; - else - /* FS/LS case */ - return 1000 * clock; -} - -/** - * dwc2_read_packet() - Reads a packet from the Rx FIFO into the destination - * buffer - * - * @core_if: Programming view of DWC_otg controller - * @dest: Destination buffer for the packet - * @bytes: Number of bytes to copy to the destination - */ -void dwc2_read_packet(struct dwc2_hsotg *hsotg, u8 *dest, u16 bytes) -{ - bus_size_t fifo = HCFIFO(0); - u32 *data_buf = (u32 *)dest; - int word_count = (bytes + 3) / 4; - int i; - - /* - * Todo: Account for the case where dest is not dword aligned. This - * requires reading data from the FIFO into a u32 temp buffer, then - * moving it into the data buffer. - */ - - dev_vdbg(hsotg->dev, "%s(%p,%p,%d)\n", __func__, hsotg, dest, bytes); - - for (i = 0; i < word_count; i++, data_buf++) - *data_buf = DWC2_READ_4(hsotg, fifo); + dev_dbg(hsotg->dev, "Enabling Active Clock Gating\n"); + pcgcctl1 |= PCGCCTL1_GATEEN; + dwc2_writel(hsotg, pcgcctl1, PCGCCTL1); + } } /** @@ -2336,56 +661,57 @@ void dwc2_dump_host_registers(struct dwc2_hsotg *hsotg) dev_dbg(hsotg->dev, "Host Global Registers\n"); addr = HCFG; dev_dbg(hsotg->dev, "HCFG @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, HCFG)); addr = HFIR; dev_dbg(hsotg->dev, "HFIR @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, HFIR)); addr = HFNUM; dev_dbg(hsotg->dev, "HFNUM @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, HFNUM)); addr = HPTXSTS; dev_dbg(hsotg->dev, "HPTXSTS @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, HPTXSTS)); addr = HAINT; dev_dbg(hsotg->dev, "HAINT @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, HAINT)); addr = HAINTMSK; dev_dbg(hsotg->dev, "HAINTMSK @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); - if (hsotg->core_params->dma_desc_enable > 0) { + (unsigned long)addr, dwc2_readl(hsotg, HAINTMSK)); + if (hsotg->params.dma_desc_enable) { addr = HFLBADDR; dev_dbg(hsotg->dev, "HFLBADDR @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, HFLBADDR)); } addr = HPRT0; dev_dbg(hsotg->dev, "HPRT0 @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, HPRT0)); - for (i = 0; i < hsotg->core_params->host_channels; i++) { + for (i = 0; i < hsotg->params.host_channels; i++) { dev_dbg(hsotg->dev, "Host Channel %d Specific Registers\n", i); addr = HCCHAR(i); dev_dbg(hsotg->dev, "HCCHAR @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, HCCHAR(i))); addr = HCSPLT(i); dev_dbg(hsotg->dev, "HCSPLT @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, HCSPLT(i))); addr = HCINT(i); dev_dbg(hsotg->dev, "HCINT @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, HCINT(i))); addr = HCINTMSK(i); dev_dbg(hsotg->dev, "HCINTMSK @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, HCINTMSK(i))); addr = HCTSIZ(i); dev_dbg(hsotg->dev, "HCTSIZ @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, HCTSIZ(i))); addr = HCDMA(i); dev_dbg(hsotg->dev, "HCDMA @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); - if (hsotg->core_params->dma_desc_enable > 0) { + (unsigned long)addr, dwc2_readl(hsotg, HCDMA(i))); + if (hsotg->params.dma_desc_enable) { addr = HCDMAB(i); dev_dbg(hsotg->dev, "HCDMAB @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, + HCDMAB(i))); } } #endif @@ -2407,80 +733,80 @@ void dwc2_dump_global_registers(struct dwc2_hsotg *hsotg) dev_dbg(hsotg->dev, "Core Global Registers\n"); addr = GOTGCTL; dev_dbg(hsotg->dev, "GOTGCTL @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GOTGCTL)); addr = GOTGINT; dev_dbg(hsotg->dev, "GOTGINT @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GOTGINT)); addr = GAHBCFG; dev_dbg(hsotg->dev, "GAHBCFG @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GAHBCFG)); addr = GUSBCFG; dev_dbg(hsotg->dev, "GUSBCFG @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GUSBCFG)); addr = GRSTCTL; dev_dbg(hsotg->dev, "GRSTCTL @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GRSTCTL)); addr = GINTSTS; dev_dbg(hsotg->dev, "GINTSTS @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GINTSTS)); addr = GINTMSK; dev_dbg(hsotg->dev, "GINTMSK @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GINTMSK)); addr = GRXSTSR; dev_dbg(hsotg->dev, "GRXSTSR @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GRXSTSR)); addr = GRXFSIZ; dev_dbg(hsotg->dev, "GRXFSIZ @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GRXFSIZ)); addr = GNPTXFSIZ; dev_dbg(hsotg->dev, "GNPTXFSIZ @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GNPTXFSIZ)); addr = GNPTXSTS; dev_dbg(hsotg->dev, "GNPTXSTS @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GNPTXSTS)); addr = GI2CCTL; dev_dbg(hsotg->dev, "GI2CCTL @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GI2CCTL)); addr = GPVNDCTL; dev_dbg(hsotg->dev, "GPVNDCTL @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GPVNDCTL)); addr = GGPIO; dev_dbg(hsotg->dev, "GGPIO @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GGPIO)); addr = GUID; dev_dbg(hsotg->dev, "GUID @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GUID)); addr = GSNPSID; dev_dbg(hsotg->dev, "GSNPSID @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GSNPSID)); addr = GHWCFG1; dev_dbg(hsotg->dev, "GHWCFG1 @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GHWCFG1)); addr = GHWCFG2; dev_dbg(hsotg->dev, "GHWCFG2 @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GHWCFG2)); addr = GHWCFG3; dev_dbg(hsotg->dev, "GHWCFG3 @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GHWCFG3)); addr = GHWCFG4; dev_dbg(hsotg->dev, "GHWCFG4 @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GHWCFG4)); addr = GLPMCFG; dev_dbg(hsotg->dev, "GLPMCFG @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GLPMCFG)); addr = GPWRDN; dev_dbg(hsotg->dev, "GPWRDN @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GPWRDN)); addr = GDFIFOCFG; dev_dbg(hsotg->dev, "GDFIFOCFG @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, GDFIFOCFG)); addr = HPTXFSIZ; dev_dbg(hsotg->dev, "HPTXFSIZ @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, HPTXFSIZ)); addr = PCGCTL; dev_dbg(hsotg->dev, "PCGCTL @0x%08lX : 0x%08X\n", - (unsigned long)addr, DWC2_READ_4(hsotg, addr)); + (unsigned long)addr, dwc2_readl(hsotg, PCGCTL)); #endif } @@ -2493,25 +819,21 @@ void dwc2_dump_global_registers(struct dwc2_hsotg *hsotg) void dwc2_flush_tx_fifo(struct dwc2_hsotg *hsotg, const int num) { u32 greset; - int count = 0; dev_vdbg(hsotg->dev, "Flush Tx FIFO %d\n", num); + /* Wait for AHB master IDLE state */ + if (dwc2_hsotg_wait_bit_set(hsotg, GRSTCTL, GRSTCTL_AHBIDLE, 10000)) + dev_warn(hsotg->dev, "%s: HANG! AHB Idle GRSCTL\n", + __func__); + greset = GRSTCTL_TXFFLSH; greset |= num << GRSTCTL_TXFNUM_SHIFT & GRSTCTL_TXFNUM_MASK; - DWC2_WRITE_4(hsotg, GRSTCTL, greset); - - do { - greset = DWC2_READ_4(hsotg, GRSTCTL); - if (++count > 10000) { - dev_warn(hsotg->dev, - "%s() HANG! GRSTCTL=%0x GNPTXSTS=0x%08x\n", - __func__, greset, - DWC2_READ_4(hsotg, GNPTXSTS)); - break; - } - udelay(1); - } while (greset & GRSTCTL_TXFFLSH); + dwc2_writel(hsotg, greset, GRSTCTL); + + if (dwc2_hsotg_wait_bit_clear(hsotg, GRSTCTL, GRSTCTL_TXFFLSH, 10000)) + dev_warn(hsotg->dev, "%s: HANG! timeout GRSTCTL GRSTCTL_TXFFLSH\n", + __func__); /* Wait for at least 3 PHY Clocks */ udelay(1); @@ -2525,1038 +847,482 @@ void dwc2_flush_tx_fifo(struct dwc2_hsotg *hsotg, const int num) void dwc2_flush_rx_fifo(struct dwc2_hsotg *hsotg) { u32 greset; - int count = 0; dev_vdbg(hsotg->dev, "%s()\n", __func__); + /* Wait for AHB master IDLE state */ + if (dwc2_hsotg_wait_bit_set(hsotg, GRSTCTL, GRSTCTL_AHBIDLE, 10000)) + dev_warn(hsotg->dev, "%s: HANG! AHB Idle GRSCTL\n", + __func__); + greset = GRSTCTL_RXFFLSH; - DWC2_WRITE_4(hsotg, GRSTCTL, greset); + dwc2_writel(hsotg, greset, GRSTCTL); - do { - greset = DWC2_READ_4(hsotg, GRSTCTL); - if (++count > 10000) { - dev_warn(hsotg->dev, "%s() HANG! GRSTCTL=%0x\n", - __func__, greset); - break; - } - udelay(1); - } while (greset & GRSTCTL_RXFFLSH); + /* Wait for RxFIFO flush done */ + if (dwc2_hsotg_wait_bit_clear(hsotg, GRSTCTL, GRSTCTL_RXFFLSH, 10000)) + dev_warn(hsotg->dev, "%s: HANG! timeout GRSTCTL GRSTCTL_RXFFLSH\n", + __func__); /* Wait for at least 3 PHY Clocks */ udelay(1); } -#define DWC2_OUT_OF_BOUNDS(a, b, c) ((a) < (b) || (a) > (c)) - -/* Parameter access functions */ -void dwc2_set_param_otg_cap(struct dwc2_hsotg *hsotg, int val) -{ - int valid = 1; - - switch (val) { - case DWC2_CAP_PARAM_HNP_SRP_CAPABLE: - if (hsotg->hw_params.op_mode != GHWCFG2_OP_MODE_HNP_SRP_CAPABLE) - valid = 0; - break; - case DWC2_CAP_PARAM_SRP_ONLY_CAPABLE: - switch (hsotg->hw_params.op_mode) { - case GHWCFG2_OP_MODE_HNP_SRP_CAPABLE: - case GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE: - case GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE: - case GHWCFG2_OP_MODE_SRP_CAPABLE_HOST: - break; - default: - valid = 0; - break; - } - break; - case DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE: - /* always valid */ - break; - default: - valid = 0; - break; - } - - if (!valid) { - if (val >= 0) - dev_err(hsotg->dev, - "%d invalid for otg_cap parameter. Check HW configuration.\n", - val); - switch (hsotg->hw_params.op_mode) { - case GHWCFG2_OP_MODE_HNP_SRP_CAPABLE: - val = DWC2_CAP_PARAM_HNP_SRP_CAPABLE; - break; - case GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE: - case GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE: - case GHWCFG2_OP_MODE_SRP_CAPABLE_HOST: - val = DWC2_CAP_PARAM_SRP_ONLY_CAPABLE; - break; - default: - val = DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE; - break; - } - dev_dbg(hsotg->dev, "Setting otg_cap to %d\n", val); - } - - hsotg->core_params->otg_cap = val; -} - -void dwc2_set_param_dma_enable(struct dwc2_hsotg *hsotg, int val) -{ - int valid = 1; - - if (val > 0 && hsotg->hw_params.arch == GHWCFG2_SLAVE_ONLY_ARCH) - valid = 0; - if (val < 0) - valid = 0; - - if (!valid) { - if (val >= 0) - dev_err(hsotg->dev, - "%d invalid for dma_enable parameter. Check HW configuration.\n", - val); - val = hsotg->hw_params.arch != GHWCFG2_SLAVE_ONLY_ARCH; - dev_dbg(hsotg->dev, "Setting dma_enable to %d\n", val); - } - - hsotg->core_params->dma_enable = val; -} - -void dwc2_set_param_dma_desc_enable(struct dwc2_hsotg *hsotg, int val) -{ - int valid = 1; - - if (val > 0 && (hsotg->core_params->dma_enable <= 0 || - !hsotg->hw_params.dma_desc_enable)) - valid = 0; - if (val < 0) - valid = 0; - - if (!valid) { - if (val >= 0) - dev_err(hsotg->dev, - "%d invalid for dma_desc_enable parameter. Check HW configuration.\n", - val); - val = (hsotg->core_params->dma_enable > 0 && - hsotg->hw_params.dma_desc_enable); - dev_dbg(hsotg->dev, "Setting dma_desc_enable to %d\n", val); - } - - hsotg->core_params->dma_desc_enable = val; -} - -void dwc2_set_param_dma_desc_fs_enable(struct dwc2_hsotg *hsotg, int val) -{ - int valid = 1; - - if (val > 0 && (hsotg->core_params->dma_enable <= 0 || - !hsotg->hw_params.dma_desc_enable)) - valid = 0; - if (val < 0) - valid = 0; - - if (!valid) { - if (val >= 0) - dev_err(hsotg->dev, - "%d invalid for dma_desc_fs_enable parameter. Check HW configuration.\n", - val); - val = (hsotg->core_params->dma_enable > 0 && - hsotg->hw_params.dma_desc_enable); - } - - hsotg->core_params->dma_desc_fs_enable = val; - dev_dbg(hsotg->dev, "Setting dma_desc_fs_enable to %d\n", val); -} - -void dwc2_set_param_host_support_fs_ls_low_power(struct dwc2_hsotg *hsotg, - int val) -{ - if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { - if (val >= 0) { - dev_err(hsotg->dev, - "Wrong value for host_support_fs_low_power\n"); - dev_err(hsotg->dev, - "host_support_fs_low_power must be 0 or 1\n"); - } - val = 0; - dev_dbg(hsotg->dev, - "Setting host_support_fs_low_power to %d\n", val); - } - - hsotg->core_params->host_support_fs_ls_low_power = val; -} - -void dwc2_set_param_enable_dynamic_fifo(struct dwc2_hsotg *hsotg, int val) -{ - int valid = 1; - - if (val > 0 && !hsotg->hw_params.enable_dynamic_fifo) - valid = 0; - if (val < 0) - valid = 0; - - if (!valid) { - if (val >= 0) - dev_err(hsotg->dev, - "%d invalid for enable_dynamic_fifo parameter. Check HW configuration.\n", - val); - val = hsotg->hw_params.enable_dynamic_fifo; - dev_dbg(hsotg->dev, "Setting enable_dynamic_fifo to %d\n", val); - } - - hsotg->core_params->enable_dynamic_fifo = val; -} - -void dwc2_set_param_host_rx_fifo_size(struct dwc2_hsotg *hsotg, int val) -{ - int valid = 1; - - if (val < 16 || val > hsotg->hw_params.host_rx_fifo_size) - valid = 0; - - if (!valid) { - if (val >= 0) - dev_err(hsotg->dev, - "%d invalid for host_rx_fifo_size. Check HW configuration.\n", - val); - val = hsotg->hw_params.host_rx_fifo_size; - dev_dbg(hsotg->dev, "Setting host_rx_fifo_size to %d\n", val); - } - - hsotg->core_params->host_rx_fifo_size = val; -} - -void dwc2_set_param_host_nperio_tx_fifo_size(struct dwc2_hsotg *hsotg, int val) -{ - int valid = 1; - - if (val < 16 || val > hsotg->hw_params.host_nperio_tx_fifo_size) - valid = 0; - - if (!valid) { - if (val >= 0) - dev_err(hsotg->dev, - "%d invalid for host_nperio_tx_fifo_size. Check HW configuration.\n", - val); - val = hsotg->hw_params.host_nperio_tx_fifo_size; - dev_dbg(hsotg->dev, "Setting host_nperio_tx_fifo_size to %d\n", - val); - } - - hsotg->core_params->host_nperio_tx_fifo_size = val; -} - -void dwc2_set_param_host_perio_tx_fifo_size(struct dwc2_hsotg *hsotg, int val) +bool dwc2_is_controller_alive(struct dwc2_hsotg *hsotg) { - int valid = 1; - - if (val < 16 || val > hsotg->hw_params.host_perio_tx_fifo_size) - valid = 0; - - if (!valid) { - if (val >= 0) - dev_err(hsotg->dev, - "%d invalid for host_perio_tx_fifo_size. Check HW configuration.\n", - val); - val = hsotg->hw_params.host_perio_tx_fifo_size; - dev_dbg(hsotg->dev, "Setting host_perio_tx_fifo_size to %d\n", - val); - } - - hsotg->core_params->host_perio_tx_fifo_size = val; + if (dwc2_readl(hsotg, GSNPSID) == 0xffffffff) + return false; + else + return true; } -void dwc2_set_param_max_transfer_size(struct dwc2_hsotg *hsotg, int val) +/** + * dwc2_enable_global_interrupts() - Enables the controller's Global + * Interrupt in the AHB Config register + * + * @hsotg: Programming view of DWC_otg controller + */ +void dwc2_enable_global_interrupts(struct dwc2_hsotg *hsotg) { - int valid = 1; - - if (val < 2047 || val > hsotg->hw_params.max_transfer_size) - valid = 0; + u32 ahbcfg = dwc2_readl(hsotg, GAHBCFG); - if (!valid) { - if (val >= 0) - dev_err(hsotg->dev, - "%d invalid for max_transfer_size. Check HW configuration.\n", - val); - val = hsotg->hw_params.max_transfer_size; - dev_dbg(hsotg->dev, "Setting max_transfer_size to %d\n", val); - } - - hsotg->core_params->max_transfer_size = val; + ahbcfg |= GAHBCFG_GLBL_INTR_EN; + dwc2_writel(hsotg, ahbcfg, GAHBCFG); } -void dwc2_set_param_max_packet_count(struct dwc2_hsotg *hsotg, int val) +/** + * dwc2_disable_global_interrupts() - Disables the controller's Global + * Interrupt in the AHB Config register + * + * @hsotg: Programming view of DWC_otg controller + */ +void dwc2_disable_global_interrupts(struct dwc2_hsotg *hsotg) { - int valid = 1; - - if (val < 15 || val > hsotg->hw_params.max_packet_count) - valid = 0; + u32 ahbcfg = dwc2_readl(hsotg, GAHBCFG); - if (!valid) { - if (val >= 0) - dev_err(hsotg->dev, - "%d invalid for max_packet_count. Check HW configuration.\n", - val); - val = hsotg->hw_params.max_packet_count; - dev_dbg(hsotg->dev, "Setting max_packet_count to %d\n", val); - } - - hsotg->core_params->max_packet_count = val; + ahbcfg &= ~GAHBCFG_GLBL_INTR_EN; + dwc2_writel(hsotg, ahbcfg, GAHBCFG); } -void dwc2_set_param_host_channels(struct dwc2_hsotg *hsotg, int val) +/* Returns the controller's GHWCFG2.OTG_MODE. */ +unsigned int dwc2_op_mode(struct dwc2_hsotg *hsotg) { - int valid = 1; - - if (val < 1 || val > hsotg->hw_params.host_channels) - valid = 0; - - if (!valid) { - if (val >= 0) - dev_err(hsotg->dev, - "%d invalid for host_channels. Check HW configuration.\n", - val); - val = hsotg->hw_params.host_channels; - dev_dbg(hsotg->dev, "Setting host_channels to %d\n", val); - } + u32 ghwcfg2 = dwc2_readl(hsotg, GHWCFG2); - hsotg->core_params->host_channels = val; + return (ghwcfg2 & GHWCFG2_OP_MODE_MASK) >> + GHWCFG2_OP_MODE_SHIFT; } -void dwc2_set_param_phy_type(struct dwc2_hsotg *hsotg, int val) +/* Returns true if the controller is capable of DRD. */ +bool dwc2_hw_is_otg(struct dwc2_hsotg *hsotg) { - int valid = 0; - u32 hs_phy_type, fs_phy_type; - - if (DWC2_OUT_OF_BOUNDS(val, DWC2_PHY_TYPE_PARAM_FS, - DWC2_PHY_TYPE_PARAM_ULPI)) { - if (val >= 0) { - dev_err(hsotg->dev, "Wrong value for phy_type\n"); - dev_err(hsotg->dev, "phy_type must be 0, 1 or 2\n"); - } - - valid = 0; - } + unsigned int op_mode = dwc2_op_mode(hsotg); - hs_phy_type = hsotg->hw_params.hs_phy_type; - fs_phy_type = hsotg->hw_params.fs_phy_type; - if (val == DWC2_PHY_TYPE_PARAM_UTMI && - (hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI || - hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI_ULPI)) - valid = 1; - else if (val == DWC2_PHY_TYPE_PARAM_ULPI && - (hs_phy_type == GHWCFG2_HS_PHY_TYPE_ULPI || - hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI_ULPI)) - valid = 1; - else if (val == DWC2_PHY_TYPE_PARAM_FS && - fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED) - valid = 1; - - if (!valid) { - if (val >= 0) - dev_err(hsotg->dev, - "%d invalid for phy_type. Check HW configuration.\n", - val); - val = DWC2_PHY_TYPE_PARAM_FS; - if (hs_phy_type != GHWCFG2_HS_PHY_TYPE_NOT_SUPPORTED) { - if (hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI || - hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI_ULPI) - val = DWC2_PHY_TYPE_PARAM_UTMI; - else - val = DWC2_PHY_TYPE_PARAM_ULPI; - } - dev_dbg(hsotg->dev, "Setting phy_type to %d\n", val); - } - - hsotg->core_params->phy_type = val; -} - -STATIC int dwc2_get_param_phy_type(struct dwc2_hsotg *hsotg) -{ - return hsotg->core_params->phy_type; + return (op_mode == GHWCFG2_OP_MODE_HNP_SRP_CAPABLE) || + (op_mode == GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE) || + (op_mode == GHWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE); } -void dwc2_set_param_speed(struct dwc2_hsotg *hsotg, int val) +/* Returns true if the controller is host-only. */ +bool dwc2_hw_is_host(struct dwc2_hsotg *hsotg) { - int valid = 1; - - if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { - if (val >= 0) { - dev_err(hsotg->dev, "Wrong value for speed parameter\n"); - dev_err(hsotg->dev, "max_speed parameter must be 0 or 1\n"); - } - valid = 0; - } - - if (val == DWC2_SPEED_PARAM_HIGH && - dwc2_get_param_phy_type(hsotg) == DWC2_PHY_TYPE_PARAM_FS) - valid = 0; + unsigned int op_mode = dwc2_op_mode(hsotg); - if (!valid) { - if (val >= 0) - dev_err(hsotg->dev, - "%d invalid for speed parameter. Check HW configuration.\n", - val); - val = dwc2_get_param_phy_type(hsotg) == DWC2_PHY_TYPE_PARAM_FS ? - DWC2_SPEED_PARAM_FULL : DWC2_SPEED_PARAM_HIGH; - dev_dbg(hsotg->dev, "Setting speed to %d\n", val); - } - - hsotg->core_params->speed = val; + return (op_mode == GHWCFG2_OP_MODE_SRP_CAPABLE_HOST) || + (op_mode == GHWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST); } -void dwc2_set_param_host_ls_low_power_phy_clk(struct dwc2_hsotg *hsotg, int val) +/* Returns true if the controller is device-only. */ +bool dwc2_hw_is_device(struct dwc2_hsotg *hsotg) { - int valid = 1; - - if (DWC2_OUT_OF_BOUNDS(val, DWC2_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ, - DWC2_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ)) { - if (val >= 0) { - dev_err(hsotg->dev, - "Wrong value for host_ls_low_power_phy_clk parameter\n"); - dev_err(hsotg->dev, - "host_ls_low_power_phy_clk must be 0 or 1\n"); - } - valid = 0; - } - - if (val == DWC2_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ && - dwc2_get_param_phy_type(hsotg) == DWC2_PHY_TYPE_PARAM_FS) - valid = 0; - - if (!valid) { - if (val >= 0) - dev_err(hsotg->dev, - "%d invalid for host_ls_low_power_phy_clk. Check HW configuration.\n", - val); - val = dwc2_get_param_phy_type(hsotg) == DWC2_PHY_TYPE_PARAM_FS - ? DWC2_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ - : DWC2_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ; - dev_dbg(hsotg->dev, "Setting host_ls_low_power_phy_clk to %d\n", - val); - } + unsigned int op_mode = dwc2_op_mode(hsotg); - hsotg->core_params->host_ls_low_power_phy_clk = val; + return (op_mode == GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE) || + (op_mode == GHWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE); } -void dwc2_set_param_phy_ulpi_ddr(struct dwc2_hsotg *hsotg, int val) +/** + * dwc2_hsotg_wait_bit_set - Waits for bit to be set. + * @hsotg: Programming view of DWC_otg controller. + * @offset: Register's offset where bit/bits must be set. + * @mask: Mask of the bit/bits which must be set. + * @timeout: Timeout to wait. + * + * Return: 0 if bit/bits are set or -ETIMEDOUT in case of timeout. + */ +int dwc2_hsotg_wait_bit_set(struct dwc2_hsotg *hsotg, u32 offset, u32 mask, + u32 timeout) { - if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { - if (val >= 0) { - dev_err(hsotg->dev, "Wrong value for phy_ulpi_ddr\n"); - dev_err(hsotg->dev, "phy_upli_ddr must be 0 or 1\n"); - } - val = 0; - dev_dbg(hsotg->dev, "Setting phy_upli_ddr to %d\n", val); - } - - hsotg->core_params->phy_ulpi_ddr = val; -} + u32 i; -void dwc2_set_param_phy_ulpi_ext_vbus(struct dwc2_hsotg *hsotg, int val) -{ - if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { - if (val >= 0) { - dev_err(hsotg->dev, - "Wrong value for phy_ulpi_ext_vbus\n"); - dev_err(hsotg->dev, - "phy_ulpi_ext_vbus must be 0 or 1\n"); - } - val = 0; - dev_dbg(hsotg->dev, "Setting phy_ulpi_ext_vbus to %d\n", val); + for (i = 0; i < timeout; i++) { + if (dwc2_readl(hsotg, offset) & mask) + return 0; + udelay(1); } - hsotg->core_params->phy_ulpi_ext_vbus = val; + return -ETIMEDOUT; } -void dwc2_set_param_phy_utmi_width(struct dwc2_hsotg *hsotg, int val) +/** + * dwc2_hsotg_wait_bit_clear - Waits for bit to be clear. + * @hsotg: Programming view of DWC_otg controller. + * @offset: Register's offset where bit/bits must be set. + * @mask: Mask of the bit/bits which must be set. + * @timeout: Timeout to wait. + * + * Return: 0 if bit/bits are set or -ETIMEDOUT in case of timeout. + */ +int dwc2_hsotg_wait_bit_clear(struct dwc2_hsotg *hsotg, u32 offset, u32 mask, + u32 timeout) { - int valid = 0; - - switch (hsotg->hw_params.utmi_phy_data_width) { - case GHWCFG4_UTMI_PHY_DATA_WIDTH_8: - valid = (val == 8); - break; - case GHWCFG4_UTMI_PHY_DATA_WIDTH_16: - valid = (val == 16); - break; - case GHWCFG4_UTMI_PHY_DATA_WIDTH_8_OR_16: - valid = (val == 8 || val == 16); - break; - } + u32 i; - if (!valid) { - if (val >= 0) { - dev_err(hsotg->dev, - "%d invalid for phy_utmi_width. Check HW configuration.\n", - val); - } - val = (hsotg->hw_params.utmi_phy_data_width == - GHWCFG4_UTMI_PHY_DATA_WIDTH_8) ? 8 : 16; - dev_dbg(hsotg->dev, "Setting phy_utmi_width to %d\n", val); + for (i = 0; i < timeout; i++) { + if (!(dwc2_readl(hsotg, offset) & mask)) + return 0; + udelay(1); } - hsotg->core_params->phy_utmi_width = val; + return -ETIMEDOUT; } -void dwc2_set_param_ulpi_fs_ls(struct dwc2_hsotg *hsotg, int val) +/* + * Initializes the FSLSPClkSel field of the HCFG register depending on the + * PHY type + */ +void dwc2_init_fs_ls_pclk_sel(struct dwc2_hsotg *hsotg) { - if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { - if (val >= 0) { - dev_err(hsotg->dev, "Wrong value for ulpi_fs_ls\n"); - dev_err(hsotg->dev, "ulpi_fs_ls must be 0 or 1\n"); - } - val = 0; - dev_dbg(hsotg->dev, "Setting ulpi_fs_ls to %d\n", val); - } - - hsotg->core_params->ulpi_fs_ls = val; -} + u32 hcfg, val; -void dwc2_set_param_ts_dline(struct dwc2_hsotg *hsotg, int val) -{ - if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { - if (val >= 0) { - dev_err(hsotg->dev, "Wrong value for ts_dline\n"); - dev_err(hsotg->dev, "ts_dline must be 0 or 1\n"); - } - val = 0; - dev_dbg(hsotg->dev, "Setting ts_dline to %d\n", val); + if ((hsotg->hw_params.hs_phy_type == GHWCFG2_HS_PHY_TYPE_ULPI && + hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED && + hsotg->params.ulpi_fs_ls) || + hsotg->params.phy_type == DWC2_PHY_TYPE_PARAM_FS) { + /* Full speed PHY */ + val = HCFG_FSLSPCLKSEL_48_MHZ; + } else { + /* High speed PHY running at full speed or high speed */ + val = HCFG_FSLSPCLKSEL_30_60_MHZ; } - hsotg->core_params->ts_dline = val; + dev_dbg(hsotg->dev, "Initializing HCFG.FSLSPClkSel to %08x\n", val); + hcfg = dwc2_readl(hsotg, HCFG); + hcfg &= ~HCFG_FSLSPCLKSEL_MASK; + hcfg |= val << HCFG_FSLSPCLKSEL_SHIFT; + dwc2_writel(hsotg, hcfg, HCFG); } -void dwc2_set_param_i2c_enable(struct dwc2_hsotg *hsotg, int val) +STATIC int dwc2_fs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) { - int valid = 1; - - if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { - if (val >= 0) { - dev_err(hsotg->dev, "Wrong value for i2c_enable\n"); - dev_err(hsotg->dev, "i2c_enable must be 0 or 1\n"); - } - - valid = 0; - } - - if (val == 1 && !(hsotg->hw_params.i2c_enable)) - valid = 0; + u32 usbcfg, ggpio, i2cctl; + int retval = 0; - if (!valid) { - if (val >= 0) - dev_err(hsotg->dev, - "%d invalid for i2c_enable. Check HW configuration.\n", - val); - val = hsotg->hw_params.i2c_enable; - dev_dbg(hsotg->dev, "Setting i2c_enable to %d\n", val); - } + /* + * core_init() is now called on every switch so only call the + * following for the first time through + */ + if (select_phy) { + dev_dbg(hsotg->dev, "FS PHY selected\n"); - hsotg->core_params->i2c_enable = val; -} + usbcfg = dwc2_readl(hsotg, GUSBCFG); + if (!(usbcfg & GUSBCFG_PHYSEL)) { + usbcfg |= GUSBCFG_PHYSEL; + dwc2_writel(hsotg, usbcfg, GUSBCFG); -void dwc2_set_param_en_multiple_tx_fifo(struct dwc2_hsotg *hsotg, int val) -{ - int valid = 1; + /* Reset after a PHY select */ + retval = dwc2_core_reset(hsotg, false); - if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { - if (val >= 0) { - dev_err(hsotg->dev, - "Wrong value for en_multiple_tx_fifo,\n"); - dev_err(hsotg->dev, - "en_multiple_tx_fifo must be 0 or 1\n"); + if (retval) { + dev_err(hsotg->dev, + "%s: Reset failed, aborting", __func__); + return retval; + } } - valid = 0; - } - if (val == 1 && !hsotg->hw_params.en_multiple_tx_fifo) - valid = 0; - - if (!valid) { - if (val >= 0) - dev_err(hsotg->dev, - "%d invalid for parameter en_multiple_tx_fifo. Check HW configuration.\n", - val); - val = hsotg->hw_params.en_multiple_tx_fifo; - dev_dbg(hsotg->dev, "Setting en_multiple_tx_fifo to %d\n", val); + if (hsotg->params.activate_stm_fs_transceiver) { + ggpio = dwc2_readl(hsotg, GGPIO); + if (!(ggpio & GGPIO_STM32_OTG_GCCFG_PWRDWN)) { + dev_dbg(hsotg->dev, "Activating transceiver\n"); + /* + * STM32F4x9 uses the GGPIO register as general + * core configuration register. + */ + ggpio |= GGPIO_STM32_OTG_GCCFG_PWRDWN; + dwc2_writel(hsotg, ggpio, GGPIO); + } + } } - hsotg->core_params->en_multiple_tx_fifo = val; -} - -void dwc2_set_param_reload_ctl(struct dwc2_hsotg *hsotg, int val) -{ - int valid = 1; + /* + * Program DCFG.DevSpd or HCFG.FSLSPclkSel to 48Mhz in FS. Also + * do this on HNP Dev/Host mode switches (done in dev_init and + * host_init). + */ + if (dwc2_is_host_mode(hsotg)) + dwc2_init_fs_ls_pclk_sel(hsotg); - if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { - if (val >= 0) { - dev_err(hsotg->dev, - "'%d' invalid for parameter reload_ctl\n", val); - dev_err(hsotg->dev, "reload_ctl must be 0 or 1\n"); - } - valid = 0; - } + if (hsotg->params.i2c_enable) { + dev_dbg(hsotg->dev, "FS PHY enabling I2C\n"); - if (val == 1 && hsotg->hw_params.snpsid < DWC2_CORE_REV_2_92a) - valid = 0; + /* Program GUSBCFG.OtgUtmiFsSel to I2C */ + usbcfg = dwc2_readl(hsotg, GUSBCFG); + usbcfg |= GUSBCFG_OTG_UTMI_FS_SEL; + dwc2_writel(hsotg, usbcfg, GUSBCFG); - if (!valid) { - if (val >= 0) - dev_err(hsotg->dev, - "%d invalid for parameter reload_ctl. Check HW configuration.\n", - val); - val = hsotg->hw_params.snpsid >= DWC2_CORE_REV_2_92a; - dev_dbg(hsotg->dev, "Setting reload_ctl to %d\n", val); + /* Program GI2CCTL.I2CEn */ + i2cctl = dwc2_readl(hsotg, GI2CCTL); + i2cctl &= ~GI2CCTL_I2CDEVADDR_MASK; + i2cctl |= 1 << GI2CCTL_I2CDEVADDR_SHIFT; + i2cctl &= ~GI2CCTL_I2CEN; + dwc2_writel(hsotg, i2cctl, GI2CCTL); + i2cctl |= GI2CCTL_I2CEN; + dwc2_writel(hsotg, i2cctl, GI2CCTL); } - hsotg->core_params->reload_ctl = val; -} - -void dwc2_set_param_ahbcfg(struct dwc2_hsotg *hsotg, int val) -{ - if (val != -1) - hsotg->core_params->ahbcfg = val; - else - hsotg->core_params->ahbcfg = GAHBCFG_HBSTLEN_INCR4 << - GAHBCFG_HBSTLEN_SHIFT; + return retval; } -void dwc2_set_param_otg_ver(struct dwc2_hsotg *hsotg, int val) +STATIC int dwc2_hs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) { - if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { - if (val >= 0) { - dev_err(hsotg->dev, - "'%d' invalid for parameter otg_ver\n", val); - dev_err(hsotg->dev, - "otg_ver must be 0 (for OTG 1.3 support) or 1 (for OTG 2.0 support)\n"); - } - val = 0; - dev_dbg(hsotg->dev, "Setting otg_ver to %d\n", val); - } + u32 usbcfg, usbcfg_old; + int retval = 0; - hsotg->core_params->otg_ver = val; -} + if (!select_phy) + return 0; -STATIC void dwc2_set_param_uframe_sched(struct dwc2_hsotg *hsotg, int val) -{ - if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { - if (val >= 0) { - dev_err(hsotg->dev, - "'%d' invalid for parameter uframe_sched\n", - val); - dev_err(hsotg->dev, "uframe_sched must be 0 or 1\n"); - } - val = 1; - dev_dbg(hsotg->dev, "Setting uframe_sched to %d\n", val); - } + usbcfg = dwc2_readl(hsotg, GUSBCFG); + usbcfg_old = usbcfg; - hsotg->core_params->uframe_sched = val; -} + /* + * HS PHY parameters. These parameters are preserved during soft reset + * so only program the first time. Do a soft reset immediately after + * setting phyif. + */ + switch (hsotg->params.phy_type) { + case DWC2_PHY_TYPE_PARAM_ULPI: + /* ULPI interface */ + dev_dbg(hsotg->dev, "HS ULPI PHY selected\n"); + usbcfg |= GUSBCFG_ULPI_UTMI_SEL; + usbcfg &= ~(GUSBCFG_PHYIF16 | GUSBCFG_DDRSEL); + if (hsotg->params.phy_ulpi_ddr) + usbcfg |= GUSBCFG_DDRSEL; -STATIC void dwc2_set_param_external_id_pin_ctl(struct dwc2_hsotg *hsotg, - int val) -{ - if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { - if (val >= 0) { - dev_err(hsotg->dev, - "'%d' invalid for parameter external_id_pin_ctl\n", - val); - dev_err(hsotg->dev, "external_id_pin_ctl must be 0 or 1\n"); - } - val = 0; - dev_dbg(hsotg->dev, "Setting external_id_pin_ctl to %d\n", val); + /* Set external VBUS indicator as needed. */ + if (hsotg->params.oc_disable) + usbcfg |= (GUSBCFG_ULPI_INT_VBUS_IND | + GUSBCFG_INDICATORPASSTHROUGH); + break; + case DWC2_PHY_TYPE_PARAM_UTMI: + /* UTMI+ interface */ + dev_dbg(hsotg->dev, "HS UTMI+ PHY selected\n"); + usbcfg &= ~(GUSBCFG_ULPI_UTMI_SEL | GUSBCFG_PHYIF16); + if (hsotg->params.phy_utmi_width == 16) + usbcfg |= GUSBCFG_PHYIF16; + break; + default: + dev_err(hsotg->dev, "FS PHY selected at HS!\n"); + break; } - hsotg->core_params->external_id_pin_ctl = val; -} + if (usbcfg != usbcfg_old) { + dwc2_writel(hsotg, usbcfg, GUSBCFG); -STATIC void dwc2_set_param_hibernation(struct dwc2_hsotg *hsotg, - int val) -{ - if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { - if (val >= 0) { + /* Reset after setting the PHY parameters */ + retval = dwc2_core_reset(hsotg, false); + if (retval) { dev_err(hsotg->dev, - "'%d' invalid for parameter hibernation\n", - val); - dev_err(hsotg->dev, "hibernation must be 0 or 1\n"); + "%s: Reset failed, aborting", __func__); + return retval; } - val = 0; - dev_dbg(hsotg->dev, "Setting hibernation to %d\n", val); } - hsotg->core_params->hibernation = val; -} - -/* - * This function is called during module intialization to pass module parameters - * for the DWC_otg core. - */ -void dwc2_set_parameters(struct dwc2_hsotg *hsotg, - const struct dwc2_core_params *params) -{ - dev_dbg(hsotg->dev, "%s()\n", __func__); - - dwc2_set_param_otg_cap(hsotg, params->otg_cap); - dwc2_set_param_dma_enable(hsotg, params->dma_enable); - dwc2_set_param_dma_desc_enable(hsotg, params->dma_desc_enable); - dwc2_set_param_dma_desc_fs_enable(hsotg, params->dma_desc_fs_enable); - dwc2_set_param_host_support_fs_ls_low_power(hsotg, - params->host_support_fs_ls_low_power); - dwc2_set_param_enable_dynamic_fifo(hsotg, - params->enable_dynamic_fifo); - dwc2_set_param_host_rx_fifo_size(hsotg, - params->host_rx_fifo_size); - dwc2_set_param_host_nperio_tx_fifo_size(hsotg, - params->host_nperio_tx_fifo_size); - dwc2_set_param_host_perio_tx_fifo_size(hsotg, - params->host_perio_tx_fifo_size); - dwc2_set_param_max_transfer_size(hsotg, - params->max_transfer_size); - dwc2_set_param_max_packet_count(hsotg, - params->max_packet_count); - dwc2_set_param_host_channels(hsotg, params->host_channels); - dwc2_set_param_phy_type(hsotg, params->phy_type); - dwc2_set_param_speed(hsotg, params->speed); - dwc2_set_param_host_ls_low_power_phy_clk(hsotg, - params->host_ls_low_power_phy_clk); - dwc2_set_param_phy_ulpi_ddr(hsotg, params->phy_ulpi_ddr); - dwc2_set_param_phy_ulpi_ext_vbus(hsotg, - params->phy_ulpi_ext_vbus); - dwc2_set_param_phy_utmi_width(hsotg, params->phy_utmi_width); - dwc2_set_param_ulpi_fs_ls(hsotg, params->ulpi_fs_ls); - dwc2_set_param_ts_dline(hsotg, params->ts_dline); - dwc2_set_param_i2c_enable(hsotg, params->i2c_enable); - dwc2_set_param_en_multiple_tx_fifo(hsotg, - params->en_multiple_tx_fifo); - dwc2_set_param_reload_ctl(hsotg, params->reload_ctl); - dwc2_set_param_ahbcfg(hsotg, params->ahbcfg); - dwc2_set_param_otg_ver(hsotg, params->otg_ver); - dwc2_set_param_uframe_sched(hsotg, params->uframe_sched); - dwc2_set_param_external_id_pin_ctl(hsotg, params->external_id_pin_ctl); - dwc2_set_param_hibernation(hsotg, params->hibernation); -} - -/* - * Forces either host or device mode if the controller is not - * currently in that mode. - * - * Returns true if the mode was forced. - */ -STATIC bool dwc2_force_mode_if_needed(struct dwc2_hsotg *hsotg, bool host) -{ - if (host && dwc2_is_host_mode(hsotg)) - return false; - else if (!host && dwc2_is_device_mode(hsotg)) - return false; - - return dwc2_force_mode(hsotg, host); + return retval; } -/* - * Gets host hardware parameters. Forces host mode if not currently in - * host mode. Should be called immediately after a core soft reset in - * order to get the reset values. - */ -STATIC void dwc2_get_host_hwparams(struct dwc2_hsotg *hsotg) +static void dwc2_set_turnaround_time(struct dwc2_hsotg *hsotg) { - struct dwc2_hw_params *hw = &hsotg->hw_params; - u32 gnptxfsiz; - u32 hptxfsiz; - bool forced; + u32 usbcfg; - if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) + if (hsotg->params.phy_type != DWC2_PHY_TYPE_PARAM_UTMI) return; - forced = dwc2_force_mode_if_needed(hsotg, true); - - gnptxfsiz = DWC2_READ_4(hsotg, GNPTXFSIZ); - hptxfsiz = DWC2_READ_4(hsotg, HPTXFSIZ); - dev_dbg(hsotg->dev, "gnptxfsiz=%08x\n", gnptxfsiz); - dev_dbg(hsotg->dev, "hptxfsiz=%08x\n", hptxfsiz); + usbcfg = dwc2_readl(hsotg, GUSBCFG); - if (forced) - dwc2_clear_force_mode(hsotg); + usbcfg &= ~GUSBCFG_USBTRDTIM_MASK; + if (hsotg->params.phy_utmi_width == 16) + usbcfg |= 5 << GUSBCFG_USBTRDTIM_SHIFT; + else + usbcfg |= 9 << GUSBCFG_USBTRDTIM_SHIFT; - hw->host_nperio_tx_fifo_size = (gnptxfsiz & FIFOSIZE_DEPTH_MASK) >> - FIFOSIZE_DEPTH_SHIFT; - hw->host_perio_tx_fifo_size = (hptxfsiz & FIFOSIZE_DEPTH_MASK) >> - FIFOSIZE_DEPTH_SHIFT; + dwc2_writel(hsotg, usbcfg, GUSBCFG); } -/* - * Gets device hardware parameters. Forces device mode if not - * currently in device mode. Should be called immediately after a core - * soft reset in order to get the reset values. - */ -STATIC void dwc2_get_dev_hwparams(struct dwc2_hsotg *hsotg) +STATIC int dwc2_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) { - struct dwc2_hw_params *hw = &hsotg->hw_params; - bool forced; - u32 gnptxfsiz; + u32 usbcfg; + u32 otgctl; + int retval = 0; - if (hsotg->dr_mode == USB_DR_MODE_HOST) - return; + if ((hsotg->params.speed == DWC2_SPEED_PARAM_FULL || + hsotg->params.speed == DWC2_SPEED_PARAM_LOW) && + hsotg->params.phy_type == DWC2_PHY_TYPE_PARAM_FS) { + /* If FS/LS mode with FS/LS PHY */ + retval = dwc2_fs_phy_init(hsotg, select_phy); + if (retval) + return retval; + } else { + /* High speed PHY */ + retval = dwc2_hs_phy_init(hsotg, select_phy); + if (retval) + return retval; - forced = dwc2_force_mode_if_needed(hsotg, false); + if (dwc2_is_device_mode(hsotg)) + dwc2_set_turnaround_time(hsotg); + } - gnptxfsiz = DWC2_READ_4(hsotg, GNPTXFSIZ); - dev_dbg(hsotg->dev, "gnptxfsiz=%08x\n", gnptxfsiz); + if (hsotg->hw_params.hs_phy_type == GHWCFG2_HS_PHY_TYPE_ULPI && + hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED && + hsotg->params.ulpi_fs_ls) { + dev_dbg(hsotg->dev, "Setting ULPI FSLS\n"); + usbcfg = dwc2_readl(hsotg, GUSBCFG); + usbcfg |= GUSBCFG_ULPI_FS_LS; + usbcfg |= GUSBCFG_ULPI_CLK_SUSP_M; + dwc2_writel(hsotg, usbcfg, GUSBCFG); + } else { + usbcfg = dwc2_readl(hsotg, GUSBCFG); + usbcfg &= ~GUSBCFG_ULPI_FS_LS; + usbcfg &= ~GUSBCFG_ULPI_CLK_SUSP_M; + dwc2_writel(hsotg, usbcfg, GUSBCFG); + } - if (forced) - dwc2_clear_force_mode(hsotg); + if (!hsotg->params.activate_ingenic_overcurrent_detection) { + if (dwc2_is_host_mode(hsotg)) { + otgctl = dwc2_readl(hsotg, GOTGCTL); + otgctl |= GOTGCTL_VBVALOEN | GOTGCTL_VBVALOVAL; + dwc2_writel(hsotg, otgctl, GOTGCTL); + } + } - hw->dev_nperio_tx_fifo_size = (gnptxfsiz & FIFOSIZE_DEPTH_MASK) >> - FIFOSIZE_DEPTH_SHIFT; + return retval; } +/*** gadget.c *****************************************************************/ +#if 0 /** - * During device initialization, read various hardware configuration - * registers and interpret the contents. + * dwc2_backup_device_registers() - Backup controller device registers. + * When suspending usb bus, registers needs to be backuped + * if controller power is disabled once suspended. + * + * @hsotg: Programming view of the DWC_otg controller */ -int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) +int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg) { - struct dwc2_hw_params *hw = &hsotg->hw_params; - unsigned width; - u32 hwcfg1, hwcfg2, hwcfg3, hwcfg4; - u32 grxfsiz; - - /* - * Attempt to ensure this device is really a DWC_otg Controller. - * Read and verify the GSNPSID register contents. The value should be - * 0x45f42xxx or 0x45f43xxx, which corresponds to either "OT2" or "OT3", - * as in "OTG version 2.xx" or "OTG version 3.xx". - */ - hw->snpsid = DWC2_READ_4(hsotg, GSNPSID); - if ((hw->snpsid & 0xfffff000) != 0x4f542000 && - (hw->snpsid & 0xfffff000) != 0x4f543000) { - dev_err(hsotg->dev, "Bad value for GSNPSID: 0x%08x\n", - hw->snpsid); - return -ENODEV; - } - - dev_dbg(hsotg->dev, "Core Release: %1x.%1x%1x%1x (snpsid=%x)\n", - hw->snpsid >> 12 & 0xf, hw->snpsid >> 8 & 0xf, - hw->snpsid >> 4 & 0xf, hw->snpsid & 0xf, hw->snpsid); + struct dwc2_dregs_backup *dr; + int i; - hwcfg1 = DWC2_READ_4(hsotg, GHWCFG1); - hwcfg2 = DWC2_READ_4(hsotg, GHWCFG2); - hwcfg3 = DWC2_READ_4(hsotg, GHWCFG3); - hwcfg4 = DWC2_READ_4(hsotg, GHWCFG4); - grxfsiz = DWC2_READ_4(hsotg, GRXFSIZ); + dev_dbg(hsotg->dev, "%s\n", __func__); - dev_dbg(hsotg->dev, "hwcfg1=%08x\n", hwcfg1); - dev_dbg(hsotg->dev, "hwcfg2=%08x\n", hwcfg2); - dev_dbg(hsotg->dev, "hwcfg3=%08x\n", hwcfg3); - dev_dbg(hsotg->dev, "hwcfg4=%08x\n", hwcfg4); - dev_dbg(hsotg->dev, "grxfsiz=%08x\n", grxfsiz); + /* Backup dev regs */ + dr = &hsotg->dr_backup; - /* - * Host specific hardware parameters. Reading these parameters - * requires the controller to be in host mode. The mode will - * be forced, if necessary, to read these values. - */ - dwc2_get_host_hwparams(hsotg); - dwc2_get_dev_hwparams(hsotg); - - /* hwcfg1 */ - hw->dev_ep_dirs = hwcfg1; - - /* hwcfg2 */ - hw->op_mode = (hwcfg2 & GHWCFG2_OP_MODE_MASK) >> - GHWCFG2_OP_MODE_SHIFT; - hw->arch = (hwcfg2 & GHWCFG2_ARCHITECTURE_MASK) >> - GHWCFG2_ARCHITECTURE_SHIFT; - hw->enable_dynamic_fifo = !!(hwcfg2 & GHWCFG2_DYNAMIC_FIFO); - hw->host_channels = 1 + ((hwcfg2 & GHWCFG2_NUM_HOST_CHAN_MASK) >> - GHWCFG2_NUM_HOST_CHAN_SHIFT); - hw->hs_phy_type = (hwcfg2 & GHWCFG2_HS_PHY_TYPE_MASK) >> - GHWCFG2_HS_PHY_TYPE_SHIFT; - hw->fs_phy_type = (hwcfg2 & GHWCFG2_FS_PHY_TYPE_MASK) >> - GHWCFG2_FS_PHY_TYPE_SHIFT; - hw->num_dev_ep = (hwcfg2 & GHWCFG2_NUM_DEV_EP_MASK) >> - GHWCFG2_NUM_DEV_EP_SHIFT; - hw->nperio_tx_q_depth = - (hwcfg2 & GHWCFG2_NONPERIO_TX_Q_DEPTH_MASK) >> - GHWCFG2_NONPERIO_TX_Q_DEPTH_SHIFT << 1; - hw->host_perio_tx_q_depth = - (hwcfg2 & GHWCFG2_HOST_PERIO_TX_Q_DEPTH_MASK) >> - GHWCFG2_HOST_PERIO_TX_Q_DEPTH_SHIFT << 1; - hw->dev_token_q_depth = - (hwcfg2 & GHWCFG2_DEV_TOKEN_Q_DEPTH_MASK) >> - GHWCFG2_DEV_TOKEN_Q_DEPTH_SHIFT; - - /* hwcfg3 */ - width = (hwcfg3 & GHWCFG3_XFER_SIZE_CNTR_WIDTH_MASK) >> - GHWCFG3_XFER_SIZE_CNTR_WIDTH_SHIFT; - hw->max_transfer_size = (1 << (width + 11)) - 1; - /* - * Clip max_transfer_size to 65535. dwc2_hc_setup_align_buf() allocates - * coherent buffers with this size, and if it's too large we can - * exhaust the coherent DMA pool. - */ - if (hw->max_transfer_size > 65535) - hw->max_transfer_size = 65535; - width = (hwcfg3 & GHWCFG3_PACKET_SIZE_CNTR_WIDTH_MASK) >> - GHWCFG3_PACKET_SIZE_CNTR_WIDTH_SHIFT; - hw->max_packet_count = (1 << (width + 4)) - 1; - hw->i2c_enable = !!(hwcfg3 & GHWCFG3_I2C); - hw->total_fifo_size = (hwcfg3 & GHWCFG3_DFIFO_DEPTH_MASK) >> - GHWCFG3_DFIFO_DEPTH_SHIFT; - - /* hwcfg4 */ - hw->en_multiple_tx_fifo = !!(hwcfg4 & GHWCFG4_DED_FIFO_EN); - hw->num_dev_perio_in_ep = (hwcfg4 & GHWCFG4_NUM_DEV_PERIO_IN_EP_MASK) >> - GHWCFG4_NUM_DEV_PERIO_IN_EP_SHIFT; - hw->dma_desc_enable = !!(hwcfg4 & GHWCFG4_DESC_DMA); - hw->power_optimized = !!(hwcfg4 & GHWCFG4_POWER_OPTIMIZ); - hw->utmi_phy_data_width = (hwcfg4 & GHWCFG4_UTMI_PHY_DATA_WIDTH_MASK) >> - GHWCFG4_UTMI_PHY_DATA_WIDTH_SHIFT; - - /* fifo sizes */ - hw->host_rx_fifo_size = (grxfsiz & GRXFSIZ_DEPTH_MASK) >> - GRXFSIZ_DEPTH_SHIFT; - - dev_dbg(hsotg->dev, "Detected values from hardware:\n"); - dev_dbg(hsotg->dev, " op_mode=%d\n", - hw->op_mode); - dev_dbg(hsotg->dev, " arch=%d\n", - hw->arch); - dev_dbg(hsotg->dev, " dma_desc_enable=%d\n", - hw->dma_desc_enable); - dev_dbg(hsotg->dev, " power_optimized=%d\n", - hw->power_optimized); - dev_dbg(hsotg->dev, " i2c_enable=%d\n", - hw->i2c_enable); - dev_dbg(hsotg->dev, " hs_phy_type=%d\n", - hw->hs_phy_type); - dev_dbg(hsotg->dev, " fs_phy_type=%d\n", - hw->fs_phy_type); - dev_dbg(hsotg->dev, " utmi_phy_data_width=%d\n", - hw->utmi_phy_data_width); - dev_dbg(hsotg->dev, " num_dev_ep=%d\n", - hw->num_dev_ep); - dev_dbg(hsotg->dev, " num_dev_perio_in_ep=%d\n", - hw->num_dev_perio_in_ep); - dev_dbg(hsotg->dev, " host_channels=%d\n", - hw->host_channels); - dev_dbg(hsotg->dev, " max_transfer_size=%d\n", - hw->max_transfer_size); - dev_dbg(hsotg->dev, " max_packet_count=%d\n", - hw->max_packet_count); - dev_dbg(hsotg->dev, " nperio_tx_q_depth=0x%0x\n", - hw->nperio_tx_q_depth); - dev_dbg(hsotg->dev, " host_perio_tx_q_depth=0x%0x\n", - hw->host_perio_tx_q_depth); - dev_dbg(hsotg->dev, " dev_token_q_depth=0x%0x\n", - hw->dev_token_q_depth); - dev_dbg(hsotg->dev, " enable_dynamic_fifo=%d\n", - hw->enable_dynamic_fifo); - dev_dbg(hsotg->dev, " en_multiple_tx_fifo=%d\n", - hw->en_multiple_tx_fifo); - dev_dbg(hsotg->dev, " total_fifo_size=%d\n", - hw->total_fifo_size); - dev_dbg(hsotg->dev, " host_rx_fifo_size=%d\n", - hw->host_rx_fifo_size); - dev_dbg(hsotg->dev, " host_nperio_tx_fifo_size=%d\n", - hw->host_nperio_tx_fifo_size); - dev_dbg(hsotg->dev, " host_perio_tx_fifo_size=%d\n", - hw->host_perio_tx_fifo_size); - dev_dbg(hsotg->dev, "\n"); + dr->dcfg = dwc2_readl(hsotg, DCFG); + dr->dctl = dwc2_readl(hsotg, DCTL); + dr->daintmsk = dwc2_readl(hsotg, DAINTMSK); + dr->diepmsk = dwc2_readl(hsotg, DIEPMSK); + dr->doepmsk = dwc2_readl(hsotg, DOEPMSK); - return 0; -} + for (i = 0; i < hsotg->num_of_eps; i++) { + /* Backup IN EPs */ + dr->diepctl[i] = dwc2_readl(hsotg, DIEPCTL(i)); -/* - * Sets all parameters to the given value. - * - * Assumes that the dwc2_core_params struct contains only integers. - */ -void dwc2_set_all_params(struct dwc2_core_params *params, int value) -{ - int *p = (int *)params; - size_t size = sizeof(*params) / sizeof(*p); - int i; + /* Ensure DATA PID is correctly configured */ + if (dr->diepctl[i] & DXEPCTL_DPID) + dr->diepctl[i] |= DXEPCTL_SETD1PID; + else + dr->diepctl[i] |= DXEPCTL_SETD0PID; - for (i = 0; i < size; i++) - p[i] = value; -} + dr->dieptsiz[i] = dwc2_readl(hsotg, DIEPTSIZ(i)); + dr->diepdma[i] = dwc2_readl(hsotg, DIEPDMA(i)); + /* Backup OUT EPs */ + dr->doepctl[i] = dwc2_readl(hsotg, DOEPCTL(i)); -u16 dwc2_get_otg_version(struct dwc2_hsotg *hsotg) -{ - return hsotg->core_params->otg_ver == 1 ? 0x0200 : 0x0103; -} + /* Ensure DATA PID is correctly configured */ + if (dr->doepctl[i] & DXEPCTL_DPID) + dr->doepctl[i] |= DXEPCTL_SETD1PID; + else + dr->doepctl[i] |= DXEPCTL_SETD0PID; -bool dwc2_is_controller_alive(struct dwc2_hsotg *hsotg) -{ - if (DWC2_READ_4(hsotg, GSNPSID) == 0xffffffff) - return false; - else - return true; + dr->doeptsiz[i] = dwc2_readl(hsotg, DOEPTSIZ(i)); + dr->doepdma[i] = dwc2_readl(hsotg, DOEPDMA(i)); + } + dr->valid = true; + return 0; } /** - * dwc2_enable_global_interrupts() - Enables the controller's Global - * Interrupt in the AHB Config register + * dwc2_restore_device_registers() - Restore controller device registers. + * When resuming usb bus, device registers needs to be restored + * if controller power were disabled. * - * @hsotg: Programming view of DWC_otg controller - */ -void dwc2_enable_global_interrupts(struct dwc2_hsotg *hsotg) -{ - u32 ahbcfg = DWC2_READ_4(hsotg, GAHBCFG); - - ahbcfg |= GAHBCFG_GLBL_INTR_EN; - DWC2_WRITE_4(hsotg, GAHBCFG, ahbcfg); -} - -/** - * dwc2_disable_global_interrupts() - Disables the controller's Global - * Interrupt in the AHB Config register + * @hsotg: Programming view of the DWC_otg controller + * @remote_wakeup: Indicates whether resume is initiated by Device or Host. * - * @hsotg: Programming view of DWC_otg controller + * Return: 0 if successful, negative error code otherwise */ -void dwc2_disable_global_interrupts(struct dwc2_hsotg *hsotg) -{ - u32 ahbcfg = DWC2_READ_4(hsotg, GAHBCFG); - - ahbcfg &= ~GAHBCFG_GLBL_INTR_EN; - DWC2_WRITE_4(hsotg, GAHBCFG, ahbcfg); -} - -/* Returns the controller's GHWCFG2.OTG_MODE. */ -unsigned dwc2_op_mode(struct dwc2_hsotg *hsotg) +STATIC int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg) { - u32 ghwcfg2 = DWC2_READ_4(hsotg, GHWCFG2); - - return (ghwcfg2 & GHWCFG2_OP_MODE_MASK) >> - GHWCFG2_OP_MODE_SHIFT; -} + struct dwc2_dregs_backup *dr; + u32 dctl; + int i; -/* Returns true if the controller is capable of DRD. */ -bool dwc2_hw_is_otg(struct dwc2_hsotg *hsotg) -{ - unsigned op_mode = dwc2_op_mode(hsotg); + dev_dbg(hsotg->dev, "%s\n", __func__); - return (op_mode == GHWCFG2_OP_MODE_HNP_SRP_CAPABLE) || - (op_mode == GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE) || - (op_mode == GHWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE); -} + /* Restore dev regs */ + dr = &hsotg->dr_backup; + if (!dr->valid) { + dev_err(hsotg->dev, "%s: no device registers to restore\n", + __func__); + return -EINVAL; + } + dr->valid = false; -/* Returns true if the controller is host-only. */ -bool dwc2_hw_is_host(struct dwc2_hsotg *hsotg) -{ - unsigned op_mode = dwc2_op_mode(hsotg); + if (!remote_wakeup) + dwc2_writel(hsotg, dr->dctl, DCTL); - return (op_mode == GHWCFG2_OP_MODE_SRP_CAPABLE_HOST) || - (op_mode == GHWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST); -} + dwc2_writel(hsotg, dr->daintmsk, DAINTMSK); + dwc2_writel(hsotg, dr->diepmsk, DIEPMSK); + dwc2_writel(hsotg, dr->doepmsk, DOEPMSK); -/* Returns true if the controller is device-only. */ -bool dwc2_hw_is_device(struct dwc2_hsotg *hsotg) -{ - unsigned op_mode = dwc2_op_mode(hsotg); + for (i = 0; i < hsotg->num_of_eps; i++) { + /* Restore IN EPs */ + dwc2_writel(hsotg, dr->dieptsiz[i], DIEPTSIZ(i)); + dwc2_writel(hsotg, dr->diepdma[i], DIEPDMA(i)); + dwc2_writel(hsotg, dr->doeptsiz[i], DOEPTSIZ(i)); + /** WA for enabled EPx's IN in DDMA mode. On entering to + * hibernation wrong value read and saved from DIEPDMAx, + * as result BNA interrupt asserted on hibernation exit + * by restoring from saved area. + */ + if (hsotg->params.g_dma_desc && + (dr->diepctl[i] & DXEPCTL_EPENA)) + dr->diepdma[i] = hsotg->eps_in[i]->desc_list_dma; + dwc2_writel(hsotg, dr->dtxfsiz[i], DPTXFSIZN(i)); + dwc2_writel(hsotg, dr->diepctl[i], DIEPCTL(i)); + /* Restore OUT EPs */ + dwc2_writel(hsotg, dr->doeptsiz[i], DOEPTSIZ(i)); + /* WA for enabled EPx's OUT in DDMA mode. On entering to + * hibernation wrong value read and saved from DOEPDMAx, + * as result BNA interrupt asserted on hibernation exit + * by restoring from saved area. + */ + if (hsotg->params.g_dma_desc && + (dr->doepctl[i] & DXEPCTL_EPENA)) + dr->doepdma[i] = hsotg->eps_out[i]->desc_list_dma; + dwc2_writel(hsotg, dr->doepdma[i], DOEPDMA(i)); + dwc2_writel(hsotg, dr->doepctl[i], DOEPCTL(i)); + } - return (op_mode == GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE) || - (op_mode == GHWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE); + return 0; } +#endif diff --git a/sys/dev/usb/dwc2/dwc2_core.h b/sys/dev/usb/dwc2/dwc2_core.h index 2a4979d0987..b64491a776e 100644 --- a/sys/dev/usb/dwc2/dwc2_core.h +++ b/sys/dev/usb/dwc2/dwc2_core.h @@ -1,4 +1,4 @@ -/* $OpenBSD: dwc2_core.h,v 1.11 2021/07/27 13:36:59 mglocker Exp $ */ +/* $OpenBSD: dwc2_core.h,v 1.12 2022/09/04 08:42:39 mglocker Exp $ */ /* $NetBSD: dwc2_core.h,v 1.5 2014/04/03 06:34:58 skrll Exp $ */ /* @@ -53,17 +53,47 @@ #include +/* + * Suggested defines for tracers: + * - no_printk: Disable tracing + * - pr_info: Print this info to the console + * - trace_printk: Print this info to trace buffer (good for verbose logging) + */ + +#ifdef DWC2_DEBUG +/* Detailed scheduler tracing, but won't overwhelm console */ +#define dwc2_sch_dbg(hsotg,fmt,...) do { \ + if (dwc2debug >= 1) { \ + printf("%s: " fmt, device_xname(hsotg->dev), \ + ## __VA_ARGS__); \ + } \ +} while (0) + +/* Verbose scheduler tracing */ +#define dwc2_sch_vdbg(hsotg,fmt,...) do { \ + if (dwc2debug >= 2) { \ + printf("%s: " fmt, device_xname(hsotg->dev), \ + ## __VA_ARGS__); \ + } \ +} while (0) +#else +#define dwc2_sch_dbg(...) do { } while (0) +#define dwc2_sch_vdbg(...) do { } while (0) +#endif + /* Maximum number of Endpoints/HostChannels */ #define MAX_EPS_CHANNELS 16 -#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) - +#if 0 /* dwc2-hsotg declarations */ STATIC const char * const dwc2_hsotg_supply_names[] = { "vusb_d", /* digital USB supply, 1.2V */ "vusb_a", /* analog USB supply, 1.1V */ }; +#define DWC2_NUM_SUPPLIES ARRAY_SIZE(dwc2_hsotg_supply_names) +#endif + /* * EP0_MPS_LIMIT * @@ -98,24 +128,33 @@ struct dwc2_hsotg_req; * and has yet to be completed (maybe due to data move, or simply * awaiting an ack from the core all the data has been completed). * @debugfs: File entry for debugfs file for this endpoint. - * @lock: State lock to protect contents of endpoint. * @dir_in: Set to true if this endpoint is of the IN direction, which * means that it is sending data to the Host. + * @map_dir: Set to the value of dir_in when the DMA buffer is mapped. * @index: The index for the endpoint registers. * @mc: Multi Count - number of transactions per microframe - * @interval - Interval for periodic endpoints + * @interval: Interval for periodic endpoints, in frames or microframes. * @name: The name array passed to the USB core. * @halted: Set if the endpoint has been halted. * @periodic: Set if this is a periodic ep, such as Interrupt * @isochronous: Set if this is a isochronous ep * @send_zlp: Set if we need to send a zero-length packet. + * @wedged: Set if ep is wedged. + * @desc_list_dma: The DMA address of descriptor chain currently in use. + * @desc_list: Pointer to descriptor DMA chain head currently in use. + * @desc_count: Count of entries within the DMA descriptor chain of EP. + * @next_desc: index of next free descriptor in the ISOC chain under SW control. + * @compl_desc: index of next descriptor to be completed by xFerComplete * @total_data: The total number of data bytes done. * @fifo_size: The size of the FIFO (for periodic IN endpoints) + * @fifo_index: For Dedicated FIFO operation, only FIFO0 can be used for EP0. * @fifo_load: The amount of data loaded into the FIFO (periodic IN) * @last_load: The offset of data for the last start of request. * @size_loaded: The last loaded size for DxEPTSIZE for periodic IN + * @target_frame: Targeted frame num to setup next ISOC transfer + * @frame_overrun: Indicates SOF number overrun in DSTS * - * This is the driver's state for each registered enpoint, allowing it + * This is the driver's state for each registered endpoint, allowing it * to keep track of transactions that need doing. Each endpoint has a * lock to protect the state, to try and avoid using an overall lock * for the host controller as much as possible. @@ -128,7 +167,7 @@ struct dwc2_hsotg_req; * buffer than a fifo) */ struct dwc2_hsotg_ep { - struct usb_ep ep; +// struct usb_ep ep; struct list_head queue; struct dwc2_hsotg *parent; struct dwc2_hsotg_req *req; @@ -142,15 +181,26 @@ struct dwc2_hsotg_ep { unsigned short fifo_index; unsigned char dir_in; + unsigned char map_dir; unsigned char index; unsigned char mc; - unsigned char interval; + u16 interval; unsigned int halted:1; unsigned int periodic:1; unsigned int isochronous:1; unsigned int send_zlp:1; - unsigned int has_correct_parity:1; + unsigned int wedged:1; + unsigned int target_frame; +#define TARGET_FRAME_INITIAL 0xFFFFFFFF + bool frame_overrun; + +// dma_addr_t desc_list_dma; + struct dwc2_dma_desc *desc_list; + u8 desc_count; + + unsigned int next_desc; + unsigned int compl_desc; char name[10]; }; @@ -162,11 +212,13 @@ struct dwc2_hsotg_ep { * @saved_req_buf: variable to save req.buf when bounce buffers are used. */ struct dwc2_hsotg_req { - struct usb_request req; +// struct usb_request req; struct list_head queue; void *saved_req_buf; }; +#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ + IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) #define call_gadget(_hs, _entry) \ do { \ if ((_hs)->gadget.speed != USB_SPEED_UNKNOWN && \ @@ -191,13 +243,6 @@ enum dwc2_lx_state { DWC2_L3, /* Off state */ }; -/* - * Gadget periodic tx fifo sizes as used by legacy driver - * EP0 is not included - */ -#define DWC2_G_P_LEGACY_TX_FIFO_SIZE {256, 256, 256, 256, 768, 768, 768, \ - 768, 0, 0, 0, 0, 0, 0, 0} - /* Gadget ep0 states */ enum dwc2_ep0_state { DWC2_EP0_SETUP, @@ -207,18 +252,33 @@ enum dwc2_ep0_state { DWC2_EP0_STATUS_OUT, }; +/** XXX: From Linux USB stack. + * struct usb_otg_caps - describes the otg capabilities of the device + * @otg_rev: The OTG revision number the device is compliant with, it's + * in binary-coded decimal (i.e. 2.0 is 0200H). + * @hnp_support: Indicates if the device supports HNP. + * @srp_support: Indicates if the device supports SRP. + * @adp_support: Indicates if the device supports ADP. + */ +struct usb_otg_caps { + u16 otg_rev; + bool hnp_support; + bool srp_support; + bool adp_support; +}; + /** * struct dwc2_core_params - Parameters for configuring the core * - * @otg_cap: Specifies the OTG capabilities. - * 0 - HNP and SRP capable - * 1 - SRP Only capable - * 2 - No HNP/SRP capable (always available) - * Defaults to best available option (0, 1, then 2) - * @otg_ver: OTG version supported - * 0 - 1.3 (default) - * 1 - 2.0 - * @dma_enable: Specifies whether to use slave or DMA mode for accessing + * @otg_caps: Specifies the OTG capabilities. OTG caps from the platform parameters, + * used to setup the: + * - HNP and SRP capable + * - SRP Only capable + * - No HNP/SRP capable (always available) + * Defaults to best available option + * - OTG revision number the device is compliant with, in binary-coded + * decimal (i.e. 2.0 is 0200H). (see struct usb_otg_caps) + * @host_dma: Specifies whether to use slave or DMA mode for accessing * the data FIFOs. The driver will automatically detect the * value for this parameter if none is specified. * 0 - Slave (always available) @@ -246,7 +306,8 @@ enum dwc2_ep0_state { * @enable_dynamic_fifo: 0 - Use coreConsultant-specified FIFO size parameters * 1 - Allow dynamic FIFO sizing (default, if available) * @en_multiple_tx_fifo: Specifies whether dedicated per-endpoint transmit FIFOs - * are enabled + * are enabled for non-periodic IN endpoints in device + * mode. * @host_rx_fifo_size: Number of 4-byte words in the Rx FIFO in host mode when * dynamic FIFO sizing is enabled * 16 to 32768 @@ -305,6 +366,12 @@ enum dwc2_ep0_state { * is FS. * 0 - No (default) * 1 - Yes + * @ipg_isoc_en: Indicates the IPG supports is enabled or disabled. + * 0 - Disable (default) + * 1 - Enable + * @acg_enable: For enabling Active Clock Gating in the controller + * 0 - No + * 1 - Yes * @ulpi_fs_ls: Make ULPI phy operate in FS/LS mode only * 0 - No (default) * 1 - Yes @@ -321,6 +388,9 @@ enum dwc2_ep0_state { * (default when phy_type is UTMI+ or ULPI) * 1 - 6 MHz * (default when phy_type is Full Speed) + * @oc_disable: Flag to disable overcurrent condition. + * 0 - Allow overcurrent condition to get detected + * 1 - Disable overcurrent condtion to get detected * @ts_dline: Enable Term Select Dline pulsing * 0 - No (default) * 1 - Yes @@ -330,7 +400,7 @@ enum dwc2_ep0_state { * @ahbcfg: This field allows the default value of the GAHBCFG * register to be overridden * -1 - GAHBCFG value will be set to 0x06 - * (INCR4, default) + * (INCR, default) * all others - GAHBCFG value will be overridden with * this value * Not all bits can be controlled like this, the @@ -343,12 +413,77 @@ enum dwc2_ep0_state { * case. * 0 - No (default) * 1 - Yes - * @hibernation: Specifies whether the controller support hibernation. - * If hibernation is enabled, the controller will enter - * hibernation in both peripheral and host mode when + * @power_down: Specifies whether the controller support power_down. + * If power_down is enabled, the controller will enter + * power_down in both peripheral and host mode when * needed. * 0 - No (default) + * 1 - Partial power down + * 2 - Hibernation + * @no_clock_gating: Specifies whether to avoid clock gating feature. + * 0 - No (use clock gating) + * 1 - Yes (avoid it) + * @lpm: Enable LPM support. + * 0 - No + * 1 - Yes + * @lpm_clock_gating: Enable core PHY clock gating. + * 0 - No + * 1 - Yes + * @besl: Enable LPM Errata support. + * 0 - No + * 1 - Yes + * @hird_threshold_en: HIRD or HIRD Threshold enable. + * 0 - No * 1 - Yes + * @hird_threshold: Value of BESL or HIRD Threshold. + * @ref_clk_per: Indicates in terms of pico seconds the period + * of ref_clk. + * 62500 - 16MHz + * 58823 - 17MHz + * 52083 - 19.2MHz + * 50000 - 20MHz + * 41666 - 24MHz + * 33333 - 30MHz (default) + * 25000 - 40MHz + * @sof_cnt_wkup_alert: Indicates in term of number of SOF's after which + * the controller should generate an interrupt if the + * device had been in L1 state until that period. + * This is used by SW to initiate Remote WakeUp in the + * controller so as to sync to the uF number from the host. + * @activate_stm_fs_transceiver: Activate internal transceiver using GGPIO + * register. + * 0 - Deactivate the transceiver (default) + * 1 - Activate the transceiver + * @activate_stm_id_vb_detection: Activate external ID pin and Vbus level + * detection using GGPIO register. + * 0 - Deactivate the external level detection (default) + * 1 - Activate the external level detection + * @activate_ingenic_overcurrent_detection: Activate Ingenic overcurrent + * detection. + * 0 - Deactivate the overcurrent detection + * 1 - Activate the overcurrent detection (default) + * @g_dma: Enables gadget dma usage (default: autodetect). + * @g_dma_desc: Enables gadget descriptor DMA (default: autodetect). + * @g_rx_fifo_size: The periodic rx fifo size for the device, in + * DWORDS from 16-32768 (default: 2048 if + * possible, otherwise autodetect). + * @g_np_tx_fifo_size: The non-periodic tx fifo size for the device in + * DWORDS from 16-32768 (default: 1024 if + * possible, otherwise autodetect). + * @g_tx_fifo_size: An array of TX fifo sizes in dedicated fifo + * mode. Each value corresponds to one EP + * starting from EP1 (max 15 values). Sizes are + * in DWORDS with possible values from + * 16-32768 (default: 256, 256, 256, 256, 768, + * 768, 768, 768, 0, 0, 0, 0, 0, 0, 0). + * @change_speed_quirk: Change speed configuration to DWC2_SPEED_PARAM_FULL + * while full&low speed device connect. And change speed + * back to DWC2_SPEED_PARAM_HIGH while device is gone. + * 0 - No (default) + * 1 - Yes + * @service_interval: Enable service interval based scheduling. + * 0 - No + * 1 - Yes * * The following parameters may be specified when starting the module. These * parameters define how the DWC_otg controller should be configured. A @@ -357,38 +492,75 @@ enum dwc2_ep0_state { * default described above. */ struct dwc2_core_params { - /* - * Don't add any non-int members here, this will break - * dwc2_set_all_params! - */ - int otg_cap; - int otg_ver; - int dma_enable; - int dma_desc_enable; - int dma_desc_fs_enable; - int speed; - int enable_dynamic_fifo; - int en_multiple_tx_fifo; - int host_rx_fifo_size; - int host_nperio_tx_fifo_size; - int host_perio_tx_fifo_size; - int max_transfer_size; - int max_packet_count; - int host_channels; - int phy_type; - int phy_utmi_width; - int phy_ulpi_ddr; - int phy_ulpi_ext_vbus; - int i2c_enable; - int ulpi_fs_ls; - int host_support_fs_ls_low_power; - int host_ls_low_power_phy_clk; - int ts_dline; - int reload_ctl; - int ahbcfg; - int uframe_sched; - int external_id_pin_ctl; - int hibernation; + struct usb_otg_caps otg_caps; + u8 phy_type; +#define DWC2_PHY_TYPE_PARAM_FS 0 +#define DWC2_PHY_TYPE_PARAM_UTMI 1 +#define DWC2_PHY_TYPE_PARAM_ULPI 2 + + u8 speed; +#define DWC2_SPEED_PARAM_HIGH 0 +#define DWC2_SPEED_PARAM_FULL 1 +#define DWC2_SPEED_PARAM_LOW 2 + + u8 phy_utmi_width; + bool phy_ulpi_ddr; + bool phy_ulpi_ext_vbus; + bool enable_dynamic_fifo; + bool en_multiple_tx_fifo; + bool i2c_enable; + bool acg_enable; + bool ulpi_fs_ls; + bool ts_dline; + bool reload_ctl; + bool uframe_sched; + bool external_id_pin_ctl; + + int power_down; +#define DWC2_POWER_DOWN_PARAM_NONE 0 +#define DWC2_POWER_DOWN_PARAM_PARTIAL 1 +#define DWC2_POWER_DOWN_PARAM_HIBERNATION 2 + bool no_clock_gating; + + bool lpm; + bool lpm_clock_gating; + bool besl; + bool hird_threshold_en; + bool service_interval; + u8 hird_threshold; + bool activate_stm_fs_transceiver; + bool activate_stm_id_vb_detection; + bool activate_ingenic_overcurrent_detection; + bool ipg_isoc_en; + u16 max_packet_count; + u32 max_transfer_size; + u32 ahbcfg; + + /* GREFCLK parameters */ + u32 ref_clk_per; + u16 sof_cnt_wkup_alert; + + /* Host parameters */ + bool host_dma; + bool dma_desc_enable; + bool dma_desc_fs_enable; + bool host_support_fs_ls_low_power; + bool host_ls_low_power_phy_clk; + bool oc_disable; + + u8 host_channels; + u16 host_rx_fifo_size; + u16 host_nperio_tx_fifo_size; + u16 host_perio_tx_fifo_size; + + /* Gadget parameters */ + bool g_dma; + bool g_dma_desc; + u32 g_rx_fifo_size; + u32 g_np_tx_fifo_size; + u32 g_tx_fifo_size[MAX_EPS_CHANNELS]; + + bool change_speed_quirk; }; /** @@ -401,7 +573,7 @@ struct dwc2_core_params { * * The values that are not in dwc2_core_params are documented below. * - * @op_mode Mode of Operation + * @op_mode: Mode of Operation * 0 - HNP- and SRP-Capable OTG (Host & Device) * 1 - SRP-Capable OTG (Host & Device) * 2 - Non-HNP and Non-SRP Capable OTG (Host & Device) @@ -409,49 +581,114 @@ struct dwc2_core_params { * 4 - Non-OTG Device * 5 - SRP-Capable Host * 6 - Non-OTG Host - * @arch Architecture + * @arch: Architecture * 0 - Slave only * 1 - External DMA * 2 - Internal DMA - * @power_optimized Are power optimizations enabled? - * @num_dev_ep Number of device endpoints available - * @num_dev_perio_in_ep Number of device periodic IN endpoints - * available - * @dev_token_q_depth Device Mode IN Token Sequence Learning Queue + * @ipg_isoc_en: This feature indicates that the controller supports + * the worst-case scenario of Rx followed by Rx + * Interpacket Gap (IPG) (32 bitTimes) as per the utmi + * specification for any token following ISOC OUT token. + * 0 - Don't support + * 1 - Support + * @power_optimized: Are power optimizations enabled? + * @num_dev_ep: Number of device endpoints available + * @num_dev_in_eps: Number of device IN endpoints available + * @num_dev_perio_in_ep: Number of device periodic IN endpoints + * available + * @dev_token_q_depth: Device Mode IN Token Sequence Learning Queue * Depth * 0 to 30 - * @host_perio_tx_q_depth + * @host_perio_tx_q_depth: * Host Mode Periodic Request Queue Depth * 2, 4 or 8 - * @nperio_tx_q_depth + * @nperio_tx_q_depth: * Non-Periodic Request Queue Depth * 2, 4 or 8 - * @hs_phy_type High-speed PHY interface type + * @hs_phy_type: High-speed PHY interface type * 0 - High-speed interface not supported * 1 - UTMI+ * 2 - ULPI * 3 - UTMI+ and ULPI - * @fs_phy_type Full-speed PHY interface type + * @fs_phy_type: Full-speed PHY interface type * 0 - Full speed interface not supported * 1 - Dedicated full speed interface * 2 - FS pins shared with UTMI+ pins * 3 - FS pins shared with ULPI pins * @total_fifo_size: Total internal RAM for FIFOs (bytes) - * @utmi_phy_data_width UTMI+ PHY data width + * @hibernation: Is hibernation enabled? + * @utmi_phy_data_width: UTMI+ PHY data width * 0 - 8 bits * 1 - 16 bits * 2 - 8 or 16 bits * @snpsid: Value from SNPSID register * @dev_ep_dirs: Direction of device endpoints (GHWCFG1) + * @g_tx_fifo_size: Power-on values of TxFIFO sizes + * @dma_desc_enable: When DMA mode is enabled, specifies whether to use + * address DMA mode or descriptor DMA mode for accessing + * the data FIFOs. The driver will automatically detect the + * value for this if none is specified. + * 0 - Address DMA + * 1 - Descriptor DMA (default, if available) + * @enable_dynamic_fifo: 0 - Use coreConsultant-specified FIFO size parameters + * 1 - Allow dynamic FIFO sizing (default, if available) + * @en_multiple_tx_fifo: Specifies whether dedicated per-endpoint transmit FIFOs + * are enabled for non-periodic IN endpoints in device + * mode. + * @host_nperio_tx_fifo_size: Number of 4-byte words in the non-periodic Tx FIFO + * in host mode when dynamic FIFO sizing is enabled + * 16 to 32768 + * Actual maximum value is autodetected and also + * the default. + * @host_perio_tx_fifo_size: Number of 4-byte words in the periodic Tx FIFO in + * host mode when dynamic FIFO sizing is enabled + * 16 to 32768 + * Actual maximum value is autodetected and also + * the default. + * @max_transfer_size: The maximum transfer size supported, in bytes + * 2047 to 65,535 + * Actual maximum value is autodetected and also + * the default. + * @max_packet_count: The maximum number of packets in a transfer + * 15 to 511 + * Actual maximum value is autodetected and also + * the default. + * @host_channels: The number of host channel registers to use + * 1 to 16 + * Actual maximum value is autodetected and also + * the default. + * @dev_nperio_tx_fifo_size: Number of 4-byte words in the non-periodic Tx FIFO + * in device mode when dynamic FIFO sizing is enabled + * 16 to 32768 + * Actual maximum value is autodetected and also + * the default. + * @i2c_enable: Specifies whether to use the I2Cinterface for a full + * speed PHY. This parameter is only applicable if phy_type + * is FS. + * 0 - No (default) + * 1 - Yes + * @acg_enable: For enabling Active Clock Gating in the controller + * 0 - Disable + * 1 - Enable + * @lpm_mode: For enabling Link Power Management in the controller + * 0 - Disable + * 1 - Enable + * @rx_fifo_size: Number of 4-byte words in the Rx FIFO when dynamic + * FIFO sizing is enabled 16 to 32768 + * Actual maximum value is autodetected and also + * the default. + * @service_interval_mode: For enabling service interval based scheduling in the + * controller. + * 0 - Disable + * 1 - Enable */ struct dwc2_hw_params { unsigned op_mode:3; unsigned arch:2; unsigned dma_desc_enable:1; - unsigned dma_desc_fs_enable:1; unsigned enable_dynamic_fifo:1; unsigned en_multiple_tx_fifo:1; - unsigned host_rx_fifo_size:16; + unsigned rx_fifo_size:16; unsigned host_nperio_tx_fifo_size:16; unsigned dev_nperio_tx_fifo_size:16; unsigned host_perio_tx_fifo_size:16; @@ -464,21 +701,28 @@ struct dwc2_hw_params { unsigned hs_phy_type:2; unsigned fs_phy_type:2; unsigned i2c_enable:1; + unsigned acg_enable:1; unsigned num_dev_ep:4; + unsigned num_dev_in_eps : 4; unsigned num_dev_perio_in_ep:4; unsigned total_fifo_size:16; unsigned power_optimized:1; + unsigned hibernation:1; unsigned utmi_phy_data_width:2; + unsigned lpm_mode:1; + unsigned ipg_isoc_en:1; + unsigned service_interval_mode:1; u32 snpsid; u32 dev_ep_dirs; + u32 g_tx_fifo_size[MAX_EPS_CHANNELS]; }; /* Size of control and EP0 buffers */ #define DWC2_CTRL_BUFF_SIZE 8 /** - * struct dwc2_gregs_backup - Holds global registers state before entering partial - * power down + * struct dwc2_gregs_backup - Holds global registers state before + * entering partial power down * @gotgctl: Backup of GOTGCTL register * @gintmsk: Backup of GINTMSK register * @gahbcfg: Backup of GAHBCFG register @@ -486,10 +730,13 @@ struct dwc2_hw_params { * @grxfsiz: Backup of GRXFSIZ register * @gnptxfsiz: Backup of GNPTXFSIZ register * @gi2cctl: Backup of GI2CCTL register - * @hptxfsiz: Backup of HPTXFSIZ register + * @glpmcfg: Backup of GLPMCFG register * @gdfifocfg: Backup of GDFIFOCFG register + * @pcgcctl: Backup of PCGCCTL register + * @pcgcctl1: Backup of PCGCCTL1 register * @dtxfsiz: Backup of DTXFSIZ registers for each endpoint * @gpwrdn: Backup of GPWRDN register + * @valid: True if registers values backuped. */ struct dwc2_gregs_backup { u32 gotgctl; @@ -499,17 +746,17 @@ struct dwc2_gregs_backup { u32 grxfsiz; u32 gnptxfsiz; u32 gi2cctl; - u32 hptxfsiz; + u32 glpmcfg; u32 pcgcctl; + u32 pcgcctl1; u32 gdfifocfg; - u32 dtxfsiz[MAX_EPS_CHANNELS]; u32 gpwrdn; bool valid; }; /** - * struct dwc2_dregs_backup - Holds device registers state before entering partial - * power down + * struct dwc2_dregs_backup - Holds device registers state before + * entering partial power down * @dcfg: Backup of DCFG register * @dctl: Backup of DCTL register * @daintmsk: Backup of DAINTMSK register @@ -521,6 +768,8 @@ struct dwc2_gregs_backup { * @doepctl: Backup of DOEPCTL register * @doeptsiz: Backup of DOEPTSIZ register * @doepdma: Backup of DOEPDMA register + * @dtxfsiz: Backup of DTXFSIZ registers for each endpoint + * @valid: True if registers values backuped. */ struct dwc2_dregs_backup { u32 dcfg; @@ -534,17 +783,20 @@ struct dwc2_dregs_backup { u32 doepctl[MAX_EPS_CHANNELS]; u32 doeptsiz[MAX_EPS_CHANNELS]; u32 doepdma[MAX_EPS_CHANNELS]; + u32 dtxfsiz[MAX_EPS_CHANNELS]; bool valid; }; /** - * struct dwc2_hregs_backup - Holds host registers state before entering partial - * power down + * struct dwc2_hregs_backup - Holds host registers state before + * entering partial power down * @hcfg: Backup of HCFG register * @haintmsk: Backup of HAINTMSK register * @hcintmsk: Backup of HCINTMSK register - * @hptr0: Backup of HPTR0 register + * @hprt0: Backup of HPTR0 register * @hfir: Backup of HFIR register + * @hptxfsiz: Backup of HPTXFSIZ register + * @valid: True if registers values backuped. */ struct dwc2_hregs_backup { u32 hcfg; @@ -552,9 +804,88 @@ struct dwc2_hregs_backup { u32 hcintmsk[MAX_EPS_CHANNELS]; u32 hprt0; u32 hfir; + u32 hptxfsiz; bool valid; }; +/* + * Constants related to high speed periodic scheduling + * + * We have a periodic schedule that is DWC2_HS_SCHEDULE_UFRAMES long. From a + * reservation point of view it's assumed that the schedule goes right back to + * the beginning after the end of the schedule. + * + * What does that mean for scheduling things with a long interval? It means + * we'll reserve time for them in every possible microframe that they could + * ever be scheduled in. ...but we'll still only actually schedule them as + * often as they were requested. + * + * We keep our schedule in a "bitmap" structure. This simplifies having + * to keep track of and merge intervals: we just let the bitmap code do most + * of the heavy lifting. In a way scheduling is much like memory allocation. + * + * We schedule 100us per uframe or 80% of 125us (the maximum amount you're + * supposed to schedule for periodic transfers). That's according to spec. + * + * Note that though we only schedule 80% of each microframe, the bitmap that we + * keep the schedule in is tightly packed (AKA it doesn't have 100us worth of + * space for each uFrame). + * + * Requirements: + * - DWC2_HS_SCHEDULE_UFRAMES must even divide 0x4000 (HFNUM_MAX_FRNUM + 1) + * - DWC2_HS_SCHEDULE_UFRAMES must be 8 times DWC2_LS_SCHEDULE_FRAMES (probably + * could be any multiple of 8 times DWC2_LS_SCHEDULE_FRAMES, but there might + * be bugs). The 8 comes from the USB spec: number of microframes per frame. + */ +#define DWC2_US_PER_UFRAME 125 +#define DWC2_HS_PERIODIC_US_PER_UFRAME 100 + +#define DWC2_HS_SCHEDULE_UFRAMES 8 +#define DWC2_HS_SCHEDULE_US (DWC2_HS_SCHEDULE_UFRAMES * \ + DWC2_HS_PERIODIC_US_PER_UFRAME) + +/* + * Constants related to low speed scheduling + * + * For high speed we schedule every 1us. For low speed that's a bit overkill, + * so we make up a unit called a "slice" that's worth 25us. There are 40 + * slices in a full frame and we can schedule 36 of those (90%) for periodic + * transfers. + * + * Our low speed schedule can be as short as 1 frame or could be longer. When + * we only schedule 1 frame it means that we'll need to reserve a time every + * frame even for things that only transfer very rarely, so something that runs + * every 2048 frames will get time reserved in every frame. Our low speed + * schedule can be longer and we'll be able to handle more overlap, but that + * will come at increased memory cost and increased time to schedule. + * + * Note: one other advantage of a short low speed schedule is that if we mess + * up and miss scheduling we can jump in and use any of the slots that we + * happened to reserve. + * + * With 25 us per slice and 1 frame in the schedule, we only need 4 bytes for + * the schedule. There will be one schedule per TT. + * + * Requirements: + * - DWC2_US_PER_SLICE must evenly divide DWC2_LS_PERIODIC_US_PER_FRAME. + */ +#define DWC2_US_PER_SLICE 25 +#define DWC2_SLICES_PER_UFRAME (DWC2_US_PER_UFRAME / DWC2_US_PER_SLICE) + +#define DWC2_ROUND_US_TO_SLICE(us) \ + (DIV_ROUND_UP((us), DWC2_US_PER_SLICE) * \ + DWC2_US_PER_SLICE) + +#define DWC2_LS_PERIODIC_US_PER_FRAME \ + 900 +#define DWC2_LS_PERIODIC_SLICES_PER_FRAME \ + (DWC2_LS_PERIODIC_US_PER_FRAME / \ + DWC2_US_PER_SLICE) + +#define DWC2_LS_SCHEDULE_FRAMES 1 +#define DWC2_LS_SCHEDULE_SLICES (DWC2_LS_SCHEDULE_FRAMES * \ + DWC2_LS_PERIODIC_SLICES_PER_FRAME) + /** * struct dwc2_hsotg - Holds the state of the driver, including the non-periodic * and periodic schedules @@ -565,7 +896,7 @@ struct dwc2_hregs_backup { * @regs: Pointer to controller regs * @hw_params: Parameters that were autodetected from the * hardware registers - * @core_params: Parameters that define how the core should be configured + * @params: Parameters that define how the core should be configured * @op_state: The operational State, during transitions (a_host=> * a_peripheral and b_device=>b_host) this may not match * the core, but allows the software to determine @@ -574,15 +905,32 @@ struct dwc2_hregs_backup { * - USB_DR_MODE_PERIPHERAL * - USB_DR_MODE_HOST * - USB_DR_MODE_OTG - * @hcd_enabled Host mode sub-driver initialization indicator. - * @gadget_enabled Peripheral mode sub-driver initialization indicator. - * @ll_hw_enabled Status of low-level hardware resources. + * @role_sw: usb_role_switch handle + * @role_sw_default_mode: default operation mode of controller while usb role + * is USB_ROLE_NONE + * @hcd_enabled: Host mode sub-driver initialization indicator. + * @gadget_enabled: Peripheral mode sub-driver initialization indicator. + * @ll_hw_enabled: Status of low-level hardware resources. + * @hibernated: True if core is hibernated + * @in_ppd: True if core is partial power down mode. + * @bus_suspended: True if bus is suspended + * @reset_phy_on_wake: Quirk saying that we should assert PHY reset on a + * remote wakeup. + * @phy_off_for_suspend: Status of whether we turned the PHY off at suspend. + * @need_phy_for_wake: Quirk saying that we should keep the PHY on at + * suspend if we need USB to wake us up. + * @frame_number: Frame number read from the core. For both device + * and host modes. The value ranges are from 0 + * to HFNUM_MAX_FRNUM. * @phy: The otg phy transceiver structure for phy control. - * @uphy: The otg phy transceiver structure for old USB phy control. - * @plat: The platform specific configuration data. This can be removed once - * all SoCs support usb transceiver. + * @uphy: The otg phy transceiver structure for old USB phy + * control. + * @plat: The platform specific configuration data. This can be + * removed once all SoCs support usb transceiver. * @supplies: Definition of USB power supplies - * @phyif: PHY interface width + * @vbus_supply: Regulator supplying vbus. + * @usb33d: Optional 3.3v regulator used on some stm32 devices to + * supply ID and VBUS detection hardware. * @lock: Spinlock that protects all the driver data structures * @priv: Stores a pointer to the struct usb_hcd * @queuing_high_bandwidth: True if multiple packets of a high-bandwidth @@ -594,13 +942,26 @@ struct dwc2_hregs_backup { * interrupt * @wkp_timer: Timer object for handling Wakeup Detected interrupt * @lx_state: Lx state of connected device - * @gregs_backup: Backup of global registers during suspend - * @dregs_backup: Backup of device registers during suspend - * @hregs_backup: Backup of host registers during suspend + * @gr_backup: Backup of global registers during suspend + * @dr_backup: Backup of device registers during suspend + * @hr_backup: Backup of host registers during suspend + * @needs_byte_swap: Specifies whether the opposite endianness. * * These are for host mode: * * @flags: Flags for handling root port state changes + * @flags.d32: Contain all root port flags + * @flags.b: Separate root port flags from each other + * @flags.b.port_connect_status_change: True if root port connect status + * changed + * @flags.b.port_connect_status: True if device connected to root port + * @flags.b.port_reset_change: True if root port reset status changed + * @flags.b.port_enable_change: True if root port enable status changed + * @flags.b.port_suspend_change: True if root port suspend status changed + * @flags.b.port_over_current_change: True if root port over current state + * changed. + * @flags.b.port_l1_change: True if root port l1 status changed + * @flags.b.reserved: Reserved bits of root port register * @non_periodic_sched_inactive: Inactive QHs in the non-periodic schedule. * Transfers associated with these QHs are not currently * assigned to a host channel. @@ -609,6 +970,9 @@ struct dwc2_hregs_backup { * assigned to a host channel. * @non_periodic_qh_ptr: Pointer to next QH to process in the active * non-periodic schedule + * @non_periodic_sched_waiting: Waiting QHs in the non-periodic schedule. + * Transfers associated with these QHs are not currently + * assigned to a host channel. * @periodic_sched_inactive: Inactive QHs in the periodic schedule. This is a * list of QHs for periodic transfers that are _not_ * scheduled for the next frame. Each QH in the list has an @@ -645,9 +1009,9 @@ struct dwc2_hregs_backup { * This value is in microseconds per (micro)frame. The * assumption is that all periodic transfers may occur in * the same (micro)frame. - * @frame_usecs: Internal variable used by the microframe scheduler - * @frame_number: Frame number read from the core at SOF. The value ranges - * from 0 to HFNUM_MAX_FRNUM. + * @hs_periodic_bitmap: Bitmap used by the microframe scheduler any time the + * host is in high speed mode; low speed schedules are + * stored elsewhere since we need one per TT. * @periodic_qh_count: Count of periodic QHs, if using several eps. Used for * SOF enable/disable. * @free_hc_list: Free host channels in the controller. This is a list of @@ -658,8 +1022,8 @@ struct dwc2_hregs_backup { * host channel is available for non-periodic transactions. * @non_periodic_channels: Number of host channels assigned to non-periodic * transfers - * @available_host_channels Number of host channels available for the microframe - * scheduler to use + * @available_host_channels: Number of host channels available for the + * microframe scheduler to use * @hc_ptr_array: Array of pointers to the host channel descriptors. * Allows accessing a host channel descriptor given the * host channel number. This is useful in interrupt @@ -669,12 +1033,14 @@ struct dwc2_hregs_backup { * @status_buf_dma: DMA address for status_buf * @start_work: Delayed work for handling host A-cable connection * @reset_work: Delayed work for handling a port reset + * @phy_reset_work: Work structure for doing a PHY reset * @otg_port: OTG port number * @frame_list: Frame list * @frame_list_dma: Frame list DMA address * @frame_list_sz: Frame list size * @desc_gen_cache: Kmem cache for generic descriptors * @desc_hsisoc_cache: Kmem cache for hs isochronous descriptors + * @unaligned_cache: Kmem cache for DMA mode to handle non-aligned buf * * These are for peripheral mode: * @@ -682,47 +1048,88 @@ struct dwc2_hregs_backup { * @dedicated_fifos: Set if the hardware has dedicated IN-EP fifos. * @num_of_eps: Number of available EPs (excluding EP0) * @debug_root: Root directrory for debugfs. - * @debug_file: Main status file for debugfs. - * @debug_testmode: Testmode status file for debugfs. - * @debug_fifo: FIFO status file for debugfs. * @ep0_reply: Request used for ep0 reply. * @ep0_buff: Buffer for EP0 reply data, if needed. * @ctrl_buff: Buffer for EP0 control requests. * @ctrl_req: Request for EP0 control packets. * @ep0_state: EP0 control transfers state + * @delayed_status: true when gadget driver asks for delayed status * @test_mode: USB test mode requested by the host - * @eps: The endpoints being supplied to the gadget framework - * @g_using_dma: Indicate if dma usage is enabled - * @g_rx_fifo_sz: Contains rx fifo size value - * @g_np_g_tx_fifo_sz: Contains Non-Periodic tx fifo size value - * @g_tx_fifo_sz: Contains tx fifo size value per endpoints + * @remote_wakeup_allowed: True if device is allowed to wake-up host by + * remote-wakeup signalling + * @setup_desc_dma: EP0 setup stage desc chain DMA address + * @setup_desc: EP0 setup stage desc chain pointer + * @ctrl_in_desc_dma: EP0 IN data phase desc chain DMA address + * @ctrl_in_desc: EP0 IN data phase desc chain pointer + * @ctrl_out_desc_dma: EP0 OUT data phase desc chain DMA address + * @ctrl_out_desc: EP0 OUT data phase desc chain pointer + * @irq: Interrupt request line number + * @clk: Pointer to otg clock + * @reset: Pointer to dwc2 reset controller + * @reset_ecc: Pointer to dwc2 optional reset controller in Stratix10. + * @regset: A pointer to a struct debugfs_regset32, which contains + * a pointer to an array of register definitions, the + * array size and the base address where the register bank + * is to be found. + * @last_frame_num: Number of last frame. Range from 0 to 32768 + * @frame_num_array: Used only if CONFIG_USB_DWC2_TRACK_MISSED_SOFS is + * defined, for missed SOFs tracking. Array holds that + * frame numbers, which not equal to last_frame_num +1 + * @last_frame_num_array: Used only if CONFIG_USB_DWC2_TRACK_MISSED_SOFS is + * defined, for missed SOFs tracking. + * If current_frame_number != last_frame_num+1 + * then last_frame_num added to this array + * @frame_num_idx: Actual size of frame_num_array and last_frame_num_array + * @dumped_frame_num_array: 1 - if missed SOFs frame numbers dumbed + * 0 - if missed SOFs frame numbers not dumbed + * @fifo_mem: Total internal RAM for FIFOs (bytes) + * @fifo_map: Each bit intend for concrete fifo. If that bit is set, + * then that fifo is used + * @gadget: Represents a usb gadget device + * @connected: Used in slave mode. True if device connected with host + * @eps_in: The IN endpoints being supplied to the gadget framework + * @eps_out: The OUT endpoints being supplied to the gadget framework + * @new_connection: Used in host mode. True if there are new connected + * device + * @enabled: Indicates the enabling state of controller + * */ struct dwc2_hsotg { struct device *dev; +// void __iomem *regs; struct dwc2_softc *hsotg_sc; /** Params detected from hardware */ struct dwc2_hw_params hw_params; /** Params to actually use */ - struct dwc2_core_params *core_params; + struct dwc2_core_params params; enum usb_otg_state op_state; enum usb_dr_mode dr_mode; +// struct usb_role_switch *role_sw; +// enum usb_dr_mode role_sw_default_mode; unsigned int hcd_enabled:1; unsigned int gadget_enabled:1; unsigned int ll_hw_enabled:1; + unsigned int hibernated:1; + unsigned int in_ppd:1; + bool bus_suspended; + unsigned int reset_phy_on_wake:1; + unsigned int need_phy_for_wake:1; + unsigned int phy_off_for_suspend:1; + u16 frame_number; - spinlock_t lock; - void *priv; - struct usb_phy *uphy; -#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) struct phy *phy; struct usb_phy *uphy; struct dwc2_hsotg_plat *plat; - struct regulator_bulk_data supplies[ARRAY_SIZE(dwc2_hsotg_supply_names)]; - u32 phyif; +// struct regulator_bulk_data supplies[DWC2_NUM_SUPPLIES]; + struct regulator *vbus_supply; + struct regulator *usb33d; + spinlock_t lock; + void *priv; int irq; - struct clk *clk; -#endif /* CONFIG_USB_DWC2_PERIPHERAL || CONFIG_USB_DWC2_DUAL_ROLE */ +// struct clk clk; +// struct reset_control *reset; +// struct reset_control *reset_ecc; unsigned int queuing_high_bandwidth:1; unsigned int srp_success:1; @@ -737,13 +1144,28 @@ struct dwc2_hsotg { struct dentry *debug_root; struct debugfs_regset32 *regset; + bool needs_byte_swap; /* DWC OTG HW Release versions */ #define DWC2_CORE_REV_2_71a 0x4f54271a +#define DWC2_CORE_REV_2_72a 0x4f54272a +#define DWC2_CORE_REV_2_80a 0x4f54280a #define DWC2_CORE_REV_2_90a 0x4f54290a +#define DWC2_CORE_REV_2_91a 0x4f54291a #define DWC2_CORE_REV_2_92a 0x4f54292a #define DWC2_CORE_REV_2_94a 0x4f54294a #define DWC2_CORE_REV_3_00a 0x4f54300a +#define DWC2_CORE_REV_3_10a 0x4f54310a +#define DWC2_CORE_REV_4_00a 0x4f54400a +#define DWC2_CORE_REV_4_20a 0x4f54420a +#define DWC2_FS_IOT_REV_1_00a 0x5531100a +#define DWC2_HS_IOT_REV_1_00a 0x5532100a +#define DWC2_CORE_REV_MASK 0x0000ffff + + /* DWC OTG HW Core ID */ +#define DWC2_OTG_ID 0x4f540000 +#define DWC2_FS_IOT_ID 0x55310000 +#define DWC2_HS_IOT_ID 0x55320000 #if IS_ENABLED(CONFIG_USB_DWC2_HOST) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) union dwc2_hcd_internal_flags { @@ -770,15 +1192,15 @@ struct dwc2_hsotg { struct list_head periodic_sched_queued; struct list_head split_order; u16 periodic_usecs; - u16 frame_usecs[8]; - u16 frame_number; + unsigned long hs_periodic_bitmap[ + DIV_ROUND_UP(DWC2_HS_SCHEDULE_US, BITS_PER_LONG)]; u16 periodic_qh_count; - bool bus_suspended; bool new_connection; + u16 last_frame_num; + #ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS #define FRAME_NUM_ARRAY_SIZE 1000 - u16 last_frame_num; u16 *frame_num_array; u16 *last_frame_num_array; int frame_num_idx; @@ -790,13 +1212,14 @@ struct dwc2_hsotg { int non_periodic_channels; int available_host_channels; struct dwc2_host_chan *hc_ptr_array[MAX_EPS_CHANNELS]; - struct usb_dma status_buf_usbdma; u8 *status_buf; + struct usb_dma status_buf_dma_usb; dma_addr_t status_buf_dma; #define DWC2_HCD_STATUS_BUF_SIZE 64 struct delayed_work start_work; struct delayed_work reset_work; +// struct work_struct phy_reset_work; u8 otg_port; struct usb_dma frame_list_usbdma; u32 *frame_list; @@ -804,28 +1227,13 @@ struct dwc2_hsotg { u32 frame_list_sz; struct kmem_cache *desc_gen_cache; struct kmem_cache *desc_hsisoc_cache; + struct kmem_cache *unaligned_cache; +#define DWC2_KMEM_UNALIGNED_BUF_SIZE 1024 -#ifdef DEBUG - u32 frrem_samples; - u64 frrem_accum; - - u32 hfnum_7_samples_a; - u64 hfnum_7_frrem_accum_a; - u32 hfnum_0_samples_a; - u64 hfnum_0_frrem_accum_a; - u32 hfnum_other_samples_a; - u64 hfnum_other_frrem_accum_a; - - u32 hfnum_7_samples_b; - u64 hfnum_7_frrem_accum_b; - u32 hfnum_0_samples_b; - u64 hfnum_0_frrem_accum_b; - u32 hfnum_other_samples_b; - u64 hfnum_other_frrem_accum_b; -#endif #endif /* CONFIG_USB_DWC2_HOST || CONFIG_USB_DWC2_DUAL_ROLE */ -#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) +#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ + IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) /* Gadget structures */ struct usb_gadget_driver *driver; int fifo_mem; @@ -838,20 +1246,63 @@ struct dwc2_hsotg { void *ep0_buff; void *ctrl_buff; enum dwc2_ep0_state ep0_state; + unsigned delayed_status : 1; u8 test_mode; +// dma_addr_t setup_desc_dma[2]; +// struct dwc2_dma_desc *setup_desc[2]; +// dma_addr_t ctrl_in_desc_dma; +// struct dwc2_dma_desc *ctrl_in_desc; +// dma_addr_t ctrl_out_desc_dma; +// struct dwc2_dma_desc *ctrl_out_desc; + struct usb_gadget gadget; unsigned int enabled:1; unsigned int connected:1; + unsigned int remote_wakeup_allowed:1; struct dwc2_hsotg_ep *eps_in[MAX_EPS_CHANNELS]; struct dwc2_hsotg_ep *eps_out[MAX_EPS_CHANNELS]; - u32 g_using_dma; - u32 g_rx_fifo_sz; - u32 g_np_g_tx_fifo_sz; - u32 g_tx_fifo_sz[MAX_EPS_CHANNELS]; #endif /* CONFIG_USB_DWC2_PERIPHERAL || CONFIG_USB_DWC2_DUAL_ROLE */ }; +#define dwc2_readl(hsotg, reg) \ + bus_space_read_4((hsotg)->hsotg_sc->sc_iot, (hsotg)->hsotg_sc->sc_ioh, \ + (reg)) +#define dwc2_writel(hsotg, data, reg) \ + bus_space_write_4((hsotg)->hsotg_sc->sc_iot, (hsotg)->hsotg_sc->sc_ioh, \ + (reg), (data)) + +#ifdef DWC2_LOG_WRITES + pr_info("info:: wrote %08x to %p\n", value, hsotg->regs + offset); +#endif + +#if 0 +static inline void dwc2_readl_rep(struct dwc2_hsotg *hsotg, u32 offset, + void *buffer, unsigned int count) +{ + if (count) { + u32 *buf = buffer; + + do { + u32 x = dwc2_readl(hsotg, offset); + *buf++ = x; + } while (--count); + } +} + +static inline void dwc2_writel_rep(struct dwc2_hsotg *hsotg, u32 offset, + const void *buffer, unsigned int count) +{ + if (count) { + const u32 *buf = buffer; + + do { + dwc2_writel(hsotg, *buf++, offset); + } while (--count); + } +} +#endif + /* Reasons for halting a host channel */ enum dwc2_halt_status { DWC2_HC_XFER_NO_HALT_STATUS, @@ -870,268 +1321,82 @@ enum dwc2_halt_status { DWC2_HC_XFER_URB_DEQUEUE, }; +/* Core version information */ +static inline bool dwc2_is_iot(struct dwc2_hsotg *hsotg) +{ + return (hsotg->hw_params.snpsid & 0xfff00000) == 0x55300000; +} + +static inline bool dwc2_is_fs_iot(struct dwc2_hsotg *hsotg) +{ + return (hsotg->hw_params.snpsid & 0xffff0000) == 0x55310000; +} + +static inline bool dwc2_is_hs_iot(struct dwc2_hsotg *hsotg) +{ + return (hsotg->hw_params.snpsid & 0xffff0000) == 0x55320000; +} + /* * The following functions support initialization of the core driver component * and the DWC_otg controller */ -extern int dwc2_core_reset(struct dwc2_hsotg *hsotg); -extern int dwc2_core_reset_and_force_dr_mode(struct dwc2_hsotg *hsotg); -extern void dwc2_core_host_init(struct dwc2_hsotg *hsotg); -extern int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg); -extern int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, bool restore); - +int dwc2_core_reset(struct dwc2_hsotg *hsotg, bool skip_wait); +int dwc2_enter_partial_power_down(struct dwc2_hsotg *hsotg); +int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, int rem_wakeup, + bool restore); +int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg, int is_host); +int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup, + int reset, int is_host); +void dwc2_init_fs_ls_pclk_sel(struct dwc2_hsotg *hsotg); +int dwc2_phy_init(struct dwc2_hsotg *hsotg, bool select_phy); + +void dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host); void dwc2_force_dr_mode(struct dwc2_hsotg *hsotg); -/* - * Host core Functions. - * The following functions support managing the DWC_otg controller in host - * mode. - */ -extern void dwc2_hc_init(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan); -extern void dwc2_hc_halt(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan, - enum dwc2_halt_status halt_status); -extern void dwc2_hc_cleanup(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan); -extern void dwc2_hc_start_transfer(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan); -extern void dwc2_hc_start_transfer_ddma(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan); -extern int dwc2_hc_continue_transfer(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan); -extern void dwc2_hc_do_ping(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan); -extern void dwc2_enable_host_interrupts(struct dwc2_hsotg *hsotg); -extern void dwc2_disable_host_interrupts(struct dwc2_hsotg *hsotg); - -extern u32 dwc2_calc_frame_interval(struct dwc2_hsotg *hsotg); -extern bool dwc2_is_controller_alive(struct dwc2_hsotg *hsotg); +bool dwc2_is_controller_alive(struct dwc2_hsotg *hsotg); + +int dwc2_check_core_version(struct dwc2_hsotg *hsotg); /* * Common core Functions. * The following functions support managing the DWC_otg controller in either * device or host mode. */ -extern void dwc2_read_packet(struct dwc2_hsotg *hsotg, u8 *dest, u16 bytes); -extern void dwc2_flush_tx_fifo(struct dwc2_hsotg *hsotg, const int num); -extern void dwc2_flush_rx_fifo(struct dwc2_hsotg *hsotg); - -extern int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup); -extern void dwc2_enable_global_interrupts(struct dwc2_hsotg *hcd); -extern void dwc2_disable_global_interrupts(struct dwc2_hsotg *hcd); - -/* This function should be called on every hardware interrupt. */ -extern irqreturn_t dwc2_handle_common_intr(void *dev); - -/* OTG Core Parameters */ - -/* - * Specifies the OTG capabilities. The driver will automatically - * detect the value for this parameter if none is specified. - * 0 - HNP and SRP capable (default) - * 1 - SRP Only capable - * 2 - No HNP/SRP capable - */ -extern void dwc2_set_param_otg_cap(struct dwc2_hsotg *hsotg, int val); -#define DWC2_CAP_PARAM_HNP_SRP_CAPABLE 0 -#define DWC2_CAP_PARAM_SRP_ONLY_CAPABLE 1 -#define DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE 2 - -/* - * Specifies whether to use slave or DMA mode for accessing the data - * FIFOs. The driver will automatically detect the value for this - * parameter if none is specified. - * 0 - Slave - * 1 - DMA (default, if available) - */ -extern void dwc2_set_param_dma_enable(struct dwc2_hsotg *hsotg, int val); - -/* - * When DMA mode is enabled specifies whether to use - * address DMA or DMA Descritor mode for accessing the data - * FIFOs in device mode. The driver will automatically detect - * the value for this parameter if none is specified. - * 0 - address DMA - * 1 - DMA Descriptor(default, if available) - */ -extern void dwc2_set_param_dma_desc_enable(struct dwc2_hsotg *hsotg, int val); - -/* - * When DMA mode is enabled specifies whether to use - * address DMA or DMA Descritor mode with full speed devices - * for accessing the data FIFOs in host mode. - * 0 - address DMA - * 1 - FS DMA Descriptor(default, if available) - */ -extern void dwc2_set_param_dma_desc_fs_enable(struct dwc2_hsotg *hsotg, - int val); - -/* - * Specifies the maximum speed of operation in host and device mode. - * The actual speed depends on the speed of the attached device and - * the value of phy_type. The actual speed depends on the speed of the - * attached device. - * 0 - High Speed (default) - * 1 - Full Speed - */ -extern void dwc2_set_param_speed(struct dwc2_hsotg *hsotg, int val); -#define DWC2_SPEED_PARAM_HIGH 0 -#define DWC2_SPEED_PARAM_FULL 1 - -/* - * Specifies whether low power mode is supported when attached - * to a Full Speed or Low Speed device in host mode. - * - * 0 - Don't support low power mode (default) - * 1 - Support low power mode - */ -extern void dwc2_set_param_host_support_fs_ls_low_power( - struct dwc2_hsotg *hsotg, int val); - -/* - * Specifies the PHY clock rate in low power mode when connected to a - * Low Speed device in host mode. This parameter is applicable only if - * HOST_SUPPORT_FS_LS_LOW_POWER is enabled. If PHY_TYPE is set to FS - * then defaults to 6 MHZ otherwise 48 MHZ. - * - * 0 - 48 MHz - * 1 - 6 MHz - */ -extern void dwc2_set_param_host_ls_low_power_phy_clk(struct dwc2_hsotg *hsotg, - int val); -#define DWC2_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ 0 -#define DWC2_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ 1 - -/* - * 0 - Use cC FIFO size parameters - * 1 - Allow dynamic FIFO sizing (default) - */ -extern void dwc2_set_param_enable_dynamic_fifo(struct dwc2_hsotg *hsotg, - int val); - -/* - * Number of 4-byte words in the Rx FIFO in host mode when dynamic - * FIFO sizing is enabled. - * 16 to 32768 (default 1024) - */ -extern void dwc2_set_param_host_rx_fifo_size(struct dwc2_hsotg *hsotg, int val); - -/* - * Number of 4-byte words in the non-periodic Tx FIFO in host mode - * when Dynamic FIFO sizing is enabled in the core. - * 16 to 32768 (default 256) - */ -extern void dwc2_set_param_host_nperio_tx_fifo_size(struct dwc2_hsotg *hsotg, - int val); - -/* - * Number of 4-byte words in the host periodic Tx FIFO when dynamic - * FIFO sizing is enabled. - * 16 to 32768 (default 256) - */ -extern void dwc2_set_param_host_perio_tx_fifo_size(struct dwc2_hsotg *hsotg, - int val); - -/* - * The maximum transfer size supported in bytes. - * 2047 to 65,535 (default 65,535) - */ -extern void dwc2_set_param_max_transfer_size(struct dwc2_hsotg *hsotg, int val); - -/* - * The maximum number of packets in a transfer. - * 15 to 511 (default 511) - */ -extern void dwc2_set_param_max_packet_count(struct dwc2_hsotg *hsotg, int val); - -/* - * The number of host channel registers to use. - * 1 to 16 (default 11) - * Note: The FPGA configuration supports a maximum of 11 host channels. - */ -extern void dwc2_set_param_host_channels(struct dwc2_hsotg *hsotg, int val); - -/* - * Specifies the type of PHY interface to use. By default, the driver - * will automatically detect the phy_type. - * - * 0 - Full Speed PHY - * 1 - UTMI+ (default) - * 2 - ULPI - */ -extern void dwc2_set_param_phy_type(struct dwc2_hsotg *hsotg, int val); -#define DWC2_PHY_TYPE_PARAM_FS 0 -#define DWC2_PHY_TYPE_PARAM_UTMI 1 -#define DWC2_PHY_TYPE_PARAM_ULPI 2 - -/* - * Specifies the UTMI+ Data Width. This parameter is - * applicable for a PHY_TYPE of UTMI+ or ULPI. (For a ULPI - * PHY_TYPE, this parameter indicates the data width between - * the MAC and the ULPI Wrapper.) Also, this parameter is - * applicable only if the OTG_HSPHY_WIDTH cC parameter was set - * to "8 and 16 bits", meaning that the core has been - * configured to work at either data path width. - * - * 8 or 16 bits (default 16) - */ -extern void dwc2_set_param_phy_utmi_width(struct dwc2_hsotg *hsotg, int val); - -/* - * Specifies whether the ULPI operates at double or single - * data rate. This parameter is only applicable if PHY_TYPE is - * ULPI. - * - * 0 - single data rate ULPI interface with 8 bit wide data - * bus (default) - * 1 - double data rate ULPI interface with 4 bit wide data - * bus - */ -extern void dwc2_set_param_phy_ulpi_ddr(struct dwc2_hsotg *hsotg, int val); - -/* - * Specifies whether to use the internal or external supply to - * drive the vbus with a ULPI phy. - */ -extern void dwc2_set_param_phy_ulpi_ext_vbus(struct dwc2_hsotg *hsotg, int val); -#define DWC2_PHY_ULPI_INTERNAL_VBUS 0 -#define DWC2_PHY_ULPI_EXTERNAL_VBUS 1 - -/* - * Specifies whether to use the I2Cinterface for full speed PHY. This - * parameter is only applicable if PHY_TYPE is FS. - * 0 - No (default) - * 1 - Yes - */ -extern void dwc2_set_param_i2c_enable(struct dwc2_hsotg *hsotg, int val); - -extern void dwc2_set_param_ulpi_fs_ls(struct dwc2_hsotg *hsotg, int val); - -extern void dwc2_set_param_ts_dline(struct dwc2_hsotg *hsotg, int val); - -/* - * Specifies whether dedicated transmit FIFOs are - * enabled for non periodic IN endpoints in device mode - * 0 - No - * 1 - Yes - */ -extern void dwc2_set_param_en_multiple_tx_fifo(struct dwc2_hsotg *hsotg, - int val); +void dwc2_read_packet(struct dwc2_hsotg *hsotg, u8 *dest, u16 bytes); +void dwc2_flush_tx_fifo(struct dwc2_hsotg *hsotg, const int num); +void dwc2_flush_rx_fifo(struct dwc2_hsotg *hsotg); -extern void dwc2_set_param_reload_ctl(struct dwc2_hsotg *hsotg, int val); +void dwc2_enable_global_interrupts(struct dwc2_hsotg *hcd); +void dwc2_disable_global_interrupts(struct dwc2_hsotg *hcd); -extern void dwc2_set_param_ahbcfg(struct dwc2_hsotg *hsotg, int val); +void dwc2_hib_restore_common(struct dwc2_hsotg *hsotg, int rem_wakeup, + int is_host); +int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg); +int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg); -extern void dwc2_set_param_otg_ver(struct dwc2_hsotg *hsotg, int val); +void dwc2_enable_acg(struct dwc2_hsotg *hsotg); -extern void dwc2_set_parameters(struct dwc2_hsotg *hsotg, - const struct dwc2_core_params *params); - -extern void dwc2_set_all_params(struct dwc2_core_params *params, int value); - -extern int dwc2_get_hwparams(struct dwc2_hsotg *hsotg); - -extern int dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg); -extern int dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg); +/* This function should be called on every hardware interrupt. */ +irqreturn_t dwc2_handle_common_intr(void *dev); + +/* The device ID match table */ +//extern const struct of_device_id dwc2_of_match_table[]; +//extern const struct acpi_device_id dwc2_acpi_match[]; + +int dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg); +int dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg); + +/* Common polling functions */ +int dwc2_hsotg_wait_bit_set(struct dwc2_hsotg *hs_otg, u32 reg, u32 bit, + u32 timeout); +int dwc2_hsotg_wait_bit_clear(struct dwc2_hsotg *hs_otg, u32 reg, u32 bit, + u32 timeout); +/* Parameters */ +int dwc2_get_hwparams(struct dwc2_hsotg *hsotg); +int dwc2_init_params(struct dwc2_hsotg *hsotg); +void dwc2_set_default_params(struct dwc2_hsotg *); +void dwc2_check_params(struct dwc2_hsotg *); /* * The following functions check the controller's OTG operation mode @@ -1141,7 +1406,7 @@ extern int dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg); * are read in and cached so they always read directly from the * GHWCFG2 register. */ -unsigned dwc2_op_mode(struct dwc2_hsotg *hsotg); +unsigned int dwc2_op_mode(struct dwc2_hsotg *hsotg); bool dwc2_hw_is_otg(struct dwc2_hsotg *hsotg); bool dwc2_hw_is_host(struct dwc2_hsotg *hsotg); bool dwc2_hw_is_device(struct dwc2_hsotg *hsotg); @@ -1151,38 +1416,59 @@ bool dwc2_hw_is_device(struct dwc2_hsotg *hsotg); */ static inline int dwc2_is_host_mode(struct dwc2_hsotg *hsotg) { - return (DWC2_READ_4(hsotg, GINTSTS) & GINTSTS_CURMODE_HOST) != 0; + return (dwc2_readl(hsotg, GINTSTS) & GINTSTS_CURMODE_HOST) != 0; } static inline int dwc2_is_device_mode(struct dwc2_hsotg *hsotg) { - return (DWC2_READ_4(hsotg, GINTSTS) & GINTSTS_CURMODE_HOST) == 0; + return (dwc2_readl(hsotg, GINTSTS) & GINTSTS_CURMODE_HOST) == 0; } -/* - * Dump core registers and SPRAM - */ -extern void dwc2_dump_dev_registers(struct dwc2_hsotg *hsotg); -extern void dwc2_dump_host_registers(struct dwc2_hsotg *hsotg); -extern void dwc2_dump_global_registers(struct dwc2_hsotg *hsotg); +int dwc2_drd_init(struct dwc2_hsotg *hsotg); +void dwc2_drd_suspend(struct dwc2_hsotg *hsotg); +void dwc2_drd_resume(struct dwc2_hsotg *hsotg); +void dwc2_drd_exit(struct dwc2_hsotg *hsotg); /* - * Return OTG version - either 1.3 or 2.0 + * Dump core registers and SPRAM */ -extern u16 dwc2_get_otg_version(struct dwc2_hsotg *hsotg); +void dwc2_dump_dev_registers(struct dwc2_hsotg *hsotg); +void dwc2_dump_host_registers(struct dwc2_hsotg *hsotg); +void dwc2_dump_global_registers(struct dwc2_hsotg *hsotg); /* Gadget defines */ -#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) -extern int dwc2_hsotg_remove(struct dwc2_hsotg *hsotg); -extern int dwc2_hsotg_suspend(struct dwc2_hsotg *dwc2); -extern int dwc2_hsotg_resume(struct dwc2_hsotg *dwc2); -extern int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq); -extern void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, - bool reset); -extern void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg); -extern void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2); -extern int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, int testmode); +#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ + IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) +int dwc2_hsotg_remove(struct dwc2_hsotg *hsotg); +int dwc2_hsotg_suspend(struct dwc2_hsotg *dwc2); +int dwc2_hsotg_resume(struct dwc2_hsotg *dwc2); +int dwc2_gadget_init(struct dwc2_hsotg *hsotg); +void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, + bool reset); +void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg); +void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg); +void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2); +int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, int testmode); #define dwc2_is_device_connected(hsotg) (hsotg->connected) +#define dwc2_is_device_enabled(hsotg) (hsotg->enabled) +int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg); +int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg, int remote_wakeup); +int dwc2_gadget_enter_hibernation(struct dwc2_hsotg *hsotg); +int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg, + int rem_wakeup, int reset); +int dwc2_gadget_enter_partial_power_down(struct dwc2_hsotg *hsotg); +int dwc2_gadget_exit_partial_power_down(struct dwc2_hsotg *hsotg, + bool restore); +void dwc2_gadget_enter_clock_gating(struct dwc2_hsotg *hsotg); +void dwc2_gadget_exit_clock_gating(struct dwc2_hsotg *hsotg, + int rem_wakeup); +int dwc2_hsotg_tx_fifo_count(struct dwc2_hsotg *hsotg); +int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg); +int dwc2_hsotg_tx_fifo_average_depth(struct dwc2_hsotg *hsotg); +void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg); +void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg); +static inline void dwc2_clear_fifo_map(struct dwc2_hsotg *hsotg) +{ hsotg->fifo_map = 0; } #else static inline int dwc2_hsotg_remove(struct dwc2_hsotg *dwc2) { return 0; } @@ -1190,32 +1476,110 @@ static inline int dwc2_hsotg_suspend(struct dwc2_hsotg *dwc2) { return 0; } static inline int dwc2_hsotg_resume(struct dwc2_hsotg *dwc2) { return 0; } -static inline int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) +static inline int dwc2_gadget_init(struct dwc2_hsotg *hsotg) { return 0; } static inline void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, - bool reset) {} + bool reset) {} +static inline void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) {} static inline void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg) {} static inline void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2) {} static inline int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, - int testmode) + int testmode) { return 0; } #define dwc2_is_device_connected(hsotg) (0) +#define dwc2_is_device_enabled(hsotg) (0) +static inline int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg, + int remote_wakeup) +{ return 0; } +static inline int dwc2_gadget_enter_hibernation(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg, + int rem_wakeup, int reset) +{ return 0; } +static inline int dwc2_gadget_enter_partial_power_down(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_gadget_exit_partial_power_down(struct dwc2_hsotg *hsotg, + bool restore) +{ return 0; } +static inline void dwc2_gadget_enter_clock_gating(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_gadget_exit_clock_gating(struct dwc2_hsotg *hsotg, + int rem_wakeup) {} +static inline int dwc2_hsotg_tx_fifo_count(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_hsotg_tx_fifo_average_depth(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_clear_fifo_map(struct dwc2_hsotg *hsotg) {} #endif #if IS_ENABLED(CONFIG_USB_DWC2_HOST) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) -extern int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg); -extern void dwc2_hcd_connect(struct dwc2_hsotg *hsotg); -extern void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force); -extern void dwc2_hcd_start(struct dwc2_hsotg *hsotg); +int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg); +int dwc2_hcd_get_future_frame_number(struct dwc2_hsotg *hsotg, int us); +void dwc2_hcd_connect(struct dwc2_hsotg *hsotg); +void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force); +void dwc2_hcd_start(struct dwc2_hsotg *hsotg); +int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup); +int dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex); +int dwc2_port_resume(struct dwc2_hsotg *hsotg); +int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg); +int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg); +int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg); +int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, + int rem_wakeup, int reset); +int dwc2_host_enter_partial_power_down(struct dwc2_hsotg *hsotg); +int dwc2_host_exit_partial_power_down(struct dwc2_hsotg *hsotg, + int rem_wakeup, bool restore); +void dwc2_host_enter_clock_gating(struct dwc2_hsotg *hsotg); +void dwc2_host_exit_clock_gating(struct dwc2_hsotg *hsotg, int rem_wakeup); +bool dwc2_host_can_poweroff_phy(struct dwc2_hsotg *dwc2); +static inline void dwc2_host_schedule_phy_reset(struct dwc2_hsotg *hsotg) +{ +// schedule_work(&hsotg->phy_reset_work); +} #else static inline int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg) { return 0; } +static inline int dwc2_hcd_get_future_frame_number(struct dwc2_hsotg *hsotg, + int us) +{ return 0; } static inline void dwc2_hcd_connect(struct dwc2_hsotg *hsotg) {} static inline void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force) {} static inline void dwc2_hcd_start(struct dwc2_hsotg *hsotg) {} -static inline void dwc2_hcd_remove(struct dwc2_hsotg *hsotg) {} +//static inline void dwc2_hcd_remove(struct dwc2_hsotg *hsotg) {} +static inline int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup) +{ return 0; } +static inline int dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex) +{ return 0; } +static inline int dwc2_port_resume(struct dwc2_hsotg *hsotg) +{ return 0; } static inline int dwc2_hcd_init(struct dwc2_hsotg *hsotg) { return 0; } +static inline int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, + int rem_wakeup, int reset) +{ return 0; } +static inline int dwc2_host_enter_partial_power_down(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_host_exit_partial_power_down(struct dwc2_hsotg *hsotg, + int rem_wakeup, bool restore) +{ return 0; } +static inline void dwc2_host_enter_clock_gating(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_host_exit_clock_gating(struct dwc2_hsotg *hsotg, + int rem_wakeup) {} +static inline bool dwc2_host_can_poweroff_phy(struct dwc2_hsotg *dwc2) +{ return false; } +static inline void dwc2_host_schedule_phy_reset(struct dwc2_hsotg *hsotg) {} + #endif #endif /* __DWC2_CORE_H__ */ diff --git a/sys/dev/usb/dwc2/dwc2_coreintr.c b/sys/dev/usb/dwc2/dwc2_coreintr.c index fcd58102119..395572eef92 100644 --- a/sys/dev/usb/dwc2/dwc2_coreintr.c +++ b/sys/dev/usb/dwc2/dwc2_coreintr.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dwc2_coreintr.c,v 1.11 2021/07/22 18:32:33 mglocker Exp $ */ +/* $OpenBSD: dwc2_coreintr.c,v 1.12 2022/09/04 08:42:40 mglocker Exp $ */ /* $NetBSD: dwc2_coreintr.c,v 1.8 2014/04/04 05:40:57 skrll Exp $ */ /* @@ -40,7 +40,6 @@ /* * This file contains the common interrupt handlers */ - #include #include #include @@ -63,7 +62,6 @@ #include #include -#ifdef DWC2_DEBUG STATIC const char *dwc2_op_state_str(struct dwc2_hsotg *hsotg) { switch (hsotg->op_state) { @@ -81,7 +79,6 @@ STATIC const char *dwc2_op_state_str(struct dwc2_hsotg *hsotg) return "unknown"; } } -#endif /** * dwc2_handle_usb_port_intr - handles OTG PRTINT interrupts. @@ -92,11 +89,11 @@ STATIC const char *dwc2_op_state_str(struct dwc2_hsotg *hsotg) */ STATIC void dwc2_handle_usb_port_intr(struct dwc2_hsotg *hsotg) { - u32 hprt0 = DWC2_READ_4(hsotg, HPRT0); + u32 hprt0 = dwc2_readl(hsotg, HPRT0); if (hprt0 & HPRT0_ENACHG) { hprt0 &= ~HPRT0_ENA; - DWC2_WRITE_4(hsotg, HPRT0, hprt0); + dwc2_writel(hsotg, hprt0, HPRT0); } } @@ -108,7 +105,7 @@ STATIC void dwc2_handle_usb_port_intr(struct dwc2_hsotg *hsotg) STATIC void dwc2_handle_mode_mismatch_intr(struct dwc2_hsotg *hsotg) { /* Clear interrupt */ - DWC2_WRITE_4(hsotg, GINTSTS, GINTSTS_MODEMIS); + dwc2_writel(hsotg, GINTSTS_MODEMIS, GINTSTS); dev_warn(hsotg->dev, "Mode Mismatch Interrupt: currently in %s mode\n", dwc2_is_host_mode(hsotg) ? "Host" : "Device"); @@ -126,8 +123,8 @@ STATIC void dwc2_handle_otg_intr(struct dwc2_hsotg *hsotg) u32 gotgctl; u32 gintmsk; - gotgint = DWC2_READ_4(hsotg, GOTGINT); - gotgctl = DWC2_READ_4(hsotg, GOTGCTL); + gotgint = dwc2_readl(hsotg, GOTGINT); + gotgctl = dwc2_readl(hsotg, GOTGCTL); dev_dbg(hsotg->dev, "++OTG Interrupt gotgint=%0x [%s]\n", gotgint, dwc2_op_state_str(hsotg)); @@ -135,7 +132,7 @@ STATIC void dwc2_handle_otg_intr(struct dwc2_hsotg *hsotg) dev_dbg(hsotg->dev, " ++OTG Interrupt: Session End Detected++ (%s)\n", dwc2_op_state_str(hsotg)); - gotgctl = DWC2_READ_4(hsotg, GOTGCTL); + gotgctl = dwc2_readl(hsotg, GOTGCTL); if (dwc2_is_device_mode(hsotg)) dwc2_hsotg_disconnect(hsotg); @@ -161,25 +158,24 @@ STATIC void dwc2_handle_otg_intr(struct dwc2_hsotg *hsotg) hsotg->lx_state = DWC2_L0; } - gotgctl = DWC2_READ_4(hsotg, GOTGCTL); + gotgctl = dwc2_readl(hsotg, GOTGCTL); gotgctl &= ~GOTGCTL_DEVHNPEN; - DWC2_WRITE_4(hsotg, GOTGCTL, gotgctl); + dwc2_writel(hsotg, gotgctl, GOTGCTL); } if (gotgint & GOTGINT_SES_REQ_SUC_STS_CHNG) { dev_dbg(hsotg->dev, " ++OTG Interrupt: Session Request Success Status Change++\n"); - gotgctl = DWC2_READ_4(hsotg, GOTGCTL); + gotgctl = dwc2_readl(hsotg, GOTGCTL); if (gotgctl & GOTGCTL_SESREQSCS) { - if (hsotg->core_params->phy_type == - DWC2_PHY_TYPE_PARAM_FS - && hsotg->core_params->i2c_enable > 0) { + if (hsotg->params.phy_type == DWC2_PHY_TYPE_PARAM_FS && + hsotg->params.i2c_enable) { hsotg->srp_success = 1; } else { /* Clear Session Request */ - gotgctl = DWC2_READ_4(hsotg, GOTGCTL); + gotgctl = dwc2_readl(hsotg, GOTGCTL); gotgctl &= ~GOTGCTL_SESREQ; - DWC2_WRITE_4(hsotg, GOTGCTL, gotgctl); + dwc2_writel(hsotg, gotgctl, GOTGCTL); } } } @@ -189,7 +185,7 @@ STATIC void dwc2_handle_otg_intr(struct dwc2_hsotg *hsotg) * Print statements during the HNP interrupt handling * can cause it to fail */ - gotgctl = DWC2_READ_4(hsotg, GOTGCTL); + gotgctl = dwc2_readl(hsotg, GOTGCTL); /* * WA for 3.00a- HW is not setting cur_mode, even sometimes * this does not help @@ -209,9 +205,9 @@ STATIC void dwc2_handle_otg_intr(struct dwc2_hsotg *hsotg) * interrupt does not get handled and Linux * complains loudly. */ - gintmsk = DWC2_READ_4(hsotg, GINTMSK); + gintmsk = dwc2_readl(hsotg, GINTMSK); gintmsk &= ~GINTSTS_SOF; - DWC2_WRITE_4(hsotg, GINTMSK, gintmsk); + dwc2_writel(hsotg, gintmsk, GINTMSK); /* * Call callback function with spin lock @@ -225,9 +221,9 @@ STATIC void dwc2_handle_otg_intr(struct dwc2_hsotg *hsotg) hsotg->op_state = OTG_STATE_B_HOST; } } else { - gotgctl = DWC2_READ_4(hsotg, GOTGCTL); + gotgctl = dwc2_readl(hsotg, GOTGCTL); gotgctl &= ~(GOTGCTL_HNPREQ | GOTGCTL_DEVHNPEN); - DWC2_WRITE_4(hsotg, GOTGCTL, gotgctl); + dwc2_writel(hsotg, gotgctl, GOTGCTL); dev_dbg(hsotg->dev, "HNP Failed\n"); dev_err(hsotg->dev, "Device Not Connected/Responding\n"); @@ -253,9 +249,9 @@ STATIC void dwc2_handle_otg_intr(struct dwc2_hsotg *hsotg) hsotg->op_state = OTG_STATE_A_PERIPHERAL; } else { /* Need to disable SOF interrupt immediately */ - gintmsk = DWC2_READ_4(hsotg, GINTMSK); + gintmsk = dwc2_readl(hsotg, GINTMSK); gintmsk &= ~GINTSTS_SOF; - DWC2_WRITE_4(hsotg, GINTMSK, gintmsk); + dwc2_writel(hsotg, gintmsk, GINTMSK); spin_unlock(&hsotg->lock); dwc2_hcd_start(hsotg); spin_lock(&hsotg->lock); @@ -270,7 +266,7 @@ STATIC void dwc2_handle_otg_intr(struct dwc2_hsotg *hsotg) dev_dbg(hsotg->dev, " ++OTG Interrupt: Debounce Done++\n"); /* Clear GOTGINT */ - DWC2_WRITE_4(hsotg, GOTGINT, gotgint); + dwc2_writel(hsotg, gotgint, GOTGINT); } /** @@ -288,26 +284,21 @@ STATIC void dwc2_handle_conn_id_status_change_intr(struct dwc2_hsotg *hsotg) u32 gintmsk; /* Clear interrupt */ - DWC2_WRITE_4(hsotg, GINTSTS, GINTSTS_CONIDSTSCHNG); + dwc2_writel(hsotg, GINTSTS_CONIDSTSCHNG, GINTSTS); /* Need to disable SOF interrupt immediately */ - gintmsk = DWC2_READ_4(hsotg, GINTMSK); + gintmsk = dwc2_readl(hsotg, GINTMSK); gintmsk &= ~GINTSTS_SOF; - DWC2_WRITE_4(hsotg, GINTMSK, gintmsk); + dwc2_writel(hsotg, gintmsk, GINTMSK); dev_dbg(hsotg->dev, " ++Connector ID Status Change Interrupt++ (%s)\n", dwc2_is_host_mode(hsotg) ? "Host" : "Device"); /* * Need to schedule a work, as there are possible DELAY function calls. - * Release lock before scheduling workq as it holds spinlock during - * scheduling. */ - if (hsotg->wq_otg) { - spin_unlock(&hsotg->lock); + if (hsotg->wq_otg) task_add(hsotg->wq_otg, &hsotg->wf_otg); - spin_lock(&hsotg->lock); - } } /** @@ -324,19 +315,28 @@ STATIC void dwc2_handle_conn_id_status_change_intr(struct dwc2_hsotg *hsotg) STATIC void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) { int ret; + u32 hprt0; /* Clear interrupt */ - DWC2_WRITE_4(hsotg, GINTSTS, GINTSTS_SESSREQINT); + dwc2_writel(hsotg, GINTSTS_SESSREQINT, GINTSTS); dev_dbg(hsotg->dev, "Session request interrupt - lx_state=%d\n", - hsotg->lx_state); + hsotg->lx_state); if (dwc2_is_device_mode(hsotg)) { if (hsotg->lx_state == DWC2_L2) { - ret = dwc2_exit_hibernation(hsotg, true); - if (ret && (ret != -ENOTSUP)) - dev_err(hsotg->dev, - "exit hibernation failed\n"); + if (hsotg->in_ppd) { + ret = dwc2_exit_partial_power_down(hsotg, 0, + true); + if (ret) + dev_err(hsotg->dev, + "exit power_down failed\n"); + } + + /* Exit gadget mode clock gating. */ + if (hsotg->params.power_down == + DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended) + dwc2_gadget_exit_clock_gating(hsotg, 0); } /* @@ -344,9 +344,67 @@ STATIC void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) * established */ dwc2_hsotg_disconnect(hsotg); + } else { + /* Turn on the port power bit. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_PWR; + dwc2_writel(hsotg, hprt0, HPRT0); + /* Connect hcd after port power is set. */ + dwc2_hcd_connect(hsotg); } } +/** + * dwc2_wakeup_from_lpm_l1 - Exit the device from LPM L1 state + * + * @hsotg: Programming view of DWC_otg controller + * + */ +static void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg) +{ + u32 glpmcfg; + u32 i = 0; + + if (hsotg->lx_state != DWC2_L1) { + dev_err(hsotg->dev, "Core isn't in DWC2_L1 state\n"); + return; + } + + glpmcfg = dwc2_readl(hsotg, GLPMCFG); + if (dwc2_is_device_mode(hsotg)) { + dev_dbg(hsotg->dev, "Exit from L1 state\n"); + glpmcfg &= ~GLPMCFG_ENBLSLPM; + glpmcfg &= ~GLPMCFG_HIRD_THRES_EN; + dwc2_writel(hsotg, glpmcfg, GLPMCFG); + + do { + glpmcfg = dwc2_readl(hsotg, GLPMCFG); + + if (!(glpmcfg & (GLPMCFG_COREL1RES_MASK | + GLPMCFG_L1RESUMEOK | GLPMCFG_SLPSTS))) + break; + + udelay(1); + } while (++i < 200); + + if (i == 200) { + dev_err(hsotg->dev, "Failed to exit L1 sleep state in 200us.\n"); + return; + } + dwc2_gadget_init_lpm(hsotg); + } else { + /* TODO */ + dev_err(hsotg->dev, "Host side LPM is not supported.\n"); + return; + } + + /* Change to L0 state */ + hsotg->lx_state = DWC2_L0; + + /* Inform gadget to exit from L1 */ +// call_gadget(hsotg, resume); +} + /* * This interrupt indicates that the DWC_otg controller has detected a * resume or remote wakeup sequence. If the DWC_otg controller is in @@ -359,39 +417,68 @@ STATIC void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) int ret; /* Clear interrupt */ - DWC2_WRITE_4(hsotg, GINTSTS, GINTSTS_WKUPINT); + dwc2_writel(hsotg, GINTSTS_WKUPINT, GINTSTS); dev_dbg(hsotg->dev, "++Resume or Remote Wakeup Detected Interrupt++\n"); dev_dbg(hsotg->dev, "%s lxstate = %d\n", __func__, hsotg->lx_state); + if (hsotg->lx_state == DWC2_L1) { + dwc2_wakeup_from_lpm_l1(hsotg); + return; + } + if (dwc2_is_device_mode(hsotg)) { - dev_dbg(hsotg->dev, "DSTS=0x%0x\n", DWC2_READ_4(hsotg, DSTS)); + dev_dbg(hsotg->dev, "DSTS=0x%0x\n", + dwc2_readl(hsotg, DSTS)); if (hsotg->lx_state == DWC2_L2) { - u32 dctl = DWC2_READ_4(hsotg, DCTL); - - /* Clear Remote Wakeup Signaling */ - dctl &= ~DCTL_RMTWKUPSIG; - DWC2_WRITE_4(hsotg, DCTL, dctl); - ret = dwc2_exit_hibernation(hsotg, true); - if (ret && (ret != -ENOTSUP)) - dev_err(hsotg->dev, "exit hibernation failed\n"); + if (hsotg->in_ppd) { + u32 dctl = dwc2_readl(hsotg, DCTL); + /* Clear Remote Wakeup Signaling */ + dctl &= ~DCTL_RMTWKUPSIG; + dwc2_writel(hsotg, dctl, DCTL); + ret = dwc2_exit_partial_power_down(hsotg, 1, + true); + if (ret) + dev_err(hsotg->dev, + "exit partial_power_down failed\n"); - call_gadget(hsotg, resume); +// call_gadget(hsotg, resume); + } +#if 0 + /* Exit gadget mode clock gating. */ + if (hsotg->params.power_down == + DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended) + dwc2_gadget_exit_clock_gating(hsotg, 0); +#endif + } else { + /* Change to L0 state */ + hsotg->lx_state = DWC2_L0; } - /* Change to L0 state */ - hsotg->lx_state = DWC2_L0; } else { - if (hsotg->core_params->hibernation) - return; + if (hsotg->lx_state == DWC2_L2) { + if (hsotg->in_ppd) { + ret = dwc2_exit_partial_power_down(hsotg, 1, + true); + if (ret) + dev_err(hsotg->dev, + "exit partial_power_down failed\n"); + } - if (hsotg->lx_state != DWC2_L1) { - u32 pcgcctl = DWC2_READ_4(hsotg, PCGCTL); + if (hsotg->params.power_down == + DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended) + dwc2_host_exit_clock_gating(hsotg, 1); + + /* + * If we've got this quirk then the PHY is stuck upon + * wakeup. Assert reset. This will propagate out and + * eventually we'll re-enumerate the device. Not great + * but the best we can do. We can't call phy_reset() + * at interrupt time but there's no hurry, so we'll + * schedule it for later. + */ + if (hsotg->reset_phy_on_wake) + dwc2_host_schedule_phy_reset(hsotg); - /* Restart the Phy Clock */ - pcgcctl &= ~PCGCTL_STOPPCLK; - DWC2_WRITE_4(hsotg, PCGCTL, pcgcctl); - timeout_set(&hsotg->wkp_timer, dwc2_wakeup_detected, - hsotg); timeout_add_msec(&hsotg->wkp_timer, 71); } else { /* Change to L0 state */ @@ -406,7 +493,7 @@ STATIC void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) */ STATIC void dwc2_handle_disconnect_intr(struct dwc2_hsotg *hsotg) { - DWC2_WRITE_4(hsotg, GINTSTS, GINTSTS_DISCONNINT); + dwc2_writel(hsotg, GINTSTS_DISCONNINT, GINTSTS); dev_dbg(hsotg->dev, "++Disconnect Detected Interrupt++ (%s) %s\n", dwc2_is_host_mode(hsotg) ? "Host" : "Device", @@ -430,7 +517,7 @@ STATIC void dwc2_handle_usb_suspend_intr(struct dwc2_hsotg *hsotg) int ret; /* Clear interrupt */ - DWC2_WRITE_4(hsotg, GINTSTS, GINTSTS_USBSUSP); + dwc2_writel(hsotg, GINTSTS_USBSUSP, GINTSTS); dev_dbg(hsotg->dev, "USB SUSPEND\n"); @@ -439,34 +526,54 @@ STATIC void dwc2_handle_usb_suspend_intr(struct dwc2_hsotg *hsotg) * Check the Device status register to determine if the Suspend * state is active */ - dsts = DWC2_READ_4(hsotg, DSTS); - dev_dbg(hsotg->dev, "DSTS=0x%0x\n", dsts); + dsts = dwc2_readl(hsotg, DSTS); + dev_dbg(hsotg->dev, "%s: DSTS=0x%0x\n", __func__, dsts); dev_dbg(hsotg->dev, - "DSTS.Suspend Status=%d HWCFG4.Power Optimize=%d\n", + "DSTS.Suspend Status=%d HWCFG4.Power Optimize=%d HWCFG4.Hibernation=%d\n", !!(dsts & DSTS_SUSPSTS), - hsotg->hw_params.power_optimized); - if ((dsts & DSTS_SUSPSTS) && hsotg->hw_params.power_optimized) { - /* Ignore suspend request before enumeration */ - if (!dwc2_is_device_connected(hsotg)) { - dev_dbg(hsotg->dev, - "ignore suspend request before enumeration\n"); - return; - } + hsotg->hw_params.power_optimized, + hsotg->hw_params.hibernation); - ret = dwc2_enter_hibernation(hsotg); - if (ret) { - if (ret != -ENOTSUP) + /* Ignore suspend request before enumeration */ + if (!dwc2_is_device_connected(hsotg)) { + dev_dbg(hsotg->dev, + "ignore suspend request before enumeration\n"); + return; + } + if (dsts & DSTS_SUSPSTS) { + switch (hsotg->params.power_down) { + case DWC2_POWER_DOWN_PARAM_PARTIAL: + ret = dwc2_enter_partial_power_down(hsotg); + if (ret) dev_err(hsotg->dev, - "enter hibernation failed\n"); - goto skip_power_saving; - } + "enter partial_power_down failed\n"); - udelay(100); + udelay(100); + + /* Ask phy to be suspended */ +#if 0 + if (!IS_ERR_OR_NULL(hsotg->uphy)) + usb_phy_set_suspend(hsotg->uphy, true); +#endif + break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: + ret = dwc2_enter_hibernation(hsotg, 0); + if (ret) + dev_err(hsotg->dev, + "enter hibernation failed\n"); + break; + case DWC2_POWER_DOWN_PARAM_NONE: + /* + * If neither hibernation nor partial power down are supported, + * clock gating is used to save power. + */ +#if 0 + if (!hsotg->params.no_clock_gating) + dwc2_gadget_enter_clock_gating(hsotg); +#endif + break; + } - /* Ask phy to be suspended */ - if (hsotg->uphy != NULL) - usb_phy_set_suspend(hsotg->uphy, true); -skip_power_saving: /* * Change to L2 (suspend) state before releasing * spinlock @@ -474,7 +581,7 @@ skip_power_saving: hsotg->lx_state = DWC2_L2; /* Call gadget suspend callback */ - call_gadget(hsotg, suspend); +// call_gadget(hsotg, suspend); } } else { if (hsotg->op_state == OTG_STATE_A_PERIPHERAL) { @@ -491,10 +598,75 @@ skip_power_saving: } } +/** + * dwc2_handle_lpm_intr - GINTSTS_LPMTRANRCVD Interrupt handler + * + * @hsotg: Programming view of DWC_otg controller + * + */ +static void dwc2_handle_lpm_intr(struct dwc2_hsotg *hsotg) +{ + u32 glpmcfg; + u32 pcgcctl; + u32 hird; + u32 hird_thres; + u32 hird_thres_en; + u32 enslpm; + + /* Clear interrupt */ + dwc2_writel(hsotg, GINTSTS_LPMTRANRCVD, GINTSTS); + + glpmcfg = dwc2_readl(hsotg, GLPMCFG); + + if (!(glpmcfg & GLPMCFG_LPMCAP)) { + dev_err(hsotg->dev, "Unexpected LPM interrupt\n"); + return; + } + + hird = (glpmcfg & GLPMCFG_HIRD_MASK) >> GLPMCFG_HIRD_SHIFT; + hird_thres = (glpmcfg & GLPMCFG_HIRD_THRES_MASK & + ~GLPMCFG_HIRD_THRES_EN) >> GLPMCFG_HIRD_THRES_SHIFT; + hird_thres_en = glpmcfg & GLPMCFG_HIRD_THRES_EN; + enslpm = glpmcfg & GLPMCFG_ENBLSLPM; + + if (dwc2_is_device_mode(hsotg)) { + dev_dbg(hsotg->dev, "HIRD_THRES_EN = %d\n", hird_thres_en); + + if (hird_thres_en && hird >= hird_thres) { + dev_dbg(hsotg->dev, "L1 with utmi_l1_suspend_n\n"); + } else if (enslpm) { + dev_dbg(hsotg->dev, "L1 with utmi_sleep_n\n"); + } else { + dev_dbg(hsotg->dev, "Entering Sleep with L1 Gating\n"); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl |= PCGCTL_ENBL_SLEEP_GATING; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + } + /** + * Examine prt_sleep_sts after TL1TokenTetry period max (10 us) + */ + udelay(10); + + glpmcfg = dwc2_readl(hsotg, GLPMCFG); + + if (glpmcfg & GLPMCFG_SLPSTS) { + /* Save the current state */ + hsotg->lx_state = DWC2_L1; + dev_dbg(hsotg->dev, + "Core is in L1 sleep glpmcfg=%08x\n", glpmcfg); + + /* Inform gadget that we are in L1 state */ +// call_gadget(hsotg, suspend); + } + } +} + #define GINTMSK_COMMON (GINTSTS_WKUPINT | GINTSTS_SESSREQINT | \ GINTSTS_CONIDSTSCHNG | GINTSTS_OTGINT | \ GINTSTS_MODEMIS | GINTSTS_DISCONNINT | \ - GINTSTS_USBSUSP | GINTSTS_PRTINT) + GINTSTS_USBSUSP | GINTSTS_PRTINT | \ + GINTSTS_LPMTRANRCVD) /* * This function returns the Core Interrupt register @@ -506,9 +678,9 @@ STATIC u32 dwc2_read_common_intr(struct dwc2_hsotg *hsotg) u32 gahbcfg; u32 gintmsk_common = GINTMSK_COMMON; - gintsts = DWC2_READ_4(hsotg, GINTSTS); - gintmsk = DWC2_READ_4(hsotg, GINTMSK); - gahbcfg = DWC2_READ_4(hsotg, GAHBCFG); + gintsts = dwc2_readl(hsotg, GINTSTS); + gintmsk = dwc2_readl(hsotg, GINTMSK); + gahbcfg = dwc2_readl(hsotg, GAHBCFG); /* If any common interrupts set */ if (gintsts & gintmsk_common) @@ -521,6 +693,141 @@ STATIC u32 dwc2_read_common_intr(struct dwc2_hsotg *hsotg) return 0; } +/** + * dwc_handle_gpwrdn_disc_det() - Handles the gpwrdn disconnect detect. + * Exits hibernation without restoring registers. + * + * @hsotg: Programming view of DWC_otg controller + * @gpwrdn: GPWRDN register + */ +static inline void dwc_handle_gpwrdn_disc_det(struct dwc2_hsotg *hsotg, + u32 gpwrdn) +{ + u32 gpwrdn_tmp; + + /* Switch-on voltage to the core */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PWRDNSWTCH; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + udelay(5); + + /* Reset core */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PWRDNRSTN; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + udelay(5); + + /* Disable Power Down Clamp */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PWRDNCLMP; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + udelay(5); + + /* Deassert reset core */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp |= GPWRDN_PWRDNRSTN; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + udelay(5); + + /* Disable PMU interrupt */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PMUINTSEL; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + + /* De-assert Wakeup Logic */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PMUACTV; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + + hsotg->hibernated = 0; + hsotg->bus_suspended = 0; + + if (gpwrdn & GPWRDN_IDSTS) { + hsotg->op_state = OTG_STATE_B_PERIPHERAL; + dwc2_core_init(hsotg, false); + dwc2_enable_global_interrupts(hsotg); + dwc2_hsotg_core_init_disconnected(hsotg, false); + dwc2_hsotg_core_connect(hsotg); + } else { + hsotg->op_state = OTG_STATE_A_HOST; + + /* Initialize the Core for Host mode */ + dwc2_core_init(hsotg, false); + dwc2_enable_global_interrupts(hsotg); + dwc2_hcd_start(hsotg); + } +} + +/* + * GPWRDN interrupt handler. + * + * The GPWRDN interrupts are those that occur in both Host and + * Device mode while core is in hibernated state. + */ +static int dwc2_handle_gpwrdn_intr(struct dwc2_hsotg *hsotg) +{ + u32 gpwrdn; + int linestate; + int ret = 0; + + gpwrdn = dwc2_readl(hsotg, GPWRDN); + /* clear all interrupt */ + dwc2_writel(hsotg, gpwrdn, GPWRDN); + linestate = (gpwrdn & GPWRDN_LINESTATE_MASK) >> GPWRDN_LINESTATE_SHIFT; + dev_dbg(hsotg->dev, + "%s: dwc2_handle_gpwrdwn_intr called gpwrdn= %08x\n", __func__, + gpwrdn); + + if ((gpwrdn & GPWRDN_DISCONN_DET) && + (gpwrdn & GPWRDN_DISCONN_DET_MSK) && !linestate) { + dev_dbg(hsotg->dev, "%s: GPWRDN_DISCONN_DET\n", __func__); + /* + * Call disconnect detect function to exit from + * hibernation + */ + dwc_handle_gpwrdn_disc_det(hsotg, gpwrdn); + } else if ((gpwrdn & GPWRDN_LNSTSCHG) && + (gpwrdn & GPWRDN_LNSTSCHG_MSK) && linestate) { + dev_dbg(hsotg->dev, "%s: GPWRDN_LNSTSCHG\n", __func__); + if (hsotg->hw_params.hibernation && + hsotg->hibernated) { + if (gpwrdn & GPWRDN_IDSTS) { + ret = dwc2_exit_hibernation(hsotg, 0, 0, 0); + if (ret) + dev_err(hsotg->dev, + "exit hibernation failed.\n"); +// call_gadget(hsotg, resume); + } else { + ret = dwc2_exit_hibernation(hsotg, 1, 0, 1); + if (ret) + dev_err(hsotg->dev, + "exit hibernation failed.\n"); + } + } + } else if ((gpwrdn & GPWRDN_RST_DET) && + (gpwrdn & GPWRDN_RST_DET_MSK)) { + dev_dbg(hsotg->dev, "%s: GPWRDN_RST_DET\n", __func__); + if (!linestate) { + ret = dwc2_exit_hibernation(hsotg, 0, 1, 0); + if (ret) + dev_err(hsotg->dev, + "exit hibernation failed.\n"); + } + } else if ((gpwrdn & GPWRDN_STS_CHGINT) && + (gpwrdn & GPWRDN_STS_CHGINT_MSK)) { + dev_dbg(hsotg->dev, "%s: GPWRDN_STS_CHGINT\n", __func__); + /* + * As GPWRDN_STS_CHGINT exit from hibernation flow is + * the same as in GPWRDN_DISCONN_DET flow. Call + * disconnect detect helper function to exit from + * hibernation. + */ + dwc_handle_gpwrdn_disc_det(hsotg, gpwrdn); + } + + return ret; +} + /* * Common interrupt handler * @@ -540,17 +847,32 @@ irqreturn_t dwc2_handle_common_intr(void *dev) u32 gintsts; irqreturn_t retval = IRQ_NONE; + spin_lock(&hsotg->lock); + if (!dwc2_is_controller_alive(hsotg)) { dev_warn(hsotg->dev, "Controller is dead\n"); goto out; } - MUTEX_ASSERT_LOCKED(&hsotg->lock); + /* Reading current frame number value in device or host modes. */ + if (dwc2_is_device_mode(hsotg)) + hsotg->frame_number = (dwc2_readl(hsotg, DSTS) + & DSTS_SOFFN_MASK) >> DSTS_SOFFN_SHIFT; + else + hsotg->frame_number = (dwc2_readl(hsotg, HFNUM) + & HFNUM_FRNUM_MASK) >> HFNUM_FRNUM_SHIFT; gintsts = dwc2_read_common_intr(hsotg); if (gintsts & ~GINTSTS_PRTINT) retval = IRQ_HANDLED; + /* In case of hibernated state gintsts must not work */ + if (hsotg->hibernated) { + dwc2_handle_gpwrdn_intr(hsotg); + retval = IRQ_HANDLED; + goto out; + } + if (gintsts & GINTSTS_MODEMIS) dwc2_handle_mode_mismatch_intr(hsotg); if (gintsts & GINTSTS_OTGINT) @@ -565,6 +887,8 @@ irqreturn_t dwc2_handle_common_intr(void *dev) dwc2_handle_wakeup_detected_intr(hsotg); if (gintsts & GINTSTS_USBSUSP) dwc2_handle_usb_suspend_intr(hsotg); + if (gintsts & GINTSTS_LPMTRANRCVD) + dwc2_handle_lpm_intr(hsotg); if (gintsts & GINTSTS_PRTINT) { /* @@ -580,5 +904,6 @@ irqreturn_t dwc2_handle_common_intr(void *dev) } out: + spin_unlock(&hsotg->lock); return retval; } diff --git a/sys/dev/usb/dwc2/dwc2_hcd.c b/sys/dev/usb/dwc2/dwc2_hcd.c index a58de6bd4c2..8d5a2e265e5 100644 --- a/sys/dev/usb/dwc2/dwc2_hcd.c +++ b/sys/dev/usb/dwc2/dwc2_hcd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dwc2_hcd.c,v 1.26 2021/11/28 09:25:02 mglocker Exp $ */ +/* $OpenBSD: dwc2_hcd.c,v 1.27 2022/09/04 08:42:40 mglocker Exp $ */ /* $NetBSD: dwc2_hcd.c,v 1.15 2014/11/24 10:14:14 skrll Exp $ */ /* @@ -41,7 +41,6 @@ * This file contains the core HCD code, and implements the Linux hc_driver * API */ - #include #include #include @@ -63,6 +62,382 @@ #include #include +/* + * ========================================================================= + * Host Core Layer Functions + * ========================================================================= + */ + +/** + * dwc2_enable_common_interrupts() - Initializes the commmon interrupts, + * used in both device and host modes + * + * @hsotg: Programming view of the DWC_otg controller + */ +STATIC void dwc2_enable_common_interrupts(struct dwc2_hsotg *hsotg) +{ + u32 intmsk; + + /* Clear any pending OTG Interrupts */ + dwc2_writel(hsotg, 0xffffffff, GOTGINT); + + /* Clear any pending interrupts */ + dwc2_writel(hsotg, 0xffffffff, GINTSTS); + + /* Enable the interrupts in the GINTMSK */ + intmsk = GINTSTS_MODEMIS | GINTSTS_OTGINT; + + if (!hsotg->params.host_dma) + intmsk |= GINTSTS_RXFLVL; + if (!hsotg->params.external_id_pin_ctl) + intmsk |= GINTSTS_CONIDSTSCHNG; + + intmsk |= GINTSTS_WKUPINT | GINTSTS_USBSUSP | + GINTSTS_SESSREQINT; + + if (dwc2_is_device_mode(hsotg) && hsotg->params.lpm) + intmsk |= GINTSTS_LPMTRANRCVD; + + dwc2_writel(hsotg, intmsk, GINTMSK); +} + +STATIC int dwc2_gahbcfg_init(struct dwc2_hsotg *hsotg) +{ + struct dwc2_softc *sc = hsotg->hsotg_sc; + u32 ahbcfg = dwc2_readl(hsotg, GAHBCFG); + + switch (hsotg->hw_params.arch) { + case GHWCFG2_EXT_DMA_ARCH: + dev_dbg(hsotg->dev, "External DMA Mode\n"); + if (!sc->sc_set_dma_addr) { + dev_err(hsotg->dev, + "External DMA Mode not supported\n"); + return -EINVAL; + } + if (hsotg->params.ahbcfg != -1) { + ahbcfg &= GAHBCFG_CTRL_MASK; + ahbcfg |= hsotg->params.ahbcfg & + ~GAHBCFG_CTRL_MASK; + } + break; + + case GHWCFG2_INT_DMA_ARCH: + dev_dbg(hsotg->dev, "Internal DMA Mode\n"); + if (hsotg->params.ahbcfg != -1) { + ahbcfg &= GAHBCFG_CTRL_MASK; + ahbcfg |= hsotg->params.ahbcfg & + ~GAHBCFG_CTRL_MASK; + } + break; + + case GHWCFG2_SLAVE_ONLY_ARCH: + default: + dev_dbg(hsotg->dev, "Slave Only Mode\n"); + break; + } + + if (hsotg->params.host_dma) + ahbcfg |= GAHBCFG_DMA_EN; + else + hsotg->params.dma_desc_enable = false; + + dwc2_writel(hsotg, ahbcfg, GAHBCFG); + + return 0; +} + +STATIC void dwc2_gusbcfg_init(struct dwc2_hsotg *hsotg) +{ + u32 usbcfg; + + usbcfg = dwc2_readl(hsotg, GUSBCFG); + usbcfg &= ~(GUSBCFG_HNPCAP | GUSBCFG_SRPCAP); + + switch (hsotg->hw_params.op_mode) { + case GHWCFG2_OP_MODE_HNP_SRP_CAPABLE: + if (hsotg->params.otg_caps.hnp_support && + hsotg->params.otg_caps.srp_support) + usbcfg |= GUSBCFG_HNPCAP; +// fallthrough; + + case GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE: + case GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE: + case GHWCFG2_OP_MODE_SRP_CAPABLE_HOST: + if (hsotg->params.otg_caps.srp_support) + usbcfg |= GUSBCFG_SRPCAP; + break; + + case GHWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE: + case GHWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE: + case GHWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST: + default: + break; + } + + dwc2_writel(hsotg, usbcfg, GUSBCFG); +} + +static int dwc2_vbus_supply_init(struct dwc2_hsotg *hsotg) +{ +#if 0 + if (hsotg->vbus_supply) + return regulator_enable(hsotg->vbus_supply); +#endif + + return 0; +} + +static int dwc2_vbus_supply_exit(struct dwc2_hsotg *hsotg) +{ +#if 0 + if (hsotg->vbus_supply) + return regulator_disable(hsotg->vbus_supply); +#endif + + return 0; +} + +/** + * dwc2_enable_host_interrupts() - Enables the Host mode interrupts + * + * @hsotg: Programming view of DWC_otg controller + */ +void dwc2_enable_host_interrupts(struct dwc2_hsotg *hsotg) +{ + u32 intmsk; + + dev_dbg(hsotg->dev, "%s()\n", __func__); + + /* Disable all interrupts */ + dwc2_writel(hsotg, 0, GINTMSK); + dwc2_writel(hsotg, 0, HAINTMSK); + + /* Enable the common interrupts */ + dwc2_enable_common_interrupts(hsotg); + + /* Enable host mode interrupts without disturbing common interrupts */ + intmsk = dwc2_readl(hsotg, GINTMSK); + intmsk |= GINTSTS_DISCONNINT | GINTSTS_PRTINT | GINTSTS_HCHINT; + dwc2_writel(hsotg, intmsk, GINTMSK); +} + +/** + * dwc2_disable_host_interrupts() - Disables the Host Mode interrupts + * + * @hsotg: Programming view of DWC_otg controller + */ +void dwc2_disable_host_interrupts(struct dwc2_hsotg *hsotg) +{ + u32 intmsk = dwc2_readl(hsotg, GINTMSK); + + /* Disable host mode interrupts without disturbing common interrupts */ + intmsk &= ~(GINTSTS_SOF | GINTSTS_PRTINT | GINTSTS_HCHINT | + GINTSTS_PTXFEMP | GINTSTS_NPTXFEMP | GINTSTS_DISCONNINT); + dwc2_writel(hsotg, intmsk, GINTMSK); +} + +/* + * dwc2_calculate_dynamic_fifo() - Calculates the default fifo size + * For system that have a total fifo depth that is smaller than the default + * RX + TX fifo size. + * + * @hsotg: Programming view of DWC_otg controller + */ +STATIC void dwc2_calculate_dynamic_fifo(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *params = &hsotg->params; + struct dwc2_hw_params *hw = &hsotg->hw_params; + u32 rxfsiz, nptxfsiz, ptxfsiz, total_fifo_size; + + total_fifo_size = hw->total_fifo_size; + rxfsiz = params->host_rx_fifo_size; + nptxfsiz = params->host_nperio_tx_fifo_size; + ptxfsiz = params->host_perio_tx_fifo_size; + + /* + * Will use Method 2 defined in the DWC2 spec: minimum FIFO depth + * allocation with support for high bandwidth endpoints. Synopsys + * defines MPS(Max Packet size) for a periodic EP=1024, and for + * non-periodic as 512. + */ + if (total_fifo_size < (rxfsiz + nptxfsiz + ptxfsiz)) { + /* + * For Buffer DMA mode/Scatter Gather DMA mode + * 2 * ((Largest Packet size / 4) + 1 + 1) + n + * with n = number of host channel. + * 2 * ((1024/4) + 2) = 516 + */ + rxfsiz = 516 + hw->host_channels; + + /* + * min non-periodic tx fifo depth + * 2 * (largest non-periodic USB packet used / 4) + * 2 * (512/4) = 256 + */ + nptxfsiz = 256; + + /* + * min periodic tx fifo depth + * (largest packet size*MC)/4 + * (1024 * 3)/4 = 768 + */ + ptxfsiz = 768; + + params->host_rx_fifo_size = rxfsiz; + params->host_nperio_tx_fifo_size = nptxfsiz; + params->host_perio_tx_fifo_size = ptxfsiz; + } + + /* + * If the summation of RX, NPTX and PTX fifo sizes is still + * bigger than the total_fifo_size, then we have a problem. + * + * We won't be able to allocate as many endpoints. Right now, + * we're just printing an error message, but ideally this FIFO + * allocation algorithm would be improved in the future. + * + * FIXME improve this FIFO allocation algorithm. + */ + if (unlikely(total_fifo_size < (rxfsiz + nptxfsiz + ptxfsiz))) + dev_err(hsotg->dev, "invalid fifo sizes\n"); +} + +STATIC void dwc2_config_fifos(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *params = &hsotg->params; + u32 nptxfsiz, hptxfsiz, dfifocfg, grxfsiz; + + if (!params->enable_dynamic_fifo) + return; + + dwc2_calculate_dynamic_fifo(hsotg); + + /* Rx FIFO */ + grxfsiz = dwc2_readl(hsotg, GRXFSIZ); + dev_dbg(hsotg->dev, "initial grxfsiz=%08x\n", grxfsiz); + grxfsiz &= ~GRXFSIZ_DEPTH_MASK; + grxfsiz |= params->host_rx_fifo_size << + GRXFSIZ_DEPTH_SHIFT & GRXFSIZ_DEPTH_MASK; + dwc2_writel(hsotg, grxfsiz, GRXFSIZ); + dev_dbg(hsotg->dev, "new grxfsiz=%08x\n", + dwc2_readl(hsotg, GRXFSIZ)); + + /* Non-periodic Tx FIFO */ + dev_dbg(hsotg->dev, "initial gnptxfsiz=%08x\n", + dwc2_readl(hsotg, GNPTXFSIZ)); + nptxfsiz = params->host_nperio_tx_fifo_size << + FIFOSIZE_DEPTH_SHIFT & FIFOSIZE_DEPTH_MASK; + nptxfsiz |= params->host_rx_fifo_size << + FIFOSIZE_STARTADDR_SHIFT & FIFOSIZE_STARTADDR_MASK; + dwc2_writel(hsotg, nptxfsiz, GNPTXFSIZ); + dev_dbg(hsotg->dev, "new gnptxfsiz=%08x\n", + dwc2_readl(hsotg, GNPTXFSIZ)); + + /* Periodic Tx FIFO */ + dev_dbg(hsotg->dev, "initial hptxfsiz=%08x\n", + dwc2_readl(hsotg, HPTXFSIZ)); + hptxfsiz = params->host_perio_tx_fifo_size << + FIFOSIZE_DEPTH_SHIFT & FIFOSIZE_DEPTH_MASK; + hptxfsiz |= (params->host_rx_fifo_size + + params->host_nperio_tx_fifo_size) << + FIFOSIZE_STARTADDR_SHIFT & FIFOSIZE_STARTADDR_MASK; + dwc2_writel(hsotg, hptxfsiz, HPTXFSIZ); + dev_dbg(hsotg->dev, "new hptxfsiz=%08x\n", + dwc2_readl(hsotg, HPTXFSIZ)); + + if (hsotg->params.en_multiple_tx_fifo && + hsotg->hw_params.snpsid >= DWC2_CORE_REV_2_91a) { + /* + * This feature was implemented in 2.91a version + * Global DFIFOCFG calculation for Host mode - + * include RxFIFO, NPTXFIFO and HPTXFIFO + */ + dfifocfg = dwc2_readl(hsotg, GDFIFOCFG); + dfifocfg &= ~GDFIFOCFG_EPINFOBASE_MASK; + dfifocfg |= (params->host_rx_fifo_size + + params->host_nperio_tx_fifo_size + + params->host_perio_tx_fifo_size) << + GDFIFOCFG_EPINFOBASE_SHIFT & + GDFIFOCFG_EPINFOBASE_MASK; + dwc2_writel(hsotg, dfifocfg, GDFIFOCFG); + } +} + +/** + * dwc2_calc_frame_interval() - Calculates the correct frame Interval value for + * the HFIR register according to PHY type and speed + * + * @hsotg: Programming view of DWC_otg controller + * + * NOTE: The caller can modify the value of the HFIR register only after the + * Port Enable bit of the Host Port Control and Status register (HPRT.EnaPort) + * has been set + */ +u32 dwc2_calc_frame_interval(struct dwc2_hsotg *hsotg) +{ + u32 usbcfg; + u32 hprt0; + int clock = 60; /* default value */ + + usbcfg = dwc2_readl(hsotg, GUSBCFG); + hprt0 = dwc2_readl(hsotg, HPRT0); + + if (!(usbcfg & GUSBCFG_PHYSEL) && (usbcfg & GUSBCFG_ULPI_UTMI_SEL) && + !(usbcfg & GUSBCFG_PHYIF16)) + clock = 60; + if ((usbcfg & GUSBCFG_PHYSEL) && hsotg->hw_params.fs_phy_type == + GHWCFG2_FS_PHY_TYPE_SHARED_ULPI) + clock = 48; + if (!(usbcfg & GUSBCFG_PHY_LP_CLK_SEL) && !(usbcfg & GUSBCFG_PHYSEL) && + !(usbcfg & GUSBCFG_ULPI_UTMI_SEL) && (usbcfg & GUSBCFG_PHYIF16)) + clock = 30; + if (!(usbcfg & GUSBCFG_PHY_LP_CLK_SEL) && !(usbcfg & GUSBCFG_PHYSEL) && + !(usbcfg & GUSBCFG_ULPI_UTMI_SEL) && !(usbcfg & GUSBCFG_PHYIF16)) + clock = 60; + if ((usbcfg & GUSBCFG_PHY_LP_CLK_SEL) && !(usbcfg & GUSBCFG_PHYSEL) && + !(usbcfg & GUSBCFG_ULPI_UTMI_SEL) && (usbcfg & GUSBCFG_PHYIF16)) + clock = 48; + if ((usbcfg & GUSBCFG_PHYSEL) && !(usbcfg & GUSBCFG_PHYIF16) && + hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_SHARED_UTMI) + clock = 48; + if ((usbcfg & GUSBCFG_PHYSEL) && + hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED) + clock = 48; + + if ((hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT == HPRT0_SPD_HIGH_SPEED) + /* High speed case */ + return 125 * clock - 1; + + /* FS/LS case */ + return 1000 * clock - 1; +} + +/** + * dwc2_read_packet() - Reads a packet from the Rx FIFO into the destination + * buffer + * + * @hsotg: Programming view of DWC_otg controller + * @dest: Destination buffer for the packet + * @bytes: Number of bytes to copy to the destination + */ +void dwc2_read_packet(struct dwc2_hsotg *hsotg, u8 *dest, u16 bytes) +{ + u32 *data_buf = (u32 *)dest; + int word_count = (bytes + 3) / 4; + int i; + + /* + * Todo: Account for the case where dest is not dword aligned. This + * requires reading data from the FIFO into a u32 temp buffer, then + * moving it into the data buffer. + */ + + dev_vdbg(hsotg->dev, "%s(%p,%p,%d)\n", __func__, hsotg, dest, bytes); + + for (i = 0; i < word_count; i++, data_buf++) + *data_buf = dwc2_readl(hsotg, HCFIFO(0)); +} + /** * dwc2_dump_channel_info() - Prints the state of a host channel * @@ -74,11 +449,11 @@ * NOTE: This function will be removed once the peripheral controller code * is integrated and the driver is stable */ -#ifdef VERBOSE_DEBUG STATIC void dwc2_dump_channel_info(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan) { - int num_channels = hsotg->core_params->host_channels; +#ifdef VERBOSE_DEBUG + int num_channels = hsotg->params.host_channels; struct dwc2_qh *qh; u32 hcchar; u32 hcsplt; @@ -86,13 +461,13 @@ STATIC void dwc2_dump_channel_info(struct dwc2_hsotg *hsotg, u32 hc_dma; int i; - if (chan == NULL) + if (!chan) return; - hcchar = DWC2_READ_4(hsotg, HCCHAR(chan->hc_num)); - hcsplt = DWC2_READ_4(hsotg, HCSPLT(chan->hc_num)); - hctsiz = DWC2_READ_4(hsotg, HCTSIZ(chan->hc_num)); - hc_dma = DWC2_READ_4(hsotg, HCDMA(chan->hc_num)); + hcchar = dwc2_readl(hsotg, HCCHAR(chan->hc_num)); + hcsplt = dwc2_readl(hsotg, HCSPLT(chan->hc_num)); + hctsiz = dwc2_readl(hsotg, HCTSIZ(chan->hc_num)); + hc_dma = dwc2_readl(hsotg, HCDMA(chan->hc_num)); dev_dbg(hsotg->dev, " Assigned to channel %p:\n", chan); dev_dbg(hsotg->dev, " hcchar 0x%08x, hcsplt 0x%08x\n", @@ -125,2087 +500,4683 @@ STATIC void dwc2_dump_channel_info(struct dwc2_hsotg *hsotg, dev_dbg(hsotg->dev, " %p\n", qh); dev_dbg(hsotg->dev, " Channels:\n"); for (i = 0; i < num_channels; i++) { - struct dwc2_host_chan *ch = hsotg->hc_ptr_array[i]; + struct dwc2_host_chan *chan = hsotg->hc_ptr_array[i]; - dev_dbg(hsotg->dev, " %2d: %p\n", i, ch); + dev_dbg(hsotg->dev, " %2d: %p\n", i, chan); } -} #endif /* VERBOSE_DEBUG */ +} + +static int _dwc2_hcd_start(struct dwc2_hsotg *hsotg); + +static void dwc2_host_start(struct dwc2_hsotg *hsotg) +{ +#if 0 + struct usb_hcd *hcd = dwc2_hsotg_to_hcd(hsotg); + + hcd->self.is_b_host = dwc2_hcd_is_b_host(hsotg); +#endif + _dwc2_hcd_start(hsotg); +} + +static void dwc2_host_disconnect(struct dwc2_hsotg *hsotg) +{ +#if 0 + struct usb_hcd *hcd = dwc2_hsotg_to_hcd(hsotg); + + hcd->self.is_b_host = 0; +#endif +} + +void dwc2_host_hub_info(struct dwc2_hsotg *hsotg, void *context, + int *hub_addr, int *hub_port) +{ + struct usbd_xfer *xfer = context; + struct dwc2_pipe *dpipe = DWC2_XFER2DPIPE(xfer); + struct usbd_device *dev = dpipe->pipe.device; + + if (dev->myhsport->tt) + *hub_addr = dev->myhsport->parent->address; + else + *hub_addr = 0; + *hub_port = dev->myhsport->portno; +} /* - * Processes all the URBs in a single list of QHs. Completes them with - * -ETIMEDOUT and frees the QTD. - * - * Must be called with interrupt disabled and spinlock held + * ========================================================================= + * Low Level Host Channel Access Functions + * ========================================================================= */ -STATIC void dwc2_kill_urbs_in_qh_list(struct dwc2_hsotg *hsotg, - struct list_head *qh_list) + +STATIC void dwc2_hc_enable_slave_ints(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan) { - struct dwc2_qh *qh, *qh_tmp; - struct dwc2_qtd *qtd, *qtd_tmp; + u32 hcintmsk = HCINTMSK_CHHLTD; - list_for_each_entry_safe(qh, qh_tmp, qh_list, qh_list_entry) { - list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, - qtd_list_entry) { - dwc2_host_complete(hsotg, qtd, -ECONNRESET); - dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); + switch (chan->ep_type) { + case USB_ENDPOINT_XFER_CONTROL: + case USB_ENDPOINT_XFER_BULK: + dev_vdbg(hsotg->dev, "control/bulk\n"); + hcintmsk |= HCINTMSK_XFERCOMPL; + hcintmsk |= HCINTMSK_STALL; + hcintmsk |= HCINTMSK_XACTERR; + hcintmsk |= HCINTMSK_DATATGLERR; + if (chan->ep_is_in) { + hcintmsk |= HCINTMSK_BBLERR; + } else { + hcintmsk |= HCINTMSK_NAK; + hcintmsk |= HCINTMSK_NYET; + if (chan->do_ping) + hcintmsk |= HCINTMSK_ACK; } - } -} -STATIC void dwc2_qh_list_free(struct dwc2_hsotg *hsotg, - struct list_head *qh_list) -{ - struct dwc2_qtd *qtd, *qtd_tmp; - struct dwc2_qh *qh, *qh_tmp; - unsigned long flags; + if (chan->do_split) { + hcintmsk |= HCINTMSK_NAK; + if (chan->complete_split) + hcintmsk |= HCINTMSK_NYET; + else + hcintmsk |= HCINTMSK_ACK; + } - if (!qh_list->next) - /* The list hasn't been initialized yet */ - return; + if (chan->error_state) + hcintmsk |= HCINTMSK_ACK; + break; - spin_lock_irqsave(&hsotg->lock, flags); + case USB_ENDPOINT_XFER_INT: + if (dbg_perio()) + dev_vdbg(hsotg->dev, "intr\n"); + hcintmsk |= HCINTMSK_XFERCOMPL; + hcintmsk |= HCINTMSK_NAK; + hcintmsk |= HCINTMSK_STALL; + hcintmsk |= HCINTMSK_XACTERR; + hcintmsk |= HCINTMSK_DATATGLERR; + hcintmsk |= HCINTMSK_FRMOVRUN; + + if (chan->ep_is_in) + hcintmsk |= HCINTMSK_BBLERR; + if (chan->error_state) + hcintmsk |= HCINTMSK_ACK; + if (chan->do_split) { + if (chan->complete_split) + hcintmsk |= HCINTMSK_NYET; + else + hcintmsk |= HCINTMSK_ACK; + } + break; - /* Ensure there are no QTDs or URBs left */ - dwc2_kill_urbs_in_qh_list(hsotg, qh_list); + case USB_ENDPOINT_XFER_ISOC: + if (dbg_perio()) + dev_vdbg(hsotg->dev, "isoc\n"); + hcintmsk |= HCINTMSK_XFERCOMPL; + hcintmsk |= HCINTMSK_FRMOVRUN; + hcintmsk |= HCINTMSK_ACK; + + if (chan->ep_is_in) { + hcintmsk |= HCINTMSK_XACTERR; + hcintmsk |= HCINTMSK_BBLERR; + } + break; + default: + dev_err(hsotg->dev, "## Unknown EP type ##\n"); + break; + } - list_for_each_entry_safe(qh, qh_tmp, qh_list, qh_list_entry) { - dwc2_hcd_qh_unlink(hsotg, qh); + dwc2_writel(hsotg, hcintmsk, HCINTMSK(chan->hc_num)); + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "set HCINTMSK to %08x\n", hcintmsk); +} - /* Free each QTD in the QH's QTD list */ - list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, - qtd_list_entry) - dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); +STATIC void dwc2_hc_enable_dma_ints(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan) +{ + u32 hcintmsk = HCINTMSK_CHHLTD; - spin_unlock_irqrestore(&hsotg->lock, flags); - dwc2_hcd_qh_free(hsotg, qh); - spin_lock_irqsave(&hsotg->lock, flags); + /* + * For Descriptor DMA mode core halts the channel on AHB error. + * Interrupt is not required. + */ + if (!hsotg->params.dma_desc_enable) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "desc DMA disabled\n"); + hcintmsk |= HCINTMSK_AHBERR; + } else { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "desc DMA enabled\n"); + if (chan->ep_type == USB_ENDPOINT_XFER_ISOC) + hcintmsk |= HCINTMSK_XFERCOMPL; } - spin_unlock_irqrestore(&hsotg->lock, flags); + if (chan->error_state && !chan->do_split && + chan->ep_type != USB_ENDPOINT_XFER_ISOC) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "setting ACK\n"); + hcintmsk |= HCINTMSK_ACK; + if (chan->ep_is_in) { + hcintmsk |= HCINTMSK_DATATGLERR; + if (chan->ep_type != USB_ENDPOINT_XFER_INT) + hcintmsk |= HCINTMSK_NAK; + } + } + + dwc2_writel(hsotg, hcintmsk, HCINTMSK(chan->hc_num)); + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "set HCINTMSK to %08x\n", hcintmsk); } -/* - * Responds with an error status of -ETIMEDOUT to all URBs in the non-periodic - * and periodic schedules. The QTD associated with each URB is removed from - * the schedule and freed. This function may be called when a disconnect is - * detected or when the HCD is being stopped. - * - * Must be called with interrupt disabled and spinlock held - */ -STATIC void dwc2_kill_all_urbs(struct dwc2_hsotg *hsotg) +STATIC void dwc2_hc_enable_ints(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan) { - dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->non_periodic_sched_inactive); - dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->non_periodic_sched_waiting); - dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->non_periodic_sched_active); - dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_inactive); - dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_ready); - dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_assigned); - dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_queued); + u32 intmsk; + + if (hsotg->params.host_dma) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "DMA enabled\n"); + dwc2_hc_enable_dma_ints(hsotg, chan); + } else { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "DMA disabled\n"); + dwc2_hc_enable_slave_ints(hsotg, chan); + } + + /* Enable the top level host channel interrupt */ + intmsk = dwc2_readl(hsotg, HAINTMSK); + intmsk |= 1 << chan->hc_num; + dwc2_writel(hsotg, intmsk, HAINTMSK); + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "set HAINTMSK to %08x\n", intmsk); + + /* Make sure host channel interrupts are enabled */ + intmsk = dwc2_readl(hsotg, GINTMSK); + intmsk |= GINTSTS_HCHINT; + dwc2_writel(hsotg, intmsk, GINTMSK); + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "set GINTMSK to %08x\n", intmsk); } /** - * dwc2_hcd_start() - Starts the HCD when switching to Host mode + * dwc2_hc_init() - Prepares a host channel for transferring packets to/from + * a specific endpoint * - * @hsotg: Pointer to struct dwc2_hsotg + * @hsotg: Programming view of DWC_otg controller + * @chan: Information needed to initialize the host channel + * + * The HCCHARn register is set up with the characteristics specified in chan. + * Host channel interrupts that may need to be serviced while this transfer is + * in progress are enabled. */ -void dwc2_hcd_start(struct dwc2_hsotg *hsotg) +void dwc2_hc_init(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan) { - u32 hprt0; + u8 hc_num = chan->hc_num; + u32 hcintmsk; + u32 hcchar; + u32 hcsplt = 0; - if (hsotg->op_state == OTG_STATE_B_HOST) { - /* - * Reset the port. During a HNP mode switch the reset - * needs to occur within 1ms and have a duration of at - * least 50ms. - */ - hprt0 = dwc2_read_hprt0(hsotg); - hprt0 |= HPRT0_RST; - DWC2_WRITE_4(hsotg, HPRT0, hprt0); + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + /* Clear old interrupt conditions for this host channel */ + hcintmsk = 0xffffffff; + hcintmsk &= ~HCINTMSK_RESERVED14_31; + dwc2_writel(hsotg, hcintmsk, HCINT(hc_num)); + + /* Enable channel interrupts required for this transfer */ + dwc2_hc_enable_ints(hsotg, chan); + + /* + * Program the HCCHARn register with the endpoint characteristics for + * the current transfer + */ + hcchar = chan->dev_addr << HCCHAR_DEVADDR_SHIFT & HCCHAR_DEVADDR_MASK; + hcchar |= chan->ep_num << HCCHAR_EPNUM_SHIFT & HCCHAR_EPNUM_MASK; + if (chan->ep_is_in) + hcchar |= HCCHAR_EPDIR; + if (chan->speed == USB_SPEED_LOW) + hcchar |= HCCHAR_LSPDDEV; + hcchar |= chan->ep_type << HCCHAR_EPTYPE_SHIFT & HCCHAR_EPTYPE_MASK; + hcchar |= chan->max_packet << HCCHAR_MPS_SHIFT & HCCHAR_MPS_MASK; + dwc2_writel(hsotg, hcchar, HCCHAR(hc_num)); + if (dbg_hc(chan)) { + dev_vdbg(hsotg->dev, "set HCCHAR(%d) to %08x\n", + hc_num, hcchar); + + dev_vdbg(hsotg->dev, "%s: Channel %d\n", + __func__, hc_num); + dev_vdbg(hsotg->dev, " Dev Addr: %d\n", + chan->dev_addr); + dev_vdbg(hsotg->dev, " Ep Num: %d\n", + chan->ep_num); + dev_vdbg(hsotg->dev, " Is In: %d\n", + chan->ep_is_in); + dev_vdbg(hsotg->dev, " Is Low Speed: %d\n", + chan->speed == USB_SPEED_LOW); + dev_vdbg(hsotg->dev, " Ep Type: %d\n", + chan->ep_type); + dev_vdbg(hsotg->dev, " Max Pkt: %d\n", + chan->max_packet); } - queue_delayed_work(hsotg->wq_otg, &hsotg->start_work, - msecs_to_jiffies(50)); + /* Program the HCSPLT register for SPLITs */ + if (chan->do_split) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, + "Programming HC %d with split --> %s\n", + hc_num, + chan->complete_split ? "CSPLIT" : "SSPLIT"); + if (chan->complete_split) + hcsplt |= HCSPLT_COMPSPLT; + hcsplt |= chan->xact_pos << HCSPLT_XACTPOS_SHIFT & + HCSPLT_XACTPOS_MASK; + hcsplt |= chan->hub_addr << HCSPLT_HUBADDR_SHIFT & + HCSPLT_HUBADDR_MASK; + hcsplt |= chan->hub_port << HCSPLT_PRTADDR_SHIFT & + HCSPLT_PRTADDR_MASK; + if (dbg_hc(chan)) { + dev_vdbg(hsotg->dev, " comp split %d\n", + chan->complete_split); + dev_vdbg(hsotg->dev, " xact pos %d\n", + chan->xact_pos); + dev_vdbg(hsotg->dev, " hub addr %d\n", + chan->hub_addr); + dev_vdbg(hsotg->dev, " hub port %d\n", + chan->hub_port); + dev_vdbg(hsotg->dev, " is_in %d\n", + chan->ep_is_in); + dev_vdbg(hsotg->dev, " Max Pkt %d\n", + chan->max_packet); + dev_vdbg(hsotg->dev, " xferlen %d\n", + chan->xfer_len); + } + } + + dwc2_writel(hsotg, hcsplt, HCSPLT(hc_num)); } -/* Must be called with interrupt disabled and spinlock held */ -STATIC void dwc2_hcd_cleanup_channels(struct dwc2_hsotg *hsotg) +/** + * dwc2_hc_halt() - Attempts to halt a host channel + * + * @hsotg: Controller register interface + * @chan: Host channel to halt + * @halt_status: Reason for halting the channel + * + * This function should only be called in Slave mode or to abort a transfer in + * either Slave mode or DMA mode. Under normal circumstances in DMA mode, the + * controller halts the channel when the transfer is complete or a condition + * occurs that requires application intervention. + * + * In slave mode, checks for a free request queue entry, then sets the Channel + * Enable and Channel Disable bits of the Host Channel Characteristics + * register of the specified channel to intiate the halt. If there is no free + * request queue entry, sets only the Channel Disable bit of the HCCHARn + * register to flush requests for this channel. In the latter case, sets a + * flag to indicate that the host channel needs to be halted when a request + * queue slot is open. + * + * In DMA mode, always sets the Channel Enable and Channel Disable bits of the + * HCCHARn register. The controller ensures there is space in the request + * queue before submitting the halt request. + * + * Some time may elapse before the core flushes any posted requests for this + * host channel and halts. The Channel Halted interrupt handler completes the + * deactivation of the host channel. + */ +void dwc2_hc_halt(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan, + enum dwc2_halt_status halt_status) { - int num_channels = hsotg->core_params->host_channels; - struct dwc2_host_chan *channel; - u32 hcchar; - int i; + u32 nptxsts, hptxsts, hcchar; - if (hsotg->core_params->dma_enable <= 0) { - /* Flush out any channel requests in slave mode */ - for (i = 0; i < num_channels; i++) { - channel = hsotg->hc_ptr_array[i]; - if (!list_empty(&channel->hc_list_entry)) - continue; - hcchar = DWC2_READ_4(hsotg, HCCHAR(i)); - if (hcchar & HCCHAR_CHENA) { - hcchar &= ~(HCCHAR_CHENA | HCCHAR_EPDIR); - hcchar |= HCCHAR_CHDIS; - DWC2_WRITE_4(hsotg, HCCHAR(i), hcchar); - } + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + /* + * In buffer DMA or external DMA mode channel can't be halted + * for non-split periodic channels. At the end of the next + * uframe/frame (in the worst case), the core generates a channel + * halted and disables the channel automatically. + */ + if ((hsotg->params.g_dma && !hsotg->params.g_dma_desc) || + hsotg->hw_params.arch == GHWCFG2_EXT_DMA_ARCH) { + if (!chan->do_split && + (chan->ep_type == USB_ENDPOINT_XFER_ISOC || + chan->ep_type == USB_ENDPOINT_XFER_INT)) { + dev_err(hsotg->dev, "%s() Channel can't be halted\n", + __func__); + return; } } - for (i = 0; i < num_channels; i++) { - channel = hsotg->hc_ptr_array[i]; - if (!list_empty(&channel->hc_list_entry)) - continue; - hcchar = DWC2_READ_4(hsotg, HCCHAR(i)); - if (hcchar & HCCHAR_CHENA) { - /* Halt the channel */ - hcchar |= HCCHAR_CHDIS; - DWC2_WRITE_4(hsotg, HCCHAR(i), hcchar); - } + if (halt_status == DWC2_HC_XFER_NO_HALT_STATUS) + dev_err(hsotg->dev, "!!! halt_status = %d !!!\n", halt_status); - dwc2_hc_cleanup(hsotg, channel); - list_add_tail(&channel->hc_list_entry, &hsotg->free_hc_list); + if (halt_status == DWC2_HC_XFER_URB_DEQUEUE || + halt_status == DWC2_HC_XFER_AHB_ERR) { /* - * Added for Descriptor DMA to prevent channel double cleanup in - * release_channel_ddma(), which is called from ep_disable when - * device disconnects + * Disable all channel interrupts except Ch Halted. The QTD + * and QH state associated with this transfer has been cleared + * (in the case of URB_DEQUEUE), so the channel needs to be + * shut down carefully to prevent crashes. */ - channel->qh = NULL; + u32 hcintmsk = HCINTMSK_CHHLTD; + + dev_vdbg(hsotg->dev, "dequeue/error\n"); + dwc2_writel(hsotg, hcintmsk, HCINTMSK(chan->hc_num)); + + /* + * Make sure no other interrupts besides halt are currently + * pending. Handling another interrupt could cause a crash due + * to the QTD and QH state. + */ + dwc2_writel(hsotg, ~hcintmsk, HCINT(chan->hc_num)); + + /* + * Make sure the halt status is set to URB_DEQUEUE or AHB_ERR + * even if the channel was already halted for some other + * reason + */ + chan->halt_status = halt_status; + + hcchar = dwc2_readl(hsotg, HCCHAR(chan->hc_num)); + if (!(hcchar & HCCHAR_CHENA)) { + /* + * The channel is either already halted or it hasn't + * started yet. In DMA mode, the transfer may halt if + * it finishes normally or a condition occurs that + * requires driver intervention. Don't want to halt + * the channel again. In either Slave or DMA mode, + * it's possible that the transfer has been assigned + * to a channel, but not started yet when an URB is + * dequeued. Don't want to halt a channel that hasn't + * started yet. + */ + return; + } } - /* All channels have been freed, mark them available */ - if (hsotg->core_params->uframe_sched > 0) { - hsotg->available_host_channels = - hsotg->core_params->host_channels; + if (chan->halt_pending) { + /* + * A halt has already been issued for this channel. This might + * happen when a transfer is aborted by a higher level in + * the stack. + */ + dev_vdbg(hsotg->dev, + "*** %s: Channel %d, chan->halt_pending already set ***\n", + __func__, chan->hc_num); + return; + } + + hcchar = dwc2_readl(hsotg, HCCHAR(chan->hc_num)); + + /* No need to set the bit in DDMA for disabling the channel */ + /* TODO check it everywhere channel is disabled */ + if (!hsotg->params.dma_desc_enable) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "desc DMA disabled\n"); + hcchar |= HCCHAR_CHENA; } else { - hsotg->non_periodic_channels = 0; - hsotg->periodic_channels = 0; + if (dbg_hc(chan)) + dev_dbg(hsotg->dev, "desc DMA enabled\n"); + } + hcchar |= HCCHAR_CHDIS; + + if (!hsotg->params.host_dma) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "DMA not enabled\n"); + hcchar |= HCCHAR_CHENA; + + /* Check for space in the request queue to issue the halt */ + if (chan->ep_type == USB_ENDPOINT_XFER_CONTROL || + chan->ep_type == USB_ENDPOINT_XFER_BULK) { + dev_vdbg(hsotg->dev, "control/bulk\n"); + nptxsts = dwc2_readl(hsotg, GNPTXSTS); + if ((nptxsts & TXSTS_QSPCAVAIL_MASK) == 0) { + dev_vdbg(hsotg->dev, "Disabling channel\n"); + hcchar &= ~HCCHAR_CHENA; + } + } else { + if (dbg_perio()) + dev_vdbg(hsotg->dev, "isoc/intr\n"); + hptxsts = dwc2_readl(hsotg, HPTXSTS); + if ((hptxsts & TXSTS_QSPCAVAIL_MASK) == 0 || + hsotg->queuing_high_bandwidth) { + if (dbg_perio()) + dev_vdbg(hsotg->dev, "Disabling channel\n"); + hcchar &= ~HCCHAR_CHENA; + } + } + } else { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "DMA enabled\n"); + } + + dwc2_writel(hsotg, hcchar, HCCHAR(chan->hc_num)); + chan->halt_status = halt_status; + + if (hcchar & HCCHAR_CHENA) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "Channel enabled\n"); + chan->halt_pending = 1; + chan->halt_on_queue = 0; + } else { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "Channel disabled\n"); + chan->halt_on_queue = 1; + } + + if (dbg_hc(chan)) { + dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, + chan->hc_num); + dev_vdbg(hsotg->dev, " hcchar: 0x%08x\n", + hcchar); + dev_vdbg(hsotg->dev, " halt_pending: %d\n", + chan->halt_pending); + dev_vdbg(hsotg->dev, " halt_on_queue: %d\n", + chan->halt_on_queue); + dev_vdbg(hsotg->dev, " halt_status: %d\n", + chan->halt_status); } } /** - * dwc2_hcd_connect() - Handles connect of the HCD + * dwc2_hc_cleanup() - Clears the transfer state for a host channel * - * @hsotg: Pointer to struct dwc2_hsotg + * @hsotg: Programming view of DWC_otg controller + * @chan: Identifies the host channel to clean up * - * Must be called with interrupt disabled and spinlock held + * This function is normally called after a transfer is done and the host + * channel is being released */ -void dwc2_hcd_connect(struct dwc2_hsotg *hsotg) +void dwc2_hc_cleanup(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan) { - if (hsotg->lx_state != DWC2_L0) - usb_hcd_resume_root_hub(hsotg->priv); + u32 hcintmsk; - hsotg->flags.b.port_connect_status_change = 1; - hsotg->flags.b.port_connect_status = 1; + chan->xfer_started = 0; + + list_del_init(&chan->split_order_list_entry); + + /* + * Clear channel interrupt enables and any unhandled channel interrupt + * conditions + */ + dwc2_writel(hsotg, 0, HCINTMSK(chan->hc_num)); + hcintmsk = 0xffffffff; + hcintmsk &= ~HCINTMSK_RESERVED14_31; + dwc2_writel(hsotg, hcintmsk, HCINT(chan->hc_num)); } /** - * dwc2_hcd_disconnect() - Handles disconnect of the HCD + * dwc2_hc_set_even_odd_frame() - Sets the channel property that indicates in + * which frame a periodic transfer should occur * - * @hsotg: Pointer to struct dwc2_hsotg - * @force: If true, we won't try to reconnect even if we see device connected. + * @hsotg: Programming view of DWC_otg controller + * @chan: Identifies the host channel to set up and its properties + * @hcchar: Current value of the HCCHAR register for the specified host channel * - * Must be called with interrupt disabled and spinlock held + * This function has no effect on non-periodic transfers */ -void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force) +STATIC void dwc2_hc_set_even_odd_frame(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, u32 *hcchar) { - u32 intr; - u32 hprt0; + if (chan->ep_type == USB_ENDPOINT_XFER_INT || + chan->ep_type == USB_ENDPOINT_XFER_ISOC) { + int host_speed; + int xfer_ns; + int xfer_us; + int bytes_in_fifo; + u16 fifo_space; + u16 frame_number; + u16 wire_frame; - /* Set status flags for the hub driver */ - hsotg->flags.b.port_connect_status_change = 1; - hsotg->flags.b.port_connect_status = 0; + /* + * Try to figure out if we're an even or odd frame. If we set + * even and the current frame number is even the the transfer + * will happen immediately. Similar if both are odd. If one is + * even and the other is odd then the transfer will happen when + * the frame number ticks. + * + * There's a bit of a balancing act to get this right. + * Sometimes we may want to send data in the current frame (AK + * right away). We might want to do this if the frame number + * _just_ ticked, but we might also want to do this in order + * to continue a split transaction that happened late in a + * microframe (so we didn't know to queue the next transfer + * until the frame number had ticked). The problem is that we + * need a lot of knowledge to know if there's actually still + * time to send things or if it would be better to wait until + * the next frame. + * + * We can look at how much time is left in the current frame + * and make a guess about whether we'll have time to transfer. + * We'll do that. + */ - /* - * Shutdown any transfers in process by clearing the Tx FIFO Empty - * interrupt mask and status bits and disabling subsequent host - * channel interrupts. - */ - intr = DWC2_READ_4(hsotg, GINTMSK); - intr &= ~(GINTSTS_NPTXFEMP | GINTSTS_PTXFEMP | GINTSTS_HCHINT); - DWC2_WRITE_4(hsotg, GINTMSK, intr); - intr = GINTSTS_NPTXFEMP | GINTSTS_PTXFEMP | GINTSTS_HCHINT; - DWC2_WRITE_4(hsotg, GINTSTS, intr); + /* Get speed host is running at */ + host_speed = (chan->speed != USB_SPEED_HIGH && + !chan->do_split) ? chan->speed : USB_SPEED_HIGH; - /* - * Turn off the vbus power only if the core has transitioned to device - * mode. If still in host mode, need to keep power on to detect a - * reconnection. - */ - if (dwc2_is_device_mode(hsotg)) { - if (hsotg->op_state != OTG_STATE_A_SUSPEND) { - dev_dbg(hsotg->dev, "Disconnect: PortPower off\n"); - DWC2_WRITE_4(hsotg, HPRT0, 0); - } + /* See how many bytes are in the periodic FIFO right now */ + fifo_space = (dwc2_readl(hsotg, HPTXSTS) & + TXSTS_FSPCAVAIL_MASK) >> TXSTS_FSPCAVAIL_SHIFT; + bytes_in_fifo = sizeof(u32) * + (hsotg->params.host_perio_tx_fifo_size - + fifo_space); - dwc2_disable_host_interrupts(hsotg); - } + /* + * Roughly estimate bus time for everything in the periodic + * queue + our new transfer. This is "rough" because we're + * using a function that makes takes into account IN/OUT + * and INT/ISO and we're just slamming in one value for all + * transfers. This should be an over-estimate and that should + * be OK, but we can probably tighten it. + */ + xfer_ns = dwc2_usb_calc_bus_time(host_speed, false, false, + chan->xfer_len + bytes_in_fifo); + xfer_us = NS_TO_US(xfer_ns); - /* Respond with an error status to all URBs in the schedule */ - dwc2_kill_all_urbs(hsotg); + /* See what frame number we'll be at by the time we finish */ + frame_number = dwc2_hcd_get_future_frame_number(hsotg, xfer_us); - if (dwc2_is_host_mode(hsotg)) - /* Clean up any host channels that were in use */ - dwc2_hcd_cleanup_channels(hsotg); + /* This is when we were scheduled to be on the wire */ + wire_frame = dwc2_frame_num_inc(chan->qh->next_active_frame, 1); - dwc2_host_disconnect(hsotg); + /* + * If we'd finish _after_ the frame we're scheduled in then + * it's hopeless. Just schedule right away and hope for the + * best. Note that it _might_ be wise to call back into the + * scheduler to pick a better frame, but this is better than + * nothing. + */ + if (dwc2_frame_num_gt(frame_number, wire_frame)) { + dwc2_sch_vdbg(hsotg, + "QH=%p EO MISS fr=%04x=>%04x (%+d)\n", + chan->qh, wire_frame, frame_number, + dwc2_frame_num_dec(frame_number, + wire_frame)); + wire_frame = frame_number; - dwc2_root_intr(hsotg->hsotg_sc); + /* + * We picked a different frame number; communicate this + * back to the scheduler so it doesn't try to schedule + * another in the same frame. + * + * Remember that next_active_frame is 1 before the wire + * frame. + */ + chan->qh->next_active_frame = + dwc2_frame_num_dec(frame_number, 1); + } - /* - * Add an extra check here to see if we're actually connected but - * we don't have a detection interrupt pending. This can happen if: - * 1. hardware sees connect - * 2. hardware sees disconnect - * 3. hardware sees connect - * 4. dwc2_port_intr() - clears connect interrupt - * 5. dwc2_handle_common_intr() - calls here - * - * Without the extra check here we will end calling disconnect - * and won't get any future interrupts to handle the connect. - */ - if (!force) { - hprt0 = DWC2_READ_4(hsotg, HPRT0); - if (!(hprt0 & HPRT0_CONNDET) && (hprt0 & HPRT0_CONNSTS)) - dwc2_hcd_connect(hsotg); + if (wire_frame & 1) + *hcchar |= HCCHAR_ODDFRM; + else + *hcchar &= ~HCCHAR_ODDFRM; + } +} + +STATIC void dwc2_set_pid_isoc(struct dwc2_host_chan *chan) +{ + /* Set up the initial PID for the transfer */ + if (chan->speed == USB_SPEED_HIGH) { + if (chan->ep_is_in) { + if (chan->multi_count == 1) + chan->data_pid_start = DWC2_HC_PID_DATA0; + else if (chan->multi_count == 2) + chan->data_pid_start = DWC2_HC_PID_DATA1; + else + chan->data_pid_start = DWC2_HC_PID_DATA2; + } else { + if (chan->multi_count == 1) + chan->data_pid_start = DWC2_HC_PID_DATA0; + else + chan->data_pid_start = DWC2_HC_PID_MDATA; + } + } else { + chan->data_pid_start = DWC2_HC_PID_DATA0; } } /** - * dwc2_hcd_rem_wakeup() - Handles Remote Wakeup + * dwc2_hc_write_packet() - Writes a packet into the Tx FIFO associated with + * the Host Channel * - * @hsotg: Pointer to struct dwc2_hsotg + * @hsotg: Programming view of DWC_otg controller + * @chan: Information needed to initialize the host channel + * + * This function should only be called in Slave mode. For a channel associated + * with a non-periodic EP, the non-periodic Tx FIFO is written. For a channel + * associated with a periodic EP, the periodic Tx FIFO is written. + * + * Upon return the xfer_buf and xfer_count fields in chan are incremented by + * the number of bytes written to the Tx FIFO. */ -STATIC void dwc2_hcd_rem_wakeup(struct dwc2_hsotg *hsotg) +STATIC void dwc2_hc_write_packet(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan) { - if (hsotg->bus_suspended) { - hsotg->flags.b.port_suspend_change = 1; - usb_hcd_resume_root_hub(hsotg->priv); - } + u32 i; + u32 remaining_count; + u32 byte_count; + u32 dword_count; + u32 *data_buf = (u32 *)chan->xfer_buf; + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + remaining_count = chan->xfer_len - chan->xfer_count; + if (remaining_count > chan->max_packet) + byte_count = chan->max_packet; + else + byte_count = remaining_count; - if (hsotg->lx_state == DWC2_L1) - hsotg->flags.b.port_l1_change = 1; + dword_count = (byte_count + 3) / 4; + + if (((unsigned long)data_buf & 0x3) == 0) { + /* xfer_buf is DWORD aligned */ + for (i = 0; i < dword_count; i++, data_buf++) + dwc2_writel(hsotg, *data_buf, HCFIFO(chan->hc_num)); + } else { + /* xfer_buf is not DWORD aligned */ + for (i = 0; i < dword_count; i++, data_buf++) { + u32 data = data_buf[0] | data_buf[1] << 8 | + data_buf[2] << 16 | data_buf[3] << 24; + dwc2_writel(hsotg, data, HCFIFO(chan->hc_num)); + } + } - dwc2_root_intr(hsotg->hsotg_sc); + chan->xfer_count += byte_count; + chan->xfer_buf += byte_count; } /** - * dwc2_hcd_stop() - Halts the DWC_otg host mode operations in a clean manner + * dwc2_hc_do_ping() - Starts a PING transfer * - * @hsotg: Pointer to struct dwc2_hsotg + * @hsotg: Programming view of DWC_otg controller + * @chan: Information needed to initialize the host channel * - * Must be called with interrupt disabled and spinlock held + * This function should only be called in Slave mode. The Do Ping bit is set in + * the HCTSIZ register, then the channel is enabled. */ -void dwc2_hcd_stop(struct dwc2_hsotg *hsotg) +STATIC void dwc2_hc_do_ping(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan) { - dev_dbg(hsotg->dev, "DWC OTG HCD STOP\n"); + u32 hcchar; + u32 hctsiz; - /* - * The root hub should be disconnected before this function is called. - * The disconnect will clear the QTD lists (via ..._hcd_urb_dequeue) - * and the QH lists (via ..._hcd_endpoint_disable). - */ + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, + chan->hc_num); - /* Turn off all host-specific interrupts */ - dwc2_disable_host_interrupts(hsotg); + hctsiz = TSIZ_DOPNG; + hctsiz |= 1 << TSIZ_PKTCNT_SHIFT; + dwc2_writel(hsotg, hctsiz, HCTSIZ(chan->hc_num)); - /* Turn off the vbus power */ - dev_dbg(hsotg->dev, "PortPower off\n"); - DWC2_WRITE_4(hsotg, HPRT0, 0); + hcchar = dwc2_readl(hsotg, HCCHAR(chan->hc_num)); + hcchar |= HCCHAR_CHENA; + hcchar &= ~HCCHAR_CHDIS; + dwc2_writel(hsotg, hcchar, HCCHAR(chan->hc_num)); } -/* Caller must hold driver lock */ -int dwc2_hcd_urb_enqueue(struct dwc2_hsotg *hsotg, - struct dwc2_hcd_urb *urb, struct dwc2_qh *qh, - struct dwc2_qtd *qtd) +/** + * dwc2_hc_start_transfer() - Does the setup for a data transfer for a host + * channel and starts the transfer + * + * @hsotg: Programming view of DWC_otg controller + * @chan: Information needed to initialize the host channel. The xfer_len value + * may be reduced to accommodate the max widths of the XferSize and + * PktCnt fields in the HCTSIZn register. The multi_count value may be + * changed to reflect the final xfer_len value. + * + * This function may be called in either Slave mode or DMA mode. In Slave mode, + * the caller must ensure that there is sufficient space in the request queue + * and Tx Data FIFO. + * + * For an OUT transfer in Slave mode, it loads a data packet into the + * appropriate FIFO. If necessary, additional data packets are loaded in the + * Host ISR. + * + * For an IN transfer in Slave mode, a data packet is requested. The data + * packets are unloaded from the Rx FIFO in the Host ISR. If necessary, + * additional data packets are requested in the Host ISR. + * + * For a PING transfer in Slave mode, the Do Ping bit is set in the HCTSIZ + * register along with a packet count of 1 and the channel is enabled. This + * causes a single PING transaction to occur. Other fields in HCTSIZ are + * simply set to 0 since no data transfer occurs in this case. + * + * For a PING transfer in DMA mode, the HCTSIZ register is initialized with + * all the information required to perform the subsequent data transfer. In + * addition, the Do Ping bit is set in the HCTSIZ register. In this case, the + * controller performs the entire PING protocol, then starts the data + * transfer. + */ +STATIC void dwc2_hc_start_transfer(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan) { - u32 intr_mask; - int retval; - int dev_speed; + u32 max_hc_xfer_size = hsotg->params.max_transfer_size; + u16 max_hc_pkt_count = hsotg->params.max_packet_count; + u32 hcchar; + u32 hctsiz = 0; + u16 num_packets = 0; + u32 ec_mc = 0; + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + if (chan->do_ping) { + if (!hsotg->params.host_dma) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "ping, no DMA\n"); + dwc2_hc_do_ping(hsotg, chan); + chan->xfer_started = 1; + return; + } - if (!hsotg->flags.b.port_connect_status) { - /* No longer connected */ - dev_err(hsotg->dev, "Not connected\n"); - return -ENODEV; + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "ping, DMA\n"); + + hctsiz |= TSIZ_DOPNG; } - dev_speed = dwc2_host_get_speed(hsotg, urb->priv); + if (chan->do_split) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "split\n"); + num_packets = 1; - /* Some configurations cannot support LS traffic on a FS root port */ - if ((dev_speed == USB_SPEED_LOW) && - (hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED) && - (hsotg->hw_params.hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI)) { - u32 hprt0 = DWC2_READ_4(hsotg, HPRT0); - u32 prtspd = (hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT; + if (chan->complete_split && !chan->ep_is_in) + /* + * For CSPLIT OUT Transfer, set the size to 0 so the + * core doesn't expect any data written to the FIFO + */ + chan->xfer_len = 0; + else if (chan->ep_is_in || chan->xfer_len > chan->max_packet) + chan->xfer_len = chan->max_packet; + else if (!chan->ep_is_in && chan->xfer_len > 188) + chan->xfer_len = 188; + + hctsiz |= chan->xfer_len << TSIZ_XFERSIZE_SHIFT & + TSIZ_XFERSIZE_MASK; + + /* For split set ec_mc for immediate retries */ + if (chan->ep_type == USB_ENDPOINT_XFER_INT || + chan->ep_type == USB_ENDPOINT_XFER_ISOC) + ec_mc = 3; + else + ec_mc = 1; + } else { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "no split\n"); + /* + * Ensure that the transfer length and packet count will fit + * in the widths allocated for them in the HCTSIZn register + */ + if (chan->ep_type == USB_ENDPOINT_XFER_INT || + chan->ep_type == USB_ENDPOINT_XFER_ISOC) { + /* + * Make sure the transfer size is no larger than one + * (micro)frame's worth of data. (A check was done + * when the periodic transfer was accepted to ensure + * that a (micro)frame's worth of data can be + * programmed into a channel.) + */ + u32 max_periodic_len = + chan->multi_count * chan->max_packet; - if (prtspd == HPRT0_SPD_FULL_SPEED) { - dev_err(hsotg->dev, - "DWC OTG HCD URB Enqueue unsupported\n"); - return -ENODEV; + if (chan->xfer_len > max_periodic_len) + chan->xfer_len = max_periodic_len; + } else if (chan->xfer_len > max_hc_xfer_size) { + /* + * Make sure that xfer_len is a multiple of max packet + * size + */ + chan->xfer_len = + max_hc_xfer_size - chan->max_packet + 1; } - } - if (!qtd) - return -EINVAL; + if (chan->xfer_len > 0) { + num_packets = (chan->xfer_len + chan->max_packet - 1) / + chan->max_packet; + if (num_packets > max_hc_pkt_count) { + num_packets = max_hc_pkt_count; + chan->xfer_len = num_packets * chan->max_packet; + } else if (chan->ep_is_in) { + /* + * Always program an integral # of max packets + * for IN transfers. + * Note: This assumes that the input buffer is + * aligned and sized accordingly. + */ + chan->xfer_len = num_packets * chan->max_packet; + } + } else { + /* Need 1 packet for transfer length of 0 */ + num_packets = 1; + } - memset(qtd, 0, sizeof(*qtd)); + if (chan->ep_type == USB_ENDPOINT_XFER_INT || + chan->ep_type == USB_ENDPOINT_XFER_ISOC) + /* + * Make sure that the multi_count field matches the + * actual transfer length + */ + chan->multi_count = num_packets; - dwc2_hcd_qtd_init(qtd, urb); - retval = dwc2_hcd_qtd_add(hsotg, qtd, qh); - if (retval) { - dev_err(hsotg->dev, - "DWC OTG HCD URB Enqueue failed adding QTD. Error status %d\n", - retval); - return retval; - } + if (chan->ep_type == USB_ENDPOINT_XFER_ISOC) + dwc2_set_pid_isoc(chan); - intr_mask = DWC2_READ_4(hsotg, GINTMSK); - if (!(intr_mask & GINTSTS_SOF)) { - enum dwc2_transaction_type tr_type; - - if (qtd->qh->ep_type == USB_ENDPOINT_XFER_BULK && - !(qtd->urb->flags & URB_GIVEBACK_ASAP)) - /* - * Do not schedule SG transactions until qtd has - * URB_GIVEBACK_ASAP set - */ - return 0; + hctsiz |= chan->xfer_len << TSIZ_XFERSIZE_SHIFT & + TSIZ_XFERSIZE_MASK; - tr_type = dwc2_hcd_select_transactions(hsotg); - if (tr_type != DWC2_TRANSACTION_NONE) - dwc2_hcd_queue_transactions(hsotg, tr_type); + /* The ec_mc gets the multi_count for non-split */ + ec_mc = chan->multi_count; } - return 0; -} - -/* Must be called with interrupt disabled and spinlock held */ -int -dwc2_hcd_urb_dequeue(struct dwc2_hsotg *hsotg, - struct dwc2_hcd_urb *urb) -{ - struct dwc2_qh *qh; - struct dwc2_qtd *urb_qtd; - - urb_qtd = urb->qtd; - if (!urb_qtd) { - dev_dbg(hsotg->dev, "## Urb QTD is NULL ##\n"); - return -EINVAL; + chan->start_pkt_count = num_packets; + hctsiz |= num_packets << TSIZ_PKTCNT_SHIFT & TSIZ_PKTCNT_MASK; + hctsiz |= chan->data_pid_start << TSIZ_SC_MC_PID_SHIFT & + TSIZ_SC_MC_PID_MASK; + dwc2_writel(hsotg, hctsiz, HCTSIZ(chan->hc_num)); + if (dbg_hc(chan)) { + dev_vdbg(hsotg->dev, "Wrote %08x to HCTSIZ(%d)\n", + hctsiz, chan->hc_num); + + dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, + chan->hc_num); + dev_vdbg(hsotg->dev, " Xfer Size: %d\n", + (hctsiz & TSIZ_XFERSIZE_MASK) >> + TSIZ_XFERSIZE_SHIFT); + dev_vdbg(hsotg->dev, " Num Pkts: %d\n", + (hctsiz & TSIZ_PKTCNT_MASK) >> + TSIZ_PKTCNT_SHIFT); + dev_vdbg(hsotg->dev, " Start PID: %d\n", + (hctsiz & TSIZ_SC_MC_PID_MASK) >> + TSIZ_SC_MC_PID_SHIFT); } - qh = urb_qtd->qh; - if (!qh) { - dev_dbg(hsotg->dev, "## Urb QTD QH is NULL ##\n"); - return -EINVAL; - } + if (hsotg->params.host_dma) { + dma_addr_t dma_addr; - urb->priv = NULL; + if (chan->align_buf) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "align_buf\n"); + dma_addr = chan->align_buf; + } else { + dma_addr = chan->xfer_dma; + } - if (urb_qtd->in_process && qh->channel) { -#ifdef VERBOSE_DEBUG - dwc2_dump_channel_info(hsotg, qh->channel); -#endif - /* The QTD is in process (it has been assigned to a channel) */ - if (hsotg->flags.b.port_connect_status) - /* - * If still connected (i.e. in host mode), halt the - * channel so it can be used for other transfers. If - * no longer connected, the host registers can't be - * written to halt the channel since the core is in - * device mode. - */ - dwc2_hc_halt(hsotg, qh->channel, - DWC2_HC_XFER_URB_DEQUEUE); + if (hsotg->hsotg_sc->sc_set_dma_addr == NULL) { + dwc2_writel(hsotg, (u32)dma_addr, HCDMA(chan->hc_num)); + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, + "Wrote %08lx to HCDMA(%d)\n", + (unsigned long)dma_addr, chan->hc_num); + } else { + (void)(*hsotg->hsotg_sc->sc_set_dma_addr)( + hsotg->dev, dma_addr, chan->hc_num); + } } - /* - * Free the QTD and clean up the associated QH. Leave the QH in the - * schedule if it has any remaining QTDs. - */ - if (hsotg->core_params->dma_desc_enable <= 0) { - u8 in_process = urb_qtd->in_process; + /* Start the split */ + if (chan->do_split) { + u32 hcsplt = dwc2_readl(hsotg, HCSPLT(chan->hc_num)); - dwc2_hcd_qtd_unlink_and_free(hsotg, urb_qtd, qh); - if (in_process) { - dwc2_hcd_qh_deactivate(hsotg, qh, 0); - qh->channel = NULL; - } else if (list_empty(&qh->qtd_list)) { - dwc2_hcd_qh_unlink(hsotg, qh); - } - } else { - dwc2_hcd_qtd_unlink_and_free(hsotg, urb_qtd, qh); + hcsplt |= HCSPLT_SPLTENA; + dwc2_writel(hsotg, hcsplt, HCSPLT(chan->hc_num)); } - return 0; + hcchar = dwc2_readl(hsotg, HCCHAR(chan->hc_num)); + hcchar &= ~HCCHAR_MULTICNT_MASK; + hcchar |= (ec_mc << HCCHAR_MULTICNT_SHIFT) & HCCHAR_MULTICNT_MASK; + dwc2_hc_set_even_odd_frame(hsotg, chan, &hcchar); + + if (hcchar & HCCHAR_CHDIS) + dev_warn(hsotg->dev, + "%s: chdis set, channel %d, hcchar 0x%08x\n", + __func__, chan->hc_num, hcchar); + + /* Set host channel enable after all other setup is complete */ + hcchar |= HCCHAR_CHENA; + hcchar &= ~HCCHAR_CHDIS; + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, " Multi Cnt: %d\n", + (hcchar & HCCHAR_MULTICNT_MASK) >> + HCCHAR_MULTICNT_SHIFT); + + dwc2_writel(hsotg, hcchar, HCCHAR(chan->hc_num)); + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "Wrote %08x to HCCHAR(%d)\n", hcchar, + chan->hc_num); + + chan->xfer_started = 1; + chan->requests++; + + if (!hsotg->params.host_dma && + !chan->ep_is_in && chan->xfer_len > 0) + /* Load OUT packet into the appropriate Tx FIFO */ + dwc2_hc_write_packet(hsotg, chan); } - -/* - * Initializes dynamic portions of the DWC_otg HCD state +/** + * dwc2_hc_start_transfer_ddma() - Does the setup for a data transfer for a + * host channel and starts the transfer in Descriptor DMA mode * - * Must be called with interrupt disabled and spinlock held + * @hsotg: Programming view of DWC_otg controller + * @chan: Information needed to initialize the host channel + * + * Initializes HCTSIZ register. For a PING transfer the Do Ping bit is set. + * Sets PID and NTD values. For periodic transfers initializes SCHED_INFO field + * with micro-frame bitmap. + * + * Initializes HCDMA register with descriptor list address and CTD value then + * starts the transfer via enabling the channel. */ -void -dwc2_hcd_reinit(struct dwc2_hsotg *hsotg) +void dwc2_hc_start_transfer_ddma(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan) { - struct dwc2_host_chan *chan, *chan_tmp; - int num_channels; - int i; + u32 hcchar; + u32 hctsiz = 0; - hsotg->flags.d32 = 0; - hsotg->non_periodic_qh_ptr = &hsotg->non_periodic_sched_active; + if (chan->do_ping) + hctsiz |= TSIZ_DOPNG; - if (hsotg->core_params->uframe_sched > 0) { - hsotg->available_host_channels = - hsotg->core_params->host_channels; - } else { - hsotg->non_periodic_channels = 0; - hsotg->periodic_channels = 0; - } + if (chan->ep_type == USB_ENDPOINT_XFER_ISOC) + dwc2_set_pid_isoc(chan); - /* - * Put all channels in the free channel list and clean up channel - * states - */ - list_for_each_entry_safe(chan, chan_tmp, &hsotg->free_hc_list, - hc_list_entry) - list_del_init(&chan->hc_list_entry); + /* Packet Count and Xfer Size are not used in Descriptor DMA mode */ + hctsiz |= chan->data_pid_start << TSIZ_SC_MC_PID_SHIFT & + TSIZ_SC_MC_PID_MASK; - num_channels = hsotg->core_params->host_channels; - for (i = 0; i < num_channels; i++) { - chan = hsotg->hc_ptr_array[i]; - list_add_tail(&chan->hc_list_entry, &hsotg->free_hc_list); - dwc2_hc_cleanup(hsotg, chan); - } + /* 0 - 1 descriptor, 1 - 2 descriptors, etc */ + hctsiz |= (chan->ntd - 1) << TSIZ_NTD_SHIFT & TSIZ_NTD_MASK; - /* Initialize the DWC core for host mode operation */ - dwc2_core_host_init(hsotg); -} + /* Non-zero only for high-speed interrupt endpoints */ + hctsiz |= chan->schinfo << TSIZ_SCHINFO_SHIFT & TSIZ_SCHINFO_MASK; -STATIC void dwc2_hc_init_split(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan, - struct dwc2_qtd *qtd, struct dwc2_hcd_urb *urb) -{ - int hub_addr, hub_port; + if (dbg_hc(chan)) { + dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, + chan->hc_num); + dev_vdbg(hsotg->dev, " Start PID: %d\n", + chan->data_pid_start); + dev_vdbg(hsotg->dev, " NTD: %d\n", chan->ntd - 1); + } - chan->do_split = 1; - chan->xact_pos = qtd->isoc_split_pos; - chan->complete_split = qtd->complete_split; - dwc2_host_hub_info(hsotg, urb->priv, &hub_addr, &hub_port); - chan->hub_addr = (u8)hub_addr; - chan->hub_port = (u8)hub_port; -} + dwc2_writel(hsotg, hctsiz, HCTSIZ(chan->hc_num)); -STATIC void *dwc2_hc_init_xfer_data(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan, - struct dwc2_qtd *qtd, struct dwc2_hcd_urb *urb) -{ - if (hsotg->core_params->dma_enable > 0) { - chan->xfer_dma = DMAADDR(urb->usbdma, urb->actual_length); + usb_syncmem(&chan->desc_list_usbdma, 0, chan->desc_list_sz, + BUS_DMASYNC_PREWRITE); - /* For non-dword aligned case */ - if (hsotg->core_params->dma_desc_enable <= 0 && - (chan->xfer_dma & 0x3)) - return (u8 *)urb->buf + urb->actual_length; + if (hsotg->hsotg_sc->sc_set_dma_addr == NULL) { + dwc2_writel(hsotg, chan->desc_list_addr, HCDMA(chan->hc_num)); + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "Wrote %pad to HCDMA(%d)\n", + &chan->desc_list_addr, chan->hc_num); } else { - chan->xfer_buf = (u8 *)urb->buf + urb->actual_length; + (void)(*hsotg->hsotg_sc->sc_set_dma_addr)( + hsotg->dev, chan->desc_list_addr, chan->hc_num); + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "Wrote %pad to ext dma(%d)\n", + &chan->desc_list_addr, chan->hc_num); } - return NULL; -} - -STATIC void *dwc2_hc_init_xfer(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan, - struct dwc2_qtd *qtd, struct dwc2_hcd_urb *urb) -{ - struct dwc2_hcd_iso_packet_desc *frame_desc; - void *bufptr = NULL; + hcchar = dwc2_readl(hsotg, HCCHAR(chan->hc_num)); + hcchar &= ~HCCHAR_MULTICNT_MASK; + hcchar |= chan->multi_count << HCCHAR_MULTICNT_SHIFT & + HCCHAR_MULTICNT_MASK; - switch (dwc2_hcd_get_pipe_type(&urb->pipe_info)) { - case USB_ENDPOINT_XFER_CONTROL: - chan->ep_type = USB_ENDPOINT_XFER_CONTROL; + if (hcchar & HCCHAR_CHDIS) + dev_warn(hsotg->dev, + "%s: chdis set, channel %d, hcchar 0x%08x\n", + __func__, chan->hc_num, hcchar); - switch (qtd->control_phase) { - case DWC2_CONTROL_SETUP: - dev_vdbg(hsotg->dev, " Control setup transaction\n"); - chan->do_ping = 0; - chan->ep_is_in = 0; - chan->data_pid_start = DWC2_HC_PID_SETUP; - if (hsotg->core_params->dma_enable > 0) - chan->xfer_dma = urb->setup_dma; - else - chan->xfer_buf = urb->setup_packet; - chan->xfer_len = 8; - break; + /* Set host channel enable after all other setup is complete */ + hcchar |= HCCHAR_CHENA; + hcchar &= ~HCCHAR_CHDIS; - case DWC2_CONTROL_DATA: - dev_vdbg(hsotg->dev, " Control data transaction\n"); - chan->data_pid_start = qtd->data_toggle; - bufptr = dwc2_hc_init_xfer_data(hsotg, chan, qtd, urb); - break; + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, " Multi Cnt: %d\n", + (hcchar & HCCHAR_MULTICNT_MASK) >> + HCCHAR_MULTICNT_SHIFT); - case DWC2_CONTROL_STATUS: - /* - * Direction is opposite of data direction or IN if no - * data - */ - dev_vdbg(hsotg->dev, " Control status transaction\n"); - if (urb->length == 0) - chan->ep_is_in = 1; - else - chan->ep_is_in = - dwc2_hcd_is_pipe_out(&urb->pipe_info); - if (chan->ep_is_in) - chan->do_ping = 0; - chan->data_pid_start = DWC2_HC_PID_DATA1; - chan->xfer_len = 0; - if (hsotg->core_params->dma_enable > 0) - chan->xfer_dma = hsotg->status_buf_dma; - else - chan->xfer_buf = hsotg->status_buf; - break; - } - break; + dwc2_writel(hsotg, hcchar, HCCHAR(chan->hc_num)); + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "Wrote %08x to HCCHAR(%d)\n", hcchar, + chan->hc_num); - case USB_ENDPOINT_XFER_BULK: - chan->ep_type = USB_ENDPOINT_XFER_BULK; - bufptr = dwc2_hc_init_xfer_data(hsotg, chan, qtd, urb); - break; + chan->xfer_started = 1; + chan->requests++; +} - case USB_ENDPOINT_XFER_INT: - chan->ep_type = USB_ENDPOINT_XFER_INT; - bufptr = dwc2_hc_init_xfer_data(hsotg, chan, qtd, urb); - break; +/** + * dwc2_hc_continue_transfer() - Continues a data transfer that was started by + * a previous call to dwc2_hc_start_transfer() + * + * @hsotg: Programming view of DWC_otg controller + * @chan: Information needed to initialize the host channel + * + * The caller must ensure there is sufficient space in the request queue and Tx + * Data FIFO. This function should only be called in Slave mode. In DMA mode, + * the controller acts autonomously to complete transfers programmed to a host + * channel. + * + * For an OUT transfer, a new data packet is loaded into the appropriate FIFO + * if there is any data remaining to be queued. For an IN transfer, another + * data packet is always requested. For the SETUP phase of a control transfer, + * this function does nothing. + * + * Return: 1 if a new request is queued, 0 if no more requests are required + * for this transfer + */ +STATIC int dwc2_hc_continue_transfer(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan) +{ + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, + chan->hc_num); - case USB_ENDPOINT_XFER_ISOC: - chan->ep_type = USB_ENDPOINT_XFER_ISOC; - if (hsotg->core_params->dma_desc_enable > 0) - break; + if (chan->do_split) + /* SPLITs always queue just once per channel */ + return 0; - frame_desc = &urb->iso_descs[qtd->isoc_frame_index]; - frame_desc->status = 0; + if (chan->data_pid_start == DWC2_HC_PID_SETUP) + /* SETUPs are queued only once since they can't be NAK'd */ + return 0; - if (hsotg->core_params->dma_enable > 0) { - chan->xfer_dma = urb->dma; - chan->xfer_dma += frame_desc->offset + - qtd->isoc_split_offset; - } else { - chan->xfer_buf = urb->buf; - chan->xfer_buf += frame_desc->offset + - qtd->isoc_split_offset; - } + if (chan->ep_is_in) { + /* + * Always queue another request for other IN transfers. If + * back-to-back INs are issued and NAKs are received for both, + * the driver may still be processing the first NAK when the + * second NAK is received. When the interrupt handler clears + * the NAK interrupt for the first NAK, the second NAK will + * not be seen. So we can't depend on the NAK interrupt + * handler to requeue a NAK'd request. Instead, IN requests + * are issued each time this function is called. When the + * transfer completes, the extra requests for the channel will + * be flushed. + */ + u32 hcchar = dwc2_readl(hsotg, HCCHAR(chan->hc_num)); + + dwc2_hc_set_even_odd_frame(hsotg, chan, &hcchar); + hcchar |= HCCHAR_CHENA; + hcchar &= ~HCCHAR_CHDIS; + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, " IN xfer: hcchar = 0x%08x\n", + hcchar); + dwc2_writel(hsotg, hcchar, HCCHAR(chan->hc_num)); + chan->requests++; + return 1; + } - chan->xfer_len = frame_desc->length - qtd->isoc_split_offset; + /* OUT transfers */ - /* For non-dword aligned buffers */ - if (hsotg->core_params->dma_enable > 0 && - (chan->xfer_dma & 0x3)) - bufptr = (u8 *)urb->buf + frame_desc->offset + - qtd->isoc_split_offset; + if (chan->xfer_count < chan->xfer_len) { + if (chan->ep_type == USB_ENDPOINT_XFER_INT || + chan->ep_type == USB_ENDPOINT_XFER_ISOC) { + u32 hcchar = dwc2_readl(hsotg, + HCCHAR(chan->hc_num)); - if (chan->xact_pos == DWC2_HCSPLT_XACTPOS_ALL) { - if (chan->xfer_len <= 188) - chan->xact_pos = DWC2_HCSPLT_XACTPOS_ALL; - else - chan->xact_pos = DWC2_HCSPLT_XACTPOS_BEGIN; + dwc2_hc_set_even_odd_frame(hsotg, chan, + &hcchar); } - break; + + /* Load OUT packet into the appropriate Tx FIFO */ + dwc2_hc_write_packet(hsotg, chan); + chan->requests++; + return 1; } - return bufptr; + return 0; } -STATIC int dwc2_hc_setup_align_buf(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, - struct dwc2_host_chan *chan, - struct dwc2_hcd_urb *urb, void *bufptr) -{ - u32 buf_size; - - if (!qh->dw_align_buf) { - int err; - - if (chan->ep_type != USB_ENDPOINT_XFER_ISOC) - buf_size = hsotg->core_params->max_transfer_size; - else - /* 3072 = 3 max-size Isoc packets */ - buf_size = 3072; +/* + * ========================================================================= + * HCD + * ========================================================================= + */ - qh->dw_align_buf = NULL; - qh->dw_align_buf_dma = 0; - err = usb_allocmem(&hsotg->hsotg_sc->sc_bus, buf_size, 0, - USB_DMA_COHERENT, &qh->dw_align_buf_usbdma); - if (!err) { - struct usb_dma *ud = &qh->dw_align_buf_usbdma; +/* + * Processes all the URBs in a single list of QHs. Completes them with + * -ETIMEDOUT and frees the QTD. + * + * Must be called with interrupt disabled and spinlock held + */ +STATIC void dwc2_kill_urbs_in_qh_list(struct dwc2_hsotg *hsotg, + struct list_head *qh_list) +{ + struct dwc2_qh *qh, *qh_tmp; + struct dwc2_qtd *qtd, *qtd_tmp; - qh->dw_align_buf = KERNADDR(ud, 0); - qh->dw_align_buf_dma = DMAADDR(ud, 0); + list_for_each_entry_safe(qh, qh_tmp, qh_list, qh_list_entry) { + list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, + qtd_list_entry) { + dwc2_host_complete(hsotg, qtd, -ECONNRESET); + dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); } - if (!qh->dw_align_buf) - return -ENOMEM; - qh->dw_align_buf_size = buf_size; } +} - if (chan->xfer_len) { - dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__); - void *usb_urb = urb->priv; +STATIC void dwc2_qh_list_free(struct dwc2_hsotg *hsotg, + struct list_head *qh_list) +{ + struct dwc2_qtd *qtd, *qtd_tmp; + struct dwc2_qh *qh, *qh_tmp; + unsigned long flags; - if (usb_urb) { - if (!chan->ep_is_in) { - memcpy(qh->dw_align_buf, bufptr, - chan->xfer_len); - } - } else { - dev_warn(hsotg->dev, "no URB in dwc2_urb\n"); - } - } + if (!qh_list->next) + /* The list hasn't been initialized yet */ + return; - usb_syncmem(&qh->dw_align_buf_usbdma, 0, qh->dw_align_buf_size, - chan->ep_is_in ? BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE); + spin_lock_irqsave(&hsotg->lock, flags); - chan->align_buf = qh->dw_align_buf_dma; - return 0; -} + /* Ensure there are no QTDs or URBs left */ + dwc2_kill_urbs_in_qh_list(hsotg, qh_list); -/** - * dwc2_assign_and_init_hc() - Assigns transactions from a QTD to a free host - * channel and initializes the host channel to perform the transactions. The - * host channel is removed from the free list. - * - * @hsotg: The HCD state structure - * @qh: Transactions from the first QTD for this QH are selected and assigned - * to a free host channel - */ -STATIC int dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) -{ - struct dwc2_host_chan *chan; - struct dwc2_hcd_urb *urb; - struct dwc2_qtd *qtd; - void *bufptr = NULL; + list_for_each_entry_safe(qh, qh_tmp, qh_list, qh_list_entry) { + dwc2_hcd_qh_unlink(hsotg, qh); - if (dbg_qh(qh)) - dev_vdbg(hsotg->dev, "%s(%p,%p)\n", __func__, hsotg, qh); + /* Free each QTD in the QH's QTD list */ + list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, + qtd_list_entry) + dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); - if (list_empty(&qh->qtd_list)) { - dev_dbg(hsotg->dev, "No QTDs in QH list\n"); - return -ENOMEM; - } + if (qh->channel && qh->channel->qh == qh) + qh->channel->qh = NULL; - if (list_empty(&hsotg->free_hc_list)) { - dev_dbg(hsotg->dev, "No free channel to assign\n"); - return -ENOMEM; + spin_unlock_irqrestore(&hsotg->lock, flags); + dwc2_hcd_qh_free(hsotg, qh); + spin_lock_irqsave(&hsotg->lock, flags); } - chan = list_first_entry(&hsotg->free_hc_list, struct dwc2_host_chan, - hc_list_entry); - - /* Remove host channel from free list */ - list_del_init(&chan->hc_list_entry); - - qtd = list_first_entry(&qh->qtd_list, struct dwc2_qtd, qtd_list_entry); - urb = qtd->urb; - qh->channel = chan; - qtd->in_process = 1; - - /* - * Use usb_pipedevice to determine device address. This address is - * 0 before the SET_ADDRESS command and the correct address afterward. - */ - chan->dev_addr = dwc2_hcd_get_dev_addr(&urb->pipe_info); - chan->ep_num = dwc2_hcd_get_ep_num(&urb->pipe_info); - chan->speed = qh->dev_speed; - chan->max_packet = dwc2_max_packet(qh->maxp); - - chan->xfer_started = 0; - chan->halt_status = DWC2_HC_XFER_NO_HALT_STATUS; - chan->error_state = (qtd->error_count > 0); - chan->halt_on_queue = 0; - chan->halt_pending = 0; - chan->requests = 0; - - /* - * The following values may be modified in the transfer type section - * below. The xfer_len value may be reduced when the transfer is - * started to accommodate the max widths of the XferSize and PktCnt - * fields in the HCTSIZn register. - */ - - chan->ep_is_in = (dwc2_hcd_is_pipe_in(&urb->pipe_info) != 0); - if (chan->ep_is_in) - chan->do_ping = 0; - else - chan->do_ping = qh->ping_state; + spin_unlock_irqrestore(&hsotg->lock, flags); +} - chan->data_pid_start = qh->data_toggle; - chan->multi_count = 1; +/* + * Responds with an error status of -ETIMEDOUT to all URBs in the non-periodic + * and periodic schedules. The QTD associated with each URB is removed from + * the schedule and freed. This function may be called when a disconnect is + * detected or when the HCD is being stopped. + * + * Must be called with interrupt disabled and spinlock held + */ +STATIC void dwc2_kill_all_urbs(struct dwc2_hsotg *hsotg) +{ + dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->non_periodic_sched_inactive); + dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->non_periodic_sched_waiting); + dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->non_periodic_sched_active); + dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_inactive); + dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_ready); + dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_assigned); + dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_queued); +} - if (urb->actual_length > urb->length && - !dwc2_hcd_is_pipe_in(&urb->pipe_info)) - urb->actual_length = urb->length; +/** + * dwc2_hcd_start() - Starts the HCD when switching to Host mode + * + * @hsotg: Pointer to struct dwc2_hsotg + */ +void dwc2_hcd_start(struct dwc2_hsotg *hsotg) +{ + u32 hprt0; - chan->xfer_len = urb->length - urb->actual_length; - chan->xfer_count = 0; + if (hsotg->op_state == OTG_STATE_B_HOST) { + /* + * Reset the port. During a HNP mode switch the reset + * needs to occur within 1ms and have a duration of at + * least 50ms. + */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_RST; + dwc2_writel(hsotg, hprt0, HPRT0); + } - /* Set the split attributes if required */ - if (qh->do_split) - dwc2_hc_init_split(hsotg, chan, qtd, urb); - else - chan->do_split = 0; + queue_delayed_work(hsotg->wq_otg, &hsotg->start_work, + msecs_to_jiffies(50)); +} - /* Set the transfer attributes */ - bufptr = dwc2_hc_init_xfer(hsotg, chan, qtd, urb); +/* Must be called with interrupt disabled and spinlock held */ +STATIC void dwc2_hcd_cleanup_channels(struct dwc2_hsotg *hsotg) +{ + int num_channels = hsotg->params.host_channels; + struct dwc2_host_chan *channel; + u32 hcchar; + int i; - /* Non DWORD-aligned buffer case */ - if (bufptr) { - dev_vdbg(hsotg->dev, "Non-aligned buffer\n"); - if (dwc2_hc_setup_align_buf(hsotg, qh, chan, urb, bufptr)) { - dev_err(hsotg->dev, - "%s: Failed to allocate memory to handle non-dword aligned buffer\n", - __func__); - /* Add channel back to free list */ - chan->align_buf = 0; - chan->multi_count = 0; - list_add_tail(&chan->hc_list_entry, - &hsotg->free_hc_list); - qtd->in_process = 0; - qh->channel = NULL; - return -ENOMEM; + if (!hsotg->params.host_dma) { + /* Flush out any channel requests in slave mode */ + for (i = 0; i < num_channels; i++) { + channel = hsotg->hc_ptr_array[i]; + if (!list_empty(&channel->hc_list_entry)) + continue; + hcchar = dwc2_readl(hsotg, HCCHAR(i)); + if (hcchar & HCCHAR_CHENA) { + hcchar &= ~(HCCHAR_CHENA | HCCHAR_EPDIR); + hcchar |= HCCHAR_CHDIS; + dwc2_writel(hsotg, hcchar, HCCHAR(i)); + } } - } else { - chan->align_buf = 0; } - if (chan->ep_type == USB_ENDPOINT_XFER_INT || - chan->ep_type == USB_ENDPOINT_XFER_ISOC) + for (i = 0; i < num_channels; i++) { + channel = hsotg->hc_ptr_array[i]; + if (!list_empty(&channel->hc_list_entry)) + continue; + hcchar = dwc2_readl(hsotg, HCCHAR(i)); + if (hcchar & HCCHAR_CHENA) { + /* Halt the channel */ + hcchar |= HCCHAR_CHDIS; + dwc2_writel(hsotg, hcchar, HCCHAR(i)); + } + + dwc2_hc_cleanup(hsotg, channel); + list_add_tail(&channel->hc_list_entry, &hsotg->free_hc_list); /* - * This value may be modified when the transfer is started - * to reflect the actual transfer length + * Added for Descriptor DMA to prevent channel double cleanup in + * release_channel_ddma(), which is called from ep_disable when + * device disconnects */ - chan->multi_count = dwc2_hb_mult(qh->maxp); - - if (hsotg->core_params->dma_desc_enable > 0) { - chan->desc_list_usbdma = qh->desc_list_usbdma; - chan->desc_list_addr = qh->desc_list_dma; - chan->desc_list_sz = qh->desc_list_sz; + channel->qh = NULL; } + /* All channels have been freed, mark them available */ + if (hsotg->params.uframe_sched) { + hsotg->available_host_channels = + hsotg->params.host_channels; + } else { + hsotg->non_periodic_channels = 0; + hsotg->periodic_channels = 0; + } +} - dwc2_hc_init(hsotg, chan); - chan->qh = qh; +/** + * dwc2_hcd_connect() - Handles connect of the HCD + * + * @hsotg: Pointer to struct dwc2_hsotg + * + * Must be called with interrupt disabled and spinlock held + */ +void dwc2_hcd_connect(struct dwc2_hsotg *hsotg) +{ + if (hsotg->lx_state != DWC2_L0) + usb_hcd_resume_root_hub(hsotg->priv); - return 0; + hsotg->flags.b.port_connect_status_change = 1; + hsotg->flags.b.port_connect_status = 1; } /** - * dwc2_hcd_select_transactions() - Selects transactions from the HCD transfer - * schedule and assigns them to available host channels. Called from the HCD - * interrupt handler functions. + * dwc2_hcd_disconnect() - Handles disconnect of the HCD * - * @hsotg: The HCD state structure + * @hsotg: Pointer to struct dwc2_hsotg + * @force: If true, we won't try to reconnect even if we see device connected. * - * Return: The types of new transactions that were assigned to host channels + * Must be called with interrupt disabled and spinlock held */ -enum dwc2_transaction_type dwc2_hcd_select_transactions( - struct dwc2_hsotg *hsotg) +void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force) { - enum dwc2_transaction_type ret_val = DWC2_TRANSACTION_NONE; - struct list_head *qh_ptr; - struct dwc2_qh *qh; - int num_channels; - -#ifdef DWC2_DEBUG_SOF - dev_vdbg(hsotg->dev, " Select Transactions\n"); -#endif + u32 intr; + u32 hprt0; - /* Process entries in the periodic ready list */ - qh_ptr = hsotg->periodic_sched_ready.next; - while (qh_ptr != &hsotg->periodic_sched_ready) { - if (list_empty(&hsotg->free_hc_list)) - break; - if (hsotg->core_params->uframe_sched > 0) { - if (hsotg->available_host_channels <= 1) - break; - hsotg->available_host_channels--; - } - qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry); - if (dwc2_assign_and_init_hc(hsotg, qh)) - break; + /* Set status flags for the hub driver */ + hsotg->flags.b.port_connect_status_change = 1; + hsotg->flags.b.port_connect_status = 0; - /* - * Move the QH from the periodic ready schedule to the - * periodic assigned schedule - */ - qh_ptr = qh_ptr->next; - list_move(&qh->qh_list_entry, &hsotg->periodic_sched_assigned); - ret_val = DWC2_TRANSACTION_PERIODIC; - } + /* + * Shutdown any transfers in process by clearing the Tx FIFO Empty + * interrupt mask and status bits and disabling subsequent host + * channel interrupts. + */ + intr = dwc2_readl(hsotg, GINTMSK); + intr &= ~(GINTSTS_NPTXFEMP | GINTSTS_PTXFEMP | GINTSTS_HCHINT); + dwc2_writel(hsotg, intr, GINTMSK); + intr = GINTSTS_NPTXFEMP | GINTSTS_PTXFEMP | GINTSTS_HCHINT; + dwc2_writel(hsotg, intr, GINTSTS); /* - * Process entries in the inactive portion of the non-periodic - * schedule. Some free host channels may not be used if they are - * reserved for periodic transfers. + * Turn off the vbus power only if the core has transitioned to device + * mode. If still in host mode, need to keep power on to detect a + * reconnection. */ - num_channels = hsotg->core_params->host_channels; - qh_ptr = hsotg->non_periodic_sched_inactive.next; - while (qh_ptr != &hsotg->non_periodic_sched_inactive) { - if (hsotg->core_params->uframe_sched <= 0 && - hsotg->non_periodic_channels >= num_channels - - hsotg->periodic_channels) - break; - if (list_empty(&hsotg->free_hc_list)) - break; - qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry); - - /* - * Check to see if this is a NAK'd retransmit, in which case - * ignore for retransmission. We hold off on bulk/control - * retransmissions to reduce NAK interrupt overhead for - * cheeky devices that just hold off using NAKs. - */ - if (qh->nak_frame != 0xffff && - dwc2_full_frame_num(qh->nak_frame) == - dwc2_full_frame_num(dwc2_hcd_get_frame_number(hsotg))) { - qh_ptr = qh_ptr->next; - continue; - } else { - qh->nak_frame = 0xffff; + if (dwc2_is_device_mode(hsotg)) { + if (hsotg->op_state != OTG_STATE_A_SUSPEND) { + dev_dbg(hsotg->dev, "Disconnect: PortPower off\n"); + dwc2_writel(hsotg, 0, HPRT0); } - if (hsotg->core_params->uframe_sched > 0) { - if (hsotg->available_host_channels < 1) - break; - hsotg->available_host_channels--; - } + dwc2_disable_host_interrupts(hsotg); + } - if (dwc2_assign_and_init_hc(hsotg, qh)) - break; + /* Respond with an error status to all URBs in the schedule */ + dwc2_kill_all_urbs(hsotg); - /* - * Move the QH from the non-periodic inactive schedule to the - * non-periodic active schedule - */ - qh_ptr = qh_ptr->next; - list_move(&qh->qh_list_entry, - &hsotg->non_periodic_sched_active); + if (dwc2_is_host_mode(hsotg)) + /* Clean up any host channels that were in use */ + dwc2_hcd_cleanup_channels(hsotg); - if (ret_val == DWC2_TRANSACTION_NONE) - ret_val = DWC2_TRANSACTION_NON_PERIODIC; - else - ret_val = DWC2_TRANSACTION_ALL; + dwc2_host_disconnect(hsotg); - if (hsotg->core_params->uframe_sched <= 0) - hsotg->non_periodic_channels++; - } + dwc2_root_intr(hsotg->hsotg_sc); /* Required for OpenBSD */ - return ret_val; + /* + * Add an extra check here to see if we're actually connected but + * we don't have a detection interrupt pending. This can happen if: + * 1. hardware sees connect + * 2. hardware sees disconnect + * 3. hardware sees connect + * 4. dwc2_port_intr() - clears connect interrupt + * 5. dwc2_handle_common_intr() - calls here + * + * Without the extra check here we will end calling disconnect + * and won't get any future interrupts to handle the connect. + */ + if (!force) { + hprt0 = dwc2_readl(hsotg, HPRT0); + if (!(hprt0 & HPRT0_CONNDET) && (hprt0 & HPRT0_CONNSTS)) + dwc2_hcd_connect(hsotg); + } } /** - * dwc2_queue_transaction() - Attempts to queue a single transaction request for - * a host channel associated with either a periodic or non-periodic transfer - * - * @hsotg: The HCD state structure - * @chan: Host channel descriptor associated with either a periodic or - * non-periodic transfer - * @fifo_dwords_avail: Number of DWORDs available in the periodic Tx FIFO - * for periodic transfers or the non-periodic Tx FIFO - * for non-periodic transfers - * - * Return: 1 if a request is queued and more requests may be needed to - * complete the transfer, 0 if no more requests are required for this - * transfer, -1 if there is insufficient space in the Tx FIFO - * - * This function assumes that there is space available in the appropriate - * request queue. For an OUT transfer or SETUP transaction in Slave mode, - * it checks whether space is available in the appropriate Tx FIFO. + * dwc2_hcd_rem_wakeup() - Handles Remote Wakeup * - * Must be called with interrupt disabled and spinlock held + * @hsotg: Pointer to struct dwc2_hsotg */ -STATIC int dwc2_queue_transaction(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan, - u16 fifo_dwords_avail) +STATIC void dwc2_hcd_rem_wakeup(struct dwc2_hsotg *hsotg) { - int retval = 0; - - if (chan->do_split) - /* Put ourselves on the list to keep order straight */ - list_move(&chan->split_order_list_entry, &hsotg->split_order); - - if (hsotg->core_params->dma_enable > 0 && chan->qh) { - if (hsotg->core_params->dma_desc_enable > 0) { - if (!chan->xfer_started || - chan->ep_type == USB_ENDPOINT_XFER_ISOC) { - dwc2_hcd_start_xfer_ddma(hsotg, chan->qh); - chan->qh->ping_state = 0; - } - } else if (!chan->xfer_started) { - dwc2_hc_start_transfer(hsotg, chan); - chan->qh->ping_state = 0; - } - } else if (chan->halt_pending) { - /* Don't queue a request if the channel has been halted */ - } else if (chan->halt_on_queue) { - dwc2_hc_halt(hsotg, chan, chan->halt_status); - } else if (chan->do_ping) { - if (!chan->xfer_started) - dwc2_hc_start_transfer(hsotg, chan); - } else if (!chan->ep_is_in || - chan->data_pid_start == DWC2_HC_PID_SETUP) { - if ((fifo_dwords_avail * 4) >= chan->max_packet) { - if (!chan->xfer_started) { - dwc2_hc_start_transfer(hsotg, chan); - retval = 1; - } else { - retval = dwc2_hc_continue_transfer(hsotg, chan); - } - } else { - retval = -1; - } - } else { - if (!chan->xfer_started) { - dwc2_hc_start_transfer(hsotg, chan); - retval = 1; - } else { - retval = dwc2_hc_continue_transfer(hsotg, chan); - } + if (hsotg->bus_suspended) { + hsotg->flags.b.port_suspend_change = 1; + usb_hcd_resume_root_hub(hsotg->priv); } - return retval; + if (hsotg->lx_state == DWC2_L1) + hsotg->flags.b.port_l1_change = 1; + + dwc2_root_intr(hsotg->hsotg_sc); /* Required for OpenBSD */ } -/* - * Processes periodic channels for the next frame and queues transactions for - * these channels to the DWC_otg controller. After queueing transactions, the - * Periodic Tx FIFO Empty interrupt is enabled if there are more transactions - * to queue as Periodic Tx FIFO or request queue space becomes available. - * Otherwise, the Periodic Tx FIFO Empty interrupt is disabled. +/** + * dwc2_hcd_stop() - Halts the DWC_otg host mode operations in a clean manner + * + * @hsotg: Pointer to struct dwc2_hsotg * * Must be called with interrupt disabled and spinlock held */ -STATIC void dwc2_process_periodic_channels(struct dwc2_hsotg *hsotg) +void dwc2_hcd_stop(struct dwc2_hsotg *hsotg) { - struct list_head *qh_ptr; - struct dwc2_qh *qh; - u32 tx_status; - u32 fspcavail; - u32 gintmsk; - int status; - int no_queue_space = 0; - int no_fifo_space = 0; - u32 qspcavail; + dev_dbg(hsotg->dev, "DWC OTG HCD STOP\n"); - if (dbg_perio()) - dev_vdbg(hsotg->dev, "Queue periodic transactions\n"); + /* + * The root hub should be disconnected before this function is called. + * The disconnect will clear the QTD lists (via ..._hcd_urb_dequeue) + * and the QH lists (via ..._hcd_endpoint_disable). + */ - tx_status = DWC2_READ_4(hsotg, HPTXSTS); - qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> - TXSTS_QSPCAVAIL_SHIFT; - fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> - TXSTS_FSPCAVAIL_SHIFT; + /* Turn off all host-specific interrupts */ + dwc2_disable_host_interrupts(hsotg); - if (dbg_perio()) { - dev_vdbg(hsotg->dev, " P Tx Req Queue Space Avail (before queue): %d\n", - qspcavail); - dev_vdbg(hsotg->dev, " P Tx FIFO Space Avail (before queue): %d\n", - fspcavail); + /* Turn off the vbus power */ + dev_dbg(hsotg->dev, "PortPower off\n"); + dwc2_writel(hsotg, 0, HPRT0); +} + +/* Caller must hold driver lock */ +STATIC int dwc2_hcd_urb_enqueue(struct dwc2_hsotg *hsotg, + struct dwc2_hcd_urb *urb, struct dwc2_qh *qh, + struct dwc2_qtd *qtd) +{ + u32 intr_mask; + int retval; + int dev_speed; + + if (!hsotg->flags.b.port_connect_status) { + /* No longer connected */ + dev_err(hsotg->dev, "Not connected\n"); + return -ENODEV; } - qh_ptr = hsotg->periodic_sched_assigned.next; - while (qh_ptr != &hsotg->periodic_sched_assigned) { - tx_status = DWC2_READ_4(hsotg, HPTXSTS); - qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> - TXSTS_QSPCAVAIL_SHIFT; - if (qspcavail == 0) { - no_queue_space = 1; - break; - } + dev_speed = dwc2_host_get_speed(hsotg, urb->priv); - qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry); - if (!qh->channel) { - qh_ptr = qh_ptr->next; - continue; - } + /* Some configurations cannot support LS traffic on a FS root port */ + if ((dev_speed == USB_SPEED_LOW) && + (hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED) && + (hsotg->hw_params.hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI)) { + u32 hprt0 = dwc2_readl(hsotg, HPRT0); + u32 prtspd = (hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT; - /* Make sure EP's TT buffer is clean before queueing qtds */ - if (qh->tt_buffer_dirty) { - qh_ptr = qh_ptr->next; - continue; - } + if (prtspd == HPRT0_SPD_FULL_SPEED) + return -ENODEV; + } - /* - * Set a flag if we're queuing high-bandwidth in slave mode. - * The flag prevents any halts to get into the request queue in - * the middle of multiple high-bandwidth packets getting queued. - */ - if (hsotg->core_params->dma_enable <= 0 && - qh->channel->multi_count > 1) - hsotg->queuing_high_bandwidth = 1; + if (!qtd) + return -EINVAL; - fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> - TXSTS_FSPCAVAIL_SHIFT; - status = dwc2_queue_transaction(hsotg, qh->channel, fspcavail); - if (status < 0) { - no_fifo_space = 1; - break; - } + memset(qtd, 0, sizeof(*qtd)); /* Required for OpenBSD */ - /* - * In Slave mode, stay on the current transfer until there is - * nothing more to do or the high-bandwidth request count is - * reached. In DMA mode, only need to queue one request. The - * controller automatically handles multiple packets for - * high-bandwidth transfers. - */ - if (hsotg->core_params->dma_enable > 0 || status == 0 || - qh->channel->requests == qh->channel->multi_count) { - qh_ptr = qh_ptr->next; + dwc2_hcd_qtd_init(qtd, urb); + retval = dwc2_hcd_qtd_add(hsotg, qtd, qh); + if (retval) { + dev_err(hsotg->dev, + "DWC OTG HCD URB Enqueue failed adding QTD. Error status %d\n", + retval); + return retval; + } + + intr_mask = dwc2_readl(hsotg, GINTMSK); + if (!(intr_mask & GINTSTS_SOF)) { + enum dwc2_transaction_type tr_type; + + if (qtd->qh->ep_type == USB_ENDPOINT_XFER_BULK && + !(qtd->urb->flags & URB_GIVEBACK_ASAP)) /* - * Move the QH from the periodic assigned schedule to - * the periodic queued schedule + * Do not schedule SG transactions until qtd has + * URB_GIVEBACK_ASAP set */ - list_move(&qh->qh_list_entry, - &hsotg->periodic_sched_queued); + return 0; - /* done queuing high bandwidth */ - hsotg->queuing_high_bandwidth = 0; - } + tr_type = dwc2_hcd_select_transactions(hsotg); + if (tr_type != DWC2_TRANSACTION_NONE) + dwc2_hcd_queue_transactions(hsotg, tr_type); } - if (hsotg->core_params->dma_enable <= 0) { - tx_status = DWC2_READ_4(hsotg, HPTXSTS); - qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> - TXSTS_QSPCAVAIL_SHIFT; - fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> - TXSTS_FSPCAVAIL_SHIFT; - if (dbg_perio()) { - dev_vdbg(hsotg->dev, - " P Tx Req Queue Space Avail (after queue): %d\n", - qspcavail); - dev_vdbg(hsotg->dev, - " P Tx FIFO Space Avail (after queue): %d\n", - fspcavail); - } + return 0; +} - if (!list_empty(&hsotg->periodic_sched_assigned) || - no_queue_space || no_fifo_space) { - /* - * May need to queue more transactions as the request - * queue or Tx FIFO empties. Enable the periodic Tx - * FIFO empty interrupt. (Always use the half-empty - * level to ensure that new requests are loaded as - * soon as possible.) - */ - gintmsk = DWC2_READ_4(hsotg, GINTMSK); - gintmsk |= GINTSTS_PTXFEMP; - DWC2_WRITE_4(hsotg, GINTMSK, gintmsk); - } else { +/* Must be called with interrupt disabled and spinlock held */ +STATIC int dwc2_hcd_urb_dequeue(struct dwc2_hsotg *hsotg, + struct dwc2_hcd_urb *urb) +{ + struct dwc2_qh *qh; + struct dwc2_qtd *urb_qtd; + + urb_qtd = urb->qtd; + if (!urb_qtd) { + dev_dbg(hsotg->dev, "## Urb QTD is NULL ##\n"); + return -EINVAL; + } + + qh = urb_qtd->qh; + if (!qh) { + dev_dbg(hsotg->dev, "## Urb QTD QH is NULL ##\n"); + return -EINVAL; + } + + urb->priv = NULL; + + if (urb_qtd->in_process && qh->channel) { + dwc2_dump_channel_info(hsotg, qh->channel); + + /* The QTD is in process (it has been assigned to a channel) */ + if (hsotg->flags.b.port_connect_status) /* - * Disable the Tx FIFO empty interrupt since there are - * no more transactions that need to be queued right - * now. This function is called from interrupt - * handlers to queue more transactions as transfer - * states change. + * If still connected (i.e. in host mode), halt the + * channel so it can be used for other transfers. If + * no longer connected, the host registers can't be + * written to halt the channel since the core is in + * device mode. */ - gintmsk = DWC2_READ_4(hsotg, GINTMSK); - gintmsk &= ~GINTSTS_PTXFEMP; - DWC2_WRITE_4(hsotg, GINTMSK, gintmsk); + dwc2_hc_halt(hsotg, qh->channel, + DWC2_HC_XFER_URB_DEQUEUE); + } + + /* + * Free the QTD and clean up the associated QH. Leave the QH in the + * schedule if it has any remaining QTDs. + */ + if (!hsotg->params.dma_desc_enable) { + u8 in_process = urb_qtd->in_process; + + dwc2_hcd_qtd_unlink_and_free(hsotg, urb_qtd, qh); + if (in_process) { + dwc2_hcd_qh_deactivate(hsotg, qh, 0); + qh->channel = NULL; + } else if (list_empty(&qh->qtd_list)) { + dwc2_hcd_qh_unlink(hsotg, qh); } + } else { + dwc2_hcd_qtd_unlink_and_free(hsotg, urb_qtd, qh); } + + return 0; } -/* - * Processes active non-periodic channels and queues transactions for these - * channels to the DWC_otg controller. After queueing transactions, the NP Tx - * FIFO Empty interrupt is enabled if there are more transactions to queue as - * NP Tx FIFO or request queue space becomes available. Otherwise, the NP Tx - * FIFO Empty interrupt is disabled. - * - * Must be called with interrupt disabled and spinlock held - */ -STATIC void dwc2_process_non_periodic_channels(struct dwc2_hsotg *hsotg) +#if 0 +/* Must NOT be called with interrupt disabled or spinlock held */ +static int dwc2_hcd_endpoint_disable(struct dwc2_hsotg *hsotg, + struct usb_host_endpoint *ep, int retry) { - struct list_head *orig_qh_ptr; + struct dwc2_qtd *qtd, *qtd_tmp; struct dwc2_qh *qh; - u32 tx_status; - u32 qspcavail; - u32 fspcavail; - u32 gintmsk; - int status; - int no_queue_space = 0; - int no_fifo_space = 0; - int more_to_do = 0; + unsigned long flags; + int rc; - dev_vdbg(hsotg->dev, "Queue non-periodic transactions\n"); + spin_lock_irqsave(&hsotg->lock, flags); - tx_status = DWC2_READ_4(hsotg, GNPTXSTS); - qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> - TXSTS_QSPCAVAIL_SHIFT; - fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> - TXSTS_FSPCAVAIL_SHIFT; - dev_vdbg(hsotg->dev, " NP Tx Req Queue Space Avail (before queue): %d\n", - qspcavail); - dev_vdbg(hsotg->dev, " NP Tx FIFO Space Avail (before queue): %d\n", - fspcavail); + qh = ep->hcpriv; + if (!qh) { + rc = -EINVAL; + goto err; + } - /* - * Keep track of the starting point. Skip over the start-of-list - * entry. - */ - if (hsotg->non_periodic_qh_ptr == &hsotg->non_periodic_sched_active) - hsotg->non_periodic_qh_ptr = hsotg->non_periodic_qh_ptr->next; - orig_qh_ptr = hsotg->non_periodic_qh_ptr; + while (!list_empty(&qh->qtd_list) && retry--) { + if (retry == 0) { + dev_err(hsotg->dev, + "## timeout in dwc2_hcd_endpoint_disable() ##\n"); + rc = -EBUSY; + goto err; + } - /* - * Process once through the active list or until no more space is - * available in the request queue or the Tx FIFO - */ - do { - tx_status = DWC2_READ_4(hsotg, GNPTXSTS); - qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> - TXSTS_QSPCAVAIL_SHIFT; - if (hsotg->core_params->dma_enable <= 0 && qspcavail == 0) { - no_queue_space = 1; - break; + spin_unlock_irqrestore(&hsotg->lock, flags); + dwc2_msleep(20); + spin_lock_irqsave(&hsotg->lock, flags); + qh = ep->hcpriv; + if (!qh) { + rc = -EINVAL; + goto err; } + } - qh = list_entry(hsotg->non_periodic_qh_ptr, struct dwc2_qh, - qh_list_entry); - if (!qh->channel) - goto next; + dwc2_hcd_qh_unlink(hsotg, qh); - /* Make sure EP's TT buffer is clean before queueing qtds */ - if (qh->tt_buffer_dirty) - goto next; + /* Free each QTD in the QH's QTD list */ + list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry) + dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); - fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> - TXSTS_FSPCAVAIL_SHIFT; - status = dwc2_queue_transaction(hsotg, qh->channel, fspcavail); + ep->hcpriv = NULL; - if (status > 0) { - more_to_do = 1; - } else if (status < 0) { - no_fifo_space = 1; - break; - } -next: - /* Advance to next QH, skipping start-of-list entry */ - hsotg->non_periodic_qh_ptr = hsotg->non_periodic_qh_ptr->next; - if (hsotg->non_periodic_qh_ptr == - &hsotg->non_periodic_sched_active) - hsotg->non_periodic_qh_ptr = - hsotg->non_periodic_qh_ptr->next; - } while (hsotg->non_periodic_qh_ptr != orig_qh_ptr); + if (qh->channel && qh->channel->qh == qh) + qh->channel->qh = NULL; - if (hsotg->core_params->dma_enable <= 0) { - tx_status = DWC2_READ_4(hsotg, GNPTXSTS); - qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> - TXSTS_QSPCAVAIL_SHIFT; - fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> - TXSTS_FSPCAVAIL_SHIFT; - dev_vdbg(hsotg->dev, - " NP Tx Req Queue Space Avail (after queue): %d\n", - qspcavail); - dev_vdbg(hsotg->dev, - " NP Tx FIFO Space Avail (after queue): %d\n", - fspcavail); + spin_unlock_irqrestore(&hsotg->lock, flags); - if (more_to_do || no_queue_space || no_fifo_space) { - /* - * May need to queue more transactions as the request - * queue or Tx FIFO empties. Enable the non-periodic - * Tx FIFO empty interrupt. (Always use the half-empty - * level to ensure that new requests are loaded as - * soon as possible.) - */ - gintmsk = DWC2_READ_4(hsotg, GINTMSK); - gintmsk |= GINTSTS_NPTXFEMP; - DWC2_WRITE_4(hsotg, GINTMSK, gintmsk); - } else { - /* - * Disable the Tx FIFO empty interrupt since there are - * no more transactions that need to be queued right - * now. This function is called from interrupt - * handlers to queue more transactions as transfer - * states change. - */ - gintmsk = DWC2_READ_4(hsotg, GINTMSK); - gintmsk &= ~GINTSTS_NPTXFEMP; - DWC2_WRITE_4(hsotg, GINTMSK, gintmsk); - } - } + dwc2_hcd_qh_free(hsotg, qh); + + return 0; + +err: + ep->hcpriv = NULL; + spin_unlock_irqrestore(&hsotg->lock, flags); + + return rc; } +/* Must be called with interrupt disabled and spinlock held */ +static int dwc2_hcd_endpoint_reset(struct dwc2_hsotg *hsotg, + struct usb_host_endpoint *ep) +{ + struct dwc2_qh *qh = ep->hcpriv; + + if (!qh) + return -EINVAL; + + qh->data_toggle = DWC2_HC_PID_DATA0; + + return 0; +} +#endif + /** - * dwc2_hcd_queue_transactions() - Processes the currently active host channels - * and queues transactions for these channels to the DWC_otg controller. Called - * from the HCD interrupt handler functions. - * - * @hsotg: The HCD state structure - * @tr_type: The type(s) of transactions to queue (non-periodic, periodic, - * or both) + * dwc2_core_init() - Initializes the DWC_otg controller registers and + * prepares the core for device mode or host mode operation * - * Must be called with interrupt disabled and spinlock held + * @hsotg: Programming view of the DWC_otg controller + * @initial_setup: If true then this is the first init for this instance. */ -void dwc2_hcd_queue_transactions(struct dwc2_hsotg *hsotg, - enum dwc2_transaction_type tr_type) +int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup) { -#ifdef DWC2_DEBUG_SOF - dev_vdbg(hsotg->dev, "Queue Transactions\n"); -#endif - /* Process host channels associated with periodic transfers */ - if ((tr_type == DWC2_TRANSACTION_PERIODIC || - tr_type == DWC2_TRANSACTION_ALL) && - !list_empty(&hsotg->periodic_sched_assigned)) - dwc2_process_periodic_channels(hsotg); + u32 usbcfg, otgctl; + int retval; - /* Process host channels associated with non-periodic transfers */ - if (tr_type == DWC2_TRANSACTION_NON_PERIODIC || - tr_type == DWC2_TRANSACTION_ALL) { - if (!list_empty(&hsotg->non_periodic_sched_active)) { - dwc2_process_non_periodic_channels(hsotg); - } else { - /* - * Ensure NP Tx FIFO empty interrupt is disabled when - * there are no non-periodic transfers to process - */ - u32 gintmsk = DWC2_READ_4(hsotg, GINTMSK); + dev_dbg(hsotg->dev, "%s(%p)\n", __func__, hsotg); - gintmsk &= ~GINTSTS_NPTXFEMP; - DWC2_WRITE_4(hsotg, GINTMSK, gintmsk); + usbcfg = dwc2_readl(hsotg, GUSBCFG); + + /* Set ULPI External VBUS bit if needed */ + usbcfg &= ~GUSBCFG_ULPI_EXT_VBUS_DRV; + if (hsotg->params.phy_ulpi_ext_vbus) + usbcfg |= GUSBCFG_ULPI_EXT_VBUS_DRV; + + /* Set external TS Dline pulsing bit if needed */ + usbcfg &= ~GUSBCFG_TERMSELDLPULSE; + if (hsotg->params.ts_dline) + usbcfg |= GUSBCFG_TERMSELDLPULSE; + + dwc2_writel(hsotg, usbcfg, GUSBCFG); + + /* + * Reset the Controller + * + * We only need to reset the controller if this is a re-init. + * For the first init we know for sure that earlier code reset us (it + * needed to in order to properly detect various parameters). + */ + if (!initial_setup) { + retval = dwc2_core_reset(hsotg, false); + if (retval) { + dev_err(hsotg->dev, "%s(): Reset failed, aborting\n", + __func__); + return retval; } } -} + /* + * This needs to happen in FS mode before any other programming occurs + */ + retval = dwc2_phy_init(hsotg, initial_setup); + if (retval) + return retval; -STATIC void dwc2_conn_id_status_change(void *data) -{ - struct dwc2_hsotg *hsotg = data; - u32 count = 0; - u32 gotgctl; - unsigned long flags; + /* Program the GAHBCFG Register */ + retval = dwc2_gahbcfg_init(hsotg); + if (retval) + return retval; - dev_dbg(hsotg->dev, "%s()\n", __func__); + /* Program the GUSBCFG register */ + dwc2_gusbcfg_init(hsotg); - gotgctl = DWC2_READ_4(hsotg, GOTGCTL); - dev_dbg(hsotg->dev, "gotgctl=%0x\n", gotgctl); - dev_dbg(hsotg->dev, "gotgctl.b.conidsts=%d\n", - !!(gotgctl & GOTGCTL_CONID_B)); + /* Program the GOTGCTL register */ + otgctl = dwc2_readl(hsotg, GOTGCTL); + otgctl &= ~GOTGCTL_OTGVER; + dwc2_writel(hsotg, otgctl, GOTGCTL); - /* B-Device connector (Device Mode) */ - if (gotgctl & GOTGCTL_CONID_B) { - /* Wait for switch to device mode */ - dev_dbg(hsotg->dev, "connId B\n"); - while (!dwc2_is_device_mode(hsotg)) { - dev_info(hsotg->dev, - "Waiting for Peripheral Mode, Mode=%s\n", - dwc2_is_host_mode(hsotg) ? "Host" : - "Peripheral"); - usleep_range(20000, 40000); - if (++count > 250) - break; - } - if (count > 250) - dev_err(hsotg->dev, - "Connection id status change timed out\n"); - hsotg->op_state = OTG_STATE_B_PERIPHERAL; - dwc2_core_init(hsotg, false); - dwc2_enable_global_interrupts(hsotg); - spin_lock_irqsave(&hsotg->lock, flags); - dwc2_hsotg_core_init_disconnected(hsotg, false); - spin_unlock_irqrestore(&hsotg->lock, flags); - dwc2_hsotg_core_connect(hsotg); - } else { - /* A-Device connector (Host Mode) */ - dev_dbg(hsotg->dev, "connId A\n"); - while (!dwc2_is_host_mode(hsotg)) { - dev_info(hsotg->dev, "Waiting for Host Mode, Mode=%s\n", - dwc2_is_host_mode(hsotg) ? - "Host" : "Peripheral"); - usleep_range(20000, 40000); - if (++count > 250) - break; - } - if (count > 250) - dev_err(hsotg->dev, - "Connection id status change timed out\n"); - hsotg->op_state = OTG_STATE_A_HOST; + /* Clear the SRP success bit for FS-I2c */ + hsotg->srp_success = 0; - /* Initialize the Core for Host mode */ - dwc2_core_init(hsotg, false); - dwc2_enable_global_interrupts(hsotg); - dwc2_hcd_start(hsotg); + /* Enable common interrupts */ + dwc2_enable_common_interrupts(hsotg); + + /* + * Do device or host initialization based on mode during PCD and + * HCD initialization + */ + if (dwc2_is_host_mode(hsotg)) { + dev_dbg(hsotg->dev, "Host Mode\n"); + hsotg->op_state = OTG_STATE_A_HOST; + } else { + dev_dbg(hsotg->dev, "Device Mode\n"); + hsotg->op_state = OTG_STATE_B_PERIPHERAL; } + + return 0; } -void dwc2_wakeup_detected(void *data) +/** + * dwc2_core_host_init() - Initializes the DWC_otg controller registers for + * Host mode + * + * @hsotg: Programming view of DWC_otg controller + * + * This function flushes the Tx and Rx FIFOs and flushes any entries in the + * request queues. Host channels are reset to ensure that they are ready for + * performing transfers. + */ +static void dwc2_core_host_init(struct dwc2_hsotg *hsotg) { - struct dwc2_hsotg *hsotg = (struct dwc2_hsotg *)data; - u32 hprt0; + u32 hcfg, hfir, otgctl, usbcfg; - dev_dbg(hsotg->dev, "%s()\n", __func__); + dev_dbg(hsotg->dev, "%s(%p)\n", __func__, hsotg); + + /* Set HS/FS Timeout Calibration to 7 (max available value). + * The number of PHY clocks that the application programs in + * this field is added to the high/full speed interpacket timeout + * duration in the core to account for any additional delays + * introduced by the PHY. This can be required, because the delay + * introduced by the PHY in generating the linestate condition + * can vary from one PHY to another. + */ + usbcfg = dwc2_readl(hsotg, GUSBCFG); + usbcfg |= GUSBCFG_TOUTCAL(7); + dwc2_writel(hsotg, usbcfg, GUSBCFG); + + /* Restart the Phy Clock */ + dwc2_writel(hsotg, 0, PCGCTL); + + /* Initialize Host Configuration Register */ + dwc2_init_fs_ls_pclk_sel(hsotg); + if (hsotg->params.speed == DWC2_SPEED_PARAM_FULL || + hsotg->params.speed == DWC2_SPEED_PARAM_LOW) { + hcfg = dwc2_readl(hsotg, HCFG); + hcfg |= HCFG_FSLSSUPP; + dwc2_writel(hsotg, hcfg, HCFG); + } /* - * Clear the Resume after 70ms. (Need 20 ms minimum. Use 70 ms - * so that OPT tests pass with all PHYs.) + * This bit allows dynamic reloading of the HFIR register during + * runtime. This bit needs to be programmed during initial configuration + * and its value must not be changed during runtime. */ - hprt0 = dwc2_read_hprt0(hsotg); - dev_dbg(hsotg->dev, "Resume: HPRT0=%0x\n", hprt0); - hprt0 &= ~HPRT0_RES; - DWC2_WRITE_4(hsotg, HPRT0, hprt0); - dev_dbg(hsotg->dev, "Clear Resume: HPRT0=%0x\n", - DWC2_READ_4(hsotg, HPRT0)); + if (hsotg->params.reload_ctl) { + hfir = dwc2_readl(hsotg, HFIR); + hfir |= HFIR_RLDCTRL; + dwc2_writel(hsotg, hfir, HFIR); + } - dwc2_hcd_rem_wakeup(hsotg); - hsotg->bus_suspended = 0; + if (hsotg->params.dma_desc_enable) { + u32 op_mode = hsotg->hw_params.op_mode; - /* Change to L0 state */ - hsotg->lx_state = DWC2_L0; -} + if (hsotg->hw_params.snpsid < DWC2_CORE_REV_2_90a || + !hsotg->hw_params.dma_desc_enable || + op_mode == GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE || + op_mode == GHWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE || + op_mode == GHWCFG2_OP_MODE_UNDEFINED) { + dev_err(hsotg->dev, + "Hardware does not support descriptor DMA mode -\n"); + dev_err(hsotg->dev, + "falling back to buffer DMA mode.\n"); + hsotg->params.dma_desc_enable = false; + } else { + hcfg = dwc2_readl(hsotg, HCFG); + hcfg |= HCFG_DESCDMA; + dwc2_writel(hsotg, hcfg, HCFG); + } + } -/* Must NOT be called with interrupt disabled or spinlock held */ -STATIC void dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex) -{ - unsigned long flags; - u32 hprt0; - u32 pcgctl; - u32 gotgctl; + /* Configure data FIFO sizes */ + dwc2_config_fifos(hsotg); - dev_dbg(hsotg->dev, "%s()\n", __func__); + /* TODO - check this */ + /* Clear Host Set HNP Enable in the OTG Control Register */ + otgctl = dwc2_readl(hsotg, GOTGCTL); + otgctl &= ~GOTGCTL_HSTSETHNPEN; + dwc2_writel(hsotg, otgctl, GOTGCTL); - spin_lock_irqsave(&hsotg->lock, flags); + /* Make sure the FIFOs are flushed */ + dwc2_flush_tx_fifo(hsotg, 0x10 /* all TX FIFOs */); + dwc2_flush_rx_fifo(hsotg); - if (windex == hsotg->otg_port && dwc2_host_is_b_hnp_enabled(hsotg)) { - gotgctl = DWC2_READ_4(hsotg, GOTGCTL); - gotgctl |= GOTGCTL_HSTSETHNPEN; - DWC2_WRITE_4(hsotg, GOTGCTL, gotgctl); - hsotg->op_state = OTG_STATE_A_SUSPEND; - } + /* Clear Host Set HNP Enable in the OTG Control Register */ + otgctl = dwc2_readl(hsotg, GOTGCTL); + otgctl &= ~GOTGCTL_HSTSETHNPEN; + dwc2_writel(hsotg, otgctl, GOTGCTL); - hprt0 = dwc2_read_hprt0(hsotg); - hprt0 |= HPRT0_SUSP; - DWC2_WRITE_4(hsotg, HPRT0, hprt0); + if (!hsotg->params.dma_desc_enable) { + int num_channels, i; + u32 hcchar; - hsotg->bus_suspended = 1; + /* Flush out any leftover queued requests */ + num_channels = hsotg->params.host_channels; + for (i = 0; i < num_channels; i++) { + hcchar = dwc2_readl(hsotg, HCCHAR(i)); + if (hcchar & HCCHAR_CHENA) { + hcchar &= ~HCCHAR_CHENA; + hcchar |= HCCHAR_CHDIS; + hcchar &= ~HCCHAR_EPDIR; + dwc2_writel(hsotg, hcchar, HCCHAR(i)); + } + } - /* - * If hibernation is supported, Phy clock will be suspended - * after registers are backuped. - */ - if (!hsotg->core_params->hibernation) { - /* Suspend the Phy Clock */ - pcgctl = DWC2_READ_4(hsotg, PCGCTL); - pcgctl |= PCGCTL_STOPPCLK; - DWC2_WRITE_4(hsotg, PCGCTL, pcgctl); - udelay(10); + /* Halt all channels to put them into a known state */ + for (i = 0; i < num_channels; i++) { + hcchar = dwc2_readl(hsotg, HCCHAR(i)); + if (hcchar & HCCHAR_CHENA) { + hcchar |= HCCHAR_CHENA | HCCHAR_CHDIS; + hcchar &= ~HCCHAR_EPDIR; + dwc2_writel(hsotg, hcchar, HCCHAR(i)); + dev_dbg(hsotg->dev, "%s: Halt channel %d\n", + __func__, i); + + if (dwc2_hsotg_wait_bit_clear(hsotg, HCCHAR(i), + HCCHAR_CHENA, + 1000)) { + dev_warn(hsotg->dev, + "Unable to clear enable on channel %d\n", + i); + } + } + } } - /* For HNP the bus must be suspended for at least 200ms */ - if (dwc2_host_is_b_hnp_enabled(hsotg)) { - pcgctl = DWC2_READ_4(hsotg, PCGCTL); - pcgctl &= ~PCGCTL_STOPPCLK; - DWC2_WRITE_4(hsotg, PCGCTL, pcgctl); + /* Enable ACG feature in host mode, if supported */ + dwc2_enable_acg(hsotg); - spin_unlock_irqrestore(&hsotg->lock, flags); + /* Turn on the vbus power */ + dev_dbg(hsotg->dev, "Init: Port Power? op_state=%d\n", hsotg->op_state); + if (hsotg->op_state == OTG_STATE_A_HOST) { + u32 hprt0 = dwc2_read_hprt0(hsotg); - usleep_range(200000, 250000); - } else { - spin_unlock_irqrestore(&hsotg->lock, flags); + dev_dbg(hsotg->dev, "Init: Power Port (%d)\n", + !!(hprt0 & HPRT0_PWR)); + if (!(hprt0 & HPRT0_PWR)) { + hprt0 |= HPRT0_PWR; + dwc2_writel(hsotg, hprt0, HPRT0); + } } + + dwc2_enable_host_interrupts(hsotg); } -/* Must NOT be called with interrupt disabled or spinlock held */ -STATIC void dwc2_port_resume(struct dwc2_hsotg *hsotg) +/* + * Initializes dynamic portions of the DWC_otg HCD state + * + * Must be called with interrupt disabled and spinlock held + */ +STATIC void dwc2_hcd_reinit(struct dwc2_hsotg *hsotg) { - struct dwc2_softc *sc = hsotg->hsotg_sc; - unsigned long flags; - u32 hprt0; - u32 pcgctl; + struct dwc2_host_chan *chan, *chan_tmp; + int num_channels; + int i; - spin_lock_irqsave(&hsotg->lock, flags); + hsotg->flags.d32 = 0; + hsotg->non_periodic_qh_ptr = &hsotg->non_periodic_sched_active; + + if (hsotg->params.uframe_sched) { + hsotg->available_host_channels = + hsotg->params.host_channels; + } else { + hsotg->non_periodic_channels = 0; + hsotg->periodic_channels = 0; + } /* - * If hibernation is supported, Phy clock is already resumed - * after registers restore. + * Put all channels in the free channel list and clean up channel + * states */ - if (!hsotg->core_params->hibernation) { - pcgctl = DWC2_READ_4(hsotg, PCGCTL); - pcgctl &= ~PCGCTL_STOPPCLK; - DWC2_WRITE_4(hsotg, PCGCTL, pcgctl); - spin_unlock_irqrestore(&hsotg->lock, flags); - usleep_range(20000, 40000); - spin_lock_irqsave(&hsotg->lock, flags); + list_for_each_entry_safe(chan, chan_tmp, &hsotg->free_hc_list, + hc_list_entry) + list_del_init(&chan->hc_list_entry); + + num_channels = hsotg->params.host_channels; + for (i = 0; i < num_channels; i++) { + chan = hsotg->hc_ptr_array[i]; + list_add_tail(&chan->hc_list_entry, &hsotg->free_hc_list); + dwc2_hc_cleanup(hsotg, chan); } - hprt0 = dwc2_read_hprt0(hsotg); - hprt0 |= HPRT0_RES; - hprt0 &= ~HPRT0_SUSP; - DWC2_WRITE_4(hsotg, HPRT0, hprt0); - spin_unlock_irqrestore(&hsotg->lock, flags); + /* Initialize the DWC core for host mode operation */ + dwc2_core_host_init(hsotg); +} - usb_delay_ms(&sc->sc_bus, USB_RESUME_TIMEOUT); +STATIC void dwc2_hc_init_split(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + struct dwc2_qtd *qtd, struct dwc2_hcd_urb *urb) +{ + int hub_addr, hub_port; - spin_lock_irqsave(&hsotg->lock, flags); - hprt0 = dwc2_read_hprt0(hsotg); - hprt0 &= ~(HPRT0_RES | HPRT0_SUSP); - DWC2_WRITE_4(hsotg, HPRT0, hprt0); - hsotg->bus_suspended = 0; - spin_unlock_irqrestore(&hsotg->lock, flags); + chan->do_split = 1; + chan->xact_pos = qtd->isoc_split_pos; + chan->complete_split = qtd->complete_split; + dwc2_host_hub_info(hsotg, urb->priv, &hub_addr, &hub_port); + chan->hub_addr = (u8)hub_addr; + chan->hub_port = (u8)hub_port; } -/* Handles hub class-specific requests */ -int -dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, - u16 wvalue, u16 windex, char *buf, u16 wlength) +STATIC void dwc2_hc_init_xfer(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + struct dwc2_qtd *qtd) { - usb_hub_descriptor_t *hub_desc; - usb_port_status_t ps; - int retval = 0; - u32 hprt0; - u32 port_status; - u32 speed; - u32 pcgctl; + struct dwc2_hcd_urb *urb = qtd->urb; + struct dwc2_hcd_iso_packet_desc *frame_desc; - switch (typereq) { - case ClearHubFeature: - dev_dbg(hsotg->dev, "ClearHubFeature %1xh\n", wvalue); + switch (dwc2_hcd_get_pipe_type(&urb->pipe_info)) { + case USB_ENDPOINT_XFER_CONTROL: + chan->ep_type = USB_ENDPOINT_XFER_CONTROL; - switch (wvalue) { - case C_HUB_LOCAL_POWER: - case C_HUB_OVER_CURRENT: - /* Nothing required here */ + switch (qtd->control_phase) { + case DWC2_CONTROL_SETUP: + dev_vdbg(hsotg->dev, " Control setup transaction\n"); + chan->do_ping = 0; + chan->ep_is_in = 0; + chan->data_pid_start = DWC2_HC_PID_SETUP; + if (hsotg->params.host_dma) + chan->xfer_dma = urb->setup_dma; + else + chan->xfer_buf = urb->setup_packet; + chan->xfer_len = 8; break; - default: - retval = -EINVAL; - dev_err(hsotg->dev, - "ClearHubFeature request %1xh unknown\n", - wvalue); + case DWC2_CONTROL_DATA: + dev_vdbg(hsotg->dev, " Control data transaction\n"); + chan->data_pid_start = qtd->data_toggle; + break; + + case DWC2_CONTROL_STATUS: + /* + * Direction is opposite of data direction or IN if no + * data + */ + dev_vdbg(hsotg->dev, " Control status transaction\n"); + if (urb->length == 0) + chan->ep_is_in = 1; + else + chan->ep_is_in = + dwc2_hcd_is_pipe_out(&urb->pipe_info); + if (chan->ep_is_in) + chan->do_ping = 0; + chan->data_pid_start = DWC2_HC_PID_DATA1; + chan->xfer_len = 0; + if (hsotg->params.host_dma) + chan->xfer_dma = hsotg->status_buf_dma; + else + chan->xfer_buf = hsotg->status_buf; + break; } break; - case ClearPortFeature: -// if (wvalue != USB_PORT_FEAT_L1) - if (!windex || windex > 1) - goto error; - switch (wvalue) { - case USB_PORT_FEAT_ENABLE: - dev_dbg(hsotg->dev, - "ClearPortFeature USB_PORT_FEAT_ENABLE\n"); - hprt0 = dwc2_read_hprt0(hsotg); - hprt0 |= HPRT0_ENA; - DWC2_WRITE_4(hsotg, HPRT0, hprt0); - break; + case USB_ENDPOINT_XFER_BULK: + chan->ep_type = USB_ENDPOINT_XFER_BULK; + break; - case USB_PORT_FEAT_SUSPEND: - dev_dbg(hsotg->dev, - "ClearPortFeature USB_PORT_FEAT_SUSPEND\n"); - if (hsotg->bus_suspended) - dwc2_port_resume(hsotg); - break; + case USB_ENDPOINT_XFER_INT: + chan->ep_type = USB_ENDPOINT_XFER_INT; + break; - case USB_PORT_FEAT_POWER: - dev_dbg(hsotg->dev, - "ClearPortFeature USB_PORT_FEAT_POWER\n"); - hprt0 = dwc2_read_hprt0(hsotg); - hprt0 &= ~HPRT0_PWR; - DWC2_WRITE_4(hsotg, HPRT0, hprt0); + case USB_ENDPOINT_XFER_ISOC: + chan->ep_type = USB_ENDPOINT_XFER_ISOC; + if (hsotg->params.dma_desc_enable) break; - case USB_PORT_FEAT_INDICATOR: + frame_desc = &urb->iso_descs[qtd->isoc_frame_index]; + frame_desc->status = 0; + + if (hsotg->params.host_dma) { + chan->xfer_dma = urb->dma; + chan->xfer_dma += frame_desc->offset + + qtd->isoc_split_offset; + } else { + chan->xfer_buf = urb->buf; + chan->xfer_buf += frame_desc->offset + + qtd->isoc_split_offset; + } + + chan->xfer_len = frame_desc->length - qtd->isoc_split_offset; + + if (chan->xact_pos == DWC2_HCSPLT_XACTPOS_ALL) { + if (chan->xfer_len <= 188) + chan->xact_pos = DWC2_HCSPLT_XACTPOS_ALL; + else + chan->xact_pos = DWC2_HCSPLT_XACTPOS_BEGIN; + } + break; + } +} + +static int dwc2_alloc_split_dma_aligned_buf(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh, + struct dwc2_host_chan *chan) +{ +#if 0 + if (!hsotg->unaligned_cache || + chan->max_packet > DWC2_KMEM_UNALIGNED_BUF_SIZE) + return -ENOMEM; + + if (!qh->dw_align_buf) { + qh->dw_align_buf = kmem_cache_alloc(hsotg->unaligned_cache, + GFP_ATOMIC | GFP_DMA); + if (!qh->dw_align_buf) + return -ENOMEM; + } + + qh->dw_align_buf_dma = dma_map_single(hsotg->dev, qh->dw_align_buf, + DWC2_KMEM_UNALIGNED_BUF_SIZE, + DMA_FROM_DEVICE); + + if (dma_mapping_error(hsotg->dev, qh->dw_align_buf_dma)) { + dev_err(hsotg->dev, "can't map align_buf\n"); + chan->align_buf = 0; + return -EINVAL; + } + + chan->align_buf = qh->dw_align_buf_dma; +#endif + return 0; +} + +#define DWC2_USB_DMA_ALIGN 4 + +#if 0 +static void dwc2_free_dma_aligned_buffer(struct urb *urb) +{ + void *stored_xfer_buffer; + size_t length; + + if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER)) + return; + + /* Restore urb->transfer_buffer from the end of the allocated area */ + memcpy(&stored_xfer_buffer, + PTR_ALIGN(urb->transfer_buffer + urb->transfer_buffer_length, + dma_get_cache_alignment()), + sizeof(urb->transfer_buffer)); + + if (usb_urb_dir_in(urb)) { + if (usb_pipeisoc(urb->pipe)) + length = urb->transfer_buffer_length; + else + length = urb->actual_length; + + memcpy(stored_xfer_buffer, urb->transfer_buffer, length); + } + kfree(urb->transfer_buffer); + urb->transfer_buffer = stored_xfer_buffer; + + urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; +} + +static int dwc2_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags) +{ + void *kmalloc_ptr; + size_t kmalloc_size; + + if (urb->num_sgs || urb->sg || + urb->transfer_buffer_length == 0 || + !((uintptr_t)urb->transfer_buffer & (DWC2_USB_DMA_ALIGN - 1))) + return 0; + + /* + * Allocate a buffer with enough padding for original transfer_buffer + * pointer. This allocation is guaranteed to be aligned properly for + * DMA + */ + kmalloc_size = urb->transfer_buffer_length + + (dma_get_cache_alignment() - 1) + + sizeof(urb->transfer_buffer); + + kmalloc_ptr = kmalloc(kmalloc_size, mem_flags); + if (!kmalloc_ptr) + return -ENOMEM; + + /* + * Position value of original urb->transfer_buffer pointer to the end + * of allocation for later referencing + */ + memcpy(PTR_ALIGN(kmalloc_ptr + urb->transfer_buffer_length, + dma_get_cache_alignment()), + &urb->transfer_buffer, sizeof(urb->transfer_buffer)); + + if (usb_urb_dir_out(urb)) + memcpy(kmalloc_ptr, urb->transfer_buffer, + urb->transfer_buffer_length); + urb->transfer_buffer = kmalloc_ptr; + + urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER; + + return 0; +} + +static int dwc2_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + int ret; + + /* We assume setup_dma is always aligned; warn if not */ + WARN_ON_ONCE(urb->setup_dma && + (urb->setup_dma & (DWC2_USB_DMA_ALIGN - 1))); + + ret = dwc2_alloc_dma_aligned_buffer(urb, mem_flags); + if (ret) + return ret; + + ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); + if (ret) + dwc2_free_dma_aligned_buffer(urb); + + return ret; +} + +static void dwc2_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) +{ + usb_hcd_unmap_urb_for_dma(hcd, urb); + dwc2_free_dma_aligned_buffer(urb); +} +#endif + +/** + * dwc2_assign_and_init_hc() - Assigns transactions from a QTD to a free host + * channel and initializes the host channel to perform the transactions. The + * host channel is removed from the free list. + * + * @hsotg: The HCD state structure + * @qh: Transactions from the first QTD for this QH are selected and assigned + * to a free host channel + */ +STATIC int dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + struct dwc2_host_chan *chan; + struct dwc2_hcd_urb *urb; + struct dwc2_qtd *qtd; + + if (dbg_qh(qh)) + dev_vdbg(hsotg->dev, "%s(%p,%p)\n", __func__, hsotg, qh); + + if (list_empty(&qh->qtd_list)) { + dev_dbg(hsotg->dev, "No QTDs in QH list\n"); + return -ENOMEM; + } + + if (list_empty(&hsotg->free_hc_list)) { + dev_dbg(hsotg->dev, "No free channel to assign\n"); + return -ENOMEM; + } + + chan = list_first_entry(&hsotg->free_hc_list, struct dwc2_host_chan, + hc_list_entry); + + /* Remove host channel from free list */ + list_del_init(&chan->hc_list_entry); + + qtd = list_first_entry(&qh->qtd_list, struct dwc2_qtd, qtd_list_entry); + urb = qtd->urb; + qh->channel = chan; + qtd->in_process = 1; + + /* + * Use usb_pipedevice to determine device address. This address is + * 0 before the SET_ADDRESS command and the correct address afterward. + */ + chan->dev_addr = dwc2_hcd_get_dev_addr(&urb->pipe_info); + chan->ep_num = dwc2_hcd_get_ep_num(&urb->pipe_info); + chan->speed = qh->dev_speed; + chan->max_packet = qh->maxp; + + chan->xfer_started = 0; + chan->halt_status = DWC2_HC_XFER_NO_HALT_STATUS; + chan->error_state = (qtd->error_count > 0); + chan->halt_on_queue = 0; + chan->halt_pending = 0; + chan->requests = 0; + + /* + * The following values may be modified in the transfer type section + * below. The xfer_len value may be reduced when the transfer is + * started to accommodate the max widths of the XferSize and PktCnt + * fields in the HCTSIZn register. + */ + + chan->ep_is_in = (dwc2_hcd_is_pipe_in(&urb->pipe_info) != 0); + if (chan->ep_is_in) + chan->do_ping = 0; + else + chan->do_ping = qh->ping_state; + + chan->data_pid_start = qh->data_toggle; + chan->multi_count = 1; + + if (urb->actual_length > urb->length && + !dwc2_hcd_is_pipe_in(&urb->pipe_info)) + urb->actual_length = urb->length; + + if (hsotg->params.host_dma) + chan->xfer_dma = urb->dma + urb->actual_length; + else + chan->xfer_buf = (u8 *)urb->buf + urb->actual_length; + + chan->xfer_len = urb->length - urb->actual_length; + chan->xfer_count = 0; + + /* Set the split attributes if required */ + if (qh->do_split) + dwc2_hc_init_split(hsotg, chan, qtd, urb); + else + chan->do_split = 0; + + /* Set the transfer attributes */ + dwc2_hc_init_xfer(hsotg, chan, qtd); + + /* For non-dword aligned buffers */ + if (hsotg->params.host_dma && qh->do_split && + chan->ep_is_in && (chan->xfer_dma & 0x3)) { + dev_vdbg(hsotg->dev, "Non-aligned buffer\n"); + if (dwc2_alloc_split_dma_aligned_buf(hsotg, qh, chan)) { + dev_err(hsotg->dev, + "Failed to allocate memory to handle non-aligned buffer\n"); + /* Add channel back to free list */ + chan->align_buf = 0; + chan->multi_count = 0; + list_add_tail(&chan->hc_list_entry, + &hsotg->free_hc_list); + qtd->in_process = 0; + qh->channel = NULL; + return -ENOMEM; + } + } else { + /* + * We assume that DMA is always aligned in non-split + * case or split out case. Warn if not. + */ + WARN_ON_ONCE(hsotg->params.host_dma && + (chan->xfer_dma & 0x3)); + chan->align_buf = 0; + } + + if (chan->ep_type == USB_ENDPOINT_XFER_INT || + chan->ep_type == USB_ENDPOINT_XFER_ISOC) + /* + * This value may be modified when the transfer is started + * to reflect the actual transfer length + */ + chan->multi_count = qh->maxp_mult; + + if (hsotg->params.dma_desc_enable) { + chan->desc_list_addr = qh->desc_list_dma; + chan->desc_list_sz = qh->desc_list_sz; + } + + dwc2_hc_init(hsotg, chan); + chan->qh = qh; + + return 0; +} + +/** + * dwc2_hcd_select_transactions() - Selects transactions from the HCD transfer + * schedule and assigns them to available host channels. Called from the HCD + * interrupt handler functions. + * + * @hsotg: The HCD state structure + * + * Return: The types of new transactions that were assigned to host channels + */ +enum dwc2_transaction_type dwc2_hcd_select_transactions( + struct dwc2_hsotg *hsotg) +{ + enum dwc2_transaction_type ret_val = DWC2_TRANSACTION_NONE; + struct list_head *qh_ptr; + struct dwc2_qh *qh; + int num_channels; + +#ifdef DWC2_DEBUG_SOF + dev_vdbg(hsotg->dev, " Select Transactions\n"); +#endif + + /* Process entries in the periodic ready list */ + qh_ptr = hsotg->periodic_sched_ready.next; + while (qh_ptr != &hsotg->periodic_sched_ready) { + if (list_empty(&hsotg->free_hc_list)) + break; + if (hsotg->params.uframe_sched) { + if (hsotg->available_host_channels <= 1) + break; + hsotg->available_host_channels--; + } + qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry); + if (dwc2_assign_and_init_hc(hsotg, qh)) + break; + + /* + * Move the QH from the periodic ready schedule to the + * periodic assigned schedule + */ + qh_ptr = qh_ptr->next; + list_move_tail(&qh->qh_list_entry, + &hsotg->periodic_sched_assigned); + ret_val = DWC2_TRANSACTION_PERIODIC; + } + + /* + * Process entries in the inactive portion of the non-periodic + * schedule. Some free host channels may not be used if they are + * reserved for periodic transfers. + */ + num_channels = hsotg->params.host_channels; + qh_ptr = hsotg->non_periodic_sched_inactive.next; + while (qh_ptr != &hsotg->non_periodic_sched_inactive) { + if (!hsotg->params.uframe_sched && + hsotg->non_periodic_channels >= num_channels - + hsotg->periodic_channels) + break; + if (list_empty(&hsotg->free_hc_list)) + break; + qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry); + if (hsotg->params.uframe_sched) { + if (hsotg->available_host_channels < 1) + break; + hsotg->available_host_channels--; + } + + if (dwc2_assign_and_init_hc(hsotg, qh)) + break; + + /* + * Move the QH from the non-periodic inactive schedule to the + * non-periodic active schedule + */ + qh_ptr = qh_ptr->next; + list_move_tail(&qh->qh_list_entry, + &hsotg->non_periodic_sched_active); + + if (ret_val == DWC2_TRANSACTION_NONE) + ret_val = DWC2_TRANSACTION_NON_PERIODIC; + else + ret_val = DWC2_TRANSACTION_ALL; + + if (!hsotg->params.uframe_sched) + hsotg->non_periodic_channels++; + } + + return ret_val; +} + +/** + * dwc2_queue_transaction() - Attempts to queue a single transaction request for + * a host channel associated with either a periodic or non-periodic transfer + * + * @hsotg: The HCD state structure + * @chan: Host channel descriptor associated with either a periodic or + * non-periodic transfer + * @fifo_dwords_avail: Number of DWORDs available in the periodic Tx FIFO + * for periodic transfers or the non-periodic Tx FIFO + * for non-periodic transfers + * + * Return: 1 if a request is queued and more requests may be needed to + * complete the transfer, 0 if no more requests are required for this + * transfer, -1 if there is insufficient space in the Tx FIFO + * + * This function assumes that there is space available in the appropriate + * request queue. For an OUT transfer or SETUP transaction in Slave mode, + * it checks whether space is available in the appropriate Tx FIFO. + * + * Must be called with interrupt disabled and spinlock held + */ +STATIC int dwc2_queue_transaction(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + u16 fifo_dwords_avail) +{ + int retval = 0; + + if (chan->do_split) + /* Put ourselves on the list to keep order straight */ + list_move_tail(&chan->split_order_list_entry, + &hsotg->split_order); + + if (hsotg->params.host_dma && chan->qh) { + if (hsotg->params.dma_desc_enable) { + if (!chan->xfer_started || + chan->ep_type == USB_ENDPOINT_XFER_ISOC) { + dwc2_hcd_start_xfer_ddma(hsotg, chan->qh); + chan->qh->ping_state = 0; + } + } else if (!chan->xfer_started) { + dwc2_hc_start_transfer(hsotg, chan); + chan->qh->ping_state = 0; + } + } else if (chan->halt_pending) { + /* Don't queue a request if the channel has been halted */ + } else if (chan->halt_on_queue) { + dwc2_hc_halt(hsotg, chan, chan->halt_status); + } else if (chan->do_ping) { + if (!chan->xfer_started) + dwc2_hc_start_transfer(hsotg, chan); + } else if (!chan->ep_is_in || + chan->data_pid_start == DWC2_HC_PID_SETUP) { + if ((fifo_dwords_avail * 4) >= chan->max_packet) { + if (!chan->xfer_started) { + dwc2_hc_start_transfer(hsotg, chan); + retval = 1; + } else { + retval = dwc2_hc_continue_transfer(hsotg, chan); + } + } else { + retval = -1; + } + } else { + if (!chan->xfer_started) { + dwc2_hc_start_transfer(hsotg, chan); + retval = 1; + } else { + retval = dwc2_hc_continue_transfer(hsotg, chan); + } + } + + return retval; +} + +/* + * Processes periodic channels for the next frame and queues transactions for + * these channels to the DWC_otg controller. After queueing transactions, the + * Periodic Tx FIFO Empty interrupt is enabled if there are more transactions + * to queue as Periodic Tx FIFO or request queue space becomes available. + * Otherwise, the Periodic Tx FIFO Empty interrupt is disabled. + * + * Must be called with interrupt disabled and spinlock held + */ +STATIC void dwc2_process_periodic_channels(struct dwc2_hsotg *hsotg) +{ + struct list_head *qh_ptr; + struct dwc2_qh *qh; + u32 tx_status; + u32 fspcavail; + u32 gintmsk; + int status; + bool no_queue_space = false; + bool no_fifo_space = false; + u32 qspcavail; + + /* If empty list then just adjust interrupt enables */ + if (list_empty(&hsotg->periodic_sched_assigned)) + goto exit; + + if (dbg_perio()) + dev_vdbg(hsotg->dev, "Queue periodic transactions\n"); + + tx_status = dwc2_readl(hsotg, HPTXSTS); + qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> + TXSTS_QSPCAVAIL_SHIFT; + fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> + TXSTS_FSPCAVAIL_SHIFT; + + if (dbg_perio()) { + dev_vdbg(hsotg->dev, " P Tx Req Queue Space Avail (before queue): %d\n", + qspcavail); + dev_vdbg(hsotg->dev, " P Tx FIFO Space Avail (before queue): %d\n", + fspcavail); + } + + qh_ptr = hsotg->periodic_sched_assigned.next; + while (qh_ptr != &hsotg->periodic_sched_assigned) { + tx_status = dwc2_readl(hsotg, HPTXSTS); + qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> + TXSTS_QSPCAVAIL_SHIFT; + if (qspcavail == 0) { + no_queue_space = true; + break; + } + + qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry); + if (!qh->channel) { + qh_ptr = qh_ptr->next; + continue; + } + + /* Make sure EP's TT buffer is clean before queueing qtds */ + if (qh->tt_buffer_dirty) { + qh_ptr = qh_ptr->next; + continue; + } + + /* + * Set a flag if we're queuing high-bandwidth in slave mode. + * The flag prevents any halts to get into the request queue in + * the middle of multiple high-bandwidth packets getting queued. + */ + if (!hsotg->params.host_dma && + qh->channel->multi_count > 1) + hsotg->queuing_high_bandwidth = 1; + + fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> + TXSTS_FSPCAVAIL_SHIFT; + status = dwc2_queue_transaction(hsotg, qh->channel, fspcavail); + if (status < 0) { + no_fifo_space = true; + break; + } + + /* + * In Slave mode, stay on the current transfer until there is + * nothing more to do or the high-bandwidth request count is + * reached. In DMA mode, only need to queue one request. The + * controller automatically handles multiple packets for + * high-bandwidth transfers. + */ + if (hsotg->params.host_dma || status == 0 || + qh->channel->requests == qh->channel->multi_count) { + qh_ptr = qh_ptr->next; + /* + * Move the QH from the periodic assigned schedule to + * the periodic queued schedule + */ + list_move_tail(&qh->qh_list_entry, + &hsotg->periodic_sched_queued); + + /* done queuing high bandwidth */ + hsotg->queuing_high_bandwidth = 0; + } + } + +exit: + if (no_queue_space || no_fifo_space || + (!hsotg->params.host_dma && + !list_empty(&hsotg->periodic_sched_assigned))) { + /* + * May need to queue more transactions as the request + * queue or Tx FIFO empties. Enable the periodic Tx + * FIFO empty interrupt. (Always use the half-empty + * level to ensure that new requests are loaded as + * soon as possible.) + */ + gintmsk = dwc2_readl(hsotg, GINTMSK); + if (!(gintmsk & GINTSTS_PTXFEMP)) { + gintmsk |= GINTSTS_PTXFEMP; + dwc2_writel(hsotg, gintmsk, GINTMSK); + } + } else { + /* + * Disable the Tx FIFO empty interrupt since there are + * no more transactions that need to be queued right + * now. This function is called from interrupt + * handlers to queue more transactions as transfer + * states change. + */ + gintmsk = dwc2_readl(hsotg, GINTMSK); + if (gintmsk & GINTSTS_PTXFEMP) { + gintmsk &= ~GINTSTS_PTXFEMP; + dwc2_writel(hsotg, gintmsk, GINTMSK); + } + } +} + +/* + * Processes active non-periodic channels and queues transactions for these + * channels to the DWC_otg controller. After queueing transactions, the NP Tx + * FIFO Empty interrupt is enabled if there are more transactions to queue as + * NP Tx FIFO or request queue space becomes available. Otherwise, the NP Tx + * FIFO Empty interrupt is disabled. + * + * Must be called with interrupt disabled and spinlock held + */ +STATIC void dwc2_process_non_periodic_channels(struct dwc2_hsotg *hsotg) +{ + struct list_head *orig_qh_ptr; + struct dwc2_qh *qh; + u32 tx_status; + u32 qspcavail; + u32 fspcavail; + u32 gintmsk; + int status; + int no_queue_space = 0; + int no_fifo_space = 0; + int more_to_do = 0; + + dev_vdbg(hsotg->dev, "Queue non-periodic transactions\n"); + + tx_status = dwc2_readl(hsotg, GNPTXSTS); + qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> + TXSTS_QSPCAVAIL_SHIFT; + fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> + TXSTS_FSPCAVAIL_SHIFT; + dev_vdbg(hsotg->dev, " NP Tx Req Queue Space Avail (before queue): %d\n", + qspcavail); + dev_vdbg(hsotg->dev, " NP Tx FIFO Space Avail (before queue): %d\n", + fspcavail); + + /* + * Keep track of the starting point. Skip over the start-of-list + * entry. + */ + if (hsotg->non_periodic_qh_ptr == &hsotg->non_periodic_sched_active) + hsotg->non_periodic_qh_ptr = hsotg->non_periodic_qh_ptr->next; + orig_qh_ptr = hsotg->non_periodic_qh_ptr; + + /* + * Process once through the active list or until no more space is + * available in the request queue or the Tx FIFO + */ + do { + tx_status = dwc2_readl(hsotg, GNPTXSTS); + qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> + TXSTS_QSPCAVAIL_SHIFT; + if (!hsotg->params.host_dma && qspcavail == 0) { + no_queue_space = 1; + break; + } + + qh = list_entry(hsotg->non_periodic_qh_ptr, struct dwc2_qh, + qh_list_entry); + if (!qh->channel) + goto next; + + /* Make sure EP's TT buffer is clean before queueing qtds */ + if (qh->tt_buffer_dirty) + goto next; + + fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> + TXSTS_FSPCAVAIL_SHIFT; + status = dwc2_queue_transaction(hsotg, qh->channel, fspcavail); + + if (status > 0) { + more_to_do = 1; + } else if (status < 0) { + no_fifo_space = 1; + break; + } +next: + /* Advance to next QH, skipping start-of-list entry */ + hsotg->non_periodic_qh_ptr = hsotg->non_periodic_qh_ptr->next; + if (hsotg->non_periodic_qh_ptr == + &hsotg->non_periodic_sched_active) + hsotg->non_periodic_qh_ptr = + hsotg->non_periodic_qh_ptr->next; + } while (hsotg->non_periodic_qh_ptr != orig_qh_ptr); + + if (!hsotg->params.host_dma) { + tx_status = dwc2_readl(hsotg, GNPTXSTS); + qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> + TXSTS_QSPCAVAIL_SHIFT; + fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> + TXSTS_FSPCAVAIL_SHIFT; + dev_vdbg(hsotg->dev, + " NP Tx Req Queue Space Avail (after queue): %d\n", + qspcavail); + dev_vdbg(hsotg->dev, + " NP Tx FIFO Space Avail (after queue): %d\n", + fspcavail); + + if (more_to_do || no_queue_space || no_fifo_space) { + /* + * May need to queue more transactions as the request + * queue or Tx FIFO empties. Enable the non-periodic + * Tx FIFO empty interrupt. (Always use the half-empty + * level to ensure that new requests are loaded as + * soon as possible.) + */ + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk |= GINTSTS_NPTXFEMP; + dwc2_writel(hsotg, gintmsk, GINTMSK); + } else { + /* + * Disable the Tx FIFO empty interrupt since there are + * no more transactions that need to be queued right + * now. This function is called from interrupt + * handlers to queue more transactions as transfer + * states change. + */ + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk &= ~GINTSTS_NPTXFEMP; + dwc2_writel(hsotg, gintmsk, GINTMSK); + } + } +} + +/** + * dwc2_hcd_queue_transactions() - Processes the currently active host channels + * and queues transactions for these channels to the DWC_otg controller. Called + * from the HCD interrupt handler functions. + * + * @hsotg: The HCD state structure + * @tr_type: The type(s) of transactions to queue (non-periodic, periodic, + * or both) + * + * Must be called with interrupt disabled and spinlock held + */ +void dwc2_hcd_queue_transactions(struct dwc2_hsotg *hsotg, + enum dwc2_transaction_type tr_type) +{ +#ifdef DWC2_DEBUG_SOF + dev_vdbg(hsotg->dev, "Queue Transactions\n"); +#endif + /* Process host channels associated with periodic transfers */ + if (tr_type == DWC2_TRANSACTION_PERIODIC || + tr_type == DWC2_TRANSACTION_ALL) + dwc2_process_periodic_channels(hsotg); + + /* Process host channels associated with non-periodic transfers */ + if (tr_type == DWC2_TRANSACTION_NON_PERIODIC || + tr_type == DWC2_TRANSACTION_ALL) { + if (!list_empty(&hsotg->non_periodic_sched_active)) { + dwc2_process_non_periodic_channels(hsotg); + } else { + /* + * Ensure NP Tx FIFO empty interrupt is disabled when + * there are no non-periodic transfers to process + */ + u32 gintmsk = dwc2_readl(hsotg, GINTMSK); + + gintmsk &= ~GINTSTS_NPTXFEMP; + dwc2_writel(hsotg, gintmsk, GINTMSK); + } + } +} + +STATIC void dwc2_conn_id_status_change(void *data) +{ + struct dwc2_hsotg *hsotg = data; + + u32 count = 0; + u32 gotgctl; + unsigned long flags; + + dev_dbg(hsotg->dev, "%s()\n", __func__); + + gotgctl = dwc2_readl(hsotg, GOTGCTL); + dev_dbg(hsotg->dev, "gotgctl=%0x\n", gotgctl); + dev_dbg(hsotg->dev, "gotgctl.b.conidsts=%d\n", + !!(gotgctl & GOTGCTL_CONID_B)); + + /* B-Device connector (Device Mode) */ + if (gotgctl & GOTGCTL_CONID_B) { + dwc2_vbus_supply_exit(hsotg); + /* Wait for switch to device mode */ + dev_dbg(hsotg->dev, "connId B\n"); + if (hsotg->bus_suspended) { + dev_info(hsotg->dev, + "Do port resume before switching to device mode\n"); + dwc2_port_resume(hsotg); + } + while (!dwc2_is_device_mode(hsotg)) { + dev_info(hsotg->dev, + "Waiting for Peripheral Mode, Mode=%s\n", + dwc2_is_host_mode(hsotg) ? "Host" : + "Peripheral"); + dwc2_msleep(20); + /* + * Sometimes the initial GOTGCTRL read is wrong, so + * check it again and jump to host mode if that was + * the case. + */ + gotgctl = dwc2_readl(hsotg, GOTGCTL); + if (!(gotgctl & GOTGCTL_CONID_B)) + goto host; + if (++count > 250) + break; + } + if (count > 250) + dev_err(hsotg->dev, + "Connection id status change timed out\n"); + + /* + * Exit Partial Power Down without restoring registers. + * No need to check the return value as registers + * are not being restored. + */ + if (hsotg->in_ppd && hsotg->lx_state == DWC2_L2) + dwc2_exit_partial_power_down(hsotg, 0, false); + + hsotg->op_state = OTG_STATE_B_PERIPHERAL; + dwc2_core_init(hsotg, false); + dwc2_enable_global_interrupts(hsotg); + spin_lock_irqsave(&hsotg->lock, flags); + dwc2_hsotg_core_init_disconnected(hsotg, false); + spin_unlock_irqrestore(&hsotg->lock, flags); + /* Enable ACG feature in device mode,if supported */ + dwc2_enable_acg(hsotg); + dwc2_hsotg_core_connect(hsotg); + } else { +host: + /* A-Device connector (Host Mode) */ + dev_dbg(hsotg->dev, "connId A\n"); + while (!dwc2_is_host_mode(hsotg)) { + dev_info(hsotg->dev, "Waiting for Host Mode, Mode=%s\n", + dwc2_is_host_mode(hsotg) ? + "Host" : "Peripheral"); + dwc2_msleep(20); + if (++count > 250) + break; + } + if (count > 250) + dev_err(hsotg->dev, + "Connection id status change timed out\n"); + + spin_lock_irqsave(&hsotg->lock, flags); + dwc2_hsotg_disconnect(hsotg); + spin_unlock_irqrestore(&hsotg->lock, flags); + + hsotg->op_state = OTG_STATE_A_HOST; + /* Initialize the Core for Host mode */ + dwc2_core_init(hsotg, false); + dwc2_enable_global_interrupts(hsotg); + dwc2_hcd_start(hsotg); + } +} + +void dwc2_wakeup_detected(void *data) +{ + struct dwc2_hsotg *hsotg = (struct dwc2_hsotg *)data; + u32 hprt0; + + dev_dbg(hsotg->dev, "%s()\n", __func__); + + /* + * Clear the Resume after 70ms. (Need 20 ms minimum. Use 70 ms + * so that OPT tests pass with all PHYs.) + */ + hprt0 = dwc2_read_hprt0(hsotg); + dev_dbg(hsotg->dev, "Resume: HPRT0=%0x\n", hprt0); + hprt0 &= ~HPRT0_RES; + dwc2_writel(hsotg, hprt0, HPRT0); + dev_dbg(hsotg->dev, "Clear Resume: HPRT0=%0x\n", + dwc2_readl(hsotg, HPRT0)); + + dwc2_hcd_rem_wakeup(hsotg); + hsotg->bus_suspended = false; + + /* Change to L0 state */ + hsotg->lx_state = DWC2_L0; +} + +static int dwc2_host_is_b_hnp_enabled(struct dwc2_hsotg *hsotg) +{ +#if 0 + struct usb_hcd *hcd = dwc2_hsotg_to_hcd(hsotg); + + return hcd->self.b_hnp_enable; +#endif + return 0; +} + +/** + * dwc2_port_suspend() - Put controller in suspend mode for host. + * + * @hsotg: Programming view of the DWC_otg controller + * @windex: The control request wIndex field + * + * Return: non-zero if failed to enter suspend mode for host. + * + * This function is for entering Host mode suspend. + * Must NOT be called with interrupt disabled or spinlock held. + */ +int dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex) +{ + unsigned long flags; + u32 pcgctl; + u32 gotgctl; + int ret = 0; + + dev_dbg(hsotg->dev, "%s()\n", __func__); + + spin_lock_irqsave(&hsotg->lock, flags); + + if (windex == hsotg->otg_port && dwc2_host_is_b_hnp_enabled(hsotg)) { + gotgctl = dwc2_readl(hsotg, GOTGCTL); + gotgctl |= GOTGCTL_HSTSETHNPEN; + dwc2_writel(hsotg, gotgctl, GOTGCTL); + hsotg->op_state = OTG_STATE_A_SUSPEND; + } + + switch (hsotg->params.power_down) { + case DWC2_POWER_DOWN_PARAM_PARTIAL: + ret = dwc2_enter_partial_power_down(hsotg); + if (ret) + dev_err(hsotg->dev, + "enter partial_power_down failed.\n"); + break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: + /* + * Perform spin unlock and lock because in + * "dwc2_host_enter_hibernation()" function there is a spinlock + * logic which prevents servicing of any IRQ during entering + * hibernation. + */ + spin_unlock_irqrestore(&hsotg->lock, flags); + ret = dwc2_enter_hibernation(hsotg, 1); + if (ret) + dev_err(hsotg->dev, "enter hibernation failed.\n"); + spin_lock_irqsave(&hsotg->lock, flags); + break; + case DWC2_POWER_DOWN_PARAM_NONE: + /* + * If not hibernation nor partial power down are supported, + * clock gating is used to save power. + */ + if (!hsotg->params.no_clock_gating) + dwc2_host_enter_clock_gating(hsotg); + break; + } + + /* For HNP the bus must be suspended for at least 200ms */ + if (dwc2_host_is_b_hnp_enabled(hsotg)) { + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl &= ~PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + + spin_unlock_irqrestore(&hsotg->lock, flags); + + dwc2_msleep(200); + } else { + spin_unlock_irqrestore(&hsotg->lock, flags); + } + + return ret; +} + +/** + * dwc2_port_resume() - Exit controller from suspend mode for host. + * + * @hsotg: Programming view of the DWC_otg controller + * + * Return: non-zero if failed to exit suspend mode for host. + * + * This function is for exiting Host mode suspend. + * Must NOT be called with interrupt disabled or spinlock held. + */ +int dwc2_port_resume(struct dwc2_hsotg *hsotg) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&hsotg->lock, flags); + + switch (hsotg->params.power_down) { + case DWC2_POWER_DOWN_PARAM_PARTIAL: + ret = dwc2_exit_partial_power_down(hsotg, 0, true); + if (ret) + dev_err(hsotg->dev, + "exit partial_power_down failed.\n"); + break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: + /* Exit host hibernation. */ + ret = dwc2_exit_hibernation(hsotg, 0, 0, 1); + if (ret) + dev_err(hsotg->dev, "exit hibernation failed.\n"); + break; + case DWC2_POWER_DOWN_PARAM_NONE: + /* + * If not hibernation nor partial power down are supported, + * port resume is done using the clock gating programming flow. + */ + spin_unlock_irqrestore(&hsotg->lock, flags); + dwc2_host_exit_clock_gating(hsotg, 0); + spin_lock_irqsave(&hsotg->lock, flags); + break; + } + + spin_unlock_irqrestore(&hsotg->lock, flags); + + return ret; +} + +/* Handles hub class-specific requests */ +int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, + u16 wvalue, u16 windex, char *buf, u16 wlength) +{ + usb_hub_descriptor_t *hub_desc; + usb_port_status_t ps; + int retval = 0; + u32 hprt0; + u32 port_status; + u32 speed; + u32 pcgctl; + u32 pwr; + + switch (typereq) { + case ClearHubFeature: + dev_dbg(hsotg->dev, "ClearHubFeature %1xh\n", wvalue); + + switch (wvalue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* Nothing required here */ + break; + + default: + retval = -EINVAL; + dev_err(hsotg->dev, + "ClearHubFeature request %1xh unknown\n", + wvalue); + } + break; + + case ClearPortFeature: +// if (wvalue != USB_PORT_FEAT_L1) + if (!windex || windex > 1) + goto error; + switch (wvalue) { + case USB_PORT_FEAT_ENABLE: + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_ENABLE\n"); + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_ENA; + dwc2_writel(hsotg, hprt0, HPRT0); + break; + + case USB_PORT_FEAT_SUSPEND: + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_SUSPEND\n"); + + if (hsotg->bus_suspended) + retval = dwc2_port_resume(hsotg); + break; + + case USB_PORT_FEAT_POWER: + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_POWER\n"); + hprt0 = dwc2_read_hprt0(hsotg); + pwr = hprt0 & HPRT0_PWR; + hprt0 &= ~HPRT0_PWR; + dwc2_writel(hsotg, hprt0, HPRT0); + if (pwr) + dwc2_vbus_supply_exit(hsotg); + break; + + case USB_PORT_FEAT_INDICATOR: + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_INDICATOR\n"); + /* Port indicator not supported */ + break; + + case USB_PORT_FEAT_C_CONNECTION: + /* + * Clears driver's internal Connect Status Change flag + */ + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_C_CONNECTION\n"); + hsotg->flags.b.port_connect_status_change = 0; + break; + + case USB_PORT_FEAT_C_RESET: + /* Clears driver's internal Port Reset Change flag */ + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_C_RESET\n"); + hsotg->flags.b.port_reset_change = 0; + break; + + case USB_PORT_FEAT_C_ENABLE: + /* + * Clears the driver's internal Port Enable/Disable + * Change flag + */ + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_C_ENABLE\n"); + hsotg->flags.b.port_enable_change = 0; + break; + + case USB_PORT_FEAT_C_SUSPEND: + /* + * Clears the driver's internal Port Suspend Change + * flag, which is set when resume signaling on the host + * port is complete + */ + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_C_SUSPEND\n"); + hsotg->flags.b.port_suspend_change = 0; + break; + + case USB_PORT_FEAT_C_PORT_L1: + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_C_PORT_L1\n"); + hsotg->flags.b.port_l1_change = 0; + break; + + case USB_PORT_FEAT_C_OVER_CURRENT: + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_C_OVER_CURRENT\n"); + hsotg->flags.b.port_over_current_change = 0; + break; + + default: + retval = -EINVAL; + dev_err(hsotg->dev, + "ClearPortFeature request %1xh unknown or unsupported\n", + wvalue); + } + break; + + case GetHubDescriptor: + dev_dbg(hsotg->dev, "GetHubDescriptor\n"); + hub_desc = (usb_hub_descriptor_t *)buf; + hub_desc->bDescLength = 9; + hub_desc->bDescriptorType = USB_DT_HUB; + hub_desc->bNbrPorts = 1; + USETW(hub_desc->wHubCharacteristics, HUB_CHAR_COMMON_LPSM | + HUB_CHAR_INDV_PORT_OCPM); + hub_desc->bPwrOn2PwrGood = 1; + hub_desc->bHubContrCurrent = 0; + hub_desc->DeviceRemovable[0] = 0; + hub_desc->DeviceRemovable[1] = 0xff; + break; + + case GetHubStatus: + dev_dbg(hsotg->dev, "GetHubStatus\n"); + memset(buf, 0, 4); + break; + + case GetPortStatus: + dev_vdbg(hsotg->dev, + "GetPortStatus wIndex=0x%04x flags=0x%08x\n", windex, + hsotg->flags.d32); + if (!windex || windex > 1) + goto error; + + port_status = 0; + if (hsotg->flags.b.port_connect_status_change) + port_status |= USB_PORT_STAT_C_CONNECTION; + if (hsotg->flags.b.port_enable_change) + port_status |= USB_PORT_STAT_C_ENABLE; + if (hsotg->flags.b.port_suspend_change) + port_status |= USB_PORT_STAT_C_SUSPEND; + if (hsotg->flags.b.port_l1_change) + port_status |= USB_PORT_STAT_C_L1; + if (hsotg->flags.b.port_reset_change) + port_status |= USB_PORT_STAT_C_RESET; + if (hsotg->flags.b.port_over_current_change) { + dev_warn(hsotg->dev, "Overcurrent change detected\n"); + port_status |= USB_PORT_STAT_C_OVERCURRENT; + } + USETW(ps.wPortChange, port_status); + dev_vdbg(hsotg->dev, "wPortChange=%04x\n", port_status); + + if (!hsotg->flags.b.port_connect_status) { + /* + * The port is disconnected, which means the core is + * either in device mode or it soon will be. Just + * return 0's for the remainder of the port status + * since the port register can't be read if the core + * is in device mode. + */ + USETW(ps.wPortStatus, 0); + memcpy(buf, &ps, sizeof(ps)); + break; + } + + port_status = 0; + hprt0 = dwc2_readl(hsotg, HPRT0); + dev_vdbg(hsotg->dev, " HPRT0: 0x%08x\n", hprt0); + + if (hprt0 & HPRT0_CONNSTS) + port_status |= USB_PORT_STAT_CONNECTION; + if (hprt0 & HPRT0_ENA) + port_status |= USB_PORT_STAT_ENABLE; + if (hprt0 & HPRT0_SUSP) + port_status |= USB_PORT_STAT_SUSPEND; + if (hprt0 & HPRT0_OVRCURRACT) + port_status |= USB_PORT_STAT_OVERCURRENT; + if (hprt0 & HPRT0_RST) + port_status |= USB_PORT_STAT_RESET; + if (hprt0 & HPRT0_PWR) + port_status |= USB_PORT_STAT_POWER; + + speed = (hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT; + if (speed == HPRT0_SPD_HIGH_SPEED) + port_status |= USB_PORT_STAT_HIGH_SPEED; + else if (speed == HPRT0_SPD_LOW_SPEED) + port_status |= USB_PORT_STAT_LOW_SPEED; + + if (hprt0 & HPRT0_TSTCTL_MASK) + port_status |= USB_PORT_STAT_TEST; + /* USB_PORT_FEAT_INDICATOR unsupported always 0 */ + USETW(ps.wPortStatus, port_status); + + if (hsotg->params.dma_desc_fs_enable) { + /* + * Enable descriptor DMA only if a full speed + * device is connected. + */ + if (hsotg->new_connection && + ((port_status & + (USB_PORT_STAT_CONNECTION | + USB_PORT_STAT_HIGH_SPEED | + USB_PORT_STAT_LOW_SPEED)) == + USB_PORT_STAT_CONNECTION)) { + u32 hcfg; + + dev_info(hsotg->dev, "Enabling descriptor DMA mode\n"); + hsotg->params.dma_desc_enable = true; + hcfg = dwc2_readl(hsotg, HCFG); + hcfg |= HCFG_DESCDMA; + dwc2_writel(hsotg, hcfg, HCFG); + hsotg->new_connection = false; + } + } + + dev_vdbg(hsotg->dev, "port_status=%08x\n", port_status); + memcpy(buf, &ps, sizeof(ps)); + break; + + case SetHubFeature: + dev_dbg(hsotg->dev, "SetHubFeature\n"); + /* No HUB features supported */ + break; + + case SetPortFeature: + dev_dbg(hsotg->dev, "SetPortFeature\n"); + if (wvalue != USB_PORT_FEAT_TEST && (!windex || windex > 1)) + goto error; + + if (!hsotg->flags.b.port_connect_status) { + /* + * The port is disconnected, which means the core is + * either in device mode or it soon will be. Just + * return without doing anything since the port + * register can't be written if the core is in device + * mode. + */ + break; + } + + switch (wvalue) { + case USB_PORT_FEAT_SUSPEND: dev_dbg(hsotg->dev, - "ClearPortFeature USB_PORT_FEAT_INDICATOR\n"); - /* Port indicator not supported */ + "SetPortFeature - USB_PORT_FEAT_SUSPEND\n"); + if (windex != hsotg->otg_port) + goto error; + if (!hsotg->bus_suspended) + retval = dwc2_port_suspend(hsotg, windex); + break; + + case USB_PORT_FEAT_POWER: + dev_dbg(hsotg->dev, + "SetPortFeature - USB_PORT_FEAT_POWER\n"); + hprt0 = dwc2_read_hprt0(hsotg); + pwr = hprt0 & HPRT0_PWR; + hprt0 |= HPRT0_PWR; + dwc2_writel(hsotg, hprt0, HPRT0); + if (!pwr) + dwc2_vbus_supply_init(hsotg); + break; + + case USB_PORT_FEAT_RESET: + dev_dbg(hsotg->dev, + "SetPortFeature - USB_PORT_FEAT_RESET\n"); + + hprt0 = dwc2_read_hprt0(hsotg); + + if (hsotg->hibernated) { + retval = dwc2_exit_hibernation(hsotg, 0, 1, 1); + if (retval) + dev_err(hsotg->dev, + "exit hibernation failed\n"); + } + + if (hsotg->in_ppd) { + retval = dwc2_exit_partial_power_down(hsotg, 1, + true); + if (retval) + dev_err(hsotg->dev, + "exit partial_power_down failed\n"); + } + + if (hsotg->params.power_down == + DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended) + dwc2_host_exit_clock_gating(hsotg, 0); + + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl &= ~(PCGCTL_ENBL_SLEEP_GATING | PCGCTL_STOPPCLK); + dwc2_writel(hsotg, pcgctl, PCGCTL); + /* ??? Original driver does this */ + dwc2_writel(hsotg, 0, PCGCTL); + + hprt0 = dwc2_read_hprt0(hsotg); + pwr = hprt0 & HPRT0_PWR; + /* Clear suspend bit if resetting from suspend state */ + hprt0 &= ~HPRT0_SUSP; + + /* + * When B-Host the Port reset bit is set in the Start + * HCD Callback function, so that the reset is started + * within 1ms of the HNP success interrupt + */ + if (!dwc2_hcd_is_b_host(hsotg)) { + hprt0 |= HPRT0_PWR | HPRT0_RST; + dev_dbg(hsotg->dev, + "In host mode, hprt0=%08x\n", hprt0); + dwc2_writel(hsotg, hprt0, HPRT0); + if (!pwr) + dwc2_vbus_supply_init(hsotg); + } + + /* Clear reset bit in 10ms (FS/LS) or 50ms (HS) */ + dwc2_msleep(50); + hprt0 &= ~HPRT0_RST; + dwc2_writel(hsotg, hprt0, HPRT0); + hsotg->lx_state = DWC2_L0; /* Now back to On state */ + break; + + case USB_PORT_FEAT_INDICATOR: + dev_dbg(hsotg->dev, + "SetPortFeature - USB_PORT_FEAT_INDICATOR\n"); + /* Not supported */ + break; + + case USB_PORT_FEAT_TEST: + hprt0 = dwc2_read_hprt0(hsotg); + dev_dbg(hsotg->dev, + "SetPortFeature - USB_PORT_FEAT_TEST\n"); + hprt0 &= ~HPRT0_TSTCTL_MASK; + hprt0 |= (windex >> 8) << HPRT0_TSTCTL_SHIFT; + dwc2_writel(hsotg, hprt0, HPRT0); + break; + + default: + retval = -EINVAL; + dev_err(hsotg->dev, + "SetPortFeature %1xh unknown or unsupported\n", + wvalue); break; + } + break; + + default: +error: + retval = -EINVAL; + dev_dbg(hsotg->dev, + "Unknown hub control request: %1xh wIndex: %1xh wValue: %1xh\n", + typereq, windex, wvalue); + break; + } + + return retval; +} + +#if 0 +static int dwc2_hcd_is_status_changed(struct dwc2_hsotg *hsotg, int port) +{ + int retval; + + if (port != 1) + return -EINVAL; + + retval = (hsotg->flags.b.port_connect_status_change || + hsotg->flags.b.port_reset_change || + hsotg->flags.b.port_enable_change || + hsotg->flags.b.port_suspend_change || + hsotg->flags.b.port_over_current_change); + + if (retval) { + dev_dbg(hsotg->dev, + "DWC OTG HCD HUB STATUS DATA: Root port status changed\n"); + dev_dbg(hsotg->dev, " port_connect_status_change: %d\n", + hsotg->flags.b.port_connect_status_change); + dev_dbg(hsotg->dev, " port_reset_change: %d\n", + hsotg->flags.b.port_reset_change); + dev_dbg(hsotg->dev, " port_enable_change: %d\n", + hsotg->flags.b.port_enable_change); + dev_dbg(hsotg->dev, " port_suspend_change: %d\n", + hsotg->flags.b.port_suspend_change); + dev_dbg(hsotg->dev, " port_over_current_change: %d\n", + hsotg->flags.b.port_over_current_change); + } + + return retval; +} +#endif + +int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg) +{ + u32 hfnum = dwc2_readl(hsotg, HFNUM); + +#ifdef DWC2_DEBUG_SOF + dev_vdbg(hsotg->dev, "DWC OTG HCD GET FRAME NUMBER %d\n", + (hfnum & HFNUM_FRNUM_MASK) >> HFNUM_FRNUM_SHIFT); +#endif + return (hfnum & HFNUM_FRNUM_MASK) >> HFNUM_FRNUM_SHIFT; +} + +int dwc2_hcd_get_future_frame_number(struct dwc2_hsotg *hsotg, int us) +{ + u32 hprt = dwc2_readl(hsotg, HPRT0); + u32 hfir = dwc2_readl(hsotg, HFIR); + u32 hfnum = dwc2_readl(hsotg, HFNUM); + unsigned int us_per_frame; + unsigned int frame_number; + unsigned int remaining; + unsigned int interval; + unsigned int phy_clks; + + /* High speed has 125 us per (micro) frame; others are 1 ms per */ + us_per_frame = (hprt & HPRT0_SPD_MASK) ? 1000 : 125; + + /* Extract fields */ + frame_number = (hfnum & HFNUM_FRNUM_MASK) >> HFNUM_FRNUM_SHIFT; + remaining = (hfnum & HFNUM_FRREM_MASK) >> HFNUM_FRREM_SHIFT; + interval = (hfir & HFIR_FRINT_MASK) >> HFIR_FRINT_SHIFT; + + /* + * Number of phy clocks since the last tick of the frame number after + * "us" has passed. + */ + phy_clks = (interval - remaining) + + DIV_ROUND_UP(interval * us, us_per_frame); + + return dwc2_frame_num_inc(frame_number, phy_clks / interval); +} + +int dwc2_hcd_is_b_host(struct dwc2_hsotg *hsotg) +{ + return hsotg->op_state == OTG_STATE_B_HOST; +} + +STATIC struct dwc2_hcd_urb *dwc2_hcd_urb_alloc(struct dwc2_hsotg *hsotg, + int iso_desc_count, + gfp_t mem_flags) +{ + struct dwc2_hcd_urb *urb; + + u32 size = sizeof(*urb) + iso_desc_count * + sizeof(struct dwc2_hcd_iso_packet_desc); + urb = malloc(size, M_USBHC, M_ZERO | mem_flags); + if (urb) + urb->packet_count = iso_desc_count; + return urb; +} + +/* Required for OpenBSD */ +void dwc2_hcd_urb_free(struct dwc2_hsotg *hsotg, struct dwc2_hcd_urb *urb, + int iso_desc_count) +{ + u32 size = sizeof(*urb) + iso_desc_count * + sizeof(struct dwc2_hcd_iso_packet_desc); + + free(urb, M_USBHC, size); +} + +void dwc2_hcd_urb_set_pipeinfo(struct dwc2_hsotg *hsotg, + struct dwc2_hcd_urb *urb, u8 dev_addr, + u8 ep_num, u8 ep_type, u8 ep_dir, + u16 maxp, u16 maxp_mult) +{ + if (dbg_perio() || + ep_type == USB_ENDPOINT_XFER_BULK || + ep_type == USB_ENDPOINT_XFER_CONTROL) + dev_vdbg(hsotg->dev, + "addr=%d, ep_num=%d, ep_dir=%1x, ep_type=%1x, maxp=%d (%d mult)\n", + dev_addr, ep_num, ep_dir, ep_type, maxp, maxp_mult); + urb->pipe_info.dev_addr = dev_addr; + urb->pipe_info.ep_num = ep_num; + urb->pipe_info.pipe_type = ep_type; + urb->pipe_info.pipe_dir = ep_dir; + urb->pipe_info.maxp = maxp; + urb->pipe_info.maxp_mult = maxp_mult; +} + +/* + * NOTE: This function will be removed once the peripheral controller code + * is integrated and the driver is stable + */ +void dwc2_hcd_dump_state(struct dwc2_hsotg *hsotg) +{ +#ifdef DWC2_DEBUG + struct dwc2_host_chan *chan; + struct dwc2_hcd_urb *urb; + struct dwc2_qtd *qtd; + int num_channels; + u32 np_tx_status; + u32 p_tx_status; + int i; + + num_channels = hsotg->params.host_channels; + dev_dbg(hsotg->dev, "\n"); + dev_dbg(hsotg->dev, + "************************************************************\n"); + dev_dbg(hsotg->dev, "HCD State:\n"); + dev_dbg(hsotg->dev, " Num channels: %d\n", num_channels); + + for (i = 0; i < num_channels; i++) { + chan = hsotg->hc_ptr_array[i]; + dev_dbg(hsotg->dev, " Channel %d:\n", i); + dev_dbg(hsotg->dev, + " dev_addr: %d, ep_num: %d, ep_is_in: %d\n", + chan->dev_addr, chan->ep_num, chan->ep_is_in); + dev_dbg(hsotg->dev, " speed: %d\n", chan->speed); + dev_dbg(hsotg->dev, " ep_type: %d\n", chan->ep_type); + dev_dbg(hsotg->dev, " max_packet: %d\n", chan->max_packet); + dev_dbg(hsotg->dev, " data_pid_start: %d\n", + chan->data_pid_start); + dev_dbg(hsotg->dev, " multi_count: %d\n", chan->multi_count); + dev_dbg(hsotg->dev, " xfer_started: %d\n", + chan->xfer_started); + dev_dbg(hsotg->dev, " xfer_buf: %p\n", chan->xfer_buf); + dev_dbg(hsotg->dev, " xfer_dma: %08lx\n", + (unsigned long)chan->xfer_dma); + dev_dbg(hsotg->dev, " xfer_len: %d\n", chan->xfer_len); + dev_dbg(hsotg->dev, " xfer_count: %d\n", chan->xfer_count); + dev_dbg(hsotg->dev, " halt_on_queue: %d\n", + chan->halt_on_queue); + dev_dbg(hsotg->dev, " halt_pending: %d\n", + chan->halt_pending); + dev_dbg(hsotg->dev, " halt_status: %d\n", chan->halt_status); + dev_dbg(hsotg->dev, " do_split: %d\n", chan->do_split); + dev_dbg(hsotg->dev, " complete_split: %d\n", + chan->complete_split); + dev_dbg(hsotg->dev, " hub_addr: %d\n", chan->hub_addr); + dev_dbg(hsotg->dev, " hub_port: %d\n", chan->hub_port); + dev_dbg(hsotg->dev, " xact_pos: %d\n", chan->xact_pos); + dev_dbg(hsotg->dev, " requests: %d\n", chan->requests); + dev_dbg(hsotg->dev, " qh: %p\n", chan->qh); + + if (chan->xfer_started) { + u32 hfnum, hcchar, hctsiz, hcint, hcintmsk; + + hfnum = dwc2_readl(hsotg, HFNUM); + hcchar = dwc2_readl(hsotg, HCCHAR(i)); + hctsiz = dwc2_readl(hsotg, HCTSIZ(i)); + hcint = dwc2_readl(hsotg, HCINT(i)); + hcintmsk = dwc2_readl(hsotg, HCINTMSK(i)); + dev_dbg(hsotg->dev, " hfnum: 0x%08x\n", hfnum); + dev_dbg(hsotg->dev, " hcchar: 0x%08x\n", hcchar); + dev_dbg(hsotg->dev, " hctsiz: 0x%08x\n", hctsiz); + dev_dbg(hsotg->dev, " hcint: 0x%08x\n", hcint); + dev_dbg(hsotg->dev, " hcintmsk: 0x%08x\n", hcintmsk); + } + + if (!(chan->xfer_started && chan->qh)) + continue; + + list_for_each_entry(qtd, &chan->qh->qtd_list, qtd_list_entry) { + if (!qtd->in_process) + break; + urb = qtd->urb; + dev_dbg(hsotg->dev, " URB Info:\n"); + dev_dbg(hsotg->dev, " qtd: %p, urb: %p\n", + qtd, urb); + if (urb) { + dev_dbg(hsotg->dev, + " Dev: %d, EP: %d %s\n", + dwc2_hcd_get_dev_addr(&urb->pipe_info), + dwc2_hcd_get_ep_num(&urb->pipe_info), + dwc2_hcd_is_pipe_in(&urb->pipe_info) ? + "IN" : "OUT"); + dev_dbg(hsotg->dev, + " Max packet size: %d (%d mult)\n", + dwc2_hcd_get_maxp(&urb->pipe_info), + dwc2_hcd_get_maxp_mult(&urb->pipe_info)); + dev_dbg(hsotg->dev, + " transfer_buffer: %p\n", + urb->buf); + dev_dbg(hsotg->dev, + " transfer_dma: %08lx\n", + (unsigned long)urb->dma); + dev_dbg(hsotg->dev, + " transfer_buffer_length: %d\n", + urb->length); + dev_dbg(hsotg->dev, " actual_length: %d\n", + urb->actual_length); + } + } + } + + dev_dbg(hsotg->dev, " non_periodic_channels: %d\n", + hsotg->non_periodic_channels); + dev_dbg(hsotg->dev, " periodic_channels: %d\n", + hsotg->periodic_channels); + dev_dbg(hsotg->dev, " periodic_usecs: %d\n", hsotg->periodic_usecs); + np_tx_status = dwc2_readl(hsotg, GNPTXSTS); + dev_dbg(hsotg->dev, " NP Tx Req Queue Space Avail: %d\n", + (np_tx_status & TXSTS_QSPCAVAIL_MASK) >> TXSTS_QSPCAVAIL_SHIFT); + dev_dbg(hsotg->dev, " NP Tx FIFO Space Avail: %d\n", + (np_tx_status & TXSTS_FSPCAVAIL_MASK) >> TXSTS_FSPCAVAIL_SHIFT); + p_tx_status = dwc2_readl(hsotg, HPTXSTS); + dev_dbg(hsotg->dev, " P Tx Req Queue Space Avail: %d\n", + (p_tx_status & TXSTS_QSPCAVAIL_MASK) >> TXSTS_QSPCAVAIL_SHIFT); + dev_dbg(hsotg->dev, " P Tx FIFO Space Avail: %d\n", + (p_tx_status & TXSTS_FSPCAVAIL_MASK) >> TXSTS_FSPCAVAIL_SHIFT); + dwc2_dump_global_registers(hsotg); + dwc2_dump_host_registers(hsotg); + dev_dbg(hsotg->dev, + "************************************************************\n"); + dev_dbg(hsotg->dev, "\n"); +#endif +} + +struct wrapper_priv_data { + struct dwc2_hsotg *hsotg; +}; + +#if 0 +/* Gets the dwc2_hsotg from a usb_hcd */ +static struct dwc2_hsotg *dwc2_hcd_to_hsotg(struct usb_hcd *hcd) +{ + struct wrapper_priv_data *p; + + p = (struct wrapper_priv_data *)&hcd->hcd_priv; + return p->hsotg; +} +#endif + +/** + * dwc2_host_get_tt_info() - Get the dwc2_tt associated with context + * + * This will get the dwc2_tt structure (and ttport) associated with the given + * context (which is really just a struct urb pointer). + * + * The first time this is called for a given TT we allocate memory for our + * structure. When everyone is done and has called dwc2_host_put_tt_info() + * then the refcount for the structure will go to 0 and we'll free it. + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @context: The priv pointer from a struct dwc2_hcd_urb. + * @mem_flags: Flags for allocating memory. + * @ttport: We'll return this device's port number here. That's used to + * reference into the bitmap if we're on a multi_tt hub. + * + * Return: a pointer to a struct dwc2_tt. Don't forget to call + * dwc2_host_put_tt_info()! Returns NULL upon memory alloc failure. + */ + +struct dwc2_tt *dwc2_host_get_tt_info(struct dwc2_hsotg *hsotg, void *context, + gfp_t mem_flags, int *ttport) +{ + struct usbd_xfer *xfer = context; + struct dwc2_pipe *dpipe = DWC2_XFER2DPIPE(xfer); + struct usbd_device *dev = dpipe->pipe.device; + struct dwc2_tt *dwc_tt = NULL; + + if (dev->myhsport->tt) { + *ttport = dev->myhsport->portno; + + dwc_tt = dev->myhsport->tt->hcpriv; + if (!dwc_tt) { + size_t bitmap_size; - case USB_PORT_FEAT_C_CONNECTION: /* - * Clears driver's internal Connect Status Change flag + * For single_tt we need one schedule. For multi_tt + * we need one per port. */ - dev_dbg(hsotg->dev, - "ClearPortFeature USB_PORT_FEAT_C_CONNECTION\n"); - hsotg->flags.b.port_connect_status_change = 0; - break; + bitmap_size = DWC2_ELEMENTS_PER_LS_BITMAP * + sizeof(dwc_tt->periodic_bitmaps[0]); + if (dev->myhsport->tt->hub->multi) + bitmap_size *= USB_MAXCHILDREN; /* XXX */ + + dwc_tt = malloc(sizeof(*dwc_tt) + bitmap_size, M_USBHC, + mem_flags); + + if (!dwc_tt) + return NULL; + + dwc_tt->usb_tt = dev->myhsport->tt; + dwc_tt->usb_tt->hcpriv = dwc_tt; + } + + dwc_tt->refcount++; + } + + return dwc_tt; +} + +/** + * dwc2_host_put_tt_info() - Put the dwc2_tt from dwc2_host_get_tt_info() + * + * Frees resources allocated by dwc2_host_get_tt_info() if all current holders + * of the structure are done. + * + * It's OK to call this with NULL. + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @dwc_tt: The pointer returned by dwc2_host_get_tt_info. + */ +void dwc2_host_put_tt_info(struct dwc2_hsotg *hsotg, struct dwc2_tt *dwc_tt) +{ + /* Model kfree and make put of NULL a no-op */ + if (!dwc_tt) + return; + + WARN_ON(dwc_tt->refcount < 1); + + dwc_tt->refcount--; + if (!dwc_tt->refcount) { + dwc_tt->usb_tt->hcpriv = NULL; + free(dwc_tt, M_USBHC, 0); + } +} + +int dwc2_host_get_speed(struct dwc2_hsotg *hsotg, void *context) +{ + struct usbd_xfer *xfer = context; + struct dwc2_pipe *dpipe = DWC2_XFER2DPIPE(xfer); + struct usbd_device *dev = dpipe->pipe.device; + + return dev->speed; +} + +STATIC void dwc2_allocate_bus_bandwidth(struct dwc2_hsotg *hsotg, u16 bw, + struct usbd_xfer *xfer) +{ +} + +STATIC void dwc2_free_bus_bandwidth(struct dwc2_hsotg *hsotg, u16 bw, + struct usbd_xfer *xfer) +{ +} + +/* + * Sets the final status of an URB and returns it to the upper layer. Any + * required cleanup of the URB is performed. + * + * Must be called with interrupt disabled and spinlock held + */ +void dwc2_host_complete(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, + int status) +{ + struct usbd_xfer *xfer; + struct dwc2_xfer *dxfer; + struct dwc2_softc *sc; + usb_endpoint_descriptor_t *ed; + uint8_t xfertype; + + if (!qtd) { + dev_dbg(hsotg->dev, "## %s: qtd is NULL ##\n", __func__); + return; + } + + if (!qtd->urb) { + dev_dbg(hsotg->dev, "## %s: qtd->urb is NULL ##\n", __func__); + return; + } + + xfer = qtd->urb->priv; + if (!xfer) { + dev_dbg(hsotg->dev, "## %s: urb->priv is NULL ##\n", __func__); + return; + } + + dxfer = DWC2_XFER2DXFER(xfer); + sc = DWC2_XFER2SC(xfer); + ed = xfer->pipe->endpoint->edesc; + xfertype = UE_GET_XFERTYPE(ed->bmAttributes); + + struct dwc2_hcd_urb *urb = qtd->urb; + xfer->actlen = dwc2_hcd_urb_get_actual_length(urb); +#if 0 + DPRINTFN(3, "xfer=%p actlen=%d\n", xfer, xfer->actlen); +#endif + if (xfertype == UE_ISOCHRONOUS) { + xfer->actlen = 0; + for (size_t i = 0; i < xfer->nframes; ++i) { + xfer->frlengths[i] = + dwc2_hcd_urb_get_iso_desc_actual_length(urb, i); +#if 0 + DPRINTFN(1, "xfer=%p frame=%zu length=%d\n", xfer, i, + xfer->frlengths[i]); +#endif + xfer->actlen += xfer->frlengths[i]; + } +#if 0 + DPRINTFN(1, "xfer=%p actlen=%d (isoc)\n", xfer, xfer->actlen); +#endif + } + + if (xfertype == UE_ISOCHRONOUS && dbg_perio()) { + for (size_t i = 0; i < xfer->nframes; i++) + dev_vdbg(hsotg->dev, " ISO Desc %zu status %d\n", + i, urb->iso_descs[i].status); + } + + if (!status) { + if (!(xfer->flags & USBD_SHORT_XFER_OK) && + xfer->actlen < xfer->length) + status = -EIO; + } + + switch (status) { + case 0: + dxfer->intr_status = USBD_NORMAL_COMPLETION; + break; + case -EPIPE: + dxfer->intr_status = USBD_STALLED; + break; + case -EPROTO: + dxfer->intr_status = USBD_INVAL; + break; + case -EIO: + dxfer->intr_status = USBD_IOERROR; + break; + case -EOVERFLOW: + dxfer->intr_status = USBD_IOERROR; + break; + default: + dxfer->intr_status = USBD_IOERROR; + printf("%s: unknown error status %d\n", __func__, status); + } + + if (dxfer->intr_status == USBD_NORMAL_COMPLETION) { + /* + * control transfers with no data phase don't touch dmabuf, but + * everything else does. + */ + if (!(xfertype == UE_CONTROL && + UGETW(xfer->request.wLength) == 0) && + xfer->actlen > 0 /* XXX PR/53503 */ + ) { + int rd = usbd_xfer_isread(xfer); + + usb_syncmem(&xfer->dmabuf, 0, xfer->actlen, + rd ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); + } + } + + if (xfertype == UE_ISOCHRONOUS || + xfertype == UE_INTERRUPT) { + struct dwc2_pipe *dpipe = DWC2_XFER2DPIPE(xfer); + + dwc2_free_bus_bandwidth(hsotg, + dwc2_hcd_get_ep_bandwidth(hsotg, dpipe), + xfer); + } + + qtd->urb = NULL; + timeout_del(&xfer->timeout_handle); + usb_rem_task(xfer->device, &xfer->abort_task); + MUTEX_ASSERT_LOCKED(&hsotg->lock); + + TAILQ_INSERT_TAIL(&sc->sc_complete, dxfer, xnext); + + mtx_leave(&hsotg->lock); + usb_schedsoftintr(&sc->sc_bus); + mtx_enter(&hsotg->lock); +} + +/* + * Work queue function for starting the HCD when A-Cable is connected + */ +STATIC void dwc2_hcd_start_func(void *data) +{ + struct dwc2_hsotg *hsotg = data; + + dev_dbg(hsotg->dev, "%s() %p\n", __func__, hsotg); + dwc2_host_start(hsotg); +} + +/* + * Reset work queue function + */ +STATIC void dwc2_hcd_reset_func(void *data) +{ + struct dwc2_hsotg *hsotg = data; + unsigned long flags; + u32 hprt0; + + dev_dbg(hsotg->dev, "USB RESET function called\n"); + + spin_lock_irqsave(&hsotg->lock, flags); + + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 &= ~HPRT0_RST; + dwc2_writel(hsotg, hprt0, HPRT0); + hsotg->flags.b.port_reset_change = 1; + + dwc2_root_intr(hsotg->hsotg_sc); /* Required for OpenBSD */ + + spin_unlock_irqrestore(&hsotg->lock, flags); +} + +#if 0 +static void dwc2_hcd_phy_reset_func(struct work_struct *work) +{ + struct dwc2_hsotg *hsotg = container_of(work, struct dwc2_hsotg, + phy_reset_work); + int ret; + + ret = phy_reset(hsotg->phy); + if (ret) + dev_warn(hsotg->dev, "PHY reset failed\n"); +} +#endif + +/* + * ========================================================================= + * Linux HC Driver Functions + * ========================================================================= + */ + +static int +_dwc2_hcd_start(struct dwc2_hsotg *hsotg) +{ + unsigned long flags; + + dev_dbg(hsotg->dev, "DWC OTG HCD START\n"); + + spin_lock_irqsave(&hsotg->lock, flags); + + hsotg->lx_state = DWC2_L0; + + if (dwc2_is_device_mode(hsotg)) { + spin_unlock_irqrestore(&hsotg->lock, flags); + return 0; /* why 0 ?? */ + } + + dwc2_hcd_reinit(hsotg); + + spin_unlock_irqrestore(&hsotg->lock, flags); + + return 0; +} +#if 0 +/* + * Initializes the DWC_otg controller and its root hub and prepares it for host + * mode operation. Activates the root port. Returns 0 on success and a negative + * error code on failure. + */ +static int _dwc2_hcd_start(struct dwc2_hsotg *hsotg) +{ + struct usb_bus *bus = hcd_to_bus(hcd); + unsigned long flags; + u32 hprt0; + int ret; + + dev_dbg(hsotg->dev, "DWC OTG HCD START\n"); + + spin_lock_irqsave(&hsotg->lock, flags); + hsotg->lx_state = DWC2_L0; + hcd->state = HC_STATE_RUNNING; + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + + if (dwc2_is_device_mode(hsotg)) { + spin_unlock_irqrestore(&hsotg->lock, flags); + return 0; /* why 0 ?? */ + } + + dwc2_hcd_reinit(hsotg); + + hprt0 = dwc2_read_hprt0(hsotg); + /* Has vbus power been turned on in dwc2_core_host_init ? */ + if (hprt0 & HPRT0_PWR) { + /* Enable external vbus supply before resuming root hub */ + spin_unlock_irqrestore(&hsotg->lock, flags); + ret = dwc2_vbus_supply_init(hsotg); + if (ret) + return ret; + spin_lock_irqsave(&hsotg->lock, flags); + } + + /* Initialize and connect root hub if one is not already attached */ + if (bus->root_hub) { + dev_dbg(hsotg->dev, "DWC OTG HCD Has Root Hub\n"); + /* Inform the HUB driver to resume */ + usb_hcd_resume_root_hub(hcd); + } + + spin_unlock_irqrestore(&hsotg->lock, flags); + + return 0; +} + +/* + * Halts the DWC_otg host mode operations in a clean manner. USB transfers are + * stopped. + */ +static void _dwc2_hcd_stop(struct usb_hcd *hcd) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + unsigned long flags; + u32 hprt0; + + /* Turn off all host-specific interrupts */ + dwc2_disable_host_interrupts(hsotg); + + /* Wait for interrupt processing to finish */ + synchronize_irq(hcd->irq); + + spin_lock_irqsave(&hsotg->lock, flags); + hprt0 = dwc2_read_hprt0(hsotg); + /* Ensure hcd is disconnected */ + dwc2_hcd_disconnect(hsotg, true); + dwc2_hcd_stop(hsotg); + hsotg->lx_state = DWC2_L3; + hcd->state = HC_STATE_HALT; + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + spin_unlock_irqrestore(&hsotg->lock, flags); + + /* keep balanced supply init/exit by checking HPRT0_PWR */ + if (hprt0 & HPRT0_PWR) + dwc2_vbus_supply_exit(hsotg); + + usleep_range(1000, 3000); +} + +static int _dwc2_hcd_suspend(struct usb_hcd *hcd) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&hsotg->lock, flags); + + if (dwc2_is_device_mode(hsotg)) + goto unlock; + + if (hsotg->lx_state != DWC2_L0) + goto unlock; + + if (!HCD_HW_ACCESSIBLE(hcd)) + goto unlock; + + if (hsotg->op_state == OTG_STATE_B_PERIPHERAL) + goto unlock; + + if (hsotg->bus_suspended) + goto skip_power_saving; + + if (hsotg->flags.b.port_connect_status == 0) + goto skip_power_saving; + + switch (hsotg->params.power_down) { + case DWC2_POWER_DOWN_PARAM_PARTIAL: + /* Enter partial_power_down */ + ret = dwc2_enter_partial_power_down(hsotg); + if (ret) + dev_err(hsotg->dev, + "enter partial_power_down failed\n"); + /* After entering suspend, hardware is not accessible */ + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: + /* Enter hibernation */ + spin_unlock_irqrestore(&hsotg->lock, flags); + ret = dwc2_enter_hibernation(hsotg, 1); + if (ret) + dev_err(hsotg->dev, "enter hibernation failed\n"); + spin_lock_irqsave(&hsotg->lock, flags); + + /* After entering suspend, hardware is not accessible */ + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + break; + case DWC2_POWER_DOWN_PARAM_NONE: + /* + * If not hibernation nor partial power down are supported, + * clock gating is used to save power. + */ + if (!hsotg->params.no_clock_gating) { + dwc2_host_enter_clock_gating(hsotg); + + /* After entering suspend, hardware is not accessible */ + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + } + break; + default: + goto skip_power_saving; + } + + spin_unlock_irqrestore(&hsotg->lock, flags); + dwc2_vbus_supply_exit(hsotg); + spin_lock_irqsave(&hsotg->lock, flags); + + /* Ask phy to be suspended */ + if (!IS_ERR_OR_NULL(hsotg->uphy)) { + spin_unlock_irqrestore(&hsotg->lock, flags); + usb_phy_set_suspend(hsotg->uphy, true); + spin_lock_irqsave(&hsotg->lock, flags); + } + +skip_power_saving: + hsotg->lx_state = DWC2_L2; +unlock: + spin_unlock_irqrestore(&hsotg->lock, flags); + + return ret; +} + +static int _dwc2_hcd_resume(struct usb_hcd *hcd) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + unsigned long flags; + u32 hprt0; + int ret = 0; - case USB_PORT_FEAT_C_RESET: - /* Clears driver's internal Port Reset Change flag */ - dev_dbg(hsotg->dev, - "ClearPortFeature USB_PORT_FEAT_C_RESET\n"); - hsotg->flags.b.port_reset_change = 0; - break; + spin_lock_irqsave(&hsotg->lock, flags); - case USB_PORT_FEAT_C_ENABLE: - /* - * Clears the driver's internal Port Enable/Disable - * Change flag - */ - dev_dbg(hsotg->dev, - "ClearPortFeature USB_PORT_FEAT_C_ENABLE\n"); - hsotg->flags.b.port_enable_change = 0; - break; + if (dwc2_is_device_mode(hsotg)) + goto unlock; - case USB_PORT_FEAT_C_SUSPEND: - /* - * Clears the driver's internal Port Suspend Change - * flag, which is set when resume signaling on the host - * port is complete - */ - dev_dbg(hsotg->dev, - "ClearPortFeature USB_PORT_FEAT_C_SUSPEND\n"); - hsotg->flags.b.port_suspend_change = 0; - break; + if (hsotg->lx_state != DWC2_L2) + goto unlock; - case USB_PORT_FEAT_C_PORT_L1: - dev_dbg(hsotg->dev, - "ClearPortFeature USB_PORT_FEAT_C_PORT_L1\n"); - hsotg->flags.b.port_l1_change = 0; - break; + hprt0 = dwc2_read_hprt0(hsotg); - case USB_PORT_FEAT_C_OVER_CURRENT: - dev_dbg(hsotg->dev, - "ClearPortFeature USB_PORT_FEAT_C_OVER_CURRENT\n"); - hsotg->flags.b.port_over_current_change = 0; - break; + /* + * Added port connection status checking which prevents exiting from + * Partial Power Down mode from _dwc2_hcd_resume() if not in Partial + * Power Down mode. + */ + if (hprt0 & HPRT0_CONNSTS) { + hsotg->lx_state = DWC2_L0; + goto unlock; + } - default: - retval = -EINVAL; + switch (hsotg->params.power_down) { + case DWC2_POWER_DOWN_PARAM_PARTIAL: + ret = dwc2_exit_partial_power_down(hsotg, 0, true); + if (ret) dev_err(hsotg->dev, - "ClearPortFeature request %1xh unknown or unsupported\n", - wvalue); - } + "exit partial_power_down failed\n"); + /* + * Set HW accessible bit before powering on the controller + * since an interrupt may rise. + */ + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: + ret = dwc2_exit_hibernation(hsotg, 0, 0, 1); + if (ret) + dev_err(hsotg->dev, "exit hibernation failed.\n"); - case GetHubDescriptor: - dev_dbg(hsotg->dev, "GetHubDescriptor\n"); - hub_desc = (usb_hub_descriptor_t *)buf; - hub_desc->bDescLength = 9; - hub_desc->bDescriptorType = USB_DT_HUB; - hub_desc->bNbrPorts = 1; - USETW(hub_desc->wHubCharacteristics, HUB_CHAR_COMMON_LPSM | - HUB_CHAR_INDV_PORT_OCPM); - hub_desc->bPwrOn2PwrGood = 1; - hub_desc->bHubContrCurrent = 0; - hub_desc->DeviceRemovable[0] = 0; - hub_desc->DeviceRemovable[1] = 0xff; + /* + * Set HW accessible bit before powering on the controller + * since an interrupt may rise. + */ + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); break; + case DWC2_POWER_DOWN_PARAM_NONE: + /* + * If not hibernation nor partial power down are supported, + * port resume is done using the clock gating programming flow. + */ + spin_unlock_irqrestore(&hsotg->lock, flags); + dwc2_host_exit_clock_gating(hsotg, 0); - case GetHubStatus: - dev_dbg(hsotg->dev, "GetHubStatus\n"); - memset(buf, 0, 4); + /* + * Initialize the Core for Host mode, as after system resume + * the global interrupts are disabled. + */ + dwc2_core_init(hsotg, false); + dwc2_enable_global_interrupts(hsotg); + dwc2_hcd_reinit(hsotg); + spin_lock_irqsave(&hsotg->lock, flags); + + /* + * Set HW accessible bit before powering on the controller + * since an interrupt may rise. + */ + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); break; + default: + hsotg->lx_state = DWC2_L0; + goto unlock; + } - case GetPortStatus: - dev_vdbg(hsotg->dev, - "GetPortStatus wIndex=0x%04x flags=0x%08x\n", windex, - hsotg->flags.d32); - if (!windex || windex > 1) - goto error; + /* Change Root port status, as port status change occurred after resume.*/ + hsotg->flags.b.port_suspend_change = 1; - port_status = 0; - if (hsotg->flags.b.port_connect_status_change) - port_status |= USB_PORT_STAT_C_CONNECTION; - if (hsotg->flags.b.port_enable_change) - port_status |= USB_PORT_STAT_C_ENABLE; - if (hsotg->flags.b.port_suspend_change) - port_status |= USB_PORT_STAT_C_SUSPEND; - if (hsotg->flags.b.port_l1_change) - port_status |= USB_PORT_STAT_C_L1; - if (hsotg->flags.b.port_reset_change) - port_status |= USB_PORT_STAT_C_RESET; - if (hsotg->flags.b.port_over_current_change) { - dev_warn(hsotg->dev, "Overcurrent change detected\n"); - port_status |= USB_PORT_STAT_C_OVERCURRENT; - } - USETW(ps.wPortChange, port_status); + /* + * Enable power if not already done. + * This must not be spinlocked since duration + * of this call is unknown. + */ + if (!IS_ERR_OR_NULL(hsotg->uphy)) { + spin_unlock_irqrestore(&hsotg->lock, flags); + usb_phy_set_suspend(hsotg->uphy, false); + spin_lock_irqsave(&hsotg->lock, flags); + } - dev_vdbg(hsotg->dev, "wPortChange=%04x\n", port_status); - if (!hsotg->flags.b.port_connect_status) { - /* - * The port is disconnected, which means the core is - * either in device mode or it soon will be. Just - * return 0's for the remainder of the port status - * since the port register can't be read if the core - * is in device mode. - */ - USETW(ps.wPortStatus, 0); - memcpy(buf, &ps, sizeof(ps)); - break; - } + /* Enable external vbus supply after resuming the port. */ + spin_unlock_irqrestore(&hsotg->lock, flags); + dwc2_vbus_supply_init(hsotg); - port_status = 0; - hprt0 = DWC2_READ_4(hsotg, HPRT0); - dev_vdbg(hsotg->dev, " HPRT0: 0x%08x\n", hprt0); + /* Wait for controller to correctly update D+/D- level */ + usleep_range(3000, 5000); + spin_lock_irqsave(&hsotg->lock, flags); - if (hprt0 & HPRT0_CONNSTS) - port_status |= USB_PORT_STAT_CONNECTION; - if (hprt0 & HPRT0_ENA) - port_status |= USB_PORT_STAT_ENABLE; - if (hprt0 & HPRT0_SUSP) - port_status |= USB_PORT_STAT_SUSPEND; - if (hprt0 & HPRT0_OVRCURRACT) - port_status |= USB_PORT_STAT_OVERCURRENT; - if (hprt0 & HPRT0_RST) - port_status |= USB_PORT_STAT_RESET; - if (hprt0 & HPRT0_PWR) - port_status |= USB_PORT_STAT_POWER; + /* + * Clear Port Enable and Port Status changes. + * Enable Port Power. + */ + dwc2_writel(hsotg, HPRT0_PWR | HPRT0_CONNDET | + HPRT0_ENACHG, HPRT0); - speed = (hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT; - if (speed == HPRT0_SPD_HIGH_SPEED) - port_status |= USB_PORT_STAT_HIGH_SPEED; - else if (speed == HPRT0_SPD_LOW_SPEED) - port_status |= USB_PORT_STAT_LOW_SPEED; + /* Wait for controller to detect Port Connect */ + spin_unlock_irqrestore(&hsotg->lock, flags); + usleep_range(5000, 7000); + spin_lock_irqsave(&hsotg->lock, flags); +unlock: + spin_unlock_irqrestore(&hsotg->lock, flags); - if (hprt0 & HPRT0_TSTCTL_MASK) - port_status |= USB_PORT_STAT_TEST; - /* USB_PORT_FEAT_INDICATOR unsupported always 0 */ - USETW(ps.wPortStatus, port_status); + return ret; +} - if (hsotg->core_params->dma_desc_fs_enable) { - /* - * Enable descriptor DMA only if a full speed - * device is connected. - */ - if (hsotg->new_connection && - ((port_status & - (USB_PORT_STAT_CONNECTION | - USB_PORT_STAT_HIGH_SPEED | - USB_PORT_STAT_LOW_SPEED)) == - USB_PORT_STAT_CONNECTION)) { - u32 hcfg; +/* Returns the current frame number */ +static int _dwc2_hcd_get_frame_number(struct usb_hcd *hcd) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); - dev_info(hsotg->dev, "Enabling descriptor DMA mode\n"); - hsotg->core_params->dma_desc_enable = 1; - hcfg = DWC2_READ_4(hsotg, HCFG); - hcfg |= HCFG_DESCDMA; - DWC2_WRITE_4(hsotg, HCFG, hcfg); - hsotg->new_connection = false; - } - } - dev_vdbg(hsotg->dev, "wPortStatus=%04x\n", port_status); - memcpy(buf, &ps, sizeof(ps)); - break; + return dwc2_hcd_get_frame_number(hsotg); +} - case SetHubFeature: - dev_dbg(hsotg->dev, "SetHubFeature\n"); - /* No HUB features supported */ +static void dwc2_dump_urb_info(struct usb_hcd *hcd, struct urb *urb, + char *fn_name) +{ +#ifdef VERBOSE_DEBUG + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + char *pipetype = NULL; + char *speed = NULL; + + dev_vdbg(hsotg->dev, "%s, urb %p\n", fn_name, urb); + dev_vdbg(hsotg->dev, " Device address: %d\n", + usb_pipedevice(urb->pipe)); + dev_vdbg(hsotg->dev, " Endpoint: %d, %s\n", + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "IN" : "OUT"); + + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + pipetype = "CONTROL"; break; + case PIPE_BULK: + pipetype = "BULK"; + break; + case PIPE_INTERRUPT: + pipetype = "INTERRUPT"; + break; + case PIPE_ISOCHRONOUS: + pipetype = "ISOCHRONOUS"; + break; + } - case SetPortFeature: - dev_dbg(hsotg->dev, "SetPortFeature\n"); - if (wvalue != USB_PORT_FEAT_TEST && (!windex || windex > 1)) - goto error; + dev_vdbg(hsotg->dev, " Endpoint type: %s %s (%s)\n", pipetype, + usb_urb_dir_in(urb) ? "IN" : "OUT", usb_pipein(urb->pipe) ? + "IN" : "OUT"); - if (!hsotg->flags.b.port_connect_status) { - /* - * The port is disconnected, which means the core is - * either in device mode or it soon will be. Just - * return without doing anything since the port - * register can't be written if the core is in device - * mode. - */ - break; + switch (urb->dev->speed) { + case USB_SPEED_HIGH: + speed = "HIGH"; + break; + case USB_SPEED_FULL: + speed = "FULL"; + break; + case USB_SPEED_LOW: + speed = "LOW"; + break; + default: + speed = "UNKNOWN"; + break; + } + + dev_vdbg(hsotg->dev, " Speed: %s\n", speed); + dev_vdbg(hsotg->dev, " Max packet size: %d (%d mult)\n", + usb_endpoint_maxp(&urb->ep->desc), + usb_endpoint_maxp_mult(&urb->ep->desc)); + + dev_vdbg(hsotg->dev, " Data buffer length: %d\n", + urb->transfer_buffer_length); + dev_vdbg(hsotg->dev, " Transfer buffer: %p, Transfer DMA: %08lx\n", + urb->transfer_buffer, (unsigned long)urb->transfer_dma); + dev_vdbg(hsotg->dev, " Setup buffer: %p, Setup DMA: %08lx\n", + urb->setup_packet, (unsigned long)urb->setup_dma); + dev_vdbg(hsotg->dev, " Interval: %d\n", urb->interval); + + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + int i; + + for (i = 0; i < urb->number_of_packets; i++) { + dev_vdbg(hsotg->dev, " ISO Desc %d:\n", i); + dev_vdbg(hsotg->dev, " offset: %d, length %d\n", + urb->iso_frame_desc[i].offset, + urb->iso_frame_desc[i].length); } + } +#endif +} - switch (wvalue) { - case USB_PORT_FEAT_SUSPEND: - dev_dbg(hsotg->dev, - "SetPortFeature - USB_PORT_FEAT_SUSPEND\n"); - if (windex != hsotg->otg_port) - goto error; - dwc2_port_suspend(hsotg, windex); - break; +/* + * Starts processing a USB transfer request specified by a USB Request Block + * (URB). mem_flags indicates the type of memory allocation to use while + * processing this URB. + */ +static int _dwc2_hcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + struct usb_host_endpoint *ep = urb->ep; + struct dwc2_hcd_urb *dwc2_urb; + int i; + int retval; + int alloc_bandwidth = 0; + u8 ep_type = 0; + u32 tflags = 0; + void *buf; + unsigned long flags; + struct dwc2_qh *qh; + bool qh_allocated = false; + struct dwc2_qtd *qtd; + struct dwc2_gregs_backup *gr; - case USB_PORT_FEAT_POWER: - dev_dbg(hsotg->dev, - "SetPortFeature - USB_PORT_FEAT_POWER\n"); - hprt0 = dwc2_read_hprt0(hsotg); - hprt0 |= HPRT0_PWR; - DWC2_WRITE_4(hsotg, HPRT0, hprt0); - break; + gr = &hsotg->gr_backup; + + if (dbg_urb(urb)) { + dev_vdbg(hsotg->dev, "DWC OTG HCD URB Enqueue\n"); + dwc2_dump_urb_info(hcd, urb, "urb_enqueue"); + } + + if (hsotg->hibernated) { + if (gr->gotgctl & GOTGCTL_CURMODE_HOST) + retval = dwc2_exit_hibernation(hsotg, 0, 0, 1); + else + retval = dwc2_exit_hibernation(hsotg, 0, 0, 0); + + if (retval) + dev_err(hsotg->dev, + "exit hibernation failed.\n"); + } + + if (hsotg->in_ppd) { + retval = dwc2_exit_partial_power_down(hsotg, 0, true); + if (retval) + dev_err(hsotg->dev, + "exit partial_power_down failed\n"); + } + + if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_NONE && + hsotg->bus_suspended) { + if (dwc2_is_device_mode(hsotg)) + dwc2_gadget_exit_clock_gating(hsotg, 0); + else + dwc2_host_exit_clock_gating(hsotg, 0); + } + + if (!ep) + return -EINVAL; + + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS || + usb_pipetype(urb->pipe) == PIPE_INTERRUPT) { + spin_lock_irqsave(&hsotg->lock, flags); + if (!dwc2_hcd_is_bandwidth_allocated(hsotg, ep)) + alloc_bandwidth = 1; + spin_unlock_irqrestore(&hsotg->lock, flags); + } + + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + ep_type = USB_ENDPOINT_XFER_CONTROL; + break; + case PIPE_ISOCHRONOUS: + ep_type = USB_ENDPOINT_XFER_ISOC; + break; + case PIPE_BULK: + ep_type = USB_ENDPOINT_XFER_BULK; + break; + case PIPE_INTERRUPT: + ep_type = USB_ENDPOINT_XFER_INT; + break; + } + + dwc2_urb = dwc2_hcd_urb_alloc(hsotg, urb->number_of_packets, + mem_flags); + if (!dwc2_urb) + return -ENOMEM; + + dwc2_hcd_urb_set_pipeinfo(hsotg, dwc2_urb, usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe), ep_type, + usb_pipein(urb->pipe), + usb_endpoint_maxp(&ep->desc), + usb_endpoint_maxp_mult(&ep->desc)); + + buf = urb->transfer_buffer; + + if (hcd_uses_dma(hcd)) { + if (!buf && (urb->transfer_dma & 3)) { + dev_err(hsotg->dev, + "%s: unaligned transfer with no transfer_buffer", + __func__); + retval = -EINVAL; + goto fail0; + } + } - case USB_PORT_FEAT_RESET: - hprt0 = dwc2_read_hprt0(hsotg); - dev_dbg(hsotg->dev, - "SetPortFeature - USB_PORT_FEAT_RESET\n"); - pcgctl = DWC2_READ_4(hsotg, PCGCTL); - pcgctl &= ~(PCGCTL_ENBL_SLEEP_GATING | PCGCTL_STOPPCLK); - DWC2_WRITE_4(hsotg, PCGCTL, pcgctl); - /* ??? Original driver does this */ - DWC2_WRITE_4(hsotg, PCGCTL, 0); + if (!(urb->transfer_flags & URB_NO_INTERRUPT)) + tflags |= URB_GIVEBACK_ASAP; + if (urb->transfer_flags & URB_ZERO_PACKET) + tflags |= URB_SEND_ZERO_PACKET; + + dwc2_urb->priv = urb; + dwc2_urb->buf = buf; + dwc2_urb->dma = urb->transfer_dma; + dwc2_urb->length = urb->transfer_buffer_length; + dwc2_urb->setup_packet = urb->setup_packet; + dwc2_urb->setup_dma = urb->setup_dma; + dwc2_urb->flags = tflags; + dwc2_urb->interval = urb->interval; + dwc2_urb->status = -EINPROGRESS; + + for (i = 0; i < urb->number_of_packets; ++i) + dwc2_hcd_urb_set_iso_desc_params(dwc2_urb, i, + urb->iso_frame_desc[i].offset, + urb->iso_frame_desc[i].length); + + urb->hcpriv = dwc2_urb; + qh = (struct dwc2_qh *)ep->hcpriv; + /* Create QH for the endpoint if it doesn't exist */ + if (!qh) { + qh = dwc2_hcd_qh_create(hsotg, dwc2_urb, mem_flags); + if (!qh) { + retval = -ENOMEM; + goto fail0; + } + ep->hcpriv = qh; + qh_allocated = true; + } - hprt0 = dwc2_read_hprt0(hsotg); - /* Clear suspend bit if resetting from suspend state */ - hprt0 &= ~HPRT0_SUSP; + qtd = kzalloc(sizeof(*qtd), mem_flags); + if (!qtd) { + retval = -ENOMEM; + goto fail1; + } - /* - * When B-Host the Port reset bit is set in the Start - * HCD Callback function, so that the reset is started - * within 1ms of the HNP success interrupt - */ - if (!dwc2_hcd_is_b_host(hsotg)) { - hprt0 |= HPRT0_PWR | HPRT0_RST; - dev_dbg(hsotg->dev, - "In host mode, hprt0=%08x\n", hprt0); - DWC2_WRITE_4(hsotg, HPRT0, hprt0); - } + spin_lock_irqsave(&hsotg->lock, flags); + retval = usb_hcd_link_urb_to_ep(hcd, urb); + if (retval) + goto fail2; - /* Clear reset bit in 10ms (FS/LS) or 50ms (HS) */ - usleep_range(50000, 70000); - hprt0 &= ~HPRT0_RST; - DWC2_WRITE_4(hsotg, HPRT0, hprt0); - hsotg->lx_state = DWC2_L0; /* Now back to On state */ - break; + retval = dwc2_hcd_urb_enqueue(hsotg, dwc2_urb, qh, qtd); + if (retval) + goto fail3; - case USB_PORT_FEAT_INDICATOR: - dev_dbg(hsotg->dev, - "SetPortFeature - USB_PORT_FEAT_INDICATOR\n"); - /* Not supported */ - break; + if (alloc_bandwidth) { + dwc2_allocate_bus_bandwidth(hcd, + dwc2_hcd_get_ep_bandwidth(hsotg, ep), + urb); + } - case USB_PORT_FEAT_TEST: - hprt0 = dwc2_read_hprt0(hsotg); - dev_dbg(hsotg->dev, - "SetPortFeature - USB_PORT_FEAT_TEST\n"); - hprt0 &= ~HPRT0_TSTCTL_MASK; - hprt0 |= (windex >> 8) << HPRT0_TSTCTL_SHIFT; - DWC2_WRITE_4(hsotg, HPRT0, hprt0); - break; + spin_unlock_irqrestore(&hsotg->lock, flags); - default: - retval = -EINVAL; - dev_err(hsotg->dev, - "SetPortFeature %1xh unknown or unsupported\n", - wvalue); - break; - } - break; + return 0; - default: -error: - retval = -EINVAL; - dev_dbg(hsotg->dev, - "Unknown hub control request: %1xh wIndex: %1xh wValue: %1xh\n", - typereq, windex, wvalue); - break; +fail3: + dwc2_urb->priv = NULL; + usb_hcd_unlink_urb_from_ep(hcd, urb); + if (qh_allocated && qh->channel && qh->channel->qh == qh) + qh->channel->qh = NULL; +fail2: + spin_unlock_irqrestore(&hsotg->lock, flags); + urb->hcpriv = NULL; + kfree(qtd); +fail1: + if (qh_allocated) { + struct dwc2_qtd *qtd2, *qtd2_tmp; + + ep->hcpriv = NULL; + dwc2_hcd_qh_unlink(hsotg, qh); + /* Free each QTD in the QH's QTD list */ + list_for_each_entry_safe(qtd2, qtd2_tmp, &qh->qtd_list, + qtd_list_entry) + dwc2_hcd_qtd_unlink_and_free(hsotg, qtd2, qh); + dwc2_hcd_qh_free(hsotg, qh); } +fail0: + kfree(dwc2_urb); return retval; } -int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg) +/* + * Aborts/cancels a USB transfer request. Always returns 0 to indicate success. + */ +static int _dwc2_hcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, + int status) { - u32 hfnum = DWC2_READ_4(hsotg, HFNUM); + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + int rc; + unsigned long flags; -#ifdef DWC2_DEBUG_SOF - dev_vdbg(hsotg->dev, "DWC OTG HCD GET FRAME NUMBER %d\n", - (hfnum & HFNUM_FRNUM_MASK) >> HFNUM_FRNUM_SHIFT); -#endif - return (hfnum & HFNUM_FRNUM_MASK) >> HFNUM_FRNUM_SHIFT; -} + dev_dbg(hsotg->dev, "DWC OTG HCD URB Dequeue\n"); + dwc2_dump_urb_info(hcd, urb, "urb_dequeue"); -int dwc2_hcd_is_b_host(struct dwc2_hsotg *hsotg) -{ - return hsotg->op_state == OTG_STATE_B_HOST; -} + spin_lock_irqsave(&hsotg->lock, flags); -struct dwc2_hcd_urb * -dwc2_hcd_urb_alloc(struct dwc2_hsotg *hsotg, int iso_desc_count, - gfp_t mem_flags) -{ - struct dwc2_hcd_urb *urb; - u32 size = sizeof(*urb) + iso_desc_count * - sizeof(struct dwc2_hcd_iso_packet_desc); + rc = usb_hcd_check_unlink_urb(hcd, urb, status); + if (rc) + goto out; - urb = malloc(size, M_USBHC, M_ZERO | mem_flags); - if (urb) - urb->packet_count = iso_desc_count; - return urb; -} + if (!urb->hcpriv) { + dev_dbg(hsotg->dev, "## urb->hcpriv is NULL ##\n"); + goto out; + } -void -dwc2_hcd_urb_free(struct dwc2_hsotg *hsotg, struct dwc2_hcd_urb *urb, - int iso_desc_count) -{ + rc = dwc2_hcd_urb_dequeue(hsotg, urb->hcpriv); - u32 size = sizeof(*urb) + iso_desc_count * - sizeof(struct dwc2_hcd_iso_packet_desc); + usb_hcd_unlink_urb_from_ep(hcd, urb); - free(urb, M_USBHC, size); -} + kfree(urb->hcpriv); + urb->hcpriv = NULL; -void -dwc2_hcd_urb_set_pipeinfo(struct dwc2_hsotg *hsotg, struct dwc2_hcd_urb *urb, - u8 dev_addr, u8 ep_num, u8 ep_type, u8 ep_dir, - u16 mps) -{ - if (dbg_perio() || - ep_type == USB_ENDPOINT_XFER_BULK || - ep_type == USB_ENDPOINT_XFER_CONTROL) - dev_dbg(hsotg->dev, "urb=%p, xfer=%p\n", urb, urb->priv); - urb->pipe_info.dev_addr = dev_addr; - urb->pipe_info.ep_num = ep_num; - urb->pipe_info.pipe_type = ep_type; - urb->pipe_info.pipe_dir = ep_dir; - urb->pipe_info.mps = mps; + /* Higher layer software sets URB status */ + spin_unlock(&hsotg->lock); + usb_hcd_giveback_urb(hcd, urb, status); + spin_lock(&hsotg->lock); + + dev_dbg(hsotg->dev, "Called usb_hcd_giveback_urb()\n"); + dev_dbg(hsotg->dev, " urb->status = %d\n", urb->status); +out: + spin_unlock_irqrestore(&hsotg->lock, flags); + + return rc; } /* - * NOTE: This function will be removed once the peripheral controller code - * is integrated and the driver is stable + * Frees resources in the DWC_otg controller related to a given endpoint. Also + * clears state in the HCD related to the endpoint. Any URBs for the endpoint + * must already be dequeued. */ -void dwc2_hcd_dump_state(struct dwc2_hsotg *hsotg) +static void _dwc2_hcd_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) { -#ifdef DWC2_DEBUG - struct dwc2_host_chan *chan; - struct dwc2_hcd_urb *urb; - struct dwc2_qtd *qtd; - int num_channels; - u32 np_tx_status; - u32 p_tx_status; - int i; + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); - num_channels = hsotg->core_params->host_channels; - dev_dbg(hsotg->dev, "\n"); dev_dbg(hsotg->dev, - "************************************************************\n"); - dev_dbg(hsotg->dev, "HCD State:\n"); - dev_dbg(hsotg->dev, " Num channels: %d\n", num_channels); - - for (i = 0; i < num_channels; i++) { - chan = hsotg->hc_ptr_array[i]; - dev_dbg(hsotg->dev, " Channel %d:\n", i); - dev_dbg(hsotg->dev, - " dev_addr: %d, ep_num: %d, ep_is_in: %d\n", - chan->dev_addr, chan->ep_num, chan->ep_is_in); - dev_dbg(hsotg->dev, " speed: %d\n", chan->speed); - dev_dbg(hsotg->dev, " ep_type: %d\n", chan->ep_type); - dev_dbg(hsotg->dev, " max_packet: %d\n", chan->max_packet); - dev_dbg(hsotg->dev, " data_pid_start: %d\n", - chan->data_pid_start); - dev_dbg(hsotg->dev, " multi_count: %d\n", chan->multi_count); - dev_dbg(hsotg->dev, " xfer_started: %d\n", - chan->xfer_started); - dev_dbg(hsotg->dev, " xfer_buf: %p\n", chan->xfer_buf); - dev_dbg(hsotg->dev, " xfer_dma: %08lx\n", - (unsigned long)chan->xfer_dma); - dev_dbg(hsotg->dev, " xfer_len: %d\n", chan->xfer_len); - dev_dbg(hsotg->dev, " xfer_count: %d\n", chan->xfer_count); - dev_dbg(hsotg->dev, " halt_on_queue: %d\n", - chan->halt_on_queue); - dev_dbg(hsotg->dev, " halt_pending: %d\n", - chan->halt_pending); - dev_dbg(hsotg->dev, " halt_status: %d\n", chan->halt_status); - dev_dbg(hsotg->dev, " do_split: %d\n", chan->do_split); - dev_dbg(hsotg->dev, " complete_split: %d\n", - chan->complete_split); - dev_dbg(hsotg->dev, " hub_addr: %d\n", chan->hub_addr); - dev_dbg(hsotg->dev, " hub_port: %d\n", chan->hub_port); - dev_dbg(hsotg->dev, " xact_pos: %d\n", chan->xact_pos); - dev_dbg(hsotg->dev, " requests: %d\n", chan->requests); - dev_dbg(hsotg->dev, " qh: %p\n", chan->qh); - - if (chan->xfer_started) { - dev_dbg(hsotg->dev, " hfnum: 0x%08x\n", - DWC2_READ_4(hsotg, HFNUM)); - dev_dbg(hsotg->dev, " hcchar: 0x%08x\n", - DWC2_READ_4(hsotg, HCCHAR(i))); - dev_dbg(hsotg->dev, " hctsiz: 0x%08x\n", - DWC2_READ_4(hsotg, HCTSIZ(i))); - dev_dbg(hsotg->dev, " hcint: 0x%08x\n", - DWC2_READ_4(hsotg, HCINT(i))); - dev_dbg(hsotg->dev, " hcintmsk: 0x%08x\n", - DWC2_READ_4(hsotg, HCINTMSK(i))); - } - - if (!(chan->xfer_started && chan->qh)) - continue; - - list_for_each_entry(qtd, &chan->qh->qtd_list, qtd_list_entry) { - if (!qtd->in_process) - break; - urb = qtd->urb; - dev_dbg(hsotg->dev, " URB Info:\n"); - dev_dbg(hsotg->dev, " qtd: %p, urb: %p\n", - qtd, urb); - if (urb) { - dev_dbg(hsotg->dev, - " Dev: %d, EP: %d %s\n", - dwc2_hcd_get_dev_addr(&urb->pipe_info), - dwc2_hcd_get_ep_num(&urb->pipe_info), - dwc2_hcd_is_pipe_in(&urb->pipe_info) ? - "IN" : "OUT"); - dev_dbg(hsotg->dev, - " Max packet size: %d\n", - dwc2_hcd_get_mps(&urb->pipe_info)); - dev_dbg(hsotg->dev, - " transfer_buffer: %p\n", - urb->buf); - dev_dbg(hsotg->dev, - " transfer_dma: %08lx\n", - (unsigned long)urb->dma); - dev_dbg(hsotg->dev, - " transfer_buffer_length: %d\n", - urb->length); - dev_dbg(hsotg->dev, " actual_length: %d\n", - urb->actual_length); - } - } - } + "DWC OTG HCD EP DISABLE: bEndpointAddress=0x%02x, ep->hcpriv=%p\n", + ep->desc.bEndpointAddress, ep->hcpriv); + dwc2_hcd_endpoint_disable(hsotg, ep, 250); +} - dev_dbg(hsotg->dev, " non_periodic_channels: %d\n", - hsotg->non_periodic_channels); - dev_dbg(hsotg->dev, " periodic_channels: %d\n", - hsotg->periodic_channels); - dev_dbg(hsotg->dev, " periodic_usecs: %d\n", hsotg->periodic_usecs); - np_tx_status = DWC2_READ_4(hsotg, GNPTXSTS); - dev_dbg(hsotg->dev, " NP Tx Req Queue Space Avail: %d\n", - (np_tx_status & TXSTS_QSPCAVAIL_MASK) >> TXSTS_QSPCAVAIL_SHIFT); - dev_dbg(hsotg->dev, " NP Tx FIFO Space Avail: %d\n", - (np_tx_status & TXSTS_FSPCAVAIL_MASK) >> TXSTS_FSPCAVAIL_SHIFT); - p_tx_status = DWC2_READ_4(hsotg, HPTXSTS); - dev_dbg(hsotg->dev, " P Tx Req Queue Space Avail: %d\n", - (p_tx_status & TXSTS_QSPCAVAIL_MASK) >> TXSTS_QSPCAVAIL_SHIFT); - dev_dbg(hsotg->dev, " P Tx FIFO Space Avail: %d\n", - (p_tx_status & TXSTS_FSPCAVAIL_MASK) >> TXSTS_FSPCAVAIL_SHIFT); - dwc2_hcd_dump_frrem(hsotg); +/* + * Resets endpoint specific parameter values, in current version used to reset + * the data toggle (as a WA). This function can be called from usb_clear_halt + * routine. + */ +static void _dwc2_hcd_endpoint_reset(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + unsigned long flags; - dwc2_dump_global_registers(hsotg); - dwc2_dump_host_registers(hsotg); dev_dbg(hsotg->dev, - "************************************************************\n"); - dev_dbg(hsotg->dev, "\n"); -#endif + "DWC OTG HCD EP RESET: bEndpointAddress=0x%02x\n", + ep->desc.bEndpointAddress); + + spin_lock_irqsave(&hsotg->lock, flags); + dwc2_hcd_endpoint_reset(hsotg, ep); + spin_unlock_irqrestore(&hsotg->lock, flags); } /* - * NOTE: This function will be removed once the peripheral controller code - * is integrated and the driver is stable + * Handles host mode interrupts for the DWC_otg controller. Returns IRQ_NONE if + * there was no interrupt to handle. Returns IRQ_HANDLED if there was a valid + * interrupt. + * + * This function is called by the USB core when an interrupt occurs */ -void dwc2_hcd_dump_frrem(struct dwc2_hsotg *hsotg) +static irqreturn_t _dwc2_hcd_irq(struct usb_hcd *hcd) { -#ifdef DWC2_DUMP_FRREM - dev_dbg(hsotg->dev, "Frame remaining at SOF:\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->frrem_samples, hsotg->frrem_accum, - hsotg->frrem_samples > 0 ? - hsotg->frrem_accum / hsotg->frrem_samples : 0); - dev_dbg(hsotg->dev, "\n"); - dev_dbg(hsotg->dev, "Frame remaining at start_transfer (uframe 7):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_7_samples, - hsotg->hfnum_7_frrem_accum, - hsotg->hfnum_7_samples > 0 ? - hsotg->hfnum_7_frrem_accum / hsotg->hfnum_7_samples : 0); - dev_dbg(hsotg->dev, "Frame remaining at start_transfer (uframe 0):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_0_samples, - hsotg->hfnum_0_frrem_accum, - hsotg->hfnum_0_samples > 0 ? - hsotg->hfnum_0_frrem_accum / hsotg->hfnum_0_samples : 0); - dev_dbg(hsotg->dev, "Frame remaining at start_transfer (uframe 1-6):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_other_samples, - hsotg->hfnum_other_frrem_accum, - hsotg->hfnum_other_samples > 0 ? - hsotg->hfnum_other_frrem_accum / hsotg->hfnum_other_samples : - 0); - dev_dbg(hsotg->dev, "\n"); - dev_dbg(hsotg->dev, "Frame remaining at sample point A (uframe 7):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_7_samples_a, hsotg->hfnum_7_frrem_accum_a, - hsotg->hfnum_7_samples_a > 0 ? - hsotg->hfnum_7_frrem_accum_a / hsotg->hfnum_7_samples_a : 0); - dev_dbg(hsotg->dev, "Frame remaining at sample point A (uframe 0):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_0_samples_a, hsotg->hfnum_0_frrem_accum_a, - hsotg->hfnum_0_samples_a > 0 ? - hsotg->hfnum_0_frrem_accum_a / hsotg->hfnum_0_samples_a : 0); - dev_dbg(hsotg->dev, "Frame remaining at sample point A (uframe 1-6):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_other_samples_a, hsotg->hfnum_other_frrem_accum_a, - hsotg->hfnum_other_samples_a > 0 ? - hsotg->hfnum_other_frrem_accum_a / hsotg->hfnum_other_samples_a - : 0); - dev_dbg(hsotg->dev, "\n"); - dev_dbg(hsotg->dev, "Frame remaining at sample point B (uframe 7):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_7_samples_b, hsotg->hfnum_7_frrem_accum_b, - hsotg->hfnum_7_samples_b > 0 ? - hsotg->hfnum_7_frrem_accum_b / hsotg->hfnum_7_samples_b : 0); - dev_dbg(hsotg->dev, "Frame remaining at sample point B (uframe 0):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_0_samples_b, hsotg->hfnum_0_frrem_accum_b, - (hsotg->hfnum_0_samples_b > 0) ? - hsotg->hfnum_0_frrem_accum_b / hsotg->hfnum_0_samples_b : 0); - dev_dbg(hsotg->dev, "Frame remaining at sample point B (uframe 1-6):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_other_samples_b, hsotg->hfnum_other_frrem_accum_b, - (hsotg->hfnum_other_samples_b > 0) ? - hsotg->hfnum_other_frrem_accum_b / hsotg->hfnum_other_samples_b - : 0); -#endif + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + + return dwc2_handle_hcd_intr(hsotg); } -struct wrapper_priv_data { - struct dwc2_hsotg *hsotg; -}; +/* + * Creates Status Change bitmap for the root hub and root port. The bitmap is + * returned in buf. Bit 0 is the status change indicator for the root hub. Bit 1 + * is the status change indicator for the single root port. Returns 1 if either + * change indicator is 1, otherwise returns 0. + */ +static int _dwc2_hcd_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + buf[0] = dwc2_hcd_is_status_changed(hsotg, 1) << 1; + return buf[0] != 0; +} -void dwc2_host_start(struct dwc2_hsotg *hsotg) +/* Handles hub class-specific requests */ +static int _dwc2_hcd_hub_control(struct usb_hcd *hcd, u16 typereq, u16 wvalue, + u16 windex, char *buf, u16 wlength) { -// struct usb_hcd *hcd = dwc2_hsotg_to_hcd(hsotg); - -// hcd->self.is_b_host = dwc2_hcd_is_b_host(hsotg); - _dwc2_hcd_start(hsotg); + int retval = dwc2_hcd_hub_control(dwc2_hcd_to_hsotg(hcd), typereq, + wvalue, windex, buf, wlength); + return retval; } -void dwc2_host_disconnect(struct dwc2_hsotg *hsotg) +/* Handles hub TT buffer clear completions */ +static void _dwc2_hcd_clear_tt_buffer_complete(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) { -// struct usb_hcd *hcd = dwc2_hsotg_to_hcd(hsotg); + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + struct dwc2_qh *qh; + unsigned long flags; + + qh = ep->hcpriv; + if (!qh) + return; -// hcd->self.is_b_host = 0; + spin_lock_irqsave(&hsotg->lock, flags); + qh->tt_buffer_dirty = 0; + + if (hsotg->flags.b.port_connect_status) + dwc2_hcd_queue_transactions(hsotg, DWC2_TRANSACTION_ALL); + + spin_unlock_irqrestore(&hsotg->lock, flags); } /* - * Work queue function for starting the HCD when A-Cable is connected + * HPRT0_SPD_HIGH_SPEED: high speed + * HPRT0_SPD_FULL_SPEED: full speed */ -STATIC void dwc2_hcd_start_func(void *data) +static void dwc2_change_bus_speed(struct usb_hcd *hcd, int speed) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + + if (hsotg->params.speed == speed) + return; + + hsotg->params.speed = speed; + queue_work(hsotg->wq_otg, &hsotg->wf_otg); +} + +static void dwc2_free_dev(struct usb_hcd *hcd, struct usb_device *udev) { - struct dwc2_hsotg *hsotg = data; + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); - dev_dbg(hsotg->dev, "%s() %p\n", __func__, hsotg); - dwc2_host_start(hsotg); + if (!hsotg->params.change_speed_quirk) + return; + + /* + * On removal, set speed to default high-speed. + */ + if (udev->parent && udev->parent->speed > USB_SPEED_UNKNOWN && + udev->parent->speed < USB_SPEED_HIGH) { + dev_info(hsotg->dev, "Set speed to default high-speed\n"); + dwc2_change_bus_speed(hcd, HPRT0_SPD_HIGH_SPEED); + } } -/* - * Reset work queue function - */ -STATIC void dwc2_hcd_reset_func(void *data) +static int dwc2_reset_device(struct usb_hcd *hcd, struct usb_device *udev) { - struct dwc2_hsotg *hsotg = data; - unsigned long flags; - u32 hprt0; + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); - dev_dbg(hsotg->dev, "USB RESET function called\n"); + if (!hsotg->params.change_speed_quirk) + return 0; - spin_lock_irqsave(&hsotg->lock, flags); + if (udev->speed == USB_SPEED_HIGH) { + dev_info(hsotg->dev, "Set speed to high-speed\n"); + dwc2_change_bus_speed(hcd, HPRT0_SPD_HIGH_SPEED); + } else if ((udev->speed == USB_SPEED_FULL || + udev->speed == USB_SPEED_LOW)) { + /* + * Change speed setting to full-speed if there's + * a full-speed or low-speed device plugged in. + */ + dev_info(hsotg->dev, "Set speed to full-speed\n"); + dwc2_change_bus_speed(hcd, HPRT0_SPD_FULL_SPEED); + } - hprt0 = dwc2_read_hprt0(hsotg); - hprt0 &= ~HPRT0_RST; - DWC2_WRITE_4(hsotg, HPRT0, hprt0); - hsotg->flags.b.port_reset_change = 1; + return 0; +} - dwc2_root_intr(hsotg->hsotg_sc); +/* XXX: Linux USB Stack */ +static struct hc_driver dwc2_hc_driver = { + .description = "dwc2_hsotg", + .product_desc = "DWC OTG Controller", + .hcd_priv_size = sizeof(struct wrapper_priv_data), - spin_unlock_irqrestore(&hsotg->lock, flags); -} + .irq = _dwc2_hcd_irq, + .flags = HCD_MEMORY | HCD_USB2 | HCD_BH, -/* - * ========================================================================= - * Linux HC Driver Functions - * ========================================================================= - */ + .start = _dwc2_hcd_start, + .stop = _dwc2_hcd_stop, + .urb_enqueue = _dwc2_hcd_urb_enqueue, + .urb_dequeue = _dwc2_hcd_urb_dequeue, + .endpoint_disable = _dwc2_hcd_endpoint_disable, + .endpoint_reset = _dwc2_hcd_endpoint_reset, + .get_frame_number = _dwc2_hcd_get_frame_number, -/* - * Initializes the DWC_otg controller and its root hub and prepares it for host - * mode operation. Activates the root port. Returns 0 on success and a negative - * error code on failure. - */ + .hub_status_data = _dwc2_hcd_hub_status_data, + .hub_control = _dwc2_hcd_hub_control, + .clear_tt_buffer_complete = _dwc2_hcd_clear_tt_buffer_complete, + + .bus_suspend = _dwc2_hcd_suspend, + .bus_resume = _dwc2_hcd_resume, + + .map_urb_for_dma = dwc2_map_urb_for_dma, + .unmap_urb_for_dma = dwc2_unmap_urb_for_dma, +}; +#endif /* * Frees secondary storage associated with the dwc2_hsotg structure contained @@ -2232,7 +5203,7 @@ STATIC void dwc2_hcd_free(struct dwc2_hsotg *hsotg) for (i = 0; i < MAX_EPS_CHANNELS; i++) { struct dwc2_host_chan *chan = hsotg->hc_ptr_array[i]; - if (chan != NULL) { + if (chan) { dev_dbg(hsotg->dev, "HCD Free channel #%i, chan=%p\n", i, chan); hsotg->hc_ptr_array[i] = NULL; @@ -2240,10 +5211,10 @@ STATIC void dwc2_hcd_free(struct dwc2_hsotg *hsotg) } } - if (hsotg->core_params->dma_enable > 0) { + if (hsotg->params.host_dma) { if (hsotg->status_buf) { usb_freemem(&hsotg->hsotg_sc->sc_bus, - &hsotg->status_buf_usbdma); + &hsotg->status_buf_dma_usb); hsotg->status_buf = NULL; } } else { @@ -2251,28 +5222,29 @@ STATIC void dwc2_hcd_free(struct dwc2_hsotg *hsotg) hsotg->status_buf = NULL; } - ahbcfg = DWC2_READ_4(hsotg, GAHBCFG); + ahbcfg = dwc2_readl(hsotg, GAHBCFG); /* Disable all interrupts */ ahbcfg &= ~GAHBCFG_GLBL_INTR_EN; - DWC2_WRITE_4(hsotg, GAHBCFG, ahbcfg); - DWC2_WRITE_4(hsotg, GINTMSK, 0); + dwc2_writel(hsotg, ahbcfg, GAHBCFG); + dwc2_writel(hsotg, 0, GINTMSK); if (hsotg->hw_params.snpsid >= DWC2_CORE_REV_3_00a) { - dctl = DWC2_READ_4(hsotg, DCTL); + dctl = dwc2_readl(hsotg, DCTL); dctl |= DCTL_SFTDISCON; - DWC2_WRITE_4(hsotg, DCTL, dctl); + dwc2_writel(hsotg, dctl, DCTL); } if (hsotg->wq_otg) { taskq_destroy(hsotg->wq_otg); } - free(hsotg->core_params, M_USBHC, sizeof(*hsotg->core_params)); - hsotg->core_params = NULL; + //free(hsotg->core_params, M_USBHC, sizeof(*hsotg->core_params)); + //hsotg->core_params = NULL; timeout_del(&hsotg->wkp_timer); } + STATIC void dwc2_hcd_release(struct dwc2_hsotg *hsotg) { /* Turn off all host-specific interrupts */ @@ -2290,6 +5262,7 @@ STATIC void dwc2_hcd_release(struct dwc2_hsotg *hsotg) int dwc2_hcd_init(struct dwc2_hsotg *hsotg) { struct dwc2_host_chan *channel; + u32 hcfg; int i, num_channels; int retval; @@ -2300,7 +5273,8 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg) retval = -ENOMEM; - dev_dbg(hsotg->dev, "hcfg=%08x\n", DWC2_READ_4(hsotg, HCFG)); + hcfg = dwc2_readl(hsotg, HCFG); + dev_dbg(hsotg->dev, "hcfg=%08x\n", hcfg); #ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS hsotg->frame_num_array = malloc(sizeof(*hsotg->frame_num_array) * @@ -2313,8 +5287,8 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg) FRAME_NUM_ARRAY_SIZE, M_USBHC, M_ZERO | M_WAITOK); if (!hsotg->last_frame_num_array) goto error1; - hsotg->last_frame_num = HFNUM_MAX_FRNUM; #endif + hsotg->last_frame_num = HFNUM_MAX_FRNUM; spin_lock_init(&hsotg->lock); @@ -2331,7 +5305,7 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg) /* Create new workqueue and init work */ retval = -ENOMEM; - hsotg->wq_otg = taskq_create("dwc2", 1, IPL_USB, 0); + hsotg->wq_otg = taskq_create("dwc2", 1, IPL_VM, 0); if (!hsotg->wq_otg) { dev_err(hsotg->dev, "Failed to create workqueue\n"); goto error2; @@ -2358,21 +5332,18 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg) * in the controller. Initialize the channel descriptor array. */ INIT_LIST_HEAD(&hsotg->free_hc_list); - num_channels = hsotg->core_params->host_channels; + num_channels = hsotg->params.host_channels; memset(&hsotg->hc_ptr_array[0], 0, sizeof(hsotg->hc_ptr_array)); for (i = 0; i < num_channels; i++) { channel = malloc(sizeof(*channel), M_USBHC, M_ZERO | M_WAITOK); - if (channel == NULL) + if (!channel) goto error3; channel->hc_num = i; INIT_LIST_HEAD(&channel->split_order_list_entry); hsotg->hc_ptr_array[i] = channel; } - if (hsotg->core_params->uframe_sched > 0) - dwc2_hcd_init_usecs(hsotg); - /* Initialize hsotg start work */ INIT_DELAYED_WORK(&hsotg->start_work, dwc2_hcd_start_func, hsotg); @@ -2386,13 +5357,15 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg) * pool. */ hsotg->status_buf = NULL; - if (hsotg->core_params->dma_enable > 0) { + if (hsotg->params.host_dma) { int error = usb_allocmem(&hsotg->hsotg_sc->sc_bus, DWC2_HCD_STATUS_BUF_SIZE, 0, USB_DMA_COHERENT, - &hsotg->status_buf_usbdma); + &hsotg->status_buf_dma_usb); if (!error) { - hsotg->status_buf = KERNADDR(&hsotg->status_buf_usbdma, 0); - hsotg->status_buf_dma = DMAADDR(&hsotg->status_buf_usbdma, 0); + hsotg->status_buf = + KERNADDR(&hsotg->status_buf_dma_usb, 0); + hsotg->status_buf_dma = + DMAADDR(&hsotg->status_buf_dma_usb, 0); } } else hsotg->status_buf = malloc(DWC2_HCD_STATUS_BUF_SIZE, M_USBHC, @@ -2410,7 +5383,7 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg) /* Initiate lx_state to L3 disconnected state */ hsotg->lx_state = DWC2_L3; - _dwc2_hcd_start(hsotg); + _dwc2_hcd_start(hsotg); dwc2_hcd_dump_state(hsotg); @@ -2421,9 +5394,10 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg) error3: dwc2_hcd_release(hsotg); error2: +#if 0 if (hsotg->core_params != NULL) free(hsotg->core_params, M_USBHC, sizeof(*hsotg->core_params)); - +#endif #ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS if (hsotg->last_frame_num_array != NULL) free(hsotg->last_frame_num_array, M_USBHC, @@ -2437,6 +5411,7 @@ error2: return retval; } +#if 0 /* * Removes the HCD. * Frees memory and resources associated with the HCD and deregisters the bus. @@ -2464,3 +5439,568 @@ void dwc2_hcd_remove(struct dwc2_hsotg *hsotg) free(hsotg->frame_num_array, M_USBHC, sizeof(*hsotg->frame_num_array) * FRAME_NUM_ARRAY_SIZE); #endif } +#endif + +/** + * dwc2_backup_host_registers() - Backup controller host registers. + * When suspending usb bus, registers needs to be backuped + * if controller power is disabled once suspended. + * + * @hsotg: Programming view of the DWC_otg controller + */ +STATIC int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hregs_backup *hr; + int i; + + dev_dbg(hsotg->dev, "%s\n", __func__); + + /* Backup Host regs */ + hr = &hsotg->hr_backup; + hr->hcfg = dwc2_readl(hsotg, HCFG); + hr->haintmsk = dwc2_readl(hsotg, HAINTMSK); + for (i = 0; i < hsotg->params.host_channels; ++i) + hr->hcintmsk[i] = dwc2_readl(hsotg, HCINTMSK(i)); + + hr->hprt0 = dwc2_read_hprt0(hsotg); + hr->hfir = dwc2_readl(hsotg, HFIR); + hr->hptxfsiz = dwc2_readl(hsotg, HPTXFSIZ); + hr->valid = true; + + return 0; +} + +/** + * dwc2_restore_host_registers() - Restore controller host registers. + * When resuming usb bus, device registers needs to be restored + * if controller power were disabled. + * + * @hsotg: Programming view of the DWC_otg controller + */ +STATIC int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hregs_backup *hr; + int i; + + dev_dbg(hsotg->dev, "%s\n", __func__); + + /* Restore host regs */ + hr = &hsotg->hr_backup; + if (!hr->valid) { + dev_err(hsotg->dev, "%s: no host registers to restore\n", + __func__); + return -EINVAL; + } + hr->valid = false; + + dwc2_writel(hsotg, hr->hcfg, HCFG); + dwc2_writel(hsotg, hr->haintmsk, HAINTMSK); + + for (i = 0; i < hsotg->params.host_channels; ++i) + dwc2_writel(hsotg, hr->hcintmsk[i], HCINTMSK(i)); + + dwc2_writel(hsotg, hr->hprt0, HPRT0); + dwc2_writel(hsotg, hr->hfir, HFIR); + dwc2_writel(hsotg, hr->hptxfsiz, HPTXFSIZ); + hsotg->frame_number = 0; + + return 0; +} + +/** + * dwc2_host_enter_hibernation() - Put controller in Hibernation. + * + * @hsotg: Programming view of the DWC_otg controller + */ +int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg) +{ + unsigned long flags; + int ret = 0; + u32 hprt0; + u32 pcgcctl; + u32 gusbcfg; + u32 gpwrdn; + + dev_dbg(hsotg->dev, "Preparing host for hibernation\n"); + ret = dwc2_backup_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup global registers\n", + __func__); + return ret; + } + ret = dwc2_backup_host_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup host registers\n", + __func__); + return ret; + } + + /* Enter USB Suspend Mode */ + hprt0 = dwc2_readl(hsotg, HPRT0); + hprt0 |= HPRT0_SUSP; + hprt0 &= ~HPRT0_ENA; + dwc2_writel(hsotg, hprt0, HPRT0); + + /* Wait for the HPRT0.PrtSusp register field to be set */ + if (dwc2_hsotg_wait_bit_set(hsotg, HPRT0, HPRT0_SUSP, 5000)) + dev_warn(hsotg->dev, "Suspend wasn't generated\n"); + + /* + * We need to disable interrupts to prevent servicing of any IRQ + * during going to hibernation + */ + spin_lock_irqsave(&hsotg->lock, flags); + hsotg->lx_state = DWC2_L2; + + gusbcfg = dwc2_readl(hsotg, GUSBCFG); + if (gusbcfg & GUSBCFG_ULPI_UTMI_SEL) { + /* ULPI interface */ + /* Suspend the Phy Clock */ + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl |= PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(10); + + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_PMUACTV; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + } else { + /* UTMI+ Interface */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_PMUACTV; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl |= PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(10); + } + + /* Enable interrupts from wake up logic */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_PMUINTSEL; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Unmask host mode interrupts in GPWRDN */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_DISCONN_DET_MSK; + gpwrdn |= GPWRDN_LNSTSCHG_MSK; + gpwrdn |= GPWRDN_STS_CHGINT_MSK; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Enable Power Down Clamp */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_PWRDNCLMP; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Switch off VDD */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_PWRDNSWTCH; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + + hsotg->hibernated = 1; + hsotg->bus_suspended = 1; + dev_dbg(hsotg->dev, "Host hibernation completed\n"); + spin_unlock_irqrestore(&hsotg->lock, flags); + return ret; +} + +/* + * dwc2_host_exit_hibernation() + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether resume is initiated by Device or Host. + * @param reset: indicates whether resume is initiated by Reset. + * + * Return: non-zero if failed to enter to hibernation. + * + * This function is for exiting from Host mode hibernation by + * Host Initiated Resume/Reset and Device Initiated Remote-Wakeup. + */ +int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup, + int reset) +{ + u32 gpwrdn; + u32 hprt0; + int ret = 0; + struct dwc2_gregs_backup *gr; + struct dwc2_hregs_backup *hr; + + gr = &hsotg->gr_backup; + hr = &hsotg->hr_backup; + + dev_dbg(hsotg->dev, + "%s: called with rem_wakeup = %d reset = %d\n", + __func__, rem_wakeup, reset); + + dwc2_hib_restore_common(hsotg, rem_wakeup, 1); + hsotg->hibernated = 0; + + /* + * This step is not described in functional spec but if not wait for + * this delay, mismatch interrupts occurred because just after restore + * core is in Device mode(gintsts.curmode == 0) + */ + mdelay(100); + + /* Clear all pending interupts */ + dwc2_writel(hsotg, 0xffffffff, GINTSTS); + + /* De-assert Restore */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_RESTORE; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Restore GUSBCFG, HCFG */ + dwc2_writel(hsotg, gr->gusbcfg, GUSBCFG); + dwc2_writel(hsotg, hr->hcfg, HCFG); + + /* De-assert Wakeup Logic */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_PMUACTV; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + hprt0 = hr->hprt0; + hprt0 |= HPRT0_PWR; + hprt0 &= ~HPRT0_ENA; + hprt0 &= ~HPRT0_SUSP; + dwc2_writel(hsotg, hprt0, HPRT0); + + hprt0 = hr->hprt0; + hprt0 |= HPRT0_PWR; + hprt0 &= ~HPRT0_ENA; + hprt0 &= ~HPRT0_SUSP; + + if (reset) { + hprt0 |= HPRT0_RST; + dwc2_writel(hsotg, hprt0, HPRT0); + + /* Wait for Resume time and then program HPRT again */ + mdelay(60); + hprt0 &= ~HPRT0_RST; + dwc2_writel(hsotg, hprt0, HPRT0); + } else { + hprt0 |= HPRT0_RES; + dwc2_writel(hsotg, hprt0, HPRT0); + + /* Wait for Resume time and then program HPRT again */ + mdelay(100); + hprt0 &= ~HPRT0_RES; + dwc2_writel(hsotg, hprt0, HPRT0); + } + /* Clear all interrupt status */ + hprt0 = dwc2_readl(hsotg, HPRT0); + hprt0 |= HPRT0_CONNDET; + hprt0 |= HPRT0_ENACHG; + hprt0 &= ~HPRT0_ENA; + dwc2_writel(hsotg, hprt0, HPRT0); + + hprt0 = dwc2_readl(hsotg, HPRT0); + + /* Clear all pending interupts */ + dwc2_writel(hsotg, 0xffffffff, GINTSTS); + + /* Restore global registers */ + ret = dwc2_restore_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore registers\n", + __func__); + return ret; + } + + /* Restore host registers */ + ret = dwc2_restore_host_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore host registers\n", + __func__); + return ret; + } + + if (rem_wakeup) { + dwc2_hcd_rem_wakeup(hsotg); + /* + * Change "port_connect_status_change" flag to re-enumerate, + * because after exit from hibernation port connection status + * is not detected. + */ + hsotg->flags.b.port_connect_status_change = 1; + } + + hsotg->hibernated = 0; + hsotg->bus_suspended = 0; + hsotg->lx_state = DWC2_L0; + dev_dbg(hsotg->dev, "Host hibernation restore complete\n"); + return ret; +} + +bool dwc2_host_can_poweroff_phy(struct dwc2_hsotg *dwc2) +{ +#if 0 + struct usb_device *root_hub = dwc2_hsotg_to_hcd(dwc2)->self.root_hub; + + /* If the controller isn't allowed to wakeup then we can power off. */ + if (!device_may_wakeup(dwc2->dev)) + return true; + + /* + * We don't want to power off the PHY if something under the + * root hub has wakeup enabled. + */ + if (usb_wakeup_enabled_descendants(root_hub)) + return false; +#endif + + /* No reason to keep the PHY powered, so allow poweroff */ + return true; +} + +/** + * dwc2_host_enter_partial_power_down() - Put controller in partial + * power down. + * + * @hsotg: Programming view of the DWC_otg controller + * + * Return: non-zero if failed to enter host partial power down. + * + * This function is for entering Host mode partial power down. + */ +int dwc2_host_enter_partial_power_down(struct dwc2_hsotg *hsotg) +{ + u32 pcgcctl; + u32 hprt0; + int ret = 0; + + dev_dbg(hsotg->dev, "Entering host partial power down started.\n"); + + /* Put this port in suspend mode. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_SUSP; + dwc2_writel(hsotg, hprt0, HPRT0); + udelay(5); + + /* Wait for the HPRT0.PrtSusp register field to be set */ + if (dwc2_hsotg_wait_bit_set(hsotg, HPRT0, HPRT0_SUSP, 3000)) + dev_warn(hsotg->dev, "Suspend wasn't generated\n"); + + /* Backup all registers */ + ret = dwc2_backup_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup global registers\n", + __func__); + return ret; + } + + ret = dwc2_backup_host_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup host registers\n", + __func__); + return ret; + } + + /* + * Clear any pending interrupts since dwc2 will not be able to + * clear them after entering partial_power_down. + */ + dwc2_writel(hsotg, 0xffffffff, GINTSTS); + + /* Put the controller in low power state */ + pcgcctl = dwc2_readl(hsotg, PCGCTL); + + pcgcctl |= PCGCTL_PWRCLMP; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(5); + + pcgcctl |= PCGCTL_RSTPDWNMODULE; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(5); + + pcgcctl |= PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + + /* Set in_ppd flag to 1 as here core enters suspend. */ + hsotg->in_ppd = 1; + hsotg->lx_state = DWC2_L2; + hsotg->bus_suspended = true; + + dev_dbg(hsotg->dev, "Entering host partial power down completed.\n"); + + return ret; +} + +/* + * dwc2_host_exit_partial_power_down() - Exit controller from host partial + * power down. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether resume is initiated by Reset. + * @restore: indicates whether need to restore the registers or not. + * + * Return: non-zero if failed to exit host partial power down. + * + * This function is for exiting from Host mode partial power down. + */ +int dwc2_host_exit_partial_power_down(struct dwc2_hsotg *hsotg, + int rem_wakeup, bool restore) +{ + u32 pcgcctl; + int ret = 0; + u32 hprt0; + + dev_dbg(hsotg->dev, "Exiting host partial power down started.\n"); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl &= ~PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(5); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl &= ~PCGCTL_PWRCLMP; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(5); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl &= ~PCGCTL_RSTPDWNMODULE; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + + udelay(100); + if (restore) { + ret = dwc2_restore_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore registers\n", + __func__); + return ret; + } + + ret = dwc2_restore_host_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore host registers\n", + __func__); + return ret; + } + } + + /* Drive resume signaling and exit suspend mode on the port. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_RES; + hprt0 &= ~HPRT0_SUSP; + dwc2_writel(hsotg, hprt0, HPRT0); + udelay(5); + + if (!rem_wakeup) { + /* Stop driveing resume signaling on the port. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 &= ~HPRT0_RES; + dwc2_writel(hsotg, hprt0, HPRT0); + + hsotg->bus_suspended = false; + } else { + /* Turn on the port power bit. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_PWR; + dwc2_writel(hsotg, hprt0, HPRT0); + + /* Connect hcd. */ + dwc2_hcd_connect(hsotg); + + timeout_add_msec(&hsotg->wkp_timer, 71); + } + + /* Set lx_state to and in_ppd to 0 as here core exits from suspend. */ + hsotg->in_ppd = 0; + hsotg->lx_state = DWC2_L0; + + dev_dbg(hsotg->dev, "Exiting host partial power down completed.\n"); + return ret; +} + +/** + * dwc2_host_enter_clock_gating() - Put controller in clock gating. + * + * @hsotg: Programming view of the DWC_otg controller + * + * This function is for entering Host mode clock gating. + */ +void dwc2_host_enter_clock_gating(struct dwc2_hsotg *hsotg) +{ + u32 hprt0; + u32 pcgctl; + + dev_dbg(hsotg->dev, "Entering host clock gating.\n"); + + /* Put this port in suspend mode. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_SUSP; + dwc2_writel(hsotg, hprt0, HPRT0); + + /* Set the Phy Clock bit as suspend is received. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl |= PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + /* Set the Gate hclk as suspend is received. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl |= PCGCTL_GATEHCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + hsotg->bus_suspended = true; + hsotg->lx_state = DWC2_L2; +} + +/** + * dwc2_host_exit_clock_gating() - Exit controller from clock gating. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether resume is initiated by remote wakeup + * + * This function is for exiting Host mode clock gating. + */ +void dwc2_host_exit_clock_gating(struct dwc2_hsotg *hsotg, int rem_wakeup) +{ + u32 hprt0; + u32 pcgctl; + + dev_dbg(hsotg->dev, "Exiting host clock gating.\n"); + + /* Clear the Gate hclk. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl &= ~PCGCTL_GATEHCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + /* Phy Clock bit. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl &= ~PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + /* Drive resume signaling and exit suspend mode on the port. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_RES; + hprt0 &= ~HPRT0_SUSP; + dwc2_writel(hsotg, hprt0, HPRT0); + udelay(5); + + if (!rem_wakeup) { + /* In case of port resume need to wait for 40 ms */ + dwc2_msleep(USB_RESUME_TIMEOUT); + + /* Stop driveing resume signaling on the port. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 &= ~HPRT0_RES; + dwc2_writel(hsotg, hprt0, HPRT0); + + hsotg->bus_suspended = false; + hsotg->lx_state = DWC2_L0; + } else { + timeout_add_msec(&hsotg->wkp_timer, 71); + } +} diff --git a/sys/dev/usb/dwc2/dwc2_hcd.h b/sys/dev/usb/dwc2/dwc2_hcd.h index c822b0fafb0..9bbcdc846e8 100644 --- a/sys/dev/usb/dwc2/dwc2_hcd.h +++ b/sys/dev/usb/dwc2/dwc2_hcd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: dwc2_hcd.h,v 1.15 2021/07/27 13:36:59 mglocker Exp $ */ +/* $OpenBSD: dwc2_hcd.h,v 1.16 2022/09/04 08:42:40 mglocker Exp $ */ /* $NetBSD: dwc2_hcd.h,v 1.9 2014/09/03 10:00:08 skrll Exp $ */ /* @@ -84,7 +84,7 @@ struct dwc2_qh; * @xfer_count: Number of bytes transferred so far * @start_pkt_count: Packet count at start of transfer * @xfer_started: True if the transfer has been started - * @ping: True if a PING request should be issued on this channel + * @do_ping: True if a PING request should be issued on this channel * @error_state: True if the error count for this transaction is non-zero * @halt_on_queue: True if this channel should be halted the next time a * request is queued for the channel. This is necessary in @@ -106,7 +106,7 @@ struct dwc2_qh; * @schinfo: Scheduling micro-frame bitmap * @ntd: Number of transfer descriptors for the transfer * @halt_status: Reason for halting the host channel - * @hcint Contents of the HCINT register when the interrupt came + * @hcint: Contents of the HCINT register when the interrupt came * @qh: QH for the transfer being processed by this channel * @hc_list_entry: For linking to list of host channels * @desc_list_addr: Current QH's descriptor list DMA address @@ -175,7 +175,8 @@ struct dwc2_hcd_pipe_info { u8 ep_num; u8 pipe_type; u8 pipe_dir; - u16 mps; + u16 maxp; + u16 maxp_mult; }; struct dwc2_hcd_iso_packet_desc { @@ -188,10 +189,10 @@ struct dwc2_hcd_iso_packet_desc { struct dwc2_qtd; struct dwc2_hcd_urb { - void *priv; /* the xfer handle */ + void *priv; struct dwc2_qtd *qtd; struct usb_dma *usbdma; - u8 *buf; + void *buf; dma_addr_t dma; struct usb_dma *setup_usbdma; void *setup_packet; @@ -204,7 +205,7 @@ struct dwc2_hcd_urb { u32 flags; u16 interval; struct dwc2_hcd_pipe_info pipe_info; - struct dwc2_hcd_iso_packet_desc iso_descs[0]; + struct dwc2_hcd_iso_packet_desc iso_descs[]; }; /* Phases for control transfers */ @@ -222,6 +223,43 @@ enum dwc2_transaction_type { DWC2_TRANSACTION_ALL, }; +/* The number of elements per LS bitmap (per port on multi_tt) */ +#define DWC2_ELEMENTS_PER_LS_BITMAP DIV_ROUND_UP(DWC2_LS_SCHEDULE_SLICES, \ + BITS_PER_LONG) + +/** + * struct dwc2_tt - dwc2 data associated with a usb_tt + * + * @refcount: Number of Queue Heads (QHs) holding a reference. + * @usb_tt: Pointer back to the official usb_tt. + * @periodic_bitmaps: Bitmap for which parts of the 1ms frame are accounted + * for already. Each is DWC2_ELEMENTS_PER_LS_BITMAP + * elements (so sizeof(long) times that in bytes). + * + * This structure is stored in the hcpriv of the official usb_tt. + */ +struct dwc2_tt { + int refcount; + struct usbd_tt *usb_tt; + unsigned long periodic_bitmaps[]; +}; + +/** + * struct dwc2_hs_transfer_time - Info about a transfer on the high speed bus. + * + * @start_schedule_us: The start time on the main bus schedule. Note that + * the main bus schedule is tightly packed and this + * time should be interpreted as tightly packed (so + * uFrame 0 starts at 0 us, uFrame 1 starts at 100 us + * instead of 125 us). + * @duration_us: How long this transfer goes. + */ + +struct dwc2_hs_transfer_time { + u32 start_schedule_us; + u16 duration_us; +}; + /** * struct dwc2_qh - Software queue head structure * @@ -233,6 +271,7 @@ enum dwc2_transaction_type { * - USB_ENDPOINT_XFER_ISOC * @ep_is_in: Endpoint direction * @maxp: Value from wMaxPacketSize field of Endpoint Descriptor + * @maxp_mult: Multiplier for maxp * @dev_speed: Device speed. One of the following values: * - USB_SPEED_LOW * - USB_SPEED_FULL @@ -246,17 +285,36 @@ enum dwc2_transaction_type { * @do_split: Full/low speed endpoint on high-speed hub requires split * @td_first: Index of first activated isochronous transfer descriptor * @td_last: Index of last activated isochronous transfer descriptor - * @usecs: Bandwidth in microseconds per (micro)frame - * @interval: Interval between transfers in (micro)frames - * @sched_frame: (Micro)frame to initialize a periodic transfer. - * The transfer executes in the following (micro)frame. - * @nak_frame: Internal variable used by the NAK holdoff code - * @frame_usecs: Internal variable used by the microframe scheduler - * @start_split_frame: (Micro)frame at which last start split was initialized + * @host_us: Bandwidth in microseconds per transfer as seen by host + * @device_us: Bandwidth in microseconds per transfer as seen by device + * @host_interval: Interval between transfers as seen by the host. If + * the host is high speed and the device is low speed this + * will be 8 times device interval. + * @device_interval: Interval between transfers as seen by the device. + * interval. + * @next_active_frame: (Micro)frame _before_ we next need to put something on + * the bus. We'll move the qh to active here. If the + * host is in high speed mode this will be a uframe. If + * the host is in low speed mode this will be a full frame. + * @start_active_frame: If we are partway through a split transfer, this will be + * what next_active_frame was when we started. Otherwise + * it should always be the same as next_active_frame. + * @num_hs_transfers: Number of transfers in hs_transfers. + * Normally this is 1 but can be more than one for splits. + * Always >= 1 unless the host is in low/full speed mode. + * @hs_transfers: Transfers that are scheduled as seen by the high speed + * bus. Not used if host is in low or full speed mode (but + * note that it IS USED if the device is low or full speed + * as long as the HOST is in high speed mode). + * @ls_start_schedule_slice: Start time (in slices) on the low speed bus + * schedule that's being used by this device. This + * will be on the periodic_bitmap in a + * "struct dwc2_tt". Not used if this device is high + * speed. Note that this is in "schedule slice" which + * is tightly packed. * @ntd: Actual number of transfer descriptors in a list * @dw_align_buf: Used instead of original buffer if its physical address * is not dword-aligned - * @dw_align_buf_size: Size of dw_align_buf * @dw_align_buf_dma: DMA address for dw_align_buf * @qtd_list: List of QTDs for this QH * @channel: Host channel currently processing transfers for this QH @@ -268,12 +326,19 @@ enum dwc2_transaction_type { * @n_bytes: Xfer Bytes array. Each element corresponds to a transfer * descriptor and indicates original XferSize value for the * descriptor + * @unreserve_timer: Timer for releasing periodic reservation. * @wait_timer: Timer used to wait before re-queuing. + * @dwc_tt: Pointer to our tt info (or NULL if no tt). + * @ttport: Port number within our tt. * @tt_buffer_dirty True if clear_tt_buffer_complete is pending + * @unreserve_pending: True if we planned to unreserve but haven't yet. + * @schedule_low_speed: True if we have a low/full speed component (either the + * host is in low/full speed mode or do_split). * @want_wait: We should wait before re-queuing; only matters for non- * periodic transfers and is ignored for periodic ones. * @wait_timer_cancel: Set to true to cancel the wait_timer. * + * @tt_buffer_dirty: True if EP's TT buffer is not clean. * A Queue Head (QH) holds the static characteristics of an endpoint and * maintains a list of transfers (QTDs) for that endpoint. A QH structure may * be entered in either the non-periodic or periodic schedule. @@ -283,34 +348,42 @@ struct dwc2_qh { u8 ep_type; u8 ep_is_in; u16 maxp; + u16 maxp_mult; u8 dev_speed; u8 data_toggle; u8 ping_state; u8 do_split; u8 td_first; u8 td_last; - u16 usecs; - u16 interval; - u16 sched_frame; - u16 nak_frame; - u16 frame_usecs[8]; - u16 start_split_frame; + u16 host_us; + u16 device_us; + u16 host_interval; + u16 device_interval; + u16 next_active_frame; + u16 start_active_frame; + s16 num_hs_transfers; + struct dwc2_hs_transfer_time hs_transfers[DWC2_HS_SCHEDULE_UFRAMES]; + u32 ls_start_schedule_slice; u16 ntd; - struct usb_dma dw_align_buf_usbdma; u8 *dw_align_buf; int dw_align_buf_size; dma_addr_t dw_align_buf_dma; + struct usb_dma dw_align_buf_usbdma; struct list_head qtd_list; struct dwc2_host_chan *channel; struct list_head qh_list_entry; struct usb_dma desc_list_usbdma; - struct dwc2_hcd_dma_desc *desc_list; + struct dwc2_dma_desc *desc_list; dma_addr_t desc_list_dma; u32 desc_list_sz; u32 *n_bytes; - /* XXX struct timer_list wait_timer; */ + struct timeout unreserve_timer; struct timeout wait_timer; + struct dwc2_tt *dwc_tt; + int ttport; unsigned tt_buffer_dirty:1; + unsigned unreserve_pending:1; + unsigned schedule_low_speed:1; unsigned want_wait:1; unsigned wait_timer_cancel:1; }; @@ -347,6 +420,10 @@ struct dwc2_qh { * @urb: URB for this transfer * @qh: Queue head for this QTD * @qtd_list_entry: For linking to the QH's list of QTDs + * @isoc_td_first: Index of first activated isochronous transfer + * descriptor in Descriptor DMA mode + * @isoc_td_last: Index of last activated isochronous transfer + * descriptor in Descriptor DMA mode * * A Queue Transfer Descriptor (QTD) holds the state of a bulk, control, * interrupt, or isochronous transfer. A single QTD is created for each URB @@ -387,11 +464,15 @@ struct hc_xfer_info { }; #endif +u32 dwc2_calc_frame_interval(struct dwc2_hsotg *hsotg); + +#if 0 /* Gets the struct usb_hcd that contains a struct dwc2_hsotg */ static inline struct usb_hcd *dwc2_hsotg_to_hcd(struct dwc2_hsotg *hsotg) { return (struct usb_hcd *)hsotg->priv; } +#endif /* * Inline used to disable one channel interrupt. Channel interrupts are @@ -402,19 +483,25 @@ static inline struct usb_hcd *dwc2_hsotg_to_hcd(struct dwc2_hsotg *hsotg) */ static inline void disable_hc_int(struct dwc2_hsotg *hsotg, int chnum, u32 intr) { - u32 mask = DWC2_READ_4(hsotg, HCINTMSK(chnum)); + u32 mask = dwc2_readl(hsotg, HCINTMSK(chnum)); mask &= ~intr; - DWC2_WRITE_4(hsotg, HCINTMSK(chnum), mask); + dwc2_writel(hsotg, mask, HCINTMSK(chnum)); } +void dwc2_hc_cleanup(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan); +void dwc2_hc_halt(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan, + enum dwc2_halt_status halt_status); +void dwc2_hc_start_transfer_ddma(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan); + /* * Reads HPRT0 in preparation to modify. It keeps the WC bits 0 so that if they * are read as 1, they won't clear when written back. */ static inline u32 dwc2_read_hprt0(struct dwc2_hsotg *hsotg) { - u32 hprt0 = DWC2_READ_4(hsotg, HPRT0); + u32 hprt0 = dwc2_readl(hsotg, HPRT0); hprt0 &= ~(HPRT0_ENA | HPRT0_CONNDET | HPRT0_ENACHG | HPRT0_OVRCURRCHG); return hprt0; @@ -430,9 +517,14 @@ static inline u8 dwc2_hcd_get_pipe_type(struct dwc2_hcd_pipe_info *pipe) return pipe->pipe_type; } -static inline u16 dwc2_hcd_get_mps(struct dwc2_hcd_pipe_info *pipe) +static inline u16 dwc2_hcd_get_maxp(struct dwc2_hcd_pipe_info *pipe) +{ + return pipe->maxp; +} + +static inline u16 dwc2_hcd_get_maxp_mult(struct dwc2_hcd_pipe_info *pipe) { - return pipe->mps; + return pipe->maxp_mult; } static inline u8 dwc2_hcd_get_dev_addr(struct dwc2_hcd_pipe_info *pipe) @@ -471,7 +563,7 @@ static inline u8 dwc2_hcd_is_pipe_out(struct dwc2_hcd_pipe_info *pipe) } extern int dwc2_hcd_init(struct dwc2_hsotg *hsotg); -extern void dwc2_hcd_remove(struct dwc2_hsotg *hsotg); +//extern void dwc2_hcd_remove(struct dwc2_hsotg *hsotg); /* Transaction Execution Functions */ extern enum dwc2_transaction_type dwc2_hcd_select_transactions( @@ -495,10 +587,16 @@ extern void dwc2_hcd_qtd_init(struct dwc2_qtd *qtd, struct dwc2_hcd_urb *urb); extern int dwc2_hcd_qtd_add(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, struct dwc2_qh *qh); -/* Removes and frees a QTD */ -extern void dwc2_hcd_qtd_unlink_and_free(struct dwc2_hsotg *hsotg, - struct dwc2_qtd *qtd, - struct dwc2_qh *qh); +/* Unlinks and frees a QTD */ +static inline void dwc2_hcd_qtd_unlink_and_free(struct dwc2_hsotg *hsotg, + struct dwc2_qtd *qtd, + struct dwc2_qh *qh) +{ + struct dwc2_softc *sc = hsotg->hsotg_sc; + + list_del(&qtd->qtd_list_entry); + pool_put(&sc->sc_qtdpool, qtd); +} /* Descriptor DMA support functions */ extern void dwc2_hcd_start_xfer_ddma(struct dwc2_hsotg *hsotg, @@ -519,6 +617,7 @@ extern void dwc2_hcd_qh_free_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh); #ifdef CONFIG_USB_DWC2_DEBUG_PERIODIC static inline bool dbg_hc(struct dwc2_host_chan *hc) { return true; } static inline bool dbg_qh(struct dwc2_qh *qh) { return true; } +static inline bool dbg_urb(struct urb *urb) { return true; } static inline bool dbg_perio(void) { return true; } #else /* !CONFIG_USB_DWC2_DEBUG_PERIODIC */ static inline bool dbg_hc(struct dwc2_host_chan *hc) @@ -533,16 +632,17 @@ static inline bool dbg_qh(struct dwc2_qh *qh) qh->ep_type == USB_ENDPOINT_XFER_CONTROL; } +#if 0 +static inline bool dbg_urb(struct urb *urb) +{ + return usb_pipetype(urb->pipe) == PIPE_BULK || + usb_pipetype(urb->pipe) == PIPE_CONTROL; +} +#endif static inline bool dbg_perio(void) { return false; } #endif -/* High bandwidth multiplier as encoded in highspeed endpoint descriptors */ -#define dwc2_hb_mult(wmaxpacketsize) (1 + (((wmaxpacketsize) >> 11) & 0x03)) - -/* Packet size for any kind of endpoint descriptor */ -#define dwc2_max_packet(wmaxpacketsize) ((wmaxpacketsize) & 0x07ff) - /* * Returns true if frame1 index is greater than frame2 index. The comparison * is done modulo FRLISTEN_64_SIZE. This accounts for the rollover of the @@ -586,6 +686,11 @@ static inline u16 dwc2_frame_num_inc(u16 frame, u16 inc) return (frame + inc) & HFNUM_MAX_FRNUM; } +static inline u16 dwc2_frame_num_dec(u16 frame, u16 dec) +{ + return (frame + HFNUM_MAX_FRNUM + 1 - dec) & HFNUM_MAX_FRNUM; +} + static inline u16 dwc2_full_frame_num(u16 frame) { return (frame & HFNUM_MAX_FRNUM) >> 3; @@ -602,7 +707,8 @@ static inline u16 dwc2_micro_frame_num(u16 frame) */ static inline u32 dwc2_read_core_intr(struct dwc2_hsotg *hsotg) { - return DWC2_READ_4(hsotg, GINTSTS) & DWC2_READ_4(hsotg, GINTMSK); + return dwc2_readl(hsotg, GINTSTS) & + dwc2_readl(hsotg, GINTMSK); } static inline u32 dwc2_hcd_urb_get_status(struct dwc2_hcd_urb *dwc2_urb) @@ -663,11 +769,11 @@ static inline u16 dwc2_hcd_get_ep_bandwidth(struct dwc2_hsotg *hsotg, return 0; } - return qh->usecs; + return qh->host_us; } extern void dwc2_hcd_save_data_toggle(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan, int chnum, + struct dwc2_host_chan *chan, int chnum, struct dwc2_qtd *qtd); /* HCD Core API */ @@ -707,19 +813,6 @@ extern int dwc2_hcd_is_b_host(struct dwc2_hsotg *hsotg); */ extern void dwc2_hcd_dump_state(struct dwc2_hsotg *hsotg); -/** - * dwc2_hcd_dump_frrem() - Dumps the average frame remaining at SOF - * - * @hsotg: The DWC2 HCD - * - * This can be used to determine average interrupt latency. Frame remaining is - * also shown for start transfer and two additional sample points. - * - * NOTE: This function will be removed once the peripheral controller code - * is integrated and the driver is stable - */ -extern void dwc2_hcd_dump_frrem(struct dwc2_hsotg *hsotg); - /* URB interface */ /* Transfer flags */ @@ -727,76 +820,30 @@ extern void dwc2_hcd_dump_frrem(struct dwc2_hsotg *hsotg); #define URB_SEND_ZERO_PACKET 0x2 /* Host driver callbacks */ +struct dwc2_tt *dwc2_host_get_tt_info(struct dwc2_hsotg *hsotg, + void *context, gfp_t mem_flags, + int *ttport); -extern void dwc2_host_start(struct dwc2_hsotg *hsotg); -extern void dwc2_host_disconnect(struct dwc2_hsotg *hsotg); -extern void dwc2_host_hub_info(struct dwc2_hsotg *hsotg, void *context, - int *hub_addr, int *hub_port); -extern int dwc2_host_get_speed(struct dwc2_hsotg *hsotg, void *context); -extern void dwc2_host_complete(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, - int status); - -#ifdef DEBUG -/* - * Macro to sample the remaining PHY clocks left in the current frame. This - * may be used during debugging to determine the average time it takes to - * execute sections of code. There are two possible sample points, "a" and - * "b", so the _letter_ argument must be one of these values. - * - * To dump the average sample times, read the "hcd_frrem" sysfs attribute. For - * example, "cat /sys/devices/lm0/hcd_frrem". - */ -#define dwc2_sample_frrem(_hcd_, _qh_, _letter_) \ -do { \ - struct hfnum_data _hfnum_; \ - struct dwc2_qtd *_qtd_; \ - \ - _qtd_ = list_entry((_qh_)->qtd_list.next, struct dwc2_qtd, \ - qtd_list_entry); \ - if (usb_pipeint(_qtd_->urb->pipe) && \ - (_qh_)->start_split_frame != 0 && !_qtd_->complete_split) { \ - _hfnum_.d32 = DWC2_READ_4((_hcd_), HFNUM); \ - switch (_hfnum_.b.frnum & 0x7) { \ - case 7: \ - (_hcd_)->hfnum_7_samples_##_letter_++; \ - (_hcd_)->hfnum_7_frrem_accum_##_letter_ += \ - _hfnum_.b.frrem; \ - break; \ - case 0: \ - (_hcd_)->hfnum_0_samples_##_letter_++; \ - (_hcd_)->hfnum_0_frrem_accum_##_letter_ += \ - _hfnum_.b.frrem; \ - break; \ - default: \ - (_hcd_)->hfnum_other_samples_##_letter_++; \ - (_hcd_)->hfnum_other_frrem_accum_##_letter_ += \ - _hfnum_.b.frrem; \ - break; \ - } \ - } \ -} while (0) -#else -#define dwc2_sample_frrem(_hcd_, _qh_, _letter_) do {} while (0) -#endif - - -void dwc2_wakeup_detected(void *); +void dwc2_host_put_tt_info(struct dwc2_hsotg *hsotg, + struct dwc2_tt *dwc_tt); +int dwc2_host_get_speed(struct dwc2_hsotg *hsotg, void *context); +void dwc2_host_complete(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, + int status); +/* Required for OpenBSD */ +struct dwc2_hcd_urb * dwc2_hcd_urb_alloc(struct dwc2_hsotg *, int, gfp_t); +void dwc2_hcd_urb_free(struct dwc2_hsotg *, struct dwc2_hcd_urb *, int); int dwc2_hcd_urb_dequeue(struct dwc2_hsotg *, struct dwc2_hcd_urb *); -void dwc2_hcd_reinit(struct dwc2_hsotg *); int dwc2_hcd_hub_control(struct dwc2_hsotg *, u16, u16, u16, char *, u16); -struct dwc2_hsotg *dwc2_hcd_to_hsotg(struct usb_hcd *); -int dwc2_hcd_urb_enqueue(struct dwc2_hsotg *hsotg, - struct dwc2_hcd_urb *urb, struct dwc2_qh *qh, - struct dwc2_qtd *qtd); void dwc2_hcd_urb_set_pipeinfo(struct dwc2_hsotg *, struct dwc2_hcd_urb *, - u8 ,u8, u8, u8, u16); + u8, u8, u8, u8, u16, u16); -struct dwc2_hcd_urb * dwc2_hcd_urb_alloc(struct dwc2_hsotg *, int, gfp_t); -void dwc2_hcd_urb_free(struct dwc2_hsotg *, struct dwc2_hcd_urb *, int); - -int _dwc2_hcd_start(struct dwc2_hsotg *); +int dwc2_hcd_urb_enqueue(struct dwc2_hsotg *hsotg, + struct dwc2_hcd_urb *urb, struct dwc2_qh *qh, + struct dwc2_qtd *qtd); +void dwc2_allocate_bus_bandwidth(struct dwc2_hsotg *, u16, struct usbd_xfer *); -int dwc2_host_is_b_hnp_enabled(struct dwc2_hsotg *); +long dwc2_usb_calc_bus_time(int, int, int, int); +int dwc2_ttthink_to_ns(struct dwc2_hsotg *, void *, int); #endif /* __DWC2_HCD_H__ */ diff --git a/sys/dev/usb/dwc2/dwc2_hcdddma.c b/sys/dev/usb/dwc2/dwc2_hcdddma.c index 78a33ad5854..c562213a111 100644 --- a/sys/dev/usb/dwc2/dwc2_hcdddma.c +++ b/sys/dev/usb/dwc2/dwc2_hcdddma.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dwc2_hcdddma.c,v 1.20 2021/11/28 09:25:02 mglocker Exp $ */ +/* $OpenBSD: dwc2_hcdddma.c,v 1.21 2022/09/04 08:42:40 mglocker Exp $ */ /* $NetBSD: dwc2_hcdddma.c,v 1.6 2014/04/03 06:34:58 skrll Exp $ */ /* @@ -40,7 +40,6 @@ /* * This file contains the Descriptor DMA implementation for Host mode */ - #include #include #include @@ -88,21 +87,28 @@ STATIC u16 dwc2_max_desc_num(struct dwc2_qh *qh) STATIC u16 dwc2_frame_incr_val(struct dwc2_qh *qh) { return qh->dev_speed == USB_SPEED_HIGH ? - (qh->interval + 8 - 1) / 8 : qh->interval; + (qh->host_interval + 8 - 1) / 8 : qh->host_interval; } STATIC int dwc2_desc_list_alloc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, gfp_t flags) { int err; +#if 0 + struct kmem_cache *desc_cache; - qh->desc_list = NULL; - qh->desc_list_sz = sizeof(struct dwc2_hcd_dma_desc) * + if (qh->ep_type == USB_ENDPOINT_XFER_ISOC && + qh->dev_speed == USB_SPEED_HIGH) + desc_cache = hsotg->desc_hsisoc_cache; + else + desc_cache = hsotg->desc_gen_cache; +#endif + + qh->desc_list_sz = sizeof(struct dwc2_dma_desc) * dwc2_max_desc_num(qh); err = usb_allocmem(&hsotg->hsotg_sc->sc_bus, qh->desc_list_sz, 0, USB_DMA_COHERENT, &qh->desc_list_usbdma); - if (err) return -ENOMEM; @@ -111,7 +117,6 @@ STATIC int dwc2_desc_list_alloc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, qh->n_bytes = malloc(sizeof(u32) * dwc2_max_desc_num(qh), M_USBHC, M_ZERO | M_WAITOK); - if (!qh->n_bytes) { usb_freemem(&hsotg->hsotg_sc->sc_bus, &qh->desc_list_usbdma); qh->desc_list = NULL; @@ -123,6 +128,15 @@ STATIC int dwc2_desc_list_alloc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, STATIC void dwc2_desc_list_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) { +#if 0 + struct kmem_cache *desc_cache; + + if (qh->ep_type == USB_ENDPOINT_XFER_ISOC && + qh->dev_speed == USB_SPEED_HIGH) + desc_cache = hsotg->desc_hsisoc_cache; + else + desc_cache = hsotg->desc_gen_cache; +#endif if (qh->desc_list) { usb_freemem(&hsotg->hsotg_sc->sc_bus, &qh->desc_list_usbdma); @@ -145,7 +159,6 @@ STATIC int dwc2_frame_list_alloc(struct dwc2_hsotg *hsotg, gfp_t mem_flags) hsotg->frame_list = NULL; err = usb_allocmem(&hsotg->hsotg_sc->sc_bus, hsotg->frame_list_sz, 0, USB_DMA_COHERENT, &hsotg->frame_list_usbdma); - if (!err) { hsotg->frame_list = KERNADDR(&hsotg->frame_list_usbdma, 0); hsotg->frame_list_dma = DMAADDR(&hsotg->frame_list_usbdma, 0); @@ -184,19 +197,19 @@ STATIC void dwc2_per_sched_enable(struct dwc2_hsotg *hsotg, u32 fr_list_en) spin_lock_irqsave(&hsotg->lock, flags); - hcfg = DWC2_READ_4(hsotg, HCFG); + hcfg = dwc2_readl(hsotg, HCFG); if (hcfg & HCFG_PERSCHEDENA) { /* already enabled */ spin_unlock_irqrestore(&hsotg->lock, flags); return; } - DWC2_WRITE_4(hsotg, HFLBADDR, hsotg->frame_list_dma); + dwc2_writel(hsotg, hsotg->frame_list_dma, HFLBADDR); hcfg &= ~HCFG_FRLISTEN_MASK; hcfg |= fr_list_en | HCFG_PERSCHEDENA; dev_vdbg(hsotg->dev, "Enabling Periodic schedule\n"); - DWC2_WRITE_4(hsotg, HCFG, hcfg); + dwc2_writel(hsotg, hcfg, HCFG); spin_unlock_irqrestore(&hsotg->lock, flags); } @@ -208,7 +221,7 @@ STATIC void dwc2_per_sched_disable(struct dwc2_hsotg *hsotg) spin_lock_irqsave(&hsotg->lock, flags); - hcfg = DWC2_READ_4(hsotg, HCFG); + hcfg = dwc2_readl(hsotg, HCFG); if (!(hcfg & HCFG_PERSCHEDENA)) { /* already disabled */ spin_unlock_irqrestore(&hsotg->lock, flags); @@ -217,7 +230,7 @@ STATIC void dwc2_per_sched_disable(struct dwc2_hsotg *hsotg) hcfg &= ~HCFG_PERSCHEDENA; dev_vdbg(hsotg->dev, "Disabling Periodic schedule\n"); - DWC2_WRITE_4(hsotg, HCFG, hcfg); + dwc2_writel(hsotg, hcfg, HCFG); spin_unlock_irqrestore(&hsotg->lock, flags); } @@ -251,7 +264,7 @@ STATIC void dwc2_update_frame_list(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, chan = qh->channel; inc = dwc2_frame_incr_val(qh); if (qh->ep_type == USB_ENDPOINT_XFER_ISOC) - i = dwc2_frame_list_idx(qh->sched_frame); + i = dwc2_frame_list_idx(qh->next_active_frame); else i = 0; @@ -275,13 +288,13 @@ STATIC void dwc2_update_frame_list(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, return; chan->schinfo = 0; - if (chan->speed == USB_SPEED_HIGH && qh->interval) { + if (chan->speed == USB_SPEED_HIGH && qh->host_interval) { j = 1; /* TODO - check this */ - inc = (8 + qh->interval - 1) / qh->interval; + inc = (8 + qh->host_interval - 1) / qh->host_interval; for (i = 0; i < inc; i++) { chan->schinfo |= j; - j = j << qh->interval; + j = j << qh->host_interval; } } else { chan->schinfo = 0xff; @@ -294,7 +307,7 @@ STATIC void dwc2_release_channel_ddma(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan = qh->channel; if (dwc2_qh_is_non_per(qh)) { - if (hsotg->core_params->uframe_sched > 0) + if (hsotg->params.uframe_sched) hsotg->available_host_channels++; else hsotg->non_periodic_channels--; @@ -319,7 +332,7 @@ STATIC void dwc2_release_channel_ddma(struct dwc2_hsotg *hsotg, qh->ntd = 0; if (qh->desc_list) - memset(qh->desc_list, 0, sizeof(struct dwc2_hcd_dma_desc) * + memset(qh->desc_list, 0, sizeof(struct dwc2_dma_desc) * dwc2_max_desc_num(qh)); } @@ -329,6 +342,7 @@ STATIC void dwc2_release_channel_ddma(struct dwc2_hsotg *hsotg, * * @hsotg: The HCD state structure for the DWC OTG controller * @qh: The QH to init + * @mem_flags: Indicates the type of memory allocation * * Return: 0 if successful, negative error code otherwise * @@ -401,7 +415,7 @@ void dwc2_hcd_qh_free_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) if ((qh->ep_type == USB_ENDPOINT_XFER_ISOC || qh->ep_type == USB_ENDPOINT_XFER_INT) && - (hsotg->core_params->uframe_sched > 0 || + (hsotg->params.uframe_sched || !hsotg->periodic_channels) && hsotg->frame_list) { dwc2_per_sched_disable(hsotg); dwc2_frame_list_free(hsotg); @@ -428,7 +442,10 @@ STATIC u16 dwc2_calc_starting_frame(struct dwc2_hsotg *hsotg, hsotg->frame_number = dwc2_hcd_get_frame_number(hsotg); - /* sched_frame is always frame number (not uFrame) both in FS and HS! */ + /* + * next_active_frame is always frame number (not uFrame) both in FS + * and HS! + */ /* * skip_frames is used to limit activated descriptors number @@ -511,13 +528,13 @@ STATIC u16 dwc2_recalc_initial_desc_idx(struct dwc2_hsotg *hsotg, */ fr_idx_tmp = dwc2_frame_list_idx(frame); fr_idx = (FRLISTEN_64_SIZE + - dwc2_frame_list_idx(qh->sched_frame) - fr_idx_tmp) - % dwc2_frame_incr_val(qh); + dwc2_frame_list_idx(qh->next_active_frame) - + fr_idx_tmp) % dwc2_frame_incr_val(qh); fr_idx = (fr_idx + fr_idx_tmp) % FRLISTEN_64_SIZE; } else { - qh->sched_frame = dwc2_calc_starting_frame(hsotg, qh, + qh->next_active_frame = dwc2_calc_starting_frame(hsotg, qh, &skip_frames); - fr_idx = dwc2_frame_list_idx(qh->sched_frame); + fr_idx = dwc2_frame_list_idx(qh->next_active_frame); } qh->td_first = qh->td_last = dwc2_frame_to_desc_idx(qh, fr_idx); @@ -536,7 +553,7 @@ STATIC void dwc2_fill_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, u32 max_xfer_size, u16 idx) { - struct dwc2_hcd_dma_desc *dma_desc = &qh->desc_list[idx]; + struct dwc2_dma_desc *dma_desc = &qh->desc_list[idx]; struct dwc2_hcd_iso_packet_desc *frame_desc; memset(dma_desc, 0, sizeof(*dma_desc)); @@ -564,8 +581,8 @@ STATIC void dwc2_fill_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, #endif usb_syncmem(&qh->desc_list_usbdma, - (idx * sizeof(struct dwc2_hcd_dma_desc)), - sizeof(struct dwc2_hcd_dma_desc), + (idx * sizeof(struct dwc2_dma_desc)), + sizeof(struct dwc2_dma_desc), BUS_DMASYNC_PREWRITE); } @@ -579,7 +596,7 @@ STATIC void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, u16 next_idx; idx = qh->td_last; - inc = qh->interval; + inc = qh->host_interval; hsotg->frame_number = dwc2_hcd_get_frame_number(hsotg); cur_idx = dwc2_frame_list_idx(hsotg->frame_number); next_idx = dwc2_desclist_idx_inc(qh->td_last, inc, qh->dev_speed); @@ -601,11 +618,11 @@ STATIC void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, } } - if (qh->interval) { - ntd_max = (dwc2_max_desc_num(qh) + qh->interval - 1) / - qh->interval; + if (qh->host_interval) { + ntd_max = (dwc2_max_desc_num(qh) + qh->host_interval - 1) / + qh->host_interval; if (skip_frames && !qh->channel) - ntd_max -= skip_frames / qh->interval; + ntd_max -= skip_frames / qh->host_interval; } max_xfer_size = qh->dev_speed == USB_SPEED_HIGH ? @@ -636,10 +653,9 @@ STATIC void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, if (qh->ntd == ntd_max) { idx = dwc2_desclist_idx_dec(qh->td_last, inc, qh->dev_speed); qh->desc_list[idx].status |= HOST_DMA_IOC; - usb_syncmem(&qh->desc_list_usbdma, - (idx * sizeof(struct dwc2_hcd_dma_desc)), - sizeof(struct dwc2_hcd_dma_desc), + (idx * sizeof(struct dwc2_dma_desc)), + sizeof(struct dwc2_dma_desc), BUS_DMASYNC_PREWRITE); } #else @@ -671,8 +687,8 @@ STATIC void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, qh->desc_list[idx].status |= HOST_DMA_IOC; usb_syncmem(&qh->desc_list_usbdma, - (idx * sizeof(struct dwc2_hcd_dma_desc)), - sizeof(struct dwc2_hcd_dma_desc), + (idx * sizeof(struct dwc2_dma_desc)), + sizeof(struct dwc2_dma_desc), BUS_DMASYNC_PREWRITE); #endif } @@ -682,11 +698,11 @@ STATIC void dwc2_fill_host_dma_desc(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, struct dwc2_qh *qh, int n_desc) { - struct dwc2_hcd_dma_desc *dma_desc = &qh->desc_list[n_desc]; + struct dwc2_dma_desc *dma_desc = &qh->desc_list[n_desc]; int len = chan->xfer_len; - if (len > MAX_DMA_DESC_SIZE - (chan->max_packet - 1)) - len = MAX_DMA_DESC_SIZE - (chan->max_packet - 1); + if (len > HOST_DMA_NBYTES_LIMIT - (chan->max_packet - 1)) + len = HOST_DMA_NBYTES_LIMIT - (chan->max_packet - 1); if (chan->ep_is_in) { int num_packets; @@ -712,8 +728,8 @@ STATIC void dwc2_fill_host_dma_desc(struct dwc2_hsotg *hsotg, dma_desc->buf = (u32)chan->xfer_dma; usb_syncmem(&qh->desc_list_usbdma, - (n_desc * sizeof(struct dwc2_hcd_dma_desc)), - sizeof(struct dwc2_hcd_dma_desc), + (n_desc * sizeof(struct dwc2_dma_desc)), + sizeof(struct dwc2_dma_desc), BUS_DMASYNC_PREWRITE); /* @@ -723,7 +739,7 @@ STATIC void dwc2_fill_host_dma_desc(struct dwc2_hsotg *hsotg, if (len > chan->xfer_len) { chan->xfer_len = 0; } else { - chan->xfer_dma += len; /* XXXNH safe */ + chan->xfer_dma += len; chan->xfer_len -= len; } } @@ -768,8 +784,8 @@ STATIC void dwc2_init_non_isoc_dma_desc(struct dwc2_hsotg *hsotg, &qh->desc_list[n_desc - 1]); usb_syncmem(&qh->desc_list_usbdma, ((n_desc - 1) * - sizeof(struct dwc2_hcd_dma_desc)), - sizeof(struct dwc2_hcd_dma_desc), + sizeof(struct dwc2_dma_desc)), + sizeof(struct dwc2_dma_desc), BUS_DMASYNC_PREWRITE); } dwc2_fill_host_dma_desc(hsotg, chan, qtd, qh, n_desc); @@ -797,15 +813,15 @@ STATIC void dwc2_init_non_isoc_dma_desc(struct dwc2_hsotg *hsotg, dev_vdbg(hsotg->dev, "set IOC/EOL/A bits in desc %d (%p)\n", n_desc - 1, &qh->desc_list[n_desc - 1]); usb_syncmem(&qh->desc_list_usbdma, - ((n_desc - 1) * sizeof(struct dwc2_hcd_dma_desc)), - sizeof(struct dwc2_hcd_dma_desc), + ((n_desc - 1) * sizeof(struct dwc2_dma_desc)), + sizeof(struct dwc2_dma_desc), BUS_DMASYNC_PREWRITE); if (n_desc > 1) { qh->desc_list[0].status |= HOST_DMA_A; dev_vdbg(hsotg->dev, "set A bit in desc 0 (%p)\n", &qh->desc_list[0]); usb_syncmem(&qh->desc_list_usbdma, 0, - sizeof(struct dwc2_hcd_dma_desc), + sizeof(struct dwc2_dma_desc), BUS_DMASYNC_PREWRITE); } chan->ntd = n_desc; @@ -881,7 +897,7 @@ STATIC int dwc2_cmpl_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, struct dwc2_qh *qh, u16 idx) { - struct dwc2_hcd_dma_desc *dma_desc; + struct dwc2_dma_desc *dma_desc; struct dwc2_hcd_iso_packet_desc *frame_desc; u16 remain = 0; int rc = 0; @@ -890,8 +906,8 @@ STATIC int dwc2_cmpl_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, return -EINVAL; usb_syncmem(&qh->desc_list_usbdma, - (idx * sizeof(struct dwc2_hcd_dma_desc)), - sizeof(struct dwc2_hcd_dma_desc), + (idx * sizeof(struct dwc2_dma_desc)), + sizeof(struct dwc2_dma_desc), BUS_DMASYNC_POSTREAD); dma_desc = &qh->desc_list[idx]; @@ -1020,7 +1036,7 @@ STATIC void dwc2_complete_isoc_xfer_ddma(struct dwc2_hsotg *hsotg, idx); if (rc < 0) return; - idx = dwc2_desclist_idx_inc(idx, qh->interval, + idx = dwc2_desclist_idx_inc(idx, qh->host_interval, chan->speed); if (!rc) continue; @@ -1030,7 +1046,7 @@ STATIC void dwc2_complete_isoc_xfer_ddma(struct dwc2_hsotg *hsotg, /* rc == DWC2_CMPL_STOP */ - if (qh->interval >= 32) + if (qh->host_interval >= 32) goto stop_scan; qh->td_first = idx; @@ -1052,9 +1068,9 @@ stop_scan: } STATIC int dwc2_update_non_isoc_urb_state_ddma(struct dwc2_hsotg *hsotg, - struct dwc2_host_chan *chan, + struct dwc2_host_chan *chan, struct dwc2_qtd *qtd, - struct dwc2_hcd_dma_desc *dma_desc, + struct dwc2_dma_desc *dma_desc, enum dwc2_halt_status halt_status, u32 n_bytes, int *xfer_done) { @@ -1142,7 +1158,7 @@ STATIC int dwc2_process_non_isoc_desc(struct dwc2_hsotg *hsotg, { struct dwc2_qh *qh = chan->qh; struct dwc2_hcd_urb *urb = qtd->urb; - struct dwc2_hcd_dma_desc *dma_desc; + struct dwc2_dma_desc *dma_desc; u32 n_bytes; int failed; @@ -1152,8 +1168,8 @@ STATIC int dwc2_process_non_isoc_desc(struct dwc2_hsotg *hsotg, return -EINVAL; usb_syncmem(&qh->desc_list_usbdma, - (desc_num * sizeof(struct dwc2_hcd_dma_desc)), - sizeof(struct dwc2_hcd_dma_desc), + (desc_num * sizeof(struct dwc2_dma_desc)), + sizeof(struct dwc2_dma_desc), BUS_DMASYNC_POSTREAD); dma_desc = &qh->desc_list[desc_num]; @@ -1164,14 +1180,11 @@ STATIC int dwc2_process_non_isoc_desc(struct dwc2_hsotg *hsotg, failed = dwc2_update_non_isoc_urb_state_ddma(hsotg, chan, qtd, dma_desc, halt_status, n_bytes, xfer_done); - if (*xfer_done && urb->status != -EINPROGRESS) - failed = 1; - - if (failed) { + if (failed || (*xfer_done && urb->status != -EINPROGRESS)) { dwc2_host_complete(hsotg, qtd, urb->status); dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); - dev_vdbg(hsotg->dev, "failed=%1x xfer_done=%1x status=%08x\n", - failed, *xfer_done, urb->status); + dev_vdbg(hsotg->dev, "failed=%1x xfer_done=%1x\n", + failed, *xfer_done); return failed; } @@ -1226,21 +1239,25 @@ STATIC void dwc2_complete_non_isoc_xfer_ddma(struct dwc2_hsotg *hsotg, list_for_each_safe(qtd_item, qtd_tmp, &qh->qtd_list) { int i; + int qtd_desc_count; qtd = list_entry(qtd_item, struct dwc2_qtd, qtd_list_entry); xfer_done = 0; + qtd_desc_count = qtd->n_desc; - for (i = 0; i < qtd->n_desc; i++) { + for (i = 0; i < qtd_desc_count; i++) { if (dwc2_process_non_isoc_desc(hsotg, chan, chnum, qtd, desc_num, halt_status, &xfer_done)) { qtd = NULL; - break; + goto stop_scan; } + desc_num++; } } +stop_scan: if (qh->ep_type != USB_ENDPOINT_XFER_CONTROL) { /* * Resetting the data toggle for bulk and interrupt endpoints @@ -1248,8 +1265,8 @@ STATIC void dwc2_complete_non_isoc_xfer_ddma(struct dwc2_hsotg *hsotg, */ if (halt_status == DWC2_HC_XFER_STALL) qh->data_toggle = DWC2_HC_PID_DATA0; - else if (qtd) - dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd); + else + dwc2_hcd_save_data_toggle(hsotg, chan, chnum, NULL); } if (halt_status == DWC2_HC_XFER_COMPLETE) { @@ -1317,7 +1334,7 @@ void dwc2_hcd_complete_xfer_ddma(struct dwc2_hsotg *hsotg, dwc2_hcd_qh_unlink(hsotg, qh); } else { /* Keep in assigned schedule to continue transfer */ - list_move(&qh->qh_list_entry, + list_move_tail(&qh->qh_list_entry, &hsotg->periodic_sched_assigned); /* * If channel has been halted during giveback of urb diff --git a/sys/dev/usb/dwc2/dwc2_hcdintr.c b/sys/dev/usb/dwc2/dwc2_hcdintr.c index 871564a984b..892ec63c93d 100644 --- a/sys/dev/usb/dwc2/dwc2_hcdintr.c +++ b/sys/dev/usb/dwc2/dwc2_hcdintr.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dwc2_hcdintr.c,v 1.13 2021/09/04 10:19:28 mglocker Exp $ */ +/* $OpenBSD: dwc2_hcdintr.c,v 1.14 2022/09/04 08:42:40 mglocker Exp $ */ /* $NetBSD: dwc2_hcdintr.c,v 1.11 2014/11/24 10:14:14 skrll Exp $ */ /* @@ -63,20 +63,20 @@ * retransmission. A 1 here means delay after the first NAK. */ #define DWC2_NAKS_BEFORE_DELAY 3 -int dwc2_naks_before_delay = DWC2_NAKS_BEFORE_DELAY; - -#define DWC2_OUT_NAKS_BEFORE_DELAY 1 -int dwc2_out_naks_before_delay = DWC2_OUT_NAKS_BEFORE_DELAY; /* This function is for debug only */ STATIC void dwc2_track_missed_sofs(struct dwc2_hsotg *hsotg) { -#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS u16 curr_frame_number = hsotg->frame_number; + u16 expected = dwc2_frame_num_inc(hsotg->last_frame_num, 1); + + if (expected != curr_frame_number) + dwc2_sch_vdbg(hsotg, "MISSED SOF %04x != %04x\n", + expected, curr_frame_number); +#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS if (hsotg->frame_num_idx < FRAME_NUM_ARRAY_SIZE) { - if (((hsotg->last_frame_num + 1) & HFNUM_MAX_FRNUM) != - curr_frame_number) { + if (expected != curr_frame_number) { hsotg->frame_num_array[hsotg->frame_num_idx] = curr_frame_number; hsotg->last_frame_num_array[hsotg->frame_num_idx] = @@ -95,15 +95,16 @@ STATIC void dwc2_track_missed_sofs(struct dwc2_hsotg *hsotg) } hsotg->dumped_frame_num_array = 1; } - hsotg->last_frame_num = curr_frame_number; #endif + hsotg->last_frame_num = curr_frame_number; } STATIC void dwc2_hc_handle_tt_clear(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan, struct dwc2_qtd *qtd) { -// struct urb *usb_urb; + //struct usb_device *root_hub = dwc2_hsotg_to_hcd(hsotg)->self.root_hub; + //struct urb *usb_urb; if (!chan->qh) return; @@ -114,9 +115,27 @@ STATIC void dwc2_hc_handle_tt_clear(struct dwc2_hsotg *hsotg, if (!qtd->urb) return; +#if 0 + usb_urb = qtd->urb->priv; + if (!usb_urb || !usb_urb->dev || !usb_urb->dev->tt) + return; + + /* + * The root hub doesn't really have a TT, but Linux thinks it + * does because how could you have a "high speed hub" that + * directly talks directly to low speed devices without a TT? + * It's all lies. Lies, I tell you. + */ + if (usb_urb->dev->tt->hub == root_hub) + return; +#endif if (qtd->urb->status != -EPIPE && qtd->urb->status != -EREMOTEIO) { chan->qh->tt_buffer_dirty = 1; +#if 0 + if (usb_hub_clear_tt_buffer(usb_urb)) + /* Clear failed; let's hope things work anyway */ +#endif chan->qh->tt_buffer_dirty = 0; } } @@ -134,7 +153,7 @@ STATIC void dwc2_sof_intr(struct dwc2_hsotg *hsotg) enum dwc2_transaction_type tr_type; /* Clear interrupt */ - DWC2_WRITE_4(hsotg, GINTSTS, GINTSTS_SOF); + dwc2_writel(hsotg, GINTSTS_SOF, GINTSTS); #ifdef DEBUG_SOF dev_vdbg(hsotg->dev, "--Start of Frame Interrupt--\n"); @@ -149,13 +168,19 @@ STATIC void dwc2_sof_intr(struct dwc2_hsotg *hsotg) while (qh_entry != &hsotg->periodic_sched_inactive) { qh = list_entry(qh_entry, struct dwc2_qh, qh_list_entry); qh_entry = qh_entry->next; - if (dwc2_frame_num_le(qh->sched_frame, hsotg->frame_number)) + if (dwc2_frame_num_le(qh->next_active_frame, + hsotg->frame_number)) { + dwc2_sch_vdbg(hsotg, "QH=%p ready fn=%04x, nxt=%04x\n", + qh, hsotg->frame_number, + qh->next_active_frame); + /* * Move QH to the ready list to be executed next * (micro)frame */ - list_move(&qh->qh_list_entry, - &hsotg->periodic_sched_ready); + list_move_tail(&qh->qh_list_entry, + &hsotg->periodic_sched_ready); + } } tr_type = dwc2_hcd_select_transactions(hsotg); if (tr_type != DWC2_TRANSACTION_NONE) @@ -169,13 +194,13 @@ STATIC void dwc2_sof_intr(struct dwc2_hsotg *hsotg) */ STATIC void dwc2_rx_fifo_level_intr(struct dwc2_hsotg *hsotg) { - u32 grxsts, chnum, bcnt, pktsts; + u32 grxsts, chnum, bcnt, dpid, pktsts; struct dwc2_host_chan *chan; if (dbg_perio()) dev_vdbg(hsotg->dev, "--RxFIFO Level Interrupt--\n"); - grxsts = DWC2_READ_4(hsotg, GRXSTSP); + grxsts = dwc2_readl(hsotg, GRXSTSP); chnum = (grxsts & GRXSTS_HCHNUM_MASK) >> GRXSTS_HCHNUM_SHIFT; chan = hsotg->hc_ptr_array[chnum]; if (!chan) { @@ -184,14 +209,14 @@ STATIC void dwc2_rx_fifo_level_intr(struct dwc2_hsotg *hsotg) } bcnt = (grxsts & GRXSTS_BYTECNT_MASK) >> GRXSTS_BYTECNT_SHIFT; + dpid = (grxsts & GRXSTS_DPID_MASK) >> GRXSTS_DPID_SHIFT; pktsts = (grxsts & GRXSTS_PKTSTS_MASK) >> GRXSTS_PKTSTS_SHIFT; /* Packet Status */ if (dbg_perio()) { dev_vdbg(hsotg->dev, " Ch num = %d\n", chnum); dev_vdbg(hsotg->dev, " Count = %d\n", bcnt); - dev_vdbg(hsotg->dev, " DPID = %d, chan.dpid = %d\n", - (grxsts & GRXSTS_DPID_MASK) >> GRXSTS_DPID_SHIFT, + dev_vdbg(hsotg->dev, " DPID = %d, chan.dpid = %d\n", dpid, chan->data_pid_start); dev_vdbg(hsotg->dev, " PStatus = %d\n", pktsts); } @@ -247,7 +272,7 @@ STATIC void dwc2_perio_tx_fifo_empty_intr(struct dwc2_hsotg *hsotg) STATIC void dwc2_hprt0_enable(struct dwc2_hsotg *hsotg, u32 hprt0, u32 *hprt0_modify) { - struct dwc2_core_params *params = hsotg->core_params; + struct dwc2_core_params *params = &hsotg->params; int do_reset = 0; u32 usbcfg; u32 prtspd; @@ -258,22 +283,22 @@ STATIC void dwc2_hprt0_enable(struct dwc2_hsotg *hsotg, u32 hprt0, dev_vdbg(hsotg->dev, "%s(%p)\n", __func__, hsotg); /* Every time when port enables calculate HFIR.FrInterval */ - hfir = DWC2_READ_4(hsotg, HFIR); + hfir = dwc2_readl(hsotg, HFIR); hfir &= ~HFIR_FRINT_MASK; hfir |= dwc2_calc_frame_interval(hsotg) << HFIR_FRINT_SHIFT & HFIR_FRINT_MASK; - DWC2_WRITE_4(hsotg, HFIR, hfir); + dwc2_writel(hsotg, hfir, HFIR); /* Check if we need to adjust the PHY clock speed for low power */ if (!params->host_support_fs_ls_low_power) { /* Port has been enabled, set the reset change flag */ hsotg->flags.b.port_reset_change = 1; - dwc2_root_intr(hsotg->hsotg_sc); + dwc2_root_intr(hsotg->hsotg_sc); /* Required for OpenBSD */ return; } - usbcfg = DWC2_READ_4(hsotg, GUSBCFG); + usbcfg = dwc2_readl(hsotg, GUSBCFG); prtspd = (hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT; if (prtspd == HPRT0_SPD_LOW_SPEED || prtspd == HPRT0_SPD_FULL_SPEED) { @@ -281,17 +306,16 @@ STATIC void dwc2_hprt0_enable(struct dwc2_hsotg *hsotg, u32 hprt0, if (!(usbcfg & GUSBCFG_PHY_LP_CLK_SEL)) { /* Set PHY low power clock select for FS/LS devices */ usbcfg |= GUSBCFG_PHY_LP_CLK_SEL; - DWC2_WRITE_4(hsotg, GUSBCFG, usbcfg); + dwc2_writel(hsotg, usbcfg, GUSBCFG); do_reset = 1; } - hcfg = DWC2_READ_4(hsotg, HCFG); + hcfg = dwc2_readl(hsotg, HCFG); fslspclksel = (hcfg & HCFG_FSLSPCLKSEL_MASK) >> HCFG_FSLSPCLKSEL_SHIFT; if (prtspd == HPRT0_SPD_LOW_SPEED && - params->host_ls_low_power_phy_clk == - DWC2_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ) { + params->host_ls_low_power_phy_clk) { /* 6 MHZ */ dev_vdbg(hsotg->dev, "FS_PHY programming HCFG to 6 MHz\n"); @@ -299,7 +323,7 @@ STATIC void dwc2_hprt0_enable(struct dwc2_hsotg *hsotg, u32 hprt0, fslspclksel = HCFG_FSLSPCLKSEL_6_MHZ; hcfg &= ~HCFG_FSLSPCLKSEL_MASK; hcfg |= fslspclksel << HCFG_FSLSPCLKSEL_SHIFT; - DWC2_WRITE_4(hsotg, HCFG, hcfg); + dwc2_writel(hsotg, hcfg, HCFG); do_reset = 1; } } else { @@ -310,7 +334,7 @@ STATIC void dwc2_hprt0_enable(struct dwc2_hsotg *hsotg, u32 hprt0, fslspclksel = HCFG_FSLSPCLKSEL_48_MHZ; hcfg &= ~HCFG_FSLSPCLKSEL_MASK; hcfg |= fslspclksel << HCFG_FSLSPCLKSEL_SHIFT; - DWC2_WRITE_4(hsotg, HCFG, hcfg); + dwc2_writel(hsotg, hcfg, HCFG); do_reset = 1; } } @@ -318,21 +342,20 @@ STATIC void dwc2_hprt0_enable(struct dwc2_hsotg *hsotg, u32 hprt0, /* Not low power */ if (usbcfg & GUSBCFG_PHY_LP_CLK_SEL) { usbcfg &= ~GUSBCFG_PHY_LP_CLK_SEL; - DWC2_WRITE_4(hsotg, GUSBCFG, usbcfg); + dwc2_writel(hsotg, usbcfg, GUSBCFG); do_reset = 1; } } if (do_reset) { *hprt0_modify |= HPRT0_RST; - DWC2_WRITE_4(hsotg, HPRT0, *hprt0_modify); + dwc2_writel(hsotg, *hprt0_modify, HPRT0); queue_delayed_work(hsotg->wq_otg, &hsotg->reset_work, msecs_to_jiffies(60)); } else { /* Port has been enabled, set the reset change flag */ hsotg->flags.b.port_reset_change = 1; - dwc2_root_intr(hsotg->hsotg_sc); - + dwc2_root_intr(hsotg->hsotg_sc); /* Required for OpenBSD */ } } @@ -348,7 +371,7 @@ STATIC void dwc2_port_intr(struct dwc2_hsotg *hsotg) dev_vdbg(hsotg->dev, "--Port Interrupt--\n"); - hprt0 = DWC2_READ_4(hsotg, HPRT0); + hprt0 = dwc2_readl(hsotg, HPRT0); hprt0_modify = hprt0; /* @@ -363,7 +386,7 @@ STATIC void dwc2_port_intr(struct dwc2_hsotg *hsotg) * Set flag and clear if detected */ if (hprt0 & HPRT0_CONNDET) { - DWC2_WRITE_4(hsotg, HPRT0, hprt0_modify | HPRT0_CONNDET); + dwc2_writel(hsotg, hprt0_modify | HPRT0_CONNDET, HPRT0); dev_vdbg(hsotg->dev, "--Port Interrupt HPRT0=0x%08x Port Connect Detected--\n", @@ -381,7 +404,7 @@ STATIC void dwc2_port_intr(struct dwc2_hsotg *hsotg) * Clear if detected - Set internal flag if disabled */ if (hprt0 & HPRT0_ENACHG) { - DWC2_WRITE_4(hsotg, HPRT0, hprt0_modify | HPRT0_ENACHG); + dwc2_writel(hsotg, hprt0_modify | HPRT0_ENACHG, HPRT0); dev_vdbg(hsotg->dev, " --Port Interrupt HPRT0=0x%08x Port Enable Changed (now %d)--\n", hprt0, !!(hprt0 & HPRT0_ENA)); @@ -390,27 +413,29 @@ STATIC void dwc2_port_intr(struct dwc2_hsotg *hsotg) dwc2_hprt0_enable(hsotg, hprt0, &hprt0_modify); } else { hsotg->flags.b.port_enable_change = 1; - if (hsotg->core_params->dma_desc_fs_enable) { + if (hsotg->params.dma_desc_fs_enable) { u32 hcfg; - hsotg->core_params->dma_desc_enable = 0; + hsotg->params.dma_desc_enable = false; hsotg->new_connection = false; - hcfg = DWC2_READ_4(hsotg, HCFG); + hcfg = dwc2_readl(hsotg, HCFG); hcfg &= ~HCFG_DESCDMA; - DWC2_WRITE_4(hsotg, HCFG, hcfg); + dwc2_writel(hsotg, hcfg, HCFG); } } } /* Overcurrent Change Interrupt */ if (hprt0 & HPRT0_OVRCURRCHG) { - DWC2_WRITE_4(hsotg, HPRT0, hprt0_modify | HPRT0_OVRCURRCHG); + dwc2_writel(hsotg, hprt0_modify | HPRT0_OVRCURRCHG, + HPRT0); dev_vdbg(hsotg->dev, " --Port Interrupt HPRT0=0x%08x Port Overcurrent Changed--\n", hprt0); hsotg->flags.b.port_over_current_change = 1; } + /* Required for OpenBSD */ if (hsotg->flags.b.port_connect_status_change || hsotg->flags.b.port_enable_change || hsotg->flags.b.port_over_current_change) @@ -434,14 +459,14 @@ STATIC u32 dwc2_get_actual_xfer_length(struct dwc2_hsotg *hsotg, { u32 hctsiz, count, length; - hctsiz = DWC2_READ_4(hsotg, HCTSIZ(chnum)); + hctsiz = dwc2_readl(hsotg, HCTSIZ(chnum)); if (halt_status == DWC2_HC_XFER_COMPLETE) { if (chan->ep_is_in) { count = (hctsiz & TSIZ_XFERSIZE_MASK) >> TSIZ_XFERSIZE_SHIFT; length = chan->xfer_len - count; - if (short_read != NULL) + if (short_read) *short_read = (count != 0); } else if (chan->qh->do_split) { length = qtd->ssplit_out_xfer_count; @@ -471,6 +496,12 @@ STATIC u32 dwc2_get_actual_xfer_length(struct dwc2_hsotg *hsotg, * of the URB based on the number of bytes transferred via the host channel. * Sets the URB status if the data transfer is finished. * + * @hsotg: Programming view of the DWC_otg controller + * @chan: Programming view of host channel + * @chnum: Channel number + * @urb: Processing URB + * @qtd: Queue transfer descriptor + * * Return: 1 if the data transfer specified by the URB is completely finished, * 0 otherwise */ @@ -479,6 +510,7 @@ STATIC int dwc2_update_urb_state(struct dwc2_hsotg *hsotg, struct dwc2_hcd_urb *urb, struct dwc2_qtd *qtd) { + u32 hctsiz; int xfer_done = 0; int short_read = 0; int xfer_length = dwc2_get_actual_xfer_length(hsotg, chan, chnum, qtd, @@ -486,24 +518,10 @@ STATIC int dwc2_update_urb_state(struct dwc2_hsotg *hsotg, &short_read); if (urb->actual_length + xfer_length > urb->length) { - dev_warn(hsotg->dev, "%s(): trimming xfer length\n", __func__); + dev_dbg(hsotg->dev, "%s(): trimming xfer length\n", __func__); xfer_length = urb->length - urb->actual_length; } - /* Non DWORD-aligned buffer case handling */ - if (chan->align_buf && xfer_length) { - dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__); - usb_syncmem(urb->usbdma, 0, chan->qh->dw_align_buf_size, - chan->ep_is_in ? - BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); - if (chan->ep_is_in) - memcpy(urb->buf + urb->actual_length, - chan->qh->dw_align_buf, xfer_length); - usb_syncmem(urb->usbdma, 0, chan->qh->dw_align_buf_size, - chan->ep_is_in ? - BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE); - } - dev_vdbg(hsotg->dev, "urb->actual_length=%d xfer_length=%d\n", urb->actual_length, xfer_length); urb->actual_length += xfer_length; @@ -518,11 +536,12 @@ STATIC int dwc2_update_urb_state(struct dwc2_hsotg *hsotg, urb->status = 0; } + hctsiz = dwc2_readl(hsotg, HCTSIZ(chnum)); dev_vdbg(hsotg->dev, "DWC_otg: %s: %s, channel %d\n", __func__, (chan->ep_is_in ? "IN" : "OUT"), chnum); dev_vdbg(hsotg->dev, " chan->xfer_len %d\n", chan->xfer_len); dev_vdbg(hsotg->dev, " hctsiz.xfersize %d\n", - (DWC2_READ_4(hsotg, HCTSIZ(chnum)) & TSIZ_XFERSIZE_MASK) >> TSIZ_XFERSIZE_SHIFT); + (hctsiz & TSIZ_XFERSIZE_MASK) >> TSIZ_XFERSIZE_SHIFT); dev_vdbg(hsotg->dev, " urb->transfer_buffer_length %d\n", urb->length); dev_vdbg(hsotg->dev, " urb->actual_length %d\n", urb->actual_length); dev_vdbg(hsotg->dev, " short_read %d, xfer_done %d\n", short_read, @@ -540,15 +559,27 @@ void dwc2_hcd_save_data_toggle(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan, int chnum, struct dwc2_qtd *qtd) { - u32 hctsiz = DWC2_READ_4(hsotg, HCTSIZ(chnum)); + u32 hctsiz = dwc2_readl(hsotg, HCTSIZ(chnum)); u32 pid = (hctsiz & TSIZ_SC_MC_PID_MASK) >> TSIZ_SC_MC_PID_SHIFT; if (chan->ep_type != USB_ENDPOINT_XFER_CONTROL) { +#if 0 + if (WARN(!chan || !chan->qh, + "chan->qh must be specified for non-control eps\n")) + return; +#endif + if (pid == TSIZ_SC_MC_PID_DATA0) chan->qh->data_toggle = DWC2_HC_PID_DATA0; else chan->qh->data_toggle = DWC2_HC_PID_DATA1; } else { +#if 0 + if (WARN(!qtd, + "qtd must be specified for control eps\n")) + return; +#endif + if (pid == TSIZ_SC_MC_PID_DATA0) qtd->data_toggle = DWC2_HC_PID_DATA0; else @@ -563,6 +594,12 @@ void dwc2_hcd_save_data_toggle(struct dwc2_hsotg *hsotg, * halt_status. Completes the Isochronous URB if all the URB frames have been * completed. * + * @hsotg: Programming view of the DWC_otg controller + * @chan: Programming view of host channel + * @chnum: Channel number + * @halt_status: Reason for halting a host channel + * @qtd: Queue transfer descriptor + * * Return: DWC2_HC_XFER_COMPLETE if there are more frames remaining to be * transferred in the URB. Otherwise return DWC2_HC_XFER_URB_COMPLETE. */ @@ -584,25 +621,6 @@ STATIC enum dwc2_halt_status dwc2_update_isoc_urb_state( frame_desc->status = 0; frame_desc->actual_length = dwc2_get_actual_xfer_length(hsotg, chan, chnum, qtd, halt_status, NULL); - - /* Non DWORD-aligned buffer case handling */ - if (chan->align_buf && frame_desc->actual_length) { - dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", - __func__); - struct usb_dma *ud = &chan->qh->dw_align_buf_usbdma; - - usb_syncmem(ud, 0, chan->qh->dw_align_buf_size, - chan->ep_is_in ? - BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); - if (chan->ep_is_in) - memcpy(urb->buf + frame_desc->offset + - qtd->isoc_split_offset, - chan->qh->dw_align_buf, - frame_desc->actual_length); - usb_syncmem(ud, 0, chan->qh->dw_align_buf_size, - chan->ep_is_in ? - BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE); - } break; case DWC2_HC_XFER_FRAME_OVERRUN: urb->error_count++; @@ -623,29 +641,10 @@ STATIC enum dwc2_halt_status dwc2_update_isoc_urb_state( frame_desc->actual_length = dwc2_get_actual_xfer_length(hsotg, chan, chnum, qtd, halt_status, NULL); - /* Non DWORD-aligned buffer case handling */ - if (chan->align_buf && frame_desc->actual_length) { - dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", - __func__); - struct usb_dma *ud = &chan->qh->dw_align_buf_usbdma; - - usb_syncmem(ud, 0, chan->qh->dw_align_buf_size, - chan->ep_is_in ? - BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); - if (chan->ep_is_in) - memcpy(urb->buf + frame_desc->offset + - qtd->isoc_split_offset, - chan->qh->dw_align_buf, - frame_desc->actual_length); - usb_syncmem(ud, 0, chan->qh->dw_align_buf_size, - chan->ep_is_in ? - BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE); - } - /* Skip whole frame */ if (chan->qh->do_split && chan->ep_type == USB_ENDPOINT_XFER_ISOC && chan->ep_is_in && - hsotg->core_params->dma_enable > 0) { + hsotg->params.host_dma) { qtd->complete_split = 0; qtd->isoc_split_offset = 0; } @@ -707,8 +706,6 @@ STATIC void dwc2_deactivate_qh(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, } no_qtd: - if (qh->channel) - qh->channel->align_buf = 0; qh->channel = NULL; dwc2_hcd_qh_deactivate(hsotg, qh, continue_split); } @@ -786,7 +783,7 @@ cleanup: dwc2_hc_cleanup(hsotg, chan); list_add_tail(&chan->hc_list_entry, &hsotg->free_hc_list); - if (hsotg->core_params->uframe_sched > 0) { + if (hsotg->params.uframe_sched) { hsotg->available_host_channels++; } else { switch (chan->ep_type) { @@ -805,9 +802,9 @@ cleanup: } } - haintmsk = DWC2_READ_4(hsotg, HAINTMSK); + haintmsk = dwc2_readl(hsotg, HAINTMSK); haintmsk &= ~(1 << chan->hc_num); - DWC2_WRITE_4(hsotg, HAINTMSK, haintmsk); + dwc2_writel(hsotg, haintmsk, HAINTMSK); /* Try to queue more transfers now that there's a free channel */ tr_type = dwc2_hcd_select_transactions(hsotg); @@ -832,7 +829,7 @@ STATIC void dwc2_halt_channel(struct dwc2_hsotg *hsotg, if (dbg_hc(chan)) dev_vdbg(hsotg->dev, "%s()\n", __func__); - if (hsotg->core_params->dma_enable > 0) { + if (hsotg->params.host_dma) { if (dbg_hc(chan)) dev_vdbg(hsotg->dev, "DMA enabled\n"); dwc2_release_channel(hsotg, chan, qtd, halt_status); @@ -854,9 +851,9 @@ STATIC void dwc2_halt_channel(struct dwc2_hsotg *hsotg, * is enabled so that the non-periodic schedule will * be processed */ - gintmsk = DWC2_READ_4(hsotg, GINTMSK); + gintmsk = dwc2_readl(hsotg, GINTMSK); gintmsk |= GINTSTS_NPTXFEMP; - DWC2_WRITE_4(hsotg, GINTMSK, gintmsk); + dwc2_writel(hsotg, gintmsk, GINTMSK); } else { dev_vdbg(hsotg->dev, "isoc/intr\n"); /* @@ -865,17 +862,17 @@ STATIC void dwc2_halt_channel(struct dwc2_hsotg *hsotg, * halt to be queued when the periodic schedule is * processed. */ - list_move(&chan->qh->qh_list_entry, - &hsotg->periodic_sched_assigned); + list_move_tail(&chan->qh->qh_list_entry, + &hsotg->periodic_sched_assigned); /* * Make sure the Periodic Tx FIFO Empty interrupt is * enabled so that the periodic schedule will be * processed */ - gintmsk = DWC2_READ_4(hsotg, GINTMSK); + gintmsk = dwc2_readl(hsotg, GINTMSK); gintmsk |= GINTSTS_PTXFEMP; - DWC2_WRITE_4(hsotg, GINTMSK, gintmsk); + dwc2_writel(hsotg, gintmsk, GINTMSK); } } } @@ -940,7 +937,7 @@ STATIC void dwc2_complete_periodic_xfer(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, enum dwc2_halt_status halt_status) { - u32 hctsiz = DWC2_READ_4(hsotg, HCTSIZ(chnum)); + u32 hctsiz = dwc2_readl(hsotg, HCTSIZ(chnum)); qtd->error_count = 0; @@ -958,6 +955,8 @@ STATIC int dwc2_xfercomp_isoc_split_in(struct dwc2_hsotg *hsotg, { struct dwc2_hcd_iso_packet_desc *frame_desc; u32 len; + u32 hctsiz; + u32 pid; if (!qtd->urb) return 0; @@ -965,16 +964,15 @@ STATIC int dwc2_xfercomp_isoc_split_in(struct dwc2_hsotg *hsotg, frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index]; len = dwc2_get_actual_xfer_length(hsotg, chan, chnum, qtd, DWC2_HC_XFER_COMPLETE, NULL); - if (!len) { + if (!len && !qtd->isoc_split_offset) { qtd->complete_split = 0; - qtd->isoc_split_offset = 0; return 0; } frame_desc->actual_length += len; if (chan->align_buf) { - dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__); + dev_vdbg(hsotg->dev, "non-aligned buffer\n"); usb_syncmem(qtd->urb->usbdma, chan->qh->dw_align_buf_dma, chan->qh->dw_align_buf_size, BUS_DMASYNC_POSTREAD); memcpy(qtd->urb->buf + frame_desc->offset + @@ -985,7 +983,10 @@ STATIC int dwc2_xfercomp_isoc_split_in(struct dwc2_hsotg *hsotg, qtd->isoc_split_offset += len; - if (frame_desc->actual_length >= frame_desc->length) { + hctsiz = dwc2_readl(hsotg, HCTSIZ(chnum)); + pid = (hctsiz & TSIZ_SC_MC_PID_MASK) >> TSIZ_SC_MC_PID_SHIFT; + + if (frame_desc->actual_length >= frame_desc->length || pid == 0) { frame_desc->status = 0; qtd->isoc_frame_index++; qtd->complete_split = 0; @@ -1027,7 +1028,7 @@ STATIC void dwc2_hc_xfercomp_intr(struct dwc2_hsotg *hsotg, pipe_type = dwc2_hcd_get_pipe_type(&urb->pipe_info); - if (hsotg->core_params->dma_desc_enable > 0) { + if (hsotg->params.dma_desc_enable) { dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, halt_status); if (pipe_type == USB_ENDPOINT_XFER_ISOC) /* Do not disable the interrupt, just clear it */ @@ -1038,7 +1039,7 @@ STATIC void dwc2_hc_xfercomp_intr(struct dwc2_hsotg *hsotg, /* Handle xfer complete on CSPLIT */ if (chan->qh->do_split) { if (chan->ep_type == USB_ENDPOINT_XFER_ISOC && chan->ep_is_in && - hsotg->core_params->dma_enable > 0) { + hsotg->params.host_dma) { if (qtd->complete_split && dwc2_xfercomp_isoc_split_in(hsotg, chan, chnum, qtd)) @@ -1126,7 +1127,8 @@ STATIC void dwc2_hc_xfercomp_intr(struct dwc2_hsotg *hsotg, dev_vdbg(hsotg->dev, " Isochronous transfer complete\n"); if (qtd->isoc_split_pos == DWC2_HCSPLT_XACTPOS_ALL) halt_status = dwc2_update_isoc_urb_state(hsotg, chan, - chnum, qtd, DWC2_HC_XFER_COMPLETE); + chnum, qtd, + DWC2_HC_XFER_COMPLETE); dwc2_complete_periodic_xfer(hsotg, chan, chnum, qtd, halt_status); break; @@ -1150,7 +1152,7 @@ STATIC void dwc2_hc_stall_intr(struct dwc2_hsotg *hsotg, dev_dbg(hsotg->dev, "--Host Channel %d Interrupt: STALL Received--\n", chnum); - if (hsotg->core_params->dma_desc_enable > 0) { + if (hsotg->params.dma_desc_enable) { dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, DWC2_HC_XFER_STALL); goto handle_stall_done; @@ -1198,38 +1200,22 @@ STATIC void dwc2_update_urb_state_abn(struct dwc2_hsotg *hsotg, { u32 xfer_length = dwc2_get_actual_xfer_length(hsotg, chan, chnum, qtd, halt_status, NULL); + u32 hctsiz; if (urb->actual_length + xfer_length > urb->length) { dev_warn(hsotg->dev, "%s(): trimming xfer length\n", __func__); xfer_length = urb->length - urb->actual_length; } - /* Non DWORD-aligned buffer case handling */ - if (chan->align_buf && xfer_length && chan->ep_is_in) { - dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__); - - struct usb_dma *ud = &chan->qh->dw_align_buf_usbdma; - - usb_syncmem(ud, 0, chan->qh->dw_align_buf_size, - chan->ep_is_in ? - BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); - if (chan->ep_is_in) - memcpy(urb->buf + urb->actual_length, - chan->qh->dw_align_buf, - xfer_length); - usb_syncmem(ud, 0, chan->qh->dw_align_buf_size, - chan->ep_is_in ? - BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE); - } - urb->actual_length += xfer_length; + hctsiz = dwc2_readl(hsotg, HCTSIZ(chnum)); dev_vdbg(hsotg->dev, "DWC_otg: %s: %s, channel %d\n", __func__, (chan->ep_is_in ? "IN" : "OUT"), chnum); dev_vdbg(hsotg->dev, " chan->start_pkt_count %d\n", chan->start_pkt_count); dev_vdbg(hsotg->dev, " hctsiz.pktcnt %d\n", - (DWC2_READ_4(hsotg, HCTSIZ(chnum)) & TSIZ_PKTCNT_MASK) >> TSIZ_PKTCNT_SHIFT); + (hctsiz & TSIZ_PKTCNT_MASK) >> TSIZ_PKTCNT_SHIFT); dev_vdbg(hsotg->dev, " chan->max_packet %d\n", chan->max_packet); dev_vdbg(hsotg->dev, " bytes_transferred %d\n", xfer_length); @@ -1295,7 +1281,7 @@ STATIC void dwc2_hc_nak_intr(struct dwc2_hsotg *hsotg, switch (dwc2_hcd_get_pipe_type(&qtd->urb->pipe_info)) { case USB_ENDPOINT_XFER_CONTROL: case USB_ENDPOINT_XFER_BULK: - if (hsotg->core_params->dma_enable > 0 && chan->ep_is_in) { + if (hsotg->params.host_dma && chan->ep_is_in) { /* * NAK interrupts are enabled on bulk/control IN * transfers in DMA mode for the sole purpose of @@ -1441,7 +1427,7 @@ STATIC void dwc2_hc_nyet_intr(struct dwc2_hsotg *hsotg, */ if (chan->do_split && chan->complete_split) { if (chan->ep_is_in && chan->ep_type == USB_ENDPOINT_XFER_ISOC && - hsotg->core_params->dma_enable > 0) { + hsotg->params.host_dma) { qtd->complete_split = 0; qtd->isoc_split_offset = 0; qtd->isoc_frame_index++; @@ -1459,14 +1445,55 @@ STATIC void dwc2_hc_nyet_intr(struct dwc2_hsotg *hsotg, if (chan->ep_type == USB_ENDPOINT_XFER_INT || chan->ep_type == USB_ENDPOINT_XFER_ISOC) { - int frnum = dwc2_hcd_get_frame_number(hsotg); + struct dwc2_qh *qh = chan->qh; + bool past_end; + + if (!hsotg->params.uframe_sched) { + int frnum = dwc2_hcd_get_frame_number(hsotg); + + /* Don't have num_hs_transfers; simple logic */ + past_end = dwc2_full_frame_num(frnum) != + dwc2_full_frame_num(qh->next_active_frame); + } else { + int end_frnum; - if (dwc2_full_frame_num(frnum) != - dwc2_full_frame_num(chan->qh->sched_frame)) { /* - * No longer in the same full speed frame. - * Treat this as a transaction error. + * Figure out the end frame based on + * schedule. + * + * We don't want to go on trying again + * and again forever. Let's stop when + * we've done all the transfers that + * were scheduled. + * + * We're going to be comparing + * start_active_frame and + * next_active_frame, both of which + * are 1 before the time the packet + * goes on the wire, so that cancels + * out. Basically if had 1 transfer + * and we saw 1 NYET then we're done. + * We're getting a NYET here so if + * next >= (start + num_transfers) + * we're done. The complexity is that + * for all but ISOC_OUT we skip one + * slot. */ + end_frnum = dwc2_frame_num_inc( + qh->start_active_frame, + qh->num_hs_transfers); + + if (qh->ep_type != USB_ENDPOINT_XFER_ISOC || + qh->ep_is_in) + end_frnum = + dwc2_frame_num_inc(end_frnum, 1); + + past_end = dwc2_frame_num_le( + end_frnum, qh->next_active_frame); + } + + if (past_end) { + /* Treat this as a transaction error. */ #if 0 /* * Todo: Fix system performance so this can @@ -1517,9 +1544,9 @@ STATIC void dwc2_hc_babble_intr(struct dwc2_hsotg *hsotg, dev_dbg(hsotg->dev, "--Host Channel %d Interrupt: Babble Error--\n", chnum); -// dwc2_hc_handle_tt_clear(hsotg, chan, qtd); + dwc2_hc_handle_tt_clear(hsotg, chan, qtd); - if (hsotg->core_params->dma_desc_enable > 0) { + if (hsotg->params.dma_desc_enable) { dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, DWC2_HC_XFER_BABBLE_ERR); goto disable_int; @@ -1549,6 +1576,11 @@ STATIC void dwc2_hc_ahberr_intr(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd) { struct dwc2_hcd_urb *urb = qtd->urb; + char *pipetype, *speed; + u32 hcchar; + u32 hcsplt; + u32 hctsiz; + u32 hc_dma; dev_dbg(hsotg->dev, "--Host Channel %d Interrupt: AHB Error--\n", chnum); @@ -1556,15 +1588,12 @@ STATIC void dwc2_hc_ahberr_intr(struct dwc2_hsotg *hsotg, if (!urb) goto handle_ahberr_halt; -// dwc2_hc_handle_tt_clear(hsotg, chan, qtd); - -#ifdef DWC2_DEBUG - const char *pipetype, *speed; + dwc2_hc_handle_tt_clear(hsotg, chan, qtd); - u32 hcchar = DWC2_READ_4(hsotg, HCCHAR(chnum)); - u32 hcsplt = DWC2_READ_4(hsotg, HCSPLT(chnum)); - u32 hctsiz = DWC2_READ_4(hsotg, HCTSIZ(chnum)); - u32 hc_dma = DWC2_READ_4(hsotg, HCDMA(chnum)); + hcchar = dwc2_readl(hsotg, HCCHAR(chnum)); + hcsplt = dwc2_readl(hsotg, HCSPLT(chnum)); + hctsiz = dwc2_readl(hsotg, HCTSIZ(chnum)); + hc_dma = dwc2_readl(hsotg, HCDMA(chnum)); dev_err(hsotg->dev, "AHB ERROR, Channel %d\n", chnum); dev_err(hsotg->dev, " hcchar 0x%08x, hcsplt 0x%08x\n", hcchar, hcsplt); @@ -1612,18 +1641,18 @@ STATIC void dwc2_hc_ahberr_intr(struct dwc2_hsotg *hsotg, dev_err(hsotg->dev, " Speed: %s\n", speed); - dev_err(hsotg->dev, " Max packet size: %d\n", - dwc2_hcd_get_mps(&urb->pipe_info)); + dev_err(hsotg->dev, " Max packet size: %d (mult %d)\n", + dwc2_hcd_get_maxp(&urb->pipe_info), + dwc2_hcd_get_maxp_mult(&urb->pipe_info)); dev_err(hsotg->dev, " Data buffer length: %d\n", urb->length); dev_err(hsotg->dev, " Transfer buffer: %p, Transfer DMA: %08lx\n", urb->buf, (unsigned long)urb->dma); dev_err(hsotg->dev, " Setup buffer: %p, Setup DMA: %08lx\n", urb->setup_packet, (unsigned long)urb->setup_dma); dev_err(hsotg->dev, " Interval: %d\n", urb->interval); -#endif /* Core halts the channel for Descriptor DMA mode */ - if (hsotg->core_params->dma_desc_enable > 0) { + if (hsotg->params.dma_desc_enable) { dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, DWC2_HC_XFER_AHB_ERR); goto handle_ahberr_done; @@ -1653,9 +1682,9 @@ STATIC void dwc2_hc_xacterr_intr(struct dwc2_hsotg *hsotg, dev_dbg(hsotg->dev, "--Host Channel %d Interrupt: Transaction Error--\n", chnum); -// dwc2_hc_handle_tt_clear(hsotg, chan, qtd); + dwc2_hc_handle_tt_clear(hsotg, chan, qtd); - if (hsotg->core_params->dma_desc_enable > 0) { + if (hsotg->params.dma_desc_enable) { dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, DWC2_HC_XFER_XACT_ERR); goto handle_xacterr_done; @@ -1666,7 +1695,6 @@ STATIC void dwc2_hc_xacterr_intr(struct dwc2_hsotg *hsotg, case USB_ENDPOINT_XFER_BULK: qtd->error_count++; if (!chan->qh->ping_state) { - dwc2_update_urb_state_abn(hsotg, chan, chnum, qtd->urb, qtd, DWC2_HC_XFER_XACT_ERR); dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd); @@ -1691,7 +1719,7 @@ STATIC void dwc2_hc_xacterr_intr(struct dwc2_hsotg *hsotg, enum dwc2_halt_status halt_status; halt_status = dwc2_update_isoc_urb_state(hsotg, chan, - chnum, qtd, DWC2_HC_XFER_XACT_ERR); + chnum, qtd, DWC2_HC_XFER_XACT_ERR); dwc2_halt_channel(hsotg, chan, qtd, halt_status); } break; @@ -1752,7 +1780,7 @@ STATIC void dwc2_hc_datatglerr_intr(struct dwc2_hsotg *hsotg, "Data Toggle Error on OUT transfer, channel %d\n", chnum); -// dwc2_hc_handle_tt_clear(hsotg, chan, qtd); + dwc2_hc_handle_tt_clear(hsotg, chan, qtd); disable_hc_int(hsotg, chnum, HCINTMSK_DATATGLERR); } @@ -1778,10 +1806,10 @@ STATIC bool dwc2_halt_status_ok(struct dwc2_hsotg *hsotg, * This code is here only as a check. This condition should * never happen. Ignore the halt if it does occur. */ - hcchar = DWC2_READ_4(hsotg, HCCHAR(chnum)); - hctsiz = DWC2_READ_4(hsotg, HCTSIZ(chnum)); - hcintmsk = DWC2_READ_4(hsotg, HCINTMSK(chnum)); - hcsplt = DWC2_READ_4(hsotg, HCSPLT(chnum)); + hcchar = dwc2_readl(hsotg, HCCHAR(chnum)); + hctsiz = dwc2_readl(hsotg, HCTSIZ(chnum)); + hcintmsk = dwc2_readl(hsotg, HCINTMSK(chnum)); + hcsplt = dwc2_readl(hsotg, HCSPLT(chnum)); dev_dbg(hsotg->dev, "%s: chan->halt_status DWC2_HC_XFER_NO_HALT_STATUS,\n", __func__); @@ -1805,7 +1833,7 @@ STATIC bool dwc2_halt_status_ok(struct dwc2_hsotg *hsotg, * when the halt interrupt occurs. Halt the channel again if it does * occur. */ - hcchar = DWC2_READ_4(hsotg, HCCHAR(chnum)); + hcchar = dwc2_readl(hsotg, HCCHAR(chnum)); if (hcchar & HCCHAR_CHDIS) { dev_warn(hsotg->dev, "%s: hcchar.chdis set unexpectedly, hcchar 0x%08x, trying to halt again\n", @@ -1849,8 +1877,8 @@ STATIC void dwc2_hc_chhltd_intr_dma(struct dwc2_hsotg *hsotg, if (chan->halt_status == DWC2_HC_XFER_URB_DEQUEUE || (chan->halt_status == DWC2_HC_XFER_AHB_ERR && - hsotg->core_params->dma_desc_enable <= 0)) { - if (hsotg->core_params->dma_desc_enable > 0) + !hsotg->params.dma_desc_enable)) { + if (hsotg->params.dma_desc_enable) dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, chan->halt_status); else @@ -1865,7 +1893,7 @@ STATIC void dwc2_hc_chhltd_intr_dma(struct dwc2_hsotg *hsotg, return; } - hcintmsk = DWC2_READ_4(hsotg, HCINTMSK(chnum)); + hcintmsk = dwc2_readl(hsotg, HCINTMSK(chnum)); if (chan->hcint & HCINTMSK_XFERCOMPL) { /* @@ -1881,7 +1909,7 @@ STATIC void dwc2_hc_chhltd_intr_dma(struct dwc2_hsotg *hsotg, } else if (chan->hcint & HCINTMSK_STALL) { dwc2_hc_stall_intr(hsotg, chan, chnum, qtd); } else if ((chan->hcint & HCINTMSK_XACTERR) && - hsotg->core_params->dma_desc_enable <= 0) { + !hsotg->params.dma_desc_enable) { if (out_nak_enh) { if (chan->hcint & (HCINTMSK_NYET | HCINTMSK_NAK | HCINTMSK_ACK)) { @@ -1901,10 +1929,10 @@ STATIC void dwc2_hc_chhltd_intr_dma(struct dwc2_hsotg *hsotg, */ dwc2_hc_xacterr_intr(hsotg, chan, chnum, qtd); } else if ((chan->hcint & HCINTMSK_XCS_XACT) && - hsotg->core_params->dma_desc_enable > 0) { + hsotg->params.dma_desc_enable) { dwc2_hc_xacterr_intr(hsotg, chan, chnum, qtd); } else if ((chan->hcint & HCINTMSK_AHBERR) && - hsotg->core_params->dma_desc_enable > 0) { + hsotg->params.dma_desc_enable) { dwc2_hc_ahberr_intr(hsotg, chan, chnum, qtd); } else if (chan->hcint & HCINTMSK_BBLERR) { dwc2_hc_babble_intr(hsotg, chan, chnum, qtd); @@ -1960,7 +1988,7 @@ STATIC void dwc2_hc_chhltd_intr_dma(struct dwc2_hsotg *hsotg, dev_err(hsotg->dev, "hcint 0x%08x, intsts 0x%08x\n", chan->hcint, - DWC2_READ_4(hsotg, GINTSTS)); + dwc2_readl(hsotg, GINTSTS)); goto error; } } @@ -2009,7 +2037,7 @@ STATIC void dwc2_hc_chhltd_intr(struct dwc2_hsotg *hsotg, dev_vdbg(hsotg->dev, "--Host Channel %d Interrupt: Channel Halted--\n", chnum); - if (hsotg->core_params->dma_enable > 0) { + if (hsotg->params.host_dma) { dwc2_hc_chhltd_intr_dma(hsotg, chan, chnum, qtd); } else { if (!dwc2_halt_status_ok(hsotg, chan, chnum, qtd)) @@ -2028,7 +2056,7 @@ STATIC bool dwc2_check_qtd_still_ok(struct dwc2_qtd *qtd, struct dwc2_qh *qh) { struct dwc2_qtd *cur_head; - if (qh == NULL) + if (!qh) return false; cur_head = list_first_entry(&qh->qtd_list, struct dwc2_qtd, @@ -2045,11 +2073,11 @@ STATIC void dwc2_hc_n_intr(struct dwc2_hsotg *hsotg, int chnum) chan = hsotg->hc_ptr_array[chnum]; - hcint = DWC2_READ_4(hsotg, HCINT(chnum)); - hcintmsk = DWC2_READ_4(hsotg, HCINTMSK(chnum)); + hcint = dwc2_readl(hsotg, HCINT(chnum)); + hcintmsk = dwc2_readl(hsotg, HCINTMSK(chnum)); if (!chan) { dev_err(hsotg->dev, "## hc_ptr_array for channel is NULL ##\n"); - DWC2_WRITE_4(hsotg, HCINT(chnum), hcint); + dwc2_writel(hsotg, hcint, HCINT(chnum)); return; } @@ -2061,7 +2089,17 @@ STATIC void dwc2_hc_n_intr(struct dwc2_hsotg *hsotg, int chnum) hcint, hcintmsk, hcint & hcintmsk); } - DWC2_WRITE_4(hsotg, HCINT(chnum), hcint); + dwc2_writel(hsotg, hcint, HCINT(chnum)); + + /* + * If we got an interrupt after someone called + * dwc2_hcd_endpoint_disable() we don't want to crash below + */ + if (!chan->qh) { + dev_warn(hsotg->dev, "Interrupt on disabled channel\n"); + return; + } + chan->hcint = hcint; hcint &= hcintmsk; @@ -2076,7 +2114,7 @@ STATIC void dwc2_hc_n_intr(struct dwc2_hsotg *hsotg, int chnum) * interrupt unmasked */ WARN_ON(hcint != HCINTMSK_CHHLTD); - if (hsotg->core_params->dma_desc_enable > 0) + if (hsotg->params.dma_desc_enable) dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, chan->halt_status); else @@ -2104,7 +2142,7 @@ STATIC void dwc2_hc_n_intr(struct dwc2_hsotg *hsotg, int chnum) qtd = list_first_entry(&chan->qh->qtd_list, struct dwc2_qtd, qtd_list_entry); - if (hsotg->core_params->dma_enable <= 0) { + if (!hsotg->params.host_dma) { if ((hcint & HCINTMSK_CHHLTD) && hcint != HCINTMSK_CHHLTD) hcint &= ~HCINTMSK_CHHLTD; } @@ -2186,7 +2224,7 @@ STATIC void dwc2_hc_intr(struct dwc2_hsotg *hsotg) int i; struct dwc2_host_chan *chan, *chan_tmp; - haint = DWC2_READ_4(hsotg, HAINT); + haint = dwc2_readl(hsotg, HAINT); if (dbg_perio()) { dev_vdbg(hsotg->dev, "%s()\n", __func__); @@ -2209,7 +2247,7 @@ STATIC void dwc2_hc_intr(struct dwc2_hsotg *hsotg) } } - for (i = 0; i < hsotg->core_params->host_channels; i++) { + for (i = 0; i < hsotg->params.host_channels; i++) { if (haint & (1 << i)) dwc2_hc_n_intr(hsotg, i); } @@ -2226,12 +2264,13 @@ irqreturn_t dwc2_handle_hcd_intr(struct dwc2_hsotg *hsotg) return retval; } - MUTEX_ASSERT_LOCKED(&hsotg->lock); + spin_lock(&hsotg->lock); /* Check if HOST Mode */ if (dwc2_is_host_mode(hsotg)) { gintsts = dwc2_read_core_intr(hsotg); if (!gintsts) { + spin_unlock(&hsotg->lock); return retval; } @@ -2269,10 +2308,12 @@ irqreturn_t dwc2_handle_hcd_intr(struct dwc2_hsotg *hsotg) "DWC OTG HCD Finished Servicing Interrupts\n"); dev_vdbg(hsotg->dev, "DWC OTG HCD gintsts=0x%08x gintmsk=0x%08x\n", - DWC2_READ_4(hsotg, GINTSTS), - DWC2_READ_4(hsotg, GINTMSK)); + dwc2_readl(hsotg, GINTSTS), + dwc2_readl(hsotg, GINTMSK)); } } + spin_unlock(&hsotg->lock); + return retval; } diff --git a/sys/dev/usb/dwc2/dwc2_hcdqueue.c b/sys/dev/usb/dwc2/dwc2_hcdqueue.c index 172e4185204..9f56c2655c7 100644 --- a/sys/dev/usb/dwc2/dwc2_hcdqueue.c +++ b/sys/dev/usb/dwc2/dwc2_hcdqueue.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dwc2_hcdqueue.c,v 1.12 2021/09/04 10:19:28 mglocker Exp $ */ +/* $OpenBSD: dwc2_hcdqueue.c,v 1.13 2022/09/04 08:42:40 mglocker Exp $ */ /* $NetBSD: dwc2_hcdqueue.c,v 1.11 2014/09/03 10:00:08 skrll Exp $ */ /* @@ -41,7 +41,6 @@ * This file contains the functions to manage Queue Heads and Queue * Transfer Descriptors for Host mode */ - #include #include #include @@ -60,220 +59,133 @@ #include #include -STATIC u32 dwc2_calc_bus_time(struct dwc2_hsotg *, int, int, int, int); -STATIC void dwc2_wait_timer_fn(void *); - -/* If we get a NAK, wait this long before retrying */ -#define DWC2_RETRY_WAIT_DELAY 1 /* msec */ +#include -/** - * dwc2_qh_init() - Initializes a QH structure - * - * @hsotg: The HCD state structure for the DWC OTG controller - * @qh: The QH to init - * @urb: Holds the information about the device/endpoint needed to initialize - * the QH +/* + * XXX: Include from dev/pci/drm/include/linux? Although, + * bitmap_find_next_zero_area() is missing there currently ... */ -#define SCHEDULE_SLOP 10 -STATIC void dwc2_qh_init(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, - struct dwc2_hcd_urb *urb) +static inline void +__set_bit(u_int b, volatile void *p) { - int dev_speed, hub_addr, hub_port; - - dev_vdbg(hsotg->dev, "%s()\n", __func__); - - /* Initialize QH */ - qh->hsotg = hsotg; - /* XXX timer_setup(&qh->wait_timer, dwc2_wait_timer_fn, 0); */ - timeout_set(&qh->wait_timer, dwc2_wait_timer_fn, qh); - qh->ep_type = dwc2_hcd_get_pipe_type(&urb->pipe_info); - qh->ep_is_in = dwc2_hcd_is_pipe_in(&urb->pipe_info) ? 1 : 0; - - qh->data_toggle = DWC2_HC_PID_DATA0; - qh->maxp = dwc2_hcd_get_mps(&urb->pipe_info); - INIT_LIST_HEAD(&qh->qtd_list); - INIT_LIST_HEAD(&qh->qh_list_entry); - - /* FS/LS Endpoint on HS Hub, NOT virtual root hub */ - dev_speed = dwc2_host_get_speed(hsotg, urb->priv); - - dwc2_host_hub_info(hsotg, urb->priv, &hub_addr, &hub_port); - qh->nak_frame = 0xffff; + volatile u_int *ptr = (volatile u_int *)p; + ptr[b >> 5] |= (1 << (b & 0x1f)); +} - if ((dev_speed == USB_SPEED_LOW || dev_speed == USB_SPEED_FULL) && - hub_addr != 0 && hub_addr != 1) { - dev_vdbg(hsotg->dev, - "QH init: EP %d: TT found at hub addr %d, for port %d\n", - dwc2_hcd_get_ep_num(&urb->pipe_info), hub_addr, - hub_port); - qh->do_split = 1; - } +static inline void +__clear_bit(u_int b, volatile void *p) +{ + volatile u_int *ptr = (volatile u_int *)p; + ptr[b >> 5] &= ~(1 << (b & 0x1f)); +} - if (qh->ep_type == USB_ENDPOINT_XFER_INT || - qh->ep_type == USB_ENDPOINT_XFER_ISOC) { - /* Compute scheduling parameters once and save them */ - u32 hprt, prtspd; - - /* Todo: Account for split transfers in the bus time */ - int bytecount = - dwc2_hb_mult(qh->maxp) * dwc2_max_packet(qh->maxp); - - qh->usecs = dwc2_calc_bus_time(hsotg, qh->do_split ? - USB_SPEED_HIGH : dev_speed, qh->ep_is_in, - qh->ep_type == USB_ENDPOINT_XFER_ISOC, - bytecount); - - /* Ensure frame_number corresponds to the reality */ - hsotg->frame_number = dwc2_hcd_get_frame_number(hsotg); - /* Start in a slightly future (micro)frame */ - qh->sched_frame = dwc2_frame_num_inc(hsotg->frame_number, - SCHEDULE_SLOP); - qh->interval = urb->interval; -#if 0 - /* Increase interrupt polling rate for debugging */ - if (qh->ep_type == USB_ENDPOINT_XFER_INT) - qh->interval = 8; -#endif - hprt = DWC2_READ_4(hsotg, HPRT0); - prtspd = (hprt & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT; - if (prtspd == HPRT0_SPD_HIGH_SPEED && - (dev_speed == USB_SPEED_LOW || - dev_speed == USB_SPEED_FULL)) { - qh->interval *= 8; - qh->sched_frame |= 0x7; - qh->start_split_frame = qh->sched_frame; +static inline int +find_next_bit(volatile void *p, int max, int b) +{ + volatile u_int *ptr = (volatile u_int *)p; + + for (; b < max; b+= 32) { + if (ptr[b >> 5] != 0) { + for (;;) { + if (ptr[b >> 5] & (1 << (b & 0x1f))) + return b; + b++; + } } - dev_dbg(hsotg->dev, "interval=%d\n", qh->interval); - } - - dev_vdbg(hsotg->dev, "DWC OTG HCD QH Initialized\n"); - dev_vdbg(hsotg->dev, "DWC OTG HCD QH - qh = %p\n", qh); - dev_vdbg(hsotg->dev, "DWC OTG HCD QH - Device Address = %d\n", - dwc2_hcd_get_dev_addr(&urb->pipe_info)); - dev_vdbg(hsotg->dev, "DWC OTG HCD QH - Endpoint %d, %s\n", - dwc2_hcd_get_ep_num(&urb->pipe_info), - dwc2_hcd_is_pipe_in(&urb->pipe_info) ? "IN" : "OUT"); - - qh->dev_speed = dev_speed; - -#ifdef DWC2_DEBUG - const char *speed, *type; - switch (dev_speed) { - case USB_SPEED_LOW: - speed = "low"; - break; - case USB_SPEED_FULL: - speed = "full"; - break; - case USB_SPEED_HIGH: - speed = "high"; - break; - default: - speed = "?"; - break; } - dev_vdbg(hsotg->dev, "DWC OTG HCD QH - Speed = %s\n", speed); + return max; +} - switch (qh->ep_type) { - case USB_ENDPOINT_XFER_ISOC: - type = "isochronous"; - break; - case USB_ENDPOINT_XFER_INT: - type = "interrupt"; - break; - case USB_ENDPOINT_XFER_CONTROL: - type = "control"; - break; - case USB_ENDPOINT_XFER_BULK: - type = "bulk"; - break; - default: - type = "?"; - break; +static inline int +find_next_zero_bit(volatile void *p, int max, int b) +{ + volatile u_int *ptr = (volatile u_int *)p; + + for (; b < max; b += 32) { + if (ptr[b >> 5] != ~0) { + for (;;) { + if ((ptr[b >> 5] & (1 << (b & 0x1f))) == 0) + return b; + b++; + } + } } + return max; +} - dev_vdbg(hsotg->dev, "DWC OTG HCD QH - Type = %s\n", type); -#endif - - if (qh->ep_type == USB_ENDPOINT_XFER_INT) { - dev_vdbg(hsotg->dev, "DWC OTG HCD QH - usecs = %d\n", - qh->usecs); - dev_vdbg(hsotg->dev, "DWC OTG HCD QH - interval = %d\n", - qh->interval); +unsigned long +bitmap_find_next_zero_area_off(unsigned long *, + unsigned long, + unsigned long, + unsigned int, + unsigned long, + unsigned long); + +#define __ALIGN_KERNEL_MASK(x, mask) (((x) + (mask)) & ~(mask)) +#define __ALIGN_MASK(x, mask) __ALIGN_KERNEL_MASK((x), (mask)) + +unsigned long +bitmap_find_next_zero_area_off(unsigned long *map, + unsigned long size, + unsigned long start, + unsigned int nr, + unsigned long align_mask, + unsigned long align_offset) +{ + unsigned long index, end, i; +again: + index = find_next_zero_bit(map, size, start); + + /* Align allocation */ + index = __ALIGN_MASK(index + align_offset, align_mask) - align_offset; + + end = index + nr; + if (end > size) + return end; + i = find_next_bit(map, end, index); + if (i < end) { + start = i + 1; + goto again; } + return index; } -/** - * dwc2_hcd_qh_create() - Allocates and initializes a QH - * - * @hsotg: The HCD state structure for the DWC OTG controller - * @urb: Holds the information about the device/endpoint needed - * to initialize the QH - * @mem_flags: Flag to do atomic allocation if needed - * - * Return: Pointer to the newly allocated QH, or NULL on error - */ -struct dwc2_qh *dwc2_hcd_qh_create(struct dwc2_hsotg *hsotg, - struct dwc2_hcd_urb *urb, - gfp_t mem_flags) +static inline unsigned long +bitmap_find_next_zero_area(unsigned long *map, + unsigned long size, + unsigned long start, + unsigned int nr, + unsigned long align_mask) { - struct dwc2_softc *sc = hsotg->hsotg_sc; - struct dwc2_qh *qh; - - if (!urb->priv) - return NULL; - - /* Allocate memory */ - qh = pool_get(&sc->sc_qhpool, PR_NOWAIT); - if (!qh) - return NULL; - - memset(qh, 0, sizeof(*qh)); - dwc2_qh_init(hsotg, qh, urb); + return bitmap_find_next_zero_area_off(map, size, start, nr, + align_mask, 0); +} - if (hsotg->core_params->dma_desc_enable > 0 && - dwc2_hcd_qh_init_ddma(hsotg, qh, mem_flags) < 0) { - dwc2_hcd_qh_free(hsotg, qh); - return NULL; - } +static inline void +bitmap_set(void *p, int b, u_int n) +{ + u_int end = b + n; - return qh; + for (; b < end; b++) + __set_bit(b, p); } -/** - * dwc2_hcd_qh_free() - Frees the QH - * - * @hsotg: HCD instance - * @qh: The QH to free - * - * QH should already be removed from the list. QTD list should already be empty - * if called from URB Dequeue. - * - * Must NOT be called with interrupt disabled or spinlock held - */ -void dwc2_hcd_qh_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +static inline void +bitmap_clear(void *p, int b, u_int n) { - struct dwc2_softc *sc = hsotg->hsotg_sc; + u_int end = b + n; - /* - * We don't have the lock so we can safely wait until the wait timer - * finishes. Of course, at this point in time we'd better have set - * wait_timer_active to false so if this timer was still pending it - * won't do anything anyway, but we want it to finish before we free - * memory. - */ - /* XXX del_timer_sync(&qh->wait_timer); */ + for (; b < end; b++) + __clear_bit(b, p); +} - timeout_del(&qh->wait_timer); - if (qh->desc_list) { - dwc2_hcd_qh_free_ddma(hsotg, qh); - } else if (qh->dw_align_buf) { - usb_freemem(&sc->sc_bus, &qh->dw_align_buf_usbdma); - qh->dw_align_buf_dma = (dma_addr_t)0; - } +STATIC void dwc2_wait_timer_fn(void *); - pool_put(&sc->sc_qhpool, qh); -} +/* Wait this long before releasing periodic reservation */ +#define DWC2_UNRESERVE_DELAY (msecs_to_jiffies(5)) + +/* If we get a NAK, wait this long before retrying */ +#define DWC2_RETRY_WAIT_DELAY 1 /* msec */ /** * dwc2_periodic_channel_available() - Checks that a channel is available for a @@ -293,15 +205,14 @@ STATIC int dwc2_periodic_channel_available(struct dwc2_hsotg *hsotg) int status; int num_channels; - num_channels = hsotg->core_params->host_channels; - if (hsotg->periodic_channels + hsotg->non_periodic_channels < - num_channels - && hsotg->periodic_channels < num_channels - 1) { + num_channels = hsotg->params.host_channels; + if ((hsotg->periodic_channels + hsotg->non_periodic_channels < + num_channels) && (hsotg->periodic_channels < num_channels - 1)) { status = 0; } else { dev_dbg(hsotg->dev, - "%s: Total channels: %d, Periodic: %d, " - "Non-periodic: %d\n", __func__, num_channels, + "%s: Total channels: %d, Periodic: %d, Non-periodic: %d\n", + __func__, num_channels, hsotg->periodic_channels, hsotg->non_periodic_channels); status = -ENOSPC; } @@ -334,19 +245,19 @@ STATIC int dwc2_check_periodic_bandwidth(struct dwc2_hsotg *hsotg, * High speed mode * Max periodic usecs is 80% x 125 usec = 100 usec */ - max_claimed_usecs = 100 - qh->usecs; + max_claimed_usecs = 100 - qh->host_us; } else { /* * Full speed mode * Max periodic usecs is 90% x 1000 usec = 900 usec */ - max_claimed_usecs = 900 - qh->usecs; + max_claimed_usecs = 900 - qh->host_us; } if (hsotg->periodic_usecs > max_claimed_usecs) { dev_err(hsotg->dev, "%s: already claimed usecs %d, required usecs %d\n", - __func__, hsotg->periodic_usecs, qh->usecs); + __func__, hsotg->periodic_usecs, qh->host_us); status = -ENOSPC; } @@ -354,194 +265,1225 @@ STATIC int dwc2_check_periodic_bandwidth(struct dwc2_hsotg *hsotg, } /** - * Microframe scheduler - * track the total use in hsotg->frame_usecs - * keep each qh use in qh->frame_usecs - * when surrendering the qh then donate the time back + * pmap_schedule() - Schedule time in a periodic bitmap (pmap). + * + * @map: The bitmap representing the schedule; will be updated + * upon success. + * @bits_per_period: The schedule represents several periods. This is how many + * bits are in each period. It's assumed that the beginning + * of the schedule will repeat after its end. + * @periods_in_map: The number of periods in the schedule. + * @num_bits: The number of bits we need per period we want to reserve + * in this function call. + * @interval: How often we need to be scheduled for the reservation this + * time. 1 means every period. 2 means every other period. + * ...you get the picture? + * @start: The bit number to start at. Normally 0. Must be within + * the interval or we return failure right away. + * @only_one_period: Normally we'll allow picking a start anywhere within the + * first interval, since we can still make all repetition + * requirements by doing that. However, if you pass true + * here then we'll return failure if we can't fit within + * the period that "start" is in. + * + * The idea here is that we want to schedule time for repeating events that all + * want the same resource. The resource is divided into fixed-sized periods + * and the events want to repeat every "interval" periods. The schedule + * granularity is one bit. + * + * To keep things "simple", we'll represent our schedule with a bitmap that + * contains a fixed number of periods. This gets rid of a lot of complexity + * but does mean that we need to handle things specially (and non-ideally) if + * the number of the periods in the schedule doesn't match well with the + * intervals that we're trying to schedule. + * + * Here's an explanation of the scheme we'll implement, assuming 8 periods. + * - If interval is 1, we need to take up space in each of the 8 + * periods we're scheduling. Easy. + * - If interval is 2, we need to take up space in half of the + * periods. Again, easy. + * - If interval is 3, we actually need to fall back to interval 1. + * Why? Because we might need time in any period. AKA for the + * first 8 periods, we'll be in slot 0, 3, 6. Then we'll be + * in slot 1, 4, 7. Then we'll be in 2, 5. Then we'll be back to + * 0, 3, and 6. Since we could be in any frame we need to reserve + * for all of them. Sucks, but that's what you gotta do. Note that + * if we were instead scheduling 8 * 3 = 24 we'd do much better, but + * then we need more memory and time to do scheduling. + * - If interval is 4, easy. + * - If interval is 5, we again need interval 1. The schedule will be + * 0, 5, 2, 7, 4, 1, 6, 3, 0 + * - If interval is 6, we need interval 2. 0, 6, 4, 2. + * - If interval is 7, we need interval 1. + * - If interval is 8, we need interval 8. + * + * If you do the math, you'll see that we need to pretend that interval is + * equal to the greatest_common_divisor(interval, periods_in_map). + * + * Note that at the moment this function tends to front-pack the schedule. + * In some cases that's really non-ideal (it's hard to schedule things that + * need to repeat every period). In other cases it's perfect (you can easily + * schedule bigger, less often repeating things). + * + * Here's the algorithm in action (8 periods, 5 bits per period): + * |** | |** | |** | |** | | OK 2 bits, intv 2 at 0 + * |*****| ***|*****| ***|*****| ***|*****| ***| OK 3 bits, intv 3 at 2 + * |*****|* ***|*****| ***|*****|* ***|*****| ***| OK 1 bits, intv 4 at 5 + * |** |* |** | |** |* |** | | Remv 3 bits, intv 3 at 2 + * |*** |* |*** | |*** |* |*** | | OK 1 bits, intv 6 at 2 + * |**** |* * |**** | * |**** |* * |**** | * | OK 1 bits, intv 1 at 3 + * |**** |**** |**** | *** |**** |**** |**** | *** | OK 2 bits, intv 2 at 6 + * |*****|*****|*****| ****|*****|*****|*****| ****| OK 1 bits, intv 1 at 4 + * |*****|*****|*****| ****|*****|*****|*****| ****| FAIL 1 bits, intv 1 + * | ***|*****| ***| ****| ***|*****| ***| ****| Remv 2 bits, intv 2 at 0 + * | ***| ****| ***| ****| ***| ****| ***| ****| Remv 1 bits, intv 4 at 5 + * | **| ****| **| ****| **| ****| **| ****| Remv 1 bits, intv 6 at 2 + * | *| ** *| *| ** *| *| ** *| *| ** *| Remv 1 bits, intv 1 at 3 + * | *| *| *| *| *| *| *| *| Remv 2 bits, intv 2 at 6 + * | | | | | | | | | Remv 1 bits, intv 1 at 4 + * |** | |** | |** | |** | | OK 2 bits, intv 2 at 0 + * |*** | |** | |*** | |** | | OK 1 bits, intv 4 at 2 + * |*****| |** **| |*****| |** **| | OK 2 bits, intv 2 at 3 + * |*****|* |** **| |*****|* |** **| | OK 1 bits, intv 4 at 5 + * |*****|*** |** **| ** |*****|*** |** **| ** | OK 2 bits, intv 2 at 6 + * |*****|*****|** **| ****|*****|*****|** **| ****| OK 2 bits, intv 2 at 8 + * |*****|*****|*****| ****|*****|*****|*****| ****| OK 1 bits, intv 4 at 12 + * + * This function is pretty generic and could be easily abstracted if anything + * needed similar scheduling. + * + * Returns either -ENOSPC or a >= 0 start bit which should be passed to the + * unschedule routine. The map bitmap will be updated on a non-error result. */ -STATIC const unsigned short max_uframe_usecs[] = { - 100, 100, 100, 100, 100, 100, 30, 0 -}; - -void dwc2_hcd_init_usecs(struct dwc2_hsotg *hsotg) +static int pmap_schedule(unsigned long *map, int bits_per_period, + int periods_in_map, int num_bits, + int interval, int start, bool only_one_period) { + int interval_bits; + int to_reserve; + int first_end; int i; - for (i = 0; i < 8; i++) - hsotg->frame_usecs[i] = max_uframe_usecs[i]; -} + if (num_bits > bits_per_period) + return -ENOSPC; -STATIC int dwc2_find_single_uframe(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) -{ - unsigned short utime = qh->usecs; - int i; + /* Adjust interval as per description */ + interval = gcd(interval, periods_in_map); - for (i = 0; i < 8; i++) { - /* At the start hsotg->frame_usecs[i] = max_uframe_usecs[i] */ - if (utime <= hsotg->frame_usecs[i]) { - hsotg->frame_usecs[i] -= utime; - qh->frame_usecs[i] += utime; - return i; - } - } - return -ENOSPC; -} + interval_bits = bits_per_period * interval; + to_reserve = periods_in_map / interval; -/* - * use this for FS apps that can span multiple uframes - */ -STATIC int dwc2_find_multi_uframe(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) -{ - unsigned short utime = qh->usecs; - unsigned short xtime; - int t_left; - int i; - int j; - int k; + /* If start has gotten us past interval then we can't schedule */ + if (start >= interval_bits) + return -ENOSPC; - for (i = 0; i < 8; i++) { - if (hsotg->frame_usecs[i] <= 0) - continue; + if (only_one_period) + /* Must fit within same period as start; end at begin of next */ + first_end = (start / bits_per_period + 1) * bits_per_period; + else + /* Can fit anywhere in the first interval */ + first_end = interval_bits; + + /* + * We'll try to pick the first repetition, then see if that time + * is free for each of the subsequent repetitions. If it's not + * we'll adjust the start time for the next search of the first + * repetition. + */ + while (start + num_bits <= first_end) { + int end; + + /* Need to stay within this period */ + end = (start / bits_per_period + 1) * bits_per_period; + + /* Look for num_bits us in this microframe starting at start */ + start = bitmap_find_next_zero_area(map, end, start, num_bits, + 0); /* - * we need n consecutive slots so use j as a start slot - * j plus j+1 must be enough time (for now) + * We should get start >= end if we fail. We might be + * able to check the next microframe depending on the + * interval, so continue on (start already updated). */ - xtime = hsotg->frame_usecs[i]; - for (j = i + 1; j < 8; j++) { - /* - * if we add this frame remaining time to xtime we may - * be OK, if not we need to test j for a complete frame - */ - if (xtime + hsotg->frame_usecs[j] < utime) { - if (hsotg->frame_usecs[j] < - max_uframe_usecs[j]) - continue; - } - if (xtime >= utime) { - t_left = utime; - for (k = i; k < 8; k++) { - t_left -= hsotg->frame_usecs[k]; - if (t_left <= 0) { - qh->frame_usecs[k] += - hsotg->frame_usecs[k] - + t_left; - hsotg->frame_usecs[k] = -t_left; - return i; - } else { - qh->frame_usecs[k] += - hsotg->frame_usecs[k]; - hsotg->frame_usecs[k] = 0; - } - } - } - /* add the frame time to x time */ - xtime += hsotg->frame_usecs[j]; - /* we must have a fully available next frame or break */ - if (xtime < utime && - hsotg->frame_usecs[j] == max_uframe_usecs[j]) + if (start >= end) { + start = end; + continue; + } + + /* At this point we have a valid point for first one */ + for (i = 1; i < to_reserve; i++) { + int ith_start = start + interval_bits * i; + int ith_end = end + interval_bits * i; + int ret; + + /* Use this as a dumb "check if bits are 0" */ + ret = bitmap_find_next_zero_area( + map, ith_start + num_bits, ith_start, num_bits, + 0); + + /* We got the right place, continue checking */ + if (ret == ith_start) continue; + + /* Move start up for next time and exit for loop */ + ith_start = bitmap_find_next_zero_area( + map, ith_end, ith_start, num_bits, 0); + if (ith_start >= ith_end) + /* Need a while new period next time */ + start = end; + else + start = ith_start - interval_bits * i; + break; } + + /* If didn't exit the for loop with a break, we have success */ + if (i == to_reserve) + break; + } + + if (start + num_bits > first_end) + return -ENOSPC; + + for (i = 0; i < to_reserve; i++) { + int ith_start = start + interval_bits * i; + + bitmap_set(map, ith_start, num_bits); } - return -ENOSPC; + + return start; } -STATIC int dwc2_find_uframe(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +/** + * pmap_unschedule() - Undo work done by pmap_schedule() + * + * @map: See pmap_schedule(). + * @bits_per_period: See pmap_schedule(). + * @periods_in_map: See pmap_schedule(). + * @num_bits: The number of bits that was passed to schedule. + * @interval: The interval that was passed to schedule. + * @start: The return value from pmap_schedule(). + */ +static void pmap_unschedule(unsigned long *map, int bits_per_period, + int periods_in_map, int num_bits, + int interval, int start) { - int ret; + int interval_bits; + int to_release; + int i; - if (qh->dev_speed == USB_SPEED_HIGH) { - /* if this is a hs transaction we need a full frame */ - ret = dwc2_find_single_uframe(hsotg, qh); - } else { - /* - * if this is a fs transaction we may need a sequence - * of frames - */ - ret = dwc2_find_multi_uframe(hsotg, qh); + /* Adjust interval as per description in pmap_schedule() */ + interval = gcd(interval, periods_in_map); + + interval_bits = bits_per_period * interval; + to_release = periods_in_map / interval; + + for (i = 0; i < to_release; i++) { + int ith_start = start + interval_bits * i; + + bitmap_clear(map, ith_start, num_bits); } - return ret; } /** - * dwc2_check_max_xfer_size() - Checks that the max transfer size allowed in a - * host channel is large enough to handle the maximum data transfer in a single - * (micro)frame for a periodic transfer + * dwc2_get_ls_map() - Get the map used for the given qh * - * @hsotg: The HCD state structure for the DWC OTG controller - * @qh: QH for a periodic endpoint + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. * - * Return: 0 if successful, negative error code otherwise + * We'll always get the periodic map out of our TT. Note that even if we're + * running the host straight in low speed / full speed mode it appears as if + * a TT is allocated for us, so we'll use it. If that ever changes we can + * add logic here to get a map out of "hsotg" if !qh->do_split. + * + * Returns: the map or NULL if a map couldn't be found. */ -STATIC int dwc2_check_max_xfer_size(struct dwc2_hsotg *hsotg, - struct dwc2_qh *qh) +static unsigned long *dwc2_get_ls_map(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) { - u32 max_xfer_size; - u32 max_channel_xfer_size; - int status = 0; + unsigned long *map; - max_xfer_size = dwc2_max_packet(qh->maxp) * dwc2_hb_mult(qh->maxp); - max_channel_xfer_size = hsotg->core_params->max_transfer_size; + /* Don't expect to be missing a TT and be doing low speed scheduling */ + if (WARN_ON(!qh->dwc_tt)) + return NULL; - if (max_xfer_size > max_channel_xfer_size) { - dev_err(hsotg->dev, - "%s: Periodic xfer length %d > max xfer length for channel %d\n", - __func__, max_xfer_size, max_channel_xfer_size); - status = -ENOSPC; - } + /* Get the map and adjust if this is a multi_tt hub */ + map = qh->dwc_tt->periodic_bitmaps; + if (qh->dwc_tt->usb_tt->hub->multi) + map += DWC2_ELEMENTS_PER_LS_BITMAP * (qh->ttport - 1); - return status; + return map; } -/** - * dwc2_schedule_periodic() - Schedules an interrupt or isochronous transfer in - * the periodic schedule +#ifdef DWC2_PRINT_SCHEDULE +/* + * cat_printf() - A printf() + strcat() helper * - * @hsotg: The HCD state structure for the DWC OTG controller - * @qh: QH for the periodic transfer. The QH should already contain the - * scheduling information. + * This is useful for concatenating a bunch of strings where each string is + * constructed using printf. * - * Return: 0 if successful, negative error code otherwise + * @buf: The destination buffer; will be updated to point after the printed + * data. + * @size: The number of bytes in the buffer (includes space for '\0'). + * @fmt: The format for printf. + * @...: The args for printf. */ -STATIC int dwc2_schedule_periodic(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +static __printf(3, 4) +void cat_printf(char **buf, size_t *size, const char *fmt, ...) { - int status; - - if (hsotg->core_params->uframe_sched > 0) { - int frame = -1; + va_list args; + int i; - status = dwc2_find_uframe(hsotg, qh); - if (status == 0) - frame = 7; - else if (status > 0) - frame = status - 1; + if (*size == 0) + return; - /* Set the new frame up */ - if (frame >= 0) { - qh->sched_frame &= ~0x7; - qh->sched_frame |= (frame & 7); - } + va_start(args, fmt); + i = vsnprintf(*buf, *size, fmt, args); + va_end(args); - if (status > 0) - status = 0; + if (i >= *size) { + (*buf)[*size - 1] = '\0'; + *buf += *size; + *size = 0; } else { - status = dwc2_periodic_channel_available(hsotg); - if (status) { - dev_info(hsotg->dev, - "%s: No host channel available for periodic transfer\n", - __func__); - return status; + *buf += i; + *size -= i; + } +} + +/* + * pmap_print() - Print the given periodic map + * + * Will attempt to print out the periodic schedule. + * + * @map: See pmap_schedule(). + * @bits_per_period: See pmap_schedule(). + * @periods_in_map: See pmap_schedule(). + * @period_name: The name of 1 period, like "uFrame" + * @units: The name of the units, like "us". + * @print_fn: The function to call for printing. + * @print_data: Opaque data to pass to the print function. + */ +static void pmap_print(unsigned long *map, int bits_per_period, + int periods_in_map, const char *period_name, + const char *units, + void (*print_fn)(const char *str, void *data), + void *print_data) +{ + int period; + + for (period = 0; period < periods_in_map; period++) { + char tmp[64]; + char *buf = tmp; + size_t buf_size = sizeof(tmp); + int period_start = period * bits_per_period; + int period_end = period_start + bits_per_period; + int start = 0; + int count = 0; + bool printed = false; + int i; + + for (i = period_start; i < period_end + 1; i++) { + /* Handle case when ith bit is set */ + if (i < period_end && + bitmap_find_next_zero_area(map, i + 1, + i, 1, 0) != i) { + if (count == 0) + start = i - period_start; + count++; + continue; + } + + /* ith bit isn't set; don't care if count == 0 */ + if (count == 0) + continue; + + if (!printed) + cat_printf(&buf, &buf_size, "%s %d: ", + period_name, period); + else + cat_printf(&buf, &buf_size, ", "); + printed = true; + + cat_printf(&buf, &buf_size, "%d %s -%3d %s", start, + units, start + count - 1, units); + count = 0; } - status = dwc2_check_periodic_bandwidth(hsotg, qh); + if (printed) + print_fn(tmp, print_data); } +} - if (status) { - dev_dbg(hsotg->dev, - "%s: Insufficient periodic bandwidth for periodic transfer\n", - __func__); - return status; - } +struct dwc2_qh_print_data { + struct dwc2_hsotg *hsotg; + struct dwc2_qh *qh; +}; + +/** + * dwc2_qh_print() - Helper function for dwc2_qh_schedule_print() + * + * @str: The string to print + * @data: A pointer to a struct dwc2_qh_print_data + */ +static void dwc2_qh_print(const char *str, void *data) +{ + struct dwc2_qh_print_data *print_data = data; + + dwc2_sch_dbg(print_data->hsotg, "QH=%p ...%s\n", print_data->qh, str); +} + +/** + * dwc2_qh_schedule_print() - Print the periodic schedule + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH to print. + */ +static void dwc2_qh_schedule_print(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) +{ + struct dwc2_qh_print_data print_data = { hsotg, qh }; + int i; + + /* + * The printing functions are quite slow and inefficient. + * If we don't have tracing turned on, don't run unless the special + * define is turned on. + */ + + if (qh->schedule_low_speed) { + unsigned long *map = dwc2_get_ls_map(hsotg, qh); + + dwc2_sch_dbg(hsotg, "QH=%p LS/FS trans: %d=>%d us @ %d us", + qh, qh->device_us, + DWC2_ROUND_US_TO_SLICE(qh->device_us), + DWC2_US_PER_SLICE * qh->ls_start_schedule_slice); + + if (map) { + dwc2_sch_dbg(hsotg, + "QH=%p Whole low/full speed map %p now:\n", + qh, map); + pmap_print(map, DWC2_LS_PERIODIC_SLICES_PER_FRAME, + DWC2_LS_SCHEDULE_FRAMES, "Frame ", "slices", + dwc2_qh_print, &print_data); + } + } + + for (i = 0; i < qh->num_hs_transfers; i++) { + struct dwc2_hs_transfer_time *trans_time = qh->hs_transfers + i; + int uframe = trans_time->start_schedule_us / + DWC2_HS_PERIODIC_US_PER_UFRAME; + int rel_us = trans_time->start_schedule_us % + DWC2_HS_PERIODIC_US_PER_UFRAME; + + dwc2_sch_dbg(hsotg, + "QH=%p HS trans #%d: %d us @ uFrame %d + %d us\n", + qh, i, trans_time->duration_us, uframe, rel_us); + } + if (qh->num_hs_transfers) { + dwc2_sch_dbg(hsotg, "QH=%p Whole high speed map now:\n", qh); + pmap_print(hsotg->hs_periodic_bitmap, + DWC2_HS_PERIODIC_US_PER_UFRAME, + DWC2_HS_SCHEDULE_UFRAMES, "uFrame", "us", + dwc2_qh_print, &print_data); + } +} +#else +static inline void dwc2_qh_schedule_print(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) {}; +#endif + +/** + * dwc2_ls_pmap_schedule() - Schedule a low speed QH + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + * @search_slice: We'll start trying to schedule at the passed slice. + * Remember that slices are the units of the low speed + * schedule (think 25us or so). + * + * Wraps pmap_schedule() with the right parameters for low speed scheduling. + * + * Normally we schedule low speed devices on the map associated with the TT. + * + * Returns: 0 for success or an error code. + */ +static int dwc2_ls_pmap_schedule(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, + int search_slice) +{ + int slices = DIV_ROUND_UP(qh->device_us, DWC2_US_PER_SLICE); + unsigned long *map = dwc2_get_ls_map(hsotg, qh); + int slice; + + if (!map) + return -EINVAL; + + /* + * Schedule on the proper low speed map with our low speed scheduling + * parameters. Note that we use the "device_interval" here since + * we want the low speed interval and the only way we'd be in this + * function is if the device is low speed. + * + * If we happen to be doing low speed and high speed scheduling for the + * same transaction (AKA we have a split) we always do low speed first. + * That means we can always pass "false" for only_one_period (that + * parameters is only useful when we're trying to get one schedule to + * match what we already planned in the other schedule). + */ + slice = pmap_schedule(map, DWC2_LS_PERIODIC_SLICES_PER_FRAME, + DWC2_LS_SCHEDULE_FRAMES, slices, + qh->device_interval, search_slice, false); + + if (slice < 0) + return slice; + + qh->ls_start_schedule_slice = slice; + return 0; +} + +/** + * dwc2_ls_pmap_unschedule() - Undo work done by dwc2_ls_pmap_schedule() + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + */ +static void dwc2_ls_pmap_unschedule(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) +{ + int slices = DIV_ROUND_UP(qh->device_us, DWC2_US_PER_SLICE); + unsigned long *map = dwc2_get_ls_map(hsotg, qh); + + /* Schedule should have failed, so no worries about no error code */ + if (!map) + return; + + pmap_unschedule(map, DWC2_LS_PERIODIC_SLICES_PER_FRAME, + DWC2_LS_SCHEDULE_FRAMES, slices, qh->device_interval, + qh->ls_start_schedule_slice); +} + +/** + * dwc2_hs_pmap_schedule - Schedule in the main high speed schedule + * + * This will schedule something on the main dwc2 schedule. + * + * We'll start looking in qh->hs_transfers[index].start_schedule_us. We'll + * update this with the result upon success. We also use the duration from + * the same structure. + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + * @only_one_period: If true we will limit ourselves to just looking at + * one period (aka one 100us chunk). This is used if we have + * already scheduled something on the low speed schedule and + * need to find something that matches on the high speed one. + * @index: The index into qh->hs_transfers that we're working with. + * + * Returns: 0 for success or an error code. Upon success the + * dwc2_hs_transfer_time specified by "index" will be updated. + */ +static int dwc2_hs_pmap_schedule(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, + bool only_one_period, int index) +{ + struct dwc2_hs_transfer_time *trans_time = qh->hs_transfers + index; + int us; + + us = pmap_schedule(hsotg->hs_periodic_bitmap, + DWC2_HS_PERIODIC_US_PER_UFRAME, + DWC2_HS_SCHEDULE_UFRAMES, trans_time->duration_us, + qh->host_interval, trans_time->start_schedule_us, + only_one_period); + + if (us < 0) + return us; + + trans_time->start_schedule_us = us; + return 0; +} + +/** + * dwc2_hs_pmap_unschedule() - Undo work done by dwc2_hs_pmap_schedule() + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + * @index: Transfer index + */ +static void dwc2_hs_pmap_unschedule(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh, int index) +{ + struct dwc2_hs_transfer_time *trans_time = qh->hs_transfers + index; + + pmap_unschedule(hsotg->hs_periodic_bitmap, + DWC2_HS_PERIODIC_US_PER_UFRAME, + DWC2_HS_SCHEDULE_UFRAMES, trans_time->duration_us, + qh->host_interval, trans_time->start_schedule_us); +} + +/** + * dwc2_uframe_schedule_split - Schedule a QH for a periodic split xfer. + * + * This is the most complicated thing in USB. We have to find matching time + * in both the global high speed schedule for the port and the low speed + * schedule for the TT associated with the given device. + * + * Being here means that the host must be running in high speed mode and the + * device is in low or full speed mode (and behind a hub). + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + */ +static int dwc2_uframe_schedule_split(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) +{ + int bytecount = qh->maxp_mult * qh->maxp; + int ls_search_slice; + int err = 0; + int host_interval_in_sched; + + /* + * The interval (how often to repeat) in the actual host schedule. + * See pmap_schedule() for gcd() explanation. + */ + host_interval_in_sched = gcd(qh->host_interval, + DWC2_HS_SCHEDULE_UFRAMES); + + /* + * We always try to find space in the low speed schedule first, then + * try to find high speed time that matches. If we don't, we'll bump + * up the place we start searching in the low speed schedule and try + * again. To start we'll look right at the beginning of the low speed + * schedule. + * + * Note that this will tend to front-load the high speed schedule. + * We may eventually want to try to avoid this by either considering + * both schedules together or doing some sort of round robin. + */ + ls_search_slice = 0; + + while (ls_search_slice < DWC2_LS_SCHEDULE_SLICES) { + int start_s_uframe; + int ssplit_s_uframe; + int second_s_uframe; + int rel_uframe; + int first_count; + int middle_count; + int end_count; + int first_data_bytes; + int other_data_bytes; + int i; + + if (qh->schedule_low_speed) { + err = dwc2_ls_pmap_schedule(hsotg, qh, ls_search_slice); + + /* + * If we got an error here there's no other magic we + * can do, so bail. All the looping above is only + * helpful to redo things if we got a low speed slot + * and then couldn't find a matching high speed slot. + */ + if (err) + return err; + } else { + /* Must be missing the tt structure? Why? */ + WARN_ON_ONCE(1); + } + + /* + * This will give us a number 0 - 7 if + * DWC2_LS_SCHEDULE_FRAMES == 1, or 0 - 15 if == 2, or ... + */ + start_s_uframe = qh->ls_start_schedule_slice / + DWC2_SLICES_PER_UFRAME; + + /* Get a number that's always 0 - 7 */ + rel_uframe = (start_s_uframe % 8); + + /* + * If we were going to start in uframe 7 then we would need to + * issue a start split in uframe 6, which spec says is not OK. + * Move on to the next full frame (assuming there is one). + * + * See 11.18.4 Host Split Transaction Scheduling Requirements + * bullet 1. + */ + if (rel_uframe == 7) { + if (qh->schedule_low_speed) + dwc2_ls_pmap_unschedule(hsotg, qh); + ls_search_slice = + (qh->ls_start_schedule_slice / + DWC2_LS_PERIODIC_SLICES_PER_FRAME + 1) * + DWC2_LS_PERIODIC_SLICES_PER_FRAME; + continue; + } + + /* + * For ISOC in: + * - start split (frame -1) + * - complete split w/ data (frame +1) + * - complete split w/ data (frame +2) + * - ... + * - complete split w/ data (frame +num_data_packets) + * - complete split w/ data (frame +num_data_packets+1) + * - complete split w/ data (frame +num_data_packets+2, max 8) + * ...though if frame was "0" then max is 7... + * + * For ISOC out we might need to do: + * - start split w/ data (frame -1) + * - start split w/ data (frame +0) + * - ... + * - start split w/ data (frame +num_data_packets-2) + * + * For INTERRUPT in we might need to do: + * - start split (frame -1) + * - complete split w/ data (frame +1) + * - complete split w/ data (frame +2) + * - complete split w/ data (frame +3, max 8) + * + * For INTERRUPT out we might need to do: + * - start split w/ data (frame -1) + * - complete split (frame +1) + * - complete split (frame +2) + * - complete split (frame +3, max 8) + * + * Start adjusting! + */ + ssplit_s_uframe = (start_s_uframe + + host_interval_in_sched - 1) % + host_interval_in_sched; + if (qh->ep_type == USB_ENDPOINT_XFER_ISOC && !qh->ep_is_in) + second_s_uframe = start_s_uframe; + else + second_s_uframe = start_s_uframe + 1; + + /* First data transfer might not be all 188 bytes. */ + first_data_bytes = 188 - + DIV_ROUND_UP(188 * (qh->ls_start_schedule_slice % + DWC2_SLICES_PER_UFRAME), + DWC2_SLICES_PER_UFRAME); + if (first_data_bytes > bytecount) + first_data_bytes = bytecount; + other_data_bytes = bytecount - first_data_bytes; + + /* + * For now, skip OUT xfers where first xfer is partial + * + * Main dwc2 code assumes: + * - INT transfers never get split in two. + * - ISOC transfers can always transfer 188 bytes the first + * time. + * + * Until that code is fixed, try again if the first transfer + * couldn't transfer everything. + * + * This code can be removed if/when the rest of dwc2 handles + * the above cases. Until it's fixed we just won't be able + * to schedule quite as tightly. + */ + if (!qh->ep_is_in && + (first_data_bytes != min_t(int, 188, bytecount))) { + dwc2_sch_dbg(hsotg, + "QH=%p avoiding broken 1st xfer (%d, %d)\n", + qh, first_data_bytes, bytecount); + if (qh->schedule_low_speed) + dwc2_ls_pmap_unschedule(hsotg, qh); + ls_search_slice = (start_s_uframe + 1) * + DWC2_SLICES_PER_UFRAME; + continue; + } + + /* Start by assuming transfers for the bytes */ + qh->num_hs_transfers = 1 + DIV_ROUND_UP(other_data_bytes, 188); + + /* + * Everything except ISOC OUT has extra transfers. Rules are + * complicated. See 11.18.4 Host Split Transaction Scheduling + * Requirements bullet 3. + */ + if (qh->ep_type == USB_ENDPOINT_XFER_INT) { + if (rel_uframe == 6) + qh->num_hs_transfers += 2; + else + qh->num_hs_transfers += 3; + + if (qh->ep_is_in) { + /* + * First is start split, middle/end is data. + * Allocate full data bytes for all data. + */ + first_count = 4; + middle_count = bytecount; + end_count = bytecount; + } else { + /* + * First is data, middle/end is complete. + * First transfer and second can have data. + * Rest should just have complete split. + */ + first_count = first_data_bytes; + middle_count = max_t(int, 4, other_data_bytes); + end_count = 4; + } + } else { + if (qh->ep_is_in) { + int last; + + /* Account for the start split */ + qh->num_hs_transfers++; + + /* Calculate "L" value from spec */ + last = rel_uframe + qh->num_hs_transfers + 1; + + /* Start with basic case */ + if (last <= 6) + qh->num_hs_transfers += 2; + else + qh->num_hs_transfers += 1; + + /* Adjust downwards */ + if (last >= 6 && rel_uframe == 0) + qh->num_hs_transfers--; + + /* 1st = start; rest can contain data */ + first_count = 4; + middle_count = min_t(int, 188, bytecount); + end_count = middle_count; + } else { + /* All contain data, last might be smaller */ + first_count = first_data_bytes; + middle_count = min_t(int, 188, + other_data_bytes); + end_count = other_data_bytes % 188; + } + } + + /* Assign durations per uFrame */ + qh->hs_transfers[0].duration_us = HS_USECS_ISO(first_count); + for (i = 1; i < qh->num_hs_transfers - 1; i++) + qh->hs_transfers[i].duration_us = + HS_USECS_ISO(middle_count); + if (qh->num_hs_transfers > 1) + qh->hs_transfers[qh->num_hs_transfers - 1].duration_us = + HS_USECS_ISO(end_count); + + /* + * Assign start us. The call below to dwc2_hs_pmap_schedule() + * will start with these numbers but may adjust within the same + * microframe. + */ + qh->hs_transfers[0].start_schedule_us = + ssplit_s_uframe * DWC2_HS_PERIODIC_US_PER_UFRAME; + for (i = 1; i < qh->num_hs_transfers; i++) + qh->hs_transfers[i].start_schedule_us = + ((second_s_uframe + i - 1) % + DWC2_HS_SCHEDULE_UFRAMES) * + DWC2_HS_PERIODIC_US_PER_UFRAME; + + /* Try to schedule with filled in hs_transfers above */ + for (i = 0; i < qh->num_hs_transfers; i++) { + err = dwc2_hs_pmap_schedule(hsotg, qh, true, i); + if (err) + break; + } + + /* If we scheduled all w/out breaking out then we're all good */ + if (i == qh->num_hs_transfers) + break; + + for (; i >= 0; i--) + dwc2_hs_pmap_unschedule(hsotg, qh, i); + + if (qh->schedule_low_speed) + dwc2_ls_pmap_unschedule(hsotg, qh); + + /* Try again starting in the next microframe */ + ls_search_slice = (start_s_uframe + 1) * DWC2_SLICES_PER_UFRAME; + } + + if (ls_search_slice >= DWC2_LS_SCHEDULE_SLICES) + return -ENOSPC; + + return 0; +} + +/** + * dwc2_uframe_schedule_hs - Schedule a QH for a periodic high speed xfer. + * + * Basically this just wraps dwc2_hs_pmap_schedule() to provide a clean + * interface. + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + */ +static int dwc2_uframe_schedule_hs(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + /* In non-split host and device time are the same */ + WARN_ON(qh->host_us != qh->device_us); + WARN_ON(qh->host_interval != qh->device_interval); + WARN_ON(qh->num_hs_transfers != 1); + + /* We'll have one transfer; init start to 0 before calling scheduler */ + qh->hs_transfers[0].start_schedule_us = 0; + qh->hs_transfers[0].duration_us = qh->host_us; + + return dwc2_hs_pmap_schedule(hsotg, qh, false, 0); +} + +/** + * dwc2_uframe_schedule_ls - Schedule a QH for a periodic low/full speed xfer. + * + * Basically this just wraps dwc2_ls_pmap_schedule() to provide a clean + * interface. + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + */ +static int dwc2_uframe_schedule_ls(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + /* In non-split host and device time are the same */ + WARN_ON(qh->host_us != qh->device_us); + WARN_ON(qh->host_interval != qh->device_interval); + WARN_ON(!qh->schedule_low_speed); + + /* Run on the main low speed schedule (no split = no hub = no TT) */ + return dwc2_ls_pmap_schedule(hsotg, qh, 0); +} + +/** + * dwc2_uframe_schedule - Schedule a QH for a periodic xfer. + * + * Calls one of the 3 sub-function depending on what type of transfer this QH + * is for. Also adds some printing. + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + */ +static int dwc2_uframe_schedule(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + int ret; + + if (qh->dev_speed == USB_SPEED_HIGH) + ret = dwc2_uframe_schedule_hs(hsotg, qh); + else if (!qh->do_split) + ret = dwc2_uframe_schedule_ls(hsotg, qh); + else + ret = dwc2_uframe_schedule_split(hsotg, qh); + + if (ret) + dwc2_sch_dbg(hsotg, "QH=%p Failed to schedule %d\n", qh, ret); + else + dwc2_qh_schedule_print(hsotg, qh); + + return ret; +} + +/** + * dwc2_uframe_unschedule - Undoes dwc2_uframe_schedule(). + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + */ +static void dwc2_uframe_unschedule(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + int i; + + for (i = 0; i < qh->num_hs_transfers; i++) + dwc2_hs_pmap_unschedule(hsotg, qh, i); + + if (qh->schedule_low_speed) + dwc2_ls_pmap_unschedule(hsotg, qh); + + dwc2_sch_dbg(hsotg, "QH=%p Unscheduled\n", qh); +} + +/** + * dwc2_pick_first_frame() - Choose 1st frame for qh that's already scheduled + * + * Takes a qh that has already been scheduled (which means we know we have the + * bandwdith reserved for us) and set the next_active_frame and the + * start_active_frame. + * + * This is expected to be called on qh's that weren't previously actively + * running. It just picks the next frame that we can fit into without any + * thought about the past. + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: QH for a periodic endpoint + * + */ +static void dwc2_pick_first_frame(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + u16 frame_number; + u16 earliest_frame; + u16 next_active_frame; + u16 relative_frame; + u16 interval; + + /* + * Use the real frame number rather than the cached value as of the + * last SOF to give us a little extra slop. + */ + frame_number = dwc2_hcd_get_frame_number(hsotg); + + /* + * We wouldn't want to start any earlier than the next frame just in + * case the frame number ticks as we're doing this calculation. + * + * NOTE: if we could quantify how long till we actually get scheduled + * we might be able to avoid the "+ 1" by looking at the upper part of + * HFNUM (the FRREM field). For now we'll just use the + 1 though. + */ + earliest_frame = dwc2_frame_num_inc(frame_number, 1); + next_active_frame = earliest_frame; + + /* Get the "no microframe schduler" out of the way... */ + if (!hsotg->params.uframe_sched) { + if (qh->do_split) + /* Splits are active at microframe 0 minus 1 */ + next_active_frame |= 0x7; + goto exit; + } + + if (qh->dev_speed == USB_SPEED_HIGH || qh->do_split) { + /* + * We're either at high speed or we're doing a split (which + * means we're talking high speed to a hub). In any case + * the first frame should be based on when the first scheduled + * event is. + */ + WARN_ON(qh->num_hs_transfers < 1); + + relative_frame = qh->hs_transfers[0].start_schedule_us / + DWC2_HS_PERIODIC_US_PER_UFRAME; + + /* Adjust interval as per high speed schedule */ + interval = gcd(qh->host_interval, DWC2_HS_SCHEDULE_UFRAMES); + + } else { + /* + * Low or full speed directly on dwc2. Just about the same + * as high speed but on a different schedule and with slightly + * different adjustments. Note that this works because when + * the host and device are both low speed then frames in the + * controller tick at low speed. + */ + relative_frame = qh->ls_start_schedule_slice / + DWC2_LS_PERIODIC_SLICES_PER_FRAME; + interval = gcd(qh->host_interval, DWC2_LS_SCHEDULE_FRAMES); + } + + /* Scheduler messed up if frame is past interval */ + WARN_ON(relative_frame >= interval); + + /* + * We know interval must divide (HFNUM_MAX_FRNUM + 1) now that we've + * done the gcd(), so it's safe to move to the beginning of the current + * interval like this. + * + * After this we might be before earliest_frame, but don't worry, + * we'll fix it... + */ + next_active_frame = (next_active_frame / interval) * interval; + + /* + * Actually choose to start at the frame number we've been + * scheduled for. + */ + next_active_frame = dwc2_frame_num_inc(next_active_frame, + relative_frame); + + /* + * We actually need 1 frame before since the next_active_frame is + * the frame number we'll be put on the ready list and we won't be on + * the bus until 1 frame later. + */ + next_active_frame = dwc2_frame_num_dec(next_active_frame, 1); + + /* + * By now we might actually be before the earliest_frame. Let's move + * up intervals until we're not. + */ + while (dwc2_frame_num_gt(earliest_frame, next_active_frame)) + next_active_frame = dwc2_frame_num_inc(next_active_frame, + interval); + +exit: + qh->next_active_frame = next_active_frame; + qh->start_active_frame = next_active_frame; + + dwc2_sch_vdbg(hsotg, "QH=%p First fn=%04x nxt=%04x\n", + qh, frame_number, qh->next_active_frame); +} + +/** + * dwc2_do_reserve() - Make a periodic reservation + * + * Try to allocate space in the periodic schedule. Depending on parameters + * this might use the microframe scheduler or the dumb scheduler. + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: QH for the periodic transfer. + * + * Returns: 0 upon success; error upon failure. + */ +static int dwc2_do_reserve(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + int status; + + if (hsotg->params.uframe_sched) { + status = dwc2_uframe_schedule(hsotg, qh); + } else { + status = dwc2_periodic_channel_available(hsotg); + if (status) { + dev_info(hsotg->dev, + "%s: No host channel available for periodic transfer\n", + __func__); + return status; + } + + status = dwc2_check_periodic_bandwidth(hsotg, qh); + } + + if (status) { + dev_dbg(hsotg->dev, + "%s: Insufficient periodic bandwidth for periodic transfer\n", + __func__); + return status; + } + + if (!hsotg->params.uframe_sched) + /* Reserve periodic channel */ + hsotg->periodic_channels++; + + /* Update claimed usecs per (micro)frame */ + hsotg->periodic_usecs += qh->host_us; + + dwc2_pick_first_frame(hsotg, qh); + + return 0; +} + +/** + * dwc2_do_unreserve() - Actually release the periodic reservation + * + * This function actually releases the periodic bandwidth that was reserved + * by the given qh. + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: QH for the periodic transfer. + */ +static void dwc2_do_unreserve(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + MUTEX_ASSERT_LOCKED(&hsotg->lock); + + WARN_ON(!qh->unreserve_pending); + + /* No more unreserve pending--we're doing it */ + qh->unreserve_pending = false; + + if (WARN_ON(!list_empty(&qh->qh_list_entry))) + list_del_init(&qh->qh_list_entry); + + /* Update claimed usecs per (micro)frame */ + hsotg->periodic_usecs -= qh->host_us; + + if (hsotg->params.uframe_sched) { + dwc2_uframe_unschedule(hsotg, qh); + } else { + /* Release periodic channel reservation */ + hsotg->periodic_channels--; + } +} + +/** + * dwc2_unreserve_timer_fn() - Timer function to release periodic reservation + * + * According to the kernel doc for usb_submit_urb() (specifically the part about + * "Reserved Bandwidth Transfers"), we need to keep a reservation active as + * long as a device driver keeps submitting. Since we're using HCD_BH to give + * back the URB we need to give the driver a little bit of time before we + * release the reservation. This worker is called after the appropriate + * delay. + * + * @t: Address to a qh unreserve_work. + */ +static void dwc2_unreserve_timer_fn(void *arg) +{ + struct dwc2_qh *qh = arg; + struct dwc2_hsotg *hsotg = qh->hsotg; + unsigned long flags; + + /* + * Wait for the lock, or for us to be scheduled again. We + * could be scheduled again if: + * - We started executing but didn't get the lock yet. + * - A new reservation came in, but cancel didn't take effect + * because we already started executing. + * - The timer has been kicked again. + * In that case cancel and wait for the next call. + */ + while (!spin_trylock_irqsave(&hsotg->lock, flags)) { + if (timeout_pending(&qh->unreserve_timer)) + return; + } + + /* + * Might be no more unreserve pending if: + * - We started executing but didn't get the lock yet. + * - A new reservation came in, but cancel didn't take effect + * because we already started executing. + * + * We can't put this in the loop above because unreserve_pending needs + * to be accessed under lock, so we can only check it once we got the + * lock. + */ + if (qh->unreserve_pending) + dwc2_do_unreserve(hsotg, qh); + + spin_unlock_irqrestore(&hsotg->lock, flags); +} + +/** + * dwc2_check_max_xfer_size() - Checks that the max transfer size allowed in a + * host channel is large enough to handle the maximum data transfer in a single + * (micro)frame for a periodic transfer + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: QH for a periodic endpoint + * + * Return: 0 if successful, negative error code otherwise + */ +STATIC int dwc2_check_max_xfer_size(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) +{ + u32 max_xfer_size; + u32 max_channel_xfer_size; + int status = 0; + + max_xfer_size = qh->maxp * qh->maxp_mult; + max_channel_xfer_size = hsotg->params.max_transfer_size; + + if (max_xfer_size > max_channel_xfer_size) { + dev_err(hsotg->dev, + "%s: Periodic xfer length %d > max xfer length for channel %d\n", + __func__, max_xfer_size, max_channel_xfer_size); + status = -ENOSPC; + } + + return status; +} + +/** + * dwc2_schedule_periodic() - Schedules an interrupt or isochronous transfer in + * the periodic schedule + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: QH for the periodic transfer. The QH should already contain the + * scheduling information. + * + * Return: 0 if successful, negative error code otherwise + */ +STATIC int dwc2_schedule_periodic(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + int status; status = dwc2_check_max_xfer_size(hsotg, qh); if (status) { @@ -551,7 +1493,36 @@ STATIC int dwc2_schedule_periodic(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) return status; } - if (hsotg->core_params->dma_desc_enable > 0) + /* Cancel pending unreserve; if canceled OK, unreserve was pending */ + if (timeout_del(&qh->unreserve_timer)) + WARN_ON(!qh->unreserve_pending); + + /* + * Only need to reserve if there's not an unreserve pending, since if an + * unreserve is pending then by definition our old reservation is still + * valid. Unreserve might still be pending even if we didn't cancel if + * dwc2_unreserve_timer_fn() already started. Code in the timer handles + * that case. + */ + if (!qh->unreserve_pending) { + status = dwc2_do_reserve(hsotg, qh); + if (status) + return status; + } else { + /* + * It might have been a while, so make sure that frame_number + * is still good. Note: we could also try to use the similar + * dwc2_next_periodic_start() but that schedules much more + * tightly and we might need to hurry and queue things up. + */ + if (dwc2_frame_num_le(qh->next_active_frame, + hsotg->frame_number)) + dwc2_pick_first_frame(hsotg, qh); + } + + qh->unreserve_pending = 0; + + if (hsotg->params.dma_desc_enable) /* Don't rely on SOF and start in ready schedule */ list_add_tail(&qh->qh_list_entry, &hsotg->periodic_sched_ready); else @@ -559,14 +1530,7 @@ STATIC int dwc2_schedule_periodic(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) list_add_tail(&qh->qh_list_entry, &hsotg->periodic_sched_inactive); - if (hsotg->core_params->uframe_sched <= 0) - /* Reserve periodic channel */ - hsotg->periodic_channels++; - - /* Update claimed usecs per (micro)frame */ - hsotg->periodic_usecs += qh->usecs; - - return status; + return 0; } /** @@ -579,22 +1543,27 @@ STATIC int dwc2_schedule_periodic(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) STATIC void dwc2_deschedule_periodic(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) { - int i; - - list_del_init(&qh->qh_list_entry); + MUTEX_ASSERT_LOCKED(&hsotg->lock); - /* Update claimed usecs per (micro)frame */ - hsotg->periodic_usecs -= qh->usecs; + /* + * Schedule the unreserve to happen in a little bit. Cases here: + * - Unreserve worker might be sitting there waiting to grab the lock. + * In this case it will notice it's been schedule again and will + * quit. + * - Unreserve worker might not be scheduled. + * + * We should never already be scheduled since dwc2_schedule_periodic() + * should have canceled the scheduled unreserve timer (hence the + * warning on did_modify). + * + * We add + 1 to the timer to guarantee that at least 1 jiffy has + * passed (otherwise if the jiffy counter might tick right after we + * read it and we'll get no delay). + */ + timeout_add(&qh->unreserve_timer, DWC2_UNRESERVE_DELAY + 1); + qh->unreserve_pending = 1; - if (hsotg->core_params->uframe_sched > 0) { - for (i = 0; i < 8; i++) { - hsotg->frame_usecs[i] += qh->frame_usecs[i]; - qh->frame_usecs[i] = 0; - } - } else { - /* Release periodic channel reservation */ - hsotg->periodic_channels--; - } + list_del_init(&qh->qh_list_entry); } /** @@ -617,6 +1586,8 @@ STATIC void dwc2_deschedule_periodic(struct dwc2_hsotg *hsotg, * qh back to the "inactive" list, then queues transactions. * * @t: Pointer to wait_timer in a qh. + * + * Return: HRTIMER_NORESTART to not automatically restart this timer. */ STATIC void dwc2_wait_timer_fn(void *arg) { @@ -646,6 +1617,223 @@ STATIC void dwc2_wait_timer_fn(void *arg) spin_unlock_irqrestore(&hsotg->lock, flags); } +/** + * dwc2_qh_init() - Initializes a QH structure + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: The QH to init + * @urb: Holds the information about the device/endpoint needed to initialize + * the QH + * @mem_flags: Flags for allocating memory. + */ +STATIC void dwc2_qh_init(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, + struct dwc2_hcd_urb *urb, gfp_t mem_flags) +{ + int dev_speed = dwc2_host_get_speed(hsotg, urb->priv); + u8 ep_type = dwc2_hcd_get_pipe_type(&urb->pipe_info); + bool ep_is_in = !!dwc2_hcd_is_pipe_in(&urb->pipe_info); + bool ep_is_isoc = (ep_type == USB_ENDPOINT_XFER_ISOC); + bool ep_is_int = (ep_type == USB_ENDPOINT_XFER_INT); + u32 hprt = dwc2_readl(hsotg, HPRT0); + u32 prtspd = (hprt & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT; + bool do_split = (prtspd == HPRT0_SPD_HIGH_SPEED && + dev_speed != USB_SPEED_HIGH); + int maxp = dwc2_hcd_get_maxp(&urb->pipe_info); + int maxp_mult = dwc2_hcd_get_maxp_mult(&urb->pipe_info); + int bytecount = maxp_mult * maxp; + char *speed, *type; + + /* Initialize QH */ + qh->hsotg = hsotg; + timeout_set(&qh->unreserve_timer, dwc2_unreserve_timer_fn, qh); + timeout_set(&qh->wait_timer, dwc2_wait_timer_fn, qh); + qh->ep_type = ep_type; + qh->ep_is_in = ep_is_in; + + qh->data_toggle = DWC2_HC_PID_DATA0; + qh->maxp = maxp; + qh->maxp_mult = maxp_mult; + INIT_LIST_HEAD(&qh->qtd_list); + INIT_LIST_HEAD(&qh->qh_list_entry); + + qh->do_split = do_split; + qh->dev_speed = dev_speed; + + if (ep_is_int || ep_is_isoc) { + /* Compute scheduling parameters once and save them */ + int host_speed = do_split ? USB_SPEED_HIGH : dev_speed; + struct dwc2_tt *dwc_tt = dwc2_host_get_tt_info(hsotg, urb->priv, + mem_flags, + &qh->ttport); + int device_ns; + + qh->dwc_tt = dwc_tt; + + qh->host_us = NS_TO_US(dwc2_usb_calc_bus_time(host_speed, + ep_is_in, ep_is_isoc, bytecount)); + device_ns = dwc2_usb_calc_bus_time(dev_speed, ep_is_in, + ep_is_isoc, bytecount); + + if (do_split && dwc_tt) + device_ns += dwc2_ttthink_to_ns(hsotg, urb->priv, + dwc_tt->usb_tt->hub->ttthink); + qh->device_us = NS_TO_US(device_ns); + + qh->device_interval = urb->interval; + qh->host_interval = urb->interval * (do_split ? 8 : 1); + + /* + * Schedule low speed if we're running the host in low or + * full speed OR if we've got a "TT" to deal with to access this + * device. + */ + qh->schedule_low_speed = prtspd != HPRT0_SPD_HIGH_SPEED || + dwc_tt; + + if (do_split) { + /* We won't know num transfers until we schedule */ + qh->num_hs_transfers = -1; + } else if (dev_speed == USB_SPEED_HIGH) { + qh->num_hs_transfers = 1; + } else { + qh->num_hs_transfers = 0; + } + + /* We'll schedule later when we have something to do */ + } + + switch (dev_speed) { + case USB_SPEED_LOW: + speed = "low"; + break; + case USB_SPEED_FULL: + speed = "full"; + break; + case USB_SPEED_HIGH: + speed = "high"; + break; + default: + speed = "?"; + break; + } + + switch (qh->ep_type) { + case USB_ENDPOINT_XFER_ISOC: + type = "isochronous"; + break; + case USB_ENDPOINT_XFER_INT: + type = "interrupt"; + break; + case USB_ENDPOINT_XFER_CONTROL: + type = "control"; + break; + case USB_ENDPOINT_XFER_BULK: + type = "bulk"; + break; + default: + type = "?"; + break; + } + + dwc2_sch_dbg(hsotg, "QH=%p Init %s, %s speed, %d bytes:\n", qh, type, + speed, bytecount); + dwc2_sch_dbg(hsotg, "QH=%p ...addr=%d, ep=%d, %s\n", qh, + dwc2_hcd_get_dev_addr(&urb->pipe_info), + dwc2_hcd_get_ep_num(&urb->pipe_info), + ep_is_in ? "IN" : "OUT"); + if (ep_is_int || ep_is_isoc) { + dwc2_sch_dbg(hsotg, + "QH=%p ...duration: host=%d us, device=%d us\n", + qh, qh->host_us, qh->device_us); + dwc2_sch_dbg(hsotg, "QH=%p ...interval: host=%d, device=%d\n", + qh, qh->host_interval, qh->device_interval); + if (qh->schedule_low_speed) + dwc2_sch_dbg(hsotg, "QH=%p ...low speed schedule=%p\n", + qh, dwc2_get_ls_map(hsotg, qh)); + } +} + +/** + * dwc2_hcd_qh_create() - Allocates and initializes a QH + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @urb: Holds the information about the device/endpoint needed + * to initialize the QH + * @mem_flags: Flags for allocating memory. + * + * Return: Pointer to the newly allocated QH, or NULL on error + */ +struct dwc2_qh *dwc2_hcd_qh_create(struct dwc2_hsotg *hsotg, + struct dwc2_hcd_urb *urb, + gfp_t mem_flags) +{ + struct dwc2_softc *sc = hsotg->hsotg_sc; + struct dwc2_qh *qh; + + if (!urb->priv) + return NULL; + + /* Allocate memory */ + qh = pool_get(&sc->sc_qhpool, PR_ZERO | PR_NOWAIT); + if (!qh) + return NULL; + + dwc2_qh_init(hsotg, qh, urb, mem_flags); + + if (hsotg->params.dma_desc_enable && + dwc2_hcd_qh_init_ddma(hsotg, qh, mem_flags) < 0) { + dwc2_hcd_qh_free(hsotg, qh); + return NULL; + } + + return qh; +} + +/** + * dwc2_hcd_qh_free() - Frees the QH + * + * @hsotg: HCD instance + * @qh: The QH to free + * + * QH should already be removed from the list. QTD list should already be empty + * if called from URB Dequeue. + * + * Must NOT be called with interrupt disabled or spinlock held + */ +void dwc2_hcd_qh_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + struct dwc2_softc *sc = hsotg->hsotg_sc; + + /* Make sure any unreserve work is finished. */ + if (timeout_del(&qh->unreserve_timer)) { + unsigned long flags; + + spin_lock_irqsave(&hsotg->lock, flags); + dwc2_do_unreserve(hsotg, qh); + spin_unlock_irqrestore(&hsotg->lock, flags); + } + + /* + * We don't have the lock so we can safely wait until the wait timer + * finishes. Of course, at this point in time we'd better have set + * wait_timer_active to false so if this timer was still pending it + * won't do anything anyway, but we want it to finish before we free + * memory. + */ + timeout_del(&qh->wait_timer); + + dwc2_host_put_tt_info(hsotg, qh->dwc_tt); + + if (qh->desc_list) + dwc2_hcd_qh_free_ddma(hsotg, qh); + else if (hsotg->unaligned_cache && qh->dw_align_buf) { + usb_freemem(&sc->sc_bus, &qh->dw_align_buf_usbdma); + qh->dw_align_buf_dma = (dma_addr_t)0; + } + + pool_put(&sc->sc_qhpool, qh); +} + /** * dwc2_hcd_qh_add() - Adds a QH to either the non periodic or periodic * schedule if it is not already in the schedule. If the QH is already in @@ -668,22 +1856,16 @@ int dwc2_hcd_qh_add(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) /* QH already in a schedule */ return 0; - if (!dwc2_frame_num_le(qh->sched_frame, hsotg->frame_number) && - !hsotg->frame_number) { - dev_dbg(hsotg->dev, - "reset frame number counter\n"); - qh->sched_frame = dwc2_frame_num_inc(hsotg->frame_number, - SCHEDULE_SLOP); - } - /* Add the new QH to the appropriate schedule */ if (dwc2_qh_is_non_per(qh)) { + /* Schedule right away */ + qh->start_active_frame = hsotg->frame_number; + qh->next_active_frame = qh->start_active_frame; + if (qh->want_wait) { list_add_tail(&qh->qh_list_entry, &hsotg->non_periodic_sched_waiting); qh->wait_timer_cancel = false; - /* XXX mod_timer(&qh->wait_timer, - jiffies + DWC2_RETRY_WAIT_DELAY + 1); */ timeout_add_msec(&qh->wait_timer, DWC2_RETRY_WAIT_DELAY); } else { @@ -697,9 +1879,9 @@ int dwc2_hcd_qh_add(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) if (status) return status; if (!hsotg->periodic_qh_count) { - intr_mask = DWC2_READ_4(hsotg, GINTMSK); + intr_mask = dwc2_readl(hsotg, GINTMSK); intr_mask |= GINTSTS_SOF; - DWC2_WRITE_4(hsotg, GINTMSK, intr_mask); + dwc2_writel(hsotg, intr_mask, GINTMSK); } hsotg->periodic_qh_count++; @@ -736,46 +1918,172 @@ void dwc2_hcd_qh_unlink(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) dwc2_deschedule_periodic(hsotg, qh); hsotg->periodic_qh_count--; - if (!hsotg->periodic_qh_count) { - intr_mask = DWC2_READ_4(hsotg, GINTMSK); + if (!hsotg->periodic_qh_count && + !hsotg->params.dma_desc_enable) { + intr_mask = dwc2_readl(hsotg, GINTMSK); intr_mask &= ~GINTSTS_SOF; - DWC2_WRITE_4(hsotg, GINTMSK, intr_mask); + dwc2_writel(hsotg, intr_mask, GINTMSK); } } -/* - * Schedule the next continuing periodic split transfer +/** + * dwc2_next_for_periodic_split() - Set next_active_frame midway thru a split. + * + * This is called for setting next_active_frame for periodic splits for all but + * the first packet of the split. Confusing? I thought so... + * + * Periodic splits are single low/full speed transfers that we end up splitting + * up into several high speed transfers. They always fit into one full (1 ms) + * frame but might be split over several microframes (125 us each). We to put + * each of the parts on a very specific high speed frame. + * + * This function figures out where the next active uFrame needs to be. + * + * @hsotg: The HCD state structure + * @qh: QH for the periodic transfer. + * @frame_number: The current frame number. + * + * Return: number missed by (or 0 if we didn't miss). */ -STATIC void dwc2_sched_periodic_split(struct dwc2_hsotg *hsotg, - struct dwc2_qh *qh, u16 frame_number, - int sched_next_periodic_split) +static int dwc2_next_for_periodic_split(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh, u16 frame_number) { + u16 old_frame = qh->next_active_frame; + u16 prev_frame_number = dwc2_frame_num_dec(frame_number, 1); + int missed = 0; u16 incr; - if (sched_next_periodic_split) { - qh->sched_frame = frame_number; - incr = dwc2_frame_num_inc(qh->start_split_frame, 1); - if (dwc2_frame_num_le(frame_number, incr)) { - /* - * Allow one frame to elapse after start split - * microframe before scheduling complete split, but - * DON'T if we are doing the next start split in the - * same frame for an ISOC out - */ - if (qh->ep_type != USB_ENDPOINT_XFER_ISOC || - qh->ep_is_in != 0) { - qh->sched_frame = - dwc2_frame_num_inc(qh->sched_frame, 1); - } - } - } else { - qh->sched_frame = dwc2_frame_num_inc(qh->start_split_frame, - qh->interval); - if (dwc2_frame_num_le(qh->sched_frame, frame_number)) - qh->sched_frame = frame_number; - qh->sched_frame |= 0x7; - qh->start_split_frame = qh->sched_frame; + /* + * See dwc2_uframe_schedule_split() for split scheduling. + * + * Basically: increment 1 normally, but 2 right after the start split + * (except for ISOC out). + */ + if (old_frame == qh->start_active_frame && + !(qh->ep_type == USB_ENDPOINT_XFER_ISOC && !qh->ep_is_in)) + incr = 2; + else + incr = 1; + + qh->next_active_frame = dwc2_frame_num_inc(old_frame, incr); + + /* + * Note that it's OK for frame_number to be 1 frame past + * next_active_frame. Remember that next_active_frame is supposed to + * be 1 frame _before_ when we want to be scheduled. If we're 1 frame + * past it just means schedule ASAP. + * + * It's _not_ OK, however, if we're more than one frame past. + */ + if (dwc2_frame_num_gt(prev_frame_number, qh->next_active_frame)) { + /* + * OOPS, we missed. That's actually pretty bad since + * the hub will be unhappy; try ASAP I guess. + */ + missed = dwc2_frame_num_dec(prev_frame_number, + qh->next_active_frame); + qh->next_active_frame = frame_number; + } + + return missed; +} + +/** + * dwc2_next_periodic_start() - Set next_active_frame for next transfer start + * + * This is called for setting next_active_frame for a periodic transfer for + * all cases other than midway through a periodic split. This will also update + * start_active_frame. + * + * Since we _always_ keep start_active_frame as the start of the previous + * transfer this is normally pretty easy: we just add our interval to + * start_active_frame and we've got our answer. + * + * The tricks come into play if we miss. In that case we'll look for the next + * slot we can fit into. + * + * @hsotg: The HCD state structure + * @qh: QH for the periodic transfer. + * @frame_number: The current frame number. + * + * Return: number missed by (or 0 if we didn't miss). + */ +static int dwc2_next_periodic_start(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh, u16 frame_number) +{ + int missed = 0; + u16 interval = qh->host_interval; + u16 prev_frame_number = dwc2_frame_num_dec(frame_number, 1); + + qh->start_active_frame = dwc2_frame_num_inc(qh->start_active_frame, + interval); + + /* + * The dwc2_frame_num_gt() function used below won't work terribly well + * with if we just incremented by a really large intervals since the + * frame counter only goes to 0x3fff. It's terribly unlikely that we + * will have missed in this case anyway. Just go to exit. If we want + * to try to do better we'll need to keep track of a bigger counter + * somewhere in the driver and handle overflows. + */ + if (interval >= 0x1000) + goto exit; + + /* + * Test for misses, which is when it's too late to schedule. + * + * A few things to note: + * - We compare against prev_frame_number since start_active_frame + * and next_active_frame are always 1 frame before we want things + * to be active and we assume we can still get scheduled in the + * current frame number. + * - It's possible for start_active_frame (now incremented) to be + * next_active_frame if we got an EO MISS (even_odd miss) which + * basically means that we detected there wasn't enough time for + * the last packet and dwc2_hc_set_even_odd_frame() rescheduled us + * at the last second. We want to make sure we don't schedule + * another transfer for the same frame. My test webcam doesn't seem + * terribly upset by missing a transfer but really doesn't like when + * we do two transfers in the same frame. + * - Some misses are expected. Specifically, in order to work + * perfectly dwc2 really needs quite spectacular interrupt latency + * requirements. It needs to be able to handle its interrupts + * completely within 125 us of them being asserted. That not only + * means that the dwc2 interrupt handler needs to be fast but it + * means that nothing else in the system has to block dwc2 for a long + * time. We can help with the dwc2 parts of this, but it's hard to + * guarantee that a system will have interrupt latency < 125 us, so + * we have to be robust to some misses. + */ + if (qh->start_active_frame == qh->next_active_frame || + dwc2_frame_num_gt(prev_frame_number, qh->start_active_frame)) { + u16 ideal_start = qh->start_active_frame; + int periods_in_map; + + /* + * Adjust interval as per gcd with map size. + * See pmap_schedule() for more details here. + */ + if (qh->do_split || qh->dev_speed == USB_SPEED_HIGH) + periods_in_map = DWC2_HS_SCHEDULE_UFRAMES; + else + periods_in_map = DWC2_LS_SCHEDULE_FRAMES; + interval = gcd(interval, periods_in_map); + + do { + qh->start_active_frame = dwc2_frame_num_inc( + qh->start_active_frame, interval); + } while (dwc2_frame_num_gt(prev_frame_number, + qh->start_active_frame)); + + missed = dwc2_frame_num_dec(qh->start_active_frame, + ideal_start); } + +exit: + qh->next_active_frame = qh->start_active_frame; + + return missed; } /* @@ -794,7 +2102,11 @@ STATIC void dwc2_sched_periodic_split(struct dwc2_hsotg *hsotg, void dwc2_hcd_qh_deactivate(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, int sched_next_periodic_split) { +#ifdef DWC2_DEBUG + u16 old_frame = qh->next_active_frame; +#endif u16 frame_number; + int missed; if (dbg_qh(qh)) dev_vdbg(hsotg->dev, "%s()\n", __func__); @@ -807,33 +2119,44 @@ void dwc2_hcd_qh_deactivate(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, return; } + /* + * Use the real frame number rather than the cached value as of the + * last SOF just to get us a little closer to reality. Note that + * means we don't actually know if we've already handled the SOF + * interrupt for this frame. + */ frame_number = dwc2_hcd_get_frame_number(hsotg); - if (qh->do_split) { - dwc2_sched_periodic_split(hsotg, qh, frame_number, - sched_next_periodic_split); - } else { - qh->sched_frame = dwc2_frame_num_inc(qh->sched_frame, - qh->interval); - if (dwc2_frame_num_le(qh->sched_frame, frame_number)) - qh->sched_frame = frame_number; - } + if (sched_next_periodic_split) + missed = dwc2_next_for_periodic_split(hsotg, qh, frame_number); + else + missed = dwc2_next_periodic_start(hsotg, qh, frame_number); + + dwc2_sch_vdbg(hsotg, + "QH=%p next(%d) fn=%04x, sch=%04x=>%04x (%+d) miss=%d %s\n", + qh, sched_next_periodic_split, frame_number, old_frame, + qh->next_active_frame, + dwc2_frame_num_dec(qh->next_active_frame, old_frame), + missed, missed ? "MISS" : ""); if (list_empty(&qh->qtd_list)) { dwc2_hcd_qh_unlink(hsotg, qh); return; } + /* * Remove from periodic_sched_queued and move to * appropriate queue + * + * Note: we purposely use the frame_number from the "hsotg" structure + * since we know SOF interrupt will handle future frames. */ - if ((hsotg->core_params->uframe_sched > 0 && - dwc2_frame_num_le(qh->sched_frame, frame_number)) || - (hsotg->core_params->uframe_sched <= 0 && - qh->sched_frame == frame_number)) - list_move(&qh->qh_list_entry, &hsotg->periodic_sched_ready); + if (dwc2_frame_num_le(qh->next_active_frame, hsotg->frame_number)) + list_move_tail(&qh->qh_list_entry, + &hsotg->periodic_sched_ready); else - list_move(&qh->qh_list_entry, &hsotg->periodic_sched_inactive); + list_move_tail(&qh->qh_list_entry, + &hsotg->periodic_sched_inactive); } /** @@ -882,11 +2205,9 @@ void dwc2_hcd_qtd_init(struct dwc2_qtd *qtd, struct dwc2_hcd_urb *urb) int dwc2_hcd_qtd_add(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, struct dwc2_qh *qh) { - - MUTEX_ASSERT_LOCKED(&hsotg->lock); int retval; - if (!qh) { + if (unlikely(!qh)) { dev_err(hsotg->dev, "%s: Invalid QH\n", __func__); retval = -EINVAL; goto fail; @@ -904,74 +2225,78 @@ fail: return retval; } -void dwc2_hcd_qtd_unlink_and_free(struct dwc2_hsotg *hsotg, - struct dwc2_qtd *qtd, - struct dwc2_qh *qh) -{ - struct dwc2_softc *sc = hsotg->hsotg_sc; - - list_del_init(&qtd->qtd_list_entry); - pool_put(&sc->sc_qtdpool, qtd); -} +/*** XXX: Include following functions in to our USB stack? *******************/ -#define BITSTUFFTIME(bytecount) ((8 * 7 * (bytecount)) / 6) -#define HS_HOST_DELAY 5 /* nanoseconds */ -#define FS_LS_HOST_DELAY 1000 /* nanoseconds */ -#define HUB_LS_SETUP 333 /* nanoseconds */ +#define BW_HUB_LS_SETUP 333L /* nanoseconds */ +#define BW_HOST_DELAY 1000L /* nanoseconds */ -STATIC u32 dwc2_calc_bus_time(struct dwc2_hsotg *hsotg, int speed, int is_in, - int is_isoc, int bytecount) +long +dwc2_usb_calc_bus_time(int speed, int is_input, int isoc, int bytecount) { - unsigned long retval; + unsigned long tmp; switch (speed) { - case USB_SPEED_HIGH: - if (is_isoc) - retval = - ((38 * 8 * 2083) + - (2083 * (3 + BITSTUFFTIME(bytecount)))) / 1000 + - HS_HOST_DELAY; - else - retval = - ((55 * 8 * 2083) + - (2083 * (3 + BITSTUFFTIME(bytecount)))) / 1000 + - HS_HOST_DELAY; - break; - case USB_SPEED_FULL: - if (is_isoc) { - retval = - (8354 * (31 + 10 * BITSTUFFTIME(bytecount))) / 1000; - if (is_in) - retval = 7268 + FS_LS_HOST_DELAY + retval; - else - retval = 6265 + FS_LS_HOST_DELAY + retval; + case USB_SPEED_LOW: /* INTR only */ + if (is_input) { + tmp = (67667L * (31L + 10L * BitTime (bytecount))) / + 1000L; + return 64060L + (2 * BW_HUB_LS_SETUP) + BW_HOST_DELAY + + tmp; } else { - retval = - (8354 * (31 + 10 * BITSTUFFTIME(bytecount))) / 1000; - retval = 9107 + FS_LS_HOST_DELAY + retval; + tmp = (66700L * (31L + 10L * BitTime (bytecount))) / + 1000L; + return 64107L + (2 * BW_HUB_LS_SETUP) + BW_HOST_DELAY + + tmp; } - break; - case USB_SPEED_LOW: - if (is_in) { - retval = - (67667 * (31 + 10 * BITSTUFFTIME(bytecount))) / - 1000; - retval = - 64060 + (2 * HUB_LS_SETUP) + FS_LS_HOST_DELAY + - retval; + case USB_SPEED_FULL: /* ISOC or INTR */ + if (isoc) { + tmp = (8354L * (31L + 10L * BitTime (bytecount))) / + 1000L; + return ((is_input) ? 7268L : 6265L) + BW_HOST_DELAY + + tmp; } else { - retval = - (66700 * (31 + 10 * BITSTUFFTIME(bytecount))) / - 1000; - retval = - 64107 + (2 * HUB_LS_SETUP) + FS_LS_HOST_DELAY + - retval; + tmp = (8354L * (31L + 10L * BitTime (bytecount))) / + 1000L; + return 9107L + BW_HOST_DELAY + tmp; } - break; + case USB_SPEED_HIGH: /* ISOC or INTR */ + /* FIXME adjust for input vs output */ + if (isoc) + tmp = HS_NSECS_ISO (bytecount); + else + tmp = HS_NSECS (bytecount); + return tmp; default: - dev_warn(hsotg->dev, "Unknown device speed\n"); - retval = -1; + printf ("%s: bogus device speed!\n", __func__); + return -1; + } +} + +int +dwc2_ttthink_to_ns(struct dwc2_hsotg *hsotg, void *context, int ttthink) +{ + struct usbd_xfer *xfer = context; + struct dwc2_pipe *dpipe = DWC2_XFER2DPIPE(xfer); + struct usbd_device *dev = dpipe->pipe.device; + + /* 8 FS bit times == (8 bits / 12000000 bps) ~= 666ns */ + switch (ttthink) { + case UHD_TT_THINK_8: + if (dev->ddesc.bDeviceProtocol != 0) + return 666; + else + return 0; + case UHD_TT_THINK_16: + return 666 * 2; + case UHD_TT_THINK_24: + return 666 * 3; + case UHD_TT_THINK_32: + return 666 * 4; + default: + dev_dbg(hsotg->dev, "%s: Invalid TT Think Time (0x%04x)!\n", + __func__, ttthink); + break; } - return NS_TO_US(retval); + return 0; } diff --git a/sys/dev/usb/dwc2/dwc2_hw.h b/sys/dev/usb/dwc2/dwc2_hw.h index 05f53b6f027..a551c2db5fa 100644 --- a/sys/dev/usb/dwc2/dwc2_hw.h +++ b/sys/dev/usb/dwc2/dwc2_hw.h @@ -1,4 +1,4 @@ -/* $OpenBSD: dwc2_hw.h,v 1.3 2021/07/22 18:32:33 mglocker Exp $ */ +/* $OpenBSD: dwc2_hw.h,v 1.4 2022/09/04 08:42:40 mglocker Exp $ */ /* $NetBSD: dwc2_hw.h,v 1.2 2013/09/25 06:19:22 skrll Exp $ */ /* @@ -46,15 +46,23 @@ #define GOTGCTL_CHIRPEN (1 << 27) #define GOTGCTL_MULT_VALID_BC_MASK (0x1f << 22) #define GOTGCTL_MULT_VALID_BC_SHIFT 22 +#define GOTGCTL_CURMODE_HOST (1 << 21) #define GOTGCTL_OTGVER (1 << 20) #define GOTGCTL_BSESVLD (1 << 19) #define GOTGCTL_ASESVLD (1 << 18) #define GOTGCTL_DBNC_SHORT (1 << 17) #define GOTGCTL_CONID_B (1 << 16) +#define GOTGCTL_DBNCE_FLTR_BYPASS (1 << 15) #define GOTGCTL_DEVHNPEN (1 << 11) #define GOTGCTL_HSTSETHNPEN (1 << 10) #define GOTGCTL_HNPREQ (1 << 9) #define GOTGCTL_HSTNEGSCS (1 << 8) +#define GOTGCTL_BVALOVAL (1 << 7) +#define GOTGCTL_BVALOEN (1 << 6) +#define GOTGCTL_AVALOVAL (1 << 5) +#define GOTGCTL_AVALOEN (1 << 4) +#define GOTGCTL_VBVALOVAL (1 << 3) +#define GOTGCTL_VBVALOEN (1 << 2) #define GOTGCTL_SESREQ (1 << 1) #define GOTGCTL_SESREQSCS (1 << 0) @@ -121,6 +129,7 @@ #define GRSTCTL HSOTG_REG(0x010) #define GRSTCTL_AHBIDLE (1 << 31) #define GRSTCTL_DMAREQ (1 << 30) +#define GRSTCTL_CSFTRST_DONE (1 << 29) #define GRSTCTL_TXFNUM_MASK (0x1f << 6) #define GRSTCTL_TXFNUM_SHIFT 6 #define GRSTCTL_TXFNUM_LIMIT 0x1f @@ -227,9 +236,14 @@ #define GPVNDCTL HSOTG_REG(0x0034) #define GGPIO HSOTG_REG(0x0038) +#define GGPIO_STM32_OTG_GCCFG_PWRDWN (1 << 16) +#define GGPIO_STM32_OTG_GCCFG_VBDEN (1 << 21) +#define GGPIO_STM32_OTG_GCCFG_IDEN (1 << 22) + #define GUID HSOTG_REG(0x003c) #define GSNPSID HSOTG_REG(0x0040) #define GHWCFG1 HSOTG_REG(0x0044) +#define GSNPSID_ID_MASK 0xffff0000 #define GHWCFG2 HSOTG_REG(0x0048) #define GHWCFG2_OTG_ENABLE_IC_USB (1 << 31) @@ -311,6 +325,9 @@ #define GHWCFG4_UTMI_PHY_DATA_WIDTH_8 0 #define GHWCFG4_UTMI_PHY_DATA_WIDTH_16 1 #define GHWCFG4_UTMI_PHY_DATA_WIDTH_8_OR_16 2 +#define GHWCFG4_ACG_SUPPORTED (1 << 12) +#define GHWCFG4_IPG_ISOC_SUPPORTED (1 << 11) +#define GHWCFG4_SERVICE_INTERVAL_SUPPORTED (1 << 10) #define GHWCFG4_XHIBER (1 << 7) #define GHWCFG4_HIBER (1 << 6) #define GHWCFG4_MIN_AHB_FREQ (1 << 5) @@ -319,28 +336,32 @@ #define GHWCFG4_NUM_DEV_PERIO_IN_EP_SHIFT 0 #define GLPMCFG HSOTG_REG(0x0054) -#define GLPMCFG_INV_SEL_HSIC (1 << 31) -#define GLPMCFG_HSIC_CONNECT (1 << 30) +#define GLPMCFG_INVSELHSIC (1 << 31) +#define GLPMCFG_HSICCON (1 << 30) +#define GLPMCFG_RSTRSLPSTS (1 << 29) +#define GLPMCFG_ENBESL (1 << 28) #define GLPMCFG_RETRY_COUNT_STS_MASK (0x7 << 25) #define GLPMCFG_RETRY_COUNT_STS_SHIFT 25 -#define GLPMCFG_SEND_LPM (1 << 24) -#define GLPMCFG_RETRY_COUNT_MASK (0x7 << 21) -#define GLPMCFG_RETRY_COUNT_SHIFT 21 +#define GLPMCFG_SNDLPM (1 << 24) +#define GLPMCFG_RETRY_CNT_MASK (0x7 << 21) +#define GLPMCFG_RETRY_CNT_SHIFT 21 +#define GLPMCFG_LPM_REJECT_CTRL_CONTROL (1 << 21) +#define GLPMCFG_LPM_ACCEPT_CTRL_ISOC (1 << 22) #define GLPMCFG_LPM_CHAN_INDEX_MASK (0xf << 17) #define GLPMCFG_LPM_CHAN_INDEX_SHIFT 17 -#define GLPMCFG_SLEEP_STATE_RESUMEOK (1 << 16) -#define GLPMCFG_PRT_SLEEP_STS (1 << 15) -#define GLPMCFG_LPM_RESP_MASK (0x3 << 13) -#define GLPMCFG_LPM_RESP_SHIFT 13 +#define GLPMCFG_L1RESUMEOK (1 << 16) +#define GLPMCFG_SLPSTS (1 << 15) +#define GLPMCFG_COREL1RES_MASK (0x3 << 13) +#define GLPMCFG_COREL1RES_SHIFT 13 #define GLPMCFG_HIRD_THRES_MASK (0x1f << 8) #define GLPMCFG_HIRD_THRES_SHIFT 8 -#define GLPMCFG_HIRD_THRES_EN (0x10 << 8) -#define GLPMCFG_EN_UTMI_SLEEP (1 << 7) -#define GLPMCFG_REM_WKUP_EN (1 << 6) +#define GLPMCFG_HIRD_THRES_EN (0x10 << 8) +#define GLPMCFG_ENBLSLPM (1 << 7) +#define GLPMCFG_BREMOTEWAKE (1 << 6) #define GLPMCFG_HIRD_MASK (0xf << 2) #define GLPMCFG_HIRD_SHIFT 2 -#define GLPMCFG_APPL_RESP (1 << 1) -#define GLPMCFG_LPM_CAP_EN (1 << 0) +#define GLPMCFG_APPL1RES (1 << 1) +#define GLPMCFG_LPMCAP (1 << 0) #define GPWRDN HSOTG_REG(0x0058) #define GPWRDN_MULT_VAL_ID_BC_MASK (0x1f << 24) @@ -398,6 +419,19 @@ #define ADPCTL_PRB_DSCHRG_MASK (0x3 << 0) #define ADPCTL_PRB_DSCHRG_SHIFT 0 +#define GREFCLK HSOTG_REG(0x0064) +#define GREFCLK_REFCLKPER_MASK (0x1ffff << 15) +#define GREFCLK_REFCLKPER_SHIFT 15 +#define GREFCLK_REF_CLK_MODE (1 << 14) +#define GREFCLK_SOF_CNT_WKUP_ALERT_MASK (0x3ff) +#define GREFCLK_SOF_CNT_WKUP_ALERT_SHIFT 0 + +#define GINTMSK2 HSOTG_REG(0x0068) +#define GINTMSK2_WKUP_ALERT_INT_MSK (1 << 0) + +#define GINTSTS2 HSOTG_REG(0x006c) +#define GINTSTS2_WKUP_ALERT_INT (1 << 0) + #define HPTXFSIZ HSOTG_REG(0x100) /* Use FIFOSIZE_* constants to access this register */ @@ -414,10 +448,12 @@ /* Device mode registers */ #define DCFG HSOTG_REG(0x800) +#define DCFG_DESCDMA_EN (1 << 23) #define DCFG_EPMISCNT_MASK (0x1f << 18) #define DCFG_EPMISCNT_SHIFT 18 #define DCFG_EPMISCNT_LIMIT 0x1f #define DCFG_EPMISCNT(_x) ((_x) << 18) +#define DCFG_IPG_ISOC_SUPPORDED (1 << 17) #define DCFG_PERFRINT_MASK (0x3 << 11) #define DCFG_PERFRINT_SHIFT 11 #define DCFG_PERFRINT_LIMIT 0x3 @@ -435,6 +471,7 @@ #define DCFG_DEVSPD_FS48 3 #define DCTL HSOTG_REG(0x804) +#define DCTL_SERVICE_INTERVAL_SUPPORTED (1 << 19) #define DCTL_PWRONPRGDONE (1 << 11) #define DCTL_CGOUTNAK (1 << 10) #define DCTL_SGOUTNAK (1 << 9) @@ -462,6 +499,9 @@ #define DSTS_SUSPSTS (1 << 0) #define DIEPMSK HSOTG_REG(0x810) +#define DIEPMSK_NAKMSK (1 << 13) +#define DIEPMSK_BNAININTRMSK (1 << 9) +#define DIEPMSK_TXFIFOUNDRNMSK (1 << 8) #define DIEPMSK_TXFIFOEMPTY (1 << 7) #define DIEPMSK_INEPNAKEFFMSK (1 << 6) #define DIEPMSK_INTKNEPMISMSK (1 << 5) @@ -472,7 +512,9 @@ #define DIEPMSK_XFERCOMPLMSK (1 << 0) #define DOEPMSK HSOTG_REG(0x814) +#define DOEPMSK_BNAMSK (1 << 9) #define DOEPMSK_BACK2BACKSETUP (1 << 6) +#define DOEPMSK_STSPHSERCVDMSK (1 << 5) #define DOEPMSK_OUTTKNEPDISMSK (1 << 4) #define DOEPMSK_SETUPMSK (1 << 3) #define DOEPMSK_AHBERRMSK (1 << 2) @@ -489,6 +531,7 @@ #define DTKNQR2 HSOTG_REG(0x824) #define DTKNQR3 HSOTG_REG(0x830) #define DTKNQR4 HSOTG_REG(0x834) +#define DIEPEMPMSK HSOTG_REG(0x834) #define DVBUSDIS HSOTG_REG(0x828) #define DVBUSPULSE HSOTG_REG(0x82C) @@ -547,9 +590,18 @@ #define DIEPINT(_a) HSOTG_REG(0x908 + ((_a) * 0x20)) #define DOEPINT(_a) HSOTG_REG(0xB08 + ((_a) * 0x20)) #define DXEPINT_SETUP_RCVD (1 << 15) +#define DXEPINT_NYETINTRPT (1 << 14) +#define DXEPINT_NAKINTRPT (1 << 13) +#define DXEPINT_BBLEERRINTRPT (1 << 12) +#define DXEPINT_PKTDRPSTS (1 << 11) +#define DXEPINT_BNAINTR (1 << 9) +#define DXEPINT_TXFIFOUNDRN (1 << 8) +#define DXEPINT_OUTPKTERR (1 << 8) +#define DXEPINT_TXFEMP (1 << 7) #define DXEPINT_INEPNAKEFF (1 << 6) #define DXEPINT_BACK2BACKSETUP (1 << 6) #define DXEPINT_INTKNEPMIS (1 << 5) +#define DXEPINT_STSPHSERCVD (1 << 5) #define DXEPINT_INTKNTXFEMP (1 << 4) #define DXEPINT_OUTTKNEPDIS (1 << 4) #define DXEPINT_TIMEOUT (1 << 3) @@ -627,6 +679,10 @@ #define PCGCTL_GATEHCLK (1 << 1) #define PCGCTL_STOPPCLK (1 << 0) +#define PCGCCTL1 HSOTG_REG(0xe04) +#define PCGCCTL1_TIMER (0x3 << 1) +#define PCGCCTL1_GATEEN (1 << 0) + #define EPFIFO(_a) HSOTG_REG(0x1000 + ((_a) * 0x1000)) /* Host Mode Registers */ @@ -778,7 +834,8 @@ #define HCFIFO(_ch) HSOTG_REG(0x1000 + 0x1000 * (_ch)) /** - * struct dwc2_hcd_dma_desc - Host-mode DMA descriptor structure + * struct dwc2_dma_desc - DMA descriptor structure, + * used for both host and gadget modes * * @status: DMA descriptor status quadlet * @buf: DMA descriptor data buffer pointer @@ -786,10 +843,12 @@ * DMA Descriptor structure contains two quadlets: * Status quadlet and Data buffer pointer. */ -struct dwc2_hcd_dma_desc { +struct dwc2_dma_desc { u32 status; u32 buf; -}; +} __packed; + +/* Host Mode DMA descriptor status quadlet */ #define HOST_DMA_A (1 << 31) #define HOST_DMA_STS_MASK (0x3 << 28) @@ -805,8 +864,43 @@ struct dwc2_hcd_dma_desc { #define HOST_DMA_ISOC_NBYTES_SHIFT 0 #define HOST_DMA_NBYTES_MASK (0x1ffff << 0) #define HOST_DMA_NBYTES_SHIFT 0 +#define HOST_DMA_NBYTES_LIMIT 131071 + +/* Device Mode DMA descriptor status quadlet */ + +#define DEV_DMA_BUFF_STS_MASK (0x3 << 30) +#define DEV_DMA_BUFF_STS_SHIFT 30 +#define DEV_DMA_BUFF_STS_HREADY 0 +#define DEV_DMA_BUFF_STS_DMABUSY 1 +#define DEV_DMA_BUFF_STS_DMADONE 2 +#define DEV_DMA_BUFF_STS_HBUSY 3 +#define DEV_DMA_STS_MASK (0x3 << 28) +#define DEV_DMA_STS_SHIFT 28 +#define DEV_DMA_STS_SUCC 0 +#define DEV_DMA_STS_BUFF_FLUSH 1 +#define DEV_DMA_STS_BUFF_ERR 3 +#define DEV_DMA_L (1 << 27) +#define DEV_DMA_SHORT (1 << 26) +#define DEV_DMA_IOC (1 << 25) +#define DEV_DMA_SR (1 << 24) +#define DEV_DMA_MTRF (1 << 23) +#define DEV_DMA_ISOC_PID_MASK (0x3 << 23) +#define DEV_DMA_ISOC_PID_SHIFT 23 +#define DEV_DMA_ISOC_PID_DATA0 0 +#define DEV_DMA_ISOC_PID_DATA2 1 +#define DEV_DMA_ISOC_PID_DATA1 2 +#define DEV_DMA_ISOC_PID_MDATA 3 +#define DEV_DMA_ISOC_FRNUM_MASK (0x7ff << 12) +#define DEV_DMA_ISOC_FRNUM_SHIFT 12 +#define DEV_DMA_ISOC_TX_NBYTES_MASK (0xfff << 0) +#define DEV_DMA_ISOC_TX_NBYTES_LIMIT 0xfff +#define DEV_DMA_ISOC_RX_NBYTES_MASK (0x7ff << 0) +#define DEV_DMA_ISOC_RX_NBYTES_LIMIT 0x7ff +#define DEV_DMA_ISOC_NBYTES_SHIFT 0 +#define DEV_DMA_NBYTES_MASK (0xffff << 0) +#define DEV_DMA_NBYTES_SHIFT 0 +#define DEV_DMA_NBYTES_LIMIT 0xffff -#define MAX_DMA_DESC_SIZE 131071 #define MAX_DMA_DESC_NUM_GENERIC 64 #define MAX_DMA_DESC_NUM_HS_ISOC 256 diff --git a/sys/dev/usb/dwc2/dwc2_params.c b/sys/dev/usb/dwc2/dwc2_params.c new file mode 100644 index 00000000000..6fa5202f57c --- /dev/null +++ b/sys/dev/usb/dwc2/dwc2_params.c @@ -0,0 +1,1030 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * Copyright (C) 2004-2016 Synopsys, Inc. + * + * 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, + * without modification. + * 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. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +STATIC void dwc2_set_dwctwo_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + struct dwc2_softc *sc = hsotg->hsotg_sc; + + p->otg_caps.hnp_support = sc->sc_params->otg_caps.hnp_support; + p->otg_caps.srp_support = sc->sc_params->otg_caps.srp_support; + p->host_dma = sc->sc_params->host_dma; + p->dma_desc_enable = sc->sc_params->dma_desc_enable; + p->speed = sc->sc_params->speed; + p->enable_dynamic_fifo = sc->sc_params->enable_dynamic_fifo; + p->en_multiple_tx_fifo = sc->sc_params->en_multiple_tx_fifo; + p->host_rx_fifo_size = sc->sc_params->host_rx_fifo_size; + p->host_nperio_tx_fifo_size = sc->sc_params->host_nperio_tx_fifo_size; + p->host_perio_tx_fifo_size = sc->sc_params->host_perio_tx_fifo_size; + p->max_transfer_size = sc->sc_params->max_transfer_size; + p->max_packet_count = sc->sc_params->max_packet_count; + p->host_channels = sc->sc_params->host_channels; + p->phy_type = sc->sc_params->phy_type; + p->phy_utmi_width = sc->sc_params->phy_utmi_width; + p->phy_ulpi_ddr = sc->sc_params->phy_ulpi_ddr; + p->phy_ulpi_ext_vbus = sc->sc_params->phy_ulpi_ext_vbus; + p->i2c_enable = sc->sc_params->i2c_enable; + p->ulpi_fs_ls = sc->sc_params->ulpi_fs_ls; + p->host_support_fs_ls_low_power = sc->sc_params->host_support_fs_ls_low_power; + p->host_ls_low_power_phy_clk = sc->sc_params->host_ls_low_power_phy_clk; + p->ts_dline = sc->sc_params->ts_dline; + p->reload_ctl = sc->sc_params->reload_ctl; + p->ahbcfg = sc->sc_params->ahbcfg; + p->uframe_sched = sc->sc_params->uframe_sched; + p->external_id_pin_ctl = sc->sc_params->external_id_pin_ctl; +} + +#if 0 +static void dwc2_set_bcm_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->host_rx_fifo_size = 774; + p->max_transfer_size = 65535; + p->max_packet_count = 511; + p->ahbcfg = 0x10; +} + +static void dwc2_set_his_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->speed = DWC2_SPEED_PARAM_HIGH; + p->host_rx_fifo_size = 512; + p->host_nperio_tx_fifo_size = 512; + p->host_perio_tx_fifo_size = 512; + p->max_transfer_size = 65535; + p->max_packet_count = 511; + p->host_channels = 16; + p->phy_type = DWC2_PHY_TYPE_PARAM_UTMI; + p->phy_utmi_width = 8; + p->i2c_enable = false; + p->reload_ctl = false; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << + GAHBCFG_HBSTLEN_SHIFT; + p->change_speed_quirk = true; + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; +} + +static void dwc2_set_jz4775_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->speed = DWC2_SPEED_PARAM_HIGH; + p->phy_type = DWC2_PHY_TYPE_PARAM_UTMI; + p->phy_utmi_width = 16; + p->activate_ingenic_overcurrent_detection = + !device_property_read_bool(hsotg->dev, "disable-over-current"); +} + +static void dwc2_set_x1600_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->speed = DWC2_SPEED_PARAM_HIGH; + p->host_channels = 16; + p->phy_type = DWC2_PHY_TYPE_PARAM_UTMI; + p->phy_utmi_width = 16; + p->activate_ingenic_overcurrent_detection = + !device_property_read_bool(hsotg->dev, "disable-over-current"); +} + +static void dwc2_set_x2000_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->speed = DWC2_SPEED_PARAM_HIGH; + p->host_rx_fifo_size = 1024; + p->host_nperio_tx_fifo_size = 1024; + p->host_perio_tx_fifo_size = 1024; + p->host_channels = 16; + p->phy_type = DWC2_PHY_TYPE_PARAM_UTMI; + p->phy_utmi_width = 16; + p->activate_ingenic_overcurrent_detection = + !device_property_read_bool(hsotg->dev, "disable-over-current"); +} + +static void dwc2_set_s3c6400_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; + p->no_clock_gating = true; + p->phy_utmi_width = 8; +} + +static void dwc2_set_socfpga_agilex_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; + p->no_clock_gating = true; +} + +static void dwc2_set_rk_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->host_rx_fifo_size = 525; + p->host_nperio_tx_fifo_size = 128; + p->host_perio_tx_fifo_size = 256; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << + GAHBCFG_HBSTLEN_SHIFT; + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; +} + +static void dwc2_set_ltq_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->host_rx_fifo_size = 288; + p->host_nperio_tx_fifo_size = 128; + p->host_perio_tx_fifo_size = 96; + p->max_transfer_size = 65535; + p->max_packet_count = 511; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << + GAHBCFG_HBSTLEN_SHIFT; +} + +static void dwc2_set_amlogic_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->speed = DWC2_SPEED_PARAM_HIGH; + p->host_rx_fifo_size = 512; + p->host_nperio_tx_fifo_size = 500; + p->host_perio_tx_fifo_size = 500; + p->host_channels = 16; + p->phy_type = DWC2_PHY_TYPE_PARAM_UTMI; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR8 << + GAHBCFG_HBSTLEN_SHIFT; + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; +} + +static void dwc2_set_amlogic_g12a_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->lpm = false; + p->lpm_clock_gating = false; + p->besl = false; + p->hird_threshold_en = false; +} + +static void dwc2_set_amcc_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << GAHBCFG_HBSTLEN_SHIFT; +} + +static void dwc2_set_stm32f4x9_fsotg_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->speed = DWC2_SPEED_PARAM_FULL; + p->host_rx_fifo_size = 128; + p->host_nperio_tx_fifo_size = 96; + p->host_perio_tx_fifo_size = 96; + p->max_packet_count = 256; + p->phy_type = DWC2_PHY_TYPE_PARAM_FS; + p->i2c_enable = false; + p->activate_stm_fs_transceiver = true; +} + +static void dwc2_set_stm32f7_hsotg_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->host_rx_fifo_size = 622; + p->host_nperio_tx_fifo_size = 128; + p->host_perio_tx_fifo_size = 256; +} + +static void dwc2_set_stm32mp15_fsotg_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->otg_caps.otg_rev = 0x200; + p->speed = DWC2_SPEED_PARAM_FULL; + p->host_rx_fifo_size = 128; + p->host_nperio_tx_fifo_size = 96; + p->host_perio_tx_fifo_size = 96; + p->max_packet_count = 256; + p->phy_type = DWC2_PHY_TYPE_PARAM_FS; + p->i2c_enable = false; + p->activate_stm_fs_transceiver = true; + p->activate_stm_id_vb_detection = true; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << GAHBCFG_HBSTLEN_SHIFT; + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; + p->host_support_fs_ls_low_power = true; + p->host_ls_low_power_phy_clk = true; +} + +static void dwc2_set_stm32mp15_hsotg_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->otg_caps.otg_rev = 0x200; + p->activate_stm_id_vb_detection = !device_property_read_bool(hsotg->dev, "usb-role-switch"); + p->host_rx_fifo_size = 440; + p->host_nperio_tx_fifo_size = 256; + p->host_perio_tx_fifo_size = 256; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << GAHBCFG_HBSTLEN_SHIFT; + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; + p->lpm = false; + p->lpm_clock_gating = false; + p->besl = false; + p->hird_threshold_en = false; +} + +const struct of_device_id dwc2_of_match_table[] = { + { .compatible = "brcm,bcm2835-usb", .data = dwc2_set_bcm_params }, + { .compatible = "hisilicon,hi6220-usb", .data = dwc2_set_his_params }, + { .compatible = "ingenic,jz4775-otg", .data = dwc2_set_jz4775_params }, + { .compatible = "ingenic,jz4780-otg", .data = dwc2_set_jz4775_params }, + { .compatible = "ingenic,x1000-otg", .data = dwc2_set_jz4775_params }, + { .compatible = "ingenic,x1600-otg", .data = dwc2_set_x1600_params }, + { .compatible = "ingenic,x1700-otg", .data = dwc2_set_x1600_params }, + { .compatible = "ingenic,x1830-otg", .data = dwc2_set_x1600_params }, + { .compatible = "ingenic,x2000-otg", .data = dwc2_set_x2000_params }, + { .compatible = "rockchip,rk3066-usb", .data = dwc2_set_rk_params }, + { .compatible = "lantiq,arx100-usb", .data = dwc2_set_ltq_params }, + { .compatible = "lantiq,xrx200-usb", .data = dwc2_set_ltq_params }, + { .compatible = "snps,dwc2" }, + { .compatible = "samsung,s3c6400-hsotg", + .data = dwc2_set_s3c6400_params }, + { .compatible = "amlogic,meson8-usb", + .data = dwc2_set_amlogic_params }, + { .compatible = "amlogic,meson8b-usb", + .data = dwc2_set_amlogic_params }, + { .compatible = "amlogic,meson-gxbb-usb", + .data = dwc2_set_amlogic_params }, + { .compatible = "amlogic,meson-g12a-usb", + .data = dwc2_set_amlogic_g12a_params }, + { .compatible = "amcc,dwc-otg", .data = dwc2_set_amcc_params }, + { .compatible = "apm,apm82181-dwc-otg", .data = dwc2_set_amcc_params }, + { .compatible = "st,stm32f4x9-fsotg", + .data = dwc2_set_stm32f4x9_fsotg_params }, + { .compatible = "st,stm32f4x9-hsotg" }, + { .compatible = "st,stm32f7-hsotg", + .data = dwc2_set_stm32f7_hsotg_params }, + { .compatible = "st,stm32mp15-fsotg", + .data = dwc2_set_stm32mp15_fsotg_params }, + { .compatible = "st,stm32mp15-hsotg", + .data = dwc2_set_stm32mp15_hsotg_params }, + { .compatible = "intel,socfpga-agilex-hsotg", + .data = dwc2_set_socfpga_agilex_params }, + {}, +}; +MODULE_DEVICE_TABLE(of, dwc2_of_match_table); + +const struct acpi_device_id dwc2_acpi_match[] = { + { "BCM2848", (kernel_ulong_t)dwc2_set_bcm_params }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, dwc2_acpi_match); +#endif + +STATIC void dwc2_set_param_otg_cap(struct dwc2_hsotg *hsotg) +{ + switch (hsotg->hw_params.op_mode) { + case GHWCFG2_OP_MODE_HNP_SRP_CAPABLE: + hsotg->params.otg_caps.hnp_support = true; + hsotg->params.otg_caps.srp_support = true; + break; + case GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE: + case GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE: + case GHWCFG2_OP_MODE_SRP_CAPABLE_HOST: + hsotg->params.otg_caps.hnp_support = false; + hsotg->params.otg_caps.srp_support = true; + break; + default: + hsotg->params.otg_caps.hnp_support = false; + hsotg->params.otg_caps.srp_support = false; + break; + } +} + +STATIC void dwc2_set_param_phy_type(struct dwc2_hsotg *hsotg) +{ + int val; + u32 hs_phy_type = hsotg->hw_params.hs_phy_type; + + val = DWC2_PHY_TYPE_PARAM_FS; + if (hs_phy_type != GHWCFG2_HS_PHY_TYPE_NOT_SUPPORTED) { + if (hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI || + hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI_ULPI) + val = DWC2_PHY_TYPE_PARAM_UTMI; + else + val = DWC2_PHY_TYPE_PARAM_ULPI; + } + + if (dwc2_is_fs_iot(hsotg)) + hsotg->params.phy_type = DWC2_PHY_TYPE_PARAM_FS; + + hsotg->params.phy_type = val; +} + +STATIC void dwc2_set_param_speed(struct dwc2_hsotg *hsotg) +{ + int val; + + val = hsotg->params.phy_type == DWC2_PHY_TYPE_PARAM_FS ? + DWC2_SPEED_PARAM_FULL : DWC2_SPEED_PARAM_HIGH; + + if (dwc2_is_fs_iot(hsotg)) + val = DWC2_SPEED_PARAM_FULL; + + if (dwc2_is_hs_iot(hsotg)) + val = DWC2_SPEED_PARAM_HIGH; + + hsotg->params.speed = val; +} + +void dwc2_set_param_phy_utmi_width(struct dwc2_hsotg *hsotg) +{ +#if 0 + int val; + + val = (hsotg->hw_params.utmi_phy_data_width == + GHWCFG4_UTMI_PHY_DATA_WIDTH_8) ? 8 : 16; + + if (hsotg->phy) { + /* + * If using the generic PHY framework, check if the PHY bus + * width is 8-bit and set the phyif appropriately. + */ + if (phy_get_bus_width(hsotg->phy) == 8) + val = 8; + } + + hsotg->params.phy_utmi_width = val; +#endif + hsotg->params.phy_utmi_width = 8; +} + +static void dwc2_set_param_tx_fifo_sizes(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + int depth_average; + int fifo_count; + int i; + + fifo_count = dwc2_hsotg_tx_fifo_count(hsotg); + + memset(p->g_tx_fifo_size, 0, sizeof(p->g_tx_fifo_size)); + depth_average = dwc2_hsotg_tx_fifo_average_depth(hsotg); + for (i = 1; i <= fifo_count; i++) + p->g_tx_fifo_size[i] = depth_average; +} + +static void dwc2_set_param_power_down(struct dwc2_hsotg *hsotg) +{ + int val; + + if (hsotg->hw_params.hibernation) + val = DWC2_POWER_DOWN_PARAM_HIBERNATION; + else if (hsotg->hw_params.power_optimized) + val = DWC2_POWER_DOWN_PARAM_PARTIAL; + else + val = DWC2_POWER_DOWN_PARAM_NONE; + + hsotg->params.power_down = val; +} + +static void dwc2_set_param_lpm(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->lpm = hsotg->hw_params.lpm_mode; + if (p->lpm) { + p->lpm_clock_gating = true; + p->besl = true; + p->hird_threshold_en = true; + p->hird_threshold = 4; + } else { + p->lpm_clock_gating = false; + p->besl = false; + p->hird_threshold_en = false; + } +} + +/** + * dwc2_set_default_params() - Set all core parameters to their + * auto-detected default values. + * + * @hsotg: Programming view of the DWC_otg controller + * + */ +STATIC void dwc2_set_default_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hw_params *hw = &hsotg->hw_params; + struct dwc2_core_params *p = &hsotg->params; + bool dma_capable = !(hw->arch == GHWCFG2_SLAVE_ONLY_ARCH); + + dwc2_set_param_otg_cap(hsotg); + dwc2_set_param_phy_type(hsotg); + dwc2_set_param_speed(hsotg); + dwc2_set_param_phy_utmi_width(hsotg); + dwc2_set_param_power_down(hsotg); + dwc2_set_param_lpm(hsotg); + p->phy_ulpi_ddr = false; + p->phy_ulpi_ext_vbus = false; + + p->enable_dynamic_fifo = hw->enable_dynamic_fifo; + p->en_multiple_tx_fifo = hw->en_multiple_tx_fifo; + p->i2c_enable = hw->i2c_enable; + p->acg_enable = hw->acg_enable; + p->ulpi_fs_ls = false; + p->ts_dline = false; + p->reload_ctl = (hw->snpsid >= DWC2_CORE_REV_2_92a); + p->uframe_sched = true; + p->external_id_pin_ctl = false; + p->ipg_isoc_en = false; + p->service_interval = false; + p->max_packet_count = hw->max_packet_count; + p->max_transfer_size = hw->max_transfer_size; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR << GAHBCFG_HBSTLEN_SHIFT; + p->ref_clk_per = 33333; + p->sof_cnt_wkup_alert = 100; + + if ((hsotg->dr_mode == USB_DR_MODE_HOST) || + (hsotg->dr_mode == USB_DR_MODE_OTG)) { + p->host_dma = dma_capable; + p->dma_desc_enable = false; + p->dma_desc_fs_enable = false; + p->host_support_fs_ls_low_power = false; + p->host_ls_low_power_phy_clk = false; + p->host_channels = hw->host_channels; + p->host_rx_fifo_size = hw->rx_fifo_size; + p->host_nperio_tx_fifo_size = hw->host_nperio_tx_fifo_size; + p->host_perio_tx_fifo_size = hw->host_perio_tx_fifo_size; + } + + if ((hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) || + (hsotg->dr_mode == USB_DR_MODE_OTG)) { + p->g_dma = dma_capable; + p->g_dma_desc = hw->dma_desc_enable; + + /* + * The values for g_rx_fifo_size (2048) and + * g_np_tx_fifo_size (1024) come from the legacy s3c + * gadget driver. These defaults have been hard-coded + * for some time so many platforms depend on these + * values. Leave them as defaults for now and only + * auto-detect if the hardware does not support the + * default. + */ + p->g_rx_fifo_size = 2048; + p->g_np_tx_fifo_size = 1024; + dwc2_set_param_tx_fifo_sizes(hsotg); + } +} + +#if 0 +/** + * dwc2_get_device_properties() - Read in device properties. + * + * @hsotg: Programming view of the DWC_otg controller + * + * Read in the device properties and adjust core parameters if needed. + */ +static void dwc2_get_device_properties(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + int num; + + if ((hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) || + (hsotg->dr_mode == USB_DR_MODE_OTG)) { + device_property_read_u32(hsotg->dev, "g-rx-fifo-size", + &p->g_rx_fifo_size); + + device_property_read_u32(hsotg->dev, "g-np-tx-fifo-size", + &p->g_np_tx_fifo_size); + + num = device_property_count_u32(hsotg->dev, "g-tx-fifo-size"); + if (num > 0) { + num = min(num, 15); + memset(p->g_tx_fifo_size, 0, + sizeof(p->g_tx_fifo_size)); + device_property_read_u32_array(hsotg->dev, + "g-tx-fifo-size", + &p->g_tx_fifo_size[1], + num); + } + + of_usb_update_otg_caps(hsotg->dev->of_node, &p->otg_caps); + } + + if (of_find_property(hsotg->dev->of_node, "disable-over-current", NULL)) + p->oc_disable = true; +} +#endif + +static void dwc2_check_param_otg_cap(struct dwc2_hsotg *hsotg) +{ + int valid = 1; + + if (hsotg->params.otg_caps.hnp_support && hsotg->params.otg_caps.srp_support) { + /* check HNP && SRP capable */ + if (hsotg->hw_params.op_mode != GHWCFG2_OP_MODE_HNP_SRP_CAPABLE) + valid = 0; + } else if (!hsotg->params.otg_caps.hnp_support) { + /* check SRP only capable */ + if (hsotg->params.otg_caps.srp_support) { + switch (hsotg->hw_params.op_mode) { + case GHWCFG2_OP_MODE_HNP_SRP_CAPABLE: + case GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE: + case GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE: + case GHWCFG2_OP_MODE_SRP_CAPABLE_HOST: + break; + default: + valid = 0; + break; + } + } + /* else: NO HNP && NO SRP capable: always valid */ + } else { + valid = 0; + } + + if (!valid) + dwc2_set_param_otg_cap(hsotg); +} + +static void dwc2_check_param_phy_type(struct dwc2_hsotg *hsotg) +{ + int valid = 0; + u32 hs_phy_type; + u32 fs_phy_type; + + hs_phy_type = hsotg->hw_params.hs_phy_type; + fs_phy_type = hsotg->hw_params.fs_phy_type; + + switch (hsotg->params.phy_type) { + case DWC2_PHY_TYPE_PARAM_FS: + if (fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED) + valid = 1; + break; + case DWC2_PHY_TYPE_PARAM_UTMI: + if ((hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI) || + (hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI_ULPI)) + valid = 1; + break; + case DWC2_PHY_TYPE_PARAM_ULPI: + if ((hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI) || + (hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI_ULPI)) + valid = 1; + break; + default: + break; + } + + if (!valid) + dwc2_set_param_phy_type(hsotg); +} + +static void dwc2_check_param_speed(struct dwc2_hsotg *hsotg) +{ + int valid = 1; + int phy_type = hsotg->params.phy_type; + int speed = hsotg->params.speed; + + switch (speed) { + case DWC2_SPEED_PARAM_HIGH: + if ((hsotg->params.speed == DWC2_SPEED_PARAM_HIGH) && + (phy_type == DWC2_PHY_TYPE_PARAM_FS)) + valid = 0; + break; + case DWC2_SPEED_PARAM_FULL: + case DWC2_SPEED_PARAM_LOW: + break; + default: + valid = 0; + break; + } + + if (!valid) + dwc2_set_param_speed(hsotg); +} + +static void dwc2_check_param_phy_utmi_width(struct dwc2_hsotg *hsotg) +{ + int valid = 0; + int param = hsotg->params.phy_utmi_width; + int width = hsotg->hw_params.utmi_phy_data_width; + + switch (width) { + case GHWCFG4_UTMI_PHY_DATA_WIDTH_8: + valid = (param == 8); + break; + case GHWCFG4_UTMI_PHY_DATA_WIDTH_16: + valid = (param == 16); + break; + case GHWCFG4_UTMI_PHY_DATA_WIDTH_8_OR_16: + valid = (param == 8 || param == 16); + break; + } + + if (!valid) + dwc2_set_param_phy_utmi_width(hsotg); +} + +static void dwc2_check_param_power_down(struct dwc2_hsotg *hsotg) +{ + int param = hsotg->params.power_down; + + switch (param) { + case DWC2_POWER_DOWN_PARAM_NONE: + break; + case DWC2_POWER_DOWN_PARAM_PARTIAL: + if (hsotg->hw_params.power_optimized) + break; + dev_dbg(hsotg->dev, + "Partial power down isn't supported by HW\n"); + param = DWC2_POWER_DOWN_PARAM_NONE; + break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: + if (hsotg->hw_params.hibernation) + break; + dev_dbg(hsotg->dev, + "Hibernation isn't supported by HW\n"); + param = DWC2_POWER_DOWN_PARAM_NONE; + break; + default: + dev_err(hsotg->dev, + "%s: Invalid parameter power_down=%d\n", + __func__, param); + param = DWC2_POWER_DOWN_PARAM_NONE; + break; + } + + hsotg->params.power_down = param; +} + +static void dwc2_check_param_tx_fifo_sizes(struct dwc2_hsotg *hsotg) +{ + int fifo_count; + int fifo; + int min; + u32 total = 0; + u32 dptxfszn; + + fifo_count = dwc2_hsotg_tx_fifo_count(hsotg); + min = hsotg->hw_params.en_multiple_tx_fifo ? 16 : 4; + + for (fifo = 1; fifo <= fifo_count; fifo++) + total += hsotg->params.g_tx_fifo_size[fifo]; + + if (total > dwc2_hsotg_tx_fifo_total_depth(hsotg) || !total) { + dev_warn(hsotg->dev, "%s: Invalid parameter g-tx-fifo-size, setting to default average\n", + __func__); + dwc2_set_param_tx_fifo_sizes(hsotg); + } + + for (fifo = 1; fifo <= fifo_count; fifo++) { + dptxfszn = hsotg->hw_params.g_tx_fifo_size[fifo]; + + if (hsotg->params.g_tx_fifo_size[fifo] < min || + hsotg->params.g_tx_fifo_size[fifo] > dptxfszn) { + dev_warn(hsotg->dev, "%s: Invalid parameter g_tx_fifo_size[%d]=%d\n", + __func__, fifo, + hsotg->params.g_tx_fifo_size[fifo]); + hsotg->params.g_tx_fifo_size[fifo] = dptxfszn; + } + } +} + +#define CHECK_RANGE(_param, _min, _max, _def) do { \ + if ((int)(hsotg->params._param) < (_min) || \ + (hsotg->params._param) > (_max)) { \ + dev_warn(hsotg->dev, "%s: Invalid parameter %s=%d\n", \ + __func__, #_param, hsotg->params._param); \ + hsotg->params._param = (_def); \ + } \ + } while (0) + +#define CHECK_BOOL(_param, _check) do { \ + if (hsotg->params._param && !(_check)) { \ + dev_warn(hsotg->dev, "%s: Invalid parameter %s=%d\n", \ + __func__, #_param, hsotg->params._param); \ + hsotg->params._param = false; \ + } \ + } while (0) + +STATIC void dwc2_check_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hw_params *hw = &hsotg->hw_params; + struct dwc2_core_params *p = &hsotg->params; + bool dma_capable = !(hw->arch == GHWCFG2_SLAVE_ONLY_ARCH); + + dwc2_check_param_otg_cap(hsotg); + dwc2_check_param_phy_type(hsotg); + dwc2_check_param_speed(hsotg); + dwc2_check_param_phy_utmi_width(hsotg); + dwc2_check_param_power_down(hsotg); + CHECK_BOOL(enable_dynamic_fifo, hw->enable_dynamic_fifo); + CHECK_BOOL(en_multiple_tx_fifo, hw->en_multiple_tx_fifo); + CHECK_BOOL(i2c_enable, hw->i2c_enable); + CHECK_BOOL(ipg_isoc_en, hw->ipg_isoc_en); + CHECK_BOOL(acg_enable, hw->acg_enable); + CHECK_BOOL(reload_ctl, (hsotg->hw_params.snpsid > DWC2_CORE_REV_2_92a)); + CHECK_BOOL(lpm, (hsotg->hw_params.snpsid >= DWC2_CORE_REV_2_80a)); + CHECK_BOOL(lpm, hw->lpm_mode); + CHECK_BOOL(lpm_clock_gating, hsotg->params.lpm); + CHECK_BOOL(besl, hsotg->params.lpm); + CHECK_BOOL(besl, (hsotg->hw_params.snpsid >= DWC2_CORE_REV_3_00a)); + CHECK_BOOL(hird_threshold_en, hsotg->params.lpm); + CHECK_RANGE(hird_threshold, 0, hsotg->params.besl ? 12 : 7, 0); + CHECK_BOOL(service_interval, hw->service_interval_mode); + CHECK_RANGE(max_packet_count, + 15, hw->max_packet_count, + hw->max_packet_count); + CHECK_RANGE(max_transfer_size, + 2047, hw->max_transfer_size, + hw->max_transfer_size); + + if ((hsotg->dr_mode == USB_DR_MODE_HOST) || + (hsotg->dr_mode == USB_DR_MODE_OTG)) { + CHECK_BOOL(host_dma, dma_capable); + CHECK_BOOL(dma_desc_enable, p->host_dma); + CHECK_BOOL(dma_desc_fs_enable, p->dma_desc_enable); + CHECK_BOOL(host_ls_low_power_phy_clk, + p->phy_type == DWC2_PHY_TYPE_PARAM_FS); + CHECK_RANGE(host_channels, + 1, hw->host_channels, + hw->host_channels); + CHECK_RANGE(host_rx_fifo_size, + 16, hw->rx_fifo_size, + hw->rx_fifo_size); + CHECK_RANGE(host_nperio_tx_fifo_size, + 16, hw->host_nperio_tx_fifo_size, + hw->host_nperio_tx_fifo_size); + CHECK_RANGE(host_perio_tx_fifo_size, + 16, hw->host_perio_tx_fifo_size, + hw->host_perio_tx_fifo_size); + } + + if ((hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) || + (hsotg->dr_mode == USB_DR_MODE_OTG)) { + CHECK_BOOL(g_dma, dma_capable); + CHECK_BOOL(g_dma_desc, (p->g_dma && hw->dma_desc_enable)); + CHECK_RANGE(g_rx_fifo_size, + 16, hw->rx_fifo_size, + hw->rx_fifo_size); + CHECK_RANGE(g_np_tx_fifo_size, + 16, hw->dev_nperio_tx_fifo_size, + hw->dev_nperio_tx_fifo_size); + dwc2_check_param_tx_fifo_sizes(hsotg); + } +} + +/* + * Gets host hardware parameters. Forces host mode if not currently in + * host mode. Should be called immediately after a core soft reset in + * order to get the reset values. + */ +STATIC void dwc2_get_host_hwparams(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hw_params *hw = &hsotg->hw_params; + u32 gnptxfsiz; + u32 hptxfsiz; + + if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) + return; + + dwc2_force_mode(hsotg, true); + + gnptxfsiz = dwc2_readl(hsotg, GNPTXFSIZ); + hptxfsiz = dwc2_readl(hsotg, HPTXFSIZ); + + hw->host_nperio_tx_fifo_size = (gnptxfsiz & FIFOSIZE_DEPTH_MASK) >> + FIFOSIZE_DEPTH_SHIFT; + hw->host_perio_tx_fifo_size = (hptxfsiz & FIFOSIZE_DEPTH_MASK) >> + FIFOSIZE_DEPTH_SHIFT; +} + +/* + * Gets device hardware parameters. Forces device mode if not + * currently in device mode. Should be called immediately after a core + * soft reset in order to get the reset values. + */ +STATIC void dwc2_get_dev_hwparams(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hw_params *hw = &hsotg->hw_params; + u32 gnptxfsiz; + int fifo, fifo_count; + + if (hsotg->dr_mode == USB_DR_MODE_HOST) + return; + + dwc2_force_mode(hsotg, false); + + gnptxfsiz = dwc2_readl(hsotg, GNPTXFSIZ); + + fifo_count = dwc2_hsotg_tx_fifo_count(hsotg); + + for (fifo = 1; fifo <= fifo_count; fifo++) { + hw->g_tx_fifo_size[fifo] = + (dwc2_readl(hsotg, DPTXFSIZN(fifo)) & + FIFOSIZE_DEPTH_MASK) >> FIFOSIZE_DEPTH_SHIFT; + } + + hw->dev_nperio_tx_fifo_size = (gnptxfsiz & FIFOSIZE_DEPTH_MASK) >> + FIFOSIZE_DEPTH_SHIFT; +} + +/** + * dwc2_get_hwparams() - During device initialization, read various hardware + * configuration registers and interpret the contents. + * + * @hsotg: Programming view of the DWC_otg controller + * + */ +int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hw_params *hw = &hsotg->hw_params; + unsigned int width; + u32 hwcfg1, hwcfg2, hwcfg3, hwcfg4; + u32 grxfsiz; + + hwcfg1 = dwc2_readl(hsotg, GHWCFG1); + hwcfg2 = dwc2_readl(hsotg, GHWCFG2); + hwcfg3 = dwc2_readl(hsotg, GHWCFG3); + hwcfg4 = dwc2_readl(hsotg, GHWCFG4); + grxfsiz = dwc2_readl(hsotg, GRXFSIZ); + + /* hwcfg1 */ + hw->dev_ep_dirs = hwcfg1; + + /* hwcfg2 */ + hw->op_mode = (hwcfg2 & GHWCFG2_OP_MODE_MASK) >> + GHWCFG2_OP_MODE_SHIFT; + hw->arch = (hwcfg2 & GHWCFG2_ARCHITECTURE_MASK) >> + GHWCFG2_ARCHITECTURE_SHIFT; + hw->enable_dynamic_fifo = !!(hwcfg2 & GHWCFG2_DYNAMIC_FIFO); + hw->host_channels = 1 + ((hwcfg2 & GHWCFG2_NUM_HOST_CHAN_MASK) >> + GHWCFG2_NUM_HOST_CHAN_SHIFT); + hw->hs_phy_type = (hwcfg2 & GHWCFG2_HS_PHY_TYPE_MASK) >> + GHWCFG2_HS_PHY_TYPE_SHIFT; + hw->fs_phy_type = (hwcfg2 & GHWCFG2_FS_PHY_TYPE_MASK) >> + GHWCFG2_FS_PHY_TYPE_SHIFT; + hw->num_dev_ep = (hwcfg2 & GHWCFG2_NUM_DEV_EP_MASK) >> + GHWCFG2_NUM_DEV_EP_SHIFT; + hw->nperio_tx_q_depth = + (hwcfg2 & GHWCFG2_NONPERIO_TX_Q_DEPTH_MASK) >> + GHWCFG2_NONPERIO_TX_Q_DEPTH_SHIFT << 1; + hw->host_perio_tx_q_depth = + (hwcfg2 & GHWCFG2_HOST_PERIO_TX_Q_DEPTH_MASK) >> + GHWCFG2_HOST_PERIO_TX_Q_DEPTH_SHIFT << 1; + hw->dev_token_q_depth = + (hwcfg2 & GHWCFG2_DEV_TOKEN_Q_DEPTH_MASK) >> + GHWCFG2_DEV_TOKEN_Q_DEPTH_SHIFT; + + /* hwcfg3 */ + width = (hwcfg3 & GHWCFG3_XFER_SIZE_CNTR_WIDTH_MASK) >> + GHWCFG3_XFER_SIZE_CNTR_WIDTH_SHIFT; + hw->max_transfer_size = (1 << (width + 11)) - 1; + width = (hwcfg3 & GHWCFG3_PACKET_SIZE_CNTR_WIDTH_MASK) >> + GHWCFG3_PACKET_SIZE_CNTR_WIDTH_SHIFT; + hw->max_packet_count = (1 << (width + 4)) - 1; + hw->i2c_enable = !!(hwcfg3 & GHWCFG3_I2C); + hw->total_fifo_size = (hwcfg3 & GHWCFG3_DFIFO_DEPTH_MASK) >> + GHWCFG3_DFIFO_DEPTH_SHIFT; + hw->lpm_mode = !!(hwcfg3 & GHWCFG3_OTG_LPM_EN); + + /* hwcfg4 */ + hw->en_multiple_tx_fifo = !!(hwcfg4 & GHWCFG4_DED_FIFO_EN); + hw->num_dev_perio_in_ep = (hwcfg4 & GHWCFG4_NUM_DEV_PERIO_IN_EP_MASK) >> + GHWCFG4_NUM_DEV_PERIO_IN_EP_SHIFT; + hw->num_dev_in_eps = (hwcfg4 & GHWCFG4_NUM_IN_EPS_MASK) >> + GHWCFG4_NUM_IN_EPS_SHIFT; + hw->dma_desc_enable = !!(hwcfg4 & GHWCFG4_DESC_DMA); + hw->power_optimized = !!(hwcfg4 & GHWCFG4_POWER_OPTIMIZ); + hw->hibernation = !!(hwcfg4 & GHWCFG4_HIBER); + hw->utmi_phy_data_width = (hwcfg4 & GHWCFG4_UTMI_PHY_DATA_WIDTH_MASK) >> + GHWCFG4_UTMI_PHY_DATA_WIDTH_SHIFT; + hw->acg_enable = !!(hwcfg4 & GHWCFG4_ACG_SUPPORTED); + hw->ipg_isoc_en = !!(hwcfg4 & GHWCFG4_IPG_ISOC_SUPPORTED); + hw->service_interval_mode = !!(hwcfg4 & + GHWCFG4_SERVICE_INTERVAL_SUPPORTED); + + /* fifo sizes */ + hw->rx_fifo_size = (grxfsiz & GRXFSIZ_DEPTH_MASK) >> + GRXFSIZ_DEPTH_SHIFT; + /* + * Host specific hardware parameters. Reading these parameters + * requires the controller to be in host mode. The mode will + * be forced, if necessary, to read these values. + */ + dwc2_get_host_hwparams(hsotg); + dwc2_get_dev_hwparams(hsotg); + + return 0; +} + +typedef void (*set_params_cb)(struct dwc2_hsotg *data); + +int dwc2_init_params(struct dwc2_hsotg *hsotg) +{ +#if 0 + const struct of_device_id *match; + set_params_cb set_params; +#endif + + dwc2_set_default_params(hsotg); + dwc2_set_dwctwo_params(hsotg); +#if 0 + dwc2_get_device_properties(hsotg); + + match = of_match_device(dwc2_of_match_table, hsotg->dev); + if (match && match->data) { + set_params = match->data; + set_params(hsotg); + } else { + const struct acpi_device_id *amatch; + + amatch = acpi_match_device(dwc2_acpi_match, hsotg->dev); + if (amatch && amatch->driver_data) { + set_params = (set_params_cb)amatch->driver_data; + set_params(hsotg); + } + } +#endif + + dwc2_check_params(hsotg); + + return 0; +} diff --git a/sys/dev/usb/dwc2/dwc2var.h b/sys/dev/usb/dwc2/dwc2var.h index 425889aff3c..b9a9d953cd3 100644 --- a/sys/dev/usb/dwc2/dwc2var.h +++ b/sys/dev/usb/dwc2/dwc2var.h @@ -1,4 +1,4 @@ -/* $OpenBSD: dwc2var.h,v 1.22 2021/07/30 18:05:24 mglocker Exp $ */ +/* $OpenBSD: dwc2var.h,v 1.23 2022/09/04 08:42:40 mglocker Exp $ */ /* $NetBSD: dwc2var.h,v 1.3 2013/10/22 12:57:40 skrll Exp $ */ /*- @@ -125,15 +125,55 @@ dwc2_root_intr(dwc2_softc_t *sc) /* * XXX Compat */ +#define USB_MAXCHILDREN 31 /* XXX: Include in to our USB stack */ #define DWC2_MAXISOCPACKETS 40 /* XXX: Fix nframes handling */ #define ENOSR 90 #define device_xname(d) ((d)->dv_xname) #define jiffies hardclock_ticks -#define mstohz(ms) \ - (__predict_false((ms) >= 0x20000) ? \ - ((ms +0u) / 1000u) * hz : \ - ((ms +0u) * hz) / 1000u) -#define msecs_to_jiffies mstohz +#define msecs_to_jiffies(x) (((uint64_t)(x)) * hz / 1000) #define IS_ENABLED(option) (option) +#define DIV_ROUND_UP(x, y) (((x) + ((y) - 1)) / (y)) +#define NS_TO_US(ns) DIV_ROUND_UP(ns, 1000L) +#define BitTime(bytecount) (7 * 8 * bytecount / 6) +#define BITS_PER_LONG 64 +#define unlikely(x) __builtin_expect(!!(x), 0) + +#define USB2_HOST_DELAY 5 +#define HS_NSECS(bytes) (((55 * 8 * 2083) \ + + (2083UL * (3 + BitTime(bytes))))/1000 \ + + USB2_HOST_DELAY) +#define HS_NSECS_ISO(bytes) (((38 * 8 * 2083) \ + + (2083UL * (3 + BitTime(bytes))))/1000 \ + + USB2_HOST_DELAY) +#define HS_USECS(bytes) NS_TO_US(HS_NSECS(bytes)) +#define HS_USECS_ISO(bytes) NS_TO_US(HS_NSECS_ISO(bytes)) + +#define min_t(t, a, b) ({ \ + t __min_a = (a); \ + t __min_b = (b); \ + __min_a < __min_b ? __min_a : __min_b; }) +#define max_t(t, a, b) ({ \ + t __max_a = (a); \ + t __max_b = (b); \ + __max_a > __max_b ? __max_a : __max_b; }) + +#define _WARN_STR(x) #x +#define WARN_ON(condition) ({ \ + int __ret = !!(condition); \ + if (__ret) \ + printf("WARNING %s failed at %s:%d\n", \ + _WARN_STR(condition), __FILE__, __LINE__); \ + unlikely(__ret); \ +}) +#define WARN_ON_ONCE(condition) ({ \ + static int __warned; \ + int __ret = !!(condition); \ + if (__ret && !__warned) { \ + printf("WARNING %s failed at %s:%d\n", \ + _WARN_STR(condition), __FILE__, __LINE__); \ + __warned = 1; \ + } \ + unlikely(__ret); \ +}) #endif /* _DWC_OTGVAR_H_ */ diff --git a/sys/dev/usb/dwc2/files.dwc2 b/sys/dev/usb/dwc2/files.dwc2 index 88d5038c2d6..17fcfa6e08e 100644 --- a/sys/dev/usb/dwc2/files.dwc2 +++ b/sys/dev/usb/dwc2/files.dwc2 @@ -1,4 +1,4 @@ -# $OpenBSD: files.dwc2,v 1.2 2015/02/10 12:51:42 uebayasi Exp $ +# $OpenBSD: files.dwc2,v 1.3 2022/09/04 08:42:40 mglocker Exp $ # $NetBSD: files.dwc2,v 1.2 2014/09/12 16:40:38 skrll Exp $ # DesignWare HS OTG Controller @@ -12,3 +12,4 @@ file dev/usb/dwc2/dwc2_hcd.c dwctwo file dev/usb/dwc2/dwc2_hcdddma.c dwctwo file dev/usb/dwc2/dwc2_hcdintr.c dwctwo file dev/usb/dwc2/dwc2_hcdqueue.c dwctwo +file dev/usb/dwc2/dwc2_params.c dwctwo diff --git a/sys/dev/usb/dwc2/gcd.h b/sys/dev/usb/dwc2/gcd.h new file mode 100644 index 00000000000..efc890f17f2 --- /dev/null +++ b/sys/dev/usb/dwc2/gcd.h @@ -0,0 +1,52 @@ +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 _LINUX_GCD_H +#define _LINUX_GCD_H + +/* + * Compute the greatest common divisor of a and b. + * from libc getopt_long.c + */ +static inline unsigned long +gcd(unsigned long a, unsigned long b) +{ + unsigned long c; + + c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + + return (b); +} + +#endif diff --git a/sys/dev/usb/uhub.c b/sys/dev/usb/uhub.c index a301eb509a4..d9f944f46a2 100644 --- a/sys/dev/usb/uhub.c +++ b/sys/dev/usb/uhub.c @@ -1,4 +1,4 @@ -/* $OpenBSD: uhub.c,v 1.96 2022/07/10 20:15:31 mlarkin Exp $ */ +/* $OpenBSD: uhub.c,v 1.97 2022/09/04 08:42:39 mglocker Exp $ */ /* $NetBSD: uhub.c,v 1.64 2003/02/08 03:32:51 ichiro Exp $ */ /* $FreeBSD: src/sys/dev/usb/uhub.c,v 1.18 1999/11/17 22:33:43 n_hibma Exp $ */ @@ -224,6 +224,7 @@ uhub_attach(struct device *parent, struct device *self, void *aux) hub->nports = nports; hub->powerdelay = powerdelay; hub->ttthink = ttthink >> 5; + hub->multi = UHUB_IS_SINGLE_TT(sc) ? 0 : 1; if (!dev->self_powered && dev->powersrc->parent != NULL && !dev->powersrc->parent->self_powered) { @@ -307,6 +308,7 @@ uhub_attach(struct device *parent, struct device *self, void *aux) if (UHUB_IS_HIGH_SPEED(sc)) { up->tt = &tts[UHUB_IS_SINGLE_TT(sc) ? 0 : p]; up->tt->hub = hub; + up->tt->hcpriv = NULL; } else { up->tt = NULL; } diff --git a/sys/dev/usb/usbdivar.h b/sys/dev/usb/usbdivar.h index e84d8f79e13..45cfe20eb90 100644 --- a/sys/dev/usb/usbdivar.h +++ b/sys/dev/usb/usbdivar.h @@ -1,4 +1,4 @@ -/* $OpenBSD: usbdivar.h,v 1.82 2022/04/12 19:41:11 naddy Exp $ */ +/* $OpenBSD: usbdivar.h,v 1.83 2022/09/04 08:42:39 mglocker Exp $ */ /* $NetBSD: usbdivar.h,v 1.70 2002/07/11 21:14:36 augustss Exp $ */ /* $FreeBSD: src/sys/dev/usb/usbdivar.h,v 1.11 1999/11/17 22:33:51 n_hibma Exp $ */ @@ -79,6 +79,7 @@ struct usbd_pipe_methods { struct usbd_tt { struct usbd_hub *hub; + void *hcpriv; }; struct usbd_port { @@ -100,6 +101,7 @@ struct usbd_hub { int nports; u_int8_t powerdelay; u_int8_t ttthink; + u_int8_t multi; }; struct usbd_bus {