| /* |
| * Copyright (c) 2023 Andes Technology Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #include <errno.h> |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/device.h> |
| #include <zephyr/init.h> |
| #include <zephyr/drivers/dma.h> |
| |
| #define DT_DRV_COMPAT andestech_atcdmac300 |
| |
| #define LOG_LEVEL CONFIG_DMA_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(dma_andes_atcdmac300); |
| |
| #define ATCDMAC100_MAX_CHAN 8 |
| |
| #define DMA_ABORT(dev) (((struct dma_atcdmac300_cfg *)dev->config)->base + 0x24) |
| #define DMA_INT_STATUS(dev) \ |
| (((struct dma_atcdmac300_cfg *)dev->config)->base + 0x30) |
| |
| #define DMA_CH_OFFSET(ch) (ch * 0x20) |
| #define DMA_CH_CTRL(dev, ch) \ |
| (((struct dma_atcdmac300_cfg *)dev->config)->base + 0x40 + DMA_CH_OFFSET(ch)) |
| #define DMA_CH_TRANSIZE(dev, ch) \ |
| (((struct dma_atcdmac300_cfg *)dev->config)->base + 0x44 + DMA_CH_OFFSET(ch)) |
| #define DMA_CH_SRC_ADDR_L(dev, ch) \ |
| (((struct dma_atcdmac300_cfg *)dev->config)->base + 0x48 + DMA_CH_OFFSET(ch)) |
| #define DMA_CH_SRC_ADDR_H(dev, ch) \ |
| (((struct dma_atcdmac300_cfg *)dev->config)->base + 0x4C + DMA_CH_OFFSET(ch)) |
| #define DMA_CH_DST_ADDR_L(dev, ch) \ |
| (((struct dma_atcdmac300_cfg *)dev->config)->base + 0x50 + DMA_CH_OFFSET(ch)) |
| #define DMA_CH_DST_ADDR_H(dev, ch) \ |
| (((struct dma_atcdmac300_cfg *)dev->config)->base + 0x54 + DMA_CH_OFFSET(ch)) |
| #define DMA_CH_LL_PTR_L(dev, ch) \ |
| (((struct dma_atcdmac300_cfg *)dev->config)->base + 0x58 + DMA_CH_OFFSET(ch)) |
| #define DMA_CH_LL_PTR_H(dev, ch) \ |
| (((struct dma_atcdmac300_cfg *)dev->config)->base + 0x5C + DMA_CH_OFFSET(ch)) |
| |
| /* Source burst size options */ |
| #define DMA_BSIZE_1 (0) |
| #define DMA_BSIZE_2 (1) |
| #define DMA_BSIZE_4 (2) |
| #define DMA_BSIZE_8 (3) |
| #define DMA_BSIZE_16 (4) |
| #define DMA_BSIZE_32 (5) |
| #define DMA_BSIZE_64 (6) |
| #define DMA_BSIZE_128 (7) |
| #define DMA_BSIZE_256 (8) |
| #define DMA_BSIZE_512 (9) |
| #define DMA_BSIZE_1024 (10) |
| |
| /* Source/Destination transfer width options */ |
| #define DMA_WIDTH_BYTE (0) |
| #define DMA_WIDTH_HALFWORD (1) |
| #define DMA_WIDTH_WORD (2) |
| #define DMA_WIDTH_DWORD (3) |
| #define DMA_WIDTH_QWORD (4) |
| #define DMA_WIDTH_EWORD (5) |
| |
| /* Bus interface index */ |
| #define DMA_INF_IDX0 (0) |
| #define DMA_INF_IDX1 (1) |
| |
| /* DMA Channel Control Register Definition */ |
| #define DMA_CH_CTRL_SBINF_MASK BIT(31) |
| #define DMA_CH_CTRL_DBINF_MASK BIT(30) |
| #define DMA_CH_CTRL_PRIORITY_HIGH BIT(29) |
| #define DMA_CH_CTRL_SBSIZE_MASK GENMASK(27, 24) |
| #define DMA_CH_CTRL_SBSIZE(n) FIELD_PREP(DMA_CH_CTRL_SBSIZE_MASK, (n)) |
| #define DMA_CH_CTRL_SWIDTH_MASK GENMASK(23, 21) |
| #define DMA_CH_CTRL_SWIDTH(n) FIELD_PREP(DMA_CH_CTRL_SWIDTH_MASK, (n)) |
| #define DMA_CH_CTRL_DWIDTH_MASK GENMASK(20, 18) |
| #define DMA_CH_CTRL_DWIDTH(n) FIELD_PREP(DMA_CH_CTRL_DWIDTH_MASK, (n)) |
| #define DMA_CH_CTRL_SMODE_HANDSHAKE BIT(17) |
| #define DMA_CH_CTRL_DMODE_HANDSHAKE BIT(16) |
| #define DMA_CH_CTRL_SRCADDRCTRL_MASK GENMASK(15, 14) |
| #define DMA_CH_CTRL_SRCADDR_INC FIELD_PREP(DMA_CH_CTRL_SRCADDRCTRL_MASK, (0)) |
| #define DMA_CH_CTRL_SRCADDR_DEC FIELD_PREP(DMA_CH_CTRL_SRCADDRCTRL_MASK, (1)) |
| #define DMA_CH_CTRL_SRCADDR_FIX FIELD_PREP(DMA_CH_CTRL_SRCADDRCTRL_MASK, (2)) |
| #define DMA_CH_CTRL_DSTADDRCTRL_MASK GENMASK(13, 12) |
| #define DMA_CH_CTRL_DSTADDR_INC FIELD_PREP(DMA_CH_CTRL_DSTADDRCTRL_MASK, (0)) |
| #define DMA_CH_CTRL_DSTADDR_DEC FIELD_PREP(DMA_CH_CTRL_DSTADDRCTRL_MASK, (1)) |
| #define DMA_CH_CTRL_DSTADDR_FIX FIELD_PREP(DMA_CH_CTRL_DSTADDRCTRL_MASK, (2)) |
| #define DMA_CH_CTRL_SRCREQ_MASK GENMASK(11, 8) |
| #define DMA_CH_CTRL_SRCREQ(n) FIELD_PREP(DMA_CH_CTRL_SRCREQ_MASK, (n)) |
| #define DMA_CH_CTRL_DSTREQ_MASK GENMASK(7, 4) |
| #define DMA_CH_CTRL_DSTREQ(n) FIELD_PREP(DMA_CH_CTRL_DSTREQ_MASK, (n)) |
| #define DMA_CH_CTRL_INTABT BIT(3) |
| #define DMA_CH_CTRL_INTERR BIT(2) |
| #define DMA_CH_CTRL_INTTC BIT(1) |
| #define DMA_CH_CTRL_ENABLE BIT(0) |
| |
| /* DMA Interrupt Status Register Definition */ |
| #define DMA_INT_STATUS_TC_MASK GENMASK(23, 16) |
| #define DMA_INT_STATUS_ABORT_MASK GENMASK(15, 8) |
| #define DMA_INT_STATUS_ERROR_MASK GENMASK(7, 0) |
| #define DMA_INT_STATUS_TC_VAL(x) FIELD_GET(DMA_INT_STATUS_TC_MASK, (x)) |
| #define DMA_INT_STATUS_ABORT_VAL(x) FIELD_GET(DMA_INT_STATUS_ABORT_MASK, (x)) |
| #define DMA_INT_STATUS_ERROR_VAL(x) FIELD_GET(DMA_INT_STATUS_ERROR_MASK, (x)) |
| #define DMA_INT_STATUS_CH_MSK(ch) (0x111 << ch) |
| |
| typedef void (*atcdmac300_cfg_func_t)(void); |
| |
| struct chain_block { |
| uint32_t ctrl; |
| uint32_t transize; |
| uint32_t srcaddrl; |
| uint32_t srcaddrh; |
| uint32_t dstaddrl; |
| uint32_t dstaddrh; |
| uint32_t llpointerl; |
| uint32_t llpointerh; |
| #if __riscv_xlen == 32 |
| uint32_t reserved; |
| #endif |
| struct chain_block *next_block; |
| }; |
| |
| /* data for each DMA channel */ |
| struct dma_chan_data { |
| void *blkuser_data; |
| dma_callback_t blkcallback; |
| struct chain_block *head_block; |
| struct dma_status status; |
| }; |
| |
| /* Device run time data */ |
| struct dma_atcdmac300_data { |
| struct dma_chan_data chan[ATCDMAC100_MAX_CHAN]; |
| struct k_spinlock lock; |
| }; |
| |
| /* Device constant configuration parameters */ |
| struct dma_atcdmac300_cfg { |
| atcdmac300_cfg_func_t irq_config; |
| uint32_t base; |
| uint32_t irq_num; |
| }; |
| |
| static struct __aligned(64) |
| chain_block dma_chain[ATCDMAC100_MAX_CHAN][sizeof(struct chain_block) * 16]; |
| |
| static void dma_atcdmac300_isr(const struct device *dev) |
| { |
| uint32_t int_status, int_ch_status, channel; |
| struct dma_atcdmac300_data *const data = dev->data; |
| struct dma_chan_data *ch_data; |
| k_spinlock_key_t key; |
| |
| key = k_spin_lock(&data->lock); |
| int_status = sys_read32(DMA_INT_STATUS(dev)); |
| /* Clear interrupt*/ |
| sys_write32(int_status, DMA_INT_STATUS(dev)); |
| |
| k_spin_unlock(&data->lock, key); |
| |
| /* Handle terminal count status */ |
| int_ch_status = DMA_INT_STATUS_TC_VAL(int_status); |
| while (int_ch_status) { |
| channel = find_msb_set(int_ch_status) - 1; |
| int_ch_status &= ~(BIT(channel)); |
| |
| ch_data = &data->chan[channel]; |
| if (ch_data->blkcallback) { |
| ch_data->blkcallback(dev, ch_data->blkuser_data, channel, 0); |
| } |
| data->chan[channel].status.busy = false; |
| } |
| |
| /* Handle error status */ |
| int_ch_status = DMA_INT_STATUS_ERROR_VAL(int_status); |
| while (int_ch_status) { |
| channel = find_msb_set(int_ch_status) - 1; |
| int_ch_status &= ~(BIT(channel)); |
| |
| ch_data = &data->chan[channel]; |
| if (ch_data->blkcallback) { |
| ch_data->blkcallback(dev, ch_data->blkuser_data, channel, -EIO); |
| } |
| } |
| } |
| |
| static int dma_atcdmac300_config(const struct device *dev, uint32_t channel, |
| struct dma_config *cfg) |
| { |
| struct dma_atcdmac300_data *const data = dev->data; |
| uint32_t src_width, dst_width, src_burst_size, ch_ctrl, tfr_size; |
| int32_t ret = 0; |
| struct dma_block_config *cfg_blocks; |
| k_spinlock_key_t key; |
| |
| if (channel >= ATCDMAC100_MAX_CHAN) { |
| return -EINVAL; |
| } |
| |
| __ASSERT_NO_MSG(cfg->source_data_size == cfg->dest_data_size); |
| __ASSERT_NO_MSG(cfg->source_burst_length == cfg->dest_burst_length); |
| |
| if (cfg->source_data_size != 1 && cfg->source_data_size != 2 && |
| cfg->source_data_size != 4) { |
| LOG_ERR("Invalid 'source_data_size' value"); |
| ret = -EINVAL; |
| goto end; |
| } |
| |
| cfg_blocks = cfg->head_block; |
| if (cfg_blocks == NULL) { |
| ret = -EINVAL; |
| goto end; |
| } |
| |
| tfr_size = cfg_blocks->block_size/cfg->source_data_size; |
| if (tfr_size == 0) { |
| ret = -EINVAL; |
| goto end; |
| } |
| |
| ch_ctrl = 0; |
| |
| switch (cfg->channel_direction) { |
| case MEMORY_TO_MEMORY: |
| break; |
| case MEMORY_TO_PERIPHERAL: |
| ch_ctrl |= DMA_CH_CTRL_DSTREQ(cfg->dma_slot); |
| ch_ctrl |= DMA_CH_CTRL_DMODE_HANDSHAKE; |
| break; |
| case PERIPHERAL_TO_MEMORY: |
| ch_ctrl |= DMA_CH_CTRL_SRCREQ(cfg->dma_slot); |
| ch_ctrl |= DMA_CH_CTRL_SMODE_HANDSHAKE; |
| break; |
| default: |
| ret = -EINVAL; |
| goto end; |
| } |
| |
| |
| switch (cfg_blocks->source_addr_adj) { |
| case DMA_ADDR_ADJ_INCREMENT: |
| ch_ctrl |= DMA_CH_CTRL_SRCADDR_INC; |
| break; |
| case DMA_ADDR_ADJ_DECREMENT: |
| ch_ctrl |= DMA_CH_CTRL_SRCADDR_DEC; |
| break; |
| case DMA_ADDR_ADJ_NO_CHANGE: |
| ch_ctrl |= DMA_CH_CTRL_SRCADDR_FIX; |
| break; |
| default: |
| ret = -EINVAL; |
| goto end; |
| } |
| |
| switch (cfg_blocks->dest_addr_adj) { |
| case DMA_ADDR_ADJ_INCREMENT: |
| ch_ctrl |= DMA_CH_CTRL_DSTADDR_INC; |
| break; |
| case DMA_ADDR_ADJ_DECREMENT: |
| ch_ctrl |= DMA_CH_CTRL_DSTADDR_DEC; |
| break; |
| case DMA_ADDR_ADJ_NO_CHANGE: |
| ch_ctrl |= DMA_CH_CTRL_DSTADDR_FIX; |
| break; |
| default: |
| ret = -EINVAL; |
| goto end; |
| } |
| |
| ch_ctrl |= DMA_CH_CTRL_INTABT; |
| |
| /* Disable the error callback */ |
| if (!cfg->error_callback_dis) { |
| ch_ctrl |= DMA_CH_CTRL_INTERR; |
| } |
| |
| src_width = find_msb_set(cfg->source_data_size) - 1; |
| dst_width = find_msb_set(cfg->dest_data_size) - 1; |
| src_burst_size = find_msb_set(cfg->source_burst_length) - 1; |
| |
| ch_ctrl |= DMA_CH_CTRL_SWIDTH(src_width) | |
| DMA_CH_CTRL_DWIDTH(dst_width) | |
| DMA_CH_CTRL_SBSIZE(src_burst_size); |
| |
| |
| /* Reset DMA channel configuration */ |
| sys_write32(0, DMA_CH_CTRL(dev, channel)); |
| |
| key = k_spin_lock(&data->lock); |
| /* Clear DMA interrupts status */ |
| sys_write32(DMA_INT_STATUS_CH_MSK(channel), DMA_INT_STATUS(dev)); |
| k_spin_unlock(&data->lock, key); |
| |
| /* Set transfer size */ |
| sys_write32(tfr_size, DMA_CH_TRANSIZE(dev, channel)); |
| |
| /* Update the status of channel */ |
| data->chan[channel].status.dir = cfg->channel_direction; |
| data->chan[channel].status.pending_length = cfg->source_data_size; |
| |
| /* Configure a callback appropriately depending on whether the |
| * interrupt is requested at the end of transaction completion or |
| * at the end of each block. |
| */ |
| data->chan[channel].blkcallback = cfg->dma_callback; |
| data->chan[channel].blkuser_data = cfg->user_data; |
| |
| sys_write32(ch_ctrl, DMA_CH_CTRL(dev, channel)); |
| |
| /* Set source and destination address */ |
| sys_write32(cfg_blocks->source_address, |
| DMA_CH_SRC_ADDR_L(dev, channel)); |
| sys_write32(0, DMA_CH_SRC_ADDR_H(dev, channel)); |
| sys_write32(cfg_blocks->dest_address, |
| DMA_CH_DST_ADDR_L(dev, channel)); |
| sys_write32(0, DMA_CH_DST_ADDR_H(dev, channel)); |
| |
| if (cfg->dest_chaining_en == 1 && cfg_blocks->next_block) { |
| uint32_t current_block_idx = 0; |
| |
| sys_write32((uint32_t)((long)&dma_chain[channel][current_block_idx]), |
| DMA_CH_LL_PTR_L(dev, channel)); |
| sys_write32(0, DMA_CH_LL_PTR_H(dev, channel)); |
| |
| for (cfg_blocks = cfg_blocks->next_block; cfg_blocks != NULL; |
| cfg_blocks = cfg_blocks->next_block) { |
| |
| ch_ctrl &= ~(DMA_CH_CTRL_SRCADDRCTRL_MASK | |
| DMA_CH_CTRL_DSTADDRCTRL_MASK); |
| |
| switch (cfg_blocks->source_addr_adj) { |
| case DMA_ADDR_ADJ_INCREMENT: |
| ch_ctrl |= DMA_CH_CTRL_SRCADDR_INC; |
| break; |
| case DMA_ADDR_ADJ_DECREMENT: |
| ch_ctrl |= DMA_CH_CTRL_SRCADDR_DEC; |
| break; |
| case DMA_ADDR_ADJ_NO_CHANGE: |
| ch_ctrl |= DMA_CH_CTRL_SRCADDR_FIX; |
| break; |
| default: |
| ret = -EINVAL; |
| goto end; |
| } |
| |
| switch (cfg_blocks->dest_addr_adj) { |
| case DMA_ADDR_ADJ_INCREMENT: |
| ch_ctrl |= DMA_CH_CTRL_DSTADDR_INC; |
| break; |
| case DMA_ADDR_ADJ_DECREMENT: |
| ch_ctrl |= DMA_CH_CTRL_DSTADDR_DEC; |
| break; |
| case DMA_ADDR_ADJ_NO_CHANGE: |
| ch_ctrl |= DMA_CH_CTRL_DSTADDR_FIX; |
| break; |
| default: |
| ret = -EINVAL; |
| goto end; |
| } |
| dma_chain[channel][current_block_idx].ctrl = ch_ctrl; |
| dma_chain[channel][current_block_idx].transize = |
| cfg_blocks->block_size/cfg->source_data_size; |
| |
| dma_chain[channel][current_block_idx].srcaddrl = |
| (uint32_t)cfg_blocks->source_address; |
| dma_chain[channel][current_block_idx].srcaddrh = 0x0; |
| |
| dma_chain[channel][current_block_idx].dstaddrl = |
| (uint32_t)((long)cfg_blocks->dest_address); |
| dma_chain[channel][current_block_idx].dstaddrh = 0x0; |
| |
| if (cfg_blocks->next_block) { |
| dma_chain[channel][current_block_idx].llpointerl = |
| (uint32_t)&dma_chain[channel][current_block_idx + 1]; |
| dma_chain[channel][current_block_idx].llpointerh = 0x0; |
| |
| current_block_idx = current_block_idx + 1; |
| |
| } else { |
| dma_chain[channel][current_block_idx].llpointerl = 0x0; |
| dma_chain[channel][current_block_idx].llpointerh = 0x0; |
| dma_chain[channel][current_block_idx].next_block = NULL; |
| } |
| } |
| } else { |
| /* Single transfer is supported, but Chain transfer is still |
| * not supported. Therefore, set LLPointer to zero |
| */ |
| sys_write32(0, DMA_CH_LL_PTR_L(dev, channel)); |
| sys_write32(0, DMA_CH_LL_PTR_H(dev, channel)); |
| } |
| |
| end: |
| return ret; |
| } |
| |
| static int dma_atcdmac300_reload(const struct device *dev, uint32_t channel, |
| uint32_t src, uint32_t dst, size_t size) |
| { |
| uint32_t src_width; |
| |
| if (channel >= ATCDMAC100_MAX_CHAN) { |
| return -EINVAL; |
| } |
| |
| /* Set source and destination address */ |
| sys_write32(src, DMA_CH_SRC_ADDR_L(dev, channel)); |
| sys_write32(0, DMA_CH_SRC_ADDR_H(dev, channel)); |
| sys_write32(dst, DMA_CH_DST_ADDR_L(dev, channel)); |
| sys_write32(0, DMA_CH_DST_ADDR_H(dev, channel)); |
| |
| src_width = FIELD_GET(DMA_CH_CTRL_SWIDTH_MASK, sys_read32(DMA_CH_CTRL(dev, channel))); |
| src_width = BIT(src_width); |
| |
| /* Set transfer size */ |
| sys_write32(size/src_width, DMA_CH_TRANSIZE(dev, channel)); |
| |
| return 0; |
| } |
| |
| static int dma_atcdmac300_transfer_start(const struct device *dev, |
| uint32_t channel) |
| { |
| struct dma_atcdmac300_data *const data = dev->data; |
| |
| if (channel >= ATCDMAC100_MAX_CHAN) { |
| return -EINVAL; |
| } |
| |
| sys_write32(sys_read32(DMA_CH_CTRL(dev, channel)) | DMA_CH_CTRL_ENABLE, |
| DMA_CH_CTRL(dev, channel)); |
| |
| data->chan[channel].status.busy = true; |
| |
| return 0; |
| } |
| |
| static int dma_atcdmac300_transfer_stop(const struct device *dev, |
| uint32_t channel) |
| { |
| struct dma_atcdmac300_data *const data = dev->data; |
| k_spinlock_key_t key; |
| |
| if (channel >= ATCDMAC100_MAX_CHAN) { |
| return -EINVAL; |
| } |
| |
| key = k_spin_lock(&data->lock); |
| |
| sys_write32(BIT(channel), DMA_ABORT(dev)); |
| sys_write32(0, DMA_CH_CTRL(dev, channel)); |
| sys_write32(FIELD_GET(DMA_INT_STATUS_ABORT_MASK, (channel)), DMA_INT_STATUS(dev)); |
| data->chan[channel].status.busy = false; |
| |
| k_spin_unlock(&data->lock, key); |
| |
| return 0; |
| } |
| |
| static int dma_atcdmac300_init(const struct device *dev) |
| { |
| const struct dma_atcdmac300_cfg *const config = (struct dma_atcdmac300_cfg *)dev->config; |
| uint32_t ch_num; |
| |
| /* Disable all channels and Channel interrupts */ |
| for (ch_num = 0; ch_num < ATCDMAC100_MAX_CHAN; ch_num++) { |
| sys_write32(0, DMA_CH_CTRL(dev, ch_num)); |
| } |
| |
| sys_write32(0xFFFFFF, DMA_INT_STATUS(dev)); |
| |
| /* Configure interrupts */ |
| config->irq_config(); |
| |
| irq_enable(config->irq_num); |
| |
| return 0; |
| } |
| |
| static int dma_atcdmac300_get_status(const struct device *dev, |
| uint32_t channel, |
| struct dma_status *stat) |
| { |
| struct dma_atcdmac300_data *const data = dev->data; |
| |
| stat->busy = data->chan[channel].status.busy; |
| stat->dir = data->chan[channel].status.dir; |
| stat->pending_length = data->chan[channel].status.pending_length; |
| |
| return 0; |
| } |
| |
| static const struct dma_driver_api dma_atcdmac300_api = { |
| .config = dma_atcdmac300_config, |
| .reload = dma_atcdmac300_reload, |
| .start = dma_atcdmac300_transfer_start, |
| .stop = dma_atcdmac300_transfer_stop, |
| .get_status = dma_atcdmac300_get_status |
| }; |
| |
| #define ATCDMAC300_INIT(n) \ |
| \ |
| static void dma_atcdmac300_irq_config_##n(void); \ |
| \ |
| static const struct dma_atcdmac300_cfg dma_config_##n = { \ |
| .irq_config = dma_atcdmac300_irq_config_##n, \ |
| .base = DT_INST_REG_ADDR(n), \ |
| .irq_num = DT_INST_IRQN(n), \ |
| }; \ |
| \ |
| static struct dma_atcdmac300_data dma_data_##n; \ |
| \ |
| DEVICE_DT_INST_DEFINE(0, \ |
| dma_atcdmac300_init, \ |
| NULL, \ |
| &dma_data_##n, \ |
| &dma_config_##n, \ |
| POST_KERNEL, \ |
| CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ |
| &dma_atcdmac300_api); \ |
| \ |
| static void dma_atcdmac300_irq_config_##n(void) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQN(n), \ |
| 1, \ |
| dma_atcdmac300_isr, \ |
| DEVICE_DT_INST_GET(n), \ |
| 0); \ |
| } |
| |
| |
| DT_INST_FOREACH_STATUS_OKAY(ATCDMAC300_INIT) |