blob: 7c4ffa72e57755de7b218597b232e32507336e3c [file] [log] [blame]
/*
* Copyright (c) 2023 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT intel_sedi_dma
#include <errno.h>
#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/pm/device.h>
#include <string.h>
#include <zephyr/init.h>
#include <zephyr/drivers/dma.h>
#include <zephyr/devicetree.h>
#include <zephyr/cache.h>
#include <soc.h>
#include "sedi_driver_dma.h"
#include "sedi_driver_core.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(sedi_dma, CONFIG_DMA_LOG_LEVEL);
extern void dma_isr(sedi_dma_t dma_device);
struct dma_sedi_config_info {
sedi_dma_t peripheral_id; /* Controller instance. */
uint8_t chn_num;
void (*irq_config)(void);
};
struct dma_sedi_driver_data {
struct dma_config dma_configs[DMA_CHANNEL_NUM];
};
#define DEV_DATA(dev) ((struct dma_sedi_driver_data *const)(dev)->data)
#define DEV_CFG(dev) \
((const struct dma_sedi_config_info *const)(dev)->config)
/*
* this function will be called when dma transferring is completed
* or error happened
*/
static void dma_handler(sedi_dma_t dma_device, int channel, int event_id,
void *args)
{
ARG_UNUSED(args);
const struct device *dev = (const struct device *)args;
struct dma_sedi_driver_data *const data = DEV_DATA(dev);
struct dma_config *config = &(data->dma_configs[channel]);
/* run user-defined callback */
if (config->dma_callback) {
if ((event_id == SEDI_DMA_EVENT_TRANSFER_DONE) &&
(config->complete_callback_en)) {
config->dma_callback(dev, config->user_data,
channel, 0);
} else if (config->error_callback_en) {
config->dma_callback(dev, config->user_data,
channel, event_id);
}
}
}
/* map width to certain macros*/
static int width_index(uint32_t num_bytes, uint32_t *index)
{
switch (num_bytes) {
case 1:
*index = DMA_TRANS_WIDTH_8;
break;
case 2:
*index = DMA_TRANS_WIDTH_16;
break;
case 4:
*index = DMA_TRANS_WIDTH_32;
break;
case 8:
*index = DMA_TRANS_WIDTH_64;
break;
case 16:
*index = DMA_TRANS_WIDTH_128;
break;
case 32:
*index = DMA_TRANS_WIDTH_256;
break;
default:
return -ENOTSUP;
}
return 0;
}
/* map burst size to certain macros*/
static int burst_index(uint32_t num_units, uint32_t *index)
{
switch (num_units) {
case 1:
*index = DMA_BURST_TRANS_LENGTH_1;
break;
case 4:
*index = DMA_BURST_TRANS_LENGTH_4;
break;
case 8:
*index = DMA_BURST_TRANS_LENGTH_8;
break;
case 16:
*index = DMA_BURST_TRANS_LENGTH_16;
break;
case 32:
*index = DMA_BURST_TRANS_LENGTH_32;
break;
case 64:
*index = DMA_BURST_TRANS_LENGTH_64;
break;
case 128:
*index = DMA_BURST_TRANS_LENGTH_128;
break;
case 256:
*index = DMA_BURST_TRANS_LENGTH_256;
break;
default:
return -ENOTSUP;
}
return 0;
}
static void dma_config_convert(struct dma_config *config,
dma_memory_type_t *src_mem,
dma_memory_type_t *dst_mem,
uint8_t *sedi_dma_dir)
{
*src_mem = DMA_SRAM_MEM;
*dst_mem = DMA_SRAM_MEM;
*sedi_dma_dir = MEMORY_TO_MEMORY;
switch (config->channel_direction) {
case MEMORY_TO_MEMORY:
case MEMORY_TO_PERIPHERAL:
case PERIPHERAL_TO_MEMORY:
case PERIPHERAL_TO_PERIPHERAL:
*sedi_dma_dir = config->channel_direction;
break;
case MEMORY_TO_HOST:
*dst_mem = DMA_DRAM_MEM;
break;
case HOST_TO_MEMORY:
*src_mem = DMA_DRAM_MEM;
break;
#ifdef MEMORY_TO_IMR
case MEMORY_TO_IMR:
*dst_mem = DMA_UMA_MEM;
break;
#endif
#ifdef IMR_TO_MEMORY
case IMR_TO_MEMORY:
*src_mem = DMA_UMA_MEM;
break;
#endif
}
}
/* config basic dma */
static int dma_sedi_apply_common_config(sedi_dma_t dev, uint32_t channel,
struct dma_config *config, uint8_t *dir)
{
uint8_t direction = MEMORY_TO_MEMORY;
dma_memory_type_t src_mem = DMA_SRAM_MEM, dst_mem = DMA_SRAM_MEM;
dma_config_convert(config, &src_mem, &dst_mem, &direction);
if (dir) {
*dir = direction;
}
/* configure dma transferring direction*/
sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_DIRECTION,
direction);
if (direction == MEMORY_TO_MEMORY) {
sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_SR_MEM_TYPE,
src_mem);
sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_DT_MEM_TYPE,
dst_mem);
} else if (direction == MEMORY_TO_PERIPHERAL) {
sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_HS_DEVICE_ID,
config->dma_slot);
sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_HS_POLARITY,
DMA_HS_POLARITY_HIGH);
sedi_dma_control(dev, channel,
SEDI_CONFIG_DMA_HS_DEVICE_ID_PER_DIR,
DMA_HS_PER_TX);
} else if (direction == PERIPHERAL_TO_MEMORY) {
sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_HS_DEVICE_ID,
config->dma_slot);
sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_HS_POLARITY,
DMA_HS_POLARITY_HIGH);
sedi_dma_control(dev, channel,
SEDI_CONFIG_DMA_HS_DEVICE_ID_PER_DIR,
DMA_HS_PER_RX);
} else {
return -1;
}
return 0;
}
static int dma_sedi_apply_single_config(sedi_dma_t dev, uint32_t channel,
struct dma_config *config)
{
int ret = 0;
uint32_t temp = 0;
ret = dma_sedi_apply_common_config(dev, channel, config, NULL);
if (ret != 0) {
goto INVALID_ARGS;
}
/* configurate dma width of source data*/
ret = width_index(config->source_data_size, &temp);
if (ret != 0) {
goto INVALID_ARGS;
}
sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_SR_TRANS_WIDTH, temp);
/* configurate dma width of destination data*/
ret = width_index(config->dest_data_size, &temp);
if (ret != 0) {
goto INVALID_ARGS;
}
sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_DT_TRANS_WIDTH, temp);
/* configurate dma burst size*/
ret = burst_index(config->source_burst_length, &temp);
if (ret != 0) {
goto INVALID_ARGS;
}
sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_BURST_LENGTH, temp);
return 0;
INVALID_ARGS:
return ret;
}
static int dma_sedi_chan_config(const struct device *dev, uint32_t channel,
struct dma_config *config)
{
if ((dev == NULL) || (channel >= DEV_CFG(dev)->chn_num)
|| (config == NULL)
|| (config->block_count != 1)) {
goto INVALID_ARGS;
}
const struct dma_sedi_config_info *const info = DEV_CFG(dev);
struct dma_sedi_driver_data *const data = DEV_DATA(dev);
memcpy(&(data->dma_configs[channel]), config, sizeof(struct dma_config));
/* initialize the dma controller, following the sedi api*/
sedi_dma_event_cb_t cb = dma_handler;
sedi_dma_init(info->peripheral_id, (int)channel, cb, (void *)dev);
return 0;
INVALID_ARGS:
return -1;
}
static int dma_sedi_reload(const struct device *dev, uint32_t channel,
uint64_t src, uint64_t dst, size_t size)
{
if ((dev == NULL) || (channel >= DEV_CFG(dev)->chn_num)) {
LOG_ERR("dma reload failed for invalid args");
return -ENOTSUP;
}
int ret = 0;
struct dma_sedi_driver_data *const data = DEV_DATA(dev);
struct dma_config *config = &(data->dma_configs[channel]);
struct dma_block_config *block_config;
if ((config == NULL) || (config->head_block == NULL)) {
LOG_ERR("dma reload failed, no config found");
return -ENOTSUP;
}
block_config = config->head_block;
if ((config->block_count == 1) || (block_config->next_block == NULL)) {
block_config->source_address = src;
block_config->dest_address = dst;
block_config->block_size = size;
} else {
LOG_ERR("no reload support for multi-linkedlist mode");
return -ENOTSUP;
}
return ret;
}
static int dma_sedi_start(const struct device *dev, uint32_t channel)
{
if ((dev == NULL) || (channel >= DEV_CFG(dev)->chn_num)) {
LOG_ERR("dma transferring failed for invalid args");
return -ENOTSUP;
}
int ret = -1;
const struct dma_sedi_config_info *const info = DEV_CFG(dev);
struct dma_sedi_driver_data *const data = DEV_DATA(dev);
struct dma_config *config = &(data->dma_configs[channel]);
struct dma_block_config *block_config = config->head_block;
uint64_t src_addr, dst_addr;
if (config->block_count == 1) {
/* call sedi start function */
ret = dma_sedi_apply_single_config(info->peripheral_id,
channel, config);
if (ret) {
goto ERR;
}
src_addr = block_config->source_address;
dst_addr = block_config->dest_address;
ret = sedi_dma_start_transfer(info->peripheral_id, channel,
src_addr, dst_addr, block_config->block_size);
} else {
LOG_ERR("MULTIPLE_BLOCK CONFIG is not set");
goto ERR;
}
if (ret != SEDI_DRIVER_OK) {
goto ERR;
}
return ret;
ERR:
LOG_ERR("dma transfer failed");
return ret;
}
static int dma_sedi_stop(const struct device *dev, uint32_t channel)
{
const struct dma_sedi_config_info *const info = DEV_CFG(dev);
LOG_DBG("stopping dma: %p, %d", dev, channel);
sedi_dma_abort_transfer(info->peripheral_id, channel);
return 0;
}
static const struct dma_driver_api dma_funcs = { .config = dma_sedi_chan_config,
.start = dma_sedi_start,
.stop = dma_sedi_stop,
.reload = dma_sedi_reload,
.get_status = NULL
};
static int dma_sedi_init(const struct device *dev)
{
const struct dma_sedi_config_info *const config = DEV_CFG(dev);
config->irq_config();
return 0;
}
#define DMA_DEVICE_INIT_SEDI(inst) \
static void dma_sedi_##inst##_irq_config(void); \
\
static struct dma_sedi_driver_data dma_sedi_dev_data_##inst; \
static const struct dma_sedi_config_info dma_sedi_config_data_##inst = { \
.peripheral_id = DT_INST_PROP(inst, peripheral_id), \
.chn_num = DT_INST_PROP(inst, dma_channels), \
.irq_config = dma_sedi_##inst##_irq_config \
}; \
DEVICE_DT_DEFINE(DT_INST(inst, DT_DRV_COMPAT), &dma_sedi_init, \
NULL, &dma_sedi_dev_data_##inst, &dma_sedi_config_data_##inst, PRE_KERNEL_2, \
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, (void *)&dma_funcs); \
\
static void dma_sedi_##inst##_irq_config(void) \
{ \
IRQ_CONNECT(DT_INST_IRQN(inst), \
DT_INST_IRQ(inst, priority), dma_isr, \
(void *)DT_INST_PROP(inst, peripheral_id), \
DT_INST_IRQ(inst, sense)); \
irq_enable(DT_INST_IRQN(inst)); \
}
DT_INST_FOREACH_STATUS_OKAY(DMA_DEVICE_INIT_SEDI)