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 <[email protected]> --- 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 + */ +#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> +#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; + + 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); diff --git a/drivers/dma/atcdmac300.h b/drivers/dma/atcdmac300.h new file mode 100644 index 000000000000..3aee00e02130 --- /dev/null +++ b/drivers/dma/atcdmac300.h @@ -0,0 +1,296 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Header file for Andes ATCDMAC300 DMA controller driver + * + * Copyright (C) 2025 Andes Technology Corporation + */ +#ifndef ATCDMAC300_H +#define ATCDMAC300_H + +#include <linux/bitfield.h> +#include <linux/dmaengine.h> +#include <linux/dmapool.h> +#include <linux/interrupt.h> +#include <linux/regmap.h> +#include <linux/spinlock.h> + +/* + * Register Map Definitions for ATCDMAC300 DMA Controller + * + * These macros define the offsets and bit masks for various registers + * within the ATCDMAC300 DMA controller. + */ + +/* Global DMAC Registers */ +#define REG_CFG 0x10 +#define CH_NUM GENMASK(3, 0) +#define DATA_WIDTH GENMASK(25, 24) + +#define REG_CTL 0x20 +#define DMAC_RESET BIT(0) + +#define REG_CH_ABT 0x24 + +#define REG_INT_STA 0x30 +#define TC_OFFSET 16 +#define ABT_OFFSET 8 +#define ERR_OFFSET 0 +#define DMA_TC(val) BIT(TC_OFFSET + (val)) +#define DMA_ABT(val) BIT(ABT_OFFSET + (val)) +#define DMA_ERR(val) BIT(ERR_OFFSET + (val)) +#define DMA_TC_FIELD GENMASK(TC_OFFSET + 7, TC_OFFSET) +#define DMA_ABT_FIELD GENMASK(ABT_OFFSET + 7, ABT_OFFSET) +#define DMA_ERR_FIELD GENMASK(ERR_OFFSET + 7, ERR_OFFSET) +#define DMA_INT_ALL(val) (FIELD_GET(DMA_TC_FIELD, (val)) | \ + FIELD_GET(DMA_ABT_FIELD, (val)) | \ + FIELD_GET(DMA_ERR_FIELD, (val))) +#define DMA_INT_CLR(val) (FIELD_PREP(DMA_TC_FIELD, (val)) | \ + FIELD_PREP(DMA_ABT_FIELD, (val)) | \ + FIELD_PREP(DMA_ERR_FIELD, (val))) + +#define REG_CH_EN 0x34 + +#define REG_CH_OFF(ch) ((ch) * 0x20 + 0x40) +#define REG_CH_CTL_OFF 0x0 +#define REG_CH_SIZE_OFF 0x4 +#define REG_CH_SRC_LOW_OFF 0x8 +#define REG_CH_SRC_HIGH_OFF 0xC +#define REG_CH_DST_LOW_OFF 0x10 +#define REG_CH_DST_HIGH_OFF 0x14 +#define REG_CH_LLP_LOW_OFF 0x18 +#define REG_CH_LLP_HIGH_OFF 0x1C + +#define SRC_BURST_SIZE_1 BIT(0) +#define SRC_BURST_SIZE_2 BIT(1) +#define SRC_BURST_SIZE_4 BIT(2) +#define SRC_BURST_SIZE_8 BIT(3) +#define SRC_BURST_SIZE_16 BIT(4) +#define SRC_BURST_SIZE_32 BIT(5) +#define SRC_BURST_SIZE_64 BIT(6) +#define SRC_BURST_SIZE_128 BIT(7) +#define SRC_BURST_SIZE_256 BIT(8) +#define SRC_BURST_SIZE_512 BIT(9) +#define SRC_BURST_SIZE_1024 BIT(10) +#define SRC_BURST_SIZE_MASK GENMASK(27, 24) +#define SRC_BURST_SIZE(size) FIELD_PREP(SRC_BURST_SIZE_MASK, size) + +#define WIDTH_1_BYTE 0x0 +#define WIDTH_2_BYTES 0x1 +#define WIDTH_4_BYTES 0x2 +#define WIDTH_8_BYTES 0x3 +#define WIDTH_16_BYTES 0x4 +#define WIDTH_32_BYTES 0x5 +#define SRC_WIDTH_MASK GENMASK(23, 21) +#define SRC_WIDTH(width) FIELD_PREP(SRC_WIDTH_MASK, width) +#define SRC_WIDTH_GET(val) FIELD_GET(SRC_WIDTH_MASK, val) +#define DST_WIDTH_MASK GENMASK(20, 18) +#define DST_WIDTH(width) FIELD_PREP(DST_WIDTH_MASK, width) +#define DST_WIDTH_GET(val) FIELD_GET(DST_WIDTH_MASK, val) + +/* DMA handshake mode */ +#define SRC_HS BIT(17) +#define DST_HS BIT(16) + +/* Address control */ +#define SRC_ADDR_CTRL_MASK GENMASK(15, 14) +#define SRC_ADDR_MODE_INCR FIELD_PREP(SRC_ADDR_CTRL_MASK, 0x0) +#define SRC_ADDR_MODE_DECR FIELD_PREP(SRC_ADDR_CTRL_MASK, 0x1) +#define SRC_ADDR_MODE_FIXED FIELD_PREP(SRC_ADDR_CTRL_MASK, 0x2) +#define DST_ADDR_CTRL_MASK GENMASK(13, 12) +#define DST_ADDR_MODE_INCR FIELD_PREP(DST_ADDR_CTRL_MASK, 0x0) +#define DST_ADDR_MODE_DECR FIELD_PREP(DST_ADDR_CTRL_MASK, 0x1) +#define DST_ADDR_MODE_FIXED FIELD_PREP(DST_ADDR_CTRL_MASK, 0x2) + +/* DMA request select */ +#define SRC_REQ_SEL_MASK GENMASK(11, 8) +#define SRC_REQ(req_num) FIELD_PREP(SRC_REQ_SEL_MASK, (req_num)) +#define DST_REQ_SEL_MASK GENMASK(7, 4) +#define DST_REQ(req_num) FIELD_PREP(DST_REQ_SEL_MASK, (req_num)) + +/* Channel abort interrupt mask */ +#define INT_ABT_MASK BIT(3) +/* Channel error interrupt mask */ +#define INT_ERR_MASK BIT(2) +/* Channel terminal count interrupt mask */ +#define INT_TC_MASK BIT(1) + +/* Channel Enable */ +#define CHEN BIT(0) + +#define IOCP_CACHE_DMAC0_AW GENMASK(3, 0) +#define IOCP_CACHE_DMAC0_AR GENMASK(7, 4) +#define IOCP_CACHE_DMAC1_AW GENMASK(11, 8) +#define IOCP_CACHE_DMAC1_AR GENMASK(15, 12) + +#define ATCDMAC_DESC_PER_CHAN 64 +#define ATCDMAC_CHAN_TIMEOUT_US 100000 +/* + * Status for bottom-half processing. + */ +enum dma_sta { + ATCDMAC_STA_TC = 0, + ATCDMAC_STA_ERR, + ATCDMAC_STA_ABORT +}; + +/** + * struct atcdmac_regs - Hardware DMA descriptor registers. + * + * @ctrl: Channel Control Register. + * @trans_size: Transfer Size Register. + * @src_addr_lo: Source Address Register (low 32-bit). + * @src_addr_hi: Source Address Register (high 32-bit). + * @dst_addr_lo: Destination Address Register (low 32-bit). + * @dst_addr_hi: Destination Address Register (high 32-bit). + * @ll_ptr_lo: Linked List Pointer Register (low 32-bit). + * @ll_ptr_hi: Linked List Pointer Register (high 32-bit). + */ +struct atcdmac_regs { + unsigned int ctrl; + unsigned int trans_size; + unsigned int src_addr_lo; + unsigned int src_addr_hi; + unsigned int dst_addr_lo; + unsigned int dst_addr_hi; + unsigned int ll_ptr_lo; + unsigned int ll_ptr_hi; +}; + +/** + * struct atcdmac_desc - Internal representation of a DMA descriptor. + * + * @regs: Hardware registers for this descriptor. + * @txd: DMA transaction descriptor for dmaengine framework. + * @desc_node: Node for linking descriptors in software lists. + * @tx_list: List head for chaining multiple descriptors in a single transfer. + * @at: Next descriptor in a cyclic transfer (for internal use). + * @num_sg: Number of scatterlist entries this descriptor handles. + */ +struct atcdmac_desc { + struct atcdmac_regs regs; + struct dma_async_tx_descriptor txd; + struct list_head desc_node; + struct list_head tx_list; + struct list_head *at; + unsigned short num_sg; +}; + +/** + * struct atcdmac_chan - Private data for each DMA channel. + * + * @dma_chan: Common DMA engine channel object members + * @dma_dev: Pointer to the struct atcdmac_dmac + * @dma_sconfig: DMA transfer config for device-to-memory or memory-to-device + * @regmap: Regmap for the DMA channel register + * @active_list: List of descriptors being processed by the DMA engine + * @queue_list: List of descriptors ready to be submitted to the DMA engine + * @free_list: List of descriptors available for reuse by the channel + * @lock: Protects data access in atomic operations + * @status: Transmit status info, shared between top-half and threaded IRQ + * @descs_allocated: Number of descriptors currently allocated in the pool + * @chan_id: Channel ID number + * @req_num: Request number assigned to the channel + * @chan_used: Indicates whether the DMA channel is currently in use + * @cyclic: Indicates if the transfer operates in cyclic mode + * @dev_chan: Indicates if the DMA channel transfers data between device + * and memory + */ +struct atcdmac_chan { + struct dma_chan dma_chan; + struct atcdmac_dmac *dma_dev; + struct dma_slave_config dma_sconfig; + struct regmap *regmap; + + struct list_head active_list; + struct list_head queue_list; + struct list_head free_list; + + spinlock_t lock; /* protects active_list, queue_list, status */ + unsigned long status; + unsigned short descs_allocated; + unsigned char chan_id; + unsigned char req_num; + bool chan_used; + bool cyclic; + bool dev_chan; +}; + +/** + * struct atcdmac_dmac - Representation of the ATCDMAC300 DMA controller + * @dma_device: DMA device object for integration with DMA engine framework + * @regmap: Regmap for main DMA controller registers + * @regmap_iocp: Regmap for IOCP registers + * @dma_desc_pool: DMA descriptor pool for allocating and managing descriptors + * @regs: Memory-mapped base address of the main DMA controller registers + * @lock: Protects data access in atomic operations + * @used_chan: Bitmask of DMA channels actively issuing DMA descriptors + * @stop_mask: Stops the DMA channel after the current transaction completes + * @data_width: Max data bus width supported by the DMA controller + * @num_ch: Total number of DMA channels available in the controller + * @chan: Array of DMA channel structures, sized by the controller's channel + * count + */ +struct atcdmac_dmac { + struct dma_device dma_device; + struct regmap *regmap; + struct regmap *regmap_iocp; + struct dma_pool *dma_desc_pool; + void __iomem *regs; + spinlock_t lock; /* protects used_chan, stop_mask */ + unsigned short used_chan; + unsigned short stop_mask; + unsigned char data_width; + unsigned char num_ch; + struct atcdmac_chan chan[] __counted_by(num_ch); +}; + +/** + * struct atcdmac_slave_cfg - Pre-computed per-transfer slave DMA configuration + * + * @reg: Device-side register address (source for DEV_TO_MEM, destination + * for MEM_TO_DEV). + * @burst_bytes: Burst size in bytes (addr_width * maxburst from + * dma_slave_config). Used to derive the hardware burst-count + * field and to select the optimal transfer width. + * @dev_width: Device-side bus width in bytes (src_addr_width for DEV_TO_MEM, + * dst_addr_width for MEM_TO_DEV). Used to convert @burst_bytes + * into a hardware burst-count for the device side. + * @width_src: Hardware-encoded source transfer width (output of + * atcdmac_map_buswidth() on the source bus width). + * @width_dst: Hardware-encoded destination transfer width (output of + * atcdmac_map_buswidth() on the destination bus width). + */ +struct atcdmac_slave_cfg { + dma_addr_t reg; + unsigned int burst_bytes; + unsigned int dev_width; + unsigned int width_src; + unsigned int width_dst; +}; + +/* + * Helper functions to convert between dmaengine and internal structures. + */ +static inline struct atcdmac_desc * +atcdmac_txd_to_dma_desc(struct dma_async_tx_descriptor *txd) +{ + return container_of(txd, struct atcdmac_desc, txd); +} + +static inline struct atcdmac_chan * +atcdmac_chan_to_dmac_chan(struct dma_chan *chan) +{ + return container_of(chan, struct atcdmac_chan, dma_chan); +} + +static inline struct atcdmac_dmac *atcdmac_dev_to_dmac(struct dma_device *dev) +{ + return container_of(dev, struct atcdmac_dmac, dma_device); +} + +static inline struct device *atcdmac_chan_to_dev(struct dma_chan *chan) +{ + return &chan->dev->device; +} + +#endif -- 2.34.1

