Commit 0f64478c authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman
Browse files

USB: add USB serial mos7720 driver



Add support for Moschip 7720 USB dual port usb to serial device.

This driver is originally based on the drivers/usb/io_edgeport.c driver.

Cleaned up and forward ported by me.

Cc: VijayaKumar <vijaykumar@aspirecom.net>
Cc: AjayKumar <ajay@aspirecom.net>
Cc: Gurudeva <gurudev@aspirecom.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 9fcde235
......@@ -422,6 +422,16 @@ config USB_SERIAL_MCT_U232
To compile this driver as a module, choose M here: the
module will be called mct_u232.
config USB_SERIAL_MOS7720
tristate "USB Moschip 7720 Single Port Serial Driver"
depends on USB_SERIAL
---help---
Say Y here if you want to use a USB Serial single port adapter from
Moschip Semiconductor Tech.
To compile this driver as a module, choose M here: the
module will be called mos7720.
config USB_SERIAL_MOS7840
tristate "USB Moschip 7840/7820 USB Serial Driver"
depends on USB_SERIAL
......
......@@ -34,6 +34,7 @@ obj-$(CONFIG_USB_SERIAL_KEYSPAN_PDA) += keyspan_pda.o
obj-$(CONFIG_USB_SERIAL_KLSI) += kl5kusb105.o
obj-$(CONFIG_USB_SERIAL_KOBIL_SCT) += kobil_sct.o
obj-$(CONFIG_USB_SERIAL_MCT_U232) += mct_u232.o
obj-$(CONFIG_USB_SERIAL_MOS7720) += mos7720.o
obj-$(CONFIG_USB_SERIAL_MOS7840) += mos7840.o
obj-$(CONFIG_USB_SERIAL_NAVMAN) += navman.o
obj-$(CONFIG_USB_SERIAL_OMNINET) += omninet.o
......
/*
* 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) {
/* let the tty driver wakeup if it has a special *
* write_wakeup function */
if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
tty->ldisc.write_wakeup)
(tty->ldisc.write_wakeup)(tty);
/* tell the tty driver that something has changed */
wake_up_interruptible(&tty->write_wait);
}
/* schedule_work(&mos7720_port->port->work); */
}
/*
* 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,SLAB_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;