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