/**************************************************************************** * arch/xtensa/src/esp32/esp32_spi.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #ifdef CONFIG_ESP32_SPI #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "esp32_spi.h" #include "esp32_gpio.h" #include "esp32_irq.h" #include "esp32_dma.h" #include "xtensa.h" #include "hardware/esp32_gpio_sigmap.h" #include "hardware/esp32_dport.h" #include "hardware/esp32_spi.h" #include "hardware/esp32_soc.h" #include "hardware/esp32_pinmap.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* SPI DMA RX/TX description number */ #define SPI_DMADESC_NUM (CONFIG_SPI_DMADESC_NUM) /* SPI DMA channel number */ #define SPI_DMA_CHANNEL_MAX (2) /* SPI DMA reset before exchange */ #define SPI_DMA_RESET_MASK (SPI_AHBM_RST_M | SPI_AHBM_FIFO_RST_M | \ SPI_OUT_RST_M | SPI_IN_RST_M) /* SPI Default speed (limited by clock divider) */ #define SPI_FREQ_DEFAULT (400000) /* Helper for applying the mask for a given register field. * Mask is determined by the macros suffixed with _V and _S from the * peripheral register description. */ #define VALUE_MASK(_val, _field) ((_val & (_field##_V)) << (_field##_S)) /* SPI Maximum buffer size in bytes */ #define SPI_MAX_BUF_SIZE (64) #ifndef MIN # define MIN(a, b) (((a) < (b)) ? (a) : (b)) #endif /**************************************************************************** * Private Types ****************************************************************************/ /* SPI Device hardware configuration */ struct esp32_spi_config_s { uint32_t id; /* SPI instance */ uint32_t clk_freq; /* SPI clock frequency */ enum spi_mode_e mode; /* SPI default mode */ uint8_t cs_pin; /* GPIO configuration for CS */ uint8_t mosi_pin; /* GPIO configuration for MOSI */ uint8_t miso_pin; /* GPIO configuration for MISO */ uint8_t clk_pin; /* GPIO configuration for CLK */ uint8_t periph; /* peripher ID */ uint8_t irq; /* Interrupt ID */ uint32_t clk_bit; /* Clock enable bit */ uint32_t rst_bit; /* SPI reset bit */ bool use_dma; /* Use DMA */ uint8_t dma_chan_s; /* DMA channel register shift */ uint8_t dma_chan; /* DMA channel */ uint32_t dma_clk_bit; /* DMA clock enable bit */ uint32_t dma_rst_bit; /* DMA reset bit */ uint32_t cs_insig; /* SPI CS input signal index */ uint32_t cs_outsig; /* SPI CS output signal index */ uint32_t mosi_insig; /* SPI MOSI input signal index */ uint32_t mosi_outsig; /* SPI MOSI output signal index */ uint32_t miso_insig; /* SPI MISO input signal index */ uint32_t miso_outsig; /* SPI MISO output signal index */ uint32_t clk_insig; /* SPI CLK input signal index */ uint32_t clk_outsig; /* SPI CLK output signal index */ }; struct esp32_spi_priv_s { /* Externally visible part of the SPI interface */ struct spi_dev_s spi_dev; /* Port configuration */ const struct esp32_spi_config_s *config; int refs; /* Reference count */ /* Held while chip is selected for mutual exclusion */ sem_t exclsem; /* Interrupt wait semaphore */ sem_t sem_isr; int cpuint; /* SPI interrupt ID */ uint8_t cpu; /* CPU ID */ uint32_t frequency; /* Requested clock frequency */ uint32_t actual; /* Actual clock frequency */ enum spi_mode_e mode; /* Actual SPI hardware mode */ /* Actual SPI send/receive bits once transmission */ uint8_t nbits; spinlock_t lock; /* Device specific lock. */ }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int esp32_spi_lock(struct spi_dev_s *dev, bool lock); #ifndef CONFIG_ESP32_SPI_UDCS static void esp32_spi_select(struct spi_dev_s *dev, uint32_t devid, bool selected); #endif static uint32_t esp32_spi_setfrequency(struct spi_dev_s *dev, uint32_t frequency); static void esp32_spi_setmode(struct spi_dev_s *dev, enum spi_mode_e mode); static void esp32_spi_setbits(struct spi_dev_s *dev, int nbits); #ifdef CONFIG_SPI_HWFEATURES static int esp32_spi_hwfeatures(struct spi_dev_s *dev, spi_hwfeatures_t features); #endif static uint32_t esp32_spi_send(struct spi_dev_s *dev, uint32_t wd); static void esp32_spi_exchange(struct spi_dev_s *dev, const void *txbuffer, void *rxbuffer, size_t nwords); #ifndef CONFIG_SPI_EXCHANGE static void esp32_spi_sndblock(struct spi_dev_s *dev, const void *txbuffer, size_t nwords); static void esp32_spi_recvblock(struct spi_dev_s *dev, void *rxbuffer, size_t nwords); #endif #ifdef CONFIG_SPI_TRIGGER static int esp32_spi_trigger(struct spi_dev_s *dev); #endif static void esp32_spi_init(struct spi_dev_s *dev); static void esp32_spi_deinit(struct spi_dev_s *dev); /**************************************************************************** * Private Data ****************************************************************************/ #ifdef CONFIG_ESP32_SPI2 static const struct esp32_spi_config_s esp32_spi2_config = { .id = 2, .clk_freq = SPI_FREQ_DEFAULT, .mode = SPIDEV_MODE0, .cs_pin = CONFIG_ESP32_SPI2_CSPIN, .mosi_pin = CONFIG_ESP32_SPI2_MOSIPIN, .miso_pin = CONFIG_ESP32_SPI2_MISOPIN, .clk_pin = CONFIG_ESP32_SPI2_CLKPIN, .periph = ESP32_PERIPH_SPI2, .irq = ESP32_IRQ_SPI2, .clk_bit = DPORT_SPI_CLK_EN_2, .rst_bit = DPORT_SPI_RST_2, #ifdef CONFIG_ESP32_SPI2_DMA .use_dma = true, #else .use_dma = false, #endif .dma_chan_s = 2, .dma_chan = 1, .dma_clk_bit = DPORT_SPI_DMA_CLK_EN, .dma_rst_bit = DPORT_SPI_DMA_RST, .cs_insig = HSPICS0_IN_IDX, .cs_outsig = HSPICS0_OUT_IDX, .mosi_insig = HSPID_IN_IDX, .mosi_outsig = HSPID_OUT_IDX, .miso_insig = HSPIQ_IN_IDX, .miso_outsig = HSPIQ_OUT_IDX, .clk_insig = HSPICLK_IN_IDX, .clk_outsig = HSPICLK_OUT_IDX }; static const struct spi_ops_s esp32_spi2_ops = { .lock = esp32_spi_lock, #ifdef CONFIG_ESP32_SPI_UDCS .select = esp32_spi2_select, #else .select = esp32_spi_select, #endif .setfrequency = esp32_spi_setfrequency, .setmode = esp32_spi_setmode, .setbits = esp32_spi_setbits, #ifdef CONFIG_SPI_HWFEATURES .hwfeatures = esp32_spi_hwfeatures, #endif .status = esp32_spi2_status, #ifdef CONFIG_SPI_CMDDATA .cmddata = esp32_spi2_cmddata, #endif .send = esp32_spi_send, #ifdef CONFIG_SPI_EXCHANGE .exchange = esp32_spi_exchange, #else .sndblock = esp32_spi_sndblock, .recvblock = esp32_spi_recvblock, #endif #ifdef CONFIG_SPI_TRIGGER .trigger = esp32_spi_trigger, #endif .registercallback = NULL, }; static struct esp32_spi_priv_s esp32_spi2_priv = { .spi_dev = { .ops = &esp32_spi2_ops }, .config = &esp32_spi2_config }; #endif /* CONFIG_ESP32_SPI2 */ #ifdef CONFIG_ESP32_SPI3 static const struct esp32_spi_config_s esp32_spi3_config = { .id = 3, .clk_freq = SPI_FREQ_DEFAULT, .mode = SPIDEV_MODE0, .cs_pin = CONFIG_ESP32_SPI3_CSPIN, .mosi_pin = CONFIG_ESP32_SPI3_MOSIPIN, .miso_pin = CONFIG_ESP32_SPI3_MISOPIN, .clk_pin = CONFIG_ESP32_SPI3_CLKPIN, .periph = ESP32_PERIPH_SPI3, .irq = ESP32_IRQ_SPI3, .clk_bit = DPORT_SPI_CLK_EN, .rst_bit = DPORT_SPI_RST, #ifdef CONFIG_ESP32_SPI3_DMA .use_dma = true, #else .use_dma = false, #endif .dma_chan_s = 4, .dma_chan = 2, .dma_clk_bit = DPORT_SPI_DMA_CLK_EN, .dma_rst_bit = DPORT_SPI_DMA_RST, .cs_insig = VSPICS0_IN_IDX, .cs_outsig = VSPICS0_OUT_IDX, .mosi_insig = VSPID_IN_IDX, .mosi_outsig = VSPID_OUT_IDX, .miso_insig = VSPIQ_IN_IDX, .miso_outsig = VSPIQ_OUT_IDX, .clk_insig = VSPICLK_IN_IDX, .clk_outsig = VSPICLK_OUT_MUX_IDX }; static const struct spi_ops_s esp32_spi3_ops = { .lock = esp32_spi_lock, #ifdef CONFIG_ESP32_SPI_UDCS .select = esp32_spi3_select, #else .select = esp32_spi_select, #endif .setfrequency = esp32_spi_setfrequency, .setmode = esp32_spi_setmode, .setbits = esp32_spi_setbits, #ifdef CONFIG_SPI_HWFEATURES .hwfeatures = esp32_spi_hwfeatures, #endif .status = esp32_spi3_status, #ifdef CONFIG_SPI_CMDDATA .cmddata = esp32_spi3_cmddata, #endif .send = esp32_spi_send, #ifdef CONFIG_SPI_EXCHANGE .exchange = esp32_spi_exchange, #else .sndblock = esp32_spi_sndblock, .recvblock = esp32_spi_recvblock, #endif #ifdef CONFIG_SPI_TRIGGER .trigger = esp32_spi_trigger, #endif .registercallback = NULL, }; static struct esp32_spi_priv_s esp32_spi3_priv = { .spi_dev = { .ops = &esp32_spi3_ops }, .config = &esp32_spi3_config }; #endif /* CONFIG_ESP32_SPI3 */ /* SPI DMA RX/TX description */ struct esp32_dmadesc_s s_dma_rxdesc[SPI_DMA_CHANNEL_MAX][SPI_DMADESC_NUM]; struct esp32_dmadesc_s s_dma_txdesc[SPI_DMA_CHANNEL_MAX][SPI_DMADESC_NUM]; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: esp32_spi_set_regbits * * Description: * Set the bits of the SPI register * * Input Parameters: * addr - Address of the register of interest * bits - Bits to be set * * Returned Value: * None * ****************************************************************************/ static inline void esp32_spi_set_regbits(uint32_t addr, uint32_t bits) { uint32_t tmp = getreg32(addr); putreg32(tmp | bits, addr); } /**************************************************************************** * Name: esp32_spi_reset_regbits * * Description: * Clear the bits of the SPI register * * Input Parameters: * addr - Address of the register of interest * bits - Bits to be cleared * * Returned Value: * None * ****************************************************************************/ static inline void esp32_spi_reset_regbits(uint32_t addr, uint32_t bits) { uint32_t tmp = getreg32(addr); putreg32(tmp & (~bits), addr); } /**************************************************************************** * Name: esp32_spi_iomux * * Description: * Check if the option SPI GPIO pins can use IOMUX directly * * Input Parameters: * priv - Private SPI device structure * * Returned Value: * True if can use IOMUX or false if can't. * ****************************************************************************/ static inline bool esp32_spi_iomux(struct esp32_spi_priv_s *priv) { bool mapped = false; const struct esp32_spi_config_s *cfg = priv->config; if (cfg->id == 2) { if (cfg->mosi_pin == SPI2_IOMUX_MOSIPIN && #ifndef CONFIG_ESP32_SPI_SWCS cfg->cs_pin == SPI2_IOMUX_CSPIN && #endif cfg->miso_pin == SPI2_IOMUX_MISOPIN && cfg->clk_pin == SPI2_IOMUX_CLKPIN) { mapped = true; } } else if (cfg->id == 3) { if (cfg->mosi_pin == SPI3_IOMUX_MOSIPIN && #ifndef CONFIG_ESP32_SPI_SWCS cfg->cs_pin == SPI3_IOMUX_CSPIN && #endif cfg->miso_pin == SPI3_IOMUX_MISOPIN && cfg->clk_pin == SPI3_IOMUX_CLKPIN) { mapped = true; } } return mapped; } /**************************************************************************** * Name: esp32_spi_lock * * Description: * Lock or unlock the SPI device * * Input Parameters: * priv - Private SPI device structure * lock - true: Lock spi bus, false: unlock SPI bus * * Returned Value: * The result of lock or unlock the SPI device * ****************************************************************************/ static int esp32_spi_lock(struct spi_dev_s *dev, bool lock) { int ret; struct esp32_spi_priv_s *priv = (struct esp32_spi_priv_s *)dev; if (lock) { ret = nxsem_wait_uninterruptible(&priv->exclsem); } else { ret = nxsem_post(&priv->exclsem); } return ret; } /**************************************************************************** * Name: esp32_spi_sem_waitdone * * Description: * Wait for a transfer to complete * ****************************************************************************/ static int esp32_spi_sem_waitdone(struct esp32_spi_priv_s *priv) { int ret; struct timespec abstime; clock_gettime(CLOCK_REALTIME, &abstime); abstime.tv_sec += 10; abstime.tv_nsec += 0; ret = nxsem_timedwait_uninterruptible(&priv->sem_isr, &abstime); return ret; } /**************************************************************************** * Name: esp32_spi_select * * Description: * Enable/disable the SPI chip select. The implementation of this method * must include handshaking: If a device is selected, it must hold off * all other attempts to select the device until the device is deselected. * * If disable ESP32_SPI_SWCS, driver will use hardware CS so that when * once transmission is started, hardware select the device and when this * transmission is done, hardware deselect the device automatically. And * the function will do nothing. * * Input Parameters: * priv - Private SPI device structure * devid - Identifies the device to select * selected - true: slave selected, false: slave de-selected * * Returned Value: * None * ****************************************************************************/ #ifndef CONFIG_ESP32_SPI_UDCS static void esp32_spi_select(struct spi_dev_s *dev, uint32_t devid, bool selected) { #ifdef CONFIG_ESP32_SPI_SWCS struct esp32_spi_priv_s *priv = (struct esp32_spi_priv_s *)dev; bool value = selected ? false : true; esp32_gpiowrite(priv->config->cs_pin, value); #endif spiinfo("devid: %08" PRIx32 " CS: %s\n", devid, selected ? "select" : "free"); } #endif /**************************************************************************** * Name: esp32_spi_setfrequency * * Description: * Set the SPI frequency. * * Input Parameters: * dev - Device-specific state data * frequency - The SPI frequency requested * * Returned Value: * Returns the actual frequency selected * ****************************************************************************/ static uint32_t esp32_spi_setfrequency(struct spi_dev_s *dev, uint32_t frequency) { uint32_t reg_val; struct esp32_spi_priv_s *priv = (struct esp32_spi_priv_s *)dev; const uint32_t duty_cycle = 128; if (priv->frequency == frequency) { /* We are already at this frequency. Return the actual. */ return priv->actual; } if (frequency > ((APB_CLK_FREQ / 4) * 3)) { reg_val = SPI_CLK_EQU_SYSCLK_M; priv->actual = APB_CLK_FREQ; } else { int pre; int n; int h; int l; int bestn = -1; int bestpre = -1; int besterr = 0; int errval; for (n = 2; n <= 64; n++) { pre = ((APB_CLK_FREQ / n) + (frequency / 2)) / frequency; if (pre <= 0) { pre = 1; } if (pre > 8192) { pre = 8192; } errval = abs(APB_CLK_FREQ / (pre * n) - frequency); if (bestn == -1 || errval <= besterr) { besterr = errval; bestn = n; bestpre = pre; } } n = bestn; pre = bestpre; l = n; h = (duty_cycle * n + 127) / 256; if (h <= 0) { h = 1; } reg_val = ((l - 1) << SPI_CLKCNT_L_S) | ((h - 1) << SPI_CLKCNT_H_S) | ((n - 1) << SPI_CLKCNT_N_S) | ((pre - 1) << SPI_CLKDIV_PRE_S); priv->actual = APB_CLK_FREQ / (n * pre); } priv->frequency = frequency; putreg32(reg_val, SPI_CLOCK_REG(priv->config->id)); spiinfo("frequency=%d, actual=%d\n", priv->frequency, priv->actual); return priv->actual; } /**************************************************************************** * Name: esp32_spi_setmode * * Description: * Set the SPI mode. * * Input Parameters: * dev - Device-specific state data * mode - The SPI mode requested * * Returned Value: * none * ****************************************************************************/ static void esp32_spi_setmode(struct spi_dev_s *dev, enum spi_mode_e mode) { uint32_t ck_idle_edge; uint32_t ck_out_edge; uint32_t delay_mode; struct esp32_spi_priv_s *priv = (struct esp32_spi_priv_s *)dev; spiinfo("mode=%d\n", mode); /* Has the mode changed? */ if (mode != priv->mode) { switch (mode) { case SPIDEV_MODE0: /* CPOL=0; CPHA=0 */ ck_idle_edge = 0; ck_out_edge = 0; delay_mode = 0; break; case SPIDEV_MODE1: /* CPOL=0; CPHA=1 */ ck_idle_edge = 0; ck_out_edge = 1; delay_mode = 2; break; case SPIDEV_MODE2: /* CPOL=1; CPHA=0 */ ck_idle_edge = 1; ck_out_edge = 1; delay_mode = 2; break; case SPIDEV_MODE3: /* CPOL=1; CPHA=1 */ ck_idle_edge = 1; ck_out_edge = 0; delay_mode = 0; break; default: return; } const uint32_t id = priv->config->id; esp32_spi_reset_regbits(SPI_PIN_REG(id), SPI_CK_IDLE_EDGE_M); esp32_spi_set_regbits(SPI_PIN_REG(id), VALUE_MASK(ck_idle_edge, SPI_CK_IDLE_EDGE)); esp32_spi_reset_regbits(SPI_USER_REG(id), SPI_CK_OUT_EDGE_M); esp32_spi_set_regbits(SPI_USER_REG(id), VALUE_MASK(ck_out_edge, SPI_CK_OUT_EDGE)); esp32_spi_reset_regbits(SPI_CTRL2_REG(id), SPI_MISO_DELAY_MODE_M | SPI_MISO_DELAY_NUM_M | SPI_MOSI_DELAY_NUM_M | SPI_MOSI_DELAY_MODE_M); esp32_spi_set_regbits(SPI_CTRL2_REG(id), VALUE_MASK(delay_mode, SPI_MISO_DELAY_MODE) | VALUE_MASK(delay_mode, SPI_MOSI_DELAY_MODE)); priv->mode = mode; } } /**************************************************************************** * Name: esp32_spi_setbits * * Description: * Set the number if bits per word. * * Input Parameters: * dev - Device-specific state data * nbits - The number of bits in an SPI word. * * Returned Value: * none * ****************************************************************************/ static void esp32_spi_setbits(struct spi_dev_s *dev, int nbits) { struct esp32_spi_priv_s *priv = (struct esp32_spi_priv_s *)dev; spiinfo("nbits=%d\n", nbits); priv->nbits = nbits; } /**************************************************************************** * Name: esp32_spi_hwfeatures * * Description: * Set hardware-specific feature flags. * * Input Parameters: * dev - Device-specific state data * features - H/W feature flags * * Returned Value: * Zero (OK) if the selected H/W features are enabled; A negated errno * value if any H/W feature is not supportable. * ****************************************************************************/ #ifdef CONFIG_SPI_HWFEATURES static int esp32_spi_hwfeatures(struct spi_dev_s *dev, spi_hwfeatures_t features) { /* Other H/W features are not supported */ return (features == 0) ? OK : -ENOSYS; } #endif /**************************************************************************** * Name: esp32_spi_dma_exchange * * Description: * Exchange a block of data from SPI by DMA. * * Input Parameters: * priv - SPI private state data * txbuffer - A pointer to the buffer of data to be sent * rxbuffer - A pointer to the buffer in which to receive data * nwords - the length of data that to be exchanged in units of words. * The wordsize is determined by the number of bits-per-word * selected for the SPI interface. If nbits <= 8, the data is * packed into uint8_t's; if nbits >8, the data is packed into * uint16_t's * * Returned Value: * None * ****************************************************************************/ static void esp32_spi_dma_exchange(struct esp32_spi_priv_s *priv, const void *txbuffer, void *rxbuffer, uint32_t nwords) { const uint32_t total = nwords * (priv->nbits / 8); uint32_t bytes = total; uint8_t *tp; uint8_t *rp; uint32_t n; uint32_t regval; struct esp32_dmadesc_s *dma_tx_desc; struct esp32_dmadesc_s *dma_rx_desc; #ifdef CONFIG_XTENSA_IMEM_USE_SEPARATE_HEAP uint8_t *alloctp; uint8_t *allocrp; #endif /* Define these constants outside transfer loop to avoid wasting CPU time * with register offset calculation. */ const uint32_t id = priv->config->id; const uint8_t dma_desc_idx = priv->config->dma_chan - 1; const uintptr_t spi_dma_in_link_reg = SPI_DMA_IN_LINK_REG(id); const uintptr_t spi_dma_out_link_reg = SPI_DMA_OUT_LINK_REG(id); const uintptr_t spi_slave_reg = SPI_SLAVE_REG(id); const uintptr_t spi_dma_conf_reg = SPI_DMA_CONF_REG(id); const uintptr_t spi_mosi_dlen_reg = SPI_MOSI_DLEN_REG(id); const uintptr_t spi_miso_dlen_reg = SPI_MISO_DLEN_REG(id); const uintptr_t spi_user_reg = SPI_USER_REG(id); const uintptr_t spi_cmd_reg = SPI_CMD_REG(id); DEBUGASSERT((txbuffer != NULL) || (rxbuffer != NULL)); /* If the buffer comes from PSRAM, allocate a new one from DRAM */ #ifdef CONFIG_XTENSA_IMEM_USE_SEPARATE_HEAP if (esp32_ptr_extram(txbuffer)) { alloctp = xtensa_imm_malloc(total); DEBUGASSERT(alloctp != NULL); memcpy(alloctp, txbuffer, total); tp = alloctp; } else #endif { tp = (uint8_t *)txbuffer; } #ifdef CONFIG_XTENSA_IMEM_USE_SEPARATE_HEAP if (esp32_ptr_extram(rxbuffer)) { allocrp = xtensa_imm_malloc(total); DEBUGASSERT(allocrp != NULL); rp = allocrp; } else #endif { rp = (uint8_t *)rxbuffer; } if (tp == NULL) { tp = rp; } dma_tx_desc = s_dma_txdesc[dma_desc_idx]; dma_rx_desc = s_dma_rxdesc[dma_desc_idx]; esp32_spi_reset_regbits(spi_slave_reg, SPI_TRANS_DONE_M); esp32_spi_set_regbits(spi_slave_reg, SPI_INT_EN_M); while (bytes != 0) { putreg32(0, spi_dma_in_link_reg); putreg32(0, spi_dma_out_link_reg); esp32_spi_set_regbits(spi_slave_reg, SPI_SYNC_RESET_M); esp32_spi_reset_regbits(spi_slave_reg, SPI_SYNC_RESET_M); esp32_spi_set_regbits(spi_dma_conf_reg, SPI_DMA_RESET_MASK); esp32_spi_reset_regbits(spi_dma_conf_reg, SPI_DMA_RESET_MASK); n = esp32_dma_init(dma_tx_desc, SPI_DMADESC_NUM, tp, bytes); regval = VALUE_MASK((uintptr_t)dma_tx_desc, SPI_OUTLINK_ADDR); regval |= SPI_OUTLINK_START_M; putreg32(regval, spi_dma_out_link_reg); putreg32((n * 8 - 1), spi_mosi_dlen_reg); esp32_spi_set_regbits(spi_user_reg, SPI_USR_MOSI_M); tp += n; if (rp != NULL) { esp32_dma_init(dma_rx_desc, SPI_DMADESC_NUM, rp, bytes); regval = VALUE_MASK((uintptr_t)dma_rx_desc, SPI_INLINK_ADDR); regval |= SPI_INLINK_START_M; putreg32(regval, spi_dma_in_link_reg); putreg32((n * 8 - 1), spi_miso_dlen_reg); esp32_spi_set_regbits(spi_user_reg, SPI_USR_MISO_M); rp += n; } else { esp32_spi_reset_regbits(spi_user_reg, SPI_USR_MISO_M); } esp32_spi_set_regbits(spi_cmd_reg, SPI_USR_M); esp32_spi_sem_waitdone(priv); bytes -= n; } esp32_spi_reset_regbits(spi_slave_reg, SPI_INT_EN_M); #ifdef CONFIG_XTENSA_IMEM_USE_SEPARATE_HEAP if (esp32_ptr_extram(rxbuffer)) { memcpy(rxbuffer, allocrp, total); xtensa_imm_free(allocrp); } #endif #ifdef CONFIG_XTENSA_IMEM_USE_SEPARATE_HEAP if (esp32_ptr_extram(txbuffer)) { xtensa_imm_free(alloctp); } #endif } /**************************************************************************** * Name: esp32_spi_poll_send * * Description: * Send one word on SPI by polling mode. * * Input Parameters: * priv - SPI private state data * wd - The word to send. the size of the data is determined by the * number of bits selected for the SPI interface. * * Returned Value: * Received value * ****************************************************************************/ static uint32_t esp32_spi_poll_send(struct esp32_spi_priv_s *priv, uint32_t wd) { uint32_t val; const uintptr_t spi_miso_dlen_reg = SPI_MISO_DLEN_REG(priv->config->id); const uintptr_t spi_mosi_dlen_reg = SPI_MOSI_DLEN_REG(priv->config->id); const uintptr_t spi_w0_reg = SPI_W0_REG(priv->config->id); const uintptr_t spi_cmd_reg = SPI_CMD_REG(priv->config->id); putreg32((priv->nbits - 1), spi_miso_dlen_reg); putreg32((priv->nbits - 1), spi_mosi_dlen_reg); putreg32(wd, spi_w0_reg); esp32_spi_set_regbits(spi_cmd_reg, SPI_USR_M); while ((getreg32(spi_cmd_reg) & SPI_USR_M) != 0) { ; } val = getreg32(spi_w0_reg); spiinfo("send=%x and recv=%x\n", wd, val); return val; } /**************************************************************************** * Name: esp32_spi_send * * Description: * Send one word on SPI. * * Input Parameters: * dev - Device-specific state data * wd - The word to send. the size of the data is determined by the * number of bits selected for the SPI interface. * * Returned Value: * Received value * ****************************************************************************/ static uint32_t esp32_spi_send(struct spi_dev_s *dev, uint32_t wd) { struct esp32_spi_priv_s *priv = (struct esp32_spi_priv_s *)dev; return esp32_spi_poll_send(priv, wd); } /**************************************************************************** * Name: esp32_spi_poll_exchange * * Description: * Exchange a block of data from SPI. * * Input Parameters: * priv - SPI private state data * txbuffer - A pointer to the buffer of data to be sent * rxbuffer - A pointer to the buffer in which to receive data * nwords - the length of data that to be exchanged in units of words. * The wordsize is determined by the number of bits-per-word * selected for the SPI interface. If nbits <= 8, the data is * packed into uint8_t's; if nbits >8, the data is packed into * uint16_t's * * Returned Value: * None * ****************************************************************************/ static void esp32_spi_poll_exchange(struct esp32_spi_priv_s *priv, const void *txbuffer, void *rxbuffer, size_t nwords) { const uintptr_t spi_user_reg = SPI_USER_REG(priv->config->id); const uintptr_t spi_w0_reg = SPI_W0_REG(priv->config->id); const uintptr_t spi_cmd_reg = SPI_CMD_REG(priv->config->id); const uintptr_t spi_miso_dlen_reg = SPI_MISO_DLEN_REG(priv->config->id); const uintptr_t spi_mosi_dlen_reg = SPI_MOSI_DLEN_REG(priv->config->id); const uint32_t total_bytes = nwords * (priv->nbits / 8); uintptr_t bytes_remaining = total_bytes; uint8_t *tp = (uint8_t *)txbuffer; uint8_t *rp = (uint8_t *)rxbuffer; while (bytes_remaining != 0) { /* Initialize data_buf_reg with the address of the first data buffer * register (W0). */ uintptr_t data_buf_reg = spi_w0_reg; uint32_t transfer_size = MIN(SPI_MAX_BUF_SIZE, bytes_remaining); /* Write data words to data buffer registers. * SPI peripheral contains 16 registers (W0 - W15). */ for (int i = 0 ; i < transfer_size; i += sizeof(uint32_t)) { uint32_t w_wd = UINT32_MAX; if (tp != NULL) { memcpy(&w_wd, tp, sizeof(uint32_t)); tp += sizeof(uintptr_t); } putreg32(w_wd, data_buf_reg); spiinfo("send=0x%" PRIx32 " data_reg=0x%" PRIx32 "\n", w_wd, data_buf_reg); /* Update data_buf_reg to point to the next data buffer register. */ data_buf_reg += sizeof(uintptr_t); } esp32_spi_set_regbits(spi_user_reg, SPI_USR_MOSI_M); if (rp == NULL) { esp32_spi_reset_regbits(spi_user_reg, SPI_USR_MISO_M); } else { esp32_spi_set_regbits(spi_user_reg, SPI_USR_MISO_M); } putreg32((transfer_size * 8) - 1, spi_mosi_dlen_reg); putreg32((transfer_size * 8) - 1, spi_miso_dlen_reg); /* Trigger start of user-defined transaction for master. */ esp32_spi_set_regbits(spi_cmd_reg, SPI_USR_M); /* Wait for the user-defined transaction to finish. */ while ((getreg32(spi_cmd_reg) & SPI_USR_M) != 0) { ; } if (rp != NULL) { /* Set data_buf_reg with the address of the first data buffer * register (W0). */ data_buf_reg = spi_w0_reg; /* Read received data words from SPI data buffer registers. */ for (int i = 0 ; i < transfer_size; i += sizeof(uint32_t)) { uint32_t r_wd = getreg32(data_buf_reg); spiinfo("recv=0x%" PRIx32 " data_reg=0x%" PRIx32 "\n", r_wd, data_buf_reg); memcpy(rp, &r_wd, sizeof(uint32_t)); rp += sizeof(uintptr_t); /* Update data_buf_reg to point to the next data buffer * register. */ data_buf_reg += sizeof(uintptr_t); } } bytes_remaining -= transfer_size; } } /**************************************************************************** * Name: esp32_spi_exchange * * Description: * Exchange a block of data from SPI. * * Input Parameters: * dev - Device-specific state data * txbuffer - A pointer to the buffer of data to be sent * rxbuffer - A pointer to the buffer in which to receive data * nwords - the length of data that to be exchanged in units of words. * The wordsize is determined by the number of bits-per-word * selected for the SPI interface. If nbits <= 8, the data is * packed into uint8_t's; if nbits >8, the data is packed into * uint16_t's * * Returned Value: * None * ****************************************************************************/ static void esp32_spi_exchange(struct spi_dev_s *dev, const void *txbuffer, void *rxbuffer, size_t nwords) { struct esp32_spi_priv_s *priv = (struct esp32_spi_priv_s *)dev; #ifdef CONFIG_ESP32_SPI_DMATHRESHOLD size_t thld = CONFIG_ESP32_SPI_DMATHRESHOLD; #else size_t thld = 0; #endif if (priv->config->use_dma && nwords > thld) { esp32_spi_dma_exchange(priv, txbuffer, rxbuffer, nwords); } else { esp32_spi_poll_exchange(priv, txbuffer, rxbuffer, nwords); } } #ifndef CONFIG_SPI_EXCHANGE /**************************************************************************** * Name: esp32_spi_sndblock * * Description: * Send a block of data on SPI. * * Input Parameters: * dev - Device-specific state data * buffer - A pointer to the buffer of data to be sent * nwords - the length of data to send from the buffer in number of words. * The wordsize is determined by the number of bits-per-word * selected for the SPI interface. If nbits <= 8, the data is * packed into uint8_t's; if nbits >8, the data is packed into * uint16_t's * * Returned Value: * None * ****************************************************************************/ static void esp32_spi_sndblock(struct spi_dev_s *dev, const void *txbuffer, size_t nwords) { spiinfo("txbuffer=%p nwords=%d\n", txbuffer, nwords); esp32_spi_exchange(dev, txbuffer, NULL, nwords); } /**************************************************************************** * Name: esp32_spi_recvblock * * Description: * Receive a block of data from SPI. * * Input Parameters: * dev - Device-specific state data * buffer - A pointer to the buffer in which to receive data * nwords - the length of data that can be received in the buffer in number * of words. The wordsize is determined by the number of bits- * per-word selected for the SPI interface. If nbits <= 8, the * data is packed into uint8_t's; if nbits >8, the data is packed * into uint16_t's * * Returned Value: * None * ****************************************************************************/ static void esp32_spi_recvblock(struct spi_dev_s *dev, void *rxbuffer, size_t nwords) { spiinfo("rxbuffer=%p nwords=%d\n", rxbuffer, nwords); esp32_spi_exchange(dev, NULL, rxbuffer, nwords); } #endif /**************************************************************************** * Name: esp32_spi_trigger * * Description: * Trigger a previously configured DMA transfer. * * Input Parameters: * dev - Device-specific state data * * Returned Value: * OK - Trigger was fired * -ENOSYS - Trigger not fired due to lack of DMA or low level support * -EIO - Trigger not fired because not previously primed * ****************************************************************************/ #ifdef CONFIG_SPI_TRIGGER static int esp32_spi_trigger(struct spi_dev_s *dev) { return -ENOSYS; } #endif /**************************************************************************** * Name: esp32_spi_init * * Description: * Initialize ESP32 SPI hardware interface * * Input Parameters: * dev - Device-specific state data * * Returned Value: * None * ****************************************************************************/ static void esp32_spi_init(struct spi_dev_s *dev) { struct esp32_spi_priv_s *priv = (struct esp32_spi_priv_s *)dev; const struct esp32_spi_config_s *config = priv->config; uint32_t regval; /* Initialize the SPI semaphore that enforces mutually exclusive access */ nxsem_init(&priv->exclsem, 0, 1); esp32_gpiowrite(config->cs_pin, 1); esp32_gpiowrite(config->mosi_pin, 1); esp32_gpiowrite(config->miso_pin, 1); esp32_gpiowrite(config->clk_pin, 1); #ifdef CONFIG_ESP32_SPI_SWCS esp32_configgpio(config->cs_pin, OUTPUT); esp32_gpio_matrix_out(config->cs_pin, SIG_GPIO_OUT_IDX, 0, 0); #endif if (esp32_spi_iomux(priv)) { #ifndef CONFIG_ESP32_SPI_SWCS esp32_configgpio(config->cs_pin, OUTPUT_FUNCTION_2); esp32_gpio_matrix_out(config->cs_pin, SIG_GPIO_OUT_IDX, 0, 0); #endif esp32_configgpio(config->mosi_pin, OUTPUT_FUNCTION_2); esp32_gpio_matrix_out(config->mosi_pin, SIG_GPIO_OUT_IDX, 0, 0); esp32_configgpio(config->miso_pin, INPUT_FUNCTION_2 | PULLUP); esp32_gpio_matrix_out(config->miso_pin, SIG_GPIO_OUT_IDX, 0, 0); esp32_configgpio(config->clk_pin, OUTPUT_FUNCTION_2); esp32_gpio_matrix_out(config->clk_pin, SIG_GPIO_OUT_IDX, 0, 0); } else { #ifndef CONFIG_ESP32_SPI_SWCS esp32_configgpio(config->cs_pin, OUTPUT_FUNCTION_3); esp32_gpio_matrix_out(config->cs_pin, config->cs_outsig, 0, 0); #endif esp32_configgpio(config->mosi_pin, OUTPUT_FUNCTION_3); esp32_gpio_matrix_out(config->mosi_pin, config->mosi_outsig, 0, 0); esp32_configgpio(config->miso_pin, INPUT_FUNCTION_3 | PULLUP); esp32_gpio_matrix_in(config->miso_pin, config->miso_insig, 0); esp32_configgpio(config->clk_pin, OUTPUT_FUNCTION_3); esp32_gpio_matrix_out(config->clk_pin, config->clk_outsig, 0, 0); } modifyreg32(DPORT_PERIP_CLK_EN_REG, 0, config->clk_bit); modifyreg32(DPORT_PERIP_RST_EN_REG, config->rst_bit, 0); regval = SPI_DOUTDIN_M | SPI_USR_MISO_M | SPI_USR_MOSI_M | SPI_CS_HOLD_M; putreg32(regval, SPI_USER_REG(config->id)); putreg32(0, SPI_USER1_REG(config->id)); putreg32(0, SPI_SLAVE_REG(config->id)); putreg32(SPI_CS1_DIS_M | SPI_CS2_DIS_M, SPI_PIN_REG(config->id)); #ifdef CONFIG_ESP32_SPI_SWCS esp32_spi_set_regbits(SPI_PIN_REG(config->id), SPI_CS0_DIS_M); #endif putreg32(0, SPI_CTRL_REG(config->id)); putreg32(VALUE_MASK(0, SPI_HOLD_TIME), SPI_CTRL2_REG(config->id)); if (config->use_dma) { nxsem_init(&priv->sem_isr, 0, 0); nxsem_set_protocol(&priv->sem_isr, SEM_PRIO_NONE); modifyreg32(DPORT_PERIP_CLK_EN_REG, 0, config->dma_clk_bit); modifyreg32(DPORT_PERIP_RST_EN_REG, config->dma_rst_bit, 0); modifyreg32(DPORT_SPI_DMA_CHAN_SEL_REG, 0, (config->dma_chan << config->dma_chan_s)); regval = SPI_OUT_DATA_BURST_EN_M | SPI_INDSCR_BURST_EN_M | SPI_OUTDSCR_BURST_EN_M; putreg32(regval, SPI_DMA_CONF_REG(config->id)); } esp32_spi_setfrequency(dev, config->clk_freq); esp32_spi_setbits(dev, 8); esp32_spi_setmode(dev, config->mode); } /**************************************************************************** * Name: esp32_spi_deinit * * Description: * Deinitialize ESP32 SPI hardware interface * * Input Parameters: * dev - Device-specific state data * * Returned Value: * None * ****************************************************************************/ static void esp32_spi_deinit(struct spi_dev_s *dev) { struct esp32_spi_priv_s *priv = (struct esp32_spi_priv_s *)dev; if (priv->config->use_dma) { modifyreg32(DPORT_PERIP_CLK_EN_REG, priv->config->dma_clk_bit, 0); } modifyreg32(DPORT_PERIP_RST_EN_REG, 0, priv->config->clk_bit); modifyreg32(DPORT_PERIP_CLK_EN_REG, priv->config->clk_bit, 0); priv->frequency = 0; priv->actual = 0; priv->mode = SPIDEV_MODE0; priv->nbits = 0; } /**************************************************************************** * Name: esp32_spi_interrupt * * Description: * Common SPI DMA interrupt handler * * Input Parameters: * arg - SPI controller private data * * Returned Value: * Standard interrupt return value. * ****************************************************************************/ static int esp32_spi_interrupt(int irq, void *context, void *arg) { struct esp32_spi_priv_s *priv = (struct esp32_spi_priv_s *)arg; esp32_spi_reset_regbits(SPI_SLAVE_REG(priv->config->id), SPI_TRANS_DONE_M); nxsem_post(&priv->sem_isr); return 0; } /**************************************************************************** * Name: esp32_spibus_initialize * * Description: * Initialize the selected SPI bus * * Input Parameters: * Port number (for hardware that has multiple SPI interfaces) * * Returned Value: * Valid SPI device structure reference on success; a NULL on failure * ****************************************************************************/ struct spi_dev_s *esp32_spibus_initialize(int port) { int ret; struct spi_dev_s *spi_dev; struct esp32_spi_priv_s *priv; irqstate_t flags; switch (port) { #ifdef CONFIG_ESP32_SPI2 case ESP32_SPI2: priv = &esp32_spi2_priv; break; #endif #ifdef CONFIG_ESP32_SPI3 case ESP32_SPI3: priv = &esp32_spi3_priv; break; #endif default: return NULL; } flags = spin_lock_irqsave(&priv->lock); spi_dev = (struct spi_dev_s *)priv; if ((volatile int)priv->refs != 0) { spin_unlock_irqrestore(&priv->lock, flags); return spi_dev; } if (priv->config->use_dma) { /* Set up to receive peripheral interrupts on the current CPU */ priv->cpu = up_cpu_index(); priv->cpuint = esp32_setup_irq(priv->cpu, priv->config->periph, 1, ESP32_CPUINT_LEVEL); if (priv->cpuint < 0) { spin_unlock_irqrestore(&priv->lock, flags); return NULL; } ret = irq_attach(priv->config->irq, esp32_spi_interrupt, priv); if (ret != OK) { esp32_teardown_irq(priv->cpu, priv->config->periph, priv->cpuint); spin_unlock_irqrestore(&priv->lock, flags); return NULL; } up_enable_irq(priv->config->irq); } esp32_spi_init(spi_dev); priv->refs++; spin_unlock_irqrestore(&priv->lock, flags); return spi_dev; } /**************************************************************************** * Name: esp32_spibus_uninitialize * * Description: * Uninitialize an SPI bus * ****************************************************************************/ int esp32_spibus_uninitialize(struct spi_dev_s *dev) { irqstate_t flags; struct esp32_spi_priv_s *priv = (struct esp32_spi_priv_s *)dev; DEBUGASSERT(dev); if (priv->refs == 0) { return ERROR; } flags = spin_lock_irqsave(&priv->lock); if (--priv->refs != 0) { spin_unlock_irqrestore(&priv->lock, flags); return OK; } spin_unlock_irqrestore(&priv->lock, flags); if (priv->config->use_dma) { up_disable_irq(priv->config->irq); esp32_teardown_irq(priv->cpu, priv->config->periph, priv->cpuint); nxsem_destroy(&priv->sem_isr); } esp32_spi_deinit(dev); nxsem_destroy(&priv->exclsem); return OK; } #endif /* CONFIG_ESP32_SPI */