diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-12-12 07:45:16 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-12-12 07:45:16 -0800 |
commit | d07e43d70eef15a44a2c328a913d8d633a90e088 (patch) | |
tree | 7a7cac7c90f4b8f19edfb2aecfa27d77e266eafc /drivers/tty | |
parent | 1ebaf4f4e6912199f8a4e30ba3ab55da2b71bcdf (diff) | |
parent | 3af08bd7adb09b0806c51c06b87db08cc7075568 (diff) | |
download | linux-d07e43d70eef15a44a2c328a913d8d633a90e088.tar.bz2 |
Merge branch 'omap-serial' of git://git.linaro.org/people/rmk/linux-arm
Pull ARM OMAP serial updates from Russell King:
"This series is a major reworking of the OMAP serial driver code fixing
various bugs in the hardware-assisted flow control, extending up into
serial_core for a couple of issues. These fixes have been done as a
set of progressive changes and transformations in the hope that no new
bugs will be introduced by this series.
The problems are many-fold, from the driver not being informed about
updated settings, to the driver not knowing what the intentions of the
upper layers are.
The first four patches tackle the serial_core layer, allowing it to
provide the necessary information to drivers, and the remaining
patches allow the OMAP serial driver to take advantage of this.
This brings hardware assisted RTS/CTS and XON/OFF flow control into a
useful state.
These patches have been in linux-next for most of the last cycle;
indeed they predate the previous merge window. They've also been
posted to the OMAP people."
* 'omap-serial' of git://git.linaro.org/people/rmk/linux-arm: (21 commits)
SERIAL: omap: fix hardware assisted flow control
SERIAL: omap: simplify (2)
SERIAL: omap: move xon/xoff setting earlier
SERIAL: omap: always set TCR
SERIAL: omap: simplify
SERIAL: omap: don't read back LCR/MCR/EFR
SERIAL: omap: serial_omap_configure_xonxoff() contents into set_termios
SERIAL: omap: configure xon/xoff before setting modem control lines
SERIAL: omap: remove OMAP_UART_SYSC_RESET and OMAP_UART_FIFO_CLR
SERIAL: omap: move driver private definitions and structures to driver
SERIAL: omap: remove 'irq_pending' bitfield
SERIAL: omap: fix MCR TCRTLR bit handling
SERIAL: omap: fix set_mctrl() breakage
SERIAL: omap: no need to re-read EFR
SERIAL: omap: remove setting of EFR SCD bit
SERIAL: omap: allow hardware assisted IXANY mode to be disabled
SERIAL: omap: allow hardware assisted rts/cts modes to be disabled
SERIAL: core: add throttle/unthrottle callbacks for hardware assisted flow control
SERIAL: core: add hardware assisted h/w flow control support
SERIAL: core: add hardware assisted s/w flow control support
...
Conflicts:
drivers/tty/serial/omap-serial.c
Diffstat (limited to 'drivers/tty')
-rw-r--r-- | drivers/tty/serial/omap-serial.c | 231 | ||||
-rw-r--r-- | drivers/tty/serial/serial_core.c | 71 |
2 files changed, 207 insertions, 95 deletions
diff --git a/drivers/tty/serial/omap-serial.c b/drivers/tty/serial/omap-serial.c index b538e2e4ae5b..23f797eb7a28 100644 --- a/drivers/tty/serial/omap-serial.c +++ b/drivers/tty/serial/omap-serial.c @@ -44,6 +44,8 @@ #include <plat/omap-serial.h> +#define OMAP_MAX_HSUART_PORTS 6 + #define UART_BUILD_REVISION(x, y) (((x) << 8) | (y)) #define OMAP_UART_REV_42 0x0402 @@ -51,10 +53,14 @@ #define OMAP_UART_REV_52 0x0502 #define OMAP_UART_REV_63 0x0603 +#define UART_ERRATA_i202_MDR1_ACCESS BIT(0) +#define UART_ERRATA_i291_DMA_FORCEIDLE BIT(1) + #define DEFAULT_CLK_SPEED 48000000 /* 48Mhz*/ /* SCR register bitmasks */ #define OMAP_UART_SCR_RX_TRIG_GRANU1_MASK (1 << 7) +#define OMAP_UART_SCR_TX_EMPTY (1 << 3) /* FCR register bitmasks */ #define OMAP_UART_FCR_RX_FIFO_TRIG_MASK (0x3 << 6) @@ -71,6 +77,52 @@ #define OMAP_UART_MVR_MAJ_SHIFT 8 #define OMAP_UART_MVR_MIN_MASK 0x3f +#define OMAP_UART_DMA_CH_FREE -1 + +#define MSR_SAVE_FLAGS UART_MSR_ANY_DELTA +#define OMAP_MODE13X_SPEED 230400 + +/* WER = 0x7F + * Enable module level wakeup in WER reg + */ +#define OMAP_UART_WER_MOD_WKUP 0X7F + +/* Enable XON/XOFF flow control on output */ +#define OMAP_UART_SW_TX 0x08 + +/* Enable XON/XOFF flow control on input */ +#define OMAP_UART_SW_RX 0x02 + +#define OMAP_UART_SW_CLR 0xF0 + +#define OMAP_UART_TCR_TRIG 0x0F + +struct uart_omap_dma { + u8 uart_dma_tx; + u8 uart_dma_rx; + int rx_dma_channel; + int tx_dma_channel; + dma_addr_t rx_buf_dma_phys; + dma_addr_t tx_buf_dma_phys; + unsigned int uart_base; + /* + * Buffer for rx dma.It is not required for tx because the buffer + * comes from port structure. + */ + unsigned char *rx_buf; + unsigned int prev_rx_dma_pos; + int tx_buf_size; + int tx_dma_used; + int rx_dma_used; + spinlock_t tx_lock; + spinlock_t rx_lock; + /* timer to poll activity on rx dma */ + struct timer_list rx_timer; + unsigned int rx_buf_size; + unsigned int rx_poll_rate; + unsigned int rx_timeout; +}; + struct uart_omap_port { struct uart_port port; struct uart_omap_dma uart_dma; @@ -99,7 +151,6 @@ struct uart_omap_port { int context_loss_cnt; u32 errata; u8 wakeups_enabled; - unsigned int irq_pending:1; int DTR_gpio; int DTR_inverted; @@ -303,6 +354,34 @@ static void serial_omap_start_tx(struct uart_port *port) pm_runtime_put_autosuspend(up->dev); } +static void serial_omap_throttle(struct uart_port *port) +{ + struct uart_omap_port *up = to_uart_omap_port(port); + unsigned long flags; + + pm_runtime_get_sync(up->dev); + spin_lock_irqsave(&up->port.lock, flags); + up->ier &= ~(UART_IER_RLSI | UART_IER_RDI); + serial_out(up, UART_IER, up->ier); + spin_unlock_irqrestore(&up->port.lock, flags); + pm_runtime_mark_last_busy(up->dev); + pm_runtime_put_autosuspend(up->dev); +} + +static void serial_omap_unthrottle(struct uart_port *port) +{ + struct uart_omap_port *up = to_uart_omap_port(port); + unsigned long flags; + + pm_runtime_get_sync(up->dev); + spin_lock_irqsave(&up->port.lock, flags); + up->ier |= UART_IER_RLSI | UART_IER_RDI; + serial_out(up, UART_IER, up->ier); + spin_unlock_irqrestore(&up->port.lock, flags); + pm_runtime_mark_last_busy(up->dev); + pm_runtime_put_autosuspend(up->dev); +} + static unsigned int check_modem_status(struct uart_omap_port *up) { unsigned int status; @@ -504,7 +583,7 @@ static unsigned int serial_omap_get_mctrl(struct uart_port *port) static void serial_omap_set_mctrl(struct uart_port *port, unsigned int mctrl) { struct uart_omap_port *up = to_uart_omap_port(port); - unsigned char mcr = 0; + unsigned char mcr = 0, old_mcr; dev_dbg(up->port.dev, "serial_omap_set_mctrl+%d\n", up->port.line); if (mctrl & TIOCM_RTS) @@ -519,8 +598,10 @@ static void serial_omap_set_mctrl(struct uart_port *port, unsigned int mctrl) mcr |= UART_MCR_LOOP; pm_runtime_get_sync(up->dev); - up->mcr = serial_in(up, UART_MCR); - up->mcr |= mcr; + old_mcr = serial_in(up, UART_MCR); + old_mcr &= ~(UART_MCR_LOOP | UART_MCR_OUT2 | UART_MCR_OUT1 | + UART_MCR_DTR | UART_MCR_RTS); + up->mcr = old_mcr | mcr; serial_out(up, UART_MCR, up->mcr); pm_runtime_mark_last_busy(up->dev); pm_runtime_put_autosuspend(up->dev); @@ -654,61 +735,6 @@ static void serial_omap_shutdown(struct uart_port *port) free_irq(up->port.irq, up); } -static inline void -serial_omap_configure_xonxoff - (struct uart_omap_port *up, struct ktermios *termios) -{ - up->lcr = serial_in(up, UART_LCR); - serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); - up->efr = serial_in(up, UART_EFR); - serial_out(up, UART_EFR, up->efr & ~UART_EFR_ECB); - - serial_out(up, UART_XON1, termios->c_cc[VSTART]); - serial_out(up, UART_XOFF1, termios->c_cc[VSTOP]); - - /* clear SW control mode bits */ - up->efr &= OMAP_UART_SW_CLR; - - /* - * IXON Flag: - * Enable XON/XOFF flow control on output. - * Transmit XON1, XOFF1 - */ - if (termios->c_iflag & IXON) - up->efr |= OMAP_UART_SW_TX; - - /* - * IXOFF Flag: - * Enable XON/XOFF flow control on input. - * Receiver compares XON1, XOFF1. - */ - if (termios->c_iflag & IXOFF) - up->efr |= OMAP_UART_SW_RX; - - serial_out(up, UART_EFR, up->efr | UART_EFR_ECB); - serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A); - - up->mcr = serial_in(up, UART_MCR); - - /* - * IXANY Flag: - * Enable any character to restart output. - * Operation resumes after receiving any - * character after recognition of the XOFF character - */ - if (termios->c_iflag & IXANY) - up->mcr |= UART_MCR_XONANY; - - serial_out(up, UART_MCR, up->mcr | UART_MCR_TCRTLR); - serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); - serial_out(up, UART_TI752_TCR, OMAP_UART_TCR_TRIG); - - serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A); - - serial_out(up, UART_MCR, up->mcr & ~UART_MCR_TCRTLR); - serial_out(up, UART_LCR, up->lcr); -} - static void serial_omap_uart_qos_work(struct work_struct *work) { struct uart_omap_port *up = container_of(work, struct uart_omap_port, @@ -726,7 +752,6 @@ serial_omap_set_termios(struct uart_port *port, struct ktermios *termios, { struct uart_omap_port *up = to_uart_omap_port(port); unsigned char cval = 0; - unsigned char efr = 0; unsigned long flags = 0; unsigned int baud, quot; @@ -836,11 +861,12 @@ serial_omap_set_termios(struct uart_port *port, struct ktermios *termios, serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); - up->efr = serial_in(up, UART_EFR); + up->efr = serial_in(up, UART_EFR) & ~UART_EFR_ECB; + up->efr &= ~UART_EFR_SCD; serial_out(up, UART_EFR, up->efr | UART_EFR_ECB); serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A); - up->mcr = serial_in(up, UART_MCR); + up->mcr = serial_in(up, UART_MCR) & ~UART_MCR_TCRTLR; serial_out(up, UART_MCR, up->mcr | UART_MCR_TCRTLR); /* FIFO ENABLE, DMA MODE */ @@ -859,9 +885,12 @@ serial_omap_set_termios(struct uart_port *port, struct ktermios *termios, serial_out(up, UART_OMAP_SCR, up->scr); - serial_out(up, UART_EFR, up->efr); + /* Reset UART_MCR_TCRTLR: this must be done with the EFR_ECB bit set */ serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A); serial_out(up, UART_MCR, up->mcr); + serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); + serial_out(up, UART_EFR, up->efr); + serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A); /* Protocol, Baud Rate, and Interrupt Settings */ @@ -871,8 +900,6 @@ serial_omap_set_termios(struct uart_port *port, struct ktermios *termios, serial_out(up, UART_OMAP_MDR1, up->mdr1); serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); - - up->efr = serial_in(up, UART_EFR); serial_out(up, UART_EFR, up->efr | UART_EFR_ECB); serial_out(up, UART_LCR, 0); @@ -899,29 +926,68 @@ serial_omap_set_termios(struct uart_port *port, struct ktermios *termios, else serial_out(up, UART_OMAP_MDR1, up->mdr1); - /* Hardware Flow Control Configuration */ + /* Configure flow control */ + serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); + + /* XON1/XOFF1 accessible mode B, TCRTLR=0, ECB=0 */ + serial_out(up, UART_XON1, termios->c_cc[VSTART]); + serial_out(up, UART_XOFF1, termios->c_cc[VSTOP]); + + /* Enable access to TCR/TLR */ + serial_out(up, UART_EFR, up->efr | UART_EFR_ECB); + serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A); + serial_out(up, UART_MCR, up->mcr | UART_MCR_TCRTLR); + + serial_out(up, UART_TI752_TCR, OMAP_UART_TCR_TRIG); + + if (termios->c_cflag & CRTSCTS && up->port.flags & UPF_HARD_FLOW) { + /* Enable AUTORTS and AUTOCTS */ + up->efr |= UART_EFR_CTS | UART_EFR_RTS; + + /* Ensure MCR RTS is asserted */ + up->mcr |= UART_MCR_RTS; + } else { + /* Disable AUTORTS and AUTOCTS */ + up->efr &= ~(UART_EFR_CTS | UART_EFR_RTS); + } - if (termios->c_cflag & CRTSCTS) { - efr |= (UART_EFR_CTS | UART_EFR_RTS); - serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A); + if (up->port.flags & UPF_SOFT_FLOW) { + /* clear SW control mode bits */ + up->efr &= OMAP_UART_SW_CLR; - up->mcr = serial_in(up, UART_MCR); - serial_out(up, UART_MCR, up->mcr | UART_MCR_TCRTLR); + /* + * IXON Flag: + * Enable XON/XOFF flow control on input. + * Receiver compares XON1, XOFF1. + */ + if (termios->c_iflag & IXON) + up->efr |= OMAP_UART_SW_RX; - serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); - up->efr = serial_in(up, UART_EFR); - serial_out(up, UART_EFR, up->efr | UART_EFR_ECB); + /* + * IXOFF Flag: + * Enable XON/XOFF flow control on output. + * Transmit XON1, XOFF1 + */ + if (termios->c_iflag & IXOFF) + up->efr |= OMAP_UART_SW_TX; - serial_out(up, UART_TI752_TCR, OMAP_UART_TCR_TRIG); - serial_out(up, UART_EFR, efr); /* Enable AUTORTS and AUTOCTS */ - serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A); - serial_out(up, UART_MCR, up->mcr | UART_MCR_RTS); - serial_out(up, UART_LCR, cval); + /* + * IXANY Flag: + * Enable any character to restart output. + * Operation resumes after receiving any + * character after recognition of the XOFF character + */ + if (termios->c_iflag & IXANY) + up->mcr |= UART_MCR_XONANY; + else + up->mcr &= ~UART_MCR_XONANY; } + serial_out(up, UART_MCR, up->mcr); + serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); + serial_out(up, UART_EFR, up->efr); + serial_out(up, UART_LCR, up->lcr); serial_omap_set_mctrl(&up->port, up->port.mctrl); - /* Software Flow Control Configuration */ - serial_omap_configure_xonxoff(up, termios); spin_unlock_irqrestore(&up->port.lock, flags); pm_runtime_mark_last_busy(up->dev); @@ -987,6 +1053,7 @@ static void serial_omap_config_port(struct uart_port *port, int flags) dev_dbg(up->port.dev, "serial_omap_config_port+%d\n", up->port.line); up->port.type = PORT_OMAP; + up->port.flags |= UPF_SOFT_FLOW | UPF_HARD_FLOW; } static int @@ -1190,6 +1257,8 @@ static struct uart_ops serial_omap_pops = { .get_mctrl = serial_omap_get_mctrl, .stop_tx = serial_omap_stop_tx, .start_tx = serial_omap_start_tx, + .throttle = serial_omap_throttle, + .unthrottle = serial_omap_unthrottle, .stop_rx = serial_omap_stop_rx, .enable_ms = serial_omap_enable_ms, .break_ctl = serial_omap_break_ctl, diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index fb5aa42fde7e..2c7230aaefd4 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -610,27 +610,50 @@ static void uart_send_xchar(struct tty_struct *tty, char ch) static void uart_throttle(struct tty_struct *tty) { struct uart_state *state = tty->driver_data; + struct uart_port *port = state->uart_port; + uint32_t mask = 0; if (I_IXOFF(tty)) + mask |= UPF_SOFT_FLOW; + if (tty->termios.c_cflag & CRTSCTS) + mask |= UPF_HARD_FLOW; + + if (port->flags & mask) { + port->ops->throttle(port); + mask &= ~port->flags; + } + + if (mask & UPF_SOFT_FLOW) uart_send_xchar(tty, STOP_CHAR(tty)); - if (tty->termios.c_cflag & CRTSCTS) - uart_clear_mctrl(state->uart_port, TIOCM_RTS); + if (mask & UPF_HARD_FLOW) + uart_clear_mctrl(port, TIOCM_RTS); } static void uart_unthrottle(struct tty_struct *tty) { struct uart_state *state = tty->driver_data; struct uart_port *port = state->uart_port; + uint32_t mask = 0; - if (I_IXOFF(tty)) { + if (I_IXOFF(tty)) + mask |= UPF_SOFT_FLOW; + if (tty->termios.c_cflag & CRTSCTS) + mask |= UPF_HARD_FLOW; + + if (port->flags & mask) { + port->ops->unthrottle(port); + mask &= ~port->flags; + } + + if (mask & UPF_SOFT_FLOW) { if (port->x_char) port->x_char = 0; else uart_send_xchar(tty, START_CHAR(tty)); } - if (tty->termios.c_cflag & CRTSCTS) + if (mask & UPF_HARD_FLOW) uart_set_mctrl(port, TIOCM_RTS); } @@ -1214,9 +1237,22 @@ static void uart_set_termios(struct tty_struct *tty, struct ktermios *old_termios) { struct uart_state *state = tty->driver_data; + struct uart_port *uport = state->uart_port; unsigned long flags; unsigned int cflag = tty->termios.c_cflag; + unsigned int iflag_mask = IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK; + bool sw_changed = false; + /* + * Drivers doing software flow control also need to know + * about changes to these input settings. + */ + if (uport->flags & UPF_SOFT_FLOW) { + iflag_mask |= IXANY|IXON|IXOFF; + sw_changed = + tty->termios.c_cc[VSTART] != old_termios->c_cc[VSTART] || + tty->termios.c_cc[VSTOP] != old_termios->c_cc[VSTOP]; + } /* * These are the bits that are used to setup various @@ -1224,11 +1260,11 @@ static void uart_set_termios(struct tty_struct *tty, * bits in c_cflag; c_[io]speed will always be set * appropriately by set_termios() in tty_ioctl.c */ -#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) if ((cflag ^ old_termios->c_cflag) == 0 && tty->termios.c_ospeed == old_termios->c_ospeed && tty->termios.c_ispeed == old_termios->c_ispeed && - RELEVANT_IFLAG(tty->termios.c_iflag ^ old_termios->c_iflag) == 0) { + ((tty->termios.c_iflag ^ old_termios->c_iflag) & iflag_mask) == 0 && + !sw_changed) { return; } @@ -1236,31 +1272,38 @@ static void uart_set_termios(struct tty_struct *tty, /* Handle transition to B0 status */ if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD)) - uart_clear_mctrl(state->uart_port, TIOCM_RTS | TIOCM_DTR); + uart_clear_mctrl(uport, TIOCM_RTS | TIOCM_DTR); /* Handle transition away from B0 status */ else if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) { unsigned int mask = TIOCM_DTR; if (!(cflag & CRTSCTS) || !test_bit(TTY_THROTTLED, &tty->flags)) mask |= TIOCM_RTS; - uart_set_mctrl(state->uart_port, mask); + uart_set_mctrl(uport, mask); } + /* + * If the port is doing h/w assisted flow control, do nothing. + * We assume that tty->hw_stopped has never been set. + */ + if (uport->flags & UPF_HARD_FLOW) + return; + /* Handle turning off CRTSCTS */ if ((old_termios->c_cflag & CRTSCTS) && !(cflag & CRTSCTS)) { - spin_lock_irqsave(&state->uart_port->lock, flags); + spin_lock_irqsave(&uport->lock, flags); tty->hw_stopped = 0; __uart_start(tty); - spin_unlock_irqrestore(&state->uart_port->lock, flags); + spin_unlock_irqrestore(&uport->lock, flags); } /* Handle turning on CRTSCTS */ else if (!(old_termios->c_cflag & CRTSCTS) && (cflag & CRTSCTS)) { - spin_lock_irqsave(&state->uart_port->lock, flags); - if (!(state->uart_port->ops->get_mctrl(state->uart_port) & TIOCM_CTS)) { + spin_lock_irqsave(&uport->lock, flags); + if (!(uport->ops->get_mctrl(uport) & TIOCM_CTS)) { tty->hw_stopped = 1; - state->uart_port->ops->stop_tx(state->uart_port); + uport->ops->stop_tx(uport); } - spin_unlock_irqrestore(&state->uart_port->lock, flags); + spin_unlock_irqrestore(&uport->lock, flags); } } |