mbed library sources. Supersedes mbed-src.

Dependents:   Nucleo_Hello_Encoder BLE_iBeaconScan AM1805_DEMO DISCO-F429ZI_ExportTemplate1 ... more

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) {
     }