// SPDX-License-Identifier: GPL-2.0+ /* * Direct Memory Access ArtInChip driver * * Copyright (C) 2020 ArtInChip Technology Co.,Ltd. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define AIC_DMA_PHY_CH_MASK 0xFFFF0000 #define AIC_DMA_PHY_CH_OFF 16 #define AIC_DMA_PORT_MASK 0x0000FFFF #define AIC_DMA_PORT_OFF 0 #define AIC_DMA_PHY_CH_CNT 8 #define AIC_DMA_PORT_CNT 24 #define AIC_DMA_BUF_SIZE 1024 #define AIC_DMA_TIMEOUT_US 10000000 #define DMA_BURST_1 (0) #define DMA_BURST_4 (1) #define DMA_BURST_8 (2) #define DMA_BURST_16 (3) #define DMA_BIT_WIDTH_8 (0) #define DMA_BIT_WIDTH_16 (1) #define DMA_BIT_WIDTH_32 (2) #define DMA_BIT_WIDTH_64 (3) #define LINEAR_MODE 0 #define IO_MODE 1 #define DMA_DESC_NULL ((u32)0xFFFFF800) #define DMA_REG_IRQ_EN(ud) (ud->base + 0x00) #define DMA_REG_IRQ_STS(ud) (ud->base + 0x10) #define DMA_REG_STATUS(ud) (ud->base + 0x30) #define CH_OFFSET (0x40) #define CH_BASE(ud) (ud->base + 0x100) #define DMA_REG_CH_EN(ud, ch) (CH_BASE(ud) + (ch) * CH_OFFSET + 0x00) #define DMA_REG_CH_PAUSE(ud, ch) (CH_BASE(ud) + (ch) * CH_OFFSET + 0x04) #define DMA_REG_CH_DESC_ADDR(ud, ch) (CH_BASE(ud) + (ch) * CH_OFFSET + 0x08) #define DMA_REG_CH_CUR_CFG(ud, ch) (CH_BASE(ud) + (ch) * CH_OFFSET + 0x0c) #define DMA_REG_CH_CUR_SADDR(ud, ch) (CH_BASE(ud) + (ch) * CH_OFFSET + 0x10) #define DMA_REG_CH_CUR_DADDR(ud, ch) (CH_BASE(ud) + (ch) * CH_OFFSET + 0x14) #define DMA_REG_CH_BCNT_LEFT(ud, ch) (CH_BASE(ud) + (ch) * CH_OFFSET + 0x18) #define DMA_REG_CH_PARAM(ud, ch) (CH_BASE(ud) + (ch) * CH_OFFSET + 0x1c) #define DMA_REG_CH_MODE(ud, ch) (CH_BASE(ud) + (ch) * CH_OFFSET + 0x28) #define DMA_REG_CH_FORMER(ud, ch) (CH_BASE(ud) + (ch) * CH_OFFSET + 0x2c) #define DMA_REG_CH_PACKAGE(ud, ch) (CH_BASE(ud) + (ch) * CH_OFFSET + 0x30) #define SRC_WAIT_DST_WAIT_MODE (0 << 2) #define SRC_HANDSHAKE_DST_WAIT_MODE (1 << 2) #define SRC_WAIT_DST_HANDSHAKE_MODE (2 << 2) #define SRC_HANDSHAKE_DST_HANDSHAKE_MODE (3 << 2) struct aic_dma_dev; struct dma_cfg { u16 port : 5; /* bit[4:0] */ u16 res0 : 1; u16 burst_size : 2; /* bit[7:6] */ u16 addr_mode : 1; /* bit[8] */ u16 bit_width : 2; /* bit[10:9] */ u16 res1 : 5; }; /* DMA descriptor */ struct aic_dma_desc { struct dma_cfg src_port; struct dma_cfg sink_port; u32 src_addr; u32 sink_addr; u32 byte_cnt; u32 wait_cyc: 8; u32 res0: 24; u32 next; }; #define DMA_CFG(p, burst, mode, width) \ { \ .port = p, \ .res0 = 0, \ .burst_size = burst, \ .addr_mode = mode, \ .bit_width = width, \ .res1 = 0, \ } static struct dma_cfg dma_port_cfg[] = { DMA_CFG(0, DMA_BURST_16, LINEAR_MODE, DMA_BIT_WIDTH_32), /* SRAM */ DMA_CFG(1, DMA_BURST_8, LINEAR_MODE, DMA_BIT_WIDTH_32), /* DRAM */ DMA_CFG(2, DMA_BURST_16, IO_MODE, DMA_BIT_WIDTH_32), /* UNDEFINED */ DMA_CFG(3, DMA_BURST_16, IO_MODE, DMA_BIT_WIDTH_32), /* UNDEFINED */ DMA_CFG(4, DMA_BURST_16, IO_MODE, DMA_BIT_WIDTH_32), /* UNDEFINED */ DMA_CFG(5, DMA_BURST_16, IO_MODE, DMA_BIT_WIDTH_32), /* UNDEFINED */ DMA_CFG(6, DMA_BURST_16, IO_MODE, DMA_BIT_WIDTH_32), /* UNDEFINED */ DMA_CFG(7, DMA_BURST_16, IO_MODE, DMA_BIT_WIDTH_32), /* UNDEFINED */ DMA_CFG(8, DMA_BURST_16, IO_MODE, DMA_BIT_WIDTH_32), /* UNDEFINED */ DMA_CFG(9, DMA_BURST_16, IO_MODE, DMA_BIT_WIDTH_32), /* UNDEFINED */ DMA_CFG(10, DMA_BURST_8, IO_MODE, DMA_BIT_WIDTH_32), /* SPI0 */ DMA_CFG(11, DMA_BURST_8, IO_MODE, DMA_BIT_WIDTH_32), /* SPI1 */ #ifndef CONFIG_SPL_BUILD DMA_CFG(12, DMA_BURST_1, IO_MODE, DMA_BIT_WIDTH_32), /* I2S0 */ DMA_CFG(13, DMA_BURST_1, IO_MODE, DMA_BIT_WIDTH_32), /* I2S1 */ DMA_CFG(14, DMA_BURST_1, IO_MODE, DMA_BIT_WIDTH_32), /* DMIC */ DMA_CFG(15, DMA_BURST_1, IO_MODE, DMA_BIT_WIDTH_32), /* UNDEFINED */ DMA_CFG(16, DMA_BURST_1, IO_MODE, DMA_BIT_WIDTH_32), /* UART0 */ DMA_CFG(17, DMA_BURST_1, IO_MODE, DMA_BIT_WIDTH_32), /* UART1 */ DMA_CFG(18, DMA_BURST_1, IO_MODE, DMA_BIT_WIDTH_32), /* UART2 */ DMA_CFG(19, DMA_BURST_1, IO_MODE, DMA_BIT_WIDTH_32), /* UART3 */ DMA_CFG(20, DMA_BURST_1, IO_MODE, DMA_BIT_WIDTH_32), /* UART4 */ DMA_CFG(21, DMA_BURST_1, IO_MODE, DMA_BIT_WIDTH_32), /* UART5 */ DMA_CFG(22, DMA_BURST_1, IO_MODE, DMA_BIT_WIDTH_32), /* UART6 */ DMA_CFG(23, DMA_BURST_1, IO_MODE, DMA_BIT_WIDTH_32), /* UART7 */ #endif }; struct aic_dma_dev { struct device *dev; void __iomem *base; struct reset_ctl reset; struct clk clk; u32 phy_ch; uchar *rx_buf; size_t rx_len; }; static int aic_dma_phy_request(struct aic_dma_dev *ud) { int i; if ((ud->phy_ch & 0xFF) == 0xFF) return -EBUSY; for (i = 0; i < AIC_DMA_PHY_CH_CNT; i++) { if ((ud->phy_ch & (1 << i)) == 0) { ud->phy_ch |= (1 << i); return i; } } return -EBUSY; } static int aic_dma_phy_release(struct aic_dma_dev *ud, int id) { ud->phy_ch &= ~(1 << id); return 0; } static int aic_dma_request(struct dma *dma) { struct aic_dma_dev *ud = dev_get_priv(dma->dev); int phy_ch = 0; if ((dma->id & AIC_DMA_PHY_CH_MASK) != 0) return -EINVAL; if (dma->id >= AIC_DMA_PORT_CNT) return -EINVAL; phy_ch = aic_dma_phy_request(ud); if (phy_ch < 0) return -EBUSY; /* * DMA ID in DTS is the DMA port id for AIC chip, driver still need to * allocate dma channel for it when user program request one DMA. * * Here set the physical dma channel to the upper bit field of the DMA * ID. */ dma->id |= (phy_ch << AIC_DMA_PHY_CH_OFF); debug("%s(dma id=0x%lx)\n", __func__, dma->id); return 0; } static int aic_dma_free(struct dma *dma) { struct aic_dma_dev *ud = dev_get_priv(dma->dev); int port, phy_ch; port = (dma->id & AIC_DMA_PORT_MASK); phy_ch = (dma->id >> AIC_DMA_PHY_CH_OFF); if (port >= AIC_DMA_PORT_CNT) return -EINVAL; debug("%s(dma id=0x%lx)\n", __func__, dma->id); aic_dma_phy_release(ud, phy_ch); ud->rx_buf = NULL; ud->rx_len = 0; return 0; } static u32 aic_dma_get_mode(struct aic_dma_desc *desc) { u16 saddr_mode, daddr_mode; saddr_mode = desc->src_port.addr_mode; daddr_mode = desc->sink_port.addr_mode; if ((saddr_mode == LINEAR_MODE) && (daddr_mode == LINEAR_MODE)) return SRC_WAIT_DST_WAIT_MODE; if ((saddr_mode == LINEAR_MODE) && (daddr_mode == IO_MODE)) return SRC_WAIT_DST_HANDSHAKE_MODE; if ((saddr_mode == IO_MODE) && (daddr_mode == LINEAR_MODE)) return SRC_HANDSHAKE_DST_WAIT_MODE; if ((saddr_mode == IO_MODE) && (daddr_mode == IO_MODE)) return SRC_HANDSHAKE_DST_HANDSHAKE_MODE; return SRC_WAIT_DST_WAIT_MODE; } static void aic_dma_start(struct aic_dma_dev *ud, int phy_ch, struct aic_dma_desc *desc) { u32 val; writel(0, DMA_REG_CH_EN(ud, phy_ch)); val = readl(DMA_REG_IRQ_STS(ud)); val |= 0xF << (phy_ch * 4); writel(val, DMA_REG_IRQ_STS(ud)); do { writel((unsigned long)desc, DMA_REG_CH_DESC_ADDR(ud, phy_ch)); } while ((unsigned long)desc != readl(DMA_REG_CH_DESC_ADDR(ud, phy_ch))); val = aic_dma_get_mode(desc); writel(val, DMA_REG_CH_MODE(ud, phy_ch)); writel(1, DMA_REG_CH_EN(ud, phy_ch)); writel(0, DMA_REG_CH_PAUSE(ud, phy_ch)); } static void aic_dma_stop(struct aic_dma_dev *ud, int phy_ch) { u32 val; val = readl(DMA_REG_CH_EN(ud, phy_ch)); if (val == 0) return; writel(1, DMA_REG_CH_PAUSE(ud, phy_ch)); writel(0, DMA_REG_CH_EN(ud, phy_ch)); writel(0, DMA_REG_CH_PAUSE(ud, phy_ch)); writel(DMA_DESC_NULL, DMA_REG_CH_DESC_ADDR(ud, phy_ch)); val = readl(DMA_REG_IRQ_STS(ud)); val |= 0xF << (phy_ch * 4); writel(val, DMA_REG_IRQ_STS(ud)); } static int aic_dma_enable(struct dma *dma) { int port; port = (dma->id & AIC_DMA_PORT_MASK); if (port >= AIC_DMA_PORT_CNT) return -EINVAL; /* Do nothing */ return 0; } static int aic_dma_disable(struct dma *dma) { struct aic_dma_dev *ud = dev_get_priv(dma->dev); int port, phy_ch; port = (dma->id & AIC_DMA_PORT_MASK); phy_ch = (dma->id >> AIC_DMA_PHY_CH_OFF); if (port >= AIC_DMA_PORT_CNT) return -EINVAL; aic_dma_stop(ud, phy_ch); return 0; } #define pr_reg(addr) debug("0x%lx: 0x%x\n", (unsigned long)addr, (u32)readl(addr)) void aic_dma_dump(struct aic_dma_dev *ud) { int i; debug("DMA Regs:\n"); pr_reg(DMA_REG_IRQ_EN(ud)); pr_reg(DMA_REG_IRQ_STS(ud)); pr_reg(DMA_REG_STATUS(ud)); for (i = 0; i < 8; i++) { pr_reg(DMA_REG_CH_EN(ud, i)); pr_reg(DMA_REG_CH_PAUSE(ud, i)); pr_reg(DMA_REG_CH_DESC_ADDR(ud, i)); pr_reg(DMA_REG_CH_CUR_CFG(ud, i)); pr_reg(DMA_REG_CH_CUR_SADDR(ud, i)); pr_reg(DMA_REG_CH_CUR_DADDR(ud, i)); pr_reg(DMA_REG_CH_BCNT_LEFT(ud, i)); pr_reg(DMA_REG_CH_PARAM(ud, i)); pr_reg(DMA_REG_CH_MODE(ud, i)); pr_reg(DMA_REG_CH_FORMER(ud, i)); pr_reg(DMA_REG_CH_PACKAGE(ud, i)); } } static void aic_dma_desc_flush(struct aic_dma_desc *desc) { u32 start, stop; start = (unsigned long)desc; stop = ALIGN(start + sizeof(struct aic_dma_desc), ARCH_DMA_MINALIGN); flush_dcache_range(start, stop); } #ifndef CONFIG_SPL_BUILD static int aic_dma_send(struct dma *dma, void *src, size_t len, void *port_addr) { struct aic_dma_dev *ud = dev_get_priv(dma->dev); struct aic_dma_desc *desc; s32 port, phy_ch, ret; u32 val, start, stop; port = (dma->id & AIC_DMA_PORT_MASK); phy_ch = (dma->id >> AIC_DMA_PHY_CH_OFF); if (port >= AIC_DMA_PORT_CNT) return -EINVAL; if (!src || !port_addr) return -EINVAL; debug("send port :%d, phy ch %d\n", port, phy_ch); desc = malloc_cache_aligned(sizeof(struct aic_dma_desc)); if (desc == NULL) return -ENOMEM; memset(desc, 0, sizeof(struct aic_dma_desc)); start = (unsigned long)src; stop = ALIGN(start + len, ARCH_DMA_MINALIGN); flush_dcache_range(start, stop); /* MEM to DEV */ desc->src_port = dma_port_cfg[1]; /* Source port DRAM */ desc->sink_port = dma_port_cfg[port]; desc->wait_cyc = 0x10; desc->src_addr = (unsigned long)src; desc->sink_addr = (unsigned long)port_addr; /* IO FIFO Address */ desc->byte_cnt = len; desc->next = DMA_DESC_NULL; aic_dma_desc_flush(desc); aic_dma_start(ud, phy_ch, desc); ret = readl_poll_timeout(DMA_REG_STATUS(ud), val, ((val & (1 << phy_ch)) == 0), AIC_DMA_TIMEOUT_US); if (desc) free(desc); if (ret) return -EFAULT; return len; } #endif static int aic_dma_prepare_rcv_buf(struct dma *dma, void *dst, size_t size) { struct aic_dma_dev *ud = dev_get_priv(dma->dev); ud->rx_buf = dst; ud->rx_len = size; debug("%s, rx buf 0x%lx, len %ld\n", __func__, (unsigned long)dst, (unsigned long)size); return 0; } static int aic_dma_receive(struct dma *dma, void **dst, void *port_addr) { struct aic_dma_dev *ud = dev_get_priv(dma->dev); struct aic_dma_desc *desc; s32 port, phy_ch, ret; u32 val, start, stop; port = (dma->id & AIC_DMA_PORT_MASK); phy_ch = (dma->id >> AIC_DMA_PHY_CH_OFF); debug("recv port :%d, phy ch %d\n", port, phy_ch); if (port >= AIC_DMA_PORT_CNT) return -EINVAL; if (!dst || !port_addr) return -EINVAL; if (!ud->rx_len || !ud->rx_buf) return 0; desc = malloc_cache_aligned(sizeof(struct aic_dma_desc)); if (desc == NULL) return -ENOMEM; memset(desc, 0, sizeof(struct aic_dma_desc)); /* DEV to MEM */ desc->src_port = dma_port_cfg[port]; desc->sink_port = dma_port_cfg[1]; desc->wait_cyc = 0x40; desc->src_addr = (unsigned long)port_addr; /* IO FIFO Address */ desc->sink_addr = (unsigned long)ud->rx_buf; desc->byte_cnt = (u32)ud->rx_len; desc->next = DMA_DESC_NULL; aic_dma_desc_flush(desc); start = (unsigned long)ud->rx_buf; stop = ALIGN(start + ud->rx_len, ARCH_DMA_MINALIGN); /* Invalidate the area, so no writeback into the RAM races with DMA */ invalidate_dcache_range(start, stop); aic_dma_start(ud, phy_ch, desc); ret = readl_poll_timeout(DMA_REG_STATUS(ud), val, ((val & (1 << phy_ch)) == 0), AIC_DMA_TIMEOUT_US); if (ret) { ret = -EIO; goto out; } invalidate_dcache_range(start, stop); *dst = ud->rx_buf; ret = (s32)ud->rx_len; ud->rx_buf = NULL; ud->rx_len = 0; out: if (desc) free(desc); return ret; } #ifndef CONFIG_SPL_BUILD static int aic_dma_transfer(struct udevice *dev, int direction, void *dst, void *src, size_t len) { struct aic_dma_dev *ud = dev_get_priv(dev); struct aic_dma_desc *desc; int phy_ch, ret; u32 val, start, stop; if (direction != DMA_MEM_TO_MEM) return -EINVAL; desc = malloc_cache_aligned(sizeof(struct aic_dma_desc)); if (desc == NULL) return -ENOMEM; memset(desc, 0, sizeof(struct aic_dma_desc)); start = (unsigned long)src; stop = ALIGN(start + len, ARCH_DMA_MINALIGN); flush_dcache_range(start, stop); phy_ch = aic_dma_phy_request(ud); if (phy_ch < 0) { ret = -EBUSY; goto out; } /* MEM to MEM */ desc->src_port = dma_port_cfg[1]; desc->sink_port = dma_port_cfg[1]; desc->wait_cyc = 0x40; desc->src_addr = (unsigned long)src; desc->sink_addr = (unsigned long)dst; desc->byte_cnt = len; desc->next = DMA_DESC_NULL; aic_dma_desc_flush(desc); aic_dma_start(ud, phy_ch, desc); ret = readl_poll_timeout(DMA_REG_STATUS(ud), val, ((val & (1 << phy_ch)) == 0), AIC_DMA_TIMEOUT_US); if (ret) { aic_dma_stop(ud, phy_ch); ret = -EFAULT; goto out; } aic_dma_stop(ud, phy_ch); start = (unsigned long)dst; stop = ALIGN(start + len, ARCH_DMA_MINALIGN); invalidate_dcache_range(start, stop); out: if (desc) free(desc); return ret; } #endif static const struct dma_ops aic_dma_ops = { #ifndef CONFIG_SPL_BUILD /* SPL don't support dma memory copy, for code size saving */ .transfer = aic_dma_transfer, #endif .request = aic_dma_request, .rfree = aic_dma_free, .enable = aic_dma_enable, .disable = aic_dma_disable, #ifndef CONFIG_SPL_BUILD /* SPL don't support dma send, for code size saving */ .send = aic_dma_send, #endif .receive = aic_dma_receive, .prepare_rcv_buf = aic_dma_prepare_rcv_buf, }; static int aic_dma_probe(struct udevice *dev) { struct dma_dev_priv *uc_priv = dev_get_uclass_priv(dev); struct aic_dma_dev *ud = dev_get_priv(dev); int i, ret = 0; uc_priv->supported = DMA_SUPPORTS_MEM_TO_MEM | DMA_SUPPORTS_MEM_TO_DEV | DMA_SUPPORTS_DEV_TO_MEM; ud->base = dev_read_addr_ptr(dev); #if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_CLK_ARTINCHIP) ret = clk_get_by_index(dev, 0, &ud->clk); if (ret < 0) { dev_err(dev, "failed to get clock\n"); return ret; } ret = clk_enable(&ud->clk); if (ret < 0) { pr_err("error enabling clock\n"); return ret; } ret = reset_get_by_index(dev, 0, &ud->reset); if (ret && ret != -ENOENT) { dev_err(dev, "failed to get reset\n"); return ret; } ret = reset_deassert(&ud->reset); if (ret < 0) { pr_err("error deasserting reset %d\n", i); return ret; } #endif ud->rx_buf = NULL; ud->rx_len = 0; for (i = 0; i < AIC_DMA_PHY_CH_CNT; i++) { writel(1, DMA_REG_CH_PAUSE(ud, i)); writel(0x0, DMA_REG_CH_EN(ud, i)); } writel(0x0, DMA_REG_IRQ_EN(ud)); writel(0xFFFFFFFF, DMA_REG_IRQ_STS(ud)); writel(0xFFFFFFFF, DMA_REG_IRQ_EN(ud)); pr_info("%s done.\n", __func__); return ret; } static const struct udevice_id aic_dma_ids[] = { { .compatible = "artinchip,aic-dma-v0.1" }, { .compatible = "artinchip,aic-dma-v1.0" }, { } }; U_BOOT_DRIVER(aic_dma) = { .name = "artinchip_aic_dma", .id = UCLASS_DMA, .of_match = aic_dma_ids, .ops = &aic_dma_ops, .probe = aic_dma_probe, .priv_auto = sizeof(struct aic_dma_dev), };