Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1367 lines (1194 sloc) 35.1 KB
// 2013.7
//********************************************
//** Copyright (C) WCH 2002-2013 ******
//** Web: http://www.winchiphead.com ******
//********************************************
//** Driver for USB to serial adaptor CH34X**
//** GCC **
//********************************************
// Support linux kernel version 2.6.25 and later
//
#include <linux/version.h>
#ifndef KERNEL_VERSION
#define KERNEL_VERSION(ver, rel, seq) ((ver << 16) | (rel << 8) | (seq))
#endif
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/spinlock.h>
#include <asm/uaccess.h>
//#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include <linux/sched/signal.h>
#define DRIVER_DESC "WCH CH34x USB to serial adaptor driver"
#define DRIVER_AUTHOR "<tech@wch.cn>"
#define CH34x_VENDOR_ID 0x1A86
#define CH340_PRODUCT_ID 0x7523
#define CH341_PRODUCT_ID 0x5523
#define CH34x_CLOSING_WAIT (30 * HZ)
#define CH34x_BUF_SIZE 1024
#define CH34x_TMP_BUF_SIZE 1024
//Vendor define
#define VENDOR_WRITE_TYPE 0x40
#define VENDOR_READ_TYPE 0xC0
#define VENDOR_READ 0x95
#define VENDOR_WRITE 0x9A
#define VENDOR_SERIAL_INIT 0xA1
#define VENDOR_MODEM_OUT 0xA4
#define VENDOR_VERSION 0x5F
//For CMD 0xA4
#define UART_CTS 0x01
#define UART_DSR 0x02
#define UART_RING 0x04
#define UART_DCD 0x08
#define CONTROL_OUT 0x10
#define CONTROL_DTR 0x20
#define CONTROL_RTS 0x40
//Uart state
#define UART_STATE 0x00
#define UART_OVERRUN_ERROR 0x01
#define UART_BREAK_ERROR //no define
#define UART_PARITY_ERROR 0x02
#define UART_FRAME_ERROR 0x06
#define UART_RECV_ERROR 0x02
#define UART_STATE_TRANSIENT_MASK 0x07
//Port state
#define PORTA_STATE 0x01
#define PORTB_STATE 0x02
#define PORTC_STATE 0x03
//CH34x Baud Rate
#define CH34x_BAUDRATE_FACTOR 1532620800
#define CH34x_BAUDRATE_DIVMAX 3
//#define DEBUG_CH34x
#undef DEBUG_CH34x
#ifdef DEBUG_CH34x
#define dbg_ch34x( format, arg... ) \
printk( KERN_DEBUG "%d: " format "\n", __LINE__, ##arg )
#else
#define dbg_ch34x( format, arg... ) \
do{ \
if(0) \
printk(KERN_DEBUG "%d: " format "\n", __LINE__, ##arg); \
} while (0)
#endif
#ifdef DEBUG_CH34x
#define err_ch34x( format, arg... ) \
printk(KERN_ERR KBUILD_MODNAME ": " format "\n", ##arg)
#else
#define err_ch34x( format, arg... ) \
do{ \
if(0) \
printk( KERN_ERR KBUILD_MODNAME ": " format "\n", ##arg)\
}while(0)
#endif
// For debug
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,7,1))
static int debug = 1;
#endif
static DECLARE_WAIT_QUEUE_HEAD(wq);
static int wait_flag = 0;
struct ch34x_buf {
unsigned int buf_size;
char *buf_buf;
char *buf_get;
char *buf_put;
};
struct ch34x_private {
spinlock_t lock; //access lock
struct ch34x_buf *buf;
int write_urb_in_use;
unsigned baud_rate;
wait_queue_head_t delta_msr_wait;
u8 line_control;
u8 line_status;
u8 termios_initialized;
};
static struct usb_device_id id_table [] = {
{ USB_DEVICE(CH34x_VENDOR_ID, CH340_PRODUCT_ID) },
{ USB_DEVICE(CH34x_VENDOR_ID, CH341_PRODUCT_ID) },
{ } //End
};
MODULE_DEVICE_TABLE( usb, id_table );
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,5,2))
static struct usb_driver ch34x_driver = {
.name = "ch34x",
.probe = usb_serial_probe,
.disconnect = usb_serial_disconnect,
.id_table = id_table,
.suspend = usb_serial_suspend,
.resume = usb_serial_resume,
.no_dynamic_id = 1,
.supports_autosuspend = 1,
};
#endif
// ch34x_buf_alloc
// Allocate a circular buffer and all associated memory
static struct ch34x_buf *ch34x_buf_alloc( unsigned int size )
{
struct ch34x_buf *pb;
if( size == 0 )
return NULL;
pb = kmalloc( sizeof(struct ch34x_buf), GFP_KERNEL );
if( pb == NULL )
return NULL;
pb->buf_buf = kmalloc( size, GFP_KERNEL );
if( pb->buf_buf == NULL ) {
kfree(pb);
return NULL;
}
pb->buf_size = size;
pb->buf_get = pb->buf_put = pb->buf_buf;
return pb;
}
// ch34x_buf_free
// Free the buffer and all associated memory
static void ch34x_buf_free( struct ch34x_buf *pb )
{
if( pb ) {
kfree( pb->buf_buf );
kfree( pb );
}
}
// ch34x_buf_clear
// Clear out all data in the circular buffer
static void ch34x_buf_clear( struct ch34x_buf *pb )
{
if( pb != NULL )
pb->buf_get = pb->buf_put;
// equivalent to a get of all data available
}
// ch34x_buf_data_avail
// Return the number of bytes of data available in he circular buffer
static unsigned int ch34x_buf_data_avail( struct ch34x_buf *pb )
{
if( pb == NULL )
return 0;
return ((pb->buf_size + pb->buf_put - pb->buf_get) % pb->buf_size );
}
// ch34x_buf_space_avail
// Return the number of bytes of space available in the circular
static unsigned int ch34x_buf_space_avail( struct ch34x_buf *pb )
{
if( pb == NULL )
return 0;
return ((pb->buf_size + pb->buf_get - pb->buf_put - 1) % pb->buf_size );
}
// ch34x_buf_put
// Copy data from a user buffer and put it into the circular buffer.
// Restrict to the amount of space available
// Return the number of bytes copied
static unsigned int ch34x_buf_put( struct ch34x_buf *pb,
const char *buf, unsigned int count )
{
unsigned int len;
if( pb == NULL )
return 0;
len = ch34x_buf_space_avail(pb);
if( count > len )
count = len;
else if( count == 0 )
return 0;
len = pb->buf_buf + pb->buf_size - pb->buf_put;
if( count > len ) {
memcpy( pb->buf_put, buf, len );
memcpy( pb->buf_buf, buf+len, count - len );
pb->buf_put = pb->buf_buf + count - len;
}
else {
memcpy( pb->buf_put, buf, count );
if( count < len )
pb->buf_put += count;
else if( count == len )
pb->buf_put = pb->buf_buf;
}
return count;
}
static unsigned int ch34x_buf_get( struct ch34x_buf *pb,
char *buf, unsigned int count )
{
unsigned int len;
if( pb == NULL )
return 0;
len = ch34x_buf_data_avail(pb);
if( count > len )
count = len;
else if( count == 0 )
return 0;
len = pb->buf_buf + pb->buf_size - pb->buf_get;
if( count > len ) {
memcpy( buf, pb->buf_get, len );
memcpy( buf+len, pb->buf_buf, count - len );
pb->buf_get = pb->buf_buf + count - len;
}
else {
memcpy( buf, pb->buf_get, count );
if( count < len )
pb->buf_get += count;
else if( count == len )
pb->buf_get = pb->buf_buf;
}
return count;
}
static int ch34x_vendor_read( __u8 request,
__u16 value,
__u16 index,
struct usb_serial *serial,
unsigned char *buf,
__u16 len )
{
int retval;
retval = usb_control_msg( serial->dev, usb_rcvctrlpipe(serial->dev, 0),
request, VENDOR_READ_TYPE, value, index, buf, len, 1000 );
dbg_ch34x("0x%x:0x%x:0x%x:0x%x %d - %d",
VENDOR_READ_TYPE, request, value, index, retval, len );
return retval;
}
static int ch34x_vendor_write( __u8 request,
__u16 value,
__u16 index,
struct usb_serial *serial,
unsigned char *buf,
__u16 len )
{
int retval;
retval = usb_control_msg( serial->dev,
usb_sndctrlpipe(serial->dev, 0),
request,
VENDOR_WRITE_TYPE,
value, index, buf, len, 1000 );
return retval;
}
static int set_control_lines( struct usb_serial *serial,
u8 value )
{
int retval;
retval = ch34x_vendor_write( VENDOR_MODEM_OUT, (unsigned short)value,
0x0000, serial, NULL, 0x00 );
dbg_ch34x("%s - value=%d, retval=%d", __func__, value, retval );
return retval;
}
static int ch34x_get_baud_rate( unsigned int baud_rate,
unsigned char *factor, unsigned char *divisor)
{
unsigned char a;
unsigned char b;
unsigned long c;
switch ( baud_rate ) {
case 921600:
a = 0xf3;
b = 7;
break;
case 307200:
a = 0xd9;
b = 7;
break;
default:
if ( baud_rate > 6000000/255 ) {
b = 3;
c = 6000000;
} else if ( baud_rate > 750000/255 ) {
b = 2;
c = 750000;
} else if (baud_rate > 93750/255) {
b = 1;
c = 93750;
} else {
b = 0;
c = 11719;
}
a = (unsigned char)(c / baud_rate);
if (a == 0 || a == 0xFF) return -EINVAL;
if ((c / a - baud_rate) > (baud_rate - c / (a + 1)))
a ++;
a = 256 - a;
break;
}
*factor = a;
*divisor = b;
return 0;
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27))
static void ch34x_set_termios( struct tty_struct *tty,
struct usb_serial_port *port, struct ktermios *old_termios )
{
#else
static void ch34x_set_termios( struct usb_serial_port *port,
struct ktermios *old_termios )
{
struct tty_struct *tty = port->tty;
#endif
struct usb_serial *serial = port->serial;
struct ch34x_private *priv = usb_get_serial_port_data(port);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,1)) //sure
struct ktermios *termios = &tty->termios;
#else
struct ktermios *termios = tty->termios;
#endif
unsigned int baud_rate;
unsigned int cflag;
unsigned long flags;
u8 control;
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;
#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
dbg_ch34x("%s - port:%d", __func__, port->number);
#else
dbg_ch34x("%s - port:%d", __func__, port->port_number);
#endif
spin_lock_irqsave( &priv->lock, flags );
if( !priv->termios_initialized ) {
*(termios) = tty_std_termios;
termios->c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
termios->c_ispeed = 9600;
termios->c_ospeed = 9600;
priv->termios_initialized = 1;
}
spin_unlock_irqrestore( &priv->lock, flags );
/*
* The ch34x is reported to lose bytes if you change serial setting
* even to the same vaules as before. Thus we actually need to filter
* in this specific case.
*/
if( !tty_termios_hw_change(termios, old_termios) )
return;
cflag = termios->c_cflag;
#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
dbg_ch34x("%s (%d) cflag=0x%x\n", __func__, port->number, cflag);
#else
dbg_ch34x("%s (%d) cflag=0x%x\n", __func__, port->port_number, cflag);
#endif
// Get the byte size
switch( cflag & CSIZE )
{
case CS5:
reg_value |= 0x00;
break;
case CS6:
reg_value |= 0x01;
break;
case CS7:
reg_value |= 0x02;
break;
case CS8:
reg_value |= 0x03;
break;
default:
reg_value |= 0x03;
break;
}
dbg_ch34x("%s - data bits = %d", __func__, reg_value + 0x05 );
// Figure out the stop bits
if( cflag & CSTOPB ) {
reg_value |= 0x04;
dbg_ch34x("%s - stop bits = 2", __func__);
}
else
dbg_ch34x("%s - stop bits = 1", __func__);
// Determine the parity
if (cflag & PARENB) {
if (cflag & CMSPAR) {
if (cflag & PARODD) {
reg_value |= (0x28 | 0x00);
dbg_ch34x("%s - parity = mark", __func__);
} else {
reg_value |= (0x38 | 0x10);
dbg_ch34x("%s - parity = space", __func__);
}
} else {
if (cflag & PARODD) {
reg_value |= (0x08 | 0x00);
dbg_ch34x("%s - parity = odd", __func__);
} else {
reg_value |= (0x18 | 0x10);
dbg_ch34x("%s - parity = even", __func__);
}
}
}
else
dbg_ch34x("%s - parity = none", __func__);
// Determine the baud rate
baud_rate = tty_get_baud_rate( tty );
dbg_ch34x("%s = baud_rate = %d", __func__, baud_rate);
ch34x_get_baud_rate( baud_rate, &factor, &divisor );
dbg_ch34x("----->>>> baud_rate = %d, factor:0x%x, divisor:0x%x",
baud_rate, factor, divisor );
//enable SFR_UART RX and TX
reg_value |= 0xc0;
//enable SFR_UART Control register and timer
reg_count |= 0x9c;
value |= reg_count;
value |= (unsigned short)reg_value << 8;
index |= 0x80 | divisor;
index |= (unsigned short)factor << 8;
ch34x_vendor_write( VENDOR_SERIAL_INIT, value, index, serial, NULL, 0 );
// change control lines if we are switching to or from B0
spin_lock_irqsave( &priv->lock, flags );
control = priv->line_control;
if( (cflag & CBAUD) == B0 )
priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS);
else
priv->line_control |= (CONTROL_DTR | CONTROL_RTS);
if( control != priv->line_control ) {
control = priv->line_control;
spin_unlock_irqrestore( &priv->lock, flags );
set_control_lines( serial, control );
}
else
spin_unlock_irqrestore( &priv->lock, flags );
if( cflag & CRTSCTS )
ch34x_vendor_write( VENDOR_WRITE, 0x2727, 0x0101, serial, NULL, 0);
// FIXME: Need to read back resulting baud rate
if( baud_rate )
tty_encode_baud_rate(tty, baud_rate, baud_rate);
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,3,3))
static int ch34x_tiocmget( struct tty_struct *tty )
{
struct usb_serial_port *port = tty->driver_data;
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27))
static int ch34x_tiocmget( struct tty_struct *tty,
struct file *filp )
{
struct usb_serial_port *port = tty->driver_data;
#else
static int ch34x_tiocmget( struct usb_serial_port *port,
struct file *filp )
{
#endif
struct ch34x_private *priv = usb_get_serial_port_data(port);
unsigned long flags;
unsigned int mcr;
/*unsigned int msr;*/
unsigned int retval;
#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
dbg_ch34x("%s - port:%d", __func__, port->number);
#else
dbg_ch34x("%s - port:%d", __func__, port->port_number);
#endif
if( !usb_get_intfdata( port->serial->interface) )
return -ENODEV;
spin_lock_irqsave( &priv->lock, flags );
mcr = priv->line_control;
spin_unlock_irqrestore( &priv->lock, flags );
retval = ((mcr & CONTROL_DTR) ? TIOCM_DTR : 0) |
((mcr & CONTROL_RTS) ? TIOCM_RTS : 0) |
((mcr & UART_CTS) ? TIOCM_CTS : 0) |
((mcr & UART_DSR) ? TIOCM_DSR : 0) |
((mcr & UART_RING) ? TIOCM_RI : 0) |
((mcr & UART_DCD) ? TIOCM_CD : 0);
dbg_ch34x("%s - retval=0x%x", __func__, retval);
return retval;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) && \
LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) )
static void ch34x_close( struct tty_struct *tty,
struct usb_serial_port *port, struct file *filp )
{
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) )
static void ch34x_close( struct usb_serial_port *port )
{
struct tty_struct *tty = port->port.tty;
#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27))
static void ch34x_close( struct usb_serial_port *port,
struct file *filp )
{
struct tty_struct *tty = port->tty;
#endif
struct ch34x_private *priv = usb_get_serial_port_data(port);
unsigned long flags;
unsigned int c_cflag;
int bps;
long timeout;
wait_queue_entry_t wait;
#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
dbg_ch34x("%s - port:%d", __func__, port->number);
#else
dbg_ch34x("%s - port:%d", __func__, port->port_number);
#endif
// dbg_ch34x("Addr of priv: 0x%x", priv);
// dbg_ch34x("Addr of tty: 0x%x", tty);
// wait for data do drain from the buffer
/*
spin_lock_irqsave( &priv->lock, flags );
timeout = CH34x_CLOSING_WAIT;
init_waitqueue_entry( &wait, current );
add_wait_queue( &tty->write_wait, &wait );
for(;;) {
set_current_state( TASK_INTERRUPTIBLE );
if( ch34x_buf_data_avail(priv->buf) == 0 || timeout == 0 ||
signal_pending(current) || port->serial->disconnected )
break;
spin_unlock_irqrestore( &priv->lock, flags );
timeout = schedule_timeout( timeout );
spin_lock_irqsave( &priv->lock, flags );
}
set_current_state( TASK_RUNNING );
remove_wait_queue( &tty->write_wait, &wait );
*/
spin_lock_irqsave( &priv->lock, flags );
// clear out any remaining data in the buffer
ch34x_buf_clear( priv->buf );
spin_unlock_irqrestore( &priv->lock, flags );
/*
bps = tty_get_baud_rate( tty );
if( bps > 1200 )
timeout = max( (HZ * 2560) / bps, HZ / 10 );
else
timeout = 2 * HZ;
schedule_timeout_interruptible(timeout);
*/
// shutdown our urbs
usb_kill_urb(port->interrupt_in_urb);
usb_kill_urb(port->read_urb);
usb_kill_urb(port->write_urb);
//usb_serial_generic_close(port, filp);
if( tty ) {
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,1)) //sure
c_cflag = tty->termios.c_cflag;
#else
c_cflag = tty->termios->c_cflag;
#endif
if( c_cflag & HUPCL ) {
// drop DTR and RTS
spin_lock_irqsave( &priv->lock, flags );
priv->line_control = 0;
spin_unlock_irqrestore( &priv->lock, flags );
set_control_lines( port->serial, 0 );
}
}
// usb_serial_generic_close(port);
// usb_kill_urb(port->interrupt_in_urb);
}
// kernel version
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) \
&& LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27))
static int ch34x_open( struct tty_struct *tty,
struct usb_serial_port *port, struct file *filp )
{
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32))
static int ch34x_open( struct tty_struct *tty,
struct usb_serial_port *port )
{
#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27))
static int ch34x_open( struct usb_serial_port *port,
struct file *filp )
{
struct tty_struct *tty = port->tty;
#endif
struct ktermios tmp_termios;
struct usb_serial *serial = port->serial;
int retval;
#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
dbg_ch34x("%s - port:%d", __func__, port->number );
#else
dbg_ch34x("%s - port:%d", __func__, port->port_number );
#endif
usb_clear_halt( serial->dev, port->write_urb->pipe );
usb_clear_halt( serial->dev, port->read_urb->pipe );
if( tty ) {
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27))
ch34x_set_termios( tty, port, &tmp_termios );
#else
ch34x_set_termios( port, &tmp_termios );
#endif
}
dbg_ch34x("%s - submit read urb", __func__);
port->read_urb->dev = serial->dev;
retval = usb_submit_urb( port->read_urb, GFP_KERNEL );
if(retval) {
dev_err( &port->dev, "%s - failed submit read urb,error %d\n",
__func__, retval );
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) && \
LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) )
ch34x_close(tty, port, NULL);
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32))
ch34x_close(port);
#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27))
ch34x_close(port, filp);
#endif
goto err_out;
}
dbg_ch34x("%s - submit interrupt urb", __func__ );
port->interrupt_in_urb->dev = serial->dev;
retval = usb_submit_urb( port->interrupt_in_urb, GFP_KERNEL );
if(retval) {
dev_err( &port->dev, "%s - failed submit interrupt urb,error %d\n",
__func__, retval );
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) && \
LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) )
ch34x_close(tty, port, NULL);
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32))
ch34x_close(port);
#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27))
ch34x_close(port, filp);
#endif
goto err_out;
}
err_out:
return retval;
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,3,3))
static int ch34x_tiocmset( struct tty_struct *tty,
unsigned int set, unsigned int clear )
{
struct usb_serial_port *port = tty->driver_data;
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27))
static int ch34x_tiocmset( struct tty_struct *tty,
struct file *filp, unsigned int set, unsigned int clear )
{
struct usb_serial_port *port = tty->driver_data;
#else
static int ch34x_tiocmset( struct usb_serial_port *port,
struct file *filp, unsigned int set, unsigned int clear )
{
#endif
struct ch34x_private *priv = usb_get_serial_port_data(port);
unsigned long flags;
/*unsigned int mcr = priv->line_control;*/
u8 control;
#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
dbg_ch34x("%s - port:%d", __func__, port->number);
#else
dbg_ch34x("%s - port:%d", __func__, port->port_number);
#endif
if( !usb_get_intfdata(port->serial->interface) )
return -ENODEV;
spin_lock_irqsave( &priv->lock, flags );
if( set & TIOCM_RTS )
priv->line_control |= CONTROL_RTS;
if( set & TIOCM_DTR )
priv->line_control |= CONTROL_DTR;
if( clear & TIOCM_RTS )
priv->line_control &= ~CONTROL_RTS;
if( clear & TIOCM_DTR )
priv->line_control &= ~CONTROL_DTR;
control = priv->line_control;
spin_unlock_irqrestore( &priv->lock, flags );
return set_control_lines( port->serial, control );
}
static int wait_modem_info( struct usb_serial_port *port,
unsigned int arg )
{
struct ch34x_private *priv = usb_get_serial_port_data(port);
unsigned long flags;
unsigned int prevstatus;
unsigned int status;
unsigned int changed;
#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
dbg_ch34x("%s -port:%d", __func__, port->number);
#else
dbg_ch34x("%s -port:%d", __func__, port->port_number);
#endif
spin_lock_irqsave( &priv->lock, flags );
prevstatus = priv->line_status;
spin_unlock_irqrestore( &priv->lock, flags );
while(1) {
wait_event_interruptible( wq, wait_flag != 0 );
wait_flag = 0;
// see if a signal did it
if( signal_pending(current) )
return -ERESTARTSYS;
spin_lock_irqsave( &priv->lock, flags );
status = priv->line_status;
spin_unlock_irqrestore( &priv->lock, flags );
changed = prevstatus ^ status;
if( ((arg & TIOCM_RNG) && (changed & UART_RING)) ||
((arg & TIOCM_DSR) && (changed & UART_DSR)) ||
((arg & TIOCM_CD) && (changed & UART_DCD)) ||
((arg & TIOCM_CTS) && (changed & UART_CTS)) )
return 0;
prevstatus = status;
}
// Not reatched
return 0;
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,3,3))
static int ch34x_ioctl( struct tty_struct *tty,
unsigned int cmd, unsigned long arg )
{
struct usb_serial_port *port = tty->driver_data;
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27))
static int ch34x_ioctl( struct tty_struct *tty,
struct file *filp, unsigned int cmd, unsigned long arg )
{
struct usb_serial_port *port = tty->driver_data;
#else
static int ch34x_ioctl( struct usb_serial_port *port,
struct file *filp, unsigned int cmd, unsigned long arg )
{
//struct usb_serial_port *port = tty->driver_data;
#endif
#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
dbg_ch34x("%s - port:%d, cmd=0x%04x", __func__, port->number, cmd);
#else
dbg_ch34x("%s - port:%d, cmd=0x%04x", __func__, port->port_number, cmd);
#endif
switch(cmd)
{
// Note here
case TIOCMIWAIT:
#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
dbg_ch34x("%s - port:%d TIOCMIWAIT", __func__, port->number);
#else
dbg_ch34x("%s - port:%d TIOCMIWAIT", __func__, port->port_number);
#endif
return wait_modem_info(port, arg);
default:
dbg_ch34x("%s not supported=0x%04x", __func__, cmd);
break;
}
return -ENOIOCTLCMD;
}
static void ch34x_send( struct usb_serial_port *port )
{
int count;
int retval;
struct ch34x_private *priv = usb_get_serial_port_data( port );
unsigned long flags;
#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
dbg_ch34x("%s - port:%d", __func__, port->number);
#else
dbg_ch34x("%s - port:%d", __func__, port->port_number);
#endif
spin_lock_irqsave( &priv->lock, flags );
if( priv->write_urb_in_use ) {
spin_unlock_irqrestore( &priv->lock, flags );
return;
}
count = ch34x_buf_get( priv->buf, port->write_urb->transfer_buffer,
port->bulk_out_size );
if( count == 0 ) {
spin_unlock_irqrestore( &priv->lock, flags );
return;
}
priv->write_urb_in_use = 1;
spin_unlock_irqrestore( &priv->lock, flags );
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,1))
usb_serial_debug_data( &port->dev, __func__, count,
port->write_urb->transfer_buffer );
#else
usb_serial_debug_data( debug, &port->dev, __func__, count,
port->write_urb->transfer_buffer );
#endif
port->write_urb->transfer_buffer_length = count;
port->write_urb->dev = port->serial->dev;
retval = usb_submit_urb( port->write_urb, GFP_ATOMIC );
if( retval ) {
dev_err( &port->dev, "%s - failed submitting write urb,error %d\n"
, __func__, retval );
priv->write_urb_in_use = 0;
// reschedule ch34x_send
}
usb_serial_port_softint( port );
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27))
static int ch34x_write( struct tty_struct *tty,
struct usb_serial_port *port, const unsigned char *buf, int count )
#else
static int ch34x_write( struct usb_serial_port *port,
const unsigned char *buf, int count )
#endif
{
struct ch34x_private *priv = usb_get_serial_port_data(port);
unsigned long flags;
#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
dbg_ch34x("%s - port:%d, %d bytes", __func__, port->number, count);
#else
dbg_ch34x("%s - port:%d, %d bytes", __func__, port->port_number, count);
#endif
if( !count )
return count;
spin_lock_irqsave( &priv->lock, flags );
count = ch34x_buf_put( priv->buf, buf, count );
spin_unlock_irqrestore( &priv->lock, flags );
ch34x_send(port);
return count;
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27))
static int ch34x_write_room( struct tty_struct *tty )
{
struct usb_serial_port *port = tty->driver_data;
#else
static int ch34x_write_room( struct usb_serial_port *port )
{
#endif
struct ch34x_private *priv = usb_get_serial_port_data( port );
int room = 0;
unsigned long flags;
#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
dbg_ch34x("%s - port:%d", __func__, port->number);
#else
dbg_ch34x("%s - port:%d", __func__, port->port_number);
#endif
spin_lock_irqsave( &priv->lock, flags );
room = ch34x_buf_space_avail( priv->buf );
spin_unlock_irqrestore( &priv->lock, flags );
dbg_ch34x("%s - room:%d", __func__, room );
return room;
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27))
static int ch34x_chars_in_buffer( struct tty_struct *tty )
{
struct usb_serial_port *port = tty->driver_data;
#else
static int ch34x_chars_in_buffer( struct usb_serial_port *port )
{
#endif
struct ch34x_private *priv = usb_get_serial_port_data(port);
int chars = 0;
unsigned long flags;
#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
dbg_ch34x("%s - port:%d", __func__, port->number);
#else
dbg_ch34x("%s - port:%d", __func__, port->port_number);
#endif
spin_lock_irqsave( &priv->lock, flags );
chars = ch34x_buf_data_avail( priv->buf );
spin_unlock_irqrestore( &priv->lock, flags );
dbg_ch34x("%s - chars:%d", __func__, chars );
return chars;
}
static int ch34x_attach( struct usb_serial *serial )
{
struct ch34x_private *priv;
int i;
char buf[8];
dbg_ch34x("%s", __func__);
for( i = 0; i < serial->num_ports; ++i ) {
priv = kzalloc( sizeof(struct ch34x_private), GFP_KERNEL );
if( !priv )
goto cleanup;
spin_lock_init( &priv->lock );
priv->buf = ch34x_buf_alloc( CH34x_BUF_SIZE );
if( priv->buf == NULL ) {
kfree( priv );
goto cleanup;
}
init_waitqueue_head( &priv->delta_msr_wait );
usb_set_serial_port_data( serial->port[i], priv );
}
ch34x_vendor_read( VENDOR_VERSION, 0x0000, 0x0000,
serial, buf, 0x02 );
ch34x_vendor_write( VENDOR_SERIAL_INIT, 0x0000, 0x0000,
serial, NULL, 0x00 );
ch34x_vendor_write( VENDOR_WRITE, 0x1312, 0xD982,
serial, NULL, 0x00 );
ch34x_vendor_write( VENDOR_WRITE, 0x0F2C, 0x0004,
serial, NULL, 0x00 );
ch34x_vendor_read( VENDOR_READ, 0x2518, 0x0000,
serial, buf, 0x02 );
ch34x_vendor_write( VENDOR_WRITE, 0x2727, 0x0000,
serial, NULL, 0x00 );
ch34x_vendor_write( VENDOR_MODEM_OUT, 0x009F, 0x0000,
serial, NULL, 0x00 );
return 0;
cleanup:
for( --i; i >= 0; --i ) {
priv = usb_get_serial_port_data( serial->port[i] );
ch34x_buf_free( priv->buf );
kfree( priv );
usb_set_serial_port_data( serial->port[i], NULL );
}
return -ENOMEM;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32))
static void ch34x_shutdown( struct usb_serial *serial )
{
struct ch34x_private *priv;
int i;
dbg_ch34x("%s", __func__);
for( i = 0; i < serial->num_ports; ++i ) {
priv = usb_get_serial_port_data( serial->port[i] );
if( priv ) {
ch34x_buf_free( priv->buf );
kfree( priv );
usb_set_serial_port_data( serial->port[i], NULL );
}
}
}
#endif
static void ch34x_update_line_status( struct usb_serial_port *port,
unsigned char *data, unsigned int actual_length )
{
struct ch34x_private *priv = usb_get_serial_port_data( port );
unsigned long flags;
u8 length = UART_STATE + 0x04;
if( actual_length < length )
return;
// Save off the uart status for others to look at
spin_lock_irqsave( &priv->lock, flags );
priv->line_status = data[UART_STATE];
priv->line_control = data[PORTB_STATE];
spin_unlock_irqrestore( &priv->lock, flags );
wait_flag = 1;
wake_up_interruptible( &priv->delta_msr_wait );
}
static void ch34x_read_int_callback( struct urb *urb )
{
struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
unsigned char *data = urb->transfer_buffer;
unsigned int actual_length = urb->actual_length;
int status = urb->status;
int retval;
#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
dbg_ch34x("%s port:%d", __func__, port->number );
#else
dbg_ch34x("%s port:%d", __func__, port->port_number );
#endif
switch( status ) {
case 0: //success
break;
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN: //this urb is terminated, clean up
dbg_ch34x("%s - urb shutting down with status:%d", __func__, status );
return;
default:
dbg_ch34x("%s - nonzero urb status received:%d", __func__, status );
goto exit;
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,1))
usb_serial_debug_data( &port->dev, __func__,
urb->actual_length, urb->transfer_buffer );
#else
usb_serial_debug_data( debug, &port->dev,
__func__, urb->actual_length, urb->transfer_buffer );
#endif
ch34x_update_line_status( port, data, actual_length );
exit:
retval = usb_submit_urb( urb, GFP_ATOMIC );
if( retval )
dev_err( &urb->dev->dev, "%s - usb_submit_urb failed with result %d\n",
__func__, retval );
}
static void ch34x_read_bulk_callback( struct urb *urb )
{
struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
struct ch34x_private *priv = usb_get_serial_port_data( port );
struct tty_struct *tty;
unsigned char *data = urb->transfer_buffer;
unsigned long flags;
int i;
int retval;
int status = urb->status;
u8 line_status;
char tty_flag;
#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
dbg_ch34x("%s - port:%d", __func__, port->number );
#else
dbg_ch34x("%s - port:%d", __func__, port->port_number);
#endif
if( status ) {
dbg_ch34x("%s - urb status=%d", __func__, status );
if( status == -EPROTO ) {
// CH34x mysteriously fails with -EPROTO reschedule the read
dbg_ch34x("%s - caught -EPROTO, resubmitting the urb", __func__);
urb->dev = port->serial->dev;
retval = usb_submit_urb( urb, GFP_ATOMIC );
if( retval ) {
dev_err( &urb->dev->dev,
"%s - failed resubmitting read urb, error %d\n",
__func__, retval );
return;
}
}
dbg_ch34x("%s - unable to handle the error, exiting.", __func__);
return;
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,1))
usb_serial_debug_data( &port->dev, __func__,
urb->actual_length, data );
#else
usb_serial_debug_data( debug, &port->dev,
__func__, urb->actual_length, data );
#endif
// get tty_flag from status
tty_flag = TTY_NORMAL;
spin_lock_irqsave( &priv->lock, flags );
line_status = priv->line_status;
priv->line_status &= ~UART_STATE_TRANSIENT_MASK;
spin_unlock_irqrestore( &priv->lock, flags );
wait_flag = 1;
wake_up_interruptible( &priv->delta_msr_wait );
// break takes precedence over parity,
// which takes precedence over framing errors
if( line_status & UART_PARITY_ERROR )
tty_flag = TTY_PARITY;
else if( line_status & UART_OVERRUN_ERROR )
tty_flag = TTY_OVERRUN;
else if( line_status & UART_FRAME_ERROR )
tty_flag = TTY_FRAME;
dbg_ch34x("%s - tty_flag=%d", __func__, tty_flag);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27))
tty = port->port.tty;
#else
tty = port->tty;
#endif
if( tty && urb->actual_length ) {
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,1))
tty_buffer_request_room( tty->port, urb->actual_length + 1);
#else
tty_buffer_request_room( tty, urb->actual_length + 1 );
#endif
// overrun is special, not associated with a char
if( line_status & UART_OVERRUN_ERROR )
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,1))
tty_insert_flip_char( tty->port, 0, TTY_OVERRUN );
#else
tty_insert_flip_char( tty, 0, TTY_OVERRUN );
#endif
for( i = 0; i < urb->actual_length; ++i )
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,1))
tty_insert_flip_char( tty->port, data[i], tty_flag );
#else
tty_insert_flip_char( tty, data[i], tty_flag );
#endif
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,1))
tty_flip_buffer_push( tty->port );
#else
tty_flip_buffer_push( tty );
#endif
}
//Schedule the next read _if_ we are still open
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27))
if( port->open_count )
#endif
{
urb->dev = port->serial->dev;
retval = usb_submit_urb( urb, GFP_ATOMIC );
if( retval )
dev_err( &urb->dev->dev,
"%s - fialed resubmitting read urb, error %d\n",
__func__, retval );
}
return;
}
static void ch34x_write_bulk_callback( struct urb *urb )
{
struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
struct ch34x_private *priv = usb_get_serial_port_data(port);
int retval;
int status = urb->status;
#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
dbg_ch34x("%s - port:%d", __func__, port->number );
#else
dbg_ch34x("%s - port:%d", __func__, port->port_number );
#endif
switch( status ) {
case 0: //success
break;
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
// this urb is terminated, clean up
dbg_ch34x("%s - urb shutting down with status:%d", __func__, status);
priv->write_urb_in_use = 0;
return;
default:
// error in the urb, so we have to resubmit it
dbg_ch34x("%s - Overflow in write", __func__);
dbg_ch34x("%s - nonzero write bulk status received:%d", __func__, status);
port->write_urb->transfer_buffer_length = 1;
port->write_urb->dev = port->serial->dev;
retval = usb_submit_urb(port->write_urb, GFP_ATOMIC);
if( retval )
dev_err( &urb->dev->dev,
"%s - failed resubmitting write urv, error:%d\n",
__func__, retval );
else
return;
}
priv->write_urb_in_use = 0;
// send any buffered data
ch34x_send(port);
}
static struct usb_serial_driver ch34x_device = {
.driver = {
.owner = THIS_MODULE,
.name = "ch34x",
},
.id_table = id_table,
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,5,1))
.usb_driver = &ch34x_driver,
#endif
.num_ports = 1,
.open = ch34x_open,
.close = ch34x_close,
.write = ch34x_write,
.ioctl = ch34x_ioctl,
.set_termios = ch34x_set_termios,
.tiocmget = ch34x_tiocmget,
.tiocmset = ch34x_tiocmset,
.read_bulk_callback = ch34x_read_bulk_callback,
.read_int_callback = ch34x_read_int_callback,
.write_bulk_callback = ch34x_write_bulk_callback,
.write_room = ch34x_write_room,
.chars_in_buffer = ch34x_chars_in_buffer,
.attach = ch34x_attach,
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) )
.shutdown = ch34x_shutdown,
#endif
};
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,5))
static struct usb_serial_driver *const serial_driver [] = {
&ch34x_device, NULL
};
#endif
static int __init ch34x_init(void)
{
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,4,5))
int retval = 0;
retval = usb_serial_register( &ch34x_device );
if( retval ) {
goto err_usb_serial_register;
}
retval = usb_register( &ch34x_driver );
if( retval ) {
goto err_usb_register;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32))
info( DRIVER_DESC );
#endif
return 0;
err_usb_register:
usb_deregister( &ch34x_driver );
err_usb_serial_register:
usb_serial_deregister( &ch34x_device );
return retval;
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,1))
return usb_serial_register_drivers( serial_driver,
KBUILD_MODNAME, id_table );
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,5) && \
LINUX_VERSION_CODE < KERNEL_VERSION(3,5,1))
return usb_serial_register_drivers(&ch34x_driver, serial_driver);
#endif
}
static void __exit ch34x_exit(void)
{
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,4,5))
usb_deregister( &ch34x_driver );
usb_serial_deregister( &ch34x_device );
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,1))
usb_serial_deregister_drivers( serial_driver );
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,5) && \
LINUX_VERSION_CODE < KERNEL_VERSION(3,5,1))
usb_serial_deregister_drivers(&ch34x_driver, serial_driver);
#endif
}
module_init( ch34x_init );
module_exit( ch34x_exit );
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_LICENSE("GPL");
You can’t perform that action at this time.