diff options
author | Vincent Palatin <vpalatin@chromium.org> | 2017-12-20 14:16:10 +0100 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2018-01-08 17:22:48 -0800 |
commit | d56195cfdbc1a3efcf7009ebe120fdb7d1a4f26c (patch) | |
tree | f86d1301c0ecf2e1f94bc2f9917de42e4e881d08 | |
parent | e24a3953c2c23df219fc0c735e0c188fd8edbded (diff) | |
download | chrome-ec-d56195cfdbc1a3efcf7009ebe120fdb7d1a4f26c.tar.gz |
stm32: add SPI master for STM32H7
Add the driver for the new silicon used in STM32H7 SPI controller,
including its bad errata when used with DMA.
Signed-off-by: Vincent Palatin <vpalatin@chromium.org>
BRANCH=none
BUG=b:67081508
TEST=on ZerbleBarn, do finger image acquisition on the SPI fingerprint
sensor.
Change-Id: Ieaf4a09e961d3e0ef78b58886c409a7dfb63aaf3
Reviewed-on: https://chromium-review.googlesource.com/836617
Commit-Ready: Vincent Palatin <vpalatin@chromium.org>
Tested-by: Vincent Palatin <vpalatin@chromium.org>
Reviewed-by: Shawn N <shawnn@chromium.org>
-rw-r--r-- | chip/stm32/build.mk | 3 | ||||
-rw-r--r-- | chip/stm32/spi_master-stm32h7.c | 339 | ||||
-rw-r--r-- | include/spi.h | 2 |
3 files changed, 343 insertions, 1 deletions
diff --git a/chip/stm32/build.mk b/chip/stm32/build.mk index a5249b4b4a..ff069c9fa6 100644 --- a/chip/stm32/build.mk +++ b/chip/stm32/build.mk @@ -31,6 +31,7 @@ endif # Select between 16-bit and 32-bit timer for clock source TIMER_TYPE=$(if $(CONFIG_STM_HWTIMER32),32,) DMA_TYPE=$(if $(CHIP_FAMILY_STM32F4)$(CHIP_FAMILY_STM32H7),-stm32f4,) +SPI_TYPE=$(if $(CHIP_FAMILY_STM32H7),-stm32h7,) chip-$(CONFIG_DMA)+=dma$(DMA_TYPE).o chip-$(CONFIG_COMMON_RUNTIME)+=system.o @@ -39,7 +40,7 @@ ifeq ($(CHIP_FAMILY),$(filter $(CHIP_FAMILY),stm32f0 stm32f3 stm32f4)) chip-y+=clock-f.o endif chip-$(CONFIG_SPI)+=spi.o -chip-$(CONFIG_SPI_MASTER)+=spi_master.o +chip-$(CONFIG_SPI_MASTER)+=spi_master$(SPI_TYPE).o chip-$(CONFIG_COMMON_GPIO)+=gpio.o gpio-$(CHIP_FAMILY).o chip-$(CONFIG_COMMON_TIMER)+=hwtimer$(TIMER_TYPE).o chip-$(CONFIG_I2C)+=i2c-$(CHIP_FAMILY).o diff --git a/chip/stm32/spi_master-stm32h7.c b/chip/stm32/spi_master-stm32h7.c new file mode 100644 index 0000000000..af4c3a30f2 --- /dev/null +++ b/chip/stm32/spi_master-stm32h7.c @@ -0,0 +1,339 @@ +/* + * Copyright 2017 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * SPI master driver. + */ + +#include "common.h" +#include "dma.h" +#include "gpio.h" +#include "shared_mem.h" +#include "spi.h" +#include "stm32-dma.h" +#include "task.h" +#include "timer.h" +#include "util.h" + +/* SPI ports are used as master */ +static stm32_spi_regs_t *SPI_REGS[] = { +#ifdef CONFIG_STM32_SPI1_MASTER + STM32_SPI1_REGS, +#endif + STM32_SPI2_REGS, + STM32_SPI3_REGS, + STM32_SPI4_REGS, +}; + +/* DMA request mapping on channels */ +static uint8_t dma_req_tx[ARRAY_SIZE(SPI_REGS)] = { +#ifdef CONFIG_STM32_SPI1_MASTER + DMAMUX1_REQ_SPI1_TX, +#endif + DMAMUX1_REQ_SPI2_TX, + DMAMUX1_REQ_SPI3_TX, + DMAMUX1_REQ_SPI4_TX, +}; +static uint8_t dma_req_rx[ARRAY_SIZE(SPI_REGS)] = { +#ifdef CONFIG_STM32_SPI1_MASTER + DMAMUX1_REQ_SPI1_RX, +#endif + DMAMUX1_REQ_SPI2_RX, + DMAMUX1_REQ_SPI3_RX, + DMAMUX1_REQ_SPI4_RX, +}; + +static struct mutex spi_mutex[ARRAY_SIZE(SPI_REGS)]; + +#define SPI_TRANSACTION_TIMEOUT_USEC (800 * MSEC) + +static const struct dma_option dma_tx_option[] = { +#ifdef CONFIG_STM32_SPI1_MASTER + { + STM32_DMAC_SPI1_TX, (void *)&STM32_SPI1_REGS->txdr, + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT + }, +#endif + { + STM32_DMAC_SPI2_TX, (void *)&STM32_SPI2_REGS->txdr, + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT + }, + { + STM32_DMAC_SPI3_TX, (void *)&STM32_SPI3_REGS->txdr, + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT + }, + { + STM32_DMAC_SPI4_TX, (void *)&STM32_SPI4_REGS->txdr, + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT + }, +}; + +static const struct dma_option dma_rx_option[] = { +#ifdef CONFIG_STM32_SPI1_MASTER + { + STM32_DMAC_SPI1_RX, (void *)&STM32_SPI1_REGS->rxdr, + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT + }, +#endif + { + STM32_DMAC_SPI2_RX, (void *)&STM32_SPI2_REGS->rxdr, + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT + }, + { + STM32_DMAC_SPI3_RX, (void *)&STM32_SPI3_REGS->rxdr, + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT + }, + { + STM32_DMAC_SPI4_RX, (void *)&STM32_SPI4_REGS->rxdr, + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT + }, +}; + +static uint8_t spi_enabled[ARRAY_SIZE(SPI_REGS)]; + +/** + * Initialize SPI module, registers, and clocks + * + * - port: which port to initialize. + */ +static void spi_master_config(int port) +{ + int i, div = 0; + + stm32_spi_regs_t *spi = SPI_REGS[port]; + + /* + * Set SPI master, baud rate, and software slave control. + */ + for (i = 0; i < spi_devices_used; i++) + if ((spi_devices[i].port == port) && + (div < spi_devices[i].div)) + div = spi_devices[i].div; + + spi->cr1 = STM32_SPI_CR1_SSI; + spi->cfg2 = STM32_SPI_CFG2_MSTR | STM32_SPI_CFG2_SSM + | STM32_SPI_CFG2_AFCNTR; + spi->cfg1 = STM32_SPI_CFG1_DATASIZE(8) | STM32_SPI_CFG1_FTHLV(4) + | STM32_SPI_CFG1_CRCSIZE(8) | STM32_SPI_CR1_DIV(div); + + dma_select_channel(dma_tx_option[port].channel, dma_req_tx[port]); + dma_select_channel(dma_rx_option[port].channel, dma_req_rx[port]); +} + +static int spi_master_initialize(int port) +{ + int i; + + spi_master_config(port); + + for (i = 0; i < spi_devices_used; i++) { + if (spi_devices[i].port != port) + continue; + /* Drive SS high */ + gpio_set_level(spi_devices[i].gpio_cs, 1); + } + + /* Set flag */ + spi_enabled[port] = 1; + + return EC_SUCCESS; +} + +/** + * Shutdown SPI module + */ +static int spi_master_shutdown(int port) +{ + int rv = EC_SUCCESS; + 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 */ + spi->cr1 &= ~STM32_SPI_CR1_SPE; + + /* Disable DMA buffers */ + spi->cfg1 &= ~(STM32_SPI_CFG1_TXDMAEN | STM32_SPI_CFG1_RXDMAEN); + + return rv; +} + +int spi_enable(int port, int enable) +{ + if (enable == spi_enabled[port]) + return EC_SUCCESS; + if (enable) + return spi_master_initialize(port); + else + return spi_master_shutdown(port); +} + +static int spi_dma_start(int port, const uint8_t *txdata, + uint8_t *rxdata, int len) +{ + dma_chan_t *txdma; + stm32_spi_regs_t *spi = SPI_REGS[port]; + + /* + * Workaround for STM32H7 errata: without resetting the SPI controller, + * the RX DMA requests will happen too early on the 2nd transfer. + */ + STM32_RCC_APB2RSTR = STM32_RCC_PB2_SPI4; + STM32_RCC_APB2RSTR = 0; + dma_clear_isr(dma_tx_option[port].channel); + dma_clear_isr(dma_rx_option[port].channel); + /* restore proper SPI configuration registers. */ + spi_master_config(port); + + spi->cr2 = len; + spi->cfg1 |= STM32_SPI_CFG1_RXDMAEN; + /* 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); + } + + spi->cfg1 |= STM32_SPI_CFG1_TXDMAEN; + spi->cr1 |= STM32_SPI_CR1_SPE; + spi->cr1 |= STM32_SPI_CR1_CSTART; + + return EC_SUCCESS; +} + +static inline int dma_is_enabled(const struct dma_option *option) +{ + /* dma_bytes_done() returns 0 if channel is not enabled */ + return dma_bytes_done(dma_get_channel(option->channel), -1); +} + +static int spi_dma_wait(int port) +{ + timestamp_t timeout; + stm32_spi_regs_t *spi = SPI_REGS[port]; + int rv = EC_SUCCESS; + + /* Wait for DMA transmission to complete */ + if (dma_is_enabled(&dma_tx_option[port])) { + rv = dma_wait(dma_tx_option[port].channel); + if (rv) + return rv; + + timeout.val = get_time().val + SPI_TRANSACTION_TIMEOUT_USEC; + /* Wait for FIFO empty and BSY bit clear */ + while (!(spi->sr & (STM32_SPI_SR_TXC))) + if (get_time().val > timeout.val) + return EC_ERROR_TIMEOUT; + + /* Disable TX DMA */ + dma_disable(dma_tx_option[port].channel); + } + + /* Wait for DMA reception to complete */ + if (dma_is_enabled(&dma_rx_option[port])) { + rv = dma_wait(dma_rx_option[port].channel); + if (rv) + return rv; + + timeout.val = get_time().val + SPI_TRANSACTION_TIMEOUT_USEC; + /* Wait for FRLVL[1:0] to indicate FIFO empty */ + while (spi->sr & (STM32_SPI_SR_FRLVL | STM32_SPI_SR_RXNE)) + if (get_time().val > timeout.val) + return EC_ERROR_TIMEOUT; + + /* Disable RX DMA */ + dma_disable(dma_rx_option[port].channel); + } + + spi->cr1 &= ~STM32_SPI_CR1_SPE; + spi->cfg1 &= ~(STM32_SPI_CFG1_TXDMAEN | STM32_SPI_CFG1_RXDMAEN); + + return rv; +} + +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; + + char *buf = NULL; + +#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 + + /* Drive SS low */ + gpio_set_level(spi_device->gpio_cs, 0); + + rv = spi_dma_start(port, txdata, buf, txlen); + if (rv != EC_SUCCESS) + goto err_free; + + if (full_readback) + return EC_SUCCESS; + + if (rxlen) { + rv = spi_dma_wait(port); + if (rv != EC_SUCCESS) + goto err_free; + + rv = spi_dma_start(port, buf, rxdata, rxlen); + if (rv != EC_SUCCESS) + goto err_free; + } + +err_free: + if (!full_readback) + shared_mem_release(buf); + return rv; +} + +int spi_transaction_flush(const struct spi_device_t *spi_device) +{ + int rv = spi_dma_wait(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; +} diff --git a/include/spi.h b/include/spi.h index 49bddd3cc3..5cf3389e91 100644 --- a/include/spi.h +++ b/include/spi.h @@ -88,6 +88,8 @@ int spi_transaction(const struct spi_device_t *spi_device, /* Similar to spi_transaction(), but hands over to DMA for reading response. * Must call spi_transaction_flush() after this to make sure the response is * received. + * Contrary the regular spi_transaction(), this function does NOT lock the + * SPI port, it's up to the caller to ensure proper mutual exclusion if needed. */ int spi_transaction_async(const struct spi_device_t *spi_device, const uint8_t *txdata, int txlen, |