/*****************************************************************************
 * Copyright (C) Gemalto M2M GmbH
 *****************************************************************************
 * Project: IMC-RIL-Driver
 *****************************************************************************
 * Author : Wireless Modules RD SW A2
 *****************************************************************************
 * $Workfile: Y:/z_ms-d/src/working/Quinn/RilDrv/voice.cpp $
 * $Revision: /main/ms-d/43 $
 *     $Date: 2014-05-08 10:31:41 $
 *   $Author: pmackent $
 *****************************************************************************
 * Target:
 *  Microsoft Windows Mobile 5.0
 *****************************************************************************
 * Description:
 *  Gemalto IMC RIL
 *****************************************************************************
 *
 * $Log: Y:/z_ms-d/src/working/Quinn/RilDrv/voice.cpp$ 
 * 
 * /main/ms-d/43   2014-05-08 10:31:41   pmackent 
 * Move AT^SAIC to RIL initialization (IPIS100096235)
 *
 * /main/ms-d/42   2013-03-04 13:20:00   pmackent
 * Adaptions for EHS5 1.300
 *
 * /main/ms-d/41   2012-10-24 13:54:55   pmackent
 * Remove unused variable
 *
 * /main/ms-d/40   2012-10-24 10:55:35   pmackent
 * Allow different audio configuration for CDMA and GSM
 *
 * /main/ms-d/39   2012-04-19 08:13:08   ctna0256
 * Don't use AT+CMUT? outside active voice call (IPIS100078740)
 *
 * /main/ms-d/38   2012-01-13 10:09:59   ctna0256
 * Implement audio workaround with SNFO/SNFI set to 0 (IPIS100076299)
 *
 * /main/ms-d/37   2012-01-06 14:34:16   ctna0256
 * New workaround for SNFO/SNFI settings (IPIS100076299)
 *
 * /main/ms-d/36   2011-12-22 10:45:31   ctna0256
 * Optional separation of audio commands (IPIS100076299)
 *
 * /main/ms-d/35   2011-02-02 11:04:49   ctna0256
 * Added missing initialization of variable
 *
 * /main/ms-d/34   2011-01-20 15:04:01   ctna0256
 * Newly supported RIL-API: RIL_GetAudioDevices()
 *
 * /main/ms-d/33   2010-12-15 10:44:14   ctna0256
 * Performed lint check
 *
 * /main/ms-d/32   2010-12-14 11:15:25   ctna0256
 * Corrected irritating comment
 *
 * /main/ms-d/31   2010-11-26 15:38:45   ctna0256
 * Set missing audio device
 *
 * /main/ms-d/30   2010-09-22 11:38:34   ctna0256
 * Disable SNFO reset on PH8 customer sample per default
 *
 * /main/ms-d/29   2010-07-02 15:58:43   ctna0256
 * Introduce SAIC-settings and modified SNFO for PH8
 *
 * /main/ms-d/28   2010-01-20 12:03:36   ctna0256
 * Send double audio mode change after every use of SNFI outside of voice calls (IPIS100061295)
 *
 * /main/ms-d/27   2009-10-07 09:22:34   ctna0256
 * Introduce no-operation command queue
 * Support 2nd alpha identifier in SAT setup call (IPIS100055375)
 *
 * /main/ms-d/26   2009-05-05 10:21:55   ctna0256
 * Deactivate AT^SNFO and AT^SNFI with registry key.
 *
 * /main/ms-d/25   2009-04-06 13:49:39   ctna0256
 * Move check for mute status before reset of audio parameters
 *
 * /main/ms-d/24   2009-01-22 10:17:30   ctna0256
 * Optional re-call of RHA_ConfirmAudioMode after RHA_ActivateVoicePath
 *
 * /main/ms-d/23   2008-12-19 15:58:26   ctna0256
 * Set audio parameter to zero as default
 *
 * /main/ms-d/22   2008-09-23 16:15:06   ctna0256
 * Mute during VTS (optional)
 *
 * /main/ms-d/21   2008-08-22 10:28:29   ctna0256
 * Set Cinterion copyright text
 *
 * /main/ms-d/20   2008-08-21 09:35:48   ctna0256
 * Prepared for new disclaimer handling
 *
 * /main/ms-d/19   2008-04-16 16:24:28   mp49210
 * Debug zones for RHA traces
 *
 * /main/ms-d/18   2008-04-03 14:42:08   mp49210
 * Introduction of new debug zones
 *
 * /main/ms-d/17   2007-10-11 16:23:19   mp49210
 * Introduction of AT^SLCC
 *
 * /main/ms-d/16   2007-10-10 10:52:17   mp49210
 * Set audio muting after change of audio device
 *
 * /main/ms-d/15   2007-10-09 10:00:01   mp49210
 * Simulating missing error at AT+CMUT
 *
 * /main/ms-d/14   2007-04-26 14:24:43   mp49210
 * IPIS100035548: new RIL option: disable audio settings
 *
 * /main/ms-d/13   2007-03-30 12:11:41   mp49210
 * Allow AT^SNFS per default
 *
 * /main/ms-d/12   2007-03-30 09:29:07   mp49210
 * Store audio gain, when module is off
 *
 * /main/ms-d/11   2007-03-28 13:48:14   mp49210
 * Remove 'Classic RIL' specific code and comments
 *
 * /main/ms-d/10   2007-03-01 16:55:01   mp49210
 * Temporary internal registry key for Audio commands
 *
 * /main/ms-d/9   2007-02-26 16:08:33   mp49210
 * Temporary deactivate application of audio settings
 *
 * /main/ms-d/8   2007-02-09 15:15:28   mp49210
 * Lint compatibility
 *
 * /main/ms-d/7   2007-01-19 10:06:55   mp49210
 * New audio configuration registry structure
 *
 * /main/ms-d/6   2007-01-03 17:04:31   mp49210
 * Replace +CIEV: sounder with +CIEV: audio
 *
 * /main/ms-d/5   2007-01-02 15:03:39   mp49210
 * Remove some features not supported by Quinn
 *
 * /main/ms-d/4   2006-12-21 18:26:48   mp49210
 * Changed handling of call states
 *
 * /main/ms-d/3   2006-12-15 17:32:41   mp49210
 * Use index parameter instead of audio value for RHA function (customer request)
 *
 * /main/ms-d/2   2006-11-22 11:41:58   mp49210
 * HC25 specific audio limitations
 *
 * /main/ms-d/1   2006-10-25 16:52:33   wm36264
 * First transformation from classic RIL
 *
*/

//////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2008 Gemalto M2M GmbH
// Copyright (C) 2003-2008 Siemens AG
// Copyright (C) 2003 Intrinsyc Europe Ltd.
//
// Version: 1.0
// Date:    dummy
//
//////////////////////////////////////////////////////////////////////////////

/*++
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
Copyright (c) 1995-1999  Microsoft Corporation
--*/

#include "precomp.h"

#define IOCTL_SPKR_ENABLE   0
#define IOCTL_SPKR_DISABLE  0

#define AUDIO_VOLUME_NOT_SET 0xFFFFFFFF

#define MAKEWRONGVALUE(value)    (value > 0 ? value-1 : value+1)

// This driver doesn't have a way to query current
// uplink and downlink volume, so we have to cache them.
DWORD   g_dwCacheUplinkVolume = 0;
DWORD   g_dwCacheDownlinkVolume = 0;
// Temporary cahce values.
static DWORD   g_dwCacheUplinkVolumeTemp = 0;
static DWORD   g_dwCacheDownlinkVolumeTemp = 0;

extern RHA_FUNCTION_LIST RHAFunctionList;

BOOL g_fNoMuteCmdSoFar = TRUE;
BOOL g_fNoAudioSettings = FALSE;
DWORD g_dwUseSNFOSNFI = 1;
DWORD g_dwSeparateAudioATCmds = 0;
DWORD g_dwModifyAudioSettingsFirst = 0;
DWORD g_dwAlwaysZeroAudioParameters = 0;
UINT g_dwLastMuteSetting = 0;

const char *GetResetAudioParams(BOOL fFullATCmd)
{
    if (fFullATCmd) {
        if (INTEL_STYLE_SNFO) return "AT^SNFO=0,0;^SNFI=0\r";
        return "AT^SNFO=0,0,0;^SNFI=0,0,0\r";
    }
    else {
        if (INTEL_STYLE_SNFO) return "^SNFO=0,0;^SNFI=0";
        return "^SNFO=0,0,0;^SNFI=0,0,0";
    }
}

//
//
//
static HRESULT ParseGetAudioGain(LPCSTR /*szRsp*/, void*& pBlob, UINT& cbBlob, LPVOID /*pParam*/)
{
    RILGAININFO* prgi = NULL;
    HRESULT hr = S_OK;

    pBlob = NULL;
    cbBlob = 0;

    prgi = (RILGAININFO*)AllocBlob(sizeof(RILGAININFO));
    if (!prgi) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    memset(prgi, 0x00, sizeof(RILGAININFO));
    prgi->cbSize = sizeof(RILGAININFO);

    prgi->dwRxGain = g_dwCacheDownlinkVolume;
    prgi->dwParams |= RIL_PARAM_GI_RXGAIN;

    prgi->dwTxGain = g_dwCacheUplinkVolume;
    prgi->dwParams |= RIL_PARAM_GI_TXGAIN;

    pBlob = (void*)prgi;
    cbBlob = sizeof(RILGAININFO);

Error:
    if (FAILED(hr)) {
        (void)FreeBlob(prgi);
    }
    return hr;
}


//
//
//
HRESULT RILDrv_GetAudioGain(DWORD dwParam)
{
    HRESULT hr = S_OK;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    // When RILDrv_SetAudioGain() is called the command port might be blocked by an ATD,
    // so we have to try to reroute the command to another multiplexer channel, which
    // is availabe.
    if (!QueueCmd(HCCMD_STANDARD, pHandle, NULL, CMDOPT_NOOP, APIID_GETAUDIOGAIN, ParseGetAudioGain, NULL, hr)) {
        hr = E_FAIL;
        goto Error;
    }

Error:
    return hr;
}

static HRESULT ParseSetAudioGain(LPCSTR /*szRsp*/, void*& pBlob, UINT& cbBlob, LPVOID /*pParam*/)
{
    HRESULT hr = S_OK;

    pBlob = NULL;
    cbBlob = 0;

    // We store the cached values
    g_dwCacheDownlinkVolume = g_dwCacheDownlinkVolumeTemp;
    g_dwCacheUplinkVolume = g_dwCacheUplinkVolumeTemp;

    return hr;
}

//
//
//
HRESULT RILDrv_SetAudioGain(DWORD dwParam, const RILGAININFO* lpGainInfo)
{
    char szCmd[MAX_ATCMD_LEN];
    LPSTR szWalk = szCmd;
    HRESULT hr = S_OK;
    BOOL bSNFOIsSet = FALSE;
    DWORD dwSNFOcdcRxGain, dwSNFOrxVol, dwSNFOstGain;
    DWORD dwSNFOoutVolStep, dwSNFOsideToneStep;
    DWORD dwDevice = RIL_AUDIO_NONE;
    AUDIO_CONFIG *pAudio;
    DWORD dwSNFImicAmp1, dwSNFImicAmp2, dwSNFImicTxVol;
    DWORD dwSNFIinVolStep;
    DWORD dwExtMicAudioCodecVol, dwExtAudioCodecVol;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    if (!(lpGainInfo->dwParams & RIL_PARAM_GI_RXGAIN) && !(lpGainInfo->dwParams & RIL_PARAM_GI_TXGAIN)) {
        hr = E_INVALIDARG;
        goto Error;
    }

    g_dwCacheDownlinkVolumeTemp = g_dwCacheDownlinkVolume;
    g_dwCacheUplinkVolumeTemp = g_dwCacheUplinkVolume;

    (void)strncpyz(szWalk, "AT", (UINT)(MAX_ATCMD_LEN - (szWalk - szCmd)));
    szWalk = strchr(szWalk, '\0');  // NO_TYPO: 27

    if (lpGainInfo->dwParams & RIL_PARAM_GI_RXGAIN) {
        UINT iIdx;

        dwDevice = pHandle->GetDevice()->GetAudioRxDevice();
        g_dwCacheDownlinkVolumeTemp = lpGainInfo->dwRxGain;

        iIdx = (lpGainInfo->dwRxGain>>24)/51;
        pAudio = GetAudioTableEntry(GetAudioTableIndex(dwDevice), pHandle->GetDevice());
        dwSNFOcdcRxGain = pAudio->dwSNFOcdcRxGainSteps[iIdx];
        dwSNFOrxVol = pAudio->dwSNFOrxVolSteps[iIdx];
        dwSNFOstGain = pAudio->dwSNFOstGainSteps[iIdx];
        dwSNFOoutVolStep = pAudio->dwSNFOoutVolSteps[iIdx];
        dwSNFOsideToneStep = pAudio->dwSNFOsideToneSteps[iIdx];
        dwExtAudioCodecVol = pAudio->dwExtAudioCodecVolSteps[iIdx];

        (void)RHA_AdjustRxVolume(dwDevice, dwExtAudioCodecVol);

        if(dwSNFOrxVol != AUDIO_VOLUME_NOT_SET) {
            if (pAudio->dwAudioMode > 1 && g_dwUseSNFOSNFI == 1) {
                if (g_dwModifyAudioSettingsFirst > 0) {
                    if (INTEL_STYLE_SNFO)
                        (void)_snprintfz(szWalk, MAX_ATCMD_LEN - (szWalk - szCmd), "^SNFO=%u,%u;", MAKEWRONGVALUE(dwSNFOoutVolStep), dwSNFOsideToneStep);
                    else
                        (void)_snprintfz(szWalk, MAX_ATCMD_LEN - (szWalk - szCmd), "^SNFO=%u,%u,%u;", MAKEWRONGVALUE(dwSNFOcdcRxGain), dwSNFOrxVol, dwSNFOstGain);
                    szWalk = strchr(szWalk, '\0');  // NO_TYPO: 27
                }
                if (INTEL_STYLE_SNFO)
                    (void)_snprintfz(szWalk, MAX_ATCMD_LEN - (szWalk - szCmd), "^SNFO=%u,%u", dwSNFOoutVolStep, dwSNFOsideToneStep);
                else
                    (void)_snprintfz(szWalk, MAX_ATCMD_LEN - (szWalk - szCmd), "^SNFO=%u,%u,%u", dwSNFOcdcRxGain, dwSNFOrxVol, dwSNFOstGain);
                bSNFOIsSet = TRUE;
            }
            szWalk = strchr(szWalk, '\0');  // NO_TYPO: 27
        }
        if (!(lpGainInfo->dwParams & RIL_PARAM_GI_TXGAIN)) {
            dwExtMicAudioCodecVol = pAudio->dwExtMicAudioCodecVolSteps[MAX_VOLUME_STEPS-1];
            g_dwCacheUplinkVolumeTemp = 0xFF000000;
            if(dwExtMicAudioCodecVol != AUDIO_VOLUME_NOT_SET)
                (void)RHA_AdjustTxVolume(dwDevice, dwExtMicAudioCodecVol);
        }
    }

    if (lpGainInfo->dwParams & RIL_PARAM_GI_TXGAIN) {
        UINT iIdx;

        dwDevice = pHandle->GetDevice()->GetAudioTxDevice();
        g_dwCacheUplinkVolumeTemp = lpGainInfo->dwTxGain;
        iIdx = (lpGainInfo->dwTxGain>>24)/51;
        pAudio = GetAudioTableEntry(GetAudioTableIndex(dwDevice), pHandle->GetDevice());

        dwExtMicAudioCodecVol = pAudio->dwExtMicAudioCodecVolSteps[iIdx];
        dwSNFImicAmp1 = pAudio->dwSNFImicAmp1Steps[iIdx];
        dwSNFImicAmp2 = pAudio->dwSNFImicAmp2Steps[iIdx];
        dwSNFImicTxVol = pAudio->dwSNFImicTxVolSteps[iIdx];
        dwSNFIinVolStep = pAudio->dwSNFIinVolSteps[iIdx];

        (void)RHA_AdjustTxVolume(dwDevice, dwExtMicAudioCodecVol);

        if(dwSNFImicAmp1 != AUDIO_VOLUME_NOT_SET) {
            if (pAudio->dwAudioMode > 1 && g_dwUseSNFOSNFI == 1) {
                if (bSNFOIsSet) {
                    (void)strncpyz(szWalk, ";", MAX_ATCMD_LEN - (szWalk - szCmd));  // NO_TYPO: 30
                    szWalk = strchr(szWalk, '\0');  // NO_TYPO: 27
                }
                if (g_dwModifyAudioSettingsFirst == 1) {
                    if (INTEL_STYLE_SNFO)
                        (void)_snprintfz(szWalk, MAX_ATCMD_LEN - (szWalk - szCmd), "^SNFI=%u; ", MAKEWRONGVALUE(dwSNFIinVolStep));
                    else
                        (void)_snprintfz(szWalk, MAX_ATCMD_LEN - (szWalk - szCmd), "^SNFI=%u,%u,%u; ", dwSNFImicAmp1, MAKEWRONGVALUE(dwSNFImicAmp2), dwSNFImicTxVol);
                    szWalk = strchr(szWalk, '\0');  // NO_TYPO: 27
                }
                else if (g_dwModifyAudioSettingsFirst >= 2 && !INTEL_STYLE_SNFO) {
                    (void)_snprintfz(szWalk, MAX_ATCMD_LEN - (szWalk - szCmd), "^SNFI=%u,%u,%u; ", MAKEWRONGVALUE(dwSNFImicAmp1), MAKEWRONGVALUE(dwSNFImicAmp2), dwSNFImicTxVol);
                    szWalk = strchr(szWalk, '\0');  // NO_TYPO: 27
                }
                if (INTEL_STYLE_SNFO)
                    (void)_snprintfz(szWalk, MAX_ATCMD_LEN - (szWalk - szCmd), "^SNFI=%u", dwSNFIinVolStep);
                else
                    (void)_snprintfz(szWalk, MAX_ATCMD_LEN - (szWalk - szCmd), "^SNFI=%u,%u,%u", dwSNFImicAmp1, dwSNFImicAmp2, dwSNFImicTxVol);
            }
            szWalk = strchr(szWalk, '\0');  // NO_TYPO: 27
        }
    }
    (void)strncpyz(szWalk, "\r", MAX_ATCMD_LEN - (szWalk - szCmd));  // NO_TYPO: 30

    if (g_PowerManager.IsRadioOff() || g_fNoAudioSettings) {
        // In case the radio is off, we simply store the values for later use.
        if (!QueueCmd(HCCMD_STANDARD, pHandle, NULL, CMDOPT_NOOP, APIID_SETAUDIOGAIN, ParseSetAudioGain, NULL, hr)) {
            hr = E_FAIL;
            goto Error;
        }
    }
    else {
        if (!QueueCmd(HCCMD_STANDARD, pHandle, szCmd, CMDOPT_NONE, APIID_SETAUDIOGAIN, ParseSetAudioGain, NULL, hr)) {
            hr = E_FAIL;
            goto Error;
        }

        PerformDoubleAudioCheck(pHandle->GetDevice(), dwDevice);
    }

Error:
    return hr;
}

//
// It might be necessary to set the audio mode, if there is currently no call active.
//
void PerformDoubleAudioCheck(CRilHandle *pDevice, DWORD dwDevice)
{
    if (g_iNrCallInfo <= 0 && NEED_DOUBLE_AUDIO_MODE_CHANGE && g_dwUseSNFOSNFI == 1) {
        INT iDevice = GetAudioTableIndex(dwDevice);
        DWORD dwCurAudioMode;
        AUDIO_CONFIG *pAudio;

        pAudio = GetAudioTableEntry(iDevice, pDevice);
        dwCurAudioMode = pAudio->dwAudioMode;

        if (dwCurAudioMode > 1) {
            char szCmd[MAX_ATCMD_LEN];
            char* szWork;

            if (g_dwAlwaysZeroAudioParameters == 1)
                (void)_snprintfz(szCmd, MAX_ATCMD_LEN, "AT%s;^SNFS=%d;%s;^SNFS=%d\r", GetResetAudioParams(FALSE), dwCurAudioMode-1, GetResetAudioParams(FALSE), dwCurAudioMode);
            else
                (void)_snprintfz(szCmd, MAX_ATCMD_LEN, "AT^SNFS=%d;^SNFS=%d\r", dwCurAudioMode-1, dwCurAudioMode);
            szWork = strchr(szCmd, '\r');

            if (SUPPORTS_SAIC_AUDIO_CONFIG && !NO_SAIC_IN_CALL && pAudio->dwSAICinterface > 0 && g_dwSeparateAudioATCmds != 1) {
                (void)_snprintfz(szWork, MAX_ATCMD_LEN - (szWork - szCmd), ";^SAIC=%u,%u,%u,%u,%u,%u,%u\r", pAudio->dwSAICinterface, pAudio->dwSAICmicrophone, pAudio->dwSAICearpiece, pAudio->dwSAICclock, pAudio->dwSAICmsMode, pAudio->dwSAICframeMode, pAudio->dwSAICextClkMode);
                szWork = strchr(szCmd, '\r');
            }

            (void)QueueInternalCmd(HCCMD_STANDARD, pDevice, szCmd, CMDOPT_NONE, APIID_SETAUDIODEVICES, 0, 0, 0);

            // If configured, the AT^SAIC must be sent as a stand-alone command.
            if (SUPPORTS_SAIC_AUDIO_CONFIG && !NO_SAIC_IN_CALL && pAudio->dwSAICinterface > 0 && g_dwSeparateAudioATCmds == 1) {
                (void)_snprintfz(szWork, MAX_ATCMD_LEN, "AT^SAIC=%u,%u,%u,%u,%u,%u,%u\r", pAudio->dwSAICinterface, pAudio->dwSAICmicrophone, pAudio->dwSAICearpiece, pAudio->dwSAICclock, pAudio->dwSAICmsMode, pAudio->dwSAICframeMode, pAudio->dwSAICextClkMode);
                (void)QueueInternalCmd(HCCMD_STANDARD, pDevice, szCmd, CMDOPT_NONE, APIID_SETAUDIODEVICES, 0, 0, 0);
            }
        }
    }
}

//
// This function constructs the audio AT command(s) for the given device.
// The buffers of szCmd and szCmd2 are supposed to to have a size of MAX_ATCMD_LEN bytes, each.
//
void GenerateAudioATCmd(char *szCmd, char *szCmd2, DWORD dwDevice, CRilHandle *pDevice)
{
    DWORD dwAudioMode = 0;
    AUDIO_CONFIG *pAudio;

    pAudio = GetAudioTableEntry(GetAudioTableIndex(dwDevice), pDevice);

    dwAudioMode = pAudio->dwAudioMode;

    memset(szCmd, 0, MAX_ATCMD_LEN);
    memset(szCmd2, 0, MAX_ATCMD_LEN);

    if (dwAudioMode == 0)
        (void)strncpyz(szCmd, "AT\r", MAX_ATCMD_LEN);
    else
        (void)_snprintfz(szCmd, MAX_ATCMD_LEN, "AT^SNFS=%d\r", dwAudioMode);

    if (dwAudioMode > 1) {
        UINT iIdx;
        DWORD dwSNFOcdcRxGain, dwSNFOrxVol, dwSNFOstGain;
        DWORD dwSNFOoutVolStep, dwSNFOsideToneStep;
        DWORD dwSNFImicAmp1, dwSNFImicAmp2, dwSNFImicTxVol;
        DWORD dwSNFIinVolStep;
        char* szWork = strchr(szCmd, '\r');

        if (szWork) {
            iIdx = (g_dwCacheDownlinkVolume >> 24) / 51;
            dwSNFOcdcRxGain = pAudio->dwSNFOcdcRxGainSteps[iIdx];
            dwSNFOrxVol = pAudio->dwSNFOrxVolSteps[iIdx];
            dwSNFOstGain = pAudio->dwSNFOstGainSteps[iIdx];
            dwSNFOoutVolStep = pAudio->dwSNFOoutVolSteps[iIdx];
            dwSNFOsideToneStep = pAudio->dwSNFOsideToneSteps[iIdx];
            if (dwSNFOrxVol != AUDIO_VOLUME_NOT_SET && g_dwUseSNFOSNFI == 1) {
                if (g_dwModifyAudioSettingsFirst > 0) {
                    if (INTEL_STYLE_SNFO)
                        (void)_snprintfz(szWork, MAX_ATCMD_LEN - (szWork - szCmd), ";^SNFO=%u,%u\r", MAKEWRONGVALUE(dwSNFOoutVolStep), dwSNFOsideToneStep);
                    else
                        (void)_snprintfz(szWork, MAX_ATCMD_LEN - (szWork - szCmd), ";^SNFO=%u,%u,%u\r", MAKEWRONGVALUE(dwSNFOcdcRxGain), dwSNFOrxVol, dwSNFOstGain);
                    szWork = strchr(szCmd, '\r');
                }
                if (INTEL_STYLE_SNFO)
                    (void)_snprintfz(szWork, MAX_ATCMD_LEN - (szWork - szCmd), ";^SNFO=%u,%u\r", dwSNFOoutVolStep, dwSNFOsideToneStep);
                else
                    (void)_snprintfz(szWork, MAX_ATCMD_LEN - (szWork - szCmd), ";^SNFO=%u,%u,%u\r", dwSNFOcdcRxGain, dwSNFOrxVol, dwSNFOstGain);
                szWork = strchr(szCmd, '\r');
            }
            iIdx = (g_dwCacheUplinkVolume >> 24) / 51;
            dwSNFImicAmp1 = pAudio->dwSNFImicAmp1Steps[iIdx];
            dwSNFImicAmp2 = pAudio->dwSNFImicAmp2Steps[iIdx];
            dwSNFImicTxVol = pAudio->dwSNFImicTxVolSteps[iIdx];
            dwSNFIinVolStep = pAudio->dwSNFIinVolSteps[iIdx];
            if (dwSNFImicAmp1 != AUDIO_VOLUME_NOT_SET && g_dwUseSNFOSNFI == 1) {
                if (g_dwModifyAudioSettingsFirst == 1) {
                    if (INTEL_STYLE_SNFO)
                        (void)_snprintfz(szWork, MAX_ATCMD_LEN - (szWork - szCmd), ";^SNFI=%u\r", MAKEWRONGVALUE(dwSNFIinVolStep));
                    else
                        (void)_snprintfz(szWork, MAX_ATCMD_LEN - (szWork - szCmd), ";^SNFI=%u,%u,%u\r", dwSNFImicAmp1, MAKEWRONGVALUE(dwSNFImicAmp2), dwSNFImicTxVol);
                    szWork = strchr(szCmd, '\r');
                }
                else if (g_dwModifyAudioSettingsFirst >= 2 && !INTEL_STYLE_SNFO) {
                    (void)_snprintfz(szWork, MAX_ATCMD_LEN - (szWork - szCmd), ";^SNFI=%u,%u,%u\r", MAKEWRONGVALUE(dwSNFImicAmp1), MAKEWRONGVALUE(dwSNFImicAmp2), dwSNFImicTxVol);
                    szWork = strchr(szCmd, '\r');
                }
                if (INTEL_STYLE_SNFO)
                    (void)_snprintfz(szWork, MAX_ATCMD_LEN - (szWork - szCmd), ";^SNFI=%u\r", dwSNFIinVolStep);
                else
                    (void)_snprintfz(szWork, MAX_ATCMD_LEN - (szWork - szCmd), ";^SNFI=%u,%u,%u\r", dwSNFImicAmp1, dwSNFImicAmp2, dwSNFImicTxVol);
                szWork = strchr(szCmd, '\r');
            }
            if (SUPPORTS_SAIC_AUDIO_CONFIG && !NO_SAIC_IN_CALL && pAudio->dwSAICinterface > 0) {
                (void)_snprintfz(szWork, MAX_ATCMD_LEN - (szWork - szCmd), ";^SAIC=%u,%u,%u,%u,%u,%u,%u\r", pAudio->dwSAICinterface, pAudio->dwSAICmicrophone, pAudio->dwSAICearpiece, pAudio->dwSAICclock, pAudio->dwSAICmsMode, pAudio->dwSAICframeMode, pAudio->dwSAICextClkMode);
                szWork = strchr(szCmd, '\r');
            }
        }

        // If configured, make two commands, the first with the AT^SNFS command, the second with
        // the rest of the audio configuration settings.
        szWork = strchr(szCmd, ';');
        if (g_dwSeparateAudioATCmds == 1 && szWork != NULL) {
            (void)strncpyz(szCmd2, "AT", MAX_ATCMD_LEN);
            (void)strncatz(szCmd2, szWork+1, MAX_ATCMD_LEN);
            *szWork = '\r';
            *(szWork+1) = '\0';
        }
    }
    return;
}

//
//
//
HRESULT RILDrv_SetAudioDevices(DWORD dwParam, const RILAUDIODEVICEINFO* lpAudioDeviceInfo)
{
    RILAUDIODEVICEINFO lAudioDeviceInfo;
    char szCmd[MAX_ATCMD_LEN];
    char szCmd2[MAX_ATCMD_LEN];
    HRESULT hr = S_OK;
    DWORD dwExtMicAudioCodecVol, dwExtAudioCodecVol;
    DWORD dwDevice = RIL_AUDIO_NONE;
    DWORD dwDisableSetSNFS = 0;
    DWORD dwResetAudio = 0;
    DWORD dwMute = MAXDWORD;
    AUDIO_CONFIG *pAudio;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

	if(UNSUPPORTS_AUDIO){
		hr = E_NOTIMPL;
        goto Error;
	}
	
    memcpy(&lAudioDeviceInfo, lpAudioDeviceInfo, sizeof(RILAUDIODEVICEINFO));
    // Problem: sometimes the MMI sets only the Tx device, but when user is using
    // the slider the MMI sets only the Rx device. This doesn't work, because it is not
    // set at this moment. To solve this problem the RIL checks, if only one of the
    // devices is set. If one isn't set, it sets that one to the value of the device,
    // which is set.
    if ((lAudioDeviceInfo.dwParams & RIL_PARAM_ADI_TXDEVICE) && !(lAudioDeviceInfo.dwParams & RIL_PARAM_ADI_RXDEVICE)) {
        lAudioDeviceInfo.dwParams |= RIL_PARAM_ADI_RXDEVICE;
        lAudioDeviceInfo.dwRxDevice = lAudioDeviceInfo.dwTxDevice;
    }
    if ((lAudioDeviceInfo.dwParams & RIL_PARAM_ADI_RXDEVICE) && !(lAudioDeviceInfo.dwParams & RIL_PARAM_ADI_TXDEVICE)) {
        lAudioDeviceInfo.dwParams |= RIL_PARAM_ADI_TXDEVICE;
        lAudioDeviceInfo.dwTxDevice = lAudioDeviceInfo.dwRxDevice;
    }
    memset(szCmd, 0, MAX_ATCMD_LEN);
    memset(szCmd2, 0, MAX_ATCMD_LEN);
    dwResetAudio = (AVOID_RESET_OF_SNFO_FOR_MODE_CHANGE ? 0 : 1);
    if (!GetRegistryDWORD(HKEY_LOCAL_MACHINE, g_tszRegKeyRIL, TEXT("DisableSetSNFS"), &dwDisableSetSNFS))
        dwDisableSetSNFS = 0;
    if (!GetRegistryDWORD(HKEY_LOCAL_MACHINE, g_tszRegKeyRIL, TEXT("ResetAudioBeforeRHAConfirm"), &dwResetAudio))
        dwResetAudio = ((g_dwAlwaysZeroAudioParameters == 1) ? 0 : 1);

    if (SNFS_DISABLES_CMUT && IsVoiceCallActive(pHandle->GetDevice())) {
        // AT^SNFS will reset the status of AT+CMUT, so the RIL has to save the old value and restore it
        // after applying the new audio mode. This command will fail, when used while no call is active,
        // the RIL doesn't need to check the commands success.
        (void)QueueInternalCmdBlocking(HCCMD_URCPORT_ONLY, pHandle->GetDevice(), "AT+CMUT?\r", CMDOPT_NONE, APIID_GETAUDIOMUTING, 0, 0, 0, ParseGetAudioMuting, NULL, (LPVOID)&dwMute);
    }

    if (lAudioDeviceInfo.dwParams & RIL_PARAM_ADI_TXDEVICE && dwDisableSetSNFS != 1 && !g_fNoAudioSettings && dwResetAudio == 1) {
        char szResetCmd[MAX_ATCMD_LEN];

        // It may be necessary to zero the settiong also before AT^SNFS.
        if (g_dwAlwaysZeroAudioParameters == 1)
            (void)QueueInternalCmdBlocking(HCCMD_STANDARD, pHandle->GetDevice(), GetResetAudioParams(TRUE), CMDOPT_NONE, APIID_SETAUDIODEVICES, g_TimeoutCmdInit, 0, 0);

        // Since the SNFS will be changed imnmediately for a second time, it is not
        // necessary to set SAIC here.
        pAudio = GetAudioTableEntry(GetAudioTableIndex(RIL_AUDIO_HANDSET), pHandle->GetDevice());
        if (g_dwUseSNFOSNFI == 1 && pAudio->dwAudioMode >= 2 && g_dwSeparateAudioATCmds != 1)
            (void)_snprintfz(szResetCmd, MAX_ATCMD_LEN, "AT^SNFS=%d;%s\r", pAudio->dwAudioMode, GetResetAudioParams(FALSE));
        else
            (void)_snprintfz(szResetCmd, MAX_ATCMD_LEN, "AT^SNFS=%d\r", pAudio->dwAudioMode ? pAudio->dwAudioMode : 1);
        (void)QueueInternalCmdBlocking(HCCMD_STANDARD, pHandle->GetDevice(), szResetCmd, CMDOPT_NONE, APIID_SETAUDIODEVICES, g_TimeoutCmdInit, 0, 0);
        if (g_dwUseSNFOSNFI == 1 && pAudio->dwAudioMode >= 2 && g_dwSeparateAudioATCmds == 1)
            (void)QueueInternalCmdBlocking(HCCMD_STANDARD, pHandle->GetDevice(), GetResetAudioParams(TRUE), CMDOPT_NONE, APIID_SETAUDIODEVICES, g_TimeoutCmdInit, 0, 0);
    }
    if (RHAFunctionList.pfnRHA_ConfirmAudioMode != NULL)
    {
        DWORD dwTxDevice, dwRxDevice;

        dwTxDevice = lAudioDeviceInfo.dwTxDevice;
        dwRxDevice = lAudioDeviceInfo.dwRxDevice;
        if (!RHA_ConfirmAudioMode(&dwTxDevice, &dwRxDevice)) {
            RILRetailTraceZone(ZONE_INFO, (TEXT("RilDrv: Settings of RILDrv_SetAudioDevices() overwritten by RHA from %d/%d to %d/%d\r\n"), lAudioDeviceInfo.dwTxDevice, lAudioDeviceInfo.dwTxDevice, dwTxDevice, dwRxDevice));
            lAudioDeviceInfo.dwTxDevice = dwTxDevice;
            lAudioDeviceInfo.dwRxDevice = dwRxDevice;
        }
    }
    if (lAudioDeviceInfo.dwParams & RIL_PARAM_ADI_TXDEVICE) {
        dwDevice = lAudioDeviceInfo.dwTxDevice;
        RILRetailTraceZone(ZONE_INFO, (TEXT("RilDrv: SetAudioDevices() - tx device = %d\r\n"), dwDevice));
        GenerateAudioATCmd(szCmd, szCmd2, dwDevice, pHandle->GetDevice());
        (void)StoreAudioSetting(dwDevice, FALSE);

        pHandle->GetDevice()->SetAudioTxDevice(dwDevice);

        pAudio = GetAudioTableEntry(GetAudioTableIndex(dwDevice), pHandle->GetDevice());
        dwExtAudioCodecVol = pAudio->dwExtAudioCodecVolSteps[(g_dwCacheDownlinkVolume >> 24) / 51];
        dwExtMicAudioCodecVol = pAudio->dwExtMicAudioCodecVolSteps[(g_dwCacheUplinkVolume >> 24) / 51];
        (void)RHA_AdjustRxVolume(dwDevice, dwExtAudioCodecVol);
        (void)RHA_AdjustTxVolume(dwDevice, dwExtMicAudioCodecVol);
    }

    if (lAudioDeviceInfo.dwParams & RIL_PARAM_ADI_RXDEVICE) {
        // Since the module cannot handle different audio devices for Rx and Tx,
        // the RIL ignores the Rx device setting and assumes, it is the same as
        // the Tx device.
        dwDevice = lAudioDeviceInfo.dwRxDevice;
        RILRetailTraceZone(ZONE_INFO, (TEXT("RilDrv: SetAudioDevices() - rx device = %d\r\n"), dwDevice));
        pHandle->GetDevice()->SetAudioRxDevice(dwDevice);
    }

    if (lAudioDeviceInfo.dwParams & RIL_PARAM_ADI_TXDEVICE && dwDisableSetSNFS != 1 && !g_fNoAudioSettings) {
        if (g_dwAlwaysZeroAudioParameters == 1)
            (void)QueueInternalCmd(HCCMD_STANDARD, pHandle->GetDevice(), GetResetAudioParams(TRUE), CMDOPT_NONE, APIID_SETAUDIODEVICES, 0, 1, 1000);
        if (!QueueCmdWithRetry(HCCMD_STANDARD, pHandle, szCmd, CMDOPT_NONE, APIID_SETAUDIODEVICES, NULL, NULL, hr, 1, 1000)) {
            hr = E_FAIL;
            goto Error;
        }
        if (strlen(szCmd2) > 0)    // If configured, send 2nd part of the command
            (void)QueueInternalCmd(HCCMD_STANDARD, pHandle->GetDevice(), szCmd2, CMDOPT_NONE, APIID_SETAUDIODEVICES, 0, 1, 1000);
        if (SNFS_DISABLES_CMUT && dwMute == 1) {
            // If necessary, the AT+CMUT value must be set now. This may fail, if there is no call active.
            (void)_snprintfz(szCmd, MAX_ATCMD_LEN, "AT+CMUT=%d\r", dwMute);
            (void)QueueInternalCmd(HCCMD_URCPORT_ONLY, pHandle->GetDevice(), szCmd, CMDOPT_NONE, APIID_SETAUDIOMUTING, 0, 0, 0);
        }

        // It might be necessary to set the audio mode, if there is currently no call active.
        PerformDoubleAudioCheck(pHandle->GetDevice(), dwDevice);
    }
    else {
        if (!QueueCmd(HCCMD_STANDARD, pHandle, NULL, CMDOPT_NOOP, APIID_SETAUDIODEVICES, NULL, NULL, hr)) {
            hr = E_FAIL;
            goto Error;
        }
    }
    pHandle->GetDevice()->SignalAudioModeEvent();

Error:

    return hr;
}

//
//
//
HRESULT ParseGetAudioDevices(LPCSTR /*szRsp*/, void*& pBlob, UINT& cbBlob, LPVOID pParam)
{
    CRilHandle *pDevice;
    HRESULT hr = S_OK;
    RILAUDIODEVICEINFO *radi = NULL;

    pBlob = NULL;
    cbBlob = 0;

    pDevice = (CRilHandle *)pParam;
    if (pDevice == NULL) {
        hr = E_FAIL;
        goto Error;
    }
    radi = (RILAUDIODEVICEINFO*)AllocBlob(sizeof(RILAUDIODEVICEINFO));
    if (!radi) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    memset(radi, 0x00, sizeof(RILAUDIODEVICEINFO));

    radi->cbSize = sizeof(RILAUDIODEVICEINFO);
    radi->dwParams = RIL_PARAM_ADI_ALL;
    radi->dwRxDevice = pDevice->GetAudioRxDevice();
    radi->dwTxDevice = pDevice->GetAudioTxDevice();

    pBlob = (void*)radi;
    cbBlob = sizeof(RILAUDIODEVICEINFO);

Error:
    if (FAILED(hr)) {
        (void)FreeBlob(radi);
    }
    return hr;
}

//
//
//
HRESULT RILDrv_GetAudioDevices(DWORD dwParam)
{
    HRESULT hr = S_OK;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    if (!QueueCmd(HCCMD_STANDARD, pHandle, NULL, CMDOPT_NOOP, APIID_GETAUDIODEVICES, ParseGetAudioDevices, NULL, hr, NULL, (LPVOID)pHandle->GetDevice())) {
        hr = E_FAIL;
        goto Error;
    }

Error:

    return hr;
}

//
//
//
HRESULT ParseGetAudioMuting(LPCSTR szRsp, void*& pBlob, UINT& cbBlob, LPVOID pParam)
{
    UINT nValue;
    BOOL* pfEnabled = NULL;
    HRESULT hr = S_OK;

    pBlob = NULL;
    cbBlob = 0;

    pfEnabled = (BOOL*)AllocBlob(sizeof(BOOL));
    if (!pfEnabled) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    memset(pfEnabled, 0x00, sizeof(BOOL));

    // Parse "<prefix>+CMUT: <enabled><postfix>"
    if (!ParseRspPrefix(szRsp, szRsp)                           ||
        !MatchStringBeginning(szRsp, "+CMUT: ", szRsp)          ||
        !ParseUIntAndVerifyAbove(szRsp, TRUE, 2, nValue, szRsp) ||
        !ParseRspPostfix(szRsp, szRsp)) {
        hr = E_FAIL;
        goto Error;
    }

    *pfEnabled = (1 == nValue);
    if (pParam != NULL) {
        DWORD *pMute;

        pMute = (DWORD *)pParam;
        *pMute = (DWORD)nValue;
    }
    g_dwLastMuteSetting = nValue;

    pBlob = (void*)pfEnabled;
    cbBlob = sizeof(BOOL);

Error:
    if (FAILED(hr)) {
        (void)FreeBlob(pfEnabled);
    }
    return hr;
}

// This Parsing function always returns not muted. This is a workaround since the module sometimes report
// that audio is muted even it is known to be not muted.
static HRESULT ParseGetAudioMutingFake(LPCSTR /*szRsp*/, void*& pBlob, UINT& cbBlob, LPVOID /*pParam*/)
{
    BOOL* pfEnabled = NULL;
    HRESULT hr = S_OK;

    pBlob = NULL;
    cbBlob = 0;

    pfEnabled = (BOOL*)AllocBlob(sizeof(BOOL));
    if (!pfEnabled) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    memset(pfEnabled, 0x00, sizeof(BOOL));

    *pfEnabled = FALSE;

    pBlob = (void*)pfEnabled;
    cbBlob = sizeof(BOOL);

Error:
    if (FAILED(hr)) {
        (void)FreeBlob(pfEnabled);
    }
    return hr;
}

//
//
//
HRESULT RILDrv_GetAudioMuting(DWORD dwParam)
{
    HRESULT hr = S_OK;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    if (!IsVoiceCallActive(pHandle->GetDevice()) || g_fNoMuteCmdSoFar)
    {
        // The module report somtimes the audio is muted, although it is in reality not muted. This happens if no
        // call is active or directly after establishing a call. In these cases we don't ask the modem and directly
        // return "not muted" to the MMI.
        if (!QueueCmd(HCCMD_STANDARD, pHandle, NULL, CMDOPT_NOOP, APIID_GETAUDIOMUTING, ParseGetAudioMutingFake, NULL, hr)) {
            hr = E_FAIL;
            goto Error;
        }
    }
    else
    {
        if (!QueueCmd(HCCMD_STANDARD, pHandle, "AT+CMUT?\r", CMDOPT_NONE, APIID_GETAUDIOMUTING, ParseGetAudioMuting, NULL, hr)) {
            hr = E_FAIL;
            goto Error;
        }
    }

Error:
    return hr;
}


//
//
//
HRESULT RILDrv_SetAudioMuting(DWORD dwParam, BOOL fEnable)
{
    char szCmd[MAX_ATCMD_LEN];
    HRESULT hr = S_OK;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    if (!IsVoiceCallActive(pHandle->GetDevice()) || !pHandle->GetDevice()->IsMutingAllowed()) {
        // When a call is in the state dialing, the AT+CMUT command will return OK, but is
        // actually doing nothing. The correct response would be "+CME ERROR: 256", so
        // the RIL simulates this response.
        if (!QueueCmd(HCCMD_STANDARD, pHandle, NULL, CMDOPT_NOOP, APIID_SETAUDIOMUTING, ParseForceFailureResponse, NULL, hr, NULL, (LPVOID)RIL_E_NETWKTEMPFAILURE))  {
            hr = E_FAIL;
            goto Error;
        }
    }
    else {
        (void)_snprintfz(szCmd, MAX_ATCMD_LEN, "AT+CMUT=%u\r", (fEnable ? 1 : 0));
        g_dwLastMuteSetting = (fEnable ? 1 : 0);
        if (!QueueCmd(HCCMD_STANDARD, pHandle, szCmd, CMDOPT_NONE, APIID_SETAUDIOMUTING, NULL, NULL, hr)) {
            hr = E_FAIL;
            goto Error;
        }
        g_fNoMuteCmdSoFar = FALSE;
    }

Error:
    return hr;
}
