diff --git a/drivers/dma/CMakeLists.txt b/drivers/dma/CMakeLists.txt index da2d43fc2c0..271ddf19ff0 100644 --- a/drivers/dma/CMakeLists.txt +++ b/drivers/dma/CMakeLists.txt @@ -36,3 +36,4 @@ zephyr_library_sources_ifdef(CONFIG_MCUX_PXP dma_mcux_pxp.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_SMARTBOND dma_smartbond.c) diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 0136eaa92a2..5a2ce5bdced 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -68,4 +68,5 @@ source "drivers/dma/Kconfig.andes_atcdmac300" source "drivers/dma/Kconfig.sedi" +source "drivers/dma/Kconfig.smartbond" endif # DMA diff --git a/drivers/dma/Kconfig.smartbond b/drivers/dma/Kconfig.smartbond new file mode 100644 index 00000000000..3234e13020f --- /dev/null +++ b/drivers/dma/Kconfig.smartbond @@ -0,0 +1,11 @@ +# Smartbond DMA Accelerator Configuration Options + +# Copyright (c) 2023 Renesas Electronics Corporation +# SPDX-License-Identifier: Apache-2.0 + +config DMA_SMARTBOND + bool "Smartbond DMA Accelerator Driver" + depends on DT_HAS_RENESAS_SMARTBOND_DMA_ENABLED + default y + help + Enable Smartbond DMA Accelerator Driver diff --git a/drivers/dma/dma_smartbond.c b/drivers/dma/dma_smartbond.c new file mode 100644 index 00000000000..79305c77d6a --- /dev/null +++ b/drivers/dma/dma_smartbond.c @@ -0,0 +1,969 @@ +/* + * Copyright (c) 2023 Renesas Electronics Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(dma_smartbond, CONFIG_DMA_LOG_LEVEL); + +#define DT_DRV_COMPAT renesas_smartbond_dma + +#define SMARTBOND_IRQN DT_INST_IRQN(0) +#define SMARTBOND_IRQ_PRIO DT_INST_IRQ(0, priority) + +#define DMA_CHANNELS_COUNT DT_PROP(DT_NODELABEL(dma), dma_channels) +#define DMA_BLOCK_COUNT DT_PROP(DT_NODELABEL(dma), block_count) +#define DMA_SECURE_CHANNEL 7 + +#define DMA_CTRL_REG_SET_FIELD(_field, _var, _val) \ + (_var) = \ + (((_var) & ~DMA_DMA0_CTRL_REG_ ## _field ## _Msk) | \ + (((_val) << DMA_DMA0_CTRL_REG_ ## _field ## _Pos) & DMA_DMA0_CTRL_REG_ ## _field ## _Msk)) + +#define DMA_CTRL_REG_GET_FIELD(_field, _var) \ + (((_var) & DMA_DMA0_CTRL_REG_ ## _field ## _Msk) >> DMA_DMA0_CTRL_REG_ ## _field ## _Pos) + +#define DMA_CHN2REG(_idx) (&((struct channel_regs *)DMA)[(_idx)]) + +#define DMA_MUX_SHIFT(_idx) (((_idx) >> 1) * 4) + +#define DMA_REQ_MUX_REG_SET(_idx, _val) \ + DMA->DMA_REQ_MUX_REG = \ + (DMA->DMA_REQ_MUX_REG & ~(0xf << DMA_MUX_SHIFT((_idx)))) | \ + (((_val) & 0xf) << DMA_MUX_SHIFT((_idx))) + +#define DMA_REQ_MUX_REG_GET(_idx) \ + ((DMA->DMA_REQ_MUX_REG >> DMA_MUX_SHIFT((_idx))) & 0xf) + +#define CRYPTO_KEYS_BUF_ADDR 0x30040100 +#define CRYPTO_KEYS_BUF_SIZE 0x100 +#define IS_AES_KEYS_BUF_RANGE(_a) ((uint32_t)(_a) >= (uint32_t)(CRYPTO_KEYS_BUF_ADDR)) && \ + ((uint32_t)(_a) < (uint32_t)(CRYPTO_KEYS_BUF_ADDR + CRYPTO_KEYS_BUF_SIZE)) + +/* + * DMA channel priority level. The smaller the value the lower the priority granted to a channel + * when two or more channels request the bus at the same time. For channels of same priority an + * inherent mechanism is applied in which the lower the channel number the higher the priority. + */ +enum dma_smartbond_channel_prio { + DMA_SMARTBOND_CHANNEL_PRIO_0 = 0x0, /* Lowest channel priority */ + DMA_SMARTBOND_CHANNEL_PRIO_1, + DMA_SMARTBOND_CHANNEL_PRIO_2, + DMA_SMARTBOND_CHANNEL_PRIO_3, + DMA_SMARTBOND_CHANNEL_PRIO_4, + DMA_SMARTBOND_CHANNEL_PRIO_5, + DMA_SMARTBOND_CHANNEL_PRIO_6, + DMA_SMARTBOND_CHANNEL_PRIO_7, /* Highest channel priority */ + DMA_SMARTBOND_CHANNEL_PRIO_MAX +}; + +enum dma_smartbond_channel { + DMA_SMARTBOND_CHANNEL_0 = 0x0, + DMA_SMARTBOND_CHANNEL_1, + DMA_SMARTBOND_CHANNEL_2, + DMA_SMARTBOND_CHANNEL_3, + DMA_SMARTBOND_CHANNEL_4, + DMA_SMARTBOND_CHANNEL_5, + DMA_SMARTBOND_CHANNEL_6, + DMA_SMARTBOND_CHANNEL_7, + DMA_SMARTBOND_CHANNEL_MAX +}; + +enum dma_smartbond_burst_len { + DMA_SMARTBOND_BURST_LEN_1B = 0x1, /* Burst mode is disabled */ + DMA_SMARTBOND_BURST_LEN_4B = 0x4, /* Perform bursts of 4 beats (INCR4) */ + DMA_SMARTBOND_BURST_LEN_8B = 0x8 /* Perform bursts of 8 beats (INCR8) */ +}; + +/* + * DMA bus width indicating how many bytes are retrived/written per transfer. + * Note that the bus width is the same for the source and destination. + */ +enum dma_smartbond_bus_width { + DMA_SMARTBOND_BUS_WIDTH_1B = 0x1, + DMA_SMARTBOND_BUS_WIDTH_2B = 0x2, + DMA_SMARTBOND_BUS_WIDTH_4B = 0x4 +}; + +enum dreq_mode { + DREQ_MODE_SW = 0x0, + DREQ_MODE_HW +}; + +enum burst_mode { + BURST_MODE_0B = 0x0, + BURST_MODE_4B = 0x1, + BURST_MODE_8B = 0x2 +}; + +enum bus_width { + BUS_WIDTH_1B = 0x0, + BUS_WIDTH_2B = 0x1, + BUS_WIDTH_4B = 0x2 +}; + +enum addr_adj { + ADDR_ADJ_NO_CHANGE = 0x0, + ADDR_ADJ_INCR +}; + +enum copy_mode { + COPY_MODE_BLOCK = 0x0, + COPY_MODE_INIT +}; + +enum req_sense { + REQ_SENSE_LEVEL = 0x0, + REQ_SENSE_EDGE +}; + +struct channel_regs { + __IO uint32_t DMA_A_START; + __IO uint32_t DMA_B_START; + __IO uint32_t DMA_INT_REG; + __IO uint32_t DMA_LEN_REG; + __IO uint32_t DMA_CTRL_REG; + + __I uint32_t DMA_IDX_REG; + __I uint32_t RESERVED[2]; +}; + +struct dma_channel_data { + dma_callback_t cb; + void *user_data; + enum dma_smartbond_bus_width bus_width; + enum dma_smartbond_burst_len burst_len; + enum dma_channel_direction dir; + bool is_dma_configured; +}; + +struct dma_smartbond_data { + /* Should be the first member of the driver data */ + struct dma_context dma_ctx; + + ATOMIC_DEFINE(channels_atomic, DMA_CHANNELS_COUNT); + + /* User callbacks and data to be stored per channel */ + struct dma_channel_data channel_data[DMA_CHANNELS_COUNT]; +}; + +/* True if there is any DMA activity on any channel, false otheriwise. */ +static bool dma_smartbond_is_dma_active(void) +{ + int idx; + struct channel_regs *regs; + + for (idx = 0; idx < DMA_CHANNELS_COUNT; idx++) { + regs = DMA_CHN2REG(idx); + + if (DMA_CTRL_REG_GET_FIELD(DMA_ON, regs->DMA_CTRL_REG)) { + return true; + } + } + + return false; +} + +static void dma_smartbond_set_channel_status(uint32_t channel, bool status) +{ + unsigned int key; + struct channel_regs *regs = DMA_CHN2REG(channel); + + key = irq_lock(); + + if (status) { + /* Make sure the status register for the requested channel is cleared. */ + DMA->DMA_CLEAR_INT_REG |= BIT(channel); + /* Enable interrupts for the requested channel. */ + DMA->DMA_INT_MASK_REG |= BIT(channel); + + /* Check if this is the first attempt to enable DMA interrupts. */ + if (!irq_is_enabled(SMARTBOND_IRQN)) { + irq_enable(SMARTBOND_IRQN); + } + + DMA_CTRL_REG_SET_FIELD(DMA_ON, regs->DMA_CTRL_REG, 0x1); + } else { + DMA_CTRL_REG_SET_FIELD(DMA_ON, regs->DMA_CTRL_REG, 0x0); + + /* + * It might happen that DMA is already in progress. Make sure the current + * on-going transfer is complete (cannot be interrupted). + */ + while (DMA_CTRL_REG_GET_FIELD(DMA_ON, regs->DMA_CTRL_REG)) { + } + + /* Disable interrupts for the requested channel */ + DMA->DMA_INT_MASK_REG &= ~(BIT(channel)); + /* Clear the status register; the requested channel should be considered obsolete */ + DMA->DMA_CLEAR_INT_REG |= BIT(channel); + + /* DMA interrupts should be disabled only if all channels are disabled. */ + if (!dma_smartbond_is_dma_active()) { + irq_disable(SMARTBOND_IRQN); + } + } + + irq_unlock(key); +} + +static bool dma_channel_dst_addr_check_and_adjust(uint32_t channel, uint32_t *dst) +{ + uint32_t phy_address; + uint32_t secure_boot_reg; + bool is_aes_keys_protected, is_qspic_keys_protected; + + phy_address = black_orca_phy_addr(*dst); + + secure_boot_reg = CRG_TOP->SECURE_BOOT_REG; + is_aes_keys_protected = + (secure_boot_reg & CRG_TOP_SECURE_BOOT_REG_PROT_AES_KEY_READ_Msk); + is_qspic_keys_protected = + (secure_boot_reg & CRG_TOP_SECURE_BOOT_REG_PROT_QSPI_KEY_READ_Msk); + + /* + * If the destination address reflects the AES key buffer area and secure keys are protected + * then only the secure channel #7 can be used to transfer data to AES key buffer. + */ + if ((IS_AES_KEYS_BUF_RANGE(phy_address) && + (is_aes_keys_protected || is_qspic_keys_protected) && + (channel != DMA_SECURE_CHANNEL))) { + LOG_ERR("Keys are protected. Only secure channel #7 can be employed."); + return false; + } + + if (IS_QSPIF_ADDRESS(phy_address) || IS_QSPIF_CACHED_ADDRESS(phy_address) || + IS_OTP_ADDRESS(phy_address) || IS_OTP_P_ADDRESS(phy_address)) { + LOG_ERR("Invalid destination location."); + return false; + } + + *dst = phy_address; + + return true; +} + +static bool dma_channel_src_addr_check_and_adjust(uint32_t channel, uint32_t *src) +{ + uint32_t phy_address; + uint32_t secure_boot_reg; + bool is_aes_keys_protected, is_qspic_keys_protected; + + /* DMA can only access physical addresses, not remapped. */ + phy_address = black_orca_phy_addr(*src); + + if (IS_QSPIF_CACHED_ADDRESS(phy_address)) { + /* + * To achiebe max. perfomance, peripherals should not access the Flash memory + * through the instruction cache controller (avoid cache misses). + */ + phy_address += (MCU_QSPIF_M_BASE - MCU_QSPIF_M_CACHED_BASE); + } else if (IS_OTP_ADDRESS(phy_address)) { + /* Peripherals should access OTP through its peripheral address space. */ + phy_address += (MCU_OTP_M_P_BASE - MCU_OTP_M_BASE); + } + + secure_boot_reg = CRG_TOP->SECURE_BOOT_REG; + is_aes_keys_protected = + (secure_boot_reg & CRG_TOP_SECURE_BOOT_REG_PROT_AES_KEY_READ_Msk); + is_qspic_keys_protected = + (secure_boot_reg & CRG_TOP_SECURE_BOOT_REG_PROT_QSPI_KEY_READ_Msk); + + /* + * If the source address reflects protected area in OTP then only the + * secure channel #7 can be used to fetch secure keys data. + */ + if (((IS_ADDRESS_USER_DATA_KEYS_SEGMENT(phy_address) && is_aes_keys_protected) || + (IS_ADDRESS_QSPI_FW_KEYS_SEGMENT(phy_address) && is_qspic_keys_protected)) && + (channel != DMA_SECURE_CHANNEL)) { + LOG_ERR("Keys are protected. Only secure channel #7 can be employed."); + return false; + } + + *src = phy_address; + + return true; +} + +static bool dma_channel_update_dreq_mode(enum dma_channel_direction direction, + uint32_t *dma_ctrl_reg) +{ + switch (direction) { + case MEMORY_TO_HOST: + case HOST_TO_MEMORY: + case MEMORY_TO_MEMORY: + /* DMA channel starts immediately */ + DMA_CTRL_REG_SET_FIELD(DREQ_MODE, *dma_ctrl_reg, DREQ_MODE_SW); + break; + case PERIPHERAL_TO_MEMORY: + case PERIPHERAL_TO_PERIPHERAL: + /* DMA channels starts by peripheral DMA req */ + DMA_CTRL_REG_SET_FIELD(DREQ_MODE, *dma_ctrl_reg, DREQ_MODE_HW); + break; + default: + return false; + }; + + return true; +} + +static bool dma_channel_update_src_addr_adj(enum dma_addr_adj addr_adj, uint32_t *dma_ctrl_reg) +{ + switch (addr_adj) { + case DMA_ADDR_ADJ_NO_CHANGE: + DMA_CTRL_REG_SET_FIELD(AINC, *dma_ctrl_reg, ADDR_ADJ_NO_CHANGE); + break; + case DMA_ADDR_ADJ_INCREMENT: + DMA_CTRL_REG_SET_FIELD(AINC, *dma_ctrl_reg, ADDR_ADJ_INCR); + break; + default: + return false; + } + + return true; +} + +static bool dma_channel_update_dst_addr_adj(enum dma_addr_adj addr_adj, uint32_t *dma_ctrl_reg) +{ + switch (addr_adj) { + case DMA_ADDR_ADJ_NO_CHANGE: + DMA_CTRL_REG_SET_FIELD(BINC, *dma_ctrl_reg, ADDR_ADJ_NO_CHANGE); + break; + case DMA_ADDR_ADJ_INCREMENT: + DMA_CTRL_REG_SET_FIELD(BINC, *dma_ctrl_reg, ADDR_ADJ_INCR); + break; + default: + return false; + } + + return true; +} + +static bool dma_channel_update_bus_width(uint16_t bw, uint32_t *dma_ctrl_reg) +{ + switch (bw) { + case DMA_SMARTBOND_BUS_WIDTH_1B: + DMA_CTRL_REG_SET_FIELD(BW, *dma_ctrl_reg, BUS_WIDTH_1B); + break; + case DMA_SMARTBOND_BUS_WIDTH_2B: + DMA_CTRL_REG_SET_FIELD(BW, *dma_ctrl_reg, BUS_WIDTH_2B); + break; + case DMA_SMARTBOND_BUS_WIDTH_4B: + DMA_CTRL_REG_SET_FIELD(BW, *dma_ctrl_reg, BUS_WIDTH_4B); + break; + default: + return false; + } + + return true; +} + +static bool dma_channel_update_burst_mode(uint16_t burst, uint32_t *dma_ctrl_reg) +{ + switch (burst) { + case DMA_SMARTBOND_BURST_LEN_1B: + DMA_CTRL_REG_SET_FIELD(BURST_MODE, *dma_ctrl_reg, BURST_MODE_0B); + break; + case DMA_SMARTBOND_BURST_LEN_4B: + DMA_CTRL_REG_SET_FIELD(BURST_MODE, *dma_ctrl_reg, BURST_MODE_4B); + break; + case DMA_SMARTBOND_BURST_LEN_8B: + DMA_CTRL_REG_SET_FIELD(BURST_MODE, *dma_ctrl_reg, BURST_MODE_8B); + break; + default: + return false; + } + + return true; +} + +static void dma_channel_update_req_sense(enum dma_smartbond_trig_mux trig_mux, + uint32_t channel, uint32_t *dma_ctrl_reg) +{ + switch (trig_mux) { + case DMA_SMARTBOND_TRIG_MUX_UART: + case DMA_SMARTBOND_TRIG_MUX_UART2: + case DMA_SMARTBOND_TRIG_MUX_UART3: + case DMA_SMARTBOND_TRIG_MUX_I2C: + case DMA_SMARTBOND_TRIG_MUX_I2C2: + case DMA_SMARTBOND_TRIG_MUX_USB: + /* Odd channel numbers should reflect TX path */ + if (channel & BIT(0)) { + DMA_CTRL_REG_SET_FIELD(REQ_SENSE, *dma_ctrl_reg, REQ_SENSE_EDGE); + break; + } + default: + DMA_CTRL_REG_SET_FIELD(REQ_SENSE, *dma_ctrl_reg, REQ_SENSE_LEVEL); + } +} + +static void dma_set_mux_request(enum dma_smartbond_trig_mux trig_mux, uint32_t channel) +{ + unsigned int key; + + key = irq_lock(); + DMA_REQ_MUX_REG_SET(channel, trig_mux); + + /* + * Having same trigger for different channels can cause unpredictable results. + * The audio triggers (src and pcm) are an exception, as they use 2 pairs each + * for DMA access. + * The lesser significant selector has higher priority and will control + * the DMA acknowledge signal driven to the selected peripheral. Make sure + * the current selector does not match with selectors of + * higher priorities (dma channels of lower indexing). It's OK if a + * channel of higher indexing defines the same peripheral request source + * (should be ignored as it has lower priority). + */ + if (trig_mux != DMA_SMARTBOND_TRIG_MUX_NONE) { + switch (channel) { + case DMA_SMARTBOND_CHANNEL_7: + case DMA_SMARTBOND_CHANNEL_6: + if (DMA_REQ_MUX_REG_GET(DMA_SMARTBOND_CHANNEL_5) == trig_mux) { + DMA_REQ_MUX_REG_SET(DMA_SMARTBOND_CHANNEL_5, + DMA_SMARTBOND_TRIG_MUX_NONE); + } + /* fall-through */ + case DMA_SMARTBOND_CHANNEL_5: + case DMA_SMARTBOND_CHANNEL_4: + if (DMA_REQ_MUX_REG_GET(DMA_SMARTBOND_CHANNEL_3) == trig_mux) { + DMA_REQ_MUX_REG_SET(DMA_SMARTBOND_CHANNEL_3, + DMA_SMARTBOND_TRIG_MUX_NONE); + } + /* fall-through */ + case DMA_SMARTBOND_CHANNEL_3: + case DMA_SMARTBOND_CHANNEL_2: + if (DMA_REQ_MUX_REG_GET(DMA_SMARTBOND_CHANNEL_1) == trig_mux) { + DMA_REQ_MUX_REG_SET(DMA_SMARTBOND_CHANNEL_1, + DMA_SMARTBOND_TRIG_MUX_NONE); + } + case DMA_SMARTBOND_CHANNEL_1: + case DMA_SMARTBOND_CHANNEL_0: + break; + } + } + + irq_unlock(key); +} + +static int dma_smartbond_config(const struct device *dev, uint32_t channel, struct dma_config *cfg) +{ + struct dma_smartbond_data *data = dev->data; + struct channel_regs *regs; + uint32_t dma_ctrl_reg; + uint32_t src_dst_address; + + if (channel >= DMA_CHANNELS_COUNT) { + LOG_ERR("Inavlid DMA channel index"); + return -EINVAL; + } + regs = DMA_CHN2REG(channel); + + dma_ctrl_reg = regs->DMA_CTRL_REG; + + if (DMA_CTRL_REG_GET_FIELD(DMA_ON, dma_ctrl_reg)) { + LOG_ERR("Requested channel is enabled. It should first be disabled"); + return -EIO; + } + + if (cfg == NULL || cfg->head_block == NULL) { + LOG_ERR("Missing configuration structure"); + return -EINVAL; + } + + /* Error handling is not supported; just warn user. */ + if (cfg->error_callback_en) { + LOG_WRN("Error handling is not supported"); + } + + if (!cfg->complete_callback_en) { + data->channel_data[channel].cb = cfg->dma_callback; + data->channel_data[channel].user_data = cfg->user_data; + } else { + LOG_WRN("User callback can only be called at completion only and not per block."); + + /* Nulify pointers to indicate notifications are disabled. */ + data->channel_data[channel].cb = NULL; + data->channel_data[channel].user_data = NULL; + } + + data->channel_data[channel].dir = cfg->channel_direction; + + if (cfg->block_count > DMA_BLOCK_COUNT) { + LOG_WRN("A single block is supported. The rest blocks will be discarded"); + } + + if (cfg->channel_priority >= DMA_SMARTBOND_CHANNEL_PRIO_MAX) { + cfg->channel_priority = DMA_SMARTBOND_CHANNEL_PRIO_7; + LOG_WRN("Channel priority exceeded max. Setting to highest valid level"); + } + + DMA_CTRL_REG_SET_FIELD(DMA_PRIO, dma_ctrl_reg, cfg->channel_priority); + + if (((cfg->source_burst_length != cfg->dest_burst_length) || + !dma_channel_update_burst_mode(cfg->source_burst_length, &dma_ctrl_reg))) { + LOG_ERR("Invalid burst mode or source and destination mode mismatch"); + return -EINVAL; + } + + data->channel_data[channel].burst_len = cfg->source_burst_length; + + if (cfg->source_data_size != cfg->dest_data_size || + !dma_channel_update_bus_width(cfg->source_data_size, &dma_ctrl_reg)) { + LOG_ERR("Invalid bus width or source and destination bus width mismatch"); + return -EINVAL; + } + + data->channel_data[channel].bus_width = cfg->source_data_size; + + if (cfg->source_chaining_en || cfg->dest_chaining_en || + cfg->head_block->source_gather_en || cfg->head_block->dest_scatter_en || + cfg->head_block->source_reload_en || cfg->head_block->dest_reload_en) { + LOG_WRN("Chainning, scattering, gathering or reloading is not supported"); + } + + if (!dma_channel_update_src_addr_adj(cfg->head_block->source_addr_adj, + &dma_ctrl_reg)) { + LOG_ERR("Invalid source address adjustment"); + return -EINVAL; + } + + if (!dma_channel_update_dst_addr_adj(cfg->head_block->dest_addr_adj, &dma_ctrl_reg)) { + LOG_ERR("Invalid destination address adjustment"); + return -EINVAL; + } + + if (!dma_channel_update_dreq_mode(cfg->channel_direction, &dma_ctrl_reg)) { + LOG_ERR("Inavlid channel direction"); + return -EINVAL; + } + + /* Cyclic is valid only when DREQ_MODE is set */ + if (cfg->cyclic && DMA_CTRL_REG_GET_FIELD(DREQ_MODE, dma_ctrl_reg) != DREQ_MODE_HW) { + LOG_ERR("Circular mode is only supported for non memory-memory transfers"); + return -EINVAL; + } + + DMA_CTRL_REG_SET_FIELD(CIRCULAR, dma_ctrl_reg, cfg->cyclic); + + if (DMA_CTRL_REG_GET_FIELD(DREQ_MODE, dma_ctrl_reg) == DREQ_MODE_SW && + DMA_CTRL_REG_GET_FIELD(AINC, dma_ctrl_reg) == ADDR_ADJ_NO_CHANGE && + DMA_CTRL_REG_GET_FIELD(BINC, dma_ctrl_reg) == ADDR_ADJ_INCR) { + /* + * Valid for memory initialization to a specific value. This process + * cannot be interrupted by other DMA channels. + */ + DMA_CTRL_REG_SET_FIELD(DMA_INIT, dma_ctrl_reg, COPY_MODE_INIT); + } else { + DMA_CTRL_REG_SET_FIELD(DMA_INIT, dma_ctrl_reg, COPY_MODE_BLOCK); + } + + dma_channel_update_req_sense(cfg->dma_slot, channel, &dma_ctrl_reg); + + regs->DMA_CTRL_REG = dma_ctrl_reg; + + /* Requested address might be changed */ + src_dst_address = cfg->head_block->source_address; + if (!dma_channel_src_addr_check_and_adjust(channel, &src_dst_address)) { + return -EINVAL; + } + + if (src_dst_address % cfg->source_data_size) { + LOG_ERR("Source address is not bus width aligned"); + return -EINVAL; + } + + regs->DMA_A_START = src_dst_address; + + src_dst_address = cfg->head_block->dest_address; + if (!dma_channel_dst_addr_check_and_adjust(channel, &src_dst_address)) { + return -EINVAL; + } + + if (src_dst_address % cfg->dest_data_size) { + LOG_ERR("Destination address is not bus width aligned"); + return -EINVAL; + } + + regs->DMA_B_START = src_dst_address; + + if (cfg->head_block->block_size % (cfg->source_data_size * cfg->source_burst_length)) { + LOG_ERR("Requested data size is not multiple of bus width"); + return -EINVAL; + } + + regs->DMA_LEN_REG = (cfg->head_block->block_size / cfg->source_data_size) - 1; + + /* Interrupt will be raised once all transfers are complete. */ + regs->DMA_INT_REG = (cfg->head_block->block_size / cfg->source_data_size) - 1; + + if ((cfg->source_handshake != cfg->dest_handshake) || + (cfg->source_handshake != 0)/*HW*/) { + LOG_ERR("Source/destination handshakes mismatch or invalid"); + return -EINVAL; + } + + dma_set_mux_request(cfg->dma_slot, channel); + + /* Designate that channel has been configured */ + data->channel_data[channel].is_dma_configured = true; + + return 0; +} + + +static int dma_smartbond_reload(const struct device *dev, uint32_t channel, uint32_t src, + uint32_t dst, size_t size) +{ + struct dma_smartbond_data *data = dev->data; + struct channel_regs *regs; + + if (channel >= DMA_CHANNELS_COUNT) { + LOG_ERR("Inavlid DMA channel index"); + return -EINVAL; + } + regs = DMA_CHN2REG(channel); + + if (!data->channel_data[channel].is_dma_configured) { + LOG_ERR("Requested DMA channel should first be configured"); + return -EINVAL; + } + + if (size == 0) { + LOG_ERR("Min. transfer size is one"); + return -EINVAL; + } + + if (DMA_CTRL_REG_GET_FIELD(DMA_ON, regs->DMA_CTRL_REG)) { + LOG_ERR("Channel is busy, settings cannot be changed mid-transfer"); + return -EBUSY; + } + + if (src % data->channel_data[channel].bus_width) { + LOG_ERR("Source address is not bus width aligned"); + return -EINVAL; + } + + if (!dma_channel_src_addr_check_and_adjust(channel, &src)) { + return -EINVAL; + } + + regs->DMA_A_START = src; + + if (dst % data->channel_data[channel].bus_width) { + LOG_ERR("Destination address is not bus width aligned"); + return -EINVAL; + } + + if (!dma_channel_dst_addr_check_and_adjust(channel, &dst)) { + return -EINVAL; + } + + regs->DMA_B_START = dst; + + if (size % (data->channel_data[channel].burst_len * + data->channel_data[channel].bus_width)) { + LOG_ERR("Requested data size is not multiple of bus width"); + return -EINVAL; + } + + regs->DMA_LEN_REG = (size / data->channel_data[channel].bus_width) - 1; + + /* Interrupt will be raised once all transfers are complete. */ + regs->DMA_INT_REG = (size / data->channel_data[channel].bus_width) - 1; + + return 0; +} + +static int dma_smartbond_start(const struct device *dev, uint32_t channel) +{ + struct channel_regs *regs; + struct dma_smartbond_data *data = dev->data; + + if (channel >= DMA_CHANNELS_COUNT) { + LOG_ERR("Inavlid DMA channel index"); + return -EINVAL; + } + regs = DMA_CHN2REG(channel); + + if (!data->channel_data[channel].is_dma_configured) { + LOG_ERR("Requested DMA channel should first be configured"); + return -EINVAL; + } + + /* Should return succss if the requested channel is already started. */ + if (DMA_CTRL_REG_GET_FIELD(DMA_ON, regs->DMA_CTRL_REG)) { + return 0; + } + + dma_smartbond_set_channel_status(channel, true); + + return 0; +} + +static int dma_smartbond_stop(const struct device *dev, uint32_t channel) +{ + struct channel_regs *regs; + + if (channel >= DMA_CHANNELS_COUNT) { + LOG_ERR("Inavlid DMA channel index"); + return -EINVAL; + } + regs = DMA_CHN2REG(channel); + + /* + * In normal mode DMA_ON is cleared automatically. However we need to clear + * the corresponding register mask and disable NVIC if there is no other + * channel in use. + */ + dma_smartbond_set_channel_status(channel, false); + + return 0; +} + +static int dma_smartbond_suspend(const struct device *dev, uint32_t channel) +{ + if (channel >= DMA_CHANNELS_COUNT) { + LOG_ERR("Inavlid DMA channel index"); + return -EINVAL; + } + + /* + * Freezing the DMA engine is valid for memory-to-memory operations. + * Valid memory locations are SYSRAM and/or PSRAM. + */ + LOG_WRN("DMA is freezed globally"); + + /* + * Freezing the DMA engine can be done universally and not per channel!. + * An attempt to disable the channel would result in resetting the IDX + * register next time the channel was re-enabled. + */ + GPREG->SET_FREEZE_REG = GPREG_SET_FREEZE_REG_FRZ_DMA_Msk; + + return 0; +} + +static int dma_smartbond_resume(const struct device *dev, uint32_t channel) +{ + if (channel >= DMA_CHANNELS_COUNT) { + LOG_ERR("Inavlid DMA channel index"); + return -EINVAL; + } + + LOG_WRN("DMA is unfreezed globally"); + + /* Unfreezing the DMA engine can be done unviversally and not per channel! */ + GPREG->RESET_FREEZE_REG = GPREG_RESET_FREEZE_REG_FRZ_DMA_Msk; + + return 0; +} + +static int dma_smartbond_get_status(const struct device *dev, uint32_t channel, + struct dma_status *stat) +{ + struct channel_regs *regs; + int key; + struct dma_smartbond_data *data = dev->data; + uint8_t bus_width; + uint32_t dma_ctrl_reg, dma_idx_reg, dma_len_reg; + + if (channel >= DMA_CHANNELS_COUNT) { + LOG_ERR("Inavlid DMA channel index"); + return -EINVAL; + } + + if (stat == NULL) { + LOG_ERR("User should provide a valid pointer to store the status info requested"); + } + + if (!data->channel_data[channel].is_dma_configured) { + LOG_ERR("Requested DMA channel should first be configured"); + return -EINVAL; + } + + regs = DMA_CHN2REG(channel); + + /* + * The DMA is running in parallel with CPU and so it might happen that an on-going transfer + * might be completed the moment user parses the status results. Disable interrupts globally + * so there is no chance for a new transfer to be initiated from within ISR and so changing + * the channel registers values. + */ + key = irq_lock(); + + dma_ctrl_reg = regs->DMA_CTRL_REG; + dma_idx_reg = regs->DMA_IDX_REG; + dma_len_reg = regs->DMA_LEN_REG; + + /* Calculate how many byes each transfer consists of. */ + bus_width = DMA_CTRL_REG_GET_FIELD(BW, dma_ctrl_reg); + if (bus_width == BUS_WIDTH_1B) { + bus_width = 1; + } else { + bus_width <<= 1; + } + + /* Convert transfers to bytes. */ + stat->total_copied = dma_idx_reg * bus_width; + stat->pending_length = ((dma_len_reg + 1) - dma_idx_reg) * bus_width; + stat->busy = DMA_CTRL_REG_GET_FIELD(DMA_ON, dma_ctrl_reg); + stat->dir = data->channel_data[channel].dir; + + /* DMA does not support circular buffer functionality */ + stat->free = 0; + stat->read_position = 0; + stat->write_position = 0; + + irq_unlock(key); + + return 0; +} + +static int dma_smartbond_get_attribute(const struct device *dev, uint32_t type, uint32_t *value) +{ + if (value == NULL) { + LOG_ERR("User should provide a valid pointer to attribute value"); + return -EINVAL; + } + + switch (type) { + /* + * Source and destination addresses should be multiple of a channel's bus width. + * This info could be provided at runtime given that attributes of a specific + * channel could be requested. + */ + case DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT: + case DMA_ATTR_COPY_ALIGNMENT: + /* + * Buffer size should be multiple of a channel's bus width multiplied by burst length. + * This info could be provided at runtime given that attributes of a specific channel + * could be requested. + */ + case DMA_ATTR_BUFFER_SIZE_ALIGNMENT: + return -ENOSYS; + case DMA_ATTR_MAX_BLOCK_COUNT: + *value = DMA_BLOCK_COUNT; + return 0; + default: + return -EINVAL; + } +} + +static bool dma_smartbond_chan_filter(const struct device *dev, int channel, void *filter_param) +{ + uint32_t requested_channel; + + if (channel >= DMA_CHANNELS_COUNT) { + LOG_ERR("Inavlid DMA channel index"); + return -EINVAL; + } + + /* If user does not provide any channel request explicitly, return true. */ + if (filter_param == NULL) { + return true; + } + + requested_channel = *(uint32_t *)filter_param; + + if (channel == requested_channel) { + return true; + } + + return false; +} + +static struct dma_driver_api dma_smartbond_driver_api = { + .config = dma_smartbond_config, + .reload = dma_smartbond_reload, + .start = dma_smartbond_start, + .stop = dma_smartbond_stop, + .suspend = dma_smartbond_suspend, + .resume = dma_smartbond_resume, + .get_status = dma_smartbond_get_status, + .get_attribute = dma_smartbond_get_attribute, + .chan_filter = dma_smartbond_chan_filter +}; + +static void smartbond_dma_isr(const void *arg) +{ + uint16_t dma_int_status_reg; + int i; + struct channel_regs *regs; + struct dma_smartbond_data *data = ((const struct device *)arg)->data; + + /* + * A single interrupt line is generated for all channels and so each channel + * should be parsed separately. + */ + for (i = 0, dma_int_status_reg = DMA->DMA_INT_STATUS_REG; + i < DMA_CHANNELS_COUNT && dma_int_status_reg != 0; ++i, dma_int_status_reg >>= 1) { + /* Check if the selected channel has raised the interrupt line */ + if (dma_int_status_reg & BIT(0)) { + + regs = DMA_CHN2REG(i); + /* + * Should be valid if callbacks are explicitly enabled by users. + * Interrupt should be triggered only when the total size of + * bytes has been transferred. Bus errors cannot raise interrupts. + */ + if (data->channel_data[i].cb) { + data->channel_data[i].cb((const struct device *)arg, + data->channel_data[i].user_data, i, DMA_STATUS_COMPLETE); + } + /* Channel line should be cleared otherwise ISR will keep firing! */ + DMA->DMA_CLEAR_INT_REG = BIT(i); + } + } +} + +static int dma_smartbond_init(const struct device *dev) +{ +#ifdef CONFIG_DMA_64BIT + LOG_ERR("64-bit addressing mode is not supported\n"); + return -ENOSYS; +#endif + + int idx; + struct dma_smartbond_data *data; + + data = dev->data; + data->dma_ctx.magic = DMA_MAGIC; + data->dma_ctx.dma_channels = DMA_CHANNELS_COUNT; + data->dma_ctx.atomic = data->channels_atomic; + + /* Make sure that all channels are disabled. */ + for (idx = 0; idx < DMA_CHANNELS_COUNT; idx++) { + dma_smartbond_set_channel_status(idx, false); + data->channel_data[idx].is_dma_configured = false; + } + + IRQ_CONNECT(SMARTBOND_IRQN, SMARTBOND_IRQ_PRIO, smartbond_dma_isr, + DEVICE_DT_INST_GET(0), 0); + + return 0; +} + +#define SMARTBOND_DMA_INIT(inst) \ + BUILD_ASSERT((inst) == 0, "multiple instances are not supported"); \ + \ + static struct dma_smartbond_data dma_smartbond_data_ ## inst; \ + \ + DEVICE_DT_INST_DEFINE(0, dma_smartbond_init, NULL, \ + &dma_smartbond_data_ ## inst, NULL, \ + POST_KERNEL, \ + CONFIG_DMA_INIT_PRIORITY, \ + &dma_smartbond_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(SMARTBOND_DMA_INIT) diff --git a/dts/bindings/dma/renesas,smartbond-dma.yaml b/dts/bindings/dma/renesas,smartbond-dma.yaml new file mode 100644 index 00000000000..0845df77463 --- /dev/null +++ b/dts/bindings/dma/renesas,smartbond-dma.yaml @@ -0,0 +1,21 @@ +# Copyright (c) 2023 Renesas Electronics Corporation +# SPDX-License-Identifier: Apache-2.0 + +include: dma-controller.yaml + +description: Renesas Smartbond(tm) DMA + +compatible: "renesas,smartbond-dma" + +properties: + reg: + required: true + + interrupts: + required: true + + block-count: + required: true + type: int + const: 1 + description: Number of block counts supported diff --git a/include/zephyr/drivers/dma/dma_smartbond.h b/include/zephyr/drivers/dma/dma_smartbond.h new file mode 100644 index 00000000000..c154488039d --- /dev/null +++ b/include/zephyr/drivers/dma/dma_smartbond.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023 Renesas Electronics Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef DMA_SMARTBOND_H_ +#define DMA_SMARTBOND_H_ + +/** + * @brief Vendror-specific DMA peripheral triggering sources. + * + * A valid triggering source should be provided when DMA + * is configured for peripheral to peripheral or memory to peripheral + * transactions. + */ +enum dma_smartbond_trig_mux { + DMA_SMARTBOND_TRIG_MUX_SPI = 0x0, + DMA_SMARTBOND_TRIG_MUX_SPI2 = 0x1, + DMA_SMARTBOND_TRIG_MUX_UART = 0x2, + DMA_SMARTBOND_TRIG_MUX_UART2 = 0x3, + DMA_SMARTBOND_TRIG_MUX_I2C = 0x4, + DMA_SMARTBOND_TRIG_MUX_I2C2 = 0x5, + DMA_SMARTBOND_TRIG_MUX_USB = 0x6, + DMA_SMARTBOND_TRIG_MUX_UART3 = 0x7, + DMA_SMARTBOND_TRIG_MUX_PCM = 0x8, + DMA_SMARTBOND_TRIG_MUX_SRC = 0x9, + DMA_SMARTBOND_TRIG_MUX_GPADC = 0xC, + DMA_SMARTBOND_TRIG_MUX_SDADC = 0xD, + DMA_SMARTBOND_TRIG_MUX_NONE = 0xF +}; + +#endif /* DMA_SMARTBOND_H_ */