|  | /* | 
|  | * Copyright 2024 NXP | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include "dma_nxp_edma.h" | 
|  |  | 
|  | /* TODO list: | 
|  | * 1) Support for requesting a specific channel. | 
|  | * 2) Support for checking if DMA transfer is pending when attempting config. (?) | 
|  | * 3) Support for error interrupt. | 
|  | * 4) Support for error if buffer overflow/underrun. | 
|  | * 5) Ideally, HALFMAJOR should be set on a per-channel basis not through a | 
|  | * config. If not possible, this should be done through a DTS property. Also, | 
|  | * maybe do the same for INTMAJOR IRQ. | 
|  | */ | 
|  |  | 
|  | static void edma_isr(const void *parameter) | 
|  | { | 
|  | const struct edma_config *cfg; | 
|  | struct edma_data *data; | 
|  | struct edma_channel *chan; | 
|  | int ret; | 
|  | uint32_t update_size; | 
|  |  | 
|  | chan = (struct edma_channel *)parameter; | 
|  | cfg = chan->dev->config; | 
|  | data = chan->dev->data; | 
|  |  | 
|  | if (!EDMA_ChannelRegRead(data->hal_cfg, chan->id, EDMA_TCD_CH_INT)) { | 
|  | /* skip, interrupt was probably triggered by another channel */ | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* clear interrupt */ | 
|  | EDMA_ChannelRegUpdate(data->hal_cfg, chan->id, | 
|  | EDMA_TCD_CH_INT, EDMA_TCD_CH_INT_MASK, 0); | 
|  |  | 
|  | if (chan->cyclic_buffer) { | 
|  | update_size = chan->bsize; | 
|  |  | 
|  | if (IS_ENABLED(CONFIG_DMA_NXP_EDMA_ENABLE_HALFMAJOR_IRQ)) { | 
|  | update_size = chan->bsize / 2; | 
|  | } else { | 
|  | update_size = chan->bsize; | 
|  | } | 
|  |  | 
|  | /* TODO: add support for error handling here */ | 
|  | ret = EDMA_CHAN_PRODUCE_CONSUME_A(chan, update_size); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("chan %d buffer overflow/underrun", chan->id); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* TODO: are there any sanity checks we have to perform before invoking | 
|  | * the registered callback? | 
|  | */ | 
|  | if (chan->cb) { | 
|  | chan->cb(chan->dev, chan->arg, chan->id, DMA_STATUS_COMPLETE); | 
|  | } | 
|  | } | 
|  |  | 
|  | static struct edma_channel *lookup_channel(const struct device *dev, | 
|  | uint32_t chan_id) | 
|  | { | 
|  | struct edma_data *data; | 
|  | const struct edma_config *cfg; | 
|  | int i; | 
|  |  | 
|  | data = dev->data; | 
|  | cfg = dev->config; | 
|  |  | 
|  |  | 
|  | /* optimization: if dma-channels property is present then | 
|  | * the channel data associated with the passed channel ID | 
|  | * can be found at index chan_id in the array of channels. | 
|  | */ | 
|  | if (cfg->contiguous_channels) { | 
|  | /* check for index out of bounds */ | 
|  | if (chan_id >= data->ctx.dma_channels) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | return &data->channels[chan_id]; | 
|  | } | 
|  |  | 
|  | /* channels are passed through the valid-channels property. | 
|  | * As such, since some channels may be missing we need to | 
|  | * look through the entire channels array for an ID match. | 
|  | */ | 
|  | for (i = 0; i < data->ctx.dma_channels; i++) { | 
|  | if (data->channels[i].id == chan_id) { | 
|  | return &data->channels[i]; | 
|  | } | 
|  | } | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static int edma_config(const struct device *dev, uint32_t chan_id, | 
|  | struct dma_config *dma_cfg) | 
|  | { | 
|  | struct edma_data *data; | 
|  | const struct edma_config *cfg; | 
|  | struct edma_channel *chan; | 
|  | uint32_t transfer_type; | 
|  | int ret; | 
|  |  | 
|  | data = dev->data; | 
|  | cfg = dev->config; | 
|  |  | 
|  | if (!dma_cfg->head_block) { | 
|  | LOG_ERR("head block shouldn't be NULL"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* validate source data size (SSIZE) */ | 
|  | if (!EDMA_TransferWidthIsValid(data->hal_cfg, dma_cfg->source_data_size)) { | 
|  | LOG_ERR("invalid source data size: %d", | 
|  | dma_cfg->source_data_size); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* validate destination data size (DSIZE) */ | 
|  | if (!EDMA_TransferWidthIsValid(data->hal_cfg, dma_cfg->dest_data_size)) { | 
|  | LOG_ERR("invalid destination data size: %d", | 
|  | dma_cfg->dest_data_size); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* validate configured alignment */ | 
|  | if (!EDMA_TransferWidthIsValid(data->hal_cfg, CONFIG_DMA_NXP_EDMA_ALIGN)) { | 
|  | LOG_ERR("configured alignment %d is invalid", | 
|  | CONFIG_DMA_NXP_EDMA_ALIGN); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Scatter-Gather configurations currently not supported */ | 
|  | if (dma_cfg->block_count != 1) { | 
|  | LOG_ERR("number of blocks %d not supported", dma_cfg->block_count); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | /* source address shouldn't be NULL */ | 
|  | if (!dma_cfg->head_block->source_address) { | 
|  | LOG_ERR("source address cannot be NULL"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* destination address shouldn't be NULL */ | 
|  | if (!dma_cfg->head_block->dest_address) { | 
|  | LOG_ERR("destination address cannot be NULL"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* check source address's (SADDR) alignment with respect to the data size (SSIZE) | 
|  | * | 
|  | * Failing to meet this condition will lead to the assertion of the SAE | 
|  | * bit (see CHn_ES register). | 
|  | * | 
|  | * TODO: this will also restrict scenarios such as the following: | 
|  | *	SADDR is 8B aligned and SSIZE is 16B. I've tested this | 
|  | *	scenario and seems to raise no hardware errors (I'm assuming | 
|  | *	because this doesn't break the 8B boundary of the 64-bit system | 
|  | *	I tested it on). Is there a need to allow such a scenario? | 
|  | */ | 
|  | if (dma_cfg->head_block->source_address % dma_cfg->source_data_size) { | 
|  | LOG_ERR("source address 0x%x alignment doesn't match data size %d", | 
|  | dma_cfg->head_block->source_address, | 
|  | dma_cfg->source_data_size); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* check destination address's (DADDR) alignment with respect to the data size (DSIZE) | 
|  | * Failing to meet this condition will lead to the assertion of the DAE | 
|  | * bit (see CHn_ES register). | 
|  | */ | 
|  | if (dma_cfg->head_block->dest_address % dma_cfg->dest_data_size) { | 
|  | LOG_ERR("destination address 0x%x alignment doesn't match data size %d", | 
|  | dma_cfg->head_block->dest_address, | 
|  | dma_cfg->dest_data_size); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* source burst length should match destination burst length. | 
|  | * This is because the burst length is the equivalent of NBYTES which | 
|  | * is used for both the destination and the source. | 
|  | */ | 
|  | if (dma_cfg->source_burst_length != | 
|  | dma_cfg->dest_burst_length) { | 
|  | LOG_ERR("source burst length %d doesn't match destination burst length %d", | 
|  | dma_cfg->source_burst_length, | 
|  | dma_cfg->dest_burst_length); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* total number of bytes should be a multiple of NBYTES. | 
|  | * | 
|  | * This is needed because the EDMA engine performs transfers based | 
|  | * on CITER (integer value) and NBYTES, thus it has no knowledge of | 
|  | * the total transfer size. If the total transfer size is not a | 
|  | * multiple of NBYTES then we'll end up with copying a wrong number | 
|  | * of bytes (CITER = TOTAL_SIZE / BITER). This, of course, raises | 
|  | * no error in the hardware but it's still wrong. | 
|  | */ | 
|  | if (dma_cfg->head_block->block_size % dma_cfg->source_burst_length) { | 
|  | LOG_ERR("block size %d should be a multiple of NBYTES %d", | 
|  | dma_cfg->head_block->block_size, | 
|  | dma_cfg->source_burst_length); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* check if NBYTES is a multiple of MAX(SSIZE, DSIZE). | 
|  | * | 
|  | * This stems from the fact that NBYTES needs to be a multiple | 
|  | * of SSIZE AND DSIZE. If NBYTES is a multiple of MAX(SSIZE, DSIZE) | 
|  | * then it will for sure satisfy the aforementioned condition (since | 
|  | * SSIZE and DSIZE are powers of 2). | 
|  | * | 
|  | * Failing to meet this condition will lead to the assertion of the | 
|  | * NCE bit (see CHn_ES register). | 
|  | */ | 
|  | if (dma_cfg->source_burst_length % | 
|  | MAX(dma_cfg->source_data_size, dma_cfg->dest_data_size)) { | 
|  | LOG_ERR("NBYTES %d should be a multiple of MAX(SSIZE(%d), DSIZE(%d))", | 
|  | dma_cfg->source_burst_length, | 
|  | dma_cfg->source_data_size, | 
|  | dma_cfg->dest_data_size); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* fetch channel data */ | 
|  | chan = lookup_channel(dev, chan_id); | 
|  | if (!chan) { | 
|  | LOG_ERR("channel ID %u is not valid", chan_id); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* save the block size for later usage in edma_reload */ | 
|  | chan->bsize = dma_cfg->head_block->block_size; | 
|  |  | 
|  | if (dma_cfg->cyclic) { | 
|  | chan->cyclic_buffer = true; | 
|  |  | 
|  | chan->stat.read_position = 0; | 
|  | chan->stat.write_position = 0; | 
|  |  | 
|  | /* ASSUMPTION: for CONSUMER-type channels, the buffer from | 
|  | * which the engine consumes should be full, while in the | 
|  | * case of PRODUCER-type channels it should be empty. | 
|  | */ | 
|  | switch (dma_cfg->channel_direction) { | 
|  | case MEMORY_TO_PERIPHERAL: | 
|  | chan->type = CHAN_TYPE_CONSUMER; | 
|  | chan->stat.free = 0; | 
|  | chan->stat.pending_length = chan->bsize; | 
|  | break; | 
|  | case PERIPHERAL_TO_MEMORY: | 
|  | chan->type = CHAN_TYPE_PRODUCER; | 
|  | chan->stat.pending_length = 0; | 
|  | chan->stat.free = chan->bsize; | 
|  | break; | 
|  | default: | 
|  | LOG_ERR("unsupported transfer dir %d for cyclic mode", | 
|  | dma_cfg->channel_direction); | 
|  | return -ENOTSUP; | 
|  | } | 
|  | } else { | 
|  | chan->cyclic_buffer = false; | 
|  | } | 
|  |  | 
|  | /* change channel's state to CONFIGURED */ | 
|  | ret = channel_change_state(chan, CHAN_STATE_CONFIGURED); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("failed to change channel %d state to CONFIGURED", chan_id); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = get_transfer_type(dma_cfg->channel_direction, &transfer_type); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | chan->cb = dma_cfg->dma_callback; | 
|  | chan->arg = dma_cfg->user_data; | 
|  |  | 
|  | /* warning: this sets SOFF and DOFF to SSIZE and DSIZE which are POSITIVE. */ | 
|  | ret = EDMA_ConfigureTransfer(data->hal_cfg, chan_id, | 
|  | dma_cfg->head_block->source_address, | 
|  | dma_cfg->head_block->dest_address, | 
|  | dma_cfg->source_data_size, | 
|  | dma_cfg->dest_data_size, | 
|  | dma_cfg->source_burst_length, | 
|  | dma_cfg->head_block->block_size, | 
|  | transfer_type); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("failed to configure transfer"); | 
|  | return to_std_error(ret); | 
|  | } | 
|  |  | 
|  | /* TODO: channel MUX should be forced to 0 based on the previous state */ | 
|  | if (EDMA_HAS_MUX(data->hal_cfg)) { | 
|  | ret = EDMA_SetChannelMux(data->hal_cfg, chan_id, dma_cfg->dma_slot); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("failed to set channel MUX"); | 
|  | return to_std_error(ret); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* set SLAST and DLAST */ | 
|  | ret = set_slast_dlast(dma_cfg, transfer_type, data, chan_id); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* allow interrupting the CPU when a major cycle is completed. | 
|  | * | 
|  | * interesting note: only 1 major loop is performed per slave peripheral | 
|  | * DMA request. For instance, if block_size = 768 and burst_size = 192 | 
|  | * we're going to get 4 transfers of 192 bytes. Each of these transfers | 
|  | * translates to a DMA request made by the slave peripheral. | 
|  | */ | 
|  | EDMA_ChannelRegUpdate(data->hal_cfg, chan_id, | 
|  | EDMA_TCD_CSR, EDMA_TCD_CSR_INTMAJOR_MASK, 0); | 
|  |  | 
|  | if (IS_ENABLED(CONFIG_DMA_NXP_EDMA_ENABLE_HALFMAJOR_IRQ)) { | 
|  | /* if enabled through the above configuration, also | 
|  | * allow the CPU to be interrupted when CITER = BITER / 2. | 
|  | */ | 
|  | EDMA_ChannelRegUpdate(data->hal_cfg, chan_id, EDMA_TCD_CSR, | 
|  | EDMA_TCD_CSR_INTHALF_MASK, 0); | 
|  | } | 
|  |  | 
|  | /* enable channel interrupt */ | 
|  | irq_enable(chan->irq); | 
|  |  | 
|  | /* dump register status - for debugging purposes */ | 
|  | edma_dump_channel_registers(data, chan_id); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int edma_get_status(const struct device *dev, uint32_t chan_id, | 
|  | struct dma_status *stat) | 
|  | { | 
|  | struct edma_data *data; | 
|  | struct edma_channel *chan; | 
|  | uint32_t citer, biter, done; | 
|  | unsigned int key; | 
|  |  | 
|  | data = dev->data; | 
|  |  | 
|  | /* fetch channel data */ | 
|  | chan = lookup_channel(dev, chan_id); | 
|  | if (!chan) { | 
|  | LOG_ERR("channel ID %u is not valid", chan_id); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (chan->cyclic_buffer) { | 
|  | key = irq_lock(); | 
|  |  | 
|  | stat->free = chan->stat.free; | 
|  | stat->pending_length = chan->stat.pending_length; | 
|  |  | 
|  | irq_unlock(key); | 
|  | } else { | 
|  | /* note: no locking required here. The DMA interrupts | 
|  | * have no effect over CITER and BITER. | 
|  | */ | 
|  | citer = EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_CITER); | 
|  | biter = EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_BITER); | 
|  | done = EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_CH_CSR) & | 
|  | EDMA_TCD_CH_CSR_DONE_MASK; | 
|  | if (done) { | 
|  | stat->free = chan->bsize; | 
|  | stat->pending_length = 0; | 
|  | } else { | 
|  | stat->free = (biter - citer) * (chan->bsize / biter); | 
|  | stat->pending_length = chan->bsize - stat->free; | 
|  | } | 
|  | } | 
|  |  | 
|  | LOG_DBG("free: %d, pending: %d", stat->free, stat->pending_length); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int edma_suspend(const struct device *dev, uint32_t chan_id) | 
|  | { | 
|  | struct edma_data *data; | 
|  | const struct edma_config *cfg; | 
|  | struct edma_channel *chan; | 
|  | int ret; | 
|  |  | 
|  | data = dev->data; | 
|  | cfg = dev->config; | 
|  |  | 
|  | /* fetch channel data */ | 
|  | chan = lookup_channel(dev, chan_id); | 
|  | if (!chan) { | 
|  | LOG_ERR("channel ID %u is not valid", chan_id); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | edma_dump_channel_registers(data, chan_id); | 
|  |  | 
|  | /* change channel's state to SUSPENDED */ | 
|  | ret = channel_change_state(chan, CHAN_STATE_SUSPENDED); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("failed to change channel %d state to SUSPENDED", chan_id); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | LOG_DBG("suspending channel %u", chan_id); | 
|  |  | 
|  | /* disable HW requests */ | 
|  | EDMA_ChannelRegUpdate(data->hal_cfg, chan_id, | 
|  | EDMA_TCD_CH_CSR, 0, EDMA_TCD_CH_CSR_ERQ_MASK); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int edma_stop(const struct device *dev, uint32_t chan_id) | 
|  | { | 
|  | struct edma_data *data; | 
|  | const struct edma_config *cfg; | 
|  | struct edma_channel *chan; | 
|  | enum channel_state prev_state; | 
|  | int ret; | 
|  |  | 
|  | data = dev->data; | 
|  | cfg = dev->config; | 
|  |  | 
|  | /* fetch channel data */ | 
|  | chan = lookup_channel(dev, chan_id); | 
|  | if (!chan) { | 
|  | LOG_ERR("channel ID %u is not valid", chan_id); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | prev_state = chan->state; | 
|  |  | 
|  | /* change channel's state to STOPPED */ | 
|  | ret = channel_change_state(chan, CHAN_STATE_STOPPED); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("failed to change channel %d state to STOPPED", chan_id); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | LOG_DBG("stopping channel %u", chan_id); | 
|  |  | 
|  | if (prev_state == CHAN_STATE_SUSPENDED) { | 
|  | /* if the channel has been suspended then there's | 
|  | * no point in disabling the HW requests again. Just | 
|  | * jump to the channel release operation. | 
|  | */ | 
|  | goto out_release_channel; | 
|  | } | 
|  |  | 
|  | /* disable HW requests */ | 
|  | EDMA_ChannelRegUpdate(data->hal_cfg, chan_id, EDMA_TCD_CH_CSR, 0, | 
|  | EDMA_TCD_CH_CSR_ERQ_MASK); | 
|  | out_release_channel: | 
|  |  | 
|  | /* clear the channel MUX so that it can used by a different peripheral. | 
|  | * | 
|  | * note: because the channel is released during dma_stop() that means | 
|  | * dma_start() can no longer be immediately called. This is because | 
|  | * one needs to re-configure the channel MUX which can only be done | 
|  | * through dma_config(). As such, if one intends to reuse the current | 
|  | * configuration then please call dma_suspend() instead of dma_stop(). | 
|  | */ | 
|  | if (EDMA_HAS_MUX(data->hal_cfg)) { | 
|  | ret = EDMA_SetChannelMux(data->hal_cfg, chan_id, 0); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("failed to set channel MUX"); | 
|  | return to_std_error(ret); | 
|  | } | 
|  | } | 
|  |  | 
|  | edma_dump_channel_registers(data, chan_id); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int edma_start(const struct device *dev, uint32_t chan_id) | 
|  | { | 
|  | struct edma_data *data; | 
|  | const struct edma_config *cfg; | 
|  | struct edma_channel *chan; | 
|  | int ret; | 
|  |  | 
|  | data = dev->data; | 
|  | cfg = dev->config; | 
|  |  | 
|  | /* fetch channel data */ | 
|  | chan = lookup_channel(dev, chan_id); | 
|  | if (!chan) { | 
|  | LOG_ERR("channel ID %u is not valid", chan_id); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* change channel's state to STARTED */ | 
|  | ret = channel_change_state(chan, CHAN_STATE_STARTED); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("failed to change channel %d state to STARTED", chan_id); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | LOG_DBG("starting channel %u", chan_id); | 
|  |  | 
|  | /* enable HW requests */ | 
|  | EDMA_ChannelRegUpdate(data->hal_cfg, chan_id, | 
|  | EDMA_TCD_CH_CSR, EDMA_TCD_CH_CSR_ERQ_MASK, 0); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int edma_reload(const struct device *dev, uint32_t chan_id, uint32_t src, | 
|  | uint32_t dst, size_t size) | 
|  | { | 
|  | struct edma_data *data; | 
|  | struct edma_channel *chan; | 
|  | int ret; | 
|  | unsigned int key; | 
|  |  | 
|  | data = dev->data; | 
|  |  | 
|  | /* fetch channel data */ | 
|  | chan = lookup_channel(dev, chan_id); | 
|  | if (!chan) { | 
|  | LOG_ERR("channel ID %u is not valid", chan_id); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* channel needs to be started to allow reloading */ | 
|  | if (chan->state != CHAN_STATE_STARTED) { | 
|  | LOG_ERR("reload is only supported on started channels"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (chan->cyclic_buffer) { | 
|  | key = irq_lock(); | 
|  | ret = EDMA_CHAN_PRODUCE_CONSUME_B(chan, size); | 
|  | irq_unlock(key); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("chan %d buffer overflow/underrun", chan_id); | 
|  | return ret; | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int edma_get_attribute(const struct device *dev, uint32_t type, uint32_t *val) | 
|  | { | 
|  | switch (type) { | 
|  | case DMA_ATTR_BUFFER_SIZE_ALIGNMENT: | 
|  | case DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT: | 
|  | *val = CONFIG_DMA_NXP_EDMA_ALIGN; | 
|  | break; | 
|  | case DMA_ATTR_MAX_BLOCK_COUNT: | 
|  | /* this is restricted to 1 because SG configurations are not supported */ | 
|  | *val = 1; | 
|  | break; | 
|  | default: | 
|  | LOG_ERR("invalid attribute type: %d", type); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct dma_driver_api edma_api = { | 
|  | .reload = edma_reload, | 
|  | .config = edma_config, | 
|  | .start = edma_start, | 
|  | .stop = edma_stop, | 
|  | .suspend = edma_suspend, | 
|  | .resume = edma_start, | 
|  | .get_status = edma_get_status, | 
|  | .get_attribute = edma_get_attribute, | 
|  | }; | 
|  |  | 
|  | static int edma_init(const struct device *dev) | 
|  | { | 
|  | const struct edma_config *cfg; | 
|  | struct edma_data *data; | 
|  | mm_reg_t regmap; | 
|  |  | 
|  | data = dev->data; | 
|  | cfg = dev->config; | 
|  |  | 
|  | /* map instance MMIO */ | 
|  | device_map(®map, cfg->regmap_phys, cfg->regmap_size, K_MEM_CACHE_NONE); | 
|  |  | 
|  | /* overwrite physical address set in the HAL configuration. | 
|  | * We can down-cast the virtual address to a 32-bit address because | 
|  | * we know we're working with 32-bit addresses only. | 
|  | */ | 
|  | data->hal_cfg->regmap = (uint32_t)POINTER_TO_UINT(regmap); | 
|  |  | 
|  | cfg->irq_config(); | 
|  |  | 
|  | /* dma_request_channel() uses this variable to keep track of the | 
|  | * available channels. As such, it needs to be initialized with NULL | 
|  | * which signifies that all channels are initially available. | 
|  | */ | 
|  | data->channel_flags = ATOMIC_INIT(0); | 
|  | data->ctx.atomic = &data->channel_flags; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* a few comments about the BUILD_ASSERT statements: | 
|  | *	1) dma-channels and valid-channels should be mutually exclusive. | 
|  | *	This means that you specify the one or the other. There's no real | 
|  | *	need to have both of them. | 
|  | *	2) Number of channels should match the number of interrupts for | 
|  | *	said channels (TODO: what about error interrupts?) | 
|  | *	3) The channel-mux property shouldn't be specified unless | 
|  | *	the eDMA is MUX-capable (signaled via the EDMA_HAS_CHAN_MUX | 
|  | *	configuration). | 
|  | */ | 
|  | #define EDMA_INIT(inst)								\ | 
|  | \ | 
|  | BUILD_ASSERT(!DT_NODE_HAS_PROP(DT_INST(inst, DT_DRV_COMPAT), dma_channels) ||	\ | 
|  | !DT_NODE_HAS_PROP(DT_INST(inst, DT_DRV_COMPAT), valid_channels),	\ | 
|  | "dma_channels and valid_channels are mutually exclusive");		\ | 
|  | \ | 
|  | BUILD_ASSERT(DT_INST_PROP_OR(inst, dma_channels, 0) ==				\ | 
|  | DT_NUM_IRQS(DT_INST(inst, DT_DRV_COMPAT)) ||			\ | 
|  | DT_INST_PROP_LEN_OR(inst, valid_channels, 0) ==			\ | 
|  | DT_NUM_IRQS(DT_INST(inst, DT_DRV_COMPAT)),				\ | 
|  | "number of interrupts needs to match number of channels");		\ | 
|  | \ | 
|  | BUILD_ASSERT(DT_PROP_OR(DT_INST(inst, DT_DRV_COMPAT), hal_cfg_index, 0) <	\ | 
|  | ARRAY_SIZE(s_edmaConfigs),						\ | 
|  | "HAL configuration index out of bounds");				\ | 
|  | \ | 
|  | static struct edma_channel channels_##inst[] = EDMA_CHANNEL_ARRAY_GET(inst);	\ | 
|  | \ | 
|  | static void interrupt_config_function_##inst(void)				\ | 
|  | {										\ | 
|  | EDMA_CONNECT_INTERRUPTS(inst);						\ | 
|  | }										\ | 
|  | \ | 
|  | static struct edma_config edma_config_##inst = {				\ | 
|  | .regmap_phys = DT_INST_REG_ADDR(inst),					\ | 
|  | .regmap_size = DT_INST_REG_SIZE(inst),					\ | 
|  | .irq_config = interrupt_config_function_##inst,				\ | 
|  | .contiguous_channels = EDMA_CHANS_ARE_CONTIGUOUS(inst),			\ | 
|  | };										\ | 
|  | \ | 
|  | static struct edma_data edma_data_##inst = {					\ | 
|  | .channels = channels_##inst,						\ | 
|  | .ctx.dma_channels = ARRAY_SIZE(channels_##inst),			\ | 
|  | .ctx.magic = DMA_MAGIC,							\ | 
|  | .hal_cfg = &EDMA_HAL_CFG_GET(inst),					\ | 
|  | };										\ | 
|  | \ | 
|  | DEVICE_DT_INST_DEFINE(inst, &edma_init, NULL,					\ | 
|  | &edma_data_##inst, &edma_config_##inst,			\ | 
|  | PRE_KERNEL_1, CONFIG_DMA_INIT_PRIORITY,			\ | 
|  | &edma_api);						\ | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(EDMA_INIT); |