////////////////////////////////////////////////////////////////
// File - V3pbclib.c
//
// Library for 'WinDriver for V3 Semiconductor PBC family of devices. 
// The basic idea is to get a handle for the board
// with V3PBC_Open() and use it in the rest of the program
// when calling WD functions.  Call V3PBC_Close() when done.
// 
////////////////////////////////////////////////////////////////

#include "../../include/windrvr.h"
#include "../../include/windrvr_int_thread.h"
#include "pbclib.h"
#include <stdio.h>

// this string is set to an error message, if one occurs
CHAR V3PBC_ErrorString[1024];

// internal function used by V3PBC_Open()
BOOL V3PBC_DetectCardElements(V3PBCHANDLE hV3);

// internal function for setting remap base address
void V3PBC_SetRemapBase0(V3PBCHANDLE hV3, DWORD dwLocalAddr);

// internal implementation of active delay
void V3PBC_SleepMicro(V3PBCHANDLE hV3, DWORD nMicro);

// internal functions used for EEPROM read/write
BOOL I2C_StartSlave(V3PBCHANDLE hV3, BYTE bAddr, BYTE ReadBit);
BOOL I2C_PollAckStart(V3PBCHANDLE hV3, BYTE bAddr, BYTE ReadBit);
BOOL I2C_Write8(V3PBCHANDLE hV3,BYTE bData);
BYTE I2C_Read8(V3PBCHANDLE hV3);

void I2C_Stop(V3PBCHANDLE hV3);
void I2C_NoAck(V3PBCHANDLE hV3);
void I2C_Ack(V3PBCHANDLE hV3);
BYTE I2C_SDAIn(V3PBCHANDLE hV3);
void I2C_SDA(V3PBCHANDLE hV3, BOOL fHigh);
void I2C_SCL(V3PBCHANDLE hV3, BOOL fHigh);
void I2C_Lock(V3PBCHANDLE hV3);
void I2C_UnLock(V3PBCHANDLE hV3);
void I2C_Delay(V3PBCHANDLE hV3);


//////////////////////////////////////////////////////////////////////////////
// Card Detection/Init
//////////////////////////////////////////////////////////////////////////////

DWORD V3PBC_CountCards (DWORD dwVendorID, DWORD dwDeviceID)
{
    WD_VERSION ver;
    WD_PCI_SCAN_CARDS pciScan;
    HANDLE hWD = INVALID_HANDLE_VALUE;

    V3PBC_ErrorString[0] = '\0';
    hWD = WD_Open();
    // check if handle valid & version OK
    if (hWD==INVALID_HANDLE_VALUE) 
    {
        sprintf( V3PBC_ErrorString, "Failed opening " WD_PROD_NAME " device\n");
        return 0;
    }

    BZERO(ver);
    WD_Version(hWD,&ver);
    if (ver.dwVer<WD_VER) 
    {
        sprintf( V3PBC_ErrorString, "Incorrect " WD_PROD_NAME " version\n");
        WD_Close (hWD);
        return 0;
    }

    BZERO(pciScan);
    pciScan.searchId.dwVendorId = dwVendorID;
    pciScan.searchId.dwDeviceId = dwDeviceID;
    WD_PciScanCards (hWD, &pciScan);
    WD_Close (hWD);
    if (pciScan.dwCards==0)
        sprintf( V3PBC_ErrorString, "no cards found\n");
    return pciScan.dwCards;
}


BOOL V3PBC_Open (V3PBCHANDLE *phV3, DWORD dwVendorID, DWORD dwDeviceID, DWORD nCardNum, DWORD dwOptions)
{
    V3PBCHANDLE hV3 = (V3PBCHANDLE) malloc (sizeof (V3PBC_STRUCT));

    WD_VERSION ver;
    WD_PCI_SCAN_CARDS pciScan;
    WD_PCI_CARD_INFO pciCardInfo;

    *phV3 = NULL;
    V3PBC_ErrorString[0] = '\0';
    BZERO(*hV3);

    hV3->cardReg.hCard = 0;
    hV3->hWD = WD_Open();

    // check if handle valid & version OK
    if (hV3->hWD==INVALID_HANDLE_VALUE) 
    {
        sprintf( V3PBC_ErrorString, "Failed opening " WD_PROD_NAME " device\n");
        goto Exit;
    }

    BZERO(ver);
    WD_Version(hV3->hWD,&ver);
    if (ver.dwVer<WD_VER) 
    {
        sprintf( V3PBC_ErrorString, "Incorrect " WD_PROD_NAME " version\n");
        goto Exit;
    }

    pciScan.searchId.dwVendorId = dwVendorID;
    pciScan.searchId.dwDeviceId = dwDeviceID;
    WD_PciScanCards (hV3->hWD, &pciScan);
    if (pciScan.dwCards==0) // Found at least one card
    {
        sprintf( V3PBC_ErrorString, "Could not find PCI card\n");
        goto Exit;
    }
    if (pciScan.dwCards<=nCardNum)
    {
        sprintf( V3PBC_ErrorString, "Card out of range of available cards\n");
        goto Exit;
    }

    BZERO(pciCardInfo);
    pciCardInfo.pciSlot = pciScan.cardSlot[nCardNum];
    WD_PciGetCardInfo (hV3->hWD, &pciCardInfo);
    hV3->pciSlot = pciCardInfo.pciSlot;
    hV3->cardReg.Card = pciCardInfo.Card;

    hV3->fUseInt = (dwOptions & V3PBC_OPEN_USE_INT) ? TRUE : FALSE;
    if (!hV3->fUseInt)
    {
        DWORD i;
        // Remove interrupt item if not needed
        for (i=0; i<hV3->cardReg.Card.dwItems; i++)
        {
            WD_ITEMS *pItem = &hV3->cardReg.Card.Item[i];
            if (pItem->item==ITEM_INTERRUPT)
                pItem->item = ITEM_NONE;
        }
    }
    else
    {
        DWORD i;
        // make interrupt resource sharable
        for (i=0; i<hV3->cardReg.Card.dwItems; i++)
        {
            WD_ITEMS *pItem = &hV3->cardReg.Card.Item[i];
            if (pItem->item==ITEM_INTERRUPT)
                pItem->fNotSharable = FALSE;
        }
    }

    hV3->cardReg.fCheckLockOnly = FALSE;
    WD_CardRegister (hV3->hWD, &hV3->cardReg);
    if (hV3->cardReg.hCard==0)
    {
        sprintf ( V3PBC_ErrorString, "Failed locking device\n");
        goto Exit;
    }

    if (!V3PBC_DetectCardElements(hV3))
    {
        sprintf ( V3PBC_ErrorString, "Card does not have all items expected for V3 PBC\n");
        goto Exit;
    }

    hV3->dwReg_PCI_MAP0 = V3PBC_ReadRegDWord (hV3, V3PBC_PCI_MAP0);

    // Open finished OK
    *phV3 = hV3;
    return TRUE;

Exit:
    // Error durin Open
    if (hV3->cardReg.hCard) 
        WD_CardUnregister(hV3->hWD, &hV3->cardReg);
    if (hV3->hWD!=INVALID_HANDLE_VALUE)
        WD_Close(hV3->hWD);
    free (hV3);
    return FALSE;
}

DWORD V3PBC_ReadPCIReg(V3PBCHANDLE hV3, DWORD dwReg)
{
    WD_PCI_CONFIG_DUMP pciCnf;
    DWORD dwVal;

    BZERO(pciCnf);
    pciCnf.pciSlot = hV3->pciSlot;
    pciCnf.pBuffer = &dwVal;
    pciCnf.dwOffset = dwReg;
    pciCnf.dwBytes = 4;
    pciCnf.fIsRead = TRUE;
    WD_PciConfigDump(hV3->hWD,&pciCnf);
    return dwVal;
}

void V3PBC_WritePCIReg(V3PBCHANDLE hV3, DWORD dwReg, DWORD dwData)
{
    WD_PCI_CONFIG_DUMP pciCnf;

    BZERO (pciCnf);
    pciCnf.pciSlot = hV3->pciSlot;
    pciCnf.pBuffer = &dwData;
    pciCnf.dwOffset = dwReg;
    pciCnf.dwBytes = 4;
    pciCnf.fIsRead = FALSE;
    WD_PciConfigDump(hV3->hWD,&pciCnf);
}

BOOL V3PBC_DetectCardElements(V3PBCHANDLE hV3)
{
    DWORD i;
    DWORD ad_sp;
    DWORD RegisterMask;

    memset (&hV3->Int, 0, sizeof (hV3->Int));
    memset (hV3->addrDesc, 0, sizeof (hV3->addrDesc));

    for (i=0; i<hV3->cardReg.Card.dwItems; i++)
    {
        WD_ITEMS *pItem = &hV3->cardReg.Card.Item[i];

        switch (pItem->item)
        {
        case ITEM_MEMORY:
        case ITEM_IO:
            {
                DWORD dwBytes;
                DWORD dwAddr;
                DWORD dwAddrDirect = 0;
                DWORD dwPhysAddr;
                BOOL fIsMemory;
                if (pItem->item==ITEM_MEMORY)
                {
                    dwBytes = pItem->I.Mem.dwBytes;
                    dwAddr = pItem->I.Mem.dwTransAddr;
                    dwAddrDirect = pItem->I.Mem.dwUserDirectAddr;
                    dwPhysAddr = pItem->I.Mem.dwPhysicalAddr;
                    fIsMemory = TRUE;
                }
                else 
                {
                    dwBytes = pItem->I.IO.dwBytes;
                    dwAddr = pItem->I.IO.dwAddr;
                    dwPhysAddr = dwAddr & 0xffff;
                    fIsMemory = FALSE;
                }

                for (ad_sp=V3PBC_ADDR_IO_BASE; ad_sp<=V3PBC_ADDR_ROM; ad_sp++)
                {
                    DWORD dwPCIAddr;
                    DWORD dwPCIReg;
                    DWORD dwApertureSize;

                    if (hV3->addrDesc[ad_sp].dwAddr) continue;
                    if (ad_sp==V3PBC_ADDR_IO_BASE) dwPCIReg = V3PBC_PCI_IO_BASE;
                    else if (ad_sp==V3PBC_ADDR_BASE0) dwPCIReg = V3PBC_PCI_BASE0;
                    else if (ad_sp==V3PBC_ADDR_BASE1) dwPCIReg = V3PBC_PCI_BASE1;
                    else if (ad_sp==V3PBC_ADDR_ROM) dwPCIReg = V3PBC_PCI_ROM;
					else continue;

                    dwPCIAddr = V3PBC_ReadPCIReg(hV3, dwPCIReg);
                    if (ad_sp==V3PBC_ADDR_IO_BASE)
                    {
                        //
                        // The V3PBC_PCI_IO_BASE register uses address lines 31-8 for decoding. 
                        //
                        RegisterMask = (DWORD) 0xffffff00;
                    }
                    else
                    {
                        dwApertureSize = (dwPCIAddr & 0x000000f0) >> 4;
                        if (dwPCIAddr & 1)
                        {
                            //
                            // The aperture defines an I/O region
                            // Valid I/O values are from 4 to 7 use address lines 31-8
                            //
                            dwApertureSize -= 4;
                            RegisterMask = (DWORD) 0xffffff00;
                        }
                        else
                        {
                            //
                            // The aperture defines a memory region use address lines 31-20
                            //
                            RegisterMask = (DWORD) 0xfff00000;
                        }    

                        //
                        // Reduce mask size base on aperture size
                        //
                        while(dwApertureSize != 0) 
                        {
                            RegisterMask <<=1;    
                            dwApertureSize--;
                        }
                    }


                    if (dwPCIAddr & 1)
                    {
                        if (fIsMemory) continue;
                        dwPCIAddr &= RegisterMask;
                    }
                    else
                    {
                        if (!fIsMemory) continue;
                        dwPCIAddr &= RegisterMask;
                    }
                    //
                    // Found Item description that matches PBC aperture settings, so break for loop 
                    //
                    if (dwPCIAddr==dwPhysAddr)
                        break;
                }
                if (ad_sp<=V3PBC_ADDR_ROM)
                {
                    DWORD j;
                    hV3->addrDesc[ad_sp].dwBytes = dwBytes;
                    hV3->addrDesc[ad_sp].dwAddr = dwAddr;
                    hV3->addrDesc[ad_sp].dwAddrDirect = dwAddrDirect;
                    hV3->addrDesc[ad_sp].fIsMemory = fIsMemory;
                    hV3->addrDesc[ad_sp].dwMask = 0;
                    for (j=1; j<hV3->addrDesc[ad_sp].dwBytes && j!=0x80000000; j *= 2)
                    {
                        hV3->addrDesc[ad_sp].dwMask = 
                            (hV3->addrDesc[ad_sp].dwMask << 1) | 1;
                    }
                }
            }
            break;
        case ITEM_INTERRUPT:
            if (hV3->Int.Int.hInterrupt) return FALSE;
            hV3->Int.Int.hInterrupt = pItem->I.Int.hInterrupt;
            break;
        }
    }

    // check that all the items needed were found
    // check if interrupt found
    if (hV3->fUseInt && !hV3->Int.Int.hInterrupt) 
    {
        return FALSE;
    }

    // check that the registers space was found
    if (!V3PBC_IsAddrSpaceActive(hV3, V3PBC_ADDR_IO_BASE)
            || hV3->addrDesc[V3PBC_ADDR_IO_BASE].dwBytes!=V3PBC_RANGE_REG)
        return FALSE;

    // check that at least one memory space was found
    for (i = V3PBC_ADDR_IO_BASE; i<=V3PBC_ADDR_ROM; i++)
        if (V3PBC_IsAddrSpaceActive(hV3, i)) break;
    if (i>V3PBC_ADDR_ROM) return FALSE;

    return TRUE;
}

void V3PBC_Close(V3PBCHANDLE hV3)
{
    // disable interrupts
    if (V3PBC_IntIsEnabled(hV3))
        V3PBC_IntDisable(hV3);

    // unregister card
    if (hV3->cardReg.hCard) 
        WD_CardUnregister(hV3->hWD, &hV3->cardReg);

    // close WinDriver
    WD_Close(hV3->hWD);

    free (hV3);
}

BOOL V3PBC_IsAddrSpaceActive(V3PBCHANDLE hV3, V3PBC_ADDR addrSpace)
{
    return hV3->addrDesc[addrSpace].dwAddr!=0;
}

DWORD V3PBC_GetRevision(V3PBCHANDLE hV3)
{
    DWORD dwRev = V3PBC_ReadRegDWord(hV3, V3PBC_PCI_CC_REV);
    return dwRev & 0xf;
}

//////////////////////////////////////////////////////////////////////////////
// Access Registers range
//////////////////////////////////////////////////////////////////////////////

void V3PBC_WriteRegDWord (V3PBCHANDLE hV3, DWORD dwReg, DWORD dwData)
{
    V3PBC_WriteSpaceDWord (hV3, V3PBC_ADDR_IO_BASE, dwReg, dwData);
}

DWORD V3PBC_ReadRegDWord (V3PBCHANDLE hV3, DWORD dwReg)
{
    return V3PBC_ReadSpaceDWord(hV3, V3PBC_ADDR_IO_BASE, dwReg);
}

void V3PBC_WriteRegWord (V3PBCHANDLE hV3, DWORD dwReg, WORD wData)
{
    V3PBC_WriteSpaceWord (hV3, V3PBC_ADDR_IO_BASE, dwReg, wData);
}

WORD V3PBC_ReadRegWord (V3PBCHANDLE hV3, DWORD dwReg)
{
    return V3PBC_ReadSpaceWord(hV3, V3PBC_ADDR_IO_BASE, dwReg);
}

void V3PBC_WriteRegByte (V3PBCHANDLE hV3, DWORD dwReg, BYTE bData)
{
    V3PBC_WriteSpaceByte (hV3, V3PBC_ADDR_IO_BASE, dwReg, bData);
}

BYTE V3PBC_ReadRegByte (V3PBCHANDLE hV3, DWORD dwReg)
{
    return V3PBC_ReadSpaceByte(hV3, V3PBC_ADDR_IO_BASE, dwReg);
}

//////////////////////////////////////////////////////////////////////////////
// Access Local range
//////////////////////////////////////////////////////////////////////////////

void V3PBC_SetRemapBase0(V3PBCHANDLE hV3, DWORD dwLocalAddr)
{
    DWORD dwMapRegValue = hV3->dwReg_PCI_MAP0; 
    dwMapRegValue |= (~hV3->addrDesc[V3PBC_ADDR_BASE0].dwMask) & dwLocalAddr;
    V3PBC_WriteRegDWord( hV3, V3PBC_PCI_MAP0, dwMapRegValue);
}

void V3PBC_WriteDWord (V3PBCHANDLE hV3, DWORD dwLocalAddr, DWORD dwData)
{
    V3PBC_SetRemapBase0(hV3, dwLocalAddr);
    V3PBC_WriteSpaceDWord (hV3, V3PBC_ADDR_BASE0, 
        hV3->addrDesc[V3PBC_ADDR_BASE0].dwMask & dwLocalAddr, dwData);
}

DWORD V3PBC_ReadDWord (V3PBCHANDLE hV3, DWORD dwLocalAddr)
{
    V3PBC_SetRemapBase0(hV3, dwLocalAddr);
    return V3PBC_ReadSpaceDWord(hV3, V3PBC_ADDR_BASE0, 
        hV3->addrDesc[V3PBC_ADDR_BASE0].dwMask & dwLocalAddr);
}

void V3PBC_WriteWord (V3PBCHANDLE hV3, DWORD dwLocalAddr, WORD wData)
{
    V3PBC_SetRemapBase0(hV3, dwLocalAddr);
    V3PBC_WriteSpaceWord (hV3, V3PBC_ADDR_BASE0, 
        hV3->addrDesc[V3PBC_ADDR_BASE0].dwMask & dwLocalAddr, wData);
}

WORD V3PBC_ReadWord (V3PBCHANDLE hV3, DWORD dwLocalAddr)
{
    V3PBC_SetRemapBase0(hV3, dwLocalAddr);
    return V3PBC_ReadSpaceWord(hV3, V3PBC_ADDR_BASE0, 
        hV3->addrDesc[V3PBC_ADDR_BASE0].dwMask & dwLocalAddr);
}

void V3PBC_WriteByte (V3PBCHANDLE hV3, DWORD dwLocalAddr, BYTE bData)
{
    V3PBC_SetRemapBase0(hV3, dwLocalAddr);
    V3PBC_WriteSpaceByte (hV3, V3PBC_ADDR_BASE0, 
        hV3->addrDesc[V3PBC_ADDR_BASE0].dwMask & dwLocalAddr, bData);
}

BYTE V3PBC_ReadByte (V3PBCHANDLE hV3, DWORD dwLocalAddr)
{
    V3PBC_SetRemapBase0(hV3, dwLocalAddr);
    return V3PBC_ReadSpaceByte(hV3, V3PBC_ADDR_BASE0, 
        hV3->addrDesc[V3PBC_ADDR_BASE0].dwMask & dwLocalAddr);
}

void V3PBC_ReadBlock (V3PBCHANDLE hV3, DWORD dwLocalAddr, PVOID buf, DWORD dwBytes)
{
    V3PBC_SetRemapBase0(hV3, dwLocalAddr);
    V3PBC_ReadSpaceBlock(hV3, hV3->addrDesc[V3PBC_ADDR_BASE0].dwMask & dwLocalAddr, 
        buf, dwBytes, V3PBC_ADDR_BASE0);
}

void V3PBC_WriteBlock (V3PBCHANDLE hV3, DWORD dwLocalAddr, PVOID buf, DWORD dwBytes)
{
    V3PBC_SetRemapBase0(hV3, dwLocalAddr);
    V3PBC_WriteSpaceBlock(hV3, hV3->addrDesc[V3PBC_ADDR_BASE0].dwMask & dwLocalAddr, 
        buf, dwBytes, V3PBC_ADDR_BASE0);
}

//////////////////////////////////////////////////////////////////////////////
// Access Address space (low-level functions)
//////////////////////////////////////////////////////////////////////////////

// Note: addrSpace is a base address register
BYTE V3PBC_ReadSpaceByte (V3PBCHANDLE hV3, V3PBC_ADDR addrSpace, DWORD dwOffset)
{
    if (hV3->addrDesc[addrSpace].fIsMemory)
    {
        DWORD dwAddr = hV3->addrDesc[addrSpace].dwAddrDirect + 
            (hV3->addrDesc[addrSpace].dwMask & dwOffset);
        BYTE *pByte = (BYTE *) dwAddr;
        return *pByte;
    }
    else
    {
        DWORD dwAddr = hV3->addrDesc[addrSpace].dwAddr + 
            (hV3->addrDesc[addrSpace].dwMask & dwOffset);
        WD_TRANSFER trans;
        BZERO(trans);
        trans.cmdTrans = RP_BYTE;
        trans.dwPort = dwAddr;
        WD_Transfer (hV3->hWD, &trans);
        return trans.Data.Byte;
    }
}

void V3PBC_WriteSpaceByte (V3PBCHANDLE hV3, V3PBC_ADDR addrSpace, DWORD dwOffset, BYTE data)
{
    if (hV3->addrDesc[addrSpace].fIsMemory)
    {
        DWORD dwAddr = hV3->addrDesc[addrSpace].dwAddrDirect + 
            (hV3->addrDesc[addrSpace].dwMask & dwOffset);
        BYTE *pByte = (BYTE *) dwAddr;
        *pByte = data;
    }
    else
    {
        DWORD dwAddr = hV3->addrDesc[addrSpace].dwAddr + 
            (hV3->addrDesc[addrSpace].dwMask & dwOffset);
        WD_TRANSFER trans;
        BZERO(trans);
        trans.cmdTrans = WP_BYTE;
        trans.dwPort = dwAddr;
        trans.Data.Byte = data;
        WD_Transfer (hV3->hWD, &trans);
    }
}

WORD V3PBC_ReadSpaceWord (V3PBCHANDLE hV3, V3PBC_ADDR addrSpace, DWORD dwOffset)
{
    if (hV3->addrDesc[addrSpace].fIsMemory)
    {
        DWORD dwAddr = hV3->addrDesc[addrSpace].dwAddrDirect + 
            (hV3->addrDesc[addrSpace].dwMask & dwOffset);
        WORD *pWord = (WORD *) dwAddr;
        return *pWord;
    }
    else
    {
        DWORD dwAddr = hV3->addrDesc[addrSpace].dwAddr + 
            (hV3->addrDesc[addrSpace].dwMask & dwOffset);
        WD_TRANSFER trans;
        BZERO(trans);
        trans.cmdTrans = RP_WORD;
        trans.dwPort = dwAddr;
        WD_Transfer (hV3->hWD, &trans);
        return trans.Data.Word;
    }
}

void V3PBC_WriteSpaceWord (V3PBCHANDLE hV3, V3PBC_ADDR addrSpace, DWORD dwOffset, WORD data)
{
    if (hV3->addrDesc[addrSpace].fIsMemory)
    {
        DWORD dwAddr = hV3->addrDesc[addrSpace].dwAddrDirect + 
            (hV3->addrDesc[addrSpace].dwMask & dwOffset);
        WORD *pWord = (WORD *) dwAddr;
        *pWord = data;
    }
    else
    {
        DWORD dwAddr = hV3->addrDesc[addrSpace].dwAddr + 
            (hV3->addrDesc[addrSpace].dwMask & dwOffset);
        WD_TRANSFER trans;
        BZERO(trans);
        trans.cmdTrans = WP_WORD;
        trans.dwPort = dwAddr;
        trans.Data.Word = data;
        WD_Transfer (hV3->hWD, &trans);
    }
}

DWORD V3PBC_ReadSpaceDWord (V3PBCHANDLE hV3, V3PBC_ADDR addrSpace, DWORD dwOffset)
{
    if (hV3->addrDesc[addrSpace].fIsMemory)
    {
        DWORD dwAddr = hV3->addrDesc[addrSpace].dwAddrDirect + 
            (hV3->addrDesc[addrSpace].dwMask & dwOffset);
        DWORD *pDword = (DWORD *) dwAddr;
        return *pDword;
    }
    else
    {
        DWORD dwAddr = hV3->addrDesc[addrSpace].dwAddr + 
            (hV3->addrDesc[addrSpace].dwMask & dwOffset);
        WD_TRANSFER trans;
        BZERO(trans);
        trans.cmdTrans = RP_DWORD;
        trans.dwPort = dwAddr;
        WD_Transfer (hV3->hWD, &trans);
        return trans.Data.Dword;
    }
}

void V3PBC_WriteSpaceDWord (V3PBCHANDLE hV3, V3PBC_ADDR addrSpace, DWORD dwOffset, DWORD data)
{
    if (hV3->addrDesc[addrSpace].fIsMemory)
    {
        DWORD dwAddr = hV3->addrDesc[addrSpace].dwAddrDirect + 
            (hV3->addrDesc[addrSpace].dwMask & dwOffset);
        DWORD *pDword = (DWORD *) dwAddr;
        *pDword = data;
    }
    else
    {
        DWORD dwAddr = hV3->addrDesc[addrSpace].dwAddr + 
            (hV3->addrDesc[addrSpace].dwMask & dwOffset);
        WD_TRANSFER trans;
        BZERO(trans);
        trans.cmdTrans = WP_DWORD;
        trans.dwPort = dwAddr;
        trans.Data.Dword = data;
        WD_Transfer (hV3->hWD, &trans);
    }
}

void V3PBC_ReadWriteSpaceBlock (V3PBCHANDLE hV3, DWORD dwOffset, PVOID buf, 
                    DWORD dwBytes, BOOL fIsRead, V3PBC_ADDR addrSpace, V3PBC_MODE mode)
{
    WD_TRANSFER trans;
    DWORD dwAddr = hV3->addrDesc[addrSpace].dwAddr +
        (hV3->addrDesc[addrSpace].dwMask & dwOffset);

    BZERO(trans);
    
    if (hV3->addrDesc[addrSpace].fIsMemory) 
    {
        if (fIsRead) 
        {
            if (mode==V3PBC_MODE_BYTE) trans.cmdTrans = RM_SBYTE;
            else if (mode==V3PBC_MODE_WORD) trans.cmdTrans = RM_SWORD;
            else trans.cmdTrans = RM_SDWORD;
        }
        else 
        {
            if (mode==V3PBC_MODE_BYTE) trans.cmdTrans = WM_SBYTE;
            else if (mode==V3PBC_MODE_WORD) trans.cmdTrans = WM_SWORD;
            else trans.cmdTrans = WM_SDWORD;
        }
    }
    else 
    {
        if (fIsRead) 
        {
            if (mode==V3PBC_MODE_BYTE) trans.cmdTrans = RP_SBYTE;
            else if (mode==V3PBC_MODE_WORD) trans.cmdTrans = RP_SWORD;
            else trans.cmdTrans = RP_SDWORD;
        }
        else 
        {
            if (mode==V3PBC_MODE_BYTE) trans.cmdTrans = WP_SBYTE;
            else if (mode==V3PBC_MODE_WORD) trans.cmdTrans = WP_SWORD;
            else trans.cmdTrans = WP_SDWORD;
        }
    }
    trans.dwPort = dwAddr;
    trans.fAutoinc = TRUE;
    trans.dwBytes = dwBytes;
    trans.dwOptions = 0;
    trans.Data.pBuffer = buf;
    WD_Transfer (hV3->hWD, &trans);
}

void V3PBC_ReadSpaceBlock (V3PBCHANDLE hV3, DWORD dwOffset, PVOID buf, 
                    DWORD dwBytes, V3PBC_ADDR addrSpace)
{
    V3PBC_ReadWriteSpaceBlock (hV3, dwOffset, buf, dwBytes, TRUE, addrSpace, V3PBC_MODE_DWORD);
}

void V3PBC_WriteSpaceBlock (V3PBCHANDLE hV3, DWORD dwOffset, PVOID buf, 
                     DWORD dwBytes, V3PBC_ADDR addrSpace)
{
    V3PBC_ReadWriteSpaceBlock (hV3, dwOffset, buf, dwBytes, FALSE, addrSpace, V3PBC_MODE_DWORD);
}


//////////////////////////////////////////////////////////////////////////////
// Interrupts
//////////////////////////////////////////////////////////////////////////////

BOOL V3PBC_IntIsEnabled (V3PBCHANDLE hV3)
{
    if (!hV3->fUseInt) return FALSE;
    if (!hV3->Int.hThread) return FALSE;
    return TRUE;
}

VOID V3PBC_IntHandler (PVOID pData)
{
    V3PBCHANDLE hV3 = (V3PBCHANDLE) pData;
    V3PBC_INT_RESULT intResult;

    intResult.dwCounter = hV3->Int.Int.dwCounter;
    intResult.dwLost = hV3->Int.Int.dwLost;
    intResult.fStopped = hV3->Int.Int.fStopped;
    intResult.dwStatusReg = hV3->Int.Trans[0].Data.Dword;
    hV3->Int.funcIntHandler(hV3, &intResult);
}

BOOL V3PBC_IntEnable (V3PBCHANDLE hV3, V3PBC_INT_HANDLER funcIntHandler)
{
    DWORD dwIntStatus;
    DWORD dwAddr;

    if (!hV3->fUseInt) return FALSE;
    // check if interrupt is already enabled
    if (hV3->Int.hThread) return FALSE;

    dwIntStatus = V3PBC_ReadRegDWord (hV3, V3PBC_PCI_BPARM);

    BZERO(hV3->Int.Trans);
    // This is a samlpe of handling interrupts:
    // Two transfer commands are issued. First the value of the interrrupt control/status
    // register is read. Then, a value of ZERO is written.
    // This will cancel interrupts after the first interrupt occurs.
    // When using interrupts, this section will have to change:
    // you must put transfer commands to CANCEL the source of the interrupt, otherwise, the 
    // PC will hang when an interrupt occurs!
    dwAddr = hV3->addrDesc[V3PBC_ADDR_IO_BASE].dwAddr + V3PBC_PCI_BPARM;
    hV3->Int.Trans[0].cmdTrans = hV3->addrDesc[V3PBC_ADDR_IO_BASE].fIsMemory ? RM_DWORD : RP_DWORD;
    hV3->Int.Trans[0].dwPort = dwAddr;
    hV3->Int.Trans[1].cmdTrans = hV3->addrDesc[V3PBC_ADDR_IO_BASE].fIsMemory ? WM_DWORD : WP_DWORD;
    hV3->Int.Trans[1].dwPort = dwAddr;
    hV3->Int.Trans[1].Data.Dword = dwIntStatus & ~BIT8; // put here the data to write to the control register
    hV3->Int.Int.dwCmds = 2; 
    hV3->Int.Int.Cmd = hV3->Int.Trans;
    hV3->Int.Int.dwOptions |= INTERRUPT_CMD_COPY;

    // this calls WD_IntEnable() and creates an interrupt handler thread
    hV3->Int.funcIntHandler = funcIntHandler;
    if (!InterruptThreadEnable(&hV3->Int.hThread, hV3->hWD, &hV3->Int.Int, V3PBC_IntHandler, (PVOID) hV3))
        return FALSE;

    // this enables interrupts
    // Enable INTA to interrupt the PCI bus
    V3PBC_WriteRegDWord (hV3, V3PBC_PCI_BPARM, dwIntStatus | BIT8);

    return TRUE;
}

void V3PBC_IntDisable (V3PBCHANDLE hV3)
{
    DWORD dwIntStatus;

    if (!hV3->fUseInt) return;
    if (!hV3->Int.hThread) return;

    // this disables interrupts
    // Disable interrupt assumes INTA was enabled
    dwIntStatus = V3PBC_ReadRegDWord (hV3, V3PBC_PCI_BPARM);
    V3PBC_WriteRegDWord (hV3, V3PBC_PCI_BPARM, dwIntStatus & ~BIT8);

    // this calls WD_IntDisable()
    InterruptThreadDisable(hV3->Int.hThread);

    hV3->Int.hThread = NULL;
}


//////////////////////////////////////////////////////////////////////////////
// DMA
//////////////////////////////////////////////////////////////////////////////

BOOL V3PBC_DMAOpen(V3PBCHANDLE hV3, WD_DMA *pDMA, DWORD dwBytes)
{
    V3PBC_ErrorString[0] = '\0';
    BZERO(*pDMA);
    pDMA->pUserAddr = NULL; // the kernel will allocate the buffer
    pDMA->dwBytes = dwBytes; // size of buffer to allocate
    pDMA->dwOptions = DMA_KERNEL_BUFFER_ALLOC; 
    WD_DMALock (hV3->hWD, pDMA);
    if (!pDMA->hDma) 
    {
        sprintf (V3PBC_ErrorString, "Failed allocating the buffer!\n");
        return FALSE;
    }

    return TRUE;
}

void V3PBC_DMAClose(V3PBCHANDLE hV3, WD_DMA *pDMA)
{
    WD_DMAUnlock (hV3->hWD, pDMA);
}

BOOL V3PBC_DMAStart(V3PBCHANDLE hV3, V3_DMA_CHANNEL dmaChannel, WD_DMA *pDMA, BOOL fRead,
    BOOL fBlocking, DWORD dwBytes, DWORD dwOffset, DWORD dwLocalAddr)
{
    DWORD dwDMACSR0;
    DWORD dwChannelOffset = dmaChannel*0x10;

    V3PBC_WriteRegDWord(hV3, V3PBC_DMA_LOCAL_ADDR0 + dwChannelOffset, dwLocalAddr);
    V3PBC_WriteRegDWord(hV3, V3PBC_DMA_PCI_ADDR0 + dwChannelOffset,
        (DWORD) pDMA->Page[0].pPhysicalAddr + dwOffset);
    // BIT[0-23] count is in DWORDS divide by 4
    // BIT24 - initiate transfer (write)
    // BIT24 - DMA transfer busy (read)
    // BIT28 - transfer direction
    // Preserve chain, priority and byte swapping
    dwDMACSR0 = V3PBC_ReadRegDWord(hV3, V3PBC_DMA_LENGTH0 + dwChannelOffset) & 0xac00000;
    V3PBC_WriteRegDWord(hV3, V3PBC_DMA_LENGTH0 + dwChannelOffset, (fRead ? 0 : BIT28) | BIT24 | dwBytes >> 2 | dwDMACSR0);
    // if blocking then check BIT24 to wait for transfer to complete
    if (fBlocking)
        while (!V3PBC_DMAIsDone(hV3, dmaChannel));

    return TRUE;
}

BOOL V3PBC_DMAIsDone(V3PBCHANDLE hV3, V3_DMA_CHANNEL dmaChannel)
{
    if (V3PBC_ReadRegDWord(hV3, V3PBC_DMA_LENGTH0 + dmaChannel*0x10) & BIT24)
        return FALSE;
    return TRUE;
}


//////////////////////////////////////////////////////////////////////////////
// Card Reset
//////////////////////////////////////////////////////////////////////////////

void V3PBC_PulseLocalReset(V3PBCHANDLE hV3, WORD wDelay)
{
    WORD System;

    // Unlock the system register
    V3PBC_WriteRegWord(hV3, V3PBC_SYSTEM, SYSTEM_UNLOCK_TOKEN);

    // Assert the local reset line
    System = V3PBC_ReadRegWord(hV3, V3PBC_SYSTEM);
    V3PBC_WriteRegWord((V3PBCHANDLE) hV3, V3PBC_SYSTEM, (WORD) (System & ~SYSTEM_RST_OUT));

    // Lock the system register
    V3PBC_WriteRegWord(hV3, V3PBC_SYSTEM, (WORD) (System | SYSTEM_LOCK));

    // Delay 
    V3PBC_SleepMicro(hV3, ((DWORD) wDelay) * 1000);

    // Unlock the system register
    V3PBC_WriteRegWord(hV3, V3PBC_SYSTEM,  SYSTEM_UNLOCK_TOKEN);

    // De-assert the local reset line
    System = V3PBC_ReadRegWord(hV3, (WORD) V3PBC_SYSTEM);
    V3PBC_WriteRegWord(hV3, V3PBC_SYSTEM, (WORD) (System | SYSTEM_RST_OUT));

    // Lock the system register
    V3PBC_WriteRegWord(hV3, V3PBC_SYSTEM, (WORD) (System | SYSTEM_LOCK));
}

//////////////////////////////////////////////////////////////////////////////
// Sleep (delay)
//////////////////////////////////////////////////////////////////////////////

void V3PBC_SleepMicro(V3PBCHANDLE hV3, DWORD nMicro)
{
    WD_SLEEP sleep;
    BZERO (sleep);
    sleep.dwMicroSeconds = nMicro;
    WD_Sleep( hV3->hWD, &sleep);
}


//////////////////////////////////////////////////////////////////////////////
// EEPROM read/write
//////////////////////////////////////////////////////////////////////////////

// Note: V3PBC_EEPROMInit should be called for each board.
// This routine will force a stop condition on the I2C bus.
// The clock and data lines will be left high and this routine will 
// return after a half clock.
void V3PBC_EEPROMInit(V3PBCHANDLE hV3)
{
    // Unlock the system register
    I2C_UnLock(hV3);
    
    // Ensure that the I2C clock and data lines are in a known state 
    I2C_Stop(hV3);
    I2C_Delay(hV3);

    // Lock the system register 
    I2C_Lock(hV3);
}

// Parameters:
// BYTE bSlaveAddr - 3 bit address of device been selected
// BYTE bAddr - address of EEPROM location to be read from
// BYTE bData - value to be written to EEPROM 
//
// Return:
// BOOL fStatus - TRUE if read successful
//
// Description:
// This routine writes a byte to the slave device's address specified.
// This routine uses the Write Byte method from the Atmel documentation.
BOOL V3PBC_EEPROMWrite(V3PBCHANDLE hV3, BYTE bSlaveAddr, BYTE bAddr, BYTE bData)
{
    BOOL fStatus = TRUE;

    // Set upper device address 
    bSlaveAddr |= I2C_1010;

    // Unlock the system register
    I2C_UnLock(hV3);

    // Send device Address and wait for Ack, bAddr is sent as a write 
    if (!I2C_PollAckStart(hV3, bSlaveAddr, I2C_WRITE))
        fStatus = FALSE;
    else
    {
        // Send bAddr as the address to write 
        if (!I2C_Write8(hV3, bAddr))
            fStatus = FALSE;
        else
        {
            // Send the value to write 
            if (!I2C_Write8(hV3, bData))
                fStatus = FALSE;
        }
    }

    // Clean up the bus with a stop condition 
    I2C_Stop(hV3);

    // Lock the system register 
    I2C_Lock(hV3);
    return fStatus;
}

// Parameters:
// BYTE bSlaveAddr - 3 bit address of device been selected
// BYTE bAddr - address of EEPROM location to be read from
// BYTE *bData - EEPROM data read back (pointer to data passed)
//
// Return:
// BOOL fStatus - TRUE if read successful
//
// Description:
// This routine reads back a byte from the slave device's address
// specificed.  This routine uses the Random Read method in the Atmel
// documentation.
BOOL V3PBC_EEPROMRead(V3PBCHANDLE hV3, BYTE bSlaveAddr, BYTE bAddr, BYTE *bData)
{
    BOOL fStatus = TRUE;

    // Set upper device address 
    bSlaveAddr |= I2C_1010;

    // Unlock the system register 
    I2C_UnLock(hV3);

    // Send device Address and wait for Ack, bAddr is sent as a write 
    if (!I2C_PollAckStart(hV3, bSlaveAddr, I2C_WRITE))
        fStatus = FALSE;
    else
    {
        // Send bAddr as the random address to be read 
        if (!I2C_Write8(hV3, bAddr))
            fStatus = FALSE;
        else
        {
            // Send a START condition and device address this time as read 
            if (!I2C_StartSlave(hV3, bSlaveAddr, I2C_READ))
                fStatus = FALSE;
            else
                // read the date back from EEPROM 
                *bData = I2C_Read8(hV3);
        }
    }

    // clean up bus with a NaK and Stop condition 
    I2C_NoAck(hV3);
    I2C_Stop(hV3);

    // Lock the system register 
    I2C_Lock(hV3);
    return fStatus;
}

// Parameters:
// BYTE bAddr - 3 bit address of device been selected
// bit ReadBit - Set if command is to be a read, reset if command is a write 
//
// Return:
// TRUE    - read was successful
// FALSE   - if negative acknowledge was received
//
// Description:
// All commands are preceded by the start condition, which is a high
// to low transition of SDA when SCL is high.  All I2C devices
// continuously monitor the SDA and SCL lines for the start condition 
// and will not respond to any command until this condition has been met.
//
// Once a device detects that it is being addressed it outputs an 
// acknowledge on the SDA line.  Depending on the state of the read/write 
// bit, the device will execute a read or write operation.
//
// +-----+-----+-----+-----+-----+-----+-----+-----+
// | SA6 | SA5 | SA4 | SA3 | SA2 | SA1 | SA0 | R/W |
// +-----+-----+-----+-----+-----+-----+-----+-----+
//                                              |--- 1=read, 0=write
BOOL I2C_StartSlave(V3PBCHANDLE hV3, BYTE bAddr, BYTE ReadBit)
{
    I2C_SCL(hV3, TRUE);
    I2C_Delay(hV3);
    I2C_SDA(hV3, FALSE);
    I2C_Delay(hV3);
    I2C_SCL(hV3, FALSE);
    I2C_Delay(hV3);
    return I2C_Write8(hV3, (BYTE) (bAddr<<1 | ReadBit));
}

// Parameters:
// V3PBCHANDLE hV3 - Handle to PBC32 data structure.
// BYTE bAddr - 3 bit address of device been selected
// bit ReadBit - Set if command is to be a read, reset if command is a write 
//
// Return:
// FALSE    - device is not responding (retried out)
// TRUE     - read was successful
//
// Description:
// This routine checks the status of the device being written to by 
// issuing a start condition followed by check for ACK.  If the slave
// device is unable to communicate, the write command will not be
// acknowledged and we should wait.  This routine will try I2C_RETRY times for 
// an ACK before returning a I2C_BUSY indication.
BOOL I2C_PollAckStart(V3PBCHANDLE hV3, BYTE bAddr, BYTE ReadBit)
{
    BYTE bTries = I2C_RETRY;
    do
    {
        I2C_SDA(hV3, FALSE);
        I2C_Delay(hV3);
        I2C_SCL(hV3, FALSE);
        I2C_Delay(hV3);
        if (I2C_Write8(hV3, (BYTE) (bAddr<<1 | ReadBit)))
            return TRUE;
    }
    while(bTries--);
    return FALSE;
}

// Reads eight bits from the I2C bus.
// Return: BYTE bData - returns a byte of data 
BYTE I2C_Read8(V3PBCHANDLE hV3)
{
    BYTE i, bData=0;

    I2C_SDA(hV3, TRUE);
    for (i=8; i; --i )
    {
        I2C_SCL(hV3, TRUE);
        I2C_Delay(hV3);
        bData = (bData<<1) | I2C_SDAIn(hV3);
        I2C_SCL(hV3, FALSE);
        I2C_Delay(hV3);
    }
    return bData;
}

// Return:
// TRUE  - OK
// FALSE - BUSY or negative acknowledge
//
// Description:
// This routine writes out all 8 bits of data out to the I2C bus.  After 
// writing out the data, the routine reads the ACK/NACK response back and
// returns it to the caller.
BOOL I2C_Write8(V3PBCHANDLE hV3,BYTE bData)
{
    BYTE bAck;
    BYTE i;
    for (i=0x80; i; i >>=1 )
    {
        I2C_SDA(hV3, (i & bData) ? TRUE : FALSE);
        I2C_SCL(hV3, TRUE);
        I2C_Delay(hV3);
        I2C_SCL(hV3, FALSE);
        I2C_Delay(hV3);
    }
    I2C_SDA(hV3, TRUE);
    I2C_SCL(hV3, TRUE);
    bAck= I2C_SDAIn(hV3);
    I2C_Delay(hV3);
    I2C_SCL(hV3, FALSE);
    I2C_Delay(hV3);

    return bAck==0;
}

// All communications must be terminated by a stop condition which
// is a low to high transition of SDA while SCL is high.  A stop 
// condition can only be issued after the transmitting device has 
// released the bus.
void I2C_Stop(V3PBCHANDLE hV3)
{
    I2C_SDA(hV3, FALSE);
    I2C_Delay(hV3);
    I2C_SCL(hV3, TRUE);
    I2C_Delay(hV3);
    I2C_SDA(hV3, TRUE);
}

// The No-Acknowledge is a software convention used to indicate
// unsucessful data transfers.  The transmitting device, either 
// master or slave, will release the bus after transmitting 
// eight bits.  During the ninth clock cycle the receiver will 
// pull the SDA line high to indicate that it did not received the 
// eight bits of data.
void I2C_NoAck(V3PBCHANDLE hV3)
{
    I2C_SDA(hV3, TRUE);
    I2C_Delay(hV3);
    I2C_SCL(hV3, TRUE);
    I2C_Delay(hV3);
    I2C_SCL(hV3, FALSE);
}

// Acknowledge is a software convention used to indicate
// sucessful data transfers.  The transmitting device, either 
// master or slave, will release the bus after transmitting 
// eight bits.  During the ninth clock cycle the receiver will 
// pull the SDA line low to acknowledge that it received the 
// eight bits of data.
void I2C_Ack(V3PBCHANDLE hV3)
{
    I2C_SDA(hV3, FALSE);
    I2C_Delay(hV3);
    I2C_SCL(hV3, TRUE);
    I2C_Delay(hV3);
    I2C_SCL(hV3, FALSE);
}

// This routine returns the state of the Serial Data line
// Return: BYTE bData - state of the Serial Data line
BYTE I2C_SDAIn(V3PBCHANDLE hV3)
{
    WORD wSystem;

    wSystem = V3PBC_ReadRegWord(hV3, V3PBC_SYSTEM);
    return ((wSystem & SYSTEM_SDA_IN) >> SYSTEM_SDA_IN_SHIFT);
}

// This routine sets the Serial Data line to the desired state.
//
// Parameters: 
// BOOL fHigh - what state the SDA is to be set to
void I2C_SDA(V3PBCHANDLE hV3, BOOL fHigh)
{
    WORD wSystem;

    wSystem = V3PBC_ReadRegWord(hV3, V3PBC_SYSTEM);
    if(fHigh)
        wSystem |= SYSTEM_SDA_OUT;
    else
        wSystem &= ~SYSTEM_SDA_OUT;
    V3PBC_WriteRegWord(hV3, V3PBC_SYSTEM, wSystem);
}

// This routine sets the Serial Clock line to the desired state.
//
// Parameters: 
// BOOL fHigh - what state the SCL is to be set to
void I2C_SCL(V3PBCHANDLE hV3, BOOL fHigh)
{
    WORD wSystem;

    wSystem = V3PBC_ReadRegWord(hV3, V3PBC_SYSTEM);
    if(fHigh)
        wSystem |= SYSTEM_SCL;
    else
        wSystem &= ~SYSTEM_SCL;
    V3PBC_WriteRegWord(hV3, V3PBC_SYSTEM, wSystem);
}

// This routine disables the Serial Clock pin, and sets the sytem
// lock bit.
void I2C_Lock(V3PBCHANDLE hV3)
{
    WORD wSystem;

    wSystem = V3PBC_ReadRegWord(hV3, V3PBC_SYSTEM);
    V3PBC_WriteRegWord(hV3, V3PBC_SYSTEM, (WORD) (wSystem & ~SYSTEM_SPROM_EN));
    V3PBC_WriteRegWord(hV3, V3PBC_SYSTEM, (WORD) (wSystem | SYSTEM_LOCK));
}

// This routine enable the sytem register for writting, and enables
// the Serial Clock output pin.
void I2C_UnLock(V3PBCHANDLE hV3)
{
    WORD wSystem;

    V3PBC_WriteRegWord(hV3, V3PBC_SYSTEM, SYSTEM_UNLOCK_TOKEN);
    wSystem = V3PBC_ReadRegWord(hV3, V3PBC_SYSTEM);
    V3PBC_WriteRegWord(hV3, V3PBC_SYSTEM, (WORD) (wSystem |SYSTEM_SPROM_EN));
}

// This routine inserts delay.  0.005 Milliseconds is enough time
void I2C_Delay(V3PBCHANDLE hV3)
{
    V3PBC_SleepMicro(hV3, 5);
}