Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8a8a66a1 authored by Girish Mahadevan's avatar Girish Mahadevan Committed by Greg Kroah-Hartman
Browse files

tty: serial: qcom_geni_serial: Add support for flow control



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: default avatarGirish Mahadevan <girishm@codeaurora.org>
Signed-off-by: default avatarMohammed Khajapasha <mkhaja@codeaurora.org>
Signed-off-by: default avatarKarthikeyan Ramasubramanian <kramasub@codeaurora.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent c58caaab
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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".
+230 −31
Original line number Diff line number Diff line
@@ -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)
@@ -62,6 +64,11 @@
#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
@@ -74,10 +81,12 @@
#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,6 +749,7 @@ static void qcom_geni_serial_handle_tx(struct uart_port *uport)
	}

	xmit->tail = tail & (UART_XMIT_SIZE - 1);
	if (uart_console(uport))
		qcom_geni_serial_poll_tx_done(uport);
out_write_wakeup:
	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
@@ -710,6 +837,7 @@ static void qcom_geni_serial_shutdown(struct uart_port *uport)
	unsigned long flags;

	/* Stop the console before stopping the current tx */
	if (uart_console(uport))
		console_stop(uport->cons);

	free_irq(uport->irq, uport);
@@ -731,6 +859,7 @@ 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;
	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);
@@ -738,6 +867,12 @@ static int qcom_geni_serial_port_setup(struct uart_port *uport)
						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 (of_device_is_compatible(pdev->dev.of_node, "qcom,geni-debug-uart"))
		console = true;

	if (pdev->dev.of_node)
	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");
		}
	}

	if (line < 0 || line >= GENI_UART_CONS_PORTS)
		return -ENXIO;
	port = get_port_from_line(line);
	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;

	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);