root/drivers/usb/serial/mos7720.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_obj(struct mos7715_parport);
        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 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 int 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 -ENODEV;

        if (break_state == -1)
                data = mos7720_port->shadowLCR | UART_LCR_SBC;
        else
                data = mos7720_port->shadowLCR & ~UART_LCR_SBC;

        mos7720_port->shadowLCR  = data;

        return 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,
                                 const 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;
        }

        lStop = 0x00;   /* 1 stop bit */
        lParity = 0x00; /* No parity */

        cflag = tty->termios.c_cflag;

        lData = UART_LCR_WLEN(tty_get_char_size(cflag));

        /* 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,
                                const 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_obj(*mos7720_port);
        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 = {
                .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");