/*.............................................................................
 * Project : SANE library for Plustek flatbed scanners.
 *.............................................................................
 */

/** @file plustek-usbhw.c
 *  @brief Functions to control the scanner hardware.
 *
 * Based on sources acquired from Plustek Inc.<br>
 * Copyright (C) 2001-2002 Gerhard Jaeger <gerhard@gjaeger.de>
 *
 * History:
 * - 0.40 - starting version of the USB support
 * - 0.41 - added EPSON1250 specific stuff
 *        - added alternative usb_IsScannerReady function
 * - 0.42 - added warmup stuff
 *        - added UMAX 3400 stuff
 *        - fixed problem with minimum wait time...
 * - 0.43 - added usb_switchLamp for non-Plustek devices
 * - 0.44 - added bStepsToReverse and active Pixelstart values
 *        - to resetRegister function
 *        - modified getLampStatus function for CIS devices
 *        - added usb_Wait4Warmup()
 *        - moved usb_IsEscPressed to this file
 *        - added usb_switchLampX
 *        - do now not reinitialized MISC I/O pins upon reset registers
 * - 0.45 - added function usb_AdjustLamps() to tweak CIS lamp settings
 * .
 * <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>
 */
 
#include <sys/time.h>

#define DEV_LampReflection      1
#define DEV_LampTPA             2
#define DEV_LampAll             3
#define DEV_LampPositive        4
#define DEV_LampNegative        5

/* HEINER: check the tick counts 'cause 1 tick on NT is about 10ms */

static u_long dwCrystalFrequency = 48000000UL;

static SANE_Bool fModuleFirstHome;  /* HEINER: this has to be initialized */
static SANE_Bool fLastScanIsAdf;
static u_char    a_bRegs[0x80];     /**< our global register file */


/** usb_GetMotorSet
 * according to the model, the function returns the address of
 * the corresponding entry of the Motor table
 */
static pClkMotorDef usb_GetMotorSet( eModelDef model )
{
	int i;

	for( i = 0; i < MODEL_LAST; i++ ) {
		if( model == Motors[i].motorModel ) {
			return &(Motors[i]);
		}
	}

	return NULL;
}

/** switch motor on or off
 * @param handle - handle to open USB device
 * @param fOn    - SANE_TRUE means motor on, SANE_FALSE means motor off
 * @return always SANE_TRUE
 */
static SANE_Bool usb_MotorOn( int handle, SANE_Bool fOn )
{
	if( fOn )
		a_bRegs[0x45] |= 0x10;
	else
		a_bRegs[0x45] &= ~0x10;

	usbio_WriteReg( handle, 0x45, a_bRegs[0x45] );

	return SANE_TRUE;
}

/*.............................................................................
 *
 */
static SANE_Bool usb_IsScannerReady( pPlustek_Device dev )
{
	u_char value;
	double 		   len;	
	long           timeout;
    struct timeval t;

	/* time in s = 1000*scanner length in inches/max step speed/in */
	len = (dev->usbDev.Caps.Normal.Size.y/(double)_MEASURE_BASE) + 5;
	len = (1000.0 * len)/dev->usbDev.HwSetting.dMaxMoveSpeed;
	len /= 1000.0;

	/* wait at least 10 seconds... */
	if( len < 10 )
		len = 10;
	
	gettimeofday( &t, NULL);	
	timeout = t.tv_sec + len;

	do {	
		_UIO( usbio_ReadReg( dev->fd, 7, &value));

		if( value == 0 ) {
			_UIO( usbio_ResetLM983x( dev ));
			return SANE_TRUE;
		}

		if((value == 0) || (value >= 0x20) || (value == 0x03)) {

			if( !usbio_WriteReg( dev->fd, 0x07, 0 )) {
				DBG( _DBG_ERROR, "Scanner not ready!!!\n" );
				return SANE_FALSE;
			}
			else
				return SANE_TRUE;	
		}
	
		gettimeofday( &t, NULL);	
		
	} while( t.tv_sec < timeout );		
	
	DBG( _DBG_ERROR, "Scanner not ready!!!\n" );
	return SANE_FALSE;
}

/**
 *
 */
static SANE_Bool usb_SensorAdf( int handle )
{
	u_char value;

	usbio_ReadReg( handle, 0x02, &value );

	return (value & 0x20);
}

/**
 *
 */
static SANE_Bool usb_SensorPaper( int handle )
{
	u_char value;

	usbio_ReadReg( handle, 0x02, &value );

	return (value & 0x02);
}

/**
 * Home sensor always on when backward move.
 * dwStep is steps to move and based on 300 dpi, but
 * if the action is MOVE_Both, it becomes the times
 * to repeatly move the module around the scanner and
 * 0 means forever.
 */
static SANE_Bool usb_ModuleMove( pPlustek_Device dev,
								 u_char bAction, u_long dwStep )
{
	SANE_Status  res;
	u_char       bReg2, reg7, mclk_div;
	u_short      wFastFeedStepSize;
	double       dMaxMoveSpeed;
	pClkMotorDef clk;
	pHWDef       hw = &dev->usbDev.HwSetting;

	if( bAction != MOVE_ToPaperSensor   &&
		bAction != MOVE_EjectAllPapers  &&
		bAction != MOVE_SkipPaperSensor &&
		bAction != MOVE_ToShading       && !dwStep ) {

		return SANE_TRUE;
	}

	if( !usb_IsScannerReady( dev )) {
		DBG( _DBG_ERROR, "Sensor-position NOT reached\n" );
		return SANE_FALSE;
	}

	if( bAction == MOVE_EjectAllPapers ) {

		double d = hw->dMaxMoveSpeed;

		hw->dMaxMoveSpeed += 0.6;

		do {
			if( usb_SensorPaper(dev->fd) &&
							!usb_ModuleMove(dev,MOVE_SkipPaperSensor, 0 )) {
				return SANE_FALSE;
			}

			if( usb_SensorAdf(dev->fd) &&
							!usb_ModuleMove(dev,MOVE_ToPaperSensor, 0 )) {
				return SANE_FALSE;
            }

		} while( usb_SensorPaper(dev->fd));

		if(!usb_ModuleMove( dev, MOVE_Forward, 300 * 3))
			return SANE_FALSE;

		usbio_WriteReg( dev->fd, 0x07, 0);
		usbio_WriteReg( dev->fd, 0x58, a_bRegs[0x58]);

		usbio_ReadReg( dev->fd, 0x02, &bReg2 );
		hw->dMaxMoveSpeed = d;

		return SANE_TRUE;
	}

	usbio_WriteReg( dev->fd, 0x0a, 0 );

	/* Compute fast feed step size, use equation 3 and equation 8 */
	if( bAction == MOVE_ToShading )	{

		double dToShadingSpeed = 0.0; /* HEINER: check callRegistry.GetToShadingSpeed(); */

		if( dToShadingSpeed != 0.0 )
			dMaxMoveSpeed = dToShadingSpeed;
		else {

			if( hw->dMaxMoveSpeed > 0.5 )
				dMaxMoveSpeed = hw->dMaxMoveSpeed - 0.5;
			else
				dMaxMoveSpeed = hw->dMaxMoveSpeed;
		}
	} else {

		dMaxMoveSpeed = hw->dMaxMoveSpeed;
	}

	clk = usb_GetMotorSet( hw->motorModel );

	mclk_div = clk->mclk_fast;

	wFastFeedStepSize = (u_short)(dwCrystalFrequency /
						((u_long)mclk_div * 8UL * 1 *
						dMaxMoveSpeed * 4 * hw->wMotorDpi));

	a_bRegs[0x48] = (u_char)(wFastFeedStepSize >> 8);
	a_bRegs[0x49] = (u_char)(wFastFeedStepSize & 0xFF);

	dwStep = dwStep * hw->wMotorDpi / 300UL;
	a_bRegs[0x4a] = _HIBYTE(_LOWORD(dwStep));
	a_bRegs[0x4b] = _LOBYTE(_LOWORD(dwStep));

	a_bRegs[0x45] |= 0x10;

	DBG( _DBG_INFO2,"MotorDPI=%u, MaxMoveSpeed=%.3f, "
					"FFStepSize=%u, Steps=%lu\n", hw->wMotorDpi,
					hw->dMaxMoveSpeed, wFastFeedStepSize, dwStep );
	DBG( _DBG_INFO2, "MOTOR: "
					"PWM=0x%02x, PWM_DUTY=0x%02x 0x45=0x%02x "
                    "0x48=0x%02x, 0x49=0x%02x \n",
					a_bRegs[0x56], a_bRegs[0x57], a_bRegs[0x45],
					a_bRegs[0x48], a_bRegs[0x49] );

	DBG( _DBG_INFO2,"MCLK_FFW = %u --> 0x%02x\n", mclk_div, (mclk_div-1)*2 );

	/* The setting for chassis moving is:
	 * MCLK divider = 6, 8 bits/pixel, HDPI divider = 12,
	 * no integration time adjustment and 1 channel grayscale
	 */

	/* MCLK divider = 6 */
	if( !usbio_WriteReg(dev->fd, 0x08, (mclk_div-1)*2 /*0x0A*/))
		return SANE_FALSE;

	/* 8 bits/pixel, HDPI divider = 12 */
	if( !usbio_WriteReg(dev->fd, 0x09, 0x1F))
		return SANE_FALSE;

	/* Turn off integration time adjustment */
	if( !usbio_WriteReg(dev->fd, 0x19, 0))
		return SANE_FALSE;

	/* 1 channel grayscale, green channel */
	if( !usbio_WriteReg(dev->fd, 0x26, 0x0C))
		return SANE_FALSE;

	_UIO(sanei_lm983x_write(dev->fd, 0x48, &a_bRegs[0x48], 2, SANE_TRUE));
	_UIO(sanei_lm983x_write(dev->fd, 0x4A, &a_bRegs[0x4A], 2, SANE_TRUE));

	/* disable home */
	if( !usbio_WriteReg(dev->fd, 0x58, a_bRegs[0x58] & ~7))
		return SANE_FALSE;

	if( !usbio_WriteReg(dev->fd, 0x45, a_bRegs[0x45] ))
		return SANE_FALSE;

	if( bAction == MOVE_Forward || bAction == MOVE_ToShading )
		reg7 = 5;
	else if( bAction == MOVE_Backward )
		reg7 = 6;
	else if( bAction == MOVE_ToPaperSensor || bAction == MOVE_EjectAllPapers ||
			 bAction == MOVE_SkipPaperSensor ) {
		reg7 = 1;
	} else {
		return SANE_TRUE;
    }

	if( usbio_WriteReg( dev->fd, 0x07, reg7 )) {

		long           dwTicks;
	    struct timeval start_time, t2;

		res = gettimeofday( &start_time, NULL );	

		/* 20000 NT-Ticks means 200s */
	    dwTicks = start_time.tv_sec + 200;

		if( bAction == MOVE_ToPaperSensor ) {

			for(;;)	{

				if( usb_SensorPaper( dev->fd )) {
					usbio_WriteReg( dev->fd, 0x07, 0 );
					usbio_WriteReg( dev->fd, 0x58, a_bRegs[0x58] );
					usbio_ReadReg ( dev->fd, 0x02, &bReg2 );
					return SANE_TRUE;
				}

				gettimeofday(&t2, NULL);	
				if( t2.tv_sec > dwTicks )
					break;	
			}
		} else if( bAction == MOVE_SkipPaperSensor ) {

			for(;;)	{

				if( usb_SensorPaper( dev->fd )) {
					usbio_WriteReg( dev->fd, 0x07, 0 );
					usbio_WriteReg( dev->fd, 0x58, a_bRegs[0x58] );
					usbio_ReadReg ( dev->fd, 0x02, &bReg2 );
					return SANE_TRUE;
				}

				gettimeofday(&t2, NULL);	
				if( t2.tv_sec > dwTicks )
					break;	
			}
		} else {

			for(;;)	{

				usleep(10 * 1000);
				_UIO( usbio_ReadReg( dev->fd, 0x07, &reg7));

				if( !reg7 ) {
					usbio_WriteReg( dev->fd, 0x58, a_bRegs[0x58] );
					usbio_ReadReg( dev->fd, 0x02, &bReg2 );
					return SANE_TRUE;
				}

				gettimeofday(&t2, NULL);	
				if( t2.tv_sec > dwTicks )
					break;	
			}
		}

		usbio_WriteReg( dev->fd, 0x58, a_bRegs[0x58] );
		usbio_ReadReg ( dev->fd, 0x02, &bReg2 );
	}

	DBG( _DBG_ERROR, "Position NOT reached\n" );
	return SANE_FALSE;
}

/*.............................................................................
 *
 */
static SANE_Bool usb_ModuleToHome( pPlustek_Device dev, SANE_Bool fWait )
{
	u_char    mclk_div;
	u_char    value;
	pDCapsDef scaps = &dev->usbDev.Caps;
	pHWDef    hw    = &dev->usbDev.HwSetting;

	/* Check if LM9831 is ready for setting command */
	usbio_WriteReg( dev->fd, 0x58, hw->bReg_0x58 );
	usbio_ReadReg ( dev->fd, 2, &value );

	/* check current position... */
	_UIO(usbio_ReadReg( dev->fd, 2, &value ));

	if( value & 1 ) {
		fModuleFirstHome = SANE_FALSE;
		return SANE_TRUE;
	}

	_UIO( usbio_ReadReg( dev->fd, 0x07, &value ));

	if( fModuleFirstHome ) {
		fModuleFirstHome = SANE_FALSE;
		if( hw->motorModel != MODEL_Tokyo600 )
			usb_ModuleMove( dev, MOVE_Forward, hw->wMotorDpi / 2);
	}

	/* if not homing, do it... */
	if( value != 2 ) {

		u_short wFastFeedStepSize;

		if( hw->motorModel == MODEL_Tokyo600 ) {
			usbio_WriteReg( dev->fd, 0x07, 0 );
		} else 	{
				
			_UIO( usbio_ResetLM983x( dev ));

			usleep(200*1000);
		}
		
		if(!_IS_PLUSTEKMOTOR(hw->motorModel)) {

			pClkMotorDef clk;

			clk = usb_GetMotorSet( hw->motorModel );

			a_bRegs[0x56] = clk->pwm_fast;
			a_bRegs[0x57] = clk->pwm_duty_fast;
			mclk_div      = clk->mclk_fast;

		} else {

			mclk_div = 6;

			if( scaps->OpticDpi.x == 1200 || scaps->bPCB == 2) {
				switch( hw->motorModel ) {

				case MODEL_KaoHsiung:
				case MODEL_HuaLien:
				default:
					a_bRegs[0x56] = 1;
					a_bRegs[0x57] = 63;
					break;
				}
			} else { /* if(Device.Caps.OpticDpi.x == 600) */

				switch( hw->motorModel ) {

				case MODEL_Tokyo600:
					a_bRegs[0x56] = 4;
					a_bRegs[0x57] = 4;	/* 2; */
					break;
				case MODEL_HuaLien:
					if( dev->caps.dwFlag & SFLAG_ADF ) {
						a_bRegs[0x56] = 64;	/* 32; */
						a_bRegs[0x57] = 4;	/* 16; */
					} else {
						a_bRegs[0x56] = 32;
						a_bRegs[0x57] = 16;
					}
					break;
				
				case MODEL_KaoHsiung:
				default:
					a_bRegs[0x56] = 64;
					a_bRegs[0x57] = 20;
					break;
				}
			}
		}

		/* Compute fast feed step size, use equation 3 and equation 8
		 * assumptions: MCLK = 6, Lineratemode (CM=1)
		 */
		wFastFeedStepSize = (u_short)(dwCrystalFrequency / (mclk_div * 8 * 1 *
									  hw->dMaxMotorSpeed * 4 * hw->wMotorDpi));
		a_bRegs[0x48] = (u_char)(wFastFeedStepSize >> 8);
		a_bRegs[0x49] = (u_char)(wFastFeedStepSize & 0xFF);
		a_bRegs[0x4a] = 0;
		a_bRegs[0x4b] = 0;

		a_bRegs[0x45] |= 0x10;

		DBG( _DBG_INFO2,"MotorDPI=%u, MaxMotorSpeed=%.3f, FFStepSize=%u\n",
						hw->wMotorDpi, hw->dMaxMotorSpeed, wFastFeedStepSize );
		DBG( _DBG_INFO, "MOTOR: "
						"PWM=0x%02x, PWM_DUTY=0x%02x 0x45=0x%02x "
                        "0x48=0x%02x, 0x49=0x%02x\n",
						a_bRegs[0x56], a_bRegs[0x57],
						a_bRegs[0x45], a_bRegs[0x48], a_bRegs[0x49] );

		/* The setting for chassis moving is:
		 * MCLK divider = 6, 8 bits/pixel, HDPI divider = 12,
		 * no integration time adjustment and 1 channel grayscale
	 	 */
		/* MCLK divider = 6 */
		value = (u_char)((mclk_div-1) * 2);

		DBG( _DBG_INFO, "MCLK_FFW = %u --> 0x%02x\n", mclk_div, value );

		if( !usbio_WriteReg(dev->fd, 0x08, value))
			return SANE_FALSE;

		/* 8 bits/pixel, HDPI divider = 12 */
		if( !usbio_WriteReg(dev->fd, 0x09, 0x1F))
			return SANE_FALSE;

		/* Turn off integration time adjustment */
		if( !usbio_WriteReg(dev->fd, 0x19, 0))
			return SANE_FALSE;

		/* 1 channel grayscale, green channel */
		if( !usbio_WriteReg(dev->fd, 0x26, 0x8C))
			return SANE_FALSE;
		
		_UIO(sanei_lm983x_write(dev->fd, 0x48, &a_bRegs[0x48], 4, SANE_TRUE));
		_UIO(sanei_lm983x_write(dev->fd, 0x56, &a_bRegs[0x56], 3, SANE_TRUE));

		if( !usbio_WriteReg(dev->fd, 0x45, a_bRegs[0x45]))
			return SANE_FALSE;

		usbio_WriteReg(dev->fd, 0x0a, 0);

		if( hw->motorModel == MODEL_HuaLien && scaps->OpticDpi.x == 600 )
			usleep(100 * 1000);

		if( !usbio_WriteReg(dev->fd, 0x07, 2))
			return SANE_FALSE;
		
#if 0
		if(Device.HwSetting.motorModel == MODEL_Tokyo600)
		{
			DWORD	dwSpeedUp = GetTickCount () + 250;
			
			//while(GetTickCount () < dwSpeedUp)
			while((int)(dwSpeedUp - GetTickCount ()) > 0)
			{
				Sleep (10);
				if (!ReadRegister (0x07, &value))
					return FALSE;
				if (!value)
					return TRUE;
			}
			wFastFeedStepSize = (WORD)(dwCrystalFrequency / (6UL * 8UL * 1 * Device.HwSetting.dMaxMotorSpeed * 4 *
				Device.HwSetting.wMotorDpi) * 60 / 78);	
			a_bRegs [0x48] = (BYTE)(wFastFeedStepSize >> 8);
			a_bRegs [0x49] = (BYTE)(wFastFeedStepSize & 0xFF);
			WriteRegisters(0x48, &a_bRegs[0x48], 2);
		}
#endif
	}

	if( fWait ) {

		long           dwTicks;
	    struct timeval start_time, t2;
		
		gettimeofday( &start_time, NULL );	

	    dwTicks =  start_time.tv_sec + 150;

		for(;;)	{

			usleep( 20 * 1000 );
			_UIO( usbio_ReadReg( dev->fd, 0x07, &value ));

			if (!value)
				return SANE_TRUE;

			gettimeofday(&t2, NULL);	
		    if( t2.tv_sec > dwTicks )
				break;
		}
		return SANE_FALSE;
	}

	return SANE_TRUE;
}

/**
 *
 */
static SANE_Bool usb_MotorSelect( pPlustek_Device dev, SANE_Bool fADF )
{
	pDCapsDef sCaps = &dev->usbDev.Caps;
	pHWDef    hw    = &dev->usbDev.HwSetting;

	if(!_IS_PLUSTEKMOTOR(hw->motorModel)) {
		return SANE_TRUE;
	}
	
	if( fADF ) {

		if( sCaps->bCCD == kNEC3778 ) {
		
			hw->wMotorDpi      = 300;
			hw->dMaxMotorSpeed = 1.5;
			hw->dMaxMoveSpeed  = 1.5;
			sCaps->OpticDpi.y  = 600;
		}
		a_bRegs[0x5b] |= 0x80;
		
	} else {
	
		if( sCaps->bCCD == kNEC3778 ) {
			
			hw->wMotorDpi      = 600;
			hw->dMaxMotorSpeed = 1.1;
			hw->dMaxMoveSpeed  = 0.9;
			sCaps->OpticDpi.y  = 1200;
		}
		a_bRegs[0x5b] &= ~0x80;
	}

	/* To stop the motor moving */
	usbio_WriteReg( dev->fd, 0x07, 0 );		
	usleep(10 * 1000);

	usbio_WriteReg( dev->fd, 0x5b, a_bRegs[0x5b] );
	return SANE_TRUE;
}

/** according to the flag field, we return the register and
 * it's maks to turn on/off the lamp.
 * @param flag - field to check
 * @param reg  - pointer to a var to receive the register value
 * @param msk  - pointer to a var to receive the mask value
 * @return Nothing
 */
static void usb_GetLampRegAndMask( u_long flag, SANE_Byte *reg, SANE_Byte *msk )
{
	if( _MIO6 == ( _MIO6 & flag )) {
		*reg = 0x5b;
		*msk = 0x80;

   	} else if( _MIO5 == ( _MIO5 & flag )) {
		*reg = 0x5b;
		*msk = 0x08;

   	} else if( _MIO4 == ( _MIO4 & flag )) {
		*reg = 0x5a;
		*msk = 0x80;

   	} else if( _MIO3 == ( _MIO3 & flag )) {
		*reg = 0x5a;
		*msk = 0x08;
   	
    } else if( _MIO2 == ( _MIO2 & flag )) {
		*reg = 0x59;
		*msk = 0x80;

   	} else if( _MIO1 == ( _MIO1 & flag )) {
		*reg = 0x59;
		*msk = 0x08;

   	} else {
       *reg = 0;
       *msk = 0;
	}
}

/** usb_Get
 * This function returns the current lamp in use.
 * For non Plustek devices, it always returns DEV_LampReflection.
 * @param dev  - pointer to our device structure,
 *               it should contain all we need
 * @return - 0 if the scanner hasn't been used before, DEV_LampReflection
 *           for the normal lamp, or DEV_LampTPA for negative/transparency
 *           lamp
 */
static int usb_GetLampStatus( pPlustek_Device dev )
{
	int    iLampStatus = 0;
	pHWDef hw          = &dev->usbDev.HwSetting;
	pDCapsDef sc       = &dev->usbDev.Caps;
	SANE_Byte reg, msk, val;


	if( NULL == hw ) {
		DBG( _DBG_ERROR, "NULL-Pointer detected: usb_GetLampStatus()\n" );
		return -1;
	}

 	/* do we use the misc I/O pins for switching the lamp ? */
	if( _WAF_MISC_IO_LAMPS & sc->workaroundFlag ) {

		usb_GetLampRegAndMask( sc->lamp, &reg, &msk );

		if( 0 == reg ) {

			usbio_ReadReg( dev->fd, 0x29, &a_bRegs[0x29] );

			if( a_bRegs[0x29] & 3)
				iLampStatus |= DEV_LampReflection;
		} else {

			/* check if the lamp is on */
			usbio_ReadReg( dev->fd, reg, &val );

			DBG( _DBG_INFO, "REG[0x%02x] = 0x%02x (msk=0x%02x)\n",reg,val,msk);
			if( val & msk )
				iLampStatus |= DEV_LampReflection;

   			/* if the device supports a TPA, we check this here */
	  		if( sc->wFlags & DEVCAPSFLAG_TPA ) {

				usb_GetLampRegAndMask( _GET_TPALAMP(sc->lamp), &reg, &msk );
				usbio_ReadReg( dev->fd, reg, &val );
				DBG( _DBG_INFO, "REG[0x%02x] = 0x%02x (msk=0x%02x)\n",
																reg,val,msk);
				if( val & msk )
					iLampStatus |= DEV_LampTPA;
			}
		}

	} else {

		sanei_lm983x_read(dev->fd, 0x29,&a_bRegs[0x29],0x37-0x29+1,SANE_TRUE);

		if((a_bRegs[0x29] & 3) == 1) {

/* HEINER: BETTER define register to check ! */

			if(!_IS_PLUSTEKMOTOR(hw->motorModel)) {
				iLampStatus |= DEV_LampReflection;

            } else {

				if((a_bRegs[0x2e] * 256 + a_bRegs[0x2f]) > hw->wLineEnd )
					iLampStatus |= DEV_LampReflection;

				if((a_bRegs[0x36] * 256 + a_bRegs[0x37]) > hw->wLineEnd )
					iLampStatus |= DEV_LampTPA;
			}
		}
	}

	DBG( _DBG_INFO, "LAMP-STATUS: 0x%08x\n", iLampStatus );
	return iLampStatus;
}

/** usb_switchLampX
 * used for all devices that use some misc I/O pins to switch the lamp
 */
static SANE_Bool usb_switchLampX( pPlustek_Device dev,
								  SANE_Bool on, SANE_Bool tpa )
{
	SANE_Byte reg, msk;
	pDCapsDef sc = &dev->usbDev.Caps;

	if( tpa )
		usb_GetLampRegAndMask( _GET_TPALAMP(sc->lamp), &reg, &msk );
	else
		usb_GetLampRegAndMask( sc->lamp, &reg, &msk );

 	if( 0 == reg )
    	return SANE_FALSE; /* no need to switch something */

	DBG( _DBG_INFO, "usb_switchLampX(ON=%u,TPA=%u)\n", on, tpa );

	if( on )
		a_bRegs[reg] |= msk;
  	else
		a_bRegs[reg] &= ~msk;

	DBG( _DBG_INFO, "Switch Lamp: %u, regs[0x%02x] = 0x%02x\n",
													on, reg, a_bRegs[reg] );
    usbio_WriteReg( dev->fd, reg, a_bRegs[reg] );

    return SANE_TRUE;
}

/** usb_switchLamp
 * used for all devices that use some misc I/O pins to switch the lamp
 */
static SANE_Bool usb_switchLamp( pPlustek_Device dev, SANE_Bool on )
{
	SANE_Bool result;

	if((dev->scanning.sParam.bSource == SOURCE_Negative) ||
	   (dev->scanning.sParam.bSource == SOURCE_Transparency)) {
		result = usb_switchLampX( dev, on, SANE_TRUE );
	} else {
		result = usb_switchLampX( dev, on, SANE_FALSE );
	}

    return result;
}

/** usb_LedOn
 *
 */
static void usb_LedOn( pPlustek_Device dev, SANE_Bool fOn )
{
	u_char value;

	if( dev->usbDev.HwSetting.motorModel != MODEL_HuaLien )
		return;

	value = a_bRegs[0x0d];

	/* if(ReadRegister(0x0d, &value)) */
	{
		if( fOn )
			value |= 0x10;
		else
			value &= ~0x10;

		a_bRegs[0x0d] = value;
		usbio_WriteReg( dev->fd, 0x0d, value );
	}
}

/** function to adjust the lamp settings of a device
 *
 */
static SANE_Bool usb_AdjustLamps( pPlustek_Device dev )
{
	pHWDef hw = &dev->usbDev.HwSetting;

	a_bRegs[0x2c] = hw->red_lamp_on / 256;
	a_bRegs[0x2d] = hw->red_lamp_on & 0xFF;
	a_bRegs[0x2e] = hw->red_lamp_off / 256;
	a_bRegs[0x2f] = hw->red_lamp_off & 0xFF;

	a_bRegs[0x30] = hw->green_lamp_on / 256;
	a_bRegs[0x31] = hw->green_lamp_on & 0xFF;
	a_bRegs[0x32] = hw->green_lamp_off / 256;
	a_bRegs[0x33] = hw->green_lamp_off & 0xFF;

	a_bRegs[0x34] = hw->blue_lamp_on / 256;
	a_bRegs[0x35] = hw->blue_lamp_on & 0xFF;
	a_bRegs[0x36] = hw->blue_lamp_off / 256;
	a_bRegs[0x37] = hw->blue_lamp_off & 0xFF;

	return sanei_lm983x_write( dev->fd, 0x2c,
							   &a_bRegs[0x2c], 0x37-0x2c+1, SANE_TRUE );
}

/** usb_LampOn
 *
 */
static SANE_Bool usb_LampOn( pPlustek_Device dev,
							 SANE_Bool fOn, SANE_Bool fResetTimer )
{
	pDCapsDef      sc          = &dev->usbDev.Caps;
    pScanDef       scanning    = &dev->scanning;
	pHWDef         hw          = &dev->usbDev.HwSetting;
	int            iLampStatus = usb_GetLampStatus( dev );
	int            lampId      = -1;
    struct timeval t;

	if( NULL == scanning ) {
		DBG( _DBG_ERROR, "NULL-Pointer detected: usb_LampOn()\n" );
		return SANE_FALSE;
	}
	
	switch( scanning->sParam.bSource ) {

	case SOURCE_Reflection:
	case SOURCE_ADF:
		lampId = DEV_LampReflection;
		break;

	case SOURCE_Transparency:
	case SOURCE_Negative:
		lampId = DEV_LampTPA;
		break;
	}

	if( fOn ) {
	
		if( iLampStatus != lampId ) {
			
			DBG( _DBG_INFO, "Switching Lamp on\n" );

/* here we might have to switch off the TPA/Main lamp before
 * using the other one
 */
			if( lampId != dev->usbDev.currentLamp ) {

				if( dev->usbDev.currentLamp == DEV_LampReflection )
					usb_switchLampX( dev, SANE_FALSE, SANE_FALSE );
				else
					usb_switchLampX( dev, SANE_FALSE, SANE_TRUE );
			}

			memset( &a_bRegs[0x29], 0, (0x37-0x29+1));
			
			a_bRegs[0x29] = hw->bReg_0x29;

			if( !usb_switchLamp(dev, SANE_TRUE )) {

				if( lampId == DEV_LampReflection ) {
					a_bRegs[0x2e] = 16383 / 256;
					a_bRegs[0x2f] = 16383 % 256;
				}

				if( lampId == DEV_LampTPA ) {
					a_bRegs[0x36] = 16383 / 256;
					a_bRegs[0x37] = 16383 % 256;
				}
			}				

			if( _WAF_MISC_IO_LAMPS & sc->workaroundFlag ) {
			
				a_bRegs[0x2c] = hw->red_lamp_on / 256;
				a_bRegs[0x2d] = hw->red_lamp_on & 0xFF;
				a_bRegs[0x2e] = hw->red_lamp_off / 256;
				a_bRegs[0x2f] = hw->red_lamp_off & 0xFF;

				a_bRegs[0x30] = hw->green_lamp_on / 256;
				a_bRegs[0x31] = hw->green_lamp_on & 0xFF;
				a_bRegs[0x32] = hw->green_lamp_off / 256;
				a_bRegs[0x33] = hw->green_lamp_off & 0xFF;

				a_bRegs[0x34] = hw->blue_lamp_on / 256;
				a_bRegs[0x35] = hw->blue_lamp_on & 0xFF;
				a_bRegs[0x36] = hw->blue_lamp_off / 256;
				a_bRegs[0x37] = hw->blue_lamp_off & 0xFF;
			}
			
			sanei_lm983x_write( dev->fd, 0x29,
								&a_bRegs[0x29], 0x37-0x29+1, SANE_TRUE );

			if( lampId != dev->usbDev.currentLamp ) {
			
				dev->usbDev.currentLamp = lampId;
				
				if( fResetTimer ) {
			
					gettimeofday( &t, NULL );	
					dev->usbDev.dwTicksLampOn = t.tv_sec;
					DBG( _DBG_INFO, "Warmup-Timer started\n" );
				}					
			}
		}
	
	} else {

		int iStatusChange = iLampStatus & ~lampId;
		
		if( iStatusChange != iLampStatus ) {

			DBG( _DBG_INFO, "Switching Lamp off\n" );
		
			memset( &a_bRegs[0x29], 0, 0x37-0x29+1 );

			if( !usb_switchLamp(dev, SANE_FALSE )) {
			
				if( iStatusChange & DEV_LampReflection ) {
					a_bRegs[0x2e] = 16383 / 256;
					a_bRegs[0x2f] = 16383 % 256;
				}
    	
				if( iStatusChange & DEV_LampTPA ) {
					a_bRegs[0x36] = 16383 / 256;
					a_bRegs[0x37] = 16383 % 256;
				}					
			}
			
			if( _WAF_MISC_IO_LAMPS & sc->workaroundFlag ) {
			
				a_bRegs[0x2c] = hw->red_lamp_on / 256;
				a_bRegs[0x2d] = hw->red_lamp_on & 0xFF;
				a_bRegs[0x2e] = hw->red_lamp_off / 256;
				a_bRegs[0x2f] = hw->red_lamp_off & 0xFF;

				a_bRegs[0x30] = hw->green_lamp_on / 256;
				a_bRegs[0x31] = hw->green_lamp_on & 0xFF;
				a_bRegs[0x32] = hw->green_lamp_off / 256;
				a_bRegs[0x33] = hw->green_lamp_off & 0xFF;

				a_bRegs[0x34] = hw->blue_lamp_on / 256;
				a_bRegs[0x35] = hw->blue_lamp_on & 0xFF;
				a_bRegs[0x36] = hw->blue_lamp_off / 256;
				a_bRegs[0x37] = hw->blue_lamp_off & 0xFF;
			}
			
			sanei_lm983x_write( dev->fd, 0x29,
								&a_bRegs[0x29], 0x37-0x29+1, SANE_TRUE );
		}
	}

	if( usb_GetLampStatus(dev))
		usb_LedOn( dev, SANE_TRUE );
	else
		usb_LedOn( dev, SANE_FALSE );

	return SANE_TRUE;
}

/**
 * Function to preset the registers for the specific device, which
 * should never change during the whole operation
 * Affected registers:<br>
 * 0x0b - 0x0e - Sensor settings - directly from the HWDef<br>
 * 0x0f - 0x18 - Sensor Configuration - directly from the HwDef<br>
 * 0x1a - 0x1b - Stepper Phase Correction<br>
 * 0x20 - 0x21 - Line End<br>
 * 0x45        - Stepper Motor Mode<br>
 * 0x4c - 0x4d - Full Steps to Scan after PAPER SENSE 2 trips<br>
 * 0x50        - Steps to reverse when buffer is full<br>
 * 0x51        - Acceleration Profile<br>
 * 0x54 - 0x5e - Motor Settings, Paper-Sense Settings and Misc I/O<br>
 *
 * @param dev    - pointer to our device structure,
 *                 it should contain all we need
 * @return - Nothing
 */
static void usb_ResetRegisters( pPlustek_Device dev )
{
	pHWDef hw = &dev->usbDev.HwSetting;

	DBG( _DBG_INFO, "RESETTING REGISTERS(%u)\n", dev->initialized );
	memset( a_bRegs, 0, sizeof(a_bRegs));

	memcpy( a_bRegs+0x0b, &hw->bSensorConfiguration, 4 );
	memcpy( a_bRegs+0x0f, &hw->bReg_0x0f_Color, 10 );
	a_bRegs[0x1a] = _HIBYTE( hw->StepperPhaseCorrection );
	a_bRegs[0x1b] = _LOBYTE( hw->StepperPhaseCorrection );
	a_bRegs[0x1e] = _HIBYTE( hw->wActivePixelsStart );
	a_bRegs[0x1f] = _LOBYTE( hw->wActivePixelsStart );
	a_bRegs[0x20] = _HIBYTE( hw->wLineEnd );
	a_bRegs[0x21] = _LOBYTE( hw->wLineEnd );
	a_bRegs[0x45] = hw->bReg_0x45;
	a_bRegs[0x4c] = _HIBYTE( hw->wStepsAfterPaperSensor2 );
	a_bRegs[0x4d] = _LOBYTE( hw->wStepsAfterPaperSensor2 );
	a_bRegs[0x50] = hw->bStepsToReverse;
	a_bRegs[0x51] = hw->bReg_0x51;

	/* if already initialized, we ignore the MISC I/O settings as
     * they are used to determine the current lamp settings...
     */
	if( dev->initialized ) {
		memcpy( a_bRegs+0x54, &hw->bReg_0x54, 0x58 - 0x54 + 1 );
		a_bRegs[0x5c] = hw->bReg_0x5c;
		a_bRegs[0x5d] = hw->bReg_0x5d;
		a_bRegs[0x5e] = hw->bReg_0x5e;
		sanei_lm983x_read( dev->fd, 0x59, &a_bRegs[0x59], 3, SANE_TRUE );
	} else {
		memcpy( a_bRegs+0x54, &hw->bReg_0x54, 0x5e - 0x54 + 1 );
	}
	DBG( _DBG_INFO, "MISC I/O after RESET: 0x%02x, 0x%02x, 0x%02x\n",
								a_bRegs[0x59], a_bRegs[0x5a], a_bRegs[0x5b] );
}

/** usb_ModuleStatus
 *
 */
static SANE_Bool usb_ModuleStatus( pPlustek_Device dev )
{
	u_char value;
	pHWDef hw = &dev->usbDev.HwSetting;

/* HEINER: Maybe needed to avoid recalibration!!! */
#if 0
	if( dev->scanning.fCalibrated )
		return SANE_TRUE;
#endif

	_UIO( usbio_ReadReg( dev->fd, 2, &value ));

	if( value & 1 )	{

		_UIO( usbio_ReadReg( dev->fd, 0x7, &value));

		if( value ) {

			usbio_WriteReg( dev->fd, 0x07, 0 );
			usbio_WriteReg( dev->fd, 0x07, 0x20 );
			usbio_WriteReg( dev->fd, 0x07, 0 );
			
			sanei_lm983x_write( dev->fd, 0x58,
								&hw->bReg_0x58, 0x5b-0x58+1, SANE_TRUE );
			usbio_ReadReg( dev->fd, 2, &value );
			usbio_ReadReg( dev->fd, 2, &value );
		}

		usb_MotorOn( dev->fd, SANE_FALSE );
		return SANE_TRUE;
	}

	_UIO( usbio_ReadReg( dev->fd, 0x7, &value ));

	if( !(value & 2))
		usb_ModuleToHome( dev, SANE_FALSE );

	return SANE_FALSE;
}

/* HEINER: replace!!! */
static pPlustek_Device dev_xxx;

/**
 * ISR to switch lamp off after time has elapsed
 */
static void usb_LampTimerIrq( int sig )
{
	int handle = -1;

	_VAR_NOT_USED( sig );
	DBG( _DBG_INFO, "LAMP OFF!!!\n" );
	
	if( -1 == dev_xxx->fd ) {
	
		if( SANE_STATUS_GOOD == sanei_usb_open(dev_xxx->sane.name, &handle)) {
	    	dev_xxx->fd = handle;
		}		
	}

	/* needs to be recalibrated */
	dev_xxx->scanning.fCalibrated = SANE_FALSE;
	
	if( -1 != dev_xxx->fd )
		usb_LampOn( dev_xxx, SANE_FALSE, SANE_FALSE );
	
	if( -1 != handle ) {
		dev_xxx->fd = -1;
		sanei_usb_close( handle );
	}
}

/** usb_StartLampTimer
 *
 */
static void usb_StartLampTimer( pPlustek_Device dev )
{
	sigset_t 		 block, pause_mask;
	struct sigaction s;
	struct itimerval interval;

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

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

	if(	sigaction( SIGALRM, &s, NULL ) < 0 )
		DBG( _DBG_ERROR, "Canīt setup timer-irq handler\n" );

	sigprocmask( SIG_UNBLOCK, &block, &pause_mask );

	/*
	 * define a one-shot timer
	 */
	interval.it_value.tv_usec    = 0;
	interval.it_value.tv_sec     = dev->usbDev.dwLampOnPeriod;
	interval.it_interval.tv_usec = 0;
	interval.it_interval.tv_sec  = 0;

	dev_xxx = dev;	
	
	if( 0 != dev->usbDev.dwLampOnPeriod ) {
		setitimer( ITIMER_REAL, &interval, &dev->saveSettings );
		DBG( _DBG_INFO, "Lamp-Timer started\n" );
	}		
}

/** usb_StopLampTimer
 *
 */
static void usb_StopLampTimer( pPlustek_Device dev )
{
	sigset_t block, pause_mask;

	/* block SIGALRM */
	sigemptyset( &block );
	sigaddset  ( &block, SIGALRM );
	sigprocmask( SIG_BLOCK, &block, &pause_mask );
	
	if( 0 != dev->usbDev.dwLampOnPeriod )
		setitimer( ITIMER_REAL, &dev->saveSettings, NULL );

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

/**
 * This function is used to detect a cancel condition,
 * our ESC key is the SIGUSR1 signal. It is sent by the backend when the
 * cancel button has been pressed
 *
 * @param - none
 * @return the function returns SANE_TRUE if a cancel condition has been
 *  detected, if not, it returns SANE_FALSE
 */
static SANE_Bool usb_IsEscPressed( void )
{
	sigset_t sigs;

	sigpending( &sigs );
	if( sigismember( &sigs, SIGUSR1 )) {
		DBG( _DBG_INFO, "SIGUSR1 is pending --> Cancel detected\n" );
		return SANE_TRUE;
	}

	return SANE_FALSE;
}

/**
 * wait until warmup has been done
 */
static SANE_Bool usb_Wait4Warmup( pPlustek_Device dev )
{
	u_long         dw;
    struct timeval t;

 	/*
	 * wait until warmup period has been elapsed
	 */
	gettimeofday( &t, NULL);
	dw = t.tv_sec - dev->usbDev.dwTicksLampOn;
	if( dw < dev->usbDev.dwWarmup )
		DBG(_DBG_INFO,"Warmup: Waiting %lu seconds\n",dev->usbDev.dwWarmup );

	do {

		gettimeofday( &t, NULL);

		dw = t.tv_sec - dev->usbDev.dwTicksLampOn;

		if( usb_IsEscPressed()) {
			return SANE_FALSE;
		}

	} while( dw < dev->usbDev.dwWarmup );

	return SANE_TRUE;
}
/* END PLUSTEK-USBHW.C ......................................................*/
