/* @file plustek-pp_ptdrv.c
 * @brief this is the driver interface
 *
 * based on sources acquired from Plustek Inc.
 * Copyright (C) 1998 Plustek Inc.
 * Copyright (C) 2000-2013 Gerhard Jaeger <gerhard@gjaeger.de>
 * also based on the work done by Rick Bronson
 *
 * History:
 * - 0.30 - initial version
 * - 0.31 - Added some comments
 *        - added claiming/release of parallel port resources for this driver
 *        - added scaling function for high resolution modes where dpix < dpiy
 * - 0.32 - Revised lamp-off behaviour
 *        - removed function ptdrvIsLampOn
 *        - fixed misbehaviour when using cat /dev/pt_drv
 *        - moved parport-functions to module misc.c
 * - 0.33 - added parameter lOffonEnd
 *        - revised parport concurrency
 *        - removed calls to ps->PositionLamp
 * - 0.34 - no changes
 * - 0.35 - removed _PTDRV_PUT_SCANNER_MODEL from ioctl interface
 *        - added Kevins' changes (MiscRestorePort)
 *        - added parameter legal and function PtDrvLegalRequested()
 * - 0.36 - removed a bug in the shutdown function
 *        - removed all OP600P specific stuff because of the Primax tests
 *        - added version code to ioctl interface
 *        - added new parameter mov - model override
 *        - removed parameter legal
 *        - removed function PtDrvLegalRequested
 *        - changes, due to define renaming
 *        - patch for OpticPro 4800P
 *        - added multiple device support
 *        - added proc fs support/also for Kernel2.4
 * - 0.37 - cleanup work, moved the procfs stuff to file procfs.c
 *        - and some definitions to plustek_scan.h
 *        - moved MODELSTR to misc.c
 *        - output of the error-code after initialization
 * - 0.38 - added P12 stuff
 *        - removed function ptdrvIdleMode
 *        - moved function ptdrvP96Calibration() to p48xxCalibration
 *        - moved function ptdrvP98Calibration() to p9636Calibration
 *        - added devfs support (patch by Gordon Heydon <gjheydon@bigfoot.com>)
 * - 0.39 - added schedule stuff after reading one line to have a better
 *          system response in SPP modes
 *        - added forceMode switch
 * - 0.40 - added MODULE_LICENSE stuff
 * - 0.41 - added _PTDRV_ADJUST functionality
 *        - changed ioctl call to PutImage
 * - 0.42 - added _PTDRV_SETMAP functionality
 *        - improved the cancel functionality
 * - 0.43 - added LINUX_26 stuff
 *        - changed include names
 *        - changed version string stuff
 * - 0.44 - added support for more recent kernels
 *        - fix format string issues, as Long types default to int32_t
 *          now
 * .
 * <hr>
 * This file is part of the SANE package.
 *
 * 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., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA.
 *
 * As a special exception, the authors of SANE give permission for
 * additional uses of the libraries contained in this release of SANE.
 *
 * The exception is that, if you link a SANE library with other files
 * to produce an executable, this does not by itself cause the
 * resulting executable to be covered by the GNU General Public
 * License.  Your use of that executable is in no way restricted on
 * account of linking the SANE library code into it.
 *
 * This exception does not, however, invalidate any other reasons why
 * the executable file might be covered by the GNU General Public
 * License.
 *
 * If you submit changes to SANE to the maintainers to be included in
 * a subsequent release, you agree by submitting the changes that
 * those changes may be distributed with this exception intact.
 *
 * If you write modifications of your own for SANE, it is your choice
 * whether to permit this exception to apply to your modifications.
 * If you do not wish that, delete this exception notice.
 * <hr>
 */
#ifdef __KERNEL__
# include <linux/module.h>
# include <linux/version.h>

# ifdef CONFIG_DEVFS_FS
#  include <linux/devfs_fs_kernel.h>
#  if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,69))
#   define DEVFS_26_STYLE
#  endif
# endif
#endif

#include "plustek-pp_scan.h"

#ifdef __KERNEL__
# include <linux/param.h>
#endif

/****************************** static vars **********************************/

/* default port is at 0x378 */
static int port[_MAX_PTDEVS] = { 0x378, 0, 0, 0 };

#ifdef __KERNEL__
static pScanData PtDrvDevices[_MAX_PTDEVS] = { [0 ... (_MAX_PTDEVS-1)] = NULL};

/* default is 180 secs for lamp switch off */
static int lampoff[_MAX_PTDEVS] = { [0 ... (_MAX_PTDEVS-1)] = 180 };

/* warmup period for lamp (30 secs) */
static int warmup[_MAX_PTDEVS] = { [0 ... (_MAX_PTDEVS-1)] = 30 };

/* switch lamp off on unload (default = no)*/
static int lOffonEnd[_MAX_PTDEVS] = { [0 ... (_MAX_PTDEVS-1)] = 0 };

/* model override (0-->none) */
static UShort mov[_MAX_PTDEVS] = { [0 ... (_MAX_PTDEVS-1)] = 0 };

/* forceMode (0--> auto, 1: SPP, 2:EPP, others: auto) */
static UShort forceMode[_MAX_PTDEVS] = { [0 ... (_MAX_PTDEVS-1)] = 0 };

/* to use delayed I/O for each device */
static Bool slowIO[_MAX_PTDEVS] = { [0 ... (_MAX_PTDEVS-1)] = _FALSE };

#else

static pScanData PtDrvDevices[_MAX_PTDEVS]= { NULL,   NULL,   NULL,   NULL   };
static int       lampoff[_MAX_PTDEVS]     = { 180,    180,    180,    180    };
static int       warmup[_MAX_PTDEVS]      = { 30,     30,     30,     30     };
static int       lOffonEnd[_MAX_PTDEVS]   = { 0,      0,      0,      0      };
static UShort    mov[_MAX_PTDEVS]         = { 0,      0,      0,      0      };
static UShort    forceMode[_MAX_PTDEVS]   = { 0,      0,      0,      0      };

#endif

/* timers for warmup checks */
static TimerDef toTimer[_MAX_PTDEVS];

#ifndef __KERNEL__
static Bool	PtDrvInitialized = _FALSE;
#ifdef HAVE_SETITIMER
static struct itimerval saveSettings;
#endif
#else
static Bool deviceScanning = _FALSE;

static struct timer_list tl[_MAX_PTDEVS];

/* for calculation of the timer expiration */
extern volatile unsigned long jiffies;

/* the parameter interface
 */
#if ((LINUX_VERSION_CODE > 0x020111) && defined(MODULE))
MODULE_AUTHOR("Gerhard Jaeger <gerhard@gjaeger.de>");
MODULE_DESCRIPTION("Plustek parallelport-scanner driver");

/* addresses this 'new' license feature... */
#ifdef MODULE_LICENSE
MODULE_LICENSE("GPL");
#endif

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13))
MODULE_PARM(port, "1-" __MODULE_STRING(_MAX_PTDEVS) "i");
MODULE_PARM(lampoff, "1-" __MODULE_STRING(_MAX_PTDEVS) "i");
MODULE_PARM(warmup,"1-" __MODULE_STRING(_MAX_PTDEVS) "i");
MODULE_PARM(lOffonEnd, "1-" __MODULE_STRING(_MAX_PTDEVS) "i");
MODULE_PARM(mov, "1-" __MODULE_STRING(_MAX_PTDEVS) "i");
MODULE_PARM(slowIO,"1-" __MODULE_STRING(_MAX_PTDEVS) "i");
MODULE_PARM(forceMode,"1-" __MODULE_STRING(_MAX_PTDEVS) "i");

#else

static int array_len = _MAX_PTDEVS;

module_param_array(port,      int,    &array_len, 0);
module_param_array(lampoff,   int,    &array_len, 0);
module_param_array(warmup,    int,    &array_len, 0);
module_param_array(lOffonEnd, int,    &array_len, 0);
module_param_array(mov,       ushort, &array_len, 0);
module_param_array(slowIO,    int,    &array_len, 0);
module_param_array(forceMode, ushort, &array_len, 0);

#endif


MODULE_PARM_DESC(port, "I/O base address of parport");
MODULE_PARM_DESC(lampoff, "Lamp-Off timer preset in seconds");
MODULE_PARM_DESC(warmup, "Minimum warmup time in seconds");
MODULE_PARM_DESC(lOffonEnd, "1 - switchoff lamp on unload");
MODULE_PARM_DESC(mov, "Modell-override switch");
MODULE_PARM_DESC(slowIO, "0 = Fast I/O, 1 = Delayed I/O");
MODULE_PARM_DESC(forceMode, "0 = use auto detection, "
                            "1 = use SPP mode, 2 = use EPP mode");
#endif

#if defined (CONFIG_DEVFS_FS)
# ifndef (DEVFS_26_STYLE)
	static devfs_handle_t devfs_handle = NULL;
# endif
#else
# ifdef LINUX_26
	static class_t *ptdrv_class;
# endif
#endif

/*
 * the module interface
 */
static int 		 pt_drv_open ( struct inode *, struct file *);
static CLOSETYPE pt_drv_close( struct inode *, struct file *);

#ifdef LINUX_20
  static int pt_drv_read(  struct inode*, struct file*, char*, int );
  static int pt_drv_write( struct inode*, struct file*, const char*, int );
#else
  static ssize_t pt_drv_read ( struct file *file,
							 char *buffer, size_t count, loff_t *);
  static ssize_t pt_drv_write( struct file *file,
							 const char *buffer, size_t tmp,loff_t *count);
#endif

#ifdef NOLOCK_IOCTL
  static long pt_drv_ioctl( struct file *, UInt, unsigned long );
#else
  static int pt_drv_ioctl( struct inode *, struct file *, UInt, unsigned long );
#endif


/*
 * the driver interface
 */
#ifdef LINUX_20

static struct file_operations pt_drv_fops =
{
	NULL,			/* seek 				*/
	pt_drv_read,	/* read 				*/
	pt_drv_write,	/* write 				*/
	NULL,			/* readdir 				*/
	NULL,			/* select 				*/
	pt_drv_ioctl,  	/* ioctl 				*/
	NULL,   		/* mmap 				*/
	pt_drv_open,    /* open 				*/
	pt_drv_close,	/* release 				*/
	NULL,			/* fsync 				*/
	NULL,			/* fasync 				*/
	NULL,			/* check_media_change 	*/
	NULL			/* revalidate 			*/
};

#else	/* 2.2.x and higher stuff */

static struct file_operations pt_drv_fops = {
#ifdef LINUX_24
	owner:		THIS_MODULE,
#endif
	read:		pt_drv_read,
	write:		pt_drv_write,
	IOCTL:		pt_drv_ioctl,
	open:		pt_drv_open,
	release:	pt_drv_close,
};

#endif

#endif	/* guard __KERNEL */

/****************************** some prototypes ******************************/

static void ptdrvStartLampTimer( pScanData ps );

/****************************** local functions ******************************/

#ifdef __KERNEL__
/** depending on the device, return the data structure
 */
static pScanData get_pt_from_inode(struct inode *ip)
{
    int minor = _MINOR(ip);

    /*
     * unit out of range
     */
    if (minor >=  _MAX_PTDEVS )
        return NULL;

    return( PtDrvDevices[minor] );
}
#endif

/** copy user-space data into kernel memory
 */
static int getUserPtr(const pVoid useraddr, pVoid where, UInt size )
{
	int err = _OK;

	/* do parameter checks */
	if((NULL == useraddr) || ( 0 == size))
		return _E_INVALID;

#ifdef __KERNEL__
	if ((err = verify_area_20(VERIFY_READ, useraddr, size)))
		return err;
#endif

	switch (size) {
#ifdef __KERNEL__
	case sizeof(u_char):
		GET_USER_RET(*(u_char *)where, (u_char *) useraddr, -EFAULT);
		break;

	case sizeof(u_short):
		GET_USER_RET(*(u_short *)where, (u_short *) useraddr, -EFAULT);
		break;

	case sizeof(u_long):
		GET_USER_RET(*(u_long *)where, (u_long *) useraddr, -EFAULT);
		break;

	default:
		if (copy_from_user(where, useraddr, size))
			return -EFAULT;
#else
	default:
		memcpy( where, useraddr, size );
#endif
	}
	return err;
}

/** copy kernel data into user mode address space
 */
static int putUserPtr( const pVoid ptr, pVoid useraddr, UInt size )
{
	int err = _OK;

	if (NULL == useraddr)
    	return _E_INVALID;

#ifdef __KERNEL__
	if ((err = verify_area_20(VERIFY_WRITE, useraddr, size)))
		return err;

	if (copy_to_user(useraddr, ptr, size ))
		return -EFAULT;
#else
	memcpy( useraddr, ptr, size );
#endif

	return err;
}

#ifndef __KERNEL__
static unsigned long copy_from_user( pVoid dest, pVoid src, unsigned long len )
{
	memcpy( dest, src, len );
	return 0;
}

static unsigned long copy_to_user( pVoid dest, pVoid src, unsigned long len )
{
	memcpy( dest, src, len );
	return 0;
}
#endif

/**
 */
static int putUserVal(const ULong value, pVoid useraddr, UInt size)
{
#ifdef __KERNEL__
	int err;
#endif

	if (NULL == useraddr)
    	return _E_INVALID;

#ifdef __KERNEL__
	if ((err = verify_area_20(VERIFY_WRITE, useraddr, size)))
    	return err;
#endif

	switch (size) {

#ifdef __KERNEL__
	case sizeof(u_char):
    	PUT_USER_RET((u_char)value, (u_char *) useraddr, -EFAULT);
    	break;
  	case sizeof(u_short):
    	PUT_USER_RET((u_short)value, (u_short *) useraddr, -EFAULT);
    	break;
  	case sizeof(u_long):
    	PUT_USER_RET((u_long)value, (u_long *) useraddr, -EFAULT);
    	break;
#else
	case sizeof(UChar):
		*(pUChar)useraddr = (UChar)value;
		break;
	case sizeof(UShort):
		*(pUShort)useraddr = (UShort)value;
		break;
	case sizeof(ULong):
		*(pULong)useraddr = (ULong)value;
		break;

#endif
  	default:
    	return _E_INVALID;
	}
	return 0;
}

/** switch lamp 0 on
 */
static void ptDrvSwitchLampOn( pScanData ps )
{
	DBG( DBG_LOW, "Switching lamp 0 on.\n" );

	if( _IS_ASIC98(ps->sCaps.AsicID)) {

		ps->AsicReg.RD_ScanControl |= _SCAN_NORMALLAMP_ON;

		ps->bLastLampStatus = _SCAN_NORMALLAMP_ON;

	} else {

		ps->AsicReg.RD_ScanControl |= ps->bLampOn;
		ps->bLastLampStatus = ps->bLampOn;
	}

	IOCmdRegisterToScanner(ps, ps->RegScanControl, ps->AsicReg.RD_ScanControl);
}

/** check the lamp warmup
 */
static void ptdrvLampWarmup( pScanData ps )
{
	Bool	 warmupNeeded;
	TimerDef timer;

	if( 0 == ps->warmup )
		return;

	warmupNeeded = _FALSE;

	/*
	 * do we have to warmup again ? Timer has not elapsed...
	 */
	if( _OK == MiscCheckTimer( &toTimer[ps->devno] )) {

		DBG( DBG_LOW, "Startup warmup needed!\n" );
		warmupNeeded = _TRUE;
	} else {

		warmupNeeded = ps->fWarmupNeeded;
	}

	if( warmupNeeded ) {

		/*
		 * correct lamp should have been switched on but
		 * before doing anything else wait until warmup has been done
		 */
		DBG( DBG_LOW, "Waiting on warmup - %u s\n", ps->warmup );

		MiscStartTimer( &timer, _SECOND * ps->warmup );
		while( !MiscCheckTimer( &timer )) {

			/* on break, we setup the initial timer again... */
			if( _FALSE == ps->fScanningStatus ) {
				MiscStartTimer( &toTimer[ps->devno], (_SECOND * ps->warmup));
				return;
			}
		};

	}
#ifdef DEBUG
	else {
		DBG( DBG_LOW, "No warm-up needed \n" );
	}
#endif

	/*
	 * start a timer here again with only a second timeout
	 * because we need this one only for startup (Force timeout!!)
	 */
	MiscStartTimer( &toTimer[ps->devno], _SECOND );
}

/**
 */
#ifdef __KERNEL__
static void ptdrvLampTimerIrq( unsigned long ptr )
#else
static void ptdrvLampTimerIrq( int sig_num )
#endif
{
	pScanData ps;

	DBG( DBG_HIGH, "!! IRQ !! Lamp-Timer stopped.\n" );

#ifdef __KERNEL__
	ps = (pScanData)ptr;
#else
    _VAR_NOT_USED( sig_num );
	ps = PtDrvDevices[0];
#endif

	/*
	 * paranoia check!
	 */
	if( NULL == ps )
		return;

	if( _NO_BASE == ps->sCaps.wIOBase )
		return;

	if( _IS_ASIC98(ps->sCaps.AsicID)) {
	    ps->AsicReg.RD_ScanControl &= ~_SCAN_LAMPS_ON;
	} else {
		ps->AsicReg.RD_ScanControl &= ~_SCAN_LAMP_ON;
	}

	/* force warmup... */
	ps->bLastLampStatus = 0xFF;

	/*
	 * claim parallel port if necessary...
	 * if the port is busy, restart the timer
	 */
	if( _OK != MiscClaimPort(ps)) {
		ptdrvStartLampTimer( ps );
		return;
	}

	IOCmdRegisterToScanner( ps, ps->RegScanControl,
							    ps->AsicReg.RD_ScanControl );
	MiscReleasePort(ps);
}

/**
 */
static void ptdrvStartLampTimer( pScanData ps )
{
#ifndef __KERNEL__
	sigset_t 		 block, pause_mask;
	struct sigaction s;
#ifdef HAVE_SETITIMER
	struct itimerval interval;
#endif

	/* block SIGALRM */
	sigemptyset( &block );
	sigaddset  ( &block, SIGALRM );
	sigprocmask( SIG_BLOCK, &block, &pause_mask );

	/* setup handler */
	sigemptyset( &s.sa_mask );
	sigaddset  (  &s.sa_mask, SIGINT );
	s.sa_flags   = 0;
	s.sa_handler = ptdrvLampTimerIrq;

	if(	sigaction( SIGALRM, &s, NULL ) < 0 ) {
		DBG(DBG_HIGH,"pt_drv%u: Can't setup timer-irq handler\n",ps->devno);
	}

	sigprocmask( SIG_UNBLOCK, &block, &pause_mask );

#ifdef HAVE_SETITIMER
	/*
	 * define a one-shot timer
	 */
	interval.it_value.tv_usec = 0;
	interval.it_value.tv_sec  = ps->lampoff;
	interval.it_interval.tv_usec = 0;
	interval.it_interval.tv_sec  = 0;

	if( 0 != ps->lampoff )
		setitimer( ITIMER_REAL, &interval, &saveSettings );
#else
	alarm( ps->lampoff );
#endif
#else
	init_timer( &tl[ps->devno] );

	/* timeout val in seconds */
	tl[ps->devno].expires  =  jiffies + ps->lampoff * HZ;
	tl[ps->devno].data     = (unsigned long)ps;
	tl[ps->devno].function = ptdrvLampTimerIrq;

	if( 0 != ps->lampoff )
		add_timer( &tl[ps->devno] );
#endif

	DBG( DBG_HIGH, "Lamp-Timer started!\n" );
}

/**
 */
static void ptdrvStopLampTimer( pScanData ps )
{
#ifndef __KERNEL__
	sigset_t block, pause_mask;

	/* block SIGALRM */
	sigemptyset( &block );
	sigaddset  ( &block, SIGALRM );
	sigprocmask( SIG_BLOCK, &block, &pause_mask );
#ifdef HAVE_SETITIMER
	if( 0 != ps->lampoff )
		setitimer( ITIMER_REAL, &saveSettings, NULL );
#else
	_VAR_NOT_USED( ps );
	alarm(0);
#endif
#else
	if( 0 != ps->lampoff )
		del_timer( &tl[ps->devno] );
#endif

	DBG( DBG_HIGH, "Lamp-Timer stopped!\n" );
}

/** claim and initialize the requested port
 */
static int ptdrvOpen( pScanData ps, int portBase )
{
	int retval;

	DBG( DBG_HIGH, "ptdrvOpen(port=0x%x)\n", (int32_t)portBase );
	if( NULL == ps )
		return _E_NULLPTR;

	/*
	 * claim port resources...
	 */
	retval = MiscClaimPort(ps);

	if( _OK != retval )
		return retval;

	return MiscInitPorts( ps, portBase );
}

/** free used memory (if necessary)
 * restore the parallel port settings and release the port
 */
static int ptdrvClose( pScanData ps )
{
	DBG( DBG_HIGH, "ptdrvClose()\n" );
	if( NULL == ps )
		return _E_NULLPTR;

	/*
	 * should be cleared by ioctl(close)
	 */
    if ( NULL != ps->driverbuf ) {
		DBG( DBG_LOW, "*** cleanup buffers ***\n" );
        _VFREE( ps->driverbuf );
        ps->driverbuf = NULL;
    }

    if ( NULL != ps->Shade.pHilight ) {
        _VFREE( ps->Shade.pHilight );
        ps->Shade.pHilight = NULL;
    }

	/*
	 * restore/release port resources...
	 */
	MiscRestorePort( ps );
	MiscReleasePort( ps );

	return _OK;
}

/** will be called during OPEN_DEVICE ioctl call
 */
static int ptdrvOpenDevice( pScanData ps )
{
	int    retval, iobase;
	UShort asic;
	UChar  lastStat;
	UShort lastMode;
	ULong  devno;

#ifdef __KERNEL__
	UShort            flags;
	struct pardevice *pd;
	struct parport   *pp;
	ProcDirDef        procDir;
#else
    int pd;
#endif

	/*
	 * push some values from the struct
     */
#ifdef __KERNEL__
	flags    = ps->flags;
	pp       = ps->pp;
	procDir  = ps->procDir;
#endif
	pd       = ps->pardev;
	iobase   = ps->sCaps.wIOBase;
	asic     = ps->sCaps.AsicID;
	lastStat = ps->bLastLampStatus;
	lastMode = ps->IO.lastPortMode;
	devno    = ps->devno;

	/*
	 * reinit the show
	 */
	ptdrvStopLampTimer( ps );
	MiscReinitStruct  ( ps );

	/*
	 * pop the val(s)
	 */
#ifdef __KERNEL__
	ps->flags   = flags;
	ps->pp      = pp;
	ps->procDir = procDir;
#endif
	ps->pardev          = pd;
	ps->bLastLampStatus = lastStat;
	ps->IO.lastPortMode = lastMode;
	ps->devno           = devno;

#ifdef __KERNEL__
	if( _TRUE == slowIO[devno] ) {
		DBG( DBG_LOW, "Using slow I/O\n" );
		ps->IO.slowIO = _TRUE;
		ps->IO.fnOut  = IOOutDelayed;
		ps->IO.fnIn   = IOInDelayed;
	} else {
		DBG( DBG_LOW, "Using fast I/O\n" );
		ps->IO.slowIO = _FALSE;
		ps->IO.fnOut  = IOOut;
		ps->IO.fnIn   = IOIn;
	}
#endif
	ps->ModelOverride = mov[devno];
	ps->warmup        = warmup[devno];
	ps->lampoff		  = lampoff[devno];
	ps->lOffonEnd	  = lOffonEnd[devno];
	ps->IO.forceMode  = forceMode[devno];

	/*
	 * try to find scanner again
	 */
	retval = ptdrvOpen( ps, iobase );

	if( _OK == retval )
		retval = DetectScanner( ps, asic );
	else
		ptdrvStartLampTimer( ps );

	return retval;
}

/*.............................................................................
 * initialize the driver
 * allocate memory for the ScanData structure and do some presets
 */
static int ptdrvInit( int devno )
{
	int       retval;
	pScanData ps;

	DBG( DBG_HIGH, "ptdrvInit(%u)\n", devno );

	if( devno >= _MAX_PTDEVS )
		return _E_NO_DEV;

	/*
	 * allocate memory for our large ScanData-structure
	 */
	ps = MiscAllocAndInitStruct();
	if( NULL == ps ) {
		return _E_ALLOC;
	}

#ifdef __KERNEL__
	if( _TRUE == slowIO[devno] ) {
		DBG( DBG_LOW, "Using slow I/O\n" );
		ps->IO.slowIO = _TRUE;
		ps->IO.fnOut  = IOOutDelayed;
		ps->IO.fnIn   = IOInDelayed;
	} else {
		DBG( DBG_LOW, "Using fast I/O\n" );
		ps->IO.slowIO = _FALSE;
		ps->IO.fnOut  = IOOut;
		ps->IO.fnIn   = IOIn;
	}
#endif
	ps->ModelOverride = mov[devno];
	ps->warmup        = warmup[devno];
	ps->lampoff       = lampoff[devno];
	ps->lOffonEnd     = lOffonEnd[devno];
	ps->IO.forceMode  = forceMode[devno];
	ps->devno         = devno;

	/* assign it right here, to allow correct shutdown */
	PtDrvDevices[devno] = ps;

	/*
	 * try to register the port
	 */
	retval = MiscRegisterPort( ps, port[devno] );

	if( _OK == retval ) {
		retval = ptdrvOpen( ps, port[devno] );
	}

	/*
	 * try to detect a scanner...
	 */
	if( _OK == retval ) {
		retval = DetectScanner( ps, 0 );

		/* do this here before releasing the port */
		if( _OK == retval ) {
			ptDrvSwitchLampOn( ps );
		}
		ptdrvClose( ps );
	}

	if( _OK == retval ) {

#ifdef __KERNEL__
		_PRINT( "pt_drv%u: %s found on port 0x%04x\n",
			 devno, MiscGetModelName(ps->sCaps.Model), ps->IO.pbSppDataPort );
#else
		DBG( DBG_LOW, "pt_drv%u: %s found\n",
									 devno, MiscGetModelName(ps->sCaps.Model));
#endif

		/*
		 * initialize the timespan timer
	     */
		MiscStartTimer( &toTimer[ps->devno], (_SECOND * ps->warmup));

		if( 0 == ps->lampoff )
#ifdef __KERNEL__
		_PRINT(
#else
		DBG( DBG_LOW,
#endif
					"pt_drv%u: Lamp-Timer switched off.\n", devno );
		else {
#ifdef __KERNEL__
		_PRINT(
#else
		DBG( DBG_LOW,
#endif
					"pt_drv%u: Lamp-Timer set to %u seconds.\n",
														devno, ps->lampoff );
		}

#ifdef __KERNEL__
		_PRINT(
#else
		DBG( DBG_LOW,
#endif
				"pt_drv%u: WarmUp period set to %u seconds.\n",
														devno, ps->warmup );

		if( 0 == ps->lOffonEnd ) {
#ifdef __KERNEL__
		_PRINT(
#else
		DBG( DBG_LOW,
#endif
				"pt_drv%u: Lamp untouched on driver unload.\n", devno );
		} else {
#ifdef __KERNEL__
		_PRINT(
#else
		DBG( DBG_LOW,
#endif
				"pt_drv%u: Lamp switch-off on driver unload.\n", devno );
		}

		ptdrvStartLampTimer( ps );
	}

	return retval;
}

/*.............................................................................
 * shutdown the driver:
 * switch the lights out
 * stop the motor
 * free memory
 */
static int ptdrvShutdown( pScanData ps )
{
	int devno;

	DBG( DBG_HIGH, "ptdrvShutdown()\n" );

	if( NULL == ps )
		return _E_NULLPTR;

	devno = ps->devno;

	DBG( DBG_HIGH, "cleanup device %u\n", devno );

	if( _NO_BASE != ps->sCaps.wIOBase ) {

		ptdrvStopLampTimer( ps );

		if( _OK == MiscClaimPort(ps)) {

			ps->PutToIdleMode( ps );

			if( 0 != ps->lOffonEnd ) {
				if( _IS_ASIC98(ps->sCaps.AsicID)) {
		            ps->AsicReg.RD_ScanControl &= ~_SCAN_LAMPS_ON;
				} else {
					ps->AsicReg.RD_ScanControl &= ~_SCAN_LAMP_ON;
	        	}
				IOCmdRegisterToScanner( ps, ps->RegScanControl,
											  ps->AsicReg.RD_ScanControl );
			}
		}
		MiscReleasePort( ps );
	}

	/* unregister the driver
	 */
	MiscUnregisterPort( ps );

	_KFREE( ps );
	if( devno < _MAX_PTDEVS )
		PtDrvDevices[devno] = NULL;

	return _OK;
}

/*.............................................................................
 * the IOCTL interface
 */
static int ptdrvIoctl( pScanData ps, UInt cmd, pVoid arg )
{
	UShort dir;
	UShort version;
	UInt   size;
	ULong  argVal;
	int    cancel;
	int    retval;

	/*
 	 * do the preliminary stuff here
	 */
	if( NULL == ps )
		return _E_NULLPTR;

	retval = _OK;

	dir  = _IOC_DIR(cmd);
	size = _IOC_SIZE(cmd);

	if ((_IOC_WRITE == dir) && size && (size <= sizeof(ULong))) {

    	if (( retval = getUserPtr( arg, &argVal, size))) {
			DBG( DBG_HIGH, "ioctl() failed - result = %i\n", retval );
      		return retval;
		}
	}

	switch( cmd ) {

	/* open */
    case _PTDRV_OPEN_DEVICE:
		DBG( DBG_LOW, "ioctl(_PTDRV_OPEN_DEVICE)\n" );
   	  	if (copy_from_user(&version, arg, sizeof(UShort)))
			return _E_FAULT;

		if( _PTDRV_IOCTL_VERSION != version ) {
			DBG( DBG_HIGH, "Version mismatch: Backend=0x%04X(0x%04X)",
							version, _PTDRV_IOCTL_VERSION );
			return _E_VERSION;
		}

		retval = ptdrvOpenDevice( ps );
      	break;

	/* close */
	case _PTDRV_CLOSE_DEVICE:
		DBG( DBG_LOW,  "ioctl(_PTDRV_CLOSE_DEVICE)\n" );

	    if ( NULL != ps->driverbuf ) {
			DBG( DBG_LOW, "*** cleanup buffers ***\n" );
	        _VFREE( ps->driverbuf );
    	    ps->driverbuf = NULL;
	    }

    	if ( NULL != ps->Shade.pHilight ) {
        	_VFREE( ps->Shade.pHilight );
	        ps->Shade.pHilight = NULL;
    	}

		ps->PutToIdleMode( ps );
		ptdrvStartLampTimer( ps );
      	break;

	/* get caps - no scanner connection necessary */
    case _PTDRV_GET_CAPABILITIES:
		DBG( DBG_LOW, "ioctl(_PTDRV_GET_CAPABILITES)\n" );

    	return putUserPtr( &ps->sCaps, arg, size);
      	break;

	/* get lens-info - no scanner connection necessary */
    case _PTDRV_GET_LENSINFO:
		DBG( DBG_LOW, "ioctl(_PTDRV_GET_LENSINFO)\n" );

      	return putUserPtr( &ps->LensInf, arg, size);
      	break;

	/* put the image info - no scanner connection necessary */
    case _PTDRV_PUT_IMAGEINFO:
      	{
            short  tmpcx, tmpcy;
      		ImgDef img;

			DBG( DBG_LOW, "ioctl(_PTDRV_PUT_IMAGEINFO)\n" );
			if (copy_from_user( &img, (pImgDef)arg, size))
				return _E_FAULT;

            tmpcx = (short)img.crArea.cx;
            tmpcy = (short)img.crArea.cy;

			if(( 0 >= tmpcx ) || ( 0 >= tmpcy )) {
				DBG( DBG_LOW, "CX or CY <= 0!!\n" );
				return _E_INVALID;
			}

			_ASSERT( ps->GetImageInfo );
	      	ps->GetImageInfo( ps, &img );
		}
      	break;

	/* get crop area - no scanner connection necessary */
    case _PTDRV_GET_CROPINFO:
    	{
      		CropInfo	outBuffer;
      		pCropInfo	pcInf = &outBuffer;

			DBG( DBG_LOW, "ioctl(_PTDRV_GET_CROPINFO)\n" );

			memset( pcInf, 0, sizeof(CropInfo));

	      	pcInf->dwPixelsPerLine = ps->DataInf.dwAppPixelsPerLine;
    	  	pcInf->dwBytesPerLine  = ps->DataInf.dwAppBytesPerLine;
      		pcInf->dwLinesPerArea  = ps->DataInf.dwAppLinesPerArea;
      		return putUserPtr( pcInf, arg, size );
      	}
      	break;

	/* adjust the driver settings */
	case _PTDRV_ADJUST:
		{
			PPAdjDef adj;

			DBG( DBG_LOW, "ioctl(_PTDRV_ADJUST)\n" );

			if (copy_from_user(&adj, (pPPAdjDef)arg, sizeof(PPAdjDef)))
				return _E_FAULT;

			DBG( DBG_LOW, "Adjusting device %u\n", ps->devno );
			DBG( DBG_LOW, "warmup:       %i\n", adj.warmup );
			DBG( DBG_LOW, "lampOff:      %i\n", adj.lampOff );
			DBG( DBG_LOW, "lampOffOnEnd: %i\n", adj.lampOffOnEnd );

			if( ps->devno < _MAX_PTDEVS ) {

				if( adj.warmup >= 0 ) {
					warmup[ps->devno] = adj.warmup;
					ps->warmup        = adj.warmup;
				}

				if( adj.lampOff >= 0 ) {
					lampoff[ps->devno] = adj.lampOff;
					ps->lampoff        = adj.lampOff;
				}

				if( adj.lampOffOnEnd >= 0 ) {
					lOffonEnd[ps->devno] = adj.lampOffOnEnd;
					ps->lOffonEnd        = adj.lampOffOnEnd;
				}
			}
		}
		break;

	/* set a specific map (r,g,b or gray) */
	case _PTDRV_SETMAP:
		{
			int     i, x_len;
			MapDef  map;

			DBG( DBG_LOW, "ioctl(_PTDRV_SETMAP)\n" );

			if (copy_from_user( &map, (pMapDef)arg, sizeof(MapDef)))
				return _E_FAULT;

			DBG( DBG_LOW, "maplen=%u, mapid=%u, addr=0x%08lx\n",
							map.len, map.map_id, (u_long)map.map );

			x_len = 256;
			if( _IS_ASIC98(ps->sCaps.AsicID))
				x_len = 4096;

			/* check for 0 pointer and len */
			if((NULL == map.map) || (x_len != map.len)) {
				DBG( DBG_LOW, "map pointer == 0, or map len invalid!!\n" );
				return _E_INVALID;
			}

    		if( _MAP_MASTER == map.map_id ) {

				for( i = 0; i < 3; i++ ) {
					if (copy_from_user((pVoid)&ps->a_bMapTable[x_len * i],
					                    map.map, x_len )) {
						return _E_FAULT;
					}
				}
			} else {

				u_long idx = 0;
				if( map.map_id == _MAP_GREEN )
					idx = 1;
				if( map.map_id == _MAP_BLUE )
					idx = 2;

				if (copy_from_user((pVoid)&ps->a_bMapTable[x_len * idx],
				                   map.map, x_len )) {
						return _E_FAULT;
				}
			}

			/* here we adjust the maps according to
			 * the brightness and contrast settings
			 */
			MapAdjust( ps, map.map_id );
		}
		break;

	/* set environment - no scanner connection necessary */
    case _PTDRV_SET_ENV:
      	{
			ScanInfo sInf;

			DBG( DBG_LOW, "ioctl(_PTDRV_SET_ENV)\n" );

			if (copy_from_user(&sInf, (pScanInfo)arg, sizeof(ScanInfo)))
				return _E_FAULT;

			/*
			 * to make the OpticPro 4800P work, we need to invert the
			 * Inverse flag
			 */
			if( _ASIC_IS_96001 == ps->sCaps.AsicID ) {
				if( SCANDEF_Inverse & sInf.ImgDef.dwFlag )
					sInf.ImgDef.dwFlag &= ~SCANDEF_Inverse;
				else
					sInf.ImgDef.dwFlag |= SCANDEF_Inverse;
			}

			_ASSERT( ps->SetupScanSettings );
      		retval = ps->SetupScanSettings( ps, &sInf );

			/* CHANGE preset map here */
			if( _OK == retval ) {
				MapInitialize ( ps );
				MapSetupDither( ps );

				ps->DataInf.dwVxdFlag |= _VF_ENVIRONMENT_READY;

				if (copy_to_user((pScanInfo)arg, &sInf, sizeof(ScanInfo)))
					return _E_FAULT;
			}
		}
		break;

	/* start scan */
	case _PTDRV_START_SCAN:
		{
			StartScan  outBuffer;
			pStartScan pstart = (pStartScan)&outBuffer;

			DBG( DBG_LOW, "ioctl(_PTDRV_START_SCAN)\n" );

			retval = IOIsReadyForScan( ps );
			if( _OK == retval ) {

				ps->dwDitherIndex      = 0;
				ps->fScanningStatus    = _TRUE;
				pstart->dwBytesPerLine = ps->DataInf.dwAppBytesPerLine;
				pstart->dwLinesPerScan = ps->DataInf.dwAppLinesPerArea;
				pstart->dwFlag 		   = ps->DataInf.dwScanFlag;

				ps->DataInf.dwVxdFlag |= _VF_FIRSTSCANLINE;
				ps->DataInf.dwScanFlag&=~(_SCANNER_SCANNING|_SCANNER_PAPEROUT);

				if (copy_to_user((pStartScan)arg, pstart, sizeof(StartScan)))
					return _E_FAULT;
			}
		}
		break;

	/* stop scan */
    case _PTDRV_STOP_SCAN:

		DBG( DBG_LOW, "ioctl(_PTDRV_STOP_SCAN)\n" );

		if (copy_from_user(&cancel, arg, sizeof(short)))
			return _E_FAULT;

		/* we may use this to abort scanning! */
		ps->fScanningStatus = _FALSE;

		/* when using this to cancel, then that's all */
		if( _FALSE == cancel ) {

			MotorToHomePosition( ps );

    		ps->DataInf.dwAppLinesPerArea = 0;
      		ps->DataInf.dwScanFlag &= ~_SCANNER_SCANNING;

			/* if environment was never set */
    	  	if (!(ps->DataInf.dwVxdFlag & _VF_ENVIRONMENT_READY))
        		retval = _E_SEQUENCE;

	      	ps->DataInf.dwVxdFlag &= ~_VF_ENVIRONMENT_READY;

		} else {
			DBG( DBG_LOW, "CANCEL Mode set\n" );
		}
		retval = putUserVal(retval, arg, size);
      	break;

	/* read the flag status register, when reading the action button, you must
	 * only do this call and none of the other ioctl's
     * like open, etc or it will always show up as "1"
	 */
	case _PTDRV_ACTION_BUTTON:
		DBG( DBG_LOW, "ioctl(_PTDRV_ACTION_BUTTON)\n" );
		IODataRegisterFromScanner( ps, ps->RegStatus );
      	retval = putUserVal( argVal, arg, size );
		break;

	default:
		retval = _E_NOSUPP;
      	break;
	}

	return retval;
}

/*.............................................................................
 * read the data
 */
static int ptdrvRead( pScanData ps, pUChar buffer, int count )
{
	pUChar	scaleBuf;
	ULong	dwLinesRead = 0;
	int 	retval      = _OK;

#ifdef _ASIC_98001_SIM
#ifdef __KERNEL__
		_PRINT(
#else
		DBG( DBG_LOW,
#endif
					"pt_drv : Software-Emulation active, can't read!\n" );
	return _E_INVALID;
#endif

	if((NULL == buffer) || (NULL == ps)) {
#ifdef __KERNEL__
		_PRINT(
#else
		DBG( DBG_HIGH,
#endif
						"pt_drv :  Internal NULL-pointer!\n" );
		return _E_NULLPTR;
	}

	if( 0 == count ) {
#ifdef __KERNEL__
		_PRINT(
#else
		DBG( DBG_HIGH,
#endif
			"pt_drv%u: reading 0 bytes makes no sense!\n", ps->devno );
		return _E_INVALID;
	}

	if( _FALSE == ps->fScanningStatus )
		return _E_ABORT;

	/*
	 * has the environment been set ?
	 * this should prevent the driver from causing a seg-fault
	 * when using the cat /dev/pt_drv command!
	 */
   	if (!(ps->DataInf.dwVxdFlag & _VF_ENVIRONMENT_READY)) {
#ifdef __KERNEL__
		_PRINT(
#else
		DBG( DBG_HIGH,
#endif
			"pt_drv%u:  Cannot read, driver not initialized!\n",ps->devno);
		return _E_SEQUENCE;
	}

	/*
	 * get some memory
	 */
	ps->Scan.bp.pMonoBuf = _KALLOC( ps->DataInf.dwAppPhyBytesPerLine, GFP_KERNEL);

	if ( NULL == ps->Scan.bp.pMonoBuf ) {
#ifdef __KERNEL__
		_PRINT(
#else
		DBG( DBG_HIGH,
#endif
			"pt_drv%u:  Not enough memory available!\n", ps->devno );
    	return _E_ALLOC;
	}

	/* if we have to do some scaling, we need another buffer... */
	if( ps->DataInf.XYRatio > 1000 ) {

		scaleBuf = _KALLOC( ps->DataInf.dwAppPhyBytesPerLine, GFP_KERNEL);
		if ( NULL == scaleBuf ) {
			_KFREE( ps->Scan.bp.pMonoBuf );
#ifdef __KERNEL__
		_PRINT(
#else
		DBG( DBG_HIGH,
#endif
			"pt_drv%u:  Not enough memory available!\n", ps->devno );
    		return _E_ALLOC;
		}
	} else {
		scaleBuf = NULL;
	}

	DBG( DBG_LOW, "PtDrvRead(%u bytes)*****************\n", count );
	DBG( DBG_LOW, "MonoBuf = 0x%08lx[%u], scaleBuf = 0x%lx\n",
			(unsigned long)ps->Scan.bp.pMonoBuf,
            ps->DataInf.dwAppPhyBytesPerLine, (unsigned long)scaleBuf );

	/*
	 * in case of a previous problem, move the sensor back home
	 */
  	MotorToHomePosition( ps );

	if( _FALSE == ps->fScanningStatus ) {
		retval = _E_ABORT;
		goto ReadFinished;
	}

	dwLinesRead = 0;

	/*
	 * first of all calibrate the show
	 */
	ps->bMoveDataOutFlag   = _DataInNormalState;
   	ps->fHalfStepTableFlag = _FALSE;
    ps->fReshaded          = _FALSE;
   	ps->fScanningStatus    = _TRUE;

    if( _ASIC_IS_98003 == ps->sCaps.AsicID )
        ps->Scan.fRefreshState = _FALSE;
    else
        ps->Scan.fRefreshState = _TRUE;

    ptdrvLampWarmup( ps );

	if( _FALSE == ps->fScanningStatus ) {
		retval = _E_ABORT;
		goto ReadFinished;
	}

    retval = ps->Calibration( ps );
	if( _OK != retval ) {
#ifdef __KERNEL__
		_PRINT(
#else
		DBG( DBG_HIGH,
#endif
			"pt_drv%u: calibration failed, result = %i\n",
														ps->devno, retval );
		goto ReadFinished;
	}

    if( _ASIC_IS_98003 == ps->sCaps.AsicID ) {

        ps->OpenScanPath( ps );

    	MotorP98003ForceToLeaveHomePos( ps );
    }

	_ASSERT(ps->SetupScanningCondition);
  	ps->SetupScanningCondition(ps);

    if( _ASIC_IS_98003 != ps->sCaps.AsicID ) {
    	ps->SetMotorSpeed( ps, ps->bCurrentSpeed, _TRUE );
        IOSetToMotorRegister( ps );
    } else {

        ps->WaitForPositionY( ps );
    	_DODELAY( 70 );
    	ps->Scan.bOldScanState = IOGetScanState( ps, _TRUE ) & _SCANSTATE_MASK;
    }

    ps->DataInf.dwScanFlag |= _SCANNER_SCANNING;

	if( _FALSE == ps->fScanningStatus ) {
		DBG( DBG_HIGH, "read aborted!\n" );
		retval = _E_ABORT;
		goto ReadFinished;
	}

	/*
	 * now get the picture data
	 */
	DBG( DBG_HIGH, "dwAppLinesPerArea = %d\n", ps->DataInf.dwAppLinesPerArea);
	DBG( DBG_HIGH, "dwAppBytesPerLine = %d\n", ps->DataInf.dwAppBytesPerLine);

/* HEINER: A3I
	ps->bMoveDataOutFlag = _DataFromStopState;
*/
  	if ( 0 != ps->DataInf.dwAppLinesPerArea ) {

	   	ps->Scan.dwLinesToRead = count / ps->DataInf.dwAppBytesPerLine;

    	if( ps->Scan.dwLinesToRead ) {

        	DBG( DBG_HIGH, "dwLinesToRead = %d\n", ps->Scan.dwLinesToRead );

      		if( ps->Scan.dwLinesToRead > ps->DataInf.dwAppLinesPerArea )
        		ps->Scan.dwLinesToRead = ps->DataInf.dwAppLinesPerArea;

      		ps->DataInf.dwAppLinesPerArea -= ps->Scan.dwLinesToRead;

      		if (ps->DataInf.dwScanFlag & SCANDEF_BmpStyle)
        		buffer += ((ps->Scan.dwLinesToRead - 1) *
                   				ps->DataInf.dwAppBytesPerLine);

      		if (ps->DataInf.dwVxdFlag & _VF_DATATOUSERBUFFER)
        		ps->DataInf.pCurrentBuffer = ps->Scan.bp.pMonoBuf;

      		while(ps->fScanningStatus && ps->Scan.dwLinesToRead) {

        		_ASSERT(ps->ReadOneImageLine);
		   		if (!ps->ReadOneImageLine(ps)) {
        			ps->fScanningStatus = _FALSE;
            		DBG( DBG_HIGH, "ReadOneImageLine() failed at line %u!\n",
                                    dwLinesRead );
					break;
				}

				/*
				 * as we might scan images that exceed the CCD-capabilities
				 * in x-resolution, we have to enlarge the line data
				 * i.e.: scanning at 1200dpi generates on a P9636 600 dpi in
				 *       x-direction but 1200dpi in y-direction...
				 */
				if( NULL != scaleBuf ) {
					ScaleX( ps, ps->Scan.bp.pMonoBuf, scaleBuf );
	    	    	if (copy_to_user( buffer, scaleBuf,
									ps->DataInf.dwAppPhyBytesPerLine)) {
						return _E_FAULT;
					}
				} else {
	    	    	if (copy_to_user( buffer, ps->Scan.bp.pMonoBuf,
								  ps->DataInf.dwAppPhyBytesPerLine)) {
						return _E_FAULT;
					}
				}

				buffer += ps->Scan.lBufferAdjust;
				dwLinesRead++;
				ps->Scan.dwLinesToRead--;

				/* needed, esp. to avoid freezing the system in SPP mode */
#ifdef __KERNEL__
				schedule();
/*#else
				sched_yield();
*/
#endif
        	}

			if (ps->fScanningStatus) {

            	if( _IS_ASIC96(ps->sCaps.AsicID))
          			MotorP96SetSpeedToStopProc(ps);

			} else {
        		if (ps->DataInf.dwScanFlag & (SCANDEF_StopWhenPaperOut |
                    	                         SCANDEF_UnlimitLength)) {
          			ps->DataInf.dwAppLinesPerArea = 0;
				} else {
        			if (ps->DataInf.dwScanFlag & SCANDEF_BmpStyle)
            			buffer -= (ps->DataInf.dwAppBytesPerLine *
                	    		   (ps->Scan.dwLinesToRead - 1));
          			memset( buffer, 0xff,
          		   			ps->Scan.dwLinesToRead * ps->DataInf.dwAppBytesPerLine );
        	  		dwLinesRead += ps->Scan.dwLinesToRead;
    	    	}
	        }

      	} else {
      		retval = _E_INTERNAL;
		}
	}

	if( _FALSE == ps->fScanningStatus ) {
		DBG( DBG_HIGH, "read aborted!\n" );
		retval = _E_ABORT;
	}

ReadFinished:


	if( _ASIC_IS_98003 == ps->sCaps.AsicID )
		ps->CloseScanPath( ps );

	if( NULL != ps->Scan.bp.pMonoBuf )
		_KFREE( ps->Scan.bp.pMonoBuf );

	if( NULL != scaleBuf )
		_KFREE( scaleBuf );

	/*
	 * on success return number of bytes red
	 */
	if ( _OK == retval )
    	return (ps->DataInf.dwAppPhyBytesPerLine * dwLinesRead);

   	return retval;
}

/*************************** the module interface ****************************/

#ifdef __KERNEL__		/* the kernel module interface */

/* Designed to be used as a module */
#ifdef MODULE

/*.............................................................................
 * gets called upon module initialization
 */
#ifdef LINUX_26
static int __init ptdrv_init( void )
#else
int init_module( void )
#endif
{
    UInt devCount;
    UInt i;
    int  retval = _OK;
    int  result = _OK;
#if (defined(CONFIG_DEVFS_FS) && !defined(DEVFS_26_STYLE))
    char controlname[24];
#endif
# ifdef LINUX_26
    char devname[20];
#endif

    DBG( DBG_HIGH, "*********************************************\n" );
    DBG( DBG_HIGH, "pt_drv: init_module()\n" );

#if (defined(CONFIG_DEVFS_FS) && !defined(DEVFS_26_STYLE))
	devfs_handle = devfs_mk_dir(NULL, "scanner", NULL);
	if( devfs_register_chrdev(_PTDRV_MAJOR, _DRV_NAME, &pt_drv_fops)) {
#else
	if( register_chrdev(_PTDRV_MAJOR, _DRV_NAME, &pt_drv_fops)) {
#endif

		_PRINT(KERN_INFO "pt_drv: unable to get major %d for pt_drv devices\n",
		       _PTDRV_MAJOR);
		return -EIO;
	}
	printk( KERN_INFO "pt_drv : driver version "_PTDRV_VERSTR"\n" );

#if !defined (CONFIG_DEVFS_FS) && defined (LINUX_26)
	ptdrv_class = class_create(THIS_MODULE, "scanner");
	if (IS_ERR(ptdrv_class))
		goto out_devfs;
#endif

	/* register the proc_fs */
	ProcFsInitialize();

	/* go through the list of defined ports and try to find a device
	 */
	devCount = 0;
	for( i = 0; i < _MAX_PTDEVS; i++ ) {

		if( 0 != port[i] ) {
			result = ptdrvInit( i );

			if ( _OK == result ) {
		    	PtDrvDevices[i]->flags |= _PTDRV_INITALIZED;

#ifdef CONFIG_DEVFS_FS
# ifndef DEVFS_26_STYLE
				sprintf( controlname, "scanner/pt_drv%d", devCount );
				devfs_register( NULL, controlname,
				                DEVFS_FL_DEFAULT, _PTDRV_MAJOR, 0,
			                    (S_IFCHR | S_IRUGO | S_IWUGO | S_IFCHR),
				                &pt_drv_fops, NULL );
# else /* DEVFS_26_STYLE */
				devfs_mk_cdev(MKDEV(_PTDRV_MAJOR, devCount),
				    (S_IFCHR | S_IRUGO | S_IWUGO | S_IFCHR),
				    "scanner/pt_drv%d", devCount);
# endif
#else
# ifdef LINUX_26
				sprintf(devname, "pt_drv%d", devCount);
				CLASS_DEV_CREATE(ptdrv_class,
				                 MKDEV(_PTDRV_MAJOR, devCount), NULL,
				                 devname);

# endif /* LINUX_26 */
#endif /* CONFIG_DEVFS_FS */
				ProcFsRegisterDevice( PtDrvDevices[i] );
				devCount++;
			} else {
				retval = result;
				ptdrvShutdown( PtDrvDevices[i] );
				PtDrvDevices[i] = NULL;
			}
		}
	}

	/* * if something went wrong, shutdown all... */
	if( devCount == 0 ) {

#if !defined (CONFIG_DEVFS_FS) && defined (LINUX_26)
out_devfs:
		class_destroy(ptdrv_class);
#endif

#if (defined(CONFIG_DEVFS_FS) && !defined(DEVFS_26_STYLE))
		devfs_unregister_chrdev( _PTDRV_MAJOR, _DRV_NAME );
#else
		unregister_chrdev( _PTDRV_MAJOR, _DRV_NAME );
#endif
		ProcFsShutdown();

#ifdef __KERNEL__
		_PRINT( KERN_INFO "pt_drv : no device(s) detected, (%i)\n", retval );
#endif

	} else {

		DBG( DBG_HIGH, "pt_drv : init done, %u device(s) found\n", devCount );
		retval = _OK;
	}
	DBG( DBG_HIGH, "---------------------------------------------\n" );

	deviceScanning = _FALSE;
	return retval;
}

/*.............................................................................
 * cleanup the show
 */
#ifdef LINUX_26
static void __exit ptdrv_exit( void )
#else
void cleanup_module( void )
#endif
{
	UInt      i;
	pScanData ps;
#if (defined(CONFIG_DEVFS_FS) && !defined(DEVFS_26_STYLE))
	char           controlname[24];
	devfs_handle_t master;
#endif

	DBG( DBG_HIGH, "pt_drv: cleanup_module()\n" );

	for ( i = 0; i < _MAX_PTDEVS; i++ ) {

		ps = PtDrvDevices[i];
		PtDrvDevices[i] = NULL;

		if ( NULL != ps ) {
#ifdef CONFIG_DEVFS_FS
# ifndef DEVFS_26_STYLE
			sprintf( controlname, "scanner/pt_drv%d", i );
			master = devfs_find_handle( NULL,controlname, 0, 0,
			                            DEVFS_SPECIAL_CHR, 0 );
			devfs_unregister( master );
# else
			devfs_remove("scanner/pt_drv%d", i);
# endif
#else
# ifdef LINUX_26
			CLASS_DEV_DESTROY(ptdrv_class, MKDEV(_PTDRV_MAJOR, i));
# endif /* LINUX_26 */
#endif /* CONFIG_DEVFS_FS */
			ptdrvShutdown( ps );
			ProcFsUnregisterDevice( ps );
		}
	}

#if (defined(CONFIG_DEVFS_FS) && !defined(DEVFS_26_STYLE))
	devfs_unregister_chrdev( _PTDRV_MAJOR, _DRV_NAME );
#else
	unregister_chrdev( _PTDRV_MAJOR, _DRV_NAME );
#endif
	ProcFsShutdown();

#if !defined (CONFIG_DEVFS_FS) && defined (LINUX_26)
	class_destroy(ptdrv_class);
#endif

	DBG( DBG_HIGH, "pt_drv: cleanup done.\n" );
	DBG( DBG_HIGH, "*********************************************\n" );
}

#ifdef LINUX_26
module_init(ptdrv_init);
module_exit(ptdrv_exit);
#endif

#endif /*MODULE*/


/*.............................................................................
 * device open...
 */
static int pt_drv_open(struct inode *inode, struct file *file)
{
	pScanData ps;

	DBG( DBG_HIGH, "pt_drv_open()\n" );

	ps = get_pt_from_inode(inode);

	if ( NULL == ps ) {
		return(-ENXIO);
	}

	/* device not found ? */
	if (!(ps->flags & _PTDRV_INITALIZED)) {
		return(-ENXIO);
	}

	/* device is busy ? */
	if (ps->flags & _PTDRV_OPEN) {
		return(-EBUSY);
	}

#ifdef LINUX_26
	if (!try_module_get(THIS_MODULE))
		return -EAGAIN;
#else
	MOD_INC_USE_COUNT;
#endif
	ps->flags |= _PTDRV_OPEN;

	return _OK;
}

/*.............................................................................
 * device close...
 */
static CLOSETYPE pt_drv_close(struct inode * inode, struct file * file)
{
	pScanData ps;

	DBG( DBG_HIGH, "pt_drv_close()\n" );

	if ((ps = get_pt_from_inode(inode)) ) {

		ptdrvClose( ps );

    	ps->flags &= ~_PTDRV_OPEN;
#ifdef LINUX_26
		module_put(THIS_MODULE);
#else
    	MOD_DEC_USE_COUNT;
#endif
	    CLOSERETURN(0);
	} else {

		DBG( DBG_HIGH, "pt_drv: - close failed!\n" );
		CLOSERETURN(-ENXIO);
	}
}

/*.............................................................................
 * read data from device
 */
#ifdef LINUX_20
static int pt_drv_read(struct inode *inode, struct file *file,
                       char *buffer, int count)
{
	int		  result;
	pScanData ps;

	if ( !(ps = get_pt_from_inode(inode)))
    	return(-ENXIO);
#else
static ssize_t pt_drv_read( struct file *file,
                             char *buffer, size_t count, loff_t *tmp )
{
	int       result;
	pScanData ps;

	if ( !(ps = get_pt_from_inode(file->f_dentry->d_inode)) )
		return(-ENXIO);
#endif
	if ((result = verify_area_20(VERIFY_WRITE, buffer, count)))
		return result;

	/*
	 * as the driver contains some global vars, it is not
	 * possible to scan simultaenously with two or more devices
	 */
	if( _TRUE == deviceScanning ) {
	    printk( KERN_INFO "pt_drv: device %u busy!!!\n", ps->devno );
		return(-EBUSY);
	}

	deviceScanning = _TRUE;

	result = ptdrvRead( ps, buffer, count );

	deviceScanning = _FALSE;
	return result;
}

/*.............................................................................
 * writing makes no sense
 */
#ifdef LINUX_20
static int pt_drv_write(struct inode * inode, struct file * file,
                        const char * buffer, int count)
{
  return -EPERM;
}
#else
 static ssize_t pt_drv_write( struct file * file,const char * buffer,
                              size_t tmp,loff_t* count)
{
  return -EPERM;
}
#endif

/*.............................................................................
 * the ioctl interface
 */
#ifdef NOLOCK_IOCTL
static long pt_drv_ioctl( struct file *file, UInt cmd, unsigned long arg )
{
	pScanData ps;

	if ( !(ps = get_pt_from_inode(file->f_dentry->d_inode)) )
    	return(-ENXIO);

  	return ptdrvIoctl( ps, cmd, (pVoid)arg);
}
#else
static int pt_drv_ioctl( struct inode *inode, struct file *file,
                         UInt cmd, unsigned long arg )
{
	pScanData ps;

	if ( !(ps = get_pt_from_inode(inode)) )
    	return(-ENXIO);

  	return ptdrvIoctl( ps, cmd, (pVoid)arg);
}
#endif

#else	/* the user-mode interface */

/*.............................................................................
 * here we only have wrapper functions
 */
static int PtDrvInit( const char *dev_name, UShort model_override )
{
	int fd;
	int result = _OK;

	if( _TRUE == PtDrvInitialized )
		return _OK;

	result = sanei_pp_open( dev_name, &fd );
	if( SANE_STATUS_GOOD != result )
		return result;

	port[0] = fd;
	mov[0]  = model_override;

	result = ptdrvInit( 0 );

	if( _OK == result ) {
		PtDrvInitialized = _TRUE;
	} else {
		ptdrvShutdown( PtDrvDevices[0] );
	}

	return result;
}

static int PtDrvShutdown( void )
{
	int result;

	if( _FALSE == PtDrvInitialized )
		return _E_NOT_INIT;

	result = ptdrvShutdown( PtDrvDevices[0] );

	PtDrvInitialized = _FALSE;

	return result;
}

static int PtDrvOpen( void )
{
	if( _FALSE == PtDrvInitialized )
		return _E_NOT_INIT;

	return _OK;
}

static int PtDrvClose( void )
{
	if( _FALSE == PtDrvInitialized )
		return _E_NOT_INIT;

	return ptdrvClose( PtDrvDevices[0] );
}

static int PtDrvIoctl( UInt cmd, pVoid arg )
{
	if( _FALSE == PtDrvInitialized )
		return _E_NOT_INIT;

	return ptdrvIoctl( PtDrvDevices[0], cmd, arg);
}

static int PtDrvRead ( pUChar buffer, int count )
{
	if( _FALSE == PtDrvInitialized )
		return _E_NOT_INIT;

	return ptdrvRead( PtDrvDevices[0], buffer, count );
}

#endif /* guard __KERNEL__ */

/* END PLUSTEK-PP_PTDRV.C ...................................................*/