/*
 *  logi32.c - Scanner-driver for Logitech Scanman,Scanman+(32) and Scanman256
 *
 *  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 LOGITECH SCANMAN+ 400 dpi halftone handheld 
 *  scanner driven thru a LOGITECH interface . IRQ and DMA channel are
 *  adjusted thru software, they are defined in scanner.h.
 *
 * PINT ioctl interface copyright (C) 1994 Kenneth Stailey ken@spacenet.com
 *
 */

/* 
 *  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.
 *
 *  logi32.c,v 0.0.2 1994/06/13 23:29:14
 */
 
#include <scanner.h>

#ifdef LOGI32_PINT
#include <sys/scanio.h>
#endif

#include "logi32.h"


#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.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 */

#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_LOGI32
#define PRINTK(x...)	do{if(LOGI_DEBUG) printk(x);}while(0)
#else
#define PRINTK(x...)
#endif

static struct { int type,ready_mask,tf2,phys_width;} 
	sct  ={        2,      0x40,  0,      0x67 };
static struct scanner_type sctxt[4]={{"LOGITECH","Unknown"},
				     {"LOGITECH","Scanman"},
				     {"LOGITECH","Scanman32"},
				     {"LOGITECH","Scanman256"}};

static struct modeinfo_struct logi_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, bpl,bps; } 
  hw_modeinfo = {      200, 200,SCM_MONO,    0,0x67,  1  };


static struct scanner_capabilities scc_logi32={

	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) */
	1				/* 1 option (Dithering) */
};

static struct scanner_option opt_logi32[1]={SCC_HARDSWITCH,"Dithering"};

static struct scanner_capabilities scc_logi256={

	SCC_SOFTDETECT|SCC_HARDSWITCH|SCC_EMULATE,	/* O_monochrome mode */
	SCC_SOFTDETECT|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 options for logi256 */
};

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;

/* Now for the exported variables - setable via the new style insmod */

#ifdef DEBUG_LOGI32
int LOGI_DEBUG =0;
#endif
int LOGI_MAJOR =LOGI32_SCANNER_MAJOR;
int LOGI_IOBASE=LOGI32_IOBASE;
int LOGI_IRQ   =LOGI32_IRQ;
int LOGI_DMA   =LOGI32_DMA;

/* End of exported variables */

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

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

static void wait_ready(void)		/* Wait until Scanner is ready */
{ while(inb(LOGI_IOBASE)&sct.ready_mask);
  if (sct.type==1)
  { while(inb(LOGI_IOBASE)&0x40) inb(LOGI_IOBASE+2); }
  else
  { while(inb(LOGI_IOBASE)&0x80) 
    { outb(1,LOGI_IOBASE);inb(LOGI_IOBASE+1); }
  }
}

static void light_on(void)		/* Turn on the LEDs */
{ int start;
  outb(0,LOGI_IOBASE);
  outb(inb(LOGI_IOBASE+1)|0x01,LOGI_IOBASE+1);
  /* there needs to be a small delay here or the settings will not be
     recognized correctly ... */
  start=jiffies;while(jiffies < start + 7);/*Delay at least 6 tics .*/
}

static void light_off(void)		/* Turn them off */
{ outb(0,LOGI_IOBASE);
  outb(inb(LOGI_IOBASE+1)&0xfe,LOGI_IOBASE+1);
}

static void start_scanning(void)	/* <== */
{ if (sct.type==1)
  { outb(6,LOGI_IOBASE);}
  else
  { light_on();
    if (sct.tf2)
    { outb(1,LOGI_IOBASE);inb(LOGI_IOBASE+1);
      outb(1,LOGI_IOBASE);inb(LOGI_IOBASE+1);
    }
  }
  if (sct.type==3)
  { outb(0x11,LOGI_IOBASE);
    if ((inb(LOGI_IOBASE+1)&0x3f)==0x3f)
       PRINTK("start_scanning: Scanner not attached ?"); 
  }

  /*The transfer width may be set to ANY value below bpl ... */
  if (sct.type==1)
  { outb(hw_modeinfo.bpl,LOGI_IOBASE+6); /* Setup transfer width */
  } else
  { outb(8,LOGI_IOBASE);outb( hw_modeinfo.bpl    &0xff,LOGI_IOBASE+1);
    outb(9,LOGI_IOBASE);outb((hw_modeinfo.bpl>>8)&0xff,LOGI_IOBASE+1);
  }
  wait_ready();

  if (sct.type==1)
  { inb(LOGI_IOBASE+3); } 
  else
  { outb(5,LOGI_IOBASE);inb(LOGI_IOBASE+1); }
}

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(LOGI_DMA);
  set_dma_mode(LOGI_DMA, DMA_MODE_READ);
  clear_dma_ff(LOGI_DMA);
  set_dma_addr(LOGI_DMA, (int) addr);
  set_dma_count(LOGI_DMA, hw_modeinfo.bpl);
}

static void scanner_standby(void)
{
  ds.irq_enabled=0;
  if (sct.type!=1) /* deactivate hardware */
  { outb(0,LOGI_IOBASE);
    outb(0xfd&inb(LOGI_IOBASE+1),LOGI_IOBASE+1);
  }  				/* gives me a safer feeling if the */
  disable_dma(LOGI_DMA);	/* DMA channel is also disabled on the 8237 */

  if (sct.type==1) /* deactivate hardware */
  { outb(2,LOGI_IOBASE);}
  else
  { light_off();
    outb(3,LOGI_IOBASE);
    outb(0x77&inb(LOGI_IOBASE+1),LOGI_IOBASE+1);
    outb(4,LOGI_IOBASE);outb(0x10,LOGI_IOBASE+1);
  }
}

static void scanner_operate(void)
{
  start_scanning();

  cli();
  setup_xfer(WR_ADDR);
  ds.irq_enabled = 1;
  sti();

  if (sct.type!=1)
  { outb(4,LOGI_IOBASE);outb(0,LOGI_IOBASE+1);
    outb(3,LOGI_IOBASE);
    outb(0x88|inb(LOGI_IOBASE+1),LOGI_IOBASE+1);/* 0x8 is IRQ-enable */
    light_on();
  }

  enable_dma(LOGI_DMA);

  if (sct.type==1) /* activate the hardware */
  { outb(0x1e,LOGI_IOBASE); /* 0x10 is IRQ-Flag */
  } else
  { outb(0,LOGI_IOBASE);
    outb(0x02|inb(LOGI_IOBASE+1),LOGI_IOBASE+1);
  }
}
      
/* This sets up Logi-Modeinfo */
static void logi32_get_mode(void)
{ static int hw_res=8;
  int irq_enab;
  
  if ((irq_enab=ds.irq_enabled)) scanner_standby(); /* STOP Scanner ! */

  if (sct.type!=1) 
  { if (sct.tf2)
    { outb(0,LOGI_IOBASE);
      outb(inb(LOGI_IOBASE+1)|0x02,LOGI_IOBASE+1);
      outb(8,LOGI_IOBASE);
      outb(2,LOGI_IOBASE+1);}

    light_on();
    if (sct.type==3)
    { outb(0x10,LOGI_IOBASE);
      hw_res=(((inb(LOGI_IOBASE+1)^0xff)&0x60)>>3)+4;
    } 
    else
    { light_off();
      outb(0,LOGI_IOBASE);
      hw_res=(inb(LOGI_IOBASE+1)&0x0c)+4;
    }
  }
  /* Here comes what was SUB_get_bps in DOS */
  if (sct.type==3)
  { static bpstab[4]={1,4,6,8};
    outb(0x10,LOGI_IOBASE);
    hw_modeinfo.bps=bpstab[(inb(LOGI_IOBASE+1)^0xff)&0x3];
    outb(0,LOGI_IOBASE);
    outb(inb(LOGI_IOBASE+1)&0xfd,LOGI_IOBASE+1);
    outb(8,LOGI_IOBASE);
    outb(0,LOGI_IOBASE+1);
    light_off();
  } else hw_modeinfo.bps=1;

  /* Here comes what was SUB_calc_by_per_scanl in DOS */

  /* If the Scanman256 selects 6bit resolution it transfers 8 bits per
     dot anyway ... */
  if (hw_modeinfo.bps==6) hw_modeinfo.bpl= sct.phys_width*hw_res;
  else 			  hw_modeinfo.bpl=(sct.phys_width*hw_res*
  					   hw_modeinfo.bps)/8;

  /* Now set the rest of the Modeinfo-struct */
  
  hw_modeinfo.xdpi=
  hw_modeinfo.ydpi=hw_res*25; /* 4 means 100dpi,8 200,... */
  
  if (sct.type!=3 && (logi_modeinfo.options&1))
     hw_modeinfo.bps=-6;/* 6 bit dithering */

  switch(hw_modeinfo.bps)
  {case 1 :hw_modeinfo.sc_mode=SCM_MONO;break;
   case 4 :
   case -6:
   case 6 :
   case 8 :hw_modeinfo.sc_mode=SCM_GREY;break;
   default:hw_modeinfo.sc_mode=SCM_UNKNOWN;break;}
   
  /* Here comes calculation for logi_modeinfo (virtual parameters) */
   
  logi_modeinfo.xdpi=hw_modeinfo.xdpi; 
  if (hw_modeinfo.bps==-6) logi_modeinfo.xdpi/=6;
  logi_modeinfo.ydpi=hw_modeinfo.ydpi;
  if (hw_modeinfo.bps==-6) logi_modeinfo.ydpi/=6;

  if (logi_modeinfo.sc_mode<SCM_MONO||
      logi_modeinfo.sc_mode>SCM_TRUE)
      logi_modeinfo.sc_mode=hw_modeinfo.sc_mode;
  
  switch(hw_modeinfo.bps)
  { case 1 :logi_modeinfo.depth=0x00;break;
    case 4 :logi_modeinfo.depth=0x0f;break;
    case -6:logi_modeinfo.depth= 6*6;break;
    case 6 :logi_modeinfo.depth=0x3f;break;
    case 8 :logi_modeinfo.depth=0xff;break; }

  switch(logi_modeinfo.sc_mode)
  {case SCM_MONO:switch(hw_modeinfo.bps)
                 {case  1:logi_modeinfo.bpl=hw_modeinfo.bpl   ;break;
		  case  4:logi_modeinfo.bpl=hw_modeinfo.bpl>>2;break;
		  case -6:logi_modeinfo.bpl=hw_modeinfo.bpl/6 ;break;
		  case  6:logi_modeinfo.bpl=hw_modeinfo.bpl>>3;break;
		  case  8:logi_modeinfo.bpl=hw_modeinfo.bpl>>3;break;}
		 break;
   case SCM_GREY:switch(hw_modeinfo.bps)
                 {case  1:logi_modeinfo.bpl= hw_modeinfo.bpl<<3   ;break;
		  case  4:logi_modeinfo.bpl= hw_modeinfo.bpl<<1   ;break;
		  case -6:logi_modeinfo.bpl=(hw_modeinfo.bpl<<3)/6;break;
		  case  6:logi_modeinfo.bpl= hw_modeinfo.bpl      ;break;
		  case  8:logi_modeinfo.bpl= hw_modeinfo.bpl      ;break;}
		 break;
   case SCM_TRUE:switch(hw_modeinfo.bps)
                 {case  1:logi_modeinfo.bpl=(hw_modeinfo.bpl<<3)  *3;break;
		  case  4:logi_modeinfo.bpl=(hw_modeinfo.bpl<<1)  *3;break;
		  case -6:logi_modeinfo.bpl=(hw_modeinfo.bpl<<3)/6*3;break;
		  case  6:logi_modeinfo.bpl=(hw_modeinfo.bpl   )  *3;break;
		  case  8:logi_modeinfo.bpl=(hw_modeinfo.bpl   )  *3;break;}
		 break;
  }

  logi_modeinfo.left=0;
  switch(logi_modeinfo.sc_mode)
  { case SCM_MONO:logi_modeinfo.right=logi_modeinfo.bpl<<3;break;
    case SCM_GREY:logi_modeinfo.right=logi_modeinfo.bpl;break;
    case SCM_TRUE:logi_modeinfo.right=logi_modeinfo.bpl/3;break;
  }
  logi_modeinfo.top=logi_modeinfo.bottom=0;
  
  logi_modeinfo.bright=logi_modeinfo.contrast=
  logi_modeinfo.hue   =logi_modeinfo.sat     =0;
  
  logi_modeinfo.options=(hw_modeinfo.bps==-6);

  if (irq_enab) scanner_operate(); /* ReSTART Scanner ! */

}

static void logi32_free_mem(void)
{
  struct buffer_block_struct *tmp = memroot, *next;
  unsigned int size;
  if(!buflines) {
    PRINTK("logi32_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 logi32_get_mem(int lines)
{
  struct buffer_block_struct *tmp, **lastptr;
  unsigned int blksiz;
  if((lines * hw_modeinfo.bpl) > MAX_BUFSIZ)
    return -EINVAL;
  if(buflines)
    logi32_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) {
      logi32_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 dithlcnt=0;

static char dithtab[64]={0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,
                         1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
                         1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
                         2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6};

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(logi_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 -6:if (dithlcnt++==0) 
               for(x=0;x<(hw_modeinfo.bpl<<3)/6;x++) copy_buffer[x]=0;
	     for(x=0;x<hw_modeinfo.bpl/3;x++)
	     { copy_buffer[(x<<2)  ]+=dithtab[ (RD_ADDR[x+x+x  ]&0xfc)>>2 ];
	       copy_buffer[(x<<2)+1]+=dithtab[((RD_ADDR[x+x+x  ]&0x03)<<4)|
	       				      ((RD_ADDR[x+x+x+1]&0xf0)>>4)];
	       copy_buffer[(x<<2)+2]+=dithtab[((RD_ADDR[x+x+x+1]&0x0f)<<2)|
	       				      ((RD_ADDR[x+x+x+2]&0xc0)>>6)];
	       copy_buffer[(x<<2)+3]+=dithtab[ (RD_ADDR[x+x+x+2]&0x3f)    ];
	     }
	     if (dithlcnt<6) break;
	     dithlcnt=0;
             switch(logi_modeinfo.sc_mode)
             {case SCM_MONO: for(x=0;x<(hw_modeinfo.bpl<<3)/6;x++)
             		     { copy_buffer[x>>3]=
             		        (copy_buffer[x>>3]&(0xff^bitmask[x&7]))
             		       |((copy_buffer[x]>18)*bitmask[x&7]);
             		     }
                             inbuffer=x>>3;
                             break;
              case SCM_GREY: for(x=0;x<(hw_modeinfo.bpl<<3)/6;x++) 
                               copy_buffer[x]=
                                 copy_buffer[x]*7+copy_buffer[x]/12;
                             inbuffer=x;
                             break;
              case SCM_TRUE: for(x=(hw_modeinfo.bpl<<3)/6-1;x>=0;x--) 
                               copy_buffer[x+x+x  ]=
                               copy_buffer[x+x+x+1]=
                               copy_buffer[x+x+x+2]=
				copy_buffer[x];
			     inbuffer=x+x+x;
                             break;
            } break;
     case 8:switch(logi_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;
     case 6:switch(logi_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]>31) ? 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]<<2)|(RD_ADDR[x]>>4);
                            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]<<2)|(RD_ADDR[x]>>4);
			    inbuffer=x+x+x;
                            break;
            } break;
     case 4:switch(logi_modeinfo.sc_mode)
            {case SCM_MONO: for(x=0;x<(hw_modeinfo.bpl>>2);x++) 
                              copy_buffer[x]=0;
            		    for(x=0;x<hw_modeinfo.bpl;x++) 
                            { copy_buffer[x>>2]|=
                                (RD_ADDR[x]&0xf0>0x70) ? bitmask[(x+x  )&7] : 0;
			      copy_buffer[(x>>2)+1]|=
                                (RD_ADDR[x]&0x0f>0x07) ? bitmask[(x+x+1)&7] : 0;
                            }
                            inbuffer=x>>2;
                            break;
             case SCM_GREY: for(x=0;x<hw_modeinfo.bpl;x++) 
                            { copy_buffer[x+x]=
                                (RD_ADDR[x]&0xf0)|(RD_ADDR[x]>>4);
                              copy_buffer[x+x+1]=
                                (RD_ADDR[x]&0x0f)|(RD_ADDR[x]<<4);
                            }
                            inbuffer=x+x;
                            break;
             case SCM_TRUE: for(x=0;x<hw_modeinfo.bpl;x++) 
                            { copy_buffer[6*x  ]=
                              copy_buffer[6*x+1]=
                              copy_buffer[6*x+2]=
                                (RD_ADDR[x]&0xf0)|(RD_ADDR[x]>>4);
                              copy_buffer[6*x+3]=
                              copy_buffer[6*x+4]=
                              copy_buffer[6*x+5]=
                                (RD_ADDR[x]&0x0f)|(RD_ADDR[x]<<4);
			    }
			    inbuffer=6*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 logi32_read(struct inode *inode, struct file *file, \
	char *buf, int requested)
{
  int r, tmp = requested;
  if(!buflines && ((r=logi32_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("logi32_read: I see some space in my buffer, will enable irq\n");
    scanner_operate();
  }
  if(file->f_flags & O_NONBLOCK) {
    PRINTK("logi32_read: reading in O_NONBLOCK mode\n");
    return tmp - requested;
  }
  while(requested > 0) {
    if (hw_modeinfo.bps==-6) 
      bytes_missing = 6*requested*hw_modeinfo.bpl/logi_modeinfo.bpl;
    else
      bytes_missing =   requested*hw_modeinfo.bpl/logi_modeinfo.bpl;
    
    PRINTK("logi32_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("logi32_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 logi32_select(struct inode *inode, struct file *file, \
	int type, select_table *st)
{
  int tmp, r;
  if(type != SEL_IN)
    return 0;
  if(!buflines && ((r=logi32_get_mem(req_lines = DEFAULT_BUFLINES)) < 0))
    return r;
  cli();
  tmp=RD_BUFSPC;
  sti();
  if(tmp >= SELECT_TH) {
    PRINTK("logi32_select: device is ready (%d bytes available)\n", tmp);
    return 1;			/* device is ready */
  }
  PRINTK("logi32_select: device NOT ready\n");
  if(st) {
    PRINTK("logi32_select: waiting for device to get ready\n");
    bytes_missing = SELECT_TH;
    ds.process_sleeps = 1;
    if(!ds.irq_enabled) {
      PRINTK("logi32_select: IRQ not enabled -- will turn on scanner\n");
      scanner_operate();
    }
  }
  select_wait(&wq, st);
  return 0;
}

static void logi32_interrupt(int irq,struct pt_regs *unused)
{
  static int bytes_til_wakeup;
  int help;
  if(!ds.irq_enabled) {
    PRINTK("logi32.c: logi32_interrupt: unexpected interrupt\n");
    return;
  }

  if(sct.type==1)
  { if (((help=inb(LOGI_IOBASE))&0x20)==0) 
    { PRINTK("logi32.c: Interrupt didn't originate from scanner\n");return;}
    outb(help&0xdf,LOGI_IOBASE);	/* Clear int mask ... */
  } else
  { outb(4,LOGI_IOBASE);
    if (((help=inb(LOGI_IOBASE+1))&0x01)==0) 
    { PRINTK("logi32.c: Interrupt didn't originate from scanner\n");return;}
    if (help&0x02) 
    { outb((help|0x10)&0xfd,LOGI_IOBASE+1);}
    else 
    { if (help&0x10) PRINTK("Flag activated !\n");
      outb(0,LOGI_IOBASE+1);PRINTK("Returning ... why ?\n");
      return;
    }
  }

  if(!dma_terminal_count(LOGI_DMA)) {
    PRINTK("logi32.c: logi32_interrupt: DMA transfer not finished yet\n");
    return;
  }

  PRINTK("logi32_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("logi32_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("logi32_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("logi_interrupt: there's still free buffer space (%d bytes), irq stays enabled\n", \
    	WR_BUFSPC);
  } else {
    PRINTK("logi32_interrupt: buffer is full -- turning off irq\n");
    scanner_standby();
    if(ds.process_sleeps) {
      wake_up_interruptible(&wq);
      ds.process_sleeps = 0;
    }
    return;
  }

  if (sct.type==2)
  { while (inb(LOGI_IOBASE)&0x80)
    {outb(1,LOGI_IOBASE);inb(LOGI_IOBASE+1);}
  }
  if (sct.type!=1)
  { outb(3,LOGI_IOBASE);inb(LOGI_IOBASE); }

  if (sct.type==1)
  { while(inb(LOGI_IOBASE)&0x01);
    outb(6,LOGI_IOBASE);}
  else
  { outb(3,LOGI_IOBASE);
    outb(inb(LOGI_IOBASE+1)&0x7f,LOGI_IOBASE+1);
  }

  setup_xfer(WR_ADDR);

  if (sct.type==1) /* activate the hardware */
  { enable_dma(LOGI_DMA);
    outb(0x1e,LOGI_IOBASE); /* 0x10 is IRQ-Flag */
  } else
  { outb(4,LOGI_IOBASE);outb(0x10,LOGI_IOBASE+1);
    outb(3,LOGI_IOBASE);
    outb(0x80|inb(LOGI_IOBASE+1),LOGI_IOBASE+1);
    enable_dma(LOGI_DMA);
    outb(0,LOGI_IOBASE);
    outb(0x02|inb(LOGI_IOBASE+1),LOGI_IOBASE+1);
  }

  return;
}

#ifdef LOGI32_PINT

/* XXX Please have a look at this ! */

struct scan_io logi_scanio;

static void logi2scanio(void)
{ /* XXX Width and height - is this correct ? */
  logi_scanio.scan_window_width=
      (logi_modeinfo.right -logi_modeinfo.left)*1200L/logi_modeinfo.xdpi;
  logi_scanio.scan_window_length=
      (logi_modeinfo.bottom-logi_modeinfo.top )*1200L/logi_modeinfo.ydpi;

  logi_scanio.scan_x_resolution=
       logi_modeinfo.xdpi; /* horizontal resolution in dots-per-inch */
  logi_scanio.scan_y_resolution=
       logi_modeinfo.ydpi; /* vertical   resolution in dots-per-inch */

  logi_scanio.scan_x_origin=	/* horizontal coordinate of upper left corner */
       logi_modeinfo.left*1200L/logi_modeinfo.xdpi; 
  logi_scanio.scan_y_origin=	/* vertical coordinate of upper left corner */
       logi_modeinfo.top *1200L/logi_modeinfo.ydpi; 

  switch(logi_modeinfo.sc_mode)
  {case SCM_MONO:logi_scanio.scan_image_mode=SIM_BINARY_MONOCHROME;break;
   case SCM_GREY:logi_scanio.scan_image_mode=SIM_GRAYSCALE;break;
   case SCM_TRUE:logi_scanio.scan_image_mode=SIM_COLOR;break;
  }
  
  logi_scanio.scan_brightness=logi_modeinfo.bright  ;  /* brightness control for those to can do it */
  logi_scanio.scan_contrast  =logi_modeinfo.contrast;  /* contrast control for those to can do it */

  logi_scanio.scan_scanner_type=LOGI_HANDSCAN;         /* type of scanner (read only variable) */

  logi_scanio.scan_use_adf=0;         /* we don't have an Automatic Document Feeder */
}

static void scanio2logi(void)
{ 
  logi_modeinfo.xdpi=
    logi_scanio.scan_x_resolution; /* horizontal resolution in dots-per-inch */
  logi_modeinfo.ydpi=
    logi_scanio.scan_y_resolution; /* vertical   resolution in dots-per-inch */

  switch(logi_scanio.scan_image_mode)
  {case SIM_BINARY_MONOCHROME:
  		logi_modeinfo.sc_mode=SCM_MONO;
  		logi_modeinfo.depth  =0;       break;
   case SIM_GRAYSCALE:
   		logi_modeinfo.sc_mode=SCM_GREY;
   		logi_modeinfo.depth  =0xff;    break;
   case SIM_COLOR:
   		logi_modeinfo.sc_mode=SCM_TRUE;
   		logi_modeinfo.depth  =0xffffff;break;
   default:	printk("Unsupported scanner mode requested !");
   		logi_modeinfo.sc_mode=SCM_UNKNOWN;break;
  }
  logi_modeinfo.bpl=0;/* This will be calculated by the get_mode call */

  logi_modeinfo.left=logi_modeinfo.right=0; /* Leave to autodetect for now. */
  logi_modeinfo.top=logi_modeinfo.bottom=0; /* Leave to autodetect for now. */

#if 0
  logi_modeinfo.left=logi_scanio.scan_x_origin; /* horizontal coordinate of upper left corner */
  logi_modeinfo.top =logi_scanio.scan_y_origin; /* vertical coordinate of upper left corner */
#endif

  logi_modeinfo.bright  =logi_scanio.scan_brightness;  /* brightness control for those to can do it */
  logi_modeinfo.contrast=logi_scanio.scan_contrast  ;  /* contrast control for those to can do it */

}

#endif

static int logi32_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 */
      if (sct.type!=3)
      memcpy_tofs((char*) arg, (char*) &opt_logi32, (long) sizeof(opt_logi32));
      return 0;
    case HSIOCGSCC:			/* get scanner_capabilities struct */
      if (sct.type==3)
      memcpy_tofs((char*) arg, (char*) &scc_logi256, (long) sizeof(struct scanner_capabilities));
      else
      memcpy_tofs((char*) arg, (char*) &scc_logi32, (long) sizeof(struct scanner_capabilities));
      return 0;
    case HSIOCGSCT:			/* get scanner_type struct */
      memcpy_tofs((char*) arg, (char*) &(sctxt[sct.type]), (long) sizeof(struct scanner_type));
      return 0;
    case HSIOCSMOD:			/* set modeinfo struct */
      memcpy_fromfs((char*) &logi_modeinfo, (char*) arg, (long) sizeof(struct modeinfo_struct));
      logi32_get_mode();
      memcpy_tofs((char*) arg, (char*) &logi_modeinfo, (long) sizeof(struct modeinfo_struct));
      return 0;
    case HSIOCGMOD:			/* get modeinfo struct */
      logi32_get_mode();
      memcpy_tofs((char*) arg, (char*) &logi_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(LOGI_IOBASE+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, LOGI_IOBASE+rs.regnum);
      return 0;
    case HSIOCSBUF:			/* set size of internal buffer */
      if(!(r=get_fs_long(arg)))
	logi32_free_mem();
      else {
	logi32_get_mode();
	req_lines = r;
	logi32_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;

#ifdef LOGI32_PINT
/* XXX and at this ... */
    case SCAN_PAPER_PUSH:
      return -EIO;			/*We don't have an ADF ...*/
    case SCAN_GET:
      logi32_get_mode();
      logi2scanio();
      memcpy_tofs((char*) arg, (char*) &logi_scanio, (long) sizeof(struct scan_io));
      return 0;
    case SCAN_SET:
      memcpy_fromfs((char*) &logi_scanio, (char*) arg, (long) sizeof(struct scan_io));
      scanio2logi();
      logi32_get_mode();
      return 0;
    case SCAN_GET_PIXEL_SIZE:
      {struct scan_pixel_sizes sps;
       logi32_get_mode();
       sps.pix_width=logi_modeinfo.bpl*8;
       sps.pix_height=0;	/* What should we return here ? */
      memcpy_tofs((char*) arg, (char*) &sps, (long) sizeof(struct scan_pixel_sizes));
      return 0;
      }
#endif

    default:
      return -EINVAL;
  }
}

static int logi32_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;

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

  logi32_get_mode(); 

  if((r=request_irq(LOGI_IRQ, logi32_interrupt, SA_INTERRUPT, LOGI32_SCANNER_NAME )))	/* request IRQ */
    return r;

  if((r=request_dma(LOGI_DMA, LOGI32_SCANNER_NAME))) {
    free_irq(LOGI_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 logi32_close(struct inode *inode, struct file *file)
{
  scanner_standby();
  wait_ready();
  if(buflines)
    logi32_free_mem();
  inbuffer=0;
  free_dma(LOGI_DMA);
  free_irq(LOGI_IRQ);
  ds.device_open = 0;
  MOD_DEC_USE_COUNT;
}

static int logi32_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 logi32_fops = {
  logi32_lseek,
  logi32_read,
  NULL,		/* write */
  NULL,		/* readdir */
  logi32_select,
  logi32_ioctl,
  NULL,		/* mmap */
  logi32_open,
  logi32_close,
};

static int check_4_scanner(void)
{ int help,tf,t3f;
  tf=0;
  t3f=1;
  if (LOGI_DMA!=1&&LOGI_DMA!=3)
    {printk("Sorry,only DMA 1 and 3 are supported.\n");return(-ENODEV);}
  help=inb(LOGI_IOBASE+7)&0x0f;
  if (help==1)
  { sct.ready_mask=0x01;
    sct.type=1;
  } else
  { sct.ready_mask=0x40;
    outb(5,LOGI_IOBASE);		/* Get reg #5 */
    help=inb(LOGI_IOBASE+1)&0x0f;
    switch(help)
    {case 0x0:
     case 0xf:printk("Scanner-interface not detected.\n");return(-ENODEV);
     case 0x2:
     case 0x3:sct.type=2;break;
     case 0x8:sct.type=2;tf=sct.tf2=1;break;
     default :printk("Scanner-interface is incompatible.\n");return(-ENODEV);
    }
  }
  if (sct.type!=1)
  { outb(0,LOGI_IOBASE);
    outb(inb(LOGI_IOBASE+1)&0x7f,LOGI_IOBASE+1);
    outb(0,LOGI_IOBASE);
    outb(inb(LOGI_IOBASE+1)|0x20,LOGI_IOBASE+1);
    outb(0,LOGI_IOBASE);
    if (inb(LOGI_IOBASE+1)&0x20) tf=1;
    light_on();
    outb(5,LOGI_IOBASE);
    if (inb(LOGI_IOBASE+1)&0x10) t3f=0;
    light_off();
  }
  if (tf==1&&t3f==0) sct.type=3;
  if (sct.type!=1)
  { outb(2,LOGI_IOBASE);
    outb(0x42,LOGI_IOBASE+1);
    outb(3,LOGI_IOBASE);
    switch(LOGI_IRQ)
    {case  2:
     case  9:help=0x04;break;
     case  3:help=0x01;break;
     case  4:help=0x02;break;
     case  5:help=0x03;break;
     case  7:help=0x05;break;
     case 11:help=0x06;break;
     case 12:help=0x07;break;
     default:printk("Sorry,unsupported IRQ !.\n");return(-ENODEV);
    }
    outb((LOGI_DMA==1 ? 0x10 : 0x30)|help,LOGI_IOBASE+1);
    outb(0,LOGI_IOBASE);
    outb(inb(LOGI_IOBASE+1)&0xfd,LOGI_IOBASE+1);
    if (sct.tf2==0) {outb(6,LOGI_IOBASE);outb(0x0e,LOGI_IOBASE+1);}
    outb(4,LOGI_IOBASE);outb(0,LOGI_IOBASE+1);
    outb(0xa,LOGI_IOBASE);outb(0,LOGI_IOBASE+1);
  }
  if (sct.type<0||sct.type>3) sct.type=0; /* Unknown scanner,continue ...*/
  help=0;
  printk("check_4_scanner detected %s scanner.\n",sctxt[sct.type].model);
  return(0);/* Scanner is there and operative */
}

int init_module(void)
{
  printk("LOGITECH handheld scanner driver  version 0.0.3\n"
         "Copyright (c) 1994 Andreas Beck <becka@hp.rz.uni-duesseldorf.de>\n");

#ifdef LOGI32_PINT
  printk("PINT support Copyright (C) 1994 Kenneth Stailey ken@spacenet.com enabled\n");
#endif

#ifdef DEBUG_LOGI32
  if (LOGI_DEBUG) printk("Debugging enabled.\n");
#endif

  if (LOGI_MAJOR !=LOGI32_SCANNER_MAJOR) 
     printk("Overriding default major  (%6d) with %6d.\n",LOGI32_SCANNER_MAJOR,LOGI_MAJOR);
  if (LOGI_IOBASE!=LOGI32_IOBASE)
     printk("Overriding default iobase (0x%4x) with 0x%4x.\n",LOGI32_IOBASE,LOGI_IOBASE);
  if (LOGI_IRQ   !=LOGI32_IRQ)
     printk("Overriding default irq    (%6d) with %6d.\n",LOGI32_IRQ,LOGI_IRQ);
  if (LOGI_DMA   !=LOGI32_DMA)
     printk("Overriding default dma    (%6d) with %6d.\n",LOGI32_DMA,LOGI_DMA);
  
  if (check_4_scanner())
  { printk("logi32.c: scanner interface not found\n");
    return -EIO;
  }
  
  if (LOGI_MAJOR==0)
  { if ((LOGI_MAJOR=register_chrdev(LOGI_MAJOR, LOGI32_SCANNER_NAME,&logi32_fops))<0)
    { printk("logi32.c: cannot find free major number\n");
      return -EIO; }
    else printk("logi32.c: auto-selected major %d.\n",LOGI_MAJOR);
  } 
  else if (register_chrdev(LOGI_MAJOR, LOGI32_SCANNER_NAME, &logi32_fops)) {
    printk("logi32.c: cannot register requested major number\n");
    return -EIO;
  }
  return 0;
}

void cleanup_module(void)
{
  printk("Removing LOGITECH handheld scanner driver from memory\n");
  if(MOD_IN_USE)
    printk("logi32.c: device is busy, delaying driver removal\n");
  if(unregister_chrdev(LOGI_MAJOR, LOGI32_SCANNER_NAME))
    printk("logi32.c: unregister_chrdev() failed\n");
}
