OSDN Git Service

tty: serial: qcom_geni_serial: Add support for flow control
authorGirish Mahadevan <girishm@codeaurora.org>
Fri, 13 Jul 2018 22:17:35 +0000 (16:17 -0600)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 16 Jul 2018 09:56:22 +0000 (11:56 +0200)
Add support for flow control functionality in the GENI serial driver
and also support for non-console higher baud rate(upto 4Mbps) usecases.

Signed-off-by: Girish Mahadevan <girishm@codeaurora.org>
Signed-off-by: Mohammed Khajapasha <mkhaja@codeaurora.org>
Signed-off-by: Karthikeyan Ramasubramanian <kramasub@codeaurora.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/devicetree/bindings/soc/qcom/qcom,geni-se.txt
drivers/tty/serial/qcom_geni_serial.c

index d330c73..a9eeca2 100644 (file)
@@ -46,7 +46,7 @@ Child nodes should conform to I2C bus binding as described in i2c.txt.
 Qualcomm Technologies Inc. GENI Serial Engine based UART Controller
 
 Required properties:
-- compatible:          Must be "qcom,geni-debug-uart".
+- compatible:          Must be "qcom,geni-debug-uart" or "qcom,geni-uart".
 - reg:                         Must contain UART register location and length.
 - interrupts:          Must contain UART core interrupts.
 - clock-names:         Must contain "se".
index c62e17c..29ec343 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/tty_flip.h>
 
 /* UART specific GENI registers */
+#define SE_UART_LOOPBACK_CFG           0x22c
 #define SE_UART_TX_TRANS_CFG           0x25c
 #define SE_UART_TX_WORD_LEN            0x268
 #define SE_UART_TX_STOP_BIT_LEN                0x26c
@@ -26,6 +27,7 @@
 #define SE_UART_RX_STALE_CNT           0x294
 #define SE_UART_TX_PARITY_CFG          0x2a4
 #define SE_UART_RX_PARITY_CFG          0x2a8
+#define SE_UART_MANUAL_RFR             0x2ac
 
 /* SE_UART_TRANS_CFG */
 #define UART_TX_PAR_EN         BIT(0)
 #define PAR_SPACE              0x10
 #define PAR_MARK               0x11
 
+/* SE_UART_MANUAL_RFR register fields */
+#define UART_MANUAL_RFR_EN     BIT(31)
+#define UART_RFR_NOT_READY     BIT(1)
+#define UART_RFR_READY         BIT(0)
+
 /* UART M_CMD OP codes */
 #define UART_START_TX          0x1
 #define UART_START_BREAK       0x4
 #define STALE_TIMEOUT          16
 #define DEFAULT_BITS_PER_CHAR  10
 #define GENI_UART_CONS_PORTS   1
+#define GENI_UART_PORTS                3
 #define DEF_FIFO_DEPTH_WORDS   16
 #define DEF_TX_WM              2
 #define DEF_FIFO_WIDTH_BITS    32
 #define UART_CONSOLE_RX_WM     2
+#define MAX_LOOPBACK_CFG       3
 
 #ifdef CONFIG_CONSOLE_POLL
 #define RX_BYTES_PW 1
@@ -101,22 +110,81 @@ struct qcom_geni_serial_port {
        unsigned int baud;
        unsigned int tx_bytes_pw;
        unsigned int rx_bytes_pw;
+       u32 *rx_fifo;
+       u32 loopback;
        bool brk;
 };
 
 static const struct uart_ops qcom_geni_console_pops;
+static const struct uart_ops qcom_geni_uart_pops;
 static struct uart_driver qcom_geni_console_driver;
+static struct uart_driver qcom_geni_uart_driver;
 static int handle_rx_console(struct uart_port *uport, u32 bytes, bool drop);
+static int handle_rx_uart(struct uart_port *uport, u32 bytes, bool drop);
 static unsigned int qcom_geni_serial_tx_empty(struct uart_port *port);
 static void qcom_geni_serial_stop_rx(struct uart_port *uport);
 
 static const unsigned long root_freq[] = {7372800, 14745600, 19200000, 29491200,
                                        32000000, 48000000, 64000000, 80000000,
-                                       96000000, 100000000};
+                                       96000000, 100000000, 102400000,
+                                       112000000, 120000000, 128000000};
 
 #define to_dev_port(ptr, member) \
                container_of(ptr, struct qcom_geni_serial_port, member)
 
+static struct qcom_geni_serial_port qcom_geni_uart_ports[GENI_UART_PORTS] = {
+       [0] = {
+               .uport = {
+                               .iotype = UPIO_MEM,
+                               .ops = &qcom_geni_uart_pops,
+                               .flags = UPF_BOOT_AUTOCONF,
+                               .line = 0,
+               },
+       },
+       [1] = {
+               .uport = {
+                               .iotype = UPIO_MEM,
+                               .ops = &qcom_geni_uart_pops,
+                               .flags = UPF_BOOT_AUTOCONF,
+                               .line = 1,
+               },
+       },
+       [2] = {
+               .uport = {
+                               .iotype = UPIO_MEM,
+                               .ops = &qcom_geni_uart_pops,
+                               .flags = UPF_BOOT_AUTOCONF,
+                               .line = 2,
+               },
+       },
+};
+
+static ssize_t loopback_show(struct device *dev,
+                               struct device_attribute *attr, char *buf)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct qcom_geni_serial_port *port = platform_get_drvdata(pdev);
+
+       return snprintf(buf, sizeof(u32), "%d\n", port->loopback);
+}
+
+static ssize_t loopback_store(struct device *dev,
+                               struct device_attribute *attr, const char *buf,
+                               size_t size)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct qcom_geni_serial_port *port = platform_get_drvdata(pdev);
+       u32 loopback;
+
+       if (kstrtoint(buf, 0, &loopback) || loopback > MAX_LOOPBACK_CFG) {
+               dev_err(dev, "Invalid input\n");
+               return -EINVAL;
+       }
+       port->loopback = loopback;
+       return size;
+}
+static DEVICE_ATTR_RW(loopback);
+
 static struct qcom_geni_serial_port qcom_geni_console_port = {
        .uport = {
                .iotype = UPIO_MEM,
@@ -148,14 +216,33 @@ static void qcom_geni_serial_config_port(struct uart_port *uport, int cfg_flags)
        }
 }
 
-static unsigned int qcom_geni_cons_get_mctrl(struct uart_port *uport)
+static unsigned int qcom_geni_serial_get_mctrl(struct uart_port *uport)
 {
-       return TIOCM_DSR | TIOCM_CAR | TIOCM_CTS;
+       unsigned int mctrl = TIOCM_DSR | TIOCM_CAR;
+       u32 geni_ios;
+
+       if (uart_console(uport) || !uart_cts_enabled(uport)) {
+               mctrl |= TIOCM_CTS;
+       } else {
+               geni_ios = readl_relaxed(uport->membase + SE_GENI_IOS);
+               if (!(geni_ios & IO2_DATA_IN))
+                       mctrl |= TIOCM_CTS;
+       }
+
+       return mctrl;
 }
 
-static void qcom_geni_cons_set_mctrl(struct uart_port *uport,
+static void qcom_geni_serial_set_mctrl(struct uart_port *uport,
                                                        unsigned int mctrl)
 {
+       u32 uart_manual_rfr = 0;
+
+       if (uart_console(uport) || !uart_cts_enabled(uport))
+               return;
+
+       if (!(mctrl & TIOCM_RTS))
+               uart_manual_rfr = UART_MANUAL_RFR_EN | UART_RFR_NOT_READY;
+       writel_relaxed(uart_manual_rfr, uport->membase + SE_UART_MANUAL_RFR);
 }
 
 static const char *qcom_geni_serial_get_type(struct uart_port *uport)
@@ -163,11 +250,16 @@ static const char *qcom_geni_serial_get_type(struct uart_port *uport)
        return "MSM";
 }
 
-static struct qcom_geni_serial_port *get_port_from_line(int line)
+static struct qcom_geni_serial_port *get_port_from_line(int line, bool console)
 {
-       if (line < 0 || line >= GENI_UART_CONS_PORTS)
+       struct qcom_geni_serial_port *port;
+       int nr_ports = console ? GENI_UART_CONS_PORTS : GENI_UART_PORTS;
+
+       if (line < 0 || line >= nr_ports)
                return ERR_PTR(-ENXIO);
-       return &qcom_geni_console_port;
+
+       port = console ? &qcom_geni_console_port : &qcom_geni_uart_ports[line];
+       return port;
 }
 
 static bool qcom_geni_serial_poll_bit(struct uart_port *uport,
@@ -346,7 +438,7 @@ static void qcom_geni_serial_console_write(struct console *co, const char *s,
 
        WARN_ON(co->index < 0 || co->index >= GENI_UART_CONS_PORTS);
 
-       port = get_port_from_line(co->index);
+       port = get_port_from_line(co->index, true);
        if (IS_ERR(port))
                return;
 
@@ -420,6 +512,32 @@ static int handle_rx_console(struct uart_port *uport, u32 bytes, bool drop)
 
 #endif /* CONFIG_SERIAL_QCOM_GENI_CONSOLE */
 
+static int handle_rx_uart(struct uart_port *uport, u32 bytes, bool drop)
+{
+       unsigned char *buf;
+       struct tty_port *tport;
+       struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
+       u32 num_bytes_pw = port->tx_fifo_width / BITS_PER_BYTE;
+       u32 words = ALIGN(bytes, num_bytes_pw) / num_bytes_pw;
+       int ret;
+
+       tport = &uport->state->port;
+       ioread32_rep(uport->membase + SE_GENI_RX_FIFOn, port->rx_fifo, words);
+       if (drop)
+               return 0;
+
+       buf = (unsigned char *)port->rx_fifo;
+       ret = tty_insert_flip_string(tport, buf, bytes);
+       if (ret != bytes) {
+               dev_err(uport->dev, "%s:Unable to push data ret %d_bytes %d\n",
+                               __func__, ret, bytes);
+               WARN_ON_ONCE(1);
+       }
+       uport->icount.rx += ret;
+       tty_flip_buffer_push(tport);
+       return ret;
+}
+
 static void qcom_geni_serial_start_tx(struct uart_port *uport)
 {
        u32 irq_en;
@@ -586,6 +704,7 @@ static void qcom_geni_serial_handle_tx(struct uart_port *uport)
        u32 status;
        unsigned int chunk;
        int tail;
+       u32 irq_en;
 
        chunk = uart_circ_chars_pending(xmit);
        status = readl_relaxed(uport->membase + SE_GENI_TX_FIFO_STATUS);
@@ -595,6 +714,13 @@ static void qcom_geni_serial_handle_tx(struct uart_port *uport)
                goto out_write_wakeup;
        }
 
+       if (!uart_console(uport)) {
+               irq_en = readl_relaxed(uport->membase + SE_GENI_M_IRQ_EN);
+               irq_en &= ~(M_TX_FIFO_WATERMARK_EN);
+               writel_relaxed(0, uport->membase + SE_GENI_TX_WATERMARK_REG);
+               writel_relaxed(irq_en, uport->membase + SE_GENI_M_IRQ_EN);
+       }
+
        avail = (port->tx_fifo_depth - port->tx_wm) * port->tx_bytes_pw;
        tail = xmit->tail;
        chunk = min3((size_t)chunk, (size_t)(UART_XMIT_SIZE - tail), avail);
@@ -623,7 +749,8 @@ static void qcom_geni_serial_handle_tx(struct uart_port *uport)
        }
 
        xmit->tail = tail & (UART_XMIT_SIZE - 1);
-       qcom_geni_serial_poll_tx_done(uport);
+       if (uart_console(uport))
+               qcom_geni_serial_poll_tx_done(uport);
 out_write_wakeup:
        if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
                uart_write_wakeup(uport);
@@ -710,7 +837,8 @@ static void qcom_geni_serial_shutdown(struct uart_port *uport)
        unsigned long flags;
 
        /* Stop the console before stopping the current tx */
-       console_stop(uport->cons);
+       if (uart_console(uport))
+               console_stop(uport->cons);
 
        free_irq(uport->irq, uport);
        spin_lock_irqsave(&uport->lock, flags);
@@ -731,13 +859,20 @@ static int qcom_geni_serial_port_setup(struct uart_port *uport)
         * it else we could end up in data loss scenarios.
         */
        port->xfer_mode = GENI_SE_FIFO;
-       qcom_geni_serial_poll_tx_done(uport);
+       if (uart_console(uport))
+               qcom_geni_serial_poll_tx_done(uport);
        geni_se_config_packing(&port->se, BITS_PER_BYTE, port->tx_bytes_pw,
                                                false, true, false);
        geni_se_config_packing(&port->se, BITS_PER_BYTE, port->rx_bytes_pw,
                                                false, false, true);
        geni_se_init(&port->se, port->rx_wm, port->rx_rfr);
        geni_se_select_mode(&port->se, port->xfer_mode);
+       if (!uart_console(uport)) {
+               port->rx_fifo = devm_kzalloc(uport->dev,
+                       port->rx_fifo_depth * sizeof(u32), GFP_KERNEL);
+               if (!port->rx_fifo)
+                       return -ENOMEM;
+       }
        port->setup = true;
        return 0;
 }
@@ -749,8 +884,13 @@ static int qcom_geni_serial_startup(struct uart_port *uport)
        struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
 
        scnprintf(port->name, sizeof(port->name),
-                 "qcom_serial_geni%d", uport->line);
+                 "qcom_serial_%s%d",
+               (uart_console(uport) ? "console" : "uart"), uport->line);
 
+       if (!uart_console(uport)) {
+               port->tx_bytes_pw = 4;
+               port->rx_bytes_pw = RX_BYTES_PW;
+       }
        proto = geni_se_read_proto(&port->se);
        if (proto != GENI_SE_UART) {
                dev_err(uport->dev, "Invalid FW loaded, proto: %d\n", proto);
@@ -886,6 +1026,9 @@ static void qcom_geni_serial_set_termios(struct uart_port *uport,
        if (baud)
                uart_update_timeout(uport, termios->c_cflag, baud);
 
+       if (!uart_console(uport))
+               writel_relaxed(port->loopback,
+                               uport->membase + SE_UART_LOOPBACK_CFG);
        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);
@@ -917,7 +1060,7 @@ static int __init qcom_geni_console_setup(struct console *co, char *options)
        if (co->index >= GENI_UART_CONS_PORTS  || co->index < 0)
                return -ENXIO;
 
-       port = get_port_from_line(co->index);
+       port = get_port_from_line(co->index, true);
        if (IS_ERR(port)) {
                pr_err("Invalid line %d\n", co->index);
                return PTR_ERR(port);
@@ -1048,16 +1191,23 @@ static void console_unregister(struct uart_driver *drv)
 }
 #endif /* CONFIG_SERIAL_QCOM_GENI_CONSOLE */
 
-static void qcom_geni_serial_cons_pm(struct uart_port *uport,
+static struct uart_driver qcom_geni_uart_driver = {
+       .owner = THIS_MODULE,
+       .driver_name = "qcom_geni_uart",
+       .dev_name = "ttyHS",
+       .nr =  GENI_UART_PORTS,
+};
+
+static void qcom_geni_serial_pm(struct uart_port *uport,
                unsigned int new_state, unsigned int old_state)
 {
        struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
 
-       if (unlikely(!uart_console(uport)))
-               return;
-
        if (new_state == UART_PM_STATE_ON && old_state == UART_PM_STATE_OFF)
                geni_se_resources_on(&port->se);
+       else if (!uart_console(uport) && (new_state == UART_PM_STATE_ON &&
+                               old_state == UART_PM_STATE_UNDEFINED))
+               geni_se_resources_on(&port->se);
        else if (new_state == UART_PM_STATE_OFF &&
                        old_state == UART_PM_STATE_ON)
                geni_se_resources_off(&port->se);
@@ -1074,13 +1224,29 @@ static const struct uart_ops qcom_geni_console_pops = {
        .config_port = qcom_geni_serial_config_port,
        .shutdown = qcom_geni_serial_shutdown,
        .type = qcom_geni_serial_get_type,
-       .set_mctrl = qcom_geni_cons_set_mctrl,
-       .get_mctrl = qcom_geni_cons_get_mctrl,
+       .set_mctrl = qcom_geni_serial_set_mctrl,
+       .get_mctrl = qcom_geni_serial_get_mctrl,
 #ifdef CONFIG_CONSOLE_POLL
        .poll_get_char  = qcom_geni_serial_get_char,
        .poll_put_char  = qcom_geni_serial_poll_put_char,
 #endif
-       .pm = qcom_geni_serial_cons_pm,
+       .pm = qcom_geni_serial_pm,
+};
+
+static const struct uart_ops qcom_geni_uart_pops = {
+       .tx_empty = qcom_geni_serial_tx_empty,
+       .stop_tx = qcom_geni_serial_stop_tx,
+       .start_tx = qcom_geni_serial_start_tx,
+       .stop_rx = qcom_geni_serial_stop_rx,
+       .set_termios = qcom_geni_serial_set_termios,
+       .startup = qcom_geni_serial_startup,
+       .request_port = qcom_geni_serial_request_port,
+       .config_port = qcom_geni_serial_config_port,
+       .shutdown = qcom_geni_serial_shutdown,
+       .type = qcom_geni_serial_get_type,
+       .set_mctrl = qcom_geni_serial_set_mctrl,
+       .get_mctrl = qcom_geni_serial_get_mctrl,
+       .pm = qcom_geni_serial_pm,
 };
 
 static int qcom_geni_serial_probe(struct platform_device *pdev)
@@ -1091,13 +1257,23 @@ static int qcom_geni_serial_probe(struct platform_device *pdev)
        struct uart_port *uport;
        struct resource *res;
        int irq;
+       bool console = false;
+       struct uart_driver *drv;
 
-       if (pdev->dev.of_node)
-               line = of_alias_get_id(pdev->dev.of_node, "serial");
+       if (of_device_is_compatible(pdev->dev.of_node, "qcom,geni-debug-uart"))
+               console = true;
 
-       if (line < 0 || line >= GENI_UART_CONS_PORTS)
-               return -ENXIO;
-       port = get_port_from_line(line);
+       if (pdev->dev.of_node) {
+               if (console) {
+                       drv = &qcom_geni_console_driver;
+                       line = of_alias_get_id(pdev->dev.of_node, "serial");
+               } else {
+                       drv = &qcom_geni_uart_driver;
+                       line = of_alias_get_id(pdev->dev.of_node, "hsuart");
+               }
+       }
+
+       port = get_port_from_line(line, console);
        if (IS_ERR(port)) {
                dev_err(&pdev->dev, "Invalid line %d\n", line);
                return PTR_ERR(port);
@@ -1134,10 +1310,12 @@ static int qcom_geni_serial_probe(struct platform_device *pdev)
        }
        uport->irq = irq;
 
-       uport->private_data = &qcom_geni_console_driver;
+       uport->private_data = drv;
        platform_set_drvdata(pdev, port);
-       port->handle_rx = handle_rx_console;
-       return uart_add_one_port(&qcom_geni_console_driver, uport);
+       port->handle_rx = console ? handle_rx_console : handle_rx_uart;
+       if (!console)
+               device_create_file(uport->dev, &dev_attr_loopback);
+       return uart_add_one_port(drv, uport);
 }
 
 static int qcom_geni_serial_remove(struct platform_device *pdev)
@@ -1154,7 +1332,17 @@ static int __maybe_unused qcom_geni_serial_sys_suspend_noirq(struct device *dev)
        struct qcom_geni_serial_port *port = dev_get_drvdata(dev);
        struct uart_port *uport = &port->uport;
 
-       uart_suspend_port(uport->private_data, uport);
+       if (uart_console(uport)) {
+               uart_suspend_port(uport->private_data, uport);
+       } else {
+               struct uart_state *state = uport->state;
+               /*
+                * If the port is open, deny system suspend.
+                */
+               if (state->pm_state == UART_PM_STATE_ON)
+                       return -EBUSY;
+       }
+
        return 0;
 }
 
@@ -1163,7 +1351,8 @@ static int __maybe_unused qcom_geni_serial_sys_resume_noirq(struct device *dev)
        struct qcom_geni_serial_port *port = dev_get_drvdata(dev);
        struct uart_port *uport = &port->uport;
 
-       if (console_suspend_enabled && uport->suspended) {
+       if (uart_console(uport) &&
+                       console_suspend_enabled && uport->suspended) {
                uart_resume_port(uport->private_data, uport);
                /*
                 * uart_suspend_port() invokes port shutdown which in turn
@@ -1185,6 +1374,7 @@ static const struct dev_pm_ops qcom_geni_serial_pm_ops = {
 
 static const struct of_device_id qcom_geni_serial_match_table[] = {
        { .compatible = "qcom,geni-debug-uart", },
+       { .compatible = "qcom,geni-uart", },
        {}
 };
 MODULE_DEVICE_TABLE(of, qcom_geni_serial_match_table);
@@ -1207,9 +1397,17 @@ static int __init qcom_geni_serial_init(void)
        if (ret)
                return ret;
 
+       ret = uart_register_driver(&qcom_geni_uart_driver);
+       if (ret) {
+               console_unregister(&qcom_geni_console_driver);
+               return ret;
+       }
+
        ret = platform_driver_register(&qcom_geni_serial_platform_driver);
-       if (ret)
+       if (ret) {
                console_unregister(&qcom_geni_console_driver);
+               uart_unregister_driver(&qcom_geni_uart_driver);
+       }
        return ret;
 }
 module_init(qcom_geni_serial_init);
@@ -1218,6 +1416,7 @@ static void __exit qcom_geni_serial_exit(void)
 {
        platform_driver_unregister(&qcom_geni_serial_platform_driver);
        console_unregister(&qcom_geni_console_driver);
+       uart_unregister_driver(&qcom_geni_uart_driver);
 }
 module_exit(qcom_geni_serial_exit);