blob: 2c3a31aa79a6faa890f5cf99e48c46c98ec2a6ce [file] [log] [blame]
/*
* Copyright (c) 2025 Core Devices LLC
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT sifli_sf32lb_dmac
#include <zephyr/arch/cpu.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/clock_control/sf32lb.h>
#include <zephyr/drivers/dma.h>
#include <zephyr/logging/log.h>
#include <zephyr/irq.h>
#include <zephyr/spinlock.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/toolchain.h>
#include <register.h>
LOG_MODULE_REGISTER(dma_sf32lb, CONFIG_DMA_LOG_LEVEL);
#define DMAC_MAX_LEN DMAC_CNDTR1_NDT
#define DMAC_MAX_PL 3U
#define DMAC_ISR offsetof(DMAC_TypeDef, ISR)
#define DMAC_IFCR offsetof(DMAC_TypeDef, IFCR)
#define DMAC_CCR1 offsetof(DMAC_TypeDef, CCR1)
#define DMAC_CCR2 offsetof(DMAC_TypeDef, CCR2)
#define DMAC_CCRX(n) (DMAC_CCR1 + (DMAC_CCR2 - DMAC_CCR1) * (n))
#define DMAC_CNDTR1 offsetof(DMAC_TypeDef, CNDTR1)
#define DMAC_CNDTR2 offsetof(DMAC_TypeDef, CNDTR2)
#define DMAC_CNDTRX(n) (DMAC_CNDTR1 + (DMAC_CNDTR2 - DMAC_CNDTR1) * (n))
#define DMAC_CPAR1 offsetof(DMAC_TypeDef, CPAR1)
#define DMAC_CPAR2 offsetof(DMAC_TypeDef, CPAR2)
#define DMAC_CPARX(n) (DMAC_CPAR1 + (DMAC_CPAR2 - DMAC_CPAR1) * (n))
#define DMAC_CM0AR1 offsetof(DMAC_TypeDef, CM0AR1)
#define DMAC_CM0AR2 offsetof(DMAC_TypeDef, CM0AR2)
#define DMAC_CM0ARX(n) (DMAC_CM0AR1 + (DMAC_CM0AR2 - DMAC_CM0AR1) * (n))
#define DMAC_CBSR1 offsetof(DMAC_TypeDef, CBSR1)
#define DMAC_CBSR2 offsetof(DMAC_TypeDef, CBSR2)
#define DMAC_CBSRX(n) (DMAC_CBSR1 + (DMAC_CBSR2 - DMAC_CBSR1) * (n))
#define DMAC_CSELR1 offsetof(DMAC_TypeDef, CSELR1)
#define DMAC_CSELR2 offsetof(DMAC_TypeDef, CSELR2)
#define DMAC_ISR_TCIF(n) (DMAC_ISR_TCIF1_Msk << (n * 4U))
#define DMAC_IFCR_ALL(n) \
((DMAC_IFCR_CGIF1_Msk | DMAC_IFCR_CTCIF1_Msk | DMAC_IFCR_CHTIF1_Msk | \
DMAC_IFCR_CTEIF1_Msk) \
<< (n * 4U))
#define DMAC_IFCR_CTCIF(n) (DMAC_IFCR_CTCIF1_Msk << (n * 4U))
#define DMAC_IFCR_CTEIF(n) (DMAC_IFCR_CTEIF1_Msk << (n * 4U))
#define DMAC_CCRX_PSIZE(n) FIELD_PREP(DMAC_CCR1_PSIZE_Msk, LOG2CEIL(n))
#define DMAC_CCRX_MSIZE(n) FIELD_PREP(DMAC_CCR1_MSIZE_Msk, LOG2CEIL(n))
struct dma_sf32lb_irq_ctx {
const struct device *dev;
uint8_t channel;
};
struct dma_sf32lb_config {
uintptr_t dmac;
uint8_t n_channels;
uint8_t n_requests;
struct sf32lb_clock_dt_spec clock;
void (*irq_configure)(void);
struct dma_sf32lb_channel *channels;
};
struct dma_sf32lb_channel {
dma_callback_t callback;
void *user_data;
enum dma_channel_direction direction;
};
struct dma_sf32lb_data {
struct dma_context ctx;
struct k_spinlock lock;
};
static void dma_sf32lb_isr(const struct device *dev, uint8_t channel)
{
const struct dma_sf32lb_config *config = dev->config;
uint32_t isr;
int status;
isr = sys_read32(config->dmac + DMAC_ISR);
if ((isr & DMAC_ISR_TCIF(channel)) != 0U) {
status = DMA_STATUS_COMPLETE;
} else {
status = -EIO;
}
config->channels[channel].callback(dev, config->channels[channel].user_data, channel,
status);
sys_write32(DMAC_IFCR_ALL(channel), config->dmac + DMAC_IFCR);
}
#define DMA_SF32LB_IRQ_DEFINE(n, _) \
static void dma_sf32lb_isr_ch##n(const struct device *dev) \
{ \
dma_sf32lb_isr(dev, n); \
}
LISTIFY(8, DMA_SF32LB_IRQ_DEFINE, ())
static int dma_sf32lb_config(const struct device *dev, uint32_t channel,
struct dma_config *config_dma)
{
const struct dma_sf32lb_config *config = dev->config;
struct dma_sf32lb_data *data = dev->data;
uint32_t ccrx;
uint32_t cselrx;
uint32_t cparx;
uint32_t cm0arx;
if (channel >= config->n_channels) {
LOG_ERR("Invalid channel (%" PRIu32 ", max %" PRIu32 ")", channel,
config->n_channels);
return -EINVAL;
}
if (config_dma->block_count != 1U) {
LOG_ERR("Chained block transfer not supported (%" PRIu32 ", max 1)",
config_dma->block_count);
return -ENOTSUP;
}
if (config_dma->head_block->block_size > DMAC_MAX_LEN) {
LOG_ERR("Block size exceeds maximum (%" PRIu32 ", max %lu)",
config_dma->head_block->block_size, DMAC_MAX_LEN);
return -EINVAL;
}
if (config_dma->dma_slot >= config->n_requests) {
LOG_ERR("Invalid DMA slot (%" PRIu32 ", max %" PRIu32 ")", config_dma->dma_slot,
config->n_requests);
return -EINVAL;
}
if (config_dma->channel_priority > DMAC_MAX_PL) {
LOG_ERR("Invalid channel priority (%" PRIu32 ", max %" PRIu32 ")",
config_dma->channel_priority, DMAC_MAX_PL);
return -EINVAL;
}
if ((config_dma->head_block->source_addr_adj == DMA_ADDR_ADJ_DECREMENT) |
(config_dma->head_block->dest_addr_adj == DMA_ADDR_ADJ_DECREMENT)) {
LOG_ERR("Address decrement not supported");
return -ENOTSUP;
}
if ((config_dma->source_data_size != 1U) && (config_dma->source_data_size != 2U) &&
(config_dma->source_data_size != 4U)) {
LOG_ERR("Invalid source data size (%" PRIu32 ", must be 1, 2, or 4)",
config_dma->source_data_size);
return -EINVAL;
}
if ((config_dma->dest_data_size != 1U) && (config_dma->dest_data_size != 2U) &&
(config_dma->dest_data_size != 4U)) {
LOG_ERR("Invalid destination data size (%" PRIu32 ", must be 1, 2, or 4)",
config_dma->dest_data_size);
return -EINVAL;
}
/* configure transfer parameters */
ccrx = sys_read32(config->dmac + DMAC_CCRX(channel));
if ((ccrx & DMAC_CCR1_EN) != 0U) {
LOG_ERR("Configuration not possible with DMA enabled");
return -EIO;
}
ccrx &= ~(DMAC_CCR1_TCIE | DMAC_CCR1_HTIE | DMAC_CCR1_TEIE | DMAC_CCR1_DIR_Msk |
DMAC_CCR1_CIRC_Msk | DMAC_CCR1_PINC_Msk | DMAC_CCR1_MINC_Msk |
DMAC_CCR1_PSIZE_Msk | DMAC_CCR1_MSIZE_Msk | DMAC_CCR1_PL_Msk |
DMAC_CCR1_MEM2MEM_Msk);
ccrx |= FIELD_PREP(DMAC_CCR1_PL_Msk, config_dma->channel_priority);
switch (config_dma->channel_direction) {
case MEMORY_TO_MEMORY:
ccrx |= DMAC_CCR1_MEM2MEM;
__fallthrough;
case PERIPHERAL_TO_MEMORY:
ccrx |= DMAC_CCRX_PSIZE(config_dma->source_data_size) |
DMAC_CCRX_MSIZE(config_dma->dest_data_size);
if (config_dma->head_block->source_addr_adj == DMA_ADDR_ADJ_INCREMENT) {
ccrx |= DMAC_CCR1_PINC;
}
if (config_dma->head_block->dest_addr_adj == DMA_ADDR_ADJ_INCREMENT) {
ccrx |= DMAC_CCR1_MINC;
}
cparx = config_dma->head_block->source_address;
cm0arx = config_dma->head_block->dest_address;
break;
case MEMORY_TO_PERIPHERAL:
ccrx |= DMAC_CCR1_DIR | DMAC_CCRX_PSIZE(config_dma->dest_data_size) |
DMAC_CCRX_MSIZE(config_dma->source_data_size);
if (config_dma->head_block->source_addr_adj == DMA_ADDR_ADJ_INCREMENT) {
ccrx |= DMAC_CCR1_MINC;
}
if (config_dma->head_block->dest_addr_adj == DMA_ADDR_ADJ_INCREMENT) {
ccrx |= DMAC_CCR1_PINC;
}
cparx = config_dma->head_block->dest_address;
cm0arx = config_dma->head_block->source_address;
break;
default:
return -ENOTSUP;
}
sys_write32(ccrx, config->dmac + DMAC_CCRX(channel));
/* single transfer */
sys_write32(FIELD_PREP(DMAC_CBSR1_BS_Msk, 0U), config->dmac + DMAC_CBSRX(channel));
/* configure transfer size, src/dst addresses */
sys_write32(config_dma->head_block->block_size, config->dmac + DMAC_CNDTRX(channel));
sys_write32(cparx, config->dmac + DMAC_CPARX(channel));
sys_write32(cm0arx, config->dmac + DMAC_CM0ARX(channel));
/* configure request */
K_SPINLOCK(&data->lock) {
if (channel < 4U) {
cselrx = sys_read32(config->dmac + DMAC_CSELR1);
cselrx &= ~(DMAC_CSELR1_C1S_Msk << (channel * 8U));
cselrx |= FIELD_PREP(DMAC_CSELR1_C1S_Msk << (channel * 8U),
config_dma->dma_slot);
sys_write32(cselrx, config->dmac + DMAC_CSELR1);
} else {
cselrx = sys_read32(config->dmac + DMAC_CSELR2);
cselrx &= ~(DMAC_CSELR1_C1S_Msk << ((channel - 4U) * 8U));
cselrx |= FIELD_PREP(DMAC_CSELR1_C1S_Msk << ((channel - 4U) * 8U),
config_dma->dma_slot);
sys_write32(cselrx, config->dmac + DMAC_CSELR2);
}
}
config->channels[channel].callback = config_dma->dma_callback;
config->channels[channel].user_data = config_dma->user_data;
config->channels[channel].direction = config_dma->channel_direction;
return 0;
}
static int dma_sf32lb_reload(const struct device *dev, uint32_t channel, uint32_t src, uint32_t dst,
size_t size)
{
const struct dma_sf32lb_config *config = dev->config;
uint32_t ccrx;
uint32_t cparx;
uint32_t cm0arx;
if (channel >= config->n_channels) {
LOG_ERR("Invalid channel (%" PRIu32 ", max %" PRIu32 ")", channel,
config->n_channels);
return -EINVAL;
}
if (size > DMAC_MAX_LEN) {
LOG_ERR("Block size exceeds maximum (%" PRIu32 ", max %lu)", size, DMAC_MAX_LEN);
return -EINVAL;
}
ccrx = sys_read32(config->dmac + DMAC_CCRX(channel));
if ((ccrx & DMAC_CCR1_EN) != 0U) {
LOG_ERR("Channel %" PRIu32 " is busy", channel);
return -EBUSY;
}
/* configure size, src/dst addresses */
sys_write32(size, config->dmac + DMAC_CNDTRX(channel));
switch (config->channels[channel].direction) {
case MEMORY_TO_MEMORY:
case PERIPHERAL_TO_MEMORY:
cparx = src;
cm0arx = dst;
break;
case MEMORY_TO_PERIPHERAL:
cparx = dst;
cm0arx = src;
break;
default:
__ASSERT_NO_MSG(false);
return -ENOTSUP;
}
sys_write32(cparx, config->dmac + DMAC_CPARX(channel));
sys_write32(cm0arx, config->dmac + DMAC_CM0ARX(channel));
return 0;
}
static int dma_sf32lb_start(const struct device *dev, uint32_t channel)
{
const struct dma_sf32lb_config *config = dev->config;
uint32_t ccrx;
if (channel >= config->n_channels) {
LOG_ERR("Invalid channel (%" PRIu32 ", max %" PRIu32 ")", channel,
config->n_channels);
return -EINVAL;
}
ccrx = sys_read32(config->dmac + DMAC_CCRX(channel));
if ((ccrx & DMAC_CCR1_EN) != 0U) {
return 0;
}
/* clear all transfer flags */
sys_write32(DMAC_IFCR_ALL(channel), config->dmac + DMAC_IFCR);
/* enable DMA, complete/error IRQs if callback configured */
ccrx |= DMAC_CCR1_EN;
if (config->channels[channel].callback != NULL) {
ccrx |= DMAC_CCR1_TCIE | DMAC_CCR1_TEIE;
}
sys_write32(ccrx, config->dmac + DMAC_CCRX(channel));
return 0;
}
static int dma_sf32lb_stop(const struct device *dev, uint32_t channel)
{
const struct dma_sf32lb_config *config = dev->config;
uint32_t ccrx;
if (channel >= config->n_channels) {
LOG_ERR("Invalid channel (%" PRIu32 ", max %" PRIu32 ")", channel,
config->n_channels);
return -EINVAL;
}
/* disable DMA and complete/error IRQs */
ccrx = sys_read32(config->dmac + DMAC_CCRX(channel));
ccrx &= ~(DMAC_CCR1_EN | DMAC_CCR1_TCIE | DMAC_CCR1_TEIE);
sys_write32(ccrx, config->dmac + DMAC_CCRX(channel));
return 0;
}
static int dma_sf32lb_get_status(const struct device *dev, uint32_t channel,
struct dma_status *stat)
{
const struct dma_sf32lb_config *config = dev->config;
uint32_t isr;
if (channel >= config->n_channels) {
LOG_ERR("Invalid channel (%" PRIu32 ", max %" PRIu32 ")", channel,
config->n_channels);
return -EINVAL;
}
isr = sys_read32(config->dmac + DMAC_ISR);
if ((isr & DMAC_IFCR_CTEIF(channel)) != 0U) {
return -EIO;
}
stat->busy = (isr & DMAC_IFCR_CTCIF(channel)) == 0U;
stat->dir = config->channels[channel].direction;
stat->pending_length = sys_read32(config->dmac + DMAC_CNDTRX(channel));
return 0;
}
static DEVICE_API(dma, dma_sf32lb_driver_api) = {
.config = dma_sf32lb_config,
.reload = dma_sf32lb_reload,
.start = dma_sf32lb_start,
.stop = dma_sf32lb_stop,
.get_status = dma_sf32lb_get_status,
};
static int dma_sf32lb_init(const struct device *dev)
{
const struct dma_sf32lb_config *config = dev->config;
if (!sf3232lb_clock_is_ready_dt(&config->clock)) {
return -ENODEV;
}
(void)sf32lb_clock_control_on_dt(&config->clock);
for (uint8_t channel = 0U; channel < config->n_channels; channel++) {
uint32_t ccrx;
ccrx = sys_read32(config->dmac + DMAC_CCRX(channel));
ccrx &= ~(DMAC_CCR1_EN | DMAC_CCR1_TCIE | DMAC_CCR1_HTIE | DMAC_CCR1_TEIE);
sys_write32(ccrx, config->dmac + DMAC_CCRX(channel));
}
config->irq_configure();
return 0;
}
#define DMA_SF32LB_IRQ_CONFIGURE(n, inst) \
IRQ_CONNECT(DT_INST_IRQ_BY_IDX(inst, n, irq), DT_INST_IRQ_BY_IDX(inst, n, priority), \
dma_sf32lb_isr_ch##n, DEVICE_DT_INST_GET(inst), 0); \
irq_enable(DT_INST_IRQ_BY_IDX(inst, n, irq));
#define DMA_SF32LB_CONFIGURE_ALL_IRQS(inst, n) LISTIFY(n, DMA_SF32LB_IRQ_CONFIGURE, (), inst)
#define DMA_SF32LB_DEFINE(inst) \
static void irq_configure##inst(void) \
{ \
DMA_SF32LB_CONFIGURE_ALL_IRQS(inst, DT_INST_NUM_IRQS(inst)); \
} \
\
static struct dma_sf32lb_channel channels##inst[DT_INST_PROP(inst, dma_channels)]; \
\
static const struct dma_sf32lb_config config##inst = { \
.dmac = DT_INST_REG_ADDR(inst), \
.n_channels = DT_INST_PROP(inst, dma_channels), \
.n_requests = DT_INST_PROP(inst, dma_requests), \
.clock = SF32LB_CLOCK_DT_INST_SPEC_GET(inst), \
.irq_configure = irq_configure##inst, \
.channels = channels##inst, \
}; \
\
ATOMIC_DEFINE(atomic##inst, DT_INST_PROP(inst, dma_channels)); \
\
static struct dma_sf32lb_data data##inst = { \
.ctx = \
{ \
.magic = DMA_MAGIC, \
.atomic = atomic##inst, \
.dma_channels = DT_INST_PROP(inst, dma_channels), \
}, \
}; \
\
DEVICE_DT_INST_DEFINE(inst, dma_sf32lb_init, NULL, &data##inst, &config##inst, \
PRE_KERNEL_1, CONFIG_DMA_INIT_PRIORITY, &dma_sf32lb_driver_api);
DT_INST_FOREACH_STATUS_OKAY(DMA_SF32LB_DEFINE)