Browse Source

drivers: dma: Add initial SiM3U1xx support

This supports the minimum of what is required to allow the AES hardware
to be used (memory to peripheral, peripheral to memory).

In addition, to pass some of the existing unit tests, it also supports
memory to memory operators.

Signed-off-by: Reto Schneider <reto.schneider@husqvarnagroup.com>
pull/78254/head
Reto Schneider 1 year ago committed by Anas Nashif
parent
commit
9d3ac80429
  1. 1
      drivers/dma/CMakeLists.txt
  2. 2
      drivers/dma/Kconfig
  3. 10
      drivers/dma/Kconfig.si32
  4. 418
      drivers/dma/dma_si32.c

1
drivers/dma/CMakeLists.txt

@ -38,6 +38,7 @@ zephyr_library_sources_ifdef(CONFIG_DMA_MAX32 dma_max32.c) @@ -38,6 +38,7 @@ zephyr_library_sources_ifdef(CONFIG_DMA_MAX32 dma_max32.c)
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_SI32 dma_si32.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)
zephyr_library_sources_ifdef(CONFIG_DMA_EMUL dma_emul.c)

2
drivers/dma/Kconfig

@ -70,6 +70,8 @@ source "drivers/dma/Kconfig.andes_atcdmac300" @@ -70,6 +70,8 @@ source "drivers/dma/Kconfig.andes_atcdmac300"
source "drivers/dma/Kconfig.sedi"
source "drivers/dma/Kconfig.si32"
source "drivers/dma/Kconfig.smartbond"
source "drivers/dma/Kconfig.nxp_sof_host_dma"

10
drivers/dma/Kconfig.si32

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
# Copyright (c) 2024 GARDENA GmbH
#
# SPDX-License-Identifier: Apache-2.0
config DMA_SI32
bool "Si32 DMA controller driver"
default y
depends on DT_HAS_SILABS_SI32_DMA_ENABLED
help
Enable DMA controller driver for Si32 line of MCUs

418
drivers/dma/dma_si32.c

@ -0,0 +1,418 @@ @@ -0,0 +1,418 @@
/*
* Copyright (c) 2024 GARDENA GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT silabs_si32_dma
#include <SI32_CLKCTRL_A_Type.h>
#include <SI32_DMACTRL_A_Type.h>
#include <SI32_DMADESC_A_Type.h>
#include <SI32_SCONFIG_A_Type.h>
#include <si32_device.h>
#include <soc.h>
#include <zephyr/device.h>
#include <zephyr/drivers/dma.h>
#include <zephyr/irq.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(dma_si32, CONFIG_DMA_LOG_LEVEL);
#include <errno.h>
#include <inttypes.h>
#include <stdint.h>
/*
* Having just one instance allows to avoid using the passed `struct device *` arguments, which in
* turn (slightly) reduces verification code and flash space needed.
*/
BUILD_ASSERT((uintptr_t)SI32_DMACTRL_0 == (uintptr_t)DT_INST_REG_ADDR(0),
"There is just one DMA controller");
#define CHANNEL_COUNT DT_INST_PROP(0, dma_channels) /* number of used/enabled DMA channels */
struct dma_si32_channel_data {
dma_callback_t callback;
void *callback_user_data;
unsigned int tmd: 3; /* transfer mode */
unsigned int memory_to_memory: 1;
};
struct dma_si32_data {
struct dma_context ctx; /* Must be first according to the API docs */
struct dma_si32_channel_data channel_data[CHANNEL_COUNT];
};
ATOMIC_DEFINE(dma_si32_atomic, CHANNEL_COUNT);
static struct dma_si32_data dma_si32_data = {.ctx = {
.magic = DMA_MAGIC,
.atomic = dma_si32_atomic,
.dma_channels = CHANNEL_COUNT,
}};
__aligned(SI32_DMADESC_PRI_ALIGN) struct SI32_DMADESC_A_Struct channel_descriptors[CHANNEL_COUNT];
static void dma_si32_isr_handler(const uint8_t channel)
{
const struct SI32_DMADESC_A_Struct *channel_descriptor = &channel_descriptors[channel];
const dma_callback_t cb = dma_si32_data.channel_data[channel].callback;
void *user_data = dma_si32_data.channel_data[channel].callback_user_data;
int result;
LOG_INF("Channel %" PRIu8 " ISR fired", channel);
irq_disable(DMACH0_IRQn + channel);
if (SI32_DMACTRL_A_is_bus_error_set(SI32_DMACTRL_0)) {
LOG_ERR("Bus error on channel %" PRIu8, channel);
result = -EIO;
} else {
result = DMA_STATUS_COMPLETE;
__ASSERT(channel_descriptor->CONFIG.TMD == 0, "Result of success: TMD set to zero");
__ASSERT(channel_descriptor->CONFIG.NCOUNT == 0,
"Result of success: All blocks processed");
(void)channel_descriptor;
__ASSERT((SI32_DMACTRL_0->CHENSET.U32 & BIT(channel)) == 0,
"Result of success: Channel disabled");
}
if (!cb) {
return;
}
cb(DEVICE_DT_INST_GET(0), user_data, channel, result);
}
#define DMA_SI32_IRQ_CONNECT(channel) \
do { \
IRQ_CONNECT(DT_INST_IRQ_BY_IDX(0, channel, irq), \
DT_INST_IRQ_BY_IDX(0, channel, priority), dma_si32_isr_handler, \
channel, 0); \
} while (false)
#define DMA_SI32_IRQ_CONNECT_GEN(i, _) DMA_SI32_IRQ_CONNECT(i);
static int dma_si32_init(const struct device *dev)
{
ARG_UNUSED(dev);
__ASSERT(SI32_DMACTRL_0 == SI32_DMACTRL_0, "There is only one DMA controller");
__ASSERT(SI32_DMACTRL_A_get_number_of_channels(SI32_DMACTRL_0) >= CHANNEL_COUNT,
"Invalid channel count");
/* Route clock to the DMA controller */
SI32_CLKCTRL_A_enable_ahb_to_dma_controller(SI32_CLKCTRL_0);
/* Configure base address of the DMA channel descriptors */
SI32_DMACTRL_A_write_baseptr(SI32_DMACTRL_0, (uintptr_t)channel_descriptors);
/* Enable the DMA interface */
SI32_DMACTRL_A_enable_module(SI32_DMACTRL_0);
/* Primary descriptors only. This driver does do not support the more complex cases yet. */
SI32_DMACTRL_A_write_chalt(SI32_DMACTRL_0, 0);
/* AN666.pdf: The SCONFIG module contains a bit (FDMAEN) that enables faster DMA transfers
* when set to 1. It is recommended that all applications using the DMA set this bit to 1.
*/
SI32_SCONFIG_A_enter_fast_dma_mode(SI32_SCONFIG_0);
/* Install handlers for all channels */
LISTIFY(DT_NUM_IRQS(DT_DRV_INST(0)), DMA_SI32_IRQ_CONNECT_GEN, (;));
return 0;
}
static int dma_si32_config(const struct device *dev, uint32_t channel, struct dma_config *cfg)
{
ARG_UNUSED(dev);
const struct dma_block_config *block;
struct SI32_DMADESC_A_Struct *channel_descriptor;
struct dma_si32_channel_data *channel_data;
uint32_t ncount;
LOG_INF("Configuring channel %" PRIu8, channel);
if (channel >= CHANNEL_COUNT) {
LOG_ERR("Invalid channel (id %" PRIu32 ", have %d)", channel, CHANNEL_COUNT);
return -EINVAL;
}
channel_descriptor = &channel_descriptors[channel];
if (cfg == NULL) {
LOG_ERR("Missing config");
return -EINVAL;
}
if (cfg->complete_callback_en > 1) {
LOG_ERR("Callback on each block not implemented");
return -ENOTSUP;
}
if (cfg->error_callback_dis > 1) {
LOG_ERR("Error callback disabling not implemented");
return -ENOTSUP;
}
if (cfg->source_handshake > 1 || cfg->dest_handshake > 1) {
LOG_ERR("Handshake not implemented");
return -ENOTSUP;
}
if (cfg->channel_priority > 1) {
LOG_ERR("Channel priority not implemented");
return -ENOTSUP;
}
if (cfg->source_chaining_en > 1 || cfg->dest_chaining_en > 1) {
LOG_ERR("Chaining not implemented");
return -ENOTSUP;
}
if (cfg->linked_channel > 1) {
LOG_ERR("Linked channel not implemented");
return -ENOTSUP;
}
if (cfg->cyclic > 1) {
LOG_ERR("Cyclic transfer not implemented");
return -ENOTSUP;
}
if (cfg->source_data_size != 1 && cfg->source_data_size != 2 &&
cfg->source_data_size != 4) {
LOG_ERR("source_data_size must be 1, 2, or 4 (%" PRIu32 ")", cfg->source_data_size);
return -ENOTSUP;
}
if (cfg->dest_data_size != 1 && cfg->dest_data_size != 2 && cfg->dest_data_size != 4) {
LOG_ERR("dest_data_size must be 1, 2, or 4 (%" PRIu32 ")", cfg->dest_data_size);
return -ENOTSUP;
}
__ASSERT(cfg->source_data_size == cfg->dest_data_size,
"The destination size (DSTSIZE) must equal the source size (SRCSIZE).");
if (cfg->source_burst_length != cfg->dest_burst_length) {
LOG_ERR("Individual burst modes not supported");
return -ENOTSUP;
}
if (POPCOUNT(cfg->source_burst_length) > 1) {
LOG_ERR("Burst lengths must be power of two");
return -ENOTSUP;
}
if (cfg->block_count > 1) {
LOG_ERR("Scatter-Gather not implemented");
return -ENOTSUP;
}
/* Config is sane, start using it */
channel_data = &dma_si32_data.channel_data[channel];
channel_data->callback = cfg->dma_callback;
channel_data->callback_user_data = cfg->user_data;
switch (cfg->source_data_size) {
case 4:
channel_descriptor->CONFIG.SRCSIZE = 0b10;
channel_descriptor->CONFIG.DSTSIZE = 0b10;
channel_descriptor->CONFIG.RPOWER =
cfg->source_burst_length ? find_msb_set(cfg->source_burst_length) - 3 : 0;
break;
case 2:
channel_descriptor->CONFIG.SRCSIZE = 0b01;
channel_descriptor->CONFIG.DSTSIZE = 0b01;
channel_descriptor->CONFIG.RPOWER =
cfg->source_burst_length ? find_msb_set(cfg->source_burst_length) - 2 : 0;
break;
case 1:
channel_descriptor->CONFIG.SRCSIZE = 0b00;
channel_descriptor->CONFIG.DSTSIZE = 0b00;
channel_descriptor->CONFIG.RPOWER =
cfg->source_burst_length ? find_msb_set(cfg->source_burst_length) - 1 : 0;
break;
default:
LOG_ERR("source_data_size must be 1, 2, or 4 (%" PRIu32 ")", cfg->source_data_size);
return -EINVAL;
}
/* Configuration evaluated and extracted, except for its (first) block. Do this now. */
if (!cfg->head_block || cfg->block_count == 0) {
LOG_ERR("Missing head block");
return -EINVAL;
}
block = cfg->head_block;
if (block->block_size % cfg->source_data_size != 0) {
LOG_ERR("Block size not a multiple of data size");
return -EINVAL;
}
if (block->source_address % cfg->source_data_size != 0) {
LOG_ERR("Block source address not aligned with source data size");
return -EINVAL;
}
if (block->dest_address % cfg->dest_data_size != 0) {
LOG_ERR("Block dest address not aligned with dest data size");
return -EINVAL;
}
ncount = block->block_size / cfg->source_data_size - 1;
/* NCOUNT (10 bits wide) works only for values up to 1023 (1024 transfers) */
if (ncount >= 1024) {
LOG_ERR("Transfer size exceeded");
return -EINVAL;
}
channel_descriptor->CONFIG.NCOUNT = ncount;
/* Copy data to own location so that cfg must not exist during all of the channels usage */
switch (cfg->channel_direction) {
case 0b000: /* memory to memory */
/* SiM3U1xx-SiM3C1xx-RM.pdf, 16.6.2. Auto-Request Transfers: This transfer type is
* recommended for memory to memory transfers.
*/
channel_data->tmd = SI32_DMADESC_A_CONFIG_TMD_AUTO_REQUEST_VALUE;
channel_data->memory_to_memory = 1;
SI32_DMACTRL_A_disable_data_request(SI32_DMACTRL_0, channel);
break;
case 0b001: /* memory to peripheral */
case 0b010: /* peripheral to memory */
/* SiM3U1xx-SiM3C1xx-RM.pdf, 4.3.1. Basic Transfers: This transfer type is
* recommended for peripheral-to-memory or memory-to-peripheral transfers.
*/
channel_data->tmd = SI32_DMADESC_A_CONFIG_TMD_BASIC_VALUE;
channel_data->memory_to_memory = 0;
SI32_DMACTRL_A_enable_data_request(SI32_DMACTRL_0, channel);
break;
default: /* everything else is not (yet) supported */
LOG_ERR("Channel direction not implemented: %d", cfg->channel_direction);
return -ENOTSUP;
}
switch (block->source_addr_adj) {
case 0b00: /* increment */
channel_descriptor->SRCEND.U32 =
block->source_address + ncount * cfg->source_data_size;
channel_descriptor->CONFIG.SRCAIMD = channel_descriptor->CONFIG.SRCSIZE;
break;
case 0b01: /* decrement */
LOG_ERR("source_addr_adj value not supported by HW");
return -ENOTSUP;
case 0b10: /* no change */
channel_descriptor->SRCEND.U32 = block->source_address;
channel_descriptor->CONFIG.SRCAIMD = 0b11;
break;
default:
LOG_ERR("Unknown source_addr_adj value");
return -EINVAL;
}
switch (block->dest_addr_adj) {
case 0b00: /* increment */
channel_descriptor->DSTEND.U32 = block->dest_address + ncount * cfg->dest_data_size;
channel_descriptor->CONFIG.DSTAIMD = channel_descriptor->CONFIG.DSTSIZE;
break;
case 0b01: /* decrement */
LOG_ERR("dest_addr_adj value not supported by HW");
return -ENOTSUP;
case 0b10: /* no change */
channel_descriptor->DSTEND.U32 = block->dest_address;
channel_descriptor->CONFIG.DSTAIMD = 0b11;
break;
default:
LOG_ERR("Unknown dest_addr_adj value");
return -EINVAL;
}
SI32_DMACTRL_A_enable_channel(SI32_DMACTRL_0, channel);
return 0;
}
static int dma_si32_start(const struct device *dev, const uint32_t channel)
{
ARG_UNUSED(dev);
struct SI32_DMADESC_A_Struct *channel_desc = &channel_descriptors[channel];
struct dma_si32_channel_data *channel_data;
LOG_INF("Starting channel %" PRIu8, channel);
if (channel >= CHANNEL_COUNT) {
LOG_ERR("Invalid channel (id %" PRIu32 ", have %d)", channel, CHANNEL_COUNT);
return -EINVAL;
}
channel_data = &dma_si32_data.channel_data[channel];
/* All of this should be set by our own, previously running code. During development
* however, it is still useful to double check here.
*/
__ASSERT(SI32_CLKCTRL_0->AHBCLKG.DMACEN,
"AHB clock to the DMA controller must be enabled.");
__ASSERT(SI32_DMACTRL_A_is_enabled(SI32_DMACTRL_0), "DMA controller must be enabled.");
__ASSERT(SI32_DMACTRL_0->BASEPTR.U32 == (uintptr_t)channel_descriptors,
"Address location of the channel transfer descriptors (BASEPTR) must be set.");
__ASSERT(SI32_DMACTRL_A_is_primary_selected(SI32_DMACTRL_0, channel),
"Primary descriptors must be used for basic and auto-request operations.");
__ASSERT(SI32_SCONFIG_0->CONFIG.FDMAEN, "Fast mode is recommened to be enabled.");
__ASSERT(SI32_DMACTRL_0->CHENSET.U32 & BIT(channel), "Channel must be enabled.");
__ASSERT(SI32_DMACTRL_0->CHSTATUS.U32 & BIT(channel),
"Channel must be waiting for request");
channel_desc->CONFIG.TMD = channel_data->tmd;
/* Get rid of potentially lingering bus errors. */
SI32_DMACTRL_A_clear_bus_error(SI32_DMACTRL_0);
/* Enable interrupt for this DMA channels. */
irq_enable(DMACH0_IRQn + channel);
/* memory-to-memory transfers have to be started by this driver. When peripherals are
* involved, the caller has to enable the peripheral to start the transfer.
*/
if (dma_si32_data.channel_data[channel].memory_to_memory) {
__ASSERT((SI32_DMACTRL_0->CHREQMSET.U32 & BIT(channel)),
"Peripheral data requests for the channel must be disabled");
SI32_DMACTRL_A_generate_software_request(SI32_DMACTRL_0, channel);
} else {
__ASSERT(!(SI32_DMACTRL_0->CHREQMSET.U32 & BIT(channel)),
"Data requests for the channel must be enabled");
}
return 0;
}
static int dma_si32_stop(const struct device *dev, const uint32_t channel)
{
ARG_UNUSED(dev);
if (channel >= CHANNEL_COUNT) {
LOG_ERR("Invalid channel (id %" PRIu32 ", have %d)", channel, CHANNEL_COUNT);
return -EINVAL;
}
irq_disable(DMACH0_IRQn + channel);
channel_descriptors[channel].CONFIG.TMD = 0; /* Stop the DMA channel. */
return 0;
}
static const struct dma_driver_api dma_si32_driver_api = {
.config = dma_si32_config,
.start = dma_si32_start,
.stop = dma_si32_stop,
};
DEVICE_DT_INST_DEFINE(0, &dma_si32_init, NULL, NULL, NULL, POST_KERNEL, CONFIG_DMA_INIT_PRIORITY,
&dma_si32_driver_api);
Loading…
Cancel
Save