drivers: spi: introduce basic spi driver for wch
introduces a basic SPI driver for CH32 series Signed-off-by: Camille BAUD <mail@massdriver.space>
This commit is contained in:
parent
a06ded8db6
commit
f81e7559bf
@ -64,6 +64,7 @@ zephyr_library_sources_ifdef(CONFIG_SPI_SMARTBOND spi_smartbond.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_STM32 spi_ll_stm32.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_TELINK_B91 spi_b91.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_TEST spi_test.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_WCH spi_wch.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_XEC_QMSPI spi_xec_qmspi.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_XEC_QMSPI_LDMA spi_xec_qmspi_ldma.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_XLNX_AXI_QUADSPI spi_xlnx_axi_quadspi.c)
|
||||
|
||||
@ -141,6 +141,7 @@ source "drivers/spi/Kconfig.smartbond"
|
||||
source "drivers/spi/Kconfig.spi_emul"
|
||||
source "drivers/spi/Kconfig.stm32"
|
||||
source "drivers/spi/Kconfig.test"
|
||||
source "drivers/spi/Kconfig.wch"
|
||||
source "drivers/spi/Kconfig.xec_qmspi"
|
||||
source "drivers/spi/Kconfig.xlnx"
|
||||
source "drivers/spi/Kconfig.xmc4xxx"
|
||||
|
||||
9
drivers/spi/Kconfig.wch
Normal file
9
drivers/spi/Kconfig.wch
Normal file
@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2025 MASSDRIVER EI (massdriver.space)
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config SPI_WCH
|
||||
bool "WCH SPI Controller"
|
||||
default y
|
||||
depends on DT_HAS_WCH_SPI_ENABLED
|
||||
help
|
||||
Enable the SPI peripherals on CH32s
|
||||
252
drivers/spi/spi_wch.c
Normal file
252
drivers/spi/spi_wch.c
Normal file
@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright (c) 2025 MASSDRIVER EI (massdriver.space)
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#define DT_DRV_COMPAT wch_spi
|
||||
|
||||
#define LOG_LEVEL CONFIG_SPI_LOG_LEVEL
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(spi_wch);
|
||||
|
||||
#include "spi_context.h"
|
||||
#include <errno.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/spi.h>
|
||||
#include <zephyr/drivers/spi/rtio.h>
|
||||
#include <zephyr/drivers/pinctrl.h>
|
||||
#include <zephyr/drivers/clock_control.h>
|
||||
|
||||
#include <hal_ch32fun.h>
|
||||
|
||||
#define SPI_CTLR1_LSBFIRST BIT(7)
|
||||
#define SPI_CTLR1_BR_POS 3
|
||||
|
||||
struct spi_wch_config {
|
||||
SPI_TypeDef *regs;
|
||||
const struct pinctrl_dev_config *pin_cfg;
|
||||
const struct device *clk_dev;
|
||||
uint8_t clock_id;
|
||||
};
|
||||
|
||||
struct spi_wch_data {
|
||||
struct spi_context ctx;
|
||||
};
|
||||
|
||||
static uint8_t spi_wch_get_br(uint32_t target_clock_ratio)
|
||||
{
|
||||
uint8_t prescaler;
|
||||
int prescaler_val = 2;
|
||||
|
||||
for (prescaler = 0; prescaler < 7; prescaler++) {
|
||||
if (prescaler_val > target_clock_ratio) {
|
||||
break;
|
||||
}
|
||||
prescaler_val *= 2;
|
||||
}
|
||||
|
||||
return prescaler;
|
||||
}
|
||||
|
||||
static int spi_wch_configure(const struct device *dev, const struct spi_config *config)
|
||||
{
|
||||
const struct spi_wch_config *cfg = dev->config;
|
||||
struct spi_wch_data *data = dev->data;
|
||||
SPI_TypeDef *regs = cfg->regs;
|
||||
int err;
|
||||
uint32_t clock_rate;
|
||||
clock_control_subsys_t clk_sys;
|
||||
int8_t prescaler;
|
||||
|
||||
if (spi_context_configured(&data->ctx, config)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((config->operation & SPI_HALF_DUPLEX) != 0U) {
|
||||
LOG_ERR("Half-duplex not supported");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (SPI_OP_MODE_GET(config->operation) != SPI_OP_MODE_MASTER) {
|
||||
LOG_ERR("Slave mode not supported");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if ((config->operation & SPI_MODE_LOOP) != 0U) {
|
||||
LOG_ERR("Loop mode not supported");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (SPI_WORD_SIZE_GET(config->operation) != 8) {
|
||||
LOG_ERR("Frame size != 8 bits not supported");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
regs->CTLR1 = 0;
|
||||
regs->CTLR2 = 0;
|
||||
regs->STATR = 0;
|
||||
|
||||
if (spi_cs_is_gpio(config)) {
|
||||
/* When using soft NSS, SSI must be set high */
|
||||
regs->CTLR1 |= SPI_CTLR1_SSM | SPI_CTLR1_SSI;
|
||||
} else {
|
||||
regs->CTLR2 |= SPI_CTLR2_SSOE;
|
||||
}
|
||||
|
||||
regs->CTLR1 |= SPI_CTLR1_MSTR;
|
||||
|
||||
if ((config->operation & SPI_TRANSFER_LSB) != 0U) {
|
||||
regs->CTLR1 |= SPI_CTLR1_LSBFIRST;
|
||||
}
|
||||
|
||||
if ((config->operation & SPI_MODE_CPOL) != 0U) {
|
||||
regs->CTLR1 |= SPI_CTLR1_CPOL;
|
||||
}
|
||||
|
||||
if ((config->operation & SPI_MODE_CPHA) != 0U) {
|
||||
regs->CTLR1 |= SPI_CTLR1_CPHA;
|
||||
}
|
||||
|
||||
clk_sys = (clock_control_subsys_t)(uintptr_t)cfg->clock_id;
|
||||
err = clock_control_get_rate(cfg->clk_dev, clk_sys, &clock_rate);
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Approximate clock rate given ratios available */
|
||||
prescaler = spi_wch_get_br(clock_rate / config->frequency);
|
||||
#if CONFIG_SPI_LOG_LEVEL >= LOG_LEVEL_INF
|
||||
uint32_t j = 2;
|
||||
|
||||
for (int i = 0; i < prescaler; i++) {
|
||||
j = j * 2;
|
||||
}
|
||||
LOG_INF("Selected divider %d, value %d, results in %d frequency", j, prescaler,
|
||||
clock_rate / j);
|
||||
#endif
|
||||
regs->CTLR1 |= prescaler << SPI_CTLR1_BR_POS;
|
||||
|
||||
data->ctx.config = config;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spi_wch_transceive(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 spi_wch_config *cfg = dev->config;
|
||||
struct spi_wch_data *data = dev->data;
|
||||
SPI_TypeDef *regs = cfg->regs;
|
||||
int err;
|
||||
uint8_t rx;
|
||||
|
||||
spi_context_lock(&data->ctx, false, NULL, NULL, config);
|
||||
|
||||
err = spi_wch_configure(dev, config);
|
||||
if (err != 0) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, 1);
|
||||
|
||||
spi_context_cs_control(&data->ctx, true);
|
||||
|
||||
/* Start SPI *AFTER* setting CS */
|
||||
regs->CTLR1 |= SPI_CTLR1_SPE;
|
||||
|
||||
while (spi_context_tx_on(&data->ctx) || spi_context_rx_on(&data->ctx)) {
|
||||
if (spi_context_tx_buf_on(&data->ctx)) {
|
||||
while ((regs->STATR & SPI_STATR_TXE) == 0U) {
|
||||
}
|
||||
regs->DATAR = *(uint8_t *)(data->ctx.tx_buf);
|
||||
} else {
|
||||
while ((regs->STATR & SPI_STATR_TXE) == 0U) {
|
||||
}
|
||||
regs->DATAR = 0;
|
||||
}
|
||||
spi_context_update_tx(&data->ctx, 1, 1);
|
||||
while ((regs->STATR & SPI_STATR_RXNE) == 0U) {
|
||||
}
|
||||
rx = regs->DATAR;
|
||||
if (spi_context_rx_buf_on(&data->ctx)) {
|
||||
*data->ctx.rx_buf = rx;
|
||||
}
|
||||
spi_context_update_rx(&data->ctx, 1, 1);
|
||||
}
|
||||
|
||||
done:
|
||||
regs->CTLR1 &= ~(SPI_CTLR1_SPE);
|
||||
spi_context_cs_control(&data->ctx, false);
|
||||
spi_context_release(&data->ctx, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int spi_wch_transceive_sync(const struct device *dev, const struct spi_config *config,
|
||||
const struct spi_buf_set *tx_bufs,
|
||||
const struct spi_buf_set *rx_bufs)
|
||||
{
|
||||
return spi_wch_transceive(dev, config, tx_bufs, rx_bufs);
|
||||
}
|
||||
|
||||
static int spi_wch_release(const struct device *dev, const struct spi_config *config)
|
||||
{
|
||||
struct spi_wch_data *data = dev->data;
|
||||
|
||||
spi_context_unlock_unconditionally(&data->ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spi_wch_init(const struct device *dev)
|
||||
{
|
||||
int err;
|
||||
const struct spi_wch_config *cfg = dev->config;
|
||||
struct spi_wch_data *data = dev->data;
|
||||
clock_control_subsys_t clk_sys;
|
||||
|
||||
clk_sys = (clock_control_subsys_t)(uintptr_t)cfg->clock_id;
|
||||
|
||||
err = clock_control_on(cfg->clk_dev, clk_sys);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = pinctrl_apply_state(cfg->pin_cfg, PINCTRL_STATE_DEFAULT);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = spi_context_cs_configure_all(&data->ctx);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
spi_context_unlock_unconditionally(&data->ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DEVICE_API(spi, spi_wch_driver_api) = {
|
||||
.transceive = spi_wch_transceive_sync,
|
||||
#ifdef CONFIG_SPI_RTIO
|
||||
.iodev_submit = spi_rtio_iodev_default_submit,
|
||||
#endif
|
||||
.release = spi_wch_release,
|
||||
};
|
||||
|
||||
#define SPI_WCH_DEVICE_INIT(n) \
|
||||
PINCTRL_DT_INST_DEFINE(n); \
|
||||
static const struct spi_wch_config spi_wch_config_##n = { \
|
||||
.regs = (SPI_TypeDef *)DT_INST_REG_ADDR(n), \
|
||||
.clk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \
|
||||
.pin_cfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
|
||||
.clock_id = DT_INST_CLOCKS_CELL(n, id)}; \
|
||||
static struct spi_wch_data spi_wch_dev_data_##n = { \
|
||||
SPI_CONTEXT_INIT_LOCK(spi_wch_dev_data_##n, ctx), \
|
||||
SPI_CONTEXT_INIT_SYNC(spi_wch_dev_data_##n, ctx), \
|
||||
SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(n), ctx)}; \
|
||||
SPI_DEVICE_DT_INST_DEFINE(n, spi_wch_init, NULL, &spi_wch_dev_data_##n, \
|
||||
&spi_wch_config_##n, POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, \
|
||||
&spi_wch_driver_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(SPI_WCH_DEVICE_INIT)
|
||||
21
dts/bindings/spi/wch,spi.yaml
Normal file
21
dts/bindings/spi/wch,spi.yaml
Normal file
@ -0,0 +1,21 @@
|
||||
# Copyright (c) 2025 MASSDRIVER EI (massdriver.space)
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: WCH SPI
|
||||
|
||||
compatible: "wch,spi"
|
||||
|
||||
include: [spi-controller.yaml, pinctrl-device.yaml]
|
||||
|
||||
properties:
|
||||
reg:
|
||||
required: true
|
||||
|
||||
clocks:
|
||||
required: true
|
||||
|
||||
pinctrl-0:
|
||||
required: true
|
||||
|
||||
pinctrl-names:
|
||||
required: true
|
||||
@ -162,6 +162,17 @@
|
||||
status = "disabled";
|
||||
};
|
||||
};
|
||||
|
||||
spi1: spi@40013000 {
|
||||
compatible = "wch,spi";
|
||||
reg = <0x40013000 0x24>;
|
||||
clocks = <&rcc CH32V00X_CLOCK_SPI1>;
|
||||
interrupt-parent = <&pfic>;
|
||||
interrupts = <33>;
|
||||
status = "disabled";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -181,6 +181,17 @@
|
||||
status = "disabled";
|
||||
};
|
||||
};
|
||||
|
||||
spi1: spi@40013000 {
|
||||
compatible = "wch,spi";
|
||||
reg = <0x40013000 0x24>;
|
||||
clocks = <&rcc CH32V00X_CLOCK_SPI1>;
|
||||
interrupt-parent = <&pfic>;
|
||||
interrupts = <33>;
|
||||
status = "disabled";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -126,6 +126,28 @@
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
spi1: spi@40013000 {
|
||||
compatible = "wch,spi";
|
||||
reg = <0x40013000 0x24>;
|
||||
clocks = <&rcc CH32V20X_V30X_CLOCK_SPI1>;
|
||||
interrupt-parent = <&pfic>;
|
||||
interrupts = <51>;
|
||||
status = "disabled";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
|
||||
spi2: spi@40003800 {
|
||||
compatible = "wch,spi";
|
||||
reg = <0x40003800 0x24>;
|
||||
clocks = <&rcc CH32V20X_V30X_CLOCK_SPI2>;
|
||||
interrupt-parent = <&pfic>;
|
||||
interrupts = <52>;
|
||||
status = "disabled";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
|
||||
rcc: rcc@40021000 {
|
||||
compatible = "wch,rcc";
|
||||
reg = <0x40021000 16>;
|
||||
|
||||
@ -151,6 +151,28 @@
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
spi1: spi@40013000 {
|
||||
compatible = "wch,spi";
|
||||
reg = <0x40013000 0x24>;
|
||||
clocks = <&rcc CH32V20X_V30X_CLOCK_SPI1>;
|
||||
interrupt-parent = <&pfic>;
|
||||
interrupts = <51>;
|
||||
status = "disabled";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
|
||||
spi2: spi@40003800 {
|
||||
compatible = "wch,spi";
|
||||
reg = <0x40003800 0x24>;
|
||||
clocks = <&rcc CH32V20X_V30X_CLOCK_SPI2>;
|
||||
interrupt-parent = <&pfic>;
|
||||
interrupts = <52>;
|
||||
status = "disabled";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
|
||||
rcc: rcc@40021000 {
|
||||
compatible = "wch,rcc";
|
||||
reg = <0x40021000 16>;
|
||||
|
||||
@ -177,4 +177,27 @@
|
||||
#define I2C1_SDA_PB7_0 CH32V20X_V30X_PINMUX_DEFINE(PB, 7, I2C1, 0)
|
||||
#define I2C1_SDA_PB9_1 CH32V20X_V30X_PINMUX_DEFINE(PB, 9, I2C1, 1)
|
||||
|
||||
#define SPI1_NSS_PA4_0 CH32V20X_V30X_PINMUX_DEFINE(PA, 4, SPI1, 0)
|
||||
#define SPI1_NSS_PA15_1 CH32V20X_V30X_PINMUX_DEFINE(PA, 15, SPI1, 1)
|
||||
#define SPI1_SCK_PA5_0 CH32V20X_V30X_PINMUX_DEFINE(PA, 5, SPI1, 0)
|
||||
#define SPI1_SCK_PB3_1 CH32V20X_V30X_PINMUX_DEFINE(PB, 3, SPI1, 1)
|
||||
#define SPI1_MISO_PA6_0 CH32V20X_V30X_PINMUX_DEFINE(PA, 6, SPI1, 0)
|
||||
#define SPI1_MISO_PB4_1 CH32V20X_V30X_PINMUX_DEFINE(PB, 4, SPI1, 1)
|
||||
#define SPI1_MOSI_PA7_0 CH32V20X_V30X_PINMUX_DEFINE(PA, 7, SPI1, 0)
|
||||
#define SPI1_MOSI_PB5_1 CH32V20X_V30X_PINMUX_DEFINE(PB, 5, SPI1, 1)
|
||||
|
||||
#define SPI2_NSS_PB12_0 CH32V20X_V30X_PINMUX_DEFINE(PB, 12, SPI2, 0)
|
||||
#define SPI2_SCK_PB13_0 CH32V20X_V30X_PINMUX_DEFINE(PB, 13, SPI2, 0)
|
||||
#define SPI2_MISO_PB14_0 CH32V20X_V30X_PINMUX_DEFINE(PB, 14, SPI2, 0)
|
||||
#define SPI2_MOSI_PB15_0 CH32V20X_V30X_PINMUX_DEFINE(PB, 15, SPI2, 0)
|
||||
|
||||
#define SPI3_NSS_PA15_0 CH32V20X_V30X_PINMUX_DEFINE(PA, 15, SPI3, 0)
|
||||
#define SPI3_NSS_PA4_1 CH32V20X_V30X_PINMUX_DEFINE(PA, 4, SPI3, 1)
|
||||
#define SPI3_SCK_PB3_0 CH32V20X_V30X_PINMUX_DEFINE(PB, 3, SPI3, 0)
|
||||
#define SPI3_SCK_PC10_1 CH32V20X_V30X_PINMUX_DEFINE(PC, 10, SPI3, 1)
|
||||
#define SPI3_MISO_PB4_0 CH32V20X_V30X_PINMUX_DEFINE(PB, 4, SPI3, 0)
|
||||
#define SPI3_MISO_PC11_1 CH32V20X_V30X_PINMUX_DEFINE(PC, 11, SPI3, 1)
|
||||
#define SPI3_MOSI_PB5_0 CH32V20X_V30X_PINMUX_DEFINE(PB, 5, SPI3, 0)
|
||||
#define SPI3_MOSI_PC12_1 CH32V20X_V30X_PINMUX_DEFINE(PC, 12, SPI3, 1)
|
||||
|
||||
#endif /* __CH32V20X_V30X_PINCTRL_H__ */
|
||||
|
||||
Loading…
Reference in New Issue
Block a user