Files
haiku-beta6/src/add-ons/kernel/drivers/audio/ac97/sis7018/Mixer.cpp
T
Siarzhuk Zharski 07910542fd Audio driver for SiS 7018, Trident DX/NX and Acer Labs M5451 hardware
was rewritten to Haiku MultiAudio API.

This changeset replaces both old version of the same driver written for
BeOS R3 Sound API and ali5451 driver that handles the same audio hardware.
The driver was tested with SiS 7018 and ALi M5451 cards. Trident DX/NX
support still should be tested.

Some parts, related to ALi M5451 support were inspired by original ali5451
code so the copyright (c) 2009 by Krzysztof Ćwiertnia was added in the source.



git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@40273 a95241bf-73f2-0310-859d-f6bbb57e9c96
2011-01-23 18:47:57 +00:00

686 lines
16 KiB
C++

/*
* SiS 7018, Trident 4D Wave DX/NX, Acer Lab M5451 Sound Driver.
* Copyright (c) 2002, 2008-2011 S.Zharski <[email protected]>
* 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),
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()
{
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 < _countof(caps); i++) {
if (ac97_has_capability(fAC97Dev, caps[i].fCap))
fOutputRates |= caps[i].fRate;
}
fInputRates = B_SR_48000;
fInputFormats = B_FMT_16BIT;
fOutputFormats = B_FMT_16BIT;
}
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);
}
// 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)
strncpy(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)
strncpy(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)
strncpy(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;
strncpy(Controls[index].name, Info.fExName, sizeof(Controls[index].name));
TRACE("MUX:%#010x\n", Controls[index].id);
index++;
for (size_t i = 0; i < _countof(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 < _countof(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 < _countof(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;
strncpy(Controls[index].name, "Record", sizeof(Controls[index].name));
index++;
for (size_t i = 0; i < _countof(RecordControls); i++) {
_CreateMIXControlGroup(Info, index, recordGroup, RecordControls[i]);
}
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;
}