| /* |
| * Copyright (c) 2020 Espressif Systems (Shanghai) Co., Ltd. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT espressif_esp32_spi |
| |
| /* Include esp-idf headers first to avoid redefining BIT() macro */ |
| #include <hal/spi_hal.h> |
| #include <esp_attr.h> |
| #include <esp_clk_tree.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(esp32_spi, CONFIG_SPI_LOG_LEVEL); |
| |
| #include <soc.h> |
| #include <esp_memory_utils.h> |
| #include <zephyr/drivers/spi.h> |
| #include <zephyr/drivers/spi/rtio.h> |
| #if defined(CONFIG_SOC_SERIES_ESP32C2) || defined(CONFIG_SOC_SERIES_ESP32C3) || \ |
| defined(CONFIG_SOC_SERIES_ESP32C6) |
| #include <zephyr/drivers/interrupt_controller/intc_esp32c3.h> |
| #else |
| #include <zephyr/drivers/interrupt_controller/intc_esp32.h> |
| #endif |
| #ifdef SOC_GDMA_SUPPORTED |
| #include <hal/gdma_hal.h> |
| #include <hal/gdma_ll.h> |
| #endif |
| #include <zephyr/drivers/clock_control.h> |
| #include "spi_context.h" |
| #include "spi_esp32_spim.h" |
| |
| #if defined(CONFIG_SOC_SERIES_ESP32C2) || defined(CONFIG_SOC_SERIES_ESP32C3) || \ |
| defined(CONFIG_SOC_SERIES_ESP32C6) |
| #define ISR_HANDLER isr_handler_t |
| #else |
| #define ISR_HANDLER intr_handler_t |
| #endif |
| |
| #define SPI_DMA_MAX_BUFFER_SIZE 4092 |
| |
| static bool spi_esp32_transfer_ongoing(struct spi_esp32_data *data) |
| { |
| return spi_context_tx_on(&data->ctx) || spi_context_rx_on(&data->ctx); |
| } |
| |
| static inline void spi_esp32_complete(const struct device *dev, |
| struct spi_esp32_data *data, |
| spi_dev_t *spi, int status) |
| { |
| #ifdef CONFIG_SPI_ESP32_INTERRUPT |
| spi_ll_disable_int(spi); |
| spi_ll_clear_int_stat(spi); |
| #endif |
| |
| spi_context_cs_control(&data->ctx, false); |
| |
| #ifdef CONFIG_SPI_ESP32_INTERRUPT |
| spi_context_complete(&data->ctx, dev, status); |
| #endif |
| |
| } |
| |
| static int IRAM_ATTR spi_esp32_transfer(const struct device *dev) |
| { |
| struct spi_esp32_data *data = dev->data; |
| const struct spi_esp32_config *cfg = dev->config; |
| struct spi_context *ctx = &data->ctx; |
| spi_hal_context_t *hal = &data->hal; |
| spi_hal_dev_config_t *hal_dev = &data->dev_config; |
| spi_hal_trans_config_t *hal_trans = &data->trans_config; |
| size_t chunk_len_bytes = spi_context_max_continuous_chunk(&data->ctx) * data->dfs; |
| size_t max_buf_sz = |
| cfg->dma_enabled ? SPI_DMA_MAX_BUFFER_SIZE : SOC_SPI_MAXIMUM_BUFFER_SIZE; |
| size_t transfer_len_bytes = MIN(chunk_len_bytes, max_buf_sz); |
| size_t transfer_len_frames = transfer_len_bytes / data->dfs; |
| size_t bit_len = transfer_len_bytes << 3; |
| uint8_t *rx_temp = NULL; |
| uint8_t *tx_temp = NULL; |
| uint8_t dma_len_tx = MIN(ctx->tx_len * data->dfs, SPI_DMA_MAX_BUFFER_SIZE); |
| uint8_t dma_len_rx = MIN(ctx->rx_len * data->dfs, SPI_DMA_MAX_BUFFER_SIZE); |
| |
| if (cfg->dma_enabled) { |
| /* bit_len needs to be at least one byte long when using DMA */ |
| bit_len = !bit_len ? 8 : bit_len; |
| if (ctx->tx_buf && !esp_ptr_dma_capable((uint32_t *)&ctx->tx_buf[0])) { |
| LOG_DBG("Tx buffer not DMA capable"); |
| tx_temp = k_malloc(dma_len_tx); |
| if (!tx_temp) { |
| LOG_ERR("Error allocating temp buffer Tx"); |
| return -ENOMEM; |
| } |
| memcpy(tx_temp, &ctx->tx_buf[0], dma_len_tx); |
| } |
| if (ctx->rx_buf && (!esp_ptr_dma_capable((uint32_t *)&ctx->rx_buf[0]) || |
| ((int)&ctx->rx_buf[0] % 4 != 0) || (dma_len_rx % 4 != 0))) { |
| /* The rx buffer need to be length of |
| * multiples of 32 bits to avoid heap |
| * corruption. |
| */ |
| LOG_DBG("Rx buffer not DMA capable"); |
| rx_temp = k_calloc(((dma_len_rx << 3) + 31) / 8, sizeof(uint8_t)); |
| if (!rx_temp) { |
| LOG_ERR("Error allocating temp buffer Rx"); |
| k_free(tx_temp); |
| return -ENOMEM; |
| } |
| } |
| } |
| |
| /* clean up and prepare SPI hal */ |
| memset((uint32_t *)hal->hw->data_buf, 0, sizeof(hal->hw->data_buf)); |
| hal_trans->send_buffer = tx_temp ? tx_temp : (uint8_t *)ctx->tx_buf; |
| hal_trans->rcv_buffer = rx_temp ? rx_temp : ctx->rx_buf; |
| hal_trans->tx_bitlen = bit_len; |
| hal_trans->rx_bitlen = bit_len; |
| |
| /* keep cs line active until last transmission */ |
| hal_trans->cs_keep_active = |
| (!ctx->num_cs_gpios && |
| (ctx->rx_count > 1 || ctx->tx_count > 1 || ctx->rx_len > transfer_len_frames || |
| ctx->tx_len > transfer_len_frames)); |
| |
| /* configure SPI */ |
| spi_hal_setup_trans(hal, hal_dev, hal_trans); |
| spi_hal_prepare_data(hal, hal_dev, hal_trans); |
| |
| /* send data */ |
| spi_hal_user_start(hal); |
| spi_context_update_tx(&data->ctx, data->dfs, transfer_len_frames); |
| |
| while (!spi_hal_usr_is_done(hal)) { |
| /* nop */ |
| } |
| |
| /* read data */ |
| spi_hal_fetch_result(hal); |
| |
| if (rx_temp) { |
| memcpy(&ctx->rx_buf[0], rx_temp, transfer_len_bytes); |
| } |
| |
| spi_context_update_rx(&data->ctx, data->dfs, transfer_len_frames); |
| |
| k_free(tx_temp); |
| k_free(rx_temp); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_SPI_ESP32_INTERRUPT |
| static void IRAM_ATTR spi_esp32_isr(void *arg) |
| { |
| const struct device *dev = (const struct device *)arg; |
| const struct spi_esp32_config *cfg = dev->config; |
| struct spi_esp32_data *data = dev->data; |
| |
| do { |
| spi_esp32_transfer(dev); |
| } while (spi_esp32_transfer_ongoing(data)); |
| |
| spi_esp32_complete(dev, data, cfg->spi, 0); |
| } |
| #endif |
| |
| static int spi_esp32_init_dma(const struct device *dev) |
| { |
| const struct spi_esp32_config *cfg = dev->config; |
| struct spi_esp32_data *data = dev->data; |
| uint8_t channel_offset; |
| |
| if (clock_control_on(cfg->clock_dev, (clock_control_subsys_t)cfg->dma_clk_src)) { |
| LOG_ERR("Could not enable DMA clock"); |
| return -EIO; |
| } |
| |
| #ifdef SOC_GDMA_SUPPORTED |
| gdma_hal_init(&data->hal_gdma, 0); |
| gdma_ll_enable_clock(data->hal_gdma.dev, true); |
| gdma_ll_tx_reset_channel(data->hal_gdma.dev, cfg->dma_host); |
| gdma_ll_rx_reset_channel(data->hal_gdma.dev, cfg->dma_host); |
| gdma_ll_tx_connect_to_periph(data->hal_gdma.dev, cfg->dma_host, GDMA_TRIG_PERIPH_SPI, |
| cfg->dma_host); |
| gdma_ll_rx_connect_to_periph(data->hal_gdma.dev, cfg->dma_host, GDMA_TRIG_PERIPH_SPI, |
| cfg->dma_host); |
| channel_offset = 0; |
| #else |
| channel_offset = 1; |
| #endif /* SOC_GDMA_SUPPORTED */ |
| #ifdef CONFIG_SOC_SERIES_ESP32 |
| /*Connect SPI and DMA*/ |
| DPORT_SET_PERI_REG_BITS(DPORT_SPI_DMA_CHAN_SEL_REG, 3, cfg->dma_host + 1, |
| ((cfg->dma_host + 1) * 2)); |
| #endif /* CONFIG_SOC_SERIES_ESP32 */ |
| |
| data->hal_config.dma_in = (spi_dma_dev_t *)cfg->spi; |
| data->hal_config.dma_out = (spi_dma_dev_t *)cfg->spi; |
| data->hal_config.dma_enabled = true; |
| data->hal_config.tx_dma_chan = cfg->dma_host + channel_offset; |
| data->hal_config.rx_dma_chan = cfg->dma_host + channel_offset; |
| data->hal_config.dmadesc_n = 1; |
| data->hal_config.dmadesc_rx = &data->dma_desc_rx; |
| data->hal_config.dmadesc_tx = &data->dma_desc_tx; |
| |
| if (data->hal_config.dmadesc_tx == NULL || data->hal_config.dmadesc_rx == NULL) { |
| k_free(data->hal_config.dmadesc_tx); |
| k_free(data->hal_config.dmadesc_rx); |
| return -ENOMEM; |
| } |
| |
| spi_hal_init(&data->hal, cfg->dma_host + 1, &data->hal_config); |
| return 0; |
| } |
| |
| static int spi_esp32_init(const struct device *dev) |
| { |
| int err; |
| const struct spi_esp32_config *cfg = dev->config; |
| struct spi_esp32_data *data = dev->data; |
| spi_hal_context_t *hal = &data->hal; |
| |
| if (!cfg->clock_dev) { |
| return -EINVAL; |
| } |
| |
| if (!device_is_ready(cfg->clock_dev)) { |
| LOG_ERR("clock control device not ready"); |
| return -ENODEV; |
| } |
| |
| /* Enables SPI peripheral */ |
| err = clock_control_on(cfg->clock_dev, cfg->clock_subsys); |
| if (err < 0) { |
| LOG_ERR("Error enabling SPI clock"); |
| return err; |
| } |
| |
| spi_ll_master_init(hal->hw); |
| |
| if (cfg->dma_enabled) { |
| spi_esp32_init_dma(dev); |
| } |
| |
| #ifdef CONFIG_SPI_ESP32_INTERRUPT |
| spi_ll_disable_int(cfg->spi); |
| spi_ll_clear_int_stat(cfg->spi); |
| |
| err = esp_intr_alloc(cfg->irq_source, |
| ESP_PRIO_TO_FLAGS(cfg->irq_priority) | |
| ESP_INT_FLAGS_CHECK(cfg->irq_flags) | ESP_INTR_FLAG_IRAM, |
| (ISR_HANDLER)spi_esp32_isr, |
| (void *)dev, |
| NULL); |
| |
| if (err != 0) { |
| LOG_ERR("could not allocate interrupt (err %d)", err); |
| return err; |
| } |
| #endif |
| |
| err = spi_context_cs_configure_all(&data->ctx); |
| if (err < 0) { |
| return err; |
| } |
| |
| err = esp_clk_tree_src_get_freq_hz( |
| cfg->clock_source, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &data->clock_source_hz); |
| if (err) { |
| LOG_ERR("Could not get clock source frequency (%d)", err); |
| return err; |
| } |
| |
| spi_context_unlock_unconditionally(&data->ctx); |
| |
| return 0; |
| } |
| |
| static inline uint8_t spi_esp32_get_line_mode(uint16_t operation) |
| { |
| if (IS_ENABLED(CONFIG_SPI_EXTENDED_MODES)) { |
| switch (operation & SPI_LINES_MASK) { |
| case SPI_LINES_SINGLE: |
| return 1; |
| case SPI_LINES_DUAL: |
| return 2; |
| case SPI_LINES_OCTAL: |
| return 8; |
| case SPI_LINES_QUAD: |
| return 4; |
| default: |
| break; |
| } |
| } |
| |
| return 1; |
| } |
| |
| static int IRAM_ATTR spi_esp32_configure(const struct device *dev, |
| const struct spi_config *spi_cfg) |
| { |
| const struct spi_esp32_config *cfg = dev->config; |
| struct spi_esp32_data *data = dev->data; |
| struct spi_context *ctx = &data->ctx; |
| spi_hal_context_t *hal = &data->hal; |
| spi_hal_dev_config_t *hal_dev = &data->dev_config; |
| int freq; |
| |
| if (spi_context_configured(ctx, spi_cfg)) { |
| return 0; |
| } |
| |
| ctx->config = spi_cfg; |
| |
| if (spi_cfg->operation & SPI_HALF_DUPLEX) { |
| LOG_ERR("Half-duplex not supported"); |
| return -ENOTSUP; |
| } |
| |
| if (spi_cfg->operation & SPI_OP_MODE_SLAVE) { |
| LOG_ERR("Slave mode not supported"); |
| return -ENOTSUP; |
| } |
| |
| if (spi_cfg->operation & SPI_MODE_LOOP) { |
| LOG_ERR("Loopback mode is not supported"); |
| return -ENOTSUP; |
| } |
| |
| hal_dev->cs_pin_id = ctx->config->slave; |
| int ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); |
| |
| if (ret) { |
| LOG_ERR("Failed to configure SPI pins"); |
| return ret; |
| } |
| |
| /* input parameters to calculate timing configuration */ |
| spi_hal_timing_param_t timing_param = { |
| .half_duplex = hal_dev->half_duplex, |
| .no_compensate = hal_dev->no_compensate, |
| .expected_freq = spi_cfg->frequency, |
| .duty_cycle = cfg->duty_cycle == 0 ? 128 : cfg->duty_cycle, |
| .input_delay_ns = cfg->input_delay_ns, |
| .use_gpio = !cfg->use_iomux, |
| .clk_src_hz = data->clock_source_hz, |
| }; |
| |
| spi_hal_cal_clock_conf(&timing_param, &freq, &hal_dev->timing_conf); |
| |
| data->trans_config.dummy_bits = hal_dev->timing_conf.timing_dummy; |
| |
| hal_dev->tx_lsbfirst = spi_cfg->operation & SPI_TRANSFER_LSB ? 1 : 0; |
| hal_dev->rx_lsbfirst = spi_cfg->operation & SPI_TRANSFER_LSB ? 1 : 0; |
| |
| data->trans_config.line_mode.data_lines = spi_esp32_get_line_mode(spi_cfg->operation); |
| |
| /* multiline for command and address not supported */ |
| data->trans_config.line_mode.addr_lines = 1; |
| data->trans_config.line_mode.cmd_lines = 1; |
| |
| /* SPI mode */ |
| hal_dev->mode = 0; |
| if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPHA) { |
| hal_dev->mode = BIT(0); |
| } |
| if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPOL) { |
| hal_dev->mode |= BIT(1); |
| } |
| |
| /* Chip select setup and hold times */ |
| /* GPIO CS have their own delay parameter*/ |
| if (!spi_cs_is_gpio(spi_cfg)) { |
| hal_dev->cs_hold = cfg->cs_hold; |
| hal_dev->cs_setup = cfg->cs_setup; |
| } |
| |
| spi_hal_setup_device(hal, hal_dev); |
| |
| /* Workaround to handle default state of MISO and MOSI lines */ |
| #ifndef CONFIG_SOC_SERIES_ESP32 |
| spi_dev_t *hw = hal->hw; |
| |
| if (cfg->line_idle_low) { |
| hw->ctrl.d_pol = 0; |
| hw->ctrl.q_pol = 0; |
| } else { |
| hw->ctrl.d_pol = 1; |
| hw->ctrl.q_pol = 1; |
| } |
| #endif |
| |
| /* |
| * Workaround for ESP32S3 and ESP32Cx SoC's. This dummy transaction is needed |
| * to sync CLK and software controlled CS when SPI is in mode 3 |
| */ |
| #if defined(CONFIG_SOC_SERIES_ESP32S3) || defined(CONFIG_SOC_SERIES_ESP32C2) || \ |
| defined(CONFIG_SOC_SERIES_ESP32C3) || defined(CONFIG_SOC_SERIES_ESP32C6) |
| if (ctx->num_cs_gpios && (hal_dev->mode & (SPI_MODE_CPOL | SPI_MODE_CPHA))) { |
| spi_esp32_transfer(dev); |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| static inline uint8_t spi_esp32_get_frame_size(const struct spi_config *spi_cfg) |
| { |
| uint8_t dfs = SPI_WORD_SIZE_GET(spi_cfg->operation); |
| |
| dfs /= 8; |
| if ((dfs == 0) || (dfs > 4)) { |
| LOG_WRN("Unsupported dfs, 1-byte size will be used"); |
| dfs = 1; |
| } |
| return dfs; |
| } |
| |
| static int transceive(const struct device *dev, |
| const struct spi_config *spi_cfg, |
| const struct spi_buf_set *tx_bufs, |
| const struct spi_buf_set *rx_bufs, bool asynchronous, |
| spi_callback_t cb, |
| void *userdata) |
| { |
| const struct spi_esp32_config *cfg = dev->config; |
| struct spi_esp32_data *data = dev->data; |
| int ret; |
| |
| if (!tx_bufs && !rx_bufs) { |
| return 0; |
| } |
| |
| #ifndef CONFIG_SPI_ESP32_INTERRUPT |
| if (asynchronous) { |
| return -ENOTSUP; |
| } |
| #endif |
| |
| spi_context_lock(&data->ctx, asynchronous, cb, userdata, spi_cfg); |
| |
| data->dfs = spi_esp32_get_frame_size(spi_cfg); |
| |
| ret = spi_esp32_configure(dev, spi_cfg); |
| if (ret) { |
| goto done; |
| } |
| |
| spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, data->dfs); |
| |
| spi_context_cs_control(&data->ctx, true); |
| |
| #ifdef CONFIG_SPI_ESP32_INTERRUPT |
| spi_ll_enable_int(cfg->spi); |
| spi_ll_set_int_stat(cfg->spi); |
| #else |
| |
| do { |
| spi_esp32_transfer(dev); |
| } while (spi_esp32_transfer_ongoing(data)); |
| |
| spi_esp32_complete(dev, data, cfg->spi, 0); |
| |
| #endif /* CONFIG_SPI_ESP32_INTERRUPT */ |
| |
| done: |
| spi_context_release(&data->ctx, ret); |
| |
| return ret; |
| } |
| |
| static int spi_esp32_transceive(const struct device *dev, |
| const struct spi_config *spi_cfg, |
| const struct spi_buf_set *tx_bufs, |
| const struct spi_buf_set *rx_bufs) |
| { |
| return transceive(dev, spi_cfg, tx_bufs, rx_bufs, false, NULL, NULL); |
| } |
| |
| #ifdef CONFIG_SPI_ASYNC |
| static int spi_esp32_transceive_async(const struct device *dev, |
| const struct spi_config *spi_cfg, |
| const struct spi_buf_set *tx_bufs, |
| const struct spi_buf_set *rx_bufs, |
| spi_callback_t cb, |
| void *userdata) |
| { |
| return transceive(dev, spi_cfg, tx_bufs, rx_bufs, true, cb, userdata); |
| } |
| #endif /* CONFIG_SPI_ASYNC */ |
| |
| static int spi_esp32_release(const struct device *dev, |
| const struct spi_config *config) |
| { |
| struct spi_esp32_data *data = dev->data; |
| |
| spi_context_unlock_unconditionally(&data->ctx); |
| |
| return 0; |
| } |
| |
| static const struct spi_driver_api spi_api = { |
| .transceive = spi_esp32_transceive, |
| #ifdef CONFIG_SPI_ASYNC |
| .transceive_async = spi_esp32_transceive_async, |
| #endif |
| #ifdef CONFIG_SPI_RTIO |
| .iodev_submit = spi_rtio_iodev_default_submit, |
| #endif |
| .release = spi_esp32_release |
| }; |
| |
| #ifdef CONFIG_SOC_SERIES_ESP32 |
| #define GET_AS_CS(idx) .as_cs = DT_INST_PROP(idx, clk_as_cs), |
| #else |
| #define GET_AS_CS(idx) |
| #endif |
| |
| #define ESP32_SPI_INIT(idx) \ |
| \ |
| PINCTRL_DT_INST_DEFINE(idx); \ |
| \ |
| static struct spi_esp32_data spi_data_##idx = { \ |
| SPI_CONTEXT_INIT_LOCK(spi_data_##idx, ctx), \ |
| SPI_CONTEXT_INIT_SYNC(spi_data_##idx, ctx), \ |
| SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(idx), ctx) \ |
| .hal = { \ |
| .hw = (spi_dev_t *)DT_INST_REG_ADDR(idx), \ |
| }, \ |
| .dev_config = { \ |
| .half_duplex = DT_INST_PROP(idx, half_duplex), \ |
| GET_AS_CS(idx) \ |
| .positive_cs = DT_INST_PROP(idx, positive_cs), \ |
| .no_compensate = DT_INST_PROP(idx, dummy_comp), \ |
| .sio = DT_INST_PROP(idx, sio) \ |
| } \ |
| }; \ |
| \ |
| static const struct spi_esp32_config spi_config_##idx = { \ |
| .spi = (spi_dev_t *)DT_INST_REG_ADDR(idx), \ |
| \ |
| .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(idx)), \ |
| .duty_cycle = 0, \ |
| .input_delay_ns = 0, \ |
| .irq_source = DT_INST_IRQ_BY_IDX(idx, 0, irq), \ |
| .irq_priority = DT_INST_IRQ_BY_IDX(idx, 0, priority), \ |
| .irq_flags = DT_INST_IRQ_BY_IDX(idx, 0, flags), \ |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(idx), \ |
| .clock_subsys = \ |
| (clock_control_subsys_t)DT_INST_CLOCKS_CELL(idx, offset), \ |
| .use_iomux = DT_INST_PROP(idx, use_iomux), \ |
| .dma_enabled = DT_INST_PROP(idx, dma_enabled), \ |
| .dma_clk_src = DT_INST_PROP(idx, dma_clk), \ |
| .dma_host = DT_INST_PROP(idx, dma_host), \ |
| .cs_setup = DT_INST_PROP_OR(idx, cs_setup_time, 0), \ |
| .cs_hold = DT_INST_PROP_OR(idx, cs_hold_time, 0), \ |
| .line_idle_low = DT_INST_PROP(idx, line_idle_low), \ |
| .clock_source = SPI_CLK_SRC_DEFAULT, \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(idx, spi_esp32_init, \ |
| NULL, &spi_data_##idx, \ |
| &spi_config_##idx, POST_KERNEL, \ |
| CONFIG_SPI_INIT_PRIORITY, &spi_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(ESP32_SPI_INIT) |