310 lines
7.8 KiB
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);
|