mbed library sources. Supersedes mbed-src. Edited target satm32f446 for user USART3 pins
Fork of mbed-dev by
Diff: targets/TARGET_NUVOTON/TARGET_M480/spi_api.c
- Revision:
- 186:707f6e361f3e
- Parent:
- 176:447f873cad2f
--- a/targets/TARGET_NUVOTON/TARGET_M480/spi_api.c Thu Apr 19 17:12:19 2018 +0100 +++ b/targets/TARGET_NUVOTON/TARGET_M480/spi_api.c Fri Jun 22 16:45:37 2018 +0100 @@ -71,6 +71,34 @@ #endif }; +/* Synchronous version of SPI_ENABLE()/SPI_DISABLE() macros + * + * The SPI peripheral clock is asynchronous with the system clock. In order to make sure the SPI + * control logic is enabled/disabled, this bit indicates the real status of SPI controller. + * + * NOTE: All configurations shall be ready before calling SPI_ENABLE_SYNC(). + * NOTE: Before changing the configurations of SPIx_CTL, SPIx_CLKDIV, SPIx_SSCTL and SPIx_FIFOCTL registers, + * user shall clear the SPIEN (SPIx_CTL[0]) and confirm the SPIENSTS (SPIx_STATUS[15]) is 0 + * (by SPI_DISABLE_SYNC here). + */ +__STATIC_INLINE void SPI_ENABLE_SYNC(SPI_T *spi_base) +{ + if (! (spi_base->CTL & SPI_CTL_SPIEN_Msk)) { + SPI_ENABLE(spi_base); + } + while (! (spi_base->STATUS & SPI_STATUS_SPIENSTS_Msk)); +} +__STATIC_INLINE void SPI_DISABLE_SYNC(SPI_T *spi_base) +{ + if (spi_base->CTL & SPI_CTL_SPIEN_Msk) { + // NOTE: SPI H/W may get out of state without the busy check. + while (SPI_IS_BUSY(spi_base)); + + SPI_DISABLE(spi_base); + } + while (spi_base->STATUS & SPI_STATUS_SPIENSTS_Msk); +} + #if DEVICE_SPI_ASYNCH static void spi_enable_vector_interrupt(spi_t *obj, uint32_t handler, uint8_t enable); static void spi_master_enable_interrupt(spi_t *obj, uint8_t enable); @@ -142,6 +170,11 @@ obj->spi.event = 0; obj->spi.dma_chn_id_tx = DMA_ERROR_OUT_OF_CHANNELS; obj->spi.dma_chn_id_rx = DMA_ERROR_OUT_OF_CHANNELS; + + /* NOTE: We use vector to judge if asynchronous transfer is on-going (spi_active). + * At initial time, asynchronous transfer is not on-going and so vector must + * be cleared to zero for correct judgement. */ + NVIC_SetVector(modinit->irq_n, 0); #endif // Mark this module to be inited. @@ -178,16 +211,14 @@ int i = modinit - spi_modinit_tab; spi_modinit_mask &= ~(1 << i); } + void spi_format(spi_t *obj, int bits, int mode, int slave) { MBED_ASSERT(bits >= NU_SPI_FRAME_MIN && bits <= NU_SPI_FRAME_MAX); SPI_T *spi_base = (SPI_T *) NU_MODBASE(obj->spi.spi); - // NOTE 1: All configurations should be ready before enabling SPI peripheral. - // NOTE 2: Re-configuration is allowed only as SPI peripheral is idle. - while (SPI_IS_BUSY(spi_base)); - SPI_DISABLE(spi_base); + SPI_DISABLE_SYNC(spi_base); SPI_Open(spi_base, slave ? SPI_SLAVE : SPI_MASTER, @@ -211,16 +242,16 @@ spi_base->SSCTL &= ~SPI_SSCTL_SSACTPOL_Msk; } - // NOTE: M451's/M480's SPI_Open() will enable SPI transfer (SPI_CTL_SPIEN_Msk). This will violate judgement of spi_active(). Disable it. - SPI_DISABLE(spi_base); + /* NOTE: M451's/M480's/M2351's SPI_Open() will enable SPI transfer (SPI_CTL_SPIEN_Msk). + * We cannot use SPI_CTL_SPIEN_Msk for judgement of spi_active(). + * Judge with vector instead. */ } void spi_frequency(spi_t *obj, int hz) { SPI_T *spi_base = (SPI_T *) NU_MODBASE(obj->spi.spi); - while (SPI_IS_BUSY(spi_base)); - SPI_DISABLE(spi_base); + SPI_DISABLE_SYNC(spi_base); SPI_SetBusClock((SPI_T *) NU_MODBASE(obj->spi.spi), hz); } @@ -231,7 +262,7 @@ SPI_T *spi_base = (SPI_T *) NU_MODBASE(obj->spi.spi); // NOTE: Data in receive FIFO can be read out via ICE. - SPI_ENABLE(spi_base); + SPI_ENABLE_SYNC(spi_base); // Wait for tx buffer empty while(! spi_writeable(obj)); @@ -241,7 +272,7 @@ while (! spi_readable(obj)); int value2 = SPI_READ_RX(spi_base); - SPI_DISABLE(spi_base); + /* We don't call SPI_DISABLE_SYNC here for performance. */ return value2; } @@ -266,7 +297,7 @@ { SPI_T *spi_base = (SPI_T *) NU_MODBASE(obj->spi.spi); - SPI_ENABLE(spi_base); + SPI_ENABLE_SYNC(spi_base); return spi_readable(obj); }; @@ -275,7 +306,7 @@ { SPI_T *spi_base = (SPI_T *) NU_MODBASE(obj->spi.spi); - SPI_ENABLE(spi_base); + SPI_ENABLE_SYNC(spi_base); // Wait for rx buffer full while (! spi_readable(obj)); @@ -287,7 +318,7 @@ { SPI_T *spi_base = (SPI_T *) NU_MODBASE(obj->spi.spi); - SPI_ENABLE(spi_base); + SPI_ENABLE_SYNC(spi_base); // Wait for tx buffer empty while(! spi_writeable(obj)); @@ -320,7 +351,7 @@ spi_enable_event(obj, event, 1); spi_buffer_set(obj, tx, tx_length, rx, rx_length); - SPI_ENABLE(spi_base); + SPI_ENABLE_SYNC(spi_base); if (obj->spi.dma_usage == DMA_USAGE_NEVER) { // Interrupt way @@ -383,12 +414,33 @@ // Register DMA event handler dma_set_handler(obj->spi.dma_chn_id_rx, (uint32_t) spi_dma_handler_rx, (uint32_t) obj, DMA_EVENT_ALL); - // Start tx/rx DMA transfer - spi_enable_vector_interrupt(obj, handler, 1); - // NOTE: It is safer to start rx DMA first and then tx DMA. Otherwise, receive FIFO is subject to overflow by tx DMA. - SPI_TRIGGER_RX_PDMA(((SPI_T *) NU_MODBASE(obj->spi.spi))); - SPI_TRIGGER_TX_PDMA(((SPI_T *) NU_MODBASE(obj->spi.spi))); - spi_master_enable_interrupt(obj, 1); + /* Start tx/rx DMA transfer + * + * If we have both PDMA and SPI interrupts enabled and PDMA priority is lower than SPI priority, + * we would trap in SPI interrupt handler endlessly with the sequence: + * + * 1. PDMA TX transfer done interrupt occurs and is well handled. + * 2. SPI RX FIFO threshold interrupt occurs. Trap here because PDMA RX transfer done interrupt doesn't get handled. + * 3. PDMA RX transfer done interrupt occurs but it cannot be handled due to above. + * + * To fix it, we don't enable SPI TX/RX threshold interrupts but keep SPI vector handler set to be called + * in PDMA TX/RX transfer done interrupt handlers (spi_dma_handler_tx/spi_dma_handler_rx). + */ + NVIC_SetVector(modinit->irq_n, handler); + + /* Order to enable PDMA TX/RX functions + * + * H/W spec: In SPI Master mode with full duplex transfer, if both TX and RX PDMA functions are + * enabled, RX PDMA function cannot be enabled prior to TX PDMA function. User can enable + * TX PDMA function firstly or enable both functions simultaneously. + * Per real test, it is safer to start RX PDMA first and then TX PDMA. Otherwise, receive FIFO is + * subject to overflow by TX DMA. + * + * With the above conflicts, we enable PDMA TX/RX functions simultaneously. + */ + spi_base->PDMACTL |= (SPI_PDMACTL_TXPDMAEN_Msk | SPI_PDMACTL_RXPDMAEN_Msk); + + /* Don't enable SPI TX/RX threshold interrupts as commented above */ } } @@ -428,9 +480,8 @@ spi_enable_vector_interrupt(obj, 0, 0); spi_master_enable_interrupt(obj, 0); - // NOTE: SPI H/W may get out of state without the busy check. - while (SPI_IS_BUSY(spi_base)); - SPI_DISABLE(spi_base); + /* Necessary for accessing FIFOCTL below */ + SPI_DISABLE_SYNC(spi_base); SPI_ClearRxFIFO(spi_base); SPI_ClearTxFIFO(spi_base); @@ -456,9 +507,14 @@ uint8_t spi_active(spi_t *obj) { - SPI_T *spi_base = (SPI_T *) NU_MODBASE(obj->spi.spi); + const struct nu_modinit_s *modinit = get_modinit(obj->spi.spi, spi_modinit_tab); + MBED_ASSERT(modinit != NULL); + MBED_ASSERT(modinit->modname == (int) obj->spi.spi); - return (spi_base->CTL & SPI_CTL_SPIEN_Msk); + /* Vector will be cleared when asynchronous transfer is finished or aborted. + Use it to judge if asynchronous transfer is on-going. */ + uint32_t vec = NVIC_GetVector(modinit->irq_n); + return vec ? 1 : 0; } static int spi_writeable(spi_t * obj) @@ -492,6 +548,7 @@ NVIC_EnableIRQ(modinit->irq_n); } else { NVIC_DisableIRQ(modinit->irq_n); + NVIC_SetVector(modinit->irq_n, 0); } } @@ -709,14 +766,14 @@ { spi_t *obj = (spi_t *) id; - // FIXME: Pass this error to caller + // TODO: Pass this error to caller if (event_dma & DMA_EVENT_ABORT) { } // Expect SPI IRQ will catch this transfer done event if (event_dma & DMA_EVENT_TRANSFER_DONE) { obj->tx_buff.pos = obj->tx_buff.length; } - // FIXME: Pass this error to caller + // TODO: Pass this error to caller if (event_dma & DMA_EVENT_TIMEOUT) { } @@ -732,14 +789,14 @@ { spi_t *obj = (spi_t *) id; - // FIXME: Pass this error to caller + // TODO: Pass this error to caller if (event_dma & DMA_EVENT_ABORT) { } // Expect SPI IRQ will catch this transfer done event if (event_dma & DMA_EVENT_TRANSFER_DONE) { obj->rx_buff.pos = obj->rx_buff.length; } - // FIXME: Pass this error to caller + // TODO: Pass this error to caller if (event_dma & DMA_EVENT_TIMEOUT) { }