/*
* Copyright 2007-2012, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Ithamar Adema, ithamar AT unet DOT nl
* Axel Dörfler, axeld@pinc-software.de
* Jérôme Duval, korli@users.berlios.de
*/
#include "driver.h"
#include "hda_codec_defs.h"
#undef TRACE
#define TRACE_CODEC
#ifdef TRACE_CODEC
# define TRACE(a...) dprintf("hda: " a)
#else
# define TRACE(a...)
#endif
#define ERROR(a...) dprintf("hda: " a)
#define HDA_ALL 0xffffffff
#define HDA_QUIRK_GPIO_COUNT 8
#define HDA_QUIRK_GPIO0 (1 << 0)
#define HDA_QUIRK_GPIO1 (1 << 1)
#define HDA_QUIRK_GPIO2 (1 << 2)
#define HDA_QUIRK_GPIO3 (1 << 3)
#define HDA_QUIRK_GPIO4 (1 << 4)
#define HDA_QUIRK_GPIO5 (1 << 5)
#define HDA_QUIRK_GPIO6 (1 << 6)
#define HDA_QUIRK_GPIO7 (1 << 7)
#define HDA_QUIRK_IVREF50 (1 << 8)
#define HDA_QUIRK_IVREF80 (1 << 9)
#define HDA_QUIRK_IVREF100 (1 << 10)
#define HDA_QUIRK_OVREF50 (1 << 11)
#define HDA_QUIRK_OVREF80 (1 << 12)
#define HDA_QUIRK_OVREF100 (1 << 13)
#define HDA_QUIRK_IVREF (HDA_QUIRK_IVREF50 | HDA_QUIRK_IVREF80 \
| HDA_QUIRK_IVREF100)
#define HDA_QUIRK_OVREF (HDA_QUIRK_OVREF50 | HDA_QUIRK_OVREF80 \
| HDA_QUIRK_OVREF100)
#define ANALOGDEVICES_VENDORID 0x11d4
#define CIRRUSLOGIC_VENDORID 0x1013
#define CONEXANT_VENDORID 0x14f1
#define IDT_VENDORID 0x111d
#define REALTEK_VENDORID 0x10ec
#define SIGMATEL_VENDORID 0x8384
static const char* kPortConnector[] = {
"Jack", "None", "Fixed", "Dual"
};
static const char* kDefaultDevice[] = {
"Line out", "Speaker", "HP out", "CD", "SPDIF out", "Digital other out",
"Modem line side", "Modem hand side", "Line in", "AUX", "Mic in",
"Telephony", "SPDIF in", "Digital other in", "Reserved", "Other"
};
static const char* kConnectionType[] = {
"N/A", "1/8\"", "1/4\"", "ATAPI internal", "RCA", "Optical",
"Other digital", "Other analog", "Multichannel analog (DIN)",
"XLR/Professional", "RJ-11 (modem)", "Combination", "-", "-", "-", "Other"
};
static const char* kJackColor[] = {
"N/A", "Black", "Grey", "Blue", "Green", "Red", "Orange", "Yellow",
"Purple", "Pink", "-", "-", "-", "-", "White", "Other"
};
static const struct {
uint32 subsystem_vendor_id, subsystem_id;
uint32 codec_vendor_id, codec_id;
uint32 quirks, nonquirks;
} kCodecQuirks[] = {
{ HDA_ALL, HDA_ALL, HDA_ALL, HDA_ALL, HDA_QUIRK_IVREF, 0 },
{ HDA_ALL, HDA_ALL, HDA_ALL, HDA_ALL, HDA_QUIRK_IVREF, 0 },
{ 0x10de, 0x0d94, CIRRUSLOGIC_VENDORID, HDA_ALL,
HDA_QUIRK_GPIO1 | HDA_QUIRK_GPIO3, 0 }, // MacBookAir 3,1(2)
{ 0x10de, 0xcb79, CIRRUSLOGIC_VENDORID, 0x4206,
HDA_QUIRK_GPIO1 | HDA_QUIRK_GPIO3, 0 }, // MacBook Pro 5,5
{ 0x10de, 0xcb89, CIRRUSLOGIC_VENDORID, 0x4206,
HDA_QUIRK_GPIO1 | HDA_QUIRK_GPIO3, 0 }, // MacBookPro 7,1
{ 0x8384, 0x7680, SIGMATEL_VENDORID, 0x7680,
HDA_QUIRK_GPIO0 | HDA_QUIRK_GPIO1, 0}, // Apple Intel Mac
{ 0x106b, 0x00a0, REALTEK_VENDORID, 0x0885,
HDA_QUIRK_GPIO0 | HDA_QUIRK_OVREF80, 0}, // iMac 8,1, Macbook Pro 3,1
{ 0x106b, 0x00a1, REALTEK_VENDORID, 0x0885,
HDA_QUIRK_GPIO0 | HDA_QUIRK_OVREF50, 0}, // MacBook
{ 0x106b, 0x00a3, REALTEK_VENDORID, 0x0885,
HDA_QUIRK_GPIO0, 0}, // MacBook
{ HDA_ALL, HDA_ALL, IDT_VENDORID, 0x76b2, HDA_QUIRK_GPIO0, 0},
};
static const char*
get_widget_type_name(hda_widget_type type)
{
switch (type) {
case WT_AUDIO_OUTPUT:
return "Audio output";
case WT_AUDIO_INPUT:
return "Audio input";
case WT_AUDIO_MIXER:
return "Audio mixer";
case WT_AUDIO_SELECTOR:
return "Audio selector";
case WT_PIN_COMPLEX:
return "Pin complex";
case WT_POWER:
return "Power";
case WT_VOLUME_KNOB:
return "Volume knob";
case WT_BEEP_GENERATOR:
return "Beep generator";
case WT_VENDOR_DEFINED:
return "Vendor defined";
default:
return "Unknown";
}
}
const char*
get_widget_location(uint32 location)
{
switch (location >> 4) {
case 0:
case 2:
switch (location & 0xf) {
case 2:
return "Front";
case 3:
return "Left";
case 4:
return "Right";
case 5:
return "Top";
case 6:
return "Bottom";
case 7:
return "Rear panel";
case 8:
return "Drive bay";
case 0:
case 1:
default:
return NULL;
}
case 1:
switch (location & 0xf) {
case 7:
return "Riser";
case 8:
return "HDMI";
default:
return NULL;
}
case 3:
switch (location & 0xf) {
case 6:
return "Bottom";
case 7:
return "Inside lid";
case 8:
return "Outside lid";
default:
return NULL;
}
}
return NULL;
}
static void
dump_widget_audio_capabilities(uint32 capabilities)
{
static const struct {
uint32 flag;
const char* name;
} kFlags[] = {
{AUDIO_CAP_CP_CAPS, "CP caps"},
{AUDIO_CAP_LEFT_RIGHT_SWAP, "L-R swap"},
{AUDIO_CAP_POWER_CONTROL, "Power"},
{AUDIO_CAP_DIGITAL, "Digital"},
{AUDIO_CAP_CONNECTION_LIST, "Conn. list"},
{AUDIO_CAP_UNSOLICITED_RESPONSES, "Unsol. responses"},
{AUDIO_CAP_PROCESSING_CONTROLS, "Proc widget"},
{AUDIO_CAP_STRIPE, "Stripe"},
{AUDIO_CAP_FORMAT_OVERRIDE, "Format override"},
{AUDIO_CAP_AMPLIFIER_OVERRIDE, "Amplifier override"},
{AUDIO_CAP_OUTPUT_AMPLIFIER, "Out amplifier"},
{AUDIO_CAP_INPUT_AMPLIFIER, "In amplifier"},
{AUDIO_CAP_STEREO, "Stereo"},
};
char buffer[256];
int offset = 0;
for (uint32 j = 0; j < sizeof(kFlags) / sizeof(kFlags[0]); j++) {
if ((capabilities & kFlags[j].flag) != 0)
offset += sprintf(buffer + offset, "[%s] ", kFlags[j].name);
}
if (offset != 0)
TRACE("\t%s\n", buffer);
}
static void
dump_widget_inputs(hda_widget& widget)
{
// dump connections
char buffer[256];
int offset = 0;
for (uint32 i = 0; i < widget.num_inputs; i++) {
int32 input = widget.inputs[i];
if ((int32)i != widget.active_input)
offset += sprintf(buffer + offset, "%ld ", input);
else
offset += sprintf(buffer + offset, "<%ld> ", input);
}
if (offset != 0)
TRACE("\tInputs: %s\n", buffer);
}
static void
dump_widget_amplifier_capabilities(hda_widget& widget, bool input)
{
uint32 capabilities;
if (input)
capabilities = widget.capabilities.input_amplifier;
else
capabilities = widget.capabilities.output_amplifier;
if (capabilities == 0)
return;
TRACE("\t%s Amp: %sstep size: %f dB, # steps: %ld, offset to 0 dB: "
"%ld\n", input ? "In" : "Out",
(capabilities & AMP_CAP_MUTE) != 0 ? "supports mute, " : "",
AMP_CAP_STEP_SIZE(capabilities),
AMP_CAP_NUM_STEPS(capabilities),
AMP_CAP_OFFSET(capabilities));
}
static void
dump_widget_pm_support(hda_widget& widget)
{
TRACE("\tSupported power states: %s%s%s%s%s%s%s%s\n",
widget.pm & POWER_STATE_D0 ? "D0 " : "",
widget.pm & POWER_STATE_D1 ? "D1 " : "",
widget.pm & POWER_STATE_D2 ? "D2 " : "",
widget.pm & POWER_STATE_D3 ? "D3 " : "",
widget.pm & POWER_STATE_D3COLD ? "D3COLD " : "",
widget.pm & POWER_STATE_S3D3COLD ? "S3D3COLD " : "",
widget.pm & POWER_STATE_CLKSTOP ? "CLKSTOP " : "",
widget.pm & POWER_STATE_EPSS ? "EPSS " : "");
}
static void
dump_widget_stream_support(hda_widget& widget)
{
TRACE("\tSupported formats: %s%s%s%s%s%s%s%s%s\n",
widget.d.io.formats & B_FMT_8BIT_S ? "8bits " : "",
widget.d.io.formats & B_FMT_16BIT ? "16bits " : "",
widget.d.io.formats & B_FMT_20BIT ? "20bits " : "",
widget.d.io.formats & B_FMT_24BIT ? "24bits " : "",
widget.d.io.formats & B_FMT_32BIT ? "32bits " : "",
widget.d.io.formats & B_FMT_FLOAT ? "float " : "",
widget.d.io.formats & B_FMT_DOUBLE ? "double " : "",
widget.d.io.formats & B_FMT_EXTENDED ? "extended " : "",
widget.d.io.formats & B_FMT_BITSTREAM ? "bitstream " : "");
TRACE("\tSupported rates: %s%s%s%s%s%s%s%s%s%s%s%s\n",
widget.d.io.rates & B_SR_8000 ? "8khz " : "",
widget.d.io.rates & B_SR_11025 ? "11khz " : "",
widget.d.io.rates & B_SR_16000 ? "16khz " : "",
widget.d.io.rates & B_SR_22050 ? "22khz " : "",
widget.d.io.rates & B_SR_32000 ? "32khz " : "",
widget.d.io.rates & B_SR_44100 ? "44khz " : "",
widget.d.io.rates & B_SR_48000 ? "48khz " : "",
widget.d.io.rates & B_SR_88200 ? "88khz " : "",
widget.d.io.rates & B_SR_96000 ? "96khz " : "",
widget.d.io.rates & B_SR_176400 ? "176khz " : "",
widget.d.io.rates & B_SR_192000 ? "192khz " : "",
widget.d.io.rates & B_SR_384000 ? "384khz " : "");
}
static void
dump_pin_complex_capabilities(hda_widget& widget)
{
TRACE("\t%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
widget.d.pin.capabilities & PIN_CAP_IMP_SENSE ? "[Imp Sense] " : "",
widget.d.pin.capabilities & PIN_CAP_TRIGGER_REQ ? "[Trigger Req]" : "",
widget.d.pin.capabilities & PIN_CAP_PRES_DETECT ? "[Pres Detect]" : "",
widget.d.pin.capabilities & PIN_CAP_HP_DRIVE ? "[HP Drive]" : "",
widget.d.pin.capabilities & PIN_CAP_OUTPUT ? "[Output]" : "",
widget.d.pin.capabilities & PIN_CAP_INPUT ? "[Input]" : "",
widget.d.pin.capabilities & PIN_CAP_BALANCE ? "[Balance]" : "",
widget.d.pin.capabilities & PIN_CAP_VREF_CTRL_HIZ ? "[VRef HIZ]" : "",
widget.d.pin.capabilities & PIN_CAP_VREF_CTRL_50 ? "[VRef 50]" : "",
widget.d.pin.capabilities & PIN_CAP_VREF_CTRL_GROUND ? "[VRef Gr]" : "",
widget.d.pin.capabilities & PIN_CAP_VREF_CTRL_80 ? "[VRef 80]" : "",
widget.d.pin.capabilities & PIN_CAP_VREF_CTRL_100 ? "[VRef 100]" : "",
widget.d.pin.capabilities & PIN_CAP_EAPD_CAP ? "[EAPD]" : "");
}
static void
dump_audiogroup_widgets(hda_audio_group* audioGroup)
{
TRACE("\tAudiogroup:\n");
// Iterate over all widgets and collect info
for (uint32 i = 0; i < audioGroup->widget_count; i++) {
hda_widget& widget = audioGroup->widgets[i];
uint32 nodeID = audioGroup->widget_start + i;
TRACE("%ld: %s\n", nodeID, get_widget_type_name(widget.type));
switch (widget.type) {
case WT_AUDIO_OUTPUT:
case WT_AUDIO_INPUT:
break;
case WT_PIN_COMPLEX:
dump_pin_complex_capabilities(widget);
break;
default:
break;
}
dump_widget_pm_support(widget);
dump_widget_audio_capabilities(widget.capabilities.audio);
dump_widget_amplifier_capabilities(widget, true);
dump_widget_amplifier_capabilities(widget, false);
dump_widget_inputs(widget);
}
}
// #pragma mark -
static void
hda_codec_get_quirks(hda_codec* codec)
{
codec->quirks = 0;
uint32 subSystemID = codec->controller->pci_info.u.h0.subsystem_id;
uint32 subSystemVendorID
= codec->controller->pci_info.u.h0.subsystem_vendor_id;
for (uint32 i = 0;
i < (sizeof(kCodecQuirks) / sizeof(kCodecQuirks[0])); i++) {
if (kCodecQuirks[i].subsystem_id != HDA_ALL
&& kCodecQuirks[i].subsystem_id != subSystemID)
continue;
if (kCodecQuirks[i].subsystem_vendor_id != HDA_ALL
&& kCodecQuirks[i].subsystem_vendor_id != subSystemVendorID)
continue;
if (kCodecQuirks[i].codec_vendor_id != HDA_ALL
&& kCodecQuirks[i].codec_vendor_id != codec->vendor_id)
continue;
if (kCodecQuirks[i].codec_id != HDA_ALL
&& kCodecQuirks[i].codec_id != codec->product_id)
continue;
codec->quirks |= kCodecQuirks[i].quirks;
codec->quirks &= ~kCodecQuirks[i].nonquirks;
}
}
static status_t
hda_get_pm_support(hda_codec* codec, uint32 nodeID, uint32* pm)
{
corb_t verb = MAKE_VERB(codec->addr, nodeID, VID_GET_PARAMETER,
PID_POWERSTATE_SUPPORT);
uint32 response;
status_t status = hda_send_verbs(codec, &verb, &response, 1);
if (status == B_OK)
*pm = response & 0xf;
return status;
}
static status_t
hda_get_stream_support(hda_codec* codec, uint32 nodeID, uint32* formats,
uint32* rates)
{
corb_t verbs[2];
uint32 resp[2];
status_t status;
verbs[0] = MAKE_VERB(codec->addr, nodeID, VID_GET_PARAMETER,
PID_STREAM_SUPPORT);
verbs[1] = MAKE_VERB(codec->addr, nodeID, VID_GET_PARAMETER,
PID_PCM_SUPPORT);
status = hda_send_verbs(codec, verbs, resp, 2);
if (status != B_OK)
return status;
*formats = 0;
*rates = 0;
if ((resp[0] & (STREAM_FLOAT | STREAM_PCM)) != 0) {
if (resp[1] & (1 << 0))
*rates |= B_SR_8000;
if (resp[1] & (1 << 1))
*rates |= B_SR_11025;
if (resp[1] & (1 << 2))
*rates |= B_SR_16000;
if (resp[1] & (1 << 3))
*rates |= B_SR_22050;
if (resp[1] & (1 << 4))
*rates |= B_SR_32000;
if (resp[1] & (1 << 5))
*rates |= B_SR_44100;
if (resp[1] & (1 << 6))
*rates |= B_SR_48000;
if (resp[1] & (1 << 7))
*rates |= B_SR_88200;
if (resp[1] & (1 << 8))
*rates |= B_SR_96000;
if (resp[1] & (1 << 9))
*rates |= B_SR_176400;
if (resp[1] & (1 << 10))
*rates |= B_SR_192000;
if (resp[1] & (1 << 11))
*rates |= B_SR_384000;
if (resp[1] & PCM_8_BIT)
*formats |= B_FMT_8BIT_S;
if (resp[1] & PCM_16_BIT)
*formats |= B_FMT_16BIT;
if (resp[1] & PCM_20_BIT)
*formats |= B_FMT_20BIT;
if (resp[1] & PCM_24_BIT)
*formats |= B_FMT_24BIT;
if (resp[1] & PCM_32_BIT)
*formats |= B_FMT_32BIT;
}
if ((resp[0] & STREAM_FLOAT) != 0)
*formats |= B_FMT_FLOAT;
if ((resp[0] & STREAM_AC3) != 0) {
*formats |= B_FMT_BITSTREAM;
}
return B_OK;
}
// #pragma mark - widget functions
static status_t
hda_widget_get_pm_support(hda_audio_group* audioGroup, hda_widget* widget)
{
return hda_get_pm_support(audioGroup->codec, widget->node_id, &widget->pm);
}
static status_t
hda_widget_get_stream_support(hda_audio_group* audioGroup, hda_widget* widget)
{
if (audioGroup->widget.node_id != widget->node_id
&& (widget->capabilities.audio & AUDIO_CAP_FORMAT_OVERRIDE) == 0) {
// adopt capabilities of the audio group
widget->d.io.formats = audioGroup->widget.d.io.formats;
widget->d.io.rates = audioGroup->widget.d.io.rates;
return B_OK;
}
return hda_get_stream_support(audioGroup->codec, widget->node_id,
&widget->d.io.formats, &widget->d.io.rates);
}
static status_t
hda_widget_get_amplifier_capabilities(hda_audio_group* audioGroup,
hda_widget* widget)
{
uint32 response;
corb_t verb;
if ((widget->capabilities.audio & AUDIO_CAP_OUTPUT_AMPLIFIER) != 0
|| audioGroup->widget.node_id == widget->node_id) {
if ((widget->capabilities.audio & AUDIO_CAP_AMPLIFIER_OVERRIDE) != 0
|| audioGroup->widget.node_id == widget->node_id) {
verb = MAKE_VERB(audioGroup->codec->addr, widget->node_id,
VID_GET_PARAMETER, PID_OUTPUT_AMPLIFIER_CAP);
status_t status = hda_send_verbs(audioGroup->codec, &verb,
&response, 1);
if (status != B_OK)
return status;
widget->capabilities.output_amplifier = response;
} else {
// adopt capabilities from the audio function group
widget->capabilities.output_amplifier
= audioGroup->widget.capabilities.output_amplifier;
}
}
if ((widget->capabilities.audio & AUDIO_CAP_INPUT_AMPLIFIER) != 0
|| audioGroup->widget.node_id == widget->node_id) {
if ((widget->capabilities.audio & AUDIO_CAP_AMPLIFIER_OVERRIDE
|| audioGroup->widget.node_id == widget->node_id) != 0) {
verb = MAKE_VERB(audioGroup->codec->addr, widget->node_id,
VID_GET_PARAMETER, PID_INPUT_AMPLIFIER_CAP);
status_t status = hda_send_verbs(audioGroup->codec, &verb,
&response, 1);
if (status != B_OK)
return status;
widget->capabilities.input_amplifier = response;
} else {
// adopt capabilities from the audio function group
widget->capabilities.input_amplifier
= audioGroup->widget.capabilities.input_amplifier;
}
}
return B_OK;
}
hda_widget*
hda_audio_group_get_widget(hda_audio_group* audioGroup, uint32 nodeID)
{
if (audioGroup->widget_start > nodeID
|| audioGroup->widget_start + audioGroup->widget_count < nodeID)
return NULL;
return &audioGroup->widgets[nodeID - audioGroup->widget_start];
}
static status_t
hda_widget_get_connections(hda_audio_group* audioGroup, hda_widget* widget)
{
corb_t verb = MAKE_VERB(audioGroup->codec->addr, widget->node_id,
VID_GET_PARAMETER, PID_CONNECTION_LIST_LENGTH);
uint32 response;
if (hda_send_verbs(audioGroup->codec, &verb, &response, 1) != B_OK)
return B_ERROR;
uint32 listEntries = response & 0x7f;
bool longForm = (response & 0xf0) != 0;
if (listEntries == 0)
return B_OK;
#if 1
if (widget->num_inputs > 1) {
// Get currently active connection
verb = MAKE_VERB(audioGroup->codec->addr, widget->node_id,
VID_GET_CONNECTION_SELECT, 0);
if (hda_send_verbs(audioGroup->codec, &verb, &response, 1) == B_OK)
widget->active_input = response & 0xff;
}
#endif
uint32 valuesPerEntry = longForm ? 2 : 4;
uint32 shift = 32 / valuesPerEntry;
uint32 rangeMask = (1 << (shift - 1));
int32 previousInput = -1;
uint32 numInputs = 0;
for (uint32 i = 0; i < listEntries; i++) {
if ((i % valuesPerEntry) == 0) {
// We get 2 or 4 answers per call depending on if we're
// in short or long list mode
verb = MAKE_VERB(audioGroup->codec->addr, widget->node_id,
VID_GET_CONNECTION_LIST_ENTRY, i);
if (hda_send_verbs(audioGroup->codec, &verb, &response, 1)
!= B_OK) {
ERROR("Error parsing inputs for widget %ld!\n",
widget->node_id);
break;
}
}
int32 input = (response >> (shift * (i % valuesPerEntry)))
& ((1 << shift) - 1);
if (input & rangeMask) {
// found range
input &= ~rangeMask;
if (input < previousInput || previousInput == -1) {
ERROR("invalid range from %ld to %ld\n", previousInput,
input);
continue;
}
for (int32 rangeInput = previousInput + 1; rangeInput <= input
&& numInputs < MAX_INPUTS; rangeInput++) {
widget->inputs[numInputs++] = rangeInput;
}
previousInput = -1;
} else if (numInputs < MAX_INPUTS) {
// standard value
widget->inputs[numInputs++] = input;
previousInput = input;
}
}
widget->num_inputs = numInputs;
if (widget->num_inputs == 1)
widget->active_input = 0;
return B_OK;
}
static status_t
hda_widget_get_associations(hda_audio_group* audioGroup)
{
uint32 index = 0;
for (uint32 i = 0; i < MAX_ASSOCIATIONS; i++) {
for (uint32 j = 0; j < audioGroup->widget_count; j++) {
if (index >= MAX_ASSOCIATIONS) {
TRACE("too many associations, bailing!\n");
return B_ERROR;
}
hda_widget& widget = audioGroup->widgets[j];
if (widget.type != WT_PIN_COMPLEX)
continue;
if (CONF_DEFAULT_ASSOCIATION(widget.d.pin.config) != i)
continue;
if (audioGroup->associations[index].pin_count == 0) {
audioGroup->associations[index].index = index;
audioGroup->associations[index].enabled = true;
}
uint32 sequence = CONF_DEFAULT_SEQUENCE(widget.d.pin.config);
if (audioGroup->associations[index].pins[sequence] != 0) {
audioGroup->associations[index].enabled = false;
}
audioGroup->associations[index].pins[sequence] = widget.node_id;
audioGroup->associations[index].pin_count++;
if (i == 15)
index++;
}
if (i != 15 && audioGroup->associations[index].pin_count != 0)
index++;
}
audioGroup->association_count = index;
return B_OK;
}
static uint32
hda_widget_prepare_pin_ctrl(hda_audio_group* audioGroup, hda_widget* widget,
bool isOutput)
{
uint32 ctrl = 0;
if (isOutput)
ctrl = PIN_ENABLE_HEAD_PHONE | PIN_ENABLE_OUTPUT;
else
ctrl = PIN_ENABLE_INPUT;
if (PIN_CAP_IS_VREF_CTRL_50_CAP(widget->d.pin.capabilities)
&& (audioGroup->codec->quirks & (isOutput ? HDA_QUIRK_OVREF50
: HDA_QUIRK_IVREF50))) {
ctrl |= PIN_ENABLE_VREF_50;
TRACE("%s vref 50 enabled\n", isOutput ? "output" : "input");
}
if (PIN_CAP_IS_VREF_CTRL_80_CAP(widget->d.pin.capabilities)
&& (audioGroup->codec->quirks & (isOutput ? HDA_QUIRK_OVREF80
: HDA_QUIRK_IVREF80))) {
ctrl |= PIN_ENABLE_VREF_80;
TRACE("%s vref 80 enabled\n", isOutput ? "output" : "input");
}
if (PIN_CAP_IS_VREF_CTRL_100_CAP(widget->d.pin.capabilities)
&& (audioGroup->codec->quirks & (isOutput ? HDA_QUIRK_OVREF100
: HDA_QUIRK_IVREF100))) {
ctrl |= PIN_ENABLE_VREF_100;
TRACE("%s vref 100 enabled\n", isOutput ? "output" : "input");
}
return ctrl;
}
// #pragma mark - audio group functions
static status_t
hda_codec_parse_audio_group(hda_audio_group* audioGroup)
{
corb_t verbs[3];
uint32 resp[3];
hda_codec* codec = audioGroup->codec;
uint32 codec_id = (codec->vendor_id << 16) | codec->product_id;
hda_widget_get_stream_support(audioGroup, &audioGroup->widget);
hda_widget_get_pm_support(audioGroup, &audioGroup->widget);
hda_widget_get_amplifier_capabilities(audioGroup, &audioGroup->widget);
verbs[0] = MAKE_VERB(audioGroup->codec->addr, audioGroup->widget.node_id,
VID_GET_PARAMETER, PID_AUDIO_GROUP_CAP);
verbs[1] = MAKE_VERB(audioGroup->codec->addr, audioGroup->widget.node_id,
VID_GET_PARAMETER, PID_GPIO_COUNT);
verbs[2] = MAKE_VERB(audioGroup->codec->addr, audioGroup->widget.node_id,
VID_GET_PARAMETER, PID_SUB_NODE_COUNT);
if (hda_send_verbs(audioGroup->codec, verbs, resp, 3) != B_OK)
return B_ERROR;
TRACE("Audio Group: Output delay: %ld samples, Input delay: %ld "
"samples, Beep Generator: %s\n", AUDIO_GROUP_CAP_OUTPUT_DELAY(resp[0]),
AUDIO_GROUP_CAP_INPUT_DELAY(resp[0]),
AUDIO_GROUP_CAP_BEEPGEN(resp[0]) ? "yes" : "no");
TRACE(" #GPIO: %ld, #GPO: %ld, #GPI: %ld, unsol: %s, wake: %s\n",
GPIO_COUNT_NUM_GPIO(resp[1]), GPIO_COUNT_NUM_GPO(resp[1]),
GPIO_COUNT_NUM_GPI(resp[1]), GPIO_COUNT_GPIUNSOL(resp[1]) ? "yes" : "no",
GPIO_COUNT_GPIWAKE(resp[1]) ? "yes" : "no");
dump_widget_stream_support(audioGroup->widget);
audioGroup->gpio = resp[1];
audioGroup->widget_start = SUB_NODE_COUNT_START(resp[2]);
audioGroup->widget_count = SUB_NODE_COUNT_TOTAL(resp[2]);
TRACE(" widget start %lu, count %lu\n", audioGroup->widget_start,
audioGroup->widget_count);
audioGroup->widgets = (hda_widget*)calloc(audioGroup->widget_count,
sizeof(*audioGroup->widgets));
if (audioGroup->widgets == NULL) {
ERROR("ERROR: Not enough memory!\n");
return B_NO_MEMORY;
}
// Iterate over all Widgets and collect info
for (uint32 i = 0; i < audioGroup->widget_count; i++) {
hda_widget& widget = audioGroup->widgets[i];
uint32 nodeID = audioGroup->widget_start + i;
uint32 capabilities;
verbs[0] = MAKE_VERB(audioGroup->codec->addr, nodeID, VID_GET_PARAMETER,
PID_AUDIO_WIDGET_CAP);
if (hda_send_verbs(audioGroup->codec, verbs, &capabilities, 1) != B_OK)
return B_ERROR;
widget.type = (hda_widget_type)((capabilities & AUDIO_CAP_TYPE_MASK)
>> AUDIO_CAP_TYPE_SHIFT);
// Check specific node ids declared as inputs as beepers
switch (codec_id) {
case 0x11d41882:
case 0x11d41883:
case 0x11d41884:
case 0x11d4194a:
case 0x11d4194b:
case 0x11d41987:
case 0x11d41988:
case 0x11d4198b:
case 0x11d4989b:
if (nodeID == 26)
widget.type = WT_BEEP_GENERATOR;
break;
case 0x10ec0260:
if (nodeID == 23)
widget.type = WT_BEEP_GENERATOR;
break;
case 0x10ec0262:
case 0x10ec0268:
case 0x10ec0880:
case 0x10ec0882:
case 0x10ec0883:
case 0x10ec0885:
case 0x10ec0888:
case 0x10ec0889:
if (nodeID == 29)
widget.type = WT_BEEP_GENERATOR;
break;
}
widget.active_input = -1;
widget.capabilities.audio = capabilities;
widget.node_id = nodeID;
if ((capabilities & AUDIO_CAP_POWER_CONTROL) != 0) {
// We support power; switch us on!
verbs[0] = MAKE_VERB(audioGroup->codec->addr, nodeID,
VID_SET_POWER_STATE, 0);
hda_send_verbs(audioGroup->codec, verbs, NULL, 1);
snooze(1000);
}
if ((capabilities & (AUDIO_CAP_INPUT_AMPLIFIER
| AUDIO_CAP_OUTPUT_AMPLIFIER)) != 0) {
hda_widget_get_amplifier_capabilities(audioGroup, &widget);
}
TRACE("%ld: %s\n", nodeID, get_widget_type_name(widget.type));
switch (widget.type) {
case WT_AUDIO_OUTPUT:
case WT_AUDIO_INPUT:
hda_widget_get_stream_support(audioGroup, &widget);
dump_widget_stream_support(widget);
break;
case WT_PIN_COMPLEX:
verbs[0] = MAKE_VERB(audioGroup->codec->addr, nodeID,
VID_GET_PARAMETER, PID_PIN_CAP);
if (hda_send_verbs(audioGroup->codec, verbs, resp, 1) == B_OK) {
widget.d.pin.capabilities = resp[0];
TRACE("\t%s%s\n", PIN_CAP_IS_INPUT(resp[0]) ? "[Input] " : "",
PIN_CAP_IS_OUTPUT(resp[0]) ? "[Output]" : "");
} else {
ERROR("%s: Error getting Pin Complex IO\n", __func__);
}
verbs[0] = MAKE_VERB(audioGroup->codec->addr, nodeID,
VID_GET_CONFIGURATION_DEFAULT, 0);
if (hda_send_verbs(audioGroup->codec, verbs, resp, 1) == B_OK) {
widget.d.pin.config = resp[0];
const char* location =
get_widget_location(CONF_DEFAULT_LOCATION(resp[0]));
TRACE("\t%s, %s%s%s, %s, %s, Association:%ld\n",
kPortConnector[CONF_DEFAULT_CONNECTIVITY(resp[0])],
location ? location : "",
location ? " " : "",
kDefaultDevice[CONF_DEFAULT_DEVICE(resp[0])],
kConnectionType[CONF_DEFAULT_CONNTYPE(resp[0])],
kJackColor[CONF_DEFAULT_COLOR(resp[0])],
CONF_DEFAULT_ASSOCIATION(resp[0]));
}
break;
case WT_VOLUME_KNOB:
verbs[0] = MAKE_VERB(audioGroup->codec->addr, nodeID,
VID_SET_VOLUME_KNOB_CONTROL, 0x0);
hda_send_verbs(audioGroup->codec, verbs, NULL, 1);
break;
default:
break;
}
hda_widget_get_pm_support(audioGroup, &widget);
hda_widget_get_connections(audioGroup, &widget);
dump_widget_pm_support(widget);
dump_widget_audio_capabilities(capabilities);
dump_widget_amplifier_capabilities(widget, true);
dump_widget_amplifier_capabilities(widget, false);
dump_widget_inputs(widget);
}
hda_widget_get_associations(audioGroup);
// init the codecs
switch (codec_id) {
case 0x10ec0888: {
hda_verb_write(codec, 0x20, VID_SET_COEFFICIENT_INDEX, 0x0);
uint32 tmp;
hda_verb_read(codec, 0x20, VID_GET_PROCESSING_COEFFICIENT, &tmp);
hda_verb_write(codec, 0x20, VID_SET_COEFFICIENT_INDEX, 0x7);
hda_verb_write(codec, 0x20, VID_SET_PROCESSING_COEFFICIENT,
(tmp & 0xf0) == 0x20 ? 0x830 : 0x3030);
break;
}
}
return B_OK;
}
/*! Find output path for widget */
static bool
hda_widget_find_output_path(hda_audio_group* audioGroup, hda_widget* widget,
uint32 depth, bool &alreadyUsed)
{
alreadyUsed = false;
if (widget == NULL || depth > 16)
return false;
switch (widget->type) {
case WT_AUDIO_OUTPUT:
widget->flags |= WIDGET_FLAG_OUTPUT_PATH;
TRACE(" %*soutput: added output widget %ld\n", (int)depth * 2, "", widget->node_id);
return true;
case WT_AUDIO_MIXER:
case WT_AUDIO_SELECTOR:
{
// already used
if ((widget->flags & WIDGET_FLAG_OUTPUT_PATH) != 0) {
alreadyUsed = true;
return false;
}
// search for output in this path
bool found = false;
for (uint32 i = 0; i < widget->num_inputs; i++) {
hda_widget* inputWidget = hda_audio_group_get_widget(audioGroup,
widget->inputs[i]);
if (hda_widget_find_output_path(audioGroup, inputWidget,
depth + 1, alreadyUsed)) {
if (widget->active_input == -1)
widget->active_input = i;
widget->flags |= WIDGET_FLAG_OUTPUT_PATH;
TRACE(" %*soutput: added mixer/selector widget %ld\n", (int)depth * 2, "", widget->node_id);
found = true;
}
}
if (!found) TRACE(" %*soutput: not added mixer/selector widget %ld\n", (int)depth * 2, "", widget->node_id);
return found;
}
default:
return false;
}
}
/*! Find input path for widget */
static bool
hda_widget_find_input_path(hda_audio_group* audioGroup, hda_widget* widget,
uint32 depth)
{
if (widget == NULL || depth > 16)
return false;
switch (widget->type) {
case WT_PIN_COMPLEX:
// already used
if ((widget->flags
& (WIDGET_FLAG_INPUT_PATH | WIDGET_FLAG_OUTPUT_PATH)) != 0)
return false;
if (PIN_CAP_IS_INPUT(widget->d.pin.capabilities)) {
switch (CONF_DEFAULT_DEVICE(widget->d.pin.config)) {
case PIN_DEV_CD:
case PIN_DEV_LINE_IN:
case PIN_DEV_MIC_IN:
widget->flags |= WIDGET_FLAG_INPUT_PATH;
TRACE(" %*sinput: added input widget %ld\n", (int)depth * 2, "", widget->node_id);
return true;
break;
}
}
return false;
case WT_AUDIO_INPUT:
case WT_AUDIO_MIXER:
case WT_AUDIO_SELECTOR:
{
// already used
if ((widget->flags
& (WIDGET_FLAG_INPUT_PATH | WIDGET_FLAG_OUTPUT_PATH)) != 0)
return false;
// search for pin complex in this path
bool found = false;
for (uint32 i = 0; i < widget->num_inputs; i++) {
hda_widget* inputWidget = hda_audio_group_get_widget(audioGroup,
widget->inputs[i]);
if (hda_widget_find_input_path(audioGroup, inputWidget,
depth + 1)) {
if (widget->active_input == -1)
widget->active_input = i;
widget->flags |= WIDGET_FLAG_INPUT_PATH;
TRACE(" %*sinput: added mixer/selector widget %ld\n", (int)depth * 2, "", widget->node_id);
found = true;
}
}
if (!found) TRACE(" %*sinput: not added mixer/selector widget %ld\n", (int)depth * 2, "", widget->node_id);
return found;
}
default:
return false;
}
}
static bool
hda_audio_group_build_output_tree(hda_audio_group* audioGroup, bool useMixer)
{
bool found = false;
TRACE("build output tree: %suse mixer\n", useMixer ? "" : "don't ");
for (uint32 i = 0; i < audioGroup->widget_count; i++) {
hda_widget& widget = audioGroup->widgets[i];
if (widget.type != WT_PIN_COMPLEX
|| !PIN_CAP_IS_OUTPUT(widget.d.pin.capabilities))
continue;
int device = CONF_DEFAULT_DEVICE(widget.d.pin.config);
if (device != PIN_DEV_HEAD_PHONE_OUT
&& device != PIN_DEV_DIGITAL_OTHER_OUT
&& device != PIN_DEV_SPEAKER
&& device != PIN_DEV_LINE_OUT)
continue;
TRACE(" look at pin widget %ld (%ld inputs)\n", widget.node_id, widget.num_inputs);
for (uint32 j = 0; j < widget.num_inputs; j++) {
hda_widget* inputWidget = hda_audio_group_get_widget(audioGroup,
widget.inputs[j]);
TRACE(" try widget %ld: %p\n", widget.inputs[j], inputWidget);
if (inputWidget == NULL)
continue;
if (useMixer && inputWidget->type != WT_AUDIO_MIXER
&& inputWidget->type != WT_AUDIO_SELECTOR)
continue;
TRACE(" widget %ld is candidate\n", inputWidget->node_id);
bool alreadyUsed = false;
if (hda_widget_find_output_path(audioGroup, inputWidget, 0,
alreadyUsed)
|| (device == PIN_DEV_HEAD_PHONE_OUT && alreadyUsed)) {
// find the output path to an audio output widget
// or for headphones, an already used widget
TRACE(" add pin widget %ld\n", widget.node_id);
if (widget.active_input == -1)
widget.active_input = j;
widget.flags |= WIDGET_FLAG_OUTPUT_PATH;
found = true;
break;
}
}
}
return found;
}
static bool
hda_audio_group_build_input_tree(hda_audio_group* audioGroup)
{
bool found = false;
TRACE("build input tree\n");
for (uint32 i = 0; i < audioGroup->widget_count; i++) {
hda_widget& widget = audioGroup->widgets[i];
if (widget.type != WT_AUDIO_INPUT)
continue;
TRACE(" look at input widget %ld (%ld inputs)\n", widget.node_id, widget.num_inputs);
for (uint32 j = 0; j < widget.num_inputs; j++) {
hda_widget* inputWidget = hda_audio_group_get_widget(audioGroup,
widget.inputs[j]);
TRACE(" try widget %ld: %p\n", widget.inputs[j], inputWidget);
if (inputWidget == NULL)
continue;
TRACE(" widget %ld is candidate\n", inputWidget->node_id);
if (hda_widget_find_input_path(audioGroup, inputWidget, 0)) {
TRACE(" add pin widget %ld\n", widget.node_id);
if (widget.active_input == -1)
widget.active_input = j;
widget.flags |= WIDGET_FLAG_INPUT_PATH;
found = true;
break;
}
}
}
return found;
}
static status_t
hda_audio_group_build_tree(hda_audio_group* audioGroup)
{
if (!hda_audio_group_build_output_tree(audioGroup, true)) {
// didn't find a mixer path, try again without
TRACE("try without mixer!\n");
if (!hda_audio_group_build_output_tree(audioGroup, false))
return ENODEV;
}
if (!hda_audio_group_build_input_tree(audioGroup)) {
ERROR("build input tree failed\n");
}
TRACE("build tree!\n");
// select active connections
for (uint32 i = 0; i < audioGroup->widget_count; i++) {
hda_widget& widget = audioGroup->widgets[i];
if (widget.active_input == -1)
widget.active_input = 0;
if (widget.num_inputs < 2)
continue;
if (widget.type != WT_AUDIO_INPUT
&& widget.type != WT_AUDIO_SELECTOR
&& widget.type != WT_PIN_COMPLEX)
continue;
corb_t verb = MAKE_VERB(audioGroup->codec->addr,
widget.node_id, VID_SET_CONNECTION_SELECT, widget.active_input);
if (hda_send_verbs(audioGroup->codec, &verb, NULL, 1) != B_OK)
ERROR("Setting output selector %ld failed on widget %ld!\n",
widget.active_input, widget.node_id);
}
// GPIO
uint32 gpio = 0;
for (uint32 i = 0; i < GPIO_COUNT_NUM_GPIO(audioGroup->gpio)
&& i < HDA_QUIRK_GPIO_COUNT; i++) {
if (audioGroup->codec->quirks & (1 << i)) {
gpio |= (1 << i);
}
}
if (gpio != 0) {
corb_t verb[] = {
MAKE_VERB(audioGroup->codec->addr,
audioGroup->widget.node_id, VID_SET_GPIO_DATA, gpio),
MAKE_VERB(audioGroup->codec->addr,
audioGroup->widget.node_id, VID_SET_GPIO_EN, gpio),
MAKE_VERB(audioGroup->codec->addr,
audioGroup->widget.node_id, VID_SET_GPIO_DIR, gpio)
};
TRACE("Setting gpio 0x%lx\n", gpio);
if (hda_send_verbs(audioGroup->codec, verb, NULL, 3) != B_OK)
ERROR("Setting gpio failed!\n");
}
dump_audiogroup_widgets(audioGroup);
return B_OK;
}
static void
hda_audio_group_switch_init(hda_audio_group* audioGroup)
{
for (uint32 i = 0; i < audioGroup->widget_count; i++) {
hda_widget& widget = audioGroup->widgets[i];
if (widget.type != WT_PIN_COMPLEX)
continue;
if ((widget.capabilities.audio & AUDIO_CAP_UNSOLICITED_RESPONSES) != 0
&& (widget.d.pin.capabilities & PIN_CAP_PRES_DETECT) != 0
&& (CONF_DEFAULT_MISC(widget.d.pin.config) & 1) == 0) {
corb_t verb = MAKE_VERB(audioGroup->codec->addr, widget.node_id,
VID_SET_UNSOLRESP, UNSOLRESP_ENABLE);
hda_send_verbs(audioGroup->codec, &verb, NULL, 1);
TRACE("Enabled unsolicited responses on widget %ld\n",
widget.node_id);
}
}
}
static void
hda_audio_group_check_sense(hda_audio_group* audioGroup, bool disable)
{
for (uint32 i = 0; i < audioGroup->widget_count; i++) {
hda_widget& widget = audioGroup->widgets[i];
if (widget.type != WT_PIN_COMPLEX
|| !PIN_CAP_IS_OUTPUT(widget.d.pin.capabilities)
|| CONF_DEFAULT_DEVICE(widget.d.pin.config)
!= PIN_DEV_HEAD_PHONE_OUT)
continue;
corb_t verb = MAKE_VERB(audioGroup->codec->addr, widget.node_id,
VID_GET_PINSENSE, 0);
uint32 response;
hda_send_verbs(audioGroup->codec, &verb, &response, 1);
disable = response & PIN_SENSE_PRESENCE_DETECT;
TRACE("sensed pin widget %ld, %d\n", widget.node_id, disable);
uint32 ctrl = hda_widget_prepare_pin_ctrl(audioGroup, &widget,
true);
verb = MAKE_VERB(audioGroup->codec->addr, widget.node_id,
VID_SET_PIN_WIDGET_CONTROL, disable ? ctrl : 0);
hda_send_verbs(audioGroup->codec, &verb, NULL, 1);
break;
}
for (uint32 i = 0; i < audioGroup->widget_count; i++) {
hda_widget& widget = audioGroup->widgets[i];
if (widget.type != WT_PIN_COMPLEX
|| !PIN_CAP_IS_OUTPUT(widget.d.pin.capabilities))
continue;
int device = CONF_DEFAULT_DEVICE(widget.d.pin.config);
if (device != PIN_DEV_AUX
&& device != PIN_DEV_SPEAKER
&& device != PIN_DEV_LINE_OUT)
continue;
uint32 ctrl = hda_widget_prepare_pin_ctrl(audioGroup, &widget,
true);
corb_t verb = MAKE_VERB(audioGroup->codec->addr, widget.node_id,
VID_SET_PIN_WIDGET_CONTROL, disable ? 0 : ctrl);
hda_send_verbs(audioGroup->codec, &verb, NULL, 1);
}
}
static status_t
hda_codec_switch_handler(hda_codec* codec)
{
while (acquire_sem(codec->unsol_response_sem) == B_OK) {
uint32 response = codec->unsol_responses[codec->unsol_response_read++];
codec->unsol_response_read %= MAX_CODEC_UNSOL_RESPONSES;
bool disable = response & 1;
hda_audio_group* audioGroup = codec->audio_groups[0];
hda_audio_group_check_sense(audioGroup, disable);
}
return B_OK;
}
static void
hda_codec_delete_audio_group(hda_audio_group* audioGroup)
{
if (audioGroup == NULL)
return;
if (audioGroup->playback_stream != NULL)
hda_stream_delete(audioGroup->playback_stream);
if (audioGroup->record_stream != NULL)
hda_stream_delete(audioGroup->record_stream);
free(audioGroup->multi);
free(audioGroup->widgets);
free(audioGroup);
}
static status_t
hda_codec_new_audio_group(hda_codec* codec, uint32 audioGroupNodeID)
{
hda_audio_group* audioGroup = (hda_audio_group*)calloc(1,
sizeof(hda_audio_group));
if (audioGroup == NULL)
return B_NO_MEMORY;
// Setup minimal info needed by hda_codec_parse_afg
audioGroup->widget.node_id = audioGroupNodeID;
audioGroup->codec = codec;
audioGroup->multi = (hda_multi*)calloc(1,
sizeof(hda_multi));
if (audioGroup->multi == NULL) {
free(audioGroup);
return B_NO_MEMORY;
}
audioGroup->multi->group = audioGroup;
// Parse all widgets in Audio Function Group
status_t status = hda_codec_parse_audio_group(audioGroup);
if (status != B_OK)
goto err;
// Setup for worst-case scenario; we cannot find any output Pin Widgets
status = ENODEV;
if (hda_audio_group_build_tree(audioGroup) != B_OK)
goto err;
hda_audio_group_switch_init(audioGroup);
audioGroup->playback_stream = hda_stream_new(audioGroup, STREAM_PLAYBACK);
audioGroup->record_stream = hda_stream_new(audioGroup, STREAM_RECORD);
TRACE("streams playback %p, record %p\n", audioGroup->playback_stream,
audioGroup->record_stream);
if (audioGroup->playback_stream != NULL
|| audioGroup->record_stream != NULL) {
codec->audio_groups[codec->num_audio_groups++] = audioGroup;
hda_audio_group_check_sense(audioGroup, false);
return B_OK;
}
err:
free(audioGroup->widgets);
free(audioGroup);
return status;
}
// #pragma mark -
status_t
hda_audio_group_get_widgets(hda_audio_group* audioGroup, hda_stream* stream)
{
hda_widget_type type;
uint32 flags;
if (stream->type == STREAM_PLAYBACK) {
type = WT_AUDIO_OUTPUT;
flags = WIDGET_FLAG_OUTPUT_PATH;
} else {
// record
type = WT_AUDIO_INPUT;
flags = WIDGET_FLAG_INPUT_PATH;
}
uint32 count = 0;
for (uint32 i = 0; i < audioGroup->widget_count && count < MAX_IO_WIDGETS;
i++) {
hda_widget& widget = audioGroup->widgets[i];
if ((widget.flags & flags) != 0) {
if (widget.type == WT_PIN_COMPLEX) {
stream->pin_widget = widget.node_id;
uint32 ctrl = hda_widget_prepare_pin_ctrl(audioGroup, &widget,
flags == WIDGET_FLAG_OUTPUT_PATH);
TRACE("ENABLE pin widget %ld\n", widget.node_id);
// FIXME: Force Pin Widget to unmute; enable hp/output
corb_t verb = MAKE_VERB(audioGroup->codec->addr,
widget.node_id,
VID_SET_PIN_WIDGET_CONTROL, ctrl);
hda_send_verbs(audioGroup->codec, &verb, NULL, 1);
if (PIN_CAP_IS_EAPD_CAP(widget.d.pin.capabilities)) {
uint32 result;
verb = MAKE_VERB(audioGroup->codec->addr,
widget.node_id, VID_GET_EAPDBTL_EN, 0);
if (hda_send_verbs(audioGroup->codec, &verb,
&result, 1) == B_OK) {
result &= 0xff;
verb = MAKE_VERB(audioGroup->codec->addr,
widget.node_id, VID_SET_EAPDBTL_EN,
result | EAPDBTL_ENABLE_EAPD);
hda_send_verbs(audioGroup->codec,
&verb, NULL, 1);
TRACE("ENABLE EAPD pin widget %ld\n", widget.node_id);
}
}
}
if (widget.capabilities.output_amplifier != 0) {
TRACE("UNMUTE/SET OUTPUT GAIN widget %ld (offset %ld)\n", widget.node_id,
AMP_CAP_OFFSET(widget.capabilities.output_amplifier));
corb_t verb = MAKE_VERB(audioGroup->codec->addr,
widget.node_id,
VID_SET_AMPLIFIER_GAIN_MUTE,
AMP_SET_OUTPUT | AMP_SET_LEFT_CHANNEL
| AMP_SET_RIGHT_CHANNEL
| AMP_CAP_OFFSET(widget.capabilities.output_amplifier));
hda_send_verbs(audioGroup->codec, &verb, NULL, 1);
}
if (widget.capabilities.input_amplifier != 0) {
TRACE("UNMUTE/SET INPUT GAIN widget %ld (offset %ld)\n", widget.node_id,
AMP_CAP_OFFSET(widget.capabilities.input_amplifier));
for (uint32 i = 0; i < widget.num_inputs; i++) {
corb_t verb = MAKE_VERB(audioGroup->codec->addr,
widget.node_id,
VID_SET_AMPLIFIER_GAIN_MUTE,
AMP_SET_INPUT | AMP_SET_LEFT_CHANNEL
| AMP_SET_RIGHT_CHANNEL
| AMP_SET_INPUT_INDEX(i)
| ((widget.active_input == (int32)i) ? 0 : AMP_MUTE)
| AMP_CAP_OFFSET(widget.capabilities.input_amplifier));
hda_send_verbs(audioGroup->codec, &verb, NULL, 1);
}
}
}
if (widget.type != type || (widget.flags & flags) == 0
|| (widget.capabilities.audio
& (AUDIO_CAP_STEREO | AUDIO_CAP_DIGITAL)) != AUDIO_CAP_STEREO
|| widget.d.io.formats == 0)
continue;
if (count == 0) {
stream->sample_format = widget.d.io.formats;
stream->sample_rate = widget.d.io.rates;
} else {
stream->sample_format &= widget.d.io.formats;
stream->sample_rate &= widget.d.io.rates;
}
stream->io_widgets[count++] = widget.node_id;
}
if (count == 0)
return B_ENTRY_NOT_FOUND;
stream->num_io_widgets = count;
return B_OK;
}
void
hda_codec_delete(hda_codec* codec)
{
if (codec == NULL)
return;
delete_sem(codec->response_sem);
delete_sem(codec->unsol_response_sem);
int32 result;
wait_for_thread(codec->unsol_response_thread, &result);
for (uint32 i = 0; i < codec->num_audio_groups; i++) {
hda_codec_delete_audio_group(codec->audio_groups[i]);
codec->audio_groups[i] = NULL;
}
free(codec);
}
hda_codec*
hda_codec_new(hda_controller* controller, uint32 codecAddress)
{
if (codecAddress > HDA_MAX_CODECS)
return NULL;
hda_codec* codec = (hda_codec*)calloc(1, sizeof(hda_codec));
if (codec == NULL) {
ERROR("Failed to alloc a codec\n");
return NULL;
}
status_t status;
codec->controller = controller;
codec->addr = codecAddress;
codec->response_sem = create_sem(0, "hda_codec_response_sem");
if (codec->response_sem < B_OK) {
ERROR("Failed to create semaphore\n");
goto err;
}
controller->codecs[codecAddress] = codec;
codec->unsol_response_sem = create_sem(0, "hda_codec_unsol_response_sem");
if (codec->unsol_response_sem < B_OK) {
ERROR("Failed to create semaphore\n");
goto err;
}
codec->unsol_response_read = 0;
codec->unsol_response_write = 0;
struct {
uint32 device : 16;
uint32 vendor : 16;
uint32 stepping : 8;
uint32 revision : 8;
uint32 minor : 4;
uint32 major : 4;
uint32 _reserved0 : 8;
uint32 count : 8;
uint32 _reserved1 : 8;
uint32 start : 8;
uint32 _reserved2 : 8;
} response;
corb_t verbs[3];
verbs[0] = MAKE_VERB(codecAddress, 0, VID_GET_PARAMETER, PID_VENDOR_ID);
verbs[1] = MAKE_VERB(codecAddress, 0, VID_GET_PARAMETER, PID_REVISION_ID);
verbs[2] = MAKE_VERB(codecAddress, 0, VID_GET_PARAMETER,
PID_SUB_NODE_COUNT);
status = hda_send_verbs(codec, verbs, (uint32*)&response, 3);
if (status != B_OK) {
ERROR("Failed to get vendor and revision parameters: %s\n",
strerror(status));
goto err;
}
codec->vendor_id = response.vendor;
codec->product_id = response.device;
codec->stepping = response.stepping;
codec->revision = response.revision;
codec->minor = response.minor;
codec->major = response.major;
hda_codec_get_quirks(codec);
TRACE("Codec %ld Vendor: %04lx Product: %04lx, Revision: "
"%lu.%lu.%lu.%lu Quirks: %04lx\n", codecAddress, response.vendor,
response.device, response.major, response.minor, response.revision,
response.stepping, codec->quirks);
for (uint32 nodeID = response.start;
nodeID < response.start + response.count; nodeID++) {
uint32 groupType;
verbs[0] = MAKE_VERB(codecAddress, nodeID, VID_GET_PARAMETER,
PID_FUNCTION_GROUP_TYPE);
if (hda_send_verbs(codec, verbs, &groupType, 1) != B_OK) {
ERROR("Failed to get function group type\n");
goto err;
}
if ((groupType & FUNCTION_GROUP_NODETYPE_MASK)
== FUNCTION_GROUP_NODETYPE_AUDIO) {
// Found an Audio Function Group!
status_t status = hda_codec_new_audio_group(codec, nodeID);
if (status != B_OK) {
ERROR("Failed to setup new audio function group (%s)!\n",
strerror(status));
goto err;
}
}
}
codec->unsol_response_thread = spawn_kernel_thread(
(status_t(*)(void*))hda_codec_switch_handler,
"hda_codec_unsol_thread", B_LOW_PRIORITY, codec);
if (codec->unsol_response_thread < B_OK) {
ERROR("Failed to spawn thread\n");
goto err;
}
resume_thread(codec->unsol_response_thread);
return codec;
err:
controller->codecs[codecAddress] = NULL;
hda_codec_delete(codec);
return NULL;
}
↑ V576 Incorrect format. Consider checking the third actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the ninth actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the eighth actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the seventh actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the sixth actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the fourth actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the third actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the third actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the third actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the fourth actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the third actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the eighth actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the fourth actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the fourth actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the fourth actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the fourth actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the fourth actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the fourth actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the fifth actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the third actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the fifth actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the third actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the sixth actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the third actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the third actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the third actual argument of the 'sprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the third actual argument of the 'sprintf' function. The memsize type argument is expected.
↑ V576 Incorrect format. Consider checking the third actual argument of the 'dprintf' function. The memsize type argument is expected.