/*
 *  gs105.c - Scanner-driver for Genius GS-B105G scanner
 *
 *  Copyright (c) 1994 Willem Dantuma (willemd@knoware.nl)
 *  Most parts Copyright (c) 1994 Andreas Beck (becka@hp.rz.uni-duesseldorf.de)
 *  Parts copyright (c) 1994 Thomas Faehnle (Thomas.Faehnle@student.uni-ulm.de)
 *
 *  This is the driver for the GENIUS GS-B105G 400 dpi halftone handheld 
 *  scanner driven thru a small interface . IRQ and DMA channel are
 *  adjusted thru software, they are defined in gs105.h.
 */

/* 
 *  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.
 *
 *  gs105.c,v 0.0.1 1994/08/13
 */
 
#include "gs105.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 SIGNAL_OCCURED	(current->signal & ~current->blocked)
#define MIN(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	(hw_modeinfo.bpl * select_threshold)

#ifdef DEBUG_GS105
#define PRINTK(x...)	do{if(debug) printk(x);}while(0)
#else
#define PRINTK(x...)
#endif

static struct modeinfo_struct gs105_modeinfo = 
{ 200, 200, 		/* xdpi,ydpi  */
  SCM_UNKNOWN, 		/* sc_mode    */
  0,			/* depth      */
  0,			/* bpl        */
  0,0,			/* left,right */
  0,0,			/* top,bottom */
  0,0,0,0,		/* bright,... */
  0			/* options    */
};

static struct hw_modeinfo_struct 
		{ int xdpi,ydpi, sc_mode,depth,scm,bpl,bps; } 
  hw_modeinfo = {      200, 200,SCM_MONO,    0,0,0x68,  1  };

static struct scanner_type gs105_scanner_type = { "GENIUS","GS-B105G"};

struct scanner_capabilities scc_gs105={

	SCC_HARDSWITCH|SCC_EMULATE,	/* O_monochrome mode */
	SCC_HARDSWITCH|SCC_EMULATE,	/* O_greyscale  mode */
	SCC_EMULATE,			/* O_true-color mode */

	SCC_SOFTDETECT|SCC_HARDSWITCH,	/* Options for x resolution */
	SCC_SOFTDETECT|SCC_HARDSWITCH,	/* Options for y resolution */

	SCC_HARDSWITCH,			/* Options for brightness */
	{0,0},
	0,				/* Options for contrast */
	{0,0},
	0,				/* Options for hue */
	{0,0},
	0,				/* Options for saturation */
	{0,0},

	105,				/* Width in mm */
	0,				/* Length in mm (unlimited) */
	0				/* no option */
};

static unsigned char bitmask[8]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};

static struct wait_queue *wq = NULL;
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_GS105
static int debug;
#endif

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

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

static void wait_ready(void)		/* Wait until Scanner is ready */
{ 
  while(inb(gsiob[GS_IOBANK].rg[GS_STATUS])&GS_READY_MSK);
}


static void start_scanning(void)	/* <== */
{ 
  wait_ready();
}

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));
}

static __inline__ void setup_xfer(char *addr)
{
  disable_dma(GS_DMA);
  set_dma_mode(GS_DMA, DMA_MODE_READ);
  clear_dma_ff(GS_DMA);
  set_dma_addr(GS_DMA, (int) addr);
  set_dma_count(GS_DMA, hw_modeinfo.bpl);
  /* enable_dma(GS_DMA); This should be done by the calling function
                             to gain some more control ... */
}

static void scanner_standby(void)
{
  ds.irq_enabled=0;
  outb(0,gsiob[GS_IOBANK].rg[GS_CLEAR]);
  outb(GS_OFF,gsiob[GS_IOBANK].rg[GS_CONTROL]);
  disable_dma(GS_DMA);
}

static void scanner_operate(void)
{
  start_scanning();
  cli();
  setup_xfer(WR_ADDR);
  ds.irq_enabled = 1;
  outb(0,gsiob[GS_IOBANK].rg[GS_CLEAR]);
  outb(GS_ON,gsiob[GS_IOBANK].rg[GS_CONTROL]);
  sti();
  enable_dma(GS_DMA);
}
      
/* This sets up gs105-Modeinfo */
static void gs105_get_mode(void)
{ 
  int status;
 
  if( !ds.irq_enabled ) outb(GS_ON,gsiob[GS_IOBANK].rg[GS_CONTROL]); /* Turn on scanner */ 

  while((status = inb(gsiob[GS_IOBANK].rg[GS_STATUS]))&GS_READY_MSK);

  if( !ds.irq_enabled ) outb(GS_OFF,gsiob[GS_IOBANK].rg[GS_CONTROL]); /* Turn off scanner */

  /* Set bits / pixel */

  if (status&0x08 )
    hw_modeinfo.bps = 8;
  else
    hw_modeinfo.bps = 1;  

  /* Set resolution and bytes / scanline */

  if ( hw_modeinfo.bps == 8 )
  {
    if((status&0x20)&&(status&0x04))
      {hw_modeinfo.xdpi = GS_100;hw_modeinfo.ydpi = GS_100;
       hw_modeinfo.bpl = GS_BPL_100G;}
    else if ((status&0x20)&&(!(status&0x04)))
      {hw_modeinfo.xdpi = GS_200;hw_modeinfo.ydpi = GS_200;
       hw_modeinfo.bpl = GS_BPL_200G;}
    else if ((!(status&0x20))&&(status&0x04))
      {hw_modeinfo.xdpi = GS_300;hw_modeinfo.ydpi = GS_300;
       hw_modeinfo.bpl = GS_BPL_300G;}
    else if ((!(status&0x20))&&(!(status&0x04)))
      {hw_modeinfo.xdpi = GS_400;hw_modeinfo.ydpi = GS_400;
       hw_modeinfo.bpl = GS_BPL_400G;}
  } 
  else
  {
    if((status&0x20)&&(status&0x04))
      {hw_modeinfo.xdpi = GS_100;hw_modeinfo.ydpi = GS_100;
       hw_modeinfo.bpl = GS_BPL_100;}
    else if ((status&0x20)&&(!(status&0x04)))
      {hw_modeinfo.xdpi = GS_200;hw_modeinfo.ydpi = GS_200;
       hw_modeinfo.bpl = GS_BPL_200;}
    else if ((!(status&0x20))&&(status&0x04))
      {hw_modeinfo.xdpi = GS_300;hw_modeinfo.ydpi = GS_300;
       hw_modeinfo.bpl = GS_BPL_300;}
    else if ((!(status&0x20))&&(!(status&0x04)))
      {hw_modeinfo.xdpi = GS_400;hw_modeinfo.ydpi = GS_400;
       hw_modeinfo.bpl = GS_BPL_400;}
  }


  /* Now set the rest of the Modeinfo-struct */
  
  switch(hw_modeinfo.bps)
  {case 1 :{hw_modeinfo.sc_mode=SCM_MONO;hw_modeinfo.scm=0;}break;
   case 8 :{hw_modeinfo.sc_mode=SCM_GREY;hw_modeinfo.scm=1 ;}break;
   default:hw_modeinfo.sc_mode=SCM_UNKNOWN;break;}
   
  /* Here comes calculation for gs105_modeinfo (virtual parameters) */
   
  gs105_modeinfo.xdpi=hw_modeinfo.xdpi; 
  gs105_modeinfo.ydpi=hw_modeinfo.ydpi;

  if (gs105_modeinfo.sc_mode<SCM_MONO||
      gs105_modeinfo.sc_mode>SCM_TRUE)
      gs105_modeinfo.sc_mode=hw_modeinfo.sc_mode;
  
  switch(hw_modeinfo.bps)
  { case 1 :gs105_modeinfo.depth=0x00;break;
    case 8 :gs105_modeinfo.depth=0xff;break; }

  switch(gs105_modeinfo.sc_mode)
  {case SCM_MONO:switch(hw_modeinfo.bps)
                 {case  1:gs105_modeinfo.bpl=hw_modeinfo.bpl   ;break;
		  case  8:gs105_modeinfo.bpl=hw_modeinfo.bpl>>3;break;}
		 break;
   case SCM_GREY:switch(hw_modeinfo.bps)
                 {case  1:gs105_modeinfo.bpl= hw_modeinfo.bpl<<3   ;break;
		  case  8:gs105_modeinfo.bpl= hw_modeinfo.bpl      ;break;}
		 break;
   case SCM_TRUE:switch(hw_modeinfo.bps)
                 {case  1:gs105_modeinfo.bpl=(hw_modeinfo.bpl<<3)  *3;break;
		  case  8:gs105_modeinfo.bpl=(hw_modeinfo.bpl   )  *3;break;}
		 break;
  }

  gs105_modeinfo.left=0;
  switch(gs105_modeinfo.sc_mode)
  { case SCM_MONO:gs105_modeinfo.right=gs105_modeinfo.bpl<<3;break;
    case SCM_GREY:gs105_modeinfo.right=gs105_modeinfo.bpl;break;
    case SCM_TRUE:gs105_modeinfo.right=gs105_modeinfo.bpl/3;break;
  }
  gs105_modeinfo.top=gs105_modeinfo.bottom=0;
  
  gs105_modeinfo.bright=gs105_modeinfo.contrast=
  gs105_modeinfo.hue   =gs105_modeinfo.sat     =0;
  
  gs105_modeinfo.options=0;
  outb(0,gsiob[GS_IOBANK].rg[GS_CLEAR]);
  outb(hw_modeinfo.scm,gsiob[GS_IOBANK].rg[GS_MODE]);

}

static void gs105_free_mem(void)
{
  struct buffer_block_struct *tmp = memroot, *next;
  unsigned int size;
  if(!buflines) {
    PRINTK("gs105_free_mem: there's nothing to free\n");
    return;
  }
  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 gs105_get_mem(int lines)
{
  struct buffer_block_struct *tmp, **lastptr;
  unsigned int blksiz;
  if((lines * hw_modeinfo.bpl) > MAX_BUFSIZ)
    return -EINVAL;
  if(buflines)
    gs105_free_mem();
  lastptr = &memroot;
  while(lines > 0) {
    blksiz = (((lines * hw_modeinfo.bpl) > MAX_BLK) ? \
	(MAX_BLK - MAX_BLK % hw_modeinfo.bpl) : (lines * hw_modeinfo.bpl));
    tmp=(struct buffer_block_struct*)kmalloc(blksiz + HEADERSIZ, GFP_KERNEL);
    if(!tmp) {
      gs105_free_mem();
      return -ENOMEM;
    }
    tmp->next = NULL;
    *lastptr = tmp;
    lastptr = &(tmp->next);
    tmp->blksiz = blksiz;
    tmp->blkoff = buflines * hw_modeinfo.bpl;
    lines -= blksiz / hw_modeinfo.bpl;
    buflines += blksiz / hw_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 unsigned char copy_buffer[COPY_BUFFER];
static int inbuffer=0;

static int refill_buffer(void)
{ int x;
  if (inbuffer) {PRINTK("refill_buffer: buffer not empty !\n");return -1;}
  while(!inbuffer)
  { if (RD_BUFSPC<hw_modeinfo.bpl) return 0;
    switch(hw_modeinfo.bps)
    {case 1:switch(gs105_modeinfo.sc_mode)
            {case SCM_MONO: for(x=0;x<hw_modeinfo.bpl;x++) 
                              copy_buffer[x]=RD_ADDR[x];
                            inbuffer=x;
                            break;
             case SCM_GREY: for(x=0;x<(hw_modeinfo.bpl<<3);x++) 
                              copy_buffer[x]=
                                (RD_ADDR[x>>3]&bitmask[x&7]) ? 255 : 0;
                            inbuffer=x;
                            break;
             case SCM_TRUE: for(x=0;x<(hw_modeinfo.bpl<<3);x++) 
                              copy_buffer[x+x+x  ]=
                              copy_buffer[x+x+x+1]=
                              copy_buffer[x+x+x+2]=
                                (RD_ADDR[x>>3]&bitmask[x&7]) ? 255 : 0;
			    inbuffer=x+x+x;
                            break;
            } break;
     case 8:switch(gs105_modeinfo.sc_mode)
            {case SCM_MONO: for(x=0;x<(hw_modeinfo.bpl>>3);x++) 
                              copy_buffer[x]=0;
            		    for(x=0;x<hw_modeinfo.bpl;x++) 
                              copy_buffer[x>>3]|=
                                (RD_ADDR[x]>127) ? bitmask[x&7] : 0;
                            inbuffer=x>>3;
                            break;
             case SCM_GREY: for(x=0;x<hw_modeinfo.bpl;x++) 
                              copy_buffer[x]=RD_ADDR[x];
                            inbuffer=x;
                            break;
             case SCM_TRUE: for(x=0;x<hw_modeinfo.bpl;x++) 
                              copy_buffer[x+x+x  ]=
                              copy_buffer[x+x+x+1]=
                              copy_buffer[x+x+x+2]=RD_ADDR[x];
			    inbuffer=x+x+x;
                            break;
            } break;
    default:PRINTK("refill_buffer: unknown bps-value !\n");
	    return -1; /* Unknown mode ! Bye ! */
    }
    rd.index += hw_modeinfo.bpl;
    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();
  }
  return 0;
}
     
static __inline__ int get_available_bytes(char **user_buf, int requested)
{
  int bytes_to_copy,h;
  int chunksiz, bytes_copied = 0;

  bytes_to_copy = requested;
  PRINTK("get_available_bytes: %d bytes requested\n",requested);
  PRINTK("get_available_bytes: rd.index == %d, wr.index == %d\n", rd.index, wr.index);

  while(bytes_to_copy > 0) {
    if (!inbuffer) 
      if (refill_buffer()) {
        PRINTK("get_available_bytes: refill_buffer failed.\n");break;
      }
    if (!inbuffer) {
      PRINTK("get_available_bytes: inbuffer empty.\n");break;
    }
    chunksiz = MIN(bytes_to_copy, inbuffer);
    memcpy_tofs(*user_buf, copy_buffer , (long) chunksiz);
    *user_buf     += chunksiz;
    bytes_copied  += chunksiz;
    bytes_to_copy -= chunksiz;
    inbuffer      -= chunksiz;
    for(h=0;h<inbuffer;h++) copy_buffer[h]=copy_buffer[h+chunksiz];
    PRINTK("get_available_bytes: copying chunk of %d bytes to user space\n", chunksiz);
  }
  PRINTK("get_available_bytes: copied total of %d bytes to user space\n", bytes_copied);
  return bytes_copied;
}

static int gs105_read(struct inode *inode, struct file *file, \
	char *buf, int requested)
{
  int r, tmp = requested;
  if(!buflines && ((r=gs105_get_mem(req_lines = DEFAULT_BUFLINES)) < 0))
    return r;
  requested -= get_available_bytes(&buf, requested);
  if(!ds.irq_enabled && (WR_BUFSPC >= hw_modeinfo.bpl)) {
    PRINTK("gs105_read: I see some space in my buffer, will enable irq\n");
    scanner_operate();
  }
  if(file->f_flags & O_NONBLOCK) {
    PRINTK("gs105_read: reading in O_NONBLOCK mode\n");
    return tmp - requested;
  }
  while(requested > 0) {
    bytes_missing =   requested*hw_modeinfo.bpl/gs105_modeinfo.bpl;
    
    PRINTK("gs105_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("gs105_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 gs105_select(struct inode *inode, struct file *file, \
	int type, select_table *st)
{
  int tmp, r;
  if(type != SEL_IN)
    return 0;
  if(!buflines && ((r=gs105_get_mem(req_lines = DEFAULT_BUFLINES)) < 0))
    return r;
  cli();
  tmp=RD_BUFSPC;
  sti();
  if(tmp >= SELECT_TH) {
    PRINTK("gs105_select: device is ready (%d bytes available)\n", tmp);
    return 1;			/* device is ready */
  }
  PRINTK("gs105_select: device NOT ready\n");
  if(st) {
    PRINTK("gs105_select: waiting for device to get ready\n");
    bytes_missing = SELECT_TH;
    ds.process_sleeps = 1;
    if(!ds.irq_enabled) {
      PRINTK("gs105_select: IRQ not enabled -- will turn on scanner\n");
      scanner_operate();
    }
  }
  select_wait(&wq, st);
  return 0;
}

static void gs105_interrupt(int unused_pointer_to_pt_regs_struct)
{
  static int bytes_til_wakeup;

  if(!ds.irq_enabled) {
    PRINTK("gs105.c: gs105_interrupt: unexpected interrupt\n");
    return;
  }

  outb(0,gsiob[GS_IOBANK].rg[GS_CLEAR]);

  if(!dma_terminal_count(GS_DMA)) {
    PRINTK("gs105.c: gs105_interrupt: DMA transfer not finished yet\n");
    return;
  }

  PRINTK("gs105_interrupt: I have been called, bytes_missing == %d\n", bytes_missing);

  if(bytes_missing > 0) {
    bytes_til_wakeup = bytes_missing;
    bytes_missing = 0;
  }

  wr.index += hw_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; /* Shouldn't happen here ...*/
  PRINTK("gs105_interrupt: wr.index is now %d\n", wr.index);

  if(bytes_til_wakeup > 0)
    bytes_til_wakeup -= hw_modeinfo.bpl;

  if((bytes_til_wakeup <= 0 || WR_BUFSPC <= hw_modeinfo.bpl )
     && ds.process_sleeps) {
      PRINTK("gs105_interrupt: the requested bytes are there, will wake up process\n");
      wake_up_interruptible(&wq);
      ds.process_sleeps = 0;
    }

  if(WR_BUFSPC >= hw_modeinfo.bpl) {
    PRINTK("gs105_interrupt: there's still free buffer space (%d bytes), irq stays enabled\n", \
    	WR_BUFSPC);
  } else {
    PRINTK("gs105_interrupt: buffer is full -- turning off irq\n");
    scanner_standby();
    if(ds.process_sleeps) {
      wake_up_interruptible(&wq);
      ds.process_sleeps = 0;
    }
    return;
  }
  setup_xfer(WR_ADDR);
  enable_dma(GS_DMA);
  return;
}

static int gs105_ioctl(struct inode *inode, struct file *file, \
	unsigned int cmd, unsigned long arg)
{
  int r;
  unsigned long tmp;
  struct registers_struct rs;
  if(!arg)
    return -EINVAL;
  switch(cmd) {
    case HSIOCGOPT:			/* get scanner_options struct */
      return 0;
    case HSIOCGSCC:			/* get scanner_capabilities struct */
      memcpy_tofs((char*) arg, (char*) &scc_gs105, (long) sizeof(struct scanner_capabilities));
      return 0;
    case HSIOCGSCT:			/* get scanner_type struct */
      memcpy_tofs((char*) arg, (char*) &gs105_scanner_type, (long) sizeof(struct scanner_type));
      return 0;
    case HSIOCSMOD:			/* set modeinfo struct */
      memcpy_fromfs((char*) &gs105_modeinfo, (char*) arg, (long) sizeof(struct modeinfo_struct));
      gs105_get_mode();
      memcpy_tofs((char*) arg, (char*) &gs105_modeinfo, (long) sizeof(struct modeinfo_struct));
      return 0;
    case HSIOCGMOD:			/* get modeinfo struct */
      gs105_get_mode();
      memcpy_tofs((char*) arg, (char*) &gs105_modeinfo, (long) sizeof(struct modeinfo_struct));
      return 0;
    case HSIOCGREG:			/* get setting of misc registers */
      memcpy_fromfs((char*) &rs, (char*) arg, (long) sizeof(struct registers_struct));
      if (rs.regnum<0||rs.regnum>0x0f) return -EACCES;
      rs.data=inb_p(gsiob[GS_IOBANK].rg[rs.regnum]);
      memcpy_tofs((char*) arg, (char*) &rs, (long) sizeof(struct registers_struct));
      return 0;
    case HSIOCSREG:			/* set misc registers */
      memcpy_fromfs((char*) &rs, (char*) arg, (long) sizeof(struct registers_struct));
      if (rs.regnum<0||rs.regnum>0x0f) return -EACCES;
      outb_p(rs.data, gsiob[GS_IOBANK].rg[rs.regnum]);
      return 0;
    case HSIOCSBUF:			/* set size of internal buffer */
      if(!(r=get_fs_long(arg)))
	gs105_free_mem();
      else {
	gs105_get_mode();
	req_lines = r;
	gs105_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;
    case HSIOCGSIB:			/* get number of lines in buffer */
      tmp=RD_BUFSPC/hw_modeinfo.bpl;
      put_fs_long(tmp , arg);
      return 0;
    default:
      return -EINVAL;
  }
}

static int gs105_open(struct inode *inode, struct file *file)
{
  int r;

  if(MINOR(inode->i_rdev) != 0)
    return -ENODEV;

  if(ds.device_open)
    return -EBUSY;

  ds.irq_enabled = 0; ds.process_sleeps = 0;

  gs105_modeinfo.xdpi=
  gs105_modeinfo.ydpi=
  gs105_modeinfo.sc_mode=
  gs105_modeinfo.depth=
  gs105_modeinfo.bpl=
  gs105_modeinfo.left=
  gs105_modeinfo.right=
  gs105_modeinfo.top=
  gs105_modeinfo.bottom=
  gs105_modeinfo.bright=
  gs105_modeinfo.contrast=
  gs105_modeinfo.hue=
  gs105_modeinfo.sat=
  /*gs105_modeinfo.options= Keep options ? What's your opinion ? */
  0;	/* Make sure settings are maximum */

  gs105_get_mode(); 

  if((r=request_irq(GS_IRQ, gs105_interrupt,SA_INTERRUPT,"GS-B105G scanner")))	/* request IRQ */
    return r;

  if((r=request_dma(GS_DMA))) {
    free_irq(GS_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 gs105_close(struct inode *inode, struct file *file)
{
  scanner_standby();
  wait_ready();
  if(buflines)
    gs105_free_mem();
  free_dma(GS_DMA);
  free_irq(GS_IRQ);
  ds.device_open = 0;
  MOD_DEC_USE_COUNT;
}

static int gs105_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 gs105_fops = {
  gs105_lseek,
  gs105_read,
  NULL,		/* write */
  NULL,		/* readdir */
  gs105_select,
  gs105_ioctl,
  NULL,		/* mmap */
  gs105_open,
  gs105_close,
};

static int check_4_scanner(void)
{ 
  unsigned int i,h=0,h2;
 
  outb(GS_OFF,gsiob[GS_IOBANK].rg[GS_CONTROL]);
  outb(5,gsiob[GS_IOBANK].rg[GS_CONTROL]);
  outb(5,gsiob[GS_IOBANK].rg[GS_CLEAR]);
  outb(0,gsiob[GS_IOBANK].rg[GS_DMA_IRQ]);
  outb(8,gsiob[GS_IOBANK].rg[GS_DMA_IRQ]);
  for(i=0;i<8;i++)
  {
    outb(0,gsiob[GS_IOBANK].rg[GS_CLEAR]);
    h2 = inb(gsiob[GS_IOBANK].rg[GS_STATUS]);
    h2 &= 0x01; h =h<<1; h|=h2;
  }
  if( h != 0x66 ) return(-ENODEV); 
  outb(0,gsiob[GS_IOBANK].rg[GS_CLEAR]);
  outb(GS_OFF,gsiob[GS_IOBANK].rg[GS_CONTROL]);
  outb(GS_IRQ_MSK|GS_DMA_MSK,gsiob[GS_IOBANK].rg[GS_DMA_IRQ]);
  return(0);/* Scanner is there and operative */
}

int init_module(void)
{
  
#ifdef DEBUG_GS105
  debug = 1;
#endif 

  printk("GENIUS GS-B105G handheld scanner driver  version 0.0.1\n");
  if (check_4_scanner())
  { printk("gs105.c: scanner interface not found\n");
    return -EIO;
  }
  else
    printk("GS-B105G scanner detected\n");
  if(register_chrdev(GS105_SCANNER_MAJOR, GS105_SCANNER_NAME, &gs105_fops)) {
    printk("gs105.c: cannot register major number\n");
    return -EIO;
  }
  return 0;
}

void cleanup_module(void)
{
  printk("Removing GENIUS GS-B105G handheld scanner driver from memory\n");
  if(MOD_IN_USE)
    printk("gs105.c: device is busy, delaying driver removal\n");
  if(unregister_chrdev(GS105_SCANNER_MAJOR, GS105_SCANNER_NAME))
    printk("gs105.c: unregister_chrdev() failed\n");
}
