mirror of
https://github.com/torvalds/linux.git
synced 2024-11-01 13:03:25 +01:00
tty: serial: uartps: Add rs485 support to uartps driver
Add rs485 support to uartps driver. Use either rts-gpios or RTS to control RS485 phy as driver or a receiver. Signed-off-by: Manikanta Guntupalli <manikanta.guntupalli@amd.com> Link: https://lore.kernel.org/r/20240123061655.2150946-4-manikanta.guntupalli@amd.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
74231ab6cc
commit
fccc9d9233
1 changed files with 196 additions and 4 deletions
|
@ -22,7 +22,9 @@
|
|||
#include <linux/of.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#define CDNS_UART_TTY_NAME "ttyPS"
|
||||
#define CDNS_UART_NAME "xuartps"
|
||||
|
@ -193,6 +195,9 @@ MODULE_PARM_DESC(rx_timeout, "Rx timeout, 1-255");
|
|||
* @clk_rate_change_nb: Notifier block for clock changes
|
||||
* @quirks: Flags for RXBS support.
|
||||
* @cts_override: Modem control state override
|
||||
* @gpiod_rts: Pointer to the gpio descriptor
|
||||
* @rs485_tx_started: RS485 tx state
|
||||
* @tx_timer: Timer for tx
|
||||
*/
|
||||
struct cdns_uart {
|
||||
struct uart_port *port;
|
||||
|
@ -203,10 +208,21 @@ struct cdns_uart {
|
|||
struct notifier_block clk_rate_change_nb;
|
||||
u32 quirks;
|
||||
bool cts_override;
|
||||
struct gpio_desc *gpiod_rts;
|
||||
bool rs485_tx_started;
|
||||
struct hrtimer tx_timer;
|
||||
};
|
||||
struct cdns_platform_data {
|
||||
u32 quirks;
|
||||
};
|
||||
|
||||
struct serial_rs485 cdns_rs485_supported = {
|
||||
.flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND |
|
||||
SER_RS485_RTS_AFTER_SEND,
|
||||
.delay_rts_before_send = 1,
|
||||
.delay_rts_after_send = 1,
|
||||
};
|
||||
|
||||
#define to_cdns_uart(_nb) container_of(_nb, struct cdns_uart, \
|
||||
clk_rate_change_nb)
|
||||
|
||||
|
@ -305,6 +321,55 @@ static void cdns_uart_handle_rx(void *dev_id, unsigned int isrstatus)
|
|||
tty_flip_buffer_push(&port->state->port);
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_rts_gpio_enable - Configure RTS/GPIO to high/low
|
||||
* @cdns_uart: Handle to the cdns_uart
|
||||
* @enable: Value to be set to RTS/GPIO
|
||||
*/
|
||||
static void cdns_rts_gpio_enable(struct cdns_uart *cdns_uart, bool enable)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
if (cdns_uart->gpiod_rts) {
|
||||
gpiod_set_value(cdns_uart->gpiod_rts, enable);
|
||||
} else {
|
||||
val = readl(cdns_uart->port->membase + CDNS_UART_MODEMCR);
|
||||
if (enable)
|
||||
val |= CDNS_UART_MODEMCR_RTS;
|
||||
else
|
||||
val &= ~CDNS_UART_MODEMCR_RTS;
|
||||
writel(val, cdns_uart->port->membase + CDNS_UART_MODEMCR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_rs485_tx_setup - Tx setup specific to rs485
|
||||
* @cdns_uart: Handle to the cdns_uart
|
||||
*/
|
||||
static void cdns_rs485_tx_setup(struct cdns_uart *cdns_uart)
|
||||
{
|
||||
bool enable;
|
||||
|
||||
enable = cdns_uart->port->rs485.flags & SER_RS485_RTS_ON_SEND;
|
||||
cdns_rts_gpio_enable(cdns_uart, enable);
|
||||
|
||||
cdns_uart->rs485_tx_started = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_rs485_rx_setup - Rx setup specific to rs485
|
||||
* @cdns_uart: Handle to the cdns_uart
|
||||
*/
|
||||
static void cdns_rs485_rx_setup(struct cdns_uart *cdns_uart)
|
||||
{
|
||||
bool enable;
|
||||
|
||||
enable = cdns_uart->port->rs485.flags & SER_RS485_RTS_AFTER_SEND;
|
||||
cdns_rts_gpio_enable(cdns_uart, enable);
|
||||
|
||||
cdns_uart->rs485_tx_started = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_uart_tx_empty - Check whether TX is empty
|
||||
* @port: Handle to the uart port structure
|
||||
|
@ -320,6 +385,37 @@ static unsigned int cdns_uart_tx_empty(struct uart_port *port)
|
|||
return (status == CDNS_UART_SR_TXEMPTY) ? TIOCSER_TEMT : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_rs485_rx_callback - Timer rx callback handler for rs485.
|
||||
* @t: Handle to the hrtimer structure
|
||||
*/
|
||||
static enum hrtimer_restart cdns_rs485_rx_callback(struct hrtimer *t)
|
||||
{
|
||||
struct cdns_uart *cdns_uart = container_of(t, struct cdns_uart, tx_timer);
|
||||
|
||||
/*
|
||||
* Default Rx should be setup, because Rx signaling path
|
||||
* need to enable to receive data.
|
||||
*/
|
||||
cdns_rs485_rx_setup(cdns_uart);
|
||||
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_calc_after_tx_delay - calculate delay required for after tx.
|
||||
* @cdns_uart: Handle to the cdns_uart
|
||||
*/
|
||||
static u64 cdns_calc_after_tx_delay(struct cdns_uart *cdns_uart)
|
||||
{
|
||||
/*
|
||||
* Frame time + stop bit time + rs485.delay_rts_after_send
|
||||
*/
|
||||
return cdns_uart->port->frame_time
|
||||
+ DIV_ROUND_UP(cdns_uart->port->frame_time, 7)
|
||||
+ (u64)cdns_uart->port->rs485.delay_rts_after_send * NSEC_PER_MSEC;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_uart_handle_tx - Handle the bytes to be transmitted.
|
||||
* @dev_id: Id of the UART port
|
||||
|
@ -328,6 +424,7 @@ static unsigned int cdns_uart_tx_empty(struct uart_port *port)
|
|||
static void cdns_uart_handle_tx(void *dev_id)
|
||||
{
|
||||
struct uart_port *port = (struct uart_port *)dev_id;
|
||||
struct cdns_uart *cdns_uart = port->private_data;
|
||||
struct circ_buf *xmit = &port->state->xmit;
|
||||
unsigned int numbytes;
|
||||
|
||||
|
@ -348,6 +445,16 @@ static void cdns_uart_handle_tx(void *dev_id)
|
|||
|
||||
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
|
||||
uart_write_wakeup(port);
|
||||
|
||||
/* Enable the TX Empty interrupt */
|
||||
writel(CDNS_UART_IXR_TXEMPTY, cdns_uart->port->membase + CDNS_UART_IER);
|
||||
|
||||
if (cdns_uart->port->rs485.flags & SER_RS485_ENABLED &&
|
||||
(uart_circ_empty(xmit) || uart_tx_stopped(port))) {
|
||||
cdns_uart->tx_timer.function = &cdns_rs485_rx_callback;
|
||||
hrtimer_start(&cdns_uart->tx_timer,
|
||||
ns_to_ktime(cdns_calc_after_tx_delay(cdns_uart)), HRTIMER_MODE_REL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -580,6 +687,21 @@ static int cdns_uart_clk_notifier_cb(struct notifier_block *nb,
|
|||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* cdns_rs485_tx_callback - Timer tx callback handler for rs485.
|
||||
* @t: Handle to the hrtimer structure
|
||||
*/
|
||||
static enum hrtimer_restart cdns_rs485_tx_callback(struct hrtimer *t)
|
||||
{
|
||||
struct cdns_uart *cdns_uart = container_of(t, struct cdns_uart, tx_timer);
|
||||
|
||||
uart_port_lock(cdns_uart->port);
|
||||
cdns_uart_handle_tx(cdns_uart->port);
|
||||
uart_port_unlock(cdns_uart->port);
|
||||
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_uart_start_tx - Start transmitting bytes
|
||||
* @port: Handle to the uart port structure
|
||||
|
@ -587,6 +709,7 @@ static int cdns_uart_clk_notifier_cb(struct notifier_block *nb,
|
|||
static void cdns_uart_start_tx(struct uart_port *port)
|
||||
{
|
||||
unsigned int status;
|
||||
struct cdns_uart *cdns_uart = port->private_data;
|
||||
|
||||
if (uart_tx_stopped(port))
|
||||
return;
|
||||
|
@ -606,10 +729,16 @@ static void cdns_uart_start_tx(struct uart_port *port)
|
|||
/* Clear the TX Empty interrupt */
|
||||
writel(CDNS_UART_IXR_TXEMPTY, port->membase + CDNS_UART_ISR);
|
||||
|
||||
if (cdns_uart->port->rs485.flags & SER_RS485_ENABLED) {
|
||||
if (!cdns_uart->rs485_tx_started) {
|
||||
cdns_uart->tx_timer.function = &cdns_rs485_tx_callback;
|
||||
cdns_rs485_tx_setup(cdns_uart);
|
||||
return hrtimer_start(&cdns_uart->tx_timer,
|
||||
ms_to_ktime(port->rs485.delay_rts_before_send),
|
||||
HRTIMER_MODE_REL);
|
||||
}
|
||||
}
|
||||
cdns_uart_handle_tx(port);
|
||||
|
||||
/* Enable the TX Empty interrupt */
|
||||
writel(CDNS_UART_IXR_TXEMPTY, port->membase + CDNS_UART_IER);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -619,6 +748,10 @@ static void cdns_uart_start_tx(struct uart_port *port)
|
|||
static void cdns_uart_stop_tx(struct uart_port *port)
|
||||
{
|
||||
unsigned int regval;
|
||||
struct cdns_uart *cdns_uart = port->private_data;
|
||||
|
||||
if (cdns_uart->port->rs485.flags & SER_RS485_ENABLED)
|
||||
cdns_rs485_rx_setup(cdns_uart);
|
||||
|
||||
regval = readl(port->membase + CDNS_UART_CR);
|
||||
regval |= CDNS_UART_CR_TX_DIS;
|
||||
|
@ -831,6 +964,9 @@ static int cdns_uart_startup(struct uart_port *port)
|
|||
(CDNS_UART_CR_TXRST | CDNS_UART_CR_RXRST))
|
||||
cpu_relax();
|
||||
|
||||
if (cdns_uart->port->rs485.flags & SER_RS485_ENABLED)
|
||||
cdns_rs485_rx_setup(cdns_uart);
|
||||
|
||||
/*
|
||||
* Clear the RX disable bit and then set the RX enable bit to enable
|
||||
* the receiver.
|
||||
|
@ -890,6 +1026,10 @@ static void cdns_uart_shutdown(struct uart_port *port)
|
|||
{
|
||||
int status;
|
||||
unsigned long flags;
|
||||
struct cdns_uart *cdns_uart = port->private_data;
|
||||
|
||||
if (cdns_uart->port->rs485.flags & SER_RS485_ENABLED)
|
||||
hrtimer_cancel(&cdns_uart->tx_timer);
|
||||
|
||||
uart_port_lock_irqsave(port, &flags);
|
||||
|
||||
|
@ -1035,6 +1175,8 @@ static void cdns_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
|
|||
|
||||
if (mctrl & TIOCM_RTS)
|
||||
val |= CDNS_UART_MODEMCR_RTS;
|
||||
if (cdns_uart_data->gpiod_rts)
|
||||
gpiod_set_value(cdns_uart_data->gpiod_rts, !(mctrl & TIOCM_RTS));
|
||||
if (mctrl & TIOCM_DTR)
|
||||
val |= CDNS_UART_MODEMCR_DTR;
|
||||
if (mctrl & TIOCM_LOOP)
|
||||
|
@ -1457,6 +1599,39 @@ MODULE_DEVICE_TABLE(of, cdns_uart_of_match);
|
|||
/* Temporary variable for storing number of instances */
|
||||
static int instances;
|
||||
|
||||
/**
|
||||
* cdns_rs485_config - Called when an application calls TIOCSRS485 ioctl.
|
||||
* @port: Pointer to the uart_port structure
|
||||
* @termios: Pointer to the ktermios structure
|
||||
* @rs485: Pointer to the serial_rs485 structure
|
||||
*
|
||||
* Return: 0
|
||||
*/
|
||||
static int cdns_rs485_config(struct uart_port *port, struct ktermios *termios,
|
||||
struct serial_rs485 *rs485)
|
||||
{
|
||||
u32 val;
|
||||
struct cdns_uart *cdns_uart = port->private_data;
|
||||
|
||||
if (rs485->flags & SER_RS485_ENABLED) {
|
||||
dev_dbg(port->dev, "Setting UART to RS485\n");
|
||||
/* Make sure auto RTS is disabled */
|
||||
val = readl(port->membase + CDNS_UART_MODEMCR);
|
||||
val &= ~CDNS_UART_MODEMCR_FCM;
|
||||
writel(val, port->membase + CDNS_UART_MODEMCR);
|
||||
|
||||
/* Timer setup */
|
||||
hrtimer_init(&cdns_uart->tx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
cdns_uart->tx_timer.function = &cdns_rs485_tx_callback;
|
||||
|
||||
/* Disable transmitter and make Rx setup*/
|
||||
cdns_uart_stop_tx(port);
|
||||
} else {
|
||||
hrtimer_cancel(&cdns_uart->tx_timer);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_uart_probe - Platform driver probe
|
||||
* @pdev: Pointer to the platform device structure
|
||||
|
@ -1599,9 +1774,23 @@ static int cdns_uart_probe(struct platform_device *pdev)
|
|||
port->private_data = cdns_uart_data;
|
||||
port->read_status_mask = CDNS_UART_IXR_TXEMPTY | CDNS_UART_IXR_RXTRIG |
|
||||
CDNS_UART_IXR_OVERRUN | CDNS_UART_IXR_TOUT;
|
||||
port->rs485_config = cdns_rs485_config;
|
||||
port->rs485_supported = cdns_rs485_supported;
|
||||
cdns_uart_data->port = port;
|
||||
platform_set_drvdata(pdev, port);
|
||||
|
||||
rc = uart_get_rs485_mode(port);
|
||||
if (rc)
|
||||
goto err_out_clk_notifier;
|
||||
|
||||
cdns_uart_data->gpiod_rts = devm_gpiod_get_optional(&pdev->dev, "rts",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(cdns_uart_data->gpiod_rts)) {
|
||||
rc = PTR_ERR(cdns_uart_data->gpiod_rts);
|
||||
dev_err(port->dev, "xuartps: devm_gpiod_get_optional failed\n");
|
||||
goto err_out_clk_notifier;
|
||||
}
|
||||
|
||||
pm_runtime_use_autosuspend(&pdev->dev);
|
||||
pm_runtime_set_autosuspend_delay(&pdev->dev, UART_AUTOSUSPEND_TIMEOUT);
|
||||
pm_runtime_set_active(&pdev->dev);
|
||||
|
@ -1620,6 +1809,8 @@ static int cdns_uart_probe(struct platform_device *pdev)
|
|||
console_port = port;
|
||||
}
|
||||
#endif
|
||||
if (cdns_uart_data->port->rs485.flags & SER_RS485_ENABLED)
|
||||
cdns_rs485_rx_setup(cdns_uart_data);
|
||||
|
||||
rc = uart_add_one_port(&cdns_uart_uart_driver, port);
|
||||
if (rc) {
|
||||
|
@ -1648,6 +1839,7 @@ static int cdns_uart_probe(struct platform_device *pdev)
|
|||
pm_runtime_disable(&pdev->dev);
|
||||
pm_runtime_set_suspended(&pdev->dev);
|
||||
pm_runtime_dont_use_autosuspend(&pdev->dev);
|
||||
err_out_clk_notifier:
|
||||
#ifdef CONFIG_COMMON_CLK
|
||||
clk_notifier_unregister(cdns_uart_data->uartclk,
|
||||
&cdns_uart_data->clk_rate_change_nb);
|
||||
|
|
Loading…
Reference in a new issue