//////////////////////////////////////////////////////////////////////
// File - altera_lib.c
//
// Library for accessing the ALTERA card.
// Code was generated by DriverWizard.
// It accesses the hardware via WinDriver functions.
// 
//////////////////////////////////////////////////////////////////////

#include "altera_lib.h"
#include "../../../include/windrvr_int_thread.h"
#include <stdio.h>

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

// internal data structures
typedef struct
{
    WD_INTERRUPT Int;
    HANDLE hThread;
    WD_TRANSFER Trans[1];
    ALTERA_INT_HANDLER funcIntHandler;
} ALTERA_INT_INTERRUPT;

typedef struct
{
    DWORD index;
    DWORD dwMask;
    BOOL  fIsMemory;
    BOOL  fActive;
} ALTERA_ADDR_DESC;

typedef struct ALTERA_STRUCT
{
    HANDLE hWD;
    BOOL   fUseInt;
    ALTERA_INT_INTERRUPT Int;
    WD_PCI_SLOT pciSlot;
    ALTERA_ADDR_DESC addrDesc[ALTERA_ITEMS];
    WD_CARD_REGISTER cardReg;
} ALTERA_STRUCT;

// internal function used by ALTERA_Open()
BOOL ALTERA_DetectCardElements(ALTERA_HANDLE hALTERA);

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

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

    BZERO(ver);
    WD_Version(hWD,&ver);
    if (ver.dwVer<WD_VER) 
    {
        sprintf( ALTERA_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( ALTERA_ErrorString, "no cards found\n");
    return pciScan.dwCards;
}

BOOL ALTERA_Open (ALTERA_HANDLE *phALTERA, DWORD dwVendorID, DWORD dwDeviceID, DWORD nCardNum, DWORD options)
{
    ALTERA_HANDLE hALTERA = (ALTERA_HANDLE) malloc (sizeof (ALTERA_STRUCT));

    WD_VERSION ver;
    WD_PCI_SCAN_CARDS pciScan;
    WD_PCI_CARD_INFO pciCardInfo;

    *phALTERA = NULL;
    ALTERA_ErrorString[0] = '\0';
   BZERO(*hALTERA);

    hALTERA->hWD = WD_Open();

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

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

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

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

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

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

    if (!ALTERA_DetectCardElements(hALTERA))
    {
        sprintf ( ALTERA_ErrorString, "Card does not have all items expected for ALTERA\n");
        goto Exit;
    }

    // Open finished OK
    *phALTERA = hALTERA;
    return TRUE;

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

void ALTERA_Close(ALTERA_HANDLE hALTERA)
{
    // disable interrupts
    if (ALTERA_IntIsEnabled(hALTERA))
        ALTERA_IntDisable(hALTERA);

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

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

    free (hALTERA);
}

void ALTERA_WritePCIReg(ALTERA_HANDLE hALTERA, DWORD dwReg, DWORD dwData)
{
    WD_PCI_CONFIG_DUMP pciCnf;

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

DWORD ALTERA_ReadPCIReg(ALTERA_HANDLE hALTERA, DWORD dwReg)
{
    WD_PCI_CONFIG_DUMP pciCnf;
    DWORD dwVal;

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

void ALTERA_WritePCIRegN(ALTERA_HANDLE hALTERA, DWORD dwReg, DWORD dwData, DWORD dwBytes)
{
    WD_PCI_CONFIG_DUMP pciCnf;

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

DWORD ALTERA_ReadPCIRegN(ALTERA_HANDLE hALTERA, DWORD dwReg, DWORD dwBytes)
{
    WD_PCI_CONFIG_DUMP pciCnf;
    DWORD dwVal = 0;

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

BOOL ALTERA_DetectCardElements(ALTERA_HANDLE hALTERA)
{
    DWORD i;
    DWORD ad_sp;

    BZERO(hALTERA->Int);
    BZERO(hALTERA->addrDesc);

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

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

                for (ad_sp=0; ad_sp<ALTERA_ITEMS; ad_sp++)
                {
                    DWORD dwPCIAddr;
                    DWORD dwPCIReg;

                    if (ALTERA_IsAddrSpaceActive(hALTERA, (ALTERA_ADDR) ad_sp)) continue;
                    if (ad_sp<ALTERA_AD_EPROM) dwPCIReg = PCI_BAR0 + 4*ad_sp;
                    else dwPCIReg = PCI_ERBAR;
                    dwPCIAddr = ALTERA_ReadPCIReg(hALTERA, dwPCIReg);
                    if (dwPCIAddr & 1)
                    {
                        if (fIsMemory) continue;
                        dwPCIAddr &= ~0x3;
                    }
                    else
                    {
                        if (!fIsMemory) continue;
                        dwPCIAddr &= ~0xf;
                    }
                    if (dwPCIAddr==dwPhysAddr)
                        break;
                }
                if (ad_sp<ALTERA_ITEMS)
                {
                    DWORD j;
                    hALTERA->addrDesc[ad_sp].fActive = TRUE;
                    hALTERA->addrDesc[ad_sp].index = i;
                    hALTERA->addrDesc[ad_sp].fIsMemory = fIsMemory;
                    hALTERA->addrDesc[ad_sp].dwMask = 0;
                    for (j=1; j<dwBytes && j!=0x80000000; j *= 2)
                    {
                        hALTERA->addrDesc[ad_sp].dwMask = 
                            (hALTERA->addrDesc[ad_sp].dwMask << 1) | 1;
                    }
                }
            }
            break;
        case ITEM_INTERRUPT:
            if (hALTERA->Int.Int.hInterrupt) return FALSE;
            hALTERA->Int.Int.hInterrupt = pItem->I.Int.hInterrupt;
            break;
        }
    }

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

    // check that at least one memory space was found
    for (i = 0; i<ALTERA_ITEMS; i++)
        if (ALTERA_IsAddrSpaceActive(hALTERA, (ALTERA_ADDR) i)) break;
    if (i==ALTERA_ITEMS) return FALSE;

    return TRUE;
}

BOOL ALTERA_IsAddrSpaceActive(ALTERA_HANDLE hALTERA, ALTERA_ADDR addrSpace)
{
    return hALTERA->addrDesc[addrSpace].fActive;
}

void ALTERA_GetPciSlot(ALTERA_HANDLE hALTERA, WD_PCI_SLOT *pPciSlot)
{
    memcpy((PVOID)pPciSlot, &(hALTERA->pciSlot), sizeof(WD_PCI_SLOT));
}

// General read/write function
void ALTERA_ReadWriteBlock(ALTERA_HANDLE hALTERA, ALTERA_ADDR addrSpace, DWORD dwOffset, BOOL fRead, PVOID buf, DWORD dwBytes, ALTERA_MODE mode)
{
    WD_TRANSFER trans;
    BOOL fMem = hALTERA->addrDesc[addrSpace].fIsMemory;
    // safty check: is the address range active
    if (!ALTERA_IsAddrSpaceActive(hALTERA, addrSpace)) return;
    BZERO(trans);
    if (fRead)
    {
        if (mode==ALTERA_MODE_BYTE) trans.cmdTrans = fMem ? RM_SBYTE : RP_SBYTE;
        else if (mode==ALTERA_MODE_WORD) trans.cmdTrans = fMem ? RM_SWORD : RP_SWORD;
        else if (mode==ALTERA_MODE_DWORD) trans.cmdTrans = fMem ? RM_SDWORD : RP_SDWORD;
    }
    else
    {
        if (mode==ALTERA_MODE_BYTE) trans.cmdTrans = fMem ? WM_SBYTE : WP_SBYTE;
        else if (mode==ALTERA_MODE_WORD) trans.cmdTrans = fMem ? WM_SWORD : WP_SWORD;
        else if (mode==ALTERA_MODE_DWORD) trans.cmdTrans = fMem ? WM_SDWORD : WP_SDWORD;
    }
    if (fMem)
        trans.dwPort = hALTERA->cardReg.Card.Item[hALTERA->addrDesc[addrSpace].index].I.Mem.dwTransAddr;
    else trans.dwPort = hALTERA->cardReg.Card.Item[hALTERA->addrDesc[addrSpace].index].I.IO.dwAddr;
    trans.dwPort += dwOffset;

    trans.fAutoinc = TRUE;
    trans.dwBytes = dwBytes;
    trans.dwOptions = 0;
    trans.Data.pBuffer = buf;
    WD_Transfer (hALTERA->hWD, &trans);
}

BYTE ALTERA_ReadByte (ALTERA_HANDLE hALTERA, ALTERA_ADDR addrSpace, DWORD dwOffset)
{
    BYTE data;
    if (hALTERA->addrDesc[addrSpace].fIsMemory)
    {
        PBYTE pData = (PBYTE) (hALTERA->cardReg.Card.Item[hALTERA->addrDesc[addrSpace].index].I.Mem.dwUserDirectAddr + dwOffset);
        data = *pData; // read from the memory mapped range directly
    }
    else ALTERA_ReadWriteBlock( hALTERA, addrSpace, dwOffset, TRUE, &data, sizeof (BYTE), ALTERA_MODE_BYTE);
    return data;
}

WORD ALTERA_ReadWord (ALTERA_HANDLE hALTERA, ALTERA_ADDR addrSpace, DWORD dwOffset)
{
    WORD data;
    if (hALTERA->addrDesc[addrSpace].fIsMemory)
    {
        PWORD pData = (PWORD) (hALTERA->cardReg.Card.Item[hALTERA->addrDesc[addrSpace].index].I.Mem.dwUserDirectAddr + dwOffset);
        data = *pData; // read from the memory mapped range directly
    }
    else ALTERA_ReadWriteBlock( hALTERA, addrSpace, dwOffset, TRUE, &data, sizeof (WORD), ALTERA_MODE_WORD);
    return data;
}

DWORD ALTERA_ReadDword (ALTERA_HANDLE hALTERA, ALTERA_ADDR addrSpace, DWORD dwOffset)
{
    DWORD data;
    if (hALTERA->addrDesc[addrSpace].fIsMemory)
    {
        PDWORD pData = (PDWORD) (hALTERA->cardReg.Card.Item[hALTERA->addrDesc[addrSpace].index].I.Mem.dwUserDirectAddr + dwOffset);
        data = *pData; // read from the memory mapped range directly
    }
    else ALTERA_ReadWriteBlock( hALTERA, addrSpace, dwOffset, TRUE, &data, sizeof (DWORD), ALTERA_MODE_DWORD);
    return data;
}

void ALTERA_WriteByte (ALTERA_HANDLE hALTERA, ALTERA_ADDR addrSpace, DWORD dwOffset, BYTE data)
{
    if (hALTERA->addrDesc[addrSpace].fIsMemory)
    {
        PBYTE pData = (PBYTE) (hALTERA->cardReg.Card.Item[hALTERA->addrDesc[addrSpace].index].I.Mem.dwUserDirectAddr + dwOffset);
        *pData = data; // write to the memory mapped range directly
    }
    else ALTERA_ReadWriteBlock( hALTERA, addrSpace, dwOffset, FALSE, &data, sizeof (BYTE), ALTERA_MODE_BYTE);
}

void ALTERA_WriteWord (ALTERA_HANDLE hALTERA, ALTERA_ADDR addrSpace, DWORD dwOffset, WORD data)
{
    if (hALTERA->addrDesc[addrSpace].fIsMemory)
    {
        PWORD pData = (PWORD) (hALTERA->cardReg.Card.Item[hALTERA->addrDesc[addrSpace].index].I.Mem.dwUserDirectAddr + dwOffset);
        *pData = data; // write to the memory mapped range directly
    }
    else ALTERA_ReadWriteBlock( hALTERA, addrSpace, dwOffset, FALSE, &data, sizeof (WORD), ALTERA_MODE_WORD);
}

void ALTERA_WriteDword (ALTERA_HANDLE hALTERA, ALTERA_ADDR addrSpace, DWORD dwOffset, DWORD data)
{
    if (hALTERA->addrDesc[addrSpace].fIsMemory)
    {
        PDWORD pData = (PDWORD) (hALTERA->cardReg.Card.Item[hALTERA->addrDesc[addrSpace].index].I.Mem.dwUserDirectAddr + dwOffset);
        *pData = data; // write to the memory mapped range directly
    }
    else ALTERA_ReadWriteBlock( hALTERA, addrSpace, dwOffset, FALSE, &data, sizeof (DWORD), ALTERA_MODE_DWORD);
}

BOOL ALTERA_IntIsEnabled (ALTERA_HANDLE hALTERA)
{
    if (!hALTERA->fUseInt) return FALSE;
    if (!hALTERA->Int.hThread) return FALSE;
    return TRUE;
}

void ALTERA_IntHandler (PVOID pData)
{
    ALTERA_HANDLE hALTERA = (ALTERA_HANDLE) pData;
    ALTERA_INT_RESULT intResult;
    intResult.dwCounter = hALTERA->Int.Int.dwCounter;
    intResult.dwLost = hALTERA->Int.Int.dwLost;
    intResult.fStopped = hALTERA->Int.Int.fStopped;
    hALTERA->Int.funcIntHandler(hALTERA, &intResult);
}

BOOL ALTERA_IntEnable (ALTERA_HANDLE hALTERA, ALTERA_INT_HANDLER funcIntHandler)
{
    ALTERA_ADDR addrSpace;

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

    BZERO(hALTERA->Int.Trans);
    // This is a sample of handling interrupts:
    // One transfer command is issued to CANCEL the source of the interrupt,
    // otherwise, the PC will hang when an interrupt occurs!
    // You will need to modify this code to fit your specific hardware.
    addrSpace = ALTERA_AD_BAR0; // put the address space of the register here
    if (hALTERA->addrDesc[addrSpace].fIsMemory)
    {
        hALTERA->Int.Trans[0].dwPort = hALTERA->cardReg.Card.Item[hALTERA->addrDesc[addrSpace].index].I.Mem.dwTransAddr;
        hALTERA->Int.Trans[0].cmdTrans = WM_DWORD;
    }
    else
    {
        hALTERA->Int.Trans[0].dwPort = hALTERA->cardReg.Card.Item[hALTERA->addrDesc[addrSpace].index].I.IO.dwAddr;
        hALTERA->Int.Trans[0].cmdTrans = WP_DWORD;
    }
    hALTERA->Int.Trans[0].dwPort += 0; // put the offset of the register from the beginning of the address space here
    hALTERA->Int.Trans[0].Data.Dword = 0x0;
    hALTERA->Int.Int.dwCmds = 1;
    hALTERA->Int.Int.Cmd = hALTERA->Int.Trans;
    hALTERA->Int.Int.dwOptions |= INTERRUPT_CMD_COPY;

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

    return TRUE;
}

void ALTERA_IntDisable (ALTERA_HANDLE hALTERA)
{
    if (!hALTERA->fUseInt) return;
    if (!hALTERA->Int.hThread) return;

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

    hALTERA->Int.hThread = NULL;
}

BOOL ALTERA_DMAWait(ALTERA_HANDLE hALTERA)
{
	BOOL fOk = FALSE;
	DWORD i = 10*1000*1000/2; // wait 10 seconds (each loop waits 2 microseconds
	for(;i;i--)
	{
		DWORD dwDMAISR = ALTERA_ReadDword (hALTERA, ALTERA_AD_BAR0, ALTERA_REG_DMAISR);
		WD_SLEEP sleep;
   		if (dwDMAISR & TX_COMP)
		{
			fOk = TRUE;
			break;
		}

		if (dwDMAISR & ERROR_PENDING) 
		{
			sprintf(ALTERA_ErrorString, "hardware dma failure\n");
			break;
		}
		BZERO(sleep);
		sleep.dwMicroSeconds = 2;
		WD_Sleep(hALTERA->hWD, &sleep);
	}
	if (!i)
		sprintf(ALTERA_ErrorString, "dma transfer timeout\n");

	return fOk;
}

BOOL ALTERA_DMAReadWriteBlock(ALTERA_HANDLE hALTERA, DWORD dwLocalAddr, PVOID pBuffer, BOOL fRead, DWORD dwBytes, BOOL fChained)
{
	BOOL fOk = FALSE;
	WD_DMA dma;
	DWORD DMACsr;
	DWORD i;

	if (dwBytes == 0)
		return fOk;
	BZERO(dma);
	dma.pUserAddr = pBuffer;
	dma.dwBytes = dwBytes;
	dma.dwOptions = 0;
	WD_DMALock(hALTERA->hWD, &dma);
	if (!dma.hDma)
	{
		sprintf(ALTERA_ErrorString, "cannot lock down buffer\n");
		return FALSE;
	}
	DMACsr = fRead ? DIRECTION : 0;
	DMACsr |= DMA_ENABLE | TRXCOMPINTDIS | INTERRUPT_ENABLE;
	if (dma.dwPages>ALTERA_DMA_FIFO_REGS)
	{
		sprintf(ALTERA_ErrorString, "too many pages for chain transfer\n");
		// not enough pages
		goto Exit;
	}
	if (fChained)
	{
		DMACsr |= CHAINEN;
		for (i=0;i<dma.dwPages;i++)
		{
			ALTERA_WriteDword (hALTERA, ALTERA_AD_BAR0, FIFO_BASE_OFFSET, (DWORD) dma.Page[i].pPhysicalAddr);
			ALTERA_WriteDword (hALTERA, ALTERA_AD_BAR0, FIFO_BASE_OFFSET, dma.Page[i].dwBytes);
		}
		ALTERA_WriteDword (hALTERA, ALTERA_AD_BAR0, ALTERA_REG_DMALAR, dwLocalAddr);
		ALTERA_WriteDword (hALTERA, ALTERA_AD_BAR0, ALTERA_REG_DMACSR, DMACsr); //this starts the dma
		fOk = ALTERA_DMAWait(hALTERA);
	}
	else
	{
		DWORD dwTotalBytes = 0;
		ALTERA_WriteDword (hALTERA, ALTERA_AD_BAR0, ALTERA_REG_DMACSR, DMACsr);
		for (i=0;i<dma.dwPages;i++)
		{
			ALTERA_WriteDword (hALTERA, ALTERA_AD_BAR0, ALTERA_REG_DMALAR, dwLocalAddr+dwTotalBytes);
			ALTERA_WriteDword (hALTERA, ALTERA_AD_BAR0, ALTERA_REG_DMABCR, dma.Page[i].dwBytes);
			ALTERA_WriteDword (hALTERA, ALTERA_AD_BAR0, ALTERA_REG_DMAACR, (DWORD) dma.Page[i].pPhysicalAddr); // this starts the dma
			fOk = ALTERA_DMAWait(hALTERA);
			if (!fOk)
				break;
			dwTotalBytes += dma.Page[i].dwBytes;
		}

	}

Exit:
	 WD_DMAUnlock(hALTERA->hWD, &dma);
	 return fOk;
}