From patchwork Tue May 7 13:15:22 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Corentin Labbe X-Patchwork-Id: 795473 Received: from mail-wr1-f48.google.com (mail-wr1-f48.google.com [209.85.221.48]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 56CD215DBCE for ; Tue, 7 May 2024 13:15:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715087732; cv=none; b=Pnn/9A0Vk07BZITkOICtktUuiW2GSWK+z2dnUGFdXwnvlcua/hcX9dnBth/7u4fuw7kAfzXce4dCMofJ/XRbhbHu0DPtKvN0N32nslLA2uPncyz+9S5nq5A5KTOt4oB9IMKpzvzk/YYjSdLCLxNM4OzN0uJNl6vgps8yYKzB9pQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715087732; c=relaxed/simple; bh=yBVKtpoZfYBKT1c/tHMoRT9ipb/zBbYmuyU6dHuqaCc=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=VaGejBqYdL9dlMDukjZ1d+qHLBmS24sNp1po7GLp6m7wubwWy1pRICjUus9gGSMxx2vx8AsybRl/Y15lVAseo5T5Sthuv5CtQCV2Vpq2DHG1HMjfqYxFyeDtw8BqjB+JiyEjG2Ms2sEC7xDC2J2FQQGFYeaPJ8WG8T3AGbWIQ84= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=baylibre.com; spf=pass smtp.mailfrom=baylibre.com; dkim=pass (2048-bit key) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.b=qRQOHLtW; arc=none smtp.client-ip=209.85.221.48 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=baylibre.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=baylibre.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.b="qRQOHLtW" Received: by mail-wr1-f48.google.com with SMTP id ffacd0b85a97d-34ddc9fe497so1809960f8f.3 for ; Tue, 07 May 2024 06:15:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=baylibre-com.20230601.gappssmtp.com; s=20230601; t=1715087728; x=1715692528; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=dS8bq6hP9Nnxo+QID78ewIsxvfj+bSiwlQY4JgsL0Xc=; b=qRQOHLtWkoLWtD1qA/ETghJM/4tJ0eCU8IiwUhMt8P6nLFFPZfYfAQvfMi/U/Ugbxp 9o2O7U8rEENJ7Gc4snTwnehpae+g08QAOmaT1PqrN9sQaNo6dE5+LgtNMxrHVyu8O4XP hLpceoE5acZMUzGcfxIXGz0+YpVuD1V8hTDnjBrp+XcNiJsOgoA3uSkfCc6J9Ba8pFKL co4vZzZionUFYWLhss3RmBCkxOGguucwm6C3c+DPD01oKtWdKatb5rPRXtXgmZUpYbUN nZ+4el6TlwkyOBYrMsKKQ6i0eBn036OKVstI0XM0YHCsyzZgrwENVeAIxsrqiZvvfj1n 4/JA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1715087728; x=1715692528; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=dS8bq6hP9Nnxo+QID78ewIsxvfj+bSiwlQY4JgsL0Xc=; b=TYBaB37kgQYRIiIn099TJZAQhRuD70uLb1c9nnRX45Y/0bVmRMhD3t7xvAZjXVh5+s zdTCH02HCibNfEv3CwRPFt9lTnapXctFgCd8/sJp0kqNekX2DMeNvjBJQTpn/KwLWoig J5wuitXhxBvpvztyNId7TJ8Dj3NQvQNAAsOhZ6tfPwSGS0W/7pYuhfWWcUUwEfaCsAQT STV5Ez2C31R49JkJ+HQjK8TkqXxkEgeQGupY40/+msEp0vlslbTlZcjABbsmxDFCW5cQ PB/HsWvbTgJK6sZluOQsGWqboUDp3DuUR2179KyvRqxO1JkiYytq9RWrqJ3CTS7n9aXh /hdQ== X-Forwarded-Encrypted: i=1; AJvYcCWtmmYnGenirqI5v15T/WYy9EpmuaI7gaOozXxOms/MSosDWCtora8OpOTP8ehTjY3T7PCJOZ+gt5oWbB6qXwnlqVBiXUo3zK28 X-Gm-Message-State: AOJu0Yy3cjkcgadeIEUpMdGeaK25Dc6+olbse1M6PC5UfD2rtPujMrMs LiLivTjfUBt1IFzPT7K5FUpae7nmL+fRTVk5FHKjbV1xr5ogSB4c31nW94j7jgs= X-Google-Smtp-Source: AGHT+IHlsOiCb2yyXNga8rNYz+/OXD9wTZ5ktM+34SM7kkaWwTscaNkj9sa6VS6qVkjzlymrbdahlw== X-Received: by 2002:a5d:5050:0:b0:346:662f:62b7 with SMTP id h16-20020a5d5050000000b00346662f62b7mr12418319wrt.71.1715087727651; Tue, 07 May 2024 06:15:27 -0700 (PDT) Received: from arnold.baylibre (laubervilliers-658-1-213-31.w90-63.abo.wanadoo.fr. [90.63.244.31]) by smtp.googlemail.com with ESMTPSA id o16-20020adfcf10000000b0034b1bd76d30sm12891416wrj.28.2024.05.07.06.15.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 07 May 2024 06:15:26 -0700 (PDT) From: Corentin Labbe To: gregkh@linuxfoundation.org, johan@kernel.org Cc: linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org, martin.blumenstingl@googlemail.com, david@ixit.cz, Corentin Labbe Subject: [PATCH 1/1 v7] usb: serial: add support for CH348 Date: Tue, 7 May 2024 13:15:22 +0000 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: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 Tested-by: Martin Blumenstingl Tested-by: Paul Spooren > --- 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 diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index ef8d1c73c754..1e1842656b54 100644 --- 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 diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile index c7bb1a88173e..d16ff59fde68 100644 --- a/drivers/usb/serial/Makefile +++ b/drivers/usb/serial/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_USB_SERIAL_AIRCABLE) += aircable.o 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 diff --git a/drivers/usb/serial/ch348.c b/drivers/usb/serial/ch348.c new file mode 100644 index 000000000000..e437ba36bb5e --- /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 + * With the help of Neil Armstrong + * and the help of Martin Blumenstingl + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("USB CH348 Octo port serial converter driver"); +MODULE_LICENSE("GPL");