diff --git a/include/dev/dwc_otg.h b/include/dev/dwc_otg.h new file mode 100644 index 0000000000..54225e0ccf --- /dev/null +++ b/include/dev/dwc_otg.h @@ -0,0 +1,117 @@ +#ifndef _DEV_DWC_OTG_ + +#include +#include +#include + +/* + * FIFO depths in terms of 32-bit words. + */ +#define DOTG_RXFSIZ 1024 /* receive FIFO depth */ +#define DOTG_NPTXFSIZ 1024 /* non-periodic transmit FIFO depth */ +#define DOTG_PTXFSIZ 1024 /* periodic transmit FIFO depth */ + +#define DOTG_TXFMAXNUM 16 /* max possible number of transmit FIFOs */ +#define DOTG_MC 1 /* multi count */ + +#define DOTG_AHBIDLE_DELAY 100 +#define DOTG_RXFFLSH_DELAY 1 +#define DOTG_TXFFLSH_DELAY 1 + +/* + * Parameters of the queue of pending transactions. + */ +#define DOTG_MAXPNDG 32 /* maximum number of pending transactions */ +#define DOTG_PNDGBUFSIZ (DOTG_MAXPNDG * sizeof(dotg_txn_t *)) /* buf size */ + +typedef enum dotg_pid dotg_pid_t; +typedef enum dotg_stg_type dotg_stg_type_t; +typedef enum dotg_txn_status dotg_txn_status_t; +typedef struct dotg_stage dotg_stage_t; +typedef struct dotg_txn dotg_txn_t; +typedef struct dotg_callout_args dotg_callout_args_t; +typedef struct dotg_chan dotg_chan_t; +typedef struct dotg_state dotg_state_t; + +/* Packet identifier (don't alter the following values!). */ +enum dotg_pid { + DOTG_PID_DATA0 = 0, + DOTG_PID_DATA1 = 2, + DOTG_PID_SETUP = 3, +} __packed; + +/* Stage type. + * NOTE: there is at most one stage of each type per transaction. */ +enum dotg_stg_type { + DOTG_STG_SETUP, + DOTG_STG_DATA, + DOTG_STG_STATUS, + DOTG_STG_COUNT, +} __packed; + +/* Stage structure. */ +struct dotg_stage { + dotg_stg_type_t type; /* stage type */ + void *data; /* data to transfer */ + size_t size; /* payload size */ + uint16_t pktcnt; /* number of packets composing the stage */ + dotg_pid_t pid; /* PID */ + usb_direction_t dir; /* transfer direction */ +}; + +/* clang-format off */ + +/* Transaction status. */ +enum dotg_txn_status { + DOTG_TXN_UNKNOWN, /* unknown status */ + DOTG_TXN_PENDING, /* waiting to be scheduled */ + DOTG_TXN_DISABLING_CHAN, /* waiting for the channel to be disabled */ + DOTG_TXN_TRANSFERING, /* waiting for the transfer to complete */ + DOTG_TXN_FINISHED, /* transaction's finished */ +} __packed; + +/* clang-format on */ + +/* Transaction structure. */ +struct dotg_txn { + usb_device_t *udev; /* USB device requesting the transaction */ + usb_buf_t *buf; /* USB buffer associated with the transaction */ + dotg_stage_t *stages[DOTG_STG_COUNT]; /* stages composing the transaction */ + uint8_t nstages; /* number of stages */ + uint8_t curr; /* current stage index */ + dotg_txn_status_t status; /* transaction status */ +}; + +/* DWC OTG callout arguments. */ +struct dotg_callout_args { + dotg_state_t *dotg; /* host controller state */ +}; + +/* Host channel structure. */ +struct dotg_chan { + callout_t callout; /* callout used for rescheduling periodic requests */ + dotg_callout_args_t args; /* callout arguments */ + void *buf; /* contiguous memory for DMA transfers */ + paddr_t buf_pa; /* buffer's physicall address */ + dotg_txn_t *txn; /* current transaction */ + uint8_t num; /* channel's sequential number in the host system */ +}; + +/* Controller's software context. */ +struct dotg_state { + condvar_t chan_cv; /* wait for a free channel */ + spin_t chan_lock; /* guards the host channel map and interrupt reg */ + bitstr_t *chan_map; /* bitmap of used host channels */ + + condvar_t txn_cv; /* wait for any transaction to schedule */ + spin_t txn_lock; /* transaction ring buffer lock */ + ringbuf_t txn_rb; /* ring buffer of pending transactions */ + + dotg_chan_t *chans; /* provided host channels */ + thread_t *thread; /* transaction scheduler */ + resource_t *mem; /* MMIO handle */ + resource_t *irq; /* IRQ handle */ + uint8_t nchans; /* number of provided host channels */ +}; + +#endif /* _DEV_DWC_OTG_ */ diff --git a/include/dev/dwc_otgreg.h b/include/dev/dwc_otgreg.h new file mode 100644 index 0000000000..957b4c7f81 --- /dev/null +++ b/include/dev/dwc_otgreg.h @@ -0,0 +1,532 @@ +/* $FreeBSD$ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2010,2011 Aleksandr Rybalko. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _DEV_DWC_OTGREG_H_ +#define _DEV_DWC_OTGREG_H_ + +#define DOTG_GOTGCTL 0x0000 +#define DOTG_GOTGINT 0x0004 +#define DOTG_GAHBCFG 0x0008 +#define DOTG_GUSBCFG 0x000C +#define DOTG_GRSTCTL 0x0010 +#define DOTG_GINTSTS 0x0014 +#define DOTG_GINTMSK 0x0018 +#define DOTG_GRXSTSRD 0x001C +#define DOTG_GRXSTSRH 0x001C +#define DOTG_GRXSTSPD 0x0020 +#define DOTG_GRXSTSPH 0x0020 +#define DOTG_GRXFSIZ 0x0024 +#define DOTG_GNPTXFSIZ 0x0028 +#define DOTG_GNPTXSTS 0x002C +#define DOTG_GI2CCTL 0x0030 +#define DOTG_GPVNDCTL 0x0034 +#define DOTG_GGPIO 0x0038 +#define DOTG_GUID 0x003C +#define DOTG_GSNPSID 0x0040 +#define DOTG_GSNPSID_REV_2_71a 0x4f54271a +#define DOTG_GSNPSID_REV_2_80a 0x4f54280a +#define DOTG_GSNPSID_REV_2_90a 0x4f54290a +#define DOTG_GSNPSID_REV_2_92a 0x4f54292a +#define DOTG_GSNPSID_REV_2_94a 0x4f54294a +#define DOTG_GSNPSID_REV_3_00a 0x4f54300a +#define DOTG_GSNPSID_REV_3_10a 0x4f54310a +#define DOTG_GHWCFG1 0x0044 +#define DOTG_GHWCFG2 0x0048 +#define DOTG_GHWCFG3 0x004C +#define DOTG_GHWCFG4 0x0050 +#define DOTG_GLPMCFG 0x0054 +#define DOTG_GPWRDN 0x0058 +#define DOTG_GDFIFOCFG 0x005C +#define DOTG_GADPCTL 0x0060 + +#define DOTG_HPTXFSIZ 0x0100 + +#define DOTG_HCFG 0x0400 +#define DOTG_HFIR 0x0404 +#define DOTG_HFNUM 0x0408 +#define DOTG_HPTXSTS 0x0410 +#define DOTG_HAINT 0x0414 +#define DOTG_HAINTMSK 0x0418 +#define DOTG_HPRT 0x0440 + +#define DOTG_HCCHAR(ch) (0x0500 + (32 * (ch))) +#define DOTG_HCSPLT(ch) (0x0504 + (32 * (ch))) +#define DOTG_HCINT(ch) (0x0508 + (32 * (ch))) +#define DOTG_HCINTMSK(ch) (0x050C + (32 * (ch))) +#define DOTG_HCTSIZ(ch) (0x0510 + (32 * (ch))) +#define DOTG_HCDMA(ch) (0x0514 + (32 * (ch))) +#define DOTG_HCDMAI(ch) (0x0514 + (32 * (ch))) +#define DOTG_HCDMAO(ch) (0x0514 + (32 * (ch))) +#define DOTG_HCDMAB(ch) (0x051C + (32 * (ch))) + +/* Power and clock gating CSR */ +#define DOTG_PCGCCTL 0x0E00 + +#define GOTGCTL_CHIRP_ON (1 << 27) +#define GOTGCTL_BSESVLD (1 << 19) +#define GOTGCTL_ASESVLD (1 << 18) +#define GOTGCTL_DBNCTIME (1 << 17) +#define GOTGCTL_CONIDSTS (1 << 16) +#define GOTGCTL_DEVHNPEN (1 << 11) +#define GOTGCTL_HSTSETHNPEN (1 << 10) +#define GOTGCTL_HNPREQ (1 << 9) +#define GOTGCTL_HSTNEGSCS (1 << 8) +#define GOTGCTL_SESREQ (1 << 1) +#define GOTGCTL_SESREQSCS (1 << 0) + +#define GOTGCTL_DBNCEDONE (1 << 19) +#define GOTGCTL_ADEVTOUTCHG (1 << 18) +#define GOTGCTL_HSTNEGDET (1 << 17) +#define GOTGCTL_HSTNEGSUCSTSCHG (1 << 9) +#define GOTGCTL_SESREQSUCSTSCHG (1 << 8) +#define GOTGCTL_SESENDDET (1 << 2) + +#define GAHBCFG_PTXFEMPLVL (1 << 8) +#define GAHBCFG_NPTXFEMPLVL (1 << 7) +#define GAHBCFG_DMAEN (1 << 5) +#define GAHBCFG_HBSTLEN_MASK 0x0000001e +#define GAHBCFG_HBSTLEN_SHIFT 1 +#define GAHBCFG_GLBLINTRMSK (1 << 0) + +#define GUSBCFG_CORRUPTTXPACKET (1 << 31) +#define GUSBCFG_FORCEDEVMODE (1 << 30) +#define GUSBCFG_FORCEHOSTMODE (1 << 29) +#define GUSBCFG_NO_PULLUP (1 << 27) +#define GUSBCFG_IC_USB_CAP (1 << 26) +#define GUSBCFG_TERMSELDLPULSE (1 << 22) +#define GUSBCFG_ULPIEXTVBUSINDICATOR (1 << 21) +#define GUSBCFG_ULPIEXTVBUSDRV (1 << 20) +#define GUSBCFG_ULPICLKSUSM (1 << 19) +#define GUSBCFG_ULPIAUTORES (1 << 18) +#define GUSBCFG_ULPIFSLS (1 << 17) +#define GUSBCFG_OTGI2CSEL (1 << 16) +#define GUSBCFG_PHYLPWRCLKSEL (1 << 15) +#define GUSBCFG_USBTRDTIM_MASK 0x00003c00 +#define GUSBCFG_USBTRDTIM_SHIFT 10 +#define GUSBCFG_TRD_TIM_SET(x) (((x)&15) << 10) +#define GUSBCFG_HNPCAP (1 << 9) +#define GUSBCFG_SRPCAP (1 << 8) +#define GUSBCFG_DDRSEL (1 << 7) +#define GUSBCFG_PHYSEL (1 << 6) +#define GUSBCFG_FSINTF (1 << 5) +#define GUSBCFG_ULPI_UTMI_SEL (1 << 4) +#define GUSBCFG_PHYIF (1 << 3) +#define GUSBCFG_TOUTCAL_MASK 0x00000007 +#define GUSBCFG_TOUTCAL_SHIFT 0 + +#define GRSTCTL_AHBIDLE (1 << 31) +#define GRSTCTL_DMAREQ (1 << 30) +#define GRSTCTL_TXFNUM_MASK 0x000007c0 +#define GRSTCTL_TXFNUM_SHIFT 6 +#define GRSTCTL_TXFIFO(n) (((n)&31) << 6) +#define GRSTCTL_TXFFLSH (1 << 5) +#define GRSTCTL_RXFFLSH (1 << 4) +#define GRSTCTL_INTKNQFLSH (1 << 3) +#define GRSTCTL_FRMCNTRRST (1 << 2) +#define GRSTCTL_HSFTRST (1 << 1) +#define GRSTCTL_CSFTRST (1 << 0) + +#define GINTSTS_ALL (-1) +#define GINTSTS_WKUPINT (1 << 31) +#define GINTSTS_SESSREQINT (1 << 30) +#define GINTSTS_DISCONNINT (1 << 29) +#define GINTSTS_CONIDSTSCHNG (1 << 28) +#define GINTSTS_LPM (1 << 27) +#define GINTSTS_PTXFEMP (1 << 26) +#define GINTSTS_HCHINT (1 << 25) +#define GINTSTS_PRTINT (1 << 24) +#define GINTSTS_RESETDET (1 << 23) +#define GINTSTS_FETSUSP (1 << 22) +#define GINTSTS_INCOMPLP (1 << 21) +#define GINTSTS_INCOMPISOIN (1 << 20) +#define GINTSTS_OEPINT (1 << 19) +#define GINTSTS_IEPINT (1 << 18) +#define GINTSTS_EPMIS (1 << 17) +#define GINTSTS_RESTORE_DONE (1 << 16) +#define GINTSTS_EOPF (1 << 15) +#define GINTSTS_ISOOUTDROP (1 << 14) +#define GINTSTS_ENUMDONE (1 << 13) +#define GINTSTS_USBRST (1 << 12) +#define GINTSTS_USBSUSP (1 << 11) +#define GINTSTS_ERLYSUSP (1 << 10) +#define GINTSTS_I2CINT (1 << 9) +#define GINTSTS_ULPICKINT (1 << 8) +#define GINTSTS_GOUTNAKEFF (1 << 7) +#define GINTSTS_GINNAKEFF (1 << 6) +#define GINTSTS_NPTXFEMP (1 << 5) +#define GINTSTS_RXFLVL (1 << 4) +#define GINTSTS_SOF (1 << 3) +#define GINTSTS_OTGINT (1 << 2) +#define GINTSTS_MODEMIS (1 << 1) +#define GINTSTS_CURMOD (1 << 0) + +#define GINTMSK_ALL (-1) +#define GINTMSK_WKUPINTMSK (1 << 31) +#define GINTMSK_SESSREQINTMSK (1 << 30) +#define GINTMSK_DISCONNINTMSK (1 << 29) +#define GINTMSK_CONIDSTSCHNGMSK (1 << 28) +#define GINTMSK_PTXFEMPMSK (1 << 26) +#define GINTMSK_HCHINTMSK (1 << 25) +#define GINTMSK_PRTINTMSK (1 << 24) +#define GINTMSK_FETSUSPMSK (1 << 22) +#define GINTMSK_INCOMPLPMSK (1 << 21) +#define GINTMSK_INCOMPISOINMSK (1 << 20) +#define GINTMSK_OEPINTMSK (1 << 19) +#define GINTMSK_IEPINTMSK (1 << 18) +#define GINTMSK_EPMISMSK (1 << 17) +#define GINTMSK_EOPFMSK (1 << 15) +#define GINTMSK_ISOOUTDROPMSK (1 << 14) +#define GINTMSK_ENUMDONEMSK (1 << 13) +#define GINTMSK_USBRSTMSK (1 << 12) +#define GINTMSK_USBSUSPMSK (1 << 11) +#define GINTMSK_ERLYSUSPMSK (1 << 10) +#define GINTMSK_I2CINTMSK (1 << 9) +#define GINTMSK_ULPICKINTMSK (1 << 8) +#define GINTMSK_GOUTNAKEFFMSK (1 << 7) +#define GINTMSK_GINNAKEFFMSK (1 << 6) +#define GINTMSK_NPTXFEMPMSK (1 << 5) +#define GINTMSK_RXFLVLMSK (1 << 4) +#define GINTMSK_SOFMSK (1 << 3) +#define GINTMSK_OTGINTMSK (1 << 2) +#define GINTMSK_MODEMISMSK (1 << 1) +#define GINTMSK_CURMODMSK (1 << 0) + +#define GRXSTSRH_PKTSTS_MASK 0x001e0000 +#define GRXSTSRH_PKTSTS_SHIFT 17 +#define GRXSTSRH_DPID_MASK 0x00018000 +#define GRXSTSRH_DPID_SHIFT 15 +#define GRXSTSRH_BCNT_MASK 0x00007ff0 +#define GRXSTSRH_BCNT_SHIFT 4 +#define GRXSTSRH_CHNUM_MASK 0x0000000f +#define GRXSTSRH_CHNUM_SHIFT 0 + +#define GRXSTSRD_FN_MASK 0x01e00000 +#define GRXSTSRD_FN_GET(x) (((x) >> 21) & 15) +#define GRXSTSRD_FN_SHIFT 21 +#define GRXSTSRD_PKTSTS_MASK 0x001e0000 +#define GRXSTSRD_PKTSTS_SHIFT 17 +#define GRXSTSRH_IN_DATA (2 << 17) +#define GRXSTSRH_IN_COMPLETE (3 << 17) +#define GRXSTSRH_DT_ERROR (5 << 17) +#define GRXSTSRH_HALTED (7 << 17) +#define GRXSTSRD_GLOB_OUT_NAK (1 << 17) +#define GRXSTSRD_OUT_DATA (2 << 17) +#define GRXSTSRD_OUT_COMPLETE (3 << 17) +#define GRXSTSRD_STP_COMPLETE (4 << 17) +#define GRXSTSRD_STP_DATA (6 << 17) +#define GRXSTSRD_DPID_MASK 0x00018000 +#define GRXSTSRD_DPID_SHIFT 15 +#define GRXSTSRD_DPID_DATA0 (0 << 15) +#define GRXSTSRD_DPID_DATA1 (2 << 15) +#define GRXSTSRD_DPID_DATA2 (1 << 15) +#define GRXSTSRD_DPID_MDATA (3 << 15) +#define GRXSTSRD_BCNT_MASK 0x00007ff0 +#define GRXSTSRD_BCNT_GET(x) (((x) >> 4) & 0x7FF) +#define GRXSTSRD_BCNT_SHIFT 4 +#define GRXSTSRD_CHNUM_MASK 0x0000000f +#define GRXSTSRD_CHNUM_GET(x) ((x)&15) +#define GRXSTSRD_CHNUM_SHIFT 0 + +#define GRXFSIZ_RXFDEP_MASK 0x0000ffff +#define GRXFSIZ_RXFDEP_SHIFT 0 + +#define GNPTXFSIZ_NPTXFDEP_MASK 0xffff0000 +#define GNPTXFSIZ_NPTXFDEP_SHIFT 16 +#define GNPTXFSIZ_NPTXFSTADDR_MASK 0x0000ffff +#define GNPTXFSIZ_NPTXFSTADDR_SHIFT 0 + +#define GNPTXSTS_NPTXQTOP_SHIFT 24 +#define GNPTXSTS_NPTXQTOP_MASK 0x7f000000 +#define GNPTXSTS_NPTXQSPCAVAIL_SHIFT 16 +#define GNPTXSTS_NPTXQSPCAVAIL_MASK 0x00ff0000 +#define GNPTXSTS_NPTXFSPCAVAIL_SHIFT 0 +#define GNPTXSTS_NPTXFSPCAVAIL_MASK 0x0000ffff + +#define GI2CCTL_BSYDNE_SC (1 << 31) +#define GI2CCTL_RW (1 << 30) +#define GI2CCTL_I2CDATSE0 (1 << 28) +#define GI2CCTL_I2CDEVADR_SHIFT 26 +#define GI2CCTL_I2CDEVADR_MASK 0x0c000000 +#define GI2CCTL_I2CSUSPCTL (1 << 25) +#define GI2CCTL_ACK (1 << 24) +#define GI2CCTL_I2CEN (1 << 23) +#define GI2CCTL_ADDR_SHIFT 16 +#define GI2CCTL_ADDR_MASK 0x007f0000 +#define GI2CCTL_REGADDR_SHIFT 8 +#define GI2CCTL_REGADDR_MASK 0x0000ff00 +#define GI2CCTL_RWDATA_SHIFT 0 +#define GI2CCTL_RWDATA_MASK 0x000000ff + +#define GPVNDCTL_DISULPIDRVR (1 << 31) +#define GPVNDCTL_VSTSDONE (1 << 27) +#define GPVNDCTL_VSTSBSY (1 << 26) +#define GPVNDCTL_NEWREGREQ (1 << 25) +#define GPVNDCTL_REGWR (1 << 22) +#define GPVNDCTL_REGADDR_SHIFT 16 +#define GPVNDCTL_REGADDR_MASK 0x003f0000 +#define GPVNDCTL_VCTRL_SHIFT 8 +#define GPVNDCTL_VCTRL_MASK 0x0000ff00 +#define GPVNDCTL_REGDATA_SHIFT 0 +#define GPVNDCTL_REGDATA_MASK 0x000000ff + +#define GGPIO_GPO_SHIFT 16 +#define GGPIO_GPO_MASK 0xffff0000 +#define GGPIO_GPI_SHIFT 0 +#define GGPIO_GPI_MASK 0x0000ffff + +#define GHWCFG1_GET_DIR(x, n) (((x) >> (2 * (n))) & 3) +#define GHWCFG1_BIDIR 0 +#define GHWCFG1_IN 1 +#define GHWCFG1_OUT 2 + +#define GHWCFG2_TKNQDEPTH_SHIFT 26 +#define GHWCFG2_TKNQDEPTH_MASK 0x7c000000 +#define GHWCFG2_PTXQDEPTH_SHIFT 24 +#define GHWCFG2_PTXQDEPTH_MASK 0x03000000 +#define GHWCFG2_NPTXQDEPTH_SHIFT 22 +#define GHWCFG2_NPTXQDEPTH_MASK 0x00c00000 +#define GHWCFG2_MPI (1 << 20) +#define GHWCFG2_DYNFIFOSIZING (1 << 19) +#define GHWCFG2_PERIOSUPPORT (1 << 18) +#define GHWCFG2_NUMHSTCHNL_SHIFT 14 +#define GHWCFG2_NUMHSTCHNL_MASK 0x0003c000 +#define GHWCFG2_NUMHSTCHNL_GET(x) ((((x) >> 14) & 15) + 1) +#define GHWCFG2_NUMDEVEPS_SHIFT 10 +#define GHWCFG2_NUMDEVEPS_MASK 0x00003c00 +#define GHWCFG2_NUMDEVEPS_GET(x) ((((x) >> 10) & 15) + 1) +#define GHWCFG2_FSPHYTYPE_SHIFT 8 +#define GHWCFG2_FSPHYTYPE_MASK 0x00000300 +#define GHWCFG2_HSPHYTYPE_SHIFT 6 +#define GHWCFG2_HSPHYTYPE_MASK 0x000000c0 +#define GHWCFG2_SINGPNT (1 << 5) +#define GHWCFG2_OTGARCH_SHIFT 3 +#define GHWCFG2_OTGARCH_MASK 0x00000018 +#define GHWCFG2_OTGARCH_GET(x) (((x) >> 3) & 3) +#define GHWCFG2_OTGARCH_INTERNAL_DMA 2 +#define GHWCFG2_OTGMODE_SHIFT 0 +#define GHWCFG2_OTGMODE_MASK 0x00000007 + +#define GHWCFG3_DFIFODEPTH_SHIFT 16 +#define GHWCFG3_DFIFODEPTH_MASK 0xffff0000 +#define GHWCFG3_DFIFODEPTH_GET(x) ((x) >> 16) +#define GHWCFG3_RSTTYPE (1 << 11) +#define GHWCFG3_OPTFEATURE (1 << 10) +#define GHWCFG3_VNDCTLSUPT (1 << 9) +#define GHWCFG3_I2CINTSEL (1 << 8) +#define GHWCFG3_OTGEN (1 << 7) +#define GHWCFG3_PKTSIZEWIDTH_SHIFT 4 +#define GHWCFG3_PKTSIZEWIDTH_MASK 0x00000070 +#define GHWCFG3_PKTSIZE_GET(x) (0x10 << (((x) >> 4) & 7)) +#define GHWCFG3_XFERSIZEWIDTH_SHIFT 0 +#define GHWCFG3_XFERSIZEWIDTH_MASK 0x0000000f +#define GHWCFG3_XFRRSIZE_GET(x) (0x400 << (((x) >> 0) & 15)) + +#define GHWCFG4_NUM_IN_EP_GET(x) ((((x) >> 26) & 15) + 1) +#define GHWCFG4_SESSENDFLTR (1 << 24) +#define GHWCFG4_BVALIDFLTR (1 << 23) +#define GHWCFG4_AVALIDFLTR (1 << 22) +#define GHWCFG4_VBUSVALIDFLTR (1 << 21) +#define GHWCFG4_IDDGFLTR (1 << 20) +#define GHWCFG4_NUMCTLEPS_SHIFT 16 +#define GHWCFG4_NUMCTLEPS_MASK 0x000f0000 +#define GHWCFG4_NUMCTLEPS_GET(x) (((x) >> 16) & 15) +#define GHWCFG4_PHYDATAWIDTH_SHIFT 14 +#define GHWCFG4_PHYDATAWIDTH_MASK 0x0000c000 +#define GHWCFG4_AHBFREQ (1 << 5) +#define GHWCFG4_ENABLEPWROPT (1 << 4) +#define GHWCFG4_NUMDEVPERIOEPS_SHIFT 0 +#define GHWCFG4_NUMDEVPERIOEPS_MASK 0x0000000f +#define GHWCFG4_NUMDEVPERIOEPS_GET(x) (((x) >> 0) & 15) + +#define GLPMCFG_HSIC_CONN (1 << 30) + +#define GPWRDN_BVALID (1 << 22) +#define GPWRDN_IDDIG (1 << 21) +#define GPWRDN_CONNDET_INT (1 << 14) +#define GPWRDN_CONNDET (1 << 13) +#define GPWRDN_DISCONN_INT (1 << 12) +#define GPWRDN_DISCONN (1 << 11) +#define GPWRDN_RESETDET_INT (1 << 10) +#define GPWRDN_RESETDET (1 << 9) +#define GPWRDN_LINESTATE_INT (1 << 8) +#define GPWRDN_LINESTATE (1 << 7) +#define GPWRDN_DISABLE_VBUS (1 << 6) +#define GPWRDN_POWER_DOWN (1 << 5) +#define GPWRDN_POWER_DOWN_RST (1 << 4) +#define GPWRDN_POWER_DOWN_CLAMP (1 << 3) +#define GPWRDN_RESTORE (1 << 2) +#define GPWRDN_PMU_ACTIVE (1 << 1) +#define GPWRDN_PMU_IRQ_SEL (1 << 0) + +#define HPTXFSIZ_PTXFSIZE_SHIFT 16 +#define HPTXFSIZ_PTXFSIZE_MASK 0xffff0000 +#define HPTXFSIZ_PTXFSTADDR_SHIFT 0 +#define HPTXFSIZ_PTXFSTADDR_MASK 0x0000ffff + +#define HCFG_MODECHANGERDY (1 << 31) +#define HCFG_PERSCHEDENABLE (1 << 26) +#define HCFG_FLENTRIES_SHIFT 24 +#define HCFG_FLENTRIES_MASK 0x03000000 +#define HCFG_FLENTRIES_8 (0) +#define HCFG_FLENTRIES_16 (1) +#define HCFG_FLENTRIES_32 (2) +#define HCFG_FLENTRIES_64 (3) +#define HCFG_MULTISEGDMA (1 << 23) +#define HCFG_32KHZSUSPEND (1 << 7) +#define HCFG_FSLSSUPP (1 << 2) +#define HCFG_FSLSPCLKSEL_SHIFT 0 +#define HCFG_FSLSPCLKSEL_MASK 0x00000003 + +#define HFIR_RELOADCTRL (1 << 16) +#define HFIR_FRINT_SHIFT 0 +#define HFIR_FRINT_MASK 0x0000ffff + +#define HFNUM_FRREM_SHIFT 16 +#define HFNUM_FRREM_MASK 0xffff0000 +#define HFNUM_FRNUM_SHIFT 0 +#define HFNUM_FRNUM_MASK 0x0000ffff + +#define HPTXSTS_ODD (1 << 31) +#define HPTXSTS_CHAN_SHIFT 27 +#define HPTXSTS_CHAN_MASK 0x78000000 +#define HPTXSTS_TOKEN_SHIFT 25 +#define HPTXSTS_TOKEN_MASK 0x06000000 +#define HPTXSTS_TOKEN_ZL 0 +#define HPTXSTS_TOKEN_PING 1 +#define HPTXSTS_TOKEN_DISABLE 2 +#define HPTXSTS_TERMINATE (1 << 24) +#define HPTXSTS_PTXQSPCAVAIL_SHIFT 16 +#define HPTXSTS_PTXQSPCAVAIL_MASK 0x00ff0000 +#define HPTXSTS_PTXFSPCAVAIL_SHIFT 0 +#define HPTXSTS_PTXFSPCAVAIL_MASK 0x0000ffff + +#define HAINT_HAINT_SHIFT 0 +#define HAINT_HAINT_MASK 0x0000ffff +#define HAINTMSK_HAINTMSK_SHIFT 0 +#define HAINTMSK_HAINTMSK_MASK 0x0000ffff + +#define HPRT_PRTSPD_SHIFT 17 +#define HPRT_PRTSPD_MASK 0x00060000 +#define HPRT_PRTSPD_GET(x) (((x) >> 17) & 3) +#define HPRT_PRTSPD_HIGH 0 +#define HPRT_PRTSPD_FULL 1 +#define HPRT_PRTSPD_LOW 2 +#define HPRT_PRTSPD_MASK 0x00060000 +#define HPRT_PRTTSTCTL_SHIFT 13 +#define HPRT_PRTTSTCTL_MASK 0x0001e000 +#define HPRT_PRTPWR (1 << 12) +#define HPRT_PRTLNSTS_SHIFT 10 +#define HPRT_PRTLNSTS_MASK 0x00000c00 +#define HPRT_PRTRST (1 << 8) +#define HPRT_PRTSUSP (1 << 7) +#define HPRT_PRTRES (1 << 6) +#define HPRT_PRTOVRCURRCHNG (1 << 5) +#define HPRT_PRTOVRCURRACT (1 << 4) +#define HPRT_PRTENCHNG (1 << 3) +#define HPRT_PRTENA (1 << 2) +#define HPRT_PRTCONNDET (1 << 1) +#define HPRT_PRTCONNSTS (1 << 0) + +#define HCCHAR_CHENA (1 << 31) +#define HCCHAR_CHDIS (1 << 30) +#define HCCHAR_ODDFRM (1 << 29) +#define HCCHAR_DEVADDR_SHIFT 22 +#define HCCHAR_DEVADDR_MASK 0x1fc00000 +#define HCCHAR_MC_SHIFT 20 +#define HCCHAR_MC_MASK 0x00300000 +#define HCCHAR_EPTYPE_SHIFT 18 +#define HCCHAR_EPTYPE_MASK 0x000c0000 +#define HCCHAR_LSPDDEV (1 << 17) +#define HCCHAR_EPDIR_SHIFT 15 +#define HCCHAR_EPDIR_OUT 0 +#define HCCHAR_EPDIR_IN 1 +#define HCCHAR_EPNUM_SHIFT 11 +#define HCCHAR_EPNUM_MASK 0x00007800 +#define HCCHAR_MPS_SHIFT 0 +#define HCCHAR_MPS_MASK 0x000007ff + +#define HCSPLT_SPLTENA (1 << 31) +#define HCSPLT_COMPSPLT (1 << 16) +#define HCSPLT_XACTPOS_SHIFT 14 +#define HCSPLT_XACTPOS_MASK 0x0000c000 +#define HCSPLT_XACTPOS_MIDDLE 0 +#define HCSPLT_XACTPOS_LAST 1 +#define HCSPLT_XACTPOS_BEGIN 2 +#define HCSPLT_XACTPOS_ALL 3 +#define HCSPLT_XACTLEN_BURST 1023 /* bytes */ +#define HCSPLT_HUBADDR_SHIFT 7 +#define HCSPLT_HUBADDR_MASK 0x00003f80 +#define HCSPLT_PRTADDR_SHIFT 0 +#define HCSPLT_PRTADDR_MASK 0x0000007f + +#define HCINT_ERROR \ + (HCINT_AHBERR | HCINT_STALL | HCINT_XACTERR | HCINT_BBLERR | \ + HCINT_FRMOVRUN | HCINT_DATATGLERR) +#define HCINT_ALL (-1) + +#define HCINT_DATATGLERR (1 << 10) +#define HCINT_FRMOVRUN (1 << 9) +#define HCINT_BBLERR (1 << 8) +#define HCINT_XACTERR (1 << 7) +#define HCINT_NYET (1 << 6) +#define HCINT_ACK (1 << 5) +#define HCINT_NAK (1 << 4) +#define HCINT_STALL (1 << 3) +#define HCINT_AHBERR (1 << 2) +#define HCINT_CHHLTD (1 << 1) +#define HCINT_XFERCOMPL (1 << 0) + +#define HCINTMSK_DATATGLERRMSK (1 << 10) +#define HCINTMSK_FRMOVRUNMSK (1 << 9) +#define HCINTMSK_BBLERRMSK (1 << 8) +#define HCINTMSK_XACTERRMSK (1 << 7) +#define HCINTMSK_NYETMSK (1 << 6) +#define HCINTMSK_ACKMSK (1 << 5) +#define HCINTMSK_NAKMSK (1 << 4) +#define HCINTMSK_STALLMSK (1 << 3) +#define HCINTMSK_AHBERRMSK (1 << 2) +#define HCINTMSK_CHHLTDMSK (1 << 1) +#define HCINTMSK_XFERCOMPLMSK (1 << 0) + +#define HCTSIZ_DOPNG (1 << 31) +#define HCTSIZ_PID_SHIFT 29 +#define HCTSIZ_PID_MASK 0x60000000 +#define HCTSIZ_PID_DATA0 +#define HCTSIZ_PID_DATA2 1 +#define HCTSIZ_PID_DATA1 2 +#define HCTSIZ_PID_MDATA 3 +#define HCTSIZ_PID_SETUP 3 +#define HCTSIZ_PKTCNT_SHIFT 19 +#define HCTSIZ_PKTCNT_MASK 0x1ff80000 +#define HCTSIZ_PKTCNT_MAX 0x3ff +#define HCTSIZ_XFERSIZE_SHIFT 0 +#define HCTSIZ_XFERSIZE_MASK 0x0007ffff +#define HCTSIZ_XFERSIZE_MAX 0x0007ffff + +#endif /* _DEV_DWC_OTGREG_H_ */ diff --git a/include/dev/usb.h b/include/dev/usb.h index 60decac6b3..216ecffb02 100644 --- a/include/dev/usb.h +++ b/include/dev/usb.h @@ -194,15 +194,16 @@ typedef enum usb_transfer { USB_TFR_INTERRUPT = 4, } __packed usb_transfer_t; +/* Don't alter the following values! */ typedef enum usb_direction { - USB_DIR_INPUT, - USB_DIR_OUTPUT, + USB_DIR_OUTPUT = 0, + USB_DIR_INPUT = 1, } __packed usb_direction_t; -/* XXX: FTTB, we only handle low and full speed devices. */ typedef enum usb_speed { USB_SPD_LOW, USB_SPD_FULL, + USB_SPD_HIGH, } __packed usb_speed_t; /* USB string kinds. */ @@ -243,7 +244,7 @@ typedef struct usb_device { typedef struct usb_buf { condvar_t cv; /* wait for the transfer to complete */ spin_t lock; /* buffer guard */ - usb_endpt_t *endpt; /* device's endpoint we're talking with */ + usb_endpt_t *endpt; /* data stage destination endpoint */ void *data; /* data buffer */ void *priv; /* buffer's private data (do not alter!) */ uint16_t transfer_size; /* size of data to transfer in the data stage */ diff --git a/launch b/launch index 186c9db3b8..6318609a65 100755 --- a/launch +++ b/launch @@ -95,7 +95,7 @@ CONFIG = { ] }, 'rpi3': { - 'binary': 'qemu-mimiker-aarch64', + 'binary': '../qemu-mimiker-aarch64', 'options': [ '-machine', 'raspi3', '-smp', '4', diff --git a/sys/drv/Makefile b/sys/drv/Makefile index c282fe9282..d30af440c6 100644 --- a/sys/drv/Makefile +++ b/sys/drv/Makefile @@ -29,7 +29,9 @@ SOURCES-MIPS = \ SOURCES-AARCH64 = \ bcm2835_gpio.c \ bcm2835_rootdev.c \ - pl011.c + dwc_otg.c \ + pl011.c \ + usb.c CPPFLAGS += -D_MACHDEP diff --git a/sys/drv/bcm2835_rootdev.c b/sys/drv/bcm2835_rootdev.c index 2da19f5119..533d4d5e11 100644 --- a/sys/drv/bcm2835_rootdev.c +++ b/sys/drv/bcm2835_rootdev.c @@ -229,6 +229,12 @@ static int rootdev_attach(device_t *bus) { BCM2835_UART0_SIZE); device_add_irq(dev, 0, BCM2835_INT_UART0); + /* Create DWC OTG device and assign resources to it. */ + dev = device_add_child(bus, 2); + device_add_memory(dev, 0, BCM2835_PERIPHERALS_BUS_TO_PHYS(BCM2835_USB_BASE), + BCM2835_USB_SIZE); + device_add_irq(dev, 0, BCM2835_INT_USB); + /* TODO: replace raw resource assignments by parsing FDT file. */ return bus_generic_probe(bus); diff --git a/sys/drv/dwc_otg.c b/sys/drv/dwc_otg.c new file mode 100644 index 0000000000..29402df7ad --- /dev/null +++ b/sys/drv/dwc_otg.c @@ -0,0 +1,799 @@ +#define KL_LOG KL_DEV +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The following definitions assume an implicit `dotg` argument. + */ + +/* Read the specified DWC OTG register. */ +#define in(addr) bus_read_4(dotg->mem, (addr)) + +/* Write the specified DWC OTG register. */ +#define out(addr, val) bus_write_4(dotg->mem, (addr), (val)) + +/* Set the specified bit in the given DWC OTG register. */ +#define set(r, b) out((r), in(r) | (b)) + +/* Clear the specified bit in the given DWC OTG register. */ +#define clr(r, b) out((r), in(r) & ~(b)) + +/* Clear the specified bit in the given DWC OTG write clear register. */ +#define wclr(r, b) set((r), b) + +/* Check if the specified bit is set in the given DWC OTG register. */ +#define chk(r, b) ((in(r) & (b)) != 0) + +static void dotg_callout(void *arg); +static intr_filter_t dotg_isr(void *arg); +static void dotg_thread(void *arg); + +/* + * Auxiliary functions. + */ + +/* Wait for the specified bit in the given DWC OTG register to establish + * the designated state. */ +static int dotg_wait(dotg_state_t *dotg, int reg, uint32_t bit, uint8_t state, + systime_t ms) { + mdelay(ms); + if (chk(reg, bit) ^ state) + return 1; + return 0; +} + +/* Convert host controller error flags into USB bus error flags. */ +static usb_error_t dotge2usbe(uint32_t error) { + usb_error_t uerr = 0; + + /* XXX: FTTB, we only distinguish a STALL condition. */ + if (error & HCINT_STALL) + uerr |= USB_ERR_STALLED; + + /* Signal any other errors. */ + error &= ~HCINT_STALL; + if (error) + uerr |= USB_ERR_OTHER; + + return uerr; +} + +/* + * Host controller interface port functions. + */ + +static uint8_t dotg_number_of_ports(device_t *dev) { + /* DWC OTG host controller has only a single root hub port. */ + return 1; +} + +static bool dotg_device_present(device_t *dev, uint8_t port) { + assert(port == 0); + dotg_state_t *dotg = dev->state; + return chk(DOTG_HPRT, HPRT_PRTCONNSTS); +} + +static usb_speed_t dotg_device_speed(device_t *dev, uint8_t port) { + dotg_state_t *dotg = dev->state; + int speed = HPRT_PRTSPD_GET(in(DOTG_HPRT)); + + if (speed == HPRT_PRTSPD_LOW) + return USB_SPD_LOW; + else if (speed == HPRT_PRTSPD_FULL) + return USB_SPD_FULL; + + assert(speed == HPRT_PRTSPD_HIGH); + return USB_SPD_HIGH; +} + +static void dotg_reset_port(device_t *dev, uint8_t port) { + assert(port == 0); + dotg_state_t *dotg = dev->state; + + set(DOTG_HPRT, HPRT_PRTRES); + mdelay(USB_PORT_ROOT_RESET_DELAY_SPEC); + clr(DOTG_HPRT, HPRT_PRTRES); + mdelay(USB_PORT_RESET_RECOVERY_SPEC); + + /* Clear any pending host port status changes. */ + wclr(DOTG_HPRT, HPRT_PRTOVRCURRCHNG | HPRT_PRTENCHNG | HPRT_PRTCONNDET); +} + +/* + * Initialization functions. + */ + +/* Initialize provided host channles. */ +static void dotg_init_chans(dotg_state_t *dotg) { + /* Obtain the number of provided host channels. */ + uint8_t nchans = GHWCFG2_NUMHSTCHNL_GET(in(DOTG_GHWCFG2)); + + dotg->chan_map = kmalloc(M_DEV, bitstr_size(nchans), M_ZERO); + assert(dotg->chan_map); + dotg->chans = kmalloc(M_DEV, nchans * sizeof(dotg_chan_t), M_ZERO); + assert(dotg->chans); + + for (uint8_t i = 0; i < nchans; i++) { + dotg_chan_t *chan = &dotg->chans[i]; + + /* Assing channel numbers. */ + dotg->chans[i].num = i; + + /* Initiate a callout for periodic requests. */ + chan->args = (dotg_callout_args_t){ + .dotg = dotg, + }; + callout_setup(&chan->callout, dotg_callout, &chan->args); + + /* Allocate contiguous memory. */ + chan->buf = (void *)kmem_alloc_contig( + &chan->buf_pa, HCTSIZ_XFERSIZE_MAX + 1, PMAP_NOCACHE); + + /* Enable basic channel interrupts. */ + set(DOTG_HCINTMSK(i), HCINTMSK_XFERCOMPLMSK | HCINTMSK_CHHLTDMSK | + HCINTMSK_AHBERRMSK | HCINTMSK_STALLMSK | + HCINTMSK_XACTERRMSK | HCINTMSK_BBLERRMSK | + HCINTMSK_FRMOVRUNMSK | HCINTMSK_DATATGLERRMSK); + } + + dotg->nchans = nchans; +} + +/* + * Driver interface functions. + */ + +static int dotg_probe(device_t *dev) { + return dev->unit == 2; +} + +/* Identify controller's release. */ +static int dotg_check_release(dotg_state_t *dotg) { + const char *str = NULL; + + switch (in(DOTG_GSNPSID)) { + case DOTG_GSNPSID_REV_2_71a: + str = "revision 2.71a"; + break; + case DOTG_GSNPSID_REV_2_80a: + str = "revision 2.80a"; + break; + case DOTG_GSNPSID_REV_2_90a: + str = "revision 2.90a"; + break; + case DOTG_GSNPSID_REV_2_92a: + str = "revision 2.92a"; + break; + case DOTG_GSNPSID_REV_2_94a: + str = "revision 2.94a"; + break; + case DOTG_GSNPSID_REV_3_00a: + str = "revision 3.00a"; + break; + case DOTG_GSNPSID_REV_3_10a: + str = "revision 3.10a"; + break; + default: + return 1; + } + + klog("DWC OTG revision %s", str); + return 0; +} + +static int dotg_attach(device_t *dev) { + dotg_state_t *dotg = dev->state; + + /* + * Initialize the software context. + */ + cv_init(&dotg->chan_cv, "channel available"); + spin_init(&dotg->chan_lock, 0); + cv_init(&dotg->txn_cv, "pending transaction"); + spin_init(&dotg->txn_lock, 0); + + void *buf = kmalloc(M_DEV, DOTG_PNDGBUFSIZ, M_WAITOK); + assert(buf); + ringbuf_init(&dotg->txn_rb, buf, DOTG_PNDGBUFSIZ); + + dotg->mem = device_take_memory(dev, 0, RF_ACTIVE); + assert(dotg->mem); + + if (dotg_check_release(dotg)) + return ENXIO; + + /* TODO: turn on the controller via video core (mail boxes). */ + + /* Disable interrupts globally. */ + clr(DOTG_GAHBCFG, GAHBCFG_GLBLINTRMSK); + + /* Wait for the AHB master state machine to enter the IDLE condition. */ + if (dotg_wait(dotg, DOTG_GRSTCTL, GRSTCTL_AHBIDLE, 1, DOTG_AHBIDLE_DELAY)) + return ENXIO; + + /* Perform a soft reset of the core. */ + set(DOTG_GRSTCTL, GRSTCTL_CSFTRST); + if (dotg_wait(dotg, DOTG_GRSTCTL, GRSTCTL_CSFTRST, 0, USB_BUS_RESET_DELAY)) + return ENXIO; + + /* Select the 8-bit UTMI+ interface. */ + clr(DOTG_GUSBCFG, GUSBCFG_ULPI_UTMI_SEL); + clr(DOTG_GUSBCFG, GUSBCFG_PHYIF); + + /* Dsiable the Session Request Protocol (SRP). */ + clr(DOTG_GUSBCFG, GUSBCFG_SRPCAP); + + /* Disable the Host Negation Protocol (HNP). */ + clr(DOTG_GUSBCFG, GUSBCFG_HNPCAP); + + /* Use internal PHY charge pump to drive VBUS in ULPI PHY. */ + clr(DOTG_GUSBCFG, GUSBCFG_ULPIEXTVBUSDRV); + + dotg_init_chans(dotg); + + /* We rely on the internal DMA. */ + if (GHWCFG2_OTGARCH_GET(in(DOTG_GHWCFG2)) != GHWCFG2_OTGARCH_INTERNAL_DMA) + return ENXIO; + + /* Set DMA burst length to 32 bits. */ + clr(DOTG_GAHBCFG, GAHBCFG_HBSTLEN_MASK); + + /* Select DMA mode as operation mode. */ + set(DOTG_GAHBCFG, GAHBCFG_DMAEN); + + /* experiment */ + set(DOTG_GAHBCFG, (1 << 4)); + clr(DOTG_GAHBCFG, (3 << 1)); + + /* Restart the PHY clock. */ + out(DOTG_PCGCCTL, 0x00000000); + + /* Set PHY clock's frequency to 30/60 MHz. */ + clr(DOTG_HCFG, HCFG_FSLSPCLKSEL_MASK); + + /* Set receive FIFO depth. */ + out(DOTG_GRXFSIZ, DOTG_RXFSIZ); + + /* Set non-periodic transmit FIFO depth and start address. */ + out(DOTG_GNPTXFSIZ, + (DOTG_NPTXFSIZ << GNPTXFSIZ_NPTXFDEP_SHIFT) | DOTG_RXFSIZ); + + /* Set periodic transmit FIFO depth and start address. */ + out(DOTG_HPTXFSIZ, (DOTG_PTXFSIZ << HPTXFSIZ_PTXFSIZE_SHIFT) | + (DOTG_RXFSIZ + DOTG_NPTXFSIZ)); + + /* Flush receive FIFO. */ + set(DOTG_GRSTCTL, GRSTCTL_RXFFLSH); + if (dotg_wait(dotg, DOTG_GRSTCTL, GRSTCTL_RXFFLSH, 0, DOTG_RXFFLSH_DELAY)) + return ENXIO; + + /* Flush all transmit FIFOs. */ + clr(DOTG_GRSTCTL, GRSTCTL_TXFNUM_MASK); + set(DOTG_GRSTCTL, GRSTCTL_TXFIFO(DOTG_TXFMAXNUM) | GRSTCTL_TXFFLSH); + if (dotg_wait(dotg, DOTG_GRSTCTL, GRSTCTL_TXFFLSH, 0, DOTG_TXFFLSH_DELAY)) + return ENXIO; + + /* Create and schedule the scheduling thread. */ + dotg->thread = thread_create("DWC OTG", dotg_thread, dotg, + prio_ithread(PRIO_ITHRD_QTY - 1)); + sched_add(dotg->thread); + + /* Acquire the HC interrupt and register an interrupt handler. */ + dotg->irq = device_take_irq(dev, 0, RF_ACTIVE); + assert(dotg->irq); + bus_intr_setup(dev, dotg->irq, dotg_isr, NULL, dotg, "DWC OTG"); + + /* Clear pending interrupts. */ + wclr(DOTG_GINTSTS, GINTSTS_ALL); + + /* Enable host channel interrupt. */ + out(DOTG_GINTMSK, GINTMSK_HCHINTMSK); + + /* Enable interrupts globally. */ + set(DOTG_GAHBCFG, GAHBCFG_GLBLINTRMSK); + + uint32_t hprt = in(DOTG_HPRT); + klog("%x", hprt); + + /* Initialize the underlying USB bus. */ + usb_init(dev); + + /* Detect and configure attached devices. */ + int error = usb_enumerate(dev); + if (error) + bus_intr_teardown(dev, dotg->irq); + return error; +} + +/* + * Transaction stage handling functions. + */ + +/* Allocate a new stage. */ +static dotg_stage_t *dotg_stage_alloc(dotg_stg_type_t type, void *data, + size_t size, usb_endpt_t *endpt, + usb_direction_t dir) { + dotg_stage_t *stage = kmalloc(M_DEV, sizeof(dotg_stage_t), M_WAITOK); + assert(stage); + + stage->data = data; + stage->size = size; + stage->type = type; + stage->dir = dir; + + /* Obtain number of packets needed to transform the stage towards + * the destination endpoint. */ + stage->pktcnt = roundup(stage->size, endpt->maxpkt) / endpt->maxpkt; + assert(stage->pktcnt <= HCTSIZ_PKTCNT_MAX); + + /* Obtain the corresponding PID. */ + if (type == DOTG_STG_SETUP) { + stage->pid = DOTG_PID_SETUP; + } else if (type == DOTG_STG_DATA) { + if (!endpt->addr) + stage->pid = DOTG_PID_DATA1; + else + stage->pid = DOTG_PID_DATA0; + } else { + assert(type == DOTG_STG_STATUS); + stage->pid = DOTG_PID_DATA1; + } + + return stage; +} + +/* Release a stage. */ +static void dotg_stage_free(dotg_stage_t *stage) { + kfree(M_DEV, stage); +} + +/* + * Transaction handling functions. + */ + +/* Alloc a new transaction. */ +static dotg_txn_t *dotg_txn_alloc(usb_device_t *udev, usb_buf_t *buf) { + dotg_txn_t *txn = kmalloc(M_DEV, sizeof(dotg_txn_t), M_ZERO); + assert(txn); + + txn->udev = udev; + txn->buf = buf; + txn->status = DOTG_TXN_UNKNOWN; + + return txn; +} + +static void dotg_txn_free(dotg_txn_t *txn) { + assert(txn->status == DOTG_TXN_FINISHED); + assert(txn->curr < txn->nstages); + + /* Release the accommodated stages. */ + for (uint8_t i = 0; i < txn->nstages; i++) + dotg_stage_free(txn->stages[i]); + + kfree(M_DEV, txn); +} + +/* Add stage `stage` to transaction `txn`. */ +static void dotg_txn_add_stage(dotg_txn_t *txn, dotg_stage_t *stage) { + txn->stages[txn->nstages++] = stage; +} + +/* + * Host controller interface transfer functions. + */ + +/* Add transaction `txn` to the scheduling queue. */ +static void dotg_schedule(dotg_state_t *dotg, dotg_txn_t *txn) { + SCOPED_SPIN_LOCK(&dotg->txn_lock); + + if (!ringbuf_putnb(&dotg->txn_rb, (void *)&txn, sizeof(dotg_txn_t *))) + panic("DWC OTG pending transaction queue too small!"); + txn->status = DOTG_TXN_PENDING; + + /* Signal a pending transaction. */ + cv_signal(&dotg->txn_cv); +} + +static void dotg_control_transfer(device_t *hcdev, device_t *dev, + usb_buf_t *buf, usb_dev_req_t *req, + usb_direction_t status_dir) { + assert(buf->transfer_size + sizeof(usb_dev_req_t) <= HCTSIZ_XFERSIZE_MAX); + + dotg_state_t *dotg = hcdev->state; + usb_device_t *udev = usb_device_of(dev); + usb_endpt_t *endpt = buf->endpt; + dotg_txn_t *txn = dotg_txn_alloc(udev, buf); + + /* Create the stetup stage. */ + dotg_stage_t *stage = dotg_stage_alloc( + DOTG_STG_SETUP, req, sizeof(usb_dev_req_t), endpt, USB_DIR_OUTPUT); + dotg_txn_add_stage(txn, stage); + + /* Create the data stage. */ + if (buf->transfer_size) { + stage = dotg_stage_alloc(DOTG_STG_DATA, buf->data, buf->transfer_size, + endpt, endpt->dir); + dotg_txn_add_stage(txn, stage); + } + + /* Create the status stage. */ + stage = dotg_stage_alloc(DOTG_STG_STATUS, NULL, 0, endpt, status_dir); + dotg_txn_add_stage(txn, stage); + + /* Schedule the transaction. */ + dotg_schedule(dotg, txn); +} + +static void dotg_data_transfer(device_t *hcdev, device_t *dev, usb_buf_t *buf) { + assert(buf->transfer_size <= HCTSIZ_XFERSIZE_MAX); + assert(buf->transfer_size); + + dotg_state_t *dotg = hcdev->state; + usb_device_t *udev = usb_device_of(dev); + usb_endpt_t *endpt = buf->endpt; + dotg_txn_t *txn = dotg_txn_alloc(udev, buf); + + /* Create the data stage. */ + dotg_stage_t *stage = dotg_stage_alloc(DOTG_STG_DATA, buf->data, + buf->transfer_size, endpt, endpt->dir); + dotg_txn_add_stage(txn, stage); + + /* Schedule the transaction. */ + dotg_schedule(dotg, txn); +} + +/* + * Host channel handling functions. + */ + +/* Allocate a free host channel. */ +static dotg_chan_t *dotg_alloc_chan(dotg_state_t *dotg) { + assert(spin_owned(&dotg->chan_lock)); + + /* Find first free host channel. */ + int idx; + bit_ffc(dotg->chan_map, dotg->nchans, &idx); + if (idx < 0) + return NULL; + + bit_set(dotg->chan_map, idx); + return &dotg->chans[idx]; +} + +/* Release a host channel. */ +static void dotg_free_chan(dotg_state_t *dotg, dotg_chan_t *chan) { + SCOPED_SPIN_LOCK(&dotg->chan_lock); + + int idx = chan->num; + assert(bit_test(dotg->chan_map, idx)); + bit_clear(dotg->chan_map, idx); + + /* Signal an available channel. */ + cv_signal(&dotg->chan_cv); +} + +/* Enable the interrupt corresponding to channel `chan`. */ +static void dotg_enable_chan_intr(dotg_state_t *dotg, dotg_chan_t *chan) { + SCOPED_SPIN_LOCK(&dotg->chan_lock); + + set(DOTG_HAINTMSK, 1 << chan->num); +} + +/* Disable the interrupt corresponding to channel `chan`. */ +static void dotg_disable_chan_intr(dotg_state_t *dotg, dotg_chan_t *chan) { + SCOPED_SPIN_LOCK(&dotg->chan_lock); + + clr(DOTG_HAINTMSK, 1 << chan->num); +} + +/* Enable channel `chan`. */ +static void dotg_enable_chan(dotg_state_t *dotg, dotg_chan_t *chan) { + int chan_chars = DOTG_HCCHAR(chan->num); + uint32_t value = in(chan_chars); + out(chan_chars, (value | HCCHAR_CHENA) & ~HCCHAR_CHDIS); +} + +/* Disable channel `chan` in oreder to stop any current transactions + * along the channel and prepare for next transaction. */ +static void dotg_disable_chan(dotg_state_t *dotg, dotg_chan_t *chan) { + dotg_txn_t *txn = chan->txn; + assert(txn); + assert(txn->status == DOTG_TXN_PENDING); + + /* We'll need to wait for an interrupt indicating that the channel + * has been disabled, so mark the corresponding channed as waiting + * for that event. */ + txn->status = DOTG_TXN_DISABLING_CHAN; + + int chan_chars = DOTG_HCCHAR(chan->num); + uint32_t value = in(chan_chars); + out(chan_chars, (value & ~HCCHAR_CHENA) | HCCHAR_CHDIS); +} + +/* + * Host channel transfer functions. + */ + +static void dotg_transfer_stage(dotg_state_t *dotg, dotg_chan_t *chan); + +/* Initiate transaction along channel `chan`. */ +static void dotg_init_transfer(dotg_state_t *dotg, dotg_chan_t *chan) { + dotg_txn_t *txn = chan->txn; + assert(txn); + assert(txn->nstages); + assert(!txn->curr); + + /* Mark the transaction as waiting for data transfer. */ + txn->status = DOTG_TXN_TRANSFERING; + + /* Transfer the first stage of the transaction. */ + dotg_transfer_stage(dotg, chan); +} + +/* Terminate the current transaction of channel `chan`. */ +static void dotg_end_transfer(dotg_state_t *dotg, dotg_chan_t *chan) { + dotg_txn_t *txn = chan->txn; + assert(txn); + + /* Mark the transaction as finished. */ + txn->status = DOTG_TXN_FINISHED; + + /* Release the transaction. */ + dotg_txn_free(txn); + + /* Disable channel's interrupt. */ + dotg_disable_chan_intr(dotg, chan); + + /* Release the host channel. */ + dotg_free_chan(dotg, chan); +} + +/* Callout for rescheduling periodic transactions. */ +static void dotg_callout(void *arg) { + dotg_callout_args_t *args = arg; + dotg_state_t *dotg = args->dotg; + dotg_chan_t *chan = container_of(args, dotg_chan_t, args); + + dotg_init_transfer(dotg, chan); +} + +/* Prepare the channel's periodic transfer to be reissued after enpoint's + * time interval. */ +static void dotg_reactivate_transfer(dotg_state_t *dotg, dotg_chan_t *chan) { + dotg_txn_t *txn = chan->txn; + assert(txn); + assert(txn->status == DOTG_TXN_FINISHED); + + /* Reset transaction's state. */ + txn->curr = 0; + + usb_endpt_t *endpt = txn->buf->endpt; + assert(endpt); + + /* Schedule a callout to reissue the transaction. */ + callout_schedule(&chan->callout, endpt->interval); +} + +/* Transfer a single stage of the transaction + * corresponding to channel `chan`. */ +static void dotg_transfer_stage(dotg_state_t *dotg, dotg_chan_t *chan) { + dotg_txn_t *txn = chan->txn; + assert(txn); + assert(txn->status == DOTG_TXN_TRANSFERING); + + dotg_stage_t *stage = txn->stages[txn->curr]; + assert(stage); + + /* Discard any pending channel interrupts. */ + wclr(DOTG_HCINT(chan->num), HCINT_ALL); + + /* + * Host channel transfer size register contains: + * - PID used for the initial transaction + * - number of packets to be transmitted + * - number of bytes to send during the transfer + */ + int chan_xfersiz = DOTG_HCTSIZ(chan->num); + out(chan_xfersiz, (stage->pid << HCTSIZ_PID_SHIFT) | + (stage->pktcnt << HCTSIZ_PKTCNT_SHIFT) | stage->size); + + /* + * Host channel characteristics register contains: + * - channel enable/disable bits + * - device address + * - multi count + * - endpoint type + * - device speed indicator + * - endpoint direction + * - endpoint number (within the device) + * - maximum packet size + */ + int chan_chars = DOTG_HCCHAR(chan->num); + usb_device_t *udev = txn->udev; + usb_endpt_t *endpt = txn->buf->endpt; + uint32_t speed = (udev->speed == USB_SPD_LOW ? HCCHAR_LSPDDEV : 0); + out(chan_chars, (udev->addr << HCCHAR_DEVADDR_SHIFT) | + (DOTG_MC << HCCHAR_MC_SHIFT) | + ((endpt->transfer - 1) << HCCHAR_EPTYPE_SHIFT) | speed | + (stage->dir << HCCHAR_EPDIR_SHIFT) | + (endpt->addr << HCCHAR_EPNUM_SHIFT) | endpt->maxpkt); + + /* We don't perform any spliting. */ + out(DOTG_HCSPLT(chan->num), 0x00000000); + + /* Copy the data into channel's DMA buffer. */ + if (stage->dir == USB_DIR_OUTPUT) + memcpy(chan->buf, stage->data, stage->size); + + /* Set host channel DMA address register. */ + out(DOTG_HCDMA(chan->num), chan->buf_pa); + + /* The transaction will be performed after the channel is enabled. */ + dotg_enable_chan(dotg, chan); +} + +/* + * Scheduling thread. + */ + +static void dotg_thread(void *arg) { + dotg_state_t *dotg = arg; + dotg_txn_t *txn = NULL; + dotg_chan_t *chan = NULL; + + for (;;) { + /* Get a pending transaction. */ + WITH_SPIN_LOCK (&dotg->txn_lock) { + while (!ringbuf_getnb(&dotg->txn_rb, (void *)&txn, sizeof(dotg_txn_t *))) + cv_wait(&dotg->txn_cv, &dotg->txn_lock); + } + assert(txn->status == DOTG_TXN_PENDING); + + /* Acquire a free host channel. */ + WITH_SPIN_LOCK (&dotg->chan_lock) { + while (!(chan = dotg_alloc_chan(dotg))) + cv_wait(&dotg->chan_cv, &dotg->chan_lock); + } + + /* Couple the transaction with the channel. */ + chan->txn = txn; + + /* Enable channel interrupt. */ + dotg_enable_chan_intr(dotg, chan); + + /* Flush the channel if needed. */ + if (chk(DOTG_HCCHAR(chan->num), HCCHAR_CHENA)) { + dotg_disable_chan(dotg, chan); + } else { + dotg_init_transfer(dotg, chan); + } + } +} + +/* + * Intrrupt handling functions. + */ + +/* Process host channel `chan`. + * XXX: FTTB, we don't sense the shortcut condition. */ +static void dotg_process_chan(dotg_state_t *dotg, dotg_chan_t *chan) { + dotg_txn_t *txn = chan->txn; + assert(txn); + + dotg_txn_status_t status = txn->status; + assert(status == DOTG_TXN_DISABLING_CHAN || status == DOTG_TXN_TRANSFERING); + + if (status == DOTG_TXN_DISABLING_CHAN) { + /* We're ready for the transfer now. */ + dotg_init_transfer(dotg, chan); + return; + } + + usb_buf_t *buf = txn->buf; + assert(buf); + + /* Host channel interrupt register. */ + uint32_t chan_intr = in(DOTG_HCINT(chan->num)); + + /* If an error has occured, signal the error and discard the transfer. */ + if (chan_intr & HCINT_ERROR) { + usb_error_t uerr = dotge2usbe(chan_intr); + usb_buf_process(buf, NULL, uerr); + dotg_end_transfer(dotg, chan); + return; + } + + /* Restart a halted stage. */ + if (chan_intr == HCINT_CHHLTD) { + dotg_transfer_stage(dotg, chan); + return; + } + + /* If there's any stage left, proceed. */ + if (txn->curr != txn->nstages - 1) { + txn->curr++; + dotg_transfer_stage(dotg, chan); + return; + } + + /* The data in channel's buffer must be from the data stage, + * so hand it to the user. */ + usb_buf_process(buf, chan->buf, 0); + + /* We need to reactivate periodic transfers. */ + if (usb_buf_periodic(buf)) { + txn->status = DOTG_TXN_FINISHED; + dotg_reactivate_transfer(dotg, chan); + } else { + dotg_end_transfer(dotg, chan); + } +} + +static intr_filter_t dotg_isr(void *arg) { + dotg_state_t *dotg = arg; + + /* Should we bother about it? */ + if (!chk(DOTG_GINTSTS, GINTSTS_HCHINT)) + return IF_STRAY; + + /* + * Host all channels interrupt register contains: + * - bit n: pending interrupt on channel `n` + */ + uint32_t chans_intr = in(DOTG_HAINT); + + /* Process each host channel. */ + for (uint8_t i = 0; i < dotg->nchans; i++) { + /* If there's no interrupt pending, let's move on to the next channel. */ + if (!(chans_intr & (1 << i))) + continue; + + /* Process the channel. */ + dotg_process_chan(dotg, &dotg->chans[i]); + + /* Clear channel's interrupts. */ + wclr(DOTG_HCINT(i), HCINT_ALL); + } + + /* Mark the interrupt as handled. */ + wclr(DOTG_GINTSTS, GINTSTS_ALL); + + return IF_FILTERED; +} + +static usbhc_methods_t dotg_usbhc_if = { + .number_of_ports = dotg_number_of_ports, + .device_present = dotg_device_present, + .device_speed = dotg_device_speed, + .reset_port = dotg_reset_port, + .control_transfer = dotg_control_transfer, + .data_transfer = dotg_data_transfer, +}; + +static driver_t dotg_driver = { + .desc = "DWC OTG driver", + .size = sizeof(dotg_state_t), + .pass = SECOND_PASS, + .probe = dotg_probe, + .attach = dotg_attach, + .interfaces = + { + [DIF_USBHC] = &dotg_usbhc_if, + }, +}; + +DEVCLASS_ENTRY(root, dotg_driver);