// SPDX-License-Identifier: (GPL-2.0-only OR MIT) /* * Copyright (C) 2026 Amlogic, Inc. All rights reserved * Author: Xianwei Zhao */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "virt-dma.h" #define RCH_REG_BASE 0x0 #define WCH_REG_BASE 0x2000 /* * Each rch (read from memory) REG offset Rch_offset 0x0 each channel total 0x40 * rch addr = DMA_base + Rch_offset+ chan_id * 0x40 + reg_offset */ #define RCH_READY 0x0 #define RCH_STATUS 0x4 #define RCH_CFG 0x8 #define CFG_CLEAR BIT(25) #define CFG_PAUSE BIT(26) #define CFG_ENABLE BIT(27) #define CFG_DONE BIT(28) #define RCH_ADDR 0xc #define RCH_LEN 0x10 #define RCH_RD_LEN 0x14 #define RCH_PRT 0x18 #define RCH_SYCN_STAT 0x1c #define RCH_ADDR_LOW 0x20 #define RCH_ADDR_HIGH 0x24 /* if work on 64, it work with RCH_PRT */ #define RCH_PTR_HIGH 0x28 /* * Each wch (write to memory) REG offset Wch_offset 0x2000 each channel total 0x40 * wch addr = DMA_base + Wch_offset+ chan_id * 0x40 + reg_offset */ #define WCH_READY 0x0 #define WCH_TOTAL_LEN 0x4 #define WCH_CFG 0x8 #define WCH_ADDR 0xc #define WCH_LEN 0x10 #define WCH_RD_LEN 0x14 #define WCH_PRT 0x18 #define WCH_CMD_CNT 0x1c #define WCH_ADDR_LOW 0x20 #define WCH_ADDR_HIGH 0x24 /* if work on 64, it work with RCH_PRT */ #define WCH_PTR_HIGH 0x28 /* DMA controller reg */ #define RCH_INT_MASK 0x1000 #define WCH_INT_MASK 0x1004 #define CLEAR_W_BATCH 0x1014 #define CLEAR_RCH 0x1024 #define CLEAR_WCH 0x1028 #define RCH_ACTIVE 0x1038 #define WCH_ACTIVE 0x103c #define RCH_DONE 0x104c #define WCH_DONE 0x1050 #define RCH_ERR 0x1060 #define RCH_LEN_ERR 0x1064 #define WCH_ERR 0x1068 #define DMA_BATCH_END 0x1078 #define WCH_EOC_DONE 0x1088 #define WDMA_RESP_ERR 0x1098 #define UPT_PKT_SYNC 0x10a8 #define RCHN_CFG 0x10ac #define WCHN_CFG 0x10b0 #define MEM_PD_CFG 0x10b4 #define MEM_BUS_CFG 0x10b8 #define DMA_GMV_CFG 0x10bc #define DMA_GMR_CFG 0x10c0 #define MAX_CHAN_ID 32 #define SG_MAX_LEN GENMASK(26, 2) #define INT_MASK 0xffffffff struct aml_dma_sg_link { #define LINK_LEN GENMASK(26, 0) #define LINK_IRQ BIT(27) #define LINK_EOC BIT(28) #define LINK_LOOP BIT(29) #define LINK_ERR BIT(30) #define LINK_OWNER BIT(31) __le32 ctl; __le32 addr_low; __le32 addr_high; __le32 revered; } __packed; /* 1 page for link 256*16 */ #define DMA_MAX_LINK 256 /* sizeof(struct aml_dma_sg_link) */ #define DMA_LINK_SIZE 16 #define DMA_LINK_MAX_SIZE (DMA_LINK_SIZE * DMA_MAX_LINK) struct aml_dma_desc { struct virt_dma_desc vd; struct aml_dma_sg_link *sg_link; struct dma_device *dma_device; dma_addr_t sg_link_phys; size_t sg_link_size; u32 data_len; struct work_struct free_work; }; struct aml_dma_chan { struct virt_dma_chan vchan; struct aml_dma_dev *aml_dma; struct aml_dma_desc *cur_desc; enum dma_status pre_status; enum dma_status status; enum dma_transfer_direction direction; int chan_id; /* reg_base (direction + chan_id) */ int reg_offs; /* When there are multiple consecutive transmission errors, this channel halt */ int err_num; }; struct aml_dma_dev { struct dma_device dma_device; void __iomem *base; struct regmap *regmap; struct clk *clk; int irq; struct platform_device *pdev; struct aml_dma_chan *aml_rch[MAX_CHAN_ID]; struct aml_dma_chan *aml_wch[MAX_CHAN_ID]; struct workqueue_struct *free_wq; unsigned int chan_nr; unsigned int chan_used; struct aml_dma_chan aml_chans[]__counted_by(chan_nr); }; static inline struct aml_dma_chan *to_aml_dma_chan(struct dma_chan *chan) { return container_of(chan, struct aml_dma_chan, vchan.chan); } static inline struct aml_dma_desc *to_aml_dma_desc(struct virt_dma_desc *vd) { return container_of(vd, struct aml_dma_desc, vd); } static void aml_dma_free_desc_work(struct work_struct *work) { struct aml_dma_desc *aml_desc = container_of(work, struct aml_dma_desc, free_work); dma_free_coherent(aml_desc->dma_device->dev, aml_desc->sg_link_size, aml_desc->sg_link, aml_desc->sg_link_phys); kfree(aml_desc); } static void aml_dma_free_desc(struct virt_dma_desc *vd) { struct aml_dma_desc *aml_desc = to_aml_dma_desc(vd); struct aml_dma_dev *aml_dev = container_of(aml_desc->dma_device, struct aml_dma_dev, dma_device); queue_work(aml_dev->free_wq, &aml_desc->free_work); } static int aml_dma_alloc_chan_resources(struct dma_chan *chan) { struct aml_dma_chan *aml_chan = to_aml_dma_chan(chan); struct aml_dma_dev *aml_dma = aml_chan->aml_dma; /* offset is the same RCH_CFG and WCH_CFG */ regmap_set_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_CLEAR); regmap_clear_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_PAUSE); regmap_clear_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_CLEAR); aml_chan->status = DMA_COMPLETE; aml_chan->cur_desc = NULL; aml_chan->err_num = 0; return 0; } static void aml_dma_free_chan_resources(struct dma_chan *chan) { struct aml_dma_chan *aml_chan = to_aml_dma_chan(chan); struct aml_dma_dev *aml_dma = aml_chan->aml_dma; struct virt_dma_desc *cur_vd = NULL; unsigned long flags; spin_lock_irqsave(&aml_chan->vchan.lock, flags); regmap_set_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_PAUSE); regmap_set_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_CLEAR); if (aml_chan->cur_desc) cur_vd = &aml_chan->cur_desc->vd; aml_chan->cur_desc = NULL; spin_unlock_irqrestore(&aml_chan->vchan.lock, flags); if (cur_vd) aml_dma_free_desc(cur_vd); vchan_free_chan_resources(&aml_chan->vchan); } /* DMA transfer state update how many data reside it */ static enum dma_status aml_dma_tx_status(struct dma_chan *chan, dma_cookie_t cookie, struct dma_tx_state *txstate) { struct aml_dma_chan *aml_chan = to_aml_dma_chan(chan); struct aml_dma_dev *aml_dma = aml_chan->aml_dma; struct aml_dma_desc *aml_desc = NULL; struct virt_dma_desc *vd; u32 residue = 0, done = 0; unsigned long flags; enum dma_status ret; ret = dma_cookie_status(chan, cookie, txstate); if (ret == DMA_COMPLETE) return ret; if (aml_chan->status == DMA_PAUSED) ret = DMA_PAUSED; if (!txstate) return ret; spin_lock_irqsave(&aml_chan->vchan.lock, flags); vd = vchan_find_desc(&aml_chan->vchan, cookie); if (vd) { aml_desc = to_aml_dma_desc(vd); residue = aml_desc->data_len; } else if (aml_chan->cur_desc && aml_chan->cur_desc->vd.tx.cookie == cookie) { aml_desc = aml_chan->cur_desc; regmap_read(aml_dma->regmap, aml_chan->reg_offs + RCH_RD_LEN, &done); residue = aml_desc->data_len - done; } else { dev_err(aml_dma->dma_device.dev, "cookie error\n"); } spin_unlock_irqrestore(&aml_chan->vchan.lock, flags); dma_set_residue(txstate, residue); return ret; } static struct dma_async_tx_descriptor *aml_dma_prep_slave_sg (struct dma_chan *chan, struct scatterlist *sgl, unsigned int sg_len, enum dma_transfer_direction direction, unsigned long flags, void *context) { struct aml_dma_chan *aml_chan = to_aml_dma_chan(chan); struct aml_dma_dev *aml_dma = aml_chan->aml_dma; struct aml_dma_desc *aml_desc = NULL; struct aml_dma_sg_link *sg_link = NULL; struct scatterlist *sg = NULL; u64 paddr; u32 link_count, avail; u32 i; if (aml_chan->direction != direction) { dev_err(aml_dma->dma_device.dev, "direction not support\n"); return NULL; } link_count = sg_nents_for_dma(sgl, sg_len, SG_MAX_LEN); if (link_count == 0) return NULL; aml_desc = kzalloc_obj(*aml_desc, GFP_NOWAIT); if (!aml_desc) return NULL; INIT_WORK(&aml_desc->free_work, aml_dma_free_desc_work); aml_desc->sg_link_size = link_count * sizeof(*sg_link); aml_desc->sg_link = dma_alloc_coherent(aml_dma->dma_device.dev, aml_desc->sg_link_size, &aml_desc->sg_link_phys, GFP_NOWAIT); if (!aml_desc->sg_link) { kfree(aml_desc); return NULL; } aml_desc->dma_device = &aml_dma->dma_device; sg_link = aml_desc->sg_link; for_each_sg(sgl, sg, sg_len, i) { avail = sg_dma_len(sg); if (avail == 0) continue; paddr = sg->dma_address; while (avail > SG_MAX_LEN) { /* set dma address and len to sglink*/ sg_link->addr_low = cpu_to_le32(lower_32_bits(paddr)); sg_link->addr_high = cpu_to_le32(upper_32_bits(paddr)); sg_link->ctl = cpu_to_le32(FIELD_PREP(LINK_LEN, SG_MAX_LEN)); paddr = paddr + SG_MAX_LEN; avail = avail - SG_MAX_LEN; sg_link++; } /* set dma address and len to sglink*/ sg_link->addr_low = cpu_to_le32(lower_32_bits(paddr)); sg_link->addr_high = cpu_to_le32(upper_32_bits(paddr)); sg_link->ctl = cpu_to_le32(FIELD_PREP(LINK_LEN, avail)); aml_desc->data_len += sg_dma_len(sg); sg_link++; } /* the last sg set eoc flag */ sg_link--; sg_link->ctl |= LINK_EOC; return vchan_tx_prep(&aml_chan->vchan, &aml_desc->vd, flags); } static int aml_dma_chan_pause(struct dma_chan *chan) { struct aml_dma_chan *aml_chan = to_aml_dma_chan(chan); struct aml_dma_dev *aml_dma = aml_chan->aml_dma; unsigned long flags; spin_lock_irqsave(&aml_chan->vchan.lock, flags); regmap_set_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_PAUSE); aml_chan->pre_status = aml_chan->status; aml_chan->status = DMA_PAUSED; spin_unlock_irqrestore(&aml_chan->vchan.lock, flags); return 0; } static int aml_dma_chan_resume(struct dma_chan *chan) { struct aml_dma_chan *aml_chan = to_aml_dma_chan(chan); struct aml_dma_dev *aml_dma = aml_chan->aml_dma; unsigned long flags; spin_lock_irqsave(&aml_chan->vchan.lock, flags); regmap_clear_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_PAUSE); aml_chan->status = aml_chan->pre_status; spin_unlock_irqrestore(&aml_chan->vchan.lock, flags); return 0; } static int aml_dma_terminate_all(struct dma_chan *chan) { struct aml_dma_chan *aml_chan = to_aml_dma_chan(chan); struct aml_dma_dev *aml_dma = aml_chan->aml_dma; int chan_id = aml_chan->chan_id; struct virt_dma_desc *cur_vd; unsigned long flags; LIST_HEAD(head); spin_lock_irqsave(&aml_chan->vchan.lock, flags); regmap_set_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_PAUSE); regmap_set_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_CLEAR); if (aml_chan->direction == DMA_MEM_TO_DEV) regmap_set_bits(aml_dma->regmap, RCH_INT_MASK, BIT(chan_id)); else if (aml_chan->direction == DMA_DEV_TO_MEM) regmap_set_bits(aml_dma->regmap, WCH_INT_MASK, BIT(chan_id)); regmap_clear_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_PAUSE); regmap_clear_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_CLEAR); vchan_get_all_descriptors(&aml_chan->vchan, &head); cur_vd = &aml_chan->cur_desc->vd; aml_chan->cur_desc = NULL; spin_unlock_irqrestore(&aml_chan->vchan.lock, flags); if (cur_vd) aml_dma_free_desc(cur_vd); vchan_dma_desc_free_list(&aml_chan->vchan, &head); aml_chan->status = DMA_COMPLETE; aml_chan->err_num = 0; return 0; } static void aml_dma_start(struct aml_dma_chan *aml_chan) { struct virt_dma_desc *vd = vchan_next_desc(&aml_chan->vchan); struct aml_dma_dev *aml_dma = aml_chan->aml_dma; struct aml_dma_desc *aml_desc = NULL; int chan_id = aml_chan->chan_id; if (aml_chan->status == DMA_ERROR) { if (aml_chan->err_num > 5) { dev_err(aml_dma->dma_device.dev, "hw error\n"); return; } regmap_set_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_PAUSE); regmap_set_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_CLEAR); aml_chan->err_num++; regmap_clear_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_PAUSE); regmap_clear_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_CLEAR); aml_chan->status = DMA_COMPLETE; } else { aml_chan->err_num = 0; } if (!vd) return; if (aml_chan->status != DMA_COMPLETE) return; list_del(&vd->node); aml_desc = to_aml_dma_desc(vd); aml_chan->cur_desc = aml_desc; if (aml_chan->direction == DMA_MEM_TO_DEV) { regmap_write(aml_dma->regmap, aml_chan->reg_offs + RCH_ADDR, lower_32_bits(aml_desc->sg_link_phys)); regmap_write(aml_dma->regmap, aml_chan->reg_offs + RCH_ADDR_HIGH, upper_32_bits(aml_desc->sg_link_phys)); regmap_write(aml_dma->regmap, aml_chan->reg_offs + RCH_LEN, aml_desc->data_len); regmap_clear_bits(aml_dma->regmap, RCH_INT_MASK, BIT(chan_id)); /* for rch (tx) need set cfg 0 to trigger start */ regmap_write(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, 0); } else if (aml_chan->direction == DMA_DEV_TO_MEM) { regmap_write(aml_dma->regmap, aml_chan->reg_offs + WCH_ADDR, lower_32_bits(aml_desc->sg_link_phys)); regmap_write(aml_dma->regmap, aml_chan->reg_offs + WCH_ADDR_HIGH, upper_32_bits(aml_desc->sg_link_phys)); regmap_write(aml_dma->regmap, aml_chan->reg_offs + WCH_LEN, aml_desc->data_len); regmap_clear_bits(aml_dma->regmap, WCH_INT_MASK, BIT(chan_id)); } } static void aml_dma_issue_pending(struct dma_chan *chan) { struct aml_dma_chan *aml_chan = to_aml_dma_chan(chan); unsigned long flags; spin_lock_irqsave(&aml_chan->vchan.lock, flags); if (vchan_issue_pending(&aml_chan->vchan) && !aml_chan->cur_desc) aml_dma_start(aml_chan); spin_unlock_irqrestore(&aml_chan->vchan.lock, flags); } static irqreturn_t aml_dma_interrupt_handler(int irq, void *dev_id) { struct aml_dma_dev *aml_dma = dev_id; struct aml_dma_chan *aml_chan; struct aml_dma_desc *aml_desc; u32 done, eoc_done, err, err_l, end; u32 cpl_data; int i = 0; /* deal with rch normal complete and error */ regmap_read(aml_dma->regmap, RCH_DONE, &done); regmap_read(aml_dma->regmap, RCH_ERR, &err); regmap_read(aml_dma->regmap, RCH_LEN_ERR, &err_l); err = err | err_l; done = done | err; while (done) { i = ffs(done) - 1; regmap_write(aml_dma->regmap, CLEAR_RCH, BIT(i)); done &= ~BIT(i); aml_chan = aml_dma->aml_rch[i]; if (!aml_chan) { dev_err(aml_dma->dma_device.dev, "idx %d rch not initialized\n", i); continue; } spin_lock(&aml_chan->vchan.lock); aml_chan->status = (err & BIT(i)) ? DMA_ERROR : DMA_COMPLETE; aml_desc = aml_chan->cur_desc; if (!aml_desc) { spin_unlock(&aml_chan->vchan.lock); continue; } if (aml_chan->status == DMA_ERROR) { aml_desc->vd.tx_result.result = DMA_TRANS_READ_FAILED; regmap_read(aml_dma->regmap, aml_chan->reg_offs + RCH_RD_LEN, &cpl_data); aml_desc->vd.tx_result.residue = aml_desc->data_len - cpl_data; } vchan_cookie_complete(&aml_desc->vd); aml_chan->cur_desc = NULL; aml_dma_start(aml_chan); spin_unlock(&aml_chan->vchan.lock); } /* deal with wch normal complete and error */ regmap_read(aml_dma->regmap, DMA_BATCH_END, &end); if (end) regmap_write(aml_dma->regmap, CLEAR_W_BATCH, end); regmap_read(aml_dma->regmap, WCH_DONE, &done); regmap_read(aml_dma->regmap, WCH_EOC_DONE, &eoc_done); done = done | eoc_done; regmap_read(aml_dma->regmap, WCH_ERR, &err); regmap_read(aml_dma->regmap, WDMA_RESP_ERR, &err_l); err = err | err_l; done = done | err; i = 0; while (done) { i = ffs(done) - 1; done &= ~BIT(i); regmap_write(aml_dma->regmap, CLEAR_WCH, BIT(i)); aml_chan = aml_dma->aml_wch[i]; if (!aml_chan) { dev_err(aml_dma->dma_device.dev, "idx %d wch not initialized\n", i); continue; } spin_lock(&aml_chan->vchan.lock); aml_chan->status = (err & BIT(i)) ? DMA_ERROR : DMA_COMPLETE; aml_desc = aml_chan->cur_desc; if (!aml_desc) { spin_unlock(&aml_chan->vchan.lock); continue; } if (aml_chan->status == DMA_ERROR) { aml_desc->vd.tx_result.result = DMA_TRANS_WRITE_FAILED; regmap_read(aml_dma->regmap, aml_chan->reg_offs + RCH_RD_LEN, &cpl_data); aml_desc->vd.tx_result.residue = aml_desc->data_len - cpl_data; } vchan_cookie_complete(&aml_desc->vd); aml_chan->cur_desc = NULL; aml_dma_start(aml_chan); spin_unlock(&aml_chan->vchan.lock); } return IRQ_HANDLED; } static struct dma_chan *aml_of_dma_xlate(struct of_phandle_args *dma_spec, struct of_dma *ofdma) { struct aml_dma_dev *aml_dma = (struct aml_dma_dev *)ofdma->of_dma_data; struct aml_dma_chan *aml_chan = NULL; u32 type; u32 phy_chan_id; if (dma_spec->args_count != 2) return NULL; type = dma_spec->args[0]; phy_chan_id = dma_spec->args[1]; if (phy_chan_id >= MAX_CHAN_ID) return NULL; if (type == AMLOGIC_A9_DMA_TX) { aml_chan = aml_dma->aml_rch[phy_chan_id]; if (!aml_chan) { if (aml_dma->chan_used >= aml_dma->chan_nr) { dev_err(aml_dma->dma_device.dev, "some dma clients err used\n"); return NULL; } aml_chan = &aml_dma->aml_chans[aml_dma->chan_used]; aml_dma->chan_used++; aml_chan->direction = DMA_MEM_TO_DEV; aml_chan->chan_id = phy_chan_id; aml_chan->reg_offs = RCH_REG_BASE + 0x40 * aml_chan->chan_id; aml_dma->aml_rch[phy_chan_id] = aml_chan; } } else if (type == AMLOGIC_A9_DMA_RX) { aml_chan = aml_dma->aml_wch[phy_chan_id]; if (!aml_chan) { if (aml_dma->chan_used >= aml_dma->chan_nr) { dev_err(aml_dma->dma_device.dev, "some dma clients err used\n"); return NULL; } aml_chan = &aml_dma->aml_chans[aml_dma->chan_used]; aml_dma->chan_used++; aml_chan->direction = DMA_DEV_TO_MEM; aml_chan->chan_id = phy_chan_id; aml_chan->reg_offs = WCH_REG_BASE + 0x40 * aml_chan->chan_id; aml_dma->aml_wch[phy_chan_id] = aml_chan; } } else { dev_err(aml_dma->dma_device.dev, "type %d not supported\n", type); return NULL; } return dma_get_slave_channel(&aml_chan->vchan.chan); } static int aml_dma_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct dma_device *dma_dev; struct aml_dma_dev *aml_dma; int ret, i, len; u32 chan_nr; const struct regmap_config aml_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, .max_register = 0x3000, }; ret = of_property_read_u32(np, "dma-channels", &chan_nr); if (ret) return dev_err_probe(&pdev->dev, ret, "failed to read dma-channels\n"); if (chan_nr > (MAX_CHAN_ID * 2)) return dev_err_probe(&pdev->dev, -EINVAL, "dma-channels unusual\n"); len = sizeof(struct aml_dma_dev) + sizeof(struct aml_dma_chan) * chan_nr; aml_dma = devm_kzalloc(&pdev->dev, len, GFP_KERNEL); if (!aml_dma) return -ENOMEM; aml_dma->free_wq = alloc_ordered_workqueue("aml_dma_free", 0); if (!aml_dma->free_wq) return -ENOMEM; aml_dma->chan_nr = chan_nr; aml_dma->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(aml_dma->base)) return PTR_ERR(aml_dma->base); aml_dma->regmap = devm_regmap_init_mmio(&pdev->dev, aml_dma->base, &aml_regmap_config); if (IS_ERR_OR_NULL(aml_dma->regmap)) return PTR_ERR(aml_dma->regmap); aml_dma->clk = devm_clk_get_enabled(&pdev->dev, NULL); if (IS_ERR(aml_dma->clk)) return PTR_ERR(aml_dma->clk); aml_dma->irq = platform_get_irq(pdev, 0); aml_dma->pdev = pdev; aml_dma->dma_device.dev = &pdev->dev; dma_dev = &aml_dma->dma_device; INIT_LIST_HEAD(&dma_dev->channels); /* Initialize channel parameters */ for (i = 0; i < chan_nr; i++) { struct aml_dma_chan *aml_chan = &aml_dma->aml_chans[i]; aml_chan->aml_dma = aml_dma; aml_chan->vchan.desc_free = aml_dma_free_desc; vchan_init(&aml_chan->vchan, &aml_dma->dma_device); } aml_dma->chan_used = 0; dma_set_max_seg_size(dma_dev->dev, SG_MAX_LEN); dma_cap_set(DMA_SLAVE, dma_dev->cap_mask); dma_dev->device_alloc_chan_resources = aml_dma_alloc_chan_resources; dma_dev->device_free_chan_resources = aml_dma_free_chan_resources; dma_dev->device_tx_status = aml_dma_tx_status; dma_dev->device_prep_slave_sg = aml_dma_prep_slave_sg; dma_dev->device_pause = aml_dma_chan_pause; dma_dev->device_resume = aml_dma_chan_resume; dma_dev->device_terminate_all = aml_dma_terminate_all; dma_dev->device_issue_pending = aml_dma_issue_pending; /* PIO 4 bytes and I2C 1 byte */ dma_dev->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) | BIT(DMA_SLAVE_BUSWIDTH_1_BYTE); dma_dev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); dma_dev->residue_granularity = DMA_RESIDUE_GRANULARITY_BURST; regmap_write(aml_dma->regmap, RCH_INT_MASK, INT_MASK); regmap_write(aml_dma->regmap, WCH_INT_MASK, INT_MASK); ret = devm_request_irq(&pdev->dev, aml_dma->irq, aml_dma_interrupt_handler, 0, dev_name(&pdev->dev), aml_dma); if (ret) return dev_err_probe(&pdev->dev, ret, "failed to register irq\n"); ret = dmaenginem_async_device_register(dma_dev); if (ret) return dev_err_probe(&pdev->dev, ret, "failed to register dmaenginem\n"); ret = of_dma_controller_register(np, aml_of_dma_xlate, aml_dma); if (ret) return dev_err_probe(&pdev->dev, ret, "failed to register xlate\n"); platform_set_drvdata(pdev, aml_dma); return 0; } static void aml_dma_remove(struct platform_device *pdev) { struct aml_dma_dev *aml_dma = platform_get_drvdata(pdev); struct aml_dma_chan *aml_chan = NULL; int i; of_dma_controller_free((&pdev->dev)->of_node); regmap_write(aml_dma->regmap, RCH_INT_MASK, INT_MASK); regmap_write(aml_dma->regmap, WCH_INT_MASK, INT_MASK); for (i = 0; i < MAX_CHAN_ID; i++) { aml_chan = aml_dma->aml_rch[i]; if (aml_chan) tasklet_kill(&aml_chan->vchan.task); aml_chan = aml_dma->aml_wch[i]; if (aml_chan) tasklet_kill(&aml_chan->vchan.task); } destroy_workqueue(aml_dma->free_wq); } static const struct of_device_id aml_dma_ids[] = { { .compatible = "amlogic,a9-dma", }, { } }; MODULE_DEVICE_TABLE(of, aml_dma_ids); static struct platform_driver aml_dma_driver = { .probe = aml_dma_probe, .remove = aml_dma_remove, .driver = { .name = "aml-dma", .of_match_table = aml_dma_ids, }, }; module_platform_driver(aml_dma_driver); MODULE_DESCRIPTION("GENERAL DMA driver for Amlogic"); MODULE_AUTHOR("Xianwei Zhao "); MODULE_LICENSE("GPL");