serial: msm_geni_serial: Prevent Rx lockup and Tx truncations
Don't touch the Rx related packing or stale registers while the Rx state
machine is running, this could lead to the Rx sequencer to be in an
unusual state preventing any further Rx operations.
Signal the framework when there fewer than wakeup characters left in
the xmit buffer else there are some characters lost during Tx.
Vote on the resources prior to resuming the port else this could lead to
bus errors.
Change-Id: I6432d6d7798d302abcbc58d985ab8ed129c37937
Signed-off-by: Girish Mahadevan <girishm@codeaurora.org>
diff --git a/drivers/tty/serial/msm_geni_serial.c b/drivers/tty/serial/msm_geni_serial.c
index df9be34..da1a0e6 100644
--- a/drivers/tty/serial/msm_geni_serial.c
+++ b/drivers/tty/serial/msm_geni_serial.c
@@ -94,8 +94,10 @@
#define UART_OVERSAMPLING (32)
#define STALE_TIMEOUT (16)
+#define DEFAULT_BITS_PER_CHAR (10)
#define GENI_UART_NR_PORTS (15)
#define DEF_FIFO_DEPTH_WORDS (16)
+#define DEF_TX_WM (2)
#define DEF_FIFO_WIDTH_BITS (32)
#define UART_CORE2X_VOTE (10000)
#define DEFAULT_SE_CLK (19200000)
@@ -134,6 +136,7 @@
unsigned int rx_fifo_wc,
unsigned int rx_last_byte_valid,
unsigned int rx_last);
+static unsigned int msm_geni_serial_tx_empty(struct uart_port *port);
static atomic_t uart_line_id = ATOMIC_INIT(0);
@@ -232,7 +235,7 @@
}
static void msm_geni_serial_setup_tx(struct uart_port *uport,
- unsigned int xmit_size)
+ unsigned int xmit_size)
{
u32 m_cmd = 0;
@@ -324,7 +327,6 @@
int b = (int) c;
struct msm_geni_serial_port *port = GET_DEV_PORT(uport);
- se_config_packing(uport->membase, 8, 1, false);
geni_write_reg_nolog(port->tx_wm, uport->membase,
SE_GENI_TX_WATERMARK_REG);
msm_geni_serial_setup_tx(uport, 1);
@@ -358,10 +360,11 @@
__msm_geni_serial_console_write(struct uart_port *uport, const char *s,
unsigned int count)
{
- struct msm_geni_serial_port *port = GET_DEV_PORT(uport);
int new_line = 0;
int i;
int bytes_to_send = count;
+ int fifo_depth = DEF_FIFO_DEPTH_WORDS;
+ int tx_wm = DEF_TX_WM;
for (i = 0; i < count; i++) {
if (s[i] == '\n')
@@ -369,14 +372,13 @@
}
bytes_to_send += new_line;
- se_config_packing(uport->membase, 8, 1, false);
- geni_write_reg_nolog(port->tx_wm, uport->membase,
+ geni_write_reg_nolog(tx_wm, uport->membase,
SE_GENI_TX_WATERMARK_REG);
msm_geni_serial_setup_tx(uport, bytes_to_send);
i = 0;
while (i < count) {
u32 chars_to_write = 0;
- u32 avail_fifo_bytes = (port->tx_fifo_depth - port->tx_wm);
+ u32 avail_fifo_bytes = (fifo_depth - tx_wm);
/*
* If the WM bit never set, then the Tx state machine is not
* in a valid state, so break, cancel/abort any existing
@@ -473,10 +475,12 @@
unsigned int geni_m_irq_en;
struct msm_geni_serial_port *port = GET_DEV_PORT(uport);
+ if (!msm_geni_serial_tx_empty(uport))
+ return;
+
geni_m_irq_en = geni_read_reg_nolog(uport->membase, SE_GENI_M_IRQ_EN);
geni_m_irq_en |= M_TX_FIFO_WATERMARK_EN;
- se_config_packing(uport->membase, 8, 4, false);
geni_write_reg_nolog(port->tx_wm, uport->membase,
SE_GENI_TX_WATERMARK_REG);
geni_write_reg_nolog(geni_m_irq_en, uport->membase, SE_GENI_M_IRQ_EN);
@@ -516,6 +520,8 @@
{
unsigned int geni_s_irq_en;
unsigned int geni_m_irq_en;
+ unsigned long cfg0, cfg1;
+ unsigned int rxstale = DEFAULT_BITS_PER_CHAR * STALE_TIMEOUT;
msm_geni_serial_abort_rx(uport);
geni_s_irq_en = geni_read_reg_nolog(uport->membase,
@@ -524,7 +530,10 @@
SE_GENI_M_IRQ_EN);
geni_s_irq_en |= S_RX_FIFO_WATERMARK_EN | S_RX_FIFO_LAST_EN;
geni_m_irq_en |= M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN;
-
+ se_get_packing_config(8, 4, false, &cfg0, &cfg1);
+ geni_write_reg_nolog(cfg0, uport->membase, SE_GENI_RX_PACKING_CFG0);
+ geni_write_reg_nolog(cfg1, uport->membase, SE_GENI_RX_PACKING_CFG1);
+ geni_write_reg_nolog(rxstale, uport->membase, SE_UART_RX_STALE_CNT);
geni_setup_s_cmd(uport->membase, UART_START_READ, 0);
geni_write_reg_nolog(geni_s_irq_en, uport->membase, SE_GENI_S_IRQ_EN);
geni_write_reg_nolog(geni_m_irq_en, uport->membase, SE_GENI_M_IRQ_EN);
@@ -622,7 +631,8 @@
int i = 0;
unsigned int tx_fifo_status;
unsigned int xmit_size;
- unsigned int fifo_width_bytes = msm_port->tx_fifo_width >> 3;
+ unsigned int fifo_width_bytes =
+ (uart_console(uport) ? 1 : (msm_port->tx_fifo_width >> 3));
tx_fifo_status = geni_read_reg_nolog(uport->membase,
SE_GENI_TX_FIFO_STATUS);
@@ -664,6 +674,8 @@
wmb();
}
msm_geni_serial_poll_cancel_tx(uport);
+ if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+ uart_write_wakeup(uport);
exit_handle_tx:
return ret;
}
@@ -765,17 +777,32 @@
{
int ret = 0;
struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport);
+ unsigned long cfg0, cfg1;
- /* For now only assume FIFO mode. */
- msm_port->xfer_mode = FIFO_MODE;
- set_rfr_wm(msm_port);
- ret = geni_se_init(uport->membase, msm_port->xfer_mode,
+
+ if (!uart_console(uport)) {
+ /* For now only assume FIFO mode. */
+ msm_port->xfer_mode = FIFO_MODE;
+ set_rfr_wm(msm_port);
+ ret = geni_se_init(uport->membase, msm_port->xfer_mode,
msm_port->rx_wm, msm_port->rx_rfr);
- if (ret) {
- dev_err(uport->dev, "%s: Fail\n", __func__);
- goto exit_portsetup;
+ if (ret) {
+ dev_err(uport->dev, "%s: Fail\n", __func__);
+ goto exit_portsetup;
+ }
+ se_get_packing_config(8, 4, false, &cfg0, &cfg1);
+ geni_write_reg_nolog(cfg0, uport->membase,
+ SE_GENI_TX_PACKING_CFG0);
+ geni_write_reg_nolog(cfg1, uport->membase,
+ SE_GENI_TX_PACKING_CFG1);
+ } else {
+ set_rfr_wm(msm_port);
+ se_get_packing_config(8, 1, false, &cfg0, &cfg1);
+ geni_write_reg_nolog(cfg0, uport->membase,
+ SE_GENI_TX_PACKING_CFG0);
+ geni_write_reg_nolog(cfg1, uport->membase,
+ SE_GENI_TX_PACKING_CFG1);
}
-
msm_port->port_setup = true;
/*
* Ensure Port setup related IO completes before returning to
@@ -848,7 +875,7 @@
static void geni_serial_write_term_regs(struct uart_port *uport, u32 loopback,
u32 tx_trans_cfg, u32 tx_parity_cfg, u32 rx_trans_cfg,
u32 rx_parity_cfg, u32 bits_per_char, u32 stop_bit_len,
- u32 rxstale, u32 s_clk_cfg)
+ u32 s_clk_cfg)
{
geni_write_reg_nolog(loopback, uport->membase, SE_UART_LOOPBACK_CFG);
geni_write_reg_nolog(tx_trans_cfg, uport->membase,
@@ -865,7 +892,6 @@
SE_UART_RX_WORD_LEN);
geni_write_reg_nolog(stop_bit_len, uport->membase,
SE_UART_TX_STOP_BIT_LEN);
- geni_write_reg_nolog(rxstale, uport->membase, SE_UART_RX_STALE_CNT);
geni_write_reg_nolog(s_clk_cfg, uport->membase, GENI_SER_M_CLK_CFG);
geni_write_reg_nolog(s_clk_cfg, uport->membase, GENI_SER_S_CLK_CFG);
}
@@ -901,7 +927,6 @@
unsigned int rx_trans_cfg;
unsigned int rx_parity_cfg;
unsigned int stop_bit_len;
- unsigned int rxstale;
unsigned int clk_div;
unsigned long ser_clk_cfg = 0;
struct msm_geni_serial_port *port = GET_DEV_PORT(uport);
@@ -966,8 +991,6 @@
break;
}
- /* stale timer, set this to 16 characters. */
- rxstale = bits_per_char * STALE_TIMEOUT;
/* stop bits */
if (termios->c_cflag & CSTOPB)
@@ -984,7 +1007,7 @@
geni_serial_write_term_regs(uport, port->loopback, tx_trans_cfg,
tx_parity_cfg, rx_trans_cfg, rx_parity_cfg, bits_per_char,
- stop_bit_len, rxstale, ser_clk_cfg);
+ stop_bit_len, ser_clk_cfg);
exit_set_termios:
return;
@@ -1066,7 +1089,6 @@
{
struct uart_port *uport = &dev->port;
int ret = 0;
- struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport);
u32 tx_trans_cfg = 0;
u32 tx_parity_cfg = 0;
u32 rx_trans_cfg = 0;
@@ -1078,6 +1100,7 @@
u32 baud = 115200;
u32 clk_div;
unsigned long clk_rate;
+ unsigned long cfg0, cfg1;
if (!uport->membase) {
ret = -ENOMEM;
@@ -1089,13 +1112,8 @@
goto exit_geni_serial_earlyconsetup;
}
- msm_port->xfer_mode = FIFO_MODE;
- set_rfr_wm(msm_port);
- msm_port->tx_fifo_depth = DEF_FIFO_DEPTH_WORDS;
- msm_port->rx_fifo_depth = DEF_FIFO_DEPTH_WORDS;
- msm_port->tx_fifo_width = DEF_FIFO_WIDTH_BITS;
- geni_se_init(uport->membase, msm_port->xfer_mode, msm_port->rx_wm,
- msm_port->rx_rfr);
+ geni_se_init(uport->membase, FIFO_MODE, (DEF_FIFO_DEPTH_WORDS >> 1),
+ (DEF_FIFO_DEPTH_WORDS - 2));
/*
* Ignore Flow control.
* Disable Tx Parity.
@@ -1120,6 +1138,9 @@
s_clk_cfg |= SER_CLK_EN;
s_clk_cfg |= (clk_div << CLK_DIV_SHFT);
+ se_get_packing_config(8, 1, false, &cfg0, &cfg1);
+ geni_write_reg_nolog(cfg0, uport->membase, SE_GENI_TX_PACKING_CFG0);
+ geni_write_reg_nolog(cfg1, uport->membase, SE_GENI_TX_PACKING_CFG1);
/*
* Make an unconditional cancel on the main sequencer to reset
@@ -1128,7 +1149,7 @@
msm_geni_serial_poll_cancel_tx(uport);
geni_serial_write_term_regs(uport, 0, tx_trans_cfg,
tx_parity_cfg, rx_trans_cfg, rx_parity_cfg, bits_per_char,
- stop_bit, rx_stale, s_clk_cfg);
+ stop_bit, s_clk_cfg);
dev->con->write = msm_geni_serial_early_console_write;
dev->con->setup = NULL;
@@ -1435,9 +1456,11 @@
struct msm_geni_serial_port *port = platform_get_drvdata(pdev);
struct uart_port *uport = &port->uport;
- if (uart_console(uport))
+ if (uart_console(uport)) {
+ se_geni_resources_on(&port->serial_rsc);
uart_resume_port((struct uart_driver *)uport->private_data,
uport);
+ }
return 0;
}
#else
diff --git a/include/linux/qcom-geni-se.h b/include/linux/qcom-geni-se.h
index cb4387d..5a10db5 100644
--- a/include/linux/qcom-geni-se.h
+++ b/include/linux/qcom-geni-se.h
@@ -479,11 +479,11 @@
return rx_fifo_depth;
}
-static inline void se_config_packing(void __iomem *base, int bpw,
- int pack_words, bool msb_to_lsb)
+static inline void se_get_packing_config(int bpw, int pack_words,
+ bool msb_to_lsb, unsigned long *cfg0,
+ unsigned long *cfg1)
{
u32 cfg[4] = {0};
- unsigned long cfg0, cfg1;
int len = ((bpw < 8) ? (bpw - 1) : 7);
int idx = ((msb_to_lsb == 1) ? len : 0);
int iter = (bpw * pack_words) >> 3;
@@ -495,8 +495,16 @@
if (i == iter - 1)
cfg[i] |= 1;
}
- cfg0 = cfg[0] | (cfg[1] << 10);
- cfg1 = cfg[2] | (cfg[3] << 10);
+ *cfg0 = cfg[0] | (cfg[1] << 10);
+ *cfg1 = cfg[2] | (cfg[3] << 10);
+}
+
+static inline void se_config_packing(void __iomem *base, int bpw,
+ int pack_words, bool msb_to_lsb)
+{
+ unsigned long cfg0, cfg1;
+
+ se_get_packing_config(bpw, pack_words, msb_to_lsb, &cfg0, &cfg1);
geni_write_reg(cfg0, base, SE_GENI_TX_PACKING_CFG0);
geni_write_reg(cfg1, base, SE_GENI_TX_PACKING_CFG1);
geni_write_reg(cfg0, base, SE_GENI_RX_PACKING_CFG0);