/* 
 * $Id: hpscan.c,v 0.1.1.5 1995/06/14 12:42:03 wingel Exp wingel $
 * Copyright 1995 by Christer Weinigel (wingel@ctrl-c.liu.se)
 *
 * LEGALESE:
 * I hereby grant anybody the right to do anything with this piece
 * of code. It would be nice if my name is mentioned, but do as you
 * please. And, oh, if this breaks something, it's not my fault,
 * I can't be held responsible for anything you do with your computer.
 *
 */

/*#include <linux/autoconf.h>*/

#ifdef MODULE
#include <linux/module.h>
#include <linux/version.h>
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#endif

#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/major.h>
#include <linux/sched.h>
#include <linux/ioport.h>

#include <asm/io.h>

#include "hpscan.h"

#define HPSJ_STATUS (iobase+1)	/* status register */
#define HPSJ_CTRL (iobase+2)	/* scanner control/status */
#define HPSJ_SWING (iobase+3)	/* swing register */
#define HPSJ_COUNT (iobase+7)	/* shared memory counter */

static unsigned iobase;		/* IO base address */
static unsigned char *membase;	/* memory base address */

/* Reset the interface and the scanner */
static __inline__ void hpsj_reset(void)
{
  outb_p(0, HPSJ_SWING);
  outb_p(0, HPSJ_SWING);
  
  outb_p(SCAN_SRC | SLCT, HPSJ_CTRL);

  current->state = TASK_INTERRUPTIBLE;
  current->timeout = jiffies + 10;
  schedule();

  outb_p(SCAN_SRC | SLCT | nRESET, HPSJ_CTRL);
}

/* Set shared memory counter */
static __inline__ void hpsj_setcount(int count)
{              
  outb_p(CNT_WEN, HPSJ_SWING);	/* write to low byte of swing counter */
  outb_p(count, HPSJ_COUNT);
  outb_p(CNT_WEN | CNT_HL, HPSJ_SWING); /* write to high byte */
  outb_p(count >> 8, HPSJ_COUNT);

#if HPSCAN_DEBUG > 2
  printk("setcount = %d (0x%x)\n", count, count);
#endif
}

/* Read shared memory counter */
static __inline__ int hpsj_getcount(void)
{              
  int count;

  outb_p(SWG_EN | SWG_HND, HPSJ_SWING);
  count = inb_p(HPSJ_COUNT);
  outb_p(SWG_EN | SWG_HND | CNT_HL, HPSJ_SWING);
  count |= inb_p(HPSJ_COUNT) << 8;

#if HPSCAN_DEBUG > 2
  printk("getcount = %d (0x%x)\n", count, count);
#endif

  return count;
}

/* Detect scanner and interface */
static int hpsj_detect(void)
{
  unsigned bases[] = { 0x278, 0x268 };
  int i;

#if HPSCAN_DEBUG > 0
  printk("Trying to detect HP ScanJet\n");
#endif

  for (i = 0; i < sizeof(bases) / sizeof(unsigned); i++)
  {
    iobase = bases[i];

    /* Clear swing register */
    outb_p(0, HPSJ_SWING);
    outb_p(0, HPSJ_SWING);
    
    /* Clear control register */
    outb_p(HOST_SRC | SLCT | nRESET, HPSJ_CTRL);
    outb_p(HOST_SRC | SLCT | nRESET | RDnWR, HPSJ_CTRL);
    outb_p(SCAN_SRC | SLCT | nRESET, HPSJ_CTRL);

    /* Write dummy value to swing counter */
    outb_p(CNT_WEN, HPSJ_SWING);
    outb_p(0x55, HPSJ_COUNT);
    outb_p(CNT_WEN | CNT_HL , HPSJ_SWING);
    outb_p(0x2a, HPSJ_COUNT);

    /* Try to read it back */
    outb_p(0x02, HPSJ_SWING);
    if (inb_p(HPSJ_COUNT) != 0x55)  
      continue;
    outb_p(0x04, HPSJ_SWING);
    if (inb_p(HPSJ_COUNT) & 0x3f != 0x2a)
      continue;
    
    hpsj_reset();

    /* Find memory base */
    membase = (unsigned char *)((SWG_SW(inb_p(HPSJ_SWING)) << 14) | 0xc0000);

    /* Inform the kernel about the I/O addresses used */
    request_region(iobase, 8, HPSJ_NAME);

    printk("HP ScanJet driver, $Revision: 0.1.1.5 $\n");
    printk("I/O at 0x%x, memory at 0x%x\n", iobase, (unsigned)membase);
  
    return 1;
  }

#if HPSCAN_DEBUG > 0
  printk("No HP ScanJet interface detected\n");
#endif

  return 0;
}

/* Why not put a null entry in the fops structure? I really don't know. */
static int hpsj_lseek(struct inode *inode, struct file *file,
			off_t offset, int origin)
{
  return -ESPIPE;
}

static int hpsj_read(struct inode *inode, struct file *file, 
		       char *buf, int count)
{
  unsigned long timeout;
  int done_count;
  int r;
  static int used_count, max_count;
#if HPSCAN_DEBUG
  int stop = 20;		/* to avoid infinite loops */
#endif

#if HPSCAN_DEBUG > 2
  printk(HPSJ_NAME ": read %d bytes\n", count);
#endif

  if (count == 0)
    return 0;

  r = verify_area(VERIFY_WRITE, buf, count);  
  if (r)
    return r;

  /* Check if the scanner is in read mode already */
  if ((inb_p(HPSJ_CTRL) & (nRESET | RDnWR)) != (nRESET | RDnWR))
  {                    
    timeout = jiffies + 100;
    while (jiffies < timeout)
    {
      if ((inb_p(HPSJ_CTRL) & (RDnWR | STROBE)) != (RDnWR | STROBE))
	break;
      
      current->state = TASK_INTERRUPTIBLE;
      current->timeout = jiffies + 10;
      schedule();
    }
    if (timeout <= jiffies)
    {
      printk(HPSJ_NAME ": not responding (read strobe)\n");
      hpsj_reset();
      return -EIO;
    }

    timeout = jiffies + 100;
    while (jiffies < timeout)
    {
      if (inb_p(HPSJ_STATUS) & nBUSY)
	break;
      
      current->state = TASK_INTERRUPTIBLE;
      current->timeout = jiffies + 10;
      schedule();
    }
    if (timeout <= jiffies)
    {
      printk(HPSJ_NAME ": not responding (read busy)\n");
      hpsj_reset();
      return -EIO;
    }
  
    outb_p(SWG_HND, HPSJ_SWING);
    outb_p(0, HPSJ_SWING);
    outb_p(SCAN_SRC | SLCT | nRESET, HPSJ_CTRL);
    outb_p(SCAN_SRC | SLCT | nRESET | RDnWR, HPSJ_CTRL);

    hpsj_setcount(0);
    outb_p(SWG_EN, HPSJ_SWING);

    /* nothing in the buffer */
    used_count = max_count = 0;
  }

  done_count = 0;

  while (done_count < count)
  {
    int n;

#if HPSCAN_DEBUG
    /* bail out if something seems wrong */
    if (stop-- <= 0)
    {
      printk(HPSJ_NAME ": something is wrong, stopping\n");
      break;
    }
#endif

    /* buffer is all used up, and hopefully new data is being transferred */
    if (used_count == max_count)
    {
      /* No more data to read */
      if (!(inb_p(HPSJ_SWING) & SWG_EN))
	break;

      /* wait for the transfer to finish  */
      timeout = jiffies + 10000;	/* 60000 */
      while (jiffies < timeout)
      {
	if (inb_p(HPSJ_SWING) & SWG_HND)
	  break;
	
	if (!(inb_p(HPSJ_CTRL) & BUSY_CTL))
	  outb_p(0, HPSJ_SWING);
	
	current->state = TASK_INTERRUPTIBLE;
	current->timeout = jiffies + 10;
	schedule();
      }
      if (timeout <= jiffies)
      {
	printk(HPSJ_NAME ": not responding (read data transfer)\n");
	hpsj_reset();
	return -EIO;
      }

      used_count = 0;		/* haven't used up anything yet */
      max_count = hpsj_getcount(); /* how much data did I get */

#if HPSCAN_DEBUG > 2
      printk("read a new buffer, %d bytes\n", max_count);
#endif

      if (max_count < SWG_SIZE)
        outb_p(2, HPSJ_SWING);
    }

    n = max_count - used_count;
    if (n > count - done_count)
      n = count - done_count;

    memcpy_tofs(buf + done_count, membase + used_count, n);
    
    done_count += n;
    used_count += n;

    /* Nothing left in the buffer, but there's more to come. */
    if ((used_count == max_count) && (inb_p(HPSJ_SWING) & SWG_EN))
      outb_p(SWG_EN, HPSJ_SWING);	/* ask for more data */
  }

  return done_count;
}

static int hpsj_write(struct inode *inode, struct file *file, 
			char *buf, int count)
{
  unsigned long timeout;
  int done_count;
  int r;

#if HPSCAN_DEBUG > 2
  printk(HPSJ_NAME ": write %d bytes\n", count);
#endif

  if (count == 0)
    return 0;

  r = verify_area(VERIFY_READ, buf, count);  
  if (r)
    return r;

  /* Enter write mode */
  if ((inb_p(HPSJ_CTRL) & (nRESET | RDnWR)) != nRESET)
  {                 
    outb_p(0, HPSJ_SWING);
    outb_p(0, HPSJ_SWING);
    outb_p(SCAN_SRC | SLCT | nRESET, HPSJ_CTRL);
  }

  timeout = jiffies + 1000;
  while (jiffies < timeout)
  {
    if (!(inb_p(HPSJ_CTRL) & BUSY_CTL) && (inb_p(HPSJ_STATUS) & nACK))
      break;

    current->state = TASK_INTERRUPTIBLE;
    current->timeout = jiffies + 10;
    schedule();
  }
  if (timeout <= jiffies)
  {
    outb_p(0, HPSJ_SWING);
    
    outb_p(HOST_SRC | SLCT | nRESET, HPSJ_CTRL);
    outb_p(HOST_SRC | SLCT | nRESET | RDnWR, HPSJ_CTRL);
    outb_p(SCAN_SRC | SLCT | nRESET, HPSJ_CTRL);

    current->state = TASK_INTERRUPTIBLE;
    current->timeout = jiffies + 100;
    schedule();

    if (!(inb_p(HPSJ_STATUS) & nACK))
    {
      printk(HPSJ_NAME ": not responding (write acknowledge)\n");
      hpsj_reset();
      return -EIO;
    }
  }

  if ((inb_p(HPSJ_SWING) & (SWG_EN | SWG_HND)) != (SWG_EN | SWG_HND))
  {
    outb_p(0, HPSJ_SWING);
    
    timeout = jiffies + 1000;	/* 12000 */
    while (jiffies < timeout)
    {
      if (inb_p(HPSJ_STATUS) & nBUSY)
	break;
      
      current->state = TASK_INTERRUPTIBLE;
      current->timeout = jiffies + 10;
      schedule();
    }
    if (timeout <= jiffies)
    {
      printk(HPSJ_NAME ": not responding (write busy)\n");
      hpsj_reset();
      return -EIO;
    }
  }

  done_count = 0;

  while (done_count < count)
  {
    int n = count - done_count;

    while (n > SWG_SIZE)
      n -= SWG_SIZE;

    memcpy_fromfs(membase + SWG_SIZE - n, buf + done_count, n);

    /* This might take some time... with my original ScanJet,
     * the maximum timeout neccesary is about 1000 jiffies,
     * so just to make sure I've set it to 3000
     */
    timeout = jiffies + 3000;
    while (jiffies < timeout)
    {
      if (!(inb_p(HPSJ_SWING) & SWG_HND))
	break;
      
      current->state = TASK_INTERRUPTIBLE;
      current->timeout = jiffies + 10;
      schedule();
    }
    if (timeout <= jiffies)
    {
      printk(HPSJ_NAME ": not responding (write handshake)\n");
      hpsj_reset();
      return -EIO;
    }
    
    hpsj_setcount(SWG_SIZE - n);
    
    outb_p(SWG_EN | SWG_HND, HPSJ_SWING);
    
    done_count += n;
  }

  return done_count;
}

static int hpsj_ioctl(struct inode *inode, struct file *file,
			unsigned int cmd, unsigned long arg)
{
  int retval = -EINVAL;

  switch (cmd) 
  {
  case HPSJRESET:
    hpsj_reset();
    retval = 0;
    break;
    
  case HPSJERRSTAT:
    if ((retval = verify_area(VERIFY_WRITE, (void *)arg, sizeof(long))) != 0)
      break;
    put_fs_long(inb(HPSJ_STATUS), (unsigned long *)arg);
    retval = 0;
    break;
    
  case HPSJBUFSIZE:
    if ((retval = verify_area(VERIFY_WRITE, (void *)arg, sizeof(long))) != 0)
      break;
    put_fs_long(SWG_SIZE, (unsigned long *)arg);
    retval = 0;
    break;
    
  case HPSJDEBUG:
    retval = 0;
    break;
  }
  
  return retval;
}

static int hpsj_open(struct inode *inode, struct file *file)
{
  if (MOD_IN_USE)
    return -EBUSY;

  hpsj_reset();		/* seems like a good thing to do */

  MOD_INC_USE_COUNT;
  
  return 0;
}

static void hpsj_close(struct inode * inode, struct file * file)
{
  hpsj_reset();		/* clean up afterwards */

  MOD_DEC_USE_COUNT;
}

static struct file_operations hpsj_fops = 
{
  hpsj_lseek,
  hpsj_read,
  hpsj_write,
  NULL,				/* hpsj_readdir */
  NULL,				/* hpsj_select */
  hpsj_ioctl,
  NULL,				/* hpsj_mmap */
  hpsj_open,
  hpsj_close
};

#ifndef MODULE

long hpsj_init(long kmem_start)
{
  if (!hpsj_detect())
    return kmem_start;

  if (register_chrdev(HPSJ_MAJOR, HPSJ_NAME, &hpsj_fops)) 
  {
    printk("unable to get major %d for %s\n", HPSJ_MAJOR, HPSJ_NAME);
    return kmem_start;
  }

  return kmem_start;
}

#else

char kernel_version[] = KERNEL_VERSION;

int init_module(void)
{
  if (!hpsj_detect())
    return -EIO;

  if (register_chrdev(HPSJ_MAJOR, HPSJ_NAME, &hpsj_fops)) 
  {
    printk("unable to get major %d for %s\n", HPSJ_MAJOR, HPSJ_NAME);
    return -EIO;
  }

  return 0;
}

void cleanup_module(void)
{
  if (MOD_IN_USE)
  {
    printk(HPSJ_NAME ": busy - remove delayed\n");
    return;
  }

  release_region(iobase, 8);

  unregister_chrdev(HPSJ_MAJOR, HPSJ_NAME);
}

#endif

/*
 *
 * $Log: hpscan.c,v $
 * Revision 0.1.1.5  1995/06/14  12:42:03  wingel
 * Fixed a few timeout problems
 *
 * Revision 0.1.1.4  1995/02/16  17:13:46  wingel
 * Modified to use the new modutils.
 * Moved a few of the comments to other files.
 * Moved the changelog to the end of the file.
 *
 * Revision 0.1.1.3  1995/02/07  16:42:38  wingel
 * Symbolic constants
 *
 * Revision 0.1.1.2  1995/01/28  12:33:24  wingel
 * Cleaned up a few includes.
 *
 * Revision 0.1.1.1  1995/01/28  12:20:22  wingel
 * More comments.
 * Attempts to use RCS (I still don't understand it)
 *
 * Revision 0.1  1995/01/28  12:19:28  wingel
 * First try
 *
 */

