970 lines
27 KiB
C
970 lines
27 KiB
C
/*
|
|
* Copyright (c) 2023 Renesas Electronics Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/dma.h>
|
|
#include <zephyr/irq.h>
|
|
#include <DA1469xAB.h>
|
|
#include <da1469x_config.h>
|
|
#include <system_DA1469x.h>
|
|
#include <da1469x_otp.h>
|
|
#include <zephyr/drivers/dma/dma_smartbond.h>
|
|
#include <zephyr/logging/log.h>
|
|
|
|
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_dis) {
|
|
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)
|