dmaengine: adma-spacemit: Add Add SpacemiT K1-X adma driver 18/316918/1
authorMichal Wilczynski <m.wilczynski@samsung.com>
Tue, 27 Aug 2024 15:19:18 +0000 (17:19 +0200)
committerMichal Wilczynski <m.wilczynski@samsung.com>
Thu, 29 Aug 2024 07:46:53 +0000 (09:46 +0200)
This driver is required for the HDMI to work correctly. Port it from the
vendor kernel [1].

[1] - https://github.com/BPI-SINOVOIP/pi-linux.git

Change-Id: Id0959b4ed4faca2b580d6f58bca61faa7cd06826
Signed-off-by: Michal Wilczynski <m.wilczynski@samsung.com>
drivers/dma/Kconfig
drivers/dma/Makefile
drivers/dma/adma-spacemit.c [new file with mode: 0644]

index 813cf87c9f03539dca5b8f88c405b0f9f138f9e8..8dc00549a0b17218fa97d30129167904a6323543 100644 (file)
@@ -423,6 +423,13 @@ config MMP_PDMA_SPACEMIT_K1X
        help
          Support the MMP PDMA engine for Spacemit-k1x platform.
 
+config ADMA_SPACEMIT_K1X
+       bool "Spacemit adma support"
+       depends on ARCH_SPACEMIT_K1X && RPMSG_VIRTIO
+       select DMA_ENGINE
+       help
+         Support the AMDA engine for Spacemit-k1x sspa.
+
 config MMP_TDMA
        tristate "MMP Two-Channel DMA support"
        depends on ARCH_MMP || COMPILE_TEST
index 33613aa18b1c8f0d2ef6f4b68f406a4e13bb84f2..cce677bed947df9018390d0616e7c777ed78bc13 100644 (file)
@@ -53,6 +53,7 @@ obj-$(CONFIG_MILBEAUT_XDMAC) += milbeaut-xdmac.o
 obj-$(CONFIG_MMP_PDMA) += mmp_pdma.o
 obj-$(CONFIG_MMP_TDMA) += mmp_tdma.o
 obj-$(CONFIG_MMP_PDMA_SPACEMIT_K1X) += mmp_pdma_k1x.o
+obj-$(CONFIG_ADMA_SPACEMIT_K1X) += adma-spacemit.o
 obj-$(CONFIG_MOXART_DMA) += moxart-dma.o
 obj-$(CONFIG_MPC512X_DMA) += mpc512x_dma.o
 obj-$(CONFIG_MV_XOR) += mv_xor.o
diff --git a/drivers/dma/adma-spacemit.c b/drivers/dma/adma-spacemit.c
new file mode 100644 (file)
index 0000000..4811649
--- /dev/null
@@ -0,0 +1,696 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2024 Spacemit K1x Adma Driver
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/dmaengine.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/dmapool.h>
+#include <linux/genalloc.h>
+#include <linux/of_device.h>
+#include <linux/of_dma.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include "dmaengine.h"
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/rpmsg.h>
+#include <linux/of_device.h>
+
+#define BCR 0x0
+#define SAR 0x10
+#define DAR 0x20
+#define NDR 0x30
+#define DCR 0x40
+#define IER 0x80
+#define ADMA_SAMPLE_BITS_MASK (0x7 << 22)
+#define ADMA_SAMPLE_BITS(x) (((x) << 22) & ADMA_SAMPLE_BITS_MASK)
+#define ADMA_CH_ABORT (1 << 20)
+#define ADMA_CLOSE_DESC_EN (1 << 17)
+#define ADMA_UNPACK_SAMPLES (1 << 16)
+#define ADMA_CH_ACTIVE (1 << 14)
+#define ADMA_FETCH_NEXT_DESC (1 << 13)
+#define ADMA_CH_EN (1 << 12)
+#define ADMA_INTRRUPT_MODE (1 << 10)
+
+#define ADMA_BURST_LIMIT_MASK (0x7 << 6)
+#define ADMA_BURST_LIMIT(x) (((x) << 6) & ADMA_BURST_LIMIT_MASK)
+
+#define ADMA_DEST_ADDR_DIR_MASK (0x3 << 4)
+#define ADMA_DEST_ADDR_INCREMENT (0x0 << 4)
+#define ADMA_DEST_ADDR_DECREMENT (0x1 << 4)
+#define ADMA_DEST_ADDR_HOLD (0x2 << 4)
+
+#define ADMA_SRC_ADDR_DIR_MASK (0x3 << 2)
+#define ADMA_SRC_ADDR_INCREMENT (0x0 << 2)
+#define ADMA_SRC_ADDR_DECREMENT (0x1 << 2)
+#define ADMA_SRC_ADDR_HOLD (0x2 << 2)
+
+/* current descriptor register */
+#define ADMA_CH_CUR_DESC_REG 0x70
+
+/* interrupt mask register */
+#define ADMA_CH_INTR_MASK_REG 0x80
+#define ADMA_FINISH_INTR_EN (0x1 << 0)
+
+/* interrupt status register */
+#define ADMA_CH_INTR_STATUS_REG 0xa0
+#define ADMA_FINISH_INTR_DONE (0x1 << 0)
+
+#define HDMI_ADMA 0x50
+#define HDMI_ENABLE (1 << 0)
+#define HDMI_DISABLE (0 << 0)
+
+#define DESC_BUF_BASE 0xc08d0000
+#define DESC_BUF_SIZE 0x400
+
+#define tx_to_adma_desc(tx) container_of(tx, struct adma_desc_sw, async_tx)
+#define to_adma_chan(dchan) container_of(dchan, struct adma_ch, chan)
+#define to_adma_dev(dmadev) container_of(dmadev, struct adma_dev, device)
+
+#define STARTUP_MSG "startup"
+#define STARTUP_OK_MSG "startup-ok"
+//#define DESC_BUFFER_ADDR
+
+enum {
+       AUDIO_SAMPLE_WORD_8BITS = 0x0,
+       AUDIO_SAMPLE_WORD_12BITS,
+       AUDIO_SAMPLE_WORD_16BITS,
+       AUDIO_SAMPLE_WORD_20BITS,
+       AUDIO_SAMPLE_WORD_24BITS,
+       AUDIO_SAMPLE_WORD_32BITS,
+};
+
+struct adma_desc_hw {
+       u32 byte_cnt;
+       u32 src_addr;
+       u32 dst_addr;
+       u32 nxt_desc;
+};
+
+struct adma_desc_sw {
+       struct adma_desc_hw desc;
+       struct list_head node;
+       struct list_head tx_list;
+       struct dma_async_tx_descriptor async_tx;
+};
+
+struct adma_pchan;
+
+struct adma_ch {
+       struct device *dev;
+       struct dma_chan chan;
+       struct dma_async_tx_descriptor desc;
+       struct adma_pchan *phy;
+       struct dma_slave_config slave_config;
+       enum dma_transfer_direction dir;
+       struct adma_desc_sw *cyclic_first;
+       bool unpack_sample;
+
+       struct tasklet_struct tasklet;
+       u32 dev_addr;
+
+       spinlock_t desc_lock;
+       struct list_head chain_pending;
+       struct list_head chain_running;
+       enum dma_status status;
+
+       struct gen_pool *desc_pool;
+};
+
+struct adma_pchan {
+       void __iomem *base;
+       void __iomem *ctrl_base;
+       struct adma_ch *vchan;
+};
+
+struct adma_dev {
+       int max_burst_size;
+       void __iomem *base;
+       void __iomem *ctrl_base;
+       void __iomem *desc_base;
+       struct dma_device device;
+       struct device *dev;
+       spinlock_t phy_lock;
+};
+
+static unsigned long long private_data[2];
+
+struct instance_data {
+       struct rpmsg_device *rpdev;
+       struct adma_ch *achan;
+};
+
+static void adma_ch_write_reg(struct adma_pchan *phy, u32 reg_offset, u32 value)
+{
+       writel(value, phy->base + reg_offset);
+}
+
+static u32 adma_ch_read_reg(struct adma_pchan *phy, u32 reg_offset)
+{
+       u32 val;
+       return val = readl(phy->base + reg_offset);
+}
+
+/*define adma-controller driver*/
+static dma_cookie_t adma_tx_submit(struct dma_async_tx_descriptor *tx)
+{
+       struct adma_ch *achan = to_adma_chan(tx->chan);
+       struct adma_desc_sw *desc = tx_to_adma_desc(tx);
+       struct adma_desc_sw *child;
+       unsigned long flags;
+       dma_cookie_t cookie = -EBUSY;
+
+       spin_lock_irqsave(&achan->desc_lock, flags);
+       list_for_each_entry(child, &desc->tx_list, node) {
+               cookie = dma_cookie_assign(&child->async_tx);
+       }
+
+       list_splice_tail_init(&desc->tx_list, &achan->chain_pending);
+       spin_unlock_irqrestore(&achan->desc_lock, flags);
+
+       return cookie;
+}
+
+static int adma_alloc_chan_resources(struct dma_chan *dchan)
+{
+       struct adma_ch *achan = to_adma_chan(dchan);
+       struct adma_dev *adev = to_adma_dev(achan->chan.device);
+       if (achan->desc_pool)
+               return 1;
+       achan->desc_pool = gen_pool_create(7, -1);
+       if (!achan->desc_pool) {
+               pr_err("unable to allocate descriptor pool\n");
+               return -ENOMEM;
+       }
+       if (gen_pool_add_virt(achan->desc_pool, (long)adev->desc_base,
+                             DESC_BUF_BASE, DESC_BUF_SIZE, -1) != 0) {
+               pr_err("gen_pool_add mem error!\n");
+               gen_pool_destroy(achan->desc_pool);
+               return -ENOMEM;
+       }
+
+       achan->status = DMA_COMPLETE;
+       achan->dir = 0;
+       achan->dev_addr = 0;
+       return 1;
+}
+
+static void adma_free_desc_list(struct adma_ch *chan, struct list_head *list)
+{
+       struct adma_desc_sw *desc, *_desc;
+
+       list_for_each_entry_safe(desc, _desc, list, node) {
+               list_del(&desc->node);
+               gen_pool_free(chan->desc_pool, (long)desc,
+                             sizeof(struct adma_desc_sw));
+       }
+}
+
+static void adma_free_chan_resources(struct dma_chan *dchan)
+{
+       struct adma_ch *achan = to_adma_chan(dchan);
+       struct adma_dev *adev = to_adma_dev(achan->chan.device);
+       unsigned long flags;
+
+       spin_lock_irqsave(&achan->desc_lock, flags);
+       adma_free_desc_list(achan, &achan->chain_pending);
+       adma_free_desc_list(achan, &achan->chain_running);
+       spin_unlock_irqrestore(&achan->desc_lock, flags);
+       gen_pool_destroy(achan->desc_pool);
+       achan->desc_pool = NULL;
+       achan->status = DMA_COMPLETE;
+       achan->dir = 0;
+       achan->dev_addr = 0;
+       spin_lock_irqsave(&adev->phy_lock, flags);
+       spin_unlock_irqrestore(&adev->phy_lock, flags);
+       return;
+}
+
+static struct adma_desc_sw *alloc_descriptor(struct adma_ch *achan)
+{
+       struct adma_desc_sw *desc;
+       dma_addr_t pdesc;
+
+       desc = (struct adma_desc_sw *)gen_pool_alloc(
+               achan->desc_pool, sizeof(struct adma_desc_sw));
+       if (!desc) {
+               dev_err(achan->dev, "out of memory for link descriptor\n");
+               return NULL;
+       }
+       memset(desc, 0, sizeof(struct adma_desc_sw));
+       pdesc = (dma_addr_t)gen_pool_virt_to_phys(achan->desc_pool, (long)desc);
+
+       INIT_LIST_HEAD(&desc->tx_list);
+       dma_async_tx_descriptor_init(&desc->async_tx, &achan->chan);
+       desc->async_tx.tx_submit = adma_tx_submit;
+       desc->async_tx.phys = pdesc;
+       return desc;
+}
+
+static struct dma_async_tx_descriptor *
+adma_prep_cyclic(struct dma_chan *dchan, dma_addr_t buf_addr, size_t len,
+                size_t period_len, enum dma_transfer_direction direction,
+                unsigned long flags)
+{
+       struct adma_ch *achan;
+       struct adma_desc_sw *first = NULL, *prev = NULL, *new;
+       dma_addr_t adma_src, adma_dst;
+
+       achan = to_adma_chan(dchan);
+
+       switch (direction) {
+       case DMA_MEM_TO_DEV:
+               adma_src = buf_addr & 0xffffffff;
+               achan->dev_addr = achan->slave_config.dst_addr;
+               adma_dst = achan->dev_addr;
+               break;
+       case DMA_DEV_TO_MEM:
+               adma_dst = buf_addr & 0xffffffff;
+               achan->dev_addr = achan->slave_config.src_addr;
+               adma_src = achan->dev_addr;
+               break;
+       default:
+               dev_err(achan->dev, "Unsupported direction for cyclic DMA\n");
+               return NULL;
+       }
+       achan->dir = direction;
+       do {
+               new = alloc_descriptor(achan);
+               if (!new) {
+                       dev_err(achan->dev, "no memory for desc\n");
+               }
+               new->desc.byte_cnt = period_len;
+               new->desc.src_addr = adma_src;
+               new->desc.dst_addr = adma_dst;
+               if (!first)
+                       first = new;
+               else
+                       prev->desc.nxt_desc = new->async_tx.phys;
+               new->async_tx.cookie = 0;
+               prev = new;
+               len -= period_len;
+
+               if (achan->dir == DMA_MEM_TO_DEV)
+                       adma_src += period_len;
+               else
+                       adma_dst += period_len;
+               list_add_tail(&new->node, &first->tx_list);
+       } while (len);
+
+       first->async_tx.flags = flags;
+       first->async_tx.cookie = -EBUSY;
+       new->desc.nxt_desc = first->async_tx.phys;
+       achan->cyclic_first = first;
+       return &first->async_tx;
+}
+
+static int adma_config(struct dma_chan *dchan, struct dma_slave_config *cfg)
+{
+       struct adma_ch *achan = to_adma_chan(dchan);
+
+       memcpy(&achan->slave_config, cfg, sizeof(*cfg));
+       return 0;
+}
+
+static void set_desc(struct adma_pchan *phy, dma_addr_t addr)
+{
+       adma_ch_write_reg(phy, NDR, addr);
+}
+
+static void set_ctrl_reg(struct adma_pchan *phy)
+{
+       u32 ctrl_reg_val;
+       u32 maxburst = 0, sample_bits = 0;
+       enum dma_slave_buswidth width = DMA_SLAVE_BUSWIDTH_UNDEFINED;
+       struct adma_ch *achan = phy->vchan;
+
+       if (achan->dir == DMA_MEM_TO_DEV) {
+               maxburst = achan->slave_config.dst_maxburst;
+               width = achan->slave_config.dst_addr_width;
+               ctrl_reg_val |= ADMA_DEST_ADDR_HOLD | ADMA_SRC_ADDR_INCREMENT;
+       } else if (achan->dir == DMA_DEV_TO_MEM) {
+               maxburst = achan->slave_config.src_maxburst;
+               width = achan->slave_config.src_addr_width;
+               ctrl_reg_val |= ADMA_SRC_ADDR_HOLD | ADMA_DEST_ADDR_INCREMENT;
+       } else
+               ctrl_reg_val |= ADMA_SRC_ADDR_HOLD | ADMA_DEST_ADDR_HOLD;
+
+       if (width == DMA_SLAVE_BUSWIDTH_1_BYTE)
+               sample_bits = AUDIO_SAMPLE_WORD_8BITS;
+       else if (width == DMA_SLAVE_BUSWIDTH_2_BYTES)
+               sample_bits = AUDIO_SAMPLE_WORD_16BITS;
+       else if (width == DMA_SLAVE_BUSWIDTH_3_BYTES)
+               sample_bits = AUDIO_SAMPLE_WORD_24BITS;
+       else if (width == DMA_SLAVE_BUSWIDTH_4_BYTES)
+               sample_bits = AUDIO_SAMPLE_WORD_32BITS;
+       ctrl_reg_val |= ADMA_SAMPLE_BITS(sample_bits);
+
+       /*no burst function information,default 0*/
+       ctrl_reg_val |= ADMA_BURST_LIMIT(0);
+       ctrl_reg_val |= ADMA_CH_ABORT;
+       if (achan->unpack_sample)
+               ctrl_reg_val |= ADMA_UNPACK_SAMPLES;
+       adma_ch_write_reg(phy, DCR, ctrl_reg_val);
+       if (!achan->unpack_sample)
+               writel(HDMI_ENABLE, phy->ctrl_base);
+}
+
+static void enable_chan(struct adma_pchan *phy)
+{
+       u32 ctrl_val;
+       struct adma_ch *achan = phy->vchan;
+
+       if (achan->dir == DMA_MEM_TO_DEV)
+               adma_ch_write_reg(phy, DAR, achan->dev_addr);
+       else if (achan->dir == DMA_DEV_TO_MEM)
+               adma_ch_write_reg(phy, SAR, achan->dev_addr);
+       adma_ch_write_reg(phy, IER, 1);
+       ctrl_val = adma_ch_read_reg(phy, DCR);
+       ctrl_val |= ADMA_FETCH_NEXT_DESC;
+       ctrl_val |= ADMA_CH_EN;
+       adma_ch_write_reg(phy, DCR, ctrl_val);
+}
+
+static void start_pending_queue(struct adma_ch *achan)
+{
+       struct adma_dev *adev = to_adma_dev(achan->chan.device);
+       struct adma_pchan *phy;
+       struct adma_desc_sw *desc;
+       unsigned long flags;
+
+       if (achan->status == DMA_IN_PROGRESS) {
+               dev_dbg(achan->dev, "DMA controller still busy\n");
+               return;
+       }
+       spin_lock_irqsave(&adev->phy_lock, flags);
+       phy = achan->phy;
+       desc = list_first_entry(&achan->chain_pending, struct adma_desc_sw,
+                               node);
+       list_splice_tail_init(&achan->chain_pending, &achan->chain_running);
+       set_desc(phy, desc->async_tx.phys);
+       set_ctrl_reg(phy);
+       enable_chan(phy);
+       spin_unlock_irqrestore(&adev->phy_lock, flags);
+       achan->status = DMA_IN_PROGRESS;
+}
+
+static void adma_issue_pending(struct dma_chan *dchan)
+{
+       struct adma_ch *achan = to_adma_chan(dchan);
+       unsigned long flags;
+
+       spin_lock_irqsave(&achan->desc_lock, flags);
+       start_pending_queue(achan);
+       spin_unlock_irqrestore(&achan->desc_lock, flags);
+}
+
+static enum dma_status adma_tx_status(struct dma_chan *dchan,
+                                     dma_cookie_t cookie,
+                                     struct dma_tx_state *txstate)
+{
+       /*struct adma_ch *chan = to_adma_chan(dchan);
+       enum dma_status ret;
+       unsigned long flags;
+       spin_lock_irqsave(&chan->desc_lock, flags);
+       ret = dma_cookie_status(dchan, cookie, txstate);
+       if (likely(ret != DMA_ERROR))
+               dma_set_residue(txstate, mmp_pdma_residue(chan, cookie));
+       spin_unlock_irqrestore(&chan->desc_lock, flags);
+       if (ret == DMA_COMPLETE)
+               return ret;
+       else
+               return chan->status;*/
+       return 0;
+}
+
+static void disable_chan(struct adma_pchan *phy)
+{
+       u32 reg_val = adma_ch_read_reg(phy, DCR);
+       reg_val |= ADMA_CH_ABORT;
+       adma_ch_write_reg(phy, DCR, reg_val);
+
+       udelay(500);
+       reg_val = adma_ch_read_reg(phy, DCR);
+       reg_val &= ~ADMA_CH_EN;
+       adma_ch_write_reg(phy, DCR, reg_val);
+       adma_ch_write_reg(phy, IER, 0);
+       if ((!phy->vchan->unpack_sample) &&
+           ((readl(phy->ctrl_base) & HDMI_ENABLE) == 0x1))
+               writel(HDMI_DISABLE, phy->ctrl_base);
+}
+
+static int adma_terminate_all(struct dma_chan *dchan)
+{
+       struct adma_ch *achan = to_adma_chan(dchan);
+       struct adma_dev *adev = to_adma_dev(achan->chan.device);
+       unsigned long flags;
+
+       spin_lock_irqsave(&achan->desc_lock, flags);
+       disable_chan(achan->phy);
+       achan->status = DMA_COMPLETE;
+       spin_lock_irqsave(&adev->phy_lock, flags);
+       spin_unlock_irqrestore(&adev->phy_lock, flags);
+
+       adma_free_desc_list(achan, &achan->chain_pending);
+       adma_free_desc_list(achan, &achan->chain_running);
+       //achan->bytes_residue = 0;
+
+       spin_unlock_irqrestore(&achan->desc_lock, flags);
+       return 0;
+}
+
+static struct dma_chan *adma_dma_xlate(struct of_phandle_args *dma_spec,
+                                      struct of_dma *ofdma)
+{
+       struct adma_dev *d = ofdma->of_dma_data;
+       struct dma_chan *chan;
+
+       chan = dma_get_any_slave_channel(&d->device);
+       if (!chan)
+               return NULL;
+       return chan;
+}
+
+static const struct of_device_id adma_id_table[] = {
+       { .compatible = "spacemit,k1x-adma", .data = (void *)&private_data[0] },
+       {},
+};
+
+static int adma_probe(struct platform_device *pdev)
+{
+       struct adma_dev *adev;
+       struct device *dev;
+       const struct of_device_id *of_id;
+       struct rpmsg_device *rpdev;
+       struct instance_data *idata;
+       struct adma_pchan *phy;
+       struct adma_ch *achan;
+       int ret;
+       const enum dma_slave_buswidth widths =
+               DMA_SLAVE_BUSWIDTH_1_BYTE | DMA_SLAVE_BUSWIDTH_2_BYTES |
+               DMA_SLAVE_BUSWIDTH_3_BYTES | DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+       of_id = of_match_device(adma_id_table, &pdev->dev);
+       if (!of_id) {
+               pr_err("Unable to match OF ID\n");
+               return -ENODEV;
+       }
+       idata = (struct instance_data *)((
+               unsigned long long *)(of_id->data))[0];
+       rpdev = idata->rpdev;
+       ret = rpmsg_send(rpdev->ept, STARTUP_MSG, strlen(STARTUP_MSG));
+       if (ret) {
+               dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
+               return ret;
+       }
+
+       /*get controller dts info*/
+       dev = &pdev->dev;
+       adev = devm_kzalloc(dev, sizeof(*adev), GFP_KERNEL);
+       adev->dev = dev;
+       adev->base = devm_platform_ioremap_resource_byname(pdev, "adma_reg");
+       if (IS_ERR(adev->base))
+               return PTR_ERR(adev->base);
+       adev->ctrl_base =
+               devm_platform_ioremap_resource_byname(pdev, "ctrl_reg");
+       if (IS_ERR(adev->ctrl_base))
+               return PTR_ERR(adev->ctrl_base);
+       adev->desc_base =
+               devm_platform_ioremap_resource_byname(pdev, "buf_addr");
+       if (IS_ERR(adev->desc_base))
+               return PTR_ERR(adev->desc_base);
+       /*if(of_property_read_u32(pdev->dev->of_node, "max-burst-size", &adev->max_burst_size))
+               adev->max_burst_size = DEFAULT_MAX_BURST_SIZE;*/
+
+       /*init adma-chan*/
+       INIT_LIST_HEAD(&adev->device.channels);
+       achan = devm_kzalloc(dev, sizeof(struct adma_ch), GFP_KERNEL);
+       if (achan == NULL)
+               return -ENOMEM;
+       phy = devm_kzalloc(dev, sizeof(struct adma_pchan), GFP_KERNEL);
+       phy->base = adev->base;
+       phy->ctrl_base = adev->ctrl_base;
+       phy->vchan = achan;
+       achan->phy = phy;
+       achan->dev = adev->dev;
+       achan->chan.device = &adev->device;
+       spin_lock_init(&achan->desc_lock);
+       spin_lock_init(&adev->phy_lock);
+       INIT_LIST_HEAD(&achan->chain_pending);
+       INIT_LIST_HEAD(&achan->chain_running);
+       achan->status = DMA_COMPLETE;
+       achan->unpack_sample =
+               !of_property_read_bool(pdev->dev.of_node, "hdmi-sample");
+
+       /* register virt channel to dma engine */
+       list_add_tail(&achan->chan.device_node, &adev->device.channels);
+       idata->achan = achan;
+
+       dma_cap_set(DMA_SLAVE, adev->device.cap_mask);
+       dma_cap_set(DMA_CYCLIC, adev->device.cap_mask);
+       adev->device.dev = dev;
+       adev->device.device_tx_status = adma_tx_status;
+       adev->device.device_alloc_chan_resources = adma_alloc_chan_resources;
+       adev->device.device_free_chan_resources = adma_free_chan_resources;
+       adev->device.device_prep_dma_cyclic = adma_prep_cyclic;
+       adev->device.device_issue_pending = adma_issue_pending;
+       adev->device.device_config = adma_config;
+       adev->device.device_terminate_all = adma_terminate_all;
+       adev->device.copy_align = DMAENGINE_ALIGN_8_BYTES;
+       adev->device.src_addr_widths = widths;
+       adev->device.dst_addr_widths = widths;
+       adev->device.directions = BIT(DMA_MEM_TO_DEV) | BIT(DMA_DEV_TO_MEM);
+
+       dma_set_mask(adev->dev, adev->dev->coherent_dma_mask);
+
+       ret = dma_async_device_register(&adev->device);
+       if (ret) {
+               dev_err(adev->device.dev, "unable to register\n");
+               return ret;
+       }
+
+       if (pdev->dev.of_node) {
+               ret = of_dma_controller_register(pdev->dev.of_node,
+                                                adma_dma_xlate, adev);
+               if (ret < 0) {
+                       dev_err(dev, "of_dma_controller_register failed\n");
+                       dma_async_device_unregister(&adev->device);
+                       return ret;
+               }
+       }
+
+       platform_set_drvdata(pdev, adev);
+       return 0;
+}
+
+static int adma_remove(struct platform_device *pdev)
+{
+       struct adma_dev *adev = platform_get_drvdata(pdev);
+       ;
+
+       if (pdev->dev.of_node)
+               of_dma_controller_free(pdev->dev.of_node);
+       dma_async_device_unregister(&adev->device);
+       platform_set_drvdata(pdev, NULL);
+       return 0;
+}
+
+static struct platform_driver adma_driver = {
+       .driver = {
+               .name   = "k1x-adma",
+               .of_match_table = adma_id_table,
+       },
+       .probe  = adma_probe,
+       .remove = adma_remove,
+};
+
+static struct rpmsg_device_id rpmsg_driver_adma_id_table[] = {
+       { .name = "adma-service", .driver_data = 0 },
+       {},
+};
+MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_adma_id_table);
+
+static int rpmsg_adma_client_cb(struct rpmsg_device *rpdev, void *data, int len,
+                               void *priv, u32 src)
+{
+       struct instance_data *idata = dev_get_drvdata(&rpdev->dev);
+       struct adma_ch *chan = idata->achan;
+
+#if 0
+       if (strcmp(data, STARTUP_OK_MSG) == 0) {
+               dev_info(&rpdev->dev, "channel: 0x%x -> 0x%x startup ok!\n",
+                                       rpdev->src, rpdev->dst);        
+       }
+
+       if (strcmp(data, "#") == 0) {
+#endif
+       /* adma irq happend */
+       struct adma_desc_sw *desc;
+       LIST_HEAD(chain_cleanup);
+       unsigned long flags;
+       struct dmaengine_desc_callback cb;
+
+       spin_lock_irqsave(&chan->desc_lock, flags);
+       if (chan->status == DMA_COMPLETE) {
+               spin_unlock_irqrestore(&chan->desc_lock, flags);
+               return 0;
+       }
+       spin_unlock_irqrestore(&chan->desc_lock, flags);
+
+       spin_lock_irqsave(&chan->desc_lock, flags);
+       desc = chan->cyclic_first;
+       dmaengine_desc_get_callback(&desc->async_tx, &cb);
+       spin_unlock_irqrestore(&chan->desc_lock, flags);
+
+       dmaengine_desc_callback_invoke(&cb, NULL);
+       //      }
+
+       return 0;
+}
+
+static int rpmsg_adma_client_probe(struct rpmsg_device *rpdev)
+{
+       struct instance_data *idata;
+
+       dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n", rpdev->src,
+                rpdev->dst);
+
+       idata = devm_kzalloc(&rpdev->dev, sizeof(*idata), GFP_KERNEL);
+       if (!idata)
+               return -ENOMEM;
+
+       dev_set_drvdata(&rpdev->dev, idata);
+       idata->rpdev = rpdev;
+
+       ((unsigned long long *)(adma_id_table[0].data))[0] =
+               (unsigned long long)idata;
+
+       platform_driver_register(&adma_driver);
+
+       return 0;
+}
+
+static void rpmsg_adma_client_remove(struct rpmsg_device *rpdev)
+{
+       dev_info(&rpdev->dev, "rpmsg adma client driver is removed\n");
+       platform_driver_unregister(&adma_driver);
+}
+
+static struct rpmsg_driver rpmsg_adma_client = {
+       .drv.name = KBUILD_MODNAME,
+       .id_table = rpmsg_driver_adma_id_table,
+       .probe = rpmsg_adma_client_probe,
+       .callback = rpmsg_adma_client_cb,
+       .remove = rpmsg_adma_client_remove,
+};
+module_rpmsg_driver(rpmsg_adma_client);