Skip to content

Commit

Permalink
serial: amba-pl011: add RS485 support
Browse files Browse the repository at this point in the history
Add basic RS485 support. This is loosely based on an implementation by Lukas
Wunner.

Signed-off-by: Lino Sanfilippo <l.sanfilippo@kunbus.com>
  • Loading branch information
linosanfilippo-kunbus authored and Philipp Rosenberger committed Oct 25, 2021
1 parent 20ee10b commit d6e42b6
Showing 1 changed file with 136 additions and 2 deletions.
138 changes: 136 additions & 2 deletions drivers/tty/serial/amba-pl011.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include <linux/sizes.h>
#include <linux/io.h>
#include <linux/acpi.h>
#include <linux/gpio/consumer.h>

#include "amba-pl011.h"

Expand Down Expand Up @@ -265,6 +266,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 long rs485_tx_drain_interval; /* average tx queue drain time in usecs */
#ifdef CONFIG_DMA_ENGINE
/* DMA stuff */
bool using_tx_dma;
Expand All @@ -275,6 +278,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)
{
Expand Down Expand Up @@ -1284,6 +1289,34 @@ static inline bool pl011_dma_rx_running(struct uart_amba_port *uap)
#define pl011_dma_flush_buffer NULL
#endif

static int pl011_rs485_tx_stop(struct uart_amba_port *uap)
{
struct uart_port *port = &uap->port;
u32 cr;

/* wait until hardware tx queue is empty */
while (!pl011_tx_empty(port))
udelay(uap->rs485_tx_drain_interval);

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;

cr &= ~UART011_CR_TXE; /* disable transmitter */
cr |= UART011_CR_RXE; /* enable receiver */
pl011_write(cr, uap, REG_CR);

uap->rs485_tx_started = false;

return 0;
}

static void pl011_stop_tx(struct uart_port *port)
{
struct uart_amba_port *uap =
Expand All @@ -1292,6 +1325,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);
Expand Down Expand Up @@ -1408,6 +1444,31 @@ 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;

cr = pl011_read(uap, REG_CR);
cr |= UART011_CR_TXE; /* re-enable transmitter */

if (!(port->rs485.flags & SER_RS485_RX_DURING_TX))
cr &= ~UART011_CR_RXE; /* disable receiver if half-duplex */

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)
{
Expand All @@ -1425,6 +1486,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;
Expand Down Expand Up @@ -1574,6 +1639,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) \
Expand Down Expand Up @@ -1812,7 +1880,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);
Expand Down Expand Up @@ -1913,6 +1991,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);
Expand Down Expand Up @@ -2046,12 +2127,21 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios,
* Update the per-port timeout.
*/
uart_update_timeout(port, termios->c_cflag, baud);
/* Calculate the approximated maximum time it takes to drain the tx
hardware queue with the given baud rate. Then use the half of this
time to get the approximated time for the average case and use this
as the poll interval when waiting for the queue to empty. */
uap->rs485_tx_drain_interval = jiffies_to_usecs(port->timeout) /
uap->fifosize / 2;

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);
Expand Down Expand Up @@ -2173,6 +2263,45 @@ 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);
bool terminate = false;

/* 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;
}

rs485->delay_rts_before_send = min(rs485->delay_rts_before_send, 1000U);
rs485->delay_rts_after_send = min(rs485->delay_rts_after_send, 1000U);
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;
terminate = !!(rs485->flags & SER_RS485_TERMINATE_BUS);
}

if (port->rs485_term_gpio)
gpiod_set_value_cansleep(port->rs485_term_gpio, terminate);

return 0;
}

static const struct uart_ops amba_pl011_pops = {
.tx_empty = pl011_tx_empty,
.set_mctrl = pl011_set_mctrl,
Expand Down Expand Up @@ -2666,6 +2795,7 @@ 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))
Expand All @@ -2687,6 +2817,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 = uart_get_rs485_mode(&uap->port);
if (ret)
return ret;

amba_ports[index] = uap;

return 0;
Expand Down Expand Up @@ -2744,7 +2878,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);
Expand Down

0 comments on commit d6e42b6

Please sign in to comment.