/* * SPDX-License-Identifier: MIT * * Copyright (C) 2013-2024 OpenMV, LLC. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * OMV SPI port for stm32. */ #include #include #include #include #include STM32_HAL_H #include "py/mphal.h" #include "omv_boardconfig.h" #include "irq.h" #include "omv_common.h" #include "stm_dma.h" #include "omv_gpio.h" #include "omv_spi.h" // If an SPI handle is already defined in MicroPython, reuse that handle to allow // MicroPython to process the SPI IRQ, otherwise define the IRQ handler and its handle. #define DEFINE_SPI_INSTANCE(n) \ static SPI_HandleTypeDef SPIHandle##n; \ void SPI##n##_IRQHandler(void) { HAL_SPI_IRQHandler(&SPIHandle##n); } #if defined(OMV_SPI1_ID) && defined(MICROPY_HW_SPI1_SCK) extern SPI_HandleTypeDef SPIHandle1; #elif defined(OMV_SPI1_ID) DEFINE_SPI_INSTANCE(1) #endif #if defined(OMV_SPI2_ID) && defined(MICROPY_HW_SPI2_SCK) extern SPI_HandleTypeDef SPIHandle2; #elif defined(OMV_SPI2_ID) DEFINE_SPI_INSTANCE(2) #endif #if defined(OMV_SPI3_ID) && defined(MICROPY_HW_SPI3_SCK) extern SPI_HandleTypeDef SPIHandle3; #elif defined(OMV_SPI3_ID) DEFINE_SPI_INSTANCE(3) #endif #if defined(OMV_SPI4_ID) && defined(MICROPY_HW_SPI4_SCK) extern SPI_HandleTypeDef SPIHandle4; #elif defined(OMV_SPI4_ID) DEFINE_SPI_INSTANCE(4) #endif #if defined(OMV_SPI5_ID) && defined(MICROPY_HW_SPI5_SCK) extern SPI_HandleTypeDef SPIHandle5; #elif defined(OMV_SPI5_ID) DEFINE_SPI_INSTANCE(5) #endif #if defined(OMV_SPI6_ID) && defined(MICROPY_HW_SPI6_SCK) extern SPI_HandleTypeDef SPIHandle6; #elif defined(OMV_SPI6_ID) DEFINE_SPI_INSTANCE(6) #endif #define INITIALIZE_SPI_DESCR(spi, spi_number) \ do { \ (spi)->id = spi_number; \ (spi)->irqn = SPI##spi_number##_IRQn; \ (spi)->cs = OMV_SPI##spi_number##_SSEL_PIN; \ (spi)->descr = &SPIHandle##spi_number; \ (spi)->descr->Instance = SPI##spi_number; \ (spi)->dma_descr_tx = (DMA_HandleTypeDef) \ { OMV_SPI##spi_number##_DMA_TX_CHANNEL, { OMV_SPI##spi_number##_DMA_TX_REQUEST } }; \ (spi)->dma_descr_rx = (DMA_HandleTypeDef) \ { OMV_SPI##spi_number##_DMA_RX_CHANNEL, { OMV_SPI##spi_number##_DMA_RX_REQUEST } }; \ } while (0) #ifndef OMV_SPI_DMA_XFER_PORTS #define OMV_SPI_DMA_XFER_PORTS (0) #endif static omv_spi_t *omv_spi_descr_all[6] = { NULL }; #if defined(STM32N6) static DMA_NodeTypeDef OMV_ATTR_SECTION(OMV_ATTR_ALIGNED(omv_dma_nodes_all[6 * 2], 32), OMV_VOSPI_DMA_BUFFER); #endif static uint32_t omv_spi_clocksource(SPI_TypeDef *spi) { #if defined(STM32F4) || defined(STM32F7) if (spi == SPI2) { // SPI2 is on APB1 return HAL_RCC_GetPCLK1Freq(); } else if (spi == SPI3) { // SPI3 is on APB1 return HAL_RCC_GetPCLK1Freq(); } else { // SPI1, SPI4, SPI5 and SPI6 are on APB2 return HAL_RCC_GetPCLK2Freq(); } #elif defined(STM32H7) if (spi == SPI1 || spi == SPI2 || spi == SPI3) { return HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SPI123); } else if (spi == SPI4 || spi == SPI5) { return HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SPI45); } else { return HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SPI6); } #elif defined(STM32N6) if (spi == SPI1) { return HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SPI1); } else if (spi == SPI2) { return HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SPI2); } else if (spi == SPI3) { return HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SPI3); } else if (spi == SPI4) { return HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SPI4); } else if (spi == SPI5) { return HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SPI5); } else { return HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SPI6); } #endif } static int omv_spi_prescaler(SPI_TypeDef *spi, uint32_t baudrate) { const uint32_t prescalers[] = { SPI_BAUDRATEPRESCALER_2, SPI_BAUDRATEPRESCALER_4, SPI_BAUDRATEPRESCALER_8, SPI_BAUDRATEPRESCALER_16, SPI_BAUDRATEPRESCALER_32, SPI_BAUDRATEPRESCALER_64, SPI_BAUDRATEPRESCALER_128, SPI_BAUDRATEPRESCALER_256 }; uint32_t clksource = omv_spi_clocksource(spi); for (size_t i = 0; i < OMV_ARRAY_SIZE(prescalers); i++) { uint32_t target = clksource / ((2 << i)); if (target <= baudrate) { return prescalers[i]; } } return SPI_BAUDRATEPRESCALER_256; } static void omv_spi_callback(SPI_HandleTypeDef *hspi) { omv_spi_t *spi = NULL; if (0) { #if defined(OMV_SPI1_ID) } else if (hspi->Instance == SPI1) { spi = omv_spi_descr_all[0]; #endif #if defined(OMV_SPI2_ID) } else if (hspi->Instance == SPI2) { spi = omv_spi_descr_all[1]; #endif #if defined(OMV_SPI3_ID) } else if (hspi->Instance == SPI3) { spi = omv_spi_descr_all[2]; #endif #if defined(OMV_SPI4_ID) } else if (hspi->Instance == SPI4) { spi = omv_spi_descr_all[3]; #endif #if defined(OMV_SPI5_ID) } else if (hspi->Instance == SPI5) { spi = omv_spi_descr_all[4]; #endif #if defined(OMV_SPI6_ID) } else if (hspi->Instance == SPI6) { spi = omv_spi_descr_all[5]; #endif } if (spi == NULL) { return; } if (hspi->ErrorCode != HAL_SPI_ERROR_NONE) { spi->xfer_flags |= OMV_SPI_XFER_FAILED; spi->xfer_error = hspi->ErrorCode; omv_spi_transfer_abort(spi); } else { spi->xfer_flags |= OMV_SPI_XFER_COMPLETE; } if (spi->callback) { uint8_t *buf = (uint8_t *) (spi->descr->pRxBuffPtr ? spi->descr->pRxBuffPtr : spi->descr->pTxBuffPtr); if (spi->dma_flags & OMV_SPI_DMA_DOUBLE) { if (spi->xfer_flags & OMV_SPI_XFER_HALF) { uint32_t size = spi->descr->RxXferSize ? spi->descr->RxXferSize : spi->descr->TxXferSize; buf += (size * ((spi->descr->Init.DataSize == SPI_DATASIZE_8BIT) ? 1 : 2)) / 2; } spi->xfer_flags ^= OMV_SPI_XFER_HALF; } spi->callback(spi, spi->userdata, buf); } } int omv_spi_transfer_start(omv_spi_t *spi, omv_spi_transfer_t *xfer) { // No TX transfers in circular or double buffer mode. if (xfer->txbuf && (spi->dma_flags & (OMV_SPI_DMA_CIRCULAR | OMV_SPI_DMA_DOUBLE))) { return -1; } spi->callback = xfer->callback; spi->userdata = xfer->userdata; spi->xfer_error = 0; spi->xfer_flags = xfer->flags; spi->xfer_flags &= ~(OMV_SPI_XFER_FAILED | OMV_SPI_XFER_COMPLETE | OMV_SPI_XFER_HALF); if (spi->xfer_flags & OMV_SPI_XFER_BLOCKING) { if (xfer->txbuf && xfer->rxbuf) { if (HAL_SPI_TransmitReceive(spi->descr, xfer->txbuf, xfer->rxbuf, xfer->size, xfer->timeout) != HAL_OK) { return -1; } } else if (xfer->txbuf) { if (HAL_SPI_Transmit(spi->descr, xfer->txbuf, xfer->size, xfer->timeout) != HAL_OK) { return -1; } } else if (xfer->rxbuf) { if (HAL_SPI_Receive(spi->descr, xfer->rxbuf, xfer->size, xfer->timeout) != HAL_OK) { return -1; } } } else if (spi->xfer_flags & OMV_SPI_XFER_NONBLOCK) { if (xfer->txbuf && xfer->rxbuf) { if (HAL_SPI_TransmitReceive_IT(spi->descr, xfer->txbuf, xfer->rxbuf, xfer->size) != HAL_OK) { return -1; } } else if (xfer->txbuf) { if (HAL_SPI_Transmit_IT(spi->descr, xfer->txbuf, xfer->size) != HAL_OK) { return -1; } } else if (xfer->rxbuf) { if (HAL_SPI_Receive_IT(spi->descr, xfer->rxbuf, xfer->size) != HAL_OK) { return -1; } } } else if (spi->xfer_flags & OMV_SPI_XFER_DMA) { if (xfer->txbuf && xfer->rxbuf) { if (HAL_SPI_TransmitReceive_DMA(spi->descr, xfer->txbuf, xfer->rxbuf, xfer->size) != HAL_OK) { return -1; } } else if (xfer->txbuf) { if (HAL_SPI_Transmit_DMA(spi->descr, xfer->txbuf, xfer->size) != HAL_OK) { return -1; } } else if (xfer->rxbuf) { if (HAL_SPI_Receive_DMA(spi->descr, xfer->rxbuf, xfer->size) != HAL_OK) { return -1; } } } else { return -1; } return 0; } int omv_spi_transfer_abort(omv_spi_t *spi) { if (spi && spi->initialized) { if (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) { HAL_SPI_Abort_IT(spi->descr); } else { HAL_SPI_Abort(spi->descr); } } return 0; } static int omv_spi_dma_init(omv_spi_t *spi, uint32_t direction, omv_spi_config_t *config) { DMA_HandleTypeDef *dma_descr = NULL; if (direction == DMA_MEMORY_TO_PERIPH) { dma_descr = &spi->dma_descr_tx; } else { dma_descr = &spi->dma_descr_rx; } #if defined(STM32N6) DMA_NodeTypeDef *dma_nodes = NULL; DMA_QListTypeDef *dma_queue = NULL; if (direction == DMA_MEMORY_TO_PERIPH) { dma_queue = &spi->dma_queue_tx; dma_nodes = &omv_dma_nodes_all[(spi->id - 1) * 2 + 0]; } else { dma_queue = &spi->dma_queue_rx; dma_nodes = &omv_dma_nodes_all[(spi->id - 1) * 2 + 1]; } #endif uint32_t ssize = 4; uint32_t dsize = 4; #if defined(STM32F4) || defined(STM32F7) ssize = config->datasize / 8; #endif #if defined(STM32H7) || defined(STM32N6) uint32_t request = dma_descr->Init.Request; #else uint32_t request = dma_descr->Init.Channel; #endif bool circular = config->dma_flags & OMV_SPI_DMA_CIRCULAR; // Initialize DMA. if (stm_dma_init(dma_descr, dma_descr->Instance, request, direction, ssize, dsize, OMV_SPI_DMA_XFER_PORTS, &stm_dma_spi_init, circular)) { return -1; } #if defined(STM32N6) if (circular) { // Initialize DMA in circular mode. if (stm_dma_ll_init(dma_descr, dma_queue, dma_nodes, 1, OMV_SPI_DMA_LIST_PORTS)) { return -1; } } #endif // Link the DMA handle to SPI handle. if (direction == DMA_MEMORY_TO_PERIPH) { __HAL_LINKDMA(spi->descr, hdmatx, spi->dma_descr_tx); } else { __HAL_LINKDMA(spi->descr, hdmarx, spi->dma_descr_rx); } // Set the SPI handle used by the DMA channel's IRQ handler. stm_dma_set_irq_descr(dma_descr->Instance, dma_descr); // Get DMA channel's IRQ number. uint8_t dma_irqn = stm_dma_channel_to_irqn(dma_descr->Instance); // Configure and enable DMA IRQ channel. NVIC_SetPriority(dma_irqn, IRQ_PRI_DMA); HAL_NVIC_EnableIRQ(dma_irqn); return 0; } static int omv_spi_bus_init(omv_spi_t *spi, omv_spi_config_t *config) { SPI_HandleTypeDef *spi_descr = spi->descr; spi_descr->Init.Mode = config->spi_mode; spi_descr->Init.TIMode = SPI_TIMODE_DISABLE; spi_descr->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; spi_descr->Init.NSS = (config->nss_enable == false) ? SPI_NSS_SOFT : SPI_NSS_HARD_OUTPUT; spi_descr->Init.DataSize = (config->datasize == 8) ? SPI_DATASIZE_8BIT : SPI_DATASIZE_16BIT; spi_descr->Init.FirstBit = config->bit_order; spi_descr->Init.CLKPhase = config->clk_pha; spi_descr->Init.CLKPolarity = config->clk_pol; spi_descr->Init.BaudRatePrescaler = omv_spi_prescaler(spi_descr->Instance, config->baudrate); #if defined(STM32F7) || defined(STM32H7) || defined(STM32N6) spi_descr->Init.NSSPMode = SPI_NSS_PULSE_DISABLE; #if defined(STM32H7) || defined(STM32N6) spi_descr->Init.NSSPolarity = (config->nss_pol == 0) ? SPI_NSS_POLARITY_LOW : SPI_NSS_POLARITY_HIGH; spi_descr->Init.FifoThreshold = SPI_FIFO_THRESHOLD_04DATA; spi_descr->Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE; spi_descr->Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE; spi_descr->Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE; spi_descr->Init.MasterKeepIOState = (config->data_retained == true) ? SPI_MASTER_KEEP_IO_STATE_ENABLE : SPI_MASTER_KEEP_IO_STATE_DISABLE; spi_descr->Init.IOSwap = SPI_IO_SWAP_DISABLE; #endif #endif // Configure bus direction. if (config->bus_mode == OMV_SPI_BUS_TX_RX) { spi_descr->Init.Direction = SPI_DIRECTION_2LINES; } else if (config->bus_mode == OMV_SPI_BUS_RX) { spi_descr->Init.Direction = SPI_DIRECTION_2LINES_RXONLY; } else { #if defined(STM32H7) || defined(STM32N6) spi_descr->Init.Direction = SPI_DIRECTION_2LINES_TXONLY; #else spi_descr->Init.Direction = SPI_DIRECTION_1LINE; #endif } if (HAL_SPI_Init(spi_descr) != HAL_OK) { HAL_SPI_DeInit(spi_descr); return -1; } return 0; } int omv_spi_init(omv_spi_t *spi, omv_spi_config_t *config) { memset(spi, 0, sizeof(omv_spi_t)); if (0) { #if defined(OMV_SPI1_ID) } else if (config->id == 1) { INITIALIZE_SPI_DESCR(spi, 1); #endif #if defined(OMV_SPI2_ID) } else if (config->id == 2) { INITIALIZE_SPI_DESCR(spi, 2); #endif #if defined(OMV_SPI3_ID) } else if (config->id == 3) { INITIALIZE_SPI_DESCR(spi, 3); #endif #if defined(OMV_SPI4_ID) } else if (config->id == 4) { INITIALIZE_SPI_DESCR(spi, 4); #endif #if defined(OMV_SPI5_ID) } else if (config->id == 5) { INITIALIZE_SPI_DESCR(spi, 5); #endif #if defined(OMV_SPI6_ID) } else if (config->id == 6) { INITIALIZE_SPI_DESCR(spi, 6); #endif } else { return -1; } if (omv_spi_bus_init(spi, config) != 0) { return -1; } if (config->dma_flags & (OMV_SPI_DMA_NORMAL | OMV_SPI_DMA_CIRCULAR)) { int ret = 0; if (config->bus_mode & OMV_SPI_BUS_TX) { ret |= omv_spi_dma_init(spi, DMA_MEMORY_TO_PERIPH, config); } if (config->bus_mode & OMV_SPI_BUS_RX) { ret |= omv_spi_dma_init(spi, DMA_PERIPH_TO_MEMORY, config); } if (ret != 0) { return -1; } } // Configure and enable SPI IRQ channel. NVIC_SetPriority(spi->irqn, IRQ_PRI_SPI); HAL_NVIC_EnableIRQ(spi->irqn); // Install TX/RX callbacks even if DMA mode is not enabled for non-blocking transfers. HAL_SPI_RegisterCallback(spi->descr, HAL_SPI_TX_RX_COMPLETE_CB_ID, omv_spi_callback); HAL_SPI_RegisterCallback(spi->descr, HAL_SPI_TX_COMPLETE_CB_ID, omv_spi_callback); HAL_SPI_RegisterCallback(spi->descr, HAL_SPI_RX_COMPLETE_CB_ID, omv_spi_callback); if (config->dma_flags & OMV_SPI_DMA_DOUBLE) { HAL_SPI_RegisterCallback(spi->descr, HAL_SPI_TX_RX_HALF_COMPLETE_CB_ID, omv_spi_callback); HAL_SPI_RegisterCallback(spi->descr, HAL_SPI_TX_HALF_COMPLETE_CB_ID, omv_spi_callback); HAL_SPI_RegisterCallback(spi->descr, HAL_SPI_RX_HALF_COMPLETE_CB_ID, omv_spi_callback); } spi->initialized = true; spi->dma_flags = config->dma_flags; omv_spi_descr_all[config->id - 1] = spi; return 0; } int omv_spi_deinit(omv_spi_t *spi) { if (spi && spi->initialized) { spi->initialized = false; omv_spi_descr_all[spi->id - 1] = NULL; omv_spi_transfer_abort(spi); if (spi->dma_flags & (OMV_SPI_DMA_NORMAL | OMV_SPI_DMA_CIRCULAR)) { if (spi->descr->hdmatx != NULL) { HAL_DMA_Abort(spi->descr->hdmatx); } if (spi->descr->hdmarx != NULL) { HAL_DMA_Abort(spi->descr->hdmarx); } } HAL_SPI_DeInit(spi->descr); HAL_NVIC_DisableIRQ(spi->irqn); // Deinit the CS pin here versus in HAL_SPI_MspDeInit which is shared code. omv_gpio_deinit(spi->cs); } return 0; } // This function is only needed for the py_tv driver on the RT1060 to slow down the SPI bus on reads. // The STM32 is capable of reading data on the SPI bus at high speeds without issues... int omv_spi_set_baudrate(omv_spi_t *spi, uint32_t baudrate) { return 0; } int omv_spi_default_config(omv_spi_config_t *config, uint32_t bus_id) { config->id = bus_id; config->baudrate = 10000000; config->datasize = 8; config->spi_mode = OMV_SPI_MODE_MASTER; config->bus_mode = OMV_SPI_BUS_TX_RX; config->bit_order = OMV_SPI_MSB_FIRST; config->clk_pol = OMV_SPI_CPOL_LOW; config->clk_pha = OMV_SPI_CPHA_1EDGE; config->nss_pol = OMV_SPI_NSS_LOW; config->nss_enable = true; config->dma_flags = 0; config->data_retained = true; return 0; }