/* * Licensed under ST Liberty SW License Agreement V2, (the "License"); * You may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.st.com/software_license_agreement_liberty_v2 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @file templates/spi_lld.c * @brief SPI Driver subsystem low level driver source template. * * @addtogroup SPI * @{ */ #include "ch.h" #include "hal.h" #if HAL_USE_SPI || defined(__DOXYGEN__) /* Some forward declarations.*/ static void spi_serve_rx_irq(edma_channel_t channel, void *p); static void spi_serve_dma_error_irq(edma_channel_t channel, void *p, uint32_t esr); /*===========================================================================*/ /* Driver local definitions. */ /*===========================================================================*/ /* Enforced MCR bits.*/ #define DSPI_MCR_ENFORCED_BITS (SPC5_MCR_MSTR) /* Excluded MCR bits.*/ #define DSPI_MCR_EXCLUDED_BITS (SPC5_MCR_CONT_SCKE | \ SPC5_MCR_DCONF_MASK | \ SPC5_MCR_ROOE | \ SPC5_MCR_MDIS | \ SPC5_MCR_DIS_TXF | \ SPC5_MCR_DIS_RXF | \ SPC5_MCR_CLR_TXF | \ SPC5_MCR_CLR_RXF | \ SPC5_MCR_HALT) /* Excluded PUSHR bits.*/ #define DSPI_PUSHR_EXCLUDED_BITS (SPC5_PUSHR_CTAS_MASK | \ SPC5_PUSHR_EOQ | \ SPC5_PUSHR_TXDATA_MASK) #define DSPI_PUSHR8_ADDRESS(spip) (((uint32_t)&(spip)->dspi->PUSHR.R) + 3) #define DSPI_PUSHR16_ADDRESS(spip) (((uint32_t)&(spip)->dspi->PUSHR.R) + 2) #define DSPI_POPR8_ADDRESS(spip) (((uint32_t)&(spip)->dspi->POPR.R) + 3) #define DSPI_POPR16_ADDRESS(spip) (((uint32_t)&(spip)->dspi->POPR.R) + 2) /*===========================================================================*/ /* Driver exported variables. */ /*===========================================================================*/ /** * @brief SPID1 driver identifier. */ #if SPC5_SPI_USE_DSPI0 || defined(__DOXYGEN__) SPIDriver SPID1; #endif /** * @brief SPID2 driver identifier. */ #if SPC5_SPI_USE_DSPI1 || defined(__DOXYGEN__) SPIDriver SPID2; #endif /** * @brief SPID3 driver identifier. */ #if SPC5_SPI_USE_DSPI2 || defined(__DOXYGEN__) SPIDriver SPID3; #endif /** * @brief SPID4 driver identifier. */ #if SPC5_SPI_USE_DSPI3 || defined(__DOXYGEN__) SPIDriver SPID4; #endif /*===========================================================================*/ /* Driver local variables and types. */ /*===========================================================================*/ #if SPC5_SPI_USE_DSPI0 || defined(__DOXYGEN__) /** * @brief DMA configuration for DSPI0 TX. */ static const edma_channel_config_t spi_dspi0_tx_dma_config = { SPC5_DSPI0_TX_DMA_DEV_ID, SPC5_SPI_DSPI0_DMA_PRIO, SPC5_SPI_DSPI0_DMA_PRIO, NULL, spi_serve_dma_error_irq, &SPID1 }; /** * @brief DMA configuration for DSPI0 RX. */ static const edma_channel_config_t spi_dspi0_rx_dma_config = { SPC5_DSPI0_RX_DMA_DEV_ID, SPC5_SPI_DSPI0_DMA_PRIO, SPC5_SPI_DSPI0_DMA_PRIO, spi_serve_rx_irq, spi_serve_dma_error_irq, &SPID1 }; #endif /* SPC5_SPI_USE_DSPI0 */ #if SPC5_SPI_USE_DSPI1 || defined(__DOXYGEN__) /** * @brief DMA configuration for DSPI1 TX. */ static const edma_channel_config_t spi_dspi1_tx_dma_config = { SPC5_DSPI1_TX_DMA_DEV_ID, SPC5_SPI_DSPI1_DMA_PRIO, SPC5_SPI_DSPI1_DMA_PRIO, NULL, spi_serve_dma_error_irq, &SPID2 }; /** * @brief DMA configuration for DSPI1 RX. */ static const edma_channel_config_t spi_dspi1_rx_dma_config = { SPC5_DSPI1_RX_DMA_DEV_ID, SPC5_SPI_DSPI1_DMA_PRIO, SPC5_SPI_DSPI1_DMA_PRIO, spi_serve_rx_irq, spi_serve_dma_error_irq, &SPID2 }; #endif /* SPC5_SPI_USE_DSPI1 */ #if SPC5_SPI_USE_DSPI2 || defined(__DOXYGEN__) /** * @brief DMA configuration for DSPI2 TX. */ static const edma_channel_config_t spi_dspi2_tx_dma_config = { SPC5_DSPI2_TX_DMA_DEV_ID, SPC5_SPI_DSPI2_DMA_PRIO, SPC5_SPI_DSPI2_DMA_PRIO, NULL, spi_serve_dma_error_irq, &SPID3 }; /** * @brief DMA configuration for DSPI2 RX. */ static const edma_channel_config_t spi_dspi2_rx_dma_config = { SPC5_DSPI2_RX_DMA_DEV_ID, SPC5_SPI_DSPI2_DMA_PRIO, SPC5_SPI_DSPI2_DMA_PRIO, spi_serve_rx_irq, spi_serve_dma_error_irq, &SPID3 }; #endif /* SPC5_SPI_USE_DSPI2 */ #if SPC5_SPI_USE_DSPI3 || defined(__DOXYGEN__) /** * @brief DMA configuration for DSPI3 TX. */ static const edma_channel_config_t spi_dspi3_tx_dma_config = { SPC5_DSPI3_TX_DMA_DEV_ID, SPC5_SPI_DSPI3_DMA_PRIO, SPC5_SPI_DSPI3_DMA_PRIO, NULL, spi_serve_dma_error_irq, &SPID4 }; /** * @brief DMA configuration for DSPI3 RX. */ static const edma_channel_config_t spi_dspi3_rx_dma_config = { SPC5_DSPI3_RX_DMA_DEV_ID, SPC5_SPI_DSPI3_DMA_PRIO, SPC5_SPI_DSPI3_DMA_PRIO, spi_serve_rx_irq, spi_serve_dma_error_irq, &SPID4 }; #endif /* SPC5_SPI_USE_DSPI3 */ /*===========================================================================*/ /* Driver local functions. */ /*===========================================================================*/ /** * @brief Starts eception using DMA for frames up to 8 bits. * * @param[in] spip pointer to the @p SPIDriver object * @param[in] n number of words to be exchanged * @param[out] rxbuf the pointer to the receive buffer * * @notapi */ static void spi_start_dma_rx8(SPIDriver *spip, size_t n, uint8_t *rxbuf) { edmaChannelSetup(spip->rx_channel, /* channel. */ DSPI_POPR8_ADDRESS(spip), /* src. */ rxbuf, /* dst. */ 0, /* soff, do not advance. */ 1, /* doff, advance by one. */ 0, /* ssize, 8 bits transfers. */ 0, /* dsize, 8 bits transfers. */ 1, /* nbytes, always one. */ n, /* iter. */ 0, /* slast. */ 0, /* dlast. */ EDMA_TCD_MODE_DREQ | EDMA_TCD_MODE_INT_END); /* mode.*/ edmaChannelStart(spip->rx_channel); } /** * @brief Starts reception using DMA for frames up to 16 bits. * * @param[in] spip pointer to the @p SPIDriver object * @param[in] n number of words to be exchanged * @param[out] rxbuf the pointer to the receive buffer * * @notapi */ static void spi_start_dma_rx16(SPIDriver *spip, size_t n, uint16_t *rxbuf) { edmaChannelSetup(spip->rx_channel, /* channel. */ DSPI_POPR16_ADDRESS(spip), /* src. */ rxbuf, /* dst. */ 0, /* soff, do not advance. */ 2, /* doff, advance by two. */ 1, /* ssize, 16 bits transfers.*/ 1, /* dsize, 16 bits transfers.*/ 2, /* nbytes, always two. */ n, /* iter. */ 0, /* slast, no source adjust. */ 0, /* dlast. */ EDMA_TCD_MODE_DREQ | EDMA_TCD_MODE_INT_END); /* mode.*/ edmaChannelStart(spip->rx_channel); } /** * @brief Starts transmission using DMA for frames up to 8 bits. * * @param[in] spip pointer to the @p SPIDriver object * @param[in] n number of words to be exchanged * @param[in] txbuf the pointer to the transmit buffer * * @notapi */ static void spi_start_dma_tx8(SPIDriver *spip, size_t n, const uint8_t *txbuf) { uint32_t cmd = spip->config->pushr & ~DSPI_PUSHR_EXCLUDED_BITS; uint8_t *cp = (uint8_t *)DSPI_PUSHR8_ADDRESS(spip); /* The first frame is pushed by the CPU, then the DMA is activated to send the following frames.*/ spip->dspi->PUSHR.R = cmd | (uint32_t)*txbuf++; *cp = 0x55; /* Setting up TX DMA TCD parameters for 8 bits transfers.*/ edmaChannelSetup(spip->tx_channel, /* channel. */ txbuf, /* src. */ DSPI_PUSHR8_ADDRESS(spip), /* dst. */ 1, /* soff, advance by 1. */ 0, /* doff, do not advance. */ 0, /* ssize, 8 bits transfers. */ 0, /* dsize, 8 bits transfers. */ 1, /* nbytes, always one. */ n - 1, /* iter. */ 0, /* slast, no source adjust. */ 0, /* dlast, no dest.adjust. */ EDMA_TCD_MODE_DREQ); /* mode. */ edmaChannelStart(spip->tx_channel); } /** * @brief Starts transmission using DMA for frames up to 16 bits. * * @param[in] spip pointer to the @p SPIDriver object * @param[in] n number of words to be exchanged * @param[in] txbuf the pointer to the transmit buffer * * @notapi */ static void spi_start_dma_tx16(SPIDriver *spip, size_t n, const uint16_t *txbuf) { uint32_t cmd = spip->config->pushr & ~DSPI_PUSHR_EXCLUDED_BITS; /* The first frame is pushed by the CPU, then the DMA is activated to send the following frames.*/ spip->dspi->PUSHR.R = cmd | (uint32_t)*txbuf++; /* Setting up TX DMA TCD parameters for 16 bits transfers.*/ edmaChannelSetup(spip->tx_channel, /* channel. */ txbuf, /* src. */ DSPI_PUSHR16_ADDRESS(spip), /* dst. */ 2, /* soff, advance by 2. */ 0, /* doff, do not advance. */ 1, /* ssize, 16 bits transfers.*/ 1, /* dsize, 16 bits transfers.*/ 2, /* nbytes, always two. */ n - 1, /* iter. */ 0, /* slast, no source adjust. */ 0, /* dlast, no dest.adjust. */ EDMA_TCD_MODE_DREQ); /* mode. */ edmaChannelStart(spip->tx_channel); } /** * @brief Starts transmission using FIFO pre-filling for frames up to 8 bits. * * @param[in] spip pointer to the @p SPIDriver object * @param[in] n number of words to be exchanged * @param[in] txbuf the pointer to the transmit buffer * * @notapi */ static void spi_tx_prefill8(SPIDriver *spip, size_t n, const uint8_t *txbuf) { uint32_t cmd = spip->config->pushr & ~DSPI_PUSHR_EXCLUDED_BITS; do { if (--n == 0) { spip->dspi->PUSHR.R = SPC5_PUSHR_EOQ | cmd | (uint32_t)*txbuf; break; } spip->dspi->PUSHR.R = cmd | (uint32_t)*txbuf; txbuf++; } while (TRUE); } /** * @brief Starts transmission using FIFO pre-filling for frames up to 16 bits. * * @param[in] spip pointer to the @p SPIDriver object * @param[in] n number of words to be exchanged * @param[in] txbuf the pointer to the transmit buffer * * @notapi */ static void spi_tx_prefill16(SPIDriver *spip, size_t n, const uint16_t *txbuf) { uint32_t cmd = spip->config->pushr & ~DSPI_PUSHR_EXCLUDED_BITS; do { if (--n == 0) { spip->dspi->PUSHR.R = SPC5_PUSHR_EOQ | cmd | (uint32_t)*txbuf; break; } spip->dspi->PUSHR.R = cmd | (uint32_t)*txbuf; txbuf++; } while (TRUE); } static void spi_serve_rx_irq(edma_channel_t channel, void *p) { SPIDriver *spip = (SPIDriver *)p; (void)channel; /* Stops the DSPI and clears the queues.*/ spip->dspi->MCR.R = SPC5_MCR_MSTR | SPC5_MCR_HALT | SPC5_MCR_CLR_TXF | SPC5_MCR_CLR_RXF; /* Portable SPI ISR code defined in the high level driver, note, it is a macro.*/ _spi_isr_code(spip); } static void spi_serve_dma_error_irq(edma_channel_t channel, void *p, uint32_t esr) { SPIDriver *spip = (SPIDriver *)p; (void)channel; (void)esr; /* Stops the DSPI and clears the queues.*/ spip->dspi->MCR.R = SPC5_MCR_MSTR | SPC5_MCR_HALT | SPC5_MCR_CLR_TXF | SPC5_MCR_CLR_RXF; SPC5_SPI_DMA_ERROR_HOOK(spip); } /*===========================================================================*/ /* Driver interrupt handlers. */ /*===========================================================================*/ /*===========================================================================*/ /* Driver exported functions. */ /*===========================================================================*/ /** * @brief Low level SPI driver initialization. * * @notapi */ void spi_lld_init(void) { #if SPC5_SPI_USE_DSPI0 /* Driver initialization.*/ spiObjectInit(&SPID1); SPID1.dspi = &SPC5_DSPI0; SPID1.tx_channel = EDMA_ERROR; SPID1.rx_channel = EDMA_ERROR; #endif /* SPC5_SPI_USE_DSPI0 */ #if SPC5_SPI_USE_DSPI1 /* Driver initialization.*/ spiObjectInit(&SPID2); SPID2.dspi = &SPC5_DSPI1; SPID2.tx_channel = EDMA_ERROR; SPID2.rx_channel = EDMA_ERROR; #endif /* SPC5_SPI_USE_DSPI1 */ #if SPC5_SPI_USE_DSPI2 /* Driver initialization.*/ spiObjectInit(&SPID3); SPID3.dspi = &SPC5_DSPI2; SPID3.tx_channel = EDMA_ERROR; SPID3.rx_channel = EDMA_ERROR; #endif /* SPC5_SPI_USE_DSPI2 */ #if SPC5_SPI_USE_DSPI03 /* Driver initialization.*/ spiObjectInit(&SPID4); SPID4.dspi = &SPC5_DSPI3; SPID4.tx_channel = EDMA_ERROR; SPID4.rx_channel = EDMA_ERROR; #endif /* SPC5_SPI_USE_DSPI3 */ } /** * @brief Configures and activates the SPI peripheral. * * @param[in] spip pointer to the @p SPIDriver object * * @notapi */ void spi_lld_start(SPIDriver *spip) { if (spip->state == SPI_STOP) { /* Enables the peripheral.*/ #if SPC5_SPI_USE_DSPI0 if (&SPID1 == spip) { SPC5_DSPI0_ENABLE_CLOCK(); spip->tx_channel = edmaChannelAllocate(&spi_dspi0_tx_dma_config); spip->rx_channel = edmaChannelAllocate(&spi_dspi0_rx_dma_config); } #endif /* SPC5_SPI_USE_DSPI0 */ #if SPC5_SPI_USE_DSPI1 if (&SPID2 == spip) { SPC5_DSPI1_ENABLE_CLOCK(); spip->tx_channel = edmaChannelAllocate(&spi_dspi1_tx_dma_config); spip->rx_channel = edmaChannelAllocate(&spi_dspi1_rx_dma_config); } #endif /* SPC5_SPI_USE_DSPI1 */ #if SPC5_SPI_USE_DSPI2 if (&SPID3 == spip) { SPC5_DSPI2_ENABLE_CLOCK(); spip->tx_channel = edmaChannelAllocate(&spi_dspi2_tx_dma_config); spip->rx_channel = edmaChannelAllocate(&spi_dspi2_rx_dma_config); } #endif /* SPC5_SPI_USE_DSPI2 */ #if SPC5_SPI_USE_DSPI3 if (&SPID4 == spip) { SPC5_DSPI3_ENABLE_CLOCK(); spip->tx_channel = edmaChannelAllocate(&spi_dspi3_tx_dma_config); spip->rx_channel = edmaChannelAllocate(&spi_dspi3_rx_dma_config); } #endif /* SPC5_SPI_USE_DSPI3 */ chDbgAssert((spip->tx_channel != EDMA_ERROR) && (spip->rx_channel != EDMA_ERROR), "spi_lld_start(), #1", "channel cannot be allocated"); } /* Configures the peripheral.*/ spip->dspi->MCR.R = SPC5_MCR_MSTR | SPC5_MCR_HALT | spip->config->mcr; spip->dspi->CTAR[0].R = spip->config->ctar0; spip->dspi->RSER.R = SPC5_RSER_TFFF_RE | SPC5_RSER_TFFF_DIRS | SPC5_RSER_RFDF_RE | SPC5_RSER_RFDF_DIRS; spip->dspi->SR.R = spip->dspi->SR.R; } /** * @brief Deactivates the SPI peripheral. * * @param[in] spip pointer to the @p SPIDriver object * * @notapi */ void spi_lld_stop(SPIDriver *spip) { if (spip->state == SPI_READY) { /* Releases the allocated EDMA channels.*/ edmaChannelRelease(spip->tx_channel); edmaChannelRelease(spip->rx_channel); /* Resets the peripheral.*/ spip->dspi->CTAR[0].R = 0; spip->dspi->RSER.R = 0; spip->dspi->SR.R = spip->dspi->SR.R; spip->dspi->MCR.R = SPC5_MCR_MSTR | SPC5_MCR_MDIS | SPC5_MCR_CLR_TXF | SPC5_MCR_CLR_RXF | SPC5_MCR_HALT; #if SPC5_SPI_USE_DSPI0 if (&SPID1 == spip) { SPC5_DSPI0_DISABLE_CLOCK(); } #endif /* SPC5_SPI_USE_DSPI0 */ #if SPC5_SPI_USE_DSPI1 if (&SPID2 == spip) { SPC5_DSPI1_DISABLE_CLOCK(); } #endif /* SPC5_SPI_USE_DSPI1 */ #if SPC5_SPI_USE_DSPI2 if (&SPID3 == spip) { SPC5_DSPI2_DISABLE_CLOCK(); } #endif /* SPC5_SPI_USE_DSPI2 */ #if SPC5_SPI_USE_DSPI3 if (&SPID4 == spip) { SPC5_DSPI3_DISABLE_CLOCK(); } #endif /* SPC5_SPI_USE_DSPI3 */ } } /** * @brief Asserts the slave select signal and prepares for transfers. * * @param[in] spip pointer to the @p SPIDriver object * * @notapi */ void spi_lld_select(SPIDriver *spip) { (void)spip; } /** * @brief Deasserts the slave select signal. * @details The previously selected peripheral is unselected. * * @param[in] spip pointer to the @p SPIDriver object * * @notapi */ void spi_lld_unselect(SPIDriver *spip) { (void)spip; } /** * @brief Ignores data on the SPI bus. * @details This asynchronous function starts the transmission of a series of * idle words on the SPI bus and ignores the received data. * @post At the end of the operation the configured callback is invoked. * * @param[in] spip pointer to the @p SPIDriver object * @param[in] n number of words to be ignored * * @notapi */ void spi_lld_ignore(SPIDriver *spip, size_t n) { (void)spip; (void)n; } /** * @brief Exchanges data on the SPI bus. * @details This asynchronous function starts a simultaneous transmit/receive * operation. * @post At the end of the operation the configured callback is invoked. * @note The buffers are organized as uint8_t arrays for data sizes below or * equal to 8 bits else it is organized as uint16_t arrays. * * @param[in] spip pointer to the @p SPIDriver object * @param[in] n number of words to be exchanged * @param[in] txbuf the pointer to the transmit buffer * @param[out] rxbuf the pointer to the receive buffer * * @notapi */ void spi_lld_exchange(SPIDriver *spip, size_t n, const void *txbuf, void *rxbuf) { /* Starting transfer.*/ spip->dspi->MCR.B.HALT = 0; /* DMAs require a different setup depending on the frame size.*/ if (spip->dspi->CTAR[0].B.FMSZ < 8) { /* Setting up the RX DMA channel.*/ spi_start_dma_rx8(spip, n, rxbuf); if (n <= SPC5_DSPI_FIFO_DEPTH) { /* If the total transfer size is smaller than the TX FIFO size then the whole transmitted data is pushed here and the TX DMA is not activated.*/ spi_tx_prefill8(spip, n, txbuf); } else { spi_start_dma_tx8(spip, n, txbuf); } } else { /* Setting up the RX DMA channel.*/ spi_start_dma_rx16(spip, n, rxbuf); if (n <= SPC5_DSPI_FIFO_DEPTH) { /* If the total transfer size is smaller than the TX FIFO size then the whole transmitted data is pushed here and the TX DMA is not activated.*/ spi_tx_prefill16(spip, n, txbuf); } else { spi_start_dma_tx16(spip, n, txbuf); } } } /** * @brief Sends data over the SPI bus. * @details This asynchronous function starts a transmit operation. * @post At the end of the operation the configured callback is invoked. * @note The buffers are organized as uint8_t arrays for data sizes below or * equal to 8 bits else it is organized as uint16_t arrays. * * @param[in] spip pointer to the @p SPIDriver object * @param[in] n number of words to send * @param[in] txbuf the pointer to the transmit buffer * * @notapi */ void spi_lld_send(SPIDriver *spip, size_t n, const void *txbuf) { (void)spip; (void)n; (void)txbuf; } /** * @brief Receives data from the SPI bus. * @details This asynchronous function starts a receive operation. * @post At the end of the operation the configured callback is invoked. * @note The buffers are organized as uint8_t arrays for data sizes below or * equal to 8 bits else it is organized as uint16_t arrays. * * @param[in] spip pointer to the @p SPIDriver object * @param[in] n number of words to receive * @param[out] rxbuf the pointer to the receive buffer * * @notapi */ void spi_lld_receive(SPIDriver *spip, size_t n, void *rxbuf) { (void)spip; (void)n; (void)rxbuf; } /** * @brief Exchanges one frame using a polled wait. * @details This synchronous function exchanges one frame using a polled * synchronization method. This function is useful when exchanging * small amount of data on high speed channels, usually in this * situation is much more efficient just wait for completion using * polling than suspending the thread waiting for an interrupt. * * @param[in] spip pointer to the @p SPIDriver object * @param[in] frame the data frame to send over the SPI bus * @return The received data frame from the SPI bus. */ uint16_t spi_lld_polled_exchange(SPIDriver *spip, uint16_t frame) { (void)spip; (void)frame; return 0; } #endif /* HAL_USE_SPI */ /** @} */