zephyr/drivers/dma/dma_nxp_sof_host_dma.c
Laurentiu Mihalcea 43a0839c6c drivers: dma: Add SOF host DMA driver
This commit introduces the SOF host DMA driver.
This driver is used by NXP platforms in the context of
SOF's host component to copy data from the host memory
to the firmware (local) memory. This is possible because
NXP platforms can access the host memory directly w/o
an actual DMA engine.

Signed-off-by: Laurentiu Mihalcea <laurentiu.mihalcea@nxp.com>
2023-11-20 09:19:53 +01:00

285 lines
6.7 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);
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);