diff mbox series

[v2,1/1] tty: i3c: add TTY over I3C master support

Message ID 20231020160027.3663772-1-Frank.Li@nxp.com (mailing list archive)
State Changes Requested
Headers show
Series [v2,1/1] tty: i3c: add TTY over I3C master support | expand

Commit Message

Frank Li Oct. 20, 2023, 4 p.m. UTC
In typical embedded Linux systems, UART consoles require at least two pins,
TX and RX. In scenarios where I2C/I3C devices like sensors or PMICs are
present, we can save these two pins by using this driver. Pins is crucial
resources, especially in small chip packages.

This introduces support for using the I3C bus to transfer console tty data,
effectively replacing the need for dedicated UART pins. This not only
conserves valuable pin resources but also facilitates testing of I3C's
advanced features, including early termination, in-band interrupt (IBI)
support, and the creation of more complex data patterns. Additionally,
it aids in identifying and addressing issues within the I3C controller
driver.

Signed-off-by: Frank Li <Frank.Li@nxp.com>
---

Notes:
    This patch depend on
    https://lore.kernel.org/imx/20231018205929.3435110-1-Frank.Li@nxp.com/T/#t
    
    Change from v1 to v2
    - update commit message.
    - using goto for err handle
    - using one working queue for all tty-i3c device
    - fixed typo found by js
    - update kconfig help
    - using kfifo
    
    Some idea about i3c tty in future
    1. early console support
       - early console is useful to debug boot problem. It requires as simple
    as possible.
        i3c SDR mode, 9th bit is parity. I3C target can't block master send out
    data. so never block kernel boot even without any expected target connected.
    i3c master may send out a boardcast message as early console to every
    target devices. Only a "i3c-usb-acm" can show it to host's minicom.  And
    this "i3c-usb-acm" can plug in at any time. Write a boardcast message is
    quite simple at I3C controller. After kernel system boot to certain phase,
    switch to this driver.
    
    2. multi port supports. create multi virtual uart ports, so firmware, such
    as arm trust firmware can dump message to difference virtual ports.
    
    3. compatible with UART network adaptor, which defined at MIPI DEBUG for
    I3C spec.
    
    This driver is just first step.
    
    This patch depend on
    https://lore.kernel.org/imx/20231018205929.3435110-1-Frank.Li@nxp.com/T/#t

 drivers/tty/Kconfig   |  13 ++
 drivers/tty/Makefile  |   1 +
 drivers/tty/i3c_tty.c | 443 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 457 insertions(+)
 create mode 100644 drivers/tty/i3c_tty.c

Comments

Greg KH Oct. 21, 2023, 5:02 p.m. UTC | #1
Note, your subject line needs to change.

On Fri, Oct 20, 2023 at 12:00:27PM -0400, Frank Li wrote:
> In typical embedded Linux systems, UART consoles require at least two pins,
> TX and RX. In scenarios where I2C/I3C devices like sensors or PMICs are
> present, we can save these two pins by using this driver. Pins is crucial

"Pins are crucial"

> resources, especially in small chip packages.
> 
> This introduces support for using the I3C bus to transfer console tty data,
> effectively replacing the need for dedicated UART pins. This not only
> conserves valuable pin resources but also facilitates testing of I3C's
> advanced features, including early termination, in-band interrupt (IBI)
> support, and the creation of more complex data patterns. Additionally,
> it aids in identifying and addressing issues within the I3C controller
> driver.

But where is the serial data ending up at?  Not a normal uart, what is
on the other end?  And do line settings mean anything here?

> 
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
> ---
> 
> Notes:
>     This patch depend on
>     https://lore.kernel.org/imx/20231018205929.3435110-1-Frank.Li@nxp.com/T/#t

Let's wait for those to be accepted first, right?

> +static DEFINE_IDR(i3c_tty_minors);
> +static DEFINE_MUTEX(i3c_tty_minors_lock);

I thought idr didn't need a mutex anymore, are you sure this is still
needed?

> +static struct tty_driver *i3c_tty_driver;
> +
> +#define I3C_TTY_MINORS		256

Do you really need 256 minors?

> +#define I3C_TTY_TRANS_SIZE	16
> +#define I3C_TTY_RX_STOP		0
> +#define I3C_TTY_RETRY		20
> +#define I3C_TTY_YIELD_US	100
> +
> +struct ttyi3c_port {
> +	struct tty_port port;
> +	int minor;
> +	spinlock_t xlock; /* protect xmit */
> +	char tx_buff[I3C_TTY_TRANS_SIZE];
> +	char rx_buff[I3C_TTY_TRANS_SIZE];
> +	struct i3c_device *i3cdev;
> +	struct work_struct txwork;
> +	struct work_struct rxwork;
> +	struct completion txcomplete;
> +	unsigned long status;
> +	int buf_overrun;

You set buf_overrun but never do anything with it.  Why care about it?

Other than these minor things, looks sane, nice work.

thanks,

greg k-h
Frank Li Oct. 23, 2023, 4:26 p.m. UTC | #2
On Sat, Oct 21, 2023 at 07:02:40PM +0200, Greg KH wrote:
> Note, your subject line needs to change.
> 
> On Fri, Oct 20, 2023 at 12:00:27PM -0400, Frank Li wrote:
> > In typical embedded Linux systems, UART consoles require at least two pins,
> > TX and RX. In scenarios where I2C/I3C devices like sensors or PMICs are
> > present, we can save these two pins by using this driver. Pins is crucial
> 
> "Pins are crucial"
> 
> > resources, especially in small chip packages.
> > 
> > This introduces support for using the I3C bus to transfer console tty data,
> > effectively replacing the need for dedicated UART pins. This not only
> > conserves valuable pin resources but also facilitates testing of I3C's
> > advanced features, including early termination, in-band interrupt (IBI)
> > support, and the creation of more complex data patterns. Additionally,
> > it aids in identifying and addressing issues within the I3C controller
> > driver.
> 
> But where is the serial data ending up at?  Not a normal uart, what is
> on the other end?  And do line settings mean anything here?

Currently, it use slave i3c code. 
https://lore.kernel.org/imx/20231018215809.3477437-1-Frank.Li@nxp.com/T/#t

idealy build an i3c->usb dongle to bride it to usb acm. 

> 
> > 
> > Signed-off-by: Frank Li <Frank.Li@nxp.com>
> > ---
> > 
> > Notes:
> >     This patch depend on
> >     https://lore.kernel.org/imx/20231018205929.3435110-1-Frank.Li@nxp.com/T/#t
> 
> Let's wait for those to be accepted first, right?

Yes.

> 
> > +static DEFINE_IDR(i3c_tty_minors);
> > +static DEFINE_MUTEX(i3c_tty_minors_lock);
> 
> I thought idr didn't need a mutex anymore, are you sure this is still
> needed?
> 
> > +static struct tty_driver *i3c_tty_driver;
> > +
> > +#define I3C_TTY_MINORS		256
> 
> Do you really need 256 minors?

Any resource concern about it. Maybe 32/64 is enough. I refer from USB tty
driver.

> 
> > +#define I3C_TTY_TRANS_SIZE	16
> > +#define I3C_TTY_RX_STOP		0
> > +#define I3C_TTY_RETRY		20
> > +#define I3C_TTY_YIELD_US	100
> > +
> > +struct ttyi3c_port {
> > +	struct tty_port port;
> > +	int minor;
> > +	spinlock_t xlock; /* protect xmit */
> > +	char tx_buff[I3C_TTY_TRANS_SIZE];
> > +	char rx_buff[I3C_TTY_TRANS_SIZE];
> > +	struct i3c_device *i3cdev;
> > +	struct work_struct txwork;
> > +	struct work_struct rxwork;
> > +	struct completion txcomplete;
> > +	unsigned long status;
> > +	int buf_overrun;
> 
> You set buf_overrun but never do anything with it.  Why care about it?
> 
> Other than these minor things, looks sane, nice work.
> 
> thanks,
> 
> greg k-h
Greg KH Oct. 24, 2023, 9:30 a.m. UTC | #3
On Mon, Oct 23, 2023 at 12:26:42PM -0400, Frank Li wrote:
> On Sat, Oct 21, 2023 at 07:02:40PM +0200, Greg KH wrote:
> > Note, your subject line needs to change.
> > 
> > On Fri, Oct 20, 2023 at 12:00:27PM -0400, Frank Li wrote:
> > > In typical embedded Linux systems, UART consoles require at least two pins,
> > > TX and RX. In scenarios where I2C/I3C devices like sensors or PMICs are
> > > present, we can save these two pins by using this driver. Pins is crucial
> > 
> > "Pins are crucial"
> > 
> > > resources, especially in small chip packages.
> > > 
> > > This introduces support for using the I3C bus to transfer console tty data,
> > > effectively replacing the need for dedicated UART pins. This not only
> > > conserves valuable pin resources but also facilitates testing of I3C's
> > > advanced features, including early termination, in-band interrupt (IBI)
> > > support, and the creation of more complex data patterns. Additionally,
> > > it aids in identifying and addressing issues within the I3C controller
> > > driver.
> > 
> > But where is the serial data ending up at?  Not a normal uart, what is
> > on the other end?  And do line settings mean anything here?
> 
> Currently, it use slave i3c code. 
> https://lore.kernel.org/imx/20231018215809.3477437-1-Frank.Li@nxp.com/T/#t
> 
> idealy build an i3c->usb dongle to bride it to usb acm. 

So no one has built such a thing yet to determine if any of this works?

> > > +static DEFINE_IDR(i3c_tty_minors);
> > > +static DEFINE_MUTEX(i3c_tty_minors_lock);
> > 
> > I thought idr didn't need a mutex anymore, are you sure this is still
> > needed?
> > 
> > > +static struct tty_driver *i3c_tty_driver;
> > > +
> > > +#define I3C_TTY_MINORS		256
> > 
> > Do you really need 256 minors?
> 
> Any resource concern about it. Maybe 32/64 is enough. I refer from USB tty
> driver.

USB serial devices are quite common, and in some places, replaced PCI
serial cards for modem connections.  So for them, we do actually use all
256 minors.

But for this, it's a debugging device, how are you going to have so many
different debugging ports on a system at once?

How about making it small, like 8, and see if you ever actually exceed
that in real life?

thanks,

greg k-h
Frank Li Oct. 24, 2023, 2:31 p.m. UTC | #4
On Tue, Oct 24, 2023 at 11:30:33AM +0200, Greg KH wrote:
> On Mon, Oct 23, 2023 at 12:26:42PM -0400, Frank Li wrote:
> > On Sat, Oct 21, 2023 at 07:02:40PM +0200, Greg KH wrote:
> > > Note, your subject line needs to change.
> > > 
> > > On Fri, Oct 20, 2023 at 12:00:27PM -0400, Frank Li wrote:
> > > > In typical embedded Linux systems, UART consoles require at least two pins,
> > > > TX and RX. In scenarios where I2C/I3C devices like sensors or PMICs are
> > > > present, we can save these two pins by using this driver. Pins is crucial
> > > 
> > > "Pins are crucial"
> > > 
> > > > resources, especially in small chip packages.
> > > > 
> > > > This introduces support for using the I3C bus to transfer console tty data,
> > > > effectively replacing the need for dedicated UART pins. This not only
> > > > conserves valuable pin resources but also facilitates testing of I3C's
> > > > advanced features, including early termination, in-band interrupt (IBI)
> > > > support, and the creation of more complex data patterns. Additionally,
> > > > it aids in identifying and addressing issues within the I3C controller
> > > > driver.
> > > 
> > > But where is the serial data ending up at?  Not a normal uart, what is
> > > on the other end?  And do line settings mean anything here?
> > 
> > Currently, it use slave i3c code. 
> > https://lore.kernel.org/imx/20231018215809.3477437-1-Frank.Li@nxp.com/T/#t
> > 
> > idealy build an i3c->usb dongle to bride it to usb acm. 
> 
> So no one has built such a thing yet to determine if any of this works?

It is easy to proof concept by I3C slave code and USB gadget ACM, then pipe
two tty (ttyACM0 and ttySI3C0 together).

Of we also can implement a USB to I3C class standard, base on this, reuse
this tty driver at host side.

Frank

> 
> > > > +static DEFINE_IDR(i3c_tty_minors);
> > > > +static DEFINE_MUTEX(i3c_tty_minors_lock);
> > > 
> > > I thought idr didn't need a mutex anymore, are you sure this is still
> > > needed?
> > > 
> > > > +static struct tty_driver *i3c_tty_driver;
> > > > +
> > > > +#define I3C_TTY_MINORS		256
> > > 
> > > Do you really need 256 minors?
> > 
> > Any resource concern about it. Maybe 32/64 is enough. I refer from USB tty
> > driver.
> 
> USB serial devices are quite common, and in some places, replaced PCI
> serial cards for modem connections.  So for them, we do actually use all
> 256 minors.
> 
> But for this, it's a debugging device, how are you going to have so many
> different debugging ports on a system at once?
> 
> How about making it small, like 8, and see if you ever actually exceed
> that in real life?
> 
> thanks,
> 
> greg k-h
Greg KH Oct. 24, 2023, 3:05 p.m. UTC | #5
On Tue, Oct 24, 2023 at 10:31:51AM -0400, Frank Li wrote:
> On Tue, Oct 24, 2023 at 11:30:33AM +0200, Greg KH wrote:
> > On Mon, Oct 23, 2023 at 12:26:42PM -0400, Frank Li wrote:
> > > On Sat, Oct 21, 2023 at 07:02:40PM +0200, Greg KH wrote:
> > > > Note, your subject line needs to change.
> > > > 
> > > > On Fri, Oct 20, 2023 at 12:00:27PM -0400, Frank Li wrote:
> > > > > In typical embedded Linux systems, UART consoles require at least two pins,
> > > > > TX and RX. In scenarios where I2C/I3C devices like sensors or PMICs are
> > > > > present, we can save these two pins by using this driver. Pins is crucial
> > > > 
> > > > "Pins are crucial"
> > > > 
> > > > > resources, especially in small chip packages.
> > > > > 
> > > > > This introduces support for using the I3C bus to transfer console tty data,
> > > > > effectively replacing the need for dedicated UART pins. This not only
> > > > > conserves valuable pin resources but also facilitates testing of I3C's
> > > > > advanced features, including early termination, in-band interrupt (IBI)
> > > > > support, and the creation of more complex data patterns. Additionally,
> > > > > it aids in identifying and addressing issues within the I3C controller
> > > > > driver.
> > > > 
> > > > But where is the serial data ending up at?  Not a normal uart, what is
> > > > on the other end?  And do line settings mean anything here?
> > > 
> > > Currently, it use slave i3c code. 
> > > https://lore.kernel.org/imx/20231018215809.3477437-1-Frank.Li@nxp.com/T/#t
> > > 
> > > idealy build an i3c->usb dongle to bride it to usb acm. 
> > 
> > So no one has built such a thing yet to determine if any of this works?
> 
> It is easy to proof concept by I3C slave code and USB gadget ACM, then pipe
> two tty (ttyACM0 and ttySI3C0 together).

So you have not actually tested this?  why write a driver that no one is
using?

> Of we also can implement a USB to I3C class standard, base on this, reuse
> this tty driver at host side.

Is there a USB I3C standard?  I see i3c descriptors assigned by the
USB-IF, but haven't dug to see if there's more than that anywhere...

thanks,

greg k-h
Frank Li Oct. 24, 2023, 3:59 p.m. UTC | #6
On Tue, Oct 24, 2023 at 05:05:47PM +0200, Greg KH wrote:
> On Tue, Oct 24, 2023 at 10:31:51AM -0400, Frank Li wrote:
> > On Tue, Oct 24, 2023 at 11:30:33AM +0200, Greg KH wrote:
> > > On Mon, Oct 23, 2023 at 12:26:42PM -0400, Frank Li wrote:
> > > > On Sat, Oct 21, 2023 at 07:02:40PM +0200, Greg KH wrote:
> > > > > Note, your subject line needs to change.
> > > > > 
> > > > > On Fri, Oct 20, 2023 at 12:00:27PM -0400, Frank Li wrote:
> > > > > > In typical embedded Linux systems, UART consoles require at least two pins,
> > > > > > TX and RX. In scenarios where I2C/I3C devices like sensors or PMICs are
> > > > > > present, we can save these two pins by using this driver. Pins is crucial
> > > > > 
> > > > > "Pins are crucial"
> > > > > 
> > > > > > resources, especially in small chip packages.
> > > > > > 
> > > > > > This introduces support for using the I3C bus to transfer console tty data,
> > > > > > effectively replacing the need for dedicated UART pins. This not only
> > > > > > conserves valuable pin resources but also facilitates testing of I3C's
> > > > > > advanced features, including early termination, in-band interrupt (IBI)
> > > > > > support, and the creation of more complex data patterns. Additionally,
> > > > > > it aids in identifying and addressing issues within the I3C controller
> > > > > > driver.
> > > > > 
> > > > > But where is the serial data ending up at?  Not a normal uart, what is
> > > > > on the other end?  And do line settings mean anything here?
> > > > 
> > > > Currently, it use slave i3c code. 
> > > > https://lore.kernel.org/imx/20231018215809.3477437-1-Frank.Li@nxp.com/T/#t
> > > > 
> > > > idealy build an i3c->usb dongle to bride it to usb acm. 
> > > 
> > > So no one has built such a thing yet to determine if any of this works?
> > 
> > It is easy to proof concept by I3C slave code and USB gadget ACM, then pipe
> > two tty (ttyACM0 and ttySI3C0 together).
> 
> So you have not actually tested this?  why write a driver that no one is
> using?

I3c slave side tty at
https://lore.kernel.org/imx/20231018215809.3477437-1-Frank.Li@nxp.com/T/#t

I just have not tested tty to USB part. This patch is major for tty -> i3c
master -> i3c taret -> tty now.

As my previous said, two major purpose now. 
1. Save two pads.
2. Test i3c master and i3c target driver. I3C target driver frame was
written by me and posted. It needs a driver to verify it works. In stead of
write a test driver (such as i2c slave, pci-endpoint), it is better to
write an actual function driver, such as tty. It can exchange message
between two boards, which connected by SDA/SCL.

Frank
> 
> > Of we also can implement a USB to I3C class standard, base on this, reuse
> > this tty driver at host side.
> 
> Is there a USB I3C standard?  I see i3c descriptors assigned by the
> USB-IF, but haven't dug to see if there's more than that anywhere...
> 
> thanks,
> 
> greg k-h
Frank Li Oct. 24, 2023, 4:04 p.m. UTC | #7
On Tue, Oct 24, 2023 at 11:59:53AM -0400, Frank Li wrote:
> On Tue, Oct 24, 2023 at 05:05:47PM +0200, Greg KH wrote:
> > On Tue, Oct 24, 2023 at 10:31:51AM -0400, Frank Li wrote:
> > > On Tue, Oct 24, 2023 at 11:30:33AM +0200, Greg KH wrote:
> > > > On Mon, Oct 23, 2023 at 12:26:42PM -0400, Frank Li wrote:
> > > > > On Sat, Oct 21, 2023 at 07:02:40PM +0200, Greg KH wrote:
> > > > > > Note, your subject line needs to change.
> > > > > > 
> > > > > > On Fri, Oct 20, 2023 at 12:00:27PM -0400, Frank Li wrote:
> > > > > > > In typical embedded Linux systems, UART consoles require at least two pins,
> > > > > > > TX and RX. In scenarios where I2C/I3C devices like sensors or PMICs are
> > > > > > > present, we can save these two pins by using this driver. Pins is crucial
> > > > > > 
> > > > > > "Pins are crucial"
> > > > > > 
> > > > > > > resources, especially in small chip packages.
> > > > > > > 
> > > > > > > This introduces support for using the I3C bus to transfer console tty data,
> > > > > > > effectively replacing the need for dedicated UART pins. This not only
> > > > > > > conserves valuable pin resources but also facilitates testing of I3C's
> > > > > > > advanced features, including early termination, in-band interrupt (IBI)
> > > > > > > support, and the creation of more complex data patterns. Additionally,
> > > > > > > it aids in identifying and addressing issues within the I3C controller
> > > > > > > driver.
> > > > > > 
> > > > > > But where is the serial data ending up at?  Not a normal uart, what is
> > > > > > on the other end?  And do line settings mean anything here?
> > > > > 
> > > > > Currently, it use slave i3c code. 
> > > > > https://lore.kernel.org/imx/20231018215809.3477437-1-Frank.Li@nxp.com/T/#t
> > > > > 
> > > > > idealy build an i3c->usb dongle to bride it to usb acm. 
> > > > 
> > > > So no one has built such a thing yet to determine if any of this works?
> > > 
> > > It is easy to proof concept by I3C slave code and USB gadget ACM, then pipe
> > > two tty (ttyACM0 and ttySI3C0 together).
> > 
> > So you have not actually tested this?  why write a driver that no one is
> > using?
> 
> I3c slave side tty at
> https://lore.kernel.org/imx/20231018215809.3477437-1-Frank.Li@nxp.com/T/#t
> 
> I just have not tested tty to USB part. This patch is major for tty -> i3c
> master -> i3c taret -> tty now.
> 
> As my previous said, two major purpose now. 
> 1. Save two pads.
> 2. Test i3c master and i3c target driver. I3C target driver frame was
> written by me and posted. It needs a driver to verify it works. In stead of
> write a test driver (such as i2c slave, pci-endpoint), it is better to
> write an actual function driver, such as tty. It can exchange message
> between two boards, which connected by SDA/SCL.
> 
> Frank
> > 
> > > Of we also can implement a USB to I3C class standard, base on this, reuse
> > > this tty driver at host side.
> > 
> > Is there a USB I3C standard?  I see i3c descriptors assigned by the
> > USB-IF, but haven't dug to see if there's more than that anywhere...

Sorry, forget answer this quesiton. Yes, it is standard class. Spec is in
usb.org. I have not found actual usage mode yet. 

> > 
> > thanks,
> > 
> > greg k-h
diff mbox series

Patch

diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
index 5646dc6242cd9..b13645f2d72bc 100644
--- a/drivers/tty/Kconfig
+++ b/drivers/tty/Kconfig
@@ -412,6 +412,19 @@  config RPMSG_TTY
 	  To compile this driver as a module, choose M here: the module will be
 	  called rpmsg_tty.
 
+config I3C_TTY
+	tristate "TTY over I3C"
+	depends on I3C
+	help
+	  Select this options if you'd like use TTY over I3C master controller
+
+	  This makes it possible for user-space programs to send and receive
+	  data as a standard tty protocol. I3C provide relatively higher data
+	  transfer rate and less pin numbers, SDA/SCL are shared with other
+	  devices.
+
+	  If unsure, say N
+
 endif # TTY
 
 source "drivers/tty/serdev/Kconfig"
diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile
index 07aca5184a55d..f329f9c7d308a 100644
--- a/drivers/tty/Makefile
+++ b/drivers/tty/Makefile
@@ -27,5 +27,6 @@  obj-$(CONFIG_GOLDFISH_TTY)	+= goldfish.o
 obj-$(CONFIG_MIPS_EJTAG_FDC_TTY) += mips_ejtag_fdc.o
 obj-$(CONFIG_VCC)		+= vcc.o
 obj-$(CONFIG_RPMSG_TTY)		+= rpmsg_tty.o
+obj-$(CONFIG_I3C_TTY)		+= i3c_tty.o
 
 obj-y += ipwireless/
diff --git a/drivers/tty/i3c_tty.c b/drivers/tty/i3c_tty.c
new file mode 100644
index 0000000000000..1497759cddb76
--- /dev/null
+++ b/drivers/tty/i3c_tty.c
@@ -0,0 +1,443 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2023 NXP.
+ *
+ * Author: Frank Li <Frank.Li@nxp.com>
+ */
+
+#include <linux/delay.h>
+#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/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/tty_flip.h>
+
+static DEFINE_IDR(i3c_tty_minors);
+static DEFINE_MUTEX(i3c_tty_minors_lock);
+
+static struct tty_driver *i3c_tty_driver;
+
+#define I3C_TTY_MINORS		256
+#define I3C_TTY_TRANS_SIZE	16
+#define I3C_TTY_RX_STOP		0
+#define I3C_TTY_RETRY		20
+#define I3C_TTY_YIELD_US	100
+
+struct ttyi3c_port {
+	struct tty_port port;
+	int minor;
+	spinlock_t xlock; /* protect xmit */
+	char tx_buff[I3C_TTY_TRANS_SIZE];
+	char rx_buff[I3C_TTY_TRANS_SIZE];
+	struct i3c_device *i3cdev;
+	struct work_struct txwork;
+	struct work_struct rxwork;
+	struct completion txcomplete;
+	unsigned long status;
+	int buf_overrun;
+};
+
+struct workqueue_struct *workqueue;
+
+static const struct i3c_device_id i3c_ids[] = {
+	I3C_DEVICE(0x011B, 0x1000, NULL),
+	{ /* sentinel */ },
+};
+
+static int i3c_port_activate(struct tty_port *port, struct tty_struct *tty)
+{
+	struct ttyi3c_port *sport = container_of(port, struct ttyi3c_port, port);
+	int ret;
+
+	ret = tty_port_alloc_xmit_buf(port);
+	if (ret < 0)
+		return ret;
+
+	sport->status = 0;
+
+	ret = i3c_device_enable_ibi(sport->i3cdev);
+	if (ret) {
+		tty_port_free_xmit_buf(port);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void i3c_port_shutdown(struct tty_port *port)
+{
+	struct ttyi3c_port *sport =
+		container_of(port, struct ttyi3c_port, port);
+
+	i3c_device_disable_ibi(sport->i3cdev);
+	tty_port_free_xmit_buf(port);
+	kfree(sport->tx_buff);
+	kfree(sport->rx_buff);
+}
+
+static void i3c_port_destruct(struct tty_port *port)
+{
+	struct ttyi3c_port *sport =
+		container_of(port, struct ttyi3c_port, port);
+
+	mutex_lock(&i3c_tty_minors_lock);
+	idr_remove(&i3c_tty_minors, sport->minor);
+	mutex_unlock(&i3c_tty_minors_lock);
+}
+
+static const struct tty_port_operations i3c_port_ops = {
+	.shutdown = i3c_port_shutdown,
+	.activate = i3c_port_activate,
+	.destruct = i3c_port_destruct,
+};
+
+static ssize_t i3c_write(struct tty_struct *tty, const unsigned char *buf, size_t count)
+{
+	struct ttyi3c_port *sport = tty->driver_data;
+	unsigned long flags;
+	bool is_empty;
+	int ret;
+
+	spin_lock_irqsave(&sport->xlock, flags);
+	ret = kfifo_in(&sport->port.xmit_fifo, buf, count);
+	is_empty = kfifo_is_empty(&sport->port.xmit_fifo);
+	spin_unlock_irqrestore(&sport->xlock, flags);
+
+	if (!is_empty)
+		queue_work(workqueue, &sport->txwork);
+
+	return ret;
+}
+
+static int i3c_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	struct ttyi3c_port *sport = tty->driver_data;
+	unsigned long flags;
+	int ret = 0;
+
+	spin_lock_irqsave(&sport->xlock, flags);
+	ret = kfifo_put(&sport->port.xmit_fifo, ch);
+	spin_unlock_irqrestore(&sport->xlock, flags);
+
+	return ret;
+}
+
+static void i3c_flush_chars(struct tty_struct *tty)
+{
+	struct ttyi3c_port *sport = tty->driver_data;
+
+	queue_work(workqueue, &sport->txwork);
+}
+
+static unsigned int i3c_write_room(struct tty_struct *tty)
+{
+	struct ttyi3c_port *sport = tty->driver_data;
+
+	return kfifo_avail(&sport->port.xmit_fifo);
+}
+
+static void i3c_throttle(struct tty_struct *tty)
+{
+	struct ttyi3c_port *sport = tty->driver_data;
+
+	clear_bit(I3C_TTY_RX_STOP, &sport->status);
+}
+
+static void i3c_unthrottle(struct tty_struct *tty)
+{
+	struct ttyi3c_port *sport = tty->driver_data;
+
+	set_bit(I3C_TTY_RX_STOP, &sport->status);
+
+	queue_work(workqueue, &sport->rxwork);
+}
+
+static int i3c_open(struct tty_struct *tty, struct file *filp)
+{
+	struct ttyi3c_port *sport = container_of(tty->port, struct ttyi3c_port, port);
+
+	tty->driver_data = sport;
+
+	return tty_port_open(&sport->port, tty, filp);
+}
+
+static void i3c_close(struct tty_struct *tty, struct file *filp)
+{
+	tty_port_close(tty->port, tty, filp);
+}
+
+static void i3c_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+	struct ttyi3c_port *sport = tty->driver_data;
+
+	wait_for_completion_timeout(&sport->txcomplete, timeout);
+	reinit_completion(&sport->txcomplete);
+}
+
+static const struct tty_operations i3c_tty_ops = {
+	.open = i3c_open,
+	.close = i3c_close,
+	.write = i3c_write,
+	.put_char = i3c_put_char,
+	.flush_chars = i3c_flush_chars,
+	.write_room = i3c_write_room,
+	.throttle = i3c_throttle,
+	.unthrottle = i3c_unthrottle,
+	.wait_until_sent = i3c_wait_until_sent,
+};
+
+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);
+
+	/* i3c_unthrottle also queue the work to fetch pending data in target side */
+	queue_work(workqueue, &sport->rxwork);
+}
+
+static void tty_i3c_rxwork(struct work_struct *work)
+{
+	struct ttyi3c_port *sport = container_of(work, struct ttyi3c_port, rxwork);
+	struct i3c_priv_xfer xfers;
+	int retry = I3C_TTY_RETRY;
+	u16 status = BIT(0);
+	int ret;
+
+	memset(&xfers, 0, sizeof(xfers));
+	xfers.data.in = sport->rx_buff;
+	xfers.len = I3C_TTY_TRANS_SIZE;
+	xfers.rnw = 1;
+
+	do {
+		if (test_bit(I3C_TTY_RX_STOP, &sport->status))
+			break;
+
+		i3c_device_do_priv_xfers(sport->i3cdev, &xfers, 1);
+
+		if (xfers.actual_len) {
+			ret = tty_insert_flip_string(&sport->port, sport->rx_buff,
+						     xfers.actual_len);
+			if (ret < xfers.actual_len)
+				sport->buf_overrun++;
+
+			retry = I3C_TTY_RETRY;
+			continue;
+		}
+
+		status = BIT(0);
+		i3c_device_getstatus_format1(sport->i3cdev, &status);
+		/*
+		 * Target side needs some time to fill data into fifo. Target side may not
+		 * have hardware update status in real time. Software update status always
+		 * needs some delays.
+		 *
+		 * Generally, target side have circular buffer in memory, it will be moved
+		 * into FIFO by CPU or DMA. 'status' just show if circular buffer empty. But
+		 * there are gap, especially CPU have not response irq to fill FIFO in time.
+		 * So xfers.actual will be zero, wait for little time to avoid flood
+		 * transfer in i3c bus.
+		 */
+		usleep_range(I3C_TTY_YIELD_US, 10 * I3C_TTY_YIELD_US);
+		retry--;
+
+	} while (retry && (status & BIT(0)));
+
+	tty_flip_buffer_push(&sport->port);
+}
+
+static void tty_i3c_txwork(struct work_struct *work)
+{
+	struct ttyi3c_port *sport = container_of(work, struct ttyi3c_port, txwork);
+	struct i3c_priv_xfer xfers;
+	int retry = I3C_TTY_RETRY;
+	unsigned long flags;
+	int ret;
+
+	xfers.rnw = 0;
+	xfers.data.out = sport->tx_buff;
+
+	while (!kfifo_is_empty(&sport->port.xmit_fifo) && retry) {
+		xfers.len = kfifo_len(&sport->port.xmit_fifo);
+		xfers.len = min_t(u16, I3C_TTY_TRANS_SIZE, xfers.len);
+
+		xfers.len = kfifo_out_peek(&sport->port.xmit_fifo, sport->tx_buff, xfers.len);
+
+		ret = i3c_device_do_priv_xfers(sport->i3cdev, &xfers, 1);
+		if (ret) {
+			/*
+			 * Target side may not move data out of FIFO. delay can't resolve problem,
+			 * just reduce some possiblity. Target can't end I3C SDR mode write
+			 * transfer, discard data is reasonable when FIFO overrun.
+			 */
+			usleep_range(I3C_TTY_YIELD_US, 10 * I3C_TTY_YIELD_US);
+			retry--;
+		} else {
+			retry = I3C_TTY_RETRY;
+			ret = kfifo_out(&sport->port.xmit_fifo, sport->tx_buff, xfers.len);
+		}
+	}
+
+	spin_lock_irqsave(&sport->xlock, flags);
+	if (kfifo_is_empty(&sport->port.xmit_fifo))
+		complete(&sport->txcomplete);
+	spin_unlock_irqrestore(&sport->xlock, flags);
+}
+
+static int i3c_probe(struct i3c_device *i3cdev)
+{
+	struct ttyi3c_port *sport;
+	struct device *tty_dev;
+	struct i3c_ibi_setup req;
+	int minor;
+	int ret;
+
+	sport = devm_kzalloc(&i3cdev->dev, sizeof(*sport), GFP_KERNEL);
+	if (!sport)
+		return -ENOMEM;
+
+	sport->i3cdev = i3cdev;
+
+	dev_set_drvdata(&i3cdev->dev, sport);
+
+	req.max_payload_len = 8;
+	req.num_slots = 4;
+	req.handler = &i3c_controller_irq_handler;
+
+	ret = i3c_device_request_ibi(i3cdev, &req);
+	if (ret)
+		return -EINVAL;
+
+	mutex_lock(&i3c_tty_minors_lock);
+	minor = idr_alloc(&i3c_tty_minors, sport, 0, I3C_TTY_MINORS, GFP_KERNEL);
+	mutex_unlock(&i3c_tty_minors_lock);
+
+	if (minor < 0) {
+		ret = -EINVAL;
+		goto err_idr_alloc;
+	}
+
+	spin_lock_init(&sport->xlock);
+	INIT_WORK(&sport->txwork, tty_i3c_txwork);
+	INIT_WORK(&sport->rxwork, tty_i3c_rxwork);
+	init_completion(&sport->txcomplete);
+
+	tty_port_init(&sport->port);
+	sport->port.ops = &i3c_port_ops;
+
+	tty_dev = tty_port_register_device(&sport->port, i3c_tty_driver, minor,
+					   &i3cdev->dev);
+	if (IS_ERR(tty_dev)) {
+		ret = PTR_ERR(tty_dev);
+		goto err_tty_port_register;
+	}
+
+	sport->minor = minor;
+
+	return 0;
+
+err_tty_port_register:
+	tty_port_put(&sport->port);
+
+	mutex_lock(&i3c_tty_minors_lock);
+	idr_remove(&i3c_tty_minors, minor);
+	mutex_unlock(&i3c_tty_minors_lock);
+
+err_idr_alloc:
+	i3c_device_free_ibi(i3cdev);
+
+	return ret;
+}
+
+void i3c_remove(struct i3c_device *dev)
+{
+	struct ttyi3c_port *sport = dev_get_drvdata(&dev->dev);
+
+	tty_port_unregister_device(&sport->port, i3c_tty_driver, sport->minor);
+	cancel_work_sync(&sport->txwork);
+
+	tty_port_put(&sport->port);
+
+	mutex_lock(&i3c_tty_minors_lock);
+	idr_remove(&i3c_tty_minors, sport->minor);
+	mutex_unlock(&i3c_tty_minors_lock);
+
+	i3c_device_free_ibi(sport->i3cdev);
+}
+
+static struct i3c_driver i3c_driver = {
+	.driver = {
+		.name = "ttyi3c",
+	},
+	.probe = i3c_probe,
+	.remove = i3c_remove,
+	.id_table = i3c_ids,
+};
+
+static int __init i3c_tty_init(void)
+{
+	int ret;
+
+	i3c_tty_driver = tty_alloc_driver(I3C_TTY_MINORS,
+					  TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV);
+
+	if (IS_ERR(i3c_tty_driver))
+		return PTR_ERR(i3c_tty_driver);
+
+	i3c_tty_driver->driver_name = "ttyI3C";
+	i3c_tty_driver->name = "ttyI3C";
+	i3c_tty_driver->minor_start = 0,
+	i3c_tty_driver->type = TTY_DRIVER_TYPE_SERIAL,
+	i3c_tty_driver->subtype = SERIAL_TYPE_NORMAL,
+	i3c_tty_driver->init_termios = tty_std_termios;
+	i3c_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL |
+					       CLOCAL;
+	i3c_tty_driver->init_termios.c_lflag = 0;
+
+	tty_set_operations(i3c_tty_driver, &i3c_tty_ops);
+
+	ret = tty_register_driver(i3c_tty_driver);
+	if (ret)
+		goto err_tty_register_driver;
+
+	ret = i3c_driver_register(&i3c_driver);
+	if (ret)
+		goto err_i3c_driver_register;
+
+	workqueue = alloc_workqueue("ttyI3C", 0, 0);
+	if (!workqueue) {
+		ret = PTR_ERR(workqueue);
+		goto err_alloc_workqueue;
+	}
+
+	return 0;
+
+err_alloc_workqueue:
+	i3c_driver_unregister(&i3c_driver);
+
+err_i3c_driver_register:
+	tty_unregister_driver(i3c_tty_driver);
+
+err_tty_register_driver:
+	tty_driver_kref_put(i3c_tty_driver);
+
+	return ret;
+}
+
+static void __exit i3c_tty_exit(void)
+{
+	i3c_driver_unregister(&i3c_driver);
+	tty_unregister_driver(i3c_tty_driver);
+	tty_driver_kref_put(i3c_tty_driver);
+	idr_destroy(&i3c_tty_minors);
+}
+
+module_init(i3c_tty_init);
+module_exit(i3c_tty_exit);
+
+MODULE_LICENSE("GPL");