//////////////////////////////////////////////////////////////////
// File - PCI_DMA.C
//
// This is a skeleton driver for a PCI card with Bus Master DMA.
// The driver implemets PCI detection, accessing memory mapped 
// ranges on the card, and interrupt handler installation.
// 
// It is recommended to take a look at real-world samples of 
// PCI Bus Master DMA, in /windrvr/amcc/lib/amcclib.c and 
// /windrvr/v3/lib/pbclib.c.
//
//////////////////////////////////////////////////////////////////

#include <windows.h>
#include <winioctl.h>
#include "../../include/windrvr.h"
#include <stdio.h>

// put your vendor_id & device_id here
enum {MY_VENDOR_ID = 0x1201};
enum {MY_DEVICE_ID = 0x0001};

// put the offset of the card's register for programing DMA here
'enum {MY_REG_DMA_STATUS = 0x10};
enum {MY_REG_DMA_PAGES = 0x100};

void MY_CardMainDriver()
{
    // put your device driver code here
}


//////////////////////////////////////////////////////////////////
// global variables section G_xxxx
//////////////////////////////////////////////////////////////////
enum {INVALID_RES = 0xffff};

HANDLE hWD;
WD_CARD_REGISTER G_cardReg;

// index to resource in card of memory region & interrupt
// if you have more than one memory region, interrupt or have an IO range,
// add more indexes here, and locate them in IO_DetectCardElements() function.
DWORD G_resMemory;
// base address for memory mapped region
DWORD G_baseMemory;
DWORD G_baseMemoryTrns;

void IO_Open()
{
    WD_VERSION ver;

    hWD = WD_Open();
    // Check whether handle is valid and version OK
    if (hWD==INVALID_HANDLE_VALUE) 
    {
        printf("Cannot open WinDriver device\n");
        exit(1);
    }
    BZERO(ver);
    WD_Version(hWD,&ver);
    if (ver.dwVer<WD_VER) 
    {
        printf("error incorrect WinDriver version\n");
        exit(1);
    }

    G_cardReg.hCard = 0;
}

// IO_DetectCardElements() scans the card recourses (Interrupts, IO & memory mapped regions)
// and sets IO_ResInterrupt & IO_ResMemory to point to the correct element number
void IO_DetectCardElements()
{
    DWORD i;

    G_resMemory = INVALID_RES;

    for (i=0; i<G_cardReg.Card.dwItems; i++)
    {
        switch(G_cardReg.Card.Item[i].item)
        {
        case ITEM_INTERRUPT:
            printf("error - Card has an Interrupt\n");
            exit(1);
            break;
        case ITEM_MEMORY:
            if (G_resMemory==INVALID_RES) 
                G_resMemory = i;
            else
            {
                printf("error - Card has more than one memory region\n");
                exit(1);
            }
            break;
        case ITEM_IO:
            printf("error - Card has an IO ragne\n");
            exit(1);
            break;
        }
    }

    if (G_resMemory==INVALID_RES)
    {
        printf("error - memory region not found\n");
        exit(1);
    }
}

// IO_DetectCard() scans the PCI bus & detect the card
void IO_DetectCard()
{
    WD_PCI_SCAN_CARDS pciScan;
    WD_PCI_CARD_INFO pciCardInfo;

    BZERO(pciScan);
    pciScan.searchId.dwVendorId = MY_VENDOR_ID;
    pciScan.searchId.dwDeviceId = MY_DEVICE_ID;
    WD_PciScanCards (hWD, &pciScan);
    if (pciScan.dwCards==0) // Found at least one card
    {
        printf("error - Cannot find PCI card\n");
        exit(1);
    }

    BZERO(pciCardInfo);
    pciCardInfo.pciSlot = pciScan.cardSlot[0];
    WD_PciGetCardInfo (hWD, &pciCardInfo);
        
    // the info for Card comes from WD_PciGetCardInfo() 
    // for PCI cards. for ISA cards the information has to be
    // set by the user (IO/memory address & interrupt number).
    BZERO(G_cardReg);
    G_cardReg.Card = pciCardInfo.Card;
    IO_DetectCardElements();
    G_cardReg.fCheckLockOnly = FALSE;
    WD_CardRegister (hWD, &G_cardReg);
    if (G_cardReg.hCard==0)
    {
        printf ("could not lock device - already in use\n");
        exit(1);
    }

    G_baseMemory = G_cardReg.Card.Item[G_resMemory].I.Mem.dwUserDirectAddr;
    G_baseMemoryTrns = G_cardReg.Card.Item[G_resMemory].I.Mem.dwTransAddr;
}

void IO_Close()
{
    // unregister card
    if (G_cardReg.hCard) 
        WD_CardUnregister(hWD, &G_cardReg);

    // close WinDriver
    WD_Close(hWD);
}

// performs a single 32 bit read from memory mapped range
DWORD IO_Read32BitRegister(DWORD dwAddr)
{
    PDWORD pDW = (PDWORD) (G_baseMemory + dwAddr);
    return *pDW;
}

// performs a single 32 bit write to memory mapped range
void IO_Write32BitRegister(DWORD dwAddr, DWORD dwData)
{
    PDWORD pDW = (PDWORD) (G_baseMemory + dwAddr);
    *pDW = dwData;
}

// transfer data to/from PCI card using bus-master DMA.
// pBuffer if the data to transfer (or to receive data), dwBytes - size of data
// fIsRead is TRUE for read (PCI card --> system memory), or FALSE for write ( memory --> PCI card)
// addr local address on PCI card
void IO_DMATransferBuffer(PVOID pBuffer, DWORD dwBytes, BOOL fIsRead, DWORD dwAddr)
{
    WD_DMA dma;
    DWORD i;
    DWORD dwSumBytes;

    BZERO(dma);
    dma.pUserAddr = pBuffer;
    dma.dwBytes = dwBytes;
    dma.dwOptions = 0;

    // lock region in memory & get page list
    WD_DMALock(hWD,&dma);

    // program page transfer list on PCI bus master card
    dwSumBytes = 0;
    for (i=0; i<dma.dwPages; i++)
    {
        IO_Write32BitRegister(MY_REG_DMA_PAGES + i*12 + 0, (DWORD) dma.Page[i].pPhysicalAddr);
        IO_Write32BitRegister(MY_REG_DMA_PAGES + i*12 + 4, dwAddr + dwSumBytes);
        IO_Write32BitRegister(MY_REG_DMA_PAGES + i*12 + 8, dma.Page[i].dwBytes);
        dwSumBytes += dma.Page[i].dwBytes;
    }
    
    // write the number of pages to transfer and direction of transfer
    IO_Write32BitRegister(MY_REG_DMA_STATUS, dma.dwPages | (fIsRead ? 0x1000 : 0));

    // wait for for status register to indicate transfer compleate here.
    // you can use the interrupt routine for non-busy wait (if the card enables interrupt
    // indication for end of transfer).

    WD_DMAUnlock(hWD,&dma);
}

// transfer data to/from PCI card NOT using DMA.
// pBuffer if the data to transfer (or to receive data), dwBytes - size of data
// fIsRead is TRUE for read (PCI card --> system memory), or FALSE for write ( memory --> PCI card)
// addr local address on PCI card
void IO_TransferBuffer(PVOID pBuffer, DWORD dwBytes, BOOL fIsRead, DWORD dwAddr)
{
    WD_TRANSFER trans;

    BZERO(trans);
    if (fIsRead)
        trans.cmdTrans = RM_SDWORD; // Read Memory String DWORD
    else trans.cmdTrans = WM_SDWORD; // Write Memory String DWORD
    trans.dwPort = G_baseMemoryTrns + dwAddr; 
    trans.Data.pBuffer = pBuffer;
    trans.dwBytes = dwBytes;

    // if fAutoinc==TRUE then read/write will run through an address range on the card
    // if fAutoinc==FALSE then read/write will perform on the same address of the card
    //    (usually used for FIFO where all reads/writes are to the same register)
    trans.fAutoinc = TRUE; 

    trans.dwOptions = 0;

    WD_Transfer (hWD, &trans);

    // this function could also be implemeted directly with memcpy().
    // for example, to read a memory region:
    // memcpy (pBuffer, (PVOID) (dwAddr + G_baseMemory), dwBytes);
}

int main(int argc, char *argv[])
{
    IO_Open();

    IO_DetectCard();

    // call your main card routine program
    // in your program you can use IO_DMATransferBuffer() for DMA read/write,
    // or IO_TransferBuffer() to do the same thing without DMA,
    // IO_Read32BitRegister() & IO_Write32BitRegister for single 32bit read/write
    MY_CardMainDriver(); 

    IO_Close();

    return 0;
}