| /* |
| * Copyright (c) 2023 Microchip Technology Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT microchip_xec_dmac |
| |
| #include <soc.h> |
| #include <zephyr/device.h> |
| #include <zephyr/devicetree.h> |
| #include <zephyr/drivers/clock_control/mchp_xec_clock_control.h> |
| #include <zephyr/drivers/dma.h> |
| #include <zephyr/drivers/interrupt_controller/intc_mchp_xec_ecia.h> |
| #include <zephyr/dt-bindings/interrupt-controller/mchp-xec-ecia.h> |
| #include <zephyr/pm/device.h> |
| #include <zephyr/sys/util_macro.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(dma_mchp_xec, CONFIG_DMA_LOG_LEVEL); |
| |
| #define XEC_DMA_DEBUG 1 |
| #ifdef XEC_DMA_DEBUG |
| #include <string.h> |
| #endif |
| |
| #define XEC_DMA_ABORT_WAIT_LOOPS 32 |
| |
| #define XEC_DMA_MAIN_REGS_SIZE 0x40 |
| #define XEC_DMA_CHAN_REGS_SIZE 0x40 |
| |
| #define XEC_DMA_CHAN_REGS_ADDR(base, channel) \ |
| (((uintptr_t)(base) + (XEC_DMA_MAIN_REGS_SIZE)) + \ |
| ((uintptr_t)(channel) * XEC_DMA_CHAN_REGS_SIZE)) |
| |
| /* main control */ |
| #define XEC_DMA_MAIN_CTRL_REG_MSK 0x3u |
| #define XEC_DMA_MAIN_CTRL_EN_POS 0 |
| #define XEC_DMA_MAIN_CTRL_SRST_POS 1 |
| |
| /* channel activate register */ |
| #define XEC_DMA_CHAN_ACTV_EN_POS 0 |
| /* channel control register */ |
| #define XEC_DMA_CHAN_CTRL_REG_MSK 0x037fff27u |
| #define XEC_DMA_CHAN_CTRL_HWFL_RUN_POS 0 |
| #define XEC_DMA_CHAN_CTRL_REQ_POS 1 |
| #define XEC_DMA_CHAN_CTRL_DONE_POS 2 |
| #define XEC_DMA_CHAN_CTRL_BUSY_POS 5 |
| #define XEC_DMA_CHAN_CTRL_M2D_POS 8 |
| #define XEC_DMA_CHAN_CTRL_HWFL_DEV_POS 9 |
| #define XEC_DMA_CHAN_CTRL_HWFL_DEV_MSK 0xfe00u |
| #define XEC_DMA_CHAN_CTRL_HWFL_DEV_MSK0 0x7fu |
| #define XEC_DMA_CHAN_CTRL_INCR_MEM_POS 16 |
| #define XEC_DMA_CHAN_CTRL_INCR_DEV_POS 17 |
| #define XEC_DMA_CHAN_CTRL_LOCK_ARB_POS 18 |
| #define XEC_DMA_CHAN_CTRL_DIS_HWFL_POS 19 |
| #define XEC_DMA_CHAN_CTRL_XFR_UNIT_POS 20 |
| #define XEC_DMA_CHAN_CTRL_XFR_UNIT_MSK 0x700000u |
| #define XEC_DMA_CHAN_CTRL_XFR_UNIT_MSK0 0x7u |
| #define XEC_DMA_CHAN_CTRL_SWFL_GO_POS 24 |
| #define XEC_DMA_CHAN_CTRL_ABORT_POS 25 |
| /* channel interrupt status and enable registers */ |
| #define XEC_DMA_CHAN_IES_REG_MSK 0xfu |
| #define XEC_DMA_CHAN_IES_BERR_POS 0 |
| #define XEC_DMA_CHAN_IES_OVFL_ERR_POS 1 |
| #define XEC_DMA_CHAN_IES_DONE_POS 2 |
| #define XEC_DMA_CHAN_IES_DEV_TERM_POS 3 |
| /* channel fsm (RO) */ |
| #define XEC_DMA_CHAN_FSM_REG_MSK 0xffffu |
| #define XEC_DMA_CHAN_FSM_ARB_STATE_POS 0 |
| #define XEC_DMA_CHAN_FSM_ARB_STATE_MSK 0xffu |
| #define XEC_DMA_CHAN_FSM_CTRL_STATE_POS 8 |
| #define XEC_DMA_CHAN_FSM_CTRL_STATE_MSK 0xff00u |
| #define XEC_DMA_CHAN_FSM_CTRL_STATE_IDLE 0 |
| #define XEC_DMA_CHAN_FSM_CTRL_STATE_ARB_REQ 0x100u |
| #define XEC_DMA_CHAN_FSM_CTRL_STATE_RD_ACT 0x200u |
| #define XEC_DMA_CHAN_FSM_CTRL_STATE_WR_ACT 0x300u |
| #define XEC_DMA_CHAN_FSM_CTRL_STATE_WAIT_DONE 0x400u |
| |
| #define XEC_DMA_HWFL_DEV_VAL(d) \ |
| (((uint32_t)(d) & XEC_DMA_CHAN_CTRL_HWFL_DEV_MSK0) << XEC_DMA_CHAN_CTRL_HWFL_DEV_POS) |
| |
| #define XEC_DMA_CHAN_CTRL_UNIT_VAL(u) \ |
| (((uint32_t)(u) & XEC_DMA_CHAN_CTRL_XFR_UNIT_MSK0) << XEC_DMA_CHAN_CTRL_XFR_UNIT_POS) |
| |
| struct dma_xec_chan_regs { |
| volatile uint32_t actv; |
| volatile uint32_t mem_addr; |
| volatile uint32_t mem_addr_end; |
| volatile uint32_t dev_addr; |
| volatile uint32_t control; |
| volatile uint32_t istatus; |
| volatile uint32_t ienable; |
| volatile uint32_t fsm; |
| uint32_t rsvd_20_3f[8]; |
| }; |
| |
| struct dma_xec_regs { |
| volatile uint32_t mctrl; |
| volatile uint32_t mpkt; |
| uint32_t rsvd_08_3f[14]; |
| }; |
| |
| struct dma_xec_irq_info { |
| uint8_t gid; /* GIRQ id [8, 26] */ |
| uint8_t gpos; /* bit position in GIRQ [0, 31] */ |
| uint8_t anid; /* aggregated external NVIC input */ |
| uint8_t dnid; /* direct NVIC input */ |
| }; |
| |
| struct dma_xec_config { |
| struct dma_xec_regs *regs; |
| uint8_t dma_channels; |
| uint8_t dma_requests; |
| uint8_t pcr_idx; |
| uint8_t pcr_pos; |
| int irq_info_size; |
| const struct dma_xec_irq_info *irq_info_list; |
| void (*irq_connect)(void); |
| }; |
| |
| struct dma_xec_channel { |
| uint32_t control; |
| uint32_t mstart; |
| uint32_t mend; |
| uint32_t dstart; |
| uint32_t isr_hw_status; |
| uint32_t block_count; |
| uint8_t unit_size; |
| uint8_t dir; |
| uint8_t flags; |
| uint8_t rsvd[1]; |
| struct dma_block_config *head; |
| struct dma_block_config *curr; |
| dma_callback_t cb; |
| void *user_data; |
| uint32_t total_req_xfr_len; |
| uint32_t total_curr_xfr_len; |
| }; |
| |
| #define DMA_XEC_CHAN_FLAGS_CB_EOB_POS 0 |
| #define DMA_XEC_CHAN_FLAGS_CB_ERR_DIS_POS 1 |
| |
| struct dma_xec_data { |
| struct dma_context ctx; |
| struct dma_xec_channel *channels; |
| }; |
| |
| #ifdef XEC_DMA_DEBUG |
| static void xec_dma_debug_clean(void); |
| #endif |
| |
| static inline struct dma_xec_chan_regs *xec_chan_regs(struct dma_xec_regs *regs, uint32_t chan) |
| { |
| uint8_t *pregs = (uint8_t *)regs + XEC_DMA_MAIN_REGS_SIZE; |
| |
| pregs += (chan * (XEC_DMA_CHAN_REGS_SIZE)); |
| |
| return (struct dma_xec_chan_regs *)pregs; |
| } |
| |
| static inline |
| struct dma_xec_irq_info const *xec_chan_irq_info(const struct dma_xec_config *devcfg, |
| uint32_t channel) |
| { |
| return &devcfg->irq_info_list[channel]; |
| } |
| |
| static int is_dma_data_size_valid(uint32_t datasz) |
| { |
| if ((datasz == 1U) || (datasz == 2U) || (datasz == 4U)) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* HW requires if unit size is 2 or 4 bytes the source/destination addresses |
| * to be aligned >= 2 or 4 bytes. |
| */ |
| static int is_data_aligned(uint32_t src, uint32_t dest, uint32_t unitsz) |
| { |
| if (unitsz == 1) { |
| return 1; |
| } |
| |
| if ((src | dest) & (unitsz - 1U)) { |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static void xec_dma_chan_clr(struct dma_xec_chan_regs * const chregs, |
| const struct dma_xec_irq_info *info) |
| { |
| chregs->actv = 0; |
| chregs->control = 0; |
| chregs->mem_addr = 0; |
| chregs->mem_addr_end = 0; |
| chregs->dev_addr = 0; |
| chregs->control = 0; |
| chregs->ienable = 0; |
| chregs->istatus = 0xffu; |
| mchp_xec_ecia_girq_src_clr(info->gid, info->gpos); |
| } |
| |
| static int is_dma_config_valid(const struct device *dev, struct dma_config *config) |
| { |
| const struct dma_xec_config * const devcfg = dev->config; |
| |
| if (config->dma_slot >= (uint32_t)devcfg->dma_requests) { |
| LOG_ERR("XEC DMA config dma slot > exceeds number of request lines"); |
| return 0; |
| } |
| |
| if (config->source_data_size != config->dest_data_size) { |
| LOG_ERR("XEC DMA requires source and dest data size identical"); |
| return 0; |
| } |
| |
| if (!((config->channel_direction == MEMORY_TO_MEMORY) || |
| (config->channel_direction == MEMORY_TO_PERIPHERAL) || |
| (config->channel_direction == PERIPHERAL_TO_MEMORY))) { |
| LOG_ERR("XEC DMA only support M2M, M2P, P2M"); |
| return 0; |
| } |
| |
| if (!is_dma_data_size_valid(config->source_data_size)) { |
| LOG_ERR("XEC DMA requires xfr unit size of 1, 2 or 4 bytes"); |
| return 0; |
| } |
| |
| if (config->block_count != 1) { |
| LOG_ERR("XEC DMA block count != 1"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int check_blocks(struct dma_xec_channel *chdata, struct dma_block_config *block, |
| uint32_t block_count, uint32_t unit_size) |
| { |
| if (!block || !chdata) { |
| LOG_ERR("bad pointer"); |
| return -EINVAL; |
| } |
| |
| chdata->total_req_xfr_len = 0; |
| |
| for (uint32_t i = 0; i < block_count; i++) { |
| if ((block->source_addr_adj == DMA_ADDR_ADJ_DECREMENT) || |
| (block->dest_addr_adj == DMA_ADDR_ADJ_DECREMENT)) { |
| LOG_ERR("XEC DMA HW does not support address decrement. Block index %u", i); |
| return -EINVAL; |
| } |
| |
| if (!is_data_aligned(block->source_address, block->dest_address, unit_size)) { |
| LOG_ERR("XEC DMA block at index %u violates source/dest unit size", i); |
| return -EINVAL; |
| } |
| |
| chdata->total_req_xfr_len += block->block_size; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * struct dma_config flags |
| * dma_slot - peripheral source/target ID. Not used for Mem2Mem |
| * channel_direction - HW supports Mem2Mem, Mem2Periph, and Periph2Mem |
| * complete_callback_en - if true invoke callback on completion (no error) |
| * error_callback_dis - if true disable callback on error |
| * source_handshake - 0=HW, 1=SW |
| * dest_handshake - 0=HW, 1=SW |
| * channel_priority - 4-bit field. HW implements round-robin only. |
| * source_chaining_en - Chaining channel together |
| * dest_chaining_en - HW does not support channel chaining. |
| * linked_channel - HW does not support |
| * cyclic - HW does not support cyclic buffer. Would have to emulate with SW. |
| * source_data_size - unit size of source data. HW supports 1, 2, or 4 bytes |
| * dest_data_size - unit size of dest data. HW requires same as source_data_size |
| * source_burst_length - HW does not support |
| * dest_burst_length - HW does not support |
| * block_count - |
| * user_data - |
| * dma_callback - |
| * head_block - pointer to struct dma_block_config |
| * |
| * struct dma_block_config |
| * source_address - |
| * source_gather_interval - N/A |
| * dest_address - |
| * dest_scatter_interval - N/A |
| * dest_scatter_count - N/A |
| * source_gather_count - N/A |
| * block_size |
| * config - flags |
| * source_gather_en - N/A |
| * dest_scatter_en - N/A |
| * source_addr_adj - 0(increment), 1(decrement), 2(no change) |
| * dest_addr_adj - 0(increment), 1(decrement), 2(no change) |
| * source_reload_en - reload source address at end of block |
| * dest_reload_en - reload destination address at end of block |
| * fifo_mode_control - N/A |
| * flow_control_mode - 0(source req service on data available) HW does this |
| * 1(source req postposed until dest req happens) N/A |
| * |
| * |
| * DMA channel implements memory start address, memory end address, |
| * and peripheral address registers. No peripheral end address. |
| * Transfer ends when memory start address increments and reaches |
| * memory end address. |
| * |
| * Memory to Memory: copy from source_address to dest_address |
| * chan direction = Mem2Dev. chan.control b[8]=1 |
| * chan mem_addr = source_address |
| * chan mem_addr_end = source_address + block_size |
| * chan dev_addr = dest_address |
| * |
| * Memory to Peripheral: copy from source_address(memory) to dest_address(peripheral) |
| * chan direction = Mem2Dev. chan.control b[8]=1 |
| * chan mem_addr = source_address |
| * chan mem_addr_end = chan mem_addr + block_size |
| * chan dev_addr = dest_address |
| * |
| * Peripheral to Memory: |
| * chan direction = Dev2Mem. chan.contronl b[8]=1 |
| * chan mem_addr = dest_address |
| * chan mem_addr_end = chan mem_addr + block_size |
| * chan dev_addr = source_address |
| */ |
| static int dma_xec_configure(const struct device *dev, uint32_t channel, |
| struct dma_config *config) |
| { |
| const struct dma_xec_config * const devcfg = dev->config; |
| struct dma_xec_regs * const regs = devcfg->regs; |
| struct dma_xec_data * const data = dev->data; |
| uint32_t ctrl, mstart, mend, dstart, unit_size; |
| int ret; |
| |
| if (!config || (channel >= (uint32_t)devcfg->dma_channels)) { |
| return -EINVAL; |
| } |
| |
| #ifdef XEC_DMA_DEBUG |
| xec_dma_debug_clean(); |
| #endif |
| |
| const struct dma_xec_irq_info *info = xec_chan_irq_info(devcfg, channel); |
| struct dma_xec_chan_regs * const chregs = xec_chan_regs(regs, channel); |
| struct dma_xec_channel *chdata = &data->channels[channel]; |
| |
| chdata->total_req_xfr_len = 0; |
| chdata->total_curr_xfr_len = 0; |
| |
| xec_dma_chan_clr(chregs, info); |
| |
| if (!is_dma_config_valid(dev, config)) { |
| return -EINVAL; |
| } |
| |
| struct dma_block_config *block = config->head_block; |
| |
| ret = check_blocks(chdata, block, config->block_count, config->source_data_size); |
| if (ret) { |
| return ret; |
| } |
| |
| unit_size = config->source_data_size; |
| chdata->unit_size = unit_size; |
| chdata->head = block; |
| chdata->curr = block; |
| chdata->block_count = config->block_count; |
| chdata->dir = config->channel_direction; |
| |
| chdata->flags = 0; |
| chdata->cb = config->dma_callback; |
| chdata->user_data = config->user_data; |
| |
| /* invoke callback on completion of each block instead of all blocks ? */ |
| if (config->complete_callback_en) { |
| chdata->flags |= BIT(DMA_XEC_CHAN_FLAGS_CB_EOB_POS); |
| } |
| if (config->error_callback_dis) { /* disable callback on errors ? */ |
| chdata->flags |= BIT(DMA_XEC_CHAN_FLAGS_CB_ERR_DIS_POS); |
| } |
| |
| /* Use the control member of struct dma_xec_channel to |
| * store control register value containing fields invariant |
| * for all buffers: HW flow control device, direction, unit size, ... |
| * derived from struct dma_config |
| */ |
| ctrl = XEC_DMA_CHAN_CTRL_UNIT_VAL(unit_size); |
| if (config->channel_direction == MEMORY_TO_MEMORY) { |
| ctrl |= BIT(XEC_DMA_CHAN_CTRL_DIS_HWFL_POS); |
| } else { |
| ctrl |= XEC_DMA_HWFL_DEV_VAL(config->dma_slot); |
| } |
| |
| if (config->channel_direction == PERIPHERAL_TO_MEMORY) { |
| mstart = block->dest_address; |
| mend = block->dest_address + block->block_size; |
| dstart = block->source_address; |
| if (block->source_addr_adj == DMA_ADDR_ADJ_INCREMENT) { |
| ctrl |= BIT(XEC_DMA_CHAN_CTRL_INCR_DEV_POS); |
| } |
| if (block->dest_addr_adj == DMA_ADDR_ADJ_INCREMENT) { |
| ctrl |= BIT(XEC_DMA_CHAN_CTRL_INCR_MEM_POS); |
| } |
| } else { |
| mstart = block->source_address; |
| mend = block->source_address + block->block_size; |
| dstart = block->dest_address; |
| ctrl |= BIT(XEC_DMA_CHAN_CTRL_M2D_POS); |
| if (block->source_addr_adj == DMA_ADDR_ADJ_INCREMENT) { |
| ctrl |= BIT(XEC_DMA_CHAN_CTRL_INCR_MEM_POS); |
| } |
| if (block->dest_addr_adj == DMA_ADDR_ADJ_INCREMENT) { |
| ctrl |= BIT(XEC_DMA_CHAN_CTRL_INCR_DEV_POS); |
| } |
| } |
| |
| chdata->control = ctrl; |
| chdata->mstart = mstart; |
| chdata->mend = mend; |
| chdata->dstart = dstart; |
| |
| chregs->actv &= ~BIT(XEC_DMA_CHAN_ACTV_EN_POS); |
| chregs->mem_addr = mstart; |
| chregs->mem_addr_end = mend; |
| chregs->dev_addr = dstart; |
| |
| chregs->control = ctrl; |
| chregs->ienable = BIT(XEC_DMA_CHAN_IES_BERR_POS) | BIT(XEC_DMA_CHAN_IES_DONE_POS); |
| chregs->actv |= BIT(XEC_DMA_CHAN_ACTV_EN_POS); |
| |
| return 0; |
| } |
| |
| /* Update previously configured DMA channel with new data source address, |
| * data destination address, and size in bytes. |
| * src = source address for DMA transfer |
| * dst = destination address for DMA transfer |
| * size = size of DMA transfer. Assume this is in bytes. |
| * We assume the caller will pass src, dst, and size that matches |
| * the unit size from the previous configure call. |
| */ |
| static int dma_xec_reload(const struct device *dev, uint32_t channel, |
| uint32_t src, uint32_t dst, size_t size) |
| { |
| const struct dma_xec_config * const devcfg = dev->config; |
| struct dma_xec_data * const data = dev->data; |
| struct dma_xec_regs * const regs = devcfg->regs; |
| uint32_t ctrl; |
| |
| if (channel >= (uint32_t)devcfg->dma_channels) { |
| return -EINVAL; |
| } |
| |
| struct dma_xec_channel *chdata = &data->channels[channel]; |
| struct dma_xec_chan_regs *chregs = xec_chan_regs(regs, channel); |
| |
| if (chregs->control & BIT(XEC_DMA_CHAN_CTRL_BUSY_POS)) { |
| return -EBUSY; |
| } |
| |
| ctrl = chregs->control & ~(BIT(XEC_DMA_CHAN_CTRL_HWFL_RUN_POS) |
| | BIT(XEC_DMA_CHAN_CTRL_SWFL_GO_POS)); |
| chregs->ienable = 0; |
| chregs->control = 0; |
| chregs->istatus = 0xffu; |
| |
| if (ctrl & BIT(XEC_DMA_CHAN_CTRL_M2D_POS)) { /* Memory to Device */ |
| chdata->mstart = src; |
| chdata->dstart = dst; |
| } else { |
| chdata->mstart = dst; |
| chdata->dstart = src; |
| } |
| |
| chdata->mend = chdata->mstart + size; |
| chdata->total_req_xfr_len = size; |
| chdata->total_curr_xfr_len = 0; |
| |
| chregs->mem_addr = chdata->mstart; |
| chregs->mem_addr_end = chdata->mend; |
| chregs->dev_addr = chdata->dstart; |
| chregs->control = ctrl; |
| |
| return 0; |
| } |
| |
| static int dma_xec_start(const struct device *dev, uint32_t channel) |
| { |
| const struct dma_xec_config * const devcfg = dev->config; |
| struct dma_xec_regs * const regs = devcfg->regs; |
| uint32_t chan_ctrl = 0U; |
| |
| if (channel >= (uint32_t)devcfg->dma_channels) { |
| return -EINVAL; |
| } |
| |
| struct dma_xec_chan_regs *chregs = xec_chan_regs(regs, channel); |
| |
| if (chregs->control & BIT(XEC_DMA_CHAN_CTRL_BUSY_POS)) { |
| return -EBUSY; |
| } |
| |
| chregs->ienable = 0u; |
| chregs->istatus = 0xffu; |
| chan_ctrl = chregs->control; |
| |
| if (chan_ctrl & BIT(XEC_DMA_CHAN_CTRL_DIS_HWFL_POS)) { |
| chan_ctrl |= BIT(XEC_DMA_CHAN_CTRL_SWFL_GO_POS); |
| } else { |
| chan_ctrl |= BIT(XEC_DMA_CHAN_CTRL_HWFL_RUN_POS); |
| } |
| |
| chregs->ienable = BIT(XEC_DMA_CHAN_IES_BERR_POS) | BIT(XEC_DMA_CHAN_IES_DONE_POS); |
| chregs->control = chan_ctrl; |
| chregs->actv |= BIT(XEC_DMA_CHAN_ACTV_EN_POS); |
| |
| return 0; |
| } |
| |
| static int dma_xec_stop(const struct device *dev, uint32_t channel) |
| { |
| const struct dma_xec_config * const devcfg = dev->config; |
| struct dma_xec_regs * const regs = devcfg->regs; |
| int wait_loops = XEC_DMA_ABORT_WAIT_LOOPS; |
| |
| if (channel >= (uint32_t)devcfg->dma_channels) { |
| return -EINVAL; |
| } |
| |
| struct dma_xec_chan_regs *chregs = xec_chan_regs(regs, channel); |
| |
| chregs->ienable = 0; |
| |
| if (chregs->control & BIT(XEC_DMA_CHAN_CTRL_BUSY_POS)) { |
| chregs->ienable = 0; |
| chregs->control |= BIT(XEC_DMA_CHAN_CTRL_ABORT_POS); |
| /* HW stops on next unit boundary (1, 2, or 4 bytes) */ |
| |
| do { |
| if (!(chregs->control & BIT(XEC_DMA_CHAN_CTRL_BUSY_POS))) { |
| break; |
| } |
| } while (wait_loops--); |
| } |
| |
| chregs->mem_addr = chregs->mem_addr_end; |
| chregs->fsm = 0; /* delay */ |
| chregs->control = 0; |
| chregs->istatus = 0xffu; |
| chregs->actv = 0; |
| |
| return 0; |
| } |
| |
| /* Get DMA transfer status. |
| * HW supports: MEMORY_TO_MEMORY, MEMORY_TO_PERIPHERAL, or |
| * PERIPHERAL_TO_MEMORY |
| * current DMA runtime status structure |
| * |
| * busy - is current DMA transfer busy or idle |
| * dir - DMA transfer direction |
| * pending_length - data length pending to be transferred in bytes |
| * or platform dependent. |
| * We don't implement a circular buffer |
| * free - free buffer space |
| * write_position - write position in a circular dma buffer |
| * read_position - read position in a circular dma buffer |
| * |
| */ |
| static int dma_xec_get_status(const struct device *dev, uint32_t channel, |
| struct dma_status *status) |
| { |
| const struct dma_xec_config * const devcfg = dev->config; |
| struct dma_xec_data * const data = dev->data; |
| struct dma_xec_regs * const regs = devcfg->regs; |
| uint32_t chan_ctrl = 0U; |
| |
| if ((channel >= (uint32_t)devcfg->dma_channels) || (!status)) { |
| LOG_ERR("unsupported channel"); |
| return -EINVAL; |
| } |
| |
| struct dma_xec_channel *chan_data = &data->channels[channel]; |
| struct dma_xec_chan_regs *chregs = xec_chan_regs(regs, channel); |
| |
| chan_ctrl = chregs->control; |
| |
| if (chan_ctrl & BIT(XEC_DMA_CHAN_CTRL_BUSY_POS)) { |
| status->busy = true; |
| /* number of bytes remaining in channel */ |
| status->pending_length = chan_data->total_req_xfr_len - |
| (chregs->mem_addr_end - chregs->mem_addr); |
| } else { |
| status->pending_length = chan_data->total_req_xfr_len - |
| chan_data->total_curr_xfr_len; |
| status->busy = false; |
| } |
| |
| if (chan_ctrl & BIT(XEC_DMA_CHAN_CTRL_DIS_HWFL_POS)) { |
| status->dir = MEMORY_TO_MEMORY; |
| } else if (chan_ctrl & BIT(XEC_DMA_CHAN_CTRL_M2D_POS)) { |
| status->dir = MEMORY_TO_PERIPHERAL; |
| } else { |
| status->dir = PERIPHERAL_TO_MEMORY; |
| } |
| |
| status->total_copied = chan_data->total_curr_xfr_len; |
| |
| return 0; |
| } |
| |
| int xec_dma_get_attribute(const struct device *dev, uint32_t type, uint32_t *value) |
| { |
| if ((type == DMA_ATTR_MAX_BLOCK_COUNT) && value) { |
| *value = 1; |
| return 0; |
| } |
| |
| return -EINVAL; |
| } |
| |
| /* returns true if filter matched otherwise returns false */ |
| static bool dma_xec_chan_filter(const struct device *dev, int ch, void *filter_param) |
| { |
| const struct dma_xec_config * const devcfg = dev->config; |
| uint32_t filter = 0u; |
| |
| if (!filter_param && devcfg->dma_channels) { |
| filter = GENMASK(devcfg->dma_channels-1u, 0); |
| } else { |
| filter = *((uint32_t *)filter_param); |
| } |
| |
| return (filter & BIT(ch)); |
| } |
| |
| /* API - HW does not stupport suspend/resume */ |
| static const struct dma_driver_api dma_xec_api = { |
| .config = dma_xec_configure, |
| .reload = dma_xec_reload, |
| .start = dma_xec_start, |
| .stop = dma_xec_stop, |
| .get_status = dma_xec_get_status, |
| .chan_filter = dma_xec_chan_filter, |
| .get_attribute = xec_dma_get_attribute, |
| }; |
| |
| #ifdef CONFIG_PM_DEVICE |
| /* TODO - DMA block has one PCR SLP_EN and one CLK_REQ. |
| * If any channel is running the block's CLK_REQ is asserted. |
| * CLK_REQ will not clear until all channels are done or disabled. |
| * Clearing the DMA Main activate will kill DMA transactions resulting |
| * possible data corruption and HW flow control device malfunctions. |
| */ |
| static int dmac_xec_pm_action(const struct device *dev, |
| enum pm_device_action action) |
| { |
| const struct dma_xec_config * const devcfg = dev->config; |
| struct dma_xec_regs * const regs = devcfg->regs; |
| int ret = 0; |
| |
| switch (action) { |
| case PM_DEVICE_ACTION_RESUME: |
| regs->mctrl |= BIT(XEC_DMA_MAIN_CTRL_EN_POS); |
| break; |
| |
| case PM_DEVICE_ACTION_SUSPEND: |
| /* regs->mctrl &= ~BIT(XEC_DMA_MAIN_CTRL_EN_POS); */ |
| break; |
| |
| default: |
| ret = -ENOTSUP; |
| } |
| |
| return ret; |
| } |
| #endif /* CONFIG_PM_DEVICE */ |
| |
| /* DMA channel interrupt handler called by ISR. |
| * Callback flags in struct dma_config |
| * completion_callback_en |
| * 0 = invoke at completion of all blocks |
| * 1 = invoke at completin of each block |
| * error_callback_dis |
| * 0 = invoke on all errors |
| * 1 = disabled, do not invoke on errors |
| */ |
| /* DEBUG */ |
| #ifdef XEC_DMA_DEBUG |
| static volatile uint8_t channel_isr_idx[16]; |
| static volatile uint8_t channel_isr_sts[16][16]; |
| static volatile uint32_t channel_isr_ctrl[16][16]; |
| |
| static void xec_dma_debug_clean(void) |
| { |
| memset((void *)channel_isr_idx, 0, sizeof(channel_isr_idx)); |
| memset((void *)channel_isr_sts, 0, sizeof(channel_isr_sts)); |
| memset((void *)channel_isr_ctrl, 0, sizeof(channel_isr_ctrl)); |
| } |
| #endif |
| |
| static void dma_xec_irq_handler(const struct device *dev, uint32_t channel) |
| { |
| const struct dma_xec_config * const devcfg = dev->config; |
| const struct dma_xec_irq_info *info = devcfg->irq_info_list; |
| struct dma_xec_data * const data = dev->data; |
| struct dma_xec_channel *chan_data = &data->channels[channel]; |
| struct dma_xec_chan_regs * const regs = xec_chan_regs(devcfg->regs, channel); |
| uint32_t sts = regs->istatus; |
| int cb_status = 0; |
| |
| #ifdef XEC_DMA_DEBUG |
| uint8_t idx = channel_isr_idx[channel]; |
| |
| if (idx < 16) { |
| channel_isr_sts[channel][idx] = sts; |
| channel_isr_ctrl[channel][idx] = regs->control; |
| channel_isr_idx[channel] = ++idx; |
| } |
| #endif |
| LOG_DBG("maddr=0x%08x mend=0x%08x daddr=0x%08x ctrl=0x%08x sts=0x%02x", regs->mem_addr, |
| regs->mem_addr_end, regs->dev_addr, regs->control, sts); |
| |
| regs->ienable = 0u; |
| regs->istatus = 0xffu; |
| mchp_xec_ecia_girq_src_clr(info[channel].gid, info[channel].gpos); |
| |
| chan_data->isr_hw_status = sts; |
| chan_data->total_curr_xfr_len += (regs->mem_addr - chan_data->mstart); |
| |
| if (sts & BIT(XEC_DMA_CHAN_IES_BERR_POS)) {/* Bus Error? */ |
| if (!(chan_data->flags & BIT(DMA_XEC_CHAN_FLAGS_CB_ERR_DIS_POS))) { |
| cb_status = -EIO; |
| } |
| } |
| |
| if (chan_data->cb) { |
| chan_data->cb(dev, chan_data->user_data, channel, cb_status); |
| } |
| } |
| |
| static int dma_xec_init(const struct device *dev) |
| { |
| const struct dma_xec_config * const devcfg = dev->config; |
| struct dma_xec_regs * const regs = devcfg->regs; |
| |
| LOG_DBG("driver init"); |
| |
| z_mchp_xec_pcr_periph_sleep(devcfg->pcr_idx, devcfg->pcr_pos, 0); |
| |
| /* soft reset, self-clearing */ |
| regs->mctrl = BIT(XEC_DMA_MAIN_CTRL_SRST_POS); |
| regs->mpkt = 0u; /* I/O delay, write to read-only register */ |
| regs->mctrl = BIT(XEC_DMA_MAIN_CTRL_EN_POS); |
| |
| devcfg->irq_connect(); |
| |
| return 0; |
| } |
| |
| /* n = node-id, p = property, i = index */ |
| #define DMA_XEC_GID(n, p, i) MCHP_XEC_ECIA_GIRQ(DT_PROP_BY_IDX(n, p, i)) |
| #define DMA_XEC_GPOS(n, p, i) MCHP_XEC_ECIA_GIRQ_POS(DT_PROP_BY_IDX(n, p, i)) |
| |
| #define DMA_XEC_GIRQ_INFO(n, p, i) \ |
| { \ |
| .gid = DMA_XEC_GID(n, p, i), \ |
| .gpos = DMA_XEC_GPOS(n, p, i), \ |
| .anid = MCHP_XEC_ECIA_NVIC_AGGR(DT_PROP_BY_IDX(n, p, i)), \ |
| .dnid = MCHP_XEC_ECIA_NVIC_DIRECT(DT_PROP_BY_IDX(n, p, i)), \ |
| }, |
| |
| /* n = node-id, p = property, i = index(channel?) */ |
| #define DMA_XEC_IRQ_DECLARE(node_id, p, i) \ |
| static void dma_xec_chan_##i##_isr(const struct device *dev) \ |
| { \ |
| dma_xec_irq_handler(dev, i); \ |
| } \ |
| |
| #define DMA_XEC_IRQ_CONNECT_SUB(node_id, p, i) \ |
| IRQ_CONNECT(DT_IRQ_BY_IDX(node_id, i, irq), \ |
| DT_IRQ_BY_IDX(node_id, i, priority), \ |
| dma_xec_chan_##i##_isr, \ |
| DEVICE_DT_GET(node_id), 0); \ |
| irq_enable(DT_IRQ_BY_IDX(node_id, i, irq)); \ |
| mchp_xec_ecia_enable(DMA_XEC_GID(node_id, p, i), DMA_XEC_GPOS(node_id, p, i)); |
| |
| /* i = instance number of DMA controller */ |
| #define DMA_XEC_IRQ_CONNECT(inst) \ |
| DT_INST_FOREACH_PROP_ELEM(inst, girqs, DMA_XEC_IRQ_DECLARE) \ |
| void dma_xec_irq_connect##inst(void) \ |
| { \ |
| DT_INST_FOREACH_PROP_ELEM(inst, girqs, DMA_XEC_IRQ_CONNECT_SUB) \ |
| } |
| |
| #define DMA_XEC_DEVICE(i) \ |
| BUILD_ASSERT(DT_INST_PROP(i, dma_channels) <= 16, "XEC DMA dma-channels > 16"); \ |
| BUILD_ASSERT(DT_INST_PROP(i, dma_requests) <= 16, "XEC DMA dma-requests > 16"); \ |
| \ |
| static struct dma_xec_channel \ |
| dma_xec_ctrl##i##_chans[DT_INST_PROP(i, dma_channels)]; \ |
| ATOMIC_DEFINE(dma_xec_atomic##i, DT_INST_PROP(i, dma_channels)); \ |
| \ |
| static struct dma_xec_data dma_xec_data##i = { \ |
| .ctx.magic = DMA_MAGIC, \ |
| .ctx.dma_channels = DT_INST_PROP(i, dma_channels), \ |
| .ctx.atomic = dma_xec_atomic##i, \ |
| .channels = dma_xec_ctrl##i##_chans, \ |
| }; \ |
| \ |
| DMA_XEC_IRQ_CONNECT(i) \ |
| \ |
| static const struct dma_xec_irq_info dma_xec_irqi##i[] = { \ |
| DT_INST_FOREACH_PROP_ELEM(i, girqs, DMA_XEC_GIRQ_INFO) \ |
| }; \ |
| static const struct dma_xec_config dma_xec_cfg##i = { \ |
| .regs = (struct dma_xec_regs *)DT_INST_REG_ADDR(i), \ |
| .dma_channels = DT_INST_PROP(i, dma_channels), \ |
| .dma_requests = DT_INST_PROP(i, dma_requests), \ |
| .pcr_idx = DT_INST_PROP_BY_IDX(i, pcrs, 0), \ |
| .pcr_pos = DT_INST_PROP_BY_IDX(i, pcrs, 1), \ |
| .irq_info_size = ARRAY_SIZE(dma_xec_irqi##i), \ |
| .irq_info_list = dma_xec_irqi##i, \ |
| .irq_connect = dma_xec_irq_connect##i, \ |
| }; \ |
| PM_DEVICE_DT_DEFINE(i, dmac_xec_pm_action); \ |
| DEVICE_DT_INST_DEFINE(i, &dma_xec_init, \ |
| PM_DEVICE_DT_GET(i), \ |
| &dma_xec_data##i, &dma_xec_cfg##i, \ |
| PRE_KERNEL_1, CONFIG_DMA_INIT_PRIORITY, \ |
| &dma_xec_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(DMA_XEC_DEVICE) |