Skip to content

Commit cc5ab0a

Browse files
drivers: spi: Add driver support for TI MSPM0 SPI module
Add support for the SPI module on TI’s MSPM0 MCUs. The driver supports master mode transfers with configurable frame size (4–16 bits), clock polarity/phase, bit order. Signed-off-by: Santhosh Charles <[email protected]> Signed-off-by: Jackson Farley <[email protected]> Co-authored-by: Hans Binderup <[email protected]>
1 parent dfefa15 commit cc5ab0a

File tree

5 files changed

+369
-0
lines changed

5 files changed

+369
-0
lines changed

drivers/spi/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_ECSPI spi_mcux_ecspi.c)
4040
zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_FLEXCOMM spi_mcux_flexcomm.c)
4141
zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_FLEXIO spi_mcux_flexio.c)
4242
zephyr_library_sources_ifdef(CONFIG_SPI_MEC5_QSPI spi_mchp_mec5_qspi.c)
43+
zephyr_library_sources_ifdef(CONFIG_SPI_MSPM0 spi_mspm0.c)
4344
zephyr_library_sources_ifdef(CONFIG_SPI_NPCX_SPIP spi_npcx_spip.c)
4445
zephyr_library_sources_ifdef(CONFIG_SPI_NRFX_SPI spi_nrfx_spi.c spi_nrfx_common.c)
4546
zephyr_library_sources_ifdef(CONFIG_SPI_NRFX_SPIM spi_nrfx_spim.c spi_nrfx_common.c)

drivers/spi/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ source "drivers/spi/Kconfig.mcux_ecspi"
119119
source "drivers/spi/Kconfig.mcux_flexcomm"
120120
source "drivers/spi/Kconfig.mcux_flexio"
121121
source "drivers/spi/Kconfig.mec5"
122+
source "drivers/spi/Kconfig.mspm0"
122123
source "drivers/spi/Kconfig.npcx"
123124
source "drivers/spi/Kconfig.nrfx"
124125
source "drivers/spi/Kconfig.numaker"

drivers/spi/Kconfig.mspm0

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Copyright (c) 2025 Linumiz GmbH
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config SPI_MSPM0
5+
bool "TI's MSPM0 SPI"
6+
default y
7+
depends on DT_HAS_TI_MSPM0_SPI_ENABLED
8+
select USE_MSPM0_DL_SPI
9+
select PINCTRL
10+
help
11+
This driver enable TI MSPM0 spi driver.

drivers/spi/spi_mspm0.c

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
/*
2+
* Copyright (c) 2024 Bang & Olufsen A/S, Denmark
3+
* Copyright (c) 2025 Linumiz GmbH
4+
*
5+
* SPDX-License-Identifier: Apache-2.0
6+
*/
7+
8+
#define DT_DRV_COMPAT ti_mspm0_spi
9+
10+
#include <zephyr/device.h>
11+
#include <zephyr/drivers/clock_control.h>
12+
#include <zephyr/drivers/clock_control/mspm0_clock_control.h>
13+
#include <zephyr/drivers/pinctrl.h>
14+
#include <zephyr/drivers/spi.h>
15+
#include <zephyr/irq.h>
16+
#include <zephyr/logging/log.h>
17+
18+
/* TI DriverLib includes */
19+
#include <driverlib/dl_spi.h>
20+
21+
LOG_MODULE_REGISTER(spi_mspm0, CONFIG_SPI_LOG_LEVEL);
22+
23+
/* This must be included after log module registration */
24+
#include "spi_context.h"
25+
26+
/* Data Frame Size (DFS) */
27+
#define SPI_DFS_8BIT 1
28+
#define SPI_DFS_16BIT 2
29+
30+
/* Range for SPI Serial Clock Rate (SCR) */
31+
#define MSPM0_SPI_SCR_MIN 0
32+
#define MSPM0_SPI_SCR_MAX 1023
33+
34+
/* Delay after enabling power for SPI module */
35+
#define POWER_STARTUP_DELAY 16
36+
37+
#define SPI_DT_CLK_DIV(inst) \
38+
DT_INST_PROP(inst, ti_clk_div)
39+
40+
#define SPI_DT_CLK_DIV_ENUM(inst) \
41+
_CONCAT(DL_SPI_CLOCK_DIVIDE_RATIO_, SPI_DT_CLK_DIV(inst))
42+
43+
#define SPI_MODE(operation) \
44+
(operation & BIT(0) ? DL_SPI_MODE_PERIPHERAL : DL_SPI_MODE_CONTROLLER)
45+
46+
#define BIT_ORDER_MODE(operation) \
47+
(operation & BIT(4) ? DL_SPI_BIT_ORDER_LSB_FIRST : DL_SPI_BIT_ORDER_MSB_FIRST)
48+
49+
/* MSPM0 DSS field expects word size - 1 */
50+
#define DATA_SIZE_MODE(operation) \
51+
(SPI_WORD_SIZE_GET(operation) - 1)
52+
53+
#define POLARITY_MODE(operation) \
54+
(SPI_MODE_GET(operation) & SPI_MODE_CPOL ? SPI_CTL0_SPO_HIGH : SPI_CTL0_SPO_LOW)
55+
56+
#define PHASE_MODE(operation) \
57+
(SPI_MODE_GET(operation) & SPI_MODE_CPHA ? SPI_CTL0_SPH_SECOND : SPI_CTL0_SPH_FIRST)
58+
59+
#define DUPLEX_MODE(operation) \
60+
(operation & BIT(11) ? SPI_CTL0_FRF_MOTOROLA_3WIRE : SPI_CTL0_FRF_MOTOROLA_4WIRE)
61+
62+
/* TI uses fixed frame format; Motorola combines duplex, polarity, phase */
63+
#define FRAME_FORMAT_MODE(operation) \
64+
(operation & SPI_FRAME_FORMAT_TI \
65+
? SPI_CTL0_FRF_TI_SYNC \
66+
: DUPLEX_MODE(operation) | POLARITY_MODE(operation) | PHASE_MODE(operation))
67+
68+
/* Computes the minimum number of bytes per frame using word_size. Utilizes ceil
69+
* division, to ensure sufficient byte allocation for non-multiples of 8 bits
70+
*/
71+
#define BYTES_PER_FRAME(word_size) (((word_size) + 7) / 8)
72+
73+
struct spi_mspm0_config {
74+
SPI_Regs *spi_base;
75+
const struct pinctrl_dev_config *pinctrl;
76+
77+
const DL_SPI_ClockConfig clock_config;
78+
const struct mspm0_sys_clock *clock_subsys;
79+
void (*irq_config_func)(const struct device *dev);
80+
};
81+
82+
struct spi_mspm0_data {
83+
struct spi_context spi_ctx;
84+
struct k_sem spi_idle;
85+
};
86+
87+
static void spi_mspm0_isr(const struct device *dev)
88+
{
89+
const struct spi_mspm0_config *config = dev->config;
90+
struct spi_mspm0_data *data = dev->data;
91+
92+
if (DL_SPI_getPendingInterrupt(config->spi_base) == DL_SPI_IIDX_IDLE) {
93+
DL_SPI_disableInterrupt(config->spi_base, DL_SPI_INTERRUPT_IDLE);
94+
k_sem_give(&data->spi_idle);
95+
}
96+
}
97+
98+
static int spi_mspm0_configure(const struct device *dev, const struct spi_config *spi_cfg,
99+
uint8_t dfs)
100+
{
101+
const struct spi_mspm0_config *const config = dev->config;
102+
struct spi_mspm0_data *const data = dev->data;
103+
const struct device *clk_dev = DEVICE_DT_GET(DT_NODELABEL(ckm));
104+
105+
uint32_t clock_rate;
106+
uint16_t clock_scr;
107+
int ret;
108+
109+
if (spi_context_configured(&data->spi_ctx, spi_cfg)) {
110+
/* This configuration is already in use */
111+
return 0;
112+
}
113+
114+
/* Only master mode is supported */
115+
if (SPI_OP_MODE_GET(spi_cfg->operation) != SPI_OP_MODE_MASTER) {
116+
return -ENOTSUP;
117+
}
118+
119+
ret = clock_control_get_rate(clk_dev, (struct mspm0_sys_clock *)config->clock_subsys,
120+
&clock_rate);
121+
if (ret < 0) {
122+
return ret;
123+
}
124+
125+
if (spi_cfg->frequency > (clock_rate / 2)) {
126+
return -EINVAL;
127+
}
128+
129+
/* See DL_SPI_setBitRateSerialClockDivider for details */
130+
clock_scr = (clock_rate / (2 * spi_cfg->frequency)) - 1;
131+
if (!IN_RANGE(clock_scr, MSPM0_SPI_SCR_MIN, MSPM0_SPI_SCR_MAX)) {
132+
return -EINVAL;
133+
}
134+
135+
const DL_SPI_Config dl_spi_cfg = {
136+
.parity = DL_SPI_PARITY_NONE, /* Currently unused in zephyr */
137+
.chipSelectPin = DL_SPI_CHIP_SELECT_NONE, /* spi_context controls the CS */
138+
.mode = SPI_MODE(spi_cfg->operation),
139+
.bitOrder = BIT_ORDER_MODE(spi_cfg->operation),
140+
.dataSize = DATA_SIZE_MODE(spi_cfg->operation),
141+
.frameFormat = FRAME_FORMAT_MODE(spi_cfg->operation),
142+
};
143+
144+
/* Peripheral should be disabled before applying a new configuration */
145+
DL_SPI_disable(config->spi_base);
146+
147+
DL_SPI_init(config->spi_base, (DL_SPI_Config *)&dl_spi_cfg);
148+
DL_SPI_setBitRateSerialClockDivider(config->spi_base, (uint32_t)clock_scr);
149+
150+
if (dfs > SPI_DFS_16BIT) {
151+
DL_SPI_enablePacking(config->spi_base);
152+
} else {
153+
DL_SPI_disablePacking(config->spi_base);
154+
}
155+
156+
if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_LOOP) {
157+
DL_SPI_enableLoopbackMode(config->spi_base);
158+
} else {
159+
DL_SPI_disableLoopbackMode(config->spi_base);
160+
}
161+
162+
DL_SPI_enable(config->spi_base);
163+
164+
/* Cache SPI config for reuse, required by spi_context owner */
165+
data->spi_ctx.config = spi_cfg;
166+
167+
return 0;
168+
}
169+
170+
static void spi_mspm0_frame_tx(const struct device *dev, uint8_t dfs)
171+
{
172+
const struct spi_mspm0_config *config = dev->config;
173+
struct spi_mspm0_data *data = dev->data;
174+
175+
/* Transmit dummy frame when no TX data is provided */
176+
uint32_t tx_frame = 0;
177+
178+
k_sem_reset(&data->spi_idle);
179+
180+
if (spi_context_tx_buf_on(&data->spi_ctx)) {
181+
if (dfs == SPI_DFS_8BIT) {
182+
tx_frame = UNALIGNED_GET((uint8_t *)(data->spi_ctx.tx_buf));
183+
} else if (dfs == SPI_DFS_16BIT) {
184+
tx_frame = UNALIGNED_GET((uint16_t *)(data->spi_ctx.tx_buf));
185+
} else {
186+
tx_frame = UNALIGNED_GET((uint32_t *)(data->spi_ctx.tx_buf));
187+
}
188+
}
189+
DL_SPI_transmitDataCheck32(config->spi_base, tx_frame);
190+
191+
DL_SPI_enableInterrupt(config->spi_base, DL_SPI_INTERRUPT_IDLE);
192+
k_sem_take(&data->spi_idle, K_FOREVER);
193+
194+
spi_context_update_tx(&data->spi_ctx, dfs, 1);
195+
}
196+
197+
static void spi_mspm0_frame_rx(const struct device *dev, uint8_t dfs)
198+
{
199+
const struct spi_mspm0_config *config = dev->config;
200+
struct spi_mspm0_data *data = dev->data;
201+
uint32_t rx_val = 0;
202+
203+
DL_SPI_receiveDataCheck32(config->spi_base, &rx_val);
204+
205+
if (!spi_context_rx_buf_on(&data->spi_ctx)) {
206+
return;
207+
}
208+
209+
if (dfs == SPI_DFS_8BIT) {
210+
UNALIGNED_PUT((uint8_t)rx_val, (uint8_t *)data->spi_ctx.rx_buf);
211+
} else if (dfs == SPI_DFS_16BIT) {
212+
UNALIGNED_PUT((uint16_t)rx_val, (uint16_t *)data->spi_ctx.rx_buf);
213+
} else {
214+
UNALIGNED_PUT(rx_val, (uint32_t *)data->spi_ctx.rx_buf);
215+
}
216+
217+
spi_context_update_rx(&data->spi_ctx, dfs, 1);
218+
}
219+
220+
static void spi_mspm0_start_transfer(const struct device *dev, uint8_t dfs)
221+
{
222+
struct spi_mspm0_data *data = dev->data;
223+
224+
spi_context_cs_control(&data->spi_ctx, true);
225+
226+
while (spi_context_tx_on(&data->spi_ctx) || spi_context_rx_on(&data->spi_ctx)) {
227+
spi_mspm0_frame_tx(dev, dfs);
228+
spi_mspm0_frame_rx(dev, dfs);
229+
}
230+
231+
spi_context_cs_control(&data->spi_ctx, false);
232+
spi_context_complete(&data->spi_ctx, dev, 0);
233+
}
234+
235+
static int spi_mspm0_transceive(const struct device *dev,
236+
const struct spi_config *spi_cfg,
237+
const struct spi_buf_set *tx_bufs,
238+
const struct spi_buf_set *rx_bufs)
239+
{
240+
struct spi_mspm0_data *data = dev->data;
241+
int ret;
242+
uint8_t dfs;
243+
244+
if (!tx_bufs && !rx_bufs) {
245+
return 0;
246+
}
247+
248+
spi_context_lock(&data->spi_ctx, false, NULL, NULL, spi_cfg);
249+
250+
dfs = BYTES_PER_FRAME(SPI_WORD_SIZE_GET(spi_cfg->operation));
251+
252+
ret = spi_mspm0_configure(dev, spi_cfg, dfs);
253+
if (ret != 0) {
254+
spi_context_release(&data->spi_ctx, ret);
255+
return ret;
256+
}
257+
258+
spi_context_buffers_setup(&data->spi_ctx, tx_bufs, rx_bufs, dfs);
259+
260+
spi_mspm0_start_transfer(dev, dfs);
261+
262+
ret = spi_context_wait_for_completion(&data->spi_ctx);
263+
spi_context_release(&data->spi_ctx, ret);
264+
265+
return ret;
266+
}
267+
268+
static int spi_mspm0_release(const struct device *dev, const struct spi_config *spi_cfg)
269+
{
270+
const struct spi_mspm0_config *config = dev->config;
271+
struct spi_mspm0_data *data = dev->data;
272+
273+
if (!spi_context_configured(&data->spi_ctx, spi_cfg)) {
274+
return -EINVAL;
275+
}
276+
277+
if (DL_SPI_isBusy(config->spi_base)) {
278+
return -EBUSY;
279+
}
280+
281+
spi_context_unlock_unconditionally(&data->spi_ctx);
282+
return 0;
283+
}
284+
285+
static const struct spi_driver_api spi_mspm0_api = {
286+
.transceive = spi_mspm0_transceive,
287+
.release = spi_mspm0_release,
288+
};
289+
290+
static int spi_mspm0_init(const struct device *dev)
291+
{
292+
const struct spi_mspm0_config *config = dev->config;
293+
struct spi_mspm0_data *data = dev->data;
294+
int ret;
295+
296+
DL_SPI_enablePower(config->spi_base);
297+
delay_cycles(POWER_STARTUP_DELAY);
298+
299+
ret = pinctrl_apply_state(config->pinctrl, PINCTRL_STATE_DEFAULT);
300+
if (ret < 0) {
301+
return ret;
302+
}
303+
304+
ret = spi_context_cs_configure_all(&data->spi_ctx);
305+
if (ret < 0) {
306+
return ret;
307+
}
308+
309+
DL_SPI_setClockConfig(config->spi_base, (DL_SPI_ClockConfig *)&config->clock_config);
310+
DL_SPI_enable(config->spi_base);
311+
312+
spi_context_unlock_unconditionally(&data->spi_ctx);
313+
314+
config->irq_config_func(dev);
315+
316+
return ret;
317+
}
318+
319+
#define MSPM0_SPI_INIT(inst) \
320+
PINCTRL_DT_INST_DEFINE(inst); \
321+
\
322+
static void spi_mspm0_irq_config_##inst(const struct device *dev) \
323+
{ \
324+
IRQ_CONNECT(DT_INST_IRQN(inst), DT_INST_IRQ(inst, priority), spi_mspm0_isr, \
325+
DEVICE_DT_INST_GET(inst), 0); \
326+
irq_enable(DT_INST_IRQN(inst)); \
327+
}; \
328+
\
329+
static const struct mspm0_sys_clock mspm0_spi_sys_clock##inst = \
330+
MSPM0_CLOCK_SUBSYS_FN(inst); \
331+
\
332+
static struct spi_mspm0_config spi_mspm0_config_##inst = { \
333+
.spi_base = (SPI_Regs *)DT_INST_REG_ADDR(inst), \
334+
.pinctrl = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
335+
.clock_config = {.clockSel = \
336+
MSPM0_CLOCK_PERIPH_REG_MASK(DT_INST_CLOCKS_CELL(inst, clk)), \
337+
.divideRatio = SPI_DT_CLK_DIV_ENUM(inst)}, \
338+
.clock_subsys = &mspm0_spi_sys_clock##inst, \
339+
.irq_config_func = spi_mspm0_irq_config_##inst, \
340+
}; \
341+
\
342+
static struct spi_mspm0_data spi_mspm0_data_##inst = { \
343+
.spi_idle = Z_SEM_INITIALIZER(spi_mspm0_data_##inst.spi_idle, 0, 1), \
344+
SPI_CONTEXT_INIT_LOCK(spi_mspm0_data_##inst, spi_ctx), \
345+
SPI_CONTEXT_INIT_SYNC(spi_mspm0_data_##inst, spi_ctx), \
346+
SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(inst), spi_ctx) \
347+
}; \
348+
\
349+
DEVICE_DT_INST_DEFINE(inst, spi_mspm0_init, NULL, &spi_mspm0_data_##inst, \
350+
&spi_mspm0_config_##inst, POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, \
351+
&spi_mspm0_api);
352+
353+
DT_INST_FOREACH_STATUS_OKAY(MSPM0_SPI_INIT)

modules/Kconfig.mspm0

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ config USE_MSPM0_DL_UART
1414

1515
config USE_MSPM0_DL_TIMER
1616
bool
17+
18+
config USE_MSPM0_DL_SPI
19+
bool

0 commit comments

Comments
 (0)