/* * Copyright (c) 2023 Andes Technology Corporation. * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #define DT_DRV_COMPAT andestech_atcdmac300 #define LOG_LEVEL CONFIG_DMA_LOG_LEVEL #include 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)