| /* |
| * Copyright 2023 NXP |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/drivers/dma.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/cache.h> |
| |
| /* used for driver binding */ |
| #define DT_DRV_COMPAT nxp_sof_host_dma |
| |
| /* macros used to parse DTS properties */ |
| #define IDENTITY_VARGS(V, ...) IDENTITY(V) |
| |
| #define _SOF_HOST_DMA_CHANNEL_INDEX_ARRAY(inst)\ |
| LISTIFY(DT_INST_PROP_OR(inst, dma_channels, 0), IDENTITY_VARGS, (,)) |
| |
| #define _SOF_HOST_DMA_CHANNEL_DECLARE(idx) {} |
| |
| #define SOF_HOST_DMA_CHANNELS_DECLARE(inst)\ |
| FOR_EACH(_SOF_HOST_DMA_CHANNEL_DECLARE,\ |
| (,), _SOF_HOST_DMA_CHANNEL_INDEX_ARRAY(inst)) |
| |
| LOG_MODULE_REGISTER(nxp_sof_host_dma); |
| |
| /* note: This driver doesn't attempt to provide |
| * a generic software-based DMA engine implementation. |
| * As its name suggests, its only usage is in SOF |
| * (Sound Open Firmware) for NXP plaforms which are |
| * able to access the host memory directly from the |
| * core on which the firmware is running. |
| */ |
| |
| enum channel_state { |
| CHAN_STATE_INIT = 0, |
| CHAN_STATE_CONFIGURED, |
| }; |
| |
| struct sof_host_dma_channel { |
| uint32_t src; |
| uint32_t dest; |
| uint32_t size; |
| uint32_t direction; |
| enum channel_state state; |
| }; |
| |
| struct sof_host_dma_data { |
| /* this needs to be first */ |
| struct dma_context ctx; |
| atomic_t channel_flags; |
| struct sof_host_dma_channel *channels; |
| }; |
| |
| static int channel_change_state(struct sof_host_dma_channel *chan, |
| enum channel_state next) |
| { |
| enum channel_state prev = chan->state; |
| |
| /* validate transition */ |
| switch (prev) { |
| case CHAN_STATE_INIT: |
| case CHAN_STATE_CONFIGURED: |
| if (next != CHAN_STATE_CONFIGURED) { |
| return -EPERM; |
| } |
| break; |
| default: |
| LOG_ERR("invalid channel previous state: %d", prev); |
| return -EINVAL; |
| } |
| |
| chan->state = next; |
| |
| return 0; |
| } |
| |
| static int sof_host_dma_reload(const struct device *dev, uint32_t chan_id, |
| uint32_t src, uint32_t dst, size_t size) |
| { |
| ARG_UNUSED(src); |
| ARG_UNUSED(dst); |
| ARG_UNUSED(size); |
| |
| struct sof_host_dma_data *data; |
| struct sof_host_dma_channel *chan; |
| int ret; |
| |
| data = dev->data; |
| |
| if (chan_id >= data->ctx.dma_channels) { |
| LOG_ERR("channel %d is not a valid channel ID", chan_id); |
| return -EINVAL; |
| } |
| |
| /* fetch channel data */ |
| chan = &data->channels[chan_id]; |
| |
| /* validate state */ |
| if (chan->state != CHAN_STATE_CONFIGURED) { |
| LOG_ERR("attempting to reload unconfigured DMA channel %d", chan_id); |
| return -EINVAL; |
| } |
| |
| if (chan->direction == HOST_TO_MEMORY) { |
| /* the host may have modified the region we're about to copy |
| * to local memory. In this case, the data cache holds stale |
| * data so invalidate it to force a read from the main memory. |
| */ |
| ret = sys_cache_data_invd_range(UINT_TO_POINTER(chan->src), |
| chan->size); |
| if (ret < 0) { |
| LOG_ERR("failed to invalidate data cache range"); |
| return ret; |
| } |
| } |
| |
| memcpy(UINT_TO_POINTER(chan->dest), UINT_TO_POINTER(chan->src), chan->size); |
| |
| if (chan->direction == MEMORY_TO_HOST) { |
| /* force range to main memory so that host doesn't read any |
| * stale data. |
| */ |
| ret = sys_cache_data_flush_range(UINT_TO_POINTER(chan->dest), |
| chan->size); |
| if (ret < 0) { |
| LOG_ERR("failed to flush data cache range"); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| |
| static int sof_host_dma_config(const struct device *dev, uint32_t chan_id, |
| struct dma_config *config) |
| { |
| struct sof_host_dma_data *data; |
| struct sof_host_dma_channel *chan; |
| int ret; |
| |
| data = dev->data; |
| |
| if (chan_id >= data->ctx.dma_channels) { |
| LOG_ERR("channel %d is not a valid channel ID", chan_id); |
| return -EINVAL; |
| } |
| |
| /* fetch channel data */ |
| chan = &data->channels[chan_id]; |
| |
| /* attempt a state transition */ |
| ret = channel_change_state(chan, CHAN_STATE_CONFIGURED); |
| if (ret < 0) { |
| LOG_ERR("failed to change channel %d's state to CONFIGURED", chan_id); |
| return ret; |
| } |
| |
| /* SG configurations are not currently supported */ |
| if (config->block_count != 1) { |
| LOG_ERR("invalid number of blocks: %d", config->block_count); |
| return -EINVAL; |
| } |
| |
| if (!config->head_block->source_address) { |
| LOG_ERR("got NULL source address"); |
| return -EINVAL; |
| } |
| |
| if (!config->head_block->dest_address) { |
| LOG_ERR("got NULL destination address"); |
| return -EINVAL; |
| } |
| |
| if (!config->head_block->block_size) { |
| LOG_ERR("got 0 bytes to copy"); |
| return -EINVAL; |
| } |
| |
| /* for now, only H2M and M2H transfers are supported */ |
| if (config->channel_direction != HOST_TO_MEMORY && |
| config->channel_direction != MEMORY_TO_HOST) { |
| LOG_ERR("invalid channel direction: %d", |
| config->channel_direction); |
| return -EINVAL; |
| } |
| |
| /* latch onto the passed configuration */ |
| chan->src = config->head_block->source_address; |
| chan->dest = config->head_block->dest_address; |
| chan->size = config->head_block->block_size; |
| chan->direction = config->channel_direction; |
| |
| LOG_DBG("configured channel %d with SRC 0x%x DST 0x%x SIZE 0x%x", |
| chan_id, chan->src, chan->dest, chan->size); |
| |
| return 0; |
| } |
| |
| static int sof_host_dma_start(const struct device *dev, uint32_t chan_id) |
| { |
| /* nothing to be done here */ |
| return 0; |
| } |
| |
| static int sof_host_dma_stop(const struct device *dev, uint32_t chan_id) |
| { |
| /* nothing to be done here */ |
| return 0; |
| } |
| |
| static int sof_host_dma_suspend(const struct device *dev, uint32_t chan_id) |
| { |
| /* nothing to be done here */ |
| return 0; |
| } |
| |
| static int sof_host_dma_resume(const struct device *dev, uint32_t chan_id) |
| { |
| /* nothing to be done here */ |
| return 0; |
| } |
| |
| static int sof_host_dma_get_status(const struct device *dev, |
| uint32_t chan_id, struct dma_status *stat) |
| { |
| /* nothing to be done here */ |
| return 0; |
| } |
| |
| static int sof_host_dma_get_attribute(const struct device *dev, uint32_t type, uint32_t *val) |
| { |
| switch (type) { |
| case DMA_ATTR_COPY_ALIGNMENT: |
| case DMA_ATTR_BUFFER_SIZE_ALIGNMENT: |
| case DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT: |
| *val = CONFIG_DMA_NXP_SOF_HOST_DMA_ALIGN; |
| break; |
| default: |
| LOG_ERR("invalid attribute type: %d", type); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static const struct dma_driver_api sof_host_dma_api = { |
| .reload = sof_host_dma_reload, |
| .config = sof_host_dma_config, |
| .start = sof_host_dma_start, |
| .stop = sof_host_dma_stop, |
| .suspend = sof_host_dma_suspend, |
| .resume = sof_host_dma_resume, |
| .get_status = sof_host_dma_get_status, |
| .get_attribute = sof_host_dma_get_attribute, |
| }; |
| |
| static int sof_host_dma_init(const struct device *dev) |
| { |
| struct sof_host_dma_data *data = dev->data; |
| |
| data->channel_flags = ATOMIC_INIT(0); |
| data->ctx.atomic = &data->channel_flags; |
| |
| return 0; |
| } |
| |
| static struct sof_host_dma_channel channels[] = { |
| SOF_HOST_DMA_CHANNELS_DECLARE(0), |
| }; |
| |
| static struct sof_host_dma_data sof_host_dma_data = { |
| .ctx.magic = DMA_MAGIC, |
| .ctx.dma_channels = ARRAY_SIZE(channels), |
| .channels = channels, |
| }; |
| |
| /* assumption: only 1 SOF_HOST_DMA instance */ |
| DEVICE_DT_INST_DEFINE(0, sof_host_dma_init, NULL, |
| &sof_host_dma_data, NULL, |
| PRE_KERNEL_1, CONFIG_DMA_INIT_PRIORITY, |
| &sof_host_dma_api); |