Skip to content

Commit ffa9236

Browse files
committed
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: Hans Binderup <[email protected]> Signed-off-by: Jackson Farley <[email protected]> Signed-off-by: Santhosh Charles <[email protected]>
1 parent f8ced6f commit ffa9236

File tree

5 files changed

+325
-0
lines changed

5 files changed

+325
-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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
select USE_MSPM0_DL_SPI
8+
select PINCTRL
9+
help
10+
This driver enable TI MSPM0 spi driver.

drivers/spi/spi_mspm0.c

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
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/logging/log.h>
16+
17+
/* TI DriverLib includes */
18+
#include <driverlib/dl_spi.h>
19+
20+
LOG_MODULE_REGISTER(spi_mspm0, CONFIG_SPI_LOG_LEVEL);
21+
22+
/* This must be included after log module registration */
23+
#include "spi_context.h"
24+
25+
#define POWER_STARTUP_DELAY 16
26+
27+
#define SPI_DT_CLK_DIV(inst) DT_INST_PROP(inst, clk_div)
28+
29+
#define SPI_DT_CLK_DIV_ENUM(inst) _CONCAT(DL_SPI_CLOCK_DIVIDE_RATIO_, SPI_DT_CLK_DIV(inst))
30+
31+
#define SPI_MODE(operation) (operation & BIT(0) ? DL_SPI_MODE_PERIPHERAL : DL_SPI_MODE_CONTROLLER)
32+
33+
#define BIT_ORDER_MODE(operation) \
34+
(operation & BIT(4) ? DL_SPI_BIT_ORDER_LSB_FIRST : DL_SPI_BIT_ORDER_MSB_FIRST)
35+
36+
#define DATA_SIZE_MODE(operation) (SPI_WORD_SIZE_GET(operation) - 1)
37+
38+
#define POLARITY_MODE(operation) \
39+
(SPI_MODE_GET(operation) & SPI_MODE_CPOL ? SPI_CTL0_SPO_HIGH : SPI_CTL0_SPO_LOW)
40+
41+
#define PHASE_MODE(operation) \
42+
(SPI_MODE_GET(operation) & SPI_MODE_CPHA ? SPI_CTL0_SPH_SECOND : SPI_CTL0_SPH_FIRST)
43+
44+
#define DUPLEX_MODE(operation) \
45+
(operation & BIT(11) ? SPI_CTL0_FRF_MOTOROLA_3WIRE : SPI_CTL0_FRF_MOTOROLA_4WIRE)
46+
47+
/* Only motorola format requires config - TI format is a single value */
48+
#define FRAME_FORMAT_MODE(operation) \
49+
(operation & SPI_FRAME_FORMAT_TI \
50+
? SPI_CTL0_FRF_TI_SYNC \
51+
: DUPLEX_MODE(operation) | POLARITY_MODE(operation) | PHASE_MODE(operation))
52+
53+
/* 0x38 represents the bits 8, 16 and 32. Knowing that 24 is bits 8 and 16
54+
* These are the bits were when you divide by 8, you keep the result as it is.
55+
* For all the other ones, 4 to 7, 9 to 15, etc... you need a +1,
56+
* since on such division it takes only the result above 0
57+
*/
58+
#define BYTES_PER_FRAME(word_size) \
59+
(((word_size) & ~0x38) ? (((word_size) / 8) + 1) : ((word_size) / 8))
60+
61+
struct spi_mspm0_config {
62+
SPI_Regs *base;
63+
uint32_t clock_frequency;
64+
const struct pinctrl_dev_config *pinctrl;
65+
const DL_SPI_ClockConfig clock_config;
66+
};
67+
68+
struct spi_mspm0_data {
69+
struct spi_context ctx;
70+
uint8_t dfs;
71+
};
72+
73+
static int spi_mspm0_configure(const struct device *dev, const struct spi_config *spi_cfg)
74+
{
75+
const struct spi_mspm0_config *const cfg = dev->config;
76+
struct spi_mspm0_data *const data = dev->data;
77+
struct spi_context *ctx = &data->ctx;
78+
uint16_t clock_scr;
79+
80+
if (spi_context_configured(ctx, spi_cfg)) {
81+
/* This configuration is already in use */
82+
return 0;
83+
}
84+
85+
/* Only master mode is supported */
86+
if (SPI_OP_MODE_GET(spi_cfg->operation) != SPI_OP_MODE_MASTER) {
87+
return -ENOTSUP;
88+
}
89+
90+
if (spi_cfg->frequency > (cfg->clock_frequency / 2)) {
91+
return -EINVAL;
92+
}
93+
94+
/* See DL_SPI_setBitRateSerialClockDivider for details */
95+
clock_scr = (cfg->clock_frequency / (2 * spi_cfg->frequency)) - 1;
96+
97+
if (!IN_RANGE(clock_scr, 0, 1023)) {
98+
return -EINVAL;
99+
}
100+
101+
const DL_SPI_Config dl_cfg = {
102+
.parity = DL_SPI_PARITY_NONE, /* Currently unused in zephyr */
103+
.chipSelectPin = DL_SPI_CHIP_SELECT_NONE, /* spi_context controls the CS */
104+
.mode = SPI_MODE(spi_cfg->operation),
105+
.bitOrder = BIT_ORDER_MODE(spi_cfg->operation),
106+
.dataSize = DATA_SIZE_MODE(spi_cfg->operation),
107+
.frameFormat = FRAME_FORMAT_MODE(spi_cfg->operation),
108+
};
109+
110+
/* Peripheral should always be disabled prior to applying a new configuration */
111+
DL_SPI_disable(cfg->base);
112+
DL_SPI_init(cfg->base, (DL_SPI_Config *)&dl_cfg);
113+
DL_SPI_setBitRateSerialClockDivider(cfg->base, (uint32_t)clock_scr);
114+
115+
data->dfs = BYTES_PER_FRAME(SPI_WORD_SIZE_GET(spi_cfg->operation));
116+
if (data->dfs > 2) {
117+
DL_SPI_enablePacking(cfg->base);
118+
} else {
119+
DL_SPI_disablePacking(cfg->base);
120+
}
121+
122+
if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_LOOP) {
123+
DL_SPI_enableLoopbackMode(cfg->base);
124+
} else {
125+
DL_SPI_disableLoopbackMode(cfg->base);
126+
}
127+
128+
DL_SPI_enable(cfg->base);
129+
if (!DL_SPI_isEnabled(cfg->base)) {
130+
return -EAGAIN;
131+
}
132+
133+
/* Save config so it can be reused, it's required for the lock owner to work */
134+
ctx->config = spi_cfg;
135+
136+
return 0;
137+
}
138+
139+
static void spi_mspm0_frame_tx(const struct device *dev)
140+
{
141+
const struct spi_mspm0_config *cfg = dev->config;
142+
struct spi_mspm0_data *data = dev->data;
143+
struct spi_context *ctx = &data->ctx;
144+
145+
/* NOP TX if no data is expected */
146+
uint32_t tx_frame = 0;
147+
148+
if (spi_context_tx_buf_on(ctx)) {
149+
if (data->dfs == 1) {
150+
tx_frame = UNALIGNED_GET((uint8_t *)(ctx->tx_buf));
151+
DL_SPI_transmitDataCheck8(cfg->base, (uint8_t)tx_frame);
152+
} else if (data->dfs == 2) {
153+
tx_frame = UNALIGNED_GET((uint16_t *)(ctx->tx_buf));
154+
DL_SPI_transmitDataCheck16(cfg->base, (uint16_t)tx_frame);
155+
} else {
156+
tx_frame = UNALIGNED_GET((uint32_t *)(ctx->tx_buf));
157+
DL_SPI_transmitDataCheck32(cfg->base, tx_frame);
158+
}
159+
}
160+
161+
while (DL_SPI_isBusy(cfg->base)) {
162+
/* Wait for tx fifo to be sent */
163+
}
164+
spi_context_update_tx(ctx, data->dfs, 1);
165+
}
166+
167+
static void spi_mspm0_frame_rx(const struct device *dev)
168+
{
169+
const struct spi_mspm0_config *cfg = dev->config;
170+
struct spi_mspm0_data *data = dev->data;
171+
struct spi_context *ctx = &data->ctx;
172+
uint32_t rx_val = 0;
173+
174+
if (data->dfs == 1) {
175+
if (DL_SPI_receiveDataCheck8(cfg->base, (uint8_t *)&rx_val)) {
176+
UNALIGNED_PUT((uint8_t)rx_val, (uint8_t *)ctx->rx_buf);
177+
}
178+
} else if (data->dfs == 2) {
179+
if (DL_SPI_receiveDataCheck16(cfg->base, (uint16_t *)&rx_val)) {
180+
UNALIGNED_PUT((uint16_t)rx_val, (uint16_t *)ctx->rx_buf);
181+
}
182+
} else {
183+
if (DL_SPI_receiveDataCheck32(cfg->base, &rx_val)) {
184+
UNALIGNED_PUT(rx_val, (uint32_t *)ctx->rx_buf);
185+
}
186+
}
187+
188+
spi_context_update_rx(ctx, data->dfs, 1);
189+
}
190+
191+
static void spi_mspm0_start_transfer(const struct device *dev, const struct spi_config *spi_cfg)
192+
{
193+
struct spi_mspm0_data *data = dev->data;
194+
struct spi_context *ctx = &data->ctx;
195+
196+
spi_context_cs_control(ctx, true);
197+
198+
while (spi_context_tx_on(ctx) || spi_context_rx_on(ctx)) {
199+
spi_mspm0_frame_tx(dev);
200+
spi_mspm0_frame_rx(dev);
201+
}
202+
203+
spi_context_cs_control(ctx, false);
204+
spi_context_complete(ctx, dev, 0);
205+
}
206+
207+
static int spi_mspm0_transceive(const struct device *dev,
208+
const struct spi_config *spi_cfg,
209+
const struct spi_buf_set *tx_bufs,
210+
const struct spi_buf_set *rx_bufs)
211+
{
212+
struct spi_mspm0_data *data = dev->data;
213+
struct spi_context *ctx = &data->ctx;
214+
int ret;
215+
216+
if (!tx_bufs && !rx_bufs) {
217+
return 0;
218+
}
219+
220+
spi_context_lock(ctx, false, NULL, NULL, spi_cfg);
221+
222+
ret = spi_mspm0_configure(dev, spi_cfg);
223+
if (ret != 0) {
224+
spi_context_release(ctx, ret);
225+
return ret;
226+
}
227+
228+
spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, data->dfs);
229+
230+
spi_mspm0_start_transfer(dev, spi_cfg);
231+
232+
ret = spi_context_wait_for_completion(ctx);
233+
spi_context_release(ctx, ret);
234+
235+
return ret;
236+
}
237+
238+
static int spi_mspm0_release(const struct device *dev, const struct spi_config *config)
239+
{
240+
const struct spi_mspm0_config *cfg = dev->config;
241+
struct spi_mspm0_data *data = dev->data;
242+
struct spi_context *ctx = &data->ctx;
243+
244+
if (!spi_context_configured(ctx, config)) {
245+
return -EINVAL;
246+
}
247+
248+
if (DL_SPI_isBusy(cfg->base)) {
249+
return -EBUSY;
250+
}
251+
252+
spi_context_unlock_unconditionally(ctx);
253+
return 0;
254+
}
255+
256+
static const struct spi_driver_api spi_mspm0_api = {
257+
.transceive = spi_mspm0_transceive,
258+
.release = spi_mspm0_release,
259+
};
260+
261+
static int spi_mspm0_init(const struct device *dev)
262+
{
263+
const struct spi_mspm0_config *cfg = dev->config;
264+
struct spi_mspm0_data *data = dev->data;
265+
struct spi_context *ctx = &data->ctx;
266+
int ret;
267+
268+
DL_SPI_enablePower(cfg->base);
269+
270+
delay_cycles(POWER_STARTUP_DELAY);
271+
272+
ret = pinctrl_apply_state(cfg->pinctrl, PINCTRL_STATE_DEFAULT);
273+
if (ret < 0) {
274+
return ret;
275+
}
276+
277+
ret = spi_context_cs_configure_all(ctx);
278+
if (ret < 0) {
279+
return ret;
280+
}
281+
282+
DL_SPI_setClockConfig(cfg->base, (DL_SPI_ClockConfig *)&cfg->clock_config);
283+
DL_SPI_enable(cfg->base);
284+
285+
spi_context_unlock_unconditionally(ctx);
286+
return ret;
287+
}
288+
289+
#define MSPM0_SPI_INIT(inst) \
290+
PINCTRL_DT_INST_DEFINE(inst); \
291+
\
292+
static struct spi_mspm0_config spi_mspm0_##inst##_cfg = { \
293+
.base = (SPI_Regs *)DT_INST_REG_ADDR(inst), \
294+
.pinctrl = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
295+
.clock_config = {.clockSel = \
296+
MSPM0_CLOCK_PERIPH_REG_MASK(DT_INST_CLOCKS_CELL(inst, clk)), \
297+
.divideRatio = SPI_DT_CLK_DIV_ENUM(inst)}, \
298+
.clock_frequency = DT_PROP(DT_INST_CLOCKS_CTLR(inst), clock_frequency), \
299+
}; \
300+
\
301+
static struct spi_mspm0_data spi_mspm0_##inst##_data = { \
302+
SPI_CONTEXT_INIT_LOCK(spi_mspm0_##inst##_data, ctx), \
303+
SPI_CONTEXT_INIT_SYNC(spi_mspm0_##inst##_data, ctx), \
304+
SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(inst), ctx)}; \
305+
\
306+
DEVICE_DT_INST_DEFINE(inst, spi_mspm0_init, NULL, &spi_mspm0_##inst##_data, \
307+
&spi_mspm0_##inst##_cfg, POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, \
308+
&spi_mspm0_api);
309+
310+
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)