//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1996  Microsoft Corporation.  All Rights Reserved.
//
//  I2CSCRPT.C
//  I2CScript class implementation.
//      Main Include Module.
//
//==========================================================================;

extern "C"
{
#include <wdm.h>
}

#include <unknown.h>
#include "ks.h"
#include "ksmedia.h"
#include <ksdebug.h>
#include "i2script.h"
#include "wdmdebug.h"

//$REVIEW - Let's find a way to get the proper module name into this
//
#define MODULENAME           "PhilTune"
#define MODULENAMEUNICODE   L"PhilTune"

#define STR_MODULENAME      MODULENAME

#define ENSURE    do
#define END_ENSURE  while( FALSE)
#define FAIL    break


/*^^*
 *      CI2CScript()
 * Purpose  : CI2CScript class constructor.
 *              Performs checking of the I2C provider presence. Sets the script in the initial state.
 *
 * Inputs   :   PUINT puiError                      : pointer to return a completion error code
 *              PHW_STREAM_REQUEST_BLOCK    pSrb    : pointer to HW_INITIALIZE SRB
 *
 * Outputs  : none
 * Author   : IKLEBANOV
 *^^*/
CI2CScript::
CI2CScript(
    PDEVICE_OBJECT pDeviceObject,
    NTSTATUS *      pStatus
    )
{
    *pStatus = STATUS_SUCCESS;

    m_dwI2CAccessKey = 0;

    m_liOperationStartTime.QuadPart = 0;

    m_i2cProviderInterface.i2cOpen = NULL;
    m_i2cProviderInterface.i2cAccess = NULL;

    m_pdoDriver = NULL;

    if( !InitializeAttachI2CProvider( &m_i2cProviderInterface, pDeviceObject))
    {
        *pStatus = STATUS_NOINTERFACE;
    }
    else
    {
        // there was no error to get I2CInterface from the MiniVDD
        m_pdoDriver = pDeviceObject;
        m_ulI2CAccessClockRate = I2C_FIXED_CLOCK_RATE;
    }

    _DbgPrintF( DEBUGLVL_VERBOSE,
                ( "CI2CScript:CI2CScript() exit Error = %x\n", * pStatus)
              );
}



/*^^*
 *      LockI2CProvider()
 * Purpose  : locks the I2CProvider for exclusive use
 *
 * Inputs   : none
 *
 * Outputs  : BOOL : retunrs TRUE, if the I2CProvider is locked
 * Author   : IKLEBANOV
 *^^*/
BOOL CI2CScript::LockI2CProvider( void)
{
    BOOL        bResult;
    I2CControl  i2cAccessBlock;

    bResult = FALSE;

    ENSURE
    {
        if(( m_i2cProviderInterface.i2cOpen == NULL)    ||
            ( m_i2cProviderInterface.i2cAccess == NULL) ||
            ( m_pdoDriver == NULL))
            FAIL;

        i2cAccessBlock.Status = I2C_STATUS_NOERROR;
        if( m_i2cProviderInterface.i2cOpen( m_pdoDriver, TRUE, &i2cAccessBlock) != STATUS_SUCCESS)
        {
            _DbgPrintF( DEBUGLVL_ERROR,
                        ( "CI2CScript: LockI2CProvider() bResult = %x\n",
                          bResult)
                      );
            FAIL;
        }

        if( i2cAccessBlock.Status != I2C_STATUS_NOERROR)
        {
            _DbgPrintF( DEBUGLVL_ERROR,
                        ( "CI2CScript: LockI2CProvider() Status = %x\n",
                          i2cAccessBlock.Status)
                      );
            FAIL;
        }

        // the I2C Provider has granted access - save dwCookie for further use
        m_dwI2CAccessKey = i2cAccessBlock.dwCookie;

        bResult = TRUE;

    } END_ENSURE;

    return( bResult);
}



/*^^*
 *      LockI2CProvider()
 * Purpose  : locks the I2CProvider for exclusive use. Provides attempts to lock the
 *              provider unless either the time-out condition or the attempt succeeded.
 *
 * Inputs   : none
 *
 * Outputs  : BOOL : retunrs TRUE, if the I2CProvider is locked
 * Author   : IKLEBANOV
 *^^*/
BOOL CI2CScript::LockI2CProviderEx( void)
{
    LARGE_INTEGER liTime;

    m_liOperationStartTime.QuadPart = 0;

    while( !LockI2CProvider())
    {
        KeQuerySystemTime( &liTime);

        if( m_liOperationStartTime.QuadPart)
            m_liOperationStartTime.QuadPart = liTime.QuadPart;
        else
            if( liTime.QuadPart - m_liOperationStartTime.QuadPart >
                I2CSCRIPT_TIMELIMIT_OPENPROVIDER)
            {
                // the time is expired - abort the initialization
                return( FALSE);
            }

        liTime.QuadPart = I2CSCRIPT_DELAY_OPENPROVIDER;
        KeDelayExecutionThread( KernelMode, FALSE, &liTime);
    }

    return( TRUE);
}




/*^^*
 *      GetI2CProviderLockStatus()
 * Purpose  : retrieves I2CProvider lock status
 *
 * Inputs   : none
 *
 * Outputs  : BOOL : retunrs TRUE, if the I2CProvider has been locked
 * Author   : IKLEBANOV
 *^^*/
BOOL CI2CScript::GetI2CProviderLockStatus( void)
{

    return( m_dwI2CAccessKey);
}




/*^^*
 *      ReleaseI2CProvider()
 * Purpose  : releases the I2CProvider for other clients' use
 *
 * Inputs   : none
 *
 * Outputs  : BOOL : retunrs TRUE, if the I2CProvider is released
 * Author   : IKLEBANOV
 *^^*/
BOOL CI2CScript::ReleaseI2CProvider( void)
{
    BOOL        bResult;
    I2CControl  i2cAccessBlock;

    bResult = FALSE;

    ENSURE
    {
        if(( m_i2cProviderInterface.i2cOpen == NULL)    ||
            ( m_i2cProviderInterface.i2cAccess == NULL) ||
            ( m_pdoDriver == NULL))
            // the I2CProvider was not found
            FAIL;

        i2cAccessBlock.Status = I2C_STATUS_NOERROR;
        i2cAccessBlock.dwCookie = m_dwI2CAccessKey;
        i2cAccessBlock.ClockRate = m_ulI2CAccessClockRate;
        if( m_i2cProviderInterface.i2cOpen( m_pdoDriver, FALSE, &i2cAccessBlock) != STATUS_SUCCESS)
        {
            _DbgPrintF( DEBUGLVL_ERROR,
                        ( "CI2CScript: ReleaseI2CProvider() bResult = %x\n",
                          bResult)
                      );
            FAIL;
        }

        if( i2cAccessBlock.Status != I2C_STATUS_NOERROR)
        {
            _DbgPrintF( DEBUGLVL_ERROR,
                        ( "CI2CScript: ReleaseI2CProvider() bResult = %x\n",
                          bResult)
                      );
            FAIL;
        }

        m_dwI2CAccessKey = 0;
        bResult = TRUE;

    } END_ENSURE;

    return( bResult);
}



/*^^*
 *      PerformI2CPacketOperation()
 * Purpose  : synchronosly executes I2C access packet. It assumed to be executed at Low priority.
 *              The function does not return until the I2C session is done. The execution
 *              is not dependent on the I2C Provider lock status
 *
 * Inputs   :   PI2CPacket pI2CPacket : pointer to I2C access packet
 *
 * Outputs  : BOOL : returns TRUE, if I2C operation was carried out successfuly
 *              The error status is returned via uchI2CResult field of the PI2CPacket
 * Author   : IKLEBANOV
 *^^*/
BOOL CI2CScript::PerformI2CPacketOperation( IN OUT PI2CPacket pI2CPacket)
{
    BOOL bResult;

    if( GetI2CProviderLockStatus())
        // the Provider was locked before and we're not going to change it
        bResult = ExecuteI2CPacket( pI2CPacket);
    else
    {
        // the Provider was not locked and it's our responsibility to lock it first,
        // execute I2C operation and release it after the use
        if( LockI2CProviderEx())
        {
            bResult = ExecuteI2CPacket( pI2CPacket);
            ReleaseI2CProvider();
        }
        else
            bResult = FALSE;
    }

    return( bResult);
}



/*^^*
 *      ExecuteI2CPacket()
 * Purpose  : synchronosly executes I2C access packet. It assumed to be executed at Low priority.
 *              The function does not return until the I2C session is done. This kind of access
 *              is used during initialization ( boot up) time only. This function should be
 *              called only after the I2CProvider was locked for exclusive service
 *
 * Inputs   :   PI2CPacket pI2CPacket : pointer to I2C access packet
 *
 * Outputs  : BOOL : returns TRUE, if I2C operation was carried out successfuly
 *              The error status is returned via uchI2CResult field of the PI2CPacket
 * Author   : IKLEBANOV
 *^^*/
BOOL CI2CScript::ExecuteI2CPacket( IN OUT PI2CPacket pI2CPacket)
{
    UINT    nError, cbCount;
    UCHAR   uchValue;
    UCHAR   uchI2CResult = I2C_STATUS_ERROR;

    ENSURE
    {
        I2CControl  i2cAccessBlock;

        if(( nError = CheckI2CScriptPacket( pI2CPacket)) != I2CSCRIPT_NOERROR)
            FAIL;

        // we'll use I2CProvider interface, assuming there is a syncronous provider
        // for asynchronous provider some work has to be added. 16 bits emulation is
        // not supported at this time either. This implementation does not support
        // Read-Modify-Write request either
        ENSURE
        {
            UINT        nIndex;

            i2cAccessBlock.dwCookie = m_dwI2CAccessKey;
            i2cAccessBlock.ClockRate = m_ulI2CAccessClockRate;

            // We assume the last byte in the buffer belongs to the Write operation
            // after Read-Modify, is specified.
            cbCount = ( pI2CPacket->usFlags & I2COPERATION_READWRITE) ?
                            ( pI2CPacket->cbWriteCount - 1) : ( pI2CPacket->cbWriteCount);

            if( cbCount)
            {
                // implement a write request
                // apply START condition with the I2C chip address first
                i2cAccessBlock.Flags = I2C_FLAGS_START | I2C_FLAGS_ACK;
                i2cAccessBlock.Command = I2C_COMMAND_WRITE;
                i2cAccessBlock.Data = pI2CPacket->uchChipAddress & 0xFE;
                if( AccessI2CProvider( m_pdoDriver, &i2cAccessBlock) != I2C_STATUS_NOERROR)
                    FAIL;

                i2cAccessBlock.Flags = I2C_FLAGS_ACK;
                for( nIndex = 0; nIndex < cbCount; nIndex ++)
                {
                    // write the data from the buffer
                    i2cAccessBlock.Data = pI2CPacket->puchWriteBuffer[nIndex];
                    if(( nIndex == cbCount - 1) &&
                        !( pI2CPacket->usFlags & I2COPERATION_RANDOMACCESS))
                        // the last byte to write - apply STOP condition, if no
                        // I2COPERATION_RANDOMACCESS flag is specified
                        i2cAccessBlock.Flags |= I2C_FLAGS_STOP;

                    if( AccessI2CProvider( m_pdoDriver, &i2cAccessBlock) != I2C_STATUS_NOERROR)
                        break;
                }
                if( nIndex != cbCount)
                    FAIL;
/*  // STOP condition is applied withe the last byte to be written
                // apply stop condition as the end of write operation
                i2cAccessBlock.Flags = I2C_FLAGS_STOP;
                i2cAccessBlock.Command = I2C_COMMAND_NULL;
                m_i2cProviderInterface.i2cAccess( m_pdoDriver, &i2cAccessBlock);
*/
            }

            if( pI2CPacket->cbReadCount)
            {
                // implement a read request
                // apply START condition with the I2C chip address first
                i2cAccessBlock.Flags = I2C_FLAGS_START | I2C_FLAGS_ACK;
                i2cAccessBlock.Command = I2C_COMMAND_WRITE;
                i2cAccessBlock.Data = pI2CPacket->uchChipAddress | 0x01;
                if( AccessI2CProvider( m_pdoDriver, &i2cAccessBlock) != I2C_STATUS_NOERROR)
                    FAIL;

                i2cAccessBlock.Flags = I2C_FLAGS_ACK;
                i2cAccessBlock.Command = I2C_COMMAND_READ;
                for( nIndex = 0; nIndex < pI2CPacket->cbReadCount; nIndex ++)
                {
                    // read the data to the buffer
                    if( nIndex == ( UINT)( pI2CPacket->cbReadCount - 1))
                    {
                        // don't apply ACK at the last read - read operation termination
                        i2cAccessBlock.Flags &= ~I2C_FLAGS_ACK;
                        // also apply STOP condition for the last byte
                        i2cAccessBlock.Flags |= I2C_FLAGS_STOP;
                    }

                    if( AccessI2CProvider( m_pdoDriver, &i2cAccessBlock) != I2C_STATUS_NOERROR)
                        break;
                    pI2CPacket->puchReadBuffer[nIndex] = i2cAccessBlock.Data;
                }
                if( nIndex != pI2CPacket->cbReadCount)
                    FAIL;

/*  // STOP condition is applied with the last byte to be read
                // apply stop condition as the end of read operation
                i2cAccessBlock.Flags = I2C_FLAGS_STOP;
                i2cAccessBlock.Command = I2C_COMMAND_NULL;
                m_i2cProviderInterface.i2cAccess( m_pdoDriver, &i2cAccessBlock);
*/
                if( pI2CPacket->usFlags & I2COPERATION_READWRITE)
                {
                    // write operation should be taken care again, the last byte in the pbyWriteBuffer
                    // should be constructed from the value read back and the binary operations OR and AND
                    // with the values specified in the packet
                    uchValue = pI2CPacket->puchReadBuffer[pI2CPacket->cbReadCount - 1];
                    uchValue &= pI2CPacket->uchANDValue;
                    pI2CPacket->puchWriteBuffer[pI2CPacket->cbWriteCount - 1] = uchValue | pI2CPacket->uchORValue;

                    if( pI2CPacket->cbWriteCount)
                    {
                        // implement a write request
                        // apply START condition with the I2C chip address first
                        i2cAccessBlock.Flags = I2C_FLAGS_START | I2C_FLAGS_ACK;
                        i2cAccessBlock.Command = I2C_COMMAND_WRITE;
                        i2cAccessBlock.Data = pI2CPacket->uchChipAddress & 0xFE;
                        if( AccessI2CProvider( m_pdoDriver, &i2cAccessBlock) != I2C_STATUS_NOERROR)
                            FAIL;

                        i2cAccessBlock.Flags = I2C_FLAGS_ACK;
                        for( nIndex = 0; nIndex < pI2CPacket->cbWriteCount; nIndex ++)
                        {
                            // write the data from the buffer
                            i2cAccessBlock.Data = pI2CPacket->puchWriteBuffer[nIndex];
                            if( nIndex == ( UINT)( pI2CPacket->cbWriteCount - 1))
                                // the last byte to write - apply STOP condition
                                i2cAccessBlock.Flags |= I2C_FLAGS_STOP;

                            if( AccessI2CProvider( m_pdoDriver, &i2cAccessBlock) != I2C_STATUS_NOERROR)
                                break;
                        }

                        if( nIndex != pI2CPacket->cbWriteCount)
                            FAIL;
/*  // STOP condition is applied withe the last byte to be written
                        // apply stop condition as the end of write operation
                        i2cAccessBlock.Flags = I2C_FLAGS_STOP;
                        i2cAccessBlock.Command = I2C_COMMAND_NULL;
                        m_i2cProviderInterface.i2cAccess( m_pdoDriver, &i2cAccessBlock);
*/
                    }
                }
            }

            uchI2CResult = I2C_STATUS_NOERROR;

        } END_ENSURE;

        if( uchI2CResult == I2C_STATUS_ERROR)
        {
            // there was an error during accessing I2C - issue Reset command
            i2cAccessBlock.Command = I2C_COMMAND_RESET;
            AccessI2CProvider( m_pdoDriver, &i2cAccessBlock);
        }

        pI2CPacket->uchI2CResult = uchI2CResult;

        return( TRUE);

    } END_ENSURE;

    _DbgPrintF( DEBUGLVL_VERBOSE,
                ( "CI2CScript:ExecuteI2CPacket() nError = %x", nError)
              );
    return( FALSE);
}



/*^^*
 *      CheckI2CScriptPacket()
 * Purpose  : checks integrity of the I2C control package
 *
 * Inputs   :   PI2CPacket pI2CPacket   : pointer to I2C access packet
 *
 * Outputs  : BOOL : returns TRUE, if I2C control package is a valid one
 *
 * Author   : IKLEBANOV
 *^^*/
UINT CI2CScript::CheckI2CScriptPacket( IN PI2CPacket pI2CPacket)
{
    UINT nPacketError;

    ENSURE
    {
        if(( m_i2cProviderInterface.i2cOpen == NULL)    ||
            ( m_i2cProviderInterface.i2cAccess == NULL) ||
            ( m_pdoDriver == NULL))
        {
            // the I2CProvider was not found
            nPacketError = I2CSCRIPT_ERROR_NOPROVIDER;
            FAIL;
        }

        if(( !pI2CPacket->cbWriteCount) && ( !pI2CPacket->cbReadCount))
        {
            // nothing to do
            nPacketError = I2CSCRIPT_ERROR_NODATA;
            FAIL;
        }

        if((( pI2CPacket->cbWriteCount) && ( pI2CPacket->puchWriteBuffer == NULL))
            || (( pI2CPacket->cbReadCount) && ( pI2CPacket->puchReadBuffer == NULL)))
        {
            // NULL pointer, when the data is specified
            nPacketError = I2CSCRIPT_ERROR_NOBUFFER;
            FAIL;
        }

        if(( pI2CPacket->usFlags & I2COPERATION_READWRITE) && ( !pI2CPacket->cbWriteCount))
        {
            // if Read-Modify-Write is specified, the Write data should be present
            nPacketError = I2CSCRIPT_ERROR_READWRITE;
            FAIL;
        }

        nPacketError = I2CSCRIPT_NOERROR;

    } END_ENSURE;

    return( nPacketError);
}




/*^^*
 *      ClearScript()
 * Purpose  : clears I2CScript to the NULL state - no I2C operations are on hold.
 *
 * Inputs   :   none
 *
 * Outputs  : none
 * Author   : IKLEBANOV
 *^^*/
void CI2CScript::ClearScript( void)
{

    m_nExecutionIndex = 0;
    m_nScriptLength = 0;
    m_pfnReturnWhenDone = NULL;
    m_bExecutionInProcess = FALSE;
}



/*^^*
 *      AppendToScript()
 * Purpose  : appends a I2CPacket to the bottom of the I2CScript.
 *              The 16 bits emulation is not implemented at this time.
 *
 * Inputs   :   PI2CPacket pI2CPacket - pointer to the I2C packet to append
 *
 * Outputs  : BOOL : returns TRUE, if the packet was successfully appended.
 *              FALSE might happend if the I2CPacket is a bad one, or overflow occurs
 * Author   : IKLEBANOV
 *^^*/
BOOL CI2CScript::AppendToScript( PI2CPacket pI2CPacket)
{
    UINT    nError, nScriptIndex;
    UINT    nIndex, cbCount;

    ENSURE
    {
        PI2CScriptPrimitive pI2CPrimitive;

        if(( nError = CheckI2CScriptPacket( pI2CPacket)) != I2CSCRIPT_NOERROR)
            FAIL;
        nError = I2CSCRIPT_ERROR_OVERFLOW;

        // m_nExecutionIndex is used as a Script build index. We will work with a local copy of it
        // first to ensure we have no overflow
        nScriptIndex = m_nExecutionIndex;
        pI2CPrimitive = &m_i2cScript[nScriptIndex];

        // We assume the last byte in the buffer belongs to the Write operation
        // after Read-Modify, is specified.
        cbCount = ( pI2CPacket->usFlags & I2COPERATION_READWRITE) ? \
                        ( pI2CPacket->cbWriteCount - 1) : ( pI2CPacket->cbWriteCount);

        if( cbCount)
        {
            // I2C Chip address should be taken care of first
            pI2CPrimitive->ulCommand = I2C_COMMAND_WRITE;
            pI2CPrimitive->byData = pI2CPacket->uchChipAddress;
            pI2CPrimitive->byANDData = 0xFE;
            pI2CPrimitive->byORData = 0x00;
            pI2CPrimitive->ulProviderFlags = I2C_FLAGS_START | I2C_FLAGS_ACK;
            pI2CPrimitive->byFlags = 0x0;

            // check the Script length
            if( ++ nScriptIndex >= I2CSCRIPT_LENGTH_MAXIMUM)
                FAIL;
            pI2CPrimitive ++;

            // I2C write buffer should be taken care of.
            for( nIndex = 0; nIndex < cbCount; nIndex ++)
            {
                pI2CPrimitive->ulCommand = I2C_COMMAND_WRITE;
                pI2CPrimitive->byData = pI2CPacket->puchWriteBuffer[nIndex];
                pI2CPrimitive->byORData = 0x00;
                pI2CPrimitive->byANDData = 0xFF;
                pI2CPrimitive->ulProviderFlags = I2C_FLAGS_ACK;
                pI2CPrimitive->byFlags = 0x0;

                if( nIndex == cbCount - 1)
                    // this is the last byte to be written - apply STOP
                    pI2CPrimitive->ulProviderFlags |= I2C_FLAGS_STOP;

                // check the Script length
                if( ++ nScriptIndex >= I2CSCRIPT_LENGTH_MAXIMUM)
                    break;
                pI2CPrimitive ++;
            }

            // check the Script length
            if( nScriptIndex >= I2CSCRIPT_LENGTH_MAXIMUM)
                FAIL;

/*
    // Stop condition is applied with the last byte to be written
    // We finished Write portion here, whether it's a Write only, Read-Modify-Write operation
            pI2CPrimitive->ulCommand = I2C_COMMAND_NULL;
            pI2CPrimitive->ulProviderFlags = I2C_FLAGS_STOP;
            pI2CPrimitive->byFlags = 0x0;

            // check the Script length
            if( ++ nScriptIndex >= I2CSCRIPT_LENGTH_MAXIMUM)
                FAIL;
            pI2CPrimitive ++;
*/
        }

        // We have to see, if there is a Read operation involved
        if( pI2CPacket->cbReadCount)
        {
            // I2C Chip address should be taken care of first
            pI2CPrimitive->ulCommand = I2C_COMMAND_WRITE;
            pI2CPrimitive->byData = pI2CPacket->uchChipAddress;
            pI2CPrimitive->byANDData = 0xFE;
            pI2CPrimitive->byORData = 0x01;
            pI2CPrimitive->ulProviderFlags = I2C_FLAGS_START | I2C_FLAGS_ACK;
            pI2CPrimitive->byFlags = 0x0;

            // check the Script length
            if( ++ nScriptIndex >= I2CSCRIPT_LENGTH_MAXIMUM)
                FAIL;
            pI2CPrimitive ++;

            // I2C read buffer should be taken care of. We assume the last byte in the buffer belongs to
            // the Write operation after Read-Modify, is specified.
            for( nIndex = 0; nIndex < pI2CPacket->cbReadCount; nIndex ++)
            {
                pI2CPrimitive->ulCommand = I2C_COMMAND_READ;
                if( nIndex == ( UINT)( pI2CPacket->cbReadCount - 1))
                {
                    pI2CPrimitive->ulProviderFlags = I2C_FLAGS_STOP;
                    pI2CPrimitive->byFlags = pI2CPacket->usFlags & I2COPERATION_READWRITE;
                }
                else
                {
                    pI2CPrimitive->ulProviderFlags = I2C_FLAGS_ACK;
                    pI2CPrimitive->byFlags = 0x0;
                }

                // check the Script length
                if( ++ nScriptIndex >= I2CSCRIPT_LENGTH_MAXIMUM)
                    break;
                pI2CPrimitive ++;
            }

            // check the Script length
            if( nScriptIndex >= I2CSCRIPT_LENGTH_MAXIMUM)
                FAIL;

/*  // Stop condition is applied with the last byte to be read
            // We finished Read portion here, whether it's a Read only, Read-Modify-Write operation
            pI2CPrimitive->ulCommand = I2C_COMMAND_NULL;
            pI2CPrimitive->ulProviderFlags = I2C_FLAGS_STOP;
            pI2CPrimitive->byFlags = 0x0;

            // check the Script length
            if( ++ nScriptIndex >= I2CSCRIPT_LENGTH_MAXIMUM)
                FAIL;
            pI2CPrimitive ++;
*/
        }

        // the last thing left to do, is to implement Write after Read-Modify, if specified
        if( pI2CPacket->usFlags & I2COPERATION_READWRITE)
        {
            // I2C Chip address should be taken care of first
            pI2CPrimitive->ulCommand = I2C_COMMAND_WRITE;
            pI2CPrimitive->byData = pI2CPacket->uchChipAddress;
            pI2CPrimitive->byANDData = 0xFE;
            pI2CPrimitive->byORData = 0x00;
            pI2CPrimitive->ulProviderFlags = I2C_FLAGS_START | I2C_FLAGS_ACK;
            pI2CPrimitive->byFlags = 0x0;

            // check the Script length
            if( ++ nScriptIndex >= I2CSCRIPT_LENGTH_MAXIMUM)
                FAIL;
            pI2CPrimitive ++;

            // I2C write buffer should be taken care of.
            for( nIndex = 0; nIndex < pI2CPacket->cbWriteCount; nIndex ++)
            {
                pI2CPrimitive->ulCommand = I2C_COMMAND_WRITE;
                pI2CPrimitive->byData = pI2CPacket->puchWriteBuffer[nIndex];
                pI2CPrimitive->ulProviderFlags = I2C_FLAGS_ACK;
                if( nIndex == ( UINT)( pI2CPacket->cbWriteCount - 1))
                {
                    // it's time to write the byte modified after the Read operation
                    pI2CPrimitive->byORData = pI2CPacket->uchORValue;
                    pI2CPrimitive->byANDData = pI2CPacket->uchANDValue;
                    pI2CPrimitive->byFlags = I2COPERATION_READWRITE;
                    // apply STOP condition with the last byte to be read
                    pI2CPrimitive->ulProviderFlags |= I2C_FLAGS_STOP;
                }
                else
                {
                    pI2CPrimitive->byORData = 0x00;
                    pI2CPrimitive->byANDData = 0xFF;
                    pI2CPrimitive->byFlags = 0x0;
                }

                // check the Script length
                if( ++ nScriptIndex >= I2CSCRIPT_LENGTH_MAXIMUM)
                    break;
                pI2CPrimitive ++;
            }

            // check the Script length
            if( nScriptIndex >= I2CSCRIPT_LENGTH_MAXIMUM)
                FAIL;

/*  // Stop condition is applied with the last byte to be written
            // We finished Write portion here, whether it's a Write only, Read-Modify-Write operation
            pI2CPrimitive->ulCommand = I2C_COMMAND_NULL;
            pI2CPrimitive->ulProviderFlags = I2C_FLAGS_STOP;
            pI2CPrimitive->byFlags = 0x0;

            // check the Script length
            if( ++ nScriptIndex >= I2CSCRIPT_LENGTH_MAXIMUM)
                FAIL;
            pI2CPrimitive ++;
*/
        }

        // the Packet was added succesfully to the Script. Modify the Script propertirs
        m_nExecutionIndex = nScriptIndex;
        m_nScriptLength = nScriptIndex;
        return( TRUE);

    } END_ENSURE;

    _DbgPrintF( DEBUGLVL_VERBOSE,
                ( "CI2CScript:AppendToScript() nError = %x", nError)
              );
    return( FALSE);
}



/*^^*
 *      ExecuteScript()
 * Purpose  : triggers the execution of previously built I2CScript. This function is also
 *              responsible for allocating I2CProvider for its own exclusive use.
 *
 * Inputs   :   PIRP pIrp
 *              PHWCompletionRoutine pfnScriptCompletion: function pointer will be called,
 *                  when the script execution is completed. Indicates the Script execution
 *                  is to be carried out asynchronously.
 *
 * Outputs  : BOOL : returns TRUE, if the execution was successfully triggered.
 *              FALSE might happend if the Script has not been built by the time of the call
 *
 * Note     : if pfnScriptExecuted is NULL pointer, the Script will be executed synchronously
 *
 * Author   : IKLEBANOV
 *^^*/
BOOL CI2CScript::ExecuteScript( IN PIRP                     pIrp,
                                IN PHWCompletionRoutine     pfnScriptCompletion)
{

    ENSURE
    {
        if( pfnScriptCompletion != NULL)
            // not supported at this point. The idea is to create a new system thread,
            // where the Script to be executed. When the Script will be copleted,
            // call-back is called and the thred terminates itself.
            FAIL;

        if( !m_nExecutionIndex)
            FAIL;

        // there is not a NULL Script - proceed
        m_nScriptLength = m_nExecutionIndex;
        m_nExecutionIndex = m_nCompletionIndex = 0;

        if( !GetI2CProviderLockStatus())
            // The provider was not locked prior the Script execution
            if( !LockI2CProviderEx())
                FAIL;

        InterpreterScript();

        ReleaseI2CProvider();

        return( TRUE);

    } END_ENSURE;

    return( FALSE);
}



/*^^*
 *      InterpreterScript()
 * Purpose  : interpreters the I2CScript line by line. The Script is not cleaned up
 *              after the completion to allow to client retrive the results of
 *              the script execution. It's the client responsibility to clean it up
 *              upon the results retrieving
 *
 * Inputs   : none
 * Outputs  : none
 *
 * Author   : IKLEBANOV
 *^^*/
void CI2CScript::InterpreterScript( void)
{
    UINT        nScriptIndex, nIndex;
    I2CControl  i2cAccessBlock;

    m_bExecutionInProcess = TRUE;

    i2cAccessBlock.dwCookie = m_dwI2CAccessKey;
    i2cAccessBlock.ClockRate = m_ulI2CAccessClockRate;

    // We'll interpreter every line of the Script and call the I2C Provider to
    // execute it. It's assumed the I2CProvider is a syncronous one. If it's not the
    // case, the special care should be taken of based upon returned value I2C_STATUS_BUSY
    // in the Status.
    for( nScriptIndex = 0; nScriptIndex < m_nScriptLength; nScriptIndex ++)
    {
        i2cAccessBlock.Command = m_i2cScript[nScriptIndex].ulCommand;
        i2cAccessBlock.Flags = m_i2cScript[nScriptIndex].ulProviderFlags;
        if( i2cAccessBlock.Command == I2C_COMMAND_WRITE)
        {
            i2cAccessBlock.Data = m_i2cScript[nScriptIndex].byData;
            i2cAccessBlock.Data &= m_i2cScript[nScriptIndex].byANDData;
            i2cAccessBlock.Data |= m_i2cScript[nScriptIndex].byORData;
        }

        if( AccessI2CProvider( m_pdoDriver, &i2cAccessBlock) == I2C_STATUS_ERROR)
            break;

        // check, wether it's a Read operation - save result
        if( i2cAccessBlock.Command == I2C_COMMAND_READ)
        {
            m_i2cScript[nScriptIndex].byData = i2cAccessBlock.Data;
            // check, if this data belongs to Read-Modify-Write operation
            if( m_i2cScript[nScriptIndex].byFlags & I2COPERATION_READWRITE)
            {
                // let's look for the next I2COPERATION_READWRITE flag - it is the pair
                for( nIndex = nScriptIndex; nIndex < m_nScriptLength; nIndex ++)
                    if(( m_i2cScript[nIndex].ulCommand == I2C_COMMAND_WRITE) &&
                        ( m_i2cScript[nIndex].byFlags & I2COPERATION_READWRITE))
                        break;

                if( nIndex >= m_nScriptLength)
                    // the Script got corrupted
                    break;

                m_i2cScript[nIndex].byData = i2cAccessBlock.Data;
            }
        }
    }

    m_nCompletionIndex = nScriptIndex;

    m_bExecutionInProcess = FALSE;
}



/*^^*
 *      AccessI2CProvider()
 * Purpose  : provide synchronous type of access to I2CProvider
 *
 * Inputs   :   PDEVICE_OBJECT pdoDriver    : pointer to the client's device object
 *              PI2CControl pi2cAccessBlock : pointer to a composed I2C access block
 *
 * Outputs  : UINT : status of the I2C operation I2C_STATUS_NOERROR or I2C_STATUS_ERROR
 *
 * Author   : IKLEBANOV
 *^^*/
UINT CI2CScript::
AccessI2CProvider( PDEVICE_OBJECT pdoClient, PI2CControl pi2cAccessBlock)
{
    UINT            uiStatus;
    LARGE_INTEGER   liTime;

    do
    {
        // this loop is infinitive. It has to be taken care of
        ASSERT( KeGetCurrentIrql() == PASSIVE_LEVEL);
        if( m_i2cProviderInterface.i2cAccess( pdoClient, pi2cAccessBlock) != STATUS_SUCCESS)
        {
            uiStatus = I2C_STATUS_ERROR;
            break;
        }

        if( pi2cAccessBlock->Status != I2C_STATUS_BUSY)
        {
            uiStatus = pi2cAccessBlock->Status;
            break;
        }

        liTime.QuadPart = I2CSCRIPT_DELAY_GETPROVIDERSTATUS;
        ::KeDelayExecutionThread( KernelMode, FALSE, &liTime);

        pi2cAccessBlock->Command = I2C_COMMAND_STATUS;

    } while( TRUE);

    return( uiStatus);
}



/*^^*
 *      GetScriptResults()
 * Purpose  : returns result of the executed Script
 *              This function idealy is called twice:
 *                  first time with the puchReadBuffer = NULL to retrive the number of bytes read
 *                  second time - to fill in the pointer
 * Inputs   :   PUINT puiReadCount  : pointer to the counter of the bytes were read
 *              PUCH puchReadBuffer : pointer to the buffer to put the data
 *
 * Outputs  : UINT : status of the I2C operation
 *              If the status is I2C_STATUS_ERROR, puiReadCount will contain the step, where
 *              I2CScript failed
 * Author   : IKLEBANOV
 *^^*/
UINT CI2CScript::GetScriptResults( PUINT puiReadCount, PUCHAR puchReadBuffer)
{
    UINT nScriptIndex, nCount;

    ASSERT( puiReadCount != NULL);

    if( m_bExecutionInProcess)
        return( I2C_STATUS_BUSY);

    if( m_nScriptLength != m_nCompletionIndex)
    {
        // if the case of failure, step where I2CScript failed is return
        // instead of Read Counter. The returned status indicates the
        // failure
        * puiReadCount = m_nCompletionIndex;

        return( I2C_STATUS_ERROR);
    }
    else
    {
        nCount = 0;

        for( nScriptIndex = 0; nScriptIndex < m_nCompletionIndex; nScriptIndex ++)
        {
            if( m_i2cScript[nScriptIndex].ulCommand == I2C_COMMAND_READ)
            {
                if( puchReadBuffer != NULL)
                    // fill in the supplied buffer
                    puchReadBuffer[nCount] = m_i2cScript[nScriptIndex].byData;
                nCount ++;
            }
        }

        * puiReadCount = nCount;

        return( I2C_STATUS_NOERROR);
    }
}



/*^^*
 *      InitializeAttachI2CProvider()
 * Purpose  : gets the pointer to the parent I2C Provider interface using
 *              several IRP_MJ_??? functions.
 *              This function will be called at Low priority
 *
 * Inputs   :   I2CINTERFACE * pI2CInterface    : pointer to the Interface to be filled in
 *              PDEVICE_OBJECT pDeviceObject    : MiniDriver device object, which is a child of I2C Master
 *
 * Outputs  : BOOL  - returns TRUE, if the interface was found
 * Author   : IKLEBANOV
 *^^*/
BOOL CI2CScript::InitializeAttachI2CProvider( I2CINTERFACE * pI2CInterface, PDEVICE_OBJECT pDeviceObject)
{
    BOOL bResult;

    // try the new path
    bResult = LocateAttachI2CProvider( pI2CInterface, pDeviceObject, IRP_MJ_PNP);
    if(( pI2CInterface->i2cOpen == NULL) || ( pI2CInterface->i2cAccess == NULL))
    {
        // TRAP;
        _DbgPrintF( DEBUGLVL_ERROR,
                    ( "CI2CScript(): interface has NULL pointers\n")
                  );
        bResult = FALSE;
    }

    return( bResult);
}



/*^^*
 *      LocateAttachI2CProvider()
 * Purpose  : gets the pointer to the parent I2C Provider interface
 *              This function will be called at Low priority
 *
 * Inputs   :   I2CINTERFACE * pI2CInterface    : pointer to the Interface to be filled in
 *              PDEVICE_OBJECT pDeviceObject    : MiniDriver device object, which is a child of I2C Master
 *              int         nIrpMajorFunction   : IRP major function to query the I2C Interface
 *
 * Outputs  : BOOL  - returns TRUE, if the interface was found
 * Author   : IKLEBANOV
 *^^*/
BOOL CI2CScript::LocateAttachI2CProvider( I2CINTERFACE * pI2CInterface, PDEVICE_OBJECT pDeviceObject, int nIrpMajorFunction)
{
    PIRP    pIrp;
    BOOL    bResult = FALSE;

    ENSURE
    {
        PIO_STACK_LOCATION  pNextStack;
        NTSTATUS            ntStatus;
        KEVENT              Event;


        pIrp = IoAllocateIrp( pDeviceObject->StackSize, FALSE);
        if( pIrp == NULL)
        {
            // TRAP;
            _DbgPrintF( DEBUGLVL_ERROR,
                        ( "CI2CScript(): can not allocate IRP\n")
                      );
            FAIL;
        }

        pNextStack = IoGetNextIrpStackLocation( pIrp);
        if( pNextStack == NULL)
        {
            // TRAP;
            _DbgPrintF( DEBUGLVL_ERROR,
                        ( "CI2CScript(): can not allocate NextStack\n")
                      );
            FAIL;
        }

        //$REVIEW - Should change function decl to make nIrpMajorFunction be UCHAR - TCP
        pNextStack->MajorFunction = (UCHAR) nIrpMajorFunction;
        pNextStack->MinorFunction = IRP_MN_QUERY_INTERFACE;
        KeInitializeEvent( &Event, NotificationEvent, FALSE);

        IoSetCompletionRoutine( pIrp,
                                I2CScriptIoSynchCompletionRoutine,
                                &Event, TRUE, TRUE, TRUE);

        pNextStack->Parameters.QueryInterface.InterfaceType = ( struct _GUID *)&GUID_I2C_INTERFACE;
        pNextStack->Parameters.QueryInterface.Size = sizeof( I2CINTERFACE);
        pNextStack->Parameters.QueryInterface.Version = 1;
        pNextStack->Parameters.QueryInterface.Interface = ( PINTERFACE)pI2CInterface;
        pNextStack->Parameters.QueryInterface.InterfaceSpecificData = NULL;

        ntStatus = IoCallDriver( pDeviceObject, pIrp);

        if( ntStatus == STATUS_PENDING)
            KeWaitForSingleObject(  &Event,
                                    Suspended, KernelMode, FALSE, NULL);
        if(( pI2CInterface->i2cOpen == NULL) || ( pI2CInterface->i2cAccess == NULL))
        {
            _DbgPrintF( DEBUGLVL_ERROR,
                        ( "CI2CScript(): interface has NULL pointers\n")
                      );
            FAIL;
        }
        bResult = TRUE;

    } END_ENSURE;

    if( pIrp != NULL)
        IoFreeIrp( pIrp);

    return( bResult);
}


/*^^*
 *      I2CScriptIoSynchCompletionRoutine()
 * Purpose  : This routine is for use with synchronous IRP processing.
 *              All it does is signal an event, so the driver knows it and can continue.
 *
 * Inputs   :   PDEVICE_OBJECT DriverObject : Pointer to driver object created by system
 *              PIRP pIrp                   : Irp that just completed
 *              PVOID Event                 : Event we'll signal to say Irp is done
 *
 * Outputs  : none
 * Author   : IKLEBANOV
 *^^*/
extern "C"
NTSTATUS I2CScriptIoSynchCompletionRoutine( IN PDEVICE_OBJECT pDeviceObject,
                                            IN PIRP pIrp,
                                            IN PVOID Event)
{

    KeSetEvent(( PKEVENT)Event, 0, FALSE);
    return( STATUS_MORE_PROCESSING_REQUIRED);
}



/*
 * WriteSeq()
 * Purpose  : Write a specified sequence to address specified.
 *
 *
 * Inputs   :   UCHAR addr : I2CAddress
 *              UCHAR  *p_seq : sequence
 *              USHORT len    : length of the sequence
 *
 * Outputs  : BOOL - TRUE if operation succeeded else FALSE
 * Author   : MM
 *
*/
BOOL CI2CScript::WriteSeq(UCHAR addr, UCHAR  *p_seq, USHORT len)
{

  I2CPacket     i2cPacket;
  LARGE_INTEGER liTime;

   // Init rest of I2CPacket
  i2cPacket.usFlags = I2COPERATION_WRITE;
  i2cPacket.uchChipAddress = addr;
  i2cPacket.cbReadCount = 0;
  i2cPacket.cbWriteCount = len;
  i2cPacket.puchReadBuffer = NULL;
  i2cPacket.puchWriteBuffer = p_seq;

  int counter = 0;

  // Somewhat arbitrary max of 1 second.
  while (!LockI2CProviderEx())
  {
    if (counter++ >= 100)
    {
//    ReleaseI2CProvider();
      return(FALSE);
    }

    liTime.QuadPart = 100000;
    KeDelayExecutionThread(KernelMode, FALSE, &liTime); // = 10 milliseconds
  }

  /*
  if(!CheckInterface(addr))
  {
    ReleaseI2CProvider();
    return FALSE;
  }
*/
  ExecuteI2CPacket(&i2cPacket);
  ReleaseI2CProvider();

  //DebugInfo(("CI2CScript::WriteSeq(): Inside\n"));

  if (i2cPacket.uchI2CResult != I2C_STATUS_NOERROR)
  {
       _DbgPrintF( DEBUGLVL_ERROR,("CI2CScript::WriteSeq(): Error\n"));
    return(FALSE);
  }

  return(TRUE);
}

/*
 * ReadSeq()
 * Purpose  : Read a specified number of bytes from address specified.
 *
 *
 * Inputs   :   UCHAR addr : I2CAddress
 *              UCHAR  *p_seq : sequence
 *              USHORT len    : length of the sequence
 *
 * Outputs  : BOOL - TRUE if operation succeeded else FALSE
 * Author   : MM
 *
 *
*/
BOOL CI2CScript::ReadSeq(UCHAR addr, UCHAR  *p_seq, USHORT len)
{

  I2CPacket     i2cPacket;
  LARGE_INTEGER liTime;

   // Init rest of I2CPacket
  i2cPacket.usFlags = I2COPERATION_READ;
  i2cPacket.uchChipAddress = addr;
  i2cPacket.cbReadCount = len;
  i2cPacket.cbWriteCount = 0;
  i2cPacket.puchReadBuffer = p_seq;
  i2cPacket.puchWriteBuffer = NULL;

  int counter = 0;
  // Somewhat arbitrary max of 1 second.
  while (!LockI2CProviderEx())
  {
    if (counter++ >= 100)
    {
//    ReleaseI2CProvider();
      return(FALSE);
    }

    liTime.QuadPart = 100000;
    KeDelayExecutionThread(KernelMode, FALSE, &liTime); // = 10 milliseconds
  }

  /*
 if(!CheckInterface(addr))
  {
    ReleaseI2CProvider();
    return FALSE;
  }
*/
 ExecuteI2CPacket(&i2cPacket);
  ReleaseI2CProvider();

//  DebugInfo(("CI2CScript::ReadSeq(): Inside\n"));

  if (i2cPacket.uchI2CResult != I2C_STATUS_NOERROR)
  {
    _DbgPrintF( DEBUGLVL_ERROR,("CI2CScript::ReadSeq(): Error\n"));
    return(FALSE);
  }

  return(TRUE);
}

/*
 * CombinedSeq()
 * Purpose  : Write a specified sequence and then read a specified number of
 *             bytes from address specified.
 *
 *
 * Inputs   :   UCHAR addr : I2CAddress
 *              UCHAR seqWr[] : write sequence
 *              USHORT lenWr    : length of the write sequence
 *              UCHAR seqRd[]   : read sequence
 *              USHORT lenRd    : length of the read sequence
 *
 * Outputs  : BOOL - TRUE if operation succeeded else FALSE
 * Author   : MM
 *
 *
*/

BOOL
CI2CScript::CombinedSeq(UCHAR addr, UCHAR seqWr[], USHORT lenWr, UCHAR seqRd[], USHORT lenRd)
{

 I2CPacket     i2cPacket;
 LARGE_INTEGER liTime;

  // Init rest of I2CPacket
  i2cPacket.usFlags = I2COPERATION_READWRITE;
  i2cPacket.uchChipAddress = addr;
  i2cPacket.cbReadCount = lenRd;
  i2cPacket.cbWriteCount = lenWr;
  i2cPacket.puchReadBuffer = seqRd;
  i2cPacket.puchWriteBuffer = seqWr;

  int counter = 0;
 // Somewhat arbitrary max of 1 second.
  while (!LockI2CProviderEx())
  {
    if (counter++ >= 100)
    {
//    ReleaseI2CProvider();
      return(FALSE);
    }

    liTime.QuadPart = 100000;
    KeDelayExecutionThread(KernelMode, FALSE, &liTime); // = 10 milliseconds
  }

  if(!CheckInterface(addr))
  {
    ReleaseI2CProvider();
    return FALSE;
  }

  ExecuteI2CPacket(&i2cPacket);
  ReleaseI2CProvider();

  //DebugInfo(("CI2CScript::CombinedSeq(): Inside\n"));

  if (i2cPacket.uchI2CResult != I2C_STATUS_NOERROR)
  {
    return(FALSE);
  }

  return(TRUE);
}

/*
 * CheckInterface()
 * Purpose  : Check if I2C Interface is working by reading a byte
 *            from the specified address
 *
 * Inputs   :   UCHAR addr : I2CAddress
 *
 * Outputs  : BOOL - TRUE if operation succeeded else FALSE
 * Author   : MM
 *
 *
*/

BOOL CI2CScript::CheckInterface(UCHAR addr)
{

  I2CPacket     i2cPacket;
  LARGE_INTEGER liTime;
  UCHAR ucData;

   // Init rest of I2CPacket
  i2cPacket.usFlags = I2COPERATION_READ;
  i2cPacket.uchChipAddress = addr;
  i2cPacket.cbReadCount = 1;
  i2cPacket.cbWriteCount = 0;
  i2cPacket.puchReadBuffer = &ucData;
  i2cPacket.puchWriteBuffer = NULL;


  ExecuteI2CPacket(&i2cPacket);

  if (i2cPacket.uchI2CResult != I2C_STATUS_NOERROR)
  {
    _DbgPrintF( DEBUGLVL_ERROR,("CI2CScript::CheckInterface(): Error\n"));
    return(FALSE);
  }

  return(TRUE);
}
