[Nuvoton] Fix SPI DMA transfer

1. Disable unnecessary TX/RX threshold interrupts to avoid potential trap in DMA transfer
2. Start TX/RX DMA transfer simultaneously to fit H/W spec and avoid potential RX FIFO overflow issue
pull/6466/head
ccli8 2018-03-26 10:58:18 +08:00
parent 9e72756878
commit 7275ee8626
4 changed files with 133 additions and 12 deletions

View File

@ -408,13 +408,45 @@ void spi_master_transfer(spi_t *obj, const void *tx, size_t tx_length, void *rx,
PDMA_INT_TRANS_DONE); // Interrupt type
// 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
/* 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).
*/
#if 0
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.
#else
NVIC_SetVector(modinit->irq_n, handler);
#endif
/* 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.
*/
#if 0
SPI_TRIGGER_RX_PDMA(((SPI_T *) NU_MODBASE(obj->spi.spi)));
SPI_TRIGGER_TX_PDMA(((SPI_T *) NU_MODBASE(obj->spi.spi)));
#else
spi_base->PDMACTL |= (SPI_PDMACTL_TXPDMAEN_Msk | SPI_PDMACTL_RXPDMAEN_Msk);
#endif
/* Don't enable SPI TX/RX threshold interrupts as commented above */
#if 0
spi_master_enable_interrupt(obj, 1);
#endif
}
}

View File

@ -413,12 +413,44 @@ void spi_master_transfer(spi_t *obj, const void *tx, size_t tx_length, void *rx,
// 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
/* 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).
*/
#if 0
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.
#else
NVIC_SetVector(modinit->irq_n, handler);
#endif
/* 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.
*/
#if 0
SPI_TRIGGER_RX_PDMA(((SPI_T *) NU_MODBASE(obj->spi.spi)));
SPI_TRIGGER_TX_PDMA(((SPI_T *) NU_MODBASE(obj->spi.spi)));
#else
spi_base->PDMACTL |= (SPI_PDMACTL_TXPDMAEN_Msk | SPI_PDMACTL_RXPDMAEN_Msk);
#endif
/* Don't enable SPI TX/RX threshold interrupts as commented above */
#if 0
spi_master_enable_interrupt(obj, 1);
#endif
}
}

View File

@ -450,14 +450,39 @@ void spi_master_transfer(spi_t *obj, const void *tx, size_t tx_length, void *rx,
PDMA_IER_TD_IE_Msk); // Interrupt type
// 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
/* 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).
*/
spi_enable_vector_interrupt(obj, handler, 1);
// No TX/RX FIFO threshold interrupt
/* No TX/RX FIFO threshold interrupt */
spi_master_enable_interrupt(obj, 0, SPI_FIFO_RX_INTEN_MASK | SPI_FIFO_TX_INTEN_MASK);
// NOTE: It is safer to start rx DMA first and then tx DMA. Otherwise, receive FIFO is subject to overflow by tx DMA.
/* 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.
*/
#if 0
SPI_TRIGGER_RX_PDMA(((SPI_T *) NU_MODBASE(obj->spi.spi)));
SPI_TRIGGER_TX_PDMA(((SPI_T *) NU_MODBASE(obj->spi.spi)));
#else
spi_base->DMA |= (SPI_DMA_TX_DMA_EN_Msk | SPI_DMA_RX_DMA_EN_Msk);
#endif
PDMA_Trigger(obj->spi.dma_chn_id_rx);
PDMA_Trigger(obj->spi.dma_chn_id_tx);
}

View File

@ -410,13 +410,45 @@ void spi_master_transfer(spi_t *obj, const void *tx, size_t tx_length, void *rx,
0); // Interrupt type. No use here
// 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
/* 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).
*/
#if 0
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.
#else
NVIC_SetVector(modinit->irq_n, handler);
#endif
/* 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.
*/
#if 0
SPI_TRIGGER_RX_PDMA(((SPI_T *) NU_MODBASE(obj->spi.spi)));
SPI_TRIGGER_TX_PDMA(((SPI_T *) NU_MODBASE(obj->spi.spi)));
#else
spi_base->PDMACTL |= (SPI_PDMACTL_TXPDMAEN_Msk | SPI_PDMACTL_RXPDMAEN_Msk);
#endif
/* Don't enable SPI TX/RX threshold interrupts as commented above */
#if 0
spi_master_enable_interrupt(obj, 1);
#endif
}
}