aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/tty
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2012-12-12 07:45:16 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2012-12-12 07:45:16 -0800
commitd07e43d70eef15a44a2c328a913d8d633a90e088 (patch)
tree7a7cac7c90f4b8f19edfb2aecfa27d77e266eafc /drivers/tty
parentMerge branch 'x86-timers-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip (diff)
parentSERIAL: omap: fix hardware assisted flow control (diff)
downloadlinux-dev-d07e43d70eef15a44a2c328a913d8d633a90e088.tar.xz
linux-dev-d07e43d70eef15a44a2c328a913d8d633a90e088.zip
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.c231
-rw-r--r--drivers/tty/serial/serial_core.c71
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);
}
}