mirror of
https://gitee.com/open-loongarch/linux-5.10.git
synced 2026-10-25 12:47:11 +08:00
1.Fix spidev delay-usec cannot work 2.Fix spi transfer failed when use gpio cs 3.Support EC200N-CN 4G model 4.Fix alert/critical temperature to 75/105 for 2k300 5.Enable ch341,pl2303 for 2k300_defconfig
1784 lines
42 KiB
C
1784 lines
42 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* CH340/CH341 USB to serial port driver
|
|
*
|
|
* Copyright (C) 2024 Nanjing Qinheng Microelectronics Co., Ltd.
|
|
* Web: http://wch.cn
|
|
* Author: WCH <tech@wch.cn>
|
|
*
|
|
* 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 <linux/errno.h>
|
|
#include <linux/idr.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/list.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/serial.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/tty.h>
|
|
#include <linux/tty_driver.h>
|
|
#include <linux/tty_flip.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/usb/cdc.h>
|
|
#include <linux/version.h>
|
|
#include <asm/byteorder.h>
|
|
#include <asm/unaligned.h>
|
|
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0))
|
|
#include <linux/sched/signal.h>
|
|
#endif
|
|
|
|
#include "ch341.h"
|
|
|
|
#define DRIVER_AUTHOR "WCH"
|
|
#define DRIVER_DESC "USB serial driver for ch340, ch341, etc."
|
|
#define VERSION_DESC "V1.8 On 2024.08"
|
|
|
|
static struct usb_driver ch341_driver;
|
|
static struct tty_driver *ch341_tty_driver;
|
|
|
|
#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);
|
|
|
|
#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.
|
|
*/
|
|
static struct ch341 *ch341_get_by_index(unsigned int minor)
|
|
{
|
|
struct ch341 *ch341;
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
minor = idr_alloc(&ch341_minors, ch341, 0, CH341_TTY_MINORS,
|
|
GFP_KERNEL);
|
|
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);
|
|
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);
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
static int ch341_get_status(struct ch341 *ch341)
|
|
{
|
|
char *buffer;
|
|
int retval;
|
|
const unsigned size = 2;
|
|
unsigned long flags;
|
|
|
|
buffer = kmalloc(size, GFP_KERNEL);
|
|
if (!buffer)
|
|
return -ENOMEM;
|
|
|
|
retval = ch341_control_in(ch341, CMD_R, 0x0706, 0, buffer, size);
|
|
if (retval < 0)
|
|
goto out;
|
|
|
|
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 retval;
|
|
}
|
|
|
|
static int ch341_configure(struct ch341 *ch341)
|
|
{
|
|
char *buffer;
|
|
int r;
|
|
const unsigned size = 2;
|
|
|
|
buffer = kmalloc(size, GFP_KERNEL);
|
|
if (!buffer)
|
|
return -ENOMEM;
|
|
|
|
r = ch341_control_in(ch341, CMD_C3, 0, 0, buffer, size);
|
|
if (r < 0)
|
|
goto out;
|
|
r = ch341_control_out(ch341, CMD_C1, 0, 0);
|
|
if (r < 0)
|
|
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;
|
|
|
|
out:
|
|
kfree(buffer);
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* Write buffer management.
|
|
* All of these assume proper locks taken by the caller.
|
|
*/
|
|
static int ch341_wb_alloc(struct ch341 *ch341)
|
|
{
|
|
int i, wbn;
|
|
struct ch341_wb *wb;
|
|
|
|
wbn = 0;
|
|
i = 0;
|
|
for (;;) {
|
|
wb = &ch341->wb[wbn];
|
|
if (!wb->use) {
|
|
wb->use = 1;
|
|
return wbn;
|
|
}
|
|
wbn = (wbn + 1) % CH341_NW;
|
|
if (++i >= CH341_NW)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static int ch341_wb_is_avail(struct ch341 *ch341)
|
|
{
|
|
int i, n;
|
|
unsigned long flags;
|
|
|
|
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_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)
|
|
{
|
|
unsigned long flags;
|
|
u8 status;
|
|
u8 difference;
|
|
u8 type = data[0];
|
|
u8 handled = 0;
|
|
|
|
if (len < 4)
|
|
return;
|
|
|
|
if (type & CH341_CTT_M) {
|
|
status = ~data[2] & CH341_CTI_ST;
|
|
|
|
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
|
|
}
|
|
|
|
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_ctrl_irq(struct urb *urb)
|
|
{
|
|
struct ch341 *ch341 = urb->context;
|
|
unsigned char *data = urb->transfer_buffer;
|
|
unsigned int len = urb->actual_length;
|
|
int status = urb->status;
|
|
int retval;
|
|
|
|
switch (status) {
|
|
case 0:
|
|
/* success */
|
|
break;
|
|
case -ECONNRESET:
|
|
case -ENOENT:
|
|
case -ESHUTDOWN:
|
|
/* this urb is terminated, clean up */
|
|
dev_dbg(&ch341->data->dev,
|
|
"%s - urb shutting down with status: %d\n",
|
|
__func__, status);
|
|
return;
|
|
default:
|
|
dev_dbg(&ch341->data->dev,
|
|
"%s - nonzero urb status received: %d\n", __func__,
|
|
status);
|
|
goto exit;
|
|
}
|
|
|
|
usb_mark_last_busy(ch341->dev);
|
|
ch341_update_status(ch341, data, len);
|
|
exit:
|
|
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_submit_read_urb(struct ch341 *ch341, int index,
|
|
gfp_t mem_flags)
|
|
{
|
|
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;
|
|
unsigned int 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_tty_tiocmset(struct tty_struct *tty, unsigned int set,
|
|
unsigned int clear)
|
|
{
|
|
struct ch341 *ch341 = tty->driver_data;
|
|
unsigned int newctrl;
|
|
|
|
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;
|
|
|
|
return ch341_set_control(ch341, ch341->ctrlout = newctrl);
|
|
}
|
|
|
|
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,
|
|
};
|
|
|
|
/*
|
|
* 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_DEVICE_TABLE(usb, ch341_ids);
|
|
|
|
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);
|