Skip to content

Commit f81e755

Browse files
VynDragonkartben
authored andcommitted
drivers: spi: introduce basic spi driver for wch
introduces a basic SPI driver for CH32 series Signed-off-by: Camille BAUD <[email protected]>
1 parent a06ded8 commit f81e755

File tree

10 files changed

+373
-0
lines changed

10 files changed

+373
-0
lines changed

drivers/spi/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ zephyr_library_sources_ifdef(CONFIG_SPI_SMARTBOND spi_smartbond.c)
6464
zephyr_library_sources_ifdef(CONFIG_SPI_STM32 spi_ll_stm32.c)
6565
zephyr_library_sources_ifdef(CONFIG_SPI_TELINK_B91 spi_b91.c)
6666
zephyr_library_sources_ifdef(CONFIG_SPI_TEST spi_test.c)
67+
zephyr_library_sources_ifdef(CONFIG_SPI_WCH spi_wch.c)
6768
zephyr_library_sources_ifdef(CONFIG_SPI_XEC_QMSPI spi_xec_qmspi.c)
6869
zephyr_library_sources_ifdef(CONFIG_SPI_XEC_QMSPI_LDMA spi_xec_qmspi_ldma.c)
6970
zephyr_library_sources_ifdef(CONFIG_SPI_XLNX_AXI_QUADSPI spi_xlnx_axi_quadspi.c)

drivers/spi/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ source "drivers/spi/Kconfig.smartbond"
141141
source "drivers/spi/Kconfig.spi_emul"
142142
source "drivers/spi/Kconfig.stm32"
143143
source "drivers/spi/Kconfig.test"
144+
source "drivers/spi/Kconfig.wch"
144145
source "drivers/spi/Kconfig.xec_qmspi"
145146
source "drivers/spi/Kconfig.xlnx"
146147
source "drivers/spi/Kconfig.xmc4xxx"

drivers/spi/Kconfig.wch

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) 2025 MASSDRIVER EI (massdriver.space)
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config SPI_WCH
5+
bool "WCH SPI Controller"
6+
default y
7+
depends on DT_HAS_WCH_SPI_ENABLED
8+
help
9+
Enable the SPI peripherals on CH32s

drivers/spi/spi_wch.c

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
/*
2+
* Copyright (c) 2025 MASSDRIVER EI (massdriver.space)
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
#define DT_DRV_COMPAT wch_spi
7+
8+
#define LOG_LEVEL CONFIG_SPI_LOG_LEVEL
9+
#include <zephyr/logging/log.h>
10+
LOG_MODULE_REGISTER(spi_wch);
11+
12+
#include "spi_context.h"
13+
#include <errno.h>
14+
#include <zephyr/device.h>
15+
#include <zephyr/drivers/spi.h>
16+
#include <zephyr/drivers/spi/rtio.h>
17+
#include <zephyr/drivers/pinctrl.h>
18+
#include <zephyr/drivers/clock_control.h>
19+
20+
#include <hal_ch32fun.h>
21+
22+
#define SPI_CTLR1_LSBFIRST BIT(7)
23+
#define SPI_CTLR1_BR_POS 3
24+
25+
struct spi_wch_config {
26+
SPI_TypeDef *regs;
27+
const struct pinctrl_dev_config *pin_cfg;
28+
const struct device *clk_dev;
29+
uint8_t clock_id;
30+
};
31+
32+
struct spi_wch_data {
33+
struct spi_context ctx;
34+
};
35+
36+
static uint8_t spi_wch_get_br(uint32_t target_clock_ratio)
37+
{
38+
uint8_t prescaler;
39+
int prescaler_val = 2;
40+
41+
for (prescaler = 0; prescaler < 7; prescaler++) {
42+
if (prescaler_val > target_clock_ratio) {
43+
break;
44+
}
45+
prescaler_val *= 2;
46+
}
47+
48+
return prescaler;
49+
}
50+
51+
static int spi_wch_configure(const struct device *dev, const struct spi_config *config)
52+
{
53+
const struct spi_wch_config *cfg = dev->config;
54+
struct spi_wch_data *data = dev->data;
55+
SPI_TypeDef *regs = cfg->regs;
56+
int err;
57+
uint32_t clock_rate;
58+
clock_control_subsys_t clk_sys;
59+
int8_t prescaler;
60+
61+
if (spi_context_configured(&data->ctx, config)) {
62+
return 0;
63+
}
64+
65+
if ((config->operation & SPI_HALF_DUPLEX) != 0U) {
66+
LOG_ERR("Half-duplex not supported");
67+
return -ENOTSUP;
68+
}
69+
70+
if (SPI_OP_MODE_GET(config->operation) != SPI_OP_MODE_MASTER) {
71+
LOG_ERR("Slave mode not supported");
72+
return -ENOTSUP;
73+
}
74+
75+
if ((config->operation & SPI_MODE_LOOP) != 0U) {
76+
LOG_ERR("Loop mode not supported");
77+
return -ENOTSUP;
78+
}
79+
80+
if (SPI_WORD_SIZE_GET(config->operation) != 8) {
81+
LOG_ERR("Frame size != 8 bits not supported");
82+
return -ENOTSUP;
83+
}
84+
85+
regs->CTLR1 = 0;
86+
regs->CTLR2 = 0;
87+
regs->STATR = 0;
88+
89+
if (spi_cs_is_gpio(config)) {
90+
/* When using soft NSS, SSI must be set high */
91+
regs->CTLR1 |= SPI_CTLR1_SSM | SPI_CTLR1_SSI;
92+
} else {
93+
regs->CTLR2 |= SPI_CTLR2_SSOE;
94+
}
95+
96+
regs->CTLR1 |= SPI_CTLR1_MSTR;
97+
98+
if ((config->operation & SPI_TRANSFER_LSB) != 0U) {
99+
regs->CTLR1 |= SPI_CTLR1_LSBFIRST;
100+
}
101+
102+
if ((config->operation & SPI_MODE_CPOL) != 0U) {
103+
regs->CTLR1 |= SPI_CTLR1_CPOL;
104+
}
105+
106+
if ((config->operation & SPI_MODE_CPHA) != 0U) {
107+
regs->CTLR1 |= SPI_CTLR1_CPHA;
108+
}
109+
110+
clk_sys = (clock_control_subsys_t)(uintptr_t)cfg->clock_id;
111+
err = clock_control_get_rate(cfg->clk_dev, clk_sys, &clock_rate);
112+
if (err != 0) {
113+
return err;
114+
}
115+
116+
/* Approximate clock rate given ratios available */
117+
prescaler = spi_wch_get_br(clock_rate / config->frequency);
118+
#if CONFIG_SPI_LOG_LEVEL >= LOG_LEVEL_INF
119+
uint32_t j = 2;
120+
121+
for (int i = 0; i < prescaler; i++) {
122+
j = j * 2;
123+
}
124+
LOG_INF("Selected divider %d, value %d, results in %d frequency", j, prescaler,
125+
clock_rate / j);
126+
#endif
127+
regs->CTLR1 |= prescaler << SPI_CTLR1_BR_POS;
128+
129+
data->ctx.config = config;
130+
131+
return 0;
132+
}
133+
134+
static int spi_wch_transceive(const struct device *dev, const struct spi_config *config,
135+
const struct spi_buf_set *tx_bufs, const struct spi_buf_set *rx_bufs)
136+
{
137+
const struct spi_wch_config *cfg = dev->config;
138+
struct spi_wch_data *data = dev->data;
139+
SPI_TypeDef *regs = cfg->regs;
140+
int err;
141+
uint8_t rx;
142+
143+
spi_context_lock(&data->ctx, false, NULL, NULL, config);
144+
145+
err = spi_wch_configure(dev, config);
146+
if (err != 0) {
147+
goto done;
148+
}
149+
150+
spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, 1);
151+
152+
spi_context_cs_control(&data->ctx, true);
153+
154+
/* Start SPI *AFTER* setting CS */
155+
regs->CTLR1 |= SPI_CTLR1_SPE;
156+
157+
while (spi_context_tx_on(&data->ctx) || spi_context_rx_on(&data->ctx)) {
158+
if (spi_context_tx_buf_on(&data->ctx)) {
159+
while ((regs->STATR & SPI_STATR_TXE) == 0U) {
160+
}
161+
regs->DATAR = *(uint8_t *)(data->ctx.tx_buf);
162+
} else {
163+
while ((regs->STATR & SPI_STATR_TXE) == 0U) {
164+
}
165+
regs->DATAR = 0;
166+
}
167+
spi_context_update_tx(&data->ctx, 1, 1);
168+
while ((regs->STATR & SPI_STATR_RXNE) == 0U) {
169+
}
170+
rx = regs->DATAR;
171+
if (spi_context_rx_buf_on(&data->ctx)) {
172+
*data->ctx.rx_buf = rx;
173+
}
174+
spi_context_update_rx(&data->ctx, 1, 1);
175+
}
176+
177+
done:
178+
regs->CTLR1 &= ~(SPI_CTLR1_SPE);
179+
spi_context_cs_control(&data->ctx, false);
180+
spi_context_release(&data->ctx, err);
181+
return err;
182+
}
183+
184+
static int spi_wch_transceive_sync(const struct device *dev, const struct spi_config *config,
185+
const struct spi_buf_set *tx_bufs,
186+
const struct spi_buf_set *rx_bufs)
187+
{
188+
return spi_wch_transceive(dev, config, tx_bufs, rx_bufs);
189+
}
190+
191+
static int spi_wch_release(const struct device *dev, const struct spi_config *config)
192+
{
193+
struct spi_wch_data *data = dev->data;
194+
195+
spi_context_unlock_unconditionally(&data->ctx);
196+
197+
return 0;
198+
}
199+
200+
static int spi_wch_init(const struct device *dev)
201+
{
202+
int err;
203+
const struct spi_wch_config *cfg = dev->config;
204+
struct spi_wch_data *data = dev->data;
205+
clock_control_subsys_t clk_sys;
206+
207+
clk_sys = (clock_control_subsys_t)(uintptr_t)cfg->clock_id;
208+
209+
err = clock_control_on(cfg->clk_dev, clk_sys);
210+
if (err < 0) {
211+
return err;
212+
}
213+
214+
err = pinctrl_apply_state(cfg->pin_cfg, PINCTRL_STATE_DEFAULT);
215+
if (err < 0) {
216+
return err;
217+
}
218+
219+
err = spi_context_cs_configure_all(&data->ctx);
220+
if (err < 0) {
221+
return err;
222+
}
223+
224+
spi_context_unlock_unconditionally(&data->ctx);
225+
226+
return 0;
227+
}
228+
229+
static DEVICE_API(spi, spi_wch_driver_api) = {
230+
.transceive = spi_wch_transceive_sync,
231+
#ifdef CONFIG_SPI_RTIO
232+
.iodev_submit = spi_rtio_iodev_default_submit,
233+
#endif
234+
.release = spi_wch_release,
235+
};
236+
237+
#define SPI_WCH_DEVICE_INIT(n) \
238+
PINCTRL_DT_INST_DEFINE(n); \
239+
static const struct spi_wch_config spi_wch_config_##n = { \
240+
.regs = (SPI_TypeDef *)DT_INST_REG_ADDR(n), \
241+
.clk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \
242+
.pin_cfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
243+
.clock_id = DT_INST_CLOCKS_CELL(n, id)}; \
244+
static struct spi_wch_data spi_wch_dev_data_##n = { \
245+
SPI_CONTEXT_INIT_LOCK(spi_wch_dev_data_##n, ctx), \
246+
SPI_CONTEXT_INIT_SYNC(spi_wch_dev_data_##n, ctx), \
247+
SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(n), ctx)}; \
248+
SPI_DEVICE_DT_INST_DEFINE(n, spi_wch_init, NULL, &spi_wch_dev_data_##n, \
249+
&spi_wch_config_##n, POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, \
250+
&spi_wch_driver_api);
251+
252+
DT_INST_FOREACH_STATUS_OKAY(SPI_WCH_DEVICE_INIT)

dts/bindings/spi/wch,spi.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright (c) 2025 MASSDRIVER EI (massdriver.space)
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
description: WCH SPI
5+
6+
compatible: "wch,spi"
7+
8+
include: [spi-controller.yaml, pinctrl-device.yaml]
9+
10+
properties:
11+
reg:
12+
required: true
13+
14+
clocks:
15+
required: true
16+
17+
pinctrl-0:
18+
required: true
19+
20+
pinctrl-names:
21+
required: true

dts/riscv/wch/ch32v0/ch32v003.dtsi

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,17 @@
162162
status = "disabled";
163163
};
164164
};
165+
166+
spi1: spi@40013000 {
167+
compatible = "wch,spi";
168+
reg = <0x40013000 0x24>;
169+
clocks = <&rcc CH32V00X_CLOCK_SPI1>;
170+
interrupt-parent = <&pfic>;
171+
interrupts = <33>;
172+
status = "disabled";
173+
#address-cells = <1>;
174+
#size-cells = <0>;
175+
};
165176
};
166177
};
167178

dts/riscv/wch/ch32v0/ch32v006.dtsi

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,17 @@
181181
status = "disabled";
182182
};
183183
};
184+
185+
spi1: spi@40013000 {
186+
compatible = "wch,spi";
187+
reg = <0x40013000 0x24>;
188+
clocks = <&rcc CH32V00X_CLOCK_SPI1>;
189+
interrupt-parent = <&pfic>;
190+
interrupts = <33>;
191+
status = "disabled";
192+
#address-cells = <1>;
193+
#size-cells = <0>;
194+
};
184195
};
185196
};
186197

dts/riscv/wch/ch32v203/ch32v203.dtsi

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,28 @@
126126
status = "disabled";
127127
};
128128

129+
spi1: spi@40013000 {
130+
compatible = "wch,spi";
131+
reg = <0x40013000 0x24>;
132+
clocks = <&rcc CH32V20X_V30X_CLOCK_SPI1>;
133+
interrupt-parent = <&pfic>;
134+
interrupts = <51>;
135+
status = "disabled";
136+
#address-cells = <1>;
137+
#size-cells = <0>;
138+
};
139+
140+
spi2: spi@40003800 {
141+
compatible = "wch,spi";
142+
reg = <0x40003800 0x24>;
143+
clocks = <&rcc CH32V20X_V30X_CLOCK_SPI2>;
144+
interrupt-parent = <&pfic>;
145+
interrupts = <52>;
146+
status = "disabled";
147+
#address-cells = <1>;
148+
#size-cells = <0>;
149+
};
150+
129151
rcc: rcc@40021000 {
130152
compatible = "wch,rcc";
131153
reg = <0x40021000 16>;

dts/riscv/wch/ch32v208/ch32v208.dtsi

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,28 @@
151151
status = "disabled";
152152
};
153153

154+
spi1: spi@40013000 {
155+
compatible = "wch,spi";
156+
reg = <0x40013000 0x24>;
157+
clocks = <&rcc CH32V20X_V30X_CLOCK_SPI1>;
158+
interrupt-parent = <&pfic>;
159+
interrupts = <51>;
160+
status = "disabled";
161+
#address-cells = <1>;
162+
#size-cells = <0>;
163+
};
164+
165+
spi2: spi@40003800 {
166+
compatible = "wch,spi";
167+
reg = <0x40003800 0x24>;
168+
clocks = <&rcc CH32V20X_V30X_CLOCK_SPI2>;
169+
interrupt-parent = <&pfic>;
170+
interrupts = <52>;
171+
status = "disabled";
172+
#address-cells = <1>;
173+
#size-cells = <0>;
174+
};
175+
154176
rcc: rcc@40021000 {
155177
compatible = "wch,rcc";
156178
reg = <0x40021000 16>;

0 commit comments

Comments
 (0)