diff options
Diffstat (limited to 'libmaple/usb')
-rwxr-xr-x | libmaple/usb/usb.c | 334 |
1 files changed, 244 insertions, 90 deletions
diff --git a/libmaple/usb/usb.c b/libmaple/usb/usb.c index 4f31c33..29d07d0 100755 --- a/libmaple/usb/usb.c +++ b/libmaple/usb/usb.c @@ -25,28 +25,31 @@ *****************************************************************************/ /** - * @brief usb-specific hardware setup, NVIC, clocks, and usb activities - * in the pre-attached state. includes some of the lower level callbacks - * needed by the usb library, like suspend,resume,init,etc + * @file usb.c + * @brief USB support. */ #include "usb.h" + #include "libmaple.h" -#include "usb_lib.h" #include "gpio.h" #include "delay.h" #include "nvic.h" #include "rcc.h" +#include "usb_reg_map.h" #include "usb_config.h" #include "usb_callbacks.h" -#include "usb_lib.h" -/* persistent usb structs */ +#include "usb_type.h" +#include "usb_core.h" -volatile uint32 bDeviceState = UNCONNECTED; -volatile uint16 wIstr = 0; -volatile uint32 bIntPackSOF = 0; +static void usb_init(void); +static void dispatch_ctr_lp(void); + +/* + * Globals required by usb_lib/ + */ DEVICE Device_Table = {NUM_ENDPTS, @@ -95,20 +98,39 @@ void (*pEpInt_OUT[7])(void) = NOP_Process, NOP_Process}; +volatile uint16 wIstr = 0; +uint8 EPindex; /* current endpoint */ +DEVICE_INFO *pInformation; +DEVICE_PROP *pProperty; +uint16 SaveTState; /* caches TX status for later use */ +uint16 SaveRState; /* caches RX status for later use */ +uint16 wInterrupt_Mask; +DEVICE_INFO Device_Info; +USER_STANDARD_REQUESTS *pUser_Standard_Requests; + +/* + * Other state + */ + +volatile uint32 bDeviceState = UNCONNECTED; +volatile uint32 bIntPackSOF = 0; + struct { volatile RESUME_STATE eState; volatile uint8 bESOFcnt; } ResumeS; +/* + * Routines + */ + void usb_cdcacm_enable(gpio_dev *disc_dev, uint8 disc_bit) { + /* Present ourselves to the host */ gpio_set_mode(disc_dev, disc_bit, GPIO_OUTPUT_PP); - - /* setup the apb1 clock for USB */ - RCC_BASE->APB1ENR |= 0x00800000; - - /* initialize the usb application */ gpio_write_bit(disc_dev, disc_bit, 0); // presents us to the host - USB_Init(); // low level init routine provided by the ST library + + /* initialize USB peripheral */ + usb_init(); } void usb_cdcacm_disable(gpio_dev *disc_dev, uint8 disc_bit) { @@ -119,31 +141,32 @@ void usb_cdcacm_disable(gpio_dev *disc_dev, uint8 disc_bit) { } void usbSuspend(void) { - u16 wCNTR; - wCNTR = _GetCNTR(); - wCNTR |= CNTR_FSUSP | CNTR_LPMODE; - _SetCNTR(wCNTR); + uint16 cntr; + + /* TODO decide if read/modify/write is really what we want + * (e.g. usbResumeInit() reconfigures CNTR). */ + cntr = USB_BASE->CNTR; + cntr |= USB_CNTR_FSUSP; + USB_BASE->CNTR = cntr; + cntr |= USB_CNTR_LP_MODE; + USB_BASE->CNTR = cntr; - /* run any power reduction handlers */ bDeviceState = SUSPENDED; } void usbResumeInit(void) { - u16 wCNTR; - - /* restart any clocks that had been stopped */ - - wCNTR = _GetCNTR(); - wCNTR &= (~CNTR_LPMODE); - _SetCNTR(wCNTR); + uint16 cntr; - /* undo power reduction handlers here */ - _SetCNTR(ISR_MSK); + cntr = USB_BASE->CNTR; + cntr &= ~USB_CNTR_LP_MODE; + USB_BASE->CNTR = cntr; + /* Enable interrupt lines */ + USB_BASE->CNTR = ISR_MSK; } void usbResume(RESUME_STATE eResumeSetVal) { - u16 wCNTR; + uint16 cntr; if (eResumeSetVal != RESUME_ESOF) ResumeS.eState = eResumeSetVal; @@ -168,18 +191,18 @@ void usbResume(RESUME_STATE eResumeSetVal) { ResumeS.eState = RESUME_START; break; case RESUME_START: - wCNTR = _GetCNTR(); - wCNTR |= CNTR_RESUME; - _SetCNTR(wCNTR); + cntr = USB_BASE->CNTR; + cntr |= USB_CNTR_RESUME; + USB_BASE->CNTR = cntr; ResumeS.eState = RESUME_ON; ResumeS.bESOFcnt = 10; break; case RESUME_ON: ResumeS.bESOFcnt--; if (ResumeS.bESOFcnt == 0) { - wCNTR = _GetCNTR(); - wCNTR &= (~CNTR_RESUME); - _SetCNTR(wCNTR); + cntr = USB_BASE->CNTR; + cntr &= ~USB_CNTR_RESUME; + USB_BASE->CNTR = cntr; ResumeS.eState = RESUME_OFF; } break; @@ -191,46 +214,39 @@ void usbResume(RESUME_STATE eResumeSetVal) { } } -/* overloaded ISR routine, this is the main usb ISR */ void __irq_usb_lp_can_rx0(void) { -wIstr = _GetISTR(); + wIstr = USB_BASE->ISTR; -/* go nuts with the preproc switches since this is an ISTR and must be FAST */ -#if (ISR_MSK & ISTR_RESET) -if (wIstr & ISTR_RESET & wInterrupt_Mask) - { - _SetISTR((u16)CLR_RESET); + /* Use ISR_MSK to only include code for bits we care about. */ + +#if (ISR_MSK & USB_ISTR_RESET) + if (wIstr & USB_ISTR_RESET & wInterrupt_Mask) { + USB_BASE->ISTR = ~USB_ISTR_RESET; Device_Property.Reset(); } #endif - -#if (ISR_MSK & ISTR_DOVR) -if (wIstr & ISTR_DOVR & wInterrupt_Mask) - { - _SetISTR((u16)CLR_DOVR); +#if (ISR_MSK & USB_ISTR_PMAOVR) + if (wIstr & ISTR_PMAOVR & wInterrupt_Mask) { + USB_BASE->ISTR = ~USB_ISTR_PMAOVR; } #endif - -#if (ISR_MSK & ISTR_ERR) -if (wIstr & ISTR_ERR & wInterrupt_Mask) - { - _SetISTR((u16)CLR_ERR); +#if (ISR_MSK & USB_ISTR_ERR) + if (wIstr & USB_ISTR_ERR & wInterrupt_Mask) { + USB_BASE->ISTR = ~USB_ISTR_ERR; } #endif - -#if (ISR_MSK & ISTR_WKUP) -if (wIstr & ISTR_WKUP & wInterrupt_Mask) { - _SetISTR((u16)CLR_WKUP); +#if (ISR_MSK & USB_ISTR_WKUP) + if (wIstr & USB_ISTR_WKUP & wInterrupt_Mask) { + USB_BASE->ISTR = ~USB_ISTR_WKUP; usbResume(RESUME_EXTERNAL); -} + } #endif -/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ -#if (ISR_MSK & ISTR_SUSP) -if (wIstr & ISTR_SUSP & wInterrupt_Mask) { +#if (ISR_MSK & USB_ISTR_SUSP) + if (wIstr & USB_ISTR_SUSP & wInterrupt_Mask) { /* check if SUSPEND is possible */ if (F_SUSPEND_ENABLED) { usbSuspend(); @@ -239,36 +255,34 @@ if (wIstr & ISTR_SUSP & wInterrupt_Mask) { usbResume(RESUME_LATER); } /* clear of the ISTR bit must be done after setting of CNTR_FSUSP */ - _SetISTR((u16)CLR_SUSP); + USB_BASE->ISTR = ~USB_ISTR_SUSP; } #endif - -#if (ISR_MSK & ISTR_SOF) -if (wIstr & ISTR_SOF & wInterrupt_Mask) { - _SetISTR((u16)CLR_SOF); +#if (ISR_MSK & USB_ISTR_SOF) + if (wIstr & USB_ISTR_SOF & wInterrupt_Mask) { + USB_BASE->ISTR = ~USB_ISTR_SOF; bIntPackSOF++; - } + } #endif - -#if (ISR_MSK & ISTR_ESOF) -if (wIstr & ISTR_ESOF & wInterrupt_Mask) { - _SetISTR((u16)CLR_ESOF); +#if (ISR_MSK & USB_ISTR_ESOF) + if (wIstr & USB_ISTR_ESOF & wInterrupt_Mask) { + USB_BASE->ISTR = ~USB_ISTR_ESOF; /* resume handling timing is made with ESOFs */ usbResume(RESUME_ESOF); /* request without change of the machine state */ - } + } #endif -/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ -#if (ISR_MSK & ISTR_CTR) -if (wIstr & ISTR_CTR & wInterrupt_Mask) { - /* servicing of the endpoint correct transfer interrupt */ - /* clear of the CTR flag into the sub */ - CTR_LP(); /* low priority ISR defined in the usb core lib */ - } -#endif + /* + * Service the correct transfer interrupt. + */ +#if (ISR_MSK & USB_ISTR_CTR) + if (wIstr & USB_ISTR_CTR & wInterrupt_Mask) { + dispatch_ctr_lp(); + } +#endif } void usbWaitReset(void) { @@ -303,16 +317,17 @@ uint32 usbSendBytes(const uint8* sendBuf, uint32 len) { } // We can only put VCOM_TX_EPSIZE bytes in the buffer + /* FIXME XXX then why are we only copying half as many? */ if (len > VCOM_TX_EPSIZE / 2) { len = VCOM_TX_EPSIZE / 2; } // Try to load some bytes if we can if (len) { - UserToPMABufferCopy(sendBuf, VCOM_TX_ADDR, len); - _SetEPTxCount(VCOM_TX_ENDP, len); + usb_copy_to_pma(sendBuf, len, VCOM_TX_ADDR); + usb_set_ep_tx_count(VCOM_TX_ENDP, len); countTx += len; - _SetEPTxValid(VCOM_TX_ENDP); + usb_set_ep_tx_stat(VCOM_TX_ENDP, USB_EP_STAT_TX_VALID); } return len; @@ -323,10 +338,10 @@ uint32 usbBytesAvailable(void) { return newBytes; } -/* copies len bytes from the local recieve FIFO (not - usb packet buffer) into recvBuf and deq's the fifo. - will only copy the minimum of len or the available - bytes. returns the number of bytes copied */ +/* Nonblocking byte receive. + * + * Copies up to len bytes from the local recieve FIFO (*NOT* the PMA) + * into recvBuf and deq's the FIFO. */ uint32 usbReceiveBytes(uint8* recvBuf, uint32 len) { static int offset = 0; @@ -342,10 +357,10 @@ uint32 usbReceiveBytes(uint8* recvBuf, uint32 len) { newBytes -= len; offset += len; - /* re-enable the rx endpoint which we had set to receive 0 bytes */ + /* Re-enable the RX endpoint, which we had set to receive 0 bytes */ if (newBytes == 0) { - SetEPRxCount(VCOM_RX_ENDP,VCOM_RX_EPSIZE); - SetEPRxStatus(VCOM_RX_ENDP,EP_RX_VALID); + usb_set_ep_rx_count(VCOM_RX_ENDP,VCOM_RX_EPSIZE); + usb_set_ep_rx_stat(VCOM_RX_ENDP, USB_EP_STAT_RX_VALID); offset = 0; } @@ -372,3 +387,142 @@ uint16 usbGetPending() { return countTx; } +/* + * Auxiliary routines + */ + +static void usb_init(void) { /* TODO make a public, improved version */ + rcc_clk_enable(RCC_USB); + + pInformation = &Device_Info; + pInformation->ControlState = 2; + pProperty = &Device_Property; + pUser_Standard_Requests = &User_Standard_Requests; + pProperty->Init(); +} + +static inline uint8 dispatch_endpt_zero(void); +static inline void dispatch_endpt(uint8 ep); +static inline void set_rx_tx_status0(uint16 rx, uint16 tx); + +static void handle_setup0(void); +static void handle_in0(void); +static void handle_out0(void); + +static void dispatch_ctr_lp(void) { + while (((wIstr = USB_BASE->ISTR) & USB_ISTR_CTR) != 0) { + /* TODO WTF, figure this out: RM0008 says CTR is read-only, + * but ST's firmware claims it's clear-only, and emphasizes + * the importance of clearing it in more than one place. */ + USB_BASE->ISTR = ~USB_ISTR_CTR; + uint8 ep_id = wIstr & USB_ISTR_EP_ID; + if (ep_id == 0) { + /* TODO figure out why it's OK to break out of the loop + * once we're done serving endpoint zero, but not okay if + * there are multiple nonzero endpoint transfers to + * handle. */ + if (dispatch_endpt_zero()) + return; + } else { + dispatch_endpt(ep_id); + } + } +} + +/* FIXME Dataflow on endpoint 0 RX/TX status is based off of ST's + * code, and is ugly/confusing in its use of SaveRState/SaveTState. + * Fixing this requires filling in handle_in0(), handle_setup0(), + * handle_out0(). */ +static inline uint8 dispatch_endpt_zero(void) { + uint32 epr = (uint16)USB_BASE->EP[0]; + + if (!(epr & (USB_EP_CTR_TX | USB_EP_SETUP | USB_EP_CTR_RX))) { + return 0; + } + + /* Cache RX/TX statuses in SaveRState/SaveTState, respectively. + * The various handle_foo0() may clobber these values + * before we reset them at the end of this routine. */ + SaveRState = epr & USB_EP_STAT_RX; + SaveTState = epr & USB_EP_STAT_TX; + + /* Set actual RX/TX statuses to NAK while we're thinking */ + set_rx_tx_status0(USB_EP_STAT_RX_NAK, USB_EP_STAT_TX_NAK); + + if ((wIstr & USB_ISTR_DIR) == 0) { + /* ST RM0008: "If DIR bit=0, CTR_TX bit is set in the USB_EPnR + * register related to the interrupting endpoint. The + * interrupting transaction is of IN type (data transmitted by + * the USB peripheral to the host PC)." */ + ASSERT_FAULT(epr & USB_EP_CTR_TX); + usb_clear_ctr_tx(USB_EP0); + handle_in0(); + } else { + /* RM0008: "If DIR bit=1, CTR_RX bit or both CTR_TX/CTR_RX + * are set in the USB_EPnR register related to the + * interrupting endpoint. The interrupting transaction is of + * OUT type (data received by the USB peripheral from the host + * PC) or two pending transactions are waiting to be + * processed." + * + * [mbolivar] Note how the following control flow (which + * replicates ST's) doesn't seem to actually handle both + * interrupts that are ostensibly pending when both CTR_RX and + * CTR_TX are set. + * + * TODO sort this mess out. + */ + if (epr & USB_EP_CTR_TX) { + usb_clear_ctr_tx(USB_EP0); + handle_in0(); + } else { /* SETUP or CTR_RX */ + /* SETUP is held constant while CTR_RX is set, so clear it + * either way */ + usb_clear_ctr_rx(USB_EP0); + if (epr & USB_EP_SETUP) { + handle_setup0(); + } else { /* CTR_RX */ + handle_out0(); + } + } + } + + set_rx_tx_status0(SaveRState, SaveTState); + return 1; +} + +static inline void dispatch_endpt(uint8 ep) { + uint32 epr = USB_BASE->EP[ep]; + /* If ISTR_CTR is set and the ISTR gave us this EP_ID to handle, + * then presumably at least one of CTR_RX and CTR_TX is set, but + * again, ST's control flow allows for the possibility of neither. + * + * TODO try to find out if neither being set is possible. */ + if (epr & USB_EP_CTR_RX) { + usb_clear_ctr_rx(ep); + (pEpInt_OUT[ep - 1])(); + } + if (epr & USB_EP_CTR_TX) { + usb_clear_ctr_tx(ep); + (pEpInt_IN[ep - 1])(); + } +} + +static inline void set_rx_tx_status0(uint16 rx, uint16 tx) { + usb_set_ep_rx_stat(USB_EP0, rx); + usb_set_ep_tx_stat(USB_EP0, tx); +} + +/* TODO Rip out usb_lib/ dependency from the following functions: */ + +static void handle_setup0(void) { + Setup0_Process(); +} + +static void handle_in0(void) { + In0_Process(); +} + +static void handle_out0(void) { + Out0_Process(); +} |