drivers: dma_wch: add support for the WCH DMA controller

This patch adds an initial driver for the WCH DMA
controller. All hardware features and most interface
features are implemented.

Signed-off-by: Paul Wedeck <paulwedeck@gmail.com>
This commit is contained in:
Paul Wedeck 2024-12-30 21:28:20 +01:00 committed by Henrik Brix Andersen
parent 0c6217fc6a
commit 1cfec8c19a
7 changed files with 593 additions and 0 deletions

View File

@ -49,3 +49,4 @@ zephyr_library_sources_ifdef(CONFIG_DMA_NXP_EDMA dma_nxp_edma.c)
zephyr_library_sources_ifdef(CONFIG_DMA_DW_AXI dma_dw_axi.c)
zephyr_library_sources_ifdef(CONFIG_DMA_XILINX_AXI_DMA dma_xilinx_axi_dma.c)
zephyr_library_sources_ifdef(CONFIG_DMA_NXP_SDMA dma_nxp_sdma.c)
zephyr_library_sources_ifdef(CONFIG_DMA_WCH dma_wch.c)

View File

@ -91,4 +91,6 @@ source "drivers/dma/Kconfig.xilinx_axi_dma"
source "drivers/dma/Kconfig.nxp_sdma"
source "drivers/dma/Kconfig.wch"
endif # DMA

9
drivers/dma/Kconfig.wch Normal file
View File

@ -0,0 +1,9 @@
# Copyright (c) 2024 Paul Wedeck <paulwedeck@gmail.com>
# SPDX-License-Identifier: Apache-2.0
config DMA_WCH
bool "CH32V DMA driver"
default y
depends on DT_HAS_WCH_WCH_DMA_ENABLED
help
DMA driver for the WCH CH32V SoC family.

502
drivers/dma/dma_wch.c Normal file
View File

@ -0,0 +1,502 @@
/*
* Copyright (c) 2024 Paul Wedeck <paulwedeck@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT wch_wch_dma
#include <zephyr/drivers/dma.h>
#include <zephyr/drivers/clock_control.h>
#include <ch32fun.h>
#define DMA_WCH_MAX_CHAN 11
#define DMA_WCH_MAX_CHAN_BASE 8
#define DMA_WCH_AIF (DMA_GIF1 | DMA_TCIF1 | DMA_HTIF1 | DMA_TEIF1)
#define DMA_WCH_IF_OFF(ch) (4 * (ch))
#define DMA_WCH_MAX_BLOCK ((UINT32_C(2) << 16) - 1)
struct dma_wch_chan_regs {
volatile uint32_t CFGR;
volatile uint32_t CNTR;
volatile uint32_t PADDR;
volatile uint32_t MADDR;
volatile uint32_t reserved1;
};
struct dma_wch_regs {
DMA_TypeDef base;
struct dma_wch_chan_regs channels[DMA_WCH_MAX_CHAN];
DMA_TypeDef ext;
};
struct dma_wch_config {
struct dma_wch_regs *regs;
uint32_t num_channels;
const struct device *clock_dev;
uint8_t clock_id;
void (*irq_config_func)(const struct device *dev);
};
struct dma_wch_channel {
void *user_data;
dma_callback_t dma_cb;
};
struct dma_wch_data {
struct dma_context ctx;
struct dma_wch_channel *channels;
};
static uint8_t dma_wch_get_ip(const struct device *dev, uint32_t chan)
{
const struct dma_wch_config *config = dev->config;
struct dma_wch_regs *regs = config->regs;
uint32_t intfr;
if (chan > DMA_WCH_MAX_CHAN_BASE) {
chan -= DMA_WCH_MAX_CHAN_BASE;
intfr = regs->ext.INTFR;
return (intfr >> DMA_WCH_IF_OFF(chan)) & DMA_WCH_AIF;
}
intfr = regs->base.INTFR;
return (intfr >> DMA_WCH_IF_OFF(chan)) & DMA_WCH_AIF;
}
static bool dma_wch_busy(const struct device *dev, uint32_t ch)
{
const struct dma_wch_config *config = dev->config;
struct dma_wch_regs *regs = config->regs;
return (regs->channels[ch].CFGR & DMA_CFGR1_EN) > 0 &&
!(dma_wch_get_ip(dev, ch) & DMA_TCIF1);
}
static int dma_wch_init(const struct device *dev)
{
const struct dma_wch_config *config = dev->config;
clock_control_subsys_t clock_sys = (clock_control_subsys_t *)(uintptr_t)config->clock_id;
if (config->num_channels > DMA_WCH_MAX_CHAN) {
return -ENOTSUP;
}
clock_control_on(config->clock_dev, clock_sys);
config->irq_config_func(dev);
return 0;
}
static int dma_wch_config(const struct device *dev, uint32_t ch, struct dma_config *dma_cfg)
{
const struct dma_wch_config *config = dev->config;
struct dma_wch_data *data = dev->data;
struct dma_wch_regs *regs = config->regs;
unsigned int key;
int ret = 0;
uint32_t cfgr = 0;
uint32_t cntr = 0;
uint32_t paddr = 0;
uint32_t maddr = 0;
if (config->num_channels <= ch) {
return -EINVAL;
}
if (dma_cfg->block_count != 1) {
return -ENOTSUP;
}
if (dma_cfg->head_block->block_size > DMA_WCH_MAX_BLOCK ||
dma_cfg->head_block->source_gather_en || dma_cfg->head_block->dest_scatter_en ||
dma_cfg->head_block->source_reload_en || dma_cfg->channel_priority > 3 ||
dma_cfg->head_block->source_addr_adj == DMA_ADDR_ADJ_DECREMENT ||
dma_cfg->head_block->dest_addr_adj == DMA_ADDR_ADJ_DECREMENT ||
dma_cfg->head_block->dest_reload_en) {
return -ENOTSUP;
}
cntr = dma_cfg->head_block->block_size;
switch (dma_cfg->channel_direction) {
case MEMORY_TO_MEMORY:
cfgr |= DMA_CFGR1_MEM2MEM;
paddr = dma_cfg->head_block->source_address;
maddr = dma_cfg->head_block->dest_address;
if (dma_cfg->cyclic) {
return -ENOTSUP;
}
break;
case MEMORY_TO_PERIPHERAL:
maddr = dma_cfg->head_block->source_address;
paddr = dma_cfg->head_block->dest_address;
cfgr |= DMA_CFGR1_DIR;
break;
case PERIPHERAL_TO_MEMORY:
paddr = dma_cfg->head_block->source_address;
maddr = dma_cfg->head_block->dest_address;
break;
default:
return -ENOTSUP;
}
cfgr |= dma_cfg->channel_priority * DMA_CFGR1_PL_0;
if (dma_cfg->channel_direction == MEMORY_TO_PERIPHERAL) {
cfgr |= dma_width_index(dma_cfg->source_data_size / BITS_PER_BYTE) *
DMA_CFGR1_MSIZE_0;
cfgr |= dma_width_index(dma_cfg->dest_data_size / BITS_PER_BYTE) *
DMA_CFGR1_PSIZE_0;
cfgr |= (dma_cfg->head_block->dest_addr_adj == DMA_ADDR_ADJ_INCREMENT)
? DMA_CFGR1_PINC
: 0;
cfgr |= (dma_cfg->head_block->source_addr_adj == DMA_ADDR_ADJ_INCREMENT)
? DMA_CFGR1_MINC
: 0;
} else {
cfgr |= dma_width_index(dma_cfg->source_data_size / BITS_PER_BYTE) *
DMA_CFGR1_PSIZE_0;
cfgr |= dma_width_index(dma_cfg->dest_data_size / BITS_PER_BYTE) *
DMA_CFGR1_MSIZE_0;
cfgr |= (dma_cfg->head_block->dest_addr_adj == DMA_ADDR_ADJ_INCREMENT)
? DMA_CFGR1_MINC
: 0;
cfgr |= (dma_cfg->head_block->source_addr_adj == DMA_ADDR_ADJ_INCREMENT)
? DMA_CFGR1_PINC
: 0;
}
if (dma_cfg->cyclic) {
cfgr |= DMA_CFGR1_CIRC;
}
if (dma_cfg->dma_callback) {
if (!dma_cfg->error_callback_dis) {
cfgr |= DMA_CFGR1_TEIE;
}
if (dma_cfg->complete_callback_en) {
cfgr |= DMA_CFGR1_HTIE;
}
cfgr |= DMA_CFGR1_TCIE;
}
key = irq_lock();
if (dma_wch_busy(dev, ch)) {
ret = -EBUSY;
goto end;
}
data->channels[ch].user_data = dma_cfg->user_data;
data->channels[ch].dma_cb = dma_cfg->dma_callback;
regs->channels[ch].CFGR = 0;
if (ch <= DMA_WCH_MAX_CHAN_BASE) {
regs->base.INTFCR = DMA_WCH_AIF << DMA_WCH_IF_OFF(ch);
} else {
regs->ext.INTFCR = DMA_WCH_AIF << DMA_WCH_IF_OFF(ch - DMA_WCH_MAX_CHAN_BASE);
}
regs->channels[ch].PADDR = paddr;
regs->channels[ch].MADDR = maddr;
regs->channels[ch].CNTR = cntr;
regs->channels[ch].CFGR = cfgr;
end:
irq_unlock(key);
return ret;
}
#ifdef CONFIG_DMA_64BIT
static int dma_wch_reload(const struct device *dev, uint32_t ch, uint64_t src, uint64_t dst,
size_t size)
#else
static int dma_wch_reload(const struct device *dev, uint32_t ch, uint32_t src, uint32_t dst,
size_t size)
#endif
{
const struct dma_wch_config *config = dev->config;
struct dma_wch_regs *regs = config->regs;
uint32_t maddr, paddr;
int ret = 0;
unsigned int key;
if (config->num_channels <= ch) {
return -EINVAL;
}
key = irq_lock();
if (dma_wch_busy(dev, ch)) {
ret = -EBUSY;
goto end;
}
if (regs->channels[ch].CFGR & DMA_CFGR1_DIR) {
maddr = src;
paddr = dst;
} else {
maddr = dst;
paddr = src;
}
regs->channels[ch].MADDR = maddr;
regs->channels[ch].PADDR = paddr;
regs->channels[ch].CNTR = size;
end:
irq_unlock(key);
return ret;
}
static int dma_wch_start(const struct device *dev, uint32_t ch)
{
const struct dma_wch_config *config = dev->config;
struct dma_wch_regs *regs = config->regs;
unsigned int key;
if (config->num_channels <= ch) {
return -EINVAL;
}
key = irq_lock();
regs->channels[ch].CFGR |= DMA_CFGR1_EN;
irq_unlock(key);
return 0;
}
static int dma_wch_stop(const struct device *dev, uint32_t ch)
{
const struct dma_wch_config *config = dev->config;
struct dma_wch_regs *regs = config->regs;
unsigned int key;
if (config->num_channels <= ch) {
return -EINVAL;
}
key = irq_lock();
regs->channels[ch].CFGR &= ~DMA_CFGR1_EN;
irq_unlock(key);
return 0;
}
static int dma_wch_resume(const struct device *dev, uint32_t ch)
{
const struct dma_wch_config *config = dev->config;
struct dma_wch_regs *regs = config->regs;
int ret = 0;
unsigned int key;
if (config->num_channels <= ch) {
return -EINVAL;
}
key = irq_lock();
if (regs->channels[ch].CFGR & DMA_CFGR1_EN) {
ret = -EINVAL;
goto end;
}
regs->channels[ch].CFGR |= DMA_CFGR1_EN;
end:
irq_unlock(key);
return ret;
}
static int dma_wch_suspend(const struct device *dev, uint32_t ch)
{
const struct dma_wch_config *config = dev->config;
struct dma_wch_regs *regs = config->regs;
int ret = 0;
unsigned int key;
if (config->num_channels <= ch) {
return -EINVAL;
}
key = irq_lock();
if (!(regs->channels[ch].CFGR & DMA_CFGR1_EN)) {
ret = -EINVAL;
goto end;
}
regs->channels[ch].CFGR &= ~DMA_CFGR1_EN;
end:
irq_unlock(key);
return ret;
}
static int dma_wch_get_status(const struct device *dev, uint32_t ch, struct dma_status *status)
{
const struct dma_wch_config *config = dev->config;
struct dma_wch_regs *regs = config->regs;
uint32_t cfgr;
unsigned int key;
if (config->num_channels <= ch) {
return -EINVAL;
}
key = irq_lock();
cfgr = regs->channels[ch].CFGR;
status->busy = dma_wch_busy(dev, ch);
if (cfgr & DMA_CFGR1_MEM2MEM) {
status->dir = MEMORY_TO_MEMORY;
} else if (cfgr & DMA_CFGR1_DIR) {
status->dir = MEMORY_TO_PERIPHERAL;
} else {
status->dir = PERIPHERAL_TO_MEMORY;
}
status->pending_length = regs->channels[ch].CNTR;
if (cfgr & DMA_CFGR1_DIR) {
status->read_position = regs->channels[ch].MADDR;
status->write_position = regs->channels[ch].PADDR;
} else {
status->read_position = regs->channels[ch].PADDR;
status->write_position = regs->channels[ch].MADDR;
}
irq_unlock(key);
return 0;
}
int dma_wch_get_attribute(const struct device *dev, uint32_t type, uint32_t *value)
{
switch (type) {
case DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT:
case DMA_ATTR_BUFFER_SIZE_ALIGNMENT:
case DMA_ATTR_COPY_ALIGNMENT:
case DMA_ATTR_MAX_BLOCK_COUNT:
*value = 1;
return 0;
default:
return -EINVAL;
}
}
static void dma_wch_handle_callback(const struct device *dev, uint32_t ch, uint8_t ip)
{
const struct dma_wch_data *data = dev->data;
void *cb_data;
dma_callback_t cb_func;
unsigned int key = irq_lock();
cb_data = data->channels[ch].user_data;
cb_func = data->channels[ch].dma_cb;
irq_unlock(key);
if (!cb_func) {
return;
}
if (ip & DMA_TCIF1) {
cb_func(dev, cb_data, ch, DMA_STATUS_COMPLETE);
} else if (ip & DMA_TEIF1) {
cb_func(dev, cb_data, ch, -EIO);
} else if (ip & DMA_HTIF1) {
cb_func(dev, cb_data, ch, DMA_STATUS_BLOCK);
}
}
static void dma_wch_isr(const struct device *dev, uint32_t chan)
{
const struct dma_wch_config *config = dev->config;
struct dma_wch_regs *regs = config->regs;
uint32_t intfr = regs->base.INTFR;
intfr &= (DMA_WCH_AIF << DMA_WCH_IF_OFF(chan));
if (intfr & DMA_TCIF1 << DMA_WCH_IF_OFF(chan)) {
regs->channels[chan].CFGR &= ~DMA_CFGR1_EN;
}
regs->base.INTFCR = intfr;
dma_wch_handle_callback(dev, chan, intfr >> DMA_WCH_IF_OFF(chan));
}
static void dma_wch_isr_ext(const struct device *dev, uint32_t chan)
{
const struct dma_wch_config *config = dev->config;
struct dma_wch_regs *regs = config->regs;
uint32_t chan_idx = chan - DMA_WCH_MAX_CHAN_BASE;
uint32_t intfr = regs->ext.INTFR;
intfr &= (DMA_WCH_AIF << DMA_WCH_IF_OFF(chan_idx));
regs->ext.INTFCR = intfr;
dma_wch_handle_callback(dev, chan, intfr >> DMA_WCH_IF_OFF(chan_idx));
}
static DEVICE_API(dma, dma_wch_driver_api) = {
.config = dma_wch_config,
.reload = dma_wch_reload,
.start = dma_wch_start,
.stop = dma_wch_stop,
.resume = dma_wch_resume,
.suspend = dma_wch_suspend,
.get_status = dma_wch_get_status,
.get_attribute = dma_wch_get_attribute,
};
#define GENERATE_ISR(ch, _) \
static void dma_wch_isr##ch(const struct device *dev) \
{ \
if (ch <= DMA_WCH_MAX_CHAN_BASE) { \
dma_wch_isr(dev, ch); \
} else { \
dma_wch_isr_ext(dev, ch); \
} \
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
LISTIFY(DMA_WCH_MAX_CHAN, GENERATE_ISR, ())
#pragma GCC diagnostic pop
#define IRQ_CONFIGURE(n, idx) \
IRQ_CONNECT(DT_INST_IRQ_BY_IDX(idx, n, irq), DT_INST_IRQ_BY_IDX(idx, n, priority), \
dma_wch_isr##n, DEVICE_DT_INST_GET(idx), 0); \
irq_enable(DT_INST_IRQ_BY_IDX(idx, n, irq));
#define CONFIGURE_ALL_IRQS(idx, n) LISTIFY(n, IRQ_CONFIGURE, (), idx)
#define WCH_DMA_INIT(idx) \
static void dma_wch##idx##_irq_config(const struct device *dev) \
{ \
CONFIGURE_ALL_IRQS(idx, DT_NUM_IRQS(DT_DRV_INST(idx))); \
} \
static const struct dma_wch_config dma_wch##idx##_config = { \
.regs = (struct dma_wch_regs *)DT_INST_REG_ADDR(idx), \
.num_channels = DT_INST_PROP(idx, dma_channels), \
.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(idx)), \
.clock_id = DT_INST_CLOCKS_CELL(idx, id), \
.irq_config_func = dma_wch##idx##_irq_config, \
}; \
static struct dma_wch_channel dma_wch##idx##_channels[DT_INST_PROP(idx, dma_channels)]; \
ATOMIC_DEFINE(dma_wch_atomic##idx, DT_INST_PROP(idx, dma_channels)); \
static struct dma_wch_data dma_wch##idx##_data = { \
.ctx = \
{ \
.magic = DMA_MAGIC, \
.atomic = dma_wch_atomic##idx, \
.dma_channels = DT_INST_PROP(idx, dma_channels), \
}, \
.channels = dma_wch##idx##_channels, \
}; \
\
DEVICE_DT_INST_DEFINE(idx, &dma_wch_init, NULL, &dma_wch##idx##_data, \
&dma_wch##idx##_config, PRE_KERNEL_1, CONFIG_DMA_INIT_PRIORITY, \
&dma_wch_driver_api);
DT_INST_FOREACH_STATUS_OKAY(WCH_DMA_INIT)

View File

@ -0,0 +1,33 @@
# Copyright (c) 2024 Paul Wedeck <paulwedeck@gmail.com>
# SPDX-License-Identifier: Apache-2.0
description: |
WCH DMA controller
The WCH DMA controller is a general-purpose direct memory access controller
featuring between 7 and 11 independent channels.
Every channel is capable of memory-to-memory, memory-to-peripheral, and
peripheral-to-memory access.
Mapping of peripheral requests to DMA channels is limited and SoC specific.
Commonly, each peripheral request maps to just a single DMA channel.
The controller supports 8, 16, and 32 bit width memory access.
It is present on WCH CH32V and CH32X series SoCs.
compatible: "wch,wch-dma"
include: dma-controller.yaml
properties:
reg:
required: true
interrupts:
required: true
"#dma-cells":
const: 1
dma-cells:
- channel

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2024 Michael Hope
* Copyright (c) 2024 Paul Wedeck <paulwedeck@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -10,6 +11,7 @@
#include <zephyr/dt-bindings/gpio/gpio.h>
#include <zephyr/dt-bindings/i2c/i2c.h>
#include <zephyr/dt-bindings/clock/ch32v00x-clocks.h>
#include <zephyr/dt-bindings/dma/ch32v003-dma.h>
/ {
clocks {
@ -114,6 +116,16 @@
#clock-cells = <1>;
status = "okay";
};
dma1: dma@40020000 {
compatible = "wch,wch-dma";
reg = <0x40020000 0x90>;
clocks = <&rcc CH32V00X_CLOCK_DMA1>;
#dma-cells = <1>;
interrupt-parent = <&pfic>;
interrupts = <22>, <23>, <24>, <25>, <26>, <27>, <28>;
dma-channels = <7>;
};
};
};

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2025 Paul Wedeck
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_CH32V003_DMA_H_
#define ZEPHYR_INCLUDE_DT_BINDINGS_CH32V003_DMA_H_
#define CH32V003_ADC1_DMA 0
#define CH32V003_SPI1_RX_DMA 1
#define CH32V003_SPI1_TX_DMA 2
#define CH32V003_USART1_RX_DMA 4
#define CH32V003_USART1_TX_DMA 3
#define CH32V003_I2C1_RX_DMA 6
#define CH32V003_I2C1_TX_DMA 5
#define CH32V003_TIM1_CH1_DMA 1
#define CH32V003_TIM1_CH2_DMA 2
#define CH32V003_TIM1_CH3_DMA 5
#define CH32V003_TIM1_CH4_DMA 3
#define CH32V003_TIM1_TRIG 3
#define CH32V003_TIM1_COM 3
#define CH32V003_TIM1_UP 4
#define CH32V003_TIM2_CH1_DMA 4
#define CH32V003_TIM2_CH2_DMA 6
#define CH32V003_TIM2_CH3_DMA 0
#define CH32V003_TIM2_CH4_DMA 6
#define CH32V003_TIM2_UP 1
#endif /*ZEPHYR_INCLUDE_DT_BINDINGS_CH32V003_DMA_H_*/