// 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 * Contact: Greg Malysa * Contact: Ian Roberts * */ #include #include #include #include #include #include #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), };