diff mbox series

[RFC,7/9] i3c: slave: func: add tty driver

Message ID 20230905213842.3035779-8-Frank.Li@nxp.com (mailing list archive)
State Superseded
Headers show
Series I3C Slave Mode support | expand

Commit Message

Frank Li Sept. 5, 2023, 9:38 p.m. UTC
Add tty over I3C slave function driver.

Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
 drivers/i3c/Kconfig       |   1 +
 drivers/i3c/Makefile      |   1 +
 drivers/i3c/func/Kconfig  |   9 +
 drivers/i3c/func/Makefile |   3 +
 drivers/i3c/func/tty.c    | 345 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 359 insertions(+)
 create mode 100644 drivers/i3c/func/Kconfig
 create mode 100644 drivers/i3c/func/Makefile
 create mode 100644 drivers/i3c/func/tty.c
diff mbox series

Patch

diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig
index bdc173bc0da12..fa0f63e0e3e6e 100644
--- a/drivers/i3c/Kconfig
+++ b/drivers/i3c/Kconfig
@@ -50,4 +50,5 @@  config I3C_SLAVE_CONFIGFS
 
 if I3C_SLAVE
 source "drivers/i3c/slave/Kconfig"
+source "drivers/i3c/func/Kconfig"
 endif #I#C_SLAVE
diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile
index ef1acbe13fe60..7814bf2dd9b40 100644
--- a/drivers/i3c/Makefile
+++ b/drivers/i3c/Makefile
@@ -5,3 +5,4 @@  obj-$(CONFIG_I3C)		+= master/
 obj-$(CONFIG_I3C_SLAVE)		+= slave.o
 obj-$(CONFIG_I3C_SLAVE_CONFIGFS)	+= i3c-cfs.o
 obj-$(CONFIG_I3C_SLAVE)		+= slave/
+obj-$(CONFIG_I3C_SLAVE)		+= func/
diff --git a/drivers/i3c/func/Kconfig b/drivers/i3c/func/Kconfig
new file mode 100644
index 0000000000000..f122e2cc32de8
--- /dev/null
+++ b/drivers/i3c/func/Kconfig
@@ -0,0 +1,9 @@ 
+# SPDX-License-Identifier: GPL-2.0
+
+config I3C_SLAVE_FUNC_TTY
+	tristate "PCI Endpoint Test driver"
+	depends on I3C_SLAVE
+	help
+	  I3C Slave TTY Function Driver
+
+	  General TTY over I3C slave controller function drivers.
diff --git a/drivers/i3c/func/Makefile b/drivers/i3c/func/Makefile
new file mode 100644
index 0000000000000..db3262e402edd
--- /dev/null
+++ b/drivers/i3c/func/Makefile
@@ -0,0 +1,3 @@ 
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_I3C_SLAVE_FUNC_TTY)              += tty.o
diff --git a/drivers/i3c/func/tty.c b/drivers/i3c/func/tty.c
new file mode 100644
index 0000000000000..be43878913452
--- /dev/null
+++ b/drivers/i3c/func/tty.c
@@ -0,0 +1,345 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023 NXP
+ * Author: Frank Li <Frank.Li@nxp.com>
+ */
+
+#include <linux/i3c/slave.h>
+#include <linux/serial_core.h>
+#include <linux/slab.h>
+#include <linux/tty_flip.h>
+
+#define PORT_I3C 124
+
+struct ttyi3c_port {
+	struct uart_port	port;
+	struct i3c_slave_func	*i3cdev;
+	unsigned long		buffer;
+	struct work_struct	work;
+	struct workqueue_struct	*workqueue;
+};
+
+static struct uart_driver ttyi3c_reg = {
+	.owner		= THIS_MODULE,
+	.driver_name	= "ttySI3C",
+	.dev_name	= "ttySI3C",
+	.nr		= 1,
+};
+
+static unsigned int ttyi3c_tx_empty(struct uart_port *port)
+{
+	return 0;
+}
+
+static void ttyi3c_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+
+}
+
+static unsigned int ttyi3c_get_mctrl(struct uart_port *port)
+{
+	return 0;
+}
+
+static void ttyi3c_stop_tx(struct uart_port *port)
+{
+}
+
+static void ttyi3c_stop_rx(struct uart_port *port)
+{
+}
+
+static void ttyi3c_break_ctl(struct uart_port *port, int break_state)
+{
+}
+
+static int ttyi3c_startup(struct uart_port *port)
+{
+	return 0;
+}
+
+static void ttyi3c_shutdown(struct uart_port *port)
+{
+
+}
+
+static void ttyi3c_uart_pm(struct uart_port *port, unsigned int state, unsigned int oldstate)
+{
+
+}
+
+static void
+ttyi3c_set_termios(struct uart_port *port, struct ktermios *termios, const struct ktermios *old)
+{
+
+}
+
+static const char *ttyi3c_type(struct uart_port *port)
+{
+	return "I3CTTY";
+}
+
+static void ttyi3c_release_port(struct uart_port *port)
+{
+
+}
+
+static int ttyi3c_request_port(struct uart_port *port)
+{
+	return 0;
+}
+
+static void ttyi3c_config_port(struct uart_port *port, int flags)
+{
+
+}
+
+static int ttyi3c_verify_port(struct uart_port *port, struct serial_struct *ser)
+{
+	return 0;
+}
+
+static void ttyi3c_flush_buffer(struct uart_port *port)
+{
+
+}
+
+static void ttyi3c_start_tx(struct uart_port *port)
+{
+	struct ttyi3c_port *sport = container_of(port, struct ttyi3c_port, port);
+
+	queue_work(sport->workqueue, &sport->work);
+}
+
+static void i3c_slave_tty_rx_complete(struct i3c_request *req)
+{
+	struct ttyi3c_port *port = req->context;
+
+	if (req->status == I3C_REQUEST_CANCEL) {
+		i3c_slave_ctrl_free_request(req);
+		return;
+	}
+
+	for (int i = 0; i < req->actual; i++) {
+		if (tty_insert_flip_char(&port->port.state->port, *(u8 *)(req->buf + i), 0) == 0)
+			port->port.icount.buf_overrun++;
+	}
+
+	tty_flip_buffer_push(&port->port.state->port);
+	req->actual = 0;
+	req->status = 0;
+	i3c_slave_ctrl_queue(req, GFP_KERNEL);
+}
+
+
+static void i3c_slave_tty_tx_complete(struct i3c_request *req)
+{
+	struct ttyi3c_port *port = req->context;
+	struct circ_buf *xmit = &port->port.state->xmit;
+
+	if (req->status == I3C_REQUEST_CANCEL) {
+		i3c_slave_ctrl_free_request(req);
+		return;
+	}
+
+	uart_xmit_advance(&port->port, req->actual);
+
+	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+		uart_write_wakeup(&port->port);
+
+
+	i3c_slave_ctrl_free_request(req);
+}
+
+static void i3c_slave_tty_i3c_work(struct work_struct *work)
+{
+	struct ttyi3c_port *sport = container_of(work, struct ttyi3c_port, work);
+	struct circ_buf *xmit = &sport->port.state->xmit;
+	int cnt = CIRC_CNT(xmit->head, xmit->tail, UART_XMIT_SIZE);
+	int actual;
+	int ret;
+
+	if (cnt == 0)
+		return;
+
+	if (cnt > 0) {
+		struct i3c_request *req = i3c_slave_ctrl_alloc_request(sport->i3cdev->ctrl,
+								       GFP_KERNEL);
+		if (!req)
+			return;
+
+		req->length = ((xmit->tail + cnt) > UART_XMIT_SIZE) ? UART_XMIT_SIZE - xmit->tail :
+								    cnt;
+		req->buf =  xmit->buf + xmit->tail;
+		req->complete = i3c_slave_tty_tx_complete;
+		req->context = sport;
+		req->tx = true;
+
+		if (i3c_slave_ctrl_queue(req, GFP_KERNEL))
+			return;
+
+		if ((xmit->tail + cnt) > UART_XMIT_SIZE) {
+			req = i3c_slave_ctrl_alloc_request(sport->i3cdev->ctrl, GFP_KERNEL);
+			if (!req)
+				return;
+			req->buf = xmit->buf;
+			req->length = xmit->tail + cnt - UART_XMIT_SIZE;
+			req->complete = i3c_slave_tty_tx_complete;
+
+			if (i3c_slave_ctrl_queue(req, GFP_KERNEL))
+				return;
+		}
+	}
+
+	i3c_slave_ctrl_raise_ibi(sport->i3cdev->ctrl, NULL, 0);
+}
+
+static const struct uart_ops ttyi3c_pops = {
+	.tx_empty       = ttyi3c_tx_empty,
+	.set_mctrl      = ttyi3c_set_mctrl,
+	.get_mctrl      = ttyi3c_get_mctrl,
+	.stop_tx        = ttyi3c_stop_tx,
+	.start_tx       = ttyi3c_start_tx,
+	.stop_rx        = ttyi3c_stop_rx,
+	.break_ctl      = ttyi3c_break_ctl,
+	.startup        = ttyi3c_startup,
+	.shutdown       = ttyi3c_shutdown,
+	.pm             = ttyi3c_uart_pm,
+	.set_termios    = ttyi3c_set_termios,
+	.type           = ttyi3c_type,
+	.request_port   = ttyi3c_request_port,
+	.release_port   = ttyi3c_release_port,
+	.config_port    = ttyi3c_config_port,
+	.verify_port    = ttyi3c_verify_port,
+	.flush_buffer   = ttyi3c_flush_buffer,
+#if defined(CONFIG_CONSOLE_POLL)
+	.poll_init      = ttyi3c_poll_init,
+	.poll_get_char  = ttyi3c_poll_get_char,
+	.poll_put_char  = ttyi3c_poll_put_char,
+#endif
+};
+
+static int i3c_slave_tty_bind(struct i3c_slave_func *func)
+{
+	const struct i3c_slave_ctrl_features *feature;
+	unsigned int rxfifo_size;
+	struct ttyi3c_port *port;
+	struct i3c_request *req;
+	int offset = 0;
+	int ret;
+
+	feature = i3c_slave_ctrl_get_features(func->ctrl);
+	if (!feature)
+		return -EINVAL;
+
+	rxfifo_size = feature->rx_fifo_sz;
+
+	if (!rxfifo_size)
+		rxfifo_size = 16;
+
+	port = dev_get_drvdata(&func->dev);
+
+	port->buffer = get_zeroed_page(GFP_KERNEL);
+	if (!port->buffer)
+		return -ENOMEM;
+
+	if (i3c_slave_ctrl_set_config(func->ctrl, func)) {
+		dev_err(&func->dev, "failure set i3c config\n");
+		return -EINVAL;
+	}
+
+	req = i3c_slave_ctrl_alloc_request(func->ctrl, GFP_KERNEL);
+	do {
+		req->buf = (void *) (port->buffer + offset);
+		req->length = rxfifo_size;
+		req->context = port;
+		req->complete = i3c_slave_tty_rx_complete;
+		offset += rxfifo_size;
+
+		if (i3c_slave_ctrl_queue(req, GFP_KERNEL))
+			break;
+	} while (req == NULL || offset >= PAGE_SIZE);
+
+	if (i3c_slave_ctrl_set_config(func->ctrl, func)) {
+		dev_err(&func->dev, "failure set i3c config\n");
+		return -EINVAL;
+	}
+
+	ret = uart_register_driver(&ttyi3c_reg);
+	if (ret)
+		return ret;
+
+	ret = uart_add_one_port(&ttyi3c_reg, &port->port);
+	if (ret)
+		goto err_one_port;
+
+	ret = i3c_slave_ctrl_enable(func->ctrl);
+	if (ret)
+		goto err_ctrl_enable;
+
+	return 0;
+
+err_ctrl_enable:
+	uart_remove_one_port(&ttyi3c_reg, &port->port);
+err_one_port:
+	uart_unregister_driver(&ttyi3c_reg);
+	dev_err(&func->dev, "bind failure\n");
+
+	return ret;
+}
+
+static void i3c_slave_tty_unbind(struct i3c_slave_func *func)
+{
+	struct ttyi3c_port *port;
+
+	port = dev_get_drvdata(&func->dev);
+
+	i3c_slave_ctrl_disable(func->ctrl);
+	i3c_slave_ctrl_cancel_all_reqs(func->ctrl, 0);
+	i3c_slave_ctrl_cancel_all_reqs(func->ctrl, 1);
+	uart_remove_one_port(&ttyi3c_reg, &port->port);
+	uart_unregister_driver(&ttyi3c_reg);
+
+	free_page(port->buffer);
+}
+
+static struct i3c_slave_func_ops i3c_tty_ops = {
+	.bind   = i3c_slave_tty_bind,
+	.unbind = i3c_slave_tty_unbind,
+};
+
+static int i3c_tty_probe(struct i3c_slave_func *func)
+{
+	struct device *dev = &func->dev;
+	struct ttyi3c_port *port;
+
+	port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
+	if (!port)
+		return -ENOMEM;
+
+	port->i3cdev = func;
+	port->port.dev = &func->dev;
+	port->port.ops = &ttyi3c_pops;
+	port->port.type = PORT_I3C;
+
+	dev_set_drvdata(&func->dev, port);
+
+	port->workqueue = alloc_workqueue("%s", 0, 0, dev_name(&func->dev));
+	if (!port->workqueue)
+		return -ENOMEM;
+
+	INIT_WORK(&port->work, i3c_slave_tty_i3c_work);
+
+	return 0;
+}
+
+static void  i3c_tty_remove(struct i3c_slave_func *func)
+{
+	struct ttyi3c_port *port;
+
+	port = dev_get_drvdata(&func->dev);
+
+	destroy_workqueue(port->workqueue);
+}
+
+DECLARE_I3C_SLAVE_INIT(tty, i3c_tty_probe, i3c_tty_remove, &i3c_tty_ops);