/* @file plustekpp-io.c
 * @brief as the name says, here we have all the I/O
 *        functions according to the parallel port hardware
 *
 * based on sources acquired from Plustek Inc.
 * Copyright (C) 1998 Plustek Inc.
 * Copyright (C) 2000-2013 Gerhard Jaeger <gerhard@gjaeger.de>
 *
 * History:
 * - 0.37 - initial version
 *        - added Kevins' suggestions
 * - 0.38 - added Asic 98003 stuff and ioP98ReadWriteTest()
 *        - added IODataRegisterToDAC()
 *        - replaced function IOSPPWrite by IOMoveDataToScanner
 *        - modified ioP98OpenScanPath again and reuse V0.36 stuff again
 *        - added IO functions
 * - 0.39 - added IO functions
 *        - added f97003 stuff from A3I code
 * - 0.40 - no changes
 * - 0.41 - no changes
 * - 0.42 - changed include names
 * - 0.43 - no changes
 * - 0.44 - 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>
 */
#include "plustek-pp_scan.h"

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

static Bool fnEPPRead  ( pScanData ps, pUChar pBuffer, ULong ulSize );
static Bool fnSPPRead  ( pScanData ps, pUChar pBuffer, ULong ulSize );
static Bool fnBiDirRead( pScanData ps, pUChar pBuffer, ULong ulSize );

typedef struct {
	pFnReadData func;
	char       *name;
} ioReadFuncDef;

static ioReadFuncDef ioReadFunc[3] = {
	{ fnEPPRead,   "fnEPPRead"   },
	{ fnSPPRead,   "fnSPPRead"   },
	{ fnBiDirRead, "fnBiDirRead" }
};

/*************************** some definitions ********************************/

#define _MEMTEST_SIZE   1280

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

/** we provide some functions to read data from SPP port according to
 * the speed we have detected (ReadWriteTest!!)
 */
static Byte ioDataFromSPPFast( pScanData ps )
{
	Byte bData, tmp;

	/* notify asic we will read the high nibble data from status port */
	if( _FALSE == ps->f97003 ) {
		_OUTB_CTRL( ps, ps->CtrlReadHighNibble );
		_DO_UDELAY( 1 );
	}

	/* read high nibble */
    bData  = _INB_STATUS( ps );
	bData &= 0xf0;

    _OUTB_CTRL( ps, ps->CtrlReadLowNibble );
	_DO_UDELAY( 1 );

	/* read low nibble */
	tmp = _INB_STATUS( ps );

	/* combine with low nibble */
    bData |= (tmp >> 4);

    _OUTB_CTRL( ps, _CTRL_GENSIGNAL );
	_DO_UDELAY( 1 );

    return bData;
}

static Byte ioDataFromSPPMiddle( pScanData ps )
{
	Byte bData, tmp;

	/* notify asic we will read the high nibble data from status port */
	if( _FALSE == ps->f97003 ) {
		_OUTB_CTRL( ps, ps->CtrlReadHighNibble );
		_DO_UDELAY( 1 );
	}

	/* read high nibble */
	_INB_STATUS( ps );
    bData  = _INB_STATUS( ps );
	bData &= 0xf0;

    _OUTB_CTRL( ps, ps->CtrlReadLowNibble );
	_DO_UDELAY( 1 );

	/* read low nibble */
	_INB_STATUS( ps );
	tmp = _INB_STATUS( ps );

	/* combine with low nibble */
    bData |= (tmp >> 4);

    _OUTB_CTRL( ps, _CTRL_GENSIGNAL );
	_DO_UDELAY( 1 );

    return bData;
}

static UChar ioDataFromSPPSlow( pScanData ps )
{
	Byte bData, tmp;

	/* notify asic we will read the high nibble data from status port */
	if( _FALSE == ps->f97003 ) {
		_OUTB_CTRL( ps, ps->CtrlReadHighNibble );
		_DO_UDELAY( 2 );
	}

	/* read high nibble */
	_INB_STATUS( ps );
	_INB_STATUS( ps );
    bData  = _INB_STATUS( ps );
	bData &= 0xf0;

    _OUTB_CTRL( ps, ps->CtrlReadLowNibble );
	_DO_UDELAY( 2 );

	/* read low nibble */
	_INB_STATUS( ps );
	_INB_STATUS( ps );
	tmp = _INB_STATUS( ps );

	/* combine with low nibble */
    bData |= (tmp >> 4);

    _OUTB_CTRL( ps, _CTRL_GENSIGNAL );
	_DO_UDELAY( 2 );

    return bData;
}

static UChar ioDataFromSPPSlowest( pScanData ps )
{
	Byte bData, tmp;

	/* notify asic we will read the high nibble data from status port */
	if( _FALSE == ps->f97003 ) {
		_OUTB_CTRL( ps, ps->CtrlReadHighNibble );
		_DO_UDELAY( 3 );
	}

	/* read high nibble */
	_INB_STATUS( ps );
	_INB_STATUS( ps );
	_INB_STATUS( ps );
    bData  = _INB_STATUS( ps );
	bData &= 0xf0;

    _OUTB_CTRL( ps, ps->CtrlReadLowNibble );
	_DO_UDELAY( 3 );

	/* read low nibble */
	_INB_STATUS( ps );
	_INB_STATUS( ps );
	_INB_STATUS( ps );
	tmp = _INB_STATUS( ps );

	/* combine with low nibble */
    bData |= (tmp >> 4);

    _OUTB_CTRL( ps, _CTRL_GENSIGNAL );
	_DO_UDELAY( 3 );

    return bData;
}

/** Read data from STATUS port. We have to read twice and combine two nibble
 *  data to one byte.
 */
static Bool fnSPPRead( pScanData ps, pUChar pBuffer, ULong ulSize )
{
	switch( ps->IO.delay ) {

		case 0:
			for (; ulSize; ulSize--, pBuffer++)
				*pBuffer = ioDataFromSPPFast( ps );
			break;

		case 1:
			for (; ulSize; ulSize--, pBuffer++)
				*pBuffer = ioDataFromSPPMiddle( ps );
			break;

		case 2:
			for (; ulSize; ulSize--, pBuffer++)
				*pBuffer = ioDataFromSPPSlow( ps );
			break;

		default:
			for (; ulSize; ulSize--, pBuffer++)
				*pBuffer = ioDataFromSPPSlowest( ps );
			break;
	}

    return _TRUE;
}


/** Using buffered I/O to read data from EPP Data Port
 */
static Bool fnEPPRead( pScanData ps, pUChar pBuffer, ULong ulSize )
{
	register ULong i;

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

#ifndef __KERNEL__
		sanei_pp_set_datadir( ps->pardev, SANEI_PP_DATAIN );
#else
		_OUTB_CTRL( ps, (_CTRL_GENSIGNAL + _CTRL_DIRECTION));
		_DO_UDELAY( 1 );
#endif
		for( i = 0; i < ulSize; i++ )
			pBuffer[i] = _INB_EPPDATA( ps );

#ifndef __KERNEL__
		sanei_pp_set_datadir( ps->pardev, SANEI_PP_DATAOUT );
#else
		_OUTB_CTRL( ps, _CTRL_GENSIGNAL );
		_DO_UDELAY( 1 );
#endif
	} else {

		for( i = 0; i < ulSize; i++ )
			pBuffer[i] = _INB_EPPDATA( ps );
	}

	return _TRUE;
}

/**
 */
static Bool fnBiDirRead( pScanData ps, pUChar pBuffer, ULong ulSize )
{
	UChar start, end;

	start = _CTRL_START_BIDIREAD;
	end   = _CTRL_END_BIDIREAD;

#ifndef __KERNEL__
	sanei_pp_set_datadir( ps->pardev, SANEI_PP_DATAIN );

	if( !sanei_pp_uses_directio()) {
		start &= ~_CTRL_DIRECTION;
		end   &= ~_CTRL_DIRECTION;
	}
#else
	if( _IS_ASIC98(ps->sCaps.AsicID)) {
		_OUTB_CTRL( ps, (_CTRL_GENSIGNAL + _CTRL_DIRECTION));
	}
#endif

	switch( ps->IO.delay ) {

		case 0:
		    for( ; ulSize; ulSize--, pBuffer++ ) {
				_OUTB_CTRL( ps, start );
				*pBuffer = _INB_DATA( ps );
				_OUTB_CTRL( ps, end );
			}
			break;

		case 1:
			_DO_UDELAY( 1 );
		    for(; ulSize; ulSize--, pBuffer++ ) {
				_OUTB_CTRL( ps, start );
				_DO_UDELAY( 1 );

				*pBuffer = _INB_DATA( ps );

				_OUTB_CTRL( ps, end );
				_DO_UDELAY( 1 );
			}
			break;

		default:
			_DO_UDELAY( 2 );
		    for(; ulSize; ulSize--, pBuffer++ ) {
				_OUTB_CTRL( ps, start );
				_DO_UDELAY( 2 );

				*pBuffer = _INB_DATA( ps );

				_OUTB_CTRL( ps, end );
				_DO_UDELAY( 2 );
			}
			break;

	}

#ifndef __KERNEL__
	sanei_pp_set_datadir( ps->pardev, SANEI_PP_DATAOUT );
#else
	if( _IS_ASIC98(ps->sCaps.AsicID)) {
		_OUTB_CTRL( ps, _CTRL_GENSIGNAL );
	}
#endif
	return _TRUE;
}

/** as the name says, we switch to SPP mode
 */
static void ioSwitchToSPPMode( pScanData ps )
{
	/* save the control and data port value
	 */
	ps->IO.bOldControlValue = _INB_CTRL( ps );
	ps->IO.bOldDataValue    = _INB_DATA( ps );

	_OUTB_CTRL( ps, _CTRL_GENSIGNAL );	/* 0xc4 */
	_DO_UDELAY( 2 );
}

/** restore the port settings
 */
static void ioRestoreParallelMode( pScanData ps )
{
	_OUTB_CTRL( ps, ps->IO.bOldControlValue & 0x3f );
	_DO_UDELAY( 1 );

	_OUTB_DATA( ps, ps->IO.bOldDataValue );
	_DO_UDELAY( 1 );
}

/** try to connect to scanner (ASIC 9600x and 98001)
 */
_LOC void ioP98001EstablishScannerConnection( pScanData ps, ULong delTime )
{
	_OUTB_DATA( ps, _ID_TO_PRINTER );
    _DO_UDELAY( delTime );

	_OUTB_DATA( ps, _ID1ST );
    _DO_UDELAY( delTime );

	_OUTB_DATA( ps, _ID2ND );
    _DO_UDELAY( delTime );

	_OUTB_DATA( ps, _ID3RD );
    _DO_UDELAY( delTime );

    _OUTB_DATA( ps, _ID4TH );
    _DO_UDELAY( delTime );
}

/** try to connect to scanner (ASIC 98003)
 */
static void ioP98003EstablishScannerConnection( pScanData ps, ULong delTime )
{
	_OUTB_DATA( ps, _ID1ST );
	_DO_UDELAY( delTime );

	_OUTB_DATA( ps, _ID2ND );
	_DO_UDELAY( delTime );

	_OUTB_DATA( ps, _ID3RD );
	_DO_UDELAY( delTime );

	_OUTB_DATA( ps, _ID4TH );
	_DO_UDELAY( delTime );
}

/** switch the printer interface to scanner
 */
static Bool ioP96OpenScanPath( pScanData ps )
{
	if( 0 == ps->IO.bOpenCount ) {

		/* not established */
		ioSwitchToSPPMode( ps );

		/* Scanner command sequence to open scanner path */
		ioP98001EstablishScannerConnection( ps, 5 );
	}
#ifdef DEBUG
	else
		DBG( DBG_IO, "!!!! Path already open (%u)!!!!\n", ps->IO.bOpenCount );
#endif

	ps->IO.bOpenCount++;			/* increment the opened count */

/*
 * CHECK to we really need that !!
 */
	ps->IO.useEPPCmdMode = _FALSE;
	return _TRUE;
}

/** try to connect to scanner
 */
static Bool ioP98OpenScanPath( pScanData ps )
{
    Byte  tmp;
    ULong dw;
	ULong dwTime = 1;

	if( 0 == ps->IO.bOpenCount ) {

		/* not established */
		ioSwitchToSPPMode( ps );

		for( dw = 10; dw; dw-- ) {

			/*
			 * this seems to be necessary...
			 */
 			if( _ASIC_IS_98001 == ps->sCaps.AsicID ) {
				ioP98001EstablishScannerConnection( ps, dw );
#if 0
				ioP98001EstablishScannerConnection( ps, dw );
				ioP98001EstablishScannerConnection( ps, dw );
#endif
			} else {
				ioP98003EstablishScannerConnection( ps, dw );
			}

			_INB_STATUS( ps );
			tmp = _INB_STATUS( ps );

			if( 0x50 == ( tmp & 0xf0 )) {

				ps->IO.bOpenCount = 1;

				if( ps->sCaps.AsicID == IODataFromRegister(ps, ps->RegAsicID)) {
					return _TRUE;
				}
				ps->IO.bOpenCount = 0;
			}

			dwTime++;
		}
		DBG( DBG_IO, "ioP98OpenScanPath() failed!\n" );
		return _FALSE;
	}
#ifdef DEBUG
	else
		DBG( DBG_IO, "!!!! Path already open (%u)!!!!\n", ps->IO.bOpenCount );
#endif

	ps->IO.bOpenCount++;			/* increment the opened count */
	return _TRUE;
}

/** Switch back to printer mode.
 * Restore the printer control/data port value.
 */
static void ioCloseScanPath( pScanData ps )
{
	if( ps->IO.bOpenCount && !(--ps->IO.bOpenCount)) {

#ifdef DEBUG
		ps->IO.bOpenCount = 1;
#endif
		IORegisterToScanner( ps, 0xff );

		/*
		 * back to pass-through printer mode
		 */
		IORegisterToScanner( ps, ps->RegSwitchBus );
#ifdef DEBUG
        ps->IO.bOpenCount = 0;
#endif
		ps->IO.useEPPCmdMode = _FALSE;

		ioRestoreParallelMode( ps );
	}
}

/** check the memory to see that the data-transfers will work.
 * (ASIC 9800x only)
 */
static int ioP98ReadWriteTest( pScanData ps )
{
	UChar  tmp;
	ULong  ul;
	pUChar buffer;
	int	   retval;

	DBG( DBG_LOW, "ioP98ReadWriteTest()\n" );

	/* _MEMTEST_SIZE: Read, _MEMTEST_SIZE:Write */
	buffer = _KALLOC( sizeof(UChar) * _MEMTEST_SIZE*2, GFP_KERNEL );
	if( NULL == buffer )
		return _E_ALLOC;

	/* prepare content */
	for( ul = 0; ul < _MEMTEST_SIZE; ul++ )
	    buffer[ul] = (UChar)ul;

	ps->OpenScanPath(ps);

	/* avoid switching to Lamp0, when previously scanned in transp./neg mode */
	tmp = ps->bLastLampStatus + _SCAN_BYTEMODE;
	IODataToRegister( ps, ps->RegScanControl, tmp );

	IODataToRegister( ps, ps->RegModelControl, (_LED_ACTIVITY | _LED_CONTROL));

	IODataToRegister( ps, ps->RegModeControl, _ModeMappingMem );
	IODataToRegister( ps, ps->RegMemoryLow,  0 );
	IODataToRegister( ps, ps->RegMemoryHigh, 0 );

	/* fill to buffer */
	IOMoveDataToScanner( ps, buffer, _MEMTEST_SIZE );

	IODataToRegister( ps, ps->RegModeControl, _ModeMappingMem );
	IODataToRegister( ps, ps->RegMemoryLow,  0 );
	IODataToRegister( ps, ps->RegMemoryHigh, 0 );
	IODataToRegister( ps, ps->RegWidthPixelsLow,  0 );
	IODataToRegister( ps, ps->RegWidthPixelsHigh, 5 );

	ps->AsicReg.RD_ModeControl = _ModeReadMappingMem;

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

	IOReadScannerImageData( ps, buffer + _MEMTEST_SIZE, _MEMTEST_SIZE );

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

	/* check the result ! */
	retval = _OK;

	for( ul = 0; ul < _MEMTEST_SIZE; ul++ ) {
		if( buffer[ul] != buffer[ul+_MEMTEST_SIZE] ) {
			DBG( DBG_HIGH, "Error in memory test at pos %u (%u != %u)\n",
				 ul, buffer[ul], buffer[ul+_MEMTEST_SIZE] );
			retval = _E_NO_DEV;
			break;
		}
	}

	_KFREE(buffer);
	return retval;
}

/** Put data to DATA port and trigger hardware through CONTROL port to read it.
 */
static void ioSPPWrite( pScanData ps, pUChar pBuffer, ULong size )
{
	DBG( DBG_IO , "Moving %u bytes to scanner, IODELAY = %u...\n",
					size, ps->IO.delay );
	switch( ps->IO.delay ) {

		case 0:
		    for (; size; size--, pBuffer++) {
				_OUTB_DATA( ps, *pBuffer );
				_OUTB_CTRL( ps, _CTRL_START_DATAWRITE );
				_OUTB_CTRL( ps, _CTRL_END_DATAWRITE );
        	}
			break;

		case 1:
		case 2:
		    for (; size; size--, pBuffer++) {
				_OUTB_DATA( ps, *pBuffer );
				_DO_UDELAY( 1 );
				_OUTB_CTRL( ps, _CTRL_START_DATAWRITE );
				_DO_UDELAY( 1 );
				_OUTB_CTRL( ps, _CTRL_END_DATAWRITE );
				_DO_UDELAY( 2 );
        	}
			break;

		default:
		    for (; size; size--, pBuffer++) {
				_OUTB_DATA( ps, *pBuffer );
				_DO_UDELAY( 1 );
				_OUTB_CTRL( ps, _CTRL_START_DATAWRITE );
				_DO_UDELAY( 2 );
				_OUTB_CTRL( ps, _CTRL_END_DATAWRITE );
				_DO_UDELAY( 3 );
        	}
			break;
	}
	DBG( DBG_IO , "... done.\n" );
}

/** set the scanner to "read" data mode
 */
static void ioEnterReadMode( pScanData ps )
{
	if( ps->IO.portMode != _PORT_SPP ) {

		_DO_UDELAY( 1 );
		IORegisterToScanner( ps, ps->RegEPPEnable );

		if( _IS_ASIC98( ps->sCaps.AsicID ))
			ps->IO.useEPPCmdMode = _TRUE;
	}

	if( _ASIC_IS_98003 == ps->sCaps.AsicID )
		ps->IO.bOldControlValue = _INB_CTRL( ps );

	/* ask ASIC to enter read mode */
	IORegisterToScanner( ps, ps->RegReadDataMode );
}

/************************ exported functions *********************************/

/** here we do some init work
 */
_LOC int IOInitialize( pScanData ps )
{
	DBG( DBG_HIGH, "IOInitialize()\n" );

	if( NULL == ps )
		return _E_NULLPTR;

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

		ps->OpenScanPath  = ioP98OpenScanPath;
		ps->ReadWriteTest = ioP98ReadWriteTest;

	} else if( _IS_ASIC96(ps->sCaps.AsicID)) {

		ps->OpenScanPath = ioP96OpenScanPath;

	} else {

		DBG( DBG_HIGH , "NOT SUPPORTED ASIC !!!\n" );
		return _E_NOSUPP;
	}

	ps->CloseScanPath   = ioCloseScanPath;
	ps->Device.ReadData = ioReadFunc[ps->IO.portMode].func;
	DBG( DBG_HIGH, "* using readfunction >%s<\n",
	                  ioReadFunc[ps->IO.portMode].name );
	return _OK;
}

/** Write specific length buffer to scanner
 * The scan path is already established
 */
_LOC void IOMoveDataToScanner( pScanData ps, pUChar pBuffer, ULong size )
{
#ifdef DEBUG
	if( 0 == ps->IO.bOpenCount )
		DBG( DBG_IO, "IOMoveDataToScanner - no connection!\n" );
#endif

	IORegisterToScanner( ps, ps->RegInitDataFifo );
	IORegisterToScanner( ps, ps->RegWriteDataMode );

	ioSPPWrite( ps, pBuffer, size );
}

/** Calling SITUATION: Scanner path is established.
 * download a scanstate-table
 */
_LOC void IODownloadScanStates( pScanData ps )
{
	TimerDef timer;
#ifdef DEBUG
	if( 0 == ps->IO.bOpenCount )
		DBG( DBG_IO, "IODownloadScanStates - no connection!\n" );
#endif

	IORegisterToScanner( ps, ps->RegScanStateControl );

	ioSPPWrite( ps, ps->a_nbNewAdrPointer, _SCANSTATE_BYTES );

	if( ps->Scan.fRefreshState ) {

		IORegisterToScanner( ps, ps->RegRefreshScanState );

		MiscStartTimer( &timer, (_SECOND/2));
		do {

			if (!( IOGetScanState( ps, _TRUE) & _SCANSTATE_STOP))
				break;
		}
		while( !MiscCheckTimer(&timer));
	}
}

/** Calling SITUATION: Scanner path is established.
 * Write a data to asic
 */
_LOC void IODataToScanner( pScanData ps, Byte bValue )
{
	ULong deltime = 4;

#ifdef DEBUG
	if( 0 == ps->IO.bOpenCount )
        DBG( DBG_IO, "IODataToScanner - no connection!\n" );
#endif

	if( ps->IO.delay < 2 )
		deltime = 2;

    /* output data */
   	_OUTB_DATA( ps, bValue );
	_DO_UDELAY( deltime );

	/* notify asic there is data */
    _OUTB_CTRL( ps, _CTRL_START_DATAWRITE );
	_DO_UDELAY( deltime );

		/* end write cycle */
   	_OUTB_CTRL( ps, _CTRL_END_DATAWRITE );
	_DO_UDELAY( deltime-1 );
}

/** Calling SITUATION: Scanner path is established.
 * Write a data to specific asic's register
 */
_LOC void IODataToRegister( pScanData ps, Byte bReg, Byte bData )
{
#ifdef DEBUG
    if( 0 == ps->IO.bOpenCount )
        DBG( DBG_IO, "IODataToRegister - no connection!\n" );
#endif

	/* specify register */
    IORegisterToScanner( ps, bReg );

	/* then write the content */
	IODataToScanner( ps, bData );
}

/** Calling SITUATION: Scanner path is established.
 * Read the content of specific asic's register
 */
_LOC Byte IODataFromRegister( pScanData ps, Byte bReg )
{
	IORegisterToScanner( ps, bReg );

	if( 0 == ps->IO.delay )
		return ioDataFromSPPFast( ps );
	else if( 1 == ps->IO.delay )
		return ioDataFromSPPMiddle( ps );
	else if( 2 == ps->IO.delay )
		return ioDataFromSPPSlow( ps );
	else
		return ioDataFromSPPSlowest( ps );
}

/** Calling SITUATION: Scanner path is established.
 * Write a register to asic (used for a command without parameter)
 */
_LOC void IORegisterToScanner( pScanData ps, Byte bReg )
{
#ifdef DEBUG
    if( 0 == ps->IO.bOpenCount )
        DBG( DBG_IO, "IORegisterToScanner - no connection!\n" );
#endif

    /*
     * write data to port
     */
	_OUTB_DATA( ps, bReg );

    /*
     * depending on the mode, generate the trigger signals
     */
    if( ps->IO.useEPPCmdMode ) {

		_DO_UDELAY( 5 );

		_OUTB_CTRL( ps, _CTRL_EPPSIGNAL_WRITE);	/* 0xc5 */
		_DO_UDELAY( 5 );

		_OUTB_CTRL( ps, _CTRL_EPPTRIG_REGWRITE);/* 0xcd */
		_DO_UDELAY( 5 );

		_OUTB_CTRL( ps, _CTRL_EPPSIGNAL_WRITE);	/* 0xc5 */
		_DO_UDELAY( 5 );

		_OUTB_CTRL( ps, _CTRL_END_REGWRITE);    /* 0xc4 */

    } else {
		if( ps->IO.delay < 2 ) {

			_DO_UDELAY( 1 );
			_OUTB_CTRL( ps, _CTRL_START_REGWRITE);
			_DO_UDELAY( 1 );
			_OUTB_CTRL( ps, _CTRL_END_REGWRITE);
		} else {

			_DO_UDELAY( 2 );
			_OUTB_CTRL( ps, _CTRL_START_REGWRITE);
			_DO_UDELAY( 2 );
			_OUTB_CTRL( ps, _CTRL_END_REGWRITE);
			_DO_UDELAY( 2 );
		}
    }
}

/** write data to the DAC - ASIC 98001/3 only
 */
_LOC void IODataRegisterToDAC( pScanData ps, Byte bReg, Byte bData )
{
    ULong i;

	IODataToRegister( ps, ps->RegADCAddress, bReg );
    IODataToRegister( ps, ps->RegADCData,    bData );
    IODataToRegister( ps, ps->RegADCSerialOutStr, bData );

    /* TEST: ORG was 1 ms for ASIC 98001 */
    _DO_UDELAY( 12 );

    for( i = 4; i; i-- ) {

    	_OUTB_CTRL( ps, _CTRL_START_DATAWRITE );
        _DO_UDELAY( 5 );
    	_OUTB_CTRL( ps, _CTRL_END_DATAWRITE );
        _DO_UDELAY( 12 );
    }
}

/** Calling SITUATION: Scanner path was not established.
 * Read the content of specific asics' register
 */
_LOC Byte IODataRegisterFromScanner( pScanData ps, Byte bReg )
{
    Byte bData;

    ps->OpenScanPath( ps );
    bData = IODataFromRegister( ps, bReg );
    ps->CloseScanPath( ps );

    return bData;
}

/** Calling SITUATION: Scanner path not established.
 * Write a value of register to asic
 */
_LOC void IOCmdRegisterToScanner( pScanData ps, Byte bReg, Byte bData )
{
    ps->OpenScanPath( ps );
    IODataToRegister( ps, bReg, bData );
    ps->CloseScanPath( ps );
}

/** Calling SITUATION: Scanner path not established.
 * Write a register to asic (used for a command without parameter)
 */
_LOC void IORegisterDirectToScanner( pScanData ps, Byte bReg )
{
    ps->OpenScanPath( ps );				/* establish the connection */
    IORegisterToScanner( ps, bReg );	/* write register to asic	*/
    ps->CloseScanPath( ps );			/* disconnect				*/
}

/** perform a SW reset of ASIC 98003 models
 */
_LOC void IOSoftwareReset( pScanData ps )
{
    if( _ASIC_IS_98003 != ps->sCaps.AsicID )
        return;

    ps->OpenScanPath( ps );

    IODataToRegister( ps, ps->RegTestMode, _SW_TESTMODE );

    ioSwitchToSPPMode( ps );

    _OUTB_DATA( ps, _RESET1ST );
    _DODELAY( 5 );

    _OUTB_DATA( ps, _RESET2ND );
    _DODELAY( 5 );

    _OUTB_DATA( ps, _RESET3RD );
    _DODELAY( 5 );

    _OUTB_DATA( ps, _RESET4TH );
    _DODELAY( 5 );

    ioRestoreParallelMode( ps );

    /* reset test mode register */
    IODataToRegister( ps, ps->RegTestMode, 0 );
    IODataToRegister( ps, ps->RegScanControl, ps->AsicReg.RD_ScanControl );

    ps->CloseScanPath( ps );
}

/** Read specific length data from scanner and the method depends on the
 *  mode defined in registry.
 */
_LOC void IOReadScannerImageData( pScanData ps, pUChar pBuf, ULong size )
{
	if( _ASIC_IS_98003 != ps->sCaps.AsicID )
		ps->OpenScanPath( ps);

	if( _IS_ASIC98( ps->sCaps.AsicID))
		IODataToRegister( ps, ps->RegModeControl, ps->AsicReg.RD_ModeControl );

	/* enter read mode */
	ioEnterReadMode( ps );

	/* call corresponding read proc */
	ps->Device.ReadData( ps, pBuf, size );

	/* Clear EPP/ECP read mode by simply close scanner path and re-open it */
	ps->CloseScanPath( ps );

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

#ifdef __KERNEL__

/** the wrapper functions to support delayed and non-delayed I/O
 */
_LOC void IOOut( Byte data, UShort port )
{
	DBG( DBG_IOF, "outb(0x%04x, 0x%02x)\n", port, data );
	outb( data, port );
}

_LOC void IOOutDelayed( Byte data, UShort port )
{
	DBG( DBG_IOF, "outb_p(0x%04x, 0x%02x)\n", port, data );
	outb_p( data, port );
}

_LOC Byte IOIn( UShort port )
{
#ifdef DEBUG
	Byte data = inb( port );

	DBG( DBG_IOF, "inb(0x%04x) = 0x%02x\n", port, data );
	return data;
#else
	return inb( port );
#endif
}

_LOC Byte IOInDelayed( UShort port )
{
#ifdef DEBUG
	Byte data = inb_p( port );

	DBG( DBG_IOF, "inb_p(0x%04x) = 0x%02x\n", port, data );
	return data;
#else
	return inb_p( port );
#endif
}
#endif /* guard __KERNEL__ */

/* END PLUSTEK-PP_IO.C ......................................................*/