//------------------------------------------------------------------------------
//
//  Copyright (C) 2004-2007, Freescale Semiconductor, Inc. All Rights Reserved.
//  THIS SOURCE CODE, AND ITS USE AND DISTRIBUTION, IS SUBJECT TO THE TERMS
//  AND CONDITIONS OF THE APPLICABLE LICENSE AGREEMENT
//
//------------------------------------------------------------------------------
//
//  File:  hwctxt.cpp
//
//  Provides BSP-specific configuration routines for the WAVEDEV driver.
//
//------------------------------------------------------------------------------

#pragma warning(push)
#pragma warning(disable: 4127 4201)
#include <windows.h>
#include <nkintr.h>
#include <ceddk.h>
#pragma warning(pop)

#include <bsp.h>
#include "wavemain.h"
#include "regs.h"
#include "regs_audio.h"
#include "regs_regulator.h"
#include "pmic_audio.h"     /* For the unified PMIC API interface. */
#include "pmic_lla.h"

//-----------------------------------------------------------------------------
// External Functions


//-----------------------------------------------------------------------------
// External Variables

#ifdef DEBUG

extern DBGPARAM dpCurSettings;

#endif // #ifdef DEBUG


//-----------------------------------------------------------------------------
// Defines


// Define master/slave mode for each SSI. The only possible values are:
//
//     TRUE    then SSI is master and the PMIC is slave
//     FALSE   then the PMIC is master and the SSI is slave
//
// This setting will also be used to configure either the Stereo DAC or Voice
// CODEC to the complementary mode (i.e., slave mode if the SSI is configured
// as master and vice versa).
//
// It is possible to have each SSI operating in a different master/slave mode
// depending upon the system's clocking configuration and functional
// requirements.
//
// See also the definition of STEREO_DAC_BUS_MODE and VOICE_CODEC_BUS_MODE
// below which determines the protocol to be used for the digital audio bus.
//
// For SSI master mode, the proper clock source must also be defined in the
// SSI1_MASTER_CLOCK_SOURCE and/or SSI2_MASTER_CLOCK_SOURCE settings below.

#define BSP_SSI1_MASTER_BOOL        FALSE  // Default TRUE
#define BSP_SSI2_MASTER_BOOL        FALSE  // Default TRUE

// Define the SSI master mode clock source. This clock source is used by
// the SSI to internally generate the proper framesync and bitclock signals
// when operating in master mode. This clock source is not used when the SSI
// is operating in slave mode.
//
// The list of valid choices are:
//
//     DDK_CLOCK_BAUD_SOURCE_SERPLL to use the Serial PLL clock source
//     DDK_CLOCK_BAUD_SOURCE_USBPLL to use the USB PLL clock source
//     DDK_CLOCK_BAUD_SOURCE_MCUPLL to use the MCU PLL clock source
//
// It is possible to use the same clock source for both SSI1 and SSI2.
// However, a different master clock source may be used for each SSI if
// required in order to generate the desired sampling rate clock signals.

#define SSI1_MASTER_CLOCK_SOURCE    DDK_CLOCK_BAUD_SOURCE_SERPLL
                                        // Default DDK_CLOCK_BAUD_SOURCE_SERPLL
#define SSI2_MASTER_CLOCK_SOURCE    DDK_CLOCK_BAUD_SOURCE_SERPLL
                                        // Default DDK_CLOCK_BAUD_SOURCE_SERPLL

// Define the SSI to be used with the PMIC Stereo DAC and Voice CODEC. The
// only possible values are:
//
//     m_pSSI1 to use SSI1
//     m_pSSI2 to use SSI2
//
// Note that the STEREO_DAC_SSI value must be different from that defined for
// the VOICE_CODEC_SSI to avoid a hardware conflict.

#define STEREO_DAC_SSI              m_pSSI2 // Default m_pSSI2
#define VOICE_CODEC_SSI             m_pSSI1 // Default m_pSSI1

// Define the Digital Audio MUX port configuration. The internal ports that
// are connected to the SSIs are hardwired for each processor. The external
// ports that are connected to the PMIC audio buses are defined by the
// platform and/or ADS board design.
//
// The selection of the external ports to be used may not be unique for each
// platform or ADS board, but we've simply selected ports below that are
// consistent with the hardware design and the operation of this driver.

#define SSI1_AUDMUX_PORT            PORT1 // Default PORT1
#define SSI2_AUDMUX_PORT            PORT2 // Default PORT2
#define VOICE_CODEC_AUDMUX_PORT     PORT5 // Default PORT5
#define STEREO_DAC_AUDMUX_PORT      PORT4 // Default PORT4

// Define the digital audio bus selections for both the PMIC Voice CODEC and
// Stereo DAC components. The only possible choices are:
//
//     AUDIO_DATA_BUS_1
//     AUDIO_DATA_BUS_2
//
// Note that the Voice CODEC and Stereo DAC must be assigned to different
// audio data buses in order to avoid a hardware conflict.
//
// Furthermore, the audio data bus selections must be consistent with the
// the Digital Audio MUX external port selections that were made above.
// The typical hardware connections are as follows but this should be
// verified from the platform reference manual and/or the schematics:
//
//     Audio MUX External PORT4 <======> AUDIO_DATA_BUS_1
//     Audio MUX External PORT5 <======> AUDIO_DATA_BUS_2

#define VOICE_CODEC_AUDIO_BUS   AUDIO_DATA_BUS_2 // Default AUDIO_DATA_BUS_2
#define STEREO_DAC_AUDIO_BUS    AUDIO_DATA_BUS_1 // Default AUDIO_DATA_BUS_1

// Define the digital audio bus mode for both the Stereo DAC and Voice CODEC.
// The only valid values for the MC13783 PMIC are:
//
//     NETWORK_MODE  to use a time-slotted network mode
//     I2S_MODE      to use the I2S mode
//
// See also the definition of BSP_SSI1_MASTER_BOOL and BSP_SSI2_MASTER_BOOL
// above which determines whether the SSI will act as the bus master or slave.

#define STEREO_DAC_BUS_MODE         NETWORK_MODE // Default NETWORK_MODE
#define VOICE_CODEC_BUS_MODE        NETWORK_MODE // Default NETWORK_MODE

// Define the SSI transmitter low water and receiver high watermarks. These
// watermark levels are used to trigger DMA requests to the core so that
// additional data can be added to the FIFO (for transmit) or data can be
// read from the FIFO (for receive).
//
// The valid values are from 1 to 8 (the SSI FIFO depth). Note that 0 is not
// a valid value for either the transmitter or receiver watermark levels and
// setting the watermark level to zero will prevent the SSI from functioning
// correctly.
//
// The proper watermark level to be used for a particular platform may need to
// be determined through performance testing and tuning. The key requirement is
// that both transmitter underrun and receiver overrun conditions must be
// avoided. Otherwise audio data will be lost. There is even the possibility
// of the left/right audio channels being randomly switched due to the nature
// of how the SSI hardware handles underrun and overrun conditions.
//
// See also the definition of AUDIO_DMA_PAGE_SIZE (in hwctxt.h) which defines
// the size in bytes of each DMA buffer. The size of the DMA buffer along with
// the SSI FIFO watermark levels are the two performance tuning parameters
// that are available for avoiding SSI FIFO underrun and overrun errors.

#define SSI_SFCSR_TX_WATERMARK      4 // Default 4
#define SSI_SFCSR_RX_WATERMARK      4 // Default 4

// Define the required timing delays for enabling/disabling the MC13783 PMIC
// audio components. These values are part of the MC13783 hardware specification
// and should not be modified.

#define MC13783_AUD_ENB_DELAY_BIAS_OFF    50      // 50 ms
#define MC13783_AUD_ENB_DELAY_BIAS_ON     5       // 5 ms

// Define the default output PGA gain. The valid values range from
// OUTPGA_GAIN_MINUS_33DB to OUTPGA_GAIN_6DB in 3 dB steps.
//
// WinCE seems to be better at reducing the volume than increasing it so it is
// recommended that the default gain be set above the halfway mark of -12 dB.
//

#define DEFAULT_OUTPGA_GAIN             OUTPGA_GAIN_MINUS_3DB
                                            // Default OUTPGA_GAIN_MINUS_3DB

#define PLAYBACK_DISABLE_DELAY_MSEC    1000

#ifdef AUDIO_RECORDING_ENABLED
#define RECORD_DISABLE_DELAY_MSEC      1000
#else
#define RECORD_DISABLE_DELAY_MSEC      0
#endif



//-----------------------------------------------------------------------------
// Types


//-----------------------------------------------------------------------------
// Global Variables

UINT32 PlaybackDisableDelayMsec = PLAYBACK_DISABLE_DELAY_MSEC;
UINT32 RecordDisableDelayMsec = RECORD_DISABLE_DELAY_MSEC;

UINT16 audioDMAPageSize = AUDIO_DMA_PAGE_SIZE;

//-----------------------------------------------------------------------------
// Local Variables

static PMIC_AUDIO_HANDLE hStDAC      = NULL; // Device handle for the PMIC Stereo DAC.

#ifdef AUDIO_RECORDING_ENABLED
    static PMIC_AUDIO_HANDLE hVoiceCODEC = NULL; // Device handle for the PMIC Voice CODEC.
    static const UINT nAudioDelayEvents = 3; // 3 event handles required:
                                             // - StopOutputDMA
                                             // - Playback Delayed Disable
                                             // - Recording Delayed Disable
#else
    static const UINT nAudioDelayEvents = 2; // 2 event handles required:
                                             // - StopOutputDMA
                                             // - Playback Delayed Disable
#endif

    static HANDLE h_AudioDelayEvents[nAudioDelayEvents];

//-----------------------------------------------------------------------------
//
//  Function: BSPAudioInit
//
//  This function maps the peripheral registers of the audio devices for
//  direct access by the driver.
//
//  Parameters:
//      None.
//
//  Returns:
//      Returns TRUE if successful, otherwise returns FALSE.
//
//-----------------------------------------------------------------------------
BOOL HardwareContext::BSPAudioInit()
{
    UINT32 irq;
    BOOL rc = FALSE;
    PHYSICAL_ADDRESS phyAddr;

    DEBUGMSG(ZONE_FUNCTION, (_T("+HardwareContext::BSPAudioInit\n")));

    h_AudioDelayEvents[0] = m_hAudioStopOutputDMA;
    h_AudioDelayEvents[1] = m_hAudioDelayDisableEvent;

#ifdef AUDIO_RECORDING_ENABLED
    h_AudioDelayEvents[2] = g_pRecordContext->m_hAudioDelayDisableEvent;
    //Need to accomodate the record buffers
    audioDMAPageSize = (AUDIO_DMA_PAGE_SIZE/2);
#endif

    // Get a device handle to the PMIC's Stereo DAC for performing audio
    // playback. If this call succeeds, then we have a device handle that
    // we must use when calling all of the other PMIC Audio APIs.
    if (PmicAudioOpen(&hStDAC, STEREO_DAC) != PMIC_SUCCESS)
    {
        ERRORMSG(ZONE_ERROR, (_T("PmicAudioOpen: failed to get Stereo DAC ")
                              _T("device handle!\r\n")));

        // We can just return here without jumping to "cleanUp" because we
        // haven't done any memory allocations or acquired any other system
        // resources.
        DEBUGMSG(ZONE_FUNCTION, (_T("-HardwareContext::BSPAudioInit\n")));
        return rc;
    }

    // We must immediately select the Stereo DAC's data bus here before we
    // also request a device handle for the Voice CODEC. Otherwise, the
    // underlying PMIC Audio API implementation may think that both audio
    // data buses are already in use and prevent any attempts at switching
    // the audio data buses.
    //
    // This call will also have the side-effect of automatically switching
    // the Voice CODEC over to the other audio data bus (which is fine since
    // that's what we wanted anyways).
    if (PmicAudioSetProtocol(hStDAC,
                             STEREO_DAC_AUDIO_BUS,
                             STEREO_DAC_BUS_MODE,
                             (((STEREO_DAC_SSI == m_pSSI1) &&
                               (BSP_SSI1_MASTER_BOOL))        ||
                              ((STEREO_DAC_SSI == m_pSSI2) &&
                               (BSP_SSI2_MASTER_BOOL))) ? BUS_SLAVE_MODE :
                                                          BUS_MASTER_MODE,
                             (STEREO_DAC_BUS_MODE == NETWORK_MODE) ?
                                 USE_4_TIMESLOTS : USE_2_TIMESLOTS) !=
        PMIC_SUCCESS)
    {
        ERRORMSG(ZONE_ERROR, (_T("PmicAudioOpen: failed to switch Stereo DAC ")
                              _T("over to audio bus %d!\r\n"),
                              STEREO_DAC_AUDIO_BUS));

        // Need to free up previously acquired Stereo DAC device handle.
        goto cleanUp;
    }

#ifdef AUDIO_RECORDING_ENABLED

    // Also get a device handle to the PMIC's Voice CODEC for performing audio
    // recording. If this call succeeds, then we have a device handle that we
    // must use to call all of the other PMIC Audio APIs.
    if (PmicAudioOpen(&hVoiceCODEC, VOICE_CODEC) != PMIC_SUCCESS)
    {
        ERRORMSG(ZONE_ERROR, (_T("PmicAudioOpen: failed to get Voice CODEC ")
                              _T("device handle!\r\n")));

        // Need to free up previously acquired Stereo DAC device handle.
        goto cleanUp;
    }

#endif // #ifdef AUDIO_RECORDING_ENABLED

    // Now that we have valid device handles to both the PMIC Stereo DAC and
    // Voice CODEC, we can proceed to setup access to the other hardware
    // components that are also needed for audio playback and recording. This
    // includes SSI1, SSI2, and the Digital Audio MUX.

    // Start by getting access to the SSI1 hardware control registers.
    phyAddr.QuadPart = CSP_BASE_REG_PA_SSI1;

    // Map peripheral physical address to virtual address.
    m_pSSI1 = (PCSP_SSI_REG) MmMapIoSpace(phyAddr, sizeof(CSP_SSI_REG), FALSE);

    // Check if the virtual mapping failed.
    if (m_pSSI1 == NULL)
    {
        ERRORMSG(ZONE_ERROR, (_T("MapRegisters:  MmMapIoSpace failed for ")
                              _T("SSI1!\r\n")));
        goto cleanUp;
    }

    // Call the OAL to translate the SSI1 IRQ into a SYSINTR value.
    irq = IRQ_SSI1;
    if (!KernelIoControl(IOCTL_HAL_REQUEST_SYSINTR, &irq, sizeof(UINT32),
                         &m_dwSysintrSSI1, sizeof(UINT32), NULL))
    {
        ERRORMSG(ZONE_ERROR, (TEXT("ERROR: HardwareContext::Init: Failed ")
                              TEXT("to obtain sysintr value for SSI1 ")
                              TEXT("interrupt.\r\n")));
        goto cleanUp;
    }

    // Also get access to the SSI2 hardware control registers.
    phyAddr.QuadPart = CSP_BASE_REG_PA_SSI2;

    // Map peripheral physical address to virtual address.
    m_pSSI2 = (PCSP_SSI_REG) MmMapIoSpace(phyAddr, sizeof(CSP_SSI_REG), FALSE);

    // Check if the virtual mapping failed.
    if (m_pSSI2 == NULL)
    {
        ERRORMSG(ZONE_ERROR, (_T("MapRegisters:  MmMapIoSpace failed for ")
                              _T("SSI2!\r\n")));
        goto cleanUp;
    }

    // Call the OAL to translate the SSI2 IRQ into a SYSINTR value.
    irq = IRQ_SSI2;
    if (!KernelIoControl(IOCTL_HAL_REQUEST_SYSINTR, &irq, sizeof(UINT32),
                         &m_dwSysintrSSI2, sizeof(UINT32), NULL))
    {
        ERRORMSG(ZONE_ERROR, (TEXT("ERROR: HardwareContext::Init: Failed to ")
                              TEXT("obtain sysintr value for SSI2 interrupt.")
                              TEXT("\r\n")));
        goto cleanUp;
    }

    // Finally, get access to the Digital Audio MUX hardware control registers.
    phyAddr.QuadPart = CSP_BASE_REG_PA_AUDMUX;

    // Map peripheral physical address to virtual address.
    m_pAUDMUX= (PCSP_AUDMUX_REG) MmMapIoSpace(phyAddr, sizeof(CSP_AUDMUX_REG),
                                              FALSE);

    // Check if the virtual mapping failed.
    if (m_pAUDMUX == NULL)
    {
        ERRORMSG(ZONE_ERROR, (_T("MapRegisters:  MmMapIoSpace failed for ")
                              _T("Audio MUX!\r\n")));
        goto cleanUp;
    }

    // We need to configure the CKO signal to output CKIH for the MC13783 CLI
    // clock input.
    DDKClockSetCKO(TRUE, DDK_CLOCK_CKO_SRC_CKIH, DDK_CLOCK_CKO_DIV_2);

    // We now have the ability to access all of the hardware components that
    // are needed to perform audio recording and playback.
    rc = TRUE;

cleanUp:
    if (!rc) BSPAudioDeinit();

    DEBUGMSG(ZONE_FUNCTION, (_T("-HardwareContext::BSPAudioInit\n")));

    return rc;
}


//-----------------------------------------------------------------------------
//
//  Function:  BSPAudioDeinit
//
//  This function unmaps the peripheral registers previously mapped with
//  the MapRegisters function.
//
//  Parameters:
//      None.
//
//  Returns:
//      Returns TRUE if successful, otherwise returns FALSE.
//
//-----------------------------------------------------------------------------
BOOL HardwareContext::BSPAudioDeinit()
{
    DEBUGMSG(ZONE_FUNCTION, (_T("+HardwareContext::BSPAudioDeinit\n")));

    // Unmap SSI1 peripheral registers
    if (m_pSSI1)
    {
        MmUnmapIoSpace(m_pSSI1, sizeof(CSP_SSI_REG));
        m_pSSI1 = NULL;
    }

    // Unmap SSI2 peripheral registers
    if (m_pSSI2)
    {
        MmUnmapIoSpace(m_pSSI2, sizeof(CSP_SSI_REG));
        m_pSSI2 = NULL;
    }

    // Unmap AUDMUX peripheral registers
    if (m_pAUDMUX)
    {
        MmUnmapIoSpace(m_pAUDMUX, sizeof(CSP_AUDMUX_REG));
        m_pAUDMUX = NULL;
    }

    if (hStDAC != NULL)
    {
        PmicAudioClose(hStDAC);
        hStDAC = NULL;
    }

#ifdef AUDIO_RECORDING_ENABLED
    if (hVoiceCODEC != NULL)
    {
        PmicAudioClose(hVoiceCODEC);
        hVoiceCODEC = NULL;
    }
#endif

    DEBUGMSG(ZONE_FUNCTION, (_T("-HardwareContext::BSPAudioDeinit\n")));

    return TRUE;
}


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioAllocDMABuffers
//
//  Allocate the audio DMA buffers.
//
//  Parameters:
//      pAddress - returns physical address of allocated DMA buffers
//
//  Returns:
//      PBYTE virtual address of allocated memory region (NULL if error)
//
//-----------------------------------------------------------------------------
PBYTE HardwareContext::BSPAudioAllocDMABuffers(PHYSICAL_ADDRESS *pAddress)
{

#ifdef BSP_AUDIO_DMA_BUF_ADDR
    PBYTE pBuf;

    // Use a statically defined memory region for the audio DMA buffers. This
    // may be either internal or external memory depending upon the address
    // region that is selected.

    pAddress->HighPart = 0;
    pAddress->LowPart  = BSP_AUDIO_DMA_BUF_ADDR;

    pBuf = (PBYTE)MmMapIoSpace(*pAddress, BSP_AUDIO_DMA_BUF_SIZE, FALSE);

    // If virtual mapping succeeded and buffer is being allocated from IRAM
    if ((pBuf) && (pAddress->LowPart >= IMAGE_WINCE_IRAM_PA_START) &&
        (pAddress->LowPart < (IMAGE_WINCE_IRAM_PA_START+IMAGE_WINCE_IRAM_SIZE)))
    {
        // Hardware constraints require IRAM to be accessed with PAHB or
        // we can incur errors on subsequent accesses to DRAM.  Update
        // the MMU attributes to use backwards-compatible extended
        // small page format.  This format allows us to set the TEX bits
        // to mark the IRAM region for non-shared device (directs
        // access to PAHB)
        //
        // Extended Small Page Format
        // --------------------------
        // BITS[31:12] = ADDRESS (preserved)
        // BITS[11:9] = SBZ (write as zero)
        // BITS[8:6] = TEX  (010b used for non-shared device)
        // BITS[5:4] = AP (preserved)
        // BITS[3:2] = CB (00b used for non-shared device)
        // BITS[1:0] = must be 11b for extended small page
        //
        // VAL = (0x0 << 9) | (0x2 << 6) | (0x0 << 2) | (0x3 << 0) = 0x083
        // MASK = (0x7 << 9) | (0x7 << 6) | (0x3 << 2) | (0x3 << 0) = 0xFCF

        if (!VirtualSetAttributes(pBuf, BSP_AUDIO_DMA_BUF_SIZE,
            0x083, 0xFCF, NULL))
        {
            ERRORMSG(ZONE_ERROR, (_T("VirtualSetAttributes failed!\r\n")));
            goto cleanUp;
        }

        // Flush the TLB since we directly updated the page tables
        CacheRangeFlush(0, 0, CACHE_SYNC_FLUSH_TLB);
    }

cleanUp:
    return pBuf;

#else // #ifdef BSP_AUDIO_DMA_BUF_ADDR

    // Use dynamically allocated memory for the audio DMA buffers.

#ifdef AUDIO_RECORDING_ENABLED
    // Allocate 2 DMA buffers each for audio playback and recording.
    static const DMA_BUFFER_REGION_SIZE_NBYTES = audioDMAPageSize * 4;
#else
    // Only allocate 2 DMA buffers for audio playback.
    static const DMA_BUFFER_REGION_SIZE_NBYTES = audioDMAPageSize  * 2;
#endif

    DMA_ADAPTER_OBJECT Adapter;

    memset(&Adapter, 0, sizeof(DMA_ADAPTER_OBJECT));
    Adapter.InterfaceType = Internal;
    Adapter.ObjectSize    = sizeof(DMA_ADAPTER_OBJECT);

    // Allocate DMA buffers from external memory.
    return (PBYTE)HalAllocateCommonBuffer(&Adapter,
                                          DMA_BUFFER_REGION_SIZE_NBYTES,
                                          pAddress,
                                          FALSE);

#endif // #ifdef BSP_AUDIO_DMA_BUF_ADDR

}


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioDeallocDMABuffers
//
//  Deallocate the audio DMA buffers that were previously allocated by calling
//  BSPAudioAllocDMABuffers().
//
//  Parameters:
//      None.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
void HardwareContext::BSPAudioDeallocDMABuffers(PVOID virtualAddress)
{
#ifndef BSP_AUDIO_DMA_BUF_ADDR

    // Only deallocate the audio DMA buffer memory if it was previously
    // dynamically allocated.

    PHYSICAL_ADDRESS phyAddr;

    // Logical address parameter is ignored
    phyAddr.QuadPart = 0;

    HalFreeCommonBuffer(NULL, 0, phyAddr, virtualAddress, FALSE);

#else

    // Explicitly indicate that "virtualAddress" is an unreferenced parameter
    // when the audio DMA buffers are allocated from internal memory so that
    // the compiler will not complain if WARNLEVEL=4 is used.

    UNREFERENCED_PARAMETER(virtualAddress);

#endif // #ifndef BSP_AUDIO_DMA_BUF_ADDR
}


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioPowerUp
//
//  This function powers up the SSI, AUDMUX, and external audio chip.
//
//  Parameters:
//      None.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
void HardwareContext::BSPAudioPowerUp(const BOOL fullPowerUp)
{
    if (!fullPowerUp)
    {
        PmicAudioPowerUp();
    }
    else
    {
        BSPAudioSetCodecPower(AUDIO_PWR_STATE_STANDBY);
    }
}


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioPowerDown
//
//  This function powers down the SSI, AUDMUX, and external audio chip.
//
//  Parameters:
//      None.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
void HardwareContext::BSPAudioPowerDown(const BOOL fullPowerOff)
{
    if (!fullPowerOff)
    {
        PmicAudioPowerDown();
    }
    else
    {
        BSPAudioSetCodecPower(AUDIO_PWR_STATE_OFF);
    }
}


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioGetStDACSSI
//
//  Returns the SSI that is currently connected to the PMIC Stereo DAC.
//
//  Parameters:
//      None.
//
//  Returns:
//      The SSI that is connected to the PMIC Stereo DAC.
//
//-----------------------------------------------------------------------------
PCSP_SSI_REG HardwareContext::BSPAudioGetStDACSSI()
{
    return STEREO_DAC_SSI;
}


#ifdef AUDIO_RECORDING_ENABLED

//-----------------------------------------------------------------------------
//
//  Function: BSPAudioGetVCodecSSI
//
//  Returns the SSI that is currently connected to the PMIC Voice CODEC.
//
//  Parameters:
//      None.
//
//  Returns:
//      The SSI that is connected to the PMIC Voice CODEC.
//
//-----------------------------------------------------------------------------
PCSP_SSI_REG HardwareContext::BSPAudioGetVCodecSSI()
{
    return VOICE_CODEC_SSI;
}

#endif // #ifdef AUDIO_RECORDING_ENABLED


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioInitOutput
//
//  This function initializes the DMA, SSI, and AUDMUX to support audio
//  output.
//
//  Parameters:
//      The SSI configuration to be used.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
BOOL HardwareContext::BSPAudioInitOutput(AUDIO_BUS bus)
{
    BOOL rc = FALSE;

    switch (bus)
    {
        case AUDIO_BUS_STEREO_OUT:

            // Configure the SSI to function as the audio output channel.
            BSPAudioInitSsi(bus, STEREO_DAC_SSI);

            // Configure the PMIC Stereo DAC.
            BSPAudioInitCodec(bus);

            // Configure the AUDMUX to route the SSI to the PMIC Stereo DAC.
            //
            // We need to do this last (and only after both the SSI and PMIC
            // has been properly configured) to avoid any possible signal
            // conflicts due to any previous SSI and/or PMIC configuration
            // settings.
      /*      BSPAudioRoute((STEREO_DAC_SSI == m_pSSI1) ? SSI1_AUDMUX_PORT :
                                                        SSI2_AUDMUX_PORT,
                          STEREO_DAC_AUDMUX_PORT,
                          (STEREO_DAC_SSI == m_pSSI1) ? BSP_SSI1_MASTER_BOOL :
                                                        BSP_SSI2_MASTER_BOOL);*/
           //Work around for Prefast warning

            BSPAudioRoute((STEREO_DAC_SSI == m_pSSI1) ? SSI1_AUDMUX_PORT :
                                                        SSI2_AUDMUX_PORT,
                          STEREO_DAC_AUDMUX_PORT,
                          (STEREO_DAC_SSI == m_pSSI1) ? BSP_SSI1_MASTER_BOOL :
                                                        BSP_SSI2_MASTER_BOOL);

            // Configure the output DMA watermark level. This value defines
            // the number of empty slots in the SSI transmit FIFO before a
            // DMA request is generated. Therefore, this value is also the
            // number of words that we should transfer using a single DMA
            // operation to try and refill the FIFO.
            m_OutputDMALevel = SSI_SFCSR_TX_WATERMARK;

            // Open the DMA channel for handling SSI transmit/playback.
            m_OutputDMAChan = DDKSdmaOpenChan((STEREO_DAC_SSI == m_pSSI1) ?
                                                  DDK_DMA_REQ_SSI1_TX0 :
                                                  DDK_DMA_REQ_SSI2_TX0,
                                              BSP_SDMA_CHNPRI_AUDIO, NULL,
                                              (STEREO_DAC_SSI == m_pSSI1) ?
                                                  IRQ_SSI1 : IRQ_SSI2);

            if (m_OutputDMAChan) rc = TRUE;

            break;
    }

    return rc;
}

#ifdef AUDIO_RECORDING_ENABLED

//-----------------------------------------------------------------------------
//
//  Function: BSPAudioInitInput
//
//  This function initializes the DMA, SSI, and AUDMUX to support audio
//  input.
//
//  Parameters:
//      The SSI configuration to be used.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
BOOL HardwareContext::BSPAudioInitInput(AUDIO_BUS bus)
{
    BOOL rc = FALSE;

    switch (bus)
    {
        case AUDIO_BUS_VOICE_IN:

            // Configure the SSI to function as the audio input channel.
            BSPAudioInitSsi(bus, VOICE_CODEC_SSI);

            // Configure the PMIC Voice CODEC.
            BSPAudioInitCodec(bus);

            // Configure the AUDMUX to route the SSI to the PMIC Voice Codec.
            //
            // We need to do this last (and only after both the SSI and PMIC
            // has been properly configured) to avoid any possible signal
            // conflicts due to any previous SSI and/or PMIC configuration
            // settings.
           /* BSPAudioRoute((VOICE_CODEC_SSI == m_pSSI1) ? SSI1_AUDMUX_PORT :
                                                         SSI2_AUDMUX_PORT,
                          VOICE_CODEC_AUDMUX_PORT,
                          (VOICE_CODEC_SSI == m_pSSI1) ? BSP_SSI1_MASTER_BOOL :
                                                         BSP_SSI2_MASTER_BOOL);*/

            //Work around for prefast warning
            BSPAudioRoute((VOICE_CODEC_SSI == m_pSSI1) ? SSI1_AUDMUX_PORT :
                                                         SSI2_AUDMUX_PORT,
                          VOICE_CODEC_AUDMUX_PORT,
                          (VOICE_CODEC_SSI == m_pSSI1) ? BSP_SSI1_MASTER_BOOL :
                                                         BSP_SSI2_MASTER_BOOL);

            // Configure the input DMA watermark level. This value is the
            // number of words currently stored in the SSI receiver FIFO
            // before a DMA request is sent. Therefore, this is also the
            // number of words that we will read (and remove) from the FIFO
            // using a single DMA operation.
            g_pRecordContext->m_InputDMALevel = SSI_SFCSR_RX_WATERMARK;

            // Open the DMA channel for handling SSI receive/recording.
            g_pRecordContext->m_InputDMAChan =
                DDKSdmaOpenChan((VOICE_CODEC_SSI == m_pSSI1) ?
                    DDK_DMA_REQ_SSI1_RX0 : DDK_DMA_REQ_SSI2_RX0,
                                             BSP_SDMA_CHNPRI_AUDIO, NULL,
                    (VOICE_CODEC_SSI == m_pSSI1) ? IRQ_SSI1 : IRQ_SSI2);

            if (g_pRecordContext->m_InputDMAChan) rc = TRUE;

            break;
    }

    return rc;
}

#endif // #ifdef AUDIO_RECORDING_ENABLED


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioStartOutput
//
//  This function configures audio output devices to start audio playback.
//
//  Parameters:
//      The SSI configuration to be used.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
BOOL HardwareContext::BSPAudioStartOutput(AUDIO_BUS bus, AUDIO_PATH path,
                                          const HWSAMPLE *const ssiFifoPrefill,
                                          const unsigned nSsiFifoPrefill)
{
    BOOL rc = TRUE;

    switch (bus)
    {
        case AUDIO_BUS_STEREO_OUT:

            // Turn on the codec and amps. This must be done before starting
            // SSI output because there are required delays involved in
            // enabling some of the PMIC audio components. These required
            // delays will cause an SSI FIFO underrun error if the SSI
            // transmitter is already running before the PMIC audio components
            // have been enabled.
            BSPAudioStartCodecOutput(bus, path);

            // Start the SSI transmitter.
            BSPAudioStartSsiOutput(STEREO_DAC_SSI, ssiFifoPrefill,
                                   nSsiFifoPrefill);

            break;
    }

    return rc;
}


#ifdef AUDIO_RECORDING_ENABLED

//-----------------------------------------------------------------------------
//
//  Function: BSPAudioStartInput
//
//  This function configures audio input devices to start audio record.
//
//  Parameters:
//      The SSI configuration to be used.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
BOOL HardwareContext::BSPAudioStartInput(AUDIO_BUS bus, AUDIO_PATH path)
{
    BOOL rc = TRUE;

    switch (bus)
    {
        case AUDIO_BUS_VOICE_IN:

            // Start SSI receive
            BSPAudioStartSsiInput(VOICE_CODEC_SSI);

            // Turn on the codec and amps
            BSPAudioStartCodecInput(bus, path);

            break;
    }

    return rc;
}

#endif // #ifdef AUDIO_RECORDING_ENABLED


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioStopOutput
//
//  This function configures audio output devices to stop audio playback.
//
//  Parameters:
//      The SSI configuration to be used.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
BOOL HardwareContext::BSPAudioStopOutput(AUDIO_BUS bus, AUDIO_PATH path)
{
    BOOL rc = TRUE;

    switch (bus)
    {
        case AUDIO_BUS_STEREO_OUT:

            // Turn off the codec and amps
            BSPAudioStopCodecOutput(bus, path);

            // Stop SSI transmit
            BSPAudioStopSsiOutput(STEREO_DAC_SSI);

            break;
    }

    return rc;
}


#ifdef AUDIO_RECORDING_ENABLED

//-----------------------------------------------------------------------------
//
//  Function: BSPAudioStopInput
//
//  This function configures audio input devices to stop audio record.
//
//  Parameters:
//      The SSI configuration to be used.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
BOOL HardwareContext::BSPAudioStopInput(AUDIO_BUS bus, AUDIO_PATH path)
{
    BOOL rc = TRUE;

    switch (bus)
    {
        case AUDIO_BUS_VOICE_IN:

            // Turn off the codec and amps
            BSPAudioStopCodecInput(bus, path);

            // Stop SSI transmit
            BSPAudioStopSsiInput(VOICE_CODEC_SSI);

            break;
    }

    return rc;
}

#endif // #ifdef AUDIO_RECORDING_ENABLED


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioSetOutputGain
//
//      This function configures the Stereo DAC's output PGA amplifier gain
//      level based upon the volume level that Windows is currently requesting.
//
//      The current mapping uses only the low-word of the DWORD dwGain
//      argument because the PMIC does not allow setting separate gain
//      levels for the left and right channels. Therefore, as documented
//      in the audio driver online help files, we should just use the
//      low-word part of the gain value. This provides a range of 0-65535.
//
//      Note that the MC13783 PMIC Stereo DAC supports 14 distinct output
//      gain levels that range from -33dB to +6dB in 3dB steps. Therefore,
//      we have implemented a linear mapping of the 0-65535 range to the
//      0-13 range in order to select the actual hardware gain level.
//
//  Parameters:
//      bus    [in] Ignored.
//      dwGain [in] The desired output volume level.
//
//  Returns:
//      TRUE  if new output gain was successfully set.
//      FALSE if new output gain could not be set.
//
//-----------------------------------------------------------------------------
BOOL HardwareContext::BSPAudioSetOutputGain(AUDIO_BUS bus, const DWORD dwGain)
{
    DWORD lowGain = (dwGain & 0xFFFF);   // Just use low-word of dwGain.
    PMIC_AUDIO_OUTPUT_PGA_GAIN pmicGain;
    PMIC_STATUS rc;

    // Remove-W4: Warning C4100 workaround
    UNREFERENCED_PARAMETER(bus);

    DEBUGMSG(ZONE_FUNCTION, (_T("+HardwareContext::BSPAudioSetOutputGain\n")));

    if (lowGain <= 2521)
    {
	// Set to minimum amplifier gain.
        pmicGain = OUTPGA_GAIN_MINUS_33DB;
    }
    else if (lowGain > 63013)
    {
	// Set to maximum amplifier gain.
        pmicGain = OUTPGA_GAIN_PLUS_6DB;
    }
    else
    {
	// Linear mapping of lowGain value to available amplifier gain levels.
        pmicGain = (PMIC_AUDIO_OUTPUT_PGA_GAIN)((lowGain - 2522) / 5041 + 1);
    }

    // Set the Stereo DAC's PGA output gain level.
    rc = PmicAudioOutputSetPgaGain(hStDAC, pmicGain);

    DEBUGMSG(ZONE_FUNCTION, (_T("-HardwareContext::BSPAudioSetOutputGain\n")));

    return (rc == PMIC_SUCCESS);
}


#ifdef AUDIO_RECORDING_ENABLED

//-----------------------------------------------------------------------------
//
//  Function: BSPAudioSetInputGain
//
//
//  Parameters:
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
BOOL HardwareContext::BSPAudioSetInputGain(AUDIO_BUS bus, const DWORD dwGain)
{
    // Remove-W4: Warning C4100 workaround
    UNREFERENCED_PARAMETER(bus);
    UNREFERENCED_PARAMETER(dwGain);

    DEBUGMSG(ZONE_FUNCTION, (_T("+HardwareContext::BSPAudioSetInputGain\n")));

    DEBUGMSG(ZONE_FUNCTION, (_T("-HardwareContext::BSPAudioSetInputGain\n")));

    return TRUE;
}

#endif // #ifdef AUDIO_RECORDING_ENABLED


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioRoute
//
//  This function configures the Audio MUX to connect/disconnect the SSI
//  to the external power management IC.
//
//  Parameters:
//      intPort [in]   Audio MUX internal port for connection to SSI.
//      extPort [in]   Audio MUX external port for connection to PMIC.
//      bMaster [in]   Boolean flag to select SSI Master (if TRUE) or
//                     PMIC Master (if FALSE) modes.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
void HardwareContext::BSPAudioRoute(AUDMUX_INTERNAL_PORT intPort,
    AUDMUX_EXTERNAL_PORT extPort, BOOL bMaster)
{
    PUINT32 pPTCR, pPDCR;

    // Get pointers to the Audio MUX internal port registers.
    pPTCR = &m_pAUDMUX->PTCR1 + intPort*2;
    pPDCR = &m_pAUDMUX->PDCR1 + intPort*2;

    // Configure the Audio MUX internal port to connect with the SSI based
    // upon who is acting as the bus master.
    //
    // But regardless of who is the master, we also configure the internal
    // port for synchronous 4-wire operation in normal mode (which is what
    // we actually need to support the SSI and PMIC for either network or I2S
    // modes).
    //
    // Note that we only configure the transmit framesync and bitclock here
    // because we are using synchronous mode and the receiver clock
    // settings will be determined by the transmitter settings.
    if (bMaster)
    {
        // All clock signals for the internal port are input signals for
        // SSI master mode.
        OUTREG32(pPTCR,
            CSP_BITFVAL(AUDMUX_PTCR_TFSDIR,  AUDMUX_PTCR_TFSDIR_INPUT)  |
            CSP_BITFVAL(AUDMUX_PTCR_TCLKDIR, AUDMUX_PTCR_TCLKDIR_INPUT) |
            CSP_BITFVAL(AUDMUX_PTCR_SYN, AUDMUX_PTCR_SYN_SYNC));
    }
    else
    {
        // All clock signals for the internal port are all output signals for
        // PMIC master mode. The source of the clock signals is the external
        // port that is connected to the PMIC.
        OUTREG32(pPTCR,
            CSP_BITFVAL(AUDMUX_PTCR_TFSDIR,  AUDMUX_PTCR_TFSDIR_OUTPUT)  |
            CSP_BITFVAL(AUDMUX_PTCR_TFSEL,  extPort)                     |
            CSP_BITFVAL(AUDMUX_PTCR_TCLKDIR, AUDMUX_PTCR_TCLKDIR_OUTPUT) |
            CSP_BITFVAL(AUDMUX_PTCR_TCSEL,  extPort)                     |
            CSP_BITFVAL(AUDMUX_PTCR_SYN, AUDMUX_PTCR_SYN_SYNC));
    }

    OUTREG32(pPDCR,
        CSP_BITFVAL(AUDMUX_PDCR_RXDSEL, extPort)                    |
        CSP_BITFVAL(AUDMUX_PDCR_TXRXEN, AUDMUX_PDCR_TXRXEN_NO_SWAP) |
        CSP_BITFVAL(AUDMUX_PDCR_MODE,   AUDMUX_PDCR_MODE_NORMAL));

    // Get pointers to the Audio MUX external port registers.
    pPTCR = &m_pAUDMUX->PTCR1 + extPort * 2;
    pPDCR = &m_pAUDMUX->PDCR1 + extPort * 2;

    // Configure the Audio MUX external port to connect with the PMIC based
    // upon who is acting as the bus master.
    //
    // But regardless of who is the master, we also configure the external
    // port for synchronous 4-wire operation in normal mode (which is what
    // we actually need to support the SSI and PMIC in either network or I2S
    // mode).
    if (bMaster)
    {
        // All clock signals for the external port are output signals for
        // SSI master mode. The source of the clock signals is the internal
        // port that is connected to the SSI.
        OUTREG32(pPTCR,
            CSP_BITFVAL(AUDMUX_PTCR_TFSDIR,  AUDMUX_PTCR_TFSDIR_OUTPUT)  |
            CSP_BITFVAL(AUDMUX_PTCR_TFSEL,  intPort)                     |
            CSP_BITFVAL(AUDMUX_PTCR_TCLKDIR, AUDMUX_PTCR_TCLKDIR_OUTPUT) |
            CSP_BITFVAL(AUDMUX_PTCR_TCSEL,  intPort)                     |
            CSP_BITFVAL(AUDMUX_PTCR_SYN, AUDMUX_PTCR_SYN_SYNC));
    }
    else
    {
        // All clock signals for the external port are input signals for
        // PMIC master mode.
        OUTREG32(pPTCR,
            CSP_BITFVAL(AUDMUX_PTCR_TFSDIR,  AUDMUX_PTCR_TFSDIR_INPUT)  |
            CSP_BITFVAL(AUDMUX_PTCR_TCLKDIR, AUDMUX_PTCR_TCLKDIR_INPUT) |
            CSP_BITFVAL(AUDMUX_PTCR_SYN, AUDMUX_PTCR_SYN_SYNC));
    }

    OUTREG32(pPDCR,
        CSP_BITFVAL(AUDMUX_PDCR_RXDSEL, intPort)                    |
        CSP_BITFVAL(AUDMUX_PDCR_TXRXEN, AUDMUX_PDCR_TXRXEN_NO_SWAP) |
        CSP_BITFVAL(AUDMUX_PDCR_MODE,   AUDMUX_PDCR_MODE_NORMAL));

    // Must also configure the IOMUX pins for connecting the Audio MUX
    // external port to off-chip peripherals such as the PMIC. Without
    // this step, we will not be able to connect the Audio MUX to the
    // PMIC audio buses.
    //
    // The key here is to configure the 4-wire interface (framesync, bitclock,
    // TX, and RX) through the IOMUX by enabling the functional/normal mode.
    // We leave the input/output direction control for all of the I/O pins up
    // to the Audio MUX.
    //
    // Note that we configure both the RX and TX pins here just for the sake
    // of completeness and simplicity even though the PMIC Stereo DAC does not
    // currently have any recording capabilities and we do not use the Voice
    // CODEC for playback.
    DDKIomuxSetPinMux((extPort == PORT4) ? DDK_IOMUX_PIN_SFS4 :
                                           DDK_IOMUX_PIN_SFS5,
                      DDK_IOMUX_OUT_FUNC,
                      DDK_IOMUX_IN_FUNC);
    DDKIomuxSetPinMux((extPort == PORT4) ? DDK_IOMUX_PIN_SCK4 :
                                           DDK_IOMUX_PIN_SCK5,
                      DDK_IOMUX_OUT_FUNC,
                      DDK_IOMUX_IN_FUNC);
    DDKIomuxSetPinMux((extPort == PORT4) ? DDK_IOMUX_PIN_STXD4 :
                                           DDK_IOMUX_PIN_STXD5,
                      DDK_IOMUX_OUT_FUNC,
                      DDK_IOMUX_IN_FUNC);
    DDKIomuxSetPinMux((extPort == PORT4) ? DDK_IOMUX_PIN_SRXD4 :
                                           DDK_IOMUX_PIN_SRXD5,
                      DDK_IOMUX_OUT_FUNC,
                      DDK_IOMUX_IN_FUNC);
}


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioInitSsi
//
//  This function initializes the specified audio port for input/output.
//
//  Parameters:
//      The SSI configuration to be used.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
void HardwareContext::BSPAudioInitSsi(AUDIO_BUS bus, PCSP_SSI_REG pSSI)
{
    // These are the only SSI configuration parameters that are different
    // between the network and I2S modes. We just default to setting up for
    // network mode and then adjust the settings if I2S mode was actually
    // selected.
    unsigned ssi_i2s_mode        = SSI_SCR_I2S_MODE_NORMAL;
    unsigned ssi_stcr_tsckp_mode = SSI_STCR_TSCKP_RISING_EDGE;
    unsigned ssi_stcr_tfsi_mode  = SSI_STCR_TFSI_ACTIVE_HIGH;
    unsigned ssi_stccr_dc_mode   = 3;
    unsigned ssi_scr_net_mode    = SSI_SCR_NET_ENABLE;
    unsigned ssi_stcr_tfsl_mode  = SSI_STCR_TFSL_1BIT;
    unsigned ssi_stccr_wl        = SSI_STCCR_WL_8BIT;

    // Remove-W4: Warning C4100 workaround
    UNREFERENCED_PARAMETER(bus);

    UINT8 size = sizeof(HWSAMPLE);
    if (size == sizeof(INT16))
    {
        ssi_stccr_wl = SSI_STCCR_WL_16BIT;
    }
    else if (size == sizeof(INT32))
    {
        // The SSI only supports a maximum transfer size of 24 bits/word.
        ssi_stccr_wl = SSI_STCCR_WL_24BIT;
    }

    // Reconfigure the SSI to use the I2S mode instead if that was what was
    // selected.
    //
    // The required SSI configuration changes consist of the following:
    //
    //     * Either I2S master/slave mode.
    //     * The transmit data clocked at the falling edge.
    //     * Transmit frame sync is active low.
    //     * Select 2 words/frame.
    //
    PMIC_AUDIO_BUS_PROTOCOL stereo = STEREO_DAC_BUS_MODE;
    PMIC_AUDIO_BUS_PROTOCOL voice = VOICE_CODEC_BUS_MODE;
    if (((stereo == I2S_MODE)  && (pSSI == STEREO_DAC_SSI)) ||
        ((voice == I2S_MODE) && (pSSI == VOICE_CODEC_SSI)))
    {
        ssi_i2s_mode = (((pSSI == m_pSSI1) && (BSP_SSI1_MASTER_BOOL)) ||
                        ((pSSI == m_pSSI2) && (BSP_SSI2_MASTER_BOOL))) ?
                           SSI_SCR_I2S_MODE_MASTER : SSI_SCR_I2S_MODE_SLAVE;
        ssi_stcr_tsckp_mode = SSI_STCR_TSCKP_FALLING_EDGE;
        ssi_stcr_tfsi_mode  = SSI_STCR_TFSI_ACTIVE_LOW;
        ssi_stccr_dc_mode   = 1;                        // 2 words/frame
        ssi_scr_net_mode    = SSI_SCR_NET_DISABLE;      // disable NETWORK mode
        ssi_stcr_tfsl_mode  = SSI_STCR_TFSL_1WORD;      // 1-word long framesync
    }

    // Enable SSI clocks before we access the SSI registers.
    DDKClockSetGatingMode((pSSI == m_pSSI1) ? DDK_CLOCK_GATE_INDEX_SSI1 :
                                              DDK_CLOCK_GATE_INDEX_SSI2,
                          DDK_CLOCK_GATE_MODE_ENABLED_ALL);

    // Disable the SSI transmitter and receiver.
    CLRREG32(&pSSI->SCR, CSP_BITFMASK(SSI_SCR_RE) | CSP_BITFMASK(SSI_SCR_TE));

    // Also mask all transmitter and receiver timeslots until we are ready
    // to begin a new audio I/O operation.
    OUTREG32(&pSSI->STMSK, 0xffffffff);
    OUTREG32(&pSSI->SRMSK, 0xffffffff);

    // Next disable the SSI so that we can reconfigure the items that
    // can only be changed when the SSI is disabled.
    //
    // We should do this as a separate write from disabling the SSI
    // transmitter and receiver in order to give the SSI sufficient
    // time to properly complete the previous operation.
    CLRREG32(&pSSI->SCR, CSP_BITFMASK(SSI_SCR_SSIEN));

    // Disable all SSI interrupts and DMA requests.
    OUTREG32(&pSSI->SIER, 0);

    // Also disable the transmit and receive FIFOs so that we can configure
    // the DMA watermark levels later.
    OUTREG32(&pSSI->STCR, 0);
    OUTREG32(&pSSI->SRCR, 0);

    // We can now begin reconfiguring the SSI for synchronous 4-wire mode.
    // Since we are using synchronous mode, we only need to configure the
    // SSI transmitter clock in the following stages and there is no need to
    // separately set the SSI receiver clock configuration.
    //
    // The SSI is configured for I2S master/slave mode here or just "normal"
    // mode if NETWORK_MODE was selected for the audio bus above. All clock
    // configuration settings for the selected master/slave mode will be done
    // later.
    //
    // The SSI SYS_CLK clock output is also disabled here since we do not need
    // it (even if the SSI is operated in master mode since the slave device
    // just needs to receive the framesync and bitclock signals).
    //
    // We never use 2-channel mode (where both FIFO0 and FIFO1 are active) so
    // that is disabled here as well.
    OUTREG32(&pSSI->SCR,
        CSP_BITFVAL(SSI_SCR_CLK_IST, SSI_SCR_CLK_IST_HIGH)      |
        CSP_BITFVAL(SSI_SCR_TCH_EN, SSI_SCR_TCH_EN_2CHAN_OFF)   |
        CSP_BITFVAL(SSI_SCR_SYS_CLK_EN, SSI_SCR_SYS_CLK_EN_OFF) |
        CSP_BITFVAL(SSI_SCR_I2S_MODE, ssi_i2s_mode)             |
        CSP_BITFVAL(SSI_SCR_SYN, SSI_SCR_SYN_SYNC)              |
        CSP_BITFVAL(SSI_SCR_NET, ssi_scr_net_mode));

    // Next, continue with the configuration of the SSI transmitter for
    // audio output/playback.
    //
    // To match the PMIC's capabilities, the SSI transmitter must transmit
    // 16-bit words MSB-first. Only FIFO0 is needed for single channel
    // operation but we leave it disabled until we are actually ready to
    // start an audio I/O operation.
    //
    // Also, the SSI transmitter's clock and framesync signals are configured
    // here for the slave mode of operation. We will change this later if it
    // turns out that the SSI is to be operated in bus master mode.
    //
    // Note that we must configure the transmitter's clock settings even if we
    // never use this SSI for audio playback because we are using synchronous
    // mode where the receiver shares the same clock configuration as the
    // transmitter.
    OUTREG32(&pSSI->STCR,
        CSP_BITFVAL(SSI_STCR_TXBIT0, SSI_STCR_TXBIT0_LSB_ALIGNED) |
        CSP_BITFVAL(SSI_STCR_TFEN1,  SSI_STCR_TFEN1_DISABLE)      |
        CSP_BITFVAL(SSI_STCR_TFEN0,  SSI_STCR_TFEN0_DISABLE)      |
        CSP_BITFVAL(SSI_STCR_TFDIR,  SSI_STCR_TFDIR_EXTERNAL)     |
        CSP_BITFVAL(SSI_STCR_TXDIR,  SSI_STCR_TXDIR_EXTERNAL)     |
        CSP_BITFVAL(SSI_STCR_TSHFD,  SSI_STCR_TSHFD_MSB_FIRST)    |
        CSP_BITFVAL(SSI_STCR_TSCKP,  ssi_stcr_tsckp_mode)         |
        CSP_BITFVAL(SSI_STCR_TFSI,   ssi_stcr_tfsi_mode)          |
        CSP_BITFVAL(SSI_STCR_TFSL,   ssi_stcr_tfsl_mode)          |
        CSP_BITFVAL(SSI_STCR_TEFS,   SSI_STCR_TEFS_EARLY)
    );

    // Now configure the SSI receiver for audio input/recording.
    //
    // To match the PMIC's capabilities, the SSI receiver must receive
    // 16-bit words MSB-first. Only FIFO0 is needed for single channel
    // operation but we leave it disabled until we are actually ready to
    // start an audio I/O operation.
    //
    // Also, we skip configuring the SSI receiver's clock settings here because
    // we're using synchronous mode and we've already configured the SSI's
    // transmitter clock configuration above. The receiver will simply share
    // the same clock configuration as the transmitter.
    //
    // Note that it is harmless to also configure the SSI receiver even if we
    // never use this SSI for audio recording.
    OUTREG32(&pSSI->SRCR,
        CSP_BITFVAL(SSI_SRCR_RXBIT0, SSI_SRCR_RXBIT0_LSB_ALIGNED) |
        CSP_BITFVAL(SSI_SRCR_RFEN1,  SSI_SRCR_RFEN1_DISABLE)      |
        CSP_BITFVAL(SSI_SRCR_RFEN0,  SSI_SRCR_RFEN0_DISABLE)      |
        CSP_BITFVAL(SSI_SRCR_RSHFD,  SSI_SRCR_RSHFD_MSB_FIRST)
    );

    // Set the Receive FIFO empty and Transmit FIFO full watermark levels.
    //
    // We set the watermark levels for both FIFO0 and FIFO1 here even though
    // we will only use FIFO0 because all of the watermark levels must be set
    // to a valid value (regardless of whether the FIFO is used or not) in
    // order for the SSI to operate properly.
    OUTREG32(&pSSI->SFCSR,
        CSP_BITFVAL(SSI_SFCSR_RFWM0, SSI_SFCSR_RX_WATERMARK) |
        CSP_BITFVAL(SSI_SFCSR_TFWM0, SSI_SFCSR_TX_WATERMARK) |
        CSP_BITFVAL(SSI_SFCSR_RFWM1, SSI_SFCSR_RX_WATERMARK) |
        CSP_BITFVAL(SSI_SFCSR_TFWM1, SSI_SFCSR_TX_WATERMARK));

    BOOL bSSI1 = BSP_SSI1_MASTER_BOOL;
    BOOL bSSI2 = BSP_SSI2_MASTER_BOOL;
    if (((pSSI == m_pSSI1) && bSSI1) ||
        ((pSSI == m_pSSI2) && bSSI2))
    {
        // First configure the Clock Control Module to route the correct
        // system clock signal to the SSI.
        if (pSSI == VOICE_CODEC_SSI)
        {
            // We want a 16 kHz sampling rate from the SSI for the Voice CODEC.
            //
            // The calculation of the appropriate SSI clock divider constants
            // is as follows:
            //
            //    Ideal Sampling Rate  = 16 kHz
            //    Oversampling Rate    = 384
            //    Ideal MCLK Frequency = 16 kHz * 384 = 6.144 MHz
            //    Serial PLL Frequency = 220.1472 MHz
            //
            //    SSI Divider = 220.1472 / 6.144 = 35.83125
            //                = 36 (rounded up)
            //
            // So that means we can set the predivider to divide-by-one (by
            // just writing 0) and the postdivider to divide-by-thirty-six
            // (which actually means writing a 35 to the postdivider control
            // register).
            DDKClockConfigBaud((pSSI == m_pSSI1) ? DDK_CLOCK_SIGNAL_SSI1 :
                                                   DDK_CLOCK_SIGNAL_SSI2,
                               (pSSI == m_pSSI1) ? SSI1_MASTER_CLOCK_SOURCE :
                                                   SSI2_MASTER_CLOCK_SOURCE,
                               0, 35);

            // The associated SSI internal clock divider constants are:
            //
            //    Ideal Sampling Rate          = 16 kHz
            //    Bits/Word                    = 16
            //    Words/Frame                  = 4
            //    Ideal Bit Clock Frequency    = 16 kHz * 16 * 4 = 1.024 MHz
            //    SSI Internal Clock Frequency = 6.144 MHz (from above)
            //    SSI Internal Divider         = 6.144 / 1.024 = 6
            //
            // An internal divider value of 6 can be achieved by setting the
            // following:
            //
            //    DIV2 = 0
            //    PSR  = 0
            //    PM   = 2
            //
            // We also set the following to configure for bits/word and
            // words/frame:
            //
            //    DC = 3  (for network mode with 4 words/frame)
            //       = 1  (for I2S mode with 2 words/frame)
            //    WL = 3  (for 8 bits/word)
            //       = 7  (for 16 bits/word)
            //       = 11 (for 24 bits/word)
            //
            OUTREG32(&pSSI->STCCR,
                CSP_BITFVAL(SSI_STCCR_DIV2, SSI_STCCR_DIV2_BYPASS)    |
                CSP_BITFVAL(SSI_STCCR_PSR, SSI_STCCR_PSR_DIV8_BYPASS) |
                CSP_BITFVAL(SSI_STCCR_WL, ssi_stccr_wl)               |
                CSP_BITFVAL(SSI_STCCR_DC, ssi_stccr_dc_mode)          |
                CSP_BITFVAL(SSI_STCCR_PM, 2));
        }
        else if (pSSI == STEREO_DAC_SSI)
        {
            // We want a 44.1 kHz sampling rate from the SSI for the Stereo DAC.
            //
            // The calculation of the appropriate SSI clock divider constants
            // is done as follows:
            //
            //    Ideal Sampling Rate  = 44.1 kHz
            //    Oversampling Rate    = 384
            //    Ideal MCLK Frequency = 44.1 kHz * 384 = 16.9344 MHz
            //    Serial PLL Frequency = 220.1472 MHz
            //
            //    SSI Divider = 220.1472 / 16.9344 = 13
            //
            // So that means we can set the predivider to divide-by-one (by
            // just writing 0) and the postdivider to divide-by-thirteen
            // (which actually means writing a 12 to the postdivider control
            // register).
            //
            // This will produce the exact 16.9344 MHz clock that we need
            // as the input to the SSI.
            DDKClockConfigBaud((pSSI == m_pSSI1) ? DDK_CLOCK_SIGNAL_SSI1 :
                                                   DDK_CLOCK_SIGNAL_SSI2,
                               (pSSI == m_pSSI1) ? SSI1_MASTER_CLOCK_SOURCE :
                                                   SSI2_MASTER_CLOCK_SOURCE,
                               0, 12);

            // The associated SSI internal clock divider constants are
            // calculated as follows:
            //
            //    Ideal Sampling Rate              = 44.1 kHz
            //
            //    For NETWORK mode only:
            //        Bits/Word                    = 16
            //        Words/Frame                  = 4
            //        Ideal Bit Clock Frequency    = 44.1 kHz * 16 * 4
            //                                     = 2.8224 MHz
            //        SSI Internal Clock Frequency = 16.9344 MHz (from above)
            //        SSI Internal Divider         = 16.9344 / 2.8224 = 6
            //
            //    For I2S mode only:
            //        Bits/Word                    = 32
            //        Words/Frame                  = 2
            //        Ideal Bit Clock Frequency    = 44.1 kHz * 32 * 2
            //                                     = 2.8224 MHz
            //        SSI Internal Clock Frequency = 16.9344 MHz (from above)
            //        SSI Internal Divider         = 16.9344 / 2.8224 = 6
            //
            // The corresponding SSI internal divider settings are as follows:
            //
            //    For both NETWORK and I2S modes:
            //        DIV2 = 0
            //        PSR  = 0
            //        PM   = 2
            //
            // We also set the following to configure for bits/word and
            // either 4 or 2 words/frame:
            //
            //    DC = 3 (for network mode with 4 words/frame)
            //       = 1 (for I2S mode 2 words/frame)
            //    WL = 3  (for 8 bits/word)
            //       = 7  (for 16 bits/word)
            //       = 11 (for 24 bits/word)
            //
            // Note that for the I2S mode, the word length is fixed at 32
            // bits/word and the WL value only serves to indicate how many
            // out of the 32 bits/word are actually valid.
            OUTREG32(&pSSI->STCCR,
                CSP_BITFVAL(SSI_STCCR_DIV2, SSI_STCCR_DIV2_BYPASS)    |
                CSP_BITFVAL(SSI_STCCR_PSR, SSI_STCCR_PSR_DIV8_BYPASS) |
                CSP_BITFVAL(SSI_STCCR_WL, ssi_stccr_wl)               |
                CSP_BITFVAL(SSI_STCCR_DC, ssi_stccr_dc_mode)          |
                CSP_BITFVAL(SSI_STCCR_PM, 2));
        }

        // Also set the SSI transmitter to use it's internal clock source to
        // generate the bit clock and framesync signals for master mode.
        SETREG32(&pSSI->STCR, CSP_BITFMASK(SSI_STCR_TXDIR));
        SETREG32(&pSSI->STCR, CSP_BITFMASK(SSI_STCR_TFDIR));
    }
    else
    {
        // Configure the SSI transmit clock for slave mode operation where
        // the internal divider and prescaler parameters are irrelevant. The
        // key settings are DC to select words/frame and WL for bits/word to
        // match the PMIC's configuration.
        OUTREG32(&pSSI->STCCR,
            CSP_BITFVAL(SSI_STCCR_DIV2, SSI_STCCR_DIV2_BYPASS)    |
            CSP_BITFVAL(SSI_STCCR_PSR, SSI_STCCR_PSR_DIV8_BYPASS) |
            CSP_BITFVAL(SSI_STCCR_WL, ssi_stccr_wl)               |
            CSP_BITFVAL(SSI_STCCR_DC, ssi_stccr_dc_mode)          |
            CSP_BITFVAL(SSI_STCCR_PM, 0));

        // We have already configured the SSI transmitter to use an external
        // clock and framesync so nothing needs to be done here.
    }

    // Leave the SSI in a disabled state until we are actually ready to perform
    // audio playback or recording.
    //
    // Also note that we leave all of the transmit and receive timeslots masked
    // at this time and only unmask the required timeslots when we call either
    // BSPAudioStartSsiOutput() or BSPAudioStartSsiInput().

    // Disable SSI clocks to minimize power consumption.
    DDKClockSetGatingMode((pSSI == m_pSSI1) ? DDK_CLOCK_GATE_INDEX_SSI1 :
                                              DDK_CLOCK_GATE_INDEX_SSI2,
                          DDK_CLOCK_GATE_MODE_DISABLED);
}


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioStartSsiOutput
//
//  This function configures SSI to start audio output.
//
//  Parameters:
//      pSSI
//          [in] Points to SSI to be configured.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
void HardwareContext::BSPAudioStartSsiOutput(PCSP_SSI_REG pSSI,
                                             const HWSAMPLE *const ssiFifoPrefill,
                                             const unsigned nSsiFifoPrefill)
{
    // Enable SSI clocks before we access the SSI registers.
    DDKClockSetGatingMode((pSSI == m_pSSI1) ? DDK_CLOCK_GATE_INDEX_SSI1 :
                                              DDK_CLOCK_GATE_INDEX_SSI2,
                          DDK_CLOCK_GATE_MODE_ENABLED_ALL);

    // Enable the SSI and the transmit FIFO0 so that we can fill it up before
    // we enable the transmitter later.
    SETREG32(&pSSI->SCR, CSP_BITFMASK(SSI_SCR_SSIEN));
    SETREG32(&pSSI->STCR, CSP_BITFMASK(SSI_STCR_TFEN0));

    if ((ssiFifoPrefill != NULL) && (nSsiFifoPrefill > 0))
    {
    // Prefill the transmit FIFO so that there is something ready to go when
    // we enable the transmitter in the next step.
    for (unsigned i = 0; i < nSsiFifoPrefill; i++)
    {
        OUTREG32(&pSSI->STX0, *(ssiFifoPrefill + i));
    }
    }

    // Enable only the first two transmit timeslots (for the left+right audio
    // channels).
    OUTREG32(&pSSI->STMSK, ~0x3);

    // The SSI has already been properly configured, we just need to enable
    // the SSI transmitter DMA request and the transmitter to begin the
    // audio output/playback process.
    SETREG32(&pSSI->SIER, CSP_BITFMASK(SSI_SIER_TDMAE));
    SETREG32(&pSSI->SCR, CSP_BITFMASK(SSI_SCR_TE));
}


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioStopSsiOutput
//
//  This function configures SSI to stop audio output.
//
//  Parameters:
//      pSSI
//          [in] Points to SSI to be configured.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
void HardwareContext::BSPAudioStopSsiOutput(PCSP_SSI_REG pSSI)
{
    UINT32 scr;

    // Start by disabling the SSI transmitter.
    CLRREG32(&pSSI->SCR, CSP_BITFMASK(SSI_SCR_TE));

    // Disable the SSI transmit DMA request.
    CLRREG32(&pSSI->SIER, CSP_BITFMASK(SSI_SIER_TDMAE));

    // Disable the transmit FIFO0 and mask all transmit timeslots.
    CLRREG32(&pSSI->STCR, CSP_BITFMASK(SSI_STCR_TFEN0));
    OUTREG32(&pSSI->STMSK, 0xffffffff);

    // If the SSI receiver is also disabled, then turn off the entire SSI.
    scr = INREG32(&pSSI->SCR);
    if (!(scr & CSP_BITFMASK(SSI_SCR_RE)))
    {
        // Completely disable the SSI.
        CLRREG32(&pSSI->SCR, CSP_BITFMASK(SSI_SCR_SSIEN));

        // Disable SSI clocks to minimize power consumption.
        DDKClockSetGatingMode((pSSI == m_pSSI1) ? DDK_CLOCK_GATE_INDEX_SSI1 :
                                                  DDK_CLOCK_GATE_INDEX_SSI2,
                              DDK_CLOCK_GATE_MODE_DISABLED);
    }
}


#ifdef AUDIO_RECORDING_ENABLED

//-----------------------------------------------------------------------------
//
//  Function: BSPAudioStartSsiInput
//
//  This function configures SSI to start audio input.
//
//  Parameters:
//      pSSI
//          [in] Points to SSI to be configured.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
void HardwareContext::BSPAudioStartSsiInput(PCSP_SSI_REG pSSI)
{
    // Enable SSI clocks before we access the SSI registers.
    DDKClockSetGatingMode((pSSI == m_pSSI1) ? DDK_CLOCK_GATE_INDEX_SSI1 :
                                              DDK_CLOCK_GATE_INDEX_SSI2,
                          DDK_CLOCK_GATE_MODE_ENABLED_ALL);

    // Start off by enabling the SSI. This must be done first.
    SETREG32(&pSSI->SCR, CSP_BITFMASK(SSI_SCR_SSIEN));

    // Next, enable the SSI receiver DMA request and RX FIFO0.
    OUTREG32(&pSSI->SIER, CSP_BITFVAL(SSI_SIER_RFF0_EN, TRUE));
    SETREG32(&pSSI->SRCR, CSP_BITFMASK(SSI_SRCR_RFEN0));

    // Unmask only the first two receive timeslots (for the primary and
    // secondary audio recording channels).
    OUTREG32(&pSSI->SRMSK, ~0x3);

    // The SSI has already been properly configured, we just need to enable
    // the SSI receiver DMA request and the receiver to begin the audio
    // input/recording process.
    SETREG32(&pSSI->SIER, CSP_BITFMASK(SSI_SIER_RDMAE));
    SETREG32(&pSSI->SCR, CSP_BITFMASK(SSI_SCR_RE));
}


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioStopSsiInput
//
//  This function configures SSI to stop audio input.
//
//  Parameters:
//      pSSI
//          [in] Points to SSI to be configured.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
void HardwareContext::BSPAudioStopSsiInput(PCSP_SSI_REG pSSI)
{
    UINT32 scr;

    // Start by disabling the SSI receiver.
    CLRREG32(&pSSI->SCR, CSP_BITFMASK(SSI_SCR_RE));

    // Disable the SSI receive DMA request
    CLRREG32(&pSSI->SIER, CSP_BITFMASK(SSI_SIER_RDMAE));

    // Disable the receive FIFO0 and mask all receive timeslots.
    CLRREG32(&pSSI->SRCR, CSP_BITFMASK(SSI_SRCR_RFEN0));
    OUTREG32(&pSSI->SRMSK, 0xffffffff);

    // If the transmitter is also disabled, then turn off the entire SSI.
    scr = INREG32(&pSSI->SCR);
    if (!(scr & CSP_BITFMASK(SSI_SCR_TE)))
    {
        // Completely disable the SSI.
        CLRREG32(&pSSI->SCR, CSP_BITFMASK(SSI_SCR_SSIEN));

        // Disable SSI clocks to minimize power consumption.
        DDKClockSetGatingMode((pSSI == m_pSSI1) ? DDK_CLOCK_GATE_INDEX_SSI1 :
                                                  DDK_CLOCK_GATE_INDEX_SSI2,
                              DDK_CLOCK_GATE_MODE_DISABLED);
    }
}

#endif // #ifdef AUDIO_RECORDING_ENABLED


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioSetCodecPower
//
//  This function sets the power state of the external audio chip.
//
//  Parameters:
//      state
//          [in] - Specifies the desired power state.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
void HardwareContext::BSPAudioSetCodecPower(AUDIO_PWR_STATE state)
{
    UINT32 regMode0, audRx0, mask;

    // Check if power state transition is required
    if (state == m_CodecPwrState) return;

    switch (state)
    {
        case AUDIO_PWR_STATE_OFF:

            regMode0 = CSP_BITFVAL(MC13783_REG_MODE0_VAUDIOEN, FALSE)  |
                       CSP_BITFVAL(MC13783_REG_MODE0_VAUDIOSTBY, TRUE) |
                       CSP_BITFVAL(MC13783_REG_MODE0_VAUDIOMODE, FALSE);

            audRx0 = CSP_BITFVAL(MC13783_AUD_RX0_VAUDIOON,
                                 MC13783_AUD_RX0_VAUDIOON_DISABLE) |
                     CSP_BITFVAL(MC13783_AUD_RX0_BIASEN,
                                 MC13783_AUD_RX0_BIASEN_DISABLE)   |
                     CSP_BITFVAL(MC13783_AUD_RX0_BIASSPEED,
                                 MC13783_AUD_RX0_BIASSPEED_ENABLE);

            break;

        case AUDIO_PWR_STATE_STANDBY:
#ifdef AUDIO_RECORDING_ENABLED
            // Only allow transition to standby state if both input
            // and output are inactive.
            if (m_OutputDMARunning
                    || g_pRecordContext->m_InputDMARunning) return;
#else
            // Only allow transition to standby state if output is inactive.
            if (m_OutputDMARunning) return;
#endif

            regMode0 = CSP_BITFVAL(MC13783_REG_MODE0_VAUDIOEN, TRUE)    |
                       CSP_BITFVAL(MC13783_REG_MODE0_VAUDIOSTBY, FALSE) |
                       CSP_BITFVAL(MC13783_REG_MODE0_VAUDIOMODE, TRUE);

            audRx0 = CSP_BITFVAL(MC13783_AUD_RX0_VAUDIOON,
                                 MC13783_AUD_RX0_VAUDIOON_DISABLE) |
                     CSP_BITFVAL(MC13783_AUD_RX0_BIASEN,
                                 MC13783_AUD_RX0_BIASEN_ENABLE)    |
                     CSP_BITFVAL(MC13783_AUD_RX0_BIASSPEED,
                                 MC13783_AUD_RX0_BIASSPEED_ENABLE);
            break;

        case AUDIO_PWR_STATE_ON:

            regMode0 = CSP_BITFVAL(MC13783_REG_MODE0_VAUDIOEN, TRUE)    |
                       CSP_BITFVAL(MC13783_REG_MODE0_VAUDIOSTBY, FALSE) |
                       CSP_BITFVAL(MC13783_REG_MODE0_VAUDIOMODE, FALSE);

            audRx0 = CSP_BITFVAL(MC13783_AUD_RX0_VAUDIOON,
                                 MC13783_AUD_RX0_VAUDIOON_FORCE) |
                     CSP_BITFVAL(MC13783_AUD_RX0_BIASEN,
                                 MC13783_AUD_RX0_BIASEN_ENABLE)  |
                     CSP_BITFVAL(MC13783_AUD_RX0_BIASSPEED,
                                 MC13783_AUD_RX0_BIASSPEED_ENABLE);
            break;

        default:

            ERRORMSG(TRUE, (_T("BSPAudioSetCodecPower: Invalid power state ")
                            _T("%d requested, request ignored\r\n"),
                            state));
            return;

    }

    mask = CSP_BITFMASK(MC13783_REG_MODE0_VAUDIOEN)   |
           CSP_BITFMASK(MC13783_REG_MODE0_VAUDIOSTBY) |
           CSP_BITFMASK(MC13783_REG_MODE0_VAUDIOMODE);

    PmicRegisterWrite(MC13783_REG_MOD0_ADDR, regMode0, mask);

    mask = CSP_BITFMASK(MC13783_AUD_RX0_VAUDIOON) |
           CSP_BITFMASK(MC13783_AUD_RX0_BIASEN)   |
           CSP_BITFMASK(MC13783_AUD_RX0_BIASSPEED);

    PmicRegisterWrite(MC13783_AUD_RX0_ADDR, audRx0, mask);

    // When transitioning to/from the OFF state, we must allow sufficient
    // time for the audio bias circuit to stabilize.
    if (m_CodecPwrState == AUDIO_PWR_STATE_OFF)
    {
        // FIXME: Temporary access to timed delay function. This will
        //        eventually be hidden internally within the PMIC audio
        //        driver once power management functions have also been
        //        added to the low-level PMIC audio driver.
        //
        PmicAudioTimedDelay(MC13783_AUD_ENB_DELAY_BIAS_OFF);
    }
    else if (state == AUDIO_PWR_STATE_OFF)
    {
        // FIXME: Temporary access to timed delay function. This will
        //        eventually be hidden internally within the PMIC audio
        //        driver once power management functions have also been
        //        added to the low-level PMIC audio driver.
        //
        PmicAudioTimedDelay(MC13783_AUD_ENB_DELAY_BIAS_ON);
    }

    m_CodecPwrState = state;
}


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioInitCodec
//
//  This function initializes codec for the specified audio bus.
//
//  Parameters:
//      The SSI configuration to be used.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
void HardwareContext::BSPAudioInitCodec(AUDIO_BUS bus)
{
    BOOL bSSI1, bSSI2;

    switch (bus)
    {
        case AUDIO_BUS_STEREO_OUT:

            PmicAudioReset(hStDAC);

            // Power up the external audio chip if necessary.
            if (m_CodecPwrState == AUDIO_PWR_STATE_OFF)
            {
                BSPAudioSetCodecPower(AUDIO_PWR_STATE_STANDBY);
            }

            // Configure the PMIC Stereo DAC clock and data bus modes.
            //
            // This is the standard Stereo DAC audio I/O configuration:
            //
            //     - SSI2 to Audio MUX Internal Port 2
            //     - Audio MUX Internal Port 2 to Audio MUX External Port 5
            //     - Audio MUX External Port 5 to PMIC Digital Audio Bus 2
            //
            PmicAudioSetProtocol(hStDAC,
                                 STEREO_DAC_AUDIO_BUS,
                                 STEREO_DAC_BUS_MODE,
                                 (((STEREO_DAC_SSI == m_pSSI1) &&
                                   (BSP_SSI1_MASTER_BOOL))        ||
                                  ((STEREO_DAC_SSI == m_pSSI2) &&
                                   (BSP_SSI2_MASTER_BOOL))) ? BUS_SLAVE_MODE :
                                                              BUS_MASTER_MODE,
                                 (STEREO_DAC_BUS_MODE == NETWORK_MODE) ?
                                     USE_4_TIMESLOTS : USE_2_TIMESLOTS);

            bSSI1 = BSP_SSI1_MASTER_BOOL;
            bSSI2 = BSP_SSI2_MASTER_BOOL;
            if (((STEREO_DAC_SSI == m_pSSI1) && bSSI1) ||
                ((STEREO_DAC_SSI == m_pSSI2) && bSSI2))
            {
                // Configure the PMIC Stereo DAC for slave mode using the
                // bitclock signal as the reference clock input.
                PmicAudioStdacSetClock(hStDAC,
                                       CLOCK_IN_BITCLK,
                                       STDAC_BCLK_IN_PLL,
                                       STDAC_RATE_44_1_KHZ,
                                       NO_INVERT);
            }
            else
            {
                // Configure the PMIC Stereo DAC for master mode using the CLIA
                // signal as the reference clock input.
                PmicAudioStdacSetClock(hStDAC,
                                       CLOCK_IN_CLIA,
                                       STDAC_CLI_13MHZ,
                                       STDAC_RATE_44_1_KHZ,
                                       NO_INVERT);
            }

            // Set the default output amplifier gain to its mid-point setting
            // of -6 dB (the gain range is from -33 dB to 6 dB in 3 dB steps).
            PmicAudioOutputSetPgaGain(hStDAC, DEFAULT_OUTPGA_GAIN);

            // Defer any other audio output section configuration until start
            // of playback (i.e., when BSPAudioStartCodecOutput() is called).

            break;

#ifdef AUDIO_RECORDING_ENABLED

        case AUDIO_BUS_VOICE_IN:

            PmicAudioReset(hVoiceCODEC);

            // Power up the external audio chip if necessary.
            if (m_CodecPwrState == AUDIO_PWR_STATE_OFF)
            {
                BSPAudioSetCodecPower(AUDIO_PWR_STATE_STANDBY);
            }

            // Configure the PMIC Voice CODEC clock and data bus modes.
            //
            // This is the standard Voice CODEC audio I/O configuration:
            //
            //     - SSI1 to Audio MUX Internal Port 1
            //     - Audio MUX Internal Port 1 to Audio MUX External Port 4
            //     - Audio MUX External Port 4 to PMIC Digital Audio Bus 1
            //
            PmicAudioSetProtocol(hVoiceCODEC,
                                 VOICE_CODEC_AUDIO_BUS,
                                 VOICE_CODEC_BUS_MODE,
                                 (((VOICE_CODEC_SSI == m_pSSI1) &&
                                   (BSP_SSI1_MASTER_BOOL))         ||
                                  ((VOICE_CODEC_SSI == m_pSSI2) &&
                                   (BSP_SSI2_MASTER_BOOL))) ? BUS_SLAVE_MODE :
                                                              BUS_MASTER_MODE,
                                 (VOICE_CODEC_BUS_MODE == NETWORK_MODE) ?
                                     USE_4_TIMESLOTS : USE_2_TIMESLOTS);

            bSSI1 = BSP_SSI1_MASTER_BOOL;
            bSSI2 = BSP_SSI2_MASTER_BOOL;
            if (((VOICE_CODEC_SSI == m_pSSI1) && bSSI1) ||
                ((VOICE_CODEC_SSI == m_pSSI2) && bSSI2))
            {
                // Configure the PMIC Voice CODEC for slave mode using the
                // bitclock signal as the reference clock input.
                PmicAudioVcodecSetClock(hVoiceCODEC,
                                        CLOCK_IN_CLIA,
                                        VCODEC_CLI_13MHZ,
                                        VCODEC_RATE_16_KHZ,
                                        NO_INVERT);
            }
            else
            {
                // Configure the PMIC Voice CODEC for master mode using the CLIA
                // signal as the reference clock input.
                PmicAudioVcodecSetClock(hVoiceCODEC,
                                        CLOCK_IN_CLIA,
                                        VCODEC_CLI_13MHZ,
                                        VCODEC_RATE_16_KHZ,
                                        NO_INVERT);
            }

            // Defer any audio input section configuration until the start of
            // recording (i.e., when BSPAudioStartCodecInput() is called).

#endif // #ifdef AUDIO_RECORDING_ENABLED

    }
}


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioStartCodecOutput
//
//  This function configures the audio chip to start audio output.
//
//  Parameters:
//      bus
//          [in] Specifies the audio bus to be used for audio output.
//
//      path
//          [in] Specifies the audio path to be used for audio output.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
void HardwareContext::BSPAudioStartCodecOutput(AUDIO_BUS bus, AUDIO_PATH path)
{
    HKEY    hRegKey             = NULL;
    LPCWSTR regSubkeyHeadset    = AUDIO_REGKEY_PREFIX TEXT("\\HeadsetDetect");
    LPCWSTR regValueNameHeadset = TEXT("Enable");
    LPCWSTR regSubkeyPlayback   = AUDIO_REGKEY_PREFIX TEXT("\\Playback");
    LPCWSTR regValueNameL       = TEXT("LeftChannel");
    LPCWSTR regValueNameR       = TEXT("RightChannel");
    LPCWSTR regValueNameMAdder  = TEXT("MonoAdder");
    DWORD   regValueL           = 0;
    DWORD   regValueR           = 0;
    DWORD   regValue            = 0;
    DWORD   regValueType        = 0;
    DWORD   regValueSize        = sizeof(DWORD);
    PMIC_AUDIO_MONO_ADDER_MODE  monoAdderConf = MONO_ADDER_OFF;
    PMIC_AUDIO_OUTPUT_PORT port = PMIC_AUDIO_OUTPUT_PORT(STEREO_HEADSET_LEFT |
                                                         STEREO_HEADSET_RIGHT);
    // Remove-W4: Warning C4100 workaround
    UNREFERENCED_PARAMETER(path);

    switch (bus)
    {
        case AUDIO_BUS_STEREO_OUT:

            // Power up the audio chip
            BSPAudioSetCodecPower(AUDIO_PWR_STATE_ON);

            // Enable the output mixer to include the Stereo DAC output.
            PmicAudioOutputEnableMixer(hStDAC);

            // Check registry settings to determine which output path is to
            // be enabled. Default is to use the stereo headset left+right
            // output paths (which is already the default value for "port").
            if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                             regSubkeyPlayback,
                             0,
                             0,
                             &hRegKey) == ERROR_SUCCESS)
            {
                if ((RegQueryValueEx(hRegKey,
                                     regValueNameL,
                                     NULL,
                                     &regValueType,
                                     (LPBYTE)&regValueL,
                                     &regValueSize) == ERROR_SUCCESS) &&
                    (RegQueryValueEx(hRegKey,
                                     regValueNameR,
                                     NULL,
                                     &regValueType,
                                     (LPBYTE)&regValueR,
                                     &regValueSize) == ERROR_SUCCESS))
                {
                    if ((regValueType == REG_DWORD) && (regValueR == 1))
                    {
                        // Enable the mono speaker output.
                        port = MONO_SPEAKER;
                    }
                    else if ((regValueType == REG_DWORD) && (regValueR == 2))
                    {
                        // Enable the loudspeaker output.
                        port = MONO_LOUDSPEAKER;
                    }
                    else if ((regValueType == REG_DWORD) &&
                             (regValueL == 16) || (regValueR == 16))
                    {
                        // Enable the Voice CODEC output.
                        port = MONO_CDCOUT;
                    }
                    else if ((regValueType == REG_DWORD) && (regValueL == 32))
                    {
                        // Enable the stereo left low power output.
                        port = STEREO_LEFT_LOW_POWER;
                    }
                    else if ((regValueType == REG_DWORD) && (regValueL == 64))
                    {
                        // Check if we want both left+right channels or just
                        // the left channel.
                        if (regValueR == 128)
                        {
                            // Enable both the stereo headset left and right
                            // outputs.
                            port = PMIC_AUDIO_OUTPUT_PORT(STEREO_HEADSET_LEFT |
                                                          STEREO_HEADSET_RIGHT);
                        }
                        else
                        {
                            // Only enable the stereo headset left output.
                            port = PMIC_AUDIO_OUTPUT_PORT(STEREO_HEADSET_LEFT);
                        }
                    }
                    else if ((regValueType == REG_DWORD) && (regValueR == 128))
                    {
                        // Only enable the stereo headset right output.
                        //
                        // Note that we've already handled the combined
                        // left+right headset output case above.
                        port = PMIC_AUDIO_OUTPUT_PORT(STEREO_HEADSET_RIGHT);
                    }
                    else if ((regValueType == REG_DWORD) && (regValueL == 256))
                    {
                        if (regValueR == 512)
                        {
                            // Enable both left+right external stereo outputs.
                            port = PMIC_AUDIO_OUTPUT_PORT(
                                       STEREO_OUT_LEFT | STEREO_OUT_RIGHT);
                        }
                        else
                        {
                            // Enable only the left external stereo output.
                            port = STEREO_OUT_LEFT;
                        }
                    }
                    else if ((regValueType == REG_DWORD) && (regValueR == 512))
                    {
                        // Enable only the right external stereo output.
                        port = STEREO_OUT_RIGHT;
                    }
                }

                // read mono adder configuration from registry
                if(RegQueryValueEx(hRegKey,
                                    regValueNameMAdder,
                                    NULL,
                                    &regValueType,
                                    (LPBYTE)&regValue,
                                    &regValueSize) == ERROR_SUCCESS)
                {
                    if (regValueType == REG_DWORD)
                    {
                        switch(regValue)
                        {
                        case 0:
                            monoAdderConf = MONO_ADDER_OFF;
                            break;
                        case 1:
                            monoAdderConf = MONO_ADD_LEFT_RIGHT;
                            break;
                        case 2:
                            monoAdderConf = MONO_ADD_OPPOSITE_PHASE;
                            break;
                        case 3:
                            monoAdderConf = STEREO_OPPOSITE_PHASE;
                            break;
                        default:
                            break;
                        }
                    }
                }

                RegCloseKey(hRegKey);
            }

            if (port == PMIC_AUDIO_OUTPUT_PORT(STEREO_HEADSET_LEFT |
                                               STEREO_HEADSET_RIGHT))
            {
                // Configure the Mono Adder for stereo output to
                // whatever is specified through the registry.
                PmicAudioOutputEnableMonoAdder(hStDAC, monoAdderConf);
            }

            // Enable the selected audio output path.
            PmicAudioOutputSetPort(hStDAC, port);

            // Check registry settings to see how the automatic output
            // amplifier disable feature should be configured.
            if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                             regSubkeyHeadset,
                             0,
                             0,
                             &hRegKey) == ERROR_SUCCESS)
            {
                if (RegQueryValueEx(hRegKey,
                                    regValueNameHeadset,
                                    NULL,
                                    &regValueType,
                                    (LPBYTE)&regValue,
                                    &regValueSize) == ERROR_SUCCESS)
                {
                    if ((regValueType == REG_DWORD) && (regValue == 1))
                    {
                        // Enable the automatic output amplifier disable
                        // feature.
                        PmicAudioOutputSetConfig(
                            hStDAC, STEREO_HEADSET_AMP_AUTO_DISABLE);
                    }
                    else
                    {
                        // Disable the automatic output amplifier disable
                        // feature. This is also the default if the registry
                        // key value is neither 0 or 1.
                        PmicAudioOutputClearConfig(
                            hStDAC, STEREO_HEADSET_AMP_AUTO_DISABLE);
                    }
                }
                else
                {
                    // Disable the automatic output amplifier disable feature
                    // if the registry key value could not be read.
                    PmicAudioOutputClearConfig(
                        hStDAC, STEREO_HEADSET_AMP_AUTO_DISABLE);
                }

                RegCloseKey(hRegKey);
            }
            else
            {
                // If no registry key is defined, then default to disabling
                // the automatic output amplifier disable feature.
                PmicAudioOutputClearConfig(
                    hStDAC, STEREO_HEADSET_AMP_AUTO_DISABLE);
            }

            // Enable the PMIC Stereo DAC.
            PmicAudioEnable(hStDAC);

            break;
    }
}


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioStopCodecOutput
//
//  This function configures the audio chip to stop audio output.
//
//  Parameters:
//      bus
//          [in] Specifies the audio bus being used for audio output.
//
//      path
//          [in] Specifies the audio path being used for audio output.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
void HardwareContext::BSPAudioStopCodecOutput(AUDIO_BUS bus, AUDIO_PATH path)
{
    // Remove-W4: Warning C4100 workaround
    UNREFERENCED_PARAMETER(path);

    switch (bus)
    {
        case AUDIO_BUS_STEREO_OUT:

            // Disable the stereo headset output port. Do this first to avoid
            // any "pops" in the audio output when we proceed to disable the
            // Stereo DAC.
            PmicAudioOutputClearPort(hStDAC,
                PMIC_AUDIO_OUTPUT_PORT(STEREO_HEADSET_LEFT   |
                                       STEREO_HEADSET_RIGHT));

            // Disable the Mono Adder.
            PmicAudioOutputEnableMonoAdder(hStDAC, MONO_ADDER_OFF);

            // Disable the Stereo DAC output to the output mixer.
            PmicAudioOutputDisableMixer(hStDAC);

            // Note that we want to immediately disable the Stereo DAC if
            // we're in the process of powering down.
            if ((PlaybackDisableDelayMsec > 0)      &&

                (m_hAudioDelayedDisableThread != NULL) &&
                (m_hAudioDelayDisableEvent != NULL) &&
                !m_audioPowerdown)
            {
                if (m_AudioDelayDisableTimerID != NULL)
                {
                    // Cancel previous timer so that we can restart it later
                    // from the beginning.
                    timeKillEvent(m_AudioDelayDisableTimerID);
                    m_AudioDelayDisableTimerID = NULL;

                    DEBUGMSG(ZONE_TEST, (_T("Disabled previous playback ")
                                         _T("disable timer\n")));
                }

                // Start a timed delay. If the timer expires without us
                // starting another audio playback operation, then we
                // should really disable the audio CODEC hardware.
                m_AudioDelayDisableTimerID =
                    timeSetEvent(PlaybackDisableDelayMsec,
                                 1,
                                 (LPTIMECALLBACK)(m_hAudioDelayDisableEvent),
                                 NULL,
                                 TIME_ONESHOT | TIME_CALLBACK_EVENT_SET);

                DEBUGMSG(ZONE_TEST, (_T("Enabled playback disable timer\n")));
            }
            else
            {
                // We want to immediately disable the Stereo DAC at the end of
                // the playback operation.
                PmicAudioDisable(hStDAC);
            }

            // Power down the audio chip
            BSPAudioSetCodecPower(AUDIO_PWR_STATE_STANDBY);

            break;
    }
}


#ifdef AUDIO_RECORDING_ENABLED

//-----------------------------------------------------------------------------
//
//  Function: BSPAudioStartCodecInput
//
//  This function configures the audio chip to start audio input.
//
//  Parameters:
//      bus
//          [in] Specifies the audio bus to be used for audio input.
//
//      path
//          [in] Specifies the audio path to be used for audio input.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
void HardwareContext::BSPAudioStartCodecInput(AUDIO_BUS bus, AUDIO_PATH path)
{
   HKEY    hRegKey           = NULL;
   LPCWSTR regSubkeyRecord   = AUDIO_REGKEY_PREFIX TEXT("\\Recording");
   LPCWSTR regValueNameL     = TEXT("LeftChannel");
   LPCWSTR regValueNameR     = TEXT("RightChannel");
   DWORD   regValueL         = 0;
   DWORD   regValueR         = 0;
   LPCWSTR regSubkeyBias1    = AUDIO_REGKEY_PREFIX TEXT("\\MicBias1");
   LPCWSTR regSubkeyBias2    = AUDIO_REGKEY_PREFIX TEXT("\\MicBias2");
   LPCWSTR regValueNameBias  = TEXT("Enable");
   LPCWSTR regSubkeyInputAmp = AUDIO_REGKEY_PREFIX TEXT("\\InputAmp");
   LPCWSTR regValueNameMode  = TEXT("Mode");
   LPCWSTR regValueNameGain  = TEXT("Gain");
   DWORD   regValue          = 0;
   DWORD   regValueType      = 0;
   DWORD   regValueSize      = sizeof(DWORD);

   PMIC_AUDIO_INPUT_MIC_STATE leftMic  = MICROPHONE_OFF;
   PMIC_AUDIO_INPUT_MIC_STATE rightMic = MICROPHONE_ON;
   PMIC_AUDIO_MIC_AMP_MODE    ampMode  = VOLTAGE_TO_VOLTAGE;
   PMIC_AUDIO_MIC_GAIN        ampGain  = MIC_GAIN_0DB;

   // Remove-W4: Warning C4100 workaround
   UNREFERENCED_PARAMETER(path);

   DEBUGMSG(ZONE_FUNCTION, (__WFUNCTION__ TEXT("+\r\n")));

   switch (bus) {
      case AUDIO_BUS_VOICE_IN:
         // Power up the audio chip.
         BSPAudioSetCodecPower(AUDIO_PWR_STATE_ON);

         // The PMIC Voice CODEC must be enabled prior to selecting (and
         // enabling) the microphone input paths.
         PmicAudioEnable(hVoiceCODEC);

         // Check registry settings to see which audio input path should
         // be used. Default is to use the MC13783 J3 (mono microphone) input.
         if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                          regSubkeyRecord,
                          0,
                          0,
                          &hRegKey) == ERROR_SUCCESS)
         {
            if ((RegQueryValueEx(hRegKey,
                                 regValueNameL,
                                 NULL,
                                 &regValueType,
                                 (LPBYTE)&regValueL,
                                 &regValueSize) == ERROR_SUCCESS) &&
                (RegQueryValueEx(hRegKey,
                                 regValueNameR,
                                 NULL,
                                 &regValueType,
                                 (LPBYTE)&regValueR,
                                 &regValueSize) == ERROR_SUCCESS))
            {
               if ((regValueType == REG_DWORD) && (regValueL == RXIN_STEREO) && (regValueR == RXIN_STEREO)) {
                  // Select the J10 LineIn.
                  PmicAudioVcodecSetMic(hVoiceCODEC,
                                        RXIN_STEREO,
                                        RXIN_STEREO);
//                  leftMic = MICROPHONE_ON;
               }
               else if ((regValueType == REG_DWORD) && (regValueL == 1) && (regValueR == 2)) {
                  // Select the MC13783 J4 stereo input path.
                  PmicAudioVcodecSetMic(hVoiceCODEC,
                                        MIC1_LEFT,
                                        MIC1_RIGHT_MIC_MONO);
                  leftMic = MICROPHONE_ON;
               }
               else {
#if 0
                  PmicAudioVcodecSetMic(hVoiceCODEC,
                                        NO_MIC,
                                        MIC2_AUX);
#else
                  PmicAudioVcodecSetMic(hVoiceCODEC,
                                        (PMIC_AUDIO_INPUT_PORT)regValueL,
                                        (PMIC_AUDIO_INPUT_PORT)regValueR);
#endif
               }
            }
            else {
                // Default to using the MC13783 J3 (mono microphone) input.
                PmicAudioVcodecSetMic(hVoiceCODEC,
                                      NO_MIC,
                                      MIC2_AUX);
            }

            RegCloseKey(hRegKey);
         }
         else {
            // Default to using the MC13783 J3 (mono microphone) input.
#if 0
            PmicAudioVcodecSetMic(hVoiceCODEC,
                                  NO_MIC,
                                  MIC2_AUX);
#else
            PmicAudioVcodecSetMic(hVoiceCODEC,
                                  RXIN_STEREO,
                                  RXIN_STEREO);
            leftMic = MICROPHONE_ON;
#endif
         }

         // Configure the timeslots to be used for audio recording.
         if ((leftMic == MICROPHONE_ON) && (rightMic == MICROPHONE_ON)) {
            // Configure for 2 channel recording.
            //
            // Note that we transmit the secondary channel in timeslot 0
            // (ahead of the primary channel) because the secondary input
            // channel corresponds to the left audio channel. The primary
            // channel corresponds to the right audio channel. Therefore,
            // the following timeslot configuration will preserve the
            // required order of left channel first followed immediately
            // by the right channel.
            PmicAudioVcodecSetRxtxTimeslot(hVoiceCODEC, USE_TS1);
            PmicAudioVcodecSetSecondaryTxslot(hVoiceCODEC, USE_TS0);
         }
         else {
            // Configure for 1 channel recording.
            //
            // The primary (right) channel is now sent in the first
            // timeslot and we switch the secondary TX timeslot to be
            // timeslot 1 just to be one the safe side (even though we
            // will not be using the secondary channel at all).
            PmicAudioVcodecSetRxtxTimeslot(hVoiceCODEC, USE_TS0);
            PmicAudioVcodecSetSecondaryTxslot(hVoiceCODEC, USE_TS1);
         }

         if (leftMic == MICROPHONE_ON) {
            // Check registry settings to see if the microphone bias 1
            // circuit should be enabled. This bias circuit is connected
            // to the MC13783 J4 (left and right) input jack.
            //
            // The default is to enable this circuit if the MIC1_LEFT and
            // MIC1_RIGHT_MIC_MONO inputs are being used.
            if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                             regSubkeyBias1,
                             0,
                             0,
                             &hRegKey) == ERROR_SUCCESS)
            {
               if ((RegQueryValueEx(hRegKey,
                                    regValueNameBias,
                                    NULL,
                                    &regValueType,
                                    (LPBYTE)&regValue,
                                    &regValueSize) == ERROR_SUCCESS))
               {
                  if ((regValueType == REG_DWORD) && (regValue == 0)) {
                     // Disable the bias 1 circuit.
                     PmicAudioVcodecDisableMicbias(hVoiceCODEC, MIC_BIAS1);
                  }
                  else {
                     // Default to enabling the bias 1 circuit.
                     PmicAudioVcodecEnableMicbias(hVoiceCODEC, MIC_BIAS1);
                  }
               }
               else {
                   // Default to enabling the bias 1 circuit.
                   PmicAudioVcodecEnableMicbias(hVoiceCODEC, MIC_BIAS1);
               }

               RegCloseKey(hRegKey);
            }
            else {
               // Default to enabling the bias 1 circuit.
               PmicAudioVcodecEnableMicbias(hVoiceCODEC, MIC_BIAS1);
            }
         }
         else {
            // Check registry settings to see if the microphone bias 2
            // circuit should be enabled. This bias circuit is connected
            // to the MC13783 J3 (mono microphone) input jack.
            //
            // The default is to enable this circuit if the MIC2_AUX
            // microphone input is being used.
            if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                             regSubkeyBias2,
                             0,
                             0,
                             &hRegKey) == ERROR_SUCCESS)
            {
               if ((RegQueryValueEx(hRegKey,
                                    regValueNameBias,
                                    NULL,
                                    &regValueType,
                                    (LPBYTE)&regValue,
                                    &regValueSize) == ERROR_SUCCESS))
               {
                  if ((regValueType == REG_DWORD) && (regValue == 0)) {
                     // Disable the bias 2 circuit.
                     PmicAudioVcodecDisableMicbias(hVoiceCODEC, MIC_BIAS2);
                  }
                  else {
                     // Default to enabling the bias 2 circuit.
                     PmicAudioVcodecEnableMicbias(hVoiceCODEC, MIC_BIAS2);
                  }
               }
               else {
                  // Default to enabling the bias 2 circuit.
                  PmicAudioVcodecEnableMicbias(hVoiceCODEC, MIC_BIAS2);
               }

               RegCloseKey(hRegKey);
            }
            else {
               // Default to enabling the bias 2 circuit.
               PmicAudioVcodecEnableMicbias(hVoiceCODEC, MIC_BIAS2);
            }
         }

         // Check registry settings to see how the input amplifiers should
         // be configured. Default is Voltage-to-Voltage mode with 0 dB gain.
         if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                          regSubkeyInputAmp,
                          0,
                          0,
                          &hRegKey) == ERROR_SUCCESS)
         {
            if ((RegQueryValueEx(hRegKey,
                                 regValueNameMode,
                                 NULL,
                                 &regValueType,
                                 (LPBYTE)&regValueL,
                                 &regValueSize) == ERROR_SUCCESS) &&
                (RegQueryValueEx(hRegKey,
                                 regValueNameGain,
                                 NULL,
                                 &regValueType,
                                 (LPBYTE)&regValueR,
                                 &regValueSize) == ERROR_SUCCESS))
            {
               if ((regValueType == REG_DWORD) && (regValueL == 2)) {
                  // Select Current-to-Voltage amplifier mode.
                  ampMode = CURRENT_TO_VOLTAGE;
               }
               if ((regValueType == REG_DWORD) && (regValueR >= 0) && (regValueR <= 31)) {
                  // Convert the registry value to the corresponding
                  // gain setting.
                  ampGain = PMIC_AUDIO_MIC_GAIN(regValueR);
               }
            }

            RegCloseKey(hRegKey);
         }

         // Configure the input amplifier mode and gain.
         PmicAudioVcodecSetRecordGain(hVoiceCODEC,
                                      ampMode, ampGain, ampMode, ampGain);

         // Enable the selected audio input path(s).
         PmicAudioVcodecSetMicOnOff(hVoiceCODEC, leftMic, rightMic);

         break;
   }

#ifdef DEBUG
   PmicAudioDumpRegisters();
#endif

   DEBUGMSG(ZONE_FUNCTION, (__WFUNCTION__ TEXT("-\r\n")));
}


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioStopCodecInput
//
//  This function configures the audio chip to stop audio input.
//
//  Parameters:
//      bus
//          [in] Specifies the audio bus being used for audio input.
//
//      path
//          [in] Specifies the audio path being used for audio input.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
void HardwareContext::BSPAudioStopCodecInput(AUDIO_BUS bus, AUDIO_PATH path)
{

    // Remove-W4: Warning C4100 workaround
    UNREFERENCED_PARAMETER(path);

    switch (bus)
    {
        case AUDIO_BUS_VOICE_IN:

            // Disable the stereo input path.
            PmicAudioVcodecSetMicOnOff(hVoiceCODEC,
                                       MICROPHONE_OFF,
                                       MICROPHONE_OFF);

            // Note that we want to immediately disable the Voice CODEC if
            // we are in the process of powering down.
            if ((RecordDisableDelayMsec > 0)        &&
                (m_hAudioDelayedDisableThread != NULL) &&
                (g_pRecordContext->m_hAudioDelayDisableEvent != NULL) &&
                !m_audioPowerdown)
            {
                if (g_pRecordContext->m_AudioDelayDisableTimerID != NULL)
                {
                    // Cancel previous timer so that we can restart it later
                    // from the beginning.
                    timeKillEvent(g_pRecordContext->m_AudioDelayDisableTimerID);
                    g_pRecordContext->m_AudioDelayDisableTimerID = NULL;

                    DEBUGMSG(ZONE_TEST, (_T("Disabled previous record disable ")
                                         _T("timer\n")));
                }

                // Start a timed delay. If the timer expires without us
                // starting another audio record operation, then we should
                // really disable the audio CODEC hardware.
                g_pRecordContext->m_AudioDelayDisableTimerID =
                    timeSetEvent(RecordDisableDelayMsec,
                                 1,
                                 (LPTIMECALLBACK)(g_pRecordContext->m_hAudioDelayDisableEvent),
                                 NULL,
                                 TIME_ONESHOT | TIME_CALLBACK_EVENT_SET);

                DEBUGMSG(ZONE_TEST, (_T("Enabled record disable timer\n")));
            }
            else
            {
                // We want to immediately disable the Voice CODEC at the end of
                // the recording operation.
                PmicAudioDisable(hVoiceCODEC);
            }

            // We can just leave the selected input path and amplifier
            // configuration as-is.

            break;
    }
}

#endif // #ifdef AUDIO_RECORDING_ENABLED


//-----------------------------------------------------------------------------
//
//  Function: BSPAudioDisableDelayHandler
//
//  This function is the entry point for the CreateThread() call in Init()
//  that sets the value for m_hAudioDelayedDisableThread.
//
//  This thread is responsible for handling the timer timeout events from
//  calling timeSetEvent() in BSPAudioStopCodecOutput() and
//  BSPAudioStopCodecInput().
//
//  Parameters:
//      pHWContext
//          [in] A pointer to an instance of HardwareContext.
//
//  Returns:
//      DWORD.
//
//-----------------------------------------------------------------------------
DWORD CALLBACK BSPAudioDisableDelayHandler(HardwareContext *pHWContext)
{
    if (pHWContext != NULL)
    {
        // We have a valid HardwareContext instance. Just call the
        // DisableDelayThread() member function. We don't expect this
        // to return.
        pHWContext->DisableDelayThread();
    }
    else
    {
        DEBUGMSG(ZONE_ERROR, (_T("BSPAudioDisableDelayHandler() called with ")
                              _T("pHWContext == NULL\n")));
    }
    return 1;
}


//-----------------------------------------------------------------------------
//
//  Function: DisableDelayThread
//
//  This function is the entry point for the CreateThread() call in Init()
//  that sets the value for m_hAudioDelayedDisableThread.
//
//  This thread is responsible for handling the timer timeout events from
//  calling timeSetEvent() in BSPAudioStopCodecOutput() and
//  BSPAudioStopCodecInput().
//
//  Parameters:
//      pHWContext
//          [in] A pointer to an instance of HardwareContext.
//
//  Returns:
//      None.
//
//-----------------------------------------------------------------------------
void HardwareContext::DisableDelayThread()
{
    DEBUGMSG(ZONE_FUNCTION, (_T("+BSPAudioDisableDelayHandler\n")));

#ifdef AUDIO_RECORDING_ENABLED
    // Verify that we have valid event handles for both playback and recording
    // delay disable timers.
    if ((m_hAudioDelayDisableEvent == NULL) ||
        (g_pRecordContext->m_hAudioDelayDisableEvent == NULL))
#else
    // Verify that we have valid event handles for the playback delay disable
    // timer.
    if (m_hAudioDelayDisableEvent == NULL)
#endif
    {
        // Immediately exit this handler because we must have two valid event
        // handles.

        DEBUGMSG(ZONE_ERROR, (_T("Unexpected NULL event handle\n")));
        DEBUGMSG(ZONE_ERROR, (_T("m_hAudioDelayDisableEvent = 0x%08x\n"),
                              m_hAudioDelayDisableEvent));
#ifdef AUDIO_RECORDING_ENABLED
        DEBUGMSG(ZONE_ERROR, (_T("g_pRecordContext->m_hAudioDelayDisableEvent ")
                              _T("= 0x%08x\n"),
                              g_pRecordContext->m_hAudioDelayDisableEvent));
#endif

        DEBUGMSG(ZONE_FUNCTION, (_T("-BSPAudioDisableDelayHandler\n")));
        return;
    }

    // We want to loop endlessly here just waiting to process timer timeout
    // events that were created by calling timeSetEvent() in both
    // BSPAudioStopCodecOutput() and BSPAudioStopCodecInput().
    while (m_Initialized)
    {
        DWORD index = WaitForMultipleObjects(nAudioDelayEvents,
                                             h_AudioDelayEvents, FALSE,
                                          INFINITE);

        // Immediately terminate this thread if Deinit() has been called to
        // unload the audio driver. Otherwise, handle the appropriate event.
        if (!m_Initialized)
        {
            break;
        }
        else if (index == WAIT_OBJECT_0)
        {
            // The audio IST has signalled us to terminate the current
            // playback operation.
            Lock();

            // Don't call StopOutputDMA() if a new playback operation has
            // been started in the meantime.
            if (m_bCanStopOutputDMA)
            {
                StopOutputDMA();

                m_bCanStopOutputDMA = FALSE;
            }

            Unlock();
        }
        else if (index == (WAIT_OBJECT_0 + 1))
        {
            // Must first acquire the critical section to avoid race conditions
            // with BSPAudioStartCodecOutput(), BSPAudioStartCodecInput(), and
            // the interrupt handler.
            Lock();

            if (!m_OutputDMARunning && (m_AudioDelayDisableTimerID != NULL))
            {
                // The timer has expired without another output audio stream
                // being activated so we can really disable the PMIC Stereo
                // DAC now.
                PmicAudioDisable(hStDAC);

                DEBUGMSG(ZONE_TEST, (_T("Disabled Stereo DAC on timer ")
                                     _T("timeout\n")));
            }
            else
            {
                // Another audio output operation has been started, so we
                // should skip disabling the MC13783 Stereo DAC.
                DEBUGMSG(ZONE_TEST, (_T("Skipped Stereo DAC disable timer ")
                                     _T("timeout\n")));
            }

            m_AudioDelayDisableTimerID = NULL;

            Unlock();
        }

#ifdef AUDIO_RECORDING_ENABLED

        else if (index == (WAIT_OBJECT_0 + 2))
        {
            // Must first acquire the critical section to avoid race conditions
            // with BSPAudioStartCodecOutput(), BSPAudioStartCodecInput(), and
            // the interrupt handler.
            Lock();

            if (!g_pRecordContext->m_InputDMARunning)
            {
                // The timer has expired without another input audio stream
                // being activated so we can really disable the PMIC Voice
                // CODEC now.
                PmicAudioDisable(hVoiceCODEC);

                DEBUGMSG(ZONE_TEST, (_T("Disabled Voice CODEC on timer ")
                                     _T("timeout\n")));
            }
            else
            {
                // Another audio input operation has been started, so we should
                // skip disabling the MC13783 Voice CODEC.
                DEBUGMSG(ZONE_TEST, (_T("Skipped Voice CODEC disable timer ")
                                     _T("timeout\n")));
            }

            g_pRecordContext->m_AudioDelayDisableTimerID = NULL;

            Unlock();
        }

#endif // #ifdef AUDIO_RECORDING_ENABLED

    }

    if (m_hAudioStopOutputDMA != NULL)
    {
        CloseHandle(m_hAudioStopOutputDMA);
        m_hAudioStopOutputDMA = NULL;
    }

    if (m_hAudioDelayDisableEvent!= NULL)
    {
        CloseHandle(m_hAudioDelayDisableEvent);
        m_hAudioDelayDisableEvent = NULL;
    }

#ifdef AUDIO_RECORDING_ENABLED

    if (g_pRecordContext->m_hAudioDelayDisableEvent!= NULL)
    {
        CloseHandle(g_pRecordContext->m_hAudioDelayDisableEvent);
        g_pRecordContext->m_hAudioDelayDisableEvent = NULL;
    }

#endif // #ifdef AUDIO_RECORDING_ENABLED

    DEBUGMSG(ZONE_FUNCTION, (_T("-BSPAudioDisableDelayHandler\n")));
}
