| /* |
| * Copyright (c) 2022 Andriy Gelman |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT infineon_xmc4xxx_dma |
| |
| #include <soc.h> |
| #include <stdint.h> |
| #include <xmc_dma.h> |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/dma.h> |
| #include <zephyr/dt-bindings/dma/infineon-xmc4xxx-dma.h> |
| #include <zephyr/irq.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(dma_xmc4xxx, CONFIG_DMA_LOG_LEVEL); |
| |
| #define MAX_PRIORITY 7 |
| #define DMA_MAX_BLOCK_LEN 4095 |
| #define DLR_LINE_UNSET 0xff |
| |
| #define DLR_SRSEL_RS_BITSIZE 4 |
| #define DLR_SRSEL_RS_MSK 0xf |
| |
| #define MULTI_BLOCK_NUM_CHANNELS 2 |
| |
| #define XMC_DMA_CTLL_MEMORY_TO_MEMORY 0 |
| #define XMC_DMA_CTLL_MEMORY_TO_PERIPHERAL 1 |
| #define XMC_DMA_CTLL_PERIPHERAL_TO_MEMORY 2 |
| |
| #define ALL_EVENTS \ |
| (XMC_DMA_CH_EVENT_TRANSFER_COMPLETE | XMC_DMA_CH_EVENT_BLOCK_TRANSFER_COMPLETE | \ |
| XMC_DMA_CH_EVENT_SRC_TRANSACTION_COMPLETE | XMC_DMA_CH_EVENT_DST_TRANSACTION_COMPLETE | \ |
| XMC_DMA_CH_EVENT_ERROR) |
| |
| struct dma_xmc4xxx_channel { |
| dma_callback_t cb; |
| void *user_data; |
| uint32_t dest_address; |
| uint32_t transfer_size; |
| uint8_t source_data_size; |
| uint8_t dlr_line; |
| uint8_t channel_direction; |
| uint8_t dest_addr_adj; |
| bool multi_block; |
| }; |
| |
| struct dma_xmc4xxx_descriptor { |
| uint32_t sar; /* source address */ |
| uint32_t dar; /* destination address */ |
| uint32_t llp; /* linked-list pointer to the next descriptor or null if last descriptor */ |
| uint32_t ctll; /* control register low */ |
| uint32_t ctlh; /* control register high */ |
| uint32_t dstat; /* status register fetched from address DSTATAR after block completes*/ |
| } __packed; |
| |
| struct dma_xmc4xxx_scatter_gather { |
| bool enabled; |
| uint32_t interval; |
| uint16_t count; |
| }; |
| |
| static struct dma_xmc4xxx_descriptor descriptor_list[MULTI_BLOCK_NUM_CHANNELS] |
| [CONFIG_DMA_XMC4XXX_NUM_DESCRIPTORS]; |
| |
| struct dma_xmc4xxx_config { |
| XMC_DMA_t *dma; |
| void (*irq_configure)(void); |
| }; |
| |
| struct dma_xmc4xxx_data { |
| struct dma_context ctx; |
| struct dma_xmc4xxx_channel *channels; |
| }; |
| |
| #define HANDLE_EVENT(event_test, get_channels_event, ret) \ |
| do { \ |
| if (event & (XMC_DMA_CH_##event_test)) { \ |
| uint32_t channels_event = get_channels_event(dma); \ |
| int channel = find_lsb_set(channels_event) - 1; \ |
| struct dma_xmc4xxx_channel *dma_channel; \ |
| \ |
| __ASSERT_NO_MSG(channel >= 0); \ |
| dma_channel = &dev_data->channels[channel]; \ |
| /* Event has to be cleared before callback. The callback may call */ \ |
| /* dma_start() and re-enable the event */ \ |
| XMC_DMA_CH_ClearEventStatus(dma, channel, XMC_DMA_CH_##event_test); \ |
| if (dma_channel->cb) { \ |
| dma_channel->cb(dev, dma_channel->user_data, channel, (ret)); \ |
| } \ |
| } \ |
| } while (0) |
| |
| /* Isr is level triggered, so we don't have to loop over all the channels */ |
| /* in a single call */ |
| static void dma_xmc4xxx_isr(const struct device *dev) |
| { |
| struct dma_xmc4xxx_data *dev_data = dev->data; |
| const struct dma_xmc4xxx_config *dev_cfg = dev->config; |
| int num_dma_channels = dev_data->ctx.dma_channels; |
| XMC_DMA_t *dma = dev_cfg->dma; |
| uint32_t event; |
| uint32_t sr_overruns; |
| |
| /* There are two types of possible DMA error events: */ |
| /* 1. Error response from AHB slave on the HRESP bus during DMA transfer. */ |
| /* Treat this as EPERM error. */ |
| /* 2. Service request overruns on the DLR line. */ |
| /* Treat this EIO error. */ |
| |
| event = XMC_DMA_GetEventStatus(dma); |
| HANDLE_EVENT(EVENT_ERROR, XMC_DMA_GetChannelsErrorStatus, -EPERM); |
| HANDLE_EVENT(EVENT_BLOCK_TRANSFER_COMPLETE, XMC_DMA_GetChannelsBlockCompleteStatus, 0); |
| HANDLE_EVENT(EVENT_TRANSFER_COMPLETE, XMC_DMA_GetChannelsTransferCompleteStatus, 0); |
| |
| sr_overruns = DLR->OVRSTAT; |
| |
| if (sr_overruns == 0) { |
| return; |
| } |
| |
| /* clear the overruns */ |
| DLR->OVRCLR = sr_overruns; |
| |
| /* notify about overruns */ |
| for (int i = 0; i < num_dma_channels; i++) { |
| struct dma_xmc4xxx_channel *dma_channel; |
| |
| dma_channel = &dev_data->channels[i]; |
| if (dma_channel->dlr_line != DLR_LINE_UNSET && |
| sr_overruns & BIT(dma_channel->dlr_line)) { |
| |
| /* From XMC4700/4800 reference documentation - Section 4.4.1 */ |
| /* Once the overrun condition is entered the user can clear the */ |
| /* overrun status bits by writing to the DLR_OVRCLR register. */ |
| /* Additionally the pending request must be reset by successively */ |
| /* disabling and enabling the respective line. */ |
| DLR->LNEN &= ~BIT(dma_channel->dlr_line); |
| DLR->LNEN |= BIT(dma_channel->dlr_line); |
| |
| LOG_ERR("Overruns detected on channel %d", i); |
| if (dma_channel->cb != NULL) { |
| dma_channel->cb(dev, dma_channel->user_data, i, -EIO); |
| } |
| } |
| } |
| } |
| |
| static uint32_t dma_xmc4xxx_reg_ctll(struct dma_block_config *block, struct dma_config *config) |
| { |
| uint32_t ctll; |
| |
| ctll = config->dest_data_size / 2 << GPDMA0_CH_CTLL_DST_TR_WIDTH_Pos | |
| config->source_data_size / 2 << GPDMA0_CH_CTLL_SRC_TR_WIDTH_Pos | |
| block->dest_addr_adj << GPDMA0_CH_CTLL_DINC_Pos | |
| block->source_addr_adj << GPDMA0_CH_CTLL_SINC_Pos | |
| config->dest_burst_length / 4 << GPDMA0_CH_CTLL_DEST_MSIZE_Pos | |
| config->source_burst_length / 4 << GPDMA0_CH_CTLL_SRC_MSIZE_Pos | |
| BIT(GPDMA0_CH_CTLL_INT_EN_Pos); |
| |
| /* Only GPDMA flow controller supported */ |
| if (config->channel_direction == MEMORY_TO_PERIPHERAL) { |
| ctll |= XMC_DMA_CTLL_MEMORY_TO_PERIPHERAL << GPDMA0_CH_CTLL_TT_FC_Pos; |
| } |
| |
| if (config->channel_direction == PERIPHERAL_TO_MEMORY) { |
| ctll |= XMC_DMA_CTLL_PERIPHERAL_TO_MEMORY << GPDMA0_CH_CTLL_TT_FC_Pos; |
| } |
| |
| if (block->source_gather_en && block->source_gather_count > 0) { |
| ctll |= BIT(GPDMA0_CH_CTLL_SRC_GATHER_EN_Pos); |
| } |
| |
| if (block->dest_scatter_en && block->dest_scatter_count > 0) { |
| ctll |= BIT(GPDMA0_CH_CTLL_DST_SCATTER_EN_Pos); |
| } |
| |
| return ctll; |
| } |
| |
| #define SET_CHECK_SCATTER_GATHER(type) \ |
| do { \ |
| if (block->type##_en && block->type##_count > 0 && !type.enabled) { \ |
| type.enabled = true; \ |
| type.interval = block->type##_interval; \ |
| type.count = block->type##_count; \ |
| } else if (block->type##_en && type.enabled) { \ |
| if (block->type##_interval != type.interval || \ |
| block->type##_count != type.count) { \ |
| LOG_ERR(STRINGIFY(type) " parameters must be consistent " \ |
| "across enabled blocks"); \ |
| return -EINVAL; \ |
| } \ |
| } \ |
| } while (0) |
| |
| static int dma_xmc4xxx_config(const struct device *dev, uint32_t channel, struct dma_config *config) |
| { |
| struct dma_xmc4xxx_data *dev_data = dev->data; |
| const struct dma_xmc4xxx_config *dev_cfg = dev->config; |
| struct dma_block_config *block = config->head_block; |
| XMC_DMA_t *dma = dev_cfg->dma; |
| uint8_t dlr_line = DLR_LINE_UNSET; |
| struct dma_xmc4xxx_scatter_gather source_gather = { 0 }; |
| struct dma_xmc4xxx_scatter_gather dest_scatter = { 0 }; |
| |
| if (channel >= dev_data->ctx.dma_channels) { |
| LOG_ERR("Invalid channel number"); |
| return -EINVAL; |
| } |
| |
| if (config->channel_priority > MAX_PRIORITY) { |
| LOG_ERR("Invalid priority"); |
| return -EINVAL; |
| } |
| |
| if (config->source_chaining_en || config->dest_chaining_en) { |
| LOG_ERR("Channel chaining is not supported"); |
| return -EINVAL; |
| } |
| |
| if (config->channel_direction != MEMORY_TO_MEMORY && |
| config->channel_direction != MEMORY_TO_PERIPHERAL && |
| config->channel_direction != PERIPHERAL_TO_MEMORY) { |
| LOG_ERR("Unsupported channel direction"); |
| return -EINVAL; |
| } |
| |
| if (config->block_count > CONFIG_DMA_XMC4XXX_NUM_DESCRIPTORS) { |
| LOG_ERR("Block count exceeds descriptor array size"); |
| return -EINVAL; |
| } |
| |
| if (block->source_gather_en || block->dest_scatter_en || config->block_count != 1 || |
| config->cyclic) { |
| if ((uint32_t)dma != (uint32_t)XMC_DMA0 || channel >= 2) { |
| LOG_ERR("Multi-block, cyclic and gather/scatter only supported on DMA0 on " |
| "ch0 and ch1"); |
| return -EINVAL; |
| } |
| } |
| |
| if (config->dest_data_size != 1 && config->dest_data_size != 2 && |
| config->dest_data_size != 4) { |
| LOG_ERR("Invalid dest size, Only 1,2,4 bytes supported"); |
| return -EINVAL; |
| } |
| |
| if (config->source_data_size != 1 && config->source_data_size != 2 && |
| config->source_data_size != 4) { |
| LOG_ERR("Invalid source size, Only 1,2,4 bytes supported"); |
| return -EINVAL; |
| } |
| |
| if (config->source_burst_length != 1 && config->source_burst_length != 4 && |
| config->source_burst_length != 8) { |
| LOG_ERR("Invalid src burst length (data size units). Only 1,4,8 units supported"); |
| return -EINVAL; |
| } |
| |
| if (config->dest_burst_length != 1 && config->dest_burst_length != 4 && |
| config->dest_burst_length != 8) { |
| LOG_ERR("Invalid dest burst length (data size units). Only 1,4,8 units supported"); |
| return -EINVAL; |
| } |
| |
| if (block->block_size / config->source_data_size > DMA_MAX_BLOCK_LEN) { |
| LOG_ERR("Block transactions must be <= 4095"); |
| return -EINVAL; |
| } |
| |
| if (XMC_DMA_CH_IsEnabled(dma, channel)) { |
| LOG_ERR("Channel is still active"); |
| return -EINVAL; |
| } |
| |
| XMC_DMA_CH_ClearEventStatus(dma, channel, ALL_EVENTS); |
| |
| /* check dma slot number */ |
| if (config->block_count == 1 && config->cyclic == 0) { |
| uint32_t ctll; |
| |
| dma->CH[channel].SAR = block->source_address; |
| dma->CH[channel].DAR = block->dest_address; |
| dma->CH[channel].LLP = 0; |
| |
| /* set number of transactions */ |
| dma->CH[channel].CTLH = block->block_size / config->source_data_size; |
| |
| ctll = dma_xmc4xxx_reg_ctll(block, config); |
| |
| SET_CHECK_SCATTER_GATHER(source_gather); |
| SET_CHECK_SCATTER_GATHER(dest_scatter); |
| |
| dma->CH[channel].CTLL = ctll; |
| |
| } else { |
| struct dma_xmc4xxx_descriptor *desc; |
| |
| dma->CH[channel].LLP = (uint32_t)&descriptor_list[channel][0]; |
| dma->CH[channel].CTLL = BIT(GPDMA0_CH_CTLL_LLP_DST_EN_Pos) | |
| BIT(GPDMA0_CH_CTLL_LLP_SRC_EN_Pos); |
| for (int i = 0; i < config->block_count; i++) { |
| uint32_t ctll; |
| |
| desc = &descriptor_list[channel][i]; |
| |
| desc->sar = block->source_address; |
| desc->dar = block->dest_address; |
| desc->ctlh = block->block_size / config->source_data_size; |
| |
| ctll = dma_xmc4xxx_reg_ctll(block, config); |
| |
| if (i < config->block_count - 1) { |
| desc->llp = (uint32_t)&descriptor_list[channel][i + 1]; |
| } else if (config->cyclic) { |
| desc->llp = (uint32_t)&descriptor_list[channel][0]; |
| } else { |
| desc->llp = 0; |
| } |
| |
| if (i < config->block_count - 1 || config->cyclic) { |
| ctll |= BIT(GPDMA0_CH_CTLL_LLP_DST_EN_Pos) | |
| BIT(GPDMA0_CH_CTLL_LLP_SRC_EN_Pos); |
| } |
| |
| desc->ctll = ctll; |
| |
| SET_CHECK_SCATTER_GATHER(source_gather); |
| SET_CHECK_SCATTER_GATHER(dest_scatter); |
| |
| block = block->next_block; |
| } |
| } |
| |
| block = config->head_block; |
| |
| /* set priority and software handshaking for src/dst. if hardware hankshaking is used */ |
| /* it will be enabled later in the code */ |
| dma->CH[channel].CFGL = (config->channel_priority << GPDMA0_CH_CFGL_CH_PRIOR_Pos) | |
| GPDMA0_CH_CFGL_HS_SEL_SRC_Msk | GPDMA0_CH_CFGL_HS_SEL_DST_Msk; |
| |
| dma->CH[channel].CFGH = 0; |
| |
| if (config->channel_direction == MEMORY_TO_PERIPHERAL || |
| config->channel_direction == PERIPHERAL_TO_MEMORY) { |
| uint8_t request_source = XMC4XXX_DMA_GET_REQUEST_SOURCE(config->dma_slot); |
| uint8_t dlr_line_reg = XMC4XXX_DMA_GET_LINE(config->dma_slot); |
| |
| dlr_line = dlr_line_reg; |
| if ((uint32_t)dma == (uint32_t)XMC_DMA0 && dlr_line > 7) { |
| LOG_ERR("Unsupported request line %d for DMA0." |
| "Should be in range [0,7]", dlr_line); |
| return -EINVAL; |
| } |
| |
| if ((uint32_t)dma == (uint32_t)XMC_DMA1 && (dlr_line < 8 || dlr_line > 11)) { |
| LOG_ERR("Unsupported request line %d for DMA1." |
| "Should be in range [8,11]", dlr_line); |
| return -EINVAL; |
| } |
| |
| /* clear any overruns */ |
| DLR->OVRCLR = BIT(dlr_line); |
| /* enable the dma line */ |
| DLR->LNEN &= ~BIT(dlr_line); |
| DLR->LNEN |= BIT(dlr_line); |
| |
| /* connect DMA Line to SR */ |
| if ((uint32_t)dma == (uint32_t)XMC_DMA0) { |
| DLR->SRSEL0 &= ~(DLR_SRSEL_RS_MSK << (dlr_line_reg * DLR_SRSEL_RS_BITSIZE)); |
| DLR->SRSEL0 |= request_source << (dlr_line_reg * DLR_SRSEL_RS_BITSIZE); |
| } |
| |
| if ((uint32_t)dma == (uint32_t)XMC_DMA1) { |
| dlr_line_reg -= 8; |
| DLR->SRSEL1 &= ~(DLR_SRSEL_RS_MSK << (dlr_line_reg * DLR_SRSEL_RS_BITSIZE)); |
| DLR->SRSEL1 |= request_source << (dlr_line_reg * DLR_SRSEL_RS_BITSIZE); |
| } |
| |
| /* connect DMA channel to DMA line */ |
| if (config->channel_direction == MEMORY_TO_PERIPHERAL) { |
| dma->CH[channel].CFGH = (dlr_line_reg << GPDMA0_CH_CFGH_DEST_PER_Pos) | 4; |
| dma->CH[channel].CFGL &= ~BIT(GPDMA0_CH_CFGL_HS_SEL_DST_Pos); |
| } |
| |
| if (config->channel_direction == PERIPHERAL_TO_MEMORY) { |
| dma->CH[channel].CFGH = (dlr_line_reg << GPDMA0_CH_CFGH_SRC_PER_Pos) | 4; |
| dma->CH[channel].CFGL &= ~BIT(GPDMA0_CH_CFGL_HS_SEL_SRC_Pos); |
| } |
| } |
| |
| if (block->fifo_mode_control > 0) { |
| dma->CH[channel].CFGH |= GPDMA0_CH_CFGH_FIFO_MODE_Msk; |
| } |
| |
| if ((uint32_t)dma == (uint32_t)XMC_DMA0) { |
| if (channel == 0 || channel == 1) { |
| /* reset scatter/gather registers */ |
| dma->CH[channel].SGR = 0; |
| dma->CH[channel].DSR = 0; |
| } |
| } |
| |
| if (source_gather.enabled) { |
| /* truncate if we are out of range */ |
| dma->CH[channel].SGR = (source_gather.interval & GPDMA0_CH_SGR_SGI_Msk) | |
| source_gather.count << GPDMA0_CH_SGR_SGC_Pos; |
| } |
| |
| if (dest_scatter.enabled) { |
| /* truncate if we are out of range */ |
| dma->CH[channel].DSR = (dest_scatter.interval & GPDMA0_CH_DSR_DSI_Msk) | |
| dest_scatter.count << GPDMA0_CH_DSR_DSC_Pos; |
| } |
| |
| dev_data->channels[channel].cb = config->dma_callback; |
| dev_data->channels[channel].user_data = config->user_data; |
| dev_data->channels[channel].transfer_size = block->block_size; |
| dev_data->channels[channel].source_data_size = config->source_data_size; |
| dev_data->channels[channel].dlr_line = dlr_line; |
| dev_data->channels[channel].channel_direction = config->channel_direction; |
| dev_data->channels[channel].dest_addr_adj = block->dest_addr_adj; |
| dev_data->channels[channel].dest_address = block->dest_address; |
| |
| if (config->block_count > 1) { |
| dev_data->channels[channel].multi_block = true; |
| } else { |
| dev_data->channels[channel].multi_block = false; |
| } |
| |
| XMC_DMA_CH_DisableEvent(dma, channel, ALL_EVENTS); |
| XMC_DMA_CH_EnableEvent(dma, channel, XMC_DMA_CH_EVENT_TRANSFER_COMPLETE); |
| |
| /* trigger enable on block transfer complete */ |
| if (config->complete_callback_en) { |
| XMC_DMA_CH_EnableEvent(dma, channel, XMC_DMA_CH_EVENT_BLOCK_TRANSFER_COMPLETE); |
| } |
| |
| if (!config->error_callback_dis) { |
| XMC_DMA_CH_EnableEvent(dma, channel, XMC_DMA_CH_EVENT_ERROR); |
| } |
| |
| LOG_DBG("Configured channel %d for %08X to %08X (%u)", channel, block->source_address, |
| block->dest_address, block->block_size); |
| |
| return 0; |
| } |
| |
| static int dma_xmc4xxx_start(const struct device *dev, uint32_t channel) |
| { |
| const struct dma_xmc4xxx_config *dev_cfg = dev->config; |
| struct dma_xmc4xxx_data *dev_data = dev->data; |
| uint8_t dlr_line = dev_data->channels[channel].dlr_line; |
| |
| LOG_DBG("Starting channel %d", channel); |
| if (dlr_line != DLR_LINE_UNSET && (DLR->LNEN & BIT(dlr_line)) == 0) { |
| DLR->LNEN |= BIT(dlr_line); |
| } |
| |
| XMC_DMA_CH_Enable(dev_cfg->dma, channel); |
| return 0; |
| } |
| |
| static int dma_xmc4xxx_stop(const struct device *dev, uint32_t channel) |
| { |
| const struct dma_xmc4xxx_config *dev_cfg = dev->config; |
| struct dma_xmc4xxx_data *dev_data = dev->data; |
| struct dma_xmc4xxx_channel *dma_channel; |
| XMC_DMA_t *dma = dev_cfg->dma; |
| |
| dma_channel = &dev_data->channels[channel]; |
| XMC_DMA_CH_Suspend(dma, channel); |
| |
| /* wait until ongoing transfer finishes */ |
| while (XMC_DMA_CH_IsEnabled(dma, channel) && |
| (dma->CH[channel].CFGL & GPDMA0_CH_CFGL_FIFO_EMPTY_Msk) == 0) { |
| } |
| |
| /* disconnect DLR line to stop overuns */ |
| if (dma_channel->dlr_line != DLR_LINE_UNSET) { |
| DLR->LNEN &= ~BIT(dma_channel->dlr_line); |
| } |
| |
| XMC_DMA_CH_Disable(dma, channel); |
| XMC_DMA_CH_Resume(dma, channel); |
| return 0; |
| } |
| |
| static int dma_xmc4xxx_reload(const struct device *dev, uint32_t channel, uint32_t src, |
| uint32_t dst, size_t size) |
| { |
| struct dma_xmc4xxx_data *dev_data = dev->data; |
| size_t block_ts; |
| const struct dma_xmc4xxx_config *dev_cfg = dev->config; |
| XMC_DMA_t *dma = dev_cfg->dma; |
| struct dma_xmc4xxx_channel *dma_channel; |
| |
| if (channel >= dev_data->ctx.dma_channels) { |
| LOG_ERR("Invalid channel number"); |
| return -EINVAL; |
| } |
| |
| if (XMC_DMA_CH_IsEnabled(dma, channel)) { |
| LOG_ERR("Channel is still active"); |
| return -EINVAL; |
| } |
| |
| dma_channel = &dev_data->channels[channel]; |
| block_ts = size / dma_channel->source_data_size; |
| if (block_ts > DMA_MAX_BLOCK_LEN) { |
| LOG_ERR("Block transactions must be <= 4095"); |
| return -EINVAL; |
| } |
| dma_channel->transfer_size = size; |
| dma_channel->dest_address = dst; |
| |
| /* do we need to clear any errors */ |
| dma->CH[channel].SAR = src; |
| dma->CH[channel].DAR = dst; |
| dma->CH[channel].CTLH = block_ts; |
| |
| return 0; |
| } |
| |
| static int dma_xmc4xxx_get_status(const struct device *dev, uint32_t channel, |
| struct dma_status *stat) |
| { |
| struct dma_xmc4xxx_data *dev_data = dev->data; |
| const struct dma_xmc4xxx_config *dev_cfg = dev->config; |
| XMC_DMA_t *dma = dev_cfg->dma; |
| struct dma_xmc4xxx_channel *dma_channel; |
| uint32_t transferred_bytes; |
| |
| if (channel >= dev_data->ctx.dma_channels) { |
| LOG_ERR("Invalid channel number"); |
| return -EINVAL; |
| } |
| dma_channel = &dev_data->channels[channel]; |
| |
| stat->busy = XMC_DMA_CH_IsEnabled(dma, channel); |
| |
| if (dma_channel->multi_block) { |
| /* not supported for multi-block transfers */ |
| stat->pending_length = 0; |
| return 0; |
| } |
| |
| /* Use DAR to check for transferred bytes when possible. Value CTL.BLOCK_TS does not */ |
| /* appear to guarantee that the last value is fully transferred to dest. */ |
| if (dma_channel->dest_addr_adj == DMA_ADDR_ADJ_INCREMENT) { |
| transferred_bytes = dma->CH[channel].DAR - dma_channel->dest_address; |
| stat->pending_length = dma_channel->transfer_size - transferred_bytes; |
| } else if (dma_channel->dest_addr_adj == DMA_ADDR_ADJ_DECREMENT) { |
| transferred_bytes = dma_channel->dest_address - dma->CH[channel].DAR; |
| stat->pending_length = dma_channel->transfer_size - transferred_bytes; |
| } else { |
| uint32_t num_source_transfers = XMC_DMA_CH_GetTransferredData(dma, channel); |
| |
| stat->pending_length = dma_channel->transfer_size - |
| num_source_transfers * dma_channel->source_data_size; |
| } |
| |
| /* stat->dir and other remaining fields are not set. They are not */ |
| /* useful for xmc4xxx peripheral drivers. */ |
| |
| return 0; |
| } |
| |
| static bool dma_xmc4xxx_chan_filter(const struct device *dev, int channel, void *filter_param) |
| { |
| uint32_t requested_channel; |
| |
| if (!filter_param) { |
| return true; |
| } |
| |
| requested_channel = *(uint32_t *)filter_param; |
| |
| if (channel == requested_channel) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static int dma_xmc4xxx_suspend(const struct device *dev, uint32_t channel) |
| { |
| struct dma_xmc4xxx_data *dev_data = dev->data; |
| const struct dma_xmc4xxx_config *dev_cfg = dev->config; |
| XMC_DMA_t *dma = dev_cfg->dma; |
| |
| if (channel >= dev_data->ctx.dma_channels) { |
| LOG_ERR("Invalid channel number"); |
| return -EINVAL; |
| } |
| |
| XMC_DMA_CH_Suspend(dma, channel); |
| return 0; |
| } |
| |
| static int dma_xmc4xxx_resume(const struct device *dev, uint32_t channel) |
| { |
| struct dma_xmc4xxx_data *dev_data = dev->data; |
| const struct dma_xmc4xxx_config *dev_cfg = dev->config; |
| XMC_DMA_t *dma = dev_cfg->dma; |
| |
| if (channel >= dev_data->ctx.dma_channels) { |
| LOG_ERR("Invalid channel number"); |
| return -EINVAL; |
| } |
| |
| XMC_DMA_CH_Resume(dma, channel); |
| return 0; |
| } |
| |
| static int dma_xmc4xxx_init(const struct device *dev) |
| { |
| const struct dma_xmc4xxx_config *dev_cfg = dev->config; |
| |
| XMC_DMA_Enable(dev_cfg->dma); |
| dev_cfg->irq_configure(); |
| return 0; |
| } |
| |
| static DEVICE_API(dma, dma_xmc4xxx_driver_api) = { |
| .config = dma_xmc4xxx_config, |
| .reload = dma_xmc4xxx_reload, |
| .start = dma_xmc4xxx_start, |
| .stop = dma_xmc4xxx_stop, |
| .get_status = dma_xmc4xxx_get_status, |
| .chan_filter = dma_xmc4xxx_chan_filter, |
| .suspend = dma_xmc4xxx_suspend, |
| .resume = dma_xmc4xxx_resume, |
| }; |
| |
| #define XMC4XXX_DMA_INIT(inst) \ |
| static void dma_xmc4xxx##inst##_irq_configure(void) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQ_BY_IDX(inst, 0, irq), \ |
| DT_INST_IRQ_BY_IDX(inst, 0, priority), \ |
| dma_xmc4xxx_isr, \ |
| DEVICE_DT_INST_GET(inst), 0); \ |
| irq_enable(DT_INST_IRQ_BY_IDX(inst, 0, irq)); \ |
| } \ |
| static const struct dma_xmc4xxx_config dma_xmc4xxx##inst##_config = { \ |
| .dma = (XMC_DMA_t *)DT_INST_REG_ADDR(inst), \ |
| .irq_configure = dma_xmc4xxx##inst##_irq_configure, \ |
| }; \ |
| \ |
| static struct dma_xmc4xxx_channel \ |
| dma_xmc4xxx##inst##_channels[DT_INST_PROP(inst, dma_channels)]; \ |
| ATOMIC_DEFINE(dma_xmc4xxx_atomic##inst, \ |
| DT_INST_PROP(inst, dma_channels)); \ |
| static struct dma_xmc4xxx_data dma_xmc4xxx##inst##_data = { \ |
| .ctx = { \ |
| .magic = DMA_MAGIC, \ |
| .atomic = dma_xmc4xxx_atomic##inst, \ |
| .dma_channels = DT_INST_PROP(inst, dma_channels), \ |
| }, \ |
| .channels = dma_xmc4xxx##inst##_channels, \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(inst, &dma_xmc4xxx_init, NULL, \ |
| &dma_xmc4xxx##inst##_data, \ |
| &dma_xmc4xxx##inst##_config, PRE_KERNEL_1, \ |
| CONFIG_DMA_INIT_PRIORITY, &dma_xmc4xxx_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(XMC4XXX_DMA_INIT) |