| /* |
| * Copyright (c) 2024 Silicon Laboratories Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <errno.h> |
| #include <zephyr/irq.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/sys/sys_io.h> |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/dma.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/types.h> |
| #include "rsi_rom_udma_wrapper.h" |
| #include "rsi_udma.h" |
| #include "sl_status.h" |
| |
| #define DT_DRV_COMPAT silabs_siwx91x_dma |
| #define DMA_MAX_TRANSFER_COUNT 1024 |
| #define DMA_CH_PRIORITY_HIGH 1 |
| #define DMA_CH_PRIORITY_LOW 0 |
| #define UDMA_ADDR_INC_NONE 0x03 |
| |
| LOG_MODULE_REGISTER(si91x_dma, CONFIG_DMA_LOG_LEVEL); |
| |
| enum { |
| TRANSFER_MEM_TO_MEM, |
| TRANSFER_TO_OR_FROM_PER, |
| }; |
| |
| struct dma_siwx91x_config { |
| UDMA0_Type *reg; /* UDMA register base address */ |
| uint8_t channels; /* UDMA channel count */ |
| uint8_t irq_number; /* IRQ number */ |
| RSI_UDMA_DESC_T *sram_desc_addr; /* SRAM Address for UDMA Descriptor Storage */ |
| const struct device *clock_dev; |
| clock_control_subsys_t clock_subsys; |
| void (*irq_configure)(void); /* IRQ configure function */ |
| }; |
| |
| struct dma_siwx91x_data { |
| UDMA_Channel_Info *chan_info; |
| dma_callback_t dma_callback; /* User callback */ |
| void *cb_data; /* User callback data */ |
| RSI_UDMA_DATACONTEXT_T udma_handle; /* Buffer to store UDMA handle |
| * related information |
| */ |
| }; |
| |
| static int siwx91x_transfer_direction(uint32_t dir) |
| { |
| if (dir == MEMORY_TO_MEMORY) { |
| return TRANSFER_MEM_TO_MEM; |
| } |
| |
| if (dir == MEMORY_TO_PERIPHERAL || dir == PERIPHERAL_TO_MEMORY) { |
| return TRANSFER_TO_OR_FROM_PER; |
| } |
| |
| return -EINVAL; |
| } |
| |
| static int siwx91x_data_width(uint32_t data_width) |
| { |
| switch (data_width) { |
| case 1: |
| return SRC_SIZE_8; |
| case 2: |
| return SRC_SIZE_16; |
| case 4: |
| return SRC_SIZE_32; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static bool siwx91x_is_burst_length_valid(uint32_t blen) |
| { |
| switch (blen / 8) { |
| case 1: |
| return true; /* 8-bit burst */ |
| default: |
| return false; |
| } |
| } |
| |
| static int siwx91x_addr_adjustment(uint32_t adjustment) |
| { |
| switch (adjustment) { |
| case 0: |
| return 0; /* Addr Increment */ |
| case 2: |
| return UDMA_ADDR_INC_NONE; /* No Address increment */ |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int siwx91x_channel_config(const struct device *dev, RSI_UDMA_HANDLE_T udma_handle, |
| uint32_t channel, const struct dma_config *config) |
| { |
| uint32_t dma_transfer_num = config->head_block->block_size / config->source_data_size; |
| const struct dma_siwx91x_config *cfg = dev->config; |
| struct dma_siwx91x_data *data = dev->data; |
| UDMA_RESOURCES udma_resources = { |
| .reg = cfg->reg, |
| .udma_irq_num = cfg->irq_number, |
| /* SRAM address where UDMA descriptor is stored */ |
| .desc = cfg->sram_desc_addr, |
| }; |
| RSI_UDMA_CHA_CONFIG_DATA_T channel_control = { |
| .transferType = UDMA_MODE_BASIC, |
| }; |
| RSI_UDMA_CHA_CFG_T channel_config = {}; |
| int status; |
| |
| if (siwx91x_transfer_direction(config->channel_direction) < 0) { |
| return -EINVAL; |
| } |
| |
| channel_config.channelPrioHigh = config->channel_priority; |
| channel_config.periphReq = siwx91x_transfer_direction(config->channel_direction); |
| channel_config.dmaCh = channel; |
| |
| if (channel_config.periphReq) { |
| /* Arbitration power for peripheral<->memory transfers */ |
| channel_control.rPower = ARBSIZE_1; |
| } else { |
| /* Arbitration power for mem-mem transfers */ |
| channel_control.rPower = ARBSIZE_1024; |
| } |
| |
| /* Obtain the number of transfers */ |
| if (dma_transfer_num >= DMA_MAX_TRANSFER_COUNT) { |
| /* Maximum number of transfers is 1024 */ |
| channel_control.totalNumOfDMATrans = DMA_MAX_TRANSFER_COUNT - 1; |
| } else { |
| channel_control.totalNumOfDMATrans = dma_transfer_num; |
| } |
| |
| if (siwx91x_data_width(config->source_data_size) < 0 || |
| siwx91x_data_width(config->dest_data_size) < 0) { |
| return -EINVAL; |
| } |
| if (siwx91x_is_burst_length_valid(config->source_burst_length) == false || |
| siwx91x_is_burst_length_valid(config->dest_burst_length) == false) { |
| return -EINVAL; |
| } |
| |
| channel_control.srcSize = siwx91x_data_width(config->source_data_size); |
| channel_control.dstSize = siwx91x_data_width(config->dest_data_size); |
| if (siwx91x_addr_adjustment(config->head_block->source_addr_adj) < 0 || |
| siwx91x_addr_adjustment(config->head_block->dest_addr_adj) < 0) { |
| return -EINVAL; |
| } |
| |
| if (siwx91x_addr_adjustment(config->head_block->source_addr_adj) == 0) { |
| channel_control.srcInc = channel_control.srcSize; |
| } else { |
| channel_control.srcInc = UDMA_SRC_INC_NONE; |
| } |
| |
| if (siwx91x_addr_adjustment(config->head_block->dest_addr_adj) == 0) { |
| channel_control.dstInc = channel_control.dstSize; |
| } else { |
| channel_control.dstInc = UDMA_DST_INC_NONE; |
| } |
| |
| status = UDMAx_ChannelConfigure(&udma_resources, (uint8_t)channel, |
| config->head_block->source_address, |
| config->head_block->dest_address, |
| dma_transfer_num, channel_control, |
| &channel_config, NULL, data->chan_info, |
| udma_handle); |
| if (status) { |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| /* Function to configure UDMA channel for transfer */ |
| static int siwx91x_dma_configure(const struct device *dev, uint32_t channel, |
| struct dma_config *config) |
| { |
| const struct dma_siwx91x_config *cfg = dev->config; |
| struct dma_siwx91x_data *data = dev->data; |
| void *udma_handle = &data->udma_handle; |
| int status; |
| |
| /* Expecting a fixed channel number between 0-31 for dma0 and 0-11 for ulpdma */ |
| if (channel >= cfg->channels) { |
| return -EINVAL; |
| } |
| |
| /* Disable the channel before configuring */ |
| if (RSI_UDMA_ChannelDisable(udma_handle, channel) != 0) { |
| return -EIO; |
| } |
| |
| if (config->channel_priority != DMA_CH_PRIORITY_LOW && |
| config->channel_priority != DMA_CH_PRIORITY_HIGH) { |
| return -EINVAL; |
| } |
| |
| /* Configure dma channel for transfer */ |
| status = siwx91x_channel_config(dev, udma_handle, channel, config); |
| if (status) { |
| return status; |
| } |
| |
| data->dma_callback = config->dma_callback; |
| data->cb_data = config->user_data; |
| |
| return 0; |
| } |
| |
| /* Function to reload UDMA channel for new transfer */ |
| static int siwx91x_dma_reload(const struct device *dev, uint32_t channel, uint32_t src, |
| uint32_t dst, size_t size) |
| { |
| const struct dma_siwx91x_config *cfg = dev->config; |
| struct dma_siwx91x_data *data = dev->data; |
| void *udma_handle = &data->udma_handle; |
| uint32_t desc_src_addr; |
| uint32_t desc_dst_addr; |
| uint32_t length; |
| RSI_UDMA_DESC_T *udma_table = cfg->sram_desc_addr; |
| |
| /* Expecting a fixed channel number between 0-31 for dma0 and 0-11 for ulpdma */ |
| if (channel >= cfg->channels) { |
| return -EINVAL; |
| } |
| |
| /* Disable the channel before reloading transfer */ |
| if (RSI_UDMA_ChannelDisable(udma_handle, channel) != 0) { |
| return -EIO; |
| } |
| |
| /* Update new channel info to dev->data structure */ |
| data->chan_info[channel].SrcAddr = src; |
| data->chan_info[channel].DestAddr = dst; |
| data->chan_info[channel].Size = size; |
| |
| /* Update new transfer size to dev->data structure */ |
| if (size >= DMA_MAX_TRANSFER_COUNT) { |
| data->chan_info[channel].Cnt = DMA_MAX_TRANSFER_COUNT - 1; |
| } else { |
| data->chan_info[channel].Cnt = size; |
| } |
| |
| /* Program the DMA descriptors with new transfer data information. */ |
| if (udma_table[channel].vsUDMAChaConfigData1.srcInc != UDMA_SRC_INC_NONE) { |
| length = data->chan_info[channel].Cnt |
| << udma_table[channel].vsUDMAChaConfigData1.srcInc; |
| desc_src_addr = src + (length - 1); |
| udma_table[channel].pSrcEndAddr = (void *)desc_src_addr; |
| } |
| |
| if (udma_table[channel].vsUDMAChaConfigData1.dstInc != UDMA_SRC_INC_NONE) { |
| length = data->chan_info[channel].Cnt |
| << udma_table[channel].vsUDMAChaConfigData1.dstInc; |
| desc_dst_addr = dst + (length - 1); |
| udma_table[channel].pDstEndAddr = (void *)desc_dst_addr; |
| } |
| |
| udma_table[channel].vsUDMAChaConfigData1.totalNumOfDMATrans = data->chan_info[channel].Cnt; |
| udma_table[channel].vsUDMAChaConfigData1.transferType = UDMA_MODE_BASIC; |
| |
| return 0; |
| } |
| |
| /* Function to start a DMA transfer */ |
| static int siwx91x_dma_start(const struct device *dev, uint32_t channel) |
| { |
| const struct dma_siwx91x_config *cfg = dev->config; |
| RSI_UDMA_DESC_T *udma_table = cfg->sram_desc_addr; |
| struct dma_siwx91x_data *data = dev->data; |
| void *udma_handle = &data->udma_handle; |
| |
| /* Expecting a fixed channel number between 0-31 for dma0 and 0-11 for ulpdma */ |
| if (channel >= cfg->channels) { |
| return -EINVAL; |
| } |
| |
| if (RSI_UDMA_ChannelEnable(udma_handle, channel) != 0) { |
| return -EINVAL; |
| } |
| |
| /* Check if the transfer type is memory-memory */ |
| if (udma_table[channel].vsUDMAChaConfigData1.srcInc != UDMA_SRC_INC_NONE && |
| udma_table[channel].vsUDMAChaConfigData1.dstInc != UDMA_DST_INC_NONE) { |
| /* Apply software trigger to start transfer */ |
| sys_set_bit((mem_addr_t)&cfg->reg->CHNL_SW_REQUEST, channel); |
| } |
| |
| return 0; |
| } |
| |
| /* Function to stop a DMA transfer */ |
| static int siwx91x_dma_stop(const struct device *dev, uint32_t channel) |
| { |
| const struct dma_siwx91x_config *cfg = dev->config; |
| struct dma_siwx91x_data *data = dev->data; |
| void *udma_handle = &data->udma_handle; |
| |
| /* Expecting a fixed channel number between 0-31 for dma0 and 0-11 for ulpdma */ |
| if (channel >= cfg->channels) { |
| return -EINVAL; |
| } |
| |
| if (RSI_UDMA_ChannelDisable(udma_handle, channel) != 0) { |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| /* Function to fetch DMA channel status */ |
| static int siwx91x_dma_get_status(const struct device *dev, uint32_t channel, |
| struct dma_status *stat) |
| { |
| const struct dma_siwx91x_config *cfg = dev->config; |
| RSI_UDMA_DESC_T *udma_table = cfg->sram_desc_addr; |
| |
| /* Expecting a fixed channel number between 0-31 for dma0 and 0-11 for ulpdma */ |
| if (channel >= cfg->channels) { |
| return -EINVAL; |
| } |
| |
| /* Read the channel status register */ |
| stat->busy = sys_test_bit((mem_addr_t)&cfg->reg->CHANNEL_STATUS_REG, channel); |
| |
| /* Obtain the transfer direction from channel descriptors */ |
| if (udma_table[channel].vsUDMAChaConfigData1.srcInc == UDMA_SRC_INC_NONE) { |
| stat->dir = PERIPHERAL_TO_MEMORY; |
| } else if (udma_table[channel].vsUDMAChaConfigData1.dstInc == UDMA_DST_INC_NONE) { |
| stat->dir = MEMORY_TO_PERIPHERAL; |
| } else { |
| stat->dir = MEMORY_TO_MEMORY; |
| } |
| |
| return 0; |
| } |
| |
| /* Function to initialize DMA peripheral */ |
| static int siwx91x_dma_init(const struct device *dev) |
| { |
| const struct dma_siwx91x_config *cfg = dev->config; |
| struct dma_siwx91x_data *data = dev->data; |
| void *udma_handle = NULL; |
| UDMA_RESOURCES udma_resources = { |
| .reg = cfg->reg, /* UDMA register base address */ |
| .udma_irq_num = cfg->irq_number, |
| .desc = cfg->sram_desc_addr, |
| }; |
| int ret; |
| |
| ret = clock_control_on(cfg->clock_dev, cfg->clock_subsys); |
| if (ret) { |
| return ret; |
| } |
| |
| udma_handle = UDMAx_Initialize(&udma_resources, udma_resources.desc, NULL, |
| (uint32_t *)&data->udma_handle); |
| if (udma_handle != &data->udma_handle) { |
| return -EINVAL; |
| } |
| |
| /* Connect the DMA interrupt */ |
| cfg->irq_configure(); |
| |
| if (UDMAx_DMAEnable(&udma_resources, udma_handle) != 0) { |
| return -EBUSY; |
| } |
| |
| return 0; |
| } |
| |
| static void siwx91x_dma_isr(const struct device *dev) |
| { |
| const struct dma_siwx91x_config *cfg = dev->config; |
| struct dma_siwx91x_data *data = dev->data; |
| UDMA_RESOURCES udma_resources = { |
| .reg = cfg->reg, |
| .udma_irq_num = cfg->irq_number, |
| .desc = cfg->sram_desc_addr, |
| }; |
| uint8_t channel; |
| |
| /* Disable the IRQ to prevent the ISR from being triggered by |
| * interrupts from other DMA channels. |
| */ |
| irq_disable(cfg->irq_number); |
| |
| channel = find_lsb_set(cfg->reg->UDMA_DONE_STATUS_REG); |
| /* Identify the interrupt channel */ |
| if (!channel || channel > cfg->channels) { |
| goto out; |
| } |
| /* find_lsb_set() returns 1 indexed value */ |
| channel -= 1; |
| |
| if (data->chan_info[channel].Cnt == data->chan_info[channel].Size) { |
| if (data->dma_callback) { |
| /* Transfer complete, call user callback */ |
| data->dma_callback(dev, data->cb_data, channel, 0); |
| } |
| sys_write32(BIT(channel), (mem_addr_t)&cfg->reg->UDMA_DONE_STATUS_REG); |
| } else { |
| /* Call UDMA ROM IRQ handler. */ |
| ROMAPI_UDMA_WRAPPER_API->uDMAx_IRQHandler(&udma_resources, udma_resources.desc, |
| data->chan_info); |
| /* Is a Memory-to-memory Transfer */ |
| if (udma_resources.desc[channel].vsUDMAChaConfigData1.srcInc != UDMA_SRC_INC_NONE && |
| udma_resources.desc[channel].vsUDMAChaConfigData1.dstInc != UDMA_DST_INC_NONE) { |
| /* Set the software trigger bit for starting next transfer */ |
| sys_set_bit((mem_addr_t)&cfg->reg->CHNL_SW_REQUEST, channel); |
| } |
| } |
| |
| out: |
| /* Enable the IRQ to restore interrupt functionality for other DMA channels */ |
| irq_enable(cfg->irq_number); |
| } |
| |
| /* Store the Si91x DMA APIs */ |
| static DEVICE_API(dma, siwx91x_dma_api) = { |
| .config = siwx91x_dma_configure, |
| .reload = siwx91x_dma_reload, |
| .start = siwx91x_dma_start, |
| .stop = siwx91x_dma_stop, |
| .get_status = siwx91x_dma_get_status, |
| }; |
| |
| #define SIWX91X_DMA_INIT(inst) \ |
| static UDMA_Channel_Info dma_channel_info_##inst[DT_INST_PROP(inst, dma_channels)]; \ |
| static struct dma_siwx91x_data dma_data_##inst = { \ |
| .chan_info = dma_channel_info_##inst, \ |
| }; \ |
| static void siwx91x_dma_irq_configure_##inst(void) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQ(inst, irq), DT_INST_IRQ(inst, priority), siwx91x_dma_isr, \ |
| DEVICE_DT_INST_GET(inst), 0); \ |
| irq_enable(DT_INST_IRQ(inst, irq)); \ |
| } \ |
| static const struct dma_siwx91x_config dma_cfg_##inst = { \ |
| .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(inst)), \ |
| .clock_subsys = (clock_control_subsys_t)DT_INST_PHA(inst, clocks, clkid), \ |
| .reg = (UDMA0_Type *)DT_INST_REG_ADDR(inst), \ |
| .channels = DT_INST_PROP(inst, dma_channels), \ |
| .irq_number = DT_INST_PROP_BY_IDX(inst, interrupts, 0), \ |
| .sram_desc_addr = (RSI_UDMA_DESC_T *)DT_INST_PROP(inst, silabs_sram_desc_addr), \ |
| .irq_configure = siwx91x_dma_irq_configure_##inst, \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(inst, &siwx91x_dma_init, NULL, &dma_data_##inst, &dma_cfg_##inst, \ |
| PRE_KERNEL_1, CONFIG_DMA_INIT_PRIORITY, &siwx91x_dma_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(SIWX91X_DMA_INIT) |