root/src/add-ons/kernel/drivers/audio/ac97/sis7018/Mixer.cpp
/*
 *      SiS 7018, Trident 4D Wave DX/NX, Acer Lab M5451 Sound Driver.
 *      Copyright (c) 2002, 2008-2011 S.Zharski <imker@gmx.li>
 *      Distributed under the terms of the MIT license.
 *
 *      Copyright for ali5451 support:
 *              (c) 2009, Krzysztof Ćwiertnia (krzysiek.bmkx_gmail_com).
 */


#include "Mixer.h"

#include <string.h>

#include "Device.h"
#include "Registers.h"
#include "Settings.h"


Mixer::Mixer(Device *device)
                :
                fDevice(device),
                fAC97Dev(NULL),
                fReadPort(RegCodecRead),
                fWritePort(RegCodecWrite),
                fMaskRW(1 << 15),
                fMaskRD(1 << 15),
                fMaskWD(1 << 15),
                fHasVRA(false),
                fInputRates(0),
                fOutputRates(0),
                fInputFormats(0),
                fOutputFormats(0)
{
        switch (device->HardwareId()) {
                case ALi5451:
                        if (fDevice->PCIInfo().revision > 0x01) {
                                fReadPort = RegCodecWrite;
                                fMaskWD = 1 << 8;
                        }
                case SiS7018:
                        break;
                case TridentDX:
                        break;
                case TridentNX:
                        fReadPort       = RegNXCodecRead;
                        fWritePort      = RegNXCodecWrite;
                        fMaskRW         = 1 << 11;
                        fMaskRD         = 1 << 10;
                        fMaskWD         = 1 << 11;
                        break;
        }

        TRACE("Regs:R:%#x;W:%#x;MRW:%#x;MRD:%#x;MWD:%#x\n",
                        fReadPort, fWritePort, fMaskRW, fMaskRD, fMaskWD);
}


void
Mixer::Init()
{
        ac97_attach(&fAC97Dev, _ReadAC97, _WriteAC97, this,
                        fDevice->PCIInfo().u.h0.subsystem_vendor_id,
                        fDevice->PCIInfo().u.h0.subsystem_id);

        _ReadSupportedFormats();
}


void
Mixer::_ReadSupportedFormats()
{
        fInputRates = B_SR_48000;
        fOutputRates = 0;
        fInputFormats = B_FMT_16BIT;
        fOutputFormats = B_FMT_16BIT;

        uint16 extId = ac97_reg_cached_read(fAC97Dev, AC97_EXTENDED_ID);
        uint16 extCtrl = ac97_reg_cached_read(fAC97Dev, AC97_EXTENDED_STAT_CTRL);
        TRACE("Ext.ID:%#010x %#010x\n", extId, extCtrl);

        fHasVRA = ((extId & EXID_VRA) == EXID_VRA);
        if (!fHasVRA) {
                fOutputRates = B_SR_8000 | B_SR_11025 | B_SR_12000
                                                | B_SR_16000 | B_SR_22050 | B_SR_24000
                                                | B_SR_32000 | B_SR_44100 | B_SR_48000;
                TRACE("VRA is not supported. Assume all rates are ok:%#010x\n",
                                        fOutputRates);
                return;
        }

        struct _Cap {
                ac97_capability fCap;
                uint32                  fRate;
        } caps[] = {
                { CAP_PCM_RATE_8000,  B_SR_8000 },
                { CAP_PCM_RATE_11025, B_SR_11025 },
                { CAP_PCM_RATE_12000, B_SR_12000 },
                { CAP_PCM_RATE_16000, B_SR_16000 },
                { CAP_PCM_RATE_22050, B_SR_22050 },
                { CAP_PCM_RATE_24000, B_SR_24000 },
                { CAP_PCM_RATE_32000, B_SR_32000 },
                { CAP_PCM_RATE_44100, B_SR_44100 },
                { CAP_PCM_RATE_48000, B_SR_48000 }
        };

        for (size_t i = 0; i < B_COUNT_OF(caps); i++) {
                if (ac97_has_capability(fAC97Dev, caps[i].fCap))
                        fOutputRates |= caps[i].fRate;
        }

        if (fOutputRates == 0) {
                ERROR("Output rates are not guessed. Force to 48 kHz.\n");
                fOutputRates = B_SR_48000;
        }
        
        TRACE("Output rates are:%#010x\n", fOutputRates);
}


void
Mixer::Free()
{
        ac97_detach(fAC97Dev);
}


uint16
Mixer::_ReadAC97(void* cookie, uint8 reg)
{
        return reinterpret_cast<Mixer*>(cookie)->_ReadAC97(reg);
}


void
Mixer::_WriteAC97(void* cookie, uint8 reg, uint16 data)
{
        return reinterpret_cast<Mixer*>(cookie)->_WriteAC97(reg, data);
}


bool
Mixer::_WaitPortReady(uint8 reg, uint32 mask, uint32* result)
{
        int count = 200;
        uint32 data = 0;

        while (count--) {
                data = fDevice->ReadPCI32(reg);
                if ((data & mask) == 0) {
                        if (result != NULL)
                                *result = data;
                        return true;
                }
                spin(1);
        }

        ERROR("AC97 register %#04x is not ready.\n", reg);
        return false;
}


bool
Mixer::_WaitSTimerReady()
{
        if (fDevice->HardwareId() != ALi5451)
                return true;

        int count = 200;
        uint32 time1 = fDevice->ReadPCI32(RegALiSTimer);

        while (count--) {
                uint32 time2 = fDevice->ReadPCI32(RegALiSTimer);
                if (time2 != time1)
                        return true;
                spin(1);
        }

        ERROR("AC97 STimer is not ready.\n");
        return false;
}


uint16
Mixer::_ReadAC97(uint8 reg)
{
        uint32 result = 0;

        cpu_status cp = fDevice->Lock();

        if (_WaitPortReady(fWritePort, fMaskRD)
                && _WaitPortReady(fReadPort, fMaskRD) && _WaitSTimerReady()) {

                fDevice->WritePCI32(fReadPort, (reg & 0x7f) | fMaskRW);

                if (_WaitSTimerReady() && _WaitPortReady(fReadPort, fMaskRD, &result))
                        result = (result >> 16) & 0xffff;
        }

        fDevice->Unlock(cp);

        return result;
}


void
Mixer::_WriteAC97(uint8 reg, uint16 data)
{
        cpu_status cp = fDevice->Lock();

        if (_WaitPortReady(fWritePort, fMaskRW) && _WaitSTimerReady()) {

                fDevice->WritePCI32(fWritePort,
                                                        (data << 16) | (reg & 0x7f) | fMaskRW | fMaskWD);
        }
        
        fDevice->Unlock(cp);
}


void
Mixer::SetOutputRate(uint32 outputRate)
{
        if (!fHasVRA)
                return;

        uint32 rate = 0;
        if (!ac97_get_rate(fAC97Dev, AC97_PCM_FRONT_DAC_RATE, &rate)) {
                ERROR("Failed to read PCM Front DAC Rate. Force to %d.\n", outputRate);
        } else
        if (rate == outputRate) {
                TRACE("AC97 PCM Front DAC rate not set to %d\n", outputRate);
                return;
        }

        if (!ac97_set_rate(fAC97Dev, AC97_PCM_FRONT_DAC_RATE, rate))
                ERROR("Failed to set AC97 PCM Front DAC rate\n");
        else
                TRACE("AC97 PCM Front DAC rate set to %d\n", outputRate);
}


// Control ids are encoded in the following way:
// GGBBRRTT --
//                      GG      - in gain controls: 10th of granularity. Signed!
//                              - in MUX controls: value of selector.
//                      BB      - in gain controls: base level - correspond to 0 db.
//                              - mute, boost, enable controls: offset of "on/off" bit
//                      RR      - AC97 Register handled by this control
//                      TT      - MIXControlTypes bits

const int stepShift     = 24;   // offset to GG
const int baseShift     = 16;   // offset to BB
const int regShift      = 8;    // offset to RR

enum MIXControlTypes {
        MIX_RGain       = 0x01,
        MIX_LGain       = 0x10,
        MIX_Mono        = MIX_RGain,
        MIX_Stereo      = MIX_LGain | MIX_RGain,
        MIX_Mute        = 0x04,
        MIX_Boost       = 0x08,
        MIX_MUX         = 0x20,
        MIX_Enable      = 0x40,
        MIX_Check       = MIX_Mute | MIX_Boost | MIX_Enable
};


struct GainInfo {
        uint8 fOff;             // offset of mask in register word
        uint8 fBase;    // base - default value of register
        uint8 fMask;    // mask - maximal value of register
        float fMult;    // multiplier - bits to dB recalculate
        uint8 fEnaOff;  // offset of "on/off" bit in register word
};


GainInfo MixGain        = { 0x00, 0x00, 0x3f, -1.5, 0x0f };
GainInfo InGain         = { 0x00, 0x08, 0x1f, -1.5, 0x0f };
GainInfo RecGain        = { 0x00, 0x00, 0x0f,  1.5, 0x0f };
GainInfo BeepGain       = { 0x01, 0x00, 0x1e,  3.0, 0x0f };
GainInfo ToneGain       = { 0x00, 0x07, 0x0f, -1.5, 0x10 };     // 0x10 - mean no "mute"
GainInfo D3DGain        = { 0x00, 0x00, 0x0f,  1.0, 0x10 };


struct MIXControlInfo {
                uint8           fAC97Reg;
                GainInfo*       fInfo;
                strind_id       fNameId;
                uint16          fType;
const   char*           fName;

                uint8           fExAC97Reg;
                strind_id       fExNameId;
const   char*           fExName;
                uint8           fExOff;
};


MIXControlInfo OutputControls[] = {
        { AC97_MASTER_VOLUME,   &MixGain,       S_MASTER,
                MIX_Stereo | MIX_Mute, NULL },
        { AC97_AUX_OUT_VOLUME,  &MixGain,       S_AUX,
                MIX_Stereo | MIX_Mute, NULL },
        { AC97_MASTER_TONE,             &ToneGain,      S_OUTPUT_BASS,
                MIX_LGain, NULL },
        { AC97_MASTER_TONE,             &ToneGain, S_OUTPUT_TREBLE,
                MIX_RGain, NULL },
        { AC97_3D_CONTROL,              &D3DGain,       S_OUTPUT_3D_DEPTH,
                MIX_RGain | MIX_Enable, NULL,
                AC97_GENERAL_PURPOSE, S_ENABLE, NULL, 0x0d },
        { AC97_3D_CONTROL,              &D3DGain,       S_OUTPUT_3D_CENTER,
                MIX_LGain, NULL },
        { AC97_MONO_VOLUME,             &MixGain,       S_MONO_MIX,
                MIX_Mono | MIX_Mute, NULL },
        { AC97_PC_BEEP_VOLUME,  &BeepGain,      S_BEEP,
                MIX_Mono | MIX_Mute, NULL }
};


MIXControlInfo InputControls[] = {
        { AC97_PCM_OUT_VOLUME,  &InGain,        S_WAVE,
                MIX_Stereo | MIX_Mute, NULL },
        { AC97_MIC_VOLUME,              &InGain,        S_MIC,
                MIX_Mono | MIX_Mute | MIX_Boost, NULL,
                AC97_MIC_VOLUME, S_null, "+ 20 dB", 0x06 },
        { AC97_LINE_IN_VOLUME,  &InGain,        S_LINE,
                MIX_Stereo | MIX_Mute, NULL },
        { AC97_CD_VOLUME,               &InGain,        S_CD,
                MIX_Stereo | MIX_Mute, NULL },
        { AC97_VIDEO_VOLUME,    &InGain,        S_VIDEO,
                MIX_Stereo | MIX_Mute, NULL },
        { AC97_AUX_IN_VOLUME,   &InGain,        S_AUX,
                MIX_Stereo | MIX_Mute, NULL },
        { AC97_PHONE_VOLUME,    &InGain,        S_PHONE,
                MIX_Mono | MIX_Mute, NULL }
};


strind_id RecordSources[] = {
        S_MIC,
        S_CD,
        S_VIDEO,
        S_AUX,
        S_LINE,
        S_STEREO_MIX,
        S_MONO_MIX,
        S_PHONE
};


MIXControlInfo RecordControls[] = {
        { AC97_RECORD_GAIN,             &RecGain,       S_null,
                MIX_Stereo | MIX_Mute | MIX_MUX, "Record Gain",
                AC97_RECORD_SELECT, S_null, "Source" },
        { AC97_RECORD_GAIN_MIC, &RecGain,       S_MIC,
                MIX_Mono | MIX_Mute, NULL }
};


void
Mixer::_InitGainLimits(multi_mix_control& Control, GainInfo& Info)
{
        float base = Info.fBase >> Info.fOff;
        float max = Info.fMask >> Info.fOff;

        float min_gain = Info.fMult * (0.0 - base);
        float max_gain = Info.fMult * (max - base);

        Control.gain.min_gain = (Info.fMult > 0) ? min_gain : max_gain;
        Control.gain.max_gain = (Info.fMult > 0) ? max_gain : min_gain;
        Control.gain.granularity = Info.fMult;

        // encode gain granularity in the MSB of the control id.
        uint8 gran = (uint8)(Info.fMult * 10.);
        Control.id |= (gran << stepShift);
        Control.id |= (Info.fBase << baseShift);

        TRACE("base:%#04x; mask:%#04x; mult:%f\n",
                        Info.fBase, Info.fMask, Info.fMult);
        TRACE(" min:%f; max:%f; gran:%f -> %#010x\n",
                        Control.gain.min_gain, Control.gain.max_gain,
                        Control.gain.granularity, Control.id);
}


bool
Mixer::_CheckRegFeatures(uint8 AC97Reg, uint16& mask, uint16& result)
{
        uint16 backup = ac97_reg_cached_read(fAC97Dev, AC97Reg);

        ac97_reg_cached_write(fAC97Dev, AC97Reg, mask);
        result = ac97_reg_cached_read(fAC97Dev, AC97Reg);
        TRACE("Check for register %#02x: %#x -> %#x.\n", AC97Reg, mask, result);
        
        ac97_reg_cached_write(fAC97Dev, AC97Reg, backup);

        return mask != result;
}


bool
Mixer::_CorrectMIXControlInfo(MIXControlInfo& Info, GainInfo& gainInfo)
{
        uint16 newMask = gainInfo.fMask;
        uint16 testMask = 0;
        uint16 testResult = 0;
        
        switch (Info.fAC97Reg) {
                case AC97_AUX_OUT_VOLUME:
                        if (ac97_has_capability(fAC97Dev, CAP_HEADPHONE_OUT)) {
                                Info.fNameId = S_HEADPHONE;
                                Info.fName = NULL;
                        }
                case AC97_MASTER_VOLUME:
                        testMask = 0x2020;
                        newMask = 0x1f;
                        break;
                case AC97_MASTER_TONE:
                        if (!ac97_has_capability(fAC97Dev, CAP_BASS_TREBLE_CTRL))
                                return false;
                        testMask = 0x0f0f;
                        break;
                case AC97_3D_CONTROL:
                        if (!ac97_has_capability(fAC97Dev, CAP_3D_ENHANCEMENT))
                                return false;
                        testMask = 0x0f0f;
                        break;
                case AC97_RECORD_GAIN_MIC:
                        if (!ac97_has_capability(fAC97Dev, CAP_PCM_MIC))
                                return false;
                        testMask = 0x0f;
                        break;
                case AC97_MONO_VOLUME:
                        testMask = 0x20;
                        newMask = 0x1f;
                        break;
                case AC97_PC_BEEP_VOLUME:
                        testMask = 0x1e;
                        break;
                case AC97_VIDEO_VOLUME:
                case AC97_AUX_IN_VOLUME:
                case AC97_PHONE_VOLUME:
                        testMask = 0x1f;
                        break;
                default:
                        return true;
        }
        
        if (_CheckRegFeatures(Info.fAC97Reg, testMask, testResult)) {
                if (testResult == 0)
                        return false;

                gainInfo.fMask = newMask;
        }

        return true;
}


int32
Mixer::_CreateMIXControlGroup(multi_mix_control_info* MultiInfo, int32& index,
                                                                        int32 parentIndex, MIXControlInfo& Info)
{
        // check the optional registers and features,
        // correct the range if required and do not add if not supported
        GainInfo gainInfo = *Info.fInfo;
        if (!_CorrectMIXControlInfo(Info, gainInfo))
                return 0;

        int32 IdReg = Info.fAC97Reg << regShift;
        multi_mix_control* Controls = MultiInfo->controls;

        int32 groupIndex
                = Controls[index].id    = IdReg | Info.fType;
        Controls[index].flags           = B_MULTI_MIX_GROUP;
        Controls[index].parent          = parentIndex;
        Controls[index].string          = Info.fNameId;
        if (Info.fName != NULL)
                strlcpy(Controls[index].name, Info.fName,
                                                                   sizeof(Controls[index].name));
        index++;

        if (Info.fType & MIX_Mute) {
                Controls[index].id              = IdReg | MIX_Check;
                Controls[index].id              |= Info.fInfo->fEnaOff << baseShift;
                Controls[index].flags   = B_MULTI_MIX_ENABLE;
                Controls[index].parent  = groupIndex;
                Controls[index].string  = S_MUTE;

                TRACE("Mute:%#010x\n", Controls[index].id);
                index++;
        }

        if (Info.fType & MIX_Enable) {
                int32 IdExReg = Info.fExAC97Reg << regShift;
                Controls[index].id              = IdExReg | MIX_Check;
                Controls[index].id              |= (Info.fExOff << baseShift);
                Controls[index].flags   = B_MULTI_MIX_ENABLE;
                Controls[index].parent  = groupIndex;
                Controls[index].string  = Info.fExNameId;
                if (Info.fExName != NULL)
                        strlcpy(Controls[index].name, Info.fExName,
                                                                sizeof(Controls[index].name));

                TRACE("Enable:%#010x\n", Controls[index].id);
                index++;
        }

        int32 gainIndex = 0;
        if (Info.fType & MIX_LGain) {
                Controls[index].id              = IdReg | MIX_LGain;
                Controls[index].flags   = B_MULTI_MIX_GAIN;
                Controls[index].parent  = groupIndex;
                Controls[index].string  = S_GAIN;
                _InitGainLimits(Controls[index], gainInfo);
                gainIndex = Controls[index].id;
                index++;
        }

        if (Info.fType & MIX_RGain) {
                Controls[index].id              = IdReg | MIX_RGain;
                Controls[index].flags   = B_MULTI_MIX_GAIN;
                Controls[index].parent  = groupIndex;
                Controls[index].master  = gainIndex;
                Controls[index].string  = S_GAIN;
                _InitGainLimits(Controls[index], gainInfo);
                index++;
        }

        if (Info.fType & MIX_Boost) {
                Controls[index].id              = IdReg | MIX_Check;
                Controls[index].id              |= (Info.fExOff << baseShift);
                Controls[index].flags   = B_MULTI_MIX_ENABLE;
                Controls[index].parent  = groupIndex;
                Controls[index].string  = Info.fExNameId;
                if (Info.fExName != NULL)
                        strlcpy(Controls[index].name, Info.fExName,
                                                                sizeof(Controls[index].name));

                TRACE("Boost:%#010x\n", Controls[index].id);
                index++;
        }

        if (Info.fType & MIX_MUX) {
                int32 IdMUXReg = Info.fExAC97Reg << regShift;
                int32 recordMUX
                        = Controls[index].id    = IdMUXReg | MIX_MUX;
                Controls[index].flags           = B_MULTI_MIX_MUX;
                Controls[index].parent          = groupIndex;
                Controls[index].string          = S_null;
                if (Info.fExName != NULL)
                        strlcpy(Controls[index].name, Info.fExName,
                                                                sizeof(Controls[index].name));

                TRACE("MUX:%#010x\n", Controls[index].id);
                index++;

                for (size_t i = 0; i < B_COUNT_OF(RecordSources); i++) {
                        Controls[index].id              = IdMUXReg | (i << stepShift) | MIX_MUX;
                        Controls[index].flags   = B_MULTI_MIX_MUX_VALUE;
                        Controls[index].master  = 0;
                        Controls[index].string  = RecordSources[i];
                        Controls[index].parent  = recordMUX;

                        TRACE("MUX Item:%#010x\n", Controls[index].id);

                        index++;
                }
        }

        return groupIndex;
}


status_t
Mixer::ListMixControls(multi_mix_control_info* Info)
{
        int32 index = 0;
        multi_mix_control* Controls = Info->controls;
        int32 mixerGroup
                = Controls[index].id    = 0x8000; // 0x80 - is not a valid AC97 register
        Controls[index].flags           = B_MULTI_MIX_GROUP;
        Controls[index].parent          = 0;
        Controls[index].string          = S_OUTPUT;
        index++;

        for (size_t i = 0; i < B_COUNT_OF(OutputControls); i++) {
                _CreateMIXControlGroup(Info, index, mixerGroup, OutputControls[i]);     
        }

        int32 inputGroup
                = Controls[index].id    = 0x8100;
        Controls[index].flags           = B_MULTI_MIX_GROUP;
        Controls[index].parent          = 0;
        Controls[index].string          = S_INPUT;
        index++;

        for (size_t i = 0; i < B_COUNT_OF(InputControls); i++) {
                _CreateMIXControlGroup(Info, index, inputGroup, InputControls[i]);
        }

        int32 recordGroup
                = Controls[index].id    = 0x8200;
        Controls[index].flags           = B_MULTI_MIX_GROUP;
        Controls[index].parent          = 0;
        Controls[index].string          = S_null;
        strlcpy(Controls[index].name, "Record", sizeof(Controls[index].name));
        index++;

        for (size_t i = 0; i < B_COUNT_OF(RecordControls); i++) {
                _CreateMIXControlGroup(Info, index, recordGroup, RecordControls[i]);
        }

        Info->control_count = index;

        return B_OK;
}


status_t
Mixer::GetMix(multi_mix_value_info *Info)
{
        for (int32 i = 0; i < Info->item_count; i++) {

                int32 Id= Info->values[i].id;
                uint8 Reg = (Id >> regShift) & 0xFF;

                if ((Reg & 0x01) || Reg > 0x7e) {
                        ERROR("Invalid AC97 register:%#04x.Bypass it.\n", Reg);
                        continue;
                }

                uint16 RegValue = ac97_reg_cached_read(fAC97Dev, Reg);

                if ((Id & MIX_Check) == MIX_Check) {
                        uint16 mask = 1 << ((Id >> baseShift) & 0xff);
                        Info->values[i].enable = (RegValue & mask) == mask;
                        TRACE("%#04x Mute|Enable|Boost:%d [data:%#04x]\n",
                                                        Reg, Info->values[i].enable, RegValue);
                        continue;
                }
                
                if ((Id & MIX_MUX) == MIX_MUX) {
                        Info->values[i].mux = (RegValue | (RegValue >> 8)) & 0x7;
                        TRACE("%#04x MUX:%d [data:%#04x]\n",
                                                        Reg, Info->values[i].mux, RegValue);
                        continue;
                }

                float mult = 0.1 * (int8)(Id >> stepShift);
                float base = mult * ((Id >> baseShift) & 0xff);

                if ((Id & MIX_RGain) == MIX_RGain) {
                        uint8 gain = RegValue & 0x3f;
                        Info->values[i].gain = mult * gain - base;
                        TRACE("%#04x for RGain:%f [mult:%f base:%f] <- %#04x\n",
                                                        Reg, Info->values[i].gain, mult, base, gain);
                        continue;
                }

                if ((Id & MIX_LGain) == MIX_LGain) {
                        uint8 gain = (RegValue >> 8) & 0x3f;
                        Info->values[i].gain = mult * gain - base;
                        TRACE("%#04x for LGain:%f [mult:%f base:%f] <- %#04x\n",
                                                        Reg, Info->values[i].gain, mult, base, gain);
                }
        }

        return B_OK;
}


status_t
Mixer::SetMix(multi_mix_value_info *Info)
{
        for (int32 i = 0; i < Info->item_count; i++) {

                int32 Id = Info->values[i].id;
                uint8 Reg = (Id >> regShift) & 0xFF;

                if ((Reg & 0x01) || Reg > 0x7e) {
                        ERROR("Invalid AC97 register:%#04x.Bypass it.\n", Reg);
                        continue;
                }

                uint16 RegValue = ac97_reg_cached_read(fAC97Dev, Reg);

                if ((Id & MIX_Check) == MIX_Check) {
                        uint16 mask = 1 << ((Id >> baseShift) & 0xff);
                        if (Info->values[i].enable)
                                RegValue |= mask;
                        else
                                RegValue &= ~mask;
                        TRACE("%#04x Mute/Enable:%d -> data:%#04x\n",
                                                        Reg, Info->values[i].enable, RegValue);
                }
                
                if ((Id & MIX_MUX) == MIX_MUX) {
                        uint8 mux = Info->values[i].mux & 0x7;
                        RegValue = mux | (mux << 8);
                        TRACE("%#04x MUX:%d -> data:%#04x\n",
                                                        Reg, Info->values[i].mux, RegValue);
                }

                float mult = 0.1 * (int8)(Id >> stepShift);
                float base = mult * ((Id >> baseShift) & 0xff);

                float gain = (Info->values[i].gain + base) / mult;
                gain += (gain > 0.) ? 0.5 : -0.5;
                uint8 gainValue = (uint8)gain;

                if ((Id & MIX_RGain) == MIX_RGain) {
                        RegValue &= 0xffc0;
                        RegValue |= gainValue;
                        
                        TRACE("%#04x for RGain:%f [mult:%f base:%f] -> %#04x\n",
                                                        Reg, Info->values[i].gain, mult, base, gainValue);
                }

                if ((Id & MIX_LGain) == MIX_LGain) {
                        RegValue &= 0xc0ff;
                        RegValue |= (gainValue << 8);
                        TRACE("%#04x for LGain:%f [mult:%f base:%f] -> %#04x\n",
                                                        Reg, Info->values[i].gain, mult, base, gainValue);
                }

                TRACE("%#04x Write:%#06x\n", Reg, RegValue);

                ac97_reg_cached_write(fAC97Dev, Reg, RegValue);
        }

        return B_OK;
}