/***************************************************************************
                          usbcphost.c  -  description
                             -------------------
    begin                : Feb 19 15:00:38 CET 2002
    copyright            : (C) 2002 by S. Wessler
    email                : siegfried.wessler@hbm.com
 ***************************************************************************/

/* This driver is to connect a Linux desktop with the usb devices CP22, CP42,
   USB-Adapter (Spider 8 /CP32).
   When probing it reacts on the official usb-if vendor id and also, from the
   days of developing the CP22, the vendor id 0x067D, which we "borrowed" from
   the Hohner company. You can add new product ids, just take a look at a "grep"
   of "USB_PRODUCT_ID".

   Be aware that this driver is very slow, since I try to get only one packet
   per millisecond. That makes a maximum rate of 64kbyte/s. You can speed up by
   setting up several usb urb's, but I couldn't find out how and didn't want to
   invest too much time for searching around. So this driver so far is usable
   only to transmit few data via usb.

   Siegfried Wessler, 16.09.2002
*/


/* based on:
 * USB Skeleton driver - 0.6
 * by Greg Kroah-Hartman (greg@kroah.com)
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License as
 *  published by the Free Software Foundation; either version 2 of
 *  the License, or (at your option) any later version.
 *
 */



#define MODULE

#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/module.h>
#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/smp_lock.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/usb.h>

#include "usbcphost.h"

/* table of devices that work with this driver */
static struct usb_device_id cphost_table [] = {
  { USB_DEVICE(USB_VENDOR_ID2, USB_PRODUCT_ID2A) },
  { USB_DEVICE(USB_VENDOR_ID2, USB_PRODUCT_ID2B) },
  { USB_DEVICE(USB_VENDOR_ID2, USB_PRODUCT_ID2C) },
  { USB_DEVICE(USB_VENDOR_ID1, USB_PRODUCT_ID1) },
  { }         /* Terminating entry */
};

MODULE_DEVICE_TABLE (usb, cphost_table);


/* Structure to hold all of our device specific stuff */
struct usb_cphost {
  struct usb_device * udev;             /* save off the usb device pointer */
  struct usb_interface *  interface;    /* the interface for this device */
  devfs_handle_t    devfs;              /* devfs device node */
  unsigned char   minor;                /* the starting minor number for this device */
  unsigned char   num_ports;            /* the number of ports this device has */
  char      num_interrupt_in;           /* number of interrupt in endpoints we have */
  char      num_bulk_in;                /* number of bulk in endpoints we have */
  char      num_bulk_out;               /* number of bulk out endpoints we have */

  unsigned char *   bulk_in_buffer;     /* the buffer to receive data */
  int     bulk_in_size;                 /* the size of the receive buffer */
  __u8      bulk_in_endpointAddr;       /* the address of the bulk in endpoint */

  unsigned char *   bulk_out_buffer;    /* the buffer to send data */
  int     bulk_out_size;                /* the size of the send buffer */
  struct urb *    write_urb;            /* the urb used to send data */
  __u8      bulk_out_endpointAddr;      /* the address of the bulk out endpoint */

  struct tq_struct  tqueue;             /* task queue for line discipline waking up */
  int     open_count;                   /* number of times this port has been opened */
  struct semaphore  sem;                /* locks this structure */

  char    SerialNumber[SerialNumberSize];
  char    Manufacturer[256];
  char    Product[256];
};


/* the global usb devfs handle */
extern devfs_handle_t usb_devfs_handle;


/* local function prototypes */
static ssize_t usb_cphost_read  (struct file *file, char *buffer, size_t count, loff_t *ppos);
static ssize_t usb_cphost_write (struct file *file, const char *buffer, size_t count, loff_t *ppos);
static int usb_cphost_ioctl   (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);
static int usb_cphost_open    (struct inode *inode, struct file *file);
static int usb_cphost_release   (struct inode *inode, struct file *file);

static void * usb_cphost_probe  (struct usb_device *dev, unsigned int ifnum, const struct usb_device_id *id);
static void usb_cphost_disconnect (struct usb_device *dev, void *ptr);

static void usb_cphost_write_bulk_callback  (struct urb *urb);


/* array of pointers to our devices that are currently connected */
static struct usb_cphost    *minor_table[MAX_DEVICES];

/* lock to protect the minor_table structure */
static DECLARE_MUTEX (minor_table_mutex);

/* file operations needed when we register this driver */
static struct file_operations usb_cphost_fops = {
  owner:    THIS_MODULE,
  read:   usb_cphost_read,
  write:    usb_cphost_write,
  ioctl:    usb_cphost_ioctl,
  open:   usb_cphost_open,
  release:  usb_cphost_release,
};


/* usb specific object needed to register this driver with the usb subsystem */
static struct usb_driver usb_cphost_driver = {
  name:       MODULE_NAME,
  probe:      usb_cphost_probe,
  disconnect: usb_cphost_disconnect,
  fops:       &usb_cphost_fops,
  minor:      USB_CPHOST_MINOR_BASE,
  id_table:   cphost_table,
};




/**
 *  usb_cphost_delete
 */
static inline void usb_cphost_delete (struct usb_cphost *dev)
{

  DPRINT("%s: called usb_host_delete\n", MODULE_NAME);

  minor_table[dev->minor] = NULL;
  if (dev->bulk_in_buffer != NULL)
    kfree (dev->bulk_in_buffer);
  if (dev->bulk_out_buffer != NULL)
    kfree (dev->bulk_out_buffer);
  if (dev->write_urb != NULL)
    usb_free_urb (dev->write_urb);
  kfree (dev);
}


/**
 *  usb_cphost_open
 */
static int usb_cphost_open (struct inode *inode, struct file *file)
{
  struct usb_cphost *dev = NULL;
  int subminor;
  int retval = 0;

  subminor = MINOR (inode->i_rdev) - USB_CPHOST_MINOR_BASE;
  if ((subminor < 0) ||
      (subminor >= MAX_DEVICES))
  {
    printk("%s: open method called - error: subminor no.=%d\n", MODULE_NAME, subminor);
    return -ENODEV;
  }

  printk("%s: open method called. subminor no.=%d\n", MODULE_NAME, subminor);


  /* increment our usage count for the module */
  MOD_INC_USE_COUNT;

  /* lock our minor table and get our local data for this minor */
  down (&minor_table_mutex);
  dev = minor_table[subminor];
  if (dev == NULL) {
    up (&minor_table_mutex);
    MOD_DEC_USE_COUNT;
    return -ENODEV;
  }

  /* lock this device */
  down (&dev->sem);

  /* unlock the minor table */
  up (&minor_table_mutex);

  /* increment our usage count for the driver */
  ++dev->open_count;
  DPRINT("%s: open - dev->open_count=%d\n", MODULE_NAME, dev->open_count);

  /* save our object in the file's private structure */
  file->private_data = dev;

  /* unlock this device */
  up (&dev->sem);

  return retval;
}


/**
 *  usb_cphost_release
 */
static int usb_cphost_release (struct inode *inode, struct file *filp)
{
  struct usb_cphost *dev;
  int retval = 0;


  dev = (struct usb_cphost *)filp->private_data;
  if (dev == NULL)
  {
    printk("%s: close - warning: object is NULL\n", MODULE_NAME);
    return -ENODEV;
  }

  printk("%s: close. minor=%d\n", MODULE_NAME, dev->minor);

  /* lock our minor table */
  down (&minor_table_mutex);

  /* lock our device */
  down (&dev->sem);

  if (dev->open_count <= 0)
  {
    printk("%s: close - warning: device was not opened\n", MODULE_NAME);
    retval = -ENODEV;
    goto exit_not_opened;
  }

  if (dev->udev == NULL)
  {
    /* the device was unplugged before the file was released */
    printk("%s: close - warning: device was unplugged before file was released\n", MODULE_NAME);

    up (&dev->sem);
    usb_cphost_delete (dev);
    MOD_DEC_USE_COUNT;
    up (&minor_table_mutex);
    return 0;
  }

  /* decrement our usage count for the device */
  --dev->open_count;
  if (dev->open_count <= 0) {
    /* shutdown any bulk writes that might be going on */
    usb_unlink_urb (dev->write_urb);
    dev->open_count = 0;
  }

  /* decrement our usage count for the module */
  MOD_DEC_USE_COUNT;

exit_not_opened:
  up (&dev->sem);
  up (&minor_table_mutex);

  return retval;
}


/**
 *  usb_cphost_read
 */
static ssize_t usb_cphost_read (struct file *file, char *buffer, size_t count, loff_t *ppos)
{
  struct usb_cphost *dev;
  int retval = 0;


  dev = (struct usb_cphost *)file->private_data;

  DPRINT("%s: read called. minor=%d, count=%d\n", MODULE_NAME, dev->minor, count);

  /* lock this object */
  down (&dev->sem);

  /* verify that the device wasn't unplugged */
  if (dev->udev == NULL)
  {
    DPRINT("%s: read called, but device unplugged before\n", MODULE_NAME);
    up (&dev->sem);
    return -ENODEV;
  }

  /* do an immediate bulk read to get data from the device */
  retval = usb_bulk_msg (dev->udev,
            usb_rcvbulkpipe (dev->udev,
            dev->bulk_in_endpointAddr),
            dev->bulk_in_buffer, dev->bulk_in_size,
            &count, HZ*10);

  /* if the read was successful, copy the data to userspace */
  if (!retval) {
    if (copy_to_user (buffer, dev->bulk_in_buffer, count))
    {
      printk("%s: read called - error: copy_to_user failed. (count=%d)\n", MODULE_NAME, count);
      retval = -EFAULT;
    }
    else
      retval = count;
  }

  /* unlock the device */
  up (&dev->sem);
  return retval;
}


/**
 *  usb_cphost_write
 */
static ssize_t usb_cphost_write (struct file *file, const char *buffer, size_t count, loff_t *ppos)
{
  struct usb_cphost *dev;
  ssize_t bytes_written = 0;
  int retval = 0;


  dev = (struct usb_cphost *)file->private_data;

  DPRINT("%s: write called. minor=%d, count=%d\n", MODULE_NAME, dev->minor, count);

  /* lock this object */
  down (&dev->sem);

  /* verify that the device wasn't unplugged */
  if (dev->udev == NULL)
  {
    DPRINT("%s: write called, but device unplugged before\n", MODULE_NAME);
    retval = -ENODEV;
    goto exit;
  }

  /* verify that we actually have some data to write */
  if (count == 0) {
    DPRINT("%s: write called, but no bytes to send\n", MODULE_NAME);
    goto exit;
  }

  /* see if we are already in the middle of a write */
  if (dev->write_urb->status == -EINPROGRESS) {
    DPRINT("%s: write called, but already in the middle of a write\n", MODULE_NAME);
    goto exit;
  }

  /* we can only write as much as 1 urb will hold */
  bytes_written = (count > dev->bulk_out_size) ?
        dev->bulk_out_size : count;

  /* copy the data from userspace into our urb */
  if (copy_from_user(dev->write_urb->transfer_buffer, buffer, bytes_written))
  {
    printk("%s: write called - error: copy_from_user failed. (bytes_written=%d)\n", MODULE_NAME, bytes_written);
    retval = -EFAULT;
    goto exit;
  }

  /* set up our urb */
  FILL_BULK_URB(dev->write_urb, dev->udev,
          usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
          dev->write_urb->transfer_buffer, bytes_written,
          usb_cphost_write_bulk_callback, dev);

  /* send the data out the bulk port */
  retval = usb_submit_urb(dev->write_urb);
  if (retval)
  {
    printk("%s: write called - error: submitting write urb failed. error code=%d\n", MODULE_NAME, retval);
  }
  else
  {
    retval = bytes_written;
  }

exit:
  /* unlock the device */
  up (&dev->sem);

  return retval;
}


/**
 *  usb_cphost_ioctl
 */
static int usb_cphost_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
  struct usb_cphost *dev;
  char  buffer[SerialNumberSize];
  int   i;
  char  *copybuf;


  dev = (struct usb_cphost *)file->private_data;

  /* lock this object */
  down (&dev->sem);

  /* verify that the device wasn't unplugged */
  if (dev->udev == NULL)
  {
    DPRINT("%s: ioctl called, but device unplugged before\n", MODULE_NAME);
    up (&dev->sem);
    return -ENODEV;
  }

  DPRINT("%s: ioctl - minor=%d, cmd=%d (0x%04X), arg=%ld (0x%04X%04X)\n", MODULE_NAME,
          dev->minor, cmd, cmd, arg, (int)(arg >> 8), (int)(arg & 0x00ff));


  switch (cmd)
  {
    case  USB_CPHOST_IOC_GET_IDSTR:
      /* This Control is to tell the user the "USB-ID" (SerialNumber) of the connected MGCplus.
          The user has to set "arg" with a pointer to a character buffer this driver should use.
          The buffer will be written with a null terminated string with max. "SerialNumberSize" bytes (including "\0").
          If "arg == 0", no information will be written to user. */

      printk("%s: ioctl-get_id, SerialNumber=%s\n", MODULE_NAME, dev->SerialNumber);
      i=0;
      do
      {
        buffer[i] = dev->SerialNumber[i];
        i++;
      } while ((i<=SerialNumberSize) || (dev->SerialNumber[i]=0));

      copybuf = arg;
      copy_to_user(copybuf, buffer, i);

      break;


    default:
    {
      up (&dev->sem);

      /* return that we did not understand this ioctl call */
      return -ENOTTY;
    }

  }

  up (&dev->sem);
  return 0;

}


/**
 *  usb_cphost_write_bulk_callback
 */
static void usb_cphost_write_bulk_callback (struct urb *urb)
{
  struct usb_cphost *dev = (struct usb_cphost *)urb->context;

  DPRINT("%s: write_bulk_callback. dev->minor=%d\n", MODULE_NAME, dev->minor);

  if ((urb->status != -ENOENT) && (urb->status != -ECONNRESET))
  {
    DPRINT("%s: write_bulk_callback - error: nonzero write bulk status received. status=%d\n",
        MODULE_NAME, urb->status);
    return;
  }

  return;
}


/**
 *  usb_cphost_probe
 *
 *  Called by the usb core when a new device is connected that it thinks
 *  this driver might be interested in.
 */
static void * usb_cphost_probe(struct usb_device *udev, unsigned int ifnum, const struct usb_device_id *id)
{
  struct usb_cphost *dev = NULL;
  struct usb_interface *interface;
  struct usb_interface_descriptor *iface_desc;
  struct usb_endpoint_descriptor *endpoint;
  int minor;
  int buffer_size;
  int i;
  char name[10];


  printk("%s: probing: idVendor=0x%.4X, idProduct=0x%.4X\n", MODULE_NAME, udev->descriptor.idVendor, udev->descriptor.idProduct);
  /* See if the device offered us matches what we can accept */
  if (!(  ( (udev->descriptor.idVendor == USB_VENDOR_ID1)
            && (udev->descriptor.idProduct == USB_PRODUCT_ID1)
          )
        || ( (udev->descriptor.idVendor == USB_VENDOR_ID2)
            && ( (udev->descriptor.idProduct == USB_PRODUCT_ID2A)
                 || (udev->descriptor.idProduct == USB_PRODUCT_ID2B)
                 || (udev->descriptor.idProduct == USB_PRODUCT_ID2C)
               )
           )
       )
     )
  {
    printk("%s: probing not successful.\n", MODULE_NAME);
    return NULL;
  }

  /* select a "subminor" number (part of a minor number) */
  down (&minor_table_mutex);
  for (minor = 0; minor < MAX_DEVICES; ++minor) {
    if (minor_table[minor] == NULL)
      break;
  }

  if (minor >= MAX_DEVICES)
  {
    printk("%s: probe - error: too many devices plugged in. this device cannot be handled\n", MODULE_NAME);
    goto exit;
  }

  /* allocate memory for our device state and intialize it */
  dev = kmalloc (sizeof(struct usb_cphost), GFP_KERNEL);
  if (dev == NULL)
  {
    printk("%s: probe - error: out of memory\n", MODULE_NAME);
    goto exit;
  }
  minor_table[minor] = dev;

  interface = &udev->actconfig->interface[ifnum];

  init_MUTEX (&dev->sem);
  dev->udev = udev;
  dev->interface = interface;
  dev->minor = minor;
  dev->open_count = 0;    //we.

  /* set up the endpoint information */
  /* check out the endpoints */
  iface_desc = &interface->altsetting[0];
  for (i = 0; i < iface_desc->bNumEndpoints; ++i) {
    endpoint = &iface_desc->endpoint[i];

    if ((endpoint->bEndpointAddress & 0x80) &&
        ((endpoint->bmAttributes & 3) == 0x02)) {
      /* we found a bulk in endpoint */
      buffer_size = endpoint->wMaxPacketSize;
      dev->bulk_in_size = buffer_size;
      dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
      dev->bulk_in_buffer = kmalloc (buffer_size, GFP_KERNEL);
      if (!dev->bulk_in_buffer)
      {
        printk("%s: probe - error: couldn't allocate bulk_in_buffer\n", MODULE_NAME);
        goto error;
      }
    }

    if (((endpoint->bEndpointAddress & 0x80) == 0x00) &&
        ((endpoint->bmAttributes & 3) == 0x02)) {
      /* we found a bulk out endpoint */
      dev->write_urb = usb_alloc_urb(0);
      if (!dev->write_urb)
      {
        printk("%s: probe - error: no free URBs available\n", MODULE_NAME);
        goto error;
      }
      buffer_size = endpoint->wMaxPacketSize;
      dev->bulk_out_size = buffer_size;
      dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
      dev->bulk_out_buffer = kmalloc (buffer_size, GFP_KERNEL);
      if (!dev->bulk_out_buffer)
      {
        printk("%s: probe - error: couldn't allocate bulk_out_buffer\n", MODULE_NAME);
        goto error;
      }
      FILL_BULK_URB(dev->write_urb, udev,
              usb_sndbulkpipe(udev, endpoint->bEndpointAddress),
              dev->bulk_out_buffer, buffer_size,
              usb_cphost_write_bulk_callback, dev);
    }
  }

  /* initialize the devfs node for this device and register it */
  sprintf(name, "cphost%d", dev->minor);

  dev->devfs = devfs_register (usb_devfs_handle, name,
             DEVFS_FL_DEFAULT, USB_MAJOR,
             USB_CPHOST_MINOR_BASE + dev->minor,
             S_IFCHR | S_IRUSR | S_IWUSR |
             S_IRGRP | S_IWGRP | S_IROTH,
             &usb_cphost_fops, NULL);


  /* let the user know what node this device is now attached to */
  printk("%s: MGCplus (CP42/CP22) now attached to USBcphost%d\n", MODULE_NAME, dev->minor);

  if (udev->descriptor.iSerialNumber)
  {
    if (usb_string(udev, "SerialNumber", dev->SerialNumber, 255) > 0)
      printk("%s: device identification (serialnumber)=%s\n", MODULE_NAME, dev->SerialNumber);
    else
      dev->SerialNumber[0] = 0;
  }
  else
    dev->SerialNumber[0] = 0;

  goto exit;

error:
  usb_cphost_delete (dev);
  dev = NULL;

exit:
  up (&minor_table_mutex);
  return dev;
}



/**
 *  usb_cphost_disconnect
 *
 *  Called by the usb core when the device is removed from the system.
 */
static void usb_cphost_disconnect(struct usb_device *udev, void *ptr)
{
  struct usb_cphost *dev;
  int minor;

//  printk("%s: disconnect method called.\n", MODULE_NAME);

  dev = (struct usb_cphost *)ptr;

  down (&minor_table_mutex);
  down (&dev->sem);

  minor = dev->minor;

  /* remove our devfs node */
  devfs_unregister(dev->devfs);

  /* if the device is not opened, then we clean up right now */
  if (!dev->open_count) {
    up (&dev->sem);
    usb_cphost_delete (dev);
  } else {
    dev->udev = NULL;
    up (&dev->sem);
  }

  printk("%s: USBcphost%d now disconnected\n", MODULE_NAME, minor);

  up (&minor_table_mutex);

}



/**
 *  usb_cphost_init
 */
static int __init usb_cphost_init(void)
{
  int result;

  printk("%s: %s, %s\n", MODULE_NAME, DRIVER_DESC, DRIVER_VERSION);
  printk("%s: driver initialization\n", MODULE_NAME);

  /* register this driver with the USB subsystem */
  result = usb_register(&usb_cphost_driver);
  if (result < 0)
  {
    printk("%s: init - error: usb_register failed. error=%d\n", MODULE_NAME, result);
    return -1;
  }

  return 0;
}


/**
 *  usb_cphost_exit
 */
static void __exit usb_cphost_exit(void)
{
  /* deregister this driver with the USB subsystem */
  usb_deregister(&usb_cphost_driver);

  printk("%s: exit called. driver deregistered\n", MODULE_NAME);

}


module_init (usb_cphost_init);
module_exit (usb_cphost_exit);

MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");


