Devicetree
 help / color / mirror / Atom feed
From: Vinod Koul <vkoul@kernel.org>
To: CL Wang <cl634@andestech.com>
Cc: Frank.Li@kernel.org, robh@kernel.org, krzk+dt@kernel.org,
	conor+dt@kernel.org, dmaengine@vger.kernel.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	tim609@andestech.com
Subject: Re: [PATCH v4 2/3] dmaengine: atcdmac300: Add driver for Andes ATCDMAC300 DMA controller
Date: Mon, 8 Jun 2026 16:36:04 +0530	[thread overview]
Message-ID: <aiaiHGsWpzkUI73R@vaman> (raw)
In-Reply-To: <20260601094846.1097678-3-cl634@andestech.com>

On 01-06-26, 17:48, CL Wang wrote:
> This patch adds support for the Andes ATCDMAC300 DMA controller.
> 
> The ATCDMAC300 is a memory-to-memory and peripheral DMA controller
> that provides scatter-gather, cyclic, and slave transfer capabilities.
> 
> Signed-off-by: CL Wang <cl634@andestech.com>
> 
> ---
>   Changes for v4:
>     - No changes from v3
> 
>   Changes for v3:
>     - Remove "andestech,atcdmac300" from of_device_id
>     - Replace deprecated tasklet with threaded IRQ using
>       devm_request_threaded_irq() and IRQF_ONESHOT to handle bottom-half
>       processing.
>     - Update locking mechanism from spin_lock_bh() to spin_lock_irqsave()
>     - Minor cleanups and correctness fixes
>         - Initialize descriptor pointers (first = NULL) explicitly
>         - Add missing headers (err.h, iopoll.h, log2.h, sprintf.h)
>         - Remove unused code paths related to tasklets
>         - Use builtin_platform_driver() instead of module_platform_driver()
>         - Remove "select DMATEST" from Kconfig
> ---
>  drivers/dma/Kconfig      |   11 +
>  drivers/dma/Makefile     |    1 +
>  drivers/dma/atcdmac300.c | 1505 ++++++++++++++++++++++++++++++++++++++
>  drivers/dma/atcdmac300.h |  296 ++++++++
>  4 files changed, 1813 insertions(+)
>  create mode 100644 drivers/dma/atcdmac300.c
>  create mode 100644 drivers/dma/atcdmac300.h
> 
> diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
> index ae6a682c9f76..f7f6c5347a25 100644
> --- a/drivers/dma/Kconfig
> +++ b/drivers/dma/Kconfig
> @@ -100,6 +100,17 @@ config ARM_DMA350
>  	help
>  	  Enable support for the Arm DMA-350 controller.
>  
> +config ATCDMAC300
> +	bool "Andes DMA support"
> +	depends on ARCH_ANDES
> +	depends on OF
> +	select DMA_ENGINE
> +	help
> +	  Enable support for the Andes ATCDMAC300 DMA controller.
> +	  Select Y if your platform includes an ATCDMAC300 device that
> +	  requires DMA engine support. This driver supports DMA_SLAVE,
> +	  DMA_MEMCPY, and DMA_CYCLIC transfer modes.
> +
>  config AT_HDMAC
>  	tristate "Atmel AHB DMA support"
>  	depends on ARCH_AT91 || COMPILE_TEST
> diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
> index 14aa086629d5..c8fffb31efc4 100644
> --- a/drivers/dma/Makefile
> +++ b/drivers/dma/Makefile
> @@ -18,6 +18,7 @@ obj-$(CONFIG_AMBA_PL08X) += amba-pl08x.o
>  obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += ppc4xx/
>  obj-$(CONFIG_APPLE_ADMAC) += apple-admac.o
>  obj-$(CONFIG_ARM_DMA350) += arm-dma350.o
> +obj-$(CONFIG_ATCDMAC300) += atcdmac300.o
>  obj-$(CONFIG_AT_HDMAC) += at_hdmac.o
>  obj-$(CONFIG_AT_XDMAC) += at_xdmac.o
>  obj-$(CONFIG_AXI_DMAC) += dma-axi-dmac.o
> diff --git a/drivers/dma/atcdmac300.c b/drivers/dma/atcdmac300.c
> new file mode 100644
> index 000000000000..367a920cd001
> --- /dev/null
> +++ b/drivers/dma/atcdmac300.c
> @@ -0,0 +1,1505 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Andes ATCDMAC300 controller driver
> + *
> + * Copyright (C) 2025 Andes Technology Corporation

25-26 now please

> + */
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/dmaengine.h>
> +#include <linux/dmapool.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/iopoll.h>
> +#include <linux/log2.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/of.h>
> +#include <linux/of_dma.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm.h>
> +#include <linux/slab.h>
> +#include <linux/sprintf.h>
> +#include <linux/regmap.h>

do you need all these headers..?

> +#include "dmaengine.h"
> +#include "atcdmac300.h"
> +
> +static int atcdmac_is_chan_enable(struct atcdmac_chan *dmac_chan)
> +{
> +	struct atcdmac_dmac *dmac =
> +		atcdmac_dev_to_dmac(dmac_chan->dma_chan.device);
> +
> +	return regmap_test_bits(dmac->regmap,
> +				REG_CH_EN,
> +				BIT(dmac_chan->chan_id));
> +}
> +
> +static void atcdmac_enable_chan(struct atcdmac_chan *dmac_chan, bool enable)
> +{
> +	regmap_update_bits(dmac_chan->regmap, REG_CH_CTL_OFF, CHEN, enable);
> +}
> +
> +static void atcdmac_abort_chan(struct atcdmac_chan *dmac_chan)
> +{
> +	regmap_write_bits(dmac_chan->dma_dev->regmap,
> +			  REG_CH_ABT,
> +			  BIT(dmac_chan->chan_id),
> +			  BIT(dmac_chan->chan_id));
> +}
> +
> +static dma_cookie_t atcdmac_tx_submit(struct dma_async_tx_descriptor *tx)
> +{
> +	struct atcdmac_chan *dmac_chan = atcdmac_chan_to_dmac_chan(tx->chan);
> +	struct atcdmac_desc *desc = atcdmac_txd_to_dma_desc(tx);
> +	dma_cookie_t cookie;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&dmac_chan->lock, flags);
> +	cookie = dma_cookie_assign(tx);
> +	list_add_tail(&desc->desc_node, &dmac_chan->queue_list);
> +	spin_unlock_irqrestore(&dmac_chan->lock, flags);
> +
> +	return cookie;
> +}
> +
> +static struct atcdmac_desc *
> +atcdmac_get_active_head(struct atcdmac_chan *dmac_chan)
> +{
> +	return list_first_entry(&dmac_chan->active_list,
> +				struct atcdmac_desc,
> +				desc_node);
> +}
> +
> +static struct atcdmac_desc *atcdmac_alloc_desc(struct dma_chan *chan,
> +					       gfp_t gfp_flags)
> +{
> +	struct atcdmac_dmac *dmac = atcdmac_dev_to_dmac(chan->device);
> +	struct atcdmac_desc *desc;
> +	dma_addr_t phys;
> +
> +	desc = dma_pool_zalloc(dmac->dma_desc_pool, gfp_flags, &phys);
> +	if (desc) {
> +		INIT_LIST_HEAD(&desc->tx_list);
> +		dma_async_tx_descriptor_init(&desc->txd, chan);
> +		desc->txd.flags = DMA_CTRL_ACK;
> +		desc->txd.tx_submit = atcdmac_tx_submit;
> +		desc->txd.phys = phys;
> +	}
> +
> +	return desc;
> +}
> +
> +static struct atcdmac_desc *atcdmac_get_desc(struct atcdmac_chan *dmac_chan)
> +{
> +	struct atcdmac_desc *ret = NULL;
> +	struct atcdmac_desc *desc_next;
> +	struct atcdmac_desc *desc;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&dmac_chan->lock, flags);
> +	list_for_each_entry_safe(desc, desc_next,
> +				 &dmac_chan->free_list,
> +				 desc_node) {
> +		if (async_tx_test_ack(&desc->txd)) {
> +			list_del_init(&desc->desc_node);
> +			ret = desc;
> +			break;
> +		}
> +	}
> +	spin_unlock_irqrestore(&dmac_chan->lock, flags);
> +
> +	if (!ret) {
> +		ret = atcdmac_alloc_desc(&dmac_chan->dma_chan, GFP_ATOMIC);
> +		if (ret) {
> +			spin_lock_irqsave(&dmac_chan->lock, flags);
> +			dmac_chan->descs_allocated++;
> +			spin_unlock_irqrestore(&dmac_chan->lock, flags);
> +		} else {
> +			dev_warn(atcdmac_chan_to_dev(&dmac_chan->dma_chan),
> +				 "not enough descriptors available\n");
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +/**
> + * atcdmac_put_desc_nolock - move a descriptor to the free list
> + * @dmac_chan: DMA channel we work on
> + * @desc: Head of the descriptor chain to be added to the free list
> + *
> + * This function does not use a lock to protect any linked lists in
> + * 'struct atcdmac_chan', so please remember to add a proper lock when
> + * calling the function.
> + */
> +static void atcdmac_put_desc_nolock(struct atcdmac_chan *dmac_chan,
> +				    struct atcdmac_desc *desc)
> +{
> +	struct atcdmac_desc *child, *tmp;
> +
> +	if (desc) {
> +		list_for_each_entry_safe(child,
> +					 tmp,
> +					 &desc->tx_list,
> +					 desc_node) {
> +			list_del_init(&child->desc_node);
> +			child->at = NULL;
> +			child->num_sg = 0;
> +			INIT_LIST_HEAD(&child->tx_list);
> +			list_add_tail(&child->desc_node,
> +				      &dmac_chan->free_list);
> +		}
> +
> +		list_del_init(&desc->desc_node);
> +		desc->at = NULL;
> +		desc->num_sg = 0;
> +		INIT_LIST_HEAD(&desc->tx_list);
> +		list_add_tail(&desc->desc_node, &dmac_chan->free_list);
> +	}
> +}
> +
> +static void atcdmac_put_desc(struct atcdmac_chan *dmac_chan,
> +			     struct atcdmac_desc *desc)
> +{
> +	unsigned long flags;
> +
> +	if (!desc) {
> +		dev_err(atcdmac_chan_to_dev(&dmac_chan->dma_chan),
> +			"A NULL descriptor was found.\n");
> +		return;
> +	}
> +
> +	spin_lock_irqsave(&dmac_chan->lock, flags);
> +	atcdmac_put_desc_nolock(dmac_chan, desc);
> +	spin_unlock_irqrestore(&dmac_chan->lock, flags);
> +}
> +
> +static void atcdmac_show_desc(struct atcdmac_chan *dmac_chan,
> +			      struct atcdmac_desc *desc)
> +{
> +	struct device *dev = atcdmac_chan_to_dev(&dmac_chan->dma_chan);
> +
> +	dev_dbg(dev, "Dump desc info of chan: %u\n", dmac_chan->chan_id);
> +	dev_dbg(dev, "chan ctrl: 0x%08x\n", desc->regs.ctrl);
> +	dev_dbg(dev, "trans size: 0x%08x\n", desc->regs.trans_size);
> +	dev_dbg(dev, "src addr: hi:0x%08x lo:0x%08x\n",
> +		desc->regs.src_addr_hi,
> +		desc->regs.src_addr_lo);
> +	dev_dbg(dev, "dst addr: hi:0x%08x lo:0x%08x\n",
> +		desc->regs.dst_addr_hi,
> +		desc->regs.dst_addr_lo);
> +	dev_dbg(dev, "link addr: hi:0x%08x lo:0x%08x\n",
> +		desc->regs.ll_ptr_hi,
> +		desc->regs.ll_ptr_lo);
> +}
> +
> +/**
> + * atcdmac_chain_desc - Chain a DMA descriptor into a linked-list
> + * @first: Pointer to the first descriptor in the chain
> + * @prev: Pointer to the previous descriptor in the chain
> + * @desc: The descriptor to be added to the chain
> + * @cyclic: Indicates if the transfer operates in cyclic mode
> + *
> + * This function appends a DMA descriptor (desc) to a linked-list of
> + * descriptors. If this is the first descriptor being added, it initializes
> + * the list and sets *first to point to desc. Otherwise, it links the
> + * new descriptor to the end of the list managed by *first.
> + *
> + * For non-cyclic descriptors, it updates the hardware linked list pointers
> + * (ll_ptr_lo and ll_ptr_hi) of the previous descriptor (*prev) to point to
> + * the physical address of the new descriptor.
> + *
> + * Finally, it adds the new descriptor to the list and updates *prev to
> + * point to the current descriptor (desc).
> + */
> +static void atcdmac_chain_desc(struct atcdmac_desc **first,
> +			       struct atcdmac_desc **prev,
> +			       struct atcdmac_desc *desc,
> +			       bool cyclic)
> +{
> +	if (!(*first)) {
> +		*first = desc;
> +		desc->at = &desc->tx_list;
> +	} else {
> +		if (!cyclic) {
> +			(*prev)->regs.ll_ptr_lo =
> +				lower_32_bits(desc->txd.phys);
> +			(*prev)->regs.ll_ptr_hi =
> +				upper_32_bits(desc->txd.phys);
> +		}
> +		list_add_tail(&desc->desc_node, &(*first)->tx_list);
> +	}
> +	*prev = desc;
> +
> +	desc->regs.ll_ptr_hi = 0;
> +	desc->regs.ll_ptr_lo = 0;
> +}
> +
> +/**
> + * atcdmac_start_transfer - Start the DMA engine with the provided descriptor
> + * @dmac_chan: The DMA channel to be started
> + * @first_desc: The first descriptor in the list to begin the transfer
> + *
> + * This function configures the DMA engine by programming the hardware
> + * registers with the information from the provided descriptor (first_desc).
> + * It then starts the DMA transfer for the specified channel (dmac_chan).
> + *
> + * The first_desc contains the initial configuration for the transfer,
> + * including source and destination addresses, transfer size, and any linked
> + * list pointers for subsequent descriptors in the chain.
> + */
> +static void atcdmac_start_transfer(struct atcdmac_chan *dmac_chan,
> +				   struct atcdmac_desc *first_desc)
> +{
> +	struct atcdmac_dmac *dmac = dmac_chan->dma_dev;
> +	struct regmap *reg = dmac_chan->regmap;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&dmac->lock, flags);
> +	dmac->used_chan |= BIT(dmac_chan->chan_id);
> +	spin_unlock_irqrestore(&dmac->lock, flags);
> +
> +	regmap_write(reg, REG_CH_CTL_OFF, first_desc->regs.ctrl);
> +	regmap_write(reg, REG_CH_SIZE_OFF, first_desc->regs.trans_size);
> +	regmap_write(reg, REG_CH_SRC_LOW_OFF, first_desc->regs.src_addr_lo);
> +	regmap_write(reg, REG_CH_DST_LOW_OFF, first_desc->regs.dst_addr_lo);
> +	regmap_write(reg, REG_CH_LLP_LOW_OFF, first_desc->regs.ll_ptr_lo);
> +	regmap_write(reg, REG_CH_SRC_HIGH_OFF, first_desc->regs.src_addr_hi);
> +	regmap_write(reg, REG_CH_DST_HIGH_OFF, first_desc->regs.dst_addr_hi);
> +	regmap_write(reg, REG_CH_LLP_HIGH_OFF, first_desc->regs.ll_ptr_hi);
> +	atcdmac_enable_chan(dmac_chan, 1);
> +}
> +
> +/**
> + * atcdmac_start_next_trans - Retrieve and initiate the next DMA transfer
> + * @dmac_chan: Pointer to the DMA channel structure
> + *
> + * In non-cyclic mode, if active_list is empty, the function moves
> + * descriptors from queue_list (if available) to active_list and starts
> + * the transfer. If both lists are empty, it marks the channel as unused.
> + * If there are already active descriptors in active_list, the function
> + * retrieves the next DMA descriptor from it and starts the transfer.
> + *
> + * In cyclic mode, the function retrieves the next DMA descriptor
> + * from the linked list of tx_list to maintain continuous transfers.
> + */
> +static void atcdmac_start_next_trans(struct atcdmac_chan *dmac_chan)
> +{
> +	struct atcdmac_desc *next_tx = NULL;
> +	struct atcdmac_desc *dma_desc;
> +
> +	if (dmac_chan->cyclic) {
> +		/* Get the next DMA descriptor from tx_list. */
> +		dma_desc = atcdmac_get_active_head(dmac_chan);
> +		dma_desc->at = dma_desc->at->next;
> +		if ((uintptr_t)dma_desc->at == (uintptr_t)&dma_desc->tx_list)
> +			next_tx = list_entry(dma_desc->at,
> +					     struct atcdmac_desc,
> +					     tx_list);
> +		else
> +			next_tx = list_entry(dma_desc->at,
> +					     struct atcdmac_desc,
> +					     desc_node);
> +	} else {
> +		if (list_empty(&dmac_chan->active_list)) {
> +			if (!list_empty(&dmac_chan->queue_list)) {
> +				list_splice_init(&dmac_chan->queue_list,
> +						 &dmac_chan->active_list);
> +				next_tx = atcdmac_get_active_head(dmac_chan);
> +			}
> +		} else {
> +			next_tx = atcdmac_get_active_head(dmac_chan);
> +		}
> +	}
> +
> +	if (next_tx) {
> +		dmac_chan->chan_used = 1;
> +		atcdmac_start_transfer(dmac_chan, next_tx);
> +	} else {
> +		dmac_chan->chan_used = 0;
> +	}
> +}
> +
> +static void atcdmac_run_tx_complete_actions(struct atcdmac_desc *desc,
> +					    enum dmaengine_tx_result result)
> +{
> +	struct dma_async_tx_descriptor *txd = &desc->txd;
> +	struct dmaengine_result res;
> +
> +	res.result = result;
> +	dma_cookie_complete(txd);
> +	dma_descriptor_unmap(txd);
> +	dmaengine_desc_get_callback_invoke(txd, &res);
> +	dma_run_dependencies(txd);
> +}
> +
> +/**
> + * atcdmac_advance_work - Process the completed transaction and move to the
> + *                        next descriptor
> + * @dmac_chan: DMA channel where the transaction has completed
> + *
> + * This function is responsible for performing necessary operations after
> + * a DMA transaction completes successfully. It retrieves the next descriptor
> + * from the active list, initiates its transfer, and communicates the result
> + * of the completed transaction to the DMA engine framework.
> + */
> +static void atcdmac_advance_work(struct atcdmac_chan *dmac_chan)
> +{
> +	struct atcdmac_dmac *dmac =
> +		atcdmac_dev_to_dmac(dmac_chan->dma_chan.device);
> +	struct atcdmac_desc *desc_next, *dma_desc, *desc;
> +	struct dmaengine_result res;
> +	LIST_HEAD(completed);
> +	unsigned long flags;
> +	unsigned short stop;
> +
> +	spin_lock_irqsave(&dmac_chan->lock, flags);
> +	if (list_empty(&dmac_chan->active_list)) {
> +		spin_unlock_irqrestore(&dmac_chan->lock, flags);
> +		return;
> +	}
> +
> +	dma_desc = atcdmac_get_active_head(dmac_chan);
> +	stop = READ_ONCE(dmac->stop_mask) & BIT(dmac_chan->chan_id);
> +	if (dmac_chan->cyclic) {
> +		if (!stop)
> +			atcdmac_start_next_trans(dmac_chan);
> +
> +		spin_unlock_irqrestore(&dmac_chan->lock, flags);
> +		res.result = DMA_TRANS_NOERROR;
> +		dmaengine_desc_get_callback_invoke(&dma_desc->txd, &res);
> +	} else {
> +		if (list_is_singular(&dmac_chan->active_list)) {
> +			list_splice_init(&dmac_chan->active_list, &completed);
> +			list_splice_init(&dmac_chan->queue_list,
> +					 &dmac_chan->active_list);
> +		} else {
> +			list_move_tail(&dma_desc->desc_node, &completed);
> +		}
> +
> +		if (!stop)
> +			atcdmac_start_next_trans(dmac_chan);
> +
> +		spin_unlock_irqrestore(&dmac_chan->lock, flags);
> +
> +		list_for_each_entry_safe(desc,
> +					 desc_next,
> +					 &completed,
> +					 desc_node) {
> +			atcdmac_run_tx_complete_actions(desc,
> +							DMA_TRANS_NOERROR);
> +			atcdmac_put_desc(dmac_chan, desc);
> +		}
> +	}
> +}
> +
> +/**
> + * atcdmac_handle_error - Handle errors reported by the DMA controller
> + * @dmac_chan: DMA channel where the error occurred
> + *
> + * This function is invoked when the DMA controller detects an error during a
> + * transaction. This function ensures that the DMA channel can recover from
> + * errors while reporting issues to aid in debugging. It prevents the DMA
> + * controller from operating on potentially invalid memory regions and
> + * attempts to maintain system stability.
> + */
> +static void atcdmac_handle_error(struct atcdmac_chan *dmac_chan)
> +{
> +	struct device *dev = atcdmac_chan_to_dev(&dmac_chan->dma_chan);
> +	struct atcdmac_dmac *dmac =
> +		atcdmac_dev_to_dmac(dmac_chan->dma_chan.device);
> +	struct atcdmac_desc *bad_desc;
> +	unsigned long flags;
> +	unsigned short stop;
> +
> +	spin_lock_irqsave(&dmac_chan->lock, flags);
> +
> +	/*
> +	 * If the active list is empty, the descriptor has already been
> +	 * handled by another function (e.g., atcdmac_terminate_all()).
> +	 * Therefore, no further action is needed.
> +	 */
> +	if (!list_empty(&dmac_chan->active_list)) {
> +		/*
> +		 * Identify the problematic descriptor at the head of the
> +		 * active list and remove it from the list for further
> +		 * processing.
> +		 */
> +		bad_desc = atcdmac_get_active_head(dmac_chan);
> +		list_del_init(&bad_desc->desc_node);
> +
> +		/*
> +		 * Transfer any pending descriptors from the queue list to the
> +		 * active list, allowing them to be processed in subsequent
> +		 * operations.
> +		 */
> +		list_splice_init(&dmac_chan->queue_list,
> +				 dmac_chan->active_list.prev);
> +
> +		stop = READ_ONCE(dmac->stop_mask) & BIT(dmac_chan->chan_id);
> +		if (!list_empty(&dmac_chan->active_list) && stop == 0)
> +			atcdmac_start_transfer(dmac_chan, atcdmac_get_active_head(dmac_chan));
> +		else
> +			dmac_chan->chan_used = 0;
> +
> +		spin_unlock_irqrestore(&dmac_chan->lock, flags);
> +
> +		/*
> +		 * Show the details information of the bad descriptor and
> +		 * return "DMA_TRANS_ABORTED" to the DMA engine framework.
> +		 */
> +		dev_err(dev, "DMA transaction failed, possible DMA desc error\n");
> +		atcdmac_show_desc(dmac_chan, bad_desc);
> +		atcdmac_run_tx_complete_actions(bad_desc, DMA_TRANS_ABORTED);
> +
> +		atcdmac_put_desc(dmac_chan, bad_desc);
> +
> +		return;
> +	}
> +
> +	spin_unlock_irqrestore(&dmac_chan->lock, flags);
> +}
> +
> +static irqreturn_t atcdmac_irq_thread(int irq, void *dev_id)
> +{
> +	struct atcdmac_dmac *dmac = dev_id;
> +	struct atcdmac_chan *dmac_chan;
> +	int i;
> +	bool handled = false;
> +
> +	for (i = 0; i < dmac->num_ch; i++) {
> +		dmac_chan = &dmac->chan[i];
> +
> +		if (test_and_clear_bit(ATCDMAC_STA_TC, &dmac_chan->status)) {
> +			atcdmac_advance_work(dmac_chan);
> +			handled = true;
> +		}
> +
> +		if (test_and_clear_bit(ATCDMAC_STA_ERR, &dmac_chan->status)) {
> +			atcdmac_handle_error(dmac_chan);
> +			handled = true;
> +		}
> +
> +		/*
> +		 * ATCDMAC_STA_ABORT only occurs when the DMA channel is
> +		 * terminated or freed, and all descriptors and callbacks
> +		 * have already been processed. Therefore, no additional
> +		 * handling is required.
> +		 */
> +		if (test_and_clear_bit(ATCDMAC_STA_ABORT, &dmac_chan->status))
> +			handled = true;
> +	}
> +
> +	return handled ? IRQ_HANDLED : IRQ_NONE;
> +}
> +
> +static irqreturn_t atcdmac_interrupt(int irq, void *dev_id)
> +{
> +	struct atcdmac_dmac *dmac = dev_id;
> +	struct atcdmac_chan *dmac_chan;
> +	unsigned int status;
> +	unsigned int int_ch;
> +	int ret = IRQ_NONE;
> +	int i;
> +
> +	regmap_read(dmac->regmap, REG_INT_STA, &status);
> +	int_ch = READ_ONCE(dmac->used_chan) & DMA_INT_ALL(status);
> +
> +	while (int_ch) {
> +		spin_lock(&dmac->lock);
> +		dmac->used_chan = READ_ONCE(dmac->used_chan) & ~int_ch;
> +		spin_unlock(&dmac->lock);
> +		regmap_write(dmac->regmap, REG_INT_STA, DMA_INT_CLR(int_ch));
> +
> +		for (i = 0; i < dmac->num_ch; i++) {
> +			if (int_ch & BIT(i)) {
> +				int_ch &= ~BIT(i);
> +				dmac_chan = &dmac->chan[i];
> +
> +				if (status & DMA_TC(i))
> +					set_bit(ATCDMAC_STA_TC,
> +						&dmac_chan->status);
> +
> +				if (status & DMA_ERR(i))
> +					set_bit(ATCDMAC_STA_ERR,
> +						&dmac_chan->status);
> +
> +				if (status & DMA_ABT(i))
> +					set_bit(ATCDMAC_STA_ABORT,
> +						&dmac_chan->status);
> +
> +				ret = IRQ_WAKE_THREAD;
> +			}
> +			if (!int_ch)
> +				break;
> +		}
> +
> +		regmap_read(dmac->regmap, REG_INT_STA, &status);
> +		int_ch = READ_ONCE(dmac->used_chan) & DMA_INT_ALL(status);
> +	}
> +
> +	return ret;
> +}
> +
> +/**
> + * atcdmac_issue_pending - Trigger the execution of queued DMA transactions
> + * @chan: DMA channel on which to issue pending transactions
> + *
> + * This function checks if the DMA channel is currently idle and if there are
> + * descriptors waiting in the queue_list. If the channel is idle and the
> + * queue_list is not empty, it moves the first queued descriptor to the active
> + * list and starts the DMA transfer by calling atcdmac_start_transfer().
> + */
> +static void atcdmac_issue_pending(struct dma_chan *chan)
> +{
> +	struct atcdmac_chan *dmac_chan = atcdmac_chan_to_dmac_chan(chan);
> +	struct atcdmac_dmac *dmac =
> +		atcdmac_dev_to_dmac(dmac_chan->dma_chan.device);
> +	unsigned long flags;
> +	unsigned short stop;
> +
> +	spin_lock_irqsave(&dmac_chan->lock, flags);
> +	stop = READ_ONCE(dmac->stop_mask) & BIT(dmac_chan->chan_id);
> +	if (dmac_chan->chan_used == 0 && stop == 0 &&
> +	    !list_empty(&dmac_chan->queue_list)) {
> +		dmac_chan->chan_used = 1;
> +		list_move(dmac_chan->queue_list.next, &dmac_chan->active_list);
> +		atcdmac_start_transfer(dmac_chan,
> +				       atcdmac_get_active_head(dmac_chan));
> +	}
> +
> +	spin_unlock_irqrestore(&dmac_chan->lock, flags);
> +}
> +
> +static unsigned char atcdmac_map_buswidth(enum dma_slave_buswidth addr_width)
> +{
> +	switch (addr_width) {
> +	case DMA_SLAVE_BUSWIDTH_1_BYTE:
> +		return 0;
> +	case DMA_SLAVE_BUSWIDTH_2_BYTES:
> +		return 1;
> +	case DMA_SLAVE_BUSWIDTH_4_BYTES:
> +		return 2;
> +	case DMA_SLAVE_BUSWIDTH_8_BYTES:
> +		return 3;
> +	case DMA_SLAVE_BUSWIDTH_16_BYTES:
> +		return 4;
> +	case DMA_SLAVE_BUSWIDTH_32_BYTES:
> +		return 5;
> +	default:
> +		return 0;
> +	}
> +}
> +
> +static unsigned int atcdmac_map_tran_width(dma_addr_t src,
> +					   dma_addr_t dst,
> +					   size_t len,
> +					   unsigned int max_align_bytes)
> +{
> +	unsigned int align = src | dst | len | max_align_bytes;
> +	unsigned int width;
> +
> +	if (!(align & 0x1F))
> +		width = WIDTH_32_BYTES;
> +	else if (!(align & 0xF))
> +		width = WIDTH_16_BYTES;
> +	else if (!(align & 0x7))
> +		width = WIDTH_8_BYTES;
> +	else if (!(align & 0x3))
> +		width = WIDTH_4_BYTES;
> +	else if (!(align & 0x1))
> +		width = WIDTH_2_BYTES;
> +	else
> +		width = WIDTH_1_BYTE;
> +
> +	return width;
> +}
> +
> +/**
> + * atcdmac_convert_burst - Convert burst size to a power of two index
> + * @burst_size: Actual burst size in bytes
> + *
> + * This function converts a burst size (e.g., 1, 2, 4, 8...) into the
> + * corresponding hardware-encoded value, which represents the index of
> + * the power of two.
> + *
> + * Return: The zero-based power-of-two index corresponding to @burst_size.
> + *
> + * Example:
> + * If burst_size is 8 (binary 1000), the most significant bit is at position
> + * 4, so the function returns 3 (i.e., 4 - 1).
> + */
> +static unsigned char atcdmac_convert_burst(unsigned int burst_size)
> +{
> +	return fls(burst_size) - 1;
> +}
> +
> +static struct atcdmac_desc *
> +atcdmac_build_desc(struct atcdmac_chan *dmac_chan,
> +		   dma_addr_t src,
> +		   dma_addr_t dst,
> +		   unsigned int ctrl,
> +		   unsigned int trans_size,
> +		   unsigned int num_sg)
> +{
> +	struct atcdmac_desc *desc;
> +
> +	desc = atcdmac_get_desc(dmac_chan);
> +	if (!desc)
> +		return NULL;
> +
> +	desc->regs.src_addr_lo = lower_32_bits(src);
> +	desc->regs.src_addr_hi = upper_32_bits(src);
> +	desc->regs.dst_addr_lo = lower_32_bits(dst);
> +	desc->regs.dst_addr_hi = upper_32_bits(dst);
> +	desc->regs.ctrl = ctrl;
> +	desc->regs.trans_size = trans_size;
> +	desc->num_sg = num_sg;
> +
> +	return desc;
> +}
> +
> +/**
> + * atcdmac_prep_dma_memcpy - Prepare a DMA memcpy operation for the specified
> + *                           channel
> + * @chan: DMA channel to configure for the operation
> + * @dst: Physical destination address for the transfer
> + * @src: Physical source address for the transfer
> + * @len: Size of the data to transfer, in bytes
> + * @flags: Status flags for the transfer descriptor
> + *
> + * This function sets up a DMA memcpy operation to transfer data from the
> + * specified source address to the destination address. It returns a DMA
> + * descriptor that represents the configured transaction.
> + */
> +static struct dma_async_tx_descriptor *
> +atcdmac_prep_dma_memcpy(struct dma_chan *chan,
> +			dma_addr_t dst,
> +			dma_addr_t src,
> +			size_t len,
> +			unsigned long flags)
> +{
> +	struct atcdmac_chan *dmac_chan = atcdmac_chan_to_dmac_chan(chan);
> +	struct atcdmac_dmac *dmac = atcdmac_dev_to_dmac(chan->device);
> +	struct atcdmac_desc *desc;
> +	unsigned int src_width;
> +	unsigned int dst_width;
> +	unsigned int ctrl;
> +	unsigned char src_max_burst;
> +
> +	if (unlikely(!len)) {
> +		dev_warn(atcdmac_chan_to_dev(chan),
> +			 "Failed to prepare DMA operation: len is zero\n");
> +		return NULL;
> +	}
> +
> +	src_max_burst =
> +		atcdmac_convert_burst((unsigned int)SRC_BURST_SIZE_1024);
> +	src_width = atcdmac_map_tran_width(src,
> +					   dst,
> +					   len,
> +					   1 << dmac->data_width);
> +	dst_width = src_width;
> +	ctrl = SRC_BURST_SIZE(src_max_burst) |
> +	       SRC_ADDR_MODE_INCR |
> +	       DST_ADDR_MODE_INCR |
> +	       DST_WIDTH(dst_width) |
> +	       SRC_WIDTH(src_width);
> +
> +	desc = atcdmac_build_desc(dmac_chan, src, dst, ctrl,
> +				  len >> src_width, 1);
> +	if (!desc)
> +		goto err_desc_get;
> +
> +	return &desc->txd;
> +
> +err_desc_get:
> +	dev_warn(atcdmac_chan_to_dev(chan), "Failed to allocate descriptor\n");
> +	return NULL;
> +}
> +
> +static int atcdmac_get_slave_cfg(struct dma_slave_config *sconfig,
> +				 enum dma_transfer_direction dir,
> +				 struct atcdmac_slave_cfg *cfg)
> +{
> +	if (dir == DMA_MEM_TO_DEV) {
> +		cfg->reg         = sconfig->dst_addr;
> +		cfg->burst_bytes = sconfig->dst_addr_width * sconfig->dst_maxburst;
> +		cfg->dev_width   = sconfig->dst_addr_width;
> +		cfg->width_src   = atcdmac_map_buswidth(sconfig->src_addr_width);
> +		cfg->width_dst   = atcdmac_map_buswidth(sconfig->dst_addr_width);
> +	} else if (dir == DMA_DEV_TO_MEM) {
> +		cfg->reg         = sconfig->src_addr;
> +		cfg->burst_bytes = sconfig->src_addr_width * sconfig->src_maxburst;
> +		cfg->dev_width   = sconfig->src_addr_width;
> +		cfg->width_src   = atcdmac_map_buswidth(sconfig->src_addr_width);
> +		cfg->width_dst   = atcdmac_map_buswidth(sconfig->dst_addr_width);
> +	} else {
> +		return -EINVAL;
> +	}
> +	return 0;
> +}
> +
> +static struct atcdmac_desc *
> +atcdmac_build_slave_desc(struct atcdmac_chan *dmac_chan,
> +			 const struct atcdmac_slave_cfg *cfg,
> +			 dma_addr_t mem, unsigned int len,
> +			 enum dma_transfer_direction dir,
> +			 unsigned int data_width, unsigned int num_desc)
> +{
> +	unsigned int   width_cal;
> +	unsigned short burst_size;
> +	unsigned int   ctrl;
> +	dma_addr_t     src, dst;
> +
> +	width_cal = atcdmac_map_tran_width(mem, cfg->reg, len,
> +					   (1 << data_width) | cfg->burst_bytes);
> +	if (dir == DMA_MEM_TO_DEV) {
> +		if (cfg->burst_bytes < (1 << width_cal)) {
> +			burst_size = cfg->burst_bytes;
> +			width_cal  = WIDTH_1_BYTE;
> +		} else {
> +			burst_size = cfg->burst_bytes / (1 << width_cal);
> +		}
> +		ctrl = SRC_ADDR_MODE_INCR | DST_ADDR_MODE_FIXED |
> +		       DST_HS | DST_REQ(dmac_chan->req_num) |
> +		       SRC_WIDTH(width_cal) | DST_WIDTH(cfg->width_dst) |
> +		       SRC_BURST_SIZE(ilog2(burst_size));
> +		src = mem;
> +		dst = cfg->reg;
> +	} else {
> +		burst_size = cfg->burst_bytes / cfg->dev_width;
> +		ctrl = SRC_ADDR_MODE_FIXED | DST_ADDR_MODE_INCR |
> +		       SRC_HS | SRC_REQ(dmac_chan->req_num) |
> +		       SRC_WIDTH(cfg->width_src) | DST_WIDTH(width_cal) |
> +		       SRC_BURST_SIZE(ilog2(burst_size));
> +		src      = cfg->reg;
> +		dst      = mem;
> +		width_cal = cfg->width_src;
> +	}
> +	return atcdmac_build_desc(dmac_chan, src, dst, ctrl,
> +				  len >> width_cal, num_desc);
> +}
> +
> +/**
> + * atcdmac_prep_device_sg - Prepare descriptors for memory/device DMA
> + *                          transactions
> + * @chan: DMA channel to configure for the operation
> + * @sgl: Scatter-gather list representing the memory regions to transfer
> + * @sg_len: Number of entries in the scatter-gather list
> + * @direction: Direction of the DMA transfer
> + * @flags: Status flags for the transfer descriptor
> + * @context: transaction context (ignored)
> + *
> + * This function prepares a DMA transaction by setting up the required
> + * descriptors based on the provided scatter-gather list and parameters.
> + * It supports memory-to-device and device-to-memory DMA transfers.
> + */
> +static struct dma_async_tx_descriptor *
> +atcdmac_prep_device_sg(struct dma_chan *chan,
> +		       struct scatterlist *sgl,
> +		       unsigned int sg_len,
> +		       enum dma_transfer_direction direction,
> +		       unsigned long flags,
> +		       void *context)
> +{
> +	struct atcdmac_chan *dmac_chan = atcdmac_chan_to_dmac_chan(chan);
> +	struct atcdmac_dmac *dmac = atcdmac_dev_to_dmac(chan->device);
> +	struct atcdmac_slave_cfg cfg;
> +	struct atcdmac_desc *first = NULL;
> +	struct atcdmac_desc *prev = NULL;
> +	struct scatterlist *sg;
> +	unsigned int i;
> +
> +	if (unlikely(!sg_len)) {
> +		dev_warn(atcdmac_chan_to_dev(chan), "sg_len is zero\n");
> +		return NULL;
> +	}
> +
> +	if (atcdmac_get_slave_cfg(&dmac_chan->dma_sconfig, direction, &cfg)) {
> +		dev_err(atcdmac_chan_to_dev(chan),
> +			"Invalid transfer direction %d\n", direction);
> +		return NULL;
> +	}
> +
> +	for_each_sg(sgl, sg, sg_len, i) {
> +		struct atcdmac_desc *desc;
> +		dma_addr_t mem = sg_dma_address(sg);
> +		unsigned int len = sg_dma_len(sg);
> +
> +		if (unlikely(!len)) {
> +			dev_err(atcdmac_chan_to_dev(chan),
> +				"sg(%u) data len is zero\n", i);
> +			goto err;
> +		}
> +
> +		desc = atcdmac_build_slave_desc(dmac_chan, &cfg, mem, len,
> +						direction, dmac->data_width,
> +						sg_len);
> +		if (!desc)
> +			goto err_desc_get;
> +
> +		atcdmac_chain_desc(&first, &prev, desc, false);
> +	}
> +
> +	first->txd.cookie = -EBUSY;
> +	first->txd.flags = flags;
> +
> +	return &first->txd;
> +
> +err_desc_get:
> +	dev_warn(atcdmac_chan_to_dev(chan), "Failed to allocate descriptor\n");
> +	if (first)
> +		first->num_sg = i;
> +
> +err:
> +	if (first)
> +		atcdmac_put_desc(dmac_chan, first);
> +	return NULL;
> +}
> +
> +static struct dma_async_tx_descriptor *
> +atcdmac_prep_dma_cyclic(struct dma_chan *chan,
> +			dma_addr_t buf_addr,
> +			size_t buf_len,
> +			size_t period_len,
> +			enum dma_transfer_direction direction,
> +			unsigned long flags)
> +{
> +	struct atcdmac_chan *dmac_chan = atcdmac_chan_to_dmac_chan(chan);
> +	struct atcdmac_dmac *dmac = atcdmac_dev_to_dmac(chan->device);
> +	struct atcdmac_slave_cfg cfg;
> +	struct atcdmac_desc *first = NULL;
> +	struct atcdmac_desc *prev = NULL;
> +	unsigned int num_periods;
> +	unsigned int period;
> +
> +	if (period_len == 0 || buf_len == 0) {
> +		dev_warn(atcdmac_chan_to_dev(chan),
> +			 "invalid cyclic params buf_len=%zu period_len=%zu\n",
> +			 buf_len, period_len);
> +		return NULL;
> +	}
> +
> +	if (atcdmac_get_slave_cfg(&dmac_chan->dma_sconfig, direction, &cfg)) {
> +		dev_err(atcdmac_chan_to_dev(chan),
> +			"Invalid transfer direction %d\n", direction);
> +		return NULL;
> +	}
> +
> +	num_periods = (buf_len + period_len - 1) / period_len;
> +
> +	for (period = 0; period < buf_len; period += period_len) {
> +		struct atcdmac_desc *desc;
> +		dma_addr_t mem = buf_addr + period;
> +		unsigned int len = min_t(unsigned int, period_len,
> +					 buf_len - period);
> +
> +		desc = atcdmac_build_slave_desc(dmac_chan, &cfg, mem, len,
> +						direction, dmac->data_width,
> +						num_periods);
> +		if (!desc)
> +			goto err_desc_get;
> +		atcdmac_chain_desc(&first, &prev, desc, true);
> +	}
> +
> +	first->txd.flags = flags;
> +	dmac_chan->cyclic = true;
> +
> +	return &first->txd;
> +
> +err_desc_get:
> +	dev_warn(atcdmac_chan_to_dev(chan), "Failed to allocate descriptor\n");
> +	if (first)
> +		atcdmac_put_desc(dmac_chan, first);
> +
> +	return NULL;
> +}
> +
> +static int atcdmac_set_device_config(struct dma_chan *chan,
> +				     struct dma_slave_config *sconfig)
> +{
> +	struct atcdmac_chan *dmac_chan = atcdmac_chan_to_dmac_chan(chan);
> +
> +	/* Check if this chan is configured for device transfers */
> +	if (!dmac_chan->dev_chan)
> +		return -EINVAL;
> +
> +	/* Must be powers of two according to ATCDMAC300 spec */
> +	if (!is_power_of_2(sconfig->src_maxburst) ||
> +	    !is_power_of_2(sconfig->dst_maxburst) ||
> +	    !is_power_of_2(sconfig->src_addr_width) ||
> +	    !is_power_of_2(sconfig->dst_addr_width))
> +		return -EINVAL;
> +
> +	memcpy(&dmac_chan->dma_sconfig, sconfig, sizeof(*sconfig));
> +
> +	return 0;
> +}
> +
> +static int atcdmac_terminate_all(struct dma_chan *chan)
> +{
> +	struct atcdmac_chan *dmac_chan = atcdmac_chan_to_dmac_chan(chan);
> +	struct atcdmac_desc *desc_cur, *desc_next;
> +	LIST_HEAD(list);
> +	unsigned long flags;
> +	unsigned int val;
> +	int ret;
> +
> +	spin_lock_irqsave(&dmac_chan->lock, flags);
> +	atcdmac_abort_chan(dmac_chan);
> +	atcdmac_enable_chan(dmac_chan, 0);
> +	ret = regmap_read_poll_timeout_atomic(dmac_chan->dma_dev->regmap,
> +					      REG_CH_EN,
> +					      val,
> +					      !(val & BIT(dmac_chan->chan_id)),
> +					      10,
> +					      ATCDMAC_CHAN_TIMEOUT_US);
> +	if (ret)
> +		dev_err(atcdmac_chan_to_dev(chan),
> +			"Timed out waiting for channel to disable\n");
> +
> +	list_splice_init(&dmac_chan->queue_list,  &list);
> +	list_splice_init(&dmac_chan->active_list, &list);
> +	dmac_chan->chan_used = 0;
> +	spin_unlock_irqrestore(&dmac_chan->lock, flags);
> +
> +	list_for_each_entry_safe(desc_cur, desc_next, &list, desc_node) {
> +		atcdmac_run_tx_complete_actions(desc_cur, DMA_TRANS_ABORTED);
> +		atcdmac_put_desc(dmac_chan, desc_cur);
> +	}
> +
> +	return ret;
> +}
> +
> +static enum dma_status atcdmac_get_tx_status(struct dma_chan *chan,
> +					     dma_cookie_t cookie,
> +					     struct dma_tx_state *state)
> +{
> +	return dma_cookie_status(chan, cookie, state);
> +}
> +
> +static int atcdmac_wait_chan_idle(struct atcdmac_dmac *dmac,
> +				  unsigned short chan_mask,
> +				  unsigned int timeout_us)
> +{
> +	unsigned int val;
> +	int ret;
> +
> +	ret = regmap_read_poll_timeout(dmac->regmap,
> +				       REG_CH_EN,
> +				       val,
> +				       !(val & chan_mask),
> +				       50,
> +				       timeout_us);
> +	if (ret)
> +		dev_err(dmac->dma_device.dev,
> +			"Timeout waiting for device ready %d\n", ret);
> +
> +	return ret;
> +}
> +
> +/**
> + * atcdmac_alloc_chan_resources - Allocate resources for a DMA channel
> + * @chan: The DMA channel for which resources are being allocated
> + *
> + * This function sets up and allocates the necessary resources for the
> + * specified DMA channel (chan). It ensures the channel is prepared to
> + * handle DMA requests from clients by allocating descriptors and any other
> + * required resources.
> + *
> + * Return: The number of descriptors successfully allocated, or a negative
> + *         error code on failure.
> + */
> +static int atcdmac_alloc_chan_resources(struct dma_chan *chan)
> +{
> +	struct atcdmac_chan *dmac_chan = atcdmac_chan_to_dmac_chan(chan);
> +	struct atcdmac_dmac *dmac = atcdmac_dev_to_dmac(chan->device);
> +	struct atcdmac_desc *desc;
> +	int i;
> +
> +	if (atcdmac_is_chan_enable(dmac_chan)) {
> +		dev_err(atcdmac_chan_to_dev(chan),
> +			"DMA channel is not in an idle state\n");
> +		return -EBUSY;
> +	}
> +
> +	if (!list_empty(&dmac_chan->free_list))
> +		return dmac_chan->descs_allocated;
> +
> +	/*
> +	 * Spin-lock protection is not necessary during DMA channel
> +	 * initialization, as the channel is not yet in use at this stage,
> +	 * and the shared resources are not accessed by other threads.
> +	 */
> +	for (i = 0; i < ATCDMAC_DESC_PER_CHAN; i++) {
> +		desc = atcdmac_alloc_desc(chan, GFP_KERNEL);
> +		if (!desc) {
> +			dev_warn(dmac->dma_device.dev,
> +				 "Insufficient descriptors: only %d descriptors available\n",
> +				 i);
> +			break;
> +		}
> +		list_add_tail(&desc->desc_node, &dmac_chan->free_list);
> +	}
> +	dmac_chan->descs_allocated = i;
> +	dmac_chan->cyclic = false;
> +	dma_cookie_init(chan);
> +	spin_lock_irq(&dmac->lock);
> +	dmac->stop_mask &= ~BIT(dmac_chan->chan_id);
> +	spin_unlock_irq(&dmac->lock);
> +
> +	return dmac_chan->descs_allocated;
> +}
> +
> +/**
> + * atcdmac_free_chan_resources - Release a DMA channel's resources
> + * @chan: The DMA channel to release
> + *
> + * This function ensures that any remaining DMA descriptors in the
> + * active_list and queue_list are properly reclaimed. It releases all
> + * resources associated with the specified DMA channel and resets the
> + * channel's management structures to their initial states.
> + */
> +static void atcdmac_free_chan_resources(struct dma_chan *chan)
> +{
> +	struct atcdmac_chan *dmac_chan = atcdmac_chan_to_dmac_chan(chan);
> +	struct atcdmac_dmac *dmac = atcdmac_dev_to_dmac(chan->device);
> +	struct atcdmac_desc *desc_next, *desc;
> +	unsigned long flags;
> +
> +	WARN_ON_ONCE(atcdmac_is_chan_enable(dmac_chan));
> +
> +	spin_lock_irq(&dmac->lock);
> +	dmac->stop_mask |= BIT(dmac_chan->chan_id);
> +	spin_unlock_irq(&dmac->lock);
> +
> +	atcdmac_terminate_all(chan);
> +
> +	spin_lock_irqsave(&dmac_chan->lock, flags);
> +	list_for_each_entry_safe(desc,
> +				 desc_next,
> +				 &dmac_chan->free_list,
> +				 desc_node) {
> +		list_del(&desc->desc_node);
> +		dma_pool_free(dmac->dma_desc_pool, desc, desc->txd.phys);
> +	}
> +
> +	INIT_LIST_HEAD(&dmac_chan->free_list);
> +	dmac_chan->descs_allocated = 0;
> +	dmac_chan->status = 0;
> +	dmac_chan->chan_used = 0;
> +	dmac_chan->dev_chan = 0;
> +	spin_unlock_irqrestore(&dmac_chan->lock, flags);
> +}
> +
> +static bool atcdmac_filter_chan(struct dma_chan *chan, void *dma_dev)
> +{
> +	if (dma_dev == chan->device->dev)
> +		return true;
> +
> +	return false;
> +}
> +
> +static struct dma_chan *atcdmac_dma_xlate_handler(struct of_phandle_args *dmac,
> +						  struct of_dma *of_dma)
> +{
> +	struct platform_device *dmac_pdev;
> +	struct atcdmac_chan *dmac_chan;
> +	struct dma_chan *chan;
> +	dma_cap_mask_t mask;
> +
> +	dmac_pdev = of_find_device_by_node(dmac->np);
> +	if (!dmac_pdev)
> +		return NULL;
> +
> +	dma_cap_zero(mask);
> +	dma_cap_set(DMA_SLAVE, mask);
> +	chan = dma_request_channel(mask, atcdmac_filter_chan, &dmac_pdev->dev);
> +	put_device(&dmac_pdev->dev);
> +
> +	if (!chan)
> +		return NULL;
> +
> +	dmac_chan = atcdmac_chan_to_dmac_chan(chan);
> +	dmac_chan->dev_chan = true;
> +	dmac_chan->req_num = dmac->args[0] & 0xff;
> +
> +	return chan;
> +}
> +
> +/**
> + * atcdmac_reset_and_wait_chan_idle - Reset the DMA controller and wait for
> + *                                    all channels to become idle
> + * @dmac: Pointer to the DMA controller structure
> + *
> + * This function performs a reset of the DMA controller and ensures that all
> + * DMA channels are disabled.
> + */
> +static int atcdmac_reset_and_wait_chan_idle(struct atcdmac_dmac *dmac)
> +{
> +	regmap_update_bits(dmac->regmap, REG_CTL, DMAC_RESET, 1);
> +	msleep(20);
> +	regmap_update_bits(dmac->regmap, REG_CTL, DMAC_RESET, 0);
> +
> +	return atcdmac_wait_chan_idle(dmac,
> +				      BIT(dmac->num_ch) - 1,
> +				      ATCDMAC_CHAN_TIMEOUT_US);
> +}
> +
> +static int atcdmac_restore_iocp(struct atcdmac_dmac *dmac)
> +{
> +	if (!dmac->regmap_iocp)
> +		return 0;
> +
> +	return regmap_write(dmac->regmap_iocp,
> +			    0,
> +			    IOCP_CACHE_DMAC0_AW |
> +			    IOCP_CACHE_DMAC0_AR |
> +			    IOCP_CACHE_DMAC1_AW |
> +			    IOCP_CACHE_DMAC1_AR);
> +}
> +
> +static int atcdmac_init_iocp(struct platform_device *pdev,
> +			     struct atcdmac_dmac *dmac)
> +{
> +	const struct regmap_config iocp_regmap_config = {
> +		.name = "iocp",
> +		.reg_bits = 32,
> +		.val_bits = 32,
> +		.reg_stride = 4,
> +		.pad_bits = 0,
> +		.max_register = 0,
> +		.cache_type = REGCACHE_NONE,
> +	};
> +	struct resource *res;
> +	void __iomem *regs;
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "iocp");
> +	if (!res) {
> +		dmac->regmap_iocp = NULL;
> +		return 0;
> +	}
> +
> +	regs = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(regs)) {
> +		dmac->regmap_iocp = NULL;
> +		return dev_err_probe(&pdev->dev, PTR_ERR(regs),
> +				     "Failed to create ioremap for IOCP\n");
> +	}
> +
> +	dmac->regmap_iocp = devm_regmap_init_mmio(&pdev->dev,
> +						  regs,
> +						  &iocp_regmap_config);
> +	if (IS_ERR(dmac->regmap_iocp)) {
> +		int ret = PTR_ERR(dmac->regmap_iocp);
> +
> +		dmac->regmap_iocp = NULL;
> +		return dev_err_probe(&pdev->dev, ret,
> +				     "Failed to create regmap for IOCP\n");
> +	}
> +
> +	return atcdmac_restore_iocp(dmac);
> +}
> +
> +static void atcdmac_init_dma_device(struct platform_device *pdev,
> +				    struct atcdmac_dmac *dmac)
> +{
> +	struct dma_device *device = &dmac->dma_device;
> +
> +	device->device_alloc_chan_resources = atcdmac_alloc_chan_resources;
> +	device->device_free_chan_resources = atcdmac_free_chan_resources;
> +	device->device_tx_status = atcdmac_get_tx_status;
> +	device->device_issue_pending = atcdmac_issue_pending;
> +	device->device_prep_dma_memcpy = atcdmac_prep_dma_memcpy;
> +	device->device_prep_slave_sg = atcdmac_prep_device_sg;
> +	device->device_config = atcdmac_set_device_config;
> +	device->device_terminate_all = atcdmac_terminate_all;
> +	device->device_prep_dma_cyclic = atcdmac_prep_dma_cyclic;
> +
> +	device->dev = &pdev->dev;
> +	device->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
> +				  BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
> +				  BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
> +	device->dst_addr_widths = device->src_addr_widths;
> +	device->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
> +	device->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
> +
> +	dma_cap_set(DMA_SLAVE, device->cap_mask);
> +	dma_cap_set(DMA_MEMCPY, device->cap_mask);
> +	dma_cap_set(DMA_CYCLIC, device->cap_mask);
> +}
> +
> +static int atcdmac_init_channels(struct platform_device *pdev,
> +				 struct atcdmac_dmac *dmac)
> +{
> +	struct regmap_config chan_regmap_config = {
> +		.reg_bits = 32,
> +		.val_bits = 32,
> +		.reg_stride = 4,
> +		.pad_bits = 0,
> +		.cache_type = REGCACHE_NONE,
> +		.max_register = REG_CH_LLP_HIGH_OFF,
> +	};
> +	struct atcdmac_chan *dmac_chan;
> +	int ret;
> +	int i;
> +
> +	INIT_LIST_HEAD(&dmac->dma_device.channels);
> +
> +	for (i = 0; i < dmac->num_ch; i++) {
> +		char *regmap_name = kasprintf(GFP_KERNEL, "chan%d", i);
> +
> +		if (!regmap_name)
> +			return -ENOMEM;
> +
> +		dmac_chan = &dmac->chan[i];
> +		chan_regmap_config.name = regmap_name;
> +		dmac_chan->regmap =
> +			devm_regmap_init_mmio(&pdev->dev,
> +					      dmac->regs + REG_CH_OFF(i),
> +					      &chan_regmap_config);
> +		kfree(regmap_name);
> +
> +		if (IS_ERR(dmac_chan->regmap)) {
> +			ret = PTR_ERR(dmac_chan->regmap);
> +			dev_err_probe(&pdev->dev, ret,
> +				      "Failed to create regmap for DMA chan%d\n",
> +				      i);
> +			return ret;
> +		}
> +
> +		spin_lock_init(&dmac_chan->lock);
> +		dmac_chan->dma_chan.device = &dmac->dma_device;
> +		dmac_chan->dma_dev = dmac;
> +		dmac_chan->chan_id = i;
> +		dmac_chan->chan_used = 0;
> +
> +		INIT_LIST_HEAD(&dmac_chan->active_list);
> +		INIT_LIST_HEAD(&dmac_chan->queue_list);
> +		INIT_LIST_HEAD(&dmac_chan->free_list);
> +
> +		list_add_tail(&dmac_chan->dma_chan.device_node,
> +			      &dmac->dma_device.channels);
> +		dma_cookie_init(&dmac_chan->dma_chan);
> +	}
> +
> +	return 0;
> +}
> +
> +static int atcdmac_init_desc_pool(struct platform_device *pdev,
> +				  struct atcdmac_dmac *dmac)
> +{
> +	dmac->dma_desc_pool = dmam_pool_create(dev_name(&pdev->dev),
> +					       &pdev->dev,
> +					       sizeof(struct atcdmac_desc),
> +					       64,
> +					       4096);
> +	if (!dmac->dma_desc_pool)
> +		return dev_err_probe(&pdev->dev, -ENOMEM,
> +				     "Failed to create memory pool for DMA descriptors\n");
> +	return 0;
> +}
> +
> +static int atcdmac_init_irq(struct platform_device *pdev,
> +			    struct atcdmac_dmac *dmac)
> +{
> +	int irq = platform_get_irq(pdev, 0);
> +
> +	if (irq < 0)
> +		return irq;
> +
> +	return devm_request_threaded_irq(&pdev->dev,
> +					 irq,
> +					 atcdmac_interrupt,
> +					 atcdmac_irq_thread,
> +					 IRQF_SHARED | IRQF_ONESHOT,
> +					 dev_name(&pdev->dev),
> +					 dmac);
> +}
> +
> +static int atcdmac_init_ioremap_and_regmap(struct platform_device *pdev,
> +					   struct atcdmac_dmac **out_dmac)
> +{
> +	const struct regmap_config dmac_regmap_config = {
> +		.name = dev_name(&pdev->dev),
> +		.reg_bits = 32,
> +		.val_bits = 32,
> +		.reg_stride = 4,
> +		.pad_bits = 0,
> +		.cache_type = REGCACHE_NONE,
> +		.max_register = REG_CH_EN,
> +	};
> +	struct atcdmac_dmac *dmac;
> +	struct regmap *regmap;
> +	void __iomem *regs;
> +	size_t size;
> +	unsigned int val;
> +	int ret = 0;
> +	unsigned char num_ch;
> +
> +	regs = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(regs))
> +		return dev_err_probe(&pdev->dev, PTR_ERR(regs),
> +				     "Failed to ioremap I/O resource\n");
> +
> +	regmap = devm_regmap_init_mmio(&pdev->dev, regs, &dmac_regmap_config);
> +	if (IS_ERR(regmap))
> +		return dev_err_probe(&pdev->dev, PTR_ERR(regmap),
> +				     "Failed to create regmap for I/O\n");
> +
> +	regmap_read(regmap, REG_CFG, &val);
> +	num_ch = val & CH_NUM;
> +	size = sizeof(*dmac) + num_ch * sizeof(struct atcdmac_chan);
> +	dmac = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
> +	if (!dmac)
> +		return -ENOMEM;
> +
> +	dmac->regmap = regmap;
> +	dmac->num_ch = num_ch;
> +	dmac->regs = regs;
> +
> +	/*
> +	 * Adjust the AXI bus data width (from the DMAC Configuration
> +	 * Register) to align with the transfer width encoding (in the
> +	 * Channel n Control Register). For example, an AXI width of 0
> +	 * (32-bit) corresponds to a transfer width of 2 (word transfer).
> +	 */
> +	dmac->data_width = FIELD_GET(DATA_WIDTH, val) + 2;
> +	spin_lock_init(&dmac->lock);
> +
> +	platform_set_drvdata(pdev, dmac);
> +	if (dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)))
> +		ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
> +	if (ret)
> +		return dev_err_probe(&pdev->dev, ret,
> +				     "Failed to set DMA mask\n");
> +
> +	*out_dmac = dmac;
> +
> +	return ret;
> +}
> +
> +static int atcdmac_probe(struct platform_device *pdev)
> +{
> +	struct atcdmac_dmac *dmac;
> +	int ret;
> +
> +	ret = atcdmac_init_ioremap_and_regmap(pdev, &dmac);
> +	if (ret)
> +		return ret;
> +
> +	ret = atcdmac_reset_and_wait_chan_idle(dmac);
> +	if (ret)
> +		return ret;
> +
> +	ret = atcdmac_init_desc_pool(pdev, dmac);
> +	if (ret)
> +		return ret;
> +
> +	ret = atcdmac_init_channels(pdev, dmac);
> +	if (ret)
> +		return ret;
> +
> +	atcdmac_init_dma_device(pdev, dmac);
> +
> +	ret = dma_async_device_register(&dmac->dma_device);
> +	if (ret)
> +		return ret;
> +
> +	ret = atcdmac_init_irq(pdev, dmac);
> +	if (ret)
> +		goto err_dma_async_register;
> +
> +	ret = of_dma_controller_register(pdev->dev.of_node,
> +					 atcdmac_dma_xlate_handler,
> +					 dmac);
> +	if (ret)
> +		goto err_dma_async_register;
> +
> +	ret = atcdmac_init_iocp(pdev, dmac);
> +	if (ret)
> +		goto err_of_dma_register;

Should this be last? I think this one should be before
of_dma_controller_register()

> +
> +	return 0;
> +
> +err_of_dma_register:
> +	of_dma_controller_free(pdev->dev.of_node);
> +err_dma_async_register:
> +	dma_async_device_unregister(&dmac->dma_device);
> +
> +	return ret;
> +}
> +
> +static int atcdmac_resume(struct device *dev)
> +{
> +	struct dma_chan *chan;
> +	struct dma_chan *chan_next;
> +	struct atcdmac_dmac *dmac = dev_get_drvdata(dev);
> +	struct atcdmac_chan *dmac_chan;
> +	unsigned long flags;
> +	int ret;
> +
> +	ret = atcdmac_reset_and_wait_chan_idle(dmac);
> +	if (ret)
> +		return ret;
> +
> +	ret = atcdmac_restore_iocp(dmac);
> +	if (ret)
> +		return ret;
> +
> +	spin_lock_irqsave(&dmac->lock, flags);
> +	dmac->stop_mask = 0;
> +	spin_unlock_irqrestore(&dmac->lock, flags);
> +	list_for_each_entry_safe(chan,
> +				 chan_next,
> +				 &dmac->dma_device.channels,
> +				 device_node) {
> +		dmac_chan = atcdmac_chan_to_dmac_chan(chan);
> +		spin_lock_irqsave(&dmac_chan->lock, flags);
> +		atcdmac_start_next_trans(dmac_chan);
> +		spin_unlock_irqrestore(&dmac_chan->lock, flags);
> +	}
> +
> +	return 0;
> +}
> +
> +static int atcdmac_suspend(struct device *dev)
> +{
> +	struct atcdmac_dmac *dmac = dev_get_drvdata(dev);
> +	int ret;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&dmac->lock, flags);
> +	dmac->stop_mask = BIT(dmac->num_ch) - 1;
> +	spin_unlock_irqrestore(&dmac->lock, flags);
> +	ret = atcdmac_wait_chan_idle(dmac,
> +				     dmac->stop_mask,
> +				     ATCDMAC_CHAN_TIMEOUT_US * dmac->num_ch);
> +
> +	return ret;
> +}
> +
> +static DEFINE_SIMPLE_DEV_PM_OPS(atcdmac_pm_ops,
> +				atcdmac_suspend,
> +				atcdmac_resume);
> +
> +static const struct of_device_id atcdmac_dt_ids[] = {
> +	{ .compatible = "andestech,ae350-dma", },
> +	{ /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, atcdmac_dt_ids);
> +
> +static struct platform_driver atcdmac_driver = {
> +	.probe = atcdmac_probe,
> +	.driver = {
> +		.name = "atcdmac300",
> +		.of_match_table = atcdmac_dt_ids,
> +		.pm = pm_sleep_ptr(&atcdmac_pm_ops),
> +	},
> +};
> +builtin_platform_driver(atcdmac_driver);

Any reason why this is not a module?

-- 
~Vinod

  parent reply	other threads:[~2026-06-08 11:06 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-01  9:48 [PATCH v4 0/3] dmaengine: atcdmac300: Add Andes ATCDMAC300 DMA driver CL Wang
2026-06-01  9:48 ` [PATCH v4 1/3] dt-bindings: dmaengine: Add support for ATCDMAC300 DMA engine CL Wang
2026-06-01  9:58   ` sashiko-bot
2026-06-01 21:35     ` Rob Herring
2026-06-01  9:48 ` [PATCH v4 2/3] dmaengine: atcdmac300: Add driver for Andes ATCDMAC300 DMA controller CL Wang
2026-06-01 10:13   ` sashiko-bot
2026-06-08 11:06   ` Vinod Koul [this message]
2026-06-01  9:48 ` [PATCH v4 3/3] MAINTAINERS: Add entry for Andes ATCDMAC300 CL Wang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=aiaiHGsWpzkUI73R@vaman \
    --to=vkoul@kernel.org \
    --cc=Frank.Li@kernel.org \
    --cc=cl634@andestech.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dmaengine@vger.kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=robh@kernel.org \
    --cc=tim609@andestech.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox