[PATCH] dmaengine: Add hisilicon k3 DMA engine driver
Vinod Koul
vinod.koul at intel.com
Fri Jun 21 20:40:36 EST 2013
On Mon, Jun 17, 2013 at 12:54:32PM +0800, Zhangfei Gao wrote:
> Add dmaengine driver for hisilicon k3 platform based on virt_dma
>
> Signed-off-by: Zhangfei Gao <zhangfei.gao at linaro.org>
> Tested-by: Kai Yang <jean.yangkai at huawei.com>
> ---
[snip]
> +#define to_k3_dma(dmadev) container_of(dmadev, struct k3_dma_dev, slave)
> +
> +static struct k3_dma_chan *to_k3_chan(struct dma_chan *chan)
> +{
> + return container_of(chan, struct k3_dma_chan, vc.chan);
> +}
> +
> +static void terminate_chan(struct k3_dma_phy *phy, struct k3_dma_dev *d)
namespace pls
> +{
> + u32 val = 0;
> +
> + val = readl_relaxed(phy->base + CX_CONFIG);
> + val &= ~CCFG_EN;
> + writel_relaxed(val, phy->base + CX_CONFIG);
> +
> + val = 0x1 << phy->idx;
> + writel_relaxed(val, d->base + INT_TC1_RAW);
> + writel_relaxed(val, d->base + INT_ERR1_RAW);
> + writel_relaxed(val, d->base + INT_ERR2_RAW);
> +}
> +
> +static void set_desc(struct k3_dma_phy *phy, struct k3_desc_hw *hw)
namespace pls
> +{
> + writel_relaxed(hw->lli, phy->base + CX_LLI);
> + writel_relaxed(hw->count, phy->base + CX_CNT);
> + writel_relaxed(hw->saddr, phy->base + CX_SRC);
> + writel_relaxed(hw->daddr, phy->base + CX_DST);
> + writel_relaxed(hw->config, phy->base + CX_CONFIG);
> +}
> +
> +static u32 get_curr_cnt(struct k3_dma_dev *d, struct k3_dma_phy *phy)
ditto
> +{
> + u32 cnt = 0;
> +
> + cnt = readl_relaxed(d->base + CX_CUR_CNT + phy->idx * 0x10);
> + cnt &= 0xffff;
> + return cnt;
> +}
> +
> +static u32 get_curr_lli(struct k3_dma_phy *phy)
> +{
> + return readl_relaxed(phy->base + CX_LLI);
> +}
> +
> +static u32 get_chan_stat(struct k3_dma_dev *d)
> +{
> + return readl_relaxed(d->base + CH_STAT);
> +}
> +
> +static void trigger_dma(struct k3_dma_dev *d, bool on)
ditto
> +{
> + 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_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 + INT_ERR1_MASK);
> + writel_relaxed(0x0, d->base + INT_ERR2_MASK);
> + }
> +}
> +
> +static irqreturn_t k3_dma_int_handler(int irq, void *dev_id)
> +{
> + struct k3_dma_dev *d = (struct k3_dma_dev *)dev_id;
> + struct k3_dma_phy *p;
> + u32 stat = readl_relaxed(d->base + INT_STAT);
> + u32 tc1 = readl_relaxed(d->base + INT_TC1);
> + u32 err1 = readl_relaxed(d->base + INT_ERR1);
> + u32 err2 = readl_relaxed(d->base + INT_ERR2);
> + u32 i, irq_chan = 0;
> +
> + while (stat) {
> + i = __ffs(stat);
> + stat &= (stat - 1);
> + if (likely(tc1 & BIT(i))) {
> + p = &d->phy[i];
> + p->ds_done = p->ds_run;
> + vchan_cookie_complete(&p->ds_run->vd);
> + irq_chan |= BIT(i);
> + }
> + if (unlikely((err1 & BIT(i)) || (err2 & BIT(i))))
> + dev_warn(d->slave.dev, "DMA ERR\n");
> + }
> +
> + writel_relaxed(irq_chan, d->base + INT_TC1_RAW);
> + writel_relaxed(err1, d->base + INT_ERR1_RAW);
> + writel_relaxed(err2, d->base + INT_ERR2_RAW);
> +
> + if (irq_chan) {
> + tasklet_schedule(&d->task);
> + return IRQ_HANDLED;
> + } else
> + return IRQ_NONE;
> +}
> +
> +static int k3_dma_start_txd(struct k3_dma_chan *c)
> +{
> + struct k3_dma_dev *d = to_k3_dma(c->vc.chan.device);
> + struct virt_dma_desc *vd = vchan_next_desc(&c->vc);
> +
> + if (BIT(c->phy->idx) & get_chan_stat(d))
> + return -EAGAIN;
> +
> + if (vd) {
> + struct k3_dma_desc_sw *ds =
> + container_of(vd, struct k3_dma_desc_sw, vd);
> + /*
> + * fetch and remove request from vc->desc_issued
> + * so vc->desc_issued only contains desc pending
> + */
> + list_del(&ds->vd.node);
> + c->phy->ds_run = ds;
> + c->phy->ds_done = NULL;
> + /* start dma */
> + set_desc(c->phy, &ds->desc_hw[0]);
> + return 0;
> + }
> + c->phy->ds_done = NULL;
> + c->phy->ds_run = NULL;
> + return -EAGAIN;
> +}
> +
> +static void k3_dma_tasklet(unsigned long arg)
> +{
> + struct k3_dma_dev *d = (struct k3_dma_dev *)arg;
> + struct k3_dma_phy *p;
> + struct k3_dma_chan *c;
> + unsigned pch, pch_alloc = 0;
> +
> + dev_dbg(d->slave.dev, "tasklet enter\n");
> +
> + /* check new dma request of running channel in vc->desc_issued */
> + list_for_each_entry(c, &d->slave.channels, vc.chan.device_node) {
this should use _safe, you might be adding a new txn while executing this
> + spin_lock_irq(&c->vc.lock);
> + p = c->phy;
> + if (p && p->ds_done) {
> + if (k3_dma_start_txd(c)) {
> + /* No current txd associated with this channel */
> + dev_dbg(d->slave.dev, "pchan %u: free\n", p->idx);
> + /* Mark this channel free */
> + c->phy = NULL;
> + p->vchan = NULL;
> + }
> + }
> + spin_unlock_irq(&c->vc.lock);
> + }
> +
> + /* check new channel request in d->chan_pending */
> + spin_lock_irq(&d->lock);
> + for (pch = 0; pch < NR_PHY_CHAN; pch++) {
> + p = &d->phy[pch];
> +
> + if (p->vchan == NULL && !list_empty(&d->chan_pending)) {
> + c = list_first_entry(&d->chan_pending,
> + struct k3_dma_chan, node);
> + /* remove from d->chan_pending */
> + list_del_init(&c->node);
> +
> + pch_alloc |= 1 << pch;
> +
> + /* Mark this channel allocated */
> + p->vchan = c;
> + dev_dbg(d->slave.dev, "pchan %u: alloc vchan %p\n", pch, &c->vc);
> + }
> + }
> + spin_unlock_irq(&d->lock);
> +
> + for (pch = 0; pch < NR_PHY_CHAN; pch++) {
> + if (pch_alloc & (1 << pch)) {
> + p = &d->phy[pch];
> + c = p->vchan;
> + spin_lock_irq(&c->vc.lock);
> + c->phy = p;
> + k3_dma_start_txd(c);
> + spin_unlock_irq(&c->vc.lock);
> + }
> + }
> +
> + dev_dbg(d->slave.dev, "tasklet exit\n");
> +}
> +
> +static int k3_dma_alloc_chan_resources(struct dma_chan *chan)
> +{
> + return 0;
> +}
> +
> +static void k3_dma_free_chan_resources(struct dma_chan *chan)
> +{
> + struct k3_dma_chan *c = to_k3_chan(chan);
> + struct k3_dma_dev *d = to_k3_dma(chan->device);
> + unsigned long flags;
> +
> + spin_lock_irqsave(&d->lock, flags);
> + list_del_init(&c->node);
> + spin_unlock_irqrestore(&d->lock, flags);
> +
> + vchan_free_chan_resources(&c->vc);
> + c->ccfg = 0;
> +}
> +
> +static enum dma_status k3_dma_tx_status(struct dma_chan *chan,
> + dma_cookie_t cookie, struct dma_tx_state *state)
> +{
> + struct k3_dma_chan *c = to_k3_chan(chan);
> + struct k3_dma_dev *d = to_k3_dma(chan->device);
> + struct k3_dma_phy *p;
> + struct virt_dma_desc *vd;
> + unsigned long flags;
> + enum dma_status ret;
> + size_t bytes = 0;
> +
> + ret = dma_cookie_status(&c->vc.chan, cookie, state);
> + if (ret == DMA_SUCCESS)
> + return ret;
> +
> + spin_lock_irqsave(&c->vc.lock, flags);
> + p = c->phy;
> +
> + /*
> + * If the cookie is on our issue queue, then the residue is
> + * its total size.
> + */
> + vd = vchan_find_desc(&c->vc, cookie);
> + if (vd) {
> + bytes = container_of(vd, struct k3_dma_desc_sw, vd)->size;
> + } else if ((!p) || (!p->ds_run)) {
> + bytes = 0;
> + } else {
> + struct k3_dma_desc_sw *ds = p->ds_run;
> + u32 clli = 0, index = 0;
> +
> + bytes = get_curr_cnt(d, p);
> + clli = get_curr_lli(p);
> + index = (clli - ds->desc_hw_lli) / sizeof(struct k3_desc_hw);
> + for (; index < LLI_MAX_NUM; index++) {
> + bytes += ds->desc_hw[index].count;
> + /* end of lli */
> + if (!ds->desc_hw[index].lli)
> + break;
> + }
> + }
> + spin_unlock_irqrestore(&c->vc.lock, flags);
> + dma_set_residue(state, bytes);
> + return ret;
> +}
> +
> +static void k3_dma_issue_pending(struct dma_chan *chan)
> +{
> + struct k3_dma_chan *c = to_k3_chan(chan);
> + struct k3_dma_dev *d = to_k3_dma(chan->device);
> + unsigned long flags;
> +
> + spin_lock_irqsave(&c->vc.lock, flags);
> + /* add request to vc->desc_issued */
> + if (vchan_issue_pending(&c->vc)) {
> + if (!c->phy) {
> + spin_lock(&d->lock);
> + if (list_empty(&c->node)) {
> + /* if new channel, add chan_pending */
> + list_add_tail(&c->node, &d->chan_pending);
> + /* check in tasklet */
> + tasklet_schedule(&d->task);
> + dev_dbg(d->slave.dev, "vchan %p: issued\n", &c->vc);
> + }
> + spin_unlock(&d->lock);
> + }
> + } else
> + dev_dbg(d->slave.dev, "vchan %p: nothing to issue\n", &c->vc);
> + spin_unlock_irqrestore(&c->vc.lock, flags);
> +}
> +
> +static void k3_fill_desc(struct k3_dma_desc_sw *ds, dma_addr_t dst,
> + dma_addr_t src, size_t len, u32 num, u32 ccfg)
> +{
> + BUG_ON(num >= LLI_MAX_NUM);
> +
> + ds->desc_hw[num].lli = ds->desc_hw_lli + (num + 1) *
> + sizeof(struct k3_desc_hw);
> + ds->desc_hw[num].lli |= CX_LLI_CHAIN_EN;
> + ds->desc_hw[num].count = len;
> + ds->desc_hw[num].saddr = src;
> + ds->desc_hw[num].daddr = dst;
> + ds->desc_hw[num].config = ccfg;
> +}
> +
> +static struct dma_async_tx_descriptor *k3_dma_prep_memcpy(
> + struct dma_chan *chan, dma_addr_t dst, dma_addr_t src,
> + size_t len, unsigned long flags)
> +{
> + struct k3_dma_chan *c = to_k3_chan(chan);
> + struct k3_dma_dev *d = to_k3_dma(chan->device);
> + struct k3_dma_desc_sw *ds;
> + size_t copy = 0;
> + int num_desc = 0;
> +
> + if (!len)
> + return NULL;
> +
> + ds = kzalloc(sizeof(struct k3_dma_desc_sw), GFP_NOWAIT);
sizeof (* ds) would be a better approach
> + if (!ds) {
> + dev_dbg(chan->device->dev, "vchan %p: kzalloc fail\n", &c->vc);
> + return NULL;
> + }
> +
> + ds->desc_hw = dma_pool_alloc(d->pool, GFP_NOWAIT, &ds->desc_hw_lli);
> + if (!ds->desc_hw) {
> + kfree(ds);
> + dev_dbg(chan->device->dev, "vchan %p: poolalloc fail\n", &c->vc);
> + return NULL;
> + }
> + ds->size = len;
> +
> + if (!c->ccfg) {
> + /* default is memtomem, without calling device_control */
> + c->ccfg = CCFG_SRCINCR | CCFG_DSTINCR | CCFG_EN;
> + c->ccfg |= (0xf << 20) | (0xf << 24); /* burst = 16 */
> + c->ccfg |= (0x3 << 12) | (0x3 << 16); /* width = 64 bit */
> + }
> +
> + do {
> + copy = min_t(size_t, len, DMA_MAX_SIZE);
> + k3_fill_desc(ds, dst, src, copy, num_desc++, c->ccfg);
> +
> + if (c->dir == DMA_MEM_TO_DEV) {
> + src += copy;
> + } else if (c->dir == DMA_DEV_TO_MEM) {
> + dst += copy;
> + } else {
> + src += copy;
> + dst += copy;
> + }
> + len -= copy;
> + } while (len);
> +
> + /* end of link */
> + ds->desc_hw[num_desc-1].lli = 0;
> + return vchan_tx_prep(&c->vc, &ds->vd, flags);
> +}
> +
> +static struct dma_async_tx_descriptor *k3_dma_prep_slave_sg(
> + struct dma_chan *chan, struct scatterlist *sgl, unsigned int sglen,
> + enum dma_transfer_direction dir, unsigned long flags, void *context)
> +{
> + struct k3_dma_chan *c = to_k3_chan(chan);
> + struct k3_dma_dev *d = to_k3_dma(chan->device);
> + struct k3_dma_desc_sw *ds;
> + size_t len, avail, total = 0;
> + struct scatterlist *sg;
> + dma_addr_t addr, src = 0, dst = 0;
> + int num_desc = 0, i;
> +
> + if (sgl == 0)
> + return NULL;
> +
> + ds = kzalloc(sizeof(struct k3_dma_desc_sw), GFP_NOWAIT);
ditto
> + if (!ds) {
> + dev_dbg(chan->device->dev, "vchan %p: kzalloc fail\n", &c->vc);
> + return NULL;
> + }
> +
> + ds->desc_hw = dma_pool_alloc(d->pool, GFP_NOWAIT, &ds->desc_hw_lli);
> + if (!ds->desc_hw) {
> + kfree(ds);
> + dev_dbg(chan->device->dev, "vchan %p: poolalloc fail\n", &c->vc);
> + return NULL;
> + }
> +
> + for_each_sg(sgl, sg, sglen, i) {
> + addr = sg_dma_address(sg);
> + avail = sg_dma_len(sg);
> + total += avail;
> +
> + do {
> + len = min_t(size_t, avail, DMA_MAX_SIZE);
> +
> + if (dir == DMA_MEM_TO_DEV) {
> + src = addr;
> + dst = c->dev_addr;
> + } else if (dir == DMA_DEV_TO_MEM) {
> + src = c->dev_addr;
> + dst = addr;
> + }
> +
> + k3_fill_desc(ds, dst, src, len, num_desc++, c->ccfg);
> +
> + addr += len;
> + avail -= len;
> + } while (avail);
> + }
> +
> + /* end of link */
> + ds->desc_hw[num_desc-1].lli = 0;
> + ds->size = total;
> + return vchan_tx_prep(&c->vc, &ds->vd, flags);
> +}
> +
> +static int k3_dma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
> + unsigned long arg)
> +{
> + struct k3_dma_chan *c = to_k3_chan(chan);
> + struct k3_dma_dev *d = to_k3_dma(chan->device);
> + struct dma_slave_config *cfg = (void *)arg;
> + struct k3_dma_phy *p = NULL;
> + unsigned long flags;
> + u32 maxburst = 0, val = 0;
> + enum dma_slave_buswidth width = DMA_SLAVE_BUSWIDTH_UNDEFINED;
> + LIST_HEAD(head);
> +
> + switch (cmd) {
> + case DMA_SLAVE_CONFIG:
> + if (cfg == NULL)
> + return -EINVAL;
> + c->dir = cfg->direction;
> + if (c->dir == DMA_DEV_TO_MEM) {
> + c->ccfg = CCFG_DSTINCR;
> + c->dev_addr = cfg->src_addr;
> + maxburst = cfg->src_maxburst;
> + width = cfg->src_addr_width;
> + } else if (c->dir == DMA_MEM_TO_DEV) {
> + c->ccfg = CCFG_SRCINCR;
> + c->dev_addr = cfg->dst_addr;
> + maxburst = cfg->dst_maxburst;
> + width = cfg->dst_addr_width;
> + }
looks like this could use some empty line above
> +
> + if (width == DMA_SLAVE_BUSWIDTH_1_BYTE)
> + val = 0;
> + else if (width == DMA_SLAVE_BUSWIDTH_2_BYTES)
> + val = 1;
> + else if (width == DMA_SLAVE_BUSWIDTH_4_BYTES)
> + val = 2;
> + else if (width == DMA_SLAVE_BUSWIDTH_8_BYTES)
> + val = 3;
and perhpas a switch case here or better a get_width macro
> + c->ccfg |= (val << 12) | (val << 16);
> +
> + if ((maxburst == 0) || (maxburst > 16))
> + val = 16;
> + else
> + val = maxburst - 1;
> + c->ccfg |= (val << 20) | (val << 24);
> + c->ccfg |= CCFG_MEM2PER | CCFG_EN;
> +
> + /* specific request line */
> + c->ccfg |= c->vc.chan.chan_id << 4;
> + break;
> +
> + case DMA_TERMINATE_ALL:
> + dev_dbg(d->slave.dev, "vchan %p: terminate all\n", &c->vc);
> + /* Clear the tx descriptor lists */
> + spin_lock_irqsave(&c->vc.lock, flags);
> + vchan_get_all_descriptors(&c->vc, &head);
> + if (c)
> + p = c->phy;
> + if (p) {
> + /* vchan is assigned to a pchan - stop the channel */
> + terminate_chan(p, d);
> + c->phy = NULL;
> + p->vchan = NULL;
> + p->ds_run = p->ds_done = NULL;
> + tasklet_schedule(&d->task);
> + }
> + spin_unlock_irqrestore(&c->vc.lock, flags);
> + vchan_dma_desc_free_list(&c->vc, &head);
> + break;
> + default:
> + return -ENXIO;
> + }
> + return 0;
> +}
> +
> +static void k3_dma_free_desc(struct virt_dma_desc *vd)
> +{
> + struct k3_dma_desc_sw *ds =
> + container_of(vd, struct k3_dma_desc_sw, vd);
> + struct k3_dma_chan *c = to_k3_chan(vd->tx.chan);
> + struct k3_dma_dev *d = to_k3_dma(c->vc.chan.device);
> +
> + if (ds->desc_hw)
> + dma_pool_free(d->pool, ds->desc_hw, ds->desc_hw_lli);
> +
> + kfree(ds);
> +}
> +
> +static struct of_device_id k3_pdma_dt_ids[] = {
> + { .compatible = "hisilicon,k3-dma-1.0", },
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, k3_pdma_dt_ids);
> +
> +static struct of_dma_filter_info k3_dma_filter;
> +static bool k3_dma_filter_fn(struct dma_chan *chan, void *param)
> +{
> + return (*(int *)param == chan->chan_id);
> +}
> +
> +static int k3_dma_probe(struct platform_device *op)
> +{
> + struct k3_dma_dev *d;
> + const struct of_device_id *of_id;
> + struct resource *iores;
> + int i, ret, irq = 0;
> + int dma_channels = 0;
> +
> + iores = platform_get_resource(op, IORESOURCE_MEM, 0);
> + if (!iores)
> + return -EINVAL;
> +
> + d = devm_kzalloc(&op->dev, sizeof(*d), GFP_KERNEL);
> + if (!d)
> + return -ENOMEM;
> +
> + d->base = devm_request_and_ioremap(&op->dev, iores);
> + if (!d->base)
> + return -EADDRNOTAVAIL;
> +
> + of_id = of_match_device(k3_pdma_dt_ids, &op->dev);
> + if (of_id)
> + of_property_read_u32((&op->dev)->of_node,
> + "dma-channels", &dma_channels);
> +
> + d->clk = devm_clk_get(&op->dev, NULL);
> + if (IS_ERR(d->clk)) {
> + dev_err(&op->dev, "no dma clk\n");
> + return PTR_ERR(d->clk);
> + }
> +
> + irq = platform_get_irq(op, 0);
> + ret = devm_request_irq(&op->dev, irq,
> + k3_dma_int_handler, IRQF_DISABLED, DRIVER_NAME, d);
> + if (ret)
> + return ret;
> +
> + /* init phy channel */
> + for (i = 0; i < NR_PHY_CHAN; i++) {
> + struct k3_dma_phy *p = &d->phy[i];
> +
> + p->idx = i;
> + p->base = d->base + i * 0x40;
> + }
> +
> + INIT_LIST_HEAD(&d->slave.channels);
> + dma_cap_set(DMA_SLAVE, d->slave.cap_mask);
> + dma_cap_set(DMA_MEMCPY, d->slave.cap_mask);
> + dma_cap_set(DMA_SLAVE, d->slave.cap_mask);
DMA_SLAVE set twice?
> + d->slave.dev = &op->dev;
> + d->slave.device_alloc_chan_resources = k3_dma_alloc_chan_resources;
> + d->slave.device_free_chan_resources = k3_dma_free_chan_resources;
> + d->slave.device_tx_status = k3_dma_tx_status;
> + d->slave.device_prep_dma_memcpy = k3_dma_prep_memcpy;
> + d->slave.device_prep_slave_sg = k3_dma_prep_slave_sg;
> + d->slave.device_issue_pending = k3_dma_issue_pending;
> + d->slave.device_control = k3_dma_control;
> + d->slave.copy_align = DMA_ALIGN;
> + d->slave.chancnt = dma_channels;
> +
> + /* init virtual channel */
> + for (i = 0; i < dma_channels; i++) {
> + struct k3_dma_chan *c;
> +
> + c = devm_kzalloc(&op->dev,
> + sizeof(struct k3_dma_chan), GFP_KERNEL);
> + if (c == NULL)
> + return -ENOMEM;
> +
> + INIT_LIST_HEAD(&c->node);
> + c->vc.desc_free = k3_dma_free_desc;
> + vchan_init(&c->vc, &d->slave);
> + }
> +
> + /* Enable clock before accessing registers */
> + ret = clk_prepare_enable(d->clk);
> + if (ret < 0) {
> + dev_err(&op->dev, "clk_prepare_enable failed: %d\n", ret);
> + return -EINVAL;
> + }
> +
> + trigger_dma(d, true);
is this turning on dma, if so why at probe?
> +
> + /* A DMA memory pool for LLIs */
> + d->pool = dma_pool_create(DRIVER_NAME, &op->dev,
> + LLI_SIZE, __alignof__(struct k3_desc_hw), 0);
> + if (!d->pool)
> + return -ENOMEM;
> +
> + ret = dma_async_device_register(&d->slave);
> + if (ret)
> + goto of_dma_register_fail;
> +
> + k3_dma_filter.dma_cap = d->slave.cap_mask;
> + k3_dma_filter.filter_fn = k3_dma_filter_fn;
> + ret = of_dma_controller_register((&op->dev)->of_node, of_dma_simple_xlate, &k3_dma_filter);
> + if (ret)
> + goto dma_async_regitster_fail;
> +
> + spin_lock_init(&d->lock);
> + INIT_LIST_HEAD(&d->chan_pending);
> + tasklet_init(&d->task, k3_dma_tasklet, (unsigned long)d);
> + platform_set_drvdata(op, d);
> + dev_info(&op->dev, "initialized\n");
> +
> + return 0;
> +
> +of_dma_register_fail:
> + dma_async_device_unregister(&d->slave);
> +dma_async_regitster_fail:
> + dma_pool_destroy(d->pool);
> + return ret;
> +}
> +
> +static int k3_dma_remove(struct platform_device *op)
> +{
> + struct k3_dma_chan *c, *cn;
> + struct k3_dma_dev *d = platform_get_drvdata(op);
> +
> + dma_async_device_unregister(&d->slave);
> + of_dma_controller_free((&op->dev)->of_node);
> +
> + list_for_each_entry_safe(c, cn, &d->slave.channels, vc.chan.device_node) {
> + list_del(&c->vc.chan.device_node);
> + tasklet_kill(&c->vc.task);
> + }
> + tasklet_kill(&d->task);
> + dma_pool_destroy(d->pool);
> + clk_disable_unprepare(d->clk);
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
PM_SLEEP?
> +static int k3_dma_suspend(struct device *dev)
> +{
> + struct k3_dma_dev *d = dev_get_drvdata(dev);
> + u32 stat = 0;
> +
> + stat = get_chan_stat(d);
> + if (stat) {
> + dev_warn(d->slave.dev,
> + "chan %d is running fail to suspend\n", stat);
> + return -1;
> + }
> + trigger_dma(d, false);
> + clk_disable_unprepare(d->clk);
> + return 0;
> +}
> +
> +static int k3_dma_resume(struct device *dev)
> +{
> + struct k3_dma_dev *d = dev_get_drvdata(dev);
> + int ret = 0;
> +
> + ret = clk_prepare_enable(d->clk);
> + if (ret < 0) {
> + dev_err(d->slave.dev, "clk_prepare_enable failed: %d\n", ret);
> + return -EINVAL;
> + }
> + trigger_dma(d, true);
> + return 0;
> +}
> +#else
> +#define k3_dma_suspend NULL
> +#define k3_dma_resume NULL
> +#endif
you can use SET_SYSTEM_SLEEP_PM_OPS macro instead
> +
> +static const struct dev_pm_ops k3_dma_pm_ops = {
> + .suspend = k3_dma_suspend,
> + .resume = k3_dma_resume,
> +};
> +
> +static struct platform_driver k3_pdma_driver = {
> + .driver = {
> + .name = DRIVER_NAME,
> + .owner = THIS_MODULE,
> + .pm = &k3_dma_pm_ops,
> + .of_match_table = k3_pdma_dt_ids,
> + },
> + .probe = k3_dma_probe,
> + .remove = k3_dma_remove,
> +};
> +
> +module_platform_driver(k3_pdma_driver);
> +
> +MODULE_DESCRIPTION("Hisilicon k3 DMA Driver");
> +MODULE_LICENSE("GPL v2");
> --
> 1.7.9.5
>
--
~Vinod
More information about the devicetree-discuss
mailing list