diff options
Diffstat (limited to 'drivers/tty/serial/qcom_geni_serial.c')
-rw-r--r-- | drivers/tty/serial/qcom_geni_serial.c | 177 |
1 files changed, 121 insertions, 56 deletions
diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index a1b3eb04cb32..c62e17c85f57 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -98,14 +98,13 @@ struct qcom_geni_serial_port { enum geni_se_xfer_mode xfer_mode; bool setup; int (*handle_rx)(struct uart_port *uport, u32 bytes, bool drop); - unsigned int xmit_size; unsigned int baud; unsigned int tx_bytes_pw; unsigned int rx_bytes_pw; bool brk; }; -static const struct uart_ops qcom_geni_serial_pops; +static const struct uart_ops qcom_geni_console_pops; static struct uart_driver qcom_geni_console_driver; static int handle_rx_console(struct uart_port *uport, u32 bytes, bool drop); static unsigned int qcom_geni_serial_tx_empty(struct uart_port *port); @@ -118,7 +117,14 @@ static const unsigned long root_freq[] = {7372800, 14745600, 19200000, 29491200, #define to_dev_port(ptr, member) \ container_of(ptr, struct qcom_geni_serial_port, member) -static struct qcom_geni_serial_port qcom_geni_console_port; +static struct qcom_geni_serial_port qcom_geni_console_port = { + .uport = { + .iotype = UPIO_MEM, + .ops = &qcom_geni_console_pops, + .flags = UPF_BOOT_AUTOCONF, + .line = 0, + }, +}; static int qcom_geni_serial_request_port(struct uart_port *uport) { @@ -189,8 +195,19 @@ static bool qcom_geni_serial_poll_bit(struct uart_port *uport, timeout_us = ((fifo_bits * USEC_PER_SEC) / baud) + 500; } - return !readl_poll_timeout_atomic(uport->membase + offset, reg, - (bool)(reg & field) == set, 10, timeout_us); + /* + * Use custom implementation instead of readl_poll_atomic since ktimer + * is not ready at the time of early console. + */ + timeout_us = DIV_ROUND_UP(timeout_us, 10) * 10; + while (timeout_us) { + reg = readl_relaxed(uport->membase + offset); + if ((bool)(reg & field) == set) + return true; + udelay(10); + timeout_us -= 10; + } + return false; } static void qcom_geni_serial_setup_tx(struct uart_port *uport, u32 xmit_size) @@ -286,6 +303,10 @@ __qcom_geni_serial_console_write(struct uart_port *uport, const char *s, u32 bytes_to_send = count; for (i = 0; i < count; i++) { + /* + * uart_console_write() adds a carriage return for each newline. + * Account for additional bytes to be written. + */ if (s[i] == '\n') bytes_to_send++; } @@ -305,7 +326,7 @@ __qcom_geni_serial_console_write(struct uart_port *uport, const char *s, if (!qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS, M_TX_FIFO_WATERMARK_EN, true)) break; - chars_to_write = min_t(size_t, (size_t)(count - i), avail / 2); + chars_to_write = min_t(size_t, count - i, avail / 2); uart_console_write(uport, s + i, chars_to_write, qcom_geni_serial_wr_char); writel_relaxed(M_TX_FIFO_WATERMARK_EN, uport->membase + @@ -406,20 +427,18 @@ static void qcom_geni_serial_start_tx(struct uart_port *uport) u32 status; if (port->xfer_mode == GENI_SE_FIFO) { - status = readl_relaxed(uport->membase + SE_GENI_STATUS); + /* + * readl ensures reading & writing of IRQ_EN register + * is not re-ordered before checking the status of the + * Serial Engine. + */ + status = readl(uport->membase + SE_GENI_STATUS); if (status & M_GENI_CMD_ACTIVE) return; if (!qcom_geni_serial_tx_empty(uport)) return; - /* - * Ensure writing to IRQ_EN & watermark registers are not - * re-ordered before checking the status of the Serial - * Engine and TX FIFO - */ - mb(); - irq_en = readl_relaxed(uport->membase + SE_GENI_M_IRQ_EN); irq_en |= M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN; @@ -442,7 +461,6 @@ static void qcom_geni_serial_stop_tx(struct uart_port *uport) writel_relaxed(0, uport->membase + SE_GENI_TX_WATERMARK_REG); } - port->xmit_size = 0; writel_relaxed(irq_en, uport->membase + SE_GENI_M_IRQ_EN); status = readl_relaxed(uport->membase + SE_GENI_STATUS); /* Possible stop tx is called multiple times. */ @@ -572,21 +590,14 @@ static void qcom_geni_serial_handle_tx(struct uart_port *uport) chunk = uart_circ_chars_pending(xmit); status = readl_relaxed(uport->membase + SE_GENI_TX_FIFO_STATUS); /* Both FIFO and framework buffer are drained */ - if (chunk == port->xmit_size && !status) { - port->xmit_size = 0; - uart_circ_clear(xmit); + if (!chunk && !status) { qcom_geni_serial_stop_tx(uport); goto out_write_wakeup; } - chunk -= port->xmit_size; avail = (port->tx_fifo_depth - port->tx_wm) * port->tx_bytes_pw; - tail = (xmit->tail + port->xmit_size) & (UART_XMIT_SIZE - 1); - if (chunk > (UART_XMIT_SIZE - tail)) - chunk = UART_XMIT_SIZE - tail; - if (chunk > avail) - chunk = avail; - + tail = xmit->tail; + chunk = min3((size_t)chunk, (size_t)(UART_XMIT_SIZE - tail), avail); if (!chunk) goto out_write_wakeup; @@ -595,24 +606,27 @@ static void qcom_geni_serial_handle_tx(struct uart_port *uport) remaining = chunk; for (i = 0; i < chunk; ) { unsigned int tx_bytes; - unsigned int buf = 0; + u8 buf[sizeof(u32)]; int c; - tx_bytes = min_t(size_t, remaining, (size_t)port->tx_bytes_pw); + memset(buf, 0, ARRAY_SIZE(buf)); + tx_bytes = min_t(size_t, remaining, port->tx_bytes_pw); for (c = 0; c < tx_bytes ; c++) - buf |= (xmit->buf[tail + c] << (c * BITS_PER_BYTE)); + buf[c] = xmit->buf[tail + c]; - writel_relaxed(buf, uport->membase + SE_GENI_TX_FIFOn); + iowrite32_rep(uport->membase + SE_GENI_TX_FIFOn, buf, 1); i += tx_bytes; - tail = (tail + tx_bytes) & (UART_XMIT_SIZE - 1); + tail += tx_bytes; uport->icount.tx += tx_bytes; remaining -= tx_bytes; } + + xmit->tail = tail & (UART_XMIT_SIZE - 1); qcom_geni_serial_poll_tx_done(uport); - port->xmit_size += chunk; out_write_wakeup: - uart_write_wakeup(uport); + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(uport); } static irqreturn_t qcom_geni_serial_isr(int isr, void *dev) @@ -627,7 +641,7 @@ static irqreturn_t qcom_geni_serial_isr(int isr, void *dev) struct qcom_geni_serial_port *port = to_dev_port(uport, uport); if (uport->suspended) - return IRQ_HANDLED; + return IRQ_NONE; spin_lock_irqsave(&uport->lock, flags); m_irq_status = readl_relaxed(uport->membase + SE_GENI_M_IRQ_STATUS); @@ -667,20 +681,16 @@ out_unlock: return IRQ_HANDLED; } -static int get_tx_fifo_size(struct qcom_geni_serial_port *port) +static void get_tx_fifo_size(struct qcom_geni_serial_port *port) { struct uart_port *uport; - if (!port) - return -ENODEV; - uport = &port->uport; port->tx_fifo_depth = geni_se_get_tx_fifo_depth(&port->se); port->tx_fifo_width = geni_se_get_tx_fifo_width(&port->se); port->rx_fifo_depth = geni_se_get_rx_fifo_depth(&port->se); uport->fifosize = (port->tx_fifo_depth * port->tx_fifo_width) / BITS_PER_BYTE; - return 0; } static void set_rfr_wm(struct qcom_geni_serial_port *port) @@ -702,7 +712,6 @@ static void qcom_geni_serial_shutdown(struct uart_port *uport) /* Stop the console before stopping the current tx */ console_stop(uport->cons); - disable_irq(uport->irq); free_irq(uport->irq, uport); spin_lock_irqsave(&uport->lock, flags); qcom_geni_serial_stop_tx(uport); @@ -892,7 +901,7 @@ out_restart_rx: static unsigned int qcom_geni_serial_tx_empty(struct uart_port *uport) { - return !readl_relaxed(uport->membase + SE_GENI_TX_FIFO_STATUS); + return !readl(uport->membase + SE_GENI_TX_FIFO_STATUS); } #ifdef CONFIG_SERIAL_QCOM_GENI_CONSOLE @@ -910,7 +919,7 @@ static int __init qcom_geni_console_setup(struct console *co, char *options) port = get_port_from_line(co->index); if (IS_ERR(port)) { - pr_err("Invalid line %d(%d)\n", co->index, (int)PTR_ERR(port)); + pr_err("Invalid line %d\n", co->index); return PTR_ERR(port); } @@ -942,6 +951,65 @@ static int __init qcom_geni_console_setup(struct console *co, char *options) return uart_set_options(uport, co, baud, parity, bits, flow); } +static void qcom_geni_serial_earlycon_write(struct console *con, + const char *s, unsigned int n) +{ + struct earlycon_device *dev = con->data; + + __qcom_geni_serial_console_write(&dev->port, s, n); +} + +static int __init qcom_geni_serial_earlycon_setup(struct earlycon_device *dev, + const char *opt) +{ + struct uart_port *uport = &dev->port; + u32 tx_trans_cfg; + u32 tx_parity_cfg = 0; /* Disable Tx Parity */ + u32 rx_trans_cfg = 0; + u32 rx_parity_cfg = 0; /* Disable Rx Parity */ + u32 stop_bit_len = 0; /* Default stop bit length - 1 bit */ + u32 bits_per_char; + struct geni_se se; + + if (!uport->membase) + return -EINVAL; + + memset(&se, 0, sizeof(se)); + se.base = uport->membase; + if (geni_se_read_proto(&se) != GENI_SE_UART) + return -ENXIO; + /* + * Ignore Flow control. + * n = 8. + */ + tx_trans_cfg = UART_CTS_MASK; + bits_per_char = BITS_PER_BYTE; + + /* + * Make an unconditional cancel on the main sequencer to reset + * it else we could end up in data loss scenarios. + */ + qcom_geni_serial_poll_tx_done(uport); + qcom_geni_serial_abort_rx(uport); + geni_se_config_packing(&se, BITS_PER_BYTE, 1, false, true, false); + geni_se_init(&se, DEF_FIFO_DEPTH_WORDS / 2, DEF_FIFO_DEPTH_WORDS - 2); + geni_se_select_mode(&se, GENI_SE_FIFO); + + writel_relaxed(tx_trans_cfg, uport->membase + SE_UART_TX_TRANS_CFG); + writel_relaxed(tx_parity_cfg, uport->membase + SE_UART_TX_PARITY_CFG); + writel_relaxed(rx_trans_cfg, uport->membase + SE_UART_RX_TRANS_CFG); + writel_relaxed(rx_parity_cfg, uport->membase + SE_UART_RX_PARITY_CFG); + writel_relaxed(bits_per_char, uport->membase + SE_UART_TX_WORD_LEN); + writel_relaxed(bits_per_char, uport->membase + SE_UART_RX_WORD_LEN); + writel_relaxed(stop_bit_len, uport->membase + SE_UART_TX_STOP_BIT_LEN); + + dev->con->write = qcom_geni_serial_earlycon_write; + dev->con->setup = NULL; + return 0; +} +OF_EARLYCON_DECLARE(qcom_geni, "qcom,geni-debug-uart", + qcom_geni_serial_earlycon_setup); + static int __init console_register(struct uart_driver *drv) { return uart_register_driver(drv); @@ -1026,16 +1094,13 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) if (pdev->dev.of_node) line = of_alias_get_id(pdev->dev.of_node, "serial"); - else - line = pdev->id; if (line < 0 || line >= GENI_UART_CONS_PORTS) return -ENXIO; port = get_port_from_line(line); if (IS_ERR(port)) { - ret = PTR_ERR(port); - dev_err(&pdev->dev, "Invalid line %d(%d)\n", line, ret); - return ret; + dev_err(&pdev->dev, "Invalid line %d\n", line); + return PTR_ERR(port); } uport = &port->uport; @@ -1072,7 +1137,6 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) uport->private_data = &qcom_geni_console_driver; platform_set_drvdata(pdev, port); port->handle_rx = handle_rx_console; - port->setup = false; return uart_add_one_port(&qcom_geni_console_driver, uport); } @@ -1087,8 +1151,7 @@ static int qcom_geni_serial_remove(struct platform_device *pdev) static int __maybe_unused qcom_geni_serial_sys_suspend_noirq(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct qcom_geni_serial_port *port = platform_get_drvdata(pdev); + struct qcom_geni_serial_port *port = dev_get_drvdata(dev); struct uart_port *uport = &port->uport; uart_suspend_port(uport->private_data, uport); @@ -1097,12 +1160,19 @@ static int __maybe_unused qcom_geni_serial_sys_suspend_noirq(struct device *dev) static int __maybe_unused qcom_geni_serial_sys_resume_noirq(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct qcom_geni_serial_port *port = platform_get_drvdata(pdev); + struct qcom_geni_serial_port *port = dev_get_drvdata(dev); struct uart_port *uport = &port->uport; if (console_suspend_enabled && uport->suspended) { uart_resume_port(uport->private_data, uport); + /* + * uart_suspend_port() invokes port shutdown which in turn + * frees the irq. uart_resume_port invokes port startup which + * performs request_irq. The request_irq auto-enables the IRQ. + * In addition, resume_noirq implicitly enables the IRQ and + * leads to an unbalanced IRQ enable warning. Disable the IRQ + * before returning so that the warning is suppressed. + */ disable_irq(uport->irq); } return 0; @@ -1133,11 +1203,6 @@ static int __init qcom_geni_serial_init(void) { int ret; - qcom_geni_console_port.uport.iotype = UPIO_MEM; - qcom_geni_console_port.uport.ops = &qcom_geni_console_pops; - qcom_geni_console_port.uport.flags = UPF_BOOT_AUTOCONF; - qcom_geni_console_port.uport.line = 0; - ret = console_register(&qcom_geni_console_driver); if (ret) return ret; |