1
0
This repository has been archived on 2025-07-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
orange_kernel/drivers/dma/adma-ky.c
2025-03-18 10:29:27 +08:00

697 lines
19 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2024 Ky X1x 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 = 0;
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 = "ky,x1-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 = "x1-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);