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>
This commit is contained in:
parent
1f3b8c0267
commit
f4a141639d
|
@ -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
|
||||
|
|
|
@ -12,3 +12,4 @@ zephyr_library_sources_ifdef(CONFIG_DMA_NIOS2_MSGDMA dma_nios2_msgdma.c)
|
|||
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)
|
||||
|
|
|
@ -56,4 +56,6 @@ source "drivers/dma/Kconfig.sam0"
|
|||
|
||||
source "drivers/dma/Kconfig.mcux_edma"
|
||||
|
||||
source "drivers/dma/Kconfig.dma_pl330"
|
||||
|
||||
endif # DMA
|
||||
|
|
8
drivers/dma/Kconfig.dma_pl330
Normal file
8
drivers/dma/Kconfig.dma_pl330
Normal file
|
@ -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.
|
599
drivers/dma/dma_pl330.c
Normal file
599
drivers/dma/dma_pl330.c
Normal file
|
@ -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);
|
169
drivers/dma/dma_pl330.h
Normal file
169
drivers/dma/dma_pl330.h
Normal file
|
@ -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
|
Loading…
Reference in a new issue