From dbcc7429e620045d49e573b0ab5951c1ddc3857d Mon Sep 17 00:00:00 2001 From: Piotr Mienkowski Date: Fri, 20 Jan 2017 17:52:34 +0100 Subject: [PATCH] drivers: Add Atmel SAM DMA (XDMAC) driver Added DMA (XDMAC) driver for Atmel SAM MCU family. The driver provides private DMA API to be used by the SAM family device drivers. Public DMA API to be used by user space programs is currently missing. Tested on Atmel SMART SAM E70 Xplained board Origin: Original Jira: ZEP-1609 Signed-off-by: Piotr Mienkowski --- .../atmel_sam/same70/Kconfig.defconfig.series | 9 + arch/arm/soc/atmel_sam/same70/soc.h | 45 +++ drivers/dma/CMakeLists.txt | 1 + drivers/dma/Kconfig | 2 + drivers/dma/Kconfig.sam_xdmac | 13 + drivers/dma/dma_sam_xdmac.c | 365 ++++++++++++++++++ drivers/dma/dma_sam_xdmac.h | 132 +++++++ 7 files changed, 567 insertions(+) create mode 100644 drivers/dma/Kconfig.sam_xdmac create mode 100644 drivers/dma/dma_sam_xdmac.c create mode 100644 drivers/dma/dma_sam_xdmac.h diff --git a/arch/arm/soc/atmel_sam/same70/Kconfig.defconfig.series b/arch/arm/soc/atmel_sam/same70/Kconfig.defconfig.series index 305d5a180dc..259826e36bd 100644 --- a/arch/arm/soc/atmel_sam/same70/Kconfig.defconfig.series +++ b/arch/arm/soc/atmel_sam/same70/Kconfig.defconfig.series @@ -34,4 +34,13 @@ config SYS_CLOCK_HW_CYCLES_PER_SEC int default 300000000 +# Configure default device drivers. If a feature is supported by more than one +# device driver the default configuration will be placed in the board defconfig +# file. + +if DMA +config DMA_SAM_XDMAC + default y +endif # DMA + endif # SOC_SERIES_SAME70 diff --git a/arch/arm/soc/atmel_sam/same70/soc.h b/arch/arm/soc/atmel_sam/same70/soc.h index 1cd592711f4..e822d21ddc8 100644 --- a/arch/arm/soc/atmel_sam/same70/soc.h +++ b/arch/arm/soc/atmel_sam/same70/soc.h @@ -48,6 +48,51 @@ #endif /* _ASMLANGUAGE */ +/** Peripheral Hardware Request Line Identifier */ +#define DMA_PERID_HSMCI_TX_RX 0 +#define DMA_PERID_SPI0_TX 1 +#define DMA_PERID_SPI0_RX 2 +#define DMA_PERID_SPI1_TX 3 +#define DMA_PERID_SPI1_RX 4 +#define DMA_PERID_QSPI_TX 5 +#define DMA_PERID_QSPI_RX 6 +#define DMA_PERID_USART0_TX 7 +#define DMA_PERID_USART0_RX 8 +#define DMA_PERID_USART1_TX 9 +#define DMA_PERID_USART1_RX 10 +#define DMA_PERID_USART2_TX 11 +#define DMA_PERID_USART2_RX 12 +#define DMA_PERID_PWM0_TX 13 +#define DMA_PERID_TWIHS0_TX 14 +#define DMA_PERID_TWIHS0_RX 15 +#define DMA_PERID_TWIHS1_TX 16 +#define DMA_PERID_TWIHS1_RX 17 +#define DMA_PERID_TWIHS2_TX 18 +#define DMA_PERID_TWIHS2_RX 19 +#define DMA_PERID_UART0_TX 20 +#define DMA_PERID_UART0_RX 21 +#define DMA_PERID_UART1_TX 22 +#define DMA_PERID_UART1_RX 23 +#define DMA_PERID_UART2_TX 24 +#define DMA_PERID_UART2_RX 25 +#define DMA_PERID_UART3_TX 26 +#define DMA_PERID_UART3_RX 27 +#define DMA_PERID_UART4_TX 28 +#define DMA_PERID_UART4_RX 29 +#define DMA_PERID_DACC_TX 30 +#define DMA_PERID_SSC_TX 32 +#define DMA_PERID_SSC_RX 33 +#define DMA_PERID_PIOA_RX 34 +#define DMA_PERID_AFEC0_RX 35 +#define DMA_PERID_AFEC1_RX 36 +#define DMA_PERID_AES_TX 37 +#define DMA_PERID_AES_RX 38 +#define DMA_PERID_PWM1_TX 39 +#define DMA_PERID_TC0_RX 40 +#define DMA_PERID_TC1_RX 41 +#define DMA_PERID_TC2_RX 42 +#define DMA_PERID_TC3_RX 43 + /** Processor Clock (HCLK) Frequency */ #define SOC_ATMEL_SAM_HCLK_FREQ_HZ CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC /** Master Clock (MCK) Frequency */ diff --git a/drivers/dma/CMakeLists.txt b/drivers/dma/CMakeLists.txt index 371b5af7119..748d89a8191 100644 --- a/drivers/dma/CMakeLists.txt +++ b/drivers/dma/CMakeLists.txt @@ -1,2 +1,3 @@ zephyr_sources_ifdef(CONFIG_DMA_QMSI dma_qmsi.c) +zephyr_sources_ifdef(CONFIG_DMA_SAM_XDMAC dma_sam_xdmac.c) zephyr_sources_ifdef(CONFIG_DMA_STM32F4X dma_stm32f4x.c) diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 70e08d75dda..8940bdde379 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -70,4 +70,6 @@ source "drivers/dma/Kconfig.qmsi" source "drivers/dma/Kconfig.stm32f4x" +source "drivers/dma/Kconfig.sam_xdmac" + endif # DMA diff --git a/drivers/dma/Kconfig.sam_xdmac b/drivers/dma/Kconfig.sam_xdmac new file mode 100644 index 00000000000..7f288f5bacd --- /dev/null +++ b/drivers/dma/Kconfig.sam_xdmac @@ -0,0 +1,13 @@ +# Kconfig - Atmel XDMAC SAM driver configuration options +# +# Copyright (c) 2017 Piotr Mienkowski +# +# SPDX-License-Identifier: Apache-2.0 +# + +menuconfig DMA_SAM_XDMAC + bool "Atmel SAM DMA (XDMAC) driver" + depends on SOC_FAMILY_SAM + default n + help + Enable Atmel SAM MCU Family Direct Memory Access (XDMAC) driver. diff --git a/drivers/dma/dma_sam_xdmac.c b/drivers/dma/dma_sam_xdmac.c new file mode 100644 index 00000000000..ab54b1aea51 --- /dev/null +++ b/drivers/dma/dma_sam_xdmac.c @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2017 comsuisse AG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file + * @brief Atmel SAM MCU family Direct Memory Access (XDMAC) driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "dma_sam_xdmac.h" + +#define SYS_LOG_DOMAIN "dev/dma_sam_xdmac" +#define SYS_LOG_LEVEL CONFIG_SYS_LOG_DMA_LEVEL +#include + +#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 { + dma_callback callback; +}; + +/* Device constant configuration parameters */ +struct sam_xdmac_dev_cfg { + Xdmac *regs; + void (*irq_config)(void); + u8_t periph_id; + u8_t irq_id; +}; + +/* Device run time data */ +struct sam_xdmac_dev_data { + struct sam_xdmac_channel_cfg dma_channels[DMA_CHANNELS_NO]; +}; + +#define DEV_NAME(dev) ((dev)->config->name) +#define DEV_CFG(dev) \ + ((const struct sam_xdmac_dev_cfg *const)(dev)->config->config_info) +#define DEV_DATA(dev) \ + ((struct sam_xdmac_dev_data *const)(dev)->driver_data) + +static void sam_xdmac_isr(void *arg) +{ + struct device *dev = (struct device *)arg; + const struct sam_xdmac_dev_cfg *const dev_cfg = DEV_CFG(dev); + struct sam_xdmac_dev_data *const dev_data = DEV_DATA(dev); + Xdmac *const xdmac = dev_cfg->regs; + struct sam_xdmac_channel_cfg *channel_cfg; + u32_t isr_status; + u32_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, err); + } + } +} + +int sam_xdmac_channel_configure(struct device *dev, u32_t channel, + struct sam_xdmac_channel_config *param) +{ + const struct sam_xdmac_dev_cfg *const dev_cfg = DEV_CFG(dev); + 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(struct device *dev, u32_t channel, + struct sam_xdmac_transfer_config *param) +{ + const struct sam_xdmac_dev_cfg *const dev_cfg = DEV_CFG(dev); + 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(struct device *dev, u32_t channel, + struct dma_config *cfg) +{ + struct sam_xdmac_dev_data *const dev_data = DEV_DATA(dev); + struct sam_xdmac_channel_config channel_cfg; + struct sam_xdmac_transfer_config transfer_cfg; + u32_t burst_size; + u32_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 != 1 && cfg->source_data_size != 2 && + cfg->source_data_size != 4) { + SYS_LOG_ERR("Invalid 'source_data_size' value"); + return -EINVAL; + } + + if (cfg->block_count != 1) { + SYS_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; + SYS_LOG_DBG("burst_size=%d", burst_size); + data_size = find_msb_set(cfg->source_data_size) - 1; + SYS_LOG_DBG("data_size=%d", data_size); + + switch (cfg->channel_direction) { + case MEMORY_TO_MEMORY: + channel_cfg.cfg = + XDMAC_CC_TYPE_MEM_TRAN + | XDMAC_CC_MBSIZE(burst_size == 0 ? 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_CC_SAM_INCREMENTED_AM + | XDMAC_CC_DAM_FIXED_AM; + break; + case PERIPHERAL_TO_MEMORY: + channel_cfg.cfg = + XDMAC_CC_TYPE_PER_TRAN + | XDMAC_CC_CSIZE(burst_size) + | XDMAC_CC_DSYNC_PER2MEM + | XDMAC_CC_SAM_FIXED_AM + | XDMAC_CC_DAM_INCREMENTED_AM; + break; + default: + SYS_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 = 0; + channel_cfg.sus = 0; + channel_cfg.dus = 0; + 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; + + 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; +} + +int sam_xdmac_transfer_start(struct device *dev, u32_t channel) +{ + Xdmac *const xdmac = DEV_CFG(dev)->regs; + + if (channel >= DMA_CHANNELS_NO) { + return -EINVAL; + } + + /* Check if the channel is enabled */ + if (xdmac->XDMAC_GS & (XDMAC_GS_ST0 << 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(struct device *dev, u32_t channel) +{ + Xdmac *const xdmac = DEV_CFG(dev)->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(struct device *dev) +{ + const struct sam_xdmac_dev_cfg *const dev_cfg = DEV_CFG(dev); + Xdmac *const xdmac = dev_cfg->regs; + + /* Configure interrupts */ + dev_cfg->irq_config(); + + /* Enable module's clock */ + soc_pmc_peripheral_enable(dev_cfg->periph_id); + + /* 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); + + SYS_LOG_INF("Device %s initialized", DEV_NAME(dev)); + + return 0; +} + +static const struct dma_driver_api sam_xdmac_driver_api = { + .config = sam_xdmac_config, + .start = sam_xdmac_transfer_start, + .stop = sam_xdmac_transfer_stop, +}; + +/* DMA0 */ + +static struct device DEVICE_NAME_GET(dma0_sam); + +static void dma0_sam_irq_config(void) +{ + IRQ_CONNECT(XDMAC_IRQn, CONFIG_DMA_0_IRQ_PRI, sam_xdmac_isr, + DEVICE_GET(dma0_sam), 0); +} + +static const struct sam_xdmac_dev_cfg dma0_sam_config = { + .regs = XDMAC, + .irq_config = dma0_sam_irq_config, + .periph_id = ID_XDMAC, + .irq_id = XDMAC_IRQn, +}; + +static struct sam_xdmac_dev_data dma0_sam_data; + +DEVICE_AND_API_INIT(dma0_sam, CONFIG_DMA_0_NAME, &sam_xdmac_initialize, + &dma0_sam_data, &dma0_sam_config, POST_KERNEL, + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &sam_xdmac_driver_api); diff --git a/drivers/dma/dma_sam_xdmac.h b/drivers/dma/dma_sam_xdmac.h new file mode 100644 index 00000000000..ef78b1bcd08 --- /dev/null +++ b/drivers/dma/dma_sam_xdmac.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2017 comsuisse AG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file + * @brief Atmel SAM MCU family Direct Memory Access (XDMAC) driver. + */ + +#ifndef _DMA_SAM_XDMAC_H_ +#define _DMA_SAM_XDMAC_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** DMA transfer callback */ +typedef void (*dma_callback)(struct device *dev, u32_t channel, int error_code); + +/* XDMA_MBR_UBC */ +#define XDMA_UBC_NDE (0x1u << 24) +#define XDMA_UBC_NDE_FETCH_DIS (0x0u << 24) +#define XDMA_UBC_NDE_FETCH_EN (0x1u << 24) +#define XDMA_UBC_NSEN (0x1u << 25) +#define XDMA_UBC_NSEN_UNCHANGED (0x0u << 25) +#define XDMA_UBC_NSEN_UPDATED (0x1u << 25) +#define XDMA_UBC_NDEN (0x1u << 26) +#define XDMA_UBC_NDEN_UNCHANGED (0x0u << 26) +#define XDMA_UBC_NDEN_UPDATED (0x1u << 26) +#define XDMA_UBC_NVIEW_SHIFT 27 +#define XDMA_UBC_NVIEW_MASK (0x3u << XDMA_UBC_NVIEW_SHIFT) +#define XDMA_UBC_NVIEW_NDV0 (0x0u << XDMA_UBC_NVIEW_SHIFT) +#define XDMA_UBC_NVIEW_NDV1 (0x1u << XDMA_UBC_NVIEW_SHIFT) +#define XDMA_UBC_NVIEW_NDV2 (0x2u << XDMA_UBC_NVIEW_SHIFT) +#define XDMA_UBC_NVIEW_NDV3 (0x3u << XDMA_UBC_NVIEW_SHIFT) + +/** DMA channel configuration parameters */ +struct sam_xdmac_channel_config { + /** Configuration Register */ + u32_t cfg; + /** Data Stride / Memory Set Pattern Register */ + u32_t ds_msp; + /** Source Microblock Stride */ + u32_t sus; + /** Destination Microblock Stride */ + u32_t dus; + /** Channel Interrupt Enable */ + u32_t cie; +}; + +/** DMA transfer configuration parameters */ +struct sam_xdmac_transfer_config { + /** Microblock length */ + u32_t ublen; + /** Source Address */ + u32_t sa; + /** Destination Address */ + u32_t da; + /** Block length (The length of the block is (blen+1) microblocks) */ + u32_t blen; + /** Next descriptor address */ + u32_t nda; + /** Next descriptor configuration */ + u32_t ndc; +}; + +/** DMA Master transfer linked list view 0 structure */ +struct sam_xdmac_linked_list_desc_view0 { + /** Next Descriptor Address */ + u32_t mbr_nda; + /** Microblock Control */ + u32_t mbr_ubc; + /** Transfer Address */ + u32_t mbr_ta; +}; + +/** DMA Master transfer linked list view 1 structure */ +struct sam_xdmac_linked_list_desc_view1 { + /** Next Descriptor Address */ + u32_t mbr_nda; + /** Microblock Control */ + u32_t mbr_ubc; + /** Source Address */ + u32_t mbr_sa; + /** Destination Address */ + u32_t mbr_da; +}; + +/** DMA Master transfer linked list view 2 structure */ +struct sam_xdmac_linked_list_desc_view2 { + /** Next Descriptor Address */ + u32_t mbr_nda; + /** Microblock Control */ + u32_t mbr_ubc; + /** Source Address */ + u32_t mbr_sa; + /** Destination Address */ + u32_t mbr_da; + /** Configuration Register */ + u32_t mbr_cfg; +}; + +/** DMA Master transfer linked list view 3 structure */ +struct sam_xdmac_linked_list_desc_view3 { + /** Next Descriptor Address */ + u32_t mbr_nda; + /** Microblock Control */ + u32_t mbr_ubc; + /** Source Address */ + u32_t mbr_sa; + /** Destination Address */ + u32_t mbr_da; + /** Configuration Register */ + u32_t mbr_cfg; + /** Block Control */ + u32_t mbr_bc; + /** Data Stride */ + u32_t mbr_ds; + /** Source Microblock Stride */ + u32_t mbr_sus; + /** Destination Microblock Stride */ + u32_t mbr_dus; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* _DMA_SAM_XDMAC_H_ */