688 lines
19 KiB
C
688 lines
19 KiB
C
/*
|
|
* Copyright 2024 NXP
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include "dma_nxp_edma.h"
|
|
|
|
/* TODO list:
|
|
* 1) Support for requesting a specific channel.
|
|
* 2) Support for checking if DMA transfer is pending when attempting config. (?)
|
|
* 3) Support for error interrupt.
|
|
* 4) Support for error if buffer overflow/underrun.
|
|
* 5) Ideally, HALFMAJOR should be set on a per-channel basis not through a
|
|
* config. If not possible, this should be done through a DTS property. Also,
|
|
* maybe do the same for INTMAJOR IRQ.
|
|
*/
|
|
|
|
static void edma_isr(const void *parameter)
|
|
{
|
|
const struct edma_config *cfg;
|
|
struct edma_data *data;
|
|
struct edma_channel *chan;
|
|
int ret;
|
|
uint32_t update_size;
|
|
|
|
chan = (struct edma_channel *)parameter;
|
|
cfg = chan->dev->config;
|
|
data = chan->dev->data;
|
|
|
|
if (!EDMA_ChannelRegRead(data->hal_cfg, chan->id, EDMA_TCD_CH_INT)) {
|
|
/* skip, interrupt was probably triggered by another channel */
|
|
return;
|
|
}
|
|
|
|
/* clear interrupt */
|
|
EDMA_ChannelRegUpdate(data->hal_cfg, chan->id,
|
|
EDMA_TCD_CH_INT, EDMA_TCD_CH_INT_MASK, 0);
|
|
|
|
if (chan->cyclic_buffer) {
|
|
update_size = chan->bsize;
|
|
|
|
if (IS_ENABLED(CONFIG_DMA_NXP_EDMA_ENABLE_HALFMAJOR_IRQ)) {
|
|
update_size = chan->bsize / 2;
|
|
} else {
|
|
update_size = chan->bsize;
|
|
}
|
|
|
|
/* TODO: add support for error handling here */
|
|
ret = EDMA_CHAN_PRODUCE_CONSUME_A(chan, update_size);
|
|
if (ret < 0) {
|
|
LOG_ERR("chan %d buffer overflow/underrun", chan->id);
|
|
}
|
|
}
|
|
|
|
/* TODO: are there any sanity checks we have to perform before invoking
|
|
* the registered callback?
|
|
*/
|
|
if (chan->cb) {
|
|
chan->cb(chan->dev, chan->arg, chan->id, DMA_STATUS_COMPLETE);
|
|
}
|
|
}
|
|
|
|
static struct edma_channel *lookup_channel(const struct device *dev,
|
|
uint32_t chan_id)
|
|
{
|
|
struct edma_data *data;
|
|
const struct edma_config *cfg;
|
|
int i;
|
|
|
|
data = dev->data;
|
|
cfg = dev->config;
|
|
|
|
|
|
/* optimization: if dma-channels property is present then
|
|
* the channel data associated with the passed channel ID
|
|
* can be found at index chan_id in the array of channels.
|
|
*/
|
|
if (cfg->contiguous_channels) {
|
|
/* check for index out of bounds */
|
|
if (chan_id >= data->ctx.dma_channels) {
|
|
return NULL;
|
|
}
|
|
|
|
return &data->channels[chan_id];
|
|
}
|
|
|
|
/* channels are passed through the valid-channels property.
|
|
* As such, since some channels may be missing we need to
|
|
* look through the entire channels array for an ID match.
|
|
*/
|
|
for (i = 0; i < data->ctx.dma_channels; i++) {
|
|
if (data->channels[i].id == chan_id) {
|
|
return &data->channels[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int edma_config(const struct device *dev, uint32_t chan_id,
|
|
struct dma_config *dma_cfg)
|
|
{
|
|
struct edma_data *data;
|
|
const struct edma_config *cfg;
|
|
struct edma_channel *chan;
|
|
uint32_t transfer_type;
|
|
int ret;
|
|
|
|
data = dev->data;
|
|
cfg = dev->config;
|
|
|
|
if (!dma_cfg->head_block) {
|
|
LOG_ERR("head block shouldn't be NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* validate source data size (SSIZE) */
|
|
if (!EDMA_TransferWidthIsValid(data->hal_cfg, dma_cfg->source_data_size)) {
|
|
LOG_ERR("invalid source data size: %d",
|
|
dma_cfg->source_data_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* validate destination data size (DSIZE) */
|
|
if (!EDMA_TransferWidthIsValid(data->hal_cfg, dma_cfg->dest_data_size)) {
|
|
LOG_ERR("invalid destination data size: %d",
|
|
dma_cfg->dest_data_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* validate configured alignment */
|
|
if (!EDMA_TransferWidthIsValid(data->hal_cfg, CONFIG_DMA_NXP_EDMA_ALIGN)) {
|
|
LOG_ERR("configured alignment %d is invalid",
|
|
CONFIG_DMA_NXP_EDMA_ALIGN);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Scatter-Gather configurations currently not supported */
|
|
if (dma_cfg->block_count != 1) {
|
|
LOG_ERR("number of blocks %d not supported", dma_cfg->block_count);
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* source address shouldn't be NULL */
|
|
if (!dma_cfg->head_block->source_address) {
|
|
LOG_ERR("source address cannot be NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* destination address shouldn't be NULL */
|
|
if (!dma_cfg->head_block->dest_address) {
|
|
LOG_ERR("destination address cannot be NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* check source address's (SADDR) alignment with respect to the data size (SSIZE)
|
|
*
|
|
* Failing to meet this condition will lead to the assertion of the SAE
|
|
* bit (see CHn_ES register).
|
|
*
|
|
* TODO: this will also restrict scenarios such as the following:
|
|
* SADDR is 8B aligned and SSIZE is 16B. I've tested this
|
|
* scenario and seems to raise no hardware errors (I'm assuming
|
|
* because this doesn't break the 8B boundary of the 64-bit system
|
|
* I tested it on). Is there a need to allow such a scenario?
|
|
*/
|
|
if (dma_cfg->head_block->source_address % dma_cfg->source_data_size) {
|
|
LOG_ERR("source address 0x%x alignment doesn't match data size %d",
|
|
dma_cfg->head_block->source_address,
|
|
dma_cfg->source_data_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* check destination address's (DADDR) alignment with respect to the data size (DSIZE)
|
|
* Failing to meet this condition will lead to the assertion of the DAE
|
|
* bit (see CHn_ES register).
|
|
*/
|
|
if (dma_cfg->head_block->dest_address % dma_cfg->dest_data_size) {
|
|
LOG_ERR("destination address 0x%x alignment doesn't match data size %d",
|
|
dma_cfg->head_block->dest_address,
|
|
dma_cfg->dest_data_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* source burst length should match destination burst length.
|
|
* This is because the burst length is the equivalent of NBYTES which
|
|
* is used for both the destination and the source.
|
|
*/
|
|
if (dma_cfg->source_burst_length !=
|
|
dma_cfg->dest_burst_length) {
|
|
LOG_ERR("source burst length %d doesn't match destination burst length %d",
|
|
dma_cfg->source_burst_length,
|
|
dma_cfg->dest_burst_length);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* total number of bytes should be a multiple of NBYTES.
|
|
*
|
|
* This is needed because the EDMA engine performs transfers based
|
|
* on CITER (integer value) and NBYTES, thus it has no knowledge of
|
|
* the total transfer size. If the total transfer size is not a
|
|
* multiple of NBYTES then we'll end up with copying a wrong number
|
|
* of bytes (CITER = TOTAL_SIZE / BITER). This, of course, raises
|
|
* no error in the hardware but it's still wrong.
|
|
*/
|
|
if (dma_cfg->head_block->block_size % dma_cfg->source_burst_length) {
|
|
LOG_ERR("block size %d should be a multiple of NBYTES %d",
|
|
dma_cfg->head_block->block_size,
|
|
dma_cfg->source_burst_length);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* check if NBYTES is a multiple of MAX(SSIZE, DSIZE).
|
|
*
|
|
* This stems from the fact that NBYTES needs to be a multiple
|
|
* of SSIZE AND DSIZE. If NBYTES is a multiple of MAX(SSIZE, DSIZE)
|
|
* then it will for sure satisfy the aforementioned condition (since
|
|
* SSIZE and DSIZE are powers of 2).
|
|
*
|
|
* Failing to meet this condition will lead to the assertion of the
|
|
* NCE bit (see CHn_ES register).
|
|
*/
|
|
if (dma_cfg->source_burst_length %
|
|
MAX(dma_cfg->source_data_size, dma_cfg->dest_data_size)) {
|
|
LOG_ERR("NBYTES %d should be a multiple of MAX(SSIZE(%d), DSIZE(%d))",
|
|
dma_cfg->source_burst_length,
|
|
dma_cfg->source_data_size,
|
|
dma_cfg->dest_data_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* fetch channel data */
|
|
chan = lookup_channel(dev, chan_id);
|
|
if (!chan) {
|
|
LOG_ERR("channel ID %u is not valid", chan_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* save the block size for later usage in edma_reload */
|
|
chan->bsize = dma_cfg->head_block->block_size;
|
|
|
|
if (dma_cfg->cyclic) {
|
|
chan->cyclic_buffer = true;
|
|
|
|
chan->stat.read_position = 0;
|
|
chan->stat.write_position = 0;
|
|
|
|
/* ASSUMPTION: for CONSUMER-type channels, the buffer from
|
|
* which the engine consumes should be full, while in the
|
|
* case of PRODUCER-type channels it should be empty.
|
|
*/
|
|
switch (dma_cfg->channel_direction) {
|
|
case MEMORY_TO_PERIPHERAL:
|
|
chan->type = CHAN_TYPE_CONSUMER;
|
|
chan->stat.free = 0;
|
|
chan->stat.pending_length = chan->bsize;
|
|
break;
|
|
case PERIPHERAL_TO_MEMORY:
|
|
chan->type = CHAN_TYPE_PRODUCER;
|
|
chan->stat.pending_length = 0;
|
|
chan->stat.free = chan->bsize;
|
|
break;
|
|
default:
|
|
LOG_ERR("unsupported transfer dir %d for cyclic mode",
|
|
dma_cfg->channel_direction);
|
|
return -ENOTSUP;
|
|
}
|
|
} else {
|
|
chan->cyclic_buffer = false;
|
|
}
|
|
|
|
/* change channel's state to CONFIGURED */
|
|
ret = channel_change_state(chan, CHAN_STATE_CONFIGURED);
|
|
if (ret < 0) {
|
|
LOG_ERR("failed to change channel %d state to CONFIGURED", chan_id);
|
|
return ret;
|
|
}
|
|
|
|
ret = get_transfer_type(dma_cfg->channel_direction, &transfer_type);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
chan->cb = dma_cfg->dma_callback;
|
|
chan->arg = dma_cfg->user_data;
|
|
|
|
/* warning: this sets SOFF and DOFF to SSIZE and DSIZE which are POSITIVE. */
|
|
ret = EDMA_ConfigureTransfer(data->hal_cfg, chan_id,
|
|
dma_cfg->head_block->source_address,
|
|
dma_cfg->head_block->dest_address,
|
|
dma_cfg->source_data_size,
|
|
dma_cfg->dest_data_size,
|
|
dma_cfg->source_burst_length,
|
|
dma_cfg->head_block->block_size,
|
|
transfer_type);
|
|
if (ret < 0) {
|
|
LOG_ERR("failed to configure transfer");
|
|
return to_std_error(ret);
|
|
}
|
|
|
|
/* TODO: channel MUX should be forced to 0 based on the previous state */
|
|
if (EDMA_HAS_MUX(data->hal_cfg)) {
|
|
ret = EDMA_SetChannelMux(data->hal_cfg, chan_id, dma_cfg->dma_slot);
|
|
if (ret < 0) {
|
|
LOG_ERR("failed to set channel MUX");
|
|
return to_std_error(ret);
|
|
}
|
|
}
|
|
|
|
/* set SLAST and DLAST */
|
|
ret = set_slast_dlast(dma_cfg, transfer_type, data, chan_id);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* allow interrupting the CPU when a major cycle is completed.
|
|
*
|
|
* interesting note: only 1 major loop is performed per slave peripheral
|
|
* DMA request. For instance, if block_size = 768 and burst_size = 192
|
|
* we're going to get 4 transfers of 192 bytes. Each of these transfers
|
|
* translates to a DMA request made by the slave peripheral.
|
|
*/
|
|
EDMA_ChannelRegUpdate(data->hal_cfg, chan_id,
|
|
EDMA_TCD_CSR, EDMA_TCD_CSR_INTMAJOR_MASK, 0);
|
|
|
|
if (IS_ENABLED(CONFIG_DMA_NXP_EDMA_ENABLE_HALFMAJOR_IRQ)) {
|
|
/* if enabled through the above configuration, also
|
|
* allow the CPU to be interrupted when CITER = BITER / 2.
|
|
*/
|
|
EDMA_ChannelRegUpdate(data->hal_cfg, chan_id, EDMA_TCD_CSR,
|
|
EDMA_TCD_CSR_INTHALF_MASK, 0);
|
|
}
|
|
|
|
/* enable channel interrupt */
|
|
irq_enable(chan->irq);
|
|
|
|
/* dump register status - for debugging purposes */
|
|
edma_dump_channel_registers(data, chan_id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int edma_get_status(const struct device *dev, uint32_t chan_id,
|
|
struct dma_status *stat)
|
|
{
|
|
struct edma_data *data;
|
|
struct edma_channel *chan;
|
|
uint32_t citer, biter, done;
|
|
unsigned int key;
|
|
|
|
data = dev->data;
|
|
|
|
/* fetch channel data */
|
|
chan = lookup_channel(dev, chan_id);
|
|
if (!chan) {
|
|
LOG_ERR("channel ID %u is not valid", chan_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (chan->cyclic_buffer) {
|
|
key = irq_lock();
|
|
|
|
stat->free = chan->stat.free;
|
|
stat->pending_length = chan->stat.pending_length;
|
|
|
|
irq_unlock(key);
|
|
} else {
|
|
/* note: no locking required here. The DMA interrupts
|
|
* have no effect over CITER and BITER.
|
|
*/
|
|
citer = EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_CITER);
|
|
biter = EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_BITER);
|
|
done = EDMA_ChannelRegRead(data->hal_cfg, chan_id, EDMA_TCD_CH_CSR) &
|
|
EDMA_TCD_CH_CSR_DONE_MASK;
|
|
if (done) {
|
|
stat->free = chan->bsize;
|
|
stat->pending_length = 0;
|
|
} else {
|
|
stat->free = (biter - citer) * (chan->bsize / biter);
|
|
stat->pending_length = chan->bsize - stat->free;
|
|
}
|
|
}
|
|
|
|
LOG_DBG("free: %d, pending: %d", stat->free, stat->pending_length);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int edma_suspend(const struct device *dev, uint32_t chan_id)
|
|
{
|
|
struct edma_data *data;
|
|
const struct edma_config *cfg;
|
|
struct edma_channel *chan;
|
|
int ret;
|
|
|
|
data = dev->data;
|
|
cfg = dev->config;
|
|
|
|
/* fetch channel data */
|
|
chan = lookup_channel(dev, chan_id);
|
|
if (!chan) {
|
|
LOG_ERR("channel ID %u is not valid", chan_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
edma_dump_channel_registers(data, chan_id);
|
|
|
|
/* change channel's state to SUSPENDED */
|
|
ret = channel_change_state(chan, CHAN_STATE_SUSPENDED);
|
|
if (ret < 0) {
|
|
LOG_ERR("failed to change channel %d state to SUSPENDED", chan_id);
|
|
return ret;
|
|
}
|
|
|
|
LOG_DBG("suspending channel %u", chan_id);
|
|
|
|
/* disable HW requests */
|
|
EDMA_ChannelRegUpdate(data->hal_cfg, chan_id,
|
|
EDMA_TCD_CH_CSR, 0, EDMA_TCD_CH_CSR_ERQ_MASK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int edma_stop(const struct device *dev, uint32_t chan_id)
|
|
{
|
|
struct edma_data *data;
|
|
const struct edma_config *cfg;
|
|
struct edma_channel *chan;
|
|
enum channel_state prev_state;
|
|
int ret;
|
|
|
|
data = dev->data;
|
|
cfg = dev->config;
|
|
|
|
/* fetch channel data */
|
|
chan = lookup_channel(dev, chan_id);
|
|
if (!chan) {
|
|
LOG_ERR("channel ID %u is not valid", chan_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
prev_state = chan->state;
|
|
|
|
/* change channel's state to STOPPED */
|
|
ret = channel_change_state(chan, CHAN_STATE_STOPPED);
|
|
if (ret < 0) {
|
|
LOG_ERR("failed to change channel %d state to STOPPED", chan_id);
|
|
return ret;
|
|
}
|
|
|
|
LOG_DBG("stopping channel %u", chan_id);
|
|
|
|
if (prev_state == CHAN_STATE_SUSPENDED) {
|
|
/* if the channel has been suspended then there's
|
|
* no point in disabling the HW requests again. Just
|
|
* jump to the channel release operation.
|
|
*/
|
|
goto out_release_channel;
|
|
}
|
|
|
|
/* disable HW requests */
|
|
EDMA_ChannelRegUpdate(data->hal_cfg, chan_id, EDMA_TCD_CH_CSR, 0,
|
|
EDMA_TCD_CH_CSR_ERQ_MASK);
|
|
out_release_channel:
|
|
|
|
/* clear the channel MUX so that it can used by a different peripheral.
|
|
*
|
|
* note: because the channel is released during dma_stop() that means
|
|
* dma_start() can no longer be immediately called. This is because
|
|
* one needs to re-configure the channel MUX which can only be done
|
|
* through dma_config(). As such, if one intends to reuse the current
|
|
* configuration then please call dma_suspend() instead of dma_stop().
|
|
*/
|
|
if (EDMA_HAS_MUX(data->hal_cfg)) {
|
|
ret = EDMA_SetChannelMux(data->hal_cfg, chan_id, 0);
|
|
if (ret < 0) {
|
|
LOG_ERR("failed to set channel MUX");
|
|
return to_std_error(ret);
|
|
}
|
|
}
|
|
|
|
edma_dump_channel_registers(data, chan_id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int edma_start(const struct device *dev, uint32_t chan_id)
|
|
{
|
|
struct edma_data *data;
|
|
const struct edma_config *cfg;
|
|
struct edma_channel *chan;
|
|
int ret;
|
|
|
|
data = dev->data;
|
|
cfg = dev->config;
|
|
|
|
/* fetch channel data */
|
|
chan = lookup_channel(dev, chan_id);
|
|
if (!chan) {
|
|
LOG_ERR("channel ID %u is not valid", chan_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* change channel's state to STARTED */
|
|
ret = channel_change_state(chan, CHAN_STATE_STARTED);
|
|
if (ret < 0) {
|
|
LOG_ERR("failed to change channel %d state to STARTED", chan_id);
|
|
return ret;
|
|
}
|
|
|
|
LOG_DBG("starting channel %u", chan_id);
|
|
|
|
/* enable HW requests */
|
|
EDMA_ChannelRegUpdate(data->hal_cfg, chan_id,
|
|
EDMA_TCD_CH_CSR, EDMA_TCD_CH_CSR_ERQ_MASK, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int edma_reload(const struct device *dev, uint32_t chan_id, uint32_t src,
|
|
uint32_t dst, size_t size)
|
|
{
|
|
struct edma_data *data;
|
|
struct edma_channel *chan;
|
|
int ret;
|
|
unsigned int key;
|
|
|
|
data = dev->data;
|
|
|
|
/* fetch channel data */
|
|
chan = lookup_channel(dev, chan_id);
|
|
if (!chan) {
|
|
LOG_ERR("channel ID %u is not valid", chan_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* channel needs to be started to allow reloading */
|
|
if (chan->state != CHAN_STATE_STARTED) {
|
|
LOG_ERR("reload is only supported on started channels");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (chan->cyclic_buffer) {
|
|
key = irq_lock();
|
|
ret = EDMA_CHAN_PRODUCE_CONSUME_B(chan, size);
|
|
irq_unlock(key);
|
|
if (ret < 0) {
|
|
LOG_ERR("chan %d buffer overflow/underrun", chan_id);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int edma_get_attribute(const struct device *dev, uint32_t type, uint32_t *val)
|
|
{
|
|
switch (type) {
|
|
case DMA_ATTR_BUFFER_SIZE_ALIGNMENT:
|
|
case DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT:
|
|
*val = CONFIG_DMA_NXP_EDMA_ALIGN;
|
|
break;
|
|
case DMA_ATTR_MAX_BLOCK_COUNT:
|
|
/* this is restricted to 1 because SG configurations are not supported */
|
|
*val = 1;
|
|
break;
|
|
default:
|
|
LOG_ERR("invalid attribute type: %d", type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool edma_channel_filter(const struct device *dev, int chan_id, void *param)
|
|
{
|
|
int *requested_channel;
|
|
|
|
if (!param) {
|
|
return false;
|
|
}
|
|
|
|
requested_channel = param;
|
|
|
|
if (*requested_channel == chan_id && lookup_channel(dev, chan_id)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static const struct dma_driver_api edma_api = {
|
|
.reload = edma_reload,
|
|
.config = edma_config,
|
|
.start = edma_start,
|
|
.stop = edma_stop,
|
|
.suspend = edma_suspend,
|
|
.resume = edma_start,
|
|
.get_status = edma_get_status,
|
|
.get_attribute = edma_get_attribute,
|
|
.chan_filter = edma_channel_filter,
|
|
};
|
|
|
|
static int edma_init(const struct device *dev)
|
|
{
|
|
const struct edma_config *cfg;
|
|
struct edma_data *data;
|
|
mm_reg_t regmap;
|
|
|
|
data = dev->data;
|
|
cfg = dev->config;
|
|
|
|
/* map instance MMIO */
|
|
device_map(®map, cfg->regmap_phys, cfg->regmap_size, K_MEM_CACHE_NONE);
|
|
|
|
/* overwrite physical address set in the HAL configuration.
|
|
* We can down-cast the virtual address to a 32-bit address because
|
|
* we know we're working with 32-bit addresses only.
|
|
*/
|
|
data->hal_cfg->regmap = (uint32_t)POINTER_TO_UINT(regmap);
|
|
|
|
cfg->irq_config();
|
|
|
|
/* dma_request_channel() uses this variable to keep track of the
|
|
* available channels. As such, it needs to be initialized with NULL
|
|
* which signifies that all channels are initially available.
|
|
*/
|
|
data->channel_flags = ATOMIC_INIT(0);
|
|
data->ctx.atomic = &data->channel_flags;
|
|
data->ctx.dma_channels = data->hal_cfg->channels;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* a few comments about the BUILD_ASSERT statements:
|
|
* 1) dma-channels and valid-channels should be mutually exclusive.
|
|
* This means that you specify the one or the other. There's no real
|
|
* need to have both of them.
|
|
* 2) Number of channels should match the number of interrupts for
|
|
* said channels (TODO: what about error interrupts?)
|
|
* 3) The channel-mux property shouldn't be specified unless
|
|
* the eDMA is MUX-capable (signaled via the EDMA_HAS_CHAN_MUX
|
|
* configuration).
|
|
*/
|
|
#define EDMA_INIT(inst) \
|
|
\
|
|
BUILD_ASSERT(!DT_NODE_HAS_PROP(DT_INST(inst, DT_DRV_COMPAT), dma_channels) || \
|
|
!DT_NODE_HAS_PROP(DT_INST(inst, DT_DRV_COMPAT), valid_channels), \
|
|
"dma_channels and valid_channels are mutually exclusive"); \
|
|
\
|
|
BUILD_ASSERT(DT_INST_PROP_OR(inst, dma_channels, 0) == \
|
|
DT_NUM_IRQS(DT_INST(inst, DT_DRV_COMPAT)) || \
|
|
DT_INST_PROP_LEN_OR(inst, valid_channels, 0) == \
|
|
DT_NUM_IRQS(DT_INST(inst, DT_DRV_COMPAT)), \
|
|
"number of interrupts needs to match number of channels"); \
|
|
\
|
|
BUILD_ASSERT(DT_PROP_OR(DT_INST(inst, DT_DRV_COMPAT), hal_cfg_index, 0) < \
|
|
ARRAY_SIZE(s_edmaConfigs), \
|
|
"HAL configuration index out of bounds"); \
|
|
\
|
|
static struct edma_channel channels_##inst[] = EDMA_CHANNEL_ARRAY_GET(inst); \
|
|
\
|
|
static void interrupt_config_function_##inst(void) \
|
|
{ \
|
|
EDMA_CONNECT_INTERRUPTS(inst); \
|
|
} \
|
|
\
|
|
static struct edma_config edma_config_##inst = { \
|
|
.regmap_phys = DT_INST_REG_ADDR(inst), \
|
|
.regmap_size = DT_INST_REG_SIZE(inst), \
|
|
.irq_config = interrupt_config_function_##inst, \
|
|
.contiguous_channels = EDMA_CHANS_ARE_CONTIGUOUS(inst), \
|
|
}; \
|
|
\
|
|
static struct edma_data edma_data_##inst = { \
|
|
.channels = channels_##inst, \
|
|
.ctx.magic = DMA_MAGIC, \
|
|
.hal_cfg = &EDMA_HAL_CFG_GET(inst), \
|
|
}; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(inst, &edma_init, NULL, \
|
|
&edma_data_##inst, &edma_config_##inst, \
|
|
PRE_KERNEL_1, CONFIG_DMA_INIT_PRIORITY, \
|
|
&edma_api); \
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(EDMA_INIT);
|