From patchwork Wed Jun 30 22:56:44 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lino Sanfilippo X-Patchwork-Id: 468967 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.0 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, MIME_BASE64_TEXT, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 780DBC11F66 for ; Wed, 30 Jun 2021 22:57:44 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 5ECF6613D1 for ; Wed, 30 Jun 2021 22:57:44 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233294AbhF3XAM (ORCPT ); Wed, 30 Jun 2021 19:00:12 -0400 Received: from mout.gmx.net ([212.227.17.22]:50585 "EHLO mout.gmx.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232971AbhF3XAJ (ORCPT ); Wed, 30 Jun 2021 19:00:09 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gmx.net; s=badeba3b8450; t=1625093854; bh=NaS6NWz5hAdA8VokT4izVYKBa9VxtERQx5BuK2c9IAQ=; h=X-UI-Sender-Class:From:To:Cc:Subject:Date; b=RlylW0XqelHXjVtqZ1iveK6yupcBMLddJ+7sC0QDL+L1esOCH66bPz+LfFctCeabD fCtPz3LHPrrljwI1anhIoD1fQHUX49TS9O0ZS8C8r9KzF7KIsMGirKwAS/x205cByI v6CvVZTGgtJV5nVbQ/nwqLTTarIQcWkIiUG0nIJU= X-UI-Sender-Class: 01bb95c1-4bf8-414a-932a-4f6e2808ef9c Received: from Venus.fritz.box ([149.172.234.120]) by mail.gmx.net (mrgmx104 [212.227.17.168]) with ESMTPSA (Nemesis) id 1MORAU-1lZtWi15Q6-00PufC; Thu, 01 Jul 2021 00:57:34 +0200 From: Lino Sanfilippo To: gregkh@linuxfoundation.org Cc: linux@armlinux.org.uk, jirislaby@kernel.org, linux-serial@vger.kernel.org, linux-kernel@vger.kernel.org, Lino Sanfilippo Subject: [PATCH v3] serial: amba-pl011: add RS485 support Date: Thu, 1 Jul 2021 00:56:44 +0200 Message-Id: <20210630225644.3744-1-LinoSanfilippo@gmx.de> X-Mailer: git-send-email 2.31.1 MIME-Version: 1.0 X-Provags-ID: V03:K1:ESBFkLxIrFDj9kXAvmHFhGgX4laKVLsYxxcVI/y0GHzpb64AMsN SdJ2oFZWSkxsI3zYV2wAiIwfJGRz8/xtpmPG7e+8Fsbsocm0mGzNQkXWo0/QE1tnwTwuQsx HfUgzjx9rM+4v/XtK/Bp6hAL4ASUsF+bKVv8mJKIZ1OwzqCqnQcKRRV+zZA56ZcEwmD7Eqw xHPJKSy6zmigAHEhcs+GQ== X-UI-Out-Filterresults: notjunk:1; V03:K0:dXFoP/Shyb0=:ro6keEg/pfiGo4uRgsxQPl LV4j7nTZUXQcYbBnVi+OsU1CadjNmtpVMYWShQ0gXsaSXoMoAld9XRDwLpXCP0CiT/kv5sD0m PYVO6yM7+3MDPtypJt3/e58xIyxBFe4XigwabxAUc5caKoKP0dicO3k9sGT+ZwO67/rziMo2w lEHHbI4JroH2PLBfn1zgF/5vHT88UU2FptngtGhDewz29HT2zkxe2jPufmwJatNJtmMjWClkC IGlhKdmhoD6/qqJDpPcwK2p23FfVXSYtx5/3RwaCyNiFZqv3QJrFWvsl2gChUUvqv2ErmIRyA ltniOox2B7lCkmAc+m1j4gyZLdEryBr4QZ2l1rpMmO1G/G1qd5J+vEysk0FleQmCf469kncoZ z00g8AVmKbj431ldaFjjIciGP37+v9LYOuKzH9YN3+E2FQZy3XqKBuTDD5m2sTz/c0HveSEmD fef2tAxjnCTFdYfJDFE481ay4t8ocsXCOmvUti4QT9v7Bx5Ypy9IR0Qv6NgCmptzEFWpbjGev ZpBxM7gk9x/STDMPIGuk4/h/Nb6PNlnooTPb9j1hcBIhWNqD8PCA5YlLLe+NHOqx78b9zxaek iXGJU/Yf3ES9gBQzsWJMJZ85QhTa8pKqHwc55xs/iLAL8KJ/1OoajUYSy6f9Ekvo98BbMdWkA JgtEQSrO7qn65yoPQp0WF9snFNk/4dQYG1/YVCuQZcyIxkNvPFz1ytGgf8tWA6cdDN7vTJczc QbnoCGJazj0K2lhk0nHBkgLUdp4LNjaOUpvjfK1hqUGxYlUN2ucblsCewql76vTu6KSDUpYP9 SH4TCXtvlm7c/3dIUPEfmXCA/7MLmlagE3ia2ZOALD0s3tygV6sYO4LD04+aA1kNJBQUR54Yx 3IqjulDJ7h+BAVe3lnTFWsCWM6U0h0Yg232wN+//Kh05+o9M6TOMJmea/e6eN1OWLO+ThgC94 BQmMg7a3FQgmlg+O7lUXWhJOzsuoJoNHEbLl7kEXbdRFLnIIwF+8yKUy0+DsfhGeI4bNvSraT ZMHAZcQTYk1uc3uAoFYzmgl5hddicgcc0pVkFD03rfVVCQORpy+exJ2DNrPcGlRHQ9170pcIe 0A5QsoLF6pyBaMbEzxHbsqmYMxbnUBFG2iSa2mQs1W5HTKyAVjctI8sCw== Precedence: bulk List-ID: X-Mailing-List: linux-serial@vger.kernel.org Add basic support for RS485: Provide a callback to configure RS485 settings. Handle the RS485 specific part in the functions pl011_rs485_tx_start() and pl011_rs485_tx_stop() which extend the generic start/stop callbacks. Beside via IOCTL from userspace RS485 can be enabled by means of the device tree property "rs485-enabled-at-boot-time". Signed-off-by: Lino Sanfilippo --- Changes in V3: - avoid potential endless loop as requested by Greg - add blank lines before comments as requested by Greg - avoid double negation in if-statement as requested by Greg and suggested by Jiri Changes in v2: - clamp RTS delays to 100ms as suggested by Jiri Slaby - instead of counting bits "by hand" use the new function tty_get_frame_size() (also suggested by Jiri) - use the term RS485 consistently in the commit message - remove one blank line drivers/tty/serial/amba-pl011.c | 163 +++++++++++++++++++++++++++++++- 1 file changed, 161 insertions(+), 2 deletions(-) base-commit: 15279ebe99d7c6142d9f1a6ae4ded66c0f168678 diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index e14f3378b8a0..d361cd84ff8c 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -265,6 +265,8 @@ struct uart_amba_port { unsigned int old_cr; /* state during shutdown */ unsigned int fixed_baud; /* vendor-set fixed baud rate */ char type[12]; + bool rs485_tx_started; + unsigned int rs485_tx_drain_interval; /* usecs */ #ifdef CONFIG_DMA_ENGINE /* DMA stuff */ bool using_tx_dma; @@ -275,6 +277,8 @@ struct uart_amba_port { #endif }; +static unsigned int pl011_tx_empty(struct uart_port *port); + static unsigned int pl011_reg_to_offset(const struct uart_amba_port *uap, unsigned int reg) { @@ -1282,6 +1286,42 @@ static inline bool pl011_dma_rx_running(struct uart_amba_port *uap) #define pl011_dma_flush_buffer NULL #endif +static void pl011_rs485_tx_stop(struct uart_amba_port *uap) +{ + struct uart_port *port = &uap->port; + int i = 0; + u32 cr; + + /* Wait until hardware tx queue is empty */ + while (!pl011_tx_empty(port)) { + if (i == port->fifosize) { + dev_warn(port->dev, + "timeout while draining hardware tx queue\n"); + break; + } + + udelay(uap->rs485_tx_drain_interval); + i++; + } + + if (port->rs485.delay_rts_after_send) + mdelay(port->rs485.delay_rts_after_send); + + cr = pl011_read(uap, REG_CR); + + if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND) + cr &= ~UART011_CR_RTS; + else + cr |= UART011_CR_RTS; + + /* Disable the transmitter and reenable the transceiver */ + cr &= ~UART011_CR_TXE; + cr |= UART011_CR_RXE; + pl011_write(cr, uap, REG_CR); + + uap->rs485_tx_started = false; +} + static void pl011_stop_tx(struct uart_port *port) { struct uart_amba_port *uap = @@ -1290,6 +1330,9 @@ static void pl011_stop_tx(struct uart_port *port) uap->im &= ~UART011_TXIM; pl011_write(uap->im, uap, REG_IMSC); pl011_dma_tx_stop(uap); + + if ((port->rs485.flags & SER_RS485_ENABLED) && uap->rs485_tx_started) + pl011_rs485_tx_stop(uap); } static bool pl011_tx_chars(struct uart_amba_port *uap, bool from_irq); @@ -1380,6 +1423,32 @@ static bool pl011_tx_char(struct uart_amba_port *uap, unsigned char c, return true; } +static void pl011_rs485_tx_start(struct uart_amba_port *uap) +{ + struct uart_port *port = &uap->port; + u32 cr; + + /* Enable transmitter */ + cr = pl011_read(uap, REG_CR); + cr |= UART011_CR_TXE; + + /* Disable receiver if half-duplex */ + if (!(port->rs485.flags & SER_RS485_RX_DURING_TX)) + cr &= ~UART011_CR_RXE; + + if (port->rs485.flags & SER_RS485_RTS_ON_SEND) + cr &= ~UART011_CR_RTS; + else + cr |= UART011_CR_RTS; + + pl011_write(cr, uap, REG_CR); + + if (port->rs485.delay_rts_before_send) + mdelay(port->rs485.delay_rts_before_send); + + uap->rs485_tx_started = true; +} + /* Returns true if tx interrupts have to be (kept) enabled */ static bool pl011_tx_chars(struct uart_amba_port *uap, bool from_irq) { @@ -1397,6 +1466,10 @@ static bool pl011_tx_chars(struct uart_amba_port *uap, bool from_irq) return false; } + if ((uap->port.rs485.flags & SER_RS485_ENABLED) && + !uap->rs485_tx_started) + pl011_rs485_tx_start(uap); + /* If we are using DMA mode, try to send some characters. */ if (pl011_dma_tx_irq(uap)) return true; @@ -1542,6 +1615,9 @@ static void pl011_set_mctrl(struct uart_port *port, unsigned int mctrl) container_of(port, struct uart_amba_port, port); unsigned int cr; + if (port->rs485.flags & SER_RS485_ENABLED) + mctrl &= ~TIOCM_RTS; + cr = pl011_read(uap, REG_CR); #define TIOCMBIT(tiocmbit, uartbit) \ @@ -1763,7 +1839,17 @@ static int pl011_startup(struct uart_port *port) /* restore RTS and DTR */ cr = uap->old_cr & (UART011_CR_RTS | UART011_CR_DTR); - cr |= UART01x_CR_UARTEN | UART011_CR_RXE | UART011_CR_TXE; + cr |= UART01x_CR_UARTEN | UART011_CR_RXE; + + if (port->rs485.flags & SER_RS485_ENABLED) { + if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND) + cr &= ~UART011_CR_RTS; + else + cr |= UART011_CR_RTS; + } else { + cr |= UART011_CR_TXE; + } + pl011_write(cr, uap, REG_CR); spin_unlock_irq(&uap->port.lock); @@ -1864,6 +1950,9 @@ static void pl011_shutdown(struct uart_port *port) pl011_dma_shutdown(uap); + if ((port->rs485.flags & SER_RS485_ENABLED) && uap->rs485_tx_started) + pl011_rs485_tx_stop(uap); + free_irq(uap->port.irq, uap); pl011_disable_uart(uap); @@ -1941,6 +2030,7 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios, unsigned int lcr_h, old_cr; unsigned long flags; unsigned int baud, quot, clkdiv; + unsigned int bits; if (uap->vendor->oversampling) clkdiv = 8; @@ -1991,6 +2081,8 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios, if (uap->fifosize > 1) lcr_h |= UART01x_LCRH_FEN; + bits = tty_get_frame_size(termios->c_cflag); + spin_lock_irqsave(&port->lock, flags); /* @@ -1998,11 +2090,21 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios, */ uart_update_timeout(port, termios->c_cflag, baud); + /* + * Calculate the approximated time it takes to transmit one character + * with the given baud rate. We use this as the poll interval when we + * wait for the tx queue to empty. + */ + uap->rs485_tx_drain_interval = (bits * 1000 * 1000) / baud; + pl011_setup_status_masks(port, termios); if (UART_ENABLE_MS(port, termios->c_cflag)) pl011_enable_ms(port); + if (port->rs485.flags & SER_RS485_ENABLED) + termios->c_cflag &= ~CRTSCTS; + /* first, disable everything */ old_cr = pl011_read(uap, REG_CR); pl011_write(0, uap, REG_CR); @@ -2124,6 +2226,41 @@ static int pl011_verify_port(struct uart_port *port, struct serial_struct *ser) return ret; } +static int pl011_rs485_config(struct uart_port *port, + struct serial_rs485 *rs485) +{ + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); + + /* pick sane settings if the user hasn't */ + if (!(rs485->flags & SER_RS485_RTS_ON_SEND) == + !(rs485->flags & SER_RS485_RTS_AFTER_SEND)) { + rs485->flags |= SER_RS485_RTS_ON_SEND; + rs485->flags &= ~SER_RS485_RTS_AFTER_SEND; + } + /* clamp the delays to [0, 100ms] */ + rs485->delay_rts_before_send = min(rs485->delay_rts_before_send, 100U); + rs485->delay_rts_after_send = min(rs485->delay_rts_after_send, 100U); + memset(rs485->padding, 0, sizeof(rs485->padding)); + + if (port->rs485.flags & SER_RS485_ENABLED) + pl011_rs485_tx_stop(uap); + + /* Set new configuration */ + port->rs485 = *rs485; + + /* Make sure auto RTS is disabled */ + if (port->rs485.flags & SER_RS485_ENABLED) { + u32 cr = pl011_read(uap, REG_CR); + + cr &= ~UART011_CR_RTSEN; + pl011_write(cr, uap, REG_CR); + port->status &= ~UPSTAT_AUTORTS; + } + + return 0; +} + static const struct uart_ops amba_pl011_pops = { .tx_empty = pl011_tx_empty, .set_mctrl = pl011_set_mctrl, @@ -2588,10 +2725,28 @@ static int pl011_find_free_port(void) return -EBUSY; } +static int pl011_get_rs485_mode(struct uart_amba_port *uap) +{ + struct uart_port *port = &uap->port; + struct serial_rs485 *rs485 = &port->rs485; + int ret; + + ret = uart_get_rs485_mode(port); + if (ret) + return ret; + + /* clamp the delays to [0, 100ms] */ + rs485->delay_rts_before_send = min(rs485->delay_rts_before_send, 100U); + rs485->delay_rts_after_send = min(rs485->delay_rts_after_send, 100U); + + return 0; +} + static int pl011_setup_port(struct device *dev, struct uart_amba_port *uap, struct resource *mmiobase, int index) { void __iomem *base; + int ret; base = devm_ioremap_resource(dev, mmiobase); if (IS_ERR(base)) @@ -2608,6 +2763,10 @@ static int pl011_setup_port(struct device *dev, struct uart_amba_port *uap, uap->port.flags = UPF_BOOT_AUTOCONF; uap->port.line = index; + ret = pl011_get_rs485_mode(uap); + if (ret) + return ret; + amba_ports[index] = uap; return 0; @@ -2665,7 +2824,7 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) uap->port.iotype = vendor->access_32b ? UPIO_MEM32 : UPIO_MEM; uap->port.irq = dev->irq[0]; uap->port.ops = &amba_pl011_pops; - + uap->port.rs485_config = pl011_rs485_config; snprintf(uap->type, sizeof(uap->type), "PL011 rev%u", amba_rev(dev)); ret = pl011_setup_port(&dev->dev, uap, &dev->res, portnr);