Messages in this thread Patch in this message | | | From | Frank Li <> | Subject | [RFC PATCH 7/9] i3c: slave: func: add tty driver | Date | Tue, 5 Sep 2023 17:38:40 -0400 |
| |
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 --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); -- 2.34.1
| |