diff options
Diffstat (limited to 'libmaple')
-rw-r--r-- | libmaple/spi.c | 333 | ||||
-rw-r--r-- | libmaple/spi.h | 495 |
2 files changed, 629 insertions, 199 deletions
diff --git a/libmaple/spi.c b/libmaple/spi.c index 7bdc18a..2fbc2ac 100644 --- a/libmaple/spi.c +++ b/libmaple/spi.c @@ -3,161 +3,250 @@ * * Copyright (c) 2010 Perry Hung. * - * 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 + * 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 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. + * 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. *****************************************************************************/ /** - * @brief libmaple serial peripheral interface (SPI) definitions - * - * Initial implementation for the SPI interface. - * - * This driver implements only the bare essentials of a polling driver at the - * moment. Master mode only, 8-bit data frames, and polling. - * - * The caller is responsible for controlling the chip select line. - * - * TODO: interrupt based driver, DMA. - * + * @file spi.c + * @author Marti Bolivar <mbolivar@leaflabs.com> + * @brief Serial Peripheral Interface (SPI) support. + * Currently, there is no Integrated Interchip Sound (I2S) support. */ -#include "libmaple.h" -#include "gpio.h" -#include "rcc.h" #include "spi.h" +#include "bitband.h" -typedef struct spi_dev { - SPI *base; - gpio_dev *gpio_d; - uint8 sck_pin; - uint8 miso_pin; - uint8 mosi_pin; -} spi_dev; - -spi_dev spi_dev1 = { - .base = (SPI*)SPI1_BASE, - .gpio_d = NULL, - .sck_pin = 5, - .miso_pin = 6, - .mosi_pin = 7 -}; +/* + * SPI devices + */ -spi_dev spi_dev2 = { - .base = (SPI*)SPI2_BASE, - .gpio_d = NULL, - .sck_pin = 13, - .miso_pin = 14, - .mosi_pin = 15 +static spi_dev spi1 = { + .regs = SPI1_BASE, + .clk_id = RCC_SPI1, + .irq_num = NVIC_SPI1, }; +spi_dev *SPI1 = &spi1; -static void spi_gpio_cfg(const spi_dev *dev); +static spi_dev spi2 = { + .regs = SPI2_BASE, + .clk_id = RCC_SPI2, + .irq_num = NVIC_SPI2, +}; +spi_dev *SPI2 = &spi2; -/** - * @brief Initialize a spi peripheral - * @param spi_num which spi to turn on, SPI1 or SPI2? - * @param prescale prescale factor on the input clock. - * @param endian data frame format (LSBFIRST?) - * @param mode SPI mode number - */ -void spi_init(uint32 spi_num, - uint32 prescale, - uint32 endian, - uint32 mode) { - ASSERT(spi_num == 1 || spi_num == 2); - ASSERT(mode < 4); - - SPI *spi; - uint32 cr1 = 0; - - switch (spi_num) { - case 1: - /* limit to 18 mhz max speed */ - ASSERT(prescale != CR1_BR_PRESCALE_2); - spi = (SPI*)SPI1_BASE; - rcc_clk_enable(RCC_SPI1); - spi_dev1.gpio_d = GPIOA; - spi_gpio_cfg(&spi_dev1); - break; - case 2: - spi = (SPI*)SPI2_BASE; - rcc_clk_enable(RCC_SPI2); - spi_dev1.gpio_d = GPIOB, - spi_gpio_cfg(&spi_dev2); - break; - } +#ifdef STM32_HIGH_DENSITY +static spi_dev spi3 = { + .regs = SPI3_BASE, + .clk_id = RCC_SPI3, + .irq_num = NVIC_SPI3, +}; +spi_dev *SPI3 = &spi3; +#endif - cr1 = prescale | endian | mode | CR1_MSTR | CR1_SSI | CR1_SSM; - spi->CR1 = cr1; +/* + * SPI convenience routines + */ - /* Peripheral enable */ - spi->CR1 |= CR1_SPE; +/** + * @brief Initialize and reset a SPI device. + * @param dev Device to initialize and reset. + */ +void spi_init(spi_dev *dev) { + rcc_clk_enable(dev->clk_id); + rcc_reset_dev(dev->clk_id); } /** - * @brief SPI synchronous 8-bit write, blocking. - * @param spi_num which spi to send on - * @return data shifted back from the slave + * @brief Configure GPIO bit modes for use as a SPI bus master's pins. + * @param nss_dev NSS pin's GPIO device + * @param comm_dev SCK, MISO, MOSI pins' GPIO device + * @param nss_bit NSS pin's GPIO bit on nss_dev + * @param sck_bit SCK pin's GPIO bit on comm_dev + * @param miso_bit MISO pin's GPIO bit on comm_dev + * @param mosi_bit MOSI pin's GPIO bit on comm_dev */ -uint8 spi_tx_byte(uint32 spi_num, uint8 data) { - SPI *spi; +void spi_master_gpio_cfg(gpio_dev *nss_dev, + gpio_dev *comm_dev, + uint8 nss_bit, + uint8 sck_bit, + uint8 miso_bit, + uint8 mosi_bit) { + gpio_set_mode(nss_dev, nss_bit, GPIO_AF_OUTPUT_PP); + gpio_set_mode(comm_dev, sck_bit, GPIO_AF_OUTPUT_PP); + gpio_set_mode(comm_dev, miso_bit, GPIO_INPUT_FLOATING); + gpio_set_mode(comm_dev, mosi_bit, GPIO_AF_OUTPUT_PP); +} - spi = (spi_num == 1) ? (SPI*)SPI1_BASE : (SPI*)SPI2_BASE; +/** + * @brief Configure GPIO bit modes for use as a SPI bus slave's pins. + * @param nss_dev NSS pin's GPIO device + * @param comm_dev SCK, MISO, MOSI pins' GPIO device + * @param nss_bit NSS pin's GPIO bit on nss_dev + * @param sck_bit SCK pin's GPIO bit on comm_dev + * @param miso_bit MISO pin's GPIO bit on comm_dev + * @param mosi_bit MOSI pin's GPIO bit on comm_dev + */ +void spi_slave_gpio_cfg(gpio_dev *nss_dev, + gpio_dev *comm_dev, + uint8 nss_bit, + uint8 sck_bit, + uint8 miso_bit, + uint8 mosi_bit) { + gpio_set_mode(nss_dev, nss_bit, GPIO_INPUT_FLOATING); + gpio_set_mode(comm_dev, sck_bit, GPIO_INPUT_FLOATING); + gpio_set_mode(comm_dev, miso_bit, GPIO_AF_OUTPUT_PP); + gpio_set_mode(comm_dev, mosi_bit, GPIO_INPUT_FLOATING); +} - while (!(spi->SR & SR_TXE)) - ; +static void spi_reconfigure(spi_dev *dev, uint32 cr1_config); - spi->DR = data; +/** + * @brief Configure and enable a SPI device as bus master. + * + * The device's peripheral will be disabled before being reconfigured. + * + * @param dev Device to configure as bus master + * @param baud Bus baud rate + * @param mode SPI mode + * @param flags Logical OR of spi_cfg_flag values. + * @see spi_cfg_flag + */ +void spi_master_enable(spi_dev *dev, + spi_baud_rate baud, + spi_mode mode, + uint32 flags) { + spi_reconfigure(dev, baud | flags | SPI_CR1_MSTR | mode); +} - while (!(spi->SR & SR_RXNE)) - ; +/** + * @brief Configure and enable a SPI device as a bus slave. + * + * The device's peripheral will be disabled before being reconfigured. + * + * @param dev Device to configure as a bus slave + * @param mode SPI mode + * @param flags Logical OR of spi_cfg_flag values. + * @see spi_cfg_flag + */ +void spi_slave_enable(spi_dev *dev, spi_mode mode, uint32 flags) { + spi_reconfigure(dev, flags | mode); +} - return spi->DR; +/** + * @brief Nonblocking SPI transmit. + * @param dev SPI port to use for transmission + * @param buf Buffer to transmit. The sizeof buf's elements are + * inferred from the dev's data frame format (i.e., are + * correctly treated as 8-bit or 16-bit quantities). + * @param len Maximum number of elements to transmit. + * @return Number of elements transmitted. + */ +uint32 spi_tx(spi_dev *dev, const void *buf, uint32 len) { + spi_reg_map *regs = dev->regs; + uint32 txed = 0; + if (spi_dff(dev) == SPI_DFF_16_BIT) { + const uint8 *buf8 = (const uint8*)buf; + while ((regs->SR & SPI_SR_TXE) && (txed < len)) { + regs->DR = buf8[txed++]; + } + } else { + const uint16 *buf16 = (const uint16*)buf; + while ((regs->SR & SPI_SR_TXE) && (txed < len)) { + regs->DR = buf16[txed++]; + } + } + return txed; } -uint8 spi_tx(uint32 spi_num, uint8 *buf, uint32 len) { - SPI *spi; - uint32 i = 0; - uint8 rc; +/** + * @brief Call a function on each SPI port + * @param fn Function to call. + */ +void spi_foreach(void (*fn)(spi_dev*)) { + fn(SPI1); + fn(SPI2); +#ifdef STM32_HIGH_DENSITY + fn(SPI3); +#endif +} - ASSERT(spi_num == 1 || spi_num == 2); - spi = (spi_num == 1) ? (SPI*)SPI1_BASE : (SPI*)SPI2_BASE; +/** + * @brief Enable a SPI peripheral + * @param dev Device to enable + */ +void spi_peripheral_enable(spi_dev *dev) { + bb_peri_set_bit(&dev->regs->CR1, SPI_CR1_SPE_BIT, 1); +} - if (!len) { - return 0; - } +/** + * @brief Disable a SPI peripheral + * @param dev Device to disable + */ +void spi_peripheral_disable(spi_dev *dev) { + bb_peri_set_bit(&dev->regs->CR1, SPI_CR1_SPE_BIT, 0); +} - while (i < len) { - while (!(spi->SR & SR_TXE)) - ; +/** + * @brief Enable DMA requests whenever the transmit buffer is empty + * @param dev SPI device on which to enable TX DMA requests + */ +void spi_tx_dma_enable(spi_dev *dev) { + bb_peri_set_bit(&dev->regs->CR2, SPI_CR2_TXDMAEN_BIT, 1); +} - spi->DR = buf[i]; +/** + * @brief Disable DMA requests whenever the transmit buffer is empty + * @param dev SPI device on which to disable TX DMA requests + */ +void spi_tx_dma_disable(spi_dev *dev) { + bb_peri_set_bit(&dev->regs->CR2, SPI_CR2_TXDMAEN_BIT, 0); +} - while (!(spi->SR & SR_RXNE)) - ; +/** + * @brief Enable DMA requests whenever the receive buffer is empty + * @param dev SPI device on which to enable RX DMA requests + */ +void spi_rx_dma_enable(spi_dev *dev) { + bb_peri_set_bit(&dev->regs->CR2, SPI_CR2_RXDMAEN_BIT, 1); +} - rc = spi->DR; - i++; - } - return rc; +/** + * @brief Disable DMA requests whenever the receive buffer is empty + * @param dev SPI device on which to disable RX DMA requests + */ +void spi_rx_dma_disable(spi_dev *dev) { + bb_peri_set_bit(&dev->regs->CR2, SPI_CR2_RXDMAEN_BIT, 0); } -static void spi_gpio_cfg(const spi_dev *dev) { - gpio_set_mode(dev->gpio_d, dev->sck_pin, GPIO_AF_OUTPUT_PP); - gpio_set_mode(dev->gpio_d, dev->miso_pin, GPIO_AF_OUTPUT_PP); - gpio_set_mode(dev->gpio_d, dev->mosi_pin, GPIO_AF_OUTPUT_PP); +/* + * SPI auxiliary routines + */ + +static void spi_reconfigure(spi_dev *dev, uint32 cr1_config) { + spi_irq_disable(dev, SPI_INTERRUPTS_ALL); + spi_peripheral_disable(dev); + dev->regs->CR1 = cr1_config; + spi_peripheral_enable(dev); } + +/* + * IRQ handlers (TODO) + */ diff --git a/libmaple/spi.h b/libmaple/spi.h index 5ebf52d..e2820dd 100644 --- a/libmaple/spi.h +++ b/libmaple/spi.h @@ -24,99 +24,440 @@ /** * @file spi.h - * @brief libmaple serial peripheral interface (SPI) prototypes and - * declarations + * @author Marti Bolivar <mbolivar@leaflabs.com> + * @brief Serial Peripheral Interface (SPI) and Integrated + * Interchip Sound (I2S) peripheral support. + * + * I2S support is currently limited to register maps and bit definitions. */ #ifndef _SPI_H_ #define _SPI_H_ #include "libmaple_types.h" +#include "rcc.h" +#include "nvic.h" +#include "gpio.h" #include "util.h" #ifdef __cplusplus extern "C" { #endif -/* peripheral addresses */ -#define SPI1_BASE 0x40013000 -#define SPI2_BASE 0x40003800 -#define SPI3_BASE 0x40003C00 - -/* baud rate prescaler bits */ -#define CR1_BR 0x00000038 -#define CR1_BR_PRESCALE_2 0x00000000 -#define CR1_BR_PRESCALE_4 0x00000008 -#define CR1_BR_PRESCALE_8 0x00000010 -#define CR1_BR_PRESCALE_16 0x00000018 -#define CR1_BR_PRESCALE_32 0x00000020 -#define CR1_BR_PRESCALE_64 0x00000028 -#define CR1_BR_PRESCALE_128 0x00000030 -#define CR1_BR_PRESCALE_256 0x00000038 - -#define CR1_LSBFIRST BIT(7) // data frame format -#define CR1_MSTR BIT(2) // master selection -#define CR1_SSM BIT(9) // software slave management -#define CR1_SSI BIT(8) // internal slave select -#define CR1_SPE BIT(6) // peripheral enable - -/* Status register bits */ -#define SR_RXNE BIT(0) // rx buffer not empty -#define SR_TXE BIT(1) // transmit buffer empty -#define SR_BSY BIT(7) // busy flag - -typedef struct SPI { - __io uint16 CR1; - uint16 pad0; - __io uint8 CR2; - uint8 pad1[3]; - __io uint8 SR; - uint8 pad2[3]; - __io uint16 DR; - uint16 pad3; - __io uint16 CRCPR; - uint16 pad4; - __io uint16 RXCRCR; - uint16 pad5; - __io uint16 TXCRCR; - uint16 pad6; -} SPI; - -enum { - SPI_MSBFIRST = 0, - SPI_LSBFIRST = BIT(7), -}; - -enum { - SPI_PRESCALE_2 = (0x0 << 3), - SPI_PRESCALE_4 = (0x1 << 3), - SPI_PRESCALE_8 = (0x2 << 3), - SPI_PRESCALE_16 = (0x3 << 3), - SPI_PRESCALE_32 = (0x4 << 3), - SPI_PRESCALE_64 = (0x5 << 3), - SPI_PRESCALE_128 = (0x6 << 3), - SPI_PRESCALE_256 = (0x7 << 3) -}; - -void spi_init(uint32 spi_num, - uint32 prescale, - uint32 endian, - uint32 mode); -uint8 spi_tx_byte(uint32 spi_num, uint8 data); -uint8 spi_tx(uint32 spi_num, uint8 *buf, uint32 len); - -static inline uint8 spi_rx(uint32 spi_num) { - SPI *spi; - - ASSERT(spi_num == 1 || spi_num == 2); - spi = (spi_num == 1) ? (SPI*)SPI1_BASE : (SPI*)SPI2_BASE; - - return spi->DR; +/* + * Register maps + */ + +/** SPI register map type. */ +typedef struct spi_reg_map { + __io uint32 CR1; /**< Control register 1 */ + __io uint32 CR2; /**< Control register 2 */ + __io uint32 SR; /**< Status register */ + __io uint32 DR; /**< Data register */ + __io uint32 CRCPR; /**< CRC polynomial register */ + __io uint32 RXCRCR; /**< RX CRC register */ + __io uint32 TXCRCR; /**< TX CRC register */ + __io uint32 I2SCFGR; /**< I2S configuration register */ + __io uint32 I2SPR; /**< I2S prescaler register */ +} spi_reg_map; + +/** SPI1 register map base pointer */ +#define SPI1_BASE ((struct spi_reg_map*)0x40013000) +/** SPI2 register map base pointer */ +#define SPI2_BASE ((struct spi_reg_map*)0x40003800) +/** SPI3 register map base pointer */ +#define SPI3_BASE ((struct spi_reg_map*)0x40003C00) + +/* + * Register bit definitions + */ + +/* Control register 1 */ + +#define SPI_CR1_BIDIMODE_BIT 15 +#define SPI_CR1_BIDIOE_BIT 14 +#define SPI_CR1_CRCEN_BIT 13 +#define SPI_CR1_CRCNEXT_BIT 12 +#define SPI_CR1_DFF_BIT 11 +#define SPI_CR1_RXONLY_BIT 10 +#define SPI_CR1_SSM_BIT 9 +#define SPI_CR1_SSI_BIT 8 +#define SPI_CR1_LSBFIRST_BIT 7 +#define SPI_CR1_SPE_BIT 6 +#define SPI_CR1_MSTR_BIT 2 +#define SPI_CR1_CPOL_BIT 1 +#define SPI_CR1_CPHA_BIT 0 + +#define SPI_CR1_BIDIMODE BIT(SPI_CR1_BIDIMODE_BIT) +#define SPI_CR1_BIDIMODE_2_LINE (0x0 << SPI_CR1_BIDIMODE_BIT) +#define SPI_CR1_BIDIMODE_1_LINE (0x1 << SPI_CR1_BIDIMODE_BIT) +#define SPI_CR1_BIDIOE BIT(SPI_CR1_BIDIOE_BIT) +#define SPI_CR1_CRCEN BIT(SPI_CR1_CRCEN_BIT) +#define SPI_CR1_CRCNEXT BIT(SPI_CR1_CRCNEXT_BIT) +#define SPI_CR1_DFF BIT(SPI_CR1_DFF_BIT) +#define SPI_CR1_DFF_8_BIT (0x0 << SPI_CR1_DFF_BIT) +#define SPI_CR1_DFF_16_BIT (0x1 << SPI_CR1_DFF_BIT) +#define SPI_CR1_RXONLY BIT(SPI_CR1_RXONLY_BIT) +#define SPI_CR1_SSM BIT(SPI_CR1_SSM_BIT) +#define SPI_CR1_SSI BIT(SPI_CR1_SSI_BIT) +#define SPI_CR1_LSBFIRST BIT(SPI_CR1_LSBFIRST_BIT) +#define SPI_CR1_SPE BIT(SPI_CR1_SPE_BIT) +#define SPI_CR1_BR (0x7 << 3) +#define SPI_CR1_BR_PCLK_DIV_2 (0x0 << 3) +#define SPI_CR1_BR_PCLK_DIV_4 (0x1 << 3) +#define SPI_CR1_BR_PCLK_DIV_8 (0x2 << 3) +#define SPI_CR1_BR_PCLK_DIV_16 (0x3 << 3) +#define SPI_CR1_BR_PCLK_DIV_32 (0x4 << 3) +#define SPI_CR1_BR_PCLK_DIV_64 (0x5 << 3) +#define SPI_CR1_BR_PCLK_DIV_128 (0x6 << 3) +#define SPI_CR1_BR_PCLK_DIV_256 (0x7 << 3) +#define SPI_CR1_MSTR BIT(SPI_CR1_MSTR_BIT) +#define SPI_CR1_CPOL BIT(SPI_CR1_CPOL_BIT) +#define SPI_CR1_CPOL_LOW (0x0 << SPI_CR1_CPOL_BIT) +#define SPI_CR1_CPOL_HIGH (0x1 << SPI_CR1_CPOL_BIT) +#define SPI_CR1_CPHA BIT(SPI_CR1_CPHA_BIT) + +/* Control register 2 */ + +/* RM0008-ism: SPI CR2 has "TXDMAEN" and "RXDMAEN" bits, while the + * USARTs have CR3 "DMAR" and "DMAT" bits. */ + +#define SPI_CR2_TXEIE_BIT 7 +#define SPI_CR2_RXNEIE_BIT 6 +#define SPI_CR2_ERRIE_BIT 5 +#define SPI_CR2_SSOE_BIT 2 +#define SPI_CR2_TXDMAEN_BIT 1 +#define SPI_CR2_RXDMAEN_BIT 0 + +#define SPI_CR2_TXEIE BIT(SPI_CR2_TXEIE_BIT) +#define SPI_CR2_RXNEIE BIT(SPI_CR2_RXNEIE_BIT) +#define SPI_CR2_ERRIE BIT(SPI_CR2_ERRIE_BIT) +#define SPI_CR2_SSOE BIT(SPI_CR2_SSOE_BIT) +#define SPI_CR2_TXDMAEN BIT(SPI_CR2_TXDMAEN_BIT) +#define SPI_CR2_RXDMAEN BIT(SPI_CR2_RXDMAEN_BIT) + +/* Status register */ + +#define SPI_SR_BSY_BIT 7 +#define SPI_SR_OVR_BIT 6 +#define SPI_SR_MODF_BIT 5 +#define SPI_SR_CRCERR_BIT 4 +#define SPI_SR_UDR_BIT 3 +#define SPI_SR_CHSIDE_BIT 2 +#define SPI_SR_TXE_BIT 1 +#define SPI_SR_RXNE_BIT 0 + +#define SPI_SR_BSY BIT(SPI_SR_BSY_BIT) +#define SPI_SR_OVR BIT(SPI_SR_OVR_BIT) +#define SPI_SR_MODF BIT(SPI_SR_MODF_BIT) +#define SPI_SR_CRCERR BIT(SPI_SR_CRCERR_BIT) +#define SPI_SR_UDR BIT(SPI_SR_UDR_BIT) +#define SPI_SR_CHSIDE BIT(SPI_SR_CHSIDE_BIT) +#define SPI_SR_CHSIDE_LEFT (0x0 << SPI_SR_CHSIDE_BIT) +#define SPI_SR_CHSIDE_RIGHT (0x1 << SPI_SR_CHSIDE_BIT) +#define SPI_SR_TXE BIT(SPI_SR_TXE_BIT) +#define SPI_SR_RXNE BIT(SPI_SR_RXNE_BIT) + +/* I2S configuration register */ + +/* RM0008-ism: CR1 has "CPOL", I2SCFGR has "CKPOL". */ + +#define SPI_I2SCFGR_I2SMOD_BIT 11 +#define SPI_I2SCFGR_I2SE_BIT 10 +#define SPI_I2SCFGR_PCMSYNC_BIT 7 +#define SPI_I2SCFGR_CKPOL_BIT 3 +#define SPI_I2SCFGR_CHLEN_BIT 0 + +#define SPI_I2SCFGR_I2SMOD BIT(SPI_I2SCFGR_I2SMOD_BIT) +#define SPI_I2SCFGR_I2SMOD_SPI (0x0 << SPI_I2SCFGR_I2SMOD_BIT) +#define SPI_I2SCFGR_I2SMOD_I2S (0x1 << SPI_I2SCFGR_I2SMOD_BIT) +#define SPI_I2SCFGR_I2SE BIT(SPI_I2SCFGR_I2SE_BIT) +#define SPI_I2SCFGR_I2SCFG (0x3 << 8) +#define SPI_I2SCFGR_I2SCFG_SLAVE_TX (0x0 << 8) +#define SPI_I2SCFGR_I2SCFG_SLAVE_RX (0x1 << 8) +#define SPI_I2SCFGR_I2SCFG_MASTER_TX (0x2 << 8) +#define SPI_I2SCFGR_I2SCFG_MASTER_RX (0x3 << 8) +#define SPI_I2SCFGR_PCMSYNC BIT(SPI_I2SCFGR_PCMSYNC_BIT) +#define SPI_I2SCFGR_PCMSYNC_SHORT (0x0 << SPI_I2SCFGR_PCMSYNC_BIT) +#define SPI_I2SCFGR_PCMSYNC_LONG (0x1 << SPI_I2SCFGR_PCMSYNC_BIT) +#define SPI_I2SCFGR_I2SSTD (0x3 << 4) +#define SPI_I2SCFGR_I2SSTD_PHILLIPS (0x0 << 4) +#define SPI_I2SCFGR_I2SSTD_MSB (0x1 << 4) +#define SPI_I2SCFGR_I2SSTD_LSB (0x2 << 4) +#define SPI_I2SCFGR_I2SSTD_PCM (0x3 << 4) +#define SPI_I2SCFGR_CKPOL BIT(SPI_I2SCFGR_CKPOL_BIT) +#define SPI_I2SCFGR_CKPOL_LOW (0x0 << SPI_I2SCFGR_CKPOL_BIT) +#define SPI_I2SCFGR_CKPOL_HIGH (0x1 << SPI_I2SCFGR_CKPOL_BIT) +#define SPI_I2SCFGR_DATLEN (0x3 << 1) +#define SPI_I2SCFGR_DATLEN_16_BIT (0x0 << 1) +#define SPI_I2SCFGR_DATLEN_24_BIT (0x1 << 1) +#define SPI_I2SCFGR_DATLEN_32_BIT (0x2 << 1) +#define SPI_I2SCFGR_CHLEN BIT(SPI_I2SCFGR_CHLEN_BIT) +#define SPI_I2SCFGR_CHLEN_16_BIT (0x0 << SPI_I2SCFGR_CHLEN_BIT) +#define SPI_I2SCFGR_CHLEN_32_BIT (0x1 << SPI_I2SCFGR_CHLEN_BIT) + +/* + * Devices + */ + +/** SPI device type */ +typedef struct spi_dev { + spi_reg_map *regs; + rcc_clk_id clk_id; + nvic_irq_num irq_num; +} spi_dev; + +/** SPI device 1 */ +extern spi_dev *SPI1; +/** SPI device 2 */ +extern spi_dev *SPI2; +#ifdef STM32_HIGH_DENSITY +/** SPI device 3 */ +extern spi_dev *SPI3; +#endif + +/* + * SPI Convenience functions + */ + +void spi_init(spi_dev *dev); + +void spi_master_gpio_cfg(gpio_dev *nss_dev, + gpio_dev *comm_dev, + uint8 nss_bit, + uint8 sck_bit, + uint8 miso_bit, + uint8 mosi_bit); + +void spi_slave_gpio_cfg(gpio_dev *nss_dev, + gpio_dev *comm_dev, + uint8 nss_bit, + uint8 sck_bit, + uint8 miso_bit, + uint8 mosi_bit); + +/** + * @brief SPI mode configuration. + * + * Determines a combination of clock polarity (CPOL), which determines + * idle state of the clock line, and clock phase (CPHA), which + * determines which clock edge triggers data capture. + */ +typedef enum { + SPI_MODE_0, /**< Clock line idles low (0), data capture on first + clock transition. */ + SPI_MODE_1, /**< Clock line idles low (0), data capture on second + clock transition */ + SPI_MODE_2, /**< Clock line idles high (1), data capture on first + clock transition. */ + SPI_MODE_3 /**< Clock line idles high (1), data capture on + second clock transition. */ +} spi_mode; + +/** + * @brief SPI baud rate configuration, as a divisor of f_PCLK, the + * PCLK clock frequency. + */ +typedef enum { + SPI_BAUD_PCLK_DIV_2 = SPI_CR1_BR_PCLK_DIV_2, /**< f_PCLK/2 */ + SPI_BAUD_PCLK_DIV_4 = SPI_CR1_BR_PCLK_DIV_4, /**< f_PCLK/4 */ + SPI_BAUD_PCLK_DIV_8 = SPI_CR1_BR_PCLK_DIV_8, /**< f_PCLK/8 */ + SPI_BAUD_PCLK_DIV_16 = SPI_CR1_BR_PCLK_DIV_16, /**< f_PCLK/16 */ + SPI_BAUD_PCLK_DIV_32 = SPI_CR1_BR_PCLK_DIV_32, /**< f_PCLK/32 */ + SPI_BAUD_PCLK_DIV_64 = SPI_CR1_BR_PCLK_DIV_64, /**< f_PCLK/64 */ + SPI_BAUD_PCLK_DIV_128 = SPI_CR1_BR_PCLK_DIV_128, /**< f_PCLK/128 */ + SPI_BAUD_PCLK_DIV_256 = SPI_CR1_BR_PCLK_DIV_256, /**< f_PCLK/256 */ +} spi_baud_rate; + +/** + * @brief SPI initialization flags. + * @see spi_master_enable() + * @see spi_slave_enable() + */ +typedef enum { + SPI_BIDIMODE = SPI_CR1_BIDIMODE, /**< Bidirectional mode enable */ + SPI_BIDIOE = SPI_CR1_BIDIOE, /**< Output enable in bidirectional + mode */ + SPI_CRCEN = SPI_CR1_CRCEN, /**< Cyclic redundancy check (CRC) + enable */ + SPI_DFF_8_BIT = SPI_CR1_DFF_8_BIT, /**< 8-bit data frame format (this is + the default) */ + SPI_DFF_16_BIT = SPI_CR1_DFF_16_BIT, /**< 16-bit data frame format */ + SPI_RX_ONLY = SPI_CR1_RXONLY, /**< Receive only */ + SPI_SW_SLAVE = SPI_CR1_SSM, /**< Software slave management */ + SPI_SOFT_SS = SPI_CR1_SSI, /**< Software (internal) slave + select. This flag only has an + effect when used in combination + with SPI_SW_SLAVE. */ + SPI_FRAME_LSB = SPI_CR1_LSBFIRST, /**< LSB-first (little-endian) frame + format */ + SPI_FRAME_MSB = 0, /**< MSB-first (big-endian) frame + format (this is the default) */ +} spi_cfg_flag; + +void spi_master_enable(spi_dev *dev, + spi_baud_rate baud, + spi_mode mode, + uint32 flags); + +void spi_slave_enable(spi_dev *dev, + spi_mode mode, + uint32 flags); + +uint32 spi_tx(spi_dev *dev, const void *buf, uint32 len); + +void spi_foreach(void (*fn)(spi_dev (*dev))); + +void spi_peripheral_enable(spi_dev *dev); +void spi_peripheral_disable(spi_dev *dev); + +void spi_tx_dma_enable(spi_dev *dev); +void spi_tx_dma_disable(spi_dev *dev); + +void spi_rx_dma_enable(spi_dev *dev); +void spi_rx_dma_disable(spi_dev *dev); + +/** + * @brief Determine if a SPI peripheral is enabled. + * @parm dev SPI device + * @return True, if and only if dev's peripheral is enabled. + */ +static inline uint8 spi_is_enabled(spi_dev *dev) { + return dev->regs->CR1 & SPI_CR1_SPE_BIT; +} + +/** + * @brief Disable all SPI peripherals + */ +static inline void spi_peripheral_disable_all(void) { + spi_foreach(spi_peripheral_disable); +} + +/** Available SPI interrupts */ +typedef enum { + SPI_TXE_INTERRUPT = SPI_CR2_TXEIE, /**< TX buffer empty interrupt */ + SPI_RXNE_INTERRUPT = SPI_CR2_RXNEIE, /**< RX buffer not empty interrupt */ + SPI_ERR_INTERRUPT = SPI_CR2_ERRIE /**< + * Error interrupt (CRC, overrun, + * and mode fault errors for SPI; + * underrun, overrun errors for I2S) + */ +} spi_interrupt; + +/** + * @brief Mask for all spi_interrupt values + * @see spi_interrupt + */ +#define SPI_INTERRUPTS_ALL (SPI_TXE_INTERRUPT | \ + SPI_RXNE_INTERRUPT | \ + SPI_ERR_INTERRUPT) + +/** + * @brief Enable SPI interrupt requests + * @param dev SPI device + * @param interrupt_flags Bitwise OR of spi_interrupt values to enable + * @see spi_interrupt + */ +static inline void spi_irq_enable(spi_dev *dev, uint32 interrupt_flags) { + dev->regs->CR2 |= interrupt_flags; + nvic_irq_enable(dev->irq_num); +} + +/** + * @brief Disable SPI interrupt requests + * @param dev SPI device + * @param interrupt_flags Bitwise OR of spi_interrupt values to disable + * @see spi_interrupt + */ +static inline void spi_irq_disable(spi_dev *dev, uint32 interrupt_flags) { + dev->regs->CR2 &= ~interrupt_flags; +} + +/** + * @brief Get the data frame format flags with which a SPI port is + * configured. + * @param dev SPI device whose data frame format to get. + * @return SPI_DFF_8_BIT, if dev has an 8-bit data frame format. + * Otherwise, SPI_DFF_16_BIT. + */ +static inline spi_cfg_flag spi_dff(spi_dev *dev) { + return ((dev->regs->CR1 & SPI_CR1_DFF) == SPI_CR1_DFF_8_BIT ? + SPI_DFF_8_BIT : + SPI_DFF_16_BIT); +} + +/** + * @brief Determine whether the device's peripheral receive (RX) + * register is empty. + * @param dev SPI device + * @return true, iff dev's RX register is empty. + */ +static inline uint8 spi_is_rx_nonempty(spi_dev *dev) { + return dev->regs->SR & SPI_SR_RXNE; +} + +/** + * @brief Retrieve the contents of the device's peripheral receive + * (RX) register. + * + * You may only call this function when the RX register is nonempty. + * Calling this function clears the contents of the RX register. + * + * @param dev SPI device + * @return Contents of dev's peripheral RX register + * @see spi_is_rx_reg_nonempty() + */ +static inline uint16 spi_rx_reg(spi_dev *dev) { + return (uint16)dev->regs->DR; +} + +/** + * @brief Determine whether the device's peripheral transmit (TX) + * register is empty. + * @param dev SPI device + * @return true, iff dev's TX register is empty. + */ +static inline uint8 spi_is_tx_empty(spi_dev *dev) { + return dev->regs->SR & SPI_SR_TXE; +} + +/** + * @brief Load a value into the device's peripheral transmit (TX) register. + * + * You may only call this function when the TX register is empty. + * Calling this function loads val into the peripheral's TX register. + * If the device is properly configured, this will initiate a + * transmission, the completion of which will cause the TX register to + * be empty again. + * + * @param dev SPI device + * @param val Value to load into the TX register. If the SPI data + * frame format is 8 bit, the value must be right-aligned. + * @see spi_is_tx_reg_empty() + * @see spi_init() + * @see spi_master_enable() + * @see spi_slave_enable() + */ +static inline void spi_tx_reg(spi_dev *dev, uint16 val) { + dev->regs->DR = val; +} + +/** + * @brief Determine whether the device's peripheral busy (SPI_SR_BSY) + * flag is set. + * @param dev SPI device + * @return true, iff dev's BSY flag is set. + */ +static inline uint8 spi_is_busy(spi_dev *dev) { + return dev->regs->SR & SPI_SR_BSY; } +/* + * I2S convenience functions (TODO) + */ + #ifdef __cplusplus } #endif #endif - |