/*
* Copyright (C) 2013-2014 Allwinner Tech Co., Ltd
* Author: Sugar <shuge@allwinnertech.com>
*
* Copyright (C) 2014 Maxime Ripard
* Maxime Ripard <maxime.ripard@free-electrons.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/dmaengine.h>
#include <linux/dmapool.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of_dma.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/types.h>
#include "virt-dma.h"
/*
* Common registers
*/
#define DMA_IRQ_EN(x) ((x) * 0x04)
#define DMA_IRQ_HALF BIT(0)
#define DMA_IRQ_PKG BIT(1)
#define DMA_IRQ_QUEUE BIT(2)
#define DMA_IRQ_CHAN_NR 8
#define DMA_IRQ_CHAN_WIDTH 4
#define DMA_IRQ_STAT(x) ((x) * 0x04 + 0x10)
#define DMA_STAT 0x30
/*
* sun8i specific registers
*/
#define SUN8I_DMA_GATE 0x20
#define SUN8I_DMA_GATE_ENABLE 0x4
/*
* Channels specific registers
*/
#define DMA_CHAN_ENABLE 0x00
#define DMA_CHAN_ENABLE_START BIT(0)
#define DMA_CHAN_ENABLE_STOP 0
#define DMA_CHAN_PAUSE 0x04
#define DMA_CHAN_PAUSE_PAUSE BIT(1)
#define DMA_CHAN_PAUSE_RESUME 0
#define DMA_CHAN_LLI_ADDR 0x08
#define DMA_CHAN_CUR_CFG 0x0c
#define DMA_CHAN_CFG_SRC_DRQ(x) ((x) & 0x1f)
#define DMA_CHAN_CFG_SRC_IO_MODE BIT(5)
#define DMA_CHAN_CFG_SRC_LINEAR_MODE (0 << 5)
#define DMA_CHAN_CFG_SRC_BURST(x) (((x) & 0x3) << 7)
#define DMA_CHAN_CFG_SRC_WIDTH(x) (((x) & 0x3) << 9)
#define DMA_CHAN_CFG_DST_DRQ(x) (DMA_CHAN_CFG_SRC_DRQ(x) << 16)
#define DMA_CHAN_CFG_DST_IO_MODE (DMA_CHAN_CFG_SRC_IO_MODE << 16)
#define DMA_CHAN_CFG_DST_LINEAR_MODE (DMA_CHAN_CFG_SRC_LINEAR_MODE << 16)
#define DMA_CHAN_CFG_DST_BURST(x) (DMA_CHAN_CFG_SRC_BURST(x) << 16)
#define DMA_CHAN_CFG_DST_WIDTH(x) (DMA_CHAN_CFG_SRC_WIDTH(x) << 16)
#define DMA_CHAN_CUR_SRC 0x10
#define DMA_CHAN_CUR_DST 0x14
#define DMA_CHAN_CUR_CNT 0x18
#define DMA_CHAN_CUR_PARA 0x1c
/*
* Various hardware related defines
*/
#define LLI_LAST_ITEM 0xfffff800
#define NORMAL_WAIT 8
#define DRQ_SDRAM 1
/*
* Hardware channels / ports representation
*
* The hardware is used in several SoCs, with differing numbers
* of channels and endpoints. This structure ties those numbers
* to a certain compatible string.
*/
struct sun6i_dma_config {
u32 nr_max_channels;
u32 nr_max_requests;
u32 nr_max_vchans;
};
/*
* Hardware representation of the LLI
*
* The hardware will be fed the physical address of this structure,
* and read its content in order to start the transfer.
*/
struct sun6i_dma_lli {
u32 cfg;
u32 src;
u32 dst;
u32 len;
u32 para;
u32 p_lli_next;
/*
* This field is not used by the DMA controller, but will be
* used by the CPU to go through the list (mostly for dumping
* or freeing it).
*/
struct sun6i_dma_lli *v_lli_next;
};
struct sun6i_desc {
struct virt_dma_desc vd;
dma_addr_t p_lli;
struct sun6i_dma_lli *v_lli;
};
struct sun6i_pchan {
u32 idx;
void __iomem *base;
struct sun6i_vchan *vchan;
struct sun6i_desc *desc;
struct sun6i_desc *done;
};
struct sun6i_vchan {
struct virt_dma_chan vc;
struct list_head node;
struct dma_slave_config cfg;
struct sun6i_pchan *phy;
u8 port;
};
struct sun6i_dma_dev {
struct dma_device slave;
void __iomem *base;
struct clk *clk;
int irq;
spinlock_t lock;
struct reset_control *rstc;
struct tasklet_struct task;
atomic_t tasklet_shutdown;
struct list_head pending;
struct dma_pool *pool;
struct sun6i_pchan *pchans;
struct sun6i_vchan *vchans;
const struct sun6i_dma_config *cfg;
};
static struct device *chan2dev(struct dma_chan *chan)
{
return &chan->dev->device;
}
static inline struct sun6i_dma_dev *to_sun6i_dma_dev(struct dma_device *d)
{
return container_of(d, struct sun6i_dma_dev, slave);
}
static inline struct sun6i_vchan *to_sun6i_vchan(struct dma_chan *chan)
{
return container_of(chan, struct sun6i_vchan, vc.chan);
}
static inline struct sun6i_desc *
to_sun6i_desc(struct dma_async_tx_descriptor *tx)
{
return container_of(tx, struct sun6i_desc, vd.tx);
}
static inline void sun6i_dma_dump_com_regs(struct sun6i_dma_dev *sdev)
{
dev_dbg(sdev->slave.dev, "Common register:\n"
"\tmask0(%04x): 0x%08x\n"
"\tmask1(%04x): 0x%08x\n"
"\tpend0(%04x): 0x%08x\n"
"\tpend1(%04x): 0x%08x\n"
"\tstats(%04x): 0x%08x\n",
DMA_IRQ_EN(0), readl(sdev->base + DMA_IRQ_EN(0)),
DMA_IRQ_EN(1), readl(sdev->base