zephyr/drivers/dma/dma_nxp_sof_host_dma.c

310 lines
7.8 KiB
C

/*
* 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);
/*
* MEMORY_TO_HOST transfer: force range to main memory so that
* the host doesn't read any stale data.
*
* HOST_TO_MEMORY transfer:
* SOF assumes that data is copied from host to local memory via
* DMA, which is not the case for imx platforms. For these
* platforms, the DSP is in charge of copying the data from host to
* local memory.
*
* Additionally, because of the aforementioned assumption,
* SOF performs a cache invalidation on the destination
* memory chunk before data is copied further down the
* pipeline.
*
* If the destination memory chunk is cacheable what seems
* to happen is that the invalidation operation forces the
* DSP to fetch the data from RAM instead of the cache.
* Since a writeback was never performed on the destination
* memory chunk, the RAM will contain stale data.
*
* With this in mind, the writeback should also be
* performed in HOST_TO_MEMORY transfers (aka playback)
* to keep the cache and RAM in sync. This way, the DSP
* will read the correct data from RAM (when forced to do
* so by the cache invalidation operation).
*
* TODO: this is NOT optimal since we perform two unneeded
* cache management operations and should be addressed in
* SOF at some point.
*/
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);