www.pudn.com > PlxSdk.rar > ApiFunctions.c


/*******************************************************************************
 * Copyright (c) 2007 PLX Technology, Inc.
 *
 * PLX Technology Inc. licenses this software under specific terms and
 * conditions.  Use of any of the software or derviatives thereof in any
 * product without a PLX Technology chip is strictly prohibited.
 *
 * PLX Technology, Inc. provides this software AS IS, WITHOUT ANY WARRANTY,
 * EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTY OF
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  PLX makes no guarantee
 * or representations regarding the use of, or the results of the use of,
 * the software and documentation in terms of correctness, accuracy,
 * reliability, currentness, or otherwise; and you rely on the software,
 * documentation and results solely at your own risk.
 *
 * IN NO EVENT SHALL PLX BE LIABLE FOR ANY LOSS OF USE, LOSS OF BUSINESS,
 * LOSS OF PROFITS, INDIRECT, INCIDENTAL, SPECIAL OR CONSEQUENTIAL DAMAGES
 * OF ANY KIND.  IN NO EVENT SHALL PLX'S TOTAL LIABILITY EXCEED THE SUM
 * PAID TO PLX FOR THE PRODUCT LICENSED HEREUNDER.
 *
 ******************************************************************************/

/*******************************************************************************
 *
 * File Name:
 *
 *      ApiFunctions.c
 *
 * Description:
 *
 *      Implements the PLX API functions
 *
 * Revision History:
 *
 *      06-01-07 : PLX SDK v5.10
 *
 ******************************************************************************/


#include 
#include "ApiFunctions.h"
#include "Eep_9000.h"
#include "PciSupport.h"
#include "PlxChipApi.h"
#include "PlxChipFn.h"
#include "PlxInterrupt.h"
#include "SupportFunc.h"




/*******************************************************************************
 *
 * Function   :  PlxDeviceFind
 *
 * Description:  Search for a specific device using device key parameters
 *
 ******************************************************************************/
RETURN_CODE
PlxDeviceFind(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U8               *pDeviceNumber
    )
{
    U8             DeviceCount;
    BOOLEAN        bMatchId;
    BOOLEAN        bMatchLoc;
    DEVICE_OBJECT *fdo;


    DeviceCount = 0;

    // Get first device instance in list
    fdo = pdx->pDeviceObject->DriverObject->DeviceObject;

    // Compare with items in device list
    while (fdo != NULL)
    {
        // Get the device extension
        pdx = fdo->DeviceExtension;

        // Assume successful match
        bMatchLoc = TRUE;
        bMatchId  = TRUE;

        //
        // Compare device key information
        //

        // Compare Bus number
        if (pKey->bus != (U8)PCI_FIELD_IGNORE)
        {
            if (pKey->bus != pdx->Key.bus)
            {
                bMatchLoc = FALSE;
            }
        }

        // Compare Slot number
        if (pKey->slot != (U8)PCI_FIELD_IGNORE)
        {
            if (pKey->slot != pdx->Key.slot)
            {
                bMatchLoc = FALSE;
            }
        }

        // Compare Function number
        if (pKey->function != (U8)PCI_FIELD_IGNORE)
        {
            if (pKey->function != pdx->Key.function)
            {
                bMatchLoc = FALSE;
            }
        }

        //
        // Compare device ID information
        //

        // Compare Vendor ID
        if (pKey->VendorId != (U16)PCI_FIELD_IGNORE)
        {
            if (pKey->VendorId != pdx->Key.VendorId)
            {
                bMatchId = FALSE;
            }
        }

        // Compare Device ID
        if (pKey->DeviceId != (U16)PCI_FIELD_IGNORE)
        {
            if (pKey->DeviceId != pdx->Key.DeviceId)
            {
                bMatchId = FALSE;
            }
        }

        // Compare Subsystem Vendor ID
        if (pKey->SubVendorId != (U16)PCI_FIELD_IGNORE)
        {
            if (pKey->SubVendorId != pdx->Key.SubVendorId)
            {
                bMatchId = FALSE;
            }
        }

        // Compare Subsystem Device ID
        if (pKey->SubDeviceId != (U16)PCI_FIELD_IGNORE)
        {
            if (pKey->SubDeviceId != pdx->Key.SubDeviceId)
            {
                bMatchId = FALSE;
            }
        }

        // Compare Revision
        if (pKey->Revision != (U8)PCI_FIELD_IGNORE)
        {
            if (pKey->Revision != pdx->Key.Revision)
            {
                bMatchId = FALSE;
            }
        }

        // Check if match on location and ID
        if (bMatchLoc && bMatchId)
        {
            // Match found, check if it is the desired device
            if (DeviceCount == *pDeviceNumber)
            {
                // Copy the device information
                *pKey = pdx->Key;

                DebugPrintf((
                    "Criteria matched device %04X_%04X [b:%02x s:%02x f:%02x]\n",
                    pdx->Key.DeviceId, pdx->Key.VendorId,
                    pdx->Key.bus, pdx->Key.slot, pdx->Key.function
                    ));

                return ApiSuccess;
            }

            // Increment device count
            DeviceCount++;
        }

        // Jump to next entry
        fdo = fdo->NextDevice;
    }

    // Return number of matched devices
    *pDeviceNumber = DeviceCount;

    DebugPrintf(("Criteria did not match any devices\n"));

    return ApiInvalidDeviceInfo;
}




/*******************************************************************************
 *
 * Function   :  PlxChipTypeGet
 *
 * Description:  Returns PLX chip type and revision
 *
 ******************************************************************************/
RETURN_CODE
PlxChipTypeGet(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U32              *pChipType,
    U8               *pRevision
    )
{
    U32 RegValue;


    // Reset values
    *pChipType = PLX_CHIP_TYPE;
    *pRevision = pKey->Revision;

    switch (*pChipType)
    {
        case 0x9080:
        case 0x9054:
        case 0x9056:
        case 0x9656:
            // Verify by reading hard-coded ID
            RegValue =
                PLX_9000_REG_READ(
                    pdx,
                    0x70               // Hard-coded ID register
                    );

            if (((RegValue & 0xFFFF) == 0x10b5) &&
                ((RegValue >> 16) == *pChipType))
            {
                // Get revision
                RegValue =
                    PLX_9000_REG_READ(
                        pdx,
                        0x74           // Revision ID
                        );

                // 9054 requires additional verification
                if (*pChipType != 0x9054)
                {
                    *pRevision = (U8)RegValue;
                }
                else
                {
                    // AA & AB versions have same revision ID
                    if (RegValue == 0xA)
                    {
                        PLX_PCI_REG_READ(
                            pdx,
                            CFG_REV_ID,
                            &RegValue
                            );

                        if ((RegValue & 0xf) == 0xb)
                            *pRevision = 0xAB;
                        else
                            *pRevision = 0xAA;
                    }
                    else
                    {
                        if (RegValue == 0xC)
                        {
                            // Set value for AC revision
                            *pRevision = 0xAC;
                        }
                        else
                        {
                            *pRevision = (U8)RegValue;
                        }
                    }
                }
            }
            break;

        case 0x9050:
        case 0x9030:
            // 9050/52 require additional verification
            if (*pChipType == 0x9030)
            {
                *pRevision = 0xAA;
            }
            else
            {
                // Read 9050/9052 PCI revision
                PLX_PCI_REG_READ(
                    pdx,
                    CFG_REV_ID,
                    &RegValue
                    );

                if ((RegValue & 0xF) == 0x2)
                {
                    *pRevision = 2;
                }
                else
                {
                    *pRevision = 1;
                }
            }
            break;

        case 0x8311:
            // Only AA revision exists
            *pRevision = 0xAA;
            break;

        default:
            DebugPrintf(("ERROR - Unable to determine chip type\n"));
            return ApiInvalidDeviceInfo;
    }

    DebugPrintf((
        "Device %04X_%04X = %04X rev %02X\n",
        pKey->DeviceId, pKey->VendorId, *pChipType, *pRevision
        ));

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxChipTypeSet
 *
 * Description:  Sets the PLX chip type dynamically
 *
 ******************************************************************************/
RETURN_CODE
PlxChipTypeSet(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U32               ChipType,
    U8                Revision
    )
{
    // Setting the PLX chip type is not supported in this PnP driver
    return ApiUnsupportedFunction;
}




/*******************************************************************************
 *
 * Function   :  PlxGetPortProperties
 *
 * Description:  Returns the current port information and status
 *
 ******************************************************************************/
RETURN_CODE
PlxGetPortProperties(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    PLX_PORT_PROP    *pPortProp
    )
{
    DebugPrintf(("Device does not support PCI Express Capability\n"));

    // Set default value for properties
    RtlZeroMemory(pPortProp, sizeof(PLX_PORT_PROP));
    pPortProp->PortType = PLX_PORT_ENDPOINT;

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxPciBoardReset
 *
 * Description:  Resets a device using software reset feature of PLX chip
 *
 ******************************************************************************/
RETURN_CODE
PlxPciBoardReset(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey
    )
{
    // Call chip-specific function
    return PlxChip_BoardReset(
        pdx
        );
}




/******************************************************************************
 *
 * Function   :  PlxRegisterRead
 *
 * Description:  Reads a PLX-specific control register
 *
 *****************************************************************************/
U32
PlxRegisterRead(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U32               offset,
    RETURN_CODE      *pReturnCode,
    BOOLEAN           bAdjustForPort
    )
{
    // Verify register offset
    if ((offset & 0x3) || (offset >= MAX_PLX_REG_OFFSET))
    {
        DebugPrintf(("ERROR - Invalid register offset (0x%x)\n", offset));

        if (pReturnCode != NULL)
            *pReturnCode = ApiInvalidOffset;

        return 0;
    }

    if (pReturnCode != NULL)
        *pReturnCode = ApiSuccess;

    return PLX_9000_REG_READ(
               pdx,
               offset
               );
}




/******************************************************************************
 *
 * Function   :  PlxRegisterWrite
 *
 * Description:  Writes to a PLX-specific control register
 *
 *****************************************************************************/
RETURN_CODE
PlxRegisterWrite(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U32               offset,
    U32               value,
    BOOLEAN           bAdjustForPort
    )
{
    // Verify register offset
    if ((offset & 0x3) || (offset >= MAX_PLX_REG_OFFSET))
    {
        DebugPrintf(("ERROR - Invalid register offset (0x%x)\n", offset));
        return ApiInvalidOffset;
    }

    PLX_9000_REG_WRITE(
        pdx,
        offset,
        value
        );

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxPciBarProperties
 *
 * Description:  Returns the properties of a PCI BAR space
 *
 ******************************************************************************/
RETURN_CODE
PlxPciBarProperties(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U8                BarIndex,
    PLX_PCI_BAR_PROP *pBarProperties
    )
{
    // Verify BAR number
    switch (BarIndex)
    {
        case 0:
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
            break;

        default:
            return ApiInvalidIndex;
    }

    // Get the actual BAR value in case OS didn't provide it
    if ((pdx->PciBar[BarIndex].Properties.BarValue == 0) &&
        (pdx->PciBar[BarIndex].Properties.Size != 0))
    {
        PLX_PCI_REG_READ(
            pdx,
            0x10 + (BarIndex * sizeof(U32)),
            &pdx->PciBar[BarIndex].Properties.BarValue
            );
    }

    // Return PCI BAR properties
    *pBarProperties = pdx->PciBar[BarIndex].Properties;

    // Display BAR properties
    DebugPrintf((
        "    PCI BAR %d: %08X\n",
        BarIndex, pdx->PciBar[BarIndex].Properties.BarValue
        ));

    DebugPrintf((
        "    Phys Addr: %08lX\n",
        (PLX_UINT_PTR)pdx->PciBar[BarIndex].Properties.Physical
        ));

    if (pdx->PciBar[BarIndex].Properties.Size >= (1 << 10))
    {
        DebugPrintf((
            "    Size     : %08lx  (%ld Kb)\n",
            (PLX_UINT_PTR)pdx->PciBar[BarIndex].Properties.Size,
            (PLX_UINT_PTR)pdx->PciBar[BarIndex].Properties.Size >> 10
            ));
    }
    else
    {
        DebugPrintf((
            "    Size     : %08lx  (%ld bytes)\n",
            (PLX_UINT_PTR)pdx->PciBar[BarIndex].Properties.Size,
            (PLX_UINT_PTR)pdx->PciBar[BarIndex].Properties.Size
            ));
    }

    DebugPrintf((
        "    Prefetch?: %s\n",
        (pdx->PciBar[BarIndex].Properties.bPrefetchable) ? "Yes" : "No"
        ));

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxPciBarMap
 *
 * Description:  Map a PCI BAR Space into User virtual space
 *
 ******************************************************************************/
RETURN_CODE
PlxPciBarMap(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U8                BarIndex,
    VOID             *pUserVa,
    VOID             *pOwner
    )
{
    // Handled in Dispatch_mmap() in Linux
    return ApiUnsupportedFunction;
}




/*******************************************************************************
 *
 * Function   :  PlxPciBarUnmap
 *
 * Description:  Unmap a previously mapped PCI BAR Space from User virtual space
 *
 ******************************************************************************/
RETURN_CODE
PlxPciBarUnmap(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    VOID             *UserVa,
    VOID             *pOwner
    )
{
    // Handled at user API level in Linux
    return ApiUnsupportedFunction;
}




/*******************************************************************************
 *
 * Function   :  PlxEepromPresent
 *
 * Description:  Returns the state of the EEPROM as reported by the PLX device
 *
 ******************************************************************************/
RETURN_CODE
PlxEepromPresent(
    DEVICE_EXTENSION  *pdx,
    PLX_DEVICE_KEY    *pKey,
    PLX_EEPROM_STATUS *pStatus
    )
{
    return Plx9000_EepromPresent(
               pdx,
               pStatus
               );
}




/******************************************************************************
 *
 * Function   :  PlxEepromProbe
 *
 * Description:  Probes for the presence of an EEPROM
 *
 *****************************************************************************/
RETURN_CODE
PlxEepromProbe(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    BOOLEAN          *pFlag
    )
{
    U16         OffsetProbe;
    U32         ValueRead;
    U32         ValueWrite;
    U32         ValueOriginal;
    RETURN_CODE rc;


    // Default to no EEPROM present
    *pFlag = FALSE;

    // Set EEPROM offset to use for probe
    OffsetProbe = 0x70;

    DebugPrintf(("Probing EEPROM at offset %02xh\n", OffsetProbe));

    // Get the current value
    rc =
        PlxEepromReadByOffset(
            pdx,
            pKey,
            OffsetProbe,
            &ValueOriginal
            );

    if (rc != ApiSuccess)
        return rc;

    // Prepare inverse value to write
    ValueWrite = ~(ValueOriginal);

    // Write inverse of original value
    rc =
        PlxEepromWriteByOffset(
            pdx,
            pKey,
            OffsetProbe,
            ValueWrite
            );

    if (rc != ApiSuccess)
        return rc;

    // Read updated value
    rc =
        PlxEepromReadByOffset(
            pdx,
            pKey,
            OffsetProbe,
            &ValueRead
            );

    if (rc != ApiSuccess)
        return rc;

    // Check if value was written properly
    if (ValueRead == ValueWrite)
    {
        DebugPrintf(("Probe detected an EEPROM present\n"));

        *pFlag = TRUE;

        // Restore the original value
        PlxEepromWriteByOffset(
            pdx,
            pKey,
            OffsetProbe,
            ValueOriginal
            );
    }
    else
    {
        DebugPrintf(("Probe did not detect an EEPROM\n"));
    }

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxEepromCrcGet
 *
 * Description:  Returns the EEPROM CRC and its status
 *
 ******************************************************************************/
RETURN_CODE
PlxEepromCrcGet(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U32              *pCrc,
    U8               *pCrcStatus
    )
{
    // Device does not support CRC
    return ApiUnsupportedFunction;
}




/*******************************************************************************
 *
 * Function   :  PlxEepromCrcUpdate
 *
 * Description:  Updates the EEPROM CRC
 *
 ******************************************************************************/
RETURN_CODE
PlxEepromCrcUpdate(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U32              *pCrc,
    BOOLEAN           bUpdateEeprom
    )
{
    // Device does not support CRC
    return ApiUnsupportedFunction;
}




/*******************************************************************************
 *
 * Function   :  PlxEepromReadByOffset
 *
 * Description:  Read a 32-bit value from the EEPROM at a specified offset
 *
 ******************************************************************************/
RETURN_CODE
PlxEepromReadByOffset(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U16               offset,
    U32              *pValue
    )
{
    // Make sure offset is aligned on 32-bit boundary
    if (offset & (3 << 0))
    {
        *pValue = 0;
        return ApiInvalidOffset;
    }

    // Call chip-specific function
    return PlxChip_EepromReadByOffset(
        pdx,
        offset,
        pValue
        );
}




/*******************************************************************************
 *
 * Function   :  PlxEepromWriteByOffset
 *
 * Description:  Write a 32-bit value to the EEPROM at a specified offset
 *
 ******************************************************************************/
RETURN_CODE
PlxEepromWriteByOffset(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U16               offset,
    U32               value
    )
{
    // Make sure offset is aligned on 32-bit boundary
    if (offset & (3 << 0))
    {
        return ApiInvalidOffset;
    }

    // Call chip-specific function
    return PlxChip_EepromWriteByOffset(
        pdx,
        offset,
        value
        );
}




/*******************************************************************************
 *
 * Function   :  PlxEepromReadByOffset_16
 *
 * Description:  Read a 16-bit value from the EEPROM at a specified offset
 *
 ******************************************************************************/
RETURN_CODE
PlxEepromReadByOffset_16(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U16               offset,
    U16              *pValue
    )
{
    U32         Value_32;
    RETURN_CODE rc;


    // Set default return value
    *pValue = 0;

    // Make sure offset is aligned on 16-bit boundary
    if (offset & (1 << 0))
    {
        return ApiInvalidOffset;
    }

    /******************************************
     * For devices that do not support 16-bit
     * EEPROM accesses, use 32-bit access
     *****************************************/

    // Get current 32-bit value
    rc =
        PlxEepromReadByOffset(
            pdx,
            pKey,
            (offset & ~0x3),
            &Value_32
            );

    if (rc != ApiSuccess)
        return rc;

    // Return desired 16-bit portion
    if ((offset & 0x3) == 2)
    {
        *pValue = (U16)(Value_32 >> 16);
    }
    else
    {
        *pValue = (U16)(Value_32);
    }

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxEepromWriteByOffset_16
 *
 * Description:  Write a 16-bit value to the EEPROM at a specified offset
 *
 ******************************************************************************/
RETURN_CODE
PlxEepromWriteByOffset_16(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U16               offset,
    U16               value
    )
{
    U32         Value_32;
    RETURN_CODE rc;


    // Make sure offset is aligned on 16-bit boundary
    if (offset & (1 << 0))
    {
        return ApiInvalidOffset;
    }

    /******************************************
     * For devices that do not support 16-bit
     * EEPROM accesses, use 32-bit access
     *****************************************/

    // Get current 32-bit value
    rc =
        PlxEepromReadByOffset(
            pdx,
            pKey,
            (offset & ~0x3),
            &Value_32
            );

    if (rc != ApiSuccess)
        return rc;

    // Insert new 16-bit value in correct location
    if ((offset & 0x3) == 2)
    {
        Value_32 = ((U32)value << 16) | (Value_32 & 0xFFFF);
    }
    else
    {
        Value_32 = ((U32)value) | (Value_32 & 0xFFFF0000);
    }

    // Update EEPROM
    return PlxEepromWriteByOffset(
        pdx,
        pKey,
        (offset & ~0x3),
        Value_32
        );
}




/*******************************************************************************
 *
 * Function   :  PlxPciIoPortTransfer
 *
 * Description:  Read or Write from/to an I/O port
 *
 ******************************************************************************/
RETURN_CODE
PlxPciIoPortTransfer(
    U64              IoPort,
    VOID            *pBuffer,
    U32              SizeInBytes,
    PLX_ACCESS_TYPE  AccessType,
    BOOLEAN          bReadOperation
    )
{
    if (pBuffer == NULL)
        return ApiNullParam;

    // Verify size & type
    switch (AccessType)
    {
        case BitSize8:
            break;

        case BitSize16:
            if (IoPort & (1 << 0))
            {
                DebugPrintf(("ERROR - I/O port not aligned on 16-bit boundary\n"));
                return ApiInvalidAddress;
            }

            if (SizeInBytes & (1 << 0))
            {
                DebugPrintf(("ERROR - Byte count not aligned on 16-bit boundary\n"));
                return ApiInvalidSize;
            }
            break;

        case BitSize32:
            if (IoPort & 0x3)
            {
                DebugPrintf(("ERROR - I/O port not aligned on 32-bit boundary\n"));
                return ApiInvalidAddress;
            }

            if (SizeInBytes & 0x3)
            {
                DebugPrintf(("ERROR - Byte count not aligned on 32-bit boundary\n"));
                return ApiInvalidSize;
            }
            break;

        default:
            return ApiInvalidAccessType;
    }

    if (bReadOperation)
    {
        switch (AccessType)
        {
            case BitSize8:
                IO_PORT_READ_8_BUFFER(
                    IoPort,
                    pBuffer,
                    SizeInBytes
                    );
                break;

            case BitSize16:
                IO_PORT_READ_16_BUFFER(
                    IoPort,
                    pBuffer,
                    SizeInBytes
                    );
                break;

            case BitSize32:
                IO_PORT_READ_32_BUFFER(
                    IoPort,
                    pBuffer,
                    SizeInBytes
                    );
                break;

            default:
                // Added to avoid compiler warnings
                break;
        }
    }
    else
    {
        switch (AccessType)
        {
            case BitSize8:
                IO_PORT_WRITE_8_BUFFER(
                    IoPort,
                    pBuffer,
                    SizeInBytes
                    );
                break;

            case BitSize16:
                IO_PORT_WRITE_16_BUFFER(
                    IoPort,
                    pBuffer,
                    SizeInBytes
                    );
                break;

            case BitSize32:
                IO_PORT_WRITE_32_BUFFER(
                    IoPort,
                    pBuffer,
                    SizeInBytes
                    );
                break;

            default:
                // Added to avoid compiler warnings
                break;
        }
    }

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxPciPhysicalMemoryAllocate
 *
 * Description:  Allocate physically contiguous page-locked memory
 *
 ******************************************************************************/
RETURN_CODE
PlxPciPhysicalMemoryAllocate(
    DEVICE_EXTENSION *pdx,
    PLX_PHYSICAL_MEM *pPciMem,
    BOOLEAN           bSmallerOk,
    VOID             *pOwner
    )
{
    U32                  DecrementAmount;
    PLX_PHYS_MEM_OBJECT *pBufferObj;


    // Initialize buffer information
    pPciMem->UserAddr     = 0;
    pPciMem->PhysicalAddr = 0;
    pPciMem->CpuPhysical  = 0;

    /*******************************************************
     * Verify size
     *
     * A size of 0 is valid because this function may
     * be called to allocate a common buffer of size 0;
     * therefore, the information is reset & return sucess.
     ******************************************************/
    if (pPciMem->Size == 0)
    {
        return ApiSuccess;
    }

    // Allocate memory for new list object
    pBufferObj =
        kmalloc(
            sizeof(PLX_PHYS_MEM_OBJECT),
            GFP_KERNEL
            );

    if (pBufferObj == NULL)
    {
        DebugPrintf((
            "ERROR - Memory allocation for list object failed\n"
            ));

        return ApiInsufficientResources;
    }

    // Set buffer request size
    pBufferObj->Size = pPciMem->Size;

    // Setup amount to reduce on failure
    DecrementAmount = (pPciMem->Size / 10);

    DebugPrintf((
        "Attempting to allocate physical memory (%d Kb)...\n",
        (pPciMem->Size >> 10)
        ));

    do
    {
        // Attempt to allocate the buffer
        pBufferObj->pKernelVa =
            Plx_dma_buffer_alloc(
                pdx,
                pBufferObj
                );

        if (pBufferObj->pKernelVa == NULL)
        {
            // Reduce memory request size if requested
            if (bSmallerOk && (pBufferObj->Size > PAGE_SIZE))
            {
                pBufferObj->Size -= DecrementAmount;
            }
            else
            {
                // Release the list object
                kfree(
                    pBufferObj
                    );

                DebugPrintf((
                    "ERROR - Physical memory allocation failed\n"
                    ));

                pPciMem->Size = 0;

                return ApiInsufficientResources;
            }
        }
    }
    while (pBufferObj->pKernelVa == NULL);

    // Record buffer owner
    pBufferObj->pOwner = pOwner;

    // Assign buffer to device if provided
    if (pOwner != pGbl_DriverObject)
    {
        // Return buffer information
        pPciMem->Size         = pBufferObj->Size;
        pPciMem->PhysicalAddr = pBufferObj->BusPhysical;
        pPciMem->CpuPhysical  = pBufferObj->CpuPhysical;

        // Add buffer object to list
        spin_lock(
            &(pdx->Lock_PhysicalMemList)
            );

        list_add_tail(
            &(pBufferObj->ListEntry),
            &(pdx->List_PhysicalMem)
            );

        spin_unlock(
            &(pdx->Lock_PhysicalMemList)
            );
    }
    else
    {
        // Store common buffer information
        pGbl_DriverObject->CommonBuffer = *pBufferObj;

        // Release the list object
        kfree(
            pBufferObj
            );
    }

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxPciPhysicalMemoryFree
 *
 * Description:  Free previously allocated physically contiguous page-locked memory
 *
 ******************************************************************************/
RETURN_CODE
PlxPciPhysicalMemoryFree(
    DEVICE_EXTENSION *pdx,
    PLX_PHYSICAL_MEM *pPciMem
    )
{
    struct list_head    *pEntry;
    PLX_PHYS_MEM_OBJECT *pBufferObj;


    spin_lock(
        &(pdx->Lock_PhysicalMemList)
        );

    pEntry = pdx->List_PhysicalMem.next;

    // Traverse list to find the desired list object
    while (pEntry != &(pdx->List_PhysicalMem))
    {
        // Get the object
        pBufferObj =
            list_entry(
                pEntry,
                PLX_PHYS_MEM_OBJECT,
                ListEntry
                );

        // Check if the physical addresses matches
        if (pBufferObj->BusPhysical == pPciMem->PhysicalAddr)
        {
            // Remove the object from the list
            list_del(
                pEntry
                );

            spin_unlock(
                &(pdx->Lock_PhysicalMemList)
                );

            // Release the buffer
            Plx_dma_buffer_free(
                pdx,
                pBufferObj
                );

            // Release the list object
            kfree(
                pBufferObj
                );

            return ApiSuccess;
        }

        // Jump to next item in the list
        pEntry = pEntry->next;
    }

    spin_unlock(
        &(pdx->Lock_PhysicalMemList)
        );

    DebugPrintf((
        "ERROR - buffer object not found in list\n"
        ));

    return ApiInvalidData;
}




/*******************************************************************************
 *
 * Function   :  PlxPciPhysicalMemoryMap
 *
 * Description:  Maps physical memory to User virtual address space
 *
 ******************************************************************************/
RETURN_CODE
PlxPciPhysicalMemoryMap(
    DEVICE_EXTENSION *pdx,
    PLX_PHYSICAL_MEM *pPciMem,
    VOID             *pOwner
    )
{
    // Handled in Dispatch_mmap() in Linux
    return ApiUnsupportedFunction;
}




/*******************************************************************************
 *
 * Function   :  PlxPciPhysicalMemoryUnmap
 *
 * Description:  Unmap physical memory from User virtual address space
 *
 ******************************************************************************/
RETURN_CODE
PlxPciPhysicalMemoryUnmap(
    DEVICE_EXTENSION *pdx,
    PLX_PHYSICAL_MEM *pPciMem,
    VOID             *pOwner
    )
{
    // Handled by user-level API in Linux
    return ApiUnsupportedFunction;
}




/******************************************************************************
 *
 * Function   :  PlxInterruptEnable
 *
 * Description:  Enables specific interupts of the PLX Chip
 *
 ******************************************************************************/
RETURN_CODE
PlxInterruptEnable(
    DEVICE_EXTENSION *pdx,
    PLX_INTERRUPT    *pPlxIntr
    )
{
    // Call chip-specific function
    return PlxChip_InterruptEnable(
        pdx,
        pPlxIntr
        );
}




/******************************************************************************
 *
 * Function   :  PlxInterruptDisable
 *
 * Description:  Disables specific interrupts of the PLX Chip
 *
 ******************************************************************************/
RETURN_CODE
PlxInterruptDisable(
    DEVICE_EXTENSION *pdx,
    PLX_INTERRUPT    *pPlxIntr
    )
{
    // Call chip-specific function
    return PlxChip_InterruptDisable(
        pdx,
        pPlxIntr
        );
}




/*******************************************************************************
 *
 * Function   :  PlxNotificationRegisterFor
 *
 * Description:  Registers a wait object for notification on interrupt(s)
 *
 ******************************************************************************/
RETURN_CODE
PlxNotificationRegisterFor(
    DEVICE_EXTENSION  *pdx,
    PLX_INTERRUPT     *pPlxIntr,
    VOID             **pUserWaitObject,
    VOID              *pOwner
    )
{
    unsigned long    flags;
    PLX_WAIT_OBJECT *pWaitObject;


    // Allocate a new wait object
    pWaitObject =
        kmalloc(
            sizeof(PLX_WAIT_OBJECT),
            GFP_KERNEL
            );

    if (pWaitObject == NULL)
    {
        DebugPrintf((
            "ERROR - memory allocation for interrupt wait object failed\n"
            ));

        return ApiInsufficientResources;
    }

    // Provide the wait object to the user app
    *pUserWaitObject = pWaitObject;

    // Record the owner
    pWaitObject->pOwner = pOwner;

    // Clear interrupt source
    pWaitObject->Source_Ints     = INTR_TYPE_NONE;
    pWaitObject->Source_Doorbell = 0;

    // Mark the object as pending
    pWaitObject->bPending = TRUE;

    // Initialize wait queue
    init_waitqueue_head(
        &(pWaitObject->WaitQueue)
        );

    // Set interrupt notification flags
    PlxChipSetInterruptNotifyFlags(
        pPlxIntr,
        pWaitObject
        );

    // Add to list of waiting objects
    spin_lock_irqsave(
        &(pdx->Lock_WaitObjectsList),
        flags
        );

    list_add_tail(
        &(pWaitObject->ListEntry),
        &(pdx->List_WaitObjects)
        );

    spin_unlock_irqrestore(
        &(pdx->Lock_WaitObjectsList),
        flags
        );

    DebugPrintf((
        "Registered interrupt wait object (%p)\n",
        pWaitObject
        ));

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxNotificationWait
 *
 * Description:  Put the process to sleep until wake-up event occurs or timeout
 *
 ******************************************************************************/
RETURN_CODE
PlxNotificationWait(
    DEVICE_EXTENSION *pdx,
    VOID             *pUserWaitObject,
    PLX_UINT_PTR      Timeout_ms
    )
{
    int               Wait_rc;
    RETURN_CODE       rc;
    PLX_UINT_PTR      Timeout_sec;
    unsigned long     flags;
    struct list_head *pEntry;
    PLX_WAIT_OBJECT  *pWaitObject;


    // Find the wait object in the list
    spin_lock_irqsave(
        &(pdx->Lock_WaitObjectsList),
        flags
        );

    pEntry = pdx->List_WaitObjects.next;

    // Find the wait object and wait for wake-up event
    while (pEntry != &(pdx->List_WaitObjects))
    {
        // Get the wait object
        pWaitObject =
            list_entry(
                pEntry,
                PLX_WAIT_OBJECT,
                ListEntry
                );

        // Check if the object address matches the Tag
        if (pWaitObject == pUserWaitObject)
        {
            spin_unlock_irqrestore(
                &(pdx->Lock_WaitObjectsList),
                flags
                );

            DebugPrintf((
                "Waiting for Interrupt wait object (%p) to wake-up\n",
                pWaitObject
                ));

            /*********************************************************
             * Convert milliseconds to jiffies.  The following
             * formula is used:
             *
             *                      ms * HZ
             *           jiffies = ---------
             *                       1,000
             *
             *
             *  where:  HZ      = System-defined clock ticks per second
             *          ms      = Timeout in milliseconds
             *          jiffies = Number of HZ's per second
             *
             *  Note: Since the timeout is stored as a "long" integer,
             *        the conversion to jiffies is split into two operations.
             *        The first is on number of seconds and the second on
             *        the remaining millisecond precision.  This mimimizes
             *        overflow when the specified timeout is large and also
             *        keeps millisecond precision.
             ********************************************************/

            // Perform conversion if not infinite wait
            if (Timeout_ms != PLX_TIMEOUT_INFINITE)
            {
                // Get number of seconds
                Timeout_sec = Timeout_ms / 1000;

                // Store milliseconds precision
                Timeout_ms = Timeout_ms - (Timeout_sec * 1000);

                // Convert to jiffies
                Timeout_sec = Timeout_sec * HZ;
                Timeout_ms  = (Timeout_ms * HZ) / 1000;

                // Compute total jiffies
                Timeout_ms = Timeout_sec + Timeout_ms;
            }

            // Timeout parameter is signed and can't be negative
            if ((signed long)Timeout_ms < 0)
            {
                // Shift out negative bit
                Timeout_ms = Timeout_ms >> 1;
            }

            do
            {
                // Wait for interrupt event
                Wait_rc =
                    Plx_wait_event_interruptible_timeout(
                        pWaitObject->WaitQueue,
                        (pWaitObject->bPending == FALSE),
                        Timeout_ms
                        );
            }
            while ((Wait_rc == 0) && (Timeout_ms == PLX_TIMEOUT_INFINITE));

            if (Wait_rc > 0)
            {
                // Condition met or interrupt occurred
                DebugPrintf(("Interrupt wait object awakened\n"));
                rc = ApiSuccess;
            }
            else if (Wait_rc == 0)
            {
                // Timeout reached
                DebugPrintf(("Timeout waiting for interrupt\n"));
                return ApiWaitTimeout;
            }
            else
            {
                // Interrupted by a signal
                DebugPrintf(("Interrupt wait object interrupted by signal or error\n"));
                rc = ApiWaitCanceled;
            }

            // Mark the object as pending to reset it
            pWaitObject->bPending = TRUE;

            return rc;
        }

        // Jump to next item in the list
        pEntry = pEntry->next;
    }

    spin_unlock_irqrestore(
        &(pdx->Lock_WaitObjectsList),
        flags
        );

    DebugPrintf((
        "Interrupt wait object (%p) not found or previously canceled\n",
        pUserWaitObject
        ));

    // Object not found at this point
    return ApiFailed;
}




/*******************************************************************************
 *
 * Function   :  PlxNotificationStatus
 *
 * Description:  Returns the interrupt(s) that have caused notification events
 *
 ******************************************************************************/
RETURN_CODE
PlxNotificationStatus(
    DEVICE_EXTENSION *pdx,
    VOID             *pUserWaitObject,
    PLX_INTERRUPT    *pPlxIntr
    )
{
    unsigned long       flags;
    struct list_head   *pEntry;
    PLX_WAIT_OBJECT    *pWaitObject;
    PLX_INTERRUPT_DATA  IntData;


    spin_lock_irqsave(
        &(pdx->Lock_WaitObjectsList),
        flags
        );

    pEntry = pdx->List_WaitObjects.next;

    // Traverse list to find the desired list object
    while (pEntry != &(pdx->List_WaitObjects))
    {
        // Get the object
        pWaitObject =
            list_entry(
                pEntry,
                PLX_WAIT_OBJECT,
                ListEntry
                );

        // Check if desired object
        if (pWaitObject == pUserWaitObject)
        {
            // Copy the interrupt sources
            IntData.Source_Ints     = pWaitObject->Source_Ints;
            IntData.Source_Doorbell = pWaitObject->Source_Doorbell;

            // Reset interrupt sources
            pWaitObject->Source_Ints     = INTR_TYPE_NONE;
            pWaitObject->Source_Doorbell = 0;

            spin_unlock_irqrestore(
                &(pdx->Lock_WaitObjectsList),
                flags
                );

            DebugPrintf((
                "Returning status for interrupt wait object (%p)...\n",
                pWaitObject
                ));

            // Set triggered interrupts
            PlxChipSetInterruptStatusFlags(
                &IntData,
                pPlxIntr
                );

            return ApiSuccess;
        }

        // Jump to next item in the list
        pEntry = pEntry->next;
    }

    spin_unlock_irqrestore(
        &(pdx->Lock_WaitObjectsList),
        flags
        );

    return ApiFailed;
}




/*******************************************************************************
 *
 * Function   :  PlxNotificationCancel
 *
 * Description:  Cancels a registered notification event
 *
 ******************************************************************************/
RETURN_CODE
PlxNotificationCancel(
    DEVICE_EXTENSION *pdx,
    VOID             *pUserWaitObject,
    VOID             *pOwner
    )
{
    BOOLEAN           bRemove;
    unsigned long     flags;
    struct list_head *pEntry;
    PLX_WAIT_OBJECT  *pWaitObject;


    spin_lock_irqsave(
        &(pdx->Lock_WaitObjectsList),
        flags
        );

    pEntry = pdx->List_WaitObjects.next;

    // Find the object and remove it
    while (pEntry != &(pdx->List_WaitObjects))
    {
        // Get the object
        pWaitObject =
            list_entry(
                pEntry,
                PLX_WAIT_OBJECT,
                ListEntry
                );

        // Default to not remove
        bRemove = FALSE;

        // Determine if object should be removed
        if (pOwner == pWaitObject->pOwner)
        {
            if (pUserWaitObject == NULL)
            {
                bRemove = TRUE;
            }
            else if (pWaitObject == pUserWaitObject)
            {
                bRemove = TRUE;
            }
        }

        // Remove object
        if (bRemove)
        {
            DebugPrintf((
                "Removing interrupt wait object (%p)...\n",
                pWaitObject
                ));

            // Remove the object from the list
            list_del(
                pEntry
                );

            spin_unlock_irqrestore(
                &(pdx->Lock_WaitObjectsList),
                flags
                );

            // Wake-up processes only if wait object is pending
            if (pWaitObject->bPending == TRUE)
            {
                // Wake-up any process waiting on the object
                wake_up_interruptible(
                    &(pWaitObject->WaitQueue)
                    );
            }

            // Release the list object
            kfree(
                pWaitObject
                );

            // Return if removing only a specific object
            if (pUserWaitObject != NULL)
                return ApiSuccess;

            // Reset to beginning of list
            spin_lock_irqsave(
                &(pdx->Lock_WaitObjectsList),
                flags
                );

            pEntry = pdx->List_WaitObjects.next;
        }
        else
        {
            // Jump to next item in the list
            pEntry = pEntry->next;
        }
    }

    spin_unlock_irqrestore(
        &(pdx->Lock_WaitObjectsList),
        flags
        );

    return ApiFailed;
}




/*******************************************************************************
 *
 * Function   :  PlxPciBarSpaceTransfer
 *
 * Description:  Accesses a PCI BAR space
 *
 ******************************************************************************/
RETURN_CODE
PlxPciBarSpaceTransfer(
    DEVICE_EXTENSION *pdx,
    U8                BarIndex,
    U32               offset,
    U8               *pBuffer,
    U32               ByteCount,
    PLX_ACCESS_TYPE   AccessType,
    BOOLEAN           bRemap,
    BOOLEAN           bReadOperation
    )
{
    U16          Offset_RegRemap;
    U32          RegValue;
    U32          SpaceRange;
    U32          SpaceOffset;
    U32          RemapOriginal;
    U32          BytesToTransfer;
    PLX_UINT_PTR SpaceVa;


    DebugPrintf((
        "%s PCI BAR %d (%s=%08X  %d bytes)\n",
        (bReadOperation) ? "Read from" : "Write to",
        BarIndex,
        (bRemap) ? "Local Addr" : "Offset",
        offset,
        ByteCount
        ));

    // Added to prevent compiler warnings
    RemapOriginal = 0;

    // Verify data alignment
    switch (AccessType)
    {
        case BitSize8:
            break;

        case BitSize16:
            if (offset & 0x1)
            {
                DebugPrintf(("ERROR - Local address not aligned\n"));
                return ApiInvalidAddress;
            }

            if (ByteCount & 0x1)
            {
                DebugPrintf(("ERROR - Byte count not aligned\n"));
                return ApiInvalidSize;
            }
            break;

        case BitSize32:
            if (offset & 0x3)
            {
                DebugPrintf(("ERROR - Local address not aligned\n"));
                return ApiInvalidAddress;
            }

            if (ByteCount & 0x3)
            {
                DebugPrintf(("ERROR - Byte count not aligned\n"));
                return ApiInvalidSize;
            }
            break;

        default:
            DebugPrintf(("ERROR - Invalid access type\n"));
            return ApiInvalidAccessType;
    }

    // Get offset of remap register
    PlxChipGetRemapOffset(
        pdx,
        BarIndex,
        &Offset_RegRemap
        );

    if (Offset_RegRemap == (U16)-1)
    {
        return ApiInvalidIopSpace;
    }

    // Only memory spaces are supported by this function
    if (pdx->PciBar[BarIndex].Properties.bIoSpace)
    {
        DebugPrintf(("ERROR - I/O spaces not supported by this function\n"));
        return ApiInvalidIopSpace;
    }

    // Get kernel virtual address for the space
    SpaceVa = (PLX_UINT_PTR)pdx->PciBar[BarIndex].pVa;

    if (SpaceVa == 0)
    {
        DebugPrintf((
            "ERROR - Invalid kernel VA (0x%08lx) for PCI BAR\n",
            SpaceVa
            ));

        return ApiInvalidAddress;
    }

    // Save the remap register
    if (bRemap)
    {
        RemapOriginal =
            PLX_9000_REG_READ(
                pdx,
                Offset_RegRemap
                );
    }
    else
    {
        // Make sure requested area doesn't exceed our local space window boundary
        if ((offset + ByteCount) > (U32)pdx->PciBar[BarIndex].Properties.Size)
        {
            DebugPrintf(("ERROR - requested area exceeds space range\n"));
            return ApiInvalidSize;
        }
    }

    // Get the range of the space
    SpaceRange = ~((U32)pdx->PciBar[BarIndex].Properties.Size - 1);

    // Transfer data in blocks
    while (ByteCount != 0)
    {
        // Adjust remap if necessary
        if (bRemap)
        {
            // Clear upper bits of remap
            RegValue = RemapOriginal & ~SpaceRange;

            // Adjust window to local address
            RegValue |= offset & SpaceRange;

            PLX_9000_REG_WRITE(
                pdx,
                Offset_RegRemap,
                RegValue
                );
        }

        // Get current offset into space
        SpaceOffset = offset & (~SpaceRange);

        // Calculate bytes to transfer for next block
        if (ByteCount <= (((~SpaceRange) + 1) - SpaceOffset))
        {
            BytesToTransfer = ByteCount;
        }
        else
        {
            BytesToTransfer = ((~SpaceRange) + 1) - SpaceOffset;
        }

        // Make sure user buffer is accessible for next block
        if (bReadOperation)
        {
            // Read from device = Write to user buffer
            if (access_ok(
                    VERIFY_WRITE,
                    pBuffer,
                    BytesToTransfer
                    ) == FALSE)
            {
                DebugPrintf(("ERROR - User buffer not accessible\n"));
                return ApiInsufficientResources;
            }
        }
        else
        {
            // Write to device = Read from user buffer
            if (access_ok(
                    VERIFY_READ,
                    pBuffer,
                    BytesToTransfer
                    ) == FALSE)
            {
                DebugPrintf(("ERROR - User buffer not accessible\n"));
                return ApiInsufficientResources;
            }
        }

        if (bReadOperation)
        {
            // Copy block to user buffer
            switch (AccessType)
            {
                case BitSize8:
                    DEV_MEM_TO_USER_8(
                        pBuffer,
                        (SpaceVa + SpaceOffset),
                        BytesToTransfer
                        );
                    break;

                case BitSize16:
                    DEV_MEM_TO_USER_16(
                        pBuffer,
                        (SpaceVa + SpaceOffset),
                        BytesToTransfer
                        );
                    break;

                case BitSize32:
                    DEV_MEM_TO_USER_32(
                        pBuffer,
                        (SpaceVa + SpaceOffset),
                        BytesToTransfer
                        );
                    break;

                case BitSize64:
                    // 64-bit not implemented yet
                    break;
            }
        }
        else
        {
            // Copy user buffer to device memory
            switch (AccessType)
            {
                case BitSize8:
                    USER_TO_DEV_MEM_8(
                        (SpaceVa + SpaceOffset),
                        pBuffer,
                        BytesToTransfer
                        );
                    break;

                case BitSize16:
                    USER_TO_DEV_MEM_16(
                        (SpaceVa + SpaceOffset),
                        pBuffer,
                        BytesToTransfer
                        );
                    break;

                case BitSize32:
                    USER_TO_DEV_MEM_32(
                        (SpaceVa + SpaceOffset),
                        pBuffer,
                        BytesToTransfer
                        );
                    break;

                case BitSize64:
                    // 64-bit not implemented yet
                    break;
            }
        }

        // Adjust for next block access
        pBuffer   += BytesToTransfer;
        offset    += BytesToTransfer;
        ByteCount -= BytesToTransfer;
    }

    // Restore the remap register
    if (bRemap)
    {
        PLX_9000_REG_WRITE(
            pdx,
            Offset_RegRemap,
            RemapOriginal
            );
    }

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxPciVpdRead
 *
 * Description:  Read the Vital Product Data
 *
 ******************************************************************************/
RETURN_CODE
PlxPciVpdRead(
    DEVICE_EXTENSION *pdx,
    U16               offset,
    U32              *pValue
    )
{
    S16 VpdRetries;
    S16 VpdPollCount;
    U16 Offset_VPD;
    U32 RegValue;


    // Check for unaligned offset
    if (offset & 0x3)
    {
        *pValue = (U32)-1;
        return ApiInvalidOffset;
    }

    // Get the VPD offset
    Offset_VPD =
        PlxGetExtendedCapabilityOffset(
            pdx,
            CAP_ID_VPD
            );

    if (Offset_VPD == 0)
    {
        return ApiUnsupportedFunction;
    }

     /**********************************************
      * The EEDO Input (bit 31) must be disabled
      * for some chips when VPD access is used.
      * Since it is a reserved bit in older chips,
      * there is no harm in clearing it for all.
      *********************************************/
    RegValue =
        PLX_9000_REG_READ(
            pdx,
            REG_EEPROM_CTRL
            );

    PLX_9000_REG_WRITE(
        pdx,
        REG_EEPROM_CTRL,
        RegValue & ~(1 << 31)
        );

    // Prepare VPD command
    RegValue = ((U32)offset << 16) | 0x3;

    VpdRetries = VPD_COMMAND_MAX_RETRIES;
    do
    {
        /**************************************
        *  This loop will continue until the
        *  VPD reports success or until we reach
        *  the maximum number of retries
        **************************************/

        // Send VPD Command
        PLX_PCI_REG_WRITE(
            pdx,
            Offset_VPD,
            RegValue
            );

        // Poll until VPD operation has completed
        VpdPollCount = VPD_STATUS_MAX_POLL;
        do
        {
            // Delay for a bit for VPD operation
            Plx_sleep(VPD_STATUS_POLL_DELAY);

            // Get VPD Status
            PLX_PCI_REG_READ(
                pdx,
                Offset_VPD,
                &RegValue
                );

            // Check for command completion
            if (RegValue & (1 << 31))
            {
                // VPD read completed successfully

                // Get the VPD Data result
                PLX_PCI_REG_READ(
                    pdx,
                    Offset_VPD + sizeof(U32),
                    pValue
                    );

                return ApiSuccess;
            }
        }
        while (VpdPollCount--);
    }
    while (VpdRetries--);

    // At this point, VPD access failed
    DebugPrintf(("ERROR - Timeout waiting for VPD read to complete\n"));

    *pValue = (U32)-1;

    return ApiWaitTimeout;
}




/*******************************************************************************
 *
 * Function   :  PlxPciVpdWrite
 *
 * Description:  Write to the Vital Product Data
 *
 ******************************************************************************/
RETURN_CODE
PlxPciVpdWrite(
    DEVICE_EXTENSION *pdx,
    U16               offset,
    U32               VpdData
    )
{
    S16 VpdRetries;
    S16 VpdPollCount;
    U16 Offset_VPD;
    U32 RegValue;


    // Check for unaligned offset
    if (offset & 0x3)
        return ApiInvalidOffset;

    // Get the VPD offset
    Offset_VPD =
        PlxGetExtendedCapabilityOffset(
            pdx,
            CAP_ID_VPD
            );

    if (Offset_VPD == 0)
    {
        return ApiUnsupportedFunction;
    }

     /**********************************************
      * The EEDO Input (bit 31) must be disabled
      * for some chips when VPD access is used.
      * Since it is a reserved bit in older chips,
      * there is no harm in clearing it for all.
      *********************************************/
    RegValue =
        PLX_9000_REG_READ(
            pdx,
            REG_EEPROM_CTRL
            );

    PLX_9000_REG_WRITE(
        pdx,
        REG_EEPROM_CTRL,
        RegValue & ~(1 << 31)
        );

    // Put write value into VPD Data register
    PLX_PCI_REG_WRITE(
        pdx,
        Offset_VPD + sizeof(U32),
        VpdData
        );

    // Prepare VPD command
    RegValue = (1 << 31) | ((U32)offset << 16) | 0x3;

    VpdRetries = VPD_COMMAND_MAX_RETRIES;
    do
    {
        /**************************************
        *  This loop will continue until the
        *  VPD reports success or until we reach
        *  the maximum number of retries
        **************************************/

        // Send VPD command
        PLX_PCI_REG_WRITE(
            pdx,
            Offset_VPD,
            RegValue
            );

        // Poll until VPD operation has completed
        VpdPollCount = VPD_STATUS_MAX_POLL;
        do
        {
            // Delay for a bit for VPD operation
            Plx_sleep(VPD_STATUS_POLL_DELAY);

            // Get VPD Status
            PLX_PCI_REG_READ(
                pdx,
                Offset_VPD,
                &RegValue
                );

            // Check for command completion
            if ((RegValue & (1 << 31)) == 0)
            {
                // VPD write completed successfully
                return ApiSuccess;
            }
        }
        while (VpdPollCount--);
    }
    while (VpdRetries--);

    // At this point, VPD access failed
    DebugPrintf(("ERROR - Timeout waiting for VPD write to complete\n"));

    return ApiWaitTimeout;
}