| /* |
| * Copyright 2024 NXP |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #ifndef ZEPHYR_DRIVERS_DMA_DMA_NXP_EDMA_H_ |
| #define ZEPHYR_DRIVERS_DMA_DMA_NXP_EDMA_H_ |
| |
| #include <zephyr/device.h> |
| #include <zephyr/irq.h> |
| #include <zephyr/drivers/dma.h> |
| #include <zephyr/logging/log.h> |
| |
| #include "fsl_edma_soc_rev2.h" |
| |
| LOG_MODULE_REGISTER(nxp_edma); |
| |
| /* used for driver binding */ |
| #define DT_DRV_COMPAT nxp_edma |
| |
| /* workaround the fact that device_map() is not defined for SoCs with no MMU */ |
| #ifndef DEVICE_MMIO_IS_IN_RAM |
| #define device_map(virt, phys, size, flags) *(virt) = (phys) |
| #endif /* DEVICE_MMIO_IS_IN_RAM */ |
| |
| /* macros used to parse DTS properties */ |
| |
| /* used in conjunction with LISTIFY which expects F to also take a variable |
| * number of arguments. Since IDENTITY doesn't do that we need to use a version |
| * of it which also takes a variable number of arguments. |
| */ |
| #define IDENTITY_VARGS(V, ...) IDENTITY(V) |
| |
| /* used to generate an array of indexes for the channels */ |
| #define _EDMA_CHANNEL_INDEX_ARRAY(inst)\ |
| LISTIFY(DT_INST_PROP_LEN_OR(inst, valid_channels, 0), IDENTITY_VARGS, (,)) |
| |
| /* used to generate an array of indexes for the channels - this is different |
| * from _EDMA_CHANNEL_INDEX_ARRAY because the number of channels is passed |
| * explicitly through dma-channels so no need to deduce it from the length |
| * of the valid-channels property. |
| */ |
| #define _EDMA_CHANNEL_INDEX_ARRAY_EXPLICIT(inst)\ |
| LISTIFY(DT_INST_PROP_OR(inst, dma_channels, 0), IDENTITY_VARGS, (,)) |
| |
| /* used to generate an array of indexes for the interrupt */ |
| #define _EDMA_INT_INDEX_ARRAY(inst)\ |
| LISTIFY(DT_NUM_IRQS(DT_INST(inst, DT_DRV_COMPAT)), IDENTITY_VARGS, (,)) |
| |
| /* used to register an ISR/arg pair. TODO: should we also use the priority? */ |
| #define _EDMA_INT_CONNECT(idx, inst) \ |
| IRQ_CONNECT(DT_INST_IRQN_BY_IDX(inst, idx), \ |
| 0, edma_isr, \ |
| &channels_##inst[idx], 0) |
| |
| /* used to declare a struct edma_channel by the non-explicit macro suite */ |
| #define _EDMA_CHANNEL_DECLARE(idx, inst) \ |
| { \ |
| .id = DT_INST_PROP_BY_IDX(inst, valid_channels, idx), \ |
| .dev = DEVICE_DT_INST_GET(inst), \ |
| .irq = DT_INST_IRQN_BY_IDX(inst, idx), \ |
| } |
| |
| /* used to declare a struct edma_channel by the explicit macro suite */ |
| #define _EDMA_CHANNEL_DECLARE_EXPLICIT(idx, inst) \ |
| { \ |
| .id = idx, \ |
| .dev = DEVICE_DT_INST_GET(inst), \ |
| .irq = DT_INST_IRQN_BY_IDX(inst, idx), \ |
| } |
| |
| /* used to create an array of channel IDs via the valid-channels property */ |
| #define _EDMA_CHANNEL_ARRAY(inst) \ |
| { FOR_EACH_FIXED_ARG(_EDMA_CHANNEL_DECLARE, (,), \ |
| inst, _EDMA_CHANNEL_INDEX_ARRAY(inst)) } |
| |
| /* used to create an array of channel IDs via the dma-channels property */ |
| #define _EDMA_CHANNEL_ARRAY_EXPLICIT(inst) \ |
| { FOR_EACH_FIXED_ARG(_EDMA_CHANNEL_DECLARE_EXPLICIT, (,), inst, \ |
| _EDMA_CHANNEL_INDEX_ARRAY_EXPLICIT(inst)) } |
| |
| /* used to construct the channel array based on the specified property: |
| * dma-channels or valid-channels. |
| */ |
| #define EDMA_CHANNEL_ARRAY_GET(inst) \ |
| COND_CODE_1(DT_NODE_HAS_PROP(DT_INST(inst, DT_DRV_COMPAT), dma_channels), \ |
| (_EDMA_CHANNEL_ARRAY_EXPLICIT(inst)), \ |
| (_EDMA_CHANNEL_ARRAY(inst))) |
| |
| #define EDMA_HAL_CFG_GET(inst) \ |
| COND_CODE_1(DT_NODE_HAS_PROP(DT_INST(inst, DT_DRV_COMPAT), hal_cfg_index), \ |
| (s_edmaConfigs[DT_INST_PROP(inst, hal_cfg_index)]), \ |
| (s_edmaConfigs[0])) |
| |
| /* used to register edma_isr for all specified interrupts */ |
| #define EDMA_CONNECT_INTERRUPTS(inst) \ |
| FOR_EACH_FIXED_ARG(_EDMA_INT_CONNECT, (;), \ |
| inst, _EDMA_INT_INDEX_ARRAY(inst)) |
| |
| #define EDMA_CHANS_ARE_CONTIGUOUS(inst)\ |
| DT_NODE_HAS_PROP(DT_INST(inst, DT_DRV_COMPAT), dma_channels) |
| |
| /* utility macros */ |
| |
| /* a few words about EDMA_CHAN_PRODUCE_CONSUME_{A/B}: |
| * - in the context of cyclic buffers we introduce |
| * the concepts of consumer and producer channels. |
| * |
| * - a consumer channel is a channel for which the |
| * DMA copies data from a buffer, thus leading to |
| * less data in said buffer (data is consumed with |
| * each transfer). |
| * |
| * - a producer channel is a channel for which the |
| * DMA copies data into a buffer, thus leading to |
| * more data in said buffer (data is produced with |
| * each transfer). |
| * |
| * - for consumer channels, each DMA interrupt will |
| * signal that an amount of data has been consumed |
| * from the buffer (half of the buffer size if |
| * HALFMAJOR is enabled, the whole buffer otherwise). |
| * |
| * - for producer channels, each DMA interrupt will |
| * signal that an amount of data has been added |
| * to the buffer. |
| * |
| * - to signal this, the ISR uses EDMA_CHAN_PRODUCE_CONSUME_A |
| * which will "consume" data from the buffer for |
| * consumer channels and "produce" data for |
| * producer channels. |
| * |
| * - since the upper layers using this driver need |
| * to let the EDMA driver know whenever they've produced |
| * (in the case of consumer channels) or consumed |
| * data (in the case of producer channels) they can |
| * do so through the reload() function. |
| * |
| * - reload() uses EDMA_CHAN_PRODUCE_CONSUME_B which |
| * for consumer channels will "produce" data and |
| * "consume" data for producer channels, thus letting |
| * the driver know what action the upper layer has |
| * performed (if the channel is a consumer it's only |
| * natural that the upper layer will write/produce more |
| * data to the buffer. The same rationale applies to |
| * producer channels). |
| * |
| * - EDMA_CHAN_PRODUCE_CONSUME_B is just the opposite |
| * of EDMA_CHAN_PRODUCE_CONSUME_A. If one produces |
| * data, the other will consume and vice-versa. |
| * |
| * - all of this information is valid only in the |
| * context of cyclic buffers. If this behaviour is |
| * not enabled, querying the status will simply |
| * resolve to querying CITER and BITER. |
| */ |
| #define EDMA_CHAN_PRODUCE_CONSUME_A(chan, size)\ |
| ((chan)->type == CHAN_TYPE_CONSUMER ?\ |
| edma_chan_cyclic_consume(chan, size) :\ |
| edma_chan_cyclic_produce(chan, size)) |
| |
| #define EDMA_CHAN_PRODUCE_CONSUME_B(chan, size)\ |
| ((chan)->type == CHAN_TYPE_CONSUMER ?\ |
| edma_chan_cyclic_produce(chan, size) :\ |
| edma_chan_cyclic_consume(chan, size)) |
| |
| enum channel_type { |
| CHAN_TYPE_CONSUMER = 0, |
| CHAN_TYPE_PRODUCER, |
| }; |
| |
| enum channel_state { |
| CHAN_STATE_INIT = 0, |
| CHAN_STATE_CONFIGURED, |
| CHAN_STATE_STARTED, |
| CHAN_STATE_STOPPED, |
| CHAN_STATE_SUSPENDED, |
| }; |
| |
| struct edma_channel { |
| /* channel ID, needs to be the same as the hardware channel ID */ |
| uint32_t id; |
| /* pointer to device representing the EDMA instance, used by edma_isr */ |
| const struct device *dev; |
| /* current state of the channel */ |
| enum channel_state state; |
| /* type of the channel (PRODUCER/CONSUMER) - only applicable to cyclic |
| * buffer configurations. |
| */ |
| enum channel_type type; |
| /* argument passed to the user-defined DMA callback */ |
| void *arg; |
| /* user-defined callback, called at the end of a channel's interrupt |
| * handling. |
| */ |
| dma_callback_t cb; |
| /* INTID associated with the channel */ |
| int irq; |
| /* the channel's status */ |
| struct dma_status stat; |
| /* cyclic buffer size - currently, this is set to head_block's size */ |
| uint32_t bsize; |
| /* set to true if the channel uses a cyclic buffer configuration */ |
| bool cyclic_buffer; |
| }; |
| |
| struct edma_data { |
| /* this needs to be the first member */ |
| struct dma_context ctx; |
| mm_reg_t regmap; |
| struct edma_channel *channels; |
| atomic_t channel_flags; |
| edma_config_t *hal_cfg; |
| }; |
| |
| struct edma_config { |
| uint32_t regmap_phys; |
| uint32_t regmap_size; |
| void (*irq_config)(void); |
| /* true if channels are contiguous. The channels may not be contiguous |
| * if the valid-channels property is used instead of dma-channels. This |
| * is used to improve the time complexity of the channel lookup |
| * function. |
| */ |
| bool contiguous_channels; |
| }; |
| |
| static inline int channel_change_state(struct edma_channel *chan, |
| enum channel_state next) |
| { |
| enum channel_state prev = chan->state; |
| |
| LOG_DBG("attempting to change state from %d to %d for channel %d", prev, next, chan->id); |
| |
| /* validate transition */ |
| switch (prev) { |
| case CHAN_STATE_INIT: |
| if (next != CHAN_STATE_CONFIGURED) { |
| return -EPERM; |
| } |
| break; |
| case CHAN_STATE_CONFIGURED: |
| if (next != CHAN_STATE_STARTED && |
| next != CHAN_STATE_CONFIGURED) { |
| return -EPERM; |
| } |
| break; |
| case CHAN_STATE_STARTED: |
| if (next != CHAN_STATE_STOPPED && |
| next != CHAN_STATE_SUSPENDED) { |
| return -EPERM; |
| } |
| break; |
| case CHAN_STATE_STOPPED: |
| if (next != CHAN_STATE_CONFIGURED) { |
| return -EPERM; |
| } |
| break; |
| case CHAN_STATE_SUSPENDED: |
| if (next != CHAN_STATE_STARTED && |
| next != CHAN_STATE_STOPPED) { |
| return -EPERM; |
| } |
| break; |
| default: |
| LOG_ERR("invalid channel previous state: %d", prev); |
| return -EINVAL; |
| } |
| |
| /* transition OK, proceed */ |
| chan->state = next; |
| |
| return 0; |
| } |
| |
| static inline int get_transfer_type(enum dma_channel_direction dir, uint32_t *type) |
| { |
| switch (dir) { |
| case MEMORY_TO_MEMORY: |
| *type = kEDMA_TransferTypeM2M; |
| break; |
| case MEMORY_TO_PERIPHERAL: |
| *type = kEDMA_TransferTypeM2P; |
| break; |
| case PERIPHERAL_TO_MEMORY: |
| *type = kEDMA_TransferTypeP2M; |
| break; |
| default: |
| LOG_ERR("invalid channel direction: %d", dir); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static inline bool data_size_is_valid(uint16_t size) |
| { |
| switch (size) { |
| case 1: |
| case 2: |
| case 4: |
| case 8: |
| case 16: |
| case 32: |
| case 64: |
| break; |
| default: |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* TODO: we may require setting the channel type through DTS |
| * or through struct dma_config. For now, we'll only support |
| * MEMORY_TO_PERIPHERAL and PERIPHERAL_TO_MEMORY directions |
| * and assume that these are bound to a certain channel type. |
| */ |
| static inline int edma_set_channel_type(struct edma_channel *chan, |
| enum dma_channel_direction dir) |
| { |
| switch (dir) { |
| case MEMORY_TO_PERIPHERAL: |
| chan->type = CHAN_TYPE_CONSUMER; |
| break; |
| case PERIPHERAL_TO_MEMORY: |
| chan->type = CHAN_TYPE_PRODUCER; |
| break; |
| default: |
| LOG_ERR("unsupported transfer direction: %d", dir); |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| /* this function is used in cyclic buffer configurations. What it does |
| * is it updates the channel's read position based on the number of |
| * bytes requested. If the number of bytes that's being read is higher |
| * than the number of bytes available in the buffer (pending_length) |
| * this will lead to an error. The main point of this check is to |
| * provide a way for the user to determine if data is consumed at a |
| * higher rate than it is being produced. |
| * |
| * This function is used in edma_isr() for CONSUMER channels to mark |
| * that data has been consumed (i.e: data has been transferred to the |
| * destination) (this is done via EDMA_CHAN_PRODUCE_CONSUME_A that's |
| * called in edma_isr()). For producer channels, this function is used |
| * in edma_reload() to mark the fact that the user of the EDMA driver |
| * has consumed data. |
| */ |
| static inline int edma_chan_cyclic_consume(struct edma_channel *chan, |
| uint32_t bytes) |
| { |
| if (bytes > chan->stat.pending_length) { |
| return -EINVAL; |
| } |
| |
| chan->stat.read_position = |
| (chan->stat.read_position + bytes) % chan->bsize; |
| |
| if (chan->stat.read_position > chan->stat.write_position) { |
| chan->stat.free = chan->stat.read_position - |
| chan->stat.write_position; |
| } else if (chan->stat.read_position == chan->stat.write_position) { |
| chan->stat.free = chan->bsize; |
| } else { |
| chan->stat.free = chan->bsize - |
| (chan->stat.write_position - chan->stat.read_position); |
| } |
| |
| chan->stat.pending_length = chan->bsize - chan->stat.free; |
| |
| return 0; |
| } |
| |
| /* this function is used in cyclic buffer configurations. What it does |
| * is it updates the channel's write position based on the number of |
| * bytes requested. If the number of bytes that's being written is higher |
| * than the number of free bytes in the buffer this will lead to an error. |
| * The main point of this check is to provide a way for the user to determine |
| * if data is produced at a higher rate than it is being consumed. |
| * |
| * This function is used in edma_isr() for PRODUCER channels to mark |
| * that data has been produced (i.e: data has been transferred to the |
| * destination) (this is done via EDMA_CHAN_PRODUCE_CONSUME_A that's |
| * called in edma_isr()). For consumer channels, this function is used |
| * in edma_reload() to mark the fact that the user of the EDMA driver |
| * has produced data. |
| */ |
| static inline int edma_chan_cyclic_produce(struct edma_channel *chan, |
| uint32_t bytes) |
| { |
| if (bytes > chan->stat.free) { |
| return -EINVAL; |
| } |
| |
| chan->stat.write_position = |
| (chan->stat.write_position + bytes) % chan->bsize; |
| |
| if (chan->stat.write_position > chan->stat.read_position) { |
| chan->stat.pending_length = chan->stat.write_position - |
| chan->stat.read_position; |
| } else if (chan->stat.write_position == chan->stat.read_position) { |
| chan->stat.pending_length = chan->bsize; |
| } else { |
| chan->stat.pending_length = chan->bsize - |
| (chan->stat.read_position - chan->stat.write_position); |
| } |
| |
| chan->stat.free = chan->bsize - chan->stat.pending_length; |
| |
| return 0; |
| } |
| |
| static inline void edma_dump_channel_registers(struct edma_data *data, |
| uint32_t chan_id) |
| { |
| LOG_DBG("dumping channel data for channel %d", chan_id); |
| |
| LOG_DBG("CH_CSR: 0x%x", |
| EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_CH_CSR)); |
| LOG_DBG("CH_ES: 0x%x", |
| EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_CH_ES)); |
| LOG_DBG("CH_INT: 0x%x", |
| EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_CH_INT)); |
| LOG_DBG("CH_SBR: 0x%x", |
| EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_CH_SBR)); |
| LOG_DBG("CH_PRI: 0x%x", |
| EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_CH_PRI)); |
| |
| if (EDMA_HAS_MUX(data->hal_cfg)) { |
| LOG_DBG("CH_MUX: 0x%x", |
| EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_CH_MUX)); |
| } |
| |
| LOG_DBG("TCD_SADDR: 0x%x", |
| EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_SADDR)); |
| LOG_DBG("TCD_SOFF: 0x%x", |
| EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_SOFF)); |
| LOG_DBG("TCD_ATTR: 0x%x", |
| EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_ATTR)); |
| LOG_DBG("TCD_NBYTES: 0x%x", |
| EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_NBYTES)); |
| LOG_DBG("TCD_SLAST_SDA: 0x%x", |
| EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_SLAST_SDA)); |
| LOG_DBG("TCD_DADDR: 0x%x", |
| EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_DADDR)); |
| LOG_DBG("TCD_DOFF: 0x%x", |
| EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_DOFF)); |
| LOG_DBG("TCD_CITER: 0x%x", |
| EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_CITER)); |
| LOG_DBG("TCD_DLAST_SGA: 0x%x", |
| EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_DLAST_SGA)); |
| LOG_DBG("TCD_CSR: 0x%x", |
| EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_CSR)); |
| LOG_DBG("TCD_BITER: 0x%x", |
| EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_BITER)); |
| } |
| |
| static inline int set_slast_dlast(struct dma_config *dma_cfg, |
| uint32_t transfer_type, |
| struct edma_data *data, |
| uint32_t chan_id) |
| { |
| int32_t slast, dlast; |
| |
| if (transfer_type == kEDMA_TransferTypeP2M) { |
| slast = 0; |
| } else { |
| switch (dma_cfg->head_block->source_addr_adj) { |
| case DMA_ADDR_ADJ_INCREMENT: |
| slast = (int32_t)dma_cfg->head_block->block_size; |
| break; |
| case DMA_ADDR_ADJ_DECREMENT: |
| slast = (-1) * (int32_t)dma_cfg->head_block->block_size; |
| break; |
| default: |
| LOG_ERR("unsupported SADDR adjustment: %d", |
| dma_cfg->head_block->source_addr_adj); |
| return -EINVAL; |
| } |
| } |
| |
| if (transfer_type == kEDMA_TransferTypeM2P) { |
| dlast = 0; |
| } else { |
| switch (dma_cfg->head_block->dest_addr_adj) { |
| case DMA_ADDR_ADJ_INCREMENT: |
| dlast = (int32_t)dma_cfg->head_block->block_size; |
| break; |
| case DMA_ADDR_ADJ_DECREMENT: |
| dlast = (-1) * (int32_t)dma_cfg->head_block->block_size; |
| break; |
| default: |
| LOG_ERR("unsupported DADDR adjustment: %d", |
| dma_cfg->head_block->dest_addr_adj); |
| return -EINVAL; |
| } |
| } |
| |
| LOG_DBG("attempting to commit SLAST %d", slast); |
| LOG_DBG("attempting to commit DLAST %d", dlast); |
| |
| /* commit configuration */ |
| EDMA_ChannelRegWrite(data->hal_cfg, chan_id, EDMA_TCD_SLAST_SDA, slast); |
| EDMA_ChannelRegWrite(data->hal_cfg, chan_id, EDMA_TCD_DLAST_SGA, dlast); |
| |
| return 0; |
| } |
| |
| /* the NXP HAL EDMA driver uses some custom return values |
| * that need to be converted to standard error codes. This function |
| * performs exactly this translation. |
| */ |
| static inline int to_std_error(int edma_err) |
| { |
| switch (edma_err) { |
| case kStatus_EDMA_InvalidConfiguration: |
| case kStatus_InvalidArgument: |
| return -EINVAL; |
| case kStatus_Busy: |
| return -EBUSY; |
| default: |
| LOG_ERR("unknown EDMA error code: %d", edma_err); |
| return -EINVAL; |
| } |
| } |
| |
| #endif /* ZEPHYR_DRIVERS_DMA_DMA_NXP_EDMA_H_ */ |