From dfb24c95e86d2b754029549c01ab98ffad487110 Mon Sep 17 00:00:00 2001 From: Silvano Seva Date: Sat, 2 Nov 2024 19:18:32 +0100 Subject: [PATCH] Drivers: SPI: added driver for STM32H7 devices --- meson.build | 3 +- .../SPI/{spi_stm32.c => spi_stm32f4.c} | 0 platform/drivers/SPI/spi_stm32h7.c | 206 ++++++++++++++++++ platform/mcu/STM32H7xx/drivers/pll.cpp | 9 + platform/mcu/STM32H7xx/drivers/pll.h | 34 +++ 5 files changed, 251 insertions(+), 1 deletion(-) rename platform/drivers/SPI/{spi_stm32.c => spi_stm32f4.c} (100%) create mode 100644 platform/drivers/SPI/spi_stm32h7.c diff --git a/meson.build b/meson.build index f6a813bb..48b44034 100644 --- a/meson.build +++ b/meson.build @@ -222,7 +222,7 @@ stm32f405_src = ['platform/mcu/STM32F4xx/boot/startup.cpp', 'platform/mcu/STM32F4xx/drivers/rcc.c', 'platform/mcu/STM32F4xx/drivers/i2c_stm32.c', 'platform/drivers/ADC/adc_stm32f4.c', - 'platform/drivers/SPI/spi_stm32.c', + 'platform/drivers/SPI/spi_stm32f4.c', 'platform/drivers/GPIO/gpio_stm32.c', 'platform/drivers/audio/stm32_dac.cpp', 'platform/drivers/audio/stm32_adc.cpp', @@ -285,6 +285,7 @@ stm32h743_src = ['platform/mcu/STM32H7xx/boot/startup.cpp', 'platform/mcu/STM32H7xx/drivers/delays.cpp', 'platform/drivers/GPIO/gpio_stm32.c', 'platform/drivers/ADC/adc_stm32h7.c', + 'platform/drivers/SPI/spi_stm32h7.c', 'platform/mcu/CMSIS/Device/ST/STM32H7xx/Source/system_stm32h7xx.c'] stm32h743_inc = ['platform/mcu/CMSIS/Include', diff --git a/platform/drivers/SPI/spi_stm32.c b/platform/drivers/SPI/spi_stm32f4.c similarity index 100% rename from platform/drivers/SPI/spi_stm32.c rename to platform/drivers/SPI/spi_stm32f4.c diff --git a/platform/drivers/SPI/spi_stm32h7.c b/platform/drivers/SPI/spi_stm32h7.c new file mode 100644 index 00000000..901f8ab5 --- /dev/null +++ b/platform/drivers/SPI/spi_stm32h7.c @@ -0,0 +1,206 @@ +/*************************************************************************** + * Copyright (C) 2024 by Silvano Seva IU2KWO * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include +#include +#include +#include "spi_stm32.h" + + +static inline uint8_t spi_sendRecv(SPI_TypeDef *spi, const uint8_t val) +{ + // Of course, setting the SPI frame size is not enough: to actually send + // 8 bits instead of 32, we have to access the TXDR register with an 8 bit + // write operation. Damn ST... + *((volatile uint8_t *) &spi->TXDR) = val; + while((spi->SR & SPI_SR_TXC) == 0) ; + return spi->RXDR; +} + +int spiStm32_init(const struct spiDevice *dev, const uint32_t speed, const uint8_t flags) +{ + SPI_TypeDef *spi = (SPI_TypeDef *) dev->priv; + uint32_t busClk; + + // On STM32H7 the clock tree is a bit more complicated than on STM32F4: + // - SPI1/2/3 use PLL1_Q clock for SCK generation and APB clock for reg access + // - SPI4/5/6 use APB clock both for SCK generation and reg access + // From PLL config PLL1_Q output is set at 100MHz + switch((uint32_t) spi) + { + case SPI1_BASE: + busClk = 100000000; + RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; + __DSB(); + break; + + case SPI2_BASE: + busClk = 100000000; + RCC->APB1LENR |= RCC_APB1LENR_SPI2EN; + __DSB(); + break; + + case SPI3_BASE: + busClk = 100000000; + RCC->APB1LENR |= RCC_APB1LENR_SPI3EN; + __DSB(); + break; + + case SPI4_BASE: + busClk = getBusClock(PERIPH_BUS_APB2); + RCC->APB2ENR |= RCC_APB2ENR_SPI4EN; + __DSB(); + break; + + case SPI5_BASE: + busClk = getBusClock(PERIPH_BUS_APB2); + RCC->APB2ENR |= RCC_APB2ENR_SPI5EN; + __DSB(); + break; + + case SPI6_BASE: + busClk = getBusClock(PERIPH_BUS_APB4); + RCC->APB4ENR |= RCC_APB4ENR_SPI6EN; + __DSB(); + break; + + default: + return -ENODEV; + break; + } + + uint8_t spiDiv; + uint32_t spiClk; + + // Find nearest clock frequency, round down + for(spiDiv = 0; spiDiv < 7; spiDiv += 1) + { + spiClk = busClk / (1 << (spiDiv + 1)); + if(spiClk <= speed) + break; + } + + if(spiClk > speed) + return -EINVAL; + + spi->I2SCFGR = 0; + spi->CR2 = 0; + spi->CFG1 = (spiDiv << SPI_CFG1_MBR_Pos) // Baud rate + | SPI_CFG1_DSIZE_2 // SPI frame size 8-bit + | SPI_CFG1_DSIZE_1 + | SPI_CFG1_DSIZE_0; + + spi->CR1 = SPI_CR1_SSI; // Force nCS state to active + spi->CFG2 = SPI_CFG2_AFCNTR // Peripheral keeps control of gpio AF mode + | SPI_CFG2_SSM // Software management of nCS + | SPI_CFG2_MASTER; + + if((flags & SPI_FLAG_CPOL) != 0) + spi->CFG2 |= SPI_CFG2_CPOL; + + if((flags & SPI_FLAG_CPHA) != 0) + spi->CFG2 |= SPI_CFG2_CPHA; + + if((flags & SPI_LSB_FIRST) != 0) + spi->CFG2 |= SPI_CFG2_LSBFRST; + + if(dev->mutex != NULL) + pthread_mutex_init((pthread_mutex_t *) dev->mutex, NULL); + + return 0; +} + +void spiStm32_terminate(const struct spiDevice *dev) +{ + SPI_TypeDef *spi = (SPI_TypeDef *) dev->priv; + + switch((uint32_t) spi) + { + case SPI1_BASE: + RCC->APB2ENR &= ~RCC_APB2ENR_SPI1EN; + __DSB(); + break; + + case SPI2_BASE: + RCC->APB1LENR &= ~RCC_APB1LENR_SPI2EN; + __DSB(); + break; + + case SPI3_BASE: + RCC->APB1LENR &= ~RCC_APB1LENR_SPI3EN; + __DSB(); + break; + + case SPI4_BASE: + RCC->APB2ENR &= ~RCC_APB2ENR_SPI4EN; + __DSB(); + break; + + case SPI5_BASE: + RCC->APB2ENR &= ~RCC_APB2ENR_SPI5EN; + __DSB(); + break; + + case SPI6_BASE: + RCC->APB4ENR &= ~RCC_APB4ENR_SPI6EN; + __DSB(); + break; + + default: + break; + } + + if(dev->mutex != NULL) + pthread_mutex_destroy((pthread_mutex_t *) dev->mutex); +} + +int spiStm32_transfer(const struct spiDevice *dev, const void *txBuf, + void *rxBuf, const size_t size) +{ + SPI_TypeDef *spi = (SPI_TypeDef *) dev->priv; + uint8_t *rxData = (uint8_t *) rxBuf; + const uint8_t *txData = (const uint8_t *) txBuf; + + spi->CR1 |= SPI_CR1_SPE; + spi->CR1 |= SPI_CR1_CSTART; + + // Send only + if(rxBuf == NULL) + { + for(size_t i = 0; i < size; i++) + spi_sendRecv(spi, txData[i]); + + return 0; + } + + // Receive only + if(txBuf == NULL) + { + for(size_t i = 0; i < size; i++) + rxData[i] = spi_sendRecv(spi, 0x00); + + return 0; + } + + // Transmit and receive + for(size_t i = 0; i < size; i++) + rxData[i] = spi_sendRecv(spi, txData[i]); + + spi->CR1 &= ~SPI_CR1_SPE; + + return 0; +} diff --git a/platform/mcu/STM32H7xx/drivers/pll.cpp b/platform/mcu/STM32H7xx/drivers/pll.cpp index 5d4311f7..c3f16231 100644 --- a/platform/mcu/STM32H7xx/drivers/pll.cpp +++ b/platform/mcu/STM32H7xx/drivers/pll.cpp @@ -97,3 +97,12 @@ void startPll() RCC->CR |= RCC_CR_PLL2ON; // Start PLL2 while((RCC->CR & RCC_CR_PLL2RDY)==0) ; // Wait until ready } + +uint32_t getBusClock(const uint8_t bus) +{ + if(bus >= PERIPH_BUS_NUM) + return 0; + + // All busses run at 200MHz + return 200000000; +} diff --git a/platform/mcu/STM32H7xx/drivers/pll.h b/platform/mcu/STM32H7xx/drivers/pll.h index 80a91ee9..c9a235df 100644 --- a/platform/mcu/STM32H7xx/drivers/pll.h +++ b/platform/mcu/STM32H7xx/drivers/pll.h @@ -29,6 +29,40 @@ #ifndef PLL_H #define PLL_H +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Enumeration type for STM32 internal busses. + */ +enum PeriphBus +{ + PERIPH_BUS_AHB = 0, + PERIPH_BUS_APB1 = 1, + PERIPH_BUS_APB2 = 2, + PERIPH_BUS_APB4 = 3, + + PERIPH_BUS_NUM +}; + +/** + * Configure and start the PLL. + */ void startPll(); +/** + * Get the clock frequency of a given peripheral bus. + * + * @param bus: bus identifier. + * @return bus clock frequency in Hz or zero in case of errors. + */ +uint32_t getBusClock(const uint8_t bus); + +#ifdef __cplusplus +} +#endif + #endif //PLL_H