diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt index 6ceda851b1e17..1b1140502dfe3 100644 --- a/drivers/spi/CMakeLists.txt +++ b/drivers/spi/CMakeLists.txt @@ -40,6 +40,7 @@ zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_ECSPI spi_mcux_ecspi.c) zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_FLEXCOMM spi_mcux_flexcomm.c) zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_FLEXIO spi_mcux_flexio.c) zephyr_library_sources_ifdef(CONFIG_SPI_MEC5_QSPI spi_mchp_mec5_qspi.c) +zephyr_library_sources_ifdef(CONFIG_SPI_MSPM0 spi_mspm0.c) zephyr_library_sources_ifdef(CONFIG_SPI_NPCX_SPIP spi_npcx_spip.c) zephyr_library_sources_ifdef(CONFIG_SPI_NRFX_SPI spi_nrfx_spi.c spi_nrfx_common.c) zephyr_library_sources_ifdef(CONFIG_SPI_NRFX_SPIM spi_nrfx_spim.c spi_nrfx_common.c) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index a083f021c7533..ada7399295842 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -119,6 +119,7 @@ source "drivers/spi/Kconfig.mcux_ecspi" source "drivers/spi/Kconfig.mcux_flexcomm" source "drivers/spi/Kconfig.mcux_flexio" source "drivers/spi/Kconfig.mec5" +source "drivers/spi/Kconfig.mspm0" source "drivers/spi/Kconfig.npcx" source "drivers/spi/Kconfig.nrfx" source "drivers/spi/Kconfig.numaker" diff --git a/drivers/spi/Kconfig.mspm0 b/drivers/spi/Kconfig.mspm0 new file mode 100644 index 0000000000000..ea9ffe0302574 --- /dev/null +++ b/drivers/spi/Kconfig.mspm0 @@ -0,0 +1,11 @@ +# Copyright (c) 2025 Linumiz GmbH +# SPDX-License-Identifier: Apache-2.0 + +config SPI_MSPM0 + bool "TI's MSPM0 SPI" + default y + depends on DT_HAS_TI_MSPM0_SPI_ENABLED + select USE_MSPM0_DL_SPI + select PINCTRL + help + This driver enable TI MSPM0 spi driver. diff --git a/drivers/spi/spi_mspm0.c b/drivers/spi/spi_mspm0.c new file mode 100644 index 0000000000000..2e18490c6f1cc --- /dev/null +++ b/drivers/spi/spi_mspm0.c @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2024 Bang & Olufsen A/S, Denmark + * Copyright (c) 2025 Linumiz GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT ti_mspm0_spi + +#include +#include +#include +#include +#include +#include +#include + +/* TI DriverLib includes */ +#include + +LOG_MODULE_REGISTER(spi_mspm0, CONFIG_SPI_LOG_LEVEL); + +/* This must be included after log module registration */ +#include "spi_context.h" + +/* Data Frame Size (DFS) */ +#define SPI_DFS_8BIT 1 +#define SPI_DFS_16BIT 2 + +/* Range for SPI Serial Clock Rate (SCR) */ +#define MSPM0_SPI_SCR_MIN 0 +#define MSPM0_SPI_SCR_MAX 1023 + +/* Delay after enabling power for SPI module */ +#define POWER_STARTUP_DELAY 16 + +#define SPI_DT_CLK_DIV(inst) \ + DT_INST_PROP(inst, ti_clk_div) + +#define SPI_DT_CLK_DIV_ENUM(inst) \ + _CONCAT(DL_SPI_CLOCK_DIVIDE_RATIO_, SPI_DT_CLK_DIV(inst)) + +#define SPI_MODE(operation) \ + (operation & BIT(0) ? DL_SPI_MODE_PERIPHERAL : DL_SPI_MODE_CONTROLLER) + +#define BIT_ORDER_MODE(operation) \ + (operation & BIT(4) ? DL_SPI_BIT_ORDER_LSB_FIRST : DL_SPI_BIT_ORDER_MSB_FIRST) + +/* MSPM0 DSS field expects word size - 1 */ +#define DATA_SIZE_MODE(operation) \ + (SPI_WORD_SIZE_GET(operation) - 1) + +#define POLARITY_MODE(operation) \ + (SPI_MODE_GET(operation) & SPI_MODE_CPOL ? SPI_CTL0_SPO_HIGH : SPI_CTL0_SPO_LOW) + +#define PHASE_MODE(operation) \ + (SPI_MODE_GET(operation) & SPI_MODE_CPHA ? SPI_CTL0_SPH_SECOND : SPI_CTL0_SPH_FIRST) + +#define DUPLEX_MODE(operation) \ + (operation & BIT(11) ? SPI_CTL0_FRF_MOTOROLA_3WIRE : SPI_CTL0_FRF_MOTOROLA_4WIRE) + +/* TI uses fixed frame format; Motorola combines duplex, polarity, phase */ +#define FRAME_FORMAT_MODE(operation) \ + (operation & SPI_FRAME_FORMAT_TI \ + ? SPI_CTL0_FRF_TI_SYNC \ + : DUPLEX_MODE(operation) | POLARITY_MODE(operation) | PHASE_MODE(operation)) + +/* Computes the minimum number of bytes per frame using word_size. Utilizes ceil + * division, to ensure sufficient byte allocation for non-multiples of 8 bits + */ +#define BYTES_PER_FRAME(word_size) (((word_size) + 7) / 8) + +struct spi_mspm0_config { + SPI_Regs *spi_base; + const struct pinctrl_dev_config *pinctrl; + + const DL_SPI_ClockConfig clock_config; + const struct mspm0_sys_clock *clock_subsys; + void (*irq_config_func)(const struct device *dev); +}; + +struct spi_mspm0_data { + struct spi_context spi_ctx; + struct k_sem spi_idle; +}; + +static void spi_mspm0_isr(const struct device *dev) +{ + const struct spi_mspm0_config *config = dev->config; + struct spi_mspm0_data *data = dev->data; + + if (DL_SPI_getPendingInterrupt(config->spi_base) == DL_SPI_IIDX_IDLE) { + DL_SPI_disableInterrupt(config->spi_base, DL_SPI_INTERRUPT_IDLE); + k_sem_give(&data->spi_idle); + } +} + +static int spi_mspm0_configure(const struct device *dev, const struct spi_config *spi_cfg, + uint8_t dfs) +{ + const struct spi_mspm0_config *const config = dev->config; + struct spi_mspm0_data *const data = dev->data; + const struct device *clk_dev = DEVICE_DT_GET(DT_NODELABEL(ckm)); + + uint32_t clock_rate; + uint16_t clock_scr; + int ret; + + if (spi_context_configured(&data->spi_ctx, spi_cfg)) { + /* This configuration is already in use */ + return 0; + } + + /* Only master mode is supported */ + if (SPI_OP_MODE_GET(spi_cfg->operation) != SPI_OP_MODE_MASTER) { + return -ENOTSUP; + } + + ret = clock_control_get_rate(clk_dev, (struct mspm0_sys_clock *)config->clock_subsys, + &clock_rate); + if (ret < 0) { + return ret; + } + + if (spi_cfg->frequency > (clock_rate / 2)) { + return -EINVAL; + } + + /* See DL_SPI_setBitRateSerialClockDivider for details */ + clock_scr = (clock_rate / (2 * spi_cfg->frequency)) - 1; + if (!IN_RANGE(clock_scr, MSPM0_SPI_SCR_MIN, MSPM0_SPI_SCR_MAX)) { + return -EINVAL; + } + + const DL_SPI_Config dl_spi_cfg = { + .parity = DL_SPI_PARITY_NONE, /* Currently unused in zephyr */ + .chipSelectPin = DL_SPI_CHIP_SELECT_NONE, /* spi_context controls the CS */ + .mode = SPI_MODE(spi_cfg->operation), + .bitOrder = BIT_ORDER_MODE(spi_cfg->operation), + .dataSize = DATA_SIZE_MODE(spi_cfg->operation), + .frameFormat = FRAME_FORMAT_MODE(spi_cfg->operation), + }; + + /* Peripheral should be disabled before applying a new configuration */ + DL_SPI_disable(config->spi_base); + + DL_SPI_init(config->spi_base, (DL_SPI_Config *)&dl_spi_cfg); + DL_SPI_setBitRateSerialClockDivider(config->spi_base, (uint32_t)clock_scr); + + if (dfs > SPI_DFS_16BIT) { + DL_SPI_enablePacking(config->spi_base); + } else { + DL_SPI_disablePacking(config->spi_base); + } + + if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_LOOP) { + DL_SPI_enableLoopbackMode(config->spi_base); + } else { + DL_SPI_disableLoopbackMode(config->spi_base); + } + + DL_SPI_enable(config->spi_base); + + /* Cache SPI config for reuse, required by spi_context owner */ + data->spi_ctx.config = spi_cfg; + + return 0; +} + +static void spi_mspm0_frame_tx(const struct device *dev, uint8_t dfs) +{ + const struct spi_mspm0_config *config = dev->config; + struct spi_mspm0_data *data = dev->data; + + /* Transmit dummy frame when no TX data is provided */ + uint32_t tx_frame = 0; + + k_sem_reset(&data->spi_idle); + + if (spi_context_tx_buf_on(&data->spi_ctx)) { + if (dfs == SPI_DFS_8BIT) { + tx_frame = UNALIGNED_GET((uint8_t *)(data->spi_ctx.tx_buf)); + } else if (dfs == SPI_DFS_16BIT) { + tx_frame = UNALIGNED_GET((uint16_t *)(data->spi_ctx.tx_buf)); + } else { + tx_frame = UNALIGNED_GET((uint32_t *)(data->spi_ctx.tx_buf)); + } + } + DL_SPI_transmitDataCheck32(config->spi_base, tx_frame); + + DL_SPI_enableInterrupt(config->spi_base, DL_SPI_INTERRUPT_IDLE); + k_sem_take(&data->spi_idle, K_FOREVER); + + spi_context_update_tx(&data->spi_ctx, dfs, 1); +} + +static void spi_mspm0_frame_rx(const struct device *dev, uint8_t dfs) +{ + const struct spi_mspm0_config *config = dev->config; + struct spi_mspm0_data *data = dev->data; + uint32_t rx_val = 0; + + DL_SPI_receiveDataCheck32(config->spi_base, &rx_val); + + if (!spi_context_rx_buf_on(&data->spi_ctx)) { + return; + } + + if (dfs == SPI_DFS_8BIT) { + UNALIGNED_PUT((uint8_t)rx_val, (uint8_t *)data->spi_ctx.rx_buf); + } else if (dfs == SPI_DFS_16BIT) { + UNALIGNED_PUT((uint16_t)rx_val, (uint16_t *)data->spi_ctx.rx_buf); + } else { + UNALIGNED_PUT(rx_val, (uint32_t *)data->spi_ctx.rx_buf); + } + + spi_context_update_rx(&data->spi_ctx, dfs, 1); +} + +static void spi_mspm0_start_transfer(const struct device *dev, uint8_t dfs) +{ + struct spi_mspm0_data *data = dev->data; + + spi_context_cs_control(&data->spi_ctx, true); + + while (spi_context_tx_on(&data->spi_ctx) || spi_context_rx_on(&data->spi_ctx)) { + spi_mspm0_frame_tx(dev, dfs); + spi_mspm0_frame_rx(dev, dfs); + } + + spi_context_cs_control(&data->spi_ctx, false); + spi_context_complete(&data->spi_ctx, dev, 0); +} + +static int spi_mspm0_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) +{ + struct spi_mspm0_data *data = dev->data; + int ret; + uint8_t dfs; + + if (!tx_bufs && !rx_bufs) { + return 0; + } + + spi_context_lock(&data->spi_ctx, false, NULL, NULL, spi_cfg); + + dfs = BYTES_PER_FRAME(SPI_WORD_SIZE_GET(spi_cfg->operation)); + + ret = spi_mspm0_configure(dev, spi_cfg, dfs); + if (ret != 0) { + spi_context_release(&data->spi_ctx, ret); + return ret; + } + + spi_context_buffers_setup(&data->spi_ctx, tx_bufs, rx_bufs, dfs); + + spi_mspm0_start_transfer(dev, dfs); + + ret = spi_context_wait_for_completion(&data->spi_ctx); + spi_context_release(&data->spi_ctx, ret); + + return ret; +} + +static int spi_mspm0_release(const struct device *dev, const struct spi_config *spi_config) +{ + const struct spi_mspm0_config *config = dev->config; + struct spi_mspm0_data *data = dev->data; + + if (!spi_context_configured(&data->spi_ctx, spi_config)) { + return -EINVAL; + } + + if (DL_SPI_isBusy(config->spi_base)) { + return -EBUSY; + } + + spi_context_unlock_unconditionally(&data->spi_ctx); + return 0; +} + +static const struct spi_driver_api spi_mspm0_api = { + .transceive = spi_mspm0_transceive, + .release = spi_mspm0_release, +}; + +static int spi_mspm0_init(const struct device *dev) +{ + const struct spi_mspm0_config *config = dev->config; + struct spi_mspm0_data *data = dev->data; + int ret; + + DL_SPI_enablePower(config->spi_base); + delay_cycles(POWER_STARTUP_DELAY); + + ret = pinctrl_apply_state(config->pinctrl, PINCTRL_STATE_DEFAULT); + if (ret < 0) { + return ret; + } + + ret = spi_context_cs_configure_all(&data->spi_ctx); + if (ret < 0) { + return ret; + } + + DL_SPI_setClockConfig(config->spi_base, (DL_SPI_ClockConfig *)&config->clock_config); + DL_SPI_enable(config->spi_base); + + spi_context_unlock_unconditionally(&data->spi_ctx); + + config->irq_config_func(dev); + + return ret; +} + +#define MSPM0_SPI_INIT(inst) \ + PINCTRL_DT_INST_DEFINE(inst); \ + \ + static void spi_mspm0_irq_config_##inst(const struct device *dev) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(inst), DT_INST_IRQ(inst, priority), spi_mspm0_isr, \ + DEVICE_DT_INST_GET(inst), 0); \ + irq_enable(DT_INST_IRQN(inst)); \ + }; \ + \ + static const struct mspm0_sys_clock mspm0_spi_sys_clock##inst = \ + MSPM0_CLOCK_SUBSYS_FN(inst); \ + \ + static struct spi_mspm0_config spi_mspm0_config_##inst = { \ + .spi_base = (SPI_Regs *)DT_INST_REG_ADDR(inst), \ + .pinctrl = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \ + .clock_config = {.clockSel = \ + MSPM0_CLOCK_PERIPH_REG_MASK(DT_INST_CLOCKS_CELL(inst, clk)), \ + .divideRatio = SPI_DT_CLK_DIV_ENUM(inst)}, \ + .clock_subsys = &mspm0_spi_sys_clock##inst, \ + .irq_config_func = spi_mspm0_irq_config_##inst, \ + }; \ + \ + static struct spi_mspm0_data spi_mspm0_data_##inst = { \ + .spi_idle = Z_SEM_INITIALIZER(spi_mspm0_data_##inst.spi_idle, 0, 1), \ + SPI_CONTEXT_INIT_LOCK(spi_mspm0_data_##inst, spi_ctx), \ + SPI_CONTEXT_INIT_SYNC(spi_mspm0_data_##inst, spi_ctx), \ + SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(inst), spi_ctx) \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, spi_mspm0_init, NULL, &spi_mspm0_data_##inst, \ + &spi_mspm0_config_##inst, POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, \ + &spi_mspm0_api); + +DT_INST_FOREACH_STATUS_OKAY(MSPM0_SPI_INIT) diff --git a/dts/arm/ti/mspm0/mspm0.dtsi b/dts/arm/ti/mspm0/mspm0.dtsi index 68c23dca6fa6f..b2bc8b7820bf6 100644 --- a/dts/arm/ti/mspm0/mspm0.dtsi +++ b/dts/arm/ti/mspm0/mspm0.dtsi @@ -148,6 +148,20 @@ clk-div = <1>; status = "disabled"; }; + + spi0: spi@40468000 { + compatible = "ti,mspm0-spi"; + reg = <0x40468000 0x2000>; + interrupts = <9 0>; + status = "disabled"; + }; + + spi1: spi@4046a000 { + compatible = "ti,mspm0-spi"; + reg = <0x4046a000 0x2000>; + interrupts = <10 0>; + status = "disabled"; + }; }; }; diff --git a/dts/bindings/spi/ti,mspm0-spi.yaml b/dts/bindings/spi/ti,mspm0-spi.yaml new file mode 100644 index 0000000000000..419eb76efc76a --- /dev/null +++ b/dts/bindings/spi/ti,mspm0-spi.yaml @@ -0,0 +1,34 @@ +# Copyright (c) 2025 Linumiz GmbH +# SPDX-License-Identifier: Apache-2.0 + +description: | + Texas Instruments MSPM0 SERIAL PERIPHERAL INTERFACE (SPI). + +compatible: "ti,mspm0-spi" +include: [spi-controller.yaml, pinctrl-device.yaml] + +properties: + reg: + required: true + + pinctrl-0: + required: true + + clocks: + required: true + + interrupts: + required: true + + ti,clk-div: + type: int + default: 1 + enum: + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + - 8 diff --git a/modules/Kconfig.mspm0 b/modules/Kconfig.mspm0 index 7125ff4919672..4f542a5f6a7b2 100644 --- a/modules/Kconfig.mspm0 +++ b/modules/Kconfig.mspm0 @@ -14,3 +14,6 @@ config USE_MSPM0_DL_UART config USE_MSPM0_DL_TIMER bool + +config USE_MSPM0_DL_SPI + bool