162306a36Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0-only */
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Virtual DMA channel support for DMAengine
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2012 Russell King
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci#ifndef VIRT_DMA_H
862306a36Sopenharmony_ci#define VIRT_DMA_H
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/dmaengine.h>
1162306a36Sopenharmony_ci#include <linux/interrupt.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include "dmaengine.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_cistruct virt_dma_desc {
1662306a36Sopenharmony_ci	struct dma_async_tx_descriptor tx;
1762306a36Sopenharmony_ci	struct dmaengine_result tx_result;
1862306a36Sopenharmony_ci	/* protected by vc.lock */
1962306a36Sopenharmony_ci	struct list_head node;
2062306a36Sopenharmony_ci};
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistruct virt_dma_chan {
2362306a36Sopenharmony_ci	struct dma_chan	chan;
2462306a36Sopenharmony_ci	struct tasklet_struct task;
2562306a36Sopenharmony_ci	void (*desc_free)(struct virt_dma_desc *);
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	spinlock_t lock;
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	/* protected by vc.lock */
3062306a36Sopenharmony_ci	struct list_head desc_allocated;
3162306a36Sopenharmony_ci	struct list_head desc_submitted;
3262306a36Sopenharmony_ci	struct list_head desc_issued;
3362306a36Sopenharmony_ci	struct list_head desc_completed;
3462306a36Sopenharmony_ci	struct list_head desc_terminated;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	struct virt_dma_desc *cyclic;
3762306a36Sopenharmony_ci};
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistatic inline struct virt_dma_chan *to_virt_chan(struct dma_chan *chan)
4062306a36Sopenharmony_ci{
4162306a36Sopenharmony_ci	return container_of(chan, struct virt_dma_chan, chan);
4262306a36Sopenharmony_ci}
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_civoid vchan_dma_desc_free_list(struct virt_dma_chan *vc, struct list_head *head);
4562306a36Sopenharmony_civoid vchan_init(struct virt_dma_chan *vc, struct dma_device *dmadev);
4662306a36Sopenharmony_cistruct virt_dma_desc *vchan_find_desc(struct virt_dma_chan *, dma_cookie_t);
4762306a36Sopenharmony_ciextern dma_cookie_t vchan_tx_submit(struct dma_async_tx_descriptor *);
4862306a36Sopenharmony_ciextern int vchan_tx_desc_free(struct dma_async_tx_descriptor *);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci/**
5162306a36Sopenharmony_ci * vchan_tx_prep - prepare a descriptor
5262306a36Sopenharmony_ci * @vc: virtual channel allocating this descriptor
5362306a36Sopenharmony_ci * @vd: virtual descriptor to prepare
5462306a36Sopenharmony_ci * @tx_flags: flags argument passed in to prepare function
5562306a36Sopenharmony_ci */
5662306a36Sopenharmony_cistatic inline struct dma_async_tx_descriptor *vchan_tx_prep(struct virt_dma_chan *vc,
5762306a36Sopenharmony_ci	struct virt_dma_desc *vd, unsigned long tx_flags)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	unsigned long flags;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	dma_async_tx_descriptor_init(&vd->tx, &vc->chan);
6262306a36Sopenharmony_ci	vd->tx.flags = tx_flags;
6362306a36Sopenharmony_ci	vd->tx.tx_submit = vchan_tx_submit;
6462306a36Sopenharmony_ci	vd->tx.desc_free = vchan_tx_desc_free;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	vd->tx_result.result = DMA_TRANS_NOERROR;
6762306a36Sopenharmony_ci	vd->tx_result.residue = 0;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	spin_lock_irqsave(&vc->lock, flags);
7062306a36Sopenharmony_ci	list_add_tail(&vd->node, &vc->desc_allocated);
7162306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc->lock, flags);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	return &vd->tx;
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci/**
7762306a36Sopenharmony_ci * vchan_issue_pending - move submitted descriptors to issued list
7862306a36Sopenharmony_ci * @vc: virtual channel to update
7962306a36Sopenharmony_ci *
8062306a36Sopenharmony_ci * vc.lock must be held by caller
8162306a36Sopenharmony_ci */
8262306a36Sopenharmony_cistatic inline bool vchan_issue_pending(struct virt_dma_chan *vc)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	list_splice_tail_init(&vc->desc_submitted, &vc->desc_issued);
8562306a36Sopenharmony_ci	return !list_empty(&vc->desc_issued);
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci/**
8962306a36Sopenharmony_ci * vchan_cookie_complete - report completion of a descriptor
9062306a36Sopenharmony_ci * @vd: virtual descriptor to update
9162306a36Sopenharmony_ci *
9262306a36Sopenharmony_ci * vc.lock must be held by caller
9362306a36Sopenharmony_ci */
9462306a36Sopenharmony_cistatic inline void vchan_cookie_complete(struct virt_dma_desc *vd)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	struct virt_dma_chan *vc = to_virt_chan(vd->tx.chan);
9762306a36Sopenharmony_ci	dma_cookie_t cookie;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	cookie = vd->tx.cookie;
10062306a36Sopenharmony_ci	dma_cookie_complete(&vd->tx);
10162306a36Sopenharmony_ci	dev_vdbg(vc->chan.device->dev, "txd %p[%x]: marked complete\n",
10262306a36Sopenharmony_ci		 vd, cookie);
10362306a36Sopenharmony_ci	list_add_tail(&vd->node, &vc->desc_completed);
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	tasklet_schedule(&vc->task);
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci/**
10962306a36Sopenharmony_ci * vchan_vdesc_fini - Free or reuse a descriptor
11062306a36Sopenharmony_ci * @vd: virtual descriptor to free/reuse
11162306a36Sopenharmony_ci */
11262306a36Sopenharmony_cistatic inline void vchan_vdesc_fini(struct virt_dma_desc *vd)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	struct virt_dma_chan *vc = to_virt_chan(vd->tx.chan);
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	if (dmaengine_desc_test_reuse(&vd->tx)) {
11762306a36Sopenharmony_ci		unsigned long flags;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci		spin_lock_irqsave(&vc->lock, flags);
12062306a36Sopenharmony_ci		list_add(&vd->node, &vc->desc_allocated);
12162306a36Sopenharmony_ci		spin_unlock_irqrestore(&vc->lock, flags);
12262306a36Sopenharmony_ci	} else {
12362306a36Sopenharmony_ci		vc->desc_free(vd);
12462306a36Sopenharmony_ci	}
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci/**
12862306a36Sopenharmony_ci * vchan_cyclic_callback - report the completion of a period
12962306a36Sopenharmony_ci * @vd: virtual descriptor
13062306a36Sopenharmony_ci */
13162306a36Sopenharmony_cistatic inline void vchan_cyclic_callback(struct virt_dma_desc *vd)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	struct virt_dma_chan *vc = to_virt_chan(vd->tx.chan);
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	vc->cyclic = vd;
13662306a36Sopenharmony_ci	tasklet_schedule(&vc->task);
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci/**
14062306a36Sopenharmony_ci * vchan_terminate_vdesc - Disable pending cyclic callback
14162306a36Sopenharmony_ci * @vd: virtual descriptor to be terminated
14262306a36Sopenharmony_ci *
14362306a36Sopenharmony_ci * vc.lock must be held by caller
14462306a36Sopenharmony_ci */
14562306a36Sopenharmony_cistatic inline void vchan_terminate_vdesc(struct virt_dma_desc *vd)
14662306a36Sopenharmony_ci{
14762306a36Sopenharmony_ci	struct virt_dma_chan *vc = to_virt_chan(vd->tx.chan);
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	list_add_tail(&vd->node, &vc->desc_terminated);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	if (vc->cyclic == vd)
15262306a36Sopenharmony_ci		vc->cyclic = NULL;
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci/**
15662306a36Sopenharmony_ci * vchan_next_desc - peek at the next descriptor to be processed
15762306a36Sopenharmony_ci * @vc: virtual channel to obtain descriptor from
15862306a36Sopenharmony_ci *
15962306a36Sopenharmony_ci * vc.lock must be held by caller
16062306a36Sopenharmony_ci */
16162306a36Sopenharmony_cistatic inline struct virt_dma_desc *vchan_next_desc(struct virt_dma_chan *vc)
16262306a36Sopenharmony_ci{
16362306a36Sopenharmony_ci	return list_first_entry_or_null(&vc->desc_issued,
16462306a36Sopenharmony_ci					struct virt_dma_desc, node);
16562306a36Sopenharmony_ci}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci/**
16862306a36Sopenharmony_ci * vchan_get_all_descriptors - obtain all submitted and issued descriptors
16962306a36Sopenharmony_ci * @vc: virtual channel to get descriptors from
17062306a36Sopenharmony_ci * @head: list of descriptors found
17162306a36Sopenharmony_ci *
17262306a36Sopenharmony_ci * vc.lock must be held by caller
17362306a36Sopenharmony_ci *
17462306a36Sopenharmony_ci * Removes all submitted and issued descriptors from internal lists, and
17562306a36Sopenharmony_ci * provides a list of all descriptors found
17662306a36Sopenharmony_ci */
17762306a36Sopenharmony_cistatic inline void vchan_get_all_descriptors(struct virt_dma_chan *vc,
17862306a36Sopenharmony_ci	struct list_head *head)
17962306a36Sopenharmony_ci{
18062306a36Sopenharmony_ci	list_splice_tail_init(&vc->desc_allocated, head);
18162306a36Sopenharmony_ci	list_splice_tail_init(&vc->desc_submitted, head);
18262306a36Sopenharmony_ci	list_splice_tail_init(&vc->desc_issued, head);
18362306a36Sopenharmony_ci	list_splice_tail_init(&vc->desc_completed, head);
18462306a36Sopenharmony_ci	list_splice_tail_init(&vc->desc_terminated, head);
18562306a36Sopenharmony_ci}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_cistatic inline void vchan_free_chan_resources(struct virt_dma_chan *vc)
18862306a36Sopenharmony_ci{
18962306a36Sopenharmony_ci	struct virt_dma_desc *vd;
19062306a36Sopenharmony_ci	unsigned long flags;
19162306a36Sopenharmony_ci	LIST_HEAD(head);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	spin_lock_irqsave(&vc->lock, flags);
19462306a36Sopenharmony_ci	vchan_get_all_descriptors(vc, &head);
19562306a36Sopenharmony_ci	list_for_each_entry(vd, &head, node)
19662306a36Sopenharmony_ci		dmaengine_desc_clear_reuse(&vd->tx);
19762306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc->lock, flags);
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	vchan_dma_desc_free_list(vc, &head);
20062306a36Sopenharmony_ci}
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci/**
20362306a36Sopenharmony_ci * vchan_synchronize() - synchronize callback execution to the current context
20462306a36Sopenharmony_ci * @vc: virtual channel to synchronize
20562306a36Sopenharmony_ci *
20662306a36Sopenharmony_ci * Makes sure that all scheduled or active callbacks have finished running. For
20762306a36Sopenharmony_ci * proper operation the caller has to ensure that no new callbacks are scheduled
20862306a36Sopenharmony_ci * after the invocation of this function started.
20962306a36Sopenharmony_ci * Free up the terminated cyclic descriptor to prevent memory leakage.
21062306a36Sopenharmony_ci */
21162306a36Sopenharmony_cistatic inline void vchan_synchronize(struct virt_dma_chan *vc)
21262306a36Sopenharmony_ci{
21362306a36Sopenharmony_ci	LIST_HEAD(head);
21462306a36Sopenharmony_ci	unsigned long flags;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	tasklet_kill(&vc->task);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	spin_lock_irqsave(&vc->lock, flags);
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	list_splice_tail_init(&vc->desc_terminated, &head);
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc->lock, flags);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	vchan_dma_desc_free_list(vc, &head);
22562306a36Sopenharmony_ci}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci#endif
228