diff --git a/drivers/dma/CMakeLists.txt b/drivers/dma/CMakeLists.txt index 00b424a37b6802..b3bf9e6f90e9a9 100644 --- a/drivers/dma/CMakeLists.txt +++ b/drivers/dma/CMakeLists.txt @@ -39,6 +39,7 @@ 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_SILABS_LDMA dma_silabs_ldma.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) diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 964727e61cd6b7..ccb660301735f9 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -72,6 +72,8 @@ source "drivers/dma/Kconfig.sedi" source "drivers/dma/Kconfig.si32" +source "drivers/dma/Kconfig.silabs" + source "drivers/dma/Kconfig.smartbond" source "drivers/dma/Kconfig.nxp_sof_host_dma" diff --git a/drivers/dma/Kconfig.silabs b/drivers/dma/Kconfig.silabs new file mode 100644 index 00000000000000..8b61f422854d70 --- /dev/null +++ b/drivers/dma/Kconfig.silabs @@ -0,0 +1,21 @@ +# Copyright (c) 2024 Silicon-labs +# SPDX-License-Identifier: Apache-2.0 + +config DMA_SILABS_LDMA + bool "Silabs DMA driver" + default y + select SYS_MEM_BLOCKS + select SOC_GECKO_LDMA + depends on DT_HAS_SILABS_LDMA_ENABLED + help + Driver for Silabs DMA. + +if DMA_SILABS_LDMA + +config DMA_MAX_DESCRIPTOR + int "Max Number of block_config (LDMA_Descriptor)" + default 8 + help + Max Number of block_config (LDMA_Descriptor) + +endif diff --git a/drivers/dma/dma_silabs_ldma.c b/drivers/dma/dma_silabs_ldma.c new file mode 100644 index 00000000000000..5baaac18b2464c --- /dev/null +++ b/drivers/dma/dma_silabs_ldma.c @@ -0,0 +1,527 @@ +/* + * Copyright (c) 2024 Silicon Laboratories Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "em_ldma.h" + +#define DT_DRV_COMPAT silabs_ldma + +#define DMA_IRQ_PRIORITY 3 + +LOG_MODULE_REGISTER(silabs_dma, CONFIG_DMA_LOG_LEVEL); + +struct dma_silabs_channel { + enum dma_channel_direction dir; + uint32_t complete_callback_en; + atomic_t busy; + void *user_data; + dma_callback_t cb; + LDMA_TransferCfg_t xfer_config; + LDMA_Descriptor_t *desc; +}; + +struct dma_silabs_config { + void (*config_irq)(const struct device *dev); + const struct device *clock_dev; +}; + +struct dma_silabs_data { + struct dma_context dma_ctx; + struct dma_silabs_channel *dma_chan_table; + struct sys_mem_blocks *dma_desc_pool; +}; + +static int dma_silabs_get_blocksize(uint32_t src_blen, uint32_t dst_blen, uint32_t src_dsize) +{ + const static struct { + int native; + int efr; + } ldma_blocksize_map[] = { + { 0x0001, ldmaCtrlBlockSizeUnit1 }, + { 0x0002, ldmaCtrlBlockSizeUnit2 }, + { 0x0003, ldmaCtrlBlockSizeUnit3 }, + { 0x0004, ldmaCtrlBlockSizeUnit4 }, + { 0x0006, ldmaCtrlBlockSizeUnit6 }, + { 0x0008, ldmaCtrlBlockSizeUnit8 }, + { 0x0010, ldmaCtrlBlockSizeUnit16 }, + { 0x0020, ldmaCtrlBlockSizeUnit32 }, + { 0x0040, ldmaCtrlBlockSizeUnit64 }, + { 0x0080, ldmaCtrlBlockSizeUnit128 }, + { 0x0100, ldmaCtrlBlockSizeUnit256 }, + { 0x0200, ldmaCtrlBlockSizeUnit512 }, + { 0x0400, ldmaCtrlBlockSizeUnit1024 } + }; + uint32_t arb_unit; + + if (src_blen != dst_blen) { + LOG_ERR("Source burst length (%u) and destination burst length(%u) must be equal", + src_blen, dst_blen); + return -ENOTSUP; + } + + if (src_blen % src_dsize) { + LOG_ERR("burst length (%u) and data size (%u) mismatch", src_blen, dst_blen); + return -EINVAL; + } + + arb_unit = src_blen / src_dsize; + + for (int i = 0; i < ARRAY_SIZE(ldma_blocksize_map); i++) { + if (ldma_blocksize_map[i].native == arb_unit) { + return ldma_blocksize_map[i].efr; + } + } + return -EINVAL; +} + +static int dma_silabs_block_to_descriptor(struct dma_config *config, + struct dma_silabs_channel *chan_conf, + struct dma_block_config *block, LDMA_Descriptor_t *desc) +{ + int ret, src_size, xfer_count; + + if (block->dest_scatter_count || block->source_gather_count || + block->source_gather_interval || block->dest_scatter_interval || + block->dest_reload_en || block->source_reload_en) { + return -ENOTSUP; + } + + if ((block->source_gather_en || block->dest_scatter_en) && config->block_count == 1) { + LOG_WRN("DMA scatter_gather enabled but there is only one descriptor " + "configured"); + } + + memset(desc, 0, sizeof(*desc)); + + desc->xfer.structReq = 1; + + if (config->source_data_size != config->dest_data_size) { + LOG_ERR("Source data size(%u) and destination data size(%u) must be equal", + config->source_data_size, config->dest_data_size); + return -ENOTSUP; + } + + if (config->source_data_size < 1 || config->source_data_size > 4) { + return -ENOTSUP; + } + + src_size = config->source_data_size; + desc->xfer.size = LOG2(src_size); + + if (block->block_size % config->source_data_size) { + xfer_count = block->block_size / config->source_data_size; + } else { + xfer_count = block->block_size / config->source_data_size - 1; + } + + if (xfer_count > LDMA_DESCRIPTOR_MAX_XFER_SIZE) { + return -ENOTSUP; + } + + desc->xfer.xferCnt = xfer_count; + + /* Warning : High LDMA blockSize (high burst) mean a large transfer + * without LDMA controller re-arbitration. + */ + ret = dma_silabs_get_blocksize(config->source_burst_length, config->dest_burst_length, + config->source_data_size); + if (ret < 0) { + return ret; + } + + desc->xfer.blockSize = ret; + + /* if complete_callbacks_enabled, callback is called at then end of each descriptor + * in the list (block for zephyr) + */ + desc->xfer.doneIfs = config->complete_callback_en; + desc->xfer.reqMode = ldmaCtrlReqModeAll; + desc->xfer.ignoreSrec = block->flow_control_mode; + + /* In silabs LDMA, increment sign is managed with the transfer configuration + * which is common for all descs of the channel. Zephyr DMA API allows + * to manage increment sign for each block desc which can't be done with + * silabs LDMA. If increment sign is different in 2 block desc, then an + * error is returned. + */ + if (block->source_addr_adj != DMA_ADDR_ADJ_NO_CHANGE && + block->source_addr_adj != chan_conf->xfer_config.ldmaCfgSrcIncSign) { + return -ENOTSUP; + } + + if (block->source_addr_adj == DMA_ADDR_ADJ_NO_CHANGE) { + desc->xfer.srcInc = ldmaCtrlSrcIncNone; + } else { + desc->xfer.srcInc = ldmaCtrlSrcIncOne; + } + + if (block->dest_addr_adj == DMA_ADDR_ADJ_NO_CHANGE) { + desc->xfer.dstInc = ldmaCtrlDstIncNone; + } else { + desc->xfer.dstInc = ldmaCtrlDstIncOne; + } + + desc->xfer.srcAddrMode = ldmaCtrlSrcAddrModeAbs; + desc->xfer.dstAddrMode = ldmaCtrlDstAddrModeAbs; + + if (block->source_address == 0) { + LOG_WRN("source_buffer address is null."); + } + if (block->dest_address == 0) { + LOG_WRN("dest_buffer address is null."); + } + + desc->xfer.srcAddr = block->source_address; + desc->xfer.dstAddr = block->dest_address; + + return 0; +} + +static int dma_silabs_release_descriptor(struct dma_silabs_data *data, LDMA_Descriptor_t *desc) +{ + LDMA_Descriptor_t *head_desc, *next_desc; + int ret; + + head_desc = desc; + while (desc) { + next_desc = LDMA_DESCRIPTOR_LINKABS_LINKADDR_TO_ADDR(desc->xfer.linkAddr); + ret = sys_mem_blocks_free(data->dma_desc_pool, 1, (void **)&desc); + if (ret) { + return ret; + } + desc = next_desc; + /* Protection against descriptor loop*/ + if (desc == head_desc) { + break; + } + } + + return 0; +} + +static int dma_silabs_configure_descriptor(struct dma_config *config, struct dma_silabs_data *data, + struct dma_silabs_channel *chan_conf) +{ + struct dma_block_config *head_block = config->head_block; + struct dma_block_config *block = config->head_block; + LDMA_Descriptor_t *desc, *prev_desc; + int ret; + + /* Descriptors configuration + * block refers to user configured block (dma_block_config structure from dma.h) + * desc refers to driver configured block (LDMA_Descriptor_t structure from silabs + * hal) + */ + prev_desc = NULL; + while (block) { + ret = sys_mem_blocks_alloc(data->dma_desc_pool, 1, (void **)&desc); + if (ret) { + goto err; + } + + ret = dma_silabs_block_to_descriptor(config, chan_conf, block, desc); + if (ret) { + goto err; + } + + if (!prev_desc) { + chan_conf->desc = desc; + } else { + prev_desc->xfer.linkAddr = LDMA_DESCRIPTOR_LINKABS_ADDR_TO_LINKADDR(desc); + prev_desc->xfer.linkMode = ldmaLinkModeAbs; + prev_desc->xfer.link = 1; + } + + prev_desc = desc; + block = block->next_block; + if (block == head_block) { + block = NULL; + prev_desc->xfer.linkAddr = + LDMA_DESCRIPTOR_LINKABS_ADDR_TO_LINKADDR(chan_conf->desc); + prev_desc->xfer.linkMode = ldmaLinkModeAbs; + prev_desc->xfer.link = 1; + } + } + + return 0; +err: + /* Free all eventually allocated descriptor */ + (void)dma_silabs_release_descriptor(data, chan_conf->desc); + + return ret; + +} + +static void dma_silabs_irq_handler(const struct device *dev, uint32_t id) +{ + const struct dma_silabs_data *data = dev->data; + struct dma_silabs_channel *chan; + int status; + uint32_t pending, chnum; + + pending = LDMA_IntGetEnabled(); + + for (chnum = 0; chnum < data->dma_ctx.dma_channels; chnum++) { + chan = &data->dma_chan_table[chnum]; + status = DMA_STATUS_COMPLETE; + + if (pending & LDMA_IF_ERROR) { + if (chan->cb) { + chan->cb(dev, chan->user_data, chnum, -EIO); + } + } else if (pending & BIT(chnum)) { + LDMA_IntClear(BIT(chnum)); + + /* Is it only an interrupt for the end of a descriptor and not a complete + * transfer. + */ + if (chan->complete_callback_en) { + status = DMA_STATUS_BLOCK; + } else { + atomic_clear(&chan->busy); + } + + if (chan->cb) { + chan->cb(dev, chan->user_data, chnum, status); + } + } + } +} + +static int dma_silabs_configure(const struct device *dev, uint32_t channel, + struct dma_config *config) +{ + struct dma_silabs_data *data = dev->data; + struct dma_silabs_channel *chan_conf = &data->dma_chan_table[channel]; + LDMA_TransferCfg_t *xfer_config = &chan_conf->xfer_config; + int ret; + + if (channel > data->dma_ctx.dma_channels) { + return -EINVAL; + } + + if (!config) { + return -EINVAL; + } + + if (atomic_get(&chan_conf->busy)) { + LOG_ERR("DMA channel %u is busy", channel); + return -EBUSY; + } + + /* Release previously owned descriptor for this channel*/ + ret = dma_silabs_release_descriptor(data, chan_conf->desc); + if (ret) { + return ret; + } + + if (config->dest_data_size != config->source_data_size) { + LOG_ERR("source and dest data size differ"); + return -ENOTSUP; + } + + if (config->source_handshake || config->dest_handshake || config->source_chaining_en || + config->dest_chaining_en || config->linked_channel) { + return -ENOTSUP; + } + + LDMA_StopTransfer(channel); + + chan_conf->user_data = config->user_data; + chan_conf->cb = config->dma_callback; + chan_conf->dir = config->channel_direction; + chan_conf->complete_callback_en = config->complete_callback_en; + + memset(xfer_config, 0, sizeof(*xfer_config)); + + switch (config->channel_direction) { + case MEMORY_TO_MEMORY: + break; + case PERIPHERAL_TO_MEMORY: + case MEMORY_TO_PERIPHERAL: + xfer_config->ldmaReqSel = config->dma_slot; + break; + case PERIPHERAL_TO_PERIPHERAL: + case HOST_TO_MEMORY: + case MEMORY_TO_HOST: + default: + return -ENOTSUP; + } + + /* Directly transform channel_priority into efr priority */ + if (config->channel_priority < ldmaCfgArbSlotsAs1 || + config->channel_priority > ldmaCfgArbSlotsAs8) { + return -EINVAL; + } + xfer_config->ldmaCfgArbSlots = config->channel_priority; + + switch (config->head_block->source_addr_adj) { + case DMA_ADDR_ADJ_INCREMENT: + xfer_config->ldmaCfgSrcIncSign = ldmaCfgSrcIncSignPos; + break; + case DMA_ADDR_ADJ_DECREMENT: + xfer_config->ldmaCfgSrcIncSign = ldmaCfgSrcIncSignNeg; + break; + case DMA_ADDR_ADJ_NO_CHANGE: + xfer_config->ldmaCfgSrcIncSign = ldmaCfgSrcIncSignPos; + break; + default: + LOG_ERR("Addr Adjustement error %d", config->head_block->source_addr_adj); + break; + } + + switch (config->head_block->dest_addr_adj) { + case DMA_ADDR_ADJ_INCREMENT: + xfer_config->ldmaCfgDstIncSign = ldmaCfgDstIncSignPos; + break; + case DMA_ADDR_ADJ_DECREMENT: + xfer_config->ldmaCfgDstIncSign = ldmaCfgDstIncSignNeg; + break; + case DMA_ADDR_ADJ_NO_CHANGE: + xfer_config->ldmaCfgDstIncSign = ldmaCfgDstIncSignPos; + break; + default: + break; + } + + ret = dma_silabs_configure_descriptor(config, data, chan_conf); + if (ret) { + return ret; + } + + atomic_set_bit(data->dma_ctx.atomic, channel); + + return 0; +} + +static int dma_silabs_start(const struct device *dev, uint32_t channel) +{ + + const struct dma_silabs_data *data = dev->data; + struct dma_silabs_channel *chan = &data->dma_chan_table[channel]; + + if (channel > data->dma_ctx.dma_channels) { + return -EINVAL; + } + + atomic_inc(&chan->busy); + + LDMA_StartTransfer(channel, &chan->xfer_config, chan->desc); + + return 0; +} + +static int dma_silabs_stop(const struct device *dev, uint32_t channel) +{ + const struct dma_silabs_data *data = dev->data; + struct dma_silabs_channel *chan = &data->dma_chan_table[channel]; + + if (channel > data->dma_ctx.dma_channels) { + return -EINVAL; + } + + LDMA_StopTransfer(channel); + + atomic_clear(&chan->busy); + + LDMA_IntClear(BIT(channel)); + + return 0; +} + +static int dma_silabs_get_status(const struct device *dev, uint32_t channel, + struct dma_status *status) +{ + const struct dma_silabs_data *data = dev->data; + + if (channel > data->dma_ctx.dma_channels) { + return -EINVAL; + } + + if (!atomic_test_bit(data->dma_ctx.atomic, channel)) { + return -EINVAL; + } + + status->busy = data->dma_chan_table[channel].busy; + status->dir = data->dma_chan_table[channel].dir; + + return 0; +} + +static int dma_silabs_init(const struct device *dev) +{ + const struct dma_silabs_config *config = dev->config; + LDMA_Init_t dmaInit = { + /* 0x7 indicate that the 8 channels have round robin priority. */ + .ldmaInitCtrlNumFixed = 0x7, + .ldmaInitIrqPriority = DMA_IRQ_PRIORITY, + }; + + /* Clock is managed by em_ldma */ + + LDMA_Init(&dmaInit); + + /* LDMA_Init configure IRQ but we want IRQ to match with configured one in the dts*/ + config->config_irq(dev); + + return 0; +} + +static DEVICE_API(dma, dma_funcs) = { + .config = dma_silabs_configure, + .start = dma_silabs_start, + .stop = dma_silabs_stop, + .get_status = dma_silabs_get_status +}; + +#define SILABS_DMA_IRQ_CONNECT(n, inst) \ + IRQ_CONNECT(DT_INST_IRQ_BY_IDX(inst, n, irq), DT_INST_IRQ_BY_IDX(inst, n, priority), \ + dma_silabs_irq_handler, DEVICE_DT_INST_GET(inst), 0); \ + irq_enable(DT_INST_IRQ_BY_IDX(inst, n, irq)); + +#define CONFIGURE_ALL_IRQS(inst, n) LISTIFY(n, SILABS_DMA_IRQ_CONNECT, (), inst) + +#define DMA_SILABS_LDMA_INIT(inst) \ + \ + static void silabs_dma_irq_configure_##inst(const struct device *dev) \ + { \ + ARG_UNUSED(dev); \ + CONFIGURE_ALL_IRQS(inst, DT_NUM_IRQS(DT_DRV_INST(inst))); \ + }; \ + \ + const struct dma_silabs_config dma_silabs_config_##inst = { \ + .config_irq = silabs_dma_irq_configure_##inst \ + }; \ + \ + static ATOMIC_DEFINE(dma_channels_atomic_##inst, DT_INST_PROP(inst, dma_channels)); \ + \ + static struct dma_silabs_channel \ + dma_silabs_channel_##inst[DT_INST_PROP(inst, dma_channels)]; \ + \ + SYS_MEM_BLOCKS_DEFINE_STATIC(desc_pool_##inst, sizeof(LDMA_Descriptor_t), \ + CONFIG_DMA_MAX_DESCRIPTOR, 4); \ + \ + static struct dma_silabs_data dma_silabs_data_##inst = { \ + .dma_ctx.magic = DMA_MAGIC, \ + .dma_ctx.dma_channels = DT_INST_PROP(inst, dma_channels), \ + .dma_ctx.atomic = dma_channels_atomic_##inst, \ + .dma_chan_table = dma_silabs_channel_##inst, \ + .dma_desc_pool = &desc_pool_##inst \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, &dma_silabs_init, NULL, &dma_silabs_data_##inst, \ + &dma_silabs_config_##inst, PRE_KERNEL_1, CONFIG_DMA_INIT_PRIORITY, \ + &dma_funcs); + +DT_INST_FOREACH_STATUS_OKAY(DMA_SILABS_LDMA_INIT); diff --git a/modules/hal_silabs/simplicity_sdk/CMakeLists.txt b/modules/hal_silabs/simplicity_sdk/CMakeLists.txt index b59c14d11ed5a6..a5bf9b683be3fd 100644 --- a/modules/hal_silabs/simplicity_sdk/CMakeLists.txt +++ b/modules/hal_silabs/simplicity_sdk/CMakeLists.txt @@ -196,6 +196,7 @@ zephyr_library_sources_ifdef(CONFIG_SOC_GECKO_I2C ${EMLIB_DIR}/src/em_i zephyr_library_sources_ifdef(CONFIG_SOC_GECKO_LETIMER ${EMLIB_DIR}/src/em_letimer.c) zephyr_library_sources_ifdef(CONFIG_SOC_GECKO_LEUART ${EMLIB_DIR}/src/em_leuart.c) zephyr_library_sources_ifdef(CONFIG_SOC_GECKO_MSC ${EMLIB_DIR}/src/em_msc.c) +zephyr_library_sources_ifdef(CONFIG_SOC_GECKO_LDMA ${EMLIB_DIR}/src/em_ldma.c) zephyr_library_sources_ifdef(CONFIG_SOC_GECKO_PRS ${EMLIB_DIR}/src/em_prs.c) zephyr_library_sources_ifdef(CONFIG_SOC_GECKO_RMU ${EMLIB_DIR}/src/em_rmu.c) zephyr_library_sources_ifdef(CONFIG_SOC_GECKO_RTC ${EMLIB_DIR}/src/em_rtc.c)