[PATCH 2/4] soc: aspeed: Add UART DMA support

Chia-Wei Wang chiawei_wang at aspeedtech.com
Fri Feb 10 18:26:41 AEDT 2023


This driver provides DMA support for AST26xx UART and VUART
devices. It is useful to offload CPU overhead while using
UART/VUART for binary file transfer.

Signed-off-by: Chia-Wei Wang <chiawei_wang at aspeedtech.com>
---
 drivers/soc/aspeed/Kconfig             |   9 +
 drivers/soc/aspeed/Makefile            |   1 +
 drivers/soc/aspeed/aspeed-udma.c       | 447 +++++++++++++++++++++++++
 include/linux/soc/aspeed/aspeed-udma.h |  34 ++
 4 files changed, 491 insertions(+)
 create mode 100644 drivers/soc/aspeed/aspeed-udma.c
 create mode 100644 include/linux/soc/aspeed/aspeed-udma.h

diff --git a/drivers/soc/aspeed/Kconfig b/drivers/soc/aspeed/Kconfig
index f579ee0b5afa..c3bb238bea75 100644
--- a/drivers/soc/aspeed/Kconfig
+++ b/drivers/soc/aspeed/Kconfig
@@ -52,6 +52,15 @@ config ASPEED_SOCINFO
 	help
 	  Say yes to support decoding of ASPEED BMC information.
 
+config ASPEED_UDMA
+	tristate "Aspeed UDMA Engine Driver"
+	default ARCH_ASPEED
+	help
+	  Enable support for the Aspeed UDMA Engine found on the
+	  Aspeed AST26xx SOCs. The UDMA engine provides UART DMA
+	  operations between the memory buffer and UART/VUART
+	  FIFO data.
+
 endmenu
 
 endif
diff --git a/drivers/soc/aspeed/Makefile b/drivers/soc/aspeed/Makefile
index b35d74592964..5aeabafee1d4 100644
--- a/drivers/soc/aspeed/Makefile
+++ b/drivers/soc/aspeed/Makefile
@@ -4,3 +4,4 @@ obj-$(CONFIG_ASPEED_LPC_SNOOP)		+= aspeed-lpc-snoop.o
 obj-$(CONFIG_ASPEED_UART_ROUTING)	+= aspeed-uart-routing.o
 obj-$(CONFIG_ASPEED_P2A_CTRL)		+= aspeed-p2a-ctrl.o
 obj-$(CONFIG_ASPEED_SOCINFO)		+= aspeed-socinfo.o
+obj-$(CONFIG_ASPEED_UDMA)		+= aspeed-udma.o
diff --git a/drivers/soc/aspeed/aspeed-udma.c b/drivers/soc/aspeed/aspeed-udma.c
new file mode 100644
index 000000000000..95898fbd6ed8
--- /dev/null
+++ b/drivers/soc/aspeed/aspeed-udma.c
@@ -0,0 +1,447 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) Aspeed Technology Inc.
+ */
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/spinlock.h>
+#include <linux/soc/aspeed/aspeed-udma.h>
+#include <linux/delay.h>
+
+#define DEVICE_NAME "aspeed-udma"
+
+/* UART DMA registers offset */
+#define UDMA_TX_DMA_EN		0x000
+#define UDMA_RX_DMA_EN		0x004
+#define UDMA_TIMEOUT_TIMER	0x00c
+#define UDMA_TX_DMA_RST		0x020
+#define UDMA_RX_DMA_RST		0x024
+#define UDMA_TX_DMA_INT_EN	0x030
+#define UDMA_TX_DMA_INT_STAT	0x034
+#define UDMA_RX_DMA_INT_EN	0x038
+#define UDMA_RX_DMA_INT_STAT	0x03c
+
+#define UDMA_CHX_OFF(x)		((x) * 0x20)
+#define UDMA_CHX_TX_RD_PTR(x)	(0x040 + UDMA_CHX_OFF(x))
+#define UDMA_CHX_TX_WR_PTR(x)	(0x044 + UDMA_CHX_OFF(x))
+#define UDMA_CHX_TX_BUF_BASE(x)	(0x048 + UDMA_CHX_OFF(x))
+#define UDMA_CHX_TX_CTRL(x)	(0x04c + UDMA_CHX_OFF(x))
+#define   UDMA_TX_CTRL_TMOUT_DISABLE	BIT(4)
+#define   UDMA_TX_CTRL_BUFSZ_MASK	GENMASK(3, 0)
+#define   UDMA_TX_CTRL_BUFSZ_SHIFT	0
+#define UDMA_CHX_RX_RD_PTR(x)	(0x050 + UDMA_CHX_OFF(x))
+#define UDMA_CHX_RX_WR_PTR(x)	(0x054 + UDMA_CHX_OFF(x))
+#define UDMA_CHX_RX_BUF_BASE(x)	(0x058 + UDMA_CHX_OFF(x))
+#define UDMA_CHX_RX_CTRL(x)	(0x05c + UDMA_CHX_OFF(x))
+#define   UDMA_RX_CTRL_TMOUT_DISABLE	BIT(4)
+#define   UDMA_RX_CTRL_BUFSZ_MASK	GENMASK(3, 0)
+#define   UDMA_RX_CTRL_BUFSZ_SHIFT	0
+
+#define UDMA_MAX_CHANNEL	14
+#define UDMA_TIMEOUT		0x200
+
+enum aspeed_udma_bufsz_code {
+	UDMA_BUFSZ_CODE_1KB,
+	UDMA_BUFSZ_CODE_4KB,
+	UDMA_BUFSZ_CODE_16KB,
+	UDMA_BUFSZ_CODE_64KB,
+
+	/*
+	 * 128KB and above are supported ONLY for
+	 * virtual UARTs. For physical UARTs, the
+	 * size code is wrapped around at the 64K
+	 * boundary.
+	 */
+	UDMA_BUFSZ_CODE_128KB,
+	UDMA_BUFSZ_CODE_256KB,
+	UDMA_BUFSZ_CODE_512KB,
+	UDMA_BUFSZ_CODE_1024KB,
+	UDMA_BUFSZ_CODE_2048KB,
+	UDMA_BUFSZ_CODE_4096KB,
+	UDMA_BUFSZ_CODE_8192KB,
+	UDMA_BUFSZ_CODE_16384KB,
+};
+
+struct aspeed_udma_chan {
+	dma_addr_t dma_addr;
+
+	struct circ_buf *rb;
+	u32 rb_sz;
+
+	aspeed_udma_cb_t cb;
+	void *cb_arg;
+
+	bool dis_tmout;
+};
+
+struct aspeed_udma {
+	struct device *dev;
+	u8 __iomem *regs;
+	int irq;
+	struct aspeed_udma_chan tx_chs[UDMA_MAX_CHANNEL];
+	struct aspeed_udma_chan rx_chs[UDMA_MAX_CHANNEL];
+	spinlock_t lock;
+};
+
+struct aspeed_udma udma[1];
+
+static int aspeed_udma_get_bufsz_code(u32 buf_sz)
+{
+	switch (buf_sz) {
+	case 0x400:
+		return UDMA_BUFSZ_CODE_1KB;
+	case 0x1000:
+		return UDMA_BUFSZ_CODE_4KB;
+	case 0x4000:
+		return UDMA_BUFSZ_CODE_16KB;
+	case 0x10000:
+		return UDMA_BUFSZ_CODE_64KB;
+	case 0x20000:
+		return UDMA_BUFSZ_CODE_128KB;
+	case 0x40000:
+		return UDMA_BUFSZ_CODE_256KB;
+	case 0x80000:
+		return UDMA_BUFSZ_CODE_512KB;
+	case 0x100000:
+		return UDMA_BUFSZ_CODE_1024KB;
+	case 0x200000:
+		return UDMA_BUFSZ_CODE_2048KB;
+	case 0x400000:
+		return UDMA_BUFSZ_CODE_4096KB;
+	case 0x800000:
+		return UDMA_BUFSZ_CODE_8192KB;
+	case 0x1000000:
+		return UDMA_BUFSZ_CODE_16384KB;
+	default:
+		return -1;
+	}
+
+	return -1;
+}
+
+static u32 aspeed_udma_get_tx_rptr(u32 ch_no)
+{
+	return readl(udma->regs + UDMA_CHX_TX_RD_PTR(ch_no));
+}
+
+static u32 aspeed_udma_get_rx_wptr(u32 ch_no)
+{
+	return readl(udma->regs + UDMA_CHX_RX_WR_PTR(ch_no));
+}
+
+static void aspeed_udma_set_ptr(u32 ch_no, u32 ptr, bool is_tx)
+{
+	writel(ptr, udma->regs +
+	       ((is_tx) ? UDMA_CHX_TX_WR_PTR(ch_no) : UDMA_CHX_RX_RD_PTR(ch_no)));
+}
+
+void aspeed_udma_set_tx_wptr(u32 ch_no, u32 wptr)
+{
+	aspeed_udma_set_ptr(ch_no, wptr, true);
+}
+EXPORT_SYMBOL(aspeed_udma_set_tx_wptr);
+
+void aspeed_udma_set_rx_rptr(u32 ch_no, u32 rptr)
+{
+	aspeed_udma_set_ptr(ch_no, rptr, false);
+}
+EXPORT_SYMBOL(aspeed_udma_set_rx_rptr);
+
+static int aspeed_udma_free_chan(u32 ch_no, bool is_tx)
+{
+	u32 reg;
+	unsigned long flags;
+
+	if (ch_no > UDMA_MAX_CHANNEL)
+		return -EINVAL;
+
+	spin_lock_irqsave(&udma->lock, flags);
+
+	reg = readl(udma->regs +
+			((is_tx) ? UDMA_TX_DMA_INT_EN : UDMA_RX_DMA_INT_EN));
+	reg &= ~(0x1 << ch_no);
+
+	writel(reg, udma->regs +
+			((is_tx) ? UDMA_TX_DMA_INT_EN : UDMA_RX_DMA_INT_EN));
+
+	spin_unlock_irqrestore(&udma->lock, flags);
+
+	return 0;
+}
+
+int aspeed_udma_free_tx_chan(u32 ch_no)
+{
+	return aspeed_udma_free_chan(ch_no, true);
+}
+EXPORT_SYMBOL(aspeed_udma_free_tx_chan);
+
+int aspeed_udma_free_rx_chan(u32 ch_no)
+{
+	return aspeed_udma_free_chan(ch_no, false);
+}
+EXPORT_SYMBOL(aspeed_udma_free_rx_chan);
+
+static int aspeed_udma_request_chan(u32 ch_no, dma_addr_t addr,
+		struct circ_buf *rb, u32 rb_sz,
+		aspeed_udma_cb_t cb, void *id, bool dis_tmout, bool is_tx)
+{
+	int retval = 0;
+	int rbsz_code;
+
+	u32 reg;
+	unsigned long flags;
+	struct aspeed_udma_chan *ch;
+
+	if (ch_no > UDMA_MAX_CHANNEL) {
+		retval = -EINVAL;
+		goto out;
+	}
+
+	if (IS_ERR_OR_NULL(rb) || IS_ERR_OR_NULL(rb->buf)) {
+		retval = -EINVAL;
+		goto out;
+	}
+
+	rbsz_code = aspeed_udma_get_bufsz_code(rb_sz);
+	if (rbsz_code < 0) {
+		retval = -EINVAL;
+		goto out;
+	}
+
+	spin_lock_irqsave(&udma->lock, flags);
+
+	if (is_tx) {
+		reg = readl(udma->regs + UDMA_TX_DMA_INT_EN);
+		if (reg & (0x1 << ch_no)) {
+			retval = -EBUSY;
+			goto unlock_n_out;
+		}
+
+		reg |= (0x1 << ch_no);
+		writel(reg, udma->regs + UDMA_TX_DMA_INT_EN);
+
+		reg = readl(udma->regs + UDMA_CHX_TX_CTRL(ch_no));
+		reg |= (dis_tmout) ? UDMA_TX_CTRL_TMOUT_DISABLE : 0;
+		reg |= (rbsz_code << UDMA_TX_CTRL_BUFSZ_SHIFT) & UDMA_TX_CTRL_BUFSZ_MASK;
+		writel(reg, udma->regs + UDMA_CHX_TX_CTRL(ch_no));
+
+		writel(addr, udma->regs + UDMA_CHX_TX_BUF_BASE(ch_no));
+	} else {
+		reg = readl(udma->regs + UDMA_RX_DMA_INT_EN);
+		if (reg & (0x1 << ch_no)) {
+			retval = -EBUSY;
+			goto unlock_n_out;
+		}
+
+		reg |= (0x1 << ch_no);
+		writel(reg, udma->regs + UDMA_RX_DMA_INT_EN);
+
+		reg = readl(udma->regs + UDMA_CHX_RX_CTRL(ch_no));
+		reg |= (dis_tmout) ? UDMA_RX_CTRL_TMOUT_DISABLE : 0;
+		reg |= (rbsz_code << UDMA_RX_CTRL_BUFSZ_SHIFT) & UDMA_RX_CTRL_BUFSZ_MASK;
+		writel(reg, udma->regs + UDMA_CHX_RX_CTRL(ch_no));
+
+		writel(addr, udma->regs + UDMA_CHX_RX_BUF_BASE(ch_no));
+	}
+
+	ch = (is_tx) ? &udma->tx_chs[ch_no] : &udma->rx_chs[ch_no];
+	ch->rb = rb;
+	ch->rb_sz = rb_sz;
+	ch->cb = cb;
+	ch->cb_arg = id;
+	ch->dma_addr = addr;
+	ch->dis_tmout = dis_tmout;
+
+unlock_n_out:
+	spin_unlock_irqrestore(&udma->lock, flags);
+out:
+	return 0;
+}
+
+int aspeed_udma_request_tx_chan(u32 ch_no, dma_addr_t addr,
+		struct circ_buf *rb, u32 rb_sz,
+		aspeed_udma_cb_t cb, void *id, bool dis_tmout)
+{
+	return aspeed_udma_request_chan(ch_no, addr, rb, rb_sz, cb, id,
+									dis_tmout, true);
+}
+EXPORT_SYMBOL(aspeed_udma_request_tx_chan);
+
+int aspeed_udma_request_rx_chan(u32 ch_no, dma_addr_t addr,
+		struct circ_buf *rb, u32 rb_sz,
+		aspeed_udma_cb_t cb, void *id, bool dis_tmout)
+{
+	return aspeed_udma_request_chan(ch_no, addr, rb, rb_sz, cb, id,
+									dis_tmout, false);
+}
+EXPORT_SYMBOL(aspeed_udma_request_rx_chan);
+
+static void aspeed_udma_chan_ctrl(u32 ch_no, u32 op, bool is_tx)
+{
+	unsigned long flags;
+	u32 reg_en, reg_rst;
+	u32 reg_en_off = (is_tx) ? UDMA_TX_DMA_EN : UDMA_RX_DMA_EN;
+	u32 reg_rst_off = (is_tx) ? UDMA_TX_DMA_RST : UDMA_TX_DMA_RST;
+
+	if (ch_no > UDMA_MAX_CHANNEL)
+		return;
+
+	spin_lock_irqsave(&udma->lock, flags);
+
+	reg_en = readl(udma->regs + reg_en_off);
+	reg_rst = readl(udma->regs + reg_rst_off);
+
+	switch (op) {
+	case ASPEED_UDMA_OP_ENABLE:
+		reg_en |= (0x1 << ch_no);
+		writel(reg_en, udma->regs + reg_en_off);
+		break;
+	case ASPEED_UDMA_OP_DISABLE:
+		reg_en &= ~(0x1 << ch_no);
+		writel(reg_en, udma->regs + reg_en_off);
+		break;
+	case ASPEED_UDMA_OP_RESET:
+		reg_en &= ~(0x1 << ch_no);
+		writel(reg_en, udma->regs + reg_en_off);
+
+		reg_rst |= (0x1 << ch_no);
+		writel(reg_rst, udma->regs + reg_rst_off);
+
+		udelay(100);
+
+		reg_rst &= ~(0x1 << ch_no);
+		writel(reg_rst, udma->regs + reg_rst_off);
+		break;
+	default:
+		break;
+	}
+
+	spin_unlock_irqrestore(&udma->lock, flags);
+}
+
+void aspeed_udma_tx_chan_ctrl(u32 ch_no, enum aspeed_udma_ops op)
+{
+	aspeed_udma_chan_ctrl(ch_no, op, true);
+}
+EXPORT_SYMBOL(aspeed_udma_tx_chan_ctrl);
+
+void aspeed_udma_rx_chan_ctrl(u32 ch_no, enum aspeed_udma_ops op)
+{
+	aspeed_udma_chan_ctrl(ch_no, op, false);
+}
+EXPORT_SYMBOL(aspeed_udma_rx_chan_ctrl);
+
+static irqreturn_t aspeed_udma_isr(int irq, void *arg)
+{
+	u32 bit;
+	unsigned long tx_stat = readl(udma->regs + UDMA_TX_DMA_INT_STAT);
+	unsigned long rx_stat = readl(udma->regs + UDMA_RX_DMA_INT_STAT);
+
+	if (udma != (struct aspeed_udma *)arg)
+		return IRQ_NONE;
+
+	if (tx_stat == 0 && rx_stat == 0)
+		return IRQ_NONE;
+
+	for_each_set_bit(bit, &tx_stat, UDMA_MAX_CHANNEL) {
+		writel((0x1 << bit), udma->regs + UDMA_TX_DMA_INT_STAT);
+		if (udma->tx_chs[bit].cb)
+			udma->tx_chs[bit].cb(aspeed_udma_get_tx_rptr(bit),
+					udma->tx_chs[bit].cb_arg);
+	}
+
+	for_each_set_bit(bit, &rx_stat, UDMA_MAX_CHANNEL) {
+		writel((0x1 << bit), udma->regs + UDMA_RX_DMA_INT_STAT);
+		if (udma->rx_chs[bit].cb)
+			udma->rx_chs[bit].cb(aspeed_udma_get_rx_wptr(bit),
+					udma->rx_chs[bit].cb_arg);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int aspeed_udma_probe(struct platform_device *pdev)
+{
+	int i, rc;
+	struct resource *res;
+	struct device *dev = &pdev->dev;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (IS_ERR_OR_NULL(res)) {
+		dev_err(dev, "failed to get register base\n");
+		return -ENODEV;
+	}
+
+	udma->regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR_OR_NULL(udma->regs)) {
+		dev_err(dev, "failed to map registers\n");
+		return PTR_ERR(udma->regs);
+	}
+
+	/* disable for safety */
+	writel(0x0, udma->regs + UDMA_TX_DMA_EN);
+	writel(0x0, udma->regs + UDMA_RX_DMA_EN);
+
+	udma->irq = platform_get_irq(pdev, 0);
+	if (udma->irq < 0) {
+		dev_err(dev, "failed to get IRQ number\n");
+		return -ENODEV;
+	}
+
+	rc = devm_request_irq(dev, udma->irq, aspeed_udma_isr,
+			IRQF_SHARED, DEVICE_NAME, udma);
+	if (rc) {
+		dev_err(dev, "failed to request IRQ handler\n");
+		return rc;
+	}
+
+	for (i = 0; i < UDMA_MAX_CHANNEL; ++i) {
+		writel(0, udma->regs + UDMA_CHX_TX_WR_PTR(i));
+		writel(0, udma->regs + UDMA_CHX_RX_RD_PTR(i));
+	}
+
+	writel(0xffffffff, udma->regs + UDMA_TX_DMA_RST);
+	writel(0x0, udma->regs + UDMA_TX_DMA_RST);
+
+	writel(0xffffffff, udma->regs + UDMA_RX_DMA_RST);
+	writel(0x0, udma->regs + UDMA_RX_DMA_RST);
+
+	writel(0x0, udma->regs + UDMA_TX_DMA_INT_EN);
+	writel(0xffffffff, udma->regs + UDMA_TX_DMA_INT_STAT);
+	writel(0x0, udma->regs + UDMA_RX_DMA_INT_EN);
+	writel(0xffffffff, udma->regs + UDMA_RX_DMA_INT_STAT);
+
+	writel(UDMA_TIMEOUT, udma->regs + UDMA_TIMEOUT_TIMER);
+
+	spin_lock_init(&udma->lock);
+
+	dev_set_drvdata(dev, udma);
+
+	return 0;
+}
+
+static const struct of_device_id aspeed_udma_match[] = {
+	{ .compatible = "aspeed,ast2600-udma" },
+	{ },
+};
+
+static struct platform_driver aspeed_udma_driver = {
+	.driver = {
+		.name = DEVICE_NAME,
+		.of_match_table = aspeed_udma_match,
+
+	},
+	.probe = aspeed_udma_probe,
+};
+
+module_platform_driver(aspeed_udma_driver);
+
+MODULE_AUTHOR("Chia-Wei Wang <chiawei_wang at aspeedtech.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Aspeed UDMA Engine Driver");
diff --git a/include/linux/soc/aspeed/aspeed-udma.h b/include/linux/soc/aspeed/aspeed-udma.h
new file mode 100644
index 000000000000..4ec95d726ede
--- /dev/null
+++ b/include/linux/soc/aspeed/aspeed-udma.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) Aspeed Technology Inc.
+ */
+#ifndef __ASPEED_UDMA_H__
+#define __ASPEED_UDMA_H__
+
+#include <linux/circ_buf.h>
+
+typedef void (*aspeed_udma_cb_t)(int rb_rwptr, void *id);
+
+enum aspeed_udma_ops {
+	ASPEED_UDMA_OP_ENABLE,
+	ASPEED_UDMA_OP_DISABLE,
+	ASPEED_UDMA_OP_RESET,
+};
+
+void aspeed_udma_set_tx_wptr(u32 ch_no, u32 wptr);
+void aspeed_udma_set_rx_rptr(u32 ch_no, u32 rptr);
+
+void aspeed_udma_tx_chan_ctrl(u32 ch_no, enum aspeed_udma_ops op);
+void aspeed_udma_rx_chan_ctrl(u32 ch_no, enum aspeed_udma_ops op);
+
+int aspeed_udma_request_tx_chan(u32 ch_no, dma_addr_t addr,
+				struct circ_buf *rb, u32 rb_sz,
+				aspeed_udma_cb_t cb, void *id, bool en_tmout);
+int aspeed_udma_request_rx_chan(u32 ch_no, dma_addr_t addr,
+				struct circ_buf *rb, u32 rb_sz,
+				aspeed_udma_cb_t cb, void *id, bool en_tmout);
+
+int aspeed_udma_free_tx_chan(u32 ch_no);
+int aspeed_udma_free_rx_chan(u32 ch_no);
+
+#endif
-- 
2.25.1



More information about the Linux-aspeed mailing list