/* @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, see <https://www.gnu.org/licenses/>.
 *
 * 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>
 */
#include "plustek-pp_scan.h"

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

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

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

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

static Bool	PtDrvInitialized = _FALSE;
#ifdef HAVE_SETITIMER
static struct itimerval saveSettings;
#endif


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

static void ptdrvStartLampTimer( pScanData ps );

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

/** 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;

	switch (size) {
	default:
		memcpy( where, useraddr, size );
	}
	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;

	memcpy( useraddr, ptr, size );

	return err;
}

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

/**
 */
static int putUserVal(const ULong value, pVoid useraddr, UInt size)
{
	if (NULL == useraddr)
    	return _E_INVALID;

	switch (size) {

	case sizeof(UChar):
		*(pUChar)useraddr = (UChar)value;
		break;
	case sizeof(UShort):
		*(pUShort)useraddr = (UShort)value;
		break;
	case sizeof(ULong):
		*(pULong)useraddr = (ULong)value;
		break;

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

/**
 */
static void ptdrvLampTimerIrq( int sig_num )
{
	pScanData ps;

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

    _VAR_NOT_USED( sig_num );
	ps = PtDrvDevices[0];

	/*
	 * 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 )
{
	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

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

/**
 */
static void ptdrvStopLampTimer( pScanData ps )
{
	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

	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;

    int pd;

	/*
	 * push some values from the struct
     */
	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)
	 */
	ps->pardev          = pd;
	ps->bLastLampStatus = lastStat;
	ps->IO.lastPortMode = lastMode;
	ps->devno           = devno;
	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;
	}

	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 ) {

		DBG( DBG_LOW, "pt_drv%u: %s found\n",
									 devno, MiscGetModelName(ps->sCaps.Model));

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

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

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

		if( 0 == ps->lOffonEnd ) {
		DBG( DBG_LOW,
				"pt_drv%u: Lamp untouched on driver unload.\n", devno );
		} else {
		DBG( DBG_LOW,
				"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
		DBG( DBG_LOW,
					"pt_drv : Software-Emulation active, can't read!\n" );
	return _E_INVALID;
#endif

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

	if( 0 == count ) {
		DBG( DBG_HIGH,
			"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)) {
		DBG( DBG_HIGH,
			"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 ) {
		DBG( DBG_HIGH,
			"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 );
		DBG( DBG_HIGH,
			"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 ) {
		DBG( DBG_HIGH,
			"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 */
/*#else
				sched_yield();
*/
        	}

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

/*.............................................................................
 * 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 );
}

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