/*
 * Prolific USB Serial Adapter Driver
 *
 *  Copyright (C) 2001
 *      Prolific Technology Inc.
 *
 *  This program is largely derived from work by the linux-usb group
 *  and associated source files.  Please see the usb/serial files for
 *  individual credits and copyrights.
 *
 */
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/signal.h>
#include <linux/errno.h>
#include <linux/poll.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/fcntl.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/usb.h>

#ifdef USB_SERIAL_DEBUG
static int  debug = 1;
#else
static int  debug;
#endif
#include "usb-serial.h"
#include "pl2303.h"

#define TRUE  1
#define FALSE 0

/* function prototypes for a Prolific USB Serial Adapter */
static int                                prolific_sa_startup(struct usb_serial *serial);
static void                               prolific_sa_shutdown(struct usb_serial *serial);
static int                                prolific_sa_open(struct usb_serial_port *port, struct file *filp);
static void                               prolific_sa_close(struct usb_serial_port *port, struct file *filp);
static void                               prolific_sa_read_int_callback(struct urb *urb);
static void                               prolific_sa_set_termios(struct usb_serial_port *port, struct termios *old);
static int                                prolific_sa_ioctl(struct usb_serial_port  *port, struct file             *file,
                                                            unsigned int            cmd, unsigned long           arg);
static void                               prolific_sa_break_ctl(struct usb_serial_port *port, int break_state);
static void                               prolific_set_dcr_state(struct usb_serial_port *port, int set);
static int							  prolific_sa_write(struct usb_serial_port *port, int from_user, const unsigned char *buf, int count);
static void							  prolific_sa_write_bulk_callback(struct urb *urb);

static __devinitdata struct usb_device_id id_table_combined[] = {
  { USB_DEVICE(PROLIFIC_SA_VID, PROLIFIC_SA_PID) },
  { } /* Terminating entry */
};

static __devinitdata struct usb_device_id prolific_sa_table[] = {
  { USB_DEVICE(PROLIFIC_SA_VID, PROLIFIC_SA_PID) },
  { } /* Terminating entry */
};

MODULE_DEVICE_TABLE(usb, id_table_combined);

/* All of the device info needed for the Prolific serial converter */
struct usb_serial_device_type prolific_sa_device =
{
name:
  "Prolific USB Serial Adapter",
  id_table : prolific_sa_table,                       /* the Prolific device */
  needs_interrupt_in : MUST_HAVE,                     /* this device must have an interrupt in endpoint */
  needs_bulk_in : MUST_HAVE,                          /* this device must have a bulk in endpoint */
  needs_bulk_out : MUST_HAVE,                         /* this device must have a bulk out endpoint */
  num_interrupt_in : 1,
  num_bulk_in : 1,
  num_bulk_out : 1,
  num_ports : 1,
  open : prolific_sa_open,
  close : prolific_sa_close,
  write : prolific_sa_write,
  read_int_callback : prolific_sa_read_int_callback,  /* How we get the status info */
  write_bulk_callback : prolific_sa_write_bulk_callback,
  ioctl : prolific_sa_ioctl,
  set_termios : prolific_sa_set_termios,
  break_ctl : prolific_sa_break_ctl,
  startup : prolific_sa_startup,
  shutdown : prolific_sa_shutdown,
};

struct line_coding {
  unsigned long dwDTERate;
  unsigned char bCharFormat;
  unsigned char bParityType;
  unsigned char bDatabits;
};

struct prolific_sa_private {
  unsigned long       control_state;
  unsigned char       last_lsr;
  unsigned char       last_msr;
  int                 bad_flow_control;
  struct line_coding  lineCoding;
  unsigned short      RTSDTRState;
};

/*
 * ***************************************************************************
 * Prolific USB Serial Adapter specific driver functions
 * ***************************************************************************
 */
#define WDR_TIMEOUT (HZ * 5)  /* default urb timeout */

/* assumes that struct usb_serial *serial is available */
#define BSA_USB_CMD(r, v) \
  usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), (r), PROLIFIC_SA_SET_REQUEST_CLASS_TYPE, (v), 0, NULL, 0, \
                  WDR_TIMEOUT)
#define BSA_USB_CMD_SET_DATA(r, v, data) \
  usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), (r), PROLIFIC_SA_SET_REQUEST_CLASS_TYPE, (v), 0, data, \
                  sizeof(data), WDR_TIMEOUT)
#define BSA_USB_CMD_SET_DATA_LEN(r, v, data, size) \
  usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), (r), PROLIFIC_SA_SET_REQUEST_CLASS_TYPE, (v), 0, data, \
                  size, WDR_TIMEOUT)
#define BSA_USB_CMD_SET_VENDOR(r, v, idx) \
  usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), (r), PROLIFIC_SA_SET_REQUEST_VENDOR_TYPE, (v), (idx), \
                  NULL, 0, WDR_TIMEOUT)
#define BSA_USB_CMD_GET_VENDOR_DATA(r, v, data, size) \
  usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), (r), PROLIFIC_SA_GET_REQUEST_VENDOR_TYPE, (v), 0, data, \
                  size, WDR_TIMEOUT)

/* do some startup allocations not currently performed by usb_serial_probe() */
static int prolific_sa_startup(struct usb_serial *serial)
{
  struct usb_device           *dev = serial->dev;
  struct prolific_sa_private  *priv;

  /* allocate the private data structure */
  serial->port->private = kmalloc(sizeof(struct prolific_sa_private), GFP_KERNEL);
  if(!serial->port->private)
    return(-1); /* error */
  priv = (struct prolific_sa_private *)serial->port->private;

  /* set initial values for control structures */
  priv->control_state = 0;
  priv->last_lsr = 0;
  priv->last_msr = 0;

  /* see comments at top of file */
  priv->bad_flow_control = (dev->descriptor.bcdDevice <= 0x0206) ? 1 : 0;
  info("bcdDevice: %04x, bfc: %d", dev->descriptor.bcdDevice, priv->bad_flow_control);

  init_waitqueue_head(&serial->port->write_wait);

  return(0);
}

static void prolific_sa_shutdown(struct usb_serial *serial)
{
  int i;

  dbg(__FUNCTION__);

  /* stop reads and writes on all ports */
  for(i = 0; i < serial->num_ports; ++i) {
    while(serial->port[i].open_count > 0) {
      prolific_sa_close(&serial->port[i], NULL);
    }

    /* My special items, the standard routines free my urbs */
    if(serial->port[i].private)
      kfree(serial->port[i].private);
  }
}

static int prolific_sa_open(struct usb_serial_port *port, struct file *filp)
{
  unsigned long flags;

  dbg(__FUNCTION__ " port %d", port->number);

  spin_lock_irqsave(&port->port_lock, flags);

  ++port->open_count;
  MOD_INC_USE_COUNT;

  if(!port->active) {
    port->active = 1;

    /*Start reading from the device*/

    /* TODO: Look at possibility of submitting mulitple URBs to device to
		 *       enhance buffering.  Win trace shows 16 initial read URBs.
		 */
    port->read_urb->dev = port->serial->dev;
    if(usb_submit_urb(port->read_urb))
      err("usb_submit_urb(read bulk) failed");

    port->interrupt_in_urb->dev = port->serial->dev;
    if(usb_submit_urb(port->interrupt_in_urb))
      err(" usb_submit_urb(read int) failed");
  }

  spin_unlock_irqrestore(&port->port_lock, flags);

  return 0;
} /* prolific_sa_open */

static void prolific_sa_close(struct usb_serial_port *port, struct file *filp)
{
  unsigned long flags;

  dbg(__FUNCTION__ " port %d", port->number);

  spin_lock_irqsave(&port->port_lock, flags);

  --port->open_count;
  MOD_DEC_USE_COUNT;

  if(port->open_count <= 0) {
    /* shutdown our bulk reads and writes */
    usb_unlink_urb(port->write_urb);
    usb_unlink_urb(port->read_urb);
    usb_unlink_urb(port->interrupt_in_urb); /* wgg - do I need this? I think so. */
    port->active = 0;
  }

  spin_unlock_irqrestore(&port->port_lock, flags);
} /* prolific_sa_close */

static void prolific_sa_read_int_callback(struct urb *urb)
{
  struct usb_serial_port      *port = (struct usb_serial_port *)urb->context;
  struct prolific_sa_private  *priv = (struct prolific_sa_private *)port->private;
  struct usb_serial           *serial;
  unsigned char               *data = urb->transfer_buffer;

  /* the urb might have been killed. */
  if(urb->status)
    return;

  if(port_paranoia_check(port, "prolific_sa_read_interrupt"))
    return;

  serial = port->serial;
  if(serial_paranoia_check(serial, "prolific_sa_read_interrupt"))
    return;

  usb_serial_debug_data(__FILE__, __FUNCTION__, urb->actual_length, data);

  /* Handle known interrupt data */

  /* ignore data[0] and data[1] */
  priv->last_msr = data[PROLIFIC_SA_MSR_INDEX];
  dbg("interrupt: 0x%02x", priv->last_msr);

  /* Record Control Line states */
  if(priv->last_msr & PROLIFIC_SA_MSR_DSR)
    priv->control_state |= TIOCM_DSR;
  else
    priv->control_state &= ~TIOCM_DSR;

  if(priv->last_msr & PROLIFIC_SA_MSR_CTS)
    priv->control_state |= TIOCM_CTS;
  else
    priv->control_state &= ~TIOCM_CTS;

  if(priv->last_msr & PROLIFIC_SA_MSR_RI)
    priv->control_state |= TIOCM_RI;
  else
    priv->control_state &= ~TIOCM_RI;

  if(priv->last_msr & PROLIFIC_SA_MSR_CD)
    priv->control_state |= TIOCM_CD;
  else
    priv->control_state &= ~TIOCM_CD;

  /* Now to report any errors */
  priv->last_lsr = data[PROLIFIC_SA_LSR_INDEX];
#if 0
  if(priv->last_lsr & PROLIFIC_SA_LSR_ERR) {
    tty = port->tty;

    /* Overrun Error */
    if(priv->last_lsr & PROLIFIC_SA_LSR_OE) {
    }

    /* Parity Error */
    if(priv->last_lsr & PROLIFIC_SA_LSR_PE) {
    }

    /* Framing Error */
    if(priv->last_lsr & PROLIFIC_SA_LSR_FE) {
    }

    /* Break Indicator */
    if(priv->last_lsr & PROLIFIC_SA_LSR_BI) {
    }
  }
#endif
  /* INT urbs are automatically re-submitted */
}

static void prolific_sa_set_termios(struct usb_serial_port *port, struct termios *old_termios)
{
  struct usb_serial           *serial = port->serial;
  struct prolific_sa_private  *priv = (struct prolific_sa_private *)port->private;
  unsigned int                cflag = port->tty->termios->c_cflag;
  unsigned int                old_cflag = old_termios->c_cflag;
  unsigned long               rate = 115200;
  unsigned char               format = 0, parity = 0, size = 8;

  info(__FUNCTION__ " - cflag: 0x%4X, old_cflag: 0x%4X", cflag, old_cflag);

  /* Set the baud rate */
  if((cflag & CBAUD) != (old_cflag & CBAUD)) {
    /* reassert DTR and (maybe) RTS on transition from B0 */
    if((old_cflag & CBAUD) == B0) {
      priv->control_state |= (TIOCM_DTR | TIOCM_RTS);
      if(BSA_USB_CMD(PROLIFIC_SA_SET_CTRL_LINE_REQUEST, priv->RTSDTRState |= 0x0001) < 0)
        err("Set DTR error");

      /* don't set RTS if using hardware flow control */
      if(!(old_cflag & CRTSCTS))
        if(BSA_USB_CMD(PROLIFIC_SA_SET_CTRL_LINE_REQUEST, priv->RTSDTRState |= 0x0002) < 0)
          err("Set RTS error");
    }

    switch(cflag & CBAUD)
    {
      case B150:    rate = 150; break;
      case B300:    rate = 300; break;
      case B600:    rate = 600; break;
      case B1200:   rate = 1200; break;
      case B2400:   rate = 2400; break;
      case B4800:   rate = 4800; break;
      case B9600:   rate = 9600; break;
      case B19200:  rate = 19200; break;
      case B38400:  rate = 38400; break;
      case B115200: rate = 115200; break;
      case B230400: rate = 230400; break;
      case B460800: rate = 460800; break;
    }

    if((cflag & CBAUD) == B0) {
      /* Drop RTS and DTR */
      priv->control_state &= ~(TIOCM_DTR | TIOCM_RTS);
      if(BSA_USB_CMD(PROLIFIC_SA_SET_CTRL_LINE_REQUEST, priv->RTSDTRState &= 0xFFFE) < 0)
        err("DTR LOW error");
      if(BSA_USB_CMD(PROLIFIC_SA_SET_CTRL_LINE_REQUEST, priv->RTSDTRState &= 0xFFFD) < 0)
        err("RTS LOW error");
    }
  }

  /* set the parity */
  if((cflag & (PARENB | PARODD)) != (old_cflag & (PARENB | PARODD))) {
    if(cflag & PARENB)
      parity = (cflag & PARODD) ? 0x01 : 0x02;
    else
      parity = 0;
  }

  /* set the number of data bits */
  if((cflag & CSIZE) != (old_cflag & CSIZE)) {
    switch(cflag & CSIZE)
    {
      case CS5: size = 5; break;
      case CS6: size = 6; break;
      case CS7: size = 7; break;
      case CS8: size = 8; break;
    }
  }

  /* set the number of stop bits */
  if((cflag & CSTOPB) != (old_cflag & CSTOPB))
    format = (cflag & CSTOPB) ? 0x02 : 0x00;

  /* Set flow control */
  if((cflag & CRTSCTS) != (old_cflag & CRTSCTS)) {
    if(cflag & CRTSCTS)
      prolific_set_dcr_state(port, TRUE);
    else
      prolific_set_dcr_state(port, FALSE);
  }

  priv->lineCoding.dwDTERate = rate;
  priv->lineCoding.bCharFormat = format;
  priv->lineCoding.bParityType = parity;
  priv->lineCoding.bDatabits = size;

  dbg(__FUNCTION__ "==> dwDTERate: %ld - bCharFormat: %d - bParityType: %d - bDatabits: %d", rate, format, parity, size);

  if(BSA_USB_CMD_SET_DATA_LEN(PROLIFIC_SA_SET_LINECODING_REQUEST, 0, &priv->lineCoding, 7) < 0)
    err("Set_line_coding request error");
} /* prolific_sa_set_termios */

static void prolific_sa_break_ctl(struct usb_serial_port *port, int break_state)
{
  struct usb_serial *serial = port->serial;

  if(break_state) {
    if(BSA_USB_CMD(PROLIFIC_SA_SET_BREAK_REQUEST, 0xFFFF) < 0)
      err("Set break_ctl %d fail", break_state);
  }
  else {
    if(BSA_USB_CMD(PROLIFIC_SA_SET_BREAK_REQUEST, 0) < 0)
      err("Set break_ctl %d fail", break_state);
  }
}

static int prolific_sa_ioctl(struct usb_serial_port *port, struct file *file, unsigned int cmd, unsigned long arg)
{
  struct usb_serial           *serial = port->serial;
  __u16                       urb_value;  /* Will hold the new flags */
  struct prolific_sa_private  *priv = (struct prolific_sa_private *)port->private;
  int                         ret, mask;

  info(__FUNCTION__ " cmd: %04x", cmd);

  /* Based on code from acm.c and others */
  switch(cmd)
  {
    case TIOCMGET:
      dbg("TIOCMGET: control_state - 0x%04x", (unsigned int)priv->control_state);
      return put_user(priv->control_state, (unsigned long *)arg);
      break;

    case TIOCMSET:  /* Turns on and off the lines as specified by the mask */
    case TIOCMBIS:  /* turns on (Sets) the lines as specified by the mask */
    case TIOCMBIC:  /* turns off (Clears) the lines as specified by the mask */
      if((ret = get_user(mask, (unsigned long *)arg)))
        return ret;

      dbg("cmd: 0x%04X mask: 0x%04X", cmd, mask);

      if((cmd == TIOCMSET) || (mask & TIOCM_RTS)) {
        /* RTS needs set */
        urb_value = ((cmd == TIOCMSET) && (mask & TIOCM_RTS)) || (cmd == TIOCMBIS) ? 1 : 0;
        if(urb_value)
          priv->control_state |= TIOCM_RTS;
        else
          priv->control_state &= ~TIOCM_RTS;

        if(urb_value) {
          if((ret = BSA_USB_CMD(PROLIFIC_SA_SET_CTRL_LINE_REQUEST, priv->RTSDTRState |= 0x0002)) < 0) {
            err("Set RTS error %d", ret);
            return(ret);
          }
        }
        else {
          if((ret = BSA_USB_CMD(PROLIFIC_SA_SET_CTRL_LINE_REQUEST, priv->RTSDTRState &= 0xFFFD)) < 0) {
            err("Clear RTS error %d", ret);
            return(ret);
          }
        }

        if((cmd == TIOCMSET) || (mask & TIOCM_DTR)) {
          /* DTR needs set */
          urb_value = ((cmd == TIOCMSET) && (mask & TIOCM_DTR)) || (cmd == TIOCMBIS) ? 1 : 0;
          if(urb_value)
            priv->control_state |= TIOCM_DTR;
          else
            priv->control_state &= ~TIOCM_DTR;

          if(urb_value) {
            if((ret = BSA_USB_CMD(PROLIFIC_SA_SET_CTRL_LINE_REQUEST, priv->RTSDTRState |= 0x0001)) < 0) {
              err("Set DTR error %d", ret);
              return(ret);
            }
          }
          else {
            if((ret = BSA_USB_CMD(PROLIFIC_SA_SET_CTRL_LINE_REQUEST, priv->RTSDTRState &= 0xFFFE)) < 0) {
              err("Clear DTR error %d", ret);
              return(ret);
            }
          }
        }
      }
      break;

    case TIOCMIWAIT:
      /* wait for any of the 4 modem inputs (DCD,RI,DSR,CTS)*/

      /* TODO */
      return(0);

    case TIOCGICOUNT:
      /* return count of modemline transitions */

      /* TODO */
      return 0;

    default:
      dbg("prolific_sa_ioctl arg not supported - 0x%04x", cmd);
      return(-ENOIOCTLCMD);
      break;
  }

  return 0;
} /* prolific_sa_ioctl */

static void prolific_set_dcr_state(struct usb_serial_port *port, int set)
{
  struct usb_serial           *serial = port->serial;
  unsigned short              code = 0;
  unsigned char               old_value = 0;
  unsigned short              new_value;
  int                         ret;

  struct prolific_sa_private  *priv = (struct prolific_sa_private *)port->private;

  info(__FUNCTION__ " - set: %d", set);

  if(BSA_USB_CMD_GET_VENDOR_DATA(PROLIFIC_SA_VENDOR_REQUEST, code, &old_value, sizeof(old_value)) < 0)
    err("Get DCR request error");

  if(set)
    new_value = old_value | 0x40;
  else
    new_value = old_value & 0x3F;

  if(BSA_USB_CMD_SET_VENDOR(PROLIFIC_SA_VENDOR_REQUEST, code, new_value) < 0)
    err("Set DCR request error");

  if(set) {
    if((ret = BSA_USB_CMD(PROLIFIC_SA_SET_CTRL_LINE_REQUEST, priv->RTSDTRState |= 0x0002)) < 0) {
      err("Set RTS error %d", ret);
      return;
    }
  }
}

static int prolific_sa_write(struct usb_serial_port *port, int from_user, const unsigned char *buf, int count)
{
  struct usb_serial *serial = port->serial;
  unsigned long     flags;
  int               result;

  dbg(__FUNCTION__ " - port %d", port->number);

  if(count == 0) {
    dbg(__FUNCTION__ " - write request of 0 bytes");
    return(0);
  }

  /* only do something if we have a bulk out endpoint */
  if(serial->num_bulk_out) {
/*
    if(port->write_urb->status == -EINPROGRESS) {
      dbg(__FUNCTION__ " - already writing");
      return(0);
    }
*/
    while(port->write_urb->status == -EINPROGRESS)
      schedule();

    spin_lock_irqsave(&port->port_lock, flags);
    count = (count > port->bulk_out_size) ? port->bulk_out_size : count;

    usb_serial_debug_data(__FILE__, __FUNCTION__, count, buf);

    if(from_user) {
      copy_from_user(port->write_urb->transfer_buffer, buf, count);
    }
    else {
      memcpy(port->write_urb->transfer_buffer, buf, count);
    }

    /* set up our urb */
    FILL_BULK_URB(port->write_urb, serial->dev, usb_sndbulkpipe(serial->dev, port->bulk_out_endpointAddress),
                  port->write_urb->transfer_buffer, count,
                  prolific_sa_write_bulk_callback,
                  port);

    /* send the data out the bulk port */
    result = usb_submit_urb(port->write_urb);
    if(result) {
      err(__FUNCTION__ " - failed submitting write urb, error %d", result);
      spin_unlock_irqrestore(&port->port_lock, flags);
      return 0;
    }

    spin_unlock_irqrestore(&port->port_lock, flags);
    return(count);
  }

  /* no bulk out, so return 0 bytes written */
  return(0);
}

static void prolific_sa_write_bulk_callback(struct urb *urb)
{
  struct usb_serial_port  *port = (struct usb_serial_port *)urb->context;
  struct usb_serial       *serial = get_usb_serial(port, __FUNCTION__);

  dbg(__FUNCTION__ " - port %d", port->number);

  if(!serial) {
    dbg(__FUNCTION__ " - bad serial pointer, exiting");
    return;
  }

  if(urb->status) {
    dbg(__FUNCTION__ " - nonzero write bulk status received: %d", urb->status);
    return;
  }

  queue_task(&port->tqueue, &tq_immediate);
  mark_bh(IMMEDIATE_BH);

  return;
}

static int __init prolific_sa_init(void)
{
  usb_serial_register(&prolific_sa_device);
  return 0;
}

static void __exit prolific_sa_exit(void)
{
  usb_serial_deregister(&prolific_sa_device);
}

module_init(prolific_sa_init);
module_exit(prolific_sa_exit);

MODULE_AUTHOR("Sam Kuo");
MODULE_DESCRIPTION("Prolific USB-Serial converter driver");

MODULE_PARM(debug, "i");
MODULE_PARM_DESC(debug, "Debug enabled or not");
