mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-30 18:47:06 +00:00
784 lines
20 KiB
Diff
784 lines
20 KiB
Diff
|
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");
|