diff options
Diffstat (limited to 'drivers/tty/serial/sh-sci.c')
-rw-r--r-- | drivers/tty/serial/sh-sci.c | 196 |
1 files changed, 165 insertions, 31 deletions
diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c index 0130feb069ae..d86eee38aae6 100644 --- a/drivers/tty/serial/sh-sci.c +++ b/drivers/tty/serial/sh-sci.c @@ -57,6 +57,7 @@ #include <asm/sh_bios.h> #endif +#include "serial_mctrl_gpio.h" #include "sh-sci.h" /* Offsets into the sci_port->irqs array */ @@ -111,6 +112,7 @@ struct sci_port { unsigned int error_clear; unsigned int sampling_rate_mask; resource_size_t reg_size; + struct mctrl_gpios *gpios; /* Break timer */ struct timer_list break_timer; @@ -139,6 +141,8 @@ struct sci_port { struct timer_list rx_timer; unsigned int rx_timeout; #endif + + bool autorts; }; #define SCI_NPORTS CONFIG_SERIAL_SH_SCI_NR_UARTS @@ -701,7 +705,6 @@ static void sci_poll_put_char(struct uart_port *port, unsigned char c) static void sci_init_pins(struct uart_port *port, unsigned int cflag) { struct sci_port *s = to_sci_port(port); - const struct plat_sci_reg *reg = sci_regmap[s->cfg->regtype] + SCSPTR; /* * Use port-specific handler if provided. @@ -711,21 +714,28 @@ static void sci_init_pins(struct uart_port *port, unsigned int cflag) return; } - /* - * For the generic path SCSPTR is necessary. Bail out if that's - * unavailable, too. - */ - if (!reg->size) - return; - - if ((s->cfg->capabilities & SCIx_HAVE_RTSCTS) && - ((!(cflag & CRTSCTS)))) { - unsigned short status; + if (port->type == PORT_SCIFA || port->type == PORT_SCIFB) { + u16 ctrl = serial_port_in(port, SCPCR); + + /* Enable RXD and TXD pin functions */ + ctrl &= ~(SCPCR_RXDC | SCPCR_TXDC); + if (to_sci_port(port)->cfg->capabilities & SCIx_HAVE_RTSCTS) { + /* RTS# is output, driven 1 */ + ctrl |= SCPCR_RTSC; + serial_port_out(port, SCPDR, + serial_port_in(port, SCPDR) | SCPDR_RTSD); + /* Enable CTS# pin function */ + ctrl &= ~SCPCR_CTSC; + } + serial_port_out(port, SCPCR, ctrl); + } else if (sci_getreg(port, SCSPTR)->size) { + u16 status = serial_port_in(port, SCSPTR); - status = serial_port_in(port, SCSPTR); - status &= ~SCSPTR_CTSIO; - status |= SCSPTR_RTSIO; - serial_port_out(port, SCSPTR, status); /* Set RTS = 1 */ + /* RTS# is output, driven 1 */ + status |= SCSPTR_RTSIO | SCSPTR_RTSDT; + /* CTS# and SCK are inputs */ + status &= ~(SCSPTR_CTSIO | SCSPTR_SCKIO); + serial_port_out(port, SCSPTR, status); } } @@ -1803,6 +1813,46 @@ static unsigned int sci_tx_empty(struct uart_port *port) return (status & SCxSR_TEND(port)) && !in_tx_fifo ? TIOCSER_TEMT : 0; } +static void sci_set_rts(struct uart_port *port, bool state) +{ + if (port->type == PORT_SCIFA || port->type == PORT_SCIFB) { + u16 data = serial_port_in(port, SCPDR); + + /* Active low */ + if (state) + data &= ~SCPDR_RTSD; + else + data |= SCPDR_RTSD; + serial_port_out(port, SCPDR, data); + + /* RTS# is output */ + serial_port_out(port, SCPCR, + serial_port_in(port, SCPCR) | SCPCR_RTSC); + } else if (sci_getreg(port, SCSPTR)->size) { + u16 ctrl = serial_port_in(port, SCSPTR); + + /* Active low */ + if (state) + ctrl &= ~SCSPTR_RTSDT; + else + ctrl |= SCSPTR_RTSDT; + serial_port_out(port, SCSPTR, ctrl); + } +} + +static bool sci_get_cts(struct uart_port *port) +{ + if (port->type == PORT_SCIFA || port->type == PORT_SCIFB) { + /* Active low */ + return !(serial_port_in(port, SCPDR) & SCPDR_CTSD); + } else if (sci_getreg(port, SCSPTR)->size) { + /* Active low */ + return !(serial_port_in(port, SCSPTR) & SCSPTR_CTSDT); + } + + return true; +} + /* * Modem control is a bit of a mixed bag for SCI(F) ports. Generally * CTS/RTS is supported in hardware by at least one port and controlled @@ -1817,6 +1867,8 @@ static unsigned int sci_tx_empty(struct uart_port *port) */ static void sci_set_mctrl(struct uart_port *port, unsigned int mctrl) { + struct sci_port *s = to_sci_port(port); + if (mctrl & TIOCM_LOOP) { const struct plat_sci_reg *reg; @@ -1829,25 +1881,72 @@ static void sci_set_mctrl(struct uart_port *port, unsigned int mctrl) serial_port_in(port, SCFCR) | SCFCR_LOOP); } + + mctrl_gpio_set(s->gpios, mctrl); + + if (!(s->cfg->capabilities & SCIx_HAVE_RTSCTS)) + return; + + if (!(mctrl & TIOCM_RTS)) { + /* Disable Auto RTS */ + serial_port_out(port, SCFCR, + serial_port_in(port, SCFCR) & ~SCFCR_MCE); + + /* Clear RTS */ + sci_set_rts(port, 0); + } else if (s->autorts) { + if (port->type == PORT_SCIFA || port->type == PORT_SCIFB) { + /* Enable RTS# pin function */ + serial_port_out(port, SCPCR, + serial_port_in(port, SCPCR) & ~SCPCR_RTSC); + } + + /* Enable Auto RTS */ + serial_port_out(port, SCFCR, + serial_port_in(port, SCFCR) | SCFCR_MCE); + } else { + /* Set RTS */ + sci_set_rts(port, 1); + } } static unsigned int sci_get_mctrl(struct uart_port *port) { + struct sci_port *s = to_sci_port(port); + struct mctrl_gpios *gpios = s->gpios; + unsigned int mctrl = 0; + + mctrl_gpio_get(gpios, &mctrl); + /* * CTS/RTS is handled in hardware when supported, while nothing - * else is wired up. Keep it simple and simply assert DSR/CAR. + * else is wired up. */ - return TIOCM_DSR | TIOCM_CAR; + if (s->autorts) { + if (sci_get_cts(port)) + mctrl |= TIOCM_CTS; + } else if (IS_ERR_OR_NULL(mctrl_gpio_to_gpiod(gpios, UART_GPIO_CTS))) { + mctrl |= TIOCM_CTS; + } + if (IS_ERR_OR_NULL(mctrl_gpio_to_gpiod(gpios, UART_GPIO_DSR))) + mctrl |= TIOCM_DSR; + if (IS_ERR_OR_NULL(mctrl_gpio_to_gpiod(gpios, UART_GPIO_DCD))) + mctrl |= TIOCM_CAR; + + return mctrl; +} + +static void sci_enable_ms(struct uart_port *port) +{ + mctrl_gpio_enable_ms(to_sci_port(port)->gpios); } static void sci_break_ctl(struct uart_port *port, int break_state) { - struct sci_port *s = to_sci_port(port); - const struct plat_sci_reg *reg = sci_regmap[s->cfg->regtype] + SCSPTR; unsigned short scscr, scsptr; /* check wheter the port has SCSPTR */ - if (!reg->size) { + if (!sci_getreg(port, SCSPTR)->size) { /* * Not supported by hardware. Most parts couple break and rx * interrupts together, with break detection always enabled. @@ -1873,7 +1972,6 @@ static void sci_break_ctl(struct uart_port *port, int break_state) static int sci_startup(struct uart_port *port) { struct sci_port *s = to_sci_port(port); - unsigned long flags; int ret; dev_dbg(port->dev, "%s(%d)\n", __func__, port->line); @@ -1884,11 +1982,6 @@ static int sci_startup(struct uart_port *port) sci_request_dma(port); - spin_lock_irqsave(&port->lock, flags); - sci_start_tx(port); - sci_start_rx(port); - spin_unlock_irqrestore(&port->lock, flags); - return 0; } @@ -1896,12 +1989,19 @@ static void sci_shutdown(struct uart_port *port) { struct sci_port *s = to_sci_port(port); unsigned long flags; + u16 scr; dev_dbg(port->dev, "%s(%d)\n", __func__, port->line); + s->autorts = false; + mctrl_gpio_disable_ms(to_sci_port(port)->gpios); + spin_lock_irqsave(&port->lock, flags); sci_stop_rx(port); sci_stop_tx(port); + /* Stop RX and TX, disable related interrupts, keep clock source */ + scr = serial_port_in(port, SCSCR); + serial_port_out(port, SCSCR, scr & (SCSCR_CKE1 | SCSCR_CKE0)); spin_unlock_irqrestore(&port->lock, flags); #ifdef CONFIG_SERIAL_SH_SCI_DMA @@ -2056,6 +2156,15 @@ static void sci_reset(struct uart_port *port) reg = sci_getreg(port, SCFCR); if (reg->size) serial_port_out(port, SCFCR, SCFCR_RFRST | SCFCR_TFRST); + + sci_clear_SCxSR(port, + SCxSR_RDxF_CLEAR(port) & SCxSR_ERROR_CLEAR(port) & + SCxSR_BREAK_CLEAR(port)); + if (sci_getreg(port, SCLSR)->size) { + status = serial_port_in(port, SCLSR); + status &= ~(SCLSR_TO | SCLSR_ORER); + serial_port_out(port, SCLSR, status); + } } static void sci_set_termios(struct uart_port *port, struct ktermios *termios, @@ -2218,15 +2327,18 @@ done: sci_init_pins(port, termios->c_cflag); + port->status &= ~UPSTAT_AUTOCTS; + s->autorts = false; reg = sci_getreg(port, SCFCR); if (reg->size) { unsigned short ctrl = serial_port_in(port, SCFCR); - if (s->cfg->capabilities & SCIx_HAVE_RTSCTS) { - if (termios->c_cflag & CRTSCTS) - ctrl |= SCFCR_MCE; - else - ctrl &= ~SCFCR_MCE; + if ((port->flags & UPF_HARD_FLOW) && + (termios->c_cflag & CRTSCTS)) { + /* There is no CTS interrupt to restart the hardware */ + port->status |= UPSTAT_AUTOCTS; + /* MCE is enabled when RTS is raised */ + s->autorts = true; } /* @@ -2300,6 +2412,9 @@ done: sci_start_rx(port); sci_port_disable(s); + + if (UART_ENABLE_MS(port, termios->c_cflag)) + sci_enable_ms(port); } static void sci_pm(struct uart_port *port, unsigned int state, @@ -2425,6 +2540,7 @@ static struct uart_ops sci_uart_ops = { .start_tx = sci_start_tx, .stop_tx = sci_stop_tx, .stop_rx = sci_stop_rx, + .enable_ms = sci_enable_ms, .break_ctl = sci_break_ctl, .startup = sci_startup, .shutdown = sci_shutdown, @@ -2890,6 +3006,9 @@ sci_parse_dt(struct platform_device *pdev, unsigned int *dev_id) p->regtype = SCI_OF_REGTYPE(match->data); p->scscr = SCSCR_RE | SCSCR_TE; + if (of_find_property(np, "uart-has-rtscts", NULL)) + p->capabilities |= SCIx_HAVE_RTSCTS; + return p; } @@ -2912,6 +3031,21 @@ static int sci_probe_single(struct platform_device *dev, if (ret) return ret; + sciport->gpios = mctrl_gpio_init(&sciport->port, 0); + if (IS_ERR(sciport->gpios) && PTR_ERR(sciport->gpios) != -ENOSYS) + return PTR_ERR(sciport->gpios); + + if (p->capabilities & SCIx_HAVE_RTSCTS) { + if (!IS_ERR_OR_NULL(mctrl_gpio_to_gpiod(sciport->gpios, + UART_GPIO_CTS)) || + !IS_ERR_OR_NULL(mctrl_gpio_to_gpiod(sciport->gpios, + UART_GPIO_RTS))) { + dev_err(&dev->dev, "Conflicting RTS/CTS config\n"); + return -EINVAL; + } + sciport->port.flags |= UPF_HARD_FLOW; + } + ret = uart_add_one_port(&sci_uart_driver, &sciport->port); if (ret) { sci_cleanup_single(sciport); |