* [PATCH 1/1] [RFC] USB: serial: ch341: Add I2C support to CH341 driver
@ 2021-03-23 3:22 Frank Zago
2021-03-23 9:32 ` Greg KH
0 siblings, 1 reply; 2+ messages in thread
From: Frank Zago @ 2021-03-23 3:22 UTC (permalink / raw)
To: linux-serial
The Winchiphead CH341 IC is more than a UART interface. Amongst other
things, it can talk to an I2C bus.
Several people have contributed to an out of tree driver: Tse Lun
Bien, Marco Gittler and Till Harbaum.
https://github.com/allanbian1017/i2c-ch341-usb.git
This commit integrates the I2C portions into the CH341 serial driver
and exposes the I2C device.
Tested with a CH341A, CH341B and CH341T.
Signed-off-by: Frank Zago <frank@zago.net>
---
I didn't find a way to say a USB device has several functions. I
followed what the CP210x driver did with gpio, but I think it's a
hack. The private data for the I2C part should be linked to the USB
device, not the serial port. It works here and in the cp210x driver
because there is only 1 serial port in the device. If it had 2 or more
ports, I believe bad things would happen. So is there a better way to
handle USB devices like that?
For info, the CH341A/B supports 3 different modes, each with a different
USB PID, usually selected by jumpers. All of them support I2C, but
only 2 support UART, one supports SPI and 2 support GPIOs.
drivers/usb/serial/Kconfig | 4 +-
drivers/usb/serial/ch341.c | 284 ++++++++++++++++++++++++++++++++++++-
2 files changed, 283 insertions(+), 5 deletions(-)
diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig
index de5c01257060..3771dc5dd055 100644
--- a/drivers/usb/serial/Kconfig
+++ b/drivers/usb/serial/Kconfig
@@ -106,7 +106,7 @@ config USB_SERIAL_CH341
tristate "USB Winchiphead CH341 Single Port Serial Driver"
help
Say Y here if you want to use a Winchiphead CH341 single port
- USB to serial adapter.
+ USB to serial adapter. This driver also provide access to I2C.
To compile this driver as a module, choose M here: the
module will be called ch341.
diff --git a/drivers/usb/serial/ch341.c b/drivers/usb/serial/ch341.c
index 2db917eab799..fc695b745fa5 100644
--- a/drivers/usb/serial/ch341.c
+++ b/drivers/usb/serial/ch341.c
@@ -4,12 +4,19 @@
* Copyright 2007, Werner Cornelius <werner@cornelius-consult.de>
* Copyright 2009, Boris Hajduk <boris@hajduk.org>
*
- * ch341.c implements a serial port driver for the Winchiphead CH341.
+ * I2C code:
+ * Copyright (c) 2016 Tse Lun Bien
+ * Copyright (c) 2014 Marco Gittler
+ * Copyright (C) 2006-2007 Till Harbaum (Till@Harbaum.org)
+ *
+ * ch341.c implements a serial port driver for the Winchiphead CH341,
+ * with I2C support
*
* The CH341 device can be used to implement an RS232 asynchronous
* serial port, an IEEE-1284 parallel printer port or a memory-like
* interface. In all cases the CH341 supports an I2C interface as well.
- * This driver only supports the asynchronous serial interface.
+ * This driver only supports the asynchronous serial and I2C
+ * interfaces.
*/
#include <linux/kernel.h>
@@ -19,6 +26,7 @@
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include <linux/serial.h>
+#include <linux/i2c.h>
#include <asm/unaligned.h>
#define DEFAULT_BAUD_RATE 9600
@@ -80,6 +88,20 @@
#define CH341_QUIRK_LIMITED_PRESCALER BIT(0)
#define CH341_QUIRK_SIMULATE_BREAK BIT(1)
+#define CH341_I2C_LOW_SPEED 0 /* low speed - 20kHz */
+#define CH341_I2C_STANDARD_SPEED 1 /* standard speed - 100kHz */
+#define CH341_I2C_FAST_SPEED 2 /* fast speed - 400kHz */
+#define CH341_I2C_HIGH_SPEED 3 /* high speed - 750kHz */
+
+#define CH341_REQ_I2C_STREAM 0xAA
+
+#define CH341_I2C_STM_STA 0x74
+#define CH341_I2C_STM_STO 0x75
+#define CH341_I2C_STM_OUT 0x80
+#define CH341_I2C_STM_IN 0xC0
+#define CH341_I2C_STM_SET 0x60
+#define CH341_I2C_STM_END 0x00
+
static const struct usb_device_id id_table[] = {
{ USB_DEVICE(0x1a86, 0x5512) },
{ USB_DEVICE(0x1a86, 0x5523) },
@@ -99,8 +121,260 @@ struct ch341_private {
u8 lcr;
unsigned long quirks;
unsigned long break_end;
+
+#ifdef CONFIG_I2C
+ /* I2C */
+ struct i2c_adapter adapter;
+ bool i2c_registered;
+ u8 ep_in;
+ u8 ep_out;
+ u8 in_buf[32];
+ u8 out_buf[32];
+#endif
+};
+
+#ifdef CONFIG_I2C
+static int ch341_i2c_bulk(struct usb_serial_port *port, int out_len, int in_len)
+{
+ struct usb_device *dev = port->serial->dev;
+ struct ch341_private *priv = usb_get_serial_port_data(port);
+ int retval;
+ int actual;
+
+ dev_dbg(&dev->dev, "bulk_out %d bytes, bulk_in %d bytes\n",
+ out_len, (in_len == 0) ? 0 : 32);
+
+ retval = usb_bulk_msg(dev, usb_sndbulkpipe(dev, priv->ep_out),
+ priv->out_buf, out_len, &actual, 2000);
+ if (retval < 0)
+ return retval;
+
+ if (in_len == 0)
+ return actual;
+
+ memset(priv->in_buf, 0, sizeof(priv->in_buf));
+ retval = usb_bulk_msg(dev, usb_rcvbulkpipe(dev, priv->ep_in),
+ priv->in_buf, 32, &actual, 2000);
+ if (retval < 0)
+ return retval;
+
+ return actual;
+}
+
+static int ch341_i2c_check_dev(struct usb_serial_port *port, u8 addr)
+{
+ struct ch341_private *priv = usb_get_serial_port_data(port);
+ int retval;
+
+ priv->out_buf[0] = CH341_REQ_I2C_STREAM;
+ priv->out_buf[1] = CH341_I2C_STM_STA;
+ priv->out_buf[2] = CH341_I2C_STM_OUT; /* must be zero length */
+ priv->out_buf[3] = (addr << 1) | 0x1;
+ priv->out_buf[4] = CH341_I2C_STM_IN; /* zero length here as well */
+ priv->out_buf[5] = CH341_I2C_STM_STO;
+ priv->out_buf[6] = CH341_I2C_STM_END;
+
+ retval = ch341_i2c_bulk(port, 7, 1);
+ if (retval < 0)
+ return retval;
+
+ if (priv->in_buf[0] & 0x80)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int ch341_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs,
+ int num)
+{
+ struct usb_serial_port *port = adapter->algo_data;
+ struct ch341_private *priv = usb_get_serial_port_data(port);
+ int retval;
+ int i;
+ int l;
+
+ dev_dbg(&port->serial->dev->dev, "master xfer %d messages\n", num);
+
+ retval = ch341_i2c_check_dev(port, msgs[0].addr);
+ if (retval < 0)
+ return retval;
+
+ if (num == 1) {
+ /* size larger than endpoint max transfer size */
+ if ((msgs[0].len + 6) > 32)
+ return -EIO;
+
+ if (msgs[0].flags & I2C_M_RD) {
+ priv->out_buf[0] = CH341_REQ_I2C_STREAM;
+ priv->out_buf[1] = CH341_I2C_STM_STA;
+ priv->out_buf[2] = CH341_I2C_STM_OUT | 0x1;
+ priv->out_buf[3] = (msgs[0].addr << 1) | 0x1;
+
+ if (msgs[0].len) {
+ for (i = 0, l = msgs[0].len; l > 1; l--, i++)
+ priv->out_buf[i + 4] =
+ CH341_I2C_STM_IN | 1;
+ priv->out_buf[msgs[0].len + 3] =
+ CH341_I2C_STM_IN;
+ }
+
+ priv->out_buf[msgs[0].len + 4] = CH341_I2C_STM_STO;
+ priv->out_buf[msgs[0].len + 5] = CH341_I2C_STM_END;
+
+ retval = ch341_i2c_bulk(port, msgs[0].len + 6, msgs[0].len);
+ if (retval < 0)
+ return retval;
+
+ memcpy(msgs[0].buf, priv->in_buf, msgs[0].len);
+ } else {
+ priv->out_buf[0] = CH341_REQ_I2C_STREAM;
+ priv->out_buf[1] = CH341_I2C_STM_STA;
+ priv->out_buf[2] =
+ CH341_I2C_STM_OUT | (msgs[0].len + 1);
+ priv->out_buf[3] = msgs[0].addr << 1;
+
+ memcpy(&priv->out_buf[4], msgs[0].buf, msgs[0].len);
+
+ priv->out_buf[msgs[0].len + 4] = CH341_I2C_STM_STO;
+ priv->out_buf[msgs[0].len + 5] = CH341_I2C_STM_END;
+
+ retval = ch341_i2c_bulk(port, msgs[0].len + 6, 0);
+ if (retval < 0)
+ return retval;
+ }
+ } else if (num == 2) {
+ if (!(msgs[0].flags & I2C_M_RD) && (msgs[1].flags & I2C_M_RD)) {
+ /* size larger than endpoint max transfer size */
+ if (((msgs[0].len + 4) > 32) ||
+ ((msgs[1].len + 6) > 32))
+ return -EIO;
+
+ /* write data phase */
+ priv->out_buf[0] = CH341_REQ_I2C_STREAM;
+ priv->out_buf[1] = CH341_I2C_STM_STA;
+ priv->out_buf[2] =
+ CH341_I2C_STM_OUT | (msgs[0].len + 1);
+ priv->out_buf[3] = msgs[0].addr << 1;
+
+ memcpy(&priv->out_buf[4], msgs[0].buf, msgs[0].len);
+
+ retval = ch341_i2c_bulk(port, msgs[0].len + 4, 0);
+ if (retval < 0)
+ return retval;
+
+ /* read data phase */
+ priv->out_buf[0] = CH341_REQ_I2C_STREAM;
+ priv->out_buf[1] = CH341_I2C_STM_STA;
+ priv->out_buf[2] = CH341_I2C_STM_OUT | 0x1;
+ priv->out_buf[3] = (msgs[1].addr << 1) | 0x1;
+
+ if (msgs[1].len) {
+ for (i = 0, l = msgs[1].len; l > 1; l--, i++)
+ priv->out_buf[i + 4] =
+ CH341_I2C_STM_IN | 1;
+ priv->out_buf[msgs[1].len + 3] =
+ CH341_I2C_STM_IN;
+ }
+
+ priv->out_buf[msgs[1].len + 4] = CH341_I2C_STM_STO;
+ priv->out_buf[msgs[1].len + 5] = CH341_I2C_STM_END;
+
+ retval = ch341_i2c_bulk(port, msgs[1].len + 6, msgs[1].len);
+ if (retval < 0)
+ return retval;
+
+ memcpy(msgs[1].buf, priv->in_buf, msgs[1].len);
+ } else {
+ return -EIO;
+ }
+ } else {
+ dev_err(&port->serial->dev->dev,
+ "This case(num > 2) is not supported for now\n");
+ return -EIO;
+ }
+
+ return num;
+}
+
+
+static u32 ch341_i2c_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm ch341_i2c_algorithm = {
+ .master_xfer = ch341_i2c_xfer,
+ .functionality = ch341_i2c_func,
};
+static void ch341_i2c_setup(struct usb_serial_port *port)
+{
+ struct ch341_private *priv = usb_get_serial_port_data(port);
+ const struct usb_host_interface *setting =
+ port->serial->interface->cur_altsetting;
+ int retval = -ENOMEM;
+
+ priv->ep_out = setting->endpoint[1].desc.bEndpointAddress;
+ priv->ep_in = setting->endpoint[0].desc.bEndpointAddress;
+
+ /* setup i2c adapter description */
+ priv->adapter.owner = THIS_MODULE;
+ priv->adapter.class = I2C_CLASS_HWMON;
+ priv->adapter.algo = &ch341_i2c_algorithm;
+ priv->adapter.algo_data = port;
+ snprintf(priv->adapter.name, sizeof(priv->adapter.name),
+ "i2c-ch341-usb at bus %03d device %03d",
+ port->serial->dev->bus->busnum, port->serial->dev->devnum);
+
+ priv->adapter.dev.parent = &port->serial->dev->dev;
+
+ /* and finally attach to i2c layer */
+ i2c_add_adapter(&priv->adapter);
+
+ /* set ch341 i2c speed */
+ priv->out_buf[0] = CH341_REQ_I2C_STREAM;
+ priv->out_buf[1] = CH341_I2C_STM_SET | CH341_I2C_STANDARD_SPEED;
+ priv->out_buf[2] = CH341_I2C_STM_END;
+ retval = ch341_i2c_bulk(port, 3, 0);
+ if (retval < 0) {
+ dev_err(&priv->adapter.dev, "failure setting I2C speed\n");
+ goto error;
+ }
+
+ priv->i2c_registered = true;
+
+ dev_info(&priv->adapter.dev, "registered i2c device\n");
+
+ return;
+
+error:
+ i2c_del_adapter(&priv->adapter);
+}
+
+static void ch341_i2c_remove(struct usb_serial_port *port)
+{
+ struct ch341_private *priv = usb_get_serial_port_data(port);
+
+ if (priv->i2c_registered) {
+ priv->i2c_registered = false;
+ i2c_del_adapter(&priv->adapter);
+ }
+}
+
+#else
+
+static int ch341_i2c_setup(struct usb_serial *serial)
+{
+ return 0;
+}
+
+static void ch341_i2c_remove(struct usb_serial *serial)
+{
+ return 0;
+}
+
+#endif
+
static void ch341_set_termios(struct tty_struct *tty,
struct usb_serial_port *port,
struct ktermios *old_termios);
@@ -414,9 +688,12 @@ static int ch341_port_probe(struct usb_serial_port *port)
if (r < 0)
goto error;
+ ch341_i2c_setup(port);
+
return 0;
-error: kfree(priv);
+error:
+ kfree(priv);
return r;
}
@@ -424,6 +701,7 @@ static void ch341_port_remove(struct usb_serial_port *port)
{
struct ch341_private *priv;
+ ch341_i2c_remove(port);
priv = usb_get_serial_port_data(port);
kfree(priv);
}
--
2.27.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH 1/1] [RFC] USB: serial: ch341: Add I2C support to CH341 driver
2021-03-23 3:22 [PATCH 1/1] [RFC] USB: serial: ch341: Add I2C support to CH341 driver Frank Zago
@ 2021-03-23 9:32 ` Greg KH
0 siblings, 0 replies; 2+ messages in thread
From: Greg KH @ 2021-03-23 9:32 UTC (permalink / raw)
To: Frank Zago; +Cc: linux-serial
On Mon, Mar 22, 2021 at 10:22:54PM -0500, Frank Zago wrote:
> The Winchiphead CH341 IC is more than a UART interface. Amongst other
> things, it can talk to an I2C bus.
>
> Several people have contributed to an out of tree driver: Tse Lun
> Bien, Marco Gittler and Till Harbaum.
>
> https://github.com/allanbian1017/i2c-ch341-usb.git
>
> This commit integrates the I2C portions into the CH341 serial driver
> and exposes the I2C device.
>
> Tested with a CH341A, CH341B and CH341T.
>
> Signed-off-by: Frank Zago <frank@zago.net>
> ---
>
> I didn't find a way to say a USB device has several functions. I
> followed what the CP210x driver did with gpio, but I think it's a
> hack. The private data for the I2C part should be linked to the USB
> device, not the serial port. It works here and in the cp210x driver
> because there is only 1 serial port in the device. If it had 2 or more
> ports, I believe bad things would happen. So is there a better way to
> handle USB devices like that?
>
> For info, the CH341A/B supports 3 different modes, each with a different
> USB PID, usually selected by jumpers. All of them support I2C, but
> only 2 support UART, one supports SPI and 2 support GPIOs.
>
>
> drivers/usb/serial/Kconfig | 4 +-
> drivers/usb/serial/ch341.c | 284 ++++++++++++++++++++++++++++++++++++-
> 2 files changed, 283 insertions(+), 5 deletions(-)
$ ./scripts/get_maintainer.pl --file drivers/usb/serial/ch341.c
Johan Hovold <johan@kernel.org> (maintainer:USB SERIAL SUBSYSTEM)
Greg Kroah-Hartman <gregkh@linuxfoundation.org> (supporter:USB SUBSYSTEM)
linux-usb@vger.kernel.org (open list:USB SERIAL SUBSYSTEM)
linux-kernel@vger.kernel.org (open list)
Please always send patches to the proper people and mailing lists,
otherwise it will be lost :(
thanks,
greg k-h
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2021-03-23 9:32 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-03-23 3:22 [PATCH 1/1] [RFC] USB: serial: ch341: Add I2C support to CH341 driver Frank Zago
2021-03-23 9:32 ` Greg KH
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.