/***************************************************************************
 *
 *  Oxford Semiconductor Proprietary and Confidential Information
 *
 *  Copyright:      Oxford Semiconductor Ltd, 2006
 *
 *  Description:    Implementation of the GPIO core functionality.
 *
 ***************************************************************************/

#include "Common.h"
#include "GpioCoreIoControls.h"
#include "GpioHwRegDef.h"
#include "GpioCore.h"
#if defined(EVENT_TRACING)
/*
 *  The trace message header (.tmh) file must be included in a source file
 *  before any WPP macro calls and after defining a WPP_CONTROL_GUIDS
 *  macro (defined in trace.h). During the compilation, WPP scans the source
 *  files for DoTraceMessage() calls and builds a .tmh file which stores a unique
 *  data GUID for each message, the text resource string for each message,
 *  and the data types of the variables passed in for each message.
 *  This file is automatically generated and used during post-processing.
 */
#include "GpioCore.tmh"
#endif



/**
 *  I/O and memory space read macros.
 */
#define REG_RD_08(ofs)          OsRegisterRead08(&pThis->os.regBaseDescr, (ofs))
#define REG_RD_16(ofs)          OsRegisterRead16(&pThis->os.regBaseDescr, (ofs))
#define REG_RD_32(ofs)          OsRegisterRead32(&pThis->os.regBaseDescr, (ofs))

/**
 *  I/O and memory space write macros.
 */
#define REG_WR_08(ofs, val)     OsRegisterWrite08(&pThis->os.regBaseDescr, (ofs), (val))
#define REG_WR_16(ofs, val)     OsRegisterWrite16(&pThis->os.regBaseDescr, (ofs), (val))
#define REG_WR_32(ofs, val)     OsRegisterWrite32(&pThis->os.regBaseDescr, (ofs), (val))



/**
 *  Offsets for different GPIO registers.
 */
static const GpioRegister gaRegConfigOffset[GPIO_MAX_NUMBER_OF_PINS] =
{
    GPIO_REG_CONFIG_0,
    GPIO_REG_CONFIG_1,
    GPIO_REG_CONFIG_2,
    GPIO_REG_CONFIG_3,
    GPIO_REG_CONFIG_4,
    GPIO_REG_CONFIG_5,
    GPIO_REG_CONFIG_6,
    GPIO_REG_CONFIG_7,
    GPIO_REG_CONFIG_8,
    GPIO_REG_CONFIG_9,
    GPIO_REG_CONFIG_10,
    GPIO_REG_CONFIG_11,
    GPIO_REG_CONFIG_12,
    GPIO_REG_CONFIG_13,
    GPIO_REG_CONFIG_14,
    GPIO_REG_CONFIG_15
};

static const GpioRegister gaRegPwmPrescalerOffset[GPIO_MAX_NUMBER_OF_PINS] =
{
    GPIO_REG_PWM_PRESCALER_0,
    GPIO_REG_PWM_PRESCALER_1,
    GPIO_REG_PWM_PRESCALER_2,
    GPIO_REG_PWM_PRESCALER_3,
    GPIO_REG_PWM_PRESCALER_4,
    GPIO_REG_PWM_PRESCALER_5,
    GPIO_REG_PWM_PRESCALER_6,
    GPIO_REG_PWM_PRESCALER_7,
    GPIO_REG_PWM_PRESCALER_8,
    GPIO_REG_PWM_PRESCALER_9,
    GPIO_REG_PWM_PRESCALER_10,
    GPIO_REG_PWM_PRESCALER_11,
    GPIO_REG_PWM_PRESCALER_12,
    GPIO_REG_PWM_PRESCALER_13,
    GPIO_REG_PWM_PRESCALER_14,
    GPIO_REG_PWM_PRESCALER_15
};

static const GpioRegister gaRegPwmPeriodOffset[GPIO_MAX_NUMBER_OF_PINS] =
{
    GPIO_REG_PWM_PERIOD_0,
    GPIO_REG_PWM_PERIOD_1,
    GPIO_REG_PWM_PERIOD_2,
    GPIO_REG_PWM_PERIOD_3,
    GPIO_REG_PWM_PERIOD_4,
    GPIO_REG_PWM_PERIOD_5,
    GPIO_REG_PWM_PERIOD_6,
    GPIO_REG_PWM_PERIOD_7,
    GPIO_REG_PWM_PERIOD_8,
    GPIO_REG_PWM_PERIOD_9,
    GPIO_REG_PWM_PERIOD_10,
    GPIO_REG_PWM_PERIOD_11,
    GPIO_REG_PWM_PERIOD_12,
    GPIO_REG_PWM_PERIOD_13,
    GPIO_REG_PWM_PERIOD_14,
    GPIO_REG_PWM_PERIOD_15
};





/**
 *  Obtain all GPIO configurations.
 *  This function can be called only during initialisation
 *  (i.e. no interrupts or any other kind of race condition).
 *  This is why no locks are obtained.
 * @param Pointer to a GPIO core object.
 * @return TRUE if at least one proper configuration is found.
 */
static BOOLEAN
ObtainAllConfigurations(
    GpioCore * const pThis);


/**
 *  Get a GPIO HW description.
 * @param Pointer to a GPIO core object.
 * @param Pointer to a location to receive a GPIO description.
 * @return OsStatus.
 */
static OsStatus
GetDescription(
    GpioCore        * const pThis,
    GpioDescription * const pGpioDescription);


/**
 *  Set a GPIO configuration.
 * @param Pointer to a GPIO core object.
 * @param Handle to an IOCTL request object.
 * @param Pointer to a GPIO configuration.
 * @return OsStatus.
 */
static OsStatus
SetConfiguration(
    GpioCore                * const pThis,
    OsIoctlHandle                   hIoctl,
    GpioConfiguration const * const pGpioConfiguration);


/**
 *  Get a GPIO configuration.
 * @param Pointer to a GPIO core object.
 * @param Pointer to a GPIO specifier.
 * @param Pointer to a location to receive a GPIO configuration.
 * @return OsStatus.
 */
static OsStatus
GetConfiguration(
    GpioCore            * const pThis,
    GpioSpecifier const * const pGpioSpecifier,
    GpioConfiguration   * const pGpioConfiguration);


/**
 *  Set all output GPIO values.
 * @param Pointer to a GPIO core object.
 * @param Pointer to output GPIO values to be applied.
 * @return OsStatus.
 */
static OsStatus
SetOutputValues(
    GpioCore               * const pThis,
    GpioOutputValues const * const pGpioOutputValues);


/**
 *  Get all input GPIO values.
 * @param Pointer to a GPIO core object.
 * @param Pointer to a location to receive current input GPIO values.
 * @return OsStatus.
 */
static OsStatus
GetInputValues(
    GpioCore        * const pThis,
    GpioInputValues * const pGpioInputValues);


/**
 *  Update current pending interrupt mask and disable interrupts that are to
 *  be handled.
 *  This function is called from Isr, so the interrupt lock is already acquired.
 * @param Pointer to a GPIO core object.
 * @return A mask representing newly detected interrupts.
 */
static OS_INLINE GpioPinMask
InterruptMaskUpdatePendingAndDisable(
    GpioCore * const pThis)
{
    GpioPinMask mask;
    GpioPinMask statusMask;

    PATH_TESTED();

    statusMask = REG_RD_32(GPIO_REG_GLOBAL_INTERRUPT_STATUS);

    mask = statusMask & 0xFF;
    if (mask != 0)
    {
        PATH_TESTED();
        REG_WR_32(GPIO_REG_INTERRUPT_DISABLE_0_7, mask);
        REG_WR_32(GPIO_REG_INTERRUPT_STATUS_0_7,  mask);
    }

    mask = statusMask >> 8;
    if (mask != 0)
    {
        PATH_TESTED();
        REG_WR_32(GPIO_REG_INTERRUPT_DISABLE_8_15, mask);
        REG_WR_32(GPIO_REG_INTERRUPT_STATUS_8_15,  mask);
    }

    // Do not update pendingMask if the interrupt is already
    // disabled, but it'd happned before it was disabled.
    pThis->hw.interruptPendingMask |= statusMask & pThis->hw.interruptEnabledMask;

    pThis->hw.interruptEnabledMask &= ~statusMask;

    return statusMask;
}



/**
 *  Get current pending interrupt mask and clear it.
 *  Interrupt lock is used to synchronize data/register access.
 *  This function is called from DpcFunc.
 * @param Pointer to a GPIO core object.
 * @return Pending interrupt mask.
 */
static OS_INLINE GpioPinMask
InterruptLockedGetAndClearPendingMask(
    GpioCore * const pThis)
{
    GpioPinMask pendingMask;

    PATH_TESTED();

    OsInterruptAcquireLock(pThis->os.hInterrupt);

    pendingMask = pThis->hw.interruptPendingMask;
    pThis->hw.interruptPendingMask = 0;

    OsInterruptReleaseLock(pThis->os.hInterrupt);

    return pendingMask;
}



/**
 *  Disable and clear pending interrupts for a specific GPIO pin.
 *  Interrupt lock is used to synchronize data/register access.
 *  This function is called from IoctlDispatch and IoctlCancel.
 * @param Pointer to a GPIO core object.
 * @param Pin number.
 */
static OS_INLINE void
InterruptLockedDisableAndClearPending(
    GpioCore * const pThis,
    GpioPinNumber    number)
{
    U32 regConfig;

    PATH_TESTED();

    OsInterruptAcquireLock(pThis->os.hInterrupt);

    regConfig =  REG_RD_32(gaRegConfigOffset[number])
               | GPIO_REG_CONFIG__INTERRUPT_DISABLE_BIT
               | GPIO_REG_CONFIG__INTERRUPT_STATUS_BIT;
    regConfig &= ~(  GPIO_REG_CONFIG__INTERRUPT_ENABLE_BIT
                   // Make sure wakeup status is not affected.
                   | GPIO_REG_CONFIG__WAKEUP_STATUS_BIT);
    REG_WR_32(gaRegConfigOffset[number], regConfig);

    pThis->hw.interruptPendingMask &= ~BIT_SEL(number);
    pThis->hw.interruptEnabledMask &= ~BIT_SEL(number);

    OsInterruptReleaseLock(pThis->os.hInterrupt);
}



/**
 *  Write a new config for a specific GPIO pin.
 *  Interrupt lock is used to synchronize data/register access.
 *  This function is called from IoctlDispatch.
 * @param Pointer to a GPIO core object.
 * @param Pin number specifying a config register.
 * @param Value to be written to the config register.
 */
static OS_INLINE void
InterruptLockedWriteConfig(
    GpioCore * const pThis,
    GpioPinNumber    number,
    U32              config)
{
    PATH_TESTED();

    PRECONDITION(!(config & GPIO_REG_CONFIG__INTERRUPT_STATUS_BIT));

    OsInterruptAcquireLock(pThis->os.hInterrupt);

    REG_WR_32(gaRegConfigOffset[number], config);

    switch (config & (  GPIO_REG_CONFIG__INTERRUPT_ENABLE_BIT
                      | GPIO_REG_CONFIG__INTERRUPT_DISABLE_BIT) )
    {
    case 0:
        // Nothing to do here.
        PATH_TESTED();
        break;

    case GPIO_REG_CONFIG__INTERRUPT_ENABLE_BIT:
        PATH_TESTED();
        pThis->hw.interruptEnabledMask |= BIT_SEL(number);
        break;

    case GPIO_REG_CONFIG__INTERRUPT_DISABLE_BIT:
        PATH_NOT_YET_TESTED();
        pThis->hw.interruptEnabledMask &= ~BIT_SEL(number);
        break;

    default:
        PRECONDITION(FALSE);
        break;
    }

    OsInterruptReleaseLock(pThis->os.hInterrupt);
}



/**
 *  Clear all wakeup status bits.
 *  Interrupt lock is used to synchronize data/register access.
 *  This function is called from different power management functions.
 * @param Pointer to a GPIO core object.
 */
static OS_INLINE void
InterruptLockedClearAllWakeupStatuses(
    GpioCore * const pThis)
{
    GpioPinNumber number;

    PATH_TESTED();

    OsInterruptAcquireLock(pThis->os.hInterrupt);

    for (number=0; number < pThis->hw.numberOfPins; number++)
    {
        U32 regConfig;

        PATH_TESTED();

        regConfig = REG_RD_32(gaRegConfigOffset[number]);
        regConfig |= GPIO_REG_CONFIG__WAKEUP_STATUS_BIT;
        // Make sure interrupt status is not affected.
        regConfig &= ~GPIO_REG_CONFIG__INTERRUPT_STATUS_BIT;
        REG_WR_32(gaRegConfigOffset[number], regConfig);
    }

    OsInterruptReleaseLock(pThis->os.hInterrupt);
}



/**
 *  Construct a GPIO core object.
 * @param GPIO core parent device handle.
 * @param Callback function that cancels the I/O request.
 * @return A pointer to newly created GPIO core object, or NULL on failure.
 */
GpioCore *
GpioCoreConstruct(
    OsDeviceHandle        hDevice,
    OsIoctlCancelCallback pfIoctlCancel)
{
    GpioCore *       pThis = NULL;
    OsSpinLockHandle hSpinLock;

    PATH_TESTED();

    PRECONDITION(hDevice != OS_INVALID_HANDLE);
    PRECONDITION(pfIoctlCancel != NULL);
    
    hSpinLock = OsSpinLockConstruct(hDevice);
    if (hSpinLock != OS_INVALID_HANDLE)
    {
        PATH_TESTED();

        pThis = (GpioCore *)OsMemoryBufferAllocate(
            sizeof(GpioCore),
            OS_MEMORY_TYPE__NON_PAGED,
            OS_MEMORY_DEFAULT_TAG);

        if (pThis != NULL)
        {
            GpioPinNumber n;

            PATH_TESTED();

            pThis->hw.numberOfPins          = 0;
            pThis->hw.inputMask             = 0;
            pThis->hw.outputMask            = 0;
            pThis->hw.outputDrainMask       = 0;
            pThis->hw.pwmOutputMask         = 0;
            pThis->hw.outputValues          = 0;
            pThis->hw.interruptEnabledMask  = 0;
            pThis->hw.interruptPendingMask  = 0;

            pThis->os.hDevice               = hDevice;
            pThis->os.hSpinLock             = hSpinLock;
            pThis->os.hInterrupt            = OS_INVALID_HANDLE;
            pThis->os.pfIoctlCancel         = pfIoctlCancel;
            for (n=0; n < GPIO_MAX_NUMBER_OF_PINS; n++)
            {
                PATH_TESTED();
                pThis->os.ahPendingIoctl[n] = OS_INVALID_HANDLE;
            }
            OsRegBaseInit(&pThis->os.regBaseDescr, NULL);

            pThis->bInitialStateObtained = FALSE;
            pThis->bHwResourcesAssigned  = FALSE;
        }
        else
        {
            PATH_TESTED();
            OsSpinLockDestruct(hSpinLock);
        }
    }
    else
    {
        PATH_TESTED();
    }

    return pThis;
}



/**
 *  Destruct a GPIO core object.
 * @return A pointer to a GPIO core object.
 */
void
GpioCoreDestruct(
    GpioCore * const pThis)
{
    GpioPinNumber  n;

    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pThis->os.hDevice   != OS_INVALID_HANDLE);
    PRECONDITION(pThis->os.hSpinLock != OS_INVALID_HANDLE);
    PRECONDITION(OS_INVALID_HANDLE == pThis->os.hInterrupt);
    PRECONDITION(pThis->os.pfIoctlCancel != NULL);
    for (n=0; n < GPIO_MAX_NUMBER_OF_PINS; n++)
    {
        PATH_TESTED();
        PRECONDITION(OS_INVALID_HANDLE == pThis->os.ahPendingIoctl[n]);
    }
    PRECONDITION(!pThis->bHwResourcesAssigned);

    pThis->os.hDevice = OS_INVALID_HANDLE;

    OsSpinLockDestruct(pThis->os.hSpinLock);
    pThis->os.hSpinLock = OS_INVALID_HANDLE;

    OsMemoryBufferFree(pThis);
}



/**
 *  Assign HW resources to a GPIO core object.
 * @param Pointer to a GPIO core object.
 * @param Pointer to a device register base descriptor.
 * @param An associated interrupt handle.
 * @return OsStatus.
 */
OsStatus
GpioCoreAssignHwResources(
    GpioCore            * const pThis,
    OsRegBaseDescriptor * const pRegBaseDescr,
    OsInterruptHandle           hInterrupt)
{
    OsStatus osStatus = OS_STATUS__INVALID_RESOURCES;

    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(!pThis->bHwResourcesAssigned);
    PRECONDITION(pRegBaseDescr != NULL);

    if (   (GPIO_REGISTER_SPAN == OsRegBaseGetLength(pRegBaseDescr))
        && (hInterrupt != OS_INVALID_HANDLE) )
    {
        PATH_TESTED();
        pThis->os.regBaseDescr = *pRegBaseDescr;

        osStatus = OsRegBaseMap(&pThis->os.regBaseDescr);
        if (OS_STATUS__OK == osStatus)
        {
            PATH_TESTED();

            pThis->os.hInterrupt = hInterrupt;
            pThis->bHwResourcesAssigned = TRUE;

            if (!pThis->bInitialStateObtained)
            {
                PATH_TESTED();
                if (ObtainAllConfigurations(pThis))
                {
                    PATH_TESTED();
                    pThis->bInitialStateObtained = TRUE;
                }
                else
                {
                    PATH_TESTED();
                    pThis->bHwResourcesAssigned = FALSE;
                    pThis->os.hInterrupt = OS_INVALID_HANDLE;
                    OsRegBaseUnmap(&pThis->os.regBaseDescr);
                    osStatus = OS_STATUS__INVALID_RESOURCES;
                }
            }
        }
    }

    return osStatus;
}



/**
 *  Release HW resources from a GPIO core object.
 * @param Pointer to a GPIO core object.
 */
void
GpioCoreReleaseHwResources(
    GpioCore * const pThis)
{
    PATH_TESTED();

    PRECONDITION(pThis != NULL);

    if (pThis->bHwResourcesAssigned)
    {
        PATH_TESTED();
        OsRegBaseUnmap(&pThis->os.regBaseDescr);
        pThis->hw.numberOfPins      = 0;
        pThis->os.hInterrupt        = OS_INVALID_HANDLE;
        pThis->bHwResourcesAssigned = FALSE;
    }
}



/**
 *  Interrupt service routine.
 * @param Pointer to a GPIO core object.
 * @return TRUE if the function services the interrupt, FALSE otherwise.
 */
BOOLEAN
GpioCoreIsr(
    GpioCore * const pThis)
{
    GpioPinMask irqMask;

    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pThis->bHwResourcesAssigned);

    irqMask = InterruptMaskUpdatePendingAndDisable(pThis);
/*
    OsTrace(OS_LEVEL_VERBOSE,
            OS_TRACE_FLAG_INTERRUPT,
            "ISR irqMask=0x%08X\n",
            irqMask);
*/
    return (irqMask != 0);
}



/**
 *  Deferred interrupt processing.
 * @param Pointer to a GPIO core object.
 */
void
GpioCoreDpcFunc(
    GpioCore * const pThis)
{
    GpioPinNumber number = 0;
    GpioPinMask   pendingMask;
    GpioPinMask   shiftMask;

    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pThis->bHwResourcesAssigned);

    OsSpinLockAcquire(pThis->os.hSpinLock);

    pendingMask = InterruptLockedGetAndClearPendingMask(pThis);

    for (shiftMask = pendingMask; shiftMask != 0; shiftMask >>= 1, number++)
    {
        PATH_TESTED();
        if (shiftMask & BIT_SEL(0))
        {
            OsIoctlHandle hIoctl;

            PATH_TESTED();

            hIoctl = pThis->os.ahPendingIoctl[number];
            if (hIoctl != OS_INVALID_HANDLE)
            {
                PATH_TESTED();
                pThis->hw.aPin[number].iwMode = (GpioInterruptAndWakeupMode)
                    (pThis->hw.aPin[number].iwMode & (~IW_MODE_INTERRUPT_ENABLED));
                pThis->os.ahPendingIoctl[number] = OS_INVALID_HANDLE;
                OsIoctlCompletePending(hIoctl, OS_STATUS__OK, 0);
            }
        }
    }

    OsSpinLockRelease(pThis->os.hSpinLock);

    OsTrace(OS_LEVEL_VERBOSE,
            TRACE_FLAG_DPC,
            "DPC 0x%08X\n",
            pendingMask);
}



/**
 *  Enable HW interrupts.
 *  The interrupt lock must already be acquired before calling this function.
 * @param Pointer to a GPIO core object.
 */
void
GpioCoreInterruptEnable(
    GpioCore * const pThis)
{
    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pThis->bHwResourcesAssigned);

    OsTrace(OS_LEVEL_VERBOSE,
            OS_TRACE_FLAG_INTERRUPT,
            "IRQ Enable\n");

    REG_WR_32(GPIO_REG_GLOBAL_INTERRUPT_ENABLE, ~0);
}



/**
 *  Disable HW interrupts.
 *  The interrupt lock must already be acquired before calling this function.
 * @param Pointer to a GPIO core object.
 */
void
GpioCoreInterruptDisable(
    GpioCore * const pThis)
{
    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pThis->bHwResourcesAssigned);

    REG_WR_32(GPIO_REG_GLOBAL_INTERRUPT_DISABLE, ~0);

    OsTrace(OS_LEVEL_VERBOSE,
            OS_TRACE_FLAG_INTERRUPT,
            "IRQ Disable\n");
}



/**
 *  The following automates IOCTL parameter checking.
 *  It is used from GpioCoreIoctlDispatch().
 *  If we were only allowed to use templates here...
 */
typedef struct
{
    GpioIoControlCode   ioControlCode;
    size_t              requiredInputLength;
    U16                 inputAlignmentMask;
    size_t              requiredOutputLength;
    U16                 outputAlignmentMask;

} IoctlBufferDescriptor;

static IoctlBufferDescriptor const gaIoctlBufDescr[NUMBER_OF_IO_CONTROL_CODES] =
{
    {
        GET_DESCRIPTION,
        0,
        0,
        sizeof(GpioDescription),
        OS_GET_ALIGNMENT(GpioDescription)   - 1
    },
    {
        SET_CONFIGURATION,
        sizeof(GpioConfiguration),
        OS_GET_ALIGNMENT(GpioConfiguration) - 1,
        0,
        0
    },
    {
        GET_CONFIGURATION,
        sizeof(GpioSpecifier),
        OS_GET_ALIGNMENT(GpioSpecifier)     - 1,
        sizeof(GpioConfiguration),
        OS_GET_ALIGNMENT(GpioConfiguration) - 1
    },
    {
        SET_OUTPUT_VALUES,
        sizeof(GpioOutputValues),
        OS_GET_ALIGNMENT(GpioOutputValues)  - 1,
        0,
        0
    },
    {
        GET_INPUT_VALUES,
        0,
        0,
        sizeof(GpioInputValues),
        OS_GET_ALIGNMENT(GpioInputValues)   - 1
    }
};

static OS_INLINE BOOLEAN
CheckIoctlBuffers(
    IoctlBufferDescriptor const * const pIoctlBufDescr,
    void                  const * const pInput,
    size_t                              inputLength,
    void                        * const pOutput,
    size_t                              outputLength,
    size_t                      * const pBytesReturned)
{
    BOOLEAN bRtn;

    PATH_TESTED();

    PRECONDITION(pIoctlBufDescr->ioControlCode < NUMBER_OF_IO_CONTROL_CODES);

    bRtn =   (pIoctlBufDescr->requiredInputLength == inputLength)
          && (0 == (((UADDR)pInput)  & pIoctlBufDescr->inputAlignmentMask))
          && (pIoctlBufDescr->requiredOutputLength == outputLength)
          && (0 == (((UADDR)pOutput) & pIoctlBufDescr->outputAlignmentMask));

    if (bRtn)
    {
        PATH_TESTED();
        *pBytesReturned = pIoctlBufDescr->requiredOutputLength;
    }
    else
    {
        PATH_TESTED();
        OsTrace(OS_LEVEL_ERROR,
                OS_TRACE_FLAG_IOCTLS,
                "I/O CTRL %u error: illegal IOCTL buffer\n",
                pIoctlBufDescr->ioControlCode);
    }

    return bRtn;
}

#define CHECK_IOCTL_BUFFERS()               \
    CheckIoctlBuffers(                      \
        &gaIoctlBufDescr[ioControlCode],    \
        pInput,                             \
        inputLength,                        \
        pOutput,                            \
        outputLength,                       \
        pBytesReturned)

/**
 *  Hanlde I/O control requests.
 * @param Pointer to a GPIO core object.
 * @param Handle to an IOCTL request object.
 *        If OS_INVALID_HANDLE, the function will never return OS_STATUS__PENDING.
 * @param I/O control code.
 * @param Pointer to an input buffer.
 * @param Input buffer size.
 * @param Pointer to an output buffer.
 * @param Output buffer size.
 * @param The number of bytes returned in output buffer.
 * @return OsStatus.
 */
OsStatus
GpioCoreIoctlDispatch(
    GpioCore * const    pThis,
    OsIoctlHandle       hIoctl,
    GpioIoControlCode   ioControlCode,
    void const * const  pInput,
    size_t              inputLength,
    void * const        pOutput,
    size_t              outputLength,
    size_t * const      pBytesReturned)
{
    BOOLEAN  bBufferOk;
    OsStatus osStatus = OS_STATUS__INVALID_PARAMETER;

    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pBytesReturned != NULL);
    bBufferOk =   ((0 == inputLength)  && (NULL == pInput))
               || ((0 != inputLength)  && (NULL != pInput));
    PRECONDITION(bBufferOk);
    bBufferOk =   ((0 == outputLength) && (NULL == pOutput))
               || ((0 != outputLength) && (NULL != pOutput));
    PRECONDITION(bBufferOk);
    PRECONDITION(pThis->bHwResourcesAssigned);

    OsTrace(OS_LEVEL_VERBOSE,
            OS_TRACE_FLAG_IOCTLS,
            "I/O CTRL %u (size in/out=%u/%u) [GpioCore=0x%p]\n",
            ioControlCode,
            inputLength,
            outputLength,
            pThis);

    switch (ioControlCode)
    {
    case GET_DESCRIPTION:
        PATH_TESTED();
        if (CHECK_IOCTL_BUFFERS())
        {
            PATH_TESTED();
            osStatus = GetDescription(
                pThis,
                (GpioDescription *)pOutput);
        }
        break;

    case SET_CONFIGURATION:
        PATH_TESTED();
        if (CHECK_IOCTL_BUFFERS())
        {
            PATH_TESTED();
            osStatus = SetConfiguration(
                pThis,
                hIoctl,
                (GpioConfiguration *)pInput);
        }
        break;

    case GET_CONFIGURATION:
        PATH_TESTED();
        if (CHECK_IOCTL_BUFFERS())
        {
            PATH_TESTED();
            osStatus = GetConfiguration(
                pThis,
                (GpioSpecifier *)pInput,
                (GpioConfiguration *)pOutput);
        }
        break;

    case SET_OUTPUT_VALUES:
        PATH_TESTED();
        if (CHECK_IOCTL_BUFFERS())
        {
            PATH_TESTED();
            osStatus = SetOutputValues(
                pThis,
                (GpioOutputValues *)pInput);
        }
        break;

    case GET_INPUT_VALUES:
        PATH_TESTED();
        if (CHECK_IOCTL_BUFFERS())
        {
            PATH_TESTED();
            osStatus = GetInputValues(
                pThis,
                (GpioInputValues *)pOutput);
        }
        break;

    default:
        PATH_NOT_YET_TESTED();
        osStatus = OS_STATUS__INVALID_IOCTL;
        OsTrace(OS_LEVEL_ERROR,
                OS_TRACE_FLAG_IOCTLS,
                "I/O CTRL %u error: unrecognized\n",
                ioControlCode);
        break;
    }

    if (osStatus != OS_STATUS__PENDING)
    {
        PATH_TESTED();
        if (hIoctl != OS_INVALID_HANDLE)
        {
            PATH_TESTED();
            OsIoctlComplete(hIoctl, osStatus, *pBytesReturned);
        }
    }
    else
    {
        EXPECT(hIoctl != OS_INVALID_HANDLE);
    }

    return osStatus;
}



/**
 *  Cancel I/O control requests.
 * @param Pointer to a GPIO core object.
 * @param Handle to an IOCTL request object.
 */
void
GpioCoreIoctlCancel(
    GpioCore * const pThis,
    OsIoctlHandle    hIoctlToCancel)
{
    GpioPinNumber number;
    BOOLEAN       bCancel = FALSE;

    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pThis->bHwResourcesAssigned);

    OsSpinLockAcquire(pThis->os.hSpinLock);

    for (number=0; number < pThis->hw.numberOfPins; number++)
    {
        OsIoctlHandle hIoctl;

        PATH_TESTED();
        
        hIoctl = pThis->os.ahPendingIoctl[number];
        if (hIoctl == hIoctlToCancel)
        {
            PATH_TESTED();
            PRECONDITION(pThis->bHwResourcesAssigned);
            InterruptLockedDisableAndClearPending(pThis, number);
            pThis->hw.aPin[number].iwMode = (GpioInterruptAndWakeupMode)
                (pThis->hw.aPin[number].iwMode & (~IW_MODE_INTERRUPT_ENABLED));
            pThis->os.ahPendingIoctl[number] = OS_INVALID_HANDLE;
            bCancel = TRUE;
            break;
        }
    }

    OsSpinLockRelease(pThis->os.hSpinLock);

    if (bCancel)
    {
        PATH_TESTED();
        OsIoctlCancelPending(hIoctlToCancel);
        OsTrace(OS_LEVEL_VERBOSE,
                OS_TRACE_FLAG_IOCTLS,
                "SET_CONFIGURATION %u cancelled\n",
                number);
    }
}



/**
 *  Handle Dx->D0 transitions.
 *  This function can be called only during Dx->D0 transitions, when
 *  no interrupts are enabled (i.e. no need to obtain interrupt locks).
 * @param Pointer to a GPIO core object.
 * @param Device power state which the device is about to leave.
 */
void
GpioCoreD0Entry(
    GpioCore * const   pThis,
    OsPowerDeviceState oldDx)
{
    GpioPinNumber number;

    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pThis->bHwResourcesAssigned);
    PRECONDITION(oldDx >= OS_POWER_DEVICE_STATE__MIN);
    PRECONDITION(oldDx <= OS_POWER_DEVICE_STATE__MAX);

    OsSpinLockAcquire(pThis->os.hSpinLock);

    /*
     *  The device is leaving Dx, so restore all the registers.
     *  Note that this is not really necessary as only the oldest
     *  ASIC/FPGA revisions did not preserve a complete context.
     */
    for (number=0; number < pThis->hw.numberOfPins; number++)
    {
        U32 regConfig;
        U32 regLowHigh;
        U32 regPrescaler;
        GpioPin const * const pGpioPin = &pThis->hw.aPin[number];

        PATH_TESTED();

        regConfig =
            (  (((U32)pGpioPin->pinMode) << GPIO_REG_CONFIG__PIN_MODE_SHIFT)
             & GPIO_REG_CONFIG__PIN_MODE_MASK)
          | (  (((U32)pGpioPin->iwMode)  << GPIO_REG_CONFIG__IW_MODE_ENABLE_SHIFT )
             & GPIO_REG_CONFIG__IW_MODE_ENABLE_MASK)
          | ((pThis->hw.outputValues & BIT_SEL(number)) ? GPIO_REG_CONFIG__OUTPUT_VALUE_BIT : 0);

        regLowHigh  =  (((U32)pGpioPin->pwmTiming.low) << GPIO_REG_PWM_PERIOD__LOW_SHIFT)
                     & GPIO_REG_PWM_PERIOD__LOW_MASK;

        regLowHigh |=  (((U32)pGpioPin->pwmTiming.high) << GPIO_REG_PWM_PERIOD__HIGH_SHIFT)
                     & GPIO_REG_PWM_PERIOD__HIGH_MASK;

        regPrescaler  =  (((U32)pGpioPin->pwmTiming.prescaler) << GPIO_REG_PWM_PRESCALER__SHIFT)
                       & GPIO_REG_PWM_PRESCALER__MASK;

        regPrescaler |=  (PWM_LOG2_DIVIDER_16 == pGpioPin->pwmTiming.log2Divider)
                       ? GPIO_REG_PWM_PRESCALER__LOG2_FREQ_DIV_16_BIT
                       : 0;

        REG_WR_32(gaRegPwmPeriodOffset[number],    regLowHigh);
        REG_WR_32(gaRegPwmPrescalerOffset[number], regPrescaler);
        REG_WR_32(gaRegConfigOffset[number],       regConfig);
    }

    OsSpinLockRelease(pThis->os.hSpinLock);
}



/**
 *  Handle D0->Dx transitions.
 *  This function can be called only during D0->Dx transitions, when
 *  no interrupts are enabled (i.e. no need to obtain interrupt locks).
 * @param Pointer to a GPIO core object.
 * @param Device power state which the device is about to enter.
 */
void
GpioCoreD0Exit(
    GpioCore * const   pThis,
    OsPowerDeviceState newDx)
{
    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pThis->bHwResourcesAssigned);
    PRECONDITION(newDx >= OS_POWER_DEVICE_STATE__MIN);
    PRECONDITION(newDx <= OS_POWER_DEVICE_STATE__MAX);

    // Nothing to do here.
}



/**
 *  Arm wakeups from Dy+S0.
 * @param Pointer to a GPIO core object.
 */
void
GpioCoreArmWakeFromS0(
    GpioCore * const pThis)
{
    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pThis->bHwResourcesAssigned);

    InterruptLockedClearAllWakeupStatuses(pThis);
    REG_WR_32(GPIO_REG_GLOBAL_WAKEUP_ENABLE, ~0);
}



/**
 *  Disarm wakeups from Dy+S0.
 * @param Pointer to a GPIO core object.
 */
void
GpioCoreDisarmWakeFromS0(
    GpioCore * const pThis)
{
    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pThis->bHwResourcesAssigned);

    REG_WR_32(GPIO_REG_GLOBAL_WAKEUP_DISABLE, ~0);
}



/**
 *  Handle wakeups from Dy+S0.
 * @param Pointer to a GPIO core object.
 */
void
GpioCoreWakeFromS0Triggered(
    GpioCore * const pThis)
{
    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pThis->bHwResourcesAssigned);

    InterruptLockedClearAllWakeupStatuses(pThis);
}



/**
 *  Arm wakeups from Dy+Sx.
 * @param Pointer to a GPIO core object.
 */
void
GpioCoreArmWakeFromSx(
    GpioCore * const pThis)
{
    PATH_TESTED();
    // S0 and Sx are handled the same way.
    GpioCoreArmWakeFromS0(pThis);
}



/**
 *  Disarm wakeups from Dy+Sx.
 * @param Pointer to a GPIO core object.
 */
void
GpioCoreDisarmWakeFromSx(
    GpioCore * const pThis)
{
    PATH_TESTED();
    // S0 and Sx are handled the same way.
    GpioCoreDisarmWakeFromS0(pThis);
}



/**
 *  Handle wakeups from Dy+Sx.
 * @param Pointer to a GPIO core object.
 */
void
GpioCoreWakeFromSxTriggered(
    GpioCore * const pThis)
{
    PATH_TESTED();
    // S0 and Sx are handled the same way.
    GpioCoreWakeFromS0Triggered(pThis);
}



/**
 *  Obtain all GPIO configurations.
 *  This function can be called only during initialisation
 *  (i.e. no interrupts or any other kind of race condition).
 *  This is why no locks are obtained.
 * @param Pointer to a GPIO core object.
 * @return TRUE if at least one proper configuration is found.
 */
static BOOLEAN
ObtainAllConfigurations(
    GpioCore * const pThis)
{
    U32           id;
    GpioPinNumber number;

    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pThis->bHwResourcesAssigned);

    pThis->hw.numberOfPins = 0;

    id = REG_RD_32(GPIO_REG_ID);
    if (GPIO_BAR_ID == id)
    {
        GpioPinNumber numberOfPins;

        PATH_TESTED();

        numberOfPins = (GpioPinNumber)REG_RD_32(GPIO_REG_NUMBER_OF_PINS);

        if (   (numberOfPins > 0)
            && (numberOfPins <= GPIO_MAX_NUMBER_OF_PINS) )
        {
            PATH_TESTED();
            OsTrace(OS_LEVEL_INFORMATION,
                    OS_TRACE_FLAG_PNP,
                    "%u GPIO pins found\n",
                    numberOfPins);
            pThis->hw.numberOfPins = numberOfPins;
        }
        else
        {
            PATH_TESTED();
            OsTrace(OS_LEVEL_ERROR,
                    OS_TRACE_FLAG_PNP,
                    "Invalid number of GPIO pins %u\n",
                    numberOfPins);
        }
    }
    else
    {
        PATH_TESTED();
        OsTrace(OS_LEVEL_ERROR,
                OS_TRACE_FLAG_PNP,
                "Invalid BAR ID 0x%08X\n",
                id);
    }


    for (number=0; number < pThis->hw.numberOfPins; number++)
    {
        U32 regConfig;
        U32 regLowHigh;
        U32 regPrescaler;

        PATH_TESTED();

        // No lock required here (see the function comment above).
        regConfig    = REG_RD_32(gaRegConfigOffset[number]);
        regLowHigh   = REG_RD_32(gaRegPwmPeriodOffset[number]);
        regPrescaler = REG_RD_32(gaRegPwmPrescalerOffset[number]);

        pThis->hw.aPin[number].pinMode = (GpioPinMode)
            (   (regConfig & GPIO_REG_CONFIG__PIN_MODE_MASK)
             >> GPIO_REG_CONFIG__PIN_MODE_SHIFT);
        pThis->hw.aPin[number].iwMode = (GpioInterruptAndWakeupMode)
            (   (regConfig & GPIO_REG_CONFIG__IW_MODE_ENABLE_MASK)
             >> GPIO_REG_CONFIG__IW_MODE_ENABLE_SHIFT);
        pThis->hw.aPin[number].pwmTiming.low =
            (   (regLowHigh & GPIO_REG_PWM_PERIOD__LOW_MASK)
             >> GPIO_REG_PWM_PERIOD__LOW_SHIFT);
        pThis->hw.aPin[number].pwmTiming.high =
            (   (regLowHigh & GPIO_REG_PWM_PERIOD__HIGH_MASK)
             >> GPIO_REG_PWM_PERIOD__HIGH_SHIFT);
        pThis->hw.aPin[number].pwmTiming.prescaler =
            (   (regPrescaler & GPIO_REG_PWM_PRESCALER__MASK)
             >> GPIO_REG_PWM_PRESCALER__SHIFT);
        pThis->hw.aPin[number].pwmTiming.log2Divider =
              (regPrescaler & GPIO_REG_PWM_PRESCALER__LOG2_FREQ_DIV_16_BIT)
            ? PWM_LOG2_DIVIDER_16
            : PWM_LOG2_DIVIDER_0;

        switch (pThis->hw.aPin[number].pinMode)
        {
        case PIN_MODE_INPUT:
            PATH_TESTED();
            pThis->hw.inputMask       |=  BIT_SEL(number);
            pThis->hw.outputMask      &= ~BIT_SEL(number);
            pThis->hw.outputDrainMask &= ~BIT_SEL(number);
            pThis->hw.pwmOutputMask   &= ~BIT_SEL(number);
            break;

        case PIN_MODE_OUTPUT:
            PATH_TESTED();
            pThis->hw.inputMask       &= ~BIT_SEL(number);
            pThis->hw.outputMask      |=  BIT_SEL(number);
            pThis->hw.outputDrainMask &= ~BIT_SEL(number);
            pThis->hw.pwmOutputMask   &= ~BIT_SEL(number);
            break;

        case PIN_MODE_OPEN_DRAIN:
            PATH_TESTED();
            pThis->hw.inputMask       &= ~BIT_SEL(number);
            pThis->hw.outputMask      &= ~BIT_SEL(number);
            pThis->hw.outputDrainMask |=  BIT_SEL(number);
            pThis->hw.pwmOutputMask   &= ~BIT_SEL(number);
            break;

        case PIN_MODE_PWM_OUTPUT:
            PATH_TESTED();
            pThis->hw.inputMask       &= ~BIT_SEL(number);
            pThis->hw.outputMask      &= ~BIT_SEL(number);
            pThis->hw.outputDrainMask &= ~BIT_SEL(number);
            pThis->hw.pwmOutputMask   |=  BIT_SEL(number);
            break;

        default:
            PRECONDITION(FALSE);
            break;
        }

        if (regConfig & GPIO_REG_CONFIG__OUTPUT_VALUE_BIT)
        {
            PATH_TESTED();
            pThis->hw.outputValues |= BIT_SEL(number);
        }
        else
        {
            PATH_TESTED();
            pThis->hw.outputValues &= ~BIT_SEL(number);
        }
    }

    return (pThis->hw.numberOfPins > 0);
}



/**
 *  Get a GPIO HW description.
 * @param Pointer to a GPIO core object.
 * @param Pointer to a location to receive a GPIO description.
 * @return OsStatus.
 */
static OsStatus
GetDescription(
    GpioCore        * const pThis,
    GpioDescription * const pGpioDescription)
{
    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pThis->bHwResourcesAssigned);
    PRECONDITION(pGpioDescription != NULL);

    pGpioDescription->version      = GPIO_VERSION_1;
    pGpioDescription->numberOfPins = pThis->hw.numberOfPins;
    pGpioDescription->pwmFrequency = GPIO_PWM_FREQUENCY;

    OsTrace(OS_LEVEL_VERBOSE,
            OS_TRACE_FLAG_IOCTLS,
            "GET_DESCRIPTION version=%u, numberOfPins=%u, f=%uHz\n",
            pGpioDescription->version,
            pGpioDescription->numberOfPins,
            pGpioDescription->pwmFrequency);

    return OS_STATUS__OK;
}



/**
 *  Set a GPIO configuration.
 * @param Pointer to a GPIO core object.
 * @param Handle to an IOCTL request object.
 * @param Pointer to a GPIO configuration.
 * @return OsStatus.
 */
static OsStatus
SetConfiguration(
    GpioCore                * const pThis,
    OsIoctlHandle                   hIoctl,
    GpioConfiguration const * const pGpioConfiguration)
{
    GpioPinNumber number;
    OsStatus      osStatus = OS_STATUS__INVALID_PARAMETER;

    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pThis->bHwResourcesAssigned);
    PRECONDITION(pGpioConfiguration != NULL);

    number = pGpioConfiguration->specifier.number;
    if (number < pThis->hw.numberOfPins)
    {
        GpioPin *                   pGpioPin;
        GpioPinMode                 pinMode;
        GpioInterruptAndWakeupMode  iwMode;
        GpioPinValue                pinValue;
        GpioPwmPeriod               low;
        GpioPwmPeriod               high;
        GpioPwmPrescaler            prescaler;
        GpioPwmLog2Divider          log2Divider;
        U32                         regConfig;
        U32                         regLowHigh;
        U32                         regPrescaler;
        BOOLEAN                     bOverrun = FALSE;
        
        PATH_TESTED();

        pGpioPin    = &pThis->hw.aPin[number];

        pinMode     = (GpioPinMode)(pGpioConfiguration->pinMode & PIN_MODE_MASK);
        iwMode      = (GpioInterruptAndWakeupMode)(pGpioConfiguration->iwMode & INTERRUPT_WAKEUP_MASK);
        pinValue    = (GpioPinValue)(pGpioConfiguration->pinValue & PIN_VALUE_MASK);

        low         = pGpioConfiguration->pwmTiming.low       & PWM_PERIOD_MASK;
        high        = pGpioConfiguration->pwmTiming.high      & PWM_PERIOD_MASK;
        prescaler   = pGpioConfiguration->pwmTiming.prescaler & PWM_PRESCALER_MASK;
        log2Divider = (GpioPwmLog2Divider)pGpioConfiguration->pwmTiming.log2Divider;

        if (OS_INVALID_HANDLE == hIoctl)
        {
            PATH_TESTED();
            // Do not wait, i.e. no do not return OS_STATUS__PENDING.
            iwMode = (GpioInterruptAndWakeupMode)(iwMode & ~IW_MODE_INTERRUPT_ENABLED);
        }

        regConfig   =
            (  (((U32)pinMode) << GPIO_REG_CONFIG__PIN_MODE_SHIFT)
             & GPIO_REG_CONFIG__PIN_MODE_MASK)
          | (  (((U32)iwMode)  << GPIO_REG_CONFIG__IW_MODE_ENABLE_SHIFT )
             & GPIO_REG_CONFIG__IW_MODE_ENABLE_MASK)
          | ((PIN_VALUE_HIGH == pinValue) ? GPIO_REG_CONFIG__OUTPUT_VALUE_BIT : 0);

        regLowHigh  =  (((U32)low) << GPIO_REG_PWM_PERIOD__LOW_SHIFT)
                     & GPIO_REG_PWM_PERIOD__LOW_MASK;

        regLowHigh |=  (((U32)high) << GPIO_REG_PWM_PERIOD__HIGH_SHIFT)
                     & GPIO_REG_PWM_PERIOD__HIGH_MASK;

        regPrescaler  =  (((U32)prescaler) << GPIO_REG_PWM_PRESCALER__SHIFT)
                       & GPIO_REG_PWM_PRESCALER__MASK;

        regPrescaler |=  (PWM_LOG2_DIVIDER_16 == log2Divider)
                       ? GPIO_REG_PWM_PRESCALER__LOG2_FREQ_DIV_16_BIT
                       : 0;


        OsSpinLockAcquire(pThis->os.hSpinLock);

        InterruptLockedDisableAndClearPending(pThis, number);

        // Complete previously pending IOCTL.
        if (pThis->os.ahPendingIoctl[number] != OS_INVALID_HANDLE)
        {
            PATH_TESTED();
            OsIoctlCompletePending(
                pThis->os.ahPendingIoctl[number],
                OS_STATUS__OVERRUN,
                0);
            pThis->os.ahPendingIoctl[number] = OS_INVALID_HANDLE;
            bOverrun = TRUE;
        }

        switch (pinMode)
        {
        case PIN_MODE_INPUT:
            PATH_TESTED();
            pThis->hw.inputMask       |=  BIT_SEL(number);
            pThis->hw.outputMask      &= ~BIT_SEL(number);
            pThis->hw.outputDrainMask &= ~BIT_SEL(number);
            pThis->hw.pwmOutputMask   &= ~BIT_SEL(number);
            osStatus = OS_STATUS__OK;
            break;

        case PIN_MODE_OUTPUT:
            PATH_TESTED();
            pThis->hw.inputMask       &= ~BIT_SEL(number);
            pThis->hw.outputMask      |=  BIT_SEL(number);
            pThis->hw.outputDrainMask &= ~BIT_SEL(number);
            pThis->hw.pwmOutputMask   &= ~BIT_SEL(number);
            osStatus = OS_STATUS__OK;
            break;

        case PIN_MODE_OPEN_DRAIN:
            PATH_TESTED();
            pThis->hw.inputMask       &= ~BIT_SEL(number);
            pThis->hw.outputMask      &= ~BIT_SEL(number);
            pThis->hw.outputDrainMask |=  BIT_SEL(number);
            pThis->hw.pwmOutputMask   &= ~BIT_SEL(number);
            osStatus = OS_STATUS__OK;
            break;

        case PIN_MODE_PWM_OUTPUT:
            PATH_TESTED();
            if (log2Divider <= PWM_LOG2_DIVIDER_MAX)
            {
                PATH_TESTED();
                pThis->hw.inputMask       &= ~BIT_SEL(number);
                pThis->hw.outputMask      &= ~BIT_SEL(number);
                pThis->hw.outputDrainMask &= ~BIT_SEL(number);
                pThis->hw.pwmOutputMask   |=  BIT_SEL(number);
                osStatus = OS_STATUS__OK;
            }
            break;

        default:
            PRECONDITION(FALSE);
            break;
        }

        if (OS_STATUS__OK == osStatus)
        {
            PATH_TESTED();
            pGpioPin->pinMode               = pinMode;
            pGpioPin->iwMode                = iwMode;
            pGpioPin->pwmTiming.low         = low;
            pGpioPin->pwmTiming.high        = high;
            pGpioPin->pwmTiming.prescaler   = prescaler;
            pGpioPin->pwmTiming.log2Divider = log2Divider;

            if (PIN_VALUE_HIGH == pinValue)
            {
                PATH_TESTED();
                pThis->hw.outputValues |= BIT_SEL(number);
            }
            else
            {
                PATH_TESTED();
                pThis->hw.outputValues &= ~BIT_SEL(number);
            }

            if (iwMode & IW_MODE_INTERRUPT_ENABLED)
            {
                PATH_TESTED();
                PRECONDITION(hIoctl != OS_INVALID_HANDLE);
                pThis->os.ahPendingIoctl[number] = hIoctl;
                // Pending I/O Controls will prevent the device transiting into a low
                // power state and they should be handled properly e.g. cancelled or
                // completed before going to sleep. In WDF this is done by EvtIoStop
                // and EvtIoResume callback functions.
                OsIoctlMarkPendingAndCancellable(hIoctl, pThis->os.pfIoctlCancel);
                osStatus = OS_STATUS__PENDING;
            }

            REG_WR_32(gaRegPwmPeriodOffset[number], regLowHigh);
            REG_WR_32(gaRegPwmPrescalerOffset[number], regPrescaler);
            InterruptLockedWriteConfig(pThis, number, regConfig);

            OsSpinLockRelease(pThis->os.hSpinLock);


#if 0 // FIXME Remove once the slave interface is stable.
{
    if (0x1234 == pGpioConfiguration->pwmTiming.high)
    {
        U32 r; 
        U32 w = 0;

        DbgPrint("RW testing started - pin %u\n", number);

        while (1)
        {
            w++;

            REG_WR_08(gaRegPwmPeriodOffset[number], (U08)w);
            r = REG_RD_08(gaRegPwmPeriodOffset[number]);
            if ((r & 0xFF) != (w & 0xFF))
            {
                REG_WR_32(gaRegPwmPrescalerOffset[number], 0xAAAAAAAA);
                DbgPrint("U08: written = 0x%X, read = 0x%X\n", w, r);
                break;
            }

            w++;

            REG_WR_16(gaRegPwmPeriodOffset[number], (U16)w);
            r = REG_RD_16(gaRegPwmPeriodOffset[number]);
            if ((r & 0x1FFF) != (w & 0x1FFF))
            {
                REG_WR_32(gaRegPwmPrescalerOffset[number], 0xAAAAAAAA);
                DbgPrint("U16: written = 0x%X, read = 0x%X\n", w, r);
                break;
            }

            w++;

            REG_WR_32(gaRegPwmPeriodOffset[number], w);
            r = REG_RD_32(gaRegPwmPeriodOffset[number]);
            if ((r & 0x1FFF1FFF) != (w & 0x1FFF1FFF))
            {
                REG_WR_32(gaRegPwmPrescalerOffset[number], 0xAAAAAAAA);
                DbgPrint("U32: written = 0x%X, read = 0x%X\n", w, r);
                break;
            }
        }

        DbgPrint("RW testing finished - pin %u\n", number);
    }
}
#endif
            OsTraceCond(OS_LEVEL_VERBOSE,
                        OS_TRACE_FLAG_IOCTLS,
                        bOverrun,
                        "SET_CONFIGURATION %u overrun\n",
                        number);

            OsTrace(OS_LEVEL_VERBOSE,
                    OS_TRACE_FLAG_IOCTLS,
                    "SET_CONFIGURATION %u%s: pm=0x%X, iwm=0x%X, pv=0x%X, tl=0x%04X, th=0x%04X, tp=0x%04X, td=%u\n",
                    number,
                    (OS_STATUS__PENDING == osStatus) ? " pending" : "",
                    pinMode,
                    iwMode,
                    pinValue,
                    low,
                    high,
                    prescaler,
                    log2Divider);
        }
        else
        {
            PATH_NOT_YET_TESTED();
            OsSpinLockRelease(pThis->os.hSpinLock);

            OsTraceCond(OS_LEVEL_VERBOSE,
                        OS_TRACE_FLAG_IOCTLS,
                        bOverrun,
                        "SET_CONFIGURATION %u overrun\n",
                        number);

            OsTrace(OS_LEVEL_ERROR,
                    OS_TRACE_FLAG_IOCTLS,
                    "SET_CONFIGURATION %u error: pm=0x%X, iwm=0x%X, pv=0x%X, tl=0x%04X, th=0x%04X, tp=0x%04X, td=%u\n",
                    number,
                    pinMode,
                    iwMode,
                    pinValue,
                    low,
                    high,
                    prescaler,
                    log2Divider);
        }
    }
    else
    {
        PATH_TESTED();
        OsTrace(OS_LEVEL_ERROR,
                OS_TRACE_FLAG_IOCTLS,
                "SET_CONFIGURATION %u error\n",
                number);
    }

    return osStatus;
}



/**
 *  Get a GPIO configuration.
 * @param Pointer to a GPIO core object.
 * @param Pointer to a GPIO specifier.
 * @param Pointer to a location to receive a GPIO configuration.
 * @return OsStatus.
 */
static OsStatus
GetConfiguration(
    GpioCore                * const pThis,
    GpioSpecifier     const * const pGpioSpecifier,
    GpioConfiguration       * const pGpioConfiguration)
{
    GpioPinNumber number;
    OsStatus      osStatus = OS_STATUS__INVALID_PARAMETER;

    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pThis->bHwResourcesAssigned);
    PRECONDITION(pGpioSpecifier != NULL);
    PRECONDITION(pGpioConfiguration != NULL);

    number = pGpioSpecifier->number;
    if (number < pThis->hw.numberOfPins)
    {
        GpioPin * pGpioPin;

        PATH_TESTED();

        pGpioPin = &pThis->hw.aPin[number];
        
        OsSpinLockAcquire(pThis->os.hSpinLock);

        pGpioConfiguration->specifier.number = number;
        pGpioConfiguration->pinMode          = pGpioPin->pinMode;
        pGpioConfiguration->iwMode           = pGpioPin->iwMode;
        pGpioConfiguration->pwmTiming        = pGpioPin->pwmTiming;
        // For an input or PWM pin, return the pin's current value.
        // For all other modes, return the pin's configured value.
        if (   (PIN_MODE_INPUT      == pGpioConfiguration->pinMode)
            || (PIN_MODE_PWM_OUTPUT == pGpioConfiguration->pinMode) )
        {
            PATH_TESTED();
            // No interrupt lock required here.
            pGpioConfiguration->pinValue =
                  (REG_RD_32(gaRegConfigOffset[number]) & GPIO_REG_CONFIG__INPUT_VALUE_BIT)
                ? PIN_VALUE_HIGH : PIN_VALUE_LOW;
        }
        else
        {
            PATH_TESTED();
            pGpioConfiguration->pinValue =
                  (pThis->hw.outputValues & BIT_SEL(number))
                ? PIN_VALUE_HIGH : PIN_VALUE_LOW;
        }

        OsSpinLockRelease(pThis->os.hSpinLock);

        osStatus = OS_STATUS__OK;

        OsTrace(OS_LEVEL_VERBOSE,
                OS_TRACE_FLAG_IOCTLS,
                "GET_CONFIGURATION %u: pm=0x%X, iwm=0x%X, pv=0x%X, tl=0x%04X, th=0x%04X, tp=0x%04X, td=%u\n",
                pGpioConfiguration->specifier.number,
                pGpioConfiguration->pinMode,
                pGpioConfiguration->iwMode,
                pGpioConfiguration->pinValue,
                pGpioConfiguration->pwmTiming.low,
                pGpioConfiguration->pwmTiming.high,
                pGpioConfiguration->pwmTiming.prescaler,
                pGpioConfiguration->pwmTiming.log2Divider);
    }
    else
    {
        PATH_TESTED();
        OsTrace(OS_LEVEL_ERROR,
                OS_TRACE_FLAG_IOCTLS,
                "GET_CONFIGURATION %u error\n",
                number);
    }

    return osStatus;
}



/**
 *  Set all output GPIO values.
 * @param Pointer to a GPIO core object.
 * @param Pointer to output GPIO values to be applied.
 * @return OsStatus.
 */
static OsStatus
SetOutputValues(
    GpioCore               * const pThis,
    GpioOutputValues const * const pGpioOutputValues)
{
    GpioPinMask mask;
    OsStatus    osStatus = OS_STATUS__INVALID_PARAMETER;

    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pThis->bHwResourcesAssigned);
    PRECONDITION(pGpioOutputValues != NULL);

    mask = pGpioOutputValues->outputMask;

    OsSpinLockAcquire(pThis->os.hSpinLock);

    mask &= (pThis->hw.outputMask | pThis->hw.outputDrainMask);
    if (mask != 0)
    {
        PATH_TESTED();
        pThis->hw.outputValues &= ~mask;
        pThis->hw.outputValues |= mask & pGpioOutputValues->outputValues;

        REG_WR_32(GPIO_REG_OUTPUT_VALUE_0_7,  pThis->hw.outputValues);
        REG_WR_32(GPIO_REG_OUTPUT_VALUE_8_15, pThis->hw.outputValues >> 8);
        
        OsSpinLockRelease(pThis->os.hSpinLock);

        osStatus = OS_STATUS__OK;

        OsTrace(OS_LEVEL_VERBOSE,
                OS_TRACE_FLAG_IOCTLS,
                "SET_OUTPUT_VALUES om=0x%08X ov=0x%08X\n",
                pGpioOutputValues->outputMask,
                pGpioOutputValues->outputValues);
    }
    else
    {
        PATH_TESTED();
        OsSpinLockRelease(pThis->os.hSpinLock);
        OsTrace(OS_LEVEL_ERROR,
                OS_TRACE_FLAG_IOCTLS,
                "SET_OUTPUT_VALUES error: invalid mask\n");
    }

    return osStatus;
}



/**
 *  Get all input GPIO values.
 * @param Pointer to a GPIO core object.
 * @param Pointer to a location to receive current input GPIO values.
 * @return OsStatus.
 */
static OsStatus
GetInputValues(
    GpioCore        * const pThis,
    GpioInputValues * const pGpioInputValues)
{
    PATH_TESTED();

    PRECONDITION(pThis != NULL);
    PRECONDITION(pThis->bHwResourcesAssigned);
    PRECONDITION(pGpioInputValues != NULL);

    OsSpinLockAcquire(pThis->os.hSpinLock);

    pGpioInputValues->inputValues =
        REG_RD_32(GPIO_REG_INPUT_VALUE_0_7) | (REG_RD_32(GPIO_REG_INPUT_VALUE_8_15) << 8);
    pGpioInputValues->inputMask       = pThis->hw.inputMask;
    pGpioInputValues->outputMask      = pThis->hw.outputMask;
    pGpioInputValues->outputDrainMask = pThis->hw.outputDrainMask;
    pGpioInputValues->pwmOutputMask   = pThis->hw.pwmOutputMask;

    OsSpinLockRelease(pThis->os.hSpinLock);

    OsTrace(OS_LEVEL_VERBOSE,
            OS_TRACE_FLAG_IOCTLS,
            "GET_INPUT_VALUES iv=0x%08X im=0x%08X om=0x%08X odm=0x%08X pwmm=0x%08X\n",
            pGpioInputValues->inputValues,
            pGpioInputValues->inputMask,
            pGpioInputValues->outputMask,
            pGpioInputValues->outputDrainMask,
            pGpioInputValues->pwmOutputMask);

    return OS_STATUS__OK;
}




