Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/revpi 1644/mainline rs485 support #37

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 58 additions & 33 deletions drivers/tty/serial/amba-pl011.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
#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 @@ -266,8 +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 long rs485_tx_drain_interval; /* average tx queue drain time in usecs */
bool rs485_tx_started;
unsigned int rs485_tx_drain_interval; /* usecs */
#ifdef CONFIG_DMA_ENGINE
/* DMA stuff */
bool using_tx_dma;
Expand Down Expand Up @@ -1289,14 +1288,23 @@ 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)
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))
/* 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);
Expand All @@ -1308,13 +1316,12 @@ static int pl011_rs485_tx_stop(struct uart_amba_port *uap)
else
cr |= UART011_CR_RTS;

cr &= ~UART011_CR_TXE; /* disable transmitter */
cr |= UART011_CR_RXE; /* enable receiver */
/* 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;

return 0;
}

static void pl011_stop_tx(struct uart_port *port)
Expand Down Expand Up @@ -1449,17 +1456,18 @@ 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; /* re-enable transmitter */
cr |= UART011_CR_TXE;

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

if (port->rs485.flags & SER_RS485_RTS_ON_SEND) {
if (port->rs485.flags & SER_RS485_RTS_ON_SEND)
cr &= ~UART011_CR_RTS;
} else {
else
cr |= UART011_CR_RTS;
}

pl011_write(cr, uap, REG_CR);

Expand Down Expand Up @@ -2071,6 +2079,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;
Expand Down Expand Up @@ -2121,18 +2130,21 @@ 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);

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

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

Expand Down Expand Up @@ -2268,37 +2280,33 @@ static int pl011_rs485_config(struct uart_port *port,
{
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)) {
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);
/* 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;
terminate = !!(rs485->flags & SER_RS485_TERMINATE_BUS);
}

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

return 0;
}

Expand Down Expand Up @@ -2791,6 +2799,23 @@ 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)
{
Expand All @@ -2817,7 +2842,7 @@ 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);
ret = pl011_get_rs485_mode(uap);
if (ret)
return ret;

Expand Down
30 changes: 3 additions & 27 deletions drivers/tty/serial/serial_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -334,39 +334,15 @@ void
uart_update_timeout(struct uart_port *port, unsigned int cflag,
unsigned int baud)
{
unsigned int bits;
unsigned int size;

/* byte size and parity */
switch (cflag & CSIZE) {
case CS5:
bits = 7;
break;
case CS6:
bits = 8;
break;
case CS7:
bits = 9;
break;
default:
bits = 10;
break; /* CS8 */
}

if (cflag & CSTOPB)
bits++;
if (cflag & PARENB)
bits++;

/*
* The total number of bits to be transmitted in the fifo.
*/
bits = bits * port->fifosize;
size = tty_get_frame_size(cflag) * port->fifosize;

/*
* Figure the timeout to send the above number of bits.
* Add .02 seconds of slop
*/
port->timeout = (HZ * bits) / baud + HZ/50;
port->timeout = (HZ * size) / baud + HZ/50;
}

EXPORT_SYMBOL(uart_update_timeout);
Expand Down
45 changes: 45 additions & 0 deletions drivers/tty/tty_ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,51 @@ int tty_termios_hw_change(const struct ktermios *a, const struct ktermios *b)
}
EXPORT_SYMBOL(tty_termios_hw_change);

/**
* tty_get_char_size - get size of a character
* @cflag: termios cflag value
*
* Get the size (in bits) of a character depending on @cflag's %CSIZE
* setting.
*/
unsigned char tty_get_char_size(unsigned int cflag)
{
switch (cflag & CSIZE) {
case CS5:
return 5;
case CS6:
return 6;
case CS7:
return 7;
case CS8:
default:
return 8;
}
}
EXPORT_SYMBOL_GPL(tty_get_char_size);

/**
* tty_get_frame_size - get size of a frame
* @cflag: termios cflag value
*
* Get the size (in bits) of a frame depending on @cflag's %CSIZE, %CSTOPB,
* and %PARENB setting. The result is a sum of character size, start and
* stop bits -- one bit each -- second stop bit (if set), and parity bit
* (if set).
*/
unsigned char tty_get_frame_size(unsigned int cflag)
{
unsigned char bits = 2 + tty_get_char_size(cflag);

if (cflag & CSTOPB)
bits++;
if (cflag & PARENB)
bits++;

return bits;
}
EXPORT_SYMBOL_GPL(tty_get_frame_size);

/**
* tty_set_termios - update termios values
* @tty: tty to update
Expand Down
3 changes: 3 additions & 0 deletions include/linux/tty.h
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,9 @@ static inline speed_t tty_get_baud_rate(struct tty_struct *tty)
return tty_termios_baud_rate(&tty->termios);
}

unsigned char tty_get_char_size(unsigned int cflag);
unsigned char tty_get_frame_size(unsigned int cflag);

extern void tty_termios_copy_hw(struct ktermios *new, struct ktermios *old);
extern int tty_termios_hw_change(const struct ktermios *a, const struct ktermios *b);
extern int tty_set_termios(struct tty_struct *tty, struct ktermios *kt);
Expand Down