generic: backport pending driver for Winchip CH348 USB serial
authorDaniel Golle <daniel@makrotopia.org>
Mon, 17 Jun 2024 14:31:12 +0000 (15:31 +0100)
committerDaniel Golle <daniel@makrotopia.org>
Thu, 20 Jun 2024 00:11:09 +0000 (01:11 +0100)
Import patch and package kernel module for Winchip CH348
USB-to-8x-UART chip.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
package/kernel/linux/modules/usb.mk
target/linux/generic/pending-6.6/890-usb-serial-add-support-for-CH348.patch [new file with mode: 0644]

index 7b680288b9dcd924705d4b1bd5d464f93b554710..12725f10ee25d9ba83e67763f0c41a338aac5542 100644 (file)
@@ -655,6 +655,22 @@ endef
 $(eval $(call KernelPackage,usb-serial-ch341))
 
 
+define KernelPackage/usb-serial-ch348
+  TITLE:=Support for CH348 devices
+  KCONFIG:=CONFIG_USB_SERIAL_CH348
+  FILES:=$(LINUX_DIR)/drivers/usb/serial/ch348.ko
+  AUTOLOAD:=$(call AutoProbe,ch348)
+  DEPENDS:=@LINUX_6_6
+  $(call AddDepends/usb-serial)
+endef
+
+define KernelPackage/usb-serial-ch348/description
+ Kernel support for Winchiphead CH348 USB-to-8x-Serial converters
+endef
+
+$(eval $(call KernelPackage,usb-serial-ch348))
+
+
 define KernelPackage/usb-serial-edgeport
   TITLE:=Support for Digi Edgeport devices
   KCONFIG:=CONFIG_USB_SERIAL_EDGEPORT
diff --git a/target/linux/generic/pending-6.6/890-usb-serial-add-support-for-CH348.patch b/target/linux/generic/pending-6.6/890-usb-serial-add-support-for-CH348.patch
new file mode 100644 (file)
index 0000000..028ecf4
--- /dev/null
@@ -0,0 +1,783 @@
+From df1357358eec062241bddd2995e7ef0ce86cf45a Mon Sep 17 00:00:00 2001
+X-Patchwork-Submitter: Corentin Labbe <clabbe@baylibre.com>
+X-Patchwork-Id: 13656881
+Message-Id: <20240507131522.3546113-2-clabbe@baylibre.com>
+X-Mailer: git-send-email 2.25.1
+In-Reply-To: <20240507131522.3546113-1-clabbe@baylibre.com>
+References: <20240507131522.3546113-1-clabbe@baylibre.com>
+Precedence: bulk
+X-Mailing-List: linux-usb@vger.kernel.org
+List-Id: <linux-usb.vger.kernel.org>
+From: Corentin Labbe <clabbe@baylibre.com>
+Date: Tue, 7 May 2024 13:15:22 +0000
+Subject: [PATCH v7] usb: serial: add support for CH348
+
+The CH348 is an USB octo port serial adapter.
+The device multiplexes all 8 ports in the same pair of Bulk endpoints.
+Since there is no public datasheet, unfortunately it remains some magic values
+
+Signed-off-by: Corentin Labbe <clabbe@baylibre.com>
+Tested-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+---
+ drivers/usb/serial/Kconfig  |   9 +
+ drivers/usb/serial/Makefile |   1 +
+ drivers/usb/serial/ch348.c  | 725 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 735 insertions(+)
+ create mode 100644 drivers/usb/serial/ch348.c
+
+--- a/drivers/usb/serial/Kconfig
++++ b/drivers/usb/serial/Kconfig
+@@ -112,6 +112,15 @@ config USB_SERIAL_CH341
+         To compile this driver as a module, choose M here: the
+         module will be called ch341.
++config USB_SERIAL_CH348
++      tristate "USB Winchiphead CH348 Octo Port Serial Driver"
++      help
++        Say Y here if you want to use a Winchiphead CH348 octo port
++        USB to serial adapter.
++
++        To compile this driver as a module, choose M here: the
++        module will be called ch348.
++
+ config USB_SERIAL_WHITEHEAT
+       tristate "USB ConnectTech WhiteHEAT Serial Driver"
+       select USB_EZUSB_FX2
+--- a/drivers/usb/serial/Makefile
++++ b/drivers/usb/serial/Makefile
+@@ -15,6 +15,7 @@ obj-$(CONFIG_USB_SERIAL_AIRCABLE)            += ai
+ obj-$(CONFIG_USB_SERIAL_ARK3116)              += ark3116.o
+ obj-$(CONFIG_USB_SERIAL_BELKIN)                       += belkin_sa.o
+ obj-$(CONFIG_USB_SERIAL_CH341)                        += ch341.o
++obj-$(CONFIG_USB_SERIAL_CH348)                        += ch348.o
+ obj-$(CONFIG_USB_SERIAL_CP210X)                       += cp210x.o
+ obj-$(CONFIG_USB_SERIAL_CYBERJACK)            += cyberjack.o
+ obj-$(CONFIG_USB_SERIAL_CYPRESS_M8)           += cypress_m8.o
+--- /dev/null
++++ b/drivers/usb/serial/ch348.c
+@@ -0,0 +1,725 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * USB serial driver for USB to Octal UARTs chip ch348.
++ *
++ * Copyright (C) 2022 Corentin Labbe <clabbe@baylibre.com>
++ * With the help of Neil Armstrong <neil.armstrong@linaro.org>
++ * and the help of Martin Blumenstingl <martin.blumenstingl@googlemail.com>
++ */
++
++#include <linux/completion.h>
++#include <linux/errno.h>
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/mutex.h>
++#include <linux/overflow.h>
++#include <linux/serial.h>
++#include <linux/slab.h>
++#include <linux/tty.h>
++#include <linux/tty_driver.h>
++#include <linux/tty_flip.h>
++#include <linux/usb.h>
++#include <linux/usb/serial.h>
++
++#define CH348_CMD_TIMEOUT   2000
++
++#define CH348_CTO_D   0x01
++#define CH348_CTO_R   0x02
++
++#define CH348_CTI_C   0x10
++#define CH348_CTI_DSR 0x20
++#define CH348_CTI_R   0x40
++#define CH348_CTI_DCD 0x80
++
++#define CH348_LO      0x02
++#define CH348_LP      0x04
++#define CH348_LF      0x08
++#define CH348_LB      0x10
++
++#define CMD_W_R               0xC0
++#define CMD_W_BR      0x80
++
++#define CMD_WB_E      0x90
++#define CMD_RB_E      0xC0
++
++#define M_NOR         0x00
++#define M_HF          0x03
++
++#define R_MOD         0x97
++#define R_IO_D                0x98
++#define R_IO_O                0x99
++#define R_IO_I                0x9b
++#define R_TM_O                0x9c
++#define R_INIT                0xa1
++
++#define R_C1          0x01
++#define R_C2          0x02
++#define R_C4          0x04
++#define R_C5          0x06
++
++#define R_II_B1               0x06
++#define R_II_B2               0x02
++#define R_II_B3               0x00
++
++#define CMD_VER               0x96
++
++#define CH348_RX_PORT_CHUNK_LENGTH    32
++#define CH348_RX_PORT_MAX_LENGTH      30
++
++struct ch348_rxbuf {
++      u8 port;
++      u8 length;
++      u8 data[CH348_RX_PORT_MAX_LENGTH];
++} __packed;
++
++struct ch348_txbuf {
++      u8 port;
++      __le16 length;
++      u8 data[];
++} __packed;
++
++#define CH348_TX_HDRSIZE offsetof(struct ch348_txbuf, data)
++
++struct ch348_initbuf {
++      u8 cmd;
++      u8 reg;
++      u8 port;
++      __be32 baudrate;
++      u8 format;
++      u8 paritytype;
++      u8 databits;
++      u8 rate;
++      u8 unknown;
++} __packed;
++
++#define CH348_MAXPORT 8
++
++/*
++ * The CH348 multiplexes rx & tx into a pair of Bulk USB endpoints for
++ * the 8 serial ports, and another pair of Bulk USB endpoints to
++ * set port settings and receive port status events.
++ *
++ * The USB serial cores ties every Bulk endpoints pairs to each ports,
++ * but in our case it will set port 0 with the rx/tx endpoints
++ * and port 1 with the setup/status endpoints.
++ *
++ * To still take advantage of the generic code, we (re-)initialize
++ * the USB serial port structure with the correct USB endpoint
++ * for read and write, and write proper process_read_urb()
++ * and prepare_write_buffer() to correctly (de-)multiplex data.
++ * Also we use a custom write() implementation to wait until the buffer
++ * has been fully transmitted to prevent TX buffer overruns.
++ */
++
++/*
++ * struct ch348_port - per-port information
++ * @uartmode:         UART port current mode
++ * @write_completion: completion event when the TX buffer has been written out
++ */
++struct ch348_port {
++      u8 uartmode;
++      struct completion write_completion;
++};
++
++/*
++ * struct ch348 - main container for all this driver information
++ * @udev:             pointer to the CH348 USB device
++ * @ports:            List of per-port information
++ * @serial:           pointer to the serial structure
++ * @write_lock:               protect against concurrent writes so we don't lose data
++ * @cmd_ep:           endpoint number for configure operations
++ * @status_urb:               URB for status
++ * @status_buffer:    buffer used by status_urb
++ */
++struct ch348 {
++      struct usb_device *udev;
++      struct ch348_port ports[CH348_MAXPORT];
++      struct usb_serial *serial;
++
++      struct mutex write_lock;
++
++      int cmd_ep;
++
++      struct urb *status_urb;
++      u8 status_buffer[];
++};
++
++struct ch348_magic {
++      u8 action;
++      u8 reg;
++      u8 control;
++} __packed;
++
++struct ch348_status_entry {
++      u8 portnum:4;
++      u8 unused:4;
++      u8 reg_iir;
++      union {
++              u8 lsr_signal;
++              u8 modem_signal;
++              u8 init_data[10];
++      };
++} __packed;
++
++static void ch348_process_status_urb(struct urb *urb)
++{
++      struct ch348_status_entry *status_entry;
++      struct ch348 *ch348 = urb->context;
++      int ret, status = urb->status;
++      struct usb_serial_port *port;
++      unsigned int i, status_len;
++
++      switch (status) {
++      case 0:
++              /* success */
++              break;
++      case -ECONNRESET:
++      case -ENOENT:
++      case -ESHUTDOWN:
++              /* this urb is terminated, clean up */
++              dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d\n",
++                      __func__, status);
++              return;
++      default:
++              dev_err(&urb->dev->dev, "%s - nonzero urb status received: %d\n",
++                      __func__, status);
++              goto exit;
++      }
++
++      if (urb->actual_length < 3) {
++              dev_warn(&ch348->udev->dev,
++                       "Received too short status buffer with %u bytes\n",
++                       urb->actual_length);
++              goto exit;
++      }
++
++      for (i = 0; i < urb->actual_length;) {
++              status_entry = urb->transfer_buffer + i;
++
++              if (status_entry->portnum >= CH348_MAXPORT) {
++                      dev_warn(&ch348->udev->dev,
++                               "Invalid port %d in status entry\n",
++                               status_entry->portnum);
++                      break;
++              }
++
++              port = ch348->serial->port[status_entry->portnum];
++              status_len = 3;
++
++              if (!status_entry->reg_iir) {
++                      dev_dbg(&port->dev, "Ignoring status with zero reg_iir\n");
++              } else if (status_entry->reg_iir == R_INIT) {
++                      status_len = 12;
++              } else if ((status_entry->reg_iir & 0x0f) == R_II_B1) {
++                      if (status_entry->lsr_signal & CH348_LO)
++                              port->icount.overrun++;
++                      if (status_entry->lsr_signal & CH348_LP)
++                              port->icount.parity++;
++                      if (status_entry->lsr_signal & CH348_LF)
++                              port->icount.frame++;
++                      if (status_entry->lsr_signal & CH348_LF)
++                              port->icount.brk++;
++              } else if ((status_entry->reg_iir & 0x0f) == R_II_B2) {
++                      complete_all(&ch348->ports[status_entry->portnum].write_completion);
++              } else {
++                      dev_warn(&port->dev,
++                               "Unsupported status with reg_iir 0x%02x\n",
++                               status_entry->reg_iir);
++              }
++
++              usb_serial_debug_data(&port->dev, __func__, status_len,
++                                    urb->transfer_buffer + i);
++
++              i += status_len;
++      }
++
++exit:
++      ret = usb_submit_urb(urb, GFP_ATOMIC);
++      if (ret)
++              dev_err(&urb->dev->dev, "%s - usb_submit_urb failed; %d\n",
++                      __func__, ret);
++}
++
++/*
++ * Some values came from vendor tree, and we have no meaning for them, this
++ * function simply use them.
++ */
++static int ch348_do_magic(struct ch348 *ch348, int portnum, u8 action, u8 reg, u8 control)
++{
++      struct ch348_magic *buffer;
++      int ret, len;
++
++      buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
++      if (!buffer)
++              return -ENOMEM;
++
++      if (portnum < 4)
++              reg += 0x10 * portnum;
++      else
++              reg += 0x10 * (portnum - 4) + 0x08;
++
++      buffer->action = action;
++      buffer->reg = reg;
++      buffer->control = control;
++
++      ret = usb_bulk_msg(ch348->udev, ch348->cmd_ep, buffer, 3, &len,
++                         CH348_CMD_TIMEOUT);
++      if (ret)
++              dev_err(&ch348->udev->dev, "Failed to write magic err=%d\n", ret);
++
++      kfree(buffer);
++
++      return ret;
++}
++
++static int ch348_configure(struct ch348 *ch348, int portnum)
++{
++      int ret;
++
++      ret = ch348_do_magic(ch348, portnum, CMD_W_R, R_C2, 0x87);
++      if (ret)
++              return ret;
++
++      return ch348_do_magic(ch348, portnum, CMD_W_R, R_C4, 0x08);
++}
++
++static void ch348_process_read_urb(struct urb *urb)
++{
++      struct usb_serial_port *port = urb->context;
++      struct ch348 *ch348 = usb_get_serial_data(port->serial);
++      unsigned int portnum, usblen, i;
++      struct ch348_rxbuf *rxb;
++
++      if (urb->actual_length < 2) {
++              dev_dbg(&ch348->udev->dev, "Empty rx buffer\n");
++              return;
++      }
++
++      for (i = 0; i < urb->actual_length; i += CH348_RX_PORT_CHUNK_LENGTH) {
++              rxb = urb->transfer_buffer + i;
++              portnum = rxb->port;
++              if (portnum >= CH348_MAXPORT) {
++                      dev_dbg(&ch348->udev->dev, "Invalid port %d\n", portnum);
++                      break;
++              }
++
++              port = ch348->serial->port[portnum];
++
++              usblen = rxb->length;
++              if (usblen > CH348_RX_PORT_MAX_LENGTH) {
++                      dev_dbg(&port->dev, "Invalid length %d for port %d\n",
++                              usblen, portnum);
++                      break;
++              }
++
++              tty_insert_flip_string(&port->port, rxb->data, usblen);
++              tty_flip_buffer_push(&port->port);
++              port->icount.rx += usblen;
++              usb_serial_debug_data(&port->dev, __func__, usblen, rxb->data);
++      }
++}
++
++static int ch348_prepare_write_buffer(struct usb_serial_port *port, void *dest, size_t size)
++{
++      struct ch348_txbuf *rxt = dest;
++      int count;
++
++      count = kfifo_out_locked(&port->write_fifo, rxt->data,
++                               size - CH348_TX_HDRSIZE, &port->lock);
++
++      rxt->port = port->port_number;
++      rxt->length = cpu_to_le16(count);
++
++      return count + CH348_TX_HDRSIZE;
++}
++
++static int ch348_write(struct tty_struct *tty, struct usb_serial_port *port,
++                     const unsigned char *buf, int count)
++{
++      struct ch348 *ch348 = usb_get_serial_data(port->serial);
++      struct ch348_port *ch348_port = &ch348->ports[port->port_number];
++      int ret, max_tx_size;
++
++      if (tty_get_baud_rate(tty) < 9600 && count >= 128)
++              /*
++               * Writing larger buffers can take longer than the hardware
++               * allows before discarding the write buffer. Limit the
++               * transfer size in such cases.
++               * These values have been found by empirical testing.
++               */
++              max_tx_size = 128;
++      else
++              /*
++              * Only ingest as many bytes as we can transfer with one URB at
++              * a time. Once an URB has been written we need to wait for the
++              * R_II_B2 status event before we are allowed to send more data.
++              * If we ingest more data then usb_serial_generic_write() will
++              * internally try to process as much data as possible with any
++              * number of URBs without giving us the chance to wait in
++              * between transfers.
++              */
++              max_tx_size = port->bulk_out_size - CH348_TX_HDRSIZE;
++
++      reinit_completion(&ch348_port->write_completion);
++
++      mutex_lock(&ch348->write_lock);
++
++      /*
++       * For any (remaining) bytes that we did not transfer TTY core will
++       * call us again, with the buffer and count adjusted to the remaining
++       * data.
++       */
++      ret = usb_serial_generic_write(tty, port, buf, min(count, max_tx_size));
++
++      mutex_unlock(&ch348->write_lock);
++
++      if (ret <= 0)
++              return ret;
++
++      if (!wait_for_completion_interruptible_timeout(&ch348_port->write_completion,
++                                                     msecs_to_jiffies(CH348_CMD_TIMEOUT))) {
++              dev_err_console(port, "Failed to wait for TX buffer flush\n");
++              return -ETIMEDOUT;
++      }
++
++      return ret;
++}
++
++static int ch348_set_uartmode(struct ch348 *ch348, int portnum, u8 mode)
++{
++      int ret;
++
++      if (ch348->ports[portnum].uartmode == M_NOR && mode == M_HF) {
++              ret = ch348_do_magic(ch348, portnum, CMD_W_BR, R_C4, 0x51);
++              if (ret)
++                      return ret;
++              ch348->ports[portnum].uartmode = M_HF;
++      }
++
++      if (ch348->ports[portnum].uartmode == M_HF && mode == M_NOR) {
++              ret = ch348_do_magic(ch348, portnum, CMD_W_BR, R_C4, 0x50);
++              if (ret)
++                      return ret;
++              ch348->ports[portnum].uartmode = M_NOR;
++      }
++      return 0;
++}
++
++static void ch348_set_termios(struct tty_struct *tty, struct usb_serial_port *port,
++                            const struct ktermios *termios_old)
++{
++      struct ch348 *ch348 = usb_get_serial_data(port->serial);
++      struct ktermios *termios = &tty->termios;
++      int ret, portnum = port->port_number;
++      struct ch348_initbuf *buffer;
++      speed_t baudrate;
++      u8 format;
++
++      if (termios_old && !tty_termios_hw_change(&tty->termios, termios_old))
++              return;
++
++      buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
++      if (!buffer) {
++              if (termios_old)
++                      tty->termios = *termios_old;
++              return;
++      }
++
++      /*
++       * The datasheet states that only baud rates in range of 1200..6000000
++       * are supported. Tests however show that even baud rates as low as 50
++       * and as high as 12000000 are working in practice.
++       */
++      baudrate = clamp(tty_get_baud_rate(tty), 50, 12000000);
++
++      format = termios->c_cflag & CSTOPB ? 2 : 1;
++
++      buffer->paritytype = 0;
++      if (termios->c_cflag & PARENB) {
++              if (termios->c_cflag & PARODD)
++                      buffer->paritytype += 1;
++              else
++                      buffer->paritytype += 2;
++              if  (termios->c_cflag & CMSPAR)
++                      buffer->paritytype += 2;
++      }
++
++      switch (C_CSIZE(tty)) {
++      case CS5:
++              buffer->databits = 5;
++              break;
++      case CS6:
++              buffer->databits = 6;
++              break;
++      case CS7:
++              buffer->databits = 7;
++              break;
++      case CS8:
++      default:
++              buffer->databits = 8;
++              break;
++      }
++      buffer->cmd = CMD_WB_E | (portnum & 0x0F);
++      buffer->reg = R_INIT;
++      buffer->port = portnum;
++      buffer->baudrate = cpu_to_be32(baudrate);
++
++      if (format == 2)
++              buffer->format = 0x02;
++      else if (format == 1)
++              buffer->format = 0x00;
++
++      buffer->rate = max_t(speed_t, 5, (10000 * 15 / baudrate) + 1);
++
++      ret = usb_bulk_msg(ch348->udev, ch348->cmd_ep, buffer,
++                         sizeof(*buffer), NULL, CH348_CMD_TIMEOUT);
++      if (ret < 0) {
++              dev_err(&ch348->udev->dev, "Failed to change line settings: err=%d\n",
++                      ret);
++              goto out;
++      }
++
++      ret = ch348_do_magic(ch348, portnum, CMD_W_R, R_C1, 0x0F);
++      if (ret < 0)
++              goto out;
++
++      if (C_CRTSCTS(tty))
++              ret = ch348_set_uartmode(ch348, portnum, M_HF);
++      else
++              ret = ch348_set_uartmode(ch348, portnum, M_NOR);
++
++out:
++      kfree(buffer);
++}
++
++static int ch348_open(struct tty_struct *tty, struct usb_serial_port *port)
++{
++      struct ch348 *ch348 = usb_get_serial_data(port->serial);
++      int ret;
++
++      if (tty)
++              ch348_set_termios(tty, port, NULL);
++
++      ret = ch348_configure(ch348, port->port_number);
++      if (ret) {
++              dev_err(&ch348->udev->dev, "Fail to configure err=%d\n", ret);
++              return ret;
++      }
++
++      return usb_serial_generic_open(tty, port);
++}
++
++static int ch348_attach(struct usb_serial *serial)
++{
++      struct usb_endpoint_descriptor *epcmd, *epstatus;
++      struct usb_serial_port *port0 = serial->port[1];
++      struct usb_device *usb_dev = serial->dev;
++      int status_buffer_size, i, ret;
++      struct usb_interface *intf;
++      struct ch348 *ch348;
++
++      intf = usb_ifnum_to_if(usb_dev, 0);
++      epstatus = &intf->cur_altsetting->endpoint[2].desc;
++      epcmd = &intf->cur_altsetting->endpoint[3].desc;
++
++      status_buffer_size = usb_endpoint_maxp(epstatus);
++
++      ch348 = kzalloc(struct_size(ch348, status_buffer, status_buffer_size),
++                      GFP_KERNEL);
++      if (!ch348)
++              return -ENOMEM;
++
++      usb_set_serial_data(serial, ch348);
++
++      ch348->udev = serial->dev;
++      ch348->serial = serial;
++      mutex_init(&ch348->write_lock);
++
++      for (i = 0; i < CH348_MAXPORT; i++)
++              init_completion(&ch348->ports[i].write_completion);
++
++      ch348->status_urb = usb_alloc_urb(0, GFP_KERNEL);
++      if (!ch348->status_urb) {
++              ret = -ENOMEM;
++              goto err_free_ch348;
++      }
++
++      usb_fill_bulk_urb(ch348->status_urb, ch348->udev,
++                        usb_rcvbulkpipe(ch348->udev, epstatus->bEndpointAddress),
++                        ch348->status_buffer, status_buffer_size,
++                        ch348_process_status_urb, ch348);
++
++      ret = usb_submit_urb(ch348->status_urb, GFP_KERNEL);
++      if (ret) {
++              dev_err(&ch348->udev->dev,
++                      "%s - failed to submit status/interrupt urb %i\n",
++                      __func__, ret);
++              goto err_free_status_urb;
++      }
++
++      ret = usb_serial_generic_submit_read_urbs(port0, GFP_KERNEL);
++      if (ret)
++              goto err_kill_status_urb;
++
++      ch348->cmd_ep = usb_sndbulkpipe(usb_dev, epcmd->bEndpointAddress);
++
++      return 0;
++
++err_kill_status_urb:
++      usb_kill_urb(ch348->status_urb);
++err_free_status_urb:
++      usb_free_urb(ch348->status_urb);
++err_free_ch348:
++      kfree(ch348);
++      return ret;
++}
++
++static void ch348_release(struct usb_serial *serial)
++{
++      struct ch348 *ch348 = usb_get_serial_data(serial);
++
++      usb_kill_urb(ch348->status_urb);
++      usb_free_urb(ch348->status_urb);
++
++      kfree(ch348);
++}
++
++static void ch348_print_version(struct usb_serial *serial)
++{
++      u8 *version_buf;
++      int ret;
++
++      version_buf = kzalloc(4, GFP_KERNEL);
++      if (!version_buf)
++              return;
++
++      ret = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
++                            CMD_VER,
++                            USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
++                            0, 0, version_buf, 4, CH348_CMD_TIMEOUT);
++      if (ret < 0)
++              dev_dbg(&serial->dev->dev, "Failed to read CMD_VER: %d\n", ret);
++      else
++              dev_info(&serial->dev->dev, "Found WCH CH348%s\n",
++                       (version_buf[1] & 0x80) ? "Q" : "L");
++
++      kfree(version_buf);
++}
++
++static int ch348_probe(struct usb_serial *serial, const struct usb_device_id *id)
++{
++      struct usb_endpoint_descriptor *epread, *epwrite, *epstatus, *epcmd;
++      struct usb_device *usb_dev = serial->dev;
++      struct usb_interface *intf;
++      int ret;
++
++      intf = usb_ifnum_to_if(usb_dev, 0);
++
++      ret = usb_find_common_endpoints(intf->cur_altsetting, &epread, &epwrite,
++                                      NULL, NULL);
++      if (ret) {
++              dev_err(&serial->dev->dev, "Failed to find basic endpoints ret=%d\n", ret);
++              return ret;
++      }
++
++      epstatus = &intf->cur_altsetting->endpoint[2].desc;
++      if (!usb_endpoint_is_bulk_in(epstatus)) {
++              dev_err(&serial->dev->dev, "Missing second bulk in (STATUS/INT)\n");
++              return -ENODEV;
++      }
++
++      epcmd = &intf->cur_altsetting->endpoint[3].desc;
++      if (!usb_endpoint_is_bulk_out(epcmd)) {
++              dev_err(&serial->dev->dev, "Missing second bulk out (CMD)\n");
++              return -ENODEV;
++      }
++
++      ch348_print_version(serial);
++
++      return 0;
++}
++
++static int ch348_calc_num_ports(struct usb_serial *serial,
++                              struct usb_serial_endpoints *epds)
++{
++      int i;
++
++      for (i = 1; i < CH348_MAXPORT; ++i) {
++              epds->bulk_out[i] = epds->bulk_out[0];
++              epds->bulk_in[i] = epds->bulk_in[0];
++      }
++
++      epds->num_bulk_out = CH348_MAXPORT;
++      epds->num_bulk_in = CH348_MAXPORT;
++
++      return CH348_MAXPORT;
++}
++
++static int ch348_suspend(struct usb_serial *serial, pm_message_t message)
++{
++      struct ch348 *ch348 = usb_get_serial_data(serial);
++
++      usb_kill_urb(ch348->status_urb);
++
++      return 0;
++}
++
++static int ch348_resume(struct usb_serial *serial)
++{
++      struct ch348 *ch348 = usb_get_serial_data(serial);
++      int ret;
++
++      ret = usb_submit_urb(ch348->status_urb, GFP_KERNEL);
++      if (ret) {
++              dev_err(&ch348->udev->dev,
++                      "%s - failed to submit status/interrupt urb %i\n",
++                      __func__, ret);
++              return ret;
++      }
++
++      ret = usb_serial_generic_resume(serial);
++      if (ret)
++              usb_kill_urb(ch348->status_urb);
++
++      return ret;
++}
++
++static const struct usb_device_id ch348_ids[] = {
++      { USB_DEVICE(0x1a86, 0x55d9), },
++      { /* sentinel */ }
++};
++
++MODULE_DEVICE_TABLE(usb, ch348_ids);
++
++static struct usb_serial_driver ch348_device = {
++      .driver = {
++              .owner = THIS_MODULE,
++              .name = "ch348",
++      },
++      .id_table =             ch348_ids,
++      .num_ports =            CH348_MAXPORT,
++      .num_bulk_in =          1,
++      .num_bulk_out =         1,
++      .open =                 ch348_open,
++      .set_termios =          ch348_set_termios,
++      .process_read_urb =     ch348_process_read_urb,
++      .prepare_write_buffer = ch348_prepare_write_buffer,
++      .write =                ch348_write,
++      .probe =                ch348_probe,
++      .calc_num_ports =       ch348_calc_num_ports,
++      .attach =               ch348_attach,
++      .release =              ch348_release,
++      .suspend =              ch348_suspend,
++      .resume =               ch348_resume,
++};
++
++static struct usb_serial_driver * const serial_drivers[] = {
++      &ch348_device, NULL
++};
++
++module_usb_serial_driver(serial_drivers, ch348_ids);
++
++MODULE_AUTHOR("Corentin Labbe <clabbe@baylibre.com>");
++MODULE_DESCRIPTION("USB CH348 Octo port serial converter driver");
++MODULE_LICENSE("GPL");