Messages in this thread Patch in this message | | | From | Frank Li <> | Subject | [RFC PATCH 8/9] tty: serial: add tty over I3C master side driver | Date | Tue, 5 Sep 2023 17:38:41 -0400 |
| |
This is master side tty driver for tty over i3c.
Signed-off-by: Frank Li <Frank.Li@nxp.com> --- drivers/tty/serial/Kconfig | 6 + drivers/tty/serial/Makefile | 1 + drivers/tty/serial/ttyi3c.c | 276 ++++++++++++++++++++++++++++++++++++ 3 files changed, 283 insertions(+) create mode 100644 drivers/tty/serial/ttyi3c.c
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index bdc568a4ab669..2ceafe94cd1dc 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1578,6 +1578,12 @@ config SERIAL_NUVOTON_MA35D1_CONSOLE but you can alter that using a kernel command line option such as "console=ttyNVTx". +config SERIAL_I3C + tristate "tty over i3c" + depends on I3C + help + Select this options if you'd like use UART over I3C master controller + endmenu config SERIAL_MCTRL_GPIO diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 138abbc897381..60c101809cdcf 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -88,6 +88,7 @@ obj-$(CONFIG_SERIAL_MILBEAUT_USIO) += milbeaut_usio.o obj-$(CONFIG_SERIAL_SIFIVE) += sifive.o obj-$(CONFIG_SERIAL_LITEUART) += liteuart.o obj-$(CONFIG_SERIAL_SUNPLUS) += sunplus-uart.o +obj-$(CONFIG_SERIAL_I3C) += ttyi3c.o # GPIOLIB helpers for modem control lines obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o diff --git a/drivers/tty/serial/ttyi3c.c b/drivers/tty/serial/ttyi3c.c new file mode 100644 index 0000000000000..e2912c8bac87d --- /dev/null +++ b/drivers/tty/serial/ttyi3c.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2023 NXP. + * + * Author: Frank Li <Frank.Li@nxp.com> + */ + +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/i3c/device.h> +#include <linux/i3c/master.h> +#include <linux/slab.h> +#include <linux/console.h> +#include <linux/serial_core.h> +#include <linux/dmaengine.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/tty_flip.h> + +#define DEVICE_NAME "ttyI3C" +#define PORT_I3C 124 + +struct ttyi3c_port { + struct uart_port port; + unsigned int txfifo_size; + unsigned int rxfifo_size; + void *buffer; + struct i3c_device *i3cdev; + struct work_struct work; + struct workqueue_struct *workqueue; +}; + +static struct uart_driver ttyi3c_reg = { + .owner = THIS_MODULE, + .driver_name = "fsl_ttyi3c", + .dev_name = DEVICE_NAME, + .nr = 1, +}; + +static const struct i3c_device_id fsl_tty_i3c_ids[] = { + I3C_DEVICE(0x011B, 0x1000, NULL), + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(i3c, fsl_tty_i3c_ids); + +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 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, +}; + +static void +i3c_controller_irq_handler(struct i3c_device *dev, const struct i3c_ibi_payload *payload) +{ + + struct ttyi3c_port *sport = dev_get_drvdata(&dev->dev); + struct i3c_priv_xfer xfers; + int i = 0; + + memset(&xfers, 0, sizeof(xfers)); + + xfers.data.in = sport->buffer; + xfers.len = 16; + xfers.rnw = 1; + + i3c_device_do_priv_xfers(sport->i3cdev, &xfers, 1); + + for (i = 0; i < xfers.actual; i++) + if (tty_insert_flip_char(&sport->port.state->port, *(u8 *)(sport->buffer + i), 0) + == 0) + sport->port.icount.buf_overrun++; + + tty_flip_buffer_push(&sport->port.state->port); +} + +static void fsl_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); + struct i3c_priv_xfer xfers; + int actual; + int ret; + + if (cnt == 0) + return; + + if (cnt > 0) { + xfers.rnw = 0; + xfers.len = ((xmit->tail + cnt) > UART_XMIT_SIZE) ? UART_XMIT_SIZE - xmit->tail : + cnt; + xfers.data.out = xmit->buf + xmit->tail; + + ret = i3c_device_do_priv_xfers(sport->i3cdev, &xfers, 1); + + actual = ret ? xfers.actual : xfers.len; + + uart_xmit_advance(&sport->port, actual); + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(&sport->port); + + cnt -= actual; + } + + if (cnt) + queue_work(sport->workqueue, &sport->work); +} + +static int fsl_tty_i3c_probe(struct i3c_device *i3cdev) +{ + struct ttyi3c_port *port; + struct i3c_ibi_setup req; + int ret; + + port = devm_kzalloc(&i3cdev->dev, sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->i3cdev = i3cdev; + port->port.ops = &ttyi3c_pops; + port->port.type = PORT_I3C; + port->port.dev = &i3cdev->dev; + port->buffer = devm_kzalloc(&i3cdev->dev, UART_XMIT_SIZE, GFP_KERNEL); + + req.max_payload_len = 8; + req.num_slots = 4; + req.handler = &i3c_controller_irq_handler; + + dev_set_drvdata(&i3cdev->dev, port); + + INIT_WORK(&port->work, fsl_tty_i3c_work); + port->workqueue = alloc_workqueue("%s", 0, 0, dev_name(&i3cdev->dev)); + if (!port->workqueue) + return -ENOMEM; + + ret = uart_register_driver(&ttyi3c_reg); + if (ret) + return ret; + + ret = uart_add_one_port(&ttyi3c_reg, &port->port); + if (ret) + goto err_add_one_port; + + ret = i3c_device_request_ibi(i3cdev, &req); + if (ret) + goto err_request_ibi; + + ret = i3c_device_enable_ibi(i3cdev); + if (ret) + goto err_enable_ibi; + + return 0; + +err_enable_ibi: + i3c_device_free_ibi(i3cdev); +err_request_ibi: + uart_remove_one_port(&ttyi3c_reg, &port->port); +err_add_one_port: + uart_unregister_driver(&ttyi3c_reg); + + return ret; +} + +static struct i3c_driver fsl_tty_i3c_driver = { + .driver = { + .name = "ttyi3c", + }, + .probe = fsl_tty_i3c_probe, + .id_table = fsl_tty_i3c_ids, +}; + +module_i3c_driver(fsl_tty_i3c_driver); +MODULE_LICENSE("GPL"); -- 2.34.1
| |