diff --git a/boards/renesas/ek_ra8d1/doc/index.rst b/boards/renesas/ek_ra8d1/doc/index.rst index b79471899a4..ba76172775a 100644 --- a/boards/renesas/ek_ra8d1/doc/index.rst +++ b/boards/renesas/ek_ra8d1/doc/index.rst @@ -100,6 +100,8 @@ The below features are currently supported on Zephyr OS for EK-RA8D1 board: +--------------+------------+------------------+ | ENTROPY | on-chip | entropy | +--------------+------------+------------------+ +| SPI | on-chip | spi | ++--------------+------------+------------------+ Other hardware features are currently not supported by the port. diff --git a/boards/renesas/ek_ra8d1/ek_ra8d1-pinctrl.dtsi b/boards/renesas/ek_ra8d1/ek_ra8d1-pinctrl.dtsi index 1604d70e5c4..57a0e064fba 100644 --- a/boards/renesas/ek_ra8d1/ek_ra8d1-pinctrl.dtsi +++ b/boards/renesas/ek_ra8d1/ek_ra8d1-pinctrl.dtsi @@ -15,4 +15,14 @@ psels = ; }; }; + + spi0_default: spi0_default { + group1 { + /* MISO MOSI RSPCK SSL */ + psels = , + , + , + ; + }; + }; }; diff --git a/boards/renesas/ek_ra8d1/ek_ra8d1.dts b/boards/renesas/ek_ra8d1/ek_ra8d1.dts index 55e050551b8..13f97b6462d 100644 --- a/boards/renesas/ek_ra8d1/ek_ra8d1.dts +++ b/boards/renesas/ek_ra8d1/ek_ra8d1.dts @@ -104,3 +104,9 @@ &trng { status = "okay"; }; + +&spi0 { + pinctrl-0 = <&spi0_default>; + pinctrl-names = "default"; + status = "okay"; +}; diff --git a/boards/renesas/ek_ra8m1/doc/index.rst b/boards/renesas/ek_ra8m1/doc/index.rst index b58f513af99..dce3b85a581 100644 --- a/boards/renesas/ek_ra8m1/doc/index.rst +++ b/boards/renesas/ek_ra8m1/doc/index.rst @@ -102,6 +102,8 @@ The below features are currently supported on Zephyr OS for EK-RA8M1 board: +-----------+------------+----------------------+ | ENTROPY | on-chip | entropy | +-----------+------------+----------------------+ +| SPI | on-chip | spi | ++-----------+------------+----------------------+ Other hardware features are currently not supported by the port. diff --git a/boards/renesas/ek_ra8m1/ek_ra8m1-pinctrl.dtsi b/boards/renesas/ek_ra8m1/ek_ra8m1-pinctrl.dtsi index 8fc5744e9c6..b43c4be60ab 100644 --- a/boards/renesas/ek_ra8m1/ek_ra8m1-pinctrl.dtsi +++ b/boards/renesas/ek_ra8m1/ek_ra8m1-pinctrl.dtsi @@ -43,4 +43,14 @@ renesas,analog-enable; }; }; + + spi1_default: spi1_default { + group1 { + /* MISO MOSI RSPCK SSL*/ + psels = , + , + , + ; + }; + }; }; diff --git a/boards/renesas/ek_ra8m1/ek_ra8m1.dts b/boards/renesas/ek_ra8m1/ek_ra8m1.dts index ac29d191e3e..114fe5516ad 100644 --- a/boards/renesas/ek_ra8m1/ek_ra8m1.dts +++ b/boards/renesas/ek_ra8m1/ek_ra8m1.dts @@ -163,3 +163,9 @@ mikrobus_serial: &uart3 {}; &trng { status = "okay"; }; + +&spi1 { + pinctrl-0 = <&spi1_default>; + pinctrl-names = "default"; + status = "okay"; +}; diff --git a/boards/renesas/mck_ra8t1/doc/index.rst b/boards/renesas/mck_ra8t1/doc/index.rst index 41ebb058c4e..19ed6eb1330 100644 --- a/boards/renesas/mck_ra8t1/doc/index.rst +++ b/boards/renesas/mck_ra8t1/doc/index.rst @@ -98,6 +98,8 @@ The below features are currently supported on Zephyr OS for MCB-RA8T1 board: +--------------+------------+----------------------+ | ENTROPY | on-chip | entropy | +--------------+------------+----------------------+ +| SPI | on-chip | spi | ++--------------+------------+----------------------+ Other hardware features are currently not supported by the port. diff --git a/boards/renesas/mck_ra8t1/mck_ra8t1-pinctrl.dtsi b/boards/renesas/mck_ra8t1/mck_ra8t1-pinctrl.dtsi index f2ac078e92e..c8183975f37 100644 --- a/boards/renesas/mck_ra8t1/mck_ra8t1-pinctrl.dtsi +++ b/boards/renesas/mck_ra8t1/mck_ra8t1-pinctrl.dtsi @@ -15,4 +15,14 @@ psels = ; }; }; + + spi0_default: spi0_default { + group1 { + /* MISO MOSI RSPCK SSL*/ + psels = , + , + , + ; + }; + }; }; diff --git a/boards/renesas/mck_ra8t1/mck_ra8t1.dts b/boards/renesas/mck_ra8t1/mck_ra8t1.dts index 99a9124899f..0b35da08dba 100644 --- a/boards/renesas/mck_ra8t1/mck_ra8t1.dts +++ b/boards/renesas/mck_ra8t1/mck_ra8t1.dts @@ -98,3 +98,9 @@ &trng { status = "okay"; }; + +&spi0 { + pinctrl-0 = <&spi0_default>; + pinctrl-names = "default"; + status = "okay"; +}; diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt index a4eb01ef8e7..722a8eba587 100644 --- a/drivers/spi/CMakeLists.txt +++ b/drivers/spi/CMakeLists.txt @@ -49,6 +49,7 @@ zephyr_library_sources_ifdef(CONFIG_SPI_AMBIQ spi_ambiq.c) zephyr_library_sources_ifdef(CONFIG_SPI_AMBIQ_BLEIF spi_ambiq_bleif.c) zephyr_library_sources_ifdef(CONFIG_SPI_RPI_PICO_PIO spi_rpi_pico_pio.c) zephyr_library_sources_ifdef(CONFIG_SPI_MCHP_MSS spi_mchp_mss.c) +zephyr_library_sources_ifdef(CONFIG_SPI_RENESAS_RA8 spi_b_renesas_ra8.c) zephyr_library_sources_ifdef(CONFIG_SPI_RTIO spi_rtio.c) zephyr_library_sources_ifdef(CONFIG_SPI_ASYNC spi_signal.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE spi_handlers.c) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index b77b5a1e9ba..5aa78d8b9ed 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -158,4 +158,6 @@ source "drivers/spi/Kconfig.grlib_spimctrl" source "drivers/spi/Kconfig.max32" +source "drivers/spi/Kconfig.renesas_ra8" + endif # SPI diff --git a/drivers/spi/Kconfig.renesas_ra8 b/drivers/spi/Kconfig.renesas_ra8 new file mode 100644 index 00000000000..fe0512d049a --- /dev/null +++ b/drivers/spi/Kconfig.renesas_ra8 @@ -0,0 +1,34 @@ +# Renesas RA Family + +# Copyright (c) 2024 Renesas Electronics Corporation +# SPDX-License-Identifier: Apache-2.0 + +config SPI_RENESAS_RA8 + bool "Renesas RA SPI B" + default y + depends on DT_HAS_RENESAS_RA8_SPI_B_ENABLED + select USE_RA_FSP_SPI_B + help + Enable Renesas RA RA SPI B Driver. + +if SPI_RENESAS_RA8 + +config SPI_B_INTERRUPT + bool "RA MCU SPI B Interrupt Support" + help + Enable Interrupt support for the SPI B Driver of RA family. + +config SPI_B_RA_DTC + bool "RA MCU SPI DTC Support" + default y + select USE_RA_FSP_DTC + help + Enable the SPI DTC mode for SPI instances + +config SPI_B_USE_HW_SS + bool "RA MCU SPI B Hardware Slave Select support" + default y + help + Use Slave Select pin instead of software Slave Select. + +endif # RA_SPI_B diff --git a/drivers/spi/spi_b_renesas_ra8.c b/drivers/spi/spi_b_renesas_ra8.c new file mode 100644 index 00000000000..58b0b86ffca --- /dev/null +++ b/drivers/spi/spi_b_renesas_ra8.c @@ -0,0 +1,771 @@ +/* + * Copyright (c) 2024 Renesas Electronics Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT renesas_ra8_spi_b + +#include +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(ra8_spi_b); + +#include "spi_context.h" + +#if defined(CONFIG_SPI_B_INTERRUPT) +void spi_b_rxi_isr(void); +void spi_b_txi_isr(void); +void spi_b_tei_isr(void); +void spi_b_eri_isr(void); +#endif + +struct ra_spi_config { + const struct pinctrl_dev_config *pcfg; + const struct device *clock_dev; + const struct clock_control_ra_subsys_cfg clock_subsys; +}; + +struct ra_spi_data { + struct spi_context ctx; + uint8_t dfs; + struct st_spi_b_instance_ctrl spi; + struct st_spi_cfg fsp_config; + struct st_spi_b_extended_cfg fsp_config_extend; +#if CONFIG_SPI_B_INTERRUPT + uint32_t data_len; +#endif +#if defined(CONFIG_SPI_B_RA_DTC) + /* RX */ + struct st_transfer_instance rx_transfer; + struct st_dtc_instance_ctrl rx_transfer_ctrl; + struct st_transfer_info rx_transfer_info; + struct st_transfer_cfg rx_transfer_cfg; + struct st_dtc_extended_cfg rx_transfer_cfg_extend; + + /* TX */ + struct st_transfer_instance tx_transfer; + struct st_dtc_instance_ctrl tx_transfer_ctrl; + struct st_transfer_info tx_transfer_info; + struct st_transfer_cfg tx_transfer_cfg; + struct st_dtc_extended_cfg tx_transfer_cfg_extend; +#endif +}; + +static void spi_cb(spi_callback_args_t *p_args) +{ + struct device *dev = (struct device *)p_args->p_context; + struct ra_spi_data *data = dev->data; + + switch (p_args->event) { + case SPI_EVENT_TRANSFER_COMPLETE: + spi_context_cs_control(&data->ctx, false); + spi_context_complete(&data->ctx, dev, 0); + break; + case SPI_EVENT_ERR_MODE_FAULT: /* Mode fault error */ + case SPI_EVENT_ERR_READ_OVERFLOW: /* Read overflow error */ + case SPI_EVENT_ERR_PARITY: /* Parity error */ + case SPI_EVENT_ERR_OVERRUN: /* Overrun error */ + case SPI_EVENT_ERR_FRAMING: /* Framing error */ + case SPI_EVENT_ERR_MODE_UNDERRUN: /* Underrun error */ + spi_context_cs_control(&data->ctx, false); + spi_context_complete(&data->ctx, dev, -EIO); + break; + default: + break; + } +} + +static int ra_spi_b_configure(const struct device *dev, const struct spi_config *config) +{ + struct ra_spi_data *data = dev->data; + fsp_err_t fsp_err; + + if (spi_context_configured(&data->ctx, config)) { + /* Nothing to do */ + return 0; + } + + fsp_err = R_SPI_B_Close(&data->spi); + + if ((config->operation & SPI_FRAME_FORMAT_TI) == SPI_FRAME_FORMAT_TI) { + return -ENOTSUP; + } + + if (config->operation & SPI_OP_MODE_SLAVE) { + data->fsp_config.operating_mode = SPI_MODE_SLAVE; + } else { + data->fsp_config.operating_mode = SPI_MODE_MASTER; + } + + if (SPI_MODE_GET(config->operation) & SPI_MODE_CPOL) { + data->fsp_config.clk_polarity = SPI_CLK_POLARITY_HIGH; + } else { + data->fsp_config.clk_polarity = SPI_CLK_POLARITY_LOW; + } + + if (SPI_MODE_GET(config->operation) & SPI_MODE_CPHA) { + data->fsp_config.clk_phase = SPI_CLK_PHASE_EDGE_EVEN; + } else { + data->fsp_config.clk_phase = SPI_CLK_PHASE_EDGE_ODD; + } + + if (config->operation & SPI_TRANSFER_LSB) { + data->fsp_config.bit_order = SPI_BIT_ORDER_LSB_FIRST; + } else { + data->fsp_config.bit_order = SPI_BIT_ORDER_MSB_FIRST; + } + + if (config->frequency > 0) { + fsp_err = R_SPI_B_CalculateBitrate(config->frequency, + data->fsp_config_extend.clock_source, + &data->fsp_config_extend.spck_div); + __ASSERT(fsp_err == 0, "spi_b: spi frequency calculate error: %d", fsp_err); + } + + data->fsp_config_extend.spi_comm = SPI_B_COMMUNICATION_FULL_DUPLEX; + if (spi_cs_is_gpio(config) || !IS_ENABLED(CONFIG_SPI_B_USE_HW_SS)) { + data->fsp_config_extend.spi_clksyn = SPI_B_SSL_MODE_CLK_SYN; + } else { + data->fsp_config_extend.spi_clksyn = SPI_B_SSL_MODE_SPI; + data->fsp_config_extend.ssl_select = SPI_B_SSL_SELECT_SSL0; + } + + data->fsp_config.p_extend = &data->fsp_config_extend; + + data->fsp_config.p_callback = spi_cb; + data->fsp_config.p_context = dev; + fsp_err = R_SPI_B_Open(&data->spi, &data->fsp_config); + if (fsp_err != FSP_SUCCESS) { + LOG_ERR("R_SPI_B_Open error: %d", fsp_err); + return -EINVAL; + } + data->ctx.config = config; + + return 0; +} + +static bool ra_spi_b_transfer_ongoing(struct ra_spi_data *data) +{ +#if defined(CONFIG_SPI_B_INTERRUPT) + return (spi_context_tx_on(&data->ctx) || spi_context_rx_on(&data->ctx)); +#else + if (spi_context_total_tx_len(&data->ctx) < spi_context_total_rx_len(&data->ctx)) { + return (spi_context_tx_on(&data->ctx) || spi_context_rx_on(&data->ctx)); + } else { + return (spi_context_tx_on(&data->ctx) && spi_context_rx_on(&data->ctx)); + } +#endif +} + +#ifndef CONFIG_SPI_B_INTERRUPT +static int ra_spi_b_transceive_slave(struct ra_spi_data *data) +{ + R_SPI_B0_Type *p_spi_reg = data->spi.p_regs; + + if (p_spi_reg->SPSR_b.SPTEF && spi_context_tx_on(&data->ctx)) { + uint32_t tx; + + if (data->ctx.tx_buf != NULL) { + if (data->dfs > 2) { + tx = *(uint32_t *)(data->ctx.tx_buf); + } else if (data->dfs > 1) { + tx = *(uint16_t *)(data->ctx.tx_buf); + } else { + tx = *(uint8_t *)(data->ctx.tx_buf); + } + } else { + tx = 0; + } + /* Clear Transmit Empty flag */ + p_spi_reg->SPSRC = R_SPI_B0_SPSRC_SPTEFC_Msk; + + p_spi_reg->SPDR = tx; + + spi_context_update_tx(&data->ctx, data->dfs, 1); + } else { + p_spi_reg->SPCR_b.SPTIE = 0; + } + + if (p_spi_reg->SPSR_b.SPRF && spi_context_rx_buf_on(&data->ctx)) { + uint32_t rx; + + rx = p_spi_reg->SPDR; + /* Clear Receive Full flag */ + p_spi_reg->SPSRC = R_SPI_B0_SPSRC_SPRFC_Msk; + if (data->dfs > 2) { + UNALIGNED_PUT(rx, (uint32_t *)data->ctx.rx_buf); + } else if (data->dfs > 1) { + UNALIGNED_PUT(rx, (uint16_t *)data->ctx.rx_buf); + } else { + UNALIGNED_PUT(rx, (uint8_t *)data->ctx.rx_buf); + } + spi_context_update_rx(&data->ctx, data->dfs, 1); + } + + return 0; +} + +static int ra_spi_b_transceive_master(struct ra_spi_data *data) +{ + R_SPI_B0_Type *p_spi_reg = data->spi.p_regs; + uint32_t tx; + uint32_t rx; + + /* Tx transfer*/ + if (spi_context_tx_buf_on(&data->ctx)) { + if (data->dfs > 2) { + tx = *(uint32_t *)(data->ctx.tx_buf); + } else if (data->dfs > 1) { + tx = *(uint16_t *)(data->ctx.tx_buf); + } else { + tx = *(uint8_t *)(data->ctx.tx_buf); + } + } else { + tx = 0U; + } + + while (!p_spi_reg->SPSR_b.SPTEF) { + } + + p_spi_reg->SPDR = tx; + + /* Clear Transmit Empty flag */ + p_spi_reg->SPSRC = R_SPI_B0_SPSRC_SPTEFC_Msk; + spi_context_update_tx(&data->ctx, data->dfs, 1); + + /* Rx receive */ + if (spi_context_rx_on(&data->ctx)) { + while (!p_spi_reg->SPSR_b.SPRF) { + } + rx = p_spi_reg->SPDR; + /* Clear Receive Full flag */ + p_spi_reg->SPSRC = R_SPI_B0_SPSRC_SPRFC_Msk; + if (data->dfs > 2) { + UNALIGNED_PUT(rx, (uint32_t *)data->ctx.rx_buf); + } else if (data->dfs > 1) { + UNALIGNED_PUT(rx, (uint16_t *)data->ctx.rx_buf); + } else { + UNALIGNED_PUT(rx, (uint8_t *)data->ctx.rx_buf); + } + spi_context_update_rx(&data->ctx, data->dfs, 1); + } + + return 0; +} + +static int ra_spi_b_transceive_data(struct ra_spi_data *data) +{ + uint16_t operation = data->ctx.config->operation; + + if (SPI_OP_MODE_GET(operation) == SPI_OP_MODE_MASTER) { + ra_spi_b_transceive_master(data); + } else { + ra_spi_b_transceive_slave(data); + } + + return 0; +} +#endif + +static int transceive(const struct device *dev, const struct spi_config *config, + const struct spi_buf_set *tx_bufs, const struct spi_buf_set *rx_bufs, + bool asynchronous, spi_callback_t cb, void *userdata) +{ + struct ra_spi_data *data = dev->data; + R_SPI_B0_Type *p_spi_reg; + int ret = 0; + + if (!tx_bufs && !rx_bufs) { + return 0; + } + +#ifndef CONFIG_SPI_B_INTERRUPT + if (asynchronous) { + return -ENOTSUP; + } +#endif + + spi_context_lock(&data->ctx, asynchronous, cb, userdata, config); + + ret = ra_spi_b_configure(dev, config); + if (ret) { + goto end; + } + data->dfs = ((SPI_WORD_SIZE_GET(config->operation) - 1) / 8) + 1; + p_spi_reg = data->spi.p_regs; + /* Set buffers info */ + spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, data->dfs); + + spi_context_cs_control(&data->ctx, true); + +#ifdef CONFIG_SPI_B_INTERRUPT + spi_bit_width_t spi_width = + (spi_bit_width_t)(SPI_WORD_SIZE_GET(data->ctx.config->operation) - 1); + + if (data->ctx.rx_len == 0) { + data->data_len = spi_context_is_slave(&data->ctx) + ? spi_context_total_tx_len(&data->ctx) + : data->ctx.tx_len; + } else if (data->ctx.tx_len == 0) { + data->data_len = spi_context_is_slave(&data->ctx) + ? spi_context_total_rx_len(&data->ctx) + : data->ctx.rx_len; + } else { + data->data_len = spi_context_is_slave(&data->ctx) + ? MAX(spi_context_total_tx_len(&data->ctx), + spi_context_total_rx_len(&data->ctx)) + : MIN(data->ctx.tx_len, data->ctx.rx_len); + } + + if (data->ctx.rx_buf == NULL) { + R_SPI_B_Write(&data->spi, data->ctx.tx_buf, data->data_len, spi_width); + } else if (data->ctx.tx_buf == NULL) { + R_SPI_B_Read(&data->spi, data->ctx.rx_buf, data->data_len, spi_width); + } else { + R_SPI_B_WriteRead(&data->spi, data->ctx.tx_buf, data->ctx.rx_buf, data->data_len, + spi_width); + } + ret = spi_context_wait_for_completion(&data->ctx); + +#else + p_spi_reg->SPCR_b.TXMD = 0x0; /* tx - rx*/ + if (!spi_context_tx_on(&data->ctx)) { + p_spi_reg->SPCR_b.TXMD = 0x2; /* rx only */ + } + if (!spi_context_rx_on(&data->ctx)) { + p_spi_reg->SPCR_b.TXMD = 0x1; /* tx only */ + } + + /* Clear FIFOs */ + p_spi_reg->SPFCR = 1; + + /* Enable the SPI Transfer. */ + p_spi_reg->SPCR_b.SPE = 1; + p_spi_reg->SPCMD0 |= (uint32_t)(SPI_WORD_SIZE_GET(data->ctx.config->operation) - 1) + << R_SPI_B0_SPCMD0_SPB_Pos; + do { + ra_spi_b_transceive_data(data); + } while (ra_spi_b_transfer_ongoing(data)); + + /* Wait for transmision complete */ + while (p_spi_reg->SPSR_b.IDLNF) { + } + + /* Disable the SPI Transfer. */ + p_spi_reg->SPCR_b.SPE = 0; +#endif +#ifdef CONFIG_SPI_SLAVE + if (spi_context_is_slave(&data->ctx) && !ret) { + ret = data->ctx.recv_frames; + } +#endif /* CONFIG_SPI_SLAVE */ + +end: + spi_context_release(&data->ctx, ret); + + return ret; +} + +static int ra_spi_b_transceive(const struct device *dev, const struct spi_config *config, + const struct spi_buf_set *tx_bufs, const struct spi_buf_set *rx_bufs) +{ + return transceive(dev, config, tx_bufs, rx_bufs, false, NULL, NULL); +} + +#ifdef CONFIG_SPI_ASYNC +static int ra_spi_b_transceive_async(const struct device *dev, const struct spi_config *config, + const struct spi_buf_set *tx_bufs, + const struct spi_buf_set *rx_bufs, spi_callback_t cb, + void *userdata) +{ + return transceive(dev, config, tx_bufs, rx_bufs, true, cb, userdata); +} +#endif /* CONFIG_SPI_ASYNC */ + +static int ra_spi_b_release(const struct device *dev, const struct spi_config *config) +{ + struct ra_spi_data *data = dev->data; + + spi_context_unlock_unconditionally(&data->ctx); + + return 0; +} + +static const struct spi_driver_api ra_spi_driver_api = {.transceive = ra_spi_b_transceive, +#ifdef CONFIG_SPI_ASYNC + .transceive_async = + ra_spi_b_transceive_async, +#endif /* CONFIG_SPI_ASYNC */ + .release = ra_spi_b_release}; + +static spi_b_clock_source_t ra_spi_b_clock_name(const struct device *clock_dev) +{ + const char *clock_dev_name = clock_dev->name; + + if (strcmp(clock_dev_name, "spiclk") == 0 || strcmp(clock_dev_name, "scispiclk") == 0) { + return SPI_B_CLOCK_SOURCE_SCISPICLK; + } + + return SPI_B_CLOCK_SOURCE_PCLK; +} + +static int spi_b_ra_init(const struct device *dev) +{ + const struct ra_spi_config *config = dev->config; + struct ra_spi_data *data = dev->data; + int ret; + + if (!device_is_ready(config->clock_dev)) { + return -ENODEV; + } + + data->fsp_config_extend.clock_source = ra_spi_b_clock_name(config->clock_dev); + + /* Configure dt provided device signals when available */ + ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); + if (ret < 0) { + return ret; + } + + ret = spi_context_cs_configure_all(&data->ctx); + if (ret < 0) { + return ret; + } + + spi_context_unlock_unconditionally(&data->ctx); + + return 0; +} + +#if defined(CONFIG_SPI_B_INTERRUPT) + +static void ra_spi_retransmit(struct ra_spi_data *data) +{ + spi_bit_width_t spi_width = + (spi_bit_width_t)(SPI_WORD_SIZE_GET(data->ctx.config->operation) - 1); + + if (data->ctx.rx_len == 0) { + data->data_len = data->ctx.tx_len; + data->spi.p_tx_data = data->ctx.tx_buf; + data->spi.p_rx_data = NULL; + } else if (data->ctx.tx_len == 0) { + data->data_len = data->ctx.rx_len; + data->spi.p_tx_data = NULL; + data->spi.p_rx_data = data->ctx.rx_buf; + } else { + data->data_len = MIN(data->ctx.tx_len, data->ctx.rx_len); + data->spi.p_tx_data = data->ctx.tx_buf; + data->spi.p_rx_data = data->ctx.rx_buf; + } + + data->spi.bit_width = spi_width; + data->spi.rx_count = 0; + data->spi.tx_count = 0; + data->spi.count = data->data_len; +#ifdef CONFIG_SPI_B_RA_DTC + /* Determine DTC transfer size */ + transfer_size_t size; + + if (spi_width > SPI_BIT_WIDTH_16_BITS) { /* Bit Widths of 17-32 bits */ + size = TRANSFER_SIZE_4_BYTE; + } else if (spi_width > SPI_BIT_WIDTH_8_BITS) { /* Bit Widths of 9-16 bits*/ + size = TRANSFER_SIZE_2_BYTE; + } else { /* Bit Widths of 4-8 bits */ + size = TRANSFER_SIZE_1_BYTE; + } + + if (data->spi.p_cfg->p_transfer_rx) { + /* When the rxi interrupt is called, all transfers will be finished. */ + data->spi.rx_count = data->data_len; + + transfer_instance_t *p_transfer_rx = + (transfer_instance_t *)data->spi.p_cfg->p_transfer_rx; + transfer_info_t *p_info = p_transfer_rx->p_cfg->p_info; + + /* Configure the receive DMA instance. */ + p_info->transfer_settings_word_b.size = size; + p_info->length = (uint16_t)data->data_len; + p_info->transfer_settings_word_b.dest_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED; + p_info->p_dest = data->ctx.rx_buf; + + if (NULL == data->ctx.rx_buf) { + static uint32_t dummy_rx; + + p_info->transfer_settings_word_b.dest_addr_mode = TRANSFER_ADDR_MODE_FIXED; + p_info->p_dest = &dummy_rx; + } + + p_transfer_rx->p_api->reconfigure(p_transfer_rx->p_ctrl, p_info); + } + + if (data->spi.p_cfg->p_transfer_tx) { + /* When the txi interrupt is called, all transfers will be finished. */ + data->spi.tx_count = data->data_len; + + transfer_instance_t *p_transfer_tx = + (transfer_instance_t *)data->spi.p_cfg->p_transfer_tx; + transfer_info_t *p_info = p_transfer_tx->p_cfg->p_info; + + /* Configure the transmit DMA instance. */ + p_info->transfer_settings_word_b.size = size; + p_info->length = (uint16_t)data->data_len; + p_info->transfer_settings_word_b.src_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED; + p_info->p_src = data->ctx.tx_buf; + + if (NULL == data->ctx.tx_buf) { + static uint32_t dummy_tx; + + p_info->transfer_settings_word_b.src_addr_mode = TRANSFER_ADDR_MODE_FIXED; + p_info->p_src = &dummy_tx; + } + + p_transfer_tx->p_api->reconfigure(p_transfer_tx->p_ctrl, p_info); + } +#endif + data->spi.p_regs->SPSRC = R_SPI_B0_SPSRC_SPTEFC_Msk; +} + +static void ra_spi_rxi_isr(const struct device *dev) +{ +#ifndef CONFIG_SPI_SLAVE + ARG_UNUSED(dev); + spi_b_rxi_isr(); +#else + struct ra_spi_data *data = dev->data; + + spi_b_rxi_isr(); + if (spi_context_is_slave(&data->ctx) && data->spi.rx_count == data->spi.count) { + + if (data->ctx.rx_buf != NULL && data->ctx.tx_buf != NULL) { + data->ctx.recv_frames = MIN(spi_context_total_tx_len(&data->ctx), + spi_context_total_rx_len(&data->ctx)); + } else if (data->ctx.tx_buf == NULL) { + data->ctx.recv_frames = data->data_len; + } else { + /* Do nothing */ + } + + R_BSP_IrqDisable(data->fsp_config.tei_irq); + + /* Writing 0 to SPE generatates a TXI IRQ. Disable the TXI IRQ. + * (See Section 38.2.1 SPI Control Register in the RA6T2 manual R01UH0886EJ0100). + */ + R_BSP_IrqDisable(data->fsp_config.txi_irq); + + /* Disable the SPI Transfer. */ + data->spi.p_regs->SPCR_b.SPE = 0; + + /* Re-enable the TXI IRQ and clear the pending IRQ. */ + R_BSP_IrqEnable(data->fsp_config.txi_irq); + + spi_context_cs_control(&data->ctx, false); + spi_context_complete(&data->ctx, dev, 0); + } +#endif +} + +static void ra_spi_txi_isr(const struct device *dev) +{ + ARG_UNUSED(dev); + spi_b_txi_isr(); +} + +static void ra_spi_tei_isr(const struct device *dev) +{ + struct ra_spi_data *data = dev->data; + + if (data->spi.rx_count == data->spi.count) { + spi_context_update_rx(&data->ctx, 1, data->data_len); + } + if (data->spi.tx_count == data->spi.count) { + spi_context_update_tx(&data->ctx, 1, data->data_len); + } + if (ra_spi_b_transfer_ongoing(data)) { + R_ICU->IELSR_b[data->fsp_config.tei_irq].IR = 0U; + ra_spi_retransmit(data); + } else { + spi_b_tei_isr(); + } +} + +static void ra_spi_eri_isr(const struct device *dev) +{ + ARG_UNUSED(dev); + spi_b_eri_isr(); +} +#endif + +#define _ELC_EVENT_SPI_RXI(channel) ELC_EVENT_SPI##channel##_RXI +#define _ELC_EVENT_SPI_TXI(channel) ELC_EVENT_SPI##channel##_TXI +#define _ELC_EVENT_SPI_TEI(channel) ELC_EVENT_SPI##channel##_TEI +#define _ELC_EVENT_SPI_ERI(channel) ELC_EVENT_SPI##channel##_ERI + +#define ELC_EVENT_SPI_RXI(channel) _ELC_EVENT_SPI_RXI(channel) +#define ELC_EVENT_SPI_TXI(channel) _ELC_EVENT_SPI_TXI(channel) +#define ELC_EVENT_SPI_TEI(channel) _ELC_EVENT_SPI_TEI(channel) +#define ELC_EVENT_SPI_ERI(channel) _ELC_EVENT_SPI_ERI(channel) + +#if defined(CONFIG_SPI_B_INTERRUPT) + +#define RA_SPI_B_IRQ_CONFIG_INIT(index) \ + do { \ + ARG_UNUSED(dev); \ + \ + R_ICU->IELSR[DT_INST_IRQ_BY_NAME(index, rxi, irq)] = \ + ELC_EVENT_SPI_RXI(DT_INST_PROP(index, channel)); \ + R_ICU->IELSR[DT_INST_IRQ_BY_NAME(index, txi, irq)] = \ + ELC_EVENT_SPI_TXI(DT_INST_PROP(index, channel)); \ + R_ICU->IELSR[DT_INST_IRQ_BY_NAME(index, tei, irq)] = \ + ELC_EVENT_SPI_TEI(DT_INST_PROP(index, channel)); \ + R_ICU->IELSR[DT_INST_IRQ_BY_NAME(index, eri, irq)] = \ + ELC_EVENT_SPI_ERI(DT_INST_PROP(index, channel)); \ + \ + IRQ_CONNECT(DT_INST_IRQ_BY_NAME(index, rxi, irq), \ + DT_INST_IRQ_BY_NAME(index, rxi, priority), ra_spi_rxi_isr, \ + DEVICE_DT_INST_GET(index), 0); \ + IRQ_CONNECT(DT_INST_IRQ_BY_NAME(index, txi, irq), \ + DT_INST_IRQ_BY_NAME(index, txi, priority), ra_spi_txi_isr, \ + DEVICE_DT_INST_GET(index), 0); \ + IRQ_CONNECT(DT_INST_IRQ_BY_NAME(index, tei, irq), \ + DT_INST_IRQ_BY_NAME(index, tei, priority), ra_spi_tei_isr, \ + DEVICE_DT_INST_GET(index), 0); \ + IRQ_CONNECT(DT_INST_IRQ_BY_NAME(index, eri, irq), \ + DT_INST_IRQ_BY_NAME(index, eri, priority), ra_spi_eri_isr, \ + DEVICE_DT_INST_GET(index), 0); \ + \ + irq_enable(DT_INST_IRQ_BY_NAME(index, rxi, irq)); \ + irq_enable(DT_INST_IRQ_BY_NAME(index, txi, irq)); \ + irq_enable(DT_INST_IRQ_BY_NAME(index, eri, irq)); \ + } while (0) + +#else + +#define RA_SPI_B_IRQ_CONFIG_INIT(index) + +#endif + +#ifndef CONFIG_SPI_B_RA_DTC +#define RA_SPI_B_DTC_STRUCT_INIT(index) +#define RA_SPI_B_DTC_INIT(index) +#else +#define RA_SPI_B_DTC_INIT(index) \ + do { \ + if (DT_INST_PROP_OR(index, rx_dtc, false)) { \ + ra_spi_data_##index.fsp_config.p_transfer_rx = \ + &ra_spi_data_##index.rx_transfer; \ + } \ + if (DT_INST_PROP_OR(index, tx_dtc, false)) { \ + ra_spi_data_##index.fsp_config.p_transfer_tx = \ + &ra_spi_data_##index.tx_transfer; \ + } \ + } while (0) +#define RA_SPI_B_DTC_STRUCT_INIT(index) \ + .rx_transfer_info = \ + { \ + .transfer_settings_word_b.dest_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, \ + .transfer_settings_word_b.repeat_area = TRANSFER_REPEAT_AREA_DESTINATION, \ + .transfer_settings_word_b.irq = TRANSFER_IRQ_END, \ + .transfer_settings_word_b.chain_mode = TRANSFER_CHAIN_MODE_DISABLED, \ + .transfer_settings_word_b.src_addr_mode = TRANSFER_ADDR_MODE_FIXED, \ + .transfer_settings_word_b.size = TRANSFER_SIZE_1_BYTE, \ + .transfer_settings_word_b.mode = TRANSFER_MODE_NORMAL, \ + .p_dest = (void *)NULL, \ + .p_src = (void const *)NULL, \ + .num_blocks = 0, \ + .length = 0, \ + }, \ + .rx_transfer_cfg_extend = {.activation_source = DT_INST_IRQ_BY_NAME(index, rxi, irq)}, \ + .rx_transfer_cfg = \ + { \ + .p_info = &ra_spi_data_##index.rx_transfer_info, \ + .p_extend = &ra_spi_data_##index.rx_transfer_cfg_extend, \ + }, \ + .rx_transfer = \ + { \ + .p_ctrl = &ra_spi_data_##index.rx_transfer_ctrl, \ + .p_cfg = &ra_spi_data_##index.rx_transfer_cfg, \ + .p_api = &g_transfer_on_dtc, \ + }, \ + .tx_transfer_info = \ + { \ + .transfer_settings_word_b.dest_addr_mode = TRANSFER_ADDR_MODE_FIXED, \ + .transfer_settings_word_b.repeat_area = TRANSFER_REPEAT_AREA_SOURCE, \ + .transfer_settings_word_b.irq = TRANSFER_IRQ_END, \ + .transfer_settings_word_b.chain_mode = TRANSFER_CHAIN_MODE_DISABLED, \ + .transfer_settings_word_b.src_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, \ + .transfer_settings_word_b.size = TRANSFER_SIZE_1_BYTE, \ + .transfer_settings_word_b.mode = TRANSFER_MODE_NORMAL, \ + .p_dest = (void *)NULL, \ + .p_src = (void const *)NULL, \ + .num_blocks = 0, \ + .length = 0, \ + }, \ + .tx_transfer_cfg_extend = {.activation_source = DT_INST_IRQ_BY_NAME(index, txi, irq)}, \ + .tx_transfer_cfg = \ + { \ + .p_info = &ra_spi_data_##index.tx_transfer_info, \ + .p_extend = &ra_spi_data_##index.tx_transfer_cfg_extend, \ + }, \ + .tx_transfer = { \ + .p_ctrl = &ra_spi_data_##index.tx_transfer_ctrl, \ + .p_cfg = &ra_spi_data_##index.tx_transfer_cfg, \ + .p_api = &g_transfer_on_dtc, \ + }, +#endif + +#define RA_SPI_INIT(index) \ + \ + PINCTRL_DT_INST_DEFINE(index); \ + \ + static const struct ra_spi_config ra_spi_config_##index = { \ + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(index), \ + .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(index)), \ + .clock_subsys = \ + { \ + .mstp = (uint32_t *)DT_INST_CLOCKS_CELL_BY_NAME(index, spiclk, \ + mstp), \ + .stop_bit = DT_INST_CLOCKS_CELL_BY_NAME(index, spiclk, stop_bit), \ + }, \ + }; \ + \ + static struct ra_spi_data ra_spi_data_##index = { \ + SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(index), ctx) \ + SPI_CONTEXT_INIT_LOCK(ra_spi_data_##index, ctx), \ + SPI_CONTEXT_INIT_SYNC(ra_spi_data_##index, ctx), \ + .fsp_config = \ + { \ + .channel = DT_INST_PROP(index, channel), \ + .rxi_ipl = DT_INST_IRQ_BY_NAME(index, rxi, priority), \ + .rxi_irq = DT_INST_IRQ_BY_NAME(index, rxi, irq), \ + .txi_ipl = DT_INST_IRQ_BY_NAME(index, txi, priority), \ + .txi_irq = DT_INST_IRQ_BY_NAME(index, txi, irq), \ + .tei_ipl = DT_INST_IRQ_BY_NAME(index, tei, priority), \ + .tei_irq = DT_INST_IRQ_BY_NAME(index, tei, irq), \ + .eri_ipl = DT_INST_IRQ_BY_NAME(index, eri, priority), \ + .eri_irq = DT_INST_IRQ_BY_NAME(index, eri, irq), \ + }, \ + RA_SPI_B_DTC_STRUCT_INIT(index)}; \ + \ + static int spi_b_ra_init##index(const struct device *dev) \ + { \ + RA_SPI_B_DTC_INIT(index); \ + int err = spi_b_ra_init(dev); \ + if (err != 0) { \ + return err; \ + } \ + RA_SPI_B_IRQ_CONFIG_INIT(index); \ + return 0; \ + } \ + \ + DEVICE_DT_INST_DEFINE(index, spi_b_ra_init##index, PM_DEVICE_DT_INST_GET(index), \ + &ra_spi_data_##index, &ra_spi_config_##index, POST_KERNEL, \ + CONFIG_SPI_INIT_PRIORITY, &ra_spi_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(RA_SPI_INIT) diff --git a/dts/arm/renesas/ra/ra8/ra8x1.dtsi b/dts/arm/renesas/ra/ra8/ra8x1.dtsi index 9dc3c7f4194..3cf329ad48a 100644 --- a/dts/arm/renesas/ra/ra8/ra8x1.dtsi +++ b/dts/arm/renesas/ra/ra8/ra8x1.dtsi @@ -303,6 +303,32 @@ compatible = "renesas,ra-rsip-e51a-trng"; }; + spi0: spi@4035c000 { + compatible = "renesas,ra8-spi-b"; + #address-cells = <1>; + #size-cells = <0>; + channel = <0>; + clocks = <&pclka MSTPB 19>; + clock-names = "spiclk"; + interrupts = <28 1>, <29 1>, <30 1>, <31 1>; + interrupt-names = "rxi", "txi", "tei", "eri"; + reg = <0x4035c000 0x100>; + status = "disabled"; + }; + + spi1: spi@4035c100 { + compatible = "renesas,ra8-spi-b"; + #address-cells = <1>; + #size-cells = <0>; + channel = <1>; + clocks = <&pclka MSTPB 18>; + clock-names = "spiclk"; + interrupts = <32 1>, <33 1>, <34 1>, <35 1>; + interrupt-names = "rxi", "txi", "tei", "eri"; + reg = <0x4035c100 0x100>; + status = "disabled"; + }; + option_setting_ofs: option_setting_ofs@300a100 { compatible = "zephyr,memory-region"; reg = <0x0300a100 0x18>; diff --git a/dts/bindings/spi/renesas,ra8-spi-b.yaml b/dts/bindings/spi/renesas,ra8-spi-b.yaml new file mode 100644 index 00000000000..bdbbd121229 --- /dev/null +++ b/dts/bindings/spi/renesas,ra8-spi-b.yaml @@ -0,0 +1,37 @@ +# Copyright (c) 2024 Renesas Electronics Corporation +# SPDX-License-Identifier: Apache-2.0 + +description: Renesas RA8 SPI_B controller + +compatible: "renesas,ra8-spi-b" + +include: [spi-controller.yaml, pinctrl-device.yaml] + +properties: + channel: + type: int + required: true + + reg: + required: true + + clocks: + required: true + + tx-dtc: + type: boolean + + rx-dtc: + type: boolean + + pinctrl-0: + required: true + + pinctrl-names: + required: true + + interrupts: + required: true + + interrupt-names: + required: true diff --git a/modules/Kconfig.renesas_fsp b/modules/Kconfig.renesas_fsp index 54d9e82452e..2e7482d2a25 100644 --- a/modules/Kconfig.renesas_fsp +++ b/modules/Kconfig.renesas_fsp @@ -48,3 +48,8 @@ config HAS_RENESAS_RA_RSIP_E51A Includes RSIP-E51A implementation for SCE driver endif + +config USE_RA_FSP_SPI_B + bool + help + Enable RA FSP SPI-B driver diff --git a/tests/drivers/spi/spi_controller_peripheral/boards/ek_ra8m1.conf b/tests/drivers/spi/spi_controller_peripheral/boards/ek_ra8m1.conf new file mode 100644 index 00000000000..8c1ed20e464 --- /dev/null +++ b/tests/drivers/spi/spi_controller_peripheral/boards/ek_ra8m1.conf @@ -0,0 +1,5 @@ +# Copyright (c) 2024 Renesas Electronics Corporation +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_SPI_B_INTERRUPT=y +CONFIG_SPI_B_RA_DTC=y diff --git a/tests/drivers/spi/spi_controller_peripheral/boards/ek_ra8m1.overlay b/tests/drivers/spi/spi_controller_peripheral/boards/ek_ra8m1.overlay new file mode 100644 index 00000000000..019a7754da7 --- /dev/null +++ b/tests/drivers/spi/spi_controller_peripheral/boards/ek_ra8m1.overlay @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 Renesas Electronics Corporation + * SPDX-License-Identifier: Apache-2.0 + */ + +&pinctrl { + spi0_default_alt: spi0_default_alt { + group1 { + /* MISO MOSI RSPCK SSL */ + psels = , + , + ; + }; + }; + + spi1_default_alt: spi1_default_alt { + group1 { + /* MISO MOSI RSPCK SSL */ + psels = , + , + , + ; + }; + }; +}; + +&ioport7 { + status = "okay"; +}; + +&ioport4 { + status = "okay"; +}; + +&spi0 { + rx-dtc; + tx-dtc; + status = "okay"; + pinctrl-0 = <&spi0_default_alt>; + pinctrl-names = "default"; + cs-gpios = <&ioport7 3 GPIO_ACTIVE_LOW>; + dut_spi_dt: test-spi-dev@0 { + compatible = "vnd,spi-device"; + reg = <0>; + spi-max-frequency = <1000000>; + }; +}; + +dut_spis: &spi1 { + rx-dtc; + tx-dtc; + status = "okay"; + pinctrl-0 = <&spi1_default_alt>; + pinctrl-names = "default"; +}; diff --git a/tests/drivers/spi/spi_loopback/boards/ek_ra8d1.conf b/tests/drivers/spi/spi_loopback/boards/ek_ra8d1.conf new file mode 100644 index 00000000000..6011f58b3d0 --- /dev/null +++ b/tests/drivers/spi/spi_loopback/boards/ek_ra8d1.conf @@ -0,0 +1,6 @@ +# Copyright (c) 2024 Renesas Electronics Corporation +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_SPI_LOOPBACK_MODE_LOOP=y +CONFIG_SPI_B_INTERRUPT=y +CONFIG_SPI_B_RA_DTC=y diff --git a/tests/drivers/spi/spi_loopback/boards/ek_ra8d1.overlay b/tests/drivers/spi/spi_loopback/boards/ek_ra8d1.overlay new file mode 100644 index 00000000000..a02d082b099 --- /dev/null +++ b/tests/drivers/spi/spi_loopback/boards/ek_ra8d1.overlay @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 Renesas Electronics Corporation + * SPDX-License-Identifier: Apache-2.0 + */ + +&spi0 { + rx-dtc; + tx-dtc; + slow@0 { + compatible = "test-spi-loopback-slow"; + reg = <0>; + spi-max-frequency = <2000000>; + }; + + fast@0 { + compatible = "test-spi-loopback-fast"; + reg = <0>; + spi-max-frequency = <3000000>; + }; +}; diff --git a/tests/drivers/spi/spi_loopback/boards/ek_ra8m1.conf b/tests/drivers/spi/spi_loopback/boards/ek_ra8m1.conf new file mode 100644 index 00000000000..6011f58b3d0 --- /dev/null +++ b/tests/drivers/spi/spi_loopback/boards/ek_ra8m1.conf @@ -0,0 +1,6 @@ +# Copyright (c) 2024 Renesas Electronics Corporation +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_SPI_LOOPBACK_MODE_LOOP=y +CONFIG_SPI_B_INTERRUPT=y +CONFIG_SPI_B_RA_DTC=y diff --git a/tests/drivers/spi/spi_loopback/boards/ek_ra8m1.overlay b/tests/drivers/spi/spi_loopback/boards/ek_ra8m1.overlay new file mode 100644 index 00000000000..781069befc3 --- /dev/null +++ b/tests/drivers/spi/spi_loopback/boards/ek_ra8m1.overlay @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 Renesas Electronics Corporation + * SPDX-License-Identifier: Apache-2.0 + */ + + #include + +&pinctrl { + spi0_default: spi0_default { + group1 { + /* MISO MOSI RSPCK SSL*/ + psels = , + , + , + ; + }; + }; +}; + +&spi0 { + rx-dtc; + tx-dtc; + pinctrl-0 = <&spi0_default>; + pinctrl-names = "default"; + status = "okay"; + slow@0 { + compatible = "test-spi-loopback-slow"; + reg = <0>; + spi-max-frequency = <2000000>; + }; + fast@0 { + compatible = "test-spi-loopback-fast"; + reg = <0>; + spi-max-frequency = <3000000>; + }; +}; diff --git a/tests/drivers/spi/spi_loopback/boards/mck_ra8t1.conf b/tests/drivers/spi/spi_loopback/boards/mck_ra8t1.conf new file mode 100644 index 00000000000..6011f58b3d0 --- /dev/null +++ b/tests/drivers/spi/spi_loopback/boards/mck_ra8t1.conf @@ -0,0 +1,6 @@ +# Copyright (c) 2024 Renesas Electronics Corporation +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_SPI_LOOPBACK_MODE_LOOP=y +CONFIG_SPI_B_INTERRUPT=y +CONFIG_SPI_B_RA_DTC=y diff --git a/tests/drivers/spi/spi_loopback/boards/mck_ra8t1.overlay b/tests/drivers/spi/spi_loopback/boards/mck_ra8t1.overlay new file mode 100644 index 00000000000..a02d082b099 --- /dev/null +++ b/tests/drivers/spi/spi_loopback/boards/mck_ra8t1.overlay @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 Renesas Electronics Corporation + * SPDX-License-Identifier: Apache-2.0 + */ + +&spi0 { + rx-dtc; + tx-dtc; + slow@0 { + compatible = "test-spi-loopback-slow"; + reg = <0>; + spi-max-frequency = <2000000>; + }; + + fast@0 { + compatible = "test-spi-loopback-fast"; + reg = <0>; + spi-max-frequency = <3000000>; + }; +};