#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>
#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;
};
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);
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)) {
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)
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;
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_get_gpiod_from_child(port->dev, "rts",
+ &np->fwnode);
+ if (!IS_ERR(gpiod)) {
+ gpiod_direction_output(gpiod, 0);
+ ascport->rts = gpiod;
+ }
+ }
+ }
+
if ((baud < 19200) && !ascport->force_m1) {
asc_out(port, ASC_BAUDRATE, (port->uartclk / (16 * baud)));
} else {
{
struct uart_port *port = &ascport->port;
struct resource *res;
+ int ret;
port->iotype = UPIO_MEM;
port->flags = UPF_BOOT_AUTOCONF;
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;
}
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];
}