summaryrefslogtreecommitdiff
path: root/drivers/dma/sprd_dma.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/dma/sprd_dma.c')
-rw-r--r--drivers/dma/sprd_dma.c1676
1 files changed, 1676 insertions, 0 deletions
diff --git a/drivers/dma/sprd_dma.c b/drivers/dma/sprd_dma.c
new file mode 100644
index 00000000..255a26a6
--- /dev/null
+++ b/drivers/dma/sprd_dma.c
@@ -0,0 +1,1676 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/semaphore.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/of_dma.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include "dmaengine.h"
+
+#include <soc/sprd/sci_glb_regs.h>
+#include <soc/sprd/sci.h>
+#include <soc/sprd/hardware.h>
+#include "sprd_dma.h"
+
+//#define DMA_DEBUG
+#ifdef DMA_DEBUG
+#define pr_dma(fmt, ...) printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__)
+#else
+#define pr_dma(fmt, ...)
+#endif
+
+#define STANDARD_DMA_NUM (24)
+#define MPC_DMA_DESCRIPTORS (64)
+#define DMA_CHN_OFFSET (0x40)
+#define DMA_MEMCPY_MIN_SIZE (64)
+#define DMA_CFG_COUNT (MPC_DMA_DESCRIPTORS)
+#define DMA_CHx_OFFSET (0x40)
+#define SPRD_DMA_REQ_CID(base,uid) ((unsigned long)base + 0x2000 + 0x4 * ((uid) -1))
+
+/* dma channel register definition */
+struct sprd_dma_chn_reg {
+ u32 pause;
+ u32 req;
+ u32 cfg;
+ u32 intc;
+ u32 src_addr;
+ u32 des_addr;
+ u32 frg_len;
+ u32 blk_len;
+ /* only full chn have following regs */
+ u32 trsc_len;
+ u32 trsf_step;
+ u32 wrap_ptr;
+ u32 wrap_to;
+ u32 llist_ptr;
+ u32 frg_step;
+ u32 src_blk_step;
+ u32 des_blk_step;
+};
+
+/* dma request description */
+struct sprd_dma_desc {
+ struct dma_async_tx_descriptor desc;
+ struct sprd_dma_chn_reg *dma_chn_reg;
+ dma_addr_t dma_chn_reg_paddr;
+ struct list_head node;
+ struct list_head next_node;
+ int done;
+ int cycle;
+};
+
+/* dma channel description */
+struct sprd_dma_chn {
+ struct dma_chan chan;
+ struct list_head free;
+ struct list_head prepared;
+ struct list_head queued;
+ struct list_head active;
+ struct list_head completed;
+ spinlock_t chn_lock;
+ int chan_num;
+ u32 dev_id;
+ enum dma_chn_status chan_status;
+ void __iomem *dma_chn_base;
+ void __iomem *dma_desc;
+ dma_addr_t dma_desc_paddr;
+ enum dma_chn_type chn_type;
+ enum request_mode re_mode;
+ int irq_handle_enable;
+};
+
+/* dma device */
+struct sprd_dma_dev {
+ struct dma_device dma_dev;
+ spinlock_t dma_lock;
+ void __iomem *dma_glb_base;
+ void __iomem *aon_dma_glb_base;
+ int irq;
+ int aon_irq;
+ struct tasklet_struct tasklet;
+ struct kmem_cache *dma_desc_node_cachep;
+ u32 ap_chn_cnt;
+ u32 aon_chn_cnt;
+ u32 chn_std_cnt;
+ struct sprd_dma_chn channels[0];
+};
+
+struct dma_cfg_group_t {
+ struct semaphore cfg_sema;
+ int dma_cfg_cnt;
+ struct sprd_dma_cfg dma_cfg[DMA_CFG_COUNT];
+};
+
+struct dma_cfg_group_t dma_cfg_group;
+
+static struct of_dma_filter_info sprd_dma_info = {
+ .filter_fn = sprd_dma_filter_fn,
+};
+
+static inline struct sprd_dma_chn *to_sprd_dma_chan(struct dma_chan *c)
+{
+ return container_of(c, struct sprd_dma_chn, chan);
+}
+
+static inline struct sprd_dma_dev *to_sprd_dma_dev(struct dma_chan *c)
+{
+ struct sprd_dma_chn *mchan = to_sprd_dma_chan(c);
+ return container_of(mchan, struct sprd_dma_dev, channels[c->chan_id]);
+}
+
+static inline struct sprd_dma_desc *to_sprd_dma_desc(struct dma_async_tx_descriptor *tx)
+{
+ return container_of(tx, struct sprd_dma_desc, desc);
+}
+
+/* dma debug function */
+static int __dma_cfg_check_register(void __iomem * dma_reg_addr)
+{
+ volatile struct sprd_dma_chn_reg *dma_reg = (struct sprd_dma_chn_reg *)dma_reg_addr;
+ volatile struct sprd_dma_glb_reg *aon_dma_glb_reg;
+ struct device_node *dma_node;
+ struct platform_device *dma_dev;
+ struct sprd_dma_dev *sdev;
+
+ dma_node = of_find_compatible_node(NULL, NULL, "sprd,sharkl64-dma");
+ if (!dma_node) {
+ pr_warn("Can't get the dmac node!\n");
+ return -ENODEV;
+ } else {
+ dma_dev = of_find_device_by_node(dma_node);
+ if (!dma_dev) {
+ pr_warn("Can't get the dma_dev!\n");
+ return -ENODEV;
+ }
+ }
+
+ sdev = platform_get_drvdata(dma_dev);
+ if(!sdev){
+ pr_warn("Can't get the sdev!\n");
+ return -ENODEV;
+ }
+ aon_dma_glb_reg = sdev->aon_dma_glb_base;
+
+ pr_dma("------------------------------------------------------------------------------------>>>\n");
+ pr_dma("DMA register:\n pause=0x%x,\n req=0x%x,\n cfg=0x%x,\n int=0x%x,\n src_addr=0x%x,\n"
+ "des_addr=0x%x,\n frg_len=0x%x,\n blk_len=0x%x,\n trsc_len=0x%x,\n trsf_step=0x%x,\n"
+ "wrap_ptr=0x%x,\n wrap_to=0x%x,\n llist_ptr=0x%x,\n frg_step=0x%x,\n src_blk_step=0x%x,\n"
+ "des_blk_step=0x%x,\n",dma_reg->pause,dma_reg->req,dma_reg->cfg,dma_reg->intc,
+ dma_reg->src_addr,dma_reg->des_addr,dma_reg->frg_len,dma_reg->blk_len,dma_reg->trsc_len,
+ dma_reg->trsf_step,dma_reg->wrap_ptr,dma_reg->wrap_to,dma_reg->llist_ptr,dma_reg->frg_step,
+ dma_reg->src_blk_step,dma_reg->des_blk_step);
+
+ pr_dma("Global reg:INTC2 aon dma irq raw status=0x%x,dma irq raw status=0x%x!\n",
+ sci_glb_read((unsigned long)(SPRD_INTC2_BASE+0x4), BIT(6)),
+ 0/*sci_glb_read((unsigned long)(SPRD_INTC1_BASE+0x4), BIT(18))*/);
+
+ pr_dma("Global reg:INTC_EB_b15=0x%x,DMA_INT_AP_EN_b0=0x%x,INTC2_DMA_EN_b6=0x%x,APB_INTC2_EB_b21=0x%x!\n",
+ sci_glb_read((unsigned long)REG_AON_APB_APB_EB0, BIT_INTC_EB),
+ sci_glb_read((unsigned long)REG_AON_APB_AON_DMA_INT_EN, BIT_AON_DMA_INT_AP_EN),
+ sci_glb_read((unsigned long)(SPRD_INTC2_BASE+0x8), BIT(6)),
+ sci_glb_read((unsigned long)REG_AP_APB_APB_EB, BIT(21)));
+
+ pr_dma("AON global debug reg:debug sts=0x%x,pause=0x%x,req_sts=0x%x,en_sts=0x%x,arb_sel_sts=0x%x!\n",
+ aon_dma_glb_reg->debug_sts,aon_dma_glb_reg->pause,aon_dma_glb_reg->req_sts,
+ aon_dma_glb_reg->en_sts,aon_dma_glb_reg->arb_sel_sts);
+ pr_dma("<<<------------------------------------------------------------------------------------\n");
+ return 0;
+}
+
+static void __dma_glb_state_check(struct sprd_dma_chn *mchan)
+{
+ struct sprd_dma_dev *sdev = to_sprd_dma_dev(&mchan->chan);
+ volatile struct sprd_dma_glb_reg *ap_dma_glb_reg;
+ ap_dma_glb_reg = sdev->dma_glb_base;
+ pr_warn("AP global debug reg:pause=0x%x,raw_sts=0x%x,msk_sts=0x%x,"
+ "req_sts=0x%x,en_sts=0x%x,debug sts=0x%x,arb_sel_sts=0x%x!\n",
+ ap_dma_glb_reg->pause, ap_dma_glb_reg->int_raw_sts,
+ ap_dma_glb_reg->int_msk_sts, ap_dma_glb_reg->req_sts,
+ ap_dma_glb_reg->en_sts, ap_dma_glb_reg->debug_sts,
+ ap_dma_glb_reg->arb_sel_sts);
+}
+
+static void __inline __ap_dma_enable(void)
+{
+ if (!sci_glb_read((unsigned long)REG_AP_AHB_AHB_EB, BIT_DMA_EB))
+ sci_glb_set((unsigned long)REG_AP_AHB_AHB_EB, BIT_DMA_EB);
+}
+
+static void __inline __ap_dma_disable(void)
+{
+ if (sci_glb_read((unsigned long)REG_AP_AHB_AHB_EB, BIT_DMA_EB))
+ sci_glb_clr((unsigned long)REG_AP_AHB_AHB_EB, BIT_DMA_EB);
+}
+
+static void __inline __ap_dma_softreset(void)
+{
+ sci_glb_set(REG_AP_AHB_AHB_RST, BIT_DMA_SOFT_RST);
+ udelay(1);
+ sci_glb_clr(REG_AP_AHB_AHB_RST, BIT_DMA_SOFT_RST);
+}
+
+#ifdef CONFIG_AON_DMA_SPRD
+static void __inline __aon_dma_enable(void)
+{
+ if (!sci_glb_read((unsigned long)REG_AON_APB_APB_EB1, BIT_AON_DMA_EB))
+ sci_glb_set((unsigned long)REG_AON_APB_APB_EB1, BIT_AON_DMA_EB);
+}
+
+static void __inline __aon_dma_disable(void)
+{
+ if (sci_glb_read((unsigned long)REG_AON_APB_APB_EB1, BIT_AON_DMA_EB))
+ sci_glb_clr((unsigned long)REG_AON_APB_APB_EB1,BIT_AON_DMA_EB);
+}
+
+static void __inline __aon_dma_reset(void)
+{
+ sci_glb_set(REG_AON_APB_APB_RST1, BIT_AON_DMA_SOFT_RST);
+ udelay(1);
+ sci_glb_clr(REG_AON_APB_APB_RST1, BIT_AON_DMA_SOFT_RST);
+}
+
+static void __inline __aon_dma_int_enable(void)
+{
+ if (!sci_glb_read((unsigned long)(SPRD_INTC2_BASE+0x8), BIT(6)))
+ sci_glb_set((unsigned long)(SPRD_INTC2_BASE+0x8), BIT(6));
+
+ if (!sci_glb_read((unsigned long)REG_AON_APB_AON_DMA_INT_EN, BIT_AON_DMA_INT_AP_EN))
+ sci_glb_set((unsigned long)REG_AON_APB_AON_DMA_INT_EN,BIT_AON_DMA_INT_AP_EN);
+}
+
+static void __inline __aon_dma_int_disable(void)
+{
+ if (sci_glb_read((unsigned long)REG_AON_APB_AON_DMA_INT_EN, BIT_AON_DMA_INT_AP_EN))
+ sci_glb_clr((unsigned long)REG_AON_APB_AON_DMA_INT_EN,BIT_AON_DMA_INT_AP_EN);
+}
+#else
+static void __inline __aon_dma_enable(void){ }
+
+static void __inline __aon_dma_disable(void){ }
+
+static void __inline __aon_dma_reset(void){ }
+
+static void __inline __aon_dma_int_enable(void){ }
+
+static void __inline __aon_dma_int_disable(void){ }
+#endif
+
+static void __inline __dma_set_uid(struct sprd_dma_dev *sdev,
+ struct sprd_dma_chn *mchan, u32 dev_id)
+{
+ u32 ap_chn_cnt = sdev->ap_chn_cnt;
+
+ if (DMA_UID_SOFTWARE != dev_id) {
+ if(mchan->chan_num < ap_chn_cnt)
+ writel_relaxed((mchan->chan_num + 1),
+ (void __iomem *)SPRD_DMA_REQ_CID(sdev->dma_glb_base, dev_id));
+ else
+ writel_relaxed((mchan->chan_num - ap_chn_cnt + 1),
+ (void __iomem *)SPRD_DMA_REQ_CID(sdev->aon_dma_glb_base, dev_id));
+ }
+}
+
+static void __inline __dma_unset_uid(struct sprd_dma_dev *sdev,
+ struct sprd_dma_chn *mchan, u32 dev_id)
+{
+ u32 ap_chn_cnt = sdev->ap_chn_cnt;
+
+ if (DMA_UID_SOFTWARE != dev_id) {
+ if(mchan->chan_num < ap_chn_cnt)
+ writel_relaxed(0x0,(void __iomem *)SPRD_DMA_REQ_CID(sdev->dma_glb_base, dev_id));
+ else
+ writel_relaxed(0x0,(void __iomem *)SPRD_DMA_REQ_CID(sdev->aon_dma_glb_base, dev_id));
+ }
+}
+
+static void __inline __dma_int_clr(struct sprd_dma_chn *mchan)
+{
+ volatile struct sprd_dma_chn_reg *dma_reg = (struct sprd_dma_chn_reg *)mchan->dma_chn_base;
+
+ dma_reg->intc |= 0x1f << 24;
+}
+
+static void __inline __dma_int_dis(struct sprd_dma_chn *mchan)
+{
+ volatile struct sprd_dma_chn_reg *dma_reg = (struct sprd_dma_chn_reg *)mchan->dma_chn_base;
+
+ dma_reg->intc |= 0x1f << 24;
+ dma_reg->intc &= ~0x1f;
+}
+
+static void __inline __dma_chn_enable(struct sprd_dma_chn *mchan)
+{
+ volatile struct sprd_dma_chn_reg *dma_reg = (struct sprd_dma_chn_reg *)mchan->dma_chn_base;
+
+ dma_reg->cfg |= 0x1;
+}
+
+static void __inline __dma_soft_request(struct sprd_dma_chn *mchan)
+{
+ volatile struct sprd_dma_chn_reg *dma_reg = (struct sprd_dma_chn_reg *)mchan->dma_chn_base;
+
+ dma_reg->req |= 0x1;
+}
+
+static void __dma_stop_and_disable(struct sprd_dma_chn *mchan)
+{
+ u32 timeout = 0x2000;
+
+ volatile struct sprd_dma_chn_reg *dma_reg = (struct sprd_dma_chn_reg *)mchan->dma_chn_base;
+ struct sprd_dma_dev *sdev = to_sprd_dma_dev(&mchan->chan);
+ u32 ap_chn_cnt = sdev->ap_chn_cnt;
+
+ if (!(dma_reg->cfg & 0x1))
+ return;
+
+ dma_reg->pause |= 0x1;
+
+ /* fixme, need to deal with timeout */
+ while (!(dma_reg->pause & (0x1 << 16))){
+ if(--timeout == 0){
+ __dma_glb_state_check(mchan);
+ if(mchan->chan_num >= ap_chn_cnt)
+ __aon_dma_reset();
+ else
+ __ap_dma_softreset();
+ break;
+ }
+ cpu_relax();
+ }
+
+ dma_reg->cfg &= ~0x1;
+ dma_reg->pause = 0x0;
+}
+
+/* get dma source reg addr */
+static unsigned long __dma_get_src_addr(struct dma_chan *dma_chn)
+{
+ struct sprd_dma_chn *mchan = to_sprd_dma_chan(dma_chn);
+ unsigned long addr = (unsigned long)mchan->dma_chn_base + 0x10;
+
+ return readl_relaxed((void __iomem *)addr);
+}
+
+/* get dma dest reg addr */
+static unsigned long __dma_get_dst_addr(struct dma_chan *dma_chn)
+{
+ struct sprd_dma_chn *mchan = to_sprd_dma_chan(dma_chn);
+ unsigned long addr = (unsigned long)mchan->dma_chn_base + 0x14;
+
+ return readl_relaxed((void __iomem *)addr);
+}
+
+static int __dma_config(struct dma_chan *chan,struct sprd_dma_desc *mdesc,
+ struct sprd_dma_cfg *cfg_list,struct sprd_dma_chn_reg *dma_reg_addr,
+ enum config_type type)
+{
+ volatile struct sprd_dma_chn_reg *dma_reg;
+ struct sprd_dma_chn *mchan = to_sprd_dma_chan(chan);
+ struct sprd_dma_cfg *dma_cfg_tmp = cfg_list;
+ u32 fix_mode = 0, llist_en = 0, wrap_en = 0;
+ u32 list_end = 0, fix_en = 0,irq_mode = 0,wrap_mode = 0;
+ int chn_type;
+
+ /* check dma fix mode */
+ if (dma_cfg_tmp->src_step != 0 && dma_cfg_tmp->des_step != 0) {
+ fix_en = 0x0;
+ } else {
+ if ((dma_cfg_tmp->src_step | dma_cfg_tmp->des_step) == 0) {
+ fix_en = 0x0;
+ } else {
+ fix_en = 0x1;
+ if (dma_cfg_tmp->src_step)
+ fix_mode = 0x1;
+ else
+ fix_mode = 0x0;
+ }
+ }
+
+ /* check dma wrap mode */
+ if (dma_cfg_tmp->wrap_ptr && dma_cfg_tmp->wrap_to) {
+ wrap_en = 0x1;
+ if (dma_cfg_tmp->wrap_to == dma_cfg_tmp->src_addr) {
+ wrap_mode = 0x0;
+ } else {
+ if (dma_cfg_tmp->wrap_to == dma_cfg_tmp->des_addr)
+ wrap_mode = 0x1;
+ else
+ return -EINVAL;
+ }
+ }
+
+ /* linklist configuration */
+ if (dma_cfg_tmp->linklist_ptr) {
+ llist_en = 0x1;
+ if (dma_cfg_tmp->is_end == 1)
+ list_end = 0x1;
+ else
+ list_end = 0;
+ }
+
+ chn_type = mchan->chn_type;
+ irq_mode = dma_cfg_tmp->irq_mode;
+
+ /* check dma mode */
+ if ((chn_type == STANDARD_DMA) &&
+ (irq_mode == TRANS_DONE || irq_mode == LIST_DONE)) {
+ pr_err("Irq type isn't compatible with channel type!");
+ return -EINVAL;
+ }
+
+ if (!IS_ALIGNED(dma_cfg_tmp->src_step, dma_cfg_tmp->datawidth)){
+ pr_err("Source step is not aligned!");
+ return -EINVAL;
+ }
+
+ if (!IS_ALIGNED(dma_cfg_tmp->des_step, dma_cfg_tmp->datawidth)){
+ pr_err("Destination step is not aligned!");
+ return -EINVAL;
+ }
+
+ if(!mchan->dev_id)
+ mchan->dev_id = dma_cfg_tmp->dev_id;
+
+ if(type == CONFIG_DESC)
+ dma_reg = mdesc->dma_chn_reg;
+ else if(type == CONFIG_LINKLIST)
+ dma_reg = dma_reg_addr;
+ else
+ return -EINVAL;
+
+ dma_reg->pause = 0x0;
+ dma_reg->req = 0x0;
+
+ /* set default priority = 1 */
+ dma_reg->cfg = DMA_PRI_1 << CHN_PRIORITY_OFFSET |
+ llist_en << LLIST_EN_OFFSET;
+
+ /* src and des addr */
+ dma_reg->src_addr = dma_cfg_tmp->src_addr;
+ dma_reg->des_addr = dma_cfg_tmp->des_addr;
+
+ /* frag len */
+ dma_reg->frg_len =
+ (dma_cfg_tmp->datawidth << SRC_DATAWIDTH_OFFSET) |
+ (dma_cfg_tmp->datawidth << DES_DATAWIDTH_OFFSET) |
+ (0x0 << SWT_MODE_OFFSET) |
+ (dma_cfg_tmp->req_mode << REQ_MODE_OFFSET) |
+ (wrap_mode << ADDR_WRAP_SEL_OFFSET) |
+ (wrap_en << ADDR_WRAP_EN_OFFSET) |
+ (fix_mode << ADDR_FIX_SEL_OFFSET) |
+ (fix_en << ADDR_FIX_SEL_EN) |
+ (list_end << LLIST_END_OFFSET) |
+ (dma_cfg_tmp->fragmens_len & FRG_LEN_MASK);
+
+ /* blk len */
+ dma_reg->blk_len = dma_cfg_tmp->block_len & BLK_LEN_MASK;
+
+ /* set interrupt type*/
+ if(type == CONFIG_DESC){
+ if(irq_mode == NO_INT)
+ mchan->irq_handle_enable = 0;
+ else
+ mchan->irq_handle_enable = 1;
+
+ dma_reg->intc &= ~0x1f;
+ dma_reg->intc |= 0x1 << 4;
+ switch (irq_mode) {
+ case NO_INT:
+ break;
+ case FRAG_DONE:
+ dma_reg->intc |= 0x1;
+ break;
+ case BLK_DONE:
+ dma_reg->intc |= 0x2;
+ break;
+ case TRANS_DONE:
+ dma_reg->intc |= 0x4;
+ break;
+ case LIST_DONE:
+ dma_reg->intc |= 0x8;
+ break;
+ case CONFIG_ERR:
+ dma_reg->intc |= 0x10;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+ else
+ dma_reg->intc = 0;
+
+ pr_dma("dma_config:cfg=0x%x,frg_len=0x%x,blk_len=0x%x,intc=0x%x!\n",
+ dma_reg->cfg,dma_reg->frg_len,dma_reg->blk_len,dma_reg->intc);
+
+ if(chn_type == STANDARD_DMA)
+ return 0;
+
+ /* full dma config */
+ if (0x0 == dma_cfg_tmp->transcation_len)
+ dma_reg->trsc_len = dma_cfg_tmp->block_len & TRSC_LEN_MASK;
+ else
+ dma_reg->trsc_len = dma_cfg_tmp->transcation_len & TRSC_LEN_MASK;
+
+ dma_reg->trsf_step =
+ (dma_cfg_tmp->des_step & TRSF_STEP_MASK) << DEST_TRSF_STEP_OFFSET |
+ (dma_cfg_tmp->src_step & TRSF_STEP_MASK) << SRC_TRSF_STEP_OFFSET;
+
+ dma_reg->wrap_ptr = dma_cfg_tmp->wrap_ptr;
+ dma_reg->wrap_to = dma_cfg_tmp->wrap_to;
+
+ dma_reg->llist_ptr = dma_cfg_tmp->linklist_ptr;
+
+ dma_reg->frg_step =
+ (dma_cfg_tmp->dst_frag_step & FRAG_STEP_MASK) << DEST_FRAG_STEP_OFFSET |
+ (dma_cfg_tmp->src_frag_step & FRAG_STEP_MASK) << SRC_FRAG_STEP_OFFSET;
+
+ dma_reg->src_blk_step = dma_cfg_tmp->src_blk_step;
+ dma_reg->des_blk_step = dma_cfg_tmp->dst_blk_step;
+
+ pr_dma("dma_config:trsc_len=0x%x,trsf_step=0x%x,llist_ptr=0x%x,frg_step=0x%x!\n",
+ dma_reg->trsc_len,dma_reg->trsf_step,dma_reg->llist_ptr,dma_reg->frg_step);
+
+ return 0;
+}
+
+/* config dma linklist */
+static int __dma_config_linklist(struct dma_chan *chan,struct sprd_dma_desc *mdesc,
+ struct sprd_dma_cfg *cfg_list,u32 node_size)
+{
+ int ret, i;
+ struct sprd_dma_chn_reg *dma_reg_list;
+ struct sprd_dma_cfg list_cfg;
+ dma_addr_t cfg_p;
+
+ if(node_size < 2)
+ return -EINVAL;
+
+ /* check dma linklist node memory */
+ if(cfg_list[0].link_cfg_v == 0 || cfg_list[0].link_cfg_p == 0){
+ pr_err("Haven't allocated memory for list node!\n");
+ return -EINVAL;
+ }
+
+ /* get linklist node virtual addr and physical addr */
+ dma_reg_list = (struct sprd_dma_chn_reg *)cfg_list[0].link_cfg_v;
+ cfg_p = (dma_addr_t)cfg_list[0].link_cfg_p;
+
+ pr_dma("Linklist:alloc addr virt:0x%lx,phys addr: 0x%lx\n",
+ (unsigned long)dma_reg_list,(unsigned long)cfg_p);
+
+ /* linklist configuration */
+ for (i = 0; i < node_size; i++) {
+ cfg_list[i].linklist_ptr = (u32)(cfg_p +
+ ((i + 1) % node_size) * sizeof(struct sprd_dma_chn_reg) + 0x10);
+
+ ret = __dma_config(chan,NULL,cfg_list + i,dma_reg_list + i,CONFIG_LINKLIST);
+ if (ret < 0) {
+ pr_err("Linklist configuration error!\n");
+ return -EINVAL;
+ }
+ pr_dma("Configuration the link list!\n");
+ __dma_cfg_check_register(dma_reg_list + i);
+ }
+
+ memset((void *)&list_cfg, 0x0, sizeof(list_cfg));
+ list_cfg.linklist_ptr = cfg_p + 0x10;
+ list_cfg.irq_mode = cfg_list[0].irq_mode;
+ list_cfg.src_addr = cfg_list[0].src_addr;
+ list_cfg.des_addr = cfg_list[0].des_addr;
+
+ /* support for audio */
+ if(cfg_list[node_size - 1].is_end > 1)
+ mdesc->cycle = 1;
+
+ ret = __dma_config(chan,mdesc,&list_cfg,NULL,CONFIG_DESC);
+
+ return 0;
+}
+
+static dma_int_type __dma_check_int_type(u32 intc_reg)
+{
+ if(intc_reg & 0x1000)
+ return CONFIG_ERR;
+ else if(intc_reg & 0x800)
+ return LIST_DONE;
+ else if(intc_reg & 0x400)
+ return TRANS_DONE;
+ else if(intc_reg & 0x200)
+ return BLK_DONE;
+ else if(intc_reg & 0x100)
+ return FRAG_DONE;
+ else
+ return NO_INT;
+}
+
+static dma_request_mode __dma_check_req_type(u32 frag_reg)
+{
+ u32 frag_reg_t = frag_reg >> 24;
+ if((frag_reg_t & 0x3) == 0)
+ return FRAG_REQ_MODE;
+ else if((frag_reg_t & 0x3) == 0x1)
+ return BLOCK_REQ_MODE;
+ else if((frag_reg_t & 0x3) == 0x2)
+ return TRANS_REQ_MODE;
+ else if((frag_reg_t & 0x3) == 0x3)
+ return LIST_REQ_MODE;
+ else
+ return FRAG_REQ_MODE;
+}
+
+/* check if the dma request desc is done */
+static void __dma_check_mdesc_done(struct sprd_dma_desc *mdesc,
+ dma_int_type int_type,dma_request_mode req_mode)
+{
+ if(mdesc->cycle == 1){
+ mdesc->done = 0;
+ return;
+ }
+
+ if((unsigned int)int_type >= ((unsigned int)req_mode + 1))
+ mdesc->done = 1;
+ else
+ mdesc->done = 0;
+}
+
+static void __dma_check_int(struct sprd_dma_dev *sdev, int type)
+{
+ struct sprd_dma_chn *mchan = NULL;
+ struct sprd_dma_chn_reg *dma_reg = NULL;
+ struct sprd_dma_desc *mdesc = NULL;
+ struct dma_async_tx_descriptor *desc = NULL;
+ u32 irq_status = 0,aon_irq_status = 0,i = 0;
+ dma_int_type int_type;
+ dma_request_mode req_type;
+ volatile struct sprd_dma_glb_reg *dma_glb_reg = sdev->dma_glb_base;
+ volatile struct sprd_dma_glb_reg *aon_dma_glb_reg = NULL;
+ if(sdev->aon_chn_cnt > 0 )
+ aon_dma_glb_reg = sdev->aon_dma_glb_base;
+
+ if(type == 1){
+ irq_status = dma_glb_reg->int_msk_sts;
+ aon_irq_status = (aon_dma_glb_reg != NULL) ? (aon_dma_glb_reg->int_msk_sts) : 0;
+ pr_dma("Enter DMA interrupt handle function!\n");
+ } else {
+ irq_status = dma_glb_reg->int_raw_sts;
+ aon_irq_status = (aon_dma_glb_reg != NULL) ? (aon_dma_glb_reg->int_raw_sts) : 0;
+ }
+
+ pr_dma("Check DMA interrupt,irq_status=0x%x,"
+ "aon_irq_status=0x%x!\n",irq_status,aon_irq_status);
+
+ while(irq_status || aon_irq_status) {
+ if(irq_status != 0){
+ i = __ffs(irq_status);
+ irq_status &= (irq_status - 1);
+ }else if(aon_irq_status != 0){
+ i = __ffs(aon_irq_status);
+ aon_irq_status &= (aon_irq_status - 1);
+ i += sdev->ap_chn_cnt;
+ }else
+ break;
+
+ mchan = &sdev->channels[i];
+ spin_lock(&mchan->chn_lock);
+ dma_reg = (struct sprd_dma_chn_reg *)(mchan->dma_chn_base);
+ int_type = __dma_check_int_type(dma_reg->intc);
+ req_type = __dma_check_req_type(dma_reg->frg_len);
+ pr_dma("DMA channel [%d] interrupt,intc=0x%x,int_type=%d,"
+ "req_type=%d!\n",i,dma_reg->intc,int_type,(req_type + 1));
+ dma_reg->intc |= 0x1f << 24;
+
+ /* check if the dma request desc is done */
+ if(!list_empty(&mchan->active)){
+ mdesc = list_first_entry(&mchan->active,struct sprd_dma_desc, node);
+ __dma_check_mdesc_done(mdesc,int_type,req_type);
+ if(mdesc->done == 1)
+ list_splice_tail_init(&mchan->active, &mchan->completed);
+
+ /* support for audio */
+ if(mdesc->cycle == 1){
+ desc = &mdesc->desc;
+ if(desc->callback)
+ desc->callback(desc->callback_param);
+ }
+ }
+ spin_unlock(&mchan->chn_lock);
+ }
+}
+
+static int sprd_dma_start(struct sprd_dma_dev *sdev, struct sprd_dma_chn *mchan,
+ struct sprd_dma_desc *mdesc, u32 dev_id)
+{
+ __dma_set_uid(sdev,mchan,dev_id);
+ __dma_chn_enable(mchan);
+
+ if(DMA_UID_SOFTWARE == dev_id)
+ __dma_soft_request(mchan);
+
+ /* for debug info */
+ if(dev_id > DMA_UID_SOFTWARE){
+ pr_dma("sprd_dma_start,dev_id=%d,ap_req_cid=%d,!\n",dev_id,
+ readl_relaxed((void __iomem *)SPRD_DMA_REQ_CID(sdev->dma_glb_base, dev_id)));
+ if(sdev->aon_dma_glb_base)
+ pr_dma("sprd_dma_start,dev_id=%d,aon_req_cid=%d!\n",dev_id,
+ readl_relaxed((void __iomem *)SPRD_DMA_REQ_CID(sdev->aon_dma_glb_base, dev_id)));
+ }
+
+ return 0;
+}
+
+static int sprd_dma_stop(struct sprd_dma_chn *mchan)
+{
+ struct sprd_dma_dev *sdev = to_sprd_dma_dev(&mchan->chan);
+
+ __dma_unset_uid(sdev,mchan,mchan->dev_id);
+ __dma_stop_and_disable(mchan);
+ __dma_int_clr(mchan);
+
+ return 0;
+}
+
+static int sprd_dma_execute(struct sprd_dma_chn *mchan)
+{
+ struct sprd_dma_desc *first = NULL;
+ struct sprd_dma_dev *sdev = to_sprd_dma_dev(&mchan->chan);
+
+ if(!list_empty(&mchan->active))
+ first = list_first_entry(&mchan->active,struct sprd_dma_desc, node);
+ else
+ return 0;
+
+ pr_dma("Before copy data to DMA reg!\n");
+ __dma_cfg_check_register(first->dma_chn_reg);
+
+ memcpy_toio((void __iomem *)mchan->dma_chn_base,(void *)first->dma_chn_reg,
+ sizeof(struct sprd_dma_chn_reg));
+
+ pr_dma("After copy data to DMA reg!\n");
+ __dma_cfg_check_register(mchan->dma_chn_base);
+
+ sprd_dma_start(sdev, mchan, first, mchan->dev_id);
+
+ pr_dma("After start the DMA!\n");
+ __dma_cfg_check_register(mchan->dma_chn_base);
+
+ return 0;
+}
+
+static dma_cookie_t sprd_desc_submit(struct dma_async_tx_descriptor *tx)
+{
+ struct sprd_dma_chn *mchan = to_sprd_dma_chan(tx->chan);
+ struct sprd_dma_desc *mdesc = to_sprd_dma_desc(tx);
+ struct sprd_dma_desc *first = NULL;
+ unsigned long flags;
+ dma_cookie_t cookie;
+
+ /* assign cookie */
+ spin_lock_irqsave(&mchan->chn_lock, flags);
+ cookie = dma_cookie_assign(tx);
+ list_move_tail(&mdesc->node, &mchan->queued);
+ if(!list_empty(&mdesc->next_node)){
+ list_splice_tail_init(&mdesc->next_node, &mchan->queued);
+ pr_dma("Submitting has next node!\n");
+ }
+
+ /* execute the dma desc */
+ if (list_empty(&mchan->active)){
+ first = list_first_entry(&mchan->queued,struct sprd_dma_desc, node);
+ list_move_tail(&first->node, &mchan->active);
+ sprd_dma_execute(mchan);
+ }
+ spin_unlock_irqrestore(&mchan->chn_lock, flags);
+
+ return cookie;
+}
+
+static irqreturn_t __dma_irq_handle(int irq, void *dev_id)
+{
+ struct sprd_dma_dev *sdev = (struct sprd_dma_dev *)dev_id;
+
+ //spin_lock(&sdev->dma_lock);
+ __dma_check_int(sdev, 1);
+ //spin_unlock(&sdev->dma_lock);
+
+ tasklet_schedule(&sdev->tasklet);
+
+ return IRQ_HANDLED;
+}
+
+static int sprd_dma_process_completed(struct sprd_dma_dev *sdev)
+{
+ struct sprd_dma_chn *mchan;
+ struct sprd_dma_desc *mdesc;
+ struct sprd_dma_desc *first;
+ dma_cookie_t last_cookie = 0;
+ struct dma_async_tx_descriptor *desc;
+ unsigned long flags;
+ LIST_HEAD(list);
+ u32 dma_chn_cnt = sdev->ap_chn_cnt + sdev->aon_chn_cnt;
+ int i;
+
+ for(i = 0;i < dma_chn_cnt; i++){
+ mchan = &sdev->channels[i];
+
+ /* deal the compelete dma request */
+ spin_lock_irqsave(&mchan->chn_lock, flags);
+ if(!list_empty(&mchan->completed))
+ list_splice_tail_init(&mchan->completed, &list);
+ spin_unlock_irqrestore(&mchan->chn_lock, flags);
+
+ if (list_empty(&list))
+ continue;
+
+ list_for_each_entry(mdesc, &list, node) {
+ pr_dma("Channel [%d] complete list have node!\n",i);
+ desc = &mdesc->desc;
+
+ if(desc->callback){
+ desc->callback(desc->callback_param);
+ }
+
+ /* submit desc->next */
+ dma_run_dependencies(desc);
+ last_cookie = desc->cookie;
+ }
+
+ spin_lock_irqsave(&mchan->chn_lock, flags);
+ list_splice_tail_init(&list, &mchan->free);
+
+ /* continue to process new adding queued request */
+ if (!list_empty(&mchan->queued)){
+ pr_dma("Channel [%d] queued list have node!\n",i);
+ if(list_empty(&mchan->active)){
+ first = list_first_entry(&mchan->queued,struct sprd_dma_desc, node);
+ list_move_tail(&first->node, &mchan->active);
+ sprd_dma_execute(mchan);
+ }
+ } else {
+ mchan->chan.completed_cookie = last_cookie;
+ pr_dma("Channel [%d] queued list is NULL,and transfer done!\n",i);
+ }
+
+ spin_unlock_irqrestore(&mchan->chn_lock, flags);
+ }
+
+ return 0;
+}
+
+static void sprd_dma_tasklet(unsigned long data)
+{
+ struct sprd_dma_dev *sdev = (void *)data;
+ sprd_dma_process_completed(sdev);
+}
+
+static int sprd_dma_alloc_chan_resources(struct dma_chan *chan)
+{
+ struct sprd_dma_chn *mchan = to_sprd_dma_chan(chan);
+ struct sprd_dma_dev *mdev = to_sprd_dma_dev(chan);
+ dma_addr_t chn_reg_paddr;
+ struct sprd_dma_desc *mdesc;
+ struct sprd_dma_chn_reg *chn_reg;
+ unsigned long i,flags;
+ LIST_HEAD(descs);
+
+ chn_reg = dmam_alloc_coherent(mdev->dma_dev.dev,
+ MPC_DMA_DESCRIPTORS * sizeof(struct sprd_dma_chn_reg),&chn_reg_paddr,GFP_KERNEL);
+ if (!chn_reg)
+ return -ENOMEM;
+
+ /* init the dma desc */
+ for (i = 0; i < MPC_DMA_DESCRIPTORS; i++) {
+ mdesc = (struct sprd_dma_desc *)kmem_cache_zalloc(mdev->dma_desc_node_cachep, GFP_ATOMIC);
+ if (!mdesc) {
+ pr_err("Memory allocation error: Allocated only %ld descriptors\n", i);
+ break;
+ }
+
+ dma_async_tx_descriptor_init(&mdesc->desc, chan);
+ mdesc->desc.flags = DMA_CTRL_ACK;
+ mdesc->desc.tx_submit = sprd_desc_submit;
+ mdesc->dma_chn_reg = &chn_reg[i];
+ mdesc->dma_chn_reg_paddr = chn_reg_paddr + (i * sizeof(struct sprd_dma_chn_reg));
+ mdesc->done = 0;
+ mdesc->cycle = 0;
+ INIT_LIST_HEAD(&mdesc->node);
+ INIT_LIST_HEAD(&mdesc->next_node);
+ list_add_tail(&mdesc->node, &descs);
+ }
+
+ if (i == 0) {
+ dma_free_coherent(mdev->dma_dev.dev,
+ MPC_DMA_DESCRIPTORS * sizeof(struct sprd_dma_chn_reg),chn_reg, chn_reg_paddr);
+ return -ENOMEM;
+ }
+
+ spin_lock_irqsave(&mchan->chn_lock, flags);
+ mchan->dma_desc = chn_reg;
+ mchan->dma_desc_paddr = chn_reg_paddr;
+ list_splice_tail_init(&descs, &mchan->free);
+ spin_unlock_irqrestore(&mchan->chn_lock, flags);
+
+ /* enable dma */
+ mchan->dev_id = 0;
+ mchan->chan_status = USED;
+ if(mchan->chan_num < mdev->ap_chn_cnt)
+ __ap_dma_enable();
+ else{
+ __aon_dma_enable();
+ __aon_dma_int_enable();
+ }
+
+ pr_dma("Alloc chan resources is OK, and chn_reg=0x%lx, chn_reg_paddr=0x%lx!\n",
+ (unsigned long)chn_reg,(unsigned long)chn_reg_paddr);
+
+ return 0;
+}
+
+static void sprd_dma_free_chan_resources(struct dma_chan *chan)
+{
+ struct sprd_dma_chn *mchan = to_sprd_dma_chan(chan);
+ struct sprd_dma_dev *mdev = to_sprd_dma_dev(chan);
+ dma_addr_t chn_reg_paddr;
+ struct sprd_dma_desc *mdesc,*tmp;
+ struct sprd_dma_chn_reg *chn_reg;
+ struct sprd_dma_chn *tchan;
+ unsigned long flags;
+ int i;
+ LIST_HEAD(descs);
+ u32 dma_chn_cnt = mdev->ap_chn_cnt + mdev->aon_chn_cnt;
+ u32 ap_chn_cnt = mdev->ap_chn_cnt;
+
+ /* delete list */
+ spin_lock_irqsave(&mchan->chn_lock, flags);
+ list_splice_tail_init(&mchan->prepared, &mchan->free);
+ list_splice_tail_init(&mchan->queued, &mchan->free);
+ list_splice_tail_init(&mchan->active, &mchan->free);
+ list_splice_tail_init(&mchan->completed, &mchan->free);
+
+ list_splice_tail_init(&mchan->free, &descs);
+ chn_reg = mchan->dma_desc;
+ chn_reg_paddr = mchan->dma_desc_paddr;
+ spin_unlock_irqrestore(&mchan->chn_lock, flags);
+
+ dmam_free_coherent(mdev->dma_dev.dev,
+ MPC_DMA_DESCRIPTORS * sizeof(struct sprd_dma_chn_reg), chn_reg, chn_reg_paddr);
+
+ list_for_each_entry_safe(mdesc, tmp, &descs, node)
+ kmem_cache_free(mdev->dma_desc_node_cachep, mdesc);
+
+ mchan->chan_status = NO_USED;
+
+ /* stop and disable dma */
+ sprd_dma_stop(mchan);
+ for (i = 0; i < ap_chn_cnt; i++) {
+ tchan = &mdev->channels[i];
+ if(tchan->chan_status == USED)
+ break;
+ }
+ if(i == ap_chn_cnt){
+ __ap_dma_softreset();
+ __ap_dma_disable();
+ }
+
+ for (i = ap_chn_cnt; i < dma_chn_cnt; i++) {
+ tchan = &mdev->channels[i];
+ if(tchan->chan_status == USED)
+ break;
+ }
+ if(i == dma_chn_cnt){
+ __aon_dma_int_disable();
+ __aon_dma_reset();
+ __aon_dma_disable();
+ }
+
+ pr_dma("Release chan resources is OK!\n");
+}
+
+static int sprd_dma_check_int(struct dma_chan *chan)
+{
+ struct sprd_dma_dev *sdev = to_sprd_dma_dev(chan);
+
+ __dma_check_int(sdev, 0);
+ sprd_dma_process_completed(sdev);
+
+ return 0;
+}
+
+static enum dma_status sprd_dma_tx_status(struct dma_chan *chan,
+ dma_cookie_t cookie,struct dma_tx_state *txstate)
+{
+ struct sprd_dma_chn *mchan = to_sprd_dma_chan(chan);
+ enum dma_status ret;
+ int residue = txstate->residue;
+
+ /* for audio no int handler */
+ if(mchan->irq_handle_enable == 0){
+ sprd_dma_check_int(chan);
+ pr_dma("Check dma interrupt by hand!\n");
+ }
+
+ //spin_lock(&mchan->chn_lock);
+ ret = dma_cookie_status(chan, cookie, txstate);
+ //spin_unlock(&mchan->chn_lock);
+
+ /* get dma sour&dest addr */
+ if(residue == SPRD_SRC_ADDR)
+ txstate->residue = __dma_get_src_addr(chan);
+ else if(residue == SPRD_DST_ADDR)
+ txstate->residue = __dma_get_dst_addr(chan);
+ else
+ txstate->residue = 0;
+
+ pr_dma("%s cookie=%d, residue=0x%x!\n",__func__, cookie,txstate->residue);
+ return ret;
+}
+
+static void sprd_dma_issue_pending(struct dma_chan *chan)
+{
+ /*
+ * We are posting descriptors to the hardware as soon as
+ * they are ready, so this function does nothing.
+ */
+}
+
+struct dma_async_tx_descriptor *sprd_dma_prep_dma_memcpy(
+ struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
+ size_t len, unsigned long flags)
+{
+ struct sprd_dma_dev *sdev = to_sprd_dma_dev(chan);
+ struct sprd_dma_chn *mchan = to_sprd_dma_chan(chan);
+ struct sprd_dma_desc *mdesc = NULL;
+ unsigned long irq_flags;
+ u32 datawidth = 0, src_step = 0,des_step = 0;
+ struct sprd_dma_cfg *dma_cfg;
+ int dma_cfg_cnt = dma_cfg_group.dma_cfg_cnt;
+ int ret;
+
+ /* for configuration */
+ if(flags & DMA_CFG_FLAG){
+ if(dma_cfg_cnt < 1 || dma_cfg_cnt > DMA_CFG_COUNT){
+ dma_cfg_group.dma_cfg_cnt = 0;
+ up(&dma_cfg_group.cfg_sema);
+ pr_err("DMA wrong configuration number!\n");
+ return NULL;
+ } else {
+ dma_cfg = (struct sprd_dma_cfg *)kzalloc(sizeof(struct sprd_dma_cfg) * dma_cfg_cnt, GFP_KERNEL);
+ memcpy(dma_cfg,dma_cfg_group.dma_cfg,sizeof(struct sprd_dma_cfg)*dma_cfg_cnt);
+
+ dma_cfg_group.dma_cfg_cnt = 0;
+ memset(dma_cfg_group.dma_cfg,0,sizeof(struct sprd_dma_cfg) * DMA_CFG_COUNT);
+ up(&dma_cfg_group.cfg_sema);
+
+ goto Have_configured;
+ }
+ } else
+ dma_cfg = (struct sprd_dma_cfg *)kzalloc(sizeof(struct sprd_dma_cfg), GFP_KERNEL);
+
+ if (len > BLK_LEN_MASK && mchan->chn_type != FULL_DMA) {
+ pr_err("Channel type isn't support!\n");
+ return NULL;
+ }
+
+ /* set step automatically */
+ if ((len & 0x3) == 0) {
+ datawidth = 2;
+ src_step = 4;
+ des_step = 4;
+ } else {
+ if ((len & 0x1) == 0) {
+ datawidth = 1;
+ src_step = 2;
+ des_step = 2;
+ } else {
+ datawidth = 0;
+ src_step = 1;
+ des_step = 1;
+ }
+ }
+
+ /* dma reg setup */
+ memset(&dma_cfg[0],0,sizeof(struct sprd_dma_cfg));
+ dma_cfg[0].src_addr = src;
+ dma_cfg[0].des_addr = dest;
+ dma_cfg[0].datawidth = datawidth;
+ dma_cfg[0].src_step = src_step;
+ dma_cfg[0].des_step = src_step;
+ dma_cfg[0].fragmens_len = DMA_MEMCPY_MIN_SIZE;
+ if (len <= BLK_LEN_MASK) {
+ dma_cfg[0].block_len = len;
+ dma_cfg[0].req_mode = BLOCK_REQ_MODE;
+ dma_cfg[0].irq_mode = BLK_DONE;
+ } else {
+ dma_cfg[0].block_len = DMA_MEMCPY_MIN_SIZE;
+ dma_cfg[0].transcation_len = len;
+ dma_cfg[0].req_mode = TRANS_REQ_MODE;
+ dma_cfg[0].irq_mode = TRANS_DONE;
+ }
+ dma_cfg_cnt = 1;
+
+Have_configured:
+ /* get a free dma desc */
+ spin_lock_irqsave(&mchan->chn_lock, irq_flags);
+ if(!list_empty(&mchan->free)){
+ mdesc = list_first_entry(&mchan->free, struct sprd_dma_desc, node);
+ list_del(&mdesc->node);
+ }
+ spin_unlock_irqrestore(&mchan->chn_lock, irq_flags);
+
+ if(!mdesc){
+ sprd_dma_process_completed(sdev);
+ kfree(dma_cfg);
+ return NULL;
+ }
+
+ /* config into dma reg */
+ if(dma_cfg_cnt == 1)
+ ret = __dma_config(chan,mdesc,&dma_cfg[0],NULL,CONFIG_DESC);
+ else if(dma_cfg_cnt > 1)
+ ret = __dma_config_linklist(chan,mdesc,&dma_cfg[0],dma_cfg_cnt);
+ else
+ pr_err("DMA configuration count isn't available!\n");
+
+ kfree(dma_cfg);
+ if(ret < 0){
+ spin_lock_irqsave(&mchan->chn_lock, irq_flags);
+ list_add_tail(&mdesc->node, &mchan->free);
+ spin_unlock_irqrestore(&mchan->chn_lock, irq_flags);
+ pr_err("Configuration is error!\n");
+ return NULL;
+ }
+
+ /* support hardware request */
+ if(flags & DMA_HARDWARE_FLAG){
+ mchan->re_mode = HARDWARE_REQ;
+ }else{
+ mchan->re_mode = SOFTWARE_REQ;
+ mchan->dev_id = DMA_UID_SOFTWARE;
+ }
+
+ /* add the prepare list */
+ spin_lock_irqsave(&mchan->chn_lock, irq_flags);
+ list_add_tail(&mdesc->node, &mchan->prepared);
+ spin_unlock_irqrestore(&mchan->chn_lock, irq_flags);
+
+ return &mdesc->desc;
+}
+
+struct dma_async_tx_descriptor *sprd_prep_dma_sg(struct dma_chan *chan,
+ struct scatterlist *dst_sg, unsigned int dst_nents,
+ struct scatterlist *src_sg, unsigned int src_nents,
+ unsigned long flags)
+{
+ struct sprd_dma_dev *sdev = to_sprd_dma_dev(chan);
+ struct sprd_dma_chn *mchan = to_sprd_dma_chan(chan);
+ struct sprd_dma_desc *mdesc = NULL;
+ struct sprd_dma_desc *first_mdesc = NULL;
+ struct scatterlist *sg_d;
+ struct scatterlist *sg_s;
+ unsigned int scatterlist_entry,src_dma_len,dst_dma_len;
+ unsigned long len;
+ int i, ret;
+ u32 datawidth = 0, src_step = 0,des_step = 0;
+ struct sprd_dma_cfg dma_cfg_t;
+ dma_addr_t dst_dma_addr,src_dma_addr;
+ unsigned long irq_flags;
+
+ /* security check */
+ if(dst_nents != src_nents ){
+ pr_err("DMA scatterlist entry count is not equal!\n");
+ return NULL;
+ }else
+ scatterlist_entry = src_nents;
+
+ if(scatterlist_entry > MPC_DMA_DESCRIPTORS){
+ pr_err("DMA scatterlist is overrun!\n");
+ return NULL;
+ }
+
+ if(flags & DMA_HARDWARE_FLAG){
+ pr_err("DMA scatterlist do not support hardware request!\n");
+ return NULL;
+ }
+
+ /* for scatter list */
+ for (i = 0, sg_d = dst_sg, sg_s = src_sg; i < scatterlist_entry;
+ i++, sg_d = sg_next(sg_d), sg_s = sg_next(sg_s)){
+ dst_dma_addr = sg_dma_address(sg_d);
+ dst_dma_len = sg_dma_len(sg_d);
+ src_dma_addr = sg_dma_address(sg_s);
+ src_dma_len = sg_dma_len(sg_s);
+
+ pr_dma("DMA scatterlist dst_dma_addr=0x%x, src_dma_addr=0x%x,dst_len=%d,src_len=%d!\n",
+ (unsigned int)dst_dma_addr,(unsigned int)src_dma_addr,dst_dma_len,src_dma_len);
+
+ if(dst_dma_len != src_dma_len)
+ continue;
+ else
+ len = src_dma_len;
+
+ if ((len & 0x3) == 0) {
+ datawidth = 2;
+ src_step = 4;
+ des_step = 4;
+ } else {
+ if ((len & 0x1) == 0) {
+ datawidth = 1;
+ src_step = 2;
+ des_step = 2;
+ } else {
+ datawidth = 0;
+ src_step = 1;
+ des_step = 1;
+ }
+ }
+
+ memset(&dma_cfg_t,0,sizeof(struct sprd_dma_cfg));
+ dma_cfg_t.src_addr = src_dma_addr;
+ dma_cfg_t.des_addr = dst_dma_addr;
+ dma_cfg_t.datawidth = datawidth;
+ dma_cfg_t.src_step = src_step;
+ dma_cfg_t.des_step = src_step;
+ dma_cfg_t.fragmens_len = DMA_MEMCPY_MIN_SIZE;
+ if (len <= BLK_LEN_MASK) {
+ dma_cfg_t.block_len = len;
+ dma_cfg_t.req_mode = BLOCK_REQ_MODE;
+ dma_cfg_t.irq_mode = BLK_DONE;
+ } else {
+ dma_cfg_t.block_len = DMA_MEMCPY_MIN_SIZE;
+ dma_cfg_t.transcation_len = len;
+ dma_cfg_t.req_mode = TRANS_REQ_MODE;
+ dma_cfg_t.irq_mode = TRANS_DONE;
+ }
+
+ spin_lock_irqsave(&mchan->chn_lock, irq_flags);
+ if(!list_empty(&mchan->free)){
+ mdesc = list_first_entry(&mchan->free, struct sprd_dma_desc, node);
+ list_del(&mdesc->node);
+ }
+ spin_unlock_irqrestore(&mchan->chn_lock, irq_flags);
+ if(!mdesc){
+ sprd_dma_process_completed(sdev);
+ pr_err("Warning: There are not enough mdesc for scatterlist!\n");
+ }
+
+ ret = __dma_config(chan,mdesc,&dma_cfg_t,NULL,CONFIG_DESC);
+ if(ret < 0){
+ pr_err("Warning: Configuration is error!\n");
+ spin_lock_irqsave(&mchan->chn_lock, irq_flags);
+ list_add_tail(&mdesc->node, &mchan->free);
+ spin_unlock_irqrestore(&mchan->chn_lock, irq_flags);
+ continue;
+ }
+
+ if(!first_mdesc){
+ first_mdesc = mdesc;
+ spin_lock_irqsave(&mchan->chn_lock, irq_flags);
+ list_add_tail(&mdesc->node, &mchan->prepared);
+ spin_unlock_irqrestore(&mchan->chn_lock, irq_flags);
+ }else{
+ spin_lock_irqsave(&mchan->chn_lock, irq_flags);
+ list_add_tail(&mdesc->node, &first_mdesc->next_node);
+ spin_unlock_irqrestore(&mchan->chn_lock, irq_flags);
+ }
+ }
+
+ mchan->re_mode = SOFTWARE_REQ;
+
+ if(first_mdesc)
+ return &first_mdesc->desc;
+ else
+ return NULL;
+}
+
+/* copy dma configuration */
+static void sprd_dma_copy(struct sprd_dma_chn *mchan, unsigned long arg)
+{
+ unsigned int i = 0;
+ do {
+ memcpy(&dma_cfg_group.dma_cfg[i],(struct sprd_dma_cfg *)arg,sizeof(struct sprd_dma_cfg));
+ arg += sizeof(struct sprd_dma_cfg);
+
+ pr_dma("%s, i:%d block_len:0x%x,fragmens_len:0x%x,src_step:0x%x,des_addr:0x%x,"
+ "src_addr:0x%x,link_cfg_p:0x%x,link_cfg_v:0x%lx,is_end:%d,transcation_len:0x%x\n",
+ __func__,i,dma_cfg_group.dma_cfg[i].block_len,dma_cfg_group.dma_cfg[i].fragmens_len,
+ dma_cfg_group.dma_cfg[i].src_step,dma_cfg_group.dma_cfg[i].des_addr,
+ dma_cfg_group.dma_cfg[i].src_addr,dma_cfg_group.dma_cfg[i].link_cfg_p,
+ dma_cfg_group.dma_cfg[i].link_cfg_v,dma_cfg_group.dma_cfg[i].is_end,
+ dma_cfg_group.dma_cfg[i].transcation_len);
+
+ } while(dma_cfg_group.dma_cfg[i++].is_end == 0 && i < (DMA_CFG_COUNT - 1));
+ dma_cfg_group.dma_cfg_cnt = i;
+ pr_dma("Get dma configuration number is %d!\n",i);
+}
+
+/* terminate dma channel */
+static int sprd_terminate_all(struct sprd_dma_chn *mchan)
+{
+ struct dma_chan *chan = &mchan->chan;
+
+ sprd_dma_free_chan_resources(chan);
+ return 0;
+}
+
+/* dma control method */
+static int sprd_dma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, unsigned long arg)
+{
+ int ret = 0;
+ struct sprd_dma_chn *mchan = to_sprd_dma_chan(chan);
+
+ switch(cmd){
+ case DMA_SLAVE_CONFIG:
+ if(down_trylock(&dma_cfg_group.cfg_sema)){
+ pr_err("DMA resource is busy, try again...\n");
+ return -ENXIO;
+ }
+ sprd_dma_copy(mchan, arg);
+ break;
+ case DMA_PAUSE:
+ ret = sprd_dma_stop(mchan);
+ break;
+ case DMA_TERMINATE_ALL:
+ ret = sprd_terminate_all(mchan);
+ break;
+ case DMA_RESUME:
+ ret = sprd_dma_execute(mchan);
+ break;
+ default:
+ ret = -ENXIO;
+ break;
+ }
+
+ return ret;
+}
+
+/* for dma channel request filter*/
+bool sprd_dma_filter_fn(struct dma_chan *chan, void *filter_param)
+{
+ struct sprd_dma_chn *mchan = NULL;
+ unsigned int type = *(unsigned int *)filter_param;
+ struct sprd_dma_dev *mdev = to_sprd_dma_dev(chan);
+ u32 ap_chn_cnt = mdev->ap_chn_cnt;
+
+ mchan = to_sprd_dma_chan(chan);
+ if(!mchan)
+ return false;
+
+ if(type == AP_STANDARD_DMA){
+ if(mchan->chn_type == STANDARD_DMA && mchan->chan_num < ap_chn_cnt)
+ return true;
+ }else if(type == AP_FULL_DMA){
+ if(mchan->chn_type == FULL_DMA && mchan->chan_num < ap_chn_cnt)
+ return true;
+ }else if(type == AON_STANDARD_DMA){
+ if(mchan->chn_type == STANDARD_DMA && mchan->chan_num > (ap_chn_cnt -1))
+ return true;
+ }else if(type == AON_FULL_DMA){
+ if(mchan->chn_type == FULL_DMA && mchan->chan_num > (ap_chn_cnt -1))
+ return true;
+ }else if((type & 0xf00) == NUM_REQUEST_DMA){
+ if(mchan->chan_num == (type & 0xff))
+ return true;
+ }
+
+ return false;
+}
+
+/* for dma channel request filter*/
+bool sprd_dma_filter_fn_t(struct dma_chan *chan, void *param)
+{
+ struct sprd_dma_chn *mchan = to_sprd_dma_chan(chan);
+ unsigned int req = *(unsigned int *)param;
+
+ return req == (mchan->chan_num + 1);
+}
+
+/* for dma debug */
+int sprd_dma_check_register(struct dma_chan *c)
+{
+ volatile struct sprd_dma_chn_reg *dma_reg = NULL;
+ struct sprd_dma_chn *mchan = to_sprd_dma_chan(c);
+
+ dma_reg = (struct sprd_dma_chn_reg *)mchan->dma_chn_base;
+ __dma_cfg_check_register((void __iomem *)dma_reg);
+
+ return 0;
+}
+
+/* probe method */
+static int sprd_dma_probe(struct platform_device *pdev)
+{
+ struct sprd_dma_dev *sdev = NULL;
+ struct sprd_dma_chn *dma_chn = NULL;
+ void __iomem *dma_ap_base = NULL;
+ void __iomem *dma_aon_base = NULL;
+ struct resource *res = NULL;
+ u32 dma_irq = 0, aon_dma_irq = 0, aon_offset = 0;
+ u32 dma_chn_cnt = 0, ap_chn_cnt = 0, aon_chn_cnt = 0;
+ int ret, i;
+
+ if(!pdev->dev.of_node){
+ pr_warn("Error: Can't find the dma node!\n");
+ return -ENODEV;
+ }
+
+ if (of_property_read_u32(pdev->dev.of_node, "#dma-channels", &dma_chn_cnt)){
+ pr_warn("Error: Can't get total dma channel number infor!\n");
+ return -EIO;
+ }
+
+ if (of_property_read_u32(pdev->dev.of_node, "sprd,aon-offset", &aon_offset)){
+ pr_warn("Error: Can't get ap dma channel number infor!\n");
+ return -EIO;
+ }
+
+ aon_chn_cnt = dma_chn_cnt - aon_offset;
+ ap_chn_cnt = dma_chn_cnt - aon_chn_cnt;
+
+ pr_dma("DMA total chn: %d,ap chn :%d,aon chn :%d!\n",dma_chn_cnt,ap_chn_cnt,aon_chn_cnt);
+
+ dma_irq = platform_get_irq(pdev, 0);
+ if (dma_irq <= 0) {
+ pr_warn("Error: Can't get the ap dma irq number!\n");
+ return -EIO;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ pr_warn("Error: Can't get ap dma registers resource!\n");
+ return -EIO;
+ }
+
+ dma_ap_base = devm_ioremap_nocache(&pdev->dev, res->start, resource_size(res));
+ if(!dma_ap_base){
+ pr_warn("Error: Can't get the ap dma base addr!\n");
+ return -ENOMEM;
+ }
+
+ if(aon_chn_cnt > 0){
+ aon_dma_irq = platform_get_irq(pdev, 1);
+ if (aon_dma_irq < 0) {
+ pr_warn("Error: Can't get the aon dma irq number!\n");
+ return -EIO;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!res) {
+ pr_warn("Error: Can't get aon dma registers resource!\n");
+ return -EIO;
+ }
+
+ dma_aon_base = devm_ioremap_nocache(&pdev->dev, res->start, resource_size(res));
+ if(!dma_aon_base){
+ pr_warn("Error: Can't get the aon dma base addr!\n");
+ return -ENOMEM;
+ }
+ }
+
+ pr_dma("DMA irq number is %d,aon irq num is %d!\n", dma_irq,aon_dma_irq);
+
+ /* dma device momery alloc */
+ sdev = devm_kzalloc(&pdev->dev,(sizeof(*sdev) +
+ (sizeof(struct sprd_dma_chn) * dma_chn_cnt)),GFP_KERNEL);
+ if(!sdev){
+ pr_err("Error: DMA alloc dma dev failed!\n");
+ return -ENOMEM;
+ }
+
+ /* init dma device */
+ dma_cap_set(DMA_MEMCPY|DMA_SG, sdev->dma_dev.cap_mask);
+ sdev->dma_dev.chancnt = dma_chn_cnt;
+ INIT_LIST_HEAD(&sdev->dma_dev.channels);
+ INIT_LIST_HEAD(&sdev->dma_dev.global_node);
+ spin_lock_init(&sdev->dma_lock);
+ sdev->dma_dev.dev = &pdev->dev;
+
+ sdev->dma_dev.device_alloc_chan_resources = sprd_dma_alloc_chan_resources;
+ sdev->dma_dev.device_free_chan_resources = sprd_dma_free_chan_resources;
+ sdev->dma_dev.device_tx_status = sprd_dma_tx_status;
+ sdev->dma_dev.device_issue_pending = sprd_dma_issue_pending;
+ sdev->dma_dev.device_prep_dma_memcpy = sprd_dma_prep_dma_memcpy;
+ sdev->dma_dev.device_prep_dma_sg = sprd_prep_dma_sg;
+ sdev->dma_dev.device_control = sprd_dma_control;
+
+ /* init dma chn */
+ for (i = 0; i < dma_chn_cnt; i++) {
+ dma_chn = &sdev->channels[i];
+ dma_chn->chan.device = &sdev->dma_dev;
+ dma_cookie_init(&dma_chn->chan);
+ list_add_tail(&dma_chn->chan.device_node, &sdev->dma_dev.channels);
+
+ dma_chn->chan_num = i;
+ if(i < STANDARD_DMA_NUM || (i > (ap_chn_cnt - 1)
+ && i < (ap_chn_cnt + STANDARD_DMA_NUM))){
+ dma_chn->chn_type = STANDARD_DMA;
+ } else
+ dma_chn->chn_type = FULL_DMA;
+
+ dma_chn->chan_status = NO_USED;
+ dma_chn->irq_handle_enable = 0;
+ if( i < ap_chn_cnt)
+ dma_chn->dma_chn_base = (void __iomem *)((unsigned long)dma_ap_base +
+ 0x1000 + DMA_CHN_OFFSET * (i));
+ else {
+ if(dma_aon_base)
+ dma_chn->dma_chn_base = (void __iomem *)((unsigned long)dma_aon_base +
+ 0x1000 + DMA_CHN_OFFSET * (i - ap_chn_cnt));
+ else
+ dma_chn->dma_chn_base = NULL;
+ }
+
+ pr_dma("dma_chn [%d] dma_chn_base = 0x%lx!\n",i,(unsigned long)dma_chn->dma_chn_base);
+
+ spin_lock_init(&dma_chn->chn_lock);
+ INIT_LIST_HEAD(&dma_chn->free);
+ INIT_LIST_HEAD(&dma_chn->prepared);
+ INIT_LIST_HEAD(&dma_chn->queued);
+ INIT_LIST_HEAD(&dma_chn->active);
+ INIT_LIST_HEAD(&dma_chn->completed);
+ }
+
+ sdev->dma_glb_base = dma_ap_base;
+ sdev->irq = dma_irq;
+ sdev->aon_dma_glb_base = dma_aon_base;
+ sdev->aon_irq = aon_dma_irq;
+ sdev->ap_chn_cnt = ap_chn_cnt;
+ sdev->aon_chn_cnt = aon_chn_cnt;
+
+ pr_dma("dma_glb_base = 0x%lx, aon_dma_glb_base = 0x%lx!\n",
+ (unsigned long)sdev->dma_glb_base,(unsigned long)sdev->aon_dma_glb_base);
+
+ sdev->dma_desc_node_cachep =
+ kmem_cache_create("dma_desc_node",sizeof(struct sprd_dma_desc), 0,
+ SLAB_HWCACHE_ALIGN,NULL);
+ if(!sdev->dma_desc_node_cachep){
+ pr_err("Error: DMA alloc cache failed!\n");
+ return -ENOMEM;
+ }
+
+ /* irq request */
+ ret = devm_request_irq(&pdev->dev, dma_irq, __dma_irq_handle, 0, "sprd_dma",(void*)sdev);
+ if (ret < 0) {
+ pr_err("Error: Request dma irq failed %d\n", ret);
+ goto irq_fail;
+ }
+
+ if(aon_chn_cnt > 0){
+ ret = devm_request_irq(&pdev->dev, aon_dma_irq, __dma_irq_handle, 0, "sprd_aon_dma",(void*)sdev);
+ if (ret < 0) {
+ pr_err("Error: Request aon dma irq failed %d\n", ret);
+ goto aon_irq_fail;
+ }
+ }
+
+ /* initial the tasklet */
+ tasklet_init(&sdev->tasklet, sprd_dma_tasklet, (unsigned long)sdev);
+ /* save the sdev as private data */
+ platform_set_drvdata(pdev,sdev);
+
+ /* dma device register */
+ ret = dma_async_device_register(&sdev->dma_dev);
+ if (ret < 0) {
+ pr_err("SPRD-DMA: failed to register slave DMA engine device: %d\n",ret);
+ goto register_fail;
+ }
+
+
+ /* Device-tree DMA controller registration */
+ sprd_dma_info.dma_cap = sdev->dma_dev.cap_mask;
+ ret = of_dma_controller_register(pdev->dev.of_node,
+ of_dma_simple_xlate, &sprd_dma_info);
+ if (ret) {
+ pr_warn("OMAP-DMA: failed to register of DMA controller\n");
+ goto of_register_fail;
+ }
+
+ sema_init(&dma_cfg_group.cfg_sema,1);
+ dma_cfg_group.dma_cfg_cnt = 0;
+ memset(dma_cfg_group.dma_cfg,0,sizeof(struct sprd_dma_cfg) * DMA_CFG_COUNT);
+ pr_notice("SPRD DMA engine driver probe OK!\n");
+ return 0;
+
+of_register_fail:
+ dma_async_device_unregister(&sdev->dma_dev);
+register_fail:
+ if(aon_chn_cnt > 0){
+ devm_free_irq(&pdev->dev, aon_dma_irq, (void*)sdev);
+ irq_dispose_mapping(sdev->aon_irq);
+ }
+aon_irq_fail:
+ devm_free_irq(&pdev->dev, dma_irq, (void*)sdev);
+ irq_dispose_mapping(sdev->irq);
+irq_fail:
+ kmem_cache_destroy(sdev->dma_desc_node_cachep);
+ return ret;
+}
+
+/* remove method */
+static int sprd_dma_remove(struct platform_device *pdev)
+{
+ struct sprd_dma_dev *sdev = platform_get_drvdata(pdev);
+
+ dma_async_device_unregister(&sdev->dma_dev);
+ devm_free_irq(&pdev->dev,sdev->irq,(void*)sdev);
+ irq_dispose_mapping(sdev->irq);
+ if(sdev->aon_chn_cnt > 0){
+ devm_free_irq(&pdev->dev,sdev->aon_irq,(void*)sdev);
+ irq_dispose_mapping(sdev->aon_irq);
+ }
+ kmem_cache_destroy(sdev->dma_desc_node_cachep);
+ pr_notice("SPRD DMA engine driver remove OK!\n");
+
+ return 0;
+}
+
+static const struct of_device_id sprd_dma_match[] = {
+ { .compatible = "sprd,sharkl64-dma",
+ },
+ {},
+};
+
+static struct platform_driver sprd_dma_driver = {
+ .probe = sprd_dma_probe,
+ .remove = sprd_dma_remove,
+ .driver = {
+ .name = "sprd_dma",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(sprd_dma_match),
+ },
+};
+
+int __init sprd_dma_init(void)
+{
+ return platform_driver_register(&sprd_dma_driver);
+}
+
+void __exit sprd_dma_exit(void)
+{
+ platform_driver_unregister(&sprd_dma_driver);
+}
+
+subsys_initcall(sprd_dma_init);
+module_exit(sprd_dma_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Baolin.wang <Baolin.wang@spreadtrum.com>");