diff --git a/arch/loongarch/boot/dts/loongson/loongson2k300.dtsi b/arch/loongarch/boot/dts/loongson/loongson2k300.dtsi index d3581fc4b..47fabb506 100644 --- a/arch/loongarch/boot/dts/loongson/loongson2k300.dtsi +++ b/arch/loongarch/boot/dts/loongson/loongson2k300.dtsi @@ -766,10 +766,20 @@ interrupt-parent = <&icu>; interrupts = <48>; dma-mask = <0x0 0xffffffff>; - /* Use for most devices */ - //loongson,dwc2-fix = ; - /* Use for 4G model*/ - loongson,dwc2-fix = ; + /* DWC2_LOONGSON_FIX_DMA: + Used for most devices, such as USB storage and USB touchscreens. + However, network devices may experience packet loss. */ + loongson,dwc2-fix = ; + /* DWC2_LOONGSON_FIX_DELAY: + Deprecated. It causes USB to operate slowly. + However, the 4G model can still dial up. Maybe optimize later. */ + //loongson,dwc2-fix = ; + /* DWC2_LOONGSON_FIX_NONE: + Original. Some USB devices cannot initialize when first powered up. + However, network devices do not experience packet loss. + Therefore, if we confirm the use of a USB-network device, then select this configuration. */ + //loongson,dwc2-fix = ; + //dr_mode = "peripheral"; /* Optional for that dr_mode = "host" or dr_mode = "peripheral" */ }; @@ -847,13 +857,13 @@ trips { cpu_alert: cpu-alert { - temperature = <55000>; + temperature = <75000>; hysteresis = <5000>; type = "active"; }; cpu_crit: cpu-crit { - temperature = <125000>; + temperature = <105000>; hysteresis = <5000>; type = "critical"; }; diff --git a/arch/loongarch/boot/dts/loongson/ls2k300_pai_common.dtsi b/arch/loongarch/boot/dts/loongson/ls2k300_pai_common.dtsi index 4831f79ef..bde5dfb10 100644 --- a/arch/loongarch/boot/dts/loongson/ls2k300_pai_common.dtsi +++ b/arch/loongarch/boot/dts/loongson/ls2k300_pai_common.dtsi @@ -270,3 +270,7 @@ loongson,sound-card-name = "ES8388"; }; +&otg { + // PAI 板卡定死使用 wifi + loongson,dwc2-fix = ; +}; diff --git a/arch/loongarch/configs/loongson_2k300_defconfig b/arch/loongarch/configs/loongson_2k300_defconfig index f7b76177f..a0a397e2c 100644 --- a/arch/loongarch/configs/loongson_2k300_defconfig +++ b/arch/loongarch/configs/loongson_2k300_defconfig @@ -553,6 +553,8 @@ CONFIG_USB_ACM=y CONFIG_USB_STORAGE=y CONFIG_USB_DWC2=y CONFIG_USB_SERIAL=y +CONFIG_USB_SERIAL_CH341=y +CONFIG_USB_SERIAL_PL2303=y CONFIG_USB_SERIAL_OPTION=y CONFIG_USB_GADGET=y CONFIG_USB_ETH=y diff --git a/drivers/iio/temperature/max31865.c b/drivers/iio/temperature/max31865.c index 041ec595f..d858f9e6b 100644 --- a/drivers/iio/temperature/max31865.c +++ b/drivers/iio/temperature/max31865.c @@ -361,6 +361,10 @@ static int max31865_probe(struct spi_device *spi) data = iio_priv(indio_dev); data->spi = spi; data->filter_50hz = false; + + if (of_property_read_bool(spi->dev.of_node, "filter_50hz")) + data->filter_50hz = true; + mutex_init(&data->lock); indio_dev->info = &max31865_info; diff --git a/drivers/spi/spi-ls-io.c b/drivers/spi/spi-ls-io.c index 598218697..a5ed6c8b0 100644 --- a/drivers/spi/spi-ls-io.c +++ b/drivers/spi/spi-ls-io.c @@ -22,7 +22,7 @@ #include #include -/*SPI IO registers */ +/*SPI IO registers */ #define REG_CR1 0x00 #define REG_CR2 0x04 #define REG_CR3 0x08 @@ -140,6 +140,11 @@ struct ls_spiio_priv { u8 dsize; struct gpio_desc **cs_gpios; struct device *dev; + + struct work_struct work; + struct workqueue_struct *work_queue; + struct list_head xfer_queue; + spinlock_t lock; }; static int ls_spiio_idle_sr1(struct ls_spiio_priv* priv, u32 bit) @@ -153,10 +158,14 @@ static int ls_spiio_idle_sr1(struct ls_spiio_priv* priv, u32 bit) static int ls_spiio_active_cs(struct ls_spiio_priv* priv, int cs) { int active = 0; + unsigned int cr1_reg; - if (!priv->cs_gpios) - ls_spiio_write_reg(priv, REG_CR1, BIT_CR1_SSREV | BIT_CR1_SPE); - else + cr1_reg = ls_spiio_read_reg(priv, REG_CR1); + cr1_reg &= 0x1ff; + cr1_reg |= BIT_CR1_SSREV | BIT_CR1_SPE; + + ls_spiio_write_reg(priv, REG_CR1, cr1_reg); + if (priv->cs_gpios) gpiod_set_raw_value(priv->cs_gpios[cs], active); return 0; @@ -167,12 +176,19 @@ static int ls_spiio_clear_cs(struct ls_spiio_priv* priv) struct spi_master* master = priv->master; int clear = 1; int i; + unsigned int cr1_reg; - if (!priv->cs_gpios) - ls_spiio_write_reg(priv, REG_CR1, BIT_CR1_SSREV); - else + cr1_reg = ls_spiio_read_reg(priv, REG_CR1); + cr1_reg &= 0x1ff; + cr1_reg |= BIT_CR1_SPE; + cr1_reg ^= BIT_CR1_SPE; + + ls_spiio_write_reg(priv, REG_CR1, cr1_reg); + + if (priv->cs_gpios) { for (i = 0; i < master->num_chipselect; i++) gpiod_set_raw_value(priv->cs_gpios[i], clear); + } return 0; } @@ -360,6 +376,39 @@ static int __spi_setup(struct spi_device *spi) return 0; } +static void __spi_work(struct work_struct *work) +{ + struct ls_spiio_priv* priv = container_of(work, struct ls_spiio_priv, work); + + spin_lock(&priv->lock); + while (!list_empty(&priv->xfer_queue)) { + struct spi_message *m; + struct spi_device *spi; + struct spi_transfer *t = NULL; + + m = container_of(priv->xfer_queue.next, struct spi_message, queue); + + list_del_init(&m->queue); + spin_unlock(&priv->lock); + spi = m->spi; + + ls_spiio_active_cs(priv, spi->chip_select); + list_for_each_entry(t, &m->transfers, transfer_list) { + /*setup spi clock*/ + ls_spiio_set_clk_timing(priv, t->speed_hz, spi->mode); + m->actual_length += ls_spiio_xfer(priv, + (u8*)t->tx_buf, (u8*)t->rx_buf, t->len); + + spi_delay_exec(&t->delay, t); + } + ls_spiio_clear_cs(priv); + + m->complete(m->context); + spin_lock(&priv->lock); + } + spin_unlock(&priv->lock); +} + static int __spi_transfer(struct spi_device *spi, struct spi_message *m) { struct ls_spiio_priv* priv = spi_master_get_devdata(spi->master); @@ -367,16 +416,34 @@ static int __spi_transfer(struct spi_device *spi, struct spi_message *m) m->actual_length = 0; m->status = 0; - ls_spiio_active_cs(priv, spi->chip_select); - list_for_each_entry(t, &m->transfers, transfer_list) { - /*setup spi clock*/ - ls_spiio_set_clk_timing(priv, t->speed_hz, spi->mode); - m->actual_length += ls_spiio_xfer(priv, - (u8*)t->tx_buf, (u8*)t->rx_buf, t->len); - } - ls_spiio_clear_cs(priv); + if (list_empty(&m->transfers) || !m->complete) + return -EINVAL; - m->complete(m->context); + list_for_each_entry(t, &m->transfers, transfer_list) { + + if (t->tx_buf == NULL && t->rx_buf == NULL && t->len) { + dev_err(&spi->dev, + "message rejected : " + "invalid transfer data buffers\n"); + goto msg_rejected; + } + + /*other things not check*/ + + } + + spin_lock(&priv->lock); + list_add_tail(&m->queue, &priv->xfer_queue); + queue_work(priv->work_queue, &priv->work); + spin_unlock(&priv->lock); + + return 0; +msg_rejected: + + m->status = -EINVAL; + if (m->complete) + m->complete(m->context); + return -EINVAL; return 0; } @@ -440,6 +507,11 @@ static int drv_ls_spiio_probe(struct platform_device *pdev) ls_spiio_init(priv); + priv->work_queue = create_singlethread_workqueue(pdev->name); + INIT_WORK(&priv->work, __spi_work); + INIT_LIST_HEAD(&priv->xfer_queue); + spin_lock_init(&priv->lock); + ret = spi_register_master(master); if (ret) { diff --git a/drivers/spi/spi-ls.c b/drivers/spi/spi-ls.c index d3163b93d..b788827b9 100644 --- a/drivers/spi/spi-ls.c +++ b/drivers/spi/spi-ls.c @@ -266,6 +266,8 @@ static void ls_spi_work(struct work_struct *work) if (t->len) m->actual_length += ls_spi_write_read(spi, t); + + spi_delay_exec(&t->delay, t); } set_cs(ls_spi, spi, 1); diff --git a/drivers/thermal/ls2k_thermal.c b/drivers/thermal/ls2k_thermal.c index c26fee5db..d6ed154a3 100644 --- a/drivers/thermal/ls2k_thermal.c +++ b/drivers/thermal/ls2k_thermal.c @@ -16,6 +16,7 @@ #define LS2K_TSENSOR_CTRL_LO (0x8) #define LS2K_TSENSOR_STATUS (0x10) #define LS2K_TSENSOR_OUT (0x14) +#define LS2K_TSENSOR_CFG (0x18) #define LS2K_MIN_TEMP -40 #define LS2K_MAX_TEMP 125 @@ -77,14 +78,65 @@ static int ls2k_tsensor_set(struct ls2k_thermal_data *data, return 0; } +static int warning_2k300_old_fuse_tip; + static int ls2k_thermal_get_temp(void *__data, int *temp) { struct ls2k_thermal_data *data = __data; u32 reg_val; + int temp_val; + int old_fuse_2k300; reg_val = readl(data->regs + LS2K_TSENSOR_OUT); - if (data->cpu_type == ID_2K300) - *temp = ((reg_val & 0x7ff) * 569) - 394700; + if (data->cpu_type == ID_2K300) { + int version; + int val_fix; + writel(0xff03, data->regs + LS2K_TSENSOR_CFG); // 0x18 + reg_val = readl(data->regs + LS2K_TSENSOR_OUT); + reg_val &= 0x7ff; + + version = readl(data->regs + 0x2AF0); // 0x16003ff0 + version >>= 4; + version &= 1; + + if(version) + val_fix = readl(data->regs + 0x2AF4) & 0xffff; // 0x16003ff4 + else + val_fix = (readl(data->regs + 0x2AF0) >> 20) & 0xffff; // 0x16003ff0 + + if (val_fix & 0x8000) + val_fix = -(val_fix & 0x7fff); + else + val_fix = val_fix & 0x7fff; + + reg_val += val_fix; + temp_val = reg_val * 570 - 394700; + /* + * fix old 2k300 use old fuse that will let kernel crash critical temperature + */ + old_fuse_2k300 = 0; + reg_val = temp_val; + if (temp_val < -55000) { + pr_info("look temp_val is %d\n", temp_val); + old_fuse_2k300 = 1; + temp_val = -55000; + } + else if (temp_val > 125000) { + pr_info("look temp_val is %d\n", temp_val); + old_fuse_2k300 = 1; + temp_val = 125000; + } + if (old_fuse_2k300) + { + if (!warning_2k300_old_fuse_tip) { + pr_err("%s this 2k300 use old fuse so thermal not right!(%d)\n", __func__, reg_val); + warning_2k300_old_fuse_tip = 1; + } + reg_val = readl(data->regs + LS2K_TSENSOR_OUT); + temp_val = ((reg_val & 0x7ff) * 569) - 394700; + } + *temp = temp_val; + } else *temp = ((reg_val & 0xff) - 100) * 1000; diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index e714a1c23..8faba10c3 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -517,6 +517,23 @@ struct dwc2_core_params { bool change_speed_quirk; +/* + * DWC2_LOONGSON_FIX_NONE: + * Original. Some USB devices cannot initialize when first powered up. + * However, network devices do not experience packet loss. + * Therefore, if we confirm the use of a USB-network device, then select this configuration. + * + * DWC2_LOONGSON_FIX_DMA: + * Used for most devices, such as USB storage and USB touchscreens. + * However, network devices may experience packet loss. + * + * DWC2_LOONGSON_FIX_DELAY: + * Deprecated. It causes USB to operate slowly. + * However, the 4G model can still dial up. Maybe optimize later. + * + * DWC2_LOONGSON_FIX_DELAY_FULL: + * Deprecated. +*/ #define DWC2_LOONGSON_FIX_NONE 0 #define DWC2_LOONGSON_FIX_DMA 1 #define DWC2_LOONGSON_FIX_DELAY 2 diff --git a/drivers/usb/serial/ch341.c b/drivers/usb/serial/ch341.c index 28deaaec5..36a20ce16 100644 --- a/drivers/usb/serial/ch341.c +++ b/drivers/usb/serial/ch341.c @@ -1,769 +1,461 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0+ /* - * Copyright 2007, Frank A Kingswood - * Copyright 2007, Werner Cornelius - * Copyright 2009, Boris Hajduk + * CH340/CH341 USB to serial port driver * - * ch341.c implements a serial port driver for the Winchiphead CH341. + * Copyright (C) 2024 Nanjing Qinheng Microelectronics Co., Ltd. + * Web: http://wch.cn + * Author: WCH * - * 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 program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * System require: + * Kernel version beyond 3.2.x + * + * Update Log: + * V1.0 - initial version + * V1.1 - add support high baudrates, modem signal, flow control, etc. + * V1.2 - fixe baud rate 0 bugs and add some debug messages + * V1.3 - special setting on usb packet upload timeout + * V1.4 - change read urb length to ep size + * V1.5 - fix hardware flowcontrol bugs + * V1.6 - add support for application to get uart state + * - remove tty throttle methods + * - submit urbs when uart open while not probe + * - fix tty kref errors in dtr_rts + * - add support for kernel version beyond 5.14.x + * - fix data analysis in status ep callback + * V1.7 - add support for kernel version beyond 6.3.x + * V1.8 - add support for kernel version beyond 6.5.x */ +#define DEBUG +#define VERBOSE_DEBUG + +#undef DEBUG +#undef VERBOSE_DEBUG + +#include +#include +#include #include -#include +#include #include -#include -#include -#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#define DEFAULT_BAUD_RATE 9600 -#define DEFAULT_TIMEOUT 1000 +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)) +#include +#endif -/* flags for IO-Bits */ -#define CH341_BIT_RTS (1 << 6) -#define CH341_BIT_DTR (1 << 5) +#include "ch341.h" -/******************************/ -/* interrupt pipe definitions */ -/******************************/ -/* always 4 interrupt bytes */ -/* first irq byte normally 0x08 */ -/* second irq byte base 0x7d + below */ -/* third irq byte base 0x94 + below */ -/* fourth irq byte normally 0xee */ +#define DRIVER_AUTHOR "WCH" +#define DRIVER_DESC "USB serial driver for ch340, ch341, etc." +#define VERSION_DESC "V1.8 On 2024.08" -/* second interrupt byte */ -#define CH341_MULT_STAT 0x04 /* multiple status since last interrupt event */ +static struct usb_driver ch341_driver; +static struct tty_driver *ch341_tty_driver; -/* status returned in third interrupt answer byte, inverted in data - from irq */ -#define CH341_BIT_CTS 0x01 -#define CH341_BIT_DSR 0x02 -#define CH341_BIT_RI 0x04 -#define CH341_BIT_DCD 0x08 -#define CH341_BITS_MODEM_STAT 0x0f /* all bits */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)) +static DEFINE_IDR(ch341_minors); +#else +static struct ch341 *ch341_table[CH341_TTY_MINORS]; +#endif +static DEFINE_MUTEX(ch341_minors_lock); -/* Break support - the information used to implement this was gleaned from - * the Net/FreeBSD uchcom.c driver by Takanori Watanabe. Domo arigato. +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)) +static void ch341_tty_set_termios(struct tty_struct *tty, + const struct ktermios *termios_old); +#else +static void ch341_tty_set_termios(struct tty_struct *tty, + struct ktermios *termios_old); +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)) +/* + * Look up an ch341 structure by minor. If found and not disconnected, + * increment its refcount and return it with its mutex held. */ - -#define CH341_REQ_READ_VERSION 0x5F -#define CH341_REQ_WRITE_REG 0x9A -#define CH341_REQ_READ_REG 0x95 -#define CH341_REQ_SERIAL_INIT 0xA1 -#define CH341_REQ_MODEM_CTRL 0xA4 - -#define CH341_REG_BREAK 0x05 -#define CH341_REG_PRESCALER 0x12 -#define CH341_REG_DIVISOR 0x13 -#define CH341_REG_LCR 0x18 -#define CH341_REG_LCR2 0x25 - -#define CH341_NBREAK_BITS 0x01 - -#define CH341_LCR_ENABLE_RX 0x80 -#define CH341_LCR_ENABLE_TX 0x40 -#define CH341_LCR_MARK_SPACE 0x20 -#define CH341_LCR_PAR_EVEN 0x10 -#define CH341_LCR_ENABLE_PAR 0x08 -#define CH341_LCR_STOP_BITS_2 0x04 -#define CH341_LCR_CS8 0x03 -#define CH341_LCR_CS7 0x02 -#define CH341_LCR_CS6 0x01 -#define CH341_LCR_CS5 0x00 - -#define CH341_QUIRK_LIMITED_PRESCALER BIT(0) -#define CH341_QUIRK_SIMULATE_BREAK BIT(1) - -static const struct usb_device_id id_table[] = { - { USB_DEVICE(0x1a86, 0x5512) }, - { USB_DEVICE(0x1a86, 0x5523) }, - { USB_DEVICE(0x1a86, 0x7522) }, - { USB_DEVICE(0x1a86, 0x7523) }, - { USB_DEVICE(0x4348, 0x5523) }, - { }, -}; -MODULE_DEVICE_TABLE(usb, id_table); - -struct ch341_private { - spinlock_t lock; /* access lock */ - unsigned baud_rate; /* set baud rate */ - u8 mcr; - u8 msr; - u8 lcr; - unsigned long quirks; - unsigned long break_end; -}; - -static void ch341_set_termios(struct tty_struct *tty, - struct usb_serial_port *port, - struct ktermios *old_termios); - -static int ch341_control_out(struct usb_device *dev, u8 request, - u16 value, u16 index) +static struct ch341 *ch341_get_by_index(unsigned int minor) { - int r; + struct ch341 *ch341; - dev_dbg(&dev->dev, "%s - (%02x,%04x,%04x)\n", __func__, - request, value, index); - - r = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), request, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, - value, index, NULL, 0, DEFAULT_TIMEOUT); - if (r < 0) - dev_err(&dev->dev, "failed to send control message: %d\n", r); - - return r; + mutex_lock(&ch341_minors_lock); + ch341 = idr_find(&ch341_minors, minor); + if (ch341) { + mutex_lock(&ch341->mutex); + if (ch341->disconnected) { + mutex_unlock(&ch341->mutex); + ch341 = NULL; + } else { + tty_port_get(&ch341->port); + mutex_unlock(&ch341->mutex); + } + } + mutex_unlock(&ch341_minors_lock); + return ch341; } -static int ch341_control_in(struct usb_device *dev, - u8 request, u16 value, u16 index, - char *buf, unsigned bufsize) +/* + * Try to find an available minor number and if found, + * associate it with 'ch341'. + */ +static int ch341_alloc_minor(struct ch341 *ch341) { - int r; + int minor; - dev_dbg(&dev->dev, "%s - (%02x,%04x,%04x,%u)\n", __func__, - request, value, index, bufsize); + mutex_lock(&ch341_minors_lock); + minor = idr_alloc(&ch341_minors, ch341, 0, CH341_TTY_MINORS, + GFP_KERNEL); + mutex_unlock(&ch341_minors_lock); - r = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), request, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - value, index, buf, bufsize, DEFAULT_TIMEOUT); - if (r < (int)bufsize) { - if (r >= 0) { - dev_err(&dev->dev, - "short control message received (%d < %u)\n", - r, bufsize); - r = -EIO; + return minor; +} + +/* Release the minor number associated with 'ch341'. */ +static void ch341_release_minor(struct ch341 *ch341) +{ + mutex_lock(&ch341_minors_lock); + idr_remove(&ch341_minors, ch341->minor); + mutex_unlock(&ch341_minors_lock); +} + +#else + +/* + * Look up an CH341 structure by index. If found and not disconnected, + * increment its refcount and return it with its mutex held. + */ +static struct ch341 *ch341_get_by_index(unsigned int index) +{ + struct ch341 *ch341; + + mutex_lock(&ch341_minors_lock); + ch341 = ch341_table[index]; + if (ch341) { + mutex_lock(&ch341->mutex); + if (ch341->disconnected) { + mutex_unlock(&ch341->mutex); + ch341 = NULL; + } else { + tty_port_get(&ch341->port); + mutex_unlock(&ch341->mutex); } - - dev_err(&dev->dev, "failed to receive control message: %d\n", - r); - return r; } + mutex_unlock(&ch341_minors_lock); + return ch341; +} + +/* + * Try to find an available minor number and if found, + * associate it with 'ch341'. + */ +static int ch341_alloc_minor(struct ch341 *ch341) +{ + int minor; + + mutex_lock(&ch341_minors_lock); + for (minor = 0; minor < CH341_TTY_MINORS; minor++) { + if (!ch341_table[minor]) { + ch341_table[minor] = ch341; + break; + } + } + mutex_unlock(&ch341_minors_lock); + + return minor; +} + +/* Release the minor number associated with 'ch341'. */ +static void ch341_release_minor(struct ch341 *ch341) +{ + mutex_lock(&ch341_minors_lock); + ch341_table[ch341->minor] = NULL; + mutex_unlock(&ch341_minors_lock); +} + +#endif +static int ch341_control_out(struct ch341 *ch341, u8 request, u16 value, + u16 index) +{ + int retval; + + retval = usb_autopm_get_interface(ch341->data); + if (retval) + return retval; + + retval = usb_control_msg( + ch341->dev, usb_sndctrlpipe(ch341->dev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, value, + index, NULL, 0, DEFAULT_TIMEOUT); + + usb_autopm_put_interface(ch341->data); + + return retval < 0 ? retval : 0; +} + +static int ch341_control_in(struct ch341 *ch341, u8 request, u16 value, + u16 index, char *buf, unsigned bufsize) +{ + int retval; + + retval = usb_autopm_get_interface(ch341->data); + if (retval) + return retval; + + retval = usb_control_msg( + ch341->dev, usb_rcvctrlpipe(ch341->dev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, value, + index, buf, bufsize, DEFAULT_TIMEOUT); + + usb_autopm_put_interface(ch341->data); + + return retval; +} + +static inline int ch341_set_control(struct ch341 *ch341, int control) +{ + u16 value = 0; + + value |= (u8)~control; + + return ch341_control_out(ch341, CMD_C2, value, 0x0000); +} + +static inline int ch341_set_line(struct ch341 *ch341, + struct usb_ch341_line_coding *line) +{ return 0; } -#define CH341_CLKRATE 48000000 -#define CH341_CLK_DIV(ps, fact) (1 << (12 - 3 * (ps) - (fact))) -#define CH341_MIN_RATE(ps) (CH341_CLKRATE / (CH341_CLK_DIV((ps), 1) * 512)) - -static const speed_t ch341_min_rates[] = { - CH341_MIN_RATE(0), - CH341_MIN_RATE(1), - CH341_MIN_RATE(2), - CH341_MIN_RATE(3), -}; - -/* Supported range is 46 to 3000000 bps. */ -#define CH341_MIN_BPS DIV_ROUND_UP(CH341_CLKRATE, CH341_CLK_DIV(0, 0) * 256) -#define CH341_MAX_BPS (CH341_CLKRATE / (CH341_CLK_DIV(3, 0) * 2)) - -/* - * The device line speed is given by the following equation: - * - * baudrate = 48000000 / (2^(12 - 3 * ps - fact) * div), where - * - * 0 <= ps <= 3, - * 0 <= fact <= 1, - * 2 <= div <= 256 if fact = 0, or - * 9 <= div <= 256 if fact = 1 - */ -static int ch341_get_divisor(struct ch341_private *priv, speed_t speed) +static int ch341_get_status(struct ch341 *ch341) { - unsigned int fact, div, clk_div; - bool force_fact0 = false; - int ps; - - /* - * Clamp to supported range, this makes the (ps < 0) and (div < 2) - * sanity checks below redundant. - */ - speed = clamp_val(speed, CH341_MIN_BPS, CH341_MAX_BPS); - - /* - * Start with highest possible base clock (fact = 1) that will give a - * divisor strictly less than 512. - */ - fact = 1; - for (ps = 3; ps >= 0; ps--) { - if (speed > ch341_min_rates[ps]) - break; - } - - if (ps < 0) - return -EINVAL; - - /* Determine corresponding divisor, rounding down. */ - clk_div = CH341_CLK_DIV(ps, fact); - div = CH341_CLKRATE / (clk_div * speed); - - /* Some devices require a lower base clock if ps < 3. */ - if (ps < 3 && (priv->quirks & CH341_QUIRK_LIMITED_PRESCALER)) - force_fact0 = true; - - /* Halve base clock (fact = 0) if required. */ - if (div < 9 || div > 255 || force_fact0) { - div /= 2; - clk_div *= 2; - fact = 0; - } - - if (div < 2) - return -EINVAL; - - /* - * Pick next divisor if resulting rate is closer to the requested one, - * scale up to avoid rounding errors on low rates. - */ - if (16 * CH341_CLKRATE / (clk_div * div) - 16 * speed >= - 16 * speed - 16 * CH341_CLKRATE / (clk_div * (div + 1))) - div++; - - /* - * Prefer lower base clock (fact = 0) if even divisor. - * - * Note that this makes the receiver more tolerant to errors. - */ - if (fact == 1 && div % 2 == 0) { - div /= 2; - fact = 0; - } - - return (0x100 - div) << 8 | fact << 2 | ps; -} - -static int ch341_set_baudrate_lcr(struct usb_device *dev, - struct ch341_private *priv, - speed_t baud_rate, u8 lcr) -{ - int val; - int r; - - if (!baud_rate) - return -EINVAL; - - val = ch341_get_divisor(priv, baud_rate); - if (val < 0) - return -EINVAL; - - /* - * CH341A buffers data until a full endpoint-size packet (32 bytes) - * has been received unless bit 7 is set. - */ - val |= BIT(7); - - r = ch341_control_out(dev, CH341_REQ_WRITE_REG, - CH341_REG_DIVISOR << 8 | CH341_REG_PRESCALER, - val); - if (r) - return r; - - /* - * Chip versions before version 0x30 as read using - * CH341_REQ_READ_VERSION used separate registers for line control - * (stop bits, parity and word length). Version 0x30 and above use - * CH341_REG_LCR only and CH341_REG_LCR2 is always set to zero. - */ - r = ch341_control_out(dev, CH341_REQ_WRITE_REG, - CH341_REG_LCR2 << 8 | CH341_REG_LCR, lcr); - if (r) - return r; - - return r; -} - -static int ch341_set_handshake(struct usb_device *dev, u8 control) -{ - return ch341_control_out(dev, CH341_REQ_MODEM_CTRL, ~control, 0); -} - -static int ch341_get_status(struct usb_device *dev, struct ch341_private *priv) -{ - const unsigned int size = 2; char *buffer; - int r; + int retval; + const unsigned size = 2; unsigned long flags; buffer = kmalloc(size, GFP_KERNEL); if (!buffer) return -ENOMEM; - r = ch341_control_in(dev, CH341_REQ_READ_REG, 0x0706, 0, buffer, size); - if (r < 0) + retval = ch341_control_in(ch341, CMD_R, 0x0706, 0, buffer, size); + if (retval < 0) goto out; - spin_lock_irqsave(&priv->lock, flags); - priv->msr = (~(*buffer)) & CH341_BITS_MODEM_STAT; - spin_unlock_irqrestore(&priv->lock, flags); + if (retval > 0) { + spin_lock_irqsave(&ch341->read_lock, flags); + ch341->ctrlin = (~(*buffer)) & CH341_CTI_ST; + spin_unlock_irqrestore(&ch341->read_lock, flags); + } else + retval = -EPROTO; -out: kfree(buffer); - return r; -} - -/* -------------------------------------------------------------------------- */ - -static int ch341_configure(struct usb_device *dev, struct ch341_private *priv) -{ - const unsigned int size = 2; - char *buffer; - int r; - - buffer = kmalloc(size, GFP_KERNEL); - if (!buffer) - return -ENOMEM; - - /* expect two bytes 0x27 0x00 */ - r = ch341_control_in(dev, CH341_REQ_READ_VERSION, 0, 0, buffer, size); - if (r < 0) - goto out; - dev_dbg(&dev->dev, "Chip version: 0x%02x\n", buffer[0]); - - r = ch341_control_out(dev, CH341_REQ_SERIAL_INIT, 0, 0); - if (r < 0) - goto out; - - r = ch341_set_baudrate_lcr(dev, priv, priv->baud_rate, priv->lcr); - if (r < 0) - goto out; - - r = ch341_set_handshake(dev, priv->mcr); - -out: kfree(buffer); - return r; -} - -static int ch341_detect_quirks(struct usb_serial_port *port) -{ - struct ch341_private *priv = usb_get_serial_port_data(port); - struct usb_device *udev = port->serial->dev; - const unsigned int size = 2; - unsigned long quirks = 0; - char *buffer; - int r; - - buffer = kmalloc(size, GFP_KERNEL); - if (!buffer) - return -ENOMEM; - - /* - * A subset of CH34x devices does not support all features. The - * prescaler is limited and there is no support for sending a RS232 - * break condition. A read failure when trying to set up the latter is - * used to detect these devices. - */ - r = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), CH341_REQ_READ_REG, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - CH341_REG_BREAK, 0, buffer, size, DEFAULT_TIMEOUT); - if (r == -EPIPE) { - dev_info(&port->dev, "break control not supported, using simulated break\n"); - quirks = CH341_QUIRK_LIMITED_PRESCALER | CH341_QUIRK_SIMULATE_BREAK; - r = 0; - goto out; - } - - if (r != size) { - if (r >= 0) - r = -EIO; - dev_err(&port->dev, "failed to read break control: %d\n", r); - goto out; - } - - r = 0; out: kfree(buffer); - - if (quirks) { - dev_dbg(&port->dev, "enabling quirk flags: 0x%02lx\n", quirks); - priv->quirks |= quirks; - } - - return r; + return retval; } -static int ch341_port_probe(struct usb_serial_port *port) +static int ch341_configure(struct ch341 *ch341) { - struct ch341_private *priv; + char *buffer; int r; + const unsigned size = 2; - priv = kzalloc(sizeof(struct ch341_private), GFP_KERNEL); - if (!priv) + buffer = kmalloc(size, GFP_KERNEL); + if (!buffer) return -ENOMEM; - spin_lock_init(&priv->lock); - priv->baud_rate = DEFAULT_BAUD_RATE; - /* - * Some CH340 devices appear unable to change the initial LCR - * settings, so set a sane 8N1 default. - */ - priv->lcr = CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX | CH341_LCR_CS8; - - r = ch341_configure(port->serial->dev, priv); + r = ch341_control_in(ch341, CMD_C3, 0, 0, buffer, size); if (r < 0) - goto error; - - usb_set_serial_port_data(port, priv); - - r = ch341_detect_quirks(port); + goto out; + r = ch341_control_out(ch341, CMD_C1, 0, 0); if (r < 0) - goto error; + goto out; + r = ch341_control_out(ch341, CMD_W, 0x1312, 0xd982); + if (r < 0) + goto out; + r = ch341_control_out(ch341, CMD_W, 0x0f2c, 0x0007); + if (r < 0) + goto out; + r = ch341_control_in(ch341, CMD_R, 0x2518, 0, buffer, size); + if (r < 0) + goto out; + r = ch341_get_status(ch341); + if (r < 0) + goto out; + r = ch341_control_out(ch341, CMD_W, 0x2727, 0); + if (r < 0) + goto out; - return 0; - -error: kfree(priv); +out: + kfree(buffer); return r; } -static int ch341_port_remove(struct usb_serial_port *port) -{ - struct ch341_private *priv; - - priv = usb_get_serial_port_data(port); - kfree(priv); - - return 0; -} - -static int ch341_carrier_raised(struct usb_serial_port *port) -{ - struct ch341_private *priv = usb_get_serial_port_data(port); - if (priv->msr & CH341_BIT_DCD) - return 1; - return 0; -} - -static void ch341_dtr_rts(struct usb_serial_port *port, int on) -{ - struct ch341_private *priv = usb_get_serial_port_data(port); - unsigned long flags; - - /* drop DTR and RTS */ - spin_lock_irqsave(&priv->lock, flags); - if (on) - priv->mcr |= CH341_BIT_RTS | CH341_BIT_DTR; - else - priv->mcr &= ~(CH341_BIT_RTS | CH341_BIT_DTR); - spin_unlock_irqrestore(&priv->lock, flags); - ch341_set_handshake(port->serial->dev, priv->mcr); -} - -static void ch341_close(struct usb_serial_port *port) -{ - usb_serial_generic_close(port); - usb_kill_urb(port->interrupt_in_urb); -} - - -/* open this device, set default parameters */ -static int ch341_open(struct tty_struct *tty, struct usb_serial_port *port) -{ - struct ch341_private *priv = usb_get_serial_port_data(port); - int r; - - if (tty) - ch341_set_termios(tty, port, NULL); - - dev_dbg(&port->dev, "%s - submitting interrupt urb\n", __func__); - r = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); - if (r) { - dev_err(&port->dev, "%s - failed to submit interrupt urb: %d\n", - __func__, r); - return r; - } - - r = ch341_get_status(port->serial->dev, priv); - if (r < 0) { - dev_err(&port->dev, "failed to read modem status: %d\n", r); - goto err_kill_interrupt_urb; - } - - r = usb_serial_generic_open(tty, port); - if (r) - goto err_kill_interrupt_urb; - - return 0; - -err_kill_interrupt_urb: - usb_kill_urb(port->interrupt_in_urb); - - return r; -} - -/* Old_termios contains the original termios settings and - * tty->termios contains the new setting to be used. - */ -static void ch341_set_termios(struct tty_struct *tty, - struct usb_serial_port *port, struct ktermios *old_termios) -{ - struct ch341_private *priv = usb_get_serial_port_data(port); - unsigned baud_rate; - unsigned long flags; - u8 lcr; - int r; - - /* redundant changes may cause the chip to lose bytes */ - if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios)) - return; - - baud_rate = tty_get_baud_rate(tty); - - lcr = CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX; - - switch (C_CSIZE(tty)) { - case CS5: - lcr |= CH341_LCR_CS5; - break; - case CS6: - lcr |= CH341_LCR_CS6; - break; - case CS7: - lcr |= CH341_LCR_CS7; - break; - case CS8: - lcr |= CH341_LCR_CS8; - break; - } - - if (C_PARENB(tty)) { - lcr |= CH341_LCR_ENABLE_PAR; - if (C_PARODD(tty) == 0) - lcr |= CH341_LCR_PAR_EVEN; - if (C_CMSPAR(tty)) - lcr |= CH341_LCR_MARK_SPACE; - } - - if (C_CSTOPB(tty)) - lcr |= CH341_LCR_STOP_BITS_2; - - if (baud_rate) { - priv->baud_rate = baud_rate; - - r = ch341_set_baudrate_lcr(port->serial->dev, priv, - priv->baud_rate, lcr); - if (r < 0 && old_termios) { - priv->baud_rate = tty_termios_baud_rate(old_termios); - tty_termios_copy_hw(&tty->termios, old_termios); - } else if (r == 0) { - priv->lcr = lcr; - } - } - - spin_lock_irqsave(&priv->lock, flags); - if (C_BAUD(tty) == B0) - priv->mcr &= ~(CH341_BIT_DTR | CH341_BIT_RTS); - else if (old_termios && (old_termios->c_cflag & CBAUD) == B0) - priv->mcr |= (CH341_BIT_DTR | CH341_BIT_RTS); - spin_unlock_irqrestore(&priv->lock, flags); - - ch341_set_handshake(port->serial->dev, priv->mcr); -} - /* - * A subset of all CH34x devices don't support a real break condition and - * reading CH341_REG_BREAK fails (see also ch341_detect_quirks). This function - * simulates a break condition by lowering the baud rate to the minimum - * supported by the hardware upon enabling the break condition and sending - * a NUL byte. - * - * Incoming data is corrupted while the break condition is being simulated. - * - * Normally the duration of the break condition can be controlled individually - * by userspace using TIOCSBRK and TIOCCBRK or by passing an argument to - * TCSBRKP. Due to how the simulation is implemented the duration can't be - * controlled. The duration is always about (1s / 46bd * 9bit) = 196ms. + * Write buffer management. + * All of these assume proper locks taken by the caller. */ -static void ch341_simulate_break(struct tty_struct *tty, int break_state) +static int ch341_wb_alloc(struct ch341 *ch341) { - struct usb_serial_port *port = tty->driver_data; - struct ch341_private *priv = usb_get_serial_port_data(port); - unsigned long now, delay; - int r; + int i, wbn; + struct ch341_wb *wb; - if (break_state != 0) { - dev_dbg(&port->dev, "enter break state requested\n"); - - r = ch341_set_baudrate_lcr(port->serial->dev, priv, - CH341_MIN_BPS, - CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX | CH341_LCR_CS8); - if (r < 0) { - dev_err(&port->dev, - "failed to change baud rate to %u: %d\n", - CH341_MIN_BPS, r); - goto restore; + wbn = 0; + i = 0; + for (;;) { + wb = &ch341->wb[wbn]; + if (!wb->use) { + wb->use = 1; + return wbn; } - - r = tty_put_char(tty, '\0'); - if (r < 0) { - dev_err(&port->dev, - "failed to write NUL byte for simulated break condition: %d\n", - r); - goto restore; - } - - /* - * Compute expected transmission duration including safety - * margin. The original baud rate is only restored after the - * computed point in time. - * - * 11 bits = 1 start, 8 data, 1 stop, 1 margin - */ - priv->break_end = jiffies + (11 * HZ / CH341_MIN_BPS); - - return; + wbn = (wbn + 1) % CH341_NW; + if (++i >= CH341_NW) + return -1; } - - dev_dbg(&port->dev, "leave break state requested\n"); - - now = jiffies; - - if (time_before(now, priv->break_end)) { - /* Wait until NUL byte is written */ - delay = priv->break_end - now; - dev_dbg(&port->dev, - "wait %d ms while transmitting NUL byte at %u baud\n", - jiffies_to_msecs(delay), CH341_MIN_BPS); - schedule_timeout_interruptible(delay); - } - -restore: - /* Restore original baud rate */ - r = ch341_set_baudrate_lcr(port->serial->dev, priv, priv->baud_rate, - priv->lcr); - if (r < 0) - dev_err(&port->dev, - "restoring original baud rate of %u failed: %d\n", - priv->baud_rate, r); } -static void ch341_break_ctl(struct tty_struct *tty, int break_state) +static int ch341_wb_is_avail(struct ch341 *ch341) { - const uint16_t ch341_break_reg = - ((uint16_t) CH341_REG_LCR << 8) | CH341_REG_BREAK; - struct usb_serial_port *port = tty->driver_data; - struct ch341_private *priv = usb_get_serial_port_data(port); - int r; - uint16_t reg_contents; - uint8_t *break_reg; - - if (priv->quirks & CH341_QUIRK_SIMULATE_BREAK) { - ch341_simulate_break(tty, break_state); - return; - } - - break_reg = kmalloc(2, GFP_KERNEL); - if (!break_reg) - return; - - r = ch341_control_in(port->serial->dev, CH341_REQ_READ_REG, - ch341_break_reg, 0, break_reg, 2); - if (r < 0) { - dev_err(&port->dev, "%s - USB control read error (%d)\n", - __func__, r); - goto out; - } - dev_dbg(&port->dev, "%s - initial ch341 break register contents - reg1: %x, reg2: %x\n", - __func__, break_reg[0], break_reg[1]); - if (break_state != 0) { - dev_dbg(&port->dev, "%s - Enter break state requested\n", __func__); - break_reg[0] &= ~CH341_NBREAK_BITS; - break_reg[1] &= ~CH341_LCR_ENABLE_TX; - } else { - dev_dbg(&port->dev, "%s - Leave break state requested\n", __func__); - break_reg[0] |= CH341_NBREAK_BITS; - break_reg[1] |= CH341_LCR_ENABLE_TX; - } - dev_dbg(&port->dev, "%s - New ch341 break register contents - reg1: %x, reg2: %x\n", - __func__, break_reg[0], break_reg[1]); - reg_contents = get_unaligned_le16(break_reg); - r = ch341_control_out(port->serial->dev, CH341_REQ_WRITE_REG, - ch341_break_reg, reg_contents); - if (r < 0) - dev_err(&port->dev, "%s - USB control write error (%d)\n", - __func__, r); -out: - kfree(break_reg); -} - -static int ch341_tiocmset(struct tty_struct *tty, - unsigned int set, unsigned int clear) -{ - struct usb_serial_port *port = tty->driver_data; - struct ch341_private *priv = usb_get_serial_port_data(port); + int i, n; unsigned long flags; - u8 control; - spin_lock_irqsave(&priv->lock, flags); - if (set & TIOCM_RTS) - priv->mcr |= CH341_BIT_RTS; - if (set & TIOCM_DTR) - priv->mcr |= CH341_BIT_DTR; - if (clear & TIOCM_RTS) - priv->mcr &= ~CH341_BIT_RTS; - if (clear & TIOCM_DTR) - priv->mcr &= ~CH341_BIT_DTR; - control = priv->mcr; - spin_unlock_irqrestore(&priv->lock, flags); - - return ch341_set_handshake(port->serial->dev, control); + n = CH341_NW; + spin_lock_irqsave(&ch341->write_lock, flags); + for (i = 0; i < CH341_NW; i++) + n -= ch341->wb[i].use; + spin_unlock_irqrestore(&ch341->write_lock, flags); + return n; } -static void ch341_update_status(struct usb_serial_port *port, - unsigned char *data, size_t len) +static void ch341_write_done(struct ch341 *ch341, struct ch341_wb *wb) +{ + wb->use = 0; + ch341->transmitting--; + usb_autopm_put_interface_async(ch341->data); +} + +static int ch341_start_wb(struct ch341 *ch341, struct ch341_wb *wb) +{ + int rc; + + ch341->transmitting++; + + wb->urb->transfer_buffer = wb->buf; + wb->urb->transfer_dma = wb->dmah; + wb->urb->transfer_buffer_length = wb->len; + wb->urb->dev = ch341->dev; + + rc = usb_submit_urb(wb->urb, GFP_ATOMIC); + if (rc < 0) { + dev_err(&ch341->data->dev, + "%s - usb_submit_urb(write bulk) failed: %d\n", + __func__, rc); + ch341_write_done(ch341, wb); + } + return rc; +} + +static void ch341_update_status(struct ch341 *ch341, unsigned char *data, + size_t len) { - struct ch341_private *priv = usb_get_serial_port_data(port); - struct tty_struct *tty; unsigned long flags; u8 status; - u8 delta; + u8 difference; + u8 type = data[0]; + u8 handled = 0; if (len < 4) return; - status = ~data[2] & CH341_BITS_MODEM_STAT; + if (type & CH341_CTT_M) { + status = ~data[2] & CH341_CTI_ST; - spin_lock_irqsave(&priv->lock, flags); - delta = status ^ priv->msr; - priv->msr = status; - spin_unlock_irqrestore(&priv->lock, flags); - - if (data[1] & CH341_MULT_STAT) - dev_dbg(&port->dev, "%s - multiple status change\n", __func__); - - if (!delta) - return; - - if (delta & CH341_BIT_CTS) - port->icount.cts++; - if (delta & CH341_BIT_DSR) - port->icount.dsr++; - if (delta & CH341_BIT_RI) - port->icount.rng++; - if (delta & CH341_BIT_DCD) { - port->icount.dcd++; - tty = tty_port_tty_get(&port->port); - if (tty) { - usb_serial_handle_dcd_change(port, tty, - status & CH341_BIT_DCD); - tty_kref_put(tty); + if (!ch341->clocal && + (ch341->ctrlin & status & CH341_CTI_DC)) { +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)) + struct tty_struct *tty = + tty_port_tty_get(&ch341->port); + tty_hangup(tty); +#else + tty_port_tty_hangup(&ch341->port, false); +#endif } - } - wake_up_interruptible(&port->port.delta_msr_wait); + spin_lock_irqsave(&ch341->read_lock, flags); + difference = status ^ ch341->ctrlin; + ch341->ctrlin = status; + ch341->oldcount = ch341->iocount; + if (difference) { + if (difference & CH341_CTI_C) { + ch341->iocount.cts++; + } + if (difference & CH341_CTI_DS) { + ch341->iocount.dsr++; + } + if (difference & CH341_CTRL_RI) { + ch341->iocount.rng++; + } + if (difference & CH341_CTI_DC) { + ch341->iocount.dcd++; + } + spin_unlock_irqrestore(&ch341->read_lock, flags); + wake_up_interruptible(&ch341->wioctl); + } else + spin_unlock_irqrestore(&ch341->read_lock, flags); + handled = 1; + } + if (type & CH341_CTT_O) { + spin_lock_irqsave(&ch341->read_lock, flags); + ch341->oldcount = ch341->iocount; + ch341->iocount.overrun++; + spin_unlock_irqrestore(&ch341->read_lock, flags); + handled = 1; + } + if ((type & CH341_CTT_F) == CH341_CTT_F) { + spin_lock_irqsave(&ch341->read_lock, flags); + ch341->oldcount = ch341->iocount; + ch341->iocount.frame++; + spin_unlock_irqrestore(&ch341->read_lock, flags); + handled = 1; + } else if (type & CH341_CTT_P) { + spin_lock_irqsave(&ch341->read_lock, flags); + ch341->oldcount = ch341->iocount; + ch341->iocount.parity++; + spin_unlock_irqrestore(&ch341->read_lock, flags); + handled = 1; + } + if (!handled) + dev_dbg(&ch341->data->dev, + "%s - unknown status received:" + "len:%d, data0:0x%x, data1:0x%x\n", + __func__, (int)len, data[0], data[1]); } -static void ch341_read_int_callback(struct urb *urb) +static void ch341_ctrl_irq(struct urb *urb) { - struct usb_serial_port *port = urb->context; + struct ch341 *ch341 = urb->context; unsigned char *data = urb->transfer_buffer; unsigned int len = urb->actual_length; - int status; + int status = urb->status; + int retval; - switch (urb->status) { + switch (status) { case 0: /* success */ break; @@ -771,108 +463,1321 @@ static void ch341_read_int_callback(struct urb *urb) case -ENOENT: case -ESHUTDOWN: /* this urb is terminated, clean up */ - dev_dbg(&urb->dev->dev, "%s - urb shutting down: %d\n", - __func__, urb->status); + dev_dbg(&ch341->data->dev, + "%s - urb shutting down with status: %d\n", + __func__, status); return; default: - dev_dbg(&urb->dev->dev, "%s - nonzero urb status: %d\n", - __func__, urb->status); + dev_dbg(&ch341->data->dev, + "%s - nonzero urb status received: %d\n", __func__, + status); goto exit; } - usb_serial_debug_data(&port->dev, __func__, len, data); - ch341_update_status(port, data, len); + usb_mark_last_busy(ch341->dev); + ch341_update_status(ch341, data, len); exit: - status = usb_submit_urb(urb, GFP_ATOMIC); - if (status) { - dev_err(&urb->dev->dev, "%s - usb_submit_urb failed: %d\n", - __func__, status); - } + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval && retval != -EPERM) + dev_err(&ch341->data->dev, + "%s - usb_submit_urb failed: %d\n", __func__, + retval); } -static int ch341_tiocmget(struct tty_struct *tty) +static int ch341_submit_read_urb(struct ch341 *ch341, int index, + gfp_t mem_flags) { - struct usb_serial_port *port = tty->driver_data; - struct ch341_private *priv = usb_get_serial_port_data(port); + int res; + + if (!test_and_clear_bit(index, &ch341->read_urbs_free)) + return 0; + + res = usb_submit_urb(ch341->read_urbs[index], mem_flags); + if (res) { + if (res != -EPERM) { + dev_err(&ch341->data->dev, + "%s - usb_submit_urb failed: %d\n", + __func__, res); + } + set_bit(index, &ch341->read_urbs_free); + return res; + } + return 0; +} + +static int ch341_submit_read_urbs(struct ch341 *ch341, gfp_t mem_flags) +{ + int res; + int i; + + for (i = 0; i < ch341->rx_buflimit; ++i) { + res = ch341_submit_read_urb(ch341, i, mem_flags); + if (res) + return res; + } + return 0; +} + +static void ch341_process_read_urb(struct ch341 *ch341, struct urb *urb) +{ + if (!urb->actual_length) + return; + + ch341->iocount.rx += urb->actual_length; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0)) + tty_insert_flip_string(&ch341->port, urb->transfer_buffer, + urb->actual_length); + tty_flip_buffer_push(&ch341->port); +#else + struct tty_struct *tty = tty_port_tty_get(&ch341->port); + tty_insert_flip_string(tty, urb->transfer_buffer, + urb->actual_length); + tty_flip_buffer_push(tty); +#endif +} + +static void ch341_read_bulk_callback(struct urb *urb) +{ + struct ch341_rb *rb = urb->context; + struct ch341 *ch341 = rb->instance; + int status = urb->status; + + if (!ch341->dev) { + set_bit(rb->index, &ch341->read_urbs_free); + dev_dbg(&ch341->data->dev, "%s - disconnected\n", + __func__); + return; + } + if (status) { + set_bit(rb->index, &ch341->read_urbs_free); + dev_dbg(&ch341->data->dev, + "%s - non-zero urb status: %d\n", __func__, + status); + return; + } + usb_mark_last_busy(ch341->dev); + ch341_process_read_urb(ch341, urb); + set_bit(rb->index, &ch341->read_urbs_free); + ch341_submit_read_urb(ch341, rb->index, GFP_ATOMIC); +} + +/* data interface wrote those outgoing bytes */ +static void ch341_write_bulk(struct urb *urb) +{ + struct ch341_wb *wb = urb->context; + struct ch341 *ch341 = wb->instance; + unsigned long flags; + int status = urb->status; + + if (status || (urb->actual_length != urb->transfer_buffer_length)) + dev_vdbg(&ch341->data->dev, "%s - len %d/%d, status %d\n", + __func__, urb->actual_length, + urb->transfer_buffer_length, status); + + ch341->iocount.tx += urb->actual_length; + spin_lock_irqsave(&ch341->write_lock, flags); + ch341_write_done(ch341, wb); + spin_unlock_irqrestore(&ch341->write_lock, flags); + schedule_work(&ch341->work); +} + +static void ch341_softint(struct work_struct *work) +{ + struct ch341 *ch341 = container_of(work, struct ch341, work); +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)) + struct tty_struct *tty; + + tty = tty_port_tty_get(&ch341->port); + if (!tty) + return; + tty_wakeup(tty); + tty_kref_put(tty); +#else + tty_port_tty_wakeup(&ch341->port); +#endif +} + +static int ch341_tty_install(struct tty_driver *driver, + struct tty_struct *tty) +{ + struct ch341 *ch341; + int retval; + + ch341 = ch341_get_by_index(tty->index); + if (!ch341) + return -ENODEV; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)) + retval = tty_standard_install(driver, tty); + if (retval) + goto error_init_termios; + + tty->driver_data = ch341; +#else + retval = tty_init_termios(tty); + if (retval) + goto error_init_termios; + + tty->driver_data = ch341; + + /* Final install (we use the default method) */ + tty_driver_kref_get(driver); + tty->count++; + driver->ttys[tty->index] = tty; +#endif + return 0; + +error_init_termios: + tty_port_put(&ch341->port); + return retval; +} + +static int ch341_tty_open(struct tty_struct *tty, struct file *filp) +{ + struct ch341 *ch341 = tty->driver_data; + + return tty_port_open(&ch341->port, tty, filp); +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0)) +static void ch341_port_dtr_rts(struct tty_port *port, bool raise) +#else +static void ch341_port_dtr_rts(struct tty_port *port, int raise) +#endif +{ + struct ch341 *ch341 = container_of(port, struct ch341, port); + int res; + + if (raise) + ch341->ctrlout |= CH341_CTO_D | CH341_CTO_R; + else + ch341->ctrlout &= ~(CH341_CTO_D | CH341_CTO_R); + if (ch341->hardflow) + ch341->ctrlout |= CH341_CTO_R; + res = ch341_set_control(ch341, ch341->ctrlout); + if (res) + dev_err(&ch341->data->dev, "failed to set dtr/rts\n"); +} + +static int ch341_port_activate(struct tty_port *port, + struct tty_struct *tty) +{ + struct ch341 *ch341 = container_of(port, struct ch341, port); + int retval = -ENODEV; + int i; + + mutex_lock(&ch341->mutex); + if (ch341->disconnected) + goto disconnected; + + retval = usb_autopm_get_interface(ch341->data); + if (retval) + goto error_get_interface; + + set_bit(TTY_NO_WRITE_SPLIT, &tty->flags); + ch341->data->needs_remote_wakeup = 1; + retval = ch341_configure(ch341); + if (retval) + goto error_configure; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)) + ch341_tty_set_termios(tty, NULL); +#endif + + retval = usb_submit_urb(ch341->ctrlurb, GFP_KERNEL); + if (retval) { + dev_err(&ch341->data->dev, + "%s - usb_submit_urb(ctrl cmd) failed\n", + __func__); + goto error_submit_urb; + } + + retval = ch341_submit_read_urbs(ch341, GFP_KERNEL); + if (retval) + goto error_submit_read_urbs; + usb_autopm_put_interface(ch341->data); + + mutex_unlock(&ch341->mutex); + + return 0; + +error_submit_read_urbs: + for (i = 0; i < ch341->rx_buflimit; i++) + usb_kill_urb(ch341->read_urbs[i]); +error_submit_urb: + usb_kill_urb(ch341->ctrlurb); +error_configure: + usb_autopm_put_interface(ch341->data); +error_get_interface: +disconnected: + mutex_unlock(&ch341->mutex); + + return usb_translate_errors(retval); +} + +static void ch341_port_destruct(struct tty_port *port) +{ + struct ch341 *ch341 = container_of(port, struct ch341, port); + + ch341_release_minor(ch341); + usb_put_intf(ch341->data); + kfree(ch341); +} + +static void ch341_port_shutdown(struct tty_port *port) +{ + struct ch341 *ch341 = container_of(port, struct ch341, port); + struct urb *urb; + struct ch341_wb *wb; + int i; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)) + usb_autopm_get_interface_no_resume(ch341->data); + ch341->data->needs_remote_wakeup = 0; + usb_autopm_put_interface(ch341->data); + + for (;;) { + urb = usb_get_from_anchor(&ch341->delayed); + if (!urb) + break; + wb = urb->context; + wb->use = 0; + usb_autopm_put_interface_async(ch341->data); + } + + usb_kill_urb(ch341->ctrlurb); + for (i = 0; i < CH341_NW; i++) + usb_kill_urb(ch341->wb[i].urb); + for (i = 0; i < ch341->rx_buflimit; i++) + usb_kill_urb(ch341->read_urbs[i]); +#else + mutex_lock(&ch341->mutex); + if (!ch341->disconnected) { + usb_autopm_get_interface(ch341->data); + ch341_set_control(ch341, ch341->ctrlout = 0); + + usb_kill_urb(ch341->ctrlurb); + for (i = 0; i < CH341_NW; i++) + usb_kill_urb(ch341->wb[i].urb); + for (i = 0; i < ch341->rx_buflimit; i++) + usb_kill_urb(ch341->read_urbs[i]); + ch341->data->needs_remote_wakeup = 0; + + usb_autopm_put_interface(ch341->data); + } + mutex_unlock(&ch341->mutex); +#endif +} + +static void ch341_tty_cleanup(struct tty_struct *tty) +{ + struct ch341 *ch341 = tty->driver_data; + + tty_port_put(&ch341->port); +} + +static void ch341_tty_hangup(struct tty_struct *tty) +{ + struct ch341 *ch341 = tty->driver_data; + + tty_port_hangup(&ch341->port); +} + +static void ch341_tty_close(struct tty_struct *tty, struct file *filp) +{ + struct ch341 *ch341 = tty->driver_data; + + tty_port_close(&ch341->port, tty, filp); +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)) +static ssize_t ch341_tty_write(struct tty_struct *tty, const u8 *buf, + size_t count) +#else +static int ch341_tty_write(struct tty_struct *tty, + const unsigned char *buf, int count) +#endif +{ + struct ch341 *ch341 = tty->driver_data; + int stat; + unsigned long flags; + int wbn; + struct ch341_wb *wb; + + if (!count) + return 0; + + spin_lock_irqsave(&ch341->write_lock, flags); + wbn = ch341_wb_alloc(ch341); + if (wbn < 0) { + spin_unlock_irqrestore(&ch341->write_lock, flags); + return 0; + } + wb = &ch341->wb[wbn]; + + if (!ch341->dev) { + wb->use = 0; + spin_unlock_irqrestore(&ch341->write_lock, flags); + return -ENODEV; + } + + count = (count > ch341->writesize) ? ch341->writesize : count; + memcpy(wb->buf, buf, count); + wb->len = count; + + stat = usb_autopm_get_interface_async(ch341->data); + if (stat) { + wb->use = 0; + spin_unlock_irqrestore(&ch341->write_lock, flags); + return stat; + } + + if (ch341->susp_count) { + usb_anchor_urb(wb->urb, &ch341->delayed); + spin_unlock_irqrestore(&ch341->write_lock, flags); + return count; + } + + stat = ch341_start_wb(ch341, wb); + spin_unlock_irqrestore(&ch341->write_lock, flags); + + if (stat < 0) + return stat; + return count; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0)) +static unsigned int ch341_tty_write_room(struct tty_struct *tty) +#else +static int ch341_tty_write_room(struct tty_struct *tty) +#endif +{ + struct ch341 *ch341 = tty->driver_data; + + return ch341_wb_is_avail(ch341) ? ch341->writesize : 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0)) +static unsigned int ch341_tty_chars_in_buffer(struct tty_struct *tty) +#else +static int ch341_tty_chars_in_buffer(struct tty_struct *tty) +#endif +{ + struct ch341 *ch341 = tty->driver_data; + + if (ch341->disconnected) + return 0; + + return (CH341_NW - ch341_wb_is_avail(ch341)) * ch341->writesize; +} + +static int ch341_tty_break_ctl(struct tty_struct *tty, int state) +{ + struct ch341 *ch341 = tty->driver_data; + int retval; + uint16_t reg_contents; + uint8_t *regbuf; + const u16 regval = ((u16)CH341_RL << 8) | CH341_RB; + + regbuf = kmalloc(2, GFP_KERNEL); + if (!regbuf) + return -1; + + retval = ch341_control_in(ch341, CMD_R, regval, 0, regbuf, 2); + if (retval < 0) { + dev_err(&ch341->data->dev, + "%s - ch341_control_in error (%d)\n", __func__, + retval); + goto out; + } + if (state != 0) { + regbuf[0] &= ~CH341_NB; + regbuf[1] &= ~CH341_L_ET; + } else { + regbuf[0] |= CH341_NB; + regbuf[1] |= CH341_L_ET; + } + reg_contents = get_unaligned_le16(regbuf); + + retval = ch341_control_out(ch341, CMD_W, regval, reg_contents); + if (retval < 0) + dev_err(&ch341->data->dev, + "%s - ch341_control_in error (%d)\n", __func__, + retval); +out: + kfree(regbuf); + return retval; +} + +static int ch341_tty_tiocmget(struct tty_struct *tty) +{ + struct ch341 *ch341 = tty->driver_data; unsigned long flags; - u8 mcr; - u8 status; unsigned int result; - spin_lock_irqsave(&priv->lock, flags); - mcr = priv->mcr; - status = priv->msr; - spin_unlock_irqrestore(&priv->lock, flags); - - result = ((mcr & CH341_BIT_DTR) ? TIOCM_DTR : 0) - | ((mcr & CH341_BIT_RTS) ? TIOCM_RTS : 0) - | ((status & CH341_BIT_CTS) ? TIOCM_CTS : 0) - | ((status & CH341_BIT_DSR) ? TIOCM_DSR : 0) - | ((status & CH341_BIT_RI) ? TIOCM_RI : 0) - | ((status & CH341_BIT_DCD) ? TIOCM_CD : 0); - - dev_dbg(&port->dev, "%s - result = %x\n", __func__, result); + spin_lock_irqsave(&ch341->read_lock, flags); + result = (ch341->ctrlout & CH341_CTO_D ? TIOCM_DTR : 0) | + (ch341->ctrlout & CH341_CTO_R ? TIOCM_RTS : 0) | + (ch341->ctrlin & CH341_CTI_C ? TIOCM_CTS : 0) | + (ch341->ctrlin & CH341_CTI_DS ? TIOCM_DSR : 0) | + (ch341->ctrlin & CH341_CTRL_RI ? TIOCM_RI : 0) | + (ch341->ctrlin & CH341_CTI_DC ? TIOCM_CD : 0); + spin_unlock_irqrestore(&ch341->read_lock, flags); return result; } -static int ch341_reset_resume(struct usb_serial *serial) +static int ch341_tty_tiocmset(struct tty_struct *tty, unsigned int set, + unsigned int clear) { - struct usb_serial_port *port = serial->port[0]; - struct ch341_private *priv; - int ret; + struct ch341 *ch341 = tty->driver_data; + unsigned int newctrl; - priv = usb_get_serial_port_data(port); - if (!priv) + newctrl = ch341->ctrlout; + set = (set & TIOCM_DTR ? CH341_CTO_D : 0) | + (set & TIOCM_RTS ? CH341_CTO_R : 0); + clear = (clear & TIOCM_DTR ? CH341_CTO_D : 0) | + (clear & TIOCM_RTS ? CH341_CTO_R : 0); + newctrl = (newctrl & ~clear) | set; + if (C_CRTSCTS(tty)) + newctrl |= CH341_CTO_R; + if (ch341->ctrlout == newctrl) return 0; - /* reconfigure ch341 serial port after bus-reset */ - ch341_configure(serial->dev, priv); - - if (tty_port_initialized(&port->port)) { - ret = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO); - if (ret) { - dev_err(&port->dev, "failed to submit interrupt urb: %d\n", - ret); - return ret; - } - - ret = ch341_get_status(port->serial->dev, priv); - if (ret < 0) { - dev_err(&port->dev, "failed to read modem status: %d\n", - ret); - } - } - - return usb_serial_generic_resume(serial); + return ch341_set_control(ch341, ch341->ctrlout = newctrl); } -static struct usb_serial_driver ch341_device = { - .driver = { - .owner = THIS_MODULE, - .name = "ch341-uart", - }, - .id_table = id_table, - .num_ports = 1, - .open = ch341_open, - .dtr_rts = ch341_dtr_rts, - .carrier_raised = ch341_carrier_raised, - .close = ch341_close, - .set_termios = ch341_set_termios, - .break_ctl = ch341_break_ctl, - .tiocmget = ch341_tiocmget, - .tiocmset = ch341_tiocmset, - .tiocmiwait = usb_serial_generic_tiocmiwait, - .read_int_callback = ch341_read_int_callback, - .port_probe = ch341_port_probe, - .port_remove = ch341_port_remove, - .reset_resume = ch341_reset_resume, +static int ch341_get_icount(struct tty_struct *tty, + struct serial_icounter_struct *icount) +{ + struct ch341 *ch341 = tty->driver_data; + struct async_icount cnow; + unsigned long flags; + + spin_lock_irqsave(&ch341->read_lock, flags); + cnow = ch341->iocount; + spin_unlock_irqrestore(&ch341->read_lock, flags); + + icount->cts = cnow.cts; + icount->dsr = cnow.dsr; + icount->rng = cnow.rng; + icount->dcd = cnow.dcd; + icount->tx = cnow.tx; + icount->rx = cnow.rx; + icount->frame = cnow.frame; + icount->parity = cnow.parity; + icount->overrun = cnow.overrun; + icount->brk = cnow.brk; + icount->buf_overrun = cnow.buf_overrun; + + return 0; +} + +static int get_serial_info(struct ch341 *ch341, + struct serial_struct __user *info) +{ + struct serial_struct tmp; + + if (!info) + return -EINVAL; + + memset(&tmp, 0, sizeof(tmp)); + tmp.flags = ASYNC_LOW_LATENCY; + tmp.xmit_fifo_size = ch341->writesize; + tmp.baud_base = le32_to_cpu(ch341->line.dwDTERate); + tmp.close_delay = ch341->port.close_delay / 10; + tmp.closing_wait = ch341->port.closing_wait == + ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : + ch341->port.closing_wait / 10; + if (copy_to_user(info, &tmp, sizeof(tmp))) + return -EFAULT; + else + return 0; +} + +static int set_serial_info(struct ch341 *ch341, + struct serial_struct __user *newinfo) +{ + struct serial_struct new_serial; + unsigned int closing_wait, close_delay; + int retval = 0; + + if (copy_from_user(&new_serial, newinfo, sizeof(new_serial))) + return -EFAULT; + + close_delay = new_serial.close_delay * 10; + closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : + new_serial.closing_wait * 10; + + mutex_lock(&ch341->port.mutex); + if (!capable(CAP_SYS_ADMIN)) { + if ((close_delay != ch341->port.close_delay) || + (closing_wait != ch341->port.closing_wait)) + retval = -EPERM; + else + retval = -EOPNOTSUPP; + } else { + ch341->port.close_delay = close_delay; + ch341->port.closing_wait = closing_wait; + } + mutex_unlock(&ch341->port.mutex); + + return retval; +} + +static int wait_serial_change(struct ch341 *ch341, unsigned long arg) +{ + int rv = 0; + DECLARE_WAITQUEUE(wait, current); + struct async_icount old, new; + + do { + spin_lock_irq(&ch341->read_lock); + old = ch341->oldcount; + new = ch341->iocount; + ch341->oldcount = new; + spin_unlock_irq(&ch341->read_lock); + + if ((arg & TIOCM_CTS) && old.cts != new.cts) + break; + if ((arg & TIOCM_DSR) && old.dsr != new.dsr) + break; + if ((arg & TIOCM_RI) && old.rng != new.rng) + break; + if ((arg & TIOCM_CD) && old.dcd != new.dcd) + break; + + add_wait_queue(&ch341->wioctl, &wait); + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + remove_wait_queue(&ch341->wioctl, &wait); + if (ch341->disconnected) { + rv = -ENODEV; + } else { + if (signal_pending(current)) + rv = -ERESTARTSYS; + } + } while (!rv); + + return rv; +} + +static int get_serial_usage(struct ch341 *ch341, + struct serial_icounter_struct __user *count) +{ + struct serial_icounter_struct icount; + int rv = 0; + + memset(&icount, 0, sizeof(icount)); + icount.cts = ch341->iocount.cts; + icount.dsr = ch341->iocount.dsr; + icount.rng = ch341->iocount.rng; + icount.dcd = ch341->iocount.dcd; + icount.tx = ch341->iocount.tx; + icount.rx = ch341->iocount.rx; + icount.frame = ch341->iocount.frame; + icount.overrun = ch341->iocount.overrun; + icount.parity = ch341->iocount.parity; + icount.brk = ch341->iocount.brk; + icount.buf_overrun = ch341->iocount.buf_overrun; + + if (copy_to_user(count, &icount, sizeof(icount)) > 0) + rv = -EFAULT; + + return rv; +} + +static int ch341_tty_ioctl(struct tty_struct *tty, unsigned int cmd, + unsigned long arg) +{ + struct ch341 *ch341 = tty->driver_data; + int rv = -ENOIOCTLCMD; + + switch (cmd) { + case TIOCGSERIAL: /* gets serial port data */ + rv = get_serial_info(ch341, + (struct serial_struct __user *)arg); + break; + case TIOCSSERIAL: + rv = set_serial_info(ch341, + (struct serial_struct __user *)arg); + break; + case TIOCMIWAIT: + rv = usb_autopm_get_interface(ch341->data); + if (rv < 0) { + rv = -EIO; + break; + } + rv = wait_serial_change(ch341, arg); + usb_autopm_put_interface(ch341->data); + break; + case TIOCGICOUNT: + rv = get_serial_usage( + ch341, + (struct serial_icounter_struct __user *)arg); + break; + } + + return rv; +} + +static int ch341_get(unsigned int baval, unsigned char *factor, + unsigned char *divisor) +{ + unsigned char a; + unsigned char b; + unsigned long c; + + switch (baval) { + case 921600: + a = 0xf3; + b = 7; + break; + case 307200: + a = 0xd9; + b = 7; + break; + default: + if (baval > 6000000 / 255) { + b = 3; + c = 6000000; + } else if (baval > 750000 / 255) { + b = 2; + c = 750000; + } else if (baval > 93750 / 255) { + b = 1; + c = 93750; + } else { + b = 0; + c = 11719; + } + a = (unsigned char)(c / baval); + if (a == 0 || a == 0xFF) + return -EINVAL; + if ((c / a - baval) > (baval - c / (a + 1))) + a++; + a = 256 - a; + break; + } + *factor = a; + *divisor = b; + + return 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)) +static void ch341_tty_set_termios(struct tty_struct *tty, + const struct ktermios *termios_old) +#else +static void ch341_tty_set_termios(struct tty_struct *tty, + struct ktermios *termios_old) +#endif +{ + struct ch341 *ch341 = tty->driver_data; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0)) + struct ktermios *termios = &tty->termios; +#else + struct ktermios *termios = tty->termios; +#endif + struct usb_ch341_line_coding newline; + int newctrl = ch341->ctrlout; + + unsigned char divisor = 0; + unsigned char reg_count = 0; + unsigned char factor = 0; + unsigned char reg_value = 0; + unsigned short value = 0; + unsigned short index = 0; + unsigned char timeout = 0; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0)) + if (termios_old && + !tty_termios_hw_change(&tty->termios, termios_old)) { +#else + if (termios_old && + !tty_termios_hw_change(tty->termios, termios_old)) { +#endif + return; + } + + newline.dwDTERate = tty_get_baud_rate(tty); + + if (newline.dwDTERate == 0) + newline.dwDTERate = 9600; + ch341_get(newline.dwDTERate, &factor, &divisor); + + newline.bCharFormat = termios->c_cflag & CSTOPB ? 2 : 1; + if (newline.bCharFormat == 2) + reg_value |= CH341_L_SB; + + newline.bParityType = + termios->c_cflag & PARENB ? + (termios->c_cflag & PARODD ? 1 : 2) + + (termios->c_cflag & CMSPAR ? 2 : 0) : + 0; + + switch (newline.bParityType) { + case 0x01: + reg_value |= CH341_L_PO; + break; + case 0x02: + reg_value |= CH341_L_PE; + break; + case 0x03: + reg_value |= CH341_L_PM; + break; + case 0x04: + reg_value |= CH341_L_PS; + break; + default: + break; + } + + switch (termios->c_cflag & CSIZE) { + case CS5: + newline.bDataBits = 5; + reg_value |= CH341_L_D5; + break; + case CS6: + newline.bDataBits = 6; + reg_value |= CH341_L_D6; + break; + case CS7: + newline.bDataBits = 7; + reg_value |= CH341_L_D7; + break; + case CS8: + default: + newline.bDataBits = 8; + reg_value |= CH341_L_D8; + break; + } + + ch341->clocal = ((termios->c_cflag & CLOCAL) != 0); + + if (C_BAUD(tty) == B0) { + newline.dwDTERate = ch341->line.dwDTERate; + newctrl &= ~CH341_CTO_D; + } else if (termios_old && (termios_old->c_cflag & CBAUD) == B0) { + newctrl |= CH341_CTO_D; + } + + reg_value |= 0xc0; + reg_count |= 0x9c; + + value |= reg_count; + value |= (unsigned short)reg_value << 8; + index |= 0x80 | divisor; + index |= (unsigned short)factor << 8; + ch341_control_out(ch341, CMD_C1, value, index); + + timeout = 76800 / newline.dwDTERate; + if (timeout < 0x07) + timeout = 0x07; + ch341_control_out(ch341, CMD_W, 0x0f2c, timeout); + + if (newctrl != ch341->ctrlout) + ch341_set_control(ch341, ch341->ctrlout = newctrl); + + if (memcmp(&ch341->line, &newline, sizeof newline)) + memcpy(&ch341->line, &newline, sizeof newline); + if (C_CRTSCTS(tty)) { + ch341_control_out(ch341, CMD_W, 0x2727, 0x0101); + newctrl |= CH341_CTO_R; + ch341_set_control(ch341, ch341->ctrlout = newctrl); + ch341->hardflow = true; + } else { + ch341_control_out(ch341, CMD_W, 0x2727, 0x0000); + ch341->hardflow = false; + } +} + +static const struct tty_port_operations ch341_port_ops = { + .dtr_rts = ch341_port_dtr_rts, + .shutdown = ch341_port_shutdown, + .activate = ch341_port_activate, + .destruct = ch341_port_destruct, }; -static struct usb_serial_driver * const serial_drivers[] = { - &ch341_device, NULL +/* + * USB probe and disconnect routines. + */ +static void ch341_write_buffers_free(struct ch341 *ch341) +{ + int i; + struct ch341_wb *wb; + struct usb_device *usb_dev = interface_to_usbdev(ch341->data); + + for (wb = &ch341->wb[0], i = 0; i < CH341_NW; i++, wb++) + usb_free_coherent(usb_dev, ch341->writesize, wb->buf, + wb->dmah); +} + +static void ch341_read_buffers_free(struct ch341 *ch341) +{ + struct usb_device *usb_dev = interface_to_usbdev(ch341->data); + int i; + + for (i = 0; i < ch341->rx_buflimit; i++) + usb_free_coherent(usb_dev, ch341->readsize, + ch341->read_buffers[i].base, + ch341->read_buffers[i].dma); +} + +static int ch341_write_buffers_alloc(struct ch341 *ch341) +{ + int i; + struct ch341_wb *wb; + + for (wb = &ch341->wb[0], i = 0; i < CH341_NW; i++, wb++) { + wb->buf = usb_alloc_coherent(ch341->dev, ch341->writesize, + GFP_KERNEL, &wb->dmah); + if (!wb->buf) { + while (i != 0) { + --i; + --wb; + usb_free_coherent(ch341->dev, + ch341->writesize, + wb->buf, wb->dmah); + } + return -ENOMEM; + } + } + return 0; +} + +static int ch341_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_interface *data_interface; + struct usb_endpoint_descriptor *epctrl = NULL; + struct usb_endpoint_descriptor *epread = NULL; + struct usb_endpoint_descriptor *epwrite = NULL; + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct ch341 *ch341; + int minor; + int ctrlsize, readsize; + u8 *buf; + unsigned long quirks; + int num_rx_buf = CH341_NR; + int i; + struct device *tty_dev; + int rv = -ENOMEM; + + /* normal quirks */ + quirks = (unsigned long)id->driver_info; + + data_interface = usb_ifnum_to_if(usb_dev, 0); + + if (intf != data_interface) + return -ENODEV; + + if (data_interface->cur_altsetting->desc.bNumEndpoints < 2 || + data_interface->cur_altsetting->desc.bNumEndpoints == 0) + return -EINVAL; + + epread = &data_interface->cur_altsetting->endpoint[0].desc; + epwrite = &data_interface->cur_altsetting->endpoint[1].desc; + epctrl = &data_interface->cur_altsetting->endpoint[2].desc; + + if (!usb_endpoint_dir_in(epread)) { + swap(epread, epwrite); + } + + ch341 = kzalloc(sizeof(struct ch341), GFP_KERNEL); + if (ch341 == NULL) + goto alloc_fail; + + minor = ch341_alloc_minor(ch341); + if (minor < 0) { + dev_err(&intf->dev, "no more free ch341 devices\n"); + kfree(ch341); + return -ENODEV; + } + + ctrlsize = usb_endpoint_maxp(epctrl); + readsize = usb_endpoint_maxp(epread) * + (quirks == SINGLE_RX_URB ? 1 : 1); + ch341->writesize = usb_endpoint_maxp(epwrite) * 20; + ch341->data = data_interface; + ch341->minor = minor; + ch341->dev = usb_dev; + ch341->ctrlsize = ctrlsize; + ch341->readsize = readsize; + ch341->rx_buflimit = num_rx_buf; + + INIT_WORK(&ch341->work, ch341_softint); + init_waitqueue_head(&ch341->wioctl); + spin_lock_init(&ch341->write_lock); + spin_lock_init(&ch341->read_lock); + mutex_init(&ch341->mutex); + ch341->rx_endpoint = + usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress); + tty_port_init(&ch341->port); + ch341->port.ops = &ch341_port_ops; + init_usb_anchor(&ch341->delayed); + ch341->quirks = quirks; + + buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL, + &ch341->ctrl_dma); + if (!buf) + goto alloc_fail2; + ch341->ctrl_buffer = buf; + + if (ch341_write_buffers_alloc(ch341) < 0) + goto alloc_fail4; + + ch341->ctrlurb = usb_alloc_urb(0, GFP_KERNEL); + if (!ch341->ctrlurb) + goto alloc_fail5; + + for (i = 0; i < num_rx_buf; i++) { + struct ch341_rb *rb = &(ch341->read_buffers[i]); + struct urb *urb; + + rb->base = usb_alloc_coherent(ch341->dev, readsize, + GFP_KERNEL, &rb->dma); + if (!rb->base) + goto alloc_fail6; + rb->index = i; + rb->instance = ch341; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + goto alloc_fail6; + + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + urb->transfer_dma = rb->dma; + usb_fill_bulk_urb(urb, ch341->dev, ch341->rx_endpoint, + rb->base, ch341->readsize, + ch341_read_bulk_callback, rb); + + ch341->read_urbs[i] = urb; + __set_bit(i, &ch341->read_urbs_free); + } + for (i = 0; i < CH341_NW; i++) { + struct ch341_wb *snd = &(ch341->wb[i]); + + snd->urb = usb_alloc_urb(0, GFP_KERNEL); + if (snd->urb == NULL) + goto alloc_fail7; + + usb_fill_bulk_urb( + snd->urb, usb_dev, + usb_sndbulkpipe(usb_dev, + epwrite->bEndpointAddress), + NULL, ch341->writesize, ch341_write_bulk, snd); + snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + snd->instance = ch341; + } + + usb_set_intfdata(intf, ch341); + + usb_fill_int_urb(ch341->ctrlurb, usb_dev, + usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress), + ch341->ctrl_buffer, ctrlsize, ch341_ctrl_irq, + ch341, + epctrl->bInterval ? epctrl->bInterval : 16); + ch341->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + ch341->ctrlurb->transfer_dma = ch341->ctrl_dma; + + dev_info(&intf->dev, "ttyCH341USB%d: ch341 USB device\n", minor); + + usb_driver_claim_interface(&ch341_driver, data_interface, ch341); + usb_set_intfdata(data_interface, ch341); + + usb_get_intf(data_interface); + ch341->line.dwDTERate = 9600; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0)) + tty_dev = tty_port_register_device(&ch341->port, ch341_tty_driver, + minor, &data_interface->dev); + if (IS_ERR(tty_dev)) { + rv = PTR_ERR(tty_dev); + goto alloc_fail7; + } +#else + tty_register_device(ch341_tty_driver, minor, &data_interface->dev); +#endif + return 0; + +alloc_fail7: + usb_set_intfdata(intf, NULL); + for (i = 0; i < CH341_NW; i++) + usb_free_urb(ch341->wb[i].urb); +alloc_fail6: + for (i = 0; i < num_rx_buf; i++) + usb_free_urb(ch341->read_urbs[i]); + ch341_read_buffers_free(ch341); + usb_free_urb(ch341->ctrlurb); +alloc_fail5: + ch341_write_buffers_free(ch341); +alloc_fail4: + usb_free_coherent(usb_dev, ctrlsize, ch341->ctrl_buffer, + ch341->ctrl_dma); +alloc_fail2: + ch341_release_minor(ch341); + kfree(ch341); +alloc_fail: + return rv; +} + +static void stop_data_traffic(struct ch341 *ch341) +{ + int i; + + usb_kill_urb(ch341->ctrlurb); + for (i = 0; i < CH341_NW; i++) + usb_kill_urb(ch341->wb[i].urb); + for (i = 0; i < ch341->rx_buflimit; i++) + usb_kill_urb(ch341->read_urbs[i]); + + cancel_work_sync(&ch341->work); +} + +static void ch341_disconnect(struct usb_interface *intf) +{ + struct ch341 *ch341 = usb_get_intfdata(intf); + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct tty_struct *tty; + int i; + + if (!ch341) + return; + mutex_lock(&ch341->mutex); + ch341->disconnected = true; + wake_up_all(&ch341->wioctl); + usb_set_intfdata(ch341->data, NULL); + mutex_unlock(&ch341->mutex); + + tty = tty_port_tty_get(&ch341->port); + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); + } + stop_data_traffic(ch341); + tty_unregister_device(ch341_tty_driver, ch341->minor); + + usb_free_urb(ch341->ctrlurb); + for (i = 0; i < CH341_NW; i++) + usb_free_urb(ch341->wb[i].urb); + for (i = 0; i < ch341->rx_buflimit; i++) + usb_free_urb(ch341->read_urbs[i]); + ch341_write_buffers_free(ch341); + usb_free_coherent(usb_dev, ch341->ctrlsize, ch341->ctrl_buffer, + ch341->ctrl_dma); + ch341_read_buffers_free(ch341); + usb_driver_release_interface(&ch341_driver, ch341->data); + tty_port_put(&ch341->port); + dev_info(&intf->dev, "%s\n", "ch341 usb device disconnect."); +} + +#ifdef CONFIG_PM +static int ch341_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct ch341 *ch341 = usb_get_intfdata(intf); + int cnt; + + spin_lock_irq(&ch341->write_lock); + if (PMSG_IS_AUTO(message)) { + if (ch341->transmitting) { + spin_unlock_irq(&ch341->write_lock); + return -EBUSY; + } + } + cnt = ch341->susp_count++; + spin_unlock_irq(&ch341->write_lock); + if (cnt) + return 0; + + stop_data_traffic(ch341); + + return 0; +} + +static int ch341_resume(struct usb_interface *intf) +{ + struct ch341 *ch341 = usb_get_intfdata(intf); + struct urb *urb; + int rv = 0; + + spin_lock_irq(&ch341->write_lock); + if (--ch341->susp_count) + goto out; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)) + if (tty_port_initialized(&ch341->port)) { +#else + if (test_bit(ASYNCB_INITIALIZED, &ch341->port.flags)) { +#endif + rv = usb_submit_urb(ch341->ctrlurb, GFP_ATOMIC); + for (;;) { + urb = usb_get_from_anchor(&ch341->delayed); + if (!urb) + break; + + ch341_start_wb(ch341, urb->context); + } + if (rv < 0) + goto out; + rv = ch341_submit_read_urbs(ch341, GFP_ATOMIC); + } +out: + spin_unlock_irq(&ch341->write_lock); + return rv; +} + +static int ch341_reset_resume(struct usb_interface *intf) +{ + struct ch341 *ch341 = usb_get_intfdata(intf); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)) + if (tty_port_initialized(&ch341->port)) +#else + if (test_bit(ASYNCB_INITIALIZED, &ch341->port.flags)) +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)) + struct tty_struct *tty = tty_port_tty_get(&ch341->port); + tty_hangup(tty); +#else + tty_port_tty_hangup(&ch341->port, false); +#endif + return ch341_resume(intf); +} +#endif /* CONFIG_PM */ + +/* + * USB driver structure. + */ +static const struct usb_device_id ch341_ids[] = { + { USB_DEVICE(0x1a86, 0x7523) }, /* ch340 chip */ + { USB_DEVICE(0x1a86, 0x7522) }, /* ch340k chip */ + { USB_DEVICE(0x1a86, 0x5523) }, /* ch341 chip */ + { USB_DEVICE(0x1a86, 0xe523) }, /* ch330 chip */ + { USB_DEVICE(0x4348, 0x5523) }, /* ch340 custom chip */ + {} }; -module_usb_serial_driver(serial_drivers, id_table); +MODULE_DEVICE_TABLE(usb, ch341_ids); -MODULE_LICENSE("GPL v2"); +static struct usb_driver ch341_driver = { + .name = "usb_ch341", + .probe = ch341_probe, + .disconnect = ch341_disconnect, +#ifdef CONFIG_PM + .suspend = ch341_suspend, + .resume = ch341_resume, + .reset_resume = ch341_reset_resume, +#endif + .id_table = ch341_ids, +#ifdef CONFIG_PM + .supports_autosuspend = 1, +#endif +#if (LINUX_VERSION_CODE > KERNEL_VERSION(3, 4, 0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +/* + * TTY driver structures. + */ +static const struct tty_operations ch341_ops = { + .install = ch341_tty_install, + .open = ch341_tty_open, + .close = ch341_tty_close, + .cleanup = ch341_tty_cleanup, + .hangup = ch341_tty_hangup, + .write = ch341_tty_write, + .write_room = ch341_tty_write_room, + .ioctl = ch341_tty_ioctl, + .chars_in_buffer = ch341_tty_chars_in_buffer, + .break_ctl = ch341_tty_break_ctl, + .set_termios = ch341_tty_set_termios, + .tiocmget = ch341_tty_tiocmget, + .tiocmset = ch341_tty_tiocmset, + .get_icount = ch341_get_icount, +}; + +static int __init ch341_init(void) +{ + int retval; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) + ch341_tty_driver = tty_alloc_driver( + CH341_TTY_MINORS, + TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV); + if (IS_ERR(ch341_tty_driver)) + return PTR_ERR(ch341_tty_driver); +#else + ch341_tty_driver = alloc_tty_driver(CH341_TTY_MINORS); + if (!ch341_tty_driver) + return -ENOMEM; +#endif + ch341_tty_driver->driver_name = "ch341_uart", + ch341_tty_driver->name = "ttyCH341USB", + ch341_tty_driver->major = CH341_TTY_MAJOR, + ch341_tty_driver->minor_start = 0, + ch341_tty_driver->type = TTY_DRIVER_TYPE_SERIAL, + ch341_tty_driver->subtype = SERIAL_TYPE_NORMAL, +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0)) + ch341_tty_driver->flags = TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV; +#endif + ch341_tty_driver->init_termios = tty_std_termios; + ch341_tty_driver->init_termios.c_cflag = B0 | CS8 | CREAD | HUPCL | + CLOCAL; + tty_set_operations(ch341_tty_driver, &ch341_ops); + + retval = tty_register_driver(ch341_tty_driver); + if (retval) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) + tty_driver_kref_put(ch341_tty_driver); +#else + put_tty_driver(ch341_tty_driver); +#endif + return retval; + } + + retval = usb_register(&ch341_driver); + if (retval) { + tty_unregister_driver(ch341_tty_driver); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) + tty_driver_kref_put(ch341_tty_driver); +#else + put_tty_driver(ch341_tty_driver); +#endif + return retval; + } + + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n"); + printk(KERN_INFO KBUILD_MODNAME ": " VERSION_DESC "\n"); + + return 0; +} + +static void __exit ch341_exit(void) +{ + usb_deregister(&ch341_driver); + tty_unregister_driver(ch341_tty_driver); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) + tty_driver_kref_put(ch341_tty_driver); +#else + put_tty_driver(ch341_tty_driver); +#endif +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)) + idr_destroy(&ch341_minors); +#endif + printk(KERN_INFO KBUILD_MODNAME ": " + "ch341 driver exit.\n"); +} + +module_init(ch341_init); +module_exit(ch341_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(VERSION_DESC); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_CHARDEV_MAJOR(CH341_TTY_MAJOR); diff --git a/drivers/usb/serial/ch341.h b/drivers/usb/serial/ch341.h new file mode 100644 index 000000000..02194c3ab --- /dev/null +++ b/drivers/usb/serial/ch341.h @@ -0,0 +1,153 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * + * Includes for ch341.c + * + */ + +#ifndef _CH341_H +#define _CH341_H + +/* + * Baud rate and default timeout + */ +#define DEFAULT_BAUD_RATE 9600 +#define DEFAULT_TIMEOUT 2000 + +/* + * CMSPAR, some architectures can't have space and mark parity. + */ +#ifndef CMSPAR +#define CMSPAR 0 +#endif + +/* + * Major and minor numbers. + */ +#define CH341_TTY_MAJOR 169 +#define CH341_TTY_MINORS 256 + +/* + * Requests. + */ +#define USB_RT_CH341 (USB_TYPE_CLASS | USB_RECIP_INTERFACE) + +#define CMD_R 0x95 +#define CMD_W 0x9A +#define CMD_C1 0xA1 +#define CMD_C2 0xA4 +#define CMD_C3 0x5F + +#define CH341_CTO_O 0x10 +#define CH341_CTO_D 0x20 +#define CH341_CTO_R 0x40 +#define CH341_CTI_C 0x01 +#define CH341_CTI_DS 0x02 +#define CH341_CTRL_RI 0x04 +#define CH341_CTI_DC 0x08 +#define CH341_CTI_ST 0x0f + +#define CH341_CTT_M BIT(3) +#define CH341_CTT_F (BIT(2) | BIT(6)) +#define CH341_CTT_P BIT(2) +#define CH341_CTT_O BIT(1) + +#define CH341_L_ER 0x80 +#define CH341_L_ET 0x40 +#define CH341_L_PS 0x38 +#define CH341_L_PM 0x28 +#define CH341_L_PE 0x18 +#define CH341_L_PO 0x08 +#define CH341_L_SB 0x04 +#define CH341_L_D8 0x03 +#define CH341_L_D7 0x02 +#define CH341_L_D6 0x01 +#define CH341_L_D5 0x00 + +#define CH341_RB 0x05 +#define CH341_RL 0x18 +#define CH341_NB 0x01 + +#define CH341_NW 2 +#define CH341_NR 4 + +struct ch341_wb { + unsigned char *buf; + dma_addr_t dmah; + int len; + int use; + struct urb *urb; + struct ch341 *instance; +}; + +struct ch341_rb { + int size; + unsigned char *base; + dma_addr_t dma; + int index; + struct ch341 *instance; +}; + +struct usb_ch341_line_coding { + __le32 dwDTERate; + __u8 bCharFormat; +#define USB_CH341_1_STOP_BITS 0 +#define USB_CH341_1_5_STOP_BITS 1 +#define USB_CH341_2_STOP_BITS 2 + + __u8 bParityType; +#define USB_CH341_NO_PARITY 0 +#define USB_CH341_ODD_PARITY 1 +#define USB_CH341_EVEN_PARITY 2 +#define USB_CH341_MARK_PARITY 3 +#define USB_CH341_SPACE_PARITY 4 + + __u8 bDataBits; +} __attribute__((packed)); + +struct ch341 { + struct usb_device *dev; /* the corresponding usb device */ + struct usb_interface *data; /* data interface */ + struct tty_port port; /* our tty port data */ + struct urb *ctrlurb; /* urbs */ + u8 *ctrl_buffer; /* buffers of urbs */ + dma_addr_t ctrl_dma; /* dma handles of buffers */ + struct ch341_wb wb[CH341_NW]; + unsigned long read_urbs_free; + struct urb *read_urbs[CH341_NR]; + struct ch341_rb read_buffers[CH341_NR]; + int rx_buflimit; + int rx_endpoint; + spinlock_t read_lock; + int write_used; /* number of non-empty write buffers */ + int transmitting; + spinlock_t write_lock; + struct mutex mutex; + bool disconnected; + struct usb_ch341_line_coding line; /* baudrate, data format */ + struct work_struct work; /* used for line discipline waking up */ + unsigned int ctrlin; /* input lines (CTS, DSR, DCD, RI) */ + unsigned int ctrlout; /* output control lines (DTR, RTS) */ + struct async_icount iocount; /* counters for control line changes */ + struct async_icount oldcount; /* for comparison of counter */ + wait_queue_head_t wioctl; /* for ioctl */ + unsigned int writesize; /* max packet size */ + unsigned int readsize, ctrlsize; /* buffer sizes for freeing */ + unsigned int minor; /* ch341 minor number */ + unsigned char clocal; /* termios CLOCAL */ + unsigned int susp_count; /* number of suspended interfaces */ + u8 bInterval; + struct usb_anchor delayed; /* used for a device about to be woken */ + unsigned long quirks; + bool hardflow; +}; + +/* constants describing various quirks and errors */ +#define NO_UNION_NORMAL BIT(0) +#define SINGLE_RX_URB BIT(1) +#define NO_CAP_LINE BIT(2) +#define NO_DATA_INTERFACE BIT(4) +#define IGNORE_DEVICE BIT(5) +#define QUIRK_CONTROL_LINE_STATE BIT(6) +#define CLEAR_HALT_CONDITIONS BIT(7) +#endif diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 4c45ba08f..25103dcb0 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -250,6 +250,7 @@ static void option_instat_callback(struct urb *urb); #define QUECTEL_PRODUCT_EP06 0x0306 #define QUECTEL_PRODUCT_EM12 0x0512 #define QUECTEL_PRODUCT_RM500Q 0x0800 +#define QUECTEL_PRODUCT_EC200N_CN 0x6002 #define QUECTEL_PRODUCT_EC200A_CN 0x6005 #define QUECTEL_PRODUCT_EC200T 0x6026 #define QUECTEL_PRODUCT_EC200U_CN 0x0901 @@ -1120,6 +1121,7 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500Q, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500Q, 0xff, 0xff, 0x10), .driver_info = ZLP }, + { USB_DEVICE(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200N_CN) }, { USB_DEVICE(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200A_CN) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200T, 0xff, 0, 0) }, { USB_DEVICE(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200U_CN) }, diff --git a/include/dt-bindings/usb/dwc2-loongson-fix.h b/include/dt-bindings/usb/dwc2-loongson-fix.h index b7bb525cf..6c02c7ea6 100644 --- a/include/dt-bindings/usb/dwc2-loongson-fix.h +++ b/include/dt-bindings/usb/dwc2-loongson-fix.h @@ -1,6 +1,23 @@ #ifndef __DT_DWC2_LOONGSON_FIX_H #define __DT_DWC2_LOONGSON_FIX_H +/* + * DWC2_LOONGSON_FIX_NONE: + * Original. Some USB devices cannot initialize when first powered up. + * However, network devices do not experience packet loss. + * Therefore, if we confirm the use of a USB-network device, then select this configuration. + * + * DWC2_LOONGSON_FIX_DMA: + * Used for most devices, such as USB storage and USB touchscreens. + * However, network devices may experience packet loss. + * + * DWC2_LOONGSON_FIX_DELAY: + * Deprecated. It causes USB to operate slowly. + * However, the 4G model can still dial up. Maybe optimize later. + * + * DWC2_LOONGSON_FIX_DELAY_FULL: + * Deprecated. +*/ #define DWC2_LOONGSON_FIX_NONE 0 #define DWC2_LOONGSON_FIX_DMA 1 #define DWC2_LOONGSON_FIX_DELAY 2