zephyr/drivers/dma/dma_dw.c
Martí Bolívar 7e0eed9235 devicetree: allow access to all nodes
Usually, we want to operate only on "available" device
nodes ("available" means "status is okay and a matching binding is
found"), but that's not true in all cases.

Sometimes we want to operate on special nodes without matching
bindings, such as those describing memory.

To handle the distinction, change various additional devicetree APIs
making it clear that they operate only on available device nodes,
adjusting gen_defines and devicetree.h implementation details
accordingly:

- emit macros for all existing nodes in gen_defines.py, regardless
  of status or matching binding
- rename DT_NUM_INST to DT_NUM_INST_STATUS_OKAY
- rename DT_NODE_HAS_COMPAT to DT_NODE_HAS_COMPAT_STATUS_OKAY
- rename DT_INST_FOREACH to DT_INST_FOREACH_STATUS_OKAY
- rename DT_ANY_INST_ON_BUS to DT_ANY_INST_ON_BUS_STATUS_OKAY
- rewrite DT_HAS_NODE_STATUS_OKAY in terms of a new DT_NODE_HAS_STATUS
- resurrect DT_HAS_NODE in the form of DT_NODE_EXISTS
- remove DT_COMPAT_ON_BUS as a public API
- use the new default_prop_types edtlib parameter

Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
2020-05-08 19:37:18 -05:00

429 lines
11 KiB
C

/*
* Copyright (c) 2018 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT snps_designware_dma
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <kernel.h>
#include <device.h>
#include <init.h>
#include <drivers/dma.h>
#include <soc.h>
#include "dma_dw.h"
#define LOG_LEVEL CONFIG_DMA_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(dma_dw);
#define BYTE (1)
#define WORD (2)
#define DWORD (4)
/* CFG_LO */
#define DW_CFG_CLASS(x) (x << 29)
/* CFG_HI */
#define DW_CFGH_SRC_PER(x) ((x & 0xf) | ((x & 0x30) << 24))
#define DW_CFGH_DST_PER(x) (((x & 0xf) << 4) | ((x & 0x30) << 26))
/* default initial setup register values */
#define DW_CFG_LOW_DEF 0x0
#define DEV_NAME(dev) ((dev)->name)
#define DEV_DATA(dev) ((struct dw_dma_dev_data *const)(dev)->driver_data)
#define DEV_CFG(dev) \
((const struct dw_dma_dev_cfg *const)(dev)->config_info)
/* number of tries to wait for reset */
#define DW_DMA_CFG_TRIES 10000
#define INT_MASK_ALL 0xFF00
static ALWAYS_INLINE void dw_write(u32_t dma_base, u32_t reg, u32_t value)
{
*((volatile u32_t*)(dma_base + reg)) = value;
}
static ALWAYS_INLINE u32_t dw_read(u32_t dma_base, u32_t reg)
{
return *((volatile u32_t*)(dma_base + reg));
}
static void dw_dma_isr(void *arg)
{
struct device *dev = (struct device *)arg;
const struct dw_dma_dev_cfg *const dev_cfg = DEV_CFG(dev);
struct dw_dma_dev_data *const dev_data = DEV_DATA(dev);
struct dma_chan_data *chan_data;
u32_t status_tfr = 0U;
u32_t status_block = 0U;
u32_t status_err = 0U;
u32_t status_intr;
u32_t channel;
status_intr = dw_read(dev_cfg->base, DW_INTR_STATUS);
if (!status_intr) {
LOG_ERR("status_intr = %d", status_intr);
}
/* get the source of our IRQ. */
status_block = dw_read(dev_cfg->base, DW_STATUS_BLOCK);
status_tfr = dw_read(dev_cfg->base, DW_STATUS_TFR);
/* TODO: handle errors, just clear them atm */
status_err = dw_read(dev_cfg->base, DW_STATUS_ERR);
if (status_err) {
LOG_ERR("status_err = %d\n", status_err);
dw_write(dev_cfg->base, DW_CLEAR_ERR, status_err);
}
/* clear interrupts */
dw_write(dev_cfg->base, DW_CLEAR_BLOCK, status_block);
dw_write(dev_cfg->base, DW_CLEAR_TFR, status_tfr);
/* Dispatch callbacks for channels depending upon the bit set */
while (status_block) {
channel = find_lsb_set(status_block) - 1;
status_block &= ~(1 << channel);
chan_data = &dev_data->chan[channel];
if (chan_data->dma_blkcallback) {
/* Ensure the linked list (chan_data->lli) is
* freed in the user callback function once
* all the blocks are transferred.
*/
chan_data->dma_blkcallback(chan_data->blkcallback_arg,
channel, 0);
}
}
while (status_tfr) {
channel = find_lsb_set(status_tfr) - 1;
status_tfr &= ~(1 << channel);
chan_data = &dev_data->chan[channel];
if (chan_data->dma_tfrcallback) {
chan_data->dma_tfrcallback(chan_data->tfrcallback_arg,
channel, 0);
}
}
}
static int dw_dma_config(struct device *dev, u32_t channel,
struct dma_config *cfg)
{
struct dw_dma_dev_data *const dev_data = DEV_DATA(dev);
const struct dw_dma_dev_cfg *const dev_cfg = DEV_CFG(dev);
struct dma_chan_data *chan_data;
struct dma_block_config *cfg_blocks;
u32_t m_size;
u32_t tr_width;
u32_t ctrl_lo;
if (channel >= DW_MAX_CHAN) {
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 != BYTE && cfg->source_data_size != WORD &&
cfg->source_data_size != DWORD) {
LOG_ERR("Invalid 'source_data_size' value");
return -EINVAL;
}
cfg_blocks = cfg->head_block;
if ((cfg_blocks->next_block) || (cfg->block_count > 1)) {
/*
* return error since the application may have allocated
* memory for the buffers that may be lost when the DMA
* driver discards the buffers provided in the linked blocks
*/
LOG_ERR("block_count > 1 not supported");
return -EINVAL;
}
chan_data = &dev_data->chan[channel];
/* default channel config */
chan_data->direction = cfg->channel_direction;
/* data_size = (2 ^ tr_width) */
tr_width = find_msb_set(cfg->source_data_size) - 1;
LOG_DBG("Ch%u: tr_width=%d", channel, tr_width);
/* burst_size = (2 ^ msize) */
m_size = find_msb_set(cfg->source_burst_length) - 1;
LOG_DBG("Ch%u: m_size=%d", channel, m_size);
ctrl_lo = DW_CTLL_SRC_WIDTH(tr_width) | DW_CTLL_DST_WIDTH(tr_width);
ctrl_lo |= DW_CTLL_SRC_MSIZE(m_size) | DW_CTLL_DST_MSIZE(m_size);
/* enable interrupt */
ctrl_lo |= DW_CTLL_INT_EN;
switch (cfg->channel_direction) {
case MEMORY_TO_MEMORY:
ctrl_lo |= DW_CTLL_FC_M2M;
ctrl_lo |= DW_CTLL_SRC_INC | DW_CTLL_DST_INC;
break;
case MEMORY_TO_PERIPHERAL:
ctrl_lo |= DW_CTLL_FC_M2P;
ctrl_lo |= DW_CTLL_SRC_INC | DW_CTLL_DST_FIX;
/* Assign a hardware handshaking interface (0-15) to the
* destination of channel
*/
dw_write(dev_cfg->base, DW_CFG_HIGH(channel),
DW_CFGH_DST_PER(cfg->dma_slot));
break;
case PERIPHERAL_TO_MEMORY:
ctrl_lo |= DW_CTLL_FC_P2M;
ctrl_lo |= DW_CTLL_SRC_FIX | DW_CTLL_DST_INC;
/* Assign a hardware handshaking interface (0-15) to the
* source of channel
*/
dw_write(dev_cfg->base, DW_CFG_HIGH(channel),
DW_CFGH_SRC_PER(cfg->dma_slot));
break;
default:
LOG_ERR("channel_direction %d is not supported",
cfg->channel_direction);
return -EINVAL;
}
/* channel needs started from scratch, so write SARn, DARn */
dw_write(dev_cfg->base, DW_SAR(channel), cfg_blocks->source_address);
dw_write(dev_cfg->base, DW_DAR(channel), cfg_blocks->dest_address);
/* Configure a callback appropriately depending on whether the
* interrupt is requested at the end of transaction completion or
* at the end of each block.
*/
if (cfg->complete_callback_en) {
chan_data->dma_blkcallback = cfg->dma_callback;
chan_data->blkcallback_arg = cfg->callback_arg;
dw_write(dev_cfg->base, DW_MASK_BLOCK, INT_UNMASK(channel));
} else {
chan_data->dma_tfrcallback = cfg->dma_callback;
chan_data->tfrcallback_arg = cfg->callback_arg;
dw_write(dev_cfg->base, DW_MASK_TFR, INT_UNMASK(channel));
}
dw_write(dev_cfg->base, DW_MASK_ERR, INT_UNMASK(channel));
/* write interrupt clear registers for the channel
* ClearTfr, ClearBlock, ClearSrcTran, ClearDstTran, ClearErr
*/
dw_write(dev_cfg->base, DW_CLEAR_TFR, 0x1 << channel);
dw_write(dev_cfg->base, DW_CLEAR_BLOCK, 0x1 << channel);
dw_write(dev_cfg->base, DW_CLEAR_SRC_TRAN, 0x1 << channel);
dw_write(dev_cfg->base, DW_CLEAR_DST_TRAN, 0x1 << channel);
dw_write(dev_cfg->base, DW_CLEAR_ERR, 0x1 << channel);
/* single transfer, must set zero */
dw_write(dev_cfg->base, DW_LLP(channel), 0);
/* program CTLn */
dw_write(dev_cfg->base, DW_CTRL_LOW(channel), ctrl_lo);
dw_write(dev_cfg->base, DW_CTRL_HIGH(channel),
DW_CFG_CLASS(dev_data->channel_data->chan[channel].class) |
cfg_blocks->block_size);
/* write channel config */
dw_write(dev_cfg->base, DW_CFG_LOW(channel), DW_CFG_LOW_DEF);
return 0;
}
static int dw_dma_reload(struct device *dev, u32_t channel,
u32_t src, u32_t dst, size_t size)
{
struct dw_dma_dev_data *const dev_data = DEV_DATA(dev);
const struct dw_dma_dev_cfg *const dev_cfg = DEV_CFG(dev);
if (channel >= DW_MAX_CHAN) {
return -EINVAL;
}
dw_write(dev_cfg->base, DW_SAR(channel), src);
dw_write(dev_cfg->base, DW_DAR(channel), dst);
dw_write(dev_cfg->base, DW_CTRL_HIGH(channel),
DW_CFG_CLASS(dev_data->channel_data->chan[channel].class) |
size);
return 0;
}
static int dw_dma_transfer_start(struct device *dev, u32_t channel)
{
const struct dw_dma_dev_cfg *const dev_cfg = DEV_CFG(dev);
if (channel >= DW_MAX_CHAN) {
return -EINVAL;
}
/* enable the channel */
dw_write(dev_cfg->base, DW_DMA_CHAN_EN, CHAN_ENABLE(channel));
return 0;
}
static int dw_dma_transfer_stop(struct device *dev, u32_t channel)
{
const struct dw_dma_dev_cfg *const dev_cfg = DEV_CFG(dev);
if (channel >= DW_MAX_CHAN) {
return -EINVAL;
}
/* disable the channel */
dw_write(dev_cfg->base, DW_DMA_CHAN_EN, CHAN_DISABLE(channel));
return 0;
}
static void dw_dma_setup(struct device *dev)
{
const struct dw_dma_dev_cfg *const dev_cfg = DEV_CFG(dev);
struct dw_dma_dev_data *const dev_data = DEV_DATA(dev);
struct dw_drv_plat_data *dp = dev_data->channel_data;
int i;
/* we cannot config DMAC if DMAC has been already enabled by host */
if (dw_read(dev_cfg->base, DW_DMA_CFG) != 0) {
dw_write(dev_cfg->base, DW_DMA_CFG, 0x0);
}
/* now check that it's 0 */
for (i = DW_DMA_CFG_TRIES; i > 0; i--) {
if (dw_read(dev_cfg->base, DW_DMA_CFG) == 0) {
goto found;
}
}
LOG_ERR("DW_DMA_CFG is non-zero\n");
return;
found:
for (i = 0; i < DW_MAX_CHAN; i++) {
dw_read(dev_cfg->base, DW_DMA_CHAN_EN);
}
/* enable the DMA controller */
dw_write(dev_cfg->base, DW_DMA_CFG, 1);
/* mask all interrupts for all 8 channels */
dw_write(dev_cfg->base, DW_MASK_TFR, INT_MASK_ALL);
dw_write(dev_cfg->base, DW_MASK_BLOCK, INT_MASK_ALL);
dw_write(dev_cfg->base, DW_MASK_SRC_TRAN, INT_MASK_ALL);
dw_write(dev_cfg->base, DW_MASK_DST_TRAN, INT_MASK_ALL);
dw_write(dev_cfg->base, DW_MASK_ERR, INT_MASK_ALL);
/* set channel priorities */
for (i = 0; i < DW_MAX_CHAN; i++) {
dw_write(dev_cfg->base, DW_CTRL_HIGH(i),
DW_CFG_CLASS(dp->chan[i].class));
}
}
static int dw_dma_init(struct device *dev)
{
const struct dw_dma_dev_cfg *const dev_cfg = DEV_CFG(dev);
/* Disable all channels and Channel interrupts */
dw_dma_setup(dev);
/* Configure interrupts */
dev_cfg->irq_config();
LOG_INF("Device %s initialized", DEV_NAME(dev));
return 0;
}
static const struct dma_driver_api dw_dma_driver_api = {
.config = dw_dma_config,
.reload = dw_dma_reload,
.start = dw_dma_transfer_start,
.stop = dw_dma_transfer_stop,
};
#define DW_DMAC_INIT(inst) \
\
static struct device DEVICE_NAME_GET(dw_dma##inst); \
\
static struct dw_drv_plat_data dmac##inst = { \
.chan[0] = { \
.class = 6, \
.weight = 0, \
}, \
.chan[1] = { \
.class = 6, \
.weight = 0, \
}, \
.chan[2] = { \
.class = 6, \
.weight = 0, \
}, \
.chan[3] = { \
.class = 6, \
.weight = 0, \
}, \
.chan[4] = { \
.class = 6, \
.weight = 0, \
}, \
.chan[5] = { \
.class = 6, \
.weight = 0, \
}, \
.chan[6] = { \
.class = 6, \
.weight = 0, \
}, \
.chan[7] = { \
.class = 6, \
.weight = 0, \
}, \
}; \
\
static void dw_dma##inst##_irq_config(void); \
\
static const struct dw_dma_dev_cfg dw_dma##inst##_config = { \
.base = DT_INST_REG_ADDR(inst), \
.irq_config = dw_dma##inst##_irq_config \
}; \
\
static struct dw_dma_dev_data dw_dma##inst##_data = { \
.channel_data = &dmac##inst, \
}; \
\
DEVICE_AND_API_INIT(dw_dma##inst, DT_INST_LABEL(inst), \
&dw_dma_init, \
&dw_dma##inst##_data, \
&dw_dma##inst##_config, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
&dw_dma_driver_api); \
\
static void dw_dma##inst##_irq_config(void) \
{ \
IRQ_CONNECT(DT_INST_IRQN(inst), \
DT_INST_IRQ(inst, priority), dw_dma_isr, \
DEVICE_GET(dw_dma##inst), \
DT_INST_IRQ(inst, sense)); \
irq_enable(DT_INST_IRQN(inst)); \
}
DT_INST_FOREACH_STATUS_OKAY(DW_DMAC_INIT)