// 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 #ifndef KERNEL_VERSION #define KERNEL_VERSION(ver, rel, seq) ((ver << 16) | (rel << 8) | (seq)) #endif #include #include #include #include #include #include #include #include #include #include #include #include //#include #include #include #if LINUX_VERSION_CODE < KERNEL_VERSION(4,11,0) #include #else #include #endif MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("WCH CH34x USB to serial adaptor driver"); MODULE_AUTHOR(""); #define DRIVER_DESC "WCH CH34x USB to serial adaptor driver" #define DRIVER_AUTHOR "" #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(6,0,1)) static void ch34x_set_termios( struct tty_struct *tty, struct usb_serial_port *port, const struct ktermios *old_termios ) { #elif (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)) 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 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; #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 ); // clear out any remaining data in the buffer ch34x_buf_clear( priv->buf ); spin_unlock_irqrestore( &priv->lock, flags ); // shutdown our urbs usb_kill_urb(port->interrupt_in_urb); usb_kill_urb(port->read_urb); usb_kill_urb(port->write_urb); if( tty ) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,1)) 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 ); } } } // 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(5,14,0)) && GCC_VERSION >= 100100 static unsigned int ch34x_write_room( struct tty_struct *tty ) { struct usb_serial_port *port = tty->driver_data; #elif (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 ); unsigned 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:%u", __func__, room ); return room; } #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0)) && GCC_VERSION >= 100100 static unsigned int ch34x_chars_in_buffer( struct tty_struct *tty ) { struct usb_serial_port *port = tty->driver_data; #elif (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); unsigned 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:%u", __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 );