diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt index b2a3bb621e9..20de5467f42 100644 --- a/drivers/spi/CMakeLists.txt +++ b/drivers/spi/CMakeLists.txt @@ -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) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 2a09d51f258..851036187e1 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -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" diff --git a/drivers/spi/Kconfig.wch b/drivers/spi/Kconfig.wch new file mode 100644 index 00000000000..c1af5e8658e --- /dev/null +++ b/drivers/spi/Kconfig.wch @@ -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 diff --git a/drivers/spi/spi_wch.c b/drivers/spi/spi_wch.c new file mode 100644 index 00000000000..023b87e6af0 --- /dev/null +++ b/drivers/spi/spi_wch.c @@ -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 +LOG_MODULE_REGISTER(spi_wch); + +#include "spi_context.h" +#include +#include +#include +#include +#include +#include + +#include + +#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) diff --git a/dts/bindings/spi/wch,spi.yaml b/dts/bindings/spi/wch,spi.yaml new file mode 100644 index 00000000000..52d2905831c --- /dev/null +++ b/dts/bindings/spi/wch,spi.yaml @@ -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 diff --git a/dts/riscv/wch/ch32v0/ch32v003.dtsi b/dts/riscv/wch/ch32v0/ch32v003.dtsi index 52b2b074da9..d28667135db 100644 --- a/dts/riscv/wch/ch32v0/ch32v003.dtsi +++ b/dts/riscv/wch/ch32v0/ch32v003.dtsi @@ -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>; + }; }; }; diff --git a/dts/riscv/wch/ch32v0/ch32v006.dtsi b/dts/riscv/wch/ch32v0/ch32v006.dtsi index 82f7697a4c5..dd6cac5e054 100644 --- a/dts/riscv/wch/ch32v0/ch32v006.dtsi +++ b/dts/riscv/wch/ch32v0/ch32v006.dtsi @@ -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>; + }; }; }; diff --git a/dts/riscv/wch/ch32v203/ch32v203.dtsi b/dts/riscv/wch/ch32v203/ch32v203.dtsi index 31c9c7a3b8d..6eeeaf75f37 100644 --- a/dts/riscv/wch/ch32v203/ch32v203.dtsi +++ b/dts/riscv/wch/ch32v203/ch32v203.dtsi @@ -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>; diff --git a/dts/riscv/wch/ch32v208/ch32v208.dtsi b/dts/riscv/wch/ch32v208/ch32v208.dtsi index e8a1d826279..659fb1e62b5 100644 --- a/dts/riscv/wch/ch32v208/ch32v208.dtsi +++ b/dts/riscv/wch/ch32v208/ch32v208.dtsi @@ -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>; diff --git a/include/zephyr/dt-bindings/pinctrl/ch32v20x_30x-pinctrl.h b/include/zephyr/dt-bindings/pinctrl/ch32v20x_30x-pinctrl.h index 209381c616f..1f9ce7f3e71 100644 --- a/include/zephyr/dt-bindings/pinctrl/ch32v20x_30x-pinctrl.h +++ b/include/zephyr/dt-bindings/pinctrl/ch32v20x_30x-pinctrl.h @@ -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__ */