diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt index 9fedab1bdbd..883444202dc 100644 --- a/drivers/spi/CMakeLists.txt +++ b/drivers/spi/CMakeLists.txt @@ -45,6 +45,7 @@ zephyr_library_sources_ifdef(CONFIG_SPI_NRFX_SPIM spi_nrfx_spim.c spi_nrfx_commo zephyr_library_sources_ifdef(CONFIG_SPI_NRFX_SPIS spi_nrfx_spis.c) zephyr_library_sources_ifdef(CONFIG_SPI_NUMAKER spi_numaker.c) zephyr_library_sources_ifdef(CONFIG_SPI_OC_SIMPLE spi_oc_simple.c) +zephyr_library_sources_ifdef(CONFIG_SPI_OMAP_MCSPI spi_omap_mcspi.c) zephyr_library_sources_ifdef(CONFIG_SPI_OPENTITAN spi_opentitan.c) zephyr_library_sources_ifdef(CONFIG_SPI_PL022 spi_pl022.c) zephyr_library_sources_ifdef(CONFIG_SPI_PSOC6 spi_psoc6.c) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 1b6ed782ad0..b7866a6aa40 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -123,6 +123,7 @@ source "drivers/spi/Kconfig.nrfx" source "drivers/spi/Kconfig.numaker" source "drivers/spi/Kconfig.nxp_s32" source "drivers/spi/Kconfig.oc_simple" +source "drivers/spi/Kconfig.omap" source "drivers/spi/Kconfig.opentitan" source "drivers/spi/Kconfig.pl022" source "drivers/spi/Kconfig.psoc6" diff --git a/drivers/spi/Kconfig.omap b/drivers/spi/Kconfig.omap new file mode 100644 index 00000000000..fd6cd837886 --- /dev/null +++ b/drivers/spi/Kconfig.omap @@ -0,0 +1,10 @@ +# Copyright 2025 Texas Instruments +# SPDX-License-Identifier: Apache-2.0 + +config SPI_OMAP_MCSPI + bool "MCSPI driver for OMAP and K3 devices" + default y + depends on DT_HAS_TI_OMAP_MCSPI_ENABLED + select PINCTRL + help + Enable support for TI OMAP MCSPI driver. diff --git a/drivers/spi/spi_omap_mcspi.c b/drivers/spi/spi_omap_mcspi.c new file mode 100644 index 00000000000..2d5faf1aa23 --- /dev/null +++ b/drivers/spi/spi_omap_mcspi.c @@ -0,0 +1,686 @@ +/* + * Copyright (c) 2025 Texas Instruments Incorporated + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT ti_omap_mcspi + +#include +LOG_MODULE_REGISTER(omap_mcspi); + +#include +#include +#include "spi_context.h" + +/* Max clock divisor for granularity of 1 (12-bit) */ +#define OMAP_MCSPI_CLK_1_MAX_DIV (4096) +/* Max clock divisor for granularity of 2^n (15-bit) */ +#define OMAP_MCSPI_CLK_2_N_MAX_DIV (32768) +#define OMAP_MCSPI_NUM_CHANNELS (4) + +/* Number of retries when reading some register status */ +#define OMAP_MCSPI_REG_RETRIES (100) +/* Time to wait between successive retries in microseconds */ +#define OMAP_MCSPI_REG_TIME_BETWEEN_RETRIES_US (10) + +struct omap_mcspi_regs { + uint8_t RESERVED_1[0x04]; /**< Reserved, offset: 0x00 - 0x04 */ + volatile uint32_t HWINFO; /**< MCSPI Hardware configuration register, offset: 0x04 */ + uint8_t RESERVED_2[0x108]; /**< Reserved, offset: 0x08 - 0x110 */ + volatile uint32_t SYSCONFIG; /**< Configuration register, offset: 0x110 */ + volatile uint32_t SYSSTATUS; /**< Status information register, offset: 0x114 */ + uint8_t RESERVED_3[0x10]; /**< Reserved, offset: 0x118 - 0x128 */ + volatile uint32_t MODULCTRL; /**< MCSPI configuration register, offset: 0x128 */ + volatile struct { + volatile uint32_t CHCONF; /**< Configuration register, offset: 0x12C + (0x14 * i) */ + volatile uint32_t CHSTAT; /**< Status register, offset: 0x130 + (0x14 * i) */ + volatile uint32_t CHCTRL; /**< Control register, offset: 0x134 + (0x14 * i) */ + volatile uint32_t TX; /**< TX register, offset: 0x138 + (0x14 * i) */ + volatile uint32_t RX; /**< RX register, offset: 0x13C + (0x14 * i) */ + } CHAN[OMAP_MCSPI_NUM_CHANNELS]; + volatile uint32_t XFERLEVEL; /**< FIFO Transfer Level register, offset: 0x17C */ +}; + +/* Hardware Information */ +#define OMAP_MCSPI_HWINFO_FFNBYTE GENMASK(5, 1) /* FIFO depth */ + +/* Configuration Register */ +#define OMAP_MCSPI_SYSCONFIG_SOFTRESET BIT(1) /* Software Reset */ + +/* Status Register */ +#define OMAP_MCSPI_SYSSTATUS_RESETDONE BIT(0) /* Internal Reset Monitoring */ + +/* MCSPI Configuration Register */ +#define OMAP_MCSPI_MODULCTRL_SYSTEST BIT(3) /* System test mode */ +#define OMAP_MCSPI_MODULCTRL_MS BIT(2) /* Peripheral mode */ +#define OMAP_MCSPI_MODULCTRL_SINGLE BIT(0) /* Single channel mode (controller only) */ + +/* Channel Configuration */ +#define OMAP_MCSPI_CHCONF_CLKG BIT(29) /* Clock divider granularity */ +#define OMAP_MCSPI_CHCONF_FFER BIT(28) /* Enable FIFO for receiving */ +#define OMAP_MCSPI_CHCONF_FFEW BIT(27) /* Enable FIFO for tramitting */ +#define OMAP_MCSPI_CHCONF_FORCE BIT(20) /* Manual SPIEN assertion */ +#define OMAP_MCSPI_CHCONF_TURBO BIT(19) /* Turbo mode */ +#define OMAP_MCSPI_CHCONF_IS BIT(18) /* Input select */ +#define OMAP_MCSPI_CHCONF_DPE1 BIT(17) /* Transmission enabled for data line 1 */ +#define OMAP_MCSPI_CHCONF_DPE0 BIT(16) /* Transmission enabled for data line 0 */ +#define OMAP_MCSPI_CHCONF_TRM GENMASK(13, 12) /* TX/RX mode */ +#define OMAP_MCSPI_CHCONF_TRM_TX_ONLY BIT(13) /* TX/RX mode - Transmit only */ +#define OMAP_MCSPI_CHCONF_TRM_RX_ONLY BIT(12) /* TX/RX mode - Receive only */ +#define OMAP_MCSPI_CHCONF_WL GENMASK(11, 7) /* SPI word length */ +#define OMAP_MCSPI_CHCONF_EPOL BIT(6) /* SPIEN polarity */ +#define OMAP_MCSPI_CHCONF_CLKD GENMASK(5, 2) /* Frequency divider for SPICLK */ +#define OMAP_MCSPI_CHCONF_POL BIT(1) /* SPICLK polarity */ +#define OMAP_MCSPI_CHCONF_PHA BIT(0) /* SPICLK phase */ + +/* Channel Control Register */ +#define OMAP_MCSPI_CHCTRL_EXTCLK GENMASK(15, 8) /* Clock ratio extension (concat with CLKD) */ +#define OMAP_MCSPI_CHCTRL_EN BIT(0) /* Channel enable */ + +/* Channel Status Register */ +#define OMAP_MCSPI_CHSTAT_TXFFE BIT(3) /* Transmit buffer empty registe */ +#define OMAP_MCSPI_CHSTAT_RXFFE BIT(5) /* Receive buffer empty registe */ +#define OMAP_MCSPI_CHSTAT_EOT BIT(2) /* End of transfer status */ +#define OMAP_MCSPI_CHSTAT_TXS BIT(1) /* Transmit register empty status */ +#define OMAP_MCSPI_CHSTAT_RXS BIT(0) /* Receiver register full status */ + +/* FIFO transfer level register */ +#define OMAP_MCSPI_XFERLEVEL_WCNT GENMASK(31, 16) /* Word counter for transfer */ + +#define DEV_CFG(dev) ((const struct omap_mcspi_cfg *)(dev)->config) +#define DEV_DATA(dev) ((struct omap_mcspi_data *)(dev)->data) +#define DEV_REGS(dev) ((struct omap_mcspi_regs *)DEVICE_MMIO_GET(dev)) + +struct omap_mcspi_cfg { + DEVICE_MMIO_ROM; + const struct pinctrl_dev_config *pinctrl; + uint32_t clock_frequency; + bool d1_miso_d0_mosi; + uint8_t num_cs; +}; + +struct omap_mcspi_data { + DEVICE_MMIO_RAM; + struct spi_context ctx; + uint32_t fifo_depth; + uint32_t chconf; + uint32_t chctrl; + uint8_t dfs; /* data frame size - word length in bytes */ +}; + +static void omap_mcspi_channel_enable(const struct device *dev, bool enable) +{ + struct omap_mcspi_regs *regs = DEV_REGS(dev); + uint8_t chan = DEV_DATA(dev)->ctx.config->slave; + + if (enable) { + regs->CHAN[chan].CHCTRL |= OMAP_MCSPI_CHCTRL_EN; + } else { + regs->CHAN[chan].CHCTRL &= ~OMAP_MCSPI_CHCTRL_EN; + } +} + +static void omap_mcspi_set_mode(const struct device *dev, bool is_peripheral) +{ + struct omap_mcspi_regs *regs = DEV_REGS(dev); + uint32_t modulctrl = regs->MODULCTRL; + + /* disable system test mode */ + modulctrl &= ~(OMAP_MCSPI_MODULCTRL_SYSTEST); + + /* set controller or peripheral (master/slave) */ + if (is_peripheral) { + modulctrl |= OMAP_MCSPI_MODULCTRL_MS; + } else { + modulctrl &= ~OMAP_MCSPI_MODULCTRL_MS; + + /* We only support single-mode for now + * TODO: add multi-mode + */ + modulctrl |= OMAP_MCSPI_MODULCTRL_SINGLE; + } + + regs->MODULCTRL = modulctrl; +} + +static int omap_mcspi_configure_clk_freq(const struct device *dev, uint32_t speed_hz, + uint32_t ref_hz) +{ + struct omap_mcspi_data *data = DEV_DATA(dev); + uint32_t extclk = 0; + uint32_t clkd = 0; + uint32_t clkg = 0; + uint32_t f_ratio = DIV_ROUND_UP(ref_hz, speed_hz); + + if (f_ratio <= OMAP_MCSPI_CLK_1_MAX_DIV) { + /* If under 4096, use the granularity of 1 */ + clkg = 1; + extclk = (f_ratio - 1) >> 4; + clkd = (f_ratio - 1) & 0xf; + /* Otherwise if power of two, use granularity of 2^n (n <= 15) */ + } else if ((f_ratio & (f_ratio - 1)) == 0 && f_ratio <= OMAP_MCSPI_CLK_2_N_MAX_DIV) { + clkg = 0; + while (f_ratio != 1) { + f_ratio >>= 1; + clkd++; + } + } else { + LOG_ERR("Invalid SPI device frequency: %uHz\n", speed_hz); + return -EINVAL; + } + + data->chconf &= ~OMAP_MCSPI_CHCONF_CLKD; + data->chconf |= FIELD_PREP(OMAP_MCSPI_CHCONF_CLKD, clkd); + + if (clkg) { + data->chconf |= OMAP_MCSPI_CHCONF_CLKG; + data->chctrl &= ~OMAP_MCSPI_CHCTRL_EXTCLK; + data->chctrl |= FIELD_PREP(OMAP_MCSPI_CHCTRL_EXTCLK, extclk); + } else { + data->chconf &= ~OMAP_MCSPI_CHCONF_CLKG; + } + + return 0; +} + +static int omap_mcspi_configure(const struct device *dev, const struct spi_config *config, + const struct spi_buf_set *tx_bufs, + const struct spi_buf_set *rx_bufs) +{ + const struct omap_mcspi_cfg *cfg = DEV_CFG(dev); + struct omap_mcspi_data *data = DEV_DATA(dev); + struct omap_mcspi_regs *regs = DEV_REGS(dev); + struct spi_context *ctx = &data->ctx; + uint8_t chan = config->slave; + uint8_t word_size = SPI_WORD_SIZE_GET(config->operation); + bool is_peripheral = config->operation & SPI_OP_MODE_SLAVE; + int rv; + + if (spi_context_configured(ctx, config)) { + /* This configuration is already in use */ + return 0; + } + + if (config->operation & SPI_HOLD_ON_CS) { + return -ENOTSUP; + } + + if (is_peripheral && !IS_ENABLED(CONFIG_SPI_SLAVE)) { + LOG_ERR("Kconfig for SPI slave mode is not enabled"); + return -ENOTSUP; + } + + if (chan >= cfg->num_cs) { + LOG_ERR("invalid slave selected"); + return -EINVAL; + } + + if ((config->operation & SPI_HALF_DUPLEX) && tx_bufs && rx_bufs) { + LOG_ERR("cannot transmit and receive simultaneously with half duplex"); + return -EINVAL; + } + + if (word_size < 4 || word_size > 32) { + LOG_ERR("invalid word size"); + return -EINVAL; + } + + /* update data frame size (word size in bytes) */ + if (word_size <= 8) { + data->dfs = 1; + } else if (word_size <= 16) { + data->dfs = 2; + } else { /* word_size <= 32 */ + data->dfs = 4; + } + + ARRAY_FOR_EACH(regs->CHAN, ch) { + if (ch != chan) { + /* only when MODULCTRL_SINGLE is set */ + regs->CHAN[ch].CHCTRL &= ~OMAP_MCSPI_CHCTRL_EN; + regs->CHAN[ch].CHCONF &= ~OMAP_MCSPI_CHCONF_FORCE; + } + + /* disable FIFO for all channels */ + regs->CHAN[ch].CHCONF &= ~(OMAP_MCSPI_CHCONF_FFER | OMAP_MCSPI_CHCONF_FFEW); + } + + /* set mode */ + omap_mcspi_set_mode(dev, is_peripheral); + + /* update cached registers */ + data->chconf = regs->CHAN[chan].CHCONF; + data->chctrl = regs->CHAN[chan].CHCTRL; + + /* configure word length */ + data->chconf &= ~OMAP_MCSPI_CHCONF_WL; + data->chconf |= FIELD_PREP(OMAP_MCSPI_CHCONF_WL, word_size - 1); + + if (config->operation & SPI_MODE_LOOP) { + /* d0-in d0-out, loopback */ + data->chconf &= ~(OMAP_MCSPI_CHCONF_IS); + data->chconf |= (OMAP_MCSPI_CHCONF_DPE1); + data->chconf &= ~(OMAP_MCSPI_CHCONF_DPE0); + } else if (cfg->d1_miso_d0_mosi) { + /* d1-in-d0-out */ + data->chconf |= (OMAP_MCSPI_CHCONF_IS); + data->chconf |= (OMAP_MCSPI_CHCONF_DPE1); + data->chconf &= ~(OMAP_MCSPI_CHCONF_DPE0); + } else { + /* d0-in d1-out, default */ + data->chconf &= ~(OMAP_MCSPI_CHCONF_IS); + data->chconf &= ~(OMAP_MCSPI_CHCONF_DPE1); + data->chconf |= (OMAP_MCSPI_CHCONF_DPE0); + } + + /* configure spien polarity */ + if (!(config->operation & SPI_CS_ACTIVE_HIGH)) { + data->chconf |= OMAP_MCSPI_CHCONF_EPOL; + } else { + data->chconf &= ~OMAP_MCSPI_CHCONF_EPOL; + } + + /* set clk polarity */ + if (config->operation & SPI_MODE_CPOL) { + data->chconf |= OMAP_MCSPI_CHCONF_POL; + } else { + data->chconf &= ~OMAP_MCSPI_CHCONF_POL; + } + + /* set clk phase */ + if (config->operation & SPI_MODE_CPHA) { + data->chconf |= OMAP_MCSPI_CHCONF_PHA; + } else { + data->chconf &= ~OMAP_MCSPI_CHCONF_PHA; + } + + /* set force */ + if (!spi_cs_is_gpio(config)) { + data->chconf |= OMAP_MCSPI_CHCONF_FORCE; + } else { + data->chconf &= ~OMAP_MCSPI_CHCONF_FORCE; + } + + rv = omap_mcspi_configure_clk_freq(dev, config->frequency, cfg->clock_frequency); + if (rv != 0) { + return rv; + } + + /* save config in the context */ + ctx->config = config; + return 0; +} + +static int omap_mcspi_wait_for_reg_bit(volatile uint32_t *reg, uint8_t bit) +{ + uint32_t retries = 0; + + while ((*reg & bit) == 0) { + /* timeout = 1ms */ + if (retries++ > OMAP_MCSPI_REG_RETRIES) { + return -ETIMEDOUT; + } + k_usleep(OMAP_MCSPI_REG_TIME_BETWEEN_RETRIES_US); + } + + return 0; +} + +static ALWAYS_INLINE void write_tx(const uint8_t *tx_buf, volatile uint32_t *tx_reg, uint8_t dfs) +{ + switch (dfs) { + case 1: { + *tx_reg = UNALIGNED_GET((uint8_t *)tx_buf); + break; + } + case 2: { + *tx_reg = UNALIGNED_GET((uint16_t *)tx_buf); + break; + } + case 4: { + *tx_reg = UNALIGNED_GET((uint32_t *)tx_buf); + break; + } + default: { + /* unreachable */ + } + } +} + +static ALWAYS_INLINE void read_rx(uint8_t *rx_buf, volatile uint32_t *rx_reg, uint8_t dfs, + uint32_t word_mask) +{ + + switch (dfs) { + case 1: { + UNALIGNED_PUT(*rx_reg & word_mask, (uint8_t *)rx_buf); + break; + } + case 2: { + UNALIGNED_PUT(*rx_reg & word_mask, (uint16_t *)rx_buf); + break; + } + case 4: { + UNALIGNED_PUT(*rx_reg & word_mask, (uint32_t *)rx_buf); + break; + } + default: { + /* unreachable */ + } + } +} + +static int omap_mcspi_transceive_pio(const struct device *dev, size_t count) +{ + struct omap_mcspi_data *data = DEV_DATA(dev); + struct omap_mcspi_regs *regs = DEV_REGS(dev); + struct spi_context *ctx = &data->ctx; + const uint32_t word_mask = (1ULL << SPI_WORD_SIZE_GET(ctx->config->operation)) - 1; + const uint8_t chan = ctx->config->slave; + const uint8_t dfs = data->dfs; + const uint8_t *tx_buf = ctx->tx_buf; + uint8_t *rx_buf = ctx->rx_buf; + + volatile uint32_t *chstat = ®s->CHAN[chan].CHSTAT; + volatile uint32_t *tx_reg = ®s->CHAN[chan].TX; + volatile uint32_t *rx_reg = ®s->CHAN[chan].RX; + + /* RX only */ + if (!tx_buf) { + /* write dummy value of 0 to TX FIFO */ + *tx_reg = 0; + + while (count != 0) { + if (omap_mcspi_wait_for_reg_bit(chstat, OMAP_MCSPI_CHSTAT_RXS)) { + LOG_ERR("RXS timed out"); + return count; + } + + read_rx(rx_buf, rx_reg, dfs, word_mask); + rx_buf += dfs; + + count--; + } + + /* Make sure RX FIFO is empty */ + if (omap_mcspi_wait_for_reg_bit(chstat, OMAP_MCSPI_CHSTAT_RXFFE) != 0) { + LOG_ERR("RXFFE timed out"); + return count; + } + + /* TX only */ + } else if (!rx_buf) { + while (count > 0) { + size_t num_words = MIN(count, (data->fifo_depth / 2) / dfs); + + /* Make sure TX FIFO is empty */ + if (omap_mcspi_wait_for_reg_bit(chstat, OMAP_MCSPI_CHSTAT_TXFFE)) { + LOG_ERR("TXFFE timed out"); + return count; + } + + /* Write and fill the entire TX FIFO */ + for (int i = 0; i < num_words; i++) { + write_tx(tx_buf, tx_reg, dfs); + tx_buf += dfs; + } + + count -= num_words; + } + + /* Make sure TX FIFO is empty */ + if (omap_mcspi_wait_for_reg_bit(chstat, OMAP_MCSPI_CHSTAT_TXFFE)) { + LOG_ERR("TXFFE timed out"); + return count; + } + + /* Both RX and TX */ + } else { + while (count > 0) { + size_t num_words = MIN(count, (data->fifo_depth / 2) / dfs); + + /* Make sure TX FIFO is empty */ + if (omap_mcspi_wait_for_reg_bit(chstat, OMAP_MCSPI_CHSTAT_TXFFE)) { + LOG_ERR("TXFFE timed out"); + return count; + } + + /* Write and fill the entire TX FIFO */ + for (int i = 0; i < num_words; i++) { + write_tx(tx_buf, tx_reg, dfs); + tx_buf += dfs; + } + + /* Read and empty the entire RX FIFO */ + for (int i = 0; i < num_words; i++) { + if (omap_mcspi_wait_for_reg_bit(chstat, OMAP_MCSPI_CHSTAT_RXS)) { + LOG_ERR("RXS timed out"); + return count; + } + + read_rx(rx_buf, rx_reg, dfs, word_mask); + rx_buf += dfs; + } + + count -= num_words; + } + } + + omap_mcspi_channel_enable(dev, false); + + return count; +} + +static int omap_mcspi_transceive_one(const struct device *dev) +{ + struct omap_mcspi_data *data = DEV_DATA(dev); + struct omap_mcspi_regs *regs = DEV_REGS(dev); + struct spi_context *ctx = &data->ctx; + const uint8_t chan = ctx->config->slave; + const uint8_t *tx_buf = ctx->tx_buf; + uint8_t *rx_buf = ctx->rx_buf; + size_t count = spi_context_max_continuous_chunk(ctx); + int rv; + + if (!tx_buf && !rx_buf) { + goto exit; + } + + /* disable channel */ + omap_mcspi_channel_enable(dev, false); + + data->chconf &= ~OMAP_MCSPI_CHCONF_TRM; + + if (rx_buf) { + /* enable read FIFO */ + data->chconf |= OMAP_MCSPI_CHCONF_FFER; + } else { + /* tx only */ + data->chconf |= OMAP_MCSPI_CHCONF_TRM_TX_ONLY; + + /* disable read FIFO */ + data->chconf &= ~OMAP_MCSPI_CHCONF_FFER; + } + + if (tx_buf) { + /* enable write FIFO */ + data->chconf |= OMAP_MCSPI_CHCONF_FFEW; + } else { + /* rx only */ + data->chconf |= OMAP_MCSPI_CHCONF_TRM_RX_ONLY; + + /* disable write FIFO */ + data->chconf &= ~OMAP_MCSPI_CHCONF_FFEW; + } + + if (count > 1) { + data->chconf |= OMAP_MCSPI_CHCONF_TURBO; + } else { + data->chconf &= ~OMAP_MCSPI_CHCONF_TURBO; + } + + /* write chconf and chctrl */ + regs->CHAN[chan].CHCONF = data->chconf; + regs->CHAN[chan].CHCTRL = data->chctrl; + + /* write WCNT */ + regs->XFERLEVEL = FIELD_PREP(OMAP_MCSPI_XFERLEVEL_WCNT, count); + + /* enable channel */ + omap_mcspi_channel_enable(dev, true); + + /* we only support PIO for now + * TODO: add DMA + */ + rv = omap_mcspi_transceive_pio(dev, count); + if (rv) { + return -EIO; + } + +exit: + /* update rx buffer */ + spi_context_update_rx(ctx, data->dfs, count); + + /* update tx buffer */ + spi_context_update_tx(ctx, data->dfs, count); + + return 0; +} + +static int omap_mcspi_transceive_all(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 callback, void *userdata) +{ + struct omap_mcspi_data *data = DEV_DATA(dev); + struct spi_context *ctx = &data->ctx; + int ret = 0; + + if (!tx_bufs && !rx_bufs) { + return 0; + } + + spi_context_lock(ctx, asynchronous, callback, userdata, config); + + ret = omap_mcspi_configure(dev, config, tx_bufs, rx_bufs); + if (ret) { + LOG_ERR("An error occurred in the SPI configuration"); + goto cleanup; + } + + spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, data->dfs); + + spi_context_cs_control(ctx, true); + + while (spi_context_tx_on(ctx) || spi_context_rx_on(ctx)) { + ret = omap_mcspi_transceive_one(dev); + if (ret < 0) { + LOG_ERR("Transaction failed, TX/RX left: %zu/%zu", + spi_context_tx_len_left(ctx, data->dfs), + spi_context_rx_len_left(ctx, data->dfs)); + goto cleanup; + } + } + +cleanup: + spi_context_cs_control(ctx, false); + + if (!(config->operation & SPI_LOCK_ON)) { + spi_context_release(ctx, ret); + } + return ret; +} + +static int omap_mcspi_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 omap_mcspi_transceive_all(dev, config, tx_bufs, rx_bufs, false, NULL, NULL); +} + +#ifdef CONFIG_SPI_ASYNC +static int omap_mcspi_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 callback, + void *userdata) +{ + /* wait for DMA to be implemented to use IRQ and ASYNC */ + return -ENOTSUP; +} +#endif /* CONFIG_SPI_ASYNC */ + +static int omap_mcspi_init(const struct device *dev) +{ + const struct omap_mcspi_cfg *cfg = DEV_CFG(dev); + struct omap_mcspi_data *data = DEV_DATA(dev); + struct omap_mcspi_regs *regs; + int ret; + + DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE); + regs = DEV_REGS(dev); + + if (cfg->num_cs > OMAP_MCSPI_NUM_CHANNELS) { + LOG_ERR("chipselect count cannot be greater than max channel count"); + return -EINVAL; + } + + ret = pinctrl_apply_state(cfg->pinctrl, PINCTRL_STATE_DEFAULT); + if (ret < 0) { + LOG_ERR("failed to apply pinctrl"); + return ret; + } + + /* Software Reset */ + regs->SYSCONFIG |= OMAP_MCSPI_SYSCONFIG_SOFTRESET; + + /* Wait till reset is done */ + ret = omap_mcspi_wait_for_reg_bit(®s->SYSSTATUS, OMAP_MCSPI_SYSSTATUS_RESETDONE); + if (ret < 0) { + LOG_ERR("RESETDONE timed out"); + return ret; + } + + data->fifo_depth = FIELD_GET(OMAP_MCSPI_HWINFO_FFNBYTE, regs->HWINFO) << 4; + + spi_context_unlock_unconditionally(&data->ctx); + + return 0; +} + +static int omap_mcspi_release(const struct device *dev, const struct spi_config *spi_cfg) +{ + struct omap_mcspi_data *data = DEV_DATA(dev); + + spi_context_unlock_unconditionally(&data->ctx); + + return 0; +} + +static DEVICE_API(spi, omap_mcspi_api) = { + .transceive = omap_mcspi_transceive, +#ifdef CONFIG_SPI_ASYNC + .transceive_async = omap_mcspi_transceive_async, +#endif /* CONFIG_SPI_ASYNC */ + .release = omap_mcspi_release, +}; + +#define OMAP_MCSPI_INIT(n) \ + PINCTRL_DT_INST_DEFINE(n); \ + static struct omap_mcspi_cfg omap_mcspi_config_##n = { \ + DEVICE_MMIO_ROM_INIT(DT_DRV_INST(n)), \ + .pinctrl = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ + .clock_frequency = DT_INST_PROP(n, clock_frequency), \ + .d1_miso_d0_mosi = DT_INST_PROP(n, ti_d1_miso_d0_mosi), \ + .num_cs = DT_INST_PROP(n, ti_spi_num_cs), \ + }; \ + \ + static struct omap_mcspi_data omap_mcspi_data_##n = { \ + SPI_CONTEXT_INIT_LOCK(omap_mcspi_data_##n, ctx), \ + SPI_CONTEXT_INIT_SYNC(omap_mcspi_data_##n, ctx), \ + SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(n), ctx)}; \ + \ + SPI_DEVICE_DT_INST_DEFINE(n, omap_mcspi_init, NULL, &omap_mcspi_data_##n, \ + &omap_mcspi_config_##n, POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, \ + &omap_mcspi_api); + +DT_INST_FOREACH_STATUS_OKAY(OMAP_MCSPI_INIT) diff --git a/dts/bindings/spi/ti,omap-mcspi.yaml b/dts/bindings/spi/ti,omap-mcspi.yaml new file mode 100644 index 00000000000..1d171b52409 --- /dev/null +++ b/dts/bindings/spi/ti,omap-mcspi.yaml @@ -0,0 +1,23 @@ +# Copyright 2025 Texas Instruments +# SPDX-License-Identifier: Apache-2.0 + +description: TI Multi Channel SPI controller for OMAP and K3 SoCs + +compatible: "ti,omap-mcspi" + +include: [spi-controller.yaml, pinctrl-device.yaml] + +properties: + reg: + required: true + + ti,spi-num-cs: + type: int + default: 1 + description: | + Number of chipselects supported by controller + + ti,d1-miso-d0-mosi: + type: boolean + description: | + Sets d0 as MOSI and d1 as MISO if true, vice versa otherwise.