Add a rudimentary MDMA driver for the Analog Devices SC5xx SoCs, primarily intended for use with and tested against the QSPI/OSPI IP included in the SoC.
Co-developed-by: Ian Roberts <ian.robe...@timesys.com> Signed-off-by: Ian Roberts <ian.robe...@timesys.com> Co-developed-by: Nathan Barrett-Morrison <nathan.morri...@timesys.com> Signed-off-by: Nathan Barrett-Morrison <nathan.morri...@timesys.com> Signed-off-by: Vasileios Bimpikas <vasileios.bimpi...@analog.com> Signed-off-by: Utsav Agarwal <utsav.agar...@analog.com> Signed-off-by: Arturs Artamonovs <arturs.artamon...@analog.com> Signed-off-by: Greg Malysa <malysag...@gmail.com> Signed-off-by: Oliver Gaskell <oliver.gask...@analog.com> --- Changes in v3: - replace readl/writel with ioread32 and iowrite32 and friends MAINTAINERS | 1 + drivers/dma/Kconfig | 7 ++ drivers/dma/Makefile | 1 + drivers/dma/adi_dma.c | 253 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 262 insertions(+) create mode 100644 drivers/dma/adi_dma.c diff --git a/MAINTAINERS b/MAINTAINERS index 0cefd888885..a9d05356763 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -632,6 +632,7 @@ F: doc/device-tree-bindings/clock/adi,sc5xx-clocks.yaml F: doc/device-tree-bindings/pinctrl/adi,adsp-pinctrl.yaml F: doc/device-tree-bindings/timer/adi,sc5xx-gptimer.yaml F: drivers/clk/adi/ +F: drivers/dma/adi_dma.c F: drivers/gpio/adp5588_gpio.c F: drivers/gpio/gpio-adi-adsp.c F: drivers/i2c/adi_i2c.c diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 3c64e894646..4b47be6b016 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -76,6 +76,13 @@ config XILINX_DPDMA this file is used as placeholder for driver. The main reason is to record compatible string and calling power domain driver. +config ADI_DMA + bool "ADI DMA driver" + depends on DMA && DMA_CHANNELS + help + Enable DMA support for Analog Devices SOCs, such as the SC5xx. + Currently this is a minimalistic driver tested against OSPI use only. + if APBH_DMA config APBH_DMA_BURST bool "Enable DMA BURST" diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index 48811eaaeb3..00d765864cd 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -13,5 +13,6 @@ obj-$(CONFIG_TI_KSNAV) += keystone_nav.o keystone_nav_cfg.o obj-$(CONFIG_TI_EDMA3) += ti-edma3.o obj-$(CONFIG_DMA_LPC32XX) += lpc32xx_dma.o obj-$(CONFIG_XILINX_DPDMA) += xilinx_dpdma.o +obj-$(CONFIG_ADI_DMA) += adi_dma.o obj-y += ti/ diff --git a/drivers/dma/adi_dma.c b/drivers/dma/adi_dma.c new file mode 100644 index 00000000000..28afe488db0 --- /dev/null +++ b/drivers/dma/adi_dma.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Analog Devices DMA controller driver + * + * (C) Copyright 2024 - Analog Devices, Inc. + * + * Written and/or maintained by Timesys Corporation + * + * Contact: Nathan Barrett-Morrison <nathan.morri...@timesys.com> + * Contact: Greg Malysa <greg.mal...@timesys.com> + * Contact: Ian Roberts <ian.robe...@timesys.com> + * + */ +#include <dm.h> +#include <dma.h> +#include <dma-uclass.h> +#include <dm/device_compat.h> +#include <linux/errno.h> +#include <linux/io.h> + +#define HAS_MDMA BIT(0) + +#define REG_ADDRSTART 0x04 +#define REG_CFG 0x08 +#define REG_XCNT 0x0C +#define REG_XMOD 0x10 +#define REG_STAT 0x30 + +#define BITP_DMA_CFG_MSIZE 8 +#define BITP_DMA_CFG_PSIZE 4 +#define BITM_DMA_CFG_WNR 0x00000002 +#define BITM_DMA_CFG_EN 0x00000001 +#define ENUM_DMA_CFG_XCNT_INT 0x00100000 + +#define BITP_DMA_STAT_PBWID 12 +#define BITP_DMA_STAT_ERRC 4 +#define BITM_DMA_STAT_PBWID 0x00003000 +#define BITM_DMA_STAT_ERRC 0x00000070 +#define BITM_DMA_STAT_PIRQ 0x00000004 +#define BITM_DMA_STAT_IRQERR 0x00000002 +#define BITM_DMA_STAT_IRQDONE 0x00000001 + +#define DMA_MDMA_SRC_DEFAULT_CONFIG(psize, msize) \ + (BITM_DMA_CFG_EN | ((psize) << BITP_DMA_CFG_PSIZE) | ((msize) << BITP_DMA_CFG_MSIZE)) +#define DMA_MDMA_DST_DEFAULT_CONFIG(psize, msize) \ + (BITM_DMA_CFG_EN | BITM_DMA_CFG_WNR | ENUM_DMA_CFG_XCNT_INT | \ + ((psize) << BITP_DMA_CFG_PSIZE) | ((msize) << BITP_DMA_CFG_MSIZE)) + +struct adi_dma_channel { + int id; + struct adi_dma *dma; + void __iomem *iosrc; + void __iomem *iodest; +}; + +struct adi_dma { + struct udevice *dev; + struct adi_dma_channel channels[1]; + void __iomem *ioaddr; + unsigned long hw_cfg; +}; + +static const struct udevice_id dma_dt_ids[] = { + { .compatible = "adi,mdma-controller", .data = HAS_MDMA }, + { } +}; + +static u8 adi_dma_get_msize(u32 n_bytecount, u32 n_address) +{ + /* Calculate MSIZE, PSIZE, XCNT and XMOD */ + u8 n_msize = 0; + u32 n_value = n_bytecount | n_address; + u32 n_mask = 0x1; + + for (n_msize = 0; n_msize < 5; n_msize++, n_mask <<= 1) { + if ((n_value & n_mask) == n_mask) + break; + } + + return n_msize; +} + +static int adi_dma_get_ch_error(void __iomem *ch) +{ + u32 cause = (ioread32(ch + REG_STAT) & BITM_DMA_STAT_ERRC) >> + BITP_DMA_STAT_ERRC; + switch (cause) { + case 0: + return -EINVAL; + case 1: + return -EBUSY; + case 2: + return -EFAULT; + case 3: + fallthrough; + case 5: + fallthrough; + case 6: + fallthrough; + default: + return -EIO; + } +} + +static int adi_mdma_transfer(struct udevice *dev, int direction, + dma_addr_t dst, dma_addr_t src, size_t len) +{ + struct adi_dma *priv = dev_get_priv(dev); + void __iomem *chsrc = priv->channels[0].iosrc; + void __iomem *chdst = priv->channels[0].iodest; + + int result = 0; + u32 reg; + u32 bytecount = len; + + u8 n_srcmsize; + u8 n_dstmsize; + u8 n_srcpsize; + u8 n_dstpsize; + u8 n_psize; + u32 srcconfig; + u32 dstconfig; + u8 srcpsizemax = (ioread32(chsrc + REG_STAT) & BITM_DMA_STAT_PBWID) >> + BITP_DMA_STAT_PBWID; + u8 dstpsizemax = (ioread32(chdst + REG_STAT) & BITM_DMA_STAT_PBWID) >> + BITP_DMA_STAT_PBWID; + + const u32 CLRSTAT = (BITM_DMA_STAT_IRQDONE | BITM_DMA_STAT_IRQERR | + BITM_DMA_STAT_PIRQ); + + if (len == 0) + return -EINVAL; + + /* Clear DMA status */ + iowrite32(CLRSTAT, chsrc + REG_STAT); + iowrite32(CLRSTAT, chdst + REG_STAT); + + /* Calculate MSIZE, PSIZE, XCNT and XMOD */ + n_srcmsize = adi_dma_get_msize(bytecount, src); + n_dstmsize = adi_dma_get_msize(bytecount, dst); + n_srcpsize = min(n_srcmsize, srcpsizemax); + n_dstpsize = min(n_dstmsize, dstpsizemax); + n_psize = min(n_srcpsize, n_dstpsize); + + srcconfig = DMA_MDMA_SRC_DEFAULT_CONFIG(n_psize, n_srcmsize); + dstconfig = DMA_MDMA_DST_DEFAULT_CONFIG(n_psize, n_dstmsize); + + /* Load the DMA descriptors */ + iowrite32(src, chsrc + REG_ADDRSTART); + iowrite32(bytecount >> n_srcmsize, chsrc + REG_XCNT); + iowrite32(1 << n_srcmsize, chsrc + REG_XMOD); + iowrite32(dst, chdst + REG_ADDRSTART); + iowrite32(bytecount >> n_dstmsize, chdst + REG_XCNT); + iowrite32(1 << n_dstmsize, chdst + REG_XMOD); + + iowrite32(dstconfig, chdst + REG_CFG); + iowrite32(srcconfig, chsrc + REG_CFG); + + /* Wait for DMA to complete while checking for a DMA error */ + do { + reg = ioread32(chsrc + REG_STAT); + if ((reg & BITM_DMA_STAT_IRQERR) == BITM_DMA_STAT_IRQERR) { + result = adi_dma_get_ch_error(chsrc); + break; + } + reg = ioread32(chdst + REG_STAT); + if ((reg & BITM_DMA_STAT_IRQERR) == BITM_DMA_STAT_IRQERR) { + result = adi_dma_get_ch_error(chdst); + break; + } + } while ((reg & BITM_DMA_STAT_IRQDONE) == 0); + + clrbits_32(chsrc + REG_CFG, 1); + clrbits_32(chdst + REG_CFG, 1); + + return result; +} + +static int adi_dma_init_channel(struct adi_dma *dma, + struct adi_dma_channel *channel, ofnode node) +{ + u32 offset; + + if (ofnode_read_u32(node, "adi,id", &channel->id)) { + dev_err(dma->dev, "Missing adi,id for channel %s\n", + ofnode_get_name(node)); + return -ENOENT; + } + + if (ofnode_read_u32(node, "adi,src-offset", &offset)) { + dev_err(dma->dev, "Missing adi,src-offset for channel %s\n", + ofnode_get_name(node)); + return -ENOENT; + } + + channel->iosrc = dma->ioaddr + offset; + channel->dma = dma; + + if (dma->hw_cfg & HAS_MDMA) { + if (ofnode_read_u32(node, "adi,dest-offset", &offset)) { + dev_err(dma->dev, + "Missing adi,dest-offset for channel %s\n", + ofnode_get_name(node)); + return -ENOENT; + } + channel->iodest = dma->ioaddr + offset; + } + + return 0; +} + +static int adi_dma_probe(struct udevice *dev) +{ + struct dma_dev_priv *uc_priv = dev_get_uclass_priv(dev); + struct adi_dma *priv = dev_get_priv(dev); + ofnode node, child; + + priv->hw_cfg = dev_get_driver_data(dev); + if (priv->hw_cfg & HAS_MDMA) + uc_priv->supported = DMA_SUPPORTS_MEM_TO_MEM; + + priv->ioaddr = dev_remap_addr(dev); + if (!priv->ioaddr) + return -EINVAL; + + node = dev_read_first_subnode(dev); + if (!ofnode_valid(node)) { + dev_err(dev, + "Error: device tree DMA channel config missing!\n"); + return -ENODEV; + } + + node = dev_ofnode(dev); + ofnode_for_each_subnode(child, node) { + adi_dma_init_channel(priv, priv->channels, child); + break; //Only 1 channel supported for now + } + + return 0; +} + +static const struct dma_ops adi_dma_ops = { + .transfer = adi_mdma_transfer, +}; + +U_BOOT_DRIVER(adi_dma) = { + .name = "adi_dma", + .id = UCLASS_DMA, + .of_match = dma_dt_ids, + .ops = &adi_dma_ops, + .probe = adi_dma_probe, + .priv_auto = sizeof(struct adi_dma), +}; -- 2.45.2