forked from Minki/linux
683c5cfa5d
The driver write_room and chars_in_buffer callbacks used to incorrectly
return a negative errno in case they were ever called with a NULL port
driver-data pointer. The return value was later changed to zero by
commit 23198fda71
("tty: fix chars_in_buffers") but the bogus sanity
checks were left in place as were the outdated function-header comments.
The port driver data isn't cleared until after the port has been
deregistered and all open ttys have been hung up so drop the unnecessary
sanity checks and the outdated comments.
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Johan Hovold <johan@kernel.org>
1781 lines
48 KiB
C
1781 lines
48 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* mos7720.c
|
|
* Controls the Moschip 7720 usb to dual port serial converter
|
|
*
|
|
* Copyright 2006 Moschip Semiconductor Tech. Ltd.
|
|
*
|
|
* Developed by:
|
|
* Vijaya Kumar <vijaykumar.gn@gmail.com>
|
|
* Ajay Kumar <naanuajay@yahoo.com>
|
|
* Gurudeva <ngurudeva@yahoo.com>
|
|
*
|
|
* 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/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 <linux/uaccess.h>
|
|
#include <linux/parport.h>
|
|
|
|
#define DRIVER_AUTHOR "Aspire Communications pvt Ltd."
|
|
#define DRIVER_DESC "Moschip USB Serial Driver"
|
|
|
|
/* default urb timeout */
|
|
#define MOS_WDR_TIMEOUT 5000
|
|
|
|
#define MOS_MAX_PORT 0x02
|
|
#define MOS_WRITE 0x0E
|
|
#define MOS_READ 0x0D
|
|
|
|
/* Interrupt Routines 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 serial 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 usb_serial_port *port; /* loop back to the owner */
|
|
struct urb *write_urb_pool[NUM_URBS];
|
|
};
|
|
|
|
#define USB_VENDOR_ID_MOSCHIP 0x9710
|
|
#define MOSCHIP_DEVICE_ID_7720 0x7720
|
|
#define MOSCHIP_DEVICE_ID_7715 0x7715
|
|
|
|
static const struct usb_device_id id_table[] = {
|
|
{ USB_DEVICE(USB_VENDOR_ID_MOSCHIP, MOSCHIP_DEVICE_ID_7720) },
|
|
{ USB_DEVICE(USB_VENDOR_ID_MOSCHIP, MOSCHIP_DEVICE_ID_7715) },
|
|
{ } /* terminating entry */
|
|
};
|
|
MODULE_DEVICE_TABLE(usb, id_table);
|
|
|
|
#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT
|
|
|
|
/* initial values for parport regs */
|
|
#define DCR_INIT_VAL 0x0c /* SLCTIN, nINIT */
|
|
#define ECR_INIT_VAL 0x00 /* SPP mode */
|
|
|
|
enum mos7715_pp_modes {
|
|
SPP = 0<<5,
|
|
PS2 = 1<<5, /* moschip calls this 'NIBBLE' mode */
|
|
PPF = 2<<5, /* moschip calls this 'CB-FIFO mode */
|
|
};
|
|
|
|
struct mos7715_parport {
|
|
struct parport *pp; /* back to containing struct */
|
|
struct kref ref_count; /* to instance of this struct */
|
|
bool msg_pending; /* usb sync call pending */
|
|
struct completion syncmsg_compl; /* usb sync call completed */
|
|
struct work_struct work; /* restore deferred writes */
|
|
struct usb_serial *serial; /* back to containing struct */
|
|
__u8 shadowECR; /* parallel port regs... */
|
|
__u8 shadowDCR;
|
|
atomic_t shadowDSR; /* updated in int-in callback */
|
|
};
|
|
|
|
/* lock guards against dereferencing NULL ptr in parport ops callbacks */
|
|
static DEFINE_SPINLOCK(release_lock);
|
|
|
|
#endif /* CONFIG_USB_SERIAL_MOS7715_PARPORT */
|
|
|
|
static const unsigned int dummy; /* for clarity in register access fns */
|
|
|
|
enum mos_regs {
|
|
MOS7720_THR, /* serial port regs */
|
|
MOS7720_RHR,
|
|
MOS7720_IER,
|
|
MOS7720_FCR,
|
|
MOS7720_ISR,
|
|
MOS7720_LCR,
|
|
MOS7720_MCR,
|
|
MOS7720_LSR,
|
|
MOS7720_MSR,
|
|
MOS7720_SPR,
|
|
MOS7720_DLL,
|
|
MOS7720_DLM,
|
|
MOS7720_DPR, /* parallel port regs */
|
|
MOS7720_DSR,
|
|
MOS7720_DCR,
|
|
MOS7720_ECR,
|
|
MOS7720_SP1_REG, /* device control regs */
|
|
MOS7720_SP2_REG, /* serial port 2 (7720 only) */
|
|
MOS7720_PP_REG,
|
|
MOS7720_SP_CONTROL_REG,
|
|
};
|
|
|
|
/*
|
|
* Return the correct value for the Windex field of the setup packet
|
|
* for a control endpoint message. See the 7715 datasheet.
|
|
*/
|
|
static inline __u16 get_reg_index(enum mos_regs reg)
|
|
{
|
|
static const __u16 mos7715_index_lookup_table[] = {
|
|
0x00, /* MOS7720_THR */
|
|
0x00, /* MOS7720_RHR */
|
|
0x01, /* MOS7720_IER */
|
|
0x02, /* MOS7720_FCR */
|
|
0x02, /* MOS7720_ISR */
|
|
0x03, /* MOS7720_LCR */
|
|
0x04, /* MOS7720_MCR */
|
|
0x05, /* MOS7720_LSR */
|
|
0x06, /* MOS7720_MSR */
|
|
0x07, /* MOS7720_SPR */
|
|
0x00, /* MOS7720_DLL */
|
|
0x01, /* MOS7720_DLM */
|
|
0x00, /* MOS7720_DPR */
|
|
0x01, /* MOS7720_DSR */
|
|
0x02, /* MOS7720_DCR */
|
|
0x0a, /* MOS7720_ECR */
|
|
0x01, /* MOS7720_SP1_REG */
|
|
0x02, /* MOS7720_SP2_REG (7720 only) */
|
|
0x04, /* MOS7720_PP_REG (7715 only) */
|
|
0x08, /* MOS7720_SP_CONTROL_REG */
|
|
};
|
|
return mos7715_index_lookup_table[reg];
|
|
}
|
|
|
|
/*
|
|
* Return the correct value for the upper byte of the Wvalue field of
|
|
* the setup packet for a control endpoint message.
|
|
*/
|
|
static inline __u16 get_reg_value(enum mos_regs reg,
|
|
unsigned int serial_portnum)
|
|
{
|
|
if (reg >= MOS7720_SP1_REG) /* control reg */
|
|
return 0x0000;
|
|
|
|
else if (reg >= MOS7720_DPR) /* parallel port reg (7715 only) */
|
|
return 0x0100;
|
|
|
|
else /* serial port reg */
|
|
return (serial_portnum + 2) << 8;
|
|
}
|
|
|
|
/*
|
|
* Write data byte to the specified device register. The data is embedded in
|
|
* the value field of the setup packet. serial_portnum is ignored for registers
|
|
* not specific to a particular serial port.
|
|
*/
|
|
static int write_mos_reg(struct usb_serial *serial, unsigned int serial_portnum,
|
|
enum mos_regs reg, __u8 data)
|
|
{
|
|
struct usb_device *usbdev = serial->dev;
|
|
unsigned int pipe = usb_sndctrlpipe(usbdev, 0);
|
|
__u8 request = (__u8)0x0e;
|
|
__u8 requesttype = (__u8)0x40;
|
|
__u16 index = get_reg_index(reg);
|
|
__u16 value = get_reg_value(reg, serial_portnum) + data;
|
|
int status = usb_control_msg(usbdev, pipe, request, requesttype, value,
|
|
index, NULL, 0, MOS_WDR_TIMEOUT);
|
|
if (status < 0)
|
|
dev_err(&usbdev->dev,
|
|
"mos7720: usb_control_msg() failed: %d\n", status);
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Read data byte from the specified device register. The data returned by the
|
|
* device is embedded in the value field of the setup packet. serial_portnum is
|
|
* ignored for registers that are not specific to a particular serial port.
|
|
*/
|
|
static int read_mos_reg(struct usb_serial *serial, unsigned int serial_portnum,
|
|
enum mos_regs reg, __u8 *data)
|
|
{
|
|
struct usb_device *usbdev = serial->dev;
|
|
unsigned int pipe = usb_rcvctrlpipe(usbdev, 0);
|
|
__u8 request = (__u8)0x0d;
|
|
__u8 requesttype = (__u8)0xc0;
|
|
__u16 index = get_reg_index(reg);
|
|
__u16 value = get_reg_value(reg, serial_portnum);
|
|
u8 *buf;
|
|
int status;
|
|
|
|
buf = kmalloc(1, GFP_KERNEL);
|
|
if (!buf) {
|
|
*data = 0;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
status = usb_control_msg(usbdev, pipe, request, requesttype, value,
|
|
index, buf, 1, MOS_WDR_TIMEOUT);
|
|
if (status == 1) {
|
|
*data = *buf;
|
|
} else {
|
|
dev_err(&usbdev->dev,
|
|
"mos7720: usb_control_msg() failed: %d\n", status);
|
|
if (status >= 0)
|
|
status = -EIO;
|
|
*data = 0;
|
|
}
|
|
|
|
kfree(buf);
|
|
|
|
return status;
|
|
}
|
|
|
|
#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT
|
|
|
|
static inline int mos7715_change_mode(struct mos7715_parport *mos_parport,
|
|
enum mos7715_pp_modes mode)
|
|
{
|
|
mos_parport->shadowECR = mode;
|
|
write_mos_reg(mos_parport->serial, dummy, MOS7720_ECR,
|
|
mos_parport->shadowECR);
|
|
return 0;
|
|
}
|
|
|
|
static void destroy_mos_parport(struct kref *kref)
|
|
{
|
|
struct mos7715_parport *mos_parport =
|
|
container_of(kref, struct mos7715_parport, ref_count);
|
|
|
|
kfree(mos_parport);
|
|
}
|
|
|
|
/*
|
|
* This is the common top part of all parallel port callback operations that
|
|
* send synchronous messages to the device. This implements convoluted locking
|
|
* that avoids two scenarios: (1) a port operation is called after usbserial
|
|
* has called our release function, at which point struct mos7715_parport has
|
|
* been destroyed, and (2) the device has been disconnected, but usbserial has
|
|
* not called the release function yet because someone has a serial port open.
|
|
* The shared release_lock prevents the first, and the mutex and disconnected
|
|
* flag maintained by usbserial covers the second. We also use the msg_pending
|
|
* flag to ensure that all synchronous usb message calls have completed before
|
|
* our release function can return.
|
|
*/
|
|
static int parport_prologue(struct parport *pp)
|
|
{
|
|
struct mos7715_parport *mos_parport;
|
|
|
|
spin_lock(&release_lock);
|
|
mos_parport = pp->private_data;
|
|
if (unlikely(mos_parport == NULL)) {
|
|
/* release fn called, port struct destroyed */
|
|
spin_unlock(&release_lock);
|
|
return -1;
|
|
}
|
|
mos_parport->msg_pending = true; /* synch usb call pending */
|
|
reinit_completion(&mos_parport->syncmsg_compl);
|
|
spin_unlock(&release_lock);
|
|
|
|
/* ensure writes from restore are submitted before new requests */
|
|
if (work_pending(&mos_parport->work))
|
|
flush_work(&mos_parport->work);
|
|
|
|
mutex_lock(&mos_parport->serial->disc_mutex);
|
|
if (mos_parport->serial->disconnected) {
|
|
/* device disconnected */
|
|
mutex_unlock(&mos_parport->serial->disc_mutex);
|
|
mos_parport->msg_pending = false;
|
|
complete(&mos_parport->syncmsg_compl);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This is the common bottom part of all parallel port functions that send
|
|
* synchronous messages to the device.
|
|
*/
|
|
static inline void parport_epilogue(struct parport *pp)
|
|
{
|
|
struct mos7715_parport *mos_parport = pp->private_data;
|
|
mutex_unlock(&mos_parport->serial->disc_mutex);
|
|
mos_parport->msg_pending = false;
|
|
complete(&mos_parport->syncmsg_compl);
|
|
}
|
|
|
|
static void deferred_restore_writes(struct work_struct *work)
|
|
{
|
|
struct mos7715_parport *mos_parport;
|
|
|
|
mos_parport = container_of(work, struct mos7715_parport, work);
|
|
|
|
mutex_lock(&mos_parport->serial->disc_mutex);
|
|
|
|
/* if device disconnected, game over */
|
|
if (mos_parport->serial->disconnected)
|
|
goto done;
|
|
|
|
write_mos_reg(mos_parport->serial, dummy, MOS7720_DCR,
|
|
mos_parport->shadowDCR);
|
|
write_mos_reg(mos_parport->serial, dummy, MOS7720_ECR,
|
|
mos_parport->shadowECR);
|
|
done:
|
|
mutex_unlock(&mos_parport->serial->disc_mutex);
|
|
}
|
|
|
|
static void parport_mos7715_write_data(struct parport *pp, unsigned char d)
|
|
{
|
|
struct mos7715_parport *mos_parport = pp->private_data;
|
|
|
|
if (parport_prologue(pp) < 0)
|
|
return;
|
|
mos7715_change_mode(mos_parport, SPP);
|
|
write_mos_reg(mos_parport->serial, dummy, MOS7720_DPR, (__u8)d);
|
|
parport_epilogue(pp);
|
|
}
|
|
|
|
static unsigned char parport_mos7715_read_data(struct parport *pp)
|
|
{
|
|
struct mos7715_parport *mos_parport = pp->private_data;
|
|
unsigned char d;
|
|
|
|
if (parport_prologue(pp) < 0)
|
|
return 0;
|
|
read_mos_reg(mos_parport->serial, dummy, MOS7720_DPR, &d);
|
|
parport_epilogue(pp);
|
|
return d;
|
|
}
|
|
|
|
static void parport_mos7715_write_control(struct parport *pp, unsigned char d)
|
|
{
|
|
struct mos7715_parport *mos_parport = pp->private_data;
|
|
__u8 data;
|
|
|
|
if (parport_prologue(pp) < 0)
|
|
return;
|
|
data = ((__u8)d & 0x0f) | (mos_parport->shadowDCR & 0xf0);
|
|
write_mos_reg(mos_parport->serial, dummy, MOS7720_DCR, data);
|
|
mos_parport->shadowDCR = data;
|
|
parport_epilogue(pp);
|
|
}
|
|
|
|
static unsigned char parport_mos7715_read_control(struct parport *pp)
|
|
{
|
|
struct mos7715_parport *mos_parport;
|
|
__u8 dcr;
|
|
|
|
spin_lock(&release_lock);
|
|
mos_parport = pp->private_data;
|
|
if (unlikely(mos_parport == NULL)) {
|
|
spin_unlock(&release_lock);
|
|
return 0;
|
|
}
|
|
dcr = mos_parport->shadowDCR & 0x0f;
|
|
spin_unlock(&release_lock);
|
|
return dcr;
|
|
}
|
|
|
|
static unsigned char parport_mos7715_frob_control(struct parport *pp,
|
|
unsigned char mask,
|
|
unsigned char val)
|
|
{
|
|
struct mos7715_parport *mos_parport = pp->private_data;
|
|
__u8 dcr;
|
|
|
|
mask &= 0x0f;
|
|
val &= 0x0f;
|
|
if (parport_prologue(pp) < 0)
|
|
return 0;
|
|
mos_parport->shadowDCR = (mos_parport->shadowDCR & (~mask)) ^ val;
|
|
write_mos_reg(mos_parport->serial, dummy, MOS7720_DCR,
|
|
mos_parport->shadowDCR);
|
|
dcr = mos_parport->shadowDCR & 0x0f;
|
|
parport_epilogue(pp);
|
|
return dcr;
|
|
}
|
|
|
|
static unsigned char parport_mos7715_read_status(struct parport *pp)
|
|
{
|
|
unsigned char status;
|
|
struct mos7715_parport *mos_parport;
|
|
|
|
spin_lock(&release_lock);
|
|
mos_parport = pp->private_data;
|
|
if (unlikely(mos_parport == NULL)) { /* release called */
|
|
spin_unlock(&release_lock);
|
|
return 0;
|
|
}
|
|
status = atomic_read(&mos_parport->shadowDSR) & 0xf8;
|
|
spin_unlock(&release_lock);
|
|
return status;
|
|
}
|
|
|
|
static void parport_mos7715_enable_irq(struct parport *pp)
|
|
{
|
|
}
|
|
|
|
static void parport_mos7715_disable_irq(struct parport *pp)
|
|
{
|
|
}
|
|
|
|
static void parport_mos7715_data_forward(struct parport *pp)
|
|
{
|
|
struct mos7715_parport *mos_parport = pp->private_data;
|
|
|
|
if (parport_prologue(pp) < 0)
|
|
return;
|
|
mos7715_change_mode(mos_parport, PS2);
|
|
mos_parport->shadowDCR &= ~0x20;
|
|
write_mos_reg(mos_parport->serial, dummy, MOS7720_DCR,
|
|
mos_parport->shadowDCR);
|
|
parport_epilogue(pp);
|
|
}
|
|
|
|
static void parport_mos7715_data_reverse(struct parport *pp)
|
|
{
|
|
struct mos7715_parport *mos_parport = pp->private_data;
|
|
|
|
if (parport_prologue(pp) < 0)
|
|
return;
|
|
mos7715_change_mode(mos_parport, PS2);
|
|
mos_parport->shadowDCR |= 0x20;
|
|
write_mos_reg(mos_parport->serial, dummy, MOS7720_DCR,
|
|
mos_parport->shadowDCR);
|
|
parport_epilogue(pp);
|
|
}
|
|
|
|
static void parport_mos7715_init_state(struct pardevice *dev,
|
|
struct parport_state *s)
|
|
{
|
|
s->u.pc.ctr = DCR_INIT_VAL;
|
|
s->u.pc.ecr = ECR_INIT_VAL;
|
|
}
|
|
|
|
/* N.B. Parport core code requires that this function not block */
|
|
static void parport_mos7715_save_state(struct parport *pp,
|
|
struct parport_state *s)
|
|
{
|
|
struct mos7715_parport *mos_parport;
|
|
|
|
spin_lock(&release_lock);
|
|
mos_parport = pp->private_data;
|
|
if (unlikely(mos_parport == NULL)) { /* release called */
|
|
spin_unlock(&release_lock);
|
|
return;
|
|
}
|
|
s->u.pc.ctr = mos_parport->shadowDCR;
|
|
s->u.pc.ecr = mos_parport->shadowECR;
|
|
spin_unlock(&release_lock);
|
|
}
|
|
|
|
/* N.B. Parport core code requires that this function not block */
|
|
static void parport_mos7715_restore_state(struct parport *pp,
|
|
struct parport_state *s)
|
|
{
|
|
struct mos7715_parport *mos_parport;
|
|
|
|
spin_lock(&release_lock);
|
|
mos_parport = pp->private_data;
|
|
if (unlikely(mos_parport == NULL)) { /* release called */
|
|
spin_unlock(&release_lock);
|
|
return;
|
|
}
|
|
mos_parport->shadowDCR = s->u.pc.ctr;
|
|
mos_parport->shadowECR = s->u.pc.ecr;
|
|
|
|
schedule_work(&mos_parport->work);
|
|
spin_unlock(&release_lock);
|
|
}
|
|
|
|
static size_t parport_mos7715_write_compat(struct parport *pp,
|
|
const void *buffer,
|
|
size_t len, int flags)
|
|
{
|
|
int retval;
|
|
struct mos7715_parport *mos_parport = pp->private_data;
|
|
int actual_len;
|
|
|
|
if (parport_prologue(pp) < 0)
|
|
return 0;
|
|
mos7715_change_mode(mos_parport, PPF);
|
|
retval = usb_bulk_msg(mos_parport->serial->dev,
|
|
usb_sndbulkpipe(mos_parport->serial->dev, 2),
|
|
(void *)buffer, len, &actual_len,
|
|
MOS_WDR_TIMEOUT);
|
|
parport_epilogue(pp);
|
|
if (retval) {
|
|
dev_err(&mos_parport->serial->dev->dev,
|
|
"mos7720: usb_bulk_msg() failed: %d\n", retval);
|
|
return 0;
|
|
}
|
|
return actual_len;
|
|
}
|
|
|
|
static struct parport_operations parport_mos7715_ops = {
|
|
.owner = THIS_MODULE,
|
|
.write_data = parport_mos7715_write_data,
|
|
.read_data = parport_mos7715_read_data,
|
|
|
|
.write_control = parport_mos7715_write_control,
|
|
.read_control = parport_mos7715_read_control,
|
|
.frob_control = parport_mos7715_frob_control,
|
|
|
|
.read_status = parport_mos7715_read_status,
|
|
|
|
.enable_irq = parport_mos7715_enable_irq,
|
|
.disable_irq = parport_mos7715_disable_irq,
|
|
|
|
.data_forward = parport_mos7715_data_forward,
|
|
.data_reverse = parport_mos7715_data_reverse,
|
|
|
|
.init_state = parport_mos7715_init_state,
|
|
.save_state = parport_mos7715_save_state,
|
|
.restore_state = parport_mos7715_restore_state,
|
|
|
|
.compat_write_data = parport_mos7715_write_compat,
|
|
|
|
.nibble_read_data = parport_ieee1284_read_nibble,
|
|
.byte_read_data = parport_ieee1284_read_byte,
|
|
};
|
|
|
|
/*
|
|
* Allocate and initialize parallel port control struct, initialize
|
|
* the parallel port hardware device, and register with the parport subsystem.
|
|
*/
|
|
static int mos7715_parport_init(struct usb_serial *serial)
|
|
{
|
|
struct mos7715_parport *mos_parport;
|
|
|
|
/* allocate and initialize parallel port control struct */
|
|
mos_parport = kzalloc(sizeof(struct mos7715_parport), GFP_KERNEL);
|
|
if (!mos_parport)
|
|
return -ENOMEM;
|
|
|
|
mos_parport->msg_pending = false;
|
|
kref_init(&mos_parport->ref_count);
|
|
usb_set_serial_data(serial, mos_parport); /* hijack private pointer */
|
|
mos_parport->serial = serial;
|
|
INIT_WORK(&mos_parport->work, deferred_restore_writes);
|
|
init_completion(&mos_parport->syncmsg_compl);
|
|
|
|
/* cycle parallel port reset bit */
|
|
write_mos_reg(mos_parport->serial, dummy, MOS7720_PP_REG, (__u8)0x80);
|
|
write_mos_reg(mos_parport->serial, dummy, MOS7720_PP_REG, (__u8)0x00);
|
|
|
|
/* initialize device registers */
|
|
mos_parport->shadowDCR = DCR_INIT_VAL;
|
|
write_mos_reg(mos_parport->serial, dummy, MOS7720_DCR,
|
|
mos_parport->shadowDCR);
|
|
mos_parport->shadowECR = ECR_INIT_VAL;
|
|
write_mos_reg(mos_parport->serial, dummy, MOS7720_ECR,
|
|
mos_parport->shadowECR);
|
|
|
|
/* register with parport core */
|
|
mos_parport->pp = parport_register_port(0, PARPORT_IRQ_NONE,
|
|
PARPORT_DMA_NONE,
|
|
&parport_mos7715_ops);
|
|
if (mos_parport->pp == NULL) {
|
|
dev_err(&serial->interface->dev,
|
|
"Could not register parport\n");
|
|
kref_put(&mos_parport->ref_count, destroy_mos_parport);
|
|
return -EIO;
|
|
}
|
|
mos_parport->pp->private_data = mos_parport;
|
|
mos_parport->pp->modes = PARPORT_MODE_COMPAT | PARPORT_MODE_PCSPP;
|
|
mos_parport->pp->dev = &serial->interface->dev;
|
|
parport_announce_port(mos_parport->pp);
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_USB_SERIAL_MOS7715_PARPORT */
|
|
|
|
/*
|
|
* 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;
|
|
int status = urb->status;
|
|
struct device *dev = &urb->dev->dev;
|
|
__u8 *data;
|
|
__u8 sp1;
|
|
__u8 sp2;
|
|
|
|
switch (status) {
|
|
case 0:
|
|
/* success */
|
|
break;
|
|
case -ECONNRESET:
|
|
case -ENOENT:
|
|
case -ESHUTDOWN:
|
|
/* this urb is terminated, clean up */
|
|
dev_dbg(dev, "%s - urb shutting down with status: %d\n", __func__, status);
|
|
return;
|
|
default:
|
|
dev_dbg(dev, "%s - nonzero urb status received: %d\n", __func__, 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 */
|
|
|
|
/* the above description is inverted
|
|
* oneukum 2007-03-14 */
|
|
|
|
if (unlikely(length != 4)) {
|
|
dev_dbg(dev, "Wrong data !!!\n");
|
|
return;
|
|
}
|
|
|
|
sp1 = data[3];
|
|
sp2 = data[2];
|
|
|
|
if ((sp1 | sp2) & 0x01) {
|
|
/* No Interrupt Pending in both the ports */
|
|
dev_dbg(dev, "No Interrupt !!!\n");
|
|
} else {
|
|
switch (sp1 & 0x0f) {
|
|
case SERIAL_IIR_RLS:
|
|
dev_dbg(dev, "Serial Port 1: Receiver status error or address bit detected in 9-bit mode\n");
|
|
break;
|
|
case SERIAL_IIR_CTI:
|
|
dev_dbg(dev, "Serial Port 1: Receiver time out\n");
|
|
break;
|
|
case SERIAL_IIR_MS:
|
|
/* dev_dbg(dev, "Serial Port 1: Modem status change\n"); */
|
|
break;
|
|
}
|
|
|
|
switch (sp2 & 0x0f) {
|
|
case SERIAL_IIR_RLS:
|
|
dev_dbg(dev, "Serial Port 2: Receiver status error or address bit detected in 9-bit mode\n");
|
|
break;
|
|
case SERIAL_IIR_CTI:
|
|
dev_dbg(dev, "Serial Port 2: Receiver time out\n");
|
|
break;
|
|
case SERIAL_IIR_MS:
|
|
/* dev_dbg(dev, "Serial Port 2: Modem status change\n"); */
|
|
break;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
result = usb_submit_urb(urb, GFP_ATOMIC);
|
|
if (result)
|
|
dev_err(dev, "%s - Error %d submitting control urb\n", __func__, result);
|
|
}
|
|
|
|
/*
|
|
* mos7715_interrupt_callback
|
|
* this is the 7715's callback function for when we have received data on
|
|
* the interrupt endpoint.
|
|
*/
|
|
static void mos7715_interrupt_callback(struct urb *urb)
|
|
{
|
|
int result;
|
|
int length;
|
|
int status = urb->status;
|
|
struct device *dev = &urb->dev->dev;
|
|
__u8 *data;
|
|
__u8 iir;
|
|
|
|
switch (status) {
|
|
case 0:
|
|
/* success */
|
|
break;
|
|
case -ECONNRESET:
|
|
case -ENOENT:
|
|
case -ESHUTDOWN:
|
|
case -ENODEV:
|
|
/* this urb is terminated, clean up */
|
|
dev_dbg(dev, "%s - urb shutting down with status: %d\n", __func__, status);
|
|
return;
|
|
default:
|
|
dev_dbg(dev, "%s - nonzero urb status received: %d\n", __func__, status);
|
|
goto exit;
|
|
}
|
|
|
|
length = urb->actual_length;
|
|
data = urb->transfer_buffer;
|
|
|
|
/* Structure of data from 7715 device:
|
|
* Byte 1: IIR serial Port
|
|
* Byte 2: unused
|
|
* Byte 2: DSR parallel port
|
|
* Byte 4: FIFO status for both */
|
|
|
|
if (unlikely(length != 4)) {
|
|
dev_dbg(dev, "Wrong data !!!\n");
|
|
return;
|
|
}
|
|
|
|
iir = data[0];
|
|
if (!(iir & 0x01)) { /* serial port interrupt pending */
|
|
switch (iir & 0x0f) {
|
|
case SERIAL_IIR_RLS:
|
|
dev_dbg(dev, "Serial Port: Receiver status error or address bit detected in 9-bit mode\n");
|
|
break;
|
|
case SERIAL_IIR_CTI:
|
|
dev_dbg(dev, "Serial Port: Receiver time out\n");
|
|
break;
|
|
case SERIAL_IIR_MS:
|
|
/* dev_dbg(dev, "Serial Port: Modem status change\n"); */
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT
|
|
{ /* update local copy of DSR reg */
|
|
struct usb_serial_port *port = urb->context;
|
|
struct mos7715_parport *mos_parport = port->serial->private;
|
|
if (unlikely(mos_parport == NULL))
|
|
return;
|
|
atomic_set(&mos_parport->shadowDSR, data[2]);
|
|
}
|
|
#endif
|
|
|
|
exit:
|
|
result = usb_submit_urb(urb, GFP_ATOMIC);
|
|
if (result)
|
|
dev_err(dev, "%s - Error %d submitting control urb\n", __func__, result);
|
|
}
|
|
|
|
/*
|
|
* 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 retval;
|
|
unsigned char *data ;
|
|
struct usb_serial_port *port;
|
|
int status = urb->status;
|
|
|
|
if (status) {
|
|
dev_dbg(&urb->dev->dev, "nonzero read bulk status received: %d\n", status);
|
|
return;
|
|
}
|
|
|
|
port = urb->context;
|
|
|
|
dev_dbg(&port->dev, "Entering...%s\n", __func__);
|
|
|
|
data = urb->transfer_buffer;
|
|
|
|
if (urb->actual_length) {
|
|
tty_insert_flip_string(&port->port, data, urb->actual_length);
|
|
tty_flip_buffer_push(&port->port);
|
|
}
|
|
|
|
if (port->read_urb->status != -EINPROGRESS) {
|
|
retval = usb_submit_urb(port->read_urb, GFP_ATOMIC);
|
|
if (retval)
|
|
dev_dbg(&port->dev, "usb_submit_urb(read bulk) failed, retval = %d\n", retval);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
int status = urb->status;
|
|
|
|
if (status) {
|
|
dev_dbg(&urb->dev->dev, "nonzero write bulk status received:%d\n", status);
|
|
return;
|
|
}
|
|
|
|
mos7720_port = urb->context;
|
|
if (!mos7720_port) {
|
|
dev_dbg(&urb->dev->dev, "NULL mos7720_port pointer\n");
|
|
return ;
|
|
}
|
|
|
|
if (mos7720_port->open)
|
|
tty_port_tty_wakeup(&mos7720_port->port->port);
|
|
}
|
|
|
|
static int mos77xx_calc_num_ports(struct usb_serial *serial,
|
|
struct usb_serial_endpoints *epds)
|
|
{
|
|
u16 product = le16_to_cpu(serial->dev->descriptor.idProduct);
|
|
|
|
if (product == MOSCHIP_DEVICE_ID_7715) {
|
|
/*
|
|
* The 7715 uses the first bulk in/out endpoint pair for the
|
|
* parallel port, and the second for the serial port. We swap
|
|
* the endpoint descriptors here so that the the first and
|
|
* only registered port structure uses the serial-port
|
|
* endpoints.
|
|
*/
|
|
swap(epds->bulk_in[0], epds->bulk_in[1]);
|
|
swap(epds->bulk_out[0], epds->bulk_out[1]);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 2;
|
|
}
|
|
|
|
static int mos7720_open(struct tty_struct *tty, struct usb_serial_port *port)
|
|
{
|
|
struct usb_serial *serial;
|
|
struct urb *urb;
|
|
struct moschip_port *mos7720_port;
|
|
int response;
|
|
int port_number;
|
|
__u8 data;
|
|
int allocated_urbs = 0;
|
|
int j;
|
|
|
|
serial = port->serial;
|
|
|
|
mos7720_port = usb_get_serial_port_data(port);
|
|
if (mos7720_port == 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_KERNEL);
|
|
mos7720_port->write_urb_pool[j] = urb;
|
|
if (!urb)
|
|
continue;
|
|
|
|
urb->transfer_buffer = kmalloc(URB_TRANSFER_BUFFER_SIZE,
|
|
GFP_KERNEL);
|
|
if (!urb->transfer_buffer) {
|
|
usb_free_urb(mos7720_port->write_urb_pool[j]);
|
|
mos7720_port->write_urb_pool[j] = NULL;
|
|
continue;
|
|
}
|
|
allocated_urbs++;
|
|
}
|
|
|
|
if (!allocated_urbs)
|
|
return -ENOMEM;
|
|
|
|
/* Initialize MCS7720 -- Write Init values to corresponding Registers
|
|
*
|
|
* Register Index
|
|
* 0 : MOS7720_THR/MOS7720_RHR
|
|
* 1 : MOS7720_IER
|
|
* 2 : MOS7720_FCR
|
|
* 3 : MOS7720_LCR
|
|
* 4 : MOS7720_MCR
|
|
* 5 : MOS7720_LSR
|
|
* 6 : MOS7720_MSR
|
|
* 7 : MOS7720_SPR
|
|
*
|
|
* 0x08 : SP1/2 Control Reg
|
|
*/
|
|
port_number = port->port_number;
|
|
read_mos_reg(serial, port_number, MOS7720_LSR, &data);
|
|
|
|
dev_dbg(&port->dev, "SS::%p LSR:%x\n", mos7720_port, data);
|
|
|
|
write_mos_reg(serial, dummy, MOS7720_SP1_REG, 0x02);
|
|
write_mos_reg(serial, dummy, MOS7720_SP2_REG, 0x02);
|
|
|
|
write_mos_reg(serial, port_number, MOS7720_IER, 0x00);
|
|
write_mos_reg(serial, port_number, MOS7720_FCR, 0x00);
|
|
|
|
write_mos_reg(serial, port_number, MOS7720_FCR, 0xcf);
|
|
mos7720_port->shadowLCR = 0x03;
|
|
write_mos_reg(serial, port_number, MOS7720_LCR,
|
|
mos7720_port->shadowLCR);
|
|
mos7720_port->shadowMCR = 0x0b;
|
|
write_mos_reg(serial, port_number, MOS7720_MCR,
|
|
mos7720_port->shadowMCR);
|
|
|
|
write_mos_reg(serial, port_number, MOS7720_SP_CONTROL_REG, 0x00);
|
|
read_mos_reg(serial, dummy, MOS7720_SP_CONTROL_REG, &data);
|
|
data = data | (port->port_number + 1);
|
|
write_mos_reg(serial, dummy, MOS7720_SP_CONTROL_REG, data);
|
|
mos7720_port->shadowLCR = 0x83;
|
|
write_mos_reg(serial, port_number, MOS7720_LCR,
|
|
mos7720_port->shadowLCR);
|
|
write_mos_reg(serial, port_number, MOS7720_THR, 0x0c);
|
|
write_mos_reg(serial, port_number, MOS7720_IER, 0x00);
|
|
mos7720_port->shadowLCR = 0x03;
|
|
write_mos_reg(serial, port_number, MOS7720_LCR,
|
|
mos7720_port->shadowLCR);
|
|
write_mos_reg(serial, port_number, MOS7720_IER, 0x0c);
|
|
|
|
response = usb_submit_urb(port->read_urb, GFP_KERNEL);
|
|
if (response)
|
|
dev_err(&port->dev, "%s - Error %d submitting read urb\n",
|
|
__func__, response);
|
|
|
|
/* 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)
|
|
*/
|
|
static unsigned int mos7720_chars_in_buffer(struct tty_struct *tty)
|
|
{
|
|
struct usb_serial_port *port = tty->driver_data;
|
|
struct moschip_port *mos7720_port = usb_get_serial_port_data(port);
|
|
int i;
|
|
unsigned int chars = 0;
|
|
|
|
for (i = 0; i < NUM_URBS; ++i) {
|
|
if (mos7720_port->write_urb_pool[i] &&
|
|
mos7720_port->write_urb_pool[i]->status == -EINPROGRESS)
|
|
chars += URB_TRANSFER_BUFFER_SIZE;
|
|
}
|
|
dev_dbg(&port->dev, "%s - returns %u\n", __func__, chars);
|
|
return chars;
|
|
}
|
|
|
|
static void mos7720_close(struct usb_serial_port *port)
|
|
{
|
|
struct usb_serial *serial;
|
|
struct moschip_port *mos7720_port;
|
|
int j;
|
|
|
|
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, otherwise nop */
|
|
usb_kill_urb(port->write_urb);
|
|
usb_kill_urb(port->read_urb);
|
|
|
|
write_mos_reg(serial, port->port_number, MOS7720_MCR, 0x00);
|
|
write_mos_reg(serial, port->port_number, MOS7720_IER, 0x00);
|
|
|
|
mos7720_port->open = 0;
|
|
}
|
|
|
|
static void mos7720_break(struct tty_struct *tty, int break_state)
|
|
{
|
|
struct usb_serial_port *port = tty->driver_data;
|
|
unsigned char data;
|
|
struct usb_serial *serial;
|
|
struct moschip_port *mos7720_port;
|
|
|
|
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;
|
|
write_mos_reg(serial, port->port_number, MOS7720_LCR,
|
|
mos7720_port->shadowLCR);
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
static unsigned int mos7720_write_room(struct tty_struct *tty)
|
|
{
|
|
struct usb_serial_port *port = tty->driver_data;
|
|
struct moschip_port *mos7720_port = usb_get_serial_port_data(port);
|
|
unsigned int room = 0;
|
|
int i;
|
|
|
|
/* FIXME: Locking */
|
|
for (i = 0; i < NUM_URBS; ++i) {
|
|
if (mos7720_port->write_urb_pool[i] &&
|
|
mos7720_port->write_urb_pool[i]->status != -EINPROGRESS)
|
|
room += URB_TRANSFER_BUFFER_SIZE;
|
|
}
|
|
|
|
dev_dbg(&port->dev, "%s - returns %u\n", __func__, room);
|
|
return room;
|
|
}
|
|
|
|
static int mos7720_write(struct tty_struct *tty, 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;
|
|
|
|
serial = port->serial;
|
|
|
|
mos7720_port = usb_get_serial_port_data(port);
|
|
if (mos7720_port == 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] &&
|
|
mos7720_port->write_urb_pool[i]->status != -EINPROGRESS) {
|
|
urb = mos7720_port->write_urb_pool[i];
|
|
dev_dbg(&port->dev, "URB:%d\n", i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (urb == NULL) {
|
|
dev_dbg(&port->dev, "%s - no more free urbs\n", __func__);
|
|
goto exit;
|
|
}
|
|
|
|
if (urb->transfer_buffer == NULL) {
|
|
urb->transfer_buffer = kmalloc(URB_TRANSFER_BUFFER_SIZE,
|
|
GFP_ATOMIC);
|
|
if (!urb->transfer_buffer) {
|
|
bytes_sent = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
}
|
|
transfer_size = min(count, URB_TRANSFER_BUFFER_SIZE);
|
|
|
|
memcpy(urb->transfer_buffer, current_position, transfer_size);
|
|
usb_serial_debug_data(&port->dev, __func__, 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) {
|
|
dev_err_console(port, "%s - usb_submit_urb(write bulk) failed "
|
|
"with status = %d\n", __func__, status);
|
|
bytes_sent = status;
|
|
goto exit;
|
|
}
|
|
bytes_sent = transfer_size;
|
|
|
|
exit:
|
|
return bytes_sent;
|
|
}
|
|
|
|
static void mos7720_throttle(struct tty_struct *tty)
|
|
{
|
|
struct usb_serial_port *port = tty->driver_data;
|
|
struct moschip_port *mos7720_port;
|
|
int status;
|
|
|
|
mos7720_port = usb_get_serial_port_data(port);
|
|
|
|
if (mos7720_port == NULL)
|
|
return;
|
|
|
|
if (!mos7720_port->open) {
|
|
dev_dbg(&port->dev, "%s - port not opened\n", __func__);
|
|
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(tty, port, &stop_char, 1);
|
|
if (status <= 0)
|
|
return;
|
|
}
|
|
|
|
/* if we are implementing RTS/CTS, toggle that line */
|
|
if (C_CRTSCTS(tty)) {
|
|
mos7720_port->shadowMCR &= ~UART_MCR_RTS;
|
|
write_mos_reg(port->serial, port->port_number, MOS7720_MCR,
|
|
mos7720_port->shadowMCR);
|
|
}
|
|
}
|
|
|
|
static void mos7720_unthrottle(struct tty_struct *tty)
|
|
{
|
|
struct usb_serial_port *port = tty->driver_data;
|
|
struct moschip_port *mos7720_port = usb_get_serial_port_data(port);
|
|
int status;
|
|
|
|
if (mos7720_port == NULL)
|
|
return;
|
|
|
|
if (!mos7720_port->open) {
|
|
dev_dbg(&port->dev, "%s - port not opened\n", __func__);
|
|
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(tty, port, &start_char, 1);
|
|
if (status <= 0)
|
|
return;
|
|
}
|
|
|
|
/* if we are implementing RTS/CTS, toggle that line */
|
|
if (C_CRTSCTS(tty)) {
|
|
mos7720_port->shadowMCR |= UART_MCR_RTS;
|
|
write_mos_reg(port->serial, port->port_number, MOS7720_MCR,
|
|
mos7720_port->shadowMCR);
|
|
}
|
|
}
|
|
|
|
/* FIXME: this function does not work */
|
|
static int set_higher_rates(struct moschip_port *mos7720_port,
|
|
unsigned int baud)
|
|
{
|
|
struct usb_serial_port *port;
|
|
struct usb_serial *serial;
|
|
int port_number;
|
|
enum mos_regs sp_reg;
|
|
if (mos7720_port == NULL)
|
|
return -EINVAL;
|
|
|
|
port = mos7720_port->port;
|
|
serial = port->serial;
|
|
|
|
/***********************************************
|
|
* Init Sequence for higher rates
|
|
***********************************************/
|
|
dev_dbg(&port->dev, "Sending Setting Commands ..........\n");
|
|
port_number = port->port_number;
|
|
|
|
write_mos_reg(serial, port_number, MOS7720_IER, 0x00);
|
|
write_mos_reg(serial, port_number, MOS7720_FCR, 0x00);
|
|
write_mos_reg(serial, port_number, MOS7720_FCR, 0xcf);
|
|
mos7720_port->shadowMCR = 0x0b;
|
|
write_mos_reg(serial, port_number, MOS7720_MCR,
|
|
mos7720_port->shadowMCR);
|
|
write_mos_reg(serial, dummy, MOS7720_SP_CONTROL_REG, 0x00);
|
|
|
|
/***********************************************
|
|
* Set for higher rates *
|
|
***********************************************/
|
|
/* writing baud rate verbatum into uart clock field clearly not right */
|
|
if (port_number == 0)
|
|
sp_reg = MOS7720_SP1_REG;
|
|
else
|
|
sp_reg = MOS7720_SP2_REG;
|
|
write_mos_reg(serial, dummy, sp_reg, baud * 0x10);
|
|
write_mos_reg(serial, dummy, MOS7720_SP_CONTROL_REG, 0x03);
|
|
mos7720_port->shadowMCR = 0x2b;
|
|
write_mos_reg(serial, port_number, MOS7720_MCR,
|
|
mos7720_port->shadowMCR);
|
|
|
|
/***********************************************
|
|
* Set DLL/DLM
|
|
***********************************************/
|
|
mos7720_port->shadowLCR = mos7720_port->shadowLCR | UART_LCR_DLAB;
|
|
write_mos_reg(serial, port_number, MOS7720_LCR,
|
|
mos7720_port->shadowLCR);
|
|
write_mos_reg(serial, port_number, MOS7720_DLL, 0x01);
|
|
write_mos_reg(serial, port_number, MOS7720_DLM, 0x00);
|
|
mos7720_port->shadowLCR = mos7720_port->shadowLCR & ~UART_LCR_DLAB;
|
|
write_mos_reg(serial, port_number, MOS7720_LCR,
|
|
mos7720_port->shadowLCR);
|
|
|
|
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 const 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(struct usb_serial_port *port, int baudrate, int *divisor)
|
|
{
|
|
int i;
|
|
__u16 custom;
|
|
__u16 round1;
|
|
__u16 round;
|
|
|
|
|
|
dev_dbg(&port->dev, "%s - %d\n", __func__, 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;
|
|
|
|
dev_dbg(&port->dev, "Baud %d = %d\n", baudrate, custom);
|
|
return 0;
|
|
}
|
|
|
|
dev_dbg(&port->dev, "Baud calculation Failed...\n");
|
|
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 number;
|
|
|
|
if (mos7720_port == NULL)
|
|
return -1;
|
|
|
|
port = mos7720_port->port;
|
|
serial = port->serial;
|
|
|
|
number = port->port_number;
|
|
dev_dbg(&port->dev, "%s - baud = %d\n", __func__, baudrate);
|
|
|
|
/* Calculate the Divisor */
|
|
status = calc_baud_rate_divisor(port, baudrate, &divisor);
|
|
if (status) {
|
|
dev_err(&port->dev, "%s - bad baud rate\n", __func__);
|
|
return status;
|
|
}
|
|
|
|
/* Enable access to divisor latch */
|
|
mos7720_port->shadowLCR = mos7720_port->shadowLCR | UART_LCR_DLAB;
|
|
write_mos_reg(serial, number, MOS7720_LCR, mos7720_port->shadowLCR);
|
|
|
|
/* Write the divisor */
|
|
write_mos_reg(serial, number, MOS7720_DLL, (__u8)(divisor & 0xff));
|
|
write_mos_reg(serial, number, MOS7720_DLM,
|
|
(__u8)((divisor & 0xff00) >> 8));
|
|
|
|
/* Disable access to divisor latch */
|
|
mos7720_port->shadowLCR = mos7720_port->shadowLCR & ~UART_LCR_DLAB;
|
|
write_mos_reg(serial, number, MOS7720_LCR, mos7720_port->shadowLCR);
|
|
|
|
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 tty_struct *tty,
|
|
struct moschip_port *mos7720_port,
|
|
struct ktermios *old_termios)
|
|
{
|
|
struct usb_serial_port *port;
|
|
struct usb_serial *serial;
|
|
int baud;
|
|
unsigned cflag;
|
|
__u8 lData;
|
|
__u8 lParity;
|
|
__u8 lStop;
|
|
int status;
|
|
int port_number;
|
|
|
|
if (mos7720_port == NULL)
|
|
return ;
|
|
|
|
port = mos7720_port->port;
|
|
serial = port->serial;
|
|
port_number = port->port_number;
|
|
|
|
if (!mos7720_port->open) {
|
|
dev_dbg(&port->dev, "%s - port not opened\n", __func__);
|
|
return;
|
|
}
|
|
|
|
lData = UART_LCR_WLEN8;
|
|
lStop = 0x00; /* 1 stop bit */
|
|
lParity = 0x00; /* No parity */
|
|
|
|
cflag = tty->termios.c_cflag;
|
|
|
|
/* Change the number of bits */
|
|
switch (cflag & CSIZE) {
|
|
case CS5:
|
|
lData = UART_LCR_WLEN5;
|
|
break;
|
|
|
|
case CS6:
|
|
lData = UART_LCR_WLEN6;
|
|
break;
|
|
|
|
case CS7:
|
|
lData = UART_LCR_WLEN7;
|
|
break;
|
|
default:
|
|
case CS8:
|
|
lData = UART_LCR_WLEN8;
|
|
break;
|
|
}
|
|
|
|
/* Change the Parity bit */
|
|
if (cflag & PARENB) {
|
|
if (cflag & PARODD) {
|
|
lParity = UART_LCR_PARITY;
|
|
dev_dbg(&port->dev, "%s - parity = odd\n", __func__);
|
|
} else {
|
|
lParity = (UART_LCR_EPAR | UART_LCR_PARITY);
|
|
dev_dbg(&port->dev, "%s - parity = even\n", __func__);
|
|
}
|
|
|
|
} else {
|
|
dev_dbg(&port->dev, "%s - parity = none\n", __func__);
|
|
}
|
|
|
|
if (cflag & CMSPAR)
|
|
lParity = lParity | 0x20;
|
|
|
|
/* Change the Stop bit */
|
|
if (cflag & CSTOPB) {
|
|
lStop = UART_LCR_STOP;
|
|
dev_dbg(&port->dev, "%s - stop bits = 2\n", __func__);
|
|
} else {
|
|
lStop = 0x00;
|
|
dev_dbg(&port->dev, "%s - stop bits = 1\n", __func__);
|
|
}
|
|
|
|
#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 */
|
|
write_mos_reg(serial, port_number, MOS7720_IER, 0x00);
|
|
write_mos_reg(serial, port_number, MOS7720_FCR, 0x00);
|
|
write_mos_reg(serial, port_number, MOS7720_FCR, 0xcf);
|
|
|
|
/* Send the updated LCR value to the mos7720 */
|
|
write_mos_reg(serial, port_number, MOS7720_LCR,
|
|
mos7720_port->shadowLCR);
|
|
mos7720_port->shadowMCR = 0x0b;
|
|
write_mos_reg(serial, port_number, MOS7720_MCR,
|
|
mos7720_port->shadowMCR);
|
|
|
|
/* 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)
|
|
write_mos_reg(serial, dummy, MOS7720_SP_CONTROL_REG,
|
|
0x01);
|
|
else
|
|
write_mos_reg(serial, dummy, MOS7720_SP_CONTROL_REG,
|
|
0x02);
|
|
|
|
} else
|
|
mos7720_port->shadowMCR &= ~(UART_MCR_XONANY);
|
|
|
|
write_mos_reg(serial, port_number, MOS7720_MCR,
|
|
mos7720_port->shadowMCR);
|
|
|
|
/* Determine divisor based on baud rate */
|
|
baud = tty_get_baud_rate(tty);
|
|
if (!baud) {
|
|
/* pick a default, any default... */
|
|
dev_dbg(&port->dev, "Picked default baud...\n");
|
|
baud = 9600;
|
|
}
|
|
|
|
if (baud >= 230400) {
|
|
set_higher_rates(mos7720_port, baud);
|
|
/* Enable Interrupts */
|
|
write_mos_reg(serial, port_number, MOS7720_IER, 0x0c);
|
|
return;
|
|
}
|
|
|
|
dev_dbg(&port->dev, "%s - baud rate = %d\n", __func__, baud);
|
|
status = send_cmd_write_baud_rate(mos7720_port, baud);
|
|
/* FIXME: needs to write actual resulting baud back not just
|
|
blindly do so */
|
|
if (cflag & CBAUD)
|
|
tty_encode_baud_rate(tty, baud, baud);
|
|
/* Enable Interrupts */
|
|
write_mos_reg(serial, port_number, MOS7720_IER, 0x0c);
|
|
|
|
if (port->read_urb->status != -EINPROGRESS) {
|
|
status = usb_submit_urb(port->read_urb, GFP_KERNEL);
|
|
if (status)
|
|
dev_dbg(&port->dev, "usb_submit_urb(read bulk) failed, status = %d\n", status);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 tty_struct *tty,
|
|
struct usb_serial_port *port, struct ktermios *old_termios)
|
|
{
|
|
int status;
|
|
struct moschip_port *mos7720_port;
|
|
|
|
mos7720_port = usb_get_serial_port_data(port);
|
|
|
|
if (mos7720_port == NULL)
|
|
return;
|
|
|
|
if (!mos7720_port->open) {
|
|
dev_dbg(&port->dev, "%s - port not opened\n", __func__);
|
|
return;
|
|
}
|
|
|
|
/* change the port settings to the new ones specified */
|
|
change_port_settings(tty, mos7720_port, old_termios);
|
|
|
|
if (port->read_urb->status != -EINPROGRESS) {
|
|
status = usb_submit_urb(port->read_urb, GFP_KERNEL);
|
|
if (status)
|
|
dev_dbg(&port->dev, "usb_submit_urb(read bulk) failed, status = %d\n", status);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 tty_struct *tty,
|
|
struct moschip_port *mos7720_port, unsigned int __user *value)
|
|
{
|
|
struct usb_serial_port *port = tty->driver_data;
|
|
unsigned int result = 0;
|
|
unsigned char data = 0;
|
|
int port_number = port->port_number;
|
|
int count;
|
|
|
|
count = mos7720_chars_in_buffer(tty);
|
|
if (count == 0) {
|
|
read_mos_reg(port->serial, port_number, MOS7720_LSR, &data);
|
|
if ((data & (UART_LSR_TEMT | UART_LSR_THRE))
|
|
== (UART_LSR_TEMT | UART_LSR_THRE)) {
|
|
dev_dbg(&port->dev, "%s -- Empty\n", __func__);
|
|
result = TIOCSER_TEMT;
|
|
}
|
|
}
|
|
if (copy_to_user(value, &result, sizeof(int)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
static int mos7720_tiocmget(struct tty_struct *tty)
|
|
{
|
|
struct usb_serial_port *port = tty->driver_data;
|
|
struct moschip_port *mos7720_port = usb_get_serial_port_data(port);
|
|
unsigned int result = 0;
|
|
unsigned int mcr ;
|
|
unsigned int msr ;
|
|
|
|
mcr = mos7720_port->shadowMCR;
|
|
msr = mos7720_port->shadowMSR;
|
|
|
|
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 */
|
|
|
|
return result;
|
|
}
|
|
|
|
static int mos7720_tiocmset(struct tty_struct *tty,
|
|
unsigned int set, unsigned int clear)
|
|
{
|
|
struct usb_serial_port *port = tty->driver_data;
|
|
struct moschip_port *mos7720_port = usb_get_serial_port_data(port);
|
|
unsigned int mcr ;
|
|
|
|
mcr = mos7720_port->shadowMCR;
|
|
|
|
if (set & TIOCM_RTS)
|
|
mcr |= UART_MCR_RTS;
|
|
if (set & TIOCM_DTR)
|
|
mcr |= UART_MCR_DTR;
|
|
if (set & TIOCM_LOOP)
|
|
mcr |= UART_MCR_LOOP;
|
|
|
|
if (clear & TIOCM_RTS)
|
|
mcr &= ~UART_MCR_RTS;
|
|
if (clear & TIOCM_DTR)
|
|
mcr &= ~UART_MCR_DTR;
|
|
if (clear & TIOCM_LOOP)
|
|
mcr &= ~UART_MCR_LOOP;
|
|
|
|
mos7720_port->shadowMCR = mcr;
|
|
write_mos_reg(port->serial, port->port_number, MOS7720_MCR,
|
|
mos7720_port->shadowMCR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mos7720_ioctl(struct tty_struct *tty,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct usb_serial_port *port = tty->driver_data;
|
|
struct moschip_port *mos7720_port;
|
|
|
|
mos7720_port = usb_get_serial_port_data(port);
|
|
if (mos7720_port == NULL)
|
|
return -ENODEV;
|
|
|
|
switch (cmd) {
|
|
case TIOCSERGETLSR:
|
|
dev_dbg(&port->dev, "%s TIOCSERGETLSR\n", __func__);
|
|
return get_lsr_info(tty, mos7720_port,
|
|
(unsigned int __user *)arg);
|
|
}
|
|
|
|
return -ENOIOCTLCMD;
|
|
}
|
|
|
|
static int mos7720_startup(struct usb_serial *serial)
|
|
{
|
|
struct usb_device *dev;
|
|
char data;
|
|
u16 product;
|
|
int ret_val;
|
|
|
|
product = le16_to_cpu(serial->dev->descriptor.idProduct);
|
|
dev = serial->dev;
|
|
|
|
if (product == MOSCHIP_DEVICE_ID_7715) {
|
|
struct urb *urb = serial->port[0]->interrupt_in_urb;
|
|
|
|
urb->complete = mos7715_interrupt_callback;
|
|
|
|
#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT
|
|
ret_val = mos7715_parport_init(serial);
|
|
if (ret_val < 0)
|
|
return ret_val;
|
|
#endif
|
|
}
|
|
/* start the interrupt urb */
|
|
ret_val = usb_submit_urb(serial->port[0]->interrupt_in_urb, GFP_KERNEL);
|
|
if (ret_val) {
|
|
dev_err(&dev->dev, "failed to submit interrupt urb: %d\n",
|
|
ret_val);
|
|
}
|
|
|
|
/* LSR For Port 1 */
|
|
read_mos_reg(serial, 0, MOS7720_LSR, &data);
|
|
dev_dbg(&dev->dev, "LSR:%x\n", data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mos7720_release(struct usb_serial *serial)
|
|
{
|
|
usb_kill_urb(serial->port[0]->interrupt_in_urb);
|
|
|
|
#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT
|
|
/* close the parallel port */
|
|
|
|
if (le16_to_cpu(serial->dev->descriptor.idProduct)
|
|
== MOSCHIP_DEVICE_ID_7715) {
|
|
struct mos7715_parport *mos_parport =
|
|
usb_get_serial_data(serial);
|
|
|
|
/* prevent NULL ptr dereference in port callbacks */
|
|
spin_lock(&release_lock);
|
|
mos_parport->pp->private_data = NULL;
|
|
spin_unlock(&release_lock);
|
|
|
|
/* wait for synchronous usb calls to return */
|
|
if (mos_parport->msg_pending)
|
|
wait_for_completion_timeout(&mos_parport->syncmsg_compl,
|
|
msecs_to_jiffies(MOS_WDR_TIMEOUT));
|
|
/*
|
|
* If delayed work is currently scheduled, wait for it to
|
|
* complete. This also implies barriers that ensure the
|
|
* below serial clearing is not hoisted above the ->work.
|
|
*/
|
|
cancel_work_sync(&mos_parport->work);
|
|
|
|
parport_remove_port(mos_parport->pp);
|
|
usb_set_serial_data(serial, NULL);
|
|
mos_parport->serial = NULL;
|
|
|
|
parport_del_port(mos_parport->pp);
|
|
|
|
kref_put(&mos_parport->ref_count, destroy_mos_parport);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int mos7720_port_probe(struct usb_serial_port *port)
|
|
{
|
|
struct moschip_port *mos7720_port;
|
|
|
|
mos7720_port = kzalloc(sizeof(*mos7720_port), GFP_KERNEL);
|
|
if (!mos7720_port)
|
|
return -ENOMEM;
|
|
|
|
mos7720_port->port = port;
|
|
|
|
usb_set_serial_port_data(port, mos7720_port);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mos7720_port_remove(struct usb_serial_port *port)
|
|
{
|
|
struct moschip_port *mos7720_port;
|
|
|
|
mos7720_port = usb_get_serial_port_data(port);
|
|
kfree(mos7720_port);
|
|
}
|
|
|
|
static struct usb_serial_driver moschip7720_2port_driver = {
|
|
.driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = "moschip7720",
|
|
},
|
|
.description = "Moschip 2 port adapter",
|
|
.id_table = id_table,
|
|
.num_bulk_in = 2,
|
|
.num_bulk_out = 2,
|
|
.num_interrupt_in = 1,
|
|
.calc_num_ports = mos77xx_calc_num_ports,
|
|
.open = mos7720_open,
|
|
.close = mos7720_close,
|
|
.throttle = mos7720_throttle,
|
|
.unthrottle = mos7720_unthrottle,
|
|
.attach = mos7720_startup,
|
|
.release = mos7720_release,
|
|
.port_probe = mos7720_port_probe,
|
|
.port_remove = mos7720_port_remove,
|
|
.ioctl = mos7720_ioctl,
|
|
.tiocmget = mos7720_tiocmget,
|
|
.tiocmset = mos7720_tiocmset,
|
|
.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 struct usb_serial_driver * const serial_drivers[] = {
|
|
&moschip7720_2port_driver, NULL
|
|
};
|
|
|
|
module_usb_serial_driver(serial_drivers, id_table);
|
|
|
|
MODULE_AUTHOR(DRIVER_AUTHOR);
|
|
MODULE_DESCRIPTION(DRIVER_DESC);
|
|
MODULE_LICENSE("GPL v2");
|