162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Driver for the Analog Devices AXI-DMAC core 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2013-2019 Analog Devices Inc. 662306a36Sopenharmony_ci * Author: Lars-Peter Clausen <lars@metafoo.de> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/bitfield.h> 1062306a36Sopenharmony_ci#include <linux/clk.h> 1162306a36Sopenharmony_ci#include <linux/device.h> 1262306a36Sopenharmony_ci#include <linux/dma-mapping.h> 1362306a36Sopenharmony_ci#include <linux/dmaengine.h> 1462306a36Sopenharmony_ci#include <linux/err.h> 1562306a36Sopenharmony_ci#include <linux/interrupt.h> 1662306a36Sopenharmony_ci#include <linux/io.h> 1762306a36Sopenharmony_ci#include <linux/kernel.h> 1862306a36Sopenharmony_ci#include <linux/module.h> 1962306a36Sopenharmony_ci#include <linux/of.h> 2062306a36Sopenharmony_ci#include <linux/of_dma.h> 2162306a36Sopenharmony_ci#include <linux/of_address.h> 2262306a36Sopenharmony_ci#include <linux/platform_device.h> 2362306a36Sopenharmony_ci#include <linux/regmap.h> 2462306a36Sopenharmony_ci#include <linux/slab.h> 2562306a36Sopenharmony_ci#include <linux/fpga/adi-axi-common.h> 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#include <dt-bindings/dma/axi-dmac.h> 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#include "dmaengine.h" 3062306a36Sopenharmony_ci#include "virt-dma.h" 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci/* 3362306a36Sopenharmony_ci * The AXI-DMAC is a soft IP core that is used in FPGA designs. The core has 3462306a36Sopenharmony_ci * various instantiation parameters which decided the exact feature set support 3562306a36Sopenharmony_ci * by the core. 3662306a36Sopenharmony_ci * 3762306a36Sopenharmony_ci * Each channel of the core has a source interface and a destination interface. 3862306a36Sopenharmony_ci * The number of channels and the type of the channel interfaces is selected at 3962306a36Sopenharmony_ci * configuration time. A interface can either be a connected to a central memory 4062306a36Sopenharmony_ci * interconnect, which allows access to system memory, or it can be connected to 4162306a36Sopenharmony_ci * a dedicated bus which is directly connected to a data port on a peripheral. 4262306a36Sopenharmony_ci * Given that those are configuration options of the core that are selected when 4362306a36Sopenharmony_ci * it is instantiated this means that they can not be changed by software at 4462306a36Sopenharmony_ci * runtime. By extension this means that each channel is uni-directional. It can 4562306a36Sopenharmony_ci * either be device to memory or memory to device, but not both. Also since the 4662306a36Sopenharmony_ci * device side is a dedicated data bus only connected to a single peripheral 4762306a36Sopenharmony_ci * there is no address than can or needs to be configured for the device side. 4862306a36Sopenharmony_ci */ 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci#define AXI_DMAC_REG_INTERFACE_DESC 0x10 5162306a36Sopenharmony_ci#define AXI_DMAC_DMA_SRC_TYPE_MSK GENMASK(13, 12) 5262306a36Sopenharmony_ci#define AXI_DMAC_DMA_SRC_TYPE_GET(x) FIELD_GET(AXI_DMAC_DMA_SRC_TYPE_MSK, x) 5362306a36Sopenharmony_ci#define AXI_DMAC_DMA_SRC_WIDTH_MSK GENMASK(11, 8) 5462306a36Sopenharmony_ci#define AXI_DMAC_DMA_SRC_WIDTH_GET(x) FIELD_GET(AXI_DMAC_DMA_SRC_WIDTH_MSK, x) 5562306a36Sopenharmony_ci#define AXI_DMAC_DMA_DST_TYPE_MSK GENMASK(5, 4) 5662306a36Sopenharmony_ci#define AXI_DMAC_DMA_DST_TYPE_GET(x) FIELD_GET(AXI_DMAC_DMA_DST_TYPE_MSK, x) 5762306a36Sopenharmony_ci#define AXI_DMAC_DMA_DST_WIDTH_MSK GENMASK(3, 0) 5862306a36Sopenharmony_ci#define AXI_DMAC_DMA_DST_WIDTH_GET(x) FIELD_GET(AXI_DMAC_DMA_DST_WIDTH_MSK, x) 5962306a36Sopenharmony_ci#define AXI_DMAC_REG_COHERENCY_DESC 0x14 6062306a36Sopenharmony_ci#define AXI_DMAC_DST_COHERENT_MSK BIT(0) 6162306a36Sopenharmony_ci#define AXI_DMAC_DST_COHERENT_GET(x) FIELD_GET(AXI_DMAC_DST_COHERENT_MSK, x) 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci#define AXI_DMAC_REG_IRQ_MASK 0x80 6462306a36Sopenharmony_ci#define AXI_DMAC_REG_IRQ_PENDING 0x84 6562306a36Sopenharmony_ci#define AXI_DMAC_REG_IRQ_SOURCE 0x88 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci#define AXI_DMAC_REG_CTRL 0x400 6862306a36Sopenharmony_ci#define AXI_DMAC_REG_TRANSFER_ID 0x404 6962306a36Sopenharmony_ci#define AXI_DMAC_REG_START_TRANSFER 0x408 7062306a36Sopenharmony_ci#define AXI_DMAC_REG_FLAGS 0x40c 7162306a36Sopenharmony_ci#define AXI_DMAC_REG_DEST_ADDRESS 0x410 7262306a36Sopenharmony_ci#define AXI_DMAC_REG_SRC_ADDRESS 0x414 7362306a36Sopenharmony_ci#define AXI_DMAC_REG_X_LENGTH 0x418 7462306a36Sopenharmony_ci#define AXI_DMAC_REG_Y_LENGTH 0x41c 7562306a36Sopenharmony_ci#define AXI_DMAC_REG_DEST_STRIDE 0x420 7662306a36Sopenharmony_ci#define AXI_DMAC_REG_SRC_STRIDE 0x424 7762306a36Sopenharmony_ci#define AXI_DMAC_REG_TRANSFER_DONE 0x428 7862306a36Sopenharmony_ci#define AXI_DMAC_REG_ACTIVE_TRANSFER_ID 0x42c 7962306a36Sopenharmony_ci#define AXI_DMAC_REG_STATUS 0x430 8062306a36Sopenharmony_ci#define AXI_DMAC_REG_CURRENT_SRC_ADDR 0x434 8162306a36Sopenharmony_ci#define AXI_DMAC_REG_CURRENT_DEST_ADDR 0x438 8262306a36Sopenharmony_ci#define AXI_DMAC_REG_PARTIAL_XFER_LEN 0x44c 8362306a36Sopenharmony_ci#define AXI_DMAC_REG_PARTIAL_XFER_ID 0x450 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci#define AXI_DMAC_CTRL_ENABLE BIT(0) 8662306a36Sopenharmony_ci#define AXI_DMAC_CTRL_PAUSE BIT(1) 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci#define AXI_DMAC_IRQ_SOT BIT(0) 8962306a36Sopenharmony_ci#define AXI_DMAC_IRQ_EOT BIT(1) 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci#define AXI_DMAC_FLAG_CYCLIC BIT(0) 9262306a36Sopenharmony_ci#define AXI_DMAC_FLAG_LAST BIT(1) 9362306a36Sopenharmony_ci#define AXI_DMAC_FLAG_PARTIAL_REPORT BIT(2) 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci#define AXI_DMAC_FLAG_PARTIAL_XFER_DONE BIT(31) 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci/* The maximum ID allocated by the hardware is 31 */ 9862306a36Sopenharmony_ci#define AXI_DMAC_SG_UNUSED 32U 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cistruct axi_dmac_sg { 10162306a36Sopenharmony_ci dma_addr_t src_addr; 10262306a36Sopenharmony_ci dma_addr_t dest_addr; 10362306a36Sopenharmony_ci unsigned int x_len; 10462306a36Sopenharmony_ci unsigned int y_len; 10562306a36Sopenharmony_ci unsigned int dest_stride; 10662306a36Sopenharmony_ci unsigned int src_stride; 10762306a36Sopenharmony_ci unsigned int id; 10862306a36Sopenharmony_ci unsigned int partial_len; 10962306a36Sopenharmony_ci bool schedule_when_free; 11062306a36Sopenharmony_ci}; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_cistruct axi_dmac_desc { 11362306a36Sopenharmony_ci struct virt_dma_desc vdesc; 11462306a36Sopenharmony_ci bool cyclic; 11562306a36Sopenharmony_ci bool have_partial_xfer; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci unsigned int num_submitted; 11862306a36Sopenharmony_ci unsigned int num_completed; 11962306a36Sopenharmony_ci unsigned int num_sgs; 12062306a36Sopenharmony_ci struct axi_dmac_sg sg[]; 12162306a36Sopenharmony_ci}; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_cistruct axi_dmac_chan { 12462306a36Sopenharmony_ci struct virt_dma_chan vchan; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci struct axi_dmac_desc *next_desc; 12762306a36Sopenharmony_ci struct list_head active_descs; 12862306a36Sopenharmony_ci enum dma_transfer_direction direction; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci unsigned int src_width; 13162306a36Sopenharmony_ci unsigned int dest_width; 13262306a36Sopenharmony_ci unsigned int src_type; 13362306a36Sopenharmony_ci unsigned int dest_type; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci unsigned int max_length; 13662306a36Sopenharmony_ci unsigned int address_align_mask; 13762306a36Sopenharmony_ci unsigned int length_align_mask; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci bool hw_partial_xfer; 14062306a36Sopenharmony_ci bool hw_cyclic; 14162306a36Sopenharmony_ci bool hw_2d; 14262306a36Sopenharmony_ci}; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistruct axi_dmac { 14562306a36Sopenharmony_ci void __iomem *base; 14662306a36Sopenharmony_ci int irq; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci struct clk *clk; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci struct dma_device dma_dev; 15162306a36Sopenharmony_ci struct axi_dmac_chan chan; 15262306a36Sopenharmony_ci}; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic struct axi_dmac *chan_to_axi_dmac(struct axi_dmac_chan *chan) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci return container_of(chan->vchan.chan.device, struct axi_dmac, 15762306a36Sopenharmony_ci dma_dev); 15862306a36Sopenharmony_ci} 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_cistatic struct axi_dmac_chan *to_axi_dmac_chan(struct dma_chan *c) 16162306a36Sopenharmony_ci{ 16262306a36Sopenharmony_ci return container_of(c, struct axi_dmac_chan, vchan.chan); 16362306a36Sopenharmony_ci} 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_cistatic struct axi_dmac_desc *to_axi_dmac_desc(struct virt_dma_desc *vdesc) 16662306a36Sopenharmony_ci{ 16762306a36Sopenharmony_ci return container_of(vdesc, struct axi_dmac_desc, vdesc); 16862306a36Sopenharmony_ci} 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_cistatic void axi_dmac_write(struct axi_dmac *axi_dmac, unsigned int reg, 17162306a36Sopenharmony_ci unsigned int val) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci writel(val, axi_dmac->base + reg); 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic int axi_dmac_read(struct axi_dmac *axi_dmac, unsigned int reg) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci return readl(axi_dmac->base + reg); 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_cistatic int axi_dmac_src_is_mem(struct axi_dmac_chan *chan) 18262306a36Sopenharmony_ci{ 18362306a36Sopenharmony_ci return chan->src_type == AXI_DMAC_BUS_TYPE_AXI_MM; 18462306a36Sopenharmony_ci} 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_cistatic int axi_dmac_dest_is_mem(struct axi_dmac_chan *chan) 18762306a36Sopenharmony_ci{ 18862306a36Sopenharmony_ci return chan->dest_type == AXI_DMAC_BUS_TYPE_AXI_MM; 18962306a36Sopenharmony_ci} 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cistatic bool axi_dmac_check_len(struct axi_dmac_chan *chan, unsigned int len) 19262306a36Sopenharmony_ci{ 19362306a36Sopenharmony_ci if (len == 0) 19462306a36Sopenharmony_ci return false; 19562306a36Sopenharmony_ci if ((len & chan->length_align_mask) != 0) /* Not aligned */ 19662306a36Sopenharmony_ci return false; 19762306a36Sopenharmony_ci return true; 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cistatic bool axi_dmac_check_addr(struct axi_dmac_chan *chan, dma_addr_t addr) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci if ((addr & chan->address_align_mask) != 0) /* Not aligned */ 20362306a36Sopenharmony_ci return false; 20462306a36Sopenharmony_ci return true; 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cistatic void axi_dmac_start_transfer(struct axi_dmac_chan *chan) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci struct axi_dmac *dmac = chan_to_axi_dmac(chan); 21062306a36Sopenharmony_ci struct virt_dma_desc *vdesc; 21162306a36Sopenharmony_ci struct axi_dmac_desc *desc; 21262306a36Sopenharmony_ci struct axi_dmac_sg *sg; 21362306a36Sopenharmony_ci unsigned int flags = 0; 21462306a36Sopenharmony_ci unsigned int val; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci val = axi_dmac_read(dmac, AXI_DMAC_REG_START_TRANSFER); 21762306a36Sopenharmony_ci if (val) /* Queue is full, wait for the next SOT IRQ */ 21862306a36Sopenharmony_ci return; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci desc = chan->next_desc; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci if (!desc) { 22362306a36Sopenharmony_ci vdesc = vchan_next_desc(&chan->vchan); 22462306a36Sopenharmony_ci if (!vdesc) 22562306a36Sopenharmony_ci return; 22662306a36Sopenharmony_ci list_move_tail(&vdesc->node, &chan->active_descs); 22762306a36Sopenharmony_ci desc = to_axi_dmac_desc(vdesc); 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci sg = &desc->sg[desc->num_submitted]; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci /* Already queued in cyclic mode. Wait for it to finish */ 23262306a36Sopenharmony_ci if (sg->id != AXI_DMAC_SG_UNUSED) { 23362306a36Sopenharmony_ci sg->schedule_when_free = true; 23462306a36Sopenharmony_ci return; 23562306a36Sopenharmony_ci } 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci desc->num_submitted++; 23862306a36Sopenharmony_ci if (desc->num_submitted == desc->num_sgs || 23962306a36Sopenharmony_ci desc->have_partial_xfer) { 24062306a36Sopenharmony_ci if (desc->cyclic) 24162306a36Sopenharmony_ci desc->num_submitted = 0; /* Start again */ 24262306a36Sopenharmony_ci else 24362306a36Sopenharmony_ci chan->next_desc = NULL; 24462306a36Sopenharmony_ci flags |= AXI_DMAC_FLAG_LAST; 24562306a36Sopenharmony_ci } else { 24662306a36Sopenharmony_ci chan->next_desc = desc; 24762306a36Sopenharmony_ci } 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci sg->id = axi_dmac_read(dmac, AXI_DMAC_REG_TRANSFER_ID); 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci if (axi_dmac_dest_is_mem(chan)) { 25262306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_DEST_ADDRESS, sg->dest_addr); 25362306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_DEST_STRIDE, sg->dest_stride); 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci if (axi_dmac_src_is_mem(chan)) { 25762306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_SRC_ADDRESS, sg->src_addr); 25862306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_SRC_STRIDE, sg->src_stride); 25962306a36Sopenharmony_ci } 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci /* 26262306a36Sopenharmony_ci * If the hardware supports cyclic transfers and there is no callback to 26362306a36Sopenharmony_ci * call and only a single segment, enable hw cyclic mode to avoid 26462306a36Sopenharmony_ci * unnecessary interrupts. 26562306a36Sopenharmony_ci */ 26662306a36Sopenharmony_ci if (chan->hw_cyclic && desc->cyclic && !desc->vdesc.tx.callback && 26762306a36Sopenharmony_ci desc->num_sgs == 1) 26862306a36Sopenharmony_ci flags |= AXI_DMAC_FLAG_CYCLIC; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci if (chan->hw_partial_xfer) 27162306a36Sopenharmony_ci flags |= AXI_DMAC_FLAG_PARTIAL_REPORT; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_X_LENGTH, sg->x_len - 1); 27462306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_Y_LENGTH, sg->y_len - 1); 27562306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_FLAGS, flags); 27662306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_START_TRANSFER, 1); 27762306a36Sopenharmony_ci} 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_cistatic struct axi_dmac_desc *axi_dmac_active_desc(struct axi_dmac_chan *chan) 28062306a36Sopenharmony_ci{ 28162306a36Sopenharmony_ci return list_first_entry_or_null(&chan->active_descs, 28262306a36Sopenharmony_ci struct axi_dmac_desc, vdesc.node); 28362306a36Sopenharmony_ci} 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_cistatic inline unsigned int axi_dmac_total_sg_bytes(struct axi_dmac_chan *chan, 28662306a36Sopenharmony_ci struct axi_dmac_sg *sg) 28762306a36Sopenharmony_ci{ 28862306a36Sopenharmony_ci if (chan->hw_2d) 28962306a36Sopenharmony_ci return sg->x_len * sg->y_len; 29062306a36Sopenharmony_ci else 29162306a36Sopenharmony_ci return sg->x_len; 29262306a36Sopenharmony_ci} 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_cistatic void axi_dmac_dequeue_partial_xfers(struct axi_dmac_chan *chan) 29562306a36Sopenharmony_ci{ 29662306a36Sopenharmony_ci struct axi_dmac *dmac = chan_to_axi_dmac(chan); 29762306a36Sopenharmony_ci struct axi_dmac_desc *desc; 29862306a36Sopenharmony_ci struct axi_dmac_sg *sg; 29962306a36Sopenharmony_ci u32 xfer_done, len, id, i; 30062306a36Sopenharmony_ci bool found_sg; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci do { 30362306a36Sopenharmony_ci len = axi_dmac_read(dmac, AXI_DMAC_REG_PARTIAL_XFER_LEN); 30462306a36Sopenharmony_ci id = axi_dmac_read(dmac, AXI_DMAC_REG_PARTIAL_XFER_ID); 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci found_sg = false; 30762306a36Sopenharmony_ci list_for_each_entry(desc, &chan->active_descs, vdesc.node) { 30862306a36Sopenharmony_ci for (i = 0; i < desc->num_sgs; i++) { 30962306a36Sopenharmony_ci sg = &desc->sg[i]; 31062306a36Sopenharmony_ci if (sg->id == AXI_DMAC_SG_UNUSED) 31162306a36Sopenharmony_ci continue; 31262306a36Sopenharmony_ci if (sg->id == id) { 31362306a36Sopenharmony_ci desc->have_partial_xfer = true; 31462306a36Sopenharmony_ci sg->partial_len = len; 31562306a36Sopenharmony_ci found_sg = true; 31662306a36Sopenharmony_ci break; 31762306a36Sopenharmony_ci } 31862306a36Sopenharmony_ci } 31962306a36Sopenharmony_ci if (found_sg) 32062306a36Sopenharmony_ci break; 32162306a36Sopenharmony_ci } 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci if (found_sg) { 32462306a36Sopenharmony_ci dev_dbg(dmac->dma_dev.dev, 32562306a36Sopenharmony_ci "Found partial segment id=%u, len=%u\n", 32662306a36Sopenharmony_ci id, len); 32762306a36Sopenharmony_ci } else { 32862306a36Sopenharmony_ci dev_warn(dmac->dma_dev.dev, 32962306a36Sopenharmony_ci "Not found partial segment id=%u, len=%u\n", 33062306a36Sopenharmony_ci id, len); 33162306a36Sopenharmony_ci } 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci /* Check if we have any more partial transfers */ 33462306a36Sopenharmony_ci xfer_done = axi_dmac_read(dmac, AXI_DMAC_REG_TRANSFER_DONE); 33562306a36Sopenharmony_ci xfer_done = !(xfer_done & AXI_DMAC_FLAG_PARTIAL_XFER_DONE); 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci } while (!xfer_done); 33862306a36Sopenharmony_ci} 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_cistatic void axi_dmac_compute_residue(struct axi_dmac_chan *chan, 34162306a36Sopenharmony_ci struct axi_dmac_desc *active) 34262306a36Sopenharmony_ci{ 34362306a36Sopenharmony_ci struct dmaengine_result *rslt = &active->vdesc.tx_result; 34462306a36Sopenharmony_ci unsigned int start = active->num_completed - 1; 34562306a36Sopenharmony_ci struct axi_dmac_sg *sg; 34662306a36Sopenharmony_ci unsigned int i, total; 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci rslt->result = DMA_TRANS_NOERROR; 34962306a36Sopenharmony_ci rslt->residue = 0; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci /* 35262306a36Sopenharmony_ci * We get here if the last completed segment is partial, which 35362306a36Sopenharmony_ci * means we can compute the residue from that segment onwards 35462306a36Sopenharmony_ci */ 35562306a36Sopenharmony_ci for (i = start; i < active->num_sgs; i++) { 35662306a36Sopenharmony_ci sg = &active->sg[i]; 35762306a36Sopenharmony_ci total = axi_dmac_total_sg_bytes(chan, sg); 35862306a36Sopenharmony_ci rslt->residue += (total - sg->partial_len); 35962306a36Sopenharmony_ci } 36062306a36Sopenharmony_ci} 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_cistatic bool axi_dmac_transfer_done(struct axi_dmac_chan *chan, 36362306a36Sopenharmony_ci unsigned int completed_transfers) 36462306a36Sopenharmony_ci{ 36562306a36Sopenharmony_ci struct axi_dmac_desc *active; 36662306a36Sopenharmony_ci struct axi_dmac_sg *sg; 36762306a36Sopenharmony_ci bool start_next = false; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci active = axi_dmac_active_desc(chan); 37062306a36Sopenharmony_ci if (!active) 37162306a36Sopenharmony_ci return false; 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci if (chan->hw_partial_xfer && 37462306a36Sopenharmony_ci (completed_transfers & AXI_DMAC_FLAG_PARTIAL_XFER_DONE)) 37562306a36Sopenharmony_ci axi_dmac_dequeue_partial_xfers(chan); 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci do { 37862306a36Sopenharmony_ci sg = &active->sg[active->num_completed]; 37962306a36Sopenharmony_ci if (sg->id == AXI_DMAC_SG_UNUSED) /* Not yet submitted */ 38062306a36Sopenharmony_ci break; 38162306a36Sopenharmony_ci if (!(BIT(sg->id) & completed_transfers)) 38262306a36Sopenharmony_ci break; 38362306a36Sopenharmony_ci active->num_completed++; 38462306a36Sopenharmony_ci sg->id = AXI_DMAC_SG_UNUSED; 38562306a36Sopenharmony_ci if (sg->schedule_when_free) { 38662306a36Sopenharmony_ci sg->schedule_when_free = false; 38762306a36Sopenharmony_ci start_next = true; 38862306a36Sopenharmony_ci } 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci if (sg->partial_len) 39162306a36Sopenharmony_ci axi_dmac_compute_residue(chan, active); 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci if (active->cyclic) 39462306a36Sopenharmony_ci vchan_cyclic_callback(&active->vdesc); 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci if (active->num_completed == active->num_sgs || 39762306a36Sopenharmony_ci sg->partial_len) { 39862306a36Sopenharmony_ci if (active->cyclic) { 39962306a36Sopenharmony_ci active->num_completed = 0; /* wrap around */ 40062306a36Sopenharmony_ci } else { 40162306a36Sopenharmony_ci list_del(&active->vdesc.node); 40262306a36Sopenharmony_ci vchan_cookie_complete(&active->vdesc); 40362306a36Sopenharmony_ci active = axi_dmac_active_desc(chan); 40462306a36Sopenharmony_ci } 40562306a36Sopenharmony_ci } 40662306a36Sopenharmony_ci } while (active); 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci return start_next; 40962306a36Sopenharmony_ci} 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_cistatic irqreturn_t axi_dmac_interrupt_handler(int irq, void *devid) 41262306a36Sopenharmony_ci{ 41362306a36Sopenharmony_ci struct axi_dmac *dmac = devid; 41462306a36Sopenharmony_ci unsigned int pending; 41562306a36Sopenharmony_ci bool start_next = false; 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci pending = axi_dmac_read(dmac, AXI_DMAC_REG_IRQ_PENDING); 41862306a36Sopenharmony_ci if (!pending) 41962306a36Sopenharmony_ci return IRQ_NONE; 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_IRQ_PENDING, pending); 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci spin_lock(&dmac->chan.vchan.lock); 42462306a36Sopenharmony_ci /* One or more transfers have finished */ 42562306a36Sopenharmony_ci if (pending & AXI_DMAC_IRQ_EOT) { 42662306a36Sopenharmony_ci unsigned int completed; 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci completed = axi_dmac_read(dmac, AXI_DMAC_REG_TRANSFER_DONE); 42962306a36Sopenharmony_ci start_next = axi_dmac_transfer_done(&dmac->chan, completed); 43062306a36Sopenharmony_ci } 43162306a36Sopenharmony_ci /* Space has become available in the descriptor queue */ 43262306a36Sopenharmony_ci if ((pending & AXI_DMAC_IRQ_SOT) || start_next) 43362306a36Sopenharmony_ci axi_dmac_start_transfer(&dmac->chan); 43462306a36Sopenharmony_ci spin_unlock(&dmac->chan.vchan.lock); 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci return IRQ_HANDLED; 43762306a36Sopenharmony_ci} 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_cistatic int axi_dmac_terminate_all(struct dma_chan *c) 44062306a36Sopenharmony_ci{ 44162306a36Sopenharmony_ci struct axi_dmac_chan *chan = to_axi_dmac_chan(c); 44262306a36Sopenharmony_ci struct axi_dmac *dmac = chan_to_axi_dmac(chan); 44362306a36Sopenharmony_ci unsigned long flags; 44462306a36Sopenharmony_ci LIST_HEAD(head); 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci spin_lock_irqsave(&chan->vchan.lock, flags); 44762306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0); 44862306a36Sopenharmony_ci chan->next_desc = NULL; 44962306a36Sopenharmony_ci vchan_get_all_descriptors(&chan->vchan, &head); 45062306a36Sopenharmony_ci list_splice_tail_init(&chan->active_descs, &head); 45162306a36Sopenharmony_ci spin_unlock_irqrestore(&chan->vchan.lock, flags); 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci vchan_dma_desc_free_list(&chan->vchan, &head); 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci return 0; 45662306a36Sopenharmony_ci} 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_cistatic void axi_dmac_synchronize(struct dma_chan *c) 45962306a36Sopenharmony_ci{ 46062306a36Sopenharmony_ci struct axi_dmac_chan *chan = to_axi_dmac_chan(c); 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci vchan_synchronize(&chan->vchan); 46362306a36Sopenharmony_ci} 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_cistatic void axi_dmac_issue_pending(struct dma_chan *c) 46662306a36Sopenharmony_ci{ 46762306a36Sopenharmony_ci struct axi_dmac_chan *chan = to_axi_dmac_chan(c); 46862306a36Sopenharmony_ci struct axi_dmac *dmac = chan_to_axi_dmac(chan); 46962306a36Sopenharmony_ci unsigned long flags; 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, AXI_DMAC_CTRL_ENABLE); 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci spin_lock_irqsave(&chan->vchan.lock, flags); 47462306a36Sopenharmony_ci if (vchan_issue_pending(&chan->vchan)) 47562306a36Sopenharmony_ci axi_dmac_start_transfer(chan); 47662306a36Sopenharmony_ci spin_unlock_irqrestore(&chan->vchan.lock, flags); 47762306a36Sopenharmony_ci} 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_cistatic struct axi_dmac_desc *axi_dmac_alloc_desc(unsigned int num_sgs) 48062306a36Sopenharmony_ci{ 48162306a36Sopenharmony_ci struct axi_dmac_desc *desc; 48262306a36Sopenharmony_ci unsigned int i; 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci desc = kzalloc(struct_size(desc, sg, num_sgs), GFP_NOWAIT); 48562306a36Sopenharmony_ci if (!desc) 48662306a36Sopenharmony_ci return NULL; 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci for (i = 0; i < num_sgs; i++) 48962306a36Sopenharmony_ci desc->sg[i].id = AXI_DMAC_SG_UNUSED; 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci desc->num_sgs = num_sgs; 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci return desc; 49462306a36Sopenharmony_ci} 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_cistatic struct axi_dmac_sg *axi_dmac_fill_linear_sg(struct axi_dmac_chan *chan, 49762306a36Sopenharmony_ci enum dma_transfer_direction direction, dma_addr_t addr, 49862306a36Sopenharmony_ci unsigned int num_periods, unsigned int period_len, 49962306a36Sopenharmony_ci struct axi_dmac_sg *sg) 50062306a36Sopenharmony_ci{ 50162306a36Sopenharmony_ci unsigned int num_segments, i; 50262306a36Sopenharmony_ci unsigned int segment_size; 50362306a36Sopenharmony_ci unsigned int len; 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci /* Split into multiple equally sized segments if necessary */ 50662306a36Sopenharmony_ci num_segments = DIV_ROUND_UP(period_len, chan->max_length); 50762306a36Sopenharmony_ci segment_size = DIV_ROUND_UP(period_len, num_segments); 50862306a36Sopenharmony_ci /* Take care of alignment */ 50962306a36Sopenharmony_ci segment_size = ((segment_size - 1) | chan->length_align_mask) + 1; 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci for (i = 0; i < num_periods; i++) { 51262306a36Sopenharmony_ci len = period_len; 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci while (len > segment_size) { 51562306a36Sopenharmony_ci if (direction == DMA_DEV_TO_MEM) 51662306a36Sopenharmony_ci sg->dest_addr = addr; 51762306a36Sopenharmony_ci else 51862306a36Sopenharmony_ci sg->src_addr = addr; 51962306a36Sopenharmony_ci sg->x_len = segment_size; 52062306a36Sopenharmony_ci sg->y_len = 1; 52162306a36Sopenharmony_ci sg++; 52262306a36Sopenharmony_ci addr += segment_size; 52362306a36Sopenharmony_ci len -= segment_size; 52462306a36Sopenharmony_ci } 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci if (direction == DMA_DEV_TO_MEM) 52762306a36Sopenharmony_ci sg->dest_addr = addr; 52862306a36Sopenharmony_ci else 52962306a36Sopenharmony_ci sg->src_addr = addr; 53062306a36Sopenharmony_ci sg->x_len = len; 53162306a36Sopenharmony_ci sg->y_len = 1; 53262306a36Sopenharmony_ci sg++; 53362306a36Sopenharmony_ci addr += len; 53462306a36Sopenharmony_ci } 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci return sg; 53762306a36Sopenharmony_ci} 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_cistatic struct dma_async_tx_descriptor *axi_dmac_prep_slave_sg( 54062306a36Sopenharmony_ci struct dma_chan *c, struct scatterlist *sgl, 54162306a36Sopenharmony_ci unsigned int sg_len, enum dma_transfer_direction direction, 54262306a36Sopenharmony_ci unsigned long flags, void *context) 54362306a36Sopenharmony_ci{ 54462306a36Sopenharmony_ci struct axi_dmac_chan *chan = to_axi_dmac_chan(c); 54562306a36Sopenharmony_ci struct axi_dmac_desc *desc; 54662306a36Sopenharmony_ci struct axi_dmac_sg *dsg; 54762306a36Sopenharmony_ci struct scatterlist *sg; 54862306a36Sopenharmony_ci unsigned int num_sgs; 54962306a36Sopenharmony_ci unsigned int i; 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci if (direction != chan->direction) 55262306a36Sopenharmony_ci return NULL; 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_ci num_sgs = 0; 55562306a36Sopenharmony_ci for_each_sg(sgl, sg, sg_len, i) 55662306a36Sopenharmony_ci num_sgs += DIV_ROUND_UP(sg_dma_len(sg), chan->max_length); 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci desc = axi_dmac_alloc_desc(num_sgs); 55962306a36Sopenharmony_ci if (!desc) 56062306a36Sopenharmony_ci return NULL; 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci dsg = desc->sg; 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci for_each_sg(sgl, sg, sg_len, i) { 56562306a36Sopenharmony_ci if (!axi_dmac_check_addr(chan, sg_dma_address(sg)) || 56662306a36Sopenharmony_ci !axi_dmac_check_len(chan, sg_dma_len(sg))) { 56762306a36Sopenharmony_ci kfree(desc); 56862306a36Sopenharmony_ci return NULL; 56962306a36Sopenharmony_ci } 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci dsg = axi_dmac_fill_linear_sg(chan, direction, sg_dma_address(sg), 1, 57262306a36Sopenharmony_ci sg_dma_len(sg), dsg); 57362306a36Sopenharmony_ci } 57462306a36Sopenharmony_ci 57562306a36Sopenharmony_ci desc->cyclic = false; 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags); 57862306a36Sopenharmony_ci} 57962306a36Sopenharmony_ci 58062306a36Sopenharmony_cistatic struct dma_async_tx_descriptor *axi_dmac_prep_dma_cyclic( 58162306a36Sopenharmony_ci struct dma_chan *c, dma_addr_t buf_addr, size_t buf_len, 58262306a36Sopenharmony_ci size_t period_len, enum dma_transfer_direction direction, 58362306a36Sopenharmony_ci unsigned long flags) 58462306a36Sopenharmony_ci{ 58562306a36Sopenharmony_ci struct axi_dmac_chan *chan = to_axi_dmac_chan(c); 58662306a36Sopenharmony_ci struct axi_dmac_desc *desc; 58762306a36Sopenharmony_ci unsigned int num_periods, num_segments; 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci if (direction != chan->direction) 59062306a36Sopenharmony_ci return NULL; 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci if (!axi_dmac_check_len(chan, buf_len) || 59362306a36Sopenharmony_ci !axi_dmac_check_addr(chan, buf_addr)) 59462306a36Sopenharmony_ci return NULL; 59562306a36Sopenharmony_ci 59662306a36Sopenharmony_ci if (period_len == 0 || buf_len % period_len) 59762306a36Sopenharmony_ci return NULL; 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ci num_periods = buf_len / period_len; 60062306a36Sopenharmony_ci num_segments = DIV_ROUND_UP(period_len, chan->max_length); 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci desc = axi_dmac_alloc_desc(num_periods * num_segments); 60362306a36Sopenharmony_ci if (!desc) 60462306a36Sopenharmony_ci return NULL; 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci axi_dmac_fill_linear_sg(chan, direction, buf_addr, num_periods, 60762306a36Sopenharmony_ci period_len, desc->sg); 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci desc->cyclic = true; 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_ci return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags); 61262306a36Sopenharmony_ci} 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_cistatic struct dma_async_tx_descriptor *axi_dmac_prep_interleaved( 61562306a36Sopenharmony_ci struct dma_chan *c, struct dma_interleaved_template *xt, 61662306a36Sopenharmony_ci unsigned long flags) 61762306a36Sopenharmony_ci{ 61862306a36Sopenharmony_ci struct axi_dmac_chan *chan = to_axi_dmac_chan(c); 61962306a36Sopenharmony_ci struct axi_dmac_desc *desc; 62062306a36Sopenharmony_ci size_t dst_icg, src_icg; 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci if (xt->frame_size != 1) 62362306a36Sopenharmony_ci return NULL; 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci if (xt->dir != chan->direction) 62662306a36Sopenharmony_ci return NULL; 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci if (axi_dmac_src_is_mem(chan)) { 62962306a36Sopenharmony_ci if (!xt->src_inc || !axi_dmac_check_addr(chan, xt->src_start)) 63062306a36Sopenharmony_ci return NULL; 63162306a36Sopenharmony_ci } 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_ci if (axi_dmac_dest_is_mem(chan)) { 63462306a36Sopenharmony_ci if (!xt->dst_inc || !axi_dmac_check_addr(chan, xt->dst_start)) 63562306a36Sopenharmony_ci return NULL; 63662306a36Sopenharmony_ci } 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_ci dst_icg = dmaengine_get_dst_icg(xt, &xt->sgl[0]); 63962306a36Sopenharmony_ci src_icg = dmaengine_get_src_icg(xt, &xt->sgl[0]); 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci if (chan->hw_2d) { 64262306a36Sopenharmony_ci if (!axi_dmac_check_len(chan, xt->sgl[0].size) || 64362306a36Sopenharmony_ci xt->numf == 0) 64462306a36Sopenharmony_ci return NULL; 64562306a36Sopenharmony_ci if (xt->sgl[0].size + dst_icg > chan->max_length || 64662306a36Sopenharmony_ci xt->sgl[0].size + src_icg > chan->max_length) 64762306a36Sopenharmony_ci return NULL; 64862306a36Sopenharmony_ci } else { 64962306a36Sopenharmony_ci if (dst_icg != 0 || src_icg != 0) 65062306a36Sopenharmony_ci return NULL; 65162306a36Sopenharmony_ci if (chan->max_length / xt->sgl[0].size < xt->numf) 65262306a36Sopenharmony_ci return NULL; 65362306a36Sopenharmony_ci if (!axi_dmac_check_len(chan, xt->sgl[0].size * xt->numf)) 65462306a36Sopenharmony_ci return NULL; 65562306a36Sopenharmony_ci } 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_ci desc = axi_dmac_alloc_desc(1); 65862306a36Sopenharmony_ci if (!desc) 65962306a36Sopenharmony_ci return NULL; 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ci if (axi_dmac_src_is_mem(chan)) { 66262306a36Sopenharmony_ci desc->sg[0].src_addr = xt->src_start; 66362306a36Sopenharmony_ci desc->sg[0].src_stride = xt->sgl[0].size + src_icg; 66462306a36Sopenharmony_ci } 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_ci if (axi_dmac_dest_is_mem(chan)) { 66762306a36Sopenharmony_ci desc->sg[0].dest_addr = xt->dst_start; 66862306a36Sopenharmony_ci desc->sg[0].dest_stride = xt->sgl[0].size + dst_icg; 66962306a36Sopenharmony_ci } 67062306a36Sopenharmony_ci 67162306a36Sopenharmony_ci if (chan->hw_2d) { 67262306a36Sopenharmony_ci desc->sg[0].x_len = xt->sgl[0].size; 67362306a36Sopenharmony_ci desc->sg[0].y_len = xt->numf; 67462306a36Sopenharmony_ci } else { 67562306a36Sopenharmony_ci desc->sg[0].x_len = xt->sgl[0].size * xt->numf; 67662306a36Sopenharmony_ci desc->sg[0].y_len = 1; 67762306a36Sopenharmony_ci } 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_ci if (flags & DMA_CYCLIC) 68062306a36Sopenharmony_ci desc->cyclic = true; 68162306a36Sopenharmony_ci 68262306a36Sopenharmony_ci return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags); 68362306a36Sopenharmony_ci} 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_cistatic void axi_dmac_free_chan_resources(struct dma_chan *c) 68662306a36Sopenharmony_ci{ 68762306a36Sopenharmony_ci vchan_free_chan_resources(to_virt_chan(c)); 68862306a36Sopenharmony_ci} 68962306a36Sopenharmony_ci 69062306a36Sopenharmony_cistatic void axi_dmac_desc_free(struct virt_dma_desc *vdesc) 69162306a36Sopenharmony_ci{ 69262306a36Sopenharmony_ci kfree(container_of(vdesc, struct axi_dmac_desc, vdesc)); 69362306a36Sopenharmony_ci} 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_cistatic bool axi_dmac_regmap_rdwr(struct device *dev, unsigned int reg) 69662306a36Sopenharmony_ci{ 69762306a36Sopenharmony_ci switch (reg) { 69862306a36Sopenharmony_ci case AXI_DMAC_REG_IRQ_MASK: 69962306a36Sopenharmony_ci case AXI_DMAC_REG_IRQ_SOURCE: 70062306a36Sopenharmony_ci case AXI_DMAC_REG_IRQ_PENDING: 70162306a36Sopenharmony_ci case AXI_DMAC_REG_CTRL: 70262306a36Sopenharmony_ci case AXI_DMAC_REG_TRANSFER_ID: 70362306a36Sopenharmony_ci case AXI_DMAC_REG_START_TRANSFER: 70462306a36Sopenharmony_ci case AXI_DMAC_REG_FLAGS: 70562306a36Sopenharmony_ci case AXI_DMAC_REG_DEST_ADDRESS: 70662306a36Sopenharmony_ci case AXI_DMAC_REG_SRC_ADDRESS: 70762306a36Sopenharmony_ci case AXI_DMAC_REG_X_LENGTH: 70862306a36Sopenharmony_ci case AXI_DMAC_REG_Y_LENGTH: 70962306a36Sopenharmony_ci case AXI_DMAC_REG_DEST_STRIDE: 71062306a36Sopenharmony_ci case AXI_DMAC_REG_SRC_STRIDE: 71162306a36Sopenharmony_ci case AXI_DMAC_REG_TRANSFER_DONE: 71262306a36Sopenharmony_ci case AXI_DMAC_REG_ACTIVE_TRANSFER_ID: 71362306a36Sopenharmony_ci case AXI_DMAC_REG_STATUS: 71462306a36Sopenharmony_ci case AXI_DMAC_REG_CURRENT_SRC_ADDR: 71562306a36Sopenharmony_ci case AXI_DMAC_REG_CURRENT_DEST_ADDR: 71662306a36Sopenharmony_ci case AXI_DMAC_REG_PARTIAL_XFER_LEN: 71762306a36Sopenharmony_ci case AXI_DMAC_REG_PARTIAL_XFER_ID: 71862306a36Sopenharmony_ci return true; 71962306a36Sopenharmony_ci default: 72062306a36Sopenharmony_ci return false; 72162306a36Sopenharmony_ci } 72262306a36Sopenharmony_ci} 72362306a36Sopenharmony_ci 72462306a36Sopenharmony_cistatic const struct regmap_config axi_dmac_regmap_config = { 72562306a36Sopenharmony_ci .reg_bits = 32, 72662306a36Sopenharmony_ci .val_bits = 32, 72762306a36Sopenharmony_ci .reg_stride = 4, 72862306a36Sopenharmony_ci .max_register = AXI_DMAC_REG_PARTIAL_XFER_ID, 72962306a36Sopenharmony_ci .readable_reg = axi_dmac_regmap_rdwr, 73062306a36Sopenharmony_ci .writeable_reg = axi_dmac_regmap_rdwr, 73162306a36Sopenharmony_ci}; 73262306a36Sopenharmony_ci 73362306a36Sopenharmony_cistatic void axi_dmac_adjust_chan_params(struct axi_dmac_chan *chan) 73462306a36Sopenharmony_ci{ 73562306a36Sopenharmony_ci chan->address_align_mask = max(chan->dest_width, chan->src_width) - 1; 73662306a36Sopenharmony_ci 73762306a36Sopenharmony_ci if (axi_dmac_dest_is_mem(chan) && axi_dmac_src_is_mem(chan)) 73862306a36Sopenharmony_ci chan->direction = DMA_MEM_TO_MEM; 73962306a36Sopenharmony_ci else if (!axi_dmac_dest_is_mem(chan) && axi_dmac_src_is_mem(chan)) 74062306a36Sopenharmony_ci chan->direction = DMA_MEM_TO_DEV; 74162306a36Sopenharmony_ci else if (axi_dmac_dest_is_mem(chan) && !axi_dmac_src_is_mem(chan)) 74262306a36Sopenharmony_ci chan->direction = DMA_DEV_TO_MEM; 74362306a36Sopenharmony_ci else 74462306a36Sopenharmony_ci chan->direction = DMA_DEV_TO_DEV; 74562306a36Sopenharmony_ci} 74662306a36Sopenharmony_ci 74762306a36Sopenharmony_ci/* 74862306a36Sopenharmony_ci * The configuration stored in the devicetree matches the configuration 74962306a36Sopenharmony_ci * parameters of the peripheral instance and allows the driver to know which 75062306a36Sopenharmony_ci * features are implemented and how it should behave. 75162306a36Sopenharmony_ci */ 75262306a36Sopenharmony_cistatic int axi_dmac_parse_chan_dt(struct device_node *of_chan, 75362306a36Sopenharmony_ci struct axi_dmac_chan *chan) 75462306a36Sopenharmony_ci{ 75562306a36Sopenharmony_ci u32 val; 75662306a36Sopenharmony_ci int ret; 75762306a36Sopenharmony_ci 75862306a36Sopenharmony_ci ret = of_property_read_u32(of_chan, "reg", &val); 75962306a36Sopenharmony_ci if (ret) 76062306a36Sopenharmony_ci return ret; 76162306a36Sopenharmony_ci 76262306a36Sopenharmony_ci /* We only support 1 channel for now */ 76362306a36Sopenharmony_ci if (val != 0) 76462306a36Sopenharmony_ci return -EINVAL; 76562306a36Sopenharmony_ci 76662306a36Sopenharmony_ci ret = of_property_read_u32(of_chan, "adi,source-bus-type", &val); 76762306a36Sopenharmony_ci if (ret) 76862306a36Sopenharmony_ci return ret; 76962306a36Sopenharmony_ci if (val > AXI_DMAC_BUS_TYPE_FIFO) 77062306a36Sopenharmony_ci return -EINVAL; 77162306a36Sopenharmony_ci chan->src_type = val; 77262306a36Sopenharmony_ci 77362306a36Sopenharmony_ci ret = of_property_read_u32(of_chan, "adi,destination-bus-type", &val); 77462306a36Sopenharmony_ci if (ret) 77562306a36Sopenharmony_ci return ret; 77662306a36Sopenharmony_ci if (val > AXI_DMAC_BUS_TYPE_FIFO) 77762306a36Sopenharmony_ci return -EINVAL; 77862306a36Sopenharmony_ci chan->dest_type = val; 77962306a36Sopenharmony_ci 78062306a36Sopenharmony_ci ret = of_property_read_u32(of_chan, "adi,source-bus-width", &val); 78162306a36Sopenharmony_ci if (ret) 78262306a36Sopenharmony_ci return ret; 78362306a36Sopenharmony_ci chan->src_width = val / 8; 78462306a36Sopenharmony_ci 78562306a36Sopenharmony_ci ret = of_property_read_u32(of_chan, "adi,destination-bus-width", &val); 78662306a36Sopenharmony_ci if (ret) 78762306a36Sopenharmony_ci return ret; 78862306a36Sopenharmony_ci chan->dest_width = val / 8; 78962306a36Sopenharmony_ci 79062306a36Sopenharmony_ci axi_dmac_adjust_chan_params(chan); 79162306a36Sopenharmony_ci 79262306a36Sopenharmony_ci return 0; 79362306a36Sopenharmony_ci} 79462306a36Sopenharmony_ci 79562306a36Sopenharmony_cistatic int axi_dmac_parse_dt(struct device *dev, struct axi_dmac *dmac) 79662306a36Sopenharmony_ci{ 79762306a36Sopenharmony_ci struct device_node *of_channels, *of_chan; 79862306a36Sopenharmony_ci int ret; 79962306a36Sopenharmony_ci 80062306a36Sopenharmony_ci of_channels = of_get_child_by_name(dev->of_node, "adi,channels"); 80162306a36Sopenharmony_ci if (of_channels == NULL) 80262306a36Sopenharmony_ci return -ENODEV; 80362306a36Sopenharmony_ci 80462306a36Sopenharmony_ci for_each_child_of_node(of_channels, of_chan) { 80562306a36Sopenharmony_ci ret = axi_dmac_parse_chan_dt(of_chan, &dmac->chan); 80662306a36Sopenharmony_ci if (ret) { 80762306a36Sopenharmony_ci of_node_put(of_chan); 80862306a36Sopenharmony_ci of_node_put(of_channels); 80962306a36Sopenharmony_ci return -EINVAL; 81062306a36Sopenharmony_ci } 81162306a36Sopenharmony_ci } 81262306a36Sopenharmony_ci of_node_put(of_channels); 81362306a36Sopenharmony_ci 81462306a36Sopenharmony_ci return 0; 81562306a36Sopenharmony_ci} 81662306a36Sopenharmony_ci 81762306a36Sopenharmony_cistatic int axi_dmac_read_chan_config(struct device *dev, struct axi_dmac *dmac) 81862306a36Sopenharmony_ci{ 81962306a36Sopenharmony_ci struct axi_dmac_chan *chan = &dmac->chan; 82062306a36Sopenharmony_ci unsigned int val, desc; 82162306a36Sopenharmony_ci 82262306a36Sopenharmony_ci desc = axi_dmac_read(dmac, AXI_DMAC_REG_INTERFACE_DESC); 82362306a36Sopenharmony_ci if (desc == 0) { 82462306a36Sopenharmony_ci dev_err(dev, "DMA interface register reads zero\n"); 82562306a36Sopenharmony_ci return -EFAULT; 82662306a36Sopenharmony_ci } 82762306a36Sopenharmony_ci 82862306a36Sopenharmony_ci val = AXI_DMAC_DMA_SRC_TYPE_GET(desc); 82962306a36Sopenharmony_ci if (val > AXI_DMAC_BUS_TYPE_FIFO) { 83062306a36Sopenharmony_ci dev_err(dev, "Invalid source bus type read: %d\n", val); 83162306a36Sopenharmony_ci return -EINVAL; 83262306a36Sopenharmony_ci } 83362306a36Sopenharmony_ci chan->src_type = val; 83462306a36Sopenharmony_ci 83562306a36Sopenharmony_ci val = AXI_DMAC_DMA_DST_TYPE_GET(desc); 83662306a36Sopenharmony_ci if (val > AXI_DMAC_BUS_TYPE_FIFO) { 83762306a36Sopenharmony_ci dev_err(dev, "Invalid destination bus type read: %d\n", val); 83862306a36Sopenharmony_ci return -EINVAL; 83962306a36Sopenharmony_ci } 84062306a36Sopenharmony_ci chan->dest_type = val; 84162306a36Sopenharmony_ci 84262306a36Sopenharmony_ci val = AXI_DMAC_DMA_SRC_WIDTH_GET(desc); 84362306a36Sopenharmony_ci if (val == 0) { 84462306a36Sopenharmony_ci dev_err(dev, "Source bus width is zero\n"); 84562306a36Sopenharmony_ci return -EINVAL; 84662306a36Sopenharmony_ci } 84762306a36Sopenharmony_ci /* widths are stored in log2 */ 84862306a36Sopenharmony_ci chan->src_width = 1 << val; 84962306a36Sopenharmony_ci 85062306a36Sopenharmony_ci val = AXI_DMAC_DMA_DST_WIDTH_GET(desc); 85162306a36Sopenharmony_ci if (val == 0) { 85262306a36Sopenharmony_ci dev_err(dev, "Destination bus width is zero\n"); 85362306a36Sopenharmony_ci return -EINVAL; 85462306a36Sopenharmony_ci } 85562306a36Sopenharmony_ci chan->dest_width = 1 << val; 85662306a36Sopenharmony_ci 85762306a36Sopenharmony_ci axi_dmac_adjust_chan_params(chan); 85862306a36Sopenharmony_ci 85962306a36Sopenharmony_ci return 0; 86062306a36Sopenharmony_ci} 86162306a36Sopenharmony_ci 86262306a36Sopenharmony_cistatic int axi_dmac_detect_caps(struct axi_dmac *dmac, unsigned int version) 86362306a36Sopenharmony_ci{ 86462306a36Sopenharmony_ci struct axi_dmac_chan *chan = &dmac->chan; 86562306a36Sopenharmony_ci 86662306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_FLAGS, AXI_DMAC_FLAG_CYCLIC); 86762306a36Sopenharmony_ci if (axi_dmac_read(dmac, AXI_DMAC_REG_FLAGS) == AXI_DMAC_FLAG_CYCLIC) 86862306a36Sopenharmony_ci chan->hw_cyclic = true; 86962306a36Sopenharmony_ci 87062306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_Y_LENGTH, 1); 87162306a36Sopenharmony_ci if (axi_dmac_read(dmac, AXI_DMAC_REG_Y_LENGTH) == 1) 87262306a36Sopenharmony_ci chan->hw_2d = true; 87362306a36Sopenharmony_ci 87462306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_X_LENGTH, 0xffffffff); 87562306a36Sopenharmony_ci chan->max_length = axi_dmac_read(dmac, AXI_DMAC_REG_X_LENGTH); 87662306a36Sopenharmony_ci if (chan->max_length != UINT_MAX) 87762306a36Sopenharmony_ci chan->max_length++; 87862306a36Sopenharmony_ci 87962306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_DEST_ADDRESS, 0xffffffff); 88062306a36Sopenharmony_ci if (axi_dmac_read(dmac, AXI_DMAC_REG_DEST_ADDRESS) == 0 && 88162306a36Sopenharmony_ci chan->dest_type == AXI_DMAC_BUS_TYPE_AXI_MM) { 88262306a36Sopenharmony_ci dev_err(dmac->dma_dev.dev, 88362306a36Sopenharmony_ci "Destination memory-mapped interface not supported."); 88462306a36Sopenharmony_ci return -ENODEV; 88562306a36Sopenharmony_ci } 88662306a36Sopenharmony_ci 88762306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_SRC_ADDRESS, 0xffffffff); 88862306a36Sopenharmony_ci if (axi_dmac_read(dmac, AXI_DMAC_REG_SRC_ADDRESS) == 0 && 88962306a36Sopenharmony_ci chan->src_type == AXI_DMAC_BUS_TYPE_AXI_MM) { 89062306a36Sopenharmony_ci dev_err(dmac->dma_dev.dev, 89162306a36Sopenharmony_ci "Source memory-mapped interface not supported."); 89262306a36Sopenharmony_ci return -ENODEV; 89362306a36Sopenharmony_ci } 89462306a36Sopenharmony_ci 89562306a36Sopenharmony_ci if (version >= ADI_AXI_PCORE_VER(4, 2, 'a')) 89662306a36Sopenharmony_ci chan->hw_partial_xfer = true; 89762306a36Sopenharmony_ci 89862306a36Sopenharmony_ci if (version >= ADI_AXI_PCORE_VER(4, 1, 'a')) { 89962306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_X_LENGTH, 0x00); 90062306a36Sopenharmony_ci chan->length_align_mask = 90162306a36Sopenharmony_ci axi_dmac_read(dmac, AXI_DMAC_REG_X_LENGTH); 90262306a36Sopenharmony_ci } else { 90362306a36Sopenharmony_ci chan->length_align_mask = chan->address_align_mask; 90462306a36Sopenharmony_ci } 90562306a36Sopenharmony_ci 90662306a36Sopenharmony_ci return 0; 90762306a36Sopenharmony_ci} 90862306a36Sopenharmony_ci 90962306a36Sopenharmony_cistatic int axi_dmac_probe(struct platform_device *pdev) 91062306a36Sopenharmony_ci{ 91162306a36Sopenharmony_ci struct dma_device *dma_dev; 91262306a36Sopenharmony_ci struct axi_dmac *dmac; 91362306a36Sopenharmony_ci struct regmap *regmap; 91462306a36Sopenharmony_ci unsigned int version; 91562306a36Sopenharmony_ci int ret; 91662306a36Sopenharmony_ci 91762306a36Sopenharmony_ci dmac = devm_kzalloc(&pdev->dev, sizeof(*dmac), GFP_KERNEL); 91862306a36Sopenharmony_ci if (!dmac) 91962306a36Sopenharmony_ci return -ENOMEM; 92062306a36Sopenharmony_ci 92162306a36Sopenharmony_ci dmac->irq = platform_get_irq(pdev, 0); 92262306a36Sopenharmony_ci if (dmac->irq < 0) 92362306a36Sopenharmony_ci return dmac->irq; 92462306a36Sopenharmony_ci if (dmac->irq == 0) 92562306a36Sopenharmony_ci return -EINVAL; 92662306a36Sopenharmony_ci 92762306a36Sopenharmony_ci dmac->base = devm_platform_ioremap_resource(pdev, 0); 92862306a36Sopenharmony_ci if (IS_ERR(dmac->base)) 92962306a36Sopenharmony_ci return PTR_ERR(dmac->base); 93062306a36Sopenharmony_ci 93162306a36Sopenharmony_ci dmac->clk = devm_clk_get(&pdev->dev, NULL); 93262306a36Sopenharmony_ci if (IS_ERR(dmac->clk)) 93362306a36Sopenharmony_ci return PTR_ERR(dmac->clk); 93462306a36Sopenharmony_ci 93562306a36Sopenharmony_ci ret = clk_prepare_enable(dmac->clk); 93662306a36Sopenharmony_ci if (ret < 0) 93762306a36Sopenharmony_ci return ret; 93862306a36Sopenharmony_ci 93962306a36Sopenharmony_ci version = axi_dmac_read(dmac, ADI_AXI_REG_VERSION); 94062306a36Sopenharmony_ci 94162306a36Sopenharmony_ci if (version >= ADI_AXI_PCORE_VER(4, 3, 'a')) 94262306a36Sopenharmony_ci ret = axi_dmac_read_chan_config(&pdev->dev, dmac); 94362306a36Sopenharmony_ci else 94462306a36Sopenharmony_ci ret = axi_dmac_parse_dt(&pdev->dev, dmac); 94562306a36Sopenharmony_ci 94662306a36Sopenharmony_ci if (ret < 0) 94762306a36Sopenharmony_ci goto err_clk_disable; 94862306a36Sopenharmony_ci 94962306a36Sopenharmony_ci INIT_LIST_HEAD(&dmac->chan.active_descs); 95062306a36Sopenharmony_ci 95162306a36Sopenharmony_ci dma_set_max_seg_size(&pdev->dev, UINT_MAX); 95262306a36Sopenharmony_ci 95362306a36Sopenharmony_ci dma_dev = &dmac->dma_dev; 95462306a36Sopenharmony_ci dma_cap_set(DMA_SLAVE, dma_dev->cap_mask); 95562306a36Sopenharmony_ci dma_cap_set(DMA_CYCLIC, dma_dev->cap_mask); 95662306a36Sopenharmony_ci dma_cap_set(DMA_INTERLEAVE, dma_dev->cap_mask); 95762306a36Sopenharmony_ci dma_dev->device_free_chan_resources = axi_dmac_free_chan_resources; 95862306a36Sopenharmony_ci dma_dev->device_tx_status = dma_cookie_status; 95962306a36Sopenharmony_ci dma_dev->device_issue_pending = axi_dmac_issue_pending; 96062306a36Sopenharmony_ci dma_dev->device_prep_slave_sg = axi_dmac_prep_slave_sg; 96162306a36Sopenharmony_ci dma_dev->device_prep_dma_cyclic = axi_dmac_prep_dma_cyclic; 96262306a36Sopenharmony_ci dma_dev->device_prep_interleaved_dma = axi_dmac_prep_interleaved; 96362306a36Sopenharmony_ci dma_dev->device_terminate_all = axi_dmac_terminate_all; 96462306a36Sopenharmony_ci dma_dev->device_synchronize = axi_dmac_synchronize; 96562306a36Sopenharmony_ci dma_dev->dev = &pdev->dev; 96662306a36Sopenharmony_ci dma_dev->src_addr_widths = BIT(dmac->chan.src_width); 96762306a36Sopenharmony_ci dma_dev->dst_addr_widths = BIT(dmac->chan.dest_width); 96862306a36Sopenharmony_ci dma_dev->directions = BIT(dmac->chan.direction); 96962306a36Sopenharmony_ci dma_dev->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR; 97062306a36Sopenharmony_ci INIT_LIST_HEAD(&dma_dev->channels); 97162306a36Sopenharmony_ci 97262306a36Sopenharmony_ci dmac->chan.vchan.desc_free = axi_dmac_desc_free; 97362306a36Sopenharmony_ci vchan_init(&dmac->chan.vchan, dma_dev); 97462306a36Sopenharmony_ci 97562306a36Sopenharmony_ci ret = axi_dmac_detect_caps(dmac, version); 97662306a36Sopenharmony_ci if (ret) 97762306a36Sopenharmony_ci goto err_clk_disable; 97862306a36Sopenharmony_ci 97962306a36Sopenharmony_ci dma_dev->copy_align = (dmac->chan.address_align_mask + 1); 98062306a36Sopenharmony_ci 98162306a36Sopenharmony_ci axi_dmac_write(dmac, AXI_DMAC_REG_IRQ_MASK, 0x00); 98262306a36Sopenharmony_ci 98362306a36Sopenharmony_ci if (of_dma_is_coherent(pdev->dev.of_node)) { 98462306a36Sopenharmony_ci ret = axi_dmac_read(dmac, AXI_DMAC_REG_COHERENCY_DESC); 98562306a36Sopenharmony_ci 98662306a36Sopenharmony_ci if (version < ADI_AXI_PCORE_VER(4, 4, 'a') || 98762306a36Sopenharmony_ci !AXI_DMAC_DST_COHERENT_GET(ret)) { 98862306a36Sopenharmony_ci dev_err(dmac->dma_dev.dev, 98962306a36Sopenharmony_ci "Coherent DMA not supported in hardware"); 99062306a36Sopenharmony_ci ret = -EINVAL; 99162306a36Sopenharmony_ci goto err_clk_disable; 99262306a36Sopenharmony_ci } 99362306a36Sopenharmony_ci } 99462306a36Sopenharmony_ci 99562306a36Sopenharmony_ci ret = dma_async_device_register(dma_dev); 99662306a36Sopenharmony_ci if (ret) 99762306a36Sopenharmony_ci goto err_clk_disable; 99862306a36Sopenharmony_ci 99962306a36Sopenharmony_ci ret = of_dma_controller_register(pdev->dev.of_node, 100062306a36Sopenharmony_ci of_dma_xlate_by_chan_id, dma_dev); 100162306a36Sopenharmony_ci if (ret) 100262306a36Sopenharmony_ci goto err_unregister_device; 100362306a36Sopenharmony_ci 100462306a36Sopenharmony_ci ret = request_irq(dmac->irq, axi_dmac_interrupt_handler, IRQF_SHARED, 100562306a36Sopenharmony_ci dev_name(&pdev->dev), dmac); 100662306a36Sopenharmony_ci if (ret) 100762306a36Sopenharmony_ci goto err_unregister_of; 100862306a36Sopenharmony_ci 100962306a36Sopenharmony_ci platform_set_drvdata(pdev, dmac); 101062306a36Sopenharmony_ci 101162306a36Sopenharmony_ci regmap = devm_regmap_init_mmio(&pdev->dev, dmac->base, 101262306a36Sopenharmony_ci &axi_dmac_regmap_config); 101362306a36Sopenharmony_ci if (IS_ERR(regmap)) { 101462306a36Sopenharmony_ci ret = PTR_ERR(regmap); 101562306a36Sopenharmony_ci goto err_free_irq; 101662306a36Sopenharmony_ci } 101762306a36Sopenharmony_ci 101862306a36Sopenharmony_ci return 0; 101962306a36Sopenharmony_ci 102062306a36Sopenharmony_cierr_free_irq: 102162306a36Sopenharmony_ci free_irq(dmac->irq, dmac); 102262306a36Sopenharmony_cierr_unregister_of: 102362306a36Sopenharmony_ci of_dma_controller_free(pdev->dev.of_node); 102462306a36Sopenharmony_cierr_unregister_device: 102562306a36Sopenharmony_ci dma_async_device_unregister(&dmac->dma_dev); 102662306a36Sopenharmony_cierr_clk_disable: 102762306a36Sopenharmony_ci clk_disable_unprepare(dmac->clk); 102862306a36Sopenharmony_ci 102962306a36Sopenharmony_ci return ret; 103062306a36Sopenharmony_ci} 103162306a36Sopenharmony_ci 103262306a36Sopenharmony_cistatic int axi_dmac_remove(struct platform_device *pdev) 103362306a36Sopenharmony_ci{ 103462306a36Sopenharmony_ci struct axi_dmac *dmac = platform_get_drvdata(pdev); 103562306a36Sopenharmony_ci 103662306a36Sopenharmony_ci of_dma_controller_free(pdev->dev.of_node); 103762306a36Sopenharmony_ci free_irq(dmac->irq, dmac); 103862306a36Sopenharmony_ci tasklet_kill(&dmac->chan.vchan.task); 103962306a36Sopenharmony_ci dma_async_device_unregister(&dmac->dma_dev); 104062306a36Sopenharmony_ci clk_disable_unprepare(dmac->clk); 104162306a36Sopenharmony_ci 104262306a36Sopenharmony_ci return 0; 104362306a36Sopenharmony_ci} 104462306a36Sopenharmony_ci 104562306a36Sopenharmony_cistatic const struct of_device_id axi_dmac_of_match_table[] = { 104662306a36Sopenharmony_ci { .compatible = "adi,axi-dmac-1.00.a" }, 104762306a36Sopenharmony_ci { }, 104862306a36Sopenharmony_ci}; 104962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, axi_dmac_of_match_table); 105062306a36Sopenharmony_ci 105162306a36Sopenharmony_cistatic struct platform_driver axi_dmac_driver = { 105262306a36Sopenharmony_ci .driver = { 105362306a36Sopenharmony_ci .name = "dma-axi-dmac", 105462306a36Sopenharmony_ci .of_match_table = axi_dmac_of_match_table, 105562306a36Sopenharmony_ci }, 105662306a36Sopenharmony_ci .probe = axi_dmac_probe, 105762306a36Sopenharmony_ci .remove = axi_dmac_remove, 105862306a36Sopenharmony_ci}; 105962306a36Sopenharmony_cimodule_platform_driver(axi_dmac_driver); 106062306a36Sopenharmony_ci 106162306a36Sopenharmony_ciMODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); 106262306a36Sopenharmony_ciMODULE_DESCRIPTION("DMA controller driver for the AXI-DMAC controller"); 106362306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 1064