| /* |
| * Copyright (c) 2022 TOKITA Hiroshi <tokita.hiroshi@gmail.com> |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/clock_control/gd32.h> |
| #include <zephyr/drivers/dma.h> |
| #include <zephyr/drivers/reset.h> |
| #include <zephyr/logging/log.h> |
| |
| #include <gd32_dma.h> |
| #include <zephyr/irq.h> |
| |
| #if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1) |
| #define DT_DRV_COMPAT gd_gd32_dma_v1 |
| #elif DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma) |
| #define DT_DRV_COMPAT gd_gd32_dma |
| #endif |
| |
| #if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1) |
| #define CHXCTL_PERIEN_OFFSET ((uint32_t)25U) |
| #define GD32_DMA_CHXCTL_DIR BIT(6) |
| #define GD32_DMA_CHXCTL_M2M BIT(7) |
| #define GD32_DMA_INTERRUPT_ERRORS (DMA_CHXCTL_SDEIE | DMA_CHXCTL_TAEIE) |
| #define GD32_DMA_FLAG_ERRORS (DMA_FLAG_SDE | DMA_FLAG_TAE) |
| #else |
| #define GD32_DMA_CHXCTL_DIR BIT(4) |
| #define GD32_DMA_CHXCTL_M2M BIT(14) |
| #define GD32_DMA_INTERRUPT_ERRORS DMA_CHXCTL_ERRIE |
| #define GD32_DMA_FLAG_ERRORS DMA_FLAG_ERR |
| #endif |
| |
| #ifdef CONFIG_SOC_SERIES_GD32F3X0 |
| #undef DMA_INTF |
| #undef DMA_INTC |
| #undef DMA_CHCTL |
| #undef DMA_CHCNT |
| #undef DMA_CHPADDR |
| #undef DMA_CHMADDR |
| |
| #define DMA_INTF(dma) REG32(dma + 0x00UL) |
| #define DMA_INTC(dma) REG32(dma + 0x04UL) |
| #define DMA_CHCTL(dma, ch) REG32((dma + 0x08UL) + 0x14UL * (uint32_t)(ch)) |
| #define DMA_CHCNT(dma, ch) REG32((dma + 0x0CUL) + 0x14UL * (uint32_t)(ch)) |
| #define DMA_CHPADDR(dma, ch) REG32((dma + 0x10UL) + 0x14UL * (uint32_t)(ch)) |
| #define DMA_CHMADDR(dma, ch) REG32((dma + 0x14UL) + 0x14UL * (uint32_t)(ch)) |
| #endif |
| |
| #define GD32_DMA_INTF(dma) DMA_INTF(dma) |
| #define GD32_DMA_INTC(dma) DMA_INTC(dma) |
| #define GD32_DMA_CHCTL(dma, ch) DMA_CHCTL((dma), (ch)) |
| #define GD32_DMA_CHCNT(dma, ch) DMA_CHCNT((dma), (ch)) |
| #define GD32_DMA_CHPADDR(dma, ch) DMA_CHPADDR((dma), (ch)) |
| #define GD32_DMA_CHMADDR(dma, ch) DMA_CHMADDR((dma), (ch)) |
| |
| LOG_MODULE_REGISTER(dma_gd32, CONFIG_DMA_LOG_LEVEL); |
| |
| struct dma_gd32_config { |
| uint32_t reg; |
| uint32_t channels; |
| uint16_t clkid; |
| bool mem2mem; |
| #if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1) |
| struct reset_dt_spec reset; |
| #endif |
| void (*irq_configure)(void); |
| }; |
| |
| struct dma_gd32_channel { |
| dma_callback_t callback; |
| void *user_data; |
| uint32_t direction; |
| bool busy; |
| }; |
| |
| struct dma_gd32_data { |
| struct dma_context ctx; |
| struct dma_gd32_channel *channels; |
| }; |
| |
| struct dma_gd32_srcdst_config { |
| uint32_t addr; |
| uint32_t adj; |
| uint32_t width; |
| }; |
| |
| /* |
| * Register access functions |
| */ |
| |
| static inline void |
| gd32_dma_periph_increase_enable(uint32_t reg, dma_channel_enum ch) |
| { |
| GD32_DMA_CHCTL(reg, ch) |= DMA_CHXCTL_PNAGA; |
| } |
| |
| static inline void |
| gd32_dma_periph_increase_disable(uint32_t reg, dma_channel_enum ch) |
| { |
| GD32_DMA_CHCTL(reg, ch) &= ~DMA_CHXCTL_PNAGA; |
| } |
| |
| static inline void |
| gd32_dma_transfer_set_memory_to_memory(uint32_t reg, dma_channel_enum ch) |
| { |
| GD32_DMA_CHCTL(reg, ch) |= GD32_DMA_CHXCTL_M2M; |
| GD32_DMA_CHCTL(reg, ch) &= ~GD32_DMA_CHXCTL_DIR; |
| } |
| |
| static inline void |
| gd32_dma_transfer_set_memory_to_periph(uint32_t reg, dma_channel_enum ch) |
| { |
| GD32_DMA_CHCTL(reg, ch) &= ~GD32_DMA_CHXCTL_M2M; |
| GD32_DMA_CHCTL(reg, ch) |= GD32_DMA_CHXCTL_DIR; |
| } |
| |
| static inline void |
| gd32_dma_transfer_set_periph_to_memory(uint32_t reg, dma_channel_enum ch) |
| { |
| GD32_DMA_CHCTL(reg, ch) &= ~GD32_DMA_CHXCTL_M2M; |
| GD32_DMA_CHCTL(reg, ch) &= ~GD32_DMA_CHXCTL_DIR; |
| } |
| |
| static inline void |
| gd32_dma_memory_increase_enable(uint32_t reg, dma_channel_enum ch) |
| { |
| GD32_DMA_CHCTL(reg, ch) |= DMA_CHXCTL_MNAGA; |
| } |
| |
| static inline void |
| gd32_dma_memory_increase_disable(uint32_t reg, dma_channel_enum ch) |
| { |
| GD32_DMA_CHCTL(reg, ch) &= ~DMA_CHXCTL_MNAGA; |
| } |
| |
| static inline void |
| gd32_dma_circulation_enable(uint32_t reg, dma_channel_enum ch) |
| { |
| GD32_DMA_CHCTL(reg, ch) |= DMA_CHXCTL_CMEN; |
| } |
| |
| static inline void |
| gd32_dma_circulation_disable(uint32_t reg, dma_channel_enum ch) |
| { |
| GD32_DMA_CHCTL(reg, ch) &= ~DMA_CHXCTL_CMEN; |
| } |
| |
| static inline void gd32_dma_channel_enable(uint32_t reg, dma_channel_enum ch) |
| { |
| GD32_DMA_CHCTL(reg, ch) |= DMA_CHXCTL_CHEN; |
| } |
| |
| static inline void gd32_dma_channel_disable(uint32_t reg, dma_channel_enum ch) |
| { |
| GD32_DMA_CHCTL(reg, ch) &= ~DMA_CHXCTL_CHEN; |
| } |
| |
| static inline void |
| gd32_dma_interrupt_enable(uint32_t reg, dma_channel_enum ch, uint32_t source) |
| { |
| GD32_DMA_CHCTL(reg, ch) |= source; |
| } |
| |
| static inline void |
| gd32_dma_interrupt_disable(uint32_t reg, dma_channel_enum ch, uint32_t source) |
| { |
| GD32_DMA_CHCTL(reg, ch) &= ~source; |
| } |
| |
| static inline void |
| gd32_dma_priority_config(uint32_t reg, dma_channel_enum ch, uint32_t priority) |
| { |
| uint32_t ctl = GD32_DMA_CHCTL(reg, ch); |
| |
| GD32_DMA_CHCTL(reg, ch) = (ctl & (~DMA_CHXCTL_PRIO)) | priority; |
| } |
| |
| static inline void |
| gd32_dma_memory_width_config(uint32_t reg, dma_channel_enum ch, uint32_t mwidth) |
| { |
| uint32_t ctl = GD32_DMA_CHCTL(reg, ch); |
| |
| GD32_DMA_CHCTL(reg, ch) = (ctl & (~DMA_CHXCTL_MWIDTH)) | mwidth; |
| } |
| |
| static inline void |
| gd32_dma_periph_width_config(uint32_t reg, dma_channel_enum ch, uint32_t pwidth) |
| { |
| uint32_t ctl = GD32_DMA_CHCTL(reg, ch); |
| |
| GD32_DMA_CHCTL(reg, ch) = (ctl & (~DMA_CHXCTL_PWIDTH)) | pwidth; |
| } |
| |
| #if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1) |
| static inline void |
| gd32_dma_channel_subperipheral_select(uint32_t reg, dma_channel_enum ch, |
| dma_subperipheral_enum sub_periph) |
| { |
| uint32_t ctl = GD32_DMA_CHCTL(reg, ch); |
| |
| GD32_DMA_CHCTL(reg, ch) = |
| (ctl & (~DMA_CHXCTL_PERIEN)) | |
| ((uint32_t)sub_periph << CHXCTL_PERIEN_OFFSET); |
| } |
| #endif |
| |
| static inline void |
| gd32_dma_periph_address_config(uint32_t reg, dma_channel_enum ch, uint32_t addr) |
| { |
| GD32_DMA_CHPADDR(reg, ch) = addr; |
| } |
| |
| static inline void |
| gd32_dma_memory_address_config(uint32_t reg, dma_channel_enum ch, uint32_t addr) |
| { |
| #if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1) |
| DMA_CHM0ADDR(reg, ch) = addr; |
| #else |
| GD32_DMA_CHMADDR(reg, ch) = addr; |
| #endif |
| } |
| |
| static inline void |
| gd32_dma_transfer_number_config(uint32_t reg, dma_channel_enum ch, uint32_t num) |
| { |
| GD32_DMA_CHCNT(reg, ch) = (num & DMA_CHXCNT_CNT); |
| } |
| |
| static inline uint32_t |
| gd32_dma_transfer_number_get(uint32_t reg, dma_channel_enum ch) |
| { |
| return GD32_DMA_CHCNT(reg, ch); |
| } |
| |
| static inline void |
| gd32_dma_interrupt_flag_clear(uint32_t reg, dma_channel_enum ch, uint32_t flag) |
| { |
| #if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1) |
| if (ch < DMA_CH4) { |
| DMA_INTC0(reg) |= DMA_FLAG_ADD(flag, ch); |
| } else { |
| DMA_INTC1(reg) |= DMA_FLAG_ADD(flag, ch - DMA_CH4); |
| } |
| #else |
| GD32_DMA_INTC(reg) |= DMA_FLAG_ADD(flag, ch); |
| #endif |
| } |
| |
| static inline void |
| gd32_dma_flag_clear(uint32_t reg, dma_channel_enum ch, uint32_t flag) |
| { |
| #if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1) |
| if (ch < DMA_CH4) { |
| DMA_INTC0(reg) |= DMA_FLAG_ADD(flag, ch); |
| } else { |
| DMA_INTC1(reg) |= DMA_FLAG_ADD(flag, ch - DMA_CH4); |
| } |
| #else |
| GD32_DMA_INTC(reg) |= DMA_FLAG_ADD(flag, ch); |
| #endif |
| } |
| |
| static inline uint32_t |
| gd32_dma_interrupt_flag_get(uint32_t reg, dma_channel_enum ch, uint32_t flag) |
| { |
| #if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1) |
| if (ch < DMA_CH4) { |
| return (DMA_INTF0(reg) & DMA_FLAG_ADD(flag, ch)); |
| } else { |
| return (DMA_INTF1(reg) & DMA_FLAG_ADD(flag, ch - DMA_CH4)); |
| } |
| #else |
| return (GD32_DMA_INTF(reg) & DMA_FLAG_ADD(flag, ch)); |
| #endif |
| } |
| |
| static inline void gd32_dma_deinit(uint32_t reg, dma_channel_enum ch) |
| { |
| GD32_DMA_CHCTL(reg, ch) &= ~DMA_CHXCTL_CHEN; |
| |
| GD32_DMA_CHCTL(reg, ch) = DMA_CHCTL_RESET_VALUE; |
| GD32_DMA_CHCNT(reg, ch) = DMA_CHCNT_RESET_VALUE; |
| GD32_DMA_CHPADDR(reg, ch) = DMA_CHPADDR_RESET_VALUE; |
| #if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1) |
| DMA_CHM0ADDR(reg, ch) = DMA_CHMADDR_RESET_VALUE; |
| DMA_CHFCTL(reg, ch) = DMA_CHFCTL_RESET_VALUE; |
| if (ch < DMA_CH4) { |
| DMA_INTC0(reg) |= DMA_FLAG_ADD(DMA_CHINTF_RESET_VALUE, ch); |
| } else { |
| DMA_INTC1(reg) |= |
| DMA_FLAG_ADD(DMA_CHINTF_RESET_VALUE, ch - DMA_CH4); |
| } |
| #else |
| GD32_DMA_CHMADDR(reg, ch) = DMA_CHMADDR_RESET_VALUE; |
| GD32_DMA_INTC(reg) |= DMA_FLAG_ADD(DMA_CHINTF_RESET_VALUE, ch); |
| #endif |
| } |
| |
| /* |
| * Utility functions |
| */ |
| |
| static inline uint32_t dma_gd32_priority(uint32_t prio) |
| { |
| return CHCTL_PRIO(prio); |
| } |
| |
| static inline uint32_t dma_gd32_memory_width(uint32_t width) |
| { |
| switch (width) { |
| case 4: |
| return CHCTL_MWIDTH(2); |
| case 2: |
| return CHCTL_MWIDTH(1); |
| default: |
| return CHCTL_MWIDTH(0); |
| } |
| } |
| |
| static inline uint32_t dma_gd32_periph_width(uint32_t width) |
| { |
| switch (width) { |
| case 4: |
| return CHCTL_PWIDTH(2); |
| case 2: |
| return CHCTL_PWIDTH(1); |
| default: |
| return CHCTL_PWIDTH(0); |
| } |
| } |
| |
| /* |
| * API functions |
| */ |
| |
| static int dma_gd32_config(const struct device *dev, uint32_t channel, |
| struct dma_config *dma_cfg) |
| { |
| const struct dma_gd32_config *cfg = dev->config; |
| struct dma_gd32_data *data = dev->data; |
| struct dma_gd32_srcdst_config src_cfg; |
| struct dma_gd32_srcdst_config dst_cfg; |
| struct dma_gd32_srcdst_config *memory_cfg = NULL; |
| struct dma_gd32_srcdst_config *periph_cfg = NULL; |
| |
| if (channel >= cfg->channels) { |
| LOG_ERR("channel must be < %" PRIu32 " (%" PRIu32 ")", |
| cfg->channels, channel); |
| return -EINVAL; |
| } |
| |
| if (dma_cfg->block_count != 1) { |
| LOG_ERR("chained block transfer not supported."); |
| return -ENOTSUP; |
| } |
| |
| if (dma_cfg->channel_priority > 3) { |
| LOG_ERR("channel_priority must be < 4 (%" PRIu32 ")", |
| dma_cfg->channel_priority); |
| return -EINVAL; |
| } |
| |
| if (dma_cfg->head_block->source_addr_adj == DMA_ADDR_ADJ_DECREMENT) { |
| LOG_ERR("source_addr_adj not supported DMA_ADDR_ADJ_DECREMENT"); |
| return -ENOTSUP; |
| } |
| |
| if (dma_cfg->head_block->dest_addr_adj == DMA_ADDR_ADJ_DECREMENT) { |
| LOG_ERR("dest_addr_adj not supported DMA_ADDR_ADJ_DECREMENT"); |
| return -ENOTSUP; |
| } |
| |
| if (dma_cfg->head_block->source_addr_adj != DMA_ADDR_ADJ_INCREMENT && |
| dma_cfg->head_block->source_addr_adj != DMA_ADDR_ADJ_NO_CHANGE) { |
| LOG_ERR("invalid source_addr_adj %" PRIu16, |
| dma_cfg->head_block->source_addr_adj); |
| return -ENOTSUP; |
| } |
| if (dma_cfg->head_block->dest_addr_adj != DMA_ADDR_ADJ_INCREMENT && |
| dma_cfg->head_block->dest_addr_adj != DMA_ADDR_ADJ_NO_CHANGE) { |
| LOG_ERR("invalid dest_addr_adj %" PRIu16, |
| dma_cfg->head_block->dest_addr_adj); |
| return -ENOTSUP; |
| } |
| |
| if (dma_cfg->source_data_size != 1 && dma_cfg->source_data_size != 2 && |
| dma_cfg->source_data_size != 4) { |
| LOG_ERR("source_data_size must be 1, 2, or 4 (%" PRIu32 ")", |
| dma_cfg->source_data_size); |
| return -EINVAL; |
| } |
| |
| if (dma_cfg->dest_data_size != 1 && dma_cfg->dest_data_size != 2 && |
| dma_cfg->dest_data_size != 4) { |
| LOG_ERR("dest_data_size must be 1, 2, or 4 (%" PRIu32 ")", |
| dma_cfg->dest_data_size); |
| return -EINVAL; |
| } |
| |
| if (dma_cfg->channel_direction > PERIPHERAL_TO_MEMORY) { |
| LOG_ERR("channel_direction must be MEMORY_TO_MEMORY, " |
| "MEMORY_TO_PERIPHERAL or PERIPHERAL_TO_MEMORY (%" PRIu32 |
| ")", |
| dma_cfg->channel_direction); |
| return -ENOTSUP; |
| } |
| |
| if (dma_cfg->channel_direction == MEMORY_TO_MEMORY && !cfg->mem2mem) { |
| LOG_ERR("not supporting MEMORY_TO_MEMORY"); |
| return -ENOTSUP; |
| } |
| |
| #if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1) |
| if (dma_cfg->dma_slot > 0xF) { |
| LOG_ERR("dma_slot must be <7 (%" PRIu32 ")", |
| dma_cfg->dma_slot); |
| return -EINVAL; |
| } |
| #endif |
| |
| gd32_dma_deinit(cfg->reg, channel); |
| |
| src_cfg.addr = dma_cfg->head_block->source_address; |
| src_cfg.adj = dma_cfg->head_block->source_addr_adj; |
| src_cfg.width = dma_cfg->source_data_size; |
| |
| dst_cfg.addr = dma_cfg->head_block->dest_address; |
| dst_cfg.adj = dma_cfg->head_block->dest_addr_adj; |
| dst_cfg.width = dma_cfg->dest_data_size; |
| |
| switch (dma_cfg->channel_direction) { |
| case MEMORY_TO_MEMORY: |
| gd32_dma_transfer_set_memory_to_memory(cfg->reg, channel); |
| memory_cfg = &dst_cfg; |
| periph_cfg = &src_cfg; |
| break; |
| case PERIPHERAL_TO_MEMORY: |
| gd32_dma_transfer_set_periph_to_memory(cfg->reg, channel); |
| memory_cfg = &dst_cfg; |
| periph_cfg = &src_cfg; |
| break; |
| case MEMORY_TO_PERIPHERAL: |
| gd32_dma_transfer_set_memory_to_periph(cfg->reg, channel); |
| memory_cfg = &src_cfg; |
| periph_cfg = &dst_cfg; |
| break; |
| } |
| |
| gd32_dma_memory_address_config(cfg->reg, channel, memory_cfg->addr); |
| if (memory_cfg->adj == DMA_ADDR_ADJ_INCREMENT) { |
| gd32_dma_memory_increase_enable(cfg->reg, channel); |
| } else { |
| gd32_dma_memory_increase_disable(cfg->reg, channel); |
| } |
| |
| gd32_dma_periph_address_config(cfg->reg, channel, periph_cfg->addr); |
| if (periph_cfg->adj == DMA_ADDR_ADJ_INCREMENT) { |
| gd32_dma_periph_increase_enable(cfg->reg, channel); |
| } else { |
| gd32_dma_periph_increase_disable(cfg->reg, channel); |
| } |
| |
| gd32_dma_transfer_number_config(cfg->reg, channel, |
| dma_cfg->head_block->block_size); |
| gd32_dma_priority_config(cfg->reg, channel, |
| dma_gd32_priority(dma_cfg->channel_priority)); |
| gd32_dma_memory_width_config(cfg->reg, channel, |
| dma_gd32_memory_width(memory_cfg->width)); |
| gd32_dma_periph_width_config(cfg->reg, channel, |
| dma_gd32_periph_width(periph_cfg->width)); |
| gd32_dma_circulation_disable(cfg->reg, channel); |
| #if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1) |
| if (dma_cfg->channel_direction != MEMORY_TO_MEMORY) { |
| gd32_dma_channel_subperipheral_select(cfg->reg, channel, |
| dma_cfg->dma_slot); |
| } |
| #endif |
| |
| data->channels[channel].callback = dma_cfg->dma_callback; |
| data->channels[channel].user_data = dma_cfg->user_data; |
| data->channels[channel].direction = dma_cfg->channel_direction; |
| |
| return 0; |
| } |
| |
| static int dma_gd32_reload(const struct device *dev, uint32_t ch, uint32_t src, |
| uint32_t dst, size_t size) |
| { |
| const struct dma_gd32_config *cfg = dev->config; |
| struct dma_gd32_data *data = dev->data; |
| |
| if (ch >= cfg->channels) { |
| LOG_ERR("reload channel must be < %" PRIu32 " (%" PRIu32 ")", |
| cfg->channels, ch); |
| return -EINVAL; |
| } |
| |
| if (data->channels[ch].busy) { |
| return -EBUSY; |
| } |
| |
| gd32_dma_channel_disable(cfg->reg, ch); |
| |
| gd32_dma_transfer_number_config(cfg->reg, ch, size); |
| |
| switch (data->channels[ch].direction) { |
| case MEMORY_TO_MEMORY: |
| case PERIPHERAL_TO_MEMORY: |
| gd32_dma_memory_address_config(cfg->reg, ch, dst); |
| gd32_dma_periph_address_config(cfg->reg, ch, src); |
| break; |
| case MEMORY_TO_PERIPHERAL: |
| gd32_dma_memory_address_config(cfg->reg, ch, src); |
| gd32_dma_periph_address_config(cfg->reg, ch, dst); |
| break; |
| } |
| |
| gd32_dma_channel_enable(cfg->reg, ch); |
| |
| return 0; |
| } |
| |
| static int dma_gd32_start(const struct device *dev, uint32_t ch) |
| { |
| const struct dma_gd32_config *cfg = dev->config; |
| struct dma_gd32_data *data = dev->data; |
| |
| if (ch >= cfg->channels) { |
| LOG_ERR("start channel must be < %" PRIu32 " (%" PRIu32 ")", |
| cfg->channels, ch); |
| return -EINVAL; |
| } |
| |
| gd32_dma_interrupt_enable(cfg->reg, ch, |
| DMA_CHXCTL_FTFIE | GD32_DMA_INTERRUPT_ERRORS); |
| gd32_dma_channel_enable(cfg->reg, ch); |
| data->channels[ch].busy = true; |
| |
| return 0; |
| } |
| |
| static int dma_gd32_stop(const struct device *dev, uint32_t ch) |
| { |
| const struct dma_gd32_config *cfg = dev->config; |
| struct dma_gd32_data *data = dev->data; |
| |
| if (ch >= cfg->channels) { |
| LOG_ERR("stop channel must be < %" PRIu32 " (%" PRIu32 ")", |
| cfg->channels, ch); |
| return -EINVAL; |
| } |
| |
| gd32_dma_interrupt_disable( |
| cfg->reg, ch, DMA_CHXCTL_FTFIE | GD32_DMA_INTERRUPT_ERRORS); |
| gd32_dma_interrupt_flag_clear(cfg->reg, ch, |
| DMA_FLAG_FTF | GD32_DMA_FLAG_ERRORS); |
| gd32_dma_channel_disable(cfg->reg, ch); |
| data->channels[ch].busy = false; |
| |
| return 0; |
| } |
| |
| static int dma_gd32_get_status(const struct device *dev, uint32_t ch, |
| struct dma_status *stat) |
| { |
| const struct dma_gd32_config *cfg = dev->config; |
| struct dma_gd32_data *data = dev->data; |
| |
| if (ch >= cfg->channels) { |
| LOG_ERR("channel must be < %" PRIu32 " (%" PRIu32 ")", |
| cfg->channels, ch); |
| return -EINVAL; |
| } |
| |
| stat->pending_length = gd32_dma_transfer_number_get(cfg->reg, ch); |
| stat->dir = data->channels[ch].direction; |
| stat->busy = data->channels[ch].busy; |
| |
| return 0; |
| } |
| |
| static bool dma_gd32_api_chan_filter(const struct device *dev, int ch, |
| void *filter_param) |
| { |
| uint32_t filter; |
| |
| if (!filter_param) { |
| LOG_ERR("filter_param must not be NULL"); |
| return false; |
| } |
| |
| filter = *((uint32_t *)filter_param); |
| |
| return (filter & BIT(ch)); |
| } |
| |
| static int dma_gd32_init(const struct device *dev) |
| { |
| const struct dma_gd32_config *cfg = dev->config; |
| |
| (void)clock_control_on(GD32_CLOCK_CONTROLLER, |
| (clock_control_subsys_t)&cfg->clkid); |
| |
| #if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1) |
| (void)reset_line_toggle_dt(&cfg->reset); |
| #endif |
| |
| for (uint32_t i = 0; i < cfg->channels; i++) { |
| gd32_dma_interrupt_disable(cfg->reg, i, |
| DMA_CHXCTL_FTFIE | GD32_DMA_INTERRUPT_ERRORS); |
| gd32_dma_deinit(cfg->reg, i); |
| } |
| |
| cfg->irq_configure(); |
| |
| return 0; |
| } |
| |
| static void dma_gd32_isr(const struct device *dev) |
| { |
| const struct dma_gd32_config *cfg = dev->config; |
| struct dma_gd32_data *data = dev->data; |
| uint32_t errflag, ftfflag; |
| int err = 0; |
| |
| for (uint32_t i = 0; i < cfg->channels; i++) { |
| errflag = gd32_dma_interrupt_flag_get(cfg->reg, i, |
| GD32_DMA_FLAG_ERRORS); |
| ftfflag = |
| gd32_dma_interrupt_flag_get(cfg->reg, i, DMA_FLAG_FTF); |
| |
| if (errflag == 0 && ftfflag == 0) { |
| continue; |
| } |
| |
| if (errflag) { |
| err = -EIO; |
| } |
| |
| gd32_dma_interrupt_flag_clear( |
| cfg->reg, i, DMA_FLAG_FTF | GD32_DMA_FLAG_ERRORS); |
| data->channels[i].busy = false; |
| |
| if (data->channels[i].callback) { |
| data->channels[i].callback( |
| dev, data->channels[i].user_data, i, err); |
| } |
| } |
| } |
| |
| static const struct dma_driver_api dma_gd32_driver_api = { |
| .config = dma_gd32_config, |
| .reload = dma_gd32_reload, |
| .start = dma_gd32_start, |
| .stop = dma_gd32_stop, |
| .get_status = dma_gd32_get_status, |
| .chan_filter = dma_gd32_api_chan_filter, |
| }; |
| |
| #define IRQ_CONFIGURE(n, inst) \ |
| IRQ_CONNECT(DT_INST_IRQ_BY_IDX(inst, n, irq), \ |
| DT_INST_IRQ_BY_IDX(inst, n, priority), dma_gd32_isr, \ |
| DEVICE_DT_INST_GET(inst), 0); \ |
| irq_enable(DT_INST_IRQ_BY_IDX(inst, n, irq)); |
| |
| #define CONFIGURE_ALL_IRQS(inst, n) LISTIFY(n, IRQ_CONFIGURE, (), inst) |
| |
| #define GD32_DMA_INIT(inst) \ |
| static void dma_gd32##inst##_irq_configure(void) \ |
| { \ |
| CONFIGURE_ALL_IRQS(inst, DT_NUM_IRQS(DT_DRV_INST(inst))); \ |
| } \ |
| static const struct dma_gd32_config dma_gd32##inst##_config = { \ |
| .reg = DT_INST_REG_ADDR(inst), \ |
| .channels = DT_INST_PROP(inst, dma_channels), \ |
| .clkid = DT_INST_CLOCKS_CELL(inst, id), \ |
| .mem2mem = DT_INST_PROP(inst, gd_mem2mem), \ |
| IF_ENABLED(DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_dma_v1), \ |
| (.reset = RESET_DT_SPEC_INST_GET(inst),)) \ |
| .irq_configure = dma_gd32##inst##_irq_configure, \ |
| }; \ |
| \ |
| static struct dma_gd32_channel \ |
| dma_gd32##inst##_channels[DT_INST_PROP(inst, dma_channels)]; \ |
| ATOMIC_DEFINE(dma_gd32_atomic##inst, \ |
| DT_INST_PROP(inst, dma_channels)); \ |
| static struct dma_gd32_data dma_gd32##inst##_data = { \ |
| .ctx = { \ |
| .magic = DMA_MAGIC, \ |
| .atomic = dma_gd32_atomic##inst, \ |
| .dma_channels = DT_INST_PROP(inst, dma_channels), \ |
| }, \ |
| .channels = dma_gd32##inst##_channels, \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(inst, &dma_gd32_init, NULL, \ |
| &dma_gd32##inst##_data, \ |
| &dma_gd32##inst##_config, POST_KERNEL, \ |
| CONFIG_DMA_INIT_PRIORITY, &dma_gd32_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(GD32_DMA_INIT) |