From nobody Fri Sep 12 13:37:23 2025 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 97C23C05027 for ; Fri, 10 Feb 2023 07:28:02 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231466AbjBJH2B (ORCPT ); Fri, 10 Feb 2023 02:28:01 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55240 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231429AbjBJH1y (ORCPT ); Fri, 10 Feb 2023 02:27:54 -0500 Received: from twspam01.aspeedtech.com (twspam01.aspeedtech.com [211.20.114.71]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id F2D1B5DC37; Thu, 9 Feb 2023 23:27:51 -0800 (PST) Received: from mail.aspeedtech.com ([192.168.0.24]) by twspam01.aspeedtech.com with ESMTP id 31A7EWrq068845; Fri, 10 Feb 2023 15:14:33 +0800 (GMT-8) (envelope-from chiawei_wang@aspeedtech.com) Received: from Chiawei-PC03.aspeed.com (192.168.2.66) by TWMBX02.aspeed.com (192.168.0.24) with Microsoft SMTP Server (TLS) id 15.0.1497.2; Fri, 10 Feb 2023 15:26:47 +0800 From: Chia-Wei Wang To: , , , , , , , , , , , Subject: [PATCH 3/4] serial: 8250: Add Aspeed UART driver Date: Fri, 10 Feb 2023 15:26:42 +0800 Message-ID: <20230210072643.2772-4-chiawei_wang@aspeedtech.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230210072643.2772-1-chiawei_wang@aspeedtech.com> References: <20230210072643.2772-1-chiawei_wang@aspeedtech.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Originating-IP: [192.168.2.66] X-ClientProxiedBy: TWMBX02.aspeed.com (192.168.0.24) To TWMBX02.aspeed.com (192.168.0.24) X-DNSRBL: X-MAIL: twspam01.aspeedtech.com 31A7EWrq068845 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Add the driver for Aspeed UART/VUART devices, which are 16550A compatible. It is an wrapper to cover the generic 16550A operation while exetending DMA feature for the devices. Signed-off-by: Chia-Wei Wang --- drivers/tty/serial/8250/8250_aspeed.c | 502 ++++++++++++++++++++++++++ drivers/tty/serial/8250/Kconfig | 8 + drivers/tty/serial/8250/Makefile | 1 + 3 files changed, 511 insertions(+) create mode 100644 drivers/tty/serial/8250/8250_aspeed.c diff --git a/drivers/tty/serial/8250/8250_aspeed.c b/drivers/tty/serial/825= 0/8250_aspeed.c new file mode 100644 index 000000000000..2eaa59ee6d27 --- /dev/null +++ b/drivers/tty/serial/8250/8250_aspeed.c @@ -0,0 +1,502 @@ +// 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 =3D (struct uart_port *)id; + struct ast8250_data *data =3D port->private_data; + + spin_lock_irqsave(&port->lock, flags); + + count =3D CIRC_CNT(tx_rb_rptr, port->state->xmit.tail, data->dma.tx_rbsz); + port->state->xmit.tail =3D tx_rb_rptr; + port->icount.tx +=3D 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 =3D (struct uart_port *)id; + struct tty_port *tp =3D &up->state->port; + struct ast8250_data *data =3D up->private_data; + struct ast8250_udma *dma =3D &data->dma; + struct circ_buf *rx_rb =3D dma->rx_rb; + u32 rx_rbsz =3D dma->rx_rbsz; + u32 count =3D 0; + + spin_lock_irqsave(&up->lock, flags); + + rx_rb->head =3D 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 =3D 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 +=3D count; + rx_rb->tail %=3D rx_rbsz; + + up->icount.rx +=3D 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 =3D port->private_data; + struct ast8250_udma *dma =3D &data->dma; + struct circ_buf *tx_rb =3D 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 =3D 1; + + if (first) { + ast8250_pops =3D *port->ops; + ast8250_pops.start_tx =3D ast8250_dma_start_tx; + } + + first =3D 0; + port->ops =3D &ast8250_pops; +} + +static void ast8250_vuart_init(struct ast8250_data *data) +{ + u8 reg; + struct ast8250_vuart *vuart =3D &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 =3D readb(data->regs + VUART_GCRB); + reg &=3D ~VUART_GCRB_HOST_SIRQ_MASK; + reg |=3D ((vuart->sirq << VUART_GCRB_HOST_SIRQ_SHIFT) & VUART_GCRB_HOST_S= IRQ_MASK); + writeb(reg, data->regs + VUART_GCRB); + + /* SIRQ polarity */ + reg =3D readb(data->regs + VUART_GCRA); + if (vuart->sirq_pol) + reg |=3D VUART_GCRA_SIRQ_POLARITY; + else + reg &=3D ~VUART_GCRA_SIRQ_POLARITY; + writeb(reg, data->regs + VUART_GCRA); +} + +static void ast8250_vuart_set_host_tx_discard(struct ast8250_data *data, b= ool discard) +{ + u8 reg; + + reg =3D readb(data->regs + VUART_GCRA); + if (discard) + reg &=3D ~VUART_GCRA_DISABLE_HOST_TX_DISCARD; + else + reg |=3D VUART_GCRA_DISABLE_HOST_TX_DISCARD; + writeb(reg, data->regs + VUART_GCRA); +} + +static void ast8250_vuart_set_enable(struct ast8250_data *data, bool enabl= e) +{ + u8 reg; + + reg =3D readb(data->regs + VUART_GCRA); + if (enable) + reg |=3D VUART_GCRA_VUART_EN; + else + reg &=3D ~VUART_GCRA_VUART_EN; + writeb(reg, data->regs + VUART_GCRA); +} + +static int ast8250_handle_irq(struct uart_port *port) +{ + u32 iir =3D port->serial_in(port, UART_IIR); + + return serial8250_handle_irq(port, iir); +} + +static int ast8250_startup(struct uart_port *port) +{ + int rc =3D 0; + struct ast8250_data *data =3D port->private_data; + struct ast8250_udma *dma; + + if (data->is_vuart) + ast8250_vuart_set_host_tx_discard(data, false); + + if (data->use_dma) { + dma =3D &data->dma; + + dma->tx_rbsz =3D DMA_TX_BUFSZ; + dma->rx_rbsz =3D 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 =3D &port->state->xmit; + dma->rx_rb->buf =3D 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 =3D -ENOMEM; + goto out; + } + + dma->tx_addr =3D 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 =3D -ENOMEM; + goto free_dma_n_out; + } + + dma->rx_addr =3D 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 =3D -ENOMEM; + goto free_dma_n_out; + } + + rc =3D 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 =3D aspeed_udma_request_rx_chan(dma->ch, dma->rx_addr, dma->rx_rb, + dma->rx_rbsz, ast8250_dma_rx_complete, + port, dma->rx_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 =3D port->private_data; + struct ast8250_udma *dma; + + if (data->use_dma) { + dma =3D &data->dma; + + aspeed_udma_tx_chan_ctrl(dma->ch, ASPEED_UDMA_OP_RESET); + aspeed_udma_rx_chan_ctrl(dma->ch, ASPEED_UDMA_OP_RESET); + + aspeed_udma_tx_chan_ctrl(dma->ch, ASPEED_UDMA_OP_DISABLE); + aspeed_udma_rx_chan_ctrl(dma->ch, ASPEED_UDMA_OP_DISABLE); + + rc =3D aspeed_udma_free_tx_chan(dma->ch); + if (rc) + dev_err(port->dev, "failed to free DMA TX channel, rc=3D%d\n", rc); + + rc =3D aspeed_udma_free_rx_chan(dma->ch); + if (rc) + dev_err(port->dev, "failed to free DMA TX channel, rc=3D%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); + + 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 =3D dev_get_drvdata(dev); + + serial8250_suspend_port(data->line); + + return 0; +} + +static int __maybe_unused ast8250_resume(struct device *dev) +{ + struct ast8250_data *data =3D 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 =3D {}; + struct uart_port *port =3D &uart.port; + struct device *dev =3D &pdev->dev; + struct ast8250_data *data; + + struct resource *res; + u32 irq; + + data =3D devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (data =3D=3D NULL) + return -ENOMEM; + + data->dma.rx_rb =3D devm_kzalloc(dev, sizeof(data->dma.rx_rb), GFP_KERNEL= ); + if (data->dma.rx_rb =3D=3D NULL) + return -ENOMEM; + + irq =3D platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + res =3D platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res =3D=3D NULL) { + dev_err(dev, "failed to get register base\n"); + return -ENODEV; + } + + data->regs =3D 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 =3D devm_clk_get(dev, NULL); + if (IS_ERR(data->clk)) { + dev_err(dev, "failed to get clocks\n"); + return -ENODEV; + } + + rc =3D clk_prepare_enable(data->clk); + if (rc) { + dev_err(dev, "failed to enable clock\n"); + return rc; + } + + data->rst =3D devm_reset_control_get_optional_exclusive(dev, NULL); + if (!IS_ERR(data->rst)) + reset_control_deassert(data->rst); + + data->is_vuart =3D of_property_read_bool(dev->of_node, "virtual"); + if (data->is_vuart) { + rc =3D 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 =3D 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 =3D 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 =3D of_property_read_bool(dev->of_node, "dma-mode"); + if (data->use_dma) { + rc =3D 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 =3D of_property_read_bool(dev->of_node, + "dma-tx-timeout-disable"); + data->dma.rx_tmout_dis =3D of_property_read_bool(dev->of_node, + "dma-rx-timeout-disable"); + } + + spin_lock_init(&port->lock); + port->dev =3D dev; + port->type =3D PORT_16550A; + port->irq =3D irq; + port->line =3D of_alias_get_id(dev->of_node, "serial"); + port->handle_irq =3D ast8250_handle_irq; + port->mapbase =3D res->start; + port->mapsize =3D resource_size(res); + port->membase =3D data->regs; + port->uartclk =3D clk_get_rate(data->clk); + port->regshift =3D 2; + port->iotype =3D UPIO_MEM32; + port->flags =3D UPF_FIXED_TYPE | UPF_FIXED_PORT | UPF_SHARE_IRQ; + port->startup =3D ast8250_startup; + port->shutdown =3D ast8250_shutdown; + port->private_data =3D data; + uart.bugs |=3D UART_BUG_TXRACE; + + data->line =3D 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 =3D 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 =3D { + SET_SYSTEM_SLEEP_PM_OPS(ast8250_suspend, ast8250_resume) +}; + +static const struct of_device_id ast8250_of_match[] =3D { + { .compatible =3D "aspeed,ast2600-uart" }, +}; + +static struct platform_driver ast8250_platform_driver =3D { + .driver =3D { + .name =3D DEVICE_NAME, + .pm =3D &ast8250_pm_ops, + .of_match_table =3D ast8250_of_match, + }, + .probe =3D ast8250_probe, + .remove =3D 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/Kcon= fig index b0f62345bc84..d8ecc261d8c4 100644 --- a/drivers/tty/serial/8250/Kconfig +++ b/drivers/tty/serial/8250/Kconfig @@ -538,6 +538,14 @@ config SERIAL_8250_BCM7271 including DMA support and high accuracy BAUD rates, say Y to this option. If unsure, say N. =20 +config SERIAL_8250_ASPEED + tristate "Aspeed serial port support" + depends on SERIAL_8250 && ARCH_ASPEED + help + If you have a system using an Aspeed AST26xx SoCs and wish to + make use of its UARTs and VUARTs, which are 16550A compatible + and include DMA support, say Y to this option. If unsure, say N. + config SERIAL_OF_PLATFORM tristate "Devicetree based probing for 8250 ports" depends on SERIAL_8250 && OF diff --git a/drivers/tty/serial/8250/Makefile b/drivers/tty/serial/8250/Mak= efile index 1615bfdde2a0..7f05b7ed422b 100644 --- a/drivers/tty/serial/8250/Makefile +++ b/drivers/tty/serial/8250/Makefile @@ -42,6 +42,7 @@ obj-$(CONFIG_SERIAL_8250_PERICOM) +=3D 8250_pericom.o obj-$(CONFIG_SERIAL_8250_PXA) +=3D 8250_pxa.o obj-$(CONFIG_SERIAL_8250_TEGRA) +=3D 8250_tegra.o obj-$(CONFIG_SERIAL_8250_BCM7271) +=3D 8250_bcm7271.o +obj-$(CONFIG_SERIAL_8250_ASPEED) +=3D 8250_aspeed.o obj-$(CONFIG_SERIAL_OF_PLATFORM) +=3D 8250_of.o =20 CFLAGS_8250_ingenic.o +=3D -I$(srctree)/scripts/dtc/libfdt --=20 2.25.1