/*
 *  linux/drivers/char/scanner.c
 *
 *  Copyright (c) 1994 Thomas Faehnle  (Thomas.Faehnle@student.uni-ulm.de)
 *
 *  This is the driver for the M105 400 dpi halftone handheld scanner driven 
 *  thru a little interface using the GI1904 chip. IRQ and DMA channel are
 *  adjusted thru software, they are defined in scanner.h.
 *  Note: This scanner is sold under various names, usually ending in "105",
 *	  the original manufacturer is Mustek.
 */

/* 
 *  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.
 *
 *  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.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  m105.c,v 1.8 1994/04/13 22:28:42 blurf Exp
 */
 
#include "m105.h"
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/malloc.h>
#include <linux/fcntl.h>	/* for f_flags bits */
#include <linux/module.h>
#define REALLY_SLOW_IO
#include <asm/io.h>
#include <asm/dma.h>
#include <asm/segment.h>

#define MODEINFO	(m105_modeinfo[modeindex])
#define SIGNAL_OCCURED	(current->signal & ~current->blocked)
#define MIN(a, b)	((a) < (b) ? (a) : (b))
#define MAX(a, b)	((a) > (b) ? (a) : (b))
#define RD_BLKEND	(rd.blkptr->blkoff + rd.blkptr->blksiz)
#define WR_BLKEND	(wr.blkptr->blkoff + wr.blkptr->blksiz)
#define RD_BUFSPC	(wr.index >= rd.index ? ((wr.index == rd.index) && !ds.buffer_empty ? \
	bufsiz : 0) + wr.index - rd.index : bufsiz - rd.index + wr.index)
#define WR_BUFSPC	(wr.index < rd.index ? rd.index - wr.index : \
	((wr.index == rd.index) && !ds.buffer_empty ? 0 : bufsiz) - wr.index + rd.index)
#define RD_ADDR		(&(rd.blkptr->data[rd.index - rd.blkptr->blkoff]))
#define WR_ADDR		(&(wr.blkptr->data[wr.index - wr.blkptr->blkoff]))
#define SELECT_TH	(MODEINFO.bpl * select_threshold)

#ifdef DEBUG_M105
#define PRINTK(x...)	{if(debug) printk(x);}
#else
#define PRINTK(x...)
#endif

static struct wait_queue *wq = NULL;
static int modeindex = 0;
static struct device_status_struct ds = { 0, 0, 0, 0 };
static struct buffer_block_struct *memroot = NULL;
static int buflines = 0, req_lines = 0, bufsiz = 0, bytes_missing = 0;
static struct buffer_pointer rd = { NULL, 0 }, wr = { NULL, 0 };
static int select_threshold = 1;
#ifdef DEBUG_M105
static int debug;
#endif

char kernel_version[]=KERNEL_VERSION;	/* needed for module installer */

static __inline__ int dma_terminal_count(unsigned int channel)
{
  if(channel <= 3)
    return inb(DMA1_STAT_REG) & (1 << channel);
  else
    return inb(DMA2_STAT_REG) & (1 << (channel & 3));
}

/* This function is evil, evil, evil.  This should be a
   _kernel_ rescheduling sleep! */
static void cold_sleep(int tics)	/* stolen from linux/drivers/net/plip.c */
{
    int start = jiffies;
    while(jiffies < start + tics)
	; /* do nothing */
    return;
}

static int m105_get_mode(void)
{
  int n, mode; 
  cold_sleep(10);
  for(n=0; n<10000; n++)
    if(!((mode=inb_p(GI1904_CONTROL)) & 0x80)) {
      mode &= GI1904_MODE_MASK;
      for(n=0; n<(sizeof(m105_modebits) / sizeof(m105_modebits[0])); n++)
	if(m105_modebits[n] == mode) {
	  modeindex=n;
	  return 0;
	}
    }
  return -EIO;
}

static __inline__ void setup_xfer(char *addr)
{
  disable_dma(GI1904_DMA);
  set_dma_mode(GI1904_DMA, DMA_MODE_READ);
  clear_dma_ff(GI1904_DMA);
  set_dma_addr(GI1904_DMA, (int) addr);
  set_dma_count(GI1904_DMA, MODEINFO.bpl);
  enable_dma(GI1904_DMA);
}

static __inline__ void scanner_standby(void)
{
  outb(STANDBY, GI1904_CONTROL);	 /* gives me a safer feeling if the */
  disable_dma(GI1904_DMA);	/* DMA channel is also disabled on the 8237 */
}

static __inline__ void scanner_operate(void)
{
  cli();
  setup_xfer(WR_ADDR);
  ds.irq_enabled = 1;
  sti();
  outb(OPERATE, GI1904_CONTROL);
  enable_dma(GI1904_DMA);
}
      
static void m105_free_mem(void)
{
  struct buffer_block_struct *tmp = memroot, *next;
  unsigned int size;
  if(!buflines) {
    PRINTK("m105_free_mem: there's nothing to free\n");
    return;
  }
  ds.irq_enabled = 0;
  scanner_standby();	/* make sure DMA xfers are really off */
  while(tmp) {
    size = tmp->blksiz + HEADERSIZ;
    next = tmp->next;
    kfree_s((char*) tmp, size);
    PRINTK("kfree_s'd %d bytes (data+header) at 0x%08x\n", size, (int) tmp);
    tmp = next;
  };
  memroot = NULL; rd.blkptr = NULL; wr.blkptr = NULL;
  buflines = 0; bufsiz = 0;
}

static int m105_get_mem(int lines)
{
  struct buffer_block_struct *tmp, **lastptr;
  unsigned int blksiz;
  if((lines * MODEINFO.bpl) > MAX_BUFSIZ)
    return -EINVAL;
  if(buflines)
    m105_free_mem();
  lastptr = &memroot;
  while(lines > 0) {
    blksiz = (((lines * MODEINFO.bpl) > MAX_BLK) ? \
	(MAX_BLK - MAX_BLK % MODEINFO.bpl) : (lines * MODEINFO.bpl));
    tmp=(struct buffer_block_struct*)kmalloc(blksiz + HEADERSIZ, GFP_KERNEL);
    if(!tmp) {
      m105_free_mem();
      return -ENOMEM;
    }
    tmp->next = NULL;
    *lastptr = tmp;
    lastptr = &(tmp->next);
    tmp->blksiz = blksiz;
    tmp->blkoff = buflines * MODEINFO.bpl;
    lines -= blksiz / MODEINFO.bpl;
    buflines += blksiz / MODEINFO.bpl;
    bufsiz += blksiz;
    PRINTK("kmalloc'ed %d bytes (data+header) at 0x%08x, %d lines, remaining: %d, offset: %d\n", \
    	blksiz+HEADERSIZ, (int) tmp, buflines, lines, tmp->blkoff);
    rd.blkptr = memroot; rd.index = 0;
    wr.blkptr = memroot; wr.index = 0;
    ds.buffer_empty = 1;
  }
  return 0;
}
     
static __inline__ int get_available_bytes(char **user_buf, int requested)
{
  int bytes_to_copy;
  int chunksiz, bytes_copied = 0;
  cli(); bytes_to_copy = MIN(requested, RD_BUFSPC); sti();
  PRINTK("get_available_bytes: %d bytes to copy (%d requested, RD_BUFSPC == %d)\n", \
  	bytes_to_copy, requested, RD_BUFSPC);
  PRINTK("get_available_bytes: rd.index == %d, wr.index == %d\n", rd.index, wr.index);
  while(bytes_to_copy > 0) {
    cli(); chunksiz = MIN(bytes_to_copy, RD_BLKEND - rd.index); sti();
    PRINTK("get_available_bytes: copying chunk of %d bytes to user space\n", chunksiz);
    PRINTK("get_available_bytes: RD_BLKEND is %d\n", RD_BLKEND);
    memcpy_tofs(*user_buf, RD_ADDR, (long) chunksiz);
    *user_buf += chunksiz;
    rd.index += chunksiz;
    bytes_copied += chunksiz;
    bytes_to_copy -= chunksiz;
    if(rd.index >= RD_BLKEND) {
      rd.blkptr = rd.blkptr->next;
      if(!rd.blkptr) {	/* last block in linked list */
	rd.blkptr=memroot;
	rd.index -= bufsiz;
      }
    }
  }
  cli(); if(rd.index == wr.index) ds.buffer_empty = 1; sti();
  PRINTK("get_available_bytes: copied total of %d bytes to user space\n", bytes_copied);
  return bytes_copied;
}

static int m105_read(struct inode *inode, struct file *file, \
	char *buf, int requested)
{
  int r, tmp = requested;
  if(!buflines && ((r=m105_get_mem(req_lines = DEFAULT_BUFLINES)) < 0))
    return r;
  requested -= get_available_bytes(&buf, requested);
  if(!ds.irq_enabled && (WR_BUFSPC >= MODEINFO.bpl)) {
    PRINTK("m105_read: I see some space in my buffer, will enable irq\n");
    scanner_operate();
  }
  if(file->f_flags & O_NONBLOCK) {
    PRINTK("m105_read: reading in O_NONBLOCK mode\n");
    return tmp - requested;
  }
  while(requested > 0) {
    bytes_missing = requested;
    PRINTK("m105_read: %d bytes are still missing, I'll sleep a bit. ds.irq_enabled == %d\n", \
    	bytes_missing, ds.irq_enabled);
    if(!ds.irq_enabled) {
      PRINTK("m105_read: re-enabling interrupts\n");
      scanner_operate();
    }
    ds.process_sleeps = 1;
    interruptible_sleep_on(&wq);
    requested -= get_available_bytes(&buf, requested);
    if(SIGNAL_OCCURED)
      return tmp - requested;
  }
  return tmp;
}

static int m105_select(struct inode *inode, struct file *file, \
	int type, select_table *st)
{
  int tmp, r;
  if(type != SEL_IN)
    return 0;
  if(!buflines && ((r=m105_get_mem(req_lines = DEFAULT_BUFLINES)) < 0))
    return r;
  cli();
  tmp=RD_BUFSPC;
  sti();
  if(tmp >= SELECT_TH) {
    PRINTK("m105_select: device is ready (%d bytes available)\n", tmp);
    return 1;			/* device is ready */
  }
  PRINTK("m105_select: device NOT ready\n");
  if(st) {
    PRINTK("m105_select: waiting for device to get ready\n");
    bytes_missing = SELECT_TH;
    ds.process_sleeps = 1;
    if(!ds.irq_enabled) {
      PRINTK("m105_select: IRQ not enabled -- will turn on scanner\n");
      scanner_operate();
    }
  }
  select_wait(&wq, st);
  return 0;
}

static void m105_interrupt(int unused_pointer_to_pt_regs_struct)
{
  static int bytes_til_wakeup;
  if(!ds.irq_enabled) {
    PRINTK("scanner.c: m105_interrupt: unexpected interrupt\n");
    return;
  }
  if(!dma_terminal_count(GI1904_DMA)) {
    PRINTK("scanner.c: m105_interrupt: DMA transfer not finished yet\n");
    return;
  }
  PRINTK("m105_interrupt: I have been called, bytes_missing == %d\n", bytes_missing);
  scanner_standby();
  if(bytes_missing > 0) {
    bytes_til_wakeup = bytes_missing;
    bytes_missing = 0;
  }
  wr.index += MODEINFO.bpl;
  if(wr.index >= WR_BLKEND) {
    wr.blkptr = wr.blkptr->next;
    if(!wr.blkptr) {
      wr.blkptr = memroot;
      wr.index -= bufsiz;
    }
  }
  if(wr.index == rd.index)
    ds.buffer_empty = 0;
  PRINTK("m105_interrupt: wr.index is now %d\n", wr.index);
  if(bytes_til_wakeup > 0)
    bytes_til_wakeup -= MODEINFO.bpl;
  if(bytes_til_wakeup <= 0)
    if(ds.process_sleeps) {
      PRINTK("m105_interrupt: the requested bytes are there, will wake up process\n");
      wake_up_interruptible(&wq);
      ds.process_sleeps = 0;
    }
  if(WR_BUFSPC >= MODEINFO.bpl) {
    PRINTK("m105_interrupt: there's still free buffer space (%d bytes), irq stays enabled\n", \
    	WR_BUFSPC);
    scanner_operate();
  } else {
    PRINTK("m105_interrupt: buffer is full -- turning off irq\n");
    ds.irq_enabled = 0;
    if(ds.process_sleeps) {
      wake_up_interruptible(&wq);
      ds.process_sleeps = 0;
    }
  }
  return;
}

static int m105_ioctl(struct inode *inode, struct file *file, \
	unsigned int cmd, unsigned long arg)
{
  int r, s;
  if(!arg)
    return -EINVAL;
  switch(cmd) {
    case HSIOCGMOD:			/* get modeinfo struct */
      if((r=m105_get_mode()) >= 0)
	memcpy_tofs((char*) arg, (char*) &MODEINFO, (long) sizeof(struct m105_modeinfo_struct));
      return r;
    case HSIOCSMOD:			/* set modeinfo struct */
      memcpy_fromfs((char*) &MODEINFO, (char*) arg, (long) sizeof(struct m105_modeinfo_struct));
      return 0;
    case HSIOCGREG:			/* get setting of misc registers */
      memcpy_tofs((char*) arg, (char*) &gi1904_regs, (long) sizeof(struct gi1904_regs_struct));
      return 0;
    case HSIOCSREG:			/* set misc registers */
      memcpy_fromfs((char*) &gi1904_regs, (char*) arg, (long) sizeof(struct gi1904_regs_struct));
      outb_p(gi1904_regs.mr0, GI1904_MISC0);
      outb_p(gi1904_regs.mr1, GI1904_MISC1);
      outb_p(gi1904_regs.mr2, GI1904_MISC2);
      return 0;
    case HSIOCSBUF:			/* set size of internal buffer */
      if(!(r=get_fs_long(arg)))
	m105_free_mem();
      else {
	if((s=m105_get_mode()) < 0)
	  return s;
	else {
	  req_lines = r;
	  m105_get_mem(req_lines);
	}
      }
      return 0;
    case HSIOCGBUF:			/* get current buffer size */
      put_fs_long((unsigned long) req_lines, arg);
      return 0;
    case HSIOCSSTH:			/* set select(2) threshold */
      select_threshold=get_fs_long(arg);
      return 0;
    case HSIOCGSTH:			/* get current select(2) threshold */
      put_fs_long((unsigned long) select_threshold, arg);
      return 0;
    default:
      return -EINVAL;
  }
}

static int m105_open(struct inode *inode, struct file *file)
{
  int r;
#ifndef DEBUG_M105
  if(MINOR(inode->i_rdev) != 0)
    return -ENODEV;
#else
  debug=MINOR(inode->i_rdev);
#endif
  if(ds.device_open)
    return -EBUSY;
  ds.irq_enabled = 0; ds.process_sleeps = 0;
  outb_p(STANDBY, GI1904_CONTROL);	 /* interrupts and DMA transfers are disabled */
  if((r=m105_get_mode()) < 0) {		 /* in standby mode */
    outb(OFF, GI1904_CONTROL);
    return r;
  }
  cold_sleep(5);
  outb_p(GI1904_IRQ_MASK | GI1904_DMA_MASK, GI1904_DMA_IRQ); /* set irq & DMA mask _before_ */
  if((r=request_irq(GI1904_IRQ, m105_interrupt))) {	     /* requesting (and enabling) */
    outb(OFF, GI1904_CONTROL); 				     /* the channels */
    return r;
  }
  if((r=request_dma(GI1904_DMA))) {
    outb(OFF, GI1904_CONTROL);
    free_irq(GI1904_IRQ);
    return r;
  }
  scanner_standby();
  MOD_INC_USE_COUNT;
  ds.device_open = 1;
  return 0;	/* device open, scanner is in standby mode now */
}

static void m105_release(struct inode *inode, struct file *file)
{
  if(buflines)
    m105_free_mem();
  free_dma(GI1904_DMA);
  free_irq(GI1904_IRQ);
  outb(OFF, GI1904_CONTROL);
  ds.device_open = 0;
  MOD_DEC_USE_COUNT;
}

static int m105_lseek(struct inode *inode, struct file *file, \
	off_t offset, int origin)
{
  return -ESPIPE;	/* in case some luser tries to seek on a scanner... */
}

static struct file_operations m105_fops = {
  m105_lseek,
  m105_read,
  NULL,		/* write */
  NULL,		/* readdir */
  m105_select,
  m105_ioctl,
  NULL,		/* mmap */
  m105_open,
  m105_release,
};

int init_module(void)
{
  int n, nodev = 0;
  printk("M105 handheld scanner driver  version 0.1.8\n");
  if(register_chrdev(M105_SCANNER_MAJOR, M105_SCANNER_NAME, &m105_fops)) {
    printk("m105.c: cannot register major number\n");
    return -EIO;
  }
  outb_p(OFF, GI1904_CONTROL);
  for(n=0; n<10000; n++)
    nodev |= (inb_p(GI1904_IDENT) != GI1904_MAGIC);
  if(nodev) {
    printk("scanner.c: GI1904 scanner interface missing or broken\n");
    return -EIO;
  }
  return 0;
}

void cleanup_module(void)
{
  printk("Removing M105 handheld scanner driver from memory\n");
  if(MOD_IN_USE)
    printk("m105.c: device is busy, delaying driver removal\n");
  if(unregister_chrdev(M105_SCANNER_MAJOR, M105_SCANNER_NAME))
    printk("m105.c: unregister_chrdev() failed\n");
}
