summaryrefslogtreecommitdiff
path: root/drivers/tty/serial/st-asc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/tty/serial/st-asc.c')
-rw-r--r--drivers/tty/serial/st-asc.c102
1 files changed, 93 insertions, 9 deletions
diff --git a/drivers/tty/serial/st-asc.c b/drivers/tty/serial/st-asc.c
index 379e5bd37df9..c334bcc59c64 100644
--- a/drivers/tty/serial/st-asc.c
+++ b/drivers/tty/serial/st-asc.c
@@ -18,6 +18,7 @@
#include <linux/serial.h>
#include <linux/console.h>
#include <linux/sysrq.h>
+#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/irq.h>
@@ -30,15 +31,23 @@
#include <linux/of_platform.h>
#include <linux/serial_core.h>
#include <linux/clk.h>
+#include <linux/gpio/consumer.h>
#define DRIVER_NAME "st-asc"
#define ASC_SERIAL_NAME "ttyAS"
#define ASC_FIFO_SIZE 16
#define ASC_MAX_PORTS 8
+/* Pinctrl states */
+#define DEFAULT 0
+#define NO_HW_FLOWCTRL 1
+
struct asc_port {
struct uart_port port;
+ struct gpio_desc *rts;
struct clk *clk;
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *states[2];
unsigned int hw_flow_control:1;
unsigned int force_m1:1;
};
@@ -287,9 +296,19 @@ static void asc_transmit_chars(struct uart_port *port)
static void asc_receive_chars(struct uart_port *port)
{
struct tty_port *tport = &port->state->port;
- unsigned long status;
+ unsigned long status, mode;
unsigned long c = 0;
char flag;
+ bool ignore_pe = false;
+
+ /*
+ * Datasheet states: If the MODE field selects an 8-bit frame then
+ * this [parity error] bit is undefined. Software should ignore this
+ * bit when reading 8-bit frames.
+ */
+ mode = asc_in(port, ASC_CTL) & ASC_CTL_MODE_MSK;
+ if (mode == ASC_CTL_MODE_8BIT || mode == ASC_CTL_MODE_8BIT_PAR)
+ ignore_pe = true;
if (port->irq_wake)
pm_wakeup_event(tport->tty->dev, 0);
@@ -299,8 +318,8 @@ static void asc_receive_chars(struct uart_port *port)
flag = TTY_NORMAL;
port->icount.rx++;
- if ((c & (ASC_RXBUF_FE | ASC_RXBUF_PE)) ||
- status & ASC_STA_OE) {
+ if (status & ASC_STA_OE || c & ASC_RXBUF_FE ||
+ (c & ASC_RXBUF_PE && !ignore_pe)) {
if (c & ASC_RXBUF_FE) {
if (c == (ASC_RXBUF_FE | ASC_RXBUF_DUMMY_RX)) {
@@ -381,12 +400,27 @@ static unsigned int asc_tx_empty(struct uart_port *port)
static void asc_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
+ struct asc_port *ascport = to_asc_port(port);
+
/*
- * This routine is used for seting signals of: DTR, DCD, CTS/RTS
- * We use ASC's hardware for CTS/RTS, so don't need any for that.
- * Some boards have DTR and DCD implemented using PIO pins,
- * code to do this should be hooked in here.
+ * This routine is used for seting signals of: DTR, DCD, CTS and RTS.
+ * We use ASC's hardware for CTS/RTS when hardware flow-control is
+ * enabled, however if the RTS line is required for another purpose,
+ * commonly controlled using HUP from userspace, then we need to toggle
+ * it manually, using GPIO.
+ *
+ * Some boards also have DTR and DCD implemented using PIO pins, code to
+ * do this should be hooked in here.
*/
+
+ if (!ascport->rts)
+ return;
+
+ /* If HW flow-control is enabled, we can't fiddle with the RTS line */
+ if (asc_in(port, ASC_CTL) & ASC_CTL_CTSENABLE)
+ return;
+
+ gpiod_set_value(ascport->rts, mctrl & TIOCM_RTS);
}
static unsigned int asc_get_mctrl(struct uart_port *port)
@@ -479,6 +513,8 @@ static void asc_set_termios(struct uart_port *port, struct ktermios *termios,
struct ktermios *old)
{
struct asc_port *ascport = to_asc_port(port);
+ struct device_node *np = port->dev->of_node;
+ struct gpio_desc *gpiod;
unsigned int baud;
u32 ctrl_val;
tcflag_t cflag;
@@ -522,9 +558,33 @@ static void asc_set_termios(struct uart_port *port, struct ktermios *termios,
ctrl_val |= ASC_CTL_PARITYODD;
/* hardware flow control */
- if ((cflag & CRTSCTS))
+ if ((cflag & CRTSCTS)) {
ctrl_val |= ASC_CTL_CTSENABLE;
+ /* If flow-control selected, stop handling RTS manually */
+ if (ascport->rts) {
+ devm_gpiod_put(port->dev, ascport->rts);
+ ascport->rts = NULL;
+
+ pinctrl_select_state(ascport->pinctrl,
+ ascport->states[DEFAULT]);
+ }
+ } else {
+ /* If flow-control disabled, it's safe to handle RTS manually */
+ if (!ascport->rts && ascport->states[NO_HW_FLOWCTRL]) {
+ pinctrl_select_state(ascport->pinctrl,
+ ascport->states[NO_HW_FLOWCTRL]);
+
+ gpiod = devm_fwnode_get_gpiod_from_child(port->dev,
+ "rts",
+ &np->fwnode,
+ GPIOD_OUT_LOW,
+ np->name);
+ if (!IS_ERR(gpiod))
+ ascport->rts = gpiod;
+ }
+ }
+
if ((baud < 19200) && !ascport->force_m1) {
asc_out(port, ASC_BAUDRATE, (port->uartclk / (16 * baud)));
} else {
@@ -667,6 +727,7 @@ static int asc_init_port(struct asc_port *ascport,
{
struct uart_port *port = &ascport->port;
struct resource *res;
+ int ret;
port->iotype = UPIO_MEM;
port->flags = UPF_BOOT_AUTOCONF;
@@ -693,6 +754,27 @@ static int asc_init_port(struct asc_port *ascport,
WARN_ON(ascport->port.uartclk == 0);
clk_disable_unprepare(ascport->clk);
+ ascport->pinctrl = devm_pinctrl_get(&pdev->dev);
+ if (IS_ERR(ascport->pinctrl)) {
+ ret = PTR_ERR(ascport->pinctrl);
+ dev_err(&pdev->dev, "Failed to get Pinctrl: %d\n", ret);
+ }
+
+ ascport->states[DEFAULT] =
+ pinctrl_lookup_state(ascport->pinctrl, "default");
+ if (IS_ERR(ascport->states[DEFAULT])) {
+ ret = PTR_ERR(ascport->states[DEFAULT]);
+ dev_err(&pdev->dev,
+ "Failed to look up Pinctrl state 'default': %d\n", ret);
+ return ret;
+ }
+
+ /* "no-hw-flowctrl" state is optional */
+ ascport->states[NO_HW_FLOWCTRL] =
+ pinctrl_lookup_state(ascport->pinctrl, "no-hw-flowctrl");
+ if (IS_ERR(ascport->states[NO_HW_FLOWCTRL]))
+ ascport->states[NO_HW_FLOWCTRL] = NULL;
+
return 0;
}
@@ -713,9 +795,11 @@ static struct asc_port *asc_of_get_asc_port(struct platform_device *pdev)
return NULL;
asc_ports[id].hw_flow_control = of_property_read_bool(np,
- "st,hw-flow-control");
+ "uart-has-rtscts");
asc_ports[id].force_m1 = of_property_read_bool(np, "st,force_m1");
asc_ports[id].port.line = id;
+ asc_ports[id].rts = NULL;
+
return &asc_ports[id];
}