162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2023 Cai Huoqing
462306a36Sopenharmony_ci * Synopsys DesignWare HDMA v0 core
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/bitfield.h>
862306a36Sopenharmony_ci#include <linux/irqreturn.h>
962306a36Sopenharmony_ci#include <linux/io-64-nonatomic-lo-hi.h>
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include "dw-edma-core.h"
1262306a36Sopenharmony_ci#include "dw-hdma-v0-core.h"
1362306a36Sopenharmony_ci#include "dw-hdma-v0-regs.h"
1462306a36Sopenharmony_ci#include "dw-hdma-v0-debugfs.h"
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cienum dw_hdma_control {
1762306a36Sopenharmony_ci	DW_HDMA_V0_CB					= BIT(0),
1862306a36Sopenharmony_ci	DW_HDMA_V0_TCB					= BIT(1),
1962306a36Sopenharmony_ci	DW_HDMA_V0_LLP					= BIT(2),
2062306a36Sopenharmony_ci	DW_HDMA_V0_LIE					= BIT(3),
2162306a36Sopenharmony_ci	DW_HDMA_V0_RIE					= BIT(4),
2262306a36Sopenharmony_ci	DW_HDMA_V0_CCS					= BIT(8),
2362306a36Sopenharmony_ci	DW_HDMA_V0_LLE					= BIT(9),
2462306a36Sopenharmony_ci};
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic inline struct dw_hdma_v0_regs __iomem *__dw_regs(struct dw_edma *dw)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	return dw->chip->reg_base;
2962306a36Sopenharmony_ci}
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic inline struct dw_hdma_v0_ch_regs __iomem *
3262306a36Sopenharmony_ci__dw_ch_regs(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch)
3362306a36Sopenharmony_ci{
3462306a36Sopenharmony_ci	if (dir == EDMA_DIR_WRITE)
3562306a36Sopenharmony_ci		return &(__dw_regs(dw)->ch[ch].wr);
3662306a36Sopenharmony_ci	else
3762306a36Sopenharmony_ci		return &(__dw_regs(dw)->ch[ch].rd);
3862306a36Sopenharmony_ci}
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci#define SET_CH_32(dw, dir, ch, name, value) \
4162306a36Sopenharmony_ci	writel(value, &(__dw_ch_regs(dw, dir, ch)->name))
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci#define GET_CH_32(dw, dir, ch, name) \
4462306a36Sopenharmony_ci	readl(&(__dw_ch_regs(dw, dir, ch)->name))
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci#define SET_BOTH_CH_32(dw, ch, name, value) \
4762306a36Sopenharmony_ci	do {					\
4862306a36Sopenharmony_ci		writel(value, &(__dw_ch_regs(dw, EDMA_DIR_WRITE, ch)->name));	\
4962306a36Sopenharmony_ci		writel(value, &(__dw_ch_regs(dw, EDMA_DIR_READ, ch)->name));	\
5062306a36Sopenharmony_ci	} while (0)
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci/* HDMA management callbacks */
5362306a36Sopenharmony_cistatic void dw_hdma_v0_core_off(struct dw_edma *dw)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	int id;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	for (id = 0; id < HDMA_V0_MAX_NR_CH; id++) {
5862306a36Sopenharmony_ci		SET_BOTH_CH_32(dw, id, int_setup,
5962306a36Sopenharmony_ci			       HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK);
6062306a36Sopenharmony_ci		SET_BOTH_CH_32(dw, id, int_clear,
6162306a36Sopenharmony_ci			       HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK);
6262306a36Sopenharmony_ci		SET_BOTH_CH_32(dw, id, ch_en, 0);
6362306a36Sopenharmony_ci	}
6462306a36Sopenharmony_ci}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic u16 dw_hdma_v0_core_ch_count(struct dw_edma *dw, enum dw_edma_dir dir)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	/*
6962306a36Sopenharmony_ci	 * The HDMA IP have no way to know the number of hardware channels
7062306a36Sopenharmony_ci	 * available, we set it to maximum channels and let the platform
7162306a36Sopenharmony_ci	 * set the right number of channels.
7262306a36Sopenharmony_ci	 */
7362306a36Sopenharmony_ci	return HDMA_V0_MAX_NR_CH;
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistatic enum dma_status dw_hdma_v0_core_ch_status(struct dw_edma_chan *chan)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	struct dw_edma *dw = chan->dw;
7962306a36Sopenharmony_ci	u32 tmp;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	tmp = FIELD_GET(HDMA_V0_CH_STATUS_MASK,
8262306a36Sopenharmony_ci			GET_CH_32(dw, chan->id, chan->dir, ch_stat));
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	if (tmp == 1)
8562306a36Sopenharmony_ci		return DMA_IN_PROGRESS;
8662306a36Sopenharmony_ci	else if (tmp == 3)
8762306a36Sopenharmony_ci		return DMA_COMPLETE;
8862306a36Sopenharmony_ci	else
8962306a36Sopenharmony_ci		return DMA_ERROR;
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic void dw_hdma_v0_core_clear_done_int(struct dw_edma_chan *chan)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	struct dw_edma *dw = chan->dw;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	SET_CH_32(dw, chan->dir, chan->id, int_clear, HDMA_V0_STOP_INT_MASK);
9762306a36Sopenharmony_ci}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_cistatic void dw_hdma_v0_core_clear_abort_int(struct dw_edma_chan *chan)
10062306a36Sopenharmony_ci{
10162306a36Sopenharmony_ci	struct dw_edma *dw = chan->dw;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	SET_CH_32(dw, chan->dir, chan->id, int_clear, HDMA_V0_ABORT_INT_MASK);
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cistatic u32 dw_hdma_v0_core_status_int(struct dw_edma_chan *chan)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	struct dw_edma *dw = chan->dw;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	return GET_CH_32(dw, chan->dir, chan->id, int_stat);
11162306a36Sopenharmony_ci}
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_cistatic irqreturn_t
11462306a36Sopenharmony_cidw_hdma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
11562306a36Sopenharmony_ci			   dw_edma_handler_t done, dw_edma_handler_t abort)
11662306a36Sopenharmony_ci{
11762306a36Sopenharmony_ci	struct dw_edma *dw = dw_irq->dw;
11862306a36Sopenharmony_ci	unsigned long total, pos, val;
11962306a36Sopenharmony_ci	irqreturn_t ret = IRQ_NONE;
12062306a36Sopenharmony_ci	struct dw_edma_chan *chan;
12162306a36Sopenharmony_ci	unsigned long off, mask;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	if (dir == EDMA_DIR_WRITE) {
12462306a36Sopenharmony_ci		total = dw->wr_ch_cnt;
12562306a36Sopenharmony_ci		off = 0;
12662306a36Sopenharmony_ci		mask = dw_irq->wr_mask;
12762306a36Sopenharmony_ci	} else {
12862306a36Sopenharmony_ci		total = dw->rd_ch_cnt;
12962306a36Sopenharmony_ci		off = dw->wr_ch_cnt;
13062306a36Sopenharmony_ci		mask = dw_irq->rd_mask;
13162306a36Sopenharmony_ci	}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	for_each_set_bit(pos, &mask, total) {
13462306a36Sopenharmony_ci		chan = &dw->chan[pos + off];
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci		val = dw_hdma_v0_core_status_int(chan);
13762306a36Sopenharmony_ci		if (FIELD_GET(HDMA_V0_STOP_INT_MASK, val)) {
13862306a36Sopenharmony_ci			dw_hdma_v0_core_clear_done_int(chan);
13962306a36Sopenharmony_ci			done(chan);
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci			ret = IRQ_HANDLED;
14262306a36Sopenharmony_ci		}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci		if (FIELD_GET(HDMA_V0_ABORT_INT_MASK, val)) {
14562306a36Sopenharmony_ci			dw_hdma_v0_core_clear_abort_int(chan);
14662306a36Sopenharmony_ci			abort(chan);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci			ret = IRQ_HANDLED;
14962306a36Sopenharmony_ci		}
15062306a36Sopenharmony_ci	}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	return ret;
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic void dw_hdma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,
15662306a36Sopenharmony_ci				     u32 control, u32 size, u64 sar, u64 dar)
15762306a36Sopenharmony_ci{
15862306a36Sopenharmony_ci	ptrdiff_t ofs = i * sizeof(struct dw_hdma_v0_lli);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
16162306a36Sopenharmony_ci		struct dw_hdma_v0_lli *lli = chunk->ll_region.vaddr.mem + ofs;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci		lli->control = control;
16462306a36Sopenharmony_ci		lli->transfer_size = size;
16562306a36Sopenharmony_ci		lli->sar.reg = sar;
16662306a36Sopenharmony_ci		lli->dar.reg = dar;
16762306a36Sopenharmony_ci	} else {
16862306a36Sopenharmony_ci		struct dw_hdma_v0_lli __iomem *lli = chunk->ll_region.vaddr.io + ofs;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci		writel(control, &lli->control);
17162306a36Sopenharmony_ci		writel(size, &lli->transfer_size);
17262306a36Sopenharmony_ci		writeq(sar, &lli->sar.reg);
17362306a36Sopenharmony_ci		writeq(dar, &lli->dar.reg);
17462306a36Sopenharmony_ci	}
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cistatic void dw_hdma_v0_write_ll_link(struct dw_edma_chunk *chunk,
17862306a36Sopenharmony_ci				     int i, u32 control, u64 pointer)
17962306a36Sopenharmony_ci{
18062306a36Sopenharmony_ci	ptrdiff_t ofs = i * sizeof(struct dw_hdma_v0_lli);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
18362306a36Sopenharmony_ci		struct dw_hdma_v0_llp *llp = chunk->ll_region.vaddr.mem + ofs;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci		llp->control = control;
18662306a36Sopenharmony_ci		llp->llp.reg = pointer;
18762306a36Sopenharmony_ci	} else {
18862306a36Sopenharmony_ci		struct dw_hdma_v0_llp __iomem *llp = chunk->ll_region.vaddr.io + ofs;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci		writel(control, &llp->control);
19162306a36Sopenharmony_ci		writeq(pointer, &llp->llp.reg);
19262306a36Sopenharmony_ci	}
19362306a36Sopenharmony_ci}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_cistatic void dw_hdma_v0_core_write_chunk(struct dw_edma_chunk *chunk)
19662306a36Sopenharmony_ci{
19762306a36Sopenharmony_ci	struct dw_edma_burst *child;
19862306a36Sopenharmony_ci	struct dw_edma_chan *chan = chunk->chan;
19962306a36Sopenharmony_ci	u32 control = 0, i = 0;
20062306a36Sopenharmony_ci	int j;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	if (chunk->cb)
20362306a36Sopenharmony_ci		control = DW_HDMA_V0_CB;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	j = chunk->bursts_alloc;
20662306a36Sopenharmony_ci	list_for_each_entry(child, &chunk->burst->list, list) {
20762306a36Sopenharmony_ci		j--;
20862306a36Sopenharmony_ci		if (!j) {
20962306a36Sopenharmony_ci			control |= DW_HDMA_V0_LIE;
21062306a36Sopenharmony_ci			if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
21162306a36Sopenharmony_ci				control |= DW_HDMA_V0_RIE;
21262306a36Sopenharmony_ci		}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci		dw_hdma_v0_write_ll_data(chunk, i++, control, child->sz,
21562306a36Sopenharmony_ci					 child->sar, child->dar);
21662306a36Sopenharmony_ci	}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	control = DW_HDMA_V0_LLP | DW_HDMA_V0_TCB;
21962306a36Sopenharmony_ci	if (!chunk->cb)
22062306a36Sopenharmony_ci		control |= DW_HDMA_V0_CB;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	dw_hdma_v0_write_ll_link(chunk, i, control, chunk->ll_region.paddr);
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_cistatic void dw_hdma_v0_sync_ll_data(struct dw_edma_chunk *chunk)
22662306a36Sopenharmony_ci{
22762306a36Sopenharmony_ci	/*
22862306a36Sopenharmony_ci	 * In case of remote HDMA engine setup, the DW PCIe RP/EP internal
22962306a36Sopenharmony_ci	 * configuration registers and application memory are normally accessed
23062306a36Sopenharmony_ci	 * over different buses. Ensure LL-data reaches the memory before the
23162306a36Sopenharmony_ci	 * doorbell register is toggled by issuing the dummy-read from the remote
23262306a36Sopenharmony_ci	 * LL memory in a hope that the MRd TLP will return only after the
23362306a36Sopenharmony_ci	 * last MWr TLP is completed
23462306a36Sopenharmony_ci	 */
23562306a36Sopenharmony_ci	if (!(chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
23662306a36Sopenharmony_ci		readl(chunk->ll_region.vaddr.io);
23762306a36Sopenharmony_ci}
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_cistatic void dw_hdma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
24062306a36Sopenharmony_ci{
24162306a36Sopenharmony_ci	struct dw_edma_chan *chan = chunk->chan;
24262306a36Sopenharmony_ci	struct dw_edma *dw = chan->dw;
24362306a36Sopenharmony_ci	u32 tmp;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	dw_hdma_v0_core_write_chunk(chunk);
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	if (first) {
24862306a36Sopenharmony_ci		/* Enable engine */
24962306a36Sopenharmony_ci		SET_CH_32(dw, chan->dir, chan->id, ch_en, BIT(0));
25062306a36Sopenharmony_ci		/* Interrupt enable&unmask - done, abort */
25162306a36Sopenharmony_ci		tmp = GET_CH_32(dw, chan->dir, chan->id, int_setup) |
25262306a36Sopenharmony_ci		      HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK |
25362306a36Sopenharmony_ci		      HDMA_V0_LOCAL_STOP_INT_EN | HDMA_V0_LOCAL_ABORT_INT_EN;
25462306a36Sopenharmony_ci		if (!(dw->chip->flags & DW_EDMA_CHIP_LOCAL))
25562306a36Sopenharmony_ci			tmp |= HDMA_V0_REMOTE_STOP_INT_EN | HDMA_V0_REMOTE_ABORT_INT_EN;
25662306a36Sopenharmony_ci		SET_CH_32(dw, chan->dir, chan->id, int_setup, tmp);
25762306a36Sopenharmony_ci		/* Channel control */
25862306a36Sopenharmony_ci		SET_CH_32(dw, chan->dir, chan->id, control1, HDMA_V0_LINKLIST_EN);
25962306a36Sopenharmony_ci		/* Linked list */
26062306a36Sopenharmony_ci		/* llp is not aligned on 64bit -> keep 32bit accesses */
26162306a36Sopenharmony_ci		SET_CH_32(dw, chan->dir, chan->id, llp.lsb,
26262306a36Sopenharmony_ci			  lower_32_bits(chunk->ll_region.paddr));
26362306a36Sopenharmony_ci		SET_CH_32(dw, chan->dir, chan->id, llp.msb,
26462306a36Sopenharmony_ci			  upper_32_bits(chunk->ll_region.paddr));
26562306a36Sopenharmony_ci	}
26662306a36Sopenharmony_ci	/* Set consumer cycle */
26762306a36Sopenharmony_ci	SET_CH_32(dw, chan->dir, chan->id, cycle_sync,
26862306a36Sopenharmony_ci		  HDMA_V0_CONSUMER_CYCLE_STAT | HDMA_V0_CONSUMER_CYCLE_BIT);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	dw_hdma_v0_sync_ll_data(chunk);
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	/* Doorbell */
27362306a36Sopenharmony_ci	SET_CH_32(dw, chan->dir, chan->id, doorbell, HDMA_V0_DOORBELL_START);
27462306a36Sopenharmony_ci}
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_cistatic void dw_hdma_v0_core_ch_config(struct dw_edma_chan *chan)
27762306a36Sopenharmony_ci{
27862306a36Sopenharmony_ci	struct dw_edma *dw = chan->dw;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	/* MSI done addr - low, high */
28162306a36Sopenharmony_ci	SET_CH_32(dw, chan->dir, chan->id, msi_stop.lsb, chan->msi.address_lo);
28262306a36Sopenharmony_ci	SET_CH_32(dw, chan->dir, chan->id, msi_stop.msb, chan->msi.address_hi);
28362306a36Sopenharmony_ci	/* MSI abort addr - low, high */
28462306a36Sopenharmony_ci	SET_CH_32(dw, chan->dir, chan->id, msi_abort.lsb, chan->msi.address_lo);
28562306a36Sopenharmony_ci	SET_CH_32(dw, chan->dir, chan->id, msi_abort.msb, chan->msi.address_hi);
28662306a36Sopenharmony_ci	/* config MSI data */
28762306a36Sopenharmony_ci	SET_CH_32(dw, chan->dir, chan->id, msi_msgdata, chan->msi.data);
28862306a36Sopenharmony_ci}
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci/* HDMA debugfs callbacks */
29162306a36Sopenharmony_cistatic void dw_hdma_v0_core_debugfs_on(struct dw_edma *dw)
29262306a36Sopenharmony_ci{
29362306a36Sopenharmony_ci	dw_hdma_v0_debugfs_on(dw);
29462306a36Sopenharmony_ci}
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_cistatic const struct dw_edma_core_ops dw_hdma_v0_core = {
29762306a36Sopenharmony_ci	.off = dw_hdma_v0_core_off,
29862306a36Sopenharmony_ci	.ch_count = dw_hdma_v0_core_ch_count,
29962306a36Sopenharmony_ci	.ch_status = dw_hdma_v0_core_ch_status,
30062306a36Sopenharmony_ci	.handle_int = dw_hdma_v0_core_handle_int,
30162306a36Sopenharmony_ci	.start = dw_hdma_v0_core_start,
30262306a36Sopenharmony_ci	.ch_config = dw_hdma_v0_core_ch_config,
30362306a36Sopenharmony_ci	.debugfs_on = dw_hdma_v0_core_debugfs_on,
30462306a36Sopenharmony_ci};
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_civoid dw_hdma_v0_core_register(struct dw_edma *dw)
30762306a36Sopenharmony_ci{
30862306a36Sopenharmony_ci	dw->core = &dw_hdma_v0_core;
30962306a36Sopenharmony_ci}
310