Commit 02d30ac5 authored by Lonnie Mendez's avatar Lonnie Mendez Committed by Greg Kroah-Hartman

[PATCH] cypress_m8: add usb-serial driver 'cypress_m8' to kernel tree

This patch adds the cypress_m8 usb-serial driver for the Delorme Earthmate usb gps and the Cypress hid->com rs232 adapter to the kernel tree.
Signed-off-by: default avatarLonnie Mendez <lmendez19@austin.rr.com>
Signed-off-by: default avatarGreg Kroah-Hartman <greg@kroah.com>
parent 465d29f1
......@@ -211,6 +211,62 @@ ZyXEL omni.net lcd plus ISDN TA
azummo@towertech.it
Cypress M8 CY4601 Family Serial Driver
This driver was in most part developed by Neil "koyama" Whelchel. It
has been improved since that previous form to support dynamic serial
line settings and improved line handling. The driver is for the most
part stable and has been tested on an smp machine. (dual p2)
Chipsets supported under CY4601 family:
CY7C63723, CY7C63742, CY7C63743, CY7C64013
Devices supported:
-DeLorme's USB Earthmate (SiRF Star II lp arch)
-Cypress HID->COM RS232 adapter
Note: Cypress Semiconductor claims no affiliation with the
the hid->com device.
Most devices using chipsets under the CY4601 family should
work with the driver. As long as they stay true to the CY4601
usbserial specification.
Technical notes:
The Earthmate starts out at 4800 8N1 by default... the driver will
upon start init to this setting. usbserial core provides the rest
of the termios settings, along with some custom termios so that the
output is in proper format and parsable.
The device can be put into sirf mode by issuing NMEA command:
$PSRF100,<protocol>,<baud>,<databits>,<stopbits>,<parity>*CHECKSUM
$PSRF100,0,9600,8,1,0*0C
It should then be sufficient to change the port termios to match this
to begin communicating.
As far as I can tell it supports pretty much every sirf command as
documented online available with firmware 2.31, with some unknown
message ids.
The hid->com adapter can run at a maximum baud of 115200bps. Please note
that the device has trouble or is incapable of raising line voltage properly.
It will be fine with null modem links, as long as you do not try to link two
together without hacking the adapter to set the line high.
The driver is smp safe. Performance with the driver is rather low when using
it for transfering files. This is being worked on, but I would be willing to
accept patches. An urb queue or packet buffer would likely fit the bill here.
If you have any questions, problems, patches, feature requests, etc. you can
contact me here via email:
dignome@gmail.com
(your problems/patches can alternately be submitted to usb-devel)
Digi AccelePort Driver
This driver supports the Digi AccelePort USB 2 and 4 devices, 2 port
......
......@@ -2337,6 +2337,15 @@ L: linux-usb-users@lists.sourceforge.net
L: linux-usb-devel@lists.sourceforge.net
S: Maintained
USB SERIAL CYPRESS M8 DRIVER
P: Lonnie Mendez
M: dignome@gmail.com
L: linux-usb-users@lists.sourceforge.net
L: linux-usb-devel@lists.sourceforge.net
S: Maintained
W: http://geocities.com/i0xox0i
W: http://firstlight.net/cvs
USB SERIAL CYBERJACK PINPAD/E-COM DRIVER
L: linux-usb-users@lists.sourceforge.net
L: linux-usb-devel@lists.sourceforge.net
......
......@@ -89,6 +89,21 @@ config USB_SERIAL_DIGI_ACCELEPORT
To compile this driver as a module, choose M here: the
module will be called digi_acceleport.
config USB_SERIAL_CYPRESS_M8
tristate "USB Cypress M8 USB Serial Driver"
depends on USB_SERIAL && EXPERIMENTAL
help
Say Y here if you want to use a device that contains the Cypress
USB to Serial microcontroller, such as the DeLorme Earthmate GPS.
Attempted SMP support... send bug reports!
Supported microcontrollers in the CY4601 family are:
CY7C63741 CY7C63742 CY7C63743 CY7C64013
To compile this driver as a module, choose M here: the
module will be called cypress_m8.
config USB_SERIAL_EMPEG
tristate "USB Empeg empeg-car Mark I/II Driver"
depends on USB_SERIAL
......
......@@ -13,6 +13,7 @@ usbserial-objs := usb-serial.o generic.o bus.o $(usbserial-obj-y)
obj-$(CONFIG_USB_SERIAL_BELKIN) += belkin_sa.o
obj-$(CONFIG_USB_SERIAL_CYBERJACK) += cyberjack.o
obj-$(CONFIG_USB_SERIAL_CYPRESS_M8) += cypress_m8.o
obj-$(CONFIG_USB_SERIAL_DIGI_ACCELEPORT) += digi_acceleport.o
obj-$(CONFIG_USB_SERIAL_EDGEPORT) += io_edgeport.o
obj-$(CONFIG_USB_SERIAL_EDGEPORT_TI) += io_ti.o
......
/*
* USB Cypress M8 driver
*
* Copyright (C) 2004
* Lonnie Mendez (dignome@gmail.com)
* Copyright (C) 2003,2004
* Neil Whelchel (koyama@firstlight.net)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* See Documentation/usb/usb-serial.txt for more information on using this driver
*
* See http://geocities.com/i0xox0i for information on this driver and the
* earthmate usb device.
*
*
* Lonnie Mendez <dignome@gmail.com>
* 04-10-2004
* Driver modified to support dynamic line settings. Various improvments
* and features.
*
* Neil Whelchel
* 10-2003
* Driver first released.
*
*
* Long Term TODO:
* Improve transfer speeds - both read/write are somewhat slow
* at this point.
*/
/* Neil Whelchel wrote the cypress m8 implementation */
/* Thanks to cypress for providing references for the hid reports. */
/* Thanks to Jiang Zhang for providing links and for general help. */
/* Code originates and was built up from ftdi_sio, belkin, pl2303 and others. */
#include <linux/config.h>
#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/moduleparam.h>
#include <linux/spinlock.h>
#include <asm/uaccess.h>
#include <linux/usb.h>
#include <linux/serial.h>
#ifdef CONFIG_USB_SERIAL_DEBUG
static int debug = 1;
#else
static int debug;
#endif
static int stats;
#include "usb-serial.h"
#include "cypress_m8.h"
/*
* Version Information
*/
#define DRIVER_VERSION "v1.06"
#define DRIVER_AUTHOR "Lonnie Mendez <dignome@gmail.com>, Neil Whelchel <koyama@firstlight.net>"
#define DRIVER_DESC "Cypress USB to Serial Driver"
static struct usb_device_id id_table_earthmate [] = {
{ USB_DEVICE(VENDOR_ID_DELORME, PRODUCT_ID_EARTHMATEUSB) },
{ } /* Terminating entry */
};
static struct usb_device_id id_table_cyphidcomrs232 [] = {
{ USB_DEVICE(VENDOR_ID_CYPRESS, PRODUCT_ID_CYPHIDCOM) },
{ } /* Terminating entry */
};
static struct usb_device_id id_table_combined [] = {
{ USB_DEVICE(VENDOR_ID_DELORME, PRODUCT_ID_EARTHMATEUSB) },
{ USB_DEVICE(VENDOR_ID_CYPRESS, PRODUCT_ID_CYPHIDCOM) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, id_table_combined);
static struct usb_driver cypress_driver = {
.name = "cypress",
.probe = usb_serial_probe,
.disconnect = usb_serial_disconnect,
.id_table = id_table_combined,
};
struct cypress_private {
spinlock_t lock; /* private lock */
int chiptype; /* identifier of device, for quirks/etc */
int bytes_in; /* used for statistics */
int bytes_out; /* used for statistics */
int cmd_count; /* used for statistics */
int cmd_ctrl; /* always set this to 1 before issuing a command */
int termios_initialized;
__u8 line_control; /* holds dtr / rts value */
__u8 current_status; /* received from last read - info on dsr,cts,cd,ri,etc */
__u8 current_config; /* stores the current configuration byte */
__u8 rx_flags; /* throttling - used from whiteheat/ftdi_sio */
int baud_rate; /* stores current baud rate in integer form */
int cbr_mask; /* stores current baud rate in masked form */
int isthrottled; /* if throttled, discard reads */
wait_queue_head_t delta_msr_wait; /* used for TIOCMIWAIT */
char prev_status, diff_status; /* used for TIOCMIWAIT */
/* we pass a pointer to this as the arguement sent to cypress_set_termios old_termios */
struct termios tmp_termios; /* stores the old termios settings */
int write_interval; /* interrupt out write interval, as obtained from interrupt_out_urb */
int writepipe; /* used for clear halt, if necessary */
};
/* function prototypes for the Cypress USB to serial device */
static int cypress_earthmate_startup (struct usb_serial *serial);
static int cypress_hidcom_startup (struct usb_serial *serial);
static void cypress_shutdown (struct usb_serial *serial);
static int cypress_open (struct usb_serial_port *port, struct file *filp);
static void cypress_close (struct usb_serial_port *port, struct file *filp);
static int cypress_write (struct usb_serial_port *port, const unsigned char *buf, int count);
static int cypress_write_room (struct usb_serial_port *port);
static int cypress_ioctl (struct usb_serial_port *port, struct file * file, unsigned int cmd, unsigned long arg);
static void cypress_set_termios (struct usb_serial_port *port, struct termios * old);
static int cypress_tiocmget (struct usb_serial_port *port, struct file *file);
static int cypress_tiocmset (struct usb_serial_port *port, struct file *file, unsigned int set, unsigned int clear);
static int cypress_chars_in_buffer (struct usb_serial_port *port);
static void cypress_throttle (struct usb_serial_port *port);
static void cypress_unthrottle (struct usb_serial_port *port);
static void cypress_read_int_callback (struct urb *urb, struct pt_regs *regs);
static void cypress_write_int_callback (struct urb *urb, struct pt_regs *regs);
static int mask_to_rate (unsigned mask);
static unsigned rate_to_mask (int rate);
static struct usb_serial_device_type cypress_earthmate_device = {
.owner = THIS_MODULE,
.name = "DeLorme Earthmate USB",
.short_name = "earthmate",
.id_table = id_table_earthmate,
.num_interrupt_in = 1,
.num_interrupt_out = 1,
.num_bulk_in = NUM_DONT_CARE,
.num_bulk_out = NUM_DONT_CARE,
.num_ports = 1,
.attach = cypress_earthmate_startup,
.shutdown = cypress_shutdown,
.open = cypress_open,
.close = cypress_close,
.write = cypress_write,
.write_room = cypress_write_room,
.ioctl = cypress_ioctl,
.set_termios = cypress_set_termios,
.tiocmget = cypress_tiocmget,
.tiocmset = cypress_tiocmset,
.chars_in_buffer = cypress_chars_in_buffer,
.throttle = cypress_throttle,
.unthrottle = cypress_unthrottle,
.read_int_callback = cypress_read_int_callback,
.write_int_callback = cypress_write_int_callback,
};
static struct usb_serial_device_type cypress_hidcom_device = {
.owner = THIS_MODULE,
.name = "HID->COM RS232 Adapter",
.short_name = "cyphidcom",
.id_table = id_table_cyphidcomrs232,
.num_interrupt_in = 1,
.num_interrupt_out = 1,
.num_bulk_in = NUM_DONT_CARE,
.num_bulk_out = NUM_DONT_CARE,
.num_ports = 1,
.attach = cypress_hidcom_startup,
.shutdown = cypress_shutdown,
.open = cypress_open,
.close = cypress_close,
.write = cypress_write,
.write_room = cypress_write_room,
.ioctl = cypress_ioctl,
.set_termios = cypress_set_termios,
.tiocmget = cypress_tiocmget,
.tiocmset = cypress_tiocmset,
.chars_in_buffer = cypress_chars_in_buffer,
.throttle = cypress_throttle,
.unthrottle = cypress_unthrottle,
.read_int_callback = cypress_read_int_callback,
.write_int_callback = cypress_write_int_callback,
};
/*****************************************************************************
* Cypress serial helper functions
*****************************************************************************/
/* This function can either set or retreive the current serial line settings */
static int cypress_serial_control (struct usb_serial_port *port, unsigned baud_mask, int data_bits, int stop_bits,
int parity_enable, int parity_type, int reset, int cypress_request_type)
{
int i, n_baud_rate = 0, retval = 0;
struct cypress_private *priv;
__u8 feature_buffer[5];
__u8 config;
unsigned long flags;
dbg("%s", __FUNCTION__);
priv = usb_get_serial_port_data(port);
switch(cypress_request_type) {
case CYPRESS_SET_CONFIG:
/*
* The general purpose firmware for the Cypress M8 allows for a maximum speed
* of 57600bps (I have no idea whether DeLorme chose to use the general purpose
* firmware or not), if you need to modify this speed setting for your own
* project please add your own chiptype and modify the code likewise. The
* Cypress HID->COM device will work successfully up to 115200bps.
*/
if (baud_mask != priv->cbr_mask) {
dbg("%s - baud rate is changing", __FUNCTION__);
if ( priv->chiptype == CT_EARTHMATE ) {
/* 300 and 600 baud rates are supported under the generic firmware,
* but are not used with NMEA and SiRF protocols */
if ( (baud_mask == B300) || (baud_mask == B600) ) {
err("%s - failed setting baud rate, unsupported speed (default to 4800)",
__FUNCTION__);
n_baud_rate = 4800;
} else if ( (n_baud_rate = mask_to_rate(baud_mask)) == -1) {
err("%s - failed setting baud rate, unsupported speed (default to 4800)",
__FUNCTION__);
n_baud_rate = 4800;
}
} else if (priv->chiptype == CT_CYPHIDCOM) {
if ( (n_baud_rate = mask_to_rate(baud_mask)) == -1) {
err("%s - failed setting baud rate, unsupported speed (default to 4800)",
__FUNCTION__);
n_baud_rate = 4800;
}
} else if (priv->chiptype == CT_GENERIC) {
if ( (n_baud_rate = mask_to_rate(baud_mask)) == -1) {
err("%s - failed setting baud rate, unsupported speed (default to 4800)",
__FUNCTION__);
n_baud_rate = 4800;
}
} else {
info("%s - please define your chiptype, using 4800bps default", __FUNCTION__);
n_baud_rate = 4800;
}
} else { /* baud rate not changing, keep the old */
n_baud_rate = priv->baud_rate;
}
dbg("%s - baud rate is being sent as %d", __FUNCTION__, n_baud_rate);
/*
* This algorithm accredited to Jiang Jay Zhang... thanks for all the help!
*/
for (i = 0; i < 4; ++i) {
feature_buffer[i] = ( n_baud_rate >> (i*8) & 0xFF );
}
config = 0; // reset config byte
config |= data_bits; // assign data bits in 2 bit space ( max 3 )
/* 1 bit gap */
config |= (stop_bits << 3); // assign stop bits in 1 bit space
config |= (parity_enable << 4); // assign parity flag in 1 bit space
config |= (parity_type << 5); // assign parity type in 1 bit space
/* 1 bit gap */
config |= (reset << 7); // assign reset at end of byte, 1 bit space
feature_buffer[4] = config;
dbg("%s - device is being sent this feature report:", __FUNCTION__);
dbg("%s - %02X - %02X - %02X - %02X - %02X", __FUNCTION__, feature_buffer[0], feature_buffer[1],
feature_buffer[2], feature_buffer[3], feature_buffer[4]);
retval = usb_control_msg (port->serial->dev, usb_sndctrlpipe(port->serial->dev, 0),
HID_REQ_SET_REPORT, USB_DIR_OUT | USB_RECIP_INTERFACE | USB_TYPE_CLASS,
0x0300, 0, feature_buffer, 5, 500);
if (retval != 5)
err("%s - failed sending serial line settings - %d", __FUNCTION__, retval);
else {
spin_lock_irqsave(&priv->lock, flags);
priv->baud_rate = n_baud_rate;
priv->cbr_mask = baud_mask;
priv->current_config = config;
++priv->cmd_count;
spin_unlock_irqrestore(&priv->lock, flags);
}
break;
case CYPRESS_GET_CONFIG:
dbg("%s - retreiving serial line settings", __FUNCTION__);
/* reset values in feature buffer */
memset(feature_buffer, 0, 5);
retval = usb_control_msg (port->serial->dev, usb_rcvctrlpipe(port->serial->dev, 0),
HID_REQ_GET_REPORT, USB_DIR_IN | USB_RECIP_INTERFACE | USB_TYPE_CLASS,
0x0300, 0, feature_buffer, 5, 500);
if (retval != 5) {
err("%s - failed to retreive serial line settings - %d", __FUNCTION__, retval);
return retval;
} else {
spin_lock_irqsave(&priv->lock, flags);
/* store the config in one byte, and later use bit masks to check values */
priv->current_config = feature_buffer[4];
/* reverse the process above to get the baud_mask value */
n_baud_rate = 0; // reset bits
for (i = 0; i < 4; ++i) {
n_baud_rate |= ( feature_buffer[i] << (i*8) );
}
priv->baud_rate = n_baud_rate;
if ( (priv->cbr_mask = rate_to_mask(n_baud_rate)) == 0x40)
dbg("%s - failed setting the baud mask (not defined)", __FUNCTION__);
++priv->cmd_count;
spin_unlock_irqrestore(&priv->lock, flags);
}
break;
default:
err("%s - unsupported serial control command issued", __FUNCTION__);
}
return retval;
} /* cypress_serial_control */
/* given a baud mask, it will return speed on success */
static int mask_to_rate (unsigned mask)
{
int rate;
switch (mask) {
case B0: rate = 0; break;
case B300: rate = 300; break;
case B600: rate = 600; break;
case B1200: rate = 1200; break;
case B2400: rate = 2400; break;
case B4800: rate = 4800; break;
case B9600: rate = 9600; break;
case B19200: rate = 19200; break;
case B38400: rate = 38400; break;
case B57600: rate = 57600; break;
case B115200: rate = 115200; break;
default: rate = -1;
}
return rate;
}
static unsigned rate_to_mask (int rate)
{
unsigned mask;
switch (rate) {
case 0: mask = B0; break;
case 300: mask = B300; break;
case 600: mask = B600; break;
case 1200: mask = B1200; break;
case 2400: mask = B2400; break;
case 4800: mask = B4800; break;
case 9600: mask = B9600; break;
case 19200: mask = B19200; break;
case 38400: mask = B38400; break;
case 57600: mask = B57600; break;
case 115200: mask = B115200; break;
default: mask = 0x40;
}
return mask;
}
/*****************************************************************************
* Cypress serial driver functions
*****************************************************************************/
static int generic_startup (struct usb_serial *serial)
{
struct cypress_private *priv;
dbg("%s - port %d", __FUNCTION__, serial->port[0]->number);
priv = kmalloc(sizeof (struct cypress_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;
memset(priv, 0x00, sizeof (struct cypress_private));
spin_lock_init(&priv->lock);
init_waitqueue_head(&priv->delta_msr_wait);
priv->writepipe = serial->port[0]->interrupt_out_urb->pipe;
/* free up interrupt_out buffer / urb allocated by usbserial
* for this port as we use our own urbs for writing */
if (serial->port[0]->interrupt_out_buffer) {
kfree(serial->port[0]->interrupt_out_buffer);
serial->port[0]->interrupt_out_buffer = NULL;
}
if (serial->port[0]->interrupt_out_urb) {
priv->write_interval = serial->port[0]->interrupt_out_urb->interval;
usb_free_urb(serial->port[0]->interrupt_out_urb);
serial->port[0]->interrupt_out_urb = NULL;
} else /* still need a write interval */
priv->write_interval = 10;
priv->cmd_ctrl = 0;
priv->line_control = 0;
priv->termios_initialized = 0;
priv->rx_flags = 0;
usb_set_serial_port_data(serial->port[0], priv);
return (0);
}
static int cypress_earthmate_startup (struct usb_serial *serial)
{
struct cypress_private *priv;
dbg("%s", __FUNCTION__);
if (generic_startup(serial)) {
dbg("%s - Failed setting up port %d", __FUNCTION__, serial->port[0]->number);
return 1;
}
priv = usb_get_serial_port_data(serial->port[0]);
priv->chiptype = CT_EARTHMATE;
return (0);
} /* cypress_earthmate_startup */
static int cypress_hidcom_startup (struct usb_serial *serial)
{
struct cypress_private *priv;
dbg("%s", __FUNCTION__);
if (generic_startup(serial)) {
dbg("%s - Failed setting up port %d", __FUNCTION__, serial->port[0]->number);
return 1;
}
priv = usb_get_serial_port_data(serial->port[0]);
priv->chiptype = CT_CYPHIDCOM;
return (0);
} /* cypress_hidcom_startup */
static void cypress_shutdown (struct usb_serial *serial)
{
struct cypress_private *priv;
dbg ("%s - port %d", __FUNCTION__, serial->port[0]->number);
/* all open ports are closed at this point */
priv = usb_get_serial_port_data(serial->port[0]);
if (priv) {
kfree(priv);
usb_set_serial_port_data(serial->port[0], NULL);
}
}
static int cypress_open (struct usb_serial_port *port, struct file *filp)
{
struct cypress_private *priv = usb_get_serial_port_data(port);
struct usb_serial *serial = port->serial;
unsigned long flags;
int result = 0;
dbg("%s - port %d", __FUNCTION__, port->number);
spin_lock_irqsave(&priv->lock, flags);
/* reset read/write statistics */
priv->bytes_in = 0;
priv->bytes_out = 0;
priv->cmd_count = 0;
/* turn on dtr / rts since we are not flow controlling by default */
priv->line_control = CONTROL_DTR | CONTROL_RTS; /* sent in status byte */
spin_unlock_irqrestore(&priv->lock, flags);
priv->cmd_ctrl = 1;
result = cypress_write(port, NULL, 0);
port->tty->low_latency = 1;
/* termios defaults are set by usb_serial_init */
cypress_set_termios(port, &priv->tmp_termios);
if (result) {
dev_err(&port->dev, "%s - failed setting the control lines - error %d\n", __FUNCTION__, result);
return result;
} else
dbg("%s - success setting the control lines", __FUNCTION__);
/* throttling off */
spin_lock_irqsave(&priv->lock, flags);
priv->rx_flags = 0;
spin_unlock_irqrestore(&priv->lock, flags);
/* setup the port and
* start reading from the device */
if(!port->interrupt_in_urb){
err("%s - interrupt_in_urb is empty!", __FUNCTION__);
return(-1);
}
usb_fill_int_urb(port->interrupt_in_urb, serial->dev,
usb_rcvintpipe(serial->dev, port->interrupt_in_endpointAddress),
port->interrupt_in_urb->transfer_buffer, port->interrupt_in_urb->transfer_buffer_length,
cypress_read_int_callback, port, port->interrupt_in_urb->interval);
result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
if (result){
dev_err(&port->dev, "%s - failed submitting read urb, error %d\n", __FUNCTION__, result);
}
return result;
} /* cypress_open */
static void cypress_close(struct usb_serial_port *port, struct file * filp)
{
struct cypress_private *priv = usb_get_serial_port_data(port);
unsigned int c_cflag;
unsigned long flags;
dbg("%s - port %d", __FUNCTION__, port->number);
if (port->tty) {
c_cflag = port->tty->termios->c_cflag;
if (c_cflag & HUPCL) {
/* drop dtr and rts */
priv = usb_get_serial_port_data(port);
spin_lock_irqsave(&priv->lock, flags);
priv->line_control = 0;
priv->cmd_ctrl = 1;
spin_unlock_irqrestore(&priv->lock, flags);
cypress_write(port, NULL, 0);
}
}
if (port->interrupt_in_urb) {
dbg("%s - stopping read urb", __FUNCTION__);
usb_kill_urb (port->interrupt_in_urb);
}
if (stats)
dev_info (&port->dev, "Statistics: %d Bytes In | %d Bytes Out | %d Commands Issued\n",
priv->bytes_in, priv->bytes_out, priv->cmd_count);
} /* cypress_close */
static int cypress_write(struct usb_serial_port *port, const unsigned char *buf, int count)
{
struct cypress_private *priv = usb_get_serial_port_data(port);
unsigned long flags;
struct urb *urb;
int status, s_pos = 0;
__u8 transfer_size = 0;
__u8 *buffer;
dbg("%s - port %d", __FUNCTION__, port->number);
spin_lock_irqsave(&priv->lock, flags);
if (count == 0 && !priv->cmd_ctrl) {
spin_unlock_irqrestore(&priv->lock, flags);
dbg("%s - write request of 0 bytes", __FUNCTION__);
return 0;
}
if (priv->cmd_ctrl)
++priv->cmd_count;
priv->cmd_ctrl = 0;
spin_unlock_irqrestore(&priv->lock, flags);
dbg("%s - interrupt out size is %d", __FUNCTION__, port->interrupt_out_size);
dbg("%s - count is %d", __FUNCTION__, count);
/* Allocate buffer and urb */
buffer = kmalloc (port->interrupt_out_size, GFP_ATOMIC);
if (!buffer) {
dev_err(&port->dev, "ran out of memory for buffer\n");
return -ENOMEM;
}
urb = usb_alloc_urb (0, GFP_ATOMIC);
if (!urb) {
dev_err(&port->dev, "failed allocating write urb\n");
kfree (buffer);
return -ENOMEM;
}
memset(buffer, 0, port->interrupt_out_size); // test if this is needed... probably not since loop removed
spin_lock_irqsave(&priv->lock, flags);
switch (port->interrupt_out_size) {
case 32:
// this is for the CY7C64013...
transfer_size = min (count, 30);
buffer[0] = priv->line_control;
buffer[1] = transfer_size;
s_pos = 2;
break;
case 8:
// this is for the CY7C63743...
transfer_size = min (count, 7);
buffer[0] = priv->line_control | transfer_size;
s_pos = 1;
break;
default:
dbg("%s - wrong packet size", __FUNCTION__);
spin_unlock_irqrestore(&priv->lock, flags);
kfree (buffer);
usb_free_urb (urb);
return -1;
}
if (priv->line_control & CONTROL_RESET)
priv->line_control &= ~CONTROL_RESET;
spin_unlock_irqrestore(&priv->lock, flags);
/* copy data to offset position in urb transfer buffer */
memcpy (&buffer[s_pos], buf, transfer_size);
usb_serial_debug_data (debug, &port->dev, __FUNCTION__, port->interrupt_out_size, buffer);
/* build up the urb */
usb_fill_int_urb (urb, port->serial->dev,
usb_sndintpipe(port->serial->dev, port->interrupt_out_endpointAddress),
buffer, port->interrupt_out_size,
cypress_write_int_callback, port, priv->write_interval);
status = usb_submit_urb(urb, GFP_ATOMIC);
if (status) {
dev_err(&port->dev, "%s - usb_submit_urb (write interrupt) failed with status %d\n",
__FUNCTION__, status);
transfer_size = status;
kfree (buffer);
goto exit;
}
spin_lock_irqsave(&priv->lock, flags);
priv->bytes_out += transfer_size;
spin_unlock_irqrestore(&priv->lock, flags);
exit:
/* buffer free'd in callback */
usb_free_urb (urb);
return transfer_size;
} /* cypress_write */
static int cypress_write_room(struct usb_serial_port *port)
{
dbg("%s - port %d", __FUNCTION__, port->number);
/*
* We really can take anything the user throw at us
* but let's pick a nice big number to tell the tty
* layer that we have lots of free space
*/
return 2048;
}
static int cypress_tiocmget (struct usb_serial_port *port, struct file *file)
{
struct cypress_private *priv = usb_get_serial_port_data(port);
__u8 status, control;
unsigned int result = 0;
unsigned long flags;
dbg("%s - port %d", __FUNCTION__, port->number);
spin_lock_irqsave(&priv->lock, flags);
control = priv->line_control;
status = priv->current_status;
spin_unlock_irqrestore(&priv->lock, flags);
result = ((control & CONTROL_DTR) ? TIOCM_DTR : 0)
| ((control & CONTROL_RTS) ? TIOCM_RTS : 0)
| ((status & UART_CTS) ? TIOCM_CTS : 0)
| ((status & UART_DSR) ? TIOCM_DSR : 0)
| ((status & UART_RI) ? TIOCM_RI : 0)
| ((status & UART_CD) ? TIOCM_CD : 0);
dbg("%s - result = %x", __FUNCTION__, result);
return result;
}
static int cypress_tiocmset (struct usb_serial_port *port, struct file *file,
unsigned int set, unsigned int clear)
{
struct cypress_private *priv = usb_get_serial_port_data(port);
unsigned long flags;
dbg("%s - port %d", __FUNCTION__, port->number);
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;
spin_unlock_irqrestore(&priv->lock, flags);
priv->cmd_ctrl = 1;
return cypress_write(port, NULL, 0);
}
static int cypress_ioctl (struct usb_serial_port *port, struct file * file, unsigned int cmd, unsigned long arg)
{
struct cypress_private *priv = usb_get_serial_port_data(port);
dbg("%s - port %d, cmd 0x%.4x", __FUNCTION__, port->number, cmd);
switch (cmd) {
case TIOCGSERIAL:
if (copy_to_user((void *)arg, port->tty->termios, sizeof(struct termios))) {
return -EFAULT;
}
return (0);
break;
case TIOCSSERIAL:
if (copy_from_user(port->tty->termios, (void *)arg, sizeof(struct termios))) {
return -EFAULT;
}
/* here we need to call cypress_set_termios to invoke the new settings */
cypress_set_termios(port, &priv->tmp_termios);
return (0);
break;
/* these are called when setting baud rate from gpsd */
case TCGETS:
if (copy_to_user((void *)arg, port->tty->termios, sizeof(struct termios))) {
return -EFAULT;
}
return (0);
break;
case TCSETS:
if (copy_from_user(port->tty->termios, (void *)arg, sizeof(struct termios))) {
return -EFAULT;
}
/* here we need to call cypress_set_termios to invoke the new settings */
cypress_set_termios(port, &priv->tmp_termios);
return (0);
break;
/* This code comes from drivers/char/serial.c and ftdi_sio.c */
case TIOCMIWAIT:
while (priv != NULL) {
interruptible_sleep_on(&priv->delta_msr_wait);
/* see if a signal did it */
if (signal_pending(current))
return -ERESTARTSYS;
else {
char diff = priv->diff_status;
if (diff == 0) {
return -EIO; /* no change => error */
}
/* consume all events */
priv->diff_status = 0;
/* return 0 if caller wanted to know about these bits */
if ( ((arg & TIOCM_RNG) && (diff & UART_RI)) ||
((arg & TIOCM_DSR) && (diff & UART_DSR)) ||
((arg & TIOCM_CD) && (diff & UART_CD)) ||
((arg & TIOCM_CTS) && (diff & UART_CTS)) ) {
return 0;
}
/* otherwise caller can't care less about what happened,
* and so we continue to wait for more events.
*/
}
}
return 0;
break;
default:
break;
}
dbg("%s - arg not supported - it was 0x%04x - check include/asm/ioctls.h", __FUNCTION__, cmd);
return -ENOIOCTLCMD;
} /* cypress_ioctl */
static void cypress_set_termios (struct usb_serial_port *port, struct termios *old_termios)
{
struct cypress_private *priv = usb_get_serial_port_data(port);
struct tty_struct *tty;
int data_bits, stop_bits, parity_type, parity_enable;
unsigned cflag, iflag, baud_mask;
unsigned long flags;
dbg("%s - port %d", __FUNCTION__, port->number);
tty = port->tty;
if ((!tty) || (!tty->termios)) {
dbg("%s - no tty structures", __FUNCTION__);
return;
}
spin_lock_irqsave(&priv->lock, flags);
if (!priv->termios_initialized) {
if (priv->chiptype == CT_EARTHMATE) {
*(tty->termios) = tty_std_termios;
tty->termios->c_cflag = B4800 | CS8 | CREAD | HUPCL | CLOCAL;
} else if (priv->chiptype == CT_CYPHIDCOM) {
*(tty->termios) = tty_std_termios;
tty->termios->c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
}
priv->termios_initialized = 1;
}
spin_unlock_irqrestore(&priv->lock, flags);
cflag = tty->termios->c_cflag;
iflag = tty->termios->c_iflag;
/* check if there are new settings */
if (old_termios) {
if ((cflag != old_termios->c_cflag) ||
(RELEVANT_IFLAG(iflag) != RELEVANT_IFLAG(old_termios->c_iflag))) {
dbg("%s - attempting to set new termios settings", __FUNCTION__);
/* should make a copy of this in case something goes wrong in the function, we can restore it */
spin_lock_irqsave(&priv->lock, flags);
priv->tmp_termios = *(tty->termios);
spin_unlock_irqrestore(&priv->lock, flags);
} else {
dbg("%s - nothing to do, exiting", __FUNCTION__);
return;
}
} else
return;
/* set number of data bits, parity, stop bits */
/* when parity is disabled the parity type bit is ignored */
stop_bits = cflag & CSTOPB ? 1 : 0; /* 1 means 2 stop bits, 0 means 1 stop bit */
if (cflag & PARENB) {
parity_enable = 1;
parity_type = cflag & PARODD ? 1 : 0; /* 1 means odd parity, 0 means even parity */
} else
parity_enable = parity_type = 0;
if (cflag & CSIZE) {
switch (cflag & CSIZE) {
case CS5: data_bits = 0; break;
case CS6: data_bits = 1; break;
case CS7: data_bits = 2; break;
case CS8: data_bits = 3; break;
default: err("%s - CSIZE was set, but not CS5-CS8", __FUNCTION__); data_bits = 3;
}
} else
data_bits = 3;
spin_lock_irqsave(&priv->lock, flags);
if ((cflag & CBAUD) == B0) {
/* drop dtr and rts */
dbg("%s - dropping the lines, baud rate 0bps", __FUNCTION__);
baud_mask = B0;
priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS);
} else {
baud_mask = (cflag & CBAUD);
switch(baud_mask) {
case B300: dbg("%s - setting baud 300bps", __FUNCTION__); break;
case B600: dbg("%s - setting baud 600bps", __FUNCTION__); break;
case B1200: dbg("%s - setting baud 1200bps", __FUNCTION__); break;
case B2400: dbg("%s - setting baud 2400bps", __FUNCTION__); break;
case B4800: dbg("%s - setting baud 4800bps", __FUNCTION__); break;
case B9600: dbg("%s - setting baud 9600bps", __FUNCTION__); break;
case B19200: dbg("%s - setting baud 19200bps", __FUNCTION__); break;
case B38400: dbg("%s - setting baud 38400bps", __FUNCTION__); break;
case B57600: dbg("%s - setting baud 57600bps", __FUNCTION__); break;
case B115200: dbg("%s - setting baud 115200bps", __FUNCTION__); break;
default: dbg("%s - unknown masked baud rate", __FUNCTION__);
}
priv->line_control |= CONTROL_DTR;
/* this is probably not what I think it is... check into it */
if (cflag & CRTSCTS)
priv->line_control |= CONTROL_RTS;
else
priv->line_control &= ~CONTROL_RTS;
}
spin_unlock_irqrestore(&priv->lock, flags);
dbg("%s - sending %d stop_bits, %d parity_enable, %d parity_type, %d data_bits (+5)", __FUNCTION__,
stop_bits, parity_enable, parity_type, data_bits);
cypress_serial_control(port, baud_mask, data_bits, stop_bits, parity_enable,
parity_type, 0, CYPRESS_SET_CONFIG);
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(50*HZ/1000); /* give some time between change and read (50ms) */
/* we perform a CYPRESS_GET_CONFIG so that the current settings are filled into the private structure
* this should confirm that all is working if it returns what we just set */
cypress_serial_control(port, 0, 0, 0, 0, 0, 0, CYPRESS_GET_CONFIG);
/* Here we can define custom tty settings for devices
*
* the main tty base comes from empeg.c
*/
spin_lock_irqsave(&priv->lock, flags);
if ( (priv->chiptype == CT_EARTHMATE) && (priv->baud_rate == 4800) ) {
dbg("Using custom termios settings for a baud rate of 4800bps.");
/* define custom termios settings for NMEA protocol */
tty->termios->c_iflag /* input modes - */
&= ~(IGNBRK /* disable ignore break */
| BRKINT /* disable break causes interrupt */
| PARMRK /* disable mark parity errors */
| ISTRIP /* disable clear high bit of input characters */
| INLCR /* disable translate NL to CR */
| IGNCR /* disable ignore CR */
| ICRNL /* disable translate CR to NL */
| IXON); /* disable enable XON/XOFF flow control */
tty->termios->c_oflag /* output modes */
&= ~OPOST; /* disable postprocess output characters */
tty->termios->c_lflag /* line discipline modes */
&= ~(ECHO /* disable echo input characters */
| ECHONL /* disable echo new line */
| ICANON /* disable erase, kill, werase, and rprnt special characters */
| ISIG /* disable interrupt, quit, and suspend special characters */
| IEXTEN); /* disable non-POSIX special characters */
} else if (priv->chiptype == CT_CYPHIDCOM) {
// Software app handling it for device...
} else {
/* do something here */
}
spin_unlock_irqrestore(&priv->lock, flags);
/* set lines */
priv->cmd_ctrl = 1;
cypress_write(port, NULL, 0);
return;
} /* cypress_set_termios */
static int cypress_chars_in_buffer(struct usb_serial_port *port)
{
dbg("%s - port %d", __FUNCTION__, port->number);
/*
* We can't really account for how much data we
* have sent out, but hasn't made it through to the
* device, so just tell the tty layer that everything
* is flushed.
*/
return 0;
}
static void cypress_throttle (struct usb_serial_port *port)
{
struct cypress_private *priv = usb_get_serial_port_data(port);
unsigned long flags;
dbg("%s - port %d", __FUNCTION__, port->number);
spin_lock_irqsave(&priv->lock, flags);
priv->rx_flags = THROTTLED;
spin_unlock_irqrestore(&priv->lock, flags);
}
static void cypress_unthrottle (struct usb_serial_port *port)
{
struct cypress_private *priv = usb_get_serial_port_data(port);
int actually_throttled, result;
unsigned long flags;
dbg("%s - port %d", __FUNCTION__, port->number);
spin_lock_irqsave(&priv->lock, flags);
actually_throttled = priv->rx_flags & ACTUALLY_THROTTLED;
priv->rx_flags = 0;
spin_unlock_irqrestore(&priv->lock, flags);
if (actually_throttled) {
port->interrupt_in_urb->dev = port->serial->dev;
result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
if (result)
dev_err(&port->dev, "%s - failed submitting read urb, error %d\n", __FUNCTION__, result);
}
}
static void cypress_read_int_callback(struct urb *urb, struct pt_regs *regs)
{
struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
struct cypress_private *priv = usb_get_serial_port_data(port);
struct tty_struct *tty;
unsigned char *data = urb->transfer_buffer;
unsigned long flags;
char tty_flag = TTY_NORMAL;
int bytes=0;
int result;
int i=0;
dbg("%s - port %d", __FUNCTION__, port->number);
if (urb->status) {
dbg("%s - nonzero read status received: %d", __FUNCTION__, urb->status);
return;
}
spin_lock_irqsave(&priv->lock, flags);
if (priv->rx_flags & THROTTLED) {
priv->rx_flags |= ACTUALLY_THROTTLED;
spin_unlock_irqrestore(&priv->lock, flags);
return;
}
spin_unlock_irqrestore(&priv->lock, flags);
tty = port->tty;
if (!tty) {
dbg("%s - bad tty pointer - exiting", __FUNCTION__);
return;
}
usb_serial_debug_data (debug, &port->dev, __FUNCTION__, urb->actual_length, data);
spin_lock_irqsave(&priv->lock, flags);
switch(urb->actual_length) {
case 32:
// This is for the CY7C64013...
priv->current_status = data[0] & 0xF8;
bytes = data[1]+2;
i=2;
break;
case 8:
// This is for the CY7C63743...
priv->current_status = data[0] & 0xF8;
bytes = (data[0] & 0x07)+1;
i=1;
break;
default:
dbg("%s - wrong packet size - received %d bytes", __FUNCTION__, urb->actual_length);
spin_unlock_irqrestore(&priv->lock, flags);
goto continue_read;
}
spin_unlock_irqrestore(&priv->lock, flags);
spin_lock_irqsave(&priv->lock, flags);
/* check to see if status has changed */
if (priv != NULL) {
if (priv->current_status != priv->prev_status) {
priv->diff_status |= priv->current_status ^ priv->prev_status;
wake_up_interruptible(&priv->delta_msr_wait);
priv->prev_status = priv->current_status;
}
}
spin_unlock_irqrestore(&priv->lock, flags);
/* hangup, as defined in acm.c... this might be a bad place for it though */
if (tty && !(tty->termios->c_cflag & CLOCAL) && !(priv->current_status & UART_CD)) {
dbg("%s - calling hangup", __FUNCTION__);
tty_hangup(tty);
goto continue_read;
}
/* There is one error bit... I'm assuming it is a parity error indicator
* as the generic firmware will set this bit to 1 if a parity error occurs.
* I can not find reference to any other error events.
*
*/
spin_lock_irqsave(&priv->lock, flags);
if (priv->current_status & CYP_ERROR) {
spin_unlock_irqrestore(&priv->lock, flags);
tty_flag = TTY_PARITY;
dbg("%s - Parity Error detected", __FUNCTION__);
} else
spin_unlock_irqrestore(&priv->lock, flags);
/* process read if there is data other than line status */
if (tty && (bytes > i)) {
for (; i < bytes ; ++i) {
dbg("pushing byte number %d - %d",i,data[i]);
if(tty->flip.count >= TTY_FLIPBUF_SIZE) {
tty_flip_buffer_push(tty);
}
tty_insert_flip_char(tty, data[i], tty_flag);
}
tty_flip_buffer_push(port->tty);
}
spin_lock_irqsave(&priv->lock, flags);
priv->bytes_in += bytes; /* control and status byte(s) are also counted */
spin_unlock_irqrestore(&priv->lock, flags);
continue_read:
/* Continue trying to always read... unless the port has closed. */
if (port->open_count > 0) {
usb_fill_int_urb(port->interrupt_in_urb, port->serial->dev,
usb_rcvintpipe(port->serial->dev, port->interrupt_in_endpointAddress),
port->interrupt_in_urb->transfer_buffer,
port->interrupt_in_urb->transfer_buffer_length,
cypress_read_int_callback, port,
port->interrupt_in_urb->interval);
result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
if (result)
dev_err(&urb->dev->dev, "%s - failed resubmitting read urb, error %d\n", __FUNCTION__, result);
}
return;
} /* cypress_read_int_callback */
static void cypress_write_int_callback(struct urb *urb, struct pt_regs *regs)
{
struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
/* free up the transfer buffer, as usb_free_urb() does not do this */
kfree (urb->transfer_buffer);
dbg("%s - port %d", __FUNCTION__, port->number);
if (urb->status) {
dbg("%s - nonzero write status received: %d", __FUNCTION__, urb->status);
return;
}
schedule_work(&port->work);
}
/*****************************************************************************
* Module functions
*****************************************************************************/
static int __init cypress_init(void)
{
int retval;
dbg("%s", __FUNCTION__);
retval = usb_serial_register(&cypress_earthmate_device);
if (retval)
goto failed_em_register;
retval = usb_serial_register(&cypress_hidcom_device);
if (retval)
goto failed_hidcom_register;
retval = usb_register(&cypress_driver);
if (retval)
goto failed_usb_register;
info(DRIVER_DESC " " DRIVER_VERSION);
return 0;
failed_usb_register:
usb_deregister(&cypress_driver);
failed_hidcom_register:
usb_serial_deregister(&cypress_hidcom_device);
failed_em_register:
usb_serial_deregister(&cypress_earthmate_device);
return retval;
}
static void __exit cypress_exit (void)
{
dbg("%s", __FUNCTION__);
usb_deregister (&cypress_driver);
usb_serial_deregister (&cypress_earthmate_device);
usb_serial_deregister (&cypress_hidcom_device);
}
module_init(cypress_init);
module_exit(cypress_exit);
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");
module_param(stats, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(stats, "Enable statistics or not");
#ifndef CYPRESS_M8_H
#define CYPRESS_M8_H
/* definitions and function prototypes used for the cypress USB to Serial controller */
/* For sending our feature buffer - controlling serial communication states */
/* Linux HID has no support for serial devices so we do this through the driver */
#define HID_REQ_GET_REPORT 0x01
#define HID_REQ_SET_REPORT 0x09
/* List other cypress USB to Serial devices here, and add them to the id_table */
/* DeLorme Earthmate USB - a GPS device */
#define VENDOR_ID_DELORME 0x1163
#define PRODUCT_ID_EARTHMATEUSB 0x0100
/* Cypress HID->COM RS232 Adapter */
#define VENDOR_ID_CYPRESS 0x04b4
#define PRODUCT_ID_CYPHIDCOM 0x5500
/* End of device listing */
/* Used for setting / requesting serial line settings */
#define CYPRESS_SET_CONFIG 0x01
#define CYPRESS_GET_CONFIG 0x02
/* Used for throttle control */
#define THROTTLED 0x1
#define ACTUALLY_THROTTLED 0x2
/* chiptypes - used in case firmware differs from the generic form ... offering
* different baud speeds/etc.
*/
#define CT_EARTHMATE 0x01
#define CT_CYPHIDCOM 0x02
#define CT_GENERIC 0x0F
/* End of chiptype definitions */
/* RS-232 serial data communication protocol definitions */
/* these are sent / read at byte 0 of the input/output hid reports */
/* You can find these values defined in the CY4601 USB to Serial design notes */
#define CONTROL_DTR 0x20 /* data terminal ready - flow control - host to device */
#define UART_DSR 0x20 /* data set ready - flow control - device to host */
#define CONTROL_RTS 0x10 /* request to send - flow control - host to device */
#define UART_CTS 0x10 /* clear to send - flow control - device to host */
#define UART_RI 0x10 /* ring indicator - modem - device to host */
#define UART_CD 0x40 /* carrier detect - modem - device to host */
#define CYP_ERROR 0x08 /* received from input report - device to host */
/* Note - the below has nothing to to with the "feature report" reset */
#define CONTROL_RESET 0x08 /* sent with output report - host to device */
/* End of RS-232 protocol definitions */
#endif /* CYPRESS_M8_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment