diff options
| author | Rosen Penev <rosenp@gmail.com> | 2026-09-27 13:35:01 -0700 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2026-10-01 11:04:53 +0200 |
| commit | 8167c1f071426706c233e93ecfd13aba7d5c06c8 (patch) | |
| tree | 2c0cd5f5e1cc35eadee98d49fe034f96a88856ce /drivers/dma/dw-edma | |
| download | linux-stable-8167c1f071426706c233e93ecfd13aba7d5c06c8.tar.gz linux-stable-8167c1f071426706c233e93ecfd13aba7d5c06c8.zip | |
tty: serial: mpc52xx_uart: move static declarations up.grafted
Avoid a compilation error so that they're not used before being
declared.
Fixes: 4d105880666a ("tty: serial: mpc52xx_uart: add bounds check for psc_num array index")
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202609260356.tJWbd1WU-lkp@intel.com/
Signed-off-by: Rosen Penev <rosenp@gmail.com>
Link: https://patch.msgid.link/20260927203501.14105-1-rosenp@gmail.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/dma/dw-edma')
| -rw-r--r-- | drivers/dma/dw-edma/Kconfig | 22 | ||||
| -rw-r--r-- | drivers/dma/dw-edma/Makefile | 9 | ||||
| -rw-r--r-- | drivers/dma/dw-edma/dw-edma-core.c | 1308 | ||||
| -rw-r--r-- | drivers/dma/dw-edma/dw-edma-core.h | 290 | ||||
| -rw-r--r-- | drivers/dma/dw-edma/dw-edma-pcie.c | 668 | ||||
| -rw-r--r-- | drivers/dma/dw-edma/dw-edma-v0-core.c | 664 | ||||
| -rw-r--r-- | drivers/dma/dw-edma/dw-edma-v0-core.h | 17 | ||||
| -rw-r--r-- | drivers/dma/dw-edma/dw-edma-v0-debugfs.c | 292 | ||||
| -rw-r--r-- | drivers/dma/dw-edma/dw-edma-v0-debugfs.h | 22 | ||||
| -rw-r--r-- | drivers/dma/dw-edma/dw-edma-v0-regs.h | 233 | ||||
| -rw-r--r-- | drivers/dma/dw-edma/dw-hdma-v0-core.c | 419 | ||||
| -rw-r--r-- | drivers/dma/dw-edma/dw-hdma-v0-core.h | 17 | ||||
| -rw-r--r-- | drivers/dma/dw-edma/dw-hdma-v0-debugfs.c | 170 | ||||
| -rw-r--r-- | drivers/dma/dw-edma/dw-hdma-v0-debugfs.h | 22 | ||||
| -rw-r--r-- | drivers/dma/dw-edma/dw-hdma-v0-regs.h | 131 |
15 files changed, 4284 insertions, 0 deletions
diff --git a/drivers/dma/dw-edma/Kconfig b/drivers/dma/dw-edma/Kconfig new file mode 100644 index 000000000..2b6f26795 --- /dev/null +++ b/drivers/dma/dw-edma/Kconfig @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0 + +config DW_EDMA + tristate "Synopsys DesignWare eDMA controller driver" + depends on PCI && PCI_MSI + select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS + help + Support the Synopsys DesignWare eDMA controller, normally + implemented on endpoints SoCs. + +if DW_EDMA + +config DW_EDMA_PCIE + tristate "Synopsys DesignWare eDMA PCIe driver" + depends on PCI && PCI_MSI + help + Provides a glue-logic between the Synopsys DesignWare + eDMA controller and an endpoint PCIe device. This also serves + as a reference design to whom desires to use this IP. + +endif # DW_EDMA diff --git a/drivers/dma/dw-edma/Makefile b/drivers/dma/dw-edma/Makefile new file mode 100644 index 000000000..83ab58f87 --- /dev/null +++ b/drivers/dma/dw-edma/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_DW_EDMA) += dw-edma.o +dw-edma-$(CONFIG_DEBUG_FS) := dw-edma-v0-debugfs.o \ + dw-hdma-v0-debugfs.o +dw-edma-objs := dw-edma-core.o \ + dw-edma-v0-core.o \ + dw-hdma-v0-core.o $(dw-edma-y) +obj-$(CONFIG_DW_EDMA_PCIE) += dw-edma-pcie.o diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c new file mode 100644 index 000000000..03b2c2188 --- /dev/null +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -0,0 +1,1308 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates. + * Synopsys DesignWare eDMA core driver + * + * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com> + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/dmaengine.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/dma/edma.h> +#include <linux/dma-mapping.h> +#include <linux/string_choices.h> + +#include "dw-edma-core.h" +#include "dw-edma-v0-core.h" +#include "dw-hdma-v0-core.h" +#include "../dmaengine.h" +#include "../virt-dma.h" + +static inline +struct dw_edma_desc *vd2dw_edma_desc(struct virt_dma_desc *vd) +{ + return container_of(vd, struct dw_edma_desc, vd); +} + +enum dw_edma_irq_event { + DW_EDMA_IRQ_DONE = BIT(0), + DW_EDMA_IRQ_ABORT = BIT(1), +}; + +static inline +u64 dw_edma_get_pci_address(struct dw_edma_chan *chan, phys_addr_t cpu_addr) +{ + struct dw_edma_chip *chip = chan->dw->chip; + + if (chip->ops->pci_address) + return chip->ops->pci_address(chip->dev, cpu_addr); + + return cpu_addr; +} + +static struct dw_edma_desc * +dw_edma_alloc_desc(struct dw_edma_chan *chan, size_t nburst) +{ + struct dw_edma_desc *desc; + + desc = kzalloc_flex(*desc, burst, nburst, GFP_NOWAIT); + if (unlikely(!desc)) + return NULL; + + desc->chan = chan; + desc->nburst = nburst; + desc->cb = true; + + return desc; +} + +static void vchan_free_desc(struct virt_dma_desc *vdesc) +{ + kfree(vd2dw_edma_desc(vdesc)); +} + +static void dw_edma_core_start(struct dw_edma_desc *desc, bool first) +{ + struct dw_edma_chan *chan = desc->chan; + size_t i = 0; + + if (chan->non_ll) { + chan->dw->core->non_ll_start(chan, &desc->burst[desc->start_burst]); + desc->done_burst = desc->start_burst; + desc->start_burst += 1; + return; + } + + for (i = 0; i + desc->start_burst < desc->nburst; i++) { + u32 idx = i + desc->start_burst; + + if (i == chan->ll_max) + break; + + dw_edma_core_ll_data(chan, &desc->burst[idx], + i, desc->cb, + idx == desc->nburst - 1 || i == chan->ll_max - 1); + } + + desc->done_burst = desc->start_burst; + desc->start_burst += i; + + dw_edma_core_ll_link(chan, i, desc->cb, chan->ll_region.paddr); + + if (first) + dw_edma_core_ch_enable(chan); + + dw_edma_core_ch_doorbell(chan); +} + +static int dw_edma_start_transfer(struct dw_edma_chan *chan) +{ + struct dw_edma_desc *desc; + struct virt_dma_desc *vd; + + vd = vchan_next_desc(&chan->vc); + if (!vd) + return 0; + + desc = vd2dw_edma_desc(vd); + if (!desc) + return 0; + + dw_edma_core_start(desc, !desc->start_burst); + + desc->cb = !desc->cb; + + return 1; +} + +static void dw_edma_terminate_vdesc(struct virt_dma_desc *vd) +{ + list_del(&vd->node); + dma_cookie_complete(&vd->tx); + vchan_terminate_vdesc(vd); +} + +static void dw_edma_terminate_vdesc_list(struct list_head *head) +{ + struct virt_dma_desc *vd, *_vd; + + list_for_each_entry_safe(vd, _vd, head, node) + dw_edma_terminate_vdesc(vd); +} + +/* Must be called with vc.lock held. */ +static void dw_edma_terminate_all_descs(struct dw_edma_chan *chan) +{ + /* + * This order must not be reversed. Cookies are assigned when + * descriptors are submitted, so desc_issued contains older cookies + * than desc_submitted. Completing desc_submitted first could move + * chan->vc.chan.completed_cookie backwards when desc_issued is + * terminated afterwards. + */ + dw_edma_terminate_vdesc_list(&chan->vc.desc_issued); + dw_edma_terminate_vdesc_list(&chan->vc.desc_submitted); +} + +static void dw_edma_device_caps(struct dma_chan *dchan, + struct dma_slave_caps *caps) +{ + struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan); + + if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) { + if (chan->dir == EDMA_DIR_READ) + caps->directions = BIT(DMA_DEV_TO_MEM); + else + caps->directions = BIT(DMA_MEM_TO_DEV); + } else { + if (chan->dir == EDMA_DIR_WRITE) + caps->directions = BIT(DMA_DEV_TO_MEM); + else + caps->directions = BIT(DMA_MEM_TO_DEV); + } +} + +static enum dw_edma_ch_irq_mode +dw_edma_get_default_irq_mode(struct dw_edma_chan *chan) +{ + struct dw_edma_chip *chip = chan->dw->chip; + + return chip->flags & DW_EDMA_CHIP_LOCAL ? DW_EDMA_CH_IRQ_LOCAL : + DW_EDMA_CH_IRQ_REMOTE; +} + +static int dw_edma_device_config(struct dma_chan *dchan, + struct dma_slave_config *config) +{ + struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan); + bool cfg_non_ll; + int non_ll = 0; + + chan->non_ll = false; + if (chan->dw->chip->mf == EDMA_MF_HDMA_NATIVE) { + if (config->peripheral_config && + config->peripheral_size != sizeof(int)) { + dev_err(dchan->device->dev, + "config param peripheral size mismatch\n"); + return -EINVAL; + } + + /* + * When there is no valid LLP base address available then the + * default DMA ops will use the non-LL mode. + * + * Cases where LL mode is enabled and client wants to use the + * non-LL mode then also client can do so via providing the + * peripheral_config param. + */ + cfg_non_ll = chan->dw->chip->cfg_non_ll; + if (config->peripheral_config) { + non_ll = *(int *)config->peripheral_config; + + if (cfg_non_ll && !non_ll) { + dev_err(dchan->device->dev, "invalid configuration\n"); + return -EINVAL; + } + } + + if (cfg_non_ll || non_ll) + chan->non_ll = true; + } else if (config->peripheral_config) { + dev_err(dchan->device->dev, + "peripheral config param applicable only for HDMA\n"); + return -EINVAL; + } + + memcpy(&chan->config, config, sizeof(*config)); + chan->configured = true; + + return 0; +} + +static struct dma_slave_config * +dw_edma_device_get_config(struct dma_chan *dchan, + struct dma_slave_config *config) +{ + struct dw_edma_chan *chan; + + if (config) + return config; + + chan = dchan2dw_edma_chan(dchan); + + return &chan->config; +} + +static int dw_edma_device_pause(struct dma_chan *dchan) +{ + struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan); + int err = 0; + + guard(spinlock_irqsave)(&chan->vc.lock); + + if (!chan->configured) + err = -EPERM; + else if (chan->status != EDMA_ST_BUSY) + err = -EPERM; + else if (chan->request != EDMA_REQ_NONE) + err = -EPERM; + else + chan->request = EDMA_REQ_PAUSE; + + return err; +} + +static int dw_edma_device_resume(struct dma_chan *dchan) +{ + struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan); + int err = 0; + + guard(spinlock_irqsave)(&chan->vc.lock); + + if (!chan->configured) { + err = -EPERM; + } else if (chan->status != EDMA_ST_PAUSE) { + err = -EPERM; + } else if (chan->request != EDMA_REQ_NONE) { + err = -EPERM; + } else { + chan->status = EDMA_ST_BUSY; + if (!dw_edma_start_transfer(chan)) + chan->status = EDMA_ST_IDLE; + } + + return err; +} + +static int dw_edma_device_terminate_all(struct dma_chan *dchan) +{ + struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan); + int err = 0; + + guard(spinlock_irqsave)(&chan->vc.lock); + + if (!chan->configured) { + dw_edma_terminate_all_descs(chan); + } else if (chan->status == EDMA_ST_PAUSE) { + dw_edma_terminate_all_descs(chan); + chan->status = EDMA_ST_IDLE; + } else if (chan->status == EDMA_ST_IDLE) { + dw_edma_terminate_all_descs(chan); + } else if (dw_edma_core_ch_status(chan) == DMA_COMPLETE) { + /* + * The channel is in a false BUSY state, probably didn't + * receive or lost an interrupt + */ + dw_edma_terminate_all_descs(chan); + chan->status = EDMA_ST_IDLE; + } else if (chan->request > EDMA_REQ_PAUSE) { + err = -EPERM; + } else { + chan->request = EDMA_REQ_STOP; + } + if (chan->status == EDMA_ST_IDLE) + chan->request = EDMA_REQ_NONE; + + return err; +} + +static void dw_edma_device_issue_pending(struct dma_chan *dchan) +{ + struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan); + unsigned long flags; + + spin_lock_irqsave(&chan->vc.lock, flags); + if (chan->configured && vchan_issue_pending(&chan->vc) && + chan->request == EDMA_REQ_NONE && + chan->status == EDMA_ST_IDLE) { + chan->status = EDMA_ST_BUSY; + dw_edma_start_transfer(chan); + } + spin_unlock_irqrestore(&chan->vc.lock, flags); +} + +static enum dma_status +dw_edma_device_tx_status(struct dma_chan *dchan, dma_cookie_t cookie, + struct dma_tx_state *txstate) +{ + struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan); + struct dw_edma_desc *desc; + struct virt_dma_desc *vd; + unsigned long flags; + enum dma_status ret; + u32 residue = 0; + + ret = dma_cookie_status(dchan, cookie, txstate); + if (ret == DMA_COMPLETE) + return ret; + + if (ret == DMA_IN_PROGRESS && chan->status == EDMA_ST_PAUSE) + ret = DMA_PAUSED; + + if (!txstate) + goto ret_residue; + + spin_lock_irqsave(&chan->vc.lock, flags); + vd = vchan_find_desc(&chan->vc, cookie); + if (vd) { + desc = vd2dw_edma_desc(vd); + + residue = desc->alloc_sz; + if (desc && desc->done_burst) + residue -= desc->burst[desc->done_burst - 1].xfer_sz; + } + spin_unlock_irqrestore(&chan->vc.lock, flags); + +ret_residue: + dma_set_residue(txstate, residue); + + return ret; +} + +static struct dma_async_tx_descriptor * +dw_edma_device_transfer(struct dw_edma_transfer *xfer, + struct dma_slave_config *config) +{ + struct dw_edma_chan *chan = dchan2dw_edma_chan(xfer->dchan); + enum dma_transfer_direction dir = xfer->direction; + struct scatterlist *sg = NULL; + struct dw_edma_burst *burst; + struct dw_edma_desc *desc; + u64 src_addr, dst_addr; + size_t fsz = 0; + size_t cnt = 0; + u32 i; + + if (!chan->configured) + return NULL; + + /* + * Local Root Port/End-point Remote End-point + * +-----------------------+ PCIe bus +----------------------+ + * | | +-+ | | + * | DEV_TO_MEM Rx Ch <----+ +---+ Tx Ch DEV_TO_MEM | + * | | | | | | + * | MEM_TO_DEV Tx Ch +----+ +---> Rx Ch MEM_TO_DEV | + * | | +-+ | | + * +-----------------------+ +----------------------+ + * + * 1. Normal logic: + * If eDMA is embedded into the DW PCIe RP/EP and controlled from the + * CPU/Application side, the Rx channel (EDMA_DIR_READ) will be used + * for the device read operations (DEV_TO_MEM) and the Tx channel + * (EDMA_DIR_WRITE) - for the write operations (MEM_TO_DEV). + * + * 2. Inverted logic: + * If eDMA is embedded into a Remote PCIe EP and is controlled by the + * MWr/MRd TLPs sent from the CPU's PCIe host controller, the Tx + * channel (EDMA_DIR_WRITE) will be used for the device read operations + * (DEV_TO_MEM) and the Rx channel (EDMA_DIR_READ) - for the write + * operations (MEM_TO_DEV). + * + * It is the client driver responsibility to choose a proper channel + * for the DMA transfers. + */ + if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) { + if ((chan->dir == EDMA_DIR_READ && dir != DMA_DEV_TO_MEM) || + (chan->dir == EDMA_DIR_WRITE && dir != DMA_MEM_TO_DEV)) + return NULL; + } else { + if ((chan->dir == EDMA_DIR_WRITE && dir != DMA_DEV_TO_MEM) || + (chan->dir == EDMA_DIR_READ && dir != DMA_MEM_TO_DEV)) + return NULL; + } + + if (xfer->type == EDMA_XFER_CYCLIC) { + if (!xfer->xfer.cyclic.len || !xfer->xfer.cyclic.cnt) + return NULL; + } else if (xfer->type == EDMA_XFER_SCATTER_GATHER) { + if (xfer->xfer.sg.len < 1) + return NULL; + } else if (xfer->type == EDMA_XFER_INTERLEAVED) { + if (!xfer->xfer.il->numf || xfer->xfer.il->frame_size < 1) + return NULL; + if (!xfer->xfer.il->src_inc || !xfer->xfer.il->dst_inc) + return NULL; + } else { + return NULL; + } + + if (xfer->type == EDMA_XFER_INTERLEAVED) { + src_addr = xfer->xfer.il->src_start; + dst_addr = xfer->xfer.il->dst_start; + } else { + src_addr = config->src_addr; + dst_addr = config->dst_addr; + } + + if (dir == DMA_DEV_TO_MEM) + src_addr = dw_edma_get_pci_address(chan, (phys_addr_t)src_addr); + else + dst_addr = dw_edma_get_pci_address(chan, (phys_addr_t)dst_addr); + + if (xfer->type == EDMA_XFER_CYCLIC) { + cnt = xfer->xfer.cyclic.cnt; + } else if (xfer->type == EDMA_XFER_SCATTER_GATHER) { + cnt = xfer->xfer.sg.len; + sg = xfer->xfer.sg.sgl; + } else if (xfer->type == EDMA_XFER_INTERLEAVED) { + cnt = xfer->xfer.il->numf * xfer->xfer.il->frame_size; + fsz = xfer->xfer.il->frame_size; + } + + desc = dw_edma_alloc_desc(chan, cnt); + if (unlikely(!desc)) + return NULL; + + for (i = 0; i < cnt; i++) { + if (xfer->type == EDMA_XFER_SCATTER_GATHER && !sg) + break; + + burst = desc->burst + i; + + if (xfer->type == EDMA_XFER_CYCLIC) + burst->sz = xfer->xfer.cyclic.len; + else if (xfer->type == EDMA_XFER_SCATTER_GATHER) + burst->sz = sg_dma_len(sg); + else if (xfer->type == EDMA_XFER_INTERLEAVED) + burst->sz = xfer->xfer.il->sgl[i % fsz].size; + + desc->alloc_sz += burst->sz; + burst->xfer_sz = desc->alloc_sz; + + if (dir == DMA_DEV_TO_MEM) { + burst->sar = src_addr; + if (xfer->type == EDMA_XFER_CYCLIC) { + burst->dar = xfer->xfer.cyclic.paddr; + } else if (xfer->type == EDMA_XFER_SCATTER_GATHER) { + src_addr += sg_dma_len(sg); + burst->dar = sg_dma_address(sg); + /* Unlike the typical assumption by other + * drivers/IPs the peripheral memory isn't + * a FIFO memory, in this case, it's a + * linear memory and that why the source + * and destination addresses are increased + * by the same portion (data length) + */ + } else if (xfer->type == EDMA_XFER_INTERLEAVED) { + burst->dar = dst_addr; + } + } else { + burst->dar = dst_addr; + if (xfer->type == EDMA_XFER_CYCLIC) { + burst->sar = xfer->xfer.cyclic.paddr; + } else if (xfer->type == EDMA_XFER_SCATTER_GATHER) { + dst_addr += sg_dma_len(sg); + burst->sar = sg_dma_address(sg); + /* Unlike the typical assumption by other + * drivers/IPs the peripheral memory isn't + * a FIFO memory, in this case, it's a + * linear memory and that why the source + * and destination addresses are increased + * by the same portion (data length) + */ + } else if (xfer->type == EDMA_XFER_INTERLEAVED) { + burst->sar = src_addr; + } + } + + if (xfer->type == EDMA_XFER_SCATTER_GATHER) { + sg = sg_next(sg); + } else if (xfer->type == EDMA_XFER_INTERLEAVED) { + struct dma_interleaved_template *il = xfer->xfer.il; + struct data_chunk *dc = &il->sgl[i % fsz]; + + src_addr += burst->sz; + if (il->src_sgl) + src_addr += dmaengine_get_src_icg(il, dc); + + dst_addr += burst->sz; + if (il->dst_sgl) + dst_addr += dmaengine_get_dst_icg(il, dc); + } + } + + return vchan_tx_prep(&chan->vc, &desc->vd, xfer->flags); +} + +static struct dma_async_tx_descriptor * +dw_edma_device_prep_config_sg(struct dma_chan *dchan, struct scatterlist *sgl, + unsigned int len, + enum dma_transfer_direction direction, + unsigned long flags, + struct dma_slave_config *config) +{ + struct dw_edma_transfer xfer; + + xfer.dchan = dchan; + xfer.direction = direction; + xfer.xfer.sg.sgl = sgl; + xfer.xfer.sg.len = len; + xfer.flags = flags; + xfer.type = EDMA_XFER_SCATTER_GATHER; + + if (config && dw_edma_device_config(dchan, config)) + return NULL; + + return dw_edma_device_transfer(&xfer, dw_edma_device_get_config(dchan, config)); +} + +static struct dma_async_tx_descriptor * +dw_edma_device_prep_dma_cyclic(struct dma_chan *dchan, dma_addr_t paddr, + size_t len, size_t count, + enum dma_transfer_direction direction, + unsigned long flags) +{ + struct dw_edma_transfer xfer; + + xfer.dchan = dchan; + xfer.direction = direction; + xfer.xfer.cyclic.paddr = paddr; + xfer.xfer.cyclic.len = len; + xfer.xfer.cyclic.cnt = count; + xfer.flags = flags; + xfer.type = EDMA_XFER_CYCLIC; + + return dw_edma_device_transfer(&xfer, dw_edma_device_get_config(dchan, NULL)); +} + +static struct dma_async_tx_descriptor * +dw_edma_device_prep_interleaved_dma(struct dma_chan *dchan, + struct dma_interleaved_template *ilt, + unsigned long flags) +{ + struct dw_edma_transfer xfer; + + xfer.dchan = dchan; + xfer.direction = ilt->dir; + xfer.xfer.il = ilt; + xfer.flags = flags; + xfer.type = EDMA_XFER_INTERLEAVED; + + return dw_edma_device_transfer(&xfer, dw_edma_device_get_config(dchan, NULL)); +} + +static void dw_hdma_set_callback_result(struct virt_dma_desc *vd, + enum dmaengine_tx_result result) +{ + u32 residue = 0; + struct dw_edma_desc *desc; + struct dmaengine_result *res; + + if (!vd->tx.callback_result) + return; + + desc = vd2dw_edma_desc(vd); + if (desc) { + residue = desc->alloc_sz; + + if (result == DMA_TRANS_NOERROR) + residue -= desc->burst[desc->start_burst - 1].xfer_sz; + else if (desc->done_burst) + residue -= desc->burst[desc->done_burst - 1].xfer_sz; + } + + res = &vd->tx_result; + res->result = result; + res->residue = residue; +} + +static void dw_edma_done_interrupt(struct dw_edma_chan *chan) +{ + struct dw_edma_desc *desc; + struct virt_dma_desc *vd; + unsigned long flags; + + spin_lock_irqsave(&chan->vc.lock, flags); + if (chan->status == EDMA_ST_PAUSE) { + spin_unlock_irqrestore(&chan->vc.lock, flags); + return; + } + + vd = vchan_next_desc(&chan->vc); + if (vd) { + switch (chan->request) { + case EDMA_REQ_NONE: + case EDMA_REQ_PAUSE: + desc = vd2dw_edma_desc(vd); + if (desc->start_burst >= desc->nburst) { + dw_hdma_set_callback_result(vd, + DMA_TRANS_NOERROR); + list_del(&vd->node); + vchan_cookie_complete(vd); + } + + if (chan->request == EDMA_REQ_PAUSE) { + chan->request = EDMA_REQ_NONE; + chan->status = EDMA_ST_PAUSE; + break; + } + + /* Continue transferring if there are remaining chunks or issued requests. + */ + chan->status = dw_edma_start_transfer(chan) ? EDMA_ST_BUSY : EDMA_ST_IDLE; + break; + + case EDMA_REQ_STOP: + dw_edma_terminate_all_descs(chan); + chan->request = EDMA_REQ_NONE; + chan->status = EDMA_ST_IDLE; + break; + + default: + break; + } + } + spin_unlock_irqrestore(&chan->vc.lock, flags); +} + +static void dw_edma_abort_interrupt(struct dw_edma_chan *chan) +{ + struct virt_dma_desc *vd; + unsigned long flags; + + spin_lock_irqsave(&chan->vc.lock, flags); + vd = vchan_next_desc(&chan->vc); + if (vd && chan->request == EDMA_REQ_STOP) { + dw_edma_terminate_all_descs(chan); + } else if (vd) { + dw_hdma_set_callback_result(vd, DMA_TRANS_ABORTED); + list_del(&vd->node); + vchan_cookie_complete(vd); + } + chan->request = EDMA_REQ_NONE; + chan->status = EDMA_ST_IDLE; + spin_unlock_irqrestore(&chan->vc.lock, flags); +} + +static void dw_edma_irq_work(struct work_struct *work) +{ + struct dw_edma_chan *chan = container_of(work, struct dw_edma_chan, + irq_work); + unsigned int events; + + do { + events = atomic_xchg(&chan->irq_pending, 0); + + if (events & DW_EDMA_IRQ_DONE) + dw_edma_done_interrupt(chan); + if (events & DW_EDMA_IRQ_ABORT) + dw_edma_abort_interrupt(chan); + } while (atomic_read(&chan->irq_pending)); +} + +static void dw_edma_queue_irq_work(struct dw_edma_chan *chan, + enum dw_edma_irq_event event) +{ + atomic_or(event, &chan->irq_pending); + queue_work(chan->dw->wq, &chan->irq_work); +} + +static void dw_edma_done_interrupt_deferred(struct dw_edma_chan *chan) +{ + dw_edma_queue_irq_work(chan, DW_EDMA_IRQ_DONE); +} + +static void dw_edma_abort_interrupt_deferred(struct dw_edma_chan *chan) +{ + dw_edma_queue_irq_work(chan, DW_EDMA_IRQ_ABORT); +} + +static void dw_edma_emul_irq_ack(struct irq_data *d) +{ + struct dw_edma *dw = irq_data_get_irq_chip_data(d); + + dw_edma_core_ack_emulated_irq(dw); +} + +/* + * irq_chip implementation for interrupt-emulation doorbells. + * + * The emulated source has no mask/unmask mechanism. With handle_level_irq(), + * the flow is therefore: + * 1) .irq_ack() deasserts the source + * 2) registered handlers (if any) are dispatched + * Since deassertion is already done in .irq_ack(), handlers do not need to take + * care of it, hence IRQCHIP_ONESHOT_SAFE. + */ +static struct irq_chip dw_edma_emul_irqchip = { + .name = "dw-edma-emul", + .irq_ack = dw_edma_emul_irq_ack, + .flags = IRQCHIP_ONESHOT_SAFE | IRQCHIP_SKIP_SET_WAKE, +}; + +static int dw_edma_emul_irq_alloc(struct dw_edma *dw) +{ + struct dw_edma_chip *chip = dw->chip; + int virq; + + chip->db_irq = 0; + chip->db_offset = ~0; + + if (chip->flags & DW_EDMA_CHIP_PARTIAL) + return 0; + + /* + * Only meaningful when the core provides the deassert sequence + * for interrupt emulation. + */ + if (!dw->core->ack_emulated_irq) + return 0; + + /* + * Allocate a single, requestable Linux virtual IRQ number. + * Use >= 1 so that 0 can remain a "not available" sentinel. + */ + virq = irq_alloc_desc(NUMA_NO_NODE); + if (virq < 0) + return virq; + + irq_set_chip_and_handler(virq, &dw_edma_emul_irqchip, handle_level_irq); + irq_set_status_flags(virq, IRQ_LEVEL); + irq_set_chip_data(virq, dw); + irq_set_noprobe(virq); + + chip->db_irq = virq; + chip->db_offset = dw_edma_core_db_offset(dw); + + return 0; +} + +static void dw_edma_emul_irq_free(struct dw_edma *dw) +{ + struct dw_edma_chip *chip = dw->chip; + + if (!chip) + return; + if (chip->db_irq <= 0) + return; + + irq_free_descs(chip->db_irq, 1); + chip->db_irq = 0; + chip->db_offset = ~0; +} + +static inline irqreturn_t dw_edma_interrupt_emulated(void *data) +{ + struct dw_edma_irq *dw_irq = data; + struct dw_edma *dw = dw_irq->dw; + int db_irq = dw->chip->db_irq; + + if (db_irq > 0) { + /* + * Interrupt emulation may assert the IRQ line without updating the + * normal DONE/ABORT status bits. With a shared IRQ handler we + * cannot reliably detect such events by status registers alone, so + * always perform the core-specific deassert sequence. + */ + generic_handle_irq(db_irq); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static inline irqreturn_t dw_edma_interrupt_write_inner(int irq, void *data) +{ + struct dw_edma_irq *dw_irq = data; + + return dw_edma_core_handle_int(dw_irq, EDMA_DIR_WRITE, + dw_edma_done_interrupt_deferred, + dw_edma_abort_interrupt_deferred); +} + +static inline irqreturn_t dw_edma_interrupt_read_inner(int irq, void *data) +{ + struct dw_edma_irq *dw_irq = data; + + return dw_edma_core_handle_int(dw_irq, EDMA_DIR_READ, + dw_edma_done_interrupt_deferred, + dw_edma_abort_interrupt_deferred); +} + +static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data) +{ + irqreturn_t ret = IRQ_NONE; + + ret |= dw_edma_interrupt_write_inner(irq, data); + ret |= dw_edma_interrupt_emulated(data); + + return ret; +} + +static inline irqreturn_t dw_edma_interrupt_read(int irq, void *data) +{ + irqreturn_t ret = IRQ_NONE; + + ret |= dw_edma_interrupt_read_inner(irq, data); + ret |= dw_edma_interrupt_emulated(data); + + return ret; +} + +static inline irqreturn_t dw_edma_interrupt_common(int irq, void *data) +{ + irqreturn_t ret = IRQ_NONE; + + ret |= dw_edma_interrupt_write_inner(irq, data); + ret |= dw_edma_interrupt_read_inner(irq, data); + ret |= dw_edma_interrupt_emulated(data); + + return ret; +} + +static int dw_edma_alloc_chan_resources(struct dma_chan *dchan) +{ + struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan); + + if (chan->status != EDMA_ST_IDLE) + return -EBUSY; + + return 0; +} + +static void dw_edma_wait_termination(struct dma_chan *dchan) +{ + struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan); + unsigned long timeout = jiffies + msecs_to_jiffies(5000); + bool stopping; + + /* + * A STOP may be deferred to a later interrupt while the channel is still + * running. Wait until that handler completes the termination. + */ + while (time_before(jiffies, timeout)) { + scoped_guard(spinlock_irqsave, &chan->vc.lock) + stopping = chan->request == EDMA_REQ_STOP; + + if (!stopping) + return; + + fsleep(1000); + } + + dev_warn(chan->dw->chip->dev, + "timeout waiting for channel termination\n"); +} + +static void dw_edma_device_synchronize(struct dma_chan *dchan) +{ + struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan); + + dw_edma_wait_termination(dchan); + cancel_work_sync(&chan->irq_work); + atomic_set(&chan->irq_pending, 0); + vchan_synchronize(&chan->vc); +} + +static void dw_edma_free_chan_resources(struct dma_chan *dchan) +{ + struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan); + + dw_edma_device_terminate_all(dchan); + dw_edma_device_synchronize(dchan); + + scoped_guard(spinlock_irqsave, &chan->vc.lock) + chan->configured = false; + + vchan_free_chan_resources(&chan->vc); +} + +static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc) +{ + struct dw_edma_chip *chip = dw->chip; + struct device *dev = chip->dev; + struct dw_edma_chan *chan; + struct dw_edma_irq *irq; + struct dma_device *dma; + u32 i, ch_cnt; + u32 pos; + + ch_cnt = dw->wr_ch_cnt + dw->rd_ch_cnt; + dma = &dw->dma; + + INIT_LIST_HEAD(&dma->channels); + + for (i = 0; i < ch_cnt; i++) { + chan = &dw->chan[i]; + + chan->dw = dw; + chan->func_no = chip->func_no; + + if (i < dw->wr_ch_cnt) { + chan->id = i; + chan->dir = EDMA_DIR_WRITE; + } else { + chan->id = i - dw->wr_ch_cnt; + chan->dir = EDMA_DIR_READ; + } + + chan->configured = false; + chan->request = EDMA_REQ_NONE; + chan->status = EDMA_ST_IDLE; + chan->irq_mode = dw_edma_get_default_irq_mode(chan); + INIT_WORK(&chan->irq_work, dw_edma_irq_work); + atomic_set(&chan->irq_pending, 0); + + if (chan->dir == EDMA_DIR_WRITE) + chan->ll_region = chip->ll_region_wr[chan->id]; + else + chan->ll_region = chip->ll_region_rd[chan->id]; + + chan->ll_max = chan->ll_region.sz / EDMA_LL_SZ - 1; + + dev_vdbg(dev, "L. List:\tChannel %s[%u] max_cnt=%u\n", + str_write_read(chan->dir == EDMA_DIR_WRITE), + chan->id, chan->ll_max); + + if (dw->nr_irqs == 1) + pos = 0; + else if (chan->dir == EDMA_DIR_WRITE) + pos = chan->id % wr_alloc; + else + pos = wr_alloc + chan->id % rd_alloc; + + irq = &dw->irq[pos]; + + if (chan->dir == EDMA_DIR_WRITE) + bitmap_set(irq->wr_mask, chan->id, 1); + else + bitmap_set(irq->rd_mask, chan->id, 1); + + memcpy(&chan->msi, &irq->msi, sizeof(chan->msi)); + + dev_vdbg(dev, "MSI:\t\tChannel %s[%u] addr=0x%.8x%.8x, data=0x%.8x\n", + str_write_read(chan->dir == EDMA_DIR_WRITE), + chan->id, + chan->msi.address_hi, chan->msi.address_lo, + chan->msi.data); + + chan->vc.desc_free = vchan_free_desc; + chan->vc.chan.private = chan->dir == EDMA_DIR_WRITE ? + &dw->chip->dt_region_wr[chan->id] : + &dw->chip->dt_region_rd[chan->id]; + + vchan_init(&chan->vc, dma); + + dw_edma_core_ch_config(chan); + } + + /* Set DMA channel capabilities */ + dma_cap_zero(dma->cap_mask); + dma_cap_set(DMA_SLAVE, dma->cap_mask); + dma_cap_set(DMA_CYCLIC, dma->cap_mask); + dma_cap_set(DMA_PRIVATE, dma->cap_mask); + dma_cap_set(DMA_INTERLEAVE, dma->cap_mask); + dma->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); + dma->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + dma->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + dma->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR; + + /* Set DMA channel callbacks */ + dma->dev = chip->dev; + dma->device_alloc_chan_resources = dw_edma_alloc_chan_resources; + dma->device_free_chan_resources = dw_edma_free_chan_resources; + dma->device_caps = dw_edma_device_caps; + dma->device_config = dw_edma_device_config; + dma->device_pause = dw_edma_device_pause; + dma->device_resume = dw_edma_device_resume; + dma->device_terminate_all = dw_edma_device_terminate_all; + dma->device_synchronize = dw_edma_device_synchronize; + dma->device_issue_pending = dw_edma_device_issue_pending; + dma->device_tx_status = dw_edma_device_tx_status; + dma->device_prep_config_sg = dw_edma_device_prep_config_sg; + dma->device_prep_dma_cyclic = dw_edma_device_prep_dma_cyclic; + dma->device_prep_interleaved_dma = dw_edma_device_prep_interleaved_dma; + + dma_set_max_seg_size(dma->dev, U32_MAX); + + /* Register DMA device */ + return dma_async_device_register(dma); +} + +static inline void dw_edma_dec_irq_alloc(int *nr_irqs, u32 *alloc, u16 cnt) +{ + if (*nr_irqs && *alloc < cnt) { + (*alloc)++; + (*nr_irqs)--; + } +} + +static int dw_edma_irq_request(struct dw_edma *dw, + u32 *wr_alloc, u32 *rd_alloc) +{ + struct dw_edma_chip *chip = dw->chip; + struct device *dev = dw->chip->dev; + struct msi_desc *msi_desc; + int i, err = 0; + u32 ch_cnt; + int irq; + + ch_cnt = dw->wr_ch_cnt + dw->rd_ch_cnt; + + if (chip->nr_irqs < 1 || !chip->ops->irq_vector) + return -EINVAL; + + dw->irq = devm_kcalloc(dev, chip->nr_irqs, sizeof(*dw->irq), GFP_KERNEL); + if (!dw->irq) + return -ENOMEM; + + if (chip->nr_irqs == 1) { + /* Common IRQ shared among all channels */ + irq = chip->ops->irq_vector(dev, 0); + dw->irq[0].dw = dw; + err = request_irq(irq, dw_edma_interrupt_common, + IRQF_SHARED, dw->name, &dw->irq[0]); + if (err) { + dw->nr_irqs = 0; + return err; + } + + if (irq_get_msi_desc(irq)) + get_cached_msi_msg(irq, &dw->irq[0].msi); + + dw->nr_irqs = 1; + } else { + /* Distribute IRQs equally among all channels */ + int tmp = chip->nr_irqs; + + while (tmp && (*wr_alloc + *rd_alloc) < ch_cnt) { + dw_edma_dec_irq_alloc(&tmp, wr_alloc, dw->wr_ch_cnt); + dw_edma_dec_irq_alloc(&tmp, rd_alloc, dw->rd_ch_cnt); + } + + for (i = 0; i < (*wr_alloc + *rd_alloc); i++) { + irq = chip->ops->irq_vector(dev, i); + dw->irq[i].dw = dw; + err = request_irq(irq, + i < *wr_alloc ? + dw_edma_interrupt_write : + dw_edma_interrupt_read, + IRQF_SHARED, dw->name, + &dw->irq[i]); + if (err) + goto err_irq_free; + msi_desc = irq_get_msi_desc(irq); + if (msi_desc) { + get_cached_msi_msg(irq, &dw->irq[i].msi); + if (!msi_desc->pci.msi_attrib.is_msix) + dw->irq[i].msi.data = dw->irq[0].msi.data + i; + } + } + + dw->nr_irqs = i; + } + + return 0; + +err_irq_free: + for (i--; i >= 0; i--) { + irq = chip->ops->irq_vector(dev, i); + free_irq(irq, &dw->irq[i]); + } + + return err; +} + +static int dw_edma_check_partial(struct dw_edma_chip *chip, + u16 hw_wr_ch_cnt, u16 hw_rd_ch_cnt) +{ + if (!(chip->flags & DW_EDMA_CHIP_PARTIAL)) + return 0; + + if (chip->mf != EDMA_MF_EDMA_UNROLL && + chip->mf != EDMA_MF_HDMA_COMPAT) + return 0; + + /* + * Direction-wide registers are shared by all channels in that + * direction, so a direction must have a single owner. + */ + if ((chip->ll_wr_cnt && chip->ll_wr_cnt != hw_wr_ch_cnt) || + (chip->ll_rd_cnt && chip->ll_rd_cnt != hw_rd_ch_cnt)) + return -EOPNOTSUPP; + + return 0; +} + +int dw_edma_probe(struct dw_edma_chip *chip) +{ + struct device *dev; + struct dw_edma *dw; + u16 hw_wr_ch_cnt; + u16 hw_rd_ch_cnt; + u32 wr_alloc = 0; + u32 rd_alloc = 0; + u16 max_wr_cnt; + u16 max_rd_cnt; + int i, err; + + if (!chip) + return -EINVAL; + + dev = chip->dev; + if (!dev || !chip->ops) + return -EINVAL; + + if (chip->flags & DW_EDMA_CHIP_PARTIAL) { + switch (chip->mf) { + case EDMA_MF_EDMA_UNROLL: + case EDMA_MF_HDMA_COMPAT: + case EDMA_MF_HDMA_NATIVE: + break; + default: + return -EOPNOTSUPP; + } + } + + dw = devm_kzalloc(dev, sizeof(*dw), GFP_KERNEL); + if (!dw) + return -ENOMEM; + + dw->chip = chip; + + if (dw->chip->mf == EDMA_MF_HDMA_NATIVE) { + dw_hdma_v0_core_register(dw); + max_wr_cnt = HDMA_MAX_WR_CH; + max_rd_cnt = HDMA_MAX_RD_CH; + } else { + dw_edma_v0_core_register(dw); + max_wr_cnt = EDMA_MAX_WR_CH; + max_rd_cnt = EDMA_MAX_RD_CH; + } + + raw_spin_lock_init(&dw->lock); + + /* + * chip->ll_*_cnt describes the channels exposed by this instance. Keep + * the usable hardware counts separate for partial ownership checks. + */ + hw_wr_ch_cnt = min(dw_edma_core_ch_count(dw, EDMA_DIR_WRITE), + max_wr_cnt); + hw_rd_ch_cnt = min(dw_edma_core_ch_count(dw, EDMA_DIR_READ), + max_rd_cnt); + + err = dw_edma_check_partial(chip, hw_wr_ch_cnt, hw_rd_ch_cnt); + if (err) + return err; + + dw->wr_ch_cnt = min(chip->ll_wr_cnt, hw_wr_ch_cnt); + dw->rd_ch_cnt = min(chip->ll_rd_cnt, hw_rd_ch_cnt); + + if (!dw->wr_ch_cnt && !dw->rd_ch_cnt) + return -EINVAL; + + dev_vdbg(dev, "Channels:\twrite=%d, read=%d\n", + dw->wr_ch_cnt, dw->rd_ch_cnt); + + /* Allocate channels */ + dw->chan = devm_kcalloc(dev, dw->wr_ch_cnt + dw->rd_ch_cnt, + sizeof(*dw->chan), GFP_KERNEL); + if (!dw->chan) + return -ENOMEM; + + snprintf(dw->name, sizeof(dw->name), "dw-edma-core:%s", + dev_name(chip->dev)); + + if (chip->flags & DW_EDMA_CHIP_PARTIAL) { + /* + * Do not reset the shared controller, but drain stale state + * from resources represented by this instance. + */ + err = dw_edma_core_quiesce(dw); + if (err) + return err; + } else { + /* Disable eDMA only when this instance owns the controller. */ + dw_edma_core_off(dw); + } + + /* + * Deferred IRQ works are queued from the hard IRQ handlers, so the + * workqueue must exist before any IRQ is requested. + */ + dw->wq = alloc_workqueue("dw-edma:%s", WQ_UNBOUND | WQ_HIGHPRI, 0, + dev_name(chip->dev)); + if (!dw->wq) + return -ENOMEM; + + /* Request IRQs */ + err = dw_edma_irq_request(dw, &wr_alloc, &rd_alloc); + if (err) { + destroy_workqueue(dw->wq); + return err; + } + + /* Allocate a dedicated virtual IRQ for interrupt-emulation doorbells */ + err = dw_edma_emul_irq_alloc(dw); + if (err) + dev_warn(dev, "Failed to allocate emulation IRQ: %d\n", err); + + /* Setup write/read channels */ + err = dw_edma_channel_setup(dw, wr_alloc, rd_alloc); + if (err) + goto err_irq_free; + + /* Turn debugfs on */ + dw_edma_core_debugfs_on(dw); + + chip->dw = dw; + + return 0; + +err_irq_free: + for (i = (dw->nr_irqs - 1); i >= 0; i--) + free_irq(chip->ops->irq_vector(dev, i), &dw->irq[i]); + dw_edma_emul_irq_free(dw); + destroy_workqueue(dw->wq); + + return err; +} +EXPORT_SYMBOL_GPL(dw_edma_probe); + +int dw_edma_remove(struct dw_edma_chip *chip) +{ + struct dw_edma_chan *chan, *_chan; + struct device *dev = chip->dev; + struct dw_edma *dw = chip->dw; + int i, err = 0; + + /* Skip removal if no private data found */ + if (!dw) + return -ENODEV; + + if (chip->flags & DW_EDMA_CHIP_PARTIAL) + err = dw_edma_core_quiesce(dw); + else + dw_edma_core_off(dw); + + /* Free irqs */ + for (i = (dw->nr_irqs - 1); i >= 0; i--) + free_irq(chip->ops->irq_vector(dev, i), &dw->irq[i]); + dw_edma_emul_irq_free(dw); + + for (i = 0; i < dw->wr_ch_cnt + dw->rd_ch_cnt; i++) + cancel_work_sync(&dw->chan[i].irq_work); + + destroy_workqueue(dw->wq); + + /* Deregister eDMA device */ + dma_async_device_unregister(&dw->dma); + list_for_each_entry_safe(chan, _chan, &dw->dma.channels, + vc.chan.device_node) { + tasklet_kill(&chan->vc.task); + list_del(&chan->vc.chan.device_node); + } + + return err; +} +EXPORT_SYMBOL_GPL(dw_edma_remove); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Synopsys DesignWare eDMA controller core driver"); +MODULE_AUTHOR("Gustavo Pimentel <gustavo.pimentel@synopsys.com>"); diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h new file mode 100644 index 000000000..f6a5ad317 --- /dev/null +++ b/drivers/dma/dw-edma/dw-edma-core.h @@ -0,0 +1,290 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates. + * Synopsys DesignWare eDMA core driver + * + * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com> + */ + +#ifndef _DW_EDMA_CORE_H +#define _DW_EDMA_CORE_H + +#include <linux/atomic.h> +#include <linux/msi.h> +#include <linux/dma/edma.h> +#include <linux/workqueue.h> + +#include "../virt-dma.h" + +#define EDMA_LL_SZ 24 + +enum dw_edma_dir { + EDMA_DIR_WRITE = 0, + EDMA_DIR_READ +}; + +enum dw_edma_request { + EDMA_REQ_NONE = 0, + EDMA_REQ_STOP, + EDMA_REQ_PAUSE +}; + +enum dw_edma_status { + EDMA_ST_IDLE = 0, + EDMA_ST_PAUSE, + EDMA_ST_BUSY +}; + +enum dw_edma_xfer_type { + EDMA_XFER_SCATTER_GATHER = 0, + EDMA_XFER_CYCLIC, + EDMA_XFER_INTERLEAVED +}; + +struct dw_edma_chan; +struct dw_edma_chunk; + +struct dw_edma_burst { + u64 sar; + u64 dar; + u32 sz; + /* precalulate summary of previous burst total size */ + u32 xfer_sz; +}; + +struct dw_edma_desc { + struct virt_dma_desc vd; + struct dw_edma_chan *chan; + + u32 alloc_sz; + + size_t done_burst; + size_t start_burst; + u8 cb; + size_t nburst; + struct dw_edma_burst burst[] __counted_by(nburst); +}; + +struct dw_edma_chan { + struct virt_dma_chan vc; + struct dw_edma *dw; + int id; + enum dw_edma_dir dir; + u8 func_no; + + u32 ll_max; + struct dw_edma_region ll_region; /* Linked list */ + + struct msi_msg msi; + + enum dw_edma_ch_irq_mode irq_mode; + + enum dw_edma_request request; + enum dw_edma_status status; + u8 configured; + + struct dma_slave_config config; + bool non_ll; + + struct work_struct irq_work; + atomic_t irq_pending; +}; + +struct dw_edma_irq { + struct msi_msg msi; + struct dw_edma *dw; + + DECLARE_BITMAP(wr_mask, HDMA_MAX_WR_CH); + DECLARE_BITMAP(rd_mask, HDMA_MAX_RD_CH); +}; + +struct dw_edma { + char name[32]; + + struct dma_device dma; + + u16 wr_ch_cnt; + u16 rd_ch_cnt; + + struct dw_edma_irq *irq; + int nr_irqs; + + struct dw_edma_chan *chan; + + /* + * WQ_HIGHPRI keeps completion processing responsive under heavy load; + * WQ_UNBOUND lets different channels run on different CPUs. + */ + struct workqueue_struct *wq; + + raw_spinlock_t lock; /* Protect v0 shared registers */ + + struct dw_edma_chip *chip; + + const struct dw_edma_core_ops *core; +}; + +typedef void (*dw_edma_handler_t)(struct dw_edma_chan *); + +struct dw_edma_core_ops { + void (*off)(struct dw_edma *dw); + int (*quiesce)(struct dw_edma *dw); + int (*ch_quiesce)(struct dw_edma_chan *chan); + u16 (*ch_count)(struct dw_edma *dw, enum dw_edma_dir dir); + enum dma_status (*ch_status)(struct dw_edma_chan *chan); + irqreturn_t (*handle_int)(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir, + dw_edma_handler_t done, dw_edma_handler_t abort); + void (*non_ll_start)(struct dw_edma_chan *chan, struct dw_edma_burst *child); + void (*ll_data)(struct dw_edma_chan *chan, struct dw_edma_burst *burst, + u32 idx, bool cb, bool irq); + void (*ll_link)(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr); + void (*ch_doorbell)(struct dw_edma_chan *chan); + void (*ch_enable)(struct dw_edma_chan *chan); + void (*ch_config)(struct dw_edma_chan *chan); + void (*debugfs_on)(struct dw_edma *dw); + void (*ack_emulated_irq)(struct dw_edma *dw); + resource_size_t (*db_offset)(struct dw_edma *dw); +}; + +struct dw_edma_sg { + struct scatterlist *sgl; + unsigned int len; +}; + +struct dw_edma_cyclic { + dma_addr_t paddr; + size_t len; + size_t cnt; +}; + +struct dw_edma_transfer { + struct dma_chan *dchan; + union dw_edma_xfer { + struct dw_edma_sg sg; + struct dw_edma_cyclic cyclic; + struct dma_interleaved_template *il; + } xfer; + enum dma_transfer_direction direction; + unsigned long flags; + enum dw_edma_xfer_type type; +}; + +static inline +struct dw_edma_chan *vc2dw_edma_chan(struct virt_dma_chan *vc) +{ + return container_of(vc, struct dw_edma_chan, vc); +} + +static inline +struct dw_edma_chan *dchan2dw_edma_chan(struct dma_chan *dchan) +{ + return vc2dw_edma_chan(to_virt_chan(dchan)); +} + +static inline u64 dw_edma_core_get_ll_paddr(struct dw_edma_chan *chan) +{ + if (chan->dir == EDMA_DIR_WRITE) + return chan->dw->chip->ll_region_wr[chan->id].paddr; + + return chan->dw->chip->ll_region_rd[chan->id].paddr; +} + +static inline +void dw_edma_core_off(struct dw_edma *dw) +{ + dw->core->off(dw); +} + +static inline +int dw_edma_core_quiesce(struct dw_edma *dw) +{ + return dw->core->quiesce(dw); +} + +static inline +int dw_edma_core_ch_quiesce(struct dw_edma_chan *chan) +{ + return chan->dw->core->ch_quiesce(chan); +} + +static inline +u16 dw_edma_core_ch_count(struct dw_edma *dw, enum dw_edma_dir dir) +{ + return dw->core->ch_count(dw, dir); +} + +static inline +enum dma_status dw_edma_core_ch_status(struct dw_edma_chan *chan) +{ + return chan->dw->core->ch_status(chan); +} + +static inline irqreturn_t +dw_edma_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir, + dw_edma_handler_t done, dw_edma_handler_t abort) +{ + return dw_irq->dw->core->handle_int(dw_irq, dir, done, abort); +} + +static inline +void dw_edma_core_ch_config(struct dw_edma_chan *chan) +{ + chan->dw->core->ch_config(chan); +} + +static inline void +dw_edma_core_ll_data(struct dw_edma_chan *chan, struct dw_edma_burst *burst, + u32 idx, bool cb, bool irq) +{ + chan->dw->core->ll_data(chan, burst, idx, cb, irq); +} + +static inline void +dw_edma_core_ll_link(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr) +{ + chan->dw->core->ll_link(chan, idx, cb, addr); +} + +static inline void dw_edma_core_ch_doorbell(struct dw_edma_chan *chan) +{ + chan->dw->core->ch_doorbell(chan); +} + +static inline void dw_edma_core_ch_enable(struct dw_edma_chan *chan) +{ + chan->dw->core->ch_enable(chan); +} + +static inline +void dw_edma_core_debugfs_on(struct dw_edma *dw) +{ + dw->core->debugfs_on(dw); +} + +static inline int dw_edma_core_ack_emulated_irq(struct dw_edma *dw) +{ + if (!dw->core->ack_emulated_irq) + return -EOPNOTSUPP; + + dw->core->ack_emulated_irq(dw); + return 0; +} + +static inline resource_size_t +dw_edma_core_db_offset(struct dw_edma *dw) +{ + return dw->core->db_offset(dw); +} + +static inline bool +dw_edma_core_ch_ignore_irq(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + + if (dw->chip->flags & DW_EDMA_CHIP_LOCAL) + return chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE; + else + return chan->irq_mode == DW_EDMA_CH_IRQ_LOCAL; +} + +#endif /* _DW_EDMA_CORE_H */ diff --git a/drivers/dma/dw-edma/dw-edma-pcie.c b/drivers/dma/dw-edma/dw-edma-pcie.c new file mode 100644 index 000000000..9f237ba91 --- /dev/null +++ b/drivers/dma/dw-edma/dw-edma-pcie.c @@ -0,0 +1,668 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates. + * Synopsys DesignWare eDMA PCIe driver + * + * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/device.h> +#include <linux/dma/edma.h> +#include <linux/pci-epf.h> +#include <linux/msi.h> +#include <linux/bitfield.h> +#include <linux/sizes.h> + +#include "dw-edma-core.h" + +/* Synopsys */ +#define DW_PCIE_SYNOPSYS_VSEC_DMA_ID 0x6 +#define DW_PCIE_SYNOPSYS_VSEC_DMA_BAR GENMASK(10, 8) +#define DW_PCIE_SYNOPSYS_VSEC_DMA_MAP GENMASK(2, 0) +#define DW_PCIE_SYNOPSYS_VSEC_DMA_WR_CH GENMASK(9, 0) +#define DW_PCIE_SYNOPSYS_VSEC_DMA_RD_CH GENMASK(25, 16) + +/* AMD MDB (Xilinx) specific defines */ +#define PCI_DEVICE_ID_XILINX_B054 0xb054 +#define PCI_DEVICE_ID_XILINX_B00F 0xb00f + +#define DW_PCIE_XILINX_MDB_VSEC_DMA_ID 0x6 +#define DW_PCIE_XILINX_MDB_VSEC_ID 0x20 +#define DW_PCIE_XILINX_MDB_VSEC_DMA_BAR GENMASK(10, 8) +#define DW_PCIE_XILINX_MDB_VSEC_DMA_MAP GENMASK(2, 0) +#define DW_PCIE_XILINX_MDB_VSEC_DMA_WR_CH GENMASK(9, 0) +#define DW_PCIE_XILINX_MDB_VSEC_DMA_RD_CH GENMASK(25, 16) + +#define DW_PCIE_XILINX_MDB_DEVMEM_OFF_REG_HIGH 0xc +#define DW_PCIE_XILINX_MDB_DEVMEM_OFF_REG_LOW 0x8 +#define DW_PCIE_XILINX_MDB_INVALID_ADDR (~0ULL) + +#define DW_PCIE_XILINX_MDB_LL_OFF_GAP 0x200000 +#define DW_PCIE_XILINX_MDB_LL_SIZE 0x800 +#define DW_PCIE_XILINX_MDB_DT_OFF_GAP 0x100000 +#define DW_PCIE_XILINX_MDB_DT_SIZE 0x800 + +#define DW_BLOCK(a, b, c) \ + { \ + .bar = a, \ + .off = b, \ + .sz = c, \ + }, + +struct dw_edma_block { + enum pci_barno bar; + off_t off; + u64 paddr; + bool paddr_valid; + size_t sz; +}; + +struct dw_edma_pcie_data { + /* eDMA registers location */ + struct dw_edma_block rg; + /* eDMA memory linked list location */ + struct dw_edma_block ll_wr[HDMA_MAX_WR_CH]; + struct dw_edma_block ll_rd[HDMA_MAX_RD_CH]; + /* eDMA memory data location */ + struct dw_edma_block dt_wr[HDMA_MAX_WR_CH]; + struct dw_edma_block dt_rd[HDMA_MAX_RD_CH]; + /* Other */ + enum dw_edma_map_format mf; + u8 irqs; + u16 wr_ch_cnt; + u16 rd_ch_cnt; + u64 devmem_phys_off; + bool cfg_non_ll; +}; + +struct dw_edma_pcie_match_data { + const struct dw_edma_pcie_data *data; + const struct dw_edma_plat_ops *plat_ops; + /* + * Mandatory callback. It may leave @pdata unchanged when the static + * template already describes the device. + */ + int (*parse_caps)(struct pci_dev *pdev, + struct dw_edma_pcie_data *pdata); + unsigned long flags; + u32 chip_flags; +}; + +#define DW_EDMA_PCIE_F_DEVMEM_PHYS_OFF BIT(0) +#define DW_EDMA_PCIE_F_REG_OFFSET BIT(1) + +static const struct dw_edma_pcie_data snps_edda_data = { + /* eDMA registers location */ + .rg.bar = BAR_0, + .rg.off = 0x00001000, /* 4 Kbytes */ + .rg.sz = 0x00002000, /* 8 Kbytes */ + /* eDMA memory linked list location */ + .ll_wr = { + /* Channel 0 - BAR 2, offset 0 Mbytes, size 2 Kbytes */ + DW_BLOCK(BAR_2, 0x00000000, 0x00000800) + /* Channel 1 - BAR 2, offset 2 Mbytes, size 2 Kbytes */ + DW_BLOCK(BAR_2, 0x00200000, 0x00000800) + }, + .ll_rd = { + /* Channel 0 - BAR 2, offset 4 Mbytes, size 2 Kbytes */ + DW_BLOCK(BAR_2, 0x00400000, 0x00000800) + /* Channel 1 - BAR 2, offset 6 Mbytes, size 2 Kbytes */ + DW_BLOCK(BAR_2, 0x00600000, 0x00000800) + }, + /* eDMA memory data location */ + .dt_wr = { + /* Channel 0 - BAR 2, offset 8 Mbytes, size 2 Kbytes */ + DW_BLOCK(BAR_2, 0x00800000, 0x00000800) + /* Channel 1 - BAR 2, offset 9 Mbytes, size 2 Kbytes */ + DW_BLOCK(BAR_2, 0x00900000, 0x00000800) + }, + .dt_rd = { + /* Channel 0 - BAR 2, offset 10 Mbytes, size 2 Kbytes */ + DW_BLOCK(BAR_2, 0x00a00000, 0x00000800) + /* Channel 1 - BAR 2, offset 11 Mbytes, size 2 Kbytes */ + DW_BLOCK(BAR_2, 0x00b00000, 0x00000800) + }, + /* Other */ + .mf = EDMA_MF_EDMA_UNROLL, + .irqs = 1, + .wr_ch_cnt = 2, + .rd_ch_cnt = 2, +}; + +static const struct dw_edma_pcie_data xilinx_mdb_data = { + /* MDB registers location */ + .rg.bar = BAR_0, + .rg.off = SZ_4K, /* 4 Kbytes */ + .rg.sz = SZ_8K, /* 8 Kbytes */ + + /* Other */ + .mf = EDMA_MF_HDMA_NATIVE, + .irqs = 1, + .wr_ch_cnt = 8, + .rd_ch_cnt = 8, +}; + +static const struct dw_edma_pcie_data xilinx_cpm6_dma_data = { + /* MDB registers location */ + .rg.bar = BAR_0, + .rg.off = SZ_4K, /* 4 Kbytes */ + .rg.sz = SZ_8K, /* 8 Kbytes */ + + /* Other */ + .mf = EDMA_MF_HDMA_NATIVE, + .irqs = 1, + .wr_ch_cnt = 8, + .rd_ch_cnt = 8, +}; + +static void dw_edma_set_chan_region_offset(struct dw_edma_pcie_data *pdata, + enum pci_barno bar, off_t start_off, + off_t ll_off_gap, size_t ll_size, + off_t dt_off_gap, size_t dt_size) +{ + u16 wr_ch = pdata->wr_ch_cnt; + u16 rd_ch = pdata->rd_ch_cnt; + off_t off; + u16 i; + + off = start_off; + + /* Write channel LL region */ + for (i = 0; i < wr_ch; i++) { + pdata->ll_wr[i].bar = bar; + pdata->ll_wr[i].off = off; + pdata->ll_wr[i].sz = ll_size; + off += ll_off_gap; + } + + /* Read channel LL region */ + for (i = 0; i < rd_ch; i++) { + pdata->ll_rd[i].bar = bar; + pdata->ll_rd[i].off = off; + pdata->ll_rd[i].sz = ll_size; + off += ll_off_gap; + } + + /* Write channel data region */ + for (i = 0; i < wr_ch; i++) { + pdata->dt_wr[i].bar = bar; + pdata->dt_wr[i].off = off; + pdata->dt_wr[i].sz = dt_size; + off += dt_off_gap; + } + + /* Read channel data region */ + for (i = 0; i < rd_ch; i++) { + pdata->dt_rd[i].bar = bar; + pdata->dt_rd[i].off = off; + pdata->dt_rd[i].sz = dt_size; + off += dt_off_gap; + } +} + +static int dw_edma_pcie_irq_vector(struct device *dev, unsigned int nr) +{ + return pci_irq_vector(to_pci_dev(dev), nr); +} + +static u64 dw_edma_pcie_address(struct device *dev, phys_addr_t cpu_addr) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct pci_bus_region region; + struct resource res = { + .flags = IORESOURCE_MEM, + .start = cpu_addr, + .end = cpu_addr, + }; + + pcibios_resource_to_bus(pdev->bus, ®ion, &res); + return region.start; +} + +static const struct dw_edma_plat_ops dw_edma_pcie_plat_ops = { + .irq_vector = dw_edma_pcie_irq_vector, + .pci_address = dw_edma_pcie_address, +}; + +static void dw_edma_pcie_get_synopsys_dma_data(struct pci_dev *pdev, + struct dw_edma_pcie_data *pdata) +{ + u32 val, map; + u16 vsec; + u64 off; + + vsec = pci_find_vsec_capability(pdev, PCI_VENDOR_ID_SYNOPSYS, + DW_PCIE_SYNOPSYS_VSEC_DMA_ID); + if (!vsec) + return; + + pci_read_config_dword(pdev, vsec + PCI_VNDR_HEADER, &val); + if (PCI_VNDR_HEADER_REV(val) != 0x00 || + PCI_VNDR_HEADER_LEN(val) != 0x18) + return; + + pci_dbg(pdev, "Detected Synopsys PCIe Vendor-Specific Extended Capability DMA\n"); + pci_read_config_dword(pdev, vsec + 0x8, &val); + map = FIELD_GET(DW_PCIE_SYNOPSYS_VSEC_DMA_MAP, val); + if (map != EDMA_MF_EDMA_LEGACY && + map != EDMA_MF_EDMA_UNROLL && + map != EDMA_MF_HDMA_COMPAT && + map != EDMA_MF_HDMA_NATIVE) + return; + + pdata->mf = map; + pdata->rg.bar = FIELD_GET(DW_PCIE_SYNOPSYS_VSEC_DMA_BAR, val); + + pci_read_config_dword(pdev, vsec + 0xc, &val); + pdata->wr_ch_cnt = min_t(u16, pdata->wr_ch_cnt, + FIELD_GET(DW_PCIE_SYNOPSYS_VSEC_DMA_WR_CH, val)); + pdata->rd_ch_cnt = min_t(u16, pdata->rd_ch_cnt, + FIELD_GET(DW_PCIE_SYNOPSYS_VSEC_DMA_RD_CH, val)); + + pci_read_config_dword(pdev, vsec + 0x14, &val); + off = val; + pci_read_config_dword(pdev, vsec + 0x10, &val); + off <<= 32; + off |= val; + pdata->rg.off = off; +} + +static void dw_edma_pcie_get_xilinx_dma_data(struct pci_dev *pdev, + struct dw_edma_pcie_data *pdata) +{ + u32 val, map; + u16 vsec; + u64 off; + + pdata->devmem_phys_off = DW_PCIE_XILINX_MDB_INVALID_ADDR; + + vsec = pci_find_vsec_capability(pdev, PCI_VENDOR_ID_XILINX, + DW_PCIE_XILINX_MDB_VSEC_DMA_ID); + if (!vsec) + return; + + pci_read_config_dword(pdev, vsec + PCI_VNDR_HEADER, &val); + if (PCI_VNDR_HEADER_REV(val) != 0x00 || + PCI_VNDR_HEADER_LEN(val) != 0x18) + return; + + pci_dbg(pdev, "Detected Xilinx PCIe Vendor-Specific Extended Capability DMA\n"); + pci_read_config_dword(pdev, vsec + 0x8, &val); + map = FIELD_GET(DW_PCIE_XILINX_MDB_VSEC_DMA_MAP, val); + if (map != EDMA_MF_HDMA_NATIVE) + return; + + pdata->mf = map; + pdata->rg.bar = FIELD_GET(DW_PCIE_XILINX_MDB_VSEC_DMA_BAR, val); + + pci_read_config_dword(pdev, vsec + 0xc, &val); + pdata->wr_ch_cnt = min(pdata->wr_ch_cnt, + FIELD_GET(DW_PCIE_XILINX_MDB_VSEC_DMA_WR_CH, val)); + pdata->rd_ch_cnt = min(pdata->rd_ch_cnt, + FIELD_GET(DW_PCIE_XILINX_MDB_VSEC_DMA_RD_CH, val)); + + pci_read_config_dword(pdev, vsec + 0x14, &val); + off = val; + pci_read_config_dword(pdev, vsec + 0x10, &val); + off <<= 32; + off |= val; + pdata->rg.off = off; + + vsec = pci_find_vsec_capability(pdev, PCI_VENDOR_ID_XILINX, + DW_PCIE_XILINX_MDB_VSEC_ID); + if (!vsec) + return; + + pci_read_config_dword(pdev, + vsec + DW_PCIE_XILINX_MDB_DEVMEM_OFF_REG_HIGH, + &val); + off = val; + pci_read_config_dword(pdev, + vsec + DW_PCIE_XILINX_MDB_DEVMEM_OFF_REG_LOW, + &val); + off <<= 32; + off |= val; + pdata->devmem_phys_off = off; +} + +static int +dw_edma_pcie_parse_synopsys_caps(struct pci_dev *pdev, + struct dw_edma_pcie_data *pdata) +{ + dw_edma_pcie_get_synopsys_dma_data(pdev, pdata); + + return 0; +} + +static int +dw_edma_pcie_parse_xilinx_caps(struct pci_dev *pdev, + struct dw_edma_pcie_data *pdata) +{ + dw_edma_pcie_get_xilinx_dma_data(pdev, pdata); + + /* + * There is no valid address found for the LL memory space on the + * device side. In the absence of LL base address use the non-LL mode or + * simple mode supported by the HDMA IP. + */ + if (pdata->devmem_phys_off == DW_PCIE_XILINX_MDB_INVALID_ADDR) { + pdata->cfg_non_ll = true; + return 0; + } + + /* + * Configure the channel LL and data blocks if number of channels + * enabled in VSEC capability are more than the channels configured in + * xilinx_mdb_data. + */ + dw_edma_set_chan_region_offset(pdata, BAR_2, 0, + DW_PCIE_XILINX_MDB_LL_OFF_GAP, + DW_PCIE_XILINX_MDB_LL_SIZE, + DW_PCIE_XILINX_MDB_DT_OFF_GAP, + DW_PCIE_XILINX_MDB_DT_SIZE); + + return 0; +} + +static u64 dw_edma_get_phys_addr(struct pci_dev *pdev, + const struct dw_edma_pcie_match_data *match, + struct dw_edma_pcie_data *pdata, + enum pci_barno bar) +{ + if (match->flags & DW_EDMA_PCIE_F_DEVMEM_PHYS_OFF) + return pdata->devmem_phys_off; + + return pci_bus_address(pdev, bar); +} + +static u64 dw_edma_get_block_addr(struct pci_dev *pdev, + const struct dw_edma_pcie_match_data *match, + struct dw_edma_pcie_data *pdata, + const struct dw_edma_block *block) +{ + if (block->paddr_valid) + return block->paddr; + + return dw_edma_get_phys_addr(pdev, match, pdata, block->bar) + + block->off; +} + +static int dw_edma_pcie_probe(struct pci_dev *pdev, + const struct pci_device_id *pid) +{ + const struct dw_edma_pcie_match_data *match = (void *)pid->driver_data; + const struct dw_edma_pcie_data *pdata; + struct device *dev = &pdev->dev; + struct dw_edma_chip *chip; + int err, nr_irqs; + int i, mask; + + if (!match) + return -ENODEV; + pdata = match->data; + + if (!pdata) + return -ENODEV; + + struct dw_edma_pcie_data *dma_data __free(kfree) = + kmemdup(pdata, sizeof(*dma_data), GFP_KERNEL); + if (!dma_data) + return -ENOMEM; + + /* Enable PCI device */ + err = pcim_enable_device(pdev); + if (err) { + pci_err(pdev, "enabling device failed\n"); + return err; + } + + /* Let device-specific discovery override the static template data. */ + if (!match->parse_caps || !match->plat_ops) + return -EINVAL; + + err = match->parse_caps(pdev, dma_data); + if (err) + return err; + + /* Mapping PCI BAR regions */ + mask = BIT(dma_data->rg.bar); + for (i = 0; i < dma_data->wr_ch_cnt; i++) { + mask |= BIT(dma_data->ll_wr[i].bar); + if (dma_data->dt_wr[i].sz) + mask |= BIT(dma_data->dt_wr[i].bar); + } + for (i = 0; i < dma_data->rd_ch_cnt; i++) { + mask |= BIT(dma_data->ll_rd[i].bar); + if (dma_data->dt_rd[i].sz) + mask |= BIT(dma_data->dt_rd[i].bar); + } + err = pcim_iomap_regions(pdev, mask, pci_name(pdev)); + if (err) { + pci_err(pdev, "eDMA BAR I/O remapping failed\n"); + return err; + } + + pci_set_master(pdev); + + /* DMA configuration */ + err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (err) { + pci_err(pdev, "DMA mask 64 set failed\n"); + return err; + } + + /* Data structure allocation */ + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + /* IRQs allocation */ + nr_irqs = pci_alloc_irq_vectors(pdev, 1, dma_data->irqs, + PCI_IRQ_MSI | PCI_IRQ_MSIX); + if (nr_irqs < 1) { + pci_err(pdev, "fail to alloc IRQ vector (number of IRQs=%u)\n", + nr_irqs); + return -EPERM; + } + + /* Data structure initialization */ + chip->dev = dev; + + chip->mf = dma_data->mf; + chip->flags = match->chip_flags; + chip->func_no = PCI_FUNC(pdev->devfn); + chip->nr_irqs = nr_irqs; + chip->ops = match->plat_ops; + chip->cfg_non_ll = dma_data->cfg_non_ll; + + chip->ll_wr_cnt = dma_data->wr_ch_cnt; + chip->ll_rd_cnt = dma_data->rd_ch_cnt; + + chip->reg_base = pcim_iomap_table(pdev)[dma_data->rg.bar]; + if (!chip->reg_base) + return -ENOMEM; + if (match->flags & DW_EDMA_PCIE_F_REG_OFFSET) + chip->reg_base += dma_data->rg.off; + + for (i = 0; i < chip->ll_wr_cnt && !dma_data->cfg_non_ll; i++) { + struct dw_edma_region *ll_region = &chip->ll_region_wr[i]; + struct dw_edma_region *dt_region = &chip->dt_region_wr[i]; + struct dw_edma_block *ll_block = &dma_data->ll_wr[i]; + struct dw_edma_block *dt_block = &dma_data->dt_wr[i]; + + ll_region->vaddr.io = pcim_iomap_table(pdev)[ll_block->bar]; + if (!ll_region->vaddr.io) + return -ENOMEM; + + ll_region->vaddr.io += ll_block->off; + ll_region->paddr = dw_edma_get_block_addr(pdev, match, dma_data, + ll_block); + ll_region->sz = ll_block->sz; + + if (!dt_block->sz) + continue; + + dt_region->vaddr.io = pcim_iomap_table(pdev)[dt_block->bar]; + if (!dt_region->vaddr.io) + return -ENOMEM; + + dt_region->vaddr.io += dt_block->off; + dt_region->paddr = dw_edma_get_block_addr(pdev, match, dma_data, + dt_block); + dt_region->sz = dt_block->sz; + } + + for (i = 0; i < chip->ll_rd_cnt && !dma_data->cfg_non_ll; i++) { + struct dw_edma_region *ll_region = &chip->ll_region_rd[i]; + struct dw_edma_region *dt_region = &chip->dt_region_rd[i]; + struct dw_edma_block *ll_block = &dma_data->ll_rd[i]; + struct dw_edma_block *dt_block = &dma_data->dt_rd[i]; + + ll_region->vaddr.io = pcim_iomap_table(pdev)[ll_block->bar]; + if (!ll_region->vaddr.io) + return -ENOMEM; + + ll_region->vaddr.io += ll_block->off; + ll_region->paddr = dw_edma_get_block_addr(pdev, match, dma_data, + ll_block); + ll_region->sz = ll_block->sz; + + if (!dt_block->sz) + continue; + + dt_region->vaddr.io = pcim_iomap_table(pdev)[dt_block->bar]; + if (!dt_region->vaddr.io) + return -ENOMEM; + + dt_region->vaddr.io += dt_block->off; + dt_region->paddr = dw_edma_get_block_addr(pdev, match, dma_data, + dt_block); + dt_region->sz = dt_block->sz; + } + + /* Debug info */ + if (chip->mf == EDMA_MF_EDMA_LEGACY) + pci_dbg(pdev, "Version:\teDMA Port Logic (0x%x)\n", chip->mf); + else if (chip->mf == EDMA_MF_EDMA_UNROLL) + pci_dbg(pdev, "Version:\teDMA Unroll (0x%x)\n", chip->mf); + else if (chip->mf == EDMA_MF_HDMA_COMPAT) + pci_dbg(pdev, "Version:\tHDMA Compatible (0x%x)\n", chip->mf); + else if (chip->mf == EDMA_MF_HDMA_NATIVE) + pci_dbg(pdev, "Version:\tHDMA Native (0x%x)\n", chip->mf); + else + pci_dbg(pdev, "Version:\tUnknown (0x%x)\n", chip->mf); + + pci_dbg(pdev, "Registers:\tBAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p)\n", + dma_data->rg.bar, dma_data->rg.off, dma_data->rg.sz, + chip->reg_base); + + + for (i = 0; i < chip->ll_wr_cnt; i++) { + pci_dbg(pdev, "L. List:\tWRITE CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n", + i, dma_data->ll_wr[i].bar, + dma_data->ll_wr[i].off, chip->ll_region_wr[i].sz, + chip->ll_region_wr[i].vaddr.io, &chip->ll_region_wr[i].paddr); + + if (!dma_data->dt_wr[i].sz) + continue; + + pci_dbg(pdev, "Data:\tWRITE CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n", + i, dma_data->dt_wr[i].bar, + dma_data->dt_wr[i].off, chip->dt_region_wr[i].sz, + chip->dt_region_wr[i].vaddr.io, + &chip->dt_region_wr[i].paddr); + } + + for (i = 0; i < chip->ll_rd_cnt; i++) { + pci_dbg(pdev, "L. List:\tREAD CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n", + i, dma_data->ll_rd[i].bar, + dma_data->ll_rd[i].off, chip->ll_region_rd[i].sz, + chip->ll_region_rd[i].vaddr.io, &chip->ll_region_rd[i].paddr); + + if (!dma_data->dt_rd[i].sz) + continue; + + pci_dbg(pdev, "Data:\tREAD CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n", + i, dma_data->dt_rd[i].bar, + dma_data->dt_rd[i].off, chip->dt_region_rd[i].sz, + chip->dt_region_rd[i].vaddr.io, + &chip->dt_region_rd[i].paddr); + } + + pci_dbg(pdev, "Nr. IRQs:\t%u\n", chip->nr_irqs); + + /* Validating if PCI interrupts were enabled */ + if (!pci_dev_msi_enabled(pdev)) { + pci_err(pdev, "enable interrupt failed\n"); + return -EPERM; + } + + /* Starting eDMA driver */ + err = dw_edma_probe(chip); + if (err) { + pci_err(pdev, "eDMA probe failed\n"); + return err; + } + + /* Saving data structure reference */ + pci_set_drvdata(pdev, chip); + + return 0; +} + +static void dw_edma_pcie_remove(struct pci_dev *pdev) +{ + struct dw_edma_chip *chip = pci_get_drvdata(pdev); + int err; + + /* Stopping eDMA driver */ + err = dw_edma_remove(chip); + if (err) + pci_warn(pdev, "can't remove device properly: %d\n", err); +} + +static const struct dw_edma_pcie_match_data snps_edda_match_data = { + .data = &snps_edda_data, + .plat_ops = &dw_edma_pcie_plat_ops, + .parse_caps = dw_edma_pcie_parse_synopsys_caps, +}; + +static const struct dw_edma_pcie_match_data xilinx_mdb_match_data = { + .data = &xilinx_mdb_data, + .plat_ops = &dw_edma_pcie_plat_ops, + .parse_caps = dw_edma_pcie_parse_xilinx_caps, + .flags = DW_EDMA_PCIE_F_DEVMEM_PHYS_OFF, +}; + +static const struct dw_edma_pcie_match_data xilinx_cpm6_dma_match_data = { + .data = &xilinx_cpm6_dma_data, + .plat_ops = &dw_edma_pcie_plat_ops, + .parse_caps = dw_edma_pcie_parse_xilinx_caps, + .flags = DW_EDMA_PCIE_F_DEVMEM_PHYS_OFF, +}; + +static const struct pci_device_id dw_edma_pcie_id_table[] = { + { PCI_DEVICE_DATA(SYNOPSYS, EDDA, &snps_edda_match_data) }, + { PCI_VDEVICE(XILINX, PCI_DEVICE_ID_XILINX_B054), + .driver_data = (kernel_ulong_t)&xilinx_mdb_match_data }, + { PCI_VDEVICE(XILINX, PCI_DEVICE_ID_XILINX_B00F), + .driver_data = (kernel_ulong_t)&xilinx_cpm6_dma_match_data }, + { } +}; +MODULE_DEVICE_TABLE(pci, dw_edma_pcie_id_table); + +static struct pci_driver dw_edma_pcie_driver = { + .name = "dw-edma-pcie", + .id_table = dw_edma_pcie_id_table, + .probe = dw_edma_pcie_probe, + .remove = dw_edma_pcie_remove, +}; + +module_pci_driver(dw_edma_pcie_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Synopsys DesignWare eDMA PCIe driver"); +MODULE_AUTHOR("Gustavo Pimentel <gustavo.pimentel@synopsys.com>"); diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c new file mode 100644 index 000000000..abc1bd4c0 --- /dev/null +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c @@ -0,0 +1,664 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates. + * Synopsys DesignWare eDMA v0 core + * + * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com> + */ + +#include <linux/bitfield.h> +#include <linux/iopoll.h> +#include <linux/irqreturn.h> +#include <linux/io-64-nonatomic-lo-hi.h> + +#include "dw-edma-core.h" +#include "dw-edma-v0-core.h" +#include "dw-edma-v0-regs.h" +#include "dw-edma-v0-debugfs.h" + +enum dw_edma_control { + DW_EDMA_V0_CB = BIT(0), + DW_EDMA_V0_TCB = BIT(1), + DW_EDMA_V0_LLP = BIT(2), + DW_EDMA_V0_LIE = BIT(3), + DW_EDMA_V0_RIE = BIT(4), + DW_EDMA_V0_CCS = BIT(8), + DW_EDMA_V0_LLE = BIT(9), +}; + +#define EDMA_V0_FUNC_NUM_MASK GENMASK(16, 12) + +static inline struct dw_edma_v0_regs __iomem *__dw_regs(struct dw_edma *dw) +{ + return dw->chip->reg_base; +} + +#define SET_32(dw, name, value) \ + writel(value, &(__dw_regs(dw)->name)) + +#define GET_32(dw, name) \ + readl(&(__dw_regs(dw)->name)) + +#define SET_RW_32(dw, dir, name, value) \ + do { \ + if ((dir) == EDMA_DIR_WRITE) \ + SET_32(dw, wr_##name, value); \ + else \ + SET_32(dw, rd_##name, value); \ + } while (0) + +#define GET_RW_32(dw, dir, name) \ + ((dir) == EDMA_DIR_WRITE \ + ? GET_32(dw, wr_##name) \ + : GET_32(dw, rd_##name)) + +#define SET_BOTH_32(dw, name, value) \ + do { \ + SET_32(dw, wr_##name, value); \ + SET_32(dw, rd_##name, value); \ + } while (0) + +#define SET_64(dw, name, value) \ + writeq(value, &(__dw_regs(dw)->name)) + +#define GET_64(dw, name) \ + readq(&(__dw_regs(dw)->name)) + +#define SET_RW_64(dw, dir, name, value) \ + do { \ + if ((dir) == EDMA_DIR_WRITE) \ + SET_64(dw, wr_##name, value); \ + else \ + SET_64(dw, rd_##name, value); \ + } while (0) + +#define GET_RW_64(dw, dir, name) \ + ((dir) == EDMA_DIR_WRITE \ + ? GET_64(dw, wr_##name) \ + : GET_64(dw, rd_##name)) + +#define SET_BOTH_64(dw, name, value) \ + do { \ + SET_64(dw, wr_##name, value); \ + SET_64(dw, rd_##name, value); \ + } while (0) + +#define SET_COMPAT(dw, name, value) \ + writel(value, &(__dw_regs(dw)->type.unroll.name)) + +#define SET_RW_COMPAT(dw, dir, name, value) \ + do { \ + if ((dir) == EDMA_DIR_WRITE) \ + SET_COMPAT(dw, wr_##name, value); \ + else \ + SET_COMPAT(dw, rd_##name, value); \ + } while (0) + +static inline struct dw_edma_v0_ch_regs __iomem * +__dw_ch_regs(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch) +{ + if (dw->chip->mf == EDMA_MF_EDMA_LEGACY) + return &(__dw_regs(dw)->type.legacy.ch); + + if (dir == EDMA_DIR_WRITE) + return &__dw_regs(dw)->type.unroll.ch[ch].wr; + + return &__dw_regs(dw)->type.unroll.ch[ch].rd; +} + +static inline void writel_ch(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch, + u32 value, void __iomem *addr) +{ + if (dw->chip->mf == EDMA_MF_EDMA_LEGACY) { + u32 viewport_sel; + unsigned long flags; + + raw_spin_lock_irqsave(&dw->lock, flags); + + viewport_sel = FIELD_PREP(EDMA_V0_VIEWPORT_MASK, ch); + if (dir == EDMA_DIR_READ) + viewport_sel |= BIT(31); + + writel(viewport_sel, + &(__dw_regs(dw)->type.legacy.viewport_sel)); + writel(value, addr); + + raw_spin_unlock_irqrestore(&dw->lock, flags); + } else { + writel(value, addr); + } +} + +static inline u32 readl_ch(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch, + const void __iomem *addr) +{ + u32 value; + + if (dw->chip->mf == EDMA_MF_EDMA_LEGACY) { + u32 viewport_sel; + unsigned long flags; + + raw_spin_lock_irqsave(&dw->lock, flags); + + viewport_sel = FIELD_PREP(EDMA_V0_VIEWPORT_MASK, ch); + if (dir == EDMA_DIR_READ) + viewport_sel |= BIT(31); + + writel(viewport_sel, + &(__dw_regs(dw)->type.legacy.viewport_sel)); + value = readl(addr); + + raw_spin_unlock_irqrestore(&dw->lock, flags); + } else { + value = readl(addr); + } + + return value; +} + +#define SET_CH_32(dw, dir, ch, name, value) \ + writel_ch(dw, dir, ch, value, &(__dw_ch_regs(dw, dir, ch)->name)) + +#define GET_CH_32(dw, dir, ch, name) \ + readl_ch(dw, dir, ch, &(__dw_ch_regs(dw, dir, ch)->name)) + +static u32 dw_edma_v0_func_num(struct dw_edma_chan *chan) +{ + return FIELD_PREP(EDMA_V0_FUNC_NUM_MASK, chan->func_no); +} + +/* eDMA management callbacks */ +static void dw_edma_v0_core_ch_power(struct dw_edma *dw, + enum dw_edma_dir dir, u16 id, bool enable) +{ + u32 value = enable ? BIT(0) : 0; + + if (WARN_ON_ONCE(id >= EDMA_V0_MAX_NR_CH)) + return; + + switch (id) { + case 0: + SET_RW_COMPAT(dw, dir, ch0_pwr_en, value); + break; + case 1: + SET_RW_COMPAT(dw, dir, ch1_pwr_en, value); + break; + case 2: + SET_RW_COMPAT(dw, dir, ch2_pwr_en, value); + break; + case 3: + SET_RW_COMPAT(dw, dir, ch3_pwr_en, value); + break; + case 4: + SET_RW_COMPAT(dw, dir, ch4_pwr_en, value); + break; + case 5: + SET_RW_COMPAT(dw, dir, ch5_pwr_en, value); + break; + case 6: + SET_RW_COMPAT(dw, dir, ch6_pwr_en, value); + break; + case 7: + SET_RW_COMPAT(dw, dir, ch7_pwr_en, value); + break; + } +} + +static int dw_edma_v0_core_engine_disable(struct dw_edma *dw, + enum dw_edma_dir dir) +{ + u32 value; + int ret; + + SET_RW_32(dw, dir, engine_en, 0); + ret = read_poll_timeout(GET_RW_32, value, !(value & BIT(0)), 100, + 200000, false, dw, dir, engine_en); + if (ret) + dev_warn(dw->chip->dev, "%s engine did not stop within 200ms\n", + dir == EDMA_DIR_WRITE ? "write" : "read"); + + return ret; +} + +static int dw_edma_v0_core_dir_off(struct dw_edma *dw, enum dw_edma_dir dir) +{ + u16 count, id; + int ret = 0; + + scoped_guard(raw_spinlock_irqsave, &dw->lock) + SET_RW_32(dw, dir, int_mask, + EDMA_V0_DONE_INT_MASK | EDMA_V0_ABORT_INT_MASK); + + if (dw->chip->mf == EDMA_MF_HDMA_COMPAT) { + /* + * DWC PCIe Controller Databook 6.10a-lca06, "Legacy DMA + * and HDMA Software Compatibility": HDMA compatibility mode + * does not implement ENGINE_EN, but retains CHi_PWR_EN for + * per-channel enable and disable. + */ + count = dir == EDMA_DIR_WRITE ? dw->wr_ch_cnt : dw->rd_ch_cnt; + for (id = 0; id < count; id++) + dw_edma_v0_core_ch_power(dw, dir, id, false); + } else { + ret = dw_edma_v0_core_engine_disable(dw, dir); + } + + SET_RW_32(dw, dir, int_clear, + EDMA_V0_DONE_INT_MASK | EDMA_V0_ABORT_INT_MASK); + + return ret; +} + +static void dw_edma_v0_core_off(struct dw_edma *dw) +{ + SET_BOTH_32(dw, int_mask, + EDMA_V0_DONE_INT_MASK | EDMA_V0_ABORT_INT_MASK); + SET_BOTH_32(dw, int_clear, + EDMA_V0_DONE_INT_MASK | EDMA_V0_ABORT_INT_MASK); + SET_BOTH_32(dw, engine_en, 0); +} + +static int dw_edma_v0_core_quiesce(struct dw_edma *dw) +{ + int ret = 0; + int err; + + if (dw->wr_ch_cnt) + ret = dw_edma_v0_core_dir_off(dw, EDMA_DIR_WRITE); + if (dw->rd_ch_cnt) { + err = dw_edma_v0_core_dir_off(dw, EDMA_DIR_READ); + if (!ret) + ret = err; + } + + return ret; +} + +/* + * The unrolled eDMA and HDMA compatibility register maps share interrupt + * control per direction, so the whole direction is quiesced. Callers must + * own the direction entirely and prevent the peer from programming it after + * this point. Partial ownership mode validates direction granularity. + */ +static int dw_edma_v0_core_ch_quiesce(struct dw_edma_chan *chan) +{ + return dw_edma_v0_core_dir_off(chan->dw, chan->dir); +} + +static u16 dw_edma_v0_core_ch_count(struct dw_edma *dw, enum dw_edma_dir dir) +{ + u32 num_ch; + + if (dir == EDMA_DIR_WRITE) + num_ch = FIELD_GET(EDMA_V0_WRITE_CH_COUNT_MASK, + GET_32(dw, ctrl)); + else + num_ch = FIELD_GET(EDMA_V0_READ_CH_COUNT_MASK, + GET_32(dw, ctrl)); + + if (num_ch > EDMA_V0_MAX_NR_CH) + num_ch = EDMA_V0_MAX_NR_CH; + + return (u16)num_ch; +} + +static enum dma_status dw_edma_v0_core_ch_status(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + u32 tmp; + + tmp = FIELD_GET(EDMA_V0_CH_STATUS_MASK, + GET_CH_32(dw, chan->dir, chan->id, ch_control1)); + + if (tmp == 1) + return DMA_IN_PROGRESS; + else if (tmp == 3) + return DMA_COMPLETE; + else + return DMA_ERROR; +} + +static void dw_edma_v0_core_clear_done_int(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + + SET_RW_32(dw, chan->dir, int_clear, + FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id))); +} + +static void dw_edma_v0_core_clear_abort_int(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + + SET_RW_32(dw, chan->dir, int_clear, + FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id))); +} + +static irqreturn_t +dw_edma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir, + dw_edma_handler_t done, dw_edma_handler_t abort) +{ + struct dw_edma *dw = dw_irq->dw; + unsigned long total, pos, val; + irqreturn_t ret = IRQ_NONE; + struct dw_edma_chan *chan; + unsigned long off; + unsigned long *mask; + u32 sts; + + if (dir == EDMA_DIR_WRITE) { + total = dw->wr_ch_cnt; + off = 0; + mask = dw_irq->wr_mask; + } else { + total = dw->rd_ch_cnt; + off = dw->wr_ch_cnt; + mask = dw_irq->rd_mask; + } + + /* + * DONE and ABORT status share one register, and on remote setups + * every read is a non-posted round trip across the PCIe link. Take + * one snapshot and derive both views from it. An abort raised + * after the snapshot is deferred, not lost: only bits observed in + * the snapshot are ever cleared below, so its status remains set and + * triggers another handler pass. + */ + sts = GET_RW_32(dw, dir, int_status); + + val = FIELD_GET(EDMA_V0_DONE_INT_MASK, sts); + val &= *mask; + for_each_set_bit(pos, &val, total) { + chan = &dw->chan[pos + off]; + + if (unlikely(dw_edma_core_ch_ignore_irq(chan))) + continue; + + dw_edma_v0_core_clear_done_int(chan); + done(chan); + + ret = IRQ_HANDLED; + } + + val = FIELD_GET(EDMA_V0_ABORT_INT_MASK, sts); + val &= *mask; + for_each_set_bit(pos, &val, total) { + chan = &dw->chan[pos + off]; + + if (unlikely(dw_edma_core_ch_ignore_irq(chan))) + continue; + + dw_edma_v0_core_clear_abort_int(chan); + abort(chan); + + ret = IRQ_HANDLED; + } + + return ret; +} + +static void dw_edma_v0_write_ll_data(struct dw_edma_chan *chan, int i, + u32 control, u32 size, u64 sar, u64 dar) +{ + ptrdiff_t ofs = i * sizeof(struct dw_edma_v0_lli); + + if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) { + struct dw_edma_v0_lli *lli = chan->ll_region.vaddr.mem + ofs; + + lli->transfer_size = size; + lli->sar.reg = sar; + lli->dar.reg = dar; + dma_wmb(); + lli->control = control; + } else { + struct dw_edma_v0_lli __iomem *lli = chan->ll_region.vaddr.io + ofs; + + writel(size, &lli->transfer_size); + writeq(sar, &lli->sar.reg); + writeq(dar, &lli->dar.reg); + writel(control, &lli->control); + } +} + +static void dw_edma_v0_write_ll_link(struct dw_edma_chan *chan, + int i, u32 control, u64 pointer) +{ + ptrdiff_t ofs = i * sizeof(struct dw_edma_v0_lli); + + if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) { + struct dw_edma_v0_llp *llp = chan->ll_region.vaddr.mem + ofs; + + llp->llp.reg = pointer; + dma_wmb(); + llp->control = control; + } else { + struct dw_edma_v0_llp __iomem *llp = chan->ll_region.vaddr.io + ofs; + + writeq(pointer, &llp->llp.reg); + writel(control, &llp->control); + } +} + +static void dw_edma_v0_core_ch_enable(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + unsigned long flags; + u32 tmp; + + /* Enable engine */ + SET_RW_32(dw, chan->dir, engine_en, BIT(0)); + if (dw->chip->mf == EDMA_MF_HDMA_COMPAT) + dw_edma_v0_core_ch_power(dw, chan->dir, chan->id, true); + /* Interrupt mask/unmask - done, abort */ + raw_spin_lock_irqsave(&dw->lock, flags); + + tmp = GET_RW_32(dw, chan->dir, int_mask); + if (chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE) { + tmp |= FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id)); + tmp |= FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id)); + } else { + tmp &= ~FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id)); + tmp &= ~FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id)); + } + SET_RW_32(dw, chan->dir, int_mask, tmp); + /* Linked list error */ + tmp = GET_RW_32(dw, chan->dir, linked_list_err_en); + tmp |= FIELD_PREP(EDMA_V0_LINKED_LIST_ERR_MASK, BIT(chan->id)); + SET_RW_32(dw, chan->dir, linked_list_err_en, tmp); + + raw_spin_unlock_irqrestore(&dw->lock, flags); + + /* Channel control */ + SET_CH_32(dw, chan->dir, chan->id, ch_control1, + DW_EDMA_V0_CCS | DW_EDMA_V0_LLE | + dw_edma_v0_func_num(chan)); + /* Linked list */ + /* llp is not aligned on 64bit -> keep 32bit accesses */ + SET_CH_32(dw, chan->dir, chan->id, llp.lsb, + lower_32_bits(chan->ll_region.paddr)); + SET_CH_32(dw, chan->dir, chan->id, llp.msb, + upper_32_bits(chan->ll_region.paddr)); +} + +static void dw_edma_v0_sync_ll_data(struct dw_edma_chan *chan) +{ + /* + * In case of remote eDMA engine setup, the DW PCIe RP/EP internal + * configuration registers and application memory are normally accessed + * over different buses. Ensure LL-data reaches the memory before the + * doorbell register is toggled by issuing the dummy-read from the remote + * LL memory in a hope that the MRd TLP will return only after the + * last MWr TLP is completed + */ + if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL)) + readl(chan->ll_region.vaddr.io); +} + +static void dw_edma_v0_core_ch_config(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + u32 tmp = 0; + + /* MSI done addr - low, high */ + SET_RW_32(dw, chan->dir, done_imwr.lsb, chan->msi.address_lo); + SET_RW_32(dw, chan->dir, done_imwr.msb, chan->msi.address_hi); + /* MSI abort addr - low, high */ + SET_RW_32(dw, chan->dir, abort_imwr.lsb, chan->msi.address_lo); + SET_RW_32(dw, chan->dir, abort_imwr.msb, chan->msi.address_hi); + /* MSI data - low, high */ + switch (chan->id) { + case 0: + case 1: + tmp = GET_RW_32(dw, chan->dir, ch01_imwr_data); + break; + + case 2: + case 3: + tmp = GET_RW_32(dw, chan->dir, ch23_imwr_data); + break; + + case 4: + case 5: + tmp = GET_RW_32(dw, chan->dir, ch45_imwr_data); + break; + + case 6: + case 7: + tmp = GET_RW_32(dw, chan->dir, ch67_imwr_data); + break; + } + + if (chan->id & BIT(0)) { + /* Channel odd {1, 3, 5, 7} */ + tmp &= EDMA_V0_CH_EVEN_MSI_DATA_MASK; + tmp |= FIELD_PREP(EDMA_V0_CH_ODD_MSI_DATA_MASK, + chan->msi.data); + } else { + /* Channel even {0, 2, 4, 6} */ + tmp &= EDMA_V0_CH_ODD_MSI_DATA_MASK; + tmp |= FIELD_PREP(EDMA_V0_CH_EVEN_MSI_DATA_MASK, + chan->msi.data); + } + + switch (chan->id) { + case 0: + case 1: + SET_RW_32(dw, chan->dir, ch01_imwr_data, tmp); + break; + + case 2: + case 3: + SET_RW_32(dw, chan->dir, ch23_imwr_data, tmp); + break; + + case 4: + case 5: + SET_RW_32(dw, chan->dir, ch45_imwr_data, tmp); + break; + + case 6: + case 7: + SET_RW_32(dw, chan->dir, ch67_imwr_data, tmp); + break; + } +} + +static void +dw_edma_v0_core_ll_data(struct dw_edma_chan *chan, struct dw_edma_burst *burst, + u32 idx, bool cb, bool irq) +{ + u32 control = 0; + + if (cb) + control |= DW_EDMA_V0_CB; + + if (irq) { + control |= DW_EDMA_V0_LIE; + + /* + * A local instance never issues transfers on a remote-routed + * channel: on CHIP_LOCAL instances, REMOTE routing denotes a + * channel handed over to the remote side, which programs the + * linked list through its own instance. The remote-only + * recipe (LIE|RIE with the local interrupt masked) is thus + * applied by the instance that owns the transfer, and the + * LIE-only write below never executes for a remote-routed + * channel. + */ + if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) && + chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE) + control |= DW_EDMA_V0_RIE; + } + + dw_edma_v0_write_ll_data(chan, idx, control, burst->sz, burst->sar, + burst->dar); +} + +static void +dw_edma_v0_core_ll_link(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr) +{ + u32 control = DW_EDMA_V0_LLP | DW_EDMA_V0_TCB; + + if (!cb) + control |= DW_EDMA_V0_CB; + + dw_edma_v0_write_ll_link(chan, idx, control, addr); +} + +static void dw_edma_v0_core_ch_doorbell(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + + dw_edma_v0_sync_ll_data(chan); + + /* Doorbell */ + SET_RW_32(dw, chan->dir, doorbell, + FIELD_PREP(EDMA_V0_DOORBELL_CH_MASK, chan->id)); +} + +/* eDMA debugfs callbacks */ +static void dw_edma_v0_core_debugfs_on(struct dw_edma *dw) +{ + dw_edma_v0_debugfs_on(dw); +} + +static void dw_edma_v0_core_ack_emulated_irq(struct dw_edma *dw) +{ + /* + * Interrupt emulation may assert the IRQ without setting + * DONE/ABORT status bits. A zero write to INT_CLEAR deasserts the + * emulated IRQ, while being a no-op for real interrupts. + */ + SET_BOTH_32(dw, int_clear, 0); +} + +static resource_size_t dw_edma_v0_core_db_offset(struct dw_edma *dw) +{ + /* + * rd_int_status is chosen arbitrarily, but wr_int_status would be + * equally suitable. + */ + return offsetof(struct dw_edma_v0_regs, rd_int_status); +} + +static const struct dw_edma_core_ops dw_edma_v0_core = { + .off = dw_edma_v0_core_off, + .quiesce = dw_edma_v0_core_quiesce, + .ch_quiesce = dw_edma_v0_core_ch_quiesce, + .ch_count = dw_edma_v0_core_ch_count, + .ch_status = dw_edma_v0_core_ch_status, + .handle_int = dw_edma_v0_core_handle_int, + .ll_data = dw_edma_v0_core_ll_data, + .ll_link = dw_edma_v0_core_ll_link, + .ch_doorbell = dw_edma_v0_core_ch_doorbell, + .ch_enable = dw_edma_v0_core_ch_enable, + .ch_config = dw_edma_v0_core_ch_config, + .debugfs_on = dw_edma_v0_core_debugfs_on, + .ack_emulated_irq = dw_edma_v0_core_ack_emulated_irq, + .db_offset = dw_edma_v0_core_db_offset, +}; + +void dw_edma_v0_core_register(struct dw_edma *dw) +{ + dw->core = &dw_edma_v0_core; +} diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.h b/drivers/dma/dw-edma/dw-edma-v0-core.h new file mode 100644 index 000000000..04a882222 --- /dev/null +++ b/drivers/dma/dw-edma/dw-edma-v0-core.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates. + * Synopsys DesignWare eDMA v0 core + * + * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com> + */ + +#ifndef _DW_EDMA_V0_CORE_H +#define _DW_EDMA_V0_CORE_H + +#include <linux/dma/edma.h> + +/* eDMA core register */ +void dw_edma_v0_core_register(struct dw_edma *dw); + +#endif /* _DW_EDMA_V0_CORE_H */ diff --git a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c new file mode 100644 index 000000000..406f169b0 --- /dev/null +++ b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates. + * Synopsys DesignWare eDMA v0 core + * + * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com> + */ + +#include <linux/debugfs.h> +#include <linux/bitfield.h> + +#include "dw-edma-v0-debugfs.h" +#include "dw-edma-v0-regs.h" +#include "dw-edma-core.h" + +#define REGS_ADDR(dw, name) \ + ({ \ + struct dw_edma_v0_regs __iomem *__regs = (dw)->chip->reg_base; \ + \ + (void __iomem *)&__regs->name; \ + }) + +#define REGS_CH_ADDR(dw, name, _dir, _ch) \ + ({ \ + struct dw_edma_v0_ch_regs __iomem *__ch_regs; \ + \ + if ((dw)->chip->mf == EDMA_MF_EDMA_LEGACY) \ + __ch_regs = REGS_ADDR(dw, type.legacy.ch); \ + else if (_dir == EDMA_DIR_READ) \ + __ch_regs = REGS_ADDR(dw, type.unroll.ch[_ch].rd); \ + else \ + __ch_regs = REGS_ADDR(dw, type.unroll.ch[_ch].wr); \ + \ + (void __iomem *)&__ch_regs->name; \ + }) + +#define REGISTER(dw, name) \ + { dw, #name, REGS_ADDR(dw, name) } + +#define CTX_REGISTER(dw, name, dir, ch) \ + { dw, #name, REGS_CH_ADDR(dw, name, dir, ch), dir, ch } + +#define WR_REGISTER(dw, name) \ + { dw, #name, REGS_ADDR(dw, wr_##name) } +#define RD_REGISTER(dw, name) \ + { dw, #name, REGS_ADDR(dw, rd_##name) } + +#define WR_REGISTER_LEGACY(dw, name) \ + { dw, #name, REGS_ADDR(dw, type.legacy.wr_##name) } +#define RD_REGISTER_LEGACY(name) \ + { dw, #name, REGS_ADDR(dw, type.legacy.rd_##name) } + +#define WR_REGISTER_UNROLL(dw, name) \ + { dw, #name, REGS_ADDR(dw, type.unroll.wr_##name) } +#define RD_REGISTER_UNROLL(dw, name) \ + { dw, #name, REGS_ADDR(dw, type.unroll.rd_##name) } + +#define WRITE_STR "write" +#define READ_STR "read" +#define CHANNEL_STR "channel" +#define REGISTERS_STR "registers" + +struct dw_edma_debugfs_entry { + struct dw_edma *dw; + const char *name; + void __iomem *reg; + enum dw_edma_dir dir; + u16 ch; +}; + +static int dw_edma_debugfs_u32_get(void *data, u64 *val) +{ + struct dw_edma_debugfs_entry *entry = data; + struct dw_edma *dw = entry->dw; + void __iomem *reg = entry->reg; + + if (dw->chip->mf == EDMA_MF_EDMA_LEGACY && + reg >= REGS_ADDR(dw, type.legacy.ch)) { + unsigned long flags; + u32 viewport_sel; + + viewport_sel = entry->dir == EDMA_DIR_READ ? BIT(31) : 0; + viewport_sel |= FIELD_PREP(EDMA_V0_VIEWPORT_MASK, entry->ch); + + raw_spin_lock_irqsave(&dw->lock, flags); + + writel(viewport_sel, REGS_ADDR(dw, type.legacy.viewport_sel)); + *val = readl(reg); + + raw_spin_unlock_irqrestore(&dw->lock, flags); + } else { + *val = readl(reg); + } + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(fops_x32, dw_edma_debugfs_u32_get, NULL, "0x%08llx\n"); + +static void dw_edma_debugfs_create_x32(struct dw_edma *dw, + const struct dw_edma_debugfs_entry ini[], + int nr_entries, struct dentry *dent) +{ + struct dw_edma_debugfs_entry *entries; + int i; + + entries = devm_kcalloc(dw->chip->dev, nr_entries, sizeof(*entries), + GFP_KERNEL); + if (!entries) + return; + + for (i = 0; i < nr_entries; i++) { + entries[i] = ini[i]; + + debugfs_create_file_unsafe(entries[i].name, 0444, dent, + &entries[i], &fops_x32); + } +} + +static void dw_edma_debugfs_regs_ch(struct dw_edma *dw, enum dw_edma_dir dir, + u16 ch, struct dentry *dent) +{ + struct dw_edma_debugfs_entry debugfs_regs[] = { + CTX_REGISTER(dw, ch_control1, dir, ch), + CTX_REGISTER(dw, ch_control2, dir, ch), + CTX_REGISTER(dw, transfer_size, dir, ch), + CTX_REGISTER(dw, sar.lsb, dir, ch), + CTX_REGISTER(dw, sar.msb, dir, ch), + CTX_REGISTER(dw, dar.lsb, dir, ch), + CTX_REGISTER(dw, dar.msb, dir, ch), + CTX_REGISTER(dw, llp.lsb, dir, ch), + CTX_REGISTER(dw, llp.msb, dir, ch), + }; + int nr_entries; + + nr_entries = ARRAY_SIZE(debugfs_regs); + dw_edma_debugfs_create_x32(dw, debugfs_regs, nr_entries, dent); +} + +static noinline_for_stack void +dw_edma_debugfs_regs_wr(struct dw_edma *dw, struct dentry *dent) +{ + const struct dw_edma_debugfs_entry debugfs_regs[] = { + /* eDMA global registers */ + WR_REGISTER(dw, engine_en), + WR_REGISTER(dw, doorbell), + WR_REGISTER(dw, ch_arb_weight.lsb), + WR_REGISTER(dw, ch_arb_weight.msb), + /* eDMA interrupts registers */ + WR_REGISTER(dw, int_status), + WR_REGISTER(dw, int_mask), + WR_REGISTER(dw, int_clear), + WR_REGISTER(dw, err_status), + WR_REGISTER(dw, done_imwr.lsb), + WR_REGISTER(dw, done_imwr.msb), + WR_REGISTER(dw, abort_imwr.lsb), + WR_REGISTER(dw, abort_imwr.msb), + WR_REGISTER(dw, ch01_imwr_data), + WR_REGISTER(dw, ch23_imwr_data), + WR_REGISTER(dw, ch45_imwr_data), + WR_REGISTER(dw, ch67_imwr_data), + WR_REGISTER(dw, linked_list_err_en), + }; + const struct dw_edma_debugfs_entry debugfs_unroll_regs[] = { + /* eDMA channel context grouping */ + WR_REGISTER_UNROLL(dw, engine_chgroup), + WR_REGISTER_UNROLL(dw, engine_hshake_cnt.lsb), + WR_REGISTER_UNROLL(dw, engine_hshake_cnt.msb), + WR_REGISTER_UNROLL(dw, ch0_pwr_en), + WR_REGISTER_UNROLL(dw, ch1_pwr_en), + WR_REGISTER_UNROLL(dw, ch2_pwr_en), + WR_REGISTER_UNROLL(dw, ch3_pwr_en), + WR_REGISTER_UNROLL(dw, ch4_pwr_en), + WR_REGISTER_UNROLL(dw, ch5_pwr_en), + WR_REGISTER_UNROLL(dw, ch6_pwr_en), + WR_REGISTER_UNROLL(dw, ch7_pwr_en), + }; + struct dentry *regs_dent, *ch_dent; + int nr_entries, i; + char name[32]; + + regs_dent = debugfs_create_dir(WRITE_STR, dent); + + nr_entries = ARRAY_SIZE(debugfs_regs); + dw_edma_debugfs_create_x32(dw, debugfs_regs, nr_entries, regs_dent); + + if (dw->chip->mf == EDMA_MF_HDMA_COMPAT) { + nr_entries = ARRAY_SIZE(debugfs_unroll_regs); + dw_edma_debugfs_create_x32(dw, debugfs_unroll_regs, nr_entries, + regs_dent); + } + + for (i = 0; i < dw->wr_ch_cnt; i++) { + snprintf(name, sizeof(name), "%s:%d", CHANNEL_STR, i); + + ch_dent = debugfs_create_dir(name, regs_dent); + + dw_edma_debugfs_regs_ch(dw, EDMA_DIR_WRITE, i, ch_dent); + } +} + +static noinline_for_stack void dw_edma_debugfs_regs_rd(struct dw_edma *dw, + struct dentry *dent) +{ + const struct dw_edma_debugfs_entry debugfs_regs[] = { + /* eDMA global registers */ + RD_REGISTER(dw, engine_en), + RD_REGISTER(dw, doorbell), + RD_REGISTER(dw, ch_arb_weight.lsb), + RD_REGISTER(dw, ch_arb_weight.msb), + /* eDMA interrupts registers */ + RD_REGISTER(dw, int_status), + RD_REGISTER(dw, int_mask), + RD_REGISTER(dw, int_clear), + RD_REGISTER(dw, err_status.lsb), + RD_REGISTER(dw, err_status.msb), + RD_REGISTER(dw, linked_list_err_en), + RD_REGISTER(dw, done_imwr.lsb), + RD_REGISTER(dw, done_imwr.msb), + RD_REGISTER(dw, abort_imwr.lsb), + RD_REGISTER(dw, abort_imwr.msb), + RD_REGISTER(dw, ch01_imwr_data), + RD_REGISTER(dw, ch23_imwr_data), + RD_REGISTER(dw, ch45_imwr_data), + RD_REGISTER(dw, ch67_imwr_data), + }; + const struct dw_edma_debugfs_entry debugfs_unroll_regs[] = { + /* eDMA channel context grouping */ + RD_REGISTER_UNROLL(dw, engine_chgroup), + RD_REGISTER_UNROLL(dw, engine_hshake_cnt.lsb), + RD_REGISTER_UNROLL(dw, engine_hshake_cnt.msb), + RD_REGISTER_UNROLL(dw, ch0_pwr_en), + RD_REGISTER_UNROLL(dw, ch1_pwr_en), + RD_REGISTER_UNROLL(dw, ch2_pwr_en), + RD_REGISTER_UNROLL(dw, ch3_pwr_en), + RD_REGISTER_UNROLL(dw, ch4_pwr_en), + RD_REGISTER_UNROLL(dw, ch5_pwr_en), + RD_REGISTER_UNROLL(dw, ch6_pwr_en), + RD_REGISTER_UNROLL(dw, ch7_pwr_en), + }; + struct dentry *regs_dent, *ch_dent; + int nr_entries, i; + char name[32]; + + regs_dent = debugfs_create_dir(READ_STR, dent); + + nr_entries = ARRAY_SIZE(debugfs_regs); + dw_edma_debugfs_create_x32(dw, debugfs_regs, nr_entries, regs_dent); + + if (dw->chip->mf == EDMA_MF_HDMA_COMPAT) { + nr_entries = ARRAY_SIZE(debugfs_unroll_regs); + dw_edma_debugfs_create_x32(dw, debugfs_unroll_regs, nr_entries, + regs_dent); + } + + for (i = 0; i < dw->rd_ch_cnt; i++) { + snprintf(name, sizeof(name), "%s:%d", CHANNEL_STR, i); + + ch_dent = debugfs_create_dir(name, regs_dent); + + dw_edma_debugfs_regs_ch(dw, EDMA_DIR_READ, i, ch_dent); + } +} + +static void dw_edma_debugfs_regs(struct dw_edma *dw) +{ + const struct dw_edma_debugfs_entry debugfs_regs[] = { + REGISTER(dw, ctrl_data_arb_prior), + REGISTER(dw, ctrl), + }; + struct dentry *regs_dent; + int nr_entries; + + regs_dent = debugfs_create_dir(REGISTERS_STR, dw->dma.dbg_dev_root); + + nr_entries = ARRAY_SIZE(debugfs_regs); + dw_edma_debugfs_create_x32(dw, debugfs_regs, nr_entries, regs_dent); + + dw_edma_debugfs_regs_wr(dw, regs_dent); + dw_edma_debugfs_regs_rd(dw, regs_dent); +} + +void dw_edma_v0_debugfs_on(struct dw_edma *dw) +{ + if (!debugfs_initialized()) + return; + + debugfs_create_u32("mf", 0444, dw->dma.dbg_dev_root, &dw->chip->mf); + debugfs_create_u16("wr_ch_cnt", 0444, dw->dma.dbg_dev_root, &dw->wr_ch_cnt); + debugfs_create_u16("rd_ch_cnt", 0444, dw->dma.dbg_dev_root, &dw->rd_ch_cnt); + + dw_edma_debugfs_regs(dw); +} diff --git a/drivers/dma/dw-edma/dw-edma-v0-debugfs.h b/drivers/dma/dw-edma/dw-edma-v0-debugfs.h new file mode 100644 index 000000000..fb3342d97 --- /dev/null +++ b/drivers/dma/dw-edma/dw-edma-v0-debugfs.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates. + * Synopsys DesignWare eDMA v0 core + * + * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com> + */ + +#ifndef _DW_EDMA_V0_DEBUG_FS_H +#define _DW_EDMA_V0_DEBUG_FS_H + +#include <linux/dma/edma.h> + +#ifdef CONFIG_DEBUG_FS +void dw_edma_v0_debugfs_on(struct dw_edma *dw); +#else +static inline void dw_edma_v0_debugfs_on(struct dw_edma *dw) +{ +} +#endif /* CONFIG_DEBUG_FS */ + +#endif /* _DW_EDMA_V0_DEBUG_FS_H */ diff --git a/drivers/dma/dw-edma/dw-edma-v0-regs.h b/drivers/dma/dw-edma/dw-edma-v0-regs.h new file mode 100644 index 000000000..e175f7b20 --- /dev/null +++ b/drivers/dma/dw-edma/dw-edma-v0-regs.h @@ -0,0 +1,233 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates. + * Synopsys DesignWare eDMA v0 core + * + * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com> + */ + +#ifndef _DW_EDMA_V0_REGS_H +#define _DW_EDMA_V0_REGS_H + +#include <linux/dmaengine.h> + +#define EDMA_V0_MAX_NR_CH 8 +#define EDMA_V0_VIEWPORT_MASK GENMASK(2, 0) +#define EDMA_V0_DONE_INT_MASK GENMASK(7, 0) +#define EDMA_V0_ABORT_INT_MASK GENMASK(23, 16) +#define EDMA_V0_WRITE_CH_COUNT_MASK GENMASK(3, 0) +#define EDMA_V0_READ_CH_COUNT_MASK GENMASK(19, 16) +#define EDMA_V0_CH_STATUS_MASK GENMASK(6, 5) +#define EDMA_V0_DOORBELL_CH_MASK GENMASK(2, 0) +#define EDMA_V0_LINKED_LIST_ERR_MASK GENMASK(7, 0) + +#define EDMA_V0_CH_ODD_MSI_DATA_MASK GENMASK(31, 16) +#define EDMA_V0_CH_EVEN_MSI_DATA_MASK GENMASK(15, 0) + +struct dw_edma_v0_ch_regs { + u32 ch_control1; /* 0x0000 */ + u32 ch_control2; /* 0x0004 */ + u32 transfer_size; /* 0x0008 */ + union { + u64 reg; /* 0x000c..0x0010 */ + struct { + u32 lsb; /* 0x000c */ + u32 msb; /* 0x0010 */ + }; + } sar; + union { + u64 reg; /* 0x0014..0x0018 */ + struct { + u32 lsb; /* 0x0014 */ + u32 msb; /* 0x0018 */ + }; + } dar; + union { + u64 reg; /* 0x001c..0x0020 */ + struct { + u32 lsb; /* 0x001c */ + u32 msb; /* 0x0020 */ + }; + } llp; +} __packed; + +struct dw_edma_v0_ch { + struct dw_edma_v0_ch_regs wr; /* 0x0200 */ + u32 padding_1[55]; /* 0x0224..0x02fc */ + struct dw_edma_v0_ch_regs rd; /* 0x0300 */ + u32 padding_2[55]; /* 0x0324..0x03fc */ +} __packed; + +struct dw_edma_v0_unroll { + u32 padding_1; /* 0x00f8 */ + u32 wr_engine_chgroup; /* 0x0100 */ + u32 rd_engine_chgroup; /* 0x0104 */ + union { + u64 reg; /* 0x0108..0x010c */ + struct { + u32 lsb; /* 0x0108 */ + u32 msb; /* 0x010c */ + }; + } wr_engine_hshake_cnt; + u32 padding_2[2]; /* 0x0110..0x0114 */ + union { + u64 reg; /* 0x0120..0x0124 */ + struct { + u32 lsb; /* 0x0120 */ + u32 msb; /* 0x0124 */ + }; + } rd_engine_hshake_cnt; + u32 padding_3[2]; /* 0x0120..0x0124 */ + u32 wr_ch0_pwr_en; /* 0x0128 */ + u32 wr_ch1_pwr_en; /* 0x012c */ + u32 wr_ch2_pwr_en; /* 0x0130 */ + u32 wr_ch3_pwr_en; /* 0x0134 */ + u32 wr_ch4_pwr_en; /* 0x0138 */ + u32 wr_ch5_pwr_en; /* 0x013c */ + u32 wr_ch6_pwr_en; /* 0x0140 */ + u32 wr_ch7_pwr_en; /* 0x0144 */ + u32 padding_4[8]; /* 0x0148..0x0164 */ + u32 rd_ch0_pwr_en; /* 0x0168 */ + u32 rd_ch1_pwr_en; /* 0x016c */ + u32 rd_ch2_pwr_en; /* 0x0170 */ + u32 rd_ch3_pwr_en; /* 0x0174 */ + u32 rd_ch4_pwr_en; /* 0x0178 */ + u32 rd_ch5_pwr_en; /* 0x018c */ + u32 rd_ch6_pwr_en; /* 0x0180 */ + u32 rd_ch7_pwr_en; /* 0x0184 */ + u32 padding_5[30]; /* 0x0188..0x01fc */ + struct dw_edma_v0_ch ch[EDMA_V0_MAX_NR_CH]; /* 0x0200..0x1120 */ +} __packed; + +struct dw_edma_v0_legacy { + u32 viewport_sel; /* 0x00f8 */ + struct dw_edma_v0_ch_regs ch; /* 0x0100..0x0120 */ +} __packed; + +struct dw_edma_v0_regs { + /* eDMA global registers */ + u32 ctrl_data_arb_prior; /* 0x0000 */ + u32 padding_1; /* 0x0004 */ + u32 ctrl; /* 0x0008 */ + u32 wr_engine_en; /* 0x000c */ + u32 wr_doorbell; /* 0x0010 */ + u32 padding_2; /* 0x0014 */ + union { + u64 reg; /* 0x0018..0x001c */ + struct { + u32 lsb; /* 0x0018 */ + u32 msb; /* 0x001c */ + }; + } wr_ch_arb_weight; + u32 padding_3[3]; /* 0x0020..0x0028 */ + u32 rd_engine_en; /* 0x002c */ + u32 rd_doorbell; /* 0x0030 */ + u32 padding_4; /* 0x0034 */ + union { + u64 reg; /* 0x0038..0x003c */ + struct { + u32 lsb; /* 0x0038 */ + u32 msb; /* 0x003c */ + }; + } rd_ch_arb_weight; + u32 padding_5[3]; /* 0x0040..0x0048 */ + /* eDMA interrupts registers */ + u32 wr_int_status; /* 0x004c */ + u32 padding_6; /* 0x0050 */ + u32 wr_int_mask; /* 0x0054 */ + u32 wr_int_clear; /* 0x0058 */ + u32 wr_err_status; /* 0x005c */ + union { + u64 reg; /* 0x0060..0x0064 */ + struct { + u32 lsb; /* 0x0060 */ + u32 msb; /* 0x0064 */ + }; + } wr_done_imwr; + union { + u64 reg; /* 0x0068..0x006c */ + struct { + u32 lsb; /* 0x0068 */ + u32 msb; /* 0x006c */ + }; + } wr_abort_imwr; + u32 wr_ch01_imwr_data; /* 0x0070 */ + u32 wr_ch23_imwr_data; /* 0x0074 */ + u32 wr_ch45_imwr_data; /* 0x0078 */ + u32 wr_ch67_imwr_data; /* 0x007c */ + u32 padding_7[4]; /* 0x0080..0x008c */ + u32 wr_linked_list_err_en; /* 0x0090 */ + u32 padding_8[3]; /* 0x0094..0x009c */ + u32 rd_int_status; /* 0x00a0 */ + u32 padding_9; /* 0x00a4 */ + u32 rd_int_mask; /* 0x00a8 */ + u32 rd_int_clear; /* 0x00ac */ + u32 padding_10; /* 0x00b0 */ + union { + u64 reg; /* 0x00b4..0x00b8 */ + struct { + u32 lsb; /* 0x00b4 */ + u32 msb; /* 0x00b8 */ + }; + } rd_err_status; + u32 padding_11[2]; /* 0x00bc..0x00c0 */ + u32 rd_linked_list_err_en; /* 0x00c4 */ + u32 padding_12; /* 0x00c8 */ + union { + u64 reg; /* 0x00cc..0x00d0 */ + struct { + u32 lsb; /* 0x00cc */ + u32 msb; /* 0x00d0 */ + }; + } rd_done_imwr; + union { + u64 reg; /* 0x00d4..0x00d8 */ + struct { + u32 lsb; /* 0x00d4 */ + u32 msb; /* 0x00d8 */ + }; + } rd_abort_imwr; + u32 rd_ch01_imwr_data; /* 0x00dc */ + u32 rd_ch23_imwr_data; /* 0x00e0 */ + u32 rd_ch45_imwr_data; /* 0x00e4 */ + u32 rd_ch67_imwr_data; /* 0x00e8 */ + u32 padding_13[4]; /* 0x00ec..0x00f8 */ + /* eDMA channel context grouping */ + union dw_edma_v0_type { + struct dw_edma_v0_legacy legacy; /* 0x00f8..0x0120 */ + struct dw_edma_v0_unroll unroll; /* 0x00f8..0x1120 */ + } type; +} __packed; + +struct dw_edma_v0_lli { + u32 control; + u32 transfer_size; + union { + u64 reg; + struct { + u32 lsb; + u32 msb; + }; + } sar; + union { + u64 reg; + struct { + u32 lsb; + u32 msb; + }; + } dar; +} __packed; + +struct dw_edma_v0_llp { + u32 control; + u32 reserved; + union { + u64 reg; + struct { + u32 lsb; + u32 msb; + }; + } llp; +} __packed; + +#endif /* _DW_EDMA_V0_REGS_H */ diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c new file mode 100644 index 000000000..36ee72efc --- /dev/null +++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023 Cai Huoqing + * Synopsys DesignWare HDMA v0 core + */ + +#include <linux/bitfield.h> +#include <linux/irqreturn.h> +#include <linux/io-64-nonatomic-lo-hi.h> + +#include "dw-edma-core.h" +#include "dw-hdma-v0-core.h" +#include "dw-hdma-v0-regs.h" +#include "dw-hdma-v0-debugfs.h" + +enum dw_hdma_control { + DW_HDMA_V0_CB = BIT(0), + DW_HDMA_V0_TCB = BIT(1), + DW_HDMA_V0_LLP = BIT(2), + DW_HDMA_V0_LWIE = BIT(3), + DW_HDMA_V0_RWIE = BIT(4), + DW_HDMA_V0_CCS = BIT(8), + DW_HDMA_V0_LLE = BIT(9), +}; + +static inline struct dw_hdma_v0_regs __iomem *__dw_regs(struct dw_edma *dw) +{ + return dw->chip->reg_base; +} + +static inline struct dw_hdma_v0_ch_regs __iomem * +__dw_ch_regs(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch) +{ + if (dir == EDMA_DIR_WRITE) + return &(__dw_regs(dw)->ch[ch].wr); + else + return &(__dw_regs(dw)->ch[ch].rd); +} + +#define SET_CH_32(dw, dir, ch, name, value) \ + writel(value, &(__dw_ch_regs(dw, dir, ch)->name)) + +#define GET_CH_32(dw, dir, ch, name) \ + readl(&(__dw_ch_regs(dw, dir, ch)->name)) + +#define SET_BOTH_CH_32(dw, ch, name, value) \ + do { \ + writel(value, &(__dw_ch_regs(dw, EDMA_DIR_WRITE, ch)->name)); \ + writel(value, &(__dw_ch_regs(dw, EDMA_DIR_READ, ch)->name)); \ + } while (0) + +static u32 dw_hdma_v0_core_int_setup(struct dw_edma_chan *chan, u32 val) +{ + val &= ~(HDMA_V0_LOCAL_ABORT_INT_EN | HDMA_V0_REMOTE_ABORT_INT_EN | + HDMA_V0_LOCAL_STOP_INT_EN | HDMA_V0_REMOTE_STOP_INT_EN | + HDMA_V0_ABORT_INT_MASK | HDMA_V0_STOP_INT_MASK); + + /* + * HDMA_INT_STATUS.STOP and .ABORT are latched only when LSIE and + * LAIE are enabled. A remote handler needs those status bits to + * identify the source of the IMWr, so keep local generation enabled + * and mask the local interrupt pins instead. + */ + val |= HDMA_V0_LOCAL_ABORT_INT_EN | HDMA_V0_LOCAL_STOP_INT_EN; + + if (chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE) + val |= HDMA_V0_REMOTE_ABORT_INT_EN | + HDMA_V0_REMOTE_STOP_INT_EN | + HDMA_V0_ABORT_INT_MASK | HDMA_V0_STOP_INT_MASK; + + return val; +} + +/* HDMA management callbacks */ +static void dw_hdma_v0_core_ch_off(struct dw_edma *dw, enum dw_edma_dir dir, + u16 id) +{ + SET_CH_32(dw, dir, id, int_setup, + HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK); + SET_CH_32(dw, dir, id, ch_en, 0); + SET_CH_32(dw, dir, id, int_clear, + HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK); +} + +static void dw_hdma_v0_core_off(struct dw_edma *dw) +{ + int id; + enum dw_edma_dir dir; + + dir = EDMA_DIR_WRITE; + for (id = 0; id < dw->wr_ch_cnt; id++) { + SET_CH_32(dw, dir, id, int_setup, + HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK); + SET_CH_32(dw, dir, id, int_clear, + HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK); + SET_CH_32(dw, dir, id, ch_en, 0); + } + + dir = EDMA_DIR_READ; + for (id = 0; id < dw->rd_ch_cnt; id++) { + SET_CH_32(dw, dir, id, int_setup, + HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK); + SET_CH_32(dw, dir, id, int_clear, + HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK); + SET_CH_32(dw, dir, id, ch_en, 0); + } +} + +static int dw_hdma_v0_core_quiesce(struct dw_edma *dw) +{ + int id; + + for (id = 0; id < dw->wr_ch_cnt; id++) + dw_hdma_v0_core_ch_off(dw, EDMA_DIR_WRITE, id); + for (id = 0; id < dw->rd_ch_cnt; id++) + dw_hdma_v0_core_ch_off(dw, EDMA_DIR_READ, id); + + return 0; +} + +static int dw_hdma_v0_core_ch_quiesce(struct dw_edma_chan *chan) +{ + dw_hdma_v0_core_ch_off(chan->dw, chan->dir, chan->id); + + return 0; +} + +static u16 dw_hdma_v0_core_ch_count(struct dw_edma *dw, enum dw_edma_dir dir) +{ + /* + * The HDMA IP have no way to know the number of hardware channels + * available, we set it to maximum channels and let the platform + * set the right number of channels. + */ + return HDMA_V0_MAX_NR_CH; +} + +static enum dma_status dw_hdma_v0_core_ch_status(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + u32 tmp; + + tmp = FIELD_GET(HDMA_V0_CH_STATUS_MASK, + GET_CH_32(dw, chan->dir, chan->id, ch_stat)); + + if (tmp == 1) + return DMA_IN_PROGRESS; + else if (tmp == 3) + return DMA_COMPLETE; + else + return DMA_ERROR; +} + +static void dw_hdma_v0_core_clear_done_int(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + + SET_CH_32(dw, chan->dir, chan->id, int_clear, HDMA_V0_STOP_INT_MASK); +} + +static void dw_hdma_v0_core_clear_abort_int(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + + SET_CH_32(dw, chan->dir, chan->id, int_clear, HDMA_V0_ABORT_INT_MASK); +} + +static u32 dw_hdma_v0_core_status_int(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + + return GET_CH_32(dw, chan->dir, chan->id, int_stat); +} + +static irqreturn_t +dw_hdma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir, + dw_edma_handler_t done, dw_edma_handler_t abort) +{ + struct dw_edma *dw = dw_irq->dw; + unsigned long total, pos, val; + irqreturn_t ret = IRQ_NONE; + struct dw_edma_chan *chan; + unsigned long off, *mask; + + if (dir == EDMA_DIR_WRITE) { + total = dw->wr_ch_cnt; + off = 0; + mask = dw_irq->wr_mask; + } else { + total = dw->rd_ch_cnt; + off = dw->wr_ch_cnt; + mask = dw_irq->rd_mask; + } + + for_each_set_bit(pos, mask, total) { + chan = &dw->chan[pos + off]; + if (unlikely(dw_edma_core_ch_ignore_irq(chan))) + continue; + + val = dw_hdma_v0_core_status_int(chan); + if (FIELD_GET(HDMA_V0_STOP_INT_MASK, val)) { + dw_hdma_v0_core_clear_done_int(chan); + done(chan); + + ret = IRQ_HANDLED; + } + + if (FIELD_GET(HDMA_V0_ABORT_INT_MASK, val)) { + dw_hdma_v0_core_clear_abort_int(chan); + abort(chan); + + ret = IRQ_HANDLED; + } + } + + return ret; +} + +static void dw_hdma_v0_write_ll_data(struct dw_edma_chan *chan, int i, + u32 control, u32 size, u64 sar, u64 dar) +{ + ptrdiff_t ofs = i * sizeof(struct dw_hdma_v0_lli); + + if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) { + struct dw_hdma_v0_lli *lli = chan->ll_region.vaddr.mem + ofs; + + lli->transfer_size = size; + lli->sar.reg = sar; + lli->dar.reg = dar; + dma_wmb(); + lli->control = control; + } else { + struct dw_hdma_v0_lli __iomem *lli = chan->ll_region.vaddr.io + ofs; + + writel(size, &lli->transfer_size); + writeq(sar, &lli->sar.reg); + writeq(dar, &lli->dar.reg); + writel(control, &lli->control); + } +} + +static void dw_hdma_v0_write_ll_link(struct dw_edma_chan *chan, + int i, u32 control, u64 pointer) +{ + ptrdiff_t ofs = i * sizeof(struct dw_hdma_v0_lli); + + if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) { + struct dw_hdma_v0_llp *llp = chan->ll_region.vaddr.mem + ofs; + + llp->llp.reg = pointer; + dma_wmb(); + llp->control = control; + } else { + struct dw_hdma_v0_llp __iomem *llp = chan->ll_region.vaddr.io + ofs; + + writeq(pointer, &llp->llp.reg); + writel(control, &llp->control); + } +} + +static void dw_hdma_v0_core_ch_enable(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + u32 tmp; + + /* Enable engine */ + SET_CH_32(dw, chan->dir, chan->id, ch_en, BIT(0)); + /* Interrupt unmask - stop, abort */ + tmp = GET_CH_32(dw, chan->dir, chan->id, int_setup); + tmp = dw_hdma_v0_core_int_setup(chan, tmp); + SET_CH_32(dw, chan->dir, chan->id, int_setup, tmp); + /* Channel control */ + SET_CH_32(dw, chan->dir, chan->id, control1, HDMA_V0_LINKLIST_EN); + /* Linked list */ + /* llp is not aligned on 64bit -> keep 32bit accesses */ + SET_CH_32(dw, chan->dir, chan->id, llp.lsb, + lower_32_bits(chan->ll_region.paddr)); + SET_CH_32(dw, chan->dir, chan->id, llp.msb, + upper_32_bits(chan->ll_region.paddr)); + /* Set consumer cycle */ + SET_CH_32(dw, chan->dir, chan->id, cycle_sync, + HDMA_V0_CONSUMER_CYCLE_STAT | HDMA_V0_CONSUMER_CYCLE_BIT); +} + +static void dw_hdma_v0_sync_ll_data(struct dw_edma_chan *chan) +{ + /* + * In case of remote HDMA engine setup, the DW PCIe RP/EP internal + * configuration registers and application memory are normally accessed + * over different buses. Ensure LL-data reaches the memory before the + * doorbell register is toggled by issuing the dummy-read from the remote + * LL memory in a hope that the MRd TLP will return only after the + * last MWr TLP is completed + */ + if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL)) + readl(chan->ll_region.vaddr.io); +} + +static void dw_hdma_v0_core_non_ll_start(struct dw_edma_chan *chan, + struct dw_edma_burst *child) +{ + struct dw_edma *dw = chan->dw; + u32 val; + + SET_CH_32(dw, chan->dir, chan->id, ch_en, HDMA_V0_CH_EN); + + /* Source address */ + SET_CH_32(dw, chan->dir, chan->id, sar.lsb, + lower_32_bits(child->sar)); + SET_CH_32(dw, chan->dir, chan->id, sar.msb, + upper_32_bits(child->sar)); + + /* Destination address */ + SET_CH_32(dw, chan->dir, chan->id, dar.lsb, + lower_32_bits(child->dar)); + SET_CH_32(dw, chan->dir, chan->id, dar.msb, + upper_32_bits(child->dar)); + + /* Transfer size */ + SET_CH_32(dw, chan->dir, chan->id, transfer_size, child->sz); + + /* Interrupt setup */ + val = GET_CH_32(dw, chan->dir, chan->id, int_setup); + val = dw_hdma_v0_core_int_setup(chan, val); + SET_CH_32(dw, chan->dir, chan->id, int_setup, val); + + /* Channel control setup */ + val = GET_CH_32(dw, chan->dir, chan->id, control1); + val &= ~HDMA_V0_LINKLIST_EN; + SET_CH_32(dw, chan->dir, chan->id, control1, val); + + SET_CH_32(dw, chan->dir, chan->id, doorbell, + HDMA_V0_DOORBELL_START); +} + +static void dw_hdma_v0_core_ch_config(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + + /* MSI done addr - low, high */ + SET_CH_32(dw, chan->dir, chan->id, msi_stop.lsb, chan->msi.address_lo); + SET_CH_32(dw, chan->dir, chan->id, msi_stop.msb, chan->msi.address_hi); + /* MSI abort addr - low, high */ + SET_CH_32(dw, chan->dir, chan->id, msi_abort.lsb, chan->msi.address_lo); + SET_CH_32(dw, chan->dir, chan->id, msi_abort.msb, chan->msi.address_hi); + /* config MSI data */ + SET_CH_32(dw, chan->dir, chan->id, msi_msgdata, chan->msi.data); + /* Configure the requester function number used by outbound TLPs. */ + SET_CH_32(dw, chan->dir, chan->id, func_num, + FIELD_PREP(HDMA_V0_FUNC_NUM_PF_MASK, chan->func_no)); +} + +static void +dw_hdma_v0_core_ll_data(struct dw_edma_chan *chan, struct dw_edma_burst *burst, + u32 idx, bool cb, bool irq) +{ + u32 control = 0; + + if (cb) + control |= DW_HDMA_V0_CB; + + dw_hdma_v0_write_ll_data(chan, idx, control, burst->sz, burst->sar, + burst->dar); +} + +static void +dw_hdma_v0_core_ll_link(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr) +{ + u32 control = DW_HDMA_V0_LLP | DW_HDMA_V0_TCB; + + if (!cb) + control |= DW_HDMA_V0_CB; + + dw_hdma_v0_write_ll_link(chan, idx, control, addr); +} + +static void dw_hdma_v0_core_ch_doorbell(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + + dw_hdma_v0_sync_ll_data(chan); + + /* Doorbell */ + SET_CH_32(dw, chan->dir, chan->id, doorbell, HDMA_V0_DOORBELL_START); +} + +/* HDMA debugfs callbacks */ +static void dw_hdma_v0_core_debugfs_on(struct dw_edma *dw) +{ + dw_hdma_v0_debugfs_on(dw); +} + +static resource_size_t dw_hdma_v0_core_db_offset(struct dw_edma *dw) +{ + /* Implement once the correct offset is known. */ + return ~0; +} + +static const struct dw_edma_core_ops dw_hdma_v0_core = { + .off = dw_hdma_v0_core_off, + .quiesce = dw_hdma_v0_core_quiesce, + .ch_quiesce = dw_hdma_v0_core_ch_quiesce, + .ch_count = dw_hdma_v0_core_ch_count, + .ch_status = dw_hdma_v0_core_ch_status, + .handle_int = dw_hdma_v0_core_handle_int, + .non_ll_start = dw_hdma_v0_core_non_ll_start, + .ll_data = dw_hdma_v0_core_ll_data, + .ll_link = dw_hdma_v0_core_ll_link, + .ch_doorbell = dw_hdma_v0_core_ch_doorbell, + .ch_enable = dw_hdma_v0_core_ch_enable, + .ch_config = dw_hdma_v0_core_ch_config, + .debugfs_on = dw_hdma_v0_core_debugfs_on, + .db_offset = dw_hdma_v0_core_db_offset, +}; + +void dw_hdma_v0_core_register(struct dw_edma *dw) +{ + dw->core = &dw_hdma_v0_core; +} diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.h b/drivers/dma/dw-edma/dw-hdma-v0-core.h new file mode 100644 index 000000000..c373b4f0b --- /dev/null +++ b/drivers/dma/dw-edma/dw-hdma-v0-core.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2023 Cai Huoqing + * Synopsys DesignWare HDMA v0 core + * + * Author: Cai Huoqing <cai.huoqing@linux.dev> + */ + +#ifndef _DW_HDMA_V0_CORE_H +#define _DW_HDMA_V0_CORE_H + +#include <linux/dma/edma.h> + +/* HDMA core register */ +void dw_hdma_v0_core_register(struct dw_edma *dw); + +#endif /* _DW_HDMA_V0_CORE_H */ diff --git a/drivers/dma/dw-edma/dw-hdma-v0-debugfs.c b/drivers/dma/dw-edma/dw-hdma-v0-debugfs.c new file mode 100644 index 000000000..dcdc57fe9 --- /dev/null +++ b/drivers/dma/dw-edma/dw-hdma-v0-debugfs.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023 Cai Huoqing + * Synopsys DesignWare HDMA v0 debugfs + * + * Author: Cai Huoqing <cai.huoqing@linux.dev> + */ + +#include <linux/debugfs.h> +#include <linux/bitfield.h> + +#include "dw-hdma-v0-debugfs.h" +#include "dw-hdma-v0-regs.h" +#include "dw-edma-core.h" + +#define REGS_ADDR(dw, name) \ + ({ \ + struct dw_hdma_v0_regs __iomem *__regs = (dw)->chip->reg_base; \ + \ + (void __iomem *)&__regs->name; \ + }) + +#define REGS_CH_ADDR(dw, name, _dir, _ch) \ + ({ \ + struct dw_hdma_v0_ch_regs __iomem *__ch_regs; \ + \ + if (_dir == EDMA_DIR_READ) \ + __ch_regs = REGS_ADDR(dw, ch[_ch].rd); \ + else \ + __ch_regs = REGS_ADDR(dw, ch[_ch].wr); \ + \ + (void __iomem *)&__ch_regs->name; \ + }) + +#define CTX_REGISTER(dw, name, dir, ch) \ + {#name, REGS_CH_ADDR(dw, name, dir, ch)} + +#define WRITE_STR "write" +#define READ_STR "read" +#define CHANNEL_STR "channel" +#define REGISTERS_STR "registers" + +struct dw_hdma_debugfs_entry { + const char *name; + void __iomem *reg; +}; + +static int dw_hdma_debugfs_u32_get(void *data, u64 *val) +{ + struct dw_hdma_debugfs_entry *entry = data; + void __iomem *reg = entry->reg; + + *val = readl(reg); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(fops_x32, dw_hdma_debugfs_u32_get, NULL, "0x%08llx\n"); + +static void dw_hdma_debugfs_create_x32(struct dw_edma *dw, + const struct dw_hdma_debugfs_entry ini[], + int nr_entries, struct dentry *dent) +{ + struct dw_hdma_debugfs_entry *entries; + int i; + + entries = devm_kcalloc(dw->chip->dev, nr_entries, sizeof(*entries), + GFP_KERNEL); + if (!entries) + return; + + for (i = 0; i < nr_entries; i++) { + entries[i] = ini[i]; + + debugfs_create_file_unsafe(entries[i].name, 0444, dent, + &entries[i], &fops_x32); + } +} + +static void dw_hdma_debugfs_regs_ch(struct dw_edma *dw, enum dw_edma_dir dir, + u16 ch, struct dentry *dent) +{ + const struct dw_hdma_debugfs_entry debugfs_regs[] = { + CTX_REGISTER(dw, ch_en, dir, ch), + CTX_REGISTER(dw, doorbell, dir, ch), + CTX_REGISTER(dw, prefetch, dir, ch), + CTX_REGISTER(dw, handshake, dir, ch), + CTX_REGISTER(dw, llp.lsb, dir, ch), + CTX_REGISTER(dw, llp.msb, dir, ch), + CTX_REGISTER(dw, cycle_sync, dir, ch), + CTX_REGISTER(dw, transfer_size, dir, ch), + CTX_REGISTER(dw, sar.lsb, dir, ch), + CTX_REGISTER(dw, sar.msb, dir, ch), + CTX_REGISTER(dw, dar.lsb, dir, ch), + CTX_REGISTER(dw, dar.msb, dir, ch), + CTX_REGISTER(dw, watermark_en, dir, ch), + CTX_REGISTER(dw, control1, dir, ch), + CTX_REGISTER(dw, func_num, dir, ch), + CTX_REGISTER(dw, qos, dir, ch), + CTX_REGISTER(dw, ch_stat, dir, ch), + CTX_REGISTER(dw, int_stat, dir, ch), + CTX_REGISTER(dw, int_setup, dir, ch), + CTX_REGISTER(dw, int_clear, dir, ch), + CTX_REGISTER(dw, msi_stop.lsb, dir, ch), + CTX_REGISTER(dw, msi_stop.msb, dir, ch), + CTX_REGISTER(dw, msi_watermark.lsb, dir, ch), + CTX_REGISTER(dw, msi_watermark.msb, dir, ch), + CTX_REGISTER(dw, msi_abort.lsb, dir, ch), + CTX_REGISTER(dw, msi_abort.msb, dir, ch), + CTX_REGISTER(dw, msi_msgdata, dir, ch), + }; + int nr_entries = ARRAY_SIZE(debugfs_regs); + + dw_hdma_debugfs_create_x32(dw, debugfs_regs, nr_entries, dent); +} + +static void dw_hdma_debugfs_regs_wr(struct dw_edma *dw, struct dentry *dent) +{ + struct dentry *regs_dent, *ch_dent; + char name[32]; + int i; + + regs_dent = debugfs_create_dir(WRITE_STR, dent); + + for (i = 0; i < dw->wr_ch_cnt; i++) { + snprintf(name, sizeof(name), "%s:%d", CHANNEL_STR, i); + + ch_dent = debugfs_create_dir(name, regs_dent); + + dw_hdma_debugfs_regs_ch(dw, EDMA_DIR_WRITE, i, ch_dent); + } +} + +static void dw_hdma_debugfs_regs_rd(struct dw_edma *dw, struct dentry *dent) +{ + struct dentry *regs_dent, *ch_dent; + char name[32]; + int i; + + regs_dent = debugfs_create_dir(READ_STR, dent); + + for (i = 0; i < dw->rd_ch_cnt; i++) { + snprintf(name, sizeof(name), "%s:%d", CHANNEL_STR, i); + + ch_dent = debugfs_create_dir(name, regs_dent); + + dw_hdma_debugfs_regs_ch(dw, EDMA_DIR_READ, i, ch_dent); + } +} + +static void dw_hdma_debugfs_regs(struct dw_edma *dw) +{ + struct dentry *regs_dent; + + regs_dent = debugfs_create_dir(REGISTERS_STR, dw->dma.dbg_dev_root); + + dw_hdma_debugfs_regs_wr(dw, regs_dent); + dw_hdma_debugfs_regs_rd(dw, regs_dent); +} + +void dw_hdma_v0_debugfs_on(struct dw_edma *dw) +{ + if (!debugfs_initialized()) + return; + + debugfs_create_u32("mf", 0444, dw->dma.dbg_dev_root, &dw->chip->mf); + debugfs_create_u16("wr_ch_cnt", 0444, dw->dma.dbg_dev_root, &dw->wr_ch_cnt); + debugfs_create_u16("rd_ch_cnt", 0444, dw->dma.dbg_dev_root, &dw->rd_ch_cnt); + + dw_hdma_debugfs_regs(dw); +} diff --git a/drivers/dma/dw-edma/dw-hdma-v0-debugfs.h b/drivers/dma/dw-edma/dw-hdma-v0-debugfs.h new file mode 100644 index 000000000..e6842c837 --- /dev/null +++ b/drivers/dma/dw-edma/dw-hdma-v0-debugfs.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2023 Cai Huoqing + * Synopsys DesignWare HDMA v0 debugfs + * + * Author: Cai Huoqing <cai.huoqing@linux.dev> + */ + +#ifndef _DW_HDMA_V0_DEBUG_FS_H +#define _DW_HDMA_V0_DEBUG_FS_H + +#include <linux/dma/edma.h> + +#ifdef CONFIG_DEBUG_FS +void dw_hdma_v0_debugfs_on(struct dw_edma *dw); +#else +static inline void dw_hdma_v0_debugfs_on(struct dw_edma *dw) +{ +} +#endif /* CONFIG_DEBUG_FS */ + +#endif /* _DW_HDMA_V0_DEBUG_FS_H */ diff --git a/drivers/dma/dw-edma/dw-hdma-v0-regs.h b/drivers/dma/dw-edma/dw-hdma-v0-regs.h new file mode 100644 index 000000000..2124c162a --- /dev/null +++ b/drivers/dma/dw-edma/dw-hdma-v0-regs.h @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2023 Cai Huoqing + * Synopsys DesignWare HDMA v0 reg + * + * Author: Cai Huoqing <cai.huoqing@linux.dev> + */ + +#ifndef _DW_HDMA_V0_REGS_H +#define _DW_HDMA_V0_REGS_H + +#include <linux/dmaengine.h> + +#define HDMA_V0_MAX_NR_CH 64 +#define HDMA_V0_CH_EN BIT(0) +#define HDMA_V0_LOCAL_ABORT_INT_EN BIT(6) +#define HDMA_V0_REMOTE_ABORT_INT_EN BIT(5) +#define HDMA_V0_LOCAL_STOP_INT_EN BIT(4) +#define HDMA_V0_REMOTE_STOP_INT_EN BIT(3) +#define HDMA_V0_ABORT_INT_MASK BIT(2) +#define HDMA_V0_STOP_INT_MASK BIT(0) +#define HDMA_V0_LINKLIST_EN BIT(0) +#define HDMA_V0_CONSUMER_CYCLE_STAT BIT(1) +#define HDMA_V0_CONSUMER_CYCLE_BIT BIT(0) +#define HDMA_V0_DOORBELL_START BIT(0) +#define HDMA_V0_CH_STATUS_MASK GENMASK(1, 0) +#define HDMA_V0_FUNC_NUM_PF_MASK GENMASK(7, 0) + +struct dw_hdma_v0_ch_regs { + u32 ch_en; /* 0x0000 */ + u32 doorbell; /* 0x0004 */ + u32 prefetch; /* 0x0008 */ + u32 handshake; /* 0x000c */ + union { + u64 reg; /* 0x0010..0x0014 */ + struct { + u32 lsb; /* 0x0010 */ + u32 msb; /* 0x0014 */ + }; + } llp; + u32 cycle_sync; /* 0x0018 */ + u32 transfer_size; /* 0x001c */ + union { + u64 reg; /* 0x0020..0x0024 */ + struct { + u32 lsb; /* 0x0020 */ + u32 msb; /* 0x0024 */ + }; + } sar; + union { + u64 reg; /* 0x0028..0x002c */ + struct { + u32 lsb; /* 0x0028 */ + u32 msb; /* 0x002c */ + }; + } dar; + u32 watermark_en; /* 0x0030 */ + u32 control1; /* 0x0034 */ + u32 func_num; /* 0x0038 */ + u32 qos; /* 0x003c */ + u32 padding_1[16]; /* 0x0040..0x007c */ + u32 ch_stat; /* 0x0080 */ + u32 int_stat; /* 0x0084 */ + u32 int_setup; /* 0x0088 */ + u32 int_clear; /* 0x008c */ + union { + u64 reg; /* 0x0090..0x0094 */ + struct { + u32 lsb; /* 0x0090 */ + u32 msb; /* 0x0094 */ + }; + } msi_stop; + union { + u64 reg; /* 0x0098..0x009c */ + struct { + u32 lsb; /* 0x0098 */ + u32 msb; /* 0x009c */ + }; + } msi_watermark; + union { + u64 reg; /* 0x00a0..0x00a4 */ + struct { + u32 lsb; /* 0x00a0 */ + u32 msb; /* 0x00a4 */ + }; + } msi_abort; + u32 msi_msgdata; /* 0x00a8 */ + u32 padding_2[21]; /* 0x00ac..0x00fc */ +} __packed; + +struct dw_hdma_v0_ch { + struct dw_hdma_v0_ch_regs wr; /* 0x0000 */ + struct dw_hdma_v0_ch_regs rd; /* 0x0100 */ +} __packed; + +struct dw_hdma_v0_regs { + struct dw_hdma_v0_ch ch[HDMA_V0_MAX_NR_CH]; /* 0x0000..0x0fa8 */ +} __packed; + +struct dw_hdma_v0_lli { + u32 control; + u32 transfer_size; + union { + u64 reg; + struct { + u32 lsb; + u32 msb; + }; + } sar; + union { + u64 reg; + struct { + u32 lsb; + u32 msb; + }; + } dar; +} __packed; + +struct dw_hdma_v0_llp { + u32 control; + u32 reserved; + union { + u64 reg; + struct { + u32 lsb; + u32 msb; + }; + } llp; +} __packed; + +#endif /* _DW_HDMA_V0_REGS_H */ |
