162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci//
362306a36Sopenharmony_ci// Copyright (c) 2013-2014 Freescale Semiconductor, Inc
462306a36Sopenharmony_ci// Copyright (c) 2017 Sysam, Angelo Dureghello  <angelo@sysam.it>
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/dmapool.h>
762306a36Sopenharmony_ci#include <linux/module.h>
862306a36Sopenharmony_ci#include <linux/slab.h>
962306a36Sopenharmony_ci#include <linux/dma-mapping.h>
1062306a36Sopenharmony_ci#include <linux/pm_runtime.h>
1162306a36Sopenharmony_ci#include <linux/pm_domain.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include "fsl-edma-common.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define EDMA_CR			0x00
1662306a36Sopenharmony_ci#define EDMA_ES			0x04
1762306a36Sopenharmony_ci#define EDMA_ERQ		0x0C
1862306a36Sopenharmony_ci#define EDMA_EEI		0x14
1962306a36Sopenharmony_ci#define EDMA_SERQ		0x1B
2062306a36Sopenharmony_ci#define EDMA_CERQ		0x1A
2162306a36Sopenharmony_ci#define EDMA_SEEI		0x19
2262306a36Sopenharmony_ci#define EDMA_CEEI		0x18
2362306a36Sopenharmony_ci#define EDMA_CINT		0x1F
2462306a36Sopenharmony_ci#define EDMA_CERR		0x1E
2562306a36Sopenharmony_ci#define EDMA_SSRT		0x1D
2662306a36Sopenharmony_ci#define EDMA_CDNE		0x1C
2762306a36Sopenharmony_ci#define EDMA_INTR		0x24
2862306a36Sopenharmony_ci#define EDMA_ERR		0x2C
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#define EDMA64_ERQH		0x08
3162306a36Sopenharmony_ci#define EDMA64_EEIH		0x10
3262306a36Sopenharmony_ci#define EDMA64_SERQ		0x18
3362306a36Sopenharmony_ci#define EDMA64_CERQ		0x19
3462306a36Sopenharmony_ci#define EDMA64_SEEI		0x1a
3562306a36Sopenharmony_ci#define EDMA64_CEEI		0x1b
3662306a36Sopenharmony_ci#define EDMA64_CINT		0x1c
3762306a36Sopenharmony_ci#define EDMA64_CERR		0x1d
3862306a36Sopenharmony_ci#define EDMA64_SSRT		0x1e
3962306a36Sopenharmony_ci#define EDMA64_CDNE		0x1f
4062306a36Sopenharmony_ci#define EDMA64_INTH		0x20
4162306a36Sopenharmony_ci#define EDMA64_INTL		0x24
4262306a36Sopenharmony_ci#define EDMA64_ERRH		0x28
4362306a36Sopenharmony_ci#define EDMA64_ERRL		0x2c
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_civoid fsl_edma_tx_chan_handler(struct fsl_edma_chan *fsl_chan)
4662306a36Sopenharmony_ci{
4762306a36Sopenharmony_ci	spin_lock(&fsl_chan->vchan.lock);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	if (!fsl_chan->edesc) {
5062306a36Sopenharmony_ci		/* terminate_all called before */
5162306a36Sopenharmony_ci		spin_unlock(&fsl_chan->vchan.lock);
5262306a36Sopenharmony_ci		return;
5362306a36Sopenharmony_ci	}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	if (!fsl_chan->edesc->iscyclic) {
5662306a36Sopenharmony_ci		list_del(&fsl_chan->edesc->vdesc.node);
5762306a36Sopenharmony_ci		vchan_cookie_complete(&fsl_chan->edesc->vdesc);
5862306a36Sopenharmony_ci		fsl_chan->edesc = NULL;
5962306a36Sopenharmony_ci		fsl_chan->status = DMA_COMPLETE;
6062306a36Sopenharmony_ci		fsl_chan->idle = true;
6162306a36Sopenharmony_ci	} else {
6262306a36Sopenharmony_ci		vchan_cyclic_callback(&fsl_chan->edesc->vdesc);
6362306a36Sopenharmony_ci	}
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	if (!fsl_chan->edesc)
6662306a36Sopenharmony_ci		fsl_edma_xfer_desc(fsl_chan);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	spin_unlock(&fsl_chan->vchan.lock);
6962306a36Sopenharmony_ci}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic void fsl_edma3_enable_request(struct fsl_edma_chan *fsl_chan)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	u32 val, flags;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	flags = fsl_edma_drvflags(fsl_chan);
7662306a36Sopenharmony_ci	val = edma_readl_chreg(fsl_chan, ch_sbr);
7762306a36Sopenharmony_ci	/* Remote/local swapped wrongly on iMX8 QM Audio edma */
7862306a36Sopenharmony_ci	if (flags & FSL_EDMA_DRV_QUIRK_SWAPPED) {
7962306a36Sopenharmony_ci		if (!fsl_chan->is_rxchan)
8062306a36Sopenharmony_ci			val |= EDMA_V3_CH_SBR_RD;
8162306a36Sopenharmony_ci		else
8262306a36Sopenharmony_ci			val |= EDMA_V3_CH_SBR_WR;
8362306a36Sopenharmony_ci	} else {
8462306a36Sopenharmony_ci		if (fsl_chan->is_rxchan)
8562306a36Sopenharmony_ci			val |= EDMA_V3_CH_SBR_RD;
8662306a36Sopenharmony_ci		else
8762306a36Sopenharmony_ci			val |= EDMA_V3_CH_SBR_WR;
8862306a36Sopenharmony_ci	}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	if (fsl_chan->is_remote)
9162306a36Sopenharmony_ci		val &= ~(EDMA_V3_CH_SBR_RD | EDMA_V3_CH_SBR_WR);
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	edma_writel_chreg(fsl_chan, val, ch_sbr);
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	if (flags & FSL_EDMA_DRV_HAS_CHMUX) {
9662306a36Sopenharmony_ci		/*
9762306a36Sopenharmony_ci		 * ch_mux: With the exception of 0, attempts to write a value
9862306a36Sopenharmony_ci		 * already in use will be forced to 0.
9962306a36Sopenharmony_ci		 */
10062306a36Sopenharmony_ci		if (!edma_readl_chreg(fsl_chan, ch_mux))
10162306a36Sopenharmony_ci			edma_writel_chreg(fsl_chan, fsl_chan->srcid, ch_mux);
10262306a36Sopenharmony_ci	}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	val = edma_readl_chreg(fsl_chan, ch_csr);
10562306a36Sopenharmony_ci	val |= EDMA_V3_CH_CSR_ERQ;
10662306a36Sopenharmony_ci	edma_writel_chreg(fsl_chan, val, ch_csr);
10762306a36Sopenharmony_ci}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistatic void fsl_edma_enable_request(struct fsl_edma_chan *fsl_chan)
11062306a36Sopenharmony_ci{
11162306a36Sopenharmony_ci	struct edma_regs *regs = &fsl_chan->edma->regs;
11262306a36Sopenharmony_ci	u32 ch = fsl_chan->vchan.chan.chan_id;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	if (fsl_edma_drvflags(fsl_chan) & FSL_EDMA_DRV_SPLIT_REG)
11562306a36Sopenharmony_ci		return fsl_edma3_enable_request(fsl_chan);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	if (fsl_chan->edma->drvdata->flags & FSL_EDMA_DRV_WRAP_IO) {
11862306a36Sopenharmony_ci		edma_writeb(fsl_chan->edma, EDMA_SEEI_SEEI(ch), regs->seei);
11962306a36Sopenharmony_ci		edma_writeb(fsl_chan->edma, ch, regs->serq);
12062306a36Sopenharmony_ci	} else {
12162306a36Sopenharmony_ci		/* ColdFire is big endian, and accesses natively
12262306a36Sopenharmony_ci		 * big endian I/O peripherals
12362306a36Sopenharmony_ci		 */
12462306a36Sopenharmony_ci		iowrite8(EDMA_SEEI_SEEI(ch), regs->seei);
12562306a36Sopenharmony_ci		iowrite8(ch, regs->serq);
12662306a36Sopenharmony_ci	}
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic void fsl_edma3_disable_request(struct fsl_edma_chan *fsl_chan)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	u32 val = edma_readl_chreg(fsl_chan, ch_csr);
13262306a36Sopenharmony_ci	u32 flags;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	flags = fsl_edma_drvflags(fsl_chan);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	if (flags & FSL_EDMA_DRV_HAS_CHMUX)
13762306a36Sopenharmony_ci		edma_writel_chreg(fsl_chan, 0, ch_mux);
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	val &= ~EDMA_V3_CH_CSR_ERQ;
14062306a36Sopenharmony_ci	edma_writel_chreg(fsl_chan, val, ch_csr);
14162306a36Sopenharmony_ci}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_civoid fsl_edma_disable_request(struct fsl_edma_chan *fsl_chan)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	struct edma_regs *regs = &fsl_chan->edma->regs;
14662306a36Sopenharmony_ci	u32 ch = fsl_chan->vchan.chan.chan_id;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	if (fsl_edma_drvflags(fsl_chan) & FSL_EDMA_DRV_SPLIT_REG)
14962306a36Sopenharmony_ci		return fsl_edma3_disable_request(fsl_chan);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	if (fsl_chan->edma->drvdata->flags & FSL_EDMA_DRV_WRAP_IO) {
15262306a36Sopenharmony_ci		edma_writeb(fsl_chan->edma, ch, regs->cerq);
15362306a36Sopenharmony_ci		edma_writeb(fsl_chan->edma, EDMA_CEEI_CEEI(ch), regs->ceei);
15462306a36Sopenharmony_ci	} else {
15562306a36Sopenharmony_ci		/* ColdFire is big endian, and accesses natively
15662306a36Sopenharmony_ci		 * big endian I/O peripherals
15762306a36Sopenharmony_ci		 */
15862306a36Sopenharmony_ci		iowrite8(ch, regs->cerq);
15962306a36Sopenharmony_ci		iowrite8(EDMA_CEEI_CEEI(ch), regs->ceei);
16062306a36Sopenharmony_ci	}
16162306a36Sopenharmony_ci}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_cistatic void mux_configure8(struct fsl_edma_chan *fsl_chan, void __iomem *addr,
16462306a36Sopenharmony_ci			   u32 off, u32 slot, bool enable)
16562306a36Sopenharmony_ci{
16662306a36Sopenharmony_ci	u8 val8;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	if (enable)
16962306a36Sopenharmony_ci		val8 = EDMAMUX_CHCFG_ENBL | slot;
17062306a36Sopenharmony_ci	else
17162306a36Sopenharmony_ci		val8 = EDMAMUX_CHCFG_DIS;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	iowrite8(val8, addr + off);
17462306a36Sopenharmony_ci}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_cistatic void mux_configure32(struct fsl_edma_chan *fsl_chan, void __iomem *addr,
17762306a36Sopenharmony_ci			    u32 off, u32 slot, bool enable)
17862306a36Sopenharmony_ci{
17962306a36Sopenharmony_ci	u32 val;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	if (enable)
18262306a36Sopenharmony_ci		val = EDMAMUX_CHCFG_ENBL << 24 | slot;
18362306a36Sopenharmony_ci	else
18462306a36Sopenharmony_ci		val = EDMAMUX_CHCFG_DIS;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	iowrite32(val, addr + off * 4);
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_civoid fsl_edma_chan_mux(struct fsl_edma_chan *fsl_chan,
19062306a36Sopenharmony_ci		       unsigned int slot, bool enable)
19162306a36Sopenharmony_ci{
19262306a36Sopenharmony_ci	u32 ch = fsl_chan->vchan.chan.chan_id;
19362306a36Sopenharmony_ci	void __iomem *muxaddr;
19462306a36Sopenharmony_ci	unsigned int chans_per_mux, ch_off;
19562306a36Sopenharmony_ci	int endian_diff[4] = {3, 1, -1, -3};
19662306a36Sopenharmony_ci	u32 dmamux_nr = fsl_chan->edma->drvdata->dmamuxs;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	if (!dmamux_nr)
19962306a36Sopenharmony_ci		return;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	chans_per_mux = fsl_chan->edma->n_chans / dmamux_nr;
20262306a36Sopenharmony_ci	ch_off = fsl_chan->vchan.chan.chan_id % chans_per_mux;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	if (fsl_chan->edma->drvdata->flags & FSL_EDMA_DRV_MUX_SWAP)
20562306a36Sopenharmony_ci		ch_off += endian_diff[ch_off % 4];
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	muxaddr = fsl_chan->edma->muxbase[ch / chans_per_mux];
20862306a36Sopenharmony_ci	slot = EDMAMUX_CHCFG_SOURCE(slot);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	if (fsl_chan->edma->drvdata->flags & FSL_EDMA_DRV_CONFIG32)
21162306a36Sopenharmony_ci		mux_configure32(fsl_chan, muxaddr, ch_off, slot, enable);
21262306a36Sopenharmony_ci	else
21362306a36Sopenharmony_ci		mux_configure8(fsl_chan, muxaddr, ch_off, slot, enable);
21462306a36Sopenharmony_ci}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cistatic unsigned int fsl_edma_get_tcd_attr(enum dma_slave_buswidth addr_width)
21762306a36Sopenharmony_ci{
21862306a36Sopenharmony_ci	u32 val;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	if (addr_width == DMA_SLAVE_BUSWIDTH_UNDEFINED)
22162306a36Sopenharmony_ci		addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	val = ffs(addr_width) - 1;
22462306a36Sopenharmony_ci	return val | (val << 8);
22562306a36Sopenharmony_ci}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_civoid fsl_edma_free_desc(struct virt_dma_desc *vdesc)
22862306a36Sopenharmony_ci{
22962306a36Sopenharmony_ci	struct fsl_edma_desc *fsl_desc;
23062306a36Sopenharmony_ci	int i;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	fsl_desc = to_fsl_edma_desc(vdesc);
23362306a36Sopenharmony_ci	for (i = 0; i < fsl_desc->n_tcds; i++)
23462306a36Sopenharmony_ci		dma_pool_free(fsl_desc->echan->tcd_pool, fsl_desc->tcd[i].vtcd,
23562306a36Sopenharmony_ci			      fsl_desc->tcd[i].ptcd);
23662306a36Sopenharmony_ci	kfree(fsl_desc);
23762306a36Sopenharmony_ci}
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ciint fsl_edma_terminate_all(struct dma_chan *chan)
24062306a36Sopenharmony_ci{
24162306a36Sopenharmony_ci	struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan);
24262306a36Sopenharmony_ci	unsigned long flags;
24362306a36Sopenharmony_ci	LIST_HEAD(head);
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	spin_lock_irqsave(&fsl_chan->vchan.lock, flags);
24662306a36Sopenharmony_ci	fsl_edma_disable_request(fsl_chan);
24762306a36Sopenharmony_ci	fsl_chan->edesc = NULL;
24862306a36Sopenharmony_ci	fsl_chan->idle = true;
24962306a36Sopenharmony_ci	vchan_get_all_descriptors(&fsl_chan->vchan, &head);
25062306a36Sopenharmony_ci	spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags);
25162306a36Sopenharmony_ci	vchan_dma_desc_free_list(&fsl_chan->vchan, &head);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	if (fsl_edma_drvflags(fsl_chan) & FSL_EDMA_DRV_HAS_PD)
25462306a36Sopenharmony_ci		pm_runtime_allow(fsl_chan->pd_dev);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	return 0;
25762306a36Sopenharmony_ci}
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ciint fsl_edma_pause(struct dma_chan *chan)
26062306a36Sopenharmony_ci{
26162306a36Sopenharmony_ci	struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan);
26262306a36Sopenharmony_ci	unsigned long flags;
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	spin_lock_irqsave(&fsl_chan->vchan.lock, flags);
26562306a36Sopenharmony_ci	if (fsl_chan->edesc) {
26662306a36Sopenharmony_ci		fsl_edma_disable_request(fsl_chan);
26762306a36Sopenharmony_ci		fsl_chan->status = DMA_PAUSED;
26862306a36Sopenharmony_ci		fsl_chan->idle = true;
26962306a36Sopenharmony_ci	}
27062306a36Sopenharmony_ci	spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags);
27162306a36Sopenharmony_ci	return 0;
27262306a36Sopenharmony_ci}
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ciint fsl_edma_resume(struct dma_chan *chan)
27562306a36Sopenharmony_ci{
27662306a36Sopenharmony_ci	struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan);
27762306a36Sopenharmony_ci	unsigned long flags;
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	spin_lock_irqsave(&fsl_chan->vchan.lock, flags);
28062306a36Sopenharmony_ci	if (fsl_chan->edesc) {
28162306a36Sopenharmony_ci		fsl_edma_enable_request(fsl_chan);
28262306a36Sopenharmony_ci		fsl_chan->status = DMA_IN_PROGRESS;
28362306a36Sopenharmony_ci		fsl_chan->idle = false;
28462306a36Sopenharmony_ci	}
28562306a36Sopenharmony_ci	spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags);
28662306a36Sopenharmony_ci	return 0;
28762306a36Sopenharmony_ci}
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_cistatic void fsl_edma_unprep_slave_dma(struct fsl_edma_chan *fsl_chan)
29062306a36Sopenharmony_ci{
29162306a36Sopenharmony_ci	if (fsl_chan->dma_dir != DMA_NONE)
29262306a36Sopenharmony_ci		dma_unmap_resource(fsl_chan->vchan.chan.device->dev,
29362306a36Sopenharmony_ci				   fsl_chan->dma_dev_addr,
29462306a36Sopenharmony_ci				   fsl_chan->dma_dev_size,
29562306a36Sopenharmony_ci				   fsl_chan->dma_dir, 0);
29662306a36Sopenharmony_ci	fsl_chan->dma_dir = DMA_NONE;
29762306a36Sopenharmony_ci}
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_cistatic bool fsl_edma_prep_slave_dma(struct fsl_edma_chan *fsl_chan,
30062306a36Sopenharmony_ci				    enum dma_transfer_direction dir)
30162306a36Sopenharmony_ci{
30262306a36Sopenharmony_ci	struct device *dev = fsl_chan->vchan.chan.device->dev;
30362306a36Sopenharmony_ci	enum dma_data_direction dma_dir;
30462306a36Sopenharmony_ci	phys_addr_t addr = 0;
30562306a36Sopenharmony_ci	u32 size = 0;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	switch (dir) {
30862306a36Sopenharmony_ci	case DMA_MEM_TO_DEV:
30962306a36Sopenharmony_ci		dma_dir = DMA_FROM_DEVICE;
31062306a36Sopenharmony_ci		addr = fsl_chan->cfg.dst_addr;
31162306a36Sopenharmony_ci		size = fsl_chan->cfg.dst_maxburst;
31262306a36Sopenharmony_ci		break;
31362306a36Sopenharmony_ci	case DMA_DEV_TO_MEM:
31462306a36Sopenharmony_ci		dma_dir = DMA_TO_DEVICE;
31562306a36Sopenharmony_ci		addr = fsl_chan->cfg.src_addr;
31662306a36Sopenharmony_ci		size = fsl_chan->cfg.src_maxburst;
31762306a36Sopenharmony_ci		break;
31862306a36Sopenharmony_ci	default:
31962306a36Sopenharmony_ci		dma_dir = DMA_NONE;
32062306a36Sopenharmony_ci		break;
32162306a36Sopenharmony_ci	}
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	/* Already mapped for this config? */
32462306a36Sopenharmony_ci	if (fsl_chan->dma_dir == dma_dir)
32562306a36Sopenharmony_ci		return true;
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	fsl_edma_unprep_slave_dma(fsl_chan);
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	fsl_chan->dma_dev_addr = dma_map_resource(dev, addr, size, dma_dir, 0);
33062306a36Sopenharmony_ci	if (dma_mapping_error(dev, fsl_chan->dma_dev_addr))
33162306a36Sopenharmony_ci		return false;
33262306a36Sopenharmony_ci	fsl_chan->dma_dev_size = size;
33362306a36Sopenharmony_ci	fsl_chan->dma_dir = dma_dir;
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	return true;
33662306a36Sopenharmony_ci}
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ciint fsl_edma_slave_config(struct dma_chan *chan,
33962306a36Sopenharmony_ci				 struct dma_slave_config *cfg)
34062306a36Sopenharmony_ci{
34162306a36Sopenharmony_ci	struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan);
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	memcpy(&fsl_chan->cfg, cfg, sizeof(*cfg));
34462306a36Sopenharmony_ci	fsl_edma_unprep_slave_dma(fsl_chan);
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci	return 0;
34762306a36Sopenharmony_ci}
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_cistatic size_t fsl_edma_desc_residue(struct fsl_edma_chan *fsl_chan,
35062306a36Sopenharmony_ci		struct virt_dma_desc *vdesc, bool in_progress)
35162306a36Sopenharmony_ci{
35262306a36Sopenharmony_ci	struct fsl_edma_desc *edesc = fsl_chan->edesc;
35362306a36Sopenharmony_ci	enum dma_transfer_direction dir = edesc->dirn;
35462306a36Sopenharmony_ci	dma_addr_t cur_addr, dma_addr;
35562306a36Sopenharmony_ci	size_t len, size;
35662306a36Sopenharmony_ci	u32 nbytes = 0;
35762306a36Sopenharmony_ci	int i;
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	/* calculate the total size in this desc */
36062306a36Sopenharmony_ci	for (len = i = 0; i < fsl_chan->edesc->n_tcds; i++) {
36162306a36Sopenharmony_ci		nbytes = le32_to_cpu(edesc->tcd[i].vtcd->nbytes);
36262306a36Sopenharmony_ci		if (nbytes & (EDMA_V3_TCD_NBYTES_DMLOE | EDMA_V3_TCD_NBYTES_SMLOE))
36362306a36Sopenharmony_ci			nbytes = EDMA_V3_TCD_NBYTES_MLOFF_NBYTES(nbytes);
36462306a36Sopenharmony_ci		len += nbytes * le16_to_cpu(edesc->tcd[i].vtcd->biter);
36562306a36Sopenharmony_ci	}
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	if (!in_progress)
36862306a36Sopenharmony_ci		return len;
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	if (dir == DMA_MEM_TO_DEV)
37162306a36Sopenharmony_ci		cur_addr = edma_read_tcdreg(fsl_chan, saddr);
37262306a36Sopenharmony_ci	else
37362306a36Sopenharmony_ci		cur_addr = edma_read_tcdreg(fsl_chan, daddr);
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	/* figure out the finished and calculate the residue */
37662306a36Sopenharmony_ci	for (i = 0; i < fsl_chan->edesc->n_tcds; i++) {
37762306a36Sopenharmony_ci		nbytes = le32_to_cpu(edesc->tcd[i].vtcd->nbytes);
37862306a36Sopenharmony_ci		if (nbytes & (EDMA_V3_TCD_NBYTES_DMLOE | EDMA_V3_TCD_NBYTES_SMLOE))
37962306a36Sopenharmony_ci			nbytes = EDMA_V3_TCD_NBYTES_MLOFF_NBYTES(nbytes);
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci		size = nbytes * le16_to_cpu(edesc->tcd[i].vtcd->biter);
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci		if (dir == DMA_MEM_TO_DEV)
38462306a36Sopenharmony_ci			dma_addr = le32_to_cpu(edesc->tcd[i].vtcd->saddr);
38562306a36Sopenharmony_ci		else
38662306a36Sopenharmony_ci			dma_addr = le32_to_cpu(edesc->tcd[i].vtcd->daddr);
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci		len -= size;
38962306a36Sopenharmony_ci		if (cur_addr >= dma_addr && cur_addr < dma_addr + size) {
39062306a36Sopenharmony_ci			len += dma_addr + size - cur_addr;
39162306a36Sopenharmony_ci			break;
39262306a36Sopenharmony_ci		}
39362306a36Sopenharmony_ci	}
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	return len;
39662306a36Sopenharmony_ci}
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_cienum dma_status fsl_edma_tx_status(struct dma_chan *chan,
39962306a36Sopenharmony_ci		dma_cookie_t cookie, struct dma_tx_state *txstate)
40062306a36Sopenharmony_ci{
40162306a36Sopenharmony_ci	struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan);
40262306a36Sopenharmony_ci	struct virt_dma_desc *vdesc;
40362306a36Sopenharmony_ci	enum dma_status status;
40462306a36Sopenharmony_ci	unsigned long flags;
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	status = dma_cookie_status(chan, cookie, txstate);
40762306a36Sopenharmony_ci	if (status == DMA_COMPLETE)
40862306a36Sopenharmony_ci		return status;
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_ci	if (!txstate)
41162306a36Sopenharmony_ci		return fsl_chan->status;
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	spin_lock_irqsave(&fsl_chan->vchan.lock, flags);
41462306a36Sopenharmony_ci	vdesc = vchan_find_desc(&fsl_chan->vchan, cookie);
41562306a36Sopenharmony_ci	if (fsl_chan->edesc && cookie == fsl_chan->edesc->vdesc.tx.cookie)
41662306a36Sopenharmony_ci		txstate->residue =
41762306a36Sopenharmony_ci			fsl_edma_desc_residue(fsl_chan, vdesc, true);
41862306a36Sopenharmony_ci	else if (vdesc)
41962306a36Sopenharmony_ci		txstate->residue =
42062306a36Sopenharmony_ci			fsl_edma_desc_residue(fsl_chan, vdesc, false);
42162306a36Sopenharmony_ci	else
42262306a36Sopenharmony_ci		txstate->residue = 0;
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags);
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci	return fsl_chan->status;
42762306a36Sopenharmony_ci}
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_cistatic void fsl_edma_set_tcd_regs(struct fsl_edma_chan *fsl_chan,
43062306a36Sopenharmony_ci				  struct fsl_edma_hw_tcd *tcd)
43162306a36Sopenharmony_ci{
43262306a36Sopenharmony_ci	u16 csr = 0;
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	/*
43562306a36Sopenharmony_ci	 * TCD parameters are stored in struct fsl_edma_hw_tcd in little
43662306a36Sopenharmony_ci	 * endian format. However, we need to load the TCD registers in
43762306a36Sopenharmony_ci	 * big- or little-endian obeying the eDMA engine model endian,
43862306a36Sopenharmony_ci	 * and this is performed from specific edma_write functions
43962306a36Sopenharmony_ci	 */
44062306a36Sopenharmony_ci	edma_write_tcdreg(fsl_chan, 0, csr);
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	edma_write_tcdreg(fsl_chan, tcd->saddr, saddr);
44362306a36Sopenharmony_ci	edma_write_tcdreg(fsl_chan, tcd->daddr, daddr);
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	edma_write_tcdreg(fsl_chan, tcd->attr, attr);
44662306a36Sopenharmony_ci	edma_write_tcdreg(fsl_chan, tcd->soff, soff);
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	edma_write_tcdreg(fsl_chan, tcd->nbytes, nbytes);
44962306a36Sopenharmony_ci	edma_write_tcdreg(fsl_chan, tcd->slast, slast);
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci	edma_write_tcdreg(fsl_chan, tcd->citer, citer);
45262306a36Sopenharmony_ci	edma_write_tcdreg(fsl_chan, tcd->biter, biter);
45362306a36Sopenharmony_ci	edma_write_tcdreg(fsl_chan, tcd->doff, doff);
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_ci	edma_write_tcdreg(fsl_chan, tcd->dlast_sga, dlast_sga);
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	csr = le16_to_cpu(tcd->csr);
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_ci	if (fsl_chan->is_sw) {
46062306a36Sopenharmony_ci		csr |= EDMA_TCD_CSR_START;
46162306a36Sopenharmony_ci		tcd->csr = cpu_to_le16(csr);
46262306a36Sopenharmony_ci	}
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	/*
46562306a36Sopenharmony_ci	 * Must clear CHn_CSR[DONE] bit before enable TCDn_CSR[ESG] at EDMAv3
46662306a36Sopenharmony_ci	 * eDMAv4 have not such requirement.
46762306a36Sopenharmony_ci	 * Change MLINK need clear CHn_CSR[DONE] for both eDMAv3 and eDMAv4.
46862306a36Sopenharmony_ci	 */
46962306a36Sopenharmony_ci	if (((fsl_edma_drvflags(fsl_chan) & FSL_EDMA_DRV_CLEAR_DONE_E_SG) &&
47062306a36Sopenharmony_ci		(csr & EDMA_TCD_CSR_E_SG)) ||
47162306a36Sopenharmony_ci	    ((fsl_edma_drvflags(fsl_chan) & FSL_EDMA_DRV_CLEAR_DONE_E_LINK) &&
47262306a36Sopenharmony_ci		(csr & EDMA_TCD_CSR_E_LINK)))
47362306a36Sopenharmony_ci		edma_writel_chreg(fsl_chan, edma_readl_chreg(fsl_chan, ch_csr), ch_csr);
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci	edma_write_tcdreg(fsl_chan, tcd->csr, csr);
47762306a36Sopenharmony_ci}
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_cistatic inline
48062306a36Sopenharmony_civoid fsl_edma_fill_tcd(struct fsl_edma_chan *fsl_chan,
48162306a36Sopenharmony_ci		       struct fsl_edma_hw_tcd *tcd, u32 src, u32 dst,
48262306a36Sopenharmony_ci		       u16 attr, u16 soff, u32 nbytes, u32 slast, u16 citer,
48362306a36Sopenharmony_ci		       u16 biter, u16 doff, u32 dlast_sga, bool major_int,
48462306a36Sopenharmony_ci		       bool disable_req, bool enable_sg)
48562306a36Sopenharmony_ci{
48662306a36Sopenharmony_ci	struct dma_slave_config *cfg = &fsl_chan->cfg;
48762306a36Sopenharmony_ci	u16 csr = 0;
48862306a36Sopenharmony_ci	u32 burst;
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ci	/*
49162306a36Sopenharmony_ci	 * eDMA hardware SGs require the TCDs to be stored in little
49262306a36Sopenharmony_ci	 * endian format irrespective of the register endian model.
49362306a36Sopenharmony_ci	 * So we put the value in little endian in memory, waiting
49462306a36Sopenharmony_ci	 * for fsl_edma_set_tcd_regs doing the swap.
49562306a36Sopenharmony_ci	 */
49662306a36Sopenharmony_ci	tcd->saddr = cpu_to_le32(src);
49762306a36Sopenharmony_ci	tcd->daddr = cpu_to_le32(dst);
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	tcd->attr = cpu_to_le16(attr);
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci	tcd->soff = cpu_to_le16(soff);
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_ci	if (fsl_chan->is_multi_fifo) {
50462306a36Sopenharmony_ci		/* set mloff to support multiple fifo */
50562306a36Sopenharmony_ci		burst = cfg->direction == DMA_DEV_TO_MEM ?
50662306a36Sopenharmony_ci				cfg->src_maxburst : cfg->dst_maxburst;
50762306a36Sopenharmony_ci		nbytes |= EDMA_V3_TCD_NBYTES_MLOFF(-(burst * 4));
50862306a36Sopenharmony_ci		/* enable DMLOE/SMLOE */
50962306a36Sopenharmony_ci		if (cfg->direction == DMA_MEM_TO_DEV) {
51062306a36Sopenharmony_ci			nbytes |= EDMA_V3_TCD_NBYTES_DMLOE;
51162306a36Sopenharmony_ci			nbytes &= ~EDMA_V3_TCD_NBYTES_SMLOE;
51262306a36Sopenharmony_ci		} else {
51362306a36Sopenharmony_ci			nbytes |= EDMA_V3_TCD_NBYTES_SMLOE;
51462306a36Sopenharmony_ci			nbytes &= ~EDMA_V3_TCD_NBYTES_DMLOE;
51562306a36Sopenharmony_ci		}
51662306a36Sopenharmony_ci	}
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci	tcd->nbytes = cpu_to_le32(nbytes);
51962306a36Sopenharmony_ci	tcd->slast = cpu_to_le32(slast);
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	tcd->citer = cpu_to_le16(EDMA_TCD_CITER_CITER(citer));
52262306a36Sopenharmony_ci	tcd->doff = cpu_to_le16(doff);
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci	tcd->dlast_sga = cpu_to_le32(dlast_sga);
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	tcd->biter = cpu_to_le16(EDMA_TCD_BITER_BITER(biter));
52762306a36Sopenharmony_ci	if (major_int)
52862306a36Sopenharmony_ci		csr |= EDMA_TCD_CSR_INT_MAJOR;
52962306a36Sopenharmony_ci
53062306a36Sopenharmony_ci	if (disable_req)
53162306a36Sopenharmony_ci		csr |= EDMA_TCD_CSR_D_REQ;
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci	if (enable_sg)
53462306a36Sopenharmony_ci		csr |= EDMA_TCD_CSR_E_SG;
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_ci	if (fsl_chan->is_rxchan)
53762306a36Sopenharmony_ci		csr |= EDMA_TCD_CSR_ACTIVE;
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci	if (fsl_chan->is_sw)
54062306a36Sopenharmony_ci		csr |= EDMA_TCD_CSR_START;
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_ci	tcd->csr = cpu_to_le16(csr);
54362306a36Sopenharmony_ci}
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_cistatic struct fsl_edma_desc *fsl_edma_alloc_desc(struct fsl_edma_chan *fsl_chan,
54662306a36Sopenharmony_ci		int sg_len)
54762306a36Sopenharmony_ci{
54862306a36Sopenharmony_ci	struct fsl_edma_desc *fsl_desc;
54962306a36Sopenharmony_ci	int i;
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_ci	fsl_desc = kzalloc(struct_size(fsl_desc, tcd, sg_len), GFP_NOWAIT);
55262306a36Sopenharmony_ci	if (!fsl_desc)
55362306a36Sopenharmony_ci		return NULL;
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ci	fsl_desc->echan = fsl_chan;
55662306a36Sopenharmony_ci	fsl_desc->n_tcds = sg_len;
55762306a36Sopenharmony_ci	for (i = 0; i < sg_len; i++) {
55862306a36Sopenharmony_ci		fsl_desc->tcd[i].vtcd = dma_pool_alloc(fsl_chan->tcd_pool,
55962306a36Sopenharmony_ci					GFP_NOWAIT, &fsl_desc->tcd[i].ptcd);
56062306a36Sopenharmony_ci		if (!fsl_desc->tcd[i].vtcd)
56162306a36Sopenharmony_ci			goto err;
56262306a36Sopenharmony_ci	}
56362306a36Sopenharmony_ci	return fsl_desc;
56462306a36Sopenharmony_ci
56562306a36Sopenharmony_cierr:
56662306a36Sopenharmony_ci	while (--i >= 0)
56762306a36Sopenharmony_ci		dma_pool_free(fsl_chan->tcd_pool, fsl_desc->tcd[i].vtcd,
56862306a36Sopenharmony_ci				fsl_desc->tcd[i].ptcd);
56962306a36Sopenharmony_ci	kfree(fsl_desc);
57062306a36Sopenharmony_ci	return NULL;
57162306a36Sopenharmony_ci}
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_cistruct dma_async_tx_descriptor *fsl_edma_prep_dma_cyclic(
57462306a36Sopenharmony_ci		struct dma_chan *chan, dma_addr_t dma_addr, size_t buf_len,
57562306a36Sopenharmony_ci		size_t period_len, enum dma_transfer_direction direction,
57662306a36Sopenharmony_ci		unsigned long flags)
57762306a36Sopenharmony_ci{
57862306a36Sopenharmony_ci	struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan);
57962306a36Sopenharmony_ci	struct fsl_edma_desc *fsl_desc;
58062306a36Sopenharmony_ci	dma_addr_t dma_buf_next;
58162306a36Sopenharmony_ci	bool major_int = true;
58262306a36Sopenharmony_ci	int sg_len, i;
58362306a36Sopenharmony_ci	u32 src_addr, dst_addr, last_sg, nbytes;
58462306a36Sopenharmony_ci	u16 soff, doff, iter;
58562306a36Sopenharmony_ci
58662306a36Sopenharmony_ci	if (!is_slave_direction(direction))
58762306a36Sopenharmony_ci		return NULL;
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci	if (!fsl_edma_prep_slave_dma(fsl_chan, direction))
59062306a36Sopenharmony_ci		return NULL;
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci	sg_len = buf_len / period_len;
59362306a36Sopenharmony_ci	fsl_desc = fsl_edma_alloc_desc(fsl_chan, sg_len);
59462306a36Sopenharmony_ci	if (!fsl_desc)
59562306a36Sopenharmony_ci		return NULL;
59662306a36Sopenharmony_ci	fsl_desc->iscyclic = true;
59762306a36Sopenharmony_ci	fsl_desc->dirn = direction;
59862306a36Sopenharmony_ci
59962306a36Sopenharmony_ci	dma_buf_next = dma_addr;
60062306a36Sopenharmony_ci	if (direction == DMA_MEM_TO_DEV) {
60162306a36Sopenharmony_ci		fsl_chan->attr =
60262306a36Sopenharmony_ci			fsl_edma_get_tcd_attr(fsl_chan->cfg.dst_addr_width);
60362306a36Sopenharmony_ci		nbytes = fsl_chan->cfg.dst_addr_width *
60462306a36Sopenharmony_ci			fsl_chan->cfg.dst_maxburst;
60562306a36Sopenharmony_ci	} else {
60662306a36Sopenharmony_ci		fsl_chan->attr =
60762306a36Sopenharmony_ci			fsl_edma_get_tcd_attr(fsl_chan->cfg.src_addr_width);
60862306a36Sopenharmony_ci		nbytes = fsl_chan->cfg.src_addr_width *
60962306a36Sopenharmony_ci			fsl_chan->cfg.src_maxburst;
61062306a36Sopenharmony_ci	}
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_ci	iter = period_len / nbytes;
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci	for (i = 0; i < sg_len; i++) {
61562306a36Sopenharmony_ci		if (dma_buf_next >= dma_addr + buf_len)
61662306a36Sopenharmony_ci			dma_buf_next = dma_addr;
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_ci		/* get next sg's physical address */
61962306a36Sopenharmony_ci		last_sg = fsl_desc->tcd[(i + 1) % sg_len].ptcd;
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_ci		if (direction == DMA_MEM_TO_DEV) {
62262306a36Sopenharmony_ci			src_addr = dma_buf_next;
62362306a36Sopenharmony_ci			dst_addr = fsl_chan->dma_dev_addr;
62462306a36Sopenharmony_ci			soff = fsl_chan->cfg.dst_addr_width;
62562306a36Sopenharmony_ci			doff = fsl_chan->is_multi_fifo ? 4 : 0;
62662306a36Sopenharmony_ci		} else if (direction == DMA_DEV_TO_MEM) {
62762306a36Sopenharmony_ci			src_addr = fsl_chan->dma_dev_addr;
62862306a36Sopenharmony_ci			dst_addr = dma_buf_next;
62962306a36Sopenharmony_ci			soff = fsl_chan->is_multi_fifo ? 4 : 0;
63062306a36Sopenharmony_ci			doff = fsl_chan->cfg.src_addr_width;
63162306a36Sopenharmony_ci		} else {
63262306a36Sopenharmony_ci			/* DMA_DEV_TO_DEV */
63362306a36Sopenharmony_ci			src_addr = fsl_chan->cfg.src_addr;
63462306a36Sopenharmony_ci			dst_addr = fsl_chan->cfg.dst_addr;
63562306a36Sopenharmony_ci			soff = doff = 0;
63662306a36Sopenharmony_ci			major_int = false;
63762306a36Sopenharmony_ci		}
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci		fsl_edma_fill_tcd(fsl_chan, fsl_desc->tcd[i].vtcd, src_addr, dst_addr,
64062306a36Sopenharmony_ci				  fsl_chan->attr, soff, nbytes, 0, iter,
64162306a36Sopenharmony_ci				  iter, doff, last_sg, major_int, false, true);
64262306a36Sopenharmony_ci		dma_buf_next += period_len;
64362306a36Sopenharmony_ci	}
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci	return vchan_tx_prep(&fsl_chan->vchan, &fsl_desc->vdesc, flags);
64662306a36Sopenharmony_ci}
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_cistruct dma_async_tx_descriptor *fsl_edma_prep_slave_sg(
64962306a36Sopenharmony_ci		struct dma_chan *chan, struct scatterlist *sgl,
65062306a36Sopenharmony_ci		unsigned int sg_len, enum dma_transfer_direction direction,
65162306a36Sopenharmony_ci		unsigned long flags, void *context)
65262306a36Sopenharmony_ci{
65362306a36Sopenharmony_ci	struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan);
65462306a36Sopenharmony_ci	struct fsl_edma_desc *fsl_desc;
65562306a36Sopenharmony_ci	struct scatterlist *sg;
65662306a36Sopenharmony_ci	u32 src_addr, dst_addr, last_sg, nbytes;
65762306a36Sopenharmony_ci	u16 soff, doff, iter;
65862306a36Sopenharmony_ci	int i;
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ci	if (!is_slave_direction(direction))
66162306a36Sopenharmony_ci		return NULL;
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_ci	if (!fsl_edma_prep_slave_dma(fsl_chan, direction))
66462306a36Sopenharmony_ci		return NULL;
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_ci	fsl_desc = fsl_edma_alloc_desc(fsl_chan, sg_len);
66762306a36Sopenharmony_ci	if (!fsl_desc)
66862306a36Sopenharmony_ci		return NULL;
66962306a36Sopenharmony_ci	fsl_desc->iscyclic = false;
67062306a36Sopenharmony_ci	fsl_desc->dirn = direction;
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_ci	if (direction == DMA_MEM_TO_DEV) {
67362306a36Sopenharmony_ci		fsl_chan->attr =
67462306a36Sopenharmony_ci			fsl_edma_get_tcd_attr(fsl_chan->cfg.dst_addr_width);
67562306a36Sopenharmony_ci		nbytes = fsl_chan->cfg.dst_addr_width *
67662306a36Sopenharmony_ci			fsl_chan->cfg.dst_maxburst;
67762306a36Sopenharmony_ci	} else {
67862306a36Sopenharmony_ci		fsl_chan->attr =
67962306a36Sopenharmony_ci			fsl_edma_get_tcd_attr(fsl_chan->cfg.src_addr_width);
68062306a36Sopenharmony_ci		nbytes = fsl_chan->cfg.src_addr_width *
68162306a36Sopenharmony_ci			fsl_chan->cfg.src_maxburst;
68262306a36Sopenharmony_ci	}
68362306a36Sopenharmony_ci
68462306a36Sopenharmony_ci	for_each_sg(sgl, sg, sg_len, i) {
68562306a36Sopenharmony_ci		if (direction == DMA_MEM_TO_DEV) {
68662306a36Sopenharmony_ci			src_addr = sg_dma_address(sg);
68762306a36Sopenharmony_ci			dst_addr = fsl_chan->dma_dev_addr;
68862306a36Sopenharmony_ci			soff = fsl_chan->cfg.dst_addr_width;
68962306a36Sopenharmony_ci			doff = 0;
69062306a36Sopenharmony_ci		} else if (direction == DMA_DEV_TO_MEM) {
69162306a36Sopenharmony_ci			src_addr = fsl_chan->dma_dev_addr;
69262306a36Sopenharmony_ci			dst_addr = sg_dma_address(sg);
69362306a36Sopenharmony_ci			soff = 0;
69462306a36Sopenharmony_ci			doff = fsl_chan->cfg.src_addr_width;
69562306a36Sopenharmony_ci		} else {
69662306a36Sopenharmony_ci			/* DMA_DEV_TO_DEV */
69762306a36Sopenharmony_ci			src_addr = fsl_chan->cfg.src_addr;
69862306a36Sopenharmony_ci			dst_addr = fsl_chan->cfg.dst_addr;
69962306a36Sopenharmony_ci			soff = 0;
70062306a36Sopenharmony_ci			doff = 0;
70162306a36Sopenharmony_ci		}
70262306a36Sopenharmony_ci
70362306a36Sopenharmony_ci		/*
70462306a36Sopenharmony_ci		 * Choose the suitable burst length if sg_dma_len is not
70562306a36Sopenharmony_ci		 * multiple of burst length so that the whole transfer length is
70662306a36Sopenharmony_ci		 * multiple of minor loop(burst length).
70762306a36Sopenharmony_ci		 */
70862306a36Sopenharmony_ci		if (sg_dma_len(sg) % nbytes) {
70962306a36Sopenharmony_ci			u32 width = (direction == DMA_DEV_TO_MEM) ? doff : soff;
71062306a36Sopenharmony_ci			u32 burst = (direction == DMA_DEV_TO_MEM) ?
71162306a36Sopenharmony_ci						fsl_chan->cfg.src_maxburst :
71262306a36Sopenharmony_ci						fsl_chan->cfg.dst_maxburst;
71362306a36Sopenharmony_ci			int j;
71462306a36Sopenharmony_ci
71562306a36Sopenharmony_ci			for (j = burst; j > 1; j--) {
71662306a36Sopenharmony_ci				if (!(sg_dma_len(sg) % (j * width))) {
71762306a36Sopenharmony_ci					nbytes = j * width;
71862306a36Sopenharmony_ci					break;
71962306a36Sopenharmony_ci				}
72062306a36Sopenharmony_ci			}
72162306a36Sopenharmony_ci			/* Set burst size as 1 if there's no suitable one */
72262306a36Sopenharmony_ci			if (j == 1)
72362306a36Sopenharmony_ci				nbytes = width;
72462306a36Sopenharmony_ci		}
72562306a36Sopenharmony_ci		iter = sg_dma_len(sg) / nbytes;
72662306a36Sopenharmony_ci		if (i < sg_len - 1) {
72762306a36Sopenharmony_ci			last_sg = fsl_desc->tcd[(i + 1)].ptcd;
72862306a36Sopenharmony_ci			fsl_edma_fill_tcd(fsl_chan, fsl_desc->tcd[i].vtcd, src_addr,
72962306a36Sopenharmony_ci					  dst_addr, fsl_chan->attr, soff,
73062306a36Sopenharmony_ci					  nbytes, 0, iter, iter, doff, last_sg,
73162306a36Sopenharmony_ci					  false, false, true);
73262306a36Sopenharmony_ci		} else {
73362306a36Sopenharmony_ci			last_sg = 0;
73462306a36Sopenharmony_ci			fsl_edma_fill_tcd(fsl_chan, fsl_desc->tcd[i].vtcd, src_addr,
73562306a36Sopenharmony_ci					  dst_addr, fsl_chan->attr, soff,
73662306a36Sopenharmony_ci					  nbytes, 0, iter, iter, doff, last_sg,
73762306a36Sopenharmony_ci					  true, true, false);
73862306a36Sopenharmony_ci		}
73962306a36Sopenharmony_ci	}
74062306a36Sopenharmony_ci
74162306a36Sopenharmony_ci	return vchan_tx_prep(&fsl_chan->vchan, &fsl_desc->vdesc, flags);
74262306a36Sopenharmony_ci}
74362306a36Sopenharmony_ci
74462306a36Sopenharmony_cistruct dma_async_tx_descriptor *fsl_edma_prep_memcpy(struct dma_chan *chan,
74562306a36Sopenharmony_ci						     dma_addr_t dma_dst, dma_addr_t dma_src,
74662306a36Sopenharmony_ci						     size_t len, unsigned long flags)
74762306a36Sopenharmony_ci{
74862306a36Sopenharmony_ci	struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan);
74962306a36Sopenharmony_ci	struct fsl_edma_desc *fsl_desc;
75062306a36Sopenharmony_ci
75162306a36Sopenharmony_ci	fsl_desc = fsl_edma_alloc_desc(fsl_chan, 1);
75262306a36Sopenharmony_ci	if (!fsl_desc)
75362306a36Sopenharmony_ci		return NULL;
75462306a36Sopenharmony_ci	fsl_desc->iscyclic = false;
75562306a36Sopenharmony_ci
75662306a36Sopenharmony_ci	fsl_chan->is_sw = true;
75762306a36Sopenharmony_ci
75862306a36Sopenharmony_ci	/* To match with copy_align and max_seg_size so 1 tcd is enough */
75962306a36Sopenharmony_ci	fsl_edma_fill_tcd(fsl_chan, fsl_desc->tcd[0].vtcd, dma_src, dma_dst,
76062306a36Sopenharmony_ci			fsl_edma_get_tcd_attr(DMA_SLAVE_BUSWIDTH_32_BYTES),
76162306a36Sopenharmony_ci			32, len, 0, 1, 1, 32, 0, true, true, false);
76262306a36Sopenharmony_ci
76362306a36Sopenharmony_ci	return vchan_tx_prep(&fsl_chan->vchan, &fsl_desc->vdesc, flags);
76462306a36Sopenharmony_ci}
76562306a36Sopenharmony_ci
76662306a36Sopenharmony_civoid fsl_edma_xfer_desc(struct fsl_edma_chan *fsl_chan)
76762306a36Sopenharmony_ci{
76862306a36Sopenharmony_ci	struct virt_dma_desc *vdesc;
76962306a36Sopenharmony_ci
77062306a36Sopenharmony_ci	lockdep_assert_held(&fsl_chan->vchan.lock);
77162306a36Sopenharmony_ci
77262306a36Sopenharmony_ci	vdesc = vchan_next_desc(&fsl_chan->vchan);
77362306a36Sopenharmony_ci	if (!vdesc)
77462306a36Sopenharmony_ci		return;
77562306a36Sopenharmony_ci	fsl_chan->edesc = to_fsl_edma_desc(vdesc);
77662306a36Sopenharmony_ci	fsl_edma_set_tcd_regs(fsl_chan, fsl_chan->edesc->tcd[0].vtcd);
77762306a36Sopenharmony_ci	fsl_edma_enable_request(fsl_chan);
77862306a36Sopenharmony_ci	fsl_chan->status = DMA_IN_PROGRESS;
77962306a36Sopenharmony_ci	fsl_chan->idle = false;
78062306a36Sopenharmony_ci}
78162306a36Sopenharmony_ci
78262306a36Sopenharmony_civoid fsl_edma_issue_pending(struct dma_chan *chan)
78362306a36Sopenharmony_ci{
78462306a36Sopenharmony_ci	struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan);
78562306a36Sopenharmony_ci	unsigned long flags;
78662306a36Sopenharmony_ci
78762306a36Sopenharmony_ci	spin_lock_irqsave(&fsl_chan->vchan.lock, flags);
78862306a36Sopenharmony_ci
78962306a36Sopenharmony_ci	if (unlikely(fsl_chan->pm_state != RUNNING)) {
79062306a36Sopenharmony_ci		spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags);
79162306a36Sopenharmony_ci		/* cannot submit due to suspend */
79262306a36Sopenharmony_ci		return;
79362306a36Sopenharmony_ci	}
79462306a36Sopenharmony_ci
79562306a36Sopenharmony_ci	if (vchan_issue_pending(&fsl_chan->vchan) && !fsl_chan->edesc)
79662306a36Sopenharmony_ci		fsl_edma_xfer_desc(fsl_chan);
79762306a36Sopenharmony_ci
79862306a36Sopenharmony_ci	spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags);
79962306a36Sopenharmony_ci}
80062306a36Sopenharmony_ci
80162306a36Sopenharmony_ciint fsl_edma_alloc_chan_resources(struct dma_chan *chan)
80262306a36Sopenharmony_ci{
80362306a36Sopenharmony_ci	struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan);
80462306a36Sopenharmony_ci
80562306a36Sopenharmony_ci	fsl_chan->tcd_pool = dma_pool_create("tcd_pool", chan->device->dev,
80662306a36Sopenharmony_ci				sizeof(struct fsl_edma_hw_tcd),
80762306a36Sopenharmony_ci				32, 0);
80862306a36Sopenharmony_ci	return 0;
80962306a36Sopenharmony_ci}
81062306a36Sopenharmony_ci
81162306a36Sopenharmony_civoid fsl_edma_free_chan_resources(struct dma_chan *chan)
81262306a36Sopenharmony_ci{
81362306a36Sopenharmony_ci	struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan);
81462306a36Sopenharmony_ci	struct fsl_edma_engine *edma = fsl_chan->edma;
81562306a36Sopenharmony_ci	unsigned long flags;
81662306a36Sopenharmony_ci	LIST_HEAD(head);
81762306a36Sopenharmony_ci
81862306a36Sopenharmony_ci	spin_lock_irqsave(&fsl_chan->vchan.lock, flags);
81962306a36Sopenharmony_ci	fsl_edma_disable_request(fsl_chan);
82062306a36Sopenharmony_ci	if (edma->drvdata->dmamuxs)
82162306a36Sopenharmony_ci		fsl_edma_chan_mux(fsl_chan, 0, false);
82262306a36Sopenharmony_ci	fsl_chan->edesc = NULL;
82362306a36Sopenharmony_ci	vchan_get_all_descriptors(&fsl_chan->vchan, &head);
82462306a36Sopenharmony_ci	fsl_edma_unprep_slave_dma(fsl_chan);
82562306a36Sopenharmony_ci	spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags);
82662306a36Sopenharmony_ci
82762306a36Sopenharmony_ci	vchan_dma_desc_free_list(&fsl_chan->vchan, &head);
82862306a36Sopenharmony_ci	dma_pool_destroy(fsl_chan->tcd_pool);
82962306a36Sopenharmony_ci	fsl_chan->tcd_pool = NULL;
83062306a36Sopenharmony_ci	fsl_chan->is_sw = false;
83162306a36Sopenharmony_ci	fsl_chan->srcid = 0;
83262306a36Sopenharmony_ci}
83362306a36Sopenharmony_ci
83462306a36Sopenharmony_civoid fsl_edma_cleanup_vchan(struct dma_device *dmadev)
83562306a36Sopenharmony_ci{
83662306a36Sopenharmony_ci	struct fsl_edma_chan *chan, *_chan;
83762306a36Sopenharmony_ci
83862306a36Sopenharmony_ci	list_for_each_entry_safe(chan, _chan,
83962306a36Sopenharmony_ci				&dmadev->channels, vchan.chan.device_node) {
84062306a36Sopenharmony_ci		list_del(&chan->vchan.chan.device_node);
84162306a36Sopenharmony_ci		tasklet_kill(&chan->vchan.task);
84262306a36Sopenharmony_ci	}
84362306a36Sopenharmony_ci}
84462306a36Sopenharmony_ci
84562306a36Sopenharmony_ci/*
84662306a36Sopenharmony_ci * On the 32 channels Vybrid/mpc577x edma version, register offsets are
84762306a36Sopenharmony_ci * different compared to ColdFire mcf5441x 64 channels edma.
84862306a36Sopenharmony_ci *
84962306a36Sopenharmony_ci * This function sets up register offsets as per proper declared version
85062306a36Sopenharmony_ci * so must be called in xxx_edma_probe() just after setting the
85162306a36Sopenharmony_ci * edma "version" and "membase" appropriately.
85262306a36Sopenharmony_ci */
85362306a36Sopenharmony_civoid fsl_edma_setup_regs(struct fsl_edma_engine *edma)
85462306a36Sopenharmony_ci{
85562306a36Sopenharmony_ci	bool is64 = !!(edma->drvdata->flags & FSL_EDMA_DRV_EDMA64);
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_ci	edma->regs.cr = edma->membase + EDMA_CR;
85862306a36Sopenharmony_ci	edma->regs.es = edma->membase + EDMA_ES;
85962306a36Sopenharmony_ci	edma->regs.erql = edma->membase + EDMA_ERQ;
86062306a36Sopenharmony_ci	edma->regs.eeil = edma->membase + EDMA_EEI;
86162306a36Sopenharmony_ci
86262306a36Sopenharmony_ci	edma->regs.serq = edma->membase + (is64 ? EDMA64_SERQ : EDMA_SERQ);
86362306a36Sopenharmony_ci	edma->regs.cerq = edma->membase + (is64 ? EDMA64_CERQ : EDMA_CERQ);
86462306a36Sopenharmony_ci	edma->regs.seei = edma->membase + (is64 ? EDMA64_SEEI : EDMA_SEEI);
86562306a36Sopenharmony_ci	edma->regs.ceei = edma->membase + (is64 ? EDMA64_CEEI : EDMA_CEEI);
86662306a36Sopenharmony_ci	edma->regs.cint = edma->membase + (is64 ? EDMA64_CINT : EDMA_CINT);
86762306a36Sopenharmony_ci	edma->regs.cerr = edma->membase + (is64 ? EDMA64_CERR : EDMA_CERR);
86862306a36Sopenharmony_ci	edma->regs.ssrt = edma->membase + (is64 ? EDMA64_SSRT : EDMA_SSRT);
86962306a36Sopenharmony_ci	edma->regs.cdne = edma->membase + (is64 ? EDMA64_CDNE : EDMA_CDNE);
87062306a36Sopenharmony_ci	edma->regs.intl = edma->membase + (is64 ? EDMA64_INTL : EDMA_INTR);
87162306a36Sopenharmony_ci	edma->regs.errl = edma->membase + (is64 ? EDMA64_ERRL : EDMA_ERR);
87262306a36Sopenharmony_ci
87362306a36Sopenharmony_ci	if (is64) {
87462306a36Sopenharmony_ci		edma->regs.erqh = edma->membase + EDMA64_ERQH;
87562306a36Sopenharmony_ci		edma->regs.eeih = edma->membase + EDMA64_EEIH;
87662306a36Sopenharmony_ci		edma->regs.errh = edma->membase + EDMA64_ERRH;
87762306a36Sopenharmony_ci		edma->regs.inth = edma->membase + EDMA64_INTH;
87862306a36Sopenharmony_ci	}
87962306a36Sopenharmony_ci}
88062306a36Sopenharmony_ci
88162306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
882