blob: 360d996a8fc881285305f65ea87f8342e1a67bf3 [file] [log] [blame]
/*
* 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)