USB: usbserial: mos7720: add support for parallel port on moschip 7715
Mike Dunn [Thu, 15 Apr 2010 21:01:33 +0000 (17:01 -0400)]
Add support for the parallel port on the moschip MCS7715 device.  The port
registers itself with the parport subsystem as a low-level driver.  A separate
entry to the kernel configuration is added beneath that for the mos7720, to
avoid the need to link with the parport subsystem code for users who don't have
or don't want the parallel port.  Only compatibility mode is currently supported
(no ECP/EPP).  Tested with both moschip devices (7720 and 7715) on UP and SMP
hosts, including regression testing of serial port, concurrent operation of
serial and parallel ports, and various connect / disconnect scenarios.

Signed-off-by: Mike Dunn <mikedunn@newsguy.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

Documentation/usb/usb-serial.txt
drivers/usb/serial/Kconfig
drivers/usb/serial/mos7720.c

index 540c91c..f4d2145 100644 (file)
@@ -440,6 +440,22 @@ Winchiphead CH341 Driver
   For any questions or problems with this driver, please contact
   frank@kingswood-consulting.co.uk.
 
+Moschip MCS7720, MCS7715 driver
+
+  These chips are present in devices sold by various manufacturers, such as Syba
+  and Cables Unlimited.  There may be others.  The 7720 provides two serial
+  ports, and the 7715 provides one serial and one standard PC parallel port.
+  Support for the 7715's parallel port is enabled by a separate option, which
+  will not appear unless parallel port support is first enabled at the top-level
+  of the Device Drivers config menu.  Currently only compatibility mode is
+  supported on the parallel port (no ECP/EPP).
+
+  TODO:
+    - Implement ECP/EPP modes for the parallel port.
+    - Baud rates higher than 115200 are currently broken.
+    - Devices with a single serial port based on the Moschip MCS7703 may work
+      with this driver with a simple addition to the usb_device_id table.  I
+      don't have one of these devices, so I can't say for sure.
 
 Generic Serial driver
 
index efb6dc7..a0b2247 100644 (file)
@@ -425,6 +425,17 @@ config USB_SERIAL_MOS7720
          To compile this driver as a module, choose M here: the
          module will be called mos7720.
 
+if USB_SERIAL_MOS7720
+config USB_SERIAL_MOS7715_PARPORT
+       bool "Support for parallel port on the Moschip 7715"
+       select PARPORT_NOT_PC
+       depends on PARPORT
+       ---help---
+       Say Y if you have a Moschip 7715 device and would like to use
+       the parallel port it provides.  The port will register with
+       the parport subsystem as a low-level driver.
+endif
+
 config USB_SERIAL_MOS7840
        tristate "USB Moschip 7840/7820 USB Serial Driver"
        ---help---
index 0d47f2c..2d35d11 100644 (file)
 #include <linux/usb.h>
 #include <linux/usb/serial.h>
 #include <linux/uaccess.h>
-
+#include <linux/parport.h>
 
 /*
  * Version Information
  */
-#define DRIVER_VERSION "1.0.0.4F"
+#define DRIVER_VERSION "2.0"
 #define DRIVER_AUTHOR "Aspire Communications pvt Ltd."
 #define DRIVER_DESC "Moschip USB Serial Driver"
 
@@ -63,7 +63,7 @@
 #define NUM_URBS                       16      /* URB Count */
 #define URB_TRANSFER_BUFFER_SIZE       32      /* URB Size */
 
-/* This structure holds all of the local port information */
+/* 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 */
@@ -74,11 +74,6 @@ struct moschip_port {
        struct urb              *write_urb_pool[NUM_URBS];
 };
 
-/* This structure holds all of the individual serial device information */
-struct moschip_serial {
-       int interrupt_started;
-};
-
 static int debug;
 
 static struct usb_serial_driver moschip7720_2port_driver;
@@ -94,6 +89,649 @@ static const struct usb_device_id moschip_port_id_table[] = {
 };
 MODULE_DEVICE_TABLE(usb, moschip_port_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 */
+
+struct urbtracker {
+       struct mos7715_parport  *mos_parport;
+       struct list_head        urblist_entry;
+       struct kref             ref_count;
+       struct urb              *urb;
+};
+
+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 */
+       struct list_head        deferred_urbs; /* list deferred async urbs */
+       struct list_head        active_urbs;   /* list async urbs in flight */
+       spinlock_t              listlock;      /* protects list access */
+       bool                    msg_pending;   /* usb sync call pending */
+       struct completion       syncmsg_compl; /* usb sync call completed */
+       struct tasklet_struct   urb_tasklet;   /* for sending deferred urbs */
+       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);
+
+enum mos_regs {
+       THR,              /* serial port regs */
+       RHR,
+       IER,
+       FCR,
+       ISR,
+       LCR,
+       MCR,
+       LSR,
+       MSR,
+       SPR,
+       DLL,
+       DLM,
+       DPR,              /* parallel port regs */
+       DSR,
+       DCR,
+       ECR,
+       SP1_REG,          /* device control regs */
+       SP2_REG,          /* serial port 2 (7720 only) */
+       PP_REG,
+       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,           /* THR */
+               0x00,           /* RHR */
+               0x01,           /* IER */
+               0x02,           /* FCR */
+               0x02,           /* ISR */
+               0x03,           /* LCR */
+               0x04,           /* MCR */
+               0x05,           /* LSR */
+               0x06,           /* MSR */
+               0x07,           /* SPR */
+               0x00,           /* DLL */
+               0x01,           /* DLM */
+               0x00,           /* DPR */
+               0x01,           /* DSR */
+               0x02,           /* DCR */
+               0x0a,           /* ECR */
+               0x01,           /* SP1_REG */
+               0x02,           /* SP2_REG (7720 only) */
+               0x04,           /* PP_REG (7715 only) */
+               0x08,           /* 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)
+{
+       if (reg >= SP1_REG)           /* control reg */
+               return 0x0000;
+       else                          /* parallel port reg (7715 only) */
+               return 0x0100;
+}
+
+/*
+ * Write data byte to the specified device register.  The data is embedded in
+ * the value field of the setup packet.
+ */
+static int write_parport_reg(struct mos7715_parport *mos_parport,
+                            enum mos_regs reg, __u8 data)
+{
+       struct usb_serial *serial = mos_parport->serial;
+       struct usb_device *usbdev = serial->dev;
+       unsigned int pipe = usb_sndctrlpipe(usbdev, 0);
+       __u8 request = (__u8)0x0e;
+       __u8 requesttype = (__u8)0x40;
+       __u16 value = get_reg_value(reg) + data;
+       __u16 index = get_reg_index(reg);
+       __u16 size = 0;
+       int status;
+       status = usb_control_msg(usbdev, pipe, request, requesttype, value,
+                                index, NULL, size, MOS_WDR_TIMEOUT);
+       if (status < 0)
+               dev_err(&usbdev->dev,
+                       "mos7720: usb_control_msg() failed: %d", 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.
+ */
+static int read_parport_reg(struct mos7715_parport *mos_parport,
+                           enum mos_regs reg, __u8 *data)
+{
+       struct usb_device *usbdev = mos_parport->serial->dev;
+       unsigned int pipe = usb_rcvctrlpipe(usbdev, 0);
+       __u8 request = (__u8)0x0d;
+       __u8 requesttype = (__u8)0xc0;
+       __u16 value = get_reg_value(reg);
+       __u16 index = get_reg_index(reg);
+       __u16 size = 1;
+       int status = usb_control_msg(usbdev, pipe, request, requesttype, value,
+                                    index, data, size, MOS_WDR_TIMEOUT);
+       if (status < 0)
+               dev_err(&usbdev->dev,
+                       "mos7720: usb_control_msg() failed: %d", status);
+       return status;
+}
+
+static inline int mos7715_change_mode(struct mos7715_parport *mos_parport,
+                                     enum mos7715_pp_modes mode)
+{
+       mos_parport->shadowECR = mode;
+       write_parport_reg(mos_parport, 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);
+
+       dbg("%s called", __func__);
+       kfree(mos_parport);
+}
+
+static void destroy_urbtracker(struct kref *kref)
+{
+       struct urbtracker *urbtrack =
+               container_of(kref, struct urbtracker, ref_count);
+       struct mos7715_parport *mos_parport = urbtrack->mos_parport;
+       dbg("%s called", __func__);
+       usb_free_urb(urbtrack->urb);
+       kfree(urbtrack);
+       kref_put(&mos_parport->ref_count, destroy_mos_parport);
+}
+
+/*
+ * This runs as a tasklet when sending an urb in a non-blocking parallel
+ * port callback had to be deferred because the disconnect mutex could not be
+ * obtained at the time.
+ */
+static void send_deferred_urbs(unsigned long _mos_parport)
+{
+       int ret_val;
+       unsigned long flags;
+       struct mos7715_parport *mos_parport = (void *)_mos_parport;
+       struct urbtracker *urbtrack;
+       struct list_head *cursor, *next;
+
+       dbg("%s called", __func__);
+
+       /* if release function ran, game over */
+       if (unlikely(mos_parport->serial == NULL))
+               return;
+
+       /* try again to get the mutex */
+       if (!mutex_trylock(&mos_parport->serial->disc_mutex)) {
+               dbg("%s: rescheduling tasklet", __func__);
+               tasklet_schedule(&mos_parport->urb_tasklet);
+               return;
+       }
+
+       /* if device disconnected, game over */
+       if (unlikely(mos_parport->serial->disconnected)) {
+               mutex_unlock(&mos_parport->serial->disc_mutex);
+               return;
+       }
+
+       spin_lock_irqsave(&mos_parport->listlock, flags);
+       if (list_empty(&mos_parport->deferred_urbs)) {
+               spin_unlock_irqrestore(&mos_parport->listlock, flags);
+               mutex_unlock(&mos_parport->serial->disc_mutex);
+               dbg("%s: deferred_urbs list empty", __func__);
+               return;
+       }
+
+       /* move contents of deferred_urbs list to active_urbs list and submit */
+       list_for_each_safe(cursor, next, &mos_parport->deferred_urbs)
+               list_move_tail(cursor, &mos_parport->active_urbs);
+       list_for_each_entry(urbtrack, &mos_parport->active_urbs,
+                           urblist_entry) {
+               ret_val = usb_submit_urb(urbtrack->urb, GFP_ATOMIC);
+               dbg("%s: urb submitted", __func__);
+               if (ret_val) {
+                       dev_err(&mos_parport->serial->dev->dev,
+                               "usb_submit_urb() failed: %d", ret_val);
+                       list_del(&urbtrack->urblist_entry);
+                       kref_put(&urbtrack->ref_count, destroy_urbtracker);
+               }
+       }
+       spin_unlock_irqrestore(&mos_parport->listlock, flags);
+       mutex_unlock(&mos_parport->serial->disc_mutex);
+}
+
+/* callback for parallel port control urbs submitted asynchronously */
+static void async_complete(struct urb *urb)
+{
+       struct urbtracker *urbtrack = urb->context;
+       int status = urb->status;
+       dbg("%s called", __func__);
+       if (unlikely(status))
+               dbg("%s - nonzero urb status received: %d", __func__, status);
+
+       /* remove the urbtracker from the active_urbs list */
+       spin_lock(&urbtrack->mos_parport->listlock);
+       list_del(&urbtrack->urblist_entry);
+       spin_unlock(&urbtrack->mos_parport->listlock);
+       kref_put(&urbtrack->ref_count, destroy_urbtracker);
+}
+
+static int write_parport_reg_nonblock(struct mos7715_parport *mos_parport,
+                                     enum mos_regs reg, __u8 data)
+{
+       struct urbtracker *urbtrack;
+       int ret_val;
+       unsigned long flags;
+       struct usb_ctrlrequest setup;
+       struct usb_serial *serial = mos_parport->serial;
+       struct usb_device *usbdev = serial->dev;
+       dbg("%s called", __func__);
+
+       /* create and initialize the control urb and containing urbtracker */
+       urbtrack = kmalloc(sizeof(struct urbtracker), GFP_ATOMIC);
+       if (urbtrack == NULL) {
+               dev_err(&usbdev->dev, "out of memory");
+               return -ENOMEM;
+       }
+       kref_get(&mos_parport->ref_count);
+       urbtrack->mos_parport = mos_parport;
+       urbtrack->urb = usb_alloc_urb(0, GFP_ATOMIC);
+       if (urbtrack->urb == NULL) {
+               dev_err(&usbdev->dev, "out of urbs");
+               kfree(urbtrack);
+               return -ENOMEM;
+       }
+       setup.bRequestType = (__u8)0x40;
+       setup.bRequest = (__u8)0x0e;
+       setup.wValue = get_reg_value(reg);
+       setup.wIndex = get_reg_index(reg);
+       setup.wLength = 0;
+       usb_fill_control_urb(urbtrack->urb, usbdev,
+                            usb_sndctrlpipe(usbdev, 0),
+                            (unsigned char *)&setup,
+                            NULL, 0, async_complete, urbtrack);
+       kref_init(&urbtrack->ref_count);
+       INIT_LIST_HEAD(&urbtrack->urblist_entry);
+
+       /*
+        * get the disconnect mutex, or add tracker to the deferred_urbs list
+        * and schedule a tasklet to try again later
+        */
+       if (!mutex_trylock(&serial->disc_mutex)) {
+               spin_lock_irqsave(&mos_parport->listlock, flags);
+               list_add_tail(&urbtrack->urblist_entry,
+                             &mos_parport->deferred_urbs);
+               spin_unlock_irqrestore(&mos_parport->listlock, flags);
+               tasklet_schedule(&mos_parport->urb_tasklet);
+               dbg("tasklet scheduled");
+               return 0;
+       }
+
+       /* bail if device disconnected */
+       if (serial->disconnected) {
+               kref_put(&urbtrack->ref_count, destroy_urbtracker);
+               mutex_unlock(&serial->disc_mutex);
+               return -ENODEV;
+       }
+
+       /* add the tracker to the active_urbs list and submit */
+       spin_lock_irqsave(&mos_parport->listlock, flags);
+       list_add_tail(&urbtrack->urblist_entry, &mos_parport->active_urbs);
+       spin_unlock_irqrestore(&mos_parport->listlock, flags);
+       ret_val = usb_submit_urb(urbtrack->urb, GFP_ATOMIC);
+       mutex_unlock(&serial->disc_mutex);
+       if (ret_val) {
+               dev_err(&usbdev->dev,
+                       "%s: submit_urb() failed: %d", __func__, ret_val);
+               spin_lock_irqsave(&mos_parport->listlock, flags);
+               list_del(&urbtrack->urblist_entry);
+               spin_unlock_irqrestore(&mos_parport->listlock, flags);
+               kref_put(&urbtrack->ref_count, destroy_urbtracker);
+               return ret_val;
+       }
+       return 0;
+}
+
+/*
+ * This is the 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 messgage 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 */
+       INIT_COMPLETION(mos_parport->syncmsg_compl);
+       spin_unlock(&release_lock);
+
+       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 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 parport_mos7715_write_data(struct parport *pp, unsigned char d)
+{
+       struct mos7715_parport *mos_parport = pp->private_data;
+       dbg("%s called: %2.2x", __func__, d);
+       if (parport_prologue(pp) < 0)
+               return;
+       mos7715_change_mode(mos_parport, SPP);
+       write_parport_reg(mos_parport, 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;
+       dbg("%s called", __func__);
+       if (parport_prologue(pp) < 0)
+               return 0;
+       read_parport_reg(mos_parport, 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;
+       dbg("%s called: %2.2x", __func__, d);
+       if (parport_prologue(pp) < 0)
+               return;
+       data = ((__u8)d & 0x0f) | (mos_parport->shadowDCR & 0xf0);
+       write_parport_reg(mos_parport, DCR, data);
+       mos_parport->shadowDCR = data;
+       parport_epilogue(pp);
+}
+
+static unsigned char parport_mos7715_read_control(struct parport *pp)
+{
+       struct mos7715_parport *mos_parport = pp->private_data;
+       __u8 dcr;
+       dbg("%s called", __func__);
+       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;
+       dbg("%s called", __func__);
+       mask &= 0x0f;
+       val &= 0x0f;
+       if (parport_prologue(pp) < 0)
+               return 0;
+       mos_parport->shadowDCR = (mos_parport->shadowDCR & (~mask)) ^ val;
+       write_parport_reg(mos_parport, 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 = pp->private_data;
+       dbg("%s called", __func__);
+       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)
+{
+       dbg("%s called", __func__);
+}
+static void parport_mos7715_disable_irq(struct parport *pp)
+{
+       dbg("%s called", __func__);
+}
+
+static void parport_mos7715_data_forward(struct parport *pp)
+{
+       struct mos7715_parport *mos_parport = pp->private_data;
+       dbg("%s called", __func__);
+       if (parport_prologue(pp) < 0)
+               return;
+       mos7715_change_mode(mos_parport, PS2);
+       mos_parport->shadowDCR &=  ~0x20;
+       write_parport_reg(mos_parport, DCR, mos_parport->shadowDCR);
+       parport_epilogue(pp);
+}
+
+static void parport_mos7715_data_reverse(struct parport *pp)
+{
+       struct mos7715_parport *mos_parport = pp->private_data;
+       dbg("%s called", __func__);
+       if (parport_prologue(pp) < 0)
+               return;
+       mos7715_change_mode(mos_parport, PS2);
+       mos_parport->shadowDCR |= 0x20;
+       write_parport_reg(mos_parport, DCR, mos_parport->shadowDCR);
+       parport_epilogue(pp);
+}
+
+static void parport_mos7715_init_state(struct pardevice *dev,
+                                      struct parport_state *s)
+{
+       dbg("%s called", __func__);
+       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;
+       dbg("%s called", __func__);
+       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;
+       dbg("%s called", __func__);
+       spin_lock(&release_lock);
+       mos_parport = pp->private_data;
+       if (unlikely(mos_parport == NULL)) {    /* release called */
+               spin_unlock(&release_lock);
+               return;
+       }
+       write_parport_reg_nonblock(mos_parport, DCR, mos_parport->shadowDCR);
+       write_parport_reg_nonblock(mos_parport, ECR, mos_parport->shadowECR);
+       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;
+       dbg("%s called: %u chars", __func__, (unsigned int)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", 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 == NULL) {
+               dbg("mos7715_parport_init: kzalloc failed");
+               return -ENOMEM;
+       }
+       mos_parport->msg_pending = false;
+       kref_init(&mos_parport->ref_count);
+       spin_lock_init(&mos_parport->listlock);
+       INIT_LIST_HEAD(&mos_parport->active_urbs);
+       INIT_LIST_HEAD(&mos_parport->deferred_urbs);
+       usb_set_serial_data(serial, mos_parport); /* hijack private pointer */
+       mos_parport->serial = serial;
+       tasklet_init(&mos_parport->urb_tasklet, send_deferred_urbs,
+                    (unsigned long) mos_parport);
+       init_completion(&mos_parport->syncmsg_compl);
+
+       /* cycle parallel port reset bit */
+       write_parport_reg(mos_parport, PP_REG, (__u8)0x80);
+       write_parport_reg(mos_parport, PP_REG, (__u8)0x00);
+
+       /* initialize device registers */
+       mos_parport->shadowDCR = DCR_INIT_VAL;
+       write_parport_reg(mos_parport, DCR, mos_parport->shadowDCR);
+       mos_parport->shadowECR = ECR_INIT_VAL;
+       write_parport_reg(mos_parport, 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
@@ -109,8 +747,6 @@ static void mos7720_interrupt_callback(struct urb *urb)
        __u8 sp1;
        __u8 sp2;
 
-       dbg(" : Entering");
-
        switch (status) {
        case 0:
                /* success */
@@ -118,6 +754,7 @@ static void mos7720_interrupt_callback(struct urb *urb)
        case -ECONNRESET:
        case -ENOENT:
        case -ESHUTDOWN:
+       case -ENODEV:
                /* this urb is terminated, clean up */
                dbg("%s - urb shutting down with status: %d", __func__,
                    status);
@@ -161,7 +798,7 @@ static void mos7720_interrupt_callback(struct urb *urb)
                        dbg("Serial Port 1: Receiver time out");
                        break;
                case SERIAL_IIR_MS:
-                       dbg("Serial Port 1: Modem status change");
+                       /* dbg("Serial Port 1: Modem status change"); */
                        break;
                }
 
@@ -174,7 +811,7 @@ static void mos7720_interrupt_callback(struct urb *urb)
                        dbg("Serial Port 2: Receiver time out");
                        break;
                case SERIAL_IIR_MS:
-                       dbg("Serial Port 2: Modem status change");
+                       /* dbg("Serial Port 2: Modem status change"); */
                        break;
                }
        }
@@ -208,6 +845,7 @@ static void mos7715_interrupt_callback(struct urb *urb)
        case -ECONNRESET:
        case -ENOENT:
        case -ESHUTDOWN:
+       case -ENODEV:
                /* this urb is terminated, clean up */
                dbg("%s - urb shutting down with status: %d", __func__,
                    status);
@@ -243,11 +881,21 @@ static void mos7715_interrupt_callback(struct urb *urb)
                        dbg("Serial Port: Receiver time out");
                        break;
                case SERIAL_IIR_MS:
-                       dbg("Serial Port: Modem status change");
+                       /* dbg("Serial Port: Modem status change"); */
                        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)
@@ -267,7 +915,6 @@ static void mos7720_bulk_in_callback(struct urb *urb)
        int retval;
        unsigned char *data ;
        struct usb_serial_port *port;
-       struct moschip_port *mos7720_port;
        struct tty_struct *tty;
        int status = urb->status;
 
@@ -276,13 +923,7 @@ static void mos7720_bulk_in_callback(struct urb *urb)
                return;
        }
 
-       mos7720_port = urb->context;
-       if (!mos7720_port) {
-               dbg("NULL mos7720_port pointer");
-               return ;
-       }
-
-       port = mos7720_port->port;
+       port = urb->context;
 
        dbg("Entering...%s", __func__);
 
@@ -332,8 +973,6 @@ static void mos7720_bulk_out_data_callback(struct urb *urb)
                return ;
        }
 
-       dbg("Entering .........");
-
        tty = tty_port_tty_get(&mos7720_port->port->port);
 
        if (tty && mos7720_port->open)
@@ -424,7 +1063,6 @@ static int mos7720_open(struct tty_struct *tty, struct usb_serial_port *port)
        struct usb_serial *serial;
        struct usb_serial_port *port0;
        struct urb *urb;
-       struct moschip_serial *mos7720_serial;
        struct moschip_port *mos7720_port;
        int response;
        int port_number;
@@ -440,11 +1078,6 @@ static int mos7720_open(struct tty_struct *tty, struct usb_serial_port *port)
 
        port0 = serial->port[0];
 
-       mos7720_serial = usb_get_serial_data(serial);
-
-       if (mos7720_serial == NULL || port0 == NULL)
-               return -ENODEV;
-
        usb_clear_halt(serial->dev, port->write_urb->pipe);
        usb_clear_halt(serial->dev, port->read_urb->pipe);
 
@@ -549,43 +1182,6 @@ static int mos7720_open(struct tty_struct *tty, struct usb_serial_port *port)
        data = 0x0c;
        send_mos_cmd(serial, MOS_WRITE, port_number, 0x01, &data);
 
-       /* see if we've set up our endpoint info yet   *
-        * (can't set it up in mos7720_startup as the  *
-        * structures were not set up at that time.)   */
-       if (!mos7720_serial->interrupt_started) {
-               dbg("Interrupt buffer NULL !!!");
-
-               /* not set up yet, so do it now */
-               mos7720_serial->interrupt_started = 1;
-
-               dbg("To Submit URB !!!");
-
-               /* set up our interrupt urb */
-               usb_fill_int_urb(port0->interrupt_in_urb, serial->dev,
-                        usb_rcvintpipe(serial->dev,
-                               port->interrupt_in_endpointAddress),
-                        port0->interrupt_in_buffer,
-                        port0->interrupt_in_urb->transfer_buffer_length,
-                        mos7720_interrupt_callback, mos7720_port,
-                        port0->interrupt_in_urb->interval);
-
-               /* start interrupt read for this mos7720 this interrupt *
-                * will continue as long as the mos7720 is connected    */
-               dbg("Submit URB over !!!");
-               response = usb_submit_urb(port0->interrupt_in_urb, GFP_KERNEL);
-               if (response)
-                       dev_err(&port->dev,
-                               "%s - Error %d submitting control urb\n",
-                               __func__, response);
-       }
-
-       /* set up our bulk in urb */
-       usb_fill_bulk_urb(port->read_urb, serial->dev,
-                         usb_rcvbulkpipe(serial->dev,
-                               port->bulk_in_endpointAddress),
-                         port->bulk_in_buffer,
-                         port->read_urb->transfer_buffer_length,
-                         mos7720_bulk_in_callback, mos7720_port);
        response = usb_submit_urb(port->read_urb, GFP_KERNEL);
        if (response)
                dev_err(&port->dev, "%s - Error %d submitting read urb\n",
@@ -897,6 +1493,7 @@ static void mos7720_unthrottle(struct tty_struct *tty)
        }
 }
 
+/* FIXME: this function does not work */
 static int set_higher_rates(struct moschip_port *mos7720_port,
                            unsigned int baud)
 {
@@ -939,6 +1536,7 @@ static int set_higher_rates(struct moschip_port *mos7720_port,
         *              Set for higher rates           *
         ***********************************************/
 
+       /* writing baud rate verbatum into uart clock field clearly not right */
        data = baud * 0x10;
        send_mos_cmd(serial, MOS_WRITE, MOS_MAX_PORT, port_number + 1, &data);
 
@@ -1308,7 +1906,7 @@ static void mos7720_set_termios(struct tty_struct *tty,
                return;
        }
 
-       dbg("setting termios - ASPIRE");
+       dbg("%s\n", "setting termios - ASPIRE");
 
        cflag = tty->termios->c_cflag;
 
@@ -1326,7 +1924,7 @@ static void mos7720_set_termios(struct tty_struct *tty,
        change_port_settings(tty, mos7720_port, old_termios);
 
        if (!port->read_urb) {
-               dbg("URB KILLED !!!!!");
+               dbg("%s", "URB KILLED !!!!!");
                return;
        }
 
@@ -1590,12 +2188,12 @@ static int mos7720_ioctl(struct tty_struct *tty, struct file *file,
 
 static int mos7720_startup(struct usb_serial *serial)
 {
-       struct moschip_serial *mos7720_serial;
        struct moschip_port *mos7720_port;
        struct usb_device *dev;
        int i;
        char data;
        u16 product = le16_to_cpu(serial->dev->descriptor.idProduct);
+       int ret_val;
 
        dbg("%s: Entering ..........", __func__);
 
@@ -1606,15 +2204,6 @@ static int mos7720_startup(struct usb_serial *serial)
 
        dev = serial->dev;
 
-       /* create our private serial structure */
-       mos7720_serial = kzalloc(sizeof(struct moschip_serial), GFP_KERNEL);
-       if (mos7720_serial == NULL) {
-               dev_err(&dev->dev, "%s - Out of memory\n", __func__);
-               return -ENOMEM;
-       }
-
-       usb_set_serial_data(serial, mos7720_serial);
-
        /*
         * The 7715 uses the first bulk in/out endpoint pair for the parallel
         * port, and the second for the serial port.  Because the usbserial core
@@ -1638,16 +2227,12 @@ static int mos7720_startup(struct usb_serial *serial)
                serial->port[1]->interrupt_in_buffer = NULL;
        }
 
-       /* we set up the pointers to the endpoints in the mos7720_open *
-        * function, as the structures aren't created yet.             */
 
-       /* set up port private structures */
+       /* set up serial port private structures */
        for (i = 0; i < serial->num_ports; ++i) {
                mos7720_port = kzalloc(sizeof(struct moschip_port), GFP_KERNEL);
                if (mos7720_port == NULL) {
                        dev_err(&dev->dev, "%s - Out of memory\n", __func__);
-                       usb_set_serial_data(serial, NULL);
-                       kfree(mos7720_serial);
                        return -ENOMEM;
                }
 
@@ -1669,6 +2254,20 @@ static int mos7720_startup(struct usb_serial *serial)
        usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
                        (__u8)0x03, 0x00, 0x01, 0x00, NULL, 0x00, 5*HZ);
 
+       /* start the interrupt urb */
+       ret_val = usb_submit_urb(serial->port[0]->interrupt_in_urb, GFP_KERNEL);
+       if (ret_val)
+               dev_err(&dev->dev,
+                       "%s - Error %d submitting control urb\n",
+                       __func__, ret_val);
+
+#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT
+       if (product == MOSCHIP_DEVICE_ID_7715) {
+               ret_val = mos7715_parport_init(serial);
+               if (ret_val < 0)
+                       return ret_val;
+       }
+#endif
        /* LSR For Port 1 */
        send_mos_cmd(serial, MOS_READ, 0x00, UART_LSR, &data);
        dbg("LSR:%x", data);
@@ -1684,12 +2283,47 @@ static void mos7720_release(struct usb_serial *serial)
 {
        int i;
 
+#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT
+       /* close the parallel port */
+
+       if (le16_to_cpu(serial->dev->descriptor.idProduct)
+           == MOSCHIP_DEVICE_ID_7715) {
+               struct urbtracker *urbtrack;
+               unsigned long flags;
+               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,
+                                                   MOS_WDR_TIMEOUT);
+
+               parport_remove_port(mos_parport->pp);
+               usb_set_serial_data(serial, NULL);
+               mos_parport->serial = NULL;
+
+               /* if tasklet currently scheduled, wait for it to complete */
+               tasklet_kill(&mos_parport->urb_tasklet);
+
+               /* unlink any urbs sent by the tasklet  */
+               spin_lock_irqsave(&mos_parport->listlock, flags);
+               list_for_each_entry(urbtrack,
+                                   &mos_parport->active_urbs,
+                                   urblist_entry)
+                       usb_unlink_urb(urbtrack->urb);
+               spin_unlock_irqrestore(&mos_parport->listlock, flags);
+
+               kref_put(&mos_parport->ref_count, destroy_mos_parport);
+       }
+#endif
        /* free private structure allocated for serial port */
        for (i = 0; i < serial->num_ports; ++i)
                kfree(usb_get_serial_port_data(serial->port[i]));
-
-       /* free private structure allocated for serial device */
-       kfree(usb_get_serial_data(serial));
 }
 
 static struct usb_driver usb_driver = {