diff options
Diffstat (limited to 'libmaple/spi.c')
| -rw-r--r-- | libmaple/spi.c | 318 | 
1 files changed, 197 insertions, 121 deletions
diff --git a/libmaple/spi.c b/libmaple/spi.c index 8bba0d6..e58ae91 100644 --- a/libmaple/spi.c +++ b/libmaple/spi.c @@ -3,159 +3,235 @@   *   * 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_Port *port; -    uint8 sck_pin; -    uint8 miso_pin; -    uint8 mosi_pin; -} spi_dev; - -static const spi_dev spi_dev1 = { -    .base     = (SPI*)SPI1_BASE, -    .port     = GPIOA_BASE, -    .sck_pin  = 5, -    .miso_pin = 6, -    .mosi_pin = 7 -}; +static void spi_reconfigure(spi_dev *dev, uint32 cr1_config); + +/* + * SPI devices + */ -static const spi_dev spi_dev2 = { -    .base     = (SPI*)SPI2_BASE, -    .port     = GPIOB_BASE, -    .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_gpio_cfg(&spi_dev1); -        break; -    case 2: -        spi = (SPI*)SPI2_BASE; -        rcc_clk_enable(RCC_SPI2); -        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 port's pins. + * @param as_master If true, configure bits for use as a bus master. + *                  Otherwise, configure bits for use as slave. + * @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_gpio_cfg(uint8 as_master, +                  gpio_dev *nss_dev, +                  uint8 nss_bit, +                  gpio_dev *comm_dev, +                  uint8 sck_bit, +                  uint8 miso_bit, +                  uint8 mosi_bit) { +    if (as_master) { +        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); +    } else { +        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); +    } +} -    spi = (spi_num == 1) ? (SPI*)SPI1_BASE : (SPI*)SPI2_BASE; +/** + * @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_TXE)) -        ; +/** + * @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); +} -    spi->DR = data; +/** + * @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 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) { +    uint32 txed = 0; +    uint8 byte_frame = spi_dff(dev) == SPI_DFF_8_BIT; +    while (spi_is_tx_empty(dev) && (txed < len)) { +        if (byte_frame) { +            dev->regs->DR = ((const uint8*)buf)[txed++]; +        } else { +            dev->regs->DR = ((const uint16*)buf)[txed++]; +        } +    } +    return txed; +} -    while (!(spi->SR & SR_RXNE)) -        ; +/** + * @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 +} -    return spi->DR; +/** + * @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);  } -uint8 spi_tx(uint32 spi_num, uint8 *buf, uint32 len) { -    SPI *spi; -    uint32 i = 0; -    uint8 rc; +/** + * @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); +} -    ASSERT(spi_num == 1 || spi_num == 2); -    spi = (spi_num == 1) ? (SPI*)SPI1_BASE : (SPI*)SPI2_BASE; +/** + * @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); +} -    if (!len) { -        return 0; -    } +/** + * @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 (i < len) { -        while (!(spi->SR & SR_TXE)) -            ; +/** + * @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); +} -        spi->DR = buf[i]; +/** + * @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); +} -        while (!(spi->SR & SR_RXNE)) -            ; +/* + * SPI auxiliary routines + */ -        rc = spi->DR; -        i++; -    } -    return rc; +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);  } -static void spi_gpio_cfg(const spi_dev *dev) { -    gpio_set_mode(dev->port, dev->sck_pin, GPIO_MODE_AF_OUTPUT_PP); -    gpio_set_mode(dev->port, dev->miso_pin, GPIO_MODE_AF_OUTPUT_PP); -    gpio_set_mode(dev->port, dev->mosi_pin, GPIO_MODE_AF_OUTPUT_PP); -} +/* + * IRQ handlers (TODO) + */  | 
