/* das1200.c, M. Welsh (mdw@cs.cornell.edu)
 * Linux kernel module implementing Keithly Data Acquisition DAS-1200
 * device driver.
 *
 * (c)1995 M. Welsh
 * 
 * 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, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

/* This is a simple driver for the DAS1200 series of data acquisition
 * boards from Keithley. It was prepared in a hurry, so apologies in
 * advance if the interface and the code seem a bit rough around the
 * edges; in future releases I plan to add more features and cleanups.
 *
 * Please consult the DAS1200 User Guide (especially Chapter 9, Register
 * Level I/O Maps) for all details. Use of this driver assumes that you
 * are familiar with the architecture of this board.
 *
 * Right now, the board supports reading A/D conversions, setting the
 * value of the MUX scan register, and reading and writing the digital
 * I/O pins. It supports software triggering only (that is, a write
 * to the DAS1200_AD0 register to trigger a single or burst acquisition).
 * Future revisions will support external triggers (asynchronous with
 * respect to an actual read() operation) as well as triggering from
 * the on-board timers.
 *
 * I have only tested this in a single-ended configuration; I assume
 * that it will be fine with respect to differential inputs. Also, I
 * do not believe that this driver is compatible with the EXP-16 expansion
 * mux/amp available for the board. Little programming, if any, will be
 * required to support this.
 * 
 * This driver is a stand-alone loadable kernel module; no patches to
 * the kernel proper are required. DMA space is allocated with kmalloc().
 *
 * This driver is configurable by editing das1200.h, which contains
 * definitions for the base address, IRQ, DMA channel, and so forth
 * used by the driver. Probing for this board would be difficult without
 * poking at some common interrupts and addresses.
 *
 * See `dastest.c' and `dasgraph.c' for some simple example programs
 * which use this driver.
 *
 * Happy hacking!  --mdw
 */

#include <linux/fs.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/dma.h>
#include <asm/irq.h>
#include <linux/errno.h>
#include <linux/major.h>
#include <asm/segment.h>
#include <linux/kernel.h>
#include <linux/signal.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/malloc.h>
#include "das1200.h"

#include <linux/version.h>
char kernel_version[] = UTS_RELEASE;

/* Defines for type of I/O to use in this driver; define only one! */
#undef USE_INTERRUPTS	/* Around 11000 samples/sec */
#undef USE_POLLING	/* Around 55000 samples/sec */
#define USE_DMA		/* Around 60000 samples/sec */

/* Number of samples for SSH burst. 16 is the max. Note that the number
 * of bytes in a burst is BURST_LENGTH*2. */
#define BURST_LENGTH 16

#define SLEEP_ON(x) interruptible_sleep_on(x)
#define WAKE_UP(x) wake_up_interruptible(x)

/* Define for verbose debugging support. */
#undef DAS1200_DEBUG

#ifdef DAS1200_DEBUG
#define PRINTK(x) printk(x)
#else
#define PRINTK(x) /**/
#endif

/* Buffer to hold most recent conversion (polling and interrupts only) */
#if (defined(USE_INTERRUPTS) || defined(USE_POLLING))
static unsigned char current_adc[2];
#endif

static struct wait_queue *das1200_waitq = NULL;

#ifdef USE_DMA
static char *das1200_aligned_dmabuf;
#endif

#ifdef USE_DMA
/* Start DMA transfer of one burst from DAS1200 and sleep until interrupt */
static inline void start_dma() {
  cli(); 
  clear_dma_ff(DAS1200_DMA);
  set_dma_mode(DAS1200_DMA,DMA_MODE_READ); /* I/O to memory, no autoinit */
  set_dma_addr(DAS1200_DMA,(int)das1200_aligned_dmabuf);
  set_dma_count(DAS1200_DMA,BURST_LENGTH*2);
    
  /* Enable conversions. When 1600 Mode is enabled, the board
   * disables conversions after each TC. */ 
  outb(0x00,DAS1200_DISARM);  

  outb_p(0xFF,DAS1200_STATUSA);  /* Clear status flag. */

  /* Trigger transfer */
  outb_p(0xFF,DAS1200_AD0); 

  /* Start computer DMA controller */
  enable_dma(DAS1200_DMA);
  /* And wait for it to finish */ 
  PRINTK("das1200: sleeping\n");
  SLEEP_ON(&das1200_waitq);
  disable_dma(DAS1200_DMA);

  /* Once DMA is complete, das1200_interrupt() will be called */
}
#endif

#ifdef USE_INTERRUPTS
/* Get results of conversion into current_adc */
static void das1200_interrupt(int irq, struct pt_regs *regs) {
  current_adc[0] = inb_p(DAS1200_AD0);
  current_adc[1] = inb_p(DAS1200_AD1);
  WAKE_UP(&das1200_waitq);
}
#endif

#ifdef USE_DMA
/* Just wake us up to copy out of the DMA buffer */
static void das1200_interrupt(int irq, struct pt_regs *regs) {
  PRINTK("das1200: interrupt called\n");
  WAKE_UP(&das1200_waitq);
} 
#endif

static int das1200_read(struct inode *node, struct file *file, char *buf, 
			int count) {

  int num_samples = count >> 1; 
  unsigned char ctrl;
#ifndef USE_DMA
  char *p = buf;
  int s;
#endif

  PRINTK("das1200: das1200_read called\n");
  
#if ((defined(USE_INTERRUPTS)) || (defined(USE_DMA)))
  /* Enable interrupts */
  ctrl = inb(DAS1200_CTRL); 
  ctrl |= 0x80; 
  outb(ctrl,DAS1200_CTRL); 
#endif

  /* Enable conversions */
  outb(0x00,DAS1200_DISARM); 

#ifdef USE_DMA
  while (num_samples > 0) {
  
    /* For DMA, we take at least one burst at a time */
    start_dma();

    if (num_samples > BURST_LENGTH) {
      memcpy_tofs(buf,das1200_aligned_dmabuf,BURST_LENGTH*2); 
      buf += BURST_LENGTH*2;
    } else {
      memcpy_tofs(buf,das1200_aligned_dmabuf,num_samples*2);
      buf += num_samples*2;
    }

    num_samples -= BURST_LENGTH;

  }

#else /* If not using DMA */

  while (num_samples) {
    /* Start a conversion */

#if ((defined(USE_INTERRUPTS)) || (defined(USE_POLLING)))
    outb_p(0xFF,DAS1200_STATUSA);  /* Clear status flag. */
#endif

#ifdef USE_INTERRUPTS
    /* Need to cli before triggering! */
    cli();
#endif

    outb_p(0xFF,DAS1200_AD0);  /* Trigger */

#ifdef USE_POLLING
    /* Spin until finished */
    while ((s = (inb_p(DAS1200_STATUSA) & 0x10)) == 0); 

    /* Read them */
    current_adc[0] = inb_p(DAS1200_AD0);
    current_adc[1] = inb_p(DAS1200_AD1);
#endif

#ifdef USE_INTERRUPTS
    SLEEP_ON(&das1200_waitq);
    sti();
#endif

    /* Stuff the values into the user's buffer */
    put_fs_byte(current_adc[0],p++);
    put_fs_byte(current_adc[1],p++);

    num_samples--;  /* Interrupts and polling: One sample at a time */

  }
#endif /* not USE_DMA */
  
  /* Disable conversions */
  outb(0x40,DAS1200_DISARM); 

#if ((defined(USE_INTERRUPTS)) || (defined(USE_DMA)))
  /* Disable interrupts */
  ctrl = inb(DAS1200_CTRL);
  ctrl &= ~(0x80);
  outb(ctrl,DAS1200_CTRL);
#endif

  return count;

}

static int das1200_ioctl(struct inode *inode, struct file *filp,
			 unsigned int iocmd, unsigned long ioarg) {

  int majnr = MAJOR(inode->i_rdev);
  int retval;
  struct das1200_chan chans;
  unsigned char val;

  if (majnr != DAS1200_MAJOR) {
    printk("das1200_ioctl: Wrong device %d (should be %d)?\n",
	   majnr,DAS1200_MAJOR);
    return -ENODEV;
  }

  switch (iocmd) {
  case DAS1200_IO_SETCHAN:
    retval = verify_area(VERIFY_READ, (struct das1200_chan *)ioarg,
			 sizeof(struct das1200_chan));
    if (retval) return retval;
    memcpy_fromfs(&chans, (struct das1200_chan *)ioarg,
			  sizeof(struct das1200_chan));
    outb_p((((chans.das_echan & 0x0F) << 4) | (chans.das_schan & 0x0F)),
	   DAS1200_MUX);
    return 0;
    break;

  case DAS1200_IO_GETCHAN:
    retval = verify_area(VERIFY_WRITE, (struct das1200_chan *)ioarg,
			 sizeof(struct das1200_chan));
    if (retval) return retval;
    val = inb_p(DAS1200_MUX);
    chans.das_schan = val & 0x0F;
    chans.das_echan = (val & 0xF0) >> 4;
    memcpy_tofs((struct das1200_chan *)ioarg, &chans,
		sizeof(struct das1200_chan));
    return 0;
    break;

  case DAS1200_IO_SETDIO:
    outb_p(((unsigned char)ioarg & 0x0F),DAS1200_DIO);
    return 0;
    break;

  case DAS1200_IO_GETDIO:
    retval = verify_area(VERIFY_WRITE, (unsigned char *)ioarg,
			 sizeof(unsigned char));
    if (retval) return retval;
    val = inb_p(DAS1200_DIO);
    val &= 0x0F;
    memcpy_tofs((unsigned char *)ioarg, &val, sizeof(unsigned char));
    return 0;
    break;

  case DAS1200_IO_STATUSA:
    retval = verify_area(VERIFY_WRITE, (unsigned char *)ioarg,
			 sizeof(unsigned char));
    if (retval) return retval;
    val = inb_p(DAS1200_STATUSA);
    memcpy_tofs((unsigned char *)ioarg, &val, sizeof(unsigned char));
    return 0;
    break;

  case DAS1200_IO_STATUSB:
    retval = verify_area(VERIFY_WRITE, (unsigned char *)ioarg,
			 sizeof(unsigned char));
    if (retval) return retval;
    val = inb_p(DAS1200_STATUSB);
    memcpy_tofs((unsigned char *)ioarg, &val, sizeof(unsigned char));
    return 0;
    break;

  default:
    return -ENOTTY;
    break;
  }

}
    
static int das1200_open(struct inode *inode, struct file *filp) {

  PRINTK("das1200: das1200_open called\n");

  if (filp->f_count > 1) 
	return -EBUSY;
  
  MOD_INC_USE_COUNT;

  return 0;

}

static void das1200_close(struct inode *inode, struct file *filp) {

  MOD_DEC_USE_COUNT;
  PRINTK("das1200: das1200_close called\n");
}

static struct file_operations das1200_fops = {
	NULL,	/* lseek */
	das1200_read,
	NULL,	/* write */
	NULL, 	/* readdir */
	NULL, 	/* select */
	das1200_ioctl,	
	NULL,	/* mmap */
	das1200_open,
	das1200_close,
	NULL,	/* fsync */
	NULL,	/* fasync */
	NULL,	/* check_media_type */
	NULL,	/* revalidate */
};

int init_module(void) {

  unsigned char sa, sb;
  unsigned char ctrl, bl;

  PRINTK("das1200: init_module called\n");
  if (register_chrdev(DAS1200_MAJOR,"das1200",&das1200_fops)) {
    printk("das1200: register_chrdev failed.\n");
    return -EIO;
  } 

  /* Disable conversions: Set 1600 Mode, Convert Disable */
  outb_p(0x40,DAS1200_1600MDEN);
  outb_p(0x40,DAS1200_DISARM);

#ifdef USE_DMA
  /* Enable Burst Mode */
  outb_p(0x40,DAS1200_BMDEN); 

  /* Program burst length */
  bl = (inb_p(DAS1200_CNTEN) & 0x0F);
  bl |= ((BURST_LENGTH - 1) << 4);
  outb_p(bl,DAS1200_CNTEN);

  /* Program burst rate */
/*   outb_p((0x3f << 2),DAS1200_GAIN); */  /* Slowest rate? */
 outb_p((0x02 << 2),DAS1200_GAIN);  /* Slowest rate? */

  /* Try to allocate DMA buffer */
  das1200_aligned_dmabuf = (char *)kmalloc(DAS1200_DMABUF_SIZE,GFP_DMA);
  if (das1200_aligned_dmabuf == NULL) {
    printk("das1200: Unable to kmalloc %ld bytes for DMA buffer\n",
	   DAS1200_DMABUF_SIZE);
    return -EIO;
  }

#else
  /* Disable Burst Mode */
  outb(0x00,DAS1200_BMDEN); 
#endif

  /* Set up status register */
  ctrl = DAS1200_IRQ << 4; 
#ifdef USE_DMA
  ctrl |= 0x04; /* Set DMAE on */
#endif
  outb(ctrl,DAS1200_CTRL); 

#if (defined(USE_INTERRUPTS) || defined(USE_DMA))
  if (request_irq(DAS1200_IRQ,das1200_interrupt,SA_INTERRUPT,"das1200")) {
    printk("das1200: Can't allocate IRQ%d\n",DAS1200_IRQ);
    return -EIO;
  }
#endif

  /* Set initial values for MUX scan address: start = 0, end = 15 */
  outb_p(0xf0,DAS1200_MUX); 

  /* Check some bits in the status registers */
  sa = inb_p(DAS1200_STATUSA);
  sb = inb_p(DAS1200_STATUSB);

  printk("das1200: At 0x%x, irq %d, ",DAS1200_BASE,DAS1200_IRQ);

  if (sa & 0x20) printk("16 channels, "); 
  else printk("8 channels, ");
  if (sb & 0x02) printk("wait state enabled, ");
  else printk("wait state disabled, ");
  if (sb & 0x01) printk("using 10MHz clock.\n");
  else printk("using 1MHz clock.\n");

#ifdef USE_INTERRUPTS
  printk("das1200: Using interrupts.\n");
#endif

#if (defined(USE_POLLING))
  printk("das1200: Using polling.\n");
#endif

#if (defined(USE_DMA))
  printk("das1200: Using DMA channel %d.\n",DAS1200_DMA);
#endif

  return 0;

}

void cleanup_module(void) {
  PRINTK("das1200: cleanup_module called\n");

  if (MOD_IN_USE) 
    printk("das1200: Device busy, remove delayed.\n");
  
  /* Free resources */
  free_irq(DAS1200_IRQ);
  kfree(das1200_aligned_dmabuf);

  if (unregister_chrdev(DAS1200_MAJOR,"das1200") != 0) {
    printk("das1200: cleanup_module failed\n");
  } else {
    PRINTK("das1200: cleanup_module succeeded\n");
  }

}
