⛏️ index : haiku.git

/*
 * 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
	{ 0x106b, 0x7200, CIRRUSLOGIC_VENDORID, 0x4208,
		HDA_QUIRK_GPIO0, 0},						// MacBookAir 6,2
	{ 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, "%" 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);
	}
}


//	#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");
	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 %" 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);
				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:%" 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]));
				}
				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 %" 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");
	}

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