#ifndef __KERNEL__
#define __KERNEL__
#endif

#ifndef MODULE
#define MODULE
#endif

#define  PCI17X4_DEBUG	0

#include "common.h"

static const char pci1714_name[] = { "pci1714" };		/* our module name and the root of all generated device names */
static struct class *pci1714_class;				/* Linux stuff for registering our device class */
static struct cdev pci1714_cdev;				/* Linux stuff for registering our device class */
static int pci1714_numboards[2];				/* number of board found (separate counts for 30MHz and 10MHz (UL) boards */
static int pci1714_major;					/* our dynamically assugned device major */

static struct pci17x4_private_data *pci17x4_boards;		/* the list of devices */
static spinlock_t pci17x4_lock;					/* used to protect the linked list of devices */

#include "common.c"

static void
pci17x4_checktrigger(struct pci17x4_private_data *adc)
{
  uint32_t trigger = pci17x4_inportw(adc, ADC_REGS, PCI17X4_TRIGGER_REG16);
  if(trigger & PCI17X4_TRIGGER_FLAG) {
    pci17x4_outportw(adc, ADC_REGS, PCI17X4_TRIGGER_REG16, (trigger | PCI17X4_TRIGGER_FLAG | PCI17X4_TRIGGER_DMATC));
  }
}

static void
pci17x4_setuptrigger(struct pci17x4_private_data *adc)
{
  /* Set up trigger register */
  uint32_t trigger = pci17x4_inportw(adc, ADC_REGS, PCI17X4_TRIGGER_REG16);
  trigger |= (PCI17X4_TRIGGER_FLAG | PCI17X4_TRIGGER_DMATC);
  pci17x4_outportw(adc, ADC_REGS, PCI17X4_TRIGGER_REG16, trigger);
  /* Set up and clear DMA TC register */
  pci17x4_outportw(adc, ADC_REGS, PCI17X4_DMACOUNTER_REG16, 0xfff0);
  pci17x4_outportw(adc, ADC_REGS, PCI17X4_DMACRESET_REG16, 0xffff);
}

/* ==== per device init / remove ==== */
static struct file_operations pci1714_fops = {
  .owner   	  = THIS_MODULE,
  .read    	  = pci17x4_read,
  .write   	  = pci17x4_write,
  .unlocked_ioctl = pci17x4_ioctl,
  .compat_ioctl   = pci17x4_ioctl,
  .open    	  = pci17x4_open,
  .release 	  = pci17x4_close,
  .mmap    	  = pci17x4_mmap
};

static int __devinit
pci1714_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
{
  struct pci17x4_private_data *adc = NULL;
  int err;
  printk(KERN_INFO "%s: intializing device at bus=%d, slot=%d.\n", pci1714_name, pdev->bus->number, PCI_SLOT(pdev->devfn));
  if((err = pci_enable_device(pdev)) != 0) {
    printk(KERN_WARNING "%s: error enabling PCI device.\n", pci1714_name);
    return(err);
  }
  /* enable bus-master */
  pci_set_master(pdev);
  /* alloc & initialize the private data structure */
  if((adc = kmalloc(sizeof(*adc), GFP_KERNEL)) == NULL) {
    printk(KERN_WARNING "%s: error allocating memory for device data.\n", pci1714_name);
    return(-ENOMEM);
  }
  /* initialize the private data in the device */
  memset(adc, 0, sizeof(*adc));
  switch(pdev->subsystem_device) {
  case PCI1714_TYPE_:
  case PCI1714_TYPE_U:
    sprintf(adc->name, "%su%d", pci1714_name, pci1714_numboards[0]);
    break;
  default:
    sprintf(adc->name, "%sul%d", pci1714_name, pci1714_numboards[1]);
    adc->dma.ulboard = 1;
    break;
  }
  adc->minor = (adc->dma.ulboard << 6) | pci1714_numboards[adc->dma.ulboard];
  adc->raddr[PLX_REGS] = pci_resource_start(pdev, PLX_REGS);
  adc->raddr[ADC_REGS] = pci_resource_start(pdev, ADC_REGS);
  adc->rsize[PLX_REGS] = pci_resource_len(pdev, PLX_REGS);
  adc->rsize[ADC_REGS] = pci_resource_len(pdev, ADC_REGS);
  adc->pdev = pdev;
  /* request I/O regions */
  if(request_region(adc->raddr[PLX_REGS], adc->rsize[PLX_REGS], adc->name) == NULL) {
    printk(KERN_WARNING "%s: error reserving io range: 0x%x:0x%x.\n", adc->name, (int)adc->raddr[PLX_REGS], (int)adc->rsize[PLX_REGS]);
    kfree(adc);
    return (-EADDRNOTAVAIL);
  }
  if(request_region(adc->raddr[ADC_REGS], adc->rsize[ADC_REGS], adc->name) == NULL) {
    printk(KERN_WARNING "%s: error reserving io range: 0x%x:0x%x.\n", adc->name, (int)adc->raddr[ADC_REGS], (int)adc->rsize[ADC_REGS]);
    release_region(adc->raddr[PLX_REGS], adc->rsize[PLX_REGS]);
    kfree(adc);
    return (-EADDRNOTAVAIL);
  }
  /* initialize working data */
  init_waitqueue_head(&adc->waitqueue);
  spin_lock_init(&adc->lock);
  /* register device with kernel */
  pci_set_drvdata(pdev, adc);
  adc->dev = device_create(pci1714_class, NULL, MKDEV(pci1714_major, adc->minor), adc, adc->name);
  /* add device to our linked list */
  spin_lock(&pci17x4_lock);
  adc->next = pci17x4_boards;
  pci17x4_boards = adc;
  pci1714_numboards[adc->dma.ulboard]++;
  spin_unlock(&pci17x4_lock);
  return(0);
}

static void __devexit
pci1714_remove_one(struct pci_dev *pdev)
{
  struct pci17x4_private_data **padc, *adc = NULL;
  spin_lock(&pci17x4_lock);
  for(padc = &pci17x4_boards; padc[0]; padc = &padc[0]->next) {
    if(padc[0]->pdev == pdev) {
      adc = padc[0];
      padc[0] = adc->next;
      pci1714_numboards[adc->dma.ulboard]--;
      break;
    }
  }
  spin_unlock(&pci17x4_lock);
  if(!adc) {
    printk(KERN_WARNING "%s: error attempt to remove unknown device 0x%x:0x%x\n", pci1714_name, pdev->bus->number, PCI_SLOT(pdev->devfn));
    return;
  }
  printk(KERN_INFO "%s: removing device.\n", adc->name);
  release_region(adc->raddr[PLX_REGS], adc->rsize[PLX_REGS]);
  release_region(adc->raddr[ADC_REGS], adc->rsize[ADC_REGS]);
  device_destroy(pci1714_class, MKDEV(pci1714_major, adc->minor));
  kfree(adc);
}

/* ==== module init / exit ==== */
static struct pci_device_id pci1714_id_tbl[] __devinitdata = {
  { 0x13fe, 0x1714, 0x13fe, PCI1714_TYPE_     },
  { 0x13fe, 0x1714, 0x13fe, PCI1714_TYPE_U    },
  { 0x13fe, 0x1714, 0x13fe, PCI1714_TYPE_UL   },
  { 0x13fe, 0x1714, 0x13fe, PCI1714_TYPE_UL_A },
  { 0x13fe, 0x1714, 0x13fe, PCI1714_TYPE_UL_B },
  { 0 }
};

static struct pci_driver pci1714_driver = {
  .name           = "pci1714",
  .id_table       = pci1714_id_tbl,
  .probe          = pci1714_init_one,
  .remove         = pci1714_remove_one,
};

static int __init
pci1714_init(void)
{
  int err;
  dev_t dev;
  printk(KERN_INFO "%s: Driver loading.\n", pci1714_name);
  printk(KERN_INFO "%s: Copyright (C) 2007 EDAS, Inc.\n", pci1714_name);
  /* Register as a device with kernel.	*/
  if((err = alloc_chrdev_region(&dev, 0, 0xff, pci1714_name)) < 0) {
    printk(KERN_INFO "%s: Could not allocate device major number, error = %d.\n", pci1714_name, err);
    return(err);
  }
  pci1714_major = MAJOR(dev);
  /* Register our file operations with the kernel */
  cdev_init(&pci1714_cdev, &pci1714_fops);
  pci1714_cdev.owner = THIS_MODULE;
  pci1714_cdev.ops = &pci1714_fops;
  err = cdev_add(&pci1714_cdev, dev, 0xff);
  if(err) {
    printk(KERN_INFO "%s: Error %d in registering file operations.\n", pci1714_name, err);
    return(err);
  }
  /* create the class for the pci1714 */
  pci1714_class = class_create(THIS_MODULE, pci1714_name);
  if(IS_ERR(pci1714_class)) {
    return PTR_ERR(pci1714_class);
  }
  spin_lock_init(&pci17x4_lock);
  pci17x4_boards = NULL;
  pci1714_numboards[0] = 0;
  pci1714_numboards[1] = 0;
  return (pci_register_driver(&pci1714_driver));
}

static void __exit
pci1714_exit(void)
{
  pci_unregister_driver(&pci1714_driver);
  class_destroy(pci1714_class);
  cdev_del(&pci1714_cdev);
  unregister_chrdev_region(MKDEV(pci1714_major, 0), 0xff);
  printk(KERN_INFO "%s: module removed.\n", pci1714_name);
}

/* ==== Module interface ==== */
module_init(pci1714_init);
module_exit(pci1714_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Csaba Biegl");
MODULE_DESCRIPTION("Driver for the Advantech PCI-1714 and PCI-1714UL DAQ cards");
MODULE_DEVICE_TABLE(pci, pci1714_id_tbl);
