linux/drivers/usb/serial/mos7720.c
Oliver Neukum e8e30c765c USB: necessary update for mos7720 driver
these devices have a shared interrupt endpoint. For serialcore to pass
an interrupt endpoint to a subdriver, the subdriver must define and
_export_ a fitting callback. The mos7720 driver failed to do so. This led
invariably to an oops upon open. This patch fixes it. The driver is useless
without it. Please try to get this into 2.6.21 and the stable kernels that
have this driver.

Signed-off-by: Oliver Neukum <oneukum@suse.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2007-03-19 13:22:18 -07:00

1677 lines
42 KiB
C

/*
* mos7720.c
* Controls the Moschip 7720 usb to dual port serial convertor
*
* Copyright 2006 Moschip Semiconductor Tech. Ltd.
*
* 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, version 2 of the License.
*
* Developed by:
* VijayaKumar.G.N. <vijaykumar@aspirecom.net>
* AjayKumar <ajay@aspirecom.net>
* Gurudeva.N. <gurudev@aspirecom.net>
*
* Cleaned up from the original by:
* Greg Kroah-Hartman <gregkh@suse.de>
*
* Originally based on drivers/usb/serial/io_edgeport.c which is:
* Copyright (C) 2000 Inside Out Networks, All rights reserved.
* Copyright (C) 2001-2002 Greg Kroah-Hartman <greg@kroah.com>
*/
#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/module.h>
#include <linux/spinlock.h>
#include <linux/serial.h>
#include <linux/serial_reg.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include <asm/uaccess.h>
/*
* Version Information
*/
#define DRIVER_VERSION "1.0.0.4F"
#define DRIVER_AUTHOR "Aspire Communications pvt Ltd."
#define DRIVER_DESC "Moschip USB Serial Driver"
/* default urb timeout */
#define MOS_WDR_TIMEOUT (HZ * 5)
#define MOS_PORT1 0x0200
#define MOS_PORT2 0x0300
#define MOS_VENREG 0x0000
#define MOS_MAX_PORT 0x02
#define MOS_WRITE 0x0E
#define MOS_READ 0x0D
/* Interrupt Rotinue Defines */
#define SERIAL_IIR_RLS 0x06
#define SERIAL_IIR_RDA 0x04
#define SERIAL_IIR_CTI 0x0c
#define SERIAL_IIR_THR 0x02
#define SERIAL_IIR_MS 0x00
#define NUM_URBS 16 /* URB Count */
#define URB_TRANSFER_BUFFER_SIZE 32 /* URB Size */
/* This structure holds all of the local port information */
struct moschip_port
{
__u8 shadowLCR; /* last LCR value received */
__u8 shadowMCR; /* last MCR value received */
__u8 shadowMSR; /* last MSR value received */
char open;
struct async_icount icount;
struct usb_serial_port *port; /* loop back to the owner */
struct urb *write_urb_pool[NUM_URBS];
};
/* This structure holds all of the individual serial device information */
struct moschip_serial
{
int interrupt_started;
};
static int debug;
#define USB_VENDOR_ID_MOSCHIP 0x9710
#define MOSCHIP_DEVICE_ID_7720 0x7720
#define MOSCHIP_DEVICE_ID_7715 0x7715
static struct usb_device_id moschip_port_id_table [] = {
{ USB_DEVICE(USB_VENDOR_ID_MOSCHIP,MOSCHIP_DEVICE_ID_7720) },
{ } /* terminating entry */
};
MODULE_DEVICE_TABLE(usb, moschip_port_id_table);
/*
* mos7720_interrupt_callback
* this is the callback function for when we have received data on the
* interrupt endpoint.
*/
static void mos7720_interrupt_callback(struct urb *urb)
{
int result;
int length;
__u32 *data;
unsigned int status;
__u8 sp1;
__u8 sp2;
__u8 st;
dbg("%s"," : Entering\n");
if (!urb) {
dbg("%s","Invalid Pointer !!!!:\n");
return;
}
switch (urb->status) {
case 0:
/* success */
break;
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
/* this urb is terminated, clean up */
dbg("%s - urb shutting down with status: %d", __FUNCTION__,
urb->status);
return;
default:
dbg("%s - nonzero urb status received: %d", __FUNCTION__,
urb->status);
goto exit;
}
length = urb->actual_length;
data = urb->transfer_buffer;
/* Moschip get 4 bytes
* Byte 1 IIR Port 1 (port.number is 0)
* Byte 2 IIR Port 2 (port.number is 1)
* Byte 3 --------------
* Byte 4 FIFO status for both */
if (length && length > 4) {
dbg("Wrong data !!!");
return;
}
status = *data;
sp1 = (status & 0xff000000)>>24;
sp2 = (status & 0x00ff0000)>>16;
st = status & 0x000000ff;
if ((sp1 & 0x01) || (sp2 & 0x01)) {
/* No Interrupt Pending in both the ports */
dbg("No Interrupt !!!");
} else {
switch (sp1 & 0x0f) {
case SERIAL_IIR_RLS:
dbg("Serial Port 1: Receiver status error or address "
"bit detected in 9-bit mode\n");
break;
case SERIAL_IIR_CTI:
dbg("Serial Port 1: Receiver time out");
break;
case SERIAL_IIR_MS:
dbg("Serial Port 1: Modem status change");
break;
}
switch (sp2 & 0x0f) {
case SERIAL_IIR_RLS:
dbg("Serial Port 2: Receiver status error or address "
"bit detected in 9-bit mode");
break;
case SERIAL_IIR_CTI:
dbg("Serial Port 2: Receiver time out");
break;
case SERIAL_IIR_MS:
dbg("Serial Port 2: Modem status change");
break;
}
}
exit:
result = usb_submit_urb(urb, GFP_ATOMIC);
if (result)
dev_err(&urb->dev->dev,
"%s - Error %d submitting control urb\n",
__FUNCTION__, result);
return;
}
/*
* mos7720_bulk_in_callback
* this is the callback function for when we have received data on the
* bulk in endpoint.
*/
static void mos7720_bulk_in_callback(struct urb *urb)
{
int status;
unsigned char *data ;
struct usb_serial_port *port;
struct moschip_port *mos7720_port;
struct tty_struct *tty;
if (urb->status) {
dbg("nonzero read bulk status received: %d",urb->status);
return;
}
mos7720_port = urb->context;
if (!mos7720_port) {
dbg("%s","NULL mos7720_port pointer \n");
return ;
}
port = mos7720_port->port;
dbg("Entering...%s", __FUNCTION__);
data = urb->transfer_buffer;
tty = port->tty;
if (tty && urb->actual_length) {
tty_buffer_request_room(tty, urb->actual_length);
tty_insert_flip_string(tty, data, urb->actual_length);
tty_flip_buffer_push(tty);
}
if (!port->read_urb) {
dbg("URB KILLED !!!");
return;
}
if (port->read_urb->status != -EINPROGRESS) {
port->read_urb->dev = port->serial->dev;
status = usb_submit_urb(port->read_urb, GFP_ATOMIC);
if (status)
dbg("usb_submit_urb(read bulk) failed, status = %d",
status);
}
}
/*
* mos7720_bulk_out_data_callback
* this is the callback function for when we have finished sending serial
* data on the bulk out endpoint.
*/
static void mos7720_bulk_out_data_callback(struct urb *urb)
{
struct moschip_port *mos7720_port;
struct tty_struct *tty;
if (urb->status) {
dbg("nonzero write bulk status received:%d", urb->status);
return;
}
mos7720_port = urb->context;
if (!mos7720_port) {
dbg("NULL mos7720_port pointer");
return ;
}
dbg("Entering .........");
tty = mos7720_port->port->tty;
if (tty && mos7720_port->open)
tty_wakeup(tty);
}
/*
* send_mos_cmd
* this function will be used for sending command to device
*/
static int send_mos_cmd(struct usb_serial *serial, __u8 request, __u16 value,
__u16 index, void *data)
{
int status;
unsigned int pipe;
u16 product = le16_to_cpu(serial->dev->descriptor.idProduct);
__u8 requesttype;
__u16 size = 0x0000;
if (value < MOS_MAX_PORT) {
if (product == MOSCHIP_DEVICE_ID_7715) {
value = value*0x100+0x100;
} else {
value = value*0x100+0x200;
}
} else {
value = 0x0000;
if ((product == MOSCHIP_DEVICE_ID_7715) &&
(index != 0x08)) {
dbg("serial->product== MOSCHIP_DEVICE_ID_7715");
//index = 0x01 ;
}
}
if (request == MOS_WRITE) {
request = (__u8)MOS_WRITE;
requesttype = (__u8)0x40;
value = value + (__u16)*((unsigned char *)data);
data = NULL;
pipe = usb_sndctrlpipe(serial->dev, 0);
} else {
request = (__u8)MOS_READ;
requesttype = (__u8)0xC0;
size = 0x01;
pipe = usb_rcvctrlpipe(serial->dev,0);
}
status = usb_control_msg(serial->dev, pipe, request, requesttype,
value, index, data, size, MOS_WDR_TIMEOUT);
if (status < 0)
dbg("Command Write failed Value %x index %x\n",value,index);
return status;
}
static int mos7720_open(struct usb_serial_port *port, struct file * filp)
{
struct usb_serial *serial;
struct usb_serial_port *port0;
struct urb *urb;
struct moschip_serial *mos7720_serial;
struct moschip_port *mos7720_port;
int response;
int port_number;
char data;
int j;
serial = port->serial;
mos7720_port = usb_get_serial_port_data(port);
if (mos7720_port == NULL)
return -ENODEV;
port0 = serial->port[0];
mos7720_serial = usb_get_serial_data(serial);
if (mos7720_serial == NULL || port0 == NULL)
return -ENODEV;
usb_clear_halt(serial->dev, port->write_urb->pipe);
usb_clear_halt(serial->dev, port->read_urb->pipe);
/* Initialising the write urb pool */
for (j = 0; j < NUM_URBS; ++j) {
urb = usb_alloc_urb(0,GFP_ATOMIC);
mos7720_port->write_urb_pool[j] = urb;
if (urb == NULL) {
err("No more urbs???");
continue;
}
urb->transfer_buffer = kmalloc(URB_TRANSFER_BUFFER_SIZE,
GFP_KERNEL);
if (!urb->transfer_buffer) {
err("%s-out of memory for urb buffers.", __FUNCTION__);
continue;
}
}
/* Initialize MCS7720 -- Write Init values to corresponding Registers
*
* Register Index
* 1 : IER
* 2 : FCR
* 3 : LCR
* 4 : MCR
*
* 0x08 : SP1/2 Control Reg
*/
port_number = port->number - port->serial->minor;
send_mos_cmd(port->serial, MOS_READ, port_number, UART_LSR, &data);
dbg("SS::%p LSR:%x\n",mos7720_port, data);
dbg("Check:Sending Command ..........");
data = 0x02;
send_mos_cmd(serial, MOS_WRITE, MOS_MAX_PORT, 0x01, &data);
data = 0x02;
send_mos_cmd(serial, MOS_WRITE, MOS_MAX_PORT, 0x02, &data);
data = 0x00;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x01, &data);
data = 0x00;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x02, &data);
data = 0xCF;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x02, &data);
data = 0x03;
mos7720_port->shadowLCR = data;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x03, &data);
data = 0x0b;
mos7720_port->shadowMCR = data;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x04, &data);
data = 0x0b;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x04, &data);
data = 0x00;
send_mos_cmd(serial, MOS_READ, MOS_MAX_PORT, 0x08, &data);
data = 0x00;
send_mos_cmd(serial, MOS_WRITE, MOS_MAX_PORT, 0x08, &data);
/* data = 0x00;
send_mos_cmd(serial, MOS_READ, MOS_MAX_PORT, port_number + 1, &data);
data = 0x03;
send_mos_cmd(serial, MOS_WRITE, MOS_MAX_PORT, port_number + 1, &data);
data = 0x00;
send_mos_cmd(port->serial, MOS_WRITE, MOS_MAX_PORT, port_number + 1, &data);
*/
data = 0x00;
send_mos_cmd(serial, MOS_READ, MOS_MAX_PORT, 0x08, &data);
data = data | (port->number - port->serial->minor + 1);
send_mos_cmd(serial, MOS_WRITE, MOS_MAX_PORT, 0x08, &data);
data = 0x83;
mos7720_port->shadowLCR = data;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x03, &data);
data = 0x0c;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x00, &data);
data = 0x00;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x01, &data);
data = 0x03;
mos7720_port->shadowLCR = data;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x03, &data);
data = 0x0c;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x01, &data);
data = 0x0c;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x01, &data);
//Matrix
/* force low_latency on so that our tty_push actually forces *
* the data through,otherwise it is scheduled, and with *
* high data rates (like with OHCI) data can get lost. */
if (port->tty)
port->tty->low_latency = 1;
/* see if we've set up our endpoint info yet *
* (can't set it up in mos7720_startup as the *
* structures were not set up at that time.) */
if (!mos7720_serial->interrupt_started) {
dbg("Interrupt buffer NULL !!!");
/* not set up yet, so do it now */
mos7720_serial->interrupt_started = 1;
dbg("To Submit URB !!!");
/* set up our interrupt urb */
usb_fill_int_urb(port0->interrupt_in_urb, serial->dev,
usb_rcvintpipe(serial->dev,
port->interrupt_in_endpointAddress),
port0->interrupt_in_buffer,
port0->interrupt_in_urb->transfer_buffer_length,
mos7720_interrupt_callback, mos7720_port,
port0->interrupt_in_urb->interval);
/* start interrupt read for this mos7720 this interrupt *
* will continue as long as the mos7720 is connected */
dbg("Submit URB over !!!");
response = usb_submit_urb(port0->interrupt_in_urb, GFP_KERNEL);
if (response)
dev_err(&port->dev,
"%s - Error %d submitting control urb",
__FUNCTION__, response);
}
/* set up our bulk in urb */
usb_fill_bulk_urb(port->read_urb, serial->dev,
usb_rcvbulkpipe(serial->dev,
port->bulk_in_endpointAddress),
port->bulk_in_buffer,
port->read_urb->transfer_buffer_length,
mos7720_bulk_in_callback, mos7720_port);
response = usb_submit_urb(port->read_urb, GFP_KERNEL);
if (response)
dev_err(&port->dev,
"%s - Error %d submitting read urb", __FUNCTION__, response);
/* initialize our icount structure */
memset(&(mos7720_port->icount), 0x00, sizeof(mos7720_port->icount));
/* initialize our port settings */
mos7720_port->shadowMCR = UART_MCR_OUT2; /* Must set to enable ints! */
/* send a open port command */
mos7720_port->open = 1;
return 0;
}
/*
* mos7720_chars_in_buffer
* this function is called by the tty driver when it wants to know how many
* bytes of data we currently have outstanding in the port (data that has
* been written, but hasn't made it out the port yet)
* If successful, we return the number of bytes left to be written in the
* system,
* Otherwise we return a negative error number.
*/
static int mos7720_chars_in_buffer(struct usb_serial_port *port)
{
int i;
int chars = 0;
struct moschip_port *mos7720_port;
dbg("%s:entering ...........", __FUNCTION__);
mos7720_port = usb_get_serial_port_data(port);
if (mos7720_port == NULL) {
dbg("%s:leaving ...........", __FUNCTION__);
return -ENODEV;
}
for (i = 0; i < NUM_URBS; ++i) {
if (mos7720_port->write_urb_pool[i]->status == -EINPROGRESS)
chars += URB_TRANSFER_BUFFER_SIZE;
}
dbg("%s - returns %d", __FUNCTION__, chars);
return chars;
}
static void mos7720_close(struct usb_serial_port *port, struct file *filp)
{
struct usb_serial *serial;
struct moschip_port *mos7720_port;
char data;
int j;
dbg("mos7720_close:entering...");
serial = port->serial;
mos7720_port = usb_get_serial_port_data(port);
if (mos7720_port == NULL)
return;
for (j = 0; j < NUM_URBS; ++j)
usb_kill_urb(mos7720_port->write_urb_pool[j]);
/* Freeing Write URBs */
for (j = 0; j < NUM_URBS; ++j) {
if (mos7720_port->write_urb_pool[j]) {
kfree(mos7720_port->write_urb_pool[j]->transfer_buffer);
usb_free_urb(mos7720_port->write_urb_pool[j]);
}
}
/* While closing port, shutdown all bulk read, write *
* and interrupt read if they exists */
if (serial->dev) {
dbg("Shutdown bulk write");
usb_kill_urb(port->write_urb);
dbg("Shutdown bulk read");
usb_kill_urb(port->read_urb);
}
data = 0x00;
send_mos_cmd(serial, MOS_WRITE, port->number - port->serial->minor,
0x04, &data);
data = 0x00;
send_mos_cmd(serial, MOS_WRITE, port->number - port->serial->minor,
0x01, &data);
mos7720_port->open = 0;
dbg("Leaving %s", __FUNCTION__);
}
static void mos7720_break(struct usb_serial_port *port, int break_state)
{
unsigned char data;
struct usb_serial *serial;
struct moschip_port *mos7720_port;
dbg("Entering %s", __FUNCTION__);
serial = port->serial;
mos7720_port = usb_get_serial_port_data(port);
if (mos7720_port == NULL)
return;
if (break_state == -1)
data = mos7720_port->shadowLCR | UART_LCR_SBC;
else
data = mos7720_port->shadowLCR & ~UART_LCR_SBC;
mos7720_port->shadowLCR = data;
send_mos_cmd(serial, MOS_WRITE, port->number - port->serial->minor,
0x03, &data);
return;
}
/*
* mos7720_write_room
* this function is called by the tty driver when it wants to know how many
* bytes of data we can accept for a specific port.
* If successful, we return the amount of room that we have for this port
* Otherwise we return a negative error number.
*/
static int mos7720_write_room(struct usb_serial_port *port)
{
struct moschip_port *mos7720_port;
int room = 0;
int i;
dbg("%s:entering ...........", __FUNCTION__);
mos7720_port = usb_get_serial_port_data(port);
if (mos7720_port == NULL) {
dbg("%s:leaving ...........", __FUNCTION__);
return -ENODEV;
}
for (i = 0; i < NUM_URBS; ++i) {
if (mos7720_port->write_urb_pool[i]->status != -EINPROGRESS)
room += URB_TRANSFER_BUFFER_SIZE;
}
dbg("%s - returns %d", __FUNCTION__, room);
return room;
}
static int mos7720_write(struct usb_serial_port *port,
const unsigned char *data, int count)
{
int status;
int i;
int bytes_sent = 0;
int transfer_size;
struct moschip_port *mos7720_port;
struct usb_serial *serial;
struct urb *urb;
const unsigned char *current_position = data;
dbg("%s:entering ...........", __FUNCTION__);
serial = port->serial;
mos7720_port = usb_get_serial_port_data(port);
if (mos7720_port == NULL) {
dbg("mos7720_port is NULL");
return -ENODEV;
}
/* try to find a free urb in the list */
urb = NULL;
for (i = 0; i < NUM_URBS; ++i) {
if (mos7720_port->write_urb_pool[i]->status != -EINPROGRESS) {
urb = mos7720_port->write_urb_pool[i];
dbg("URB:%d",i);
break;
}
}
if (urb == NULL) {
dbg("%s - no more free urbs", __FUNCTION__);
goto exit;
}
if (urb->transfer_buffer == NULL) {
urb->transfer_buffer = kmalloc(URB_TRANSFER_BUFFER_SIZE,
GFP_KERNEL);
if (urb->transfer_buffer == NULL) {
err("%s no more kernel memory...", __FUNCTION__);
goto exit;
}
}
transfer_size = min (count, URB_TRANSFER_BUFFER_SIZE);
memcpy(urb->transfer_buffer, current_position, transfer_size);
usb_serial_debug_data(debug, &port->dev, __FUNCTION__, transfer_size,
urb->transfer_buffer);
/* fill urb with data and submit */
usb_fill_bulk_urb(urb, serial->dev,
usb_sndbulkpipe(serial->dev,
port->bulk_out_endpointAddress),
urb->transfer_buffer, transfer_size,
mos7720_bulk_out_data_callback, mos7720_port);
/* send it down the pipe */
status = usb_submit_urb(urb,GFP_ATOMIC);
if (status) {
err("%s - usb_submit_urb(write bulk) failed with status = %d",
__FUNCTION__, status);
bytes_sent = status;
goto exit;
}
bytes_sent = transfer_size;
exit:
return bytes_sent;
}
static void mos7720_throttle(struct usb_serial_port *port)
{
struct moschip_port *mos7720_port;
struct tty_struct *tty;
int status;
dbg("%s- port %d\n", __FUNCTION__, port->number);
mos7720_port = usb_get_serial_port_data(port);
if (mos7720_port == NULL)
return;
if (!mos7720_port->open) {
dbg("port not opened");
return;
}
dbg("%s: Entering ..........", __FUNCTION__);
tty = port->tty;
if (!tty) {
dbg("%s - no tty available", __FUNCTION__);
return;
}
/* if we are implementing XON/XOFF, send the stop character */
if (I_IXOFF(tty)) {
unsigned char stop_char = STOP_CHAR(tty);
status = mos7720_write(port, &stop_char, 1);
if (status <= 0)
return;
}
/* if we are implementing RTS/CTS, toggle that line */
if (tty->termios->c_cflag & CRTSCTS) {
mos7720_port->shadowMCR &= ~UART_MCR_RTS;
status = send_mos_cmd(port->serial, MOS_WRITE,
port->number - port->serial->minor,
UART_MCR, &mos7720_port->shadowMCR);
if (status != 0)
return;
}
}
static void mos7720_unthrottle(struct usb_serial_port *port)
{
struct tty_struct *tty;
int status;
struct moschip_port *mos7720_port = usb_get_serial_port_data(port);
if (mos7720_port == NULL)
return;
if (!mos7720_port->open) {
dbg("%s - port not opened", __FUNCTION__);
return;
}
dbg("%s: Entering ..........", __FUNCTION__);
tty = port->tty;
if (!tty) {
dbg("%s - no tty available", __FUNCTION__);
return;
}
/* if we are implementing XON/XOFF, send the start character */
if (I_IXOFF(tty)) {
unsigned char start_char = START_CHAR(tty);
status = mos7720_write(port, &start_char, 1);
if (status <= 0)
return;
}
/* if we are implementing RTS/CTS, toggle that line */
if (tty->termios->c_cflag & CRTSCTS) {
mos7720_port->shadowMCR |= UART_MCR_RTS;
status = send_mos_cmd(port->serial, MOS_WRITE,
port->number - port->serial->minor,
UART_MCR, &mos7720_port->shadowMCR);
if (status != 0)
return;
}
}
static int set_higher_rates(struct moschip_port *mos7720_port,
unsigned int baud)
{
unsigned char data;
struct usb_serial_port *port;
struct usb_serial *serial;
int port_number;
if (mos7720_port == NULL)
return -EINVAL;
port = mos7720_port->port;
serial = port->serial;
/***********************************************
* Init Sequence for higher rates
***********************************************/
dbg("Sending Setting Commands ..........");
port_number = port->number - port->serial->minor;
data = 0x000;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x01, &data);
data = 0x000;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x02, &data);
data = 0x0CF;
send_mos_cmd(serial, MOS_WRITE, port->number, 0x02, &data);
data = 0x00b;
mos7720_port->shadowMCR = data;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x04, &data);
data = 0x00b;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x04, &data);
data = 0x000;
send_mos_cmd(serial, MOS_READ, MOS_MAX_PORT, 0x08, &data);
data = 0x000;
send_mos_cmd(serial, MOS_WRITE, MOS_MAX_PORT, 0x08, &data);
/***********************************************
* Set for higher rates *
***********************************************/
data = baud * 0x10;
send_mos_cmd(serial, MOS_WRITE, MOS_MAX_PORT, port_number + 1,&data);
data = 0x003;
send_mos_cmd(serial, MOS_READ, MOS_MAX_PORT, 0x08, &data);
data = 0x003;
send_mos_cmd(serial, MOS_WRITE, MOS_MAX_PORT, 0x08, &data);
data = 0x02b;
mos7720_port->shadowMCR = data;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x04, &data);
data = 0x02b;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x04, &data);
/***********************************************
* Set DLL/DLM
***********************************************/
data = mos7720_port->shadowLCR | UART_LCR_DLAB;
mos7720_port->shadowLCR = data;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x03, &data);
data = 0x001; /* DLL */
send_mos_cmd(serial, MOS_WRITE, port_number, 0x00, &data);
data = 0x000; /* DLM */
send_mos_cmd(serial, MOS_WRITE, port_number, 0x01, &data);
data = mos7720_port->shadowLCR & ~UART_LCR_DLAB;
mos7720_port->shadowLCR = data;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x03, &data);
return 0;
}
/* baud rate information */
struct divisor_table_entry
{
__u32 baudrate;
__u16 divisor;
};
/* Define table of divisors for moschip 7720 hardware *
* These assume a 3.6864MHz crystal, the standard /16, and *
* MCR.7 = 0. */
static struct divisor_table_entry divisor_table[] = {
{ 50, 2304},
{ 110, 1047}, /* 2094.545455 => 230450 => .0217 % over */
{ 134, 857}, /* 1713.011152 => 230398.5 => .00065% under */
{ 150, 768},
{ 300, 384},
{ 600, 192},
{ 1200, 96},
{ 1800, 64},
{ 2400, 48},
{ 4800, 24},
{ 7200, 16},
{ 9600, 12},
{ 19200, 6},
{ 38400, 3},
{ 57600, 2},
{ 115200, 1},
};
/*****************************************************************************
* calc_baud_rate_divisor
* this function calculates the proper baud rate divisor for the specified
* baud rate.
*****************************************************************************/
static int calc_baud_rate_divisor(int baudrate, int *divisor)
{
int i;
__u16 custom;
__u16 round1;
__u16 round;
dbg("%s - %d", __FUNCTION__, baudrate);
for (i = 0; i < ARRAY_SIZE(divisor_table); i++) {
if (divisor_table[i].baudrate == baudrate) {
*divisor = divisor_table[i].divisor;
return 0;
}
}
/* After trying for all the standard baud rates *
* Try calculating the divisor for this baud rate */
if (baudrate > 75 && baudrate < 230400) {
/* get the divisor */
custom = (__u16)(230400L / baudrate);
/* Check for round off */
round1 = (__u16)(2304000L / baudrate);
round = (__u16)(round1 - (custom * 10));
if (round > 4)
custom++;
*divisor = custom;
dbg("Baud %d = %d",baudrate, custom);
return 0;
}
dbg("Baud calculation Failed...");
return -EINVAL;
}
/*
* send_cmd_write_baud_rate
* this function sends the proper command to change the baud rate of the
* specified port.
*/
static int send_cmd_write_baud_rate(struct moschip_port *mos7720_port,
int baudrate)
{
struct usb_serial_port *port;
struct usb_serial *serial;
int divisor;
int status;
unsigned char data;
unsigned char number;
if (mos7720_port == NULL)
return -1;
port = mos7720_port->port;
serial = port->serial;
dbg("%s: Entering ..........", __FUNCTION__);
number = port->number - port->serial->minor;
dbg("%s - port = %d, baud = %d", __FUNCTION__, port->number, baudrate);
/* Calculate the Divisor */
status = calc_baud_rate_divisor(baudrate, &divisor);
if (status) {
err("%s - bad baud rate", __FUNCTION__);
return status;
}
/* Enable access to divisor latch */
data = mos7720_port->shadowLCR | UART_LCR_DLAB;
mos7720_port->shadowLCR = data;
send_mos_cmd(serial, MOS_WRITE, number, UART_LCR, &data);
/* Write the divisor */
data = ((unsigned char)(divisor & 0xff));
send_mos_cmd(serial, MOS_WRITE, number, 0x00, &data);
data = ((unsigned char)((divisor & 0xff00) >> 8));
send_mos_cmd(serial, MOS_WRITE, number, 0x01, &data);
/* Disable access to divisor latch */
data = mos7720_port->shadowLCR & ~UART_LCR_DLAB;
mos7720_port->shadowLCR = data;
send_mos_cmd(serial, MOS_WRITE, number, 0x03, &data);
return status;
}
/*
* change_port_settings
* This routine is called to set the UART on the device to match
* the specified new settings.
*/
static void change_port_settings(struct moschip_port *mos7720_port,
struct ktermios *old_termios)
{
struct usb_serial_port *port;
struct usb_serial *serial;
struct tty_struct *tty;
int baud;
unsigned cflag;
unsigned iflag;
__u8 mask = 0xff;
__u8 lData;
__u8 lParity;
__u8 lStop;
int status;
int port_number;
char data;
if (mos7720_port == NULL)
return ;
port = mos7720_port->port;
serial = port->serial;
port_number = port->number - port->serial->minor;
dbg("%s - port %d", __FUNCTION__, port->number);
if (!mos7720_port->open) {
dbg("%s - port not opened", __FUNCTION__);
return;
}
tty = mos7720_port->port->tty;
if ((!tty) || (!tty->termios)) {
dbg("%s - no tty structures", __FUNCTION__);
return;
}
dbg("%s: Entering ..........", __FUNCTION__);
lData = UART_LCR_WLEN8;
lStop = 0x00; /* 1 stop bit */
lParity = 0x00; /* No parity */
cflag = tty->termios->c_cflag;
iflag = tty->termios->c_iflag;
/* Change the number of bits */
switch (cflag & CSIZE) {
case CS5:
lData = UART_LCR_WLEN5;
mask = 0x1f;
break;
case CS6:
lData = UART_LCR_WLEN6;
mask = 0x3f;
break;
case CS7:
lData = UART_LCR_WLEN7;
mask = 0x7f;
break;
default:
case CS8:
lData = UART_LCR_WLEN8;
break;
}
/* Change the Parity bit */
if (cflag & PARENB) {
if (cflag & PARODD) {
lParity = UART_LCR_PARITY;
dbg("%s - parity = odd", __FUNCTION__);
} else {
lParity = (UART_LCR_EPAR | UART_LCR_PARITY);
dbg("%s - parity = even", __FUNCTION__);
}
} else {
dbg("%s - parity = none", __FUNCTION__);
}
if (cflag & CMSPAR)
lParity = lParity | 0x20;
/* Change the Stop bit */
if (cflag & CSTOPB) {
lStop = UART_LCR_STOP;
dbg("%s - stop bits = 2", __FUNCTION__);
} else {
lStop = 0x00;
dbg("%s - stop bits = 1", __FUNCTION__);
}
#define LCR_BITS_MASK 0x03 /* Mask for bits/char field */
#define LCR_STOP_MASK 0x04 /* Mask for stop bits field */
#define LCR_PAR_MASK 0x38 /* Mask for parity field */
/* Update the LCR with the correct value */
mos7720_port->shadowLCR &= ~(LCR_BITS_MASK | LCR_STOP_MASK | LCR_PAR_MASK);
mos7720_port->shadowLCR |= (lData | lParity | lStop);
/* Disable Interrupts */
data = 0x00;
send_mos_cmd(serial,MOS_WRITE,port->number - port->serial->minor, UART_IER, &data);
data = 0x00;
send_mos_cmd(serial, MOS_WRITE, port_number, UART_FCR, &data);
data = 0xcf;
send_mos_cmd(serial, MOS_WRITE, port_number, UART_FCR, &data);
/* Send the updated LCR value to the mos7720 */
data = mos7720_port->shadowLCR;
send_mos_cmd(serial, MOS_WRITE, port_number, UART_LCR, &data);
data = 0x00b;
mos7720_port->shadowMCR = data;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x04, &data);
data = 0x00b;
send_mos_cmd(serial, MOS_WRITE, port_number, 0x04, &data);
/* set up the MCR register and send it to the mos7720 */
mos7720_port->shadowMCR = UART_MCR_OUT2;
if (cflag & CBAUD)
mos7720_port->shadowMCR |= (UART_MCR_DTR | UART_MCR_RTS);
if (cflag & CRTSCTS) {
mos7720_port->shadowMCR |= (UART_MCR_XONANY);
/* To set hardware flow control to the specified *
* serial port, in SP1/2_CONTROL_REG */
if (port->number) {
data = 0x001;
send_mos_cmd(serial, MOS_WRITE, MOS_MAX_PORT,
0x08, &data);
} else {
data = 0x002;
send_mos_cmd(serial, MOS_WRITE, MOS_MAX_PORT,
0x08, &data);
}
} else {
mos7720_port->shadowMCR &= ~(UART_MCR_XONANY);
}
data = mos7720_port->shadowMCR;
send_mos_cmd(serial, MOS_WRITE, port_number, UART_MCR, &data);
/* Determine divisor based on baud rate */
baud = tty_get_baud_rate(tty);
if (!baud) {
/* pick a default, any default... */
dbg("Picked default baud...");
baud = 9600;
}
if (baud >= 230400) {
set_higher_rates(mos7720_port, baud);
/* Enable Interrupts */
data = 0x0c;
send_mos_cmd(serial, MOS_WRITE, port_number, UART_IER, &data);
return;
}
dbg("%s - baud rate = %d", __FUNCTION__, baud);
status = send_cmd_write_baud_rate(mos7720_port, baud);
/* Enable Interrupts */
data = 0x0c;
send_mos_cmd(serial, MOS_WRITE, port_number, UART_IER, &data);
if (port->read_urb->status != -EINPROGRESS) {
port->read_urb->dev = serial->dev;
status = usb_submit_urb(port->read_urb, GFP_ATOMIC);
if (status)
dbg("usb_submit_urb(read bulk) failed, status = %d",
status);
}
return;
}
/*
* mos7720_set_termios
* this function is called by the tty driver when it wants to change the
* termios structure.
*/
static void mos7720_set_termios(struct usb_serial_port *port,
struct ktermios *old_termios)
{
int status;
unsigned int cflag;
struct usb_serial *serial;
struct moschip_port *mos7720_port;
struct tty_struct *tty;
serial = port->serial;
mos7720_port = usb_get_serial_port_data(port);
if (mos7720_port == NULL)
return;
tty = port->tty;
if (!port->tty || !port->tty->termios) {
dbg("%s - no tty or termios", __FUNCTION__);
return;
}
if (!mos7720_port->open) {
dbg("%s - port not opened", __FUNCTION__);
return;
}
dbg("%s\n","setting termios - ASPIRE");
cflag = tty->termios->c_cflag;
if (!cflag) {
printk("%s %s\n",__FUNCTION__,"cflag is NULL");
return;
}
/* check that they really want us to change something */
if (old_termios) {
if ((cflag == old_termios->c_cflag) &&
(RELEVANT_IFLAG(tty->termios->c_iflag) ==
RELEVANT_IFLAG(old_termios->c_iflag))) {
dbg("Nothing to change");
return;
}
}
dbg("%s - clfag %08x iflag %08x", __FUNCTION__,
tty->termios->c_cflag,
RELEVANT_IFLAG(tty->termios->c_iflag));
if (old_termios)
dbg("%s - old clfag %08x old iflag %08x", __FUNCTION__,
old_termios->c_cflag,
RELEVANT_IFLAG(old_termios->c_iflag));
dbg("%s - port %d", __FUNCTION__, port->number);
/* change the port settings to the new ones specified */
change_port_settings(mos7720_port, old_termios);
if(!port->read_urb) {
dbg("%s","URB KILLED !!!!!\n");
return;
}
if(port->read_urb->status != -EINPROGRESS) {
port->read_urb->dev = serial->dev;
status = usb_submit_urb(port->read_urb, GFP_ATOMIC);
if (status)
dbg("usb_submit_urb(read bulk) failed, status = %d",
status);
}
return;
}
/*
* get_lsr_info - get line status register info
*
* Purpose: Let user call ioctl() to get info when the UART physically
* is emptied. On bus types like RS485, the transmitter must
* release the bus after transmitting. This must be done when
* the transmit shift register is empty, not be done when the
* transmit holding register is empty. This functionality
* allows an RS485 driver to be written in user space.
*/
static int get_lsr_info(struct moschip_port *mos7720_port,
unsigned int __user *value)
{
int count;
unsigned int result = 0;
count = mos7720_chars_in_buffer(mos7720_port->port);
if (count == 0) {
dbg("%s -- Empty", __FUNCTION__);
result = TIOCSER_TEMT;
}
if (copy_to_user(value, &result, sizeof(int)))
return -EFAULT;
return 0;
}
/*
* get_number_bytes_avail - get number of bytes available
*
* Purpose: Let user call ioctl to get the count of number of bytes available.
*/
static int get_number_bytes_avail(struct moschip_port *mos7720_port,
unsigned int __user *value)
{
unsigned int result = 0;
struct tty_struct *tty = mos7720_port->port->tty;
if (!tty)
return -ENOIOCTLCMD;
result = tty->read_cnt;
dbg("%s(%d) = %d", __FUNCTION__, mos7720_port->port->number, result);
if (copy_to_user(value, &result, sizeof(int)))
return -EFAULT;
return -ENOIOCTLCMD;
}
static int set_modem_info(struct moschip_port *mos7720_port, unsigned int cmd,
unsigned int __user *value)
{
unsigned int mcr ;
unsigned int arg;
unsigned char data;
struct usb_serial_port *port;
if (mos7720_port == NULL)
return -1;
port = (struct usb_serial_port*)mos7720_port->port;
mcr = mos7720_port->shadowMCR;
if (copy_from_user(&arg, value, sizeof(int)))
return -EFAULT;
switch (cmd) {
case TIOCMBIS:
if (arg & TIOCM_RTS)
mcr |= UART_MCR_RTS;
if (arg & TIOCM_DTR)
mcr |= UART_MCR_RTS;
if (arg & TIOCM_LOOP)
mcr |= UART_MCR_LOOP;
break;
case TIOCMBIC:
if (arg & TIOCM_RTS)
mcr &= ~UART_MCR_RTS;
if (arg & TIOCM_DTR)
mcr &= ~UART_MCR_RTS;
if (arg & TIOCM_LOOP)
mcr &= ~UART_MCR_LOOP;
break;
case TIOCMSET:
/* turn off the RTS and DTR and LOOPBACK
* and then only turn on what was asked to */
mcr &= ~(UART_MCR_RTS | UART_MCR_DTR | UART_MCR_LOOP);
mcr |= ((arg & TIOCM_RTS) ? UART_MCR_RTS : 0);
mcr |= ((arg & TIOCM_DTR) ? UART_MCR_DTR : 0);
mcr |= ((arg & TIOCM_LOOP) ? UART_MCR_LOOP : 0);
break;
}
mos7720_port->shadowMCR = mcr;
data = mos7720_port->shadowMCR;
send_mos_cmd(port->serial, MOS_WRITE,
port->number - port->serial->minor, UART_MCR, &data);
return 0;
}
static int get_modem_info(struct moschip_port *mos7720_port,
unsigned int __user *value)
{
unsigned int result = 0;
unsigned int msr = mos7720_port->shadowMSR;
unsigned int mcr = mos7720_port->shadowMCR;
result = ((mcr & UART_MCR_DTR) ? TIOCM_DTR: 0) /* 0x002 */
| ((mcr & UART_MCR_RTS) ? TIOCM_RTS: 0) /* 0x004 */
| ((msr & UART_MSR_CTS) ? TIOCM_CTS: 0) /* 0x020 */
| ((msr & UART_MSR_DCD) ? TIOCM_CAR: 0) /* 0x040 */
| ((msr & UART_MSR_RI) ? TIOCM_RI: 0) /* 0x080 */
| ((msr & UART_MSR_DSR) ? TIOCM_DSR: 0); /* 0x100 */
dbg("%s -- %x", __FUNCTION__, result);
if (copy_to_user(value, &result, sizeof(int)))
return -EFAULT;
return 0;
}
static int get_serial_info(struct moschip_port *mos7720_port,
struct serial_struct __user *retinfo)
{
struct serial_struct tmp;
if (!retinfo)
return -EFAULT;
memset(&tmp, 0, sizeof(tmp));
tmp.type = PORT_16550A;
tmp.line = mos7720_port->port->serial->minor;
tmp.port = mos7720_port->port->number;
tmp.irq = 0;
tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ;
tmp.xmit_fifo_size = NUM_URBS * URB_TRANSFER_BUFFER_SIZE;
tmp.baud_base = 9600;
tmp.close_delay = 5*HZ;
tmp.closing_wait = 30*HZ;
if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
return -EFAULT;
return 0;
}
static int mos7720_ioctl(struct usb_serial_port *port, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct moschip_port *mos7720_port;
struct async_icount cnow;
struct async_icount cprev;
struct serial_icounter_struct icount;
mos7720_port = usb_get_serial_port_data(port);
if (mos7720_port == NULL)
return -ENODEV;
dbg("%s - port %d, cmd = 0x%x", __FUNCTION__, port->number, cmd);
switch (cmd) {
case TIOCINQ:
/* return number of bytes available */
dbg("%s (%d) TIOCINQ", __FUNCTION__, port->number);
return get_number_bytes_avail(mos7720_port,
(unsigned int __user *)arg);
break;
case TIOCSERGETLSR:
dbg("%s (%d) TIOCSERGETLSR", __FUNCTION__, port->number);
return get_lsr_info(mos7720_port, (unsigned int __user *)arg);
return 0;
case TIOCMBIS:
case TIOCMBIC:
case TIOCMSET:
dbg("%s (%d) TIOCMSET/TIOCMBIC/TIOCMSET", __FUNCTION__,
port->number);
return set_modem_info(mos7720_port, cmd,
(unsigned int __user *)arg);
case TIOCMGET:
dbg("%s (%d) TIOCMGET", __FUNCTION__, port->number);
return get_modem_info(mos7720_port,
(unsigned int __user *)arg);
case TIOCGSERIAL:
dbg("%s (%d) TIOCGSERIAL", __FUNCTION__, port->number);
return get_serial_info(mos7720_port,
(struct serial_struct __user *)arg);
case TIOCSSERIAL:
dbg("%s (%d) TIOCSSERIAL", __FUNCTION__, port->number);
break;
case TIOCMIWAIT:
dbg("%s (%d) TIOCMIWAIT", __FUNCTION__, port->number);
cprev = mos7720_port->icount;
while (1) {
if (signal_pending(current))
return -ERESTARTSYS;
cnow = mos7720_port->icount;
if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
cnow.dcd == cprev.dcd && cnow.cts == cprev.cts)
return -EIO; /* no change => error */
if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) ||
((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) {
return 0;
}
cprev = cnow;
}
/* NOTREACHED */
break;
case TIOCGICOUNT:
cnow = mos7720_port->icount;
icount.cts = cnow.cts;
icount.dsr = cnow.dsr;
icount.rng = cnow.rng;
icount.dcd = cnow.dcd;
icount.rx = cnow.rx;
icount.tx = cnow.tx;
icount.frame = cnow.frame;
icount.overrun = cnow.overrun;
icount.parity = cnow.parity;
icount.brk = cnow.brk;
icount.buf_overrun = cnow.buf_overrun;
dbg("%s (%d) TIOCGICOUNT RX=%d, TX=%d", __FUNCTION__,
port->number, icount.rx, icount.tx );
if (copy_to_user((void __user *)arg, &icount, sizeof(icount)))
return -EFAULT;
return 0;
}
return -ENOIOCTLCMD;
}
static int mos7720_startup(struct usb_serial *serial)
{
struct moschip_serial *mos7720_serial;
struct moschip_port *mos7720_port;
struct usb_device *dev;
int i;
char data;
dbg("%s: Entering ..........", __FUNCTION__);
if (!serial) {
dbg("Invalid Handler");
return -ENODEV;
}
dev = serial->dev;
/* create our private serial structure */
mos7720_serial = kzalloc(sizeof(struct moschip_serial), GFP_KERNEL);
if (mos7720_serial == NULL) {
err("%s - Out of memory", __FUNCTION__);
return -ENOMEM;
}
usb_set_serial_data(serial, mos7720_serial);
/* we set up the pointers to the endpoints in the mos7720_open *
* function, as the structures aren't created yet. */
/* set up port private structures */
for (i = 0; i < serial->num_ports; ++i) {
mos7720_port = kzalloc(sizeof(struct moschip_port), GFP_KERNEL);
if (mos7720_port == NULL) {
err("%s - Out of memory", __FUNCTION__);
usb_set_serial_data(serial, NULL);
kfree(mos7720_serial);
return -ENOMEM;
}
/* Initialize all port interrupt end point to port 0 int
* endpoint. Our device has only one interrupt endpoint
* comman to all ports */
serial->port[i]->interrupt_in_endpointAddress = serial->port[0]->interrupt_in_endpointAddress;
mos7720_port->port = serial->port[i];
usb_set_serial_port_data(serial->port[i], mos7720_port);
dbg("port number is %d", serial->port[i]->number);
dbg("serial number is %d", serial->minor);
}
/* setting configuration feature to one */
usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
(__u8)0x03, 0x00,0x01,0x00, NULL, 0x00, 5*HZ);
send_mos_cmd(serial,MOS_READ,0x00, UART_LSR, &data); // LSR For Port 1
dbg("LSR:%x",data);
send_mos_cmd(serial,MOS_READ,0x01, UART_LSR, &data); // LSR For Port 2
dbg("LSR:%x",data);
return 0;
}
static void mos7720_shutdown(struct usb_serial *serial)
{
int i;
/* free private structure allocated for serial port */
for (i=0; i < serial->num_ports; ++i) {
kfree(usb_get_serial_port_data(serial->port[i]));
usb_set_serial_port_data(serial->port[i], NULL);
}
/* free private structure allocated for serial device */
kfree(usb_get_serial_data(serial));
usb_set_serial_data(serial, NULL);
}
static struct usb_driver usb_driver = {
.name = "moschip7720",
.probe = usb_serial_probe,
.disconnect = usb_serial_disconnect,
.id_table = moschip_port_id_table,
.no_dynamic_id = 1,
};
static struct usb_serial_driver moschip7720_2port_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "moschip7720",
},
.description = "Moschip 2 port adapter",
.usb_driver = &usb_driver,
.id_table = moschip_port_id_table,
.num_interrupt_in = 1,
.num_bulk_in = 2,
.num_bulk_out = 2,
.num_ports = 2,
.open = mos7720_open,
.close = mos7720_close,
.throttle = mos7720_throttle,
.unthrottle = mos7720_unthrottle,
.attach = mos7720_startup,
.shutdown = mos7720_shutdown,
.ioctl = mos7720_ioctl,
.set_termios = mos7720_set_termios,
.write = mos7720_write,
.write_room = mos7720_write_room,
.chars_in_buffer = mos7720_chars_in_buffer,
.break_ctl = mos7720_break,
.read_bulk_callback = mos7720_bulk_in_callback,
.read_int_callback = mos7720_interrupt_callback,
};
static int __init moschip7720_init(void)
{
int retval;
dbg("%s: Entering ..........", __FUNCTION__);
/* Register with the usb serial */
retval = usb_serial_register(&moschip7720_2port_driver);
if (retval)
goto failed_port_device_register;
info(DRIVER_DESC " " DRIVER_VERSION);
/* Register with the usb */
retval = usb_register(&usb_driver);
if (retval)
goto failed_usb_register;
return 0;
failed_usb_register:
usb_serial_deregister(&moschip7720_2port_driver);
failed_port_device_register:
return retval;
}
static void __exit moschip7720_exit(void)
{
usb_deregister(&usb_driver);
usb_serial_deregister(&moschip7720_2port_driver);
}
module_init(moschip7720_init);
module_exit(moschip7720_exit);
/* Module information */
MODULE_AUTHOR( DRIVER_AUTHOR );
MODULE_DESCRIPTION( DRIVER_DESC );
MODULE_LICENSE("GPL");
module_param(debug, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "Debug enabled or not");