/*
 * Copyright (C)  1996  Warren Jasper
 * All rights reserved.
 *
 */

/***************************************************************************
 *
 * dio48H.c
 *
 ***************************************************************************/

#define MODULE

/* retrieve the CONFIG_* macros */
#include <linux/autoconf.h>
#if defined(CONFIG_MODVERSIONS) && !defined(MODVERSIONS)
#define MODVERSIONS
#endif

#ifdef MODVERSIONS
#include <linux/modversions.h>
#endif

#include <linux/version.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/major.h>
#include <linux/mm.h>
#include <linux/timer.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/malloc.h>
#include <linux/string.h>
#include <linux/signal.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/system.h>
#include "dio48h.h"
#include "dio.h"

/***************************************************************************
 *
 * Prototype of public and private functions.
 *
 ***************************************************************************/

static int dio_read(struct inode *iNode, struct file *filePtr, char *buf, int count);
static int dio_write(struct inode *iNode, struct file *filePtr, const char *buf, int count);
static int dio_open(struct inode *iNode, struct file *filePtr);
static void dio_close(struct inode *iNode, struct file *filePtr);
static int dio_ioctl(struct inode *iNode, struct file *filePtr, unsigned int cmd, LONG arg);
static int dio_find( WORD base_reg );
int init_module(void);
void cleanup_module(void) ;

/***************************************************************************
 *
 * Global data.
 *
 ***************************************************************************/

static WORD base_reg =  PORT_MIN;              /* base register address */
static int MajorNumber = DEFAULT_MAJOR_DEV;    /* Major number compiled in      */
static ChanRec Chan[DIO_PORTS];                /* Channel specific information  */
static BYTE dio_reg_1 = 0x80;                  /* status register 1             */
static BYTE dio_reg_2= 0x80;                   /* status register 2             */
#if LINUX_VERSION_CODE < 0x020000
char kernel_version[] = UTS_RELEASE;           /* Required to by module             */
#endif


/***************************************************************************
 *
 * read() service function
 *
 ***************************************************************************/

static int dio_read(struct inode *iNode, struct file *filePtr, char *buf, int count)
{
  BYTE  value;
  int   minor = MINOR(iNode->i_rdev);

  /* check to see if reading a value from the DIO */


  if ( minor >= 0 && minor < DIO_PORTS ) {
    value = inb(Chan[minor].addr);
    put_fs_byte(value, buf);
  } else {
    printk("dio_read: Incorrect minor number (%d).\n", minor);
    return -1;
  }
  #ifdef DEBUG
  printk("dio_read: %s DIO Port %#x set to %#x\n", ADAPTER_ID, minor, value);
  #endif

  return 1;
}

/***************************************************************************
 *
 * write() service function
 *
 ***************************************************************************/

static int dio_write(struct inode *iNode, struct file *filePtr, const char *buf, int count)
{
  int  minor = MINOR(iNode->i_rdev);
  BYTE value;

  /* check to see if writing a value to the DIO */
  if ( minor >= 0 && minor <  DIO_PORTS ) {
    value = get_fs_byte(buf);
    Chan[minor].value = value;
    outb_p( value, Chan[minor].addr);
  } else {
    printk("dio_write: Incorrect minor number (%d).\n", minor);
    return -1;
  }
  #ifdef DEBUG
  printk("dio_write %s: DIO Port %#x set to %#x\n", ADAPTER_ID, minor, value);
  #endif
  return 1;
}


/***************************************************************************
 *
 * open() service handler
 *
 ***************************************************************************/

static int dio_open(struct inode *iNode, struct file *filePtr)
{
  int minor = MINOR(iNode->i_rdev);

  /* 
     check if device is already open: only one process may read from a
     port at a time.  There is still the possibility of two processes 
     reading from two different channels messing things up. However,
     the overhead to check for this may not be worth it.
  */

  if ( Chan[minor].open == TRUE ) {
      return -EBUSY;
  }

  MOD_INC_USE_COUNT;
  Chan[minor].open = TRUE;              /* The device is open */

  #ifdef DEBUG
      printk("%s: open(): minor %d mode %d.\n", ADAPTER_ID, minor, Chan[minor].mode);
  #endif
  return 0;   
}

/***************************************************************************
 *
 * close() service handler
 *
 ***************************************************************************/

static void dio_close(struct inode *iNode, struct file *filePtr)
{
  int minor = MINOR(iNode->i_rdev);

  #ifdef DEBUG
      printk("%s: close() of minor number %d.\n", ADAPTER_ID, minor);
  #endif

  MOD_DEC_USE_COUNT;
  Chan[minor].open = FALSE;
}

/***************************************************************************
 *
 * iotctl() service handler
 *
 ***************************************************************************/

/* Note:
    Remember that FIOCLEX, FIONCLEX, FIONBIO, and FIOASYN are reserved ioctl cmd numbers
*/

static int dio_ioctl(struct inode *iNode, struct file *filePtr, unsigned int cmd, LONG arg)
{
  int minor = MINOR(iNode->i_rdev);

  if (_IOC_TYPE(cmd) != IOCTL_MAGIC) return -EINVAL;
  if (_IOC_NR(cmd) > IOCTL_MAXNR)  return -EINVAL;

  if (minor < 0 || minor >= DIO_PORTS) {
        return(-EINVAL);
  }

  switch (cmd) {

      case DIO_SET_MODE:
        arg &= 0x3;
        switch (minor) {
	    case 0:		       /* Port 1A */
              #ifdef DEBUG
              printk("DIO_SET_MODE for Port 1A\n");
              #endif
              dio_reg_1 &= 0x9f;
              dio_reg_1 |= (arg << 5);
              outb_p(dio_reg_1, DIO_CNTRL_REG_1);
              Chan[minor].mode = arg;
              break;

            case 1:			/* Port 1B */
              #ifdef DEBUG
              printk("DIO_SET_MODE for Port 1B\n");
              #endif
              if (arg & 0x2)
	        return(-EINVAL);	       /* Port 1B only has Modes 0 & 1 */
              dio_reg_1 &=  0xfb;
              dio_reg_1 |= (arg << 2);
              outb_p(dio_reg_1, DIO_CNTRL_REG_1);
              Chan[minor].mode = arg;
              break;

            case 2:			/* Port 1C */
              #ifdef DEBUG
              printk("DIO_SET_MODE for Port 1C\n");
              #endif
              if (arg)
	        return(-EINVAL);	/* Port 1C only has Mode 0 I/O */
              Chan[minor].mode = arg;
              break;

	    case 3:		       /* Port 2A */
              #ifdef DEBUG
              printk("DIO_SET_MODE for Port 2A\n");
              #endif
              dio_reg_2 &= 0x9f;
              dio_reg_2 |= (arg << 5);
              outb_p(dio_reg_2, DIO_CNTRL_REG_2);
              Chan[minor].mode = arg;
              break;

            case 4:			/* Port 2B */
              #ifdef DEBUG
              printk("DIO_SET_MODE for Port 2B\n");
              #endif
              if (arg & 0x2)
	        return(-EINVAL);	       /* Port 2B only has Modes 0 & 1 */
              dio_reg_2 &=  0xfb;
              dio_reg_2 |= (arg << 2);
              outb_p(dio_reg_2, DIO_CNTRL_REG_2);
              Chan[minor].mode = arg;
              break;

            case 5:			/* Port 2C */
              #ifdef DEBUG
              printk("DIO_SET_MODE for Port 2C\n");
              #endif
              if (arg)
	        return(-EINVAL);	/* Port 2C only has Mode 0 I/O */
              Chan[minor].mode = arg;
              break;

            default:
              #ifdef DEBUG
              printk("DIO_SET_MODE for Invalid Port\n");
              #endif
              return(-EINVAL);	/* Wrong Port Number */
              break;
        }
        break;
    
      case DIO_SET_DIRECTION:

        #ifdef DEBUG
        printk("DIO_SET_DIRECTION: minor = %d, arg = %d\n", (int) minor, (int) arg );
        #endif

        switch (minor) {
          case 0:			/* Port 1A */
            #ifdef DEBUG
            printk("DIO_SET_DIRECTION for Port 1A\n");
            #endif
            arg &= 0x1;
            dio_reg_1 &=  0xef;
            dio_reg_1 |= (arg << 4);
            outb_p(dio_reg_1, DIO_CNTRL_REG_1);
            break;

          case 1:			/* Port 1B */
            #ifdef DEBUG
            printk("DIO_SET_DIRECTION for Port 1B\n");
            #endif
            arg &= 0x1;
            dio_reg_1 &=  0xfd;
            dio_reg_1 |= (arg << 1);
            outb_p(dio_reg_1, DIO_CNTRL_REG_1);
            break;

          case 2:			/* Port 1C */
            #ifdef DEBUG
            printk("DIO_SET_DIRECTION for Port 1C\n");
            #endif
            switch ( arg ) {
              case PORT_INPUT:         /* CU = IN  CL = IN */
                  dio_reg_1 |= 0x9;
                  break;
              case PORT_OUTPUT:        /* CU = OUT  CL = OUT */
                  dio_reg_1 &= ~(0x9);
                  break;
              case LOW_PORT_INPUT:     /* CL = IN */
                  dio_reg_1 |= 0x1;
                  break;
              case LOW_PORT_OUTPUT:    /* CL = OUT */
                  dio_reg_1 &= ~(0x1);
                  break;
              case HIGH_PORT_INPUT:    /* CU = IN */
                  dio_reg_1 |= 0x8;
                  break;
              case HIGH_PORT_OUTPUT:   /* CU = OUT */
                  dio_reg_1 &= ~(0x8);
                  break;
              default:
                  return(-EINVAL);
                  break;
            }
            outb_p(dio_reg_1, DIO_CNTRL_REG_1);
            break;

          case 3:			/* Port 2A */
            #ifdef DEBUG
            printk("DIO_SET_DIRECTION for Port 2A\n");
            #endif
            arg &= 0x1;
            dio_reg_2 &=  0xef;
            dio_reg_2 |= (arg << 4);
            outb_p(dio_reg_2, DIO_CNTRL_REG_2);
            break;

          case 4:			/* Port 2B */
            #ifdef DEBUG
            printk("DIO_SET_DIRECTION for Port 2B\n");
            #endif
            arg &= 0x1;
            dio_reg_2 &=  0xfd;
            dio_reg_2 |= (arg << 1);
            outb_p(dio_reg_2, DIO_CNTRL_REG_2);
            break;

          case 5:			/* Port 2C */
            #ifdef DEBUG
            printk("DIO_SET_DIRECTION for Port 2C\n");
            #endif
            switch ( arg ) {
              case PORT_INPUT:         /* CU = IN  CL = IN */
                  dio_reg_2 |= 0x9;
                  break;
              case PORT_OUTPUT:        /* CU = OUT  CL = OUT */
                  dio_reg_2 &= ~(0x9);
                  break;
              case LOW_PORT_INPUT:     /* CL = IN */
                  dio_reg_2 |= 0x1;
                  break;
              case LOW_PORT_OUTPUT:    /* CL = OUT */
                  dio_reg_2 &= ~(0x1);
                  break;
              case HIGH_PORT_INPUT:    /* CU = IN */
                  dio_reg_2 |= 0x8;
                  break;
              case HIGH_PORT_OUTPUT:   /* CU = OUT */
                  dio_reg_2 &= ~(0x8);
                  break;
              default:
                  return(-EINVAL);
                  break;
            }
            outb_p(dio_reg_2, DIO_CNTRL_REG_2);
            break;

          default:
            #ifdef DEBUG
            printk("DIO_SET_DIRECTION for Invalid Port\n");
            #endif
            return(-EINVAL);
            break;
        }
        break;
    
      default:
        return(-EINVAL);
        break;
  }

  return 0;
}

/***************************************************************************
 *
 *
 ***************************************************************************/

static struct file_operations dio_fops = {
	NULL,           /* dio_seek */
	dio_read,
	dio_write,      /* dio_write */
	NULL,		/* dio_readdir */
	NULL,		/* dio_select */
	dio_ioctl,	/* dio_ioctl */
	NULL,
	dio_open,
	dio_close,
	NULL		/* fsync */
};

#ifdef __cplusplus
extern "C" {
#endif

/***************************************************************************
 *
 * Loads driver. Called when "insmod dio48H.o" is invoked on the command line.
 *
 ***************************************************************************/

int init_module(void) 
{
  register int i;
  int err;

  /* Register as a device with kernel.  */

  if ((err = register_chrdev(MajorNumber, "dio48H", &dio_fops))) {
      printk("%s: Failure to load module. error %d\n", ADAPTER_ID, -err);
      return err;
  } 

  /* Register the region of iospace with kernel and get base address */

#ifdef DIO48H_BASE
    base_reg = DIO48H_BASE;
    if ( check_region(base_reg, PORT_SIZE ) != 0 ) {  /* in use */
        printk("%s: Can't allocate region port address %#x\n", 
                ADAPTER_ID, base_reg );
        return -ENODEV;
    } else {
      request_region( base_reg, PORT_SIZE, "dio48H" );
    }
#else
  /* Look to see if DIO is installed */
  for ( base_reg = PORT_MIN; base_reg <= PORT_MAX; base_reg += PORT_STEP ) {
      if ( check_region(base_reg, PORT_SIZE ) == 0 ) {  /* not in use */
          request_region(base_reg, PORT_SIZE, "dio48H");
          if (dio_find( base_reg )) {  /* found the board */
              break;
          } else {
              release_region(base_reg, PORT_SIZE);
          }
      }
  }

  if ( base_reg > PORT_MAX ) {
      printk("%s: No board found from address %#x to %#x.\n", 
             ADAPTER_ID, PORT_MIN, PORT_MAX);
          if (unregister_chrdev(MajorNumber, "dio48H") != 0) {
              printk("%s: cleanup_module failed\n", ADAPTER_ID);
          }
      return -ENODEV;
  }

#endif

  printk("%s: address=%#x.", ADAPTER_ID, base_reg);
  printk(" 7/3/96 wjasper@tx.ncsu.edu\n");

  /* Set all channel structures to show nothing active/open */

  Chan[0].addr = DIO_PORT1A;
  Chan[1].addr = DIO_PORT1B;
  Chan[2].addr = DIO_PORT1C;
  Chan[3].addr = DIO_PORT2A;
  Chan[4].addr = DIO_PORT2B;
  Chan[5].addr = DIO_PORT2C;

  for (i = 0; i < DIO_PORTS; i++) {
      Chan[i].open = FALSE;
      Chan[i].mode = 0;
      outb_p(0x0, Chan[i].addr);
  }

  return 0;
}

/***************************************************************************
 *
 * Test memory locations for exsistance of dio48H board.
 *
 ***************************************************************************/
int dio_find( WORD base_reg)
{

/*
    To test if there is a CIO-DIO48 board do the following:
        1. The first three registers are the ports of the 8255.  Should
           be able to read and write from them.  The 8255 is in mode 0 on
           powerup.
        2. The  next three  registers are the ports of the 8255.  Should
           be able to read and write from them.  The 8255 is in mode 0 on
           powerup.

*/
    BYTE bReg, value;
    WORD i;
    
    value = 0xa5;

    /* Test the first 82C55 */

    dio_reg_1 = 0x80;
    outb_p( dio_reg_1, base_reg+3);      /* put in output mode 0 ? */

    for ( i = 0; i < 3; i++ ) {
        outb_p(value, base_reg + i);
        bReg = inb_p(base_reg + i);
        if ( bReg != value ) {
            return FALSE;   /* not the DIO48H */
        }
    }

    /* Test the second 82C55 */

    dio_reg_2 = 0x80;
    outb_p(dio_reg_2, base_reg+7);      /* put in output mode 0 ? */

    for ( i = 4; i < 7; i++ ) {
        outb_p(value, base_reg + i);
        bReg = inb_p(base_reg + i);
        if ( bReg != value ) {
            return FALSE;   /* not the DIO48H */
        }
    }

    return TRUE;
}

/***************************************************************************
 *
 * Remove driver. Called when "rmmod dio48H" is run on the command line.
 *
 ***************************************************************************/

void cleanup_module(void) 
{
  if (MOD_IN_USE) {
    printk("%s: device busy, remove delayed\n", ADAPTER_ID);
    return;
  }

  release_region( base_reg, PORT_SIZE );

  if (unregister_chrdev(MajorNumber, "dio48H") != 0) {
    printk("%s: cleanup_module failed\n", ADAPTER_ID);
  } else {

  #ifdef DEBUG
      printk("%s: module removed.\n", ADAPTER_ID);
  #endif

  }    
}





