From a4897aed25195742ec9fd992a52a6e7bf872f318 Mon Sep 17 00:00:00 2001 From: "Chia-Wei, Wang" Date: Wed, 22 Jul 2020 13:46:21 +0800 Subject: [PATCH] serial: 8250: Add Aspeed UART driver with DMA supported This patch adds drivers for Aspeed UARTs, which are 16550A compatible. The drivers includes an wrapper to support the extended DMA feature of UART devices and another UDMA driver to control the UART DMA engine. Signed-off-by: Chia-Wei, Wang --- arch/arm/boot/dts/aspeed-ast2600-evb.dts | 5 - .../arm/boot/dts/aspeed-bmc-intel-ast2600.dts | 2 - arch/arm/boot/dts/aspeed-g6.dtsi | 28 +- drivers/soc/aspeed/Kconfig | 8 + drivers/soc/aspeed/Makefile | 1 + drivers/soc/aspeed/aspeed-udma.c | 441 ++++++++++++++++ drivers/tty/serial/8250/8250_aspeed.c | 494 ++++++++++++++++++ drivers/tty/serial/8250/Kconfig | 8 + drivers/tty/serial/8250/Makefile | 1 + include/linux/soc/aspeed/aspeed-udma.h | 30 ++ 10 files changed, 997 insertions(+), 21 deletions(-) create mode 100644 drivers/soc/aspeed/aspeed-udma.c create mode 100644 drivers/tty/serial/8250/8250_aspeed.c create mode 100644 include/linux/soc/aspeed/aspeed-udma.h diff --git a/arch/arm/boot/dts/aspeed-ast2600-evb.dts b/arch/arm/boot/dts/aspeed-ast2600-evb.dts index acbd1c947465..913749205c1d 100644 --- a/arch/arm/boot/dts/aspeed-ast2600-evb.dts +++ b/arch/arm/boot/dts/aspeed-ast2600-evb.dts @@ -180,11 +180,6 @@ }; }; -&uart5 { - // Workaround for A0 - compatible = "snps,dw-apb-uart"; -}; - &i2c0 { status = "okay"; diff --git a/arch/arm/boot/dts/aspeed-bmc-intel-ast2600.dts b/arch/arm/boot/dts/aspeed-bmc-intel-ast2600.dts index 02c837c3e2c4..210d2bbdf836 100644 --- a/arch/arm/boot/dts/aspeed-bmc-intel-ast2600.dts +++ b/arch/arm/boot/dts/aspeed-bmc-intel-ast2600.dts @@ -427,8 +427,6 @@ &uart5 { status = "okay"; - // Workaround for A0 - compatible = "snps,dw-apb-uart"; }; &uart_routing { diff --git a/arch/arm/boot/dts/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed-g6.dtsi index e76dfb73430e..001ecf9ad33c 100644 --- a/arch/arm/boot/dts/aspeed-g6.dtsi +++ b/arch/arm/boot/dts/aspeed-g6.dtsi @@ -524,23 +524,22 @@ }; uart1: serial@1e783000 { - compatible = "ns16550a"; + compatible = "aspeed,ast2600-uart"; reg = <0x1e783000 0x20>; - reg-shift = <2>; - reg-io-width = <4>; interrupts = ; clocks = <&syscon ASPEED_CLK_GATE_UART1CLK>; resets = <&lpc_reset 4>; no-loopback-test; + dma-mode; + dma-channel = <0>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_txd1_default &pinctrl_rxd1_default>; status = "disabled"; }; uart5: serial@1e784000 { - compatible = "ns16550a"; + compatible = "aspeed,ast2600-uart"; reg = <0x1e784000 0x1000>; - reg-shift = <2>; interrupts = ; clocks = <&syscon ASPEED_CLK_GATE_UART5CLK>; no-loopback-test; @@ -754,10 +753,8 @@ }; uart2: serial@1e78d000 { - compatible = "ns16550a"; + compatible = "aspeed,ast2600-uart"; reg = <0x1e78d000 0x20>; - reg-shift = <2>; - reg-io-width = <4>; interrupts = ; clocks = <&syscon ASPEED_CLK_GATE_UART2CLK>; resets = <&lpc_reset 5>; @@ -768,10 +765,8 @@ }; uart3: serial@1e78e000 { - compatible = "ns16550a"; + compatible = "aspeed,ast2600-uart"; reg = <0x1e78e000 0x20>; - reg-shift = <2>; - reg-io-width = <4>; interrupts = ; clocks = <&syscon ASPEED_CLK_GATE_UART3CLK>; resets = <&lpc_reset 6>; @@ -782,10 +777,8 @@ }; uart4: serial@1e78f000 { - compatible = "ns16550a"; + compatible = "aspeed,ast2600-uart"; reg = <0x1e78f000 0x20>; - reg-shift = <2>; - reg-io-width = <4>; interrupts = ; clocks = <&syscon ASPEED_CLK_GATE_UART4CLK>; resets = <&lpc_reset 7>; @@ -834,6 +827,13 @@ clocks = <&syscon ASPEED_CLK_GATE_FSICLK>; status = "disabled"; }; + + udma: uart-dma@1e79e000 { + compatible = "aspeed,ast2600-udma"; + reg = <0x1e79e000 0x400>; + interrupts = ; + }; + }; }; }; diff --git a/drivers/soc/aspeed/Kconfig b/drivers/soc/aspeed/Kconfig index d36ee43451a5..ca284ac2f0ab 100644 --- a/drivers/soc/aspeed/Kconfig +++ b/drivers/soc/aspeed/Kconfig @@ -90,6 +90,14 @@ config ASPEED_XDMA SoCs. The XDMA engine can perform PCIe DMA operations between the BMC and a host processor. +config ASPEED_UDMA + tristate "Aspeed UDMA Engine Driver" + depends on SOC_ASPEED && REGMAP && MFD_SYSCON && HAS_DMA + help + Enable support for the Aspeed UDMA Engine found on the Aspeed AST2XXX + SOCs. The UDMA engine can perform UART DMA operations between the memory + buffer and the UART/VUART devices. + config ASPEED_VGA_SHAREDMEM tristate "Aspeed VGA Shared memory" help diff --git a/drivers/soc/aspeed/Makefile b/drivers/soc/aspeed/Makefile index f3afc32b58b9..e6248ecdeee3 100644 --- a/drivers/soc/aspeed/Makefile +++ b/drivers/soc/aspeed/Makefile @@ -8,5 +8,6 @@ obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o obj-$(CONFIG_ASPEED_P2A_CTRL) += aspeed-p2a-ctrl.o obj-$(CONFIG_ASPEED_SOCINFO) += aspeed-socinfo.o obj-$(CONFIG_ASPEED_XDMA) += aspeed-xdma.o +obj-$(CONFIG_ASPEED_UDMA) += aspeed-udma.o obj-$(CONFIG_ASPEED_VGA_SHAREDMEM) += aspeed-vga-sharedmem.o obj-$(CONFIG_ASPEED_MCTP) += aspeed-mctp.o diff --git a/drivers/soc/aspeed/aspeed-udma.c b/drivers/soc/aspeed/aspeed-udma.c new file mode 100644 index 000000000000..01b73ebe1880 --- /dev/null +++ b/drivers/soc/aspeed/aspeed-udma.c @@ -0,0 +1,441 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + u32 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); + 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,ast2500-udma" }, + { .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 "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Aspeed UDMA Engine Driver"); diff --git a/drivers/tty/serial/8250/8250_aspeed.c b/drivers/tty/serial/8250/8250_aspeed.c new file mode 100644 index 000000000000..1dc798298fca --- /dev/null +++ b/drivers/tty/serial/8250/8250_aspeed.c @@ -0,0 +1,494 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) ASPEED Technology Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "8250.h" + +#define DEVICE_NAME "aspeed-uart" + +/* offsets for the aspeed virtual uart registers */ +#define VUART_GCRA 0x20 +#define VUART_GCRA_VUART_EN BIT(0) +#define VUART_GCRA_SIRQ_POLARITY BIT(1) +#define VUART_GCRA_DISABLE_HOST_TX_DISCARD BIT(5) +#define VUART_GCRB 0x24 +#define VUART_GCRB_HOST_SIRQ_MASK GENMASK(7, 4) +#define VUART_GCRB_HOST_SIRQ_SHIFT 4 +#define VUART_ADDRL 0x28 +#define VUART_ADDRH 0x2c + +#define DMA_TX_BUFSZ PAGE_SIZE +#define DMA_RX_BUFSZ (64 * 1024) + +struct uart_ops ast8250_pops; + +struct ast8250_vuart { + u32 port; + u32 sirq; + u32 sirq_pol; +}; + +struct ast8250_udma { + u32 ch; + + u32 tx_rbsz; + u32 rx_rbsz; + + dma_addr_t tx_addr; + dma_addr_t rx_addr; + + struct circ_buf *tx_rb; + struct circ_buf *rx_rb; + + bool tx_tmout_dis; + bool rx_tmout_dis; +}; + +struct ast8250_data { + int line; + + u8 __iomem *regs; + + bool is_vuart; + bool use_dma; + + struct reset_control *rst; + struct clk *clk; + + struct ast8250_vuart vuart; + struct ast8250_udma dma; +}; + +static void ast8250_dma_tx_complete(int tx_rb_rptr, void *id) +{ + u32 count; + unsigned long flags; + struct uart_port *port = (struct uart_port*)id; + struct ast8250_data *data = port->private_data; + + spin_lock_irqsave(&port->lock, flags); + + count = CIRC_CNT(tx_rb_rptr, port->state->xmit.tail, data->dma.tx_rbsz); + port->state->xmit.tail = tx_rb_rptr; + port->icount.tx += count; + + if (uart_circ_chars_pending(&port->state->xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); + + spin_unlock_irqrestore(&port->lock, flags); +} + +static void ast8250_dma_rx_complete(int rx_rb_wptr, void *id) +{ + unsigned long flags; + struct uart_port *up = (struct uart_port*)id; + struct tty_port *tp = &up->state->port; + struct ast8250_data *data = up->private_data; + struct ast8250_udma *dma = &data->dma; + struct circ_buf *rx_rb = dma->rx_rb; + u32 rx_rbsz = dma->rx_rbsz; + u32 count = 0; + + spin_lock_irqsave(&up->lock, flags); + + rx_rb->head = rx_rb_wptr; + + dma_sync_single_for_cpu(up->dev, + dma->rx_addr, dma->rx_rbsz, DMA_FROM_DEVICE); + + while (CIRC_CNT(rx_rb->head, rx_rb->tail, rx_rbsz)) { + count = CIRC_CNT_TO_END(rx_rb->head, rx_rb->tail, rx_rbsz); + + tty_insert_flip_string(tp, rx_rb->buf + rx_rb->tail, count); + + rx_rb->tail += count; + rx_rb->tail %= rx_rbsz; + + up->icount.rx += count; + } + + if (count) { + aspeed_udma_set_rx_rptr(data->dma.ch, rx_rb->tail); + tty_flip_buffer_push(tp); + } + + spin_unlock_irqrestore(&up->lock, flags); +} + +static void ast8250_dma_start_tx(struct uart_port *port) +{ + struct ast8250_data *data = port->private_data; + struct ast8250_udma *dma = &data->dma; + struct circ_buf *tx_rb = dma->tx_rb; + + dma_sync_single_for_device(port->dev, + dma->tx_addr, dma->tx_rbsz, DMA_TO_DEVICE); + + aspeed_udma_set_tx_wptr(dma->ch, tx_rb->head); +} + +static void ast8250_dma_pops_hook(struct uart_port *port) +{ + static int first = 1; + + if (first) { + ast8250_pops = *port->ops; + ast8250_pops.start_tx = ast8250_dma_start_tx; + } + + first = 0; + port->ops = &ast8250_pops; +} + +static void ast8250_vuart_init(struct ast8250_data *data) +{ + u8 reg; + struct ast8250_vuart *vuart = &data->vuart; + + /* IO port address */ + writeb((u8)(vuart->port >> 0), data->regs + VUART_ADDRL); + writeb((u8)(vuart->port >> 8), data->regs + VUART_ADDRH); + + /* SIRQ number */ + reg = readb(data->regs + VUART_GCRB); + reg &= ~VUART_GCRB_HOST_SIRQ_MASK; + reg |= ((vuart->sirq << VUART_GCRB_HOST_SIRQ_SHIFT) & VUART_GCRB_HOST_SIRQ_MASK); + writeb(reg, data->regs + VUART_GCRB); + + /* SIRQ polarity */ + reg = readb(data->regs + VUART_GCRA); + if (vuart->sirq_pol) + reg |= VUART_GCRA_SIRQ_POLARITY; + else + reg &= ~VUART_GCRA_SIRQ_POLARITY; + writeb(reg, data->regs + VUART_GCRA); +} + +static void ast8250_vuart_set_host_tx_discard(struct ast8250_data *data, bool discard) +{ + u8 reg; + + reg = readb(data->regs + VUART_GCRA); + if (discard) + reg &= ~VUART_GCRA_DISABLE_HOST_TX_DISCARD; + else + reg |= VUART_GCRA_DISABLE_HOST_TX_DISCARD; + writeb(reg, data->regs + VUART_GCRA); +} + +static void ast8250_vuart_set_enable(struct ast8250_data *data, bool enable) +{ + u8 reg; + + reg = readb(data->regs + VUART_GCRA); + if (enable) + reg |= VUART_GCRA_VUART_EN; + else + reg &= ~VUART_GCRA_VUART_EN; + writeb(reg, data->regs + VUART_GCRA); +} + +static int ast8250_handle_irq(struct uart_port *port) +{ + u32 iir = port->serial_in(port, UART_IIR); + return serial8250_handle_irq(port, iir); +} + +static int ast8250_startup(struct uart_port *port) +{ + int rc = 0; + struct ast8250_data *data = port->private_data; + struct ast8250_udma *dma; + + if (data->is_vuart) + ast8250_vuart_set_host_tx_discard(data, false); + + if (data->use_dma) { + dma = &data->dma; + + dma->tx_rbsz = DMA_TX_BUFSZ; + dma->rx_rbsz = DMA_RX_BUFSZ; + + /* + * We take the xmit buffer passed from upper layers as + * the DMA TX buffer and allocate a new buffer for the + * RX use. + * + * To keep the TX/RX operation consistency, we use the + * streaming DMA interface instead of the coherent one + */ + dma->tx_rb = &port->state->xmit; + dma->rx_rb->buf = kzalloc(data->dma.rx_rbsz, GFP_KERNEL); + if (IS_ERR_OR_NULL(dma->rx_rb->buf)) { + dev_err(port->dev, "failed to allcoate RX DMA buffer\n"); + rc = -ENOMEM; + goto out; + } + + dma->tx_addr = dma_map_single(port->dev, dma->tx_rb->buf, + dma->tx_rbsz, DMA_TO_DEVICE); + if (dma_mapping_error(port->dev, dma->tx_addr)) { + dev_err(port->dev, "failed to map streaming TX DMA region\n"); + rc = -ENOMEM; + goto free_dma_n_out; + } + + dma->rx_addr = dma_map_single(port->dev, dma->rx_rb->buf, + dma->rx_rbsz, DMA_FROM_DEVICE); + if (dma_mapping_error(port->dev, dma->rx_addr)) { + dev_err(port->dev, "failed to map streaming RX DMA region\n"); + rc = -ENOMEM; + goto free_dma_n_out; + } + + rc = aspeed_udma_request_tx_chan(dma->ch, dma->tx_addr, + dma->tx_rb, dma->tx_rbsz, ast8250_dma_tx_complete, port, dma->tx_tmout_dis); + if (rc) { + dev_err(port->dev, "failed to request DMA TX channel\n"); + goto free_dma_n_out; + } + + rc = aspeed_udma_request_rx_chan(dma->ch, dma->rx_addr, + dma->rx_rb, dma->rx_rbsz, ast8250_dma_rx_complete, port, dma->tx_tmout_dis); + if (rc) { + dev_err(port->dev, "failed to request DMA RX channel\n"); + goto free_dma_n_out; + } + + ast8250_dma_pops_hook(port); + + aspeed_udma_tx_chan_ctrl(dma->ch, ASPEED_UDMA_OP_ENABLE); + aspeed_udma_rx_chan_ctrl(dma->ch, ASPEED_UDMA_OP_ENABLE); + } + + memset(&port->icount, 0, sizeof(port->icount)); + return serial8250_do_startup(port); + +free_dma_n_out: + kfree(dma->rx_rb->buf); +out: + return rc; +} + +static void ast8250_shutdown(struct uart_port *port) +{ + int rc; + struct ast8250_data *data = port->private_data; + struct ast8250_udma *dma; + + if (data->use_dma) { + dma = &data->dma; + + aspeed_udma_tx_chan_ctrl(dma->ch, ASPEED_UDMA_OP_DISABLE); + aspeed_udma_rx_chan_ctrl(dma->ch, ASPEED_UDMA_OP_DISABLE); + + rc = aspeed_udma_free_tx_chan(dma->ch); + if (rc) + dev_err(port->dev, "failed to free DMA TX channel, rc=%d\n", rc); + + rc = aspeed_udma_free_rx_chan(dma->ch); + if (rc) + dev_err(port->dev, "failed to free DMA TX channel, rc=%d\n", rc); + + dma_unmap_single(port->dev, dma->tx_addr, + dma->tx_rbsz, DMA_TO_DEVICE); + dma_unmap_single(port->dev, dma->rx_addr, + dma->rx_rbsz, DMA_FROM_DEVICE); + + if (dma->rx_rb->buf) + kfree(dma->rx_rb->buf); + } + + if (data->is_vuart) + ast8250_vuart_set_host_tx_discard(data, true); + + serial8250_do_shutdown(port); +} + +static int __maybe_unused ast8250_suspend(struct device *dev) +{ + struct ast8250_data *data = dev_get_drvdata(dev); + serial8250_suspend_port(data->line); + return 0; +} + +static int __maybe_unused ast8250_resume(struct device *dev) +{ + struct ast8250_data *data = dev_get_drvdata(dev); + serial8250_resume_port(data->line); + return 0; +} + +static int ast8250_probe(struct platform_device *pdev) +{ + int rc; + struct uart_8250_port uart = {}; + struct uart_port *port = &uart.port; + struct device *dev = &pdev->dev; + struct ast8250_data *data; + + struct resource *res; + u32 irq; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + data->dma.rx_rb = devm_kzalloc(dev, sizeof(data->dma.rx_rb), GFP_KERNEL); + if (data->dma.rx_rb == NULL) + return -ENOMEM; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + if (irq != -EPROBE_DEFER) + dev_err(dev, "failed to get IRQ number\n"); + return irq; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(dev, "failed to get register base\n"); + return -ENODEV; + } + + data->regs = devm_ioremap(dev, res->start, resource_size(res)); + if (IS_ERR(data->regs)) { + dev_err(dev, "failed to map registers\n"); + return PTR_ERR(data->regs); + } + + data->clk = devm_clk_get(dev, NULL); + if (IS_ERR(data->clk)) { + dev_err(dev, "failed to get clocks\n"); + return -ENODEV; + } + + rc = clk_prepare_enable(data->clk); + if (rc) { + dev_err(dev, "failed to enable clock\n"); + return rc; + } + + data->rst = devm_reset_control_get_optional_exclusive(dev, NULL); + if (!IS_ERR(data->rst)) + reset_control_deassert(data->rst); + + data->is_vuart = of_property_read_bool(dev->of_node, "virtual"); + if (data->is_vuart) { + rc = of_property_read_u32(dev->of_node, "port", &data->vuart.port); + if (rc) { + dev_err(dev, "failed to get VUART port address\n"); + return -ENODEV; + } + + rc = of_property_read_u32(dev->of_node, "sirq", &data->vuart.sirq); + if (rc) { + dev_err(dev, "failed to get VUART SIRQ number\n"); + return -ENODEV; + } + + rc = of_property_read_u32(dev->of_node, "sirq-polarity", &data->vuart.sirq_pol); + if (rc) { + dev_err(dev, "failed to get VUART SIRQ polarity\n"); + return -ENODEV; + } + + ast8250_vuart_init(data); + ast8250_vuart_set_host_tx_discard(data, true); + ast8250_vuart_set_enable(data, true); + } + + data->use_dma = of_property_read_bool(dev->of_node, "dma-mode"); + if (data->use_dma) { + rc = of_property_read_u32(dev->of_node, "dma-channel", &data->dma.ch); + if (rc) { + dev_err(dev, "failed to get DMA channel\n"); + return -ENODEV; + } + + data->dma.tx_tmout_dis = of_property_read_bool(dev->of_node, "dma-tx-timeout-disable"); + data->dma.rx_tmout_dis = of_property_read_bool(dev->of_node, "dma-rx-timeout-disable"); + } + + spin_lock_init(&port->lock); + port->dev = dev; + port->type = PORT_16550; + port->irq = irq; + port->line = of_alias_get_id(dev->of_node, "serial"); + port->handle_irq = ast8250_handle_irq; + port->mapbase = res->start; + port->mapsize = resource_size(res); + port->membase = data->regs; + port->uartclk = clk_get_rate(data->clk); + port->regshift = 2; + port->iotype = UPIO_MEM32; + port->flags = UPF_FIXED_PORT | UPF_SHARE_IRQ; + port->startup = ast8250_startup; + port->shutdown = ast8250_shutdown; + port->private_data = data; + + data->line = serial8250_register_8250_port(&uart); + if (data->line < 0) { + dev_err(dev, "failed to register 8250 port\n"); + return data->line; + } + + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + platform_set_drvdata(pdev, data); + return 0; +} + +static int ast8250_remove(struct platform_device *pdev) +{ + struct ast8250_data *data = platform_get_drvdata(pdev); + + if (data->is_vuart) + ast8250_vuart_set_enable(data, false); + + serial8250_unregister_port(data->line); + return 0; +} + +static const struct dev_pm_ops ast8250_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ast8250_suspend, ast8250_resume) +}; + +static const struct of_device_id ast8250_of_match[] = { + { .compatible = "aspeed,ast2500-uart" }, + { .compatible = "aspeed,ast2600-uart" }, +}; + +static struct platform_driver ast8250_platform_driver = { + .driver = { + .name = DEVICE_NAME, + .pm = &ast8250_pm_ops, + .of_match_table = ast8250_of_match, + }, + .probe = ast8250_probe, + .remove = ast8250_remove, +}; + +module_platform_driver(ast8250_platform_driver); + +MODULE_AUTHOR("Chia-Wei Wang "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Aspeed UART Driver"); diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig index d1b3c2373fa4..b62a972c9c97 100644 --- a/drivers/tty/serial/8250/Kconfig +++ b/drivers/tty/serial/8250/Kconfig @@ -249,6 +249,14 @@ config SERIAL_8250_ACCENT To compile this driver as a module, choose M here: the module will be called 8250_accent. +config SERIAL_8250_ASPEED + tristate "Aspeed serial port support" + depends on SERIAL_8250 && ARCH_ASPEED + select ASPEED_UDMA + help + If you have a system using an Aspeed ASTXXXX SoCs and wish to make use + of its UARTs, say Y to this option. If unsure, say N. + config SERIAL_8250_ASPEED_VUART tristate "Aspeed Virtual UART" depends on SERIAL_8250 diff --git a/drivers/tty/serial/8250/Makefile b/drivers/tty/serial/8250/Makefile index b9bcd73c8997..310375fe8d7e 100644 --- a/drivers/tty/serial/8250/Makefile +++ b/drivers/tty/serial/8250/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_SERIAL_8250_EXAR) += 8250_exar.o obj-$(CONFIG_SERIAL_8250_HP300) += 8250_hp300.o obj-$(CONFIG_SERIAL_8250_CS) += serial_cs.o obj-$(CONFIG_SERIAL_8250_ACORN) += 8250_acorn.o +obj-$(CONFIG_SERIAL_8250_ASPEED) += 8250_aspeed.o obj-$(CONFIG_SERIAL_8250_ASPEED_VUART) += 8250_aspeed_vuart.o obj-$(CONFIG_SERIAL_8250_BCM2835AUX) += 8250_bcm2835aux.o obj-$(CONFIG_SERIAL_8250_CONSOLE) += 8250_early.o diff --git a/include/linux/soc/aspeed/aspeed-udma.h b/include/linux/soc/aspeed/aspeed-udma.h new file mode 100644 index 000000000000..33acea745f1c --- /dev/null +++ b/include/linux/soc/aspeed/aspeed-udma.h @@ -0,0 +1,30 @@ +#ifndef __ASPEED_UDMA_H__ +#define __ASPEED_UDMA_H__ + +#include + +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.17.1