Quoting Karthikeyan Ramasubramanian (2018-03-14 16:58:49)
> diff --git a/drivers/tty/serial/qcom_geni_serial.c
> b/drivers/tty/serial/qcom_geni_serial.c
> new file mode 100644
> index 0000000..1442777
> --- /dev/null
> +++ b/drivers/tty/serial/qcom_geni_serial.c
> @@ -0,0 +1,1158 @@
> +
> +#ifdef CONFIG_SERIAL_QCOM_GENI_CONSOLE
> +static void qcom_geni_serial_wr_char(struct uart_port *uport, int ch)
> +{
> + writel_relaxed(ch, uport->membase + SE_GENI_TX_FIFOn);
Does this expect the whole word to have data to write? Or does the FIFO
output a character followed by three NUL bytes each time it gets
written? The way that uart_console_write() works is to take each
character a byte at a time, put it into an int (so extend that byte with
zero) and then pass it to the putchar function. I would expect that at
this point the hardware sees the single character and then 3 NULs enter
the FIFO each time.
For previous MSM uarts I had to handle this oddity by packing the words
into the fifo four at a time. You may need to do the same here.
> +}
> +
> +static void
> +__qcom_geni_serial_console_write(struct uart_port *uport, const char *s,
> + unsigned int count)
> +{
> + int i;
> + u32 bytes_to_send = count;
> +
> + for (i = 0; i < count; i++) {
> + if (s[i] == '\n')
Can you add a comment that uart_console_write() adds a carriage return
for each newline?
> + bytes_to_send++;
> + }
> +
> + writel_relaxed(DEF_TX_WM, uport->membase + SE_GENI_TX_WATERMARK_REG);
> + qcom_geni_serial_setup_tx(uport, bytes_to_send);
> + for (i = 0; i < count; ) {
> + size_t chars_to_write = 0;
> + size_t avail = DEF_FIFO_DEPTH_WORDS - DEF_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
> + * command. Unfortunately the current data being written is
> + * lost.
> + */
> + 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);
The _t part of min_t should do the casting already, so we can drop the
cast on count - i?
> + uart_console_write(uport, s + i, chars_to_write,
> + qcom_geni_serial_wr_char);
> + writel_relaxed(M_TX_FIFO_WATERMARK_EN, uport->membase +
> + SE_GENI_M_IRQ_CLEAR);
> + i += chars_to_write;
> + }
> + qcom_geni_serial_poll_tx_done(uport);
> +}
> +
> +static void qcom_geni_serial_console_write(struct console *co, const char *s,
> + unsigned int count)
> +{
> + struct uart_port *uport;
> + struct qcom_geni_serial_port *port;
> + bool locked = true;
> + unsigned long flags;
> +
> + WARN_ON(co->index < 0 || co->index >= GENI_UART_CONS_PORTS);
> +
> + port = get_port_from_line(co->index);
> + if (IS_ERR(port))
> + return;
> +
> + uport = &port->uport;
> + if (oops_in_progress)
> + locked = spin_trylock_irqsave(&uport->lock, flags);
> + else
> + spin_lock_irqsave(&uport->lock, flags);
> +
> + /* Cancel the current write to log the fault */
> + if (!locked) {
> + geni_se_cancel_m_cmd(&port->se);
> + if (!qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS,
> + M_CMD_CANCEL_EN, true)) {
> + geni_se_abort_m_cmd(&port->se);
> + qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS,
> + M_CMD_ABORT_EN, true);
> + writel_relaxed(M_CMD_ABORT_EN, uport->membase +
> + SE_GENI_M_IRQ_CLEAR);
> + }
> + writel_relaxed(M_CMD_CANCEL_EN, uport->membase +
> + SE_GENI_M_IRQ_CLEAR);
> + }
> +
> + __qcom_geni_serial_console_write(uport, s, count);
> + if (locked)
> + spin_unlock_irqrestore(&uport->lock, flags);
> +}
Can you also support the OF_EARLYCON_DECLARE method of console writing
so we can get an early printk style debug console?
> +
> +static int handle_rx_console(struct uart_port *uport, u32 bytes, bool drop)
> +{
> + u32 i;
> + unsigned char buf[sizeof(u32)];
> + struct tty_port *tport;
> + struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
> +
> + tport = &uport->state->port;
> + for (i = 0; i < bytes; ) {
> + int c;
> + int chunk = min_t(int, bytes - i, port->rx_bytes_pw);
> +
> + ioread32_rep(uport->membase + SE_GENI_RX_FIFOn, buf, 1);
> + i += chunk;
> + if (drop)
> + continue;
> +
> + for (c = 0; c < chunk; c++) {
> + int sysrq;
> +
> + uport->icount.rx++;
> + if (port->brk && buf[c] == 0) {
> + port->brk = false;
> + if (uart_handle_break(uport))
> + continue;
Thanks!
> + }
> +
> + sysrq = uart_handle_sysrq_char(uport, buf[c]);
> + if (!sysrq)
> + tty_insert_flip_char(tport, buf[c],
> TTY_NORMAL);
> + }
> + }
> + if (!drop)
> + tty_flip_buffer_push(tport);
> + return 0;
> +}
[...]
> +
> +static void qcom_geni_serial_handle_tx(struct uart_port *uport)
> +{
> + struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
> + struct circ_buf *xmit = &uport->state->xmit;
> + size_t avail;
> + size_t remaining;
> + int i;
> + u32 status;
> + unsigned int chunk;
> + int tail;
> +
> + 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);
> + 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;
chunk = min3(chunk, UART_XMIT_SIZE - tail, avail);
> +
> + if (!chunk)
> + goto out_write_wakeup;
> +
> + qcom_geni_serial_setup_tx(uport, chunk);
> +
> + remaining = chunk;
> + for (i = 0; i < chunk; ) {
> + unsigned int tx_bytes;
> + unsigned int buf = 0;
Make buf into a u8 array of 4?
> + int c;
> +
> + tx_bytes = min_t(size_t, remaining,
> (size_t)port->tx_bytes_pw);
> + for (c = 0; c < tx_bytes ; c++)
> + buf |= (xmit->buf[tail + c] << (c * BITS_PER_BYTE));
And then just put buf[c] = xmit->buf[tail + c] here?
> +
> + writel_relaxed(buf, uport->membase + SE_GENI_TX_FIFOn);
This also needs to be an iowrite32_rep(uport->membase, buf, 1) for
endian reasons.
> +
> + i += tx_bytes;
> + tail = (tail + tx_bytes) & (UART_XMIT_SIZE - 1);
> + uport->icount.tx += tx_bytes;
> + remaining -= tx_bytes;
> + }
> + qcom_geni_serial_poll_tx_done(uport);
> + port->xmit_size += chunk;
> +out_write_wakeup:
> + uart_write_wakeup(uport);
> +}
> +
> +static irqreturn_t qcom_geni_serial_isr(int isr, void *dev)
> +{
> + unsigned int m_irq_status;
> + unsigned int s_irq_status;
> + struct uart_port *uport = dev;
> + unsigned long flags;
> + unsigned int m_irq_en;
> + bool drop_rx = false;
> + struct tty_port *tport = &uport->state->port;
> + struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
> +
> + if (uport->suspended)
> + return IRQ_HANDLED;
Is it a spurious IRQ if this happens? Return IRQ_NONE instead?
> +
> + spin_lock_irqsave(&uport->lock, flags);
> + m_irq_status = readl_relaxed(uport->membase + SE_GENI_M_IRQ_STATUS);
> + s_irq_status = readl_relaxed(uport->membase + SE_GENI_S_IRQ_STATUS);
> + m_irq_en = readl_relaxed(uport->membase + SE_GENI_M_IRQ_EN);
> + writel_relaxed(m_irq_status, uport->membase + SE_GENI_M_IRQ_CLEAR);
> + writel_relaxed(s_irq_status, uport->membase + SE_GENI_S_IRQ_CLEAR);
> +
> + if (WARN_ON(m_irq_status & M_ILLEGAL_CMD_EN))
> + goto out_unlock;
> +
> + if (s_irq_status & S_RX_FIFO_WR_ERR_EN) {
> + uport->icount.overrun++;
> + tty_insert_flip_char(tport, 0, TTY_OVERRUN);
> + }
> +
> + if (m_irq_status & (M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN) &&
> + m_irq_en & (M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN))
> + qcom_geni_serial_handle_tx(uport);
> +
> + if (s_irq_status & S_GP_IRQ_0_EN || s_irq_status & S_GP_IRQ_1_EN) {
> + if (s_irq_status & S_GP_IRQ_0_EN)
> + uport->icount.parity++;
> + drop_rx = true;
> + } else if (s_irq_status & S_GP_IRQ_2_EN ||
> + s_irq_status & S_GP_IRQ_3_EN) {
> + uport->icount.brk++;
Maybe move this stat accounting to the place where brk is handled?
> + port->brk = true;
> + }
> +
> + if (s_irq_status & S_RX_FIFO_WATERMARK_EN ||
> + s_irq_status & S_RX_FIFO_LAST_EN)
> + qcom_geni_serial_handle_rx(uport, drop_rx);
> +
> +out_unlock:
> + spin_unlock_irqrestore(&uport->lock, flags);
> + return IRQ_HANDLED;
> +}
> +
> +static int get_tx_fifo_size(struct qcom_geni_serial_port *port)
> +{
> + struct uart_port *uport;
> +
> + if (!port)
> + return -ENODEV;
This happens?
> +
> + 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;
> +}
> +
[...]
> +
> +#ifdef CONFIG_SERIAL_QCOM_GENI_CONSOLE
> +static int __init qcom_geni_console_setup(struct console *co, char *options)
> +{
> + struct uart_port *uport;
> + struct qcom_geni_serial_port *port;
> + int baud;
> + int bits = 8;
> + int parity = 'n';
> + int flow = 'n';
> +
> + if (co->index >= GENI_UART_CONS_PORTS || co->index < 0)
> + return -ENXIO;
> +
> + port = get_port_from_line(co->index);
> + if (IS_ERR(port)) {
> + pr_err("Invalid line %d(%d)\n", co->index,
> (int)PTR_ERR(port));
Use %ld to avoid casting the error value? Or just don't print it at all
because it doesn't really help the end user.
> + return PTR_ERR(port);
> + }
> +
> + uport = &port->uport;
> +
> + if (unlikely(!uport->membase))
> + return -ENXIO;
> +
> + if (geni_se_resources_on(&port->se)) {
> + dev_err(port->se.dev, "Error turning on resources\n");
> + return -ENXIO;
> + }
> +
> + if (unlikely(geni_se_read_proto(&port->se) != GENI_SE_UART)) {
> + geni_se_resources_off(&port->se);
> + return -ENXIO;
> + }
> +
> + if (!port->setup) {
> + port->tx_bytes_pw = 1;
> + port->rx_bytes_pw = RX_BYTES_PW;
> + qcom_geni_serial_stop_rx(uport);
> + qcom_geni_serial_port_setup(uport);
> + }
> +
> + if (options)
> + uart_parse_options(options, &baud, &parity, &bits, &flow);
> +
> + return uart_set_options(uport, co, baud, parity, bits, flow);
> +}
[..]
> +
> +static int qcom_geni_serial_probe(struct platform_device *pdev)
> +{
> + int ret = 0;
> + int line = -1;
> + struct qcom_geni_serial_port *port;
> + struct uart_port *uport;
> + struct resource *res;
> +
> + if (pdev->dev.of_node)
> + line = of_alias_get_id(pdev->dev.of_node, "serial");
> + else
> + line = pdev->id;
Do we need to support the non-alias method?
> +
> + 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;
> + }
> +
> + uport = &port->uport;
> + /* Don't allow 2 drivers to access the same port */
> + if (uport->private_data)
> + return -ENODEV;
> +
> + uport->dev = &pdev->dev;
> + port->se.dev = &pdev->dev;
> + port->se.wrapper = dev_get_drvdata(pdev->dev.parent);
> + port->se.clk = devm_clk_get(&pdev->dev, "se");
> + if (IS_ERR(port->se.clk)) {
> + ret = PTR_ERR(port->se.clk);
> + dev_err(&pdev->dev, "Err getting SE Core clk %d\n", ret);
> + return ret;
> + }
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (IS_ERR(res))
> + return PTR_ERR(res);
> + uport->mapbase = res->start;
> +
> + port->tx_fifo_depth = DEF_FIFO_DEPTH_WORDS;
> + port->rx_fifo_depth = DEF_FIFO_DEPTH_WORDS;
> + port->tx_fifo_width = DEF_FIFO_WIDTH_BITS;
> +
> + uport->irq = platform_get_irq(pdev, 0);
> + if (uport->irq < 0) {
> + dev_err(&pdev->dev, "Failed to get IRQ %d\n", uport->irq);
> + return uport->irq;
> + }
> +
> + uport->private_data = &qcom_geni_console_driver;
> + platform_set_drvdata(pdev, port);
> + port->handle_rx = handle_rx_console;
> + port->setup = false;
This would be set to false already?
> + return uart_add_one_port(&qcom_geni_console_driver, uport);
> +}
> +
> +static int qcom_geni_serial_remove(struct platform_device *pdev)
> +{
> + struct qcom_geni_serial_port *port = platform_get_drvdata(pdev);
> + struct uart_driver *drv = port->uport.private_data;
> +
> + uart_remove_one_port(drv, &port->uport);
> + return 0;
> +}
> +
> +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 uart_port *uport = &port->uport;
> +
> + uart_suspend_port(uport->private_data, uport);
> + return 0;
> +}
> +
> +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 uart_port *uport = &port->uport;
> +
> + if (console_suspend_enabled && uport->suspended) {
> + uart_resume_port(uport->private_data, uport);
> + disable_irq(uport->irq);
I missed the enable_irq() part. Is this still necessary?
> + }
> + return 0;
> +}
> +
> +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;
Why can't this be done statically?
> +
> + ret = console_register(&qcom_geni_console_driver);
> + if (ret)
> + return ret;
> +
> + ret = platform_driver_register(&qcom_geni_serial_platform_driver);
> + if (ret)
> + console_unregister(&qcom_geni_console_driver);
> + return ret;
> +}
> +module_init(qcom_geni_serial_init);
--
To unsubscribe from this list: send the line "unsubscribe linux-doc" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html