/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
 * Synopsys DesignWare eDMA core driver
 *
 * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
 */

#ifndef _DW_EDMA_CORE_H
#define _DW_EDMA_CORE_H

#include <linux/atomic.h>
#include <linux/msi.h>
#include <linux/dma/edma.h>
#include <linux/workqueue.h>

#include "../virt-dma.h"

#define EDMA_LL_SZ					24

enum dw_edma_dir {
	EDMA_DIR_WRITE = 0,
	EDMA_DIR_READ
};

enum dw_edma_request {
	EDMA_REQ_NONE = 0,
	EDMA_REQ_STOP,
	EDMA_REQ_PAUSE
};

enum dw_edma_status {
	EDMA_ST_IDLE = 0,
	EDMA_ST_PAUSE,
	EDMA_ST_BUSY
};

enum dw_edma_xfer_type {
	EDMA_XFER_SCATTER_GATHER = 0,
	EDMA_XFER_CYCLIC,
	EDMA_XFER_INTERLEAVED
};

struct dw_edma_chan;
struct dw_edma_chunk;

struct dw_edma_burst {
	u64				sar;
	u64				dar;
	u32				sz;
	/* precalulate summary of previous burst total size */
	u32				xfer_sz;
};

struct dw_edma_desc {
	struct virt_dma_desc		vd;
	struct dw_edma_chan		*chan;

	u32				alloc_sz;

	size_t				done_burst;
	size_t				start_burst;
	u8				cb;
	size_t				nburst;
	struct dw_edma_burst            burst[] __counted_by(nburst);
};

struct dw_edma_chan {
	struct virt_dma_chan		vc;
	struct dw_edma			*dw;
	int				id;
	enum dw_edma_dir		dir;
	u8				func_no;

	u32				ll_max;
	struct dw_edma_region		ll_region;	/* Linked list */

	struct msi_msg			msi;

	enum dw_edma_ch_irq_mode	irq_mode;

	enum dw_edma_request		request;
	enum dw_edma_status		status;
	u8				configured;

	struct dma_slave_config		config;
	bool				non_ll;

	struct work_struct		irq_work;
	atomic_t			irq_pending;
};

struct dw_edma_irq {
	struct msi_msg                  msi;
	struct dw_edma			*dw;

	DECLARE_BITMAP(wr_mask, HDMA_MAX_WR_CH);
	DECLARE_BITMAP(rd_mask, HDMA_MAX_RD_CH);
};

struct dw_edma {
	char				name[32];

	struct dma_device		dma;

	u16				wr_ch_cnt;
	u16				rd_ch_cnt;

	struct dw_edma_irq		*irq;
	int				nr_irqs;

	struct dw_edma_chan		*chan;

	/*
	 * WQ_HIGHPRI keeps completion processing responsive under heavy load;
	 * WQ_UNBOUND lets different channels run on different CPUs.
	 */
	struct workqueue_struct		*wq;

	raw_spinlock_t			lock;		/* Protect v0 shared registers */

	struct dw_edma_chip             *chip;

	const struct dw_edma_core_ops	*core;
};

typedef void (*dw_edma_handler_t)(struct dw_edma_chan *);

struct dw_edma_core_ops {
	void (*off)(struct dw_edma *dw);
	int (*quiesce)(struct dw_edma *dw);
	int (*ch_quiesce)(struct dw_edma_chan *chan);
	u16 (*ch_count)(struct dw_edma *dw, enum dw_edma_dir dir);
	enum dma_status (*ch_status)(struct dw_edma_chan *chan);
	irqreturn_t (*handle_int)(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
				  dw_edma_handler_t done, dw_edma_handler_t abort);
	void (*non_ll_start)(struct dw_edma_chan *chan, struct dw_edma_burst *child);
	void (*ll_data)(struct dw_edma_chan *chan, struct dw_edma_burst *burst,
			u32 idx, bool cb, bool irq);
	void (*ll_link)(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr);
	void (*ch_doorbell)(struct dw_edma_chan *chan);
	void (*ch_enable)(struct dw_edma_chan *chan);
	void (*ch_config)(struct dw_edma_chan *chan);
	void (*debugfs_on)(struct dw_edma *dw);
	void (*ack_emulated_irq)(struct dw_edma *dw);
	resource_size_t (*db_offset)(struct dw_edma *dw);
};

struct dw_edma_sg {
	struct scatterlist		*sgl;
	unsigned int			len;
};

struct dw_edma_cyclic {
	dma_addr_t			paddr;
	size_t				len;
	size_t				cnt;
};

struct dw_edma_transfer {
	struct dma_chan			*dchan;
	union dw_edma_xfer {
		struct dw_edma_sg		sg;
		struct dw_edma_cyclic		cyclic;
		struct dma_interleaved_template *il;
	} xfer;
	enum dma_transfer_direction	direction;
	unsigned long			flags;
	enum dw_edma_xfer_type		type;
};

static inline
struct dw_edma_chan *vc2dw_edma_chan(struct virt_dma_chan *vc)
{
	return container_of(vc, struct dw_edma_chan, vc);
}

static inline
struct dw_edma_chan *dchan2dw_edma_chan(struct dma_chan *dchan)
{
	return vc2dw_edma_chan(to_virt_chan(dchan));
}

static inline u64 dw_edma_core_get_ll_paddr(struct dw_edma_chan *chan)
{
	if (chan->dir == EDMA_DIR_WRITE)
		return chan->dw->chip->ll_region_wr[chan->id].paddr;

	return chan->dw->chip->ll_region_rd[chan->id].paddr;
}

static inline
void dw_edma_core_off(struct dw_edma *dw)
{
	dw->core->off(dw);
}

static inline
int dw_edma_core_quiesce(struct dw_edma *dw)
{
	return dw->core->quiesce(dw);
}

static inline
int dw_edma_core_ch_quiesce(struct dw_edma_chan *chan)
{
	return chan->dw->core->ch_quiesce(chan);
}

static inline
u16 dw_edma_core_ch_count(struct dw_edma *dw, enum dw_edma_dir dir)
{
	return dw->core->ch_count(dw, dir);
}

static inline
enum dma_status dw_edma_core_ch_status(struct dw_edma_chan *chan)
{
	return chan->dw->core->ch_status(chan);
}

static inline irqreturn_t
dw_edma_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
			dw_edma_handler_t done, dw_edma_handler_t abort)
{
	return dw_irq->dw->core->handle_int(dw_irq, dir, done, abort);
}

static inline
void dw_edma_core_ch_config(struct dw_edma_chan *chan)
{
	chan->dw->core->ch_config(chan);
}

static inline void
dw_edma_core_ll_data(struct dw_edma_chan *chan, struct dw_edma_burst *burst,
		     u32 idx, bool cb, bool irq)
{
	chan->dw->core->ll_data(chan, burst, idx, cb, irq);
}

static inline void
dw_edma_core_ll_link(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr)
{
	chan->dw->core->ll_link(chan, idx, cb, addr);
}

static inline void dw_edma_core_ch_doorbell(struct dw_edma_chan *chan)
{
	chan->dw->core->ch_doorbell(chan);
}

static inline void dw_edma_core_ch_enable(struct dw_edma_chan *chan)
{
	chan->dw->core->ch_enable(chan);
}

static inline
void dw_edma_core_debugfs_on(struct dw_edma *dw)
{
	dw->core->debugfs_on(dw);
}

static inline int dw_edma_core_ack_emulated_irq(struct dw_edma *dw)
{
	if (!dw->core->ack_emulated_irq)
		return -EOPNOTSUPP;

	dw->core->ack_emulated_irq(dw);
	return 0;
}

static inline resource_size_t
dw_edma_core_db_offset(struct dw_edma *dw)
{
	return dw->core->db_offset(dw);
}

static inline bool
dw_edma_core_ch_ignore_irq(struct dw_edma_chan *chan)
{
	struct dw_edma *dw = chan->dw;

	if (dw->chip->flags & DW_EDMA_CHIP_LOCAL)
		return chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE;
	else
		return chan->irq_mode == DW_EDMA_CH_IRQ_LOCAL;
}

#endif /* _DW_EDMA_CORE_H */