|  | /* | 
|  | * Copyright (c) 2017 comsuisse AG | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT atmel_sam_xdmac | 
|  |  | 
|  | /** @file | 
|  | * @brief Atmel SAM MCU family Direct Memory Access (XDMAC) driver. | 
|  | */ | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <zephyr/sys/__assert.h> | 
|  | #include <zephyr/device.h> | 
|  | #include <zephyr/init.h> | 
|  | #include <string.h> | 
|  | #include <soc.h> | 
|  | #include <zephyr/drivers/dma.h> | 
|  | #include <zephyr/drivers/clock_control/atmel_sam_pmc.h> | 
|  | #include "dma_sam_xdmac.h" | 
|  |  | 
|  | #define LOG_LEVEL CONFIG_DMA_LOG_LEVEL | 
|  | #include <zephyr/logging/log.h> | 
|  | #include <zephyr/irq.h> | 
|  | LOG_MODULE_REGISTER(dma_sam_xdmac); | 
|  |  | 
|  | #define XDMAC_INT_ERR (XDMAC_CIE_RBIE | XDMAC_CIE_WBIE | XDMAC_CIE_ROIE) | 
|  | #define DMA_CHANNELS_NO  XDMACCHID_NUMBER | 
|  |  | 
|  | /* DMA channel configuration */ | 
|  | struct sam_xdmac_channel_cfg { | 
|  | void *user_data; | 
|  | dma_callback_t callback; | 
|  | uint32_t data_size; | 
|  | }; | 
|  |  | 
|  | /* Device constant configuration parameters */ | 
|  | struct sam_xdmac_dev_cfg { | 
|  | Xdmac *regs; | 
|  | void (*irq_config)(void); | 
|  | const struct atmel_sam_pmc_config clock_cfg; | 
|  | uint8_t irq_id; | 
|  | }; | 
|  |  | 
|  | /* Device run time data */ | 
|  | struct sam_xdmac_dev_data { | 
|  | struct sam_xdmac_channel_cfg dma_channels[DMA_CHANNELS_NO]; | 
|  | }; | 
|  |  | 
|  | static void sam_xdmac_isr(const struct device *dev) | 
|  | { | 
|  | const struct sam_xdmac_dev_cfg *const dev_cfg = dev->config; | 
|  | struct sam_xdmac_dev_data *const dev_data = dev->data; | 
|  |  | 
|  | Xdmac * const xdmac = dev_cfg->regs; | 
|  | struct sam_xdmac_channel_cfg *channel_cfg; | 
|  | uint32_t isr_status; | 
|  | uint32_t err; | 
|  |  | 
|  | /* Get global interrupt status */ | 
|  | isr_status = xdmac->XDMAC_GIS; | 
|  |  | 
|  | for (int channel = 0; channel < DMA_CHANNELS_NO; channel++) { | 
|  | if (!(isr_status & (1 << channel))) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | channel_cfg = &dev_data->dma_channels[channel]; | 
|  |  | 
|  | /* Get channel errors */ | 
|  | err = xdmac->XDMAC_CHID[channel].XDMAC_CIS & XDMAC_INT_ERR; | 
|  |  | 
|  | /* Execute callback */ | 
|  | if (channel_cfg->callback) { | 
|  | channel_cfg->callback(dev, channel_cfg->user_data, | 
|  | channel, err); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | int sam_xdmac_channel_configure(const struct device *dev, uint32_t channel, | 
|  | struct sam_xdmac_channel_config *param) | 
|  | { | 
|  | const struct sam_xdmac_dev_cfg *const dev_cfg = dev->config; | 
|  |  | 
|  | Xdmac * const xdmac = dev_cfg->regs; | 
|  |  | 
|  | if (channel >= DMA_CHANNELS_NO) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Check if the channel is enabled */ | 
|  | if (xdmac->XDMAC_GS & (XDMAC_GS_ST0 << channel)) { | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | /* Disable all channel interrupts */ | 
|  | xdmac->XDMAC_CHID[channel].XDMAC_CID = 0xFF; | 
|  | /* Clear pending Interrupt Status bit(s) */ | 
|  | (void)xdmac->XDMAC_CHID[channel].XDMAC_CIS; | 
|  |  | 
|  | /* NOTE: | 
|  | * Setting channel configuration is not required for linked list view 2 | 
|  | * to 3 modes. It is done anyway to keep the code simple. It has no | 
|  | * negative impact on the DMA functionality. | 
|  | */ | 
|  |  | 
|  | /* Set channel configuration */ | 
|  | xdmac->XDMAC_CHID[channel].XDMAC_CC = param->cfg; | 
|  |  | 
|  | /* Set data stride memory pattern */ | 
|  | xdmac->XDMAC_CHID[channel].XDMAC_CDS_MSP = param->ds_msp; | 
|  | /* Set source microblock stride */ | 
|  | xdmac->XDMAC_CHID[channel].XDMAC_CSUS = param->sus; | 
|  | /* Set destination microblock stride */ | 
|  | xdmac->XDMAC_CHID[channel].XDMAC_CDUS = param->dus; | 
|  |  | 
|  | /* Enable selected channel interrupts */ | 
|  | xdmac->XDMAC_CHID[channel].XDMAC_CIE = param->cie; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int sam_xdmac_transfer_configure(const struct device *dev, uint32_t channel, | 
|  | struct sam_xdmac_transfer_config *param) | 
|  | { | 
|  | const struct sam_xdmac_dev_cfg *const dev_cfg = dev->config; | 
|  |  | 
|  | Xdmac * const xdmac = dev_cfg->regs; | 
|  |  | 
|  | if (channel >= DMA_CHANNELS_NO) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Check if the channel is enabled */ | 
|  | if (xdmac->XDMAC_GS & (XDMAC_GS_ST0 << channel)) { | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | /* NOTE: | 
|  | * Setting source, destination address is not required for linked list | 
|  | * view 1 to 3 modes. It is done anyway to keep the code simple. It has | 
|  | * no negative impact on the DMA functionality. | 
|  | */ | 
|  |  | 
|  | /* Set source address */ | 
|  | xdmac->XDMAC_CHID[channel].XDMAC_CSA = param->sa; | 
|  | /* Set destination address */ | 
|  | xdmac->XDMAC_CHID[channel].XDMAC_CDA = param->da; | 
|  |  | 
|  | if ((param->ndc & XDMAC_CNDC_NDE) == XDMAC_CNDC_NDE_DSCR_FETCH_DIS) { | 
|  | /* | 
|  | * Linked List is disabled, configure additional transfer | 
|  | * parameters. | 
|  | */ | 
|  |  | 
|  | /* Set length of data in the microblock */ | 
|  | xdmac->XDMAC_CHID[channel].XDMAC_CUBC = param->ublen; | 
|  | /* Set block length: block length is (blen+1) microblocks */ | 
|  | xdmac->XDMAC_CHID[channel].XDMAC_CBC = param->blen; | 
|  | } else { | 
|  | /* | 
|  | * Linked List is enabled, configure additional transfer | 
|  | * parameters. | 
|  | */ | 
|  |  | 
|  | /* Set next descriptor address */ | 
|  | xdmac->XDMAC_CHID[channel].XDMAC_CNDA = param->nda; | 
|  | } | 
|  |  | 
|  | /* Set next descriptor configuration */ | 
|  | xdmac->XDMAC_CHID[channel].XDMAC_CNDC = param->ndc; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int sam_xdmac_config(const struct device *dev, uint32_t channel, | 
|  | struct dma_config *cfg) | 
|  | { | 
|  | struct sam_xdmac_dev_data *const dev_data = dev->data; | 
|  | struct sam_xdmac_channel_config channel_cfg; | 
|  | struct sam_xdmac_transfer_config transfer_cfg; | 
|  | uint32_t burst_size; | 
|  | uint32_t data_size; | 
|  | int ret; | 
|  |  | 
|  | if (channel >= DMA_CHANNELS_NO) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | __ASSERT_NO_MSG(cfg->source_data_size == cfg->dest_data_size); | 
|  | __ASSERT_NO_MSG(cfg->source_burst_length == cfg->dest_burst_length); | 
|  |  | 
|  | if (cfg->source_data_size != 1U && cfg->source_data_size != 2U && | 
|  | cfg->source_data_size != 4U) { | 
|  | LOG_ERR("Invalid 'source_data_size' value"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (cfg->block_count != 1U) { | 
|  | LOG_ERR("Only single block transfer is currently supported." | 
|  | " Please submit a patch."); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | burst_size = find_msb_set(cfg->source_burst_length) - 1; | 
|  | LOG_DBG("burst_size=%d", burst_size); | 
|  | data_size = find_msb_set(cfg->source_data_size) - 1; | 
|  | dev_data->dma_channels[channel].data_size = data_size; | 
|  | LOG_DBG("data_size=%d", data_size); | 
|  |  | 
|  | uint32_t xdmac_inc_cfg = 0; | 
|  |  | 
|  | if (cfg->head_block->source_addr_adj == DMA_ADDR_ADJ_INCREMENT | 
|  | && cfg->channel_direction == MEMORY_TO_PERIPHERAL) { | 
|  | xdmac_inc_cfg |= XDMAC_CC_SAM_INCREMENTED_AM; | 
|  | } else { | 
|  | xdmac_inc_cfg |= XDMAC_CC_SAM_FIXED_AM; | 
|  | } | 
|  |  | 
|  | if (cfg->head_block->dest_addr_adj == DMA_ADDR_ADJ_INCREMENT | 
|  | && cfg->channel_direction == PERIPHERAL_TO_MEMORY) { | 
|  | xdmac_inc_cfg |= XDMAC_CC_DAM_INCREMENTED_AM; | 
|  | } else { | 
|  | xdmac_inc_cfg |= XDMAC_CC_DAM_FIXED_AM; | 
|  | } | 
|  |  | 
|  | switch (cfg->channel_direction) { | 
|  | case MEMORY_TO_MEMORY: | 
|  | channel_cfg.cfg = | 
|  | XDMAC_CC_TYPE_MEM_TRAN | 
|  | | XDMAC_CC_MBSIZE(burst_size == 0U ? 0 : burst_size - 1) | 
|  | | XDMAC_CC_SAM_INCREMENTED_AM | 
|  | | XDMAC_CC_DAM_INCREMENTED_AM; | 
|  | break; | 
|  | case MEMORY_TO_PERIPHERAL: | 
|  | channel_cfg.cfg = | 
|  | XDMAC_CC_TYPE_PER_TRAN | 
|  | | XDMAC_CC_CSIZE(burst_size) | 
|  | | XDMAC_CC_DSYNC_MEM2PER | 
|  | | xdmac_inc_cfg; | 
|  | break; | 
|  | case PERIPHERAL_TO_MEMORY: | 
|  | channel_cfg.cfg = | 
|  | XDMAC_CC_TYPE_PER_TRAN | 
|  | | XDMAC_CC_CSIZE(burst_size) | 
|  | | XDMAC_CC_DSYNC_PER2MEM | 
|  | | xdmac_inc_cfg; | 
|  | break; | 
|  | default: | 
|  | LOG_ERR("'channel_direction' value %d is not supported", | 
|  | cfg->channel_direction); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | channel_cfg.cfg |= | 
|  | XDMAC_CC_DWIDTH(data_size) | 
|  | | XDMAC_CC_SIF_AHB_IF1 | 
|  | | XDMAC_CC_DIF_AHB_IF1 | 
|  | | XDMAC_CC_PERID(cfg->dma_slot); | 
|  | channel_cfg.ds_msp = 0U; | 
|  | channel_cfg.sus = 0U; | 
|  | channel_cfg.dus = 0U; | 
|  | channel_cfg.cie = | 
|  | (cfg->complete_callback_en ? XDMAC_CIE_BIE : XDMAC_CIE_LIE) | 
|  | | (cfg->error_callback_en ? XDMAC_INT_ERR : 0); | 
|  |  | 
|  | ret = sam_xdmac_channel_configure(dev, channel, &channel_cfg); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | dev_data->dma_channels[channel].callback = cfg->dma_callback; | 
|  | dev_data->dma_channels[channel].user_data = cfg->user_data; | 
|  |  | 
|  | (void)memset(&transfer_cfg, 0, sizeof(transfer_cfg)); | 
|  | transfer_cfg.sa = cfg->head_block->source_address; | 
|  | transfer_cfg.da = cfg->head_block->dest_address; | 
|  | transfer_cfg.ublen = cfg->head_block->block_size >> data_size; | 
|  |  | 
|  | ret = sam_xdmac_transfer_configure(dev, channel, &transfer_cfg); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int sam_xdmac_transfer_reload(const struct device *dev, uint32_t channel, | 
|  | uint32_t src, uint32_t dst, size_t size) | 
|  | { | 
|  | struct sam_xdmac_dev_data *const dev_data = dev->data; | 
|  | struct sam_xdmac_transfer_config transfer_cfg = { | 
|  | .sa = src, | 
|  | .da = dst, | 
|  | .ublen = size >> dev_data->dma_channels[channel].data_size, | 
|  | }; | 
|  |  | 
|  | return sam_xdmac_transfer_configure(dev, channel, &transfer_cfg); | 
|  | } | 
|  |  | 
|  | int sam_xdmac_transfer_start(const struct device *dev, uint32_t channel) | 
|  | { | 
|  | const struct sam_xdmac_dev_cfg *config = dev->config; | 
|  |  | 
|  | Xdmac * const xdmac = config->regs; | 
|  |  | 
|  | if (channel >= DMA_CHANNELS_NO) { | 
|  | LOG_DBG("Channel %d out of range", channel); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Check if the channel is enabled */ | 
|  | if (xdmac->XDMAC_GS & (XDMAC_GS_ST0 << channel)) { | 
|  | LOG_DBG("Channel %d already enabled", channel); | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | /* Enable channel interrupt */ | 
|  | xdmac->XDMAC_GIE = XDMAC_GIE_IE0 << channel; | 
|  | /* Enable channel */ | 
|  | xdmac->XDMAC_GE = XDMAC_GE_EN0 << channel; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int sam_xdmac_transfer_stop(const struct device *dev, uint32_t channel) | 
|  | { | 
|  | const struct sam_xdmac_dev_cfg *config = dev->config; | 
|  |  | 
|  | Xdmac * const xdmac = config->regs; | 
|  |  | 
|  | if (channel >= DMA_CHANNELS_NO) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Check if the channel is enabled */ | 
|  | if (!(xdmac->XDMAC_GS & (XDMAC_GS_ST0 << channel))) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Disable channel */ | 
|  | xdmac->XDMAC_GD = XDMAC_GD_DI0 << channel; | 
|  | /* Disable channel interrupt */ | 
|  | xdmac->XDMAC_GID = XDMAC_GID_ID0 << channel; | 
|  | /* Disable all channel interrupts */ | 
|  | xdmac->XDMAC_CHID[channel].XDMAC_CID = 0xFF; | 
|  | /* Clear the pending Interrupt Status bit(s) */ | 
|  | (void)xdmac->XDMAC_CHID[channel].XDMAC_CIS; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int sam_xdmac_initialize(const struct device *dev) | 
|  | { | 
|  | const struct sam_xdmac_dev_cfg *const dev_cfg = dev->config; | 
|  |  | 
|  | Xdmac * const xdmac = dev_cfg->regs; | 
|  |  | 
|  | /* Configure interrupts */ | 
|  | dev_cfg->irq_config(); | 
|  |  | 
|  | /* Enable XDMAC clock in PMC */ | 
|  | (void)clock_control_on(SAM_DT_PMC_CONTROLLER, | 
|  | (clock_control_subsys_t)&dev_cfg->clock_cfg); | 
|  |  | 
|  | /* Disable all channels */ | 
|  | xdmac->XDMAC_GD = UINT32_MAX; | 
|  | /* Disable all channel interrupts */ | 
|  | xdmac->XDMAC_GID = UINT32_MAX; | 
|  |  | 
|  | /* Enable module's IRQ */ | 
|  | irq_enable(dev_cfg->irq_id); | 
|  |  | 
|  | LOG_INF("Device %s initialized", dev->name); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct dma_driver_api sam_xdmac_driver_api = { | 
|  | .config = sam_xdmac_config, | 
|  | .reload = sam_xdmac_transfer_reload, | 
|  | .start = sam_xdmac_transfer_start, | 
|  | .stop = sam_xdmac_transfer_stop, | 
|  | }; | 
|  |  | 
|  | /* DMA0 */ | 
|  |  | 
|  | static void dma0_sam_irq_config(void) | 
|  | { | 
|  | IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), sam_xdmac_isr, | 
|  | DEVICE_DT_INST_GET(0), 0); | 
|  | } | 
|  |  | 
|  | static const struct sam_xdmac_dev_cfg dma0_sam_config = { | 
|  | .regs = (Xdmac *)DT_INST_REG_ADDR(0), | 
|  | .irq_config = dma0_sam_irq_config, | 
|  | .clock_cfg = SAM_DT_INST_CLOCK_PMC_CFG(0), | 
|  | .irq_id = DT_INST_IRQN(0), | 
|  | }; | 
|  |  | 
|  | static struct sam_xdmac_dev_data dma0_sam_data; | 
|  |  | 
|  | DEVICE_DT_INST_DEFINE(0, &sam_xdmac_initialize, NULL, | 
|  | &dma0_sam_data, &dma0_sam_config, POST_KERNEL, | 
|  | CONFIG_DMA_INIT_PRIORITY, &sam_xdmac_driver_api); |