diff --git a/boards/xtensa/esp32/esp32.dts b/boards/xtensa/esp32/esp32.dts index 4e9c0bfe2ba..87d5405dc41 100644 --- a/boards/xtensa/esp32/esp32.dts +++ b/boards/xtensa/esp32/esp32.dts @@ -71,6 +71,26 @@ scl-pin = <5>; }; +&spi2 { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + miso-pin = <12>; + mosi-pin = <13>; + sclk-pin = <14>; + csel-pin = <15>; +}; + +&spi3 { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + miso-pin = <19>; + mosi-pin = <23>; + sclk-pin = <18>; + csel-pin = <5>; +}; + &trng0 { status = "okay"; }; diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt index 00c85fbcbb4..4e21b923737 100644 --- a/drivers/spi/CMakeLists.txt +++ b/drivers/spi/CMakeLists.txt @@ -21,5 +21,6 @@ zephyr_library_sources_ifdef(CONFIG_SPI_OC_SIMPLE spi_oc_simple.c) zephyr_library_sources_ifdef(CONFIG_SPI_XEC_QMSPI spi_xec_qmspi.c) zephyr_library_sources_ifdef(CONFIG_SPI_GECKO spi_gecko.c) zephyr_library_sources_ifdef(CONFIG_SPI_XLNX_AXI_QUADSPI spi_xlnx_axi_quadspi.c) +zephyr_library_sources_ifdef(CONFIG_ESP32_SPIM spi_esp32_spim.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE spi_handlers.c) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index fe259520be4..d9ba8088f6c 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -223,4 +223,6 @@ source "drivers/spi/Kconfig.gecko" source "drivers/spi/Kconfig.xlnx" +source "drivers/spi/Kconfig.esp32" + endif # SPI diff --git a/drivers/spi/Kconfig.esp32 b/drivers/spi/Kconfig.esp32 new file mode 100644 index 00000000000..f3a78a22d7f --- /dev/null +++ b/drivers/spi/Kconfig.esp32 @@ -0,0 +1,20 @@ +# ESP32 SPI configuration + +# Copyright (c) 2020 Espressif Systems (Shanghai) Co., Ltd. +# SPDX-License-Identifier: Apache-2.0 + +menuconfig ESP32_SPIM + bool "ESP32 SPI Master driver" + depends on SOC_ESP32 + default y + help + Enables support for ESP32 SPI Master driver. + +if ESP32_SPIM + +config SPI_ESP32_INTERRUPT + bool "ESP32 SPI interrupt mode" + help + Enables interrupt support for ESP32 SPI driver. + +endif # ESP32_SPIM diff --git a/drivers/spi/spi_esp32_spim.c b/drivers/spi/spi_esp32_spim.c new file mode 100644 index 00000000000..b81ffcafe79 --- /dev/null +++ b/drivers/spi/spi_esp32_spim.c @@ -0,0 +1,436 @@ +/* + * 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 +#include + +#include +LOG_MODULE_REGISTER(esp32_spi, CONFIG_SPI_LOG_LEVEL); + +#include +#include +#include +#include +#include "spi_context.h" +#include "spi_esp32_spim.h" + +/* pins, signals and interrupts shall be placed into dts */ +#define MISO_IDX_2 HSPIQ_IN_IDX +#define MISO_IDX_3 VSPIQ_IN_IDX +#define MOSI_IDX_2 HSPID_OUT_IDX +#define MOSI_IDX_3 VSPID_OUT_IDX +#define SCLK_IDX_2 HSPICLK_OUT_IDX +#define SCLK_IDX_3 VSPICLK_OUT_IDX +#define CSEL_IDX_2 HSPICS0_OUT_IDX +#define CSEL_IDX_3 VSPICS0_OUT_IDX + +#define INST_2_ESPRESSIF_ESP32_SPI_IRQ_0 13 +#define INST_3_ESPRESSIF_ESP32_SPI_IRQ_0 17 + +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(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, status); +#endif + +} + +static int IRAM_ATTR spi_esp32_transfer(const struct device *dev) +{ + struct spi_esp32_data *data = dev->data; + struct spi_context *ctx = &data->ctx; + spi_hal_context_t *hal = &data->hal; + size_t chunk_len = spi_context_max_continuous_chunk(&data->ctx); + + /* clean up and prepare SPI hal */ + memset((uint32_t *) hal->hw->data_buf, 0, sizeof(hal->hw->data_buf)); + hal->send_buffer = (uint8_t *) ctx->tx_buf; + hal->rcv_buffer = ctx->rx_buf; + hal->tx_bitlen = chunk_len << 3; + hal->rx_bitlen = chunk_len << 3; + + /* configure SPI */ + spi_hal_setup_trans(hal); + spi_hal_prepare_data(hal); + + /* send data */ + spi_hal_user_start(hal); + spi_context_update_tx(&data->ctx, data->dfs, chunk_len); + + while (!spi_hal_usr_is_done(hal)) { + /* nop */ + } + + /* read data */ + spi_hal_fetch_result(hal); + spi_context_update_rx(&data->ctx, data->dfs, chunk_len); + + return 0; +} + +static int spi_esp32_init(const struct device *dev) +{ + const struct spi_esp32_config *cfg = dev->config; + struct spi_esp32_data *data = dev->data; + + if (!cfg->clock_dev) { + return -EINVAL; + } + +#ifdef CONFIG_SPI_ESP32_INTERRUPT + cfg->irq_config_func(dev); +#endif + + spi_context_unlock_unconditionally(&data->ctx); + + return 0; +} + +static int spi_esp32_configure_pin(gpio_pin_t pin, int pin_sig, + gpio_flags_t pin_mode) +{ + const char *device_name = gpio_esp32_get_gpio_for_pin(pin); + const struct device *gpio; + int ret; + + if (!device_name) { + LOG_ERR("Could not find GPIO node on devicetree"); + return -EINVAL; + } + + gpio = device_get_binding(device_name); + if (!gpio) { + LOG_ERR("Could not bind to GPIO device"); + return -EIO; + } + + ret = gpio_pin_configure(gpio, pin, pin_mode); + if (ret < 0) { + LOG_ERR("SPI pin configuration failed"); + return ret; + } + + if (pin_mode == GPIO_INPUT) { + esp32_rom_gpio_matrix_in(pin, pin_sig, false); + } else { + esp32_rom_gpio_matrix_out(pin, pin_sig, false, false); + } + + return 0; +} + +static inline spi_ll_io_mode_t spi_esp32_get_io_mode(uint16_t operation) +{ + switch (operation & SPI_LINES_MASK) { + case SPI_LINES_SINGLE: + return SPI_LL_IO_MODE_NORMAL; + case SPI_LINES_DUAL: + return SPI_LL_IO_MODE_DUAL; + case SPI_LINES_OCTAL: + return SPI_LL_IO_MODE_QIO; + case SPI_LINES_QUAD: + return SPI_LL_IO_MODE_QUAD; + default: + return SPI_LL_IO_MODE_NORMAL; + } +} + +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; + + if (spi_context_configured(ctx, spi_cfg)) { + return 0; + } + + /* enables SPI peripheral */ + if (clock_control_on(cfg->clock_dev, cfg->clock_subsys)) { + LOG_ERR("Could not enable SPI clock"); + return -EIO; + } + + ctx->config = spi_cfg; + + 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; + } + + spi_esp32_configure_pin(cfg->pins.miso, + cfg->signals.miso_s, + GPIO_INPUT); + + spi_esp32_configure_pin(cfg->pins.mosi, + cfg->signals.mosi_s, + GPIO_OUTPUT_LOW); + + spi_esp32_configure_pin(cfg->pins.sclk, + cfg->signals.sclk_s, + GPIO_OUTPUT); + + if (ctx->config->cs == NULL) { + data->hal.cs_setup = 1; + data->hal.cs_hold = 1; + data->hal.cs_pin_id = 0; + + spi_esp32_configure_pin(cfg->pins.csel, + cfg->signals.csel_s, + GPIO_OUTPUT | GPIO_ACTIVE_LOW); + } + + spi_context_cs_configure(&data->ctx); + + spi_hal_get_clock_conf(hal, spi_cfg->frequency, 128, true, 0, NULL, + &data->timing_conf); + + data->hal.timing_conf = &data->timing_conf; + data->hal.dummy_bits = data->hal.timing_conf->timing_dummy; + + data->hal.tx_lsbfirst = spi_cfg->operation & SPI_TRANSFER_LSB ? 1 : 0; + data->hal.rx_lsbfirst = spi_cfg->operation & SPI_TRANSFER_LSB ? 1 : 0; + + data->hal.io_mode = spi_esp32_get_io_mode(spi_cfg->operation); + + /* SPI mode */ + data->hal.mode = 0; + if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPOL) { + data->hal.mode = BIT(0); + } + + if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPHA) { + data->hal.mode |= BIT(1); + } + + spi_hal_setup_device(hal); + + 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, + struct k_poll_signal *signal) +{ + 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, signal, spi_cfg); + + ret = spi_esp32_configure(dev, spi_cfg); + if (ret) { + goto done; + } + + data->dfs = spi_esp32_get_frame_size(spi_cfg); + + 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(data, cfg->spi, 0); + +#endif /* CONFIG_SPI_ESP32_INTERRUPT */ + +done: + spi_context_release(&data->ctx, ret); + + return ret; +} + +#ifdef CONFIG_SPI_ESP32_INTERRUPT +static void IRAM_ATTR spi_esp32_isr(const struct device *dev) +{ + 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(data, cfg->spi, 0); +} +#endif + +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); +} + +#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, + struct k_poll_signal *async) +{ + return transceive(dev, spi_cfg, tx_bufs, rx_bufs, true, async); +} +#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 + .release = spi_esp32_release +}; + +#ifdef CONFIG_SPI_ESP32_INTERRUPT +#define ESP32_SPI_IRQ_HANDLER_DECL(idx) \ + static void spi_esp32_irq_config_func_##idx(const struct device *dev) + +#define ESP32_SPI_IRQ_HANDLER_FUNC(idx) \ + .irq_config_func = spi_esp32_irq_config_func_##idx, + +#define ESP32_SPI_IRQ_HANDLER(idx) \ + static void spi_esp32_irq_config_func_##idx(const struct device *dev) \ + { \ + intr_matrix_set(0, ETS_SPI##idx##_INTR_SOURCE, \ + INST_##idx##_ESPRESSIF_ESP32_SPI_IRQ_0); \ + IRQ_CONNECT(INST_##idx##_ESPRESSIF_ESP32_SPI_IRQ_0, 1, \ + spi_esp32_isr, DEVICE_DT_GET(DT_NODELABEL(spi##idx)), 0); \ + irq_enable(INST_##idx##_ESPRESSIF_ESP32_SPI_IRQ_0); \ + } +#else +#define ESP32_SPI_IRQ_HANDLER_DECL(idx) +#define ESP32_SPI_IRQ_HANDLER_FUNC(idx) +#define ESP32_SPI_IRQ_HANDLER(idx) +#endif + +#define ESP32_SPI_INIT(idx) \ + ESP32_SPI_IRQ_HANDLER_DECL(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), \ + .hal = { \ + .hw = (spi_dev_t *)DT_REG_ADDR(DT_NODELABEL(spi##idx)), \ + .half_duplex = DT_PROP(DT_NODELABEL(spi##idx), half_duplex), \ + .as_cs = DT_PROP(DT_NODELABEL(spi##idx), clk_as_cs), \ + .positive_cs = DT_PROP(DT_NODELABEL(spi##idx), positive_cs), \ + .dma_enabled = DT_PROP(DT_NODELABEL(spi##idx), dma), \ + .no_compensate = DT_PROP(DT_NODELABEL(spi##idx), dummy_comp), \ + .sio = DT_PROP(DT_NODELABEL(spi##idx), sio) \ + } \ + }; \ + \ + static const struct spi_esp32_config spi_config_##idx = { \ + .spi = (spi_dev_t *)DT_REG_ADDR(DT_NODELABEL(spi##idx)), \ + \ + .clock_dev = DEVICE_DT_GET(DT_CLOCKS_CTLR(DT_NODELABEL(spi##idx))), \ + ESP32_SPI_IRQ_HANDLER_FUNC(idx) \ + \ + .signals = { \ + .miso_s = MISO_IDX_##idx, \ + .mosi_s = MOSI_IDX_##idx, \ + .sclk_s = SCLK_IDX_##idx, \ + .csel_s = CSEL_IDX_##idx \ + }, \ + \ + .pins = { \ + .miso = DT_PROP(DT_NODELABEL(spi##idx), miso_pin), \ + .mosi = DT_PROP(DT_NODELABEL(spi##idx), mosi_pin), \ + .sclk = DT_PROP(DT_NODELABEL(spi##idx), sclk_pin), \ + .csel = DT_PROP(DT_NODELABEL(spi##idx), csel_pin) \ + }, \ + \ + .clock_subsys = \ + (clock_control_subsys_t)DT_CLOCKS_CELL( \ + DT_NODELABEL(spi##idx), offset), \ + \ + .irq = { \ + .source = ETS_SPI##idx##_INTR_SOURCE, \ + .line = INST_##idx##_ESPRESSIF_ESP32_SPI_IRQ_0 \ + }, \ + }; \ + \ + DEVICE_DT_DEFINE(DT_NODELABEL(spi##idx), &spi_esp32_init, \ + device_pm_control_no, &spi_data_##idx, \ + &spi_config_##idx, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &spi_api); \ + \ + ESP32_SPI_IRQ_HANDLER(idx) + +#if DT_NODE_HAS_STATUS(DT_NODELABEL(spi2), okay) +ESP32_SPI_INIT(2); +#endif + +#if DT_NODE_HAS_STATUS(DT_NODELABEL(spi3), okay) +ESP32_SPI_INIT(3); +#endif diff --git a/drivers/spi/spi_esp32_spim.h b/drivers/spi/spi_esp32_spim.h new file mode 100644 index 00000000000..d3489185345 --- /dev/null +++ b/drivers/spi/spi_esp32_spim.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Espressif Systems (Shanghai) Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_SPI_ESP32_SPIM_H_ +#define ZEPHYR_DRIVERS_SPI_ESP32_SPIM_H_ + +struct spi_esp32_config { + spi_dev_t *spi; + const struct device *clock_dev; + void (*irq_config_func)(const struct device *dev); + + struct { + int miso_s; + int mosi_s; + int sclk_s; + int csel_s; + } signals; + + struct { + int miso; + int mosi; + int sclk; + int csel; + } pins; + + clock_control_subsys_t clock_subsys; + + struct { + int source; + int line; + } irq; +}; + +struct spi_esp32_data { + struct spi_context ctx; + spi_hal_context_t hal; + spi_hal_timing_conf_t timing_conf; + uint8_t dfs; +}; + +#endif /* ZEPHYR_DRIVERS_SPI_ESP32_SPIM_H_ */ diff --git a/dts/bindings/spi/espressif,esp32-spi.yaml b/dts/bindings/spi/espressif,esp32-spi.yaml new file mode 100644 index 00000000000..c6e982217dd --- /dev/null +++ b/dts/bindings/spi/espressif,esp32-spi.yaml @@ -0,0 +1,71 @@ +description: ESP32 SPI + +compatible: "espressif,esp32-spi" + +include: spi-controller.yaml + +properties: + reg: + required: true + + interrupts: + required: false + + miso-pin: + type: int + description: MISO pin + required: true + + mosi-pin: + type: int + description: MOSI pin + required: true + + sclk-pin: + type: int + description: SPI generated clock pin + required: true + + csel-pin: + type: int + description: chip select pin + required: true + + half-duplex: + type: boolean + required: false + description: | + Enable half-duplex communication mode. + + Transmit data before receiving it, instead of simultaneously + + dummy-comp: + type: boolean + required: false + description: Enable dummy SPI compensation cycles + + sio: + type: boolean + required: false + description: | + Enable 3-wire mode + + Use MOSI for both sending and receiving data + + dma: + type: boolean + required: false + description: Enable SPI DMA support + + clk-as-cs: + type: boolean + required: false + description: | + Support to toggle the CS while the clock toggles + + Output clock on CS line if CS is active + + positive-cs: + type: boolean + required: false + description: Make CS positive during a transaction instead of negative diff --git a/dts/xtensa/espressif/esp32.dtsi b/dts/xtensa/espressif/esp32.dtsi index 3654d6e4057..eed0a3e1bc3 100644 --- a/dts/xtensa/espressif/esp32.dtsi +++ b/dts/xtensa/espressif/esp32.dtsi @@ -3,6 +3,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ +#include #include #include #include @@ -144,5 +145,21 @@ label = "WDT_1"; status = "disabled"; }; + + spi2: spi@3ff64000 { + compatible = "espressif,esp32-spi"; + reg = <0x3ff64000 DT_SIZE_K(4)>; + label = "SPI_2"; + clocks = <&rtc ESP32_SPI2_MODULE>; + status = "disabled"; + }; + + spi3: spi@3ff65000 { + compatible = "espressif,esp32-spi"; + reg = <0x3ff65000 DT_SIZE_K(4)>; + label = "SPI_3"; + clocks = <&rtc ESP32_SPI3_MODULE>; + status = "disabled"; + }; }; }; diff --git a/soc/xtensa/esp32/linker.ld b/soc/xtensa/esp32/linker.ld index f597657706e..b70bc607652 100644 --- a/soc/xtensa/esp32/linker.ld +++ b/soc/xtensa/esp32/linker.ld @@ -39,6 +39,9 @@ PROVIDE ( crc32_le = 0x4005cfec ); PROVIDE ( Cache_Read_Disable_rom = 0x40009ab8 ); PROVIDE ( Cache_Read_Enable_rom = 0x40009a84 ); PROVIDE ( Cache_Read_Init_rom = 0x40009950 ); +PROVIDE ( SPI1 = 0x3ff42fff ); +PROVIDE ( SPI2 = 0x3ff64fff ); +PROVIDE ( SPI3 = 0x3ff65fff ); PROVIDE ( esp32_rom_uart_tx_one_char = 0x40009200 ); PROVIDE ( esp32_rom_uart_rx_one_char = 0x400092d0 ); diff --git a/tests/drivers/spi/spi_loopback/boards/esp32.conf b/tests/drivers/spi/spi_loopback/boards/esp32.conf new file mode 100644 index 00000000000..b4dd9d09285 --- /dev/null +++ b/tests/drivers/spi/spi_loopback/boards/esp32.conf @@ -0,0 +1,2 @@ +CONFIG_SPI_LOOPBACK_DRV_NAME="SPI_3" +CONFIG_SPI_ESP32_INTERRUPT=y