aboutsummaryrefslogtreecommitdiffstats
path: root/libmaple/spi.c
diff options
context:
space:
mode:
Diffstat (limited to 'libmaple/spi.c')
-rw-r--r--libmaple/spi.c333
1 files changed, 211 insertions, 122 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)
+ */