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>
This commit is contained in:
Laurentiu Mihalcea 2023-10-03 11:10:34 +03:00 committed by Carles Cufí
parent 8b2c5120aa
commit 43a0839c6c
5 changed files with 334 additions and 0 deletions

View file

@ -37,3 +37,4 @@ zephyr_library_sources_ifdef(CONFIG_DMA_MCUX_SMARTDMA dma_mcux_smartdma.c)
zephyr_library_sources_ifdef(CONFIG_DMA_ANDES_ATCDMAC300 dma_andes_atcdmac300.c)
zephyr_library_sources_ifdef(CONFIG_DMA_SEDI dma_sedi.c)
zephyr_library_sources_ifdef(CONFIG_DMA_SMARTBOND dma_smartbond.c)
zephyr_library_sources_ifdef(CONFIG_DMA_NXP_SOF_HOST_DMA dma_nxp_sof_host_dma.c)

View file

@ -69,4 +69,7 @@ source "drivers/dma/Kconfig.andes_atcdmac300"
source "drivers/dma/Kconfig.sedi"
source "drivers/dma/Kconfig.smartbond"
source "drivers/dma/Kconfig.nxp_sof_host_dma"
endif # DMA

View file

@ -0,0 +1,34 @@
# Copyright 2023 NXP
# SPDX-License-Identifier: Apache-2.0
config DMA_NXP_SOF_HOST_DMA
bool "NXP DMA driver used by SOF's host component"
default y
depends on DT_HAS_NXP_SOF_HOST_DMA_ENABLED
help
Enable NXP's DMA driver used by
SOF (Sound Open Firmware) host
component. Specifically, this driver
is used by the SOF host component to
perform transfers between the host
memory and firmware (local) memory, which
can be accessed without an actual
DMA engine.
if DMA_NXP_SOF_HOST_DMA
config DMA_NXP_SOF_HOST_DMA_ALIGN
int "Alignment (in bytes) required for memory regions passed to this driver"
default 8
help
Use this to set the alignment (in bytes)
which shall be used by entities employing
this driver to adjust a memory region's size
and base address. Since this driver doesn't
actually have any hardware to back it up this
configuration doesn't make much sense as there's
no alignment restrictions imposed by memcpy.
Nevertheless, this is needed because this driver
needs to act as if it controls a DMA engine.
endif # DMA_NXP_SOF_HOST_DMA

View file

@ -0,0 +1,284 @@
/*
* 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);

View file

@ -0,0 +1,12 @@
# Copyright 2023 NXP
# SPDX-License-Identifier: Apache-2.0
description: NXP SOF host DMA node
compatible: "nxp,sof-host-dma"
include: [base.yaml, dma-controller.yaml]
properties:
dma-channels:
required: true