// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013 - 2015 Linaro Ltd.
* Copyright (c) 2013 Hisilicon Limited.
*/
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/dmapool.h>
#include <linux/dmaengine.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/of_device.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/of_dma.h>
#include "virt-dma.h"
#define DRIVER_NAME "k3-dma"
#define DMA_MAX_SIZE 0x1ffc
#define DMA_CYCLIC_MAX_PERIOD 0x1000
#define LLI_BLOCK_SIZE (4 * PAGE_SIZE)
#define INT_STAT 0x00
#define INT_TC1 0x04
#define INT_TC2 0x08
#define INT_ERR1 0x0c
#define INT_ERR2 0x10
#define INT_TC1_MASK 0x18
#define INT_TC2_MASK 0x1c
#define INT_ERR1_MASK 0x20
#define INT_ERR2_MASK 0x24
#define INT_TC1_RAW 0x600
#define INT_TC2_RAW 0x608
#define INT_ERR1_RAW 0x610
#define INT_ERR2_RAW 0x618
#define CH_PRI 0x688
#define CH_STAT 0x690
#define CX_CUR_CNT 0x704
#define CX_LLI 0x800
#define CX_CNT1 0x80c
#define CX_CNT0 0x810
#define CX_SRC 0x814
#define CX_DST 0x818
#define CX_CFG 0x81c
#define CX_LLI_CHAIN_EN 0x2
#define CX_CFG_EN 0x1
#define CX_CFG_NODEIRQ BIT(1)
#define CX_CFG_MEM2PER (0x1 << 2)
#define CX_CFG_PER2MEM (0x2 << 2)
#define CX_CFG_SRCINCR (0x1 << 31)
#define CX_CFG_DSTINCR (0x1 << 30)
struct k3_desc_hw {
u32 lli;
u32 reserved[3];
u32 count;
u32 saddr;
u32 daddr;
u32 config;
} __aligned(32);
struct k3_dma_desc_sw {
struct virt_dma_desc vd;
dma_addr_t desc_hw_lli;
size_t desc_num;
size_t size;
struct k3_desc_hw *desc_hw;
};
struct k3_dma_phy;
struct k3_dma_chan {
u32 ccfg;
struct virt_dma_chan vc;
struct k3_dma_phy *phy;
struct list_head node;
dma_addr_t dev_addr;
enum dma_status status;
bool cyclic;
struct dma_slave_config slave_config;
};
struct k3_dma_phy {
u32 idx;
void __iomem *base;
struct k3_dma_chan *vchan;
struct k3_dma_desc_sw *ds_run;
struct k3_dma_desc_sw *ds_done;
};
struct k3_dma_dev {
struct dma_device slave;
void __iomem *base;
struct tasklet_struct task;
spinlock_t lock;
struct list_head chan_pending;
struct k3_dma_phy *phy;
struct k3_dma_chan *chans;
struct clk *clk;
struct dma_pool *pool;
u32 dma_channels;
u32 dma_requests;
u32 dma_channel_mask;
unsigned int irq;
};
#define K3_FLAG_NOCLK BIT(1)
struct k3dma_soc_data {
unsigned long flags;
};
#define to_k3_dma(dmadev) container_of(dmadev, struct k3_dma_dev, slave)
static int k3_dma_config_write(struct dma_chan *chan,
enum dma_transfer_direction dir,
struct dma_slave_config *cfg);
static struct k3_dma_chan *to_k3_chan(struct dma_chan *chan)
{
return container_of(chan, struct k3_dma_chan, vc.chan);
}
static void k3_dma_pause_dma(struct k3_dma_phy *phy, bool on)
{
u32 val = 0;
if (on) {
val = readl_relaxed(phy->base + CX_CFG);
val |= CX_CFG_EN;
writel_relaxed(val, phy->base + CX_CFG);
} else {
val = readl_relaxed(phy->base + CX_CFG);
val &= ~CX_CFG_EN;
writel_relaxed(val, phy->base + CX_CFG);
}
}
static void k3_dma_terminate_chan(struct k3_dma_phy *phy, struct k3_dma_dev *d)
{
u32 val = 0;
k3_dma_pause_dma(phy, false);
val = 0x1 << phy->idx;
writel_relaxed(val, d->base + INT_TC1_RAW);
writel_relaxed(val, d->base + INT_TC2_RAW);
writel_relaxed(val, d->base + INT_ERR1_RAW);
writel_relaxed(val, d->base + INT_ERR2_RAW);
}
static void k3_dma_set_desc(struct k3_dma_phy *phy, struct k3_desc_hw *hw)
{
writel_relaxed(hw->lli, phy->base + CX_LLI);
writel_relaxed(hw->count, phy->base + CX_CNT0);
writel_relaxed(hw->saddr, phy->base + CX_SRC);
writel_relaxed(hw->daddr, phy->base + CX_DST);
writel_relaxed(hw->config, phy->base + CX_CFG);
}
static u32 k3_dma_get_curr_cnt(struct k3_dma_dev *d, struct k3_dma_phy *phy)
{
u32 cnt = 0;
cnt = readl_relaxed(d->base + CX_CUR_CNT + phy->idx * 0x10);
cnt &= 0xffff;
return cnt;
}
static u32 k3_dma_get_curr_lli(struct k3_dma_phy *phy)
{
return readl_relaxed(phy->base + CX_LLI);
}
static u32 k3_dma_get_chan_stat(struct k3_dma_dev *d)
{
return readl_relaxed(d->base + CH_STAT);
}
static void k3_dma_enable_dma(struct k3_dma_dev *d, bool on)
{
if (on) {
/* set same priority */
writel_relaxed(0x0, d->base + CH_PRI);
/* unmask irq */
writel_relaxed(0xffff, d->base + INT_TC1_MASK);
writel_relaxed(0xffff, d->base + INT_TC2_MASK);
writel_relaxed(0xffff, d->base + INT_ERR1_MASK);
writel_relaxed(0xffff, d->base + INT_ERR2_MASK);
} else {
/* mask irq */
writel_relaxed(0x0, d->base + INT_TC1_MASK);
writel_relaxed(0x0, d->base + IN