root/src/add-ons/kernel/drivers/audio/hda/hda_codec.cpp
/*
 * 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


#ifdef TRACE_CODEC
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"
};
#endif

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
        { 0x106b, 0x7200, CIRRUSLOGIC_VENDORID, 0x4208,
                HDA_QUIRK_GPIO0, 0},                                            // MacBookAir 6,2
        { HDA_ALL, HDA_ALL, IDT_VENDORID, 0x76b2, HDA_QUIRK_GPIO0, 0},
};


#ifdef TRACE_CODEC
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";
        }
}
#endif


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;
}


#ifdef TRACE_CODEC

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, "%" B_PRId32 " ", input);
                else
                        offset += sprintf(buffer + offset, "<%" B_PRId32 "> ", 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: %" B_PRIu32 ", "
                "offset to 0 dB: %" B_PRIu32 "\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("%" B_PRIu32 ": %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);
        }
}

#endif // TRACE_CODEC


//      #pragma mark -


static void
hda_codec_get_quirks(hda_codec* codec)
{
        codec->quirks = 0;

        uint32 subSystemID = codec->subsystem_id & 0xffff;
        uint32 subSystemVendorID = codec->subsystem_id >> 16;

        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 %" B_PRIu32 "!\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 %" B_PRId32 " to %" B_PRId32 "\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;

        // Power up the audio function
        verbs[0] = MAKE_VERB(audioGroup->codec->addr, audioGroup->widget.node_id,
                VID_SET_POWER_STATE, 0);
        hda_send_verbs(audioGroup->codec, verbs, NULL, 1);

        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: %" B_PRIu32 " "
                "samples, Input delay: %" B_PRIu32 " "
                "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: %" B_PRIu32 ", #GPO: %" B_PRIu32 ", #GPI: %" B_PRIu32 ", "
                "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");

#ifdef TRACE_CODEC
        dump_widget_stream_support(audioGroup->widget);
#endif

        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 %" B_PRIu32 ", count %" B_PRIu32 "\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("%" B_PRIu32 ": %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);
#ifdef TRACE_CODEC
                                dump_widget_stream_support(widget);
#endif
                                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];
#ifdef TRACE_CODEC
                                        const char* location =
                                                get_widget_location(CONF_DEFAULT_LOCATION(resp[0]));
                                        TRACE("\t%s, %s%s%s, %s, %s, Association:%" B_PRIu32 "\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]));
#endif
                                }
                                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);

#ifdef TRACE_CODEC
                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);
#endif
        }

        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 %" B_PRIu32 "\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 %"
                                                B_PRIu32 "\n", (int)depth * 2, "", widget->node_id);
                                        found = true;
                                }
                        }
                        if (!found) TRACE("      %*soutput: not added mixer/selector widget %"
                                         B_PRIu32 "\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 %" B_PRIu32 "\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 %"
                                                B_PRIu32 "\n", (int)depth * 2, "", widget->node_id);
                                        found = true;
                                }
                        }
                        if (!found) TRACE("      %*sinput: not added mixer/selector widget %"
                                B_PRIu32 "\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 %" B_PRIu32 " (%" B_PRIu32 " 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 %" B_PRIu32 ": %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 %" B_PRIu32 " 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 %" B_PRIu32 "\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 %" B_PRIu32 " (%" B_PRIu32 " 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 %" B_PRIu32 ": %p\n",
                                widget.inputs[j], inputWidget);
                        if (inputWidget == NULL)
                                continue;

                        TRACE("    widget %" B_PRIu32 " is candidate\n",
                                inputWidget->node_id);

                        if (hda_widget_find_input_path(audioGroup, inputWidget, 0)) {
                                TRACE("    add pin widget %" B_PRIu32 "\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 %" B_PRIu32
                                " failed on widget %" B_PRIu32 "!\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%" B_PRIx32 "\n", gpio);
                if (hda_send_verbs(audioGroup->codec, verb, NULL, 3) != B_OK)
                        ERROR("Setting gpio failed!\n");
        }

#ifdef TRACE_CODEC
        dump_audiogroup_widgets(audioGroup);
#endif

        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 %" B_PRIu32 "\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 %" B_PRIu32 ", %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 %" B_PRIu32 "\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 %" B_PRIu32 "\n",
                                                        widget.node_id);
                                        }
                                }
                        }

                        if (widget.capabilities.output_amplifier != 0) {
                                TRACE("UNMUTE/SET OUTPUT GAIN widget %" B_PRIu32 " "
                                        "(offset %" B_PRIu32 ")\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 %" B_PRIu32 " "
                                        "(offset %" B_PRIu32 ")\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 {
                uint16 device;
                uint16 vendor;
                uint32 subsystem;
                uint8 stepping;
                uint8 revision;
                uint8 minor : 4;
                uint8 major : 4;
                uint8 _reserved0;
                uint8 count;
                uint8 _reserved1;
                uint8 start;
                uint8 _reserved2;
        } response;

        corb_t verbs[4];
        verbs[0] = MAKE_VERB(codecAddress, 0, VID_GET_PARAMETER, PID_VENDOR_ID);
        verbs[1] = MAKE_VERB(codecAddress, 0, VID_GET_PARAMETER, PID_SUBSYSTEM_ID);
        verbs[2] = MAKE_VERB(codecAddress, 0, VID_GET_PARAMETER, PID_REVISION_ID);
        verbs[3] = MAKE_VERB(codecAddress, 0, VID_GET_PARAMETER,
                PID_SUB_NODE_COUNT);

        status = hda_send_verbs(codec, verbs, (uint32*)&response, 4);
        if (status != B_OK) {
                ERROR("Failed to get vendor and revision parameters: %s\n",
                        strerror(status));
                goto err;
        }

        codec->vendor_id = response.vendor;
        codec->subsystem_id = response.subsystem;
        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);

        for (uint32 nodeID = response.start;
                        nodeID < response.start + response.count; nodeID++) {
                struct {
                        uint32 groupType;
                        uint32 subsystem;
                } functionResponse;
                verbs[0] = MAKE_VERB(codecAddress, nodeID, VID_GET_PARAMETER,
                        PID_FUNCTION_GROUP_TYPE);
                verbs[1] = MAKE_VERB(codecAddress, nodeID, VID_GET_SUBSYSTEMID, 0);

                if (hda_send_verbs(codec, verbs, (uint32*)&functionResponse, 2) != B_OK) {
                        ERROR("Failed to get function group type\n");
                        goto err;
                }

                if ((functionResponse.groupType & FUNCTION_GROUP_NODETYPE_MASK)
                                == FUNCTION_GROUP_NODETYPE_AUDIO) {
                        // Found an Audio Function Group!
                        if (response.subsystem == 0 && functionResponse.subsystem != 0) {
                                // Update our subsystem, and re-check quirks for this codec
                                codec->subsystem_id = functionResponse.subsystem;
                                hda_codec_get_quirks(codec);
                        }

                        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;
                        }
                }
        }

        TRACE("Codec %" B_PRIu32 " Vendor: %04" B_PRIx16 " Product: %04" B_PRIx16 " "
                "Subsystem: %08" B_PRIx32 ", "
                "Revision: %" B_PRIu8 ".%" B_PRIu8 ".%" B_PRIu8 ".%" B_PRIu8 " "
                "Quirks: %04" B_PRIx32 "\n",
                codecAddress, response.vendor, response.device,
                (uint32)codec->subsystem_id,
                response.major, response.minor, response.revision, response.stepping,
                codec->quirks);

        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;
}