drivers: dma: Add pl330 dma driver

Add PL330 dma driver support, this driver
- supports 32 bit memory to memory operation
- supports 36 bit memory to memory operation
  under a config flag DMA_64BIT
- supports secure channel

Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
diff --git a/CODEOWNERS b/CODEOWNERS
index bf53304..5c193d5 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -158,6 +158,7 @@
 /drivers/dma/*dw*                         @tbursztyka
 /drivers/dma/*sam0*                       @Sizurka
 /drivers/dma/dma_stm32*                   @cybertale
+/drivers/dma/*pl330*                      @raveenp
 /drivers/eeprom/                          @henrikbrixandersen
 /drivers/eeprom/eeprom_stm32.c            @KwonTae-young
 /drivers/entropy/*rv32m1*                 @MaureenHelm
diff --git a/drivers/dma/CMakeLists.txt b/drivers/dma/CMakeLists.txt
index c5314d2..fa153a7 100644
--- a/drivers/dma/CMakeLists.txt
+++ b/drivers/dma/CMakeLists.txt
@@ -12,3 +12,4 @@
 zephyr_library_sources_ifdef(CONFIG_DMA_SAM0		dma_sam0.c)
 zephyr_library_sources_ifdef(CONFIG_USERSPACE		dma_handlers.c)
 zephyr_library_sources_ifdef(CONFIG_DMA_MCUX_EDMA	dma_mcux_edma.c)
+zephyr_library_sources_ifdef(CONFIG_DMA_PL330		dma_pl330.c)
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index 383bc59..0467a94 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -56,4 +56,6 @@
 
 source "drivers/dma/Kconfig.mcux_edma"
 
+source "drivers/dma/Kconfig.dma_pl330"
+
 endif # DMA
diff --git a/drivers/dma/Kconfig.dma_pl330 b/drivers/dma/Kconfig.dma_pl330
new file mode 100644
index 0000000..4e51d8f
--- /dev/null
+++ b/drivers/dma/Kconfig.dma_pl330
@@ -0,0 +1,8 @@
+# Copyright 2020 Broadcom
+# SPDX-License-Identifier: Apache-2.0
+
+config DMA_PL330
+	bool
+	prompt "PL330 DMA driver"
+	help
+	  This option enables support of pl330 DMA Controller.
diff --git a/drivers/dma/dma_pl330.c b/drivers/dma/dma_pl330.c
new file mode 100644
index 0000000..1b9af79
--- /dev/null
+++ b/drivers/dma/dma_pl330.c
@@ -0,0 +1,599 @@
+/*
+ * Copyright 2020 Broadcom
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <device.h>
+#include <drivers/dma.h>
+#include <errno.h>
+#include <init.h>
+#include <string.h>
+#include <soc.h>
+#include <sys/__assert.h>
+#include "dma_pl330.h"
+
+#define LOG_LEVEL CONFIG_DMA_LOG_LEVEL
+#include <logging/log.h>
+LOG_MODULE_REGISTER(dma_pl330);
+
+#define DEV_NAME(dev) ((dev)->name)
+#define DEV_CFG(dev) \
+	((const struct dma_pl330_config *const)(dev)->config_info)
+
+#define DEV_DATA(dev) \
+	((struct dma_pl330_dev_data *const)(dev)->driver_data)
+
+#define BYTE_WIDTH(burst_size) (1 << (burst_size))
+
+static int dma_pl330_submit(struct device *dev, uint64_t dst,
+			    uint64_t src, uint32_t channel, uint32_t size);
+
+static void dma_pl330_get_counter(struct dma_pl330_ch_internal *ch_handle,
+				  uint32_t *psrc_byte_width,
+				  uint32_t *pdst_byte_width,
+				  uint32_t *ploop_counter,
+				  uint32_t *presidue)
+{
+	uint32_t srcbytewidth, dstbytewidth;
+	uint32_t loop_counter, residue;
+
+	srcbytewidth = BYTE_WIDTH(ch_handle->src_burst_sz);
+	dstbytewidth = BYTE_WIDTH(ch_handle->dst_burst_sz);
+
+	loop_counter = ch_handle->trans_size /
+		       (srcbytewidth * (ch_handle->src_burst_len + 1));
+
+	residue = ch_handle->trans_size - loop_counter *
+		  (srcbytewidth * (ch_handle->src_burst_len + 1));
+
+	*psrc_byte_width = srcbytewidth;
+	*pdst_byte_width = dstbytewidth;
+	*ploop_counter = loop_counter;
+	*presidue = residue;
+}
+
+static uint32_t dma_pl330_ch_ccr(struct dma_pl330_ch_internal *ch_handle)
+{
+	uint32_t ccr;
+	int secure = ch_handle->nonsec_mode ? SRC_PRI_NONSEC_VALUE :
+		     SRC_PRI_SEC_VALUE;
+
+	ccr = ((ch_handle->dst_cache_ctrl & CC_SRCCCTRL_MASK) <<
+		CC_DSTCCTRL_SHIFT) +
+	       ((ch_handle->nonsec_mode) << CC_DSTNS_SHIFT) +
+	       (ch_handle->dst_burst_len << CC_DSTBRSTLEN_SHIFT) +
+	       (ch_handle->dst_burst_sz << CC_DSTBRSTSIZE_SHIFT) +
+	       (ch_handle->dst_inc << CC_DSTINC_SHIFT) +
+	       ((ch_handle->src_cache_ctrl & CC_SRCCCTRL_MASK) <<
+		CC_SRCCCTRL_SHIFT) +
+	       (secure << CC_SRCPRI_SHIFT) +
+	       (ch_handle->src_burst_len << CC_SRCBRSTLEN_SHIFT)  +
+	       (ch_handle->src_burst_sz << CC_SRCBRSTSIZE_SHIFT)  +
+	       (ch_handle->src_inc << CC_SRCINC_SHIFT);
+
+	return ccr;
+}
+
+static void dma_pl330_calc_burstsz_len(struct dma_pl330_ch_internal *ch_handle,
+				       uint64_t dst, uint64_t src,
+				       uint32_t size)
+{
+	uint32_t byte_width, burst_sz, burst_len;
+
+	burst_sz = MAX_BURST_SIZE_LOG2;
+	/* src, dst and size should be aligned to burst size in bytes */
+	while ((src | dst | size) & ((BYTE_WIDTH(burst_sz)) - 1)) {
+		burst_sz--;
+	}
+
+	byte_width = BYTE_WIDTH(burst_sz);
+
+	burst_len = MAX_BURST_LEN;
+	while (burst_len) {
+		/* Choose burst length so that size is aligned */
+		if (!(size % ((burst_len + 1) << byte_width))) {
+			break;
+		}
+
+		burst_len--;
+	}
+
+	ch_handle->src_burst_len = burst_len;
+	ch_handle->src_burst_sz = burst_sz;
+	ch_handle->dst_burst_len = burst_len;
+	ch_handle->dst_burst_sz = burst_sz;
+}
+
+#ifdef CONFIG_DMA_64BIT
+static void dma_pl330_cfg_dmac_add_control(uint32_t control_reg_base,
+					   uint64_t dst, uint64_t src, int ch)
+{
+	uint32_t src_h = src >> 32;
+	uint32_t dst_h = dst >> 32;
+	uint32_t dmac_higher_addr;
+
+	dmac_higher_addr = ((dst_h & HIGHER_32_ADDR_MASK) << DST_ADDR_SHIFT) |
+			   (src_h & HIGHER_32_ADDR_MASK);
+
+	sys_write32(dmac_higher_addr,
+		    control_reg_base +
+		    (ch * CONTROL_OFFSET)
+		   );
+}
+#endif
+
+static void dma_pl330_config_channel(struct dma_pl330_ch_config *ch_cfg,
+				     uint64_t dst, uint64_t src, uint32_t size)
+{
+	struct dma_pl330_ch_internal *ch_handle = &ch_cfg->internal;
+
+	memset(ch_handle, 0, sizeof(*ch_handle));
+	ch_handle->src_addr = src;
+	ch_handle->dst_addr = dst;
+	ch_handle->trans_size = size;
+
+	if (ch_cfg->src_addr_adj == DMA_ADDR_ADJ_INCREMENT) {
+		ch_handle->src_inc = 1;
+	}
+
+	if (ch_cfg->dst_addr_adj == DMA_ADDR_ADJ_INCREMENT) {
+		ch_handle->dst_inc = 1;
+	}
+}
+
+static inline uint32_t dma_pl330_gen_mov(uint8_t *buf,
+					 enum dmamov_type type,
+					 uint32_t val)
+{
+	sys_write8(OP_DMA_MOV, (uint32_t)buf + 0);
+	sys_write8(type, (uint32_t)buf + 1);
+	sys_write8(val, (uint32_t)buf + 2);
+	sys_write8(val >> 8, (uint32_t)buf + 3);
+	sys_write8(val >> 16, (uint32_t)buf + 4);
+	sys_write8(val >> 24, (uint32_t)buf + 5);
+
+	return SZ_CMD_DMAMOV;
+}
+
+static inline void dma_pl330_gen_op(uint8_t opcode, uint32_t addr, uint32_t val)
+{
+	sys_write8(opcode, addr);
+	sys_write8(val, addr + 1);
+}
+
+static int dma_pl330_setup_ch(struct device *dev,
+			      struct dma_pl330_ch_internal *ch_dat,
+			      int ch)
+{
+	uint32_t dma_exe_addr, offset = 0, ccr;
+	uint32_t lp0_start, lp1_start;
+	uint32_t loop_counter0 = 0, loop_counter1 = 0;
+	uint32_t srcbytewidth, dstbytewidth;
+	uint32_t loop_counter, residue;
+	struct dma_pl330_dev_data *const dev_data = DEV_DATA(dev);
+	struct dma_pl330_ch_config *channel_cfg;
+	uint8_t *dma_exec_addr8;
+	int secure = ch_dat->nonsec_mode ? SRC_PRI_NONSEC_VALUE :
+				SRC_PRI_SEC_VALUE;
+
+	channel_cfg = &dev_data->channels[ch];
+	dma_exe_addr = channel_cfg->dma_exe_addr;
+	dma_exec_addr8 = (uint8_t *)dma_exe_addr;
+
+	offset  += dma_pl330_gen_mov(dma_exec_addr8,
+				     SAR, ch_dat->src_addr);
+
+	offset  += dma_pl330_gen_mov(dma_exec_addr8 + offset,
+				     DAR, ch_dat->dst_addr);
+
+	ccr = dma_pl330_ch_ccr(ch_dat);
+
+	offset  += dma_pl330_gen_mov(dma_exec_addr8 + offset,
+				     CCR, ccr);
+
+	dma_pl330_get_counter(ch_dat, &srcbytewidth, &dstbytewidth,
+			      &loop_counter, &residue);
+
+	if (loop_counter >= PL330_LOOP_COUNTER0_MAX) {
+		loop_counter0 = PL330_LOOP_COUNTER0_MAX - 1;
+		loop_counter1 = loop_counter / PL330_LOOP_COUNTER0_MAX - 1;
+		dma_pl330_gen_op(OP_DMA_LOOP_COUNT1, dma_exe_addr + offset,
+				 (loop_counter1 & 0xff));
+		offset = offset + 2;
+		dma_pl330_gen_op(OP_DMA_LOOP, dma_exe_addr + offset,
+				 (loop_counter0 & 0xff));
+		offset = offset + 2;
+		lp1_start = offset;
+		lp0_start = offset;
+		sys_write8(OP_DMA_LD, (dma_exe_addr + offset));
+		sys_write8(OP_DMA_ST, (dma_exe_addr + offset + 1));
+		offset = offset + 2;
+		dma_pl330_gen_op(OP_DMA_LP_BK_JMP1, (dma_exe_addr + offset),
+				 ((offset - lp0_start) & 0xff));
+		offset = offset + 2;
+		dma_pl330_gen_op(OP_DMA_LOOP, (dma_exe_addr + offset),
+				 (loop_counter0 & 0xff));
+		offset = offset + 2;
+		loop_counter1--;
+		dma_pl330_gen_op(OP_DMA_LP_BK_JMP2, (dma_exe_addr + offset),
+				 ((offset - lp1_start) & 0xff));
+		offset = offset + 2;
+	}
+
+	if ((loop_counter % PL330_LOOP_COUNTER0_MAX) != 0) {
+		loop_counter0 = (loop_counter % PL330_LOOP_COUNTER0_MAX) - 1;
+		dma_pl330_gen_op(OP_DMA_LOOP, (dma_exe_addr + offset),
+				 (loop_counter0 & 0xff));
+		offset = offset + 2;
+		loop_counter1--;
+		lp0_start = offset;
+		sys_write8(OP_DMA_LD, (dma_exe_addr + offset));
+		sys_write8(OP_DMA_ST, (dma_exe_addr + offset + 1));
+		offset = offset + 2;
+		dma_pl330_gen_op(OP_DMA_LP_BK_JMP1, (dma_exe_addr + offset),
+				 ((offset - lp0_start) & 0xff));
+		offset = offset + 2;
+	}
+
+	if (residue != 0) {
+		ccr = ((ch_dat->nonsec_mode) << CC_DSTNS_SHIFT) +
+		       (0x0 << CC_DSTBRSTLEN_SHIFT) +
+		       (0x0 << CC_DSTBRSTSIZE_SHIFT) +
+		       (ch_dat->dst_inc << CC_DSTINC_SHIFT) +
+		       (secure << CC_SRCPRI_SHIFT) +
+		       (0x0 << CC_SRCBRSTLEN_SHIFT) +
+		       (0x0 << CC_SRCBRSTSIZE_SHIFT) +
+		       ch_dat->src_inc;
+		offset += dma_pl330_gen_mov(dma_exec_addr8 + offset,
+					    CCR, ccr);
+		dma_pl330_gen_op(OP_DMA_LOOP, (dma_exe_addr + offset),
+				 ((residue - 1) & 0xff));
+		offset = offset + 2;
+		lp0_start = offset;
+		sys_write8(OP_DMA_LD, (dma_exe_addr + offset));
+		sys_write8(OP_DMA_ST, (dma_exe_addr + offset + 1));
+		offset = offset + 2;
+		dma_pl330_gen_op(OP_DMA_LP_BK_JMP1, (dma_exe_addr + offset),
+				 ((offset - lp0_start) & 0xff));
+		offset = offset + 2;
+	}
+
+	sys_write8(OP_DMA_END, (dma_exe_addr + offset));
+	sys_write8(OP_DMA_END, (dma_exe_addr + offset + 1));
+	sys_write8(OP_DMA_END, (dma_exe_addr + offset + 2));
+	sys_write8(OP_DMA_END, (dma_exe_addr + offset + 3));
+
+	return 0;
+}
+
+static int dma_pl330_start_dma_ch(struct device *dev,
+				  uint32_t reg_base, int ch, int secure)
+{
+	struct dma_pl330_dev_data *const dev_data = DEV_DATA(dev);
+	struct dma_pl330_ch_config *channel_cfg;
+	uint32_t count = 0U;
+	uint32_t data;
+
+	channel_cfg = &dev_data->channels[ch];
+	do {
+		data = sys_read32(reg_base + DMAC_PL330_DBGSTATUS);
+		if (++count > DMA_TIMEOUT_US) {
+			return -ETIMEDOUT;
+		}
+		k_busy_wait(1);
+	} while ((data & DATA_MASK) != 0);
+
+	sys_write32(((ch << DMA_INTSR1_SHIFT) +
+		    (DMA_INTSR0 << DMA_INTSR0_SHIFT) +
+		    (secure << DMA_SECURE_SHIFT) + (ch << DMA_CH_SHIFT)),
+		    reg_base + DMAC_PL330_DBGINST0);
+
+	sys_write32(channel_cfg->dma_exe_addr,
+		    reg_base + DMAC_PL330_DBGINST1);
+
+	sys_write32(0x0, reg_base + DMAC_PL330_DBGCMD);
+
+	count = 0U;
+	do {
+		data = sys_read32(reg_base + DMAC_PL330_DBGCMD);
+		if (++count > DMA_TIMEOUT_US) {
+			return -ETIMEDOUT;
+		}
+		k_busy_wait(1);
+	} while ((data & DATA_MASK) != 0);
+
+	return 0;
+}
+
+static int dma_pl330_wait(uint32_t reg_base, int ch)
+{
+	int count = 0U;
+	uint32_t cs0_reg = reg_base + DMAC_PL330_CS0;
+
+	do {
+		if (++count > DMA_TIMEOUT_US) {
+			return -ETIMEDOUT;
+		}
+		k_busy_wait(1);
+	} while (((sys_read32(cs0_reg + ch * 8)) & CH_STATUS_MASK) != 0);
+
+	return 0;
+}
+
+static int dma_pl330_xfer(struct device *dev, uint64_t dst,
+			  uint64_t src, uint32_t size, uint32_t channel,
+			  uint32_t *xfer_size)
+{
+	struct dma_pl330_dev_data *const dev_data = DEV_DATA(dev);
+	const struct dma_pl330_config *const dev_cfg = DEV_CFG(dev);
+	struct dma_pl330_ch_config *channel_cfg;
+	struct dma_pl330_ch_internal *ch_handle;
+	int ret;
+	uint32_t max_size;
+
+	channel_cfg = &dev_data->channels[channel];
+	ch_handle = &channel_cfg->internal;
+
+	dma_pl330_calc_burstsz_len(ch_handle, dst, src, size);
+
+	max_size = GET_MAX_DMA_SIZE((1 << ch_handle->src_burst_sz),
+				    ch_handle->src_burst_len);
+
+	if (size > max_size) {
+		size = max_size;
+	}
+
+	dma_pl330_config_channel(channel_cfg, dst, src, size);
+#ifdef CONFIG_DMA_64BIT
+	/*
+	 * Pl330 supports only 4GB boundary, but boundary region can be
+	 * configured.
+	 * Support added for 36bit address, lower 32bit address are configured
+	 * in pl330 registers and higher 4bit address are configured in
+	 * LS_ICFG_DMAC_AXI_ADD_CONTROL registers.
+	 * Each channel has 1 control register to configure higher 4bit address.
+	 */
+
+	dma_pl330_cfg_dmac_add_control(dev_cfg->control_reg_base,
+				       dst, src, channel);
+#endif
+	ret = dma_pl330_setup_ch(dev, ch_handle, channel);
+	if (ret) {
+		LOG_ERR("Failed to setup channel for DMA PL330");
+		goto err;
+	}
+
+	ret = dma_pl330_start_dma_ch(dev, dev_cfg->reg_base, channel,
+				     ch_handle->nonsec_mode);
+	if (ret) {
+		LOG_ERR("Failed to start DMA PL330");
+		goto err;
+	}
+
+	ret = dma_pl330_wait(dev_cfg->reg_base, channel);
+	if (ret) {
+		LOG_ERR("Failed waiting to finish DMA PL330");
+		goto err;
+	}
+
+	*xfer_size = size;
+err:
+	return ret;
+}
+
+#if CONFIG_DMA_64BIT
+static int dma_pl330_handle_boundary(struct device *dev, uint64_t dst,
+				     uint64_t src, uint32_t channel,
+				     uint32_t size)
+{
+	uint32_t dst_low = (uint32_t)dst;
+	uint32_t src_low = (uint32_t)src;
+	uint32_t transfer_size;
+	int ret;
+
+	/*
+	 * Pl330 has only 32bit registers and supports 4GB memory.
+	 * 4GB memory window can be configured using DMAC_AXI_ADD_CONTROL
+	 * registers.
+	 * Divide the DMA operation in 2 parts, 1st DMA from given address
+	 * to boundary (0xffffffff) and 2nd DMA on remaining size.
+	 */
+
+	if (size > (PL330_MAX_OFFSET - dst_low)) {
+		transfer_size = PL330_MAX_OFFSET - dst_low;
+		ret = dma_pl330_submit(dev, dst, src, channel,
+				       transfer_size);
+		if (ret < 0) {
+			return ret;
+		}
+
+		dst += transfer_size;
+		src += transfer_size;
+		size -= transfer_size;
+		return dma_pl330_submit(dev, dst, src, channel, size);
+	}
+
+	if (size > (PL330_MAX_OFFSET - src_low)) {
+		transfer_size = PL330_MAX_OFFSET - src_low;
+		ret = dma_pl330_submit(dev, dst, src, channel, transfer_size);
+		if (ret < 0) {
+			return ret;
+		}
+
+		src += transfer_size;
+		dst += transfer_size;
+		size -= transfer_size;
+		return dma_pl330_submit(dev, dst, src, channel, size);
+	}
+
+	return 0;
+}
+#endif
+
+static int dma_pl330_submit(struct device *dev, uint64_t dst, uint64_t src,
+			    uint32_t channel, uint32_t size)
+{
+	int ret;
+	uint32_t xfer_size;
+
+#if CONFIG_DMA_64BIT
+	/*
+	 * Pl330 has only 32bit registers and supports 4GB memory.
+	 * 4GB memory window can be configured using DMAC_AXI_ADD_CONTROL
+	 * registers. 32bit boundary (0xffffffff) should be check.
+	 * DMA on boundary condition is taken care in below function.
+	 */
+
+	if ((size > (PL330_MAX_OFFSET - (uint32_t)dst)) ||
+	    (size > (PL330_MAX_OFFSET - (uint32_t)src))) {
+		return dma_pl330_handle_boundary(dev, dst, src,
+						 channel, size);
+	}
+#endif
+	while (size) {
+		xfer_size = 0;
+		ret = dma_pl330_xfer(dev, dst, src, size,
+				     channel, &xfer_size);
+		if (ret) {
+			return ret;
+		}
+		if (xfer_size > size) {
+			return -EFAULT;
+		}
+		size -= xfer_size;
+		dst += xfer_size;
+		src += xfer_size;
+	}
+
+	return 0;
+}
+
+static int dma_pl330_configure(struct device *dev, uint32_t channel,
+			       struct dma_config *cfg)
+{
+	struct dma_pl330_dev_data *const dev_data = DEV_DATA(dev);
+	struct dma_pl330_ch_config *channel_cfg;
+
+	if (channel >= MAX_DMA_CHANNELS) {
+		return -EINVAL;
+	}
+
+	channel_cfg = &dev_data->channels[channel];
+	k_mutex_lock(&channel_cfg->ch_mutex, K_FOREVER);
+	if (channel_cfg->channel_active) {
+		k_mutex_unlock(&channel_cfg->ch_mutex);
+		return -EBUSY;
+	}
+	channel_cfg->channel_active = 1;
+	k_mutex_unlock(&channel_cfg->ch_mutex);
+
+	if (cfg->channel_direction != MEMORY_TO_MEMORY) {
+		return -ENOTSUP;
+	}
+
+	channel_cfg->direction = cfg->channel_direction;
+	channel_cfg->dst_addr_adj = cfg->head_block->dest_addr_adj;
+
+	channel_cfg->src_addr = cfg->head_block->source_address;
+	channel_cfg->dst_addr = cfg->head_block->dest_address;
+	channel_cfg->trans_size = cfg->head_block->block_size;
+
+	channel_cfg->dma_callback = cfg->dma_callback;
+	channel_cfg->callback_arg = cfg->callback_arg;
+
+	if (cfg->head_block->source_addr_adj == DMA_ADDR_ADJ_INCREMENT ||
+	    cfg->head_block->source_addr_adj == DMA_ADDR_ADJ_NO_CHANGE) {
+		channel_cfg->src_addr_adj = cfg->head_block->source_addr_adj;
+	} else {
+		return -ENOTSUP;
+	}
+
+	if (cfg->head_block->dest_addr_adj == DMA_ADDR_ADJ_INCREMENT ||
+	    cfg->head_block->dest_addr_adj == DMA_ADDR_ADJ_NO_CHANGE) {
+		channel_cfg->dst_addr_adj = cfg->head_block->dest_addr_adj;
+	} else {
+		return -ENOTSUP;
+	}
+
+	return 0;
+}
+
+static int dma_pl330_transfer_start(struct device *dev, uint32_t channel)
+{
+	struct dma_pl330_dev_data *const dev_data = DEV_DATA(dev);
+	struct dma_pl330_ch_config *channel_cfg;
+	int ret;
+
+	if (channel >= MAX_DMA_CHANNELS) {
+		return -EINVAL;
+	}
+
+	channel_cfg = &dev_data->channels[channel];
+	ret = dma_pl330_submit(dev, channel_cfg->dst_addr,
+			       channel_cfg->src_addr, channel,
+			       channel_cfg->trans_size);
+
+	/* Execute callback */
+	if (channel_cfg->dma_callback) {
+		channel_cfg->dma_callback(channel_cfg->callback_arg,
+					  channel, ret);
+	}
+
+	k_mutex_lock(&channel_cfg->ch_mutex, K_FOREVER);
+	channel_cfg->channel_active = 0;
+	k_mutex_unlock(&channel_cfg->ch_mutex);
+
+	return 0;
+}
+
+static int dma_pl330_transfer_stop(struct device *dev, uint32_t channel)
+{
+	if (channel >= MAX_DMA_CHANNELS) {
+		return -EINVAL;
+	}
+
+	/* Nothing as of now */
+	return 0;
+}
+
+static int dma_pl330_initialize(struct device *dev)
+{
+	const struct dma_pl330_config *const dev_cfg = DEV_CFG(dev);
+	struct dma_pl330_dev_data *const dev_data = DEV_DATA(dev);
+	struct dma_pl330_ch_config *channel_cfg;
+
+	for (int channel = 0; channel < MAX_DMA_CHANNELS; channel++) {
+		channel_cfg = &dev_data->channels[channel];
+		channel_cfg->dma_exe_addr = dev_cfg->mcode_base +
+					(channel * MICROCODE_SIZE_MAX);
+		k_mutex_init(&channel_cfg->ch_mutex);
+	}
+
+	LOG_INF("Device %s initialized", DEV_NAME(dev));
+	return 0;
+}
+
+static const struct dma_driver_api pl330_driver_api = {
+	.config = dma_pl330_configure,
+	.start = dma_pl330_transfer_start,
+	.stop = dma_pl330_transfer_stop,
+};
+
+static struct device DEVICE_NAME_GET(dma_pl330);
+
+static const struct dma_pl330_config pl330_config = {
+	.reg_base = DT_INST_REG_ADDR(0),
+#ifdef CONFIG_DMA_64BIT
+	.control_reg_base = DT_INST_REG_ADDR_BY_NAME(0, control_regs),
+#endif
+	.mcode_base = DT_INST_PROP_BY_IDX(0, microcode, 0),
+};
+
+static struct dma_pl330_dev_data pl330_data;
+
+DEVICE_AND_API_INIT(dma_pl330, CONFIG_DMA_0_NAME,
+		    &dma_pl330_initialize,
+		    &pl330_data, &pl330_config,
+		    POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
+		    &pl330_driver_api);
diff --git a/drivers/dma/dma_pl330.h b/drivers/dma/dma_pl330.h
new file mode 100644
index 0000000..32ba3fa
--- /dev/null
+++ b/drivers/dma/dma_pl330.h
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2020 Broadcom
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef DMA_PL330_H
+#define DMA_PL330_H
+
+#include <drivers/dma.h>
+
+#define DT_DRV_COMPAT arm_dma_pl330
+/*
+ * Max burst length and max burst size for 32bit system with
+ * 128bit bus width for memory to memory data transfer
+ *
+ * Burst length is encoded in following format for pl330
+ * b0000 = 1 data transfer
+ * b0001 = 2 data transfers
+ * b0010 = 3 data transfers
+ * .
+ * .
+ * b1111 = 16 data transfers
+ *
+ * Burst size is encoded in following format for pl330
+ * b000 = 1 byte
+ * b001 = 2 bytes
+ * b010 = 4 bytes
+ * b011 = 8 bytes
+ * b100 = 16 bytes
+ * b101 = 32 bytes
+ * b110 = 64 bytes
+ * b111 = 128 bytes.
+ */
+#define MAX_BURST_LEN		0xf /* 16byte data */
+#define MAX_BURST_SIZE_LOG2	4
+
+/*
+ * PL330 works only on 4GB boundary.
+ * PL330 has 32bit registers for source and destination addresses
+ */
+#define PL330_MAX_OFFSET	0x100000000
+
+/* PL330 supports max 16MB dma based on AXI bus size */
+#define PL330_MAX_DMA_SIZE	0x1000000
+
+/* Maximum possible values for PL330 ucode loop counters */
+#define PL330_LOOP_COUNTER0_MAX	0x100
+#define PL330_LOOP_COUNTER1_MAX	0x100
+
+#define MAX_DMA_CHANNELS	DT_INST_PROP(0, dma_channels)
+
+#define DMAC_PL330_CS0		0x100
+#define DMAC_PL330_DBGSTATUS	0xd00
+#define DMAC_PL330_DBGCMD	0xd04
+#define DMAC_PL330_DBGINST0	0xd08
+#define DMAC_PL330_DBGINST1	0xd0c
+
+#define DMA_TIMEOUT_US		10
+#define CH_STATUS_MASK		0xf
+#define DATA_MASK		0xf
+
+#define DMA_INTSR1_SHIFT	24
+#define DMA_INTSR0_SHIFT	16
+#define DMA_INTSR0		0xa0
+#define DMA_SECURE_SHIFT	17
+#define DMA_CH_SHIFT		8
+
+#define CONTROL_OFFSET		0x4
+#define HIGHER_32_ADDR_MASK	0x0f
+#define DST_ADDR_SHIFT		0x4
+
+#define MICROCODE_SIZE_MAX	0x400
+#define TOTAL_MICROCODE_SIZE	(MAX_DMA_CHANNELS * MICROCODE_SIZE_MAX)
+#define GET_MAX_DMA_SIZE(byte_width, burst_len) \
+		(PL330_LOOP_COUNTER0_MAX * PL330_LOOP_COUNTER1_MAX * \
+		(byte_width) * ((burst_len) + 1))
+
+#define CC_SRCINC_SHIFT		0
+#define CC_DSTINC_SHIFT		14
+#define CC_SRCPRI_SHIFT		8
+#define CC_DSTPRI_SHIFT		22
+#define CC_DSTNS_SHIFT		23
+#define CC_SRCBRSTLEN_SHIFT	4
+#define CC_DSTBRSTLEN_SHIFT	18
+#define CC_SRCBRSTSIZE_SHIFT	1
+#define CC_DSTBRSTSIZE_SHIFT	15
+#define CC_SRCCCTRL_SHIFT	11
+#define CC_SRCCCTRL_MASK	0x7
+#define CC_DSTCCTRL_SHIFT	25
+#define CC_DRCCCTRL_MASK	0x7
+#define CC_SWAP_SHIFT		28
+#define SRC_PRI_NONSEC_VALUE	0x2
+#define SRC_PRI_SEC_VALUE	0x0
+
+#define OP_DMA_MOV		0xbc
+#define OP_DMA_LOOP_COUNT1	0x22
+#define OP_DMA_LOOP		0x20
+#define OP_DMA_LD		0x4
+#define OP_DMA_ST		0x8
+#define OP_DMA_SEV		0x34
+#define OP_DMA_END		0x00
+#define OP_DMA_LP_BK_JMP1	0x38
+#define OP_DMA_LP_BK_JMP2	0x3c
+#define SZ_CMD_DMAMOV		0x6
+
+enum dmamov_type {
+	/* Source Address Register */
+	SAR = 0,
+	/* Channel Control Register */
+	CCR,
+	/* Destination Address Register */
+	DAR,
+};
+
+/* Channel specific private data */
+struct dma_pl330_ch_internal {
+	uint64_t src_addr;
+	uint64_t dst_addr;
+	int src_burst_sz;
+	uint32_t src_burst_len;
+	int dst_burst_sz;
+	uint32_t dst_burst_len;
+	uint32_t trans_size;
+	uint32_t dst_id;
+	uint32_t src_id;
+	uint32_t perip_type;
+	uint32_t breq_only;
+	uint32_t src_cache_ctrl;
+	uint32_t dst_cache_ctrl;
+	uint32_t dst_inc;
+	uint32_t src_inc;
+	int nonsec_mode;
+};
+
+typedef void (*dma_xfer_callback)(void *callback_arg, uint32_t channel,
+				  int error_code);
+
+struct dma_pl330_ch_config {
+	/* Channel configuration details */
+	uint64_t src_addr;
+	enum dma_addr_adj src_addr_adj;
+	uint64_t dst_addr;
+	enum dma_addr_adj dst_addr_adj;
+	enum dma_channel_direction direction;
+	uint32_t trans_size;
+	void *callback_arg;
+	dma_xfer_callback dma_callback;
+	uint32_t dma_exe_addr;
+	struct k_mutex ch_mutex;
+	int channel_active;
+
+	/* Channel specific private data */
+	struct dma_pl330_ch_internal internal;
+};
+
+struct dma_pl330_config {
+	uint32_t mcode_base;
+	uint32_t reg_base;
+#ifdef CONFIG_DMA_64BIT
+	uint32_t control_reg_base;
+#endif
+};
+
+struct dma_pl330_dev_data {
+	struct dma_pl330_ch_config channels[MAX_DMA_CHANNELS];
+};
+
+#endif