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#include <linux/device.h>
862306a36Sopenharmony_ci#include <linux/dmaengine.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/spinlock.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include "virt-dma.h"
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_cistatic struct virt_dma_desc *to_virt_desc(struct dma_async_tx_descriptor *tx)
1562306a36Sopenharmony_ci{
1662306a36Sopenharmony_ci	return container_of(tx, struct virt_dma_desc, tx);
1762306a36Sopenharmony_ci}
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cidma_cookie_t vchan_tx_submit(struct dma_async_tx_descriptor *tx)
2062306a36Sopenharmony_ci{
2162306a36Sopenharmony_ci	struct virt_dma_chan *vc = to_virt_chan(tx->chan);
2262306a36Sopenharmony_ci	struct virt_dma_desc *vd = to_virt_desc(tx);
2362306a36Sopenharmony_ci	unsigned long flags;
2462306a36Sopenharmony_ci	dma_cookie_t cookie;
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci	spin_lock_irqsave(&vc->lock, flags);
2762306a36Sopenharmony_ci	cookie = dma_cookie_assign(tx);
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	list_move_tail(&vd->node, &vc->desc_submitted);
3062306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc->lock, flags);
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	dev_dbg(vc->chan.device->dev, "vchan %p: txd %p[%x]: submitted\n",
3362306a36Sopenharmony_ci		vc, vd, cookie);
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	return cookie;
3662306a36Sopenharmony_ci}
3762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(vchan_tx_submit);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci/**
4062306a36Sopenharmony_ci * vchan_tx_desc_free - free a reusable descriptor
4162306a36Sopenharmony_ci * @tx: the transfer
4262306a36Sopenharmony_ci *
4362306a36Sopenharmony_ci * This function frees a previously allocated reusable descriptor. The only
4462306a36Sopenharmony_ci * other way is to clear the DMA_CTRL_REUSE flag and submit one last time the
4562306a36Sopenharmony_ci * transfer.
4662306a36Sopenharmony_ci *
4762306a36Sopenharmony_ci * Returns 0 upon success
4862306a36Sopenharmony_ci */
4962306a36Sopenharmony_ciint vchan_tx_desc_free(struct dma_async_tx_descriptor *tx)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	struct virt_dma_chan *vc = to_virt_chan(tx->chan);
5262306a36Sopenharmony_ci	struct virt_dma_desc *vd = to_virt_desc(tx);
5362306a36Sopenharmony_ci	unsigned long flags;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	spin_lock_irqsave(&vc->lock, flags);
5662306a36Sopenharmony_ci	list_del(&vd->node);
5762306a36Sopenharmony_ci	spin_unlock_irqrestore(&vc->lock, flags);
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	dev_dbg(vc->chan.device->dev, "vchan %p: txd %p[%x]: freeing\n",
6062306a36Sopenharmony_ci		vc, vd, vd->tx.cookie);
6162306a36Sopenharmony_ci	vc->desc_free(vd);
6262306a36Sopenharmony_ci	return 0;
6362306a36Sopenharmony_ci}
6462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(vchan_tx_desc_free);
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistruct virt_dma_desc *vchan_find_desc(struct virt_dma_chan *vc,
6762306a36Sopenharmony_ci	dma_cookie_t cookie)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	struct virt_dma_desc *vd;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	list_for_each_entry(vd, &vc->desc_issued, node)
7262306a36Sopenharmony_ci		if (vd->tx.cookie == cookie)
7362306a36Sopenharmony_ci			return vd;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	return NULL;
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(vchan_find_desc);
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci/*
8062306a36Sopenharmony_ci * This tasklet handles the completion of a DMA descriptor by
8162306a36Sopenharmony_ci * calling its callback and freeing it.
8262306a36Sopenharmony_ci */
8362306a36Sopenharmony_cistatic void vchan_complete(struct tasklet_struct *t)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	struct virt_dma_chan *vc = from_tasklet(vc, t, task);
8662306a36Sopenharmony_ci	struct virt_dma_desc *vd, *_vd;
8762306a36Sopenharmony_ci	struct dmaengine_desc_callback cb;
8862306a36Sopenharmony_ci	LIST_HEAD(head);
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	spin_lock_irq(&vc->lock);
9162306a36Sopenharmony_ci	list_splice_tail_init(&vc->desc_completed, &head);
9262306a36Sopenharmony_ci	vd = vc->cyclic;
9362306a36Sopenharmony_ci	if (vd) {
9462306a36Sopenharmony_ci		vc->cyclic = NULL;
9562306a36Sopenharmony_ci		dmaengine_desc_get_callback(&vd->tx, &cb);
9662306a36Sopenharmony_ci	} else {
9762306a36Sopenharmony_ci		memset(&cb, 0, sizeof(cb));
9862306a36Sopenharmony_ci	}
9962306a36Sopenharmony_ci	spin_unlock_irq(&vc->lock);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	dmaengine_desc_callback_invoke(&cb, &vd->tx_result);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	list_for_each_entry_safe(vd, _vd, &head, node) {
10462306a36Sopenharmony_ci		dmaengine_desc_get_callback(&vd->tx, &cb);
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci		list_del(&vd->node);
10762306a36Sopenharmony_ci		dmaengine_desc_callback_invoke(&cb, &vd->tx_result);
10862306a36Sopenharmony_ci		vchan_vdesc_fini(vd);
10962306a36Sopenharmony_ci	}
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_civoid vchan_dma_desc_free_list(struct virt_dma_chan *vc, struct list_head *head)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	struct virt_dma_desc *vd, *_vd;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	list_for_each_entry_safe(vd, _vd, head, node) {
11762306a36Sopenharmony_ci		list_del(&vd->node);
11862306a36Sopenharmony_ci		vchan_vdesc_fini(vd);
11962306a36Sopenharmony_ci	}
12062306a36Sopenharmony_ci}
12162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(vchan_dma_desc_free_list);
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_civoid vchan_init(struct virt_dma_chan *vc, struct dma_device *dmadev)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	dma_cookie_init(&vc->chan);
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	spin_lock_init(&vc->lock);
12862306a36Sopenharmony_ci	INIT_LIST_HEAD(&vc->desc_allocated);
12962306a36Sopenharmony_ci	INIT_LIST_HEAD(&vc->desc_submitted);
13062306a36Sopenharmony_ci	INIT_LIST_HEAD(&vc->desc_issued);
13162306a36Sopenharmony_ci	INIT_LIST_HEAD(&vc->desc_completed);
13262306a36Sopenharmony_ci	INIT_LIST_HEAD(&vc->desc_terminated);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	tasklet_setup(&vc->task, vchan_complete);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	vc->chan.device = dmadev;
13762306a36Sopenharmony_ci	list_add_tail(&vc->chan.device_node, &dmadev->channels);
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(vchan_init);
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ciMODULE_AUTHOR("Russell King");
14262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
143