| /* |
| * Copyright 2014 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| * |
| * SPI controller driver. |
| */ |
| |
| #include "common.h" |
| #include "dma.h" |
| #include "gpio.h" |
| #include "hwtimer.h" |
| #include "shared_mem.h" |
| #include "spi.h" |
| #include "stm32-dma.h" |
| #include "task.h" |
| #include "timer.h" |
| #include "util.h" |
| |
| #if defined(CHIP_VARIANT_STM32F373) || defined(CHIP_FAMILY_STM32L4) || \ |
| defined(CHIP_FAMILY_STM32L5) || defined(CHIP_VARIANT_STM32F76X) |
| #define HAS_SPI3 |
| #else |
| #undef HAS_SPI3 |
| #endif |
| |
| /* The second (and third if available) SPI port are used as controller */ |
| static stm32_spi_regs_t *SPI_REGS[] = { |
| #ifdef CONFIG_STM32_SPI1_CONTROLLER |
| STM32_SPI1_REGS, |
| #endif |
| STM32_SPI2_REGS, |
| #ifdef HAS_SPI3 |
| STM32_SPI3_REGS, |
| #endif |
| }; |
| |
| /* DMA request mapping on channels */ |
| struct dma_req_t { |
| uint8_t tx_req; |
| uint8_t rx_req; |
| }; |
| #ifdef CHIP_FAMILY_STM32L4 |
| static struct dma_req_t dma_req[ARRAY_SIZE(SPI_REGS)] = { |
| #ifdef CONFIG_STM32_SPI1_CONTROLLER |
| /* SPI1 */ { 1, 1 }, |
| #endif |
| /* SPI2 */ { 1, 1 }, |
| /* SPI3 */ { 3, 3 }, |
| }; |
| #elif defined(CHIP_FAMILY_STM32L5) |
| static struct dma_req_t dma_req[ARRAY_SIZE(SPI_REGS)] = { |
| #ifdef CONFIG_STM32_SPI1_CONTROLLER |
| /* SPI1 */ { DMAMUX_REQ_SPI1_TX, DMAMUX_REQ_SPI1_RX }, |
| #endif |
| /* SPI2 */ { DMAMUX_REQ_SPI2_TX, DMAMUX_REQ_SPI2_RX }, |
| /* SPI3 */ { DMAMUX_REQ_SPI3_TX, DMAMUX_REQ_SPI3_RX }, |
| }; |
| #endif |
| |
| static mutex_t spi_mutex[ARRAY_SIZE(SPI_REGS)]; |
| |
| #define SPI_TRANSACTION_TIMEOUT_USEC (800 * MSEC) |
| |
| /* Default DMA channel options */ |
| #ifdef CHIP_FAMILY_STM32F4 |
| #define F4_CHANNEL(ch) STM32_DMA_CCR_CHANNEL(ch) |
| #else |
| #define F4_CHANNEL(ch) 0 |
| #endif |
| |
| static const struct dma_option dma_tx_option[] = { |
| #ifdef CONFIG_STM32_SPI1_CONTROLLER |
| { STM32_DMAC_SPI1_TX, (void *)&STM32_SPI1_REGS->dr, |
| STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT | |
| F4_CHANNEL(STM32_SPI1_TX_REQ_CH) }, |
| #endif |
| { STM32_DMAC_SPI2_TX, (void *)&STM32_SPI2_REGS->dr, |
| STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT | |
| F4_CHANNEL(STM32_SPI2_TX_REQ_CH) }, |
| #ifdef HAS_SPI3 |
| { STM32_DMAC_SPI3_TX, (void *)&STM32_SPI3_REGS->dr, |
| STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT | |
| F4_CHANNEL(STM32_SPI3_TX_REQ_CH) }, |
| #endif |
| }; |
| |
| static const struct dma_option dma_rx_option[] = { |
| #ifdef CONFIG_STM32_SPI1_CONTROLLER |
| { STM32_DMAC_SPI1_RX, (void *)&STM32_SPI1_REGS->dr, |
| STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT | |
| F4_CHANNEL(STM32_SPI1_RX_REQ_CH) }, |
| #endif |
| { STM32_DMAC_SPI2_RX, (void *)&STM32_SPI2_REGS->dr, |
| STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT | |
| F4_CHANNEL(STM32_SPI2_RX_REQ_CH) }, |
| #ifdef HAS_SPI3 |
| { STM32_DMAC_SPI3_RX, (void *)&STM32_SPI3_REGS->dr, |
| STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT | |
| F4_CHANNEL(STM32_SPI3_RX_REQ_CH) }, |
| #endif |
| }; |
| |
| static uint8_t spi_enabled[ARRAY_SIZE(SPI_REGS)]; |
| |
| static int spi_tx_done(stm32_spi_regs_t *spi) |
| { |
| return !(spi->sr & (STM32_SPI_SR_FTLVL | STM32_SPI_SR_BSY)); |
| } |
| |
| static int spi_rx_done(stm32_spi_regs_t *spi) |
| { |
| return !(spi->sr & (STM32_SPI_SR_FRLVL | STM32_SPI_SR_RXNE)); |
| } |
| |
| /* Read until RX FIFO is empty (i.e. RX done) */ |
| static int spi_clear_rx_fifo(stm32_spi_regs_t *spi) |
| { |
| uint8_t unused __attribute__((unused)); |
| uint32_t start = __hw_clock_source_read(), delta; |
| |
| while (!spi_rx_done(spi)) { |
| unused = spi->dr; /* Read one byte from FIFO */ |
| delta = __hw_clock_source_read() - start; |
| if (delta >= SPI_TRANSACTION_TIMEOUT_USEC) |
| return EC_ERROR_TIMEOUT; |
| } |
| return EC_SUCCESS; |
| } |
| |
| /* Wait until TX FIFO is empty (i.e. TX done) */ |
| static int spi_clear_tx_fifo(stm32_spi_regs_t *spi) |
| { |
| uint32_t start = __hw_clock_source_read(), delta; |
| |
| while (!spi_tx_done(spi)) { |
| /* wait for TX complete */ |
| delta = __hw_clock_source_read() - start; |
| if (delta >= SPI_TRANSACTION_TIMEOUT_USEC) |
| return EC_ERROR_TIMEOUT; |
| } |
| return EC_SUCCESS; |
| } |
| |
| /** |
| * Initialize SPI module, registers, and clocks |
| * |
| * - port: which port to initialize. |
| */ |
| static int spi_controller_initialize(const struct spi_device_t *spi_device) |
| { |
| int port = spi_device->port; |
| |
| stm32_spi_regs_t *spi = SPI_REGS[port]; |
| |
| /* |
| * Set SPI controller, baud rate, and software peripheral control. |
| * */ |
| |
| /* |
| * STM32F412 |
| * Section 26.3.5 Chip select (NSS) pin management and Figure 276 |
| * https://www.st.com/resource/en/reference_manual/dm00180369.pdf#page=817 |
| * |
| * The documentation in this section is a bit confusing, so here's a |
| * summary based on discussion with ST: |
| * |
| * Software NSS management (SSM = 1): |
| * - In controller mode, the NSS output is deactivated. You need to |
| * use a GPIO in output mode for chip select. This is generally used |
| * for multi-peripheral operation, but you can also use it for |
| * single peripheral operation. In this case, you should make sure |
| * to configure a GPIO for NSS, but *not* activate the SPI alternate |
| * function on that same pin since that will enable hardware NSS |
| * management (see below). |
| * - In peripheral mode, the NSS input level is equal to the SSI bit |
| * value. |
| * |
| * Hardware NSS management (SSM = 0): |
| * - In peripheral mode, when NSS pin is detected low the peripheral |
| * (MCU) is selected. |
| * - In controller mode, there are two configurations, depending on |
| * the SSOE bit in register SPIx_CR1. |
| * - NSS output enable (SSM=0, SSOE=1): |
| * The MCU (controller) drives NSS low as soon as SPI is enabled |
| * (SPE=1) and releases it when SPI is disabled (SPE=0). |
| * |
| * - NSS output disable (SSM=0, SSOE=0): |
| * Allows multi-controller capability. The MCU (controller) |
| * drives NSS low. If another controller tries to takes control |
| * of the bus and NSS is pulled low, a mode fault is generated |
| * and the MCU changes to peripheral mode. |
| * |
| * - NSS output disable (SSM=0, SSOE=0): if the MCU is acting as |
| * controller on the bus, this config allows multi-controller |
| * capability. If the NSS pin is pulled low in this mode, the SPI |
| * enters controller mode fault state and the device is |
| * automatically reconfigured in peripheral mode. In peripheral |
| * mode, the NSS pin works as a standard "chip select" input and the |
| * peripheral is selected while NSS lin is at low level. |
| */ |
| spi->cr1 = STM32_SPI_CR1_MSTR | STM32_SPI_CR1_SSM | STM32_SPI_CR1_SSI | |
| (spi_device->div << 3); |
| |
| #if defined(CHIP_FAMILY_STM32L4) || defined(CHIP_FAMILY_STM32L5) |
| dma_select_channel(dma_tx_option[port].channel, dma_req[port].tx_req); |
| dma_select_channel(dma_rx_option[port].channel, dma_req[port].rx_req); |
| #endif |
| /* |
| * Configure 8-bit datasize, set FRXTH, enable DMA, |
| * and set data size (applies to STM32F0 only). |
| * |
| * STM32F412: |
| * https://www.st.com/resource/en/reference_manual/dm00180369.pdf#page=852 |
| * |
| * |
| * STM32F0: |
| * https://www.st.com/resource/en/reference_manual/dm00031936.pdf#page=803 |
| */ |
| spi->cr2 = STM32_SPI_CR2_TXDMAEN | STM32_SPI_CR2_RXDMAEN | |
| STM32_SPI_CR2_FRXTH | STM32_SPI_CR2_DATASIZE(8); |
| |
| #ifdef CONFIG_SPI_HALFDUPLEX |
| spi->cr1 |= STM32_SPI_CR1_BIDIMODE | STM32_SPI_CR1_BIDIOE; |
| #endif |
| |
| /* Drive Chip Select high before turning on SPI module */ |
| gpio_set_level(spi_device->gpio_cs, 1); |
| |
| /* Enable SPI hardware module. This will actively drive the CLK pin */ |
| spi->cr1 |= STM32_SPI_CR1_SPE; |
| |
| /* Set flag */ |
| spi_enabled[port] = 1; |
| |
| return EC_SUCCESS; |
| } |
| |
| /** |
| * Shutdown SPI module |
| */ |
| static int spi_controller_shutdown(const struct spi_device_t *spi_device) |
| { |
| int rv = EC_SUCCESS; |
| int port = spi_device->port; |
| stm32_spi_regs_t *spi = SPI_REGS[port]; |
| |
| /* Set flag */ |
| spi_enabled[port] = 0; |
| |
| /* Disable DMA streams */ |
| dma_disable(dma_tx_option[port].channel); |
| dma_disable(dma_rx_option[port].channel); |
| |
| /* Disable SPI. Let the CLK pin float. */ |
| spi->cr1 &= ~STM32_SPI_CR1_SPE; |
| |
| spi_clear_rx_fifo(spi); |
| |
| /* Disable DMA buffers */ |
| spi->cr2 &= ~(STM32_SPI_CR2_TXDMAEN | STM32_SPI_CR2_RXDMAEN); |
| |
| return rv; |
| } |
| |
| int spi_enable(const struct spi_device_t *spi_device, int enable) |
| { |
| if (enable == spi_enabled[spi_device->port]) |
| return EC_SUCCESS; |
| if (enable) |
| return spi_controller_initialize(spi_device); |
| else |
| return spi_controller_shutdown(spi_device); |
| } |
| |
| static int spi_dma_start(int port, const uint8_t *txdata, uint8_t *rxdata, |
| int len) |
| { |
| dma_chan_t *txdma; |
| |
| /* Set up RX DMA */ |
| if (rxdata) |
| dma_start_rx(&dma_rx_option[port], len, rxdata); |
| |
| /* Set up TX DMA */ |
| if (txdata) { |
| txdma = dma_get_channel(dma_tx_option[port].channel); |
| dma_prepare_tx(&dma_tx_option[port], len, txdata); |
| dma_go(txdma); |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| static bool dma_is_enabled_(const struct dma_option *option) |
| { |
| return dma_is_enabled(dma_get_channel(option->channel)); |
| } |
| |
| static int spi_dma_wait(int port) |
| { |
| int rv = EC_SUCCESS; |
| |
| /* Wait for DMA transmission to complete */ |
| if (dma_is_enabled_(&dma_tx_option[port])) { |
| /* |
| * In TX mode, SPI only generates clock when we write to FIFO. |
| * Therefore, even though `dma_wait` polls with interval 0.1ms, |
| * we won't send extra bytes. |
| */ |
| rv = dma_wait(dma_tx_option[port].channel); |
| if (rv) |
| return rv; |
| /* Disable TX DMA */ |
| dma_disable(dma_tx_option[port].channel); |
| } |
| |
| /* Wait for DMA reception to complete */ |
| if (dma_is_enabled_(&dma_rx_option[port])) { |
| /* |
| * Because `dma_wait` polls with interval 0.1ms, we will read at |
| * least ~100 bytes (with 8MHz clock). If you don't want this |
| * overhead, you can use interrupt handler |
| * (`dma_enable_tc_interrupt_callback`) and disable SPI |
| * interface in callback function. |
| */ |
| rv = dma_wait(dma_rx_option[port].channel); |
| if (rv) |
| return rv; |
| /* Disable RX DMA */ |
| dma_disable(dma_rx_option[port].channel); |
| } |
| return rv; |
| } |
| |
| static uint8_t spi_chip_select_already_asserted[ARRAY_SIZE(SPI_REGS)]; |
| |
| int spi_transaction_async(const struct spi_device_t *spi_device, |
| const uint8_t *txdata, int txlen, uint8_t *rxdata, |
| int rxlen) |
| { |
| int rv = EC_SUCCESS; |
| int port = spi_device->port; |
| int full_readback = 0; |
| |
| stm32_spi_regs_t *spi = SPI_REGS[port]; |
| char *buf = NULL; |
| |
| /* We should not ever be called when disabled, but fail early if so. */ |
| if (!spi_enabled[port]) |
| return EC_ERROR_BUSY; |
| |
| #ifndef CONFIG_SPI_HALFDUPLEX |
| if (rxlen == SPI_READBACK_ALL) { |
| buf = rxdata; |
| full_readback = 1; |
| } else { |
| rv = shared_mem_acquire(MAX(txlen, rxlen), &buf); |
| if (rv != EC_SUCCESS) |
| return rv; |
| } |
| #endif |
| |
| if (IS_ENABLED(CONFIG_USB_SPI)) { |
| spi_chip_select_already_asserted[port] = |
| !gpio_get_level(spi_device->gpio_cs); |
| } |
| |
| /* Drive SS low */ |
| gpio_set_level(spi_device->gpio_cs, 0); |
| |
| spi_clear_rx_fifo(spi); |
| |
| /* Initiate write part of the transaction, non-blocking. */ |
| if (txlen) { |
| rv = spi_dma_start(port, txdata, buf, txlen); |
| if (rv != EC_SUCCESS) |
| goto err_free; |
| #ifdef CONFIG_SPI_HALFDUPLEX |
| spi->cr1 |= STM32_SPI_CR1_BIDIOE; |
| #endif |
| } |
| |
| if (full_readback) |
| return EC_SUCCESS; |
| |
| if (rxlen) { |
| /* |
| * If "write then read" was requested, then we have to wait for |
| * the write to complete, before we can initiate read. |
| */ |
| if (txlen) { |
| rv = spi_dma_wait(port); |
| if (rv != EC_SUCCESS) |
| goto err_free; |
| |
| spi_clear_tx_fifo(spi); |
| } |
| |
| /* Initiate read part of the transaction, non-blocking. */ |
| rv = spi_dma_start(port, buf, rxdata, rxlen); |
| if (rv != EC_SUCCESS) |
| goto err_free; |
| #ifdef CONFIG_SPI_HALFDUPLEX |
| spi->cr1 &= ~STM32_SPI_CR1_BIDIOE; |
| #endif |
| } |
| |
| /* |
| * At this point, there is EITHER a pending non-blocking write OR a |
| * pending non-blocking read. In either case, spi_transaction_flush() |
| * will wait for completion of the DMA transfer, and also make sure |
| * that any last bits in the transmit shift register is flushed. |
| */ |
| |
| err_free: |
| #ifndef CONFIG_SPI_HALFDUPLEX |
| if (!full_readback) |
| shared_mem_release(buf); |
| #endif |
| return rv; |
| } |
| |
| int spi_transaction_flush(const struct spi_device_t *spi_device) |
| { |
| int rv = spi_dma_wait(spi_device->port); |
| int port = spi_device->port; |
| stm32_spi_regs_t *spi = SPI_REGS[port]; |
| |
| /* |
| * For the case that the DMA transfer just finished was for SPI |
| * transfer, ensure that the last bits are fully transmitted before |
| * releasing CS. |
| */ |
| spi_clear_tx_fifo(spi); |
| |
| if (!IS_ENABLED(CONFIG_USB_SPI) || |
| !spi_chip_select_already_asserted[spi_device->port]) { |
| /* Drive SS high */ |
| gpio_set_level(spi_device->gpio_cs, 1); |
| } |
| |
| return rv; |
| } |
| |
| int spi_transaction_wait(const struct spi_device_t *spi_device) |
| { |
| return spi_dma_wait(spi_device->port); |
| } |
| |
| int spi_transaction(const struct spi_device_t *spi_device, |
| const uint8_t *txdata, int txlen, uint8_t *rxdata, |
| int rxlen) |
| { |
| int rv; |
| int port = spi_device->port; |
| |
| mutex_lock(spi_mutex + port); |
| rv = spi_transaction_async(spi_device, txdata, txlen, rxdata, rxlen); |
| rv |= spi_transaction_flush(spi_device); |
| mutex_unlock(spi_mutex + port); |
| |
| return rv; |
| } |